From 33e11818c5bc2e67adac0c75fddb4b3a54487332 Mon Sep 17 00:00:00 2001 From: lanyuanxiaoyao Date: Thu, 14 Aug 2025 16:33:11 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=AE=8C=E6=88=90=E5=9F=BA=E6=9C=AC?= =?UTF-8?q?=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 134 ++++++++ pom.xml | 70 ++++ .../template/controller/DetailController.java | 9 + .../template/controller/ListController.java | 11 + .../service/template/controller/Query.java | 304 ++++++++++++++++++ .../template/controller/RemoveController.java | 9 + .../template/controller/SaveController.java | 9 + .../template/controller/SimpleController.java | 4 + .../controller/SimpleControllerSupport.java | 100 ++++++ .../response/GlobalCrudResponse.java | 30 ++ .../response/GlobalDetailResponse.java | 13 + .../response/GlobalMapResponse.java | 21 ++ .../controller/response/GlobalResponse.java | 138 ++++++++ .../service/template/entity/IdOnlyEntity.java | 34 ++ .../service/template/entity/SimpleEntity.java | 44 +++ .../service/template/helper/ObjectHelper.java | 41 +++ .../template/repository/SimpleRepository.java | 10 + .../template/service/SimpleService.java | 34 ++ .../service/SimpleServiceSupport.java | 234 ++++++++++++++ 19 files changed, 1249 insertions(+) create mode 100644 .gitignore create mode 100644 pom.xml create mode 100644 src/main/java/com/lanyuanxiaoyao/service/template/controller/DetailController.java create mode 100644 src/main/java/com/lanyuanxiaoyao/service/template/controller/ListController.java create mode 100644 src/main/java/com/lanyuanxiaoyao/service/template/controller/Query.java create mode 100644 src/main/java/com/lanyuanxiaoyao/service/template/controller/RemoveController.java create mode 100644 src/main/java/com/lanyuanxiaoyao/service/template/controller/SaveController.java create mode 100644 src/main/java/com/lanyuanxiaoyao/service/template/controller/SimpleController.java create mode 100644 src/main/java/com/lanyuanxiaoyao/service/template/controller/SimpleControllerSupport.java create mode 100644 src/main/java/com/lanyuanxiaoyao/service/template/controller/response/GlobalCrudResponse.java create mode 100644 src/main/java/com/lanyuanxiaoyao/service/template/controller/response/GlobalDetailResponse.java create mode 100644 src/main/java/com/lanyuanxiaoyao/service/template/controller/response/GlobalMapResponse.java create mode 100644 src/main/java/com/lanyuanxiaoyao/service/template/controller/response/GlobalResponse.java create mode 100644 src/main/java/com/lanyuanxiaoyao/service/template/entity/IdOnlyEntity.java create mode 100644 src/main/java/com/lanyuanxiaoyao/service/template/entity/SimpleEntity.java create mode 100644 src/main/java/com/lanyuanxiaoyao/service/template/helper/ObjectHelper.java create mode 100644 src/main/java/com/lanyuanxiaoyao/service/template/repository/SimpleRepository.java create mode 100644 src/main/java/com/lanyuanxiaoyao/service/template/service/SimpleService.java create mode 100644 src/main/java/com/lanyuanxiaoyao/service/template/service/SimpleServiceSupport.java diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..86ca00d --- /dev/null +++ b/.gitignore @@ -0,0 +1,134 @@ +.idea +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/**/usage.statistics.xml +.idea/**/dictionaries +.idea/**/shelf +.idea/**/aws.xml +.idea/**/contentModel.xml +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml +.idea/**/dbnavigator.xml +.idea/**/gradle.xml +.idea/**/libraries +cmake-build-*/ +.idea/**/mongoSettings.xml +*.iws +out/ +.idea_modules/ +atlassian-ide-plugin.xml +.idea/replstate.xml +.idea/sonarlint/ +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties +.idea/httpRequests +.idea/caches/build_file_checksums.ser +target/ +pom.xml.tag +pom.xml.releaseBackup +pom.xml.versionsBackup +pom.xml.next +release.properties +dependency-reduced-pom.xml +buildNumber.properties +.mvn/timing.properties +.mvn/wrapper/maven-wrapper.jar +.project +.classpath +*.class +*.log +*.ctxt +.mtj.tmp/ +*.jar +*.war +*.nar +*.ear +*.zip +*.tar.gz +*.rar +hs_err_pid* +replay_pid* +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +!.vscode/*.code-snippets +.history/ +*.vsix +.metadata +bin/ +tmp/ +*.tmp +*.bak +*.swp +*~.nib +local.properties +.settings/ +.loadpath +.recommenders +.externalToolBuilders/ +*.launch +*.pydevproject +.cproject +.autotools +.factorypath +.buildpath +.target +.tern-project +.texlipse +.springBeans +.recommenders/ +.apt_generated/ +.apt_generated_test/ +.cache-main +.scala_dependencies +.worksheet +Thumbs.db +Thumbs.db:encryptable +ehthumbs.db +ehthumbs_vista.db +*.stackdump +[Dd]esktop.ini +$RECYCLE.BIN/ +*.cab +*.msi +*.msix +*.msm +*.msp +*.lnk +*~ +.fuse_hidden* +.directory +.Trash-* +.nfs* +.gradle +**/build/ +!src/**/build/ +gradle-app.setting +!gradle-wrapper.jar +!gradle-wrapper.properties +.gradletasknamecache +.DS_Store +.AppleDouble +.LSOverride +Icon +._* +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..d28a5b4 --- /dev/null +++ b/pom.xml @@ -0,0 +1,70 @@ + + + 4.0.0 + + com.lanyuanxiaoyao + spring-boot-service-template + 1.0.0-SNAPSHOT + + + 17 + 17 + UTF-8 + + 3.4.3 + 2024.0.1 + 11.1.0 + + + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-data-jpa + + + com.blinkfox + fenix-spring-boot-starter + 3.0.0 + + + + + + + + org.springframework.boot + spring-boot-dependencies + ${spring-boot.version} + pom + import + + + org.springframework.cloud + spring-cloud-dependencies + ${spring-cloud.version} + pom + import + + + + + + + ${releases.id} + ${releases.name} + ${releases.url} + + + ${snapshots.id} + ${snapshots.name} + ${snapshots.url} + + + + \ No newline at end of file diff --git a/src/main/java/com/lanyuanxiaoyao/service/template/controller/DetailController.java b/src/main/java/com/lanyuanxiaoyao/service/template/controller/DetailController.java new file mode 100644 index 0000000..59b3f6e --- /dev/null +++ b/src/main/java/com/lanyuanxiaoyao/service/template/controller/DetailController.java @@ -0,0 +1,9 @@ +package com.lanyuanxiaoyao.service.template.controller; + +import com.lanyuanxiaoyao.service.template.controller.response.GlobalResponse; + +public interface DetailController { + String DETAIL = "/detail/{id}"; + + GlobalResponse detail(Long id) throws Exception; +} diff --git a/src/main/java/com/lanyuanxiaoyao/service/template/controller/ListController.java b/src/main/java/com/lanyuanxiaoyao/service/template/controller/ListController.java new file mode 100644 index 0000000..e303dfc --- /dev/null +++ b/src/main/java/com/lanyuanxiaoyao/service/template/controller/ListController.java @@ -0,0 +1,11 @@ +package com.lanyuanxiaoyao.service.template.controller; + +import com.lanyuanxiaoyao.service.template.controller.response.GlobalCrudResponse; + +public interface ListController { + String LIST = "/list"; + + GlobalCrudResponse list() throws Exception; + + GlobalCrudResponse list(Query query) throws Exception; +} diff --git a/src/main/java/com/lanyuanxiaoyao/service/template/controller/Query.java b/src/main/java/com/lanyuanxiaoyao/service/template/controller/Query.java new file mode 100644 index 0000000..2e5a3d9 --- /dev/null +++ b/src/main/java/com/lanyuanxiaoyao/service/template/controller/Query.java @@ -0,0 +1,304 @@ +package com.lanyuanxiaoyao.service.template.controller; + +import java.util.List; +import java.util.Map; + +public class Query { + private Queryable query; + private List sort; + private Pageable page; + + public Queryable getQuery() { + return query; + } + + public void setQuery(Queryable query) { + this.query = query; + } + + public List getSort() { + return sort; + } + + public void setSort(List 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 + + '}'; + } + + public static class Queryable { + private List nullEqual; + private List notNullEqual; + private List empty; + private List notEmpty; + private Map equal; + private Map notEqual; + private Map like; + private Map notLike; + private Map> great; + private Map> less; + private Map> greatEqual; + private Map> lessEqual; + private Map> in; + private Map> notIn; + private Map between; + private Map notBetween; + + public java.util.List getNullEqual() { + return nullEqual; + } + + public void setNullEqual(java.util.List nullEqual) { + this.nullEqual = nullEqual; + } + + public java.util.List getNotNullEqual() { + return notNullEqual; + } + + public void setNotNullEqual(java.util.List notNullEqual) { + this.notNullEqual = notNullEqual; + } + + public java.util.List getEmpty() { + return empty; + } + + public void setEmpty(java.util.List empty) { + this.empty = empty; + } + + public java.util.List getNotEmpty() { + return notEmpty; + } + + public void setNotEmpty(java.util.List notEmpty) { + this.notEmpty = notEmpty; + } + + public java.util.Map getEqual() { + return equal; + } + + public void setEqual(java.util.Map equal) { + this.equal = equal; + } + + public java.util.Map getNotEqual() { + return notEqual; + } + + public void setNotEqual(java.util.Map notEqual) { + this.notEqual = notEqual; + } + + public java.util.Map getLike() { + return like; + } + + public void setLike(java.util.Map like) { + this.like = like; + } + + public java.util.Map getNotLike() { + return notLike; + } + + public void setNotLike(java.util.Map notLike) { + this.notLike = notLike; + } + + public java.util.Map> getGreat() { + return great; + } + + public void setGreat(java.util.Map> great) { + this.great = great; + } + + public java.util.Map> getLess() { + return less; + } + + public void setLess(java.util.Map> less) { + this.less = less; + } + + public java.util.Map> getGreatEqual() { + return greatEqual; + } + + public void setGreatEqual(java.util.Map> greatEqual) { + this.greatEqual = greatEqual; + } + + public java.util.Map> getLessEqual() { + return lessEqual; + } + + public void setLessEqual(java.util.Map> lessEqual) { + this.lessEqual = lessEqual; + } + + public java.util.Map> getIn() { + return in; + } + + public void setIn(java.util.Map> in) { + this.in = in; + } + + public java.util.Map> getNotIn() { + return notIn; + } + + public void setNotIn(java.util.Map> notIn) { + this.notIn = notIn; + } + + public java.util.Map getBetween() { + return between; + } + + public void setBetween(java.util.Map between) { + this.between = between; + } + + public java.util.Map getNotBetween() { + return notBetween; + } + + public void setNotBetween(java.util.Map 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 + + '}'; + } + + 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 + '\'' + + '}'; + } + } + } + + 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, + } + } + + public static class Pageable { + 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 + + '}'; + } + } +} diff --git a/src/main/java/com/lanyuanxiaoyao/service/template/controller/RemoveController.java b/src/main/java/com/lanyuanxiaoyao/service/template/controller/RemoveController.java new file mode 100644 index 0000000..4a2b2e9 --- /dev/null +++ b/src/main/java/com/lanyuanxiaoyao/service/template/controller/RemoveController.java @@ -0,0 +1,9 @@ +package com.lanyuanxiaoyao.service.template.controller; + +import com.lanyuanxiaoyao.service.template.controller.response.GlobalResponse; + +public interface RemoveController { + String REMOVE = "/remove/{id}"; + + GlobalResponse remove(Long id) throws Exception; +} diff --git a/src/main/java/com/lanyuanxiaoyao/service/template/controller/SaveController.java b/src/main/java/com/lanyuanxiaoyao/service/template/controller/SaveController.java new file mode 100644 index 0000000..c75e362 --- /dev/null +++ b/src/main/java/com/lanyuanxiaoyao/service/template/controller/SaveController.java @@ -0,0 +1,9 @@ +package com.lanyuanxiaoyao.service.template.controller; + +import com.lanyuanxiaoyao.service.template.controller.response.GlobalResponse; + +public interface SaveController { + String SAVE = "/save"; + + GlobalResponse save(SAVE_ITEM item) throws Exception; +} diff --git a/src/main/java/com/lanyuanxiaoyao/service/template/controller/SimpleController.java b/src/main/java/com/lanyuanxiaoyao/service/template/controller/SimpleController.java new file mode 100644 index 0000000..53aa280 --- /dev/null +++ b/src/main/java/com/lanyuanxiaoyao/service/template/controller/SimpleController.java @@ -0,0 +1,4 @@ +package com.lanyuanxiaoyao.service.template.controller; + +public interface SimpleController extends SaveController, ListController, DetailController, RemoveController { +} diff --git a/src/main/java/com/lanyuanxiaoyao/service/template/controller/SimpleControllerSupport.java b/src/main/java/com/lanyuanxiaoyao/service/template/controller/SimpleControllerSupport.java new file mode 100644 index 0000000..e6118df --- /dev/null +++ b/src/main/java/com/lanyuanxiaoyao/service/template/controller/SimpleControllerSupport.java @@ -0,0 +1,100 @@ +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.helper.ObjectHelper; +import com.lanyuanxiaoyao.service.template.service.SimpleServiceSupport; +import java.util.List; +import org.springframework.data.domain.Page; +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; + +public abstract class SimpleControllerSupport implements SimpleController { + protected final SimpleServiceSupport service; + + public SimpleControllerSupport(SimpleServiceSupport service) { + this.service = service; + } + + @PostMapping(SAVE) + @Override + public GlobalResponse save(@RequestBody SAVE_ITEM item) throws Exception { + SaveItemMapper mapper = saveItemMapper(); + return GlobalResponse.responseSuccess(service.save(mapper.from(item))); + } + + @GetMapping(LIST) + @Override + public GlobalCrudResponse list() throws Exception { + ListItemMapper mapper = listItemMapper(); + return GlobalCrudResponse.responseCrudData( + service.list() + .stream() + .map(entity -> { + try { + return mapper.from(entity); + } catch (Exception e) { + throw new RuntimeException(e); + } + }) + .toList() + ); + } + + @PostMapping(LIST) + @Override + public GlobalCrudResponse list(@RequestBody Query query) throws Exception { + if (ObjectHelper.isNull(query)) { + return GlobalCrudResponse.responseCrudData(List.of(), 0); + } + ListItemMapper mapper = listItemMapper(); + Page result = service.list(query); + return GlobalCrudResponse.responseCrudData( + result.get() + .map(entity -> { + try { + return mapper.from(entity); + } catch (Exception e) { + throw new RuntimeException(e); + } + }) + .toList(), + result.getTotalElements() + ); + } + + @GetMapping(DETAIL) + @Override + public GlobalResponse detail(@PathVariable("id") Long id) throws Exception { + DetailItemMapper mapper = detailItemMapper(); + return GlobalResponse.responseSuccess(mapper.from(service.detailOrThrow(id))); + } + + @GetMapping(REMOVE) + @Override + public GlobalResponse remove(@PathVariable("id") Long id) throws Exception { + service.remove(id); + return GlobalResponse.responseSuccess(); + } + + protected abstract SaveItemMapper saveItemMapper(); + + protected abstract ListItemMapper listItemMapper(); + + protected abstract DetailItemMapper detailItemMapper(); + + public interface SaveItemMapper { + ENTITY from(SAVE_ITEM item) throws Exception; + } + + public interface ListItemMapper { + LIST_ITEM from(ENTITY entity) throws Exception; + } + + public interface DetailItemMapper { + DETAIL_ITEM from(ENTITY entity) throws Exception; + } +} diff --git a/src/main/java/com/lanyuanxiaoyao/service/template/controller/response/GlobalCrudResponse.java b/src/main/java/com/lanyuanxiaoyao/service/template/controller/response/GlobalCrudResponse.java new file mode 100644 index 0000000..bb852d7 --- /dev/null +++ b/src/main/java/com/lanyuanxiaoyao/service/template/controller/response/GlobalCrudResponse.java @@ -0,0 +1,30 @@ +package com.lanyuanxiaoyao.service.template.controller.response; + +/** + * Crud 响应 + * + * @author lanyuanxiaoyao + * @date 2023-07-06 + */ +public class GlobalCrudResponse extends GlobalMapResponse { + public void setData(Iterable list) { + setData("items", list); + } + + public void setTotal(Long total) { + setData("total", total); + } + + public void setTotal(Integer total) { + setTotal(total.longValue()); + } + + public void setData(Iterable list, Long total) { + setData(list); + setTotal(total); + } + + public void setData(Iterable list, Integer total) { + setData(list, total.longValue()); + } +} diff --git a/src/main/java/com/lanyuanxiaoyao/service/template/controller/response/GlobalDetailResponse.java b/src/main/java/com/lanyuanxiaoyao/service/template/controller/response/GlobalDetailResponse.java new file mode 100644 index 0000000..6719f41 --- /dev/null +++ b/src/main/java/com/lanyuanxiaoyao/service/template/controller/response/GlobalDetailResponse.java @@ -0,0 +1,13 @@ +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); + } +} diff --git a/src/main/java/com/lanyuanxiaoyao/service/template/controller/response/GlobalMapResponse.java b/src/main/java/com/lanyuanxiaoyao/service/template/controller/response/GlobalMapResponse.java new file mode 100644 index 0000000..f4aaa0a --- /dev/null +++ b/src/main/java/com/lanyuanxiaoyao/service/template/controller/response/GlobalMapResponse.java @@ -0,0 +1,21 @@ +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> { + public GlobalMapResponse() { + setData(new HashMap<>()); + } + + public GlobalMapResponse setData(String key, Object value) { + getData().put(key, value); + return this; + } +} diff --git a/src/main/java/com/lanyuanxiaoyao/service/template/controller/response/GlobalResponse.java b/src/main/java/com/lanyuanxiaoyao/service/template/controller/response/GlobalResponse.java new file mode 100644 index 0000000..a9737a7 --- /dev/null +++ b/src/main/java/com/lanyuanxiaoyao/service/template/controller/response/GlobalResponse.java @@ -0,0 +1,138 @@ +package com.lanyuanxiaoyao.service.template.controller.response; + +import java.util.Map; + +/** + * Amis 组件结构化返回值 + * + * @author lanyuanxiaoyao + * @date 2022-09-21 + */ +public class GlobalResponse { + 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 responseError() { + return responseError(ERROR_MESSAGE); + } + + public static GlobalResponse responseError(String message) { + GlobalResponse response = new GlobalResponse<>(); + response.setStatus(ERROR_STATUS); + response.setMessage(message); + return response; + } + + public static GlobalResponse responseSuccess() { + GlobalResponse response = new GlobalResponse<>(); + response.setStatus(SUCCESS_STATUS); + response.setMessage(SUCCESS_MESSAGE); + return response; + } + + public static GlobalResponse responseSuccess(String message) { + GlobalResponse response = new GlobalResponse<>(); + response.setStatus(SUCCESS_STATUS); + response.setMessage(message); + return response; + } + + public static GlobalResponse responseSuccess(String message, E data) { + GlobalResponse response = new GlobalResponse<>(); + response.setStatus(SUCCESS_STATUS); + response.setMessage(message); + response.setData(data); + return response; + } + + public static GlobalResponse responseSuccess(E data) { + GlobalResponse 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 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 GlobalCrudResponse responseCrudData(Iterable data) { + GlobalCrudResponse response = new GlobalCrudResponse<>(); + response.setStatus(SUCCESS_STATUS); + response.setMessage(SUCCESS_MESSAGE); + response.setData(data); + return response; + } + + public static GlobalCrudResponse responseCrudData(Iterable data, Integer total) { + GlobalCrudResponse response = responseCrudData(data); + response.setTotal(total); + return response; + } + + public static GlobalCrudResponse responseCrudData(Iterable data, Long total) { + GlobalCrudResponse response = responseCrudData(data); + response.setTotal(total); + return response; + } + + public static GlobalDetailResponse responseDetailData(Object detail) { + GlobalDetailResponse response = new GlobalDetailResponse(); + 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 + + '}'; + } +} diff --git a/src/main/java/com/lanyuanxiaoyao/service/template/entity/IdOnlyEntity.java b/src/main/java/com/lanyuanxiaoyao/service/template/entity/IdOnlyEntity.java new file mode 100644 index 0000000..ad0cafb --- /dev/null +++ b/src/main/java/com/lanyuanxiaoyao/service/template/entity/IdOnlyEntity.java @@ -0,0 +1,34 @@ +package com.lanyuanxiaoyao.service.template.entity; + +import jakarta.persistence.EntityListeners; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.Id; +import jakarta.persistence.MappedSuperclass; +import org.hibernate.annotations.Comment; +import org.hibernate.annotations.GenericGenerator; +import org.springframework.data.jpa.domain.support.AuditingEntityListener; + +@MappedSuperclass +@EntityListeners(AuditingEntityListener.class) +public class IdOnlyEntity { + @Comment("记录唯一标记") + @Id + @GeneratedValue(generator = "snowflake") + @GenericGenerator(name = "snowflake", strategy = "com.lanyuanxiaoyao.service.ai.web.configuration.SnowflakeIdGenerator") + private Long id; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + @Override + public String toString() { + return "IdOnlyEntity{" + + "id=" + id + + '}'; + } +} diff --git a/src/main/java/com/lanyuanxiaoyao/service/template/entity/SimpleEntity.java b/src/main/java/com/lanyuanxiaoyao/service/template/entity/SimpleEntity.java new file mode 100644 index 0000000..bfbb859 --- /dev/null +++ b/src/main/java/com/lanyuanxiaoyao/service/template/entity/SimpleEntity.java @@ -0,0 +1,44 @@ +package com.lanyuanxiaoyao.service.template.entity; + +import jakarta.persistence.EntityListeners; +import jakarta.persistence.MappedSuperclass; +import java.time.LocalDateTime; +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; + +@MappedSuperclass +@EntityListeners(AuditingEntityListener.class) +public class SimpleEntity extends IdOnlyEntity { + @Comment("记录创建时间") + @CreatedDate + private LocalDateTime createdTime; + @Comment("记录更新时间") + @LastModifiedDate + 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 + + '}'; + } +} diff --git a/src/main/java/com/lanyuanxiaoyao/service/template/helper/ObjectHelper.java b/src/main/java/com/lanyuanxiaoyao/service/template/helper/ObjectHelper.java new file mode 100644 index 0000000..1f2b29f --- /dev/null +++ b/src/main/java/com/lanyuanxiaoyao/service/template/helper/ObjectHelper.java @@ -0,0 +1,41 @@ +package com.lanyuanxiaoyao.service.template.helper; + +import java.util.Collection; +import java.util.Map; +import java.util.Optional; + +public class ObjectHelper { + public static boolean isNull(Object obj) { + return obj == null; + } + + public static boolean isNotNull(Object obj) { + return !isNull(obj); + } + + public static boolean isEmpty(Object obj) { + if (isNull(obj)) return true; + else if (obj instanceof Collection collection) return collection.isEmpty(); + 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; + else if (obj instanceof int[] 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 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; + else if (obj instanceof Optional optional) return optional.isEmpty(); + else return false; + } + + public static boolean isNotEmpty(Object obj) { + return !isEmpty(obj); + } + + public static T defaultIfNull(final T object, final T defaultValue) { + return isNull(object) ? defaultValue : object; + } +} \ No newline at end of file diff --git a/src/main/java/com/lanyuanxiaoyao/service/template/repository/SimpleRepository.java b/src/main/java/com/lanyuanxiaoyao/service/template/repository/SimpleRepository.java new file mode 100644 index 0000000..6c3c2a8 --- /dev/null +++ b/src/main/java/com/lanyuanxiaoyao/service/template/repository/SimpleRepository.java @@ -0,0 +1,10 @@ +package com.lanyuanxiaoyao.service.template.repository; + +import com.blinkfox.fenix.jpa.FenixJpaRepository; +import com.blinkfox.fenix.specification.FenixJpaSpecificationExecutor; +import org.springframework.data.repository.NoRepositoryBean; +import org.springframework.data.repository.query.QueryByExampleExecutor; + +@NoRepositoryBean +public interface SimpleRepository extends FenixJpaRepository, FenixJpaSpecificationExecutor, QueryByExampleExecutor { +} diff --git a/src/main/java/com/lanyuanxiaoyao/service/template/service/SimpleService.java b/src/main/java/com/lanyuanxiaoyao/service/template/service/SimpleService.java new file mode 100644 index 0000000..0aa5652 --- /dev/null +++ b/src/main/java/com/lanyuanxiaoyao/service/template/service/SimpleService.java @@ -0,0 +1,34 @@ +package com.lanyuanxiaoyao.service.template.service; + +import com.lanyuanxiaoyao.service.template.controller.Query; +import com.lanyuanxiaoyao.service.template.entity.SimpleEntity; +import java.util.List; +import java.util.Optional; +import java.util.Set; +import org.springframework.data.domain.Page; + +/** + * @author lanyuanxiaoyao + * @date 2024-11-28 + */ +public interface SimpleService { + Long save(ENTITY entity) throws Exception; + + Long count() throws Exception; + + List list() throws Exception; + + List list(Set ids) throws Exception; + + Page list(Query query) throws Exception; + + Optional detailOptional(Long id) throws Exception; + + ENTITY detail(Long id) throws Exception; + + ENTITY detailOrThrow(Long id) throws Exception; + + ENTITY detailOrNull(Long id) throws Exception; + + void remove(Long id) throws Exception; +} diff --git a/src/main/java/com/lanyuanxiaoyao/service/template/service/SimpleServiceSupport.java b/src/main/java/com/lanyuanxiaoyao/service/template/service/SimpleServiceSupport.java new file mode 100644 index 0000000..346304a --- /dev/null +++ b/src/main/java/com/lanyuanxiaoyao/service/template/service/SimpleServiceSupport.java @@ -0,0 +1,234 @@ +package com.lanyuanxiaoyao.service.template.service; + +import com.lanyuanxiaoyao.service.template.controller.Query; +import com.lanyuanxiaoyao.service.template.entity.SimpleEntity; +import com.lanyuanxiaoyao.service.template.helper.ObjectHelper; +import com.lanyuanxiaoyao.service.template.repository.SimpleRepository; +import jakarta.persistence.criteria.CriteriaBuilder; +import jakarta.persistence.criteria.CriteriaQuery; +import jakarta.persistence.criteria.Path; +import jakarta.persistence.criteria.Predicate; +import jakarta.persistence.criteria.Root; +import jakarta.transaction.Transactional; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.Set; +import org.springframework.beans.BeanUtils; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Sort; + +public abstract class SimpleServiceSupport implements SimpleService { + private static final Integer DEFAULT_PAGE_INDEX = 1; + private static final Integer DEFAULT_PAGE_SIZE = 10; + protected final SimpleRepository repository; + + public SimpleServiceSupport(SimpleRepository repository) { + this.repository = repository; + } + + @Transactional(rollbackOn = Throwable.class) + @Override + public Long save(ENTITY entity) { + if (!ObjectHelper.isNull(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); + return entity.getId(); + } + + @Override + public Long count() { + return repository.count( + (root, query, builder) -> + builder.and( + listPredicate(root, query, builder) + .stream() + .filter(ObjectHelper::isNotNull) + .toArray(Predicate[]::new) + ) + ); + } + + @Override + public List list() { + return repository.findAll( + (root, query, builder) -> + builder.and( + listPredicate(root, query, builder) + .stream() + .filter(ObjectHelper::isNotNull) + .toArray(Predicate[]::new) + ) + ); + } + + @Override + public List list(Set ids) { + return repository.findAll( + (root, query, builder) -> { + var predicates = listPredicate(root, query, builder); + predicates.add(builder.in(root.get("id")).value(ids)); + return builder.and(predicates.stream().filter(ObjectHelper::isNotNull).toArray(Predicate[]::new)); + } + ); + } + + private Path column(Root root, String column) { + if (ObjectHelper.isEmpty(column)) { + throw new IllegalArgumentException("Column cannot be blank"); + } + String[] columns = column.split("/"); + Path path = root.get(columns[0]); + for (int i = 1; i < columns.length; i++) { + path = path.get(columns[i]); + } + return path; + } + + @SuppressWarnings({"unchecked", "rawtypes"}) + private Object value(Path column, Object value) { + Class javaType = column.getJavaType(); + if (javaType.isEnum()) { + return Enum.valueOf((Class) javaType, (String) value); + } + return value; + } + + protected List queryPredicates(Query.Queryable queryable, Root root, CriteriaQuery query, CriteriaBuilder builder) { + List predicates = new ArrayList<>(); + if (ObjectHelper.isNull(queryable)) { + return predicates; + } + if (ObjectHelper.isNotEmpty(queryable.getNullEqual())) { + 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)))); + } + if (ObjectHelper.isNotEmpty(queryable.getEmpty())) { + queryable.getEmpty().forEach(column -> predicates.add(builder.isEmpty(column(root, column)))); + } + if (ObjectHelper.isNotEmpty(queryable.getNotEmpty())) { + queryable.getNotEmpty().forEach(column -> predicates.add(builder.isNotEmpty(column(root, column)))); + } + if (ObjectHelper.isNotEmpty(queryable.getEqual())) { + queryable.getEqual().forEach((column, value) -> { + Path path = column(root, column); + predicates.add(builder.equal(path, value(path, value))); + }); + } + if (ObjectHelper.isNotEmpty(queryable.getNotEqual())) { + queryable.getEqual().forEach((column, value) -> { + Path path = column(root, column); + predicates.add(builder.notEqual(path, value(path, value))); + }); + } + if (ObjectHelper.isNotEmpty(queryable.getLike())) { + queryable.getLike().forEach((column, value) -> 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))); + } + if (ObjectHelper.isNotEmpty(queryable.getGreat())) { + queryable.getGreat().forEach((column, value) -> predicates.add(builder.greaterThan(column(root, column), value))); + } + if (ObjectHelper.isNotEmpty(queryable.getLess())) { + queryable.getLess().forEach((column, value) -> predicates.add(builder.lessThan(column(root, column), value))); + } + if (ObjectHelper.isNotEmpty(queryable.getGreatEqual())) { + queryable.getGreatEqual().forEach((column, value) -> predicates.add(builder.greaterThanOrEqualTo(column(root, column), value))); + } + if (ObjectHelper.isNotEmpty(queryable.getLessEqual())) { + queryable.getLessEqual().forEach((column, value) -> predicates.add(builder.lessThanOrEqualTo(column(root, column), value))); + } + if (ObjectHelper.isNotEmpty(queryable.getIn())) { + queryable.getIn().forEach((column, value) -> predicates.add(builder.in(column(root, column)).value(value))); + } + if (ObjectHelper.isNotEmpty(queryable.getNotIn())) { + 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()))); + } + if (ObjectHelper.isNotEmpty(queryable.getNotBetween())) { + queryable.getNotBetween().forEach((column, value) -> predicates.add(builder.between(column(root, column), value.getStart(), value.getEnd()))); + } + return predicates; + } + + protected List listPredicate(Root root, CriteriaQuery query, CriteriaBuilder builder) { + return new ArrayList<>(0); + } + + @Override + public Page list(Query listQuery) { + PageRequest pageRequest = PageRequest.of(DEFAULT_PAGE_INDEX - 1, DEFAULT_PAGE_SIZE, Sort.by("created_time").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() + ); + } + return repository.findAll( + (root, query, builder) -> { + var predicates = listPredicate(root, query, builder); + predicates.addAll(queryPredicates(listQuery.getQuery(), root, query, builder)); + return builder.and(predicates.stream().filter(ObjectHelper::isNotNull).toArray(Predicate[]::new)); + }, + pageRequest + ); + } + + @Override + public Optional detailOptional(Long id) { + if (ObjectHelper.isNull(id)) { + return Optional.empty(); + } + return repository.findOne( + (root, query, builder) -> { + var predicates = listPredicate(root, query, builder); + predicates.add(builder.equal(root.get("id"), id)); + return builder.and(predicates.stream().filter(ObjectHelper::isNotNull).toArray(Predicate[]::new)); + } + ); + } + + @Override + public ENTITY detail(Long id) { + return detailOrNull(id); + } + + @Override + public ENTITY detailOrThrow(Long id) { + return detailOptional(id).orElseThrow(() -> new IdNotFoundException(id)); + } + + @Override + public ENTITY detailOrNull(Long id) { + return detailOptional(id).orElse(null); + } + + @Transactional(rollbackOn = Throwable.class) + @Override + public void remove(Long id) { + if (ObjectHelper.isNotNull(id)) { + repository.deleteById(id); + } + } + + public static final class IdNotFoundException extends RuntimeException { + public IdNotFoundException() { + super("资源不存在"); + } + + public IdNotFoundException(Long id) { + super("ID为%d的资源不存在".formatted(id)); + } + } +}