将开源版本的修改打补丁应用到商业版
This commit is contained in:
@@ -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);
|
||||
//}
|
||||
+11
@@ -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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user