# 编码规范 > 一个人的项目更需要规范。没有规范,三个月后你自己都看不懂自己写的代码。 > 规范的核心目的:**让代码可读、可维护、不纠结**。 --- ## 一、技术栈与语言 | 层级 | 语言 | 框架 | 代码放在哪 | |------|------|------|-----------| | 后端 | Python 3.12 + Flask | BaoDan 原生框架 | `api/insurance/` | | 前端 | TypeScript | Vue 3 + Vite | `frontend/src/` | | 脚本 | Python 3.12 | 脚本工具 | `scripts/` | | 配置 | YAML / JSON | — | 项目根目录 | --- ## 二、命名规范 ### 2.1 Python 后端 | 类型 | 规则 | 示例 | 反例 | |------|------|------|------| | 变量 | 小写 + 下划线 | `user_id`, `chat_history` | `userId`, `ChatHistory` | | 常量 | 全大写 + 下划线 | `MAX_RETRY_COUNT`, `BAODAN_BASE_URL` | `maxRetry`, `baodanBaseUrl` | | 函数 | 小写 + 下划线 | `get_user_info()`, `send_message()` | `GetUserInfo()`, `sendMessage()` | | 类 | 大驼峰 | `WecomClient`, `BaoDanService` | `wecom_client`, `baodan_service` | | 文件名 | 小写 + 下划线 | `wecom_client.py`, `config.py` | `WecomClient.py`, `Config.py` | | 模块/包 | 小写 + 下划线 | `routers/wecom_oauth.py` | `routers/WecomOauth.py` | ### 2.2 TypeScript / Vue 前端 | 类型 | 规则 | 示例 | 反例 | |------|------|------|------| | 变量 | 小驼峰 | `userName`, `chatHistory` | `user_name`, `UserName` | | 常量 | 全大写 + 下划线 | `MAX_RETRY_COUNT` | `maxRetryCount` | | 函数 | 小驼峰 | `getUserInfo()`, `sendMessage()` | `get_user_info()` | | 接口/类型 | 大驼峰,I 开头或直接命名 | `UserInfo`, `ApiResponse` | `userInfo`, `i_user_info` | | Vue 组件 | 大驼峰 | `RecommendForm.vue`, `ChatPanel.vue` | `recommend-form.vue` | | Vue 组件引用 | 大驼峰 | `` | `` | | CSS 类名 | 短横线 | `.chat-panel`, `.btn-primary` | `.chatPanel`, `.chat_panel` | ### 2.3 数据库 | 类型 | 规则 | 示例 | |------|------|------| | 表名 | 小写 + 下划线,复数 | `wecom_users`, `recommend_logs` | | 字段名 | 小写 + 下划线 | `created_at`, `user_id` | | 主键 | `id` | `id SERIAL PRIMARY KEY` | | 外键 | `{关联表单数}_id` | `user_id`, `document_id` | ### 2.4 API 路径 | 规则 | 示例 | 反例 | |------|------|------| | 小写 + 短横线 | `/api/wecom/oauth` | `/api/wecomOAuth` | | 名词复数 | `/api/recommend/logs` | `/api/recommend/log` | | 版本号可选 | `/api/v1/wecom/callback` | — | --- ## 三、注释规范 ### 3.1 什么需要注释 **必须注释**: - 所有公开函数/方法的 docstring - 所有类的 docstring - 复杂业务逻辑(不是显而易见的代码) - 为什么要这样做的原因("为什么"比"做了什么"重要) **不需要注释**: - 简单的 getter/setter - 一看就懂的代码(`user_id = data["user_id"]`) - TODO / FIXME(用 issue 追踪,不要堆在代码里) ### 3.2 Python 注释格式 ```python class BaoDanClient: """BaoDan API 客户端,负责与 BaoDan 后端通信。""" def chat(self, user_id: str, query: str, conversation_id: str = "") -> dict: """发送对话消息并获取 AI 回复。 Args: user_id: 企微用户 ID,作为 BaoDan 的用户标识 query: 用户的问题文本 conversation_id: 对话 ID,空字符串表示新对话 Returns: dict: 包含 answer(回答文本)和 conversation_id(对话 ID) Raises: BaoDanAPIError: BaoDan 返回非 200 状态码时抛出 """ # 企微要求 5 秒内响应,所以用 blocking 模式 # 如果 BaoDan 响应超过 5 秒,调用方需要用异步队列处理 resp = requests.post(...) ... ``` ### 3.3 TypeScript / Vue 注释格式 ```typescript /** * 调用 BaoDan Workflow 生成产品推荐方案 * * @param formData - 用户填写的需求信息(年龄、职业、预算等) * @returns 推荐方案的 Markdown 文本 * @throws 当 BaoDan API 调用失败时抛出错误 */ async function generateRecommendation(formData: RecommendFormData): Promise { // ... } ``` ```vue ``` ### 3.4 中文还是英文 | 场景 | 语言 | 原因 | |------|------|------| | 变量名/函数名 | 英文 | 代码必须英文 | | 注释 | 中文 | 你是中文团队,读中文比英文快 | | Docstring | 中文 | 同上 | | Git commit | 英文 | 国际惯例,工具兼容性好 | | 日志 | 中文 | 方便排查问题 | | 错误信息 | 中文 | 用户/管理员看得懂 | --- ## 四、代码格式 ### 4.1 Python 使用自动格式化工具,不要手动对齐: ```bash # 安装 pip install ruff # 格式化 ruff format backend/ # 检查代码风格 ruff check backend/ ``` **`pyproject.toml` 配置**(放在 `backend/` 目录下): ```toml [tool.ruff] line-length = 120 # 行宽 120(比默认 88 宽一些,适合现代屏幕) target-version = "py312" # Python 版本 [tool.ruff.format] quote-style = "double" # 字符串用双引号 [tool.ruff.lint] select = ["E", "F", "I"] # 基础检查 + import 排序 ``` ### 4.2 TypeScript / Vue 使用 ESLint + Prettier: ```bash # 安装(在 frontend/ 目录下) pnpm add -D eslint @vue/eslint-config-typescript prettier # 格式化 pnpm prettier --write src/ # 检查 pnpm eslint src/ ``` ### 4.3 通用规则 | 规则 | Python | TypeScript | |------|--------|------------| | 行宽 | 120 字符 | 120 字符 | | 缩进 | 4 空格 | 2 空格 | | 引号 | 双引号 `"` | 单引号 `'` | | 分号 | 不需要 | 不需要(Prettier 自动处理) | | 尾逗号 | 是 | 是 | --- ## 五、项目结构规范 ### 5.1 Python 后端 ``` backend/ ├── main.py # 入口文件,只做 app 初始化和路由注册 ├── config.py # 配置读取,所有配置从这里获取 ├── routers/ # 路由层:只做参数校验和响应,不写业务逻辑 │ ├── __init__.py │ ├── wecom.py # 企微消息回调 │ ├── wecom_oauth.py # 企微 OAuth │ └── recommend.py # 产品推荐 ├── services/ # 业务层:核心逻辑在这里 │ ├── __init__.py │ ├── baodan_client.py # 封装 BaoDan API 调用 │ └── wecom_client.py # 封装企微 API 调用 ├── models/ # 数据模型 │ ├── __init__.py │ └── schemas.py # Pydantic 请求/响应模型 ├── utils/ # 工具函数 │ ├── __init__.py │ └── crypto.py # 加解密工具 └── requirements.txt # 依赖列表(锁定版本号) ``` **分层原则**: - `routers/` 只做 HTTP 相关的事(解析请求、校验参数、返回响应) - `services/` 做业务逻辑(调 API、处理数据) - `models/` 定义数据结构(Pydantic 模型) - `utils/` 放通用工具函数 ### 5.2 Vue 前端 ``` frontend/src/ ├── main.ts # 入口 ├── App.vue # 根组件 ├── router/ │ └── index.ts # 路由定义 ├── pages/ # 页面组件(一个路由对应一个页面) │ ├── ChatPage.vue # 对话页面 │ ├── RecommendPage.vue # 产品推荐页面 │ └── LoginPage.vue # 登录页面 ├── components/ # 通用组件(可复用的 UI 组件) │ ├── RecommendForm.vue │ └── RecommendResult.vue ├── composables/ # 组合函数(复用逻辑) │ ├── useApi.ts # API 请求封装 │ └── useAuth.ts # 登录状态管理 ├── utils/ │ └── request.ts # HTTP 请求工具(axios 封装) └── types/ └── index.ts # TypeScript 类型定义 ``` **组件拆分原则**: - 一个 `.vue` 文件不超过 300 行,超过就拆分 - `pages/` 放页面级组件(有路由) - `components/` 放可复用组件(无路由) - `composables/` 放可复用逻辑(类似 React hooks) --- ## 六、错误处理规范 ### 6.1 Python 后端 ```python # 定义统一的错误响应格式 class AppError(Exception): """业务异常基类""" def __init__(self, code: int, message: str): self.code = code self.message = message class BaoDanAPIError(AppError): """BaoDan API 调用失败""" def __init__(self, detail: str = ""): super().__init__(code=2001, message=f"BaoDan API 调用失败: {detail}") class WecomAPIError(AppError): """企微 API 调用失败""" def __init__(self, detail: str = ""): super().__init__(code=2002, message=f"企微 API 调用失败: {detail}") # 全局异常处理器 @app.exception_handler(AppError) async def app_error_handler(request, exc: AppError): return JSONResponse( status_code=200, # 业务异常用 200,靠 code 区分 content={"code": exc.code, "message": exc.message, "data": None}, ) @app.exception_handler(Exception) async def general_error_handler(request, exc: Exception): # 记录日志 logger.error(f"未处理的异常: {exc}", exc_info=True) return JSONResponse( status_code=500, content={"code": 9999, "message": "服务器内部错误", "data": None}, ) ``` ### 6.2 Vue 前端 ```typescript // utils/request.ts import axios from 'axios' const request = axios.create({ baseURL: '/api', timeout: 60000, // 产品推荐可能要等 30-60 秒 }) // 响应拦截器:统一处理错误 request.interceptors.response.use( (response) => { const { code, message, data } = response.data if (code !== 0) { // 业务错误,用组件库的 message 提示 ElMessage.error(message) return Promise.reject(new Error(message)) } return data }, (error) => { // 网络错误 ElMessage.error('网络异常,请稍后重试') return Promise.reject(error) }, ) ``` --- ## 七、日志规范 ### 7.1 日志级别 | 级别 | 什么时候用 | 示例 | |------|-----------|------| | `DEBUG` | 开发调试信息 | 请求参数、API 响应详情 | | `INFO` | 正常业务流程 | 收到企微消息、AI 回复成功 | | `WARNING` | 不影响功能但需要注意 | API 响应慢、重试成功 | | `ERROR` | 功能失败 | BaoDan 调用失败、企微回复失败 | ### 7.2 日志格式 ```python import logging logging.basicConfig( level=logging.INFO, format="%(asctime)s [%(levelname)s] %(name)s: %(message)s", datefmt="%Y-%m-%d %H:%M:%S", ) logger = logging.getLogger(__name__) # 使用 logger.info("收到企微消息: user=%s, content=%s", user_id, content[:50]) logger.error("BaoDan API 调用失败: status=%d, body=%s", resp.status_code, resp.text[:200]) ``` ### 7.3 日志原则 1. **记录关键节点**:收到请求、调用外部 API、返回结果 2. **不要记录敏感信息**:API Key、用户密码、完整的消息内容(记录前 50 字符即可) 3. **包含上下文**:user_id、request_id 等,方便排查 4. **日志不是调试工具**:不要到处 `print()`,用 `logger.debug()` 代替 --- ## 八、Git 分支管理 ### 8.1 分支策略 一个人的项目不需要复杂的 Git Flow,用简单的 **主分支 + 功能分支** 即可: ``` main ← 稳定版本,可随时部署 │ ├── dev ← 日常开发分支,功能合入这里 │ │ │ ├── feature/wecom-bot ← 功能分支 │ ├── feature/recommend ← 功能分支 │ └── fix/timeout-bug ← 修复分支 │ └── hotfix/xxx ← 紧急修复(从 main 拉出) ``` ### 8.2 分支命名 | 类型 | 命名规则 | 示例 | |------|---------|------| | 主分支 | `main` | — | | 开发分支 | `dev` | — | | 功能分支 | `feature/功能名` | `feature/wecom-bot` | | 修复分支 | `fix/问题描述` | `fix/message-timeout` | | 紧急修复 | `hotfix/问题描述` | `hotfix/crash-on-startup` | ### 8.3 Commit Message 规范 格式:`类型(范围): 描述` ``` 类型: feat 新功能 fix 修复 bug docs 文档更新 style 代码格式调整(不影响功能) refactor 重构 test 测试 chore 构建/依赖/配置变更 ``` 示例: ``` feat(wecom): 添加企微群聊消息接收功能 fix(recommend): 修复推荐结果为空时页面报错 docs(api): 更新产品推荐接口文档 refactor(baodan): 将 BaoDan 调用抽成独立 service chore(deps): 升级 fastapi 到 0.115 ``` ### 8.4 工作流程 ``` 1. 从 dev 创建功能分支:git checkout -b feature/xxx dev 2. 开发 + 提交(一个小功能一个 commit) 3. 功能完成后合并回 dev:git checkout dev && git merge feature/xxx 4. 删除功能分支:git branch -d feature/xxx 5. dev 稳定后合并到 main:git checkout main && git merge dev 6. 打 tag:git tag v0.1.0 ``` ### 8.5 Git 忽略文件 ```gitignore # .gitignore(项目根目录) # Python __pycache__/ *.pyc .venv/ *.egg-info/ # Node node_modules/ dist/ # 环境变量(包含密钥,绝对不能提交) .env .env.local .env.production # IDE .vscode/ .idea/ *.swp # 系统文件 .DS_Store Thumbs.db # BaoDan 源码(如果作为子模块引入) baodan/ # 上传的文件 uploads/ ``` --- ## 九、依赖管理 ### 9.1 Python 依赖 ```bash # 安装时锁定版本 pip install fastapi==0.115.0 pip install uvicorn==0.32.0 # 生成 requirements.txt pip freeze > requirements.txt # requirements.txt 里必须锁定版本号,不要用 >= 或 ~= # 正确:fastapi==0.115.0 # 错误:fastapi>=0.100 ``` ### 9.2 Node 依赖 ```bash # 安装时精确版本 pnpm add axios@1.7.0 # 不要混用 npm 和 pnpm,统一用 pnpm ``` ### 9.3 核心依赖清单 **Python 后端(`backend/requirements.txt`)**: ``` fastapi==0.115.0 uvicorn==0.32.0 requests==2.32.0 python-dotenv==1.0.0 pydantic==2.10.0 wechatpy==1.8.18 cryptography==44.0.0 ``` **Vue 前端(`frontend/package.json` 核心依赖)**: ``` vue: ^3.5.0 vue-router: ^4.4.0 axios: ^1.7.0 marked: ^15.0.0 element-plus: ^2.9.0 ``` --- ## 十、代码审查清单(自检用) 一个人没有 code review 搭档,但提交前自己过一遍这个清单: ### Python - [ ] 函数有 docstring 吗? - [ ] 参数有类型注解吗? - [ ] 异常有处理吗?(不要裸 `except:`) - [ ] 日志有记录关键节点吗? - [ ] API Key / 密码等敏感信息没写死在代码里吧? - [ ] `ruff format` 和 `ruff check` 通过了吗? ### Vue / TypeScript - [ ] 组件不超过 300 行? - [ ] 变量有类型定义吗?(不要到处 `any`) - [ ] 有 loading / error 状态处理吗? - [ ] API 请求超时设置合理吗? - [ ] ESLint 和 Prettier 通过了吗? ### Git - [ ] commit message 符合规范? - [ ] `.env` 文件没有提交吧? - [ ] 不相关的改动没有混在这个 commit 里吧? --- ## 十一、BaoDan 集成规范 ### 11.1 架构说明 自研代码放在 `baodan-main/api/insurance/` 目录下,作为 Flask Blueprint 注册到 BaoDan 的 Flask 应用中。**不使用 FastAPI**,直接复用 BaoDan 的 Flask 框架。 ### 11.2 代码组织 ``` baodan-main/api/insurance/ ├── __init__.py ├── wecom/ │ ├── wecom_bot.py # 企微消息回调处理 │ ├── wecom_oauth.py # 企微 OAuth 登录 │ └── routes.py # Blueprint 路由 ├── recommend/ │ ├── recommend_api.py # 产品推荐接口 │ └── workflow_helper.py # Workflow 调用封装 ├── permissions/ │ ├── models.py # 角色/权限数据模型 │ ├── middleware.py # 权限校验 │ └── routes.py # 角色管理接口 ├── stats/ │ └── stats_api.py # 数据统计接口 └── db/ ├── wecom_user.py # 企微用户映射表 └── recommend_record.py # 推荐记录表 ``` ### 11.3 调用 BaoDan 核心功能(直接 import) ```python # 调用 BaoDan RAG 检索(直接 import,不走 HTTP) from core.rag.retrieval.dataset_retrieval import DatasetRetrieval # 调用 BaoDan LLM from core.model_runtime.model_manager import ModelManager # 调用 BaoDan Workflow from core.workflow.workflow_engine import WorkflowEngine # 复用 BaoDan 数据库连接 from extensions.ext_database import db from models.dataset import Dataset, Document # 复用 BaoDan 认证 from extensions.ext_login import login_required ``` ### 11.4 禁止事项 - **不要在 `api/insurance/` 以外的目录写代码** - **不要修改 `api/core/`、`api/controllers/`、`api/models/` 等 BaoDan 目录** - **不要自己装 FastAPI**——直接用 BaoDan 的 Flask - **不要自己建数据库连接**——用 `extensions.ext_database.db` - **不要自己装 Redis 客户端**——用 BaoDan 的 Redis 连接 - **不要自己写日志系统**——用 BaoDan 的 logging def chat(self, user_id: str, query: str, conversation_id: str = "") -> dict: """发送对话消息。返回 {answer, conversation_id, sources}""" resp = requests.post( f"{self.base_url}/chat-messages", headers={"Authorization": f"Bearer {self.app_key}"}, json={ "query": query, "response_mode": "blocking", "user": user_id, "conversation_id": conversation_id, }, timeout=60, ) resp.raise_for_status() data = resp.json() return { "answer": data.get("answer", ""), "conversation_id": data.get("conversation_id", ""), "sources": data.get("metadata", {}).get("retriever_resources", []), } def run_workflow(self, inputs: dict, user_id: str) -> dict: """执行 Workflow。返回 {status, outputs}""" resp = requests.post( f"{self.base_url}/workflows/run", headers={"Authorization": f"Bearer {self.workflow_key}"}, json={"inputs": inputs, "response_mode": "blocking", "user": user_id}, timeout=120, ) resp.raise_for_status() data = resp.json() return { "status": data.get("data", {}).get("status", "unknown"), "outputs": data.get("data", {}).get("outputs", {}), } ``` ### 11.2 调用规则 | 规则 | 说明 | |------|------| | 统一入口 | 所有 BaoDan 调用走 `BaoDanClient`,不直接 `requests.post` | | 超时设置 | Chat API 60 秒,Workflow API 120 秒 | | 错误处理 | 捕获异常后抛出 `BaoDanAPIError`,不要吞掉异常 | | 日志记录 | 每次调用记录 request_id、user_id、耗时 | | 重试策略 | BaoDan 5xx 错误重试 1 次,4xx 不重试 | | 用户标识 | 企微用户用 `wecom_{userid}`,网页用户用 `web_{baodan_user_id}` | ### 11.3 禁止事项 - 禁止在前端直接调用 BaoDan API(API Key 会暴露) - 禁止在多个文件中重复写 BaoDan 调用逻辑 - 禁止不设超时调用 BaoDan(可能导致请求永久挂起) - 禁止将 BaoDan 返回的原始 JSON 直接传给前端(需先清洗字段)