(一)创建项目
介绍
项目是基于 Spring boot
的 maven
项目,本章节介绍怎样创建基于HZERO平台的项目。
- 新建maven项目
- 添加项目依赖
- 添加默认配置文件
创建maven项目
$ mkdir -p hzero-todo-service
$ cd hzero-todo-service
添加项目依赖
$ touch pom.xml
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<!--hzero-parent dependency-->
<parent>
<groupId>org.hzero</groupId>
<artifactId>hzero-parent</artifactId>
<version>1.2.0.RELEASE</version>
</parent>
<artifactId>hzero-todo-service</artifactId>
<dependencies>
<!--hzero-->
<dependency>
<groupId>org.hzero.starter</groupId>
<artifactId>hzero-starter-redis</artifactId>
</dependency>
<dependency>
<groupId>org.hzero.starter</groupId>
<artifactId>hzero-starter-core</artifactId>
</dependency>
<dependency>
<groupId>org.hzero.boot</groupId>
<artifactId>hzero-boot-admin</artifactId>
</dependency>
<!-- undertow -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-undertow</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- actuator -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!-- config client -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-client</artifactId>
</dependency>
<!-- register client -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
</dependencies>
<repositories>
<repository>
<id>HzeroRelease</id>
<name>Hzero-Release Repository</name>
<url>http://nexus.saas.hand-china.com/content/repositories/Hzero-Release/</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
<repository>
<id>HzeroSnapshot</id>
<name>Hzero-Snapshot Repository</name>
<url>http://nexus.saas.hand-china.com/content/repositories/Hzero-Snapshot/</url>
<snapshots>
<enabled>true</enabled>
</snapshots>
</repository>
</repositories>
</project>
根据子级模块所需jar包添加需要的依赖。
- (必须)hzero-starter-core:核心工具包,提供了基础支持类、消息工具、Redis工具、及其他一些通用工具、常量封装等。
- hzero-starter-mybatis-mapper,通用 mapper 和分页插件集成,扩展多语言、审计字段等功能。
添加默认配置文件
在根目录下创建源码文件夹和资源文件夹。
$ mkdir -p src/main/java
$ mkdir -p src/main/resources
项目采用spring boot 进行管理。需要在子项目中配置默认的配置项。
在resource
文件夹中创建 application.yml
, bootstrap.yml
。
$ cd src/main/resources
$ touch application.yml
$ touch bootstrap.yml
-
bootstrap.yml
: 配置不会通过环境变量替换和必须在bootstrap中指定的变量。包括项目端口,应用名,hzero-config
地址等。 -
application.yml
: 配置项目的应用程序配置,包含默认的线上数据库连接配置,注册中心地址等,这些变量可以通过profile
或者环境变量修改。 - 二者区别:
bootstrap.yml
在程序引导时执行,应用于更加早期配置信息读取,如可以配置application.yml中使用到参数。application.yml
是应用程序特有配置信息,可以用来配置后续各个模块中需使用的公共参数等。bootstrap.yml 先于 application.yml 加载。
bootstrap.yml
server:
# 服务端口
port: 8088
management:
server:
# 监控管理端口
port: 8089
endpoints:
web:
exposure:
# 需要开放的 Actuator 监控端点,默认开放所有
include: '*'
spring:
application:
name: hzero-todo-service
profiles:
active: ${SPRING_PROFILES_ACTIVE:default}
cloud:
config:
fail-fast: false
# 是否启用配置中心
enabled: ${SPRING_CLOUD_CONFIG_ENABLED:false}
# 配置中心地址
uri: ${SPRING_CLOUD_CONFIG_URI:http://dev.hzero.com.cn:8010}
retry:
# 最大重试次数
maxAttempts: 6
multiplier: 1.1
# 重试间隔时间
maxInterval: 2000
# 标签
label: ${SPRING_CLOUD_CONFIG_LABEL:}
inetutils:
# 本地多网卡时,忽略回环网卡
ignored-interfaces[0]: lo
# 本地多网卡时,选择注册的网段
preferred-networks[0]: 192.168
application.yml
# 日志配置
logging:
level:
org.hzero: ${LOG_LEVEL:debug}
org.apache.ibatis: ${LOG_LEVEL:debug}
io.choerodon: ${LOG_LEVEL:debug}
编写TodoApplication类
在src/main/java
中创建TodoApplication,在项目根目录下执行如下命名:
$ mkdir -p src/main/java/org/hzero/todo
$ touch src/main/java/org/hzero/todo/TodoApplication.java
TodoApplication.java
package org.hzero.todo;
import io.swagger.annotations.ApiOperation;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import io.choerodon.core.iam.ResourceLevel;
import io.choerodon.resource.annoation.EnableChoerodonResourceServer;
import io.choerodon.swagger.annotation.Permission;
@SpringBootApplication
@RestController
// @EnableChoerodonResourceServer 用于开启资源认证、关闭 Security 安全认证
@EnableChoerodonResourceServer
public class TodoApplication {
public static void main(String[] args) {
SpringApplication.run(TodoApplication.class, args);
}
@GetMapping
@Permission(level = ResourceLevel.SITE, permissionPublic = true)
@ApiOperation(value = "demo")
public ResponseEntity<String> hello() {
return new ResponseEntity<>("hello hzero!", HttpStatus.OK);
}
}
编写TodoExtraDataManager类
在src/main/java
中创建TodoExtraDataManager,在项目根目录下执行如下命名:
$ mkdir -p src/main/java/org/hzero/todo
$ touch src/main/java/org/hzero/todo/config/TodoExtraDataManager.java
TodoExtraDataManager.java
指定当前服务的路由信息
package org.hzero.todo.config;
import io.choerodon.core.swagger.ChoerodonRouteData;
import io.choerodon.swagger.annotation.ChoerodonExtraData;
import io.choerodon.swagger.swagger.extra.ExtraData;
import io.choerodon.swagger.swagger.extra.ExtraDataManager;
@ChoerodonExtraData
public class TodoExtraDataManager implements ExtraDataManager {
@Override
public ExtraData getData() {
ChoerodonRouteData choerodonRouteData = new ChoerodonRouteData();
choerodonRouteData.setName("htdo");
choerodonRouteData.setPath("/htdo/**");
choerodonRouteData.setServiceId("hzero-todo-service");
choerodonRouteData.setPackages("org.hzero.todo");
extraData.put(ExtraData.ZUUL_ROUTE_DATA, choerodonRouteData);
return extraData;
}
}
启动应用
启动Eureka
cd D:\Workspace\Raycus\hzero\hzero-service\hzero-register
mvn spring-boot:run
http://localhost:8000
启动redis
docker run -d --name redis -p 6379:6379 redis:latest
启动应用
$ mvn clean spring-boot:run
控制台打印出如下信息,则表示启动成功。
Started TodoApplication in 14.189 seconds (JVM running for 14.942)
此时可以打开浏览器,在浏览器输入:http://localhost:8089/actuator/health
返回如下信息:
{
status: "UP"
}
在浏览器输入:http://localhost:8088/hello
,页面打印 hello world
。
这样,一个简单的Spring boot
应用就已经搭建成功。
(二)初始化数据库
介绍
项目创建成功之后,需要初始化本地数据库。在开发之前,请确保本地项目已经创建成功,详见 创建项目
创建用户
CREATE USER 'hzero'@'%' IDENTIFIED BY "hzero";
创建数据库
CREATE DATABASE todo_service DEFAULT CHARACTER SET utf8;
GRANT ALL PRIVILEGES ON todo_service.* TO hzero@'%';
FLUSH PRIVILEGES;
设计表结构
- 先使用Excel设计
todo_user
及todo_task
表结构。Excel 设计参考:TODO表设计
- 设计完成后,将相应数据库的脚本拷贝到
todo_service
库下执行,创建表。
在todo_service数据库中创建如下两张表:
todo_user表
Drop Table IF EXISTS TODO_USER;
Create Table TODO_USER
(
ID bigint Not Null auto_increment primary key,
EMPLOYEE_NAME Varchar(30) Not Null,
EMPLOYEE_NUMBER Varchar(30) Not Null,
EMAIL Varchar(60),
object_version_number BigInt(20) Not Null Default 1,
creation_date datetime Not Null Default CURRENT_TIMESTAMP,
created_by bigint(20) Not Null Default -1,
last_updated_by bigint(20) Not Null Default -1,
last_update_date datetime Not Null Default CURRENT_TIMESTAMP
);
ALTER TABLE TODO_USER COMMENT '用户表';
ALTER TABLE TODO_USER MODIFY `ID` bigint Not Null auto_increment Comment '表ID,主键,供其他表做外键';
ALTER TABLE TODO_USER MODIFY `EMPLOYEE_NAME` Varchar(30) Not Null Comment '员工名';
ALTER TABLE TODO_USER MODIFY `EMPLOYEE_NUMBER` Varchar(30) Not Null Comment '员工编号';
ALTER TABLE TODO_USER MODIFY `EMAIL` Varchar(60) Comment '邮箱';
ALTER TABLE TODO_USER MODIFY `object_version_number` BigInt(20) Not Null Default 1 Comment '行版本号,用来处理锁';
ALTER TABLE `TODO_USER` ADD UNIQUE TODO_USER_u1(`EMPLOYEE_NUMBER`);
todo_task表
Drop Table IF EXISTS TODO_TASK;
Create Table TODO_TASK
(
ID bigint Not Null auto_increment primary key,
EMPLOYEE_ID BigInt(20) Not Null,
STATE Varchar(30) Not Null,
TASK_NUMBER Varchar(60) Not Null,
TASK_DESCRIPTION Varchar(240),
TENANT_ID BigInt(20) Not Null,
object_version_number BigInt(20) Not Null Default 1,
creation_date datetime Not Null Default CURRENT_TIMESTAMP,
created_by bigint(20) Not Null Default -1,
last_updated_by bigint(20) Not Null Default -1,
last_update_date datetime Not Null Default CURRENT_TIMESTAMP
);
ALTER TABLE TODO_TASK COMMENT '任务表';
ALTER TABLE TODO_TASK MODIFY `ID` bigint Not Null auto_increment Comment '表ID,主键,供其他表做外键';
ALTER TABLE TODO_TASK MODIFY `EMPLOYEE_ID` BigInt(20) Not Null Comment '员工ID,TODO_USER.ID';
ALTER TABLE TODO_TASK MODIFY `STATE` Varchar(30) Not Null Comment '状态,值集:TODO.STATE';
ALTER TABLE TODO_TASK MODIFY `TASK_NUMBER` Varchar(60) Not Null Comment '任务编号';
ALTER TABLE TODO_TASK MODIFY `TASK_DESCRIPTION` Varchar(240) Comment '任务描述';
ALTER TABLE TODO_TASK MODIFY `TENANT_ID` BigInt(20) Not Null Comment '租户ID';
ALTER TABLE TODO_TASK MODIFY `object_version_number` BigInt(20) Not Null Default 1 Comment '行版本号,用来处理锁';
ALTER TABLE `TODO_TASK` ADD UNIQUE TODO_TASK_u1(`TASK_NUMBER`,`TENANT_ID`);
导出Groovy脚本
HZERO 采用Liquibase
+ groovy
的方式对数据库管理,便于后续同步各个环境数据库结构以及升级。
更多有关Liguibase的资料见 Liquibase 官网。
- 使用代码生成器工具生成 Groovy 脚本。
todo_user.groovy
package script.db
databaseChangeLog(logicalFilePath: 'script/db/todo_user.groovy') {
changeSet(author: "your.email@email.com", id: "2020-02-03-todo_user") {
def weight = 1
if(helper.isSqlServer()){
weight = 2
} else if(helper.isOracle()){
weight = 3
}
if(helper.dbType().isSupportSequence()){
createSequence(sequenceName: 'todo_user_s', startValue:"1")
}
createTable(tableName: "todo_user", remarks: "用户表") {
column(name: "ID", type: "bigint(20)", autoIncrement: true , remarks: "表ID,主键,供其他表做外键") {constraints(primaryKey: true)}
column(name: "EMPLOYEE_NAME", type: "varchar(" + 30 * weight + ")", remarks: "员工名") {constraints(nullable:"false")}
column(name: "EMPLOYEE_NUMBER", type: "varchar(" + 30 * weight + ")", remarks: "员工编号") {constraints(nullable:"false")}
column(name: "EMAIL", type: "varchar(" + 60 * weight + ")", remarks: "邮箱")
column(name: "object_version_number", type: "bigint(20)", defaultValue:"1", remarks: "行版本号,用来处理锁") {constraints(nullable:"false")}
column(name: "creation_date", type: "datetime", defaultValueComputed:"CURRENT_TIMESTAMP", remarks: "") {constraints(nullable:"false")}
column(name: "created_by", type: "bigint(20)", defaultValue:"-1", remarks: "") {constraints(nullable:"false")}
column(name: "last_updated_by", type: "bigint(20)", defaultValue:"-1", remarks: "") {constraints(nullable:"false")}
column(name: "last_update_date", type: "datetime", defaultValueComputed:"CURRENT_TIMESTAMP", remarks: "") {constraints(nullable:"false")}
}
addUniqueConstraint(columnNames:"EMPLOYEE_NUMBER",tableName:"todo_user",constraintName: "TODO_USER_u1")
}
}
todo_task.groovy
package script.db
databaseChangeLog(logicalFilePath: 'script/db/todo_task.groovy') {
changeSet(author: "your.email@email.com", id: "2020-02-03-todo_task") {
def weight = 1
if(helper.isSqlServer()){
weight = 2
} else if(helper.isOracle()){
weight = 3
}
if(helper.dbType().isSupportSequence()){
createSequence(sequenceName: 'todo_task_s', startValue:"1")
}
createTable(tableName: "todo_task", remarks: "任务表") {
column(name: "ID", type: "bigint(20)", autoIncrement: true , remarks: "表ID,主键,供其他表做外键") {constraints(primaryKey: true)}
column(name: "EMPLOYEE_ID", type: "bigint(20)", remarks: "员工ID,TODO_USER.ID") {constraints(nullable:"false")}
column(name: "STATE", type: "varchar(" + 30 * weight + ")", remarks: "状态,值集:TODO.STATE") {constraints(nullable:"false")}
column(name: "TASK_NUMBER", type: "varchar(" + 60 * weight + ")", remarks: "任务编号") {constraints(nullable:"false")}
column(name: "TASK_DESCRIPTION", type: "varchar(" + 240 * weight + ")", remarks: "任务描述")
column(name: "TENANT_ID", type: "bigint(20)", remarks: "租户ID") {constraints(nullable:"false")}
column(name: "object_version_number", type: "bigint(20)", defaultValue:"1", remarks: "行版本号,用来处理锁") {constraints(nullable:"false")}
column(name: "creation_date", type: "datetime", defaultValueComputed:"CURRENT_TIMESTAMP", remarks: "") {constraints(nullable:"false")}
column(name: "created_by", type: "bigint(20)", defaultValue:"-1", remarks: "") {constraints(nullable:"false")}
column(name: "last_updated_by", type: "bigint(20)", defaultValue:"-1", remarks: "") {constraints(nullable:"false")}
column(name: "last_update_date", type: "datetime", defaultValueComputed:"CURRENT_TIMESTAMP", remarks: "") {constraints(nullable:"false")}
}
addUniqueConstraint(columnNames:"TASK_NUMBER,TENANT_ID",tableName:"todo_task",constraintName: "TODO_TASK_u1")
}
}
同步表结构
database-init.sh的使用
将表结构同步到其它环境时,可使用数据安装工具来安装数据库,将 groovy 脚本导出后,放到本地 hzero-resource
项目的 groovy 目录下,然后启动工具安装数据库即可。
- 将导出的 groovy 脚本放到
~/hzero-resource/groovy/todo_service
目录下 - 修改
~/hzero-resource/docs/mapping/service-mapping.xml
,加入 todo_service 的数据库信息
<service name="hzero-todo-service" filename="todo_service" schema="todo_service" description="TODO示例服务"/>
- 通过
database-init.sh
启动安装工具
- 访问安装工具页面,选择 todo_service 安装
- 生成 groovy 之后,如果表结构有变更,首先更新 Excel 表设计,再向 groovy 脚本中添加 changeSet 来添加变更记录,再使用安装工具来同步其它环境。
验证表结构
登录数据库,查询现有的表结构。
mysql> show tables;
+---------------------------------------+
| Tables_in_todo_service |
+---------------------------------------+
| DATABASECHANGELOG |
| DATABASECHANGELOGLOCK |
| TODO_TASK |
| TODO_USER |
+---------------------------------------+
5 rows in set (0.00 sec)
项目数据库配置
添加pom依赖
在pom.xml
文件中添加数据库操作相关依赖。
<dependency>
<groupId>org.hzero.starter</groupId>
<artifactId>hzero-starter-mybatis-mapper</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
配置application.yml
在项目的 application.yml
文件中添加数据库连接信息:
spring:
datasource:
url: ${SPRING_DATASOURCE_URL:jdbc:mysql://db.hzero.com.cn:3306/todo_service?useUnicode=true&characterEncoding=utf-8&useSSL=false}
username: ${SPRING_DATASOURCE_USERNAME:hzero}
password: ${SPRING_DATASOURCE_PASSWORD:hzero}
# MyBatis Mapper 扫描
mybatis:
mapperLocations: classpath*:/mapper/*.xml
configuration:
mapUnderscoreToCamelCase: true
启动项目
项目根目录下执行命令。项目正常启动,则数据库连接配置正常。
$ mvn clean spring-boot:run
(三)编写domain-领域模型层
前置条件
在开发之前,请确保
- 本地项目已经创建成功,详见
创建项目
- 数据库创建成功,详见
初始化数据库
介绍
此 demo
需涉及到 domain
层的 entity
、多 entity
的 service
、repository
接口类以及 infra
层的 repository
实现类。
编写entity
实体规范
- 实体继承
AuditDomain
,AuditDomain 包含标准的审计字段 - 使用
@Table
(javax.persistence.Table) 映射表名 - 使用
@ModifyAudit
注解标明在更新数据时需要更新 lastUpdateDate、lastUpdatedBy 两个审计字段 - 使用
@VersionAudit
注解标明在更新数据时需要更新版本号 objectVersionNumber - 使用
@ApiModel
注解说明实体含义,在 Swagger 文档上就可以看到实体说明。 - 实体主键使用
@Id
(javax.persistence.Id) 注解标注 - 对于自增张、序列(SEQUENCE)类型的主键,需要添加注解
@GeneratedValue
。 序列命名规范:表名_S
。例如:表SYS_USER
对应的序列为SYS_USER_S
。 - 实体字段使用
@ApiModelProperty
说明字段含义,在 Swagger 文档上可以看到字段说明。 - 非数据库字段使用
@Transient
注解标注,如果页面用到的非数据库字段比较多,建议使用 DTO 封装数据。 - 所有属性均为
private
属性,每一个属性需要生成对应的getter
、setter
方法。 - 字段名称应根据驼峰命名规则从数据库列名转换过来。例如:数据库列名为
USER_NAME
,则字段名为userName
,特殊字段名称,可以在字段在添加@Column(name = "xxx")
注解,指定数据库列名。 - 不使用基本类型,全部使用基本类型的包装类,如
Long
对应数据库中的INTEGER
,而不是使用long
。 - 数字类型主键统一采用
Long
。金额、数量 等精度严格浮点类型采用BigDecimal
(注意:BigDecimal 在计算、比较方面的特殊性) - 实体中可以包含一些实体自治的方法,这些方法通常用于对本身的属性做一些计算、操作等。
User.java
代码
package org.hzero.todo.domain.entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Table;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Pattern;
import com.fasterxml.jackson.annotation.JsonInclude;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import org.hibernate.validator.constraints.Length;
import io.choerodon.mybatis.annotation.ModifyAudit;
import io.choerodon.mybatis.annotation.VersionAudit;
import io.choerodon.mybatis.domain.AuditDomain;
import org.hzero.core.util.Regexs;
@ApiModel("用户信息") // Swagger 实体描述
@ModifyAudit //在类上使用,启用审计字段支持,实体类加上该注解后,插入和更新会启动对 lastUpdateDate、lastUpdatedBy 自维护字段支持
@VersionAudit //在类上使用,启用objectVersionNumber自维护支持,插入一条数据objectVersionNumber默认为1,每次update后objectVersionNumber自增1
@Table(name = "todo_user") // 表映射
@JsonInclude(JsonInclude.Include.NON_NULL) // 数据返回前端时,排除为空的字段
public class User extends AuditDomain { //AuditDomain包含5个自维护字段,使用@ModifyAudit和@VersionAudit的实体类要继承该类
@Id // 主键主键,注意是 javax.persistence.Id
@GeneratedValue //对于自增张、序列(SEQUENCE)类型的主键,需要添加该注解
private Long id;
@Length(max = 30) // 长度控制
@NotBlank // 非空控制
@ApiModelProperty("员工姓名") // Swagger 字段描述
private String employeeName;
@Length(max = 30)
@NotBlank
@Pattern(regexp = Regexs.CODE, message = "htdo.warn.user.numberFormatIncorrect") // 格式控制
@ApiModelProperty("员工编号")
private String employeeNumber;
@Length(max = 60)
@Pattern(regexp = Regexs.EMAIL, message = "htdo.warn.user.emailFormatIncorrect")
@ApiModelProperty("员工邮箱")
private String email;
// 省略 getter/setter 方法
}
Task.java
代码
package org.hzero.todo.domain.entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Table;
import javax.validation.constraints.NotBlank;
import com.fasterxml.jackson.annotation.JsonInclude;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import org.hibernate.validator.constraints.Length;
import io.choerodon.mybatis.annotation.ModifyAudit;
import io.choerodon.mybatis.annotation.VersionAudit;
import io.choerodon.mybatis.domain.AuditDomain;
@ApiModel("任务信息")
@ModifyAudit
@VersionAudit
@Table(name = "todo_task")
@JsonInclude(JsonInclude.Include.NON_NULL)
public class Task extends AuditDomain {
public static final String FIELD_ID = "id";
public static final String FIELD_EMPLOYEE_ID = "employeeId";
public static final String FIELD_STATE = "state";
public static final String FIELD_TASK_DESCRIPTION = "taskDescription";
@Id
@GeneratedValue
private Long id;
@NotNull(message = "用户ID不能为空")
@ApiModelProperty("用户ID")
private Long employeeId;
@ApiModelProperty("任务状态")
private String state;
@ApiModelProperty("任务编号")
private String taskNumber;
@Length(max = 240)
@ApiModelProperty("任务描述")
private String taskDescription;
@NotNull
@ApiModelProperty("租户ID")
private Long tenantId;
@Transient
@ApiModelProperty("员工编号")
private String employeeNumber;
@Transient
@ApiModelProperty("员工姓名")
private String employeeName;
/**
* 生成任务编号
*/
public void generateTaskNumber() {
this.taskNumber = UUID.randomUUID().toString().replace("-", "");
}
// 省略 getter/setter
}
编写Repository
Repository
接口类
-
Repository
接口类定义了数据操作的一系列接口,并不提供实现,具体实现需要通过Repository
实现层提供。创建在项目模块的xxx.domain.repository
包下。 - 每一个
Repository
对应一个entity
,命名为entity
类名尾缀替换为Repository
。如:TaskRepository
对应Task
。 -
Repository
继承BaseRepository<T>
,BaseRepository
封装了基本的资源库增删改查、批量增删改等。单表查询基本就不需要我们再写方法了。
UserRepository.java
代码
package org.hzero.todo.domain.repository;
import org.hzero.mybatis.base.BaseRepository;
import org.hzero.todo.domain.entity.User;
/**
* 用户资源库
*/
public interface UserRepository extends BaseRepository<User> {
}
TaskRepository.java
代码
package org.hzero.todo.domain.repository;
import java.util.List;
import io.choerodon.core.domain.Page;
import io.choerodon.mybatis.pagehelper.domain.PageRequest;
import org.hzero.mybatis.base.BaseRepository;
import org.hzero.todo.domain.entity.Task;
/**
* 任务资源库
*/
public interface TaskRepository extends BaseRepository<Task> {
/**
* 分页查询任务
*
* @param task Task
* @param pageRequest 分页参数
* @return Page<Task>
*/
Page<Task> pageTask(Task task, PageRequest pageRequest);
/**
* 根据员工ID查询任务
*
* @param employeeId 员工ID
* @return List<Task>
*/
List<Task> selectByEmployeeId(Long employeeId);
/**
* 根据任务编号查询任务详细(包含员工姓名)
*
* @param taskNumber 任务编号
* @return Task
*/
Task selectDetailByTaskNumber(String taskNumber);
}
编写Service
Domain Service
接口类
- 领域层的
Service
不是程序必要组成,如没有特殊或复杂的业务逻辑处理,则可以不需要领域服务类。 - 领域层的
Service
是业务软件的核心,是反应多个领域模型的业务情况的具体实现,是领域模型对外提供的实际服务。 -
Service
接口类定义了业务操作的一系列接口,并不提供实现,具体实现需要通过服务实现层提供,所以属于供应方的服务接口层。创建在项目模块 的xxx.domain.service
包下。 - 每一个
Service
对应多个entity
类,因需要与app
层service
区分,所以规定命名为I + 涉及主要entity类名 + Service
。如:ITaskService
。
Service 实现类
-
Service
接口的具体实现通过服务实现层提供,所以属于供应方的服务实现层。创建在项目模块的xxx.domian.service.impl
包下。 - 实现类,如无特殊情况,需要用
@Service
标注,以自动扫描注册
(四)编写infra-基础设置层
前置条件
在开发之前,请确保
- 本地项目已经创建成功,详见
创建项目
- 数据库创建成功,详见
初始化数据库
介绍
此 demo
需涉及到 infra
层的 mapper
类及 repository
实现类。
Mapper
mapper
接口类
-
mapper
接口类即为传统意义上的DAO
,但与interface
不同,mapper
本身就是对数据访问的具体实现,所以属于供应方的服务实现层。创建在 项目模块 的xxx.infra.mapper
包下。 - 每一个
mapper
接口类封装了对数据库表的操作,每一个mapper
对应一个实体
类,命名为实体
类名尾缀替换为Mapper
。如:TaskMapper
对应实体
为Task
类。 - 基础的
CRUD
操作不需要再次实现,通过继承BaseMapper<T>
类实现。其中T
为 对应实体
的泛型。 - 复杂的数据库操作需要定义具体的接口方法。
mapper.xml
-
Mapper
的xml
文件 是数据库的的具体映射,与Mapper
接口同级,创建在 项目模块resources
目录下的mapper
目录下。 -
Mapper
的xml
文件,与Mapper
接口对应。所以命名与Mapper
接口类相同。 -
Mapper
的xml
文件非必须,由于继承BaseMapper类后基本的CRUD
不需要进行配置,所以只有CRUD
操作时不需要创建对应的xml
文件。 - 对于自定义的数据库方法,需要创建对应的
mapper.xml
文件。 -
Mapper
的xml
中的操作id
对应Mapper
接口类的方法名。
UserMapper.java
代码
package org.hzero.todo.infra.mapper;
import io.choerodon.mybatis.common.BaseMapper;
import org.hzero.todo.domain.entity.User;
/**
* UserMapper
*/
public interface UserMapper extends BaseMapper<User> {
}
TaskMapper.java
代码
package org.hzero.todo.infra.mapper;
import java.util.List;
import io.choerodon.mybatis.common.BaseMapper;
import org.hzero.todo.domain.entity.Task;
/**
* TaskMapper
*/
public interface TaskMapper extends BaseMapper<Task> {
/**
* 查询任务
*
* @param params 任务查询参数
* @return Task
*/
List<Task> selectTask(Task params);
}
UserMapper.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="org.hzero.todo.infra.mapper.UserMapper">
</mapper>
TaskMapper.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="org.hzero.todo.infra.mapper.TaskMapper">
<select id="selectTask" resultType="org.hzero.todo.domain.entity.Task">
select
tt.id,
tt.employee_id,
tt.state,
tt.task_number,
tt.task_description,
tt.tenant_id,
tt.object_version_number,
tu.employee_name,
tu.employee_number
from todo_task tt join todo_user tu on tt.employee_id = tu.id
<where>
<if test="taskNumber != null and taskNumber != ''">
and tt.task_number = #{taskNumber}
</if>
<if test="taskDescription != null and taskDescription != ''">
<bind name="taskDescriptionLike" value="'%' + taskDescription + '%'" />
and tt.task_description like #{taskDescriptionLike}
</if>
</where>
</select>
</mapper>
Repository
-
Repository
接口的具体实现。创建在项目模块的xxx.infra.repository.impl
包下。 - 每一个
Repository
实现类对应一个Repository
接口类,命名为Repository 接口类名 + Impl
。如:TaskRepositoryImpl
对应TaskRepository
。 -
Repository
继承BaseRepositoryImpl<T>
类,该类是BaseRepository<T>
的实现。 - 需要通过
@Component
纳入spring
管理。
UserRepositoryImpl.java
代码
package org.hzero.todo.infra.repository.impl;
import org.springframework.stereotype.Component;
import org.hzero.mybatis.base.impl.BaseRepositoryImpl;
import org.hzero.todo.domain.entity.User;
import org.hzero.todo.domain.repository.UserRepository;
/**
* 用户资源库实现
*/
@Component
public class UserRepositoryImpl extends BaseRepositoryImpl<User> implements UserRepository {
}
TaskRepositoryImpl.java
代码
package org.hzero.todo.infra.repository.impl;
import java.util.List;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.stereotype.Component;
import io.choerodon.core.domain.Page;
import io.choerodon.mybatis.pagehelper.PageHelper;
import io.choerodon.mybatis.pagehelper.domain.PageRequest;
import org.hzero.mybatis.base.impl.BaseRepositoryImpl;
import org.hzero.mybatis.common.Criteria;
import org.hzero.todo.domain.entity.Task;
import org.hzero.todo.domain.repository.TaskRepository;
import org.hzero.todo.infra.mapper.TaskMapper;
/**
* 任务资源库实现
*/
@Component
public class TaskRepositoryImpl extends BaseRepositoryImpl<Task> implements TaskRepository {
private final TaskMapper taskMapper;
public TaskRepositoryImpl(TaskMapper taskMapper) {
this.taskMapper = taskMapper;
}
@Override
public Page<Task> pageTask(Task task, PageRequest pageRequest) {
return PageHelper.doPage(pageRequest, () -> taskMapper.selectTask(task));
}
@Override
public List<Task> selectByEmployeeId(Long employeeId) {
Task task = new Task();
task.setEmployeeId(employeeId);
return this.selectOptional(task, new Criteria()
.select(Task.FIELD_ID, Task.FIELD_EMPLOYEE_ID, Task.FIELD_STATE, Task.FIELD_TASK_DESCRIPTION)
.where(Task.FIELD_EMPLOYEE_ID)
);
}
@Override
public Task selectDetailByTaskNumber(String taskNumber) {
Task params = new Task();
params.setTaskNumber(taskNumber);
List<Task> tasks = taskMapper.selectTask(params);
return CollectionUtils.isNotEmpty(tasks) ? tasks.get(0) : null;
}
}
(五)编写app-应用层
前置条件
在开发之前,请确保
- 本地项目已经创建成功,详见
创建项目
- 数据库创建成功,详见
初始化数据库
介绍
此
demo
需涉及到app
层的service
接口类与其实现类。特别说明 为了Demo的完整性,这里使用了
app
层的服务类,如业务十分简单,api
层亦可直接调用通用的repository
相关方法,不需要app
服务
service
调用领域对象或服务来解决问题,应用层Service主要有以下特性:
- 负责事务处理,所以事务的注解可以在这一层的
service
中使用。 - 只处理非业务逻辑,重点是调度业务处理流程。业务逻辑处理一定要放在领域层处理。
- 不做单元测试,只做验收测试。
- 可能会有比较多的依赖组件(领域服务),使用
field
注入依赖的组件。
Service
接口类
-
Service
接口类定义了业务操作的一系列接口,并不提供实现,具体实现需要通过服务实现层提供,所以属于供应方的服务接口层。创建在项目模块的xxx.app.service
包下。
UserService.java
代码
package org.hzero.todo.app.service;
import org.hzero.todo.domain.entity.User;
/**
* 用户应用服务
*/
public interface UserService {
/**
* 创建用户
*
* @param user User
* @return User
*/
User create(User user);
/**
* 删除用户(同时删除任务)
*
* @param userId 用户ID
*/
void delete(Long userId);
}
TaskService.java
代码
package org.hzero.todo.app.service;
import org.hzero.todo.domain.entity.Task;
/**
* 任务应用服务
*/
public interface TaskService {
/**
* 创建任务
*
* @param task 任务
* @return Task
*/
Task create(Task task);
/**
* 更新任务
*
* @param task 任务
* @return Task
*/
Task update(Task task);
/**
* 根据任务编号删除
*
* @param taskNumber 任务编号
*/
void deleteByTaskNumber(String taskNumber);
}
Service
实现类
-
Service
接口的具体实现通过服务实现层提供,所以属于供应方的服务实现层。创建在项目模块的xxx.app.service.impl
包下。 - 实现类,需要用
@Service
标注
UserServiceImpl.java
代码
package org.hzero.todo.app.service.impl;
import java.util.List;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import io.choerodon.core.exception.CommonException;
import org.hzero.todo.app.service.UserService;
import org.hzero.todo.domain.entity.Task;
import org.hzero.todo.domain.entity.User;
import org.hzero.todo.domain.repository.TaskRepository;
import org.hzero.todo.domain.repository.UserRepository;
/**
* 用户应用服务实现
*/
@Service
public class UserServiceImpl implements UserService {
private final TaskRepository taskRepository;
private final UserRepository userRepository;
public UserServiceImpl(TaskRepository taskRepository, UserRepository userRepository) {
this.taskRepository = taskRepository;
this.userRepository = userRepository;
}
@Override
@Transactional(rollbackFor = Exception.class)
public User create(User user) {
userRepository.insert(user);
return user;
}
@Override
@Transactional(rollbackFor = Exception.class)
public void delete(Long userId) {
User exist = userRepository.selectByPrimaryKey(userId);
if (exist == null) {
throw new CommonException("htdo.warn.user.notFound");
}
// 删除用户
userRepository.deleteByPrimaryKey(userId);
// 删除与用户关联的任务
List<Task> tasks = taskRepository.selectByEmployeeId(userId);
if (CollectionUtils.isNotEmpty(tasks)) {
taskRepository.batchDelete(tasks);
}
}
}
TaskServiceImpl.java
代码
package org.hzero.todo.app.service.impl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import io.choerodon.core.exception.CommonException;
import org.hzero.todo.app.service.TaskService;
import org.hzero.todo.domain.entity.Task;
import org.hzero.todo.domain.repository.TaskRepository;
/**
* 任务应用服务实现
*/
@Service
public class TaskServiceImpl implements TaskService {
@Autowired
private TaskRepository taskRepository;
@Override
@Transactional(rollbackFor = Exception.class)
public Task create(Task task) {
// 生成任务编号
task.generateTaskNumber();
// 插入数据
taskRepository.insert(task);
return task;
}
@Override
@Transactional(rollbackFor = Exception.class)
public Task update(Task task) {
Task exist = taskRepository.selectByPrimaryKey(task);
if (exist == null) {
throw new CommonException("htdo.warn.task.notFound");
}
// 更新指定字段
taskRepository.updateOptional(task,
Task.FIELD_STATE,
Task.FIELD_TASK_DESCRIPTION
);
return task;
}
@Override
@Transactional(rollbackFor = Exception.class)
public void deleteByTaskNumber(String taskNumber) {
Task task = new Task();
task.setTaskNumber(taskNumber);
taskRepository.delete(task);
}
}
(六)编写api-展现层
前置条件
在开发之前,请确保
- 本地项目已经创建成功,详见
创建项目
- 数据库创建成功,详见
初始化数据库
介绍
此 demo
需涉及 api
层的 controller
。
编写 Controller
-
Controller
负责对Model
和View
的处理,创建在项目模块的xxx.api.controller.v1
包下。如xxx.api.controller.v1
。 - 需要通过
@Controller
指定该类为一个Controller
类。
UserController.java
代码
package org.hzero.todo.api.controller.v1;
import ....;
/**
* 用户接口
*/
@Api(tags = SwaggerApiConfig.USER)
@RestController("userController.v1")
@RequestMapping("/v1/users")
public class UserController extends BaseController {
private final UserService userService;
private final UserRepository userRepository;
public UserController(UserService userService, UserRepository userRepository) {
this.userService = userService;
this.userRepository = userRepository;
}
@Permission(level = ResourceLevel.SITE)
@ApiOperation(value = "分页查询用户")
@GetMapping
public ResponseEntity<Page<User>> list(User user, PageRequest pageRequest) {
return Results.success(userRepository.pageAndSort(pageRequest, user));
}
@Permission(level = ResourceLevel.SITE)
@ApiOperation(value = "创建 todo 用户")
@PostMapping
public ResponseEntity<User> create(@RequestBody User user) {
// 简单数据校验
this.validObject(user);
// 创建用户
return Results.success(userService.create(user));
}
@Permission(level = ResourceLevel.SITE)
@ApiOperation(value = "删除 todo 用户")
@DeleteMapping
public ResponseEntity<User> delete(@RequestBody User user) {
// 数据防篡改校验
SecurityTokenHelper.validToken(user);
// 删除用户
userService.delete(user.getId());
return Results.success();
}
}
TaskController.java
代码
package org.hzero.todo.api.controller.v1;
import ....;
/**
* 任务接口(全是租户级接口)
*/
@Api(tags = SwaggerApiConfig.TASK)
@RestController("taskController.v1")
@RequestMapping("/v1/{organizationId}/tasks")
public class TaskController extends BaseController {
private final TaskService taskService;
private final TaskRepository taskRepository;
public TaskController(TaskService taskService, TaskRepository taskRepository) {
this.taskService = taskService;
this.taskRepository = taskRepository;
}
/**
* 注意分页参数是 io.choerodon.mybatis.pagehelper.domain.PageRequest;
*/
@Permission(level = ResourceLevel.ORGANIZATION)
@ApiOperation(value = "根据taskNumber分页查询task")
@GetMapping
public ResponseEntity<Page<Task>> list(@PathVariable("organizationId") Long tenantId, Task task, PageRequest pageRequest) {
task.setTenantId(tenantId);
return Results.success(taskRepository.pageTask(task, pageRequest));
}
@Permission(level = ResourceLevel.ORGANIZATION)
@ApiOperation(value = "创建task")
@PostMapping
public ResponseEntity<Task> create(@PathVariable("organizationId") Long tenantId, @RequestBody Task task) {
task.setTenantId(tenantId);
// 简单数据校验
this.validObject(task);
return Results.success(taskService.create(task));
}
@Permission(level = ResourceLevel.ORGANIZATION)
@ApiOperation(value = "更新task")
@PutMapping
public ResponseEntity<Task> update(@PathVariable("organizationId") Long tenantId, @RequestBody Task task) {
// 简单数据校验
this.validObject(task);
// 数据防篡改校验
SecurityTokenHelper.validToken(task);
return Results.success(taskService.update(task));
}
@Permission(level = ResourceLevel.ORGANIZATION)
@ApiOperation(value = "根据taskNumber查询task")
@ApiImplicitParams({
@ApiImplicitParam(value = "任务编号", paramType = "string")
})
@GetMapping("/{taskNumber}")
public ResponseEntity<Task> query(@PathVariable Long organizationId, @PathVariable String taskNumber) {
return Results.success(taskRepository.selectDetailByTaskNumber(taskNumber));
}
@Permission(level = ResourceLevel.ORGANIZATION)
@ApiOperation(value = "根据taskNumber删除task")
@DeleteMapping("/{taskNumber}")
public void delete(@PathVariable Long organizationId, @PathVariable @ApiParam(value = "任务编号") String taskNumber) {
taskService.deleteByTaskNumber(taskNumber);
}
}
编写 SwaggerApiConfig
- Controller 在 Swagger 上的描述需要定义在配置文件中
- 一般会把配置相关的建在
config
包下,如xxx.config
-
SwaggerApiConfig
需要使用@Configuration
注解标注
SwaggerApiConfig.java
代码
package org.hzero.todo.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.service.Tag;
import springfox.documentation.spring.web.plugins.Docket;
/**
* Swagger Api 描述配置
*/
@Configuration
public class SwaggerApiConfig {
public static final String USER = "User";
public static final String TASK = "Task";
@Autowired
public SwaggerApiConfig(Docket docket) {
docket.tags(
new Tag(USER, "用户信息"),
new Tag(TASK, "任务信息")
);
}
}
Controller
类相关标签
-
@RestController
,是一个组合注解,是@ResponseBody
和@Controller
的组合。 @Permission
,设置API访问权限,常用有三种属性
- level :设置访问资源层级,包括
site
,organization
两种层级 - permissionLogin :设置是否需要登录访问
- permissionPublic :设置任意访问。
-
@ApiOperation
,显示在swagger ui上的接口注释,同时与该接口对应的权限表中的描述字段对应(iam_permission.description) -
@GetMapping
,是@RequestMapping(mathod = RequestMethod.GET)
的缩写,@PostMapping
等同理。 -
@Api(tags = SwaggerApiConfig.TASK)
,在类上对类进行说明,显示在 Swagger 文档上 -
@ApiImplicitParams
,在方法上对方法参数进行说明,显示在 Swagger 文档上 -
@ApiParam
,在方法参数上对参数进行说明,显示在 Swagger 文档上
编写DTO
-
DTO
类用来封装用户请求的数据信息,可以用来屏蔽一些程序交互细节。 - 创建在 项目模块 的
xxx.api.dto
包下,DTO不是必要选项,需要根据需求自行决定。
文档地址