# 飞书机器人与人才测评集成设计文档 > **文档版本**: v1.0 > **创建日期**: 2026-03-17 > **作者**: AI Assistant > **状态**: Draft --- ## 目录 1. [概述](#概述) 2. [现状分析](#现状分析) 3. [需求分析](#需求分析) 4. [详细设计方案](#详细设计方案) 5. [API 接口设计](#api-接口设计) 6. [数据库设计](#数据库设计) 7. [实施计划](#实施计划) 8. [安全考虑](#安全考虑) 9. [附录](#附录) --- ## 概述 ### 背景 本项目是一个基于 RAG(检索增强生成)的问答系统,支持多知识库管理。飞书机器人作为外部接入点,目前与聊天系统集成,但知识库选择机制不明确。用户希望: 1. 明确飞书机器人当前对接的知识库 2. 将飞书机器人与人才测评模块集成 ### 设计目标 - 明确飞书机器人的知识库选择机制 - 实现飞书机器人与人才测评的完整集成 - 保持多租户隔离和系统安全性 - 提供友好的用户交互体验 --- ## 现状分析 ### 1. 飞书机器人知识库对接现状 #### 当前实现位置 - **主服务**: `D:\aura\AuraK\server\src\feishu\feishu.service.ts` - **控制器**: `D:\aura\AuraK\server\src\feishu\feishu.controller.ts` - **WebSocket 管理**: `D:\aura\AuraK\server\src\feishu\feishu-ws.manager.ts` #### 集成方式 飞书机器人通过以下两种方式接收消息: 1. **Webhook**:飞书开放平台推送事件 2. **WebSocket**:实时消息推送(推荐,性能更好) #### 知识库选择逻辑(关键代码) ```typescript // 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`。 #### 数据库实体 ```typescript // 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; // ❌ 缺少知识库配置字段 } ``` ### 2. 人才测评模块现状 #### 模块位置 - **主服务**: `D:\aura\AuraK\server\src\assessment\assessment.service.ts` - **控制器**: `D:\aura\AuraK\server\src\assessment\assessment.controller.ts` - **实体**: `D:\aura\AuraK\server\src\assessment\entities\` #### 核心功能 1. **会话管理**:创建、查询、删除测评会话 2. **问题生成**:基于知识库内容生成测评问题 3. **问答交互**:用户回答问题,系统评估并生成下一个问题 4. **报告生成**:测评完成后生成详细报告和评分 5. **流式支持**:实时更新测评进度 #### 关键接口 ```typescript // 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 交互 - 使用 LangGraph 构建评估图算法 --- ## 需求分析 ### 用户需求 1. **明确知识库选择机制** - 飞书机器人当前对接哪个知识库? - 如何配置飞书机器人使用特定知识库? 2. **飞书机器人与人才测评集成** - 通过飞书机器人启动测评 - 通过飞书机器人回答测评问题 - 通过飞书机器人获取测评结果 ### 功能需求 1. **知识库配置功能** - 支持为每个飞书机器人配置特定知识库或知识组 - 支持动态切换知识库 2. **测评命令支持** - `/assessment start [kbId|templateId]` - 开始测评 - `/assessment answer [answer]` - 回答问题 - `/assessment status` - 查看状态 - `/assessment result` - 获取结果 3. **交互体验优化** - 使用飞书卡片展示问题 - 实时更新测评进度 - 友好的错误提示 ### 非功能需求 1. **安全性**:多租户隔离,防止越权访问 2. **性能**:WebSocket 实时推送,避免超时 3. **可扩展性**:支持未来新增测评类型 4. **兼容性**:不影响现有聊天功能 --- ## 详细设计方案 ### 方案 1:飞书机器人知识库选择机制 #### 设计思路 在 `FeishuBot` 实体中增加知识库配置字段,支持以下模式: 1. **默认模式**:使用用户所有文件(当前行为) 2. **特定知识库**:只搜索指定知识库的文件 3. **知识组**:搜索知识组下的所有文件 #### 数据库变更 ##### 1.1 新增字段到 FeishuBot 实体 ```typescript // 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; } ``` ##### 1.2 创建数据库迁移 ```typescript // 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 { 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 { 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; `); } } ``` #### 1.3 更新 DTO ```typescript // 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; } ``` #### 1.4 修改 FeishuService ##### 1.4.1 更新创建机器人方法 ```typescript // feishu.service.ts async createBot(userId: string, dto: CreateFeishuBotDto): Promise { 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); } ``` ##### 1.4.2 修改消息处理逻辑 ```typescript // feishu.service.ts async processChatMessage( bot: FeishuBot, openId: string, messageId: string, userMessage: string, ): Promise { // ... 前面的代码保持不变 // 确定搜索范围 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 { 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 []; } } ``` ### 方案 2:飞书机器人与人才测评集成 #### 设计思路 通过自然语言命令触发测评功能,支持以下场景: 1. 用户发送 `/assessment start` 启动测评 2. 系统发送问题卡片 3. 用户回复答案 4. 系统评估并发送下一个问题 5. 测评完成发送结果报告 #### 2.1 命令解析机制 ##### 2.1.1 命令类型定义 ```typescript // 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; } ``` ##### 2.1.2 命令解析器 ```typescript // 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, }; } } } ``` #### 2.2 测评会话管理 ##### 2.2.1 数据库实体 ```typescript // 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; } ``` ##### 2.2.2 迁移脚本 ```typescript // 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 { 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 { await queryRunner.query(` DROP TABLE feishu_assessment_sessions; `); } } ``` #### 2.3 服务层实现 ##### 2.3.1 飞书测评服务 ```typescript // 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, private assessmentService: AssessmentService, private feishuService: FeishuService, private commandParser: AssessmentCommandParser, ) {} /** * 处理测评命令 */ async handleCommand( bot: FeishuBot, openId: string, message: string, ): Promise { 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 { // 检查是否已有进行中的测评 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 { 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 { 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 { 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 { 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 { 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 { 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); } } ``` #### 2.4 集成到 FeishuService ##### 2.4.1 修改消息处理 ```typescript // feishu.service.ts // 新增字段 private feishuAssessmentService: FeishuAssessmentService; // 在构造函数后初始化 setFeishuAssessmentService(service: FeishuAssessmentService): void { this.feishuAssessmentService = service; } // 修改 _handleMessage 方法 private async _handleMessage(bot: any, event: any): Promise { 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'); } ``` ##### 2.4.2 模块初始化 ```typescript // 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 {} ``` --- ## API 接口设计 ### 1. 飞书机器人管理接口 #### 1.1 创建/更新飞书机器人 ```http POST /feishu/bots ``` **请求体**: ```json { "appId": "cli_xxx", "appSecret": "xxx", "botName": "测评机器人", "knowledgeBaseId": "kb_xxx", // 可选:特定知识库 "knowledgeGroupId": "group_xxx" // 可选:知识组 } ``` **响应**: ```json { "id": "bot_xxx", "appId": "cli_xxx", "botName": "测评机器人", "webhookUrl": "/api/feishu/webhook/cli_xxx" } ``` #### 1.2 更新知识库配置 ```http PATCH /feishu/bots/:id/knowledge ``` **请求体**: ```json { "knowledgeBaseId": "kb_xxx", "knowledgeGroupId": null } ``` #### 1.3 获取机器人列表 ```http GET /feishu/bots ``` **响应**: ```json [ { "id": "bot_xxx", "appId": "cli_xxx", "botName": "测评机器人", "enabled": true, "knowledgeBaseId": "kb_xxx", "knowledgeGroupName": "产品文档" } ] ``` ### 2. 测评会话接口(可选) #### 2.1 通过飞书启动测评 ```http POST /feishu/assessment/start ``` **请求体**: ```json { "botId": "bot_xxx", "openId": "ou_xxx", "knowledgeBaseId": "kb_xxx", "templateId": "tmpl_xxx" } ``` **响应**: ```json { "sessionId": "sess_xxx", "question": { "id": "q_xxx", "text": "问题内容", "difficulty": "普通" } } ``` #### 2.2 提交测评答案 ```http POST /feishu/assessment/answer ``` **请求体**: ```json { "botId": "bot_xxx", "openId": "ou_xxx", "answer": "用户答案" } ``` #### 2.3 获取测评状态 ```http GET /feishu/assessment/status/:botId/:openId ``` **响应**: ```json { "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 │ │ │ ... │ │ │ └─────────────────────┘ │ └─────────────────────┘ │ └─────────────────────────┴─────────────────────────┘ ``` ### 数据表结构 #### feishu_assessment_sessions ```sql 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 ); ``` --- ## 实施计划 ### 阶段 1:基础架构(1-2 天) #### 任务清单 - [ ] 1.1 创建数据库迁移脚本 - [ ] 1.2 更新 FeishuBot 实体和 DTO - [ ] 1.3 修改 FeishuService 支持知识库选择 - [ ] 1.4 更新飞书机器人创建/更新接口 **交付物**: - 数据库迁移脚本 - 更新后的实体和 DTO - 修改后的 FeishuService ### 阶段 2:测评集成(2-3 天) #### 任务清单 - [ ] 2.1 创建 FeishuAssessmentSession 实体和迁移 - [ ] 2.2 实现命令解析器 - [ ] 2.3 实现 FeishuAssessmentService - [ ] 2.4 集成到 FeishuService - [ ] 2.5 设计并实现飞书卡片模板 **交付物**: - 测评会话实体和迁移 - 命令解析器 - 测评服务实现 - 飞书卡片设计 ### 阶段 3:测试优化(1-2 天) #### 任务清单 - [ ] 3.1 单元测试 - [ ] 3.2 集成测试 - [ ] 3.3 性能测试 - [ ] 3.4 文档编写 **交付物**: - 测试用例和测试报告 - 性能测试结果 - 用户使用文档 --- ## 安全考虑 ### 1. 多租户隔离 - **机制**:所有查询必须包含 `userId` 和 `tenantId` 过滤 - **实现**:在 `FeishuBot` 实体中关联 `User` 实体,确保机器人只能访问所属用户的数据 ### 2. 命令验证 - **机制**:白名单命令验证,防止恶意命令注入 - **实现**:命令解析器只识别预定义的命令格式 ### 3. 会话超时 - **机制**:测评会话设置超时时间(如 24 小时) - **实现**:定时清理过期会话 ### 4. 数据隐私 - **机制**:测评结果仅对授权用户可见 - **实现**:所有接口使用 JWT 认证,验证用户权限 ### 5. 敏感信息保护 - **机制**:不存储明文的 App Secret - **实现**:加密存储 App Secret,使用时解密 --- ## 附录 ### A. 参考资料 - [飞书开放平台文档](https://open.feishu.cn/document) - [RAG 系统架构设计](./rag-architecture.md) - [人才测评模块文档](./assessment-module.md) ### B. 术语表 - **RAG**:检索增强生成 (Retrieval-Augmented Generation) - **FeishuBot**:飞书机器人实体 - **KnowledgeBase**:知识库实体 - **AssessmentSession**:测评会话实体 ### C. 变更记录 | 版本 | 日期 | 修改内容 | 作者 | |------|------|----------|------| | v1.0 | 2026-03-17 | 初始版本 | AI Assistant | --- **文档结束**