Files
martial-master/docs/开发指南.md
n72595987@gmail.com 1c96ef4f6f
All checks were successful
continuous-integration/drone/push Build is passing
refactor: 重组项目目录结构
将 doc/ 目录重组为更标准的结构:

目录变更:
- doc/ → docs/ (文档目录,只包含 .md 文件)
- doc/sql/ → database/ (数据库脚本目录)
  - database/bladex/ (BladeX 框架数据库)
  - database/flowable/ (Flowable 工作流数据库)
  - database/martial-db/ (武术系统业务数据库)
  - database/upgrade/ (数据库升级脚本)
- doc/script/ → scripts/ (部署和运维脚本)
  - scripts/docker/ (Docker 部署脚本)
  - scripts/fatjar/ (Fat JAR 启动脚本)

优势:
- 符合标准项目结构规范
- 文档、数据库、脚本分离更清晰
- 便于维护和查找

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-30 10:53:50 +08:00

30 KiB
Raw Blame History

BladeX 项目开发指南

本指南提供在 BladeX 框架下高效开发的最佳实践

目录

  1. 快速开始
  2. 开发流程
  3. 代码规范
  4. 常见场景
  5. 最佳实践
  6. 调试技巧
  7. 常见问题

一、快速开始

1.1 环境准备

# 确保服务运行
docker ps | grep -E "(dev-mysql|dev-redis)"

# 在 VS Code 中按 F5 启动调试
# 或在终端运行:
mvn spring-boot:run

1.2 访问应用

API 文档http://localhost:8123/doc.html
Druid 监控http://localhost:8123/druid
健康检查http://localhost:8123/actuator/health

1.3 核心开发目录

99% 的开发工作在这里:

src/main/java/org/springblade/modules/martial/
├── controller/     # API 接口
├── service/        # 业务逻辑
├── mapper/         # 数据访问
└── pojo/
    ├── entity/     # 实体类
    ├── dto/        # 数据传输对象
    └── vo/         # 视图对象

二、标准开发流程

2.1 新增功能完整流程

以"添加裁判等级管理"为例:

步骤 1创建数据库表

-- doc/sql/mysql/martial-competition-tables.sql
CREATE TABLE mt_judge_level (
    id BIGINT PRIMARY KEY,
    level_name VARCHAR(50) NOT NULL COMMENT '等级名称',
    level_code VARCHAR(20) NOT NULL COMMENT '等级代码',
    description VARCHAR(500) COMMENT '描述',

    -- BladeX 基础字段(必须)
    create_user BIGINT COMMENT '创建人',
    create_dept BIGINT COMMENT '创建部门',
    create_time DATETIME COMMENT '创建时间',
    update_user BIGINT COMMENT '更新人',
    update_time DATETIME COMMENT '更新时间',
    status INT DEFAULT 1 COMMENT '状态',
    is_deleted INT DEFAULT 0 COMMENT '是否删除',

    -- 多租户字段(如果需要)
    tenant_id VARCHAR(12) COMMENT '租户ID'
) COMMENT '裁判等级表';

步骤 2创建实体类 Entity

// modules/martial/pojo/entity/JudgeLevel.java
package org.springblade.modules.martial.pojo.entity;

import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.springblade.core.tenant.mp.TenantEntity;

/**
 * 裁判等级实体类
 */
@Data
@TableName("mt_judge_level")
@EqualsAndHashCode(callSuper = true)
@Schema(description = "裁判等级")
public class JudgeLevel extends TenantEntity {

    private static final long serialVersionUID = 1L;

    @Schema(description = "等级名称")
    private String levelName;

    @Schema(description = "等级代码")
    private String levelCode;

    @Schema(description = "描述")
    private String description;
}

关键点

  • 继承 TenantEntity(包含 id、createTime 等基础字段)
  • 使用 @TableName 指定表名
  • 使用 @Schema 添加 Swagger 文档
  • 使用 Lombok @Data 减少代码

步骤 3创建 Mapper 接口

// modules/martial/mapper/JudgeLevelMapper.java
package org.springblade.modules.martial.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.springblade.modules.martial.pojo.entity.JudgeLevel;

/**
 * 裁判等级 Mapper
 */
public interface JudgeLevelMapper extends BaseMapper<JudgeLevel> {
    // 基础 CRUD 已由 BaseMapper 提供
    // 只需添加复杂查询
}

关键点

  • 继承 BaseMapper<JudgeLevel>
  • 自动获得增删改查方法
  • 复杂 SQL 可以在 XML 中编写

步骤 4创建 Mapper XML可选

如果有复杂查询,创建 XML 文件:

<!-- src/main/resources/org/springblade/modules/martial/mapper/JudgeLevelMapper.xml -->
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.springblade.modules.martial.mapper.JudgeLevelMapper">

    <!-- 复杂查询示例 -->
    <select id="selectLevelWithCount" resultType="map">
        SELECT
            jl.*,
            COUNT(j.id) as judge_count
        FROM mt_judge_level jl
        LEFT JOIN mt_judge j ON j.level_id = jl.id
        GROUP BY jl.id
    </select>

</mapper>

步骤 5创建 Service 接口

// modules/martial/service/IJudgeLevelService.java
package org.springblade.modules.martial.service;

import com.baomidou.mybatisplus.extension.service.IService;
import org.springblade.modules.martial.pojo.entity.JudgeLevel;

/**
 * 裁判等级服务接口
 */
public interface IJudgeLevelService extends IService<JudgeLevel> {
    // 基础 CRUD 已由 IService 提供

    /**
     * 根据等级代码查询
     */
    JudgeLevel getByLevelCode(String levelCode);
}

步骤 6创建 Service 实现类

// modules/martial/service/impl/JudgeLevelServiceImpl.java
package org.springblade.modules.martial.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.AllArgsConstructor;
import org.springblade.modules.martial.mapper.JudgeLevelMapper;
import org.springblade.modules.martial.pojo.entity.JudgeLevel;
import org.springblade.modules.martial.service.IJudgeLevelService;
import org.springframework.stereotype.Service;

/**
 * 裁判等级服务实现
 */
@Service
@AllArgsConstructor
public class JudgeLevelServiceImpl
    extends ServiceImpl<JudgeLevelMapper, JudgeLevel>
    implements IJudgeLevelService {

    @Override
    public JudgeLevel getByLevelCode(String levelCode) {
        LambdaQueryWrapper<JudgeLevel> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(JudgeLevel::getLevelCode, levelCode);
        return this.getOne(wrapper);
    }
}

关键点

  • 继承 ServiceImpl<Mapper, Entity>
  • 实现自己的 Service 接口
  • 使用 @Service 注解
  • 使用 @AllArgsConstructor 自动注入

步骤 7创建 Controller

// modules/martial/controller/JudgeLevelController.java
package org.springblade.modules.martial.controller;

import com.baomidou.mybatisplus.core.metadata.IPage;
import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.AllArgsConstructor;
import org.springblade.core.boot.ctrl.BladeController;
import org.springblade.core.mp.support.Condition;
import org.springblade.core.mp.support.Query;
import org.springblade.core.tool.api.R;
import org.springblade.core.tool.utils.Func;
import org.springblade.modules.martial.pojo.entity.JudgeLevel;
import org.springblade.modules.martial.service.IJudgeLevelService;
import org.springframework.web.bind.annotation.*;

/**
 * 裁判等级控制器
 */
@RestController
@AllArgsConstructor
@RequestMapping("/api/martial/judge-level")
@Tag(name = "裁判等级管理", description = "裁判等级相关接口")
public class JudgeLevelController extends BladeController {

    private final IJudgeLevelService judgeLevelService;

    /**
     * 详情
     */
    @GetMapping("/detail")
    @ApiOperationSupport(order = 1)
    @Operation(summary = "详情", description = "传入id")
    public R<JudgeLevel> detail(@RequestParam Long id) {
        JudgeLevel detail = judgeLevelService.getById(id);
        return R.data(detail);
    }

    /**
     * 分页列表
     */
    @GetMapping("/list")
    @ApiOperationSupport(order = 2)
    @Operation(summary = "分页", description = "传入查询条件")
    public R<IPage<JudgeLevel>> list(JudgeLevel judgeLevel, Query query) {
        IPage<JudgeLevel> pages = judgeLevelService.page(
            Condition.getPage(query),
            Condition.getQueryWrapper(judgeLevel)
        );
        return R.data(pages);
    }

    /**
     * 新增或修改
     */
    @PostMapping("/submit")
    @ApiOperationSupport(order = 3)
    @Operation(summary = "新增或修改", description = "传入实体")
    public R submit(@RequestBody JudgeLevel judgeLevel) {
        return R.status(judgeLevelService.saveOrUpdate(judgeLevel));
    }

    /**
     * 删除
     */
    @PostMapping("/remove")
    @ApiOperationSupport(order = 4)
    @Operation(summary = "逻辑删除", description = "传入ids")
    public R remove(@RequestParam String ids) {
        return R.status(judgeLevelService.deleteLogic(Func.toLongList(ids)));
    }
}

关键点

  • 继承 BladeController
  • 使用 @RestController@RequestMapping
  • 统一返回 R<T> 包装类
  • 详细的 Swagger 注解

步骤 8测试

  1. 启动应用F5
  2. 访问 http://localhost:8123/doc.html
  3. 找到"裁判等级管理"模块
  4. 测试增删改查接口

三、代码规范

3.1 命名规范

类命名

// Entity实体类
JudgeLevel.java          // 名词,大驼峰

// DTO数据传输对象
JudgeLevelDTO.java       // 实体名 + DTO

// VO视图对象
JudgeLevelVO.java        // 实体名 + VO

// Mapper
JudgeLevelMapper.java    // 实体名 + Mapper

// Service
IJudgeLevelService.java          // I + 实体名 + Service
JudgeLevelServiceImpl.java       // 实体名 + ServiceImpl

// Controller
JudgeLevelController.java        // 实体名 + Controller

方法命名

// Service 层
JudgeLevel getById(Long id)                    // get + By + 条件
List<JudgeLevel> listByStatus(Integer status)  // list + By + 条件
boolean saveOrUpdate(JudgeLevel entity)        // save/update/delete
boolean deleteLogic(List<Long> ids)            // 逻辑删除

// Controller 层
R<JudgeLevel> detail(@RequestParam Long id)    // detail详情
R<IPage<JudgeLevel>> list(...)                 // list列表
R submit(@RequestBody JudgeLevel entity)       // submit提交
R remove(@RequestParam String ids)             // remove删除

变量命名

// 小驼峰
private String levelName;          // ✅
private String level_name;         // ❌
private String LevelName;          // ❌

// 集合
List<JudgeLevel> judgeLevelList;   // ✅ 复数或带List后缀
List<JudgeLevel> levels;           // ✅
List<JudgeLevel> judgeLevel;       // ❌ 单数

3.2 注解规范

Entity 注解

@Data                              // Lombok生成getter/setter
@TableName("mt_judge_level")       // MyBatis-Plus指定表名
@EqualsAndHashCode(callSuper = true)  // Lombok继承父类equals/hashCode
@Schema(description = "裁判等级")   // SwaggerAPI文档描述

public class JudgeLevel extends TenantEntity {

    @Schema(description = "等级名称")  // 字段描述
    private String levelName;

    @TableField(exist = false)     // 非数据库字段
    private Integer judgeCount;
}

Controller 注解

@RestController                    // REST控制器
@AllArgsConstructor                // Lombok构造器注入
@RequestMapping("/api/martial/judge-level")  // 请求路径
@Tag(name = "裁判等级管理", description = "裁判等级相关接口")  // Swagger分组

public class JudgeLevelController extends BladeController {

    @GetMapping("/detail")         // GET请求
    @ApiOperationSupport(order = 1)  // Swagger排序
    @Operation(summary = "详情", description = "传入id")  // 接口描述
    public R<JudgeLevel> detail(@RequestParam Long id) {
        ...
    }
}

3.3 包结构规范

modules/martial/
├── controller/
│   └── XxxController.java           # 一个实体一个Controller
│
├── service/
│   ├── IXxxService.java             # 接口
│   └── impl/
│       └── XxxServiceImpl.java      # 实现
│
├── mapper/
│   └── XxxMapper.java               # Mapper接口
│
└── pojo/
    ├── entity/
    │   └── Xxx.java                 # 实体类
    ├── dto/
    │   └── XxxDTO.java              # DTO可选
    └── vo/
        └── XxxVO.java               # VO可选

四、常见开发场景

4.1 简单查询

// Service
public JudgeLevel getByLevelCode(String levelCode) {
    LambdaQueryWrapper<JudgeLevel> wrapper = new LambdaQueryWrapper<>();
    wrapper.eq(JudgeLevel::getLevelCode, levelCode);
    return this.getOne(wrapper);
}

// 或使用 Condition更简洁
public JudgeLevel getByLevelCode(String levelCode) {
    JudgeLevel query = new JudgeLevel();
    query.setLevelCode(levelCode);
    return this.getOne(Condition.getQueryWrapper(query));
}

4.2 复杂条件查询

public List<JudgeLevel> search(String keyword, Integer status) {
    LambdaQueryWrapper<JudgeLevel> wrapper = new LambdaQueryWrapper<>();

    // 模糊查询
    wrapper.like(Func.isNotBlank(keyword), JudgeLevel::getLevelName, keyword);

    // 等值查询
    wrapper.eq(status != null, JudgeLevel::getStatus, status);

    // 排序
    wrapper.orderByDesc(JudgeLevel::getCreateTime);

    return this.list(wrapper);
}

4.3 分页查询

// Controller
@GetMapping("/list")
public R<IPage<JudgeLevel>> list(JudgeLevel judgeLevel, Query query) {
    IPage<JudgeLevel> pages = judgeLevelService.page(
        Condition.getPage(query),  // 分页参数current, size
        Condition.getQueryWrapper(judgeLevel)  // 查询条件
    );
    return R.data(pages);
}

// 请求示例:
// GET /api/martial/judge-level/list?current=1&size=10&levelName=国家级

4.4 关联查询

// 方式1Mapper XML
// JudgeLevelMapper.xml
<select id="selectWithJudgeCount" resultType="org.springblade.modules.martial.pojo.vo.JudgeLevelVO">
    SELECT
        jl.*,
        COUNT(j.id) as judgeCount
    FROM mt_judge_level jl
    LEFT JOIN mt_judge j ON j.level_id = jl.id
    WHERE jl.is_deleted = 0
    GROUP BY jl.id
</select>

// JudgeLevelMapper.java
List<JudgeLevelVO> selectWithJudgeCount();

// 方式2代码中关联
public JudgeLevelVO getDetailWithCount(Long id) {
    JudgeLevel level = this.getById(id);
    JudgeLevelVO vo = new JudgeLevelVO();
    BeanUtil.copy(level, vo);

    // 统计关联的裁判数量
    long count = judgeService.count(
        Wrappers.<Judge>lambdaQuery()
            .eq(Judge::getLevelId, id)
    );
    vo.setJudgeCount(count);

    return vo;
}

4.5 批量操作

// 批量新增
public boolean batchSave(List<JudgeLevel> list) {
    return this.saveBatch(list);
}

// 批量删除(逻辑删除)
public boolean batchDelete(List<Long> ids) {
    return this.deleteLogic(ids);
}

// 批量更新
public boolean batchUpdate(List<JudgeLevel> list) {
    return this.updateBatchById(list);
}

4.6 事务处理

@Service
public class JudgeLevelServiceImpl extends ServiceImpl<JudgeLevelMapper, JudgeLevel>
    implements IJudgeLevelService {

    @Autowired
    private IJudgeService judgeService;

    /**
     * 删除等级,同时更新关联的裁判
     */
    @Transactional(rollbackFor = Exception.class)
    public boolean deleteWithUpdate(Long levelId, Long newLevelId) {
        // 1. 更新关联的裁判到新等级
        judgeService.update(
            Wrappers.<Judge>lambdaUpdate()
                .eq(Judge::getLevelId, levelId)
                .set(Judge::getLevelId, newLevelId)
        );

        // 2. 删除等级
        return this.deleteLogic(Collections.singletonList(levelId));
    }
}

4.7 DTO 和 VO 的使用

// DTO接收前端数据
public class JudgeLevelDTO {
    private Long id;
    private String levelName;
    private String levelCode;
    // 可能包含额外的业务字段
    private List<Long> judgeIds;  // 批量关联裁判
}

// VO返回给前端
public class JudgeLevelVO extends JudgeLevel {
    private Integer judgeCount;    // 关联的裁判数量
    private String createUserName; // 创建人姓名
}

// Controller
@PostMapping("/submit")
public R submit(@RequestBody JudgeLevelDTO dto) {
    JudgeLevel entity = new JudgeLevel();
    BeanUtil.copy(dto, entity);

    // 处理额外逻辑
    if (Func.isNotEmpty(dto.getJudgeIds())) {
        // 批量关联裁判
    }

    return R.status(judgeLevelService.saveOrUpdate(entity));
}

@GetMapping("/detail-vo")
public R<JudgeLevelVO> detailWithVO(@RequestParam Long id) {
    JudgeLevelVO vo = judgeLevelService.getDetailVO(id);
    return R.data(vo);
}

五、最佳实践

5.1 Service 层最佳实践

@Service
@AllArgsConstructor
public class JudgeLevelServiceImpl extends ServiceImpl<JudgeLevelMapper, JudgeLevel>
    implements IJudgeLevelService {

    // ✅ 使用 @AllArgsConstructor 注入依赖
    // 避免 @Autowired

    /**
     * ✅ 方法命名清晰
     * ✅ 参数验证
     * ✅ 异常处理
     */
    @Override
    public JudgeLevel getByLevelCode(String levelCode) {
        // 参数验证
        if (Func.isBlank(levelCode)) {
            throw new ServiceException("等级代码不能为空");
        }

        LambdaQueryWrapper<JudgeLevel> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(JudgeLevel::getLevelCode, levelCode);

        JudgeLevel level = this.getOne(wrapper);
        if (level == null) {
            throw new ServiceException("等级不存在");
        }

        return level;
    }

    /**
     * ✅ 复杂业务逻辑在 Service 层
     * ✅ Controller 只负责接收和返回
     */
    @Transactional(rollbackFor = Exception.class)
    public boolean saveWithValidation(JudgeLevel judgeLevel) {
        // 1. 验证等级代码唯一性
        long count = this.count(
            Wrappers.<JudgeLevel>lambdaQuery()
                .eq(JudgeLevel::getLevelCode, judgeLevel.getLevelCode())
                .ne(judgeLevel.getId() != null, JudgeLevel::getId, judgeLevel.getId())
        );
        if (count > 0) {
            throw new ServiceException("等级代码已存在");
        }

        // 2. 保存
        return this.saveOrUpdate(judgeLevel);
    }
}

5.2 Controller 层最佳实践

@RestController
@AllArgsConstructor
@RequestMapping("/api/martial/judge-level")
@Tag(name = "裁判等级管理", description = "裁判等级相关接口")
public class JudgeLevelController extends BladeController {

    private final IJudgeLevelService judgeLevelService;

    /**
     * ✅ 统一返回 R<T>
     * ✅ 清晰的 Swagger 文档
     * ✅ 参数校验
     */
    @GetMapping("/detail")
    @ApiOperationSupport(order = 1)
    @Operation(summary = "详情", description = "传入id")
    public R<JudgeLevel> detail(@RequestParam Long id) {
        // Controller 只负责调用 Service不包含业务逻辑
        JudgeLevel detail = judgeLevelService.getById(id);
        return R.data(detail);
    }

    /**
     * ✅ 使用 @Valid 参数校验如果有DTO
     */
    @PostMapping("/submit")
    @ApiOperationSupport(order = 3)
    @Operation(summary = "新增或修改", description = "传入实体")
    public R submit(@Valid @RequestBody JudgeLevel judgeLevel) {
        // 复杂逻辑在 Service 层
        return R.status(judgeLevelService.saveWithValidation(judgeLevel));
    }

    /**
     * ✅ RESTful 风格
     * ❌ 避免:/add, /update, /delete
     * ✅ 使用:/submit, /remove 或 HTTP 方法区分
     */
}

5.3 异常处理

// 使用框架提供的 ServiceException
import org.springblade.core.tool.api.ServiceException;

// Service 层
public JudgeLevel getById(Long id) {
    JudgeLevel level = super.getById(id);
    if (level == null) {
        throw new ServiceException("裁判等级不存在");
    }
    return level;
}

// Controller 会自动捕获并返回错误信息:
// {
//   "code": 400,
//   "success": false,
//   "msg": "裁判等级不存在"
// }

5.4 缓存使用

// 使用 BladeX 提供的缓存工具
import org.springblade.common.cache.CacheNames;
import org.springblade.core.cache.utils.CacheUtil;

@Service
public class JudgeLevelServiceImpl extends ServiceImpl<JudgeLevelMapper, JudgeLevel>
    implements IJudgeLevelService {

    // 定义缓存名
    private static final String CACHE_KEY = "judge:level:";

    public JudgeLevel getByIdWithCache(Long id) {
        // 先查缓存
        String cacheKey = CACHE_KEY + id;
        JudgeLevel cached = CacheUtil.get(CacheNames.DICT_CACHE, cacheKey, JudgeLevel.class);
        if (cached != null) {
            return cached;
        }

        // 查数据库
        JudgeLevel level = this.getById(id);

        // 存缓存
        if (level != null) {
            CacheUtil.put(CacheNames.DICT_CACHE, cacheKey, level);
        }

        return level;
    }

    @Override
    public boolean saveOrUpdate(JudgeLevel entity) {
        boolean result = super.saveOrUpdate(entity);

        // 清除缓存
        if (result && entity.getId() != null) {
            CacheUtil.evict(CacheNames.DICT_CACHE, CACHE_KEY + entity.getId());
        }

        return result;
    }
}

六、调试技巧

6.1 VS Code 调试

// 1. 在代码中打断点
@GetMapping("/detail")
public R<JudgeLevel> detail(@RequestParam Long id) {
    JudgeLevel detail = judgeLevelService.getById(id);  // ← 点击行号打断点
    return R.data(detail);
}

// 2. 按 F5 启动调试模式
// 3. 访问 http://localhost:8123/api/martial/judge-level/detail?id=1
// 4. 程序会在断点处暂停
// 5. 使用调试工具栏:
//    F10 - 单步跳过
//    F11 - 单步进入
//    Shift+F11 - 单步跳出
//    F5 - 继续执行

6.2 日志调试

import lombok.extern.slf4j.Slf4j;

@Slf4j  // Lombok 自动生成 log 对象
@Service
public class JudgeLevelServiceImpl extends ServiceImpl<JudgeLevelMapper, JudgeLevel>
    implements IJudgeLevelService {

    public JudgeLevel getById(Long id) {
        log.info("查询裁判等级id: {}", id);

        JudgeLevel level = super.getById(id);

        log.debug("查询结果: {}", level);  // debug 级别

        if (level == null) {
            log.warn("裁判等级不存在id: {}", id);  // warn 级别
        }

        return level;
    }
}

// 日志级别配置application.yml
logging:
  level:
    org.springblade.modules.martial: DEBUG  # 设置 martial 模块为 DEBUG

6.3 SQL 调试

# application-dev.yml
mybatis-plus:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl  # 打印 SQL

# 控制台会输出:
# ==>  Preparing: SELECT * FROM mt_judge_level WHERE id = ?
# ==> Parameters: 1(Long)
# <==      Total: 1

6.4 使用 Knife4j 调试

  1. 访问:http://localhost:8123/doc.html
  2. 找到对应的接口
  3. 点击"调试"
  4. 填写参数
  5. 点击"发送"查看响应

七、常见问题

7.1 实体类继承选择

// ❓ 我应该继承哪个基类?

// 选择1TenantEntity推荐支持多租户
public class JudgeLevel extends TenantEntity {
    // 包含字段:
    // - id, createUser, createDept, createTime
    // - updateUser, updateTime, status, isDeleted
    // - tenantId租户ID
}

// 选择2BaseEntity不需要多租户
public class JudgeLevel extends BaseEntity {
    // 包含字段:
    // - id, createUser, createDept, createTime
    // - updateUser, updateTime, status, isDeleted
    // - 没有 tenantId
}

// 选择3不继承不推荐
public class JudgeLevel {
    private Long id;
    // 需要手动定义所有字段,失去框架支持
}

// ✅ 建议:统一继承 TenantEntity

7.2 Mapper 没有生效

// ❌ 问题:注入 Mapper 报错
@Autowired
private JudgeLevelMapper judgeLevelMapper;  // 找不到 Bean

// ✅ 解决方案1确保主类有 @MapperScan
@SpringBootApplication
@MapperScan("org.springblade.**.mapper.**")  // 扫描所有 mapper
public class Application {
    ...
}

// ✅ 解决方案2在 Mapper 上添加 @Mapper
@Mapper
public interface JudgeLevelMapper extends BaseMapper<JudgeLevel> {
    ...
}

7.3 查询返回空

// ❌ 问题:查询总是返回 null
JudgeLevel level = judgeLevelMapper.selectById(1L);
// level = null

// ✅ 检查项:
// 1. 表名是否正确
@TableName("mt_judge_level")  // 确保表名对应

// 2. 是否被逻辑删除
// BladeX 默认启用逻辑删除is_deleted = 1 的数据查不到
SELECT * FROM mt_judge_level WHERE id = 1;  // 检查 is_deleted 字段

// 3. 多租户隔离
// 如果启用多租户,只能查到当前租户的数据
// 检查 tenant_id 字段

7.4 删除不生效

// ❌ 问题:删除后数据还在
judgeLevelService.removeById(1L);
// 数据库中数据还在

// ✅ 原因BladeX 使用逻辑删除
// 实际执行的是UPDATE mt_judge_level SET is_deleted = 1 WHERE id = 1

// 如果需要物理删除:
@TableLogic(value = "0", delval = "1")  // 禁用逻辑删除
private Integer isDeleted;

// 或在 application.yml 配置
mybatis-plus:
  global-config:
    db-config:
      logic-delete-value: 1
      logic-not-delete-value: 0

7.5 时间字段自动填充

// ✅ BladeX 已自动配置时间字段填充
// 继承 BaseEntity 或 TenantEntity 即可

public class JudgeLevel extends TenantEntity {
    // createTime 和 updateTime 会自动填充
}

// 如果需要自定义字段自动填充:
public class MyMetaObjectHandler implements MetaObjectHandler {

    @Override
    public void insertFill(MetaObject metaObject) {
        this.setFieldValByName("createTime", new Date(), metaObject);
    }

    @Override
    public void updateFill(MetaObject metaObject) {
        this.setFieldValByName("updateTime", new Date(), metaObject);
    }
}

7.6 分页查询无效

// ❌ 问题:分页不生效,返回全部数据
@GetMapping("/list")
public R<IPage<JudgeLevel>> list(Query query) {
    IPage<JudgeLevel> pages = judgeLevelService.page(
        Condition.getPage(query)
    );
    return R.data(pages);
}

// ✅ 解决:确保前端传了分页参数
// GET /api/martial/judge-level/list?current=1&size=10

// ✅ 确保配置了分页插件BladeX 已配置)
@Configuration
public class MybatisPlusConfiguration {
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor());
        return interceptor;
    }
}

八、开发清单

8.1 新增功能检查清单

  • 创建数据库表(包含 BladeX 基础字段)
  • 创建 Entity继承 TenantEntity
  • 创建 Mapper 接口(继承 BaseMapper
  • 创建 Mapper XML如有复杂SQL
  • 创建 Service 接口(继承 IService
  • 创建 Service 实现(继承 ServiceImpl
  • 创建 Controller继承 BladeController
  • 添加 Swagger 注解(完善 API 文档)
  • 测试增删改查功能
  • 编写单元测试(可选)

8.2 代码审查清单

  • 类命名符合规范
  • 方法命名清晰
  • 使用了 Lombok 注解
  • 添加了 Swagger 文档
  • Service 层包含业务逻辑
  • Controller 层只负责接收和返回
  • 使用了统一的返回格式 R
  • 添加了必要的日志
  • 处理了异常情况
  • 代码格式化

九、快速参考

9.1 常用 MyBatis-Plus 方法

// 查询
T getById(Serializable id)
T getOne(Wrapper<T> queryWrapper)
List<T> list()
List<T> list(Wrapper<T> queryWrapper)
IPage<T> page(IPage<T> page, Wrapper<T> queryWrapper)
long count(Wrapper<T> queryWrapper)

// 新增
boolean save(T entity)
boolean saveBatch(Collection<T> entityList)

// 更新
boolean updateById(T entity)
boolean update(Wrapper<T> updateWrapper)
boolean updateBatchById(Collection<T> entityList)

// 删除
boolean removeById(Serializable id)
boolean removeByIds(Collection<? extends Serializable> idList)
boolean deleteLogic(Collection<? extends Serializable> idList)  // 逻辑删除

9.2 常用工具类

// 字符串工具
Func.isBlank(str)          // 判断空字符串
Func.isNotBlank(str)       // 判断非空
Func.toLong(obj)           // 转 Long
Func.toLongList(str)       // 字符串转 Long 列表(如:"1,2,3"

// Bean 拷贝
BeanUtil.copy(source, target)

// 缓存工具
CacheUtil.get(cacheName, key, type)
CacheUtil.put(cacheName, key, value)
CacheUtil.evict(cacheName, key)

// 当前用户
AuthUtil.getUserId()       // 当前用户ID
AuthUtil.getUserName()     // 当前用户名
AuthUtil.getTenantId()     // 当前租户ID

9.3 常用注解速查

// Lombok
@Data                      // getter/setter/toString/equals/hashCode
@AllArgsConstructor        // 全参构造器
@NoArgsConstructor         // 无参构造器
@Slf4j                     // 日志对象 log

// MyBatis-Plus
@TableName("表名")          // 指定表名
@TableField(exist = false) // 非数据库字段
@TableLogic                // 逻辑删除字段

// Spring
@Service                   // Service 层
@RestController            // Controller 层
@RequestMapping("/path")   // 请求路径
@GetMapping                // GET 请求
@PostMapping               // POST 请求
@RequestParam              // 请求参数
@RequestBody               // 请求体
@Transactional             // 事务

// Swagger
@Tag(name = "xxx")         // API 分组
@Operation(summary = "xxx") // 接口说明
@Schema(description = "xxx") // 字段说明
@ApiOperationSupport(order = 1) // 排序

十、总结

核心要点

  1. 专注业务开发

    • 主要工作在 modules/martial/
    • 不需要关心框架其他模块
  2. 遵循现有规范

    • 模仿 modules/system/ 的写法
    • 保持代码风格一致
  3. 利用框架能力

    • 继承 BaseMapper、IService 获得基础CRUD
    • 使用 BladeX 提供的工具类
    • 遵循统一的返回格式
  4. 理解不完美

    • 架构有缺陷,但可以正常工作
    • 接受现状,不要试图大规模重构

学习路径

  1. 第一周:熟悉现有代码,理解框架规范
  2. 第二周:完成一个简单的 CRUD 功能
  3. 第三周:尝试复杂业务逻辑和关联查询
  4. 第四周:掌握调试技巧和常见问题解决

相关文档