该文档用于讲解如何实现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代表如传输权限信息则不返回任何数据
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。