diff --git a/apps/backend/src/services/open-91/shared.js b/apps/backend/src/services/open-91/shared.ts similarity index 85% rename from apps/backend/src/services/open-91/shared.js rename to apps/backend/src/services/open-91/shared.ts index e3584619..096b28ca 100644 --- a/apps/backend/src/services/open-91/shared.js +++ b/apps/backend/src/services/open-91/shared.ts @@ -9,10 +9,10 @@ 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 + export function getOpen91Config() { - const config = /** @type {import('../../types/runtime-config.js').RuntimeConfig['platforms']['ninetyone']} */ ( - runtimeConfig.platforms?.ninetyone || {} - ) + const config = (runtimeConfig.platforms?.ninetyone || {}) as JsonObject return { userId: String(config.userId || '').trim(), @@ -49,7 +49,7 @@ export function normalizeOpen91String(value) { return String(value || '').trim() } -export function normalizeOpen91CreatePayload(payload = {}) { +export function normalizeOpen91CreatePayload(payload: JsonObject = {}) { return { orderNo: normalizeOpen91String(payload.orderNo), productNo: normalizeOpen91String(payload.productNo), @@ -62,7 +62,7 @@ export function normalizeOpen91CreatePayload(payload = {}) { } } -export function normalizeOpen91QueryPayload(payload = {}) { +export function normalizeOpen91QueryPayload(payload: JsonObject = {}) { return { orderNo: normalizeOpen91String(payload.orderNo), timestamp: normalizeOpen91Timestamp(payload.timestamp), @@ -71,7 +71,7 @@ export function normalizeOpen91QueryPayload(payload = {}) { } } -export function assertOpen91CreatePayload(payload, config = assertOpen91Config()) { +export function assertOpen91CreatePayload(payload: JsonObject, config = assertOpen91Config()) { if (!payload.orderNo) { throw createHttpError('缺少 orderNo', { statusCode: 400, @@ -96,7 +96,7 @@ export function assertOpen91CreatePayload(payload, config = assertOpen91Config() assertOpen91CommonPayload(payload, config) } -export function assertOpen91QueryPayload(payload, config = assertOpen91Config()) { +export function assertOpen91QueryPayload(payload: JsonObject, config = assertOpen91Config()) { if (!payload.orderNo) { throw createHttpError('缺少 orderNo', { statusCode: 400, @@ -107,7 +107,7 @@ export function assertOpen91QueryPayload(payload, config = assertOpen91Config()) assertOpen91CommonPayload(payload, config) } -function assertOpen91CommonPayload(payload, config) { +function assertOpen91CommonPayload(payload: JsonObject, config: JsonObject) { if (!payload.timestamp) { throw createHttpError('缺少 timestamp', { statusCode: 400, @@ -137,7 +137,7 @@ function assertOpen91CommonPayload(payload, config) { } } -export function assertOpen91Timestamp(timestamp, toleranceSeconds = assertOpen91Config().timestampToleranceSeconds) { +export function assertOpen91Timestamp(timestamp: unknown, toleranceSeconds = assertOpen91Config().timestampToleranceSeconds) { const nowSeconds = Math.floor(Date.now() / 1000) if (Math.abs(nowSeconds - Number(timestamp)) > Math.max(1, Number(toleranceSeconds) || 600)) { throw createHttpError('timestamp 已过期', { @@ -147,7 +147,7 @@ export function assertOpen91Timestamp(timestamp, toleranceSeconds = assertOpen91 } } -export function buildOpen91SignSource(params = {}, { secret } = assertOpen91Config()) { +export function buildOpen91SignSource(params: JsonObject = {}, { secret } = assertOpen91Config()) { const entries = Object.entries(params) .filter(([key]) => key !== 'sign') .sort(([left], [right]) => { @@ -164,7 +164,7 @@ export function buildOpen91SignSource(params = {}, { secret } = assertOpen91Conf return `${secret}${queryString}${secret}` } -export function signOpen91Payload(params = {}, config = assertOpen91Config()) { +export function signOpen91Payload(params: JsonObject = {}, config = assertOpen91Config()) { return crypto .createHash('md5') .update(buildOpen91SignSource(params, config), 'utf8') @@ -172,13 +172,13 @@ export function signOpen91Payload(params = {}, config = assertOpen91Config()) { .toUpperCase() } -export function verifyOpen91Signature(params = {}, config = assertOpen91Config()) { +export function verifyOpen91Signature(params: JsonObject = {}, config = assertOpen91Config()) { const expected = signOpen91Payload(params, config) const actual = normalizeOpen91String(params.sign).toUpperCase() return expected === actual } -export function assertOpen91Signature(params = {}, config = assertOpen91Config()) { +export function assertOpen91Signature(params: JsonObject = {}, config = assertOpen91Config()) { if (!verifyOpen91Signature(params, config)) { throw createHttpError('验签失败', { statusCode: 400, @@ -203,7 +203,7 @@ export function encryptOpen91Cards(cards = [], secret = assertOpen91Config().sec return `${cipher.update(plainText, 'utf8', 'base64')}${cipher.final('base64')}` } -export function buildOpen91Cards(cards = [], config = assertOpen91Config()) { +export function buildOpen91Cards(cards: unknown[] = [], config = assertOpen91Config()) { const encoding = normalizeOpen91String(config.cardsEncoding).toLowerCase() if (encoding && encoding !== 'aes-256-ecb-base64') { throw createHttpError(`暂不支持的 cardsEncoding: ${config.cardsEncoding}`, { @@ -215,7 +215,7 @@ export function buildOpen91Cards(cards = [], config = assertOpen91Config()) { return encryptOpen91Cards(cards, config.secret) } -export function buildOpen91SuccessResponse(data, message = OPEN_91_SUCCESS_MESSAGE) { +export function buildOpen91SuccessResponse(data: unknown, message = OPEN_91_SUCCESS_MESSAGE) { return { code: 200, message, @@ -223,7 +223,7 @@ export function buildOpen91SuccessResponse(data, message = OPEN_91_SUCCESS_MESSA } } -export function buildOpen91ErrorResponse(message, code = 500) { +export function buildOpen91ErrorResponse(message: unknown, code = 500) { return { code, message: normalizeOpen91String(message) || '系统错误', @@ -231,7 +231,7 @@ export function buildOpen91ErrorResponse(message, code = 500) { } } -export function buildOpen91OutTradeNo(order) { +export function buildOpen91OutTradeNo(order: JsonObject) { const orderId = Number(order?.id || 0) return orderId > 0 ? `OS${String(orderId).padStart(10, '0')}` : '' } @@ -241,7 +241,7 @@ export function buildOpen91OrderCost(value = 0) { return Number.isFinite(amount) ? amount.toFixed(4) : '0.0000' } -export function buildOpen91CardItem({ claimUrl = '', expireTime = '' } = {}) { +export function buildOpen91CardItem({ claimUrl = '', expireTime = '' }: { claimUrl?: unknown, expireTime?: unknown } = {}) { const normalizedClaimUrl = normalizeOpen91String(claimUrl) return { cardNo: normalizedClaimUrl, @@ -251,8 +251,7 @@ export function buildOpen91CardItem({ claimUrl = '', expireTime = '' } = {}) { } } -/** @param {unknown} value */ -export function formatOpen91ExpireTime(value = '') { +export function formatOpen91ExpireTime(value: unknown = '') { const normalized = normalizeOpen91String(value) if (!normalized) { return '' @@ -282,7 +281,9 @@ export function formatOpen91ExpireTime(value = '') { return `${byType.year}-${byType.month}-${byType.day} ${byType.hour}:${byType.minute}:${byType.second}` } -export function resolveOpen91QueryState({ tasks = [], readyTaskIds = [] } = {}) { +export function resolveOpen91QueryState( + { tasks = [], readyTaskIds = [] }: { tasks?: JsonObject[], readyTaskIds?: unknown[] } = {}, +) { const normalizedTasks = Array.isArray(tasks) ? tasks : [] const readyTaskIdSet = new Set((Array.isArray(readyTaskIds) ? readyTaskIds : []).map((item) => Number(item))) diff --git a/docs/backend-typescript-migration-plan.md b/docs/backend-typescript-migration-plan.md index fb273d1d..daf663d8 100644 --- a/docs/backend-typescript-migration-plan.md +++ b/docs/backend-typescript-migration-plan.md @@ -545,6 +545,14 @@ - `npm run typecheck` - `npm run build` - `npm test` 共 139 个用例通过 +123. 91卡券 Open API 共享模块迁移到 `.ts`: + - `src/services/open-91/shared.ts` +124. 91 卡券配置读取、请求归一化、签名 / 验签、cards AES 加密、卡密响应构造与查询状态解析已进入 TS 编译链路;动态配置与请求 payload 补齐对象类型 +125. Docker 内验证通过: + - `src/services/open-91/shared.test.js` 共 6 个用例通过 + - `npm run typecheck` + - `npm run build` + - `npm test` 共 139 个用例通过 ## 下一步建议