更新内容: 前后端架构说明.md: - 更新项目状态说明,反映 martial-web 已存在 - 添加开发环境和生产环境架构对比图 - 添加详细的请求流程示例 - 更新访问地址为域名(生产环境) - 更新开发方式说明,包含本地全栈开发 - 完善环境对比表,包含开发和生产地址 - 强调 martial-web 项目而非商业版 Saber 开发指南.md: - 更新 SQL 脚本路径:doc/sql/ → database/ 总体改进: - 所有生产环境地址使用域名替代 IP:端口 - 反映当前项目的实际状态(前后端都已部署) - 提供开发和生产两种环境的清晰对比 - 帮助开发者快速理解完整的系统架构 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
30 KiB
30 KiB
BladeX 项目开发指南
本指南提供在 BladeX 框架下高效开发的最佳实践
目录
一、快速开始
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:创建数据库表
-- database/martial-db/新增表.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:测试
- 启动应用(F5)
- 访问 http://localhost:8123/doc.html
- 找到"裁判等级管理"模块
- 测试增删改查接口
三、代码规范
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 = "裁判等级") // Swagger,API文档描述
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 关联查询
// 方式1:Mapper 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 调试
- 访问:http://localhost:8123/doc.html
- 找到对应的接口
- 点击"调试"
- 填写参数
- 点击"发送"查看响应
七、常见问题
7.1 实体类继承选择
// ❓ 我应该继承哪个基类?
// 选择1:TenantEntity(推荐,支持多租户)
public class JudgeLevel extends TenantEntity {
// 包含字段:
// - id, createUser, createDept, createTime
// - updateUser, updateTime, status, isDeleted
// - tenantId(租户ID)
}
// 选择2:BaseEntity(不需要多租户)
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) // 排序
十、总结
核心要点
-
专注业务开发
- 主要工作在
modules/martial/ - 不需要关心框架其他模块
- 主要工作在
-
遵循现有规范
- 模仿
modules/system/的写法 - 保持代码风格一致
- 模仿
-
利用框架能力
- 继承 BaseMapper、IService 获得基础CRUD
- 使用 BladeX 提供的工具类
- 遵循统一的返回格式
-
理解不完美
- 架构有缺陷,但可以正常工作
- 接受现状,不要试图大规模重构
学习路径
- 第一周:熟悉现有代码,理解框架规范
- 第二周:完成一个简单的 CRUD 功能
- 第三周:尝试复杂业务逻辑和关联查询
- 第四周:掌握调试技巧和常见问题解决
相关文档:
- 架构说明.md - 理解 BladeX 架构设计
- CLAUDE.md - 项目整体说明
- VS Code 调试指南 - 调试配置