568 lines
16 KiB
Markdown
568 lines
16 KiB
Markdown
# 保险智能客服系统 — 后端开发文档
|
||
|
||
> **文档版本**: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
|
||
```
|