feat(密钥): 用户 API 密钥服务端化——V115 按账号绑定存储 + 后台密钥管理页 + 桌面端全站拦截与配置引导
- 新增 usersecret 模块:外观专利/货源查询密钥从本地 localStorage 迁移至 biz_user_api_secret(AES 加密、按 uid 绑定) - 后台「密钥管理」页:脱敏展示、立即检测、清空;每日 04:30 分布式锁定时巡检 - 桌面端:密钥设置面板走服务端、未配齐引导 /setup-secrets、代理余量展示 - 删除专利汇令牌全链路与密钥保留时长选择器
This commit is contained in:
@@ -201,6 +201,14 @@ export const API_ENDPOINTS = {
|
||||
taskDelete: '/api/appearance-patent/tasks/{taskId}',
|
||||
resultDownload: '/api/appearance-patent/results/{resultId}/download',
|
||||
},
|
||||
userSecret: {
|
||||
bundle: '/api/user-secrets',
|
||||
save: '/api/user-secrets/{moduleKey}',
|
||||
clear: '/api/user-secrets/{moduleKey}',
|
||||
check: '/api/user-secrets/{moduleKey}/check',
|
||||
migrate: '/api/user-secrets/migrate',
|
||||
proxyBalance: '/api/user-secrets/proxy-balance',
|
||||
},
|
||||
collectData: {
|
||||
parse: '/api/collect-data/parse',
|
||||
countryPreference: '/api/collect-data/country-preference',
|
||||
|
||||
@@ -13,6 +13,7 @@ export * from "./types/modules/collect-data.ts";
|
||||
export * from "./types/modules/image-video.ts";
|
||||
export * from "./types/modules/brand.ts";
|
||||
export * from "./types/modules/permission.ts";
|
||||
export * from "./types/modules/user-secret.ts";
|
||||
export * from "./types/modules/digital-human.ts";
|
||||
export * from "./progress-light.ts";
|
||||
export * from "./upload.ts";
|
||||
|
||||
@@ -49,7 +49,6 @@ export interface AppearancePatentParseVo {
|
||||
export interface AppearancePatentParsedPayloadDto {
|
||||
aiPrompt?: string;
|
||||
apiKey?: string;
|
||||
patentToken?: string;
|
||||
sourceFiles?: UploadedFileRef[];
|
||||
headers?: string[];
|
||||
items?: AppearancePatentParsedRow[];
|
||||
@@ -173,17 +172,16 @@ async function postTaskProgressBatch<T>(
|
||||
return requestPromise;
|
||||
}
|
||||
|
||||
export function parseAppearancePatent(files: UploadedFileRef[], aiPrompt: string, apiKey?: string, patentToken?: string) {
|
||||
export function parseAppearancePatent(files: UploadedFileRef[], aiPrompt: string, apiKey?: string) {
|
||||
return unwrapJavaResponse(
|
||||
post<
|
||||
JavaApiResponse<AppearancePatentParseVo>,
|
||||
{ user_id: number; files: UploadedFileRef[]; ai_prompt: string; api_key?: string; patent_token?: string }
|
||||
{ user_id: number; files: UploadedFileRef[]; ai_prompt: string; api_key?: string }
|
||||
>(buildJavaUrl(API_ENDPOINTS.appearancePatent.parse), {
|
||||
user_id: getCurrentUserId(),
|
||||
files,
|
||||
ai_prompt: aiPrompt,
|
||||
api_key: apiKey,
|
||||
patent_token: patentToken,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
import { del, get, post, put, type JavaApiResponse, unwrapJavaResponse } from '../../http.ts'
|
||||
import { buildJavaUrl } from '../../url.ts'
|
||||
import { API_ENDPOINTS } from '../../endpoints.ts'
|
||||
|
||||
/** 用户密钥模块 key:与服务端 UserSecretModule 枚举一致。 */
|
||||
export type ApiSecretModuleKey = 'appearance-patent' | 'similar-asin'
|
||||
|
||||
/** 连通性状态:unknown=未检测出结果(拦截)/ passed=通过 / failed=密钥无效(拦截)/ error=无法判定(放行)。 */
|
||||
export type ApiSecretCheckStatus = 'unknown' | 'passed' | 'failed' | 'error'
|
||||
|
||||
export interface UserApiSecretItem {
|
||||
moduleKey: string
|
||||
moduleLabel: string
|
||||
masked: string
|
||||
exists: boolean
|
||||
checkStatus: string
|
||||
checkCode: string
|
||||
checkMessage: string
|
||||
checkLatencyMs: number | null
|
||||
checkedAt: string | null
|
||||
updatedAt: string | null
|
||||
}
|
||||
|
||||
export interface UserApiSecretBundle {
|
||||
items: UserApiSecretItem[]
|
||||
requiredModules: string[]
|
||||
complete: boolean
|
||||
}
|
||||
|
||||
export interface UserApiSecretCheckResult {
|
||||
moduleKey: string
|
||||
checkStatus: string
|
||||
checkCode: string
|
||||
checkMessage: string
|
||||
checkLatencyMs: number | null
|
||||
checkedAt: string | null
|
||||
viaProxy: boolean
|
||||
}
|
||||
|
||||
export interface UserApiSecretBalance {
|
||||
available: boolean
|
||||
surplus: string | null
|
||||
balance: string | null
|
||||
message: string | null
|
||||
}
|
||||
|
||||
export interface UserApiSecretMigrateItem {
|
||||
moduleKey: string
|
||||
value: string
|
||||
}
|
||||
|
||||
export function fetchMyApiSecrets() {
|
||||
return unwrapJavaResponse(
|
||||
get<JavaApiResponse<UserApiSecretBundle>>(buildJavaUrl(API_ENDPOINTS.userSecret.bundle)),
|
||||
)
|
||||
}
|
||||
|
||||
export function putMyApiSecret(moduleKey: string, value: string) {
|
||||
return unwrapJavaResponse(
|
||||
put<JavaApiResponse<UserApiSecretItem>, { value: string }>(
|
||||
buildJavaUrl(API_ENDPOINTS.userSecret.save.replace('{moduleKey}', encodeURIComponent(moduleKey))),
|
||||
{ value },
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
export function deleteMyApiSecret(moduleKey: string) {
|
||||
return unwrapJavaResponse(
|
||||
del<JavaApiResponse<null>>(buildJavaUrl(API_ENDPOINTS.userSecret.clear.replace('{moduleKey}', encodeURIComponent(moduleKey)))),
|
||||
)
|
||||
}
|
||||
|
||||
/** value 非空时检测输入值(不落库);为空时检测服务端已存密钥并把结果落库。 */
|
||||
export function checkMyApiSecret(moduleKey: string, value?: string) {
|
||||
return unwrapJavaResponse(
|
||||
post<JavaApiResponse<UserApiSecretCheckResult>, { value?: string }>(
|
||||
buildJavaUrl(API_ENDPOINTS.userSecret.check.replace('{moduleKey}', encodeURIComponent(moduleKey))),
|
||||
{ value },
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
/** 上报本地已保存的密钥,服务端只写空缺模块、不覆盖已有值。 */
|
||||
export function migrateMyApiSecrets(items: UserApiSecretMigrateItem[]) {
|
||||
return unwrapJavaResponse(
|
||||
post<JavaApiResponse<{ migrated: number }>, { items: UserApiSecretMigrateItem[] }>(
|
||||
buildJavaUrl(API_ENDPOINTS.userSecret.migrate),
|
||||
{ items },
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
export function fetchProxyBalance() {
|
||||
return unwrapJavaResponse(
|
||||
get<JavaApiResponse<UserApiSecretBalance>>(buildJavaUrl(API_ENDPOINTS.userSecret.proxyBalance)),
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user