feat(密钥管理): 列表按用户聚合三字段列 + 代理配置服务端上报
- Java:后台列表一行一用户(货源查询密钥/外观专利密钥/代理设置),行级状态三项全通过才算通过;检测/清空改为按用户;搜索仅按用户名(去 UID);新增代理检测(经用户代理请求自家域名)与代理掩码(隐去账密) - 后台前端:三字段列改版 + 行级状态筛选 + 用户名筛选 - 桌面前端:登录加载密钥时补报本地代理(只填空缺不覆盖)、保存/清空代理实时上报
This commit is contained in:
@@ -2,8 +2,8 @@ import { del, get, post, put, type JavaApiResponse, unwrapJavaResponse } from '.
|
||||
import { buildJavaUrl } from '../../url.ts'
|
||||
import { API_ENDPOINTS } from '../../endpoints.ts'
|
||||
|
||||
/** 用户密钥模块 key:与服务端 UserSecretModule 枚举一致。 */
|
||||
export type ApiSecretModuleKey = 'appearance-patent' | 'similar-asin'
|
||||
/** 用户密钥模块 key:与服务端 UserSecretModule 枚举一致(proxy=代理设置,由设置面板单独区块管理)。 */
|
||||
export type ApiSecretModuleKey = 'appearance-patent' | 'similar-asin' | 'proxy'
|
||||
|
||||
/** 连通性状态:unknown=未检测出结果(拦截)/ passed=通过 / failed=密钥无效(拦截)/ error=无法判定(放行)。 */
|
||||
export type ApiSecretCheckStatus = 'unknown' | 'passed' | 'failed' | 'error'
|
||||
|
||||
@@ -115,6 +115,7 @@ import {
|
||||
type UserApiSecretCheckResult,
|
||||
} from '@/shared/utils/api-secret-store'
|
||||
import { getPywebviewApi, type ProxyMode, type DesktopConfigUpdate } from '@/shared/bridges/pywebview'
|
||||
import { deleteMyApiSecret, putMyApiSecret } from '@/shared/api/types/modules/user-secret'
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
@@ -352,6 +353,21 @@ async function loadProxyConfig() {
|
||||
}
|
||||
}
|
||||
|
||||
/** 代理配置上报服务端(后台密钥管理观测/检测):有地址则保存、清空则删除;失败不阻断本地保存。 */
|
||||
async function syncProxyToServer(url: string) {
|
||||
if (proxyUserId() === '0') return
|
||||
try {
|
||||
if (url) {
|
||||
await putMyApiSecret('proxy', url)
|
||||
} else {
|
||||
await deleteMyApiSecret('proxy')
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('[api-secret] 代理配置上报失败(不影响本地任务):', error)
|
||||
ElMessage.warning('代理已保存到本地,但上报后台失败')
|
||||
}
|
||||
}
|
||||
|
||||
async function loadBalance() {
|
||||
balanceLoading.value = true
|
||||
try {
|
||||
@@ -404,6 +420,7 @@ async function saveAll(options: { requireAll?: boolean } = {}): Promise<boolean>
|
||||
await api.save_config(userProxyPatch(nextProxyUrl, proxyMode.value) as DesktopConfigUpdate)
|
||||
proxyUrl.value = nextProxyUrl
|
||||
proxyDirty.value = false
|
||||
await syncProxyToServer(nextProxyUrl)
|
||||
}
|
||||
|
||||
// 保存成功的模块立即自动检测,结果直接反映在卡片状态行
|
||||
|
||||
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user