0a9588abb7
- Add pagination support to findAll (page, limit query params) - Add findByTemplateId method to service - Add GET /by-template/:templateId endpoint to controller - Service already includes CRUD for QuestionBank and QuestionBankItem
780 lines
27 KiB
HTML
780 lines
27 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="zh-CN">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>Simple Knowledge Base - 系统概览</title>
|
||
<style>
|
||
:root {
|
||
--primary: #6366f1;
|
||
--primary-light: #818cf8;
|
||
--primary-dark: #4f46e5;
|
||
--bg: #0f172a;
|
||
--bg-card: #1e293b;
|
||
--bg-code: #0d1117;
|
||
--text: #e2e8f0;
|
||
--text-muted: #94a3b8;
|
||
--border: #334155;
|
||
--accent-green: #34d399;
|
||
--accent-blue: #38bdf8;
|
||
--accent-orange: #fb923c;
|
||
--accent-pink: #f472b6;
|
||
}
|
||
|
||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||
|
||
body {
|
||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Noto Sans SC', 'PingFang SC', 'Microsoft YaHei', sans-serif;
|
||
background: var(--bg);
|
||
color: var(--text);
|
||
line-height: 1.8;
|
||
}
|
||
|
||
.hero {
|
||
background: linear-gradient(135deg, #1e1b4b 0%, #312e81 30%, #0f172a 70%, #0c0a09 100%);
|
||
padding: 80px 20px 60px;
|
||
text-align: center;
|
||
position: relative;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.hero::before {
|
||
content: '';
|
||
position: absolute;
|
||
top: -50%;
|
||
left: -50%;
|
||
width: 200%;
|
||
height: 200%;
|
||
background: radial-gradient(circle at 30% 50%, rgba(99, 102, 241, 0.15) 0%, transparent 50%),
|
||
radial-gradient(circle at 70% 50%, rgba(56, 189, 248, 0.1) 0%, transparent 50%);
|
||
animation: pulse 8s ease-in-out infinite;
|
||
}
|
||
|
||
@keyframes pulse {
|
||
0%, 100% { opacity: 0.6; }
|
||
50% { opacity: 1; }
|
||
}
|
||
|
||
.hero h1 {
|
||
font-size: 2.8rem;
|
||
font-weight: 800;
|
||
background: linear-gradient(135deg, #c7d2fe, #818cf8, #38bdf8);
|
||
-webkit-background-clip: text;
|
||
-webkit-text-fill-color: transparent;
|
||
background-clip: text;
|
||
position: relative;
|
||
margin-bottom: 12px;
|
||
}
|
||
|
||
.hero .subtitle {
|
||
font-size: 1.15rem;
|
||
color: var(--text-muted);
|
||
position: relative;
|
||
max-width: 700px;
|
||
margin: 0 auto;
|
||
}
|
||
|
||
.badge-row {
|
||
display: flex;
|
||
gap: 10px;
|
||
justify-content: center;
|
||
flex-wrap: wrap;
|
||
margin-top: 24px;
|
||
position: relative;
|
||
}
|
||
|
||
.badge {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 6px;
|
||
padding: 5px 14px;
|
||
border-radius: 999px;
|
||
font-size: 0.82rem;
|
||
font-weight: 600;
|
||
border: 1px solid;
|
||
}
|
||
|
||
.badge.react { color: #61dafb; border-color: #61dafb33; background: #61dafb10; }
|
||
.badge.nest { color: #e0234e; border-color: #e0234e33; background: #e0234e10; }
|
||
.badge.rag { color: var(--accent-green); border-color: #34d39933; background: #34d39910; }
|
||
.badge.ts { color: #3178c6; border-color: #3178c633; background: #3178c610; }
|
||
|
||
.container {
|
||
max-width: 1100px;
|
||
margin: 0 auto;
|
||
padding: 0 20px;
|
||
}
|
||
|
||
.features-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
||
gap: 20px;
|
||
margin: -40px auto 60px;
|
||
position: relative;
|
||
z-index: 1;
|
||
}
|
||
|
||
.feature-card {
|
||
background: var(--bg-card);
|
||
border: 1px solid var(--border);
|
||
border-radius: 16px;
|
||
padding: 28px;
|
||
transition: transform 0.2s, border-color 0.2s;
|
||
}
|
||
|
||
.feature-card:hover {
|
||
transform: translateY(-3px);
|
||
border-color: var(--primary);
|
||
}
|
||
|
||
.feature-card .icon {
|
||
width: 44px;
|
||
height: 44px;
|
||
border-radius: 12px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
font-size: 1.3rem;
|
||
margin-bottom: 16px;
|
||
}
|
||
|
||
.feature-card h3 {
|
||
font-size: 1.05rem;
|
||
margin-bottom: 8px;
|
||
color: #f1f5f9;
|
||
}
|
||
|
||
.feature-card p {
|
||
font-size: 0.9rem;
|
||
color: var(--text-muted);
|
||
}
|
||
|
||
.icon-blue { background: #38bdf818; }
|
||
.icon-green { background: #34d39918; }
|
||
.icon-orange { background: #fb923c18; }
|
||
.icon-pink { background: #f472b618; }
|
||
.icon-purple { background: #a78bfa18; }
|
||
.icon-yellow { background: #fbbf2418; }
|
||
|
||
section {
|
||
margin-bottom: 56px;
|
||
}
|
||
|
||
.section-title {
|
||
font-size: 1.5rem;
|
||
font-weight: 700;
|
||
margin-bottom: 24px;
|
||
padding-bottom: 12px;
|
||
border-bottom: 2px solid var(--border);
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 10px;
|
||
}
|
||
|
||
.section-title .num {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
width: 32px;
|
||
height: 32px;
|
||
border-radius: 8px;
|
||
background: var(--primary);
|
||
font-size: 0.85rem;
|
||
font-weight: 700;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
/* Architecture Diagram */
|
||
.arch-diagram {
|
||
background: var(--bg-card);
|
||
border: 1px solid var(--border);
|
||
border-radius: 16px;
|
||
padding: 32px;
|
||
overflow-x: auto;
|
||
}
|
||
|
||
.arch-row {
|
||
display: flex;
|
||
justify-content: center;
|
||
gap: 16px;
|
||
margin-bottom: 16px;
|
||
flex-wrap: wrap;
|
||
}
|
||
|
||
.arch-box {
|
||
padding: 14px 22px;
|
||
border-radius: 10px;
|
||
font-size: 0.85rem;
|
||
font-weight: 600;
|
||
text-align: center;
|
||
min-width: 140px;
|
||
border: 1px solid;
|
||
}
|
||
|
||
.arch-box.frontend { background: #61dafb12; border-color: #61dafb33; color: #61dafb; }
|
||
.arch-box.backend { background: #e0234e12; border-color: #e0234e33; color: #e0234e; }
|
||
.arch-box.infra { background: #34d39912; border-color: #34d39933; color: #34d399; }
|
||
.arch-box.ai { background: #a78bfa12; border-color: #a78bfa33; color: #a78bfa; }
|
||
.arch-box.data { background: #fb923c12; border-color: #fb923c33; color: #fb923c; }
|
||
|
||
.arch-arrow {
|
||
text-align: center;
|
||
color: var(--text-muted);
|
||
font-size: 1.2rem;
|
||
margin-bottom: 16px;
|
||
}
|
||
|
||
.arch-label {
|
||
text-align: center;
|
||
font-size: 0.78rem;
|
||
color: var(--text-muted);
|
||
letter-spacing: 1.5px;
|
||
margin-bottom: 10px;
|
||
}
|
||
|
||
/* File Tree */
|
||
.file-tree {
|
||
background: var(--bg-code);
|
||
border: 1px solid var(--border);
|
||
border-radius: 12px;
|
||
padding: 24px 28px;
|
||
font-family: 'Cascadia Code', 'Fira Code', monospace;
|
||
font-size: 0.85rem;
|
||
line-height: 1.9;
|
||
overflow-x: auto;
|
||
}
|
||
|
||
.file-tree .dir { color: var(--accent-blue); font-weight: 600; }
|
||
.file-tree .comment { color: #64748b; font-style: italic; }
|
||
.file-tree .file { color: var(--text); }
|
||
|
||
/* Pipeline */
|
||
.pipeline {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 0;
|
||
flex-wrap: wrap;
|
||
justify-content: center;
|
||
margin: 20px 0;
|
||
}
|
||
|
||
.pipeline-step {
|
||
padding: 12px 20px;
|
||
border-radius: 10px;
|
||
font-size: 0.85rem;
|
||
font-weight: 600;
|
||
text-align: center;
|
||
border: 1px solid var(--border);
|
||
background: var(--bg-card);
|
||
}
|
||
|
||
.pipeline-arrow {
|
||
color: var(--text-muted);
|
||
font-size: 1.1rem;
|
||
padding: 0 6px;
|
||
}
|
||
|
||
.pipeline-step.active {
|
||
border-color: var(--primary);
|
||
background: #6366f118;
|
||
color: var(--primary-light);
|
||
}
|
||
|
||
/* Tables */
|
||
.info-table {
|
||
width: 100%;
|
||
border-collapse: collapse;
|
||
margin: 12px 0;
|
||
}
|
||
|
||
.info-table th, .info-table td {
|
||
padding: 10px 16px;
|
||
text-align: left;
|
||
border-bottom: 1px solid var(--border);
|
||
font-size: 0.9rem;
|
||
}
|
||
|
||
.info-table th {
|
||
color: var(--text-muted);
|
||
font-weight: 600;
|
||
font-size: 0.82rem;
|
||
letter-spacing: 0.5px;
|
||
}
|
||
|
||
.info-table .port {
|
||
font-family: 'Cascadia Code', monospace;
|
||
color: var(--accent-orange);
|
||
font-weight: 600;
|
||
}
|
||
|
||
.info-table .env-key {
|
||
font-family: 'Cascadia Code', monospace;
|
||
color: var(--accent-green);
|
||
font-size: 0.83rem;
|
||
}
|
||
|
||
/* Code Block */
|
||
.code-block {
|
||
background: var(--bg-code);
|
||
border: 1px solid var(--border);
|
||
border-radius: 10px;
|
||
padding: 18px 22px;
|
||
font-family: 'Cascadia Code', 'Fira Code', monospace;
|
||
font-size: 0.83rem;
|
||
line-height: 1.8;
|
||
overflow-x: auto;
|
||
margin: 12px 0;
|
||
}
|
||
|
||
.code-block .cmd { color: var(--accent-green); }
|
||
.code-block .comment { color: #64748b; }
|
||
|
||
/* Two Column Layout */
|
||
.two-col {
|
||
display: grid;
|
||
grid-template-columns: 1fr 1fr;
|
||
gap: 24px;
|
||
}
|
||
|
||
@media (max-width: 768px) {
|
||
.two-col { grid-template-columns: 1fr; }
|
||
.hero h1 { font-size: 2rem; }
|
||
.features-grid { grid-template-columns: 1fr; }
|
||
}
|
||
|
||
.card {
|
||
background: var(--bg-card);
|
||
border: 1px solid var(--border);
|
||
border-radius: 14px;
|
||
padding: 24px;
|
||
}
|
||
|
||
.card h4 {
|
||
font-size: 1rem;
|
||
margin-bottom: 14px;
|
||
color: #f1f5f9;
|
||
}
|
||
|
||
.card ul {
|
||
list-style: none;
|
||
padding: 0;
|
||
}
|
||
|
||
.card ul li {
|
||
padding: 6px 0;
|
||
font-size: 0.88rem;
|
||
color: var(--text-muted);
|
||
display: flex;
|
||
align-items: flex-start;
|
||
gap: 8px;
|
||
}
|
||
|
||
.card ul li::before {
|
||
content: '›';
|
||
color: var(--primary-light);
|
||
font-weight: 700;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.step-list {
|
||
list-style: none;
|
||
padding: 0;
|
||
counter-reset: step;
|
||
}
|
||
|
||
.step-list li {
|
||
counter-increment: step;
|
||
padding: 8px 0;
|
||
font-size: 0.88rem;
|
||
color: var(--text-muted);
|
||
display: flex;
|
||
align-items: flex-start;
|
||
gap: 10px;
|
||
}
|
||
|
||
.step-list li::before {
|
||
content: counter(step);
|
||
display: inline-flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
width: 22px;
|
||
height: 22px;
|
||
border-radius: 6px;
|
||
background: var(--primary);
|
||
color: white;
|
||
font-size: 0.72rem;
|
||
font-weight: 700;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.troubleshoot-item {
|
||
background: var(--bg-card);
|
||
border: 1px solid var(--border);
|
||
border-radius: 10px;
|
||
padding: 16px 20px;
|
||
margin-bottom: 10px;
|
||
}
|
||
|
||
.troubleshoot-item strong {
|
||
color: #f1f5f9;
|
||
font-size: 0.92rem;
|
||
}
|
||
|
||
.troubleshoot-item p {
|
||
color: var(--text-muted);
|
||
font-size: 0.85rem;
|
||
margin-top: 4px;
|
||
}
|
||
|
||
footer {
|
||
text-align: center;
|
||
padding: 40px 20px;
|
||
color: var(--text-muted);
|
||
font-size: 0.82rem;
|
||
border-top: 1px solid var(--border);
|
||
}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
|
||
<!-- Hero -->
|
||
<div class="hero">
|
||
<h1>Simple Knowledge Base</h1>
|
||
<p class="subtitle">全栈 RAG 问答系统 — 基于 React 19 + NestJS 的检索增强生成技术</p>
|
||
<div class="badge-row">
|
||
<span class="badge react">React 19</span>
|
||
<span class="badge nest">NestJS</span>
|
||
<span class="badge rag">RAG 系统</span>
|
||
<span class="badge ts">TypeScript</span>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="container">
|
||
|
||
<!-- 核心特性 -->
|
||
<div class="features-grid">
|
||
<div class="feature-card">
|
||
<div class="icon icon-purple"> </div>
|
||
<h3>多模型支持</h3>
|
||
<p>兼容 OpenAI API(OpenAI、DeepSeek、Claude 等)+ Google Gemini 原生 SDK,可配置 LLM、Embedding 和 Rerank 模型。</p>
|
||
</div>
|
||
<div class="feature-card">
|
||
<div class="icon icon-blue">⚡</div>
|
||
<h3>双处理模式</h3>
|
||
<p>快速模式通过 Apache Tika 提取文本,高精度模式通过视觉管线处理图文混合文档。</p>
|
||
</div>
|
||
<div class="feature-card">
|
||
<div class="icon icon-green"> </div>
|
||
<h3>混合检索</h3>
|
||
<p>基于 Elasticsearch 的向量 + 关键词混合搜索,支持来源引用、相似度评分,可配置分块大小与重叠。</p>
|
||
</div>
|
||
<div class="feature-card">
|
||
<div class="icon icon-orange"> </div>
|
||
<h3>用户隔离</h3>
|
||
<p>JWT 认证 + 每用户独立知识库,数据与配置完全隔离,保障隐私安全。</p>
|
||
</div>
|
||
<div class="feature-card">
|
||
<div class="icon icon-pink"> </div>
|
||
<h3>流式响应</h3>
|
||
<p>基于 Server-Sent Events (SSE) 的实时流式输出,提供流畅低延迟的对话体验。</p>
|
||
</div>
|
||
<div class="feature-card">
|
||
<div class="icon icon-yellow"> </div>
|
||
<h3>多语言支持</h3>
|
||
<p>界面支持日语、中文和英文,错误信息和 API 响应消息全面国际化。</p>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 系统架构 -->
|
||
<section>
|
||
<h2 class="section-title"><span class="num">1</span> 系统架构概览</h2>
|
||
|
||
<div class="arch-diagram">
|
||
<div class="arch-label">前端层</div>
|
||
<div class="arch-row">
|
||
<div class="arch-box frontend">React 19 + Vite<br><small style="opacity:0.7">端口 13001(开发)/ 80(生产)</small></div>
|
||
</div>
|
||
<div class="arch-arrow">↕</div>
|
||
<div class="arch-label">后端层</div>
|
||
<div class="arch-row">
|
||
<div class="arch-box backend">NestJS API<br><small style="opacity:0.7">端口 3001</small></div>
|
||
<div class="arch-box backend">JWT 认证</div>
|
||
<div class="arch-box backend">对话 / RAG</div>
|
||
<div class="arch-box backend">视觉管线</div>
|
||
</div>
|
||
<div class="arch-arrow">↕</div>
|
||
<div class="arch-label">AI 与数据层</div>
|
||
<div class="arch-row">
|
||
<div class="arch-box ai">OpenAI / Gemini<br><small style="opacity:0.7">LLM + 向量嵌入</small></div>
|
||
<div class="arch-box infra">Elasticsearch<br><small style="opacity:0.7">端口 9200</small></div>
|
||
<div class="arch-box infra">Apache Tika<br><small style="opacity:0.7">端口 9998</small></div>
|
||
<div class="arch-box infra">LibreOffice<br><small style="opacity:0.7">端口 8100</small></div>
|
||
<div class="arch-box data">SQLite<br><small style="opacity:0.7">元数据存储</small></div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 处理管线 -->
|
||
<h4 style="margin-top:28px; margin-bottom:14px; color:#f1f5f9;">双处理管线</h4>
|
||
<div class="two-col">
|
||
<div class="card">
|
||
<h4>快速模式(Tika)</h4>
|
||
<div class="pipeline">
|
||
<div class="pipeline-step active">上传</div>
|
||
<span class="pipeline-arrow">→</span>
|
||
<div class="pipeline-step active">Tika 提取</div>
|
||
<span class="pipeline-arrow">→</span>
|
||
<div class="pipeline-step active">向量化</div>
|
||
<span class="pipeline-arrow">→</span>
|
||
<div class="pipeline-step active">存储</div>
|
||
</div>
|
||
<p style="font-size:0.85rem; color:var(--text-muted); text-align:center;">快速文本提取,无 API 调用成本</p>
|
||
</div>
|
||
<div class="card">
|
||
<h4>高精度模式(视觉管线)</h4>
|
||
<div class="pipeline">
|
||
<div class="pipeline-step active">上传</div>
|
||
<span class="pipeline-arrow">→</span>
|
||
<div class="pipeline-step">LibreOffice</div>
|
||
<span class="pipeline-arrow">→</span>
|
||
<div class="pipeline-step">PDF 转图片</div>
|
||
<span class="pipeline-arrow">→</span>
|
||
<div class="pipeline-step">视觉模型</div>
|
||
</div>
|
||
<p style="font-size:0.85rem; color:var(--text-muted); text-align:center;">保留原始排版、图表和图片信息</p>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- 项目结构 -->
|
||
<section>
|
||
<h2 class="section-title"><span class="num">2</span> 项目结构</h2>
|
||
<div class="file-tree">
|
||
<span class="dir">simple-kb/</span><br>
|
||
├── <span class="dir">web/</span> <span class="comment"># React 前端(Vite)</span><br>
|
||
│ ├── <span class="dir">components/</span> <span class="comment"># UI 组件(ChatInterface, ConfigPanel 等)</span><br>
|
||
│ ├── <span class="dir">contexts/</span> <span class="comment"># React Context 提供者</span><br>
|
||
│ ├── <span class="dir">services/</span> <span class="comment"># API 客户端服务</span><br>
|
||
│ └── <span class="dir">utils/</span> <span class="comment"># 工具函数</span><br>
|
||
├── <span class="dir">server/</span> <span class="comment"># NestJS 后端</span><br>
|
||
│ ├── <span class="dir">src/</span><br>
|
||
│ │ ├── <span class="dir">ai/</span> <span class="comment"># AI 服务(向量嵌入等)</span><br>
|
||
│ │ ├── <span class="dir">api/</span> <span class="comment"># API 模块</span><br>
|
||
│ │ ├── <span class="dir">auth/</span> <span class="comment"># JWT 认证</span><br>
|
||
│ │ ├── <span class="dir">chat/</span> <span class="comment"># 对话 / RAG 模块</span><br>
|
||
│ │ ├── <span class="dir">elasticsearch/</span> <span class="comment"># Elasticsearch 集成</span><br>
|
||
│ │ ├── <span class="dir">import-task/</span> <span class="comment"># 导入任务管理</span><br>
|
||
│ │ ├── <span class="dir">knowledge-base/</span> <span class="comment"># 知识库管理</span><br>
|
||
│ │ ├── <span class="dir">libreoffice/</span> <span class="comment"># LibreOffice 集成</span><br>
|
||
│ │ ├── <span class="dir">model-config/</span> <span class="comment"># 模型配置管理</span><br>
|
||
│ │ ├── <span class="dir">vision/</span> <span class="comment"># 视觉模型集成</span><br>
|
||
│ │ └── <span class="dir">vision-pipeline/</span> <span class="comment"># 视觉管线编排</span><br>
|
||
│ ├── <span class="dir">data/</span> <span class="comment"># SQLite 数据库存储</span><br>
|
||
│ ├── <span class="dir">uploads/</span> <span class="comment"># 上传文件存储</span><br>
|
||
│ └── <span class="dir">temp/</span> <span class="comment"># 临时文件</span><br>
|
||
├── <span class="dir">docs/</span> <span class="comment"># 项目文档</span><br>
|
||
├── <span class="dir">nginx/</span> <span class="comment"># Nginx 配置</span><br>
|
||
├── <span class="dir">libreoffice-server/</span> <span class="comment"># LibreOffice 转换服务(Python/FastAPI)</span><br>
|
||
└── <span class="file">docker-compose.yml</span> <span class="comment"># Docker 编排配置</span>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- 开发环境搭建 -->
|
||
<section>
|
||
<h2 class="section-title"><span class="num">3</span> 开发环境搭建</h2>
|
||
|
||
<h4 style="margin-bottom:10px; color:#f1f5f9;">前置条件</h4>
|
||
<div class="card" style="margin-bottom:20px;">
|
||
<ul>
|
||
<li>Node.js 18+</li>
|
||
<li>Yarn 包管理器</li>
|
||
<li>Docker & Docker Compose</li>
|
||
</ul>
|
||
</div>
|
||
|
||
<h4 style="margin-bottom:10px; color:#f1f5f9;">快速启动</h4>
|
||
<div class="code-block">
|
||
<span class="comment"># 安装依赖</span><br>
|
||
<span class="cmd">yarn install</span><br><br>
|
||
<span class="comment"># 启动基础设施服务</span><br>
|
||
<span class="cmd">docker-compose up -d elasticsearch tika libreoffice</span><br><br>
|
||
<span class="comment"># 配置环境变量</span><br>
|
||
<span class="cmd">cp server/.env.sample server/.env</span><br><br>
|
||
<span class="comment"># 同时启动前后端</span><br>
|
||
<span class="cmd">yarn dev</span>
|
||
</div>
|
||
|
||
<h4 style="margin:20px 0 10px; color:#f1f5f9;">开发命令</h4>
|
||
<div class="code-block">
|
||
<span class="comment"># 仅启动前端(端口 13001)</span><br>
|
||
<span class="cmd">cd web && yarn dev</span><br><br>
|
||
<span class="comment"># 仅启动后端(端口 3001)</span><br>
|
||
<span class="cmd">cd server && yarn start:dev</span><br><br>
|
||
<span class="comment"># 运行测试</span><br>
|
||
<span class="cmd">cd server && yarn test</span><br>
|
||
<span class="cmd">cd server && yarn test:e2e</span><br><br>
|
||
<span class="comment"># 代码检查和格式化</span><br>
|
||
<span class="cmd">cd server && yarn lint</span><br>
|
||
<span class="cmd">cd server && yarn format</span>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- Docker 服务 -->
|
||
<section>
|
||
<h2 class="section-title"><span class="num">4</span> Docker 服务与端口</h2>
|
||
<div class="card">
|
||
<table class="info-table">
|
||
<thead>
|
||
<tr><th>服务</th><th>端口</th><th>用途</th></tr>
|
||
</thead>
|
||
<tbody>
|
||
<tr><td>Elasticsearch</td><td class="port">9200</td><td>向量存储与混合检索</td></tr>
|
||
<tr><td>Apache Tika</td><td class="port">9998</td><td>文档文本提取</td></tr>
|
||
<tr><td>LibreOffice Server</td><td class="port">8100</td><td>文档格式转换</td></tr>
|
||
<tr><td>后端 API</td><td class="port">3001</td><td>NestJS REST API</td></tr>
|
||
<tr><td>前端(开发)</td><td class="port">13001</td><td>Vite 开发服务器</td></tr>
|
||
<tr><td>前端(生产)</td><td class="port">80 / 443</td><td>Nginx 反向代理</td></tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- 环境配置 -->
|
||
<section>
|
||
<h2 class="section-title"><span class="num">5</span> 环境配置</h2>
|
||
<div class="card">
|
||
<p style="font-size:0.88rem; color:var(--text-muted); margin-bottom:14px;">关键环境变量,配置于 <code style="color:var(--accent-green); background:#0d1117; padding:2px 8px; border-radius:4px; font-size:0.83rem;">server/.env</code></p>
|
||
<table class="info-table">
|
||
<thead>
|
||
<tr><th>变量名</th><th>默认值</th><th>说明</th></tr>
|
||
</thead>
|
||
<tbody>
|
||
<tr><td class="env-key">OPENAI_API_KEY</td><td>—</td><td>OpenAI 兼容 API 密钥</td></tr>
|
||
<tr><td class="env-key">GEMINI_API_KEY</td><td>—</td><td>Google Gemini API 密钥</td></tr>
|
||
<tr><td class="env-key">ELASTICSEARCH_HOST</td><td>http://localhost:9200</td><td>Elasticsearch 地址</td></tr>
|
||
<tr><td class="env-key">TIKA_HOST</td><td>http://localhost:9998</td><td>Apache Tika 地址</td></tr>
|
||
<tr><td class="env-key">LIBREOFFICE_URL</td><td>http://localhost:8100</td><td>LibreOffice 服务器地址</td></tr>
|
||
<tr><td class="env-key">JWT_SECRET</td><td>—</td><td>JWT 签名密钥</td></tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- 代码规范 -->
|
||
<section>
|
||
<h2 class="section-title"><span class="num">6</span> 代码规范</h2>
|
||
<div class="two-col">
|
||
<div class="card">
|
||
<h4>语言要求</h4>
|
||
<ul>
|
||
<li>代码注释必须使用英文</li>
|
||
<li>日志消息必须使用英文</li>
|
||
<li>错误消息必须支持国际化</li>
|
||
<li>API 响应消息必须支持国际化</li>
|
||
<li>界面支持日语、中文和英文</li>
|
||
</ul>
|
||
</div>
|
||
<div class="card">
|
||
<h4>代码质量</h4>
|
||
<ul>
|
||
<li>后端使用 Jest 进行单元测试和端到端测试</li>
|
||
<li>后端已配置 ESLint 和 Prettier</li>
|
||
<li>格式化:<code style="color:var(--accent-green);">cd server && yarn format</code></li>
|
||
<li>检查:<code style="color:var(--accent-green);">cd server && yarn lint</code></li>
|
||
</ul>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- 常见开发任务 -->
|
||
<section>
|
||
<h2 class="section-title"><span class="num">7</span> 常见开发任务</h2>
|
||
<div class="two-col">
|
||
<div class="card">
|
||
<h4>添加新的 API 端点</h4>
|
||
<ol class="step-list">
|
||
<li>在 <code style="color:var(--accent-blue);">server/src/</code> 对应模块下创建 Controller</li>
|
||
<li>添加 Service 方法,使用英文注释</li>
|
||
<li>更新 DTO 和参数校验</li>
|
||
<li>在 <code style="color:var(--accent-blue);">*.spec.ts</code> 文件中添加测试</li>
|
||
</ol>
|
||
</div>
|
||
<div class="card">
|
||
<h4>添加新的前端组件</h4>
|
||
<ol class="step-list">
|
||
<li>在 <code style="color:var(--accent-blue);">web/components/</code> 下创建组件</li>
|
||
<li>在 <code style="color:var(--accent-blue);">web/types.ts</code> 中添加 TypeScript 接口</li>
|
||
<li>使用 Tailwind CSS 进行样式开发</li>
|
||
<li>在 <code style="color:var(--accent-blue);">web/services/</code> 中连接后端服务</li>
|
||
</ol>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- 部署 -->
|
||
<section>
|
||
<h2 class="section-title"><span class="num">8</span> 部署</h2>
|
||
<div class="two-col">
|
||
<div class="card">
|
||
<h4>开发环境</h4>
|
||
<div class="code-block">
|
||
<span class="cmd">docker-compose up -d elasticsearch tika libreoffice</span><br>
|
||
<span class="cmd">yarn dev</span>
|
||
</div>
|
||
</div>
|
||
<div class="card">
|
||
<h4>生产环境</h4>
|
||
<div class="code-block">
|
||
<span class="comment"># 构建并启动所有服务</span><br>
|
||
<span class="cmd">docker-compose up -d</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- 故障排查 -->
|
||
<section>
|
||
<h2 class="section-title"><span class="num">9</span> 故障排查</h2>
|
||
<div class="troubleshoot-item">
|
||
<strong>Elasticsearch 无法启动</strong>
|
||
<p>检查 docker-compose.yml 中的内存限制配置</p>
|
||
</div>
|
||
<div class="troubleshoot-item">
|
||
<strong>文件上传失败</strong>
|
||
<p>确保 <code style="color:var(--accent-green);">uploads/</code> 和 <code style="color:var(--accent-green);">temp/</code> 目录存在且具有正确的读写权限</p>
|
||
</div>
|
||
<div class="troubleshoot-item">
|
||
<strong>视觉管线报错</strong>
|
||
<p>确认 LibreOffice 服务正在运行且 8100 端口可访问</p>
|
||
</div>
|
||
<div class="troubleshoot-item">
|
||
<strong>API 密钥错误</strong>
|
||
<p>检查 <code style="color:var(--accent-green);">server/.env</code> 中的环境变量配置</p>
|
||
</div>
|
||
<div class="troubleshoot-item">
|
||
<strong>数据库重置</strong>
|
||
<p>删除 <code style="color:var(--accent-green);">server/data/metadata.db</code> 及 Elasticsearch 数据卷</p>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- 调试 -->
|
||
<section>
|
||
<h2 class="section-title"><span class="num">10</span> 调试与健康检查</h2>
|
||
<div class="code-block">
|
||
<span class="comment"># 检查 Elasticsearch 状态</span><br>
|
||
<span class="cmd">curl http://localhost:9200/_cat/indices</span><br><br>
|
||
<span class="comment"># 检查 Tika 状态</span><br>
|
||
<span class="cmd">curl http://localhost:9998/tika</span><br><br>
|
||
<span class="comment"># 检查 LibreOffice 状态</span><br>
|
||
<span class="cmd">curl http://localhost:8100/health</span>
|
||
</div>
|
||
</section>
|
||
|
||
</div>
|
||
|
||
<footer>
|
||
Simple Knowledge Base — 全栈 RAG 问答系统 — React 19 + NestJS + Elasticsearch
|
||
</footer>
|
||
|
||
</body>
|
||
</html>
|