优化后端代码质量基础

This commit is contained in:
yml2213
2026-05-26 10:48:30 +08:00
parent a2bce8b272
commit d126b35579
13 changed files with 142 additions and 25 deletions
+6
View File
@@ -0,0 +1,6 @@
{
"semi": false,
"singleQuote": true,
"printWidth": 100,
"trailingComma": "all"
}
+17
View File
@@ -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",
+2
View File
@@ -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"
}
@@ -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);
@@ -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<Tas
})
if (!token) {
throw new Error('领取 token 创建失败')
throw createHttpError('领取 token 创建失败', {
statusCode: 500,
errorCode: 'claim_token_create_failed',
})
}
return {
@@ -2,7 +2,7 @@ import { createHttpError } from '../../utils/http.js'
const DEFAULT_TIMEOUT_MS = 10000
type JsonObject = Record<string, any>
type JsonObject = Record<string, unknown>
type BarkSendInput = {
serverUrl?: string
recipient?: {
@@ -4,7 +4,7 @@ import { sendInternalNotification } from './notification-service.js'
const DEFAULT_COOLDOWN_MS = 10 * 60 * 1000
const notificationCooldownMap = new Map<string, number>()
type JsonObject = Record<string, any>
type JsonObject = Record<string, unknown>
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)
@@ -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<string, any>
type JsonObject = Record<string, unknown>
type NodeHttpResponse = {
ok: boolean
status: number
+7 -3
View File
@@ -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<string, any>
type Open91RuntimeConfig = RuntimeConfig['platforms']['ninetyone']
export function getOpen91Config() {
const config = (runtimeConfig.platforms?.ninetyone || {}) as JsonObject
export function getOpen91Config(overrides: Partial<Open91RuntimeConfig> = {}) {
const config = {
...(runtimeConfig.platforms?.ninetyone || {}),
...overrides,
}
return {
userId: String(config.userId || '').trim(),
@@ -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(
@@ -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()
@@ -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()
@@ -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) {