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

16 KiB
Raw Blame History

保险智能客服系统 — 后端开发文档

文档版本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

当前代码

"""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 输出
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

当前代码

@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
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

添加路由

@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 方法

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

当前代码

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}

在查询接口中使用

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

添加方法

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 添加分享字段

-- 添加分享相关字段
ALTER TABLE recommendation_records
ADD COLUMN IF NOT EXISTS share_token VARCHAR(64),
ADD COLUMN IF NOT EXISTS share_expire_at TIMESTAMP;

4.2 添加文档元数据表

-- 文档元数据(编号、标签、险种、保司)
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

# 更新 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 重新构建镜像

# 后端
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 数据库迁移

# 执行 SQL 变更
docker exec -i baodanagent-db-1 psql -U postgres -d baodan < deploy/sql/migration.sql

7.3 验证部署

# 检查服务状态
docker compose -f docker-compose.dify.yml ps

# 测试 API
curl http://localhost:5001/health