一、背景
在一些特定场景下需要前端将多个字符串数据用分隔符拼接后传给后端,然后后端通过分隔符拆分字符串然后进行处理。
如果是dubbo调用那没有问题,直接传对象就行,关键是需要通过HTTP的GET方法传入。
但是如何选取这个分隔符非常伤脑筋。
比如可以选择常见的逗号,或者换行符等,在字符串里也很容易出现,肿么办?肿么办?肿么办?
二、方案
2.1 采用ASCII码特定的控制字符
由于回车符、换行符很常见,可见字符更容易重复,我们为啥不选择其他非常见的用户绝对不可能输入的字符呢? http://ascii.911cha.com/
通过查ASCII表,我们找到了RS 记录分隔符、和US单元分隔符,而我们的场景符合是传递『记录』,因此选取RS更适合。
前端使用
代码语言:javascript复制String.fromCharCode(30) 获取记录分隔符,用来分隔多条记录
后端模拟前端的拼接和后端的解析
对应工具类中的内容:
2.2 Base64加密后加非Base64的字符作为分隔符
编码介绍:http://www.ruanyifeng.com/blog/2008/06/base64.html
居然选择分隔符容易重复,如果我们把原始的字符串转成Base64编码,然后采用非Base64编码的字符作为分隔符,这样不管之前的字符串是何种形式,甚至是上述的记录分隔符,一律被编码成了base64形式,不可能出现非base64中的字符,我们就可以放心的将其当做分隔符了。
想到这里我们开始写代码
对应的运行结果
三、 方法对比
第一种方法简单,且不需要转化,几乎不太可能重复,是一个非常不错的方案。
第二种基于base64的加密解密, 需要加密解密,且传输时字节会增长,但是最安全。
三、工具类
我们把这个逻辑封装成工具类,方便使用
代码语言:javascript复制import org.springframework.util.Base64Utils;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.StringJoiner;
import java.util.function.Function;
import java.util.stream.Collectors;
/**
* 记录传递工具类
*
* @author liuwangyangedu@163.com
*/
public class RecordDeliverUtil {
/**
* ASCII的记录分隔符
*/
private static final char RS = 0x1E;
/**
* ASCII的记录分隔符字符串形式
*/
private static final String RS_STRING = String.valueOf(RS);
/**
* BASE64编码记录的分隔符
*/
private static final String BASE64_SEPARATOR = "|";
/**
* BASE64编码记录的分隔符个正则形式
*/
private static final String BASE64_SEPARATOR_REG = "\|";
/**
* 通过记录分隔符拼接数据的构造
*/
public static String contactRecordsWithRS(T data, Function> function) {
if (data == null) {
return null;
}
return String.join(RS_STRING, function.apply(data));
}
/**
* 通过记录分隔符拼接数据的解析
*/
public static String[] getOriginRecordSeparateWithRS(String dataWithRs) {
if (dataWithRs == null) {
return null;
}
return dataWithRs.split(RS_STRING);
}
/**
* 通过base64编码加密每个记录,并且通过BASE64_SEPARATOR_REG拼接
*/
public static String encodeBase64WithSeparator(T data, Function> function) {
if (data == null) {
return null;
}
List records = function.apply(data);
if (CollectionUtils.isEmpty(records)) {
return null;
}
return records.stream().map(record -> Base64Utils.encodeToString(record.getBytes())).collect(Collectors.joining(BASE64_SEPARATOR));
}
/**
* 解析base64编码并且通过BASE64_SEPARATOR_REG分割
*/
public static List decodeBase64WithSeparator(String dataWithBase64WithSeparator) {
if (dataWithBase64WithSeparator == null) {
return null;
}
// 判断是否有分隔符
if (!dataWithBase64WithSeparator.contains(BASE64_SEPARATOR)) {
return Collections.singletonList(dataWithBase64WithSeparator);
}
// 通过分割
String[] resultList = dataWithBase64WithSeparator.split(BASE64_SEPARATOR_REG);
if (ArrayUtils.isEmpty(resultList)) {
return new ArrayList<>(0);
}
// base64解码
return Arrays.stream(resultList).map(s -> new String(Base64Utils.decodeFromString(s))).collect(Collectors.toList());
}
}
四、思考
类似问题的主要思考角度是
- 对字节、字符和字符串的深刻理解
- 尝试问题转化,既然找不到不常用的字符,那就去找用户不可能输入的非可见字符,如果还有可能发生,我们利用base64编码就不可能再重复。
另外感谢“无聊之园”的建议
还需要思考的问题是:如果有安全性需求,可能还得考虑加入其他非对称加密算法。
不过大多数前端直接传给后端用分隔符分隔的场景,没有加密的需要,具体根据自己的场景来。