diff --git a/cf-module-report/cf-module-report-api/src/main/java/com/cf/imes/module/report/enums/ErrorCodeConstants.java b/cf-module-report/cf-module-report-api/src/main/java/com/cf/imes/module/report/enums/ErrorCodeConstants.java index 701e526ce..5085ebbea 100644 --- a/cf-module-report/cf-module-report-api/src/main/java/com/cf/imes/module/report/enums/ErrorCodeConstants.java +++ b/cf-module-report/cf-module-report-api/src/main/java/com/cf/imes/module/report/enums/ErrorCodeConstants.java @@ -10,13 +10,13 @@ import com.cf.imes.framework.common.exception.ErrorCode; */ 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 ========== - ErrorCode UREPORT_DATA_NOT_EXISTS = new ErrorCode(1_003_001_001, "Ureport2 报表不存在"); - ErrorCode UREPORT_DATABASE_NOT_EXISTS = new ErrorCode(1_003_001_002, "Ureport2 报表数据源不存在"); - ErrorCode TEMPLATE_NOT_EXISTS = 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_005, "报表数据集不存在"); + ErrorCode TEMPLATE_NOT_EXISTS = new ErrorCode(1_003_001_001, "报表模板信息不存在"); + ErrorCode DATASOURCE_NOT_EXISTS = new ErrorCode(1_003_001_002, "报表数据源不存在"); + ErrorCode DATASOURCE_CONNECT_FAIL = new ErrorCode(1_003_001_003, "报表数据源连接失败"); + ErrorCode DATASET_NOT_EXISTS = new ErrorCode(1_003_001_004, "报表数据集不存在"); + 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, "获取表字段"); } diff --git a/cf-module-report/cf-module-report-biz/pom.xml b/cf-module-report/cf-module-report-biz/pom.xml index 4495719c0..eef2d3137 100644 --- a/cf-module-report/cf-module-report-biz/pom.xml +++ b/cf-module-report/cf-module-report-biz/pom.xml @@ -103,6 +103,13 @@ cf-spring-boot-starter-monitor + + com.ureport + ureport-core + 2.3.0-SNAPSHOT + system + ${project.basedir}/src/main/resources/lib/ureport-core-2.3.0-SNAPSHOT.jar + diff --git a/cf-module-report/cf-module-report-biz/src/main/java/com/cf/imes/module/report/controller/admin/dataset/vo/ReportDatasetSaveReqVO.java b/cf-module-report/cf-module-report-biz/src/main/java/com/cf/imes/module/report/controller/admin/dataset/vo/ReportDatasetSaveReqVO.java index f9257b07f..5ae98d4e3 100644 --- a/cf-module-report/cf-module-report-biz/src/main/java/com/cf/imes/module/report/controller/admin/dataset/vo/ReportDatasetSaveReqVO.java +++ b/cf-module-report/cf-module-report-biz/src/main/java/com/cf/imes/module/report/controller/admin/dataset/vo/ReportDatasetSaveReqVO.java @@ -7,6 +7,7 @@ import lombok.Data; import lombok.NoArgsConstructor; import javax.validation.constraints.NotNull; +import java.util.ArrayList; import java.util.List; /** @@ -19,7 +20,7 @@ import java.util.List; @AllArgsConstructor @NoArgsConstructor public class ReportDatasetSaveReqVO { - @Schema(description = "数据集id", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + @Schema(description = "数据集id", example = "1") private Long id; @Schema(description = "数据集名称", example = "测试数据集") @@ -33,8 +34,8 @@ public class ReportDatasetSaveReqVO { private String sql; @Schema(description = "参数列表") - private List parameters; + private List parameters = new ArrayList<>(); @Schema(description = "字段列表") - private List fields; + private List fields = new ArrayList<>(); } diff --git a/cf-module-report/cf-module-report-biz/src/main/java/com/cf/imes/module/report/controller/admin/datasource/ReportDatasourceController.java b/cf-module-report/cf-module-report-biz/src/main/java/com/cf/imes/module/report/controller/admin/datasource/ReportDatasourceController.java index 4f89c8be2..c96f0bc69 100644 --- a/cf-module-report/cf-module-report-biz/src/main/java/com/cf/imes/module/report/controller/admin/datasource/ReportDatasourceController.java +++ b/cf-module-report/cf-module-report-biz/src/main/java/com/cf/imes/module/report/controller/admin/datasource/ReportDatasourceController.java @@ -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.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.ReportDatasourceReqVO; 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.ReportDatasourceTestConnReqVO; import com.cf.imes.module.report.dal.dataobject.datasource.ReportDatasourceDO; import com.cf.imes.module.report.service.datasource.ReportDatasourceService; import io.swagger.v3.oas.annotations.Operation; @@ -19,8 +21,11 @@ import javax.annotation.Resource; import javax.validation.Valid; 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.module.report.enums.ErrorCodeConstants.DATASOURCE_CONNECT_FAIL; /** * 报表数据源控制器 @@ -30,7 +35,7 @@ import static com.cf.imes.framework.common.pojo.CommonResult.success; */ @Tag(name = "管理后台 - 报表数据源") @RestController -@RequestMapping("/report/datasource") +@RequestMapping("/report") @Validated public class ReportDatasourceController { @Resource @@ -61,7 +66,7 @@ public class ReportDatasourceController { } @GetMapping("/datasource/{id}") - @Operation(summary = "获得报表数据源") + @Operation(summary = "获取报表数据源") @Parameter(name = "id", description = "编号", required = true, example = "1") @PreAuthorize("@ss.hasPermission('report:datasource:query')") public CommonResult getDatasource(@PathVariable("id") Long id) { @@ -69,14 +74,11 @@ public class ReportDatasourceController { return success(BeanUtils.toBean(datasource, ReportDatasourceRespVO.class)); } - @GetMapping("/datasources") + @GetMapping("/{templateId}/datasources") @Operation(summary = "获取报表模板下的报表数据源") @PreAuthorize("@ss.hasPermission('report:datasource:query')") - public CommonResult> getDatasourcePage(@RequestParam(value = "reportId") Long reportId, - @RequestParam(value = "name", required = false) String name, - @RequestParam(value = "type", required = false) Integer type) { - ReportDatasourceReqVO reqVO = new ReportDatasourceReqVO(reportId, name, type); - return success(BeanUtils.toBean(datasourceService.getTemplateDatasourceList(reqVO), ReportDatasourceRespVO.class)); + public CommonResult> getDatasourcePage(@PathVariable(value = "templateId") Long templateId) { + return success(BeanUtils.toBean(datasourceService.getTemplateDatasourceList(ReportDatasourceReqVO.builder().templateId(templateId).build()), ReportDatasourceRespVO.class)); } @GetMapping("/datasource/beans") @@ -92,4 +94,26 @@ public class ReportDatasourceController { public CommonResult> getBeanDatasourceList(@RequestParam(value = "beanId") String beanId) { return success(datasourceService.loadBeanMethods(beanId)); } + + @PostMapping("/datasource/connect") + @Operation(summary = "测试数据源连接") + @PreAuthorize("@ss.hasPermission('report:datasource:query')") + public CommonResult 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>> getDatasourceTables(@RequestBody ReportDatasourceTestConnReqVO reqVO) { + return success(datasourceService.getDatasourceTables(reqVO)); + } + + @PostMapping("/datasource/table/fields") + @Operation(summary = "获取数据源表字段") + @PreAuthorize("@ss.hasPermission('report:datasource:query')") + public CommonResult> getDatasourceTableFields(@RequestBody ReportDatasourceTestConnReqVO reqVO) { + return success(datasourceService.getTableFields(reqVO)); + } } diff --git a/cf-module-report/cf-module-report-biz/src/main/java/com/cf/imes/module/report/controller/admin/datasource/vo/ReportDatasourceReqVO.java b/cf-module-report/cf-module-report-biz/src/main/java/com/cf/imes/module/report/controller/admin/datasource/vo/ReportDatasourceReqVO.java index 9fc3c5ca8..2878f0d62 100644 --- a/cf-module-report/cf-module-report-biz/src/main/java/com/cf/imes/module/report/controller/admin/datasource/vo/ReportDatasourceReqVO.java +++ b/cf-module-report/cf-module-report-biz/src/main/java/com/cf/imes/module/report/controller/admin/datasource/vo/ReportDatasourceReqVO.java @@ -7,6 +7,9 @@ import lombok.Data; import lombok.NoArgsConstructor; import lombok.ToString; +import java.io.Serial; +import java.io.Serializable; + /** * @author Gqr * @since 2024/7/8 11:01 @@ -17,13 +20,16 @@ import lombok.ToString; @Builder @AllArgsConstructor @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) private Long templateId; @Schema(description = "数据源名称", example = "测试库") private String name; - @Schema(description = "数据源类型,jdbc、buildin", example = "jdbc") + @Schema(description = "数据源类型,0 jdbc、1 buildin、2 spring", example = "jdbc") private Integer type; } diff --git a/cf-module-report/cf-module-report-biz/src/main/java/com/cf/imes/module/report/controller/admin/datasource/vo/ReportDatasourceRespVO.java b/cf-module-report/cf-module-report-biz/src/main/java/com/cf/imes/module/report/controller/admin/datasource/vo/ReportDatasourceRespVO.java index 2921db9af..82ba7889d 100644 --- a/cf-module-report/cf-module-report-biz/src/main/java/com/cf/imes/module/report/controller/admin/datasource/vo/ReportDatasourceRespVO.java +++ b/cf-module-report/cf-module-report-biz/src/main/java/com/cf/imes/module/report/controller/admin/datasource/vo/ReportDatasourceRespVO.java @@ -1,6 +1,7 @@ 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.enums.datasource.ReportDatasourceTypeEnum; import io.swagger.v3.oas.annotations.media.Schema; import lombok.AllArgsConstructor; import lombok.Data; @@ -27,8 +28,8 @@ public class ReportDatasourceRespVO { @Schema(description = "数据源名称", example = "测试库") private String name; - @Schema(description = "数据源类型,jdbc、buildin", example = "jdbc") - private String type; + @Schema(description = "数据源类型,jdbc、buildin、spring", example = "jdbc") + private ReportDatasourceTypeEnum type; @Schema(description = "数据源驱动类",example = "com.mysql.cj.jdbc.Driver") private String driver; diff --git a/cf-module-report/cf-module-report-biz/src/main/java/com/cf/imes/module/report/controller/admin/datasource/vo/ReportDatasourceSaveReqVO.java b/cf-module-report/cf-module-report-biz/src/main/java/com/cf/imes/module/report/controller/admin/datasource/vo/ReportDatasourceSaveReqVO.java index cce0b9d30..883d9c02d 100644 --- a/cf-module-report/cf-module-report-biz/src/main/java/com/cf/imes/module/report/controller/admin/datasource/vo/ReportDatasourceSaveReqVO.java +++ b/cf-module-report/cf-module-report-biz/src/main/java/com/cf/imes/module/report/controller/admin/datasource/vo/ReportDatasourceSaveReqVO.java @@ -1,6 +1,7 @@ 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.validation.datasource.ReportDatasourceTypeInEnum; import io.swagger.v3.oas.annotations.media.Schema; import lombok.AllArgsConstructor; import lombok.Builder; @@ -8,6 +9,9 @@ import lombok.Data; import lombok.NoArgsConstructor; import javax.validation.constraints.NotNull; +import java.io.Serial; +import java.io.Serializable; +import java.util.ArrayList; import java.util.List; /** @@ -19,7 +23,10 @@ import java.util.List; @Builder @AllArgsConstructor @NoArgsConstructor -public class ReportDatasourceSaveReqVO { +public class ReportDatasourceSaveReqVO implements Serializable { + @Serial + private static final long serialVersionUID = -1738392826420649562L; + @Schema(description = "数据源id", example = "1") private Long id; @@ -30,9 +37,13 @@ public class ReportDatasourceSaveReqVO { @Schema(description = "数据源名称", example = "测试库") private String name; - @Schema(description = "数据源类型,jdbc、buildin", example = "jdbc") + @Schema(description = "数据源类型,jdbc、buildin、spring", example = "jdbc") + @ReportDatasourceTypeInEnum private String type; + @Schema(description = "spring型数据源id") + private String beanId; + @Schema(description = "数据源驱动类",example = "com.mysql.cj.jdbc.Driver") private String driver; @@ -49,5 +60,5 @@ public class ReportDatasourceSaveReqVO { private String remark; @Schema(description = "数据集") - private List datasets; + private List datasets = new ArrayList<>(); } diff --git a/cf-module-report/cf-module-report-biz/src/main/java/com/cf/imes/module/report/controller/admin/datasource/vo/ReportDatasourceTestConnReqVO.java b/cf-module-report/cf-module-report-biz/src/main/java/com/cf/imes/module/report/controller/admin/datasource/vo/ReportDatasourceTestConnReqVO.java new file mode 100644 index 000000000..c51e5afe9 --- /dev/null +++ b/cf-module-report/cf-module-report-biz/src/main/java/com/cf/imes/module/report/controller/admin/datasource/vo/ReportDatasourceTestConnReqVO.java @@ -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 parameters = new ArrayList<>(); +} diff --git a/cf-module-report/cf-module-report-biz/src/main/java/com/cf/imes/module/report/controller/admin/template/vo/ReportTemplateSaveReqVO.java b/cf-module-report/cf-module-report-biz/src/main/java/com/cf/imes/module/report/controller/admin/template/vo/ReportTemplateSaveReqVO.java index 9f7bdbfce..375de199c 100644 --- a/cf-module-report/cf-module-report-biz/src/main/java/com/cf/imes/module/report/controller/admin/template/vo/ReportTemplateSaveReqVO.java +++ b/cf-module-report/cf-module-report-biz/src/main/java/com/cf/imes/module/report/controller/admin/template/vo/ReportTemplateSaveReqVO.java @@ -7,6 +7,7 @@ import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; +import java.util.ArrayList; import java.util.List; /** @@ -30,7 +31,7 @@ public class ReportTemplateSaveReqVO { private String content; @Schema(description = "数据源") - private List datasource; + private List datasource = new ArrayList<>(); @Schema(description = "模板类型,0内置、1自定义", example = "1") private Integer type; diff --git a/cf-module-report/cf-module-report-biz/src/main/java/com/cf/imes/module/report/dal/dataobject/datasource/ReportDatasourceDO.java b/cf-module-report/cf-module-report-biz/src/main/java/com/cf/imes/module/report/dal/dataobject/datasource/ReportDatasourceDO.java index 37eecdb03..3beab7a73 100644 --- a/cf-module-report/cf-module-report-biz/src/main/java/com/cf/imes/module/report/dal/dataobject/datasource/ReportDatasourceDO.java +++ b/cf-module-report/cf-module-report-biz/src/main/java/com/cf/imes/module/report/dal/dataobject/datasource/ReportDatasourceDO.java @@ -6,6 +6,7 @@ import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; 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.enums.datasource.ReportDatasourceTypeEnum; import lombok.*; import java.util.List; @@ -39,9 +40,13 @@ public class ReportDatasourceDO extends BaseDO { */ private String name; /** - * 模板类型,jdbc、buildin + * 模板类型,jdbc、buildin、spring */ - private String type; + private ReportDatasourceTypeEnum type; + /** + * spring型数据源id + */ + private String beanId; /** * 数据源驱动类 */ diff --git a/cf-module-report/cf-module-report-biz/src/main/java/com/cf/imes/module/report/enums/datasource/ReportDatasourceTypeEnum.java b/cf-module-report/cf-module-report-biz/src/main/java/com/cf/imes/module/report/enums/datasource/ReportDatasourceTypeEnum.java new file mode 100644 index 000000000..d31672640 --- /dev/null +++ b/cf-module-report/cf-module-report-biz/src/main/java/com/cf/imes/module/report/enums/datasource/ReportDatasourceTypeEnum.java @@ -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; + } +} diff --git a/cf-module-report/cf-module-report-biz/src/main/java/com/cf/imes/module/report/service/datasource/ReportDatasourceService.java b/cf-module-report/cf-module-report-biz/src/main/java/com/cf/imes/module/report/service/datasource/ReportDatasourceService.java index a2ea0a14b..d140b9363 100644 --- a/cf-module-report/cf-module-report-biz/src/main/java/com/cf/imes/module/report/service/datasource/ReportDatasourceService.java +++ b/cf-module-report/cf-module-report-biz/src/main/java/com/cf/imes/module/report/service/datasource/ReportDatasourceService.java @@ -1,12 +1,15 @@ 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.ReportDatasourceReqVO; 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 javax.validation.Valid; import java.util.List; +import java.util.Map; /** * 报表数据源 Service 接口 @@ -67,4 +70,28 @@ public interface ReportDatasourceService { * @return */ List loadBeanMethods(String beanId); + + /** + * 测试数据源连接 + * + * @param reqVO 连接信息请求 + * @return + */ + boolean testConnect(ReportDatasourceTestConnReqVO reqVO); + + /** + * 获取数据源表列表 + * + * @param reqVO 连接信息请求 + * @return + */ + List> getDatasourceTables(ReportDatasourceTestConnReqVO reqVO); + + /** + * 获取数据源表字段 + * + * @param reqVO 连接信息请求 + * @return + */ + List getTableFields(ReportDatasourceTestConnReqVO reqVO); } diff --git a/cf-module-report/cf-module-report-biz/src/main/java/com/cf/imes/module/report/service/datasource/ReportDatasourceServiceImpl.java b/cf-module-report/cf-module-report-biz/src/main/java/com/cf/imes/module/report/service/datasource/ReportDatasourceServiceImpl.java index dad1f6368..7a02e5d22 100644 --- a/cf-module-report/cf-module-report-biz/src/main/java/com/cf/imes/module/report/service/datasource/ReportDatasourceServiceImpl.java +++ b/cf-module-report/cf-module-report-biz/src/main/java/com/cf/imes/module/report/service/datasource/ReportDatasourceServiceImpl.java @@ -1,25 +1,53 @@ package com.cf.imes.module.report.service.datasource; 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.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.ReportDatasourceReqVO; 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.mysql.datasource.ReportDatasourceMapper; 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.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.validation.annotation.Validated; import javax.annotation.Resource; +import javax.sql.DataSource; 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.HashMap; import java.util.List; import java.util.Map; 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; /** @@ -30,6 +58,7 @@ import static com.cf.imes.module.report.enums.ErrorCodeConstants.DATASOURCE_NOT_ */ @Service @Validated +@Slf4j public class ReportDatasourceServiceImpl implements ReportDatasourceService { @Resource private ReportDatasourceMapper datasourceMapper; @@ -84,11 +113,12 @@ public class ReportDatasourceServiceImpl implements ReportDatasourceService { @Override public List getTemplateDatasourceList(ReportDatasourceReqVO reqVO) { - return datasourceMapper.selectList(new LambdaQueryWrapperX() + List reportDatasourceDOS = datasourceMapper.selectList(new LambdaQueryWrapperX() .eqIfPresent(ReportDatasourceDO::getTemplateId, reqVO.getTemplateId()) .likeIfPresent(ReportDatasourceDO::getName, reqVO.getName()) .eqIfPresent(ReportDatasourceDO::getType, reqVO.getType()) .orderByDesc(ReportDatasourceDO::getCreateTime)); + return reportDatasourceDOS; } @Override @@ -133,4 +163,133 @@ public class ReportDatasourceServiceImpl implements ReportDatasourceService { } 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> getDatasourceTables(ReportDatasourceTestConnReqVO reqVO) { + Connection conn = null; + ResultSet rs = null; + List> tables = new ArrayList>(); + 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 table = new HashMap(); + 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 getTableFields(ReportDatasourceTestConnReqVO reqVO) { + Connection conn = null; + String sql = reqVO.getSql(); + List 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 params = new HashMap(); + Map pmap = sqlDatasetDefinition.buildParameters(params); + String sqlForUse = sqlDatasetDefinition.parseSql(pmap); + if (ProcedureUtils.isProcedure(sqlForUse)) { + List 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 fields = new ArrayList<>(); + jdbcTemplate.query(sqlForUse, pmap, (ResultSetExtractor) rs -> { + ResultSetMetaData metadata = rs.getMetaData(); + List 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; + } } diff --git a/cf-module-report/cf-module-report-biz/src/main/java/com/cf/imes/module/report/util/SqlInjectionUtil.java b/cf-module-report/cf-module-report-biz/src/main/java/com/cf/imes/module/report/util/SqlInjectionUtil.java new file mode 100644 index 000000000..82da07248 --- /dev/null +++ b/cf-module-report/cf-module-report-biz/src/main/java/com/cf/imes/module/report/util/SqlInjectionUtil.java @@ -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("'", ""); + } +} diff --git a/cf-module-report/cf-module-report-biz/src/main/java/com/cf/imes/module/report/validation/datasource/ReportDatasourceTypeInEnum.java b/cf-module-report/cf-module-report-biz/src/main/java/com/cf/imes/module/report/validation/datasource/ReportDatasourceTypeInEnum.java new file mode 100644 index 000000000..c3af82df2 --- /dev/null +++ b/cf-module-report/cf-module-report-biz/src/main/java/com/cf/imes/module/report/validation/datasource/ReportDatasourceTypeInEnum.java @@ -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[] payload() default {}; + +} diff --git a/cf-module-report/cf-module-report-biz/src/main/java/com/cf/imes/module/report/validation/datasource/ReportDatasourceTypeInEnumValidator.java b/cf-module-report/cf-module-report-biz/src/main/java/com/cf/imes/module/report/validation/datasource/ReportDatasourceTypeInEnumValidator.java new file mode 100644 index 000000000..76d858956 --- /dev/null +++ b/cf-module-report/cf-module-report-biz/src/main/java/com/cf/imes/module/report/validation/datasource/ReportDatasourceTypeInEnumValidator.java @@ -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 { + + @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; + } + } + } + +} + diff --git a/cf-module-report/cf-module-report-biz/src/main/resources/lib/ureport-core-2.3.0-SNAPSHOT.jar b/cf-module-report/cf-module-report-biz/src/main/resources/lib/ureport-core-2.3.0-SNAPSHOT.jar new file mode 100644 index 000000000..89e49ecad Binary files /dev/null and b/cf-module-report/cf-module-report-biz/src/main/resources/lib/ureport-core-2.3.0-SNAPSHOT.jar differ diff --git a/cf-module-report/cf-module-report-biz/src/test/java/com/cf/imes/module/report/service/template/ReportCommonServiceImplTest.java b/cf-module-report/cf-module-report-biz/src/test/java/com/cf/imes/module/report/service/template/ReportCommonServiceImplTest.java index b38f44f96..af4954b58 100644 --- a/cf-module-report/cf-module-report-biz/src/test/java/com/cf/imes/module/report/service/template/ReportCommonServiceImplTest.java +++ b/cf-module-report/cf-module-report-biz/src/test/java/com/cf/imes/module/report/service/template/ReportCommonServiceImplTest.java @@ -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.datasource.vo.ReportDatasourceSaveReqVO; 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 java.util.ArrayList; @@ -31,6 +32,7 @@ public abstract class ReportCommonServiceImplTest { .content(JsonUtil.zipString(TEMPLATE)) .type(ReportTemplateTypeEnum.SYSTEM.getType()) .remark(RandomUtils.randomString()) + .datasource(new ArrayList<>()) .build(); } @@ -40,12 +42,13 @@ public abstract class ReportCommonServiceImplTest { .id(id) .name("单元测试数据源") .templateId(reportId) - .type("jdbc") + .type(ReportDatasourceTypeEnum.SPRING.getDesc()) .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") .username("root") .password("root") .remark(RandomUtils.randomString()) + .datasets(new ArrayList<>()) .build(); } diff --git a/cf-module-report/cf-module-report-biz/src/test/java/com/cf/imes/module/report/service/template/ReportDatasetServiceImplTest.java b/cf-module-report/cf-module-report-biz/src/test/java/com/cf/imes/module/report/service/template/ReportDatasetServiceImplTest.java index bdf1d5c86..336d2ccf9 100644 --- a/cf-module-report/cf-module-report-biz/src/test/java/com/cf/imes/module/report/service/template/ReportDatasetServiceImplTest.java +++ b/cf-module-report/cf-module-report-biz/src/test/java/com/cf/imes/module/report/service/template/ReportDatasetServiceImplTest.java @@ -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.ReportDatasetSaveReqVO; 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.datasource.ReportDatasourceDO; import com.cf.imes.module.report.dal.dataobject.template.ReportTemplateDO; diff --git a/cf-module-report/cf-module-report-biz/src/test/java/com/cf/imes/module/report/service/template/ReportDatasourceServiceImplTest.java b/cf-module-report/cf-module-report-biz/src/test/java/com/cf/imes/module/report/service/template/ReportDatasourceServiceImplTest.java index 89716203e..c9148c430 100644 --- a/cf-module-report/cf-module-report-biz/src/test/java/com/cf/imes/module/report/service/template/ReportDatasourceServiceImplTest.java +++ b/cf-module-report/cf-module-report-biz/src/test/java/com/cf/imes/module/report/service/template/ReportDatasourceServiceImplTest.java @@ -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.dal.dataobject.datasource.ReportDatasourceDO; 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 org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; @@ -68,7 +68,7 @@ public class ReportDatasourceServiceImplTest extends ReportCommonServiceImplTest // 列表查询参数准备 ReportDatasourceReqVO reqVO = new ReportDatasourceReqVO(); reqVO.setTemplateId(templateId); - reqVO.setType(ReportTemplateTypeEnum.SYSTEM.getType()); + reqVO.setType(ReportDatasourceTypeEnum.JDBC.getCode()); reqVO.setName(updateReqVO.getName()); List templateDatasourceList = reportDatasourceService.getTemplateDatasourceList(reqVO);