baodan/docs/保险智能客服系统_编码规范.md

682 lines
19 KiB
Markdown
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.

# 编码规范
> 一个人的项目更需要规范。没有规范,三个月后你自己都看不懂自己写的代码。
> 规范的核心目的:**让代码可读、可维护、不纠结**。
---
## 一、技术栈与语言
| 层级 | 语言 | 框架 | 代码放在哪 |
|------|------|------|-----------|
| 后端 | 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 组件引用 | 大驼峰 | `<RecommendForm />` | `<recommend-form />` |
| 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<string> {
// ...
}
```
```vue
<template>
<!-- 产品推荐表单 用户填写需求信息 -->
<div class="recommend-form">
...
</div>
</template>
<script setup lang="ts">
/**
* RecommendForm 组件
*
* 用户填写保险需求信息(年龄、职业、预算等),
* 提交后调用后端接口生成 AI 推荐方案。
*/
// 表单数据 — 所有字段都有默认值,避免提交时空值
const formData = ref<RecommendFormData>({
age: 30,
gender: 'male',
occupation: '',
annualIncome: 0,
budget: 0,
insuranceTypes: [],
})
</script>
```
### 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. 功能完成后合并回 devgit checkout dev && git merge feature/xxx
4. 删除功能分支git branch -d feature/xxx
5. dev 稳定后合并到 maingit checkout main && git merge dev
6. 打 taggit 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 APIAPI Key 会暴露)
- 禁止在多个文件中重复写 BaoDan 调用逻辑
- 禁止不设超时调用 BaoDan可能导致请求永久挂起
- 禁止将 BaoDan 返回的原始 JSON 直接传给前端(需先清洗字段)