1. 修复Swagger配置,添加武术模块API分组 - 在SwaggerConfiguration中新增martialApi()方法 - 使武术模块的66个接口能在Knife4j界面正常显示 2. 优化项目依赖配置 - 添加spring-boot-starter-actuator用于健康检查 - 暂时注释flowable工作流依赖以简化项目 3. 更新数据库结构 - 优化martial_db.sql,精简表结构从123张减少到53张 - 保留核心BladeX系统表和15张武术业务表 - 更新测试数据:2场比赛、10名运动员、9个项目 4. 补充项目文档 - 添加架构说明、开发指南等中文文档 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
1172 lines
30 KiB
Markdown
1172 lines
30 KiB
Markdown
# 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("/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 命名规范
|
||
|
||
#### 类命名
|
||
|
||
```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 = "裁判等级") // Swagger,API文档描述
|
||
|
||
public class JudgeLevel extends TenantEntity {
|
||
|
||
@Schema(description = "等级名称") // 字段描述
|
||
private String levelName;
|
||
|
||
@TableField(exist = false) // 非数据库字段
|
||
private Integer judgeCount;
|
||
}
|
||
```
|
||
|
||
#### Controller 注解
|
||
|
||
```java
|
||
@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 简单查询
|
||
|
||
```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
|
||
// 方式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 批量操作
|
||
|
||
```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("/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 异常处理
|
||
|
||
```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
|
||
// ❓ 我应该继承哪个基类?
|
||
|
||
// 选择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 没有生效
|
||
|
||
```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) - 调试配置
|