坑爹的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~

相关推荐
心在飞扬16 小时前
MultiVector 多向量检索
前端·后端
Gopher_HBo16 小时前
Go之基于TCP/IP协议栈的socket通信
后端
HelloReader16 小时前
在 Windows 上配置 Claude Code从安装到解决网络问题
后端
进击的丸子17 小时前
虹软人脸服务器版SDK(Linux/ARM Pro)多线程调用及性能优化
linux·数据库·后端
小王和八蛋17 小时前
DecimalFormat 与 BigDecimal
java·后端
郭钊荣17 小时前
为什么 OpenClaw 能出圈:扒一扒小龙虾的agent系统设计
后端·github
Nyarlathotep011317 小时前
gin02:gin路径中的参数
后端·go
beata17 小时前
Java基础-16:Java内置锁的四种状态及其转换机制详解-从无锁到重量级锁的进化与优化指南
java·后端
Mintopia17 小时前
软件系统中的订单-审核业务架构分析与实践
后端·架构
茶杯梦轩17 小时前
从零起步学习RabbitMQ || 第二章:RabbitMQ 深入理解概念 Producer、Consumer、Exchange、Queue 与企业实战案例
服务器·后端·消息队列