This commit is contained in:
yml
2026-04-08 16:30:42 +08:00
commit 313c036845
131 changed files with 22393 additions and 0 deletions
+161
View File
@@ -0,0 +1,161 @@
export function buildSuccessPayload(data, msg = 'ok') {
return {
code: 0,
msg,
time: Math.floor(Date.now() / 1000),
data: normalizeResponseDateTime(data),
}
}
export function buildErrorPayload(error, fallbackMessage) {
const classification = classifyRouteError(error)
return {
code: 1,
msg: error instanceof Error ? error.message : fallbackMessage,
errorCode: classification.errorCode,
time: Math.floor(Date.now() / 1000),
data: null,
}
}
export function sendRouteError(res, error, fallbackMessage, scope) {
const classification = classifyRouteError(error)
const logger = classification.statusCode >= 500 ? console.error : console.warn
logger(`${scope} ${fallbackMessage}:`, error)
res.status(classification.statusCode).json(buildErrorPayload(error, fallbackMessage))
}
export function buildNotFoundPayload(req) {
return {
code: 1,
msg: `未实现接口: ${req.method} ${req.originalUrl}`,
time: Math.floor(Date.now() / 1000),
data: null,
}
}
export function createHttpError(message, { statusCode = 500, errorCode = '' } = {}) {
const error = new Error(message)
error.statusCode = statusCode
error.errorCode = errorCode || defaultErrorCodeForStatus(statusCode)
return error
}
function classifyRouteError(error) {
if (error && typeof error === 'object') {
const statusCode = Number(error.statusCode)
const errorCode = String(error.errorCode || '').trim()
if (Number.isInteger(statusCode) && statusCode >= 400 && statusCode < 600) {
return {
statusCode,
errorCode: errorCode || defaultErrorCodeForStatus(statusCode),
}
}
}
const message = error instanceof Error ? error.message : String(error || '')
if (message.includes('缺少') || message.includes('无效') || message.includes('验签失败')) {
return { statusCode: 400, errorCode: 'invalid_request' }
}
if (message.includes('不存在')) {
return { statusCode: 404, errorCode: 'not_found' }
}
if (message.includes('已关闭')) {
return { statusCode: 410, errorCode: 'gone' }
}
if (message.includes('不能') || message.includes('冲突')) {
return { statusCode: 409, errorCode: 'conflict' }
}
if (
message.includes('OCR') ||
message.includes('uv sync') ||
message.includes('spawn uv ENOENT') ||
message.includes("Executable doesn't exist") ||
message.includes('please run the following command to download new browsers')
) {
return { statusCode: 503, errorCode: 'dependency_unavailable' }
}
return { statusCode: 500, errorCode: 'internal_error' }
}
function defaultErrorCodeForStatus(statusCode) {
if (statusCode === 400) {
return 'invalid_request'
}
if (statusCode === 401) {
return 'unauthorized'
}
if (statusCode === 403) {
return 'forbidden'
}
if (statusCode === 404) {
return 'not_found'
}
if (statusCode === 409) {
return 'conflict'
}
if (statusCode === 410) {
return 'gone'
}
if (statusCode === 503) {
return 'dependency_unavailable'
}
return 'internal_error'
}
function normalizeResponseDateTime(value) {
if (Array.isArray(value)) {
return value.map((item) => normalizeResponseDateTime(item))
}
if (value && typeof value === 'object') {
return Object.fromEntries(
Object.entries(value).map(([key, currentValue]) => [key, normalizeResponseDateTime(currentValue)]),
)
}
if (isIsoDateTimeString(value)) {
return formatResponseDateTime(value)
}
return value
}
function isIsoDateTimeString(value) {
return (
typeof value === 'string' &&
/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})?$/.test(value.trim())
)
}
function formatResponseDateTime(value) {
const date = new Date(value)
if (Number.isNaN(date.getTime())) {
return String(value || '').replace('T', ' ').replace('Z', '')
}
const year = date.getFullYear()
const month = String(date.getMonth() + 1).padStart(2, '0')
const day = String(date.getDate()).padStart(2, '0')
const hours = String(date.getHours()).padStart(2, '0')
const minutes = String(date.getMinutes()).padStart(2, '0')
const seconds = String(date.getSeconds()).padStart(2, '0')
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`
}
+9
View File
@@ -0,0 +1,9 @@
import crypto from 'node:crypto'
export function randomToken(bytes = 24) {
return crypto.randomBytes(bytes).toString('hex')
}
export function randomId(prefix, bytes = 6) {
return `${prefix}${crypto.randomBytes(bytes).toString('hex')}`
}
+8
View File
@@ -0,0 +1,8 @@
export function nowIso() {
return new Date().toISOString()
}
export function addHours(baseIso, hours) {
const baseTime = baseIso ? new Date(baseIso).getTime() : Date.now()
return new Date(baseTime + hours * 60 * 60 * 1000).toISOString()
}