1
0

Compare commits

...

10 Commits

30 changed files with 1643 additions and 1441 deletions

View File

@@ -96,6 +96,11 @@
<artifactId>lombok</artifactId> <artifactId>lombok</artifactId>
<version>1.18.36</version> <version>1.18.36</version>
</path> </path>
<path>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-jpamodelgen</artifactId>
<version>6.6.8.Final</version>
</path>
<path> <path>
<groupId>io.github.openfeign.querydsl</groupId> <groupId>io.github.openfeign.querydsl</groupId>
<artifactId>querydsl-apt</artifactId> <artifactId>querydsl-apt</artifactId>

View File

@@ -1,7 +1,5 @@
package com.lanyuanxiaoyao.service.template.controller; package com.lanyuanxiaoyao.service.template.controller;
import com.lanyuanxiaoyao.service.template.controller.response.GlobalResponse;
/** /**
* 详情控制器接口,用于定义统一的获取实体详情的接口规范 * 详情控制器接口,用于定义统一的获取实体详情的接口规范
* *
@@ -16,14 +14,14 @@ import com.lanyuanxiaoyao.service.template.controller.response.GlobalResponse;
* @author lanyuanxiaoyao * @author lanyuanxiaoyao
*/ */
public interface DetailController<DETAIL_ITEM> { public interface DetailController<DETAIL_ITEM> {
String DETAIL = "/detail/{id}"; String DETAIL = "/detail/{id}";
/** /**
* 根据ID获取实体详情 * 根据ID获取实体详情
* *
* @param id 实体ID * @param id 实体ID
* @return GlobalResponse<DETAIL_ITEM> 返回实体详情 * @return GlobalResponse<DETAIL_ITEM> 返回实体详情
* @throws Exception 查询过程中可能抛出的异常 * @throws Exception 查询过程中可能抛出的异常
*/ */
GlobalResponse<DETAIL_ITEM> detail(Long id) throws Exception; GlobalResponse<DETAIL_ITEM> detail(Long id) throws Exception;
} }

View File

@@ -0,0 +1,77 @@
package com.lanyuanxiaoyao.service.template.controller;
import java.util.Map;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
@Setter
@Getter
@ToString
public class GlobalResponse<T> {
private static final int SUCCESS_STATUS = 0;
private static final int ERROR_STATUS = 500;
private static final String SUCCESS_MESSAGE = "OK";
private static final String ERROR_MESSAGE = "ERROR";
private Integer status;
private String message;
private T data;
private GlobalResponse() {
this(SUCCESS_STATUS, SUCCESS_MESSAGE, null);
}
private GlobalResponse(Integer status, String message) {
this(status, message, null);
}
private GlobalResponse(Integer status, String message, T data) {
this.status = status;
this.message = message;
this.data = data;
}
public static GlobalResponse<Object> responseError() {
return responseError(ERROR_MESSAGE);
}
public static GlobalResponse<Object> responseError(String message) {
return new GlobalResponse<>(ERROR_STATUS, message);
}
public static GlobalResponse<Object> responseSuccess() {
return responseSuccess(SUCCESS_MESSAGE);
}
public static GlobalResponse<Object> responseSuccess(String message) {
return responseSuccess(message, null);
}
public static <E> GlobalResponse<E> responseSuccess(E data) {
return responseSuccess(SUCCESS_MESSAGE, data);
}
public static <E> GlobalResponse<E> responseSuccess(String message, E data) {
return new GlobalResponse<>(SUCCESS_STATUS, message, data);
}
public static GlobalResponse<Map<String, Object>> responseMapData(Map<String, Object> data) {
return responseSuccess(data);
}
public static GlobalResponse<Map<String, Object>> responseMapData(String key, Object value) {
return responseMapData(Map.of(key, value));
}
public static <T> GlobalResponse<Map<String, Object>> responseCrudData(Iterable<T> data, Integer total) {
return responseCrudData(data, total.longValue());
}
public static <T> GlobalResponse<Map<String, Object>> responseCrudData(Iterable<T> data, Long total) {
return responseMapData(Map.of("items", data, "total", total));
}
public static GlobalResponse<Map<String, Object>> responseDetailData(Object detail) {
return responseMapData("detail", detail);
}
}

View File

@@ -1,6 +1,6 @@
package com.lanyuanxiaoyao.service.template.controller; package com.lanyuanxiaoyao.service.template.controller;
import com.lanyuanxiaoyao.service.template.controller.response.GlobalCrudResponse; import java.util.Map;
/** /**
* 列表控制器接口,用于定义统一的获取实体列表的接口规范 * 列表控制器接口,用于定义统一的获取实体列表的接口规范
@@ -57,22 +57,22 @@ import com.lanyuanxiaoyao.service.template.controller.response.GlobalCrudRespons
* @author lanyuanxiaoyao * @author lanyuanxiaoyao
*/ */
public interface ListController<LIST_ITEM> { public interface ListController<LIST_ITEM> {
String LIST = "/list"; String LIST = "/list";
/** /**
* 获取所有实体列表 * 获取所有实体列表
* *
* @return GlobalCrudResponse<LIST_ITEM> 返回实体列表 * @return GlobalCrudResponse<LIST_ITEM> 返回实体列表
* @throws Exception 查询过程中可能抛出的异常 * @throws Exception 查询过程中可能抛出的异常
*/ */
GlobalCrudResponse<LIST_ITEM> list() throws Exception; GlobalResponse<Map<String, Object>> list() throws Exception;
/** /**
* 根据查询条件获取实体列表 * 根据查询条件获取实体列表
* *
* @param query 查询条件对象 * @param query 查询条件对象
* @return GlobalCrudResponse<LIST_ITEM> 返回符合条件的实体列表 * @return GlobalCrudResponse<LIST_ITEM> 返回符合条件的实体列表
* @throws Exception 查询过程中可能抛出的异常 * @throws Exception 查询过程中可能抛出的异常
*/ */
GlobalCrudResponse<LIST_ITEM> list(Query query) throws Exception; GlobalResponse<Map<String, Object>> list(Query query) throws Exception;
} }

View File

@@ -9,7 +9,7 @@ import lombok.ToString;
/** /**
* 查询条件封装类,用于构建复杂的查询条件 * 查询条件封装类,用于构建复杂的查询条件
* 包含查询条件、排序条件和分页条件 * 包含查询条件、排序条件和分页条件
* *
* <p>前端传入的JSON格式示例:</p> * <p>前端传入的JSON格式示例:</p>
* <pre> * <pre>
* { * {
@@ -45,7 +45,7 @@ import lombok.ToString;
* } * }
* } * }
* </pre> * </pre>
* *
* <p>查询条件说明:</p> * <p>查询条件说明:</p>
* <ul> * <ul>
* <li>nullEqual: 字段值为null的条件</li> * <li>nullEqual: 字段值为null的条件</li>
@@ -70,154 +70,178 @@ import lombok.ToString;
@Getter @Getter
@ToString @ToString
public class Query { public class Query {
/**
* 查询条件
*/
private Queryable query;
/**
* 排序条件列表
*/
private List<Sortable> sort;
/**
* 分页条件
*/
private Pageable page;
/**
* 可查询条件类,封装各种查询条件
*/
@Setter
@Getter
@ToString
public static class Queryable {
/** /**
* 查询条件 * 指定字段值为null的条件列表
*/ */
private Queryable query; private List<String> nullEqual;
/** /**
* 排序条件列表 * 指定字段值不为null的条件列表
*/ */
private List<Sortable> sort; private List<String> notNullEqual;
/** /**
* 分页条件 * 指定字段值为空的条件列表(如空字符串、空集合等)
*/ */
private Pageable page; 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, String> contain;
/**
* 指定字段不包含指定字符串的条件映射(字段名 -> 不包含值)
*/
private Map<String, String> notContain;
/**
* 指定字段以指定字符串开头的条件映射(字段名 -> 开头值)
*/
private Map<String, String> startWith;
/**
* 指定字段不以指定字符串开头的条件映射(字段名 -> 不开头值)
*/
private Map<String, String> notStartWith;
/**
* 指定字段以指定字符串结尾的条件映射(字段名 -> 结尾值)
*/
private Map<String, String> endWith;
/**
* 指定字段不以指定字符串结尾的条件映射(字段名 -> 不结尾值)
*/
private Map<String, String> notEndWith;
/**
* 指定字段大于条件的映射(字段名 -> 值)
*/
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>> inside;
/**
* 指定字段值不在指定范围内的条件映射(字段名 -> 值列表)
*/
private Map<String, List<Object>> notInside;
/**
* 指定字段值在指定区间内的条件映射(字段名 -> 区间范围)
*/
private Map<String, Between> between;
/**
* 指定字段值不在指定区间内的条件映射(字段名 -> 区间范围)
*/
private Map<String, Between> notBetween;
/** /**
* 可查询条件类,封装各种查询条件 * 区间范围类,用于表示起始值和结束值
*/ */
@Setter @Setter
@Getter @Getter
@ToString @ToString
public static class Queryable { public static class Between {
/** /**
* 指定字段值为null的条件列表 * 起始值
*/ */
private List<String> nullEqual; private Object start;
/** /**
* 指定字段值不为null的条件列表 * 结束值
*/ */
private List<String> notNullEqual; private Object end;
/**
* 指定字段值为空的条件列表(如空字符串、空集合等)
*/
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, 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;
/**
* 区间范围类,用于表示起始值和结束值
*/
@Setter
@Getter
@ToString
public static class Between {
/**
* 起始值
*/
private Object start;
/**
* 结束值
*/
private Object end;
}
} }
}
/**
* 可排序条件类,用于指定排序字段和排序方向
*/
@Setter
@Getter
@ToString
public static class Sortable {
/**
* 排序字段名
*/
private String column;
/**
* 排序方向
*/
private Direction direction;
/** /**
* 可排序条件类,用于指定排序字段和排序方向 * 排序方向枚举
*/ */
@Setter public enum Direction {
@Getter /**
@ToString * 升序排列
public static class Sortable { */
/** ASC,
* 排序字段名 /**
*/ * 降序排列
private String column; */
/** DESC,
* 排序方向
*/
private Direction direction;
/**
* 排序方向枚举
*/
public enum Direction {
/**
* 升序排列
*/
ASC,
/**
* 降序排列
*/
DESC,
}
} }
}
/**
* 可分页条件类,用于指定分页参数
*/
@Setter
@Getter
@ToString
public static class Pageable {
/** /**
* 可分页条件类,用于指定分页参数 * 页码索引从0开始
*/ */
@Setter private Integer index;
@Getter /**
@ToString * 每页大小
public static class Pageable { */
/** private Integer size;
* 页码索引从0开始 }
*/
private Integer index;
/**
* 每页大小
*/
private Integer size;
}
} }

View File

@@ -1,7 +1,5 @@
package com.lanyuanxiaoyao.service.template.controller; package com.lanyuanxiaoyao.service.template.controller;
import com.lanyuanxiaoyao.service.template.controller.response.GlobalResponse;
/** /**
* 删除控制器接口,用于定义统一的删除实体对象的接口规范 * 删除控制器接口,用于定义统一的删除实体对象的接口规范
* *
@@ -15,14 +13,14 @@ import com.lanyuanxiaoyao.service.template.controller.response.GlobalResponse;
* @author lanyuanxiaoyao * @author lanyuanxiaoyao
*/ */
public interface RemoveController { public interface RemoveController {
String REMOVE = "/remove/{id}"; String REMOVE = "/remove/{id}";
/** /**
* 根据ID删除实体对象 * 根据ID删除实体对象
* *
* @param id 需要删除的实体ID * @param id 需要删除的实体ID
* @return GlobalResponse<Object> 返回删除结果 * @return GlobalResponse<Object> 返回删除结果
* @throws Exception 删除过程中可能抛出的异常 * @throws Exception 删除过程中可能抛出的异常
*/ */
GlobalResponse<Object> remove(Long id) throws Exception; GlobalResponse<Object> remove(Long id) throws Exception;
} }

View File

@@ -1,7 +1,5 @@
package com.lanyuanxiaoyao.service.template.controller; package com.lanyuanxiaoyao.service.template.controller;
import com.lanyuanxiaoyao.service.template.controller.response.GlobalResponse;
/** /**
* 保存控制器接口,用于定义统一的保存实体对象的接口规范 * 保存控制器接口,用于定义统一的保存实体对象的接口规范
* *
@@ -19,14 +17,14 @@ import com.lanyuanxiaoyao.service.template.controller.response.GlobalResponse;
* @author lanyuanxiaoyao * @author lanyuanxiaoyao
*/ */
public interface SaveController<SAVE_ITEM> { public interface SaveController<SAVE_ITEM> {
String SAVE = "/save"; String SAVE = "/save";
/** /**
* 保存实体对象 * 保存实体对象
* *
* @param item 需要保存的实体对象 * @param item 需要保存的实体对象
* @return GlobalResponse<Long> 返回保存后的实体ID * @return GlobalResponse<Long> 返回保存后的实体ID
* @throws Exception 保存过程中可能抛出的异常 * @throws Exception 保存过程中可能抛出的异常
*/ */
GlobalResponse<Long> save(SAVE_ITEM item) throws Exception; GlobalResponse<Long> save(SAVE_ITEM item) throws Exception;
} }

View File

@@ -1,11 +1,10 @@
package com.lanyuanxiaoyao.service.template.controller; package com.lanyuanxiaoyao.service.template.controller;
import com.lanyuanxiaoyao.service.template.controller.response.GlobalCrudResponse;
import com.lanyuanxiaoyao.service.template.controller.response.GlobalResponse;
import com.lanyuanxiaoyao.service.template.entity.SimpleEntity; import com.lanyuanxiaoyao.service.template.entity.SimpleEntity;
import com.lanyuanxiaoyao.service.template.helper.ObjectHelper; import com.lanyuanxiaoyao.service.template.helper.ObjectHelper;
import com.lanyuanxiaoyao.service.template.service.SimpleServiceSupport; import com.lanyuanxiaoyao.service.template.service.SimpleServiceSupport;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.function.Function; import java.util.function.Function;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.GetMapping;
@@ -87,140 +86,140 @@ import org.springframework.web.bind.annotation.RequestBody;
* </ul> * </ul>
* </p> * </p>
* *
* @param <ENTITY> 实体类型必须继承SimpleEntity * @param <ENTITY> 实体类型必须继承SimpleEntity
* @param <SAVE_ITEM> 保存项类型 * @param <SAVE_ITEM> 保存项类型
* @param <LIST_ITEM> 列表项类型 * @param <LIST_ITEM> 列表项类型
* @param <DETAIL_ITEM> 详情项类型 * @param <DETAIL_ITEM> 详情项类型
* @author lanyuanxiaoyao * @author lanyuanxiaoyao
*/ */
@Slf4j @Slf4j
public abstract class SimpleControllerSupport<ENTITY extends SimpleEntity, SAVE_ITEM, LIST_ITEM, DETAIL_ITEM> implements SimpleController<SAVE_ITEM, LIST_ITEM, DETAIL_ITEM> { 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; protected final SimpleServiceSupport<ENTITY> service;
/** /**
* 构造函数 * 构造函数
* *
* @param service 简单服务支持类实例 * @param service 简单服务支持类实例
*/ */
public SimpleControllerSupport(SimpleServiceSupport<ENTITY> service) { public SimpleControllerSupport(SimpleServiceSupport<ENTITY> service) {
this.service = 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.apply(item)));
}
/**
* 获取所有实体列表
*
* @return GlobalCrudResponse<LIST_ITEM> 返回实体列表
* @throws Exception 查询过程中可能抛出的异常
*/
@GetMapping(LIST)
@Override
public GlobalResponse<Map<String, Object>> list() throws Exception {
var mapper = listItemMapper();
var result = service.list();
return GlobalResponse.responseCrudData(
result
.stream()
.map(entity -> {
try {
return mapper.apply(entity);
} catch (Exception e) {
throw new RuntimeException(e);
}
})
.toList(),
result.size()
);
}
/**
* 根据查询条件获取实体列表
*
* @param query 查询条件对象
* @return GlobalCrudResponse<LIST_ITEM> 返回符合条件的实体列表
* @throws Exception 查询过程中可能抛出的异常
*/
@PostMapping(LIST)
@Override
public GlobalResponse<Map<String, Object>> list(@RequestBody Query query) throws Exception {
if (ObjectHelper.isNull(query)) {
return GlobalResponse.responseCrudData(List.of(), 0);
} }
var mapper = listItemMapper();
var result = service.list(query);
return GlobalResponse.responseCrudData(
result.get()
.map(entity -> {
try {
return mapper.apply(entity);
} catch (Exception e) {
throw new RuntimeException(e);
}
})
.toList(),
result.getTotalElements()
);
}
/** /**
* 保存实体对象 * 根据ID获取实体详情
* *
* @param item 需要保存的项 * @param id 实体ID
* @return GlobalResponse<Long> 返回保存后的实体ID * @return GlobalResponse<DETAIL_ITEM> 返回实体详情
* @throws Exception 保存过程中可能抛出的异常 * @throws Exception 查询过程中可能抛出的异常
*/ */
@PostMapping(SAVE) @GetMapping(DETAIL)
@Override @Override
public GlobalResponse<Long> save(@RequestBody SAVE_ITEM item) throws Exception { public GlobalResponse<DETAIL_ITEM> detail(@PathVariable("id") Long id) throws Exception {
var mapper = saveItemMapper(); var mapper = detailItemMapper();
return GlobalResponse.responseSuccess(service.save(mapper.apply(item))); return GlobalResponse.responseSuccess(mapper.apply(service.detailOrThrow(id)));
} }
/** /**
* 获取所有实体列表 * 根据ID删除实体对象
* *
* @return GlobalCrudResponse<LIST_ITEM> 返回实体列表 * @param id 需要删除的实体ID
* @throws Exception 查询过程中可能抛出的异常 * @return GlobalResponse<Object> 返回删除结果
*/ * @throws Exception 删除过程中可能抛出的异常
@GetMapping(LIST) */
@Override @GetMapping(REMOVE)
public GlobalCrudResponse<LIST_ITEM> list() throws Exception { @Override
var mapper = listItemMapper(); public GlobalResponse<Object> remove(@PathVariable("id") Long id) throws Exception {
var result = service.list(); service.remove(id);
return GlobalCrudResponse.responseCrudData( return GlobalResponse.responseSuccess();
result }
.stream()
.map(entity -> {
try {
return mapper.apply(entity);
} catch (Exception e) {
throw new RuntimeException(e);
}
})
.toList(),
result.size()
);
}
/** /**
* 根据查询条件获取实体列表 * 保存项映射器,将保存项转换为实体对象
* *
* @param query 查询条件对象 * @return Function<SAVE_ITEM, ENTITY> 保存项到实体的转换函数
* @return GlobalCrudResponse<LIST_ITEM> 返回符合条件的实体列表 */
* @throws Exception 查询过程中可能抛出的异常 protected abstract Function<SAVE_ITEM, ENTITY> saveItemMapper();
*/
@PostMapping(LIST)
@Override
public GlobalCrudResponse<LIST_ITEM> list(@RequestBody Query query) throws Exception {
if (ObjectHelper.isNull(query)) {
return GlobalCrudResponse.responseCrudData(List.of(), 0);
}
var mapper = listItemMapper();
var result = service.list(query);
return GlobalCrudResponse.responseCrudData(
result.get()
.map(entity -> {
try {
return mapper.apply(entity);
} catch (Exception e) {
throw new RuntimeException(e);
}
})
.toList(),
result.getTotalElements()
);
}
/** /**
* 根据ID获取实体详情 * 列表项映射器,将实体对象转换为列表项
* *
* @param id 实体ID * @return Function<ENTITY, LIST_ITEM> 实体到列表项的转换函数
* @return GlobalResponse<DETAIL_ITEM> 返回实体详情 */
* @throws Exception 查询过程中可能抛出的异常 protected abstract Function<ENTITY, LIST_ITEM> listItemMapper();
*/
@GetMapping(DETAIL)
@Override
public GlobalResponse<DETAIL_ITEM> detail(@PathVariable("id") Long id) throws Exception {
var mapper = detailItemMapper();
return GlobalResponse.responseSuccess(mapper.apply(service.detailOrThrow(id)));
}
/** /**
* 根据ID删除实体对象 * 详情项映射器,将实体对象转换为详情项
* *
* @param id 需要删除的实体ID * @return Function<ENTITY, DETAIL_ITEM> 实体到详情项的转换函数
* @return GlobalResponse<Object> 返回删除结果 */
* @throws Exception 删除过程中可能抛出的异常 protected abstract Function<ENTITY, DETAIL_ITEM> detailItemMapper();
*/
@GetMapping(REMOVE)
@Override
public GlobalResponse<Object> remove(@PathVariable("id") Long id) throws Exception {
service.remove(id);
return GlobalResponse.responseSuccess();
}
/**
* 保存项映射器,将保存项转换为实体对象
*
* @return Function<SAVE_ITEM, ENTITY> 保存项到实体的转换函数
*/
protected abstract Function<SAVE_ITEM, ENTITY> saveItemMapper();
/**
* 列表项映射器,将实体对象转换为列表项
*
* @return Function<ENTITY, LIST_ITEM> 实体到列表项的转换函数
*/
protected abstract Function<ENTITY, LIST_ITEM> listItemMapper();
/**
* 详情项映射器,将实体对象转换为详情项
*
* @return Function<ENTITY, DETAIL_ITEM> 实体到详情项的转换函数
*/
protected abstract Function<ENTITY, DETAIL_ITEM> detailItemMapper();
} }

View File

@@ -1,24 +0,0 @@
package com.lanyuanxiaoyao.service.template.controller.response;
public class GlobalCrudResponse<T> extends GlobalMapResponse {
public void setData(Iterable<T> list) {
setData("items", list);
}
public void setTotal(Long total) {
setData("total", total);
}
public void setTotal(Integer total) {
setTotal(total.longValue());
}
public void setData(Iterable<T> list, Long total) {
setData(list);
setTotal(total);
}
public void setData(Iterable<T> list, Integer total) {
setData(list, total.longValue());
}
}

View File

@@ -1,7 +0,0 @@
package com.lanyuanxiaoyao.service.template.controller.response;
public class GlobalDetailResponse extends GlobalMapResponse {
public void setDetail(Object detail) {
setData("detail", detail);
}
}

View File

@@ -1,15 +0,0 @@
package com.lanyuanxiaoyao.service.template.controller.response;
import java.util.HashMap;
import java.util.Map;
public class GlobalMapResponse extends GlobalResponse<Map<String, Object>> {
public GlobalMapResponse() {
setData(new HashMap<>());
}
public GlobalMapResponse setData(String key, Object value) {
getData().put(key, value);
return this;
}
}

View File

@@ -1,105 +0,0 @@
package com.lanyuanxiaoyao.service.template.controller.response;
import java.util.Map;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
@Setter
@Getter
@ToString
public class GlobalResponse<T> {
private static final int SUCCESS_STATUS = 0;
private static final int ERROR_STATUS = 500;
private static final String SUCCESS_MESSAGE = "OK";
private static final String ERROR_MESSAGE = "ERROR";
private Integer status;
private String message;
private T data;
public static GlobalResponse<Object> responseError() {
return responseError(ERROR_MESSAGE);
}
public static GlobalResponse<Object> responseError(String message) {
GlobalResponse<Object> response = new GlobalResponse<>();
response.setStatus(ERROR_STATUS);
response.setMessage(message);
return response;
}
public static GlobalResponse<Object> responseSuccess() {
GlobalResponse<Object> response = new GlobalResponse<>();
response.setStatus(SUCCESS_STATUS);
response.setMessage(SUCCESS_MESSAGE);
return response;
}
public static GlobalResponse<Object> responseSuccess(String message) {
GlobalResponse<Object> response = new GlobalResponse<>();
response.setStatus(SUCCESS_STATUS);
response.setMessage(message);
return response;
}
public static <E> GlobalResponse<E> responseSuccess(String message, E data) {
GlobalResponse<E> response = new GlobalResponse<>();
response.setStatus(SUCCESS_STATUS);
response.setMessage(message);
response.setData(data);
return response;
}
public static <E> GlobalResponse<E> responseSuccess(E data) {
GlobalResponse<E> response = new GlobalResponse<>();
response.setStatus(SUCCESS_STATUS);
response.setMessage(SUCCESS_MESSAGE);
response.setData(data);
return response;
}
public static GlobalMapResponse responseMapData() {
GlobalMapResponse response = new GlobalMapResponse();
response.setStatus(SUCCESS_STATUS);
response.setMessage(SUCCESS_MESSAGE);
return response;
}
public static GlobalMapResponse responseMapData(Map<String, Object> data) {
GlobalMapResponse response = responseMapData();
response.setData(data);
return response;
}
public static GlobalMapResponse responseMapData(String key, Object value) {
GlobalMapResponse response = responseMapData();
response.setData(key, value);
return response;
}
public static <T> GlobalCrudResponse<T> responseCrudData(Iterable<T> data) {
GlobalCrudResponse<T> response = new GlobalCrudResponse<>();
response.setStatus(SUCCESS_STATUS);
response.setMessage(SUCCESS_MESSAGE);
response.setData(data);
return response;
}
public static <T> GlobalCrudResponse<T> responseCrudData(Iterable<T> data, Integer total) {
GlobalCrudResponse<T> response = responseCrudData(data);
response.setTotal(total);
return response;
}
public static <T> GlobalCrudResponse<T> responseCrudData(Iterable<T> data, Long total) {
GlobalCrudResponse<T> response = responseCrudData(data);
response.setTotal(total);
return response;
}
public static GlobalDetailResponse responseDetailData(Object detail) {
GlobalDetailResponse response = new GlobalDetailResponse();
response.setDetail(detail);
return response;
}
}

View File

@@ -38,14 +38,14 @@ import org.springframework.data.jpa.domain.support.AuditingEntityListener;
@MappedSuperclass @MappedSuperclass
@EntityListeners(AuditingEntityListener.class) @EntityListeners(AuditingEntityListener.class)
public class IdOnlyEntity { public class IdOnlyEntity {
/** /**
* 实体唯一标识符 * 实体唯一标识符
* <p> * <p>
* 使用Snowflake算法生成的Long类型ID保证全局唯一性。 * 使用Snowflake算法生成的Long类型ID保证全局唯一性。
* </p> * </p>
*/ */
@Id @Id
@SnowflakeId @SnowflakeId
@Comment("记录唯一标记") @Comment("记录唯一标记")
private Long id; private Long id;
} }

View File

@@ -39,23 +39,23 @@ import org.springframework.data.jpa.domain.support.AuditingEntityListener;
@MappedSuperclass @MappedSuperclass
@EntityListeners(AuditingEntityListener.class) @EntityListeners(AuditingEntityListener.class)
public class SimpleEntity extends IdOnlyEntity { public class SimpleEntity extends IdOnlyEntity {
/** /**
* 记录创建时间 * 记录创建时间
* <p> * <p>
* 由Spring Data JPA自动维护当实体首次持久化时设置该字段的值。 * 由Spring Data JPA自动维护当实体首次持久化时设置该字段的值。
* </p> * </p>
*/ */
@CreatedDate @CreatedDate
@Comment("记录创建时间") @Comment("记录创建时间")
private LocalDateTime createdTime; private LocalDateTime createdTime;
/** /**
* 记录更新时间 * 记录更新时间
* <p> * <p>
* 由Spring Data JPA自动维护当实体每次更新时刷新该字段的值。 * 由Spring Data JPA自动维护当实体每次更新时刷新该字段的值。
* </p> * </p>
*/ */
@LastModifiedDate @LastModifiedDate
@Comment("记录更新时间") @Comment("记录更新时间")
private LocalDateTime modifiedTime; private LocalDateTime modifiedTime;
} }

View File

@@ -5,105 +5,105 @@ import java.util.Map;
import java.util.Optional; import java.util.Optional;
public class ObjectHelper { public class ObjectHelper {
/** /**
* 判断对象是否为null * 判断对象是否为null
* *
* @param obj 待检查的对象 * @param obj 待检查的对象
* @return 如果对象为null返回true否则返回false * @return 如果对象为null返回true否则返回false
*/ */
public static boolean isNull(Object obj) { public static boolean isNull(Object obj) {
return obj == null; return obj == null;
} }
/** /**
* 判断对象是否不为null * 判断对象是否不为null
* *
* @param obj 待判断的对象 * @param obj 待判断的对象
* @return 如果对象不为null则返回true否则返回false * @return 如果对象不为null则返回true否则返回false
*/ */
public static boolean isNotNull(Object obj) { public static boolean isNotNull(Object obj) {
return !isNull(obj); return !isNull(obj);
} }
/** /**
* 判断对象是否为空 * 判断对象是否为空
* *
* @param obj 待判断的对象 * @param obj 待判断的对象
* @return 如果对象为null或为空则返回true否则返回false * @return 如果对象为null或为空则返回true否则返回false
*/ */
public static boolean isEmpty(Object obj) { public static boolean isEmpty(Object obj) {
// 首先判断对象是否为null // 首先判断对象是否为null
if (isNull(obj)) return true; if (isNull(obj)) return true;
// 判断是否为集合类型 // 判断是否为集合类型
else if (obj instanceof Collection<?> collection) return collection.isEmpty(); else if (obj instanceof Collection<?> collection) return collection.isEmpty();
// 判断是否为Map类型 // 判断是否为Map类型
else if (obj instanceof Map<?, ?> map) return map.isEmpty(); else if (obj instanceof Map<?, ?> map) return map.isEmpty();
// 判断是否为字符序列类型 // 判断是否为字符序列类型
else if (obj instanceof CharSequence sequence) return sequence.isEmpty(); else if (obj instanceof CharSequence sequence) return sequence.isEmpty();
// 判断是否为各种基本类型数组 // 判断是否为各种基本类型数组
else if (obj instanceof Object[] array) return array.length == 0; else if (obj instanceof Object[] array) return array.length == 0;
else if (obj instanceof byte[] 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; else if (obj instanceof short[] array) return array.length == 0;
else if (obj instanceof int[] array) return array.length == 0; else if (obj instanceof int[] array) return array.length == 0;
else if (obj instanceof long[] array) return array.length == 0; else if (obj instanceof long[] array) return array.length == 0;
else if (obj instanceof float[] array) return array.length == 0; else if (obj instanceof float[] array) return array.length == 0;
else if (obj instanceof double[] array) return array.length == 0; else if (obj instanceof double[] array) return array.length == 0;
else if (obj instanceof char[] 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; else if (obj instanceof boolean[] array) return array.length == 0;
// 判断是否为Optional类型 // 判断是否为Optional类型
else if (obj instanceof Optional<?> optional) return optional.isEmpty(); else if (obj instanceof Optional<?> optional) return optional.isEmpty();
// 其他情况认为对象不为空 // 其他情况认为对象不为空
else return false; else return false;
} }
public static boolean isNotEmpty(Object obj) { public static boolean isNotEmpty(Object obj) {
return !isEmpty(obj); return !isEmpty(obj);
} }
public static <T> T defaultIfNull(final T object, final T defaultValue) { public static <T> T defaultIfNull(final T object, final T defaultValue) {
return isNull(object) ? defaultValue : object; return isNull(object) ? defaultValue : object;
} }
/** /**
* 判断给定的类是否可比较 * 判断给定的类是否可比较
* *
* @param clazz 待判断的类对象 * @param clazz 待判断的类对象
* @return 如果类是枚举、字符序列、可比较接口的实现类或基本数据类型则返回true否则返回false * @return 如果类是枚举、字符序列、可比较接口的实现类或基本数据类型则返回true否则返回false
*/ */
public static boolean isComparable(Class<?> clazz) { public static boolean isComparable(Class<?> clazz) {
if (isNull(clazz)) return false; if (isNull(clazz)) return false;
// 判断类是否为可比较类型:枚举、字符序列、可比较接口实现类或基本数据类型 // 判断类是否为可比较类型:枚举、字符序列、可比较接口实现类或基本数据类型
return clazz.isEnum() || return clazz.isEnum() ||
CharSequence.class.isAssignableFrom(clazz) || CharSequence.class.isAssignableFrom(clazz) ||
Comparable.class.isAssignableFrom(clazz) || Comparable.class.isAssignableFrom(clazz) ||
clazz.isPrimitive(); clazz.isPrimitive();
} }
public static boolean isComparable(Object obj) { public static boolean isComparable(Object obj) {
if (isNull(obj)) return false; if (isNull(obj)) return false;
return isComparable(obj.getClass()); return isComparable(obj.getClass());
} }
public static boolean isCollection(Class<?> clazz) { public static boolean isCollection(Class<?> clazz) {
if (isNull(clazz)) return false; if (isNull(clazz)) return false;
return Collection.class.isAssignableFrom(clazz); return Collection.class.isAssignableFrom(clazz);
} }
public static boolean isCollection(Object obj) { public static boolean isCollection(Object obj) {
if (isNull(obj)) return false; if (isNull(obj)) return false;
return isCollection(obj.getClass()); return isCollection(obj.getClass());
} }
public static boolean isString(Class<?> clazz) { public static boolean isString(Class<?> clazz) {
if (isNull(clazz)) return false; if (isNull(clazz)) return false;
return String.class.isAssignableFrom(clazz); return String.class.isAssignableFrom(clazz);
} }
public static boolean isString(Object obj) { public static boolean isString(Object obj) {
if (isNull(obj)) return false; if (isNull(obj)) return false;
return isString(obj.getClass()); return isString(obj.getClass());
} }
} }

View File

@@ -96,7 +96,7 @@ import org.springframework.data.repository.query.QueryByExampleExecutor;
* </ul> * </ul>
* </p> * </p>
* *
* @param <E> 实体类型 * @param <E> 实体类型
* @param <ID> 实体ID类型 * @param <ID> 实体ID类型
* @author lanyuanxiaoyao * @author lanyuanxiaoyao
*/ */

View File

@@ -3,28 +3,23 @@ package com.lanyuanxiaoyao.service.template.service;
import com.lanyuanxiaoyao.service.template.controller.Query; import com.lanyuanxiaoyao.service.template.controller.Query;
import com.lanyuanxiaoyao.service.template.entity.SimpleEntity; import com.lanyuanxiaoyao.service.template.entity.SimpleEntity;
import java.util.List; import java.util.List;
import java.util.Optional;
import java.util.Set; import java.util.Set;
import org.springframework.data.domain.Page; import org.springframework.data.domain.Page;
public interface SimpleService<ENTITY extends SimpleEntity> { public interface SimpleService<ENTITY extends SimpleEntity> {
Long save(ENTITY entity) throws Exception; Long save(ENTITY entity) throws Exception;
Long count() throws Exception; Long count() throws Exception;
List<ENTITY> list() throws Exception; List<ENTITY> list() throws Exception;
List<ENTITY> list(Set<Long> ids) throws Exception; List<ENTITY> list(Set<Long> ids) throws Exception;
Page<ENTITY> list(Query query) throws Exception; Page<ENTITY> list(Query query) throws Exception;
Optional<ENTITY> detailOptional(Long id) throws Exception; ENTITY detail(Long id) throws Exception;
ENTITY detail(Long id) throws Exception; ENTITY detailOrThrow(Long id) throws Exception;
ENTITY detailOrThrow(Long id) throws Exception; void remove(Long id) throws Exception;
ENTITY detailOrNull(Long id) throws Exception;
void remove(Long id) throws Exception;
} }

View File

@@ -4,6 +4,15 @@ import com.blinkfox.fenix.EnableFenix;
import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import com.lanyuanxiaoyao.service.template.entity.Company_;
import com.lanyuanxiaoyao.service.template.entity.Employee;
import com.lanyuanxiaoyao.service.template.entity.Employee_;
import com.lanyuanxiaoyao.service.template.entity.QEmployee;
import com.lanyuanxiaoyao.service.template.entity.Report;
import com.lanyuanxiaoyao.service.template.entity.Report_;
import com.lanyuanxiaoyao.service.template.repository.EmployeeRepository;
import jakarta.annotation.Resource;
import java.util.List;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.boot.SpringApplication; import org.springframework.boot.SpringApplication;
@@ -25,161 +34,253 @@ import org.springframework.web.client.RestTemplate;
@EnableFenix @EnableFenix
@EnableJpaAuditing @EnableJpaAuditing
public class TestApplication { public class TestApplication {
private static final Logger log = LoggerFactory.getLogger(TestApplication.class); private static final Logger log = LoggerFactory.getLogger(TestApplication.class);
private static final String BASE_URL = "http://localhost:2490"; private static final String BASE_URL = "http://localhost:2490";
private static final RestTemplate REST_CLIENT = new RestTemplate(); private static final RestTemplate REST_CLIENT = new RestTemplate();
private static final ObjectMapper MAPPER = new ObjectMapper(); private static final ObjectMapper MAPPER = new ObjectMapper();
@Resource
private EmployeeRepository employeeRepository;
public static void main(String[] args) { public static void main(String[] args) {
SpringApplication.run(TestApplication.class, args); SpringApplication.run(TestApplication.class, args);
} }
@EventListener(ApplicationReadyEvent.class) @EventListener(ApplicationReadyEvent.class)
public void runTests() throws JsonProcessingException { public void runTests() throws JsonProcessingException {
// 增 // 增
var cid1 = saveItem("company", "{\"name\": \"Apple\",\"members\": 10}").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 cid2 = saveItem("company", "{\"name\": \"Banana\",\"members\": 20}").get("data").asLong();
var cid3 = saveItem("company", "{\"name\": \"Cheery\",\"members\": 20}").get("data").asLong(); var cid3 = saveItem("company", "{\"name\": \"Cheery\",\"members\": 20}").get("data").asLong();
// 查 // 查
var companies = listItems("company"); var companies = listItems("company");
Assert.isTrue(companies.at("/data/items").size() == 3, "数量错误"); Assert.isTrue(companies.at("/data/items").size() == 3, "数量错误");
Assert.isTrue(companies.at("/data/total").asLong() == 3, "返回数量错误"); Assert.isTrue(companies.at("/data/total").asLong() == 3, "返回数量错误");
// language=JSON // language=JSON
var companies2 = listItems("company", "{\n" + var companies2 = listItems("company", "{\n" +
" \"page\": {\n" + " \"page\": {\n" +
" \"index\": 1,\n" + " \"index\": 1,\n" +
" \"size\": 2\n" + " \"size\": 2\n" +
" }\n" + " }\n" +
"}"); "}");
Assert.isTrue(companies2.at("/data/items").size() == 2, "数量错误"); Assert.isTrue(companies2.at("/data/items").size() == 2, "数量错误");
Assert.isTrue(companies2.at("/data/total").asLong() == 3, "返回数量错误"); Assert.isTrue(companies2.at("/data/total").asLong() == 3, "返回数量错误");
// language=JSON // language=JSON
var companies3 = listItems("company", "{\n" + var companies3 = listItems("company", "{\n" +
" \"query\": {\n" + " \"query\": {\n" +
" \"notNullEqual\": [\n" + " \"notNullEqual\": [\n" +
" \"name\"\n" + " \"name\"\n" +
" ],\n" + " ],\n" +
" \"equal\": {\n" + " \"equal\": {\n" +
" \"name\": \"Apple\"\n" + " \"name\": \"Apple\"\n" +
" },\n" + " },\n" +
" \"like\": {\n" + " \"like\": {\n" +
" \"name\": \"Appl%\"\n" + " \"name\": \"Appl%\"\n" +
" },\n" + " },\n" +
" \"less\": {\n" + " \"contain\": {\n" +
" \"members\": 50\n" + " \"name\": \"ple\"\n" +
" },\n" + " },\n" +
" \"greatEqual\": {\n" + " \"startWith\": {\n" +
" \"members\": 0\n" + " \"name\": \"Appl\"\n" +
" },\n" + " },\n" +
" \"in\": {\n" + " \"endWith\": {\n" +
" \"name\": [\n" + " \"name\": \"le\"\n" +
" \"Apple\",\n" + " },\n" +
" \"Banana\"\n" + " \"less\": {\n" +
" ]\n" + " \"members\": 50\n" +
" },\n" + " },\n" +
" \"between\": {\n" + " \"greatEqual\": {\n" +
" \"members\": {\n" + " \"members\": 0,\n" +
" \"start\": 0,\n" + " \"createdTime\": \"2025-01-01 00:00:00\"\n" +
" \"end\": 50\n" + " },\n" +
" }\n" + " \"in\": {\n" +
" }\n" + " \"name\": [\n" +
" },\n" + " \"Apple\",\n" +
" \"page\": {\n" + " \"Banana\"\n" +
" \"index\": 1,\n" + " ]\n" +
" \"size\": 2\n" + " },\n" +
" }\n" + " \"between\": {\n" +
"}"); " \"members\": {\n" +
Assert.isTrue(companies3.at("/data/items").size() == 1, "数量错误"); " \"start\": 0,\n" +
Assert.isTrue(companies3.at("/data/total").asLong() == 1, "返回数量错误"); " \"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); var company1 = detailItem("company", cid1);
Assert.isTrue(cid1 == company1.at("/data/id").asLong(), "id错误"); Assert.isTrue(cid1 == company1.at("/data/id").asLong(), "id错误");
Assert.isTrue("Apple".equals(company1.at("/data/name").asText()), "name错误"); Assert.isTrue("Apple".equals(company1.at("/data/name").asText()), "name错误");
// 改 // 改
var cid4 = saveItem("company", "{\"id\": %d, \"name\": \"Dog\"}".formatted(cid2)).get("data").asLong(); var cid4 = saveItem("company", "{\"id\": %d, \"name\": \"Dog\"}".formatted(cid2)).get("data").asLong();
Assert.isTrue(cid2 == cid4, "id错误"); Assert.isTrue(cid2 == cid4, "id错误");
var company2 = detailItem("company", cid2); var company2 = detailItem("company", cid2);
Assert.isTrue("Dog".equals(company2.at("/data/name").asText()), "name错误"); Assert.isTrue("Dog".equals(company2.at("/data/name").asText()), "name错误");
// 删 // 删
removeItem("company", cid3); removeItem("company", cid3);
Assert.isTrue(listItems("company").at("/data/items").size() == 2, "数量错误"); Assert.isTrue(listItems("company").at("/data/items").size() == 2, "数量错误");
Assert.isTrue(listItems("company").at("/data/total").asLong() == 2, "返回数量错误"); Assert.isTrue(listItems("company").at("/data/total").asLong() == 2, "返回数量错误");
log.info(listItems("company").toPrettyString()); log.info(listItems("company").toPrettyString());
var eid1 = saveItem("employee", "{\"name\": \"Tom\",\"age\": 18, \"companyId\": %d}".formatted(cid1)).get("data").asLong(); 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 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 eid3 = saveItem("employee", "{\"name\": \"Mike\",\"age\": 18, \"companyId\": %d}".formatted(cid2)).get("data").asLong();
var employees = listItems("employee"); var employees = listItems("employee");
Assert.isTrue(employees.at("/data/items").size() == 3, "数量错误"); Assert.isTrue(employees.at("/data/items").size() == 3, "数量错误");
Assert.isTrue(employees.at("/data/total").asLong() == 3, "返回数量错误"); Assert.isTrue(employees.at("/data/total").asLong() == 3, "返回数量错误");
var employee1 = detailItem("employee", eid1); var employee1 = detailItem("employee", eid1);
Assert.isTrue(eid1 == employee1.at("/data/id").asLong(), "id错误"); Assert.isTrue(eid1 == employee1.at("/data/id").asLong(), "id错误");
Assert.isTrue("Tom".equals(employee1.at("/data/name").asText()), "name错误"); Assert.isTrue("Tom".equals(employee1.at("/data/name").asText()), "name错误");
Assert.isTrue(18 == employee1.at("/data/age").asInt(), "age错误"); Assert.isTrue(18 == employee1.at("/data/age").asInt(), "age错误");
System.exit(0); System.exit(0);
} }
private HttpHeaders headers() { @EventListener(ApplicationReadyEvent.class)
var headers = new HttpHeaders(); public void runSpecificationTests() throws JsonProcessingException {
headers.setContentType(MediaType.APPLICATION_JSON); // 增
return headers; 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 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 rid = saveItem("report", "{\"employeeId\": %d, \"score\": 56.38, \"level\": \"A\"}".formatted(eid1)).get("data").asLong();
var rid2 = saveItem("report", "{\"employeeId\": %d, \"score\": 78.98, \"level\": \"B\"}".formatted(eid2)).get("data").asLong();
private JsonNode saveItem(String path, String body) throws JsonProcessingException { log.debug(
var response = REST_CLIENT.postForEntity( "Results: {}",
"%s/%s/save".formatted(BASE_URL, path), employeeRepository.findAll(
new HttpEntity<>(body, headers()), builder -> builder
String.class .andIsNotNull(Employee.Fields.name)
); .andEquals(Employee.Fields.name, "Tom")
Assert.isTrue(response.getStatusCode().is2xxSuccessful(), "请求失败"); .andLike(Employee.Fields.name, "To%")
Assert.notNull(response.getBody(), "请求失败"); .andStartsWith(Employee.Fields.name, "To")
return MAPPER.readTree(response.getBody()); .andEndsWith(Employee.Fields.name, "om")
} .andLessThan(Employee.Fields.age, 50)
.andGreaterThanEqual(Employee.Fields.age, 0)
.andIn(Employee.Fields.name, List.of("Tom", "Mike"))
.andBetween(Employee.Fields.age, 0, 50)
.build()
)
);
private JsonNode listItems(String path) throws JsonProcessingException { log.debug(
var response = REST_CLIENT.getForEntity( "Results: {}",
"%s/%s/list".formatted(BASE_URL, path), employeeRepository.findAll(
String.class (root, query, builder) ->
); builder.and(
Assert.isTrue(response.getStatusCode().is2xxSuccessful(), "请求失败"); builder.isNotNull(root.get(Employee_.name)),
Assert.notNull(response.getBody(), "请求失败"); builder.equal(root.get(Employee_.name), "Tom"),
return MAPPER.readTree(response.getBody()); builder.like(root.get(Employee_.name), "To%"),
} builder.lessThan(root.get(Employee_.age), 50),
builder.greaterThanOrEqualTo(root.get(Employee_.age), 0),
builder.in(root.get(Employee_.NAME)).value(List.of("Tom", "Mike")),
builder.between(root.get(Employee_.age), 0, 50),
builder.isNotEmpty(root.get(Employee_.company).get(Company_.employees)),
builder.notEqual(root.get(Employee_.company).get(Company_.name), "Apple")
)
)
);
private JsonNode listItems(String path, String query) throws JsonProcessingException { log.debug(
var response = REST_CLIENT.postForEntity( "Results: {}",
"%s/%s/list".formatted(BASE_URL, path), employeeRepository.findAll(
new HttpEntity<>(query, headers()), QEmployee.employee.name.isNotNull()
String.class .and(QEmployee.employee.name.eq("Tom"))
); .and(QEmployee.employee.name.like("To%"))
Assert.isTrue(response.getStatusCode().is2xxSuccessful(), "请求失败"); .and(QEmployee.employee.name.startsWith("To"))
Assert.notNull(response.getBody(), "请求失败"); .and(QEmployee.employee.name.endsWith("om"))
return MAPPER.readTree(response.getBody()); .and(QEmployee.employee.age.lt(50))
} .and(QEmployee.employee.age.goe(0))
.and(QEmployee.employee.name.in("Tom", "Mike"))
.and(QEmployee.employee.age.between(0, 50))
.and(QEmployee.employee.company().employees.isNotEmpty())
.and(QEmployee.employee.company().employees.any().name.ne("Tom"))
)
);
private JsonNode detailItem(String path, Long id) throws JsonProcessingException { log.debug(
var response = REST_CLIENT.getForEntity( "Results: {}",
"%s/%s/detail/%d".formatted(BASE_URL, path, id), employeeRepository.findAll(
String.class (root, query, builder) -> {
); var reportRoot = query.from(Report.class);
Assert.isTrue(response.getStatusCode().is2xxSuccessful(), "请求失败"); return builder.and(
Assert.notNull(response.getBody(), "请求失败"); builder.equal(root.get(Employee_.id), reportRoot.get(Report_.employeeId)),
return MAPPER.readTree(response.getBody()); builder.equal(reportRoot.get(Report_.level), Report.Level.A)
} );
}
)
);
private void removeItem(String path, Long id) { System.exit(0);
var response = REST_CLIENT.getForEntity( }
"%s/%s/remove/%d".formatted(BASE_URL, path, id),
Void.class private HttpHeaders headers() {
); var headers = new HttpHeaders();
Assert.isTrue(response.getStatusCode().is2xxSuccessful(), "请求失败"); headers.setContentType(MediaType.APPLICATION_JSON);
} return headers;
}
private JsonNode saveItem(String path, String body) throws JsonProcessingException {
var response = REST_CLIENT.postForEntity(
"%s/%s/save".formatted(BASE_URL, path),
new HttpEntity<>(body, headers()),
String.class
);
Assert.isTrue(response.getStatusCode().is2xxSuccessful(), "请求失败");
Assert.notNull(response.getBody(), "请求失败");
return MAPPER.readTree(response.getBody());
}
private JsonNode listItems(String path) throws JsonProcessingException {
var response = REST_CLIENT.getForEntity(
"%s/%s/list".formatted(BASE_URL, path),
String.class
);
Assert.isTrue(response.getStatusCode().is2xxSuccessful(), "请求失败");
Assert.notNull(response.getBody(), "请求失败");
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),
String.class
);
Assert.isTrue(response.getStatusCode().is2xxSuccessful(), "请求失败");
Assert.notNull(response.getBody(), "请求失败");
return MAPPER.readTree(response.getBody());
}
private void removeItem(String path, Long id) {
var response = REST_CLIENT.getForEntity(
"%s/%s/remove/%d".formatted(BASE_URL, path, id),
Void.class
);
Assert.isTrue(response.getStatusCode().is2xxSuccessful(), "请求失败");
}
} }

View File

@@ -15,73 +15,73 @@ import org.springframework.web.bind.annotation.RestController;
@RestController @RestController
@RequestMapping("company") @RequestMapping("company")
public class CompanyController extends SimpleControllerSupport<Company, CompanyController.SaveItem, CompanyController.ListItem, CompanyController.DetailItem> { public class CompanyController extends SimpleControllerSupport<Company, CompanyController.SaveItem, CompanyController.ListItem, CompanyController.DetailItem> {
public CompanyController(CompanyService service) { public CompanyController(CompanyService service) {
super(service); super(service);
} }
@Override @Override
protected Function<SaveItem, Company> saveItemMapper() { protected Function<SaveItem, Company> saveItemMapper() {
return item -> { return item -> {
var company = new Company(); var company = new Company();
company.setId(item.getId()); company.setId(item.getId());
company.setName(item.getName()); company.setName(item.getName());
company.setMembers(item.getMembers()); company.setMembers(item.getMembers());
return company; return company;
}; };
} }
@Override @Override
protected Function<Company, ListItem> listItemMapper() { protected Function<Company, ListItem> listItemMapper() {
return company -> new ListItem( return company -> new ListItem(
company.getId(), company.getId(),
company.getName(), company.getName(),
company.getMembers() company.getMembers()
); );
} }
@Override @Override
protected Function<Company, DetailItem> detailItemMapper() { protected Function<Company, DetailItem> detailItemMapper() {
return company -> new DetailItem( return company -> new DetailItem(
company.getId(), company.getId(),
company.getName(), company.getName(),
company.getMembers(), company.getMembers(),
company.getCreatedTime(), company.getCreatedTime(),
company.getModifiedTime() company.getModifiedTime()
); );
} }
@Setter @Setter
@Getter @Getter
@ToString @ToString
@AllArgsConstructor @AllArgsConstructor
@NoArgsConstructor @NoArgsConstructor
public static class SaveItem { public static class SaveItem {
private Long id; private Long id;
private String name; private String name;
private Integer members; private Integer members;
} }
@Setter @Setter
@Getter @Getter
@ToString @ToString
@AllArgsConstructor @AllArgsConstructor
@NoArgsConstructor @NoArgsConstructor
public static class ListItem { public static class ListItem {
private Long id; private Long id;
private String name; private String name;
private Integer members; private Integer members;
} }
@Setter @Setter
@Getter @Getter
@ToString @ToString
@AllArgsConstructor @AllArgsConstructor
@NoArgsConstructor @NoArgsConstructor
public static class DetailItem { public static class DetailItem {
private Long id; private Long id;
private String name; private String name;
private Integer members; private Integer members;
private LocalDateTime createdTime; private LocalDateTime createdTime;
private LocalDateTime modifiedTime; private LocalDateTime modifiedTime;
} }
} }

View File

@@ -16,85 +16,85 @@ import org.springframework.web.bind.annotation.RestController;
@RestController @RestController
@RequestMapping("employee") @RequestMapping("employee")
public class EmployeeController extends SimpleControllerSupport<Employee, EmployeeController.SaveItem, EmployeeController.ListItem, EmployeeController.DetailItem> { public class EmployeeController extends SimpleControllerSupport<Employee, EmployeeController.SaveItem, EmployeeController.ListItem, EmployeeController.DetailItem> {
private final CompanyService companyService; private final CompanyService companyService;
public EmployeeController(EmployeeService service, CompanyService companyService) { public EmployeeController(EmployeeService service, CompanyService companyService) {
super(service); super(service);
this.companyService = companyService; this.companyService = companyService;
} }
@Override @Override
protected Function<SaveItem, Employee> saveItemMapper() { protected Function<SaveItem, Employee> saveItemMapper() {
return item -> { return item -> {
var employee = new Employee(); var employee = new Employee();
employee.setId(item.getId()); employee.setId(item.getId());
employee.setName(item.getName()); employee.setName(item.getName());
employee.setAge(item.getAge()); employee.setAge(item.getAge());
employee.setRole(Employee.Role.USER); employee.setRole(Employee.Role.USER);
employee.setCompany(companyService.detailOrThrow(item.getCompanyId())); employee.setCompany(companyService.detailOrThrow(item.getCompanyId()));
return employee; return employee;
}; };
} }
@Override @Override
protected Function<Employee, ListItem> listItemMapper() { protected Function<Employee, ListItem> listItemMapper() {
return employee -> new ListItem( return employee -> new ListItem(
employee.getId(), employee.getId(),
employee.getName(), employee.getName(),
employee.getAge(), employee.getAge(),
employee.getRole() employee.getRole()
); );
} }
@Override @Override
protected Function<Employee, DetailItem> detailItemMapper() { protected Function<Employee, DetailItem> detailItemMapper() {
return employee -> new DetailItem( return employee -> new DetailItem(
employee.getId(), employee.getId(),
employee.getCompany().getId(), employee.getCompany().getId(),
employee.getName(), employee.getName(),
employee.getAge(), employee.getAge(),
employee.getRole(), employee.getRole(),
employee.getCreatedTime(), employee.getCreatedTime(),
employee.getModifiedTime() employee.getModifiedTime()
); );
} }
@Setter @Setter
@Getter @Getter
@ToString @ToString
@AllArgsConstructor @AllArgsConstructor
@NoArgsConstructor @NoArgsConstructor
public static class SaveItem { public static class SaveItem {
private Long id; private Long id;
private Long companyId; private Long companyId;
private String name; private String name;
private Integer age; private Integer age;
} }
@Setter @Setter
@Getter @Getter
@ToString @ToString
@AllArgsConstructor @AllArgsConstructor
@NoArgsConstructor @NoArgsConstructor
public static class ListItem { public static class ListItem {
private Long id; private Long id;
private String name; private String name;
private Integer age; private Integer age;
private Employee.Role role; private Employee.Role role;
} }
@Setter @Setter
@Getter @Getter
@ToString @ToString
@AllArgsConstructor @AllArgsConstructor
@NoArgsConstructor @NoArgsConstructor
public static class DetailItem { public static class DetailItem {
private Long id; private Long id;
private Long companyId; private Long companyId;
private String name; private String name;
private Integer age; private Integer age;
private Employee.Role role; private Employee.Role role;
private LocalDateTime createdTime; private LocalDateTime createdTime;
private LocalDateTime modifiedTime; private LocalDateTime modifiedTime;
} }
} }

View File

@@ -0,0 +1,107 @@
package com.lanyuanxiaoyao.service.template.controller;
import com.lanyuanxiaoyao.service.template.entity.Report;
import com.lanyuanxiaoyao.service.template.service.EmployeeService;
import com.lanyuanxiaoyao.service.template.service.ReportService;
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("report")
public class ReportController extends SimpleControllerSupport<Report, ReportController.SaveItem, ReportController.ListItem, ReportController.DetailItem> {
private final EmployeeService employeeService;
public ReportController(ReportService service, EmployeeService employeeService) {
super(service);
this.employeeService = employeeService;
}
@Override
protected Function<SaveItem, Report> saveItemMapper() {
return item -> {
var report = new Report();
report.setId(item.getId());
report.setScore(item.getScore());
report.setLevel(item.getLevel());
report.setEmployeeId(item.getEmployeeId());
return report;
};
}
@Override
protected Function<Report, ListItem> listItemMapper() {
return report -> {
var employee = employeeService.detailOrThrow(report.getEmployeeId());
return new ListItem(
report.getId(),
employee.getId(),
employee.getName(),
report.getScore(),
report.getLevel()
);
};
}
@Override
protected Function<Report, DetailItem> detailItemMapper() {
return report -> {
var employee = employeeService.detailOrThrow(report.getEmployeeId());
return new DetailItem(
report.getId(),
employee.getId(),
employee.getName(),
report.getScore(),
report.getLevel(),
report.getCreatedTime(),
report.getModifiedTime()
);
};
}
@Setter
@Getter
@ToString
@AllArgsConstructor
@NoArgsConstructor
public static class SaveItem {
private Long id;
private Double score;
private Report.Level level;
private Long employeeId;
}
@Setter
@Getter
@ToString
@AllArgsConstructor
@NoArgsConstructor
public static class ListItem {
private Long id;
private Long employeeId;
private String employeeName;
private Double score;
private Report.Level level;
}
@Setter
@Getter
@ToString
@AllArgsConstructor
@NoArgsConstructor
public static class DetailItem {
private Long id;
private Long employeeId;
private String employeeName;
private Double score;
private Report.Level level;
private LocalDateTime createdTime;
private LocalDateTime modifiedTime;
}
}

View File

@@ -22,14 +22,14 @@ import org.springframework.data.jpa.domain.support.AuditingEntityListener;
@EntityListeners(AuditingEntityListener.class) @EntityListeners(AuditingEntityListener.class)
@Comment("企业") @Comment("企业")
public class Company extends SimpleEntity { public class Company extends SimpleEntity {
@Column(nullable = false) @Column(nullable = false)
@Comment("名称") @Comment("名称")
private String name; private String name;
@Column(nullable = false) @Column(nullable = false)
@Comment("成员数") @Comment("成员数")
private Integer members; private Integer members;
@OneToMany(mappedBy = "company") @OneToMany(mappedBy = "company")
@ToString.Exclude @ToString.Exclude
private Set<Employee> employees; private Set<Employee> employees;
} }

View File

@@ -28,28 +28,28 @@ import org.springframework.data.jpa.domain.support.AuditingEntityListener;
@DynamicUpdate @DynamicUpdate
@EntityListeners(AuditingEntityListener.class) @EntityListeners(AuditingEntityListener.class)
@NamedEntityGraph(name = "employee.detail", attributeNodes = { @NamedEntityGraph(name = "employee.detail", attributeNodes = {
@NamedAttributeNode("company") @NamedAttributeNode("company")
}) })
@Comment("员工") @Comment("员工")
public class Employee extends SimpleEntity { public class Employee extends SimpleEntity {
@Column(nullable = false) @Column(nullable = false)
@Comment("名称") @Comment("名称")
private String name; private String name;
@Column(nullable = false) @Column(nullable = false)
@Comment("年龄") @Comment("年龄")
private Integer age; private Integer age;
@Column(nullable = false) @Column(nullable = false)
@Enumerated(EnumType.STRING) @Enumerated(EnumType.STRING)
@Comment("角色") @Comment("角色")
private Role role; private Role role;
@ManyToOne(fetch = FetchType.LAZY) @ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(nullable = false, foreignKey = @ForeignKey(ConstraintMode.NO_CONSTRAINT)) @JoinColumn(nullable = false, foreignKey = @ForeignKey(ConstraintMode.NO_CONSTRAINT))
@ToString.Exclude @ToString.Exclude
private Company company; private Company company;
public enum Role { public enum Role {
USER, USER,
ADMIN, ADMIN,
} }
} }

View File

@@ -0,0 +1,40 @@
package com.lanyuanxiaoyao.service.template.entity;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.EntityListeners;
import jakarta.persistence.EnumType;
import jakarta.persistence.Enumerated;
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 Report extends SimpleEntity {
@Column(nullable = false)
@Comment("分数")
private Double score = 0.0;
@Column(nullable = false)
@Enumerated(EnumType.STRING)
@Comment("等级")
private Level level;
@Column(nullable = false)
@Comment("员工id")
private Long employeeId;
public enum Level {
A, B, C, D, E
}
}

View File

@@ -9,7 +9,7 @@ import org.springframework.stereotype.Repository;
@SuppressWarnings("NullableProblems") @SuppressWarnings("NullableProblems")
@Repository @Repository
public interface EmployeeRepository extends SimpleRepository<Employee, Long> { public interface EmployeeRepository extends SimpleRepository<Employee, Long> {
@EntityGraph(value = "employee.detail", type = EntityGraph.EntityGraphType.FETCH) @EntityGraph(value = "employee.detail", type = EntityGraph.EntityGraphType.FETCH)
@Override @Override
Optional<Employee> findOne(Specification<Employee> specification); Optional<Employee> findOne(Specification<Employee> specification);
} }

View File

@@ -0,0 +1,8 @@
package com.lanyuanxiaoyao.service.template.repository;
import com.lanyuanxiaoyao.service.template.entity.Report;
import org.springframework.stereotype.Repository;
@Repository
public interface ReportRepository extends SimpleRepository<Report, Long> {
}

View File

@@ -6,7 +6,7 @@ import org.springframework.stereotype.Service;
@Service @Service
public class CompanyService extends SimpleServiceSupport<Company> { public class CompanyService extends SimpleServiceSupport<Company> {
public CompanyService(CompanyRepository repository) { public CompanyService(CompanyRepository repository) {
super(repository); super(repository);
} }
} }

View File

@@ -6,7 +6,7 @@ import org.springframework.stereotype.Service;
@Service @Service
public class EmployeeService extends SimpleServiceSupport<Employee> { public class EmployeeService extends SimpleServiceSupport<Employee> {
public EmployeeService(EmployeeRepository repository) { public EmployeeService(EmployeeRepository repository) {
super(repository); super(repository);
} }
} }

View File

@@ -0,0 +1,12 @@
package com.lanyuanxiaoyao.service.template.service;
import com.lanyuanxiaoyao.service.template.entity.Report;
import com.lanyuanxiaoyao.service.template.repository.ReportRepository;
import org.springframework.stereotype.Service;
@Service
public class ReportService extends SimpleServiceSupport<Report> {
public ReportService(ReportRepository repository) {
super(repository);
}
}