├── README.md
├── pom.xml
└── src
├── main
├── java
│ └── com
│ │ └── example
│ │ └── demo
│ │ ├── DemoApplication.java
│ │ ├── controller
│ │ ├── CaptchaController.java
│ │ └── CaptchaRestController.java
│ │ ├── model
│ │ ├── BaseBo.java
│ │ └── ResponseMessage.java
│ │ ├── service
│ │ ├── AutzQueryService.java
│ │ └── impl
│ │ │ └── AutzQueryServiceImpl.java
│ │ ├── support
│ │ ├── CaptchaConfig.java
│ │ ├── CaptchaConst.java
│ │ └── cache
│ │ │ ├── CacheConfig.java
│ │ │ ├── CacheManagerHolder.java
│ │ │ └── CaffeineAutoConfiguration.java
│ │ └── util
│ │ ├── CaptchaUtil.java
│ │ ├── ClassLoaderWrapper.java
│ │ ├── ExceptionHelper.java
│ │ ├── NamedThreadFactory.java
│ │ ├── Resources.java
│ │ ├── UtilFile.java
│ │ ├── UtilNet.java
│ │ ├── UtilString.java
│ │ └── UtilWeb.java
├── resources
│ ├── application.yml
│ └── static
│ │ ├── css
│ │ └── captcha.css
│ │ ├── img
│ │ ├── erricon.png
│ │ ├── loading.png
│ │ ├── refresh.png
│ │ └── source
│ │ │ ├── 0.png
│ │ │ ├── 1.png
│ │ │ ├── 2.png
│ │ │ ├── 3.png
│ │ │ └── 4.png
│ │ └── js
│ │ ├── drag.js
│ │ └── jquery.1.12.4.min.js
└── webapp
│ └── jsp
│ └── login.jsp
└── test
└── java
└── com
└── example
└── demo
└── DemoApplicationTests.java
/README.md:
--------------------------------------------------------------------------------
1 | # java-captcha
2 | 关于java-captcha项目,他是一个基于Java平台,滑动拼图解锁的一种验证,或者说一种解决方案。
3 | 受限于自身研究。具体解锁时的因素可以有更多。目前只是简单的判断是否在范围正负内。有能力的,可以将滑动轨迹等传入后台进行判断。
4 |
5 | ## 何为验证码
6 | 验证码,通常是为了识别操作的发起者,是人还是机器。目前现有的验证码有图片验证,即让你输入图片中的字符,中文等。
7 | 也有12306那样的点击对应图片,以及选择图中对应汉字等。实现逻辑,各有千秋。但都是为了简单方便准确的区别开,判断是人还是机器。
8 |
9 | ## 滑动拼图验证码
10 | 滑动拼图验证码是这两年流行起来的一种方式,验证码只需要,在滚动条上将拼图拖到对应位置,符合即可验证成功。不需要输入,不需要计算等。
11 | 目前流行的有极验平台:https://www.geetest.com/Sensebot 有兴趣的可以关注下。
12 | 关于拼图组成:我采用原始图片,拼图图片,水印图片组成。拼图图片为原始图片上一个区域内的一块,水印图片是指在原图上新增一块拼图图片对应的黑块,或者别的颜色的区域。
13 |
14 | ## 验证思路
15 | 用户进入验证页面---->后台选择原始图片---->后台生成水印图片与拼图图片---->放置到缓存中并记录偏移量---->返回给前台
16 | ---->前台进行加载三张图片---->滑动验证码---->后台校验偏移量---->返回一个随机码给前台---->前台操作时带上返回的验证码---->完成校验
17 |
18 | ## 验证设计
19 | 大体分为
20 | RestController:
21 | captcha: 选择并生成原始图片,拼图图片与水印图片,并存入缓存,在将缓存对应的key返回给前台。
22 | image: 前台根据后台返回的key,调用此接口从缓存中加载对应的图片
23 | check: 验证偏移量,同时返回成功与否,成功则带上一个随机码,并放入缓存中
24 | Service:
25 | captcha: 这里改成了Utils,作为一个工具类,方便别的地方直接引用,主要作用完成图片的生成
26 | auth: 缓存相关,以及验证随机码。
27 |
28 | ### 为什么采用缓存存储图片
29 | 验证码生成的图片多小而多,且复用性低。缓存效率会高很多,同时易于清理。
30 | ### 关于caffeine缓存
31 | caffeine缓存是一个GitHub上基于Java8编写的高性能高速缓存,设计机制等参照guava。在spring boot2中,默认推荐使用caffeine替代guava。所以这里我也做出了改动。
32 | ### 为什么读取图片采用自己写的Resources去读取
33 | 读取选择图片有很多种方式,这里可以自己去抽取封装下,方便改造成自己使用的。前后分离中,可以去直接调某些接口,甚至直接访问某些别的接口获取图片。
34 | 同时在springboot的jar包部署中。只能精准去读取对应的图片。不能模糊读取对应目录。不然无法读取到内部路径。
35 | ### 为什么采用验证偏移量
36 | 因个人实力和需求限定。只是简单的判断最终拼图所在的偏移量。这里其实可以扩展开,比如前台记录并传入后台,拼图在滑动过程中的轨迹,同时可以用上
37 | 机器学习或人工智能来判断滑动的轨迹,是人为,还是机器所为。(人移到过超中理论上会上下有所偏移)
38 | ### 注意极限问题
39 | 1.注意缓存的过期时间,图片的过期时间应该是很短的,例如30S.因为缓存验证码生成后,页面会立即调用加载。加载后可以主动删除,也可以偷懒,通过过期时间去控制。
40 | 我这里设置的图片30S,验证码和偏移量可以规定为5分钟,或者10分钟。同时如果图片时间设计缓存时间过长,高并发时,可能会出现内存溢出等情况。
41 | 2.原图可以先从缓存中查找是否存在。理论在高并发过程中,能减少100-200ms单次请求。减轻IO读取压力。
42 | ### 小扩展思路
43 | 其实后台还可以在上一个RSA等非对称加密。每次请求给出具体图片地址时,返回一个公钥给前台,前台根据公钥加密关键参数,
44 | 后台根据私钥解密获取参数,私钥和公钥一起生成,每个不一样,私钥存储与缓存中。这样更安全和增加爆破成本。
45 |
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 | 4.0.0
5 |
6 | com.example
7 | demo
8 | 0.0.1-SNAPSHOT
9 | jar
10 |
11 | demo
12 | Demo project for Spring Boot
13 |
14 |
15 |
16 | org.springframework.boot
17 | spring-boot-starter-parent
18 | 2.1.1.RELEASE
19 |
20 |
21 |
22 |
23 | UTF-8
24 | UTF-8
25 | 1.8
26 |
27 |
28 |
29 |
30 | org.springframework.boot
31 | spring-boot-starter-web
32 |
33 |
34 |
35 | org.springframework.boot
36 | spring-boot-starter-test
37 | test
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 | org.springframework
48 | spring-context-support
49 | 5.1.3.RELEASE
50 |
51 |
52 |
53 |
54 | com.google.guava
55 | guava
56 | 25.1-jre
57 |
58 |
59 |
60 | com.github.ben-manes.caffeine
61 | caffeine
62 | 2.6.2
63 |
64 |
65 |
66 | org.projectlombok
67 | lombok
68 | 1.16.16
69 |
70 |
71 |
72 | com.alibaba
73 | fastjson
74 | 1.2.47
75 |
76 |
77 | commons-codec
78 | commons-codec
79 | 1.10
80 |
81 |
82 | commons-lang
83 | commons-lang
84 | 2.4
85 |
86 |
87 | org.apache.commons
88 | commons-lang3
89 | 3.3.2
90 |
91 |
92 |
93 | commons-io
94 | commons-io
95 | 1.3.2
96 |
97 |
98 |
99 | org.apache.tomcat.embed
100 | tomcat-embed-jasper
101 |
102 |
103 |
104 | javax.servlet
105 | javax.servlet-api
106 |
107 |
108 |
109 |
110 | javax.servlet.jsp
111 | javax.servlet.jsp-api
112 | 2.3.1
113 |
114 |
115 |
116 |
117 | javax.servlet
118 | jstl
119 |
120 |
121 |
122 |
123 |
124 | ${project.artifactId}
125 |
126 |
127 |
128 |
129 | src/main/webapp
130 |
131 | META-INF/resources
132 |
133 | **/**
134 |
135 |
136 |
137 | src/main/resources
138 |
139 | **/**
140 |
141 | false
142 |
143 |
144 | src/main/java
145 |
146 | **/**
147 |
148 | false
149 |
150 |
151 |
152 |
153 | org.apache.maven.plugins
154 | maven-compiler-plugin
155 | 3.1
156 |
157 | ${java.version}
158 | ${java.version}
159 | ${project.build.sourceEncoding}
160 |
161 |
162 |
163 |
164 | org.apache.maven.plugins
165 | maven-surefire-plugin
166 |
167 | true
168 |
169 |
170 |
171 |
172 | org.springframework.boot
173 | spring-boot-maven-plugin
174 | 1.3.7.RELEASE
175 |
176 | com.example.demo.DemoApplication
177 | true
178 |
179 |
180 |
181 |
182 | repackage
183 |
184 |
185 |
186 |
187 |
188 |
189 | org.apache.maven.plugins
190 | maven-war-plugin
191 | 2.6
192 |
193 | false
194 |
195 |
196 |
197 |
198 |
199 |
200 |
201 |
--------------------------------------------------------------------------------
/src/main/java/com/example/demo/DemoApplication.java:
--------------------------------------------------------------------------------
1 | package com.example.demo;
2 |
3 | import com.example.demo.support.CaptchaConfig;
4 | import org.springframework.boot.SpringApplication;
5 | import org.springframework.boot.autoconfigure.SpringBootApplication;
6 | import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
7 | import org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration;
8 | import org.springframework.boot.context.properties.EnableConfigurationProperties;
9 |
10 | @SpringBootApplication(exclude={DataSourceAutoConfiguration.class,HibernateJpaAutoConfiguration.class})
11 | @EnableConfigurationProperties({CaptchaConfig.class})
12 | public class DemoApplication {
13 |
14 | public static void main(String[] args) {
15 | SpringApplication.run(DemoApplication.class, args);
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/src/main/java/com/example/demo/controller/CaptchaController.java:
--------------------------------------------------------------------------------
1 | package com.example.demo.controller;
2 |
3 | import org.springframework.stereotype.Controller;
4 | import org.springframework.web.bind.annotation.GetMapping;
5 |
6 | /**
7 | * @author wuchen
8 | * @version 0.1
9 | * @date 2018/9/1 15:50
10 | * @use 访问滑动验证码相关页面
11 | */
12 | @Controller
13 | public class CaptchaController {
14 |
15 | @GetMapping("/captcha")
16 | public String login(){
17 | return "login";
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/main/java/com/example/demo/controller/CaptchaRestController.java:
--------------------------------------------------------------------------------
1 | package com.example.demo.controller;
2 |
3 | import com.alibaba.fastjson.JSON;
4 | import com.example.demo.model.ResponseMessage;
5 | import com.example.demo.service.AutzQueryService;
6 | import com.example.demo.support.CaptchaConfig;
7 | import com.example.demo.support.CaptchaConst;
8 | import com.example.demo.util.*;
9 | import lombok.NonNull;
10 | import org.slf4j.Logger;
11 | import org.slf4j.LoggerFactory;
12 | import org.springframework.beans.factory.annotation.Autowired;
13 | import org.springframework.web.bind.annotation.*;
14 |
15 | import javax.imageio.ImageIO;
16 | import javax.servlet.http.HttpServletRequest;
17 | import javax.servlet.http.HttpServletResponse;
18 | import java.awt.image.BufferedImage;
19 | import java.io.IOException;
20 | import java.io.InputStream;
21 | import java.util.Map;
22 | import java.util.Objects;
23 | import java.util.Random;
24 |
25 | /**
26 | * @author wuchen
27 | * @version 0.1
28 | * @date 2018/9/1 15:54
29 | * @use 滑动验证码rest接口
30 | */
31 | @RestController
32 | @RequestMapping("captcha")
33 | public class CaptchaRestController {
34 |
35 | /**
36 | * 偏移量区间
37 | */
38 | private static final int OFFSET = 4;
39 | /**
40 | * 日志提供器
41 | * Modifiers should be declared in the correct order
42 | */
43 | private static final Logger log = LoggerFactory.getLogger(CaptchaRestController.class);
44 |
45 | @Autowired
46 | private AutzQueryService autzQueryService;
47 |
48 | @Autowired(required = false)
49 | private CaptchaConfig captchaConfig;
50 |
51 | /**
52 | * 判断是否验证成功
53 | *
54 | * @param request 请求来源
55 | * @return 成功则返回验证码,失败则返回失败信息
56 | */
57 | @PostMapping("/checkCaptcha")
58 | public ResponseMessage checkCaptcha(HttpServletRequest request, @NonNull String point) {
59 | if (!UtilString.isNumber(point)) {
60 | log.warn("传入的偏移量为:{}", point);
61 | return ResponseMessage.error("非法参数!");
62 | }
63 | String host = UtilWeb.getIpAddr(request);
64 | Integer veriCode = autzQueryService.getCurrentIdCaptcha(host);
65 | if ((Integer.valueOf(point) < veriCode + OFFSET) && (Integer.valueOf(point) > veriCode - OFFSET)) {
66 | // 验证通过后,生成一个验证码放入缓存并返回给前台
67 | String code = autzQueryService.putCurrentIpCode(host);
68 | //说明验证通过
69 | return ResponseMessage.ok(code);
70 | } else {
71 | return ResponseMessage.error("error");
72 |
73 | }
74 |
75 | }
76 |
77 | /**
78 | * 生成图片
79 | *
80 | * @param request 请求
81 | * @return 返回图片及其对应请求地址
82 | * @throws IOException 丢出异常
83 | */
84 | @PostMapping("/captchaImage")
85 | @ResponseBody
86 | public String captchaImage(HttpServletRequest request) throws IOException {
87 | CaptchaUtil resUtil = new CaptchaUtil();
88 | String hostIp = UtilWeb.getIpAddr(request);
89 | byte[] imageData ;
90 |
91 | // 获取验证码原图
92 | String sourceImageName = getSourceImageName();
93 | String pngName = sourceImageName.substring(sourceImageName.lastIndexOf("/") + 1);
94 | String pngBaseStr = autzQueryService.getCaptchaImageBase64Str(pngName);
95 | InputStream sourceImageInputStream;
96 | if (UtilString.isNotEmpty(pngBaseStr)) {
97 | log.info("从缓存加载了原文件:{}", pngName);
98 | sourceImageInputStream = CaptchaUtil.getInputStreamFromBase64Str(pngBaseStr);
99 | } else {
100 | // 获取对应的流
101 | sourceImageInputStream = getSourceImageInputStream(sourceImageName);
102 | }
103 | if (Objects.nonNull(sourceImageInputStream)) {
104 | imageData = UtilFile.input2byte(sourceImageInputStream);
105 | }else {
106 | log.error("读取原文件异常!");
107 | return null;
108 | }
109 | // 读取文件
110 | Map result = resUtil.createCaptchaImage(hostIp, sourceImageName, imageData);
111 | if (result.size() > 0) {
112 | return JSON.toJSONString(result);
113 | } else {
114 | return null;
115 | }
116 |
117 | }
118 |
119 | private String getSourceImageName() {
120 | Random random = new Random();
121 | // 获取原始图片的完整路径,随机采用一张
122 | int sourceSize = random.nextInt(captchaConfig.getSize());
123 | return UtilString.join(captchaConfig.getPath(), sourceSize, CaptchaConst.PIC_SUFFIX);
124 | }
125 |
126 | /**
127 | * 根据原图文件路径去获取对应的文件流
128 | *
129 | * @param sourceImageName 原图名
130 | * @return 文件流
131 | * @throws IOException 异常
132 | */
133 | private InputStream getSourceImageInputStream(String sourceImageName) throws IOException {
134 | return Resources.getResourceAsStream(sourceImageName);
135 | }
136 |
137 | /**
138 | * 从缓存中去加载某些图片
139 | *
140 | * @param imageName 图片名
141 | * @param response 从缓存中获取的图片
142 | */
143 | @GetMapping("/image/{imageName:.+}")
144 | public void getImage(@PathVariable String imageName, HttpServletResponse response) {
145 | if (UtilString.isNotEmpty(imageName)) {
146 | try {
147 | // 先从缓存中获取是否有对应图片名的base64字符串
148 | String base64Str = autzQueryService.getCaptchaImageBase64Str(imageName);
149 | if (UtilString.isNotEmpty(base64Str)) {
150 | // 有的话则转为图片的输入流,并写出去
151 | InputStream inputStreamFromBase64Str = CaptchaUtil.getInputStreamFromBase64Str(base64Str);
152 | if (Objects.nonNull(inputStreamFromBase64Str)) {
153 | BufferedImage bufferedImage = ImageIO.read(inputStreamFromBase64Str);
154 | ImageIO.write(bufferedImage, "png", response.getOutputStream());
155 | }
156 | }
157 | } catch (IOException e) {
158 | log.error("获取图片失败!");
159 | }
160 | }
161 | }
162 | }
163 |
--------------------------------------------------------------------------------
/src/main/java/com/example/demo/model/BaseBo.java:
--------------------------------------------------------------------------------
1 | package com.example.demo.model;
2 |
3 | import java.io.Serializable;
4 |
5 | /**
6 | * @author itw_wangjb03
7 | * @date 2018/6/12
8 | * sprint by itw_wangjb03:用于
9 | */
10 | public interface BaseBo extends Serializable {
11 | }
12 |
--------------------------------------------------------------------------------
/src/main/java/com/example/demo/model/ResponseMessage.java:
--------------------------------------------------------------------------------
1 | package com.example.demo.model;
2 |
3 | import com.example.demo.util.ExceptionHelper;
4 | import lombok.Getter;
5 | import org.apache.commons.lang3.builder.ReflectionToStringBuilder;
6 | import org.apache.commons.lang3.builder.ToStringStyle;
7 |
8 | /**
9 | * @author itw_wangjb03
10 | * @date 2018/6/12
11 | * sprint by itw_wangjb03:用于
12 | */
13 | @Getter
14 | public class ResponseMessage implements BaseBo {
15 | /**
16 | * 时间戳
17 | */
18 | private Long timestamp;
19 | /**
20 | * 成功状态
21 | */
22 | private Boolean success;
23 | /**
24 | * 状态码
25 | */
26 | private Integer code;
27 | /**
28 | * 消息内容
29 | */
30 | private String message;
31 | /**
32 | * 数据存放字段
33 | */
34 | private Object data;
35 |
36 | //成功构造
37 | public static ResponseMessage ok() {
38 | return ok(null);
39 | }
40 |
41 |
42 | public static ResponseMessage ok(Object data) {
43 | ResponseMessage msg = new ResponseMessage();
44 | msg.timeStamp().code(200).data(data).success(Boolean.TRUE);
45 | return msg;
46 | }
47 |
48 |
49 | //失败构造
50 | public static ResponseMessage error(Exception ex) {
51 | return error(500, ExceptionHelper.getBootMessage(ex));
52 | }
53 |
54 | public static ResponseMessage error(String message) {
55 | return error(500, message);
56 | }
57 |
58 |
59 | public static ResponseMessage error(int code, String message) {
60 | ResponseMessage msg = new ResponseMessage();
61 | msg.code(code).message(message).timeStamp().success(Boolean.FALSE);
62 | return msg;
63 | }
64 |
65 |
66 | //设置报文头信息
67 | private ResponseMessage timeStamp() {
68 | this.timeStamp(System.currentTimeMillis());
69 | return this;
70 | }
71 |
72 | private ResponseMessage timeStamp(Long timeStamp) {
73 | this.timestamp = timeStamp;
74 | return this;
75 | }
76 |
77 | public ResponseMessage success(Boolean success){
78 | this.success=success;
79 | return this;
80 | }
81 |
82 | public ResponseMessage code(int code) {
83 | this.code = code;
84 | return this;
85 | }
86 |
87 | public ResponseMessage message(String message) {
88 | this.message = message;
89 | return this;
90 | }
91 |
92 |
93 | //设置数据信息
94 | public ResponseMessage data(T data) {
95 | //处理分页信息
96 | this.data = data;
97 | return this;
98 | }
99 |
100 | @Override
101 | public String toString() {
102 | return ReflectionToStringBuilder.toString(this , ToStringStyle.SHORT_PREFIX_STYLE );
103 | }
104 |
105 |
106 | }
107 |
--------------------------------------------------------------------------------
/src/main/java/com/example/demo/service/AutzQueryService.java:
--------------------------------------------------------------------------------
1 | package com.example.demo.service;
2 |
3 | /**
4 | * @author itw_wangjb03
5 | * @date 2018/5/4
6 | * sprint13 by itw_wangjb03:用于处理验证码
7 | */
8 | public interface AutzQueryService {
9 | /**
10 | * 获取当前IP验证码
11 | * @param host 当前IP
12 | * @return 验证码
13 | */
14 | String getCurrentIPCode(String host);
15 |
16 | /**
17 | * 获取对应IP地址的滑动验证码的距离
18 | * @param host IP
19 | * @return 距离
20 | */
21 | Integer getCurrentIdCaptcha(String host);
22 |
23 | /**
24 | * 给当前IP放置一个验证码
25 | * @param host IP
26 | */
27 | String putCurrentIpCode(String host);
28 |
29 | /**
30 | * 获取存放在redis中的某个图片的base64编码
31 | * @param imageName
32 | * @return
33 | */
34 | String getCaptchaImageBase64Str(String imageName);
35 |
36 | /**
37 | * 移除当前IP的验证码
38 | * @param host 当前IP
39 | */
40 | void removeCurrentIPCode(String host);
41 | }
42 |
--------------------------------------------------------------------------------
/src/main/java/com/example/demo/service/impl/AutzQueryServiceImpl.java:
--------------------------------------------------------------------------------
1 | package com.example.demo.service.impl;
2 |
3 | import com.example.demo.service.AutzQueryService;
4 | import com.example.demo.support.CaptchaConst;
5 | import com.example.demo.support.cache.CacheManagerHolder;
6 | import com.example.demo.util.UtilString;
7 | import org.slf4j.Logger;
8 | import org.slf4j.LoggerFactory;
9 | import org.springframework.cache.Cache;
10 | import org.springframework.cache.CacheManager;
11 | import org.springframework.stereotype.Service;
12 |
13 | import java.util.Objects;
14 | import java.util.UUID;
15 |
16 | /**
17 | * @author itw_wangjb03
18 | * @date 2018/5/4
19 | * sprint13 by itw_wangjb03:用于处理当前IP的验证码
20 | */
21 | @Service
22 | public class AutzQueryServiceImpl implements AutzQueryService {
23 |
24 | private static final Logger log = LoggerFactory.getLogger(AutzQueryServiceImpl.class);
25 |
26 |
27 | /**
28 | * 获取当前IP验证码
29 | * sprint by itw_wangjb03:获取当前IP的验证码
30 | *
31 | * @param host 当前IP
32 | * @return 验证码
33 | */
34 | @Override
35 | public String getCurrentIPCode(String host) {
36 | String verificationCode = null;
37 | // 先去取cache块
38 | Cache cache = CacheManagerHolder.getManager().getCache(CaptchaConst.VERIFICATION_CODE);
39 | if (cache != null) {
40 | // 再取当前IP
41 | Cache.ValueWrapper wrapper = cache.get(host);
42 | if (wrapper != null) {
43 | // 有值就返回出去
44 | verificationCode = wrapper.get().toString();
45 | }
46 | }
47 | return verificationCode;
48 | }
49 |
50 | @Override
51 | public Integer getCurrentIdCaptcha(String host) {
52 | String verificationCode = null;
53 | // 先去取cache块
54 | Cache cache = CacheManagerHolder.getManager().getCache(CaptchaConst.VERIFICATION_CODE);
55 | if (cache != null) {
56 | // 再取当前IP
57 | Cache.ValueWrapper wrapper = cache.get(host);
58 | if (wrapper != null) {
59 | // 有值就返回出去
60 | verificationCode = wrapper.get().toString();
61 | }
62 | }
63 | if (UtilString.isNotEmpty(verificationCode)) {
64 | return Integer.parseInt(verificationCode);
65 | }
66 | return 0;
67 | }
68 |
69 | /**
70 | * 给当前IP放置一个验证码
71 | *
72 | * @param host IP
73 | */
74 | @Override
75 | public String putCurrentIpCode(String host) {
76 | String veriCode = "";
77 | if (UtilString.isNotEmpty(host)) {
78 | String code = UUID.randomUUID().toString().replaceAll("-", "").substring(0, 6);
79 | CacheManager manager = CacheManagerHolder.getManager();
80 | if (Objects.nonNull(manager)) {
81 | Cache cache = manager.getCache(CaptchaConst.VERIFICATION_CODE);
82 | if (Objects.nonNull(cache)) {
83 | cache.put(host, code);
84 | }
85 | }
86 | veriCode = code;
87 | }
88 | return veriCode;
89 | }
90 |
91 | /**
92 | * 获取存放在redis中的某个图片的base64编码
93 | *
94 | * @param imageName
95 | * @return
96 | */
97 | @Override
98 | public String getCaptchaImageBase64Str(String imageName) {
99 | String base64Str = null;
100 | // 先去取cache块
101 | Cache cache = CacheManagerHolder.getManager().getCache(CaptchaConst.CACHE_CAPTCHA_IMG);
102 | if (cache != null) {
103 | // 再取当前IP
104 | Cache.ValueWrapper wrapper = cache.get(imageName);
105 | if (wrapper != null) {
106 | log.info("验证码图片从缓存加载成功:{}", imageName);
107 | // 有值就返回出去
108 | base64Str = wrapper.get().toString();
109 | }
110 | }
111 | return base64Str;
112 | }
113 |
114 | /**
115 | * 移除当前IP的验证码
116 | * sprint13 by itw_wangjb03:用于清除当前IP的验证码
117 | *
118 | * @param host 当前IP
119 | */
120 | @Override
121 | public void removeCurrentIPCode(String host) {
122 | // 先去取cache块
123 | Cache cache = CacheManagerHolder.getManager().getCache(CaptchaConst.VERIFICATION_CODE);
124 | if (cache != null) {
125 | // 再取当前IP
126 | Cache.ValueWrapper wrapper = cache.get(host);
127 | if (wrapper != null) {
128 | // 判断是否有验证码
129 | String verificationCode = wrapper.get().toString();
130 | // 有的话就移除该验证码
131 | if (UtilString.isNotEmpty(verificationCode)) {
132 | cache.evict(host);
133 | }
134 | }
135 | }
136 | }
137 |
138 | }
139 |
--------------------------------------------------------------------------------
/src/main/java/com/example/demo/support/CaptchaConfig.java:
--------------------------------------------------------------------------------
1 | package com.example.demo.support;
2 |
3 | import org.springframework.boot.context.properties.ConfigurationProperties;
4 |
5 | /**
6 | * @author wuchenl
7 | * @date 2018/12/8.
8 | * 读取滑动验证码原始图片所在路径和数量
9 | */
10 | @ConfigurationProperties(prefix = "com.letters7.wuchen.captcha.source")
11 | public class CaptchaConfig {
12 | private String path;
13 | private Integer size;
14 |
15 | public String getPath() {
16 | return path;
17 | }
18 |
19 | public void setPath(String path) {
20 | this.path = path;
21 | }
22 |
23 | public Integer getSize() {
24 | return size;
25 | }
26 |
27 | public void setSize(Integer size) {
28 | this.size = size;
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/main/java/com/example/demo/support/CaptchaConst.java:
--------------------------------------------------------------------------------
1 | package com.example.demo.support;
2 |
3 | /**
4 | * @author itw_wangjb03
5 | * @date 2018/9/21
6 | * sprint by itw_wangjb03:用于
7 | */
8 | public class CaptchaConst {
9 |
10 | /**
11 | * 中划线
12 | */
13 | public static final String MIDDLE_LINE="-";
14 |
15 | /**
16 | * 验证码在缓存中的key
17 | */
18 | public static final String CAPTCHA="captcha";
19 |
20 | /**
21 | * 缓存中的集合名
22 | */
23 | public static final String VERIFICATION_CODE="verificationCode";
24 |
25 | /**
26 | * 图片的缓存集合名
27 | */
28 | public static final String CACHE_CAPTCHA_IMG="captchaImage";
29 |
30 | /**
31 | * 生成图片后缀
32 | */
33 | public static final String PIC_SUFFIX = ".png";
34 | }
35 |
--------------------------------------------------------------------------------
/src/main/java/com/example/demo/support/cache/CacheConfig.java:
--------------------------------------------------------------------------------
1 | package com.example.demo.support.cache;
2 |
3 | import com.google.common.collect.Maps;
4 | import org.springframework.boot.autoconfigure.cache.CacheType;
5 | import org.springframework.boot.context.properties.ConfigurationProperties;
6 |
7 | import java.util.Map;
8 |
9 | /**
10 | * @author wuchen
11 | * @version 0.1
12 | * @date 2018/12/13 14:36
13 | * @use
14 | */
15 | @ConfigurationProperties(prefix = "spring.cache")
16 | public class CacheConfig {
17 |
18 | /**
19 | * 缓存类型
20 | */
21 | private CacheType type;
22 | /**
23 | * 扩展了springboot默认的List cacheNames方式,支持过期时间的设置
24 | */
25 | private Map cacheNames= Maps.newConcurrentMap();
26 |
27 | public CacheType getType() {
28 | return type;
29 | }
30 |
31 | public CacheConfig setType(CacheType type) {
32 | this.type = type;
33 | return this;
34 | }
35 |
36 | public Map getCacheNames() {
37 | return cacheNames;
38 | }
39 |
40 | public CacheConfig setCacheNames(Map cacheNames) {
41 | this.cacheNames = cacheNames;
42 | return this;
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/src/main/java/com/example/demo/support/cache/CacheManagerHolder.java:
--------------------------------------------------------------------------------
1 | package com.example.demo.support.cache;
2 |
3 |
4 | import com.example.demo.util.NamedThreadFactory;
5 | import org.slf4j.Logger;
6 | import org.slf4j.LoggerFactory;
7 | import org.springframework.beans.factory.annotation.Autowired;
8 | import org.springframework.cache.CacheManager;
9 | import org.springframework.cache.concurrent.ConcurrentMapCacheManager;
10 | import org.springframework.stereotype.Component;
11 |
12 | import javax.annotation.PostConstruct;
13 | import java.util.concurrent.Executors;
14 | import java.util.concurrent.ScheduledExecutorService;
15 | import java.util.concurrent.ScheduledFuture;
16 | import java.util.concurrent.TimeUnit;
17 |
18 | /**
19 | * @author zoubin02
20 | */
21 | @Component
22 | public class CacheManagerHolder {
23 |
24 | private static final Logger logger = LoggerFactory.getLogger(CacheManagerHolder.class);
25 |
26 | // 定时任务执行器
27 | private final ScheduledExecutorService scheduledExecutorService
28 | = Executors.newScheduledThreadPool(1, new NamedThreadFactory("PRINT_CACHE", true));
29 |
30 | private ScheduledFuture> sendFuture = null;
31 |
32 | private int monitorInterval = 60*60;
33 |
34 |
35 | //单例模式
36 | private CacheManagerHolder(){
37 |
38 | }
39 | private static CacheManagerHolder instance;
40 | public static CacheManagerHolder getInstance(){
41 | if(instance==null){
42 | instance = new CacheManagerHolder();
43 | }
44 | return instance;
45 | }
46 |
47 |
48 | @Autowired(required = false)
49 | private CacheManager cacheManager;
50 |
51 | public static CacheManager target;
52 |
53 | public static final CacheManager getManager() {
54 | return target;
55 | }
56 |
57 | @PostConstruct
58 | public void init() {
59 | //如果系统没有配置cacheManager,则使用ConcurrentMapCacheManager
60 | if (cacheManager == null) {
61 | cacheManager = new ConcurrentMapCacheManager();
62 | }
63 |
64 | if (target == null){
65 | target = cacheManager;
66 | logger.info("系统选择了缓存-{}",cacheManager.getClass());
67 | }
68 |
69 | //
70 | sendFuture = scheduledExecutorService.scheduleWithFixedDelay(() -> {
71 | // 收集统计信息
72 | try {
73 | send();
74 | } catch (Throwable t) { // 防御性容错
75 | logger.error("Unexpected error occur at send statistic, cause: " + t.getMessage(), t);
76 | }
77 | }, monitorInterval, monitorInterval, TimeUnit.MINUTES);
78 | }
79 |
80 |
81 | private void send() {
82 | logger.info("");
83 | cacheManager.getCacheNames().forEach(
84 | cacheName -> {
85 | logger.info("["+cacheName + "]-------->{}",cacheManager.getCache(cacheName).getClass());
86 | }
87 | );
88 | logger.info("");
89 | }
90 |
91 |
92 |
93 | public void destroy(){
94 | if(sendFuture!=null){
95 | sendFuture.cancel(true);
96 | }
97 | }
98 |
99 |
100 |
101 |
102 | }
103 |
--------------------------------------------------------------------------------
/src/main/java/com/example/demo/support/cache/CaffeineAutoConfiguration.java:
--------------------------------------------------------------------------------
1 | package com.example.demo.support.cache;
2 |
3 | import com.github.benmanes.caffeine.cache.Caffeine;
4 | import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
5 | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
6 | import org.springframework.boot.context.properties.EnableConfigurationProperties;
7 | import org.springframework.cache.CacheManager;
8 | import org.springframework.cache.annotation.CachingConfigurerSupport;
9 | import org.springframework.cache.annotation.EnableCaching;
10 | import org.springframework.cache.caffeine.CaffeineCache;
11 | import org.springframework.cache.support.SimpleCacheManager;
12 | import org.springframework.context.annotation.Bean;
13 | import org.springframework.context.annotation.ComponentScan;
14 | import org.springframework.context.annotation.Configuration;
15 |
16 | import java.util.ArrayList;
17 | import java.util.Iterator;
18 | import java.util.Map;
19 | import java.util.concurrent.TimeUnit;
20 |
21 | /**
22 | * @author wuchen
23 | * @version 0.1
24 | * @date 2018/12/13 14:41
25 | * @use
26 | */
27 | @EnableCaching
28 | @Configuration
29 | @EnableConfigurationProperties(CacheConfig.class)
30 | @ComponentScan("com.example.demo.support.cache")
31 | public class CaffeineAutoConfiguration extends CachingConfigurerSupport {
32 | @Bean
33 | @ConditionalOnClass(CaffeineCache.class)
34 | @ConditionalOnProperty(prefix = "spring.cache", name = "caffeine", matchIfMissing = true)
35 | public CacheManager caffeineCacheManager(CacheConfig cacheConfig) {
36 |
37 | SimpleCacheManager simpleCacheManager = new SimpleCacheManager();
38 | ArrayList caches = new ArrayList<>();
39 | Map cacheNames = cacheConfig.getCacheNames();
40 | Iterator cacheNameIter = cacheNames.keySet().iterator();
41 | while (cacheNameIter.hasNext()) {
42 | String cacheName = cacheNameIter.next();
43 | Long outTime = cacheNames.get(cacheName);
44 | caches.add(new CaffeineCache(cacheName,Caffeine.newBuilder().recordStats().expireAfterWrite(outTime,TimeUnit.SECONDS).build()));
45 | }
46 | simpleCacheManager.setCaches(caches);
47 | return simpleCacheManager;
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/src/main/java/com/example/demo/util/CaptchaUtil.java:
--------------------------------------------------------------------------------
1 | package com.example.demo.util;
2 |
3 | import com.example.demo.support.CaptchaConst;
4 | import com.example.demo.support.cache.CacheManagerHolder;
5 | import com.google.common.collect.Maps;
6 | import org.apache.commons.codec.binary.Base64;
7 | import org.apache.commons.codec.digest.DigestUtils;
8 | import org.apache.commons.io.IOUtils;
9 | import org.apache.commons.lang.math.RandomUtils;
10 | import org.slf4j.Logger;
11 | import org.slf4j.LoggerFactory;
12 | import org.springframework.cache.Cache;
13 | import org.springframework.cache.CacheManager;
14 |
15 | import javax.imageio.ImageIO;
16 | import javax.imageio.ImageReadParam;
17 | import javax.imageio.ImageReader;
18 | import javax.imageio.stream.ImageInputStream;
19 | import java.awt.*;
20 | import java.awt.geom.Arc2D;
21 | import java.awt.geom.Area;
22 | import java.awt.geom.Rectangle2D;
23 | import java.awt.image.BufferedImage;
24 | import java.io.*;
25 | import java.util.*;
26 | import java.util.List;
27 |
28 | /**
29 | * @author wuchenl
30 | * @date 2018/12/8.
31 | */
32 | public class CaptchaUtil {
33 | private static Logger log = LoggerFactory.getLogger(CaptchaUtil.class);
34 |
35 | /**
36 | * 将我们配置的图片原始路径和数量配置注入进来使用
37 | */
38 |
39 | /**
40 | * 阴影宽度
41 | */
42 | private static final int SHADOW_WIDTH = 2;
43 | /**
44 | * 图片边缘亮色(黄色)宽度。
45 | */
46 | private static final int LIGHT_HEIGHT_WIDTH = 2;
47 | /**
48 | * 圆弧直径
49 | */
50 | private static final int ARC = 10;
51 | /**
52 | * 生成图片后缀
53 | */
54 | private static final String PIC_SUFFIX = ".png";
55 | /**
56 | * 生成图片后缀
57 | */
58 | private static final String PNG_SUFFIX = "png";
59 |
60 | private static final Color clrGlowInnerHi = new Color(253, 239, 175, 148);
61 | private static final Color clrGlowInnerLo = new Color(255, 209, 0);
62 | private static final Color clrGlowOuterHi = new Color(253, 239, 175, 124);
63 | private static final Color clrGlowOuterLo = new Color(255, 179, 0);
64 |
65 |
66 | //----------------临时全局变量区域----------------------
67 |
68 | /**
69 | * 小图的宽---剪裁的图的宽度
70 | */
71 | private int tailoringWidth = 50;
72 | /**
73 | * 小图的高---剪裁的图的高度
74 | */
75 | private int tailoringHeight = 50;
76 | /**
77 | * 随机X位置---位于原图的X位置
78 | */
79 | private int locationX = 0;
80 | /**
81 | * 随机Y位置----位于原图的Y位置
82 | */
83 | private int locationY = 0;
84 |
85 | /**
86 | * 根据传入的文件流以及文件名,生成对应的验证码模块以及对应的缓存获取key
87 | *
88 | * @param host 请求ip地址
89 | * @param sourceImageName 请求对应的原图
90 | * @param imageData 原文件图片对应的数组
91 | * @return 对应的相关存取key
92 | * @throws IOException
93 | */
94 | public Map createCaptchaImage(String host, String sourceImageName, byte[] imageData) throws IOException {
95 | String sourceName = sourceImageName.substring(sourceImageName.lastIndexOf("/")+1);
96 | Map resultMap = Maps.newConcurrentMap();
97 | log.info("开始创建滑动验证码相关图片----请求地址为:{}", host);
98 | // 获取原始图片的完整路径,随机采用一张
99 | InputStream sourceImageInputStream = UtilFile.byte2Input(imageData);
100 | if (Objects.isNull(sourceImageInputStream)) {
101 | log.warn("读取原始图片异常:{}", sourceImageName);
102 | return resultMap;
103 | }
104 |
105 | // 读取原始图片大小。并判断是否符合预设值
106 | BufferedImage bufferedImage = ImageIO.read(sourceImageInputStream);
107 | int width = bufferedImage.getWidth();
108 | int height = bufferedImage.getHeight();
109 | if (width <= tailoringWidth * 2 || height <= tailoringHeight) {
110 | log.warn("原始图片不符合默认尺寸:{}*{}", width, height);
111 | return resultMap;
112 | }
113 | // 这里是控制剪裁图片生成的区域。尽量位于中间。同时。图片大于100*50
114 | Random random = new Random();
115 | this.locationX = random.nextInt(width - tailoringWidth * 2) + tailoringWidth;
116 | this.locationY = random.nextInt(height - tailoringHeight);
117 | // 获取裁剪小图
118 | BufferedImage tailoringImageBuffer = tailoringImage(imageData);
119 |
120 | //创建shape区域
121 | List shapes = createSmallShape();
122 | if (shapes.isEmpty()) {
123 | log.error("生成剪裁小图随机形状异常!");
124 | return resultMap;
125 | }
126 |
127 | Shape area = shapes.get(0);
128 | Shape bigarea = shapes.get(1);
129 | //创建图层用于处理小图的阴影
130 | BufferedImage bfm1 = new BufferedImage(tailoringWidth, tailoringHeight, BufferedImage.TYPE_INT_ARGB);
131 | //创建图层用于处理大图的凹槽
132 | BufferedImage bfm2 = new BufferedImage(tailoringWidth, tailoringHeight, BufferedImage.TYPE_INT_ARGB);
133 | for (int i = 0; i < tailoringWidth; i++) {
134 | for (int j = 0; j < tailoringHeight; j++) {
135 | if (area.contains(i, j)) {
136 | bfm1.setRGB(i, j, tailoringImageBuffer.getRGB(i, j));
137 | }
138 | if (bigarea.contains(i, j)) {
139 | bfm2.setRGB(i, j, Color.black.getRGB());
140 | }
141 | }
142 | }
143 | //处理图片的边缘高亮及其阴影效果
144 | BufferedImage resultImgBuff = dealLightAndShadow(bfm1, area);
145 | //生成大小图随机名称
146 | String smallFileName = createSmallImg(resultImgBuff);
147 | //将灰色图当做水印印到原图上
148 | String bigImgName = createBigImg(bfm2, sourceImageName);
149 | if (smallFileName == null) {
150 | return null;
151 | }
152 | resultMap.put("smallImgName", smallFileName);
153 | resultMap.put("bigImgName", bigImgName);
154 | resultMap.put("location_y", String.valueOf(locationY));
155 | resultMap.put("sourceImgName", sourceName);
156 |
157 | // 拼接放入redis的key
158 | // host = UtilString.join(host, CaptchaConst.MIDDLE_LINE, CaptchaConst.CAPTCHA);
159 |
160 | InputStream sourceInput = UtilFile.byte2Input(imageData);
161 | String sourcePngBase64 = getBase64FromInputStream(sourceInput);
162 | boolean cacheFlag;
163 | cacheFlag = putDataToCache(CaptchaConst.CACHE_CAPTCHA_IMG, sourceName, sourcePngBase64);
164 | if (!cacheFlag) {
165 | log.error("加载原始图片进缓存异常!");
166 | return null;
167 | }
168 | String point=String.valueOf(locationX);
169 | //将x 轴位置作为验证码 放入到redis中,key为IP-captcha
170 | cacheFlag = putDataToCache(CaptchaConst.VERIFICATION_CODE, host, point);
171 | if (!cacheFlag) {
172 | log.error("加载验证图片偏移量进缓存异常!");
173 | return null;
174 | }
175 | return resultMap;
176 | }
177 |
178 |
179 | /**
180 | * 创建小图
181 | *
182 | * @param resultImgBuff
183 | * @return
184 | */
185 | private String createSmallImg(BufferedImage resultImgBuff) {
186 | String smallFileName = randomImgName("small_source_");
187 |
188 | // 图片流先转输入流
189 | InputStream inputStream = getInputStreamFromBufferedImage(resultImgBuff, PNG_SUFFIX);
190 | if (Objects.isNull(inputStream)) {
191 | log.warn("生成小图失败:转inputStream流失败!");
192 | return null;
193 | }
194 | // 然后转为base64编码
195 | String smallPngBase64 = getBase64FromInputStream(inputStream);
196 | if (UtilString.isEmpty(smallPngBase64)) {
197 | log.warn("生成小图失败:转base64编码失败!");
198 | return null;
199 | }
200 | // 最后放入cache
201 | boolean cacheFlag = putDataToCache(CaptchaConst.CACHE_CAPTCHA_IMG, smallFileName, smallPngBase64);
202 | if (!cacheFlag) {
203 | log.error("加载小图进缓存异常!");
204 | return null;
205 | }
206 | return smallFileName;
207 | }
208 |
209 |
210 | /**
211 | * 创建大图,即带小图水印缺口的图片
212 | *
213 | * @param sourceImageBuffer 大图,拼图的buffer
214 | * @param sourceName 源文件
215 | * @return 大图的buffer
216 | * @throws IOException
217 | */
218 | private String createBigImg(BufferedImage sourceImageBuffer, String sourceName) throws IOException {
219 | //创建一个灰度化图层, 将生成的小图,覆盖到该图层,使其灰度化,用于作为一个水印图
220 | String bigImgName = randomImgName("big_source_");
221 | //将灰度化之后的图片,整合到原有图片上
222 | BufferedImage bigImg = addWatermark(sourceName, sourceImageBuffer, 0.6F);
223 | // 转is流
224 | InputStream inputStream = getInputStreamFromBufferedImage(bigImg, "png");
225 | if (Objects.isNull(inputStream)) {
226 | log.warn("生成大图失败:转inputStream流失败!");
227 | return null;
228 | }
229 | //转base64编码
230 | String bigPngBase64 = getBase64FromInputStream(inputStream);
231 | if (UtilString.isEmpty(bigPngBase64)) {
232 | log.warn("生成大图失败:转base64编码失败!");
233 | return null;
234 | }
235 | //存入redis
236 | boolean cacheFlag = putDataToCache(CaptchaConst.CACHE_CAPTCHA_IMG, bigImgName, bigPngBase64);
237 | if (!cacheFlag) {
238 | log.error("加载大图进缓存异常!");
239 | return null;
240 | }
241 | return bigImgName;
242 | }
243 |
244 | /**
245 | * 添加水印
246 | *
247 | * @param sourceName
248 | * @param smallImage
249 | * @param alpha
250 | * @return
251 | * @throws IOException
252 | */
253 | private BufferedImage addWatermark(String sourceName, BufferedImage smallImage, float alpha) throws IOException {
254 | InputStream inputStream = Resources.getResourceAsStream(sourceName);
255 | BufferedImage source = ImageIO.read(inputStream);
256 | Graphics2D graphics2D = source.createGraphics();
257 | graphics2D.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_ATOP, alpha));
258 | graphics2D.drawImage(smallImage, locationX, locationY, null);
259 | graphics2D.dispose(); //释放
260 | return source;
261 | }
262 |
263 | /**
264 | * 生成大小图片文件名
265 | *
266 | * @param suf 文件名称
267 | * @return 根据位置生成后的
268 | */
269 | private String randomImgName(String suf) {
270 | //按照坐标位生成图片
271 | return suf + locationY + "_" + DigestUtils.md5Hex(String.valueOf(locationX)).substring(0, 16) + PIC_SUFFIX;
272 | }
273 |
274 | /**
275 | * 生成 点随机形状
276 | *
277 | * @return
278 | */
279 | private List createSmallShape() {
280 | //处理小图,在4个方向上 随机找到2个方向添加凸出
281 | //凸出1
282 | int face1 = RandomUtils.nextInt(3);
283 | //凸出2
284 | int face2;
285 | //使凸出1 与 凸出2不在同一个方向
286 | while (true) {
287 | face2 = RandomUtils.nextInt(3);
288 | if (face1 != face2) {
289 | break;
290 | }
291 | }
292 | //生成随机区域值, (10-20)之间
293 | int position1 = RandomUtils.nextInt((tailoringHeight - ARC * 2) / 2) + (tailoringHeight - ARC * 2) / 2;
294 | Shape shape1 = createShape(face1, 0, position1);
295 | Shape bigshape1 = createShape(face1, 2, position1);
296 |
297 | //生成中间正方体Shape, (具体边界+弧半径 = x坐标位)
298 | Shape centre = new Rectangle2D.Float(ARC, ARC, tailoringWidth - 2 * 10, tailoringHeight - 2 * 10);
299 | int position2 = RandomUtils.nextInt((tailoringHeight - ARC * 2) / 2) + (tailoringHeight - ARC * 2) / 2;
300 | Shape shape2 = createShape(face2, 0, position2);
301 |
302 | //因为后边图形需要生成阴影, 所以生成的小图shape + 阴影宽度 = 灰度化的背景小图shape(即大图上的凹槽)
303 | Shape bigshape2 = createShape(face2, SHADOW_WIDTH / 2, position2);
304 | Shape bigcentre = new Rectangle2D.Float(10 - SHADOW_WIDTH / 2, 10 - SHADOW_WIDTH / 2, 30 + SHADOW_WIDTH, 30 + SHADOW_WIDTH);
305 |
306 | //合并Shape
307 | Area area = new Area(centre);
308 | area.add(new Area(shape1));
309 | area.add(new Area(shape2));
310 | //合并大Shape
311 | Area bigarea = new Area(bigcentre);
312 | bigarea.add(new Area(bigshape1));
313 | bigarea.add(new Area(bigshape2));
314 | List list = new ArrayList<>();
315 | list.add(area);
316 | list.add(bigarea);
317 | return list;
318 | }
319 |
320 | /**
321 | * 对图片进行裁剪
322 | *
323 | * @param imageData 剪裁前原始图片流
324 | * @return 裁剪之后的图片Buffered
325 | * @throws IOException
326 | */
327 | private BufferedImage tailoringImage(byte[] imageData) throws IOException {
328 | Iterator iterator = ImageIO.getImageReadersByFormatName(PNG_SUFFIX);
329 | ImageReader render = (ImageReader) iterator.next();
330 | InputStream sourceInputStream = UtilFile.byte2Input(imageData);
331 | if (Objects.isNull(sourceInputStream)) {
332 | log.info("剪裁图片时获取原图文件流异常!");
333 | throw new IOException("剪裁图片时获取原图文件流异常");
334 | }
335 | InputStream inputStream=UtilFile.byte2Input(imageData);
336 | ImageInputStream in = ImageIO.createImageInputStream(inputStream);
337 | render.setInput(in, true);
338 | BufferedImage tailoringImageBuffer;
339 | try {
340 | ImageReadParam param = render.getDefaultReadParam();
341 | Rectangle rect = new Rectangle(locationX, locationY, tailoringWidth, tailoringHeight);
342 | param.setSourceRegion(rect);
343 | tailoringImageBuffer = render.read(0, param);
344 | } finally {
345 |
346 | try {
347 | in.close();
348 | inputStream.close();
349 | sourceInputStream.close();
350 | } catch (Exception e) {
351 | log.error("关闭流出现异常{}",e);
352 | e.printStackTrace();
353 | }
354 | }
355 | return tailoringImageBuffer;
356 | }
357 |
358 |
359 | /**
360 | * 创建圆形区域, 半径为5 type , 0:上方,1:右方 2:下方,3:左方
361 | *
362 | * @param type
363 | * @param size
364 | * @param position
365 | * @return
366 | */
367 | private Shape createShape(int type, int size, int position) {
368 | Arc2D.Float d;
369 | if (type == 0) {
370 | //上
371 | d = new Arc2D.Float(position, 5, 10 + size, 10 + size, 0, 190, Arc2D.CHORD);
372 | } else if (type == 1) {
373 | //右
374 | d = new Arc2D.Float(35, position, 10 + size, 10 + size, 270, 190, Arc2D.CHORD);
375 | } else if (type == 2) {
376 | //下
377 | d = new Arc2D.Float(position, 35, 10 + size, 10 + size, 180, 190, Arc2D.CHORD);
378 | } else if (type == 3) {
379 | //左
380 | d = new Arc2D.Float(5, position, 10 + size, 10 + size, 90, 190, Arc2D.CHORD);
381 | } else {
382 | d = new Arc2D.Float(5, position, 10 + size, 10 + size, 90, 190, Arc2D.CHORD);
383 | }
384 | return d;
385 | }
386 |
387 |
388 | /**
389 | * 处理小图的边缘灯光及其阴影效果
390 | *
391 | * @param bfm
392 | * @param shape
393 | * @return
394 | */
395 | private BufferedImage dealLightAndShadow(BufferedImage bfm, Shape shape) {
396 | //创建新的透明图层,该图层用于边缘化阴影, 将生成的小图合并到该图上
397 | BufferedImage buffimg = ((Graphics2D) bfm.getGraphics()).getDeviceConfiguration().createCompatibleImage(50, 50, Transparency.TRANSLUCENT);
398 | Graphics2D graphics2D = buffimg.createGraphics();
399 | Graphics2D g2 = (Graphics2D) bfm.getGraphics();
400 | //原有小图,边缘亮色处理
401 | paintBorderGlow(g2, LIGHT_HEIGHT_WIDTH, shape);
402 | //新图层添加阴影
403 | paintBorderShadow(graphics2D, SHADOW_WIDTH, shape);
404 | graphics2D.drawImage(bfm, 0, 0, null);
405 | return buffimg;
406 | }
407 |
408 | /**
409 | * 处理边缘亮色
410 | *
411 | * @param g2
412 | * @param glowWidth
413 | * @param clipShape
414 | */
415 | private void paintBorderGlow(Graphics2D g2, int glowWidth, Shape clipShape) {
416 | int gw = glowWidth * 2;
417 | for (int i = gw; i >= 2; i -= 2) {
418 | float pct = (float) (gw - i) / (gw - 1);
419 | Color mixHi = getMixedColor(clrGlowInnerHi, pct, clrGlowOuterHi, 1.0f - pct);
420 | Color mixLo = getMixedColor(clrGlowInnerLo, pct, clrGlowOuterLo, 1.0f - pct);
421 | g2.setPaint(new GradientPaint(0.0f, 35 * 0.25f, mixHi, 0.0f, 35, mixLo));
422 | g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_ATOP, pct));
423 | g2.setStroke(new BasicStroke(i));
424 | g2.draw(clipShape);
425 | }
426 | }
427 |
428 | /**
429 | * 处理阴影
430 | *
431 | * @param g2
432 | * @param shadowWidth
433 | * @param clipShape
434 | */
435 | private void paintBorderShadow(Graphics2D g2, int shadowWidth, Shape clipShape) {
436 | g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
437 | int sw = shadowWidth * 2;
438 | for (int i = sw; i >= 2; i -= 2) {
439 | float pct = (float) (sw - i) / (sw - 1);
440 | //pct<03. 用于去掉阴影边缘白边, pct>0.8用于去掉过深的色彩, 如果使用Color.lightGray. 可去掉pct>0.8
441 | if (pct < 0.3 || pct > 0.8) {
442 | continue;
443 | }
444 | g2.setColor(getMixedColor(new Color(54, 54, 54), pct, Color.WHITE, 1.0f - pct));
445 | g2.setStroke(new BasicStroke(i));
446 | g2.draw(clipShape);
447 | }
448 | }
449 |
450 | /**
451 | * 加点颜色更明显
452 | *
453 | * @param c1
454 | * @param pct1
455 | * @param c2
456 | * @param pct2
457 | * @return
458 | */
459 | private static Color getMixedColor(Color c1, float pct1, Color c2, float pct2) {
460 | float[] clr1 = c1.getComponents(null);
461 | float[] clr2 = c2.getComponents(null);
462 | for (int i = 0; i < clr1.length; i++) {
463 | clr1[i] = (clr1[i] * pct1) + (clr2[i] * pct2);
464 | }
465 | return new Color(clr1[0], clr1[1], clr1[2], clr1[3]);
466 | }
467 |
468 | /**
469 | * 图片转base64
470 | *
471 | * @param inputStream 图片的输入流
472 | * @return 字符串
473 | */
474 | public static String getBase64FromInputStream(InputStream inputStream) {
475 | if (Objects.isNull(inputStream)) {
476 | return null;
477 | }
478 | byte[] data;
479 | try {
480 | ByteArrayOutputStream arrayOutputStream = new ByteArrayOutputStream();
481 | byte[] buffer = new byte[100];
482 | int rc = 0;
483 | while ((rc = inputStream.read(buffer, 0, 100)) > 0) {
484 | arrayOutputStream.write(buffer, 0, rc);
485 | }
486 | data = arrayOutputStream.toByteArray();
487 | } catch (IOException e) {
488 | log.error("图片流转base64编码异常:{}", e);
489 | return null;
490 | } finally {
491 | IOUtils.closeQuietly(inputStream);
492 | }
493 | return new String(org.apache.commons.codec.binary.Base64.encodeBase64(data));
494 | }
495 |
496 | /**
497 | * base64转字节流
498 | *
499 | * @param base64Str base64字符串
500 | * @return 流
501 | */
502 | public static InputStream getInputStreamFromBase64Str(String base64Str) {
503 | if (UtilString.isEmpty(base64Str)) {
504 | return null;
505 | }
506 | byte[] bytes = Base64.decodeBase64(base64Str);
507 | if (Objects.isNull(bytes) || bytes.length == 0) {
508 | return null;
509 | }
510 | return new ByteArrayInputStream(bytes);
511 | }
512 |
513 | /**
514 | * bufferedImage 转为普通的InputStream
515 | *
516 | * @param bufferedImage bufferedImage流
517 | * @param fileType 图片类型
518 | * @return InputStream流
519 | */
520 | private static InputStream getInputStreamFromBufferedImage(BufferedImage bufferedImage, String fileType) {
521 | if (Objects.isNull(bufferedImage)) {
522 | log.warn("BufferImage转InputStream异常:传入bufferedImage为空!");
523 | return null;
524 | }
525 | //默认为png
526 | if (UtilString.isEmpty(fileType)) {
527 | fileType = "png";
528 | }
529 | ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
530 | try {
531 | ImageIO.write(bufferedImage, fileType, outputStream);
532 | return new ByteArrayInputStream(outputStream.toByteArray());
533 | } catch (IOException e) {
534 | log.error("BufferImage转InputStream异常:{}", e);
535 | return null;
536 | }
537 | }
538 |
539 |
540 | /**
541 | * 放置数据进缓存
542 | *
543 | * @param cacheName 缓存块名称
544 | * @param key 缓存的key
545 | * @param value 值域
546 | * @return 是否放置成功
547 | */
548 | private boolean putDataToCache(String cacheName, String key, Object value) {
549 | boolean cacheFlag = false;
550 | CacheManager manager = CacheManagerHolder.getManager();
551 | if (Objects.nonNull(manager)) {
552 | Cache cache = manager.getCache(cacheName);
553 | if (Objects.nonNull(cache)) {
554 | log.info("即将放入缓存:{}",key);
555 | cache.put(key, value);
556 | cacheFlag = true;
557 | }
558 | }
559 | return cacheFlag;
560 | }
561 | }
562 |
--------------------------------------------------------------------------------
/src/main/java/com/example/demo/util/ClassLoaderWrapper.java:
--------------------------------------------------------------------------------
1 | package com.example.demo.util;
2 |
3 | import java.io.InputStream;
4 | import java.net.URL;
5 |
6 | /**
7 | * A class to wrap access to multiple class loaders making them work as one
8 | */
9 | public class ClassLoaderWrapper {
10 |
11 | private ClassLoader defaultClassLoader;
12 |
13 | public ClassLoaderWrapper() {
14 | }
15 |
16 | public void setDefaultClassLoader(ClassLoader defaultClassLoader){
17 | this.defaultClassLoader = defaultClassLoader;
18 | }
19 |
20 | public ClassLoader getDefaultClassLoader(){
21 | return this.defaultClassLoader;
22 | }
23 |
24 | /**
25 | * Get a resource as a URL using the current class path
26 | *
27 | * @param resource - the resource to locate
28 | * @return the resource or null
29 | */
30 | public URL getResourceAsURL(String resource) {
31 | return getResourceAsURL(resource, new ClassLoader[]{
32 | defaultClassLoader,
33 | Thread.currentThread().getContextClassLoader(),
34 | getClass().getClassLoader(),
35 | ClassLoader.getSystemClassLoader()
36 | });
37 | }
38 |
39 | /**
40 | * Get a resource from the classpath, starting with a specific class loader
41 | *
42 | * @param resource - the resource to find
43 | * @param classLoader - the first classloader to try
44 | * @return the stream or null
45 | */
46 | public URL getResourceAsURL(String resource, ClassLoader classLoader) {
47 | return getResourceAsURL(resource, new ClassLoader[]{
48 | classLoader,
49 | defaultClassLoader,
50 | Thread.currentThread().getContextClassLoader(),
51 | getClass().getClassLoader(),
52 | ClassLoader.getSystemClassLoader()
53 | });
54 | }
55 |
56 |
57 |
58 |
59 |
60 | /**
61 | * Get a resource from the classpath
62 | *
63 | * @param resource - the resource to find
64 | * @return the stream or null
65 | */
66 | public InputStream getResourceAsStream(String resource) {
67 | return getResourceAsStream(resource, new ClassLoader[]{
68 | defaultClassLoader,
69 | Thread.currentThread().getContextClassLoader(),
70 | getClass().getClassLoader(),
71 | ClassLoader.getSystemClassLoader()
72 | });
73 | }
74 |
75 | /**
76 | * Get a resource from the classpath, starting with a specific class loader
77 | *
78 | * @param resource - the resource to find
79 | * @param classLoader - the first class loader to try
80 | * @return the stream or null
81 | */
82 | public InputStream getResourceAsStream(String resource, ClassLoader classLoader) {
83 | return getResourceAsStream(resource, new ClassLoader[]{
84 | classLoader,
85 | defaultClassLoader,
86 | Thread.currentThread().getContextClassLoader(),
87 | getClass().getClassLoader(),
88 | ClassLoader.getSystemClassLoader()
89 | });
90 | }
91 |
92 |
93 |
94 |
95 |
96 | /**
97 | * Find a class on the classpath (or die trying)
98 | *
99 | * @param name - the class to look for
100 | * @return - the class
101 | * @throws ClassNotFoundException Duh.
102 | */
103 | public Class classForName(String name) throws ClassNotFoundException {
104 | return classForName(name, new ClassLoader[]{
105 | defaultClassLoader,
106 | Thread.currentThread().getContextClassLoader(),
107 | getClass().getClassLoader(),
108 | ClassLoader.getSystemClassLoader()
109 | });
110 | }
111 |
112 | /**
113 | * Find a class on the classpath, starting with a specific classloader (or die trying)
114 | *
115 | * @param name - the class to look for
116 | * @param classLoader - the first classloader to try
117 | * @return - the class
118 | * @throws ClassNotFoundException Duh.
119 | */
120 | public Class classForName(String name, ClassLoader classLoader) throws ClassNotFoundException {
121 | return classForName(name, new ClassLoader[]{
122 | classLoader,
123 | defaultClassLoader,
124 | Thread.currentThread().getContextClassLoader(),
125 | getClass().getClassLoader(),
126 | ClassLoader.getSystemClassLoader()
127 | });
128 | }
129 |
130 | /**
131 | * Try to getByUserId a resource from a group of classloaders
132 | *
133 | * @param resource - the resource to getByUserId
134 | * @param classLoader - the classloaders to examine
135 | * @return the resource or null
136 | */
137 | InputStream getResourceAsStream(String resource, ClassLoader[] classLoader) {
138 | for (ClassLoader cl : classLoader) {
139 | if (null != cl) {
140 |
141 | // try to find the resource as passed
142 | InputStream returnValue = cl.getResourceAsStream(resource);
143 |
144 | // now, some class loaders want this leading "/", so we'll add it and try again if we didn't find the resource
145 | if (null == returnValue) {
146 | returnValue = cl.getResourceAsStream("/" + resource);
147 | }
148 |
149 | if (null != returnValue) {
150 | return returnValue;
151 | }
152 | }
153 | }
154 | return null;
155 | }
156 |
157 | /**
158 | * Get a resource as a URL using the current class path
159 | *
160 | * @param resource - the resource to locate
161 | * @param classLoader - the class loaders to examine
162 | * @return the resource or null
163 | */
164 | URL getResourceAsURL(String resource, ClassLoader[] classLoader) {
165 | URL url;
166 | for (ClassLoader cl : classLoader) {
167 |
168 | if (null != cl) {
169 |
170 | // look for the resource as passed in...
171 | url = cl.getResource(resource);
172 |
173 | // ...but some class loaders want this leading "/", so we'll add it
174 | // and try again if we didn't find the resource
175 | if (null == url) url = cl.getResource("/" + resource);
176 |
177 | // "It's always in the last place I look for it!"
178 | // ... because only an idiot would keep looking for it after finding it, so stop looking already.
179 | if (null != url) return url;
180 |
181 | }
182 |
183 | }
184 | // didn't find it anywhere.
185 | return null;
186 |
187 | }
188 |
189 | /**
190 | * Attempt to load a class from a group of classloaders
191 | *
192 | * @param name - the class to load
193 | * @param classLoader - the group of classloaders to examine
194 | * @return the class
195 | * @throws ClassNotFoundException - Remember the wisdom of Judge Smails: Well, the world needs ditch diggers, too.
196 | */
197 | Class classForName(String name, ClassLoader[] classLoader) throws ClassNotFoundException {
198 | for (ClassLoader cl : classLoader) {
199 | if (null != cl) {
200 | try {
201 | Class c = cl.loadClass(name);
202 |
203 | if (null != c) return c;
204 |
205 | } catch (ClassNotFoundException e) {
206 | // we'll ignore this until all classloaders fail to locate the class
207 | }
208 |
209 | }
210 |
211 | }
212 | throw new ClassNotFoundException("Cannot find class: " + name);
213 |
214 | }
215 |
216 | }
217 |
--------------------------------------------------------------------------------
/src/main/java/com/example/demo/util/ExceptionHelper.java:
--------------------------------------------------------------------------------
1 | package com.example.demo.util;
2 |
3 | import javax.servlet.http.HttpServletRequest;
4 | import java.io.PrintWriter;
5 | import java.io.StringWriter;
6 | import java.lang.reflect.InvocationTargetException;
7 |
8 | public class ExceptionHelper {
9 |
10 | /**
11 | * 在request中获取异常类
12 | * @param request
13 | * @return
14 | */
15 | public static Throwable getThrowable(HttpServletRequest request){
16 | Throwable ex = null;
17 | if (request.getAttribute("exception") != null) {
18 | ex = (Throwable) request.getAttribute("exception");
19 | } else if (request.getAttribute("javax.servlet.error.exception") != null) {
20 | ex = (Throwable) request.getAttribute("javax.servlet.error.exception");
21 | }
22 | return ex;
23 | }
24 |
25 | /**
26 | * 将ErrorStack转化为String.
27 | */
28 | public static String getStackTraceAsString(Throwable e) {
29 | if (e == null){
30 | return "";
31 | }
32 | StringWriter stringWriter = new StringWriter();
33 | e.printStackTrace(new PrintWriter(stringWriter));
34 | return stringWriter.toString();
35 | }
36 |
37 |
38 | /**
39 | * 找出根异常消息
40 | */
41 | public static String getBootMessage(Throwable ex) {
42 | if(ex == null){
43 | return "";
44 | }
45 |
46 | if( ex instanceof NullPointerException ){
47 | String message = "NullPointerException["+ex.getStackTrace()[0]+"]";
48 | return message;
49 | }
50 |
51 | if( ex.getCause()!=null ){
52 | return ex.getCause().getMessage();
53 | }
54 | return ex.getMessage();
55 | }
56 |
57 |
58 | /**
59 | * 将反射时的checked exception转换为unchecked exception.
60 | */
61 | public static RuntimeException convertReflectExceptionToUnchecked(Exception e) {
62 | if (e instanceof IllegalAccessException || e instanceof IllegalArgumentException
63 | || e instanceof NoSuchMethodException) {
64 | return new IllegalArgumentException(e);
65 | } else if (e instanceof InvocationTargetException) {
66 | return new RuntimeException(((InvocationTargetException) e).getTargetException());
67 | } else if (e instanceof RuntimeException) {
68 | return (RuntimeException) e;
69 | }
70 | return new RuntimeException("Unexpected Checked Exception.", e);
71 | }
72 |
73 | /**
74 | * 将CheckedException转换为UncheckedException.
75 | */
76 | public static RuntimeException unchecked(Exception e) {
77 | if (e instanceof RuntimeException) {
78 | return (RuntimeException) e;
79 | } else {
80 | return new RuntimeException(e);
81 | }
82 | }
83 |
84 |
85 | }
--------------------------------------------------------------------------------
/src/main/java/com/example/demo/util/NamedThreadFactory.java:
--------------------------------------------------------------------------------
1 | package com.example.demo.util;
2 |
3 | import java.util.concurrent.ThreadFactory;
4 | import java.util.concurrent.atomic.AtomicInteger;
5 |
6 | /**
7 | * 带有名称的 线程 工厂类
8 | * @author zoubin02 on 5/5/14.
9 | */
10 | public class NamedThreadFactory implements ThreadFactory {
11 | private static final AtomicInteger POOL_SEQ = new AtomicInteger(1);
12 |
13 | private final AtomicInteger threadNum = new AtomicInteger(1);
14 |
15 | private final String prefix;
16 |
17 | private final boolean daemon;
18 |
19 | private final ThreadGroup group;
20 |
21 | public NamedThreadFactory() {
22 | this("pool-" + POOL_SEQ.getAndIncrement(), false);
23 | }
24 |
25 | public NamedThreadFactory(String prefix) {
26 | this(prefix, false);
27 | }
28 |
29 | public NamedThreadFactory(String prefix, boolean daemon) {
30 | this.prefix = prefix + "-thread-";
31 | this.daemon = daemon;
32 | SecurityManager s = System.getSecurityManager();
33 | group = (s == null) ? Thread.currentThread().getThreadGroup() : s.getThreadGroup();
34 | }
35 |
36 | @Override
37 | public Thread newThread(Runnable runnable) {
38 | String name = prefix + threadNum.getAndIncrement();
39 | Thread ret = new Thread(group, runnable, name, 0);
40 | ret.setDaemon(daemon);
41 | return ret;
42 | }
43 |
44 | public ThreadGroup getThreadGroup() {
45 | return group;
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/src/main/java/com/example/demo/util/Resources.java:
--------------------------------------------------------------------------------
1 | package com.example.demo.util;
2 |
3 |
4 |
5 | import java.io.*;
6 | import java.net.URL;
7 | import java.net.URLConnection;
8 | import java.nio.charset.Charset;
9 | import java.util.Properties;
10 |
11 | /**
12 | * A class to simplify access to resources through the classloader.
13 | */
14 | public class Resources {
15 |
16 | private static ClassLoaderWrapper classLoaderWrapper = new ClassLoaderWrapper();
17 |
18 | /**
19 | * Charset to use when calling getResourceAsReader.
20 | * null means use the system dsl.
21 | */
22 | private static Charset charset;
23 |
24 | Resources() {
25 | }
26 |
27 | /**
28 | * Returns the dsl classloader (may be null).
29 | *
30 | * @return The dsl classloader
31 | */
32 | public static ClassLoader getDefaultClassLoader() {
33 | return classLoaderWrapper.getDefaultClassLoader();
34 | }
35 |
36 | /**
37 | * Sets the dsl classloader
38 | *
39 | * @param defaultClassLoader - the new dsl ClassLoader
40 | */
41 | public static void setDefaultClassLoader(ClassLoader defaultClassLoader) {
42 | classLoaderWrapper.setDefaultClassLoader(defaultClassLoader);
43 | }
44 |
45 | /**
46 | * Returns the URL of the resource on the classpath
47 | *
48 | * @param resource The resource to find
49 | * @return The resource
50 | * @throws IOException If the resource cannot be found or read
51 | */
52 | public static URL getResourceURL(String resource){
53 | return classLoaderWrapper.getResourceAsURL(resource);
54 | }
55 |
56 | /**
57 | * Returns the URL of the resource on the classpath
58 | *
59 | * @param loader The classloader used to fetch the resource
60 | * @param resource The resource to find
61 | * @return The resource
62 | * @throws IOException If the resource cannot be found or read
63 | */
64 | public static URL getResourceURL(ClassLoader loader, String resource) throws IOException {
65 | URL url = classLoaderWrapper.getResourceAsURL(resource, loader);
66 | if (url == null) throw new IOException("Could not find resource " + resource);
67 | return url;
68 | }
69 |
70 | /**
71 | * Returns a resource on the classpath as a Stream object
72 | *
73 | * @param resource The resource to find
74 | * @return The resource
75 | * @throws IOException If the resource cannot be found or read
76 | */
77 | public static InputStream getResourceAsStream(String resource) throws IOException {
78 | return getResourceAsStream(null, resource);
79 | }
80 |
81 | /**
82 | * Returns a resource on the classpath as a Stream object
83 | *
84 | * @param loader The classloader used to fetch the resource
85 | * @param resource The resource to find
86 | * @return The resource
87 | * @throws IOException If the resource cannot be found or read
88 | */
89 | public static InputStream getResourceAsStream(ClassLoader loader, String resource) throws IOException {
90 | InputStream in = classLoaderWrapper.getResourceAsStream(resource, loader);
91 | if (in == null) {
92 | throw new IOException("Could not find resource " + resource);
93 | }
94 | return in;
95 | }
96 |
97 | /**
98 | * Returns a resource on the classpath as a Properties object
99 | *
100 | * @param resource The resource to find
101 | * @return The resource
102 | * @throws IOException If the resource cannot be found or read
103 | */
104 | public static Properties getResourceAsProperties(String resource) throws IOException {
105 | Properties props = new Properties();
106 | InputStream in = getResourceAsStream(resource);
107 | props.load(in);
108 | in.close();
109 | return props;
110 | }
111 |
112 | /**
113 | * Returns a resource on the classpath as a Properties object
114 | *
115 | * @param loader The classloader used to fetch the resource
116 | * @param resource The resource to find
117 | * @return The resource
118 | * @throws IOException If the resource cannot be found or read
119 | */
120 | public static Properties getResourceAsProperties(ClassLoader loader, String resource) throws IOException {
121 | Properties props = new Properties();
122 | InputStream in = getResourceAsStream(loader, resource);
123 | props.load(in);
124 | in.close();
125 | return props;
126 | }
127 |
128 | /**
129 | * Returns a resource on the classpath as a Reader object
130 | *
131 | * @param resource The resource to find
132 | * @return The resource
133 | * @throws IOException If the resource cannot be found or read
134 | */
135 | public static Reader getResourceAsReader(String resource) throws IOException {
136 | Reader reader;
137 | if (charset == null) {
138 | reader = new InputStreamReader(getResourceAsStream(resource));
139 | } else {
140 | reader = new InputStreamReader(getResourceAsStream(resource), charset);
141 | }
142 | return reader;
143 | }
144 |
145 | /**
146 | * Returns a resource on the classpath as a Reader object
147 | *
148 | * @param loader The classloader used to fetch the resource
149 | * @param resource The resource to find
150 | * @return The resource
151 | * @throws IOException If the resource cannot be found or read
152 | */
153 | public static Reader getResourceAsReader(ClassLoader loader, String resource) throws IOException {
154 | Reader reader;
155 | if (charset == null) {
156 | reader = new InputStreamReader(getResourceAsStream(loader, resource));
157 | } else {
158 | reader = new InputStreamReader(getResourceAsStream(loader, resource), charset);
159 | }
160 | return reader;
161 | }
162 |
163 | /**
164 | * Returns a resource on the classpath as a File object
165 | *
166 | * @param resource The resource to find
167 | * @return The resource
168 | * @throws IOException If the resource cannot be found or read
169 | */
170 | public static File getResourceAsFile(String resource) throws IOException {
171 | return new File(getResourceURL(resource).getFile());
172 | }
173 |
174 | /**
175 | * Returns a resource on the classpath as a File object
176 | *
177 | * @param loader - the classloader used to fetch the resource
178 | * @param resource - the resource to find
179 | * @return The resource
180 | * @throws IOException If the resource cannot be found or read
181 | */
182 | public static File getResourceAsFile(ClassLoader loader, String resource) throws IOException {
183 | return new File(getResourceURL(loader, resource).getFile());
184 | }
185 |
186 | /**
187 | * Gets a URL as an input stream
188 | *
189 | * @param urlString - the URL to getByUserId
190 | * @return An input stream with the data from the URL
191 | * @throws IOException If the resource cannot be found or read
192 | */
193 | public static InputStream getUrlAsStream(String urlString) throws IOException {
194 | URL url = new URL(urlString);
195 | URLConnection conn = url.openConnection();
196 | return conn.getInputStream();
197 | }
198 |
199 | /**
200 | * Gets a URL as a Reader
201 | *
202 | * @param urlString - the URL to getByUserId
203 | * @return A Reader with the data from the URL
204 | * @throws IOException If the resource cannot be found or read
205 | */
206 | public static Reader getUrlAsReader(String urlString) throws IOException {
207 | return new InputStreamReader(getUrlAsStream(urlString));
208 | }
209 |
210 | /**
211 | * Gets a URL as a Properties object
212 | *
213 | * @param urlString - the URL to getByUserId
214 | * @return A Properties object with the data from the URL
215 | * @throws IOException If the resource cannot be found or read
216 | */
217 | public static Properties getUrlAsProperties(String urlString) throws IOException {
218 | Properties props = new Properties();
219 | InputStream in = getUrlAsStream(urlString);
220 | props.load(in);
221 | in.close();
222 | return props;
223 | }
224 |
225 | /**
226 | * Loads a class
227 | *
228 | * @param className - the class to fetch
229 | * @return The loaded class
230 | * @throws ClassNotFoundException If the class cannot be found (duh!)
231 | */
232 | public static Class classForName(String className) throws ClassNotFoundException {
233 | return classLoaderWrapper.classForName(className);
234 | }
235 |
236 | public static Charset getCharset() {
237 | return charset;
238 | }
239 |
240 | public static void setCharset(Charset charset) {
241 | Resources.charset = charset;
242 | }
243 |
244 | }
245 |
--------------------------------------------------------------------------------
/src/main/java/com/example/demo/util/UtilFile.java:
--------------------------------------------------------------------------------
1 | package com.example.demo.util;
2 |
3 | import com.google.common.collect.Lists;
4 | import org.apache.commons.io.IOUtils;
5 | import org.slf4j.Logger;
6 | import org.slf4j.LoggerFactory;
7 |
8 | import javax.servlet.http.HttpServletRequest;
9 | import javax.servlet.http.HttpServletResponse;
10 | import java.io.*;
11 | import java.util.List;
12 | import java.util.Objects;
13 |
14 |
15 | /**
16 | * 文件流辅助类
17 | *
18 | * @author zoubin
19 | * @since 2013-09-13
20 | */
21 | public class UtilFile extends Resources {
22 |
23 | private static Logger logger = LoggerFactory.getLogger(UtilFile.class);
24 |
25 | private static String LINE_SEPARATOR = System.getProperty("line.separator");
26 |
27 |
28 | /**
29 | * 得到文件名中的父路径部分。 对两种路径分隔符都有效。 不存在时返回""。 如果文件名是以路径分隔符结尾的则不考虑该分隔符,例如"/path/"返回""。
30 | */
31 | public static String getPathPart(String fileName) {
32 | int point = getPathLsatIndex(fileName);
33 | int length = fileName.length();
34 | if (point == -1) {
35 | return "";
36 | } else if (point == length - 1) {
37 | int secondPoint = getPathLsatIndex(fileName, point - 1);
38 | if (secondPoint == -1) {
39 | return "";
40 | } else {
41 | return fileName.substring(0, secondPoint);
42 | }
43 | } else {
44 | return fileName.substring(0, point);
45 | }
46 | }
47 |
48 | /**
49 | * 得到文件的名字部分。 实际上就是路径中的最后一个路径分隔符后的部分。
50 | */
51 | public static String getNamePart(String fileName) {
52 | int point = getPathLsatIndex(fileName);
53 | int length = fileName.length();
54 | if (point == -1) {
55 | return fileName;
56 | } else if (point == length - 1) {
57 | int secondPoint = getPathLsatIndex(fileName, point - 1);
58 | if (secondPoint == -1) {
59 | if (length == 1) {
60 | return fileName;
61 | } else {
62 | return fileName.substring(0, point);
63 | }
64 | } else {
65 | return fileName.substring(secondPoint, point);
66 | }
67 | } else {
68 | return fileName.substring(point + 1, length);
69 | }
70 | }
71 |
72 | /**
73 | * 得到文件的类型。 实际上就是得到文件名中最后一个“.”后面的部分。
74 | */
75 | public static String getTypePart(String fileName) {
76 | int point = fileName.lastIndexOf('.');
77 | int length = fileName.length();
78 | if (point == -1 || point == length - 1) {
79 | return "";
80 | } else {
81 | return fileName.substring(point, length);
82 | }
83 | }
84 |
85 | //得到路径分隔符在文件路径中最后出现的位置。 对于DOS或者UNIX风格的分隔符都可以。
86 | private static int getPathLsatIndex(String fileName) {
87 | int point = fileName.lastIndexOf('/');
88 | if (point == -1) {
89 | point = fileName.lastIndexOf('\\');
90 | }
91 | return point;
92 | }
93 |
94 |
95 | //得到路径分隔符在文件路径中指定位置前最后出现的位置。 对于DOS或者UNIX风格的分隔符都可以。
96 | private static int getPathLsatIndex(String fileName, int fromIndex) {
97 | int point = fileName.lastIndexOf('/', fromIndex);
98 | if (point == -1) {
99 | point = fileName.lastIndexOf('\\', fromIndex);
100 | }
101 | return point;
102 | }
103 |
104 |
105 | public static boolean exist(String path) {
106 | return new File(path).exists();
107 | }
108 |
109 | /**
110 | * *****************************读相关*****************************
111 | */
112 | public static String read(InputStream is) throws IOException {
113 | return read(is, "utf-8");
114 | }
115 |
116 | public static String read(InputStream is, String encoding) throws IOException {
117 | BufferedReader br = new BufferedReader(new InputStreamReader(is, encoding));
118 | StringBuilder content = new StringBuilder();
119 | String data = null;
120 | while ((data = br.readLine()) != null) {
121 | content.append(data);
122 | content.append(LINE_SEPARATOR);
123 | }
124 | return content.toString();
125 | }
126 |
127 | //得到文件或者文件夹的大小(包含所有子文件)
128 | public static long getSize(File file) {
129 | if (file.exists()) {
130 | if (!file.isFile()) {
131 | long size = 0;
132 | File[] files = file.listFiles();
133 | if (files != null && files.length > 0) {
134 | for (File f : files) {
135 | size += getSize(f);
136 | }
137 | }
138 | return size;
139 | } else {
140 | return file.length();
141 | }
142 | }
143 | return 0;
144 | }
145 |
146 | //文件变为流
147 | public static byte[] readFile2Byte(String filePath) {
148 | byte[] buffer = null;
149 | FileInputStream fis = null;
150 | try {
151 | File file = new File(filePath);
152 | fis = new FileInputStream(file);
153 | ByteArrayOutputStream bos = new ByteArrayOutputStream();
154 | byte[] b = new byte[1024];
155 | int n;
156 | while ((n = fis.read(b)) != -1) {
157 | bos.write(b, 0, n);
158 | }
159 | fis.close();
160 | bos.close();
161 | buffer = bos.toByteArray();
162 | } catch (FileNotFoundException e) {
163 | e.printStackTrace();
164 | } catch (IOException e) {
165 | e.printStackTrace();
166 | } finally {
167 | if (fis != null) {
168 | IOUtils.closeQuietly(fis);
169 | }
170 | }
171 | return buffer;
172 | }
173 |
174 |
175 | /**
176 | * 根据“文件名的后缀”获取文件内容类型(而非根据File.getContentType()读取的文件类型)
177 | *
178 | * @param returnFileName 带验证的文件名
179 | * @return 返回文件类型
180 | */
181 | public static String getContentType(String returnFileName) {
182 | String contentType = "application/octet-stream";
183 | if (returnFileName.lastIndexOf(".") < 0)
184 | return contentType;
185 | returnFileName = returnFileName.toLowerCase();
186 | returnFileName = returnFileName.substring(returnFileName.lastIndexOf(".") + 1);
187 | if (returnFileName.equals("html") || returnFileName.equals("htm") || returnFileName.equals("shtml")) {
188 | contentType = "text/html";
189 | } else if (returnFileName.equals("apk")) {
190 | contentType = "application/vnd.android.package-archive";
191 | } else if (returnFileName.equals("sis")) {
192 | contentType = "application/vnd.symbian.install";
193 | } else if (returnFileName.equals("sisx")) {
194 | contentType = "application/vnd.symbian.install";
195 | } else if (returnFileName.equals("exe")) {
196 | contentType = "application/x-msdownload";
197 | } else if (returnFileName.equals("msi")) {
198 | contentType = "application/x-msdownload";
199 | } else if (returnFileName.equals("css")) {
200 | contentType = "text/css";
201 | } else if (returnFileName.equals("xml")) {
202 | contentType = "text/xml";
203 | } else if (returnFileName.equals("gif")) {
204 | contentType = "image/gif";
205 | } else if (returnFileName.equals("jpeg") || returnFileName.equals("jpg")) {
206 | contentType = "image/jpeg";
207 | } else if (returnFileName.equals("js")) {
208 | contentType = "application/x-javascript";
209 | } else if (returnFileName.equals("atom")) {
210 | contentType = "application/atom+xml";
211 | } else if (returnFileName.equals("rss")) {
212 | contentType = "application/rss+xml";
213 | } else if (returnFileName.equals("mml")) {
214 | contentType = "text/mathml";
215 | } else if (returnFileName.equals("txt")) {
216 | contentType = "text/plain";
217 | } else if (returnFileName.equals("jad")) {
218 | contentType = "text/vnd.sun.j2me.app-descriptor";
219 | } else if (returnFileName.equals("wml")) {
220 | contentType = "text/vnd.wap.wml";
221 | } else if (returnFileName.equals("htc")) {
222 | contentType = "text/x-component";
223 | } else if (returnFileName.equals("png")) {
224 | contentType = "image/png";
225 | } else if (returnFileName.equals("tif") || returnFileName.equals("tiff")) {
226 | contentType = "image/tiff";
227 | } else if (returnFileName.equals("wbmp")) {
228 | contentType = "image/vnd.wap.wbmp";
229 | } else if (returnFileName.equals("ico")) {
230 | contentType = "image/x-icon";
231 | } else if (returnFileName.equals("jng")) {
232 | contentType = "image/x-jng";
233 | } else if (returnFileName.equals("bmp")) {
234 | contentType = "image/x-ms-bmp";
235 | } else if (returnFileName.equals("svg")) {
236 | contentType = "image/svg+xml";
237 | } else if (returnFileName.equals("jar") || returnFileName.equals("var")
238 | || returnFileName.equals("ear")) {
239 | contentType = "application/java-archive";
240 | } else if (returnFileName.equals("doc")) {
241 | contentType = "application/msword";
242 | } else if (returnFileName.equals("pdf")) {
243 | contentType = "application/pdf";
244 | } else if (returnFileName.equals("rtf")) {
245 | contentType = "application/rtf";
246 | } else if (returnFileName.equals("xls")) {
247 | contentType = "application/vnd.ms-excel";
248 | } else if (returnFileName.equals("ppt")) {
249 | contentType = "application/vnd.ms-powerpoint";
250 | } else if (returnFileName.equals("7z")) {
251 | contentType = "application/x-7z-compressed";
252 | } else if (returnFileName.equals("rar")) {
253 | contentType = "application/x-rar-compressed";
254 | } else if (returnFileName.equals("swf")) {
255 | contentType = "application/x-shockwave-flash";
256 | } else if (returnFileName.equals("rpm")) {
257 | contentType = "application/x-redhat-package-manager";
258 | } else if (returnFileName.equals("der") || returnFileName.equals("pem")
259 | || returnFileName.equals("crt")) {
260 | contentType = "application/x-x509-ca-cert";
261 | } else if (returnFileName.equals("xhtml")) {
262 | contentType = "application/xhtml+xml";
263 | } else if (returnFileName.equals("zip")) {
264 | contentType = "application/zip";
265 | } else if (returnFileName.equals("mid") || returnFileName.equals("midi")
266 | || returnFileName.equals("kar")) {
267 | contentType = "audio/midi";
268 | } else if (returnFileName.equals("mp3")) {
269 | contentType = "audio/mpeg";
270 | } else if (returnFileName.equals("ogg")) {
271 | contentType = "audio/ogg";
272 | } else if (returnFileName.equals("m4a")) {
273 | contentType = "audio/x-m4a";
274 | } else if (returnFileName.equals("ra")) {
275 | contentType = "audio/x-realaudio";
276 | } else if (returnFileName.equals("3gpp")
277 | || returnFileName.equals("3gp")) {
278 | contentType = "video/3gpp";
279 | } else if (returnFileName.equals("mp4")) {
280 | contentType = "video/mp4";
281 | } else if (returnFileName.equals("mpeg")
282 | || returnFileName.equals("mpg")) {
283 | contentType = "video/mpeg";
284 | } else if (returnFileName.equals("mov")) {
285 | contentType = "video/quicktime";
286 | } else if (returnFileName.equals("flv")) {
287 | contentType = "video/x-flv";
288 | } else if (returnFileName.equals("m4v")) {
289 | contentType = "video/x-m4v";
290 | } else if (returnFileName.equals("mng")) {
291 | contentType = "video/x-mng";
292 | } else if (returnFileName.equals("asx") || returnFileName.equals("asf")) {
293 | contentType = "video/x-ms-asf";
294 | } else if (returnFileName.equals("wmv")) {
295 | contentType = "video/x-ms-wmv";
296 | } else if (returnFileName.equals("avi")) {
297 | contentType = "video/x-msvideo";
298 | }
299 | return contentType;
300 | }
301 |
302 |
303 | /**
304 | * 修正路径,将 \\ 或 / 等替换为 File.separator
305 | *
306 | * @param path 待修正的路径
307 | * @return 修正后的路径
308 | */
309 | public static String path(String path) {
310 | String p = UtilString.replace(path, "\\", "/");
311 | p = UtilString.join(UtilString.split(p, "/"), "/");
312 | if (!UtilString.startsWithAny(p, "/") && UtilString.startsWithAny(path, "\\", "/")) {
313 | p += "/";
314 | }
315 | if (!UtilString.endsWithAny(p, "/") && UtilString.endsWithAny(path, "\\", "/")) {
316 | p = p + "/";
317 | }
318 | if (path != null && path.startsWith("/")) {
319 | p = "/" + p; // linux下路径
320 | }
321 | return p;
322 | }
323 |
324 | /**
325 | * 获目录下的文件列表
326 | *
327 | * @param dir 搜索目录
328 | * @param searchDirs 是否是搜索目录
329 | * @return 文件列表
330 | */
331 | public static List findChildrenList(File dir, boolean searchDirs) {
332 | List files = Lists.newArrayList();
333 | for (String subFiles : dir.list()) {
334 | File file = new File(dir + "/" + subFiles);
335 | if (((searchDirs) && (file.isDirectory())) || ((!searchDirs) && (!file.isDirectory()))) {
336 | files.add(file.getName());
337 | }
338 | }
339 | return files;
340 | }
341 |
342 | /**
343 | * 获取文件扩展名(返回小写)
344 | *
345 | * @param fileName 文件名
346 | * @return 例如:test.jpg 返回: jpg
347 | */
348 | public static String getFileExtension(String fileName) {
349 | if ((fileName == null) || (fileName.lastIndexOf(".") == -1) || (fileName.lastIndexOf(".") == fileName.length() - 1)) {
350 | return null;
351 | }
352 | return UtilString.lowerCase(fileName.substring(fileName.lastIndexOf(".") + 1));
353 | }
354 |
355 | /**
356 | * 获取文件名,不包含扩展名
357 | *
358 | * @param fileName 文件名
359 | * @return 例如:d:\files\test.jpg 返回:d:\files\test
360 | */
361 | public static String getFileNameWithoutExtension(String fileName) {
362 | if ((fileName == null) || (fileName.lastIndexOf(".") == -1)) {
363 | return null;
364 | }
365 | return fileName.substring(0, fileName.lastIndexOf("."));
366 | }
367 |
368 |
369 | /**
370 | ******************************复制相关*****************************
371 | */
372 | /**
373 | * 复制单个文件,如果目标文件存在,则不覆盖
374 | *
375 | * @param srcFileName 待复制的文件名
376 | * @param descFileName 目标文件名
377 | * @return 如果复制成功,则返回true,否则返回false
378 | */
379 | public static boolean copyFile(String srcFileName, String descFileName) {
380 | return UtilFile.copyFileCover(srcFileName, descFileName, false);
381 | }
382 |
383 | /**
384 | * 复制单个文件
385 | *
386 | * @param srcFileName 待复制的文件名
387 | * @param descFileName 目标文件名
388 | * @param coverlay 如果目标文件已存在,是否覆盖
389 | * @return 如果复制成功,则返回true,否则返回false
390 | */
391 | public static boolean copyFileCover(String srcFileName,
392 | String descFileName, boolean coverlay) {
393 | File srcFile = new File(srcFileName);
394 | // 判断源文件是否存在
395 | if (!srcFile.exists()) {
396 | logger.debug("复制文件失败,源文件 " + srcFileName + " 不存在!");
397 | return false;
398 | }
399 | // 判断源文件是否是合法的文件
400 | else if (!srcFile.isFile()) {
401 | logger.debug("复制文件失败," + srcFileName + " 不是一个文件!");
402 | return false;
403 | }
404 | File descFile = new File(descFileName);
405 | // 判断目标文件是否存在
406 | if (descFile.exists()) {
407 | // 如果目标文件存在,并且允许覆盖
408 | if (coverlay) {
409 | logger.debug("目标文件已存在,准备删除!");
410 | if (!UtilFile.delFile(descFileName)) {
411 | logger.debug("删除目标文件 " + descFileName + " 失败!");
412 | return false;
413 | }
414 | } else {
415 | logger.debug("复制文件失败,目标文件 " + descFileName + " 已存在!");
416 | return false;
417 | }
418 | } else {
419 | if (!descFile.getParentFile().exists()) {
420 | // 如果目标文件所在的目录不存在,则创建目录
421 | logger.debug("目标文件所在的目录不存在,创建目录!");
422 | // 创建目标文件所在的目录
423 | if (!descFile.getParentFile().mkdirs()) {
424 | logger.debug("创建目标文件所在的目录失败!");
425 | return false;
426 | }
427 | }
428 | }
429 |
430 | // 准备复制文件
431 | // 读取的位数
432 | int readByte = 0;
433 | InputStream ins = null;
434 | OutputStream outs = null;
435 | try {
436 | // 打开源文件
437 | ins = new FileInputStream(srcFile);
438 | // 打开目标文件的输出流
439 | outs = new FileOutputStream(descFile);
440 | byte[] buf = new byte[1024];
441 | // 一次读取1024个字节,当readByte为-1时表示文件已经读取完毕
442 | while ((readByte = ins.read(buf)) != -1) {
443 | // 将读取的字节流写入到输出流
444 | outs.write(buf, 0, readByte);
445 | }
446 | logger.debug("复制单个文件 " + srcFileName + " 到" + descFileName
447 | + "成功!");
448 | return true;
449 | } catch (Exception e) {
450 | logger.debug("复制文件失败:" + e.getMessage());
451 | return false;
452 | } finally {
453 | // 关闭输入输出流,首先关闭输出流,然后再关闭输入流
454 | if (outs != null) {
455 | try {
456 | outs.close();
457 | } catch (IOException oute) {
458 | oute.printStackTrace();
459 | }
460 | }
461 | if (ins != null) {
462 | try {
463 | ins.close();
464 | } catch (IOException ine) {
465 | ine.printStackTrace();
466 | }
467 | }
468 | }
469 | }
470 |
471 | /**
472 | * 复制整个目录的内容,如果目标目录存在,则不覆盖
473 | *
474 | * @param srcDirName 源目录名
475 | * @param descDirName 目标目录名
476 | * @return 如果复制成功返回true,否则返回false
477 | */
478 | public static boolean copyDirectory(String srcDirName, String descDirName) {
479 | return UtilFile.copyDirectoryCover(srcDirName, descDirName,
480 | false);
481 | }
482 |
483 | /**
484 | * 复制整个目录的内容
485 | *
486 | * @param srcDirName 源目录名
487 | * @param descDirName 目标目录名
488 | * @param coverlay 如果目标目录存在,是否覆盖
489 | * @return 如果复制成功返回true,否则返回false
490 | */
491 | public static boolean copyDirectoryCover(String srcDirName,
492 | String descDirName, boolean coverlay) {
493 | File srcDir = new File(srcDirName);
494 | // 判断源目录是否存在
495 | if (!srcDir.exists()) {
496 | logger.debug("复制目录失败,源目录 " + srcDirName + " 不存在!");
497 | return false;
498 | }
499 | // 判断源目录是否是目录
500 | else if (!srcDir.isDirectory()) {
501 | logger.debug("复制目录失败," + srcDirName + " 不是一个目录!");
502 | return false;
503 | }
504 | // 如果目标文件夹名不以文件分隔符结尾,自动添加文件分隔符
505 | String descDirNames = descDirName;
506 | if (!descDirNames.endsWith(File.separator)) {
507 | descDirNames = descDirNames + File.separator;
508 | }
509 | File descDir = new File(descDirNames);
510 | // 如果目标文件夹存在
511 | if (descDir.exists()) {
512 | if (coverlay) {
513 | // 允许覆盖目标目录
514 | logger.debug("目标目录已存在,准备删除!");
515 | if (!UtilFile.delFile(descDirNames)) {
516 | logger.debug("删除目录 " + descDirNames + " 失败!");
517 | return false;
518 | }
519 | } else {
520 | logger.debug("目标目录复制失败,目标目录 " + descDirNames + " 已存在!");
521 | return false;
522 | }
523 | } else {
524 | // 创建目标目录
525 | logger.debug("目标目录不存在,准备创建!");
526 | if (!descDir.mkdirs()) {
527 | logger.debug("创建目标目录失败!");
528 | return false;
529 | }
530 |
531 | }
532 |
533 | boolean flag = true;
534 | // 列出源目录下的所有文件名和子目录名
535 | File[] files = srcDir.listFiles();
536 | for (int i = 0; i < files.length; i++) {
537 | // 如果是一个单个文件,则直接复制
538 | if (files[i].isFile()) {
539 | flag = UtilFile.copyFile(files[i].getAbsolutePath(),
540 | descDirName + files[i].getName());
541 | // 如果拷贝文件失败,则退出循环
542 | if (!flag) {
543 | break;
544 | }
545 | }
546 | // 如果是子目录,则继续复制目录
547 | if (files[i].isDirectory()) {
548 | flag = UtilFile.copyDirectory(files[i]
549 | .getAbsolutePath(), descDirName + files[i].getName());
550 | // 如果拷贝目录失败,则退出循环
551 | if (!flag) {
552 | break;
553 | }
554 | }
555 | }
556 |
557 | if (!flag) {
558 | logger.debug("复制目录 " + srcDirName + " 到 " + descDirName + " 失败!");
559 | return false;
560 | }
561 | logger.debug("复制目录 " + srcDirName + " 到 " + descDirName + " 成功!");
562 | return true;
563 |
564 | }
565 |
566 |
567 | /**
568 | ******************************删除相关*****************************
569 | */
570 | /**
571 | * 删除文件,可以删除单个文件或文件夹
572 | *
573 | * @param fileName 被删除的文件名
574 | * @return 如果删除成功,则返回true,否是返回false
575 | */
576 | public static boolean delFile(String fileName) {
577 | File file = new File(fileName);
578 | if (!file.exists()) {
579 | logger.debug(fileName + " 文件不存在!");
580 | return true;
581 | } else {
582 | if (file.isFile()) {
583 | return UtilFile.deleteFile(fileName);
584 | } else {
585 | return UtilFile.deleteDirectory(fileName);
586 | }
587 | }
588 | }
589 |
590 | /**
591 | * 删除单个文件
592 | *
593 | * @param fileName 被删除的文件名
594 | * @return 如果删除成功,则返回true,否则返回false
595 | */
596 | public static boolean deleteFile(String fileName) {
597 | File file = new File(fileName);
598 | if (file.exists() && file.isFile()) {
599 | if (file.delete()) {
600 | logger.debug("删除文件 " + fileName + " 成功!");
601 | return true;
602 | } else {
603 | logger.debug("删除文件 " + fileName + " 失败!");
604 | return false;
605 | }
606 | } else {
607 | logger.debug(fileName + " 文件不存在!");
608 | return true;
609 | }
610 | }
611 |
612 | /**
613 | * 删除目录及目录下的文件
614 | *
615 | * @param dirName 被删除的目录所在的文件路径
616 | * @return 如果目录删除成功,则返回true,否则返回false
617 | */
618 | public static boolean deleteDirectory(String dirName) {
619 | String dirNames = dirName;
620 | if (!dirNames.endsWith(File.separator)) {
621 | dirNames = dirNames + File.separator;
622 | }
623 | File dirFile = new File(dirNames);
624 | if (!dirFile.exists() || !dirFile.isDirectory()) {
625 | logger.debug(dirNames + " 目录不存在!");
626 | return true;
627 | }
628 | boolean flag = true;
629 | // 列出全部文件及子目录
630 | File[] files = dirFile.listFiles();
631 | for (int i = 0; i < files.length; i++) {
632 | // 删除子文件
633 | if (files[i].isFile()) {
634 | flag = UtilFile.deleteFile(files[i].getAbsolutePath());
635 | // 如果删除文件失败,则退出循环
636 | if (!flag) {
637 | break;
638 | }
639 | }
640 | // 删除子目录
641 | else if (files[i].isDirectory()) {
642 | flag = UtilFile.deleteDirectory(files[i]
643 | .getAbsolutePath());
644 | // 如果删除子目录失败,则退出循环
645 | if (!flag) {
646 | break;
647 | }
648 | }
649 | }
650 |
651 | if (!flag) {
652 | logger.debug("删除目录失败!");
653 | return false;
654 | }
655 | // 删除当前目录
656 | if (dirFile.delete()) {
657 | logger.debug("删除目录 " + dirName + " 成功!");
658 | return true;
659 | } else {
660 | logger.debug("删除目录 " + dirName + " 失败!");
661 | return false;
662 | }
663 |
664 | }
665 |
666 |
667 | /**
668 | ******************************创建相关*****************************
669 | */
670 | /**
671 | * 创建单个文件
672 | *
673 | * @param descFileName 文件名,包含路径
674 | * @return 如果创建成功,则返回true,否则返回false
675 | */
676 | public static boolean createFile(String descFileName) {
677 | File file = new File(descFileName);
678 | if (file.exists()) {
679 | logger.debug("文件 " + descFileName + " 已存在!");
680 | return false;
681 | }
682 | if (descFileName.endsWith(File.separator)) {
683 | logger.debug(descFileName + " 为目录,不能创建目录!");
684 | return false;
685 | }
686 | if (!file.getParentFile().exists()) {
687 | // 如果文件所在的目录不存在,则创建目录
688 | if (!file.getParentFile().mkdirs()) {
689 | logger.debug("创建文件所在的目录失败!");
690 | return false;
691 | }
692 | }
693 |
694 | // 创建文件
695 | try {
696 | if (file.createNewFile()) {
697 | logger.debug(descFileName + " 文件创建成功!");
698 | return true;
699 | } else {
700 | logger.debug(descFileName + " 文件创建失败!");
701 | return false;
702 | }
703 | } catch (Exception e) {
704 | e.printStackTrace();
705 | logger.debug(descFileName + " 文件创建失败!");
706 | return false;
707 | }
708 | }
709 |
710 |
711 | public static void writeFileByUTF8String(String filePathAndName, String buf) {
712 | try {
713 | writeFileByByte(filePathAndName, buf.getBytes("UTF-8"));
714 | } catch (UnsupportedEncodingException e) {
715 | e.printStackTrace();
716 | throw ExceptionHelper.unchecked(e);
717 | }
718 | }
719 |
720 | public static void writeFileByByte(String filePathAndName, byte[] buf) {
721 | //创建文件
722 | createFile(filePathAndName);
723 | //
724 | BufferedOutputStream bos = null;
725 | FileOutputStream fos = null;
726 | File file = null;
727 | try {
728 | file = new File(filePathAndName);
729 | fos = new FileOutputStream(file);
730 | bos = new BufferedOutputStream(fos);
731 | bos.write(buf);
732 | } catch (Exception e) {
733 | e.printStackTrace();
734 | } finally {
735 | if (bos != null) {
736 | IOUtils.closeQuietly(bos);
737 | }
738 | if (fos != null) {
739 | IOUtils.closeQuietly(fos);
740 | }
741 | }
742 | }
743 |
744 | public static void writeFileByUTF8String(String filePath, String fileName, String buf) {
745 | try {
746 | writeFileByByte(filePath, fileName, buf.getBytes("UTF-8"));
747 | } catch (UnsupportedEncodingException e) {
748 | e.printStackTrace();
749 | throw ExceptionHelper.unchecked(e);
750 | }
751 | }
752 |
753 | public static void writeFileByByte(String filePath, String fileName, byte[] buf) {
754 | BufferedOutputStream bos = null;
755 | FileOutputStream fos = null;
756 | File file = null;
757 | try {
758 | File dir = new File(filePath);
759 | if (!dir.exists() && dir.isDirectory()) {
760 | dir.mkdirs();
761 | }
762 | //创建文件
763 | createFile(filePath + File.separator + fileName);
764 | //
765 | file = new File(filePath + File.separator + fileName);
766 | fos = new FileOutputStream(file);
767 | bos = new BufferedOutputStream(fos);
768 | bos.write(buf);
769 | } catch (Exception e) {
770 | e.printStackTrace();
771 | } finally {
772 | if (bos != null) {
773 | IOUtils.closeQuietly(bos);
774 | }
775 | if (fos != null) {
776 | IOUtils.closeQuietly(fos);
777 | }
778 | }
779 | }
780 |
781 | /**
782 | * 创建目录
783 | *
784 | * @param descDirName 目录名,包含路径
785 | * @return 如果创建成功,则返回true,否则返回false
786 | */
787 | public static boolean createDirectory(String descDirName) {
788 | String descDirNames = descDirName;
789 | if (!descDirNames.endsWith(File.separator)) {
790 | descDirNames = descDirNames + File.separator;
791 | }
792 | File descDir = new File(descDirNames);
793 | if (descDir.exists()) {
794 | logger.debug("目录 " + descDirNames + " 已存在!");
795 | return false;
796 | }
797 | // 创建目录
798 | if (descDir.mkdirs()) {
799 | logger.debug("目录 " + descDirNames + " 创建成功!");
800 | return true;
801 | } else {
802 | logger.debug("目录 " + descDirNames + " 创建失败!");
803 | return false;
804 | }
805 |
806 | }
807 |
808 |
809 | //=================================下载相关=================================
810 |
811 | /**
812 | * 向浏览器发送文件下载,支持断点续传
813 | *
814 | * @param file 要下载的文件
815 | * @param request 请求对象
816 | * @param response 响应对象
817 | * @return 返回错误信息,无错误信息返回null
818 | */
819 | public static String downFile(File file, HttpServletRequest request, HttpServletResponse response) {
820 | return downFile(file, request, response, null);
821 | }
822 |
823 | /**
824 | * 向浏览器发送文件下载,支持断点续传
825 | *
826 | * @param file 要下载的文件
827 | * @param request 请求对象
828 | * @param response 响应对象
829 | * @param fileName 指定下载的文件名
830 | * @return 返回错误信息,无错误信息返回null
831 | */
832 | public static String downFile(File file, HttpServletRequest request, HttpServletResponse response, String fileName) {
833 | String error = null;
834 | if (file != null && file.exists()) {
835 | if (file.isFile()) {
836 | if (file.length() <= 0) {
837 | error = "该文件是一个空文件。";
838 | }
839 | if (!file.canRead()) {
840 | error = "该文件没有读取权限。";
841 | }
842 | } else {
843 | error = "该文件是一个文件夹。";
844 | }
845 | } else {
846 | error = "文件已丢失或不存在!";
847 | }
848 | if (error != null) {
849 | logger.debug("---------------" + file + " " + error);
850 | return error;
851 | }
852 |
853 | long fileLength = file.length(); // 记录文件大小
854 | long pastLength = 0; // 记录已下载文件大小
855 | int rangeSwitch = 0; // 0:从头开始的全文下载;1:从某字节开始的下载(bytes=27000-);2:从某字节开始到某字节结束的下载(bytes=27000-39000)
856 | long toLength = 0; // 记录客户端需要下载的字节段的最后一个字节偏移量(比如bytes=27000-39000,则这个值是为39000)
857 | long contentLength = 0; // 客户端请求的字节总量
858 | String rangeBytes = ""; // 记录客户端传来的形如“bytes=27000-”或者“bytes=27000-39000”的内容
859 | RandomAccessFile raf = null; // 负责读取数据
860 | OutputStream os = null; // 写出数据
861 | OutputStream out = null; // 缓冲
862 | byte b[] = new byte[1024]; // 暂存容器
863 |
864 | if (request.getHeader("Range") != null) { // 客户端请求的下载的文件块的开始字节
865 | response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);
866 | logger.debug("request.getHeader(\"Range\") = " + request.getHeader("Range"));
867 | rangeBytes = request.getHeader("Range").replaceAll("bytes=", "");
868 | if (rangeBytes.indexOf('-') == rangeBytes.length() - 1) {// bytes=969998336-
869 | rangeSwitch = 1;
870 | rangeBytes = rangeBytes.substring(0, rangeBytes.indexOf('-'));
871 | pastLength = Long.parseLong(rangeBytes.trim());
872 | contentLength = fileLength - pastLength; // 客户端请求的是 969998336 之后的字节
873 | } else { // bytes=1275856879-1275877358
874 | rangeSwitch = 2;
875 | String temp0 = rangeBytes.substring(0, rangeBytes.indexOf('-'));
876 | String temp2 = rangeBytes.substring(rangeBytes.indexOf('-') + 1, rangeBytes.length());
877 | pastLength = Long.parseLong(temp0.trim()); // bytes=1275856879-1275877358,从第 1275856879 个字节开始下载
878 | toLength = Long.parseLong(temp2); // bytes=1275856879-1275877358,到第 1275877358 个字节结束
879 | contentLength = toLength - pastLength; // 客户端请求的是 1275856879-1275877358 之间的字节
880 | }
881 | } else { // 从开始进行下载
882 | contentLength = fileLength; // 客户端要求全文下载
883 | }
884 |
885 | // 如果设置了Content-Length,则客户端会自动进行多线程下载。如果不希望支持多线程,则不要设置这个参数。 响应的格式是:
886 | // Content-Length: [文件的总大小] - [客户端请求的下载的文件块的开始字节]
887 | // ServletActionContext.getResponse().setHeader("Content- Length", new Long(file.length() - p).toString());
888 | response.reset(); // 告诉客户端允许断点续传多线程连接下载,响应的格式是:Accept-Ranges: bytes
889 | if (pastLength != 0) {
890 | response.setHeader("Accept-Ranges", "bytes");// 如果是第一次下,还没有断点续传,状态是默认的 200,无需显式设置;响应的格式是:HTTP/1.1 200 OK
891 | // 不是从最开始下载, 响应的格式是: Content-Range: bytes [文件块的开始字节]-[文件的总大小 - 1]/[文件的总大小]
892 | logger.debug("---------------不是从开始进行下载!服务器即将开始断点续传...");
893 | switch (rangeSwitch) {
894 | case 1: { // 针对 bytes=27000- 的请求
895 | String contentRange = new StringBuffer("bytes ").append(new Long(pastLength).toString()).append("-")
896 | .append(new Long(fileLength - 1).toString()).append("/").append(new Long(fileLength).toString()).toString();
897 | response.setHeader("Content-Range", contentRange);
898 | break;
899 | }
900 | case 2: { // 针对 bytes=27000-39000 的请求
901 | String contentRange = rangeBytes + "/" + new Long(fileLength).toString();
902 | response.setHeader("Content-Range", contentRange);
903 | break;
904 | }
905 | default: {
906 | break;
907 | }
908 | }
909 | } else {
910 | // 是从开始下载
911 | logger.debug("---------------是从开始进行下载!");
912 | }
913 |
914 | try {
915 | response.addHeader("Content-Disposition", "attachment; filename=\"" +
916 | UtilString.urlEncode(UtilString.isBlank(fileName) ? file.getName() : fileName) + "\"");
917 | response.setContentType(getContentType(file.getName())); // set the MIME type.
918 | response.addHeader("Content-Length", String.valueOf(contentLength));
919 | os = response.getOutputStream();
920 | out = new BufferedOutputStream(os);
921 | raf = new RandomAccessFile(file, "r");
922 | try {
923 | switch (rangeSwitch) {
924 | case 0: { // 普通下载,或者从头开始的下载 同1
925 | }
926 | case 1: { // 针对 bytes=27000- 的请求
927 | raf.seek(pastLength); // 形如 bytes=969998336- 的客户端请求,跳过 969998336 个字节
928 | int n = 0;
929 | while ((n = raf.read(b, 0, 1024)) != -1) {
930 | out.write(b, 0, n);
931 | }
932 | break;
933 | }
934 | case 2: { // 针对 bytes=27000-39000 的请求
935 | raf.seek(pastLength); // 形如 bytes=1275856879-1275877358 的客户端请求,找到第 1275856879 个字节
936 | int n = 0;
937 | long readLength = 0; // 记录已读字节数
938 | while (readLength <= contentLength - 1024) {// 大部分字节在这里读取
939 | n = raf.read(b, 0, 1024);
940 | readLength += 1024;
941 | out.write(b, 0, n);
942 | }
943 | if (readLength <= contentLength) { // 余下的不足 1024 个字节在这里读取
944 | n = raf.read(b, 0, (int) (contentLength - readLength));
945 | out.write(b, 0, n);
946 | }
947 | break;
948 | }
949 | default: {
950 | break;
951 | }
952 | }
953 | out.flush();
954 | logger.debug("---------------下载完成!");
955 | } catch (IOException ie) {
956 | /**
957 | * 在写数据的时候, 对于 ClientAbortException 之类的异常,
958 | * 是因为客户端取消了下载,而服务器端继续向浏览器写入数据时, 抛出这个异常,这个是正常的。
959 | * 尤其是对于迅雷这种吸血的客户端软件, 明明已经有一个线程在读取 bytes=1275856879-1275877358,
960 | * 如果短时间内没有读取完毕,迅雷会再启第二个、第三个。。。线程来读取相同的字节段, 直到有一个线程读取完毕,迅雷会 KILL
961 | * 掉其他正在下载同一字节段的线程, 强行中止字节读出,造成服务器抛 ClientAbortException。
962 | * 所以,我们忽略这种异常
963 | */
964 | logger.warn("提醒:向客户端传输时出现IO异常,但此异常是允许的,有可能客户端取消了下载,导致此异常,不用关心!");
965 | }
966 | } catch (Exception e) {
967 | logger.error(e.getMessage(), e);
968 | } finally {
969 | if (out != null) {
970 | try {
971 | out.close();
972 | } catch (IOException e) {
973 | logger.error(e.getMessage(), e);
974 | }
975 | }
976 | if (raf != null) {
977 | try {
978 | raf.close();
979 | } catch (IOException e) {
980 | logger.error(e.getMessage(), e);
981 | }
982 | }
983 | }
984 | return null;
985 | }
986 |
987 | /**
988 | * byte[] 转 InputStream 数组转数据流
989 | *
990 | * @param byteData 数组
991 | * @return
992 | */
993 | public static InputStream byte2Input(byte[] byteData) {
994 | if (byteData.length > 0) {
995 | return new ByteArrayInputStream(byteData);
996 | }
997 | logger.info("byte数组转InputStream 流异常,传入数组为空");
998 | return null;
999 | }
1000 |
1001 | /**
1002 | * InputStream流转byte数组
1003 | *
1004 | * @param inStream 输入流
1005 | * @return 数组
1006 | * @throws IOException
1007 | */
1008 | public static byte[] input2byte(InputStream inStream)
1009 | throws IOException {
1010 | if (Objects.isNull(inStream)) {
1011 | return new byte[0];
1012 | }
1013 | ByteArrayOutputStream swapStream = new ByteArrayOutputStream();
1014 | byte[] buff = new byte[100];
1015 | int rc = 0;
1016 | while ((rc = inStream.read(buff, 0, 100)) > 0) {
1017 | swapStream.write(buff, 0, rc);
1018 | }
1019 | return swapStream.toByteArray();
1020 | }
1021 | }
--------------------------------------------------------------------------------
/src/main/java/com/example/demo/util/UtilNet.java:
--------------------------------------------------------------------------------
1 | package com.example.demo.util;
2 |
3 | import java.io.IOException;
4 | import java.net.*;
5 | import java.util.Enumeration;
6 | import java.util.Random;
7 | import java.util.regex.Pattern;
8 |
9 | /**
10 | * @author zoubin02 on 7/23/14.
11 | */
12 | public class UtilNet {
13 |
14 | public static final String LOCALHOST = "127.0.0.1";
15 | public static final String ANYHOST = "0.0.0.0";
16 |
17 | private static final int RND_PORT_START = 30000;
18 | private static final int RND_PORT_RANGE = 10000;
19 |
20 | private static final Random RANDOM = new Random(System.currentTimeMillis());
21 | private static final int MIN_PORT = 0;
22 | private static final int MAX_PORT = 65535;
23 | private static final Pattern ADDRESS_PATTERN = Pattern.compile("^\\d{1,3}(\\.\\d{1,3}){3}\\:\\d{1,5}$");
24 | private static final Pattern LOCAL_IP_PATTERN = Pattern.compile("127(\\.\\d{1,3}){3}$");
25 | private static final Pattern IP_PATTERN = Pattern.compile("\\d{1,3}(\\.\\d{1,3}){3,5}$");
26 | private static volatile InetAddress LOCAL_ADDRESS = null;
27 |
28 | private UtilNet() {
29 | }
30 |
31 | public static int getRandomPort() {
32 | return RND_PORT_START + RANDOM.nextInt(RND_PORT_RANGE);
33 | }
34 |
35 | public static int getAvailablePort() {
36 | ServerSocket ss = null;
37 | try {
38 | ss = new ServerSocket();
39 | ss.bind(null);
40 | return ss.getLocalPort();
41 | } catch (IOException e) {
42 | return getRandomPort();
43 | } finally {
44 | if (ss != null) {
45 | try {
46 | ss.close();
47 | } catch (IOException e) {
48 | }
49 | }
50 | }
51 | }
52 |
53 | public static int getAvailablePort(int port) {
54 | if (port <= 0) {
55 | return getAvailablePort();
56 | }
57 | for (int i = port; i < MAX_PORT; i++) {
58 | ServerSocket ss = null;
59 | try {
60 | ss = new ServerSocket(i);
61 | return i;
62 | } catch (IOException e) {
63 | // continue
64 | } finally {
65 | if (ss != null) {
66 | try {
67 | ss.close();
68 | } catch (IOException e) {
69 | }
70 | }
71 | }
72 | }
73 | return port;
74 | }
75 |
76 | public static boolean isInvalidPort(int port) {
77 | return port > MIN_PORT || port <= MAX_PORT;
78 | }
79 |
80 | public static boolean isValidAddress(String address) {
81 | return ADDRESS_PATTERN.matcher(address).matches();
82 | }
83 |
84 | public static boolean isLocalHost(String host) {
85 | return host != null
86 | && (LOCAL_IP_PATTERN.matcher(host).matches()
87 | || host.equalsIgnoreCase("localhost"));
88 | }
89 |
90 | public static boolean isAnyHost(String host) {
91 | return "0.0.0.0".equals(host);
92 | }
93 |
94 | public static boolean isInvalidLocalHost(String host) {
95 | return host == null
96 | || host.length() == 0
97 | || host.equalsIgnoreCase("localhost")
98 | || host.equals("0.0.0.0")
99 | || (LOCAL_IP_PATTERN.matcher(host).matches());
100 | }
101 |
102 | public static boolean isValidLocalHost(String host) {
103 | return !isInvalidLocalHost(host);
104 | }
105 |
106 | public static InetSocketAddress getLocalSocketAddress(String host, int port) {
107 | return isInvalidLocalHost(host) ?
108 | new InetSocketAddress(port) : new InetSocketAddress(host, port);
109 | }
110 |
111 | private static boolean isValidAddress(InetAddress address) {
112 | if (address == null || address.isLoopbackAddress())
113 | return false;
114 | String name = address.getHostAddress();
115 | return (name != null
116 | && !ANYHOST.equals(name)
117 | && !LOCALHOST.equals(name)
118 | && IP_PATTERN.matcher(name).matches());
119 | }
120 |
121 | public static boolean isValidHost(String host){
122 | return IP_PATTERN.matcher(host).matches();
123 | }
124 |
125 | public static String getLocalHost() {
126 | InetAddress address = getLocalAddress();
127 | String result = address == null ? LOCALHOST : address.getHostAddress();
128 | if("0:0:0:0:0:0:0:1".equalsIgnoreCase(result)){
129 | return LOCALHOST;
130 | }
131 | return result;
132 | }
133 |
134 | public static String getLocalHostName(){
135 | InetAddress address = getLocalAddress();
136 | if(address == null){
137 | return "localhost";
138 | }
139 | return address.getHostName();
140 | }
141 |
142 | /**
143 | * 遍历本地网卡,返回第一个合理的IP。
144 | *
145 | * @return 本地网卡IP
146 | */
147 | public static InetAddress getLocalAddress() {
148 | if (LOCAL_ADDRESS != null)
149 | return LOCAL_ADDRESS;
150 | InetAddress localAddress = getLocalAddress0();
151 | LOCAL_ADDRESS = localAddress;
152 | return localAddress;
153 | }
154 |
155 | private static InetAddress getLocalAddress0() {
156 | InetAddress localAddress = null;
157 | try {
158 | localAddress = InetAddress.getLocalHost();
159 | if (isValidAddress(localAddress)) {
160 | return localAddress;
161 | }
162 | } catch (Exception e) {
163 | // logger.warn("Failed to retriving ip address, " + e.getMessage(), e);
164 | }
165 | try {
166 | Enumeration interfaces = NetworkInterface.getNetworkInterfaces();
167 | if (interfaces != null) {
168 | while (interfaces.hasMoreElements()) {
169 | try {
170 | NetworkInterface network = interfaces.nextElement();
171 | if (network.isLoopback() || network.isVirtual() || !network.isUp()) {
172 | continue;
173 | }
174 | Enumeration addresses = network.getInetAddresses();
175 | while (addresses.hasMoreElements()) {
176 | try {
177 | InetAddress address = addresses.nextElement();
178 | if (isValidAddress(address)) {
179 | return address;
180 | }
181 | } catch (Exception e) {
182 | // logger.warn("Failed to retriving ip address, " + e.getMessage(), e);
183 | }
184 | }
185 | } catch (Exception e) {
186 | // logger.warn("Failed to retriving ip address, " + e.getMessage(), e);
187 | }
188 | }
189 | }
190 | } catch (Exception e) {
191 | // logger.warn("Failed to retriving ip address, " + e.getMessage(), e);
192 | }
193 | // logger.error("Could not get local host ip address, will use 127.0.0.1 instead.");
194 | return localAddress;
195 | }
196 |
197 |
198 | /**
199 | * @param hostName
200 | * @return ip address or hostName if UnknownHostException
201 | */
202 | public static String getIpByHost(String hostName) {
203 | try {
204 | return InetAddress.getByName(hostName).getHostAddress();
205 | } catch (UnknownHostException e) {
206 | return hostName;
207 | }
208 | }
209 |
210 | public static String toAddressString(InetSocketAddress address) {
211 | return address.getAddress().getHostAddress() + ":" + address.getPort();
212 | }
213 |
214 | public static InetSocketAddress toAddress(String address) {
215 | int i = address.indexOf(':');
216 | String host;
217 | int port;
218 | if (i > -1) {
219 | host = address.substring(0, i);
220 | port = Integer.parseInt(address.substring(i + 1));
221 | } else {
222 | host = address;
223 | port = 0;
224 | }
225 | return new InetSocketAddress(host, port);
226 | }
227 |
228 | public static String toURL(String protocol, String host, int port, String path) {
229 | StringBuilder sb = new StringBuilder();
230 | sb.append(protocol).append("://");
231 | sb.append(host).append(':').append(port);
232 | if (path.charAt(0) != '/')
233 | sb.append('/');
234 | sb.append(path);
235 | return sb.toString();
236 | }
237 |
238 | }
239 |
--------------------------------------------------------------------------------
/src/main/java/com/example/demo/util/UtilString.java:
--------------------------------------------------------------------------------
1 | package com.example.demo.util;
2 |
3 | import org.apache.commons.lang3.StringEscapeUtils;
4 | import org.slf4j.helpers.FormattingTuple;
5 | import org.slf4j.helpers.MessageFormatter;
6 |
7 | import java.io.UnsupportedEncodingException;
8 | import java.net.URLEncoder;
9 | import java.util.Collection;
10 | import java.util.Map;
11 | import java.util.concurrent.ConcurrentHashMap;
12 | import java.util.regex.Matcher;
13 | import java.util.regex.Pattern;
14 |
15 | /**
16 | * UtilString
17 | *
18 | * @author wuchenl
19 | */
20 |
21 | public class UtilString extends org.apache.commons.lang3.StringUtils {
22 |
23 |
24 | //编译后的正则表达式缓存
25 | private static final Map PATTERN_CACHE = new ConcurrentHashMap<>();
26 | private static final char SEPARATOR = '_';
27 | private static final String CHARSET_NAME = "UTF-8";
28 |
29 |
30 |
31 | public static String defaultIfNull(String object, String defaultValue) {
32 | return object == null ? defaultValue : object;
33 | }
34 |
35 |
36 | /**
37 | * 编译一个正则表达式,并且进行缓存,如果换成已存在则使用缓存
38 | *
39 | * @param regex 表达式
40 | * @return 编译后的Pattern
41 | */
42 | public static final Pattern compileRegex(String regex) {
43 | Pattern pattern = PATTERN_CACHE.get(regex);
44 | if (pattern == null) {
45 | pattern = Pattern.compile(regex);
46 | PATTERN_CACHE.put(regex, pattern);
47 | }
48 | return pattern;
49 | }
50 |
51 | /**
52 | * 对象是否为无效值
53 | *
54 | * @param obj 要判断的对象
55 | * @return 是否为有效值(不为null 和 "" 字符串)
56 | */
57 | public static boolean isEmptyObject(Object obj) {
58 | return obj == null || "".equals(obj.toString());
59 | }
60 |
61 |
62 |
63 |
64 |
65 |
66 | /**
67 | * 对象是否为true
68 | *
69 | * @param obj
70 | * @return
71 | */
72 | public static boolean isTrue(Object obj) {
73 | return "true".equals(String.valueOf(obj));
74 | }
75 |
76 |
77 | /**
78 | * 参数是否是有效数字 (整数或者小数)
79 | *
80 | * @param obj 参数(对象将被调用string()转为字符串类型)
81 | * @return 是否是数字
82 | */
83 | public static boolean isNumber(Object obj) {
84 | if (obj instanceof Number) {
85 | return true;
86 | }
87 | return isInt(obj) || isDouble(obj);
88 | }
89 |
90 | /**
91 | * 参数是否是有效整数
92 | *
93 | * @param obj 参数(对象将被调用string()转为字符串类型)
94 | * @return 是否是整数
95 | */
96 | public static boolean isInt(Object obj) {
97 | if (isEmptyObject(obj)) {
98 | return false;
99 | }
100 | if (obj instanceof Integer) {
101 | return true;
102 | }
103 | return obj.toString().matches("[-+]?\\d+");
104 | }
105 |
106 | /**
107 | * 字符串参数是否是double
108 | *
109 | * @param obj 参数(对象将被调用string()转为字符串类型)
110 | * @return 是否是double
111 | */
112 | public static boolean isDouble(Object obj) {
113 | if (isEmptyObject(obj)) {
114 | return false;
115 | }
116 | if (obj instanceof Double || obj instanceof Float) {
117 | return true;
118 | }
119 | return compileRegex("[-+]?\\d+\\.\\d+").matcher(obj.toString()).matches();
120 | }
121 |
122 | /**
123 | * 判断一个对象是否为boolean类型,包括字符串中的true和false
124 | *
125 | * @param obj 要判断的对象
126 | * @return 是否是一个boolean类型
127 | */
128 | public static boolean isBoolean(Object obj) {
129 | if (obj instanceof Boolean) {
130 | return true;
131 | }
132 | String strVal = String.valueOf(obj);
133 | return "true".equalsIgnoreCase(strVal) || "false".equalsIgnoreCase(strVal);
134 | }
135 |
136 | /**
137 | * 将对象转为int值,如果对象无法进行转换,则使用默认值
138 | *
139 | * @param object 要转换的对象
140 | * @param defaultValue 默认值
141 | * @return 转换后的值
142 | */
143 | public static int toInt(Object object, int defaultValue) {
144 | if (object instanceof Number) {
145 | return ((Number) object).intValue();
146 | }
147 | if (isInt(object)) {
148 | return Integer.parseInt(object.toString());
149 | }
150 | if (isDouble(object)) {
151 | return (int) Double.parseDouble(object.toString());
152 | }
153 | return defaultValue;
154 | }
155 |
156 | /**
157 | * 将对象转为int值,如果对象不能转为,将返回0
158 | *
159 | * @param object 要转换的对象
160 | * @return 转换后的值
161 | */
162 | public static int toInt(Object object) {
163 | return toInt(object, 0);
164 | }
165 |
166 | /**
167 | * 将对象转为long类型,如果对象无法转换,将返回默认值
168 | *
169 | * @param object 要转换的对象
170 | * @param defaultValue 默认值
171 | * @return 转换后的值
172 | */
173 | public static long toLong(Object object, long defaultValue) {
174 | if (object instanceof Number) {
175 | return ((Number) object).longValue();
176 | }
177 | if (isInt(object)) {
178 | return Long.parseLong(object.toString());
179 | }
180 | if (isDouble(object)) {
181 | return (long) Double.parseDouble(object.toString());
182 | }
183 | return defaultValue;
184 | }
185 |
186 | /**
187 | * 将对象转为 long值,如果无法转换,则转为0
188 | *
189 | * @param object 要转换的对象
190 | * @return 转换后的值
191 | */
192 | public static long toLong(Object object) {
193 | return toLong(object, 0);
194 | }
195 |
196 | /**
197 | * 将对象转为Double,如果对象无法转换,将使用默认值
198 | *
199 | * @param object 要转换的对象
200 | * @param defaultValue 默认值
201 | * @return 转换后的值
202 | */
203 | public static double toDouble(Object object, double defaultValue) {
204 | if (object instanceof Number) {
205 | return ((Number) object).doubleValue();
206 | }
207 | if (isNumber(object)) {
208 | return Double.parseDouble(object.toString());
209 | }
210 | if (null == object) {
211 | return defaultValue;
212 | }
213 | return 0;
214 | }
215 |
216 | /**
217 | * 将对象转为Double,如果对象无法转换,将使用默认值0
218 | *
219 | * @param object 要转换的对象
220 | * @return 转换后的值
221 | */
222 | public static double toDouble(Object object) {
223 | return toDouble(object, 0);
224 | }
225 |
226 |
227 |
228 |
229 |
230 |
231 |
232 |
233 | /**
234 | * 字符串连接,将参数列表拼接为一个字符串
235 | *
236 | * @return 返回拼接后的字符串
237 | */
238 | public static String concatWithMarkAndSplit(String mark , String split , Collection more) {
239 | StringBuilder buf = new StringBuilder();
240 | if(more==null || more.isEmpty()){
241 | return buf.toString();
242 | }
243 | int i = 0;
244 | for (Object obj : more) {
245 | if (i != 0) {
246 | buf.append(mark).append(obj).append(mark).append(split);
247 | }
248 | buf.append(mark).append(obj).append(mark);
249 | i++;
250 | }
251 | return buf.toString();
252 | }
253 |
254 | public static String concat(Collection more) {
255 | return concatWithSpilt("", more);
256 | }
257 |
258 | public static String concatWithSpilt(String split , Collection more) {
259 | StringBuilder buf = new StringBuilder();
260 | int i = 0;
261 | for (Object obj : more) {
262 | if (i != 0) {
263 | buf.append(split);
264 | }
265 | buf.append(obj);
266 | i++;
267 | }
268 | return buf.toString();
269 | }
270 |
271 | public static String concat(Object... more) {
272 | return concatWithSpilt("", more);
273 | }
274 |
275 | public static String concatWithSpilt(String split , Object... more) {
276 | StringBuilder buf = new StringBuilder();
277 | for (int i = 0; i < more.length; i++) {
278 | if (i != 0) {
279 | buf.append(split);
280 | }
281 | buf.append(more[i]);
282 | }
283 | return buf.toString();
284 | }
285 |
286 |
287 |
288 |
289 |
290 |
291 |
292 | /**
293 | * 驼峰命名法工具
294 | * @return
295 | * toCamelCase("hello_world") == "helloWorld"
296 | * toCapitalizeCamelCase("hello_world") == "HelloWorld"
297 | * toUnderScoreCase("helloWorld") = "hello_world"
298 | */
299 | public static String toCamelCase(String s) {
300 | if (s == null) {
301 | return null;
302 | }
303 |
304 | s = s.toLowerCase();
305 |
306 | StringBuilder sb = new StringBuilder(s.length());
307 | boolean upperCase = false;
308 | for (int i = 0; i < s.length(); i++) {
309 | char c = s.charAt(i);
310 |
311 | if (c == SEPARATOR) {
312 | upperCase = true;
313 | } else if (upperCase) {
314 | sb.append(Character.toUpperCase(c));
315 | upperCase = false;
316 | } else {
317 | sb.append(c);
318 | }
319 | }
320 |
321 | return sb.toString();
322 | }
323 |
324 | /**
325 | * 驼峰命名法工具
326 | * @return
327 | * toCamelCase("hello_world") == "helloWorld"
328 | * toCapitalizeCamelCase("hello_world") == "HelloWorld"
329 | * toUnderScoreCase("helloWorld") = "hello_world"
330 | */
331 | public static String toCapitalizeCamelCase(String s) {
332 | if (s == null) {
333 | return null;
334 | }
335 | s = toCamelCase(s);
336 | return s.substring(0, 1).toUpperCase() + s.substring(1);
337 | }
338 |
339 | /**
340 | * 驼峰命名法工具
341 | * @return
342 | * toCamelCase("hello_world") == "helloWorld"
343 | * toCapitalizeCamelCase("hello_world") == "HelloWorld"
344 | * toUnderScoreCase("helloWorld") = "hello_world"
345 | */
346 | public static String toUnderScoreCase(String s) {
347 | if (s == null) {
348 | return null;
349 | }
350 |
351 | StringBuilder sb = new StringBuilder();
352 | boolean upperCase = false;
353 | for (int i = 0; i < s.length(); i++) {
354 | char c = s.charAt(i);
355 |
356 | boolean nextUpperCase = true;
357 |
358 | if (i < (s.length() - 1)) {
359 | nextUpperCase = Character.isUpperCase(s.charAt(i + 1));
360 | }
361 |
362 | if ((i > 0) && Character.isUpperCase(c)) {
363 | if (!upperCase || !nextUpperCase) {
364 | sb.append(SEPARATOR);
365 | }
366 | upperCase = true;
367 | } else {
368 | upperCase = false;
369 | }
370 |
371 | sb.append(Character.toLowerCase(c));
372 | }
373 |
374 | return sb.toString();
375 | }
376 |
377 |
378 |
379 |
380 |
381 |
382 |
383 |
384 | /**
385 | * 缩略字符串(不区分中英文字符)
386 | * @param str 目标字符串
387 | * @param length 截取长度
388 | * @return
389 | */
390 | public static String abbr(String str, int length) {
391 | if (str == null) {
392 | return "";
393 | }
394 | try {
395 | StringBuilder sb = new StringBuilder();
396 | int currentLength = 0;
397 | for (char c : replaceHtml(StringEscapeUtils.unescapeHtml4(str)).toCharArray()) {
398 | currentLength += String.valueOf(c).getBytes("GBK").length;
399 | if (currentLength <= length - 3) {
400 | sb.append(c);
401 | } else {
402 | sb.append("...");
403 | break;
404 | }
405 | }
406 | return sb.toString();
407 | } catch (UnsupportedEncodingException e) {
408 | e.printStackTrace();
409 | }
410 | return "";
411 | }
412 |
413 |
414 |
415 | /**
416 | * 替换掉HTML标签方法
417 | */
418 | public static String replaceHtml(String html) {
419 | if (isBlank(html)){
420 | return "";
421 | }
422 | String regEx = "<.+?>";
423 | Pattern p = Pattern.compile(regEx);
424 | Matcher m = p.matcher(html);
425 | String s = m.replaceAll("");
426 | return s;
427 | }
428 |
429 |
430 |
431 | /**
432 | * 转换为JS获取对象值,生成三目运算返回结果
433 | * @param objectString 对象串
434 | * 例如:row.user.id
435 | * 返回:!row?'':!row.user?'':!row.user.id?'':row.user.id
436 | */
437 | public static String javascriptGetValueStyle(String objectString){
438 | StringBuilder result = new StringBuilder();
439 | StringBuilder val = new StringBuilder();
440 | String[] vals = split(objectString, ".");
441 | for (int i=0; i getParameters(HttpServletRequest request) {
45 | Map parameters = new HashMap<>();
46 | Enumeration enumeration = request.getParameterNames();
47 | while (enumeration.hasMoreElements()) {
48 | String name = String.valueOf(enumeration.nextElement());
49 | parameters.put(name, request.getParameter(name));
50 | }
51 | return parameters;
52 | }
53 |
54 | public static Map getHeaders(HttpServletRequest request) {
55 | Map map = new LinkedHashMap<>();
56 | Enumeration enumeration = request.getHeaderNames();
57 | while (enumeration.hasMoreElements()) {
58 | String key = enumeration.nextElement();
59 | String value = request.getHeader(key);
60 | map.put(key, value);
61 | }
62 | return map;
63 | }
64 |
65 |
66 | //获取请求客户端的真实ip地址
67 | public static String getIpAddr(HttpServletRequest request) {
68 | String ip = request.getHeader("x-forwarded-for");
69 |
70 | if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
71 | ip = request.getHeader("x-forwarded-host");
72 | }
73 | if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
74 | ip = request.getHeader("X-Real-IP");
75 | }
76 | if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
77 | ip = request.getHeader("Proxy-Client-IP");
78 | }
79 | if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
80 | ip = request.getHeader("WL-Proxy-Client-IP");
81 | }
82 | if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
83 | ip = request.getRemoteAddr();
84 | }
85 | if("0:0:0:0:0:0:0:1".equalsIgnoreCase(ip)){
86 | return UtilNet.LOCALHOST;
87 | }
88 | return ip;
89 | }
90 |
91 | /**
92 | * 获取请求体内容
93 | */
94 | public static String getRequestPayload(HttpServletRequest request) {
95 | StringBuilder sb = new StringBuilder();
96 | BufferedReader reader = null;
97 | try {
98 | request.setCharacterEncoding("utf8");
99 | reader = request.getReader();
100 | //做标记为了reset
101 | reader.mark(0);
102 | char[] buff = new char[1024];
103 | int len;
104 | while ((len = reader.read(buff)) != -1) {
105 | sb.append(buff, 0, len);
106 | }
107 | } catch (IOException e) {
108 | logger.error(e.getMessage());
109 | }finally{
110 | if (reader!=null){
111 | try {
112 | reader.reset();
113 | } catch (IOException e) {
114 | logger.error(e.getMessage());
115 | }
116 | }
117 | }
118 | return sb.toString();
119 | }
120 | }
121 |
--------------------------------------------------------------------------------
/src/main/resources/application.yml:
--------------------------------------------------------------------------------
1 | server:
2 | port: 8080
3 | servlet:
4 | context-path: /captcha
5 | spring:
6 | mvc:
7 | view:
8 | prefix: /jsp/
9 | suffix: .jsp
10 | cache:
11 | type: caffeine
12 | cacheNames:
13 | verificationCode: 600
14 | # 生成的图片缓存时间 30S,图片生成完以后就会立刻会消费掉 30S周期已经够长。或者考虑改为使用后即移除。
15 | captchaImage: 30
16 | com:
17 | letters7:
18 | wuchen:
19 | captcha:
20 | source:
21 | path: static/img/source/
22 | size: 5
--------------------------------------------------------------------------------
/src/main/resources/static/css/captcha.css:
--------------------------------------------------------------------------------
1 | body {
2 | padding: 0px;
3 | margin: 0px;
4 | }
5 |
6 | .yzm {
7 | width: 260px;
8 | height: 120px;
9 | position: relative;
10 | margin-left: 80px;
11 | }
12 |
13 | #drag {
14 | position: relative;
15 | background-color: #e8e8e8;
16 | width: 300px;
17 | height: 34px;
18 | line-height: 34px;
19 | margin-left: 80px;
20 | text-align: center;
21 | }
22 |
23 | #drag .handler {
24 | position: absolute;
25 | top: 0px;
26 | left: 0px;
27 | width: 40px;
28 | height: 32px;
29 | border: 1px solid #ccc;
30 | cursor: move;
31 | }
32 |
33 | .handler_bg {
34 | background: #fff url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAA3hpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuNS1jMDIxIDc5LjE1NTc3MiwgMjAxNC8wMS8xMy0xOTo0NDowMCAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtcE1NOk9yaWdpbmFsRG9jdW1lbnRJRD0ieG1wLmRpZDo0ZDhlNWY5My05NmI0LTRlNWQtOGFjYi03ZTY4OGYyMTU2ZTYiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6NTEyNTVEMURGMkVFMTFFNEI5NDBCMjQ2M0ExMDQ1OUYiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6NTEyNTVEMUNGMkVFMTFFNEI5NDBCMjQ2M0ExMDQ1OUYiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENDIDIwMTQgKE1hY2ludG9zaCkiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDo2MTc5NzNmZS02OTQxLTQyOTYtYTIwNi02NDI2YTNkOWU5YmUiIHN0UmVmOmRvY3VtZW50SUQ9InhtcC5kaWQ6NGQ4ZTVmOTMtOTZiNC00ZTVkLThhY2ItN2U2ODhmMjE1NmU2Ii8+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+YiRG4AAAALFJREFUeNpi/P//PwMlgImBQkA9A+bOnfsIiBOxKcInh+yCaCDuByoswaIOpxwjciACFegBqZ1AvBSIS5OTk/8TkmNEjwWgQiUgtQuIjwAxUF3yX3xyGIEIFLwHpKyAWB+I1xGSwxULIGf9A7mQkBwTlhBXAFLHgPgqEAcTkmNCU6AL9d8WII4HOvk3ITkWJAXWUMlOoGQHmsE45ViQ2KuBuASoYC4Wf+OUYxz6mQkgwAAN9mIrUReCXgAAAABJRU5ErkJggg==") no-repeat center;
35 | }
36 |
37 | .handler_ok_bg {
38 | background: #fff url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAA3hpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuNS1jMDIxIDc5LjE1NTc3MiwgMjAxNC8wMS8xMy0xOTo0NDowMCAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtcE1NOk9yaWdpbmFsRG9jdW1lbnRJRD0ieG1wLmRpZDo0ZDhlNWY5My05NmI0LTRlNWQtOGFjYi03ZTY4OGYyMTU2ZTYiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6NDlBRDI3NjVGMkQ2MTFFNEI5NDBCMjQ2M0ExMDQ1OUYiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6NDlBRDI3NjRGMkQ2MTFFNEI5NDBCMjQ2M0ExMDQ1OUYiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENDIDIwMTQgKE1hY2ludG9zaCkiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDphNWEzMWNhMC1hYmViLTQxNWEtYTEwZS04Y2U5NzRlN2Q4YTEiIHN0UmVmOmRvY3VtZW50SUQ9InhtcC5kaWQ6NGQ4ZTVmOTMtOTZiNC00ZTVkLThhY2ItN2U2ODhmMjE1NmU2Ii8+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+k+sHwwAAASZJREFUeNpi/P//PwMyKD8uZw+kUoDYEYgloMIvgHg/EM/ptHx0EFk9I8wAoEZ+IDUPiIMY8IN1QJwENOgj3ACo5gNAbMBAHLgAxA4gQ5igAnNJ0MwAVTsX7IKyY7L2UNuJAf+AmAmJ78AEDTBiwGYg5gbifCSxFCZoaBMCy4A4GOjnH0D6DpK4IxNSVIHAfSDOAeLraJrjgJp/AwPbHMhejiQnwYRmUzNQ4VQgDQqXK0ia/0I17wJiPmQNTNBEAgMlQIWiQA2vgWw7QppBekGxsAjIiEUSBNnsBDWEAY9mEFgMMgBk00E0iZtA7AHEctDQ58MRuA6wlLgGFMoMpIG1QFeGwAIxGZo8GUhIysmwQGSAZgwHaEZhICIzOaBkJkqyM0CAAQDGx279Jf50AAAAAABJRU5ErkJggg==") no-repeat center;
39 | }
40 |
41 | .handler_err_bg {
42 | background: #fff url("../img/erricon.png");
43 | }
44 |
45 | #drag .drag_bg {
46 | background-color: #7ac23c;
47 | height: 34px;
48 | width: 0px;
49 | }
50 |
51 | #drag .drag_err {
52 | background-color: darkred;
53 | height: 34px;
54 | width: 0px;
55 | }
56 |
57 | #drag .drag_text {
58 | position: absolute;
59 | top: 0px;
60 | width: 300px;
61 | -moz-user-select: none;
62 | -webkit-user-select: none;
63 | user-select: none;
64 | -o-user-select: none;
65 | -ms-user-select: none;
66 | }
67 |
68 | .yzm_image_source {
69 | float: left;
70 | width: 260px;
71 | height: 113px;
72 | margin: 0 !important;
73 | border: 0px;
74 | padding: 0 !important;
75 | background-image: url("../img/source/0.png");
76 | }
77 |
78 | .yzm_image_source:before {
79 | content: "";
80 | position: absolute;
81 | width: 100px;
82 | height: 100%;
83 | top: 0;
84 | left: -150px;
85 | overflow: hidden;
86 | background: -moz-linear-gradient(left, rgba(255, 255, 255, 0) 0, rgba(255, 255, 255, .2) 50%, rgba(255, 255, 255, 0) 100%);
87 | background: -webkit-gradient(linear, left top, right top, color-stop(0%, rgba(255, 255, 255, 0)),
88 | color-stop(50%, rgba(255, 255, 255, .2)), color-stop(100%, rgba(255, 255, 255, 0)));
89 | background: -webkit-linear-gradient(left, rgba(255, 255, 255, 0) 0, rgba(255, 255, 255, .2) 50%, rgba(255, 255, 255, 0) 100%);
90 | background: -o-linear-gradient(left, rgba(255, 255, 255, 0) 0, rgba(255, 255, 255, .2) 50%, rgba(255, 255, 0, 0) 100%);
91 | -webkit-transform: skewX(-25deg);
92 | -moz-transform: skewX(-25deg)
93 | }
94 |
95 | .run:before {
96 | left: 70%;
97 | transition: left 2s ease 0s;
98 | }
99 |
100 | .yzm_image_cut_big {
101 | float: left;
102 | width: 260px;
103 | height: 113px;
104 | margin: 0 !important;
105 | border: 0px;
106 | padding: 0 !important;
107 | }
108 |
109 | .yzm_image_cut_loading {
110 | float: left;
111 | width: 260px;
112 | height: 113px;
113 | margin: 0 !important;
114 | border: 0px;
115 | padding: 0 !important;
116 | background-image: url("../img/loading.png");
117 | }
118 |
119 | #xy_img {
120 | z-index: 999;
121 | width: 60px;
122 | height: 60px;
123 | position: relative;
124 | }
--------------------------------------------------------------------------------
/src/main/resources/static/img/erricon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wuchenl/java-JigsawVerification/5e4305ad3069582c46c1666ae1f099497b5b8ad7/src/main/resources/static/img/erricon.png
--------------------------------------------------------------------------------
/src/main/resources/static/img/loading.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wuchenl/java-JigsawVerification/5e4305ad3069582c46c1666ae1f099497b5b8ad7/src/main/resources/static/img/loading.png
--------------------------------------------------------------------------------
/src/main/resources/static/img/refresh.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wuchenl/java-JigsawVerification/5e4305ad3069582c46c1666ae1f099497b5b8ad7/src/main/resources/static/img/refresh.png
--------------------------------------------------------------------------------
/src/main/resources/static/img/source/0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wuchenl/java-JigsawVerification/5e4305ad3069582c46c1666ae1f099497b5b8ad7/src/main/resources/static/img/source/0.png
--------------------------------------------------------------------------------
/src/main/resources/static/img/source/1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wuchenl/java-JigsawVerification/5e4305ad3069582c46c1666ae1f099497b5b8ad7/src/main/resources/static/img/source/1.png
--------------------------------------------------------------------------------
/src/main/resources/static/img/source/2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wuchenl/java-JigsawVerification/5e4305ad3069582c46c1666ae1f099497b5b8ad7/src/main/resources/static/img/source/2.png
--------------------------------------------------------------------------------
/src/main/resources/static/img/source/3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wuchenl/java-JigsawVerification/5e4305ad3069582c46c1666ae1f099497b5b8ad7/src/main/resources/static/img/source/3.png
--------------------------------------------------------------------------------
/src/main/resources/static/img/source/4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wuchenl/java-JigsawVerification/5e4305ad3069582c46c1666ae1f099497b5b8ad7/src/main/resources/static/img/source/4.png
--------------------------------------------------------------------------------
/src/main/resources/static/js/drag.js:
--------------------------------------------------------------------------------
1 | (function ($) {
2 | $.fn.drag = function (options, sucfun, errfun) {
3 | var isvalid = false;
4 | var x, drag = this, isMove = false, defaults = {};
5 | var options = $.extend(defaults, options);
6 | //添加背景,文字,滑块
7 | var html = '' +
8 | '拖动图片验证登陆
' +
9 | '';
10 | this.append(html);
11 |
12 | var handler = drag.find('.handler');
13 | var drag_bg = drag.find('.drag_bg');
14 | var text = drag.find('.drag_text');
15 | var maxWidth = drag.width() - handler.width(); //能滑动的最大间距
16 |
17 | //鼠标按下时候的x轴的位置
18 | handler.mousedown(function (e) {
19 | if (isvalid) {
20 | return false;
21 | }
22 | $gt_cut.css("display", "none");
23 | $gt_cut_hidden.show();
24 | $xy_img.show();
25 | isMove = true;
26 | x = e.pageX - parseInt(handler.css('left'), 10);
27 | });
28 | var $xy_img = $("#xy_img");
29 | var $gt_cut = $("#yzm_image_source");
30 | var $gt_cut_hidden = $("#yzm_image_cut_big");
31 | //鼠标指针在上下文移动时,移动距离大于0小于最大间距,滑块x轴位置等于鼠标移动距离
32 | $("#drag").mousemove(function (e) {
33 | if (isvalid) {
34 | return false;
35 | }
36 | var _x = e.pageX - x;
37 | if (isMove) {
38 | if (_x > 0 && _x <= maxWidth) {
39 | $xy_img.css({'left': _x});
40 | handler.css({'left': _x});
41 | drag_bg.css({'width': _x});
42 | } else if (_x > maxWidth) { //鼠标指针移动距离达到最大时清空事件
43 | // dragOk();
44 | }
45 | }
46 | }).mouseup(function (e) {
47 | if (isvalid) {
48 | return false;
49 | }
50 | isMove = false;
51 | var _x = e.pageX - x;
52 | console.log(_x);
53 | $.ajax({
54 | type: "POST",
55 | url: "captcha/checkCaptcha",
56 | dataType: "JSON",
57 | async: false,
58 | data: {point: _x},
59 | success: function (result) {
60 | console.log(result);
61 | if (result.code == 200) {
62 | dragOk(_x);
63 | } else {
64 | dragErr();
65 | }
66 | },
67 | error: function (err) {
68 | console.log(err);
69 | $.ligerDialog.error('服务异常!');
70 | }
71 | });
72 | });
73 |
74 | var errcount = 0;
75 |
76 | function dragErr() {
77 | errcount = errcount + 1;
78 | isvalid = false;
79 | $(".drag_bg").css("background-color", "#C22A0E");
80 | text.text("验证失败");
81 | handler.removeClass('handler_bg').addClass('handler_err_bg');
82 | setTimeout(function () {
83 | //还原
84 | text.text("拖动图片验证登陆");
85 | $(".drag_bg").css("background-color", "#7ac23c");
86 | handler.removeClass("handler_err_bg").addClass("handler_bg");
87 | // $xy_img.css("display", "none");
88 | $xy_img.css({'left': 0});
89 | handler.css({'left': 0});
90 | drag_bg.css({'width': 0});
91 | //验证失败, 拖动错误数大于3次,那么就重置
92 | if (errcount >= 3) {
93 | errfun();
94 | errcount = 0;
95 | $xy_img.css("display", "none");
96 | $gt_cut.show();
97 | $gt_cut_hidden.css("display", "none");
98 | }
99 | }, 1000);
100 | }
101 |
102 | //清空事件
103 | function dragOk(_x) {
104 | isvalid = true;
105 | handler.removeClass('handler_bg').addClass('handler_ok_bg');
106 | text.text('验证通过');
107 | drag.css({'color': '#fff'});
108 | handler.unbind('mousedown');
109 | $(document).unbind('mousemove');
110 | $(document).unbind('mouseup');
111 | //验证通过,隐藏刷新
112 | $("#refreshyzm").css("display", "none");
113 | $("#passcheck").val("1");
114 | $("#yzm").val(_x);
115 |
116 | //显示原始图片,并做光线闪过
117 | $xy_img.css("display", "none");
118 | $gt_cut_hidden.css("display", "none");
119 | $gt_cut.show();
120 | $gt_cut.addClass("run");
121 | }
122 | };
123 | })(jQuery);
124 |
125 |
126 |
--------------------------------------------------------------------------------
/src/main/webapp/jsp/login.jsp:
--------------------------------------------------------------------------------
1 | <%@ page contentType="text/html;charset=UTF-8" language="java" %>
2 |
3 |
4 | 登1录
5 |
6 |
7 |
8 |
47 |
48 |
49 |
84 |
85 |
128 |
129 |
130 |
--------------------------------------------------------------------------------
/src/test/java/com/example/demo/DemoApplicationTests.java:
--------------------------------------------------------------------------------
1 | package com.example.demo;
2 |
3 | import org.junit.Test;
4 | import org.junit.runner.RunWith;
5 | import org.springframework.boot.test.context.SpringBootTest;
6 | import org.springframework.test.context.junit4.SpringRunner;
7 |
8 | @RunWith(SpringRunner.class)
9 | @SpringBootTest
10 | public class DemoApplicationTests {
11 |
12 | @Test
13 | public void contextLoads() {
14 | }
15 |
16 | }
17 |
--------------------------------------------------------------------------------