feat(auth): 实现平台超管与多租户角色权限深度隔离、前端动态路由白名单及DeepSeek配额守卫

这个提交包含在:
wikiboss
2026-09-21 11:21:54 +08:00
父节点 0f1a318f95
当前提交 13752f9906
共有 11 个文件被更改,包括 380 次插入 和 28 次删除

查看文件

@@ -1,13 +1,15 @@
import { Body, Inject, Post } from '@midwayjs/core';
import { BaseController, CoolController } from '@cool-midway/core';
import { Context } from '@midwayjs/koa';
import { BaseController, CoolController, CoolCommException } from '@cool-midway/core';
import { DeepSeekService } from '../../service/deepseek';
import {
DeepSeekChatCompletionRequest,
DeepSeekConfig,
} from '../../../../comm/types/omni';
import { assertSuperAdmin } from '../../../../comm/guard/super-admin';
/**
* DeepSeek 大模型后台管理与调试控制器
* DeepSeek 大模型后台管理与调试控制器 (支持多租户 BYOK 与 Token 计量)
*/
@CoolController({
api: [],
@@ -20,18 +22,32 @@ import {
],
})
export class AdminDeepSeekController extends BaseController {
@Inject()
ctx: Context;
@Inject()
deepSeekService: DeepSeekService;
/**
* 获取当前有效配置(脱敏 API Key)
* 获取当前有效配置(脱敏 API Key,支持租户 BYOK 隔离)
*/
@Post('/config', { summary: '获取或保存 DeepSeek 配置' })
async config(@Body() body?: Partial<DeepSeekConfig>) {
const tenantId = this.ctx?.admin?.tenantId;
if (body && Object.keys(body).length > 0) {
this.deepSeekService.updateConfig(body);
if (tenantId !== undefined && tenantId !== null) {
this.deepSeekService.updateTenantConfig(tenantId, body);
} else {
this.deepSeekService.updateConfig(body);
}
}
const current = this.deepSeekService.getConfig();
const current =
tenantId !== undefined && tenantId !== null
? this.deepSeekService.getTenantConfig(tenantId)
: this.deepSeekService.getConfig();
const maskedKey = current.apiKey
? current.apiKey.length > 8
? `${current.apiKey.slice(0, 4)}****${current.apiKey.slice(-4)}`
@@ -41,16 +57,51 @@ export class AdminDeepSeekController extends BaseController {
return this.ok({
...current,
apiKey: maskedKey,
isConfigured: this.deepSeekService.isConfigured(),
isConfigured: this.deepSeekService.isConfigured(tenantId),
});
}
/**
* 获取当前租户的 Token 消耗统计与额度进度
*/
@Post('/usage', { summary: '租户 Token 消耗与额度统计' })
async usage() {
const tenantId = this.ctx?.admin?.tenantId || 0;
const usage = this.deepSeekService.getTenantTokenUsage(tenantId);
const quota = this.deepSeekService.getTenantTokenQuota(tenantId);
const percent =
quota > 0 ? Math.min(100, Math.round((usage.totalTokens / quota) * 100)) : 0;
return this.ok({
...usage,
quota,
percent,
});
}
/**
* 超级管理员设置租户的 Token 配额上限
*/
@Post('/quota', { summary: '设置租户 Token 配额上限' })
async quota(@Body() body: { tenantId: string | number; maxTokens: number }) {
assertSuperAdmin(this.ctx);
if (!body?.tenantId) {
throw new CoolCommException('缺少租户标识 tenantId');
}
this.deepSeekService.setTenantTokenQuota(body.tenantId, Number(body.maxTokens) || 0);
return this.ok({ success: true });
}
/**
* 测试与 DeepSeek 官方端点连通性
*/
@Post('/test-connection', { summary: '测试连通性' })
async testConnection(@Body('apiKey') apiKey?: string) {
const res = await this.deepSeekService.testConnection(apiKey);
const tenantId = this.ctx?.admin?.tenantId;
const res =
tenantId !== undefined && tenantId !== null
? await this.deepSeekService.testConnection(apiKey, tenantId)
: await this.deepSeekService.testConnection(apiKey);
return this.ok(res);
}
@@ -59,7 +110,11 @@ export class AdminDeepSeekController extends BaseController {
*/
@Post('/chat', { summary: '调试对话补全' })
async chat(@Body() body: DeepSeekChatCompletionRequest) {
const reply = await this.deepSeekService.chatCompletion(body);
const tenantId = this.ctx?.admin?.tenantId;
const reply =
tenantId !== undefined && tenantId !== null
? await this.deepSeekService.chatCompletion(body, undefined, tenantId)
: await this.deepSeekService.chatCompletion(body);
return this.ok({ reply });
}
}

查看文件

@@ -148,6 +148,13 @@ export class DeepSeekService extends BaseService {
this.tenantQuotas.set(tenantId, Math.max(0, Number(maxTotalTokens) || 0));
}
/**
* 获取指定租户的 Token 最大额度上限
*/
getTenantTokenQuota(tenantId: string | number): number {
return this.tenantQuotas.get(tenantId) || 0;
}
/**
* 断言当前租户 Token 配额充足,超限则抛出 429 阻断
*/
@@ -339,8 +346,12 @@ export class DeepSeekService extends BaseService {
/**
* 测试与 DeepSeek 官方端点连通性
*/
async testConnection(testApiKey?: string): Promise<DeepSeekTestConnectionResult> {
const key = (testApiKey || this.config.apiKey || '').trim();
async testConnection(
testApiKey?: string,
tenantId?: number | string
): Promise<DeepSeekTestConnectionResult> {
const cfg = this.getTenantConfig(tenantId);
const key = (testApiKey || cfg.apiKey || '').trim();
if (!key) {
return {
success: false,
@@ -349,8 +360,8 @@ export class DeepSeekService extends BaseService {
}
const startTime = Date.now();
const endpoint = `${this.config.baseUrl || 'https://api.deepseek.com'}/chat/completions`;
const model = this.config.model || 'deepseek-chat';
const endpoint = `${cfg.baseUrl || 'https://api.deepseek.com'}/chat/completions`;
const model = cfg.model || 'deepseek-chat';
try {
await this.httpClient.post(

查看文件

@@ -49,7 +49,10 @@ export class BaseAuthorityMiddleware
let statusCode = 200;
let { url } = ctx;
url = url.replace(this.prefix, '').split('?')[0];
const token = ctx.get('Authorization');
let token = ctx.get('Authorization') || '';
if (token.startsWith('Bearer ')) {
token = token.substring(7);
}
const adminUrl = '/admin/';
// 路由地址为 admin前缀的 需要权限校验
if (_.startsWith(url, adminUrl)) {