0%

文章字数:52,阅读全文大约需要1分钟

相比于@Value这个方法更加简便一点

person.properties

1
2
3
person:
lastName: hello
age: 18
1
2
3
4
5
6
7
8
9
10
11
12
13
// 1. 引入配置文件
@PropertySource(value = {"classpath:person.properties"})
@Component
// 2. 加载开头为person的
@ConfigurationProperties(prefix = "person")

public class Person {
// 3. 属性和配置文件的值对应
private String lastName;

private Integer age;

...

文章字数:548,阅读全文大约需要2分钟

归纳自原文

从文件读取信息并发送到其它服务器的过程中普通文件读取和零拷贝的区别

传统方式

  1. File.read(file, buf, len)读取文件内容到缓冲区buf
  2. Socket.send(socket, buf, len)发送数据

看上去数据只经过两次复制: 文件->buf->socket的缓冲区
其实底层步骤是

  1. 调用read()方法,DMA(direct memory access 直接内存存取)会现将数据存储到内核空间的读取缓冲区。
  2. read()方法调用返回时,因为应用程序需要操作此数据(即赋值给buf),因此触发了一次上下文切换(内核态->用户态)。数据被拷贝到了用户地址空间。(cpu需要参与操作)
  3. 调用send()方法,此时又触发了一次上下文切换(用户态->内核态)。buf里的数据被拷贝到与目标套接字相关的内核空间缓冲区,此操作需要cpu参与
  4. send()方法调用返回前会进行最后一次拷贝,由DMA(direct memory access 直接内存存取)将数据从缓冲区传到协议引擎进行发送。

也就是总共进行了四次拷贝操作
dma->cpu->cpu->dma

零拷贝方式

传统方式中cpu的两次操作其实是多余的。我们只是发送数据,不需要对于数据进行操作。所以无需进入用户态,直接在内核态进行数据转移即可。

FileChannel类的transferTo()可以实现将在两个内核缓冲区中搭建一个传递通道,可以将传统方式的两次cpu操作转换成一次cpu操作,即transferTo()

从操作系统角度来看,数据只在内核空间内传递就已经算是零拷贝。
内核需要复制的原因是因为通用硬件DMA访问需要连续的内存空间(因此需要缓冲区)。 但是,如果硬件支持scatter-and-gather,这是可以避免的。
即内部会自动将内核的数据之间发送给套接字引擎。即内核区域只存在一份数据。


文章字数:2051,阅读全文大约需要8分钟

Java.Util.Concurrent是JDK1.5之后推出的java并发工具包。其中有很多多线程工具类,能有效减少竞争条件,减少死锁进程。

线程池

线程池是一组可复用的线程集合。由处理线程+任务队列组成。

涉及对象信息

  1. Executor: 线程池中Runnable任务执行者。
  2. ExecutorService: 管理线程池对象,能够把RunnableCallable交到线程池中执行
  3. Executors: 此类的静态方法能够生成不同的线程池,并返回ExecutorService用于管理。

常用程池类型

可以使用ThreadPoolExecutor 的构造函数创建属性细节不同的线程池,一下为已经定义好的线程池

  1. newFixedThreadPool: 固定大小的线程池,共享的无界队列管理任务。关闭前发生异常导致线程终止,则会使用新线程代替。(如果需要)

  2. newCachedThreadPool: 无界线程池,当需要线程使用但是线程池内无线程,则会新建一个线程并加入线程池。60s未用的线程会被移除。

  3. newSingleThreadExecutor: 无界队列,单个执行线程。如果关闭前因为异常线程被迫结束,后续需要线程时会创建新的线程并替换不可用的。

  4. ThreadPoolExecutor: 这个类的构造方法可以生成自定义配置的线程池,可以设置最大线程数最小线程数空闲线程keepAlive时间

以上线程池生成后返回的都是ExecutorService对象

  1. ExecutorService.submit(): 方法可以提交任务给线程池

  2. ExecutorService.shutdown(): 此方法可以结束线程池

Semaphore计数信号量(许可集合)

可以初始化任意数量的许可。acquire()方法会拿走一个许可,如果没有许可了则阻塞。release()方法可以释放当前线程拿走的许可。限制同一时间可以同时执行的线程数量

10个人抢2个位置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;

public class MySemaphore extends Thread {
private Semaphore position;
private int id;

public MySemaphore(int i, Semaphore s) {
this.id = i;
this.position = s;
}

public void run() {
try {
//这个方法可以判断还有多少许可
if (position.availablePermits() > 0) {
System.out.println("顾客[" + this.id + "]进入厕所,有空位");
}else {
System.out.println("顾客[" + this.id + "]进入厕所,没空位,排队");
}
//获取到空厕所了(没许可等待许可)
position.acquire();
System.out.println("顾客[" + this.id + "]获得坑位");
//使用中...
Thread.sleep((int) (Math.random() * 1000));
System.out.println("顾客[" + this.id + "]使用完毕");
//厕所使用完之后释放
position.release();
}catch (Exception e) {
e.printStackTrace();
}
}

public static void main(String args[]) {
ExecutorService list = Executors.newCachedThreadPool();
Semaphore position = new Semaphore(2);//只有两个厕所
//有十个人
for (int i = 0; i < 10; i++) {
list.submit(new MySemaphore(i + 1, position));
}
list.shutdown();
position.acquireUninterruptibly(2);
System.out.println("使用完毕,需要清扫了");
position.release(2);
}
}

ReentrantLock可重入互斥锁

一个可重入的互斥锁定 Lock,它具有与使用 synchronized 方法和语句所访问的隐式监视器锁定相同的一些基本行为和语义,但功能更强大。

  1. ReentrantLock将由最近成功获得锁定,并且还没有释放该锁定的线程所拥有。

  2. 当锁定没有被另一个线程所拥有时,调用 lock 的线程将成功获取该锁定并返回。

  3. 如果当前线程已经拥有该锁定,此方法将立即返回。可以使用 isHeldByCurrentThread() 和 getHoldCount() 方法来检查此情况是否发生。

  4. 此类的构造方法接受一个可选的公平参数。当设置为 true时,在多个线程的争用下,这些锁定倾向于将访问权授予等待时间最长的线程。否则此锁定将无法保证任何特定访问顺序。

与采用默认设置(使用不公平锁定)相比,使用公平锁定的程序在许多线程访问时表现为很低的总体吞吐量(即速度很慢,常常极其慢),但是在获得锁定和保证锁定分配的均衡性时差异较小。
不过要注意的是,公平锁定不能保证线程调度的公平性。因此,使用公平锁定的众多线程中的一员可能获得多倍的成功机会,这种情况发生在其他活动线程没有被处理并且目前并未持有锁定时。还要注意的是,未定时的 tryLock 方法并没有使用公平设置。因为即使其他线程正在等待,只要该锁定是可用的,此方法就可以获得成功。

基本使用

1
2
3
4
5
6
7
8
9
10
11
12
class X {
private final ReentrantLock lock = new ReentrantLock();
// ...
public void m() {
lock.lock(); // block until condition holds
try {
// ... method body
}finally {
lock.unlock()
}
}
}

CountDownLatch同步辅助类(计数)

初始化锁,指定一个数量。begin.await()会阻塞线程,begin.countDown();每次计数-1,直到为0阻塞的线程才会释放

CyclicBarrier同步辅助类(公共屏障)

锁初始化时指定几个await()之后await()方法才会释放当前线程

例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class TestCyclicBarrier {

// 徒步需要的时间: 分别代表Shenzhen, Guangzhou, Shaoguan, Changsha, Wuhan的时间
private static int[] timeWalk = {5, 8, 15, 15, 10};
// 自驾游
private static int[] timeSelf = {1, 3, 4, 4, 5};
// 旅游大巴
private static int[] timeBus = {2, 4, 6, 6, 7};

static String now() {
SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss");
return sdf.format(new Date()) + ": ";
}

static class Tour implements Runnable {

private int[] times;
private CyclicBarrier barrier;
private String tourName;

public Tour(CyclicBarrier barrier, String tourName, int[] times) {
this.times = times;
this.tourName = tourName;
this.barrier = barrier;
}

public void run() {
try {
Thread.sleep(times[0] * 1000);
System.out.println(now() + tourName + " Reached Shenzhen");
// 等三个线程执行完之后才会释放
barrier.await();
Thread.sleep(times[1] * 1000);
System.out.println(now() + tourName + " Reached Guangzhou");
barrier.await();
Thread.sleep(times[2] * 1000);
System.out.println(now() + tourName + " Reached Shaoguan");
barrier.await();
Thread.sleep(times[3] * 1000);
System.out.println(now() + tourName + " Reached Changsha");
barrier.await();
Thread.sleep(times[4] * 1000);
System.out.println(now() + tourName + " Reached Wuhan");
barrier.await();
} catch (InterruptedException e) {
} catch (BrokenBarrierException e) {
}
}
}

public static void main(String[] args) {
// 三个旅行团(三个await()之后才会释放)
CyclicBarrier barrier = new CyclicBarrier(3);
ExecutorService exec = Executors.newFixedThreadPool(3);
exec.submit(new Tour(barrier, "WalkTour", timeWalk));
exec.submit(new Tour(barrier, "SelfTour", timeSelf));
// 当我们把下面的这段代码注释后,会发现,程序阻塞了,无法继续运行下去。
exec.submit(new Tour(barrier, "BusTour", timeBus));
exec.shutdown();
}
}

阻塞队列

BlockingQueue强化的阻塞队列

支持两个附加操作的 Queue,这两个操作是:检索元素时等待队列变为非空,以及存储元素时等待空间变得可用。

  1. 支持两个附加操作的 Queue,这两个操作是:检索元素时等待队列变为非空,以及存储元素时等待空间变得可用。

  2. BlockingQueue 不接受 null 元素。试图 addputoffer 一个 null 元素时,某些实现会抛出 NullPointerException。null 被用作指示 poll 操作失败的警戒值。

  3. BlockingQueue 可以是限定容量的。它在任意给定时间都可以有一个 remainingCapacity,超出此容量,便无法无阻塞地 put 额外的元素。

  4. 没有任何内部容量约束的 BlockingQueue 总是报告 Integer.MAX_VALUE 的剩余容量。

5.BlockingQueue 实现主要用于生产者-使用者队列,但它另外还支持 Collection 接口。因此,举例来说,使用 remove(x) 从队列中移除任意一个元素是有可能的。(最好不要使用这些操作)

然而,这种操作通常不会有效执行,只能有计划地偶尔使用,比如在取消排队信息时。
BlockingQueue 实现是线程安全的。所有排队方法都可以使用内部锁定或其他形式的并发控制来自动达到它们的目的。
然而,大量的 Collection 操作(addAll、containsAll、retainAll 和 removeAll)没有必要自动执行,除非在实现中特别说明。
因此,举例来说,在只添加了 c 中的一些元素后,addAll(c) 有可能失败(抛出一个异常)。
BlockingQueue 实质上不 支持使用任何一种“close”或“shutdown”操作来指示不再添加任何项。
这种功能的需求和使用有依赖于实现的倾向。例如,一种常用的策略是:对于生产者,插入特殊的 end-of-stream 或 poison 对象,并根据使用者获取这些对象的时间来对它们进行解释。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
import java.util.concurrent.BlockingQueue;    
import java.util.concurrent.ExecutorService;   
import java.util.concurrent.Executors;  
import java.util.concurrent.LinkedBlockingQueue;  
  
public class MyBlockingQueue extends Thread {  
  
    public static BlockingQueue<String> queue = new LinkedBlockingQueue<String>(3);  
    private int index;  
  
    public MyBlockingQueue(int i) {  
        this.index = i;  
    }  
  
    public void run() {  
        try {  
            queue.put(String.valueOf(this.index));  
            System.out.println("{" + this.index + "} in queue!");  
        } catch (Exception e) {  
            e.printStackTrace();  
        }  
    }  
  
    public static void main(String args[]) {  
        ExecutorService service = Executors.newCachedThreadPool();  
        for (int i = 0; i < 10; i++) {  
            service.submit(new MyBlockingQueue(i));  
        }  
  
        Thread thread = new Thread() {  
            public void run() {  
                try {  
                    while (true) {  
                        Thread.sleep((int) (Math.random() * 1000));  
                        if (MyBlockingQueue.queue.isEmpty())  
                            break;  
                        String str = MyBlockingQueue.queue.take();  
                        System.out.println(str + " has take!");  
                    }  
                } catch (Exception e) {  
                    e.printStackTrace();  
                }  
            }  
        };  
        service.submit(thread);  
        service.shutdown();  
    }  
}

线程返回结果

CompletionService包装线程池返回结果

相比于一个个去获取线程的结果Future.get()会造成线程阻塞,消耗时间。循环CompletionService就是一个保存已经完成的线程的结果FutureBlockingQueue,take()方法就会从中取出一个。

循环executorCompletionService.take().get()就能得到结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
import java.util.concurrent.Callable;  
  
import java.util.concurrent.CompletionService;  
  
import java.util.concurrent.ExecutorCompletionService;  
  
import java.util.concurrent.ExecutorService;  
  
import java.util.concurrent.Executors;  
  
public class MyCompletionService implements Callable<String{  
    private int id;  
    public MyCompletionService(int i) {  
        this.id = i;  
    }  
  
    public static void main(String[] args) throws Exception {  
        ExecutorService service = Executors.newCachedThreadPool();  
        CompletionService<String> completion = new ExecutorCompletionService<String>(service);  
        for (int i = 0; i < 10; i++) {  
            completion.submit(new MyCompletionService(i));  
        }  
          
        for (int i = 0; i < 10; i++) {  
            System.out.println(completion.take().get());  
        }  
        service.shutdown();  
    }  
  
    public String call() throws Exception {  
        Integer time = (int) (Math.random() * 1000);  
        try {  
            System.out.println(this.id + " start");  
            Thread.sleep(time);  
            System.out.println(this.id + " end");  
        }catch (Exception e) {  
            e.printStackTrace();  
        }  
        return this.id + ":" + time;  
    }  
  
}

Future异步执行结果

执行线程后返回的Future代表线程返回的结果,用于检测异步线程是否结束,如果结束获取返回值,没结束可以阻塞线程直到有结果。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
import java.util.concurrent.Callable;  
import java.util.concurrent.ExecutionException;  
import java.util.concurrent.ExecutorService;  
import java.util.concurrent.Executors;  
import java.util.concurrent.Future;  
public class MyFutureTask {  
    /** 
     * @param args 
     * @throws InterruptedException  
     * @throws ExecutionException 
     * @throws InterruptedException 
     * @throws ExecutionException  
     */  
    public static void main(String[] args) throws InterruptedException, ExecutionException {  
          
        final ExecutorService exe=Executors.newFixedThreadPool(3);  
        Callable<String> call=new Callable<String>(){  
            public String call() throws InterruptedException {  
                return "Thread is finished";  
            }  
        };  
        Future<String> task=exe.submit(call);  
        String obj=task.get();  
        System.out.println(obj+"进程结束");  
        System.out.println("总进程结束");  
        exe.shutdown();  
    }  
}  
class MyThreadTest implements Runnable {  
    private String str;  
    public MyThreadTest(String str) {  
        this.str = str;  
    }  
    public void run() {  
        this.setStr("allen"+str);  
    }  
    public void addString(String str) {  
        this.str = "allen:" + str;  
    }  
    public String getStr() {  
        return str;  
    }  
    public void setStr(String str) {  
        this.str = str;  
    }  
}

定时器延时器

创建延时任务,取消任务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import static java.util.concurrent.TimeUnit.SECONDS;  
  
import java.util.Date;  
  
import java.util.concurrent.Executors;  
  
import java.util.concurrent.ScheduledExecutorService;  
  
import java.util.concurrent.ScheduledFuture;  
  
public class TestScheduledThread {  
    public static void main(String[] args) {  
        final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(2);  
        final Runnable beeper = new Runnable() {  
            int count = 0;  
            public void run() {  
                System.out.println(new Date() + " beep " + (++count));  
            }  
        };  
  
        // 1秒钟后运行,并每隔2秒运行一次  
        final ScheduledFuture beeperHandle = scheduler.scheduleAtFixedRate(beeper, 12, SECONDS);  
        // 2秒钟后运行,并每次在上次任务运行完后等待5秒后重新运行  
        final ScheduledFuture beeperHandle2 = scheduler.scheduleWithFixedDelay(beeper, 25, SECONDS);  
        // 30秒后结束关闭任务,并且关闭Scheduler  
        scheduler.schedule(new Runnable() {  
            public void run() {  
                beeperHandle.cancel(true);  
                beeperHandle2.cancel(true);  
                scheduler.shutdown();  
            }  
  
        }, 30, SECONDS);   
    }   
}

文章字数:827,阅读全文大约需要3分钟

  • maven
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
      <dependency>
<groupId>org.bytedeco</groupId>
<artifactId>javacpp</artifactId>
<version>1.5.5</version>
</dependency>
<dependency>
<groupId>org.bytedeco</groupId>
<artifactId>javacv</artifactId>
<version>1.5.5</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.bytedeco/ffmpeg -->
<dependency>
<groupId>org.bytedeco</groupId>
<artifactId>ffmpeg</artifactId>
<version>4.3.2-1.5.5</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.bytedeco/ffmpeg-platform -->
<dependency>
<groupId>org.bytedeco</groupId>
<artifactId>ffmpeg-platform</artifactId>
<version>4.3.2-1.5.5</version>
</dependency>

<!-- https://mvnrepository.com/artifact/org.bytedeco/opencv -->
<dependency>
<groupId>org.bytedeco</groupId>
<artifactId>opencv</artifactId>
<version>4.5.1-1.5.5</version>
</dependency>
<dependency>
<groupId>org.bytedeco</groupId>
<artifactId>opencv-platform</artifactId>
<version>4.5.1-1.5.5</version>
</dependency>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
package org.example.util;

import org.bytedeco.ffmpeg.*;
import org.bytedeco.ffmpeg.global.avcodec;
import org.bytedeco.javacv.*;
import org.bytedeco.javacv.Frame;
import org.bytedeco.opencv.*;
import org.opencv.core.*;
import org.opencv.core.Point;
import org.opencv.imgcodecs.Imgcodecs;
import org.opencv.imgproc.Imgproc;
import org.opencv.objdetect.CascadeClassifier;

import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.awt.image.DataBufferByte;
import java.io.*;
import java.util.*;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Consumer;

/**
* 视频工具
*
* @author colin.cheng
* @since 1.0.0
*/
public class VidUtil {
/** 人脸分类器地址 */
static final String HAARCASCADES = "F:\\test\\vid\\haarcascade_frontalface_alt_tree.xml";
/** 分类器 */
static CascadeClassifier cascade;

static {
new opencv_java();
new ffmpeg();
cascade = new CascadeClassifier(HAARCASCADES);
}

/**
* 根据视频获取帧信息
*
* @param vidPath 地址
* @param interval 获取帧的间隔
* @param doFunc 每帧如何操作
* @param indexFunc 当前执行帧数回调
* @param totalFunc 总帧数回调
* @throws FrameGrabber.Exception
*/
public static void getFrameMatByVideo(String vidPath, int interval, Consumer<Mat> doFunc, Consumer<Integer> indexFunc, Consumer<Integer> totalFunc) throws FrameGrabber.Exception {
try(FFmpegFrameGrabber fFmpegFrameGrabber = new FFmpegFrameGrabber(vidPath)) {
fFmpegFrameGrabber.start();
int totalIndex = fFmpegFrameGrabber.getLengthInFrames();
if(totalFunc != null) {
totalFunc.accept(totalIndex);
}
Frame frame;
final OpenCVFrameConverter.ToOrgOpenCvCoreMat converter = new OpenCVFrameConverter.ToOrgOpenCvCoreMat();
for (int i = 0; i < totalIndex; i += interval) {
if(indexFunc != null) {
indexFunc.accept(i);
}
fFmpegFrameGrabber.setFrameNumber(i);
frame = fFmpegFrameGrabber.grabImage();
doFunc.accept(converter.convert(frame));
}
}
}

/**
* 根据视频获取帧信息
*
* @param vidPath 地址
* @param interval 获取帧的间隔
* @param doFunc 每帧如何操作
* @throws FrameGrabber.Exception
*/
public static void getFrameMatByVideo(String vidPath, int interval, Consumer<Mat> doFunc) throws FrameGrabber.Exception {
getFrameMatByVideo(vidPath, interval, doFunc, null, null);
}

/**
* 将Mat保存成图片
*
* @param path 图片路径 + 名称
* @param mat Mat信息
*/
public static void saveMatAsImg(String path, Mat mat) {
Imgcodecs.imwrite(path, mat);
}

/**
* 根据图片获取Mat
*
* @param path
* @return
*/
public static Mat getMatFromImg(String path) {
return Imgcodecs.imread(path);
}

/**
* 复制Mat
*
* @param from
* @return
*/
public static Mat copy(Mat from) {
Mat to = new Mat();
from.copyTo(to);
return to;
}

/**
* 检测人脸,并返回位置
*
* @param srcImage
* @return
*/
public static MatOfRect faceDetect(Mat srcImage) {
Mat grayImage = new Mat();
Imgproc.cvtColor(srcImage, grayImage, Imgproc.COLOR_BGR2GRAY);
MatOfRect faceDetections = new MatOfRect();
cascade.detectMultiScale(grayImage, faceDetections);
return faceDetections;
}

/**
* 三帧法检测运动区域
* @param previousFrame 第一帧
* @param currentFrame 第二帧
* @param frame3 第三帧
* @return 运动区域
*/
public static Mat mobileMonitoring(Mat previousFrame, Mat currentFrame, Mat frame3) {
Imgproc.cvtColor(previousFrame, previousFrame, Imgproc.COLOR_BGR2GRAY);
Imgproc.cvtColor(currentFrame, currentFrame, Imgproc.COLOR_BGR2GRAY);
Imgproc.cvtColor(frame3, frame3, Imgproc.COLOR_BGR2GRAY);

Mat difFrame = new Mat(), difFrame2 = new Mat(), difFrame3 = new Mat(), tempFrame = new Mat();

Core.absdiff(currentFrame, previousFrame, difFrame);
Core.absdiff(previousFrame, frame3, difFrame2);
Core.bitwise_and(difFrame, difFrame2, difFrame3);

Imgproc.threshold(difFrame3, tempFrame, 20, 255.0, Imgproc.THRESH_BINARY);
Imgproc.dilate(tempFrame, tempFrame, new Mat());
Imgproc.erode(tempFrame, tempFrame, new Mat());

return tempFrame;
}

/**
* 三帧法运动检测
* @param previousFrame
* @param currentFrame
* @param frame3
* @return
*/
public static boolean isMove(Mat previousFrame, Mat currentFrame, Mat frame3) {
final ArrayList<MatOfPoint> list = new ArrayList<>();
Mat hierarchy = new Mat();
Mat moveMat = mobileMonitoring(previousFrame, currentFrame, frame3);
/**
* mode:
* RETR_EXTERNAL 只检测最外围的轮廓。
* RETR_LIST 检测所有轮廓,不建立等级关系,彼此独立。
* RETR_CCOMP 检测所有轮廓,但所有轮廓都只建立两个等级关系 。
* RETR_TREE 检测所有轮廓,并且所有轮廓建立一个树结构,层次完整。
* RETR_FLOODFILL 洪水填充法
*
* method:
* CHAIN_APPROX_NONE 保存物体边界上所有连续的轮廓点
* CHAIN_APPROX_SIMPLE 压缩水平方向,垂直方向,对角线方向的元素,只保留该方向的终点坐标,例如一个矩形轮廓只需4个点来保存轮廓信息
* CV_CHAIN_APPROX_TC89_L1 使用Teh-Chin 链近似算法
* CV_CHAIN_APPROX_TC89_KCOS 使用Teh-Chin 链近似算法
*/
Imgproc.findContours(moveMat, list, hierarchy, Imgproc.RETR_EXTERNAL, Imgproc.CHAIN_APPROX_SIMPLE);
for (MatOfPoint point : list) {
final double contour = Imgproc.contourArea(point);
if(contour > 1000) {
return true;
}
}
return false;
}

/**
* 三帧法运动检测并画框
* @param previousFrame
* @param currentFrame
* @param frame3
* @param path
*/
public static void moveRectangle(Mat previousFrame, Mat currentFrame, Mat frame3, String path) {
Mat baseFrame = new Mat();
currentFrame.copyTo(baseFrame);
final ArrayList<MatOfPoint> list = new ArrayList<>();
Mat hierarchy = new Mat();
Mat moveMat = mobileMonitoring(previousFrame, currentFrame, frame3);
Imgproc.findContours(moveMat, list, hierarchy, Imgproc.RETR_EXTERNAL, Imgproc.CHAIN_APPROX_SIMPLE);
for (MatOfPoint point : list) {
final MatOfPoint2f matOfPoint2f = new MatOfPoint2f(point.toArray());
final RotatedRect rotatedRect = Imgproc.minAreaRect(matOfPoint2f);
final Rect rect = rotatedRect.boundingRect();
final double contour = Imgproc.contourArea(point);
if(contour > 1000) {
Imgproc.rectangle(baseFrame, new Point(rect.x, rect.y), new Point(rect.x + rect.width, rect.y + rect.height),new Scalar(0, 0, 255), 2);
}
}
saveMatAsImg(path, baseFrame);
}

/**
* 人脸画框
*
* @param srcImage
*/
public static Mat faceRectangle(Mat srcImage) {
MatOfRect faceDetections = faceDetect(srcImage);
Mat dstImage = new Mat();
srcImage.copyTo(dstImage);
for (Rect rect : faceDetections.toArray()) {
Imgproc.rectangle(dstImage, new Point(rect.x, rect.y), new Point(rect.x + rect.width, rect.y + rect.height),new Scalar(0, 0, 255), 2);
}
return dstImage;
}

/**
* 切割人脸
*
* @param srcImage
* @return
*/
public static List<Mat> cutFace(Mat srcImage) {
Rect[] rectArr = faceDetect(srcImage).toArray();
final LinkedList<Mat> res = new LinkedList<>();
if(rectArr.length > 0) {
for (Rect rect : rectArr) {
res.add(new Mat(srcImage, rect));
}
}
return res;
}

/**
* 切割第一张人脸
*
* @param srcImage
* @return
*/
public static Mat cutFirstFace(Mat srcImage) {
Rect[] rectArr = faceDetect(srcImage).toArray();
if(rectArr.length > 0) {
return new Mat(srcImage, rectArr[0]);
}
return null;
}


/**
* 使用直方图比对照片
* @param img1
* @param img2
* @return
*/
public static int compareHistogram(Mat img1, Mat img2) {
int retVal = 0;
if (null != img1 && null != img2) {
Mat hsvImg1 = new Mat();
Mat hsvImg2 = new Mat();
// Convert to HSV 转换为彩色模型
Imgproc.cvtColor(img1, hsvImg1, Imgproc.COLOR_BGR2HSV);
Imgproc.cvtColor(img2, hsvImg2, Imgproc.COLOR_BGR2HSV);
// Set configuration for calchist()
List<Mat> listImg1 = new ArrayList<Mat>();
List<Mat> listImg2 = new ArrayList<Mat>();
listImg1.add(hsvImg1);
listImg2.add(hsvImg2);
MatOfFloat ranges = new MatOfFloat(0, 255);
MatOfInt histSize = new MatOfInt(50);
MatOfInt channels = new MatOfInt(0);
// Histograms 直方图
Mat histImg1 = new Mat();
Mat histImg2 = new Mat();
// Calculate the histogram for the HSV imgaes
// 计算HSV imgaes的直方图。
Imgproc.calcHist(listImg1, channels, new Mat(), histImg1, histSize, ranges);
Imgproc.calcHist(listImg2, channels, new Mat(), histImg2, histSize, ranges);
Core.normalize(histImg1, histImg1, 0, 1, Core.NORM_MINMAX, -1, new Mat());
Core.normalize(histImg2, histImg2, 0, 1, Core.NORM_MINMAX, -1, new Mat());
// Apply the histogram comparison methods
// 0 - correlation: the higher the metric, the more accurate the
// match
// "> 0.9"
// 1 - chi-square: the lower the metric, the more accurate the match
// "<
// 0.1"
// 2 - intersection: the higher the metric, the more accurate the
// match
// "> 1.5"
// 3 - bhattacharyya: the lower the metric, the more accurate the
// match
// "< 0.3"
double result0, result1, result2, result3;
result0 = Imgproc.compareHist(histImg1, histImg2, 0);
result1 = Imgproc.compareHist(histImg1, histImg2, 1);
result2 = Imgproc.compareHist(histImg1, histImg2, 2);
result3 = Imgproc.compareHist(histImg1, histImg2, 3);
// If the count that it is satisfied with the condition is over 3,
// two
// images is same.
int count = 0;
if (result0 > 0.9) {
count++;
}
if (result1 < 0.1) {
count++;
}
if (result2 > 1.5) {
count++;
}
if (result3 < 0.3) {
count++;
}
// 对相似度进行评判
if (count >= 3) {
retVal = 1;
}
}

return retVal;
}

/**
* Mat转换成BufferedImage
*
* @param matrix
* 要转换的Mat
* @param fileExtension
* 格式为 ".jpg", ".png", etc
* @return
*/
public static BufferedImage Mat2BufImg (Mat matrix, String fileExtension) {
MatOfByte mob = new MatOfByte();
Imgcodecs.imencode(fileExtension, matrix, mob);
byte[] byteArray = mob.toArray();
BufferedImage bufImage = null;
try {
InputStream in = new ByteArrayInputStream(byteArray);
bufImage = ImageIO.read(in);
} catch (Exception e) {
e.printStackTrace();
}
return bufImage;
}

/**
* BufferedImage转换成Mat
*
* @param original
* 要转换的BufferedImage
* @param imgType
* bufferedImage的类型 如 BufferedImage.TYPE_3BYTE_BGR
* @param matType
* 转换成mat的type 如 CvType.CV_8UC3
*/
public static Mat BufImg2Mat (BufferedImage original, int imgType, int matType) {
if (original == null) {
throw new IllegalArgumentException("original == null");
}
// Don't convert if it already has correct type
if (original.getType() != imgType) {
// Create a buffered image
BufferedImage image = new BufferedImage(original.getWidth(), original.getHeight(), imgType);
// Draw the image onto the new buffer
Graphics2D g = image.createGraphics();
try {
g.setComposite(AlphaComposite.Src);
g.drawImage(original, 0, 0, null);
} finally {
g.dispose();
}
}
byte[] pixels = ((DataBufferByte) original.getRaster().getDataBuffer()).getData();
Mat mat = Mat.eye(original.getHeight(), original.getWidth(), matType);
mat.put(0, 0, pixels);
return mat;
}



public static void main(String[] args) {
VidUtil util = new VidUtil();
util.getMove("F:\\test\\vid\\2.mp4", "F:\\test\\vid\\img\\res.flv");

}

/**
* 提取移动的信息
* @param vidPath
* @param outputFile
*/
private void getMove(String vidPath, String outputFile) {
try(FFmpegFrameGrabber fFmpegFrameGrabber = new FFmpegFrameGrabber(vidPath)) {
fFmpegFrameGrabber.start();

FrameRecorder recorder = FrameRecorder.createDefault(outputFile, fFmpegFrameGrabber.getImageWidth(), fFmpegFrameGrabber.getImageHeight());
recorder.setVideoCodec(avcodec.AV_CODEC_ID_H264);
recorder.setFormat("flv");
recorder.setFrameRate(fFmpegFrameGrabber.getFrameRate());
recorder.start();

final OpenCVFrameConverter.ToOrgOpenCvCoreMat converter = new OpenCVFrameConverter.ToOrgOpenCvCoreMat();
Mat frame1 = null, frame2 = null;
final int total = fFmpegFrameGrabber.getLengthInFrames();
for (int i = 0; i < total; i++) {
System.out.println(i + "/" + total);
Frame frame = fFmpegFrameGrabber.grab();
Mat mat = converter.convert(frame);

if(frame1 == null) {
frame1 = new Mat();
mat.copyTo(frame1);
} else if(frame2 == null) {
frame2 = new Mat();
mat.copyTo(frame2);
} else {
Mat f1 = new Mat(), f2 = new Mat(), f3 = new Mat();
frame1.copyTo(f1);
frame2.copyTo(f2);
mat.copyTo(f3);
boolean move = VidUtil.isMove(f1, f2, f3);
if(move) {
recorder.record(frame);
}
frame2.copyTo(frame1);
mat.copyTo(frame2);
}
}
recorder.stop();
recorder.release();
fFmpegFrameGrabber.stop();
fFmpegFrameGrabber.release();
} catch (Exception e) {
e.printStackTrace();
}
}


/**
* 推送rmtp服务器
*
* @param outputFile 服务器地址
* @param frameRate
* @throws Exception
* @throws InterruptedException
* @throws org.bytedeco.javacv.FrameRecorder.Exception
*/
public static void recordCamera(String outputFile, double frameRate)throws Exception {
Loader.load(opencv_objdetect.class);
FrameGrabber grabber = FrameGrabber.createDefault(0);
grabber.start();//开启抓取器

OpenCVFrameConverter.ToIplImage converter = new OpenCVFrameConverter.ToIplImage();
IplImage grabbedImage = converter.convert(grabber.grab());
int width = grabbedImage.width();
int height = grabbedImage.height();

FrameRecorder recorder = FrameRecorder.createDefault(outputFile, width, height);
recorder.setVideoCodec(avcodec.AV_CODEC_ID_H264);
recorder.setFormat("flv");
recorder.setFrameRate(frameRate);

recorder.start();//开启录制器
long startTime=0;
long videoTS=0;
CanvasFrame frame = new CanvasFrame("camera", CanvasFrame.getDefaultGamma() / grabber.getGamma());
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setAlwaysOnTop(true);
Frame rotatedFrame=converter.convert(grabbedImage);
while (frame.isVisible() && (grabbedImage = converter.convert(grabber.grab())) != null) {
rotatedFrame = converter.convert(grabbedImage);
frame.showImage(rotatedFrame);
if (startTime == 0) {
startTime = System.currentTimeMillis();
}
videoTS = 1000 * (System.currentTimeMillis() - startTime);
recorder.setTimestamp(videoTS);
recorder.record(rotatedFrame);
Thread.sleep(40);
}
frame.dispose();
recorder.stop();
recorder.release();
grabber.stop();
}
}

文章字数:1467,阅读全文大约需要5分钟

javassl加密方式连接mqtt服务器。其它ssl加密的也可以参考,SSLSocketFactory获取部分都是一样的。踩了很多坑,根据生成工具不同(opensslkeytool)以及秘钥文件编码不同有若干种方法。这里把自己遇到的所有情况都统一记录一下。

一、连接MQTT服务器

不加密的连接方式之前有写过,就不赘述了,这里列出不同的地方

1
2
3
4
5
mqttClient = new MqttClient(host, clientId, new MemoryPersistence());
MqttConnectOptions options = new MqttConnectOptions();
options.setCleanSession(true);
// 这里多了一步设置SSLSocketFactory的步骤
options.setSocketFactory(SslUtil.getSocketFactoryByCert(caPath,certPath,privateKeyPath, privateKeyPwd));

SSLSocketFactory获取方式有两种:

  1. 通过CA证书、客户端证书、客户端私钥、私钥密码 获取(使用openssl生成的,keytool能生成证书,但是不能直接导出秘钥文件)
  2. 直接通过keystoretruststore获取(通过keytool生成的)

读取证书和秘钥也有两种方式(证书获取的方式)

  1. 使用bcpkix-jdk15on包提供的方法,需要引包
  2. 使用原生方法,但是不支持直接读取pem秘钥文件,需要先把文件PKCS8编码一下。(编码方法在openssl的文章里)

稍微解释一下上面的两种方式

  • 第一种,通过证书的方式
  1. CA证书是用来验证服务端发过来的证书,因为这里是双向认证,所以需要CA证书来认证服务端发过来的是否是合法证书。
  2. 客户端证书,发给服务端,让服务端验证的。(需要用CA证书签发,这样服务端那边才能用CA证书验证合法)
  3. 客户端私钥,服务端拿到客户端证书后会用证书里的公钥加密信息发过来,需要用私钥解密拿到原信息
  4. 私钥密码,openssl生成私钥的时候设置的密码(具体生成方式之前的文章有)
  • 第二种,通过keystoretruststore
  1. keystore是用jdk自带的工具keytool生成的秘钥和证书管理库,用来保存自己的秘钥和证书。需要用keytool生成并导入客户端的证书和秘钥。具体使用之前有文章可以参考。
  2. truststore本质也是keystore,只是里面存的是受信的证书。用来验证服务端证书是否可信,将CA导入即可
  3. 第一种方式本质也是通过keystoretruststore验证,只不过导入的步骤用代码实现了,第二种方式使用命令实现的。

二、SslUtil具体实现

  1. 导入依赖
    1
    2
    3
    4
    5
    <dependency>
    <groupId>org.bouncycastle</groupId>
    <artifactId>bcpkix-jdk15on</artifactId>
    <version>1.47</version>
    </dependency>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128

import java.io.*;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.*;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.Base64;

import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManagerFactory;

import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.openssl.PEMReader;

/***
* 两种方式验证
* @author colin
* @date 2021-02-03 14:39
* @since 1.0.0
*/
public class SslUtil {

/**
* 用证书和私钥配置sslContext
*
* @param caCrtFile
* CA证书(验证连接)
* @param crtFile
* 发给对方的证书
* @param keyFile
* pem 私钥(请求连接的消息是用公钥加密的,需要用私钥解密)
* @param password
* 私钥密码
* @return
* @throws Exception
*/
public static SSLSocketFactory getSocketFactoryByCert(final String caCrtFile, final String crtFile,
final String keyFile, final String password) throws Exception {
Security.addProvider(new BouncyCastleProvider());

// 加载CA证书(用于验证的根证书)
PEMReader reader =
new PEMReader(new InputStreamReader(new ByteArrayInputStream(Files.readAllBytes(Paths.get(caCrtFile)))));
X509Certificate caCert = (X509Certificate)reader.readObject();
reader.close();

// 加载自己的证书,用于发送给客户端
reader = new PEMReader(new InputStreamReader(new ByteArrayInputStream(Files.readAllBytes(Paths.get(crtFile)))));
X509Certificate cert = (X509Certificate)reader.readObject();
reader.close();

// 加载私钥
reader = new PEMReader(new InputStreamReader(new ByteArrayInputStream(Files.readAllBytes(Paths.get(keyFile)))),
() -> password.toCharArray());
KeyPair key = (KeyPair)reader.readObject();
reader.close();

// 用CA证书创建TrustManagerFactory
KeyStore caKs = KeyStore.getInstance(KeyStore.getDefaultType());
caKs.load(null, null);
caKs.setCertificateEntry("ca-certificate", caCert);
TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmf.init(caKs);

// 用证书和私钥创建KeyManagerFactory
KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
ks.load(null, null);
ks.setCertificateEntry("certificate", cert);
ks.setKeyEntry("private-key", key.getPrivate(), password.toCharArray(),
new java.security.cert.Certificate[] {cert});
KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
kmf.init(ks, password.toCharArray());

SSLContext context = SSLContext.getInstance("TLSv1");
// kmf用于发送关键信息让服务端校验,tmf用于校验服务端的证书。双向认证
context.init(kmf.getKeyManagers(), tmf.getTrustManagers(), new SecureRandom());
return context.getSocketFactory();
}

/**
* 通过keyStore加载
*
* @param keyStorePath
* keystore路径(保存自己的秘钥和证书)
* @param trustKeyStorePath
* truststore路径(保存受信的证书)
* @param ksPass
* keystore密码
* @param tsPass
* truststore密码
* @return
* @throws Exception
*/
public static SSLSocketFactory getSocketFactoryByKeystore(String keyStorePath, String trustKeyStorePath,
String ksPass, String tsPass) throws Exception {
// keytool生成的keystore的类型就是JKS
KeyStore keyStore = KeyStore.getInstance("JKS");
KeyStore trustKeyStore = KeyStore.getInstance("JKS");
// 通过密码加载keystore
FileInputStream fis = new FileInputStream(keyStorePath);
keyStore.load(fis, ksPass.toCharArray());
fis.close();
// 加载trustKeyStore
FileInputStream trustFis = new FileInputStream(trustKeyStorePath);
trustKeyStore.load(trustFis, tsPass.toCharArray());
trustFis.close();
// 创建管理JKS密钥库的密钥管理器 (SunX509)
KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
// 使用密钥内容源初始化此工厂。 提供者通常使用 KeyStore 来获取在安全套接字协商期间所使用的密钥内容
kmf.init(keyStore, ksPass.toCharArray());
// SunX509
TrustManagerFactory tmFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmFactory.init(trustKeyStore);

// 初始sslcontext
SSLContext sslContext = SSLContext.getInstance("SSLv3");
// SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(kmf.getKeyManagers(), tmFactory.getTrustManagers(), new SecureRandom());
return sslContext.getSocketFactory();
}

}

三、不引包的方式

  1. pem秘钥文件pkcs8编码
1
openssl pkcs8 -topk8 -in client.private.pem -out pkcs8.client.private.pem -nocrypt
  1. 代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
/**
* 用证书和私钥配置sslContext
*
* @param caCrtFile
* CA证书(验证连接)
* @param crtFile
* 发给对方的证书
* @param keyFile
* 私钥(请求连接的消息是用公钥加密的,需要用私钥解密)
* @param password
* 私钥密码
* @return
* @throws Exception
*/
public static SSLSocketFactory getSocketFactoryByCert(final String caCrtFile, final String crtFile,
final String keyFile, final String password) throws Exception {
// 加载CA证书(用于验证的根证书)
X509Certificate caCert = getCertificate(caCrtFile);
// 加载自己的证书,用于发送给客户端
X509Certificate cert = getCertificate(crtFile);
// 加载私钥
final PrivateKey privateKey = getPrivateKey(keyFile);
// 用CA证书创建TrustManagerFactory
KeyStore caKs = KeyStore.getInstance(KeyStore.getDefaultType());
caKs.load(null, null);
caKs.setCertificateEntry("ca-certificate", caCert);
TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmf.init(caKs);

// 用证书和私钥创建KeyManagerFactory
KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
ks.load(null, null);
ks.setCertificateEntry("certificate", cert);
ks.setKeyEntry("private-key", privateKey, password.toCharArray(), new java.security.cert.Certificate[] {cert});
KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
kmf.init(ks, password.toCharArray());

SSLContext context = SSLContext.getInstance("TLSv1");
// kmf用于发送关键信息让服务端校验,tmf用于校验服务端的证书。双向认证
context.init(kmf.getKeyManagers(), tmf.getTrustManagers(), new SecureRandom());
return context.getSocketFactory();
}

/**
* 读取x509格式的证书
*
* @param certPath
* @return
* @throws FileNotFoundException
* @throws CertificateException
*/
private static X509Certificate getCertificate(String certPath) throws FileNotFoundException, CertificateException {
InputStream inStream = new FileInputStream(certPath);
CertificateFactory cf = CertificateFactory.getInstance("X.509");
X509Certificate caCert = (X509Certificate)cf.generateCertificate(inStream);
inStream.close();
return caCert;
}

/**
* 读取 PKCS8 编码的 RSA 秘钥文件
*
* @param path
* @return
* @throws IOException
* @throws NoSuchAlgorithmException
* @throws InvalidKeySpecException
*/
private static PrivateKey getPrivateKey(String path)
throws IOException, NoSuchAlgorithmException, InvalidKeySpecException {
BufferedReader br = new BufferedReader(new FileReader(path));
String s = br.readLine();
String str = "";
s = br.readLine();
while (s.charAt(0) != '-') {
str += s + "\r";
s = br.readLine();
}
// BASE64Decoder base64decoder = new BASE64Decoder();
byte[] bytes = Base64.getMimeDecoder().decode(str);
// byte[] bytes = base64decoder.decodeBuffer(str);
br.close();
// 生成私钥
KeyFactory kf = KeyFactory.getInstance("RSA");
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(bytes);
PrivateKey privateKey = kf.generatePrivate(keySpec);
return privateKey;
}

发现项目中有生成好的p12证书,可以直接使用。这里再追加一种p12证书和CA证书验证的方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
/**
* 通过p12证书和ca证书双向认证
*
* @param caCrtFile
* @param p12Keystore
* @param p12Pwd
* @return
* @throws Exception
*/
public static SSLSocketFactory getSocketFactoryByP12AndCA(String caCrtFile, String p12Keystore, String p12Pwd)
throws Exception {
// 加载CA证书(用于验证的根证书)
X509Certificate caCert = getCertificate(caCrtFile);
// 用CA证书创建TrustManagerFactory
KeyStore caKs = KeyStore.getInstance(KeyStore.getDefaultType());
caKs.load(null, null);
caKs.setCertificateEntry("ca-certificate", caCert);
TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmf.init(caKs);

KeyStore keyStore = KeyStore.getInstance("pkcs12");
FileInputStream p12Fis = new FileInputStream(p12Keystore);
keyStore.load(p12Fis , p12Pwd.toCharArray());
p12Fis.close();
// 创建管理JKS密钥库的密钥管理器 (SunX509)
KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
// 使用密钥内容源初始化此工厂。 提供者通常使用 KeyStore 来获取在安全套接字协商期间所使用的密钥内容
kmf.init(keyStore, p12Pwd.toCharArray());

// 初始sslcontext
SSLContext sslContext = SSLContext.getInstance("SSLv3");
sslContext.init(kmf.getKeyManagers(), tmf.getTrustManagers(), new SecureRandom());
return sslContext.getSocketFactory();
}
  • 单向认证,即不认证服务端的证书
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 自定义一个不验证的TrustManager 即可
final TrustManager trustManager = new X509TrustManager(){
@Override
public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {

}

@Override
public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {

}

@Override
public X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[0];
}
};
sslContext.init(kmf.getKeyManagers(), new TrustManager[] {trustManager}, new SecureRandom());
  • 不验证证书及ip是否匹配
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
final TrustManager trustManager = new X509ExtendedTrustManager(){

@Override
public void checkClientTrusted(X509Certificate[] chain, String authType, Socket socket) throws CertificateException {

}

@Override
public void checkServerTrusted(X509Certificate[] chain, String authType, Socket socket) throws CertificateException {

}

@Override
public void checkClientTrusted(X509Certificate[] chain, String authType, SSLEngine engine) throws CertificateException {

}

@Override
public void checkServerTrusted(X509Certificate[] chain, String authType, SSLEngine engine) throws CertificateException {

}

@Override
public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {

}

@Override
public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {

}

@Override
public X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[0];
}
};
sslContext.init(kmf.getKeyManagers(), new TrustManager[] {trustManager}, new SecureRandom());

文章字数:492,阅读全文大约需要1分钟

VerifyCodeUtil 代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Arrays;
import java.util.Random;
import javax.imageio.ImageIO;

public class VerifyCodeUtil {
//使用到Algerian字体,系统里没有的话需要安装字体,字体只显示大写,去掉了1,0,i,o几个容易混淆的字符

public static final String VERIFY_CODES = "23456789ABCDEFGHJKLMNPQRSTUVWXYZ";
private static Random random = new Random();


/**
* 使用系统默认字符源生成验证码
* @param verifySize 验证码长度
* @return
*/
public static String generateVerifyCode(int verifySize){
return generateVerifyCode(verifySize, VERIFY_CODES);
}
/**
* 使用指定源生成验证码
* @param verifySize 验证码长度
* @param sources 验证码字符源
* @return
*/
public static String generateVerifyCode(int verifySize, String sources){
if(sources == null || sources.length() == 0){
sources = VERIFY_CODES;
}
int codesLen = sources.length();
Random rand = new Random(System.currentTimeMillis());
StringBuilder verifyCode = new StringBuilder(verifySize);
for(int i = 0; i < verifySize; i++){
verifyCode.append(sources.charAt(rand.nextInt(codesLen-1)));
}
return verifyCode.toString();
}

/**
* 生成随机验证码文件,并返回验证码值
* @param w
* @param h
* @param outputFile
* @param verifySize
* @return
* @throws IOException
*/
public static String outputVerifyImage(int w, int h, File outputFile, int verifySize) throws IOException{
String verifyCode = generateVerifyCode(verifySize);
outputImage(w, h, outputFile, verifyCode);
return verifyCode;
}

/**
* 输出随机验证码图片流,并返回验证码值
* @param w
* @param h
* @param os
* @param verifySize
* @return
* @throws IOException
*/
public static String outputVerifyImage(int w, int h, OutputStream os, int verifySize) throws IOException{
String verifyCode = generateVerifyCode(verifySize);
outputImage(w, h, os, verifyCode);
return verifyCode;
}

/**
* 生成指定验证码图像文件
* @param w
* @param h
* @param outputFile
* @param code
* @throws IOException
*/
public static void outputImage(int w, int h, File outputFile, String code) throws IOException{
if(outputFile == null){
return;
}
File dir = outputFile.getParentFile();
if(!dir.exists()){
dir.mkdirs();
}
try{
outputFile.createNewFile();
FileOutputStream fos = new FileOutputStream(outputFile);
outputImage(w, h, fos, code);
fos.close();
} catch(IOException e){
throw e;
}
}

/**
* 输出指定验证码图片流
* @param w
* @param h
* @param os
* @param code
* @throws IOException
*/
public static void outputImage(int w, int h, OutputStream os, String code) throws IOException{
int verifySize = code.length();
BufferedImage image = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB);
Random rand = new Random();
Graphics2D g2 = image.createGraphics();
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,RenderingHints.VALUE_ANTIALIAS_ON);
Color[] colors = new Color[5];
Color[] colorSpaces = new Color[] { Color.WHITE, Color.CYAN,
Color.GRAY, Color.LIGHT_GRAY, Color.MAGENTA, Color.ORANGE,
Color.PINK, Color.YELLOW };
float[] fractions = new float[colors.length];
for(int i = 0; i < colors.length; i++){
colors[i] = colorSpaces[rand.nextInt(colorSpaces.length)];
fractions[i] = rand.nextFloat();
}
Arrays.sort(fractions);

g2.setColor(Color.GRAY);// 设置边框色
g2.fillRect(0, 0, w, h);

Color c = getRandColor(200, 250);
g2.setColor(c);// 设置背景色
g2.fillRect(0, 2, w, h-4);

//绘制干扰线
Random random = new Random();
g2.setColor(getRandColor(160, 200));// 设置线条的颜色
for (int i = 0; i < 20; i++) {
int x = random.nextInt(w - 1);
int y = random.nextInt(h - 1);
int xl = random.nextInt(6) + 1;
int yl = random.nextInt(12) + 1;
g2.drawLine(x, y, x + xl + 40, y + yl + 20);
}

// 添加噪点
float yawpRate = 0.05f;// 噪声率
int area = (int) (yawpRate * w * h);
for (int i = 0; i < area; i++) {
int x = random.nextInt(w);
int y = random.nextInt(h);
int rgb = getRandomIntColor();
image.setRGB(x, y, rgb);
}

shear(g2, w, h, c);// 使图片扭曲

g2.setColor(getRandColor(100, 160));
int fontSize = h-4;
Font font = new Font("Algerian", Font.ITALIC, fontSize);
g2.setFont(font);
char[] chars = code.toCharArray();
for(int i = 0; i < verifySize; i++){
AffineTransform affine = new AffineTransform();
affine.setToRotation(Math.PI / 4 * rand.nextDouble() * (rand.nextBoolean() ? 1 : -1), (w / verifySize) * i + fontSize/2, h/2);
g2.setTransform(affine);
g2.drawChars(chars, i, 1, ((w-10) / verifySize) * i + 5, h/2 + fontSize/2 - 10);
}

g2.dispose();
ImageIO.write(image, "jpg", os);
}

private static Color getRandColor(int fc, int bc) {
if (fc > 255)
fc = 255;
if (bc > 255)
bc = 255;
int r = fc + random.nextInt(bc - fc);
int g = fc + random.nextInt(bc - fc);
int b = fc + random.nextInt(bc - fc);
return new Color(r, g, b);
}

private static int getRandomIntColor() {
int[] rgb = getRandomRgb();
int color = 0;
for (int c : rgb) {
color = color << 8;
color = color | c;
}
return color;
}

private static int[] getRandomRgb() {
int[] rgb = new int[3];
for (int i = 0; i < 3; i++) {
rgb[i] = random.nextInt(255);
}
return rgb;
}

private static void shear(Graphics g, int w1, int h1, Color color) {
shearX(g, w1, h1, color);
shearY(g, w1, h1, color);
}

private static void shearX(Graphics g, int w1, int h1, Color color) {

int period = random.nextInt(2);

boolean borderGap = true;
int frames = 1;
int phase = random.nextInt(2);

for (int i = 0; i < h1; i++) {
double d = (double) (period >> 1)
* Math.sin((double) i / (double) period
+ (6.2831853071795862D * (double) phase)
/ (double) frames);
g.copyArea(0, i, w1, 1, (int) d, 0);
if (borderGap) {
g.setColor(color);
g.drawLine((int) d, i, 0, i);
g.drawLine((int) d + w1, i, w1, i);
}
}

}

private static void shearY(Graphics g, int w1, int h1, Color color) {

int period = random.nextInt(40) + 10; // 50;

boolean borderGap = true;
int frames = 20;
int phase = 7;
for (int i = 0; i < w1; i++) {
double d = (double) (period >> 1)
* Math.sin((double) i / (double) period
+ (6.2831853071795862D * (double) phase)
/ (double) frames);
g.copyArea(i, 0, 1, h1, 0, (int) d);
if (borderGap) {
g.setColor(color);
g.drawLine(i, (int) d, i, 0);
g.drawLine(i, (int) d + h1, i, h1);
}

}
}
}

使用

1
2
3
4
5
6
7
8
9
10
11
response.setHeader("Pragma", "No-cache");
response.setHeader("Cache-Control", "no-cache");
response.setDateHeader("Expires", 0);
response.setContentType("image/jpeg");
//生成随机字符串
String verifyCode = VerifyCodeUtil.generateVerifyCode(4);
//将字符串写入输出流 130宽 40高
VerifyCodeUtil.outputImage(130, 40, response.getOutputStream(), verifyCode);
//存入Session
HttpSession session = request.getSession(true);
session.removeAttribute("Code");

文章字数:252,阅读全文大约需要1分钟

鼠标

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;

import com.sun.jna.Structure;
import com.sun.jna.examples.win32.Kernel32;
import com.sun.jna.examples.win32.User32;
import com.sun.jna.examples.win32.User32.HHOOK;
import com.sun.jna.examples.win32.User32.MSG;
import com.sun.jna.examples.win32.W32API.HMODULE;
import com.sun.jna.examples.win32.W32API.LRESULT;
import com.sun.jna.examples.win32.W32API.WPARAM;
import com.sun.jna.examples.win32.User32.HOOKPROC;

public class MouseHook implements Runnable{

public static final int WM_MOUSEMOVE = 512;
private static HHOOK hhk;
private static LowLevelMouseProc mouseHook;
final static User32 lib = User32.INSTANCE;
private boolean [] on_off=null;

public MouseHook(boolean [] on_off){
this.on_off = on_off;
}

public interface LowLevelMouseProc extends HOOKPROC {
LRESULT callback(int nCode, WPARAM wParam, MOUSEHOOKSTRUCT lParam);
}

public static class MOUSEHOOKSTRUCT extends Structure {
public static class ByReference extends MOUSEHOOKSTRUCT implements
Structure.ByReference {
};
public User32.POINT pt;
public int wHitTestCode;
public User32.ULONG_PTR dwExtraInfo;
}

public void run() {
HMODULE hMod = Kernel32.INSTANCE.GetModuleHandle(null);
mouseHook = new LowLevelMouseProc() {
public LRESULT callback(int nCode, WPARAM wParam,
MOUSEHOOKSTRUCT info) {
SimpleDateFormat df1 = new SimpleDateFormat("yyyy-MM-dd");
SimpleDateFormat df2 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String fileName=df1.format(new Date());
String time=df2.format(new Date());
BufferedWriter bw1=null;
BufferedWriter bw2=null;
try {
bw1=new BufferedWriter(new FileWriter(new File(".//log//"+fileName+"_Mouse.txt"),true));
bw2=new BufferedWriter(new FileWriter(new File(".//log//"+fileName+"_Common.txt"),true));
} catch (IOException e) {
e.printStackTrace();
}
if (on_off[0] == false) {
System.exit(0);
}
if (nCode >= 0) {
switch (wParam.intValue()) {
case MouseHook.WM_MOUSEMOVE:
try {
bw1.write(time+" #### "+"x=" + info.pt.x
+ " y=" + info.pt.y+"\r\n");
bw2.write(time+" #### "+"x=" + info.pt.x
+ " y=" + info.pt.y+"\r\n");
bw1.flush();
bw2.flush();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return lib
.CallNextHookEx(hhk, nCode, wParam, info.getPointer());
}
};
hhk = lib.SetWindowsHookEx(User32.WH_MOUSE_LL, mouseHook, hMod, 0);
int result;
MSG msg = new MSG();
while ((result = lib.GetMessage(msg, null, 0, 0)) != 0) {
if (result == -1) {
System.err.println("error in get message");
break;
} else {
System.err.println("got message");
lib.TranslateMessage(msg);
lib.DispatchMessage(msg);
}
}
lib.UnhookWindowsHookEx(hhk);
}
}

监听键盘

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;

import com.sun.jna.examples.win32.Kernel32;
import com.sun.jna.examples.win32.User32;
import com.sun.jna.examples.win32.User32.HHOOK;
import com.sun.jna.examples.win32.User32.KBDLLHOOKSTRUCT;
import com.sun.jna.examples.win32.User32.LowLevelKeyboardProc;
import com.sun.jna.examples.win32.User32.MSG;
import com.sun.jna.examples.win32.W32API.HMODULE;
import com.sun.jna.examples.win32.W32API.LRESULT;
import com.sun.jna.examples.win32.W32API.WPARAM;


public class KeyboardHook implements Runnable{

private static HHOOK hhk;
private static LowLevelKeyboardProc keyboardHook;
final static User32 lib = User32.INSTANCE;
private boolean [] on_off=null;

public KeyboardHook(boolean [] on_off){
this.on_off = on_off;
}

public void run() {

HMODULE hMod = Kernel32.INSTANCE.GetModuleHandle(null);
keyboardHook = new LowLevelKeyboardProc() {
public LRESULT callback(int nCode, WPARAM wParam, KBDLLHOOKSTRUCT info) {
SimpleDateFormat df1 = new SimpleDateFormat("yyyy-MM-dd");
SimpleDateFormat df2 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String fileName=df1.format(new Date());
String time=df2.format(new Date());
BufferedWriter bw1=null;
BufferedWriter bw2=null;
try {
bw1=new BufferedWriter(new FileWriter(new File(".//log//"+fileName+"_Keyboard.txt"),true));
bw2=new BufferedWriter(new FileWriter(new File(".//log//"+fileName+"_Common.txt"),true));

} catch (IOException e) {
e.printStackTrace();
}
if (on_off[0] == false) {
System.exit(0);
}
try {
bw1.write(time+" #### "+info.vkCode+"\r\n");
bw2.write(time+" #### "+info.vkCode+"\r\n");
bw1.flush();
bw2.flush();
} catch (IOException e) {
e.printStackTrace();
}
return lib.CallNextHookEx(hhk, nCode, wParam, info.getPointer());
}
};
hhk = lib.SetWindowsHookEx(User32.WH_KEYBOARD_LL, keyboardHook, hMod, 0);
int result;
MSG msg = new MSG();
while ((result = lib.GetMessage(msg, null, 0, 0)) != 0) {
if (result == -1) {
System.err.println("error in get message");
break;
} else {
System.err.println("got message");
lib.TranslateMessage(msg);
lib.DispatchMessage(msg);
}
}
lib.UnhookWindowsHookEx(hhk);
}

}

监听进程信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.text.SimpleDateFormat;
import java.util.Date;

public class ProcessInfo implements Runnable{

private boolean [] on_off=null;

public ProcessInfo(boolean [] on_off){
this.on_off = on_off;
}

public void run() {
BufferedReader input = null;
Process process = null;
BufferedWriter bw=null;
SimpleDateFormat df1 = new SimpleDateFormat("yyyy-MM-dd");
SimpleDateFormat df2 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String fileName=null;
String time=null;
try {
while(on_off[0]){
fileName=df1.format(new Date());
time=df2.format(new Date());
bw=new BufferedWriter(new FileWriter(new File(".//log//"+fileName+"_ProcessInfo.txt"),true));
Thread.sleep(60000);
process = Runtime.getRuntime().exec("cmd.exe /c tasklist");
input =new BufferedReader(
new InputStreamReader(process.getInputStream()));
String line = " ";
int i=0;
input.readLine();
input.readLine();
input.readLine();
while ((line = input.readLine()) != null) {
bw.write(time+" #### "+line+"\r\n");
bw.flush();
i++;
}
}
} catch (Exception e) {
e.printStackTrace();
} finally{
try {
bw.close();
input.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}

}

开启以上工具类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64

import java.awt.AWTException;
import java.awt.Image;
import java.awt.MenuItem;
import java.awt.PopupMenu;
import java.awt.SystemTray;
import java.awt.Toolkit;
import java.awt.TrayIcon;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

public class Monitor {

public Monitor() {
boolean [] on_off={true};
new Thread(new ProcessInfo(on_off)).start();
new Thread(new KeyboardHook(on_off)).start();
new Thread(new MouseHook(on_off)).start();
final TrayIcon trayIcon;

if (SystemTray.isSupported()) {

SystemTray tray = SystemTray.getSystemTray();
Image image = Toolkit.getDefaultToolkit().getImage(".//lib//monitor.png");

ActionListener exitListener = new ActionListener() {
public void actionPerformed(ActionEvent e) {
System.out.println("Exiting...");
System.exit(0);
}
};

PopupMenu popup = new PopupMenu();
MenuItem defaultItem = new MenuItem("Exit");
defaultItem.addActionListener(exitListener);
popup.add(defaultItem);

trayIcon = new TrayIcon(image, "monitor", popup);

ActionListener actionListener = new ActionListener() {
public void actionPerformed(ActionEvent e) {
trayIcon.displayMessage("Action Event",
"An Action Event Has Been Peformed!",
TrayIcon.MessageType.INFO);
}
};

trayIcon.setImageAutoSize(true);
trayIcon.addActionListener(actionListener);

try {
tray.add(trayIcon);
} catch (AWTException e1) {
e1.printStackTrace();
}

}
}

public static void main(String[] args) {
new Monitor();
}

}

文章字数:1192,阅读全文大约需要4分钟

Logback是由log4j创始人设计的另一个开源日志。

配置

  • 依赖springBoot自带logback的依赖
  • 基础配置
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    # 控制台日志打印debug级别的日志(默认不打印)
    # 生产环境下配置 java -jar c://sss.jar --debug
    debug=true
    # 设置具体包的日志级别
    logging.level.root=INFO
    logging.level.org.springframwork.web=DEBUG
    logging.level.org.hibernate=ERROR
    # 指定输出文件
    logging.path=F:\\demo
    logging.file=demo.log
    logging.level.root=info
    # 指定配置xml
    logging.config=classpath:log/logback-spring.xml

详细配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
<?xml version="1.0" encoding="UTF-8"?>
<!-- 日志级别从低到高分为TRACE < DEBUG < INFO < WARN < ERROR < FATAL,如果设置为WARN,则低于WARN的信息都不会输出 -->
<!-- scan:当此属性设置为true时,配置文件如果发生改变,将会被重新加载,默认值为true -->
<!-- scanPeriod:设置监测配置文件是否有修改的时间间隔,如果没有给出时间单位,默认单位是毫秒。当scan为true时,此属性生效。默认的时间间隔为1分钟。 -->
<!-- debug:当此属性设置为true时,将打印出logback内部日志信息,实时查看logback运行状态。默认值为false。 -->
<configuration scan="true" scanPeriod="10 seconds">

<!--<include resource="org/springframework/boot/logging/logback/base.xml" />-->

<contextName>logback</contextName>
<!-- name的值是变量的名称,value的值时变量定义的值。通过定义的值会被插入到logger上下文中。定义变量后,可以使“${}”来使用变量。 -->
<property name="log.path" value="D:/nmyslog/nmys" />

<!-- 彩色日志 -->
<!-- 彩色日志依赖的渲染类 -->
<conversionRule conversionWord="clr" converterClass="org.springframework.boot.logging.logback.ColorConverter" />
<conversionRule conversionWord="wex" converterClass="org.springframework.boot.logging.logback.WhitespaceThrowableProxyConverter" />
<conversionRule conversionWord="wEx" converterClass="org.springframework.boot.logging.logback.ExtendedWhitespaceThrowableProxyConverter" />
<!-- 彩色日志格式 -->
<property name="CONSOLE_LOG_PATTERN" value="${CONSOLE_LOG_PATTERN:-%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(${LOG_LEVEL_PATTERN:-%5p}) %clr(${PID:- }){magenta} %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}}"/>


<!--输出到控制台-->
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<!--此日志appender是为开发使用,只配置最底级别,控制台输出的日志级别是大于或等于此级别的日志信息-->
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>info</level>
</filter>
<encoder>
<Pattern>${CONSOLE_LOG_PATTERN}</Pattern>
<!-- 设置字符集 -->
<charset>UTF-8</charset>
</encoder>
</appender>


<!--输出到文件-->

<!-- 时间滚动输出 level为 DEBUG 日志 -->
<appender name="DEBUG_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!-- 正在记录的日志文件的路径及文件名 -->
<file>${log.path}/log_debug.log</file>
<!--日志文件输出格式-->
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
<charset>UTF-8</charset> <!-- 设置字符集 -->
</encoder>
<!-- 日志记录器的滚动策略,按日期,按大小记录 -->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!-- 日志归档 -->
<fileNamePattern>${log.path}/debug/log-debug-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<maxFileSize>100MB</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
<!--日志文件保留天数-->
<maxHistory>15</maxHistory>
</rollingPolicy>
<!-- 此日志文件只记录debug级别的 -->
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>debug</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
</appender>

<!-- 时间滚动输出 level为 INFO 日志 -->
<appender name="INFO_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!-- 正在记录的日志文件的路径及文件名 -->
<file>${log.path}/log_info.log</file>
<!--日志文件输出格式-->
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
<charset>UTF-8</charset>
</encoder>
<!-- 日志记录器的滚动策略,按日期,按大小记录 -->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!-- 每天日志归档路径以及格式 -->
<fileNamePattern>${log.path}/info/log-info-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<maxFileSize>100MB</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
<!--日志文件保留天数-->
<maxHistory>15</maxHistory>
</rollingPolicy>
<!-- 此日志文件只记录info级别的 -->
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>info</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
</appender>

<!-- 时间滚动输出 level为 WARN 日志 -->
<appender name="WARN_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!-- 正在记录的日志文件的路径及文件名 -->
<file>${log.path}/log_warn.log</file>
<!--日志文件输出格式-->
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
<charset>UTF-8</charset> <!-- 此处设置字符集 -->
</encoder>
<!-- 日志记录器的滚动策略,按日期,按大小记录 -->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${log.path}/warn/log-warn-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<maxFileSize>100MB</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
<!--日志文件保留天数-->
<maxHistory>15</maxHistory>
</rollingPolicy>
<!-- 此日志文件只记录warn级别的 -->
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>warn</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
</appender>


<!-- 时间滚动输出 level为 ERROR 日志 -->
<appender name="ERROR_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!-- 正在记录的日志文件的路径及文件名 -->
<file>${log.path}/log_error.log</file>
<!--日志文件输出格式-->
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
<charset>UTF-8</charset> <!-- 此处设置字符集 -->
</encoder>
<!-- 日志记录器的滚动策略,按日期,按大小记录 -->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${log.path}/error/log-error-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<maxFileSize>100MB</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
<!--日志文件保留天数-->
<maxHistory>15</maxHistory>
</rollingPolicy>
<!-- 此日志文件只记录ERROR级别的 -->
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>ERROR</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
</appender>

<!--
<logger>用来设置某一个包或者具体的某一个类的日志打印级别、
以及指定<appender>。<logger>仅有一个name属性,
一个可选的level和一个可选的addtivity属性。
name:用来指定受此logger约束的某一个包或者具体的某一个类。
level:用来设置打印级别,大小写无关:TRACE, DEBUG, INFO, WARN, ERROR, ALL 和 OFF,
还有一个特俗值INHERITED或者同义词NULL,代表强制执行上级的级别。
如果未设置此属性,那么当前logger将会继承上级的级别。
addtivity:是否向上级logger传递打印信息。默认是true。
-->
<!--<logger name="org.springframework.web" level="info"/>-->
<!--<logger name="org.springframework.scheduling.annotation.ScheduledAnnotationBeanPostProcessor" level="INFO"/>-->
<!--
使用mybatis的时候,sql语句是debug下才会打印,而这里我们只配置了info,所以想要查看sql语句的话,有以下两种操作:
第一种把<root level="info">改成<root level="DEBUG">这样就会打印sql,不过这样日志那边会出现很多其他消息
第二种就是单独给dao下目录配置debug模式,代码如下,这样配置sql语句会打印,其他还是正常info级别:
-->


<!--
root节点是必选节点,用来指定最基础的日志输出级别,只有一个level属性
level:用来设置打印级别,大小写无关:TRACE, DEBUG, INFO, WARN, ERROR, ALL 和 OFF,
不能设置为INHERITED或者同义词NULL。默认是DEBUG
可以包含零个或多个元素,标识这个appender将会添加到这个logger。
-->

<!--开发环境:打印控制台-->
<springProfile name="dev">
<logger name="com.nmys.view" level="debug"/>
</springProfile>

<root level="info">
<appender-ref ref="CONSOLE" />
<appender-ref ref="DEBUG_FILE" />
<appender-ref ref="INFO_FILE" />
<appender-ref ref="WARN_FILE" />
<appender-ref ref="ERROR_FILE" />
</root>

<!--生产环境:输出到文件-->
<!--<springProfile name="pro">-->
<!--<root level="info">-->
<!--<appender-ref ref="CONSOLE" />-->
<!--<appender-ref ref="DEBUG_FILE" />-->
<!--<appender-ref ref="INFO_FILE" />-->
<!--<appender-ref ref="ERROR_FILE" />-->
<!--<appender-ref ref="WARN_FILE" />-->
<!--</root>-->
<!--</springProfile>-->

</configuration>

简单使用

1
2
private static Logger logger = LoggerFactory.getLogger(demo.class);//当前类的class,方便定位
logger.info("xx");

文章字数:1197,阅读全文大约需要4分钟

配置

常用插件有三种,对应三个配置

  1. maven-jar-plugin: maven 默认打包插件,用来创建 project jar
  2. maven-shade-plugin: 打可执行包,executable(fat) jar
  3. maven-assembly-plugin: 支持自定义打包方式

打包

  1. 打包前需要先clean一下,重新加载依赖

  2. maven-jar-plugin配置

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    <plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-jar-plugin</artifactId>
    <version>2.4</version>
    <!-- 对要打的jar包进行配置 -->
    <configuration>
    <!-- Configuration of the archiver -->
    <archive>
    <!--生成的jar中,不要包含pom.xml和pom.properties这两个文件-->
    <addMavenDescriptor>false</addMavenDescriptor>

    <!-- Manifest specific configuration -->
    <manifest>
    <!--是否要把第三方jar放到manifest的classpath中-->
    <addClasspath>true</addClasspath>

    <!--生成的manifest中classpath的前缀,
    因为要把第三方jar放到lib目录下,
    所以classpath的前缀是lib/-->
    <classpathPrefix>lib/</classpathPrefix>
    </manifest>
    </archive>
    <!--过滤掉不希望包含在jar中的文件-->
    <excludes>
    <!-- 排除不需要的文件夹(路径是jar包内部的路径) -->
    <exclude>**/assembly/</exclude>
    </excludes>
    </configuration>
    </plugin>
  3. maven-assembly-plugin

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    <plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-assembly-plugin</artifactId>
    <version>2.4</version>
    <!-- 对项目的组装进行配置 -->
    <configuration>
    <!-- 指定assembly插件的配置文件所在位置 -->
    <descriptors>
    <descriptor>src/main/resources/assembly/package.xml</descriptor>
    </descriptors>
    </configuration>
    <executions>
    <execution>
    <id>make-assembly</id>
    <!-- 将组装绑定到maven生命周期的哪一阶段 -->
    <phase>package</phase>
    <goals>
    <!-- 指定assembly插件的打包方式-->
    <goal>single</goal>
    </goals>
    </execution>
    </executions>
    </plugin>
  4. 双击idea maven菜单栏的package或者直接命令执行mvn:package生成两个包(可执行jar和项目压缩包)

pom配置

包含两个文件:
pom.xml整体的配置
package.xml包含在pom.xml中,用于指定assembly装配时的配置

pom.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<!-- ####################### 基础设置 ###################### -->
<!--groupId:项目或者组织的唯一标志,并且配置时生成路径也是由此生成,如org.myproject.mojo生成的相对路径为:/org/myproject/mojo-->
<groupId>com.dong</groupId>
<!--项目的通用名称-->
<artifactId>bigdata</artifactId>
<!--打包机制,如pom,jar,maven-plugin,ejb,war,ear,rar,par-->
<packaging>jar</packaging>
<!--项目的版本-->
<version>1.0-SNAPSHOT</version>

<!-- ####################### 项目信息 ###################### -->
<!--用户描述项目的名称,无关紧要的东西-->
<name>bigdata</name>
<!--写明开发团队的网站,无关紧要-->
<url>http://http://www.dong.com/.com</url>

<!-- ####################### 环境设置 ###################### -->
<properties>
<!-- 项目执行脚本目录 -->
<project.script.execute.directory>src/main/scripts/execute</project.script.execute.directory>
<!-- 项目说明文档目录 -->
<project.document.directory>document</project.document.directory>
<!-- 项目配置文件目录 -->
<project.config.directory>src/main/resources</project.config.directory>
<!-- 项目编码 -->
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>

<!-- 本地编译JDK版本 -->
<maven.compiler.source>1.8</maven.compiler.source>
<!-- 项目部署JDK版本 -->
<maven.compiler.target>1.8</maven.compiler.target>
</properties>

<!--
配置Maven的仓库, 在此处配置的仓库会优先于setting.xml里配置的仓库,
建议哪个仓库快,哪个配置在前面, 然后如果Maven在前面配置的仓库找不到的话会去后面的仓库找,
如果后面的仓库都找不到,会去setting.xml中央仓库里找
-->
<repositories>
<!-- 阿里云仓库,配置Maven仓库,速度快配置在最前面 -->
<repository>
<id>aliyun</id>
<url>http://maven.aliyun.com/nexus/content/groups/public</url>
</repository>
<!-- 国内备选仓库 -->
<repository>
<id>repo2</id>
<url>http://repo2.maven.org/maven2/</url>
</repository>

<!-- Cloudera仓库,如果在阿里云仓库里找不到去Cloudera的仓库里找,主要是CDH版本Hadoop依赖的jar -->
<repository>
<id>cloudera</id>
<url>https://repository.cloudera.com/artifactory/cloudera-repos/</url>
</repository>

<!-- Scala仓库,如果前面两个都找不到来仓库找,如果此仓库也找不到,去中央仓库找 -->
<repository>
<id>scala-tools.org</id>
<name>Scala-Tools Maven2 Repository</name>
<url>http://scala-tools.org/repo-releases</url>
</repository>
</repositories>

<dependencies>
<!-- https://mvnrepository.com/artifact/org.apache.commons/commons-lang3 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.4</version>
</dependency>
</dependencies>

<build>
<finalName>dong</finalName>
<plugins>
<!-- The configuration of maven-jar-plugin -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>2.4</version>
<!-- 对要打的jar包进行配置 -->
<configuration>
<!-- Configuration of the archiver -->
<archive>
<!--生成的jar中,不要包含pom.xml和pom.properties这两个文件-->
<addMavenDescriptor>false</addMavenDescriptor>

<!-- Manifest specific configuration -->
<manifest>
<!--是否要把第三方jar放到manifest的classpath中-->
<addClasspath>true</addClasspath>

<!--
生成的manifest中classpath的前缀,
因为要把第三方jar放到lib目录下,
所以classpath的前缀是lib/
-->
<classpathPrefix>lib/</classpathPrefix>
</manifest>
</archive>
<!--过滤掉不希望包含在jar中的文件-->
<excludes>
<!-- 排除不需要的文件夹(路径是jar包内部的路径) -->
<exclude>**/assembly/</exclude>
</excludes>
</configuration>
</plugin>

<!-- The configuration of maven-assembly-plugin -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<version>2.4</version>
<!-- 对项目的组装进行配置 -->
<configuration>
<!-- 指定assembly插件的配置文件所在位置 -->
<descriptors>
<descriptor>src/main/resources/assembly/package.xml</descriptor>
</descriptors>
</configuration>
<executions>
<execution>
<id>make-assembly</id>
<!-- 将组装绑定到maven生命周期的哪一阶段 -->
<!--<phase>package</phase>-->
<goals>
<!-- 指定assembly插件的打包方式-->
<goal>single</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

package.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
<?xml version="1.0" encoding="UTF-8"?>
<assembly>
<id>full</id>
<!-- 最终打包成一个用于发布的zip文件 -->
<formats>
<format>zip</format>
</formats>

<!-- 把依赖jar包打包进Zip压缩文件的lib目录下 -->
<dependencySets>
<dependencySet>
<!--不使用项目的artifact,第三方jar不要解压,打包进zip文件的lib目录-->
<useProjectArtifact>false</useProjectArtifact>

<!-- 第三方jar打包进Zip文件的lib目录下, -->
<!-- 注意此目录要与maven-jar-plugin中classpathPrefix指定的目录相同, -->
<!-- 不然这些依赖的jar包加载到ClassPath的时候会找不到-->
<outputDirectory>lib</outputDirectory>

<!-- 第三方jar不要解压-->
<!--<unpack>false</unpack>-->
</dependencySet>
</dependencySets>

<!-- 文件设置,你想把哪些文件包含进去,或者把某些文件排除掉,都是在这里配置-->
<fileSets>
<!-- 把项目自己编译出来的可执行jar,打包进zip文件的根目录 -->
<fileSet>
<directory>${project.build.directory}</directory>
<outputDirectory></outputDirectory>
<includes>
<include>*.jar</include>
</includes>
</fileSet>

<!--
把项目readme说明文档,打包进zip文件根目录下
(这里针对目录document/readme.txt文件)
${projet.document.directory}是pom.xml中自己配置的
-->
<fileSet>
<directoryl>${projet.document.directory}</directoryl>
<outputDirectory></outputDirectory>
<includes>
<include>readme.*</include>
</includes>
</fileSet>

<!--
把项目相关的说明文档(除了readme文档),
打包进zip文件根目录下的document目录
(这里针对document/exclode.txt文件)
${project.document.directory}是在pom.xml中自己配置的
-->
<fileSet>
<directory>${project.document.directory}</directory>
<outputDirectory>document</outputDirectory>
<excludes>
<exclude>readme.*</exclude>
</excludes>
</fileSet>

<!--
把项目的脚本文件目录(src/main/scripts )中的启动脚本文件,
打包进zip文件的根目录
(这里针对的是src/scripts/execute/include-file.sh文件)
${project.script.execute.directory}
-->
<fileSet>
<directory>${project.script.execute.directory}</directory>
<outputDirectory></outputDirectory>
<includes>
<include>*</include>
</includes>
</fileSet>

</fileSets>
</assembly>

文章字数:2100,阅读全文大约需要8分钟

基础概念

  • nginxmaxterworker线程组成,master接受外部信号,并发送给各个worker线程。监控worker线程的状态,重启异常结束的worker

基本命令

  • ./nginx -s stop停止
  • ./nginx -s quit退出
  • ./nginx -s reload重新加载nginx.conf

配置

  • 结构
1
2
3
4
5
6
main //全局设置
-event // nginx工作模式和连接数上限
-http // 服务器相关属性
-server // 虚拟主机设置
-upstream // 上有服务器设置,主要是反向代理、负载均衡相关
-location // url匹配特定位置后的设置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
#user www; //work进程的用户权限
worker_processes 1;// 启用一个worker进程,一般和cpu数量一致

#error_log logs/error.log notice; // 日志级别 常用: debug notice warn error crit

events { // 主要配置工作模式和连接数
use epoll; // 默认就是这个,一般不用配置
worker_connections 1024;// 线程都文件限制,不配会默认使用linux的默认值
}

http {
include mine.types; // 引入mine.types这个文件
#log_format main ·remote_addr - $remote_user... // 定义访问日志格式(下面由表格)
# access_log logs/access.log main;// 访问日志及格式,main就是上面定义的那个
sendfile on;// 启用高效文件下载
#tcp_nopush on;// 高效下载使用tcp_nopush

#keepalive_timeout 65;

#gzip on;//开启压缩支持

upstream nginx { // 申明一个负载
# ip_hash;
server 192.168.1.1:8098 weight=2; // 权重为2的负载
server 192.168.1.2:8098 weight=1; // 权重为1的负载
}

server { // 虚拟主机
listen 80;// 监听端口
server_name localhost;// 主机名(域名访问时匹配)
...
location / {// 匹配url
root html; // 根目录路径,即最终取的文件是html/location的url
index index.html index.htm;// 等url以/结尾时,取那个文件。不以/结尾则是location指定的
# root路径+location路径,root代表读取文件的根目录名(nginx目录下的)再加上url后面指定的路径,读取具体文件
# alias html; // 当location =/xxx 时,因为url后面的第一个已经指定了(即location的值),所以root不能指定具体的根目录,此时就能用alias。
# 例如 url: 127.0.0.1/zzz/a.html,设置 alias html;就会读取 html/a.html
# return 500; // 可以直接返回错误
# set $a 32; // 设置变量 nginx 先执行rewrite(定义)相关指令,再执行access(执行),最后是content(读文件)相关命令,所以多次set变量,echo时只会取最后一次的值,不是按照书写顺序执行的。
# echo $a;// 输出a的值到http res
# proxy_pass http://xxx.xxx.xxx/aaa/yyy/ //反向代理,末尾有 / 的情况下将 location匹配的字符之后的内容补充到 yyy/ 后面,没有 / 的情况补充location匹配的字符及后面的字符到 yyy/之后
# proxy_pass http://nginx; // 使用负载
# rewrite ^/ a.html break/replacement // 匹配正则“^/”指定的路径,转发(break,服务器内部转发)或重定向(replacement,url切换)
}



error_page 500 502 503 504 /50x.html // 配置错误页面
location = /50x.html {
root html;
}
}
}
名称 含义
$remote_addr 客户端ip
$remote_user 远程客户端的用户名(一般是‘-’)
$time_local 访问时间和时区
$request url及请求方法
$status 响应码
$body_bytes_sent 给客户的发送的文件主题内容字节数
$http_user_agent 用户使用的代理(一般是浏览器)
$http_x_forwarded_for 可以记录客户端ip
$http_referer 用户从那个链接过来的

url路由配置

优先级 表达式 说明
1 location =/ 精准匹配/
2 location /xxx 开头是/xxx,正常匹配(^~)
2 location ^~/static/ ^~ 正常匹配,以/static/ 开头
3 location ~ /a 正则匹配,严格匹配,区分大小写
3 location ~* /a 不区分大小写正则
  • 同级别会取最长值作为匹配结果
  • 精准匹配会直接返回结果,普通匹配结束后还会再进行正则匹配,如果正则有结果,会返回正则匹配到的结果,正则匹配会覆盖普通匹配。
  • 匹配多条则会进入最长的规则中

负载均衡

  1. 默认使用轮询,逐个转发到配置的服务器中,如果服务器挂了,能够自动剔除
  2. weight,指定轮询记录,和被访问几率成正比
  3. ip_hash 每个请求按访问的ip结果分配,这样每个访客固定再一个服务器上,解决session问题

运作原理

  • url: 域名+端口(port)+路径(path)+参数(param)
  1. 匹配server虚拟主机
  2. 匹配location,根据path从前往后匹配。根据匹配结果分为 匹配到的path1剩余path2
  3. 代理转发
  • root转,整个path1+path2
  • alias只转path2
  • proxy_pass,如果port后面有/则只传path2,没有/则传path1 + path2
  • response根据传过来的信息,生成内容,返回页面
  1. echo内容生成的命令,echo信息到response (需要echo-nginx-module第三方插件)

rewrite

  • rewrite regex replacement [flag]
  • regex正则,^/代表总是匹配
  • replacement替换值,新值
  • flag处理标志,可选
  1. break内部转发,替换新值,然后走后续的执行阶段
  2. last内部转发,用替换值重新走location,警惕死循环
  3. redirect 301重定向
  4. permanent 302重定向
  5. 没有flag的时候最后一个rewrite会覆盖之前的,有flag则第一个生效

location

  • nginx配置location [=|~|~*|^~] /uri/ { … }用法
  1. = 严格匹配。如果这个查询匹配,那么将停止搜索并立即处理此请求。
  2. ~ 为区分大小写匹配(可用正则表达式)
  3. !~为区分大小写不匹配
  4. ~* 为不区分大小写匹配(可用正则表达式)
  5. !~*为不区分大小写不匹配
  6. ^~ 如果把这个前缀用于一个常规字符串,那么告诉nginx 如果路径匹配那么不测试正则表达式。

if判断

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
server{

listen 80;
server_name xxx.xx.com;

# set $flag 0;
# if($flag = 0) {
# return 501;
#}

# 客户端完整请求($request_uri) 不区分大小写(~*) 匹配php结尾的正则时返回502
if( $request_uri ~* /(.*)\.php ) {
return 502;
}

# 自动读取linux文件目录,当没有请求的文件时,返回414
if(!-f $request_filename) {
return 414;
}

# 拦截chrome浏览器的请求
if($http_user_agent ~ Chrome) {
return 503;
}
}

文件服务器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
server {
listen 80;
server_name localhost;

# 开启目录显示
autoindex on;
autoindex_exact_size off;
autoindex_localtime on;
charset utf-8;

location / {

# 选择文件存储的根目录
root /mnt/d/TDDownload;
}
}

跨域

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
server{
...
if($http_origin ~ http://(.*).xx.com) {
# http_origin是nginx内置变量,代表请求的origin值,即请求的网址,将它保存为变量http_origin
set $allpw_url $http_origin;
}

# 是否允许请求带验证信息
add_header Access-Controller-Allow-Credentials true;
# 允许跨域的域名,可以是列表也可以是 * 全部允许跨域
add_header Access-Control-Allow-Origin $allow_url;
# 允许脚本访问的返回头
add_header Access-Control-Allow-Headers 'x-requested-whith,content-type,Cache-Control,paragma,Date,x-timestamp';
# 允许请求的方法
add_header Access-Control-Allow-Methods 'POST,GET,OPTIONS,PUT,DELETE';
# 允许自定义的头部
add_header Access-Control-Expose-Headers 'WWW-Autenticate,Server-Authorization';
# P3P支持跨域cookie操作
add_header P3P 'policyref="/w3c/p3p.xml", CP="NOI DSP PSAa OUR IND ONL UNI COM NAV INT LOC"';

if($request_method = 'OPTIONS') {
return 204;
}
}

防盗链

1
2
3
4
5
6
7
8
location /xxx.png {

valid_referers xx.com;
if($invalid_referer) {
return 404;
}
return ...;
}

缓存静态文件

1
2
3
4
5
location xxx {
# s秒,m分,h小时,d天
expires 2m; # 缓存时间
root img/src;
}

压缩传输

  1. 浏览器请求时会携带支持的解压缩格式Accept-Encoding: gzip, deflate
  2. nginx压缩文件,并携带Content-Encoding: gzip的返回给浏览器,表示当前是用什么方式压缩的。
1
2
3
4
5
6
7
8
9
10
location xx {
gzip on; # 启用gzip压缩,默认off
# 那些文件开启压缩功能
gzip_types application/javascript text/css image/jpeg image/png image/gif;
gzip_min_length 1024;# 超过多少大小才压缩
gzip_buffers 4 1k;# 压缩相应的缓冲块大小和数量,默认是内存一个页大小
gzip_comp_level 1;# 压缩等级,1-9,默认1,取值越大压缩比例越大,越耗时

root src;
}

https

conf文件

1
2
ssl_certificate /xxx/xxx/xx.crt;
ssl_certificate_key /xx/x/server.key;
1
2
3
server {
listen 8080 ssl;
}

高可用

  • 使用keepalive解决高可用问题
  • lvs思想

多个Nginx配置keepalive,就会生成一个虚拟网关(固定ip),虚拟网关按照策略将流量分发给各个Nginx,虚拟网关不是真实网关所以不会挂掉。

  1. 下载对应nginx版本的keepalived压缩包
  2. ./configure --prefix=/data/program/keepalived --sysconf=/etc/指定文件位置(prefix)和配置文件位置(sysconf)
  3. make执行安装
  4. keepalived.conf是配置文件
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    global_defs {
    router_id LVS_1 # 设置当前lvs的id
    }

    vrrp_script chk_http_port {
    script "/xxx/chk_nginx_pid.sh"# 设置执行的脚本
    interval 2 # 脚本执行时间,秒
    weight 2
    }

    vrrp_instance VI_1 {
    state MASTER # 主还是从
    interface eth0 # 系统网卡
    virtual_router_id 51 # 主备机器一致
    priority 100 # 优先级 值大的胜出
    advert_int 1
    authentication {
    auth_type PASS
    auth_pass 1111
    }

    virtual_ipaddress {
    192.168.100.1 # 虚拟ip,可以是多个
    }
    srack_script {
    chk_http_port # 调用检测脚本
    }
    }
  • keepalive监控Nginx

chk_http_port.sh内容

1
2
3
4
5
6
7
8
#!/bin/bash
A=`ps -C nginx --no-header |wc -l` # 统计nginx数量
if [$A -eq 0];then
/user/local/nginx/sbin/nginx # 重启nginx
if[ `ps -C nginx --no-header |wc -l` -eq 0 ];then # 如果重启失败,则关闭keepalived服务,进行VIP(虚拟ip)转移(本机keepalived杀死,则流量就会自动转移到其他机子上)
killall keepalived
fi
fi