初始化提交

This commit is contained in:
YangJ
2024-03-20 09:42:17 +08:00
commit 72f30209cf
3705 changed files with 285827 additions and 0 deletions

View File

@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<groupId>cn.iocoder.boot</groupId>
<artifactId>yudao</artifactId>
<version>${revision}</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<modules>
<module>yudao-module-report-api</module>
<module>yudao-module-report-biz</module>
</modules>
<artifactId>yudao-module-report</artifactId>
<packaging>pom</packaging>
<name>${project.artifactId}</name>
<description>
report 模块,主要实现数据可视化报表等功能。
</description>
</project>

View File

@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<groupId>cn.iocoder.boot</groupId>
<artifactId>yudao-module-report</artifactId>
<version>${revision}</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>yudao-module-report-api</artifactId>
<packaging>jar</packaging>
<name>${project.artifactId}</name>
<description>
report 模块 API暴露给其它模块调用
</description>
<dependencies>
<dependency>
<groupId>cn.iocoder.boot</groupId>
<artifactId>yudao-common</artifactId>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,4 @@
/**
* 占位,避免 api 目录无文件时git 无法提交
*/
package cn.iocoder.yudao.module.report.api;

View File

@ -0,0 +1,19 @@
package cn.iocoder.yudao.module.report.enums;
import cn.iocoder.yudao.framework.common.exception.ErrorCode;
/**
* Report 错误码枚举类
*
* report 系统,使用 1-003-000-000 段
*/
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 报表数据源不存在");
}

View File

@ -0,0 +1,88 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<groupId>cn.iocoder.boot</groupId>
<artifactId>yudao-module-report</artifactId>
<version>${revision}</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>yudao-module-report-biz</artifactId>
<packaging>jar</packaging>
<name>${project.artifactId}</name>
<description>
report 模块,主要实现数据可视化报表等功能:
1. 基于「积木报表」实现,打印设计、报表设计、图形设计、大屏设计等。
</description>
<dependencies>
<dependency>
<groupId>cn.iocoder.boot</groupId>
<artifactId>yudao-module-report-api</artifactId>
<version>${revision}</version>
</dependency>
<dependency>
<groupId>cn.iocoder.boot</groupId>
<artifactId>yudao-module-system-api</artifactId>
<version>${revision}</version>
</dependency>
<!-- 业务组件 -->
<dependency>
<groupId>cn.iocoder.boot</groupId>
<artifactId>yudao-spring-boot-starter-biz-operatelog</artifactId>
</dependency>
<dependency>
<groupId>cn.iocoder.boot</groupId>
<artifactId>yudao-spring-boot-starter-biz-tenant</artifactId>
</dependency>
<!-- Web 相关 -->
<dependency>
<groupId>cn.iocoder.boot</groupId>
<artifactId>yudao-spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>cn.iocoder.boot</groupId>
<artifactId>yudao-spring-boot-starter-security</artifactId>
</dependency>
<!-- DB 相关 -->
<dependency>
<groupId>cn.iocoder.boot</groupId>
<artifactId>yudao-spring-boot-starter-mybatis</artifactId>
</dependency>
<!-- Test 测试相关 -->
<dependency>
<groupId>cn.iocoder.boot</groupId>
<artifactId>yudao-spring-boot-starter-test</artifactId>
</dependency>
<!-- 积木报表-->
<dependency>
<groupId>org.jeecgframework.jimureport</groupId>
<artifactId>jimureport-spring-boot-starter</artifactId>
</dependency>
<!-- 单独依赖升级版本解决低版本validator失败问题 -->
<dependency>
<groupId>xerces</groupId>
<artifactId>xercesImpl</artifactId>
</dependency>
<dependency>
<groupId>com.bstek.ureport</groupId>
<artifactId>ureport2-console</artifactId>
</dependency>
<dependency>
<groupId>cn.iocoder.boot</groupId>
<artifactId>yudao-spring-boot-starter-excel</artifactId>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1 @@
package cn.iocoder.yudao.module.report.controller.admin.ajreport;

View File

@ -0,0 +1,66 @@
package cn.iocoder.yudao.module.report.controller.admin.goview;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.RandomUtil;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog;
import cn.iocoder.yudao.module.report.controller.admin.goview.vo.data.GoViewDataGetBySqlReqVO;
import cn.iocoder.yudao.module.report.controller.admin.goview.vo.data.GoViewDataRespVO;
import cn.iocoder.yudao.module.report.service.goview.GoViewDataService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import javax.validation.Valid;
import java.util.*;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
@Tag(name = "管理后台 - GoView 数据", description = "提供 SQL、HTTP 等数据查询的能力")
@RestController
@RequestMapping("/report/go-view/data")
@Validated
public class GoViewDataController {
@Resource
private GoViewDataService goViewDataService;
@RequestMapping("/get-by-sql")
@Operation(summary = "使用 SQL 查询数据")
@PreAuthorize("@ss.hasPermission('report:go-view-data:get-by-sql')")
@OperateLog(enable = false) // 不记录操作日志,因为不需要
public CommonResult<GoViewDataRespVO> getDataBySQL(@Valid @RequestBody GoViewDataGetBySqlReqVO reqVO) {
return success(goViewDataService.getDataBySQL(reqVO.getSql()));
}
@RequestMapping("/get-by-http")
@Operation(summary = "使用 HTTP 查询数据", description = "这个只是示例接口,实际应该每个查询,都要写一个接口")
@PreAuthorize("@ss.hasPermission('report:go-view-data:get-by-http')")
@OperateLog(enable = false) // 不记录操作日志,因为不需要
public CommonResult<GoViewDataRespVO> getDataByHttp(
@RequestParam(required = false) Map<String, String> params,
@RequestBody(required = false) String body) { // params、body 按照需要去接收,这里仅仅是示例
GoViewDataRespVO respVO = new GoViewDataRespVO();
// 1. 数据维度
respVO.setDimensions(Arrays.asList("日期", "PV", "UV")); // PV 是每天访问次数UV 是每天访问人数
// 2. 明细数据列表
// 目前通过随机的方式生成。一般来说,这里你可以写逻辑来实现数据的返回
respVO.setSource(new LinkedList<>());
for (int i = 1; i <= 12; i++) {
String date = "2021-" + (i < 10 ? "0" + i : i);
Integer pv = RandomUtil.randomInt(1000, 10000);
Integer uv = RandomUtil.randomInt(100, 1000);
respVO.getSource().add(MapUtil.<String, Object>builder().put("日期", date)
.put("PV", pv).put("UV", uv).build());
}
return success(respVO);
}
}

View File

@ -0,0 +1,77 @@
package cn.iocoder.yudao.module.report.controller.admin.goview;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.report.controller.admin.goview.vo.project.GoViewProjectCreateReqVO;
import cn.iocoder.yudao.module.report.controller.admin.goview.vo.project.GoViewProjectRespVO;
import cn.iocoder.yudao.module.report.controller.admin.goview.vo.project.GoViewProjectUpdateReqVO;
import cn.iocoder.yudao.module.report.convert.goview.GoViewProjectConvert;
import cn.iocoder.yudao.module.report.dal.dataobject.goview.GoViewProjectDO;
import cn.iocoder.yudao.module.report.service.goview.GoViewProjectService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import javax.validation.Valid;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
@Tag(name = "管理后台 - GoView 项目")
@RestController
@RequestMapping("/report/go-view/project")
@Validated
public class GoViewProjectController {
@Resource
private GoViewProjectService goViewProjectService;
@PostMapping("/create")
@Operation(summary = "创建项目")
@PreAuthorize("@ss.hasPermission('report:go-view-project:create')")
public CommonResult<Long> createProject(@Valid @RequestBody GoViewProjectCreateReqVO createReqVO) {
return success(goViewProjectService.createProject(createReqVO));
}
@PutMapping("/update")
@Operation(summary = "更新项目")
@PreAuthorize("@ss.hasPermission('report:go-view-project:update')")
public CommonResult<Boolean> updateProject(@Valid @RequestBody GoViewProjectUpdateReqVO updateReqVO) {
goViewProjectService.updateProject(updateReqVO);
return success(true);
}
@DeleteMapping("/delete")
@Operation(summary = "删除 GoView 项目")
@Parameter(name = "id", description = "编号", required = true, example = "1024")
@PreAuthorize("@ss.hasPermission('report:go-view-project:delete')")
public CommonResult<Boolean> deleteProject(@RequestParam("id") Long id) {
goViewProjectService.deleteProject(id);
return success(true);
}
@GetMapping("/get")
@Operation(summary = "获得项目")
@Parameter(name = "id", description = "编号", required = true, example = "1024")
@PreAuthorize("@ss.hasPermission('report:go-view-project:query')")
public CommonResult<GoViewProjectRespVO> getProject(@RequestParam("id") Long id) {
GoViewProjectDO project = goViewProjectService.getProject(id);
return success(GoViewProjectConvert.INSTANCE.convert(project));
}
@GetMapping("/my-page")
@Operation(summary = "获得我的项目分页")
@PreAuthorize("@ss.hasPermission('report:go-view-project:query')")
public CommonResult<PageResult<GoViewProjectRespVO>> getMyProjectPage(@Valid PageParam pageVO) {
PageResult<GoViewProjectDO> pageResult = goViewProjectService.getMyProjectPage(
pageVO, getLoginUserId());
return success(GoViewProjectConvert.INSTANCE.convertPage(pageResult));
}
}

View File

@ -0,0 +1,16 @@
package cn.iocoder.yudao.module.report.controller.admin.goview.vo.data;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import javax.validation.constraints.NotEmpty;
@Schema(description = "管理后台 - GoView 使用 SQL 查询数据 Request VO")
@Data
public class GoViewDataGetBySqlReqVO {
@Schema(description = "SQL 语句", requiredMode = Schema.RequiredMode.REQUIRED, example = "SELECT * FROM user")
@NotEmpty(message = "SQL 语句不能为空")
private String sql;
}

View File

@ -0,0 +1,19 @@
package cn.iocoder.yudao.module.report.controller.admin.goview.vo.data;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.util.List;
import java.util.Map;
@Schema(description = "管理后台 - GoView 数据 Response VO")
@Data
public class GoViewDataRespVO {
@Schema(description = "数据维度", requiredMode = Schema.RequiredMode.REQUIRED, example = "['product', 'data1', 'data2']")
private List<String> dimensions;
@Schema(description = "数据明细列表", requiredMode = Schema.RequiredMode.REQUIRED)
private List<Map<String, Object>> source;
}

View File

@ -0,0 +1,15 @@
package cn.iocoder.yudao.module.report.controller.admin.goview.vo.project;
import lombok.*;
import io.swagger.v3.oas.annotations.media.Schema;
import javax.validation.constraints.*;
@Schema(description = "管理后台 - GoView 项目创建 Request VO")
@Data
public class GoViewProjectCreateReqVO {
@Schema(description = "项目名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "王五")
@NotEmpty(message = "项目名称不能为空")
private String name;
}

View File

@ -0,0 +1,36 @@
package cn.iocoder.yudao.module.report.controller.admin.goview.vo.project;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.*;
import java.time.LocalDateTime;
@Schema(description = "管理后台 - GoView 项目 Response VO")
@Data
public class GoViewProjectRespVO {
@Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "18993")
private Long id;
@Schema(description = "项目名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "王五")
private String name;
@Schema(description = "发布状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
private Integer status;
@Schema(description = "报表内容") // JSON 格式
private String content;
@Schema(description = "预览图片 URL", example = "https://www.iocoder.cn")
private String picUrl;
@Schema(description = "项目备注", example = "你猜")
private String remark;
@Schema(description = "创建人编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
private String creator;
@Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
private LocalDateTime createTime;
}

View File

@ -0,0 +1,34 @@
package cn.iocoder.yudao.module.report.controller.admin.goview.vo.project;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.common.validation.InEnum;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.*;
import javax.validation.constraints.*;
@Schema(description = "管理后台 - GoView 项目更新 Request VO")
@Data
public class GoViewProjectUpdateReqVO {
@Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "18993")
@NotNull(message = "编号不能为空")
private Long id;
@Schema(description = "项目名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "王五")
private String name;
@Schema(description = "发布状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
@InEnum(value = CommonStatusEnum.class, message = "发布状态必须是 {value}")
private Integer status;
@Schema(description = "报表内容") // JSON 格式
private String content;
@Schema(description = "预览图片 URL", example = "https://www.iocoder.cn")
private String picUrl;
@Schema(description = "项目备注", example = "你猜")
private String remark;
}

View File

@ -0,0 +1,93 @@
package cn.iocoder.yudao.module.report.controller.admin.ureport;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog;
import cn.iocoder.yudao.module.report.controller.admin.ureport.vo.UReportDataPageReqVO;
import cn.iocoder.yudao.module.report.controller.admin.ureport.vo.UReportDataRespVO;
import cn.iocoder.yudao.module.report.controller.admin.ureport.vo.UReportDataSaveReqVO;
import cn.iocoder.yudao.module.report.dal.dataobject.ureport.UReportDataDO;
import cn.iocoder.yudao.module.report.service.ureport.UReportDataService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import javax.validation.Valid;
import java.io.IOException;
import java.util.List;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
import static cn.iocoder.yudao.framework.operatelog.core.enums.OperateTypeEnum.EXPORT;
@Tag(name = "管理后台 - Ureport2 报表")
@RestController
@RequestMapping("/report/ureport-data")
@Validated
public class UReportDataController {
@Resource
private UReportDataService uReportDataService;
@PostMapping("/create")
@Operation(summary = "创建 Ureport2 报表")
@PreAuthorize("@ss.hasPermission('report:ureport-data:create')")
public CommonResult<Long> createUReportData(@Valid @RequestBody UReportDataSaveReqVO createReqVO) {
return success(uReportDataService.createUReportData(createReqVO));
}
@PutMapping("/update")
@Operation(summary = "更新 Ureport2 报表")
@PreAuthorize("@ss.hasPermission('report:ureport-data:update')")
public CommonResult<Boolean> updateUReportData(@Valid @RequestBody UReportDataSaveReqVO updateReqVO) {
uReportDataService.updateUReportData(updateReqVO);
return success(true);
}
@DeleteMapping("/delete")
@Operation(summary = "删除 Ureport2 报表")
@Parameter(name = "id", description = "编号", required = true)
@PreAuthorize("@ss.hasPermission('report:ureport-data:delete')")
public CommonResult<Boolean> deleteUReportData(@RequestParam("id") Long id) {
uReportDataService.deleteUReportData(id);
return success(true);
}
@GetMapping("/get")
@Operation(summary = "获得Ureport2报表")
@Parameter(name = "id", description = "编号", required = true, example = "1024")
@PreAuthorize("@ss.hasPermission('report:ureport-data:query')")
public CommonResult<UReportDataRespVO> getUReportData(@RequestParam("id") Long id) {
UReportDataDO uReportData = uReportDataService.getUReportData(id);
return success(BeanUtils.toBean(uReportData, UReportDataRespVO.class));
}
@GetMapping("/page")
@Operation(summary = "获得Ureport2报表分页")
@PreAuthorize("@ss.hasPermission('report:ureport-data:query')")
public CommonResult<PageResult<UReportDataRespVO>> getUReportDataPage(@Valid UReportDataPageReqVO pageReqVO) {
PageResult<UReportDataDO> pageResult = uReportDataService.getUReportDataPage(pageReqVO);
return success(BeanUtils.toBean(pageResult, UReportDataRespVO.class));
}
@GetMapping("/export-excel")
@Operation(summary = "导出 Ureport2 报表 Excel")
@PreAuthorize("@ss.hasPermission('report:ureport-data:export')")
@OperateLog(type = EXPORT)
public void exportUReportDataExcel(@Valid UReportDataPageReqVO pageReqVO,
HttpServletResponse response) throws IOException {
pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE);
List<UReportDataDO> list = uReportDataService.getUReportDataPage(pageReqVO).getList();
// 导出 Excel
ExcelUtils.write(response, "Ureport2 报表.xls", "数据", UReportDataRespVO.class,
BeanUtils.toBean(list, UReportDataRespVO.class));
}
}

View File

@ -0,0 +1,33 @@
package cn.iocoder.yudao.module.report.controller.admin.ureport.vo;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDateTime;
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
@Schema(description = "管理后台 - Ureport2 报表分页 Request VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class UReportDataPageReqVO extends PageParam {
@Schema(description = "文件名称", example = "李四")
private String name;
@Schema(description = "状态", example = "1")
private Integer status;
@Schema(description = "备注", example = "你猜")
private String remark;
@Schema(description = "创建时间")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
private LocalDateTime[] createTime;
}

View File

@ -0,0 +1,43 @@
package cn.iocoder.yudao.module.report.controller.admin.ureport.vo;
import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
import cn.iocoder.yudao.module.system.enums.DictTypeConstants;
import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
import com.alibaba.excel.annotation.ExcelProperty;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.time.LocalDateTime;
@Schema(description = "管理后台 - Ureport2 报表 Response VO")
@Data
@ExcelIgnoreUnannotated
public class UReportDataRespVO {
@Schema(description = "ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "26175")
@ExcelProperty("ID")
private Long id;
@Schema(description = "文件名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "李四")
@ExcelProperty("文件名称")
private String name;
@Schema(description = "状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
@ExcelProperty(value = "状态", converter = DictConvert.class)
@DictFormat(DictTypeConstants.COMMON_STATUS)
private Integer status;
@Schema(description = "文件内容")
@ExcelProperty("文件内容")
private String content;
@Schema(description = "备注", example = "你猜")
@ExcelProperty("备注")
private String remark;
@Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
@ExcelProperty("创建时间")
private LocalDateTime createTime;
}

View File

@ -0,0 +1,33 @@
package cn.iocoder.yudao.module.report.controller.admin.ureport.vo;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.common.validation.InEnum;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
@Schema(description = "管理后台 - Ureport2 报表新增/修改 Request VO")
@Data
public class UReportDataSaveReqVO {
@Schema(description = "ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "26175")
private Long id;
@Schema(description = "文件名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "李四")
@NotEmpty(message = "文件名称不能为空")
private String name;
@Schema(description = "状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
@NotNull(message = "状态不能为空")
@InEnum(CommonStatusEnum.class)
private Integer status;
@Schema(description = "文件内容")
private String content;
@Schema(description = "备注", example = "你猜")
private String remark;
}

View File

@ -0,0 +1,6 @@
/**
* 提供 RESTful API 给前端:
* 1. admin 包:提供给管理后台 yudao-ui-admin 前端项目
* 2. app 包:提供给用户 APP yudao-ui-app 前端项目,它的 Controller 和 VO 都要添加 App 前缀,用于和管理后台进行区分
*/
package cn.iocoder.yudao.module.report.controller;

View File

@ -0,0 +1,4 @@
/**
* TODO 占位,后续删除
*/
package cn.iocoder.yudao.module.report.convert.ajreport;

View File

@ -0,0 +1,24 @@
package cn.iocoder.yudao.module.report.convert.goview;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.report.controller.admin.goview.vo.project.GoViewProjectCreateReqVO;
import cn.iocoder.yudao.module.report.controller.admin.goview.vo.project.GoViewProjectRespVO;
import cn.iocoder.yudao.module.report.controller.admin.goview.vo.project.GoViewProjectUpdateReqVO;
import cn.iocoder.yudao.module.report.dal.dataobject.goview.GoViewProjectDO;
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;
@Mapper
public interface GoViewProjectConvert {
GoViewProjectConvert INSTANCE = Mappers.getMapper(GoViewProjectConvert.class);
GoViewProjectDO convert(GoViewProjectCreateReqVO bean);
GoViewProjectDO convert(GoViewProjectUpdateReqVO bean);
GoViewProjectRespVO convert(GoViewProjectDO bean);
PageResult<GoViewProjectRespVO> convertPage(PageResult<GoViewProjectDO> page);
}

View File

@ -0,0 +1,4 @@
/**
* TODO 芋艿:占位,待删除
*/
package cn.iocoder.yudao.module.report.dal.dataobject.ajreport;

View File

@ -0,0 +1,57 @@
package cn.iocoder.yudao.module.report.dal.dataobject.goview;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
import com.baomidou.mybatisplus.annotation.KeySequence;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.*;
/**
* GoView 项目表
*
* 每个大屏图标,对应一个项目
*
* @author 芋道源码
*/
@TableName(value = "report_go_view_project", autoResultMap = true) // 由于 SQL Server 的 system_user 是关键字,所以使用 system_users
@KeySequence("report_go_view_project_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
@Data
@EqualsAndHashCode(callSuper = true)
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class GoViewProjectDO extends BaseDO {
/**
* 编号,数据库自增
*/
@TableId
private Long id;
/**
* 项目名称
*/
private String name;
/**
* 预览图片 URL
*/
private String picUrl;
/**
* 报表内容
*
* JSON 配置,使用字符串存储
*/
private String content;
/**
* 发布状态
*
* 0 - 已发布
* 1 - 未发布
*
* 枚举 {@link cn.iocoder.yudao.framework.common.enums.CommonStatusEnum}
*/
private Integer status;
/**
* 项目备注
*/
private String remark;
}

View File

@ -0,0 +1,50 @@
package cn.iocoder.yudao.module.report.dal.dataobject.ureport;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
import com.baomidou.mybatisplus.annotation.KeySequence;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.*;
// TODO @赤焰:这个是不是可以支持多租户?
/**
* Ureport2 报表 DO
*
* @author 芋道源码
*/
@TableName("report_ureport_data")
@KeySequence("report_ureport_data_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class UReportDataDO extends BaseDO {
/**
* ID
*/
@TableId
private Long id;
/**
* 文件名称
*/
private String name;
/**
* 状态
*
* 枚举 {@link CommonStatusEnum#getStatus()}
*/
private Integer status;
/**
* 文件内容
*/
private String content;
/**
* 备注
*/
private String remark;
}

View File

@ -0,0 +1,4 @@
/**
* TODO 芋艿:占位,待删除
*/
package cn.iocoder.yudao.module.report.dal.mysql.ajreport;

View File

@ -0,0 +1,19 @@
package cn.iocoder.yudao.module.report.dal.mysql.goview;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.module.report.dal.dataobject.goview.GoViewProjectDO;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface GoViewProjectMapper extends BaseMapperX<GoViewProjectDO> {
default PageResult<GoViewProjectDO> selectPage(PageParam reqVO, Long userId) {
return selectPage(reqVO, new LambdaQueryWrapperX<GoViewProjectDO>()
.eq(GoViewProjectDO::getCreator, userId)
.orderByDesc(GoViewProjectDO::getId));
}
}

View File

@ -0,0 +1,44 @@
package cn.iocoder.yudao.module.report.dal.mysql.ureport;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.module.report.controller.admin.ureport.vo.UReportDataPageReqVO;
import cn.iocoder.yudao.module.report.dal.dataobject.ureport.UReportDataDO;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
/**
* Ureport2报表 Mapper
*
* @author 芋道源码
*/
@Mapper
public interface UReportDataMapper extends BaseMapperX<UReportDataDO> {
default PageResult<UReportDataDO> selectPage(UReportDataPageReqVO reqVO) {
return selectPage(reqVO, new LambdaQueryWrapperX<UReportDataDO>()
.likeIfPresent(UReportDataDO::getName, reqVO.getName())
.eqIfPresent(UReportDataDO::getStatus, reqVO.getStatus())
.eqIfPresent(UReportDataDO::getRemark, reqVO.getRemark())
.betweenIfPresent(UReportDataDO::getCreateTime, reqVO.getCreateTime())
.orderByDesc(UReportDataDO::getId));
}
default List<UReportDataDO> selectListByName(String name) {
return selectList(new LambdaQueryWrapperX<UReportDataDO>()
.eqIfPresent(UReportDataDO::getName,name));
}
default UReportDataDO selectByName(String name){
return selectOne(new LambdaQueryWrapperX<UReportDataDO>()
.eqIfPresent(UReportDataDO::getName,name));
}
default int deleteByName(String name) {
return delete(new LambdaQueryWrapperX<UReportDataDO>()
.eqIfPresent(UReportDataDO::getName,name));
}
}

View File

@ -0,0 +1,26 @@
package cn.iocoder.yudao.module.report.framework.jmreport.config;
import cn.iocoder.yudao.framework.security.config.SecurityProperties;
import cn.iocoder.yudao.module.system.api.oauth2.OAuth2TokenApi;
import cn.iocoder.yudao.module.report.framework.jmreport.core.service.JmReportTokenServiceImpl;
import org.jeecg.modules.jmreport.api.JmReportTokenServiceI;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
/**
* 积木报表的配置类
*
* @author 芋道源码
*/
@Configuration(proxyBeanMethods = false)
@ComponentScan(basePackages = "org.jeecg.modules.jmreport") // 扫描积木报表的包
public class JmReportConfiguration {
@Bean
@SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection")
public JmReportTokenServiceI jmReportTokenService(OAuth2TokenApi oAuth2TokenApi, SecurityProperties securityProperties) {
return new JmReportTokenServiceImpl(oAuth2TokenApi, securityProperties);
}
}

View File

@ -0,0 +1,148 @@
package cn.iocoder.yudao.module.report.framework.jmreport.core.service;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.exception.ServiceException;
import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils;
import cn.iocoder.yudao.framework.security.config.SecurityProperties;
import cn.iocoder.yudao.framework.security.core.LoginUser;
import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils;
import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder;
import cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils;
import cn.iocoder.yudao.module.system.api.oauth2.OAuth2TokenApi;
import cn.iocoder.yudao.module.system.api.oauth2.dto.OAuth2AccessTokenCheckRespDTO;
import cn.iocoder.yudao.module.system.api.permission.RoleApi;
import lombok.RequiredArgsConstructor;
import org.jeecg.modules.jmreport.api.JmReportTokenServiceI;
import org.springframework.http.HttpHeaders;
import javax.servlet.http.HttpServletRequest;
import java.util.Objects;
/**
* {@link JmReportTokenServiceI} 实现类,提供积木报表的 Token 校验、用户信息的查询等功能
*
* @author 随心
*/
@RequiredArgsConstructor
public class JmReportTokenServiceImpl implements JmReportTokenServiceI {
/**
* 积木 token head 头
*/
private static final String JM_TOKEN_HEADER = "X-Access-Token";
/**
* auth 相关格式
*/
private static final String AUTHORIZATION_FORMAT = SecurityFrameworkUtils.AUTHORIZATION_BEARER + " %s";
private final OAuth2TokenApi oauth2TokenApi;
private final SecurityProperties securityProperties;
/**
* 自定义 API 数据集appian自定义 Header解决 Token 传递。
* 参考 <a href="http://report.jeecg.com/2222224">api数据集token机制详解</a> 文档
*
* @return 新 head
*/
@Override
public HttpHeaders customApiHeader() {
// 读取积木标标系统的 token
HttpServletRequest request = ServletUtils.getRequest();
String token = request.getHeader(JM_TOKEN_HEADER);
// 设置到 yudao 系统的 token
HttpHeaders headers = new HttpHeaders();
headers.add(securityProperties.getTokenHeader(), String.format(AUTHORIZATION_FORMAT, token));
return headers;
}
/**
* 校验 Token 是否有效,即验证通过
*
* @param token JmReport 前端传递的 token
* @return 是否认证通过
*/
@Override
public Boolean verifyToken(String token) {
Long userId = SecurityFrameworkUtils.getLoginUserId();
if (!Objects.isNull(userId)) {
return true;
}
return buildLoginUserByToken(token) != null;
}
/**
* 获得用户编号
* <p>
* 虽然方法名获得的是 username实际对应到项目中是用户编号
*
* @param token JmReport 前端传递的 token
* @return 用户编号
*/
@Override
public String getUsername(String token) {
Long userId = SecurityFrameworkUtils.getLoginUserId();
if (ObjectUtil.isNotNull(userId)) {
return String.valueOf(userId);
}
LoginUser user = buildLoginUserByToken(token);
return user == null ? null : String.valueOf(user.getId());
}
/**
* 基于 token 构建登录用户
*
* @param token token
* @return 返回 token 对应的用户信息
*/
private LoginUser buildLoginUserByToken(String token) {
if (StrUtil.isEmpty(token)) {
return null;
}
// TODO 如下的实现不算特别优雅,主要咱是不想搞的太复杂,所以参考对应的 Filter 先实现了
// ① 参考 TokenAuthenticationFilter 的认证逻辑Security 的上下文清理,交给 Spring Security 完成)
// 目的:实现基于 JmReport 前端传递的 token实现认证
TenantContextHolder.setIgnore(true); // 忽略租户,保证可查询到 token 信息
LoginUser user = null;
try {
OAuth2AccessTokenCheckRespDTO accessToken = oauth2TokenApi.checkAccessToken(token);
if (accessToken == null) {
return null;
}
user = new LoginUser().setId(accessToken.getUserId()).setUserType(accessToken.getUserType())
.setTenantId(accessToken.getTenantId()).setScopes(accessToken.getScopes());
} catch (ServiceException ignored) {
// do nothing如果报错说明认证失败则返回 false 即可
}
if (user == null) {
return null;
}
SecurityFrameworkUtils.setLoginUser(user, WebFrameworkUtils.getRequest());
// ② 参考 TenantContextWebFilter 实现Tenant 的上下文清理,交给 TenantContextWebFilter 完成)
// 目的:基于 LoginUser 获得到的租户编号,设置到 Tenant 上下文,避免查询数据库时的报错
TenantContextHolder.setIgnore(false);
TenantContextHolder.setTenantId(user.getTenantId());
return user;
}
@Override
public String[] getRoles(String s) {
// 暂时不用实现,因为不用 JmReport 的角色
return null;
}
@Override
public String getTenantId() {
// 补充说明:不能直接通过 TenantContext 获取,因为 jimu 报表前端请求时,没有带上 tenant-id Header
LoginUser loginUser = SecurityFrameworkUtils.getLoginUser();
if (loginUser == null) {
return null;
}
return StrUtil.toStringOrNull(loginUser.getTenantId());
}
}

View File

@ -0,0 +1,4 @@
/**
* 占位,后续会基于 Filter 实现积木报表的认证等功能,替代 {@link cn.iocoder.yudao.module.report.framework.jmreport.core.service.JmReportTokenServiceImpl}
*/
package cn.iocoder.yudao.module.report.framework.jmreport.core.web;

View File

@ -0,0 +1,6 @@
/**
* 属于 report 模块的 framework 封装
*
* @author 芋道源码
*/
package cn.iocoder.yudao.module.report.framework;

View File

@ -0,0 +1,50 @@
package cn.iocoder.yudao.module.report.framework.security.config;
import cn.iocoder.yudao.framework.common.enums.WebFilterOrderEnum;
import cn.iocoder.yudao.framework.security.config.AuthorizeRequestsCustomizer;
import cn.iocoder.yudao.framework.security.config.SecurityProperties;
import cn.iocoder.yudao.module.system.api.oauth2.OAuth2TokenApi;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configurers.ExpressionUrlAuthorizationConfigurer;
import org.springframework.util.StringUtils;
import javax.annotation.Resource;
/**
* Report 模块的 Security 配置
*/
@Configuration("reportSecurityConfiguration")
public class SecurityConfiguration {
@Resource
private OAuth2TokenApi oauth2TokenApi;
@Bean("reportAuthorizeRequestsCustomizer")
public AuthorizeRequestsCustomizer authorizeRequestsCustomizer() {
return new AuthorizeRequestsCustomizer() {
@Override
public void customize(ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry registry) {
registry.antMatchers("/jmreport/**").permitAll(); // 积木报表
registry.antMatchers("/ureport/**").permitAll(); // UReport 报表
}
};
}
/**
* 创建 UReportFilter 过滤器,响应 header 设置 token
*/
/*@Bean
public FilterRegistrationBean<UReportFilter> uReportFilterFilterRegistrationBean() {
FilterRegistrationBean<UReportFilter> registrationBean = new FilterRegistrationBean<>();
registrationBean.setFilter(new UReportFilter(oauth2TokenApi));
registrationBean.setOrder(WebFilterOrderEnum.TRACE_FILTER);
return registrationBean;
}*/
}

View File

@ -0,0 +1,4 @@
/**
* 占位
*/
package cn.iocoder.yudao.module.report.framework.security.core;

View File

@ -0,0 +1,29 @@
package cn.iocoder.yudao.module.report.framework.ureport.config;
import com.bstek.ureport.console.UReportServlet;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ImportResource;
import org.springframework.context.annotation.PropertySource;
import javax.servlet.Servlet;
/**
* UReport2 配置类
*
* @author 赤焰
*/
@Configuration
@ImportResource({"classpath:ureport-console-context.xml"})
@PropertySource(value = {"classpath:ureport.properties"}) // TODO @赤焰:这个可以搞到 application.yaml 里么?
@EnableConfigurationProperties({UReportProperties.class})
public class UReportConfiguration {
@Bean
public ServletRegistrationBean<Servlet> uReportRegistrationBean() {
return new ServletRegistrationBean<>(new UReportServlet(), "/ureport/*");
}
}

View File

@ -0,0 +1,22 @@
package cn.iocoder.yudao.module.report.framework.ureport.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* UReport2 配置类
*
* @author 赤焰
*/
@Data
@ConfigurationProperties(prefix = "ureport.provider.database")
public class UReportProperties {
// TODO @赤焰:每个字段的注释写下哈;
private String name = "数据库文件系统";
private String prefix = "db-";
private boolean disabled = false;
}

View File

@ -0,0 +1,50 @@
package cn.iocoder.yudao.module.report.framework.ureport.core;
import com.bstek.ureport.definition.datasource.BuildinDatasource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.module.report.enums.ErrorCodeConstants.UREPORT_DATABASE_NOT_EXISTS;
/**
* UReport2 内置数据源
*
* @author 赤焰
*/
@Slf4j
@Component
public class UReportDataSource implements BuildinDatasource {
private static final String NAME = "UReportDataSource";
@Resource
private DataSource dataSource;
/**
* @return 数据源名称
*/
@Override
public String name() {
return NAME;
}
/**
* @return 获取连接
*/
@Override
public Connection getConnection() {
try {
return dataSource.getConnection();
} catch (SQLException e) {
log.error("[getConnection][获取连接失败!]", e);
throw exception(UREPORT_DATABASE_NOT_EXISTS);
}
}
}

View File

@ -0,0 +1,110 @@
package cn.iocoder.yudao.module.report.framework.ureport.core;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.common.util.date.DateUtils;
import cn.iocoder.yudao.module.report.controller.admin.ureport.vo.UReportDataSaveReqVO;
import cn.iocoder.yudao.module.report.dal.dataobject.ureport.UReportDataDO;
import cn.iocoder.yudao.module.report.framework.ureport.config.UReportProperties;
import cn.iocoder.yudao.module.report.service.ureport.UReportDataService;
import com.bstek.ureport.provider.report.ReportFile;
import com.bstek.ureport.provider.report.ReportProvider;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.util.Collections;
import java.util.List;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
/**
* 基于数据库的 {@link ReportProvider} 实现类
*
* @author 赤焰
*/
// TODO @赤焰:这个 bean 的注解,交给 UReportConfiguration 搞
@Component
@Slf4j
@Setter
public class UReportDatabaseProvider implements ReportProvider {
@Autowired
private UReportProperties uReportProperties;
@Resource
private UReportDataService uReportDataService;
@Override
public InputStream loadReport(String name) {
uReportDataService.validateUReportDataExists(getCorrectName(name));
UReportDataDO uReportDataDO = uReportDataService.selectOneByName(getCorrectName(name));
String content = uReportDataDO.getContent();
return new ByteArrayInputStream(content.getBytes());
}
@Override
public void deleteReport(String name) {
uReportDataService.deleteByName(getCorrectName(name));
}
@Override
public List<ReportFile> getReportFiles() {
List<UReportDataDO> list = uReportDataService.getReportDataList();
// TODO @赤焰这里不用判空CollectionUtils.convertList 已经处理了哈。
if(CollUtil.isEmpty(list)) {
return Collections.emptyList();
}
return convertList(list, report -> new ReportFile(report.getName(), DateUtils.of(report.getUpdateTime())));
}
@Override
public void saveReport(String name, String content) {
// TODO @赤焰:收到 uReportDataService 里面实现一个 saveUReportData 方法,然后这里调用即可。
name = getCorrectName(name);
UReportDataDO uReportDataDO = uReportDataService.selectOneByName(name);
UReportDataSaveReqVO saveReqVO = new UReportDataSaveReqVO();
if (uReportDataDO == null) {
saveReqVO.setName(name);
saveReqVO.setContent(content);
saveReqVO.setStatus(CommonStatusEnum.ENABLE.getStatus());
uReportDataService.createUReportData(saveReqVO);
} else {
saveReqVO.setId(uReportDataDO.getId());
saveReqVO.setName(name);
saveReqVO.setContent(content);
saveReqVO.setStatus(CommonStatusEnum.ENABLE.getStatus());
uReportDataService.updateUReportData(saveReqVO);
}
}
@Override
public String getName() {
return uReportProperties.getName();
}
@Override
public boolean disabled() {
return uReportProperties.isDisabled();
}
@Override
public String getPrefix() {
return uReportProperties.getPrefix();
}
/**
* 去除存储媒介,获取报表名字
*
* @param name 前端传入的报表带存储媒介的名字
* @return 表名字
*/
private String getCorrectName(String name) {
return StrUtil.removePrefix(name,getPrefix());
}
}

View File

@ -0,0 +1,79 @@
package cn.iocoder.yudao.module.report.framework.ureport.core;
import cn.iocoder.yudao.framework.common.exception.ServiceException;
import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils;
import cn.iocoder.yudao.framework.security.core.LoginUser;
import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils;
import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder;
import cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils;
import cn.iocoder.yudao.module.system.api.oauth2.OAuth2TokenApi;
import cn.iocoder.yudao.module.system.api.oauth2.dto.OAuth2AccessTokenCheckRespDTO;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.filter.OncePerRequestFilter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Map;
/**
* UReport 认证过滤器
* @author 赤焰
*/
@Slf4j
@RequiredArgsConstructor
public class UReportFilter extends OncePerRequestFilter {
private final static String TOKEN = "token";
private final OAuth2TokenApi oauth2TokenApi;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws ServletException, IOException {
if(log.isDebugEnabled()){
log.debug("UReportFilter自定义过滤器");
}
Map<String, String> paramMap = ServletUtils.getParamMap(request);
String requestURI = request.getRequestURI();
boolean contains = requestURI.contains("/ureport");
if (paramMap.containsKey(TOKEN)&&contains) {
String token = request.getParameter(TOKEN);
if(log.isDebugEnabled()){
log.debug("UReportFilter自定义过滤器 token="+token);
}
response.addHeader(TOKEN,token);
TenantContextHolder.setIgnore(true); // 忽略租户,保证可查询到 token 信息
LoginUser user = null;
try {
OAuth2AccessTokenCheckRespDTO accessToken = oauth2TokenApi.checkAccessToken(token);
if (accessToken != null) {
user = new LoginUser().setId(accessToken.getUserId()).setUserType(accessToken.getUserType())
.setTenantId(accessToken.getTenantId()).setScopes(accessToken.getScopes());
if (user != null) {
SecurityFrameworkUtils.setLoginUser(user, WebFrameworkUtils.getRequest());
// ② 参考 TenantContextWebFilter 实现Tenant 的上下文清理,交给 TenantContextWebFilter 完成)
// 目的:基于 LoginUser 获得到的租户编号,设置到 Tenant 上下文,避免查询数据库时的报错
TenantContextHolder.setIgnore(false);
TenantContextHolder.setTenantId(user.getTenantId());
}
}
} catch (ServiceException ignored) {
chain.doFilter(request, response);
}
}
// 继续过滤
chain.doFilter(request, response);
}
}

View File

@ -0,0 +1,7 @@
/**
* ureport2https://github.com/youseries/ureport
*
* ureport2 和 jimurepot 是相同类型的产品,不过停更了,最好发布时间是 2018 年。
* 它们之间的功能对比,可见 https://juejin.cn/post/6939836480269320200 地址
*/
package cn.iocoder.yudao.module.report.framework.ureport;

View File

@ -0,0 +1,9 @@
/**
* report 模块,主要实现数据可视化报表等功能:
* 1. 基于「积木报表」实现打印设计、报表设计、图形设计、大屏设计等。URL 前缀是 /jmreport表名前缀是 jimu_
*
* 由于「积木报表」的大屏设计器需要收费,后续会自研,对应的是:
* 1. Controller URL以 /report/ 开头,避免和其它 Module 冲突
* 2. DataObject 表名:以 report_ 开头,方便在数据库中区分
*/
package cn.iocoder.yudao.module.report;

View File

@ -0,0 +1,4 @@
/**
* TODO 芋艿:占位,待删除
*/
package cn.iocoder.yudao.module.report.service.ajreport;

View File

@ -0,0 +1,20 @@
package cn.iocoder.yudao.module.report.service.goview;
import cn.iocoder.yudao.module.report.controller.admin.goview.vo.data.GoViewDataRespVO;
/**
* GoView 数据 Service 接口
*
* @author 芋道源码
*/
public interface GoViewDataService {
/**
* 使用 SQL 查询数据
*
* @param sql SQL 语句
* @return 数据
*/
GoViewDataRespVO getDataBySQL(String sql);
}

View File

@ -0,0 +1,55 @@
package cn.iocoder.yudao.module.report.service.goview;
import cn.iocoder.yudao.module.report.controller.admin.goview.vo.data.GoViewDataRespVO;
import com.google.common.collect.Maps;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.support.rowset.SqlRowSet;
import org.springframework.jdbc.support.rowset.SqlRowSetMetaData;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
import javax.annotation.Resource;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.Map;
/**
* GoView 数据 Service 实现类
*
* 补充说明:
* 1. 目前默认使用 jdbcTemplate 查询项目配置的数据源。如果你想查询其它数据源,可以新建对应数据源的 jdbcTemplate 来实现。
* 2. 默认数据源是 MySQL 关系数据源,可能数据量比较大的情况下,会比较慢,可以考虑后续使用 Click House 等等。
*
* @author 芋道源码
*/
@Service
@Validated
public class GoViewDataServiceImpl implements GoViewDataService {
@Resource
private JdbcTemplate jdbcTemplate;
@Override
public GoViewDataRespVO getDataBySQL(String sql) {
// 1. 执行查询
SqlRowSet sqlRowSet = jdbcTemplate.queryForRowSet(sql);
// 2. 构建返回结果
GoViewDataRespVO respVO = new GoViewDataRespVO();
// 2.1 解析元数据
SqlRowSetMetaData metaData = sqlRowSet.getMetaData();
String[] columnNames = metaData.getColumnNames();
respVO.setDimensions(Arrays.asList(columnNames));
// 2.2 解析数据明细
respVO.setSource(new LinkedList<>()); // 由于数据量不确认,使用 LinkedList 虽然内存占用大一点,但是不存在扩容复制的问题
while (sqlRowSet.next()) {
Map<String, Object> data = Maps.newHashMapWithExpectedSize(columnNames.length);
for (String columnName : columnNames) {
data.put(columnName, sqlRowSet.getObject(columnName));
}
respVO.getSource().add(data);
}
return respVO;
}
}

View File

@ -0,0 +1,57 @@
package cn.iocoder.yudao.module.report.service.goview;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.report.controller.admin.goview.vo.project.GoViewProjectCreateReqVO;
import cn.iocoder.yudao.module.report.controller.admin.goview.vo.project.GoViewProjectUpdateReqVO;
import cn.iocoder.yudao.module.report.dal.dataobject.goview.GoViewProjectDO;
import javax.validation.Valid;
/**
* GoView 项目 Service 接口
*
* @author 芋道源码
*/
public interface GoViewProjectService {
/**
* 创建项目
*
* @param createReqVO 创建信息
* @return 编号
*/
Long createProject(@Valid GoViewProjectCreateReqVO createReqVO);
/**
* 更新项目
*
* @param updateReqVO 更新信息
*/
void updateProject(@Valid GoViewProjectUpdateReqVO updateReqVO);
/**
* 删除项目
*
* @param id 编号
*/
void deleteProject(Long id);
/**
* 获得项目
*
* @param id 编号
* @return 项目
*/
GoViewProjectDO getProject(Long id);
/**
* 获得我的项目分页
*
* @param pageReqVO 分页查询
* @param userId 用户编号
* @return GoView 项目分页
*/
PageResult<GoViewProjectDO> getMyProjectPage(PageParam pageReqVO, Long userId);
}

View File

@ -0,0 +1,74 @@
package cn.iocoder.yudao.module.report.service.goview;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.report.controller.admin.goview.vo.project.GoViewProjectCreateReqVO;
import cn.iocoder.yudao.module.report.controller.admin.goview.vo.project.GoViewProjectUpdateReqVO;
import cn.iocoder.yudao.module.report.convert.goview.GoViewProjectConvert;
import cn.iocoder.yudao.module.report.dal.dataobject.goview.GoViewProjectDO;
import cn.iocoder.yudao.module.report.dal.mysql.goview.GoViewProjectMapper;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
import javax.annotation.Resource;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.module.report.enums.ErrorCodeConstants.GO_VIEW_PROJECT_NOT_EXISTS;
/**
* GoView 项目 Service 实现类
*
* @author 芋道源码
*/
@Service
@Validated
public class GoViewProjectServiceImpl implements GoViewProjectService {
@Resource
private GoViewProjectMapper goViewProjectMapper;
@Override
public Long createProject(GoViewProjectCreateReqVO createReqVO) {
// 插入
GoViewProjectDO goViewProject = GoViewProjectConvert.INSTANCE.convert(createReqVO)
.setStatus(CommonStatusEnum.DISABLE.getStatus());
goViewProjectMapper.insert(goViewProject);
// 返回
return goViewProject.getId();
}
@Override
public void updateProject(GoViewProjectUpdateReqVO updateReqVO) {
// 校验存在
validateProjectExists(updateReqVO.getId());
// 更新
GoViewProjectDO updateObj = GoViewProjectConvert.INSTANCE.convert(updateReqVO);
goViewProjectMapper.updateById(updateObj);
}
@Override
public void deleteProject(Long id) {
// 校验存在
validateProjectExists(id);
// 删除
goViewProjectMapper.deleteById(id);
}
private void validateProjectExists(Long id) {
if (goViewProjectMapper.selectById(id) == null) {
throw exception(GO_VIEW_PROJECT_NOT_EXISTS);
}
}
@Override
public GoViewProjectDO getProject(Long id) {
return goViewProjectMapper.selectById(id);
}
@Override
public PageResult<GoViewProjectDO> getMyProjectPage(PageParam pageReqVO, Long userId) {
return goViewProjectMapper.selectPage(pageReqVO, userId);
}
}

View File

@ -0,0 +1,89 @@
package cn.iocoder.yudao.module.report.service.ureport;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.report.controller.admin.ureport.vo.UReportDataPageReqVO;
import cn.iocoder.yudao.module.report.controller.admin.ureport.vo.UReportDataSaveReqVO;
import cn.iocoder.yudao.module.report.dal.dataobject.ureport.UReportDataDO;
import javax.validation.Valid;
import java.util.List;
/**
* Ureport2 报表 Service 接口
*
* @author 芋道源码
*/
public interface UReportDataService {
/**
* 创建 Ureport2 报表
*
* @param createReqVO 创建信息
* @return 编号
*/
Long createUReportData(@Valid UReportDataSaveReqVO createReqVO);
/**
* 更新 Ureport2 报表
*
* @param updateReqVO 更新信息
*/
void updateUReportData(@Valid UReportDataSaveReqVO updateReqVO);
/**
* 删除 Ureport2 报表
*
* @param id 编号
*/
void deleteUReportData(Long id);
/**
* 获得 Ureport2 报表
*
* @param id 编号
* @return Ureport2 报表
*/
UReportDataDO getUReportData(Long id);
/**
* 获得 Ureport2 报表分页
*
* @param pageReqVO 分页查询
* @return Ureport2 报表分页
*/
PageResult<UReportDataDO> getUReportDataPage(UReportDataPageReqVO pageReqVO);
// TODO @赤焰:可以不用返回 int。如果不需要哈。
/**
* 根据名称删除报表
*
* @param name 报表名称
* @return
*/
int deleteByName(String name);
// TODO @赤焰:这里直接返回 UReportDataDO 是不是更好?上层业务直接使用啦
/**
* 根据名称校验报表是否存在
*
* @param name 报表名称
*/
void validateUReportDataExists(String name);
// TODO @赤焰:这里方法名改成 getUReportDataByName。select 只用于 mapper
/**
* 根据名称查询报表
*
* @param name 报表名称
* @return Ureport2 报表
*/
UReportDataDO selectOneByName(String name);
/**
* 获取全部报表
*
* @return 全部报表
*/
List<UReportDataDO> getReportDataList();
}

View File

@ -0,0 +1,94 @@
package cn.iocoder.yudao.module.report.service.ureport;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.report.controller.admin.ureport.vo.UReportDataPageReqVO;
import cn.iocoder.yudao.module.report.controller.admin.ureport.vo.UReportDataSaveReqVO;
import cn.iocoder.yudao.module.report.dal.dataobject.ureport.UReportDataDO;
import cn.iocoder.yudao.module.report.dal.mysql.ureport.UReportDataMapper;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
import javax.annotation.Resource;
import java.util.List;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.module.report.enums.ErrorCodeConstants.UREPORT_DATA_NOT_EXISTS;
/**
* Ureport2报表 Service 实现类
*
* @author 芋道源码
*/
@Service
@Validated
public class UReportDataServiceImpl implements UReportDataService {
@Resource
private UReportDataMapper uReportDataMapper;
@Override
public Long createUReportData(UReportDataSaveReqVO createReqVO) {
// TODO @赤焰:名字不要重复的校验,要加下
UReportDataDO uReportData = BeanUtils.toBean(createReqVO, UReportDataDO.class);
uReportDataMapper.insert(uReportData);
return uReportData.getId();
}
@Override
public void updateUReportData(UReportDataSaveReqVO updateReqVO) {
// 校验存在
validateUReportDataExists(updateReqVO.getId());
// TODO @赤焰:名字不要重复的校验,要加下
// 更新
UReportDataDO updateObj = BeanUtils.toBean(updateReqVO, UReportDataDO.class);
uReportDataMapper.updateById(updateObj);
}
@Override
public void deleteUReportData(Long id) {
// 校验存在
validateUReportDataExists(id);
// 删除
uReportDataMapper.deleteById(id);
}
private void validateUReportDataExists(Long id) {
if (uReportDataMapper.selectById(id) == null) {
throw exception(UREPORT_DATA_NOT_EXISTS);
}
}
@Override
public void validateUReportDataExists(String name) {
if (uReportDataMapper.selectListByName(name) == null) {
throw exception(UREPORT_DATA_NOT_EXISTS);
}
}
@Override
public UReportDataDO getUReportData(Long id) {
return uReportDataMapper.selectById(id);
}
@Override
public PageResult<UReportDataDO> getUReportDataPage(UReportDataPageReqVO pageReqVO) {
return uReportDataMapper.selectPage(pageReqVO);
}
@Override
public int deleteByName(String name) {
return uReportDataMapper.deleteByName(name);
}
@Override
public UReportDataDO selectOneByName(String name) {
return uReportDataMapper.selectByName(name);
}
@Override
public List<UReportDataDO> getReportDataList() {
return uReportDataMapper.selectList();
}
}

View File

@ -0,0 +1,5 @@
## TODO ????????? application.yaml ?????
ureport.disableHttpSessionReportCache=true
ureport.disableFileProvider=true
ureport.fileStoreDir=/WEB-INF/ureportfiles
ureport.debug=true

View File

@ -0,0 +1,58 @@
package cn.iocoder.yudao.module.report.service.goview;
import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
import cn.iocoder.yudao.module.report.controller.admin.goview.vo.data.GoViewDataRespVO;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.context.annotation.Import;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.support.rowset.SqlRowSet;
import org.springframework.jdbc.support.rowset.SqlRowSetMetaData;
import javax.annotation.Resource;
import java.util.Arrays;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
@Import(GoViewDataServiceImpl.class)
public class GoViewDataServiceImplTest extends BaseDbUnitTest {
@Resource
private GoViewDataServiceImpl goViewDataService;
@MockBean
private JdbcTemplate jdbcTemplate;
@Test
public void testGetDataBySQL() {
// 准备参数
String sql = "SELECT id, name FROM system_users";
// mock 方法
SqlRowSet sqlRowSet = mock(SqlRowSet.class);
when(jdbcTemplate.queryForRowSet(eq(sql))).thenReturn(sqlRowSet);
// mock 元数据
SqlRowSetMetaData metaData = mock(SqlRowSetMetaData.class);
when(sqlRowSet.getMetaData()).thenReturn(metaData);
when(metaData.getColumnNames()).thenReturn(new String[]{"id", "name"});
// mock 数据明细
when(sqlRowSet.next()).thenReturn(true).thenReturn(true).thenReturn(false);
when(sqlRowSet.getObject("id")).thenReturn(1L).thenReturn(2L);
when(sqlRowSet.getObject("name")).thenReturn("芋道源码").thenReturn("芋道");
// 调用
GoViewDataRespVO dataBySQL = goViewDataService.getDataBySQL(sql);
// 断言
assertEquals(Arrays.asList("id", "name"), dataBySQL.getDimensions());
assertEquals(2, dataBySQL.getDimensions().size());
assertEquals(2, dataBySQL.getSource().get(0).size());
assertEquals(1L, dataBySQL.getSource().get(0).get("id"));
assertEquals("芋道源码", dataBySQL.getSource().get(0).get("name"));
assertEquals(2, dataBySQL.getSource().get(1).size());
assertEquals(2L, dataBySQL.getSource().get(1).get("id"));
assertEquals("芋道", dataBySQL.getSource().get(1).get("name"));
}
}

View File

@ -0,0 +1,135 @@
package cn.iocoder.yudao.module.report.service.goview;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
import cn.iocoder.yudao.module.report.controller.admin.goview.vo.project.GoViewProjectCreateReqVO;
import cn.iocoder.yudao.module.report.controller.admin.goview.vo.project.GoViewProjectUpdateReqVO;
import cn.iocoder.yudao.module.report.dal.dataobject.goview.GoViewProjectDO;
import cn.iocoder.yudao.module.report.dal.mysql.goview.GoViewProjectMapper;
import org.junit.jupiter.api.Test;
import org.springframework.context.annotation.Import;
import javax.annotation.Resource;
import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.cloneIgnoreId;
import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals;
import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServiceException;
import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.*;
import static cn.iocoder.yudao.module.report.enums.ErrorCodeConstants.GO_VIEW_PROJECT_NOT_EXISTS;
import static org.junit.jupiter.api.Assertions.*;
/**
* {@link GoViewProjectServiceImpl} 的单元测试类
*
* @author 芋道源码
*/
@Import(GoViewProjectServiceImpl.class)
public class GoViewProjectServiceImplTest extends BaseDbUnitTest {
@Resource
private GoViewProjectServiceImpl goViewProjectService;
@Resource
private GoViewProjectMapper goViewProjectMapper;
@Test
public void testCreateProject_success() {
// 准备参数
GoViewProjectCreateReqVO reqVO = randomPojo(GoViewProjectCreateReqVO.class);
// 调用
Long goViewProjectId = goViewProjectService.createProject(reqVO);
// 断言
assertNotNull(goViewProjectId);
// 校验记录的属性是否正确
GoViewProjectDO goViewProject = goViewProjectMapper.selectById(goViewProjectId);
assertPojoEquals(reqVO, goViewProject);
}
@Test
public void testUpdateProject_success() {
// mock 数据
GoViewProjectDO dbGoViewProject = randomPojo(GoViewProjectDO.class);
goViewProjectMapper.insert(dbGoViewProject);// @Sql: 先插入出一条存在的数据
// 准备参数
GoViewProjectUpdateReqVO reqVO = randomPojo(GoViewProjectUpdateReqVO.class, o -> {
o.setId(dbGoViewProject.getId()); // 设置更新的 ID
o.setStatus(randomCommonStatus());
});
// 调用
goViewProjectService.updateProject(reqVO);
// 校验是否更新正确
GoViewProjectDO goViewProject = goViewProjectMapper.selectById(reqVO.getId()); // 获取最新的
assertPojoEquals(reqVO, goViewProject);
}
@Test
public void testUpdateProject_notExists() {
// 准备参数
GoViewProjectUpdateReqVO reqVO = randomPojo(GoViewProjectUpdateReqVO.class);
// 调用, 并断言异常
assertServiceException(() -> goViewProjectService.updateProject(reqVO), GO_VIEW_PROJECT_NOT_EXISTS);
}
@Test
public void testDeleteProject_success() {
// mock 数据
GoViewProjectDO dbGoViewProject = randomPojo(GoViewProjectDO.class);
goViewProjectMapper.insert(dbGoViewProject);// @Sql: 先插入出一条存在的数据
// 准备参数
Long id = dbGoViewProject.getId();
// 调用
goViewProjectService.deleteProject(id);
// 校验数据不存在了
assertNull(goViewProjectMapper.selectById(id));
}
@Test
public void testDeleteProject_notExists() {
// 准备参数
Long id = randomLongId();
// 调用, 并断言异常
assertServiceException(() -> goViewProjectService.deleteProject(id), GO_VIEW_PROJECT_NOT_EXISTS);
}
@Test
public void testGetProject() {
// mock 数据
GoViewProjectDO dbGoViewProject = randomPojo(GoViewProjectDO.class);
goViewProjectMapper.insert(dbGoViewProject);// @Sql: 先插入出一条存在的数据
// 准备参数
Long id = dbGoViewProject.getId();
// 调用
GoViewProjectDO goViewProject = goViewProjectService.getProject(id);
// 断言
assertPojoEquals(dbGoViewProject, goViewProject);
}
@Test
public void testGetMyGoViewProjectPage() {
// mock 数据
GoViewProjectDO dbGoViewProject = randomPojo(GoViewProjectDO.class, o -> { // 等会查询到
o.setCreator("1");
});
goViewProjectMapper.insert(dbGoViewProject);
// 测试 userId 不匹配
goViewProjectMapper.insert(cloneIgnoreId(dbGoViewProject, o -> o.setCreator("2")));
// 准备参数
PageParam reqVO = new PageParam();
Long userId = 1L;
// 调用
PageResult<GoViewProjectDO> pageResult = goViewProjectService.getMyProjectPage(reqVO, userId);
// 断言
assertEquals(1, pageResult.getTotal());
assertEquals(1, pageResult.getList().size());
assertPojoEquals(dbGoViewProject, pageResult.getList().get(0));
}
}

View File

@ -0,0 +1,136 @@
package cn.iocoder.yudao.module.report.service.ureport;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
import cn.iocoder.yudao.module.report.controller.admin.ureport.vo.UReportDataPageReqVO;
import cn.iocoder.yudao.module.report.controller.admin.ureport.vo.UReportDataSaveReqVO;
import cn.iocoder.yudao.module.report.dal.dataobject.ureport.UReportDataDO;
import cn.iocoder.yudao.module.report.dal.mysql.ureport.UReportDataMapper;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.springframework.context.annotation.Import;
import javax.annotation.Resource;
import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.buildBetweenTime;
import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.cloneIgnoreId;
import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals;
import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServiceException;
import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomLongId;
import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo;
import static cn.iocoder.yudao.module.report.enums.ErrorCodeConstants.UREPORT_DATA_NOT_EXISTS;
import static org.junit.jupiter.api.Assertions.*;
/**
* {@link UReportDataServiceImpl} 的单元测试类
*
* @author 芋道源码
*/
@Disabled // TODO 芋艿:临时禁用,暂时不修复,等重构后解决
@Import(UReportDataServiceImpl.class)
public class UReportDataServiceImplTest extends BaseDbUnitTest {
@Resource
private UReportDataServiceImpl uReportDataService;
@Resource
private UReportDataMapper uReportDataMapper;
@Test
public void testCreateUReportData_success() {
// 准备参数
UReportDataSaveReqVO createReqVO = randomPojo(UReportDataSaveReqVO.class).setId(null);
// 调用
Long uReportDataId = uReportDataService.createUReportData(createReqVO);
// 断言
assertNotNull(uReportDataId);
// 校验记录的属性是否正确
UReportDataDO uReportData = uReportDataMapper.selectById(uReportDataId);
assertPojoEquals(createReqVO, uReportData, "id");
}
@Test
public void testUpdateUReportData_success() {
// mock 数据
UReportDataDO dbUReportData = randomPojo(UReportDataDO.class);
uReportDataMapper.insert(dbUReportData);// @Sql: 先插入出一条存在的数据
// 准备参数
UReportDataSaveReqVO updateReqVO = randomPojo(UReportDataSaveReqVO.class, o -> {
o.setId(dbUReportData.getId()); // 设置更新的 ID
});
// 调用
uReportDataService.updateUReportData(updateReqVO);
// 校验是否更新正确
UReportDataDO uReportData = uReportDataMapper.selectById(updateReqVO.getId()); // 获取最新的
assertPojoEquals(updateReqVO, uReportData);
}
@Test
public void testUpdateUReportData_notExists() {
// 准备参数
UReportDataSaveReqVO updateReqVO = randomPojo(UReportDataSaveReqVO.class);
// 调用, 并断言异常
assertServiceException(() -> uReportDataService.updateUReportData(updateReqVO), UREPORT_DATA_NOT_EXISTS);
}
@Test
public void testDeleteUReportData_success() {
// mock 数据
UReportDataDO dbUReportData = randomPojo(UReportDataDO.class);
uReportDataMapper.insert(dbUReportData);// @Sql: 先插入出一条存在的数据
// 准备参数
Long id = dbUReportData.getId();
// 调用
uReportDataService.deleteUReportData(id);
// 校验数据不存在了
assertNull(uReportDataMapper.selectById(id));
}
@Test
public void testDeleteUReportData_notExists() {
// 准备参数
Long id = randomLongId();
// 调用, 并断言异常
assertServiceException(() -> uReportDataService.deleteUReportData(id), UREPORT_DATA_NOT_EXISTS);
}
@Test
@Disabled // TODO 请修改 null 为需要的值,然后删除 @Disabled 注解
public void testGetUReportDataPage() {
// mock 数据
UReportDataDO dbUReportData = randomPojo(UReportDataDO.class, o -> { // 等会查询到
o.setName(null);
o.setStatus(null);
o.setRemark(null);
o.setCreateTime(null);
});
uReportDataMapper.insert(dbUReportData);
// 测试 name 不匹配
uReportDataMapper.insert(cloneIgnoreId(dbUReportData, o -> o.setName(null)));
// 测试 status 不匹配
uReportDataMapper.insert(cloneIgnoreId(dbUReportData, o -> o.setStatus(null)));
// 测试 remark 不匹配
uReportDataMapper.insert(cloneIgnoreId(dbUReportData, o -> o.setRemark(null)));
// 测试 createTime 不匹配
uReportDataMapper.insert(cloneIgnoreId(dbUReportData, o -> o.setCreateTime(null)));
// 准备参数
UReportDataPageReqVO reqVO = new UReportDataPageReqVO();
reqVO.setName(null);
reqVO.setStatus(null);
reqVO.setRemark(null);
reqVO.setCreateTime(buildBetweenTime(2023, 2, 1, 2023, 2, 28));
// 调用
PageResult<UReportDataDO> pageResult = uReportDataService.getUReportDataPage(reqVO);
// 断言
assertEquals(1, pageResult.getTotal());
assertEquals(1, pageResult.getList().size());
assertPojoEquals(dbUReportData, pageResult.getList().get(0));
}
}

View File

@ -0,0 +1,55 @@
spring:
main:
lazy-initialization: true # 开启懒加载,加快速度
banner-mode: off # 单元测试,禁用 Banner
--- #################### 数据库相关配置 ####################
spring:
# 数据源配置项
datasource:
name: ruoyi-vue-pro
url: jdbc:h2:mem:testdb;MODE=MYSQL;DATABASE_TO_UPPER=false;NON_KEYWORDS=value; # MODE 使用 MySQL 模式DATABASE_TO_UPPER 配置表和字段使用小写
driver-class-name: org.h2.Driver
username: sa
password:
druid:
async-init: true # 单元测试,异步初始化 Druid 连接池,提升启动速度
initial-size: 1 # 单元测试,配置为 1提升启动速度
sql:
init:
schema-locations: classpath:/sql/create_tables.sql
# Redis 配置。Redisson 默认的配置足够使用,一般不需要进行调优
redis:
host: 127.0.0.1 # 地址
port: 16379 # 端口(单元测试,使用 16379 端口)
database: 0 # 数据库索引
mybatis:
lazy-initialization: true # 单元测试,设置 MyBatis Mapper 延迟加载,加速每个单元测试
--- #################### 定时任务相关配置 ####################
--- #################### 配置中心相关配置 ####################
--- #################### 服务保障相关配置 ####################
# Lock4j 配置项(单元测试,禁用 Lock4j
# Resilience4j 配置项
--- #################### 监控相关配置 ####################
--- #################### 芋道相关配置 ####################
# 芋道配置项,设置当前项目所有自定义的配置
yudao:
info:
base-package: cn.iocoder.yudao.module
captcha:
timeout: 5m
width: 160
height: 60
enable: true

View File

@ -0,0 +1,4 @@
<configuration>
<!-- 引用 Spring Boot 的 logback 基础配置 -->
<include resource="org/springframework/boot/logging/logback/defaults.xml" />
</configuration>

View File

@ -0,0 +1,2 @@
DELETE FROM "report_go_view_project";
DELETE FROM "report_ureport_data";

View File

@ -0,0 +1,27 @@
CREATE TABLE IF NOT EXISTS "report_go_view_project" (
"id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY,
"name" varchar NOT NULL,
"pic_url" varchar,
"content" varchar,
"status" varchar NOT NULL,
"remark" varchar,
"creator" varchar DEFAULT '',
"create_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updater" varchar DEFAULT '',
"update_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
"deleted" bit NOT NULL DEFAULT FALSE,
PRIMARY KEY ("id")
) COMMENT 'GoView 项目表';
CREATE TABLE IF NOT EXISTS "report_ureport_data" (
"id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY,
"name" varchar NOT NULL,
"status" int NOT NULL,
"content" varchar,
"remark" varchar,
"creator" varchar DEFAULT '',
"create_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updater" varchar DEFAULT '',
"update_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
"deleted" bit NOT NULL DEFAULT FALSE,
PRIMARY KEY ("id")
) COMMENT 'Ureport2报表';