diff --git a/pom.xml b/pom.xml index 286e85be..7b77c4ab 100644 --- a/pom.xml +++ b/pom.xml @@ -40,12 +40,24 @@ 2.10.0 3.2.2 2.12.2 + 4.1.0 - + + org.mockito + mockito-junit-jupiter + ${mockito.version} + test + + + org.mockito + mockito-inline + ${mockito.version} + test + org.springframework.cloud diff --git a/ruoyi-common/ruoyi-common-core/pom.xml b/ruoyi-common/ruoyi-common-core/pom.xml index 3bc14f45..b1c96b12 100644 --- a/ruoyi-common/ruoyi-common-core/pom.xml +++ b/ruoyi-common/ruoyi-common-core/pom.xml @@ -1,130 +1,144 @@ - - - - com.ruoyi - ruoyi-common - 3.2.0 - - 4.0.0 - - ruoyi-common-core - - - ruoyi-common-core核心模块 - - - - - - - org.springframework.cloud - spring-cloud-starter-openfeign - - - - - org.springframework.cloud - spring-cloud-starter-loadbalancer - - - - - org.springframework - spring-context-support - - - - - org.springframework - spring-web - - - - - com.alibaba - transmittable-thread-local - - - - - org.apache.commons - commons-pool2 - - - - - com.github.pagehelper - pagehelper-spring-boot-starter - - - - - org.springframework.boot - spring-boot-starter-validation - - - - - com.fasterxml.jackson.core - jackson-databind - - - - - com.alibaba - fastjson - - - - - io.jsonwebtoken - jjwt - - - - - javax.xml.bind - jaxb-api - - - - - org.apache.commons - commons-lang3 - - - - - commons-io - commons-io - - - - - commons-fileupload - commons-fileupload - - - - - org.apache.poi - poi-ooxml - - - - - javax.servlet - javax.servlet-api - - - - - io.swagger - swagger-annotations - - - - - + + + + com.ruoyi + ruoyi-common + 3.2.0 + + 4.0.0 + + ruoyi-common-core + + + ruoyi-common-core核心模块 + + + + + + + org.springframework.cloud + spring-cloud-starter-openfeign + + + + + org.springframework.cloud + spring-cloud-starter-loadbalancer + + + + + org.springframework + spring-context-support + + + + + org.springframework + spring-web + + + + + com.alibaba + transmittable-thread-local + + + + + org.apache.commons + commons-pool2 + + + + + com.github.pagehelper + pagehelper-spring-boot-starter + + + + + org.springframework.boot + spring-boot-starter-validation + + + + + com.fasterxml.jackson.core + jackson-databind + + + + + com.alibaba + fastjson + + + + + io.jsonwebtoken + jjwt + + + + + javax.xml.bind + jaxb-api + + + + + org.apache.commons + commons-lang3 + + + + + commons-io + commons-io + + + + + commons-fileupload + commons-fileupload + + + + + org.apache.poi + poi-ooxml + + + + + javax.servlet + javax.servlet-api + + + + + io.swagger + swagger-annotations + + + org.springframework.boot + spring-boot-starter-test + test + + + + org.mockito + mockito-junit-jupiter + test + + + org.mockito + mockito-inline + test + + + diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/annotation/order/CustomOrder.java b/ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/annotation/order/CustomOrder.java new file mode 100644 index 00000000..069bf068 --- /dev/null +++ b/ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/annotation/order/CustomOrder.java @@ -0,0 +1,28 @@ +package com.ruoyi.common.core.annotation.order; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Repeatable; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * 自定义排序 + * 如何使用: + * @DefaultOrder(column="createTime", orderType="desc", tableName="user") + * class UserVO { + * ... + * } + * startPage(UserVO.class) + * @author ruoyi + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@Repeatable(CustomOrders.class) +public @interface CustomOrder { + // 列名 驼峰最终会转换为下划线命名法作为最终排序列名。 + String column() default ""; + + // 列对应的表名 + String tableName() default ""; +} \ No newline at end of file diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/annotation/order/CustomOrders.java b/ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/annotation/order/CustomOrders.java new file mode 100644 index 00000000..05667372 --- /dev/null +++ b/ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/annotation/order/CustomOrders.java @@ -0,0 +1,19 @@ +package com.ruoyi.common.core.annotation.order; + +import org.springframework.stereotype.Component; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * 自定义排序组合注解 + * @author ruoyi + */ +@Target({ElementType.TYPE}) +@Retention(RetentionPolicy.RUNTIME) +@Component +public @interface CustomOrders { + CustomOrder[] value(); +} \ No newline at end of file diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/annotation/order/DefaultOrder.java b/ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/annotation/order/DefaultOrder.java new file mode 100644 index 00000000..67f7a840 --- /dev/null +++ b/ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/annotation/order/DefaultOrder.java @@ -0,0 +1,27 @@ +package com.ruoyi.common.core.annotation.order; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * 默认排序 + * 如何使用: + * @DefaultOrder(column="createTime", orderType="desc", tableName="user") + * class UserVO { + * ... + * } + * startPage(UserVO.class) + * @author ruoyi + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +public @interface DefaultOrder { + // 默认排序列 驼峰最终会转换为下划线命名法作为最终排序列名。 + String column() default ""; + // 排序类型, desc/asc + String orderType() default "asc"; + // 表名, 可不指定 + String tableName() default ""; +} \ No newline at end of file diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/annotation/order/TableAlias.java b/ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/annotation/order/TableAlias.java new file mode 100644 index 00000000..edcec32b --- /dev/null +++ b/ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/annotation/order/TableAlias.java @@ -0,0 +1,25 @@ +package com.ruoyi.common.core.annotation.order; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * 字段表别名 + * 如何使用: + * class UserVO { + * ... + * @TableAlias("dept") + * private String deptName; + * ... + * } + * startPage(UserVO.class) + * 查询多表关联时, 指定表别名, 即可简单实现完成对应的字段排序。 + * @author ruoyi + */ +@Target(ElementType.FIELD) +@Retention(RetentionPolicy.RUNTIME) +public @interface TableAlias { + String value() default ""; +} \ No newline at end of file diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/web/controller/BaseController.java b/ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/web/controller/BaseController.java index 64169511..3c687cc6 100644 --- a/ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/web/controller/BaseController.java +++ b/ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/web/controller/BaseController.java @@ -1,8 +1,14 @@ package com.ruoyi.common.core.web.controller; import java.beans.PropertyEditorSupport; +import java.lang.reflect.Field; import java.util.Date; import java.util.List; +import java.util.Objects; + +import com.ruoyi.common.core.annotation.order.CustomOrder; +import com.ruoyi.common.core.annotation.order.DefaultOrder; +import com.ruoyi.common.core.annotation.order.TableAlias; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.web.bind.WebDataBinder; @@ -20,7 +26,7 @@ import com.ruoyi.common.core.web.page.TableSupport; /** * web层通用数据处理 - * + * * @author ruoyi */ public class BaseController @@ -60,12 +66,162 @@ public class BaseController } } + /** + * 设置请求分页数据 + * 并根据class的注解设置排序列 + * 列对应表名优先级 + * TableAlias > CustomOrder > DefaultOrder + * @param clazz + */ + protected void startPage(Class clazz) { + // 正常分页的情况 + if (clazz == null) { + startPage(); + return; + } + PageDomain pageDomain = TableSupport.buildPageRequest(); + Integer pageNum = pageDomain.getPageNum(); + Integer pageSize = pageDomain.getPageSize(); + if (pageNum != null && pageSize != null) { + PageHelper.startPage(pageNum, pageSize); + } + + // 处理orderBy的情况。 + String orderBy = SqlUtil.escapeOrderBySql(pageDomain.getOrderBy()); + String column = SqlUtil.escapeOrderBySql(pageDomain.getOrderByColumn()); + // 获取默认排序 + if (StringUtils.isEmpty(orderBy)) { + orderBy = getDefaultOrderByAnnotation(clazz); + } + + if (StringUtils.isEmpty(orderBy)) { + return; + } + // 根据列获取表名 + String tableName = getTableNameByColumn(clazz, column); + if (StringUtils.isNotEmpty(tableName)) { + orderBy = tableName + "." + orderBy; + } + PageHelper.orderBy(orderBy); + } + + /** + * 尝试从field的TableAlias获取表别名 + * @param clazz + * @param column + * @return + */ + private static String getFieldTableName(Class clazz, String column) { + // 获取对象中所有的成员变量 + Field[] declaredFields = clazz.getDeclaredFields(); + for (Field field : declaredFields) { + String fieldName = field.getName(); + if (Objects.equals(column, fieldName)) { + if (field.isAnnotationPresent(TableAlias.class)) { + TableAlias orderTableAlias = field.getAnnotation(TableAlias.class); + return orderTableAlias.value(); + } + } + } + return null; + } + + /** + * 尝试从CustomOrder获取表别名 + * @param clazz + * @param column + * @return + */ + private static String getCustomOrderTableName(Class clazz, String column) { + // 获取对象中所有的成员变量 + if (clazz.isAnnotationPresent(CustomOrder.class)) { + CustomOrder[] customOrders = clazz.getAnnotationsByType(CustomOrder.class); + for (CustomOrder customOrder : customOrders) { + String realColumn = column; + if (column.contains("_")) { + realColumn = StringUtils.toCamelCase(customOrder.column()); + } + if (Objects.equals(column, realColumn)) { + return customOrder.tableName(); + } + } + } + return null; + } + /** + * 获取默认排序 + * @param clazz + * @return + */ + private static String getDefaultTableName(Class clazz) { + // 最后,查找类上的tableName + if (clazz.isAnnotationPresent(DefaultOrder.class)) { + DefaultOrder defaultOrder = clazz.getAnnotation(DefaultOrder.class); + if (StringUtils.isNotEmpty(defaultOrder.tableName())) { + return defaultOrder.tableName(); + } + } + return null; + } + + /** + * 通过过滤列获取对应的table + * @param clazz + * @param column + * @return + */ + private static String getTableNameByColumn(Class clazz, String column) { + if (clazz == null) { + return null; + } + String tableName = null; + // 未指定排序 则尝试设置默认排序的Table + if (StringUtils.isEmpty(column)) { + tableName = getDefaultTableName(clazz); + return tableName; + } + + + // 优先获取字段的tableName + tableName = getFieldTableName(clazz, column); + if (StringUtils.isNotEmpty(tableName)) { + return tableName; + } + + // 其次查找CustomOrder定义的column对应的TableName + tableName = getCustomOrderTableName(clazz, column); + if (StringUtils.isNotEmpty(tableName)) { + return tableName; + } + + // 获取默认排序 + tableName = getDefaultTableName(clazz); + + return tableName; + } + + /** + * 获取类注解获取默认过滤列 + * @param clazz + * @return + */ + private String getDefaultOrderByAnnotation(Class clazz) { + if (!clazz.isAnnotationPresent(DefaultOrder.class)) { + return null; + } + DefaultOrder defaultOrder = clazz.getAnnotation(DefaultOrder.class); + String orderBy = null; + if (StringUtils.isNotEmpty(defaultOrder.column())) { + orderBy = StringUtils.toUnderScoreCase(defaultOrder.column()) + " " + defaultOrder.orderType(); + } + return orderBy; + } + /** * 响应请求分页数据 */ @SuppressWarnings({ "rawtypes", "unchecked" }) - protected TableDataInfo getDataTable(List list) - { + protected TableDataInfo getDataTable(List list) { TableDataInfo rspData = new TableDataInfo(); rspData.setCode(HttpStatus.SUCCESS); rspData.setRows(list); @@ -76,7 +232,7 @@ public class BaseController /** * 响应返回结果 - * + * * @param rows 影响行数 * @return 操作结果 */ @@ -87,7 +243,7 @@ public class BaseController /** * 响应返回结果 - * + * * @param result 结果 * @return 操作结果 */ diff --git a/ruoyi-common/ruoyi-common-core/src/test/com/ruoyi/common/core/web/controller/BaseControllerTest.java b/ruoyi-common/ruoyi-common-core/src/test/com/ruoyi/common/core/web/controller/BaseControllerTest.java new file mode 100644 index 00000000..84d47465 --- /dev/null +++ b/ruoyi-common/ruoyi-common-core/src/test/com/ruoyi/common/core/web/controller/BaseControllerTest.java @@ -0,0 +1,125 @@ +package com.ruoyi.common.core.web.controller; + +import com.github.pagehelper.Page; +import com.github.pagehelper.PageHelper; +import com.ruoyi.common.core.annotation.order.CustomOrder; +import com.ruoyi.common.core.annotation.order.DefaultOrder; +import com.ruoyi.common.core.annotation.order.TableAlias; +import com.ruoyi.common.core.web.domain.BaseEntity; +import com.ruoyi.common.core.web.page.PageDomain; +import com.ruoyi.common.core.web.page.TableSupport; +import org.junit.jupiter.api.Test; +import org.mockito.MockedStatic; +import org.mockito.Mockito; + +import static org.junit.jupiter.api.Assertions.*; + +class BaseControllerTest { + + @Test + void testStartPageByDefaultOrder() + { + @DefaultOrder(tableName = "user", column = "userName") + class UserVO { + private String userName; + } + PageDomain pageDomain = new PageDomain(); + pageDomain.setPageNum(10); + pageDomain.setPageSize(20); + + MockedStatic mocked = Mockito.mockStatic(TableSupport.class); + mocked.when(TableSupport::buildPageRequest).thenReturn( + pageDomain + ); + new BaseController().startPage(UserVO.class); + Page localPage = PageHelper.getLocalPage(); + String orderBy = localPage.getOrderBy(); + assertEquals("user.user_name asc", orderBy); + } + + @Test + void testStartPageByDefaultOrder2() + { + @DefaultOrder(tableName = "user", column = "user_name") + class UserVO { + private String userName; + } + PageDomain pageDomain = new PageDomain(); + pageDomain.setPageNum(10); + pageDomain.setPageSize(20); + + MockedStatic mocked = Mockito.mockStatic(TableSupport.class); + mocked.when(TableSupport::buildPageRequest).thenReturn( + pageDomain + ); + new BaseController().startPage(UserVO.class); + Page localPage = PageHelper.getLocalPage(); + String orderBy = localPage.getOrderBy(); + assertEquals("user.user_name asc", orderBy); + } + @Test + void testStartPageByTableAlias() + { + class UserVO { + @TableAlias("user") + private String userName; + } + PageDomain pageDomain = new PageDomain(); + pageDomain.setPageNum(10); + pageDomain.setPageSize(20); + pageDomain.setOrderByColumn("userName"); + MockedStatic mocked = Mockito.mockStatic(TableSupport.class); + mocked.when(TableSupport::buildPageRequest).thenReturn( + pageDomain + ); + new BaseController().startPage(UserVO.class); + Page localPage = PageHelper.getLocalPage(); + String orderBy = localPage.getOrderBy(); + assertEquals("user.user_name asc", orderBy); + } + @Test + void testStartPageByCustomOrder() + { + @CustomOrder(tableName = "user", column = "createTime") + class UserVO extends BaseEntity { + private String userName; + } + PageDomain pageDomain = new PageDomain(); + pageDomain.setPageNum(10); + pageDomain.setPageSize(20); + pageDomain.setOrderByColumn("createTime"); + pageDomain.setIsAsc("desc"); + + MockedStatic mocked = Mockito.mockStatic(TableSupport.class); + mocked.when(TableSupport::buildPageRequest).thenReturn( + pageDomain + ); + new BaseController().startPage(UserVO.class); + Page localPage = PageHelper.getLocalPage(); + String orderBy = localPage.getOrderBy(); + assertEquals("user.create_time desc", orderBy); + } + + @Test + void testStartPageByCustomOrder2() + { + @CustomOrder(tableName = "user", column = "create_time") + class UserVO extends BaseEntity { + private String userName; + } + PageDomain pageDomain = new PageDomain(); + pageDomain.setPageNum(10); + pageDomain.setPageSize(20); + pageDomain.setOrderByColumn("createTime"); + pageDomain.setIsAsc("desc"); + + MockedStatic mocked = Mockito.mockStatic(TableSupport.class); + mocked.when(TableSupport::buildPageRequest).thenReturn( + pageDomain + ); + new BaseController().startPage(UserVO.class); + Page localPage = PageHelper.getLocalPage(); + String orderBy = localPage.getOrderBy(); + assertEquals("user.create_time desc", orderBy); + } +} \ No newline at end of file