1. 新增分页接口聚合查询注解支持

2. 优化 databus api 日志记录的字段缺失问题
3. 新增 eplat sso 页面登录校验
4. 用户、部门编辑新增 seata 事务支持
5. 新增 iwork 流程发起接口
6. 新增 eban 同步用户时的岗位处理逻辑
7. 新增无 skywalking 时的 traceId 支持
This commit is contained in:
chenbowen
2025-11-18 10:03:34 +08:00
parent af7f103a38
commit 266eb45e00
74 changed files with 5001 additions and 102 deletions

View File

@@ -1,9 +1,9 @@
package com.zt.plat.framework.mybatis.config;
import cn.hutool.core.util.StrUtil;
import com.zt.plat.framework.mybatis.core.handler.DefaultDBFieldHandler;
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.autoconfigure.MybatisPlusAutoConfiguration;
import com.baomidou.mybatisplus.autoconfigure.MybatisPlusPropertiesCustomizer;
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import com.baomidou.mybatisplus.core.incrementer.IKeyGenerator;
import com.baomidou.mybatisplus.extension.incrementer.*;
@@ -11,6 +11,8 @@ import com.baomidou.mybatisplus.extension.parser.JsqlParserGlobal;
import com.baomidou.mybatisplus.extension.parser.cache.JdkSerialCaffeineJsqlParseCache;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import com.zt.plat.framework.mybatis.core.handler.DefaultDBFieldHandler;
import com.zt.plat.framework.mybatis.core.sum.PageSumTableFieldAnnotationHandler;
import org.apache.ibatis.annotations.Mapper;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.autoconfigure.AutoConfiguration;
@@ -25,29 +27,40 @@ import java.util.concurrent.TimeUnit;
*
* @author ZT
*/
@AutoConfiguration(before = MybatisPlusAutoConfiguration.class) // 目的:先于 MyBatis Plus 自动配置,避免 @MapperScan 可能扫描不到 Mapper 打印 warn 日志
@AutoConfiguration(before = MybatisPlusAutoConfiguration.class) // 先于官方自动配置,避免 Mapper 未扫描完成
@MapperScan(value = "${zt.info.base-package}", annotationClass = Mapper.class,
lazyInitialization = "${mybatis.lazy-initialization:false}") // Mapper 懒加载,目前仅用于单元测试
lazyInitialization = "${mybatis.lazy-initialization:false}") // Mapper 懒加载,目前仅单测需要
public class ZtMybatisAutoConfiguration {
static {
// 动态 SQL 智能优化支持本地缓存加速解析,更完善的租户复杂 XML 动态 SQL 支持,静态注入缓存
// 使用本地缓存加速 JsqlParser 解析,复杂动态 SQL 性能更稳定
JsqlParserGlobal.setJsqlParseCache(new JdkSerialCaffeineJsqlParseCache(
(cache) -> cache.maximumSize(1024)
.expireAfterWrite(5, TimeUnit.SECONDS))
);
cache -> cache.maximumSize(1024).expireAfterWrite(5, TimeUnit.SECONDS)));
}
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();
mybatisPlusInterceptor.addInnerInterceptor(new PaginationInnerInterceptor()); // 分页插件
return mybatisPlusInterceptor;
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new PaginationInnerInterceptor()); // 分页插件
return interceptor;
}
@Bean
public MetaObjectHandler defaultMetaObjectHandler() {
return new DefaultDBFieldHandler(); // 自动填充参数类
return new DefaultDBFieldHandler(); // 统一的公共字段填充
}
@Bean
public MybatisPlusPropertiesCustomizer pageSumAnnotationCustomizer() {
// 通过官方扩展点为 @PageSum 字段自动注入 exist = false 的 TableField 注解
return properties -> {
var globalConfig = properties.getGlobalConfig();
if (globalConfig == null) {
return;
}
globalConfig.setAnnotationHandler(
new PageSumTableFieldAnnotationHandler(globalConfig.getAnnotationHandler()));
};
}
@Bean

View File

@@ -5,6 +5,7 @@ import com.zt.plat.framework.common.pojo.PageParam;
import com.zt.plat.framework.common.pojo.PageResult;
import com.zt.plat.framework.common.pojo.SortablePageParam;
import com.zt.plat.framework.common.pojo.SortingField;
import com.zt.plat.framework.mybatis.core.sum.PageSumSupport;
import com.zt.plat.framework.mybatis.core.util.JdbcUtils;
import com.zt.plat.framework.mybatis.core.util.MyBatisUtils;
import com.baomidou.mybatisplus.annotation.DbType;
@@ -43,14 +44,18 @@ public interface BaseMapperX<T> extends MPJBaseMapper<T> {
// 特殊:不分页,直接查询全部
if (PageParam.PAGE_SIZE_NONE.equals(pageParam.getPageSize())) {
List<T> list = selectList(queryWrapper);
return new PageResult<>(list, (long) list.size());
PageResult<T> pageResult = new PageResult<>(list, (long) list.size());
PageSumSupport.tryAttachSummary(this, queryWrapper, pageResult);
return pageResult;
}
// MyBatis Plus 查询
IPage<T> mpPage = MyBatisUtils.buildPage(pageParam, sortingFields);
selectPage(mpPage, queryWrapper);
// 转换返回
return new PageResult<>(mpPage.getRecords(), mpPage.getTotal());
PageResult<T> pageResult = new PageResult<>(mpPage.getRecords(), mpPage.getTotal());
PageSumSupport.tryAttachSummary(this, queryWrapper, pageResult);
return pageResult;
}
default <D> PageResult<D> selectJoinPage(PageParam pageParam, Class<D> clazz, MPJLambdaWrapper<T> lambdaWrapper) {

View File

@@ -0,0 +1,76 @@
package com.zt.plat.framework.mybatis.core.sum;
import java.lang.reflect.Field;
import java.util.Objects;
/**
* Metadata describing a field participating in page-level SUM aggregation.
*/
final class PageSumFieldMeta {
private final String propertyName;
private final String columnExpression;
private final String selectAlias;
private final Class<?> fieldType;
PageSumFieldMeta(String propertyName, String columnExpression, String selectAlias, Class<?> fieldType) {
this.propertyName = propertyName;
this.columnExpression = columnExpression;
this.selectAlias = selectAlias;
this.fieldType = fieldType;
}
static PageSumFieldMeta of(Field field, String columnExpression) {
String property = field.getName();
return new PageSumFieldMeta(property, columnExpression, property, field.getType());
}
String getPropertyName() {
return propertyName;
}
String getColumnExpression() {
return columnExpression;
}
String getSelectAlias() {
return selectAlias;
}
Class<?> getFieldType() {
return fieldType;
}
String buildSelectSegment() {
return "SUM(" + columnExpression + ") AS " + selectAlias;
}
@Override
public int hashCode() {
return Objects.hash(propertyName, columnExpression, selectAlias, fieldType);
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof PageSumFieldMeta other)) {
return false;
}
return Objects.equals(propertyName, other.propertyName)
&& Objects.equals(columnExpression, other.columnExpression)
&& Objects.equals(selectAlias, other.selectAlias)
&& Objects.equals(fieldType, other.fieldType);
}
@Override
public String toString() {
return "PageSumFieldMeta{" +
"propertyName='" + propertyName + '\'' +
", columnExpression='" + columnExpression + '\'' +
", selectAlias='" + selectAlias + '\'' +
", fieldType=" + fieldType +
'}';
}
}

View File

@@ -0,0 +1,341 @@
package com.zt.plat.framework.mybatis.core.sum;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.metadata.TableFieldInfo;
import com.baomidou.mybatisplus.core.metadata.TableInfo;
import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
import com.zt.plat.framework.common.annotation.PageSum;
import com.zt.plat.framework.common.pojo.PageResult;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
/**
* Utility that inspects {@link PageSum} annotations and attaches aggregated SUM results to {@link PageResult}.
*/
public final class PageSumSupport {
private static final Logger LOGGER = LoggerFactory.getLogger(PageSumSupport.class);
private static final ConcurrentMap<Class<?>, Optional<Class<?>>> ENTITY_CLASS_CACHE = new ConcurrentHashMap<>();
private static final ConcurrentMap<Class<?>, List<PageSumFieldMeta>> FIELD_META_CACHE = new ConcurrentHashMap<>();
private static final ConcurrentMap<Class<?>, Optional<Field>> SQL_SELECT_FIELD_CACHE = new ConcurrentHashMap<>();
private PageSumSupport() {
}
public static <T> void tryAttachSummary(Object mapperProxy, Wrapper<T> wrapper, PageResult<?> pageResult) {
if (mapperProxy == null || pageResult == null) {
return;
}
Class<?> entityClass = resolveEntityClass(mapperProxy.getClass());
if (entityClass == null) {
return;
}
List<PageSumFieldMeta> fieldMetas = resolveFieldMetas(entityClass);
if (fieldMetas.isEmpty()) {
return;
}
Map<String, BigDecimal> summary = executeSum((BaseMapper<T>) mapperProxy, wrapper, fieldMetas);
if (!summary.isEmpty()) {
pageResult.setSummary(summary);
}
}
private static Class<?> resolveEntityClass(Class<?> mapperProxyClass) {
return ENTITY_CLASS_CACHE.computeIfAbsent(mapperProxyClass, PageSumSupport::extractEntityClass)
.orElse(null);
}
private static Optional<Class<?>> extractEntityClass(Class<?> mapperProxyClass) {
Class<?>[] interfaces = mapperProxyClass.getInterfaces();
for (Class<?> iface : interfaces) {
Class<?> entityClass = extractEntityClassFromInterface(iface);
if (entityClass != null) {
return Optional.of(entityClass);
}
}
return Optional.empty();
}
private static Class<?> extractEntityClassFromInterface(Class<?> interfaceClass) {
if (interfaceClass == null || interfaceClass == Object.class) {
return null;
}
// inspect direct generic interfaces
for (Type type : interfaceClass.getGenericInterfaces()) {
Class<?> resolved = resolveFromType(type);
if (resolved != null) {
return resolved;
}
}
// fallback to parent interfaces recursively
for (Class<?> parent : interfaceClass.getInterfaces()) {
Class<?> resolved = extractEntityClassFromInterface(parent);
if (resolved != null) {
return resolved;
}
}
// handle generic super class (rare for interfaces but keep for completeness)
return resolveFromType(interfaceClass.getGenericSuperclass());
}
private static Class<?> resolveFromType(Type type) {
if (type == null) {
return null;
}
if (type instanceof ParameterizedType parameterizedType) {
Type raw = parameterizedType.getRawType();
if (raw instanceof Class<?> rawClass) {
if (BaseMapper.class.isAssignableFrom(rawClass)) {
Type[] actualTypes = parameterizedType.getActualTypeArguments();
if (actualTypes.length > 0) {
Type actual = actualTypes[0];
return toClass(actual);
}
}
Class<?> resolved = extractEntityClassFromInterface(rawClass);
if (resolved != null) {
return resolved;
}
}
for (Type actual : parameterizedType.getActualTypeArguments()) {
Class<?> resolved = resolveFromType(actual);
if (resolved != null) {
return resolved;
}
}
} else if (type instanceof Class<?> clazz) {
return extractEntityClassFromInterface(clazz);
}
return null;
}
private static Class<?> toClass(Type type) {
if (type instanceof Class<?> clazz) {
return clazz;
}
if (type instanceof ParameterizedType parameterizedType) {
Type raw = parameterizedType.getRawType();
if (raw instanceof Class<?>) {
return (Class<?>) raw;
}
}
return null;
}
private static List<PageSumFieldMeta> resolveFieldMetas(Class<?> entityClass) {
return FIELD_META_CACHE.computeIfAbsent(entityClass, PageSumSupport::scanFieldMetas);
}
private static List<PageSumFieldMeta> scanFieldMetas(Class<?> entityClass) {
TableInfo tableInfo = TableInfoHelper.getTableInfo(entityClass);
if (tableInfo == null) {
LOGGER.debug("No TableInfo found for entity {}, falling back to annotation provided column expressions.",
entityClass.getName());
}
Map<String, String> propertyColumnMap = tableInfo != null
? buildPropertyColumnMap(tableInfo)
: Collections.emptyMap();
List<PageSumFieldMeta> metas = new ArrayList<>();
Class<?> current = entityClass;
while (current != null && current != Object.class) {
Field[] fields = current.getDeclaredFields();
for (Field field : fields) {
PageSum annotation = field.getAnnotation(PageSum.class);
if (annotation == null) {
continue;
}
if (!isNumeric(field.getType())) {
LOGGER.warn("Field {}.{} annotated with @PageSum is not numeric and will be ignored.",
entityClass.getSimpleName(), field.getName());
continue;
}
String columnExpression = resolveColumnExpression(annotation, field, propertyColumnMap);
if (StrUtil.isBlank(columnExpression)) {
LOGGER.warn("Unable to resolve column for field {}.{} with @PageSum, skipping.",
entityClass.getSimpleName(), field.getName());
continue;
}
metas.add(PageSumFieldMeta.of(field, columnExpression));
}
current = current.getSuperclass();
}
return metas.isEmpty() ? Collections.emptyList() : Collections.unmodifiableList(metas);
}
private static Map<String, String> buildPropertyColumnMap(TableInfo tableInfo) {
Map<String, String> mapping = new LinkedHashMap<>();
if (StrUtil.isNotBlank(tableInfo.getKeyProperty()) && StrUtil.isNotBlank(tableInfo.getKeyColumn())) {
mapping.put(tableInfo.getKeyProperty(), tableInfo.getKeyColumn());
}
for (TableFieldInfo fieldInfo : tableInfo.getFieldList()) {
mapping.put(fieldInfo.getProperty(), fieldInfo.getColumn());
}
return mapping;
}
private static String resolveColumnExpression(PageSum annotation, Field field, Map<String, String> propertyColumnMap) {
if (StrUtil.isNotBlank(annotation.column())) {
return annotation.column();
}
return propertyColumnMap.get(field.getName());
}
private static boolean isNumeric(Class<?> type) {
if (type.isPrimitive()) {
return type == int.class || type == long.class || type == double.class
|| type == float.class || type == short.class || type == byte.class;
}
return Number.class.isAssignableFrom(type) || BigDecimal.class.isAssignableFrom(type)
|| BigInteger.class.isAssignableFrom(type);
}
private static <T> Map<String, BigDecimal> executeSum(BaseMapper<T> mapper, Wrapper<T> wrapper, List<PageSumFieldMeta> metas) {
if (metas.isEmpty()) {
return Collections.emptyMap();
}
Wrapper<T> workingWrapper = cloneWrapper(wrapper);
applySelect(workingWrapper, metas);
List<Map<String, Object>> rows = mapper.selectMaps(workingWrapper);
Map<String, BigDecimal> result = new LinkedHashMap<>(metas.size());
Map<String, Object> row = rows.isEmpty() ? Collections.emptyMap() : rows.get(0);
for (PageSumFieldMeta meta : metas) {
Object value = extractValue(row, meta.getSelectAlias());
result.put(meta.getPropertyName(), toBigDecimal(value));
}
return result;
}
private static <T> Wrapper<T> cloneWrapper(Wrapper<T> wrapper) {
if (wrapper == null) {
return new QueryWrapper<>();
}
if (wrapper instanceof com.baomidou.mybatisplus.core.conditions.AbstractWrapper<?, ?, ?> abstractWrapper) {
@SuppressWarnings("unchecked")
Wrapper<T> clone = (Wrapper<T>) abstractWrapper.clone();
return clone;
}
return wrapper;
}
private static void applySelect(Wrapper<?> wrapper, List<PageSumFieldMeta> metas) {
String selectSql = buildSelectSql(metas);
if (wrapper instanceof QueryWrapper<?> queryWrapper) {
queryWrapper.select(selectSql);
return;
}
if (wrapper instanceof LambdaQueryWrapper<?> lambdaQueryWrapper) {
setSqlSelect(lambdaQueryWrapper, selectSql);
return;
}
// attempt reflective fallback for other wrapper implementations extending LambdaQueryWrapper
setSqlSelect(wrapper, selectSql);
}
private static String buildSelectSql(List<PageSumFieldMeta> metas) {
StringBuilder builder = new StringBuilder();
for (int i = 0; i < metas.size(); i++) {
if (i > 0) {
builder.append(',');
}
builder.append(metas.get(i).buildSelectSegment());
}
return builder.toString();
}
private static void setSqlSelect(Object wrapper, String selectSql) {
Field field = SQL_SELECT_FIELD_CACHE.computeIfAbsent(wrapper.getClass(), PageSumSupport::locateSqlSelectField)
.orElse(null);
if (field == null) {
LOGGER.debug("Unable to locate sqlSelect field on wrapper {}, summary aggregation skipped.",
wrapper.getClass().getName());
return;
}
try {
com.baomidou.mybatisplus.core.conditions.SharedString shared = (com.baomidou.mybatisplus.core.conditions.SharedString) field.get(wrapper);
if (shared == null) {
shared = com.baomidou.mybatisplus.core.conditions.SharedString.emptyString();
field.set(wrapper, shared);
}
shared.setStringValue(selectSql);
} catch (IllegalAccessException ex) {
LOGGER.warn("Failed to set sqlSelect on wrapper {}: {}", wrapper.getClass().getName(), ex.getMessage());
}
}
private static Optional<Field> locateSqlSelectField(Class<?> wrapperClass) {
Class<?> current = wrapperClass;
while (current != null && current != Object.class) {
try {
Field field = current.getDeclaredField("sqlSelect");
field.setAccessible(true);
return Optional.of(field);
} catch (NoSuchFieldException ignored) {
current = current.getSuperclass();
}
}
return Optional.empty();
}
private static Object extractValue(Map<String, Object> row, String alias) {
if (row == null || row.isEmpty()) {
return null;
}
if (row.containsKey(alias)) {
return row.get(alias);
}
for (Map.Entry<String, Object> entry : row.entrySet()) {
if (alias.equalsIgnoreCase(entry.getKey())) {
return entry.getValue();
}
}
return null;
}
private static BigDecimal toBigDecimal(Object value) {
if (value == null) {
return BigDecimal.ZERO;
}
if (value instanceof BigDecimal decimal) {
return decimal;
}
if (value instanceof BigInteger bigInteger) {
return new BigDecimal(bigInteger);
}
if (value instanceof Number number) {
return new BigDecimal(number.toString());
}
if (value instanceof CharSequence sequence) {
String text = sequence.toString().trim();
if (text.isEmpty()) {
return BigDecimal.ZERO;
}
try {
return new BigDecimal(text);
} catch (NumberFormatException ex) {
LOGGER.warn("Unable to parse numeric summary value '{}': {}", text, ex.getMessage());
return BigDecimal.ZERO;
}
}
LOGGER.warn("Unsupported summary value type: {}", value.getClass().getName());
return BigDecimal.ZERO;
}
}

View File

@@ -0,0 +1,79 @@
package com.zt.plat.framework.mybatis.core.sum;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.core.handlers.AnnotationHandler;
import com.zt.plat.framework.common.annotation.PageSum;
import org.springframework.core.annotation.AnnotationUtils;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.util.Collections;
import java.util.Map;
/**
* 让 {@link PageSum#exist()} 能够自动生成 {@link TableField#exist()} = false 的能力,
* 这样 DO 层无需重复编写 {@code @TableField(exist = false)}。
*/
public class PageSumTableFieldAnnotationHandler implements AnnotationHandler {
private static final AnnotationHandler DEFAULT_HANDLER = new AnnotationHandler() { };
/** 预构建 @TableField(exist = false) 的属性集合,避免重复创建 Map 对象 */
private static final Map<String, Object> TABLE_FIELD_EXIST_FALSE_ATTRIBUTES =
Collections.singletonMap("exist", Boolean.FALSE);
private final AnnotationHandler delegate;
public PageSumTableFieldAnnotationHandler(AnnotationHandler delegate) {
this.delegate = delegate != null ? delegate : DEFAULT_HANDLER;
}
@Override
public <T extends Annotation> T getAnnotation(Class<?> target, Class<T> annotationClass) {
return delegate.getAnnotation(target, annotationClass);
}
@Override
public <T extends Annotation> boolean isAnnotationPresent(Class<?> target, Class<T> annotationClass) {
return delegate.isAnnotationPresent(target, annotationClass);
}
@Override
public <T extends Annotation> T getAnnotation(java.lang.reflect.Method method, Class<T> annotationClass) {
return delegate.getAnnotation(method, annotationClass);
}
@Override
public <T extends Annotation> boolean isAnnotationPresent(java.lang.reflect.Method method, Class<T> annotationClass) {
return delegate.isAnnotationPresent(method, annotationClass);
}
@Override
public <T extends Annotation> T getAnnotation(Field field, Class<T> annotationClass) {
T annotation = delegate.getAnnotation(field, annotationClass);
if (annotation != null || annotationClass != TableField.class) {
return annotation;
}
PageSum pageSum = delegate.getAnnotation(field, PageSum.class);
if (pageSum != null && !pageSum.exist()) {
// 当字段只用于分页汇总时,动态合成一个 exist = false 的 TableField 注解
return annotationClass.cast(synthesizeTableField(field));
}
return null;
}
@Override
public <T extends Annotation> boolean isAnnotationPresent(Field field, Class<T> annotationClass) {
if (delegate.isAnnotationPresent(field, annotationClass)) {
return true;
}
if (annotationClass != TableField.class) {
return false;
}
PageSum pageSum = delegate.getAnnotation(field, PageSum.class);
return pageSum != null && !pageSum.exist();
}
private static TableField synthesizeTableField(Field field) {
return AnnotationUtils.synthesizeAnnotation(TABLE_FIELD_EXIST_FALSE_ATTRIBUTES, TableField.class, field);
}
}

View File

@@ -0,0 +1,68 @@
package com.zt.plat.framework.mybatis.core.sum;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.zt.plat.framework.common.annotation.PageSum;
import com.zt.plat.framework.common.pojo.PageResult;
import com.zt.plat.framework.mybatis.core.mapper.BaseMapperX;
import org.junit.jupiter.api.Test;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.math.BigDecimal;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
class PageSumSupportTest {
@Test
void shouldAttachSummaryWhenAnnotationPresent() {
TestMapper mapper = createMapperProxy();
PageResult<TestEntity> pageResult = new PageResult<>(Collections.emptyList(), 0L);
QueryWrapper<TestEntity> wrapper = new QueryWrapper<>();
PageSumSupport.tryAttachSummary(mapper, wrapper, pageResult);
assertFalse(pageResult.getSummary().isEmpty());
assertEquals(new BigDecimal("123.45"), pageResult.getSummary().get("amount"));
assertEquals(new BigDecimal("50"), pageResult.getSummary().get("virtualAmount"));
}
private TestMapper createMapperProxy() {
InvocationHandler handler = new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (method.getDeclaringClass() == Object.class) {
return method.invoke(this, args);
}
if ("selectMaps".equals(method.getName())) {
Map<String, Object> row = new HashMap<>();
row.put("amount", new BigDecimal("123.45"));
row.put("virtualAmount", new BigDecimal("50"));
return List.of(row);
}
return Collections.emptyList();
}
};
return (TestMapper) Proxy.newProxyInstance(
TestMapper.class.getClassLoader(),
new Class[]{TestMapper.class},
handler);
}
interface TestMapper extends BaseMapperX<TestEntity> {
}
static class TestEntity {
@PageSum(column = "amount")
private BigDecimal amount;
@PageSum(column = "virtual_column", exist = false)
private BigDecimal virtualAmount;
}
}