# 步骤四:后端 API 实现 > 前置条件:step-03 已完成。 > 实施依据:`docs/crm-migration/plans/{module}/06-steps.md` 中的后端实现步骤。 > 同步参考:[ref-database-rules.md](ref-database-rules.md)、[ref-lessons-learned.md](ref-lessons-learned.md) > 模块方案:`docs/crm-migration/plans/{module}/04-api-spec.md`(API规格)、`docs/crm-migration/plans/{module}/02-database.md`(数据库) --- ## 核心原则 **先理解 Java 业务逻辑,再写 Python 代码。** 每个接口先读 Java 源码(Controller → Service → Mapper),用中文写出业务逻辑说明,再编码。 ### Java 快照(按需,SHALL) **写到哪一步,快照哪一步的 Java**——不在方案阶段一次性拷贝整个模块。 1. 打开 `plans/{module}/06-steps.md`,找到当前步骤的「Sub-step 0 快照」清单 2. 执行 `docs/crm-migration/scripts/snapshot-java.sh {module} ` 3. 只加载快照目录中**本步文件** + `05-special.md`,再写 Python 权威源:`../shanxia/crm/backend/ruoyi-modules/ruoyi-crm/`。快照目录:`docs/crm-migration/reference/java/`。 ## 操作流程 ### 1. 数据库先行 按 [ref-database-rules.md](ref-database-rules.md) 规范: 1. 编写迁移后 DDL(业务字段与 sx.sql 一致,公共字段按适配规则调整) 2. 编写 SQL 增量脚本保存到 `docs/sql/` 3. 更新 SQLAlchemy Model(`backend/app/models/crm_models.py`) 4. 导入字典数据(如需新建,从 sx.sql 筛选 `crm_%` 字典) ### 2. Schema 层 创建 `backend/app/schemas/crm/{module}.py`: - CreateBody、UpdateBody、ListBody 等 Pydantic v2 模型 - 使用 `Field(..., alias="camelCase")` 实现命名转换 - 参考 `backend/app/schemas/crm/product.py` ### 3. CRUD 层 创建 `backend/app/crud/crm/{module}.py`: - `_row_to_dict`:ORM → camelCase dict(所有可读字段的**单点真相**) - `list_xxx`:分页查询(租户隔离 + 软删除过滤 + create_time DESC) - `create_xxx`:新增(UUID主键 + 编号生成 + 唯一校验 + 冗余字段写入) - `update_xxx`:更新 - `soft_delete_xxx`:软删除(del_flag=1) - 参考 `backend/app/crud/crm/product.py` ### 4. API 路由层 在 `backend/app/api/crm.py` 中追加路由: - 每个接口一个路由函数 - `Depends(require_admin_or_super_admin)` 认证 - 路由路径与前端 API 调用一致 ### 5. API 路径约定 | 操作 | 路径格式 | 示例 | |------|---------|------| | 列表 | `/web/crm/{module}/list` | POST,含 page/pageSize | | 新增 | `/web/crm/{module}/add` | POST | | 编辑 | `/web/crm/{module}/update` | POST | | 删除 | `/web/crm/{module}/delete` | POST | | 详情 | `/web/crm/{module}/detail` | POST | | 下拉 | `/web/crm/{module}/options` | POST | | 导出 | `/web/crm/{module}/export` | POST,返回 xlsx 流(非 JSON) | | 导入模板 | `/web/crm/{module}/importTemplate` 或 `/import-template` | POST,返回 xlsx 流 | ### 7. Excel 导出(CRM 集中字典翻译) 对齐 Java `ExcelUtil.exportExcel` + `*Vo` 的 `@ExcelDictFormat`: 1. **列定义**:在 `backend/app/utils/crm_export_columns.py` 增加 `XXX_EXPORT_COLUMNS`(`ExportColumn(title, field, dict_type=...)`)。 2. **字典对照**:打开原 CRM `domain/vo/CrmXxxVo.java`,逐字段核对 `dictType`(如线索 `crm_clue_source`、`crm_clue_level`)。 3. **路由实现**:查询列表数据后 **只调用** `export_rows_to_xlsx_response(db, tenant_no=..., columns=..., rows=..., filename_stem=...)`。 4. **禁止**:在 `crm.py` 内重复 openpyxl 表头/循环;禁止导出原始 dict code(除非该列 Java 也无 `@ExcelDictFormat`)。 5. **依赖**:`openpyxl` 须在运行 venv 中安装(见 `ref-lessons-learned.md` §30)。 参考实现:`backend/app/utils/excel_export.py`;已接入:线索/客户/产品/联系人/跟进/商机。 ### 8. 定时任务 & 特殊场景 - 检查原 CRM 是否有定时任务,在 `backend/app/tasks/` 中用 APScheduler 实现 - 在 API 路由中处理触发器逻辑(如统计字段同步更新) ## 常见业务场景处理 | 场景 | 处理方式 | |------|---------| | 编号自动生成 | CRUD 层 create_xxx 中实现,参考 customer.py | | 唯一性校验 | CRUD 层校验,`del_flag=0` 排除软删除 | | 关联统计更新 | 新增/删除关联记录时同步更新主表 | | 冗余字段写入 | create/update 时从主表获取写入 | | 租户隔离 | 所有查询 WHERE 加 `tenant_no` | | 数据权限 | 列表查询根据角色数据范围过滤 | ## 字段一致性 - `_row_to_dict` 是所有可读字段的**单点真相** - CreateBody/UpdateBody 的字段必须与 `_row_to_dict` 中可写字段一一对应 - 前端 `formData` 初始化必须包含 CreateBody 中的所有字段 - 可用 `python scripts/check_field_consistency.py` 验证 ## 完成标准 - [ ] DDL 已编写并保存到 docs/sql/ - [ ] Model、Schema、CRUD、API 路由全部实现 - [ ] 每个接口的业务逻辑与 Java 源码一致 - [ ] 字段一致性检查通过 - [ ] 若有导出:已在 `crm_export_columns.py` 声明列与 `dict_type`,并走 `export_rows_to_xlsx_response` ## 下一步 完成后加载 [step-05-verify.md](step-05-verify.md)。