坑爹的urlencode

故事背景

我们接口有一个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~

相关推荐
优雅的落幕4 小时前
Spring AOP---面向切面编程由认识到使用
java·后端·spring
嘵奇4 小时前
Spring Boot API版本控制实践指南
java·spring boot·后端
╰つ゛木槿4 小时前
全面解析SimHash算法:原理、对比与Spring Boot实践指南
spring boot·后端·算法
南方下小雨4 小时前
基于Spring Boot + Vue 项目中引入deepseek方法
vue.js·spring boot·后端
努力的搬砖人.4 小时前
redis常用集合操作命令
java·redis·后端
运维@小兵5 小时前
SpringBoot使用分组校验解决同一个实体对象在不同场景下需要不同校验规则的问题
java·spring boot·后端
有梦想的攻城狮16 小时前
spring中的@Configuration注解详解
java·后端·spring·configuration·配置类
言之。16 小时前
Go语言中的错误处理
开发语言·后端·golang
优雅的落幕18 小时前
【SpringBoot】基于mybatisPlus的博客管理系统(2)
java·spring boot·后端
robch20 小时前
golang接口和具体实现之间的类型转换
开发语言·后端·golang