zcloud_gbs_xinyi_gate/docs/开发指南.md

632 lines
19 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

# 信义门禁开发指南
## 一、项目概览
| 项目 | 值 |
|------|-----|
| groupId | `com.zcloud.xinyigate` |
| artifactId | `zcloud_gbs_xinyi_gate` |
| 包名 | `com.zcloud.xinyigate` |
| 应用名 | `jjb-saas-zcloud-xinyi-gate` |
| 网关名 | `xinyiGate` |
| 父POM | `com.jjb.saas:jjb-saas-parent:2.2.0-SNAPSHOT` |
| 公共库 | `com.zcloud.gbscommon:zcloud_gbscommon:1.0.0-SNAPSHOT` |
| 技术栈 | Spring Boot 2.6 + Spring Cloud + Nacos + MyBatis-Plus + XXL-Job |
## 二、模块结构
```
zcloud_gbs_xinyi_gate/
├── start/ # 启动模块
│ └── src/main/
│ ├── java/.../Application.java # Spring Boot 入口
│ └── resources/
│ ├── nacos.yml # Nacos配置开发
│ ├── nacos-prod.yml # Nacos配置生产
│ ├── bootstrap.yml # 引导配置
│ └── templates/xinyiGate/ # 前端模板
├── web-adapter/ # 适配层 - 接收外部请求
│ └── src/main/java/.../
│ ├── web/{module}/ # REST Controller
│ │ └── XxxController.java
│ └── job/ # 定时任务
│ └── XxxJob.java
├── web-app/ # 应用层 - 业务编排
│ └── src/main/java/.../
│ ├── command/{module}/ # 写操作执行器
│ │ ├── XxxAddExe.java
│ │ ├── XxxEditExe.java
│ │ └── XxxRemoveExe.java
│ ├── command/query/{module}/ # 读操作执行器
│ │ └── XxxQueryExe.java
│ ├── command/convertor/{module}/ # 对象转换器
│ │ └── XxxCoConvertor.java
│ └── service/{module}/ # Service实现
│ └── XxxServiceImpl.java
├── web-client/ # 客户端层 - 接口定义 + DTO
│ └── src/main/java/.../
│ ├── api/{module}/ # 服务接口
│ │ └── XxxServiceI.java
│ ├── dto/{module}/ # 入参Cmd/Qry
│ │ ├── XxxAddCmd.java
│ │ ├── XxxEditCmd.java
│ │ └── XxxPageQry.java
│ ├── dto/clientobject/{module}/ # 出参CO
│ │ └── XxxCO.java
│ └── config/ # 配置类
├── web-domain/ # 领域层 - 核心业务模型
│ └── src/main/java/.../
│ └── domain/
│ ├── model/{module}/ # 领域实体
│ │ └── XxxE.java
│ ├── gateway/{module}/ # 网关接口
│ │ └── XxxGateway.java
│ ├── enums/ # 枚举
│ └── util/ # 领域工具
├── web-infrastructure/ # 基础设施层 - 数据库对接
│ └── src/main/
│ ├── java/.../
│ │ ├── gatewayimpl/{module}/ # 网关实现
│ │ │ └── XxxGatewayImpl.java
│ │ ├── persistence/
│ │ │ ├── dataobject/{module}/ # 数据对象
│ │ │ │ └── XxxDO.java
│ │ │ ├── mapper/{module}/ # Mapper接口
│ │ │ │ └── XxxMapper.java
│ │ │ ├── repository/{module}/ # Repository接口
│ │ │ │ └── XxxRepository.java
│ │ │ └── repository/impl/{module}/ # Repository实现
│ │ │ └── XxxRepositoryImpl.java
│ │ └── utils/ # 工具类
│ └── resources/
│ ├── mapper/{module}/ # MyBatis XML
│ │ └── XxxMapper.xml
│ └── mybatis/mybatis-config.xml
└── pom.xml # 父POM
```
## 三、模块依赖关系
```
start → adapter → app → client → domain
infrastructure → domain
```
| 模块 | 可依赖 |
|------|--------|
| web-domain | 无(纯核心,不依赖任何业务模块) |
| web-client | web-domain |
| web-infrastructure | web-domain |
| web-app | web-client, web-infrastructure |
| web-adapter | web-app, web-client |
| start | web-adapter |
## 四、调用链路
```
HTTP请求
→ Controller (adapter层参数校验、权限控制)
→ ServiceI (client层接口定义)
→ ServiceImpl (app层业务编排)
→ XxxExe (app层单一职责执行器)
→ XxxGateway (domain层网关接口)
→ XxxGatewayImpl (infrastructure层网关实现)
→ XxxMapper (infrastructure层MyBatis接口)
→ XxxMapper.xml (SQL映射)
→ 数据库
```
## 五、各层代码模板
### 5.1 web-client — 服务接口 + DTO
#### 服务接口 `XxxServiceI.java`
```java
package com.zcloud.xinyigate.api.module;
import com.jjb.saas.framework.client.ResponseData;
import com.zcloud.xinyigate.dto.module.XxxAddCmd;
import com.zcloud.xinyigate.dto.module.XxxEditCmd;
import com.zcloud.xinyigate.dto.module.XxxPageQry;
import com.zcloud.xinyigate.dto.clientobject.module.XxxCO;
public interface XxxServiceI {
ResponseData addXxx(XxxAddCmd cmd);
ResponseData editXxx(XxxEditCmd cmd);
ResponseData removeXxx(Long id);
ResponseData<XxxCO> getXxx(Long id);
ResponseData queryXxxPage(XxxPageQry qry);
}
```
#### 新增命令 `XxxAddCmd.java`
```java
package com.zcloud.xinyigate.dto.module;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import javax.validation.constraints.NotBlank;
@Data
@ApiModel("Xxx新增命令")
public class XxxAddCmd {
@NotBlank(message = "名称不能为空")
@ApiModelProperty("名称")
private String name;
@ApiModelProperty("备注")
private String remark;
}
```
#### 分页查询 `XxxPageQry.java`
```java
package com.zcloud.xinyigate.dto.module;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
@Data
@ApiModel("Xxx分页查询")
public class XxxPageQry {
@ApiModelProperty("页码")
private Integer pageIndex = 1;
@ApiModelProperty("每页条数")
private Integer pageSize = 10;
@ApiModelProperty("名称(模糊查询)")
private String name;
}
```
#### 返回对象 `XxxCO.java`
```java
package com.zcloud.xinyigate.dto.clientobject.module;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
@Data
@ApiModel("Xxx返回对象")
public class XxxCO {
@ApiModelProperty("ID")
private Long id;
@ApiModelProperty("名称")
private String name;
@ApiModelProperty("备注")
private String remark;
}
```
### 5.2 web-domain — 领域实体 + 网关接口
#### 领域实体 `XxxE.java`
```java
package com.zcloud.xinyigate.domain.model.module;
import lombok.Data;
@Data
public class XxxE {
private Long id;
private String name;
private String remark;
}
```
#### 网关接口 `XxxGateway.java`
```java
package com.zcloud.xinyigate.domain.gateway.module;
import com.zcloud.xinyigate.domain.model.module.XxxE;
public interface XxxGateway {
void add(XxxE xxxE);
void edit(XxxE xxxE);
void remove(Long id);
XxxE getById(Long id);
}
```
### 5.3 web-app — Service实现 + 执行器
#### Service实现 `XxxServiceImpl.java`
```java
package com.zcloud.xinyigate.service.module;
import com.jjb.saas.framework.client.ResponseData;
import com.zcloud.xinyigate.api.module.XxxServiceI;
import com.zcloud.xinyigate.command.module.XxxAddExe;
import com.zcloud.xinyigate.command.module.XxxEditExe;
import com.zcloud.xinyigate.command.query.module.XxxQueryExe;
import com.zcloud.xinyigate.dto.module.XxxAddCmd;
import com.zcloud.xinyigate.dto.module.XxxEditCmd;
import com.zcloud.xinyigate.dto.module.XxxPageQry;
import com.zcloud.xinyigate.dto.clientobject.module.XxxCO;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
@Service
@RequiredArgsConstructor
public class XxxServiceImpl implements XxxServiceI {
private final XxxAddExe xxxAddExe;
private final XxxEditExe xxxEditExe;
private final XxxQueryExe xxxQueryExe;
@Override
public ResponseData addXxx(XxxAddCmd cmd) {
xxxAddExe.execute(cmd);
return ResponseData.success();
}
@Override
public ResponseData editXxx(XxxEditCmd cmd) {
xxxEditExe.execute(cmd);
return ResponseData.success();
}
@Override
public ResponseData removeXxx(Long id) {
// 直接调用Gateway或写RemoveExe
return ResponseData.success();
}
@Override
public ResponseData<XxxCO> getXxx(Long id) {
return ResponseData.success(xxxQueryExe.getById(id));
}
@Override
public ResponseData queryXxxPage(XxxPageQry qry) {
return ResponseData.success(xxxQueryExe.queryPage(qry));
}
}
```
#### 新增执行器 `XxxAddExe.java`
```java
package com.zcloud.xinyigate.command.module;
import com.zcloud.xinyigate.domain.gateway.module.XxxGateway;
import com.zcloud.xinyigate.domain.model.module.XxxE;
import com.zcloud.xinyigate.dto.module.XxxAddCmd;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
@Component
@RequiredArgsConstructor
public class XxxAddExe {
private final XxxGateway xxxGateway;
public void execute(XxxAddCmd cmd) {
XxxE xxxE = new XxxE();
xxxE.setName(cmd.getName());
xxxE.setRemark(cmd.getRemark());
xxxGateway.add(xxxE);
}
}
```
#### 查询执行器 `XxxQueryExe.java`
```java
package com.zcloud.xinyigate.command.query.module;
import com.zcloud.xinyigate.domain.gateway.module.XxxGateway;
import com.zcloud.xinyigate.domain.model.module.XxxE;
import com.zcloud.xinyigate.dto.clientobject.module.XxxCO;
import com.zcloud.xinyigate.dto.module.XxxPageQry;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
@Component
@RequiredArgsConstructor
public class XxxQueryExe {
private final XxxGateway xxxGateway;
public XxxCO getById(Long id) {
XxxE xxxE = xxxGateway.getById(id);
// E → CO 转换
XxxCO co = new XxxCO();
co.setId(xxxE.getId());
co.setName(xxxE.getName());
co.setRemark(xxxE.getRemark());
return co;
}
public Object queryPage(XxxPageQry qry) {
// 调用Gateway分页查询转换返回
return null;
}
}
```
### 5.4 web-infrastructure — 数据库对接
#### 数据对象 `XxxDO.java`
```java
package com.zcloud.xinyigate.persistence.dataobject.module;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
@Data
@TableName("t_xxx")
public class XxxDO {
@TableId(type = IdType.AUTO)
private Long id;
private String name;
private String remark;
}
```
#### Mapper接口 `XxxMapper.java`
```java
package com.zcloud.xinyigate.persistence.mapper.module;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.zcloud.xinyigate.persistence.dataobject.module.XxxDO;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface XxxMapper extends BaseMapper<XxxDO> {
}
```
#### Mapper XML `XxxMapper.xml`
```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="com.zcloud.xinyigate.persistence.mapper.module.XxxMapper">
</mapper>
```
#### 网关实现 `XxxGatewayImpl.java`
```java
package com.zcloud.xinyigate.gatewayimpl.module;
import com.zcloud.xinyigate.domain.gateway.module.XxxGateway;
import com.zcloud.xinyigate.domain.model.module.XxxE;
import com.zcloud.xinyigate.persistence.dataobject.module.XxxDO;
import com.zcloud.xinyigate.persistence.mapper.module.XxxMapper;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
@Component
@RequiredArgsConstructor
public class XxxGatewayImpl implements XxxGateway {
private final XxxMapper xxxMapper;
@Override
public void add(XxxE xxxE) {
XxxDO xxxDO = new XxxDO();
xxxDO.setName(xxxE.getName());
xxxDO.setRemark(xxxE.getRemark());
xxxMapper.insert(xxxDO);
}
@Override
public void edit(XxxE xxxE) {
XxxDO xxxDO = new XxxDO();
xxxDO.setId(xxxE.getId());
xxxDO.setName(xxxE.getName());
xxxDO.setRemark(xxxE.getRemark());
xxxMapper.updateById(xxxDO);
}
@Override
public void remove(Long id) {
xxxMapper.deleteById(id);
}
@Override
public XxxE getById(Long id) {
XxxDO xxxDO = xxxMapper.selectById(id);
if (xxxDO == null) return null;
XxxE xxxE = new XxxE();
xxxE.setId(xxxDO.getId());
xxxE.setName(xxxDO.getName());
xxxE.setRemark(xxxDO.getRemark());
return xxxE;
}
}
```
### 5.5 web-adapter — Controller
#### REST接口 `XxxController.java`
```java
package com.zcloud.xinyigate.web.module;
import com.jjb.saas.framework.client.ResponseData;
import com.zcloud.xinyigate.api.module.XxxServiceI;
import com.zcloud.xinyigate.dto.module.XxxAddCmd;
import com.zcloud.xinyigate.dto.module.XxxEditCmd;
import com.zcloud.xinyigate.dto.module.XxxPageQry;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
@RestController
@RequestMapping("/xxx")
@Api(tags = "Xxx管理")
@RequiredArgsConstructor
public class XxxController {
private final XxxServiceI xxxService;
@PostMapping("/add")
@ApiOperation("新增")
public ResponseData add(@Valid @RequestBody XxxAddCmd cmd) {
return xxxService.addXxx(cmd);
}
@PostMapping("/edit")
@ApiOperation("编辑")
public ResponseData edit(@Valid @RequestBody XxxEditCmd cmd) {
return xxxService.editXxx(cmd);
}
@PostMapping("/remove/{id}")
@ApiOperation("删除")
public ResponseData remove(@PathVariable Long id) {
return xxxService.removeXxx(id);
}
@GetMapping("/get/{id}")
@ApiOperation("详情")
public ResponseData get(@PathVariable Long id) {
return xxxService.getXxx(id);
}
@PostMapping("/page")
@ApiOperation("分页查询")
public ResponseData page(@RequestBody XxxPageQry qry) {
return xxxService.queryXxxPage(qry);
}
}
```
### 5.6 定时任务web-adapter
#### `XxxJob.java`
```java
package com.zcloud.xinyigate.job;
import com.jjb.saas.framework.job.Job;
import com.jjb.saas.framework.job.annotation.JobRegister;
import com.xxl.job.core.biz.model.ReturnT;
import com.xxl.job.core.handler.annotation.XxlJob;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
@Component
@RequiredArgsConstructor
@Slf4j
public class XxxJob implements Job {
private final XxxServiceI xxxService;
@Override
@JobRegister(cron = "0 0 0 * * ?", jobDesc = "Xxx定时任务", triggerStatus = 1)
@XxlJob("com.zcloud.xinyigate.job.XxxJob")
public ReturnT<String> execute(String param) {
log.info("【Xxx定时任务】开始执行");
try {
// xxxService.doSomething();
log.info("【Xxx定时任务】执行完成");
} catch (Exception e) {
log.error("【Xxx定时任务】执行异常", e);
return new ReturnT<>(ReturnT.FAIL_CODE, e.getMessage());
}
return ReturnT.SUCCESS;
}
}
```
## 六、开发步骤清单
实现一个新接口时,按以下顺序创建文件:
| 步骤 | 模块 | 文件 | 说明 |
|------|------|------|------|
| 1 | web-client | `dto/module/XxxAddCmd.java` | 定义入参 |
| 2 | web-client | `dto/module/XxxPageQry.java` | 定义查询参数 |
| 3 | web-client | `dto/clientobject/module/XxxCO.java` | 定义出参 |
| 4 | web-client | `api/module/XxxServiceI.java` | 定义服务接口 |
| 5 | web-domain | `domain/model/module/XxxE.java` | 定义领域实体 |
| 6 | web-domain | `domain/gateway/module/XxxGateway.java` | 定义网关接口 |
| 7 | web-infrastructure | `persistence/dataobject/module/XxxDO.java` | 定义数据对象 |
| 8 | web-infrastructure | `persistence/mapper/module/XxxMapper.java` | 定义Mapper接口 |
| 9 | web-infrastructure | `resources/mapper/module/XxxMapper.xml` | 编写SQL |
| 10 | web-infrastructure | `gatewayimpl/module/XxxGatewayImpl.java` | 实现网关 |
| 11 | web-app | `command/module/XxxAddExe.java` | 实现新增逻辑 |
| 12 | web-app | `command/query/module/XxxQueryExe.java` | 实现查询逻辑 |
| 13 | web-app | `service/module/XxxServiceImpl.java` | 实现Service |
| 14 | web-adapter | `web/module/XxxController.java` | 暴露REST接口 |
## 七、命名规范
| 类型 | 命名 | 示例 |
|------|------|------|
| Controller | `{业务}Controller` | `GateRecordController` |
| Service接口 | `{业务}ServiceI` | `GateRecordServiceI` |
| Service实现 | `{业务}ServiceImpl` | `GateRecordServiceImpl` |
| 新增执行器 | `{业务}AddExe` | `GateRecordAddExe` |
| 编辑执行器 | `{业务}EditExe` | `GateRecordEditExe` |
| 删除执行器 | `{业务}RemoveExe` | `GateRecordRemoveExe` |
| 查询执行器 | `{业务}QueryExe` | `GateRecordQueryExe` |
| 领域实体 | `{业务}E` | `GateRecordE` |
| 网关接口 | `{业务}Gateway` | `GateRecordGateway` |
| 网关实现 | `{业务}GatewayImpl` | `GateRecordGatewayImpl` |
| 数据对象 | `{业务}DO` | `GateRecordDO` |
| Mapper接口 | `{业务}Mapper` | `GateRecordMapper` |
| 新增命令 | `{业务}AddCmd` | `GateRecordAddCmd` |
| 编辑命令 | `{业务}EditCmd` | `GateRecordEditCmd` |
| 分页查询 | `{业务}PageQry` | `GateRecordPageQry` |
| 返回对象 | `{业务}CO` | `GateRecordCO` |
| 定时任务 | `{业务}Job` | `GateRecordJob` |
## 八、注意事项
1. **包名统一为 `com.zcloud.xinyigate`**Java目录对应 `com/zcloud/xinyigate/`
2. **Application.java** 中的 `mapperPackages` 已配置为 `com.zcloud.xinyigate.persistence.mapper`新Mapper会自动扫描
3. **分页参数**统一使用 `pageIndex`(页码)和 `pageSize`(每页条数),参考 `Query.java` 工具类
4. **定时任务**必须实现 `Job` 接口,同时加 `@JobRegister``@XxlJob` 注解
5. **对象转换**Cmd → E在Exe中DO → E在GatewayImpl中E → CO在QueryExe中
6. **Flyway** 已集成,数据库变更脚本放在 `start/src/main/resources/db/migration/`