diff --git a/apps/backend/src/config/env-overrides.ts b/apps/backend/src/config/env-overrides.ts index 3de2c3bf..d8bad757 100644 --- a/apps/backend/src/config/env-overrides.ts +++ b/apps/backend/src/config/env-overrides.ts @@ -354,5 +354,10 @@ function setConfigValue( current = current[key] as Record; } - current[configPath[configPath.length - 1]] = value; + const lastKey = configPath[configPath.length - 1]; + if (!lastKey) { + return; + } + + current[lastKey] = value; } diff --git a/apps/backend/src/middleware/access-log.ts b/apps/backend/src/middleware/access-log.ts index cd3caa36..feb0ce56 100644 --- a/apps/backend/src/middleware/access-log.ts +++ b/apps/backend/src/middleware/access-log.ts @@ -43,7 +43,7 @@ function resolveRequestId(req: Request): string { } function shouldSkipAccessLog(originalUrl: string, statusCode: number): boolean { - const pathname = String(originalUrl || "").split("?")[0]; + const pathname = String(originalUrl || "").split("?")[0] || ""; return ( ["/health", "/health/live", "/health/ready"].includes(pathname) && Number(statusCode) < 400 diff --git a/apps/backend/src/repositories/order-item-repo.ts b/apps/backend/src/repositories/order-item-repo.ts index 11511cc9..10299618 100644 --- a/apps/backend/src/repositories/order-item-repo.ts +++ b/apps/backend/src/repositories/order-item-repo.ts @@ -109,6 +109,10 @@ export function resolveOrderItemSyncPlan( if (matchedIndex >= 0) { const matched = unmatchedExisting.splice(matchedIndex, 1)[0] + if (!matched) { + creates.push(item) + continue + } updates.push({ orderItemId: matched.id, item, diff --git a/apps/backend/src/repositories/task-repo.ts b/apps/backend/src/repositories/task-repo.ts index e7f2bd44..d0ed2625 100644 --- a/apps/backend/src/repositories/task-repo.ts +++ b/apps/backend/src/repositories/task-repo.ts @@ -195,18 +195,19 @@ export async function updateTask(taskId: number | string, patch: TaskUpdatePatch ) if (containsRuntimeContextPatch(patch)) { - await upsertTaskRuntimeContextWithClient(client, Number(taskId), { - runtimeSessionId: patch.runtime_session_id, - loginType: patch.login_type, - nickname: patch.nickname, - roleId: patch.role_id, - roleName: patch.role_name, - area: patch.area, - partitionName: patch.partition_name, - screenshotPath: patch.screenshot_path, - artifactsJson: patch.artifacts_json, - stateJson: patch.state_json, - }, patch.updated_at || current.updated_at) + const runtimeContextPatch: TaskRuntimeContextPatch = {} + if (patch.runtime_session_id !== undefined) runtimeContextPatch.runtimeSessionId = patch.runtime_session_id + if (patch.login_type !== undefined) runtimeContextPatch.loginType = patch.login_type + if (patch.nickname !== undefined) runtimeContextPatch.nickname = patch.nickname + if (patch.role_id !== undefined) runtimeContextPatch.roleId = patch.role_id + if (patch.role_name !== undefined) runtimeContextPatch.roleName = patch.role_name + if (patch.area !== undefined) runtimeContextPatch.area = patch.area + if (patch.partition_name !== undefined) runtimeContextPatch.partitionName = patch.partition_name + if (patch.screenshot_path !== undefined) runtimeContextPatch.screenshotPath = patch.screenshot_path + if (patch.artifacts_json !== undefined) runtimeContextPatch.artifactsJson = patch.artifacts_json + if (patch.state_json !== undefined) runtimeContextPatch.stateJson = patch.state_json + + await upsertTaskRuntimeContextWithClient(client, Number(taskId), runtimeContextPatch, patch.updated_at || current.updated_at) } return getTaskByIdWithExecutor(client.query.bind(client) as TaskQueryExecutor, taskId) diff --git a/apps/backend/src/routes/admin/session.ts b/apps/backend/src/routes/admin/session.ts index 5bd5d5a3..11cc5c3d 100644 --- a/apps/backend/src/routes/admin/session.ts +++ b/apps/backend/src/routes/admin/session.ts @@ -123,5 +123,13 @@ export function extractBearerToken(req: Request): string { }) } - return matched[1] + const token = matched[1]?.trim() + if (!token) { + throw createHttpError('未登录或登录已失效', { + statusCode: 401, + errorCode: 'admin_auth_required', + }) + } + + return token } diff --git a/apps/backend/src/routes/open-91.ts b/apps/backend/src/routes/open-91.ts index 74fd3b43..4320e4ee 100644 --- a/apps/backend/src/routes/open-91.ts +++ b/apps/backend/src/routes/open-91.ts @@ -16,6 +16,14 @@ const open91RateLimit = createRateLimitMiddleware({ }, }) +function resolveErrorStatusCode(error: unknown): number { + if (!error || typeof error !== 'object' || !('statusCode' in error)) { + return 500 + } + + return Number(error.statusCode || 500) +} + router.post('/orders/create', open91RateLimit, async (req, res) => { const requestId = createRequestId('91') const startedAt = Date.now() @@ -38,7 +46,7 @@ router.post('/orders/create', open91RateLimit, async (req, res) => { res.status(200).json(result) } catch (error) { const message = error instanceof Error ? error.message : '系统错误' - const code = Number(error?.statusCode || 500) >= 500 ? 500 : 400 + const code = resolveErrorStatusCode(error) >= 500 ? 500 : 400 logIntegration('[open-91/create]', '91卡券异步下单处理失败', { requestId, durationMs: Date.now() - startedAt, @@ -70,7 +78,7 @@ router.post('/orders/query', open91RateLimit, async (req, res) => { res.status(200).json(result) } catch (error) { const message = error instanceof Error ? error.message : '系统错误' - const code = Number(error?.statusCode || 500) >= 500 ? 500 : 400 + const code = resolveErrorStatusCode(error) >= 500 ? 500 : 400 logIntegration('[open-91/query]', '91卡券订单查询处理失败', { requestId, durationMs: Date.now() - startedAt, diff --git a/apps/backend/src/services/notification/domain-notifications.ts b/apps/backend/src/services/notification/domain-notifications.ts index ab57db72..287c9029 100644 --- a/apps/backend/src/services/notification/domain-notifications.ts +++ b/apps/backend/src/services/notification/domain-notifications.ts @@ -26,9 +26,9 @@ export async function notifyInternalSafely(payload: InternalNotificationPayload) try { const result = await sendInternalNotification({ title: payload.title, - body: payload.body, - category: payload.category, - url: payload.url, + ...(payload.body !== undefined ? { body: payload.body } : {}), + ...(payload.category !== undefined ? { category: payload.category } : {}), + ...(payload.url !== undefined ? { url: payload.url } : {}), }) markNotificationCooldown(cooldownKey) return result diff --git a/apps/backend/src/services/notification/notification-service.ts b/apps/backend/src/services/notification/notification-service.ts index 090d5421..c4e667cd 100644 --- a/apps/backend/src/services/notification/notification-service.ts +++ b/apps/backend/src/services/notification/notification-service.ts @@ -40,12 +40,12 @@ export async function sendInternalNotification(input: NotificationInput = {}) { ...(await Promise.all(barkRecipients.map(async (recipient: JsonObject) => { try { const result = await sendBarkNotification({ - serverUrl: bark.serverUrl, recipient, title, body, group: `订单系统/${category}`, - url: input.url, + ...(bark.serverUrl !== undefined ? { serverUrl: bark.serverUrl } : {}), + ...(input.url !== undefined ? { url: input.url } : {}), }) return mapNotificationResult('bark', recipient, recipient.deviceKey, true, '', result.status, result.response) } catch (error) { diff --git a/apps/backend/src/services/order/order-service.ts b/apps/backend/src/services/order/order-service.ts index da7a1940..0d8e1690 100644 --- a/apps/backend/src/services/order/order-service.ts +++ b/apps/backend/src/services/order/order-service.ts @@ -115,8 +115,8 @@ export async function upsertOrderFromSource( provider: event.provider, platform: event.platform, shopId: event.shopId, - shopIdAliases: event.shopIdAliases, item, + ...(event.shopIdAliases !== undefined ? { shopIdAliases: event.shopIdAliases } : {}), })), ) const configuredItems = (resolvedItems as FulfillmentOrderItem[]).filter((item) => item.isConfigured) @@ -155,7 +155,7 @@ export async function upsertOrderFromSource( totalAmount: event.totalAmount, currency: event.currency, rawPayloadJson: JSON.stringify(event.rawPayload), - paidAt: event.paidAt, + ...(event.paidAt !== undefined ? { paidAt: event.paidAt } : {}), } const mergedPayload = mergeSourceOrderState(existing, basePayload) diff --git a/apps/backend/src/services/platforms/cloudtentacles/http-client.ts b/apps/backend/src/services/platforms/cloudtentacles/http-client.ts index cecf84c6..0f903759 100644 --- a/apps/backend/src/services/platforms/cloudtentacles/http-client.ts +++ b/apps/backend/src/services/platforms/cloudtentacles/http-client.ts @@ -34,11 +34,12 @@ export async function cloudtentaclesRequest(pathname: unknown, options: JsonObje }) try { + const body = normalizeRequestBody(options.body, headers['content-type']) const response = await requestViaNodeHttp(url, { method, headers, - body: normalizeRequestBody(options.body, headers['content-type']), signal: controller.signal, + ...(body !== undefined ? { body } : {}), }) const rawText = response.bodyText diff --git a/apps/backend/src/services/platforms/kuaishou-eticket/http-client.ts b/apps/backend/src/services/platforms/kuaishou-eticket/http-client.ts index 038494db..8fe8fd2f 100644 --- a/apps/backend/src/services/platforms/kuaishou-eticket/http-client.ts +++ b/apps/backend/src/services/platforms/kuaishou-eticket/http-client.ts @@ -42,15 +42,16 @@ export async function kuaishouEticketRequest(pathname: string, options: Kuaishou userAgent: config.userAgent, cookie: options.cookie ?? config.cookie, contentType: options.contentType === null ? '' : options.contentType || inferContentType(options.body), - extra: options.headers, + ...(options.headers !== undefined ? { extra: options.headers } : {}), }) try { + const body = normalizeRequestBody(options.body, headers['content-type']) const response = await requestViaNodeHttp(url, { method, headers, - body: normalizeRequestBody(options.body, headers['content-type']), signal: controller.signal, + ...(body !== undefined ? { body } : {}), }) const rawText = response.bodyText diff --git a/apps/backend/src/services/platforms/ninetyone/order-service.ts b/apps/backend/src/services/platforms/ninetyone/order-service.ts index 3bed443c..f46a801c 100644 --- a/apps/backend/src/services/platforms/ninetyone/order-service.ts +++ b/apps/backend/src/services/platforms/ninetyone/order-service.ts @@ -113,6 +113,13 @@ export async function upsertOpen91PendingOrder(payload: JsonObject = {}, config: } const [item] = event.items + if (!item) { + throw createHttpError('91卡券订单商品明细为空', { + statusCode: 400, + errorCode: 'open91_order_item_required', + }) + } + const orderItems = await replaceOrderItems(order.id, [ { skuCode: item.skuCode, diff --git a/apps/backend/src/startup/state.ts b/apps/backend/src/startup/state.ts index 53f36847..9cc3c531 100644 --- a/apps/backend/src/startup/state.ts +++ b/apps/backend/src/startup/state.ts @@ -62,7 +62,7 @@ export function buildHealthPayload(startupState: StartupState, shutdownStarted: export function formatStartupError(error: unknown) { if (error instanceof Error && error.message) { - return error.message.split("\n")[0].trim(); + return error.message.split("\n")[0]?.trim() || "未知错误"; } return String(error || "未知错误").trim(); diff --git a/apps/backend/tsconfig.json b/apps/backend/tsconfig.json index da0b9aed..4a2c1a75 100644 --- a/apps/backend/tsconfig.json +++ b/apps/backend/tsconfig.json @@ -6,9 +6,9 @@ "allowJs": true, "checkJs": true, "noEmit": true, - "strict": false, - "noImplicitAny": true, - "strictNullChecks": true, + "strict": true, + "noUncheckedIndexedAccess": true, + "exactOptionalPropertyTypes": true, "skipLibCheck": true, "types": ["node"], "lib": ["ES2022"]