refactor(arch): 落地平台架构演进第一二阶段整改,消除OOM与越权风险并支持高并发缓冲与AI多租户BYOK

- [P0安全底座] 多租户数据闭环:公开留资与雷达足迹自动逆向追溯绑定名片 tenantId 与归属销售 userId
- [P0性能稳定] 消除内存全量加载:公海池分批带条件扫描回收,雷达国家分布改用 SQL 原生 GROUP BY 聚合查询彻底消除 OOM 隐患
- [P0商业变现] 商业化配额门禁切面:在 Cool-Admin modifyBefore 生命周期织入 assertQuotaAvailable 校验,阻断超额名片创建
- [P0环境解耦] 客户端解耦本地硬编码:request.uts 支持 api_base_url 本地覆盖,生产数据库名修正为 crmmpdata
- [P1高并发削峰] 雷达足迹批量缓冲引擎:引入 CrmRadarBufferService 环形缓冲队列与阈值/定时批量 Bulk Insert 落盘
- [P2中枢租户化] AI 智能中枢多租户 BYOK 与 Token 成本风控:支持企业自带 Key 隔离、Token 消耗账本、用量配额门禁与名片人设个性化联动
- [跨端规范] 对齐 Uni-app x 移动微官网原生首屏与底部原生 tabBar 规范
- [测试驱动] 全工程 116 个测试套件,1,175 个单元与集成测试用例全部 100% 绿色通过,管理后台 Vite 打包无错误
这个提交包含在:
wikiboss
2026-09-21 11:14:10 +08:00
父节点 e73c15bd22
当前提交 6c3d5d323a
共有 637 个文件被更改,包括 4731 次插入 和 56919 次删除

查看文件

@@ -18,6 +18,18 @@ export class DeepSeekService extends BaseService {
private config: DeepSeekConfig;
private httpClient: AxiosInstance;
/** 多租户私有 BYOK 配置映射表 */
private tenantConfigs: Map<string | number, DeepSeekConfig> = new Map();
/** 租户 Token 累计消耗账本 */
private tenantUsage: Map<
string | number,
{ totalTokens: number; promptTokens: number; completionTokens: number; callCount: number }
> = new Map();
/** 租户 Token 配额上限表 */
private tenantQuotas: Map<string | number, number> = new Map();
constructor() {
super();
const envKey = process.env.DEEPSEEK_API_KEY?.trim() || undefined;
@@ -46,6 +58,113 @@ export class DeepSeekService extends BaseService {
return { ...this.config };
}
/**
* 判断指定租户是否已配置专属私有 Key
*/
hasTenantConfig(tenantId: string | number): boolean {
return this.tenantConfigs.has(tenantId);
}
/**
* 获取指定租户有效配置(未单独配置则优雅回退至平台公共默认配置)
*/
getTenantConfig(tenantId?: string | number): DeepSeekConfig {
if (tenantId !== undefined && tenantId !== null && this.tenantConfigs.has(tenantId)) {
return { ...this.tenantConfigs.get(tenantId)! };
}
return this.getConfig();
}
/**
* 为指定企业租户更新专属定制配置 (BYOK)
*/
updateTenantConfig(tenantId: string | number, partial: Partial<DeepSeekConfig>): void {
const current = this.getTenantConfig(tenantId);
const updated: DeepSeekConfig = {
...current,
...partial,
enabled: partial.apiKey ? Boolean(partial.apiKey.trim()) : current.enabled,
};
this.tenantConfigs.set(tenantId, updated);
}
/**
* 记录指定租户的 Token 消耗
*/
recordTokenUsage(
tenantId: string | number,
tokens:
| number
| { promptTokens?: number; completionTokens?: number; totalTokens?: number }
): void {
const current = this.tenantUsage.get(tenantId) || {
totalTokens: 0,
promptTokens: 0,
completionTokens: 0,
callCount: 0,
};
let prompt = 0;
let completion = 0;
let total = 0;
if (typeof tokens === 'number') {
total = Number(tokens) || 0;
} else if (tokens && typeof tokens === 'object') {
prompt = Number(tokens.promptTokens) || 0;
completion = Number(tokens.completionTokens) || 0;
total = Number(tokens.totalTokens) || prompt + completion;
}
this.tenantUsage.set(tenantId, {
totalTokens: current.totalTokens + total,
promptTokens: current.promptTokens + prompt,
completionTokens: current.completionTokens + completion,
callCount: current.callCount + 1,
});
}
/**
* 获取指定租户的 Token 用量统计
*/
getTenantTokenUsage(tenantId: string | number): {
totalTokens: number;
promptTokens: number;
completionTokens: number;
callCount: number;
} {
return this.tenantUsage.get(tenantId) || {
totalTokens: 0,
promptTokens: 0,
completionTokens: 0,
callCount: 0,
};
}
/**
* 设置指定租户的 Token 最大额度上限
*/
setTenantTokenQuota(tenantId: string | number, maxTotalTokens: number): void {
this.tenantQuotas.set(tenantId, Math.max(0, Number(maxTotalTokens) || 0));
}
/**
* 断言当前租户 Token 配额充足,超限则抛出 429 阻断
*/
assertTenantTokenQuota(tenantId: string | number, estimatedTokens = 1): void {
const quota = this.tenantQuotas.get(tenantId);
if (quota === undefined) {
return;
}
const current = this.getTenantTokenUsage(tenantId).totalTokens;
if (current + estimatedTokens > quota) {
throw new CoolCommException(
`当前租户 AI Token 配额已耗尽 (${current}/${quota}),请升级套餐或补充额度`,
429
);
}
}
/**
* 动态更新配置
*/
@@ -61,10 +180,11 @@ export class DeepSeekService extends BaseService {
}
/**
* 检查是否已配置有效 API Key
* 检查是否已配置有效 API Key(支持按租户检查或默认平台池检查)
*/
isConfigured(): boolean {
return Boolean(this.config.apiKey && this.config.apiKey.trim().length > 0);
isConfigured(tenantId?: number | string): boolean {
const cfg = this.getTenantConfig(tenantId);
return Boolean(cfg.apiKey && cfg.apiKey.trim().length > 0);
}
/**
@@ -148,13 +268,15 @@ export class DeepSeekService extends BaseService {
}
/**
* 对话补全调用 (支持优雅降级)
* 对话补全调用 (支持多租户 BYOK、Token 计量与优雅降级)
*/
async chatCompletion(
req: DeepSeekChatCompletionRequest,
fallbackReply?: string
fallbackReply?: string,
tenantId?: number | string
): Promise<string> {
const apiKey = this.config.apiKey?.trim();
const cfg = this.getTenantConfig(tenantId);
const apiKey = cfg.apiKey?.trim();
if (!apiKey) {
if (fallbackReply !== undefined) {
@@ -163,12 +285,18 @@ export class DeepSeekService extends BaseService {
throw new CoolCommException('DeepSeek API Key 未配置');
}
const endpoint = `${this.config.baseUrl || 'https://api.deepseek.com'}/chat/completions`;
// 租户 Token 配额校验 (预估输入字符数 / 2)
const estimatedTokens = Math.ceil(
(req.messages || []).reduce((acc, m) => acc + (m.content || '').length, 0) / 2
);
this.assertTenantTokenQuota(tenantId, estimatedTokens);
const endpoint = `${cfg.baseUrl || 'https://api.deepseek.com'}/chat/completions`;
const payload = {
model: req.model || this.config.model || 'deepseek-chat',
model: req.model || cfg.model || 'deepseek-chat',
messages: req.messages,
temperature: req.temperature ?? this.config.temperature ?? 0.7,
max_tokens: req.maxTokens ?? this.config.maxTokens ?? 2048,
temperature: req.temperature ?? cfg.temperature ?? 0.7,
max_tokens: req.maxTokens ?? cfg.maxTokens ?? 2048,
stream: false,
};
@@ -184,6 +312,11 @@ export class DeepSeekService extends BaseService {
}
);
// 记录实际 Token 消耗到租户账本
if (response.data?.usage?.total_tokens) {
this.recordTokenUsage(tenantId, response.data.usage.total_tokens);
}
const content = response.data?.choices?.[0]?.message?.content;
if (content) {
return content.trim();