报表基础模块:1、测试连接、获取数据源表列表、获取表字段接口实现,部分入参检查、swagger参数、do类型转换完善;2、增加resources/lib下的ureport包依赖;

This commit is contained in:
gaoqr
2024-07-18 15:16:59 +08:00
parent 5f26418d08
commit 640c7f89fe
20 changed files with 502 additions and 34 deletions
@@ -10,13 +10,13 @@ import com.cf.imes.framework.common.exception.ErrorCode;
*/ */
public interface ErrorCodeConstants { public interface ErrorCodeConstants {
// ========== GoView 模块 1-003-000-000 ==========
ErrorCode GO_VIEW_PROJECT_NOT_EXISTS = new ErrorCode(1_003_000_000, "GoView 项目不存在");
// ========== UREPORT 模块 1-003-001-000 ========== // ========== UREPORT 模块 1-003-001-000 ==========
ErrorCode UREPORT_DATA_NOT_EXISTS = new ErrorCode(1_003_001_001, "Ureport2 报表不存在"); ErrorCode TEMPLATE_NOT_EXISTS = new ErrorCode(1_003_001_001, "报表模板信息不存在");
ErrorCode UREPORT_DATABASE_NOT_EXISTS = new ErrorCode(1_003_001_002, "Ureport2 报表数据源不存在"); ErrorCode DATASOURCE_NOT_EXISTS = new ErrorCode(1_003_001_002, "报表数据源不存在");
ErrorCode TEMPLATE_NOT_EXISTS = new ErrorCode(1_003_001_003, "报表模板信息不存在"); ErrorCode DATASOURCE_CONNECT_FAIL = new ErrorCode(1_003_001_003, "报表数据源连接失败");
ErrorCode DATASOURCE_NOT_EXISTS = new ErrorCode(1_003_001_004, "报表数据不存在"); ErrorCode DATASET_NOT_EXISTS = new ErrorCode(1_003_001_004, "报表数据不存在");
ErrorCode DATASET_NOT_EXISTS = new ErrorCode(1_003_001_005, "报表数据集不存在"); ErrorCode DATASET_SQL_INJECTION_RISK = new ErrorCode(1_003_001_005, "存在SQL注入风险");
ErrorCode DATASET_SQL_REQUIRED = new ErrorCode(1_003_001_006, "SQL语句不能为空");
ErrorCode DATASET_SQL_ILLEGAL = new ErrorCode(1_003_001_007, "SQL语句非法");
ErrorCode DATASET_GET_FIELDS_ERROR = new ErrorCode(1_003_001_008, "获取表字段");
} }
@@ -103,6 +103,13 @@
<artifactId>cf-spring-boot-starter-monitor</artifactId> <artifactId>cf-spring-boot-starter-monitor</artifactId>
</dependency> </dependency>
<dependency>
<groupId>com.ureport</groupId>
<artifactId>ureport-core</artifactId>
<version>2.3.0-SNAPSHOT</version>
<scope>system</scope>
<systemPath>${project.basedir}/src/main/resources/lib/ureport-core-2.3.0-SNAPSHOT.jar</systemPath>
</dependency>
</dependencies> </dependencies>
<build> <build>
<!-- 设置构建的 jar 包名 --> <!-- 设置构建的 jar 包名 -->
@@ -7,6 +7,7 @@ import lombok.Data;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
import javax.validation.constraints.NotNull; import javax.validation.constraints.NotNull;
import java.util.ArrayList;
import java.util.List; import java.util.List;
/** /**
@@ -19,7 +20,7 @@ import java.util.List;
@AllArgsConstructor @AllArgsConstructor
@NoArgsConstructor @NoArgsConstructor
public class ReportDatasetSaveReqVO { public class ReportDatasetSaveReqVO {
@Schema(description = "数据集id", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") @Schema(description = "数据集id", example = "1")
private Long id; private Long id;
@Schema(description = "数据集名称", example = "测试数据集") @Schema(description = "数据集名称", example = "测试数据集")
@@ -33,8 +34,8 @@ public class ReportDatasetSaveReqVO {
private String sql; private String sql;
@Schema(description = "参数列表") @Schema(description = "参数列表")
private List<ReportDatasetParameterVO> parameters; private List<ReportDatasetParameterVO> parameters = new ArrayList<>();
@Schema(description = "字段列表") @Schema(description = "字段列表")
private List<ReportDatasetFieldVO> fields; private List<ReportDatasetFieldVO> fields = new ArrayList<>();
} }
@@ -2,10 +2,12 @@ package com.cf.imes.module.report.controller.admin.datasource;
import com.cf.imes.framework.common.pojo.CommonResult; import com.cf.imes.framework.common.pojo.CommonResult;
import com.cf.imes.framework.common.util.object.BeanUtils; import com.cf.imes.framework.common.util.object.BeanUtils;
import com.cf.imes.module.report.controller.admin.dataset.vo.ReportDatasetFieldVO;
import com.cf.imes.module.report.controller.admin.datasource.vo.ReportBeanDatasourceRespVO; import com.cf.imes.module.report.controller.admin.datasource.vo.ReportBeanDatasourceRespVO;
import com.cf.imes.module.report.controller.admin.datasource.vo.ReportDatasourceReqVO; import com.cf.imes.module.report.controller.admin.datasource.vo.ReportDatasourceReqVO;
import com.cf.imes.module.report.controller.admin.datasource.vo.ReportDatasourceRespVO; import com.cf.imes.module.report.controller.admin.datasource.vo.ReportDatasourceRespVO;
import com.cf.imes.module.report.controller.admin.datasource.vo.ReportDatasourceSaveReqVO; import com.cf.imes.module.report.controller.admin.datasource.vo.ReportDatasourceSaveReqVO;
import com.cf.imes.module.report.controller.admin.datasource.vo.ReportDatasourceTestConnReqVO;
import com.cf.imes.module.report.dal.dataobject.datasource.ReportDatasourceDO; import com.cf.imes.module.report.dal.dataobject.datasource.ReportDatasourceDO;
import com.cf.imes.module.report.service.datasource.ReportDatasourceService; import com.cf.imes.module.report.service.datasource.ReportDatasourceService;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
@@ -19,8 +21,11 @@ import javax.annotation.Resource;
import javax.validation.Valid; import javax.validation.Valid;
import java.util.List; import java.util.List;
import java.util.Map;
import static com.cf.imes.framework.common.pojo.CommonResult.error;
import static com.cf.imes.framework.common.pojo.CommonResult.success; import static com.cf.imes.framework.common.pojo.CommonResult.success;
import static com.cf.imes.module.report.enums.ErrorCodeConstants.DATASOURCE_CONNECT_FAIL;
/** /**
* 报表数据源控制器 * 报表数据源控制器
@@ -30,7 +35,7 @@ import static com.cf.imes.framework.common.pojo.CommonResult.success;
*/ */
@Tag(name = "管理后台 - 报表数据源") @Tag(name = "管理后台 - 报表数据源")
@RestController @RestController
@RequestMapping("/report/datasource") @RequestMapping("/report")
@Validated @Validated
public class ReportDatasourceController { public class ReportDatasourceController {
@Resource @Resource
@@ -61,7 +66,7 @@ public class ReportDatasourceController {
} }
@GetMapping("/datasource/{id}") @GetMapping("/datasource/{id}")
@Operation(summary = "报表数据源") @Operation(summary = "报表数据源")
@Parameter(name = "id", description = "编号", required = true, example = "1") @Parameter(name = "id", description = "编号", required = true, example = "1")
@PreAuthorize("@ss.hasPermission('report:datasource:query')") @PreAuthorize("@ss.hasPermission('report:datasource:query')")
public CommonResult<ReportDatasourceRespVO> getDatasource(@PathVariable("id") Long id) { public CommonResult<ReportDatasourceRespVO> getDatasource(@PathVariable("id") Long id) {
@@ -69,14 +74,11 @@ public class ReportDatasourceController {
return success(BeanUtils.toBean(datasource, ReportDatasourceRespVO.class)); return success(BeanUtils.toBean(datasource, ReportDatasourceRespVO.class));
} }
@GetMapping("/datasources") @GetMapping("/{templateId}/datasources")
@Operation(summary = "获取报表模板下的报表数据源") @Operation(summary = "获取报表模板下的报表数据源")
@PreAuthorize("@ss.hasPermission('report:datasource:query')") @PreAuthorize("@ss.hasPermission('report:datasource:query')")
public CommonResult<List<ReportDatasourceRespVO>> getDatasourcePage(@RequestParam(value = "reportId") Long reportId, public CommonResult<List<ReportDatasourceRespVO>> getDatasourcePage(@PathVariable(value = "templateId") Long templateId) {
@RequestParam(value = "name", required = false) String name, return success(BeanUtils.toBean(datasourceService.getTemplateDatasourceList(ReportDatasourceReqVO.builder().templateId(templateId).build()), ReportDatasourceRespVO.class));
@RequestParam(value = "type", required = false) Integer type) {
ReportDatasourceReqVO reqVO = new ReportDatasourceReqVO(reportId, name, type);
return success(BeanUtils.toBean(datasourceService.getTemplateDatasourceList(reqVO), ReportDatasourceRespVO.class));
} }
@GetMapping("/datasource/beans") @GetMapping("/datasource/beans")
@@ -92,4 +94,26 @@ public class ReportDatasourceController {
public CommonResult<List<String>> getBeanDatasourceList(@RequestParam(value = "beanId") String beanId) { public CommonResult<List<String>> getBeanDatasourceList(@RequestParam(value = "beanId") String beanId) {
return success(datasourceService.loadBeanMethods(beanId)); return success(datasourceService.loadBeanMethods(beanId));
} }
@PostMapping("/datasource/connect")
@Operation(summary = "测试数据源连接")
@PreAuthorize("@ss.hasPermission('report:datasource:query')")
public CommonResult<Boolean> testDatasourceConnect(@RequestBody ReportDatasourceTestConnReqVO reqVO) {
boolean connResult = datasourceService.testConnect(reqVO);
return connResult ? success(true) : error(DATASOURCE_CONNECT_FAIL);
}
@PostMapping("/datasource/tables")
@Operation(summary = "获取数据源表列表")
@PreAuthorize("@ss.hasPermission('report:datasource:query')")
public CommonResult<List<Map<String, String>>> getDatasourceTables(@RequestBody ReportDatasourceTestConnReqVO reqVO) {
return success(datasourceService.getDatasourceTables(reqVO));
}
@PostMapping("/datasource/table/fields")
@Operation(summary = "获取数据源表字段")
@PreAuthorize("@ss.hasPermission('report:datasource:query')")
public CommonResult<List<ReportDatasetFieldVO>> getDatasourceTableFields(@RequestBody ReportDatasourceTestConnReqVO reqVO) {
return success(datasourceService.getTableFields(reqVO));
}
} }
@@ -7,6 +7,9 @@ import lombok.Data;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
import lombok.ToString; import lombok.ToString;
import java.io.Serial;
import java.io.Serializable;
/** /**
* @author Gqr * @author Gqr
* @since 2024/7/8 11:01 * @since 2024/7/8 11:01
@@ -17,13 +20,16 @@ import lombok.ToString;
@Builder @Builder
@AllArgsConstructor @AllArgsConstructor
@NoArgsConstructor @NoArgsConstructor
public class ReportDatasourceReqVO { public class ReportDatasourceReqVO implements Serializable {
@Serial
private static final long serialVersionUID = 4591901987664100102L;
@Schema(description = "模板id", example = "1", requiredMode = Schema.RequiredMode.REQUIRED) @Schema(description = "模板id", example = "1", requiredMode = Schema.RequiredMode.REQUIRED)
private Long templateId; private Long templateId;
@Schema(description = "数据源名称", example = "测试库") @Schema(description = "数据源名称", example = "测试库")
private String name; private String name;
@Schema(description = "数据源类型,jdbc、buildin", example = "jdbc") @Schema(description = "数据源类型,0 jdbc、1 buildin、2 spring", example = "jdbc")
private Integer type; private Integer type;
} }
@@ -1,6 +1,7 @@
package com.cf.imes.module.report.controller.admin.datasource.vo; package com.cf.imes.module.report.controller.admin.datasource.vo;
import com.cf.imes.module.report.controller.admin.dataset.vo.ReportDatasetRespVO; import com.cf.imes.module.report.controller.admin.dataset.vo.ReportDatasetRespVO;
import com.cf.imes.module.report.enums.datasource.ReportDatasourceTypeEnum;
import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Data; import lombok.Data;
@@ -27,8 +28,8 @@ public class ReportDatasourceRespVO {
@Schema(description = "数据源名称", example = "测试库") @Schema(description = "数据源名称", example = "测试库")
private String name; private String name;
@Schema(description = "数据源类型,jdbc、buildin", example = "jdbc") @Schema(description = "数据源类型,jdbc、buildin、spring", example = "jdbc")
private String type; private ReportDatasourceTypeEnum type;
@Schema(description = "数据源驱动类",example = "com.mysql.cj.jdbc.Driver") @Schema(description = "数据源驱动类",example = "com.mysql.cj.jdbc.Driver")
private String driver; private String driver;
@@ -1,6 +1,7 @@
package com.cf.imes.module.report.controller.admin.datasource.vo; package com.cf.imes.module.report.controller.admin.datasource.vo;
import com.cf.imes.module.report.controller.admin.dataset.vo.ReportDatasetSaveReqVO; import com.cf.imes.module.report.controller.admin.dataset.vo.ReportDatasetSaveReqVO;
import com.cf.imes.module.report.validation.datasource.ReportDatasourceTypeInEnum;
import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Builder; import lombok.Builder;
@@ -8,6 +9,9 @@ import lombok.Data;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
import javax.validation.constraints.NotNull; import javax.validation.constraints.NotNull;
import java.io.Serial;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List; import java.util.List;
/** /**
@@ -19,7 +23,10 @@ import java.util.List;
@Builder @Builder
@AllArgsConstructor @AllArgsConstructor
@NoArgsConstructor @NoArgsConstructor
public class ReportDatasourceSaveReqVO { public class ReportDatasourceSaveReqVO implements Serializable {
@Serial
private static final long serialVersionUID = -1738392826420649562L;
@Schema(description = "数据源id", example = "1") @Schema(description = "数据源id", example = "1")
private Long id; private Long id;
@@ -30,9 +37,13 @@ public class ReportDatasourceSaveReqVO {
@Schema(description = "数据源名称", example = "测试库") @Schema(description = "数据源名称", example = "测试库")
private String name; private String name;
@Schema(description = "数据源类型,jdbc、buildin", example = "jdbc") @Schema(description = "数据源类型,jdbc、buildin、spring", example = "jdbc")
@ReportDatasourceTypeInEnum
private String type; private String type;
@Schema(description = "spring型数据源id")
private String beanId;
@Schema(description = "数据源驱动类",example = "com.mysql.cj.jdbc.Driver") @Schema(description = "数据源驱动类",example = "com.mysql.cj.jdbc.Driver")
private String driver; private String driver;
@@ -49,5 +60,5 @@ public class ReportDatasourceSaveReqVO {
private String remark; private String remark;
@Schema(description = "数据集") @Schema(description = "数据集")
private List<ReportDatasetSaveReqVO> datasets; private List<ReportDatasetSaveReqVO> datasets = new ArrayList<>();
} }
@@ -0,0 +1,44 @@
package com.cf.imes.module.report.controller.admin.datasource.vo;
import com.cf.imes.module.report.controller.admin.dataset.vo.ReportDatasetParameterVO;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.ArrayList;
import java.util.List;
/**
* @author Gqr
* @since 2024/7/8 11:01
*/
@Schema(description = "管理后台 - 报表数据源测试连接 Request VO")
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class ReportDatasourceTestConnReqVO {
@Schema(description = "数据源驱动类",example = "com.mysql.cj.jdbc.Driver")
private String driver;
@Schema(description = "数据源地址", example = "https://www.cf.com")
private String url;
@Schema(description = "数据源用户名", example = "晨丰")
private String userName;
@Schema(description = "数据源密码", example = "晨丰")
private String password;
@Schema(description = "数据源id", example = "1")
private Long datasourceId;
@Schema(description = "数据库语句")
private String sql;
@Schema(description = "参数列表")
private List<ReportDatasetParameterVO> parameters = new ArrayList<>();
}
@@ -7,6 +7,7 @@ import lombok.Builder;
import lombok.Data; import lombok.Data;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
import java.util.ArrayList;
import java.util.List; import java.util.List;
/** /**
@@ -30,7 +31,7 @@ public class ReportTemplateSaveReqVO {
private String content; private String content;
@Schema(description = "数据源") @Schema(description = "数据源")
private List<ReportDatasourceSaveReqVO> datasource; private List<ReportDatasourceSaveReqVO> datasource = new ArrayList<>();
@Schema(description = "模板类型,0内置、1自定义", example = "1") @Schema(description = "模板类型,0内置、1自定义", example = "1")
private Integer type; private Integer type;
@@ -6,6 +6,7 @@ import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName; import com.baomidou.mybatisplus.annotation.TableName;
import com.cf.imes.framework.mybatis.core.dataobject.BaseDO; import com.cf.imes.framework.mybatis.core.dataobject.BaseDO;
import com.cf.imes.module.report.dal.dataobject.dataset.ReportDatasetDO; import com.cf.imes.module.report.dal.dataobject.dataset.ReportDatasetDO;
import com.cf.imes.module.report.enums.datasource.ReportDatasourceTypeEnum;
import lombok.*; import lombok.*;
import java.util.List; import java.util.List;
@@ -39,9 +40,13 @@ public class ReportDatasourceDO extends BaseDO {
*/ */
private String name; private String name;
/** /**
* 模板类型,jdbc、buildin * 模板类型,jdbc、buildin、spring
*/ */
private String type; private ReportDatasourceTypeEnum type;
/**
* spring型数据源id
*/
private String beanId;
/** /**
* 数据源驱动类 * 数据源驱动类
*/ */
@@ -0,0 +1,41 @@
package com.cf.imes.module.report.enums.datasource;
import com.baomidou.mybatisplus.annotation.EnumValue;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonValue;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* @author Gqr
* @since 2024/7/17 9:43
*/
@Getter
@AllArgsConstructor
public enum ReportDatasourceTypeEnum {
JDBC(0,"jdbc"),
BUILDIN(1,"buildin"),
SPRING(2,"spring");
@EnumValue
private final Integer code;
@JsonValue
private final String desc;
/**
* 匹配枚举
*
* @param desc
* @return
*/
@JsonCreator
public static ReportDatasourceTypeEnum fromDesc(String desc) {
for (ReportDatasourceTypeEnum value : ReportDatasourceTypeEnum.values()) {
if (value.getDesc().equals(desc)) {
return value;
}
}
return null;
}
}
@@ -1,12 +1,15 @@
package com.cf.imes.module.report.service.datasource; package com.cf.imes.module.report.service.datasource;
import com.cf.imes.module.report.controller.admin.dataset.vo.ReportDatasetFieldVO;
import com.cf.imes.module.report.controller.admin.datasource.vo.ReportBeanDatasourceRespVO; import com.cf.imes.module.report.controller.admin.datasource.vo.ReportBeanDatasourceRespVO;
import com.cf.imes.module.report.controller.admin.datasource.vo.ReportDatasourceReqVO; import com.cf.imes.module.report.controller.admin.datasource.vo.ReportDatasourceReqVO;
import com.cf.imes.module.report.controller.admin.datasource.vo.ReportDatasourceSaveReqVO; import com.cf.imes.module.report.controller.admin.datasource.vo.ReportDatasourceSaveReqVO;
import com.cf.imes.module.report.controller.admin.datasource.vo.ReportDatasourceTestConnReqVO;
import com.cf.imes.module.report.dal.dataobject.datasource.ReportDatasourceDO; import com.cf.imes.module.report.dal.dataobject.datasource.ReportDatasourceDO;
import javax.validation.Valid; import javax.validation.Valid;
import java.util.List; import java.util.List;
import java.util.Map;
/** /**
* 报表数据源 Service 接口 * 报表数据源 Service 接口
@@ -67,4 +70,28 @@ public interface ReportDatasourceService {
* @return * @return
*/ */
List<String> loadBeanMethods(String beanId); List<String> loadBeanMethods(String beanId);
/**
* 测试数据源连接
*
* @param reqVO 连接信息请求
* @return
*/
boolean testConnect(ReportDatasourceTestConnReqVO reqVO);
/**
* 获取数据源表列表
*
* @param reqVO 连接信息请求
* @return
*/
List<Map<String, String>> getDatasourceTables(ReportDatasourceTestConnReqVO reqVO);
/**
* 获取数据源表字段
*
* @param reqVO 连接信息请求
* @return
*/
List<ReportDatasetFieldVO> getTableFields(ReportDatasourceTestConnReqVO reqVO);
} }
@@ -1,25 +1,53 @@
package com.cf.imes.module.report.service.datasource; package com.cf.imes.module.report.service.datasource;
import cn.hutool.core.annotation.AnnotationUtil; import cn.hutool.core.annotation.AnnotationUtil;
import cn.hutool.core.util.ObjectUtil;
import com.bstek.ureport.definition.dataset.Field;
import com.bstek.ureport.definition.dataset.Parameter;
import com.bstek.ureport.definition.dataset.SqlDatasetDefinition;
import com.bstek.ureport.utils.ProcedureUtils;
import com.cf.imes.framework.common.exception.ServiceException;
import com.cf.imes.framework.common.util.object.BeanUtils; import com.cf.imes.framework.common.util.object.BeanUtils;
import com.cf.imes.framework.mybatis.core.query.LambdaQueryWrapperX; import com.cf.imes.framework.mybatis.core.query.LambdaQueryWrapperX;
import com.cf.imes.module.report.controller.admin.dataset.vo.ReportDatasetFieldVO;
import com.cf.imes.module.report.controller.admin.dataset.vo.ReportDatasetParameterVO;
import com.cf.imes.module.report.controller.admin.datasource.vo.ReportBeanDatasourceRespVO; import com.cf.imes.module.report.controller.admin.datasource.vo.ReportBeanDatasourceRespVO;
import com.cf.imes.module.report.controller.admin.datasource.vo.ReportDatasourceReqVO; import com.cf.imes.module.report.controller.admin.datasource.vo.ReportDatasourceReqVO;
import com.cf.imes.module.report.controller.admin.datasource.vo.ReportDatasourceSaveReqVO; import com.cf.imes.module.report.controller.admin.datasource.vo.ReportDatasourceSaveReqVO;
import com.cf.imes.module.report.controller.admin.datasource.vo.ReportDatasourceTestConnReqVO;
import com.cf.imes.module.report.dal.dataobject.datasource.ReportDatasourceDO; import com.cf.imes.module.report.dal.dataobject.datasource.ReportDatasourceDO;
import com.cf.imes.module.report.dal.mysql.datasource.ReportDatasourceMapper; import com.cf.imes.module.report.dal.mysql.datasource.ReportDatasourceMapper;
import com.cf.imes.module.report.framework.ureport.annotation.CfReportSpringbeanDatasource; import com.cf.imes.module.report.framework.ureport.annotation.CfReportSpringbeanDatasource;
import com.cf.imes.module.report.util.SqlInjectionUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContext;
import org.springframework.jdbc.core.ResultSetExtractor;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
import org.springframework.jdbc.datasource.SingleConnectionDataSource;
import org.springframework.jdbc.support.JdbcUtils;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated; import org.springframework.validation.annotation.Validated;
import javax.annotation.Resource; import javax.annotation.Resource;
import javax.sql.DataSource;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import static com.cf.imes.framework.common.exception.util.ServiceExceptionUtil.exception; import static com.cf.imes.framework.common.exception.util.ServiceExceptionUtil.exception;
import static com.cf.imes.module.report.enums.ErrorCodeConstants.DATASET_GET_FIELDS_ERROR;
import static com.cf.imes.module.report.enums.ErrorCodeConstants.DATASET_SQL_ILLEGAL;
import static com.cf.imes.module.report.enums.ErrorCodeConstants.DATASET_SQL_INJECTION_RISK;
import static com.cf.imes.module.report.enums.ErrorCodeConstants.DATASET_SQL_REQUIRED;
import static com.cf.imes.module.report.enums.ErrorCodeConstants.DATASOURCE_NOT_EXISTS; import static com.cf.imes.module.report.enums.ErrorCodeConstants.DATASOURCE_NOT_EXISTS;
/** /**
@@ -30,6 +58,7 @@ import static com.cf.imes.module.report.enums.ErrorCodeConstants.DATASOURCE_NOT_
*/ */
@Service @Service
@Validated @Validated
@Slf4j
public class ReportDatasourceServiceImpl implements ReportDatasourceService { public class ReportDatasourceServiceImpl implements ReportDatasourceService {
@Resource @Resource
private ReportDatasourceMapper datasourceMapper; private ReportDatasourceMapper datasourceMapper;
@@ -84,11 +113,12 @@ public class ReportDatasourceServiceImpl implements ReportDatasourceService {
@Override @Override
public List<ReportDatasourceDO> getTemplateDatasourceList(ReportDatasourceReqVO reqVO) { public List<ReportDatasourceDO> getTemplateDatasourceList(ReportDatasourceReqVO reqVO) {
return datasourceMapper.selectList(new LambdaQueryWrapperX<ReportDatasourceDO>() List<ReportDatasourceDO> reportDatasourceDOS = datasourceMapper.selectList(new LambdaQueryWrapperX<ReportDatasourceDO>()
.eqIfPresent(ReportDatasourceDO::getTemplateId, reqVO.getTemplateId()) .eqIfPresent(ReportDatasourceDO::getTemplateId, reqVO.getTemplateId())
.likeIfPresent(ReportDatasourceDO::getName, reqVO.getName()) .likeIfPresent(ReportDatasourceDO::getName, reqVO.getName())
.eqIfPresent(ReportDatasourceDO::getType, reqVO.getType()) .eqIfPresent(ReportDatasourceDO::getType, reqVO.getType())
.orderByDesc(ReportDatasourceDO::getCreateTime)); .orderByDesc(ReportDatasourceDO::getCreateTime));
return reportDatasourceDOS;
} }
@Override @Override
@@ -133,4 +163,133 @@ public class ReportDatasourceServiceImpl implements ReportDatasourceService {
} }
return result; return result;
} }
@Override
public boolean testConnect(ReportDatasourceTestConnReqVO reqVO) {
Connection conn = null;
try {
// 获取数据库连接
conn = buildConn(reqVO);
} catch (Exception ex) {
log.error("[testConnect][报表数据源测试连接失败:{}]", ex.getMessage());
return false;
} finally {
JdbcUtils.closeConnection(conn);
}
return true;
}
@Override
public List<Map<String, String>> getDatasourceTables(ReportDatasourceTestConnReqVO reqVO) {
Connection conn = null;
ResultSet rs = null;
List<Map<String, String>> tables = new ArrayList<Map<String, String>>();
try {
// 获取数据库连接
conn = buildConn(reqVO);
DatabaseMetaData metaData = conn.getMetaData();
String url = metaData.getURL();
String schema = null;
if (url.toLowerCase().contains("oracle")) {
schema = metaData.getUserName();
}
rs = metaData.getTables(conn.getCatalog(), schema, null, new String[]{"TABLE", "VIEW"});
// 轮询获取表信息
while (rs.next()) {
Map<String, String> table = new HashMap<String, String>();
table.put("name", rs.getString("TABLE_NAME"));
table.put("type", rs.getString("TABLE_TYPE"));
tables.add(table);
}
} catch (Exception ex) {
log.error("[testConnect][报表数据源库表失败:{}]", ex.getMessage());
} finally {
JdbcUtils.closeResultSet(rs);
JdbcUtils.closeConnection(conn);
}
return tables;
}
@Override
public List<ReportDatasetFieldVO> getTableFields(ReportDatasourceTestConnReqVO reqVO) {
Connection conn = null;
String sql = reqVO.getSql();
List<ReportDatasetParameterVO> parameters = reqVO.getParameters();
try {
if(StringUtils.isEmpty(sql)) {
throw exception(DATASET_SQL_REQUIRED);
}
// 获取数据库连接
conn = buildConn(reqVO);
// 校验sql
if (SqlInjectionUtil.checkEditSql(sql)) {
throw exception(DATASET_SQL_ILLEGAL);
}
// 检查参数sql注入
for (ReportDatasetParameterVO parameterVO : parameters) {
if (SqlInjectionUtil.checkParam(parameterVO.getDefaultValue())) {
throw exception(DATASET_SQL_INJECTION_RISK);
}
}
// 准备sql执行参数
SqlDatasetDefinition sqlDatasetDefinition = new SqlDatasetDefinition();
sqlDatasetDefinition.setSql(sql);
sqlDatasetDefinition.setParameters(BeanUtils.toBean(parameters, Parameter.class));
Map<String, Object> params = new HashMap<String, Object>();
Map<String, Object> pmap = sqlDatasetDefinition.buildParameters(params);
String sqlForUse = sqlDatasetDefinition.parseSql(pmap);
if (ProcedureUtils.isProcedure(sqlForUse)) {
List<Field> fields = ProcedureUtils.procedureColumnsQuery(sqlForUse, pmap, conn);
return BeanUtils.toBean(fields, ReportDatasetFieldVO.class);
}
DataSource dataSource = new SingleConnectionDataSource(conn, false);
NamedParameterJdbcTemplate jdbcTemplate = new NamedParameterJdbcTemplate(dataSource);
final List<ReportDatasetFieldVO> fields = new ArrayList<>();
jdbcTemplate.query(sqlForUse, pmap, (ResultSetExtractor<Integer>) rs -> {
ResultSetMetaData metadata = rs.getMetaData();
List<String> metadataList = new ArrayList<>();
for (int i = 1; i <= metadata.getColumnCount(); i++) {
String columnName = metadata.getColumnLabel(i);
metadataList.add(columnName);
fields.add(new ReportDatasetFieldVO(columnName));
}
return null;
});
return fields;
} catch (ServiceException sex) {
throw sex;
} catch (Exception ex) {
log.error("[getTableFields][报表数据源获取表字段异常]", ex);
throw new ServiceException(DATASET_GET_FIELDS_ERROR);
} finally {
JdbcUtils.closeConnection(conn);
}
}
/**
* 构建数据库连接
* 有数据源id从库里查数据源信息
* 没有数据源id用请求中的driver等连接
* @param reqVO
* @return
* @throws ClassNotFoundException
* @throws SQLException
*/
private Connection buildConn(ReportDatasourceTestConnReqVO reqVO) throws ClassNotFoundException, SQLException {
Long datasourceId = reqVO.getDatasourceId();
Connection conn = null;
if (ObjectUtil.isNotNull(datasourceId)) {
ReportDatasourceDO datasourceDO = datasourceMapper.selectById(datasourceId);
if (ObjectUtil.isNotNull(datasourceDO)) {
Class.forName(datasourceDO.getDriver());
conn = DriverManager.getConnection(datasourceDO.getUrl(), datasourceDO.getUsername(), datasourceDO.getPassword());
}
} else {
Class.forName(reqVO.getDriver());
conn = DriverManager.getConnection(reqVO.getUrl(), reqVO.getUserName(), reqVO.getPassword());
}
return conn;
}
} }
@@ -0,0 +1,63 @@
package com.cf.imes.module.report.util;
import java.util.Objects;
import java.util.regex.Pattern;
/**
* SQL 注入验证工具类
* from com.baomidou.mybatisplus.core.toolkit.sql.SqlInjectionUtil
* @author Gqr
* @since 2024/7/18 11:09
*/
public class SqlInjectionUtil {
/**
* SQL语法检查正则:符合两个关键字(有先后顺序)才算匹配
*/
private static final Pattern SQL_EDIT_PATTERN = Pattern.compile("(insert|delete|update|create|drop|truncate|grant|alter|deny|revoke|call|execute|exec|declare|show|rename|set)" +
"\\s+.*(into|from|set|where|table|database|view|index|on|cursor|procedure|trigger|for|password|union|and|or)", Pattern.CASE_INSENSITIVE);
/**
* SQL语法检查正则:符合两个关键字(有先后顺序)才算匹配
*/
private static final Pattern SQL_SYNTAX_PATTERN = Pattern.compile("(insert|delete|update|select|create|drop|truncate|grant|alter|deny|revoke|call|execute|exec|declare|show|rename|set)" +
"\\s+.*(into|from|set|where|table|database|view|index|on|cursor|procedure|trigger|for|password|union|and|or)|(select\\s*\\*\\s*from\\s+)|(and|or)\\s+.*(like|=|>|<|in|between|is|not|exists)", Pattern.CASE_INSENSITIVE);
/**
* 使用'、;或注释截断SQL检查正则
*/
private static final Pattern SQL_COMMENT_PATTERN = Pattern.compile("'.*(or|union|--|#|/\\*|;)", Pattern.CASE_INSENSITIVE);
/**
* 检查SQL 不允许修改结构或数据
*
* @param sql sql语句
* @return true 非法 false 合法
*/
public static boolean checkEditSql(String sql) {
Objects.requireNonNull(sql);
return SQL_COMMENT_PATTERN.matcher(sql).find() || SQL_EDIT_PATTERN.matcher(sql).find();
}
/**
* 检查参数是否存在 SQL 注入
*
* @param value 检查参数
* @return true 非法 false 合法
*/
public static boolean checkParam(String value) {
Objects.requireNonNull(value);
// 处理是否包含SQL注释字符 || 检查是否包含SQL注入敏感字符
return SQL_COMMENT_PATTERN.matcher(value).find() || SQL_SYNTAX_PATTERN.matcher(value).find();
}
/**
* 刪除字段转义符单引号双引号
*
* @param text 待处理字段
* @return
*/
public static String removeEscapeCharacter(String text) {
Objects.nonNull(text);
return text.replaceAll("\"", "").replaceAll("'", "");
}
}
@@ -0,0 +1,37 @@
package com.cf.imes.module.report.validation.datasource;
import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 自定义报表数据源类型入参校验注解
*
* @author Gqr
* @since 2024/7/17 9:33
*/
@Target({
ElementType.METHOD,
ElementType.FIELD,
ElementType.ANNOTATION_TYPE,
ElementType.CONSTRUCTOR,
ElementType.PARAMETER,
ElementType.TYPE_USE
})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Constraint(
validatedBy = {ReportDatasourceTypeInEnumValidator.class}
)
public @interface ReportDatasourceTypeInEnum {
String message() default "数据源类型[type]错误,请检查是否jdbc/buildin/spring";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
@@ -0,0 +1,38 @@
package com.cf.imes.module.report.validation.datasource;
import cn.hutool.core.util.ObjectUtil;
import com.cf.imes.module.report.enums.datasource.ReportDatasourceTypeEnum;
import org.apache.commons.lang3.StringUtils;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
/**
* 自定义报表数据源类型入参校验器
*
* @author Gqr
* @since 2024/7/17 9:33
*/
public class ReportDatasourceTypeInEnumValidator implements ConstraintValidator<ReportDatasourceTypeInEnum, String> {
@Override
public void initialize(ReportDatasourceTypeInEnum constraintAnnotation) {
ConstraintValidator.super.initialize(constraintAnnotation);
}
@Override
public boolean isValid(String value, ConstraintValidatorContext context) {
if (StringUtils.isEmpty(value)) {
return true;
} else {
ReportDatasourceTypeEnum byType = ReportDatasourceTypeEnum.fromDesc(value);
if (ObjectUtil.isNotNull(byType)) {
return true;
} else {
return false;
}
}
}
}
@@ -7,6 +7,7 @@ import com.cf.imes.module.report.controller.admin.dataset.vo.ReportDatasetParame
import com.cf.imes.module.report.controller.admin.dataset.vo.ReportDatasetSaveReqVO; import com.cf.imes.module.report.controller.admin.dataset.vo.ReportDatasetSaveReqVO;
import com.cf.imes.module.report.controller.admin.datasource.vo.ReportDatasourceSaveReqVO; import com.cf.imes.module.report.controller.admin.datasource.vo.ReportDatasourceSaveReqVO;
import com.cf.imes.module.report.controller.admin.template.vo.ReportTemplateSaveReqVO; import com.cf.imes.module.report.controller.admin.template.vo.ReportTemplateSaveReqVO;
import com.cf.imes.module.report.enums.datasource.ReportDatasourceTypeEnum;
import com.cf.imes.module.report.enums.template.ReportTemplateTypeEnum; import com.cf.imes.module.report.enums.template.ReportTemplateTypeEnum;
import java.util.ArrayList; import java.util.ArrayList;
@@ -31,6 +32,7 @@ public abstract class ReportCommonServiceImplTest {
.content(JsonUtil.zipString(TEMPLATE)) .content(JsonUtil.zipString(TEMPLATE))
.type(ReportTemplateTypeEnum.SYSTEM.getType()) .type(ReportTemplateTypeEnum.SYSTEM.getType())
.remark(RandomUtils.randomString()) .remark(RandomUtils.randomString())
.datasource(new ArrayList<>())
.build(); .build();
} }
@@ -40,12 +42,13 @@ public abstract class ReportCommonServiceImplTest {
.id(id) .id(id)
.name("单元测试数据源") .name("单元测试数据源")
.templateId(reportId) .templateId(reportId)
.type("jdbc") .type(ReportDatasourceTypeEnum.SPRING.getDesc())
.driver("com.mysql.cj.jdbc.Driver") .driver("com.mysql.cj.jdbc.Driver")
.url("jdbc:mysql://192.168.1.205:3307/imes_base?allowMultiQueries=true&useUnicode=true&useSSL=false&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&autoReconnect=true&nullCatalogMeansCurrent=true") .url("jdbc:mysql://192.168.1.205:3307/imes_base?allowMultiQueries=true&useUnicode=true&useSSL=false&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&autoReconnect=true&nullCatalogMeansCurrent=true")
.username("root") .username("root")
.password("root") .password("root")
.remark(RandomUtils.randomString()) .remark(RandomUtils.randomString())
.datasets(new ArrayList<>())
.build(); .build();
} }
@@ -4,7 +4,7 @@ import com.cf.imes.framework.test.core.util.RandomUtils;
import com.cf.imes.module.report.controller.admin.dataset.vo.ReportDatasetReqVO; import com.cf.imes.module.report.controller.admin.dataset.vo.ReportDatasetReqVO;
import com.cf.imes.module.report.controller.admin.dataset.vo.ReportDatasetSaveReqVO; import com.cf.imes.module.report.controller.admin.dataset.vo.ReportDatasetSaveReqVO;
import com.cf.imes.module.report.controller.admin.datasource.vo.ReportDatasourceSaveReqVO; import com.cf.imes.module.report.controller.admin.datasource.vo.ReportDatasourceSaveReqVO;
import com.cf.imes.module.report.controller.admin.template.vo.*; import com.cf.imes.module.report.controller.admin.template.vo.ReportTemplateSaveReqVO;
import com.cf.imes.module.report.dal.dataobject.dataset.ReportDatasetDO; import com.cf.imes.module.report.dal.dataobject.dataset.ReportDatasetDO;
import com.cf.imes.module.report.dal.dataobject.datasource.ReportDatasourceDO; import com.cf.imes.module.report.dal.dataobject.datasource.ReportDatasourceDO;
import com.cf.imes.module.report.dal.dataobject.template.ReportTemplateDO; import com.cf.imes.module.report.dal.dataobject.template.ReportTemplateDO;
@@ -6,7 +6,7 @@ import com.cf.imes.module.report.controller.admin.datasource.vo.ReportDatasource
import com.cf.imes.module.report.controller.admin.template.vo.ReportTemplateSaveReqVO; import com.cf.imes.module.report.controller.admin.template.vo.ReportTemplateSaveReqVO;
import com.cf.imes.module.report.dal.dataobject.datasource.ReportDatasourceDO; import com.cf.imes.module.report.dal.dataobject.datasource.ReportDatasourceDO;
import com.cf.imes.module.report.dal.dataobject.template.ReportTemplateDO; import com.cf.imes.module.report.dal.dataobject.template.ReportTemplateDO;
import com.cf.imes.module.report.enums.template.ReportTemplateTypeEnum; import com.cf.imes.module.report.enums.datasource.ReportDatasourceTypeEnum;
import com.cf.imes.module.report.service.datasource.ReportDatasourceService; import com.cf.imes.module.report.service.datasource.ReportDatasourceService;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.context.SpringBootTest;
@@ -68,7 +68,7 @@ public class ReportDatasourceServiceImplTest extends ReportCommonServiceImplTest
// 列表查询参数准备 // 列表查询参数准备
ReportDatasourceReqVO reqVO = new ReportDatasourceReqVO(); ReportDatasourceReqVO reqVO = new ReportDatasourceReqVO();
reqVO.setTemplateId(templateId); reqVO.setTemplateId(templateId);
reqVO.setType(ReportTemplateTypeEnum.SYSTEM.getType()); reqVO.setType(ReportDatasourceTypeEnum.JDBC.getCode());
reqVO.setName(updateReqVO.getName()); reqVO.setName(updateReqVO.getName());
List<ReportDatasourceDO> templateDatasourceList = reportDatasourceService.getTemplateDatasourceList(reqVO); List<ReportDatasourceDO> templateDatasourceList = reportDatasourceService.getTemplateDatasourceList(reqVO);