后端迁移云触手请求与目录服务
This commit is contained in:
+13
-13
@@ -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)
|
||||||
}
|
}
|
||||||
+32
-14
@@ -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
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
+5
-5
@@ -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)
|
||||||
}
|
}
|
||||||
+7
-7
@@ -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)
|
||||||
}
|
}
|
||||||
@@ -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 个用例通过
|
||||||
|
|
||||||
## 下一步建议
|
## 下一步建议
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user