AI知识库
开源知识库
知识库搭建组合
Fast + Ollama
RAGFlow + Ollama
深度解读RAGFlow的深度文档理解DeepDoc
Ollama + Dify.ai
AnythingLLM + Ollama
MaxKB + Ollama
LobeChat + Ollama
ChatNio + Ollama
运维知识库
02-新框架技术选型说明
07-性能与并发能力分析
05-开发规范文档
01-现有架构分析报告
06-实施计划与时间表
03-新框架详细设计文档
README
04-数据库迁移方案
00-项目总结与建议
文档管理详细设计
-
+
首页
05-开发规范文档
# PandaWiki 开发规范文档 ## 一、代码规范 ### 1.1 .NET 后端代码规范 #### 1.1.1 命名规范 **类名、接口名、方法名**:使用 PascalCase ```csharp // ✓ 正确 public class NodeService { } public interface INodeRepository { } public async Task<Node> GetNodeAsync(string id) { } // ✗ 错误 public class nodeService { } public interface nodeRepository { } public async Task<Node> getNode(string id) { } ``` **局部变量、参数**:使用 camelCase ```csharp // ✓ 正确 var nodeId = "123"; public void ProcessNode(string nodeId, string kbId) { } // ✗ 错误 var NodeId = "123"; public void ProcessNode(string NodeId, string KbId) { } ``` **私有字段**:使用 _camelCase ```csharp // ✓ 正确 private readonly INodeRepository _nodeRepository; private readonly ILogger<NodeService> _logger; // ✗ 错误 private readonly INodeRepository nodeRepository; private readonly ILogger<NodeService> logger; ``` **常量**:使用 PascalCase ```csharp // ✓ 正确 public const int MaxNodeDepth = 10; private const string CacheKeyPrefix = "node:"; // ✗ 错误 public const int MAX_NODE_DEPTH = 10; private const string cache_key_prefix = "node:"; ``` #### 1.1.2 文件组织 ```csharp // 文件头注释 /// <summary> /// 节点服务实现 /// </summary> /// <author>开发者姓名</author> /// <created>2025-10-30</created> // using 语句排序 using System; using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; // 第三方库 using AutoMapper; using Microsoft.Extensions.Logging; // 项目内部引用 using PandaWiki.Domain.Entities; using PandaWiki.Domain.Interfaces; namespace PandaWiki.Application.Services.Node; /// <summary> /// 节点服务 /// </summary> public class NodeService : INodeService { // 1. 私有字段 private readonly IUnitOfWork _unitOfWork; private readonly IMapper _mapper; private readonly ILogger<NodeService> _logger; // 2. 构造函数 public NodeService( IUnitOfWork unitOfWork, IMapper mapper, ILogger<NodeService> logger) { _unitOfWork = unitOfWork; _mapper = mapper; _logger = logger; } // 3. 公共属性 // 4. 公共方法 public async Task<NodeResponse> GetNodeAsync(string id, CancellationToken cancellationToken = default) { // 实现 } // 5. 私有方法 private async Task<double> CalculatePositionAsync(string kbId, string? parentId, CancellationToken cancellationToken) { // 实现 } } ``` #### 1.1.3 异步编程规范 ```csharp // ✓ 正确:所有异步方法使用 Async 后缀,返回 Task public async Task<Node> GetNodeAsync(string id, CancellationToken cancellationToken = default) { return await _nodeRepository.GetByIdAsync(id, cancellationToken); } // ✓ 正确:传递 CancellationToken public async Task ProcessNodesAsync(List<string> nodeIds, CancellationToken cancellationToken = default) { foreach (var id in nodeIds) { cancellationToken.ThrowIfCancellationRequested(); await ProcessNodeAsync(id, cancellationToken); } } // ✗ 错误:使用 .Result 或 .Wait() public Node GetNode(string id) { return GetNodeAsync(id).Result; // 可能导致死锁 } // ✗ 错误:async void(除了事件处理器) public async void ProcessNode(string id) // 错误!应该返回 Task { await DoSomethingAsync(id); } ``` #### 1.1.4 异常处理 ```csharp // ✓ 正确:使用特定异常类型 public async Task<Node> GetNodeAsync(string id, CancellationToken cancellationToken = default) { var node = await _nodeRepository.GetByIdAsync(id, cancellationToken); if (node == null) { throw new NodeNotFoundException(id); } return node; } // ✓ 正确:记录异常 try { await ProcessNodeAsync(id, cancellationToken); } catch (Exception ex) { _logger.LogError(ex, "处理节点失败: {NodeId}", id); throw; } // ✗ 错误:吞掉异常 try { await ProcessNodeAsync(id, cancellationToken); } catch { // 什么都不做 - 错误! } // ✗ 错误:抛出 Exception throw new Exception("节点不存在"); // 应使用具体异常类型 ``` #### 1.1.5 日志记录 ```csharp // ✓ 正确:使用结构化日志 _logger.LogInformation("创建节点: {NodeId}, 知识库: {KbId}", node.Id, node.KbId); _logger.LogWarning("节点已存在: {NodeId}", id); _logger.LogError(ex, "删除节点失败: {NodeId}", id); // ✓ 正确:日志级别使用 // Trace: 详细调试信息 // Debug: 调试信息 // Information: 一般信息 // Warning: 警告 // Error: 错误 // Critical: 严重错误 // ✗ 错误:使用字符串拼接 _logger.LogInformation("创建节点: " + node.Id); // 性能差 ``` #### 1.1.6 LINQ 查询规范 ```csharp // ✓ 正确:使用方法语法(推荐) var nodes = await _context.Nodes .Where(n => n.KbId == kbId) .Where(n => n.Type == NodeType.Document) .OrderBy(n => n.Position) .ToListAsync(cancellationToken); // ✓ 正确:使用查询语法(复杂查询时) var result = from n in _context.Nodes join v in _context.NodeVersions on n.Id equals v.NodeId where n.KbId == kbId select new { Node = n, Version = v }; // ✗ 错误:多次数据库查询(N+1 问题) var nodes = await _context.Nodes.ToListAsync(); foreach (var node in nodes) { node.Versions = await _context.NodeVersions .Where(v => v.NodeId == node.Id) .ToListAsync(); // 应该使用 Include } // ✓ 正确:使用 Include 预加载 var nodes = await _context.Nodes .Include(n => n.Versions) .Where(n => n.KbId == kbId) .ToListAsync(cancellationToken); ``` ### 1.2 Vue 前端代码规范 #### 1.2.1 组件命名 ```typescript // ✓ 正确:PascalCase 组件名 export default defineComponent({ name: 'NodeTree', // ... }); // 文件名:NodeTree.vue // ✗ 错误 export default defineComponent({ name: 'nodeTree', // 应该用 PascalCase // ... }); ``` #### 1.2.2 组件结构 ```vue <template> <!-- 1. 模板 --> <div class="node-tree"> <!-- 使用 kebab-case 属性 --> <node-item v-for="node in nodes" :key="node.id" :node="node" @select="handleSelect" /> </div> </template> <script setup lang="ts"> // 2. 导入 import { ref, computed, onMounted } from 'vue'; import { useNodeStore } from '@/store/modules/node'; import NodeItem from './NodeItem.vue'; import type { Node } from '@/types/node'; // 3. Props interface Props { kbId: string; } const props = defineProps<Props>(); // 4. Emits interface Emits { (e: 'select', node: Node): void; } const emit = defineEmits<Emits>(); // 5. Composables const nodeStore = useNodeStore(); // 6. State const nodes = ref<Node[]>([]); const loading = ref(false); // 7. Computed const hasNodes = computed(() => nodes.value.length > 0); // 8. Methods const loadNodes = async () => { loading.value = true; try { await nodeStore.fetchNodeTree(props.kbId); nodes.value = nodeStore.nodeTree; } finally { loading.value = false; } }; const handleSelect = (node: Node) => { emit('select', node); }; // 9. Lifecycle onMounted(() => { loadNodes(); }); </script> <style scoped lang="scss"> // 10. 样式 .node-tree { // 使用 BEM 命名 &__item { // ... } &__icon { // ... } } </style> ``` #### 1.2.3 TypeScript 类型定义 ```typescript // ✓ 正确:定义接口 export interface Node { id: string; kbId: string; name: string; type: NodeType; content?: string; children?: Node[]; } export enum NodeType { Folder = 0, Document = 1 } // ✓ 正确:使用类型别名 export type NodeId = string; export type CreateNodeRequest = Omit<Node, 'id' | 'createdAt'>; // ✗ 错误:使用 any const node: any = { ... }; // 应该定义具体类型 ``` #### 1.2.4 组合式函数 (Composables) ```typescript // composables/useNode.ts import { ref, computed } from 'vue'; import type { Ref, ComputedRef } from 'vue'; import { useNodeStore } from '@/store/modules/node'; import type { Node } from '@/types/node'; interface UseNodeReturn { currentNode: Ref<Node | null>; loading: Ref<boolean>; hasUnsavedChanges: ComputedRef<boolean>; loadNode: (id: string) => Promise<void>; saveNode: (id: string, content: string) => Promise<void>; } export function useNode(): UseNodeReturn { const store = useNodeStore(); const loading = ref(false); const currentNode = computed(() => store.currentNode); const hasUnsavedChanges = computed(() => store.hasUnsavedChanges); const loadNode = async (id: string) => { loading.value = true; try { await store.fetchNode(id); } finally { loading.value = false; } }; const saveNode = async (id: string, content: string) => { await store.updateNode(id, { content }); }; return { currentNode, loading, hasUnsavedChanges, loadNode, saveNode }; } ``` --- ## 二、Git 工作流规范 ### 2.1 分支管理 ``` main # 主分支,生产环境代码 └─ develop # 开发分支 ├─ feature/xxx # 功能分支 ├─ bugfix/xxx # 修复分支 └─ hotfix/xxx # 紧急修复分支 ``` **分支命名规范**: ```bash # 功能分支 feature/node-editor feature/ai-chat # 修复分支 bugfix/login-error bugfix/node-tree-render # 紧急修复 hotfix/security-patch # 发布分支 release/v1.0.0 ``` ### 2.2 提交规范 (Conventional Commits) **格式**: ``` <type>(<scope>): <subject> <body> <footer> ``` **类型 (type)**: - `feat`: 新功能 - `fix`: 修复 bug - `docs`: 文档更新 - `style`: 代码格式(不影响代码运行) - `refactor`: 重构 - `perf`: 性能优化 - `test`: 测试相关 - `chore`: 构建过程或辅助工具的变动 **示例**: ```bash # 新功能 git commit -m "feat(node): 添加节点拖拽排序功能" # 修复 bug git commit -m "fix(auth): 修复 JWT token 过期问题" # 文档更新 git commit -m "docs(readme): 更新安装文档" # 详细提交 git commit -m "feat(chat): 添加流式输出支持 - 实现 Server-Sent Events (SSE) - 添加流式响应处理 - 优化前端渲染性能 Closes #123" ``` ### 2.3 代码审查 (Code Review) **PR 规范**: 1. **标题**:简明扼要描述变更 2. **描述**:详细说明变更内容、原因、测试情况 3. **关联 Issue**:引用相关 Issue 4. **截图**:UI 变更提供截图 **审查清单**: - [ ] 代码符合规范 - [ ] 有充分的单元测试 - [ ] 文档已更新 - [ ] 无明显性能问题 - [ ] 无安全隐患 - [ ] 日志记录适当 --- ## 三、测试规范 ### 3.1 单元测试 **命名规范**: ```csharp // 测试类命名:被测试类名 + Tests public class NodeServiceTests { // 测试方法命名:MethodName_Scenario_ExpectedResult [Fact] public async Task GetNodeAsync_WithValidId_ReturnsNode() { // Arrange var id = "test-node-id"; var expectedNode = new Node { Id = id, Name = "Test" }; _mockRepository.Setup(r => r.GetByIdAsync(id, default)) .ReturnsAsync(expectedNode); // Act var result = await _service.GetNodeAsync(id); // Assert Assert.NotNull(result); Assert.Equal(id, result.Id); } [Fact] public async Task GetNodeAsync_WithInvalidId_ThrowsNotFoundException() { // Arrange var id = "invalid-id"; _mockRepository.Setup(r => r.GetByIdAsync(id, default)) .ReturnsAsync((Node?)null); // Act & Assert await Assert.ThrowsAsync<NodeNotFoundException>( () => _service.GetNodeAsync(id) ); } } ``` **测试覆盖率要求**: - 核心业务逻辑:≥ 80% - 工具类:≥ 90% - 整体覆盖率:≥ 70% ### 3.2 集成测试 ```csharp public class NodeApiTests : IClassFixture<WebApplicationFactory<Program>> { private readonly HttpClient _client; public NodeApiTests(WebApplicationFactory<Program> factory) { _client = factory.CreateClient(); } [Fact] public async Task GetNode_ReturnsSuccessStatusCode() { // Arrange var id = "test-node-id"; // Act var response = await _client.GetAsync($"/api/v1/nodes/{id}"); // Assert response.EnsureSuccessStatusCode(); var content = await response.Content.ReadAsStringAsync(); Assert.Contains(id, content); } } ``` --- ## 四、数据库规范 ### 4.1 表命名规范 ```sql -- ✓ 正确:小写 + 下划线,复数形式 CREATE TABLE users (...); CREATE TABLE knowledge_bases (...); CREATE TABLE conversation_messages (...); -- ✗ 错误 CREATE TABLE User (...); -- 应该小写 CREATE TABLE knowledgeBase (...); -- 应该用下划线 CREATE TABLE message (...); -- 应该用复数 ``` ### 4.2 列命名规范 ```sql -- ✓ 正确:小写 + 下划线 CREATE TABLE nodes ( id TEXT PRIMARY KEY, kb_id TEXT NOT NULL, parent_id TEXT, created_at TIMESTAMP NOT NULL, updated_at TIMESTAMP NOT NULL ); -- ✗ 错误 CREATE TABLE nodes ( Id TEXT, -- 应该小写 kbId TEXT, -- 应该用下划线 ParentId TEXT -- 应该小写 ); ``` ### 4.3 索引命名规范 ```sql -- 格式:idx_表名_列名 CREATE INDEX idx_nodes_kb_id ON nodes(kb_id); CREATE INDEX idx_nodes_kb_parent ON nodes(kb_id, parent_id); -- 唯一索引:uniq_表名_列名 CREATE UNIQUE INDEX uniq_users_account ON users(account); -- 外键:fk_表名_引用表名 ALTER TABLE nodes ADD CONSTRAINT fk_nodes_knowledge_bases FOREIGN KEY (kb_id) REFERENCES knowledge_bases(id); ``` --- ## 五、API 设计规范 ### 5.1 RESTful API 规范 **URL 设计**: ``` # ✓ 正确 GET /api/v1/nodes # 获取列表 GET /api/v1/nodes/{id} # 获取详情 POST /api/v1/nodes # 创建 PUT /api/v1/nodes/{id} # 完整更新 PATCH /api/v1/nodes/{id} # 部分更新 DELETE /api/v1/nodes/{id} # 删除 # 嵌套资源 GET /api/v1/nodes/{id}/versions # 获取节点的版本列表 POST /api/v1/nodes/{id}/move # 节点操作(非 CRUD) # ✗ 错误 GET /api/v1/getNode?id=xxx # 不要在 URL 中使用动词 POST /api/v1/node/create # 使用 POST /api/v1/nodes GET /api/v1/nodes/delete/{id} # 使用 DELETE /api/v1/nodes/{id} ``` **HTTP 状态码**: ``` 200 OK # 成功 201 Created # 创建成功 204 No Content # 删除成功 400 Bad Request # 请求参数错误 401 Unauthorized # 未认证 403 Forbidden # 无权限 404 Not Found # 资源不存在 409 Conflict # 资源冲突 422 Unprocessable Entity # 验证失败 500 Internal Server Error # 服务器错误 ``` ### 5.2 请求/响应格式 **请求**: ```json // POST /api/v1/nodes { "kbId": "xxx", "name": "新文档", "type": 1, "parentId": "yyy" } ``` **成功响应**: ```json { "success": true, "data": { "id": "zzz", "kbId": "xxx", "name": "新文档", "type": 1, "createdAt": "2025-10-30T12:00:00Z" }, "message": "创建成功", "timestamp": "2025-10-30T12:00:00Z" } ``` **错误响应**: ```json { "success": false, "error": { "code": "VALIDATION_ERROR", "message": "验证失败", "details": { "name": ["名称不能为空"] } }, "timestamp": "2025-10-30T12:00:00Z" } ``` --- ## 六、安全规范 ### 6.1 认证授权 ```csharp // ✓ 正确:使用授权特性 [Authorize] [HttpGet("{id}")] public async Task<IActionResult> GetNode(string id) { // 验证用户权限 var hasPermission = await _authService.CheckPermissionAsync( User.GetUserId(), ResourceType.Node, id, Permission.Read ); if (!hasPermission) { return Forbid(); } // ... } // ✗ 错误:没有权限检查 [HttpGet("{id}")] public async Task<IActionResult> GetNode(string id) { // 直接返回数据,没有权限检查 return Ok(await _service.GetNodeAsync(id)); } ``` ### 6.2 输入验证 ```csharp // ✓ 正确:使用验证器 public class CreateNodeRequestValidator : AbstractValidator<CreateNodeRequest> { public CreateNodeRequestValidator() { RuleFor(x => x.Name) .NotEmpty().WithMessage("名称不能为空") .MaximumLength(255).WithMessage("名称不能超过255个字符"); RuleFor(x => x.KbId) .NotEmpty().WithMessage("知识库ID不能为空"); RuleFor(x => x.Content) .MaximumLength(1000000).WithMessage("内容过长"); } } // ✗ 错误:不验证输入 [HttpPost] public async Task<IActionResult> CreateNode(CreateNodeRequest request) { // 直接使用用户输入,没有验证 var node = await _service.CreateNodeAsync(request); return Ok(node); } ``` ### 6.3 SQL 注入防护 ```csharp // ✓ 正确:使用参数化查询 (EF Core 自动处理) var nodes = await _context.Nodes .Where(n => n.KbId == kbId) // 安全 .ToListAsync(); // ✗ 错误:拼接 SQL (如果使用原生 SQL) var sql = $"SELECT * FROM nodes WHERE kb_id = '{kbId}'"; // 危险! var nodes = await _context.Nodes.FromSqlRaw(sql).ToListAsync(); // ✓ 正确:使用参数 var sql = "SELECT * FROM nodes WHERE kb_id = {0}"; var nodes = await _context.Nodes.FromSqlRaw(sql, kbId).ToListAsync(); ``` ### 6.4 XSS 防护 ```typescript // ✓ 正确:Vue 自动转义 <template> <div>{{ userInput }}</div> <!-- 自动转义 --> </template> // ✗ 错误:使用 v-html 显示用户输入 <template> <div v-html="userInput"></div> <!-- 危险!可能 XSS --> </template> // ✓ 正确:如需显示 HTML,先净化 import DOMPurify from 'dompurify'; const sanitizedHtml = computed(() => { return DOMPurify.sanitize(props.content); }); <template> <div v-html="sanitizedHtml"></div> <!-- 安全 --> </template> ``` --- ## 七、性能规范 ### 7.1 数据库查询优化 ```csharp // ✓ 正确:使用分页 public async Task<PagedResponse<Node>> GetNodesAsync(int page, int pageSize) { var query = _context.Nodes.Where(n => n.KbId == kbId); var total = await query.CountAsync(); var nodes = await query .OrderBy(n => n.Position) .Skip((page - 1) * pageSize) .Take(pageSize) .ToListAsync(); return new PagedResponse<Node>(nodes, total, page, pageSize); } // ✗ 错误:一次查询所有数据 public async Task<List<Node>> GetNodesAsync() { return await _context.Nodes.ToListAsync(); // 可能数据量巨大 } ``` ### 7.2 缓存使用 ```csharp // ✓ 正确:使用缓存 public async Task<Node> GetNodeAsync(string id) { var cacheKey = $"node:{id}"; var cached = await _cache.GetAsync<Node>(cacheKey); if (cached != null) { return cached; } var node = await _repository.GetByIdAsync(id); await _cache.SetAsync(cacheKey, node, TimeSpan.FromMinutes(10)); return node; } ``` ### 7.3 前端性能 ```vue <!-- ✓ 正确:使用虚拟滚动 --> <template> <RecycleScroller :items="nodes" :item-size="50" key-field="id" > <template #default="{ item }"> <NodeItem :node="item" /> </template> </RecycleScroller> </template> <!-- ✗ 错误:渲染大量 DOM --> <template> <div v-for="node in nodes" :key="node.id"> <NodeItem :node="node" /> <!-- 如果 nodes 有10000项... --> </div> </template> ``` --- ## 八、文档规范 ### 8.1 代码注释 ```csharp /// <summary> /// 获取节点详情 /// </summary> /// <param name="id">节点 ID</param> /// <param name="cancellationToken">取消令牌</param> /// <returns>节点详情</returns> /// <exception cref="NodeNotFoundException">节点不存在时抛出</exception> public async Task<NodeResponse> GetNodeAsync( string id, CancellationToken cancellationToken = default) { // 实现 } ``` ### 8.2 API 文档 使用 Swagger/OpenAPI 自动生成文档: ```csharp /// <summary> /// 获取节点详情 /// </summary> /// <param name="id">节点 ID</param> /// <response code="200">成功返回节点详情</response> /// <response code="404">节点不存在</response> [HttpGet("{id}")] [ProducesResponseType(typeof(NodeResponse), 200)] [ProducesResponseType(404)] public async Task<IActionResult> GetNode(string id) { // ... } ``` --- ## 九、总结 本规范涵盖: 1. **代码规范**:C# 和 TypeScript/Vue 代码风格 2. **Git 规范**:分支管理和提交规范 3. **测试规范**:单元测试和集成测试 4. **数据库规范**:表设计和命名规范 5. **API 规范**:RESTful API 设计 6. **安全规范**:认证、授权、输入验证 7. **性能规范**:查询优化、缓存使用 8. **文档规范**:注释和 API 文档 所有开发人员应严格遵守这些规范,确保代码质量和可维护性。 --- **文档版本**:v1.0 **编写时间**:2025-10-30 **审核状态**:待审核
张猛
2025年11月3日 10:12
转发文档
收藏文档
上一篇
下一篇
手机扫码
复制链接
手机扫一扫转发分享
复制链接
Markdown文件
分享
链接
类型
密码
更新密码