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
338 lines
8.0 KiB
TypeScript
338 lines
8.0 KiB
TypeScript
/**
|
||
* 系统知识库控制器
|
||
*/
|
||
|
||
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,
|
||
});
|
||
}
|
||
}
|