后端迁移云触手请求与目录服务

This commit is contained in:
yml
2026-05-21 16:23:04 +08:00
parent ce6a6af820
commit 98781f484c
5 changed files with 68 additions and 39 deletions
@@ -1,10 +1,10 @@
// @ts-check
import { createHttpError } from '../../../utils/http.js' import { createHttpError } from '../../../utils/http.js'
import { cloudtentaclesRequest } from './http-client.js' import { cloudtentaclesRequest } from './http-client.js'
import { resolveCloudtentaclesConfig } from './shared.js' import { resolveCloudtentaclesConfig } from './shared.js'
export async function getCloudtentaclesAsset(payload = {}) { type JsonObject = Record<string, any>
export async function getCloudtentaclesAsset(payload: JsonObject = {}) {
const token = requireToken(payload.token, 'cloudtentacles 余额查询缺少 token', 'cloudtentacles_asset_missing_token') const token = requireToken(payload.token, 'cloudtentacles 余额查询缺少 token', 'cloudtentacles_asset_missing_token')
const config = resolveCloudtentaclesConfig(payload) 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 token = requireToken(payload.token, 'cloudtentacles 分类查询缺少 token', 'cloudtentacles_categories_missing_token')
const config = resolveCloudtentaclesConfig(payload) 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 token = requireToken(payload.token, 'cloudtentacles SKU 列表查询缺少 token', 'cloudtentacles_sku_list_missing_token')
const config = resolveCloudtentaclesConfig(payload) 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 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 skuId = requireId(payload.id, 'cloudtentacles 购买 SKU 缺少商品 id', 'cloudtentacles_sku_buy_missing_id')
const count = requireCount(payload.count) 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 token = requireToken(payload.token, 'cloudtentacles 发货缺少 token', 'cloudtentacles_sku_use_missing_token')
const skuId = requireId(payload.id, 'cloudtentacles 发货缺少商品 id', 'cloudtentacles_sku_use_missing_id') const skuId = requireId(payload.id, 'cloudtentacles 发货缺少商品 id', 'cloudtentacles_sku_use_missing_id')
const virtualNumberId = requireId( const virtualNumberId = requireId(
@@ -140,7 +140,7 @@ export async function useCloudtentaclesSku(payload = {}) {
} }
} }
function mapCategoryItem(item) { function mapCategoryItem(item: unknown) {
const source = isPlainObject(item) ? item : {} const source = isPlainObject(item) ? item : {}
return { return {
@@ -158,7 +158,7 @@ function mapCategoryItem(item) {
} }
} }
function mapSkuItem(item) { function mapSkuItem(item: unknown) {
const source = isPlainObject(item) ? item : {} const source = isPlainObject(item) ? item : {}
return { 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() const token = String(value || '').trim()
if (!token) { if (!token) {
throw createHttpError(message, { throw createHttpError(message, {
@@ -189,7 +189,7 @@ function requireToken(value, message, errorCode) {
return token return token
} }
function requireId(value, message, errorCode) { function requireId(value: unknown, message: string, errorCode: string) {
const id = Number(value) const id = Number(value)
if (!Number.isInteger(id) || id <= 0) { if (!Number.isInteger(id) || id <= 0) {
throw createHttpError(message, { throw createHttpError(message, {
@@ -201,7 +201,7 @@ function requireId(value, message, errorCode) {
return id return id
} }
function requireCount(value) { function requireCount(value: unknown) {
const count = Number(value) const count = Number(value)
if (!Number.isInteger(count) || count <= 0) { if (!Number.isInteger(count) || count <= 0) {
throw createHttpError('cloudtentacles 购买数量无效', { throw createHttpError('cloudtentacles 购买数量无效', {
@@ -213,6 +213,6 @@ function requireCount(value) {
return count return count
} }
function isPlainObject(value) { function isPlainObject(value: unknown): value is JsonObject {
return Boolean(value) && typeof value === 'object' && !Array.isArray(value) return Boolean(value) && typeof value === 'object' && !Array.isArray(value)
} }
@@ -1,5 +1,3 @@
// @ts-check
import http from 'node:http' import http from 'node:http'
import https from 'node:https' import https from 'node:https'
@@ -8,9 +6,21 @@ import { logInfo } from '../../../utils/logger.js'
import { notifyCloudtentaclesAuthExpired } from '../../notification/domain-notifications.js' import { notifyCloudtentaclesAuthExpired } from '../../notification/domain-notifications.js'
import { buildCloudtentaclesHeaders, buildCloudtentaclesUrl, resolveCloudtentaclesConfig } from './shared.js' import { buildCloudtentaclesHeaders, buildCloudtentaclesUrl, resolveCloudtentaclesConfig } from './shared.js'
export async function cloudtentaclesRequest(pathname, options = {}) { type JsonObject = Record<string, any>
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 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 timeoutMs = Number(options.timeoutMs || config.timeoutMs || 5000)
const controller = new AbortController() const controller = new AbortController()
const timer = setTimeout(() => controller.abort(), timeoutMs) const timer = setTimeout(() => controller.abort(), timeoutMs)
@@ -48,7 +58,7 @@ export async function cloudtentaclesRequest(pathname, options = {}) {
if (statusCode === 401 || isCloudtentaclesExpiredMessage(message)) { if (statusCode === 401 || isCloudtentaclesExpiredMessage(message)) {
await notifyCloudtentaclesAuthExpired({ await notifyCloudtentaclesAuthExpired({
pathname, pathname: normalizedPathname,
errorCode, errorCode,
message, message,
}) })
@@ -62,7 +72,7 @@ export async function cloudtentaclesRequest(pathname, options = {}) {
logInfo('[cloudtentacles/http]', '请求完成', { logInfo('[cloudtentacles/http]', '请求完成', {
method, method,
pathname, pathname: normalizedPathname,
status: response.status, 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() const normalized = String(message || '').trim().toLowerCase()
return normalized.includes('expired') || normalized.includes('过期') || normalized.includes('失效') return normalized.includes('expired') || normalized.includes('过期') || normalized.includes('失效')
} }
function inferContentType(body) { function inferContentType(body: unknown) {
if (body == null) { if (body == null) {
return '' return ''
} }
@@ -103,7 +113,7 @@ function inferContentType(body) {
return 'application/json' return 'application/json'
} }
function normalizeRequestBody(body, contentType) { function normalizeRequestBody(body: unknown, contentType: unknown) {
if (body == null) { if (body == null) {
return undefined return undefined
} }
@@ -119,7 +129,7 @@ function normalizeRequestBody(body, contentType) {
return String(body) return String(body)
} }
function tryParseJson(text) { function tryParseJson(text: string) {
try { try {
return JSON.parse(text) return JSON.parse(text)
} catch { } 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<NodeHttpResponse> {
const isHttps = url.protocol === 'https:' const isHttps = url.protocol === 'https:'
const transport = isHttps ? https : http const transport = isHttps ? https : http
@@ -137,7 +155,7 @@ async function requestViaNodeHttp(url, { method, headers, body, signal }) {
headers, headers,
rejectUnauthorized: false, rejectUnauthorized: false,
}, (response) => { }, (response) => {
const chunks = [] const chunks: Buffer[] = []
response.on('data', (chunk) => { response.on('data', (chunk) => {
chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(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() const normalized = new Map()
for (const [key, value] of Object.entries(headers || {})) { for (const [key, value] of Object.entries(headers || {})) {
@@ -193,7 +211,7 @@ function createHeaderAdapter(headers) {
} }
return { return {
get(name) { get(name: unknown) {
return normalized.get(String(name || '').toLowerCase()) || null return normalized.get(String(name || '').toLowerCase()) || null
}, },
} }
@@ -1,10 +1,10 @@
// @ts-check
import { createHttpError } from '../../../utils/http.js' import { createHttpError } from '../../../utils/http.js'
import { cloudtentaclesRequest } from './http-client.js' import { cloudtentaclesRequest } from './http-client.js'
import { resolveCloudtentaclesConfig } from './shared.js' import { resolveCloudtentaclesConfig } from './shared.js'
export async function getCloudtentaclesKnapsack(payload = {}) { type JsonObject = Record<string, any>
export async function getCloudtentaclesKnapsack(payload: JsonObject = {}) {
const token = String(payload.token || '').trim() const token = String(payload.token || '').trim()
if (!token) { if (!token) {
throw createHttpError('cloudtentacles 背包查询缺少 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 : {} const source = isPlainObject(item) ? item : {}
return { 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) return Boolean(value) && typeof value === 'object' && !Array.isArray(value)
} }
@@ -1,12 +1,12 @@
// @ts-check
import { createHttpError } from '../../../utils/http.js' import { createHttpError } from '../../../utils/http.js'
import { logInfo } from '../../../utils/logger.js' import { logInfo } from '../../../utils/logger.js'
import { encryptCloudtentaclesPayload, md5CloudtentaclesPassword } from './crypto-service.js' import { encryptCloudtentaclesPayload, md5CloudtentaclesPassword } from './crypto-service.js'
import { cloudtentaclesRequest } from './http-client.js' import { cloudtentaclesRequest } from './http-client.js'
import { resolveCloudtentaclesConfig } from './shared.js' import { resolveCloudtentaclesConfig } from './shared.js'
export async function sendCloudtentaclesSmsCode(payload = {}) { type JsonObject = Record<string, any>
export async function sendCloudtentaclesSmsCode(payload: JsonObject = {}) {
const username = String(payload.username || '').trim() const username = String(payload.username || '').trim()
const phone = String(payload.phone || '').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 username = String(payload.username || '').trim()
const password = String(payload.password || '').trim() const password = String(payload.password || '').trim()
const phone = String(payload.phone || '').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() const token = String(payload.token || '').trim()
if (!token) { if (!token) {
@@ -181,7 +181,7 @@ export async function validateCloudtentaclesSession(payload = {}) {
} }
} }
function maskPhone(phone) { function maskPhone(phone: unknown) {
const normalized = String(phone || '').trim() const normalized = String(phone || '').trim()
if (normalized.length < 7) { if (normalized.length < 7) {
return normalized return normalized
@@ -190,6 +190,6 @@ function maskPhone(phone) {
return `${normalized.slice(0, 3)}****${normalized.slice(-4)}` 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) return Boolean(value) && typeof value === 'object' && !Array.isArray(value)
} }
+11
View File
@@ -647,6 +647,17 @@
- `npm run typecheck` - `npm run typecheck`
- `npm run build` - `npm run build`
- `npm test` 共 139 个用例通过 - `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 个用例通过
## 下一步建议 ## 下一步建议