19 KiB
编码规范
一个人的项目更需要规范。没有规范,三个月后你自己都看不懂自己写的代码。 规范的核心目的:让代码可读、可维护、不纠结。
一、技术栈与语言
| 层级 | 语言 | 框架 | 代码放在哪 |
|---|---|---|---|
| 后端 | 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 注释格式
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 注释格式
/**
* 调用 BaoDan Workflow 生成产品推荐方案
*
* @param formData - 用户填写的需求信息(年龄、职业、预算等)
* @returns 推荐方案的 Markdown 文本
* @throws 当 BaoDan API 调用失败时抛出错误
*/
async function generateRecommendation(formData: RecommendFormData): Promise<string> {
// ...
}
<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
使用自动格式化工具,不要手动对齐:
# 安装
pip install ruff
# 格式化
ruff format backend/
# 检查代码风格
ruff check backend/
pyproject.toml 配置(放在 backend/ 目录下):
[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:
# 安装(在 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 后端
# 定义统一的错误响应格式
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 前端
// 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 日志格式
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 日志原则
- 记录关键节点:收到请求、调用外部 API、返回结果
- 不要记录敏感信息:API Key、用户密码、完整的消息内容(记录前 50 字符即可)
- 包含上下文:user_id、request_id 等,方便排查
- 日志不是调试工具:不要到处
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(项目根目录)
# 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 依赖
# 安装时锁定版本
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 依赖
# 安装时精确版本
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)
# 调用 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 直接传给前端(需先清洗字段)