1
+ package io.github.talelin.latticy.common.util;
2
+
3
+ import com.fasterxml.jackson.core.JsonProcessingException;
4
+ import com.fasterxml.jackson.databind.ObjectMapper;
5
+ import io.github.talelin.latticy.bo.LoginCaptchaBO;
6
+ import org.springframework.core.io.ClassPathResource;
7
+
8
+ import javax.crypto.Cipher;
9
+ import javax.crypto.SecretKey;
10
+ import javax.crypto.spec.IvParameterSpec;
11
+ import javax.crypto.spec.SecretKeySpec;
12
+ import javax.imageio.ImageIO;
13
+ import java.awt.*;
14
+ import java.awt.image.BufferedImage;
15
+ import java.io.ByteArrayOutputStream;
16
+ import java.io.IOException;
17
+ import java.nio.charset.StandardCharsets;
18
+ import java.security.GeneralSecurityException;
19
+ import java.time.LocalDateTime;
20
+ import java.time.ZoneId;
21
+ import java.util.Base64;
22
+ import java.util.Random;
23
+
24
+ /**
25
+ * @author Gadfly
26
+ */
27
+ @SuppressWarnings("SpellCheckingInspection")
28
+ public class CaptchaUtil {
29
+
30
+ /**
31
+ * 验证码字符个数
32
+ */
33
+ public static final int RANDOM_STR_NUM = 4;
34
+ private static final Random RANDOM = new Random();
35
+ /**
36
+ * 验证码的宽
37
+ */
38
+ private static final int WIDTH = 80;
39
+ /**
40
+ * 验证码的高
41
+ */
42
+ private static final int HEIGHT = 40;
43
+ /**
44
+ * 验证码中夹杂的干扰线数量
45
+ */
46
+ private static final int LINE_SIZE = 30;
47
+ private static final String RANDOM_STRING = "23456789abcdefghijkmnpqrstuvwxyzABCDEFGHJKLMNPQRSTUVWSYZ";
48
+ private static final String AES = "AES";
49
+ private static final String CIPHER_ALGORITHM = "AES/CBC/PKCS5Padding";
50
+ private static final ObjectMapper MAPPER = new ObjectMapper();
51
+
52
+ static {
53
+ java.security.Security.setProperty("crypto.policy", "unlimited");
54
+ }
55
+
56
+ /**
57
+ * 颜色的设置
58
+ */
59
+ private static Color getRandomColor(int fc, int bc) {
60
+
61
+ fc = Math.min(fc, 255);
62
+ bc = Math.min(bc, 255);
63
+
64
+ int r = fc + RANDOM.nextInt(bc - fc - 16);
65
+ int g = fc + RANDOM.nextInt(bc - fc - 14);
66
+ int b = fc + RANDOM.nextInt(bc - fc - 12);
67
+
68
+ return new Color(r, g, b);
69
+ }
70
+
71
+ /**
72
+ * 字体的设置
73
+ */
74
+ private static Font getFont() throws IOException, FontFormatException {
75
+ ClassPathResource dejavuSerifBold = new ClassPathResource("DejaVuSerif-Bold.ttf");
76
+ return Font.createFont(Font.TRUETYPE_FONT, dejavuSerifBold.getInputStream()).deriveFont(Font.BOLD, 24);
77
+ }
78
+
79
+ /**
80
+ * 随机字符的获取
81
+ */
82
+ public static String getRandomString(int num) {
83
+ num = num > 0 ? num : RANDOM_STRING.length();
84
+ StringBuilder sb = new StringBuilder();
85
+ for (int i = 0; i < num; i++) {
86
+ int number = RANDOM.nextInt(RANDOM_STRING.length());
87
+ sb.append(RANDOM_STRING.charAt(number));
88
+ }
89
+ return sb.toString();
90
+ }
91
+
92
+ /**
93
+ * 干扰线的绘制
94
+ */
95
+ private static void drawLine(Graphics2D g) {
96
+ int x = RANDOM.nextInt(WIDTH);
97
+ int y = RANDOM.nextInt(HEIGHT);
98
+ int xl = WIDTH;
99
+ int yl = HEIGHT;
100
+ g.setStroke(new BasicStroke(2.0f));
101
+ g.setColor(getRandomColor(98, 200));
102
+ g.drawLine(x, y, x + xl, y + yl);
103
+ }
104
+
105
+ /**
106
+ * 字符串的绘制
107
+ */
108
+ private static void drawString(Graphics2D g, String randomStr, int i) throws IOException, FontFormatException {
109
+ g.setFont(getFont());
110
+ g.setColor(getRandomColor(28, 130));
111
+ // 设置每个字符的随机旋转
112
+ double radianPercent = (RANDOM.nextBoolean() ? -1 : 1) * Math.PI * (RANDOM.nextInt(60) / 320D);
113
+ g.rotate(radianPercent, WIDTH * 0.8 / RANDOM_STR_NUM * i, HEIGHT / 2);
114
+ int y = (RANDOM.nextBoolean() ? -1 : 1) * RANDOM.nextInt(4) + 4;
115
+ g.translate(RANDOM.nextInt(3), y);
116
+ g.drawString(randomStr, WIDTH / RANDOM_STR_NUM * i, HEIGHT / 2);
117
+ g.rotate(-radianPercent, WIDTH * 0.8 / RANDOM_STR_NUM * i, HEIGHT / 2);
118
+ g.translate(0, -y);
119
+ }
120
+
121
+ private static BufferedImage getBufferedImage(String code) throws IOException, FontFormatException {
122
+ // BufferedImage类是具有缓冲区的Image类,Image类是用于描述图像信息的类
123
+ BufferedImage image = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_BGR);
124
+ Graphics2D g = (Graphics2D) image.getGraphics();
125
+ g.fillRect(0, 0, WIDTH, HEIGHT);
126
+ g.setColor(getRandomColor(105, 189));
127
+ g.setFont(getFont());
128
+ int lineSize = RANDOM.nextInt(5);
129
+ // 干扰线
130
+ for (int i = 0; i < lineSize; i++) {
131
+ drawLine(g);
132
+ }
133
+ // 随机字符
134
+ for (int i = 0; i < code.length(); i++) {
135
+ drawString(g, String.valueOf(code.charAt(i)), i);
136
+ }
137
+ g.dispose();
138
+ return image;
139
+ }
140
+
141
+ /**
142
+ * 生成随机图片的base64编码字符串
143
+ *
144
+ * @param code 验证码
145
+ * @return base64
146
+ */
147
+ public static String getRandomCodeBase64(String code) throws IOException, FontFormatException {
148
+ BufferedImage image = getBufferedImage(code);
149
+ // 返回 base64
150
+ ByteArrayOutputStream bos = new ByteArrayOutputStream();
151
+ ImageIO.write(image, "PNG", bos);
152
+
153
+ byte[] bytes = bos.toByteArray();
154
+ Base64.Encoder encoder = Base64.getEncoder();
155
+
156
+ return encoder.encodeToString(bytes);
157
+ }
158
+
159
+ public static String getTag(String captcha, String secret, String iv) throws JsonProcessingException, GeneralSecurityException {
160
+ LocalDateTime time = LocalDateTime.now().plusMinutes(5);
161
+ LoginCaptchaBO captchaBO = new LoginCaptchaBO(captcha, time.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli());
162
+ String json = MAPPER.writeValueAsString(captchaBO);
163
+ return aesEncode(secret, iv, json);
164
+ }
165
+
166
+ public static LoginCaptchaBO decodeTag(String secret, String iv, String tag) throws JsonProcessingException, GeneralSecurityException {
167
+ String decrypted = aesDecode(secret, iv, tag);
168
+ return MAPPER.readValue(decrypted, LoginCaptchaBO.class);
169
+ }
170
+
171
+ /**
172
+ * AES加密
173
+ */
174
+ public static String aesEncode(String secret, String iv, String content) throws GeneralSecurityException {
175
+ SecretKey secretKey = new SecretKeySpec(secret.getBytes(), AES);
176
+ Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM);
177
+ System.out.println(iv.length() + "///" + iv.getBytes(StandardCharsets.UTF_8).length + "///" + iv.getBytes(StandardCharsets.US_ASCII).length);
178
+ cipher.init(Cipher.ENCRYPT_MODE, secretKey, new IvParameterSpec(iv.getBytes(StandardCharsets.US_ASCII)));
179
+ byte[] byteEncode = content.getBytes(StandardCharsets.UTF_8);
180
+ // 根据密码器的初始化方式加密
181
+ byte[] byteAES = cipher.doFinal(byteEncode);
182
+
183
+ // 将加密后的数据转换为字符串
184
+ return Base64.getEncoder().encodeToString(byteAES);
185
+ }
186
+
187
+ /**
188
+ * AES解密
189
+ */
190
+ public static String aesDecode(String secret, String iv, String content) throws GeneralSecurityException {
191
+ SecretKey secretKey = new SecretKeySpec(secret.getBytes(), AES);
192
+ Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM);
193
+ cipher.init(Cipher.DECRYPT_MODE, secretKey, new IvParameterSpec(iv.getBytes(StandardCharsets.US_ASCII)));
194
+ // 将加密并编码后的内容解码成字节数组
195
+ byte[] byteContent = Base64.getDecoder().decode(content);
196
+ // 解密
197
+ byte[] byteDecode = cipher.doFinal(byteContent);
198
+ return new String(byteDecode, StandardCharsets.UTF_8);
199
+ }
200
+ }
0 commit comments