zcloud_gbs_xinyi_gate/docs/开发指南.md

632 lines
19 KiB
Markdown
Raw Normal View History

2026-06-10 14:39:21 +08:00
# 信义门禁开发指南
## 一、项目概览
| 项目 | 值 |
|------|-----|
| 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/`