# 后端开发规范

> 本项目后端开发的完整规范，基于代码库中实际存在的模式编写。

**技术栈**：Java 8、Spring Boot 2.0.8、MyBatis-Plus 2.3、MySQL/SQL Server、Redis、Druid

---

# 一、目录结构

本项目是一个 Java 8 多模块 Maven 项目（Spring Boot 2.0.8），基础包名为 `com.lansun.kjsd`。每个模块有独立的 `pom.xml`，采用分层架构：Controller → Service → Mapper → Database。

## 模块结构

```
kjsd-manage/                          # 根 POM（父工程）
├── kjsd-core/                        # 主应用模块 — 控制器、服务、Mapper、实体类
├── kjsd-framework/                   # 基础框架 — 安全、拦截器、配置、启动类
├── kjsd-tool/                        # 公共工具 — 通用领域对象、异常类、工具类
├── kjsd-system/                      # 系统管理 — 用户、角色、部门、权限
├── kjsd-api/                         # 第三方 API 集成（百度、支付宝、微信等）
├── kjsd-flowable/                    # 工作流引擎（Flowable 6.6.0）
├── kjsd-freeswitch/                  # 语音/通话模块（FreeSwitch ESL）
├── kjsd-netty/                       # 自定义 TCP/WebSocket（Netty）
├── kjsd-quartz/                      # 定时任务（Quartz）
└── kjsd-generate/                    # 代码生成（Velocity 模板）
```

### 模块依赖关系

```
kjsd-core → kjsd-framework → kjsd-tool
                           → kjsd-system → kjsd-tool
kjsd-api → kjsd-tool
```

- **kjsd-tool**：所有模块共用，不包含业务逻辑
- **kjsd-framework**：依赖 kjsd-system + kjsd-tool
- **kjsd-core**：可运行的 Spring Boot 应用，依赖 framework、tool、api

## kjsd-core 包结构

```
kjsd-core/src/main/java/com/lansun/kjsd/
├── Application.java                  # Spring Boot 启动类
├── controller/
│   ├── BaseController.java           # 通用控制器工具类（分页、文件上传）
│   ├── server/                       # PC 管理后台接口（如 /admin/goods/type）
│   ├── app/                          # 移动端 App 接口
│   │   ├── biz/                      # 业务员 App
│   │   ├── merchant/                 # 商户 App
│   │   ├── mini/                     # 小程序（微信/百度）
│   │   └── mp/                       # 公众号
│   ├── freeswitch/                   # FreeSwitch 事件处理
│   ├── mall/                         # 商城相关接口
│   └── report/                       # 报表接口
├── service/
│   ├── interfaces/                   # 服务接口（IXxxService）
│   └── impl/                         # 服务实现（XxxServiceImpl）
├── mapper/                           # MyBatis Mapper 接口
├── model/                            # 实体类（数据库表映射）
│   ├── dto/                          # 数据传输对象（XxxDTO）
│   └── vo/                           # 视图对象（XxxVO）
├── form/                             # 请求表单对象（XxxForm）
├── common/                           # Core 内共享 DTO 和工具
├── config/                           # Spring @Configuration 配置类
├── enums/                            # 顶层枚举
├── exception/                        # Core 专用异常
├── utils/                            # Core 专用工具类
├── pay/                              # 支付处理
├── task/                             # 定时任务实现
└── flowable/                         # 工作流相关代码

kjsd-core/src/main/resources/
├── application.yml                   # 主配置
├── application-dev.yml               # 开发环境
├── application-test.yml              # 测试环境
├── logback-spring.xml                # 日志配置
└── mapper/                           # MyBatis XML Mapper 文件
```

## kjsd-tool 包结构

```
kjsd-tool/src/main/java/com/lansun/kjsd/
├── core/
│   ├── domain/                       # 基础领域对象
│   │   ├── AjaxResult.java           # 标准 API 响应包装类（基于 HashMap）
│   │   ├── JsonResult.java           # 泛型响应包装类
│   │   ├── BaseEntity.java           # 基础实体（含审计字段）
│   │   └── entity/                   # 系统实体（SysUser、SysDept 等）
│   ├── page/                         # PageHelper 支撑类
│   └── redis/                        # Redis 工具类
├── utils/                            # 公共工具类（StringUtils、DateUtils 等）
├── exception/                        # 全局异常体系
│   ├── app/                          # AppServiceException
│   ├── equipment/                    # EquipmentBindException
│   ├── file/                         # FileException
│   ├── goods/                        # GoodsErrorException
│   ├── user/                         # UserException
│   ├── zone/                         # ZoneException
│   └── api/                          # ApiRequestException
├── enums/                            # 公共枚举（RespResultCode、UserRole 等）
├── constant/                         # 常量（HttpStatus、Constants）
└── annotation/                       # 自定义注解（@Log）
```

## 命名约定

| 类型 | 命名模式 | 示例 |
|------|---------|------|
| 控制器 | `XxxController` | `GoodsTypeController` |
| 服务接口 | `IXxxService` | `IGoodsTypeService` |
| 服务实现 | `XxxServiceImpl` | `GoodsTypeServiceImpl` |
| Mapper 接口 | `XxxMapper` | `GoodsInventoryMapper` |
| Mapper XML | `XxxMapper.xml` | `GoodsInventoryMapper.xml` |
| 实体类 | `Xxx`（无后缀） | `Order`、`GoodsType` |
| DTO | `XxxDTO` | `GoodsTypeListDTO` |
| VO | `XxxVO` | `OrderDetailVO` |
| 表单 | `XxxForm` | `OrderQueryForm` |
| 异常 | `XxxException` | `ServiceException` |

### URL 约定

| 面向对象 | 前缀 | 示例 |
|----------|------|------|
| PC 管理后台 | `/admin/` | `/admin/goods/type/list` |
| 业务员 App | `/app/biz/` | `/app/biz/goods/list` |
| 商户 App | `/app/merchant/` | `/app/merchant/order/list` |
| 小程序 | `/app/mini/` | `/app/mini/goods/list` |
| 公众号 | `/app/mp/` | `/app/mp/sign/area/list` |

### 新增代码放置位置

| 新增内容 | 放置位置 |
|----------|----------|
| 新 REST 接口 | `kjsd-core/.../controller/{server\|app}/` |
| 新业务逻辑 | `kjsd-core/.../service/interfaces/` + `impl/` |
| 新数据库查询 | `kjsd-core/.../mapper/` + `resources/mapper/` |
| 新实体类 | `kjsd-core/.../model/` |
| 新公共异常 | `kjsd-tool/.../exception/`（按领域分子目录） |
| 新公共工具 | `kjsd-tool/.../utils/` |
| 新枚举 | `kjsd-tool/.../enums/` |
| 新系统功能 | `kjsd-system/`（如果是用户/角色/部门相关） |
| 第三方 API 客户端 | `kjsd-api/` |

---

# 二、数据库规范

## 概述

- **ORM 框架**：MyBatis-Plus 2.3（MyBatis 的增强工具）
- **连接池**：Alibaba Druid 1.1.14
- **数据库**：MySQL（主库）、SQL Server（辅库）
- **分页插件**：PageHelper 1.2.10

### MyBatis-Plus 配置

```yaml
mybatis-plus:
  mapper-locations: classpath*:mapper/*.xml
  typeAliasesPackage: com.lansun.*.model,com.lansun.kjsd.core.domain.entity
  global-config:
    id-type: 0               # 自增主键
    db-column-underline: true # 驼峰 → 下划线自动转换
    logic-delete-value: 1
    logic-not-delete-value: 0
```

## 表命名规范

- 所有表名以 `kj_` 为前缀（如 `kj_order`、`kj_merchant`）
- 表名使用 **snake_case**（如 `kj_merchandise_library_product`）
- 列名使用 **snake_case**（如 `order_no`、`merchant_id`）

## 实体类模式

实体类直接使用 MyBatis-Plus 注解，不通过继承 `BaseEntity` 来做持久化映射：

```java
@TableName("kj_order")
public class Order implements Serializable {

    @TableId(value = "id", type = IdType.AUTO)
    private Long id;

    @TableField("order_no")
    private String orderNo;

    @TableField("merchant_id")
    private String merchantId;
}
```

`BaseEntity`（`kjsd-tool/.../core/domain/BaseEntity.java`）仅用于查询参数传递，其字段标记 `@TableField(exist = false)`。

## Mapper 模式

Mapper 接口继承 `BaseMapper<T>`，自定义 SQL 在 XML 中编写：

```java
public interface OrderMapper extends BaseMapper<Order> {
    List<Order> selectOrderList(Order query);
}
```

XML 文件位于 `kjsd-core/src/main/resources/mapper/`：

```xml
<mapper namespace="com.lansun.kjsd.mapper.OrderMapper">
    <select id="selectOrderList" resultType="Order">
        SELECT * FROM kj_order WHERE ...
    </select>
</mapper>
```

- **BaseMapper 内置方法**用于单表 CRUD
- **XML** 用于多表关联、复杂查询、批量操作
- **避免**在 Mapper 接口上使用 `@Select`/`@Insert` 注解

## 查询模式

### 分页

```java
startPage();
List<Order> list = orderService.selectOrderList(query);
return getDataTable(list);
```

### 条件查询

```java
EntityWrapper<Order> wrapper = new EntityWrapper<>();
wrapper.eq("merchant_id", merchantId)
       .orderBy("create_time", false);
List<Order> orders = orderMapper.selectList(wrapper);
```

## 逻辑删除

- 已删除：`del_flag = 1`，正常：`del_flag = 0`
- MyBatis-Plus 内置 `deleteById` 自动设置 `del_flag=1`
- 自定义 XML 查询必须显式过滤 `WHERE del_flag = 0`

## 事务处理

```java
@Transactional(rollbackFor = Exception.class)
public void createOrder(OrderForm form) {
    // ...
}
```

- 必须指定 `rollbackFor = Exception.class`
- 事务应尽量短，不要在事务内调用外部 API

## 常见错误

1. 新实体类忘记加 `@TableName`
2. 错误使用 `@TableField(exist = false)` 在数据库存在的字段上
3. 自定义 XML 中未过滤 `del_flag`
4. N+1 查询——优先在 XML 中使用 JOIN
5. 分页忘记在查询前调用 `startPage()`

---

# 三、错误处理

## 概述

- **全局异常处理器**：`GlobalExceptionHandler`（`kjsd-framework/.../web/exception/GlobalExceptionHandler.java`）
- **注解**：`@RestControllerAdvice`，拦截所有控制器异常
- 所有异常被捕获后转换为 `AjaxResult` 或 `JsonResult`

## 异常体系

```
RuntimeException
├── ServiceException              # 通用业务异常（最常用）
├── DataServiceException          # 携带附加数据的业务异常
├── AppServiceException           # App 专用异常 → 返回 JsonResult
├── LoginException                # 认证失败
├── UserException                 # 用户相关错误
├── GoodsErrorException           # 商品/业务校验错误
├── GoodsImportException          # 批量导入聚合异常
├── ZoneException                 # 区域校验错误
├── ApiRequestException           # 第三方 API 调用失败
├── EquipmentBindException        # 设备绑定错误
├── FileException                 # 文件上传错误
└── WxRuntimeException            # 微信支付错误
```

## ServiceException（最常用）

```java
throw new ServiceException("参数不能为空");
throw new ServiceException("操作失败", ResultCode.WRONG_PARAMS);
throw new ServiceException(RespResultCode.NOT_LOGIN);
```

## 响应码（RespResultCode）

| 编码 | 常量 | 用途 |
|------|------|------|
| 200 | `SUCCESS` | 请求成功 |
| 201 | `EMPTY_RESULT` | 查询无数据 |
| 500 | `FAIL` | 通用失败 |
| 700 | `TOKEN_ERROR` | Token 过期/无效 |
| 104000 | `NOT_LOGIN` | 未登录 |
| 104104 | `WRONG_PARAMS` | 参数错误 |

## 错误处理模式

控制器应**抛出异常**，不要自行捕获：

```java
// 正确
@GetMapping("/detail")
public AjaxResult detail(Long id) {
    if (id == null) {
        throw new ServiceException("参数不能为空");
    }
    return AjaxResult.success("查询成功!", orderService.getById(id));
}

// 避免
@GetMapping("/detail")
public AjaxResult detail(Long id) {
    try { ... } catch (Exception e) { return AjaxResult.error("查询失败!"); }
}
```

- 携带数据的错误用 `DataServiceException`
- App 接口用 `AppServiceException`（返回 `JsonResult`）

## 常见错误

1. Service 层 catch 后返回 `AjaxResult.error()`——应抛出 `ServiceException`
2. 使用 `e.printStackTrace()`——用 `log.error()` 代替
3. App 控制器返回了 `AjaxResult` 而非 `JsonResult`
4. 硬编码错误码——使用 `RespResultCode` 枚举

---

# 四、日志规范

## 概述

- **日志框架**：SLF4J + Logback
- **配置文件**：`kjsd-core/src/main/resources/logback-spring.xml`
- **日志存储**：`/usr/local/tomcat_kuaijin/logs/${appname}/`
- **根级别**：INFO

## 日志文件

| 文件 | 级别 | 保留 | 单文件上限 |
|------|------|------|-----------|
| `${appname}_debug.log` | DEBUG | 5 天 | 100MB |
| `${appname}_info.log` | INFO | 15 天 | 100MB |
| `${appname}_warn.log` | WARN | 5 天 | 100MB |
| `${appname}_error.log` | ERROR | 5 天 | 100MB |

## 日志格式

```
%d %p [%X{traceId}] [%X{traceUser}] [%t] %C: %msg%n
```

- `%X{traceId}` — MDC 链路追踪 ID
- `%X{traceUser}` — MDC 当前用户

## Logger 声明

```java
private static final Logger log = LoggerFactory.getLogger(ClassName.class);
```

## 日志级别

| 级别 | 使用场景 | 示例 |
|------|---------|------|
| `error` | 意料之外的故障 | `log.error("请求地址'{}',发生未知异常.", requestURI, e)` |
| `warn` | 预期内的业务异常 | `log.warn("商品导入异常: {}", e.getMessage(), e)` |
| `info` | 重要业务事件 | `log.info("订单{}创建成功", orderNo)` |
| `debug` | 诊断细节 | `log.debug("查询参数: {}", queryParams)` |

## 禁止记录

- 用户密码、Token、银行卡号、API 密钥

## 常见错误

1. `e.printStackTrace()` —— 用 `log.error("msg", e)` 代替
2. `log.error("message")` 不带异常对象——堆栈信息会丢失
3. 字符串拼接——用 `{}` 占位符
4. 循环内逐条打日志——改为汇总记录

---

# 五、代码质量规范

## 必须遵循的模式

### 控制器

```java
@RestController
@RequestMapping("/admin/goods/type")
public class GoodsTypeController extends BaseController {

    @Autowired
    private GoodsTypeService goodsTypeService;

    @PreAuthorize("@ss.hasPermi('basic:goodsType:list')")
    @GetMapping(value = "/list")
    public AjaxResult list(GoodsTypeListDTO dto) {
        List<GoodsType> list = goodsTypeService.list(dto);
        return AjaxResult.success("查询成功", list);
    }
}
```

- 继承 `BaseController`
- 使用 `@PreAuthorize` 鉴权
- 管理后台返回 `AjaxResult`，App 返回 `JsonResult`
- 使用 `@Autowired` 字段注入

### 服务

- 接口放 `service/interfaces/`，命名 `IXxxService`
- 实现放 `service/impl/`，命名 `XxxServiceImpl`，标注 `@Service`

### 实体类

- 必须有 `serialVersionUID`
- 使用 `@TableName`（含 `kj_` 前缀）、`@TableId(type = IdType.AUTO)`、`@TableField`

## 禁止使用的模式

1. `System.out.println()` 或 `e.printStackTrace()`
2. 控制器中 catch 泛 `Exception` 后返回错误响应
3. 控制器中写业务逻辑
4. Mapper 接口上用 `@Select`/`@Insert` 注解写 SQL
5. 硬编码错误码
6. 新建响应包装类（使用已有的 `AjaxResult`、`JsonResult` 等）
7. 构造器注入（项目使用字段注入）
8. Lombok（项目未使用）

## 代码审查检查清单

- [ ] 控制器返回正确的响应类型
- [ ] 管理后台接口有 `@PreAuthorize` 权限注解
- [ ] Service 层抛出 `ServiceException` 而非返回错误响应
- [ ] 新实体类有 `@TableName`、`@TableId`、`@TableField`
- [ ] Mapper XML 过滤了 `del_flag = 0`
- [ ] 分页使用 `startPage()` + `getDataTable()`
- [ ] 日志用 SLF4J `{}` 占位符
- [ ] 没有 `System.out.println()` 或 `e.printStackTrace()`
- [ ] 事务方法有 `rollbackFor = Exception.class`
- [ ] 日期字段有 `@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")`
