feat(mobile): 解决移动端authorization为空问题并完善加密登录功能
主要修复: - 修复登录流程逻辑顺序,先保存Token再调用getUserInfo API - 解决纯JavaScript RSA加密兼容性问题,替代JSEncrypt - 创建Token测试工具,便于调试和状态检查 - 优化请求头配置,符合后端API要求 技术改进: - 新增encrypt-pure.js:纯JavaScript RSA加密实现 - 新增token-test.js:Token存储和状态测试工具 - 优化request.js:增强Token获取和调试功能 - 更新login/index.vue:修复登录流程和添加测试功能 解决问题: - ✅ authorization请求头不再为空 - ✅ AES+RSA双重加密登录正常工作 - ✅ uni-app环境下RSA加密兼容性问题已解决 - ✅ 完整的登录认证流程验证通过
This commit is contained in:
1 parent
975a4f2e33
commit
5589cba249
8 files changed
+1862
-107
No files matched your search
@@ -0,0 +1,283 @@
|
||||
// @ts-nocheck
|
||||
import CryptoJS from 'crypto-js';
|
||||
import bigInt from 'big-integer';
|
||||
|
||||
// 配置信息
|
||||
const ENCRYPT_CONFIG = {
|
||||
// RSA公钥 - 与后端保持一致
|
||||
RSA_PUBLIC_KEY:
|
||||
'MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAKoR8mX0rGKLqzcWmOzbfj64K8ZIgOdHnzkXSOVOZbFu/TJhZ7rFAN+eaGkl3C4buccQd/EjEsj9ir7ijT7h96MCAwEAAQ==',
|
||||
CLIENT_ID: 'e5cd7e4891bf95d1d19206ce24a7b32e',
|
||||
TENANT_ID: '000000',
|
||||
};
|
||||
|
||||
/**
|
||||
* Base64解码
|
||||
*/
|
||||
function base64Decode(str) {
|
||||
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
|
||||
let result = '';
|
||||
let buffer = 0;
|
||||
let bits = 0;
|
||||
|
||||
for (let i = 0; i < str.length; i++) {
|
||||
const char = str.charAt(i);
|
||||
if (char === '=') break;
|
||||
|
||||
const index = chars.indexOf(char);
|
||||
if (index === -1) continue;
|
||||
|
||||
buffer = (buffer << 6) | index;
|
||||
bits += 6;
|
||||
|
||||
if (bits >= 8) {
|
||||
bits -= 8;
|
||||
result += String.fromCharCode((buffer >> bits) & 0xff);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析RSA公钥
|
||||
*/
|
||||
function parseRSAPublicKey(keyStr) {
|
||||
try {
|
||||
console.log('🔧 开始解析RSA公钥...');
|
||||
|
||||
// Base64解码
|
||||
const binaryStr = base64Decode(keyStr);
|
||||
const bytes = [];
|
||||
for (let i = 0; i < binaryStr.length; i++) {
|
||||
bytes.push(binaryStr.charCodeAt(i));
|
||||
}
|
||||
|
||||
console.log('🔧 公钥字节长度:', bytes.length);
|
||||
|
||||
// 简化的ASN.1解析 - 跳过前面的头部直接获取n和e
|
||||
// 对于我们的固定公钥,直接使用已知的n和e值
|
||||
// 这个是从公钥解析得出的正确n和e值
|
||||
const n = bigInt(
|
||||
'8907294147844816284558538478551088035165434911351789542173700276555680401825462782219485742265212984242385862425017144718658441075282309582555957891823523',
|
||||
);
|
||||
const e = bigInt('65537'); // 标准的RSA指数
|
||||
|
||||
console.log('✅ RSA公钥解析成功');
|
||||
console.log('🔧 n值长度:', n.toString().length);
|
||||
console.log('🔧 e值:', e.toString());
|
||||
|
||||
return { n, e };
|
||||
} catch (error) {
|
||||
console.error('❌ RSA公钥解析失败:', error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* PKCS#1 v1.5 填充
|
||||
*/
|
||||
function pkcs1Padding(data, keyLength) {
|
||||
const dataBytes = [];
|
||||
for (let i = 0; i < data.length; i++) {
|
||||
dataBytes.push(data.charCodeAt(i));
|
||||
}
|
||||
|
||||
const paddingLength = keyLength - 3 - dataBytes.length;
|
||||
if (paddingLength < 8) {
|
||||
throw new Error('数据太长,无法进行PKCS#1填充');
|
||||
}
|
||||
|
||||
const padded = [0x00, 0x02];
|
||||
|
||||
// 添加随机填充字节
|
||||
for (let i = 0; i < paddingLength; i++) {
|
||||
let randomByte;
|
||||
do {
|
||||
randomByte = Math.floor(Math.random() * 256);
|
||||
} while (randomByte === 0);
|
||||
padded.push(randomByte);
|
||||
}
|
||||
|
||||
padded.push(0x00);
|
||||
padded.push(...dataBytes);
|
||||
|
||||
return padded;
|
||||
}
|
||||
|
||||
/**
|
||||
* 纯JavaScript RSA加密
|
||||
*/
|
||||
function rsaEncrypt(data, publicKey) {
|
||||
try {
|
||||
console.log('🔧 开始纯JavaScript RSA加密...');
|
||||
console.log('🔧 待加密数据:', data);
|
||||
|
||||
const keyLength = 64; // 512位密钥 = 64字节
|
||||
|
||||
// PKCS#1填充
|
||||
const paddedBytes = pkcs1Padding(data, keyLength);
|
||||
console.log('🔧 填充后字节长度:', paddedBytes.length);
|
||||
|
||||
// 转换为大整数
|
||||
let message = bigInt(0);
|
||||
for (let i = 0; i < paddedBytes.length; i++) {
|
||||
message = message.multiply(256).add(paddedBytes[i]);
|
||||
}
|
||||
|
||||
console.log('🔧 消息大整数长度:', message.toString().length);
|
||||
|
||||
// RSA加密: c = m^e mod n
|
||||
const encrypted = message.modPow(publicKey.e, publicKey.n);
|
||||
console.log('🔧 加密后大整数长度:', encrypted.toString().length);
|
||||
|
||||
// 转换为字节数组
|
||||
const encryptedBytes = [];
|
||||
let temp = encrypted;
|
||||
for (let i = 0; i < keyLength; i++) {
|
||||
encryptedBytes.unshift(temp.mod(256).toJSNumber());
|
||||
temp = temp.divide(256);
|
||||
}
|
||||
|
||||
// 转换为Base64
|
||||
let binaryStr = '';
|
||||
for (let i = 0; i < encryptedBytes.length; i++) {
|
||||
binaryStr += String.fromCharCode(encryptedBytes[i]);
|
||||
}
|
||||
|
||||
const base64 = btoa(binaryStr);
|
||||
console.log('✅ RSA加密成功,Base64长度:', base64.length);
|
||||
|
||||
return base64;
|
||||
} catch (error) {
|
||||
console.error('❌ 纯JavaScript RSA加密失败:', error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成随机AES密钥
|
||||
* @param {number} length 密钥长度,默认32位
|
||||
* @returns {string} AES密钥
|
||||
*/
|
||||
export function generateRandomAESKey(length = 32) {
|
||||
const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
||||
let result = '';
|
||||
for (let i = 0; i < length; i++) {
|
||||
result += characters.charAt(Math.floor(Math.random() * characters.length));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* AES加密数据
|
||||
* @param {string} data 待加密的数据
|
||||
* @param {string} key AES密钥
|
||||
* @returns {string} 加密后的数据
|
||||
*/
|
||||
export function encryptWithAES(data, key) {
|
||||
const keyUtf8 = CryptoJS.enc.Utf8.parse(key);
|
||||
const encrypted = CryptoJS.AES.encrypt(data, keyUtf8, {
|
||||
mode: CryptoJS.mode.ECB,
|
||||
padding: CryptoJS.pad.Pkcs7,
|
||||
});
|
||||
return encrypted.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* RSA加密AES密钥 - 使用纯JavaScript实现
|
||||
* @param {string} aesKey AES密钥
|
||||
* @returns {string|null} RSA加密后的AES密钥
|
||||
*/
|
||||
export function encryptAESKeyWithRSA(aesKey) {
|
||||
try {
|
||||
console.log('🔧 开始纯JavaScript RSA加密,AES密钥:', aesKey);
|
||||
|
||||
// 解析RSA公钥
|
||||
const publicKey = parseRSAPublicKey(ENCRYPT_CONFIG.RSA_PUBLIC_KEY);
|
||||
if (!publicKey) {
|
||||
throw new Error('RSA公钥解析失败');
|
||||
}
|
||||
|
||||
// 将AES密钥转为Base64(与custom-login-script.js保持一致)
|
||||
const aesKeyBase64 = btoa(unescape(encodeURIComponent(aesKey)));
|
||||
console.log('🔧 AES密钥Base64:', aesKeyBase64);
|
||||
|
||||
// RSA加密
|
||||
const encrypted = rsaEncrypt(aesKeyBase64, publicKey);
|
||||
|
||||
if (!encrypted) {
|
||||
throw new Error('纯JavaScript RSA加密失败');
|
||||
}
|
||||
|
||||
console.log('✅ 纯JavaScript RSA加密成功,结果长度:', encrypted.length);
|
||||
return encrypted;
|
||||
} catch (error) {
|
||||
console.error('❌ 纯JavaScript RSA加密失败:', error.message);
|
||||
console.error('❌ 错误堆栈:', error.stack);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 登录数据加密处理
|
||||
* @param {Object} loginData 登录数据对象
|
||||
* @returns {Object} 包含加密数据和请求头的对象
|
||||
*/
|
||||
export function encryptLoginData(loginData) {
|
||||
try {
|
||||
// 1. 生成AES密钥
|
||||
const aesKey = generateRandomAESKey(32);
|
||||
console.log('🔑 生成AES密钥:', aesKey);
|
||||
|
||||
// 2. 准备完整的登录数据
|
||||
const fullLoginData = {
|
||||
tenantId: loginData.tenantId || ENCRYPT_CONFIG.TENANT_ID,
|
||||
username: loginData.username,
|
||||
password: loginData.password,
|
||||
clientId: loginData.clientId || ENCRYPT_CONFIG.CLIENT_ID,
|
||||
grantType: loginData.grantType || 'password',
|
||||
};
|
||||
|
||||
console.log('📋 登录数据:', JSON.stringify(fullLoginData, null, 2));
|
||||
|
||||
// 3. AES加密登录数据
|
||||
const encryptedData = encryptWithAES(JSON.stringify(fullLoginData), aesKey);
|
||||
console.log('🔐 AES加密后数据长度:', encryptedData.length);
|
||||
|
||||
// 4. RSA加密AES密钥
|
||||
const encryptedKey = encryptAESKeyWithRSA(aesKey);
|
||||
if (!encryptedKey) {
|
||||
throw new Error('RSA加密AES密钥失败');
|
||||
}
|
||||
console.log('🔑 RSA加密后密钥长度:', encryptedKey.length);
|
||||
|
||||
// 5. 返回加密结果和请求头
|
||||
return {
|
||||
success: true,
|
||||
encryptedData: encryptedData,
|
||||
headers: {
|
||||
'Content-Type': 'application/json;charset=utf-8',
|
||||
clientid: ENCRYPT_CONFIG.CLIENT_ID,
|
||||
'encrypt-key': encryptedKey,
|
||||
'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8',
|
||||
isToken: 'false',
|
||||
isEncrypt: 'true',
|
||||
repeatSubmit: 'false',
|
||||
},
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('登录数据加密失败:', error);
|
||||
return {
|
||||
success: false,
|
||||
error: error.message,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取加密配置
|
||||
*/
|
||||
export function getEncryptConfig() {
|
||||
return ENCRYPT_CONFIG;
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
// 加密功能测试工具
|
||||
import {
|
||||
encryptLoginData,
|
||||
generateRandomAESKey,
|
||||
encryptWithAES,
|
||||
encryptAESKeyWithRSA,
|
||||
} from './encrypt-pure.js';
|
||||
|
||||
/**
|
||||
* 测试RSA加密功能
|
||||
*/
|
||||
export async function testRSAEncryption() {
|
||||
console.log('🧪 开始测试RSA加密功能...');
|
||||
|
||||
try {
|
||||
// 测试生成AES密钥
|
||||
const aesKey = generateRandomAESKey(32);
|
||||
console.log('✅ AES密钥生成成功:', aesKey);
|
||||
|
||||
// 测试AES加密
|
||||
const testData = 'test data';
|
||||
const aesEncrypted = encryptWithAES(testData, aesKey);
|
||||
console.log('✅ AES加密成功:', aesEncrypted.substring(0, 50) + '...');
|
||||
|
||||
// 测试RSA加密 - 这里是关键
|
||||
const rsaEncrypted = encryptAESKeyWithRSA(aesKey);
|
||||
if (rsaEncrypted) {
|
||||
console.log('✅ RSA加密成功:', rsaEncrypted.substring(0, 50) + '...');
|
||||
console.log('✅ RSA加密结果长度:', rsaEncrypted.length);
|
||||
return true;
|
||||
} else {
|
||||
console.error('❌ RSA加密失败: 返回null');
|
||||
return false;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('❌ RSA加密测试失败:', error);
|
||||
console.error('❌ 错误详情:', error.message);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试完整登录加密流程
|
||||
*/
|
||||
export async function testLoginEncryption() {
|
||||
console.log('🧪 开始测试完整登录加密流程...');
|
||||
|
||||
try {
|
||||
const loginData = {
|
||||
username: 'admin',
|
||||
password: 'admin123',
|
||||
tenantId: '000000',
|
||||
};
|
||||
|
||||
const result = encryptLoginData(loginData);
|
||||
|
||||
// 如果返回Promise,需要await
|
||||
const actualResult = result.then ? await result : result;
|
||||
|
||||
if (actualResult.success) {
|
||||
console.log('✅ 登录加密成功');
|
||||
console.log('✅ 加密数据长度:', actualResult.encryptedData.length);
|
||||
console.log('✅ 请求头:', JSON.stringify(actualResult.headers, null, 2));
|
||||
return true;
|
||||
} else {
|
||||
console.error('❌ 登录加密失败:', actualResult.error);
|
||||
return false;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('❌ 登录加密测试失败:', error);
|
||||
console.error('❌ 错误详情:', error.message);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 运行所有测试
|
||||
*/
|
||||
export async function runAllTests() {
|
||||
console.log('🧪 开始运行所有加密测试...');
|
||||
|
||||
try {
|
||||
const rsaTest = await testRSAEncryption();
|
||||
const loginTest = await testLoginEncryption();
|
||||
|
||||
if (rsaTest && loginTest) {
|
||||
console.log('🎉 所有加密测试通过!');
|
||||
return true;
|
||||
} else {
|
||||
console.log('❌ 存在测试失败!');
|
||||
return false;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('❌ 测试执行失败:', error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
+313
-96
@@ -1,120 +1,337 @@
|
||||
import { encryptLoginData } from './encrypt-pure.js';
|
||||
|
||||
// API请求配置
|
||||
const baseURL =
|
||||
process.env.NODE_ENV === 'development'
|
||||
? 'http://localhost:3000/api'
|
||||
: 'https://your-api-domain.com/api'
|
||||
? '/api' // 开发环境使用代理
|
||||
: 'https://your-api-domain.com';
|
||||
|
||||
// 统一请求方法
|
||||
export const request = (options) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
uni.request({
|
||||
url: baseURL + options.url,
|
||||
method: options.method || 'GET',
|
||||
data: options.data || {},
|
||||
header: {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: getToken(),
|
||||
...options.header,
|
||||
},
|
||||
success: (res) => {
|
||||
if (res.statusCode === 200) {
|
||||
resolve(res.data)
|
||||
} else {
|
||||
reject(res)
|
||||
}
|
||||
},
|
||||
fail: (error) => {
|
||||
reject(error)
|
||||
},
|
||||
})
|
||||
})
|
||||
}
|
||||
return new Promise((resolve, reject) => {
|
||||
// 获取token
|
||||
const token = getToken();
|
||||
|
||||
uni.request({
|
||||
url: baseURL + options.url,
|
||||
method: options.method || 'GET',
|
||||
data: options.data || {},
|
||||
header: {
|
||||
'Content-Type': 'application/json',
|
||||
authorization: token,
|
||||
clientid: 'e5cd7e4891bf95d1d19206ce24a7b32e',
|
||||
referer: 'http://localhost/login?redirect=/index',
|
||||
...options.header,
|
||||
},
|
||||
success: (res) => {
|
||||
// 处理响应
|
||||
if (res.statusCode === 200) {
|
||||
// 检查业务状态码
|
||||
if (res.data && res.data.code !== undefined) {
|
||||
if (res.data.code === 200) {
|
||||
resolve(res.data);
|
||||
} else {
|
||||
// 业务错误
|
||||
uni.showToast({
|
||||
title: res.data.msg || '请求失败',
|
||||
icon: 'none',
|
||||
});
|
||||
reject(res.data);
|
||||
}
|
||||
} else {
|
||||
resolve(res.data);
|
||||
}
|
||||
} else {
|
||||
// HTTP状态码错误
|
||||
uni.showToast({
|
||||
title: `请求失败: ${res.statusCode}`,
|
||||
icon: 'none',
|
||||
});
|
||||
reject(res);
|
||||
}
|
||||
},
|
||||
fail: (error) => {
|
||||
uni.showToast({
|
||||
title: '网络请求失败',
|
||||
icon: 'none',
|
||||
});
|
||||
reject(error);
|
||||
},
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
// 获取token
|
||||
function getToken() {
|
||||
try {
|
||||
const token = uni.getStorageSync('token')
|
||||
return token ? `Bearer ${token}` : ''
|
||||
} catch (e) {
|
||||
return ''
|
||||
}
|
||||
try {
|
||||
const token = uni.getStorageSync('access_token');
|
||||
console.log(
|
||||
'🔍 获取Token:',
|
||||
token ? `Bearer ${token.substring(0, 20)}...` : '❌ Token为空',
|
||||
);
|
||||
return token ? `Bearer ${token}` : '';
|
||||
} catch (e) {
|
||||
console.error('❌ 获取Token失败:', e);
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
// API方法封装
|
||||
export const api = {
|
||||
// 用户登录
|
||||
login: (data) =>
|
||||
request({
|
||||
url: '/auth/login',
|
||||
method: 'POST',
|
||||
data,
|
||||
}),
|
||||
// 用户登录
|
||||
login: async (data) => {
|
||||
try {
|
||||
console.log('🔐 开始加密登录流程...');
|
||||
|
||||
// 获取用户信息
|
||||
getUserInfo: () =>
|
||||
request({
|
||||
url: '/user/info',
|
||||
}),
|
||||
// 加密登录数据
|
||||
const encryptResult = encryptLoginData({
|
||||
username: data.username,
|
||||
password: data.password,
|
||||
tenantId: data.tenantId || '000000',
|
||||
clientId: data.clientId || 'e5cd7e4891bf95d1d19206ce24a7b32e',
|
||||
grantType: data.grantType || 'password',
|
||||
});
|
||||
|
||||
// 获取首页数据
|
||||
getHomeData: () =>
|
||||
request({
|
||||
url: '/home/data',
|
||||
}),
|
||||
}
|
||||
// 如果encryptLoginData返回的是Promise,需要await
|
||||
const actualEncryptResult = encryptResult.then ? await encryptResult : encryptResult;
|
||||
|
||||
if (!actualEncryptResult.success) {
|
||||
console.error('❌ 登录数据加密失败:', actualEncryptResult.error);
|
||||
throw new Error(actualEncryptResult.error);
|
||||
}
|
||||
|
||||
console.log('✅ 登录数据加密成功,发送加密请求...');
|
||||
|
||||
// 使用加密数据和自定义header发送请求
|
||||
return new Promise((resolve, reject) => {
|
||||
uni.request({
|
||||
url: baseURL + '/auth/login',
|
||||
method: 'POST',
|
||||
data: actualEncryptResult.encryptedData, // 发送加密字符串,不是JSON对象
|
||||
header: {
|
||||
...actualEncryptResult.headers,
|
||||
referer: 'http://localhost/login?redirect=/index', // 添加正确格式的referer
|
||||
},
|
||||
success: (res) => {
|
||||
console.log('🔐 加密登录响应:', res);
|
||||
if (res.statusCode === 200) {
|
||||
if (res.data && res.data.code !== undefined) {
|
||||
if (res.data.code === 200) {
|
||||
console.log('✅ 加密登录成功');
|
||||
resolve(res.data);
|
||||
} else {
|
||||
console.log('❌ 登录失败:', res.data.msg);
|
||||
uni.showToast({
|
||||
title: res.data.msg || '登录失败',
|
||||
icon: 'none',
|
||||
});
|
||||
reject(res.data);
|
||||
}
|
||||
} else {
|
||||
resolve(res.data);
|
||||
}
|
||||
} else {
|
||||
console.log('❌ HTTP状态错误:', res.statusCode);
|
||||
uni.showToast({
|
||||
title: `请求失败: ${res.statusCode}`,
|
||||
icon: 'none',
|
||||
});
|
||||
reject(res);
|
||||
}
|
||||
},
|
||||
fail: (error) => {
|
||||
console.log('❌ 登录请求失败:', error);
|
||||
uni.showToast({
|
||||
title: '网络请求失败',
|
||||
icon: 'none',
|
||||
});
|
||||
reject(error);
|
||||
},
|
||||
});
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('❌ 登录加密处理失败:', error);
|
||||
console.error('❌ 错误详情:', error.message);
|
||||
console.error('❌ 错误堆栈:', error.stack);
|
||||
uni.showToast({
|
||||
title: '登录处理失败: ' + error.message,
|
||||
icon: 'none',
|
||||
});
|
||||
return Promise.reject(error);
|
||||
}
|
||||
},
|
||||
|
||||
// 获取用户信息
|
||||
getUserInfo: () => {
|
||||
const token = getToken();
|
||||
console.log(
|
||||
'🎫 获取用户信息,当前token:',
|
||||
token ? token.substring(0, 20) + '...' : '无token',
|
||||
);
|
||||
|
||||
return request({
|
||||
url: '/system/user/getInfo',
|
||||
method: 'GET',
|
||||
header: {
|
||||
authorization: token,
|
||||
clientid: 'e5cd7e4891bf95d1d19206ce24a7b32e',
|
||||
referer: 'http://localhost/login?redirect=/index',
|
||||
},
|
||||
});
|
||||
},
|
||||
|
||||
// 获取租户列表
|
||||
getTenantList: (isToken = false) =>
|
||||
request({
|
||||
url: '/auth/tenant/list',
|
||||
method: 'GET',
|
||||
header: {
|
||||
isToken: isToken,
|
||||
},
|
||||
}),
|
||||
|
||||
// 注销登录
|
||||
logout: () =>
|
||||
request({
|
||||
url: '/auth/logout',
|
||||
method: 'POST',
|
||||
}),
|
||||
};
|
||||
|
||||
// 用户信息缓存工具
|
||||
export const userStorage = {
|
||||
// 保存登录信息
|
||||
saveLoginInfo: (loginResult, userInfo) => {
|
||||
try {
|
||||
// 保存token
|
||||
uni.setStorageSync('access_token', loginResult.access_token);
|
||||
|
||||
// 保存用户信息(增加登录时间)
|
||||
if (userInfo) {
|
||||
const enhancedUserInfo = {
|
||||
...userInfo,
|
||||
loginTime: new Date().toISOString(), // 记录登录时间
|
||||
tokenExpireTime: loginResult.expires_in
|
||||
? new Date(Date.now() + loginResult.expires_in * 1000).toISOString()
|
||||
: null,
|
||||
};
|
||||
uni.setStorageSync('userInfo', JSON.stringify(enhancedUserInfo));
|
||||
}
|
||||
|
||||
// 保存登录状态
|
||||
uni.setStorageSync('isLoggedIn', true);
|
||||
|
||||
// 保存其他token信息
|
||||
if (loginResult.refresh_token) {
|
||||
uni.setStorageSync('refresh_token', loginResult.refresh_token);
|
||||
}
|
||||
|
||||
console.log('用户登录信息已保存', {
|
||||
hasToken: !!loginResult.access_token,
|
||||
hasUserInfo: !!userInfo,
|
||||
loginTime: new Date().toLocaleString(),
|
||||
});
|
||||
} catch (e) {
|
||||
console.error('保存登录信息失败:', e);
|
||||
}
|
||||
},
|
||||
|
||||
// 获取用户信息
|
||||
getUserInfo: () => {
|
||||
try {
|
||||
const userInfo = uni.getStorageSync('userInfo');
|
||||
return userInfo ? JSON.parse(userInfo) : null;
|
||||
} catch (e) {
|
||||
console.error('获取用户信息失败:', e);
|
||||
return null;
|
||||
}
|
||||
},
|
||||
|
||||
// 获取token
|
||||
getToken: () => {
|
||||
try {
|
||||
return uni.getStorageSync('access_token') || '';
|
||||
} catch (e) {
|
||||
return '';
|
||||
}
|
||||
},
|
||||
|
||||
// 检查登录状态
|
||||
isLoggedIn: () => {
|
||||
try {
|
||||
const isLoggedIn = uni.getStorageSync('isLoggedIn');
|
||||
const token = uni.getStorageSync('access_token');
|
||||
return !!(isLoggedIn && token);
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
|
||||
// 清除登录信息
|
||||
clearLoginInfo: () => {
|
||||
try {
|
||||
uni.removeStorageSync('access_token');
|
||||
uni.removeStorageSync('userInfo');
|
||||
uni.removeStorageSync('isLoggedIn');
|
||||
console.log('用户登录信息已清除');
|
||||
} catch (e) {
|
||||
console.error('清除登录信息失败:', e);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
// 数据缓存工具
|
||||
export const storage = {
|
||||
// 设置缓存
|
||||
set: (key, value, expire) => {
|
||||
try {
|
||||
const data = {
|
||||
value,
|
||||
timestamp: Date.now(),
|
||||
expire: expire ? Date.now() + expire * 1000 : null,
|
||||
}
|
||||
uni.setStorageSync(key, JSON.stringify(data))
|
||||
} catch (e) {
|
||||
console.error('缓存设置失败:', e)
|
||||
}
|
||||
},
|
||||
// 设置缓存
|
||||
set: (key, value, expire) => {
|
||||
try {
|
||||
const data = {
|
||||
value,
|
||||
timestamp: Date.now(),
|
||||
expire: expire ? Date.now() + expire * 1000 : null,
|
||||
};
|
||||
uni.setStorageSync(key, JSON.stringify(data));
|
||||
} catch (e) {
|
||||
console.error('缓存设置失败:', e);
|
||||
}
|
||||
},
|
||||
|
||||
// 获取缓存
|
||||
get: (key) => {
|
||||
try {
|
||||
const data = uni.getStorageSync(key)
|
||||
if (!data) return null
|
||||
// 获取缓存
|
||||
get: (key) => {
|
||||
try {
|
||||
const data = uni.getStorageSync(key);
|
||||
if (!data) return null;
|
||||
|
||||
const parsed = JSON.parse(data)
|
||||
const parsed = JSON.parse(data);
|
||||
|
||||
// 检查是否过期
|
||||
if (parsed.expire && Date.now() > parsed.expire) {
|
||||
uni.removeStorageSync(key)
|
||||
return null
|
||||
}
|
||||
// 检查是否过期
|
||||
if (parsed.expire && Date.now() > parsed.expire) {
|
||||
uni.removeStorageSync(key);
|
||||
return null;
|
||||
}
|
||||
|
||||
return parsed.value
|
||||
} catch (e) {
|
||||
console.error('缓存获取失败:', e)
|
||||
return null
|
||||
}
|
||||
},
|
||||
return parsed.value;
|
||||
} catch (e) {
|
||||
console.error('缓存获取失败:', e);
|
||||
return null;
|
||||
}
|
||||
},
|
||||
|
||||
// 删除缓存
|
||||
remove: (key) => {
|
||||
try {
|
||||
uni.removeStorageSync(key)
|
||||
} catch (e) {
|
||||
console.error('缓存删除失败:', e)
|
||||
}
|
||||
},
|
||||
// 删除缓存
|
||||
remove: (key) => {
|
||||
try {
|
||||
uni.removeStorageSync(key);
|
||||
} catch (e) {
|
||||
console.error('缓存删除失败:', e);
|
||||
}
|
||||
},
|
||||
|
||||
// 清空缓存
|
||||
clear: () => {
|
||||
try {
|
||||
uni.clearStorageSync()
|
||||
} catch (e) {
|
||||
console.error('缓存清空失败:', e)
|
||||
}
|
||||
},
|
||||
}
|
||||
// 清空缓存
|
||||
clear: () => {
|
||||
try {
|
||||
uni.clearStorageSync();
|
||||
} catch (e) {
|
||||
console.error('缓存清空失败:', e);
|
||||
}
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,79 @@
|
||||
// Token测试工具
|
||||
export function testTokenStorage() {
|
||||
console.log('🧪 开始Token存储测试...');
|
||||
|
||||
try {
|
||||
// 1. 清除现有Token
|
||||
uni.removeStorageSync('access_token');
|
||||
console.log('🗑️ 已清除现有Token');
|
||||
|
||||
// 2. 测试存储Token
|
||||
const testToken =
|
||||
'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJsb2dpblR5cGUiOiJsb2dpbiIsImxvZ2luSWQiOiJzeXNfdXNlcjoxIn0.test';
|
||||
uni.setStorageSync('access_token', testToken);
|
||||
console.log('💾 已存储测试Token');
|
||||
|
||||
// 3. 测试获取Token
|
||||
const retrievedToken = uni.getStorageSync('access_token');
|
||||
console.log(
|
||||
'📖 获取到的Token:',
|
||||
retrievedToken ? retrievedToken.substring(0, 30) + '...' : '❌ 为空',
|
||||
);
|
||||
|
||||
// 4. 验证Token是否一致
|
||||
if (retrievedToken === testToken) {
|
||||
console.log('✅ Token存储和获取测试通过');
|
||||
return true;
|
||||
} else {
|
||||
console.log('❌ Token存储和获取测试失败');
|
||||
return false;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('❌ Token测试失败:', error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前存储的Token信息
|
||||
*/
|
||||
export function getCurrentTokenInfo() {
|
||||
try {
|
||||
const token = uni.getStorageSync('access_token');
|
||||
const userInfo = uni.getStorageSync('userInfo');
|
||||
const isLoggedIn = uni.getStorageSync('isLoggedIn');
|
||||
|
||||
console.log('📊 当前存储状态:');
|
||||
console.log(' 🎫 Token:', token ? `存在 (${token.length}字符)` : '❌ 不存在');
|
||||
console.log(' 👤 用户信息:', userInfo ? '存在' : '❌ 不存在');
|
||||
console.log(' 🔐 登录状态:', isLoggedIn ? '已登录' : '❌ 未登录');
|
||||
|
||||
return {
|
||||
hasToken: !!token,
|
||||
tokenLength: token ? token.length : 0,
|
||||
hasUserInfo: !!userInfo,
|
||||
isLoggedIn: !!isLoggedIn,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('❌ 获取Token信息失败:', error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除所有登录信息
|
||||
*/
|
||||
export function clearAllLoginInfo() {
|
||||
try {
|
||||
uni.removeStorageSync('access_token');
|
||||
uni.removeStorageSync('userInfo');
|
||||
uni.removeStorageSync('isLoggedIn');
|
||||
uni.removeStorageSync('refresh_token');
|
||||
|
||||
console.log('🗑️ 已清除所有登录信息');
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error('❌ 清除登录信息失败:', error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user