3
该文档用于讲解如何实现UReport2在SpringCloud的集成

概要

由于目前实行的Saas化业务系统,各租户都有通用的以及个性的报表需求。而这些报表需求如果按照传统的开发模式,会出现研发周期长的问题;且部分用户会提出短期的数据报表需求,并且会反复变更。这更加加大了整个研发成本

所以我们寻找了相关的替代方案用于解决该问题,此处使用了UReport2

适用场景

针对客户需求,完成数据简单(靠SQL可完成)对UI要求不高(缺少绚丽报表)的基础数据呈现工作,用于报表展示。

不适用以下场景:

  • 高度自由化的报表呈现(复杂布局)
  • 需要深度挖掘统计的数据(大数据)
  • 数据大屏展示(UI绚丽)

PS: 以上内容会与Ant-design的UI存在差异

可实现效果
  • 查询表格
  • 统计表格
  • 统计图表(柱状/折线等等)
  • 下载打印

教程: https://www.w3cschool.cn/urep...

参考文档:
https://www.jianshu.com/p/652...
https://blog.csdn.net/qq_3517...
https://www.cnblogs.com/Seven...

本文除简单讲解在SpringBoot+SpringCloud集成外,会额外讲解最终业务应用场景。主要包含以下

  • 个人使用的案例分享
  • 报表引擎在前端React项目应用
  • 自定义xml存储
  • 租户下如何应用报表引擎

基础配置

POM.xml
<!-- 集成ureport2 -->  
<dependency>  
 <groupId>com.bstek.ureport</groupId>  
 <artifactId>ureport2-console</artifactId>  
 <version>2.2.9</version>  
</dependency>

<!-- Spring Boot JDBC -->  
<dependency>  
 <groupId>org.springframework.boot</groupId>  
 <artifactId>spring-boot-starter-jdbc</artifactId>  
</dependency>  
<!-- mysql -->  
<dependency>  
 <groupId>mysql</groupId>  
 <artifactId>mysql-connector-java</artifactId>  
 <version>8.0.16</version>  
</dependency>  
<!-- 集成mybatis  -->  
<dependency>  
 <groupId>org.mybatis.spring.boot</groupId>  
 <artifactId>mybatis-spring-boot-starter</artifactId>  
 <version>2.0.0</version>  
</dependency>  
<!-- 使用druid -->  
<dependency>  
 <groupId>com.alibaba</groupId>  
 <artifactId>druid-spring-boot-starter</artifactId>  
 <version>1.1.10</version>  
</dependency>

<dependency>  
 <groupId>org.springframework.cloud</groupId>  
 <artifactId>spring-cloud-starter-eureka</artifactId>  
</dependency>  
<dependency>  
 <groupId>org.springframework.cloud</groupId>  
 <artifactId>spring-cloud-starter-config</artifactId>  
</dependency>
<dependency>  
 <groupId>org.glassfish.jersey.core</groupId>  
 <artifactId>jersey-common</artifactId>  
 <version>2.27</version>  
</dependency>
ureport.properties
# 将ureport.disableFileProvider改成true,即可禁用默认提供的文件存储机制
ureport.disableHttpSessionReportCache=true
ureport.disableFileProvider=false
ureport.fileStoreDir=/WEB-INF/ureportfiles
ureport.debug=true
application.properties
\# 指定springboot内嵌容器启动的端口  
server.port\=8080  
server.servlet.context-path\=/  
server.uri-encoding\=utf-8  
\# datasource参数配置  
spring.datasource.druid.dbType\=com.alibaba.druid.pool.DruidDataSource  
spring.datasource.druid.url\=${xxx.datasource.business.url}  
spring.datasource.druid.username\=${xxx.datasource.business.username}  
spring.datasource.druid.password\=${xxx.datasource.business.password}  
spring.datasource.druid.driverClassName\=${xxx.datasource.business.driver-class-name}  
\# 连接池配置  
\# 初始化大小,最大连接数,最小连接数  
spring.datasource.druid.initial-size\=1  
spring.datasource.druid.max-active\=20  
spring.datasource.druid.min-idle\=1  
\# 配置获取连接等待超时的时间  
spring.datasource.druid.max-wait\=60000  
\# 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒  
spring.datasource.druid.time-between-eviction-runs-millis\=60000  
\# 配置一个连接在池中最小生存的时间,单位是毫秒  
spring.datasource.druid.min-evictable-idle-time-millis\=300000  
\# config,stat,wall,log4j 配置监控统计拦截的filters,去掉后监控界面SQL无法进行统计,'wall'用于防火墙  
spring.datasource.druid.filters\=stat  
\##### druid 监控 ####\## WebStatFilter配置  
spring.datasource.druid.web-stat-filter.enabled\=true  
spring.datasource.druid.web-stat-filter.url-pattern\=/\*  
spring.datasource.druid.web-stat-filter.exclusions\=\*.js,\*.gif,\*.jpg,\*.png,\*.css,\*.ico,/druid/\*  
spring.datasource.druid.web-stat-filter.session-stat-enable\=false  
spring.datasource.druid.web-stat-filter.session-stat-max-count\=1000  
spring.datasource.druid.web-stat-filter.principal-session-name\=admin  
spring.datasource.druid.web-stat-filter.principal-cookie-name\=admin  
spring.datasource.druid.web-stat-filter.profile-enable\=true  
\## StatViewServlet配置  
spring.datasource.druid.stat-view-servlet.enabled\=true  
spring.datasource.druid.stat-view-servlet.url-pattern\=/druid/\*  
spring.datasource.druid.stat-view-servlet.reset-enable\=true  
spring.datasource.druid.stat-view-servlet.login-username\=admin  
spring.datasource.druid.stat-view-servlet.login-password\=admin  
  
\# mybatis实体类包  
mybatis.typeAliasesPackage\=com.xxx.report.entity  
\# mybatis mapper 文件的位置  
mybatis.mapperLocations\=classpath:mapper/\*.xml  
  
\# 日志配置  
logging.level.root\=INFO  
logging.level.org.springframework.web\=DEBUG  
logging.level.org.mybatis\=DEBUG  
logging.level.org.apache.ibatis\=DEBUG  
logging.level.java.sql.Connection\=DEBUG  
logging.level.java.sql.Statement\=DEBUG  
logging.path\=/logs/myreport  
\# ureport Mysql 存储  
\# 前缀  
ureport.mysql.provider.prefix\=report-  
\# 是否开启mysql存储,false为开启  
ureport.mysql.provider.disabled\=false  
  
\# SpringCloud  
spring.application.name\=skp-report  
info.owner\= xxx@xxx.com  
info.version\= @project.version@
bootstrap.yml
eureka:  
  instance:  
    preferIpAddress: true  
    lease-renewal-interval-in-seconds: 10  
    lease-expiration-duration-in-seconds: 30  
    instance-id: ${spring.application.name}:${spring.cloud.client.ipAddress}:${server.port}  
  client:  
    serviceUrl:  
      defaultZone: http://root:root@dev-xxx-eureka-0.dev-xxx-eureka.dev.svc.cluster.local:38761/eureka  
    healthcheck:  
      enabled: false  
spring:  
  cloud:  
    inetutils:  
      ignoredInterfaces:  
        - docker0  
        - veth.\*  
    config:  
      discovery:  
        enabled: true  
        service-id: discovery  
      label: master  
      profile: ${spring.profiles.active:dev}  
      name: ${spring.application.name:gateway}  
      username: admin  
      password: admin  
  http:  
    encoding:  
      charset: UTF-8  
      force: true  
      enabled: true  
    multipart:  
      enabled: true  
      max-request-size: 350MB  
      max-file-size: 350MB  
  application:  
    name: skp-report  
  boot:  
    admin:  
      username: ${security.user.name:admin}  
      password: ${security.user.password:admin}  
server:  
  port: 8080  
  tomcat:  
    uri-encoding: UTF-8  
    max-http-post-size: 10485760  
management:  
  security:  
    enabled: false  
info:  
  owner: xxx@xxx.com  
  version: 1.0  
hystrix:  
  propagate:  
    request-attribute:  
      enabled: true  
  command:  
    default:  
      execution:  
        timeout:  
          enabled: true  
        isolation:  
          thread:  
            timeoutInMilliseconds: 360000  
ribbon:  
  ReadTimeout: 300000  
  ConnectTimeout: 300000  
  MaxAutoRetries: 0  
  MaxAutoRetriesNextServer: 1  
logging:  
  level.root: info  
  path: logs/  
  file: ${spring.application.name}.log  
redis:  
  database: 0  
  host: ${spring.redis.host}  
  port: ${spring.redis.port}  
  password: ${spring.redis.password}  
  pool:  
    max-active: ${spring.redis.jedis.pool.max-active}  
    max-wait: ${spring.redis.jedis.pool.max-wiat}  
    max-idle: ${spring.redis.jedis.pool.max-idle}  
    min-idle: 0  
  timeout: 0
ReportApplication
@ImportResource("classpath:ureport-console-context.xml")  
@SpringBootApplication(scanBasePackages \= "com.xxx")  
@EnableDiscoveryClient  
public class ReportApplication {  
  
   public static void main(String\[\] args) {  
        SpringApplication.run(ReportApplication.class, args);  
  }  
  
  @Bean  
  public ServletRegistrationBean reportServlet() {  
        ServletRegistrationBean bean=new ServletRegistrationBean(new UReportServlet());  
  bean.addUrlMappings("/ureport/\*");  
 return bean;  
  }
}

以上配置完毕,访问以下网址即可

http://localhost:8080/ureport/designer

个人使用的案例分享

什么是数据源

数据源可以理解为数据库,但不建议直接使用业务库进行报表统计。会严重拖慢业务进行速度,建议使用报表库。

配置数据库

在右侧选择浮层,选择数据源,在点击最左边数据库图表。添加数据库连接

后续填入以下内容

内容 描述 参考
数据源名称 数据源别名 test
连接用户名 数据库账户 root
连接密码 数据库密码 xxxx
驱动名称 数据库驱动 com.mysql.cj.jdbc.Driver
连接URL 数据库连接 jdbc:mysql://xxx:3306/xxx

填写完成后,可以看到右侧出现了该别名的数据源

配置数据源

右键点击该数据源,在菜单中点击添加数据集

  • 此时左侧为表名一栏
  • 右上角填写数据集名称
  • 右中间添加SQL
  • 右下方定义传入的参数字段(例如分页,按某条件查询,支持从输入框和下拉框联动过来)

以下以'总条码数查询'为例

分组 字段 内容 备注
头部 数据集名称 总条码数查询 -
查询 SQL select count(id) as count from xxx -
查询参数 - - -

可以点击预览数据获取结果

保存之后,可以在右侧看到结构数据

  • dev
    • 总条码数查询
      • count
配置界面

左侧有类似于excel表格的内容,他就是我们用于进行呈现的地方。

一般选中单元格,在右侧属性栏进行操作

例子

单元格 单元格类型 属性
A1 普通文本 {文本内容:总条码数查询}
A2 数据集 {数据集:总条码数查询,属性:count}

PS:也可以使用右侧数据源-数据集-属性 双击,快速填充到单元格。

预览报表

在左上角预览报表,点击预览可以浏览,可以在新窗口查看到结果

例子如下

总条码数查询
53
保存报表

在左上角保存报表,点击保存可以存储到数据库,例如文件名 barcodecount。

下次可以在打开报表文件中找到该文件,例如barcodecount.ureport.xml

PS:保存的同时,会将数据源一并保存

应用报表

一般使用如下格式进行显示html,我们会将它嵌入到我们的业务系统中使用。

http://127.0.0.1:8080/ureport/preview?_u=report-barcodecount.ureport.xml

参数内容如下

参数 描述 参考
_u 报表文件 report-barcodecount.ureport.xml
_t 显示按钮 1,3 or 0

参考资料:

报表引擎在前端React项目应用

一般该界面会嵌入到一些业务系统中使用,此处以React项目为例

建立以下组件,用于替代所有动态表格的使用

主要实现思路为

  • 由后台配置菜单,在启动系统时,将该url以及对应的ureport的url一并传给前端
  • 将ureport的url,使用iframe打开
  • 追加token在该url的参数中,用于用户身份的判断
ReportFrame.tsx
import React, { Fragment } from 'react';
import { connect } from 'dva';
import { Row, Col, Form, Button, Select, Input, Card, Divider, Popconfirm, message } from 'antd';
import PageHeaderWrapper from '@/components/PageHeaderWrapper';
import { FormComponentProps } from 'antd/lib/form/Form';
import ReactDOM from 'react-dom';

const styles = require("@/pages/common.less");

const FormItem = Form.Item;

// 查询表格的属性
interface IReportFrameProps extends FormComponentProps {
    dispatch: IDispatchDefine,
    loading: boolean,
    loginStatus: boolean,
    // 菜单数据
    menuData: any
    // 路由数据
    match?: any
}

// 查询表格的状态
interface IReportFrameState {
    iFrameHeight: string,
    // 嵌入的路径
    frameUrl: string,
}
@connect(({ menu, login }) => ({
    loginStatus: login.status,
    menuData: menu.menuData
}))
class ReportFrame extends React.Component<IReportFrameProps, IReportFrameState>{

    constructor(props) {
        super(props);

        this.state = {
            iFrameHeight: '600px',
            frameUrl:null
        }

    }

    public componentDidMount() {
        const nodes=TreeDataUtils.loopTreeToList(this.props.menuData,'children',(data)=>data.path===this.props.match.url)
        if(nodes&&nodes.length>0){
            let frameUrl=nodes[0].frameUrl
            if (localStorage.getItem("user-identity") && JSON.parse(localStorage.getItem("user-identity")) && JSON.parse(localStorage.getItem("user-identity")).token) {
                frameUrl+= "&token="+JSON.parse(localStorage.getItem("user-identity")).token;
            }
            this.setState({
                frameUrl
            })
        }
    }

    public findTreeNode(){

    }

    public render() {
        const frameUrl = this.state.frameUrl;
        return (
            <PageHeaderWrapper>
                <Card loading={!frameUrl}>
                    {frameUrl ? <iframe
                        style={{ width: '100%', height: this.state.iFrameHeight, overflow: 'visible' }}
                        onLoad={() => {
                            const obj = ReactDOM.findDOMNode(this.refs.iframe);
                            // console.log('document',obj.contentDocument)
                            // console.log('contentWindow',obj.contentWindow)
                            // console.log('documentElement',obj.documentElement)
                            // this.setState({
                            //     "iFrameHeight": (obj as any).contentWindow.document.body.scrollHeight + 'px'
                            // });
                        }}
                        ref="iframe"
                        src={frameUrl}
                        width="100%"
                        // height={this.state.iFrameHeight}
                        scrolling="no"
                        frameBorder="0"
                    /> : null}

                </Card>

            </PageHeaderWrapper>
        )
    }
}

export default Form.create()(ReportFrame)
router.config.js
{
        path: '/report/:code',
        icon: 'table',
        name: 'report',
        hideInMenu: true,
        component: './business/Report/ReportFrame'
      }
TreeDataUtils.ts
export class TreeDataUtils {
/**
   * 将树转换为列表
   * @param datas 
   * @param childrenName 
   * @param find 可为空,如不为空的话只会找到匹配的内容
   */
  public static loopTreeToList<T>(datas: T[], childrenName: string,find?: (data: T) => boolean): T[] {
    const newDatas: T[] = [];
    if (datas) {
      datas.forEach(data => {
        if (data[childrenName]) {
          TreeDataUtils.loopTreeToList(data[childrenName] as T[], childrenName,find).forEach(
            cData => {
              const newCData = {};
              Object.assign(newCData, cData)
              newCData[childrenName] = null;
              if(find){
                if(find(newCData as T)){
                  newDatas.push(newCData as T);
                }
              }else{
                newDatas.push(newCData as T);
              }
            }
          )
        }
        const cNewData = {};
        Object.assign(cNewData, data);
        cNewData[childrenName] = null;
        if(find){
          if(find(cNewData as T)){
            newDatas.push(cNewData as T);
          }
        }else{
          newDatas.push(cNewData as T);
        }
      })
    }
    return newDatas;
  }
}

自定义xml存储

默认文件会存储到硬盘上,但实际使用中我们更可能的是存储在数据库中

以下是相关代码(借鉴码云上一个用户的,暂时找不到原始连接了,这里要感谢他的分享)

UreportFileEntity
public class UreportFileEntity {  
    private Long id;  
 private String name;  
 private byte\[\] content;  
 private Date createTime;  
 private Date updateTime;  
  
 public Long getId() {  
        return id;  
  }  
  
    public void setId(Long id) {  
        this.id \= id;  
  }  
  
    public String getName() {  
        return name;  
  }  
  
    public void setName(String name) {  
        this.name \= name;  
  }  
  
    public byte\[\] getContent() {  
        return content;  
  }  
  
    public void setContent(byte\[\] content) {  
        this.content \= content;  
  }  
  
    public Date getCreateTime() {  
        return createTime;  
  }  
  
    public void setCreateTime(Date createTime) {  
        this.createTime \= createTime;  
  }  
  
    public Date getUpdateTime() {  
        return updateTime;  
  }  
  
    public void setUpdateTime(Date updateTime) {  
        this.updateTime \= updateTime;  
  }  
}
ViewUreportEntity
public class ViewUreportEntity{  
  
    private String id;  
 private String name;  
  
 public String getId() {  
        return id;  
  }  
  
    public void setId(String id) {  
        this.id \= id;  
  }  
  
    public String getName() {  
        return name;  
  }  
  
    public void setName(String name) {  
        this.name \= name;  
  }  
}
UreportFileMapper
import org.apache.ibatis.annotations.Mapper;
import com.xxx.report.entity.UreportFileEntity;

import java.util.List;

@Mapper
public interface UreportFileMapper {
    /**
     *  根据报表名称检查报表是否存在
     * @param name 报表名称
     * @return
     */
    public int checkExistByName(String name);

    /**
     *  根据报表名称查询报表
     * @param name 报表名称
     * @return
     */
    public UreportFileEntity queryUreportFileEntityByName(String name);

    /**
     * 查询全部报表
     * @return
     */
    public List<UreportFileEntity> queryReportFileList();

    /**
     * 根据报表名称删除报表
     * @param name
     * @return
     */
    public int deleteReportFileByName(String name);


    /**
     *  保存报表
     */
    public int insertReportFile(UreportFileEntity entity);

    /**
     *  更新报表
     * @param entity
     * @return
     */
    public int updateReportFile(UreportFileEntity entity);
}
ViewUreportMapper
import com.xxx.report.entity.ViewUreportEntity;
import org.apache.ibatis.annotations.Mapper;


@Mapper
public interface ViewUreportMapper {

    public String queryNameByID(String id);
    public int insertOne(ViewUreportEntity viewUreportEntity);
}
MySQLProvider
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;

import com.xxx.report.entity.UreportFileEntity;
import com.xxx.report.entity.ViewUreportEntity;
import com.xxx.report.service.UreportFileService;
import com.xxx.report.service.ViewUreportService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

import com.bstek.ureport.provider.report.ReportFile;
import com.bstek.ureport.provider.report.ReportProvider;

@Component
@ConfigurationProperties(prefix = "ureport.mysql.provider")
public class MySQLProvider implements ReportProvider {
    private final Logger log= LoggerFactory.getLogger(getClass());
    private static final String NAME = "mysql-provider";
    private String prefix = "";
    private boolean disabled;

    @Autowired
    private ViewUreportService viewUreportService;
    @Autowired
    private UreportFileService ureportFileService;
    @Autowired
    private HttpServletRequest request;
    /**
     * 根据报表名加载报表文件
     * @param file 报表名称
     * @return 返回的InputStream
     */
    @Override
    public InputStream loadReport(String file) {
        UreportFileEntity ureportFileEntity = ureportFileService.queryUreportFileEntityByName(getCorrectName(file));
        byte[] content = ureportFileEntity.getContent();
        ByteArrayInputStream inputStream = new ByteArrayInputStream(content);
        return inputStream;
    }
    /**
     * 根据报表名,删除指定的报表文件
     * @param file 报表名称
     */
    @Override
    public void deleteReport(String file) {
        ureportFileService.deleteReportFileByName(getCorrectName(file));
    }
    /**
     * 获取所有的报表文件
     * @return 返回报表文件列表
     */
    @Override
    public List<ReportFile> getReportFiles() {
        List<UreportFileEntity> list = ureportFileService.queryReportFileList();
        List<ReportFile> reportList = new ArrayList<>();
        for (UreportFileEntity ureportFileEntity : list) {
            reportList.add(new ReportFile(ureportFileEntity.getName(), ureportFileEntity.getUpdateTime()));
        }
        return reportList;
    }
    /**
     * 保存报表文件
     * @param file 报表名称
     * @param content 报表的XML内容
     */
    @Override
    public void saveReport(String file, String content) {
        file = getCorrectName(file);
        UreportFileEntity ureportFileEntity = ureportFileService.queryUreportFileEntityByName(file);
        Date currentDate = new Date();
        if(ureportFileEntity == null){
            ureportFileEntity = new UreportFileEntity();
            ureportFileEntity.setName(file);
            ureportFileEntity.setContent(content.getBytes());
            ureportFileEntity.setCreateTime(currentDate);
            ureportFileEntity.setUpdateTime(currentDate);
            ureportFileService.insertReportFile(ureportFileEntity);
        }else{
            ureportFileEntity.setContent(content.getBytes());
            ureportFileEntity.setUpdateTime(currentDate);
            ureportFileService.updateReportFile(ureportFileEntity);
        }
        HttpSession session = request.getSession();
        if(session.getAttribute("viewid") == null){
            log.info("viewid为空");
        }else{
            String viewid = session.getAttribute("viewid").toString();
            log.info("viewid:"+viewid);
            ViewUreportEntity viewUreportEntity = new ViewUreportEntity();
            viewUreportEntity.setId(viewid);
            viewUreportEntity.setName(prefix+file);
            int i = viewUreportService.insertOne(viewUreportEntity);
            log.info(file+"ViewUreportEntity插入成功"+i);
        }

    }
    /**
     * @return 返回存储器名称
     */
    @Override
    public String getName() {
        return NAME;
    }
    /**
     * @return 返回是否禁用
     */
    @Override
    public boolean disabled() {
        return disabled;
    }
    /**
     * @return 返回报表文件名前缀
     */
    @Override
    public String getPrefix() {
        return prefix;
    }

    /**
     * @description 获取没有前缀的文件名
     * @param name
     * @return
     */

    private String getCorrectName(String name){


        log.info("前缀:"+prefix);
        if(name.startsWith(prefix)){
            name = name.substring(prefix.length(), name.length());
        }
        return name;
    }

    public void setPrefix(String prefix) {
        this.prefix = prefix;
    }

    public boolean isDisabled() {
        return disabled;
    }

    public void setDisabled(boolean disabled) {
        this.disabled = disabled;
    }
}
UreportFileService
import com.xxx.report.entity.UreportFileEntity;

import java.util.List;

public interface UreportFileService {
    /**
     *  根据报表名称检查报表是否存在
     * @param name 报表名称
     * @return
     */
    public int checkExistByName(String name);

    /**
     *  根据报表名称查询报表
     * @param name 报表名称
     * @return
     */
    public UreportFileEntity queryUreportFileEntityByName(String name);

    /**
     * 查询全部报表
     * @return
     */
    public List<UreportFileEntity> queryReportFileList();

    /**
     * 根据报表名称删除报表
     * @param name
     * @return
     */
    public int deleteReportFileByName(String name);


    /**
     *  保存报表
     */
    public int insertReportFile(UreportFileEntity entity);

    /**
     *  更新报表
     * @param entity
     * @return
     */
    public int updateReportFile(UreportFileEntity entity);
}
ViewUreportService
import com.xxx.report.entity.ViewUreportEntity;  
  
public interface ViewUreportService {  
    int insertOne(ViewUreportEntity viewUreportEntity);  
  String queryNameByID(String id);  
}
UreportFileServiceImpl
import com.xxx.report.entity.UreportFileEntity;  
import com.xxx.report.mapper.UreportFileMapper;  
import com.xxx.report.service.UreportFileService;  
import org.springframework.stereotype.Service;  
  
import javax.annotation.Resource;  
import java.util.List;  
  
@Service  
public class UreportFileServiceImpl implements UreportFileService {  
    @Resource  
  private UreportFileMapper ureportFileMapper;  
  @Override  
  public int checkExistByName(String name) {  
  
        return ureportFileMapper.checkExistByName(name);  
  }  
  
    @Override  
  public UreportFileEntity queryUreportFileEntityByName(String name) {  
        // TODO Auto-generated method stub  
  return ureportFileMapper.queryUreportFileEntityByName(name);  
  }  
  
    @Override  
  public List<UreportFileEntity> queryReportFileList() {  
        // TODO Auto-generated method stub  
  return ureportFileMapper.queryReportFileList();  
  }  
  
    @Override  
  public int deleteReportFileByName(String name) {  
        // TODO Auto-generated method stub  
  return ureportFileMapper.deleteReportFileByName(name);  
  }  
  
    @Override  
  public int insertReportFile(UreportFileEntity entity) {  
        // TODO Auto-generated method stub  
  return ureportFileMapper.insertReportFile(entity);  
  }  
  
    @Override  
  public int updateReportFile(UreportFileEntity entity) {  
        // TODO Auto-generated method stub  
  return ureportFileMapper.updateReportFile(entity);  
  }  
  
}
ViewUreportServiceImpl
import javax.annotation.Resource;  
  
import com.xxx.report.entity.ViewUreportEntity;  
import com.xxx.report.mapper.ViewUreportMapper;  
import com.xxx.report.service.ViewUreportService;  
import org.springframework.stereotype.Service;  
import org.springframework.transaction.annotation.Transactional;  
  
  
@Service  
public class ViewUreportServiceImpl implements ViewUreportService {  
    @Resource  
  private ViewUreportMapper viewUreportMapper;  
  @Transactional  
  public int insertOne(ViewUreportEntity viewUreportEntity) {  
        int i = 0;  
  String name = viewUreportMapper.queryNameByID(viewUreportEntity.getId());  
 if(name==null || name.isEmpty()){  
            i = viewUreportMapper.insertOne(viewUreportEntity);  
  }  
        return i;  
  }  
    @Override  
  public String queryNameByID(String id) {  
        String name = viewUreportMapper.queryNameByID(id);  
 return name;  
  }  
  
}

UreportFileMapper.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.xxx.report.mapper.UreportFileMapper">

    <resultMap type="UreportFileEntity" id="ureportFile">
        <id column="id_" jdbcType="BIGINT" property="id"/>
        <result column="name_" jdbcType="VARCHAR" property="name"/>
        <result column="content_" jdbcType="BLOB" property="content"/>
        <result column="create_time_" jdbcType="TIMESTAMP" property="createTime"/>
        <result column="update_time_" jdbcType="TIMESTAMP" property="updateTime"/>
    </resultMap>

    <select id="checkExistByName" parameterType="java.lang.String" resultType="java.lang.Integer">
        select count(*) from lg_sys_ureport_file where name_ = #{name}
    </select>

    <select id="queryReportFileList" resultMap="ureportFile">
        select * from lg_sys_ureport_file
    </select>

    <select id="queryUreportFileEntityByName" resultMap="ureportFile">
        select * from lg_sys_ureport_file where name_ = #{name}
    </select>

    <delete id="deleteReportFileByName" parameterType="java.lang.String">
        delete from lg_sys_ureport_file where name_ = #{name}
    </delete>

    <insert id="insertReportFile" parameterType="UreportFileEntity">
        insert into lg_sys_ureport_file (name_, content_, create_time_, update_time_) values
        (#{name}, #{content}, #{createTime}, #{updateTime})
    </insert>


    <update id="updateReportFile" parameterType="UreportFileEntity">
        update lg_sys_ureport_file set
        name_ = #{name} ,
        content_ = #{content},
        create_time_ = #{createTime},
        update_time_ = #{updateTime}
        where  id_ = #{id}
    </update>

</mapper>
ViewUreportMapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.xxx.report.mapper.ViewUreportMapper">
    <resultMap type="ViewUreportEntity" id="viewUreport">
        <id column="id" jdbcType="VARCHAR" property="id"/>
        <result column="name" jdbcType="VARCHAR" property="name"/>
    </resultMap>
    <select id="queryNameByID" parameterType="java.lang.String" resultType="java.lang.String">
        select name from lg_sys_ureport_view where id = #{id}
    </select>
    <insert id="insertOne" parameterType="ViewUreportEntity">
        insert into lg_sys_ureport_view (id, name) values
        (#{id}, #{name})
    </insert>
</mapper>

完成以上配置,在进行xml保存的时候,就可以看到mysql的存储了

租户下如何应用报表引擎

回到最初,我们的目标是为了满足可以针对不同租户建立报表。那么我们再次基础上需要满足一定的逻辑数据隔离。最基础的我们需要在UReport设计时候,追加参数。进行数据的隔离~当前我们使用了物理隔离后不存在该问题

基本思路

  • 通过过滤器,拦截请求参数
  • 将请求参数转换为属性

以下为代码参考

TokenFilter
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;

@Component
public class TokenFilter implements Filter {

    private final Logger log= LoggerFactory.getLogger(getClass());

    @Autowired
    private RestTemplate restTemplate;

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        log.info("初始化自定义过滤器");
    }

    @Override
    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
        chain.doFilter(new TokenRequestWrapper((HttpServletRequest) req,restTemplate), res);
    }

    @Override
    public void destroy() {

    }
}
TokenRequestWrapper
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.client.RestTemplate;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;

public class TokenRequestWrapper extends HttpServletRequestWrapper {

    private final Logger logger= LoggerFactory.getLogger(getClass());

    RestTemplate restTemplate;

    TokenRequestWrapper(HttpServletRequest request, RestTemplate restTemplate) {
        super(request);
        this.restTemplate=restTemplate;
    }
    /**
     * 修改此方法主要是因为当RequestMapper中的参数为pojo类型时,
     * 会通过此方法获取所有的请求参数并进行遍历,对pojo属性赋值
     * @return
     */
    @Override
    public Enumeration<String> getParameterNames() {
        Enumeration<String> enumeration = super.getParameterNames();
        ArrayList<String> list = Collections.list(enumeration);
        //当有token字段时动态的添加uid字段
        if (list.contains("token")){
            return Collections.enumeration(TokenUtil.getParameterNames());
        }else {
            return super.getParameterNames();
        }
    }
    @Override
    public String getParameter(String name) {
        if ("tenant_id".equals(name)){
            return TokenUtil.getParameterValues(restTemplate,getParameter("token")).get("tenant_id");
        }
        if ("org_code".equals(name)){
            return TokenUtil.getParameterValues(restTemplate,getParameter("token")).get("org_code");
        }
        if ("current_user_id".equals(name)){
            return TokenUtil.getParameterValues(restTemplate,getParameter("token")).get("current_user_id");
        }
        if ("current_user_name".equals(name)){
            return TokenUtil.getParameterValues(restTemplate,getParameter("token")).get("current_user_name");
        }
        return super.getParameter(name);
    }
}

自定义实现TokenUtil,校验该token返回后,即可在design使用时,使用以上参数。

以上同样可以完成url和token的鉴权等

应用

报表系统默认提供以下属性

属性名 名称 备注
tenant_id 当前用户-租户ID -
org_code 当前用户-组织code -
current_user_id 当前用户-用户ID -
current_user_name 当前用户-用户名称 -

将以上数据在sql中使用,例如

select count(*) as count from xxx where status_cd=6
and tenant_id like concat('%',:tenant_id,'%')
and org_code like concat('%',:org_code,'%')

接着添加为参数

参数名 数据类型 默认值
tenant_id String 0
org_code String 0

即可完成该功能,设置为0代表如传输权限信息则不返回任何数据


思绪回忆
49 声望16 粉丝