From 34d3e4c71568254c5e9a117fe3faa49e57b31575 Mon Sep 17 00:00:00 2001 From: liangzai <2440983361@qq.com> Date: Thu, 12 Feb 2026 13:27:11 +0800 Subject: [PATCH] f --- .eslintrc-auto-import.json | 3 + auto-imports.d.ts | 6 + .../examples/javascript/sms_signature_demo.js | 266 ++++++++++++++++++ public/examples/nodejs/sms_signature_demo.js | 246 ++++++++++++++++ src/stores/user.js | 13 +- src/utils/smsSignature.js | 212 ++++++++++++++ 6 files changed, 741 insertions(+), 5 deletions(-) create mode 100644 public/examples/javascript/sms_signature_demo.js create mode 100644 public/examples/nodejs/sms_signature_demo.js create mode 100644 src/utils/smsSignature.js diff --git a/.eslintrc-auto-import.json b/.eslintrc-auto-import.json index 7b77945..7c358d0 100644 --- a/.eslintrc-auto-import.json +++ b/.eslintrc-auto-import.json @@ -66,6 +66,7 @@ "downloadFile": true, "eagerComputed": true, "effectScope": true, + "encodeRequest": true, "endsWith": true, "escape": true, "extendRef": true, @@ -77,6 +78,8 @@ "formatMoney": true, "formatPhone": true, "fromNow": true, + "generateNonce": true, + "generateSMSRequest": true, "generateUUID": true, "get": true, "getActivePinia": true, diff --git a/auto-imports.d.ts b/auto-imports.d.ts index 6fdca1a..3c83087 100644 --- a/auto-imports.d.ts +++ b/auto-imports.d.ts @@ -63,6 +63,7 @@ declare global { const downloadFile: typeof import('./src/utils/index.js')['downloadFile'] const eagerComputed: typeof import('@vueuse/core')['eagerComputed'] const effectScope: typeof import('vue')['effectScope'] + const encodeRequest: typeof import('./src/utils/smsSignature.js')['encodeRequest'] const endsWith: typeof import('lodash-es')['endsWith'] const errorMonitor: typeof import('./src/utils/errorMonitor.js')['default'] const escape: typeof import('lodash-es')['escape'] @@ -80,6 +81,8 @@ declare global { const formatPhone: typeof import('./src/utils/index.js')['formatPhone'] const fromNow: typeof import('./src/utils/index.js')['fromNow'] const generateFilename: typeof import('./src/utils/export.js')['generateFilename'] + const generateNonce: typeof import('./src/utils/smsSignature.js')['generateNonce'] + const generateSMSRequest: typeof import('./src/utils/smsSignature.js')['generateSMSRequest'] const generateUUID: typeof import('./src/utils/index.js')['generateUUID'] const get: typeof import('lodash-es')['get'] const getActivePinia: typeof import('pinia')['getActivePinia'] @@ -512,6 +515,7 @@ declare module 'vue' { readonly downloadFile: UnwrapRef readonly eagerComputed: UnwrapRef readonly effectScope: UnwrapRef + readonly encodeRequest: UnwrapRef readonly endsWith: UnwrapRef readonly escape: UnwrapRef readonly extendRef: UnwrapRef @@ -523,6 +527,8 @@ declare module 'vue' { readonly formatMoney: UnwrapRef readonly formatPhone: UnwrapRef readonly fromNow: UnwrapRef + readonly generateNonce: UnwrapRef + readonly generateSMSRequest: UnwrapRef readonly generateUUID: UnwrapRef readonly get: UnwrapRef readonly getActivePinia: UnwrapRef diff --git a/public/examples/javascript/sms_signature_demo.js b/public/examples/javascript/sms_signature_demo.js new file mode 100644 index 0000000..fd3c707 --- /dev/null +++ b/public/examples/javascript/sms_signature_demo.js @@ -0,0 +1,266 @@ +/** + * 短信发送接口签名示例(浏览器版本) + * + * 本示例演示如何在浏览器中为短信发送接口生成HMAC-SHA256签名 + * + * 安全提示: + * 1. 密钥应该通过代码混淆、字符串拆分等方式隐藏 + * 2. 不要在前端代码中直接暴露完整密钥 + * 3. 建议使用构建工具进行代码混淆和压缩 + * 4. 可以考虑将签名逻辑放在后端代理接口中 + */ + +/** + * 获取签名密钥(通过多种方式混淆,增加破解难度) + * 注意:这只是示例,实际使用时应该进一步混淆 + */ +function getSecretKey() { + // 方式1: 字符串拆分和拼接 + const part1 = 'TyApi2024'; + const part2 = 'SMSSecret'; + const part3 = 'Key!@#$%^'; + const part4 = '&*()_+QWERTY'; + const part5 = 'UIOP'; + + // 方式2: 使用数组和join(增加混淆) + const arr = [part1, part2, part3, part4, part5]; + return arr.join(''); + + // 方式3: 字符数组拼接(更复杂的方式) + // const chars = ['T', 'y', 'A', 'p', 'i', '2', '0', '2', '4', ...]; + // return chars.join(''); + + // 方式4: 使用atob解码(如果密钥经过base64编码) + // const encoded = 'base64_encoded_string'; + // return atob(encoded); +} + +/** + * 生成随机字符串(用于nonce) + */ +function generateNonce(length = 16) { + const chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; + let result = ''; + const array = new Uint8Array(length); + crypto.getRandomValues(array); + for (let i = 0; i < length; i++) { + result += chars[array[i] % chars.length]; + } + return result; +} + +/** + * 使用Web Crypto API生成HMAC-SHA256签名 + * + * @param {Object} params - 请求参数对象 + * @param {string} secretKey - 签名密钥 + * @param {number} timestamp - 时间戳(秒) + * @param {string} nonce - 随机字符串 + * @returns {Promise} 签名字符串(hex编码) + */ +async function generateSignature(params, secretKey, timestamp, nonce) { + // 1. 构建待签名字符串:按key排序,拼接成 key1=value1&key2=value2 格式 + const keys = Object.keys(params) + .filter(k => k !== 'signature') // 排除签名字段 + .sort(); + + const parts = keys.map(k => `${k}=${params[k]}`); + + // 2. 添加时间戳和随机数 + parts.push(`timestamp=${timestamp}`); + parts.push(`nonce=${nonce}`); + + // 3. 拼接成待签名字符串 + const signString = parts.join('&'); + + // 4. 使用Web Crypto API计算HMAC-SHA256签名 + const encoder = new TextEncoder(); + const keyData = encoder.encode(secretKey); + const messageData = encoder.encode(signString); + + // 导入密钥 + const cryptoKey = await crypto.subtle.importKey( + 'raw', + keyData, + { name: 'HMAC', hash: 'SHA-256' }, + false, + ['sign'] + ); + + // 计算签名 + const signature = await crypto.subtle.sign('HMAC', cryptoKey, messageData); + + // 转换为hex字符串 + const hashArray = Array.from(new Uint8Array(signature)); + const hashHex = hashArray.map(b => b.toString(16).padStart(2, '0')).join(''); + + return hashHex; +} + +/** + * 自定义编码字符集(与后端保持一致) + */ +const CUSTOM_ENCODE_CHARSET = "0123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghjkmnpqrstuvwxyz!@#$%^&*()_+-=[]{}|;:,.<>?"; + +/** + * 自定义Base64编码(使用自定义字符集) + */ +function customBase64Encode(data) { + if (data.length === 0) return ''; + + // 将字符串转换为UTF-8字节数组 + const encoder = new TextEncoder(); + const bytes = encoder.encode(data); + const charset = CUSTOM_ENCODE_CHARSET; + let result = ''; + + // 将3个字节(24位)编码为4个字符 + for (let i = 0; i < bytes.length; i += 3) { + const b1 = bytes[i]; + const b2 = i + 1 < bytes.length ? bytes[i + 1] : 0; + const b3 = i + 2 < bytes.length ? bytes[i + 2] : 0; + + // 组合成24位 + const combined = (b1 << 16) | (b2 << 8) | b3; + + // 分成4个6位段 + result += charset[(combined >> 18) & 0x3F]; + result += charset[(combined >> 12) & 0x3F]; + + if (i + 1 < bytes.length) { + result += charset[(combined >> 6) & 0x3F]; + } else { + result += '='; // 填充字符 + } + + if (i + 2 < bytes.length) { + result += charset[combined & 0x3F]; + } else { + result += '='; // 填充字符 + } + } + + return result; +} + +/** + * 应用字符偏移混淆 + */ +function applyCharShift(data, shift) { + const charset = CUSTOM_ENCODE_CHARSET; + const charsetLen = charset.length; + let result = ''; + + for (let i = 0; i < data.length; i++) { + const c = data[i]; + if (c === '=') { + result += c; // 填充字符不变 + continue; + } + + const idx = charset.indexOf(c); + if (idx === -1) { + result += c; // 不在字符集中,保持不变 + } else { + // 应用偏移 + const newIdx = (idx + shift) % charsetLen; + result += charset[newIdx]; + } + } + + return result; +} + +/** + * 自定义编码请求数据 + */ +function encodeRequest(data) { + // 1. 使用自定义Base64编码 + const encoded = customBase64Encode(data); + + // 2. 应用字符偏移混淆(偏移7个位置) + const confused = applyCharShift(encoded, 7); + + return confused; +} + +/** + * 发送短信验证码(带签名)- 方式2:编码后传输(推荐,更安全) + * 将所有参数(包括签名)使用自定义编码方案编码后传输,隐藏参数结构 + * + * @param {string} phone - 手机号 + * @param {string} scene - 场景(register/login/change_password/reset_password等) + * @param {string} apiBaseUrl - API基础URL + * @returns {Promise} 响应结果 + */ +async function sendSMSWithEncodedSignature(phone, scene, apiBaseUrl = 'http://localhost:8080') { + // 1. 准备参数 + const timestamp = Math.floor(Date.now() / 1000); // 当前时间戳(秒) + const nonce = generateNonce(16); // 生成随机字符串 + + const params = { + phone: phone, + scene: scene, + }; + + // 2. 生成签名 + const secretKey = getSecretKey(); + const signature = await generateSignature(params, secretKey, timestamp, nonce); + + // 3. 构建包含所有参数的JSON对象 + const allParams = { + phone: phone, + scene: scene, + timestamp: timestamp, + nonce: nonce, + signature: signature, + }; + + // 4. 将JSON对象转换为字符串,然后使用自定义编码方案编码 + const jsonString = JSON.stringify(allParams); + const encodedData = encodeRequest(jsonString); + + // 5. 构建请求体(只包含编码后的data字段) + const requestBody = { + data: encodedData, + }; + + // 6. 发送请求 + try { + const response = await fetch(`${apiBaseUrl}/api/v1/users/send-code`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(requestBody), + }); + + const result = await response.json(); + return result; + } catch (error) { + console.error('发送短信失败:', error); + throw error; + } +} + +// 如果在浏览器环境中使用,可以导出到全局 +if (typeof window !== 'undefined') { + window.SMSSignature = { + sendSMSWithEncodedSignature, + generateSignature, + generateNonce, + encodeRequest, + }; +} + +// 如果在Node.js环境中使用(需要安装crypto-js等库) +if (typeof module !== 'undefined' && module.exports) { + module.exports = { + sendSMSWithEncodedSignature, + generateSignature, + generateNonce, + getSecretKey, + encodeRequest, + }; +} + diff --git a/public/examples/nodejs/sms_signature_demo.js b/public/examples/nodejs/sms_signature_demo.js new file mode 100644 index 0000000..4b58476 --- /dev/null +++ b/public/examples/nodejs/sms_signature_demo.js @@ -0,0 +1,246 @@ +/** + * 短信发送接口签名示例 + * + * 本示例演示如何为短信发送接口生成HMAC-SHA256签名 + * + * 安全提示: + * 1. 密钥应该通过代码混淆、字符串拆分等方式隐藏 + * 2. 不要在前端代码中直接暴露完整密钥 + * 3. 建议使用构建工具进行代码混淆 + */ + +const crypto = require('crypto'); + +/** + * 获取签名密钥(通过多种方式混淆,增加破解难度) + * 注意:这只是示例,实际使用时应该进一步混淆 + */ +function getSecretKey() { + // 方式1: 字符串拆分和拼接 + const part1 = 'TyApi2024'; + const part2 = 'SMSSecret'; + const part3 = 'Key!@#$%^'; + const part4 = '&*()_+QWERTY'; + const part5 = 'UIOP'; + + // 方式2: Base64解码(可选,增加一层混淆) + // const encoded = Buffer.from('some_base64_string', 'base64').toString(); + + // 方式3: 字符数组拼接 + const chars = ['T', 'y', 'A', 'p', 'i', '2', '0', '2', '4', 'S', 'M', 'S', 'S', 'e', 'c', 'r', 'e', 't', 'K', 'e', 'y', '!', '@', '#', '$', '%', '^', '&', '*', '(', ')', '_', '+', 'Q', 'W', 'E', 'R', 'T', 'Y', 'U', 'I', 'O', 'P']; + const fromChars = chars.join(''); + + // 组合多种方式(实际密钥:TyApi2024SMSSecretKey!@#$%^&*()_+QWERTYUIOP) + return part1 + part2 + part3 + part4 + part5; +} + +/** + * 生成随机字符串(用于nonce) + */ +function generateNonce(length = 16) { + const chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; + let result = ''; + for (let i = 0; i < length; i++) { + result += chars.charAt(Math.floor(Math.random() * chars.length)); + } + return result; +} + +/** + * 生成HMAC-SHA256签名 + * + * @param {Object} params - 请求参数对象 + * @param {string} secretKey - 签名密钥 + * @param {number} timestamp - 时间戳(秒) + * @param {string} nonce - 随机字符串 + * @returns {string} 签名字符串(hex编码) + */ +function generateSignature(params, secretKey, timestamp, nonce) { + // 1. 构建待签名字符串:按key排序,拼接成 key1=value1&key2=value2 格式 + const keys = Object.keys(params) + .filter(k => k !== 'signature') // 排除签名字段 + .sort(); + + const parts = keys.map(k => `${k}=${params[k]}`); + + // 2. 添加时间戳和随机数 + parts.push(`timestamp=${timestamp}`); + parts.push(`nonce=${nonce}`); + + // 3. 拼接成待签名字符串 + const signString = parts.join('&'); + + // 4. 使用HMAC-SHA256计算签名 + const signature = crypto + .createHmac('sha256', secretKey) + .update(signString) + .digest('hex'); + + return signature; +} + +/** + * 自定义编码字符集(与后端保持一致) + */ +const CUSTOM_ENCODE_CHARSET = "0123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghjkmnpqrstuvwxyz!@#$%^&*()_+-=[]{}|;:,.<>?"; + +/** + * 自定义Base64编码(使用自定义字符集) + */ +function customBase64Encode(data) { + if (data.length === 0) return ''; + + const bytes = Buffer.from(data, 'utf8'); + const charset = CUSTOM_ENCODE_CHARSET; + let result = ''; + + // 将3个字节(24位)编码为4个字符 + for (let i = 0; i < bytes.length; i += 3) { + const b1 = bytes[i]; + const b2 = i + 1 < bytes.length ? bytes[i + 1] : 0; + const b3 = i + 2 < bytes.length ? bytes[i + 2] : 0; + + // 组合成24位 + const combined = (b1 << 16) | (b2 << 8) | b3; + + // 分成4个6位段 + result += charset[(combined >> 18) & 0x3F]; + result += charset[(combined >> 12) & 0x3F]; + + if (i + 1 < bytes.length) { + result += charset[(combined >> 6) & 0x3F]; + } else { + result += '='; // 填充字符 + } + + if (i + 2 < bytes.length) { + result += charset[combined & 0x3F]; + } else { + result += '='; // 填充字符 + } + } + + return result; +} + +/** + * 应用字符偏移混淆 + */ +function applyCharShift(data, shift) { + const charset = CUSTOM_ENCODE_CHARSET; + const charsetLen = charset.length; + let result = ''; + + for (let i = 0; i < data.length; i++) { + const c = data[i]; + if (c === '=') { + result += c; // 填充字符不变 + continue; + } + + const idx = charset.indexOf(c); + if (idx === -1) { + result += c; // 不在字符集中,保持不变 + } else { + // 应用偏移 + const newIdx = (idx + shift) % charsetLen; + result += charset[newIdx]; + } + } + + return result; +} + +/** + * 自定义编码请求数据 + */ +function encodeRequest(data) { + // 1. 使用自定义Base64编码 + const encoded = customBase64Encode(data); + + // 2. 应用字符偏移混淆(偏移7个位置) + const confused = applyCharShift(encoded, 7); + + return confused; +} + +/** + * 发送短信验证码(带签名)- 方式2:编码后传输(推荐,更安全) + * 将所有参数(包括签名)使用自定义编码方案编码后传输,隐藏参数结构 + * + * @param {string} phone - 手机号 + * @param {string} scene - 场景(register/login/change_password/reset_password等) + * @param {string} apiBaseUrl - API基础URL + * @returns {Promise} 响应结果 + */ +async function sendSMSWithEncodedSignature(phone, scene, apiBaseUrl = 'http://localhost:8080') { + // 1. 准备参数 + const timestamp = Math.floor(Date.now() / 1000); // 当前时间戳(秒) + const nonce = generateNonce(16); // 生成随机字符串 + + const params = { + phone: phone, + scene: scene, + }; + + // 2. 生成签名 + const secretKey = getSecretKey(); + const signature = generateSignature(params, secretKey, timestamp, nonce); + + // 3. 构建包含所有参数的JSON对象 + const allParams = { + phone: phone, + scene: scene, + timestamp: timestamp, + nonce: nonce, + signature: signature, + }; + + // 4. 将JSON对象转换为字符串,然后使用自定义编码方案编码 + const jsonString = JSON.stringify(allParams); + const encodedData = encodeRequest(jsonString); + + // 5. 构建请求体(只包含编码后的data字段) + const requestBody = { + data: encodedData, + }; + + // 6. 发送请求 + try { + const response = await fetch(`${apiBaseUrl}/api/v1/users/send-code`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(requestBody), + }); + + const result = await response.json(); + return result; + } catch (error) { + console.error('发送短信失败:', error); + throw error; + } +} + +// 使用示例 +if (require.main === module) { + console.log('=== 发送短信验证码(使用自定义编码) ==='); + // 示例:发送注册验证码(使用自定义编码方案,只传递data字段) + sendSMSWithEncodedSignature('13800138000', 'register', 'http://localhost:8080') + .then(result => { + console.log('发送成功:', result); + }) + .catch(error => { + console.error('发送失败:', error); + }); +} + +module.exports = { + sendSMSWithEncodedSignature, + generateSignature, + generateNonce, + getSecretKey, + encodeRequest, +}; + diff --git a/src/stores/user.js b/src/stores/user.js index 6ebe1b4..d65770a 100644 --- a/src/stores/user.js +++ b/src/stores/user.js @@ -94,6 +94,7 @@ import { userApi } from '@/api' import router from '@/router' import { authEventBus } from '@/utils/request' +import { generateSMSRequest } from '@/utils/smsSignature' import { clearLocalVersions, saveLocalVersions, VERSION_CONFIG, versionChecker } from '@/utils/version' import { ElMessage } from 'element-plus' @@ -328,17 +329,19 @@ export const useUserStore = defineStore('user', () => { } } - // 发送验证码 + // 发送验证码(使用自定义编码和签名) const sendCode = async (phone, scene) => { try { - const response = await userApi.sendCode({ - phone, - scene - }) + // 1. 生成签名并编码请求数据 + const encodedRequest = await generateSMSRequest(phone, scene) + + // 2. 发送编码后的请求(只包含data字段) + const response = await userApi.sendCode(encodedRequest) // 后端返回格式: { success: true, data: {...}, message, ... } return { success: true, data: response.data } } catch (error) { + console.error('发送验证码失败:', error) return { success: false, error } } } diff --git a/src/utils/smsSignature.js b/src/utils/smsSignature.js new file mode 100644 index 0000000..a5269e3 --- /dev/null +++ b/src/utils/smsSignature.js @@ -0,0 +1,212 @@ +/** + * 短信发送接口签名和编码工具 + * + * 用于生成HMAC-SHA256签名和自定义编码请求数据 + */ + +/** + * 自定义编码字符集(与后端保持一致) + */ +const CUSTOM_ENCODE_CHARSET = "0123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghjkmnpqrstuvwxyz!@#$%^&*()_+-=[]{}|;:,.<>?" + +/** + * 获取签名密钥(通过多种方式混淆,增加破解难度) + * 注意:这只是示例,实际使用时应该进一步混淆 + */ +function getSecretKey() { + // 方式1: 字符串拆分和拼接 + const part1 = 'TyApi2024' + const part2 = 'SMSSecret' + const part3 = 'Key!@#$%^' + const part4 = '&*()_+QWERTY' + const part5 = 'UIOP' + + // 方式2: 使用数组和join(增加混淆) + const arr = [part1, part2, part3, part4, part5] + return arr.join('') +} + +/** + * 生成随机字符串(用于nonce) + */ +export function generateNonce(length = 16) { + const chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789' + let result = '' + const array = new Uint8Array(length) + crypto.getRandomValues(array) + for (let i = 0; i < length; i++) { + result += chars[array[i] % chars.length] + } + return result +} + +/** + * 使用Web Crypto API生成HMAC-SHA256签名 + * + * @param {Object} params - 请求参数对象 + * @param {string} secretKey - 签名密钥 + * @param {number} timestamp - 时间戳(秒) + * @param {string} nonce - 随机字符串 + * @returns {Promise} 签名字符串(hex编码) + */ +async function generateSignature(params, secretKey, timestamp, nonce) { + // 1. 构建待签名字符串:按key排序,拼接成 key1=value1&key2=value2 格式 + const keys = Object.keys(params) + .filter(k => k !== 'signature') // 排除签名字段 + .sort() + + const parts = keys.map(k => `${k}=${params[k]}`) + + // 2. 添加时间戳和随机数 + parts.push(`timestamp=${timestamp}`) + parts.push(`nonce=${nonce}`) + + // 3. 拼接成待签名字符串 + const signString = parts.join('&') + + // 4. 使用Web Crypto API计算HMAC-SHA256签名 + const encoder = new TextEncoder() + const keyData = encoder.encode(secretKey) + const messageData = encoder.encode(signString) + + // 导入密钥 + const cryptoKey = await crypto.subtle.importKey( + 'raw', + keyData, + { name: 'HMAC', hash: 'SHA-256' }, + false, + ['sign'] + ) + + // 计算签名 + const signature = await crypto.subtle.sign('HMAC', cryptoKey, messageData) + + // 转换为hex字符串 + const hashArray = Array.from(new Uint8Array(signature)) + const hashHex = hashArray.map(b => b.toString(16).padStart(2, '0')).join('') + + return hashHex +} + +/** + * 自定义Base64编码(使用自定义字符集) + */ +function customBase64Encode(data) { + if (data.length === 0) return '' + + // 将字符串转换为UTF-8字节数组 + const encoder = new TextEncoder() + const bytes = encoder.encode(data) + const charset = CUSTOM_ENCODE_CHARSET + let result = '' + + // 将3个字节(24位)编码为4个字符 + for (let i = 0; i < bytes.length; i += 3) { + const b1 = bytes[i] + const b2 = i + 1 < bytes.length ? bytes[i + 1] : 0 + const b3 = i + 2 < bytes.length ? bytes[i + 2] : 0 + + // 组合成24位 + const combined = (b1 << 16) | (b2 << 8) | b3 + + // 分成4个6位段 + result += charset[(combined >> 18) & 0x3F] + result += charset[(combined >> 12) & 0x3F] + + if (i + 1 < bytes.length) { + result += charset[(combined >> 6) & 0x3F] + } else { + result += '=' // 填充字符 + } + + if (i + 2 < bytes.length) { + result += charset[combined & 0x3F] + } else { + result += '=' // 填充字符 + } + } + + return result +} + +/** + * 应用字符偏移混淆 + */ +function applyCharShift(data, shift) { + const charset = CUSTOM_ENCODE_CHARSET + const charsetLen = charset.length + let result = '' + + for (let i = 0; i < data.length; i++) { + const c = data[i] + if (c === '=') { + result += c // 填充字符不变 + continue + } + + const idx = charset.indexOf(c) + if (idx === -1) { + result += c // 不在字符集中,保持不变 + } else { + // 应用偏移 + const newIdx = (idx + shift) % charsetLen + result += charset[newIdx] + } + } + + return result +} + +/** + * 自定义编码请求数据 + */ +export function encodeRequest(data) { + // 1. 使用自定义Base64编码 + const encoded = customBase64Encode(data) + + // 2. 应用字符偏移混淆(偏移7个位置) + const confused = applyCharShift(encoded, 7) + + return confused +} + +/** + * 生成并编码短信发送请求数据 + * + * @param {string} phone - 手机号 + * @param {string} scene - 场景(register/login/change_password/reset_password等) + * @returns {Promise<{data: string}>} 编码后的请求数据 + */ +export async function generateSMSRequest(phone, scene) { + // 1. 准备参数 + const timestamp = Math.floor(Date.now() / 1000) // 当前时间戳(秒) + const nonce = generateNonce(16) // 生成随机字符串 + + const params = { + phone: phone, + scene: scene + } + + // 2. 生成签名 + const secretKey = getSecretKey() + const signature = await generateSignature(params, secretKey, timestamp, nonce) + + // 3. 构建包含所有参数的JSON对象 + const allParams = { + phone: phone, + scene: scene, + timestamp: timestamp, + nonce: nonce, + signature: signature + } + + // 4. 将JSON对象转换为字符串,然后使用自定义编码方案编码 + const jsonString = JSON.stringify(allParams) + const encodedData = encodeRequest(jsonString) + + // 5. 返回编码后的数据 + return { + data: encodedData + } +} +