文档版本: v1.0
创建日期: 2026-03-17
作者: AI Assistant
状态: Draft
本项目是一个基于 RAG(检索增强生成)的问答系统,支持多知识库管理。飞书机器人作为外部接入点,目前与聊天系统集成,但知识库选择机制不明确。用户希望:
D:\aura\AuraK\server\src\feishu\feishu.service.tsD:\aura\AuraK\server\src\feishu\feishu.controller.tsD:\aura\AuraK\server\src\feishu\feishu-ws.manager.ts飞书机器人通过以下两种方式接收消息:
// feishu.service.ts (line 311-331)
const stream = this.chatService.streamChat(
userMessage,
[],
userId,
llmModel as any,
language,
undefined, // selectedEmbeddingId - 未指定
undefined, // selectedGroups - 未指定
undefined, // selectedFiles - 未指定 ← 关键点
undefined, // historyId
false, // enableRerank
// ... 其他参数
tenantId,
);
结论:飞书机器人当前使用默认知识库(用户的所有文件),因为 selectedFiles 和 selectedGroups 都是 undefined。
// feishu-bot.entity.ts
@Entity('feishu_bots')
export class FeishuBot {
id: string;
userId: string;
appId: string;
appSecret: string;
botName?: string;
enabled: boolean;
isDefault: boolean;
useWebSocket: boolean;
// ❌ 缺少知识库配置字段
}
D:\aura\AuraK\server\src\assessment\assessment.service.tsD:\aura\AuraK\server\src\assessment\assessment.controller.tsD:\aura\AuraK\server\src\assessment\entities\// assessment.controller.ts
POST /assessment/start // 开始测评会话
POST /assessment/:id/answer // 提交答案
SSE /assessment/:id/start-stream // 流式获取初始问题
SSE /assessment/:id/answer-stream // 流式获取评估结果
GET /assessment/:id/state // 获取会话状态
GET /assessment // 获取历史记录
KnowledgeBaseService 和 KnowledgeGroupService 获取内容RagService 进行混合搜索ChatService 进行 LLM 交互明确知识库选择机制
飞书机器人与人才测评集成
知识库配置功能
测评命令支持
/assessment start [kbId|templateId] - 开始测评/assessment answer [answer] - 回答问题/assessment status - 查看状态/assessment result - 获取结果交互体验优化
在 FeishuBot 实体中增加知识库配置字段,支持以下模式:
// D:\aura\AuraK\server\src\feishu\entities\feishu-bot.entity.ts
@Entity('feishu_bots')
export class FeishuBot {
// ... 现有字段保持不变
@Column({ name: 'knowledge_base_id', nullable: true, length: 36 })
knowledgeBaseId: string;
@Column({ name: 'knowledge_group_id', nullable: true, length: 36 })
knowledgeGroupId: string;
@ManyToOne(() => KnowledgeBase, { onDelete: 'SET NULL' })
@JoinColumn({ name: 'knowledge_base_id' })
knowledgeBase?: KnowledgeBase;
@ManyToOne(() => KnowledgeGroup, { onDelete: 'SET NULL' })
@JoinColumn({ name: 'knowledge_group_id' })
knowledgeGroup?: KnowledgeGroup;
}
// D:\aura\AuraK\server\src\migrations\XXXXXX-AddFeishuBotKnowledgeFields.ts
import { MigrationInterface, QueryRunner } from "typeorm";
export class AddFeishuBotKnowledgeFieldsXXXXXX implements MigrationInterface {
name = 'AddFeishuBotKnowledgeFields';
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`
ALTER TABLE feishu_bots
ADD COLUMN knowledge_base_id VARCHAR(36) NULL,
ADD COLUMN knowledge_group_id VARCHAR(36) NULL,
ADD CONSTRAINT fk_feishu_bot_knowledge_base
FOREIGN KEY (knowledge_base_id)
REFERENCES knowledge_bases(id)
ON DELETE SET NULL,
ADD CONSTRAINT fk_feishu_bot_knowledge_group
FOREIGN KEY (knowledge_group_id)
REFERENCES knowledge_groups(id)
ON DELETE SET NULL;
`);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`
ALTER TABLE feishu_bots
DROP FOREIGN KEY fk_feishu_bot_knowledge_base,
DROP FOREIGN KEY fk_feishu_bot_knowledge_group,
DROP COLUMN knowledge_base_id,
DROP COLUMN knowledge_group_id;
`);
}
}
// D:\aura\AuraK\server\src\feishu\dto\create-bot.dto.ts
export class CreateFeishuBotDto {
@IsString()
@IsNotEmpty()
appId: string;
@IsString()
@IsNotEmpty()
appSecret: string;
@IsString()
@IsOptional()
botName?: string;
// 新增知识库配置字段
@IsString()
@IsOptional()
knowledgeBaseId?: string;
@IsString()
@IsOptional()
knowledgeGroupId?: string;
}
// feishu.service.ts
async createBot(userId: string, dto: CreateFeishuBotDto): Promise<FeishuBot> {
const existing = await this.botRepository.findOne({
where: { userId, appId: dto.appId },
});
if (existing) {
Object.assign(existing, dto);
return this.botRepository.save(existing);
}
const bot = this.botRepository.create({ userId, ...dto });
return this.botRepository.save(bot);
}
// feishu.service.ts
async processChatMessage(
bot: FeishuBot,
openId: string,
messageId: string,
userMessage: string,
): Promise<void> {
// ... 前面的代码保持不变
// 确定搜索范围
let selectedFiles: string[] | undefined;
let selectedGroups: string[] | undefined;
// 如果配置了特定知识库,获取该知识库的文件ID
if (bot.knowledgeBaseId) {
selectedFiles = await this.getFilesByKnowledgeBase(
bot.knowledgeBaseId,
userId,
tenantId
);
}
// 如果配置了知识组,使用知识组
else if (bot.knowledgeGroupId) {
selectedGroups = [bot.knowledgeGroupId];
}
// 否则使用默认(所有文件)
const stream = this.chatService.streamChat(
userMessage,
[],
userId,
llmModel as any,
language,
undefined, // selectedEmbeddingId
selectedGroups, // 改为使用配置的知识组
selectedFiles, // 改为使用配置的知识库文件
undefined, // historyId
false, // enableRerank
undefined, // selectedRerankId
undefined, // temperature
undefined, // maxTokens
10, // topK
0.7, // similarityThreshold
undefined, // rerankSimilarityThreshold
undefined, // enableQueryExpansion
undefined, // enableHyDE
tenantId,
);
// ... 后续处理保持不变
}
/**
* 获取知识库下的所有文件ID
*/
private async getFilesByKnowledgeBase(
knowledgeBaseId: string,
userId: string,
tenantId: string
): Promise<string[]> {
try {
// 调用 KnowledgeBaseService 获取文件列表
const kb = await this.knowledgeBaseService.findOne(knowledgeBaseId, userId, tenantId);
if (!kb) {
this.logger.warn(`Knowledge base not found: ${knowledgeBaseId}`);
return [];
}
// 假设 KnowledgeBase 有 files 字段或通过关联表获取
// 这里需要根据实际的 KnowledgeBase 实体结构调整
return kb.files?.map(f => f.id) || [];
} catch (error) {
this.logger.error(`Failed to get files from knowledge base: ${knowledgeBaseId}`, error);
return [];
}
}
通过自然语言命令触发测评功能,支持以下场景:
/assessment start 启动测评// D:\aura\AuraK\server\src\feishu\dto\assessment-command.dto.ts
export enum AssessmentCommandType {
START = 'start',
ANSWER = 'answer',
STATUS = 'status',
RESULT = 'result',
HELP = 'help',
}
export interface AssessmentCommand {
type: AssessmentCommandType;
parameters: string[];
rawMessage: string;
}
// D:\aura\AuraK\server\src\feishu\services\assessment-command.parser.ts
@Injectable()
export class AssessmentCommandParser {
private readonly commandPrefixes = ['/assessment', '/测评', '/eval'];
parse(message: string): AssessmentCommand | null {
const trimmed = message.trim();
// 检查是否是测评命令
const isCommand = this.commandPrefixes.some(prefix =>
trimmed.toLowerCase().startsWith(prefix)
);
if (!isCommand) {
return null;
}
// 解析命令
const parts = trimmed.split(/\s+/);
const commandType = parts[1]?.toLowerCase();
switch (commandType) {
case 'start':
return {
type: AssessmentCommandType.START,
parameters: parts.slice(2),
rawMessage: message,
};
case 'answer':
return {
type: AssessmentCommandType.ANSWER,
parameters: [parts.slice(2).join(' ')],
rawMessage: message,
};
case 'status':
return {
type: AssessmentCommandType.STATUS,
parameters: [],
rawMessage: message,
};
case 'result':
return {
type: AssessmentCommandType.RESULT,
parameters: [],
rawMessage: message,
};
case 'help':
case '?':
return {
type: AssessmentCommandType.HELP,
parameters: [],
rawMessage: message,
};
default:
return {
type: AssessmentCommandType.HELP,
parameters: [],
rawMessage: message,
};
}
}
}
// D:\aura\AuraK\server\src\feishu\entities\feishu-assessment-session.entity.ts
@Entity('feishu_assessment_sessions')
export class FeishuAssessmentSession {
@PrimaryGeneratedColumn('uuid')
id: string;
@Column({ name: 'bot_id' })
botId: string;
@Column({ name: 'open_id' })
openId: string;
@Column({ name: 'assessment_session_id' })
assessmentSessionId: string;
@Column({
type: 'enum',
enum: ['active', 'completed', 'cancelled'],
default: 'active'
})
status: 'active' | 'completed' | 'cancelled';
@CreateDateColumn({ name: 'created_at' })
createdAt: Date;
@UpdateDateColumn({ name: 'updated_at' })
updatedAt: Date;
// 关联关系
@ManyToOne(() => FeishuBot, { onDelete: 'CASCADE' })
@JoinColumn({ name: 'bot_id' })
bot: FeishuBot;
}
// D:\aura\AuraK\server\src\migrations\XXXXXX-CreateFeishuAssessmentSessionTable.ts
import { MigrationInterface, QueryRunner } from "typeorm";
export class CreateFeishuAssessmentSessionTableXXXXXX implements MigrationInterface {
name = 'CreateFeishuAssessmentSessionTable';
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`
CREATE TABLE feishu_assessment_sessions (
id VARCHAR(36) PRIMARY KEY,
bot_id VARCHAR(36) NOT NULL,
open_id VARCHAR(255) NOT NULL,
assessment_session_id VARCHAR(36) NOT NULL,
status ENUM('active', 'completed', 'cancelled') DEFAULT 'active',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
INDEX idx_bot_open (bot_id, open_id),
INDEX idx_assessment_session (assessment_session_id),
CONSTRAINT fk_feishu_assessment_bot
FOREIGN KEY (bot_id)
REFERENCES feishu_bots(id)
ON DELETE CASCADE
);
`);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`
DROP TABLE feishu_assessment_sessions;
`);
}
}
// D:\aura\AuraK\server\src\feishu\services\feishu-assessment.service.ts
@Injectable()
export class FeishuAssessmentService {
private readonly logger = new Logger(FeishuAssessmentService.name);
constructor(
@InjectRepository(FeishuAssessmentSession)
private sessionRepository: Repository<FeishuAssessmentSession>,
private assessmentService: AssessmentService,
private feishuService: FeishuService,
private commandParser: AssessmentCommandParser,
) {}
/**
* 处理测评命令
*/
async handleCommand(
bot: FeishuBot,
openId: string,
message: string,
): Promise<void> {
const command = this.commandParser.parse(message);
if (!command) {
// 不是测评命令,使用默认聊天处理
await this.feishuService.processChatMessage(bot, openId, '', message);
return;
}
try {
switch (command.type) {
case AssessmentCommandType.START:
await this.startAssessment(bot, openId, command.parameters);
break;
case AssessmentCommandType.ANSWER:
await this.submitAnswer(bot, openId, command.parameters[0]);
break;
case AssessmentCommandType.STATUS:
await this.getStatus(bot, openId);
break;
case AssessmentCommandType.RESULT:
await this.getResult(bot, openId);
break;
case AssessmentCommandType.HELP:
await this.sendHelp(bot, openId);
break;
}
} catch (error) {
this.logger.error(`Failed to handle assessment command: ${error.message}`, error);
await this.feishuService.sendTextMessage(
bot,
'open_id',
openId,
`处理测评命令时出错: ${error.message}`
);
}
}
/**
* 开始测评
*/
async startAssessment(
bot: FeishuBot,
openId: string,
parameters: string[],
): Promise<void> {
// 检查是否已有进行中的测评
const existingSession = await this.getActiveSession(bot.id, openId);
if (existingSession) {
await this.feishuService.sendTextMessage(
bot,
'open_id',
openId,
'您已有进行中的测评会话,请先完成当前测评。'
);
return;
}
// 解析参数
const [kbIdOrTemplateId, secondParam] = parameters;
let knowledgeBaseId: string | undefined;
let templateId: string | undefined;
// 判断是知识库ID还是模板ID
if (kbIdOrTemplateId) {
// 这里可以根据实际需求判断参数类型
// 简单实现:如果参数是UUID格式,假设是模板ID
if (kbIdOrTemplateId.length === 36) {
templateId = kbIdOrTemplateId;
} else {
// 否则尝试作为知识库ID
knowledgeBaseId = kbIdOrTemplateId;
}
}
// 使用机器人配置的知识库(如果未指定)
if (!knowledgeBaseId && !templateId && bot.knowledgeBaseId) {
knowledgeBaseId = bot.knowledgeBaseId;
}
this.logger.log(`Starting assessment: bot=${bot.id}, openId=${openId}, kb=${knowledgeBaseId}, template=${templateId}`);
// 创建测评会话
const session = await this.assessmentService.startSession(
bot.userId,
knowledgeBaseId,
bot.user?.tenantId || 'default',
'zh',
templateId,
);
// 存储飞书会话关联
const feishuSession = this.sessionRepository.create({
botId: bot.id,
openId,
assessmentSessionId: session.id,
status: 'active',
});
await this.sessionRepository.save(feishuSession);
// 发送第一个问题
if (session.questions_json && session.questions_json.length > 0) {
const firstQuestion = session.questions_json[0];
const card = this.buildQuestionCard(firstQuestion, session.id, 1, session.questions_json.length);
await this.feishuService.sendCardMessage(bot, 'open_id', openId, card);
} else {
await this.feishuService.sendTextMessage(
bot,
'open_id',
openId,
'测评会话已创建,但未能生成问题。'
);
}
}
/**
* 提交答案
*/
async submitAnswer(
bot: FeishuBot,
openId: string,
answer: string,
): Promise<void> {
const session = await this.getActiveSession(bot.id, openId);
if (!session) {
await this.feishuService.sendTextMessage(
bot,
'open_id',
openId,
'没有进行中的测评会话。请发送 /assessment start 开始测评。'
);
return;
}
this.logger.log(`Submitting answer for session ${session.assessmentSessionId}`);
// 提交答案到测评服务
const result = await this.assessmentService.submitAnswer(
session.assessmentSessionId,
bot.userId,
answer,
'zh',
);
// 更新飞书会话状态
if (result.report) {
session.status = 'completed';
await this.sessionRepository.save(session);
// 发送测评结果
await this.sendAssessmentResult(bot, openId, result);
} else if (result.questions && result.questions.length > 0) {
// 发送下一个问题
const currentQuestionIndex = result.currentQuestionIndex || 0;
const nextQuestion = result.questions[currentQuestionIndex];
const totalQuestions = result.questions.length;
const card = this.buildQuestionCard(
nextQuestion,
session.assessmentSessionId,
currentQuestionIndex + 1,
totalQuestions
);
await this.feishuService.sendCardMessage(bot, 'open_id', openId, card);
}
}
/**
* 获取测评状态
*/
async getStatus(bot: FeishuBot, openId: string): Promise<void> {
const session = await this.getActiveSession(bot.id, openId);
if (!session) {
await this.feishuService.sendTextMessage(
bot,
'open_id',
openId,
'没有进行中的测评会话。'
);
return;
}
const assessmentState = await this.assessmentService.getSessionState(
session.assessmentSessionId,
bot.userId
);
const currentQuestionIndex = assessmentState.currentQuestionIndex || 0;
const totalQuestions = assessmentState.questions?.length || 0;
const message = `测评状态:\n` +
`- 进度: ${currentQuestionIndex + 1}/${totalQuestions}\n` +
`- 状态: ${session.status}\n` +
`- 开始时间: ${session.createdAt.toLocaleString('zh-CN')}`;
await this.feishuService.sendTextMessage(bot, 'open_id', openId, message);
}
/**
* 获取测评结果
*/
async getResult(bot: FeishuBot, openId: string): Promise<void> {
const session = await this.getActiveSession(bot.id, openId);
if (!session) {
await this.feishuService.sendTextMessage(
bot,
'open_id',
openId,
'没有进行中的测评会话。'
);
return;
}
if (session.status !== 'completed') {
await this.feishuService.sendTextMessage(
bot,
'open_id',
openId,
'测评尚未完成,请先完成所有问题。'
);
return;
}
const assessmentState = await this.assessmentService.getSessionState(
session.assessmentSessionId,
bot.userId
);
await this.sendAssessmentResult(bot, openId, assessmentState);
}
/**
* 发送帮助信息
*/
async sendHelp(bot: FeishuBot, openId: string): Promise<void> {
const helpText = `
**人才测评机器人帮助**
命令格式:
- `/assessment start [kbId|templateId]` - 开始测评
- `/assessment answer [answer]` - 提交答案
- `/assessment status` - 查看测评状态
- `/assessment result` - 获取测评结果
- `/assessment help` - 显示帮助
说明:
- 如果未指定知识库/模板,将使用机器人配置的默认知识库
- 也可直接回复答案,无需命令前缀
`.trim();
await this.feishuService.sendTextMessage(bot, 'open_id', openId, helpText);
}
/**
* 获取活跃会话
*/
private async getActiveSession(
botId: string,
openId: string,
): Promise<FeishuAssessmentSession | null> {
return this.sessionRepository.findOne({
where: {
botId,
openId,
status: 'active',
},
order: { createdAt: 'DESC' },
});
}
/**
* 构建问题卡片
*/
private buildQuestionCard(
question: any,
sessionId: string,
currentIndex: number,
totalQuestions: number,
): any {
return {
config: { wide_screen_mode: true },
header: {
template: 'blue',
title: {
content: `人才测评 (${currentIndex}/${totalQuestions})`,
tag: 'plain_text',
},
},
elements: [
{
tag: 'div',
text: {
content: `**问题 ${currentIndex}:** ${question.text || question.content}`,
tag: 'lark_md',
},
},
...(question.options ? [
{
tag: 'div',
text: {
content: `选项:\n${question.options.map((opt: string, i: number) =>
`${String.fromCharCode(65 + i)}. ${opt}`
).join('\n')}`,
tag: 'lark_md',
},
}
] : []),
{
tag: 'div',
text: {
content: `难度: ${question.difficulty || '普通'} | 分值: ${question.score || 1}`,
tag: 'lark_md',
},
},
{
tag: 'hr',
},
{
tag: 'note',
elements: [
{
content: `直接回复答案或使用 /assessment answer [你的答案]`,
tag: 'plain_text',
},
],
},
],
};
}
/**
* 发送测评结果
*/
private async sendAssessmentResult(
bot: FeishuBot,
openId: string,
result: any,
): Promise<void> {
const report = result.report || result.finalReport;
const score = result.finalScore || result.score;
const resultCard = {
config: { wide_screen_mode: true },
header: {
template: 'green',
title: {
content: '测评完成',
tag: 'plain_text',
},
},
elements: [
{
tag: 'div',
text: {
content: `**测评结果**`,
tag: 'lark_md',
},
},
...(score !== undefined ? [
{
tag: 'div',
text: {
content: `**总分**: ${score.toFixed(1)}`,
tag: 'lark_md',
},
}
] : []),
...(report ? [
{
tag: 'div',
text: {
content: `**报告**:\n${report}`,
tag: 'lark_md',
},
}
] : []),
{
tag: 'hr',
},
{
tag: 'note',
elements: [
{
content: `发送 /assessment start 开始新的测评`,
tag: 'plain_text',
},
],
},
],
};
await this.feishuService.sendCardMessage(bot, 'open_id', openId, resultCard);
}
}
// feishu.service.ts
// 新增字段
private feishuAssessmentService: FeishuAssessmentService;
// 在构造函数后初始化
setFeishuAssessmentService(service: FeishuAssessmentService): void {
this.feishuAssessmentService = service;
}
// 修改 _handleMessage 方法
private async _handleMessage(bot: any, event: any): Promise<void> {
const message = event?.message;
if (!message) return;
const messageId = message.message_id;
const openId = event?.sender?.sender_id?.open_id;
if (!openId) {
this.logger.warn('No sender open_id found in Feishu event');
return;
}
// 解析文本内容
let userText = '';
try {
const content = JSON.parse(message.content || '{}');
userText = content.text || '';
} catch {
this.logger.warn('Failed to parse Feishu message content');
return;
}
if (!userText.trim()) return;
try {
// 检查是否是测评命令
if (this.isAssessmentCommand(userText)) {
// 委托给测评服务处理
await this.feishuAssessmentService.handleCommand(bot, openId, userText);
} else {
// 默认使用知识库问答
await this.processChatMessage(bot, openId, messageId, userText);
}
} catch (error) {
this.logger.error('Message handling failed', error);
try {
await this.sendTextMessage(
bot,
'open_id',
openId,
'抱歉,处理您的消息时遇到了错误,请稍后重试。',
);
} catch (sendError) {
this.logger.error('Failed to send error message to Feishu', sendError);
}
}
}
private isAssessmentCommand(message: string): boolean {
const trimmed = message.trim().toLowerCase();
return trimmed.startsWith('/assessment') ||
trimmed.startsWith('/测评') ||
trimmed.startsWith('/eval');
}
// D:\aura\AuraK\server\src\feishu\feishu.module.ts
@Module({
imports: [
TypeOrmModule.forFeature([
FeishuBot,
FeishuAssessmentSession,
]),
forwardRef(() => ChatModule),
forwardRef(() => AssessmentModule),
forwardRef(() => KnowledgeBaseModule),
],
controllers: [FeishuController],
providers: [
FeishuService,
FeishuWsManager,
FeishuAssessmentService,
AssessmentCommandParser,
],
exports: [FeishuService, FeishuAssessmentService],
})
export class FeishuModule {}
POST /feishu/bots
请求体:
{
"appId": "cli_xxx",
"appSecret": "xxx",
"botName": "测评机器人",
"knowledgeBaseId": "kb_xxx", // 可选:特定知识库
"knowledgeGroupId": "group_xxx" // 可选:知识组
}
响应:
{
"id": "bot_xxx",
"appId": "cli_xxx",
"botName": "测评机器人",
"webhookUrl": "/api/feishu/webhook/cli_xxx"
}
PATCH /feishu/bots/:id/knowledge
请求体:
{
"knowledgeBaseId": "kb_xxx",
"knowledgeGroupId": null
}
GET /feishu/bots
响应:
[
{
"id": "bot_xxx",
"appId": "cli_xxx",
"botName": "测评机器人",
"enabled": true,
"knowledgeBaseId": "kb_xxx",
"knowledgeGroupName": "产品文档"
}
]
POST /feishu/assessment/start
请求体:
{
"botId": "bot_xxx",
"openId": "ou_xxx",
"knowledgeBaseId": "kb_xxx",
"templateId": "tmpl_xxx"
}
响应:
{
"sessionId": "sess_xxx",
"question": {
"id": "q_xxx",
"text": "问题内容",
"difficulty": "普通"
}
}
POST /feishu/assessment/answer
请求体:
{
"botId": "bot_xxx",
"openId": "ou_xxx",
"answer": "用户答案"
}
GET /feishu/assessment/status/:botId/:openId
响应:
{
"sessionId": "sess_xxx",
"status": "active",
"currentQuestion": 3,
"totalQuestions": 10,
"startTime": "2026-03-17T10:00:00Z"
}
┌─────────────────┐
│ FeishuBot │
│─────────────────│
│ id │◄──────┐
│ userId │ │
│ appId │ │ 1..*
│ knowledgeBaseId │───────┼──────┐
│ knowledgeGroupId│ │ │
└─────────────────┘ │ │
│ │
│ │
┌─────────────────────────┼──────┼─────────────────────┐
│ │ │ │
│ ┌─────────────────────┐ │ │ ┌─────────────────┐ │
│ │ FeishuAssessment │ │ │ │ KnowledgeBase │ │
│ │ Session │ │ │ └─────────────────┘ │
│ │─────────────────────│ │ │ │
│ │ id │ │ │ ┌─────────────────┐ │
│ │ botId │─┼──────┼─┤ KnowledgeGroup │ │
│ │ openId │ │ │ └─────────────────┘ │
│ │ assessmentSessionId │ │ │ │
│ │ status │ │ │ │
│ └─────────────────────┘ │ │ │
└─────────────────────────┴──────┴─────────────────────┘
│
│ 1..*
┌─────────────────────────┼─────────────────────────┐
│ │ │
│ ┌─────────────────────┐ │ ┌─────────────────────┐ │
│ │ AssessmentSession │ │ │ AssessmentResult │ │
│ │─────────────────────│ │ │─────────────────────│ │
│ │ id │ │ │ id │ │
│ │ userId │ │ │ sessionId │ │
│ │ knowledgeBaseId │ │ │ report │ │
│ │ questions_json │ │ │ score │ │
│ │ finalScore │ │ │ ... │ │
│ └─────────────────────┘ │ └─────────────────────┘ │
└─────────────────────────┴─────────────────────────┘
CREATE TABLE feishu_assessment_sessions (
id VARCHAR(36) PRIMARY KEY,
bot_id VARCHAR(36) NOT NULL,
open_id VARCHAR(255) NOT NULL,
assessment_session_id VARCHAR(36) NOT NULL,
status ENUM('active', 'completed', 'cancelled') DEFAULT 'active',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
INDEX idx_bot_open (bot_id, open_id),
INDEX idx_assessment_session (assessment_session_id),
CONSTRAINT fk_feishu_assessment_bot
FOREIGN KEY (bot_id)
REFERENCES feishu_bots(id)
ON DELETE CASCADE
);
交付物:
交付物:
交付物:
userId 和 tenantId 过滤FeishuBot 实体中关联 User 实体,确保机器人只能访问所属用户的数据| 版本 | 日期 | 修改内容 | 作者 |
|---|---|---|---|
| v1.0 | 2026-03-17 | 初始版本 | AI Assistant |
文档结束