该工具能实现任何实体类的动态生成传入参数名称自动生成get/set方法, 供反射调用.
该方法生成的实体类是在程序运行过程动态生成加载出来的,实际代码文件并不存在.所以我暂定他为虚拟实体类生成工具
本方法我自己暂时用在mybatis中当统一的传参实体,只要前端请求传回的参数有效则就可以将前端传
回的参数封装到这个虚拟的实体类Ling里面去,然后mybatis xml文件中直接#{ling.xxx}就能获取到前
端传回的参数了.我在应用中将sql语句存放在数据库,发现xml传入字符串形式的sql语句中包含
#{ling.xxx}也能被解析,所以这个方法在设计通用执行sql语句的业务中起到方便传参取值的关键作用.
最终SQL示例
此sql存在数据库,然后同步更新在redis中
SELECT *,#{ling.name} AS name,
concat_ws('***',#{ling.sysLabelArray[0]}, #{ling.sysLabelArray[1]},#{ling.sysLabelArray[2]}) AS 中文哈哈哈哈〇°❀
FROM `sys_communication`
<where>
<if test = "name!=null and !name.eq('')">
and 1=1
</if>
and id not in
<foreach item="item" index="index" open="(" separator="," close=")" collection="sysLabelArray">
#{item}
</foreach>
</where>
特别强调:
在实际使用过程中要考虑类加载的生命周期,防止无用类的无限创建浪费资源!
虚拟类生成工具(核心代码)
package com.ling.common.core.lingdu;
import javassist.*;
import javassist.bytecode.DuplicateMemberException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.util.*;
/**
* 虚拟实体构建类
* <br><br><b>作者: 990130556 <a class=b href="https://blog.csdn.net/lingdu_dou">lingdu</a></b><br>
* 创建时间: 2021年11月23日 16:19:48
* @author 99013
*/
public class Lingdu {
private static final Logger log = LoggerFactory.getLogger(Lingdu.class);
/**
* 虚拟实体类编译路径(初始化路径,是从类池里面获取的)
*/
private final String className = "com.lingdu.dimensiondoor.utils.Ling"+UUID.randomUUID();
/**
* 目标类
*/
private Class<?> clazz;
public Class<?> getClazz(){
return clazz;
}
/**
* 可编辑目标类
*/
private CtClass cc;
/**
* 入参信息<参数名,参数值>
* <br><br><b>作者: 990130556 <a class=b href="https://blog.csdn.net/lingdu_dou">lingdu</a></b><br>
* 创建时间: 2022年03月16日 17:52:00
*/
Map<String, Object> param;
public Map<String, Object> getParam() {
return param;
}
public void setParam(Map<String, Object> param){
this.param = param;
}
public Lingdu(){
}
public Lingdu(List<String> names){
if(names!=null){
param=new HashMap<>((int)(names.size()/0.75+1));
for (String name : names) {
param.put(name,null);
}
}
}
public Lingdu(Map<String, Object> param){
this.param = param;
}
/**
* 虚拟实体封装
* @return 返回封装好的实体集合
* <br><br><b>作者: 990130556 <a class=b href="https://blog.csdn.net/lingdu_dou">lingdu</a></b><br>
* 创建时间: 2022年02月18日 09:52:23
*/
public Object sviVirtualEntityEncapsulation() {
try {
/*初始化虚拟实体*/
initializesVirtualEntity();
/*封装数据*/
return encapsulateData();
} catch (Exception e) {
log.error("初始化虚拟实体/虚拟实体封装出错!",e);
}
return null;
}
/**
* 初始化虚拟实体
* <br><br><b>作者: 990130556 <a class=b href="https://blog.csdn.net/lingdu_dou">lingdu</a></b><br>
* 创建时间: 2022年02月18日 09:37:44
*/
public void initializesVirtualEntity() throws Exception {
// 初始化可编辑目标类
getMyClass();
for (String name : param.keySet()) {
try {
changeClass(name);
// 字段重复异常
} catch (DuplicateMemberException e) {
log.error("字段重复!",e);
}
}
/*重新加载修改后的类*/
reload(cc);
}
/**
* 创建属性及get\set方法
* @param fieldName 属性名
* <br><br><b>作者: 990130556 <a class=b href="https://blog.csdn.net/lingdu_dou">lingdu</a></b><br>
* 创建时间: 2022年02月17日 17:19:52
*/
private void changeClass(String fieldName) throws CannotCompileException, NotFoundException, IOException {
String type = "Object";
//为cTclass对象添加一个属性
cc.addField(CtField.make("private "+type+" " + fieldName + ";", cc));
createMethod("public void set" + fieldName + "("+type+" " + fieldName + "){this." + fieldName + " = " + fieldName + ";}");
createMethod("public "+type+" get" + fieldName + "(){return this." + fieldName + ";}");
}
/**
* 封装数据
* @return 返回封装好的实体集合
* <br><br><b>作者: 990130556 <a class=b href="https://blog.csdn.net/lingdu_dou">lingdu</a></b><br>
* 创建时间: 2022年02月18日 09:38:11
*/
private Object encapsulateData() throws Exception {
Object o = clazz.newInstance();
for (String s : param.keySet()) {
clazz.getDeclaredMethod("set" + s, Object.class).invoke(o, param.get(s));
}
clazz=null;
/*类重置*/
// resetClass();
return o;
}
/**
* 获取自定义虚拟类
* <br><br><b>作者: 990130556 <a class=b href="https://blog.csdn.net/lingdu_dou">lingdu</a></b><br>
* 创建时间: 2022年02月17日 16:50:55
*/
private void getMyClass() throws NotFoundException {
// 类池
/*永远获取子级类池,这样类池就不会因为随着方法的调用越来越多*/
ClassPool pool = MyClassLoader.getClassPool();
try {
// 创建虚拟实体类
cc = pool.makeClass(className);
} catch (Exception e) {
// 已经存在了
cc = pool.get(className);
}
cc.defrost();// 解冻
}
/**
* 重新加载修改后的类
* @param cc 修改后的类
* <br><br><b>作者: 990130556 <a class=b href="https://blog.csdn.net/lingdu_dou">lingdu</a></b><br>
* 创建时间: 2022年02月17日 17:00:00
*/
private void reload(CtClass cc) throws IOException, CannotCompileException {
byte[] bytes = cc.toBytecode();
// 使用自定义的ClassLoader
MyClassLoader cl = new MyClassLoader();
// 加载我们生成的 Ling 类
clazz = cl.defineClass(className, bytes);
}
/*
* 重置类 之前由于虚拟类编译路径相同,考虑到相同实体类字段越用越多的问题所以在创建使用完
* 调用本方法清空所有成员变量以及相应的get/set方法.
* 虚拟类2.0在类编译路径后面加上了UUID唯一标识,就不存在字段越用越多的问题.所以可舍弃本方
* 法清空字段.猜测接口调用过于频繁导致的mybatis底层偶尔获取不到字段信息于此有关,所以该方
* 法被弃用
* <br><br><b>作者: 990130556 <a class=b href="https://blog.csdn.net/lingdu_dou">lingdu</a></b><br>
* 创建时间: 2022年02月17日 16:29:04
*/
// @Deprecated
// public void resetClass() throws Exception {
// // 初始化可编辑目标类
// getMyClass();
// CtMethod[] methods = cc.getDeclaredMethods();
// for (CtMethod method : methods) {
// /*删除本类所有方法*/
// cc.removeMethod(method);
// }
// CtField[] fields = cc.getDeclaredFields();
// for (CtField field : fields) {
// /*删除本类所有变量*/
// cc.removeField(field);
// }
// /*重新加载修改后的类*/
// reload(cc);
// }
/**
* 单独创建方法
* @param m 具体方法结构
* @return java.lang.Class<?>
* <br><br><b>作者: 990130556 <a class=b href="https://blog.csdn.net/lingdu_dou">lingdu</a></b><br>
* 创建时间: 2022年02月17日 10:50:05
*/
public Class<?> createMethod(String m) throws NotFoundException, CannotCompileException, IOException {
boolean b=false;
if(cc==null){
b=true;
// 初始化可编辑目标类
getMyClass();
}
cc.addMethod(CtMethod.make(m, cc));
if(b){
reload(cc);
return clazz;
}else {
return null;
}
}
}
自定义类加载器
package com.ling.common.core.lingdu;
import javassist.ClassPool;
/**
* @author 99013
* @ClassName MyClassLoader
* @Description 自定义类加载器
* 作者:99013
* 创建时间:2022年02月17日 15:36:54
* @Version 1.0
**/
public class MyClassLoader extends ClassLoader {
// 类池
private static ClassPool pool;
/**
* 利用子类池覆盖默认类池,从而防止ClassPool中类越来越多的情况
* @return javassist.ClassPool
* <br><br><b>作者: 990130556 <a class=b href="https://blog.csdn.net/lingdu_dou">lingdu</a></b><br>
* 创建时间: 2022年05月24日 14:33:11
*/
public static ClassPool getClassPool(){
return new ClassPool(getPool());
}
/**
* 初始默认类池
* @return javassist.ClassPool
* <br><br><b>作者: 990130556 <a class=b href="https://blog.csdn.net/lingdu_dou">lingdu</a></b><br>
* 创建时间: 2022年05月24日 14:35:01
*/
private static ClassPool getPool(){
if(pool==null){
pool = ClassPool.getDefault();
}
return pool;
}
public Class<?> defineClass(String name, byte[] b) {
// ClassLoader是个抽象类,而ClassLoader.defineClass 方法是protected的
// 所以我们需要定义一个子类将这个方法暴露出来
return super.defineClass(name, b, 0, b.length);
}
@Override
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
// 根据类的全类名进行加锁操作,也就是保证了线程安全
synchronized (getClassLoadingLock(name)) {
Class<?> klass = findLoadedClass(name);
// 到已经加载的缓存中查看是否已经被加载了如果是则直接返回,如果没有就需要进行加载
if (klass == null) {
// 如果缓存中没有,则表示这个类是第一次被加载,对于类进行判断操作
if (name.startsWith("java.") || name.startsWith("javax.")) {
try {
klass = getSystemClassLoader().loadClass(name);
} catch (Exception e) {
throw e;
}
// 如果不满足要求则表示使用自定义的类加载器进行加载操作。
} else {
try {
klass = this.findClass(name);
} catch (Exception ignored) {
}
// 如果自定义加载器没有完成则需要交给父类加载器去进行加载操作
if (klass == null) {
if (getParent() != null) {
klass = getParent().loadClass(name);
} else {
klass = getSystemClassLoader().loadClass(name);
}
}
}
}
// 如果加载不成功的话就抛出异常。
if (null == klass) {
throw new ClassNotFoundException("The class " + name + " not found.");
}
if (resolve) {
resolveClass(klass);
}
return klass;
}
}
}
SQL门控制器
package com.ling.dimensiondoor.controller;
import com.alibaba.druid.proxy.jdbc.ClobProxy;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import com.ling.common.core.constant.Constants;
import com.ling.common.core.constant.HttpStatus;
import com.ling.common.core.context.SecurityContextHolder;
import com.ling.common.core.domain.R;
import com.ling.common.core.exception.ServiceException;
import com.ling.common.core.lingdu.Lingdu;
import com.ling.common.core.lingdu.domain.SysApiParameter;
import com.ling.common.core.lingdu.domain.SysVirtualInterface;
import com.ling.common.core.lingdu.utils.ByteConvertUtil;
import com.ling.common.core.lingdu.utils.GetRequestJsonUtils;
import com.ling.common.core.lingdu.utils.HttpRequestUtil;
import com.ling.common.core.utils.DateUtils;
import com.ling.common.core.utils.ServletUtils;
import com.ling.common.core.utils.SpringUtils;
import com.ling.common.core.utils.StringUtils;
import com.ling.common.core.web.controller.BaseController;
import com.ling.common.log.annotation.Log;
import com.ling.common.log.enums.BusinessType;
import com.ling.common.log.service.AsyncLogService;
import com.ling.common.redis.service.RedisService;
import com.ling.common.security.annotation.RequiresPermissions;
import com.ling.common.security.annotation.RequiresRoles;
import com.ling.common.security.auth.AuthUtil;
import com.ling.common.security.utils.SecurityUtils;
import com.ling.dimensiondoor.service.ISqlDoorService;
import com.ling.dimensiondoor.utils.BeanInvokeUtil;
import com.ling.dimensiondoor.utils.mybatis.DynamicSqlParser;
import com.ling.system.api.RemoteSysApiRepositoryService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.*;
import java.io.IOException;
import java.io.Reader;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.sql.Clob;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.concurrent.TimeUnit;
/**
*
* *********************************************此模块代码勿随意改动!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
* *********************************************否则后果自负
* sql任意门执行控制器层
* 所有调用本模块的请求理论上都走该控制器
* 执行sql语句
*
* @author 99013
* @ClassName SqlDoorController
* @Description 作者:99013
* 创建时间:2021年06月21日 18:47:16
* @Version 3.0
**/
@RestController
@RequestMapping("/*/sqlDoor")
@Api(value = "任意sql执行接口", tags = "任意sql执行接口")
public class SqlDoorController extends BaseController {
private static final Logger log = LoggerFactory.getLogger(SqlDoorController.class);
/**
* sql门业务层接口
*/
private final ISqlDoorService sqlDoorService;
/**
* 虚拟接口缓存配置
*/
private final RedisService redisService;
/**
* 异步日志
*/
private final AsyncLogService asyncLogService;
/**
* 过期时间 天
*/
public final static long EXPIRE_TIME = 30;
public SqlDoorController(ISqlDoorService sqlDoorService, RedisService redisService, AsyncLogService asyncLogService) {
this.sqlDoorService = sqlDoorService;
this.redisService = redisService;
this.asyncLogService = asyncLogService;
}
/**
* 任意sql门 执行 POST
*
* @param key 虚拟接口唯一标识
* @return com.ling.common.core.domain.R<?>
* <br><br><b>作者: 990130556 <a class=b href="https://blog.csdn.net/lingdu_dou">lingdu</a></b><br>
* 创建时间: 2021年06月21日 19:04:59
*/
@PostMapping("invoke/{virtualInterfaceKey}")
@RequiresRoles("dba")
@ApiOperation(value = "任意sql门", notes = "可执行任意sql")
@Log(title = "sql门执行POST请求 form-data传参", businessType = BusinessType.INSERT)
public R<Object> invokePost(@PathVariable("virtualInterfaceKey") String key) {
return invoke(HttpRequestUtil.POST, key);
}
/**
* feign调用
*/
@PostMapping("invokeFeignPost/{virtualInterfaceKey}")
@RequiresPermissions("dimensiondoor:sqlDoor:invokePostFeign")/*权限字符串防止非法访问*/
@ApiOperation(value = "任意sql门 feign请求 json传参", notes = "可执行任意sql")
@Log(title = "sql门执行POST请求 json传参[feign请求]", businessType = BusinessType.INSERT)
public R<Object> invokePostFeign(@PathVariable("virtualInterfaceKey") String key, @RequestBody(required = false) String json, @RequestParam(value = "type") String type) {
return invoke(true, type, key, json);
}
/**
* json方式传参
*/
@PostMapping("invoke/{virtualInterfaceKey}/json")
@RequiresRoles("dba")
@ApiOperation(value = "任意sql门 json传参", notes = "可执行任意sql")
@Log(title = "sql门执行POST请求 json传参", businessType = BusinessType.INSERT)
public R<Object> invokePostJson(@PathVariable("virtualInterfaceKey") String key, @RequestBody(required = false) String json) {
return invoke(HttpRequestUtil.POST, key, json);
}
/**
* 任意sql门 执行 PUT
*
* @param key 虚拟接口唯一标识
* @return com.ling.common.core.domain.R<?>
* <br><br><b>作者: 990130556 <a class=b href="https://blog.csdn.net/lingdu_dou">lingdu</a></b><br>
* 创建时间: 2021年06月21日 19:04:59
*/
@PutMapping("invoke/{virtualInterfaceKey}")
@RequiresRoles("dba")
@ApiOperation(value = "任意sql门", notes = "可执行任意sql")
@Log(title = "sql门执行PUT请求", businessType = BusinessType.UPDATE)
public R<Object> invokePut(@PathVariable("virtualInterfaceKey") String key) {
return invoke(HttpRequestUtil.PUT, key);
}
// @PutMapping("invokeFeignPut/{virtualInterfaceKey}")
// @RequiresPermissions("dimensiondoor:sqlDoor:invokePutFeign")// 权限字符串防止非法访问
// @ApiOperation(value = "任意sql门 feign调用", notes = "可执行任意sql")
// @Log(title = "sql门执行PUT请求 feign调用", businessType = BusinessType.UPDATE)
// public R<?> invokePutFeign(@PathVariable("virtualInterfaceKey") String key, @RequestBody(required = false) String json) {
// return invoke(HttpRequestUtil.PUT, key, json);
// }
/**
* json方式传参
*/
@PutMapping("invoke/{virtualInterfaceKey}/json")
@RequiresRoles("dba")
@ApiOperation(value = "任意sql门 json传参", notes = "可执行任意sql")
@Log(title = "sql门执行PUT请求 json传参", businessType = BusinessType.UPDATE)
public R<Object> invokePutJson(@PathVariable("virtualInterfaceKey") String key, @RequestBody() String json) {
return invoke(HttpRequestUtil.PUT, key, json);
}
/**
* 任意sql门 执行 GET
*
* @param key 虚拟接口唯一标识
* @return com.ling.common.core.domain.R<?>
* <br><br><b>作者: 990130556 <a class=b href="https://blog.csdn.net/lingdu_dou">lingdu</a></b><br>
* 创建时间: 2021年06月21日 19:04:59
*/
@GetMapping("invoke/{virtualInterfaceKey}")
@RequiresRoles("dba")
@ApiOperation(value = "任意sql门", notes = "可执行任意sql")
@Log(title = "sql门执行GET请求", businessType = BusinessType.SELECT)
public R<Object> invokeGet(@PathVariable("virtualInterfaceKey") String key) {
return invoke(HttpRequestUtil.GET, key);
}
/**
* feign请求任意sql门 执行 GET
*
* @param key 虚拟接口唯一标识
* @return com.ling.common.core.domain.R<?>
* <br><br><b>作者: 990130556 <a class=b href="https://blog.csdn.net/lingdu_dou">lingdu</a></b><br>
* 创建时间: 2021年06月21日 19:04:59
*/
@GetMapping("invokeFeignGet/{virtualInterfaceKey}")
@RequiresPermissions("dimensiondoor:sqlDoor:invokeGetFeign")/*权限字符串防止非法访问*/
@ApiOperation(value = "任意sql门 feign请求 json传参", notes = "可执行任意sql")
@Log(title = "sql门执行GET请求[feign请求]", businessType = BusinessType.SELECT)
public R<Object> invokeGetFeign(@PathVariable("virtualInterfaceKey") String key, @RequestParam(value = "json", required = false) String json) {
return invoke(true, HttpRequestUtil.GET, key, json);
}
/**
* 任意sql门 执行 DELETE
*
* @param key 虚拟接口唯一标识
* @return com.ling.common.core.domain.R<?>
* <br><br><b>作者: 990130556 <a class=b href="https://blog.csdn.net/lingdu_dou">lingdu</a></b><br>
* 创建时间: 2021年06月21日 19:04:59
*/
@DeleteMapping("invoke/{virtualInterfaceKey}")
@RequiresRoles("dba")
@ApiOperation(value = "任意sql门", notes = "可执行任意sql")
@Log(title = "sql门执行DELETE请求", businessType = BusinessType.DELETE)
public R<Object> invokeDelete(@PathVariable("virtualInterfaceKey") String key) {
return invoke(HttpRequestUtil.DELETE, key);
}
// @DeleteMapping("invokeFeignDelete/{virtualInterfaceKey}")
// @RequiresPermissions("dimensiondoor:sqlDoor:invokeDeleteFeign")// 权限字符串防止非法访问
// @ApiOperation(value = "任意sql门[feign请求]", notes = "可执行任意sql")
// @Log(title = "sql门执行DELETE请求 [feign请求]", businessType = BusinessType.DELETE)
// public R<?> invokeDeleteFeign(@PathVariable("virtualInterfaceKey") String key, @RequestParam(value = "json", required = false) String json) {
// return invoke(HttpRequestUtil.DELETE, key, json);
// }
/**
* 非json传参方式统一调用
*/
private R<Object> invoke(String type, String virtualInterfaceKey) {
return invoke(false, type, virtualInterfaceKey, null);
}
/**
* json传参方式统一调用
*/
private R<Object> invoke(String type, String virtualInterfaceKey, String json) {
return invoke(false, type, virtualInterfaceKey, json);
}
/**
* 获取虚拟接口信息
*
* @param isFeign 是否feign调用
* @param type 请求类型
* @param virtualInterfaceKey 虚拟接口唯一标识
* @param json json字符串
* @return com.ling.common.dimensiondoor.lingdu.datareduction.domain.SysVirtualInterface
* <br><br><b>作者: 990130556 <a class=b href="https://blog.csdn.net/lingdu_dou">lingdu</a></b><br>
* 创建时间: 2021年06月24日 11:48:12
*/
private R<Object> invoke(Boolean isFeign, String type, String virtualInterfaceKey, String json) {
// 先尝试从redis获取
SysVirtualInterface svi = redisService.getCacheObject(Constants.SYS_VIRTUAL_INTERFACE_REDIS_KEY + virtualInterfaceKey);
// redis中没获取到
if (svi == null) {
// 再尝试从数据库获取
svi = SpringUtils.getBean(RemoteSysApiRepositoryService.class).selectSysVirtualInterfaceByVirtualInterfaceKey(virtualInterfaceKey).getData();
}
// 如果没获取到任何数据
if (null == svi) {
return R.fail(HttpStatus.NOT_FOUND, "接口不存在");
}
// 更新到redis(重置失效时间) // 不设置时间则永久有效
redisService.setCacheObject(Constants.SYS_VIRTUAL_INTERFACE_REDIS_KEY + virtualInterfaceKey, svi, EXPIRE_TIME, TimeUnit.DAYS);
// 非feign调用
if (!isFeign) {
// 权限认证
AuthUtil.authLogic.checkPermiAnd(svi.getPerms());
}
String requestType = svi.getRequestType();
//判断接口请求方式是否一致 转大写↓
if (!requestType.toUpperCase().contains(type)) {
return R.fail(HttpStatus.BAD_METHOD, "只允许" + requestType + "请求!");
}
String sqlValue = svi.getSqlValue();
// 接口参数集合 直接拿可能会出现被锁住不能修改的情况 所以new一下
Map<String, Object[]> parameterMap = new HashMap<>(Objects.requireNonNull(ServletUtils.getRequest()).getParameterMap());
try {
// 尝试自主获取json数据
json = json == null ? GetRequestJsonUtils.getParametersJsonString(ServletUtils.getRequest()) : json;
} catch (Exception ignored) {
}
// 设置json方式传参内容到集合 优先设置
if (StringUtils.isNotEmpty(json)) {
setJsonParameters(json, parameterMap);
}
// 参数实体化(构建动态实体类)
Lingdu lingdu = new Lingdu(getParams(svi, parameterMap));
Object ling = lingdu.sviVirtualEntityEncapsulation();
/*标签支持:解析sql语句中的标签.目前只支持where\if标签[<where>][<if test="name != null ANd !''.eq(name)">]
* 新增支持foreach标签
* */
List<String> strings = DynamicSqlParser.generateSql(sqlValue, lingdu.getParam());
sqlValue = strings.get(0);
/**/
log.info("\n请求者:" + SecurityUtils.getUsername() + "\n请求方法: " + virtualInterfaceKey + "\n请求参数:" + JSONObject.toJSONString(ling));
// 设置请求参数到线程变量中 供切面日志获取
SecurityContextHolder.set("ling", ling);
SecurityContextHolder.set("virtualInterfaceKey", virtualInterfaceKey);
SecurityContextHolder.set("virtualInterfaceName", svi.getVirtualInterfaceName());
// 查询请求 走分页
if (type.equals(HttpRequestUtil.GET)) {
Object o = sqlDoorService.executeTheSql(type, svi.getSystemDsn(), sqlValue, ling);
// 新日期时间类型格式化
localDateTimeFormatter(o);
return R.ok(getDataTable((List<?>) o), "获取成功");
} else {
Integer i = (Integer) sqlDoorService.executeTheSql(type, svi.getSystemDsn(), sqlValue, ling);
if (i == 0) {
return R.fail(HttpStatus.NOT_MODIFIED, "资源没有被修改");
}
return R.ok(i, "操作成功,成功执行数参考data返回值");
}
}
/**
* 尝试识别ISO_8859_1编码方式的字符串并将其转码为UTF_8编码形式
* @param str 目标字符串
* @return 返回识别并转码后的字符串或返回原值
* @作者 <b><a class=b href="https://blog.csdn.net/lingdu_dou" color="red">⭕°</a></b>
* @创建时间 2022-11-24 19:40 */
public String getEncoding(String str) {
Charset encode = StandardCharsets.ISO_8859_1;
try {
// 判断如果匹配上ISO_8859_1编码则转码成UTF_8
if (str.equals(new String(str.getBytes(encode), encode))) {
return new String(str.getBytes(encode), StandardCharsets.UTF_8);
}
} catch (Exception ignored) {
}
return str;
}
/**
* 辅助处理可能存在的中文乱码问题
* @param map 目标对象
* @param key 目标对象键值
* @作者 <b><a class=b href="https://blog.csdn.net/lingdu_dou" color="red">⭕°</a></b>
* @创建时间 2022-11-24 20:48 */
public void fuzhu(Map<Object, Object> map,String key){
// 针对隔离装置字段名中文乱码问题
String encoding = getEncoding(key);
if(!key.equals(encoding)){
Object o1 = map.get(key);
map.remove(key);
map.put(encoding,o1);
}
}
/**
* 新日期时间类型格式化/大文本类型字段转字符串[隔离装置自定义数据库连接驱动导致mybatis不能自主分析数据源信息]
*
* @param o 分页数据集合
* @作者 <b><a class=b href="https://blog.csdn.net/lingdu_dou" color="red">⭕°</a></b>
* @创建时间 2022-09-15 17:15
*/
private void localDateTimeFormatter(Object o) {
if (o != null) {
List<?> list = o instanceof List ? ((List<?>) o) : null;
if (list != null) {
for (Object om : list) {
if (om instanceof Map) {
Map<Object, Object> map = (Map) om;
for (Object key : map.keySet()) {
// 针对隔离装置字段名中文乱码问题
fuzhu(map,key.toString());
Object v = map.get(key);
if (v != null) {
if (v instanceof String || v instanceof Integer) {
continue;
}
if (v instanceof Timestamp) {
v = ((Timestamp) v).toLocalDateTime();
}
if (v instanceof LocalDateTime) {
Object format = ((LocalDateTime) v).format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
map.put(key, format);
}
// 解决Druid的坑 大文本类型数据处理[隔离装置自定义数据库连接驱动导致mybatis不能自主分析数据源信息]
if (v instanceof ClobProxy) {
ClobProxy cp = (ClobProxy) v;
map.put(key, clobToString(cp.getRawClob()));
}
}
}
}
}
}
}
}
/**
* 数据库Clob对象转换为String
*/
private static String clobToString(Clob clob) {
if (clob == null) {
return null;
}
Reader inStreamDoc = null;
try {
inStreamDoc = clob.getCharacterStream();
char[] tempDoc = new char[(int) clob.length()];
inStreamDoc.read(tempDoc);
inStreamDoc.close();
return new String(tempDoc);
} catch (IOException | SQLException e) {
e.printStackTrace();
} finally {
try {
if (inStreamDoc != null) {
inStreamDoc.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
return null;
}
/**
* 设置json传参方式的参数到集合
*
* @param json json字符串
* @param parameterMap 参数集
* <br><br><b>作者: 990130556 <a class=b href="https://blog.csdn.net/lingdu_dou">lingdu</a></b><br>
* 创建时间: 2021年12月17日 15:59:11
*/
private void setJsonParameters(String json, Map<String, Object[]> parameterMap) {
try {
JSONObject jsonObject = JSON.parseObject(json);
Set<String> set = jsonObject.keySet();
Object o;
for (String s : set) {
o = jsonObject.get(s);
o = o == null ? "" : o;
parameterMap.put(s, new Object[]{o});
}
} catch (Exception e) {
throw new ServiceException("json解析异常:" + e, HttpStatus.ERROR);
}
}
/**
* 根据虚拟接口信息和入参信息生成组合参数信息
*
* @param svi 接口信息
* @param parameterMap 入参信息
* <br><br><b>作者: 990130556 <a class=b href="https://blog.csdn.net/lingdu_dou">lingdu</a></b><br>
* 创建时间: 2021年11月23日 16:25:40
*/
private synchronized Map<String, Object> getParams(SysVirtualInterface svi, Map<String, Object[]> parameterMap) {
// 接口参数集合
List<SysApiParameter> sysApiParameterList = svi.getSysApiParameterList();
// 接口参数集合是否为空
boolean b = sysApiParameterList != null;
/*
有6个元素,则HashMap的初始化容量应为(6/0.75 +1=9)即new HashMap(9),实际容量为比9大的最近的2的指数即16
为什么要+1 ?
因为扩容不是在插入前,而是在插入后进行的。如果我们不+1,指定为new HashMap(8),则扩容阈值为8*0.75=6
在我们插入最后一个即第6个元素后,会进行自增6变成7,7>阈值6因此会进行扩容,而我们已经没有元素需要添加了,从而造成额外的一次扩容操作
因此初始化时指定的容量应为(需要的容量/负载因子+1)
或者也可以是(需要的容量 x 1.5) 即6 x 1.5 = 9
*/
Map<String, Object> params = new HashMap<>(b ? (int) ((sysApiParameterList.size() / 0.75) + 1) : 6);
if (b) {
for (SysApiParameter sysApiParameter : sysApiParameterList) {
// 接口其中一个参数名
String apiParameterName = sysApiParameter.getApiParameterName();
// 接口其中一个参数备注
String remark = sysApiParameter.getRemark();
// 请求入参
Object[] objects = parameterMap.get(apiParameterName);
// 前端传参了
if (objects != null) {
Object o = objects[0];
if (o != null) {
// 参数类型是byte[]
if ("byte[]".equals(sysApiParameter.getType())) {
// 字符串转数组
byte[] bytes = ByteConvertUtil.hexToByteArray((String) o);
params.put(apiParameterName, bytes);
// String(array),Integer(array),Long(array)等
} else if (sysApiParameter.getType().contains("array")) {
params.put(apiParameterName, objects);
} else {
params.put(apiParameterName, o);
}
}
}
// 验证参数(未传参,且参数为必须)
if (params.get(apiParameterName) == null && "Y".equals(sysApiParameter.getRequire())) {
log.error("未传参数: " + apiParameterName + " " + remark + "!");
throw new ServiceException("未传参数: " + apiParameterName + " " + remark + "!");
}
}
}
String type = svi.getRequestType().toUpperCase();
// 新增或更新操作
if (type.equals(HttpRequestUtil.PUT) || type.equals(HttpRequestUtil.POST)) {
// 设置当前访问系统的用户信息和操作时间
if (type.equals(HttpRequestUtil.POST)) {
// 获取子系统当前操作用户
Object createBy = params.get("createBy");
createBy = createBy == null ? "" : "->" + createBy;
params.put("createBy", SecurityUtils.getUsername() + createBy);
params.put("createTime", DateUtils.getTime());
}
// 获取子系统当前操作用户
Object updateBy = params.get("updateBy");
updateBy = updateBy == null ? "" : "->" + updateBy;
params.put("updateBy", SecurityUtils.getUsername() + updateBy);
params.put("updateTime", DateUtils.getTime());
}
// 设置系统内置参数 优先级最低
settingBuiltInParameters(svi, params);
return params;
}
/**
* 设置系统内置方法参数
*
* @param svi 虚拟接口信息
* @param params 参数信息
* <br><br><b>作者: 990130556 <a class=b href="https://blog.csdn.net/lingdu_dou">lingdu</a></b><br>
* 创建时间: 2021年12月16日 09:22:26
*/
private void settingBuiltInParameters(SysVirtualInterface svi, Map<String, Object> params) {
// 设置系统内置方法参数
if (StringUtils.isNotEmpty(svi.getParameterName()) && StringUtils.isNotEmpty(svi.getParameterMethodsThePath())) {
// 内置 系统参数 名
String[] parameterNames = svi.getParameterName().replaceAll("\\s", "").split(";");
// 内置系统参数 获取方法全路径
String[] parameterMethodsThePaths = svi.getParameterMethodsThePath().replaceAll("\\s", "").split(";");
if (StringUtils.isNotEmpty(parameterNames) && StringUtils.isNotEmpty(parameterMethodsThePaths)) {
for (int i = 0; i < parameterNames.length; i++) {
// 先尝试从前端获取该参数
Object obj = params.get(parameterNames[i]);
// 如果前端没传有效值
if (StringUtils.isNull(obj)) {
try {
Object o = BeanInvokeUtil.invokeMethod(parameterMethodsThePaths[i]);
if (null == o) {
o = "";
}
// 补充内置参数
params.put(parameterNames[i], o.toString());
} catch (Exception e) {
log.error("补充内置参数出错!", e);
}
}
}
}
}
}
}
仿mybatis解析xml工具
package com.jsxy.dimensiondoor.utils.mybatis;
import com.jsxy.common.core.constant.HttpStatus;
import com.jsxy.common.core.exception.ServiceException;
import com.jsxy.common.core.utils.StringUtils;
import org.dom4j.*;
import org.dom4j.tree.DefaultCDATA;
import org.dom4j.tree.DefaultText;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.*;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import java.util.stream.Stream;
import static java.lang.Boolean.FALSE;
import static java.lang.Boolean.TRUE;
import static java.util.Objects.isNull;
import static java.util.stream.Collectors.joining;
import static java.util.stream.Collectors.toList;
/**
* @author 99013
* @ClassName DynamicSqlParser
* @Description xml标签解析工具类
* 作者:99013
* 创建时间:2022年01月06日 09:18:53
* @Version 1.0
**/
public class DynamicSqlParser {
private static final Logger log = LoggerFactory.getLogger(DynamicSqlParser.class);
public static final String SQL = "sql";
public static final String DYNAMIC_SQL = "DynamicSql";
public static final String ONE_SQL = "OneSql";
public static final String MORE_SQL = "MoreSql";
public static final String EMPTY_STRING = "";
public static final Pattern XML_PATTERN = Pattern.compile("^<[^/]\\S+?>.*</\\S+>$");
public static final String DEFAULT_XML_TAG = "<sql>";
public static final String DEFAULT_XML_TAG_END = "</sql>";
public static final String LINE = "\n";
public static final String SPACE = " ";
public static final String SQL_DELIMITER = "; ";
public static final String OPEN_TOKEN = "#";
public static final String CLOSE_TOKEN = "#";
public static final String WHERE = "where";
//
public static final String OTHERWISE = "otherwise";
public static final String CHOOSE = "choose";
public static final String WHEN = "when";
public static final String WHERE_DEFAULT = "where 1=1";
public static final String AND = "and";
public static final String OR = "or";
public static final String AND_OR = "^(and |AND |or |OR )";
public static final String IF = "if";
public static final String TEST = "test";
public static final String FOREACH = "foreach";
public static final String LIST = "list";
public static final String COLLECTION = "collection";
public static final String ITEM = "item";
public static final String START = "start";
public static final String OPEN = "open";
public static final String END = "end";
public static final String CLOSE = "close";
public static final String JOIN = "join";
public static final String SEPARATOR = "separator";
public static final String INTERNAL_ERROR_CODE = "500";
private DynamicSqlParser() {
//这是一个工具类
}
/**
* 将数据库中的sql解析为可执行的sql语句
*
* @param sql 数据库中的sql
* @param params 参数
* @return sql list
*/
public static List<String> generateSql(String sql, Map<String, Object> params) {
//如果不是xml格式,先封装成xml,用于适配以往版本
if (!XML_PATTERN.matcher(sql).matches()) {
sql = DEFAULT_XML_TAG + sql + DEFAULT_XML_TAG_END;
}
Document document;
try {
// 自定义转义
sql=repl(sql);
document = DocumentHelper.parseText(sql);
} catch (DocumentException e) {
e.printStackTrace();
throw new ServiceException("xml解析异常!可能出现>(大于号)或<(小于号)等特殊字符未处理 可尝试添加(\\)转义.如:\\<、\\>;或添加<![CDATA[...]]> \n"+e, HttpStatus.ERROR);
}
String sqlString = parseXml(document.getRootElement(), params);
if (StringUtils.isEmpty(sqlString)) {
return Collections.emptyList();
}
//解析生成的sql可能包含多个sql
String[] sqlArray = sqlString.split(";");
return Stream.of(sqlArray).filter(StringUtils::isNotBlank).collect(toList());
}
/**
* 自定义转义字符
* @param sql 目标字符串
* @作者 <b><a class=b href="https://blog.csdn.net/lingdu_dou" color="red">⭕°</a></b>
* @创建时间 2022-11-23 17:26 */
private static String repl(String sql){
sql=sql.replaceAll("\\\\<","<![CDATA[<]]>");
sql=sql.replaceAll("\\\\>","<![CDATA[>]]>");
return sql;
}
public static void main(String[] args) {
String sql= "SELECT *,#{ling.name} AS name,\n" +
"#{ling.sysLabelArray[0]} AS 中文\n" +
"FROM `sys_communication`\n" +
"<where>\n" +
" <if test = \"name==null \">\n" +
// " <if test = \"name==null or !name.eq('')\">\n" +
"\t and 1\\<=2\n" +
"\t and 1\\<=2\n" +
"\t and 2\\>=2\n" +
"\t and 2\\>=2\n" +
"\t </if>\n" +
"\t and id not in\n" +
"\t <foreach item=\"item\" index=\"index\" open=\"(\" separator=\",\" close=\")\" collection=\"sysLabelArray\">\n" +
" #{item.id}\n" +
" </foreach>\n" +
"</where>";
System.out.println("转换前。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。");
System.out.println(sql);
Map<String, Object> params=new HashMap<>();
params.put("sysLabelArray",new Object[]{new HashMap<>().put("id","1"),new HashMap<>().put("id","2"),new HashMap<>().put("id","3")});
// params.put("name","null");
List<String> strings = generateSql(sql, params);
System.out.println("转换后。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。");
System.out.println(strings.get(0));
}
/**
* 处理节点
* @param node node
* @param context 参数上下文
* @return sql
*/
private static String parseXml(Node node, Map<String, Object> context) {
//如果是普通sql || 如果是CDATA
if (node instanceof DefaultText || node instanceof DefaultCDATA) {
return node.getText();
}
//如果不认识,则返回空串
if (!(node instanceof Element)) {
return EMPTY_STRING;
}
Element element = (Element) node;
String name = element.getName();
//处理各种标签
switch (name) {
case WHERE:
return handleWhere(element, context);
case IF:
case WHEN:
return handleIf(element, context);
case FOREACH:
return handleFor(element, context);
case SQL:
case DYNAMIC_SQL:
case OTHERWISE:
return handleRoot(element, context);
case CHOOSE:
return handleChoose(element, context);
case ONE_SQL:
case MORE_SQL:
return handleRoot(element, context) + SQL_DELIMITER;
default:
return EMPTY_STRING;
}
}
/**
* 处理根节点
* @param element 元素
* @param context 上下文
* @return sql
*/
private static String handleRoot(Element element, Map<String, Object> context) {
return element.content()
.stream()
.map(n -> parseXml((Node) n, context))
.collect(joining()).toString();
}
/**
* 处理<if>标签
* @param element 元素
* @param context 参数
* @return sql
*/
private static String handleIf(Element element, Map<String, Object> context) {
Attribute test = element.attribute(TEST);
Object value = OgnlUtils.parseIf(test.getValue(), context);
if (FALSE.equals(value)) {
return EMPTY_STRING;
}
return handleRoot(element, context);
}
/**
* 处理where标签
* @param element 元素
* @param context 参数
* @return sql
*/
private static String handleWhere(Element element, Map<String, Object> context) {
String sql = handleRoot(element, context);
if (StringUtils.isEmpty(sql)) {
return EMPTY_STRING;
}
sql = sql.replaceAll(LINE, SPACE).trim();
if (StringUtils.isEmpty(sql)) {
return EMPTY_STRING;
}
//替换掉开头的and或者or
return WHERE + SPACE + sql.replaceFirst(AND_OR, EMPTY_STRING);
}
/**
* 处理for标签 语法兼容mybatis
* 原理:将for标签解析成多个sql片段,将其中的#xxx#标签转换为对应的#list[i].xxx#
* 在执行sql时,使用ognl表达式取值
* @param element 元素
* @param context 上下文
* @return sql
*/
private static String handleFor(Element element, Map<String, Object> context) {
String listName = getAttribute(element, LIST, COLLECTION);
String itemName = getAttribute(element, ITEM);
String start = getAttribute(element, START, OPEN);
String end = getAttribute(element, END, CLOSE);
String join = getAttribute(element, JOIN, SEPARATOR);
if(StringUtils.isEmpty(listName)){
throw new RuntimeException("SQL_ERROR");
}
itemName = StringUtils.isEmpty(itemName) ? ITEM : itemName;
String sqlFragment = element.getText().replace(LINE, SPACE);
Collection<Object> list = OgnlUtils.parseForeach(listName, context);
if (StringUtils.isEmpty(list)) {
log.warn("- list: {} is empty!", listName);
return start + end;
}
//生成sql循环
String regex = "#\\{" + itemName + "(\\.?\\S*)}";
return IntStream.range(0, list.size())
.boxed()
.map(i -> sqlFragment.replaceAll(regex, "#{ling." + listName + "[" + i + "]$1}"))
.collect(joining(join, start, end));
}
private static String getAttribute(Element element, String... names) {
return Stream.of(names)
.map(element::attribute)
.filter(Objects::nonNull)
.findAny()
.map(Attribute::getValue)
.orElse(EMPTY_STRING);
}
/**
* 处理choose标签
* @param element element
* @param context context
* @return sql
*/
private static String handleChoose(Element element, Map<String, Object> context) {
List<Node> content = element.content();
Optional<Node> whenNode = content.stream()
.filter(node -> WHEN.equals(node.getName()))
.filter(node -> testWhen(node, context))
.findFirst();
//如果存在满足条件地when,则删除其他的when和otherwise
if (whenNode.isPresent()) {
Node when = whenNode.get();
content.removeIf(node -> WHEN.equals(node.getName()) && !node.equals(when));
content.removeIf(node -> OTHERWISE.equals(node.getName()));
}
//否则删除所有的when,保留otherwise
else {
content.removeIf(node -> WHEN.equals(node.getName()));
}
//校验最多只有一个when或者other
long count = content.stream()
.filter(node -> WHEN.equals(node.getName()) || OTHERWISE.equals(node.getName()))
.count();
if (count > 1) {
log.error("when tag or otherwise tag is more than one: {}", element);
throw new ServiceException("xml解析异常!", HttpStatus.ERROR);
}
return content.stream()
.map(n -> parseXml(n, context))
.collect(joining());
}
private static boolean testWhen(Node node, Map<String, Object> context) {
Element element = (Element) node;
Attribute test = element.attribute(TEST);
Boolean result = (Boolean) OgnlUtils.parseIf(test.getValue(), context);
return TRUE.equals(result);
}
// /**
// * 解析sql语句为可执行的sql和相应的参数
// * 这部分为了避免使用正则表达式因而手动解析
// * 逻辑极其复杂普通人别tm乱动
// *
// * @param sql sql
// * @return 可执行sql: select * from tt where id = ?
// */
// public static SqlAndParams parseSql(String sql, Map<String, Object> context) {
// List<Object> params = new ArrayList<>();
// Assert.notEmpty(sql, INTERNAL_ERROR_CODE, SQL_ERROR);
// // search open token
// int start = sql.indexOf(OPEN_TOKEN);
// if (start == -1) {
// return new SqlAndParams(sql);
// }
// char[] src = sql.toCharArray();
// int offset = 0;
// final StringBuilder builder = new StringBuilder();
// StringBuilder expression = null;
// do {
// if (start > 0 && src[start - 1] == '\\') {
// builder.append(src, offset, start - offset - 1).append(OPEN_TOKEN);
// offset = start + OPEN_TOKEN.length();
// } else {
// if (expression == null) {
// expression = new StringBuilder();
// } else {
// expression.setLength(0);
// }
// builder.append(src, offset, start - offset);
// offset = start + OPEN_TOKEN.length();
// int end = sql.indexOf(CLOSE_TOKEN, offset);
// while (end > -1) {
// if (end > offset && src[end - 1] == '\\') {
// expression.append(src, offset, end - offset - 1).append(CLOSE_TOKEN);
// offset = end + CLOSE_TOKEN.length();
// end = sql.indexOf(CLOSE_TOKEN, offset);
// } else {
// expression.append(src, offset, end - offset);
// break;
// }
// }
// if (end == -1) {
// builder.append(src, start, src.length - start);
// offset = src.length;
// } else {
// Collection<?> objects = handleExpression(expression, builder, context);
// params.addAll(objects);
// offset = end + CLOSE_TOKEN.length();
// }
// }
// start = sql.indexOf(OPEN_TOKEN, offset);
// } while (start > -1);
// if (offset < src.length) {
// builder.append(src, offset, src.length - offset);
// }
//
// return new SqlAndParams(builder.toString(), params);
// }
private static Collection<?> handleExpression(StringBuilder expression, StringBuilder builder, Map<String, Object> context) {
String key = expression.toString();
Object value = context.get(key);
if (isNull(value)) {
value = OgnlUtils.parseIf(key, context);
}
//处理数组和集合
if (value instanceof Collection) {
Collection<?> collection = (Collection<?>) value;
String replace = collection.stream().map(s -> "?")
.collect(Collectors.joining(",", "(", ")"));
builder.append(replace);
return collection;
}
//普通数据类型
else {
builder.append('?');
return Collections.singletonList(value);
}
}
}
辅助类
package com.jsxy.dimensiondoor.utils.mybatis;
import com.alibaba.fastjson2.JSONArray;
import com.jsxy.common.core.lingdu.Lingdu;
import java.lang.reflect.Method;
import java.util.*;
/**
* @ClassName OgnlUtils
* @Description 标签对象转换辅助工具类
* 作者:99013
* 创建时间:2022年02月11日 11:21:49
* @Version 1.0
**/
public class OgnlUtils {
/**
* 解析(if)写死的
* @param value 值
* @param context 参数集合
* @return boolean
* <br><br><b>作者: 990130556 <a class=b href="https://blog.csdn.net/lingdu_dou">lingdu</a></b><br>
* 创建时间: 2022年02月11日 11:23:37
*/
public static Object parseIf(String value, Map<String, Object> context) {
/*新初始化一个虚拟实体类工具*/
Lingdu lingdu=new Lingdu();
try {
String[] split = value.split("(!=)|(==)");
String keyName = split[0].trim();
value=value.replaceAll("[\\s][a,A][n,N][d,D][\\s]","&&");
value=value.replaceAll("[\\s][o,O][r,R][\\s]","||");
value=value.replaceAll("'","\"");
value=value.replaceAll(".[e,E][q,Q]",".equals");
Class<?> a = lingdu.createMethod(compileMethod(value,keyName));
if(a==null){
throw new RuntimeException("分析sql出错");
}
Method lingIf1 = a.getDeclaredMethod("lingIf"+keyName,Object.class);
Object s1 = context.get(keyName);
return lingIf1.invoke(a.newInstance(),s1);
}catch (Exception e){
e.printStackTrace();
}
return false;
}
public static void main(String[] args) {
String a="asdfghjkl";
String b="asdfghjkl";
if(a==b){
System.out.println("==");
}else {
System.out.println("!==");
}
}
/**
* 解析(foreach)写死的
* @param listName 集合数组名字
* @param context 参数集合
* @return boolean
* <br><br><b>作者: 990130556 <a class=b href="https://blog.csdn.net/lingdu_dou">lingdu</a></b><br>
* 创建时间: 2022年02月11日 11:23:37
*/
public static Collection<Object> parseForeach(String listName, Map<String, Object> context) {
Object o = context.get(listName);
if(o instanceof JSONArray){
return new ArrayList<>((JSONArray) o);
}else {
Object[] o2=null;
if(o instanceof String[]){
o2=(String[]) o;
}else if (o instanceof Integer[]){
o2=(Integer[])o;
} else if (o instanceof Long[]) {
o2=(Long[])o;
} else if (o != null) {
o2=(Object[]) o;
}
if (o2 != null){
return Arrays.asList(o2);
}else {
return new ArrayList<>();
}
}
}
/**
* 编写方法
* @param value if方法判断条件
* @param keyName 参数名
* <br><br><b>作者: 990130556 <a class=b href="https://blog.csdn.net/lingdu_dou">lingdu</a></b><br>
* 创建时间: 2022年02月17日 11:00:28
*/
public static String compileMethod(String value, String keyName){
return "public boolean lingIf"+keyName+" (Object "+keyName+"){" +
"if("+value+"){" +
"return true;" +
"}else{" +
"return false;" +
"}" +
"}";
}
}
Service层
接口
package com.ling.dimensiondoor.service;
/**
* sql门业务层接口
* @ClassName ISqlDoorService
* @Description TODO
* 作者:99013
* 创建时间:2021年06月07日 19:31:25
* @Version 1.0
**/
public interface ISqlDoorService {
/**
* 执行sql
* @param systemDsn 数据源
* @param sql sql语句
* @param ling 通用实体类
* @return java.util.List<java.util.LinkedHashMap<java.lang.String,java.lang.Object>>
* <br><br><b>作者: 990130556 <a class=b href="https://blog.csdn.net/lingdu_dou">lingdu</a></b><br>
* 创建时间: 2021年08月27日 11:39:33
*/
public Object executeTheSql(String type,String systemDsn, String sql, Object ling);
}
实现类
package com.ling.dimensiondoor.service.impl;
import com.alibaba.fastjson2.JSONObject;
import com.baomidou.dynamic.datasource.toolkit.DynamicDataSourceContextHolder;
import com.github.pagehelper.PageHelper;
import com.ling.common.core.constant.Constants;
import com.ling.common.core.exception.ServiceException;
import com.ling.common.core.lingdu.MyThreadPool;
import com.ling.common.core.lingdu.utils.HttpRequestUtil;
import com.ling.common.core.utils.SpringUtils;
import com.ling.common.core.utils.StringUtils;
import com.ling.common.core.utils.sql.SqlUtil;
import com.ling.common.core.utils.uuid.IdUtils;
import com.ling.common.core.web.page.PageDomain;
import com.ling.common.core.web.page.TableSupport;
import com.ling.common.redis.service.RedisService;
import com.ling.common.core.lingdu.domain.SysDatabase;
import com.ling.dimensiondoor.service.*;
import com.ling.dimensiondoor.utils.DynamicDataSourceAspect;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.PostConstruct;
import javax.sql.DataSource;
import java.util.*;
import java.util.concurrent.TimeUnit;
import static com.ling.dimensiondoor.utils.DynamicDataSourceAspect.threadList;
/**
* 项目启动后运行此方法:CommandLineRunner实现
* @作者 <b><a class=b href="https://blog.csdn.net/lingdu_dou" color="red">⭕°</a></b>
* @创建时间 2022-09-19 17:37
*/
@Component
@Order(value = 3)
class InitClass implements CommandLineRunner {
@Override
public void run(String... var1) {
SpringUtils.getBean(SqlDoorServiceImpl.class).initDs();
}
}
/**
* @author 99013
* @ClassName SqlDoorServiceImpl
* @Description TODO
* 作者:99013
* 创建时间:2021年06月07日 19:32:07
* @Version 1.0
**/
@Service
public class SqlDoorServiceImpl extends Method implements ISqlDoorService {
private static final Logger log = LoggerFactory.getLogger(SqlDoorServiceImpl.class);
@Autowired
protected DynamicDataSourceAspect dynamicDataSourceAspect;
@Autowired
protected SysDatabaseServiceImpl sysDatabaseService;
@Autowired
protected RedisService redisService;
/**
* 更新标识
*/
private static String DF = "";
/**
* 本地缓存数据源信息
*/
private static Map<String, SysDatabase> DS = new HashMap<>();
/**
* 作用:
*
* @PostConstruct 注解的方法在项目启动的时候执行这个方法,也可以理解为在spring容器启动的时候执行,可作为一些数据的常规化加载,比如数据字典之类的。 执行顺序:
* 其实从依赖注入的字面意思就可以知道,要将对象p注入到对象a,那么首先就必须得生成对象a和对象p,才能执行注入。所以,如果一个类A中有个成员变量p被@Autowried注解,那么@Autowired注入是发生在A的构造方法执行完之后的。
* <p>
* 如果想在生成对象时完成某些初始化操作,而偏偏这些初始化操作又依赖于依赖注入,那么久无法在构造函数中实现。为此,可以使用@PostConstruct注解一个方法来完成初始化,@PostConstruct注解的方法将会在依赖注入完成后被自动调用。
* <p>
* Constructor >> @Autowired >> @PostConstruct
* ————————————————
* 版权声明:本文为CSDN博主「一个喜欢健身的程序员」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
* 原文链接:<a href="https://blog.csdn.net/sunayn/article/details/92840439">...</a>
* @作者 <b><a class=b href="https://blog.csdn.net/lingdu_dou" color="red">⭕°</a></b>
* @创建时间 2022-10-08 11:04
*/
// @PostConstruct // 这里用这个注解会导致配置文件参数还没获取到就调用了此方法导致参数缺失,从而使得内外网相同名称的数据源都查到了导致map key相同值被后来者替换了,导致数据源加载不成功
public void initDs() {
MyThreadPool.threadPool.execute(this::init);
}
/**
* 初始化数据源信息
*
* @作者 <b><a class=b href="https://blog.csdn.net/lingdu_dou" color="red">⭕°</a></b>
* @创建时间 2022-08-23 11:13
*/
private void init() {
// 查询出数据库最新数据源信息(状态为正常的数据)
SysDatabase sysDatabase1 = new SysDatabase();
sysDatabase1.setStatus("0");
sysDatabase1.setDeployedEnvironment(Constants.deployedEnvironment);
List<SysDatabase> sysDatabases = sysDatabaseService.selectSysDatabaseList(sysDatabase1);
Map<String, SysDatabase> mds = new HashMap<>();
// 将数据源信息转换成map形式
for (SysDatabase sysDatabase : sysDatabases) {
mds.put(sysDatabase.getDsKey(), sysDatabase);
}
DS = mds;
// redis缓存更新标识
redisService.setCacheObject(Constants.DS_FLUSH_KSY, IdUtils.fastSimpleUUID());
// 先删除 清空
redisService.deleteObject(Constants.DS_VALUE_KEY);
// 更新数据源信息
redisService.setCacheMap(Constants.DS_VALUE_KEY, DS);
syncDs();
}
/**
* 同步数据源
* @作者 <b><a class=b href="https://blog.csdn.net/lingdu_dou" color="red">⭕°</a></b>
* @创建时间 2022-08-23 11:01
*/
private void syncDs() {
/*变动标记值*/
String df = redisService.getCacheObject(Constants.DS_FLUSH_KSY);
// 版本号不一致
if (!DF.equals(df)) {
// 数据源同步信息
Map<String, SysDatabase> cacheMap = redisService.getCacheMap(Constants.DS_VALUE_KEY);
/*当前已生效的数据源*/
Map<String, DataSource> dataSources = dynamicDataSourceAspect.getDataSources();
Set<String> newkey = cacheMap.keySet();
// 不能直接拿 引用变量在循环后会被还原所以要new
Set<String> oldkey = new HashSet<>(dataSources.keySet());
for (String s : newkey) {
SysDatabase sysDatabase = JSONObject.parseObject(JSONObject.toJSONString(cacheMap.get(s)), SysDatabase.class);
cacheMap.put(s,sysDatabase);
DataSource dataSource = dataSources.get(s);
SysDatabase ds = DS.get(s);
// 未连接数据源
if (dataSource == null) {
// 添加新数据源
dynamicDataSourceAspect.addDataSource(sysDatabase);
// 已连接的数据源
} else {
// 删除相同标记的数据项
oldkey.remove(s);
// 本地缓存与redis缓存不一致
if (ds == null || !sysDatabase.toString().equals(ds.toString())) {
// 覆盖数据源
dynamicDataSourceAspect.addDataSource(sysDatabase);
}
}
}
for (String s : oldkey) {
// 删除多余的数据源
dynamicDataSourceAspect.removeDataSource(s);
}
// 更新本地缓存
DS = cacheMap;
// 更新本地标记
DF = df;
long l = System.currentTimeMillis();
long l2;
// 死循环检测线程完成情况
do {
// 清除已执行完的线程标记
threadList.removeIf(thread -> thread.getState().equals(Thread.State.WAITING));
l2 = System.currentTimeMillis();
// 10秒超时时间
if ((l2 - l) > 10000) {
// 清除所有数据 防止循环中未清除干净导致脏数据
threadList=null;
threadList=new ArrayList<>();
break;
}
// 延迟100毫秒左右执行下一轮循环
try {
TimeUnit.MILLISECONDS.sleep(100);
} catch (InterruptedException ie) {
Thread.currentThread().interrupt();
}
} while (!threadList.isEmpty());
}
}
/**
* 在线数据源切换方法已测试成功
*
* @param type 请求类型
* @param systemDsn 数据源名称 数据源唯一标识
* @param sql sql语句
* @param ling 参数实体
* @return 返回执行结果
*/
@Override
// @Transactional(rollbackFor = Exception.class)// 该写法会导致数据源切换失败的可能
@Transactional(rollbackFor = Exception.class,propagation= Propagation.NEVER)
public Object executeTheSql(String type, String systemDsn, String sql, Object ling) {
// 校验数据源同步信息
syncDs();
/*当前已生效的数据源*/
Map<String, DataSource> dataSources = dynamicDataSourceAspect.getDataSources();
if(dataSources.get(systemDsn)==null){
throw new ServiceException("目标数据源["+systemDsn+"]未找到");
}
//切换数据源
String push = DynamicDataSourceContextHolder.push(systemDsn);
if(!push.equals(systemDsn)){
throw new ServiceException("切换数据源失败");
}
if (type.equals(HttpRequestUtil.GET)) {
// 注意字符串规范识别问题(本页最后有对应说明)
startPageForSqlDoor(DS.get(systemDsn).getType());
}
try {
return executeTheSqlByType(type, sql, ling);
} catch (Exception e) {
throw new ServiceException("数据源["+systemDsn+"]执行 接口内部异常 请联系系统管理员 " + e.getMessage());
}finally {
// 可以最后选择清理掉此数据源 还原默认数据源
DynamicDataSourceContextHolder.clear();
}
}
/**
* 任意sql模块设置请求分页数据
* 经过测试不支持SQL server
*
* @param dialectClass 数据库类型(设置方言) 目前由于不知明原因导致不能自动获取方言,所以目前手动设置一下
*/
private void startPageForSqlDoor(String dialectClass) {
PageDomain pageDomain = TableSupport.buildPageRequest();
Integer pageNum = pageDomain.getPageNum();
Integer pageSize = pageDomain.getPageSize();
String orderBy = SqlUtil.escapeOrderBySql(pageDomain.getOrderBy());
Boolean reasonable = pageDomain.getReasonable();
PageHelper.startPage(pageNum, pageSize, orderBy).setReasonable(reasonable).setDialectClass(dialectClass);
}
/*
分页插件数据库方言别名参考
//注册别名
registerDialectAlias("hsqldb", HsqldbDialect.class);
registerDialectAlias("h2", HsqldbDialect.class);
registerDialectAlias("phoenix", HsqldbDialect.class);
registerDialectAlias("postgresql", PostgreSqlDialect.class);
registerDialectAlias("mysql", MySqlDialect.class);
registerDialectAlias("mariadb", MySqlDialect.class);
registerDialectAlias("sqlite", MySqlDialect.class);
registerDialectAlias("herddb", HerdDBDialect.class);
registerDialectAlias("oracle", OracleDialect.class);
registerDialectAlias("oracle9i", Oracle9iDialect.class);
registerDialectAlias("db2", Db2Dialect.class);
registerDialectAlias("as400", AS400Dialect.class);
registerDialectAlias("informix", InformixDialect.class);
//解决 informix-sqli #129,仍然保留上面的
registerDialectAlias("informix-sqli", InformixDialect.class);
registerDialectAlias("sqlserver", SqlServerDialect.class);
registerDialectAlias("sqlserver2012", SqlServer2012Dialect.class);
registerDialectAlias("derby", SqlServer2012Dialect.class);
//达梦数据库,https://github.com/mybatis-book/book/issues/43
registerDialectAlias("dm", OracleDialect.class);
//阿里云PPAS数据库,https://github.com/pagehelper/Mybatis-PageHelper/issues/281
registerDialectAlias("edb", OracleDialect.class);
//神通数据库
registerDialectAlias("oscar", OscarDialect.class);
registerDialectAlias("clickhouse", MySqlDialect.class);
//瀚高数据库
registerDialectAlias("highgo", HsqldbDialect.class);
//虚谷数据库
registerDialectAlias("xugu", HsqldbDialect.class);
registerDialectAlias("impala", HsqldbDialect.class);
registerDialectAlias("firebirdsql", FirebirdDialect.class);
//注册 AutoDialect
//想要实现和以前版本相同的效果时,可以配置 autoDialectClass=old
registerAutoDialectAlias("old", DefaultAutoDialect.class);
registerAutoDialectAlias("hikari", HikariAutoDialect.class);
registerAutoDialectAlias("druid", DruidAutoDialect.class);
registerAutoDialectAlias("tomcat-jdbc", TomcatAutoDialect.class);
registerAutoDialectAlias("dbcp", DbcpAutoDialect.class);
registerAutoDialectAlias("c3p0", C3P0AutoDialect.class);
//不配置时,默认使用 DataSourceNegotiationAutoDialect
registerAutoDialectAlias("default", DataSourceNegotiationAutoDialect.class);
*/
}
动态数据源相关
package com.ling.dimensiondoor.utils;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.dynamic.datasource.DynamicRoutingDataSource;
import com.baomidou.dynamic.datasource.creator.DefaultDataSourceCreator;
import com.baomidou.dynamic.datasource.spring.boot.autoconfigure.DataSourceProperty;
import com.baomidou.dynamic.datasource.spring.boot.autoconfigure.druid.DruidConfig;
import com.ling.common.core.constant.Constants;
import com.ling.common.core.exception.ServiceException;
import com.ling.common.core.lingdu.MyThreadPool;
import com.ling.common.core.lingdu.utils.IpTest;
import com.ling.common.core.lingdu.utils.JabotUtil;
import com.ling.common.core.lingdu.domain.SysDatabase;
import com.ling.common.core.utils.uuid.IdUtils;
import org.aspectj.lang.annotation.Aspect;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import javax.sql.DataSource;
import java.util.*;
/**
* 描述:动态数据源相关操作
* @author 99013
* @作者 <b><a class=b href="https://blog.csdn.net/lingdu_dou" color="red">⭕°</a></b>
* @创建时间 2022-08-31 15:48
*/
@Aspect
@Component
@Order(0) // 请注意:这里order一定要小于tx:annotation-driven的order,即先执行DynamicDataSourceAspectAdvice切面,再执行事务切面,才能获取到最终的数据源
@EnableAspectJAutoProxy(proxyTargetClass = true)
public class DynamicDataSourceAspect {
private static final Logger log = LoggerFactory.getLogger(DynamicDataSourceAspect.class);
@Autowired
private DataSource dataSource;
@Autowired
private DefaultDataSourceCreator dataSourceCreator;
/**
* 记录线程
* 存在并发修改的可能,所以此处采用线程安全的方式创建对象
*/
public static List<Thread> threadList = Collections.synchronizedList(new ArrayList<>());
/**
* 使用线程执行
* @param d 数据源连接信息
* @作者 <b><a class=b href="https://blog.csdn.net/lingdu_dou" color="red">⭕°</a></b>
* @创建时间 2022-08-30 13:51 */
public void addDataSource(SysDatabase d){
MyThreadPool.threadPool.execute(() -> addDataSource2(d,false));
}
/**
* 添加数据源
* @param d 数据源连接信息
* @param test 是否是测试
* @作者 <b><a class=b href="https://blog.csdn.net/lingdu_dou" color="red">⭕°</a></b>
* @创建时间 2022-08-17 15:27 */
public boolean addDataSource2(SysDatabase d,boolean test){
// 当前线程
Thread t = Thread.currentThread();
threadList.add(t);
// 数据源唯一标识
String dsKey = JabotUtil.jiemi(d.getDsKey());
String dsKeyTest=test? IdUtils.simpleUUID():null;
try {
String url = JabotUtil.jiemi(d.getUrl());
// 连接地址ping不通
if (!IpTest.testIp(url)){
return false;
}
DataSourceProperty property = new DataSourceProperty();
// 设置数据库连接地址
property.setUrl(url);
// 设置登录用户名
property.setUsername(JabotUtil.jiemi(d.getUsername()));
// 设置登录密码
property.setPassword(JabotUtil.jiemi(d.getPsw()));
// 设置数据库连接驱动类名
property.setDriverClassName(d.getDriverClassName());
// 德鲁伊配置
DruidConfig druidConfig=new DruidConfig();
// 失败后重连的次数
druidConfig.setConnectionErrorRetryAttempts(3);
// 请求失败之后中断
druidConfig.setBreakAfterAcquireFailure(true);
// 设置验证连接语句
druidConfig.setValidationQuery(d.getValidationQuery());
// keepAlive配置即可使得数据库连接保持活性
druidConfig.setKeepAlive(true);
// 设置德鲁伊配置
property.setDruid(druidConfig);
DynamicRoutingDataSource ds = (DynamicRoutingDataSource) dataSource;
DataSource dataSource = dataSourceCreator.createDataSource(property);
ds.addDataSource(test?dsKeyTest:dsKey, dataSource);
log.info("连接数据源{}成功",dsKey);
// 连接成功后设置当前运行环境标识
if(Constants.deployedEnvironment==null){
Constants.deployedEnvironment=d.getDeployedEnvironment();
}
}catch (Exception e){
log.error(e.getMessage()+" 尝试连接数据库[{}]失败,请检查连接信息",dsKey);
if(test){
throw new ServiceException("尝试连接数据库["+dsKey+"]失败,请检查连接信息");
}
return false;
}
if (test){
removeDataSource(dsKeyTest);
}
return true;
}
/**
* 根据数据源名称断开数据源 线上删除或者禁用数据源时调用
* @param dsName 数据源名称
* @作者 <b><a class=b href="https://blog.csdn.net/lingdu_dou" color="red">⭕°</a></b>
* @创建时间 2022-08-17 15:50 */
public void removeDataSource(String dsName){
DynamicRoutingDataSource ds = (DynamicRoutingDataSource) dataSource;
ds.removeDataSource(dsName);
}
/**
* 获取所有已加载到连接池的数据源
* @作者 <b><a class=b href="https://blog.csdn.net/lingdu_dou" color="red">⭕°</a></b>
* @创建时间 2022-08-17 18:02 */
public Map<String, javax.sql.DataSource> getDataSources(){
return ((DynamicRoutingDataSource)dataSource).getDataSources();
}
/**
* @Author andy
* @Description //根据tenant新增到数据源中
* @Date 10:59 2022/4/23
* @Param
* @return
**/
public void addDataSource(JSONObject jsonObject){
DataSourceProperty property = new DataSourceProperty();
property.setUrl("jdbc:mysql://"+jsonObject.getString("dataIp")+":"+jsonObject.getString("dataPort")+"/"+jsonObject.getString("dataName"));
property.setUsername(jsonObject.getString("dataUser"));
property.setPassword(jsonObject.getString("dataPassword"));
DynamicRoutingDataSource ds = (DynamicRoutingDataSource) dataSource;
DataSource dataSource = dataSourceCreator.createDataSource(property);
ds.addDataSource(jsonObject.getString("dataName"), dataSource);
}
}
Mapper
package com.ling.dimensiondoor.mapper;
import org.apache.ibatis.annotations.Param;
import org.springframework.stereotype.Repository;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
/**
* @author 99013
* @ClassName DatareductionMapper
* @Description TODO
* 作者:<b><a class=b href="https://blog.csdn.net/lingdu_dou" color="red">⭕°</a></b>
* 创建时间:2021年06月07日 19:25:28
* @Version 1.0
**/
@Repository
public interface DatareductionMapper {
/**
* 自定义sql查询
* @param sql sql语句
* @param ling 传参实体类
* @return 返回查询结果
* @作者 <b><a class=b href="https://blog.csdn.net/lingdu_dou" color="red">⭕°</a></b>
* @创建时间 2021年12月16日 18:51:39 */
public List<LinkedHashMap<String, Object>> selectBySql(@Param("sql") String sql, @Param("ling") Object ling);
/**
* 插入
* @param sql sql 语句
* @param ling 传参实体类
* @return int 返回成功条数
* <br><br><b>作者: 990130556 <a class=b href="https://blog.csdn.net/lingdu_dou">lingdu</a></b><br>
* 创建时间: 2021年12月16日 18:52:52
*/
public int insertBySql(@Param("sql") String sql,@Param("ling") Object ling);
/**
* 修改
* @param sql sql 语句
* @param ling 传参实体类
* @return int 返回成功条数
* <br><br><b>作者: 990130556 <a class=b href="https://blog.csdn.net/lingdu_dou">lingdu</a></b><br>
* 创建时间: 2021年12月16日 18:53:30
*/
public int updateBySql(@Param("sql") String sql,@Param("ling") Object ling);
/**
* 删除
* @param sql sql 语句
* @param ling 传参实体类
* @return int 返回成功条数
* <br><br><b>作者: 990130556 <a class=b href="https://blog.csdn.net/lingdu_dou">lingdu</a></b><br>
* 创建时间: 2021年12月16日 18:54:00
*/
public int deleteBySql(@Param("sql") String sql,@Param("ling") Object ling);
}
Mapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.ling.dimensiondoor.mapper.DatareductionMapper">
<!--自定义sql查询 statementType="STATEMENT" 非预编译 -->
<select id="selectBySql" parameterType="Object" resultType="java.util.LinkedHashMap">
<![CDATA[
${sql}
]]>
</select>
<!--插入-->
<insert id="insertBySql" parameterType="Object">
<![CDATA[
${sql}
]]>
</insert>
<!--修改-->
<update id="updateBySql" parameterType="Object">
<![CDATA[
${sql}
]]>
</update>
<!--删除-->
<delete id="deleteBySql" parameterType="Object">
<![CDATA[
${sql}
]]>
</delete>
</mapper>