# 数据地图项目

# 简介

一个通用的查询统计表的,可动态配置修改统计语句,灵活的参数查询,结合 starrocks、flink 可以做到一个大数据实时统计表查询中

# 框架

框架说明版本
springboot应用开发框架2.3.12.RELEASE
spring-boot-starter-data-jpa操作数据的框架
spring-boot-starter-webMVC 框架
spring-boot-starter-securitySpring 安全框架
spring-security-cascas 验证框架
knife4j-spring-boot-starterapi 文档框架
mybatis数据库框架

# 系统功能

# 动态处理查询参数,实现通用统计表查询

# 功能点:

配置查询统计语句,条件和相关统计指标由前端传递,因为用的是 mybatis plus 的自定义 Wrapper 条件构造器,是预编译的,不会出现 sql 注入的问题

  1. 统一接口,不同类型的查询类型和条件动态传递

  2. 灵活只有配置统计表

  3. 如果有数据中台,那么就可以多平台使用通一个查询接口

# 思路:

  1. 核心点就是怎么实现构造通用的查询条件的参数, 可以在数据库中的 sql 语句外层在套一层查询,这样就可以在最外层拼接条件。

    select * from (数据库中配置的查询统计表语句) as data ew.customSqlSegment这里的{ew.customSqlSegment} 这里的 {ew.customSqlSegment} 就是 Wrapper 构造的条件语句

  2. 对条件的处理,也就是 eq、like、in、or 这些参数的处理

# 具体实现步骤:

application.yml

server:
  port: 8085
spring:
  devtools:
    restart:
      enabled: true  #设置开启热部署
      additional-paths: src/main/java #重启目录
      exclude: WEB-INF/**
  freemarker:
    cache: false    #页面不加载缓存,修改即时生效
  thymeleaf:
    cache: false
  datasource:
    driver-class-name: com.mysql.jdbc.Driver
    # 填写你数据库的 url、登录名、密码和数据库名
    url: jdbc:mysql://127.0.0.1:3306/map_data?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true
    username: root
    password: 123456
swagger:
  enable: false
pagehelper:
  helperDialect: mysql
  reasonable: true
  supportMethodsArguments: true
  params: count=countSql
logging:
  level:
    com.bright.map.mapper: debug

配置表:

CREATE TABLE `sys_config` (
  `id` bigint(20) NOT NULL COMMENT "主键",
  `base_data_type` varchar(65533) NULL COMMENT "统计数据类型",
  `base_data_name` varchar(65533) NULL COMMENT "统计数据名称",
  `base_data_text` varchar(65533) NULL COMMENT "统计数据说明",
  `base_data_sql` varchar(65533) NULL COMMENT "统计数据查询sql",
  `visible` boolean NULL COMMENT "是否启用",
  `revision` int(11) NULL COMMENT "",
  `created_by` varchar(65533) NULL COMMENT "",
  `created_time` date NULL COMMENT "",
  `updated_by` varchar(65533) NULL COMMENT "",
  `updated_time` date NULL COMMENT ""
)

对应实体类:

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
import java.util.Date;
/**
 * @author  Tz
 * @date  2022/2/25 10:49
 * @Description  
 */
/**
    * undefined
    */
@ApiModel(value="com-bright-map-domain-SysConfig")
@Data
@AllArgsConstructor
@NoArgsConstructor
@TableName(value = "sys_config")
public class SysConfig implements Serializable {
    /**
     * ID
     */
    @TableId(value = "id", type = IdType.AUTO)
    @ApiModelProperty(value="ID")
    private Long id;
    /**
     * 统计数据类型
     */
    @TableField(value = "base_data_type")
    @ApiModelProperty(value="统计数据类型")
    private String baseDataType;
    /**
     * 统计数据名称
     */
    @TableField(value = "base_data_name")
    @ApiModelProperty(value="统计数据名称")
    private String baseDataName;
    /**
     * 统计数据说明
     */
    @TableField(value = "base_data_text")
    @ApiModelProperty(value="统计数据说明")
    private String baseDataText;
    /**
     * 数据表 sql
     */
    @TableField(value = "base_data_sql")
    @ApiModelProperty(value="统计数据查询sql")
    private String baseDataSql;
    /**
     * 是否显示
     */
    @TableField(value = "visible")
    @ApiModelProperty(value="是否显示")
    private Boolean visible;
    /**
     * 乐观锁
     */
    @TableField(value = "revision")
    @ApiModelProperty(value="乐观锁")
    private Integer revision;
    /**
     * 创建人
     */
    @TableField(value = "created_by")
    @ApiModelProperty(value="创建人")
    private String createdBy;
    /**
     * 创建时间
     */
    @TableField(value = "created_time")
    @ApiModelProperty(value="创建时间")
    private Date createdTime;
    /**
     * 更新人
     */
    @TableField(value = "updated_by")
    @ApiModelProperty(value="更新人")
    private String updatedBy;
    /**
     * 更新时间
     */
    @TableField(value = "updated_time")
    @ApiModelProperty(value="更新时间")
    private Date updatedTime;
    private static final long serialVersionUID = 1L;
    public static final String COL_ID = "id";
    public static final String COL_BASE_DATA_TYPE = "base_data_type";
    public static final String COL_BASE_DATA_NAME = "base_data_name";
    public static final String COL_BASE_DATA_TEXT = "base_data_text";
    public static final String COL_BASE_DATA_SQL = "base_data_sql";
    public static final String COL_VISIBLE = "visible";
    public static final String COL_REVISION = "revision";
    public static final String COL_CREATED_BY = "created_by";
    public static final String COL_CREATED_TIME = "created_time";
    public static final String COL_UPDATED_BY = "updated_by";
    public static final String COL_UPDATED_TIME = "updated_time";
}

定义通用的 mapper 查询方法,这里使用自定义的类来提供动态 sql:

import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.toolkit.Constants;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.SelectProvider;
import java.util.List;
import java.util.Map;
/**
 * @author Tz
 * @date 2022/2/22 14:36
 * @Description
 */
public interface CurrencyMapper {
    /**
     * 通过数据库表配置 sql 查询数据
     * @param sysConfig
     * @param wrapper
     * @return
     */
    @SelectProvider(type = SqlProvider.class, method = "currencySql")
    public List<Map<String, Object>> select(SysConfig sysConfig, @Param(Constants.WRAPPER) Wrapper wrapper);
}

提供动态 sql 的 SqlProvider

/**
 * @author Tz
 * @date 2022/2/22 14:33
 * @Description
 */
public class SqlProvider {
    public String currencySql(SysConfig sysConfig){
        String sql = "select * from (" + sysConfig.getBaseDataSql() + ") as data ${ew.customSqlSegment}";
        return sql;
    }
}

基础的查询 mapper 已经可以了,现在新建查询接口,接口需要接收前端传递的条件参数,首先定好接收参数的实体

import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
/**
 * @author Tz
 * @date 2022/2/22 15:20
 * @Description
 */
@ApiModel(value="通用的Vo")
@Data
@AllArgsConstructor
@NoArgsConstructor
public class CurrencyVo implements Serializable {
    
    private static final long serialVersionUID = -2150837333679328148L;
    /**
     * 统计数据名称
     */
    @ApiModelProperty(value="统计数据名称")
    private String baseDataName;
    /**
     * 是否分页
     */
    @ApiModelProperty(value="是否分页")
    private Boolean isPagination;
    /**
     * 当前页码
     */
    @ApiModelProperty(value="当前页码")
    private Integer pageNo;
    /**
     * 显示多少记录数
     */
    @ApiModelProperty(value="显示多少记录数")
    private Integer pageSize;
    /**
     * 筛选条件字符串
     * [{\"tableColumn\":\"distno\",\"connectType\":\"like\",\"fieldType\":\"String\",\"value\":\"0111\"}]
     * tableColumn: 查询字段名称
     * connectType: 查询条件
     * fieldType: 查询字段类型
     * value: 查询的值
     */
    @ApiModelProperty(value="筛选条件字符串")
    private String searchJson;
}

对应 searchJson 特殊处理,写一个工具类专门处理

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import java.util.List;
/**
 * @author Tz
 * @date 2022/1/27 14:59
 * @Description
 */
public class SearchUtil {
    public static QueryWrapper parseWhereSql(String searchJson) {
        QueryWrapper queryWrapper = new QueryWrapper();
        if (StrUtil.isNotEmpty(searchJson)) {
            List<ScreenVo> screenList = JSON.parseArray(searchJson, ScreenVo.class);
            if (CollUtil.isNotEmpty(screenList)) {
                for (ScreenVo screenVo : screenList) {
                    switch (screenVo.getConnectType()) {
                        case "eq":
                            queryWrapper.eq(screenVo.getTableColumn(), screenVo.getFieldType() == "Integer" ? Integer.valueOf(screenVo.getValue().toString()) : screenVo.getFieldType() == "String" ? screenVo.getValue().toString() : screenVo.getValue().toString());
                            break;
                        case "ne":
                            queryWrapper.ne(screenVo.getTableColumn(), screenVo.getFieldType() == "Integer" ? Integer.valueOf(screenVo.getValue().toString()) : screenVo.getFieldType() == "String" ? screenVo.getValue().toString() : screenVo.getValue().toString());
                            break;
                        case "like":
                            queryWrapper.like(screenVo.getTableColumn(), screenVo.getFieldType() == "Integer" ? Integer.valueOf(screenVo.getValue().toString()) : screenVo.getFieldType() == "String" ? screenVo.getValue().toString() : screenVo.getValue().toString());
                            break;
                        case "likeLeft":
                            queryWrapper.likeLeft(screenVo.getTableColumn(), screenVo.getFieldType() == "Integer" ? Integer.valueOf(screenVo.getValue().toString()) : screenVo.getFieldType() == "String" ? screenVo.getValue().toString() : screenVo.getValue().toString());
                            break;
                        case "likeRight":
                            queryWrapper.likeRight(screenVo.getTableColumn(), screenVo.getFieldType() == "Integer" ? Integer.valueOf(screenVo.getValue().toString()) : screenVo.getFieldType() == "String" ? screenVo.getValue().toString() : screenVo.getValue().toString());
                            break;
                        case "notLike":
                            queryWrapper.notLike(screenVo.getTableColumn(), screenVo.getFieldType() == "Integer" ? Integer.valueOf(screenVo.getValue().toString()) : screenVo.getFieldType() == "String" ? screenVo.getValue().toString() : screenVo.getValue().toString());
                            break;
                        case "gt":
                            queryWrapper.gt(screenVo.getTableColumn(), screenVo.getFieldType() == "Integer" ? Integer.valueOf(screenVo.getValue().toString()) : screenVo.getFieldType() == "String" ? screenVo.getValue().toString() : screenVo.getValue().toString());
                            break;
                        case "lt":
                            queryWrapper.lt(screenVo.getTableColumn(), screenVo.getFieldType() == "Integer" ? Integer.valueOf(screenVo.getValue().toString()) : screenVo.getFieldType() == "String" ? screenVo.getValue().toString() : screenVo.getValue().toString());
                            break;
                        case "ge":
                            queryWrapper.ge(screenVo.getTableColumn(), screenVo.getFieldType() == "Integer" ? Integer.valueOf(screenVo.getValue().toString()) : screenVo.getFieldType() == "String" ? screenVo.getValue().toString() : screenVo.getValue().toString());
                            break;
                        case "le":
                            queryWrapper.le(screenVo.getTableColumn(), screenVo.getFieldType() == "Integer" ? Integer.valueOf(screenVo.getValue().toString()) : screenVo.getFieldType() == "String" ? screenVo.getValue().toString() : screenVo.getValue().toString());
                            break;
                        case "in":
                            queryWrapper.in(screenVo.getTableColumn(), JSONUtil.parseArray(screenVo.getFieldType() == "Integer" ? Integer.valueOf(screenVo.getValue().toString()) : screenVo.getFieldType() == "String" ? screenVo.getValue().toString() : screenVo.getFieldType() == "Array" ? JSONUtil.parseArray(screenVo.getValue()) : screenVo.getValue().toString()));
                            break;
                        default:
                            queryWrapper.eq(screenVo.getTableColumn(), screenVo.getFieldType() == "Integer" ? Integer.valueOf(screenVo.getValue().toString()) : screenVo.getFieldType() == "String" ? screenVo.getValue().toString() : screenVo.getValue().toString());
                            break;
                    }
                }
            }
        }
        return queryWrapper;
    }
}

创建 service

/**
 * @author Tz
 * @date 2022/2/22 14:56
 * @Description
 */
public interface CurrencyService {
    /**
     * 查询数据
     * @param currencyVo
     * @return
     */
    Result<?> getData(CurrencyVo currencyVo);
}

service 具体实现

package com.bright.map.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.bright.map.domain.SysConfig;
import com.bright.map.mapper.CurrencyMapper;
import com.bright.map.mapper.SysConfigMapper;
import com.bright.map.service.CurrencyService;
import com.bright.map.utils.Result;
import com.bright.map.utils.SearchUtil;
import com.bright.map.vo.CurrencyVo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.Map;
/**
 * @author Tz
 * @date 2022/2/22 14:56
 * @Description
 */
@Service
public class CurrencyServiceImpl implements CurrencyService {
    @Autowired
    private CurrencyMapper currencyMapper;
    @Autowired
    private SysConfigMapper sysConfigMapper;
    @Override
    public Result<?> getData(CurrencyVo currencyVo) {
        QueryWrapper<SysConfig> sysConfigQueryWrapper = new QueryWrapper<>();
        sysConfigQueryWrapper.eq(SysConfig.COL_BASE_DATA_NAME, currencyVo.getBaseDataName());
        SysConfig sysConfig = sysConfigMapper.selectOne(sysConfigQueryWrapper);
        QueryWrapper queryWrapper = SearchUtil.parseWhereSql(currencyVo.getSearchJson());
        List<Map<String, Object>> maps = currencyMapper.select(sysConfig, queryWrapper);
        return new Result<>(maps.size(), maps);
    }
}

添加查询的 controller

import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiOperation;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
/**
 * @author Tz
 * @date 2022/2/22 15:14
 * @Description
 */
@RestController
@RequestMapping
@Api(tags = "基础数据接口")
public class CurrencyController {
    @Resource
    private CurrencyService currencyService;
    @PostMapping("/getData")
    @ApiOperation(value = "获取基础数据")
    @ApiImplicitParam(name="currencyVo", value = "currencyVo", required = false, dataType = "CurrencyVo")
    public Result<?> getData(@RequestBody CurrencyVo currencyVo){
        Result<?> data = currencyService.getData(currencyVo);
        return data;
    }
}

# 使用:

直接注入使用

// 请求参数
{"baseDataName":"jgyt_htqdnxgc","pageNo":1,"pageSize":1000,"searchJson":"[{\"tableColumn\":\"distno\",\"connectType\":\"like\",\"fieldType\":\"String\",\"value\":\"0111\"}]"}