Files
frontend/src/services/authService.ts
lafay a005fb0a15 feat: add QR code login and enhance chat experience
- Add QR code login flow with scanner and confirmation screens
- Implement swipe-to-reply in chat with SwipeableMessageBubble
- Replace FlatList with FlashList for chat performance optimization
- Add Schedule stack navigator with CourseDetail screen support
- Configure deep linking for QR code login (carrotbbs://qrcode/login/:sessionId)
- Update branding from 胡萝卜 to 萝卜社区
- Display dynamic app version in settings
2026-03-20 19:28:42 +08:00

495 lines
14 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 认证服务
* 处理用户登录、注册、登出等认证功能
*/
import { api } from './api';
import { User } from '../types';
export function resolveAuthApiError(error: any, fallback = '操作失败,请稍后重试'): string {
const code: number = error?.code ?? 0;
const msg: string = String(error?.message ?? '').toLowerCase();
if (msg.includes('network request failed') || msg.includes('failed to fetch')) {
return '网络连接失败,请检查网络后重试';
}
if (code === 429 || msg.includes('too frequently')) {
return '验证码发送过于频繁,请稍后再试';
}
if (msg.includes('verification code expired')) {
return '验证码已过期,请重新获取';
}
if (msg.includes('invalid verification code')) {
return '验证码错误,请重新输入';
}
if (msg.includes('email already exists')) {
return '该邮箱已被注册,请直接登录';
}
if (msg.includes('email not bound')) {
return '当前账号未绑定邮箱,请先完成邮箱绑定验证';
}
if (msg.includes('user not found')) {
return '该邮箱尚未注册';
}
if (msg.includes('email service unavailable')) {
return '邮件服务暂不可用,请稍后再试';
}
if (msg.includes('invalid email')) {
return '邮箱格式不正确';
}
if (msg.includes('invalid username or password')) {
return '账号或密码错误';
}
if (typeof error?.message === 'string' && error.message.trim().length > 0) {
return error.message;
}
return fallback;
}
// 登录请求参数
export interface LoginRequest {
username?: string;
account?: string;
password: string;
}
// 注册请求参数
export interface RegisterRequest {
username: string;
password: string;
nickname: string;
email: string;
verification_code: string;
phone?: string;
}
export interface SendEmailCodeRequest {
email: string;
}
export interface ResetPasswordRequest {
email: string;
verification_code: string;
new_password: string;
}
export interface VerifyCurrentUserEmailRequest {
email: string;
verification_code: string;
}
// 认证响应
export interface AuthResponse {
token: string;
refreshToken?: string;
refresh_token?: string; // 兼容后端的snake_case
user: User;
}
// 刷新Token响应
export interface RefreshTokenResponse {
token: string;
refreshToken?: string;
refresh_token?: string; // 兼容后端的snake_case
}
// 用户信息响应 - API直接返回User对象不需要包装
export type UserResponse = User;
// 更新用户信息请求
export interface UpdateUserRequest {
nickname?: string;
avatar?: string;
bio?: string;
website?: string;
location?: string;
phone?: string;
email?: string;
}
export interface BlockedUserListResponse {
list: User[];
total: number;
page: number;
page_size: number;
total_pages: number;
}
export interface BlockStatusResponse {
is_blocked: boolean;
}
// 认证服务类
// 职责:纯 HTTP 层 + Token 管理,不涉及 SQLite
// SQLite 的读写编排由各领域 Manager/store 统一负责
class AuthService {
// ── 内部辅助:从服务器获取用户 ──
private async refreshCurrentUserFromServer(): Promise<User | null> {
try {
const response = await api.get<UserResponse>('/users/me');
return response.data;
} catch (error) {
console.error('[AuthService] 获取当前用户失败:', error);
return null;
}
}
private async refreshUserByIdFromServer(userId: string): Promise<User | null> {
try {
const response = await api.get<UserResponse>(`/users/${userId}`);
return response.data;
} catch (error) {
console.error('[AuthService] 获取用户信息失败:', error);
return null;
}
}
// ── 公开 API仅做 HTTP 请求 + Token 存储,不碰 SQLite ──
// 用户登录(只管 API + TokenDB 操作交给 authStore
async login(data: LoginRequest): Promise<AuthResponse> {
const response = await api.post<AuthResponse>('/auth/login', data);
if (response.data.token) {
await api.setToken(response.data.token);
}
const refreshToken = response.data.refresh_token || response.data.refreshToken;
if (refreshToken) {
await api.setRefreshToken(refreshToken);
}
return response.data;
}
// 用户注册(只管 API + TokenDB 操作交给 authStore
async register(data: RegisterRequest): Promise<AuthResponse> {
const response = await api.post<AuthResponse>('/auth/register', data);
if (response.data.token) {
await api.setToken(response.data.token);
}
const refreshToken = response.data.refresh_token || response.data.refreshToken;
if (refreshToken) {
await api.setRefreshToken(refreshToken);
}
return response.data;
}
// 发送注册验证码
async sendRegisterCode(email: string): Promise<boolean> {
const response = await api.post<{ success: boolean }>('/auth/register/send-code', { email } as SendEmailCodeRequest);
return response.code === 0;
}
// 发送找回密码验证码
async sendPasswordResetCode(email: string): Promise<boolean> {
const response = await api.post<{ success: boolean }>('/auth/password/send-code', { email } as SendEmailCodeRequest);
return response.code === 0;
}
// 找回密码重置
async resetPassword(data: ResetPasswordRequest): Promise<boolean> {
const response = await api.post<{ success: boolean }>('/auth/password/reset', data);
return response.code === 0;
}
// 已登录用户发送邮箱验证验证码
async sendCurrentUserEmailVerifyCode(email: string): Promise<boolean> {
const response = await api.post<{ success: boolean }>('/users/me/email/send-code', { email } as SendEmailCodeRequest);
return response.code === 0;
}
// 已登录用户提交邮箱验证码
async verifyCurrentUserEmail(data: VerifyCurrentUserEmailRequest): Promise<boolean> {
const response = await api.post<{ success: boolean }>('/users/me/email/verify', data);
return response.code === 0;
}
// 用户登出(只管 API + TokenDB/缓存清理交给 authStore
async logout(): Promise<void> {
try {
await api.post('/auth/logout');
} catch (error) {
console.error('[AuthService] 登出 API 失败:', error);
} finally {
await api.clearToken();
}
}
// 纯 API 获取当前用户(无任何 DB 依赖,供冷启动校验 Token 时使用)
async fetchCurrentUserFromAPI(): Promise<User | null> {
try {
const response = await api.get<UserResponse>('/users/me');
return response.data;
} catch (error) {
console.error('[AuthService] fetchCurrentUserFromAPI 失败:', error);
return null;
}
}
// 刷新Token
async refreshToken(): Promise<RefreshTokenResponse | null> {
try {
const response = await api.post<RefreshTokenResponse>('/auth/refresh');
if (response.data.token) {
await api.setToken(response.data.token);
}
if (response.data.refreshToken) {
await api.setRefreshToken(response.data.refreshToken);
}
return response.data;
} catch (error) {
console.error('刷新Token失败:', error);
await api.clearToken();
return null;
}
}
// 获取当前用户信息(兼容旧调用,始终走服务端)
async getCurrentUser(): Promise<User | null> {
return this.refreshCurrentUserFromServer();
}
// 强制从服务器获取当前用户信息(别名,保留兼容)
async fetchCurrentUserFresh(): Promise<User | null> {
return this.fetchCurrentUserFromAPI();
}
async fetchUserByIdFromAPI(userId: string): Promise<User | null> {
return this.refreshUserByIdFromServer(userId);
}
// 获取用户信息根据用户ID
async getUserById(userId: string, forceRefresh = false): Promise<User | null> {
void forceRefresh; // 兼容旧签名:缓存由 userManager 统一编排
return this.refreshUserByIdFromServer(userId);
}
// 更新用户信息
async updateUser(data: UpdateUserRequest): Promise<User | null> {
try {
const response = await api.put<UserResponse>('/users/me', data);
return response.data;
} catch (error) {
console.error('更新用户信息失败:', error);
return null;
}
}
// 修改密码
async sendChangePasswordCode(): Promise<boolean> {
const response = await api.post<{ success: boolean }>('/users/change-password/send-code');
return response.code === 0;
}
// 修改密码(需要邮箱验证码)
async changePassword(oldPassword: string, newPassword: string, verificationCode: string): Promise<boolean> {
try {
const response = await api.post('/users/change-password', {
old_password: oldPassword,
new_password: newPassword,
verification_code: verificationCode,
});
return response.code === 0;
} catch (error) {
console.error('修改密码失败:', error);
return false;
}
}
// 检查用户名是否可用
async checkUsernameAvailable(username: string): Promise<boolean> {
try {
const response = await api.get<{ available: boolean }>('/auth/check-username', {
username,
});
return response.data.available;
} catch (error) {
console.error('检查用户名失败:', error);
return false;
}
}
// 关注用户
async followUser(userId: string): Promise<boolean> {
try {
const response = await api.post(`/users/${userId}/follow`);
return response.code === 0;
} catch (error) {
console.error('关注用户失败:', error);
return false;
}
}
// 取消关注用户
async unfollowUser(userId: string): Promise<boolean> {
try {
const response = await api.delete(`/users/${userId}/follow`);
return response.code === 0;
} catch (error) {
console.error('取消关注用户失败:', error);
return false;
}
}
// 获取用户关注列表
async getFollowingList(userId: string, page = 1, pageSize = 20): Promise<User[]> {
try {
const response = await api.get<{ list: User[] }>(`/users/${userId}/following`, {
page,
page_size: pageSize,
});
return response.data.list;
} catch (error) {
console.error('获取关注列表失败:', error);
return [];
}
}
// 获取用户粉丝列表
async getFollowersList(userId: string, page = 1, pageSize = 20): Promise<User[]> {
try {
const response = await api.get<{ list: User[] }>(`/users/${userId}/followers`, {
page,
page_size: pageSize,
});
return response.data.list;
} catch (error) {
console.error('获取粉丝列表失败:', error);
return [];
}
}
// 拉黑用户
async blockUser(userId: string): Promise<boolean> {
try {
const response = await api.post(`/users/${userId}/block`);
return response.code === 0;
} catch (error) {
console.error('拉黑用户失败:', error);
return false;
}
}
// 取消拉黑
async unblockUser(userId: string): Promise<boolean> {
try {
const response = await api.delete(`/users/${userId}/block`);
return response.code === 0;
} catch (error) {
console.error('取消拉黑失败:', error);
return false;
}
}
// 获取黑名单
async getBlockedUsers(page = 1, pageSize = 20): Promise<BlockedUserListResponse> {
try {
const response = await api.get<BlockedUserListResponse>('/users/blocks', {
page,
page_size: pageSize,
});
return response.data;
} catch (error) {
console.error('获取黑名单失败:', error);
return {
list: [],
total: 0,
page,
page_size: pageSize,
total_pages: 0,
};
}
}
// 获取拉黑状态
async getBlockStatus(userId: string): Promise<boolean> {
try {
const response = await api.get<BlockStatusResponse>(`/users/${userId}/block-status`);
return !!response.data.is_blocked;
} catch (error) {
console.error('获取拉黑状态失败:', error);
return false;
}
}
// 搜索用户
async searchUsers(keyword: string, page = 1, pageSize = 20): Promise<{ list: User[]; total: number }> {
try {
const response = await api.get<{ list: User[]; total: number }>('/users/search', {
keyword,
page,
page_size: pageSize,
});
return response.data;
} catch (error) {
console.error('搜索用户失败:', error);
return { list: [], total: 0 };
}
}
}
// ── 二维码登录相关接口 ──
export interface QRCodeSession {
session_id: string;
qrcode_url: string;
expires_in: number;
expires_at: number;
}
export interface ScanResponse {
user: {
id: string;
nickname: string;
avatar: string;
};
}
// 二维码登录 API扩展 AuthService
export const qrcodeApi = {
/**
* 获取二维码
*/
getQRCode: async (): Promise<QRCodeSession> => {
const response = await api.get<QRCodeSession>('/auth/qrcode');
return response.data;
},
/**
* 扫描二维码
*/
scan: async (sessionId: string): Promise<ScanResponse> => {
const response = await api.post<ScanResponse>('/auth/qrcode/scan', {
session_id: sessionId,
});
return response.data;
},
/**
* 确认登录
*/
confirm: async (sessionId: string): Promise<{ success: boolean }> => {
const response = await api.post<{ success: boolean }>('/auth/qrcode/confirm', {
session_id: sessionId,
});
return response.data;
},
/**
* 取消登录
*/
cancel: async (sessionId: string): Promise<{ success: boolean }> => {
const response = await api.post<{ success: boolean }>('/auth/qrcode/cancel', {
session_id: sessionId,
});
return response.data;
},
};
// 导出认证服务实例
export const authService = new AuthService();