后端迁移云触手平台底座

This commit is contained in:
yml
2026-05-21 16:19:41 +08:00
parent 889be72125
commit ce6a6af820
6 changed files with 63 additions and 42 deletions
@@ -196,8 +196,8 @@ export async function validateAdminCloudtentaclesSession(
payload: AdminCloudtentaclesValidateSessionInput = {} payload: AdminCloudtentaclesValidateSessionInput = {}
) { ) {
const sourceKey = String(payload.sourceKey || "").trim() || "default"; const sourceKey = String(payload.sourceKey || "").trim() || "default";
const savedSource = getCloudtentaclesSourceByKey(sourceKey) || {}; const savedSource: JsonObject = getCloudtentaclesSourceByKey(sourceKey) || {};
const persistedSession = getCloudtentaclesSessionStateByKey(sourceKey) || {}; const persistedSession: JsonObject = getCloudtentaclesSessionStateByKey(sourceKey) || {};
const sessionContext = resolveAdminCloudtentaclesSessionPayload(payload, { const sessionContext = resolveAdminCloudtentaclesSessionPayload(payload, {
savedSource, savedSource,
persistedSession, persistedSession,
@@ -1,15 +1,15 @@
// @ts-check
import { createHash, constants, publicEncrypt } from 'node:crypto' import { createHash, constants, publicEncrypt } from 'node:crypto'
import { createHttpError } from '../../../utils/http.js' import { createHttpError } from '../../../utils/http.js'
import { resolveCloudtentaclesConfig } from './shared.js' import { resolveCloudtentaclesConfig } from './shared.js'
export function md5CloudtentaclesPassword(password) { type JsonObject = Record<string, any>
export function md5CloudtentaclesPassword(password: unknown) {
return createHash('md5').update(String(password || ''), 'utf8').digest('hex') return createHash('md5').update(String(password || ''), 'utf8').digest('hex')
} }
export function encryptCloudtentaclesPayload(payload = {}, options = {}) { export function encryptCloudtentaclesPayload(payload: JsonObject = {}, options: JsonObject = {}) {
const config = resolveCloudtentaclesConfig(options) const config = resolveCloudtentaclesConfig(options)
const publicKeyPem = String(config.publicKeyPem || '').trim() const publicKeyPem = String(config.publicKeyPem || '').trim()
@@ -54,7 +54,7 @@ export function encryptCloudtentaclesPayload(payload = {}, options = {}) {
} }
} }
function removeEmptyFields(payload) { function removeEmptyFields(payload: unknown) {
if (!payload || typeof payload !== 'object') { if (!payload || typeof payload !== 'object') {
return {} return {}
} }
@@ -1,5 +1,3 @@
// @ts-check
import fs from 'node:fs' import fs from 'node:fs'
import path from 'node:path' import path from 'node:path'
@@ -7,6 +5,8 @@ import { PROJECT_ROOT } from '../../../config/runtime.js'
const CLOUDTENTACLES_SESSION_FILE_PATH = path.join(PROJECT_ROOT, 'data', 'cloudtentacles-session.json') const CLOUDTENTACLES_SESSION_FILE_PATH = path.join(PROJECT_ROOT, 'data', 'cloudtentacles-session.json')
type JsonObject = Record<string, any>
export function getCloudtentaclesSessionFilePath() { export function getCloudtentaclesSessionFilePath() {
return CLOUDTENTACLES_SESSION_FILE_PATH return CLOUDTENTACLES_SESSION_FILE_PATH
} }
@@ -23,7 +23,7 @@ export function getCloudtentaclesSessionState() {
/** /**
* Get a session by its sourceKey. Returns null if not found. * Get a session by its sourceKey. Returns null if not found.
*/ */
export function getCloudtentaclesSessionStateByKey(sourceKey) { export function getCloudtentaclesSessionStateByKey(sourceKey: unknown) {
const states = loadCloudtentaclesSessionStatesFromFile() const states = loadCloudtentaclesSessionStatesFromFile()
const key = String(sourceKey || '').trim() const key = String(sourceKey || '').trim()
if (!key) return null if (!key) return null
@@ -41,7 +41,7 @@ export function getAllCloudtentaclesSessionStates() {
* Backward-compatible save: accepts both old single-object format * Backward-compatible save: accepts both old single-object format
* and new sessions-map format, normalizes, and persists. * and new sessions-map format, normalizes, and persists.
*/ */
export function saveCloudtentaclesSessionState(rawValue) { export function saveCloudtentaclesSessionState(rawValue: unknown) {
const normalized = normalizeSessionStatesFile(rawValue) const normalized = normalizeSessionStatesFile(rawValue)
fs.mkdirSync(path.dirname(CLOUDTENTACLES_SESSION_FILE_PATH), { recursive: true }) fs.mkdirSync(path.dirname(CLOUDTENTACLES_SESSION_FILE_PATH), { recursive: true })
fs.writeFileSync(CLOUDTENTACLES_SESSION_FILE_PATH, `${JSON.stringify(normalized, null, 2)}\n`, 'utf8') fs.writeFileSync(CLOUDTENTACLES_SESSION_FILE_PATH, `${JSON.stringify(normalized, null, 2)}\n`, 'utf8')
@@ -51,7 +51,7 @@ export function saveCloudtentaclesSessionState(rawValue) {
/** /**
* Save a session for a specific sourceKey. * Save a session for a specific sourceKey.
*/ */
export function saveCloudtentaclesSessionStateByKey(sourceKey, rawValue) { export function saveCloudtentaclesSessionStateByKey(sourceKey: unknown, rawValue: unknown) {
const key = String(sourceKey || '').trim() const key = String(sourceKey || '').trim()
if (!key) { if (!key) {
throw new Error('saveCloudtentaclesSessionStateByKey: sourceKey is required') throw new Error('saveCloudtentaclesSessionStateByKey: sourceKey is required')
@@ -75,7 +75,7 @@ export function clearCloudtentaclesSessionState() {
/** /**
* Clear a session by sourceKey (sets it to default empty state). * Clear a session by sourceKey (sets it to default empty state).
*/ */
export function clearCloudtentaclesSessionStateByKey(sourceKey) { export function clearCloudtentaclesSessionStateByKey(sourceKey: unknown) {
const key = String(sourceKey || '').trim() const key = String(sourceKey || '').trim()
if (!key) { if (!key) {
throw new Error('clearCloudtentaclesSessionStateByKey: sourceKey is required') throw new Error('clearCloudtentaclesSessionStateByKey: sourceKey is required')
@@ -106,7 +106,7 @@ function loadCloudtentaclesSessionStatesFromFile() {
/** /**
* Normalize a single session state item. * Normalize a single session state item.
*/ */
function normalizeCloudtentaclesSessionState(rawValue) { function normalizeCloudtentaclesSessionState(rawValue: unknown) {
const source = isPlainObject(rawValue) ? rawValue : {} const source = isPlainObject(rawValue) ? rawValue : {}
return { return {
@@ -125,7 +125,7 @@ function normalizeCloudtentaclesSessionState(rawValue) {
* Handles old format (single object without sessions key) by auto-wrapping * Handles old format (single object without sessions key) by auto-wrapping
* into { sessions: { 'default': ... } }. * into { sessions: { 'default': ... } }.
*/ */
function normalizeSessionStatesFile(rawValue) { function normalizeSessionStatesFile(rawValue: unknown) {
// Old format: { token: 'xxx', ... } (single object, no sessions key) // Old format: { token: 'xxx', ... } (single object, no sessions key)
if (isPlainObject(rawValue) && !rawValue.sessions) { if (isPlainObject(rawValue) && !rawValue.sessions) {
return { return {
@@ -137,7 +137,7 @@ function normalizeSessionStatesFile(rawValue) {
// New format: { sessions: { 'default': {...}, ... } } // New format: { sessions: { 'default': {...}, ... } }
return { return {
sessions: isPlainObject(rawValue?.sessions) sessions: isPlainObject(rawValue) && isPlainObject(rawValue.sessions)
? Object.fromEntries( ? Object.fromEntries(
Object.entries(rawValue.sessions).map(([k, v]) => [k, normalizeCloudtentaclesSessionState(v)]) Object.entries(rawValue.sessions).map(([k, v]) => [k, normalizeCloudtentaclesSessionState(v)])
) )
@@ -165,11 +165,11 @@ function createDefaultCloudtentaclesSessionStates() {
} }
} }
function normalizeInteger(value, fallback) { function normalizeInteger(value: unknown, fallback: number) {
const parsed = Number(value) const parsed = Number(value)
return Number.isInteger(parsed) ? parsed : fallback return Number.isInteger(parsed) ? parsed : fallback
} }
function isPlainObject(value) { function isPlainObject(value: unknown): value is JsonObject {
return Object.prototype.toString.call(value) === '[object Object]' return Object.prototype.toString.call(value) === '[object Object]'
} }
@@ -1,11 +1,11 @@
// @ts-check
import { runtimeConfig } from '../../../config/runtime.js' import { runtimeConfig } from '../../../config/runtime.js'
/** @typedef {import('../../../types/runtime-config.js').RuntimeConfig} RuntimeConfig */ import type { RuntimeConfig } from '../../../types/runtime-config.js'
export function resolveCloudtentaclesConfig(overrides = {}) { type CloudtentaclesRuntimeConfig = RuntimeConfig['platforms']['cloudtentacles']
/** @type {RuntimeConfig['platforms']['cloudtentacles']} */ type JsonObject = Record<string, any>
export function resolveCloudtentaclesConfig(overrides: Partial<CloudtentaclesRuntimeConfig> = {}) {
const baseConfig = runtimeConfig.platforms?.cloudtentacles || { const baseConfig = runtimeConfig.platforms?.cloudtentacles || {
baseUrl: '', baseUrl: '',
timeoutMs: 5000, timeoutMs: 5000,
@@ -92,7 +92,11 @@ export function resolveCloudtentaclesConfig(overrides = {}) {
} }
} }
export function buildCloudtentaclesUrl(baseUrl, pathname, searchParams = null) { export function buildCloudtentaclesUrl(
baseUrl: unknown,
pathname: unknown,
searchParams: JsonObject | null = null,
) {
const url = new URL(normalizePath(pathname, '/'), normalizeBaseUrl(baseUrl) || 'https://123.207.217.176') const url = new URL(normalizePath(pathname, '/'), normalizeBaseUrl(baseUrl) || 'https://123.207.217.176')
if (searchParams && typeof searchParams === 'object') { if (searchParams && typeof searchParams === 'object') {
@@ -113,8 +117,14 @@ export function buildCloudtentaclesHeaders({
deviceId = '-', deviceId = '-',
deviceType = 0, deviceType = 0,
extra = {}, extra = {},
}: {
token?: unknown
contentType?: string
deviceId?: unknown
deviceType?: unknown
extra?: JsonObject
} = {}) { } = {}) {
const headers = { const headers: JsonObject = {
accept: 'application/json, text/plain, */*', accept: 'application/json, text/plain, */*',
...(contentType ? { 'content-type': contentType } : {}), ...(contentType ? { 'content-type': contentType } : {}),
deviceid: String(deviceId || '-').trim() || '-', deviceid: String(deviceId || '-').trim() || '-',
@@ -130,11 +140,11 @@ export function buildCloudtentaclesHeaders({
return headers return headers
} }
function normalizeBaseUrl(value) { function normalizeBaseUrl(value: unknown) {
return String(value || '').trim().replace(/\/+$/, '') return String(value || '').trim().replace(/\/+$/, '')
} }
function normalizePath(value, fallback) { function normalizePath(value: unknown, fallback: string) {
const normalized = String(value || '').trim() const normalized = String(value || '').trim()
if (!normalized) { if (!normalized) {
return fallback return fallback
@@ -143,17 +153,17 @@ function normalizePath(value, fallback) {
return normalized.startsWith('/') ? normalized : `/${normalized}` return normalized.startsWith('/') ? normalized : `/${normalized}`
} }
function normalizePositiveInteger(value, fallback) { function normalizePositiveInteger(value: unknown, fallback: number) {
const parsed = Number(value) const parsed = Number(value)
return Number.isInteger(parsed) && parsed > 0 ? parsed : fallback return Number.isInteger(parsed) && parsed > 0 ? parsed : fallback
} }
function normalizeInteger(value, fallback) { function normalizeInteger(value: unknown, fallback: number) {
const parsed = Number(value) const parsed = Number(value)
return Number.isInteger(parsed) ? parsed : fallback return Number.isInteger(parsed) ? parsed : fallback
} }
function normalizeHeaderMap(extra) { function normalizeHeaderMap(extra: unknown) {
if (!extra || typeof extra !== 'object') { if (!extra || typeof extra !== 'object') {
return {} return {}
} }
@@ -165,7 +175,7 @@ function normalizeHeaderMap(extra) {
) )
} }
function normalizePem(value) { function normalizePem(value: unknown) {
return String(value || '') return String(value || '')
.replace(/\r/g, '') .replace(/\r/g, '')
.split('\n') .split('\n')
@@ -1,5 +1,3 @@
// @ts-check
import fs from 'node:fs' import fs from 'node:fs'
import path from 'node:path' import path from 'node:path'
@@ -7,6 +5,8 @@ import { PROJECT_ROOT } from '../../../config/runtime.js'
const CLOUDTENTACLES_SOURCES_FILE_PATH = path.join(PROJECT_ROOT, 'data', 'cloudtentacles-sources.json') const CLOUDTENTACLES_SOURCES_FILE_PATH = path.join(PROJECT_ROOT, 'data', 'cloudtentacles-sources.json')
type JsonObject = Record<string, any>
export function getCloudtentaclesSourcesFilePath() { export function getCloudtentaclesSourcesFilePath() {
return CLOUDTENTACLES_SOURCES_FILE_PATH return CLOUDTENTACLES_SOURCES_FILE_PATH
} }
@@ -24,7 +24,7 @@ export function getCloudtentaclesSourceConfig() {
/** /**
* Get a source item by its key. Returns null if not found. * Get a source item by its key. Returns null if not found.
*/ */
export function getCloudtentaclesSourceByKey(sourceKey) { export function getCloudtentaclesSourceByKey(sourceKey: unknown) {
const config = loadCloudtentaclesSourcesConfigFromFile() const config = loadCloudtentaclesSourcesConfigFromFile()
const key = String(sourceKey || '').trim() const key = String(sourceKey || '').trim()
if (!key) return null if (!key) return null
@@ -42,7 +42,7 @@ export function listCloudtentaclesSources() {
* Backward-compatible save: accepts both old single-object format * Backward-compatible save: accepts both old single-object format
* and new list format, normalizes, and persists. * and new list format, normalizes, and persists.
*/ */
export function saveCloudtentaclesSourceConfig(rawValue) { export function saveCloudtentaclesSourceConfig(rawValue: unknown) {
const normalized = normalizeCloudtentaclesSourcesConfig(rawValue) const normalized = normalizeCloudtentaclesSourcesConfig(rawValue)
fs.mkdirSync(path.dirname(CLOUDTENTACLES_SOURCES_FILE_PATH), { recursive: true }) fs.mkdirSync(path.dirname(CLOUDTENTACLES_SOURCES_FILE_PATH), { recursive: true })
fs.writeFileSync(CLOUDTENTACLES_SOURCES_FILE_PATH, `${JSON.stringify(normalized, null, 2)}\n`, 'utf8') fs.writeFileSync(CLOUDTENTACLES_SOURCES_FILE_PATH, `${JSON.stringify(normalized, null, 2)}\n`, 'utf8')
@@ -52,7 +52,7 @@ export function saveCloudtentaclesSourceConfig(rawValue) {
/** /**
* Save the entire list-format config object: { enabled, sources: [...] }. * Save the entire list-format config object: { enabled, sources: [...] }.
*/ */
export function saveCloudtentaclesSourcesList(rawValue) { export function saveCloudtentaclesSourcesList(rawValue: unknown) {
return saveCloudtentaclesSourceConfig(rawValue) return saveCloudtentaclesSourceConfig(rawValue)
} }
@@ -60,7 +60,7 @@ export function saveCloudtentaclesSourcesList(rawValue) {
* Save or update a single source item identified by sourceKey. * Save or update a single source item identified by sourceKey.
* If a source with the same key exists, it is replaced; otherwise it is appended. * If a source with the same key exists, it is replaced; otherwise it is appended.
*/ */
export function saveCloudtentaclesSourceByKey(sourceKey, data) { export function saveCloudtentaclesSourceByKey(sourceKey: unknown, data: JsonObject = {}) {
const key = String(sourceKey || '').trim() const key = String(sourceKey || '').trim()
if (!key) { if (!key) {
throw new Error('saveCloudtentaclesSourceByKey: sourceKey is required') throw new Error('saveCloudtentaclesSourceByKey: sourceKey is required')
@@ -84,7 +84,7 @@ export function saveCloudtentaclesSourceByKey(sourceKey, data) {
/** /**
* Delete a single source by key. Throws if key is 'default' (cannot delete default source). * Delete a single source by key. Throws if key is 'default' (cannot delete default source).
*/ */
export function deleteCloudtentaclesSourceByKey(sourceKey) { export function deleteCloudtentaclesSourceByKey(sourceKey: unknown) {
const key = String(sourceKey || '').trim() const key = String(sourceKey || '').trim()
if (!key) { if (!key) {
throw new Error('deleteCloudtentaclesSourceByKey: sourceKey is required') throw new Error('deleteCloudtentaclesSourceByKey: sourceKey is required')
@@ -127,7 +127,7 @@ function loadCloudtentaclesSourcesConfigFromFile() {
/** /**
* Normalize a single source item. Adds key (required) and label (optional). * Normalize a single source item. Adds key (required) and label (optional).
*/ */
function normalizeCloudtentaclesSourceItem(rawValue) { function normalizeCloudtentaclesSourceItem(rawValue: unknown) {
const source = isPlainObject(rawValue) ? rawValue : {} const source = isPlainObject(rawValue) ? rawValue : {}
return { return {
@@ -146,7 +146,7 @@ function normalizeCloudtentaclesSourceItem(rawValue) {
* Normalize the overall config. Handles both old single-object format * Normalize the overall config. Handles both old single-object format
* (auto-migrates to new list format) and new { enabled, sources } format. * (auto-migrates to new list format) and new { enabled, sources } format.
*/ */
function normalizeCloudtentaclesSourcesConfig(rawValue) { function normalizeCloudtentaclesSourcesConfig(rawValue: unknown) {
// Old format: { enabled: true, username: 'xxx', ... } (single object, no sources array) // Old format: { enabled: true, username: 'xxx', ... } (single object, no sources array)
if (isPlainObject(rawValue) && !Array.isArray(rawValue.sources)) { if (isPlainObject(rawValue) && !Array.isArray(rawValue.sources)) {
return { return {
@@ -179,11 +179,11 @@ function createDefaultCloudtentaclesSourcesConfig() {
} }
} }
function normalizeInteger(value, fallback) { function normalizeInteger(value: unknown, fallback: number) {
const parsed = Number(value) const parsed = Number(value)
return Number.isInteger(parsed) ? parsed : fallback return Number.isInteger(parsed) ? parsed : fallback
} }
function isPlainObject(value) { function isPlainObject(value: unknown): value is JsonObject {
return Object.prototype.toString.call(value) === '[object Object]' return Object.prototype.toString.call(value) === '[object Object]'
} }
+11
View File
@@ -636,6 +636,17 @@
- `npm run typecheck` - `npm run typecheck`
- `npm run build` - `npm run build`
- `npm test` 共 139 个用例通过 - `npm test` 共 139 个用例通过
153. Cloudtentacles 平台底座模块迁移到 `.ts`
- `src/services/platforms/cloudtentacles/shared.ts`
- `src/services/platforms/cloudtentacles/source-config-service.ts`
- `src/services/platforms/cloudtentacles/session-state-service.ts`
- `src/services/platforms/cloudtentacles/crypto-service.ts`
154. Cloudtentacles 运行时配置解析、URL / headers 构造、来源配置读写、会话状态读写、RSA 加密入口已进入 TS 编译链路;动态配置、会话 map、加密 payload 与 header map 补齐类型
155. Docker 内验证通过:
- `src/services/admin/platform-config/*.test.js` 共 37 个用例通过
- `npm run typecheck`
- `npm run build`
- `npm test` 共 139 个用例通过
## 下一步建议 ## 下一步建议