baodan/docs/后端开发文档.md

568 lines
16 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.

# 保险智能客服系统 — 后端开发文档
> **文档版本**V1.0
> **更新日期**2026-06-25
> **技术栈**Python + Flask + SQLAlchemy + PostgreSQL
---
## 一、项目结构
```
api/
├── insurance/ # 保险模块(自研代码)
│ ├── __init__.py
│ ├── routes.py # Blueprint 注册
│ ├── auth/ # 认证模块
│ │ ├── routes.py
│ │ └── service.py
│ ├── chat/ # 对话模块
│ │ ├── routes.py
│ │ ├── service.py
│ │ └── agent_routes.py
│ ├── recommend/ # 推荐模块
│ │ ├── routes.py
│ │ ├── service.py
│ │ └── workflow_helper.py
│ ├── admin/ # 管理模块
│ │ ├── routes.py
│ │ ├── service.py
│ │ ├── template_service.py
│ │ ├── notification_service.py
│ │ └── department_service.py
│ ├── kb/ # 知识库模块
│ │ ├── routes.py
│ │ └── service.py
│ ├── stats/ # 统计模块
│ │ ├── routes.py
│ │ └── service.py
│ ├── wecom/ # 企微模块
│ │ ├── routes.py
│ │ └── service.py
│ ├── middleware/ # 中间件
│ │ └── auth_middleware.py
│ ├── models/ # 数据模型
│ │ ├── wecom_user.py
│ │ ├── recommendation.py
│ │ ├── template.py
│ │ ├── notification.py
│ │ ├── department.py
│ │ ├── role.py
│ │ └── prompt.py
│ ├── db/ # 数据库
│ │ └── compat.py
│ └── utils/ # 工具函数
│ ├── error_handler.py
│ └── audit.py
├── main.py # BaoDan 入口(已修改)
└── scripts/
├── patch_app.py # 路由注册补丁
└── entrypoint.sh # 容器入口脚本
```
---
## 二、待开发功能
### 2.1 产品推荐 Workflow 调用
**目标**:封装 Dify Workflow API实现产品推荐
**文件**`api/insurance/recommend/workflow_helper.py`
#### 当前代码
```python
"""BaoDan Workflow 调用封装。"""
import requests
from flask import current_app
class WorkflowHelper:
"""Workflow API 调用封装。"""
def run_workflow(self, inputs: dict, user_id: str) -> dict:
"""执行 Workflow 获取推荐方案。"""
base_url = current_app.config.get("BAODAN_API_URL", "http://localhost:5001")
api_key = current_app.config.get("BAODAN_WORKFLOW_API_KEY", "")
resp = requests.post(
f"{base_url}/v1/workflows/run",
json={
"inputs": inputs,
"response_mode": "blocking",
"user": f"recommend_{user_id}",
},
headers={"Authorization": f"Bearer {api_key}"},
timeout=120,
)
return resp.json()
```
#### 需要修改
1. 添加错误处理
2. 添加重试机制
3. 解析 Workflow 输出
```python
def run_workflow(self, inputs: dict, user_id: str) -> dict:
"""执行 Workflow 获取推荐方案。"""
base_url = current_app.config.get("BAODAN_API_URL", "http://localhost:5001")
api_key = current_app.config.get("BAODAN_WORKFLOW_API_KEY", "")
try:
resp = requests.post(
f"{base_url}/v1/workflows/run",
json={
"inputs": inputs,
"response_mode": "blocking",
"user": f"recommend_{user_id}",
},
headers={"Authorization": f"Bearer {api_key}"},
timeout=120,
)
if resp.status_code != 200:
return {"code": 5001, "message": f"Workflow 调用失败: {resp.status_code}"}
result = resp.json()
# 解析 Workflow 输出
if result.get("status") == "succeeded":
outputs = result.get("data", {}).get("outputs", {})
return {"code": 0, "data": outputs}
else:
error = result.get("error", "未知错误")
return {"code": 5001, "message": f"Workflow 执行失败: {error}"}
except requests.Timeout:
return {"code": 5001, "message": "Workflow 调用超时"}
except Exception as e:
return {"code": 9999, "message": f"Workflow 调用异常: {str(e)}"}
```
---
### 2.2 产品推荐 API 完善
**目标**:完善 generate/status/export/share API
**文件**`api/insurance/recommend/routes.py`
#### 当前代码
```python
@recommend_bp.route("/generate", methods=["POST"])
@jwt_required
def generate():
"""生成推荐方案。"""
data = request.get_json()
if not data:
return jsonify({"code": 1001, "message": "请求体不能为空", "data": None}), 400
user_id = request.user_id
# 参数校验
errors = []
# ... 校验逻辑
if errors:
return jsonify({"code": 1001, "message": "; ".join(errors), "data": None}), 400
# 创建推荐记录
from insurance.recommend.service import RecommendService
service = RecommendService()
result = service.create_proposal(user_id, data)
if result["code"] != 0:
return jsonify(result), 400
task_id = result["data"]["task_id"]
# 异步调用 Workflow
import threading
threading.Thread(
target=self._run_workflow_async,
args=(task_id, data, user_id),
daemon=True,
).start()
return jsonify({"code": 0, "data": {"task_id": task_id, "status": "processing"}})
```
#### 需要实现
1. `_run_workflow_async` 方法
2. `get_status` API
3. `export` API
4. `share` API
```python
def _run_workflow_async(self, task_id: str, data: dict, user_id: str):
"""异步执行 Workflow。"""
from insurance.recommend.service import RecommendService
from insurance.recommend.workflow_helper import WorkflowHelper
service = RecommendService()
workflow = WorkflowHelper()
# 调用 Workflow
result = workflow.run_workflow(data, user_id)
if result["code"] == 0:
service.update_proposal(task_id, "done", result["data"])
else:
service.update_proposal(task_id, "failed", error_message=result["message"])
@recommend_bp.route("/generate/<task_id>", methods=["GET"])
@jwt_required
def get_status(task_id):
"""查询推荐任务状态。"""
from insurance.recommend.service import RecommendService
service = RecommendService()
result = service.get_proposal_status(task_id)
return jsonify(result)
@recommend_bp.route("/<proposal_id>/export", methods=["POST"])
@jwt_required
def export_proposal(proposal_id):
"""导出推荐方案。"""
data = request.get_json()
format_type = data.get("format", "pdf")
from insurance.recommend.service import RecommendService
service = RecommendService()
result = service.export_proposal(proposal_id, format_type)
if result["code"] != 0:
return jsonify(result), 400
return jsonify(result)
@recommend_bp.route("/<proposal_id>/share", methods=["POST"])
@jwt_required
def share_proposal(proposal_id):
"""生成分享链接。"""
from insurance.recommend.service import RecommendService
service = RecommendService()
result = service.create_share_link(proposal_id)
return jsonify(result)
```
---
### 2.3 批量导入用户
**目标**:支持 Excel 批量导入用户
**文件**`api/insurance/admin/routes.py`
#### 添加路由
```python
@bp.route("/users/batch-import", methods=["POST"])
@super_admin_required
def batch_import_users():
"""批量导入用户。"""
file = request.files.get("file")
if not file:
return jsonify({"code": 1001, "message": "请选择文件", "data": None}), 400
# 解析 Excel
import openpyxl
wb = openpyxl.load_workbook(file)
ws = wb.active
users = []
for row in ws.iter_rows(min_row=2, values_only=True):
username, department, role = row[0], row[1], row[2]
if username:
users.append({
"username": username,
"department": department or "",
"role": role or "sales",
})
# 批量插入
from insurance.admin.service import AdminService
service = AdminService()
result = service.batch_create_users(users)
return jsonify(result)
```
#### 实现 service 方法
```python
def batch_create_users(self, users: list) -> dict:
"""批量创建用户。"""
from insurance.models.wecom_user import WeComUserMapping
from insurance.db.compat import db
import bcrypt
import uuid
created = 0
for user_data in users:
username = user_data.get("username", "")
if not username:
continue
# 检查用户是否已存在
existing = db.session.query(WeComUserMapping).filter_by(username=username).first()
if existing:
continue
# 生成默认密码
default_password = "12345678"
password_hash = bcrypt.hashpw(default_password.encode(), bcrypt.gensalt()).decode()
# 创建用户
mapping = WeComUserMapping(
wecom_userid=f"batch_{uuid.uuid4().hex[:8]}",
internal_user_id=f"user_{uuid.uuid4().hex[:8]}",
username=username,
password_hash=password_hash,
department=user_data.get("department", ""),
role=user_data.get("role", "sales"),
status="active",
)
db.session.add(mapping)
created += 1
db.session.commit()
return {"code": 0, "message": f"成功导入 {created} 个用户", "data": {"created": created}}
```
---
### 2.4 数据权限过滤
**目标**:实现销售只看自己的数据,主管看本组数据
**文件**`api/insurance/middleware/auth_middleware.py`
#### 当前代码
```python
def get_data_scope():
"""获取当前用户的数据权限范围。"""
role = getattr(request, "user_role", "")
user_id = getattr(request, "user_id", "")
# 超级管理员和管理员:查看所有数据
if role in ("super_admin", "admin"):
return {"scope": "all", "department": None, "user_id": None}
# 销售主管:查看本部门数据
if role == "manager":
department = getattr(request, "user_department", None)
return {"scope": "team", "department": department, "user_id": None}
# 销售人员和其他角色:只看自己的数据
return {"scope": "self", "department": None, "user_id": user_id}
```
#### 在查询接口中使用
```python
def get_proposals(user_id, params):
"""获取推荐记录列表(带数据权限)。"""
from insurance.middleware.auth_middleware import get_data_scope
scope = get_data_scope()
query = db.session.query(RecommendationRecord)
if scope["scope"] == "self":
# 销售:只看自己的
query = query.filter(RecommendationRecord.user_id == user_id)
elif scope["scope"] == "team":
# 主管:看本部门的
query = query.filter(
RecommendationRecord.user_id.in_(
db.session.query(WeComUserMapping.id).filter(
WeComUserMapping.department == scope["department"]
)
)
)
# admin/super_admin看全部
return query.all()
```
---
### 2.5 文档编号自动生成
**目标**:文档上传时自动生成编号
**文件**`api/insurance/kb/service.py`
#### 添加方法
```python
def generate_doc_number(self, insurance_type: str, company: str) -> str:
"""生成文档编号。"""
from insurance.db.compat import db
from sqlalchemy import text
type_codes = {
"重疾险": "CJ", "寿险": "RS", "医疗险": "YL",
"意外险": "YW", "车险": "CX", "年金险": "NJ", "储蓄险": "CX",
}
type_code = type_codes.get(insurance_type, "XX")
company_code = company[:2] if company else "XX"
# 查询当前序号
result = db.session.execute(
text("""
SELECT COUNT(*) FROM document_metadata
WHERE insurance_type = :type AND company = :company
"""),
{"type": insurance_type, "company": company}
)
count = result.scalar() or 0
return f"{type_code}-{company_code}-{count + 1:03d}"
```
---
## 三、API 接口清单
### 3.1 产品推荐 API
| 方法 | 路径 | 说明 | 状态 |
|------|------|------|:----:|
| POST | /recommend/generate | 生成推荐方案 | ✅ 已实现 |
| GET | /recommend/generate/{task_id} | 查询任务状态 | ✅ 已实现 |
| POST | /recommend/{id}/export | 导出方案 | ✅ 已实现 |
| POST | /recommend/{id}/share | 生成分享链接 | ✅ 已实现 |
| GET | /recommend/list | 推荐历史列表 | ✅ 已实现 |
| DELETE | /recommend/{id} | 删除推荐记录 | ✅ 已实现 |
### 3.2 用户管理 API
| 方法 | 路径 | 说明 | 状态 |
|------|------|------|:----:|
| GET | /admin/users | 用户列表 | ✅ 已实现 |
| POST | /admin/users | 新增用户 | ✅ 已实现 |
| PUT | /admin/users/{id} | 编辑用户 | ✅ 已实现 |
| DELETE | /admin/users/{id} | 停用用户 | ✅ 已实现 |
| POST | /admin/users/batch-import | 批量导入 | ⚠️ 待实现 |
| POST | /admin/users/sync-to-dify | 同步到 Dify | ✅ 已实现 |
### 3.3 知识库 API
| 方法 | 路径 | 说明 | 状态 |
|------|------|------|:----:|
| GET | /kb/documents | 文档列表 | ✅ 已实现 |
| POST | /kb/documents/upload | 上传文档 | ✅ 已实现 |
| GET | /kb/documents/{id}/status | 文档状态 | ✅ 已实现 |
| PATCH | /kb/documents/{id} | 更新元数据 | ✅ 已实现 |
| DELETE | /kb/documents/{id} | 删除文档 | ✅ 已实现 |
| POST | /kb/documents/{id}/retry | 重试文档 | ✅ 已实现 |
---
## 四、数据库变更
### 4.1 添加分享字段
```sql
-- 添加分享相关字段
ALTER TABLE recommendation_records
ADD COLUMN IF NOT EXISTS share_token VARCHAR(64),
ADD COLUMN IF NOT EXISTS share_expire_at TIMESTAMP;
```
### 4.2 添加文档元数据表
```sql
-- 文档元数据(编号、标签、险种、保司)
CREATE TABLE IF NOT EXISTS document_metadata (
id SERIAL PRIMARY KEY,
baodan_document_id VARCHAR(64) NOT NULL,
dataset_id VARCHAR(64) NOT NULL,
doc_number VARCHAR(32),
insurance_type VARCHAR(32),
company VARCHAR(64),
tags JSONB DEFAULT '[]',
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP DEFAULT NOW()
);
```
---
## 五、配置变更
### 5.1 docker-compose.dify.yml
```yaml
# 更新 Workflow API Key
BAODAN_WORKFLOW_API_KEY: "app-real-workflow-api-key"
```
### 5.2 环境变量
| 变量 | 说明 | 默认值 |
|------|------|--------|
| BAODAN_API_URL | BaoDan API 地址 | http://baodan-api:5001 |
| BAODAN_WORKFLOW_API_KEY | Workflow API Key | app-placeholder |
| BAODAN_CHAT_API_KEY | 对话 API Key | app-placeholder |
| JWT_SECRET | JWT 密钥 | change-this |
| GUEST_MODE | 访客模式 | true |
---
## 六、测试清单
| # | 接口 | 测试用例 | 验证标准 |
|---|------|----------|----------|
| 1 | POST /recommend/generate | 正常请求 | 返回 task_id |
| 2 | POST /recommend/generate | 缺少参数 | 返回 1001 |
| 3 | GET /recommend/generate/{id} | 查询状态 | 返回 status |
| 4 | POST /recommend/{id}/export | 导出 PDF | 返回 download_url |
| 5 | POST /recommend/{id}/share | 分享链接 | 返回 share_url |
| 6 | POST /admin/users/batch-import | 上传 Excel | 用户增加 |
| 7 | POST /admin/users/batch-import | 空文件 | 返回 1001 |
| 8 | GET /admin/users | 权限过滤 | 销售只看自己 |
---
## 七、部署说明
### 7.1 重新构建镜像
```bash
# 后端
cd D:\work\code\python\coding\baodanagent
docker compose -f docker-compose.dify.yml build baodan-api
# 重启服务
docker compose -f docker-compose.dify.yml up -d baodan-api
```
### 7.2 数据库迁移
```bash
# 执行 SQL 变更
docker exec -i baodanagent-db-1 psql -U postgres -d baodan < deploy/sql/migration.sql
```
### 7.3 验证部署
```bash
# 检查服务状态
docker compose -f docker-compose.dify.yml ps
# 测试 API
curl http://localhost:5001/health
```