feat(密钥管理): 列表按用户聚合三字段列 + 代理配置服务端上报

- Java:后台列表一行一用户(货源查询密钥/外观专利密钥/代理设置),行级状态三项全通过才算通过;检测/清空改为按用户;搜索仅按用户名(去 UID);新增代理检测(经用户代理请求自家域名)与代理掩码(隐去账密)
- 后台前端:三字段列改版 + 行级状态筛选 + 用户名筛选
- 桌面前端:登录加载密钥时补报本地代理(只填空缺不覆盖)、保存/清空代理实时上报
This commit is contained in:
2026-09-13 10:47:19 +08:00
parent 82a782550e
commit b7d4d325e0
14 changed files with 665 additions and 226 deletions
@@ -342,6 +342,7 @@ async function doLoad(): Promise<ApiSecretLoadState> {
}
notify()
}
await tryBackfillProxyToServer()
return computeApiSecretsComplete() ? 'complete' : 'incomplete'
} catch (error) {
console.warn('[api-secret] 服务端密钥拉取失败,本次按未知处理(不阻断使用):', error)
@@ -388,6 +389,37 @@ async function tryMigrateLocalSecrets(serverItems: UserApiSecretItem[]): Promise
}
}
/**
* 本地代理配置补报服务端(只写空缺、不覆盖;后台密钥管理观测/检测用)。
* 非桌面环境、未登录、无本地代理时静默跳过;失败静默,下次加载重试。
* 桥模块动态导入:node 测试环境不触发(currentUid 为 0 时直接返回),避免加载浏览器侧依赖。
*/
async function tryBackfillProxyToServer(): Promise<void> {
try {
if (currentUid() === '0') return
const { getPywebviewApi } = await import('../bridges/pywebview.ts')
const api = getPywebviewApi()
if (!api?.read_config) return
const config = (await api.read_config()) as Record<string, unknown> | null
const url = readLocalProxyUrl(config)
if (!url) return
await migrateMyApiSecrets([{ moduleKey: 'proxy', value: url }])
} catch (error) {
console.warn('[api-secret] 代理配置补报失败(下次加载重试):', error)
}
}
/** 读取当前登录用户的本地代理地址:proxy_users[uid] 优先,未登录回退全局 proxy_url。 */
function readLocalProxyUrl(config: Record<string, unknown> | null | undefined): string {
const uid = currentUid()
if (uid !== '0') {
const users = (config?.proxy_users ?? {}) as Record<string, unknown>
const own = (users[uid] ?? {}) as Record<string, unknown>
return typeof own.proxy_url === 'string' ? own.proxy_url.trim() : ''
}
return typeof config?.proxy_url === 'string' ? config.proxy_url.trim() : ''
}
/** 保存密钥到服务端;成功后刷新本地缓存(失败不写本地,避免本地有值服务端没有的假象)。 */
export async function saveApiSecret(moduleKey: ApiSecretModuleKey, value: string): Promise<ApiSecretSnapshot> {
const trimmed = value.trim()