refactor(constants): 提取常量并优化国际化配置

- 创建API相关常量文件,包括响应码、HTTP状态码、请求头等
- 将认证相关常量从auth模块提取到独立的常量文件
- 在API客户端中使用新定义的常量替换硬编码值
- 更新认证API和服务中对常量的引用
- 在国际化配置中创建统一的文案常量以减少重复
- 将认证表单验证规则改为使用常量配置
This commit is contained in:
JiaJun
2026-06-02 12:03:29 +08:00
parent 68cf8c0be2
commit 901ad1c30b
30 changed files with 707 additions and 273 deletions

View File

@@ -2,14 +2,19 @@ import ky, { HTTPError, type Options, TimeoutError } from 'ky'
import {
ACCESS_TOKEN_REFRESH_SKEW_MS,
API_ERROR_MESSAGES,
API_SUCCESS_CODE,
AUTH_REFRESH_ATTEMPTED_CONTEXT_KEY,
AUTH_REFRESH_ENDPOINT,
AUTH_RELOGIN_REQUIRED_CODES,
AUTH_SKIP_REFRESH_CONTEXT_KEY,
AUTH_TOKEN_CACHE_SKEW_MS,
AUTH_TOKEN_ENDPOINT,
AUTHORIZATION_BEARER_PREFIX,
CONTENT_TYPE_JSON,
DEFAULT_REQUEST_ACCEPT_HEADER,
DEFAULT_REQUEST_TIMEOUT_MS,
HTTP_STATUS,
REQUEST_HEADERS,
} from '@/constants'
import type { AuthTokenDto } from '@/features/auth/api/types'
import { getPreferredLanguage, isSupportedLanguage } from '@/i18n'
@@ -65,13 +70,13 @@ function normalizeApiBaseUrl(baseUrl: string | undefined) {
}
async function parseResponseBody(response: Response) {
if (response.status === 204) {
if (response.status === HTTP_STATUS.noContent) {
return null
}
const contentType = response.headers.get('content-type') ?? ''
if (contentType.includes('application/json')) {
if (contentType.includes(CONTENT_TYPE_JSON)) {
return response.json()
}
@@ -110,7 +115,7 @@ async function toApiError(error: unknown) {
if (error instanceof TimeoutError) {
return new ApiError({
message: API_ERROR_MESSAGES.timeout,
status: 408,
status: HTTP_STATUS.requestTimeout,
})
}
@@ -143,14 +148,20 @@ const apiClient = ky.create({
hooks: {
beforeRequest: [
({ request }) => {
request.headers.set('Accept', DEFAULT_REQUEST_ACCEPT_HEADER)
request.headers.set('lang', getRequestLanguage())
request.headers.set(
REQUEST_HEADERS.accept,
DEFAULT_REQUEST_ACCEPT_HEADER,
)
request.headers.set(REQUEST_HEADERS.lang, getRequestLanguage())
const token = useAuthStore.getState().accessToken
if (token) {
request.headers.set('Authorization', `Bearer ${token}`)
request.headers.set('user-token', token)
request.headers.set(
REQUEST_HEADERS.authorization,
`${AUTHORIZATION_BEARER_PREFIX}${token}`,
)
request.headers.set(REQUEST_HEADERS.userToken, token)
}
if (shouldLogRequests) {
@@ -222,14 +233,14 @@ function assertValidAuthEnvelope(data: unknown) {
throw new ApiError({
data,
message: getApiEnvelopeMessage(data),
status: 401,
status: HTTP_STATUS.unauthorized,
})
}
function unwrapEnvelopeData<T>(response: ApiResponse<T>) {
assertValidAuthEnvelope(response)
if (response.code === 1) {
if (response.code === API_SUCCESS_CODE) {
return response.data
}
@@ -333,8 +344,8 @@ function createHeaders(headersInit?: Options['headers']) {
async function buildRequestOptions(input: string, options?: Options) {
const headers = createHeaders(options?.headers)
if (shouldAttachAuthToken(input) && !headers.has('auth-token')) {
headers.set('auth-token', await fetchAuthToken())
if (shouldAttachAuthToken(input) && !headers.has(REQUEST_HEADERS.authToken)) {
headers.set(REQUEST_HEADERS.authToken, await fetchAuthToken())
}
return {
@@ -361,7 +372,7 @@ async function request<TResponse>(input: string, options?: Options) {
} catch (error) {
if (
error instanceof HTTPError &&
error.response.status === 401 &&
error.response.status === HTTP_STATUS.unauthorized &&
input !== AUTH_REFRESH_ENDPOINT &&
options?.context?.[AUTH_SKIP_REFRESH_CONTEXT_KEY] !== true &&
options?.context?.[AUTH_REFRESH_ATTEMPTED_CONTEXT_KEY] !== true
@@ -379,7 +390,10 @@ async function request<TResponse>(input: string, options?: Options) {
}
}
if (error instanceof HTTPError && error.response.status === 401) {
if (
error instanceof HTTPError &&
error.response.status === HTTP_STATUS.unauthorized
) {
handleUnauthorizedSession()
}

View File

@@ -1,3 +1,4 @@
import { LOGIN_PROMPT_DEDUP_MS } from '@/constants'
import i18n from '@/i18n'
import { notify } from '@/lib/notify'
import { queryClient } from '@/lib/query/query-client'
@@ -16,8 +17,6 @@ let authInitializationPromise: Promise<void> | null = null
let refreshSessionPromise: Promise<boolean> | null = null
let lastLoginPromptAt = 0
const LOGIN_PROMPT_DEDUP_MS = 1200
interface ClearAuthenticatedSessionOptions {
clearBrowserStorage?: boolean
clearQueryCache?: boolean