feat(admin): 后台新增「抖音码速达」配置面板

- 新增 Tab:/admin/platform-shops?tab=douyinMashida
- 面板字段:enabled / baseUrl / uid / secret / certLink / afterVerifyLink /
  afterVerifyMessage / messageType / timeoutMs / handledTopics(tags)
- 密钥脱敏:出参 secret 永远为空,输入框未编辑时对外 value 恒为 '',
  掩码不会污染后端「空串=保留原值」语义
- 顶部展示 ready 状态,未就绪时用 Alert 列出缺哪几项;
  始终展示平台侧要配的推送地址(可复制)
- baseUrl 命中本地地址时提示「当前指向本地网关桩,不会请求真实码速达」
- 把 AdminPlatformShopsPage 内联的 SecretFieldControl 抽成共享组件
  (函数体与原实现逐字节一致,8 处既有调用点参数不变)
This commit is contained in:
yml2213
2026-10-09 12:01:29 +08:00
parent 72606796b3
commit 080480f03b
9 changed files with 721 additions and 153 deletions
+64
View File
@@ -0,0 +1,64 @@
/**
* 抖音码速达后台配置的纯展示 / 校验逻辑。
*
* 刻意写成纯函数:不依赖 React / antd,也不使用 `@/` 别名,只做 `import type`,
* 方便 `node --experimental-strip-types --test` 直接跑单测。
*/
import type { AdminDouyinMashidaConfig } from '../types/admin/platform-config'
/** 配置完整度检查所需的配置子集 */
type MissingConfigCheck = Pick<
AdminDouyinMashidaConfig,
'enabled' | 'uid' | 'hasSecret' | 'certLink'
>
/** 配置完整度检查的固定展示顺序 */
const MISSING_FIELD_LABELS = ['接入开关', '商户uid', '用户秘钥', '兑换链接'] as const
/** 本地/内网 host:命中即认为不会请求真实码速达 */
const LOCAL_DEV_HOSTS = new Set(['127.0.0.1', 'localhost', 'host.docker.internal'])
/**
* 判断 baseUrl 是否指向本地/内网地址(这类地址平台永远访问不到)。
*
* 容忍端口、结尾斜杠、大小写与缺失协议(如 `127.0.0.1:3000`)。
* 无法解析的地址一律视为非本地。
*/
export function isLocalDevBaseUrl(baseUrl: string): boolean {
const text = String(baseUrl ?? '').trim()
if (!text) return false
try {
const url = new URL(text.includes('://') ? text : `http://${text}`)
return LOCAL_DEV_HOSTS.has(url.hostname.toLowerCase())
} catch {
return false
}
}
/**
* 拼接平台侧需要配置的消息推送地址(容错结尾斜杠)。
* 例:`https://ks.khhao.com/` → `https://ks.khhao.com/api/v1/open/douyin-mashida/message`
*/
export function buildDouyinMashidaMessageUrl(origin: string): string {
const normalized = String(origin ?? '')
.trim()
.replace(/\/+$/, '')
return `${normalized}/api/v1/open/douyin-mashida/message`
}
/**
* 返回缺失项的中文名数组,顺序固定为:接入开关 / 商户uid / 用户秘钥 / 兑换链接。
* 全齐时返回空数组。
*/
export function describeMissingConfigFields(config: MissingConfigCheck): string[] {
const source = config ?? ({} as MissingConfigCheck)
const missing: string[] = []
if (!source.enabled) missing.push(MISSING_FIELD_LABELS[0])
if (!String(source.uid ?? '').trim()) missing.push(MISSING_FIELD_LABELS[1])
if (!source.hasSecret) missing.push(MISSING_FIELD_LABELS[2])
if (!String(source.certLink ?? '').trim()) missing.push(MISSING_FIELD_LABELS[3])
return missing
}