2025-08-10 22:42:56 +08:00
|
|
|
|
// 认证相关API接口
|
|
|
|
|
import { ApiRequest } from '../request'
|
|
|
|
|
import type {
|
|
|
|
|
ApiResponse,
|
|
|
|
|
User,
|
|
|
|
|
LoginRequest,
|
|
|
|
|
LoginResponse,
|
|
|
|
|
// BackendLoginResponse,
|
|
|
|
|
RegisterRequest,
|
|
|
|
|
UserProfile,
|
|
|
|
|
} from '../types'
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 认证API模块
|
|
|
|
|
*/
|
|
|
|
|
export class AuthApi {
|
|
|
|
|
// 用户登录
|
|
|
|
|
static async login(data: LoginRequest): Promise<ApiResponse<LoginResponse>> {
|
|
|
|
|
try {
|
2025-08-15 13:21:10 +08:00
|
|
|
|
// 转换参数格式:将email/phone转换为username
|
|
|
|
|
const loginData = {
|
|
|
|
|
username: data.email || data.phone || '',
|
|
|
|
|
password: data.password
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
console.log('🚀 发送登录请求:', { url: '/biz/user/login', data: { ...loginData, password: '***' } })
|
2025-08-10 22:42:56 +08:00
|
|
|
|
|
|
|
|
|
// 调用后端API
|
2025-08-15 13:21:10 +08:00
|
|
|
|
const response = await ApiRequest.post<any>('/biz/user/login', loginData)
|
2025-08-10 22:42:56 +08:00
|
|
|
|
|
|
|
|
|
console.log('🔍 Login API Response:', response)
|
|
|
|
|
console.log('🔍 Response Code:', response.code)
|
|
|
|
|
console.log('🔍 Response Data:', response.data)
|
|
|
|
|
console.log('🔍 Response Data Type:', typeof response.data)
|
|
|
|
|
|
|
|
|
|
// 处理响应格式问题 - 如果code和message是undefined,可能是响应拦截器的问题
|
|
|
|
|
let actualCode = response.code
|
|
|
|
|
let actualMessage = response.message
|
|
|
|
|
let actualData = response.data
|
|
|
|
|
|
|
|
|
|
// 如果response.code是undefined,检查response.data是否包含完整的API响应
|
|
|
|
|
if (actualCode === undefined && actualData && typeof actualData === 'object') {
|
2025-08-15 13:21:10 +08:00
|
|
|
|
// 检查是否是标准的jeecg-boot响应格式 (success, code, message, result)
|
|
|
|
|
if ('success' in actualData && 'code' in actualData && 'message' in actualData && 'result' in actualData) {
|
|
|
|
|
actualCode = actualData.code
|
|
|
|
|
actualMessage = actualData.message
|
|
|
|
|
actualData = actualData.result
|
|
|
|
|
console.log('🔧 修正后的响应 (jeecg-boot格式):', { code: actualCode, message: actualMessage, data: actualData })
|
|
|
|
|
}
|
|
|
|
|
// 检查是否是其他格式 (code, message, data)
|
|
|
|
|
else if ('code' in actualData && 'message' in actualData && 'data' in actualData) {
|
2025-08-10 22:42:56 +08:00
|
|
|
|
actualCode = actualData.code
|
|
|
|
|
actualMessage = actualData.message
|
|
|
|
|
actualData = actualData.data
|
2025-08-15 13:21:10 +08:00
|
|
|
|
console.log('🔧 修正后的响应 (标准格式):', { code: actualCode, message: actualMessage, data: actualData })
|
2025-08-10 22:42:56 +08:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 检查响应格式并适配
|
|
|
|
|
if (actualCode === 200 || actualCode === 0) {
|
|
|
|
|
// 如果后端返回的是完整的用户信息格式(mock数据格式)
|
|
|
|
|
if (actualData && actualData.user && actualData.token) {
|
|
|
|
|
return {
|
|
|
|
|
code: actualCode,
|
|
|
|
|
message: actualMessage,
|
|
|
|
|
data: actualData
|
|
|
|
|
} as ApiResponse<LoginResponse>
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-15 13:21:10 +08:00
|
|
|
|
// 如果后端返回的是jeecg-boot格式(只包含token)
|
|
|
|
|
if (actualData && actualData.token) {
|
2025-08-10 22:42:56 +08:00
|
|
|
|
const adaptedResponse: ApiResponse<LoginResponse> = {
|
|
|
|
|
code: actualCode,
|
|
|
|
|
message: actualMessage || '登录成功',
|
|
|
|
|
data: {
|
|
|
|
|
user: {
|
|
|
|
|
id: 1, // 真实API没有返回用户ID,使用默认值
|
|
|
|
|
email: data.email || '',
|
|
|
|
|
phone: data.phone || '',
|
2025-08-15 13:21:10 +08:00
|
|
|
|
username: data.email || data.phone || '',
|
2025-08-10 22:42:56 +08:00
|
|
|
|
nickname: '用户',
|
|
|
|
|
avatar: '',
|
|
|
|
|
role: 'student',
|
|
|
|
|
status: 'active',
|
|
|
|
|
createdAt: new Date().toISOString(),
|
|
|
|
|
updatedAt: new Date().toISOString()
|
|
|
|
|
},
|
|
|
|
|
token: actualData.token,
|
|
|
|
|
refreshToken: '', // 真实API没有返回refreshToken
|
|
|
|
|
expiresIn: 3600 // 默认1小时过期
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return adaptedResponse
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 如果后端返回的是简化格式(BackendLoginResponse)
|
|
|
|
|
if (actualData && actualData.token) {
|
|
|
|
|
const adaptedResponse: ApiResponse<LoginResponse> = {
|
|
|
|
|
code: actualCode,
|
|
|
|
|
message: actualMessage || '登录成功',
|
|
|
|
|
data: {
|
|
|
|
|
user: {
|
|
|
|
|
id: actualData.id || 1,
|
|
|
|
|
email: data.email || '',
|
|
|
|
|
phone: data.phone || '',
|
|
|
|
|
username: data.phone || data.email?.split('@')[0] || 'user',
|
|
|
|
|
nickname: '用户',
|
|
|
|
|
avatar: '',
|
|
|
|
|
role: 'student',
|
|
|
|
|
status: 'active',
|
|
|
|
|
createdAt: new Date().toISOString(),
|
|
|
|
|
updatedAt: new Date().toISOString()
|
|
|
|
|
},
|
|
|
|
|
token: actualData.token,
|
|
|
|
|
refreshToken: actualData.refreshToken || '',
|
|
|
|
|
expiresIn: actualData.expiresIn || 3600
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return adaptedResponse
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 如果只有token字段,直接使用token
|
|
|
|
|
if (typeof actualData === 'string') {
|
|
|
|
|
const adaptedResponse: ApiResponse<LoginResponse> = {
|
|
|
|
|
code: actualCode,
|
|
|
|
|
message: actualMessage || '登录成功',
|
|
|
|
|
data: {
|
|
|
|
|
user: {
|
|
|
|
|
id: 1,
|
|
|
|
|
email: data.email || '',
|
|
|
|
|
phone: data.phone || '',
|
|
|
|
|
username: data.phone || data.email?.split('@')[0] || 'user',
|
|
|
|
|
nickname: '用户',
|
|
|
|
|
avatar: '',
|
|
|
|
|
role: 'student',
|
|
|
|
|
status: 'active',
|
|
|
|
|
createdAt: new Date().toISOString(),
|
|
|
|
|
updatedAt: new Date().toISOString()
|
|
|
|
|
},
|
|
|
|
|
token: actualData,
|
|
|
|
|
refreshToken: '',
|
|
|
|
|
expiresIn: 3600
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return adaptedResponse
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 如果data是null或undefined,但响应成功,可能是某些API的特殊情况
|
|
|
|
|
if (!actualData) {
|
|
|
|
|
console.warn('⚠️ API返回成功但data为空,可能需要检查API实现')
|
|
|
|
|
throw new Error('登录成功但未返回用户信息')
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 尝试处理其他可能的响应格式
|
|
|
|
|
console.warn('⚠️ 未知的响应格式,尝试通用处理:', actualData)
|
|
|
|
|
|
|
|
|
|
// 如果data是对象但不包含预期字段,尝试创建默认响应
|
|
|
|
|
if (typeof actualData === 'object') {
|
|
|
|
|
const adaptedResponse: ApiResponse<LoginResponse> = {
|
|
|
|
|
code: actualCode,
|
|
|
|
|
message: actualMessage || '登录成功',
|
|
|
|
|
data: {
|
|
|
|
|
user: {
|
|
|
|
|
id: actualData.id || actualData.userId || 1,
|
|
|
|
|
email: data.email || '',
|
|
|
|
|
phone: data.phone || '',
|
|
|
|
|
username: data.phone || data.email?.split('@')[0] || 'user',
|
|
|
|
|
nickname: actualData.nickname || actualData.name || '用户',
|
|
|
|
|
avatar: actualData.avatar || '',
|
|
|
|
|
role: actualData.role || 'student',
|
|
|
|
|
status: 'active',
|
|
|
|
|
createdAt: new Date().toISOString(),
|
|
|
|
|
updatedAt: new Date().toISOString()
|
|
|
|
|
},
|
|
|
|
|
token: actualData.accessToken || actualData.access_token || 'temp_token_' + Date.now(),
|
|
|
|
|
refreshToken: actualData.refreshToken || actualData.refresh_token || '',
|
|
|
|
|
expiresIn: actualData.expiresIn || actualData.expires_in || 3600
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return adaptedResponse
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 如果响应格式不符合预期,抛出错误
|
|
|
|
|
console.error('🚨 Unexpected response format:', {
|
|
|
|
|
code: actualCode,
|
|
|
|
|
message: actualMessage,
|
|
|
|
|
data: actualData,
|
|
|
|
|
dataType: typeof actualData
|
|
|
|
|
})
|
|
|
|
|
throw new Error(actualMessage || '登录响应格式错误')
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('🚨 Login API Error:', error)
|
|
|
|
|
throw error
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 用户注册
|
|
|
|
|
static register(data: RegisterRequest): Promise<ApiResponse<User>> {
|
|
|
|
|
return ApiRequest.post('/auth/register', data)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 用户登出
|
|
|
|
|
static logout(): Promise<ApiResponse<null>> {
|
|
|
|
|
// 尝试多个可能的登出端点
|
|
|
|
|
return ApiRequest.post('/auth/logout').catch(async (error) => {
|
|
|
|
|
// 如果 /auth/logout 失败,尝试其他可能的端点
|
|
|
|
|
if (error.response?.status === 404) {
|
|
|
|
|
try {
|
|
|
|
|
return await ApiRequest.post('/users/logout')
|
|
|
|
|
} catch (secondError) {
|
|
|
|
|
// 如果所有端点都失败,返回成功(客户端登出)
|
|
|
|
|
console.log('所有登出端点都不存在,执行客户端登出')
|
|
|
|
|
return {
|
|
|
|
|
code: 200,
|
|
|
|
|
message: '登出成功',
|
|
|
|
|
data: null
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
throw error
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 刷新Token
|
|
|
|
|
static refreshToken(refreshToken: string): Promise<ApiResponse<LoginResponse>> {
|
|
|
|
|
return ApiRequest.post('/auth/refresh', { refreshToken })
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 获取当前用户信息
|
|
|
|
|
static getCurrentUser(): Promise<ApiResponse<User>> {
|
2025-08-15 13:21:10 +08:00
|
|
|
|
return ApiRequest.get('/users/info')
|
2025-08-10 22:42:56 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 更新用户资料
|
|
|
|
|
static updateProfile(data: Partial<UserProfile>): Promise<ApiResponse<User>> {
|
|
|
|
|
return ApiRequest.put('/auth/profile', data)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 修改密码
|
|
|
|
|
static changePassword(data: {
|
|
|
|
|
oldPassword: string
|
|
|
|
|
newPassword: string
|
|
|
|
|
confirmPassword: string
|
|
|
|
|
}): Promise<ApiResponse<null>> {
|
|
|
|
|
return ApiRequest.post('/auth/change-password', data)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 忘记密码 - 发送重置邮件
|
|
|
|
|
static forgotPassword(email: string): Promise<ApiResponse<null>> {
|
|
|
|
|
return ApiRequest.post('/auth/forgot-password', { email })
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 重置密码
|
|
|
|
|
static resetPassword(data: {
|
|
|
|
|
token: string
|
|
|
|
|
password: string
|
|
|
|
|
confirmPassword: string
|
|
|
|
|
}): Promise<ApiResponse<null>> {
|
|
|
|
|
return ApiRequest.post('/auth/reset-password', data)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 发送邮箱验证码
|
|
|
|
|
static sendEmailVerification(email: string): Promise<ApiResponse<null>> {
|
|
|
|
|
return ApiRequest.post('/auth/send-email-verification', { email })
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 验证邮箱
|
|
|
|
|
static verifyEmail(data: {
|
|
|
|
|
email: string
|
|
|
|
|
code: string
|
|
|
|
|
}): Promise<ApiResponse<null>> {
|
|
|
|
|
return ApiRequest.post('/auth/verify-email', data)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 发送手机验证码
|
|
|
|
|
static sendSmsVerification(phone: string): Promise<ApiResponse<null>> {
|
|
|
|
|
return ApiRequest.post('/auth/send-sms-verification', { phone })
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 验证手机号
|
|
|
|
|
static verifyPhone(data: {
|
|
|
|
|
phone: string
|
|
|
|
|
code: string
|
|
|
|
|
}): Promise<ApiResponse<null>> {
|
|
|
|
|
return ApiRequest.post('/auth/verify-phone', data)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 绑定第三方账号
|
|
|
|
|
static bindThirdParty(data: {
|
|
|
|
|
provider: 'wechat' | 'qq' | 'weibo' | 'github'
|
|
|
|
|
code: string
|
|
|
|
|
state?: string
|
|
|
|
|
}): Promise<ApiResponse<User>> {
|
|
|
|
|
return ApiRequest.post('/auth/bind-third-party', data)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 解绑第三方账号
|
|
|
|
|
static unbindThirdParty(provider: string): Promise<ApiResponse<null>> {
|
|
|
|
|
return ApiRequest.delete(`/auth/unbind-third-party/${provider}`)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 获取第三方登录URL
|
|
|
|
|
static getThirdPartyLoginUrl(provider: string, redirectUrl?: string): Promise<ApiResponse<{ url: string }>> {
|
|
|
|
|
return ApiRequest.get(`/auth/third-party-login-url/${provider}`, { redirectUrl })
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 第三方登录回调
|
|
|
|
|
static thirdPartyLoginCallback(data: {
|
|
|
|
|
provider: string
|
|
|
|
|
code: string
|
|
|
|
|
state?: string
|
|
|
|
|
}): Promise<ApiResponse<LoginResponse>> {
|
|
|
|
|
return ApiRequest.post('/auth/third-party-login-callback', data)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 上传头像
|
|
|
|
|
static uploadAvatar(file: File, onProgress?: (progress: number) => void): Promise<ApiResponse<{ url: string }>> {
|
|
|
|
|
return ApiRequest.upload('/auth/upload-avatar', file, onProgress)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 删除账号
|
|
|
|
|
static deleteAccount(password: string): Promise<ApiResponse<null>> {
|
|
|
|
|
return ApiRequest.post('/auth/delete-account', { password })
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 获取账号安全信息
|
|
|
|
|
static getSecurityInfo(): Promise<ApiResponse<{
|
|
|
|
|
hasPassword: boolean
|
|
|
|
|
hasEmail: boolean
|
|
|
|
|
hasPhone: boolean
|
|
|
|
|
emailVerified: boolean
|
|
|
|
|
phoneVerified: boolean
|
|
|
|
|
twoFactorEnabled: boolean
|
|
|
|
|
thirdPartyAccounts: Array<{
|
|
|
|
|
provider: string
|
|
|
|
|
nickname: string
|
|
|
|
|
avatar?: string
|
|
|
|
|
bindTime: string
|
|
|
|
|
}>
|
|
|
|
|
loginHistory: Array<{
|
|
|
|
|
ip: string
|
|
|
|
|
location: string
|
|
|
|
|
device: string
|
|
|
|
|
loginTime: string
|
|
|
|
|
}>
|
|
|
|
|
}>> {
|
|
|
|
|
return ApiRequest.get('/auth/security-info')
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 启用两步验证
|
|
|
|
|
static enableTwoFactor(): Promise<ApiResponse<{
|
|
|
|
|
qrCode: string
|
|
|
|
|
secret: string
|
|
|
|
|
backupCodes: string[]
|
|
|
|
|
}>> {
|
|
|
|
|
return ApiRequest.post('/auth/enable-two-factor')
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 确认启用两步验证
|
|
|
|
|
static confirmTwoFactor(code: string): Promise<ApiResponse<{
|
|
|
|
|
backupCodes: string[]
|
|
|
|
|
}>> {
|
|
|
|
|
return ApiRequest.post('/auth/confirm-two-factor', { code })
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 禁用两步验证
|
|
|
|
|
static disableTwoFactor(data: {
|
|
|
|
|
password: string
|
|
|
|
|
code: string
|
|
|
|
|
}): Promise<ApiResponse<null>> {
|
|
|
|
|
return ApiRequest.post('/auth/disable-two-factor', data)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 生成新的备用码
|
|
|
|
|
static generateBackupCodes(): Promise<ApiResponse<{
|
|
|
|
|
backupCodes: string[]
|
|
|
|
|
}>> {
|
|
|
|
|
return ApiRequest.post('/auth/generate-backup-codes')
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 验证两步验证码
|
|
|
|
|
static verifyTwoFactor(code: string): Promise<ApiResponse<null>> {
|
|
|
|
|
return ApiRequest.post('/auth/verify-two-factor', { code })
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export default AuthApi
|