故事背景
我们接口有一个sign签名,用来校验接口调用方是不是合作方,sign的规则是就是post的数据按参数名asc值从小到大排序+key,然后算md5。我们接口文档已经写了java版本的demo,前端那边需要有个js版本的demo,然后我们尝试写了下demo,发现坑很多。
java部分代码:
java
JSONObject json = JSON.parseObject(input);
//TreeMap自动把参数排序
TreeMap<String, String> data = new TreeMap<String, String>();
for (String key : json.keySet()) {
if ("sign".equals(key)) {
//sign不参与签名
continue;
}
data.put(key, json.getString(key));
}
List<String> params = new ArrayList<String>();
// 重组参数
for (String key : data.keySet()) {
String value = String.format("%s=%s", key, data.get(key));
params.add(value);
}
// 组合参数和签名 secret_key
String temp = URLEncoder.encode(StringUtils.join(params, "&").toLowerCase() + "&key=" + secret_key);
String result = encode(temp);
AI辅助编写代码
把java的签名规则转换成js的签名规则,可以使用AI工具直接生成,下面是我用CHATGPT生成的JS代码。
js
const crypto = require('crypto');
// input: 接口传入的JSON字符串
// secret_key: 用于签名的密钥
function calculateSign(input, secret_key) {
// 解析 JSON 字符串 const json = JSON.parse(input);
// 将参数放入一个对象并按键名排序
const data = {}; Object.keys(json) .sort()
// 按字典顺序排序
.forEach((key) => { if (key !== 'sign') { data[key] = json[key]; } });
// 重组参数
const params = []; for (const key in data) { params.push(`${key}=${data[key]}`); }
// 拼接参数和 secret_key
const temp = encodeURIComponent( params.join('&').toLowerCase() + `&key=${secret_key}` );
// 计算 MD5 签名
const hash = crypto.createHash('md5');
hash.update(temp);
const result = hash.digest('hex'); return result; }
// 示例调用
const input = '{"param1":"value1","param2":"value2","sign":"abcd1234"}';
const secret_key = 'my_secret_key';
const sign = calculateSign(input, secret_key);
console.log('签名:', sign);
一眼看过去好像没什么问题,但是测试的时候发现有很多坑...
首先请求参数的格式有可能是个数组,比如"test" :[{"test1":"xxx"}]这种,直接获取json的时候只会获取到object,需要做一下转换
js
if (Array.isArray(json[key])) {
// 如果值是数组,将其转换为 JSON 字符串
data.set(key, JSON.stringify(json[key]));
} else {
// 否则,直接设置值
data.set(key, json[key]);
}
如果有多重嵌套循环,可以进行递归操作
js
function processObject(obj) {
const result = {};
for (const key in obj) {
if (key === "sign") {
continue;
}
const value = obj[key];
if (Array.isArray(value)) {
result[key] = value.map(item => {
if (typeof item === 'object') {
return processObject(item);
}
return item;
});
} else if (typeof value === 'object') {
result[key] = processObject(value);
} else {
result[key] = value;
}
}
return result;
}
urlEncode的坑
解决了数组解析成object的坑,兴高采烈地校验签名,发现签名还是不一样,md5的代码规则是一样的,最后一对比发现urlencode后的字符串是不一样的,这让我回想起两年前跟合作方对接的时候也遇到这个问题,当时他们是用的GO语言,他说他们GO语言号是要转义的,我发了个网址给他说 号不用转义呀。www.jsons.cn/urlencode
他也发了一个地址给我说java特殊处理了。
stackoverflow.com/questions/6...
我一看源码真的是
数字字母,' '空格变+加号,还有"-" , "_", "." , "*"
因为我们是服务端,是公共的接口对接多个合作方,所以要他们兼容我们规则,规则说清楚就行了。
现在又来了一个前端的urlencode...查了一下前端是用了encodeURIComponent这个方法,该方法不会对 ASCII 字母和数字进行编码,也不会对这些 ASCII 标点符号进行编码: - _ . ! ~ * ' ( ) 。
还好看起来规则差不多,最后用一个replace大法把有差异的编码做一个适配。
js
const originalEncodeURIComponent = encodeURIComponent;
encodeURIComponent = function (str) {
const encoded = originalEncodeURIComponent(str);
return encoded.replace(/%20/g, '+') //空格
.replace(/!/g, '%21') //!
.replace(/\(/g, '%28') //(
.replace(/\)/g, '%29')//)
.replace(/~/g, '%7E')//~
.replace(/'/g, '%27');//'
}
总结
不知道为什么,这个urlencode居然没有一个统一的标准,导致JAVA、GO和JS都有不一样的规则,如果要对接urlencode就会有一些编码对不齐,不过用urldecode会获得一样的结果......
使用AI编程的时候一定要多测试,因为他的代码很可能有不少bug~