/** * 系统常量配置文件 * 消除魔法数字,提供统一的配置管理 */ // 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 = { 0: '华语', 1: '粤语', 2: '闽南语', 3: '日语', 4: '韩语', 5: '英语' } // QQ 官网流派分类英文名(get_song_detail_yqq 的 info.genre)→ 黑名单曲风值 // 仅覆盖候选曲风;未收录流派名(Animation/World Music 等)由调用方归入「其他」 export const QQ_GENRE_NAME_MAP: Record = { 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