短码生成与还原方案:JS 实现数字与短字符串可逆转换 36 进制编码
在短链接生成、订单号脱敏、资源标识压缩等业务场景中,常需将冗长数字转为简短字符串(短码),且必须保证转换可逆,以便从短码回溯原始数字。本文将分享一套基于 36 进制编码的 JavaScript 实现方案,封装转换与还原两大核心方法,兼顾实用性、健壮性与边界处理,可直接落地到短链接生成等实际业务中。

核心思路:基于 36 进制的编码逻辑
数字与短字符串的可逆转换,本质是进制之间的相互转换。选用 36 进制作为中间载体,原因在于其字符集由 0-9、a-z 组成,共 36 个字符,无需特殊符号,兼容性强(可直接用于 URL、数据库存储等场景),同时能有效缩短数字长度 - 相比 10 进制,36 进制表示同一数字的字符串长度可大幅减少(例如 123456789 的 10 进制表示为 9 位,36 进制仅为 6 位 21i3v9 )。 编码过程(数字→短字符串):将 10 进制数字通过 取余→记录字符→整数除法 循环处理,最终反转结果得到 36 进制字符串;解码过程(短字符串→数字):遍历字符串,按 36 进制权重累加计算,还原为 10 进制数字,确保转换可逆。
完整实现代码
以下是封装好的两个核心方法,包含严格的输入校验、边界处理,可直接集成到项目中使用。
/**
* 将 10 进制数字转换为 0-9、a-z 组成的短字符串(36 进制编码)
* @param {number} num - 非负整数(原始数字)
* @returns {string} 36 进制短字符串
* @throws {Error} 输入非非负整数时抛出异常
*/
function numToShortStr(num) {
// 校验输入类型和范围
if (typeof num !== 'number' || !Number.isInteger(num) || num < 0) {
throw new Error('仅支持非负整数转换');
}
// 定义 36 进制字符集(0-9 + a-z)
const chars = '0123456789abcdefghijklmnopqrstuvwxyz';
const base = chars.length;
// 特殊处理数字 0
if (num === 0) {
return '0';
}
let result = [];
while (num > 0) {
// 取余数,对应字符集的索引
const remainder = num % base;
result.push(chars[remainder]);
// 更新数字为商(整数除法)
num = Math.floor(num / base);
}
// 余数是逆序的,反转后拼接成字符串
return result.reverse().join('');
}
/**
* 将 36 进制短字符串还原为 10 进制数字(解码)
* @param {string} str - 0-9、a-z 组成的字符串
* @returns {number} 原始 10 进制数字
* @throws {Error} 输入非法字符/非字符串时抛出异常
*/
function shortStrToNum(str) {
// 校验输入类型
if (typeof str !== 'string') {
throw new Error('输入必须是字符串类型');
}
const chars = '0123456789abcdefghijklmnopqrstuvwxyz';
const base = chars.length;
// 统一转为小写,兼容大写输入
const lowerStr = str.toLowerCase();
// 校验字符串是否包含非法字符
for (const char of lowerStr) {
if (!chars.includes(char)) {
throw new Error(`包含非法字符:${char},仅支持 0-9、a-z`);
}
}
let num = 0;
for (const char of lowerStr) {
// 字符转对应数值
const val = chars.indexOf(char);
num = num * base + val;
}
return num;
}代码细节解析
1. 输入校验机制
健壮的输入校验是避免转换异常的关键,两个方法分别针对输入做了严格限制: 编码方法 numToShortStr 仅接受非负整数,过滤小数、负数、非数字类型,防止因输入不规范导致的逻辑错误;解码方法 shortStrToNum 首先校验输入为字符串类型,再通过遍历判断是否包含 0-9、a-z 以外的非法字符,同时自动将输入转为小写,兼容大写字符串输入场景,提升方法的灵活性。
2. 边界场景处理
针对数字 0 做了特殊处理 - 若不单独处理,0 会因循环条件不满足导致返回空字符串,不符合实际使用预期。通过直接返回 0 ,确保边界值转换的正确性。
3. 可逆性保障
编码时通过取余记录字符,最终反转结果是因为取余得到的字符顺序与 36 进制字符串顺序相反;解码时按字符串顺序遍历,每一位字符对应的数值乘以 36 的对应权重(从左到右权重依次递增),累加后即可准确还原原始数字,全程无信息丢失,保证转换可逆。
实战测试示例
为验证方法的正确性,可通过以下测试代码验证编码、解码全流程,覆盖普通数字、边界值 0 等场景:
(function test() {
const originalNum = 123456789; // 原始数字
try {
// 编码
const shortStr = numToShortStr(originalNum);
console.log('原始数字:', originalNum); // 输出:123456789
console.log('转换后的短字符串:', shortStr); // 输出:21i3v9
// 解码还原
const restoredNum = shortStrToNum(shortStr);
console.log('还原后的数字:', restoredNum); // 输出:123456789
console.log('是否可逆:', originalNum === restoredNum); // 输出:true
// 测试边界值 0
console.log('0 转换结果:', numToShortStr(0)); // 输出:0
console.log('0 还原结果:', shortStrToNum('0')); // 输出:0
} catch (e) {
console.error('转换失败:', e.message);
}
})();运行测试代码后,控制台将输出编码结果、还原结果及可逆性验证,可直观确认方法的正确性。
扩展方案:62 进制进一步缩短长度
若需要更短的字符串长度,可将编码方案改为 62 进制(字符集包含 0-9、a-z、A-Z),仅需修改字符集和进制基数即可,核心逻辑保持不变。调整后的字符集与基数如下:
// 62 进制字符集
const chars = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
const base = 62; // 基数改为 6262 进制相比 36 进制,字符数量更多,相同数字转换后的字符串长度更短,适合对字符串长度有极致要求的场景(如短链接生成)。需注意的是,62 进制区分大小写,若需兼容不区分大小写的场景,建议仍使用 36 进制方案。
注意事项
JavaScript 中的 Number 类型能安全表示的整数范围为 2^53 - 1 (即 9007199254740991),超出该范围的整数转换可能会丢失精度。若需处理超大整数,可将方法中的 Number 类型改为 BigInt,适配更大范围的数字转换。
总结
本文分享的 36 进制编码方案,通过简洁的 JavaScript 代码实现了数字与短字符串的可逆转换,兼顾了兼容性、健壮性和实用性。核心逻辑清晰易懂,可直接集成到各类项目中,满足 URL 参数简化、标识压缩等开发需求。同时,通过调整字符集可扩展为 62 进制方案,灵活适配不同的长度要求,是开发中处理数字与短字符串转换的优质方案。




