信义门禁开发指南
一、项目概览
| 项目 |
值 |
| 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
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
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
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
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
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
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
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
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
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
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
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 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
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
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
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 |
八、注意事项
- 包名统一为
com.zcloud.xinyigate,Java目录对应 com/zcloud/xinyigate/
- Application.java 中的
mapperPackages 已配置为 com.zcloud.xinyigate.persistence.mapper,新Mapper会自动扫描
- 分页参数统一使用
pageIndex(页码)和 pageSize(每页条数),参考 Query.java 工具类
- 定时任务必须实现
Job 接口,同时加 @JobRegister 和 @XxlJob 注解
- 对象转换:Cmd → E(在Exe中),DO → E(在GatewayImpl中),E → CO(在QueryExe中)
- Flyway 已集成,数据库变更脚本放在
start/src/main/resources/db/migration/ 下