refactor(game): 重构项目结构,优化链路, 移动端适配

- 移除 useGameBoardVm 数据层实施说明文档
- 移除核心玩法与前端规则摘要文档
- 移除游戏模块数据与界面分层第一阶段实施稿文档
- 清理与数据层重构相关的技术方案说明
- 删除关于 PC 和 Mobile 界面分离的设计规划
- 移除 view-model hooks 架构设计相关内容
This commit is contained in:
JiaJun
2026-06-03 17:21:13 +08:00
parent 3efcb3bba6
commit bfb4b76611
129 changed files with 4534 additions and 4227 deletions

233
src/api/auth-api.ts Normal file
View File

@@ -0,0 +1,233 @@
import {
API_SUCCESS_CODE,
AUTH_ENDPOINTS,
AUTH_SKIP_REFRESH_CONTEXT_KEY,
SMS_SEND_EVENT_REGISTER,
} from '@/constants'
import { api } from '@/lib/api/api-client'
import { ApiError } from '@/lib/api/api-error'
import {
mergeAuthUsers,
normalizeAuthSession,
normalizeAuthUserProfile,
normalizeRefreshAuthSession,
} from '@/lib/auth/auth-normalizers'
import { getAuthDeviceId } from '@/store/auth'
import type {
ApiResponse,
AuthSessionDto,
AuthSessionInput,
AuthUserProfileDto,
LoginPayload,
LoginRequestDto,
LogoutPayload,
LogoutRequestDto,
RefreshTokenDto,
RefreshTokenRequestDto,
RegisterPayload,
RegisterRequestDto,
SendSmsCodeDto,
SendSmsCodePayload,
SendSmsCodeRequestDto,
SendSmsCodeResult,
} from '@/type'
const shouldLogAuthLifecycle =
import.meta.env.VITE_ENABLE_REQUEST_LOG === 'true'
function unwrapEnvelope<T>(
response: ApiResponse<T>,
fallbackErrorKey = 'auth.errors.requestFailed',
) {
if (response.code === API_SUCCESS_CODE) {
return response.data
}
throw new ApiError({
data: response,
message:
typeof response.msg === 'string' && response.msg.length > 0
? response.msg
: typeof response.message === 'string' && response.message.length > 0
? response.message
: fallbackErrorKey,
})
}
function logAuthSessionExpiry(action: string, session: AuthSessionInput) {
if (!shouldLogAuthLifecycle || !session.accessTokenExpiresAt) {
return
}
console.info(
`[auth] ${action} user-token expires at ${new Date(
session.accessTokenExpiresAt,
).toISOString()} (${session.accessTokenExpiresAt})`,
)
}
async function getCurrentUserProfileByToken(userToken: string) {
const response = await api.post<AuthUserProfileDto>(AUTH_ENDPOINTS.profile, {
headers: {
Authorization: `Bearer ${userToken}`,
'user-token': userToken,
},
})
return normalizeAuthUserProfile(
unwrapEnvelope(
response as ApiResponse<AuthUserProfileDto>,
'auth.errors.requestFailed',
),
)
}
async function buildEnrichedAuthSession(dto: AuthSessionDto) {
const session = normalizeAuthSession(dto)
try {
const profileUser = await getCurrentUserProfileByToken(session.accessToken)
return {
...session,
currentUser: mergeAuthUsers(session.currentUser, profileUser),
} satisfies AuthSessionInput
} catch {
return session
}
}
export async function loginWithPassword(
payload: LoginPayload,
): Promise<AuthSessionInput> {
const response = await api.post<AuthSessionDto, LoginRequestDto>(
AUTH_ENDPOINTS.login,
{
json: {
device_id: getAuthDeviceId(),
password: payload.password,
username: payload.username,
},
},
)
const session = await buildEnrichedAuthSession(
unwrapEnvelope(
response as ApiResponse<AuthSessionDto>,
'auth.login.errors.submitFailed',
),
)
logAuthSessionExpiry('login', session)
return session
}
export async function logoutWithPassword(
payload: LogoutPayload,
): Promise<void> {
const response = await api.post<null, LogoutRequestDto>(
AUTH_ENDPOINTS.logout,
{
json: {
password: payload.password,
username: payload.username,
},
},
)
unwrapEnvelope(
response as ApiResponse<null>,
'auth.logout.errors.submitFailed',
)
}
export async function registerWithPassword(
payload: RegisterPayload,
): Promise<AuthSessionInput> {
const response = await api.post<AuthSessionDto, RegisterRequestDto>(
AUTH_ENDPOINTS.register,
{
json: {
captcha: payload.captcha,
device_id: getAuthDeviceId(),
invite_code: payload.inviteCode,
password: payload.password,
username: payload.mobile,
},
},
)
const session = await buildEnrichedAuthSession(
unwrapEnvelope(
response as ApiResponse<AuthSessionDto>,
'auth.register.errors.submitFailed',
),
)
logAuthSessionExpiry('register', session)
return session
}
export async function sendSmsCode(
payload: SendSmsCodePayload,
): Promise<SendSmsCodeResult> {
const response = await api.post<SendSmsCodeDto, SendSmsCodeRequestDto>(
AUTH_ENDPOINTS.sendSmsCode,
{
json: {
event: SMS_SEND_EVENT_REGISTER,
mobile: payload.mobile,
},
},
)
const data = unwrapEnvelope(
response as ApiResponse<SendSmsCodeDto>,
'auth.register.sms.errors.submitFailed',
)
return {
expiresIn: data.expires_in,
messageId: data.message_id,
}
}
export async function getCurrentUserProfile() {
const response = await api.post<AuthUserProfileDto>(AUTH_ENDPOINTS.profile)
return normalizeAuthUserProfile(
unwrapEnvelope(
response as ApiResponse<AuthUserProfileDto>,
'auth.errors.requestFailed',
),
)
}
export async function refreshAuthSession(
refreshToken: string,
): Promise<AuthSessionInput | null> {
const response = await api.post<RefreshTokenDto, RefreshTokenRequestDto>(
AUTH_ENDPOINTS.refreshToken,
{
context: {
[AUTH_SKIP_REFRESH_CONTEXT_KEY]: true,
},
json: {
refresh_token: refreshToken,
},
},
)
const session = normalizeRefreshAuthSession(
unwrapEnvelope(
response as ApiResponse<RefreshTokenDto>,
'auth.errors.requestFailed',
),
)
logAuthSessionExpiry('refresh', session)
return session
}