# 信义门禁开发指南 ## 一、项目概览 | 项目 | 值 | |------|-----| | 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 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 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 { } ``` #### Mapper XML `XxxMapper.xml` ```xml ``` #### 网关实现 `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 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/` 下