From c839dfc4e301215f0f468520e4e65881302528d6 Mon Sep 17 00:00:00 2001 From: lanyuanxiaoyao Date: Tue, 20 Jan 2026 18:47:08 +0800 Subject: [PATCH] =?UTF-8?q?feat(eq):=20=E5=AE=8C=E6=88=90easy-query?= =?UTF-8?q?=E6=A1=86=E6=9E=B6=E7=9A=84=E9=80=82=E9=85=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pom.xml | 1 + spring-boot-service-template-eq/pom.xml | 80 ++++++ .../controller/SimpleControllerSupport.java | 209 ++++++++++++++ .../template/eq/entity/IdOnlyEntity.java | 16 ++ .../template/eq/entity/SimpleEntity.java | 16 ++ .../eq/entity/SnowflakeIdGenerator.java | 14 + .../eq/service/SimpleServiceSupport.java | 270 ++++++++++++++++++ .../src/test/initial.sql | 17 ++ .../service/template/eq/TestApplication.java | 167 +++++++++++ .../eq/controller/CompanyController.java | 73 +++++ .../service/template/eq/entity/Company.java | 21 ++ .../service/template/eq/entity/Employee.java | 21 ++ .../template/eq/service/CompanyService.java | 13 + .../template/eq/service/EmployeeService.java | 13 + .../src/test/resources/application.yml | 24 ++ 15 files changed, 955 insertions(+) create mode 100644 spring-boot-service-template-eq/pom.xml create mode 100644 spring-boot-service-template-eq/src/main/java/com/lanyuanxiaoyao/service/template/eq/controller/SimpleControllerSupport.java create mode 100644 spring-boot-service-template-eq/src/main/java/com/lanyuanxiaoyao/service/template/eq/entity/IdOnlyEntity.java create mode 100644 spring-boot-service-template-eq/src/main/java/com/lanyuanxiaoyao/service/template/eq/entity/SimpleEntity.java create mode 100644 spring-boot-service-template-eq/src/main/java/com/lanyuanxiaoyao/service/template/eq/entity/SnowflakeIdGenerator.java create mode 100644 spring-boot-service-template-eq/src/main/java/com/lanyuanxiaoyao/service/template/eq/service/SimpleServiceSupport.java create mode 100644 spring-boot-service-template-eq/src/test/initial.sql create mode 100644 spring-boot-service-template-eq/src/test/java/com/lanyuanxiaoyao/service/template/eq/TestApplication.java create mode 100644 spring-boot-service-template-eq/src/test/java/com/lanyuanxiaoyao/service/template/eq/controller/CompanyController.java create mode 100644 spring-boot-service-template-eq/src/test/java/com/lanyuanxiaoyao/service/template/eq/entity/Company.java create mode 100644 spring-boot-service-template-eq/src/test/java/com/lanyuanxiaoyao/service/template/eq/entity/Employee.java create mode 100644 spring-boot-service-template-eq/src/test/java/com/lanyuanxiaoyao/service/template/eq/service/CompanyService.java create mode 100644 spring-boot-service-template-eq/src/test/java/com/lanyuanxiaoyao/service/template/eq/service/EmployeeService.java create mode 100644 spring-boot-service-template-eq/src/test/resources/application.yml diff --git a/pom.xml b/pom.xml index f5eea49..9cd5176 100644 --- a/pom.xml +++ b/pom.xml @@ -11,6 +11,7 @@ spring-boot-service-template-common + spring-boot-service-template-eq spring-boot-service-template-jpa spring-boot-service-template-xbatis diff --git a/spring-boot-service-template-eq/pom.xml b/spring-boot-service-template-eq/pom.xml new file mode 100644 index 0000000..5a80493 --- /dev/null +++ b/spring-boot-service-template-eq/pom.xml @@ -0,0 +1,80 @@ + + + 4.0.0 + + com.lanyuanxiaoyao + spring-boot-service-template + 1.1.0-SNAPSHOT + + + spring-boot-service-template-eq + + + + com.lanyuanxiaoyao + spring-boot-service-template-common + + + org.springframework.boot + spring-boot-starter-web + + + + com.easy-query + sql-springboot4-starter + 3.1.68 + + + + com.github.gavlyukovskiy + p6spy-spring-boot-starter + + + + org.jspecify + jspecify + + + + com.h2database + h2 + test + + + + + + + org.apache.maven.plugins + maven-source-plugin + + + package + + jar-no-fork + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + + org.projectlombok + lombok + + + com.easy-query + sql-processor + 3.1.68 + + + + + + + \ No newline at end of file diff --git a/spring-boot-service-template-eq/src/main/java/com/lanyuanxiaoyao/service/template/eq/controller/SimpleControllerSupport.java b/spring-boot-service-template-eq/src/main/java/com/lanyuanxiaoyao/service/template/eq/controller/SimpleControllerSupport.java new file mode 100644 index 0000000..c7ed64b --- /dev/null +++ b/spring-boot-service-template-eq/src/main/java/com/lanyuanxiaoyao/service/template/eq/controller/SimpleControllerSupport.java @@ -0,0 +1,209 @@ +package com.lanyuanxiaoyao.service.template.eq.controller; + +import com.easy.query.core.proxy.AbstractProxyEntity; +import com.easy.query.core.proxy.ProxyEntityAvailable; +import com.lanyuanxiaoyao.service.template.common.controller.SimpleController; +import com.lanyuanxiaoyao.service.template.common.entity.GlobalResponse; +import com.lanyuanxiaoyao.service.template.common.entity.Query; +import com.lanyuanxiaoyao.service.template.common.helper.ObjectHelper; +import com.lanyuanxiaoyao.service.template.eq.entity.SimpleEntity; +import com.lanyuanxiaoyao.service.template.eq.service.SimpleServiceSupport; +import java.util.function.Function; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; + +/** + * 简单控制器支持类,提供基础的CRUD操作实现 + *

+ * 该类实现了基本的增删改查功能,通过泛型支持不同类型的数据转换。 + * 子类需要实现对应的Mapper函数来完成实体类与传输对象之间的转换。 + *

+ * + *

设计特点

+ * + * + *

使用说明

+ *

子类需要实现以下抽象方法:

+ * + * + * @param 实体类型,必须继承SimpleEntity + * @param 保存项类型 + * @param 列表项类型 + * @param 详情项类型 + */ +@Slf4j +@RequiredArgsConstructor +public abstract class SimpleControllerSupport, PROXY extends AbstractProxyEntity, SAVE_ITEM, LIST_ITEM, DETAIL_ITEM> implements SimpleController { + protected final SimpleServiceSupport service; + + /** + * 保存实体对象 + *

+ * 将保存项转换为实体对象后保存,返回保存后的实体ID。 + * 支持新增和更新操作,通过事务保证数据一致性。 + *

+ * + * @param item 需要保存的项 + * @return 返回保存后的实体ID响应对象,格式:{status: 0, message: "OK", data: 实体ID} + * @throws Exception 保存过程中可能抛出的异常 + */ + @Transactional(rollbackFor = Throwable.class) + @PostMapping(SAVE) + @Override + public GlobalResponse save(@RequestBody SAVE_ITEM item) throws Exception { + var mapper = saveItemMapper(); + return GlobalResponse.responseSuccess(service.save(mapper.apply(item))); + } + + /** + * 获取所有实体列表 + *

+ * 查询所有记录,不带任何过滤条件,返回分页格式的数据。 + * 将实体对象转换为列表项对象后返回。 + *

+ * + * @return 返回实体列表响应对象,格式:{status: 0, message: "OK", data: {items: [...], total: total}} + * @throws Exception 查询过程中可能抛出的异常 + */ + @Transactional(readOnly = true) + @GetMapping(LIST) + @Override + public GlobalResponse> list() throws Exception { + var mapper = listItemMapper(); + var result = service.list(); + return GlobalResponse.responseListData( + result + .stream() + .map(entity -> { + try { + return mapper.apply(entity); + } catch (Exception e) { + throw new RuntimeException(e); + } + }) + .toList(), + result.size() + ); + } + + /** + * 根据查询条件获取实体列表 + *

+ * 支持复杂的查询条件、排序和分页,返回符合条件的数据。 + * 将实体对象转换为列表项对象后返回。 + *

+ * + * @param query 查询条件对象,包含过滤条件、排序规则和分页信息 + * @return 返回符合条件的实体列表响应对象,格式:{status: 0, message: "OK", data: {items: [...], total: total}} + * @throws Exception 查询过程中可能抛出的异常 + */ + @Transactional(readOnly = true) + @PostMapping(LIST) + @Override + public GlobalResponse> list(@RequestBody Query query) throws Exception { + if (ObjectHelper.isNull(query)) { + return GlobalResponse.responseListData(); + } + var mapper = listItemMapper(); + var result = service.list(query); + return GlobalResponse.responseListData( + result.items() + .stream() + .map(entity -> { + try { + return mapper.apply(entity); + } catch (Exception e) { + throw new RuntimeException(e); + } + }) + .toList(), + result.total() + ); + } + + /** + * 根据ID获取实体详情 + *

+ * 根据主键ID查询单条记录的详细信息,转换为详情项对象后返回。 + * 如果记录不存在则抛出异常。 + *

+ * + * @param id 实体主键ID + * @return 返回实体详情响应对象,格式:{status: 0, message: "OK", data: 详情数据} + * @throws Exception 查询过程中可能抛出的异常 + */ + @Transactional(readOnly = true) + @GetMapping(DETAIL) + @Override + public GlobalResponse detail(@PathVariable("id") Long id) throws Exception { + var mapper = detailItemMapper(); + return GlobalResponse.responseSuccess(mapper.apply(service.detailOrThrow(id))); + } + + /** + * 根据ID删除实体对象 + *

+ * 根据主键ID删除指定的记录,执行成功后返回成功响应。 + * 通过事务保证删除操作的一致性。 + *

+ * + * @param id 需要删除的实体主键ID + * @return 返回删除结果响应对象,格式:{status: 0, message: "OK", data: null} + * @throws Exception 删除过程中可能抛出的异常 + */ + @Transactional(rollbackFor = Throwable.class) + @GetMapping(REMOVE) + @Override + public GlobalResponse remove(@PathVariable("id") Long id) throws Exception { + service.remove(id); + return GlobalResponse.responseSuccess(); + } + + /** + * 保存项映射器,将保存项转换为实体对象 + *

+ * 子类需要实现此方法,定义保存项到实体的转换逻辑。 + *

+ * + * @return Function 保存项到实体的转换函数 + */ + protected abstract Function saveItemMapper(); + + /** + * 列表项映射器,将实体对象转换为列表项 + *

+ * 子类需要实现此方法,定义实体到列表项的转换逻辑。 + *

+ * + * @return Function 实体到列表项的转换函数 + */ + protected abstract Function listItemMapper(); + + /** + * 详情项映射器,将实体对象转换为详情项 + *

+ * 子类需要实现此方法,定义实体到详情项的转换逻辑。 + *

+ * + * @return Function 实体到详情项的转换函数 + */ + protected abstract Function detailItemMapper(); + + public interface Mapper { + T map(S source) throws Exception; + } +} \ No newline at end of file diff --git a/spring-boot-service-template-eq/src/main/java/com/lanyuanxiaoyao/service/template/eq/entity/IdOnlyEntity.java b/spring-boot-service-template-eq/src/main/java/com/lanyuanxiaoyao/service/template/eq/entity/IdOnlyEntity.java new file mode 100644 index 0000000..5963a77 --- /dev/null +++ b/spring-boot-service-template-eq/src/main/java/com/lanyuanxiaoyao/service/template/eq/entity/IdOnlyEntity.java @@ -0,0 +1,16 @@ +package com.lanyuanxiaoyao.service.template.eq.entity; + +import com.easy.query.core.annotation.Column; +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; +import lombok.experimental.FieldNameConstants; + +@Getter +@Setter +@ToString +@FieldNameConstants +public class IdOnlyEntity { + @Column(primaryKey = true, primaryKeyGenerator = SnowflakeIdGenerator.class) + private Long id; +} \ No newline at end of file diff --git a/spring-boot-service-template-eq/src/main/java/com/lanyuanxiaoyao/service/template/eq/entity/SimpleEntity.java b/spring-boot-service-template-eq/src/main/java/com/lanyuanxiaoyao/service/template/eq/entity/SimpleEntity.java new file mode 100644 index 0000000..8ace449 --- /dev/null +++ b/spring-boot-service-template-eq/src/main/java/com/lanyuanxiaoyao/service/template/eq/entity/SimpleEntity.java @@ -0,0 +1,16 @@ +package com.lanyuanxiaoyao.service.template.eq.entity; + +import java.time.LocalDateTime; +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; +import lombok.experimental.FieldNameConstants; + +@Getter +@Setter +@ToString(callSuper = true) +@FieldNameConstants +public class SimpleEntity extends IdOnlyEntity { + private LocalDateTime createdTime; + private LocalDateTime modifiedTime; +} \ No newline at end of file diff --git a/spring-boot-service-template-eq/src/main/java/com/lanyuanxiaoyao/service/template/eq/entity/SnowflakeIdGenerator.java b/spring-boot-service-template-eq/src/main/java/com/lanyuanxiaoyao/service/template/eq/entity/SnowflakeIdGenerator.java new file mode 100644 index 0000000..22d5a4d --- /dev/null +++ b/spring-boot-service-template-eq/src/main/java/com/lanyuanxiaoyao/service/template/eq/entity/SnowflakeIdGenerator.java @@ -0,0 +1,14 @@ +package com.lanyuanxiaoyao.service.template.eq.entity; + +import com.easy.query.core.basic.extension.generated.PrimaryKeyGenerator; +import com.lanyuanxiaoyao.service.template.common.helper.SnowflakeHelper; +import java.io.Serializable; +import org.springframework.stereotype.Component; + +@Component +public class SnowflakeIdGenerator implements PrimaryKeyGenerator { + @Override + public Serializable getPrimaryKey() { + return SnowflakeHelper.next(); + } +} diff --git a/spring-boot-service-template-eq/src/main/java/com/lanyuanxiaoyao/service/template/eq/service/SimpleServiceSupport.java b/spring-boot-service-template-eq/src/main/java/com/lanyuanxiaoyao/service/template/eq/service/SimpleServiceSupport.java new file mode 100644 index 0000000..8305488 --- /dev/null +++ b/spring-boot-service-template-eq/src/main/java/com/lanyuanxiaoyao/service/template/eq/service/SimpleServiceSupport.java @@ -0,0 +1,270 @@ +package com.lanyuanxiaoyao.service.template.eq.service; + +import com.easy.query.api.proxy.client.EasyEntityQuery; +import com.easy.query.core.enums.SQLExecuteStrategyEnum; +import com.easy.query.core.proxy.AbstractProxyEntity; +import com.easy.query.core.proxy.ProxyEntityAvailable; +import com.lanyuanxiaoyao.service.template.common.entity.Page; +import com.lanyuanxiaoyao.service.template.common.entity.Query; +import com.lanyuanxiaoyao.service.template.common.exception.IdNotFoundException; +import com.lanyuanxiaoyao.service.template.common.helper.ObjectHelper; +import com.lanyuanxiaoyao.service.template.common.service.QueryParser; +import com.lanyuanxiaoyao.service.template.common.service.SimpleService; +import com.lanyuanxiaoyao.service.template.eq.entity.SimpleEntity; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.Set; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@RequiredArgsConstructor +public abstract class SimpleServiceSupport, PROXY extends AbstractProxyEntity> implements SimpleService { + private static final int DEFAULT_PAGE_INDEX = 1; + private static final int DEFAULT_PAGE_SIZE = 10; + + protected final EasyEntityQuery entityQuery; + private final Class target; + + @Override + public Long save(ENTITY entity) { + if (ObjectHelper.isNull(entity.getId())) { + entityQuery.insertable(entity).executeRows(); + } else { + entityQuery.updatable(entity) + .setSQLStrategy(SQLExecuteStrategyEnum.ONLY_NOT_NULL_COLUMNS) + .executeRows(); + } + return entity.getId(); + } + + @Override + public void save(Iterable entities) { + var insertList = new ArrayList(); + var updateList = new ArrayList(); + for (var entity : entities) { + if (ObjectHelper.isNull(entity.getId())) { + insertList.add(entity); + } else { + updateList.add(entity); + } + } + if (ObjectHelper.isNotEmpty(insertList)) { + entityQuery.insertable(insertList).executeRows(); + } + if (ObjectHelper.isNotEmpty(updateList)) { + entityQuery.updatable(updateList) + .setSQLStrategy(SQLExecuteStrategyEnum.ONLY_NOT_NULL_COLUMNS) + .executeRows(); + } + } + + @Override + public Long count() { + return entityQuery.queryable(target).count(); + } + + @Override + public List list() { + return entityQuery.queryable(target).toList(); + } + + @Override + public List list(Set ids) { + if (ObjectHelper.isEmpty(ids)) { + return List.of(); + } + return entityQuery.queryable(target) + .whereByIds(ids) + .toList(); + } + + protected void commonPredicates(PROXY proxy) { + } + + @Override + public Page list(Query query) { + var index = DEFAULT_PAGE_INDEX; + var size = DEFAULT_PAGE_SIZE; + if (ObjectHelper.isNotNull(query.page())) { + index = Math.max(ObjectHelper.defaultIfNull(query.page().index(), DEFAULT_PAGE_INDEX), 1); + size = Math.max(ObjectHelper.defaultIfNull(query.page().size(), DEFAULT_PAGE_SIZE), 1); + } + + var result = entityQuery.queryable(target) + .where(this::commonPredicates) + .where(proxy -> new EqQueryParser(proxy).build(query.query())) + .orderBy(ObjectHelper.isNotEmpty(query.sort()), proxy -> query.sort().forEach(sort -> proxy.anyColumn(sort.column()).orderBy(Query.Sortable.Direction.ASC.equals(sort.direction())))) + .toPageResult(index, size); + + return new Page<>(result.getData(), result.getTotal()); + } + + private Optional detailOptional(Long id) { + if (ObjectHelper.isNull(id)) { + return Optional.empty(); + } + return entityQuery.queryable(target) + .whereById(id) + .singleOptional(); + } + + @Override + public ENTITY detail(Long id) { + return detailOptional(id).orElse(null); + } + + @Override + public ENTITY detailOrThrow(Long id) { + return detailOptional(id).orElseThrow(() -> new IdNotFoundException(id)); + } + + @Override + public void remove(Long id) { + if (ObjectHelper.isNotNull(id)) { + entityQuery.deletable(target) + .whereById(id) + .allowDeleteStatement(true) + .executeRows(); + } + } + + @Override + public void remove(Set ids) { + if (ObjectHelper.isNotEmpty(ids)) { + entityQuery.deletable(target) + .whereByIds(ids) + .allowDeleteStatement(true) + .executeRows(); + } + } + + @RequiredArgsConstructor + private static final class EqQueryParser, PROXY extends AbstractProxyEntity> extends QueryParser { + private final PROXY proxy; + + @Override + protected void nullEqual(Query.Queryable queryable) { + queryable.nullEqual().forEach(column -> proxy.anyColumn(column).isNull()); + } + + @Override + protected void notNullEqual(Query.Queryable queryable) { + queryable.notNullEqual().forEach(column -> proxy.anyColumn(column).isNotNull()); + } + + @Override + protected void empty(Query.Queryable queryable) { + throw new UnsupportedOperationException(); + } + + @Override + protected void notEmpty(Query.Queryable queryable) { + throw new UnsupportedOperationException(); + } + + @Override + protected void equal(Query.Queryable queryable) { + queryable.equal().forEach((column, value) -> proxy.anyColumn(column).eq(value)); + } + + @Override + protected void notEqual(Query.Queryable queryable) { + queryable.notEqual().forEach((column, value) -> proxy.anyColumn(column).ne(value)); + } + + @Override + protected void like(Query.Queryable queryable) { + queryable.like().forEach((column, value) -> proxy.anyColumn(column).likeRaw(value)); + } + + @Override + protected void notLike(Query.Queryable queryable) { + queryable.notLike().forEach((column, value) -> proxy.anyColumn(column).notLikeRaw(value)); + } + + @Override + protected void contain(Query.Queryable queryable) { + queryable.contain().forEach((column, value) -> proxy.anyColumn(column).like(value)); + } + + @Override + protected void notContain(Query.Queryable queryable) { + queryable.notContain().forEach((column, value) -> proxy.anyColumn(column).notLike(value)); + } + + @Override + protected void startWith(Query.Queryable queryable) { + queryable.startWith().forEach((column, value) -> proxy.anyColumn(column).likeMatchLeft(value)); + } + + @Override + protected void notStartWith(Query.Queryable queryable) { + queryable.notStartWith().forEach((column, value) -> proxy.anyColumn(column).notLikeMatchLeft(value)); + } + + @Override + protected void endWith(Query.Queryable queryable) { + queryable.endWith().forEach((column, value) -> proxy.anyColumn(column).likeMatchRight(value)); + } + + @Override + protected void notEndWith(Query.Queryable queryable) { + queryable.notEndWith().forEach((column, value) -> proxy.anyColumn(column).notLikeMatchRight(value)); + } + + @Override + protected void great(Query.Queryable queryable) { + queryable.great().forEach((column, value) -> proxy.anyColumn(column).gt(value)); + } + + @Override + protected void less(Query.Queryable queryable) { + queryable.less().forEach((column, value) -> proxy.anyColumn(column).lt(value)); + } + + @Override + protected void greatEqual(Query.Queryable queryable) { + queryable.greatEqual().forEach((column, value) -> proxy.anyColumn(column).ge(value)); + } + + @Override + protected void lessEqual(Query.Queryable queryable) { + queryable.lessEqual().forEach((column, value) -> proxy.anyColumn(column).le(value)); + } + + @Override + protected void inside(Query.Queryable queryable) { + queryable.inside() + .entrySet() + .stream() + .filter(entry -> ObjectHelper.isNotEmpty(entry.getValue())) + .forEach(entry -> proxy.anyColumn(entry.getKey()).in(entry.getValue())); + } + + @Override + protected void notInside(Query.Queryable queryable) { + queryable.notInside() + .entrySet() + .stream() + .filter(entry -> ObjectHelper.isNotEmpty(entry.getValue())) + .forEach(entry -> proxy.anyColumn(entry.getKey()).notIn(entry.getValue())); + } + + @Override + protected void between(Query.Queryable queryable) { + throw new UnsupportedOperationException(); + } + + @Override + protected void notBetween(Query.Queryable queryable) { + throw new UnsupportedOperationException(); + } + + @Override + protected Void build() { + return null; + } + } +} diff --git a/spring-boot-service-template-eq/src/test/initial.sql b/spring-boot-service-template-eq/src/test/initial.sql new file mode 100644 index 0000000..830cc57 --- /dev/null +++ b/spring-boot-service-template-eq/src/test/initial.sql @@ -0,0 +1,17 @@ +create table if not exists Company +( + id bigint primary key, + name varchar(255) not null, + members int not null, + created_time timestamp not null default current_timestamp(), + modified_time timestamp not null default current_timestamp() on update current_timestamp() +); + +create table if not exists Employee +( + id bigint primary key, + name varchar(255) not null, + age int not null, + created_time timestamp not null default current_timestamp(), + modified_time timestamp not null default current_timestamp() on update current_timestamp() +); diff --git a/spring-boot-service-template-eq/src/test/java/com/lanyuanxiaoyao/service/template/eq/TestApplication.java b/spring-boot-service-template-eq/src/test/java/com/lanyuanxiaoyao/service/template/eq/TestApplication.java new file mode 100644 index 0000000..9a67043 --- /dev/null +++ b/spring-boot-service-template-eq/src/test/java/com/lanyuanxiaoyao/service/template/eq/TestApplication.java @@ -0,0 +1,167 @@ +package com.lanyuanxiaoyao.service.template.eq; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.context.event.ApplicationReadyEvent; +import org.springframework.context.event.EventListener; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.util.Assert; +import org.springframework.web.client.RestTemplate; +import tools.jackson.databind.JsonNode; +import tools.jackson.databind.ObjectMapper; + +@Slf4j +@RequiredArgsConstructor +@SpringBootApplication +public class TestApplication { + private static final String BASE_URL = "http://localhost:2490"; + private static final RestTemplate REST_CLIENT = new RestTemplate(); + private static final ObjectMapper MAPPER = new ObjectMapper(); + + public static void main(String[] args) { + SpringApplication.run(TestApplication.class, args); + } + + @EventListener(ApplicationReadyEvent.class) + public void runTests() { + // 增 + var cid1 = saveItem("company", "{\"name\": \"Apple\",\"members\": 10}").get("data").asLong(); + var cid2 = saveItem("company", "{\"name\": \"Banana\",\"members\": 20}").get("data").asLong(); + var cid3 = saveItem("company", "{\"name\": \"Cheery\",\"members\": 20}").get("data").asLong(); + + // 查 + var companies = listItems("company"); + Assert.isTrue(companies.at("/data/items").size() == 3, "数量错误"); + Assert.isTrue(companies.at("/data/total").asLong() == 3, "返回数量错误"); + + // language=JSON + var companies2 = listItems("company", "{\n" + + " \"page\": {\n" + + " \"index\": 1,\n" + + " \"size\": 2\n" + + " }\n" + + "}"); + Assert.isTrue(companies2.at("/data/items").size() == 2, "数量错误"); + Assert.isTrue(companies2.at("/data/total").asLong() == 3, "返回数量错误"); + // language=JSON + var companies3 = listItems("company", "{\n" + + " \"query\": {\n" + + " \"notNullEqual\": [\n" + + " \"name\"\n" + + " ],\n" + + " \"equal\": {\n" + + " \"name\": \"Apple\"\n" + + " },\n" + + " \"like\": {\n" + + " \"name\": \"Appl%\"\n" + + " },\n" + + " \"contain\": {\n" + + " \"name\": \"ple\"\n" + + " },\n" + + " \"startWith\": {\n" + + " \"name\": \"Appl\"\n" + + " },\n" + + " \"endWith\": {\n" + + " \"name\": \"le\"\n" + + " },\n" + + " \"less\": {\n" + + " \"members\": 50\n" + + " },\n" + + " \"greatEqual\": {\n" + + " \"members\": 0,\n" + + " \"createdTime\": \"2025-01-01 00:00:00\"\n" + + " },\n" + + " \"inside\": {\n" + + " \"name\": [\n" + + " \"Apple\",\n" + + " \"Banana\"\n" + + " ]\n" + + " }\n" + + " },\n" + + " \"page\": {\n" + + " \"index\": 1,\n" + + " \"size\": 2\n" + + " }\n" + + "}"); + Assert.isTrue(companies3.at("/data/items").size() == 1, "数量错误"); + Assert.isTrue(companies3.at("/data/total").asLong() == 1, "返回数量错误"); + + var company1 = detailItem("company", cid1); + Assert.isTrue(cid1 == company1.at("/data/id").asLong(), "id错误"); + Assert.isTrue("Apple".equals(company1.at("/data/name").asString()), "name错误"); + + // 改 + var cid4 = saveItem("company", "{\"id\": %d, \"name\": \"Dog\"}".formatted(cid2)).get("data").asLong(); + Assert.isTrue(cid2 == cid4, "id错误"); + var company2 = detailItem("company", cid2); + Assert.isTrue("Dog".equals(company2.at("/data/name").asString()), "name错误"); + + // 删 + removeItem("company", cid3); + Assert.isTrue(listItems("company").at("/data/items").size() == 2, "数量错误"); + Assert.isTrue(listItems("company").at("/data/total").asLong() == 2, "返回数量错误"); + + log.info(listItems("company").toPrettyString()); + System.exit(0); + } + + private HttpHeaders headers() { + var headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_JSON); + return headers; + } + + private JsonNode saveItem(String path, String body) { + 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) { + 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) { + 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) { + 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(), "请求失败"); + } +} diff --git a/spring-boot-service-template-eq/src/test/java/com/lanyuanxiaoyao/service/template/eq/controller/CompanyController.java b/spring-boot-service-template-eq/src/test/java/com/lanyuanxiaoyao/service/template/eq/controller/CompanyController.java new file mode 100644 index 0000000..c5a55e4 --- /dev/null +++ b/spring-boot-service-template-eq/src/test/java/com/lanyuanxiaoyao/service/template/eq/controller/CompanyController.java @@ -0,0 +1,73 @@ +package com.lanyuanxiaoyao.service.template.eq.controller; + +import com.lanyuanxiaoyao.service.template.eq.entity.Company; +import com.lanyuanxiaoyao.service.template.eq.entity.proxy.CompanyProxy; +import com.lanyuanxiaoyao.service.template.eq.service.CompanyService; +import java.time.LocalDateTime; +import java.util.function.Function; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@Slf4j +@RestController +@RequestMapping("company") +public class CompanyController extends SimpleControllerSupport { + public CompanyController(CompanyService service) { + super(service); + } + + @Override + protected Function saveItemMapper() { + return item -> { + var company = new Company(); + company.setId(item.id()); + company.setName(item.name()); + company.setMembers(item.members()); + return company; + }; + } + + @Override + protected Function listItemMapper() { + return company -> new ListItem( + company.getId(), + company.getName(), + company.getMembers() + ); + } + + @Override + protected Function detailItemMapper() { + return company -> new DetailItem( + company.getId(), + company.getName(), + company.getMembers(), + company.getCreatedTime(), + company.getModifiedTime() + ); + } + + public record SaveItem( + Long id, + String name, + Integer members + ) { + } + + public record ListItem( + Long id, + String name, + Integer members + ) { + } + + public record DetailItem( + Long id, + String name, + Integer members, + LocalDateTime createdTime, + LocalDateTime modifiedTime + ) { + } +} diff --git a/spring-boot-service-template-eq/src/test/java/com/lanyuanxiaoyao/service/template/eq/entity/Company.java b/spring-boot-service-template-eq/src/test/java/com/lanyuanxiaoyao/service/template/eq/entity/Company.java new file mode 100644 index 0000000..9ade63a --- /dev/null +++ b/spring-boot-service-template-eq/src/test/java/com/lanyuanxiaoyao/service/template/eq/entity/Company.java @@ -0,0 +1,21 @@ +package com.lanyuanxiaoyao.service.template.eq.entity; + +import com.easy.query.core.annotation.EntityProxy; +import com.easy.query.core.annotation.Table; +import com.easy.query.core.proxy.ProxyEntityAvailable; +import com.lanyuanxiaoyao.service.template.eq.entity.proxy.CompanyProxy; +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; +import lombok.experimental.FieldNameConstants; + +@Getter +@Setter +@ToString(callSuper = true) +@FieldNameConstants +@Table +@EntityProxy +public class Company extends SimpleEntity implements ProxyEntityAvailable { + private String name; + private Integer members; +} diff --git a/spring-boot-service-template-eq/src/test/java/com/lanyuanxiaoyao/service/template/eq/entity/Employee.java b/spring-boot-service-template-eq/src/test/java/com/lanyuanxiaoyao/service/template/eq/entity/Employee.java new file mode 100644 index 0000000..204ac5b --- /dev/null +++ b/spring-boot-service-template-eq/src/test/java/com/lanyuanxiaoyao/service/template/eq/entity/Employee.java @@ -0,0 +1,21 @@ +package com.lanyuanxiaoyao.service.template.eq.entity; + +import com.easy.query.core.annotation.EntityProxy; +import com.easy.query.core.annotation.Table; +import com.easy.query.core.proxy.ProxyEntityAvailable; +import com.lanyuanxiaoyao.service.template.eq.entity.proxy.EmployeeProxy; +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; +import lombok.experimental.FieldNameConstants; + +@Getter +@Setter +@ToString(callSuper = true) +@FieldNameConstants +@Table +@EntityProxy +public class Employee extends SimpleEntity implements ProxyEntityAvailable { + private String name; + private Integer age; +} diff --git a/spring-boot-service-template-eq/src/test/java/com/lanyuanxiaoyao/service/template/eq/service/CompanyService.java b/spring-boot-service-template-eq/src/test/java/com/lanyuanxiaoyao/service/template/eq/service/CompanyService.java new file mode 100644 index 0000000..77081d6 --- /dev/null +++ b/spring-boot-service-template-eq/src/test/java/com/lanyuanxiaoyao/service/template/eq/service/CompanyService.java @@ -0,0 +1,13 @@ +package com.lanyuanxiaoyao.service.template.eq.service; + +import com.easy.query.api.proxy.client.EasyEntityQuery; +import com.lanyuanxiaoyao.service.template.eq.entity.Company; +import com.lanyuanxiaoyao.service.template.eq.entity.proxy.CompanyProxy; +import org.springframework.stereotype.Service; + +@Service +public class CompanyService extends SimpleServiceSupport { + public CompanyService(EasyEntityQuery entityQuery) { + super(entityQuery, Company.class); + } +} diff --git a/spring-boot-service-template-eq/src/test/java/com/lanyuanxiaoyao/service/template/eq/service/EmployeeService.java b/spring-boot-service-template-eq/src/test/java/com/lanyuanxiaoyao/service/template/eq/service/EmployeeService.java new file mode 100644 index 0000000..eb491d9 --- /dev/null +++ b/spring-boot-service-template-eq/src/test/java/com/lanyuanxiaoyao/service/template/eq/service/EmployeeService.java @@ -0,0 +1,13 @@ +package com.lanyuanxiaoyao.service.template.eq.service; + +import com.easy.query.api.proxy.client.EasyEntityQuery; +import com.lanyuanxiaoyao.service.template.eq.entity.Employee; +import com.lanyuanxiaoyao.service.template.eq.entity.proxy.EmployeeProxy; +import org.springframework.stereotype.Service; + +@Service +public class EmployeeService extends SimpleServiceSupport { + public EmployeeService(EasyEntityQuery entityQuery) { + super(entityQuery, Employee.class); + } +} diff --git a/spring-boot-service-template-eq/src/test/resources/application.yml b/spring-boot-service-template-eq/src/test/resources/application.yml new file mode 100644 index 0000000..a95ef94 --- /dev/null +++ b/spring-boot-service-template-eq/src/test/resources/application.yml @@ -0,0 +1,24 @@ +server: + port: 2490 +spring: + application: + name: Test + datasource: + url: "jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1;MODE=MySQL;DATABASE_TO_LOWER=TRUE;INIT=runscript from '/Users/lanyuanxiaoyao/Project/IdeaProjects/spring-boot-service-template/spring-boot-service-template-eq/src/test/initial.sql'" + username: test + password: test + driver-class-name: org.h2.Driver +easy-query: + database: mysql + name-conversion: underlined + print-sql: false +decorator: + datasource: + p6spy: + multiline: false + exclude-categories: + - commit + - result + - resultset + - rollback + log-format: "%(category)|%(executionTime)|%(sqlSingleLine)" \ No newline at end of file