# API 开发最佳实践总结 **基于场地预约管理系统开发实践** ## 📖 概述 本文档总结了场地预约管理系统的完整 API 开发实践,形成了一套标准化的 API 开发流程和规范,为后续 API 模块开发提供参考。 **实践基础**: - 项目:校园管理系统 (hzdyjs_xg) - 模块:场地预约管理系统 - 开发时间:2025-07-01 - 技术栈:Spring Boot 3.4.6 + MyBatis-Plus + Sa-Token ## 🎯 开发成果总览 ### 完整交付物 | 类型 | 数量 | 文件/功能 | 质量标准 | | ---------- | --------- | ------------------ | ------------ | | 需求文档 | 2 个 | 业务需求+技术架构 | 完整详细 | | 数据库脚本 | 3 个 | 表结构+字典+权限 | 规范标准 | | 后端代码 | 18 个文件 | 完整 ruoyi 模块 | 符合架构规范 | | API 接口 | 16 个 | 标准 CRUD+业务接口 | 功能完整 | | API 文档 | 1 个 | 400+行详细文档 | 准确规范 | | 测试脚本 | 1 个 | 450+行自动化测试 | 覆盖完整 | | 变更记录 | 1 个 | 详细开发过程记录 | 追溯完整 | ### 开发时长统计 | 阶段 | 时间投入 | 主要工作 | | ---------- | ---------- | -------------------- | | 需求分析 | 0.5 天 | 需求整理+业务设计 | | 数据库设计 | 0.5 天 | 表结构+权限+示例数据 | | 后端开发 | 1.5 天 | 完整 ruoyi 模块开发 | | API 文档 | 0.5 天 | 详细 API 文档编写 | | 测试脚本 | 0.5 天 | 自动化测试脚本 | | **总计** | **3.5 天** | **完整 API 模块** | ## 🚀 标准开发流程 ### 阶段一:需求分析和文档创建(必须先行) #### 1. 业务需求文档 ``` docs/需求文档/[模块名]/ ├── [模块名]系统需求说明.md # 详细业务需求 └── README.md # 需求概览 ``` **内容要求**: - 项目概述和目标 - 详细业务流程分析 - 功能需求规格说明 - 数据模型设计 - 权限设计方案 - 验收标准定义 #### 2. 技术架构文档 ``` docs/业务模块/[模块名]/ └── README.md # 技术架构说明 ``` ### 阶段二:数据库设计(代码开发前置条件) #### 3. 数据库脚本创建 ``` docs/设计文档/database/ ├── XX-[模块名]-module.sql # 数据表结构 ├── XX-[模块名]-data.sql # 字典数据 └── XX-[模块名]-menu.sql # 菜单权限 ``` **标准内容**: - **数据表**:2 个表(主实体+业务实体),包含标准基础字段 - **字典数据**:2 个类型+6-9 条数据 - **菜单权限**:12 个权限节点(目录+菜单+按钮) - **示例数据**:5-8 条测试数据 #### 4. 数据库执行验证 - 在测试环境执行 SQL 脚本 - 验证所有表结构、字典、权限、示例数据 - 确保数据库初始化完成 ### 阶段三:后端模块开发 #### 5. ruoyi 模块标准结构 ``` ruoyi-[模块名]/ ├── pom.xml # 模块依赖配置 ├── src/main/java/org/dromara/[模块名]/ │ ├── domain/ # 实体类(3个类) │ │ ├── [Entity].java # 数据库实体 │ │ ├── bo/[EntityBo].java # 业务对象 │ │ └── vo/[EntityVo].java # 视图对象 │ ├── mapper/ # 数据访问层(2个类) │ │ └── [Entity]Mapper.java # Mapper接口 │ ├── service/ # 业务逻辑层(4个类) │ │ ├── I[Entity]Service.java # 服务接口 │ │ └── impl/[Entity]ServiceImpl.java # 服务实现 │ └── controller/ # 控制器层(2个类) │ └── [Entity]Controller.java # REST API控制器 └── src/main/resources/ └── mapper/[Entity]Mapper.xml # MyBatis映射文件(2个) ``` #### 6. 16 个标准 API 接口模式 **基础管理接口(6 个)**: - `GET /[module]/[entity]/list` - 分页查询列表 - `GET /[module]/[entity]/{id}` - 查询详情 - `POST /[module]/[entity]` - 新增 - `PUT /[module]/[entity]` - 修改 - `DELETE /[module]/[entity]/{ids}` - 删除 - `GET /[module]/[entity]/available` - 获取可用选项 **业务功能接口(4 个)**: - `GET /[module]/[business]/my` - 查询我的数据 - `GET /[module]/[business]/pending` - 查询待处理数据 - `GET /[module]/[business]/conflict` - 业务规则检查 - `POST /[module]/[business]/approve` - 审批操作 **辅助功能接口(6 个)**: - `POST /[module]/[business]/export` - 导出 Excel - `POST /[module]/[business]/import` - 导入 Excel - `GET /[module]/[business]/statistics` - 统计数据 - `POST /[module]/[business]/batch` - 批量操作 - `POST /[module]/[business]/{id}/enable` - 启用操作 - `POST /[module]/[business]/{id}/disable` - 禁用操作 #### 7. 模块集成配置 - `ruoyi-modules/pom.xml` - 添加子模块 - `ruoyi-admin/pom.xml` - 添加依赖 - `backend/pom.xml` - 版本管理配置 ### 阶段四:API 文档编写(代码完成后立即执行) #### 8. API 文档创建 ``` docs/开发文档/API/ ├── [模块名]管理API.md # 完整API文档(400+行) ├── API文档总览.md # 更新导航索引 └── API文档模板.md # 标准模板 ``` **标准文档结构**: - 📖 概述和核心功能 - 🏢 主实体管理 API(6 个接口) - 📅 业务实体管理 API(10 个接口) - 📋 数据字典 - 🔐 权限配置 - 📏 业务规则 - ❌ 错误码说明 ### 阶段五:自动化测试脚本开发 #### 9. Python 测试脚本 ``` test/ └── [模块名]API测试脚本.py # 自动化测试脚本(450+行) ``` **标准脚本结构**: - 强制 AES+RSA 双重加密登录 - 服务可用性检测 - 16 个 API 接口完整测试 - 业务规则验证测试 - 自动数据清理 ### 阶段六:变更记录和验证 #### 10. 变更记录文档 ``` docs/变更管理/ └── code-adjust-record-YYYY-MM-DD-{username}.md ``` #### 11. 完整功能验证 - 后端服务启动测试 - API 接口功能测试 - 文档准确性验证 - 权限控制测试 ## 🔧 技术实现要点 ### 数据库设计规范 #### 强制性基础字段 ```sql `id` bigint(20) NOT NULL COMMENT '主键ID', `tenant_id` varchar(20) NOT NULL DEFAULT '000000' COMMENT '租户编号', `create_dept` bigint(20) DEFAULT NULL COMMENT '创建部门', `create_by` bigint(20) DEFAULT NULL COMMENT '创建者', `create_time` datetime DEFAULT NULL COMMENT '创建时间', `update_by` bigint(20) DEFAULT NULL COMMENT '更新者', `update_time` datetime DEFAULT NULL COMMENT '更新时间', `deleted` tinyint(1) DEFAULT 0 COMMENT '删除标志(0代表存在 1代表删除)', ``` #### 业务字段设计原则 - 雪花 ID 作为主键 - 逻辑删除,禁止物理删除 - 多租户支持 - 完整的中文注释 - 合理的索引设计 ### 后端代码规范 #### 实体类设计 ```java // 继承TenantEntity,包含完整注释 @TableName("table_name") public class Entity extends TenantEntity { @TableId("id") private Long id; @TableField("field_name") private String fieldName; } ``` #### 业务对象设计 ```java // 添加验证注解 public class EntityBo { @NotBlank(message = "字段不能为空", groups = {AddGroup.class, EditGroup.class}) @Size(max = 100, message = "字段长度不能超过100个字符") private String fieldName; } ``` #### 视图对象设计 ```java // 支持Excel导出 public class EntityVo { @ExcelProperty(value = "字段名称") private String fieldName; } ``` #### 权限控制 ```java // 每个接口必须添加权限注解 @SaCheckPermission("module:entity:list") @GetMapping("/list") public TableDataInfo list(EntityBo bo, PageQuery pageQuery) { // 接口实现 } ``` #### 响应格式规范 ```java // 分页接口直接返回TableDataInfo @GetMapping("/list") public TableDataInfo list(...) { return entityService.queryPageList(bo, pageQuery); } // 非分页接口使用R类型 @GetMapping("/options") public R> options() { return R.ok(entityService.selectOptions()); } ``` ### API 测试脚本规范 #### 强制加密登录 ```python # 强制依赖检查,缺少加密库直接退出 try: from Crypto.Cipher import AES from Crypto.Util.Padding import pad from Crypto.PublicKey import RSA from Crypto.Cipher import PKCS1_v1_5 CRYPTO_AVAILABLE = True except ImportError: print("❌ 缺少必需的加密库!") exit(1) ``` #### 标准测试流程 ```python def run_all_tests(self): # 1. 检测服务可用性 if not self.get_server_info(): return False # 2. 强制加密登录 if not self.login(): return False try: # 3. 执行各类测试 self.test_entity_management() self.test_business_features() self.test_business_rules() finally: # 4. 清理测试数据 self.cleanup_test_data() ``` ## 📊 质量保证标准 ### 开发完成标准 **一个完整的 API 模块开发必须包含**: - ✅ **完整的需求和技术文档**(2-3 个文档) - ✅ **数据库脚本和权限配置**(3 个 SQL 文件) - ✅ **完整的后端模块代码**(15-20 个 Java 文件) - ✅ **详细的 API 接口文档**(400+行) - ✅ **自动化测试脚本**(450+行) - ✅ **完整的变更记录日志** - ✅ **模块成功集成和功能验证** ### 文档质量标准 - **完整性**:16 个标准接口的完整文档 - **准确性**:所有参数和响应与实际代码一致 - **详细性**:包含完整的业务规则和错误码说明 - **规范性**:使用统一的文档模板和格式 - **可测试性**:提供真实的请求响应示例 ### 代码质量标准 - **架构规范**:严格遵循 RuoYi-Vue-Plus 架构规范 - **完整功能**:涵盖管理和业务的全流程 - **权限控制**:实现接口权限和数据权限控制 - **业务规则**:包含完整的业务验证逻辑 - **可扩展性**:支持后续功能扩展和优化 ## 💡 最佳实践经验 ### 开发流程优化 1. **先文档后代码**:需求分析完成后再开始编码 2. **数据库优先**:表结构确定后再开发业务逻辑 3. **标准化接口**:16 个接口模式覆盖 90%的业务场景 4. **文档同步更新**:代码完成立即更新文档 5. **完整测试验证**:自动化测试确保质量 6. **规范化集成**:严格按照项目架构规范集成 ### 常见问题和解决方案 #### 1. 版本配置问题 **问题**:新模块版本号不一致导致编译失败 **解决**:在根 pom.xml 的 dependencyManagement 中配置版本管理 #### 2. Excel 注解错误 **问题**:使用了错误的 Excel 注解包 **解决**:使用`cn.idev.excel.annotation.ExcelProperty` #### 3. 登录认证问题 **问题**:API 测试脚本登录失败 **解决**:强制使用 AES+RSA 双重加密登录,确保加密库正确安装 #### 4. 文档不同步问题 **问题**:代码变更后文档未更新 **解决**:建立强制性文档同步更新规则,代码完成后立即更新文档 #### 5. MyBatis-Plus 实体字段映射问题 (2025-08-13 新增) **问题**:SQL 错误 "Unknown column 'parent_name' in 'field list'" **原因**:实体类中包含数据库表不存在的字段,MyBatis-Plus 自动生成 SQL 时包含了这些字段 **典型场景**:实体类中有用于显示的冗余字段(如 parentName),但数据库表中没有对应字段 **解决方案**: ```java /** * 父部门名称(冗余字段,用于显示,不持久化到数据库) */ @TableField(exist = false) private String parentName; ``` **关键要点**: - 添加 `@TableField(exist = false)` 注解告诉 MyBatis-Plus 该字段不存在于数据库表中 - 导入正确的注解包:`import com.baomidou.mybatisplus.annotation.TableField;` - 适用于所有用于显示但不持久化的冗余字段 - 避免在使用 `selectList()` 等 MyBatis-Plus 方法时出现字段不存在的错误 **错误示例**: ``` org.springframework.jdbc.BadSqlGrammarException: ### Error querying database. Cause: java.sql.SQLSyntaxErrorException: Unknown column 'parent_name' in 'field list' ### SQL: SELECT dept_id, parent_id, ancestors, dept_name, order_num, leader, phone, email, status, del_flag, parent_name, dept_category, create_dept, create_by, create_time, update_by, update_time FROM sys_dept WHERE... ``` ### 性能优化建议 1. **数据库查询优化**:合理使用索引,避免全表扫描 2. **分页查询优化**:使用 PageHelper 进行高效分页 3. **缓存策略**:对频繁查询的字典数据使用 Redis 缓存 4. **接口响应优化**:减少不必要的字段传输 ## 📁 文件下载最佳实践 (2025-08-02 新增) ### 核心问题解决 基于OSS文件下载功能的修复经验,总结文件下载相关的最佳实践。 #### 1. 响应头设置规范 **🔴 关键点:二进制文件下载不要设置charset** ```java // ❌ 错误方式 - 会导致下载失败 response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE + "; charset=UTF-8"); // ✅ 正确方式 - 纯二进制类型 response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE); // ✅ 必需的缓存控制头 response.setHeader("Cache-Control", "no-cache, no-store, must-revalidate"); response.setHeader("Pragma", "no-cache"); response.setHeader("Expires", "0"); ``` #### 2. Content-Length 正确设置 ```java // ❌ 可能失效的方法引用 storage.download(fileName, response.getOutputStream(), response::setContentLengthLong); // ✅ 确保安全的设置方式 storage.download(fileName, response.getOutputStream(), contentLength -> { if (contentLength != null && contentLength > 0) { response.setContentLengthLong(contentLength); } }); ``` #### 3. 输出流处理 ```java try { // 文件下载逻辑 storage.download(fileName, response.getOutputStream(), contentLengthConsumer); // 🔴 关键:必须手动刷新输出流 response.getOutputStream().flush(); } catch (Exception e) { throw new ServiceException("文件下载失败: " + e.getMessage()); } ``` #### 4. 完整的文件下载模板 ```java @GetMapping("/download/{fileId}") public void downloadFile(@PathVariable Long fileId, HttpServletResponse response) { try { // 1. 权限验证 if (!hasDownloadPermission(fileId)) { throw new ServiceException("无权限下载该文件"); } // 2. 获取文件信息 FileVo fileInfo = fileService.getById(fileId); if (fileInfo == null) { throw new ServiceException("文件不存在"); } // 3. 设置响应头 FileUtils.setAttachmentResponseHeader(response, fileInfo.getOriginalName()); response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE); response.setHeader("Cache-Control", "no-cache, no-store, must-revalidate"); response.setHeader("Pragma", "no-cache"); response.setHeader("Expires", "0"); // 4. 执行下载 OssClient storage = OssFactory.instance(fileInfo.getService()); storage.download(fileInfo.getFileName(), response.getOutputStream(), contentLength -> { if (contentLength != null && contentLength > 0) { response.setContentLengthLong(contentLength); } }); // 5. 刷新输出流 response.getOutputStream().flush(); } catch (Exception e) { log.error("文件下载失败: {}", e.getMessage(), e); throw new ServiceException("文件下载失败: " + e.getMessage()); } } ``` ### 常见问题排查 #### 问题1:文件传输完成但客户端未收到 - **原因**:Content-Type包含charset或缺少flush() - **解决**:移除charset,添加手动flush #### 问题2:下载的文件损坏 - **原因**:Content-Length设置错误 - **解决**:使用lambda表达式确保Content-Length正确设置 #### 问题3:浏览器缓存导致下载异常 - **原因**:缺少缓存控制头 - **解决**:设置no-cache相关响应头 ### 测试验证标准 1. **Postman测试**:能正确下载文件 2. **浏览器测试**:点击下载链接正常 3. **文件完整性**:下载文件与原文件MD5一致 4. **不同文件类型**:图片、文档、压缩包都能正常下载 5. **权限控制**:无权限用户被正确拒绝 ## 🔄 持续改进 ### 规范迭代 本最佳实践将根据后续项目开发经验持续优化: 1. **新技术集成**:随着技术栈升级调整开发规范 2. **流程优化**:根据开发效率反馈优化工作流程 3. **质量提升**:根据测试反馈完善质量标准 4. **工具改进**:开发更多自动化工具提升效率 ### 应用推广 **适用场景**: - 校园管理系统的其他业务模块 - 类似的管理信息系统 - Spring Boot + MyBatis-Plus 架构的项目 **推广方式**: - 作为新团队成员的培训材料 - 作为代码评审的质量标准 - 作为项目规划的参考模板 ## 📚 参考文档 - **后端开发规范**:`.cursor/cursorrules-backend.md` - **API 测试规范**:`.cursor/cursorrules-api-test.md` - **文档管理规范**:`.cursor/cursorrules-docs.md` - **实际案例参考**: - `docs/开发文档/API/场地预约管理API.md` - `test/场地预约API测试脚本.py` - `docs/变更管理/code-adjust-record-2025-07-01-wuzilong.md` --- **总结**:本最佳实践基于场地预约管理系统的成功开发经验,形成了一套完整的 API 开发标准流程,为后续 API 模块开发提供了可复制的标准模板和质量保证体系。 **维护人员**:开发团队 **最后更新**:2025-08-02 **文档版本**:v1.1 (新增文件下载最佳实践) ## 前端开发最佳实践 ### 🎯 组件开发规范 #### ImagePreview图片预览组件 - **认证图片加载**: 通过axios获取blob数据,确保认证信息传递 - **内存管理**: 使用`onBeforeUnmount`清理blob URLs,防止内存泄漏 - **缓存优化**: 实现blob URL缓存机制,提升性能 **核心实现**: ```javascript // 认证图片加载 const getImageBlobUrl = async (imageUrl: string): Promise => { const response = await request({ url: imageUrl, method: 'get', responseType: 'blob' }); return URL.createObjectURL(response.data); }; // 生命周期管理 onBeforeUnmount(() => { blobUrls.value.forEach(url => URL.revokeObjectURL(url)); }); ```