核心改进: - HTTP客户端:工厂函数模式,支持自定义拦截器和401/403处理 - 认证服务:函数式实现,消除this绑定问题,支持业务码+HTTP状态码双通道 - Token管理:简化为直接实例导出,移除bind()和箭头函数包装 - 路由守卫:优化逻辑,移除冗余代码,更简洁易维护 技术亮点: - 统一401/403错误处理(业务code和HTTP status双检查) - 自动刷新token并重试请求,保留自定义拦截器 - 分层清晰:clientAxios (Mono) -> http (应用) -> AuthService - 支持扩展:业务代码可创建自定义HTTP实例并添加拦截器 文件变更: - 新增 AuthService.js (函数式) 和 Login.vue - 重构 http.js、token-manager.js、router/index.js - 删除 TokenInput.vue、utils/auth.js 等冗余文件 - 更新所有API调用点使用直接实例导入 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
253 lines
7.8 KiB
JavaScript
253 lines
7.8 KiB
JavaScript
|
||
import api from '@/api/http'
|
||
// 直接使用实例(最简单、最可靠)
|
||
import tokenManager from '@gold/utils/token-manager'
|
||
// 使用公共配置
|
||
import { API_BASE } from '@gold/config/api'
|
||
|
||
const SERVER_BASE = API_BASE.APP_MEMBER
|
||
|
||
/**
|
||
* 保存 token 的辅助函数
|
||
* @param {Object} info - 包含 accessToken 和 refreshToken 的对象
|
||
*/
|
||
function saveTokens(info) {
|
||
if (info?.accessToken || info?.refreshToken) {
|
||
tokenManager.setTokens({
|
||
accessToken: info.accessToken || '',
|
||
refreshToken: info.refreshToken || '',
|
||
})
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 响应拦截(可选):统一错误处理
|
||
*/
|
||
// api.interceptors.response.use(
|
||
// (resp) => resp,
|
||
// (err) => {
|
||
// // 统一错误日志/提示
|
||
// return Promise.reject(err);
|
||
// }
|
||
// );
|
||
|
||
/**
|
||
* 短信场景枚举(请与后端配置保持一致)
|
||
* - MEMBER_LOGIN: 会员短信登录场景
|
||
* - MEMBER_UPDATE_PASSWORD: 已登录用户修改密码(短信校验)场景
|
||
* - MEMBER_RESET_PASSWORD: 未登录用户忘记密码重置(短信校验)场景
|
||
* 如有"注册"独立场景,可在此添加:MEMBER_REGISTER: 13
|
||
*/
|
||
export const SMS_SCENE = {
|
||
MEMBER_LOGIN: 1,
|
||
MEMBER_UPDATE_PASSWORD: 3,
|
||
MEMBER_RESET_PASSWORD: 4,
|
||
};
|
||
|
||
/**
|
||
* 短信模板编码常量
|
||
*/
|
||
export const SMS_TEMPLATE_CODE = {
|
||
USER_REGISTER: 'muye-user-code', // 用户注册模板编码
|
||
};
|
||
|
||
/**
|
||
* 账号密码登录
|
||
* POST /member/auth/login
|
||
*
|
||
* @param {string} mobile - 手机号(必填)
|
||
* @param {string} password - 密码(必填,长度 4-16)
|
||
* @returns {Promise<Object>} data.data: { accessToken, refreshToken, expiresTime, userInfo }
|
||
*/
|
||
export async function loginByPassword(mobile, password) {
|
||
const { data } = await api.post(`${SERVER_BASE}/auth/login`, { mobile, password });
|
||
const info = data || {};
|
||
saveTokens(info);
|
||
return info;
|
||
}
|
||
|
||
/**
|
||
* 发送短信验证码
|
||
* POST /member/auth/send-sms-code
|
||
*
|
||
* @param {string} mobile - 手机号(登录/忘记密码等需要;修改密码场景可不传)
|
||
* @param {number} scene - 短信场景(见 SMS_SCENE)
|
||
* @param {string} templateCode - 模板编码(可选,如 'muye-user-code')
|
||
* @returns {Promise<Object>} 后端通用响应结构
|
||
*/
|
||
export async function sendSmsCode(mobile, scene, templateCode) {
|
||
const body = { scene };
|
||
if (mobile) body.mobile = mobile; // 修改密码场景后端会根据当前登录用户自动填充手机号
|
||
if (templateCode) body.templateCode = templateCode; // 添加模板编码参数
|
||
const { data } = await api.post(`${SERVER_BASE}/auth/send-sms-code`, body);
|
||
return data;
|
||
}
|
||
|
||
/**
|
||
* 校验短信验证码(可选前置校验,一般直接在业务接口 use)
|
||
* POST /member/auth/validate-sms-code
|
||
*
|
||
* @param {string} mobile - 手机号(必填)
|
||
* @param {string} code - 验证码(必填,4-6 位数字)
|
||
* @param {number} scene - 短信场景(必填,见 SMS_SCENE)
|
||
* @returns {Promise<Object>} 后端通用响应结构
|
||
*/
|
||
export async function validateSmsCode(mobile, code, scene) {
|
||
const { data } = await api.post(`${SERVER_BASE}/auth/validate-sms-code`, { mobile, code, scene });
|
||
return data;
|
||
}
|
||
|
||
/**
|
||
* 手机+验证码登录(首次即自动注册)
|
||
* POST /member/auth/sms-login
|
||
*
|
||
* @param {string} mobile - 手机号(必填)
|
||
* @param {string} code - 短信验证码(必填)
|
||
* @returns {Promise<Object>} data.data: { accessToken, refreshToken, expiresTime, userInfo }
|
||
*/
|
||
export async function loginBySms(mobile, code) {
|
||
const { data } = await api.post(`${SERVER_BASE}/auth/sms-login`, { mobile, code });
|
||
const info = data || {};
|
||
saveTokens(info);
|
||
return info;
|
||
}
|
||
|
||
/**
|
||
* 刷新令牌
|
||
* POST /member/auth/refresh-token?refreshToken=xxx
|
||
*
|
||
* @returns {Promise<Object>} data.data: { accessToken, refreshToken, expiresTime, userInfo }
|
||
*/
|
||
export async function refreshToken() {
|
||
const rt = tokenManager.getRefreshToken();
|
||
if (!rt) throw new Error('缺少 refresh_token');
|
||
// refreshToken 作为 URL 查询参数传递
|
||
const { data } = await api.post(`${SERVER_BASE}/auth/refresh-token`, null, { params: { refreshToken: rt } });
|
||
const info = data || {};
|
||
saveTokens(info);
|
||
return info;
|
||
}
|
||
|
||
/**
|
||
* 登录态下:发送“修改密码”验证码
|
||
* - 场景:SMS_SCENE.MEMBER_UPDATE_PASSWORD
|
||
* - 特性:后端会以当前登录用户的手机号为准,无需传 mobile
|
||
* POST /member/auth/send-sms-code
|
||
*
|
||
* @returns {Promise<Object>} 后端通用响应结构
|
||
*/
|
||
export async function sendUpdatePasswordCode() {
|
||
const { data } = await api.post(`${SERVER_BASE}/auth/send-sms-code`, {
|
||
scene: SMS_SCENE.MEMBER_UPDATE_PASSWORD,
|
||
});
|
||
return data;
|
||
}
|
||
|
||
/**
|
||
* 登录态下:通过短信验证码修改密码
|
||
* PUT /member/user/update-password
|
||
*
|
||
* @param {string} newPassword - 新密码(必填,4-16 位)
|
||
* @param {string} smsCode - 短信验证码(必填,4-6 位数字)
|
||
* @returns {Promise<Object>} 后端通用响应结构
|
||
*/
|
||
export async function updatePasswordBySmsCode(newPassword, smsCode) {
|
||
const { data } = await api.put(`${SERVER_BASE}/user/update-password`, {
|
||
password: newPassword,
|
||
code: smsCode,
|
||
});
|
||
return data;
|
||
}
|
||
|
||
/**
|
||
* 未登录:发送“忘记密码”验证码
|
||
* - 场景:SMS_SCENE.MEMBER_RESET_PASSWORD
|
||
* POST /member/auth/send-sms-code
|
||
*
|
||
* @param {string} mobile - 手机号(必填)
|
||
* @returns {Promise<Object>} 后端通用响应结构
|
||
*/
|
||
export async function sendResetPasswordCode(mobile) {
|
||
const { data } = await api.post(`${SERVER_BASE}/auth/send-sms-code`, {
|
||
mobile,
|
||
scene: SMS_SCENE.MEMBER_RESET_PASSWORD,
|
||
});
|
||
return data;
|
||
}
|
||
|
||
/**
|
||
* 未登录:通过手机验证码重置密码(忘记密码)
|
||
* PUT /member/user/reset-password
|
||
*
|
||
* @param {string} mobile - 手机号(必填)
|
||
* @param {string} newPassword - 新密码(必填,4-16 位)
|
||
* @param {string} smsCode - 短信验证码(必填,4-6 位数字)
|
||
* @returns {Promise<Object>} 后端通用响应结构
|
||
*/
|
||
export async function resetPasswordBySms(mobile, newPassword, smsCode) {
|
||
const { data } = await api.put(`${SERVER_BASE}/user/reset-password`, {
|
||
mobile,
|
||
password: newPassword,
|
||
code: smsCode,
|
||
});
|
||
return data;
|
||
}
|
||
|
||
/**
|
||
* 获取用户信息(C端)
|
||
* GET /member/user/get
|
||
*
|
||
* @returns {Promise<Object>} 用户信息对象
|
||
*/
|
||
export async function getUserInfo() {
|
||
const { data } = await api.get(`${SERVER_BASE}/user/get`)
|
||
return data || {}
|
||
}
|
||
|
||
/**
|
||
* “手机+验证码+密码注册”组合流程(基于短信登录即注册 + 设置密码)
|
||
* 说明:
|
||
* - 1) 发送登录场景验证码(可选:若已拿到 code 可跳过)
|
||
* - 2) 短信登录:首次会自动注册并返回 token
|
||
* - 3) 登录态下发送“修改密码”验证码
|
||
* - 4) 用短信验证码设置密码
|
||
*
|
||
* @param {string} mobile - 手机号(必填)
|
||
* @param {string} loginCode - 第一次登录使用的短信验证码(必填)
|
||
* @param {string} newPassword - 注册后设置的新密码(必填)
|
||
* @param {string} updatePwdCode - 设置密码用到的短信验证码(必填)
|
||
* @returns {Promise<void>}
|
||
*/
|
||
export async function registerWithMobileCodePassword(mobile, loginCode, newPassword, updatePwdCode) {
|
||
// 1) 可选:发送登录场景验证码(若已拿到 loginCode 可跳过)
|
||
// await sendSmsCode(mobile, SMS_SCENE.MEMBER_LOGIN);
|
||
|
||
// 2) 短信登录(首次即注册)
|
||
await loginBySms(mobile, loginCode);
|
||
|
||
// 3) 登录态下发送“修改密码”验证码(短信会发到当前账号绑定的手机号)
|
||
// await sendUpdatePasswordCode();
|
||
|
||
// 4) 用短信验证码设置密码
|
||
await updatePasswordBySmsCode(newPassword, updatePwdCode);
|
||
}
|
||
|
||
/**
|
||
* 导出一个默认对象,便于统一引入
|
||
*/
|
||
export default {
|
||
SMS_SCENE,
|
||
SMS_TEMPLATE_CODE,
|
||
loginByPassword,
|
||
sendSmsCode,
|
||
validateSmsCode,
|
||
loginBySms,
|
||
refreshToken,
|
||
sendUpdatePasswordCode,
|
||
updatePasswordBySmsCode,
|
||
sendResetPasswordCode,
|
||
resetPasswordBySms,
|
||
registerWithMobileCodePassword,
|
||
getUserInfo,
|
||
};
|