公司有项目在使用ureport来实现报表功能,报表功能已经实现了,但是目前存在一个问题,生产环境是多机集群环境,报表生成后,报表文件是存在单机的服务器上,这样会存在问题,导致访问负载地址时,报表信息不存在。
基于以上问题,在网上找了些资料看了下,原来ureport中提供了ReportProvider接口,实现其接口即可实现存储到sftp服务器(还有其他的模式,比如说存储到数据库),这样就能解决集群不同步的问题。好了,开始上代码
pom.xml
<dependency>
<groupId>com.bstek.ureport</groupId>
<artifactId>ureport2-console</artifactId>
<version>2.2.9</version>
</dependency>
<dependency>
<groupId>commons-net</groupId>
<artifactId>commons-net</artifactId>
<version>3.3</version>
</dependency>
<!-- https://mvnrepository.com/artifact/commons-pool/commons-pool -->
<dependency>
<groupId>commons-pool</groupId>
<artifactId>commons-pool</artifactId>
<version>1.6</version>
</dependency>
<!-- 消除冗余代码,可以省去get、set、构造器... 方法 -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.jcraft</groupId>
<artifactId>jsch</artifactId>
<version>0.1.54</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<!-- 添加MySQL依赖 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<!-- 添加JDBC依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<!-- 添加Druid依赖 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.6</version>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.3.5</version>
</dependency>
application.yml
# ureport FTP 存储
ureport:
ftp:
provider:
prefix: ftp-
disabled: false
basePath: ureport_file/
#
sftp:
client:
protocol: sftp
host: ***
port: ***
username: ****
password: ****
root: /data/home/miapp/ureport/
privateKey:
passphrase:
sessionStrictHostKeyChecking: no
sessionConnectTimeout: 15000
channelConnectedTimeout: 15000
DataSourceConfig
import com.alibaba.druid.pool.DruidDataSource;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.sql.DataSource;
@Configuration
@Data
@ConfigurationProperties(prefix = "spring.datasource")
public class DataSourceConfig {
private String url;
private String username;
private String password;
@Bean
public DataSource getDataSource() {
DruidDataSource dataSource = new DruidDataSource();
dataSource.setUrl(url);
dataSource.setUsername(username);
dataSource.setPassword(password);
return dataSource;
}
}
UreportDataSource
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import javax.sql.DataSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import com.bstek.ureport.definition.datasource.BuildinDatasource;
/**
* Ureport 数据源
* @author
* @version
*
*/
@Component
public class UreportDataSource implements BuildinDatasource {
private static final String NAME = "MyDataSource";
private Logger log = LoggerFactory.getLogger(UreportDataSource.class);
@Autowired
private DataSource dataSource;
/**
* 数据源名称
**/
@Override
public String name() {
return NAME;
}
/**
* 获取连接
**/
@Override
public Connection getConnection() {
try {
System.out.println(dataSource.toString());
Connection connection = dataSource.getConnection();
return dataSource.getConnection();
} catch (SQLException e) {
log.error("Ureport 数据源 获取连接失败!");
e.printStackTrace();
}
return null;
}
}
SFTPProvider
import com.bstek.ureport.provider.report.ReportFile;
import com.bstek.ureport.provider.report.ReportProvider;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import net.yondervision.service.FileSystemService;
import org.apache.commons.net.ftp.FTPFile;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;
/**
* @Description FTP文件服务器 报表存储
* @Author
* @Date Created in 2020/5/25
*/
@Setter
@Slf4j
@Component
@ConfigurationProperties(prefix = "ureport.ftp.provider")
public class SFTPProvider implements ReportProvider {
private static final String NAME = "ftp-provider";
private String basePath = "/data/home/miapp/ureport/";
private String prefix = "ftp:";
private boolean disabled;
@Autowired
private FileSystemService ftpUtils;
@Override
public InputStream loadReport(String file) {
return ftpUtils.downFile(getCorrectName(file));
}
@Override
public void deleteReport(String file) {
ftpUtils.deleteFile(getCorrectName(file));
}
@Override
public List<ReportFile> getReportFiles() {
List<FTPFile> fileList = ftpUtils.getFileList(basePath);
List<ReportFile> reportFile = new ArrayList<>();
for (FTPFile ftpFile : fileList) {
Calendar timestamp = ftpFile.getTimestamp();
reportFile.add(new ReportFile(ftpFile.getName(), timestamp.getTime()));
log.info("---listinfo----" + ftpFile.getName());
}
return reportFile;
}
@Override
public void saveReport(String file, String content) {
ftpUtils.uploadFile(getCorrectName(file), new ByteArrayInputStream(content.getBytes()));
}
@Override
public String getName() {
return NAME;
}
@Override
public boolean disabled() {
return disabled;
}
@Override
public String getPrefix() {
return prefix;
}
/**
* 获取没有前缀的文件名并加上FTP下存放Ureport文件夹前缀
*
* @param name
* @return
*/
private String getCorrectName(String name) {
if (name.startsWith(prefix)) {
name = name.substring(prefix.length(), name.length());
}
return basePath + name;
}
}
ServletRegistrationBeanConfig
import com.bstek.ureport.console.UReportServlet;
import org.springframework.boot.SpringBootConfiguration;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
/**
* @author: Mr.Du
* @date 2020-03-16 14:05
**/
@SpringBootConfiguration
public class ServletRegistrationBeanConfig {
@Bean
public ServletRegistrationBean ureportServlet() {
ServletRegistrationBean<UReportServlet> bean = new ServletRegistrationBean<>(new UReportServlet());
bean.addUrlMappings("/ureport/*");
return bean;
}
}
SFTPConstants
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
/**
* SFTPConstants
*/
@Data
@Slf4j
@Component
@ConfigurationProperties("sftp.client")
public class SFTPConstants {
/**
* 协议信息
**/
private String protocol;
/**
* 地址信息
**/
private String host;
/**
* 端口
**/
private int port;
/**
* 用户名
*/
private String username;
/**
* 密码
*/
private String password;
/**
* 根目录
*/
private String root;
/**
* 秘钥文件路径
*/
private String privateKey;
/**
* 秘钥密码
*/
private String passphrase;
private String sessionStrictHostKeyChecking = "no";
/**
* session连接超时时间
*/
private int sessionConnectTimeout;
/**
* channel连接超时时间
*/
private int channelConnectedTimeout;
}
FileSystemService
import org.apache.commons.net.ftp.FTPFile;
import java.io.File;
import java.io.InputStream;
import java.util.List;
/**
* @Description FileSystemService
*/
public interface FileSystemService {
/**
* 文件上传
*
* @param targetPath 目标文件目录
* @param inputStream 文件流
* @return boolean
*/
boolean uploadFile(String targetPath, InputStream inputStream);
/**
* 文件上传
*
* @param targetPath 目标文件目录
* @param file 文件
* @return boolean
*/
boolean uploadFile(String targetPath, File file);
/**
* 文件下传
*
* @param targetPath 目标文件目录
* @return file
*/
File downloadFile(String targetPath);
/**
* 文件下传
*
* @param targetPath 目标文件目录
* @return InputStream
*/
InputStream downFile(String targetPath);
/**
* 删除文件
*
* @param targetPath 目标文件目录
* @return boolean
*/
boolean deleteFile(String targetPath);
/**
* 获取文件目录文件信息
*
* @param targetPath 目标文件目录
* @return list
*/
List<FTPFile> getFileList(String targetPath);
}
FileSystemServiceImpl
import cn.hutool.core.date.DateUtil;
import com.jcraft.jsch.*;
import lombok.extern.slf4j.Slf4j;
import net.yondervision.service.FileSystemService;
import net.yondervision.constans.SFTPConstants;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.net.ftp.FTPFile;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.io.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Vector;
/**
* @Description
*/
@Slf4j
@Service("fileSystemService")
public class FileSystemServiceImpl implements FileSystemService {
@Autowired
private SFTPConstants config;
/**
* 创建SFTP连接
*
* @return
* @throws Exception
*/
private ChannelSftp createSftp() throws Exception {
JSch jsch = new JSch();
Session session = createSession(jsch, config.getHost(), config.getUsername(), config.getPort());
session.setPassword(config.getPassword());
session.connect(config.getSessionConnectTimeout());
Channel channel = session.openChannel(config.getProtocol());
channel.connect(config.getChannelConnectedTimeout());
return (ChannelSftp) channel;
}
/**
* 加密秘钥方式登陆
*
* @return
*/
private ChannelSftp connectByKey() throws Exception {
JSch jsch = new JSch();
// 设置密钥和密码 ,支持密钥的方式登陆
if (StringUtils.isNotBlank(config.getPrivateKey())) {
if (StringUtils.isNotBlank(config.getPassphrase())) {
// 设置带口令的密钥
jsch.addIdentity(config.getPrivateKey(), config.getPassphrase());
} else {
// 设置不带口令的密钥
jsch.addIdentity(config.getPrivateKey());
}
}
Session session = createSession(jsch, config.getHost(), config.getUsername(), config.getPort());
// 设置登陆超时时间
session.connect(config.getSessionConnectTimeout());
// 创建sftp通信通道
Channel channel = session.openChannel(config.getProtocol());
channel.connect(config.getChannelConnectedTimeout());
return (ChannelSftp) channel;
}
/**
* 创建session
*
* @param jsch
* @param host
* @param username
* @param port
* @return
* @throws Exception
*/
private Session createSession(JSch jsch, String host, String username, Integer port) throws Exception {
Session session = null;
if (port <= 0) {
session = jsch.getSession(username, host);
} else {
session = jsch.getSession(username, host, port);
}
if (session == null) {
throw new Exception(host + " session is null");
}
session.setConfig(SESSION_CONFIG_STRICT_HOST_KEY_CHECKING, config.getSessionStrictHostKeyChecking());
return session;
}
/**
* 关闭连接
*
* @param sftp
*/
private void disconnect(ChannelSftp sftp) {
try {
if (sftp != null) {
if (sftp.isConnected()) {
sftp.disconnect();
} else if (sftp.isClosed()) {
System.out.println("sftp is closed already");
}
if (null != sftp.getSession()) {
sftp.getSession().disconnect();
}
}
} catch (JSchException e) {
e.printStackTrace();
}
}
// 设置第一次登陆的时候提示,可选值:(ask | yes | no)
private static final String SESSION_CONFIG_STRICT_HOST_KEY_CHECKING = "StrictHostKeyChecking";
@Override
public boolean uploadFile(String targetPath, InputStream inputStream) {
ChannelSftp sftp = null;
try {
sftp = this.createSftp();
sftp.cd(config.getRoot());
int index = targetPath.lastIndexOf("/");
String fileDir = targetPath.substring(0, index);
String fileName = targetPath.substring(index + 1);
boolean dirs = this.createDirs(fileDir, sftp);
if (!dirs) {
throw new Exception("Upload File failure");
}
sftp.put(inputStream, fileName);
} catch (Exception e) {
e.printStackTrace();
} finally {
this.disconnect(sftp);
}
return true;
}
@Override
public boolean uploadFile(String targetPath, File file) {
boolean flag = false;
try {
flag = this.uploadFile(targetPath, new FileInputStream(file));
} catch (Exception e) {
e.printStackTrace();
}
return flag;
}
@Override
public File downloadFile(String targetPath) {
ChannelSftp sftp = null;
OutputStream outputStream = null;
File file = null;
try {
sftp = this.createSftp();
sftp.cd(config.getRoot());
file = new File(targetPath.substring(targetPath.lastIndexOf("/") + 1));
outputStream = new FileOutputStream(file);
sftp.get(targetPath, outputStream);
} catch (Exception e) {
e.printStackTrace();
} finally {
if (outputStream != null) {
try {
outputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
this.disconnect(sftp);
}
return file;
}
@Override
public InputStream downFile(String targetPath) {
InputStream fileStream = null;
try {
File file = downloadFile(targetPath);
fileStream = new FileInputStream(file);
} catch (Exception e) {
e.printStackTrace();
}
return fileStream;
}
@Override
public boolean deleteFile(String targetPath) {
ChannelSftp sftp = null;
try {
sftp = this.createSftp();
sftp.cd(config.getRoot());
sftp.rm(targetPath);
} catch (Exception e) {
e.printStackTrace();
log.error("删除文件失败", e.getMessage());
} finally {
if (sftp != null) {
this.disconnect(sftp);
}
}
return true;
}
@Override
public List<FTPFile> getFileList(String targetPath) {
ChannelSftp sftp = null;
List<FTPFile> list = new ArrayList<>();
try {
sftp = this.createSftp();
String path = config.getRoot() + targetPath;
SftpATTRS attrs = null;
Vector<ChannelSftp.LsEntry> lsEntries = sftp.ls(path);
for (ChannelSftp.LsEntry entry : lsEntries) {
String fileName = entry.getFilename();
if (checkFileName(fileName)) {
continue;
}
attrs = sftp.lstat(path + fileName);
FTPFile ftpFile = new FTPFile();
log.info("file-info-time" + attrs.getATime());
ftpFile.setTimestamp(getCalendar(attrs.getMtimeString()));
ftpFile.setName(fileName);
list.add(ftpFile);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if (sftp != null) {
this.disconnect(sftp);
}
}
return list;
}
private boolean createDirs(String dirPath, ChannelSftp sftp) {
if (dirPath != null && !dirPath.isEmpty()
&& sftp != null) {
String[] dirs = Arrays.stream(dirPath.split("/"))
.filter(StringUtils::isNotBlank)
.toArray(String[]::new);
for (String dir : dirs) {
try {
sftp.cd(dir);
} catch (Exception e) {
try {
sftp.mkdir(dir);
} catch (SftpException e1) {
e1.printStackTrace();
}
try {
sftp.cd(dir);
} catch (SftpException e1) {
e1.printStackTrace();
}
}
}
return true;
}
return false;
}
private boolean checkFileName(String fileName) {
if (".".equals(fileName) || "..".equals(fileName)) {
return true;
}
return false;
}
public Calendar getCalendar(String str){
Calendar cal = Calendar.getInstance();
String pattern = "EEE MMM dd HH:mm:ss zzz yyyy";
SimpleDateFormat df = new SimpleDateFormat(pattern,Locale.US);
Date date = null;
try {
date = df.parse(str);
} catch (ParseException e) {
e.printStackTrace();
}
cal.setTime(date);
return cal;
}
}
App
import net.yondervision.config.DataSourceConfig;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.ImportResource;
/**
* Hello world!
*
*/
@ImportResource("classpath:ureport-console-context.xml")
@SpringBootApplication
@EnableConfigurationProperties({DataSourceConfig.class})
public class App
{
public static void main( String[] args )
{
SpringApplication.run(App.class,args);
}
}