小工具: DSL执行任意javaBean的任意方法

前言

最近的项目需要大量的调试工作,公司基建不完善,一些权限还不太好申请,只好自己动手写了一系列工具,包括:
1 新增调试接口:可执行任意javaBean的任意方法
2 新增调试接口:可执行任意只读sql
3 新增调试接口:可自动采集调试日志, 并给出初步的分析建议
4 新增数据比对工具:可对任意数据源(db/http/rpc等)的数据以指定schema进行比对和结果分析。

这里和大家分享下第一个小工具:可执行任意javaBean的任意方法。
目前该工具还有些小缺陷TODO,比如不支持复杂对象的嵌套解析、不支持普通类的方法等,不过已经能满足我们项目的绝大部分需求,后面有时间在优化吧。

效果展示

先上效果图:
在这里插入图片描述
我们发起了一个post请求,body里面指定了一个自定义语言的脚本,response会返回服务执行这个方法的结果。

对上面的自定义脚本的解释:

remoteUserManager#listByClassCode#I|1,S|ssscode,I|1

remoteUserManager 是我的Spring项目里面的一个bean, 对应的类为RemoteUserManager;
listByClassCode 是RemoteUserManager里面的一个方法。
函数原型如下:

@Component
public class RemoteUserManager {
   
    public List<UserDTO> listByClassCode(Integer schoolId, String classCode, Integer type) {
    ... }
}

I|1,S|ssscode,I|1 是我们要传给RemoteUserManager里listByClassCode()方法的参数列表,I表示Integer, S表示String, 整个参数列表对应的java值为: [Integer(1), String(“ssscode”), Integer(1)]。
整个脚本相当于执行如下调用:

remoteUserManager.listByClassCode(1, "ssscode", 1);

然后将调用的结果通过httpResponse展现出来。

工具源码

controller部分:

@RestController
public class ToolController {
   
    @Resource
    private ScriptUtil scriptUtil;
        @PostMapping(value = {
   "/tool/exe/script"})
    public Result executeScript(@RequestBody ScriptToolParam script) {
   
        Object r = scriptUtil.executeBeanMethodScript(script.getScript());
        return Result.getSuccessResult(r);
    }
}

入参:

@Data
public class ScriptToolParam {
   
    private String script;
    //private String  type;
}

核心逻辑:


/**
 * Created by mrpp on 2020/7/25.
 * DSL执行任意bean的public方法
 * todo: 支持private方法
 */
@Component
public class ScriptUtil implements ApplicationContextAware {
   
    private static ApplicationContext context = null;

    /** 配置中心指定的ip白名单,只有开发环境或白名单ip的机器才能执行 **/
    @Value("#{'${whiteList.debugTool.ip}'.split(',')}")
    private List<String> ipWhiteList;

    /** 当前环境,只有开发环境或白名单ip的机器才能执行 **/
    @Value("${spring.profiles.active}")
    private String env;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
   
        context = applicationContext;
    }

    public static Object getBean(String name) {
   
        return context.getBean(name);
    }

    private static List<Class> NUMBER_CLASSES = Arrays.asList(Integer.class, Long.class, Double.class, Float.class);
    private static final Map<String, Class> paramTypeMap = new HashMap<>();

    static {
   
        paramTypeMap.put("B", Boolean.class);
        paramTypeMap.put("S", String.class);
        paramTypeMap.put("I", Integer.class);
        paramTypeMap.put("L", Long.class);
        paramTypeMap.put("D", Double.class);
        paramTypeMap.put("F", Float.class);
        paramTypeMap.put("L", List.class);
        paramTypeMap.put("M", Map.class);
        paramTypeMap.put("O", Object.class);
    }

    /**
     * @param script 自定义语法的java脚本,可执行任意bean的方法
     *               分三部分: beanName#methodName#paramList
     *               举例: remoteUserManager#listByClassCode#I|1,S|ssscode,I|1
     *               其中paramList比较复杂, I|1, S|[ssscode], I|1 表示(Integer 1, String ssscode, Integer 1)
     *               String的表示方法举例: S|sss 或者 S|[sss] 或者 S|[ss,ss] 当字符串中有逗号时必须用[]包裹起来,否则可以省略包裹
     *               List的表示方法举例: L<S>|[[sss],fefe,[wefwer]]
     *               Map的表示方法举例:  M<S,I>|[[sfefe,12],[[hhllg],88]]
     *               对象的表示方法举例: O<cn.hpp.domain.param.SqlParam>|[[sql,[select * from ods_class limit 3]],[type, mysql]]
     *                   说明: cn.hpp.domain.param.SqlParam这个POJO类有两个字段: String sql 和 String type;
     *               list<对象>的表示方法: L<O<cn.hpp.domain.param.SqlParam>>|[[[sql,[select * from ods_class limit 1]],[type, mysql]], [[sql,[select * from ods_class limit 3]],[type, orcal]]]
     *               注意:参数暂时不支持复杂对象
     *               todo: 简化对象表示法,尽量可以不使用全类名
     * @return 返回结果 或者 错误信息
     */
    public Object executeBeanMethodScript(String script) {
   
        // 只有开发环境或白名单ip的机器才能真正执行脚本
        Pair<Boolean, String> checkResult = DebugTool.debugModeAllowed(env, ipWhiteList);
        if (!checkResult.getKey()) {
   
            return "err: " + checkResult.getValue();
        }
        if (StringUtils.isEmpty(script)) {
   
            return "err: blank script";
        }
        String[] parts = script.split("#");
        if (parts.length < 2) {
   
            return "err: invalid script";
        }
        String beanName = parts[0];
        String methodName = parts[1];
        List<Pair> paramTypeValuePairs = new ArrayList<>();
        if (parts.length > 2) {
   
            String paramString = script.substring(beanName.length(
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值