Skip to content

Commit 412439e

Browse files
committed
fix: can't comment sporadically
数据库ID进行加密与Base64后,特殊字符(+,/,=)不符合HTML标签id属性值的规范
1 parent 167fd17 commit 412439e

5 files changed

Lines changed: 76 additions & 31 deletions

File tree

blog-consumer/src/main/java/com/hackyle/blog/consumer/controller/ArticleController.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ public ModelAndView articleDetail(ModelAndView modelAndView, HttpServletRequest
104104
modelAndView.addObject("articleVo", articleVo);
105105
modelAndView.addObject("metaVo", metaVo);
106106

107-
List<CommentVo> commentVos = commentService.fetchListByHierarchy(IDUtils.decryptByAES(articleVo.getId()));
107+
List<CommentVo> commentVos = commentService.fetchListByHierarchy(IDUtils.decrypt(articleVo.getId()));
108108
modelAndView.addObject("commentVos", commentVos);
109109

110110
ArticleVo articleVoLog = BeanCopyUtils.copy(articleVo, ArticleVo.class);
@@ -147,7 +147,7 @@ public ModelAndView articleDetail(ModelAndView modelAndView, HttpServletRequest
147147
metaVo.setKeywords(articleVo.getTags());
148148
modelAndView.addObject("metaVo", metaVo);
149149

150-
List<CommentVo> commentVos = commentService.fetchListByHierarchy(IDUtils.decryptByAES(articleVo.getId()));
150+
List<CommentVo> commentVos = commentService.fetchListByHierarchy(IDUtils.decrypt(articleVo.getId()));
151151
modelAndView.addObject("commentVos", commentVos);
152152

153153
ArticleVo articleVoLog = BeanCopyUtils.copy(articleVo, ArticleVo.class);

blog-consumer/src/main/java/com/hackyle/blog/consumer/service/impl/ArticleServiceImpl.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,7 @@ public ArticleVo articleDetail(String uri) {
137137

138138
ArticleVo articleVo = BeanCopyUtils.copy(articleEntity, ArticleVo.class);
139139
//加密ID
140-
articleVo.setId(IDUtils.encryptByAES(articleEntity.getId()));
140+
articleVo.setId(IDUtils.encrypt(articleEntity.getId()));
141141

142142
Long articleId = articleEntity.getId();
143143
List<Long> articleIds = new ArrayList<>();

blog-consumer/src/main/java/com/hackyle/blog/consumer/service/impl/CommentServiceImpl.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -43,14 +43,14 @@ public class CommentServiceImpl extends ServiceImpl<CommentMapper, CommentEntity
4343
@Override
4444
public ApiResponse<String> add(CommentAddDto addDto) {
4545
CommentEntity commentEntity = BeanCopyUtils.copy(addDto, CommentEntity.class);
46-
commentEntity.setTargetId(IDUtils.decryptByAES(addDto.getTargetId()));
46+
commentEntity.setTargetId(IDUtils.decrypt(addDto.getTargetId()));
4747
commentEntity.setId(IDUtils.timestampID());
4848

4949
if(StringUtils.isNotBlank(addDto.getParentId())) {
50-
commentEntity.setParentId(IDUtils.decryptByAES(addDto.getParentId()));
50+
commentEntity.setParentId(IDUtils.decrypt(addDto.getParentId()));
5151

5252
String replyWhoId = addDto.getReplyWhoId();
53-
CommentEntity commentReplyWho = commentMapper.selectById(IDUtils.decryptByAES(replyWhoId));
53+
CommentEntity commentReplyWho = commentMapper.selectById(IDUtils.decrypt(replyWhoId));
5454
commentEntity.setReplyWho(commentReplyWho.getName());
5555
}
5656

@@ -83,7 +83,7 @@ public List<CommentVo> fetchListByHierarchy(long targetId) {
8383
List<CommentVo> resultComments = new ArrayList<>(parentComments.size());
8484
for (CommentEntity parentComment : parentComments) {
8585
CommentVo commentVo = BeanCopyUtils.copy(parentComment, CommentVo.class);
86-
commentVo.setId(IDUtils.encryptByAES(parentComment.getId()));
86+
commentVo.setId(IDUtils.encrypt(parentComment.getId()));
8787

8888
List<CommentEntity> subComments = groupByParentIdMap.get(parentComment.getId());
8989
if(subComments != null && subComments.size() > 0) {

blog-consumer/src/main/java/com/hackyle/blog/consumer/util/AESUtils.java

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ public class AESUtils {
2626
* @param password 密码
2727
* @return 加密后的Base64
2828
*/
29-
public static String encrypt(String plaintext, String password) {
29+
public static String encryptAndToBase64(String plaintext, String password) {
3030
byte[] passwordKeyBytes = passwordKey(password);
3131
byte[] plaintextBytes = plaintext.getBytes(StandardCharsets.UTF_8);
3232
byte[] encryptedBytes = doFinal(Cipher.ENCRYPT_MODE, plaintextBytes, passwordKeyBytes);
@@ -39,13 +39,39 @@ public static String encrypt(String plaintext, String password) {
3939
* @param password 密码
4040
* @return 解密后的字节数组
4141
*/
42-
public static String decrypt(String ciphertext, String password) {
42+
public static String decryptAndToBase64(String ciphertext, String password) {
4343
byte[] ciphertextBytes = Base64.getDecoder().decode(ciphertext);
4444
byte[] passwordKeyBytes = passwordKey(password);
4545
byte[] decryptedBytes = doFinal(Cipher.DECRYPT_MODE, ciphertextBytes, passwordKeyBytes);
4646
return new String(decryptedBytes);
4747
}
4848

49+
/**
50+
* 加密后转为Base64串
51+
* @param plaintext 待加密的数据
52+
* @param password 密码
53+
* @return 加密后的Base64
54+
*/
55+
public static String encryptAndToBase64URL(String plaintext, String password) {
56+
byte[] passwordKeyBytes = passwordKey(password);
57+
byte[] plaintextBytes = plaintext.getBytes(StandardCharsets.UTF_8);
58+
byte[] encryptedBytes = doFinal(Cipher.ENCRYPT_MODE, plaintextBytes, passwordKeyBytes);
59+
return Base64.getUrlEncoder().encodeToString(encryptedBytes);
60+
}
61+
62+
/**
63+
* 解密前解码Base64
64+
* @param ciphertext 待解密的数据
65+
* @param password 密码
66+
* @return 解密后的字节数组
67+
*/
68+
public static String decryptAndToBase64URL(String ciphertext, String password) {
69+
byte[] ciphertextBytes = Base64.getUrlDecoder().decode(ciphertext);
70+
byte[] passwordKeyBytes = passwordKey(password);
71+
byte[] decryptedBytes = doFinal(Cipher.DECRYPT_MODE, ciphertextBytes, passwordKeyBytes);
72+
return new String(decryptedBytes);
73+
}
74+
4975
/**
5076
* 根据password生成AES所需的128、192、256位的密码
5177
* @param password 密码
@@ -96,10 +122,10 @@ public static void main(String[] args) {
96122
String data = "1658814571488";
97123
String password = "kyle";
98124

99-
String encrypt = encrypt(data, password);
125+
String encrypt = encryptAndToBase64(data, password);
100126
System.out.println(encrypt);
101127

102-
String decrypt = decrypt(encrypt, password);
128+
String decrypt = decryptAndToBase64(encrypt, password);
103129
System.out.println(decrypt);
104130
}
105131
}

blog-consumer/src/main/java/com/hackyle/blog/consumer/util/IDUtils.java

Lines changed: 39 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,14 @@
99

1010
/**
1111
* ID混淆工具类
12+
* 主要思路:将ID进行对称加密,再转换为Base64串
13+
*
14+
* Base64的定义:用 64 个可打印字符来表示二进制数据,小写字母 a-z、大写字母 A-Z、数字 0-9、符号"+"、"/"(当原始数据凑不够三个字节时,编码结果中会使用额外的符号'=',实际上是 65 个字符)。
15+
*
1216
* 注意:
1317
* 混淆规则不应该轻易改动
14-
* 由于混淆后的ID可能会用作URL的一部分,需要替换Base64中的不符合URL规则的字符
18+
* 由于混淆后的ID可能会用作URL的一部分,所以直接转换为URL类型的Base64串(或者替换掉常规Base64中的不符合URL规则的字符)
19+
* 由于混淆后的ID可能作为HTML标签的ID属性值(只能包含字母、数字、下划线和连字符'-'),所以需要替换Base64中除了那三种的字符
1520
*/
1621
public class IDUtils {
1722
private static final String DEFAULT_PW = "blog.hackyle.com:default.pw";
@@ -24,7 +29,7 @@ public static List<String> encrypt(List<Long> sourceIdList) {
2429
List<String> encryptedIdList = new ArrayList<>(sourceIdList.size());
2530

2631
for (Long id : sourceIdList) {
27-
encryptedIdList.add(encryptByAES(id));
32+
encryptedIdList.add(encrypt(id));
2833
}
2934

3035
return encryptedIdList;
@@ -34,7 +39,7 @@ public static List<Long> decrypt(List<String> sourceIdList) {
3439
List<Long> decryptIdList = new ArrayList<>(sourceIdList.size());
3540

3641
for (String id : sourceIdList) {
37-
decryptIdList.add(decryptByAES(id));
42+
decryptIdList.add(decrypt(id));
3843
}
3944
return decryptIdList;
4045
}
@@ -74,7 +79,7 @@ public static void encrypt(Object sourceObj, String sourceField, Object targetOb
7479
sourceIdField.setAccessible(false);
7580

7681
//加密
77-
String encryptedId = encryptByAES(Long.parseLong(id.toString()));
82+
String encryptedId = encrypt(Long.parseLong(id.toString()));
7883

7984
Method targetIdField = targetObj.getClass().getMethod(targetMethod, String.class);
8085
targetIdField.invoke(targetObj, encryptedId);
@@ -117,7 +122,7 @@ public static void decrypt(Object sourceObj, String sourceField, Object targetOb
117122
sourceIdField.setAccessible(false);
118123

119124
//解密
120-
long decryptedId = decryptByAES(id.toString());
125+
long decryptedId = decrypt(id.toString());
121126

122127
Method targetIdField = targetObj.getClass().getMethod("setId", Long.class);
123128
targetIdField.invoke(targetObj, decryptedId);
@@ -204,32 +209,46 @@ public static void batchDecrypt(List<?> sourceObjList, String sourceField, List<
204209
}
205210

206211
/**
207-
* 将ID对称加密加密
208-
* Base64的定义:用 64 个可打印字符来表示二进制数据,小写字母 a-z、大写字母 A-Z、数字 0-9、符号"+"、"/"(再加上作为垫字的"=",实际上是 65 个字符)。
212+
* ID混淆:AES加密,转换为Base64串,特殊字符处理
209213
*/
210-
public static String encryptByAES(long id) {
211-
String encrypt = AESUtils.encrypt(String.valueOf(id), DEFAULT_PW);
214+
public static String encrypt(long id) {
215+
String encrypt = AESUtils.encryptAndToBase64(String.valueOf(id), DEFAULT_PW);
212216

213-
//由于返回的是Base64字符串,作为URL时可能存在冲突,所以再进行转换
217+
//混淆后的ID可能作为HTML标签的ID属性值(只能包含字母、数字、下划线和连字符'-'),所以需要替换Base64中除了那三种的字符
214218
encrypt = encrypt.replaceAll("\\+", "-")
215-
.replaceAll("/", ".")
216-
.replaceAll("=","_");
219+
.replaceAll("/", "_");
220+
//替换Base64串后面可能存在的=。有几个=,就将其替换后,在尾巴后添加数字
221+
int count = 0;
222+
for (int i = encrypt.length()-1; i >= 0; i--) {
223+
if('=' != encrypt.charAt(i)) { //=只可能出现在Base64串的最后面
224+
encrypt = encrypt.substring(0, i+1); //移除Base64串最后面的=
225+
break;
226+
}
227+
count++;
228+
}
229+
encrypt += count;
230+
217231
return encrypt;
218232
}
219233

220234
/**
221-
* 将ID对称解密
235+
* ID解混淆:特殊字符恢复处理,Base64转换,AES解密
222236
*/
223-
public static long decryptByAES(String encryptedId) {
237+
public static long decrypt(String encryptedId) {
224238
if(StringUtils.isBlank(encryptedId)) {
225239
throw new RuntimeException("encryptedId为空");
226240
}
227241

228242
encryptedId = encryptedId.replaceAll("-", "+")
229-
.replaceAll("\\.", "/")
230-
.replaceAll("_","=");
243+
.replaceAll("_", "/");
244+
char count = encryptedId.charAt(encryptedId.length()-1);
245+
int cc = Integer.parseInt(count + "");
246+
encryptedId = encryptedId.substring(0, encryptedId.length()-1);
247+
for (int i = 0; i < cc; i++) {
248+
encryptedId += "=";
249+
}
231250

232-
String decryptedId = AESUtils.decrypt(encryptedId, DEFAULT_PW);
251+
String decryptedId = AESUtils.decryptAndToBase64(encryptedId, DEFAULT_PW);
233252
return Long.parseLong(decryptedId);
234253
}
235254

@@ -241,12 +260,12 @@ public static void main(String[] args) throws Exception {
241260
}
242261

243262
private static void testAES() {
244-
long id = timestampID();
263+
long id = 169899L;
245264
System.out.println(id);
246-
String encryptByAES = encryptByAES(id);
265+
String encryptByAES = encrypt(id);
247266
System.out.println(encryptByAES);
248267

249-
long decryptByAES = decryptByAES(encryptByAES);
268+
long decryptByAES = decrypt(encryptByAES);
250269
System.out.println(decryptByAES);
251270
}
252271
}

0 commit comments

Comments
 (0)