Files
AIclinicalresearch/backend/src/modules/admin/system-kb/systemKbController.ts
HaHafeng 0d9e6b9922 feat(admin): Implement System Knowledge Base management module
Features:

- Backend: SystemKbService with full CRUD (knowledge bases + documents)

- Backend: 8 RESTful API endpoints (list/detail/create/update/delete/upload/download)

- Backend: OSS storage integration (system/knowledge-bases/{kbId}/{docId})

- Backend: RAG engine integration (document parsing, chunking, vectorization)

- Frontend: SystemKbListPage with card-based layout

- Frontend: SystemKbDetailPage with document management table

- Frontend: Master-Detail UX pattern for better user experience

- Document upload (single/batch), download (preserving original filename), delete

Technical:

- Database migration for system_knowledge_bases and system_kb_documents tables

- OSSAdapter.getSignedUrl with Content-Disposition for original filename

- Reuse RAG engine from common/rag for document processing

Tested: Local environment verified, all features working
2026-01-28 21:57:44 +08:00

338 lines
8.0 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 系统知识库控制器
*/
import { FastifyRequest, FastifyReply } from 'fastify';
import { getSystemKbService } from './systemKbService.js';
import { prisma } from '../../../config/database.js';
import { logger } from '../../../common/logging/index.js';
// ==================== 类型定义 ====================
interface CreateKbBody {
code: string;
name: string;
description?: string;
category?: string;
}
interface UpdateKbBody {
name?: string;
description?: string;
category?: string;
}
interface ListKbQuery {
category?: string;
status?: string;
}
interface KbIdParams {
id: string;
}
interface DocIdParams {
id: string;
docId: string;
}
// ==================== 控制器函数 ====================
/**
* 获取知识库列表
*/
export async function listKnowledgeBases(
request: FastifyRequest<{ Querystring: ListKbQuery }>,
reply: FastifyReply
) {
try {
const { category, status } = request.query;
const service = getSystemKbService(prisma);
const kbs = await service.listKnowledgeBases({ category, status });
return reply.send({
success: true,
data: kbs,
});
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
logger.error('获取知识库列表失败', { error: message });
return reply.status(500).send({
success: false,
error: message,
});
}
}
/**
* 获取知识库详情
*/
export async function getKnowledgeBase(
request: FastifyRequest<{ Params: KbIdParams }>,
reply: FastifyReply
) {
try {
const { id } = request.params;
const service = getSystemKbService(prisma);
const kb = await service.getKnowledgeBase(id);
if (!kb) {
return reply.status(404).send({
success: false,
error: '知识库不存在',
});
}
return reply.send({
success: true,
data: kb,
});
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
logger.error('获取知识库详情失败', { error: message });
return reply.status(500).send({
success: false,
error: message,
});
}
}
/**
* 创建知识库
*/
export async function createKnowledgeBase(
request: FastifyRequest<{ Body: CreateKbBody }>,
reply: FastifyReply
) {
try {
const { code, name, description, category } = request.body;
if (!code || !name) {
return reply.status(400).send({
success: false,
error: '编码和名称为必填项',
});
}
const service = getSystemKbService(prisma);
const kb = await service.createKnowledgeBase({ code, name, description, category });
return reply.status(201).send({
success: true,
data: kb,
});
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
logger.error('创建知识库失败', { error: message });
if (message.includes('已存在')) {
return reply.status(409).send({
success: false,
error: message,
});
}
return reply.status(500).send({
success: false,
error: message,
});
}
}
/**
* 更新知识库
*/
export async function updateKnowledgeBase(
request: FastifyRequest<{ Params: KbIdParams; Body: UpdateKbBody }>,
reply: FastifyReply
) {
try {
const { id } = request.params;
const { name, description, category } = request.body;
const service = getSystemKbService(prisma);
const kb = await service.updateKnowledgeBase(id, { name, description, category });
return reply.send({
success: true,
data: kb,
});
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
logger.error('更新知识库失败', { error: message });
return reply.status(500).send({
success: false,
error: message,
});
}
}
/**
* 删除知识库
*/
export async function deleteKnowledgeBase(
request: FastifyRequest<{ Params: KbIdParams }>,
reply: FastifyReply
) {
try {
const { id } = request.params;
const service = getSystemKbService(prisma);
await service.deleteKnowledgeBase(id);
return reply.send({
success: true,
message: '删除成功',
});
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
logger.error('删除知识库失败', { error: message });
return reply.status(500).send({
success: false,
error: message,
});
}
}
/**
* 获取文档列表
*/
export async function listDocuments(
request: FastifyRequest<{ Params: KbIdParams }>,
reply: FastifyReply
) {
try {
const { id } = request.params;
const service = getSystemKbService(prisma);
const docs = await service.listDocuments(id);
return reply.send({
success: true,
data: docs,
});
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
logger.error('获取文档列表失败', { error: message });
return reply.status(500).send({
success: false,
error: message,
});
}
}
/**
* 上传文档
*/
export async function uploadDocument(
request: FastifyRequest<{ Params: KbIdParams }>,
reply: FastifyReply
) {
try {
const { id: kbId } = request.params;
// 处理 multipart 文件上传
const data = await request.file();
if (!data) {
return reply.status(400).send({
success: false,
error: '请上传文件',
});
}
const filename = data.filename;
const fileBuffer = await data.toBuffer();
// 验证文件类型
const allowedTypes = ['pdf', 'docx', 'doc', 'txt', 'md'];
const ext = filename.toLowerCase().split('.').pop();
if (!ext || !allowedTypes.includes(ext)) {
return reply.status(400).send({
success: false,
error: `不支持的文件类型: ${ext},支持: ${allowedTypes.join(', ')}`,
});
}
// 验证文件大小(最大 50MB
const maxSize = 50 * 1024 * 1024;
if (fileBuffer.length > maxSize) {
return reply.status(400).send({
success: false,
error: '文件大小超过限制(最大 50MB',
});
}
const service = getSystemKbService(prisma);
const result = await service.uploadDocument(kbId, filename, fileBuffer);
return reply.status(201).send({
success: true,
data: {
docId: result.docId,
filename,
chunkCount: result.ingestResult.chunkCount,
tokenCount: result.ingestResult.tokenCount,
duration: result.ingestResult.duration,
},
});
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
logger.error('上传文档失败', { error: message });
return reply.status(500).send({
success: false,
error: message,
});
}
}
/**
* 删除文档
*/
export async function deleteDocument(
request: FastifyRequest<{ Params: DocIdParams }>,
reply: FastifyReply
) {
try {
const { id: kbId, docId } = request.params;
const service = getSystemKbService(prisma);
await service.deleteDocument(kbId, docId);
return reply.send({
success: true,
message: '删除成功',
});
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
logger.error('删除文档失败', { error: message });
return reply.status(500).send({
success: false,
error: message,
});
}
}
/**
* 下载文档
*/
export async function downloadDocument(
request: FastifyRequest<{ Params: DocIdParams }>,
reply: FastifyReply
) {
try {
const { id: kbId, docId } = request.params;
const service = getSystemKbService(prisma);
const result = await service.getDocumentDownloadUrl(kbId, docId);
return reply.send({
success: true,
data: result,
});
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
logger.error('获取下载链接失败', { error: message });
return reply.status(500).send({
success: false,
error: message,
});
}
}