diff --git a/backend/services/auth-service/src/application/services/auth.service.ts b/backend/services/auth-service/src/application/services/auth.service.ts index a1b0cfb..361c35a 100644 --- a/backend/services/auth-service/src/application/services/auth.service.ts +++ b/backend/services/auth-service/src/application/services/auth.service.ts @@ -77,8 +77,9 @@ export class AuthService { // Verify SMS code await this.smsService.verifyCode(dto.phone, dto.smsCode, SmsVerificationType.REGISTER); - // Hash password - const password = await Password.create(dto.password); + // Hash password (if not provided, generate a random one for SMS-only registration) + const rawPassword = dto.password || require('crypto').randomBytes(16).toString('hex'); + const password = await Password.create(rawPassword); // Create user const user = await this.userRepo.create({ diff --git a/backend/services/auth-service/src/interface/http/dto/register.dto.ts b/backend/services/auth-service/src/interface/http/dto/register.dto.ts index 2bb9c7e..f4d3464 100644 --- a/backend/services/auth-service/src/interface/http/dto/register.dto.ts +++ b/backend/services/auth-service/src/interface/http/dto/register.dto.ts @@ -12,11 +12,12 @@ export class RegisterDto { @Length(6, 6, { message: '验证码必须为6位数字' }) smsCode: string; - @ApiProperty({ description: '登录密码 (8-128位)', example: 'Password123!' }) + @ApiPropertyOptional({ description: '登录密码 (8-128位), 不传则后端自动生成', example: 'Password123!' }) + @IsOptional() @IsString() @MinLength(8) @MaxLength(128) - password: string; + password?: string; @ApiPropertyOptional({ description: '昵称', example: 'John' }) @IsOptional() diff --git a/frontend/admin-app/lib/core/services/auth_service.dart b/frontend/admin-app/lib/core/services/auth_service.dart index c01f823..6d8ae7f 100644 --- a/frontend/admin-app/lib/core/services/auth_service.dart +++ b/frontend/admin-app/lib/core/services/auth_service.dart @@ -52,10 +52,10 @@ class AuthService { } /// 密码登录 - Future loginByPassword(String email, String password) async { + Future loginByPassword(String identifier, String password) async { try { final response = await _apiClient.post('/api/v1/auth/login', data: { - 'email': email, + 'identifier': identifier, 'password': password, }); final data = response.data is Map ? response.data : {}; @@ -104,9 +104,19 @@ class LoginResult { }); factory LoginResult.fromJson(Map json) { + // 后端返回 { user, tokens: { accessToken, refreshToken, expiresIn } } + final tokens = json['tokens'] as Map?; + if (tokens != null) { + return LoginResult( + accessToken: tokens['accessToken'] as String? ?? '', + refreshToken: tokens['refreshToken'] as String?, + user: json['user'] as Map?, + ); + } + // 兼容 refresh 等直接返回 tokens 的接口 return LoginResult( - accessToken: json['accessToken'] ?? '', - refreshToken: json['refreshToken'], + accessToken: json['accessToken'] as String? ?? '', + refreshToken: json['refreshToken'] as String?, user: json['user'] as Map?, ); } diff --git a/frontend/admin-web/src/lib/auth-context.tsx b/frontend/admin-web/src/lib/auth-context.tsx index add5ab5..f810940 100644 --- a/frontend/admin-web/src/lib/auth-context.tsx +++ b/frontend/admin-web/src/lib/auth-context.tsx @@ -41,14 +41,17 @@ export function AuthProvider({ children }: { children: React.ReactNode }) { }, []); const login = useCallback(async (email: string, password: string) => { - const result = await apiClient.post<{ accessToken: string; user: AdminUser }>( + const result = await apiClient.post<{ + user: AdminUser; + tokens: { accessToken: string; refreshToken: string; expiresIn: number }; + }>( '/api/v1/auth/login', - { email, password }, + { identifier: email, password }, ); - const { accessToken, user: adminUser } = result; - localStorage.setItem('admin_token', accessToken); + const { tokens, user: adminUser } = result; + localStorage.setItem('admin_token', tokens.accessToken); localStorage.setItem('admin_user', JSON.stringify(adminUser)); - setToken(accessToken); + setToken(tokens.accessToken); setUser(adminUser); }, []); diff --git a/frontend/miniapp/src/services/auth.ts b/frontend/miniapp/src/services/auth.ts index ad3ce2f..a7927a7 100644 --- a/frontend/miniapp/src/services/auth.ts +++ b/frontend/miniapp/src/services/auth.ts @@ -1,22 +1,40 @@ import { post } from '../utils/request'; -interface LoginResult { +/** 后端返回的 auth 结构 (request.ts 已自动解包外层 data) */ +interface AuthResponse { + user: { id: string; phone?: string; nickname?: string; kycLevel: number }; + tokens: { accessToken: string; refreshToken: string; expiresIn: number }; +} + +/** 前端使用的扁平结构 */ +export interface LoginResult { accessToken: string; refreshToken: string; user: { id: string; phone?: string; nickname?: string; kycLevel: number }; } +/** 将后端嵌套结构转为前端扁平结构 */ +function toLoginResult(resp: AuthResponse): LoginResult { + return { + accessToken: resp.tokens.accessToken, + refreshToken: resp.tokens.refreshToken, + user: resp.user, + }; +} + /** SMS 验证码类型 */ export type SmsCodeType = 'REGISTER' | 'LOGIN' | 'RESET_PASSWORD' | 'CHANGE_PHONE'; /** 手机号+验证码登录 */ -export function loginByPhone(phone: string, smsCode: string, deviceInfo?: string) { - return post('/api/v1/auth/login-phone', { phone, smsCode, ...(deviceInfo ? { deviceInfo } : {}) }, false); +export async function loginByPhone(phone: string, smsCode: string, deviceInfo?: string) { + const resp = await post('/api/v1/auth/login-phone', { phone, smsCode, ...(deviceInfo ? { deviceInfo } : {}) }, false); + return toLoginResult(resp); } /** 密码登录 */ -export function loginByPassword(identifier: string, password: string) { - return post('/api/v1/auth/login', { identifier, password }, false); +export async function loginByPassword(identifier: string, password: string) { + const resp = await post('/api/v1/auth/login', { identifier, password }, false); + return toLoginResult(resp); } /** 发送短信验证码 */ @@ -25,17 +43,19 @@ export function sendSmsCode(phone: string, type: SmsCodeType = 'LOGIN') { } /** 微信登录 */ -export function loginByWechat(code: string) { - return post('/api/v1/auth/login-wechat', { code }, false); +export async function loginByWechat(code: string) { + const resp = await post('/api/v1/auth/login-wechat', { code }, false); + return toLoginResult(resp); } /** 注册 */ -export function register(phone: string, smsCode: string, password?: string, nickname?: string) { - return post('/api/v1/auth/register', { +export async function register(phone: string, smsCode: string, password?: string, nickname?: string) { + const resp = await post('/api/v1/auth/register', { phone, smsCode, ...(password ? { password } : {}), ...(nickname ? { nickname } : {}), }, false); + return toLoginResult(resp); } /** 重置密码 */ diff --git a/frontend/miniapp/src/store/auth.ts b/frontend/miniapp/src/store/auth.ts index d50f103..7230b76 100644 --- a/frontend/miniapp/src/store/auth.ts +++ b/frontend/miniapp/src/store/auth.ts @@ -11,10 +11,9 @@ export interface UserProfile { avatarUrl?: string; } -interface LoginResult { - accessToken: string; - refreshToken: string; +interface AuthResponse { user: UserProfile; + tokens: { accessToken: string; refreshToken: string; expiresIn: number }; } /** @@ -46,10 +45,10 @@ export const authStore = { /** 手机号+验证码登录 */ async loginByPhone(phone: string, smsCode: string): Promise { - const result = await post('/api/v1/auth/login-phone', { phone, smsCode }, false); - Taro.setStorageSync(config.TOKEN_KEY, result.accessToken); - Taro.setStorageSync(config.USER_KEY, JSON.stringify(result.user)); - _user = result.user; + const resp = await post('/api/v1/auth/login-phone', { phone, smsCode }, false); + Taro.setStorageSync(config.TOKEN_KEY, resp.tokens.accessToken); + Taro.setStorageSync(config.USER_KEY, JSON.stringify(resp.user)); + _user = resp.user; notify(); }, @@ -61,10 +60,10 @@ export const authStore = { /** 微信一键登录 */ async loginByWechat(): Promise { const { code } = await Taro.login(); - const result = await post('/api/v1/auth/login-wechat', { code }, false); - Taro.setStorageSync(config.TOKEN_KEY, result.accessToken); - Taro.setStorageSync(config.USER_KEY, JSON.stringify(result.user)); - _user = result.user; + const resp = await post('/api/v1/auth/login-wechat', { code }, false); + Taro.setStorageSync(config.TOKEN_KEY, resp.tokens.accessToken); + Taro.setStorageSync(config.USER_KEY, JSON.stringify(resp.user)); + _user = resp.user; notify(); },