feat: align with upstream 1.5.9.9

This commit is contained in:
2026-09-12 18:21:11 +08:00
commit 34f0679117
805 changed files with 376275 additions and 0 deletions
+494
View File
@@ -0,0 +1,494 @@
/**
* 系统常量配置文件
* 消除魔法数字,提供统一的配置管理
*/
// API Key 相关常量
export const API_KEY_CONSTANTS = {
// API Key 格式
PREFIX: 'vhub_',
TOTAL_LENGTH: 37,
PREFIX_LENGTH: 10,
// 数据库字段长度
NAME_MAX_LENGTH: 255,
KEY_HASH_MAX_LENGTH: 255,
PREFIX_MAX_LENGTH: 10,
PERMISSION_MAX_LENGTH: 100,
ENDPOINT_MAX_LENGTH: 500,
METHOD_MAX_LENGTH: 10,
// 默认值
DEFAULT_USAGE_COUNT: 0,
DEFAULT_IS_ACTIVE: true
} as const
// HTTP 状态码常量
export const HTTP_STATUS = {
OK: 200,
CREATED: 201,
BAD_REQUEST: 400,
UNAUTHORIZED: 401,
FORBIDDEN: 403,
NOT_FOUND: 404,
CONFLICT: 409,
UNPROCESSABLE_ENTITY: 422,
TOO_MANY_REQUESTS: 429,
INTERNAL_SERVER_ERROR: 500,
SERVICE_UNAVAILABLE: 503
} as const
// API 错误码常量
export const API_ERROR_CODES = {
MISSING_API_KEY: 'MISSING_API_KEY',
INVALID_API_KEY: 'INVALID_API_KEY',
INVALID_API_KEY_FORMAT: 'INVALID_API_KEY_FORMAT',
API_KEY_DISABLED: 'API_KEY_DISABLED',
API_KEY_EXPIRED: 'API_KEY_EXPIRED',
INSUFFICIENT_PERMISSIONS: 'INSUFFICIENT_PERMISSIONS',
RATE_LIMIT_EXCEEDED: 'RATE_LIMIT_EXCEEDED',
INVALID_PARAMETERS: 'INVALID_PARAMETERS',
INTERNAL_ERROR: 'INTERNAL_ERROR'
} as const
// API 错误消息常量
export const API_ERROR_MESSAGES = {
[API_ERROR_CODES.MISSING_API_KEY]:
'API Key is required. Please provide a valid API Key in the X-API-Key header.',
[API_ERROR_CODES.INVALID_API_KEY]: 'Invalid API Key',
[API_ERROR_CODES.INVALID_API_KEY_FORMAT]: 'Invalid API Key format',
[API_ERROR_CODES.API_KEY_DISABLED]: 'API Key is disabled',
[API_ERROR_CODES.API_KEY_EXPIRED]: 'API Key has expired',
[API_ERROR_CODES.INSUFFICIENT_PERMISSIONS]: 'Insufficient permissions',
[API_ERROR_CODES.RATE_LIMIT_EXCEEDED]: 'Rate limit exceeded',
[API_ERROR_CODES.INVALID_PARAMETERS]: 'Invalid parameters',
[API_ERROR_CODES.INTERNAL_ERROR]: 'Internal server error'
} as const
// 用户可见业务错误码(按子系统分组)。
// 配合 server/utils/apiError.ts 的 createApiError() 抛出,客户端通过 serverErrors 词典按 code 本地化。
// 说明:admin/** 子系统暂缓码化,仍直接返回中文文案,故此处不含 ADMIN_* 分组。
export const SERVER_ERROR_CODES = {
// 点歌券(card-codes)
CARD_CODE_AUTH_REQUIRED: 'CARD_CODE_AUTH_REQUIRED',
CARD_CODE_REQUIRED: 'CARD_CODE_REQUIRED',
CARD_CODE_DISABLED: 'CARD_CODE_DISABLED',
CARD_CODE_INVALID_OR_USED: 'CARD_CODE_INVALID_OR_USED',
CARD_CODE_LOCKED_OR_UNAVAILABLE: 'CARD_CODE_LOCKED_OR_UNAVAILABLE',
CARD_CODE_REQUIRED_FOR_SITE: 'CARD_CODE_REQUIRED_FOR_SITE',
CARD_CODE_TOO_LONG: 'CARD_CODE_TOO_LONG',
AUTH_CSRF_COOKIE_MISSING: 'AUTH_CSRF_COOKIE_MISSING',
AUTH_CHALLENGE_EXPIRED: 'AUTH_CHALLENGE_EXPIRED',
AUTH_CHALLENGE_INVALID: 'AUTH_CHALLENGE_INVALID',
AUTH_STATE_INVALID: 'AUTH_STATE_INVALID',
AUTH_MISSING_CODE_OR_STATE: 'AUTH_MISSING_CODE_OR_STATE',
AUTH_MISSING_PROVIDER: 'AUTH_MISSING_PROVIDER',
AUTH_OAUTH_PROVIDER_DISABLED: 'AUTH_OAUTH_PROVIDER_DISABLED',
AUTH_OAUTH_PROVIDER_STATE_MISMATCH: 'AUTH_OAUTH_PROVIDER_STATE_MISMATCH',
AUTH_OAUTH_REDIRECT_ORIGIN_MISMATCH: 'AUTH_OAUTH_REDIRECT_ORIGIN_MISMATCH',
AUTH_OAUTH_REDIRECT_INVALID: 'AUTH_OAUTH_REDIRECT_INVALID',
AUTH_WEBAUTHN_RP_ID_INVALID: 'AUTH_WEBAUTHN_RP_ID_INVALID',
AUTH_UNSUPPORTED_VERIFICATION_TYPE: 'AUTH_UNSUPPORTED_VERIFICATION_TYPE',
AUTH_CAPTCHA_FAILED_OR_EXPIRED: 'AUTH_CAPTCHA_FAILED_OR_EXPIRED',
AUTH_CAPTCHA_SERVICE_UNAVAILABLE: 'AUTH_CAPTCHA_SERVICE_UNAVAILABLE',
AUTH_SESSION_EXPIRED: 'AUTH_SESSION_EXPIRED',
AUTH_CREDENTIAL_CORRUPTED: 'AUTH_CREDENTIAL_CORRUPTED',
AUTH_CREDENTIAL_MISSING: 'AUTH_CREDENTIAL_MISSING',
AUTH_CREDENTIAL_MISSING_PUBLICKEY: 'AUTH_CREDENTIAL_MISSING_PUBLICKEY',
AUTH_TOTP_CODE_INVALID: 'AUTH_TOTP_CODE_INVALID',
AUTH_TOTP_TOO_MANY_ATTEMPTS: 'AUTH_TOTP_TOO_MANY_ATTEMPTS',
AUTH_INCOMPLETE_PARAMS: 'AUTH_INCOMPLETE_PARAMS',
COMMON_INVALID_PARAMS: 'COMMON_INVALID_PARAMS',
THEME_INVALID_LIST: 'THEME_INVALID_LIST',
THEME_INVALID_DEFAULT: 'THEME_INVALID_DEFAULT',
THEME_SYSTEM_REQUIRES_CLASSIC: 'THEME_SYSTEM_REQUIRES_CLASSIC',
SETTINGS_GRADE_CLASS_OPTIONS_MISSING: 'SETTINGS_GRADE_CLASS_OPTIONS_MISSING',
BILIBILI_PLAYURL_FAILED: 'BILIBILI_PLAYURL_FAILED',
AUTH_NAME_USERNAME_PASSWORD_REQUIRED: 'AUTH_NAME_USERNAME_PASSWORD_REQUIRED',
AUTH_USERNAME_LENGTH_INVALID: 'AUTH_USERNAME_LENGTH_INVALID',
AUTH_USERNAME_PATTERN_INVALID: 'AUTH_USERNAME_PATTERN_INVALID',
AUTH_PASSWORD_TOO_SHORT: 'AUTH_PASSWORD_TOO_SHORT',
AUTH_PASSWORD_TOO_LONG: 'AUTH_PASSWORD_TOO_LONG',
AUTH_PASSWORD_TOO_MANY_BYTES: 'AUTH_PASSWORD_TOO_MANY_BYTES',
AUTH_PASSWORD_TOO_COMMON: 'AUTH_PASSWORD_TOO_COMMON',
AUTH_PASSWORD_COMPLEXITY_REQUIRED: 'AUTH_PASSWORD_COMPLEXITY_REQUIRED',
AUTH_INITIAL_PASSWORD_COMPLEXITY_REQUIRED: 'AUTH_INITIAL_PASSWORD_COMPLEXITY_REQUIRED',
AUTH_PASSWORD_NOT_SET: 'AUTH_PASSWORD_NOT_SET',
AUTH_INITIAL_PASSWORD_NOT_REQUIRED: 'AUTH_INITIAL_PASSWORD_NOT_REQUIRED',
AUTH_PASSWORD_CHANGE_REQUIRED: 'AUTH_PASSWORD_CHANGE_REQUIRED',
AUTH_PASSWORD_STATE_CHANGED: 'AUTH_PASSWORD_STATE_CHANGED',
AUTH_GRADE_CLASS_TOGETHER: 'AUTH_GRADE_CLASS_TOGETHER',
AUTH_UNSUPPORTED_OAUTH_PROVIDER: 'AUTH_UNSUPPORTED_OAUTH_PROVIDER',
AUTH_CURRENT_PASSWORD_INCORRECT: 'AUTH_CURRENT_PASSWORD_INCORRECT',
AUTH_CURRENT_AND_NEW_PASSWORD_REQUIRED: 'AUTH_CURRENT_AND_NEW_PASSWORD_REQUIRED',
AUTH_PASSWORD_ALREADY_SET: 'AUTH_PASSWORD_ALREADY_SET',
AUTH_IP_BLOCKED_RETRY_MINUTES: 'AUTH_IP_BLOCKED_RETRY_MINUTES',
AUTH_ACCOUNT_NAME_NOT_FOUND: 'AUTH_ACCOUNT_NAME_NOT_FOUND',
AUTH_RATE_LIMITED_MINUTES: 'AUTH_RATE_LIMITED_MINUTES',
AUTH_RATE_LIMITED_SECONDS: 'AUTH_RATE_LIMITED_SECONDS',
AUTH_DATABASE_UNAVAILABLE: 'AUTH_DATABASE_UNAVAILABLE',
AUTH_NEW_PASSWORD_SAME_AS_CURRENT: 'AUTH_NEW_PASSWORD_SAME_AS_CURRENT',
AUTH_NEW_PASSWORD_REQUIRED: 'AUTH_NEW_PASSWORD_REQUIRED',
AUTH_REGISTER_PASSWORD_MISMATCH: 'AUTH_REGISTER_PASSWORD_MISMATCH',
AUTH_INVALID_REGISTER_TOKEN: 'AUTH_INVALID_REGISTER_TOKEN',
AUTH_INVALID_BINDING_TOKEN: 'AUTH_INVALID_BINDING_TOKEN',
AUTH_INVALID_RESET_LINK: 'AUTH_INVALID_RESET_LINK',
AUTH_UPDATE_FAILED: 'AUTH_UPDATE_FAILED',
AUTH_SERVER_CONFIG_ERROR: 'AUTH_SERVER_CONFIG_ERROR',
AUTH_AGGREGATED_LOGIN_UNSUPPORTED: 'AUTH_AGGREGATED_LOGIN_UNSUPPORTED',
AUTH_TOTP_NOT_ENABLED: 'AUTH_TOTP_NOT_ENABLED',
AUTH_PASSKEY_ACCOUNT_NOT_FOUND: 'AUTH_PASSKEY_ACCOUNT_NOT_FOUND',
AUTH_DEVICE_NOT_FOUND: 'AUTH_DEVICE_NOT_FOUND',
AUTH_UNAUTHORIZED: 'AUTH_UNAUTHORIZED',
AUTH_UNAUTHORIZED_ACCESS: 'AUTH_UNAUTHORIZED_ACCESS',
AUTH_TOKEN_MISSING: 'AUTH_TOKEN_MISSING',
AUTH_MULTI_IP_PROTECTION: 'AUTH_MULTI_IP_PROTECTION',
AUTH_REGISTER_SESSION_EXPIRED: 'AUTH_REGISTER_SESSION_EXPIRED',
USER_NOT_FOUND: 'USER_NOT_FOUND',
AUTH_USER_NOT_FOUND_OR_NO_EMAIL: 'AUTH_USER_NOT_FOUND_OR_NO_EMAIL',
AUTH_USERNAME_PASSWORD_REQUIRED: 'AUTH_USERNAME_PASSWORD_REQUIRED',
AUTH_USERNAME_TAKEN: 'AUTH_USERNAME_TAKEN',
AUTH_INVALID_CREDENTIALS: 'AUTH_INVALID_CREDENTIALS',
AUTH_LOGIN_UNKNOWN_ERROR: 'AUTH_LOGIN_UNKNOWN_ERROR',
AUTH_OAUTH_REGISTER_DISABLED: 'AUTH_OAUTH_REGISTER_DISABLED',
AUTH_OAUTH_REGISTER_DISABLED_BIND: 'AUTH_OAUTH_REGISTER_DISABLED_BIND',
AUTH_REGISTER_DISABLED: 'AUTH_REGISTER_DISABLED',
AUTH_USER_PENDING_APPROVAL: 'AUTH_USER_PENDING_APPROVAL',
AUTH_SYSTEM_ERROR: 'AUTH_SYSTEM_ERROR',
AUTH_BINDING_SESSION_INVALID: 'AUTH_BINDING_SESSION_INVALID',
AUTH_BINDING_SESSION_EXPIRED: 'AUTH_BINDING_SESSION_EXPIRED',
AUTH_MISSING_CREDENTIAL_ID: 'AUTH_MISSING_CREDENTIAL_ID',
AUTH_MISSING_REQUIRED_PARAMS: 'AUTH_MISSING_REQUIRED_PARAMS',
AUTH_MISSING_PROVIDER_PARAM: 'AUTH_MISSING_PROVIDER_PARAM',
AUTH_MISSING_PRE_TOKEN: 'AUTH_MISSING_PRE_TOKEN',
AUTH_MISSING_PRE_TOKEN_RELOGIN: 'AUTH_MISSING_PRE_TOKEN_RELOGIN',
AUTH_AGGREGATED_CALLBACK_STATE_MISMATCH: 'AUTH_AGGREGATED_CALLBACK_STATE_MISMATCH',
AUTH_AGGREGATED_METHOD_CHANGED: 'AUTH_AGGREGATED_METHOD_CHANGED',
AUTH_AGGREGATED_STATE_INVALID: 'AUTH_AGGREGATED_STATE_INVALID',
AUTH_DEVICE_NAME_TOO_LONG: 'AUTH_DEVICE_NAME_TOO_LONG',
AUTH_OAUTH_BOUND_OTHER_USER: 'AUTH_OAUTH_BOUND_OTHER_USER',
AUTH_OAUTH_ALREADY_BOUND: 'AUTH_OAUTH_ALREADY_BOUND',
AUTH_ACCOUNT_UNAVAILABLE_OR_RESTRICTED: 'AUTH_ACCOUNT_UNAVAILABLE_OR_RESTRICTED',
AUTH_ACCOUNT_GRADUATED: 'AUTH_ACCOUNT_GRADUATED',
AUTH_ACCOUNT_BANNED: 'AUTH_ACCOUNT_BANNED',
AUTH_ACCOUNT_WITHDRAWN: 'AUTH_ACCOUNT_WITHDRAWN',
AUTH_ACCOUNT_CURRENTLY_UNAVAILABLE: 'AUTH_ACCOUNT_CURRENTLY_UNAVAILABLE',
AUTH_NO_SECURITY_EMAIL: 'AUTH_NO_SECURITY_EMAIL',
AUTH_RESET_LINK_INVALIDATED: 'AUTH_RESET_LINK_INVALIDATED',
AUTH_CAPTCHA_REQUIRED: 'AUTH_CAPTCHA_REQUIRED',
AUTH_IMAGE_CAPTCHA_REQUIRED: 'AUTH_IMAGE_CAPTCHA_REQUIRED',
AUTH_ACCOUNT_NAME_REQUIRED: 'AUTH_ACCOUNT_NAME_REQUIRED',
AUTH_GRADE_CLASS_MUST_EXIST: 'AUTH_GRADE_CLASS_MUST_EXIST',
AUTH_GRADE_CLASS_REQUIRED: 'AUTH_GRADE_CLASS_REQUIRED',
AUTH_ACCOUNT_DISABLED_OR_RESTRICTED: 'AUTH_ACCOUNT_DISABLED_OR_RESTRICTED',
AUTH_ACCOUNT_RISK_CONTROL: 'AUTH_ACCOUNT_RISK_CONTROL',
AUTH_ACCOUNT_LOCKED_MINUTES: 'AUTH_ACCOUNT_LOCKED_MINUTES',
AUTH_ACCOUNT_LOCKED: 'AUTH_ACCOUNT_LOCKED',
AUTH_EMAIL_MISMATCH: 'AUTH_EMAIL_MISMATCH',
AUTH_EMAIL_CODE_INVALID: 'AUTH_EMAIL_CODE_INVALID',
AUTH_EMAIL_CODE_COOLDOWN: 'AUTH_EMAIL_CODE_COOLDOWN',
AUTH_EMAIL_CODE_DAILY_LIMIT: 'AUTH_EMAIL_CODE_DAILY_LIMIT',
AUTH_EMAIL_SERVICE_UNAVAILABLE: 'AUTH_EMAIL_SERVICE_UNAVAILABLE',
AUTH_RESET_PASSWORD_TOO_MANY: 'AUTH_RESET_PASSWORD_TOO_MANY',
AUTH_RESET_LINK_EXPIRED: 'AUTH_RESET_LINK_EXPIRED',
AUTH_USER_ID_MISMATCH: 'AUTH_USER_ID_MISMATCH',
AUTH_VERIFICATION_FAILED: 'AUTH_VERIFICATION_FAILED',
AUTH_TOO_MANY_VERIFY_ATTEMPTS: 'AUTH_TOO_MANY_VERIFY_ATTEMPTS',
AUTH_CODE_SEND_FAILED: 'AUTH_CODE_SEND_FAILED',
AUTH_CODE_ALREADY_USED: 'AUTH_CODE_ALREADY_USED',
AUTH_CODE_EXPIRED: 'AUTH_CODE_EXPIRED',
AUTH_CODE_EXPIRED_OR_MISSING: 'AUTH_CODE_EXPIRED_OR_MISSING',
AUTH_CODE_SERVICE_CONFIG_ERROR: 'AUTH_CODE_SERVICE_CONFIG_ERROR',
AUTH_CODE_INVALID_OR_EXPIRED: 'AUTH_CODE_INVALID_OR_EXPIRED',
AUTH_CODE_WRONG_ATTEMPTS_LEFT: 'AUTH_CODE_WRONG_ATTEMPTS_LEFT',
SONG_INVALID_ID: 'SONG_INVALID_ID',
HTTP_METHOD_NOT_ALLOWED: 'HTTP_METHOD_NOT_ALLOWED',
SONG_ALREADY_EXISTS: 'SONG_ALREADY_EXISTS',
SONG_TITLE_ARTIST_REQUIRED: 'SONG_TITLE_ARTIST_REQUIRED',
SONG_CANNOT_SELF_VOTE: 'SONG_CANNOT_SELF_VOTE',
SONG_VOTE_NOT_FOUND: 'SONG_VOTE_NOT_FOUND',
SONG_ALREADY_VOTED: 'SONG_ALREADY_VOTED',
SONG_REPLAY_CANCEL_FAILED: 'SONG_REPLAY_CANCEL_FAILED',
SONG_VOTERS_ADMIN_ONLY: 'SONG_VOTERS_ADMIN_ONLY',
SONG_WITHDRAW_OWN_ONLY: 'SONG_WITHDRAW_OWN_ONLY',
SONG_REPLAY_CURRENT_SEMESTER_ONLY: 'SONG_REPLAY_CURRENT_SEMESTER_ONLY',
SONG_INVITATION_FAILED: 'SONG_INVITATION_FAILED',
SONG_SCHEDULED_CANNOT_WITHDRAW: 'SONG_SCHEDULED_CANNOT_WITHDRAW',
SONG_PLAYED_CANNOT_WITHDRAW: 'SONG_PLAYED_CANNOT_WITHDRAW',
SONG_NO_ACTIVE_SEMESTER_REPLAY: 'SONG_NO_ACTIVE_SEMESTER_REPLAY',
SONG_RESTRICTION_SAME_SONG: 'SONG_RESTRICTION_SAME_SONG',
SONG_RESTRICTION_SAME_ARTIST: 'SONG_RESTRICTION_SAME_ARTIST',
SONG_REPLAY_ALREADY_REQUESTED: 'SONG_REPLAY_ALREADY_REQUESTED',
SONG_REPLAY_COOLDOWN: 'SONG_REPLAY_COOLDOWN',
SONG_REPLAY_INVALID_REQUEST: 'SONG_REPLAY_INVALID_REQUEST',
SONG_REPLAY_PLAY_TIME_DISABLED: 'SONG_REPLAY_PLAY_TIME_DISABLED',
SONG_REPLAY_PLAY_TIME_INVALID: 'SONG_REPLAY_PLAY_TIME_INVALID',
SONG_REQUESTER_NOT_FOUND: 'SONG_REQUESTER_NOT_FOUND',
SONG_OPERATION_FAILED: 'SONG_OPERATION_FAILED',
SONG_INVITATION_NOT_FOUND: 'SONG_INVITATION_NOT_FOUND',
AUTH_LOGIN_REQUIRED: 'AUTH_LOGIN_REQUIRED',
SONG_NO_ACTIVE_SEMESTER_VOTE: 'SONG_NO_ACTIVE_SEMESTER_VOTE',
COMMON_INSUFFICIENT_PERMISSION: 'COMMON_INSUFFICIENT_PERMISSION',
COMMON_TARGET_NOT_FOUND: 'COMMON_TARGET_NOT_FOUND',
USER_NOT_PENDING: 'USER_NOT_PENDING',
GRADE_CLASS_DUPLICATE: 'GRADE_CLASS_DUPLICATE',
SONG_ID_REQUIRED: 'SONG_ID_REQUIRED',
SONG_NOT_FOUND: 'SONG_NOT_FOUND',
SONG_DURATION_PLATFORM_REQUIRED: 'SONG_DURATION_PLATFORM_REQUIRED',
SONG_CARD_RELEASE_FAILED: 'SONG_CARD_RELEASE_FAILED',
SONG_NO_ACTIVE_SEMESTER_IMPORT: 'SONG_NO_ACTIVE_SEMESTER_IMPORT',
SONG_FETCH_VOTERS_FAILED: 'SONG_FETCH_VOTERS_FAILED',
SONG_FETCH_STATUS_FAILED: 'SONG_FETCH_STATUS_FAILED',
SONG_FETCH_COUNT_FAILED: 'SONG_FETCH_COUNT_FAILED',
SONG_TEMP_PROTECTED_SECONDS: 'SONG_TEMP_PROTECTED_SECONDS',
SONG_NOT_PLAYED_NO_REPLAY: 'SONG_NOT_PLAYED_NO_REPLAY',
SONG_SCHEDULED_NO_VOTE: 'SONG_SCHEDULED_NO_VOTE',
SONG_PLAYED_NO_VOTE: 'SONG_PLAYED_NO_VOTE',
SONG_SELECT_TO_IMPORT: 'SONG_SELECT_TO_IMPORT',
SONG_REPLAY_NOT_FOUND_OR_FORBIDDEN: 'SONG_REPLAY_NOT_FOUND_OR_FORBIDDEN',
SONG_REPLAY_DISABLED: 'SONG_REPLAY_DISABLED',
SONG_LOGIN_REQUIRED_CANCEL_REPLAY: 'SONG_LOGIN_REQUIRED_CANCEL_REPLAY',
SONG_LOGIN_REQUIRED_VOTE: 'SONG_LOGIN_REQUIRED_VOTE',
SONG_LOGIN_REQUIRED_WITHDRAW: 'SONG_LOGIN_REQUIRED_WITHDRAW',
SONG_LOGIN_REQUIRED_VIEW_VOTERS: 'SONG_LOGIN_REQUIRED_VIEW_VOTERS',
SONG_LOGIN_REQUIRED_VIEW_STATUS: 'SONG_LOGIN_REQUIRED_VIEW_STATUS',
SONG_LOGIN_REQUIRED_REQUEST: 'SONG_LOGIN_REQUIRED_REQUEST',
SONG_LOGIN_REQUIRED_REPLAY: 'SONG_LOGIN_REQUIRED_REPLAY',
SONG_INACTIVE_SEMESTER_VOTE: 'SONG_INACTIVE_SEMESTER_VOTE',
USER_API_KEY_NOT_FOUND: 'USER_API_KEY_NOT_FOUND',
USER_API_KEY_ID_REQUIRED: 'USER_API_KEY_ID_REQUIRED',
USER_API_KEY_ID_INVALID: 'USER_API_KEY_ID_INVALID',
USER_API_KEY_CREATE_FAILED: 'USER_API_KEY_CREATE_FAILED',
USER_API_KEY_CREATE_FAILED_DETAIL: 'USER_API_KEY_CREATE_FAILED_DETAIL',
USER_API_KEY_DELETE_FAILED_DETAIL: 'USER_API_KEY_DELETE_FAILED_DETAIL',
USER_CODE_SEND_FAILED: 'USER_CODE_SEND_FAILED',
USER_PASSWORD_INCORRECT: 'USER_PASSWORD_INCORRECT',
USER_2FA_ALREADY_ENABLED: 'USER_2FA_ALREADY_ENABLED',
USER_API_KEY_LIMIT_REACHED: 'USER_API_KEY_LIMIT_REACHED',
USER_EMAIL_BIND_FAILED: 'USER_EMAIL_BIND_FAILED',
USER_MISSING_CODE_OR_SECRET: 'USER_MISSING_CODE_OR_SECRET',
USER_API_KEY_FETCH_FAILED_DETAIL: 'USER_API_KEY_FETCH_FAILED_DETAIL',
USER_API_KEY_LOGS_FETCH_FAILED_DETAIL: 'USER_API_KEY_LOGS_FETCH_FAILED_DETAIL',
USER_YEAR_REVIEW_FETCH_FAILED: 'USER_YEAR_REVIEW_FETCH_FAILED',
USER_EMAIL_UNBIND_FAILED: 'USER_EMAIL_UNBIND_FAILED',
USER_EMAIL_TAKEN: 'USER_EMAIL_TAKEN',
USER_SEND_CODE_FIRST: 'USER_SEND_CODE_FIRST',
USER_BIND_EMAIL_FIRST: 'USER_BIND_EMAIL_FIRST',
USER_REQUEST_VALIDATION_FAILED: 'USER_REQUEST_VALIDATION_FAILED',
USER_PASSWORD_REQUIRED: 'USER_PASSWORD_REQUIRED',
USER_INVALID_EMAIL: 'USER_INVALID_EMAIL',
USER_EMAIL_AND_CODE_REQUIRED: 'USER_EMAIL_AND_CODE_REQUIRED',
USER_EMAIL_CHANGED_RESEND: 'USER_EMAIL_CHANGED_RESEND',
USER_EMAIL_ALREADY_VERIFIED: 'USER_EMAIL_ALREADY_VERIFIED',
USER_RESEND_EMAIL_FAILED: 'USER_RESEND_EMAIL_FAILED',
USER_CODE_ALREADY_USED: 'USER_CODE_ALREADY_USED',
USER_CODE_EXPIRED_RESEND: 'USER_CODE_EXPIRED_RESEND',
USER_CODE_INVALID: 'USER_CODE_INVALID',
USER_CODE_TOO_MANY_ATTEMPTS: 'USER_CODE_TOO_MANY_ATTEMPTS',
// 音源平台
MUSIC_SOURCE_PLATFORM_DISABLED: 'MUSIC_SOURCE_PLATFORM_DISABLED',
QQ_COMMENT_FETCH_FAILED: 'QQ_COMMENT_FETCH_FAILED',
// 自动备份
BACKUP_DISABLED: 'BACKUP_DISABLED',
BACKUP_NOT_CONFIGURED: 'BACKUP_NOT_CONFIGURED',
NO_BACKUP_METHOD_ENABLED: 'NO_BACKUP_METHOD_ENABLED',
BACKUP_FAILED: 'BACKUP_FAILED',
SMTP_NOT_CONFIGURED: 'SMTP_NOT_CONFIGURED',
// 通知
NOTIFICATION_AUTH_REQUIRED: 'NOTIFICATION_AUTH_REQUIRED',
NOTIFICATION_ADMIN_REQUIRED: 'NOTIFICATION_ADMIN_REQUIRED',
NOTIFICATION_IMPORTANT_INVALID: 'NOTIFICATION_IMPORTANT_INVALID',
NOTIFICATION_TITLE_CONTENT_REQUIRED: 'NOTIFICATION_TITLE_CONTENT_REQUIRED',
NOTIFICATION_TITLE_TOO_LONG: 'NOTIFICATION_TITLE_TOO_LONG',
NOTIFICATION_CONTENT_TOO_LONG: 'NOTIFICATION_CONTENT_TOO_LONG',
NOTIFICATION_USER_ID_INVALID: 'NOTIFICATION_USER_ID_INVALID',
NOTIFICATION_SCOPE_INVALID: 'NOTIFICATION_SCOPE_INVALID',
NOTIFICATION_GRADE_REQUIRED: 'NOTIFICATION_GRADE_REQUIRED',
NOTIFICATION_GRADE_CLASS_REQUIRED: 'NOTIFICATION_GRADE_CLASS_REQUIRED',
NOTIFICATION_CLASSES_REQUIRED: 'NOTIFICATION_CLASSES_REQUIRED',
NOTIFICATION_USERS_REQUIRED: 'NOTIFICATION_USERS_REQUIRED',
NOTIFICATION_SEND_FAILED: 'NOTIFICATION_SEND_FAILED',
NOTIFICATION_FETCH_FAILED: 'NOTIFICATION_FETCH_FAILED',
NOTIFICATION_ID_INVALID: 'NOTIFICATION_ID_INVALID',
NOTIFICATION_NOT_FOUND: 'NOTIFICATION_NOT_FOUND',
NOTIFICATION_FORBIDDEN: 'NOTIFICATION_FORBIDDEN',
NOTIFICATION_MARK_READ_FAILED: 'NOTIFICATION_MARK_READ_FAILED',
NOTIFICATION_DELETE_FAILED: 'NOTIFICATION_DELETE_FAILED',
NOTIFICATION_CLEAR_FAILED: 'NOTIFICATION_CLEAR_FAILED',
NOTIFICATION_HISTORY_STATUS_INVALID: 'NOTIFICATION_HISTORY_STATUS_INVALID',
NOTIFICATION_HISTORY_FILTER_INVALID: 'NOTIFICATION_HISTORY_FILTER_INVALID',
NOTIFICATION_HISTORY_FETCH_FAILED: 'NOTIFICATION_HISTORY_FETCH_FAILED',
NOTIFICATION_HISTORY_UPDATE_FAILED: 'NOTIFICATION_HISTORY_UPDATE_FAILED',
NOTIFICATION_HISTORY_DELETE_FAILED: 'NOTIFICATION_HISTORY_DELETE_FAILED'
// 认证(AUTH_*)、歌曲(SONG_*)、用户(USER_*)等分组在 B2 转换时逐步补充
} as const
export type ServerErrorCode = keyof typeof SERVER_ERROR_CODES
// 分页常量
export const PAGINATION_CONSTANTS = {
DEFAULT_PAGE: 1,
DEFAULT_LIMIT: 20,
MAX_LIMIT: 100,
MIN_LIMIT: 1
} as const
// 日志相关常量
export const LOG_CONSTANTS = {
MAX_RESPONSE_BODY_LENGTH: 10000, // 最大响应体长度
LOG_LEVEL: {
ERROR: 'error',
WARN: 'warn',
INFO: 'info',
DEBUG: 'debug'
} as const,
// 日志轮转和归档配置
LOG_DIR: 'logs',
ARCHIVE_DIR: 'logs/archive',
RETENTION_DAYS: 30, // 日志文件保留天数
ARCHIVE_AFTER_DAYS: 7, // 多少天后归档
DB_RETENTION_DAYS: 90, // 数据库日志保留天数
MAX_LOG_FILE_SIZE: 100 * 1024 * 1024, // 100MB
ROTATION_INTERVAL: 24 * 60 * 60 * 1000 // 24小时轮转间隔
} as const
// 通知设置默认值常量
export const NOTIFICATION_DEFAULTS = {
ENABLED: true,
SONG_REQUEST_ENABLED: true,
SONG_VOTED_ENABLED: true,
SONG_PLAYED_ENABLED: true,
REFRESH_INTERVAL: 60,
SONG_VOTED_THRESHOLD: 1
} as const
// 系统设置默认值常量
export const SYSTEM_DEFAULTS = {
ENABLE_PLAY_TIME_SELECTION: false,
ENABLE_SUBMISSION_LIMIT: false,
SHOW_BLACKLIST_KEYWORDS: false,
HIDE_STUDENT_INFO: true,
IS_ACTIVE: false
} as const
// 排序相关常量
export const SORT_CONSTANTS = {
DEFAULT_SORT_BY: 'createdAt',
DEFAULT_SORT_ORDER: 'desc',
VALID_SORT_ORDERS: ['asc', 'desc'] as const,
// 排期排序字段
SCHEDULE_SORT_FIELDS: ['playDate', 'createdAt'] as const,
// 歌曲排序字段
SONG_SORT_FIELDS: ['createdAt', 'title', 'artist', 'playedAt', 'votes'] as const
} as const
// 权限常量
export const PERMISSIONS = {
SCHEDULES_READ: 'schedules:read',
SONGS_READ: 'songs:read',
SONGS_REQUEST: 'songs:request',
ADMIN_ACCESS: 'admin:access'
} as const
// 用户角色常量
export const USER_ROLES = {
USER: 'USER',
SONG_ADMIN: 'SONG_ADMIN',
ADMIN: 'ADMIN',
SUPER_ADMIN: 'SUPER_ADMIN'
} as const
// 黑名单类型常量
export const BLACKLIST_TYPES = {
SONG: 'SONG',
KEYWORD: 'KEYWORD',
LANGUAGE: 'LANGUAGE',
GENRE: 'GENRE'
} as const
// 语种黑名单候选值(存储值即匹配值;新增需同步前端 app/utils/blacklist.ts)
export const BLACKLIST_LANGUAGE_VALUES = ['华语', '粤语', '闽南语', '英语', '日语', '韩语', '其他'] as const
// 曲风黑名单候选值(取网易云曲风标签一级分类与 QQ 官网流派分类;新增需同步前端 app/utils/blacklist.ts)
export const BLACKLIST_GENRE_VALUES = [
'流行', '摇滚', '民谣', '电子', '舞曲', '说唱', '古典', '爵士',
'乡村', '原声带', '蓝调', '轻音乐', '其他'
] as const
// QQ 音乐语种数字码 → 黑名单语种值(track_info.language 实测映射;info.lan 缺失时兜底)
export const QQ_LANGUAGE_CODE_MAP: Record<number, string> = {
0: '华语',
1: '粤语',
2: '闽南语',
3: '日语',
4: '韩语',
5: '英语'
}
// QQ 官网流派分类英文名(get_song_detail_yqq 的 info.genre)→ 黑名单曲风值
// 仅覆盖候选曲风;未收录流派名(Animation/World Music 等)由调用方归入「其他」
export const QQ_GENRE_NAME_MAP: Record<string, string> = {
Pop: '流行',
Rock: '摇滚',
Folk: '民谣',
Electronic: '电子',
Dance: '舞曲',
Rap: '说唱',
'Hip Hop': '说唱',
'Hip-Hop': '说唱',
Classical: '古典',
Jazz: '爵士',
Country: '乡村',
Soundtrack: '原声带',
OST: '原声带',
Blues: '蓝调',
'Easy Listening': '轻音乐'
}
// 音乐平台常量
export const MUSIC_PLATFORMS = {
NETEASE: 'netease',
TENCENT: 'tencent',
KUGOU: 'kugou',
KUWO: 'kuwo',
MIGU: 'migu'
} as const
// 音源控制功能平台白名单(enabledPlatforms/platformOrder 校验用;新增平台需同步 app/drizzle/schema.ts 默认值、迁移文件与前端 app/utils/platforms.ts)
export const MUSIC_SOURCE_PLATFORMS = ['netease', 'tencent', 'bilibili', 'migu'] as const
// 歌曲时长合法区间(秒),投稿、往期导入、后台补齐共用
export const SONG_DURATION_MIN_SECONDS = 0
export const SONG_DURATION_MAX_SECONDS = 7200
// 主题白名单(主题管理校验用;新增主题需同步 app/drizzle/schema.ts 默认值、迁移文件与前端 app/composables/useTheme.ts)
export const THEMES = ['System', 'ClassicDark', 'ClassicLight', 'ModernLight'] as const
export const DEFAULT_THEMES = [...THEMES]
export const RISK_CONTROL_CONSTANTS = {
IP_SWITCH_WINDOW_MS: 5 * 60 * 1000,
IP_SWITCH_THRESHOLD: 3,
IP_SWITCH_BLOCK_MINUTES: 10,
USER_BLOCK_MINUTES: 10,
SONG_VOTE_PROTECT_WINDOW_MS: 60 * 60 * 1000,
SONG_VOTE_PROTECT_THRESHOLD: 10,
SONG_VOTE_PROTECT_DURATION_MS: 10 * 60 * 1000,
USER_BASELINE_MIN_PER_MIN: 0.5,
USER_BASELINE_MULTIPLIER: 3,
EMA_ALPHA: 0.2,
SONG_IP_DOMINANCE_PERCENT: 0.6,
SONG_IP_MIN_SAMPLE: 8
} as const
// 图形验证码策略
export const CAPTCHA_MAX_FAILURES = 3
export const CAPTCHA_CODE_EXPIRE = 300 // 5分钟
// 投稿公开留言审核状态(Song.submissionNotePublicStatus / song_replay_requests.submission_note_public_status)
export const SUBMISSION_NOTE_STATUS = {
PENDING: 'pending',
APPROVED: 'approved',
REJECTED: 'rejected'
} as const