导语:
昨天接到项目经理这么一个需求,让我在POI导出Excel的时候写一份到我之前搭建的ftp服务器上。所以就有了这篇博客首先我们来分析下之前的业务逻辑:我们创建并构造了一个workbook,然后构建了一个OutputStream输出流,然后我们把数据写入输出流中就可以被客户端下载。
现在我们要在此基础上写一份到ftp服务器
那么我们就需要两个流,首先一个输入流把文件写到ftp服务器,然后需要一个输出流把文件输出到客户端。千万不要用workbook.write(out)一份到客户端,然后又workbook.write(in)一份到ftp,会报错的。Stream is closed,(为啥报错,我也解释不清希望大神解惑一下)
正确思路应该是先写一份到ftp服务器,然后再读取这个文件,然后再把这个文件输出给客户端
参考:
来看代码吧:
1. 我们只需要对 oi实现百万级数据导出 中的 CommentController 稍作修改,然后配合一些工具类就可以实现
/**
* excel导出功能
* @param commentSearch
* @param response
* @param request
* @return
* @throws Exception
*/
@RequestMapping("/exportCommentInfo")
@ResponseBody
@NoRepeatRequest
public BaseDTO exportCommentInfo(CommentSearch commentSearch, HttpServletResponse response, HttpServletRequest request) throws Exception{
LOGGER.info("CommentController.exportCommentInfo start");
long startTime = System.currentTimeMillis();
LOGGER.info("开始下载.........................................");
List<ErrorInfo> errors = null;
int result = ;
String fileName = FileNameUtils.getExportCommontExcelFileName();
OutputStream fileOut = null;
SXSSFWorkbook workbook = null;
try {
LOGGER.debug("classpath: " + fileName);
workbook = new SXSSFWorkbook();
commentService.exportCommentInfo(request,workbook, commentSearch);
// 定义excel文件名
response.setCharacterEncoding("UTF-8");
response.setHeader("Content-Disposition", "attachment; filename=\""
+ URLEncoder.encode(fileName, "UTF-8") + "\"");
// 定义输出流
fileOut = response.getOutputStream();
// 调用导出方法
//workbook.write(fileOut);
//写一份到ftp服务器
ByteArrayOutputStream os = new ByteArrayOutputStream();
workbook.write(os);
byte[] b = os.toByteArray();
ByteArrayInputStream in = new ByteArrayInputStream(b);
FtpUtil.uploadFileToFtp(fileName,in,fileOut);
workbook.dispose();
} catch (Exception e) {
LOGGER.error("InterfaceInfoController.exportInterfaceInfo Exception: ", e);
ErrorInfo errorInfo = new ErrorInfo("system.error", "系统异常!");
errors = Arrays.asList(errorInfo);
request.getSession().setAttribute("exportStatus","error");
}finally {
fileOut.close();
workbook.close();
}
LOGGER.info("下载完成....|||||.......用时:" + (System.currentTimeMillis() - startTime));
return tranferBaseDTO(errors, result);
}
1.首先把workbook写到 ByteArrayOutputStream 输出流中,然后转换成 字节数组 byte[] b 在转换成 ByteArrayInputStream 输入流用来写入ftp
ByteArrayOutputStream os = new ByteArrayOutputStream();
workbook.write(os);
byte[] b = os.toByteArray();
ByteArrayInputStream in = new ByteArrayInputStream(b);
FtpUtil.uploadFileToFtp(fileName,in,fileOut);
2.然后转换成 字节数组 在转换成输入流用来写入ftp ,主要看这几个方法
/**
* 上传重载 二进制流
* @param filename
* @param input
* @return
*/
public static boolean uploadFileToFtp(String filename, ByteArrayInputStream input, OutputStream fileOut) {
getPropertity();
return uploadFileToFtp( host, port, username, password, basePath,
filePath, filename, input, fileOut);
} /**
* Description: 向FTP服务器上传文件 二进制流文件
* @param host FTP服务器hostname
* @param port FTP服务器端口
* @param username FTP登录账号
* @param password FTP登录密码
* @param basePath FTP服务器基础目录
* @param filePath FTP服务器文件存放路径。例如分日期存放:/2015/01/01。文件的路径为basePath+filePath
* @param filename 上传到FTP服务器上的文件名
* @return 成功返回true,否则返回false
*/
public static boolean uploadFileToFtp(String host, String port, String username, String password, String basePath,
String filePath, String filename, ByteArrayInputStream input, OutputStream fileOut) {
FTPClient ftp = new FTPClient();
try {
//开启ftp连接
boolean result = connectFtp(ftp, host, port, username, password, basePath, filePath);
if(!result){
return result;
}
if (FTPReply.isPositiveCompletion(ftp.getReplyCode())) {
// 开启服务器对UTF-8的支持,如果服务器支持就用UTF-8编码,否则就使用本地编码(GBK).
if (FTPReply.isPositiveCompletion(ftp.sendCommand("OPTS UTF8", "ON"))) {
LOCAL_CHARSET = "UTF-8";
}
}
//防止中文乱码
filename = new String(filename.getBytes(LOCAL_CHARSET),SERVER_CHARSET );
//为了加大上传文件速度,将InputStream转成BufferInputStream , InputStream input
BufferedInputStream in = new BufferedInputStream(input);
//加大缓存区
ftp.setBufferSize(1024*1024);
//设置上传文件的类型为二进制类型
ftp.setFileType(FTP.BINARY_FILE_TYPE);
//上传文件
if (!ftp.storeFile(filename, in)) {
return false;
}
//写一份给客户端
FTPFile[] fs = ftp.listFiles();
for (FTPFile ff : fs) {
if (ff.getName().equals(filename)) {
ftp.retrieveFile(ff.getName(), fileOut);
fileOut.close();
}
}
in.close();
ftp.logout();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (ftp.isConnected()) {
try {
ftp.disconnect();
} catch (IOException ioe) {
}
}
}
return true;
} /**
* 连接ftp服务器并切换到目的目录
* 调用此方法需手动关闭ftp连接
* @param ftp
* @param host
* @param port
* @param username
* @param password
* @param basePath
* @param filePath
* @return
*/
private static boolean connectFtp( FTPClient ftp,String host, String port, String username, String password, String basePath, String filePath){
boolean result = false;
try {
int portNum = Integer.parseInt(port);
int reply;
// 连接FTP服务器
ftp.connect(host, portNum);
// 如果采用默认端口,可以使用ftp.connect(host)的方式直接连接FTP服务器
ftp.login(username, password);
reply = ftp.getReplyCode();
if (!FTPReply.isPositiveCompletion(reply)) {
ftp.disconnect();
return result;
}
//切换到上传目录
if (!ftp.changeWorkingDirectory(basePath+filePath)) {
//如果目录不存在创建目录
String[] dirs = filePath.split("/");
String tempPath = basePath;
for (String dir : dirs) {
if (null == dir || "".equals(dir)) {
continue;
}
tempPath += "/" + dir;
if (!ftp.changeWorkingDirectory(tempPath)) {
if (!ftp.makeDirectory(tempPath)) {
return result;
} else {
ftp.changeWorkingDirectory(tempPath);
}
}
}
}
result = true;
} catch (IOException e) {
e.printStackTrace();
}
return result;
}
}
主要是 我们为了加快文件上传速度把刚才传入的 ByteArrayInputStream 转换成了 BufferedInputStream
BufferedInputStream in = new BufferedInputStream(input);
然后我们把 BufferedInputStream 写入到ftp服务器
ftp.storeFile(filename, in)
到这里第一步就完成了,接下来是从服务器下载我们刚才上传 文件,然后通过刚才传入的 输出流 fileOut 输出到客户端
//写一份给客户端
FTPFile[] fs = ftp.listFiles();
for (FTPFile ff : fs) {
if (ff.getName().equals(filename)) {
ftp.retrieveFile(ff.getName(), fileOut);
fileOut.close();
}
}