SpringBoot + Vue以文件流的形式预览FTP服务器的PDF文件 3246次阅读 默认分类 2021-02-05 ### 本文将尽可能详细的描述以下功能: 1.前端点击预览按钮,传参到后端 2.后端连接FTP服务器,然后返回文件流 3.前端打开新页签,以流的形式预览PDF文件 **用到的技术:springboot、vue、pdf.js** **附pdf.js的下载地址** ## 正文 **1.第一步**,下载pdfjs。 下载地址:[https://github.com/goSunadeod/vue-pdf.js-demo](https://github.com/goSunadeod/vue-pdf.js-demo "vue-pdf.js-demo") 帅气的博主提供的本地下载:[vue-pdf.js-demo-master.zip](https://linxuan.fun/usr/uploads/2021/02/3069154048.zip) 将其中的`static`下的`pdf`文件夹复制到VUE项目的静态资源目录即可,static或者public之类的,按各人的静态目录具体配置来。  **2.第二步**,前端展示。 这里可以用`iframe`展示,也可以打开一个新页签展示。我用的是新页签展示 `iframe方式`:在页面合适的地方放上iframe ``` ``` `新页签方式`: ``` // 后台接口地址 let url = baseURL + '/rs/project/previewPDF?id='+row.id this.pdfUrl = `/pdf/web/viewer.html?file=${encodeURIComponent(url)}` if (this.pdfUrl == null || this.pdfUrl == '') { Message({ message: 'pdf文件路径为空,无法预览!', type: 'error', duration: 5 * 1000 }) return } // 新建页签预览 window.open(this.pdfUrl, '_blank') // 新建页签预览 ``` **如果有加密解密相关的可以看最下面的参考资料** ## 如果还是不知道怎么做,可以继续往下看前后端完整代码(超详细) ### 前端 ``` // 前面是一些vue的import var baseURL = process.env.VUE_APP_BASE_API; // 浏览器下载文件前缀 export default { data() { return { pdfUrl:'' } // 点击预览按钮的方法 // 预览PDF handleEdit(index, row){ this.pdfUrl = '' // 后台接口地址 // 这里的row.id是我要传到后台的参数,用来查询数据库的文件名 let url = baseURL + '/rs/project/previewPDF?id='+row.id // pdfjs预览文件页面的路径,我的从public开始解析,所以是/pdf/web/viewer.html // 此处注意不是单引号,而是反引号,Tab键上面那个键!!!不这样写最后那个url就被解析为字符串了 this.pdfUrl = `/pdf/web/viewer.html?file=${encodeURIComponent(url)}` if (this.pdfUrl == null || this.pdfUrl == '') { Message({ message: 'pdf文件路径为空,无法预览!', type: 'error', duration: 5 * 1000 }) return } // 1.新建页签预览 window.open(this.pdfUrl, '_blank') // 新建页签预览/下载 // 2.用弹框的方式 在本页面预览 // this.dialogVisibleEdit = true }, // 下载PDF文件 download(index, row){ if (row.id != null && row.id != '') { try { // 下载接口 let url = baseURL + '/rs/project/downloadPDF?id='+row.id + '&storeType='; let link = document.createElement('a'); // 不显示 link.style.display = 'none'; link.href = url; $("body").append(link); // 修复firefox中无法触发click link.click(); $(link).remove(); } catch (error) { Message({ message: '下载失败!错误信息:'+error, type: 'error', duration: 5 * 1000 }) } } else { Message({ message: '参数id为空,无法下载!', type: 'error', duration: 5 * 1000 }) return } }, ``` ### 后端Controller ``` // 需要注意的只有请求方式为Get,以及参数接收 @ApiOperation(value = "预览PDf", notes = "无") @GetMapping("/previewPDF") public ReturnVO previewPDF(HttpServletRequest request, HttpServletResponse response, Long id){ return rsMonitorTaskLogService.previewPDF(request, response, id); } ``` ### 后端Service ``` /** * 文件流形式预览PDF */ @Override public ReturnVO previewPDF(HttpServletRequest request, HttpServletResponse response, Long id) { log.info("预览PDf -> {}", id); if (id == null) { return new ReturnVO<>(ReturnEnum.ERROR_POST_PARAM); } RsMonitorTaskLog sysTaskLog =baseMapper.selectById(id); String pdfName = sysTaskLog.getPdfName(); // xxx.pdf // ftpProperties是读取的FTP配置文件,百度一下:“springboot获取配置文件的参数” String host = ftpProperties.getHost(); String port = ftpProperties.getPort().toString(); String username = ftpProperties.getUsername(); String pwd = ftpProperties.getPassword(); String path = ftpProperties.getPdfPath(); // 服务器储存pdf文件的路径 String encode = ftpProperties.getEncoding(); // inline即返回文件流,attachment即浏览器下载 response.setHeader("Content-Disposition", "inline; filename="+pdfName); // 文件流 // response.setHeader("Content-Disposition", "attachment; filename="+pdfName); // 下载 try { // 通过配置的文件路径+文件名下载 FTPUtil.downFileByBrowser(host, port, username, pwd, encode, path, pdfName, request, response); } catch (Throwable e) { log.error("下载PDf出现错误!"); e.printStackTrace(); return new ReturnVO<>(ReturnEnum.FAILED.getCode(), e.getMessage()); } return new ReturnVO<>(ReturnEnum.SUCCESS); } ``` ### 后端application-dev.yml配置文件 ``` #FTP配置 ftp: host: 192.168.60.54 port: 21 username: ftpuser password: ftpuser #imagesPath: /ftp/images pdfPath: /ftp/pdfs encoding: GBK ``` ### 后端FTPUtil工具类 ``` import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.commons.lang3.StringUtils; import org.apache.commons.net.ftp.FTPClient; import org.apache.commons.net.ftp.FTPReply; import lombok.extern.slf4j.Slf4j; /** * FTP工具类(部分) * @author 临轩 * 2021-02-01 */ @Slf4j public class FTPUtil { /** * 登陆FTP服务器 * @param host * @param port * @param username * @param pwd * @param encoding * @return * @throws Exception */ public static FTPClient login(String host, String port, String username, String pwd, String encoding) throws Exception { FTPClient ftpClient = new FTPClient(); // 设置文件传输的编码 ftpClient.setControlEncoding(encoding); if (StringUtils.isBlank(port)) { ftpClient.connect(host); } else { ftpClient.connect(host, Integer.parseInt(port)); } if (ftpClient.login(username, pwd) == false) { throw new Exception("FTP登陆失败,请检查用户名和密码"); } /** * 设置传输的文件类型 * BINARY_FILE_TYPE:二进制文件类型 * ASCII_FILE_TYPE:ASCII传输方式,这是默认的方式 .... */ ftpClient.setFileType(FTPClient.BINARY_FILE_TYPE); /** * 确认应答状态码是否正确完成响应 * 凡是 2开头的 isPositiveCompletion 都会返回 true,因为它底层判断是: * return (reply >= 200 && reply < 300); */ if (!FTPReply.isPositiveCompletion(ftpClient.getReplyCode())) { /** * 如果 FTP 服务器响应错误 中断传输、断开连接 * abort:中断文件正在进行的文件传输,成功时返回 true,否则返回 false * disconnect:断开与服务器的连接,并恢复默认参数值 */ ftpClient.abort(); ftpClient.disconnect(); log.warn("FTP server refused connection."); return null; } log.debug("FTP客户端登陆成功,连接信息:host=" + host + ", port=" + port + ", encoding=" + encoding); return ftpClient; } /** * 关闭连接 ,释放资源 * @param ftpClient * @return */ public static FTPClient closeFTPconnect(FTPClient ftpClient) { try { if (ftpClient != null && ftpClient.isConnected()) { ftpClient.abort(); ftpClient.disconnect(); } log.debug("关闭FTP连接成功"); } catch (Exception e) { log.error("关闭FTP连接失败:", e); } return ftpClient; } /** * 通过浏览器下载文件 * @param host * @param port * @param username * @param password * @param encode * @param path * @param fileName * @param request * @param response * @return * @throws Throwable */ public static int downFileByBrowser(String host, String port, String username, String password, String encode, String path, String fileName, HttpServletRequest request, HttpServletResponse response) throws Throwable { FTPClient ftpClient = null; try { ftpClient = login(host, port, username, password, encode); // 切换工作目录 if (!ftpClient.changeWorkingDirectory(path)) { throw new Exception("切换工作目录失败:" + path); } // 下载文件到HTTP流 ftpClient.retrieveFile(fileName, response.getOutputStream()); return 0; } finally { // 关闭输出流 try { if (response.getOutputStream() != null) { response.getOutputStream().flush(); response.getOutputStream().close(); } } catch (Exception e) { log.warn("HttpServletResponse输出流关闭失败:", e); } // 关闭FTP连接资源 closeFTPconnect(ftpClient); } } } ``` ### 完成图  ### 教程完结!感谢阅读,欢迎在下方评论~ 网上其他的教程都写的是一半或者已失效,浪费了许多时间,只有这个是可行的。希望让后来人少走弯路…… 参考资料:[https://www.cnblogs.com/…](https://www.cnblogs.com/usebtf/p/10329977.html "https://www.cnblogs.com/……") 如果没有测试的PDF文件,可以用 [这个->test.pdf](https://linxuan.fun/usr/uploads/2021/02/116961704.pdf) ,实际上新建一个Word,然后另存为PDF就可以了 Alipay手机上阅读 最后一次更新于2021-02-05 None
本人是小白,ReturnVO能不能也贴出来下,
By 请教 at 2022-4-26 17:30:09
@请教
就是一个包含了code、msg、data的返回包装类,可以用枚举快速创建code和msg。这个网上一大堆,不用抄我的
By 临轩 at 2022-4-26 18:03:01
ReturnVOs是啥
By 哈哈哈 at 2022-2-17 16:32:23
@哈哈哈
哪有ReturnVOs?ReturnVO是封装的返回包装类,code、msg、data这些。
By 临轩 at 2022-2-17 16:39:03