From d126b35579d73f91401955fb16c6928f4b221405 Mon Sep 17 00:00:00 2001 From: yml2213 Date: Tue, 26 May 2026 10:48:30 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96=E5=90=8E=E7=AB=AF=E4=BB=A3?= =?UTF-8?q?=E7=A0=81=E8=B4=A8=E9=87=8F=E5=9F=BA=E7=A1=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/backend/.prettierrc.json | 6 ++ apps/backend/package-lock.json | 17 +++++ apps/backend/package.json | 2 + .../platform-config/cloudtentacles/index.ts | 6 +- .../src/services/claim/claim-service.ts | 6 +- .../src/services/notification/bark-service.ts | 2 +- .../notification/domain-notifications.ts | 67 ++++++++++++++++--- .../services/notification/wpush-service.ts | 2 +- apps/backend/src/services/open-91/config.ts | 10 ++- .../src/services/order/order-service.ts | 6 +- .../cloudtentacles/session-state-service.ts | 16 ++++- .../cloudtentacles/source-config-service.ts | 16 ++++- .../services/scheduler/scheduler-service.ts | 11 ++- 13 files changed, 142 insertions(+), 25 deletions(-) create mode 100644 apps/backend/.prettierrc.json diff --git a/apps/backend/.prettierrc.json b/apps/backend/.prettierrc.json new file mode 100644 index 00000000..3ee2d113 --- /dev/null +++ b/apps/backend/.prettierrc.json @@ -0,0 +1,6 @@ +{ + "semi": false, + "singleQuote": true, + "printWidth": 100, + "trailingComma": "all" +} diff --git a/apps/backend/package-lock.json b/apps/backend/package-lock.json index 30a329c8..eb301548 100644 --- a/apps/backend/package-lock.json +++ b/apps/backend/package-lock.json @@ -15,6 +15,7 @@ "@types/express": "^5.0.6", "@types/node": "^25.6.0", "@types/pg": "^8.20.0", + "prettier": "^3.8.3", "tsx": "^4.22.3", "typescript": "^6.0.2" } @@ -1299,6 +1300,22 @@ "node": ">=0.10.0" } }, + "node_modules/prettier": { + "version": "3.8.3", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.3.tgz", + "integrity": "sha512-7igPTM53cGHMW8xWuVTydi2KO233VFiTNyF5hLJqpilHfmn8C8gPf+PS7dUT64YcXFbiMGZxS9pCSxL/Dxm/Jw==", + "dev": true, + "license": "MIT", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, "node_modules/proxy-addr": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", diff --git a/apps/backend/package.json b/apps/backend/package.json index 23e1d140..b752fd7b 100644 --- a/apps/backend/package.json +++ b/apps/backend/package.json @@ -8,6 +8,7 @@ "build": "tsc -p tsconfig.build.json && rm -rf dist/db/migrations && mkdir -p dist/db && cp -R src/db/migrations dist/db/migrations", "db:migrate": "tsx src/db/migrate.ts", "dev": "tsx watch --clear-screen=false src/index.ts", + "format": "prettier --write .", "test": "node --import tsx --test $(find src \\( -name '*.test.ts' -o -name '*.test.js' \\) -print)", "typecheck": "tsc -p tsconfig.json --noEmit", "start": "node dist/index.js", @@ -21,6 +22,7 @@ "@types/express": "^5.0.6", "@types/node": "^25.6.0", "@types/pg": "^8.20.0", + "prettier": "^3.8.3", "tsx": "^4.22.3", "typescript": "^6.0.2" } diff --git a/apps/backend/src/services/admin/platform-config/cloudtentacles/index.ts b/apps/backend/src/services/admin/platform-config/cloudtentacles/index.ts index b572b521..fa2b23dd 100644 --- a/apps/backend/src/services/admin/platform-config/cloudtentacles/index.ts +++ b/apps/backend/src/services/admin/platform-config/cloudtentacles/index.ts @@ -55,6 +55,7 @@ import { mapAdminCloudtentaclesSourceConfig, maskPhone, } from "./mappers.js"; +import { createHttpError } from "../../../../utils/http.js"; import type { AdminCloudtentaclesCatalogQueryInput, @@ -153,7 +154,10 @@ export function updateAdminCloudtentaclesSourceConfig( export function deleteAdminCloudtentaclesSource(sourceKey: string) { const key = String(sourceKey || "").trim(); if (!key) { - throw new Error("sourceKey is required for deletion"); + throw createHttpError("cloudtentacles sourceKey 不能为空", { + statusCode: 400, + errorCode: "cloudtentacles_source_key_required", + }); } deleteCloudtentaclesSessionStateByKey(key); deleteCloudtentaclesSourceByKey(key); diff --git a/apps/backend/src/services/claim/claim-service.ts b/apps/backend/src/services/claim/claim-service.ts index 5c32645e..b45951b5 100644 --- a/apps/backend/src/services/claim/claim-service.ts +++ b/apps/backend/src/services/claim/claim-service.ts @@ -1,5 +1,6 @@ import { runtimeConfig } from '../../config/runtime.js' import { createClaimToken } from '../../repositories/claim-token-repo.js' +import { createHttpError } from '../../utils/http.js' import { addHours, nowIso } from '../../utils/time.js' import { randomToken } from '../../utils/random.js' @@ -23,7 +24,10 @@ export async function createTaskClaimToken(taskId: number | string): Promise +type JsonObject = Record type BarkSendInput = { serverUrl?: string recipient?: { diff --git a/apps/backend/src/services/notification/domain-notifications.ts b/apps/backend/src/services/notification/domain-notifications.ts index 287c9029..f14c17ad 100644 --- a/apps/backend/src/services/notification/domain-notifications.ts +++ b/apps/backend/src/services/notification/domain-notifications.ts @@ -4,7 +4,7 @@ import { sendInternalNotification } from './notification-service.js' const DEFAULT_COOLDOWN_MS = 10 * 60 * 1000 const notificationCooldownMap = new Map() -type JsonObject = Record +type JsonObject = Record type InternalNotificationPayload = { title: string body?: string @@ -13,6 +13,55 @@ type InternalNotificationPayload = { cooldownKey?: string cooldownMs?: number } +type KuaishouCloudAssetNotEnoughPayload = { + task?: unknown + flow?: unknown + assetBefore?: unknown + requiredAsset?: unknown + skuName?: unknown +} +type CloudtentaclesAuthExpiredPayload = { + pathname?: unknown + errorCode?: unknown + message?: unknown + cooldownSeconds?: unknown + sourceKey?: unknown + accountLabel?: unknown +} +type CloudtentaclesAssetLowPayload = { + asset?: unknown + threshold?: unknown + cooldownSeconds?: unknown + sourceKey?: unknown + accountLabel?: unknown +} +type Open91PendingConfigPayload = { + order?: unknown + orderNo?: unknown + productNo?: unknown + buyNum?: unknown + reason?: unknown +} +type TaskNotificationPayload = { + task?: unknown + reason?: unknown + source?: unknown + errorMessage?: unknown +} +type KuaishouCloudConsumeFailedPayload = { + task?: unknown + order?: unknown + ticketCodeMasked?: unknown + shopId?: unknown + shopName?: unknown + errorMessage?: unknown +} +type ClaimRedeemNeedsAttentionPayload = { + task?: unknown + order?: unknown + status?: unknown + errorMessage?: unknown +} export async function notifyInternalSafely(payload: InternalNotificationPayload) { const cooldownKey = String(payload.cooldownKey || '').trim() @@ -52,7 +101,7 @@ export function notifyKuaishouCloudAssetNotEnough({ assetBefore = 0, requiredAsset = 0, skuName = '', -}: JsonObject = {}) { +}: KuaishouCloudAssetNotEnoughPayload = {}) { const taskRecord = toRecord(task) const flowRecord = toRecord(flow) const binding = toRecord(flowRecord.binding) @@ -81,7 +130,7 @@ export function notifyCloudtentaclesAuthExpired({ cooldownSeconds = 600, sourceKey = '', accountLabel = '', -}: JsonObject = {}) { +}: CloudtentaclesAuthExpiredPayload = {}) { const normalizedSourceKey = String(sourceKey || '').trim() const normalizedAccountLabel = String(accountLabel || '').trim() @@ -107,7 +156,7 @@ export function notifyCloudtentaclesAssetLow({ cooldownSeconds = 1800, sourceKey = '', accountLabel = '', -}: JsonObject = {}) { +}: CloudtentaclesAssetLowPayload = {}) { const normalizedSourceKey = String(sourceKey || '').trim() const normalizedAccountLabel = String(accountLabel || '').trim() @@ -133,7 +182,7 @@ export function notifyOpen91PendingConfig({ productNo = '', buyNum = 0, reason = '未命中履约配置', -}: JsonObject = {}) { +}: Open91PendingConfigPayload = {}) { const orderRecord = toRecord(order) return notifyInternalSafely({ @@ -152,7 +201,7 @@ export function notifyOpen91PendingConfig({ export function notifyKuaishouCloudBindUrlRefreshFailed({ task = {}, errorMessage = '', -}: JsonObject = {}) { +}: TaskNotificationPayload = {}) { const taskRecord = toRecord(task) return notifyInternalSafely({ @@ -174,7 +223,7 @@ export function notifyKuaishouCloudConsumeFailed({ shopId = '', shopName = '', errorMessage = '', -}: JsonObject = {}) { +}: KuaishouCloudConsumeFailedPayload = {}) { const taskRecord = toRecord(task) const orderRecord = toRecord(order) @@ -196,7 +245,7 @@ export function notifyTaskAutoManualReview({ task = {}, reason = '', source = '', -}: JsonObject = {}) { +}: TaskNotificationPayload = {}) { const taskRecord = toRecord(task) return notifyInternalSafely({ @@ -216,7 +265,7 @@ export function notifyClaimRedeemNeedsAttention({ order = {}, status = '', errorMessage = '', -}: JsonObject = {}) { +}: ClaimRedeemNeedsAttentionPayload = {}) { const taskRecord = toRecord(task) const orderRecord = toRecord(order) diff --git a/apps/backend/src/services/notification/wpush-service.ts b/apps/backend/src/services/notification/wpush-service.ts index 592ed59d..69ae72a2 100644 --- a/apps/backend/src/services/notification/wpush-service.ts +++ b/apps/backend/src/services/notification/wpush-service.ts @@ -6,7 +6,7 @@ import { createHttpError } from '../../utils/http.js' const DEFAULT_WPUSH_ENDPOINT = 'https://api.wpush.cn/api/v1/send' const DEFAULT_TIMEOUT_MS = 10000 -type JsonObject = Record +type JsonObject = Record type NodeHttpResponse = { ok: boolean status: number diff --git a/apps/backend/src/services/open-91/config.ts b/apps/backend/src/services/open-91/config.ts index 49bc5ff2..af4a3781 100644 --- a/apps/backend/src/services/open-91/config.ts +++ b/apps/backend/src/services/open-91/config.ts @@ -1,5 +1,6 @@ import { runtimeConfig } from '../../config/runtime.js' import { createHttpError } from '../../utils/http.js' +import type { RuntimeConfig } from '../../types/runtime-config.js' export const OPEN_91_PROVIDER = '91kaquan' export const OPEN_91_PLATFORM = 'kuaishou' @@ -7,10 +8,13 @@ export const OPEN_91_SUCCESS_MESSAGE = '接口调用成功' export const OPEN_91_DEFAULT_FAIL_CODE = 1204 export const OPEN_91_COST_EXCEED_FAIL_CODE = 1220 -type JsonObject = Record +type Open91RuntimeConfig = RuntimeConfig['platforms']['ninetyone'] -export function getOpen91Config() { - const config = (runtimeConfig.platforms?.ninetyone || {}) as JsonObject +export function getOpen91Config(overrides: Partial = {}) { + const config = { + ...(runtimeConfig.platforms?.ninetyone || {}), + ...overrides, + } return { userId: String(config.userId || '').trim(), diff --git a/apps/backend/src/services/order/order-service.ts b/apps/backend/src/services/order/order-service.ts index 0d8e1690..ede473d8 100644 --- a/apps/backend/src/services/order/order-service.ts +++ b/apps/backend/src/services/order/order-service.ts @@ -9,6 +9,7 @@ import { syncDeliveryTasksForOrder } from './delivery-task-service.js' import { resolveOrderItemForFulfillment } from './product-match-service.js' import { nowIso } from '../../utils/time.js' import { logIntegration } from '../../utils/logger.js' +import { createHttpError } from '../../utils/http.js' import type { OrderItemRow, OrderRow, TaskRow } from '../../types/repository/rows.js' type SourceOrderItem = { @@ -173,7 +174,10 @@ export async function upsertOrderFromSource( }) if (!order) { - throw new Error('订单写入失败') + throw createHttpError('订单写入失败', { + statusCode: 500, + errorCode: 'order_write_failed', + }) } const orderItems = await replaceOrderItems( diff --git a/apps/backend/src/services/platforms/cloudtentacles/session-state-service.ts b/apps/backend/src/services/platforms/cloudtentacles/session-state-service.ts index 412bb06d..fdda69c1 100644 --- a/apps/backend/src/services/platforms/cloudtentacles/session-state-service.ts +++ b/apps/backend/src/services/platforms/cloudtentacles/session-state-service.ts @@ -2,6 +2,7 @@ import fs from 'node:fs' import path from 'node:path' import { PROJECT_ROOT } from '../../../config/runtime.js' +import { createHttpError } from '../../../utils/http.js' const CLOUDTENTACLES_SESSION_FILE_PATH = path.join(PROJECT_ROOT, 'data', 'cloudtentacles-session.json') @@ -58,7 +59,10 @@ export function saveCloudtentaclesSessionState(rawValue: unknown) { export function saveCloudtentaclesSessionStateByKey(sourceKey: unknown, rawValue: unknown) { const key = String(sourceKey || '').trim() if (!key) { - throw new Error('saveCloudtentaclesSessionStateByKey: sourceKey is required') + throw createHttpError('cloudtentacles sourceKey 不能为空', { + statusCode: 400, + errorCode: 'cloudtentacles_source_key_required', + }) } const states = loadCloudtentaclesSessionStatesFromFile() @@ -75,7 +79,10 @@ export function saveCloudtentaclesSessionStateByKey(sourceKey: unknown, rawValue export function deleteCloudtentaclesSessionStateByKey(sourceKey: unknown) { const key = String(sourceKey || '').trim() if (!key) { - throw new Error('deleteCloudtentaclesSessionStateByKey: sourceKey is required') + throw createHttpError('cloudtentacles sourceKey 不能为空', { + statusCode: 400, + errorCode: 'cloudtentacles_source_key_required', + }) } const states = loadCloudtentaclesSessionStatesFromFile() @@ -99,7 +106,10 @@ export function clearCloudtentaclesSessionState() { export function clearCloudtentaclesSessionStateByKey(sourceKey: unknown) { const key = String(sourceKey || '').trim() if (!key) { - throw new Error('clearCloudtentaclesSessionStateByKey: sourceKey is required') + throw createHttpError('cloudtentacles sourceKey 不能为空', { + statusCode: 400, + errorCode: 'cloudtentacles_source_key_required', + }) } const cleared = createDefaultCloudtentaclesSessionState() diff --git a/apps/backend/src/services/platforms/cloudtentacles/source-config-service.ts b/apps/backend/src/services/platforms/cloudtentacles/source-config-service.ts index 5ec9fba9..c37fad9c 100644 --- a/apps/backend/src/services/platforms/cloudtentacles/source-config-service.ts +++ b/apps/backend/src/services/platforms/cloudtentacles/source-config-service.ts @@ -2,6 +2,7 @@ import fs from 'node:fs' import path from 'node:path' import { PROJECT_ROOT } from '../../../config/runtime.js' +import { createHttpError } from '../../../utils/http.js' const CLOUDTENTACLES_SOURCES_FILE_PATH = path.join(PROJECT_ROOT, 'data', 'cloudtentacles-sources.json') @@ -63,7 +64,10 @@ export function saveCloudtentaclesSourcesList(rawValue: unknown) { export function saveCloudtentaclesSourceByKey(sourceKey: unknown, data: JsonObject = {}) { const key = String(sourceKey || '').trim() if (!key) { - throw new Error('saveCloudtentaclesSourceByKey: sourceKey is required') + throw createHttpError('cloudtentacles sourceKey 不能为空', { + statusCode: 400, + errorCode: 'cloudtentacles_source_key_required', + }) } const config = loadCloudtentaclesSourcesConfigFromFile() @@ -87,10 +91,16 @@ export function saveCloudtentaclesSourceByKey(sourceKey: unknown, data: JsonObje export function deleteCloudtentaclesSourceByKey(sourceKey: unknown) { const key = String(sourceKey || '').trim() if (!key) { - throw new Error('deleteCloudtentaclesSourceByKey: sourceKey is required') + throw createHttpError('cloudtentacles sourceKey 不能为空', { + statusCode: 400, + errorCode: 'cloudtentacles_source_key_required', + }) } if (key === 'default') { - throw new Error('deleteCloudtentaclesSourceByKey: cannot delete the default source') + throw createHttpError('不能删除默认 cloudtentacles 账号', { + statusCode: 409, + errorCode: 'cloudtentacles_default_source_forbidden', + }) } const config = loadCloudtentaclesSourcesConfigFromFile() diff --git a/apps/backend/src/services/scheduler/scheduler-service.ts b/apps/backend/src/services/scheduler/scheduler-service.ts index 2468a926..bac8939b 100644 --- a/apps/backend/src/services/scheduler/scheduler-service.ts +++ b/apps/backend/src/services/scheduler/scheduler-service.ts @@ -1,4 +1,5 @@ import { logError, logInfo, logWarn } from '../../utils/logger.js' +import { createHttpError } from '../../utils/http.js' import { getCloudtentaclesHealthJob, getScheduledJobsConfig, @@ -53,7 +54,10 @@ export async function runScheduledJobNow(jobId: unknown) { const job = config.jobs.find((item) => String(item.id || '').trim() === String(jobId || '').trim()) if (!job) { - throw new Error('定时任务不存在') + throw createHttpError('定时任务不存在', { + statusCode: 404, + errorCode: 'scheduled_job_not_found', + }) } const currentTimer = timers.get(job.id) @@ -171,7 +175,10 @@ function dispatchJob(job: JsonObject) { return runCloudtentaclesHealthJob(job) } - throw new Error(`不支持的定时任务类型:${job.type}`) + throw createHttpError(`不支持的定时任务类型:${job.type}`, { + statusCode: 400, + errorCode: 'unsupported_scheduled_job_type', + }) } function updateJobState(jobId: string, patch: JsonObject) {