Files
hzdyjs/docs/开发文档/API开发最佳实践总结.md
2025-08-13 22:16:44 +08:00

19 KiB
Raw Permalink Blame History

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 接口功能测试
  • 文档准确性验证
  • 权限控制测试

🔧 技术实现要点

数据库设计规范

强制性基础字段

`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 作为主键
  • 逻辑删除,禁止物理删除
  • 多租户支持
  • 完整的中文注释
  • 合理的索引设计

后端代码规范

实体类设计

// 继承TenantEntity,包含完整注释
@TableName("table_name")
public class Entity extends TenantEntity {
    @TableId("id")
    private Long id;

    @TableField("field_name")
    private String fieldName;
}

业务对象设计

// 添加验证注解
public class EntityBo {
    @NotBlank(message = "字段不能为空", groups = {AddGroup.class, EditGroup.class})
    @Size(max = 100, message = "字段长度不能超过100个字符")
    private String fieldName;
}

视图对象设计

// 支持Excel导出
public class EntityVo {
    @ExcelProperty(value = "字段名称")
    private String fieldName;
}

权限控制

// 每个接口必须添加权限注解
@SaCheckPermission("module:entity:list")
@GetMapping("/list")
public TableDataInfo<EntityVo> list(EntityBo bo, PageQuery pageQuery) {
    // 接口实现
}

响应格式规范

// 分页接口直接返回TableDataInfo
@GetMapping("/list")
public TableDataInfo<EntityVo> list(...) {
    return entityService.queryPageList(bo, pageQuery);
}

// 非分页接口使用R类型
@GetMapping("/options")
public R<List<EntityVo>> options() {
    return R.ok(entityService.selectOptions());
}

API 测试脚本规范

强制加密登录

# 强制依赖检查,缺少加密库直接退出
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)

标准测试流程

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),但数据库表中没有对应字段

解决方案:

/**
 * 父部门名称(冗余字段,用于显示,不持久化到数据库)
 */
@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

// ❌ 错误方式 - 会导致下载失败
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 正确设置

// ❌ 可能失效的方法引用
storage.download(fileName, response.getOutputStream(), response::setContentLengthLong);

// ✅ 确保安全的设置方式
storage.download(fileName, response.getOutputStream(), contentLength -> {
    if (contentLength != null && contentLength > 0) {
        response.setContentLengthLong(contentLength);
    }
});

3. 输出流处理

try {
    // 文件下载逻辑
    storage.download(fileName, response.getOutputStream(), contentLengthConsumer);
    
    // 🔴 关键:必须手动刷新输出流
    response.getOutputStream().flush();
    
} catch (Exception e) {
    throw new ServiceException("文件下载失败: " + e.getMessage());
}

4. 完整的文件下载模板

@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缓存机制,提升性能

核心实现:

// 认证图片加载
const getImageBlobUrl = async (imageUrl: string): Promise<string> => {
  const response = await request({
    url: imageUrl,
    method: 'get',
    responseType: 'blob'
  });
  return URL.createObjectURL(response.data);
};

// 生命周期管理
onBeforeUnmount(() => {
  blobUrls.value.forEach(url => URL.revokeObjectURL(url));
});