From 98781f484ce912304a708357d4b0d52c643608ef Mon Sep 17 00:00:00 2001 From: yml Date: Thu, 21 May 2026 16:23:04 +0800 Subject: [PATCH] =?UTF-8?q?=E5=90=8E=E7=AB=AF=E8=BF=81=E7=A7=BB=E4=BA=91?= =?UTF-8?q?=E8=A7=A6=E6=89=8B=E8=AF=B7=E6=B1=82=E4=B8=8E=E7=9B=AE=E5=BD=95?= =?UTF-8?q?=E6=9C=8D=E5=8A=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...{catalog-service.js => catalog-service.ts} | 26 +++++------ .../{http-client.js => http-client.ts} | 46 +++++++++++++------ ...napsack-service.js => knapsack-service.ts} | 10 ++-- ...{session-service.js => session-service.ts} | 14 +++--- docs/backend-typescript-migration-plan.md | 11 +++++ 5 files changed, 68 insertions(+), 39 deletions(-) rename apps/backend/src/services/platforms/cloudtentacles/{catalog-service.js => catalog-service.ts} (89%) rename apps/backend/src/services/platforms/cloudtentacles/{http-client.js => http-client.ts} (82%) rename apps/backend/src/services/platforms/cloudtentacles/{knapsack-service.js => knapsack-service.ts} (87%) rename apps/backend/src/services/platforms/cloudtentacles/{session-service.js => session-service.ts} (93%) diff --git a/apps/backend/src/services/platforms/cloudtentacles/catalog-service.js b/apps/backend/src/services/platforms/cloudtentacles/catalog-service.ts similarity index 89% rename from apps/backend/src/services/platforms/cloudtentacles/catalog-service.js rename to apps/backend/src/services/platforms/cloudtentacles/catalog-service.ts index f6fa2d1a..114cb835 100644 --- a/apps/backend/src/services/platforms/cloudtentacles/catalog-service.js +++ b/apps/backend/src/services/platforms/cloudtentacles/catalog-service.ts @@ -1,10 +1,10 @@ -// @ts-check - import { createHttpError } from '../../../utils/http.js' import { cloudtentaclesRequest } from './http-client.js' import { resolveCloudtentaclesConfig } from './shared.js' -export async function getCloudtentaclesAsset(payload = {}) { +type JsonObject = Record + +export async function getCloudtentaclesAsset(payload: JsonObject = {}) { const token = requireToken(payload.token, 'cloudtentacles 余额查询缺少 token', 'cloudtentacles_asset_missing_token') const config = resolveCloudtentaclesConfig(payload) @@ -24,7 +24,7 @@ export async function getCloudtentaclesAsset(payload = {}) { } } -export async function getCloudtentaclesCategories(payload = {}) { +export async function getCloudtentaclesCategories(payload: JsonObject = {}) { const token = requireToken(payload.token, 'cloudtentacles 分类查询缺少 token', 'cloudtentacles_categories_missing_token') const config = resolveCloudtentaclesConfig(payload) @@ -47,7 +47,7 @@ export async function getCloudtentaclesCategories(payload = {}) { } } -export async function listCloudtentaclesSku(payload = {}) { +export async function listCloudtentaclesSku(payload: JsonObject = {}) { const token = requireToken(payload.token, 'cloudtentacles SKU 列表查询缺少 token', 'cloudtentacles_sku_list_missing_token') const config = resolveCloudtentaclesConfig(payload) @@ -70,7 +70,7 @@ export async function listCloudtentaclesSku(payload = {}) { } } -export async function buyCloudtentaclesSku(payload = {}) { +export async function buyCloudtentaclesSku(payload: JsonObject = {}) { const token = requireToken(payload.token, 'cloudtentacles 购买 SKU 缺少 token', 'cloudtentacles_sku_buy_missing_token') const skuId = requireId(payload.id, 'cloudtentacles 购买 SKU 缺少商品 id', 'cloudtentacles_sku_buy_missing_id') const count = requireCount(payload.count) @@ -96,7 +96,7 @@ export async function buyCloudtentaclesSku(payload = {}) { } } -export async function useCloudtentaclesSku(payload = {}) { +export async function useCloudtentaclesSku(payload: JsonObject = {}) { const token = requireToken(payload.token, 'cloudtentacles 发货缺少 token', 'cloudtentacles_sku_use_missing_token') const skuId = requireId(payload.id, 'cloudtentacles 发货缺少商品 id', 'cloudtentacles_sku_use_missing_id') const virtualNumberId = requireId( @@ -140,7 +140,7 @@ export async function useCloudtentaclesSku(payload = {}) { } } -function mapCategoryItem(item) { +function mapCategoryItem(item: unknown) { const source = isPlainObject(item) ? item : {} return { @@ -158,7 +158,7 @@ function mapCategoryItem(item) { } } -function mapSkuItem(item) { +function mapSkuItem(item: unknown) { const source = isPlainObject(item) ? item : {} return { @@ -177,7 +177,7 @@ function mapSkuItem(item) { } } -function requireToken(value, message, errorCode) { +function requireToken(value: unknown, message: string, errorCode: string) { const token = String(value || '').trim() if (!token) { throw createHttpError(message, { @@ -189,7 +189,7 @@ function requireToken(value, message, errorCode) { return token } -function requireId(value, message, errorCode) { +function requireId(value: unknown, message: string, errorCode: string) { const id = Number(value) if (!Number.isInteger(id) || id <= 0) { throw createHttpError(message, { @@ -201,7 +201,7 @@ function requireId(value, message, errorCode) { return id } -function requireCount(value) { +function requireCount(value: unknown) { const count = Number(value) if (!Number.isInteger(count) || count <= 0) { throw createHttpError('cloudtentacles 购买数量无效', { @@ -213,6 +213,6 @@ function requireCount(value) { return count } -function isPlainObject(value) { +function isPlainObject(value: unknown): value is JsonObject { return Boolean(value) && typeof value === 'object' && !Array.isArray(value) } diff --git a/apps/backend/src/services/platforms/cloudtentacles/http-client.js b/apps/backend/src/services/platforms/cloudtentacles/http-client.ts similarity index 82% rename from apps/backend/src/services/platforms/cloudtentacles/http-client.js rename to apps/backend/src/services/platforms/cloudtentacles/http-client.ts index f4498f27..b37debf9 100644 --- a/apps/backend/src/services/platforms/cloudtentacles/http-client.js +++ b/apps/backend/src/services/platforms/cloudtentacles/http-client.ts @@ -1,5 +1,3 @@ -// @ts-check - import http from 'node:http' import https from 'node:https' @@ -8,9 +6,21 @@ import { logInfo } from '../../../utils/logger.js' import { notifyCloudtentaclesAuthExpired } from '../../notification/domain-notifications.js' import { buildCloudtentaclesHeaders, buildCloudtentaclesUrl, resolveCloudtentaclesConfig } from './shared.js' -export async function cloudtentaclesRequest(pathname, options = {}) { +type JsonObject = Record +type HeaderAdapter = { + get(name: unknown): string | null +} +type NodeHttpResponse = { + ok: boolean + status: number + headers: HeaderAdapter + bodyText: string +} + +export async function cloudtentaclesRequest(pathname: unknown, options: JsonObject = {}) { + const normalizedPathname = String(pathname || '').trim() const config = resolveCloudtentaclesConfig(options) - const url = buildCloudtentaclesUrl(config.baseUrl, pathname, options.searchParams) + const url = buildCloudtentaclesUrl(config.baseUrl, normalizedPathname, options.searchParams) const timeoutMs = Number(options.timeoutMs || config.timeoutMs || 5000) const controller = new AbortController() const timer = setTimeout(() => controller.abort(), timeoutMs) @@ -48,7 +58,7 @@ export async function cloudtentaclesRequest(pathname, options = {}) { if (statusCode === 401 || isCloudtentaclesExpiredMessage(message)) { await notifyCloudtentaclesAuthExpired({ - pathname, + pathname: normalizedPathname, errorCode, message, }) @@ -62,7 +72,7 @@ export async function cloudtentaclesRequest(pathname, options = {}) { logInfo('[cloudtentacles/http]', '请求完成', { method, - pathname, + pathname: normalizedPathname, status: response.status, }) @@ -86,12 +96,12 @@ export async function cloudtentaclesRequest(pathname, options = {}) { } } -function isCloudtentaclesExpiredMessage(message) { +function isCloudtentaclesExpiredMessage(message: unknown) { const normalized = String(message || '').trim().toLowerCase() return normalized.includes('expired') || normalized.includes('过期') || normalized.includes('失效') } -function inferContentType(body) { +function inferContentType(body: unknown) { if (body == null) { return '' } @@ -103,7 +113,7 @@ function inferContentType(body) { return 'application/json' } -function normalizeRequestBody(body, contentType) { +function normalizeRequestBody(body: unknown, contentType: unknown) { if (body == null) { return undefined } @@ -119,7 +129,7 @@ function normalizeRequestBody(body, contentType) { return String(body) } -function tryParseJson(text) { +function tryParseJson(text: string) { try { return JSON.parse(text) } catch { @@ -127,7 +137,15 @@ function tryParseJson(text) { } } -async function requestViaNodeHttp(url, { method, headers, body, signal }) { +async function requestViaNodeHttp( + url: URL, + { method, headers, body, signal }: { + method: string + headers: JsonObject + body?: string | URLSearchParams + signal?: AbortSignal + }, +): Promise { const isHttps = url.protocol === 'https:' const transport = isHttps ? https : http @@ -137,7 +155,7 @@ async function requestViaNodeHttp(url, { method, headers, body, signal }) { headers, rejectUnauthorized: false, }, (response) => { - const chunks = [] + const chunks: Buffer[] = [] response.on('data', (chunk) => { chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk)) @@ -178,7 +196,7 @@ async function requestViaNodeHttp(url, { method, headers, body, signal }) { }) } -function createHeaderAdapter(headers) { +function createHeaderAdapter(headers: http.IncomingHttpHeaders): HeaderAdapter { const normalized = new Map() for (const [key, value] of Object.entries(headers || {})) { @@ -193,7 +211,7 @@ function createHeaderAdapter(headers) { } return { - get(name) { + get(name: unknown) { return normalized.get(String(name || '').toLowerCase()) || null }, } diff --git a/apps/backend/src/services/platforms/cloudtentacles/knapsack-service.js b/apps/backend/src/services/platforms/cloudtentacles/knapsack-service.ts similarity index 87% rename from apps/backend/src/services/platforms/cloudtentacles/knapsack-service.js rename to apps/backend/src/services/platforms/cloudtentacles/knapsack-service.ts index c417045f..339ceb71 100644 --- a/apps/backend/src/services/platforms/cloudtentacles/knapsack-service.js +++ b/apps/backend/src/services/platforms/cloudtentacles/knapsack-service.ts @@ -1,10 +1,10 @@ -// @ts-check - import { createHttpError } from '../../../utils/http.js' import { cloudtentaclesRequest } from './http-client.js' import { resolveCloudtentaclesConfig } from './shared.js' -export async function getCloudtentaclesKnapsack(payload = {}) { +type JsonObject = Record + +export async function getCloudtentaclesKnapsack(payload: JsonObject = {}) { const token = String(payload.token || '').trim() if (!token) { throw createHttpError('cloudtentacles 背包查询缺少 token', { @@ -33,7 +33,7 @@ export async function getCloudtentaclesKnapsack(payload = {}) { } } -function mapKnapsackItem(item) { +function mapKnapsackItem(item: unknown) { const source = isPlainObject(item) ? item : {} return { @@ -48,6 +48,6 @@ function mapKnapsackItem(item) { } } -function isPlainObject(value) { +function isPlainObject(value: unknown): value is JsonObject { return Boolean(value) && typeof value === 'object' && !Array.isArray(value) } diff --git a/apps/backend/src/services/platforms/cloudtentacles/session-service.js b/apps/backend/src/services/platforms/cloudtentacles/session-service.ts similarity index 93% rename from apps/backend/src/services/platforms/cloudtentacles/session-service.js rename to apps/backend/src/services/platforms/cloudtentacles/session-service.ts index a8b00ce2..dc47a801 100644 --- a/apps/backend/src/services/platforms/cloudtentacles/session-service.js +++ b/apps/backend/src/services/platforms/cloudtentacles/session-service.ts @@ -1,12 +1,12 @@ -// @ts-check - import { createHttpError } from '../../../utils/http.js' import { logInfo } from '../../../utils/logger.js' import { encryptCloudtentaclesPayload, md5CloudtentaclesPassword } from './crypto-service.js' import { cloudtentaclesRequest } from './http-client.js' import { resolveCloudtentaclesConfig } from './shared.js' -export async function sendCloudtentaclesSmsCode(payload = {}) { +type JsonObject = Record + +export async function sendCloudtentaclesSmsCode(payload: JsonObject = {}) { const username = String(payload.username || '').trim() const phone = String(payload.phone || '').trim() @@ -56,7 +56,7 @@ export async function sendCloudtentaclesSmsCode(payload = {}) { } } -export async function loginCloudtentaclesSession(payload = {}) { +export async function loginCloudtentaclesSession(payload: JsonObject = {}) { const username = String(payload.username || '').trim() const password = String(payload.password || '').trim() const phone = String(payload.phone || '').trim() @@ -131,7 +131,7 @@ export async function loginCloudtentaclesSession(payload = {}) { } } -export async function validateCloudtentaclesSession(payload = {}) { +export async function validateCloudtentaclesSession(payload: JsonObject = {}) { const token = String(payload.token || '').trim() if (!token) { @@ -181,7 +181,7 @@ export async function validateCloudtentaclesSession(payload = {}) { } } -function maskPhone(phone) { +function maskPhone(phone: unknown) { const normalized = String(phone || '').trim() if (normalized.length < 7) { return normalized @@ -190,6 +190,6 @@ function maskPhone(phone) { return `${normalized.slice(0, 3)}****${normalized.slice(-4)}` } -function isPlainObject(value) { +function isPlainObject(value: unknown): value is JsonObject { return Boolean(value) && typeof value === 'object' && !Array.isArray(value) } diff --git a/docs/backend-typescript-migration-plan.md b/docs/backend-typescript-migration-plan.md index ab017405..d7f0f917 100644 --- a/docs/backend-typescript-migration-plan.md +++ b/docs/backend-typescript-migration-plan.md @@ -647,6 +647,17 @@ - `npm run typecheck` - `npm run build` - `npm test` 共 139 个用例通过 +156. Cloudtentacles 平台 HTTP / 商品目录 / 背包 / 会话模块迁移到 `.ts`: + - `src/services/platforms/cloudtentacles/http-client.ts` + - `src/services/platforms/cloudtentacles/catalog-service.ts` + - `src/services/platforms/cloudtentacles/knapsack-service.ts` + - `src/services/platforms/cloudtentacles/session-service.ts` +157. Cloudtentacles Node HTTP 请求封装、业务错误通知、资产 / 分类 / SKU 查询、SKU 购买 / 发货、背包查询、短信验证码、登录与会话校验已进入 TS 编译链路;请求 options、HTTP response、payload 和返回数据映射补齐类型 +158. Docker 内验证通过: + - `src/services/admin/platform-config/*.test.js` 共 37 个用例通过 + - `npm run typecheck` + - `npm run build` + - `npm test` 共 139 个用例通过 ## 下一步建议