Compare commits
10 Commits
89cdecef3d
...
ad56478d9b
| Author | SHA1 | Date | |
|---|---|---|---|
| ad56478d9b | |||
| c8501e7baf | |||
| 895ce7ee82 | |||
| 6d8a1b26c1 | |||
| 6e2629a8b5 | |||
| 2469c1bcf3 | |||
| 3d1f96dd41 | |||
| 42f1d896af | |||
| 50043157df | |||
| 84d9b63707 |
61
pom.xml
61
pom.xml
@@ -15,7 +15,7 @@
|
||||
|
||||
<spring-boot.version>3.4.3</spring-boot.version>
|
||||
<spring-cloud.version>2024.0.1</spring-cloud.version>
|
||||
<eclipse-collections.version>11.1.0</eclipse-collections.version>
|
||||
<querydsl.version>7.0</querydsl.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
@@ -30,7 +30,17 @@
|
||||
<dependency>
|
||||
<groupId>com.blinkfox</groupId>
|
||||
<artifactId>fenix-spring-boot-starter</artifactId>
|
||||
<version>3.0.0</version>
|
||||
<version>3.1.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.github.openfeign.querydsl</groupId>
|
||||
<artifactId>querydsl-jpa</artifactId>
|
||||
<version>${querydsl.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
@@ -60,6 +70,53 @@
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-source-plugin</artifactId>
|
||||
<version>3.3.1</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<phase>package</phase>
|
||||
<goals>
|
||||
<goal>jar-no-fork</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.14.0</version>
|
||||
<configuration>
|
||||
<annotationProcessorPaths>
|
||||
<path>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<version>1.18.36</version>
|
||||
</path>
|
||||
<path>
|
||||
<groupId>io.github.openfeign.querydsl</groupId>
|
||||
<artifactId>querydsl-apt</artifactId>
|
||||
<version>${querydsl.version}</version>
|
||||
<classifier>jpa</classifier>
|
||||
</path>
|
||||
<path>
|
||||
<groupId>jakarta.persistence</groupId>
|
||||
<artifactId>jakarta.persistence-api</artifactId>
|
||||
<version>3.2.0</version>
|
||||
</path>
|
||||
</annotationProcessorPaths>
|
||||
<compilerArgs>
|
||||
<arg>-Aquerydsl.entityAccessors=true</arg>
|
||||
<arg>-Aquerydsl.createDefaultVariable=true</arg>
|
||||
</compilerArgs>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
<distributionManagement>
|
||||
<repository>
|
||||
<id>${releases.id}</id>
|
||||
|
||||
@@ -2,8 +2,28 @@ package com.lanyuanxiaoyao.service.template.controller;
|
||||
|
||||
import com.lanyuanxiaoyao.service.template.controller.response.GlobalResponse;
|
||||
|
||||
/**
|
||||
* 详情控制器接口,用于定义统一的获取实体详情的接口规范
|
||||
*
|
||||
* <p>
|
||||
* 前端传入的JSON格式示例:
|
||||
* <pre>
|
||||
* GET /detail/1
|
||||
* </pre>
|
||||
* </p>
|
||||
*
|
||||
* @param <DETAIL_ITEM> 详情实体类型
|
||||
* @author lanyuanxiaoyao
|
||||
*/
|
||||
public interface DetailController<DETAIL_ITEM> {
|
||||
String DETAIL = "/detail/{id}";
|
||||
|
||||
/**
|
||||
* 根据ID获取实体详情
|
||||
*
|
||||
* @param id 实体ID
|
||||
* @return GlobalResponse<DETAIL_ITEM> 返回实体详情
|
||||
* @throws Exception 查询过程中可能抛出的异常
|
||||
*/
|
||||
GlobalResponse<DETAIL_ITEM> detail(Long id) throws Exception;
|
||||
}
|
||||
}
|
||||
@@ -2,10 +2,77 @@ package com.lanyuanxiaoyao.service.template.controller;
|
||||
|
||||
import com.lanyuanxiaoyao.service.template.controller.response.GlobalCrudResponse;
|
||||
|
||||
/**
|
||||
* 列表控制器接口,用于定义统一的获取实体列表的接口规范
|
||||
*
|
||||
* <p>
|
||||
* 前端传入的JSON格式示例:
|
||||
* <pre>
|
||||
* {
|
||||
* "query": {
|
||||
* "equal": {
|
||||
* "status": "ACTIVE"
|
||||
* },
|
||||
* "like": {
|
||||
* "name": "关键字"
|
||||
* }
|
||||
* },
|
||||
* "sort": [
|
||||
* {
|
||||
* "column": "createTime",
|
||||
* "direction": "DESC"
|
||||
* }
|
||||
* ],
|
||||
* "page": {
|
||||
* "index": 0,
|
||||
* "size": 10
|
||||
* }
|
||||
* }
|
||||
* </pre>
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* 支持的查询条件说明:
|
||||
* <ul>
|
||||
* <li>nullEqual: 指定字段值为null的条件列表</li>
|
||||
* <li>notNullEqual: 指定字段值不为null的条件列表</li>
|
||||
* <li>empty: 指定字段值为空的条件列表(如空字符串、空集合等)</li>
|
||||
* <li>notEmpty: 指定字段值不为空的条件列表</li>
|
||||
* <li>equal: 指定字段值相等的条件映射(字段名 -> 值)</li>
|
||||
* <li>notEqual: 指定字段值不相等的条件映射(字段名 -> 值)</li>
|
||||
* <li>like: 指定字段模糊匹配的条件映射(字段名 -> 匹配值)</li>
|
||||
* <li>notLike: 指定字段不模糊匹配的条件映射(字段名 -> 匹配值)</li>
|
||||
* <li>great: 指定字段大于条件的映射(字段名 -> 值)</li>
|
||||
* <li>less: 指定字段小于条件的映射(字段名 -> 值)</li>
|
||||
* <li>greatEqual: 指定字段大于等于条件的映射(字段名 -> 值)</li>
|
||||
* <li>lessEqual: 指定字段小于等于条件的映射(字段名 -> 值)</li>
|
||||
* <li>in: 指定字段值在指定范围内的条件映射(字段名 -> 值列表)</li>
|
||||
* <li>notIn: 指定字段值不在指定范围内的条件映射(字段名 -> 值列表)</li>
|
||||
* <li>between: 指定字段值在指定区间内的条件映射(字段名 -> 区间范围)</li>
|
||||
* <li>notBetween: 指定字段值不在指定区间内的条件映射(字段名 -> 区间范围)</li>
|
||||
* </ul>
|
||||
* </p>
|
||||
*
|
||||
* @param <LIST_ITEM> 列表项的实体类型
|
||||
* @author lanyuanxiaoyao
|
||||
*/
|
||||
public interface ListController<LIST_ITEM> {
|
||||
String LIST = "/list";
|
||||
|
||||
/**
|
||||
* 获取所有实体列表
|
||||
*
|
||||
* @return GlobalCrudResponse<LIST_ITEM> 返回实体列表
|
||||
* @throws Exception 查询过程中可能抛出的异常
|
||||
*/
|
||||
GlobalCrudResponse<LIST_ITEM> list() throws Exception;
|
||||
|
||||
/**
|
||||
* 根据查询条件获取实体列表
|
||||
*
|
||||
* @param query 查询条件对象
|
||||
* @return GlobalCrudResponse<LIST_ITEM> 返回符合条件的实体列表
|
||||
* @throws Exception 查询过程中可能抛出的异常
|
||||
*/
|
||||
GlobalCrudResponse<LIST_ITEM> list(Query query) throws Exception;
|
||||
}
|
||||
}
|
||||
@@ -2,303 +2,222 @@ package com.lanyuanxiaoyao.service.template.controller;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import lombok.ToString;
|
||||
|
||||
/**
|
||||
* 查询条件封装类,用于构建复杂的查询条件
|
||||
* 包含查询条件、排序条件和分页条件
|
||||
*
|
||||
* <p>前端传入的JSON格式示例:</p>
|
||||
* <pre>
|
||||
* {
|
||||
* "query": {
|
||||
* "equal": {
|
||||
* "name": "张三"
|
||||
* },
|
||||
* "like": {
|
||||
* "address": "%北京%"
|
||||
* },
|
||||
* "greatEqual": {
|
||||
* "age": 18
|
||||
* },
|
||||
* "less": {
|
||||
* "age": 60
|
||||
* },
|
||||
* "between": {
|
||||
* "salary": {
|
||||
* "start": 5000,
|
||||
* "end": 10000
|
||||
* }
|
||||
* }
|
||||
* },
|
||||
* "sort": [
|
||||
* {
|
||||
* "column": "createTime",
|
||||
* "direction": "DESC"
|
||||
* }
|
||||
* ],
|
||||
* "page": {
|
||||
* "index": 0,
|
||||
* "size": 10
|
||||
* }
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
* <p>查询条件说明:</p>
|
||||
* <ul>
|
||||
* <li>nullEqual: 字段值为null的条件</li>
|
||||
* <li>notNullEqual: 字段值不为null的条件</li>
|
||||
* <li>empty: 字段值为空的条件</li>
|
||||
* <li>notEmpty: 字段值不为空的条件</li>
|
||||
* <li>equal: 字段值相等的条件</li>
|
||||
* <li>notEqual: 字段值不相等的条件</li>
|
||||
* <li>like: 字段值模糊匹配的条件</li>
|
||||
* <li>notLike: 字段值不模糊匹配的条件</li>
|
||||
* <li>great: 字段值大于的条件</li>
|
||||
* <li>less: 字段值小于的条件</li>
|
||||
* <li>greatEqual: 字段值大于等于的条件</li>
|
||||
* <li>lessEqual: 字段值小于等于的条件</li>
|
||||
* <li>in: 字段值在指定范围内的条件</li>
|
||||
* <li>notIn: 字段值不在指定范围内的条件</li>
|
||||
* <li>between: 字段值在指定区间内的条件</li>
|
||||
* <li>notBetween: 字段值不在指定区间内的条件</li>
|
||||
* </ul>
|
||||
*/
|
||||
@Setter
|
||||
@Getter
|
||||
@ToString
|
||||
public class Query {
|
||||
/**
|
||||
* 查询条件
|
||||
*/
|
||||
private Queryable query;
|
||||
/**
|
||||
* 排序条件列表
|
||||
*/
|
||||
private List<Sortable> sort;
|
||||
/**
|
||||
* 分页条件
|
||||
*/
|
||||
private Pageable page;
|
||||
|
||||
public Queryable getQuery() {
|
||||
return query;
|
||||
}
|
||||
|
||||
public void setQuery(Queryable query) {
|
||||
this.query = query;
|
||||
}
|
||||
|
||||
public List<Sortable> getSort() {
|
||||
return sort;
|
||||
}
|
||||
|
||||
public void setSort(List<Sortable> sort) {
|
||||
this.sort = sort;
|
||||
}
|
||||
|
||||
public Pageable getPage() {
|
||||
return page;
|
||||
}
|
||||
|
||||
public void setPage(Pageable page) {
|
||||
this.page = page;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Query{" +
|
||||
"query=" + query +
|
||||
", sort=" + sort +
|
||||
", page=" + page +
|
||||
'}';
|
||||
}
|
||||
|
||||
/**
|
||||
* 可查询条件类,封装各种查询条件
|
||||
*/
|
||||
@Setter
|
||||
@Getter
|
||||
@ToString
|
||||
public static class Queryable {
|
||||
/**
|
||||
* 指定字段值为null的条件列表
|
||||
*/
|
||||
private List<String> nullEqual;
|
||||
/**
|
||||
* 指定字段值不为null的条件列表
|
||||
*/
|
||||
private List<String> notNullEqual;
|
||||
/**
|
||||
* 指定字段值为空的条件列表(如空字符串、空集合等)
|
||||
*/
|
||||
private List<String> empty;
|
||||
/**
|
||||
* 指定字段值不为空的条件列表
|
||||
*/
|
||||
private List<String> notEmpty;
|
||||
/**
|
||||
* 指定字段值相等的条件映射(字段名 -> 值)
|
||||
*/
|
||||
private Map<String, Object> equal;
|
||||
/**
|
||||
* 指定字段值不相等的条件映射(字段名 -> 值)
|
||||
*/
|
||||
private Map<String, Object> notEqual;
|
||||
/**
|
||||
* 指定字段模糊匹配的条件映射(字段名 -> 匹配值)
|
||||
*/
|
||||
private Map<String, String> like;
|
||||
/**
|
||||
* 指定字段不模糊匹配的条件映射(字段名 -> 匹配值)
|
||||
*/
|
||||
private Map<String, String> notLike;
|
||||
private Map<String, Comparable<Object>> great;
|
||||
private Map<String, Comparable<Object>> less;
|
||||
private Map<String, Comparable<Object>> greatEqual;
|
||||
private Map<String, Comparable<Object>> lessEqual;
|
||||
/**
|
||||
* 指定字段大于条件的映射(字段名 -> 值)
|
||||
*/
|
||||
private Map<String, Object> great;
|
||||
/**
|
||||
* 指定字段小于条件的映射(字段名 -> 值)
|
||||
*/
|
||||
private Map<String, Object> less;
|
||||
/**
|
||||
* 指定字段大于等于条件的映射(字段名 -> 值)
|
||||
*/
|
||||
private Map<String, Object> greatEqual;
|
||||
/**
|
||||
* 指定字段小于等于条件的映射(字段名 -> 值)
|
||||
*/
|
||||
private Map<String, Object> lessEqual;
|
||||
/**
|
||||
* 指定字段值在指定范围内的条件映射(字段名 -> 值列表)
|
||||
*/
|
||||
private Map<String, List<Object>> in;
|
||||
/**
|
||||
* 指定字段值不在指定范围内的条件映射(字段名 -> 值列表)
|
||||
*/
|
||||
private Map<String, List<Object>> notIn;
|
||||
/**
|
||||
* 指定字段值在指定区间内的条件映射(字段名 -> 区间范围)
|
||||
*/
|
||||
private Map<String, Between> between;
|
||||
/**
|
||||
* 指定字段值不在指定区间内的条件映射(字段名 -> 区间范围)
|
||||
*/
|
||||
private Map<String, Between> notBetween;
|
||||
|
||||
public List<String> getNullEqual() {
|
||||
return nullEqual;
|
||||
}
|
||||
|
||||
public void setNullEqual(List<String> nullEqual) {
|
||||
this.nullEqual = nullEqual;
|
||||
}
|
||||
|
||||
public List<String> getNotNullEqual() {
|
||||
return notNullEqual;
|
||||
}
|
||||
|
||||
public void setNotNullEqual(List<String> notNullEqual) {
|
||||
this.notNullEqual = notNullEqual;
|
||||
}
|
||||
|
||||
public List<String> getEmpty() {
|
||||
return empty;
|
||||
}
|
||||
|
||||
public void setEmpty(List<String> empty) {
|
||||
this.empty = empty;
|
||||
}
|
||||
|
||||
public List<String> getNotEmpty() {
|
||||
return notEmpty;
|
||||
}
|
||||
|
||||
public void setNotEmpty(List<String> notEmpty) {
|
||||
this.notEmpty = notEmpty;
|
||||
}
|
||||
|
||||
public Map<String, Object> getEqual() {
|
||||
return equal;
|
||||
}
|
||||
|
||||
public void setEqual(Map<String, Object> equal) {
|
||||
this.equal = equal;
|
||||
}
|
||||
|
||||
public Map<String, Object> getNotEqual() {
|
||||
return notEqual;
|
||||
}
|
||||
|
||||
public void setNotEqual(Map<String, Object> notEqual) {
|
||||
this.notEqual = notEqual;
|
||||
}
|
||||
|
||||
public Map<String, String> getLike() {
|
||||
return like;
|
||||
}
|
||||
|
||||
public void setLike(Map<String, String> like) {
|
||||
this.like = like;
|
||||
}
|
||||
|
||||
public Map<String, String> getNotLike() {
|
||||
return notLike;
|
||||
}
|
||||
|
||||
public void setNotLike(Map<String, String> notLike) {
|
||||
this.notLike = notLike;
|
||||
}
|
||||
|
||||
public Map<String, Comparable<Object>> getGreat() {
|
||||
return great;
|
||||
}
|
||||
|
||||
public void setGreat(Map<String, Comparable<Object>> great) {
|
||||
this.great = great;
|
||||
}
|
||||
|
||||
public Map<String, Comparable<Object>> getLess() {
|
||||
return less;
|
||||
}
|
||||
|
||||
public void setLess(Map<String, Comparable<Object>> less) {
|
||||
this.less = less;
|
||||
}
|
||||
|
||||
public Map<String, Comparable<Object>> getGreatEqual() {
|
||||
return greatEqual;
|
||||
}
|
||||
|
||||
public void setGreatEqual(Map<String, Comparable<Object>> greatEqual) {
|
||||
this.greatEqual = greatEqual;
|
||||
}
|
||||
|
||||
public Map<String, Comparable<Object>> getLessEqual() {
|
||||
return lessEqual;
|
||||
}
|
||||
|
||||
public void setLessEqual(Map<String, Comparable<Object>> lessEqual) {
|
||||
this.lessEqual = lessEqual;
|
||||
}
|
||||
|
||||
public Map<String, List<Object>> getIn() {
|
||||
return in;
|
||||
}
|
||||
|
||||
public void setIn(Map<String, List<Object>> in) {
|
||||
this.in = in;
|
||||
}
|
||||
|
||||
public Map<String, List<Object>> getNotIn() {
|
||||
return notIn;
|
||||
}
|
||||
|
||||
public void setNotIn(Map<String, List<Object>> notIn) {
|
||||
this.notIn = notIn;
|
||||
}
|
||||
|
||||
public Map<String, Between> getBetween() {
|
||||
return between;
|
||||
}
|
||||
|
||||
public void setBetween(Map<String, Between> between) {
|
||||
this.between = between;
|
||||
}
|
||||
|
||||
public Map<String, Between> getNotBetween() {
|
||||
return notBetween;
|
||||
}
|
||||
|
||||
public void setNotBetween(Map<String, Between> notBetween) {
|
||||
this.notBetween = notBetween;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Queryable{" +
|
||||
"nullEqual=" + nullEqual +
|
||||
", notNullEqual=" + notNullEqual +
|
||||
", empty=" + empty +
|
||||
", notEmpty=" + notEmpty +
|
||||
", equal=" + equal +
|
||||
", notEqual=" + notEqual +
|
||||
", like=" + like +
|
||||
", notLike=" + notLike +
|
||||
", great=" + great +
|
||||
", less=" + less +
|
||||
", greatEqual=" + greatEqual +
|
||||
", lessEqual=" + lessEqual +
|
||||
", in=" + in +
|
||||
", notIn=" + notIn +
|
||||
", between=" + between +
|
||||
", notBetween=" + notBetween +
|
||||
'}';
|
||||
}
|
||||
|
||||
/**
|
||||
* 区间范围类,用于表示起始值和结束值
|
||||
*/
|
||||
@Setter
|
||||
@Getter
|
||||
@ToString
|
||||
public static class Between {
|
||||
private String start;
|
||||
private String end;
|
||||
|
||||
public String getStart() {
|
||||
return start;
|
||||
}
|
||||
|
||||
public void setStart(String start) {
|
||||
this.start = start;
|
||||
}
|
||||
|
||||
public String getEnd() {
|
||||
return end;
|
||||
}
|
||||
|
||||
public void setEnd(String end) {
|
||||
this.end = end;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Between{" +
|
||||
"start='" + start + '\'' +
|
||||
", end='" + end + '\'' +
|
||||
'}';
|
||||
}
|
||||
/**
|
||||
* 起始值
|
||||
*/
|
||||
private Object start;
|
||||
/**
|
||||
* 结束值
|
||||
*/
|
||||
private Object end;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 可排序条件类,用于指定排序字段和排序方向
|
||||
*/
|
||||
@Setter
|
||||
@Getter
|
||||
@ToString
|
||||
public static class Sortable {
|
||||
/**
|
||||
* 排序字段名
|
||||
*/
|
||||
private String column;
|
||||
/**
|
||||
* 排序方向
|
||||
*/
|
||||
private Direction direction;
|
||||
|
||||
public String getColumn() {
|
||||
return column;
|
||||
}
|
||||
|
||||
public void setColumn(String column) {
|
||||
this.column = column;
|
||||
}
|
||||
|
||||
public Direction getDirection() {
|
||||
return direction;
|
||||
}
|
||||
|
||||
public void setDirection(Direction direction) {
|
||||
this.direction = direction;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Sortable{" +
|
||||
"column='" + column + '\'' +
|
||||
", direction=" + direction +
|
||||
'}';
|
||||
}
|
||||
|
||||
/**
|
||||
* 排序方向枚举
|
||||
*/
|
||||
public enum Direction {
|
||||
/**
|
||||
* 升序排列
|
||||
*/
|
||||
ASC,
|
||||
/**
|
||||
* 降序排列
|
||||
*/
|
||||
DESC,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 可分页条件类,用于指定分页参数
|
||||
*/
|
||||
@Setter
|
||||
@Getter
|
||||
@ToString
|
||||
public static class Pageable {
|
||||
/**
|
||||
* 页码索引(从0开始)
|
||||
*/
|
||||
private Integer index;
|
||||
/**
|
||||
* 每页大小
|
||||
*/
|
||||
private Integer size;
|
||||
|
||||
public Integer getIndex() {
|
||||
return index;
|
||||
}
|
||||
|
||||
public void setIndex(Integer index) {
|
||||
this.index = index;
|
||||
}
|
||||
|
||||
public Integer getSize() {
|
||||
return size;
|
||||
}
|
||||
|
||||
public void setSize(Integer size) {
|
||||
this.size = size;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Pageable{" +
|
||||
"index=" + index +
|
||||
", size=" + size +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,8 +2,27 @@ package com.lanyuanxiaoyao.service.template.controller;
|
||||
|
||||
import com.lanyuanxiaoyao.service.template.controller.response.GlobalResponse;
|
||||
|
||||
/**
|
||||
* 删除控制器接口,用于定义统一的删除实体对象的接口规范
|
||||
*
|
||||
* <p>
|
||||
* 前端传入的JSON格式示例:
|
||||
* <pre>
|
||||
* DELETE /remove/1
|
||||
* </pre>
|
||||
* </p>
|
||||
*
|
||||
* @author lanyuanxiaoyao
|
||||
*/
|
||||
public interface RemoveController {
|
||||
String REMOVE = "/remove/{id}";
|
||||
|
||||
/**
|
||||
* 根据ID删除实体对象
|
||||
*
|
||||
* @param id 需要删除的实体ID
|
||||
* @return GlobalResponse<Object> 返回删除结果
|
||||
* @throws Exception 删除过程中可能抛出的异常
|
||||
*/
|
||||
GlobalResponse<Object> remove(Long id) throws Exception;
|
||||
}
|
||||
}
|
||||
@@ -2,8 +2,31 @@ package com.lanyuanxiaoyao.service.template.controller;
|
||||
|
||||
import com.lanyuanxiaoyao.service.template.controller.response.GlobalResponse;
|
||||
|
||||
/**
|
||||
* 保存控制器接口,用于定义统一的保存实体对象的接口规范
|
||||
*
|
||||
* <p>
|
||||
* 前端传入的JSON格式示例:
|
||||
* <pre>
|
||||
* {
|
||||
* // 实体对象的具体字段
|
||||
* "name": "示例名称",
|
||||
* "description": "示例描述"
|
||||
* }
|
||||
* </pre>
|
||||
* </p>
|
||||
*
|
||||
* @author lanyuanxiaoyao
|
||||
*/
|
||||
public interface SaveController<SAVE_ITEM> {
|
||||
String SAVE = "/save";
|
||||
|
||||
/**
|
||||
* 保存实体对象
|
||||
*
|
||||
* @param item 需要保存的实体对象
|
||||
* @return GlobalResponse<Long> 返回保存后的实体ID
|
||||
* @throws Exception 保存过程中可能抛出的异常
|
||||
*/
|
||||
GlobalResponse<Long> save(SAVE_ITEM item) throws Exception;
|
||||
}
|
||||
}
|
||||
@@ -6,25 +6,126 @@ import com.lanyuanxiaoyao.service.template.entity.SimpleEntity;
|
||||
import com.lanyuanxiaoyao.service.template.helper.ObjectHelper;
|
||||
import com.lanyuanxiaoyao.service.template.service.SimpleServiceSupport;
|
||||
import java.util.List;
|
||||
import java.util.function.Function;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
|
||||
/**
|
||||
* 简单控制器支持类,提供基础的CRUD操作实现
|
||||
* <p>
|
||||
* 该类实现了基本的增删改查功能,通过泛型支持不同类型的数据转换。
|
||||
* 子类需要实现对应的Mapper函数来完成实体类与传输对象之间的转换。
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* 前端传入的JSON格式示例:
|
||||
* <pre>
|
||||
* // 保存实体
|
||||
* POST /save
|
||||
* {
|
||||
* // 保存项的具体字段
|
||||
* "name": "示例名称",
|
||||
* "description": "示例描述"
|
||||
* }
|
||||
*
|
||||
* // 查询列表(无条件)
|
||||
* GET /list
|
||||
*
|
||||
* // 查询列表(带条件)
|
||||
* POST /list
|
||||
* {
|
||||
* "query": {
|
||||
* "equal": {
|
||||
* "status": "ACTIVE"
|
||||
* },
|
||||
* "like": {
|
||||
* "name": "关键字"
|
||||
* }
|
||||
* },
|
||||
* "sort": [
|
||||
* {
|
||||
* "column": "createTime",
|
||||
* "direction": "DESC"
|
||||
* }
|
||||
* ],
|
||||
* "page": {
|
||||
* "index": 0,
|
||||
* "size": 10
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* // 获取详情
|
||||
* GET /detail/1
|
||||
*
|
||||
* // 删除实体
|
||||
* GET /remove/1
|
||||
* </pre>
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* 支持的查询条件说明:
|
||||
* <ul>
|
||||
* <li>nullEqual: 指定字段值为null的条件列表</li>
|
||||
* <li>notNullEqual: 指定字段值不为null的条件列表</li>
|
||||
* <li>empty: 指定字段值为空的条件列表(如空字符串、空集合等)</li>
|
||||
* <li>notEmpty: 指定字段值不为空的条件列表</li>
|
||||
* <li>equal: 指定字段值相等的条件映射(字段名 -> 值)</li>
|
||||
* <li>notEqual: 指定字段值不相等的条件映射(字段名 -> 值)</li>
|
||||
* <li>like: 指定字段模糊匹配的条件映射(字段名 -> 匹配值)</li>
|
||||
* <li>notLike: 指定字段不模糊匹配的条件映射(字段名 -> 匹配值)</li>
|
||||
* <li>great: 指定字段大于条件的映射(字段名 -> 值)</li>
|
||||
* <li>less: 指定字段小于条件的映射(字段名 -> 值)</li>
|
||||
* <li>greatEqual: 指定字段大于等于条件的映射(字段名 -> 值)</li>
|
||||
* <li>lessEqual: 指定字段小于等于条件的映射(字段名 -> 值)</li>
|
||||
* <li>in: 指定字段值在指定范围内的条件映射(字段名 -> 值列表)</li>
|
||||
* <li>notIn: 指定字段值不在指定范围内的条件映射(字段名 -> 值列表)</li>
|
||||
* <li>between: 指定字段值在指定区间内的条件映射(字段名 -> 区间范围)</li>
|
||||
* <li>notBetween: 指定字段值不在指定区间内的条件映射(字段名 -> 区间范围)</li>
|
||||
* </ul>
|
||||
* </p>
|
||||
*
|
||||
* @param <ENTITY> 实体类型,必须继承SimpleEntity
|
||||
* @param <SAVE_ITEM> 保存项类型
|
||||
* @param <LIST_ITEM> 列表项类型
|
||||
* @param <DETAIL_ITEM> 详情项类型
|
||||
* @author lanyuanxiaoyao
|
||||
*/
|
||||
@Slf4j
|
||||
public abstract class SimpleControllerSupport<ENTITY extends SimpleEntity, SAVE_ITEM, LIST_ITEM, DETAIL_ITEM> implements SimpleController<SAVE_ITEM, LIST_ITEM, DETAIL_ITEM> {
|
||||
protected final SimpleServiceSupport<ENTITY> service;
|
||||
|
||||
/**
|
||||
* 构造函数
|
||||
*
|
||||
* @param service 简单服务支持类实例
|
||||
*/
|
||||
public SimpleControllerSupport(SimpleServiceSupport<ENTITY> service) {
|
||||
this.service = service;
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存实体对象
|
||||
*
|
||||
* @param item 需要保存的项
|
||||
* @return GlobalResponse<Long> 返回保存后的实体ID
|
||||
* @throws Exception 保存过程中可能抛出的异常
|
||||
*/
|
||||
@PostMapping(SAVE)
|
||||
@Override
|
||||
public GlobalResponse<Long> save(@RequestBody SAVE_ITEM item) throws Exception {
|
||||
var mapper = saveItemMapper();
|
||||
return GlobalResponse.responseSuccess(service.save(mapper.from(item)));
|
||||
return GlobalResponse.responseSuccess(service.save(mapper.apply(item)));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有实体列表
|
||||
*
|
||||
* @return GlobalCrudResponse<LIST_ITEM> 返回实体列表
|
||||
* @throws Exception 查询过程中可能抛出的异常
|
||||
*/
|
||||
@GetMapping(LIST)
|
||||
@Override
|
||||
public GlobalCrudResponse<LIST_ITEM> list() throws Exception {
|
||||
@@ -35,7 +136,7 @@ public abstract class SimpleControllerSupport<ENTITY extends SimpleEntity, SAVE_
|
||||
.stream()
|
||||
.map(entity -> {
|
||||
try {
|
||||
return mapper.from(entity);
|
||||
return mapper.apply(entity);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
@@ -45,6 +146,13 @@ public abstract class SimpleControllerSupport<ENTITY extends SimpleEntity, SAVE_
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据查询条件获取实体列表
|
||||
*
|
||||
* @param query 查询条件对象
|
||||
* @return GlobalCrudResponse<LIST_ITEM> 返回符合条件的实体列表
|
||||
* @throws Exception 查询过程中可能抛出的异常
|
||||
*/
|
||||
@PostMapping(LIST)
|
||||
@Override
|
||||
public GlobalCrudResponse<LIST_ITEM> list(@RequestBody Query query) throws Exception {
|
||||
@@ -57,7 +165,7 @@ public abstract class SimpleControllerSupport<ENTITY extends SimpleEntity, SAVE_
|
||||
result.get()
|
||||
.map(entity -> {
|
||||
try {
|
||||
return mapper.from(entity);
|
||||
return mapper.apply(entity);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
@@ -67,13 +175,27 @@ public abstract class SimpleControllerSupport<ENTITY extends SimpleEntity, SAVE_
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据ID获取实体详情
|
||||
*
|
||||
* @param id 实体ID
|
||||
* @return GlobalResponse<DETAIL_ITEM> 返回实体详情
|
||||
* @throws Exception 查询过程中可能抛出的异常
|
||||
*/
|
||||
@GetMapping(DETAIL)
|
||||
@Override
|
||||
public GlobalResponse<DETAIL_ITEM> detail(@PathVariable("id") Long id) throws Exception {
|
||||
var mapper = detailItemMapper();
|
||||
return GlobalResponse.responseSuccess(mapper.from(service.detailOrThrow(id)));
|
||||
return GlobalResponse.responseSuccess(mapper.apply(service.detailOrThrow(id)));
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据ID删除实体对象
|
||||
*
|
||||
* @param id 需要删除的实体ID
|
||||
* @return GlobalResponse<Object> 返回删除结果
|
||||
* @throws Exception 删除过程中可能抛出的异常
|
||||
*/
|
||||
@GetMapping(REMOVE)
|
||||
@Override
|
||||
public GlobalResponse<Object> remove(@PathVariable("id") Long id) throws Exception {
|
||||
@@ -81,21 +203,24 @@ public abstract class SimpleControllerSupport<ENTITY extends SimpleEntity, SAVE_
|
||||
return GlobalResponse.responseSuccess();
|
||||
}
|
||||
|
||||
protected abstract SaveItemMapper<ENTITY, SAVE_ITEM> saveItemMapper();
|
||||
/**
|
||||
* 保存项映射器,将保存项转换为实体对象
|
||||
*
|
||||
* @return Function<SAVE_ITEM, ENTITY> 保存项到实体的转换函数
|
||||
*/
|
||||
protected abstract Function<SAVE_ITEM, ENTITY> saveItemMapper();
|
||||
|
||||
protected abstract ListItemMapper<ENTITY, LIST_ITEM> listItemMapper();
|
||||
/**
|
||||
* 列表项映射器,将实体对象转换为列表项
|
||||
*
|
||||
* @return Function<ENTITY, LIST_ITEM> 实体到列表项的转换函数
|
||||
*/
|
||||
protected abstract Function<ENTITY, LIST_ITEM> listItemMapper();
|
||||
|
||||
protected abstract DetailItemMapper<ENTITY, DETAIL_ITEM> detailItemMapper();
|
||||
|
||||
public interface SaveItemMapper<ENTITY, SAVE_ITEM> {
|
||||
ENTITY from(SAVE_ITEM item) throws Exception;
|
||||
}
|
||||
|
||||
public interface ListItemMapper<ENTITY, LIST_ITEM> {
|
||||
LIST_ITEM from(ENTITY entity) throws Exception;
|
||||
}
|
||||
|
||||
public interface DetailItemMapper<ENTITY, DETAIL_ITEM> {
|
||||
DETAIL_ITEM from(ENTITY entity) throws Exception;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 详情项映射器,将实体对象转换为详情项
|
||||
*
|
||||
* @return Function<ENTITY, DETAIL_ITEM> 实体到详情项的转换函数
|
||||
*/
|
||||
protected abstract Function<ENTITY, DETAIL_ITEM> detailItemMapper();
|
||||
}
|
||||
@@ -1,11 +1,5 @@
|
||||
package com.lanyuanxiaoyao.service.template.controller.response;
|
||||
|
||||
/**
|
||||
* Crud 响应
|
||||
*
|
||||
* @author lanyuanxiaoyao
|
||||
* @date 2023-07-06
|
||||
*/
|
||||
public class GlobalCrudResponse<T> extends GlobalMapResponse {
|
||||
public void setData(Iterable<T> list) {
|
||||
setData("items", list);
|
||||
|
||||
@@ -1,11 +1,5 @@
|
||||
package com.lanyuanxiaoyao.service.template.controller.response;
|
||||
|
||||
/**
|
||||
* Crud 响应
|
||||
*
|
||||
* @author lanyuanxiaoyao
|
||||
* @date 2023-07-06
|
||||
*/
|
||||
public class GlobalDetailResponse extends GlobalMapResponse {
|
||||
public void setDetail(Object detail) {
|
||||
setData("detail", detail);
|
||||
|
||||
@@ -3,12 +3,6 @@ package com.lanyuanxiaoyao.service.template.controller.response;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Map 响应
|
||||
*
|
||||
* @author lanyuanxiaoyao
|
||||
* @date 2023-07-06
|
||||
*/
|
||||
public class GlobalMapResponse extends GlobalResponse<Map<String, Object>> {
|
||||
public GlobalMapResponse() {
|
||||
setData(new HashMap<>());
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
package com.lanyuanxiaoyao.service.template.controller.response;
|
||||
|
||||
import java.util.Map;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import lombok.ToString;
|
||||
|
||||
/**
|
||||
* Amis 组件结构化返回值
|
||||
*
|
||||
* @author lanyuanxiaoyao
|
||||
* @date 2022-09-21
|
||||
*/
|
||||
@Setter
|
||||
@Getter
|
||||
@ToString
|
||||
public class GlobalResponse<T> {
|
||||
private static final int SUCCESS_STATUS = 0;
|
||||
private static final int ERROR_STATUS = 500;
|
||||
@@ -102,37 +102,4 @@ public class GlobalResponse<T> {
|
||||
response.setDetail(detail);
|
||||
return response;
|
||||
}
|
||||
|
||||
public Integer getStatus() {
|
||||
return status;
|
||||
}
|
||||
|
||||
public void setStatus(Integer status) {
|
||||
this.status = status;
|
||||
}
|
||||
|
||||
public String getMessage() {
|
||||
return message;
|
||||
}
|
||||
|
||||
public void setMessage(String message) {
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
public T getData() {
|
||||
return data;
|
||||
}
|
||||
|
||||
public void setData(T data) {
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "AmisResponse{" +
|
||||
"status=" + status +
|
||||
", message='" + message + '\'' +
|
||||
", data=" + data +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,34 +1,51 @@
|
||||
package com.lanyuanxiaoyao.service.template.entity;
|
||||
|
||||
import com.blinkfox.fenix.id.SnowflakeId;
|
||||
import jakarta.persistence.EntityListeners;
|
||||
import jakarta.persistence.GeneratedValue;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.MappedSuperclass;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import lombok.ToString;
|
||||
import lombok.experimental.FieldNameConstants;
|
||||
import org.hibernate.annotations.Comment;
|
||||
import org.hibernate.annotations.GenericGenerator;
|
||||
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
|
||||
|
||||
/**
|
||||
* 仅包含ID的基础实体类
|
||||
* <p>
|
||||
* 该类作为所有实体类的基类,仅提供ID字段,使用Snowflake算法生成唯一标识。
|
||||
* 通过继承此类,实体类可以自动获得唯一ID字段以及相关的JPA注解配置。
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* 该类使用了以下注解:
|
||||
* <ul>
|
||||
* <li>@MappedSuperclass: 标识该类为JPA映射的超类,其属性会被映射到子类的数据库表字段中</li>
|
||||
* <li>@EntityListeners(AuditingEntityListener.class): 启用Spring Data JPA的审计功能</li>
|
||||
* <li>@Id: 标识id字段为主键</li>
|
||||
* <li>@SnowflakeId: 使用Snowflake算法生成唯一ID</li>
|
||||
* <li>@Comment: 为数据库字段添加注释</li>
|
||||
* </ul>
|
||||
* </p>
|
||||
*
|
||||
* @author lanyuanxiaoyao
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
@ToString
|
||||
@FieldNameConstants
|
||||
@MappedSuperclass
|
||||
@EntityListeners(AuditingEntityListener.class)
|
||||
public class IdOnlyEntity {
|
||||
@Comment("记录唯一标记")
|
||||
/**
|
||||
* 实体唯一标识符
|
||||
* <p>
|
||||
* 使用Snowflake算法生成的Long类型ID,保证全局唯一性。
|
||||
* </p>
|
||||
*/
|
||||
@Id
|
||||
@GeneratedValue(generator = "snowflake")
|
||||
@GenericGenerator(name = "snowflake", strategy = "com.lanyuanxiaoyao.service.template.helper.SnowflakeIdGenerator")
|
||||
@SnowflakeId
|
||||
@Comment("记录唯一标记")
|
||||
private Long id;
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(Long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "IdOnlyEntity{" +
|
||||
"id=" + id +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,42 +3,59 @@ package com.lanyuanxiaoyao.service.template.entity;
|
||||
import jakarta.persistence.EntityListeners;
|
||||
import jakarta.persistence.MappedSuperclass;
|
||||
import java.time.LocalDateTime;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import lombok.ToString;
|
||||
import lombok.experimental.FieldNameConstants;
|
||||
import org.hibernate.annotations.Comment;
|
||||
import org.springframework.data.annotation.CreatedDate;
|
||||
import org.springframework.data.annotation.LastModifiedDate;
|
||||
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
|
||||
|
||||
/**
|
||||
* 简单实体类,包含基础字段
|
||||
* <p>
|
||||
* 该类继承自IdOnlyEntity,除了具备唯一ID外,还提供了创建时间和修改时间字段。
|
||||
* 通过Spring Data JPA的审计功能自动维护时间字段,适用于大多数业务实体场景。
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* 该类使用了以下注解:
|
||||
* <ul>
|
||||
* <li>@MappedSuperclass: 标识该类为JPA映射的超类,其属性会被映射到子类的数据库表字段中</li>
|
||||
* <li>@EntityListeners(AuditingEntityListener.class): 启用Spring Data JPA的审计功能</li>
|
||||
* <li>@CreatedDate: 自动设置实体创建时间</li>
|
||||
* <li>@LastModifiedDate: 自动更新实体最后修改时间</li>
|
||||
* <li>@Comment: 为数据库字段添加注释</li>
|
||||
* </ul>
|
||||
* </p>
|
||||
*
|
||||
* @author lanyuanxiaoyao
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
@ToString(callSuper = true)
|
||||
@FieldNameConstants
|
||||
@MappedSuperclass
|
||||
@EntityListeners(AuditingEntityListener.class)
|
||||
public class SimpleEntity extends IdOnlyEntity {
|
||||
@Comment("记录创建时间")
|
||||
/**
|
||||
* 记录创建时间
|
||||
* <p>
|
||||
* 由Spring Data JPA自动维护,当实体首次持久化时设置该字段的值。
|
||||
* </p>
|
||||
*/
|
||||
@CreatedDate
|
||||
@Comment("记录创建时间")
|
||||
private LocalDateTime createdTime;
|
||||
@Comment("记录更新时间")
|
||||
|
||||
/**
|
||||
* 记录更新时间
|
||||
* <p>
|
||||
* 由Spring Data JPA自动维护,当实体每次更新时刷新该字段的值。
|
||||
* </p>
|
||||
*/
|
||||
@LastModifiedDate
|
||||
@Comment("记录更新时间")
|
||||
private LocalDateTime modifiedTime;
|
||||
|
||||
public LocalDateTime getCreatedTime() {
|
||||
return createdTime;
|
||||
}
|
||||
|
||||
public void setCreatedTime(LocalDateTime createdTime) {
|
||||
this.createdTime = createdTime;
|
||||
}
|
||||
|
||||
public LocalDateTime getModifiedTime() {
|
||||
return modifiedTime;
|
||||
}
|
||||
|
||||
public void setModifiedTime(LocalDateTime modifiedTime) {
|
||||
this.modifiedTime = modifiedTime;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "SimpleEntity{" +
|
||||
"createdTime=" + createdTime +
|
||||
", modifiedTime=" + modifiedTime +
|
||||
"} " + super.toString();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,19 +5,44 @@ import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
public class ObjectHelper {
|
||||
/**
|
||||
* 判断对象是否为null
|
||||
*
|
||||
* @param obj 待检查的对象
|
||||
* @return 如果对象为null返回true,否则返回false
|
||||
*/
|
||||
public static boolean isNull(Object obj) {
|
||||
return obj == null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 判断对象是否不为null
|
||||
*
|
||||
* @param obj 待判断的对象
|
||||
* @return 如果对象不为null则返回true,否则返回false
|
||||
*/
|
||||
public static boolean isNotNull(Object obj) {
|
||||
return !isNull(obj);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 判断对象是否为空
|
||||
*
|
||||
* @param obj 待判断的对象
|
||||
* @return 如果对象为null或为空则返回true,否则返回false
|
||||
*/
|
||||
public static boolean isEmpty(Object obj) {
|
||||
// 首先判断对象是否为null
|
||||
if (isNull(obj)) return true;
|
||||
// 判断是否为集合类型
|
||||
else if (obj instanceof Collection<?> collection) return collection.isEmpty();
|
||||
// 判断是否为Map类型
|
||||
else if (obj instanceof Map<?, ?> map) return map.isEmpty();
|
||||
// 判断是否为字符序列类型
|
||||
else if (obj instanceof CharSequence sequence) return sequence.isEmpty();
|
||||
// 判断是否为各种基本类型数组
|
||||
else if (obj instanceof Object[] array) return array.length == 0;
|
||||
else if (obj instanceof byte[] array) return array.length == 0;
|
||||
else if (obj instanceof short[] array) return array.length == 0;
|
||||
@@ -27,10 +52,13 @@ public class ObjectHelper {
|
||||
else if (obj instanceof double[] array) return array.length == 0;
|
||||
else if (obj instanceof char[] array) return array.length == 0;
|
||||
else if (obj instanceof boolean[] array) return array.length == 0;
|
||||
// 判断是否为Optional类型
|
||||
else if (obj instanceof Optional<?> optional) return optional.isEmpty();
|
||||
// 其他情况认为对象不为空
|
||||
else return false;
|
||||
}
|
||||
|
||||
|
||||
public static boolean isNotEmpty(Object obj) {
|
||||
return !isEmpty(obj);
|
||||
}
|
||||
@@ -38,4 +66,44 @@ public class ObjectHelper {
|
||||
public static <T> T defaultIfNull(final T object, final T defaultValue) {
|
||||
return isNull(object) ? defaultValue : object;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断给定的类是否可比较
|
||||
*
|
||||
* @param clazz 待判断的类对象
|
||||
* @return 如果类是枚举、字符序列、可比较接口的实现类或基本数据类型则返回true,否则返回false
|
||||
*/
|
||||
public static boolean isComparable(Class<?> clazz) {
|
||||
if (isNull(clazz)) return false;
|
||||
// 判断类是否为可比较类型:枚举、字符序列、可比较接口实现类或基本数据类型
|
||||
return clazz.isEnum() ||
|
||||
CharSequence.class.isAssignableFrom(clazz) ||
|
||||
Comparable.class.isAssignableFrom(clazz) ||
|
||||
clazz.isPrimitive();
|
||||
}
|
||||
|
||||
public static boolean isComparable(Object obj) {
|
||||
if (isNull(obj)) return false;
|
||||
return isComparable(obj.getClass());
|
||||
}
|
||||
|
||||
public static boolean isCollection(Class<?> clazz) {
|
||||
if (isNull(clazz)) return false;
|
||||
return Collection.class.isAssignableFrom(clazz);
|
||||
}
|
||||
|
||||
public static boolean isCollection(Object obj) {
|
||||
if (isNull(obj)) return false;
|
||||
return isCollection(obj.getClass());
|
||||
}
|
||||
|
||||
public static boolean isString(Class<?> clazz) {
|
||||
if (isNull(clazz)) return false;
|
||||
return String.class.isAssignableFrom(clazz);
|
||||
}
|
||||
|
||||
public static boolean isString(Object obj) {
|
||||
if (isNull(obj)) return false;
|
||||
return isString(obj.getClass());
|
||||
}
|
||||
}
|
||||
@@ -1,82 +0,0 @@
|
||||
package com.lanyuanxiaoyao.service.template.helper;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.time.Instant;
|
||||
import org.hibernate.engine.spi.SharedSessionContractImplementor;
|
||||
import org.hibernate.id.IdentifierGenerator;
|
||||
|
||||
public class SnowflakeIdGenerator implements IdentifierGenerator {
|
||||
@Override
|
||||
public Serializable generate(SharedSessionContractImplementor session, Object object) {
|
||||
try {
|
||||
return Snowflake.next();
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public static class Snowflake {
|
||||
/**
|
||||
* 起始的时间戳
|
||||
*/
|
||||
private final static long START_TIMESTAMP = 1;
|
||||
|
||||
/**
|
||||
* 序列号占用的位数
|
||||
*/
|
||||
private final static long SEQUENCE_BIT = 11;
|
||||
|
||||
/**
|
||||
* 序列号最大值
|
||||
*/
|
||||
private final static long MAX_SEQUENCE_BIT = ~(-1 << SEQUENCE_BIT);
|
||||
|
||||
/**
|
||||
* 时间戳值向左位移
|
||||
*/
|
||||
private final static long TIMESTAMP_OFFSET = SEQUENCE_BIT;
|
||||
|
||||
/**
|
||||
* 序列号
|
||||
*/
|
||||
private static long sequence = 0;
|
||||
/**
|
||||
* 上一次时间戳
|
||||
*/
|
||||
private static long lastTimestamp = -1;
|
||||
|
||||
public static synchronized long next() {
|
||||
long currentTimestamp = nowTimestamp();
|
||||
if (currentTimestamp < lastTimestamp) {
|
||||
throw new RuntimeException("Clock have moved backwards.");
|
||||
}
|
||||
|
||||
if (currentTimestamp == lastTimestamp) {
|
||||
// 相同毫秒内, 序列号自增
|
||||
sequence = (sequence + 1) & MAX_SEQUENCE_BIT;
|
||||
// 同一毫秒的序列数已经达到最大
|
||||
if (sequence == 0) {
|
||||
currentTimestamp = nextTimestamp();
|
||||
}
|
||||
} else {
|
||||
// 不同毫秒内, 序列号置为0
|
||||
sequence = 0;
|
||||
}
|
||||
|
||||
lastTimestamp = currentTimestamp;
|
||||
return (currentTimestamp - START_TIMESTAMP) << TIMESTAMP_OFFSET | sequence;
|
||||
}
|
||||
|
||||
private static long nextTimestamp() {
|
||||
long milli = nowTimestamp();
|
||||
while (milli <= lastTimestamp) {
|
||||
milli = nowTimestamp();
|
||||
}
|
||||
return milli;
|
||||
}
|
||||
|
||||
private static long nowTimestamp() {
|
||||
return Instant.now().toEpochMilli();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,9 +2,104 @@ package com.lanyuanxiaoyao.service.template.repository;
|
||||
|
||||
import com.blinkfox.fenix.jpa.FenixJpaRepository;
|
||||
import com.blinkfox.fenix.specification.FenixJpaSpecificationExecutor;
|
||||
import org.springframework.data.querydsl.QuerydslPredicateExecutor;
|
||||
import org.springframework.data.repository.NoRepositoryBean;
|
||||
import org.springframework.data.repository.query.QueryByExampleExecutor;
|
||||
|
||||
/**
|
||||
* 简单仓库接口,整合多种数据访问功能
|
||||
* <p>
|
||||
* 该接口继承了多个Spring Data JPA和扩展功能接口,为数据访问层提供丰富的查询和操作能力。
|
||||
* 通过继承此接口,可以快速实现常见的数据访问功能,包括基本CRUD操作、复杂条件查询、
|
||||
* 示例查询和QueryDSL查询等。
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* 继承的接口功能说明:
|
||||
* <ul>
|
||||
* <li>
|
||||
* FenixJpaRepository: 扩展的JPA仓库接口,提供基本的CRUD操作、分页和排序功能。
|
||||
* <p><b>常用方法示例:</b></p>
|
||||
* <pre>
|
||||
* // 保存实体
|
||||
* User user = new User("张三", "zhangsan@example.com");
|
||||
* User savedUser = repository.save(user);
|
||||
*
|
||||
* // 根据ID查找
|
||||
* Optional<User> user = repository.findById(1L);
|
||||
*
|
||||
* // 查找所有
|
||||
* List<User> users = repository.findAll();
|
||||
*
|
||||
* // 分页查询
|
||||
* Pageable pageable = PageRequest.of(0, 10, Sort.by("createdTime").descending());
|
||||
* Page<User> userPages = repository.findAll(pageable);
|
||||
*
|
||||
* // 根据ID删除
|
||||
* repository.deleteById(1L);
|
||||
* </pre>
|
||||
* </li>
|
||||
* <li>
|
||||
* FenixJpaSpecificationExecutor: Fenix扩展的JPA规范执行器,支持通过Specification构建复杂查询条件。
|
||||
* <p><b>常用方法示例:</b></p>
|
||||
* <pre>
|
||||
* // 使用Specification构建复杂查询
|
||||
* Specification<User> spec = (root, query, criteriaBuilder) -> {
|
||||
* List<Predicate> predicates = new ArrayList<>();
|
||||
* predicates.add(criteriaBuilder.equal(root.get("status"), "ACTIVE"));
|
||||
* predicates.add(criteriaBuilder.like(root.get("name"), "%张三%"));
|
||||
* return criteriaBuilder.and(predicates.toArray(new Predicate[0]));
|
||||
* };
|
||||
* List<User> users = repository.findAll(spec);
|
||||
*
|
||||
* // 分页查询
|
||||
* Page<User> userPages = repository.findAll(spec, pageable);
|
||||
* </pre>
|
||||
* </li>
|
||||
* <li>
|
||||
* QueryByExampleExecutor: 示例查询执行器,通过示例对象进行查询。
|
||||
* <p><b>常用方法示例:</b></p>
|
||||
* <pre>
|
||||
* // 使用示例对象查询
|
||||
* User exampleUser = new User();
|
||||
* exampleUser.setName("张三");
|
||||
* exampleUser.setStatus("ACTIVE");
|
||||
* Example<User> example = Example.of(exampleUser);
|
||||
* List<User> users = repository.findAll(example);
|
||||
*
|
||||
* // 使用匹配器增强查询
|
||||
* ExampleMatcher matcher = ExampleMatcher.matching()
|
||||
* .withMatcher("name", ExampleMatcher.GenericPropertyMatchers.startsWith())
|
||||
* .withIgnorePaths("createdTime");
|
||||
* Example<User> exampleWithMatcher = Example.of(exampleUser, matcher);
|
||||
* List<User> matchedUsers = repository.findAll(exampleWithMatcher);
|
||||
* </pre>
|
||||
* </li>
|
||||
* <li>
|
||||
* QuerydslPredicateExecutor: QueryDSL查询执行器,支持类型安全的查询构建。
|
||||
* <p><b>常用方法示例:</b></p>
|
||||
* <pre>
|
||||
* // 使用QueryDSL构建类型安全查询
|
||||
* QUser qUser = QUser.user;
|
||||
* Iterable<User> users = repository.findAll(
|
||||
* qUser.status.eq("ACTIVE")
|
||||
* .and(qUser.name.like("%张三%"))
|
||||
* );
|
||||
*
|
||||
* // 排序和分页
|
||||
* Iterable<User> sortedUsers = repository.findAll(
|
||||
* qUser.status.eq("ACTIVE"),
|
||||
* qUser.createdTime.desc()
|
||||
* );
|
||||
* </pre>
|
||||
* </li>
|
||||
* </ul>
|
||||
* </p>
|
||||
*
|
||||
* @param <E> 实体类型
|
||||
* @param <ID> 实体ID类型
|
||||
* @author lanyuanxiaoyao
|
||||
*/
|
||||
@NoRepositoryBean
|
||||
public interface SimpleRepository<E, ID> extends FenixJpaRepository<E, ID>, FenixJpaSpecificationExecutor<E>, QueryByExampleExecutor<E> {
|
||||
}
|
||||
public interface SimpleRepository<E, ID> extends FenixJpaRepository<E, ID>, FenixJpaSpecificationExecutor<E>, QueryByExampleExecutor<E>, QuerydslPredicateExecutor<E> {
|
||||
}
|
||||
@@ -7,10 +7,6 @@ import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import org.springframework.data.domain.Page;
|
||||
|
||||
/**
|
||||
* @author lanyuanxiaoyao
|
||||
* @date 2024-11-28
|
||||
*/
|
||||
public interface SimpleService<ENTITY extends SimpleEntity> {
|
||||
Long save(ENTITY entity) throws Exception;
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.lanyuanxiaoyao.service.template.service;
|
||||
|
||||
import com.lanyuanxiaoyao.service.template.controller.Query;
|
||||
import com.lanyuanxiaoyao.service.template.entity.IdOnlyEntity;
|
||||
import com.lanyuanxiaoyao.service.template.entity.SimpleEntity;
|
||||
import com.lanyuanxiaoyao.service.template.helper.ObjectHelper;
|
||||
import com.lanyuanxiaoyao.service.template.repository.SimpleRepository;
|
||||
@@ -11,39 +12,119 @@ import jakarta.persistence.criteria.Predicate;
|
||||
import jakarta.persistence.criteria.Root;
|
||||
import jakarta.transaction.Transactional;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.PageRequest;
|
||||
import org.springframework.data.domain.Sort;
|
||||
|
||||
/**
|
||||
* 简单服务支持类,提供基础的业务逻辑实现
|
||||
* <p>
|
||||
* 该类实现了SimpleService接口,提供实体的增删改查等基本操作。
|
||||
* 通过继承此类,可以快速实现常见的业务逻辑功能,包括:
|
||||
* <ul>
|
||||
* <li>实体的保存和更新</li>
|
||||
* <li>实体的条件查询和分页查询</li>
|
||||
* <li>实体的详情查询(多种方式)</li>
|
||||
* <li>实体的删除操作</li>
|
||||
* </ul>
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* 查询条件说明:
|
||||
* <ul>
|
||||
* <li>nullEqual: 指定字段值为null的条件列表</li>
|
||||
* <li>notNullEqual: 指定字段值不为null的条件列表</li>
|
||||
* <li>empty: 指定字段值为空的条件列表(如空字符串、空集合等)</li>
|
||||
* <li>notEmpty: 指定字段值不为空的条件列表</li>
|
||||
* <li>equal: 指定字段值相等的条件映射(字段名 -> 值)</li>
|
||||
* <li>notEqual: 指定字段值不相等的条件映射(字段名 -> 值)</li>
|
||||
* <li>like: 指定字段模糊匹配的条件映射(字段名 -> 匹配值)</li>
|
||||
* <li>notLike: 指定字段不模糊匹配的条件映射(字段名 -> 匹配值)</li>
|
||||
* <li>great: 指定字段大于条件的映射(字段名 -> 值)</li>
|
||||
* <li>less: 指定字段小于条件的映射(字段名 -> 值)</li>
|
||||
* <li>greatEqual: 指定字段大于等于条件的映射(字段名 -> 值)</li>
|
||||
* <li>lessEqual: 指定字段小于等于条件的映射(字段名 -> 值)</li>
|
||||
* <li>in: 指定字段值在指定范围内的条件映射(字段名 -> 值列表)</li>
|
||||
* <li>notIn: 指定字段值不在指定范围内的条件映射(字段名 -> 值列表)</li>
|
||||
* <li>between: 指定字段值在指定区间内的条件映射(字段名 -> 区间范围)</li>
|
||||
* <li>notBetween: 指定字段值不在指定区间内的条件映射(字段名 -> 区间范围)</li>
|
||||
* </ul>
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* 前端传入的JSON格式示例:
|
||||
* <pre>
|
||||
* {
|
||||
* "query": {
|
||||
* "equal": {
|
||||
* "status": "ACTIVE"
|
||||
* },
|
||||
* "like": {
|
||||
* "name": "关键字"
|
||||
* }
|
||||
* },
|
||||
* "sort": [
|
||||
* {
|
||||
* "column": "createdTime",
|
||||
* "direction": "DESC"
|
||||
* }
|
||||
* ],
|
||||
* "page": {
|
||||
* "index": 1,
|
||||
* "size": 10
|
||||
* }
|
||||
* }
|
||||
* </pre>
|
||||
* </p>
|
||||
*
|
||||
* @param <ENTITY> 实体类型,必须继承SimpleEntity
|
||||
* @author lanyuanxiaoyao
|
||||
*/
|
||||
@Slf4j
|
||||
public abstract class SimpleServiceSupport<ENTITY extends SimpleEntity> implements SimpleService<ENTITY> {
|
||||
private static final Logger log = LoggerFactory.getLogger(SimpleServiceSupport.class);
|
||||
private static final Integer DEFAULT_PAGE_INDEX = 1;
|
||||
private static final Integer DEFAULT_PAGE_SIZE = 10;
|
||||
protected final SimpleRepository<ENTITY, Long> repository;
|
||||
|
||||
/**
|
||||
* 构造函数
|
||||
*
|
||||
* @param repository 简单仓库实例
|
||||
*/
|
||||
public SimpleServiceSupport(SimpleRepository<ENTITY, Long> repository) {
|
||||
this.repository = repository;
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存实体对象
|
||||
* <p>
|
||||
* 使用saveOrUpdateByNotNullProperties方法保存实体,只更新非空字段。
|
||||
* 该方法具有事务性,遇到任何异常都会回滚。
|
||||
* </p>
|
||||
*
|
||||
* @param entity 需要保存的实体对象
|
||||
* @return Long 返回保存后的实体ID
|
||||
*/
|
||||
@Transactional(rollbackOn = Throwable.class)
|
||||
@Override
|
||||
public Long save(ENTITY entity) {
|
||||
if (ObjectHelper.isNotNull(entity.getId())) {
|
||||
var id = entity.getId();
|
||||
var targetEntity = repository.findById(entity.getId()).orElseThrow(() -> new IdNotFoundException(id));
|
||||
BeanUtils.copyProperties(entity, targetEntity, "id", "created_time", "modified_time");
|
||||
entity = targetEntity;
|
||||
}
|
||||
entity = repository.save(entity);
|
||||
entity = repository.saveOrUpdateByNotNullProperties(entity);
|
||||
return entity.getId();
|
||||
}
|
||||
|
||||
/**
|
||||
* 统计符合条件的实体数量
|
||||
* <p>
|
||||
* 根据[listPredicate](file:///Users/lanyuanxiaoyao/Project/IdeaProjects/spring-boot-service-template/src/main/java/com/lanyuanxiaoyao/service/template/service/SimpleServiceSupport.java#L261-L263)方法构建的条件统计实体数量。
|
||||
* </p>
|
||||
*
|
||||
* @return Long 返回符合条件的实体数量
|
||||
*/
|
||||
@Override
|
||||
public Long count() {
|
||||
return repository.count(
|
||||
@@ -57,6 +138,14 @@ public abstract class SimpleServiceSupport<ENTITY extends SimpleEntity> implemen
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有符合条件的实体列表
|
||||
* <p>
|
||||
* 根据[listPredicate](file:///Users/lanyuanxiaoyao/Project/IdeaProjects/spring-boot-service-template/src/main/java/com/lanyuanxiaoyao/service/template/service/SimpleServiceSupport.java#L261-L263)方法构建的条件查询所有实体。
|
||||
* </p>
|
||||
*
|
||||
* @return List<ENTITY> 返回符合条件的实体列表
|
||||
*/
|
||||
@Override
|
||||
public List<ENTITY> list() {
|
||||
return repository.findAll(
|
||||
@@ -70,17 +159,39 @@ public abstract class SimpleServiceSupport<ENTITY extends SimpleEntity> implemen
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据ID集合获取实体列表
|
||||
* <p>
|
||||
* 根据提供的ID集合查询对应的实体列表,并结合[listPredicate](file:///Users/lanyuanxiaoyao/Project/IdeaProjects/spring-boot-service-template/src/main/java/com/lanyuanxiaoyao/service/template/service/SimpleServiceSupport.java#L261-L263)方法构建的条件。
|
||||
* </p>
|
||||
*
|
||||
* @param ids ID集合
|
||||
* @return List<ENTITY> 返回ID集合对应的实体列表
|
||||
*/
|
||||
@Override
|
||||
public List<ENTITY> list(Set<Long> ids) {
|
||||
return repository.findAll(
|
||||
(root, query, builder) -> {
|
||||
var predicates = listPredicate(root, query, builder);
|
||||
predicates.add(builder.in(root.get("id")).value(ids));
|
||||
predicates.add(builder.in(root.get(IdOnlyEntity.Fields.id)).value(ids));
|
||||
return builder.and(predicates.stream().filter(ObjectHelper::isNotNull).toArray(Predicate[]::new));
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析字段路径
|
||||
* <p>
|
||||
* 支持多级字段路径解析,使用"/"分隔多级字段。
|
||||
* 例如: "user/name" 表示实体的user属性的name字段。
|
||||
* </p>
|
||||
*
|
||||
* @param root JPA Criteria查询根节点
|
||||
* @param column 字段路径字符串
|
||||
* @param <Y> 字段类型
|
||||
* @return Path<Y> 返回字段路径对象
|
||||
* @throws IllegalArgumentException 当字段路径为空时抛出
|
||||
*/
|
||||
private <Y> Path<Y> column(Root<ENTITY> root, String column) {
|
||||
if (ObjectHelper.isEmpty(column)) {
|
||||
throw new IllegalArgumentException("Column cannot be blank");
|
||||
@@ -93,6 +204,19 @@ public abstract class SimpleServiceSupport<ENTITY extends SimpleEntity> implemen
|
||||
return path;
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理字段值
|
||||
* <p>
|
||||
* 对于枚举类型字段,将字符串值转换为对应的枚举值。
|
||||
* 其他类型直接返回原值。
|
||||
* </p>
|
||||
*
|
||||
* @param column 字段路径
|
||||
* @param value 字段值
|
||||
* @param <Y> 字段类型
|
||||
* @return Object 处理后的字段值
|
||||
* @throws IllegalArgumentException 当枚举类型字段的值不是字符串时抛出
|
||||
*/
|
||||
@SuppressWarnings({"unchecked", "rawtypes"})
|
||||
private <Y> Object value(Path<Y> column, Object value) {
|
||||
var javaType = column.getJavaType();
|
||||
@@ -101,12 +225,26 @@ public abstract class SimpleServiceSupport<ENTITY extends SimpleEntity> implemen
|
||||
var enumType = (Class<Enum>) javaType;
|
||||
return Enum.valueOf(enumType, enumName);
|
||||
} else {
|
||||
throw new IllegalArgumentException("枚举类型字段需要String类型的值");
|
||||
throw new IllegalArgumentException("枚举类型字段需要 String 类型的值");
|
||||
}
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建查询条件谓词列表
|
||||
* <p>
|
||||
* 根据Query.Queryable对象构建JPA Criteria查询的谓词列表。
|
||||
* 支持多种查询条件类型,包括相等、不等、模糊匹配、范围查询等。
|
||||
* </p>
|
||||
*
|
||||
* @param queryable 查询条件对象
|
||||
* @param root JPA Criteria查询根节点
|
||||
* @param query JPA Criteria查询对象
|
||||
* @param builder JPA Criteria构建器
|
||||
* @return List<Predicate> 返回构建的谓词列表
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
protected List<Predicate> queryPredicates(Query.Queryable queryable, Root<ENTITY> root, CriteriaQuery<?> query, CriteriaBuilder builder) {
|
||||
var predicates = new ArrayList<Predicate>();
|
||||
if (ObjectHelper.isNull(queryable)) {
|
||||
@@ -116,13 +254,21 @@ public abstract class SimpleServiceSupport<ENTITY extends SimpleEntity> implemen
|
||||
queryable.getNullEqual().forEach(column -> predicates.add(builder.isNull(column(root, column))));
|
||||
}
|
||||
if (ObjectHelper.isNotEmpty(queryable.getNotNullEqual())) {
|
||||
queryable.getNotNullEqual().forEach(column -> predicates.add(builder.isNull(column(root, column))));
|
||||
queryable.getNotNullEqual().forEach(column -> predicates.add(builder.isNotNull(column(root, column))));
|
||||
}
|
||||
if (ObjectHelper.isNotEmpty(queryable.getEmpty())) {
|
||||
queryable.getEmpty().forEach(column -> predicates.add(builder.isEmpty(column(root, column))));
|
||||
queryable.getEmpty().forEach(column -> {
|
||||
var path = this.<Collection<Object>>column(root, column);
|
||||
checkCollection(path, column);
|
||||
predicates.add(builder.isEmpty(path));
|
||||
});
|
||||
}
|
||||
if (ObjectHelper.isNotEmpty(queryable.getNotEmpty())) {
|
||||
queryable.getNotEmpty().forEach(column -> predicates.add(builder.isNotEmpty(column(root, column))));
|
||||
queryable.getNotEmpty().forEach(column -> {
|
||||
var path = this.<Collection<Object>>column(root, column);
|
||||
checkCollection(path, column);
|
||||
predicates.add(builder.isNotEmpty(path));
|
||||
});
|
||||
}
|
||||
if (ObjectHelper.isNotEmpty(queryable.getEqual())) {
|
||||
queryable.getEqual().forEach((column, value) -> {
|
||||
@@ -137,22 +283,52 @@ public abstract class SimpleServiceSupport<ENTITY extends SimpleEntity> implemen
|
||||
});
|
||||
}
|
||||
if (ObjectHelper.isNotEmpty(queryable.getLike())) {
|
||||
queryable.getLike().forEach((column, value) -> predicates.add(builder.like(column(root, column), value)));
|
||||
queryable.getLike().forEach((column, value) -> {
|
||||
var path = column(root, column);
|
||||
checkString(path, column);
|
||||
checkString(value, column);
|
||||
predicates.add(builder.like(column(root, column), value));
|
||||
});
|
||||
}
|
||||
if (ObjectHelper.isNotEmpty(queryable.getNotLike())) {
|
||||
queryable.getNotLike().forEach((column, value) -> predicates.add(builder.notLike(column(root, column), value)));
|
||||
queryable.getNotLike().forEach((column, value) -> {
|
||||
var path = column(root, column);
|
||||
checkString(path, column);
|
||||
checkString(value, column);
|
||||
predicates.add(builder.notLike(column(root, column), value));
|
||||
});
|
||||
}
|
||||
if (ObjectHelper.isNotEmpty(queryable.getGreat())) {
|
||||
queryable.getGreat().forEach((column, value) -> predicates.add(builder.greaterThan(column(root, column), value)));
|
||||
queryable.getGreat().forEach((column, value) -> {
|
||||
var path = this.<Comparable<Object>>column(root, column);
|
||||
checkComparable(path, column);
|
||||
checkComparable(value, column);
|
||||
predicates.add(builder.greaterThan(path, (Comparable<Object>) value));
|
||||
});
|
||||
}
|
||||
if (ObjectHelper.isNotEmpty(queryable.getLess())) {
|
||||
queryable.getLess().forEach((column, value) -> predicates.add(builder.lessThan(column(root, column), value)));
|
||||
queryable.getLess().forEach((column, value) -> {
|
||||
var path = this.<Comparable<Object>>column(root, column);
|
||||
checkComparable(path, column);
|
||||
checkComparable(value, column);
|
||||
predicates.add(builder.lessThan(path, (Comparable<Object>) value));
|
||||
});
|
||||
}
|
||||
if (ObjectHelper.isNotEmpty(queryable.getGreatEqual())) {
|
||||
queryable.getGreatEqual().forEach((column, value) -> predicates.add(builder.greaterThanOrEqualTo(column(root, column), value)));
|
||||
queryable.getGreatEqual().forEach((column, value) -> {
|
||||
var path = this.<Comparable<Object>>column(root, column);
|
||||
checkComparable(path, column);
|
||||
checkComparable(value, column);
|
||||
predicates.add(builder.greaterThanOrEqualTo(path, (Comparable<Object>) value));
|
||||
});
|
||||
}
|
||||
if (ObjectHelper.isNotEmpty(queryable.getLessEqual())) {
|
||||
queryable.getLessEqual().forEach((column, value) -> predicates.add(builder.lessThanOrEqualTo(column(root, column), value)));
|
||||
queryable.getLessEqual().forEach((column, value) -> {
|
||||
var path = this.<Comparable<Object>>column(root, column);
|
||||
checkComparable(path, column);
|
||||
checkComparable(value, column);
|
||||
predicates.add(builder.lessThanOrEqualTo(path, (Comparable<Object>) value));
|
||||
});
|
||||
}
|
||||
if (ObjectHelper.isNotEmpty(queryable.getIn())) {
|
||||
queryable.getIn().forEach((column, value) -> predicates.add(builder.in(column(root, column)).value(value)));
|
||||
@@ -161,26 +337,148 @@ public abstract class SimpleServiceSupport<ENTITY extends SimpleEntity> implemen
|
||||
queryable.getNotIn().forEach((column, value) -> predicates.add(builder.in(column(root, column)).value(value).not()));
|
||||
}
|
||||
if (ObjectHelper.isNotEmpty(queryable.getBetween())) {
|
||||
queryable.getBetween().forEach((column, value) -> predicates.add(builder.between(column(root, column), value.getStart(), value.getEnd())));
|
||||
queryable.getBetween().forEach((column, value) -> {
|
||||
var path = this.<Comparable<Object>>column(root, column);
|
||||
checkComparable(path, column);
|
||||
checkComparable(value, column);
|
||||
predicates.add(builder.between(column(root, column), (Comparable<Object>) value.getStart(), (Comparable<Object>) value.getEnd()));
|
||||
});
|
||||
}
|
||||
if (ObjectHelper.isNotEmpty(queryable.getNotBetween())) {
|
||||
queryable.getNotBetween().forEach((column, value) -> predicates.add(builder.between(column(root, column), value.getStart(), value.getEnd())));
|
||||
queryable.getNotBetween().forEach((column, value) -> {
|
||||
var path = this.<Comparable<Object>>column(root, column);
|
||||
checkComparable(path, column);
|
||||
checkComparable(value, column);
|
||||
predicates.add(builder.between(column(root, column), (Comparable<Object>) value.getStart(), (Comparable<Object>) value.getEnd()).not());
|
||||
});
|
||||
}
|
||||
return predicates;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查字段类型是否可比较
|
||||
*
|
||||
* @param path 字段路径
|
||||
* @param column 字段名称
|
||||
* @throws NotComparableException 当字段类型不可比较时抛出
|
||||
*/
|
||||
private void checkComparable(Path<?> path, String column) {
|
||||
if (!ObjectHelper.isComparable(path.getJavaType())) {
|
||||
throw new NotComparableException(column);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查值是否可比较
|
||||
*
|
||||
* @param value 值对象
|
||||
* @param column 字段名称
|
||||
* @throws NotComparableException 当值不可比较时抛出
|
||||
*/
|
||||
private void checkComparable(Object value, String column) {
|
||||
if (!ObjectHelper.isComparable(value)) {
|
||||
throw new NotComparableException(column);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查区间值是否可比较
|
||||
*
|
||||
* @param value 区间对象
|
||||
* @param column 字段名称
|
||||
* @throws NotComparableException 当区间值不可比较时抛出
|
||||
*/
|
||||
private void checkComparable(Query.Queryable.Between value, String column) {
|
||||
checkComparable(value.getStart(), column);
|
||||
checkComparable(value.getEnd(), column);
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查字段类型是否为集合
|
||||
*
|
||||
* @param path 字段路径
|
||||
* @param column 字段名称
|
||||
* @throws NotCollectionException 当字段类型不是集合时抛出
|
||||
*/
|
||||
private void checkCollection(Path<?> path, String column) {
|
||||
if (!ObjectHelper.isCollection(path.getJavaType())) {
|
||||
throw new NotCollectionException(column);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查值是否为集合
|
||||
*
|
||||
* @param value 值对象
|
||||
* @param column 字段名称
|
||||
* @throws NotCollectionException 当值不是集合时抛出
|
||||
*/
|
||||
private void checkCollection(Object value, String column) {
|
||||
if (!ObjectHelper.isCollection(value)) {
|
||||
throw new NotCollectionException(column);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查字段类型是否为字符串
|
||||
*
|
||||
* @param path 字段路径
|
||||
* @param column 字段名称
|
||||
* @throws NotStringException 当字段类型不是字符串时抛出
|
||||
*/
|
||||
private void checkString(Path<?> path, String column) {
|
||||
if (!ObjectHelper.isString(path.getJavaType())) {
|
||||
throw new NotStringException(column);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查值是否为字符串
|
||||
*
|
||||
* @param value 值对象
|
||||
* @param column 字段名称
|
||||
* @throws NotStringException 当值不是字符串时抛出
|
||||
*/
|
||||
private void checkString(Object value, String column) {
|
||||
if (!ObjectHelper.isString(value)) {
|
||||
throw new NotStringException(column);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建列表查询条件
|
||||
* <p>
|
||||
* 子类可以重写此方法以添加特定的查询条件。
|
||||
* 默认返回空列表,表示不添加额外条件。
|
||||
* </p>
|
||||
*
|
||||
* @param root JPA Criteria查询根节点
|
||||
* @param query JPA Criteria查询对象
|
||||
* @param builder JPA Criteria构建器
|
||||
* @return List<Predicate> 返回查询条件谓词列表
|
||||
*/
|
||||
protected List<Predicate> listPredicate(Root<ENTITY> root, CriteriaQuery<?> query, CriteriaBuilder builder) {
|
||||
return new ArrayList<>(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据查询条件分页获取实体列表
|
||||
* <p>
|
||||
* 支持复杂的查询条件和分页功能。
|
||||
* 默认分页参数:第1页,每页10条记录,按创建时间降序排列。
|
||||
* </p>
|
||||
*
|
||||
* @param listQuery 查询条件对象
|
||||
* @return Page<ENTITY> 返回分页查询结果
|
||||
*/
|
||||
@Override
|
||||
public Page<ENTITY> list(Query listQuery) {
|
||||
var pageRequest = PageRequest.of(DEFAULT_PAGE_INDEX - 1, DEFAULT_PAGE_SIZE, Sort.by("created_time").descending());
|
||||
var pageRequest = PageRequest.of(DEFAULT_PAGE_INDEX - 1, DEFAULT_PAGE_SIZE, Sort.by(SimpleEntity.Fields.createdTime).descending());
|
||||
if (ObjectHelper.isNotNull(listQuery.getPage())) {
|
||||
pageRequest = PageRequest.of(
|
||||
ObjectHelper.defaultIfNull(listQuery.getPage().getIndex(), DEFAULT_PAGE_INDEX) - 1,
|
||||
ObjectHelper.defaultIfNull(listQuery.getPage().getSize(), DEFAULT_PAGE_SIZE),
|
||||
Sort.by("create_time").descending()
|
||||
Sort.by(SimpleEntity.Fields.createdTime).descending()
|
||||
);
|
||||
}
|
||||
return repository.findAll(
|
||||
@@ -193,6 +491,15 @@ public abstract class SimpleServiceSupport<ENTITY extends SimpleEntity> implemen
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据ID获取实体详情(Optional包装)
|
||||
* <p>
|
||||
* 如果ID为空则返回空Optional,否则根据ID查询实体。
|
||||
* </p>
|
||||
*
|
||||
* @param id 实体ID
|
||||
* @return Optional<ENTITY> 返回实体详情的Optional包装
|
||||
*/
|
||||
@Override
|
||||
public Optional<ENTITY> detailOptional(Long id) {
|
||||
if (ObjectHelper.isNull(id)) {
|
||||
@@ -201,27 +508,64 @@ public abstract class SimpleServiceSupport<ENTITY extends SimpleEntity> implemen
|
||||
return repository.findOne(
|
||||
(root, query, builder) -> {
|
||||
var predicates = listPredicate(root, query, builder);
|
||||
predicates.add(builder.equal(root.get("id"), id));
|
||||
predicates.add(builder.equal(root.get(IdOnlyEntity.Fields.id), id));
|
||||
return builder.and(predicates.stream().filter(ObjectHelper::isNotNull).toArray(Predicate[]::new));
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据ID获取实体详情
|
||||
* <p>
|
||||
* 如果实体不存在则返回null。
|
||||
* </p>
|
||||
*
|
||||
* @param id 实体ID
|
||||
* @return ENTITY 返回实体详情,不存在时返回null
|
||||
*/
|
||||
@Override
|
||||
public ENTITY detail(Long id) {
|
||||
return detailOrNull(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据ID获取实体详情,不存在时抛出异常
|
||||
* <p>
|
||||
* 如果实体不存在则抛出IdNotFoundException异常。
|
||||
* </p>
|
||||
*
|
||||
* @param id 实体ID
|
||||
* @return ENTITY 返回实体详情
|
||||
* @throws IdNotFoundException 当实体不存在时抛出
|
||||
*/
|
||||
@Override
|
||||
public ENTITY detailOrThrow(Long id) {
|
||||
return detailOptional(id).orElseThrow(() -> new IdNotFoundException(id));
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据ID获取实体详情,不存在时返回null
|
||||
* <p>
|
||||
* 与[detail](file:///Users/lanyuanxiaoyao/Project/IdeaProjects/spring-boot-service-template/src/main/java/com/lanyuanxiaoyao/service/template/service/SimpleService.java#L21-L21)方法功能相同。
|
||||
* </p>
|
||||
*
|
||||
* @param id 实体ID
|
||||
* @return ENTITY 返回实体详情,不存在时返回null
|
||||
*/
|
||||
@Override
|
||||
public ENTITY detailOrNull(Long id) {
|
||||
return detailOptional(id).orElse(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据ID删除实体
|
||||
* <p>
|
||||
* 具有事务性,遇到任何异常都会回滚。
|
||||
* 如果ID为空则不执行任何操作。
|
||||
* </p>
|
||||
*
|
||||
* @param id 实体ID
|
||||
*/
|
||||
@Transactional(rollbackOn = Throwable.class)
|
||||
@Override
|
||||
public void remove(Long id) {
|
||||
@@ -230,13 +574,78 @@ public abstract class SimpleServiceSupport<ENTITY extends SimpleEntity> implemen
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* ID未找到异常
|
||||
* <p>
|
||||
* 当根据ID查询实体但实体不存在时抛出此异常。
|
||||
* </p>
|
||||
*/
|
||||
public static final class IdNotFoundException extends RuntimeException {
|
||||
/**
|
||||
* 构造函数(无参)
|
||||
*/
|
||||
public IdNotFoundException() {
|
||||
super("资源不存在");
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造函数(带ID参数)
|
||||
*
|
||||
* @param id 实体ID
|
||||
*/
|
||||
public IdNotFoundException(Long id) {
|
||||
super("ID为%d的资源不存在".formatted(id));
|
||||
super("ID为 %d 的资源不存在".formatted(id));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 不可比较异常
|
||||
* <p>
|
||||
* 当尝试对不可比较的字段或值执行比较操作时抛出此异常。
|
||||
* </p>
|
||||
*/
|
||||
public static final class NotComparableException extends RuntimeException {
|
||||
/**
|
||||
* 构造函数
|
||||
*
|
||||
* @param variable 变量名称
|
||||
*/
|
||||
public NotComparableException(String variable) {
|
||||
super("变量 %s 不能比较".formatted(variable));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 非集合异常
|
||||
* <p>
|
||||
* 当尝试对非集合类型的字段或值执行集合操作时抛出此异常。
|
||||
* </p>
|
||||
*/
|
||||
public static final class NotCollectionException extends RuntimeException {
|
||||
/**
|
||||
* 构造函数
|
||||
*
|
||||
* @param variable 变量名称
|
||||
*/
|
||||
public NotCollectionException(String variable) {
|
||||
super("变量 %s 不是集合".formatted(variable));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 非字符串异常
|
||||
* <p>
|
||||
* 当尝试对非字符串类型的字段或值执行字符串操作时抛出此异常。
|
||||
* </p>
|
||||
*/
|
||||
public static final class NotStringException extends RuntimeException {
|
||||
/**
|
||||
* 构造函数
|
||||
*
|
||||
* @param variable 变量名称
|
||||
*/
|
||||
public NotStringException(String variable) {
|
||||
super("变量 %s 不是字符串".formatted(variable));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -37,15 +37,63 @@ public class TestApplication {
|
||||
@EventListener(ApplicationReadyEvent.class)
|
||||
public void runTests() throws JsonProcessingException {
|
||||
// 增
|
||||
var cid1 = saveItem("company", "{\"name\": \"Apple\"}").get("data").asLong();
|
||||
var cid2 = saveItem("company", "{\"name\": \"Banana\"}").get("data").asLong();
|
||||
var cid3 = saveItem("company", "{\"name\": \"Cheery\"}").get("data").asLong();
|
||||
var cid1 = saveItem("company", "{\"name\": \"Apple\",\"members\": 10}").get("data").asLong();
|
||||
var cid2 = saveItem("company", "{\"name\": \"Banana\",\"members\": 20}").get("data").asLong();
|
||||
var cid3 = saveItem("company", "{\"name\": \"Cheery\",\"members\": 20}").get("data").asLong();
|
||||
|
||||
// 查
|
||||
var companies = listItems("company");
|
||||
Assert.isTrue(companies.at("/data/items").size() == 3, "数量错误");
|
||||
Assert.isTrue(companies.at("/data/total").asLong() == 3, "返回数量错误");
|
||||
|
||||
// language=JSON
|
||||
var companies2 = listItems("company", "{\n" +
|
||||
" \"page\": {\n" +
|
||||
" \"index\": 1,\n" +
|
||||
" \"size\": 2\n" +
|
||||
" }\n" +
|
||||
"}");
|
||||
Assert.isTrue(companies2.at("/data/items").size() == 2, "数量错误");
|
||||
Assert.isTrue(companies2.at("/data/total").asLong() == 3, "返回数量错误");
|
||||
// language=JSON
|
||||
var companies3 = listItems("company", "{\n" +
|
||||
" \"query\": {\n" +
|
||||
" \"notNullEqual\": [\n" +
|
||||
" \"name\"\n" +
|
||||
" ],\n" +
|
||||
" \"equal\": {\n" +
|
||||
" \"name\": \"Apple\"\n" +
|
||||
" },\n" +
|
||||
" \"like\": {\n" +
|
||||
" \"name\": \"Appl%\"\n" +
|
||||
" },\n" +
|
||||
" \"less\": {\n" +
|
||||
" \"members\": 50\n" +
|
||||
" },\n" +
|
||||
" \"greatEqual\": {\n" +
|
||||
" \"members\": 0\n" +
|
||||
" },\n" +
|
||||
" \"in\": {\n" +
|
||||
" \"name\": [\n" +
|
||||
" \"Apple\",\n" +
|
||||
" \"Banana\"\n" +
|
||||
" ]\n" +
|
||||
" },\n" +
|
||||
" \"between\": {\n" +
|
||||
" \"members\": {\n" +
|
||||
" \"start\": 0,\n" +
|
||||
" \"end\": 50\n" +
|
||||
" }\n" +
|
||||
" }\n" +
|
||||
" },\n" +
|
||||
" \"page\": {\n" +
|
||||
" \"index\": 1,\n" +
|
||||
" \"size\": 2\n" +
|
||||
" }\n" +
|
||||
"}");
|
||||
Assert.isTrue(companies3.at("/data/items").size() == 1, "数量错误");
|
||||
Assert.isTrue(companies3.at("/data/total").asLong() == 1, "返回数量错误");
|
||||
|
||||
var company1 = detailItem("company", cid1);
|
||||
Assert.isTrue(cid1 == company1.at("/data/id").asLong(), "id错误");
|
||||
Assert.isTrue("Apple".equals(company1.at("/data/name").asText()), "name错误");
|
||||
@@ -61,6 +109,21 @@ public class TestApplication {
|
||||
Assert.isTrue(listItems("company").at("/data/items").size() == 2, "数量错误");
|
||||
Assert.isTrue(listItems("company").at("/data/total").asLong() == 2, "返回数量错误");
|
||||
|
||||
log.info(listItems("company").toPrettyString());
|
||||
|
||||
var eid1 = saveItem("employee", "{\"name\": \"Tom\",\"age\": 18, \"companyId\": %d}".formatted(cid1)).get("data").asLong();
|
||||
var eid2 = saveItem("employee", "{\"name\": \"Jerry\",\"age\": 18, \"companyId\": %d}".formatted(cid1)).get("data").asLong();
|
||||
var eid3 = saveItem("employee", "{\"name\": \"Mike\",\"age\": 18, \"companyId\": %d}".formatted(cid2)).get("data").asLong();
|
||||
|
||||
var employees = listItems("employee");
|
||||
Assert.isTrue(employees.at("/data/items").size() == 3, "数量错误");
|
||||
Assert.isTrue(employees.at("/data/total").asLong() == 3, "返回数量错误");
|
||||
|
||||
var employee1 = detailItem("employee", eid1);
|
||||
Assert.isTrue(eid1 == employee1.at("/data/id").asLong(), "id错误");
|
||||
Assert.isTrue("Tom".equals(employee1.at("/data/name").asText()), "name错误");
|
||||
Assert.isTrue(18 == employee1.at("/data/age").asInt(), "age错误");
|
||||
|
||||
System.exit(0);
|
||||
}
|
||||
|
||||
@@ -91,6 +154,17 @@ public class TestApplication {
|
||||
return MAPPER.readTree(response.getBody());
|
||||
}
|
||||
|
||||
private JsonNode listItems(String path, String query) throws JsonProcessingException {
|
||||
var response = REST_CLIENT.postForEntity(
|
||||
"%s/%s/list".formatted(BASE_URL, path),
|
||||
new HttpEntity<>(query, headers()),
|
||||
String.class
|
||||
);
|
||||
Assert.isTrue(response.getStatusCode().is2xxSuccessful(), "请求失败");
|
||||
Assert.notNull(response.getBody(), "请求失败");
|
||||
return MAPPER.readTree(response.getBody());
|
||||
}
|
||||
|
||||
private JsonNode detailItem(String path, Long id) throws JsonProcessingException {
|
||||
var response = REST_CLIENT.getForEntity(
|
||||
"%s/%s/detail/%d".formatted(BASE_URL, path, id),
|
||||
|
||||
@@ -3,13 +3,15 @@ package com.lanyuanxiaoyao.service.template.controller;
|
||||
import com.lanyuanxiaoyao.service.template.entity.Company;
|
||||
import com.lanyuanxiaoyao.service.template.service.CompanyService;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.function.Function;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
import lombok.ToString;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
/**
|
||||
* @author lanyuanxiaoyao
|
||||
* @version 20250814
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("company")
|
||||
public class CompanyController extends SimpleControllerSupport<Company, CompanyController.SaveItem, CompanyController.ListItem, CompanyController.DetailItem> {
|
||||
@@ -18,141 +20,68 @@ public class CompanyController extends SimpleControllerSupport<Company, CompanyC
|
||||
}
|
||||
|
||||
@Override
|
||||
protected SaveItemMapper<Company, SaveItem> saveItemMapper() {
|
||||
protected Function<SaveItem, Company> saveItemMapper() {
|
||||
return item -> {
|
||||
var company = new Company();
|
||||
company.setId(item.getId());
|
||||
company.setName(item.getName());
|
||||
company.setMembers(item.getMembers());
|
||||
return company;
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ListItemMapper<Company, ListItem> listItemMapper() {
|
||||
return company -> {
|
||||
var item = new ListItem();
|
||||
item.setId(company.getId());
|
||||
item.setName(company.getName());
|
||||
return item;
|
||||
};
|
||||
protected Function<Company, ListItem> listItemMapper() {
|
||||
return company -> new ListItem(
|
||||
company.getId(),
|
||||
company.getName(),
|
||||
company.getMembers()
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected DetailItemMapper<Company, DetailItem> detailItemMapper() {
|
||||
return company -> {
|
||||
var item = new DetailItem();
|
||||
item.setId(company.getId());
|
||||
item.setName(company.getName());
|
||||
item.setCreatedTime(company.getCreatedTime());
|
||||
item.setModifiedTime(company.getModifiedTime());
|
||||
return item;
|
||||
};
|
||||
protected Function<Company, DetailItem> detailItemMapper() {
|
||||
return company -> new DetailItem(
|
||||
company.getId(),
|
||||
company.getName(),
|
||||
company.getMembers(),
|
||||
company.getCreatedTime(),
|
||||
company.getModifiedTime()
|
||||
);
|
||||
}
|
||||
|
||||
@Setter
|
||||
@Getter
|
||||
@ToString
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public static class SaveItem {
|
||||
private Long id;
|
||||
private String name;
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(Long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "SaveItem{" +
|
||||
"id=" + id +
|
||||
", name='" + name + '\'' +
|
||||
'}';
|
||||
}
|
||||
private Integer members;
|
||||
}
|
||||
|
||||
@Setter
|
||||
@Getter
|
||||
@ToString
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public static class ListItem {
|
||||
private Long id;
|
||||
private String name;
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(Long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ListItem{" +
|
||||
"id=" + id +
|
||||
", name='" + name + '\'' +
|
||||
'}';
|
||||
}
|
||||
private Integer members;
|
||||
}
|
||||
|
||||
@Setter
|
||||
@Getter
|
||||
@ToString
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public static class DetailItem {
|
||||
private Long id;
|
||||
private String name;
|
||||
private Integer members;
|
||||
private LocalDateTime createdTime;
|
||||
private LocalDateTime modifiedTime;
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(Long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public LocalDateTime getCreatedTime() {
|
||||
return createdTime;
|
||||
}
|
||||
|
||||
public void setCreatedTime(LocalDateTime createdTime) {
|
||||
this.createdTime = createdTime;
|
||||
}
|
||||
|
||||
public LocalDateTime getModifiedTime() {
|
||||
return modifiedTime;
|
||||
}
|
||||
|
||||
public void setModifiedTime(LocalDateTime modifiedTime) {
|
||||
this.modifiedTime = modifiedTime;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "DetailItem{" +
|
||||
"id=" + id +
|
||||
", name='" + name + '\'' +
|
||||
", createdTime=" + createdTime +
|
||||
", modifiedTime=" + modifiedTime +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
package com.lanyuanxiaoyao.service.template.controller;
|
||||
|
||||
import com.lanyuanxiaoyao.service.template.entity.Employee;
|
||||
import com.lanyuanxiaoyao.service.template.service.CompanyService;
|
||||
import com.lanyuanxiaoyao.service.template.service.EmployeeService;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.function.Function;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
import lombok.ToString;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("employee")
|
||||
public class EmployeeController extends SimpleControllerSupport<Employee, EmployeeController.SaveItem, EmployeeController.ListItem, EmployeeController.DetailItem> {
|
||||
private final CompanyService companyService;
|
||||
|
||||
public EmployeeController(EmployeeService service, CompanyService companyService) {
|
||||
super(service);
|
||||
this.companyService = companyService;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Function<SaveItem, Employee> saveItemMapper() {
|
||||
return item -> {
|
||||
var employee = new Employee();
|
||||
employee.setId(item.getId());
|
||||
employee.setName(item.getName());
|
||||
employee.setAge(item.getAge());
|
||||
employee.setRole(Employee.Role.USER);
|
||||
employee.setCompany(companyService.detailOrThrow(item.getCompanyId()));
|
||||
return employee;
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Function<Employee, ListItem> listItemMapper() {
|
||||
return employee -> new ListItem(
|
||||
employee.getId(),
|
||||
employee.getName(),
|
||||
employee.getAge(),
|
||||
employee.getRole()
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Function<Employee, DetailItem> detailItemMapper() {
|
||||
return employee -> new DetailItem(
|
||||
employee.getId(),
|
||||
employee.getCompany().getId(),
|
||||
employee.getName(),
|
||||
employee.getAge(),
|
||||
employee.getRole(),
|
||||
employee.getCreatedTime(),
|
||||
employee.getModifiedTime()
|
||||
);
|
||||
}
|
||||
|
||||
@Setter
|
||||
@Getter
|
||||
@ToString
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public static class SaveItem {
|
||||
private Long id;
|
||||
private Long companyId;
|
||||
private String name;
|
||||
private Integer age;
|
||||
}
|
||||
|
||||
@Setter
|
||||
@Getter
|
||||
@ToString
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public static class ListItem {
|
||||
private Long id;
|
||||
private String name;
|
||||
private Integer age;
|
||||
private Employee.Role role;
|
||||
}
|
||||
|
||||
@Setter
|
||||
@Getter
|
||||
@ToString
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public static class DetailItem {
|
||||
private Long id;
|
||||
private Long companyId;
|
||||
private String name;
|
||||
private Integer age;
|
||||
private Employee.Role role;
|
||||
private LocalDateTime createdTime;
|
||||
private LocalDateTime modifiedTime;
|
||||
}
|
||||
}
|
||||
@@ -1,31 +1,35 @@
|
||||
package com.lanyuanxiaoyao.service.template.entity;
|
||||
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.EntityListeners;
|
||||
import jakarta.persistence.OneToMany;
|
||||
import java.util.Set;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import lombok.ToString;
|
||||
import lombok.experimental.FieldNameConstants;
|
||||
import org.hibernate.annotations.Comment;
|
||||
import org.hibernate.annotations.DynamicUpdate;
|
||||
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
|
||||
|
||||
|
||||
@Setter
|
||||
@Getter
|
||||
@ToString(callSuper = true)
|
||||
@FieldNameConstants
|
||||
@Entity
|
||||
@DynamicUpdate
|
||||
@EntityListeners(AuditingEntityListener.class)
|
||||
@Comment("企业")
|
||||
public class Company extends SimpleEntity {
|
||||
@Column(nullable = false)
|
||||
@Comment("名称")
|
||||
private String name;
|
||||
@Column(nullable = false)
|
||||
@Comment("成员数")
|
||||
private Integer members;
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Company{" +
|
||||
"name='" + name + '\'' +
|
||||
"} " + super.toString();
|
||||
}
|
||||
@OneToMany(mappedBy = "company")
|
||||
@ToString.Exclude
|
||||
private Set<Employee> employees;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
package com.lanyuanxiaoyao.service.template.entity;
|
||||
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.ConstraintMode;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.EntityListeners;
|
||||
import jakarta.persistence.EnumType;
|
||||
import jakarta.persistence.Enumerated;
|
||||
import jakarta.persistence.FetchType;
|
||||
import jakarta.persistence.ForeignKey;
|
||||
import jakarta.persistence.JoinColumn;
|
||||
import jakarta.persistence.ManyToOne;
|
||||
import jakarta.persistence.NamedAttributeNode;
|
||||
import jakarta.persistence.NamedEntityGraph;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import lombok.ToString;
|
||||
import lombok.experimental.FieldNameConstants;
|
||||
import org.hibernate.annotations.Comment;
|
||||
import org.hibernate.annotations.DynamicUpdate;
|
||||
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
|
||||
|
||||
@Setter
|
||||
@Getter
|
||||
@ToString(callSuper = true)
|
||||
@FieldNameConstants
|
||||
@Entity
|
||||
@DynamicUpdate
|
||||
@EntityListeners(AuditingEntityListener.class)
|
||||
@NamedEntityGraph(name = "employee.detail", attributeNodes = {
|
||||
@NamedAttributeNode("company")
|
||||
})
|
||||
@Comment("员工")
|
||||
public class Employee extends SimpleEntity {
|
||||
@Column(nullable = false)
|
||||
@Comment("名称")
|
||||
private String name;
|
||||
@Column(nullable = false)
|
||||
@Comment("年龄")
|
||||
private Integer age;
|
||||
@Column(nullable = false)
|
||||
@Enumerated(EnumType.STRING)
|
||||
@Comment("角色")
|
||||
private Role role;
|
||||
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
@JoinColumn(nullable = false, foreignKey = @ForeignKey(ConstraintMode.NO_CONSTRAINT))
|
||||
@ToString.Exclude
|
||||
private Company company;
|
||||
|
||||
public enum Role {
|
||||
USER,
|
||||
ADMIN,
|
||||
}
|
||||
}
|
||||
@@ -3,10 +3,6 @@ package com.lanyuanxiaoyao.service.template.repository;
|
||||
import com.lanyuanxiaoyao.service.template.entity.Company;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
/**
|
||||
* @author lanyuanxiaoyao
|
||||
* @version 20250814
|
||||
*/
|
||||
@Repository
|
||||
public interface CompanyRepository extends SimpleRepository<Company, Long> {
|
||||
}
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
package com.lanyuanxiaoyao.service.template.repository;
|
||||
|
||||
import com.lanyuanxiaoyao.service.template.entity.Employee;
|
||||
import java.util.Optional;
|
||||
import org.springframework.data.jpa.domain.Specification;
|
||||
import org.springframework.data.jpa.repository.EntityGraph;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
@SuppressWarnings("NullableProblems")
|
||||
@Repository
|
||||
public interface EmployeeRepository extends SimpleRepository<Employee, Long> {
|
||||
@EntityGraph(value = "employee.detail", type = EntityGraph.EntityGraphType.FETCH)
|
||||
@Override
|
||||
Optional<Employee> findOne(Specification<Employee> specification);
|
||||
}
|
||||
@@ -2,13 +2,8 @@ package com.lanyuanxiaoyao.service.template.service;
|
||||
|
||||
import com.lanyuanxiaoyao.service.template.entity.Company;
|
||||
import com.lanyuanxiaoyao.service.template.repository.CompanyRepository;
|
||||
import com.lanyuanxiaoyao.service.template.repository.SimpleRepository;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
/**
|
||||
* @author lanyuanxiaoyao
|
||||
* @version 20250814
|
||||
*/
|
||||
@Service
|
||||
public class CompanyService extends SimpleServiceSupport<Company> {
|
||||
public CompanyService(CompanyRepository repository) {
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
package com.lanyuanxiaoyao.service.template.service;
|
||||
|
||||
import com.lanyuanxiaoyao.service.template.entity.Employee;
|
||||
import com.lanyuanxiaoyao.service.template.repository.EmployeeRepository;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Service
|
||||
public class EmployeeService extends SimpleServiceSupport<Employee> {
|
||||
public EmployeeService(EmployeeRepository repository) {
|
||||
super(repository);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user