将开源版本的修改打补丁应用到商业版

This commit is contained in:
wsm
2026-04-09 19:33:52 +08:00
parent d94e9c9bc4
commit b52aec1611
111 changed files with 7215 additions and 275 deletions
+1
View File
@@ -8,6 +8,7 @@
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>jeecg-boot-base-core</artifactId>
<version>3.8.1</version>
<repositories>
<repository>
@@ -0,0 +1,75 @@
package org.jeecg.common.aspect;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.jeecg.common.aspect.annotation.CascadeDelete;
import org.jeecg.common.aspect.annotation.SonTable;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
@Slf4j
@Aspect
@Component
public class CascadeDeleteAspect {
@Autowired
private ApplicationContext context;
/**
* 环绕通知:通过 REQUIRED 传播属性确保事务一致性
*/
@Around("@annotation(cascadeDelete)")
@Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)//已完成测试,子表删除时抛出异常主表会一起回滚
public Object doCascadeDelete(ProceedingJoinPoint joinPoint, CascadeDelete cascadeDelete) throws Throwable {
// 1. 获取主表 ID (约定第一个参数)
Object[] args = joinPoint.getArgs();
if (args == null || args.length == 0 || args[0] == null) {
return joinPoint.proceed();
}
String mainId = args[0].toString();
// 2. 执行子表逻辑删除
// 因为标记了 @Transactional,这里的删除操作会自动进入事务
for (SonTable son : cascadeDelete.sons()) {
Object mapper = context.getBean(son.mapper());
executeDelete(mapper, son.joinColumn(), mainId);
log.info("级联删除子表 [{}] 成功", son.mapper().getSimpleName());
}
// 3. 执行主表删除逻辑
if (true) {
throw new RuntimeException("模拟子表删除异常,触发回滚!");
}
// 如果主表 Service 抛出异常,整个事务(含上面的子表删除)都会回滚
return joinPoint.proceed();
}
/**
* 辅助方法:解决 MyBatis-Plus 泛型注入问题
*/
@SuppressWarnings("unchecked")
private <T> void executeDelete(Object mapper, String column, Object value) {
if (mapper instanceof BaseMapper) {
BaseMapper<T> baseMapper = (BaseMapper<T>) mapper;
QueryWrapper<T> wrapper = new QueryWrapper<>();
wrapper.eq(column, value);
baseMapper.delete(wrapper);
}
}
}
// 使用方法如下
//@Override
//@Transactional(rollbackFor = Exception.class)
//@CascadeDelete(sons = {
// // 配置子表1:Mapper类 + 子表中关联主表的字段名
// @SonTable(mapper = TestSonTableMapper.class, joinColumn = "main_table_id"),
//})
//public void delMain(String id) {
// testMainTableMapper.deleteById(id);
//}
@@ -0,0 +1,11 @@
package org.jeecg.common.aspect.annotation;
import java.lang.annotation.*;
@Target(ElementType.METHOD) // 作用在方法上
@Retention(RetentionPolicy.RUNTIME) // 运行时可见
@Documented
public @interface CascadeDelete {
// 子表配置数组,支持一个主表对应多个子表
SonTable[] sons();
}
@@ -0,0 +1,16 @@
package org.jeecg.common.aspect.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.ANNOTATION_TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface SonTable {
// 子表 Mapper 的类对象
Class<?> mapper();
// 子表中关联主表 ID 的字段名(数据库字段名,如 main_id)
String joinColumn();
}
@@ -138,4 +138,13 @@ public class LoginUser {
/**设备id uniapp推送用*/
private String clientId;
/**人员密级*/
private Integer userSecurityLevel;
/**
* 人员密级
*/
private Integer isSpecial;
}
@@ -8,8 +8,13 @@ import org.jeecg.common.util.filter.SsrfFileTypeFilter;
import org.jeecg.common.util.filter.StrAttackFilter;
import org.springframework.web.multipart.MultipartFile;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URLDecoder;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
/**
* minio文件上传工具类
@@ -221,4 +226,315 @@ public class MinioUtil {
return minioUrl+bucketName+"/"+relativePath;
}
/**
* 文件加密上传
*
* @param file
* @param bizPath
* @return
*/
public static String encryptUpload(MultipartFile file, String bizPath) {
return encryptUpload(file, bizPath, null);
}
/**
* 加密上传文件(带年份,文件名)
*
* @param file
* @return
*/
public static String encryptUpload(File file, String bizPath, String customBucket, String year, String fileName) {
String file_url = "";
//update-begin-author:wangshuai date:20201012 for: 过滤上传文件夹名特殊字符,防止攻击
bizPath = StrAttackFilter.filter(bizPath);
// fileName = StrAttackFilter.filter(fileName);
//update-end-author:wangshuai date:20201012 for: 过滤上传文件夹名特殊字符,防止攻击
String newBucket = bucketName;
if (oConvertUtils.isNotEmpty(customBucket)) {
newBucket = customBucket;
}
try {
initMinio(minioUrl, minioName, minioPass);
// 检查存储桶是否已经存在
if (minioClient.bucketExists(BucketExistsArgs.builder().bucket(newBucket).build())) {
log.info("Bucket already exists.");
} else {
// 创建一个名为ota的存储桶
minioClient.makeBucket(MakeBucketArgs.builder().bucket(newBucket).build());
log.info("create a new bucket.");
}
//update-begin-author:liusq date:20210809 for: 过滤上传文件类型
//FileTypeFilter.fileTypeFilter(file);
//SecretKey key = AESUtil.getKeyFromString(AES_KEY);
MultipartFile multipartFile = (MultipartFile) file;
byte[] fileBytes = multipartFile.getBytes();
fileBytes = SecureUtils.encrypt(fileBytes);
InputStream stream = new ByteArrayInputStream(fileBytes);
/*
原公文系统的加密
InputStream inputStream = new FileInputStream(file);
InputStream stream = inputStreamEncrypt(inputStream);
*/
//update-end-author:liusq date:20210809 for: 过滤上传文件类型
//InputStream stream = file.getInputStream();
// 获取文件名
String orgName = fileName;
if ("".equals(orgName)) {
orgName = file.getName();
}
orgName = CommonUtils.getFileName(orgName);
String objectName = bizPath + "/"
+ (orgName.indexOf(".") == -1
? orgName + "_" + System.currentTimeMillis()
: orgName.substring(0, orgName.lastIndexOf(".")) + "_" + System.currentTimeMillis() + orgName.substring(orgName.lastIndexOf("."))
);
// 使用putObject上传一个本地文件到存储桶中。
if (objectName.startsWith("/")) {
objectName = objectName.substring(1);
}
PutObjectArgs objectArgs = PutObjectArgs.builder().object(objectName)
.bucket(newBucket)
.contentType("application/octet-stream")
.stream(stream, stream.available(), -1).build();
minioClient.putObject(objectArgs);
stream.close();
file_url = "/" + objectName;
} catch (Exception e) {
log.error(e.getMessage(), e);
}
return file_url;
}
/**
* 加密上传文件(带年份)
*
* @param file
* @return
*/
public static String encryptUpload(File file, String bizPath, String customBucket, String year) {
String file_url = "";
//update-begin-author:wangshuai date:20201012 for: 过滤上传文件夹名特殊字符,防止攻击
bizPath = StrAttackFilter.filter(bizPath);
//update-end-author:wangshuai date:20201012 for: 过滤上传文件夹名特殊字符,防止攻击
String newBucket = bucketName;
if (oConvertUtils.isNotEmpty(customBucket)) {
newBucket = customBucket;
}
try {
initMinio(minioUrl, minioName, minioPass);
// 检查存储桶是否已经存在
if (minioClient.bucketExists(BucketExistsArgs.builder().bucket(newBucket).build())) {
log.info("Bucket already exists.");
} else {
// 创建一个名为ota的存储桶
minioClient.makeBucket(MakeBucketArgs.builder().bucket(newBucket).build());
log.info("create a new bucket.");
}
//update-begin-author:liusq date:20210809 for: 过滤上传文件类型
//FileTypeFilter.fileTypeFilter(file);
//SecretKey key = AESUtil.getKeyFromString(AES_KEY);
MultipartFile multipartFile = (MultipartFile) file;
byte[] fileBytes = multipartFile.getBytes();
fileBytes = SecureUtils.encrypt(fileBytes);
InputStream stream = new ByteArrayInputStream(fileBytes);
/*
原公文系统的加密
InputStream inputStream = new FileInputStream(file);
InputStream stream = inputStreamEncrypt(inputStream);
*/
//update-end-author:liusq date:20210809 for: 过滤上传文件类型
//InputStream stream = file.getInputStream();
// 获取文件名
String orgName = file.getName();
if ("".equals(orgName)) {
orgName = file.getName();
}
orgName = CommonUtils.getFileName(orgName);
String objectName = bizPath + "/"
+ (orgName.indexOf(".") == -1
? orgName + "_" + System.currentTimeMillis()
: orgName.substring(0, orgName.lastIndexOf(".")) + "_" + System.currentTimeMillis() + orgName.substring(orgName.lastIndexOf("."))
);
// 使用putObject上传一个本地文件到存储桶中。
if (objectName.startsWith("/")) {
objectName = objectName.substring(1);
}
PutObjectArgs objectArgs = PutObjectArgs.builder().object(objectName)
.bucket(newBucket)
.contentType("application/octet-stream")
.stream(stream, stream.available(), -1).build();
minioClient.putObject(objectArgs);
stream.close();
file_url = "/" + objectName;
} catch (Exception e) {
log.error(e.getMessage(), e);
}
return file_url;
}
/**
* 加密上传文件
*
* @param file
* @return
*/
public static String encryptUpload(MultipartFile file, String bizPath, String customBucket) {
String file_url = "";
//update-begin-author:wangshuai date:20201012 for: 过滤上传文件夹名特殊字符,防止攻击
bizPath = StrAttackFilter.filter(bizPath);
//update-end-author:wangshuai date:20201012 for: 过滤上传文件夹名特殊字符,防止攻击
String newBucket = bucketName;
if (oConvertUtils.isNotEmpty(customBucket)) {
newBucket = customBucket;
}
try {
initMinio(minioUrl, minioName, minioPass);
// 检查存储桶是否已经存在
if (minioClient.bucketExists(BucketExistsArgs.builder().bucket(newBucket).build())) {
log.info("Bucket already exists.");
} else {
// 创建一个名为ota的存储桶
minioClient.makeBucket(MakeBucketArgs.builder().bucket(newBucket).build());
log.info("create a new bucket.");
}
//update-begin-author:liusq date:20210809 for: 过滤上传文件类型
SsrfFileTypeFilter.checkUploadFileType(file);
//SecretKey key = AESUtil.getKeyFromString(AES_KEY);
byte[] fileBytes = file.getBytes();
fileBytes = SecureUtils.encrypt(fileBytes);
InputStream stream = new ByteArrayInputStream(fileBytes);
/*
原公文系统的加密
InputStream inputStream = new FileInputStream(file);
InputStream stream = inputStreamEncrypt(inputStream);
*/
//update-end-author:liusq date:20210809 for: 过滤上传文件类型
//InputStream stream = file.getInputStream();
// 获取文件名
String orgName = file.getOriginalFilename();
if ("".equals(orgName)) {
orgName = file.getName();
}
orgName = CommonUtils.getFileName(orgName);
String objectName = bizPath + "/"
+ (orgName.indexOf(".") == -1
? orgName + "_" + System.currentTimeMillis()
: orgName.substring(0, orgName.lastIndexOf(".")) + "_" + System.currentTimeMillis() + orgName.substring(orgName.lastIndexOf("."))
);
// 使用putObject上传一个本地文件到存储桶中。
if (objectName.startsWith("/")) {
objectName = objectName.substring(1);
}
PutObjectArgs objectArgs = PutObjectArgs.builder().object(objectName)
.bucket(newBucket)
.contentType("application/octet-stream")
.stream(stream, stream.available(), -1).build();
minioClient.putObject(objectArgs);
stream.close();
file_url = objectName;
// file_url = minioUrl + newBucket + "/" + objectName;
} catch (Exception e) {
log.error(e.getMessage(), e);
}
return file_url;
}
// /*
//获取文件元数据
// */
// public static String getLastModifiedTime(String objectName) throws ServerException, InsufficientDataException, ErrorResponseException, IOException, NoSuchAlgorithmException, InvalidKeyException, InvalidResponseException, XmlParserException, InternalException {
// initMinio(minioUrl, minioName, minioPass);
// StatObjectResponse statObjectResponse = minioClient.statObject(StatObjectArgs.builder().bucket(bucketName).object(objectName).build());
//
// return String.valueOf(statObjectResponse.lastModified().toInstant().toEpochMilli()) ;
//
// }
// /*
// 判断文件是否存在
// */
// public static String checkFileExistence( String objectName) {
// try {
// initMinio(minioUrl, minioName, minioPass);
//
// // 使用 StatObject 来获取对象的元数据,判断文件是否存在
// StatObjectResponse statObjectResponse = minioClient.statObject(StatObjectArgs.builder().bucket(bucketName).object(objectName).build());
//
// // 如果没有异常抛出,文件存在,返回 1
// return minioUrl + bucketName + "/"+objectName;
// } catch (MinioException e) {
// // 如果文件不存在或发生错误,返回 0
// return "0";
// } catch (Exception e) {
// // 捕获其他异常,返回 0
// return "0";
// }
// }
/**
* 上传并更新文件
*
* @param file
* @return
*/
public static String uploadUpdate(MultipartFile file, String objectName, String customBucket) {
String file_url = "";
//update-begin-author:wangshuai date:20201012 for: 过滤上传文件夹名特殊字符,防止攻击
// objectName = StrAttackFilter.filter(objectName);
//update-end-author:wangshuai date:20201012 for: 过滤上传文件夹名特殊字符,防止攻击
String newBucket = bucketName;
if (oConvertUtils.isNotEmpty(customBucket)) {
newBucket = customBucket;
}
try {
initMinio(minioUrl, minioName, minioPass);
// 检查存储桶是否已经存在
if (minioClient.bucketExists(BucketExistsArgs.builder().bucket(newBucket).build())) {
log.info("Bucket already exists.");
} else {
// 创建一个名为ota的存储桶
minioClient.makeBucket(MakeBucketArgs.builder().bucket(newBucket).build());
log.info("create a new bucket.");
}
//update-begin-author:liusq date:20210809 for: 过滤上传文件类型
SsrfFileTypeFilter.checkUploadFileType(file);
//update-end-author:liusq date:20210809 for: 过滤上传文件类型
// 以下为管理信息化系统加密
byte[] fileBytes = file.getBytes();
fileBytes = SecureUtils.encrypt(fileBytes);
InputStream stream = new ByteArrayInputStream(fileBytes);
// 使用putObject上传一个本地文件到存储桶中。
if (objectName.startsWith("/")) {
objectName = objectName.substring(1);
}
PutObjectArgs objectArgs = PutObjectArgs.builder().object(objectName)
.bucket(newBucket)
.contentType("application/octet-stream")
.stream(stream, stream.available(), -1).build();
minioClient.putObject(objectArgs);
stream.close();
file_url = objectName;
// file_url = minioUrl + newBucket + "/" + objectName;
} catch (Exception e) {
log.error(e.getMessage(), e);
}
return file_url;
}
}
@@ -0,0 +1,44 @@
package org.jeecg.common.util;
import cn.hutool.crypto.SecureUtil;
/*
* @ClassName SecureUtils
* @Description 功能说明: 对称加密工具类 aes、des对对称加密 ras非对称加密
* 此为凌安公司在管理信息化系统中使用的加解密
* 注意:密钥不要修改
*/
public class SecureUtils {
/**
* 密钥
*/
private final static byte[] KEY = {-40, -1, 45, -41, -51, -34, -33, 56, -22, 88, -46, 113, 110, -61, -82, -84};
/**
* 解密文件内容
*
* @param fileContent 已加密的文件内容byte数组
* @return 解密的文件内容byte数组
*/
public static byte[] decrypt(byte[] fileContent) {
if (fileContent == null) {
throw new RuntimeException("需要解密的文件内容不能为空");
}
return SecureUtil.aes(SecureUtils.KEY).decrypt(fileContent);
}
/**
* 加密文件内容
*
* @param fileContent 的文件内容byte数组
* @return 解密的文件内容byte数组
*/
public static byte[] encrypt(byte[] fileContent) {
if (fileContent == null) {
throw new RuntimeException("需要加密的文件内容不能为空");
}
return SecureUtil.aes(SecureUtils.KEY).encrypt(fileContent);
}
}