【同步】BOOT 和 CLOUD 的功能

This commit is contained in:
YunaiV
2025-07-15 21:54:41 +08:00
parent 031fa11e38
commit 08c31e889d
211 changed files with 9932 additions and 2671 deletions

View File

@@ -3,6 +3,7 @@ package cn.iocoder.yudao.framework.common.util.date;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.LocalDateTimeUtil;
import cn.hutool.core.date.TemporalAccessorUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.enums.DateIntervalEnum;
@@ -19,7 +20,7 @@ import static cn.hutool.core.date.DatePattern.UTC_MS_WITH_XXX_OFFSET_PATTERN;
import static cn.hutool.core.date.DatePattern.createFormatter;
/**
* 时间工具类,用于 {@link java.time.LocalDateTime}
* 时间工具类,用于 {@link LocalDateTime}
*
* @author 芋道源码
*/
@@ -312,4 +313,16 @@ public class LocalDateTimeUtils {
}
}
/**
* 将给定的 {@link LocalDateTime} 转换为自 Unix 纪元时间1970-01-01T00:00:00Z以来的秒数。
*
* @param sourceDateTime 需要转换的本地日期时间,不能为空
* @return 自 1970-01-01T00:00:00Z 起的秒数epoch second
* @throws NullPointerException 如果 {@code sourceDateTime} 为 {@code null}
* @throws DateTimeException 如果转换过程中发生时间超出范围或其他时间处理异常
*/
public static Long toEpochSecond(LocalDateTime sourceDateTime) {
return TemporalAccessorUtil.toInstant(sourceDateTime).getEpochSecond();
}
}

View File

@@ -3,6 +3,7 @@ package cn.iocoder.yudao.framework.excel.core.handler;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.ReflectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.spring.SpringUtil;
import cn.hutool.poi.excel.ExcelUtil;
@@ -10,6 +11,8 @@ import cn.iocoder.yudao.framework.common.core.KeyValue;
import cn.iocoder.yudao.framework.dict.core.DictFrameworkUtils;
import cn.iocoder.yudao.framework.excel.core.annotations.ExcelColumnSelect;
import cn.iocoder.yudao.framework.excel.core.function.ExcelColumnSelectFunction;
import com.alibaba.excel.annotation.ExcelIgnore;
import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.write.handler.SheetWriteHandler;
import com.alibaba.excel.write.metadata.holder.WriteSheetHolder;
@@ -20,6 +23,7 @@ import org.apache.poi.ss.usermodel.*;
import org.apache.poi.ss.util.CellRangeAddressList;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
@@ -56,7 +60,20 @@ public class SelectSheetWriteHandler implements SheetWriteHandler {
public SelectSheetWriteHandler(Class<?> head) {
// 解析下拉数据
int colIndex = 0;
boolean ignoreUnannotated = head.isAnnotationPresent(ExcelIgnoreUnannotated.class);
for (Field field : head.getDeclaredFields()) {
// 关联 https://github.com/YunaiV/ruoyi-vue-pro/pull/853
// 1.1 忽略 static final 或 transient 的字段
if (isStaticFinalOrTransient(field) ) {
continue;
}
// 1.2 忽略的字段跳过
if ((ignoreUnannotated && !field.isAnnotationPresent(ExcelProperty.class))
|| field.isAnnotationPresent(ExcelIgnore.class)) {
continue;
}
// 2. 核心:处理有 ExcelColumnSelect 注解的字段
if (field.isAnnotationPresent(ExcelColumnSelect.class)) {
ExcelProperty excelProperty = field.getAnnotation(ExcelProperty.class);
if (excelProperty != null && excelProperty.index() != -1) {
@@ -68,6 +85,19 @@ public class SelectSheetWriteHandler implements SheetWriteHandler {
}
}
/**
* 判断字段是否是静态的、最终的、 transient 的
* 原因EasyExcel 默认是忽略 static final 或 transient 的字段,所以需要判断
*
* @param field 字段
* @return 是否是静态的、最终的、transient 的
*/
private boolean isStaticFinalOrTransient(Field field) {
return (Modifier.isStatic(field.getModifiers()) && Modifier.isFinal(field.getModifiers()))
|| Modifier.isTransient(field.getModifiers());
}
/**
* 获得下拉数据,并添加到 {@link #selectMap} 中
*

View File

@@ -5,9 +5,9 @@ import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.core.task.SimpleAsyncTaskExecutor;
/**
* 异步任务 Configuration
@@ -22,13 +22,20 @@ public class YudaoAsyncAutoConfiguration {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
if (!(bean instanceof ThreadPoolTaskExecutor)) {
return bean;
// 处理 ThreadPoolTaskExecutor
if (bean instanceof ThreadPoolTaskExecutor) {
ThreadPoolTaskExecutor executor = (ThreadPoolTaskExecutor) bean;
executor.setTaskDecorator(TtlRunnable::get);
return executor;
}
// 修改提交的任务,接入 TransmittableThreadLocal
ThreadPoolTaskExecutor executor = (ThreadPoolTaskExecutor) bean;
executor.setTaskDecorator(TtlRunnable::get);
return executor;
// 处理 SimpleAsyncTaskExecutor
// 参考 https://t.zsxq.com/CBoks 增加
if (bean instanceof SimpleAsyncTaskExecutor) {
SimpleAsyncTaskExecutor executor = (SimpleAsyncTaskExecutor) bean;
executor.setTaskDecorator(TtlRunnable::get);
return executor;
}
return bean;
}
};

View File

@@ -42,6 +42,7 @@ public interface BaseMapperX<T> extends MPJBaseMapper<T> {
default PageResult<T> selectPage(PageParam pageParam, Collection<SortingField> sortingFields, @Param("ew") Wrapper<T> queryWrapper) {
// 特殊:不分页,直接查询全部
if (PageParam.PAGE_SIZE_NONE.equals(pageParam.getPageSize())) {
MyBatisUtils.addOrder(queryWrapper, sortingFields);
List<T> list = selectList(queryWrapper);
return new PageResult<>(list, (long) list.size());
}

View File

@@ -1,11 +1,15 @@
package cn.iocoder.yudao.framework.mybatis.core.util;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.lang.func.Func1;
import cn.hutool.core.lang.func.LambdaUtil;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import cn.iocoder.yudao.framework.common.pojo.SortingField;
import cn.iocoder.yudao.framework.mybatis.core.enums.DbTypeEnum;
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.OrderItem;
import com.baomidou.mybatisplus.core.toolkit.StringPool;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
@@ -18,7 +22,6 @@ import net.sf.jsqlparser.schema.Table;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;
/**
* MyBatis 工具类
@@ -35,15 +38,27 @@ public class MyBatisUtils {
// 页码 + 数量
Page<T> page = new Page<>(pageParam.getPageNo(), pageParam.getPageSize());
// 排序字段
if (!CollectionUtil.isEmpty(sortingFields)) {
page.addOrder(sortingFields.stream().map(sortingField -> SortingField.ORDER_ASC.equals(sortingField.getOrder())
? OrderItem.asc(StrUtil.toUnderlineCase(sortingField.getField()))
: OrderItem.desc(StrUtil.toUnderlineCase(sortingField.getField())))
.collect(Collectors.toList()));
if (CollUtil.isNotEmpty(sortingFields)) {
for (SortingField sortingField : sortingFields) {
page.addOrder(new OrderItem().setAsc(SortingField.ORDER_ASC.equals(sortingField.getOrder()))
.setColumn(StrUtil.toUnderlineCase(sortingField.getField())));
}
}
return page;
}
public static <T> void addOrder(Wrapper<T> wrapper, Collection<SortingField> sortingFields) {
if (CollUtil.isEmpty(sortingFields)) {
return;
}
QueryWrapper<T> query = (QueryWrapper<T>) wrapper;
for (SortingField sortingField : sortingFields) {
query.orderBy(true,
SortingField.ORDER_ASC.equals(sortingField.getOrder()),
StrUtil.toUnderlineCase(sortingField.getField()));
}
}
/**
* 将拦截器添加到链中
* 由于 MybatisPlusInterceptor 不支持添加拦截器,所以只能全量设置
@@ -103,4 +118,18 @@ public class MyBatisUtils {
.replace("#{value}", StrUtil.toString(value));
}
/**
* 将驼峰命名转换为下划线命名
*
* 使用场景:
* 1. <a href="https://gitee.com/zhijiantianya/ruoyi-vue-pro/pulls/1357/files">fix:修复"商品统计聚合函数的别名与排序字段不符"导致的 SQL 异常</a>
*
* @param func 字段名函数(驼峰命名)
* @return 字段名(下划线命名)
*/
public static <T> String toUnderlineCase(Func1<T, ?> func) {
String fieldName = LambdaUtil.getFieldName(func);
return StrUtil.toUnderlineCase(fieldName);
}
}

View File

@@ -128,8 +128,10 @@ public class SecurityFrameworkUtils {
// 额外设置到 request 中,用于 ApiAccessLogFilter 可以获取到用户编号;
// 原因是Spring Security 的 Filter 在 ApiAccessLogFilter 后面,在它记录访问日志时,线上上下文已经没有用户编号等信息
WebFrameworkUtils.setLoginUserId(request, loginUser.getId());
WebFrameworkUtils.setLoginUserType(request, loginUser.getUserType());
if (request != null) {
WebFrameworkUtils.setLoginUserId(request, loginUser.getId());
WebFrameworkUtils.setLoginUserType(request, loginUser.getUserType());
}
}
private static Authentication buildAuthentication(LoginUser loginUser, HttpServletRequest request) {

View File

@@ -84,7 +84,7 @@ public class StringDesensitizeSerializer extends StdSerializer<String> implement
*/
private Field getField(JsonGenerator generator) {
String currentName = generator.getOutputContext().getCurrentName();
Object currentValue = generator.getCurrentValue();
Object currentValue = generator.currentValue();
Class<?> currentValueClass = currentValue.getClass();
return ReflectUtil.getField(currentValueClass, currentName);
}