Files
martial-master/docs/开发指南.md
宅房 f01c5c6a6a
Some checks failed
continuous-integration/drone/push Build is failing
Merge remote-tracking branch 'origin/main'
解决目录重组冲突:
- doc/ → docs/ (文档目录重命名)
- doc/sql/ → database/ (数据库脚本目录重组)
- doc/script/ → scripts/ (脚本目录重组)

保留本地新增的武术比赛系统文件:
- docs/sql/mysql/martial-*.sql (4个数据库脚本)
- docs/后端开发完成报告.md
- docs/数据库字段检查报告.md
- docs/问题修复报告.md

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-30 12:13:15 +08:00

1172 lines
30 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# BladeX 项目开发指南
> 本指南提供在 BladeX 框架下高效开发的最佳实践
## 目录
1. [快速开始](#一快速开始)
2. [开发流程](#二标准开发流程)
3. [代码规范](#三代码规范)
4. [常见场景](#四常见开发场景)
5. [最佳实践](#五最佳实践)
6. [调试技巧](#六调试技巧)
7. [常见问题](#七常见问题)
---
## 一、快速开始
### 1.1 环境准备
```bash
# 确保服务运行
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创建数据库表
```sql
-- 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
```java
// 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 接口
```java
// 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 文件:
```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 接口
```java
// 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 实现类
```java
// 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
```java
// 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("/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 命名规范
#### 类命名
```java
// 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
```
#### 方法命名
```java
// 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删除
```
#### 变量命名
```java
// 小驼峰
private String levelName; // ✅
private String level_name; // ❌
private String LevelName; // ❌
// 集合
List<JudgeLevel> judgeLevelList; // ✅ 复数或带List后缀
List<JudgeLevel> levels; // ✅
List<JudgeLevel> judgeLevel; // ❌ 单数
```
### 3.2 注解规范
#### Entity 注解
```java
@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 注解
```java
@RestController // REST控制器
@AllArgsConstructor // Lombok构造器注入
@RequestMapping("/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 简单查询
```java
// 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 复杂条件查询
```java
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 分页查询
```java
// 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 关联查询
```java
// 方式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 批量操作
```java
// 批量新增
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 事务处理
```java
@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 的使用
```java
// 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 层最佳实践
```java
@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 层最佳实践
```java
@RestController
@AllArgsConstructor
@RequestMapping("/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 异常处理
```java
// 使用框架提供的 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 缓存使用
```java
// 使用 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 调试
```java
// 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 日志调试
```java
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 调试
```yaml
# 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 实体类继承选择
```java
// ❓ 我应该继承哪个基类?
// 选择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 没有生效
```java
// ❌ 问题:注入 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 查询返回空
```java
// ❌ 问题:查询总是返回 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 删除不生效
```java
// ❌ 问题:删除后数据还在
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 时间字段自动填充
```java
// ✅ 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 分页查询无效
```java
// ❌ 问题:分页不生效,返回全部数据
@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<T>
- [ ] 添加了必要的日志
- [ ] 处理了异常情况
- [ ] 代码格式化
---
## 九、快速参考
### 9.1 常用 MyBatis-Plus 方法
```java
// 查询
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 常用工具类
```java
// 字符串工具
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 常用注解速查
```java
// 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. **第四周**:掌握调试技巧和常见问题解决
---
**相关文档**
- [架构说明.md](./架构说明.md) - 理解 BladeX 架构设计
- [CLAUDE.md](../CLAUDE.md) - 项目整体说明
- [VS Code 调试指南](../.vscode/DEBUG_GUIDE.md) - 调试配置