build: db interceptor

This commit is contained in:
CaptainB 2023-04-17 16:27:30 +08:00
parent 25a2a3b601
commit d74cae4ce8
7 changed files with 794 additions and 0 deletions

View File

@ -0,0 +1,33 @@
package io.metersphere.config;
import io.metersphere.sdk.interceptor.MybatisInterceptor;
import io.metersphere.sdk.util.CompressUtils;
import io.metersphere.sdk.util.MybatisInterceptorConfig;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import java.util.ArrayList;
import java.util.List;
@Configuration
@MapperScan(basePackages = {"io.metersphere.*.mapper"}, sqlSessionFactoryRef = "sqlSessionFactory")
@EnableTransactionManagement
public class MybatisConfig {
@Bean
@ConditionalOnMissingBean
public MybatisInterceptor dbInterceptor() {
MybatisInterceptor interceptor = new MybatisInterceptor();
List<MybatisInterceptorConfig> configList = new ArrayList<>();
// configList.add(new MybatisInterceptorConfig(FileContent.class, "file", CompressUtils.class, "zip", "unZip"));
// configList.add(new MybatisInterceptorConfig(ApiTestReportDetail.class, "content", CompressUtils.class, "gzip", "unGzip"));
// configList.add(new MybatisInterceptorConfig(TestResource.class, "configuration"));
// configList.add(new MybatisInterceptorConfig(AuthSource.class, "configuration"));
// configList.add(new MybatisInterceptorConfig(LoadTestReportLog.class, "content", CompressUtils.class, "zipString", "unZipString"));
interceptor.setInterceptorConfigList(configList);
return interceptor;
}
}

View File

@ -0,0 +1,204 @@
package io.metersphere.sdk.interceptor;
import io.metersphere.sdk.util.BeanUtils;
import io.metersphere.sdk.util.MybatisInterceptorConfig;
import org.apache.commons.collections.MapUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.ibatis.cache.CacheKey;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import java.lang.reflect.Method;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
@Intercepts({
@Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class}),
@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}),
@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class}),
})
public class MybatisInterceptor implements Interceptor {
private List<MybatisInterceptorConfig> interceptorConfigList;
private ConcurrentHashMap<String, Class> classMap = new ConcurrentHashMap<>();
private ConcurrentHashMap<String, Map<String, Map<String, MybatisInterceptorConfig>>> interceptorConfigMap = new ConcurrentHashMap<>();
@Override
public Object intercept(Invocation invocation) throws Throwable {
String methodName = invocation.getMethod().getName();
Object parameter = invocation.getArgs()[1];
if (parameter != null && methodName.equals("update")) {
invocation.getArgs()[1] = process(parameter);
}
Object returnValue = invocation.proceed();
Object result = returnValue;
if (returnValue instanceof ArrayList<?>) {
List<Object> list = new ArrayList<>();
boolean isDecrypted = false;
for (Object val : (ArrayList<?>) returnValue) {
Object a = undo(val);
if (a != val) {
isDecrypted = true;
list.add(a);
} else {
break;
}
}
if (isDecrypted) {
result = list;
}
} else {
result = undo(returnValue);
}
return result;
}
private Map<String, Map<String, MybatisInterceptorConfig>> getConfig(Object p) {
Map<String, Map<String, MybatisInterceptorConfig>> result = new HashMap<>();
if (p == null) {
return null;
}
String pClassName = p.getClass().getName();
if (interceptorConfigMap.get(pClassName) != null) {
return interceptorConfigMap.get(pClassName);
}
Map<String, List<MybatisInterceptorConfig>> m = new HashMap<>();
for (MybatisInterceptorConfig interceptorConfig : interceptorConfigList) {
String className = interceptorConfig.getModelName();
String attrName = interceptorConfig.getAttrName();
if (StringUtils.isNotBlank(className)) {
Class c = classMap.get(className);
if (c == null) {
try {
c = Class.forName(className);
classMap.put(className, c);
} catch (ClassNotFoundException e) {
continue;
}
}
if (c.isInstance(p)) {
if (result.get(attrName) == null) {
result.put(attrName, new HashMap<>());
}
if (StringUtils.isNotBlank(interceptorConfig.getInterceptorMethod())) {
result.get(attrName).put(Methods.encrypt.name(), interceptorConfig);
}
if (StringUtils.isNotBlank(interceptorConfig.getInterceptorMethod())) {
result.get(attrName).put(Methods.decrypt.name(), interceptorConfig);
}
}
}
}
interceptorConfigMap.put(pClassName, result);
return result;
}
private Object process(Object obj) throws Throwable {
if (obj instanceof Map) {
Map paramMap = (Map) obj;
for (Object key : paramMap.keySet()) {
if (paramMap.get(key) != null) {
paramMap.put(key, process(paramMap.get(key)));
}
}
return paramMap;
}
Map<String, Map<String, MybatisInterceptorConfig>> localInterceptorConfigMap = getConfig(obj);
if (MapUtils.isEmpty(localInterceptorConfigMap)) {
return obj;
}
Object newObject = obj.getClass().newInstance();
BeanUtils.copyBean(newObject, obj);
for (String attrName : localInterceptorConfigMap.keySet()) {
if (MapUtils.isEmpty(localInterceptorConfigMap.get(attrName))) {
continue;
}
MybatisInterceptorConfig interceptorConfig = localInterceptorConfigMap.get(attrName).get(Methods.encrypt.name());
if (interceptorConfig == null || StringUtils.isBlank(interceptorConfig.getInterceptorClass())
|| StringUtils.isBlank(interceptorConfig.getInterceptorMethod())) {
continue;
}
Object fieldValue = BeanUtils.getFieldValueByName(interceptorConfig.getAttrName(), newObject);
if (fieldValue != null) {
Class<?> processClazz = Class.forName(interceptorConfig.getInterceptorClass());
Method method = processClazz.getMethod(interceptorConfig.getInterceptorMethod(), Object.class);
Object processedValue = method.invoke(null, fieldValue);
if (processedValue instanceof byte[]) {
BeanUtils.setFieldValueByName(newObject, interceptorConfig.getAttrName(), processedValue, byte[].class);
} else {
BeanUtils.setFieldValueByName(newObject, interceptorConfig.getAttrName(), processedValue, fieldValue.getClass());
}
}
}
return newObject;
}
private Object undo(Object obj) throws Throwable {
Map<String, Map<String, MybatisInterceptorConfig>> localDecryptConfigMap = getConfig(obj);
Object result;
if (MapUtils.isEmpty(localDecryptConfigMap)) {
return obj;
}
result = obj.getClass().newInstance();
BeanUtils.copyBean(result, obj);
for (String attrName : localDecryptConfigMap.keySet()) {
if (MapUtils.isEmpty(localDecryptConfigMap.get(attrName))) {
continue;
}
MybatisInterceptorConfig interceptorConfig = localDecryptConfigMap.get(attrName).get(Methods.decrypt.name());
if (interceptorConfig == null || StringUtils.isBlank(interceptorConfig.getUndoClass())
|| StringUtils.isBlank(interceptorConfig.getUndoMethod())) {
continue;
}
Object fieldValue = BeanUtils.getFieldValueByName(interceptorConfig.getAttrName(), result);
if (fieldValue != null) {
Class<?> processClazz = Class.forName(interceptorConfig.getUndoClass());
Object undoValue;
if (fieldValue instanceof List) {
Method method = processClazz.getMethod(interceptorConfig.getUndoMethod(), List.class, String.class);
//fieldValue获取的是list的引用所以list类型的属性不需要再调用setFieldValueByName了
method.invoke(null, fieldValue, interceptorConfig.getAttrNameForList());
} else {
Method method = processClazz.getMethod(interceptorConfig.getUndoMethod(), Object.class);
undoValue = method.invoke(null, fieldValue);
if (undoValue instanceof byte[]) {
BeanUtils.setFieldValueByName(result, interceptorConfig.getAttrName(), undoValue, byte[].class);
} else {
BeanUtils.setFieldValueByName(result, interceptorConfig.getAttrName(), undoValue, fieldValue.getClass());
}
}
}
}
return result;
}
@Override
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
@Override
public void setProperties(Properties properties) {
// TODO Auto-generated method stub
}
public List<MybatisInterceptorConfig> getInterceptorConfigList() {
return interceptorConfigList;
}
public void setInterceptorConfigList(List<MybatisInterceptorConfig> interceptorConfigList) {
this.interceptorConfigList = interceptorConfigList;
}
private enum Methods {
encrypt, decrypt
}
}

View File

@ -0,0 +1,66 @@
package io.metersphere.sdk.util;
import org.apache.commons.lang3.StringUtils;
import java.lang.reflect.Method;
public class BeanUtils {
public static <T> T copyBean(T target, Object source) {
try {
org.springframework.beans.BeanUtils.copyProperties(source, target);
return target;
} catch (Exception e) {
throw new RuntimeException("Failed to copy object: ", e);
}
}
public static <T> T copyBean(T target, Object source, String... ignoreProperties) {
try {
org.springframework.beans.BeanUtils.copyProperties(source, target, ignoreProperties);
return target;
} catch (Exception e) {
throw new RuntimeException("Failed to copy object: ", e);
}
}
public static Object getFieldValueByName(String fieldName, Object bean) {
try {
if (StringUtils.isBlank(fieldName)) {
return null;
}
String firstLetter = fieldName.substring(0, 1).toUpperCase();
String getter = "get" + firstLetter + fieldName.substring(1);
Method method = bean.getClass().getMethod(getter);
return method.invoke(bean);
} catch (Exception e) {
return null;
}
}
public static void setFieldValueByName(Object bean, String fieldName, Object value, Class<?> type) {
try {
if (StringUtils.isBlank(fieldName)) {
return;
}
String firstLetter = fieldName.substring(0, 1).toUpperCase();
String setter = "set" + firstLetter + fieldName.substring(1);
Method method = bean.getClass().getMethod(setter, type);
method.invoke(bean, value);
} catch (Exception ignore) {
}
}
public static Method getMethod(Object bean, String fieldName, Class<?> type) {
try {
if (StringUtils.isBlank(fieldName)) {
return null;
}
String firstLetter = fieldName.substring(0, 1).toUpperCase();
String setter = "set" + firstLetter + fieldName.substring(1);
return bean.getClass().getMethod(setter, type);
} catch (Exception e) {
return null;
}
}
}

View File

@ -0,0 +1,167 @@
package io.metersphere.sdk.util;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.lang3.StringUtils;
import javax.crypto.*;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.security.MessageDigest;
/**
* 加密解密工具
*
* @author kun.mo
*/
public class CodingUtil {
private static final String UTF_8 = "UTF-8";
private static final char[] HEX_DIGITS = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
/**
* MD5加密
*
* @param src 要加密的串
* @return 加密后的字符串
*/
public static String md5(String src) {
return md5(src, UTF_8);
}
/**
* MD5加密
*
* @param src 要加密的串
* @param charset 加密字符集
* @return 加密后的字符串
*/
public static String md5(String src, String charset) {
try {
byte[] strTemp = StringUtils.isEmpty(charset) ? src.getBytes() : src.getBytes(charset);
MessageDigest mdTemp = MessageDigest.getInstance("MD5");
mdTemp.update(strTemp);
byte[] md = mdTemp.digest();
int j = md.length;
char[] str = new char[j * 2];
int k = 0;
for (byte byte0 : md) {
str[k++] = HEX_DIGITS[byte0 >>> 4 & 0xf];
str[k++] = HEX_DIGITS[byte0 & 0xf];
}
return new String(str);
} catch (Exception e) {
throw new RuntimeException("MD5 encrypt error:", e);
}
}
/**
* BASE64解密
*
* @param src 待解密的字符串
* @return 解密后的字符串
*/
public static String base64Decoding(String src) {
byte[] b;
String result = null;
if (src != null) {
try {
b = Base64.decodeBase64(src);
result = new String(b, UTF_8);
} catch (Exception e) {
throw new RuntimeException("BASE64 decoding error:", e);
}
}
return result;
}
/**
* BASE64加密
*
* @param src 待加密的字符串
* @return 加密后的字符串
*/
public static String base64Encoding(String src) {
String result = null;
if (src != null) {
try {
result = Base64.encodeBase64String(src.getBytes(UTF_8));
} catch (Exception e) {
throw new RuntimeException("BASE64 encoding error:", e);
}
}
return result;
}
/**
* AES加密
*
* @param src 待加密字符串
* @param secretKey 密钥
* @param iv 向量
* @return 加密后字符串
*/
public static String aesEncrypt(String src, String secretKey, String iv) {
if (StringUtils.isBlank(secretKey)) {
throw new RuntimeException("secretKey is empty");
}
try {
byte[] raw = secretKey.getBytes(UTF_8);
SecretKeySpec secretKeySpec = new SecretKeySpec(raw, "AES");
// "算法/模式/补码方式" ECB
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
IvParameterSpec iv1 = new IvParameterSpec(iv.getBytes());
cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, iv1);
byte[] encrypted = cipher.doFinal(src.getBytes(UTF_8));
return Base64.encodeBase64String(encrypted);
} catch (Exception e) {
throw new RuntimeException("AES encrypt error:", e);
}
}
/**
* AES 解密
*
* @param src 待解密字符串
* @param secretKey 密钥
* @param iv 向量
* @return 解密后字符串
*/
public static String aesDecrypt(String src, String secretKey, String iv) {
if (StringUtils.isBlank(secretKey)) {
throw new RuntimeException("secretKey is empty");
}
try {
byte[] raw = secretKey.getBytes(UTF_8);
SecretKeySpec secretKeySpec = new SecretKeySpec(raw, "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
IvParameterSpec iv1 = new IvParameterSpec(iv.getBytes());
cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, iv1);
byte[] encrypted1 = Base64.decodeBase64(src);
byte[] original = cipher.doFinal(encrypted1);
return new String(original, UTF_8);
} catch (BadPaddingException | IllegalBlockSizeException e) {
// 解密的原字符串为非加密字符串则直接返回原字符串
return src;
} catch (Exception e) {
throw new RuntimeException("decrypt errorplease check parameters", e);
}
}
public static String secretKey() {
try {
KeyGenerator keyGen = KeyGenerator.getInstance("AES");
keyGen.init(128);
SecretKey secretKey = keyGen.generateKey();
return Base64.encodeBase64String(secretKey.getEncoded());
} catch (Exception e) {
throw new RuntimeException("generate secretKey error", e);
}
}
}

View File

@ -0,0 +1,237 @@
package io.metersphere.sdk.util;
import org.apache.commons.codec.binary.Base64;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.zip.*;
public class CompressUtils {
private final static String ZIP_PATH = "/opt/metersphere/data/tmp/";
/***
* Zip压缩
*
* @param data 待压缩数据
* @return 压缩后数据
*/
public static Object zip(Object data) {
if (!(data instanceof byte[])) {
return data;
}
byte[] temp = (byte[]) data;
byte[] b = (byte[]) data;
try {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ZipOutputStream zip = new ZipOutputStream(bos);
ZipEntry entry = new ZipEntry("zip");
entry.setSize(temp.length);
zip.putNextEntry(entry);
zip.write(temp);
zip.closeEntry();
zip.close();
b = bos.toByteArray();
bos.close();
} catch (Exception ignore) {
}
return b;
}
private static File getFile(String fileName) throws IOException {
// 创建文件对象
File file;
if (ZIP_PATH != null && !ZIP_PATH.equals("")) {
file = new File(ZIP_PATH, fileName);
} else {
file = new File(fileName);
}
if (!file.exists()) {
file.createNewFile();
}
// 返回文件
return file;
}
public static FileOutputStream getFileStream(File file) throws FileNotFoundException {
return new FileOutputStream(file);
}
private static void zipFile(File file, ZipOutputStream zipOutputStream) throws IOException {
if (file.exists()) {
if (file.isFile()) {
FileInputStream fis = new FileInputStream(file);
BufferedInputStream bis = new BufferedInputStream(fis);
ZipEntry entry = new ZipEntry(file.getName());
zipOutputStream.putNextEntry(entry);
final int MAX_BYTE = 10 * 1024 * 1024; // 最大流为10MB
long streamTotal = 0; // 接收流的容量
int streamNum = 0; // 需要分开的流数目
int leaveByte = 0; // 文件剩下的字符数
byte[] buffer; // byte数据接受文件的数据
streamTotal = bis.available(); // 获取流的最大字符数
streamNum = (int) Math.floor(streamTotal / MAX_BYTE);
leaveByte = (int) (streamTotal % MAX_BYTE);
if (streamNum > 0) {
for (int i = 0; i < streamNum; i++) {
buffer = new byte[MAX_BYTE];
bis.read(buffer, 0, MAX_BYTE);
zipOutputStream.write(buffer, 0, MAX_BYTE);
}
}
// 写入剩下的流数据
buffer = new byte[leaveByte];
bis.read(buffer, 0, leaveByte); // 读入流
zipOutputStream.write(buffer, 0, leaveByte); // 写入流
zipOutputStream.closeEntry(); // 关闭当前的zip entry
// 关闭输入流
bis.close();
fis.close();
}
}
}
/**
* 将多个文件压缩
*
* @param fileList 待压缩的文件列表
* @param zipFileName 压缩文件名
* @return 返回压缩好的文件
* @throws IOException
*/
public static File zipFiles(String zipFileName, List<File> fileList) throws IOException {
File zipFile = getFile(zipFileName);
// 文件输出流
FileOutputStream outputStream = getFileStream(zipFile);
// 压缩流
ZipOutputStream zipOutputStream = new ZipOutputStream(outputStream);
int size = fileList.size();
// 压缩列表中的文件
for (int i = 0; i < size; i++) {
File file = fileList.get(i);
zipFile(file, zipOutputStream);
}
// 关闭压缩流文件流
zipOutputStream.close();
outputStream.close();
return zipFile;
}
/***
* Zip解压
*
* @param data 待解压数据
* @return 解压后数据
*/
public static Object unZip(Object data) {
if (!(data instanceof byte[])) {
return data;
}
byte[] temp = (byte[]) data;
byte[] b = (byte[]) data;
try {
ByteArrayInputStream bis = new ByteArrayInputStream(temp);
ZipInputStream zip = new ZipInputStream(bis);
while (zip.getNextEntry() != null) {
byte[] buf = new byte[1024];
int num;
ByteArrayOutputStream baos = new ByteArrayOutputStream();
while ((num = zip.read(buf, 0, buf.length)) != -1) {
baos.write(buf, 0, num);
}
b = baos.toByteArray();
baos.flush();
baos.close();
}
zip.close();
bis.close();
} catch (Exception ignore) {
}
return b;
}
/**
* GZip压缩
*
* @param data 待压缩数据
* @return 压缩后数
*/
public static Object gzip(Object data) {
if (!(data instanceof byte[])) {
return data;
}
byte[] bytes = (byte[]) data;
try (ByteArrayOutputStream obj = new ByteArrayOutputStream(); GZIPOutputStream gzip = new GZIPOutputStream(obj)) {
gzip.write(bytes);
gzip.flush();
gzip.finish();
return obj.toByteArray();
} catch (Exception e) {
return data;
}
}
/**
* GZip解压
*
* @param data 待解压数据
* @return 解压后数据
*/
public static Object unGzip(Object data) {
if (!(data instanceof byte[])) {
return data;
}
byte[] bytes = (byte[]) data;
if (bytes.length == 0) {
return bytes;
}
try (GZIPInputStream gis = new GZIPInputStream(new ByteArrayInputStream(bytes)); ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
byte[] buffer = new byte[8192];
int len;
while ((len = gis.read(buffer)) > 0) {
baos.write(buffer, 0, len);
}
baos.flush();
return baos.toByteArray();
} catch (Exception e) {
return data;
}
}
public static Object zipString(Object data) {
if (!(data instanceof String)) {
return data;
}
try (ByteArrayOutputStream out = new ByteArrayOutputStream()) {
try (DeflaterOutputStream deflaterOutputStream = new DeflaterOutputStream(out)) {
deflaterOutputStream.write(((String) data).getBytes(StandardCharsets.UTF_8));
}
return Base64.encodeBase64String(out.toByteArray());
} catch (Exception e) {
return data;
}
}
public static Object unZipString(Object data) {
if (!(data instanceof String)) {
return data;
}
try (ByteArrayOutputStream os = new ByteArrayOutputStream()) {
try (OutputStream outputStream = new InflaterOutputStream(os)) {
outputStream.write(Base64.decodeBase64((String) data));
}
return os.toString(StandardCharsets.UTF_8);
} catch (Exception e) {
return data;
}
}
}

View File

@ -0,0 +1,42 @@
package io.metersphere.sdk.util;
import java.util.List;
import java.util.stream.Collectors;
public class EncryptUtils extends CodingUtil {
private static final String secretKey = "www.fit2cloud.co";
private static final String iv = "1234567890123456";
public static Object aesEncrypt(Object o) {
if (o == null) {
return null;
}
return aesEncrypt(o.toString(), secretKey, iv);
}
public static Object aesDecrypt(Object o) {
if (o == null) {
return null;
}
return aesDecrypt(o.toString(), secretKey, iv);
}
public static <T> Object aesDecrypt(List<T> o, String attrName) {
if (o == null) {
return null;
}
return o.stream()
.filter(element -> BeanUtils.getFieldValueByName(attrName, element) != null)
.peek(element -> BeanUtils.setFieldValueByName(element, attrName, aesDecrypt(BeanUtils.getFieldValueByName(attrName, element).toString(), secretKey, iv), String.class))
.collect(Collectors.toList());
}
public static Object md5Encrypt(Object o) {
if (o == null) {
return null;
}
return md5(o.toString());
}
}

View File

@ -0,0 +1,45 @@
package io.metersphere.sdk.util;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
public class MybatisInterceptorConfig {
private String modelName;
private String attrName;
private String attrNameForList;
private String interceptorClass;
private String interceptorMethod;
private String undoClass;
private String undoMethod;
public MybatisInterceptorConfig() {
}
/**
* 用时需谨慎
* 主要配置多个的时候参数少一点
*
* @param modelClass
* @param attrName
*/
public MybatisInterceptorConfig(Class<?> modelClass, String attrName) {
this.modelName = modelClass.getName();
this.attrName = attrName;
this.interceptorClass = EncryptUtils.class.getName();
this.interceptorMethod = "aesEncrypt";
this.undoClass = EncryptUtils.class.getName();
this.undoMethod = "aesDecrypt";
}
public MybatisInterceptorConfig(Class<?> modelClass, String attrName, Class<?> interceptorClass, String interceptorMethod, String undoMethod) {
this.modelName = modelClass.getName();
this.attrName = attrName;
this.interceptorClass = interceptorClass.getName();
this.interceptorMethod = interceptorMethod;
this.undoClass = interceptorClass.getName();
this.undoMethod = undoMethod;
}
}