后端迁移云触手虚拟号服务
This commit is contained in:
+49
-31
@@ -1,5 +1,3 @@
|
||||
// @ts-check
|
||||
|
||||
import http from 'node:http'
|
||||
import https from 'node:https'
|
||||
|
||||
@@ -10,7 +8,23 @@ import { resolveCloudtentaclesConfig } from './shared.js'
|
||||
const BIND_URL_PROBE_MAX_BODY_BYTES = 64 * 1024
|
||||
const AMS_SIGNATURE_EXPIRED_CODE = '99998'
|
||||
|
||||
export async function listCloudtentaclesVirtualNumbers(payload = {}) {
|
||||
type JsonObject = Record<string, any>
|
||||
type HeaderAdapter = {
|
||||
get(name: unknown): string | null
|
||||
}
|
||||
type AmsProbeResponse = {
|
||||
status: number
|
||||
headers: HeaderAdapter
|
||||
bodyText: string
|
||||
}
|
||||
type AmsSignatureParams = {
|
||||
userId: string
|
||||
timestamp: string
|
||||
nonce: string
|
||||
sign: string
|
||||
}
|
||||
|
||||
export async function listCloudtentaclesVirtualNumbers(payload: JsonObject = {}) {
|
||||
const token = requireToken(payload.token, 'cloudtentacles 虚拟号列表缺少 token', 'cloudtentacles_vn_list_missing_token')
|
||||
const key = requireKey(payload.key, 'cloudtentacles 虚拟号列表缺少 key', 'cloudtentacles_vn_list_missing_key')
|
||||
const config = resolveCloudtentaclesConfig(payload)
|
||||
@@ -35,7 +49,7 @@ export async function listCloudtentaclesVirtualNumbers(payload = {}) {
|
||||
}
|
||||
}
|
||||
|
||||
export async function appointCloudtentaclesVirtualNumber(payload = {}) {
|
||||
export async function appointCloudtentaclesVirtualNumber(payload: JsonObject = {}) {
|
||||
const token = requireToken(payload.token, 'cloudtentacles 申请虚拟号缺少 token', 'cloudtentacles_vn_appoint_missing_token')
|
||||
const key = requireKey(payload.key, 'cloudtentacles 申请虚拟号缺少 key', 'cloudtentacles_vn_appoint_missing_key')
|
||||
const config = resolveCloudtentaclesConfig(payload)
|
||||
@@ -57,7 +71,7 @@ export async function appointCloudtentaclesVirtualNumber(payload = {}) {
|
||||
}
|
||||
}
|
||||
|
||||
export async function generateCloudtentaclesLoginCode(payload = {}) {
|
||||
export async function generateCloudtentaclesLoginCode(payload: JsonObject = {}) {
|
||||
const token = requireToken(payload.token, 'cloudtentacles 生成登录码缺少 token', 'cloudtentacles_vn_generate_code_missing_token')
|
||||
const key = requireKey(payload.key, 'cloudtentacles 生成登录码缺少 key', 'cloudtentacles_vn_generate_code_missing_key')
|
||||
const id = requireId(payload.id, 'cloudtentacles 生成登录码缺少 id', 'cloudtentacles_vn_generate_code_missing_id')
|
||||
@@ -80,7 +94,7 @@ export async function generateCloudtentaclesLoginCode(payload = {}) {
|
||||
}
|
||||
}
|
||||
|
||||
export async function fetchCloudtentaclesVirtualNumberCode(payload = {}) {
|
||||
export async function fetchCloudtentaclesVirtualNumberCode(payload: JsonObject = {}) {
|
||||
const token = requireToken(payload.token, 'cloudtentacles 获取验证码缺少 token', 'cloudtentacles_vn_verif_code_missing_token')
|
||||
const key = requireKey(payload.key, 'cloudtentacles 获取验证码缺少 key', 'cloudtentacles_vn_verif_code_missing_key')
|
||||
const phone = String(payload.phone || '').trim()
|
||||
@@ -110,7 +124,7 @@ export async function fetchCloudtentaclesVirtualNumberCode(payload = {}) {
|
||||
}
|
||||
}
|
||||
|
||||
export async function verifyCloudtentaclesLoginCode(payload = {}) {
|
||||
export async function verifyCloudtentaclesLoginCode(payload: JsonObject = {}) {
|
||||
const token = requireToken(payload.token, 'cloudtentacles 校验登录码缺少 token', 'cloudtentacles_vn_verify_code_missing_token')
|
||||
const key = requireKey(payload.key, 'cloudtentacles 校验登录码缺少 key', 'cloudtentacles_vn_verify_code_missing_key')
|
||||
const id = requireId(payload.id, 'cloudtentacles 校验登录码缺少 id', 'cloudtentacles_vn_verify_code_missing_id')
|
||||
@@ -141,7 +155,7 @@ export async function verifyCloudtentaclesLoginCode(payload = {}) {
|
||||
}
|
||||
}
|
||||
|
||||
export async function getCloudtentaclesBindUrl(payload = {}) {
|
||||
export async function getCloudtentaclesBindUrl(payload: JsonObject = {}) {
|
||||
const token = requireToken(payload.token, 'cloudtentacles 获取兑换链接缺少 token', 'cloudtentacles_vn_bind_url_missing_token')
|
||||
const key = requireKey(payload.key, 'cloudtentacles 获取兑换链接缺少 key', 'cloudtentacles_vn_bind_url_missing_key')
|
||||
const id = requireId(payload.id, 'cloudtentacles 获取兑换链接缺少 id', 'cloudtentacles_vn_bind_url_missing_id')
|
||||
@@ -165,7 +179,7 @@ export async function getCloudtentaclesBindUrl(payload = {}) {
|
||||
}
|
||||
}
|
||||
|
||||
export async function probeCloudtentaclesBindUrl(payload = {}) {
|
||||
export async function probeCloudtentaclesBindUrl(payload: JsonObject = {}) {
|
||||
const bindUrl = String(payload.bindUrl || '').trim()
|
||||
if (!bindUrl) {
|
||||
return {
|
||||
@@ -239,7 +253,7 @@ export async function probeCloudtentaclesBindUrl(payload = {}) {
|
||||
}
|
||||
}
|
||||
|
||||
export async function getCloudtentaclesBindInfo(payload = {}) {
|
||||
export async function getCloudtentaclesBindInfo(payload: JsonObject = {}) {
|
||||
const token = requireToken(payload.token, 'cloudtentacles 获取绑定信息缺少 token', 'cloudtentacles_vn_bind_info_missing_token')
|
||||
const key = requireKey(payload.key, 'cloudtentacles 获取绑定信息缺少 key', 'cloudtentacles_vn_bind_info_missing_key')
|
||||
const id = requireId(payload.id, 'cloudtentacles 获取绑定信息缺少 id', 'cloudtentacles_vn_bind_info_missing_id')
|
||||
@@ -268,7 +282,7 @@ export async function getCloudtentaclesBindInfo(payload = {}) {
|
||||
}
|
||||
}
|
||||
|
||||
export async function backCloudtentaclesVirtualNumber(payload = {}) {
|
||||
export async function backCloudtentaclesVirtualNumber(payload: JsonObject = {}) {
|
||||
const token = requireToken(payload.token, 'cloudtentacles 退还号码缺少 token', 'cloudtentacles_vn_back_missing_token')
|
||||
const key = requireKey(payload.key, 'cloudtentacles 退还号码缺少 key', 'cloudtentacles_vn_back_missing_key')
|
||||
const id = requireId(payload.id, 'cloudtentacles 退还号码缺少 id', 'cloudtentacles_vn_back_missing_id')
|
||||
@@ -292,7 +306,7 @@ export async function backCloudtentaclesVirtualNumber(payload = {}) {
|
||||
}
|
||||
}
|
||||
|
||||
function mapVirtualNumberItem(item) {
|
||||
function mapVirtualNumberItem(item: unknown) {
|
||||
const source = isPlainObject(item) ? item : {}
|
||||
const bindInfo = parseBindInfo(source.bind_info)
|
||||
|
||||
@@ -306,7 +320,7 @@ function mapVirtualNumberItem(item) {
|
||||
}
|
||||
}
|
||||
|
||||
function requireToken(value, message, errorCode) {
|
||||
function requireToken(value: unknown, message: string, errorCode: string) {
|
||||
const token = String(value || '').trim()
|
||||
if (!token) {
|
||||
throw createHttpError(message, {
|
||||
@@ -318,7 +332,7 @@ function requireToken(value, message, errorCode) {
|
||||
return token
|
||||
}
|
||||
|
||||
function requireKey(value, message, errorCode) {
|
||||
function requireKey(value: unknown, message: string, errorCode: string) {
|
||||
const key = String(value || '').trim()
|
||||
if (!key) {
|
||||
throw createHttpError(message, {
|
||||
@@ -330,7 +344,7 @@ function requireKey(value, message, errorCode) {
|
||||
return key
|
||||
}
|
||||
|
||||
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, {
|
||||
@@ -342,7 +356,7 @@ function requireId(value, message, errorCode) {
|
||||
return id
|
||||
}
|
||||
|
||||
function tryParseJson(text) {
|
||||
function tryParseJson(text: string) {
|
||||
try {
|
||||
return JSON.parse(text)
|
||||
} catch {
|
||||
@@ -350,12 +364,12 @@ function tryParseJson(text) {
|
||||
}
|
||||
}
|
||||
|
||||
function parseBindInfo(value) {
|
||||
function parseBindInfo(value: unknown) {
|
||||
const bindInfoText = String(value || '').trim()
|
||||
return tryParseJson(bindInfoText) || bindInfoText || null
|
||||
}
|
||||
|
||||
async function requestAmsIdeProbe(endpoint, options = {}) {
|
||||
async function requestAmsIdeProbe(endpoint: string, options: JsonObject = {}): Promise<AmsProbeResponse> {
|
||||
const url = new URL(endpoint)
|
||||
if (!['http:', 'https:'].includes(url.protocol)) {
|
||||
throw new Error('unsupported_ams_probe_protocol')
|
||||
@@ -376,7 +390,7 @@ async function requestAmsIdeProbe(endpoint, options = {}) {
|
||||
'accept-encoding': 'identity',
|
||||
},
|
||||
}, (response) => {
|
||||
const chunks = []
|
||||
const chunks: Buffer[] = []
|
||||
let receivedBytes = 0
|
||||
|
||||
response.on('data', (chunk) => {
|
||||
@@ -414,7 +428,7 @@ async function requestAmsIdeProbe(endpoint, options = {}) {
|
||||
})
|
||||
}
|
||||
|
||||
function createHeaderAdapter(headers) {
|
||||
function createHeaderAdapter(headers: http.IncomingHttpHeaders): HeaderAdapter {
|
||||
const normalized = new Map()
|
||||
|
||||
for (const [key, value] of Object.entries(headers || {})) {
|
||||
@@ -429,13 +443,13 @@ function createHeaderAdapter(headers) {
|
||||
}
|
||||
|
||||
return {
|
||||
get(name) {
|
||||
get(name: unknown) {
|
||||
return normalized.get(String(name || '').toLowerCase()) || null
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
function buildAmsIdeProbeBody(signatureParams, config, payload = {}) {
|
||||
function buildAmsIdeProbeBody(signatureParams: AmsSignatureParams, config: JsonObject, payload: JsonObject = {}) {
|
||||
const body = new URLSearchParams()
|
||||
const chartId = String(payload.chartId || config.bindUrlProbeChartId || '323794').trim()
|
||||
const subChartId = String(payload.subChartId || config.bindUrlProbeSubChartId || chartId).trim()
|
||||
@@ -456,7 +470,11 @@ function buildAmsIdeProbeBody(signatureParams, config, payload = {}) {
|
||||
return body
|
||||
}
|
||||
|
||||
function buildAmsIdeProbeHeaders(signatureParams, config, options = {}) {
|
||||
function buildAmsIdeProbeHeaders(
|
||||
signatureParams: AmsSignatureParams,
|
||||
config: JsonObject,
|
||||
options: JsonObject = {},
|
||||
) {
|
||||
const userAgent = String(options.userAgent || '').trim()
|
||||
const referer = String(config.bindUrlProbeReferer || 'https://gp.qq.com/').trim()
|
||||
|
||||
@@ -479,7 +497,7 @@ function buildAmsIdeProbeHeaders(signatureParams, config, options = {}) {
|
||||
}
|
||||
}
|
||||
|
||||
function buildAmsIdeProbeCookie(signatureParams, config) {
|
||||
function buildAmsIdeProbeCookie(signatureParams: AmsSignatureParams, config: JsonObject) {
|
||||
const tokenParams = `?userId=${signatureParams.userId}×tamp=${signatureParams.timestamp}&nonce=${signatureParams.nonce}&sign=${signatureParams.sign}`
|
||||
const cookies = [
|
||||
`tokenParams=${encodeURIComponent(tokenParams)}`,
|
||||
@@ -490,7 +508,7 @@ function buildAmsIdeProbeCookie(signatureParams, config) {
|
||||
return cookies.join('; ')
|
||||
}
|
||||
|
||||
function extractBindUrlSignatureParams(bindUrl) {
|
||||
function extractBindUrlSignatureParams(bindUrl: string): AmsSignatureParams | null {
|
||||
try {
|
||||
const url = new URL(bindUrl)
|
||||
const params = new URLSearchParams(url.search)
|
||||
@@ -533,13 +551,13 @@ function extractBindUrlSignatureParams(bindUrl) {
|
||||
}
|
||||
}
|
||||
|
||||
function isAmsBusinessOk(raw) {
|
||||
function isAmsBusinessOk(raw: any) {
|
||||
const ret = String(raw?.ret ?? '').trim()
|
||||
const iRet = String(raw?.iRet ?? raw?.jData?.iRet ?? '').trim()
|
||||
return ret === '0' || iRet === '0'
|
||||
}
|
||||
|
||||
function isAmsSignatureExpired(raw) {
|
||||
function isAmsSignatureExpired(raw: any) {
|
||||
const codes = [
|
||||
raw?.ret,
|
||||
raw?.iRet,
|
||||
@@ -559,7 +577,7 @@ function isAmsSignatureExpired(raw) {
|
||||
return codes.includes(AMS_SIGNATURE_EXPIRED_CODE) || messages.some((message) => message.includes('签名已过期'))
|
||||
}
|
||||
|
||||
function extractAmsBindInfo(raw) {
|
||||
function extractAmsBindInfo(raw: any) {
|
||||
const candidates = [
|
||||
raw?.jData?.sBindInfo,
|
||||
raw?.jData?.jData?.sBindInfo,
|
||||
@@ -569,7 +587,7 @@ function extractAmsBindInfo(raw) {
|
||||
return candidates.find((item) => isPlainObject(item)) || null
|
||||
}
|
||||
|
||||
function normalizeAmsBindRoleInfo(bindInfo) {
|
||||
function normalizeAmsBindRoleInfo(bindInfo: unknown) {
|
||||
const source = isPlainObject(bindInfo) ? bindInfo : {}
|
||||
|
||||
return {
|
||||
@@ -579,7 +597,7 @@ function normalizeAmsBindRoleInfo(bindInfo) {
|
||||
}
|
||||
}
|
||||
|
||||
function normalizeHeaderMap(headers) {
|
||||
function normalizeHeaderMap(headers: unknown) {
|
||||
if (!headers || typeof headers !== 'object') {
|
||||
return {}
|
||||
}
|
||||
@@ -591,6 +609,6 @@ function normalizeHeaderMap(headers) {
|
||||
)
|
||||
}
|
||||
|
||||
function isPlainObject(value) {
|
||||
function isPlainObject(value: unknown): value is JsonObject {
|
||||
return Boolean(value) && typeof value === 'object' && !Array.isArray(value)
|
||||
}
|
||||
@@ -666,6 +666,14 @@
|
||||
- `npm run typecheck`
|
||||
- `npm run build`
|
||||
- `npm test` 共 139 个用例通过
|
||||
162. Cloudtentacles 虚拟号服务迁移到 `.ts`:
|
||||
- `src/services/platforms/cloudtentacles/virtual-number-service.ts`
|
||||
163. Cloudtentacles 虚拟号列表、申请、生成登录码、获取验证码、校验验证码、兑换链接、绑定信息、退还号码、AMS 兑换链接探测已进入 TS 编译链路;虚拟号 payload、AMS 签名参数、探测 response、header adapter 与动态响应对象补齐类型
|
||||
164. 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