# 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
-- 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

```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 = "裁判等级")   // 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("/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("/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) - 调试配置
