一个“脱裤子放屁“的功能!

一个"脱裤子放屁"的功能!

哩语
“脱了裤子放屁”,此话通常用来讥讽别人说话做事画蛇添足、多此一举。

1.背景

故事是这样的.公司的其他部门经常会要求我们部门以接口形式提供一些数据,这种突然的,毫无规划的,杂乱的需求很多.但是好在通过sql都可以完成查询,但是通过接口的形式就需要开发.
针对上面的情况,每次都需要开发功能(mybatis),然后测试上线,流程很长,对业务支撑也不好.
于是,我想到一个"脱裤子放屁"的功能(因为使用现有模式可以实现功能).我能不能开发一个"系统",在业务部门提出需求的时候,可以快速的通过sql,把数据以接口的形式返回呢?
答案当然是能,说着说着,我把功能实现了.至于是不是脱裤子放屁,已经不重要了,大不了不用.

2.问题

需要解决的问题:
1.通过接口形式提供数据,要控制好入参和出参.
2.入参的统一格式控制
3.出参的统一格式返回
4.内部的业务逻辑实现.
4.1需要实现sql可配置,因为接口的数据是根据录入的接口来返回的.

3.实现

1.一个web页面,完成数据源和sql的 crud;网上有很多现成的后台管理框架,我使用若依.
2.一个接口程序,根据传入的参数,到数据库中查询对应的数据源与sql完成查询,并返回结果集;

1.数据源信息管理

数据源管理

2.sql管理

sql管理
在sql管理页面,记录2个值,后面我们会用到.
1.数据源id
2.接口名称

3.接口应用程序

接口请求

4.实现思路

当访问接口被访问的时候,接口程序,会根据传入的数据源id和接口名称,到数据库中(之前在后台管理程序中添加的)查找对应的数据源id与接口名称.并且在指定的数据源中运行sql名称对应的sql脚本,并且把结果返回.

在这里插入图片描述

5.核心代码

5.1http请求入口

    /**
     *  公用查询方法2
     * @param db    选择的db,根据输入的值来判断使用那个数据源
     * @param sqlName 选择执行的sql
     * @param paramStr 查询参数
     * @return 返回json数据
     */
    @RequestMapping(value={"/commonv2_2/interface/{db}/{sqlName}/{paramStr}",
            "/xxxx_2/xxxxx/{db}/{sqlName}/{paramStr}"}, method = {RequestMethod.POST,RequestMethod.GET})
    public Object commonInterfaceV2(@PathVariable String db,@PathVariable String sqlName,@PathVariable String paramStr) {
        return commonQueryv2(db, sqlName, paramStr);
    }

5.2接口程序,根据数据源和sql完成sql的执行

private Map<String, Object> commonQueryv2(String db, String sqlName, String paramStr) {
        Map<String, Object> resultMap = new HashMap<>();
        List<CommonInterfaceEntity> commonInterfaceEntities = commonService.getSqlByName(db,sqlName);
        if (commonInterfaceEntities.isEmpty()){
            resultMap.put("code", ResultCode.APIKEY_INVALID);
            resultMap.put("message",ResultCode.APIKEY_INVALID.msg());
            resultMap.put("data",new ArrayList<Map<String, Object>>());
            return resultMap;
        }

        CommonInterfaceEntity commonInterfaceEntity = commonInterfaceEntities.get(0);

        NamedParameterJdbcTemplate namedParameterJdbcTemplate = null;
        //判断数据库是否存在
        Optional dataSource = Optional.ofNullable(Constants.DATASOURCE_MAP.get(commonInterfaceEntity.getAliasName()));
        if (!dataSource.isPresent()){
            //创建数据源
            namedParameterJdbcTemplate = sqlUtils.getDataSource(commonInterfaceEntity);
        }else{
            namedParameterJdbcTemplate = (NamedParameterJdbcTemplate)(Constants.DATASOURCE_MAP.get(commonInterfaceEntity.getAliasName()));
        }


        //封装注释
        try {
            String document = commonInterfaceEntity.getSqlDocument();
            if (null != document) {
                resultMap.put("INAME", sqlName);        //请求的接口名称
                String[] documentList = document.trim().split("\\|");
                for (String d : documentList){
                    String[] documents = d.trim().split("=");
                    resultMap.put(documents[0], documents[1]);
                }

            }else{
                log.warn("接口={},没有注释,请完善!",sqlName);
            }
        } catch (Exception e) {
            e.printStackTrace();
            log.error("解析注解异常"+sqlName,e);
        }


        List<Map<String, Object>> resultList = new ArrayList<Map<String, Object>>();

        //sql注入验证;
        if (sqlInj(paramStr.toLowerCase())){
            resultMap.put("code", ResultCode.DATA_FORMAT_ISVALID);
            resultMap.put("message",ResultCode.DATA_FORMAT_ISVALID.msg());
            resultMap.put("data",resultList);
            return resultMap;
        }

        String[] paramsList1 = paramStr.split("&");
        Map<String,Object> paramsMap = new HashMap<>();
        String sql = commonInterfaceEntity.getSqlContent();
        try {
            for (String param : paramsList1) {
                String[] params2 = param.split("=");
                /**
                 * 对参数转换,
                 * 如果是 l_ 开头的,转换成long 类型
                 * 如果是 i_ 开头的,转换成int 类型
                 * 如果是 s_ 开头的,转换成string 类型
                 * 其他没有标记的,默认转换成string类型
                 */

                if (params2[0].startsWith("l_")){
                    paramsMap.put(params2[0],Long.parseLong(params2[1]));
                }else if (params2[0].startsWith("i_")) {
                    paramsMap.put(params2[0],Integer.parseInt(params2[1]));
                }else if (params2[0].startsWith("s_")) {
                    paramsMap.put(params2[0],params2[1]);
                }else{
                    paramsMap.put(params2[0],params2[1]);
                }

                //进行字符串替换
                sql = sql.replaceAll(String.format("\\$\\{%s}",params2[0]),params2[1]);     //字符串拼接
                sql = sql.replaceAll(String.format("\\#\\{%s}",params2[0]),":"+params2[0]); //参数拼接
            }
        } catch (Exception e) {
            e.printStackTrace();
            log.error(e.getMessage(),e);
            resultMap.put("code", ResultCode.PARAMS_INVALID);
            resultMap.put("message","参数无效或参数格式不正确");
            resultMap.put("data",resultList);
            return resultMap;
        }

        try {
            resultList = namedParameterJdbcTemplate.queryForList(sql,paramsMap);

            if(resultList.isEmpty()){
                resultMap.put("code", ResultCode.DATA_IS_NULL.val());
                resultMap.put("message", ResultCode.DATA_IS_NULL.msg());
            }else{
                resultMap.put("code", ResultCode.SUCCESS.val());
                resultMap.put("message",ResultCode.SUCCESS.msg());
            }

            resultMap.put("data",resultList);
        } catch (Exception e) {
            e.printStackTrace();
            log.error(e.getMessage(),e);
            resultMap.put("code",ResultCode.DATABASE_OPER_ERROR);
            resultMap.put("message","服务器内部错误,查询结果异常");
            resultMap.put("data",resultList);
        }

        return resultMap;
    }

5.3根据db和接口名,查询数据库中对应的数据源与需要执行的sql

/**
     * 通过db和接口名,查询数据库中对应的数据源与需要执行的sql
     * @param dbAliasName
     * @param sqlName
     * @return 接口对象
     */
@Override
    public List<CommonInterfaceEntity> getSqlByName(String dbAliasName, String sqlName) {
        //通过dbAliasName 初始化数据源
        String sql = "    select *\n" +
                "    from ci_data_source\n" +
                "             inner join ci_sql_properties on ci_data_source.id = ci_sql_properties.data_source_id\n" +
                "    where ci_data_source.alias_name = :alias_name\n" +
                "      and ci_sql_properties.sql_name = :sql_name\n" +
                "      and ci_data_source.is_effective = :is_effective\n" +
                "      and ci_sql_properties.is_effective = :is_effective;";

        NamedParameterJdbcTemplate namedParameterJdbcTemplateMaster = new NamedParameterJdbcTemplate(jdbcTemplate_master.getDataSource());
        Map<String,Object> params = new HashMap<>();
        params.put("alias_name",dbAliasName);
        params.put("sql_name",sqlName);
        params.put("is_effective",1);
        List<CommonInterfaceEntity> commonInterfaceEntities = new ArrayList<>();
        try {
            commonInterfaceEntities = namedParameterJdbcTemplateMaster.query(sql.replaceAll("[\r\n]", " "),params,
                    new BeanPropertyRowMapper<CommonInterfaceEntity>(CommonInterfaceEntity.class));
        } catch (DataAccessException e) {
            e.printStackTrace();
        }
        return commonInterfaceEntities;
    }

6.使用体验

代码实现了,也运行了几年了.发现确实可以解决一些问题,尤其是一些在会议上的"遭遇战",响应及时,配置灵活.而且因为都是"简单sql"完成的,可以保证接口的性能,同时好扩展.

7.后续

在使用程序的这段时间,发现一个很严重的问题,就是要控制好需求的边界,要知道哪些东西可以做,哪些东西不可以做,不能无休止的无边界.现在这个功能实现起来,发现越来越像mybatis了.如果不及时控制,我恐怕要实现一个新的mybatis了.
我的例子:本来代码第一版实现,只是通过xml配置的方式来完成对sql的管理,但是业务部门抱怨,每次修改代码,都需要找运维到服务器上修改配置,还是不够灵活,然后我就实现了通过数据库来管理sql的版本.

8.后续的问题

1. 对可变参数的查询条件支持不好 – mybatis支持,抗住,不改.
2. 对系统的验证功能不够强大 – 接口现在只支持查询,同时只支持nginx代理的方式访问,可以在nginx上做一些验证,例如jwt.
3. 接口大部分都是原子性的,但是调用方总是希望通过一个接口来获取到所有的数据,这就出现了一个问题.
3.1 针对这种情况,是通过一个接口来满足调用方呢?(一个大而全的接口,多个表的联合查询,判断等等).
3.2 让调用方,通过调用多次接口来完成数据的获取呢?
3.3 我接着"脱裤子放屁",再做一个所谓的"二级接口",通过调用多次原子接口来把数据组合好,再返回给调用方.就是我来完成调用方的需求?

9.代码下载

https://download.csdn.net/download/taotao6086/86568163?spm=1001.2014.3001.5503

具体使用方法,详见附件中readme.md

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值