【Java实战】Java实现PDF在线预览、上传功能

前言

最近需要实现在浏览器上预览 PDF 并能提供下载的功能,这里对功能的实现做一个简单的记录

一、功能实现

PDF 的预览网上说的最多的是PDF.JS实现预览,作者对这个接触的不多,所以第一时间还是想的后台通过文件流操作,利用各个浏览器的内核支持PDF预览功能来实现。

二、PDF在线预览

1.方法一

将需要在浏览器上预览的PDF放在静态文件夹下,使用ClassLoader获取class路径得到pdf文件的目录,通过流操作实现预览

代码如下(示例):

@ResponseBody
@RequestMapping("/preview")
public void preview(HttpServletResponse response, boolean flag) {
    String filePath = this.getClass().getClassLoader().getResource("../../static/pdf/readme.pdf").getPath();
    System.out.println("filePath:" + filePath);
    File f = new File(filePath);
    if (StringUtils.isBlank(filePath)) {
        LogUtil.appLog.info("文件不存在!");
        return;
    }
    BufferedInputStream br = null;
    OutputStream out = null;
    try {
        br = new BufferedInputStream(new FileInputStream(f));
        byte[] bs = new byte[1024];
        int len = 0;
        response.reset(); // 非常重要
        if (flag) {
            // 在线打开方式
            URL u = new URL("file:///" + filePath);
            String contentType = u.openConnection().getContentType();
            response.setContentType(contentType);
            response.setHeader("Content-Disposition", "inline;filename="
                    + "操作手册V1.0.pdf");
        } else {
            // 纯下载方式
            //response.setContentType("application/x-msdownload");
            response.setContentType("application/pdf;charset=utf-8");
            response.setHeader("Content-Disposition", "attachment;filename="
                    + "操作手册V1.0.pdf");
        }
        out = response.getOutputStream();
        while ((len = br.read(bs)) > 0) {
            out.write(bs, 0, len);
        }
        out.flush();
        out.close();
        br.close();
    } catch (IOException e) {
        e.printStackTrace();
        LogUtil.appLog.info("pdf处理文件异常" + e);
    } finally {
        if (out != null) {
            try {
                out.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

2.方法二

引入IOUtils来实现,这个里面的流不需要关闭,浏览器会自动获取然后展示。关闭的话浏览器就无法加载!

代码如下(示例):

@RequestMapping(value = "/preview")
public void showPdf(HttpServletResponse response) {
    try {
        String filePath = this.getClass().getClassLoader().getResource("../../static/pdf/readme.pdf").getPath();
        File file = new File(filePath);
        FileInputStream fileInputStream = new FileInputStream(file);
        response.setHeader("Content-Type", "application/pdf");
        OutputStream outputStream = response.getOutputStream();
        IOUtils.write(IOUtils.toByteArray(fileInputStream), outputStream);
    } catch(Exception e) {
        e.printStackTrace();
    }
}

2022-08-05更新

三、PDF文件上传

引入css样式

<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css"
integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">

前端代码

<form action = "" method = "post" enctype= "multipart/form-data">
	<div class="centerEle">
		<div class="form-group col-md-3 mb-2" style="float: left;width: 230px;">
			<input type="text" name="file" id="file" placeholder="未选择文件" disabled autocomplete="off" class="form-control">
			<input type="file" id="file_upload" style="display: none" onchange="reShow();" accept=".pdf"/>
		</div>
		<div style="float: left;width: 100px;margin-left: -12px;">
			<label class="btn btn-primary mb-2" for="file_upload">浏览</label>
		</div>
	</div>
</form>

ajax实现文件上传,这里通过Formdata实现的

function reShow(){
    $('#file').val($('#file_upload').val());
}

function submitForm() {
    let form = new FormData();
    form.append("file", $('#file_upload')[0].files[0]);
    $.ajax({
        type: "POST",
        url: contextPath + "/scenic/fdPdfInfo/upload",
        beforeSend: function (XMLHttpRequest) {
            XMLHttpRequest.setRequestHeader("Token", localStorage.token);
        },
        data: form,
        contentType: false,
        processData: false,
        success: function (r) {
            if (r.apiResult.code === 0) {
                messageSuccess("上传成功");
            } else {
                message("错误", r.apiResult.message);
            }
        }
    });
}

后台代码

/**
 * 上传pdf文件
 *
 * @param file
 * @return
 */
@RequestMapping(value = "/upload")
public String upload(MultipartFile file) {
    FdPdfInfoVO vo = new FdPdfInfoVO();
    String operateUserName = UserSessionContext.getLoginUserName();
    // 文件名
    String originalFileName = file.getOriginalFilename().toString();
    if (!file.getName().matches("^.+\\.(?i)(pdf)$")
            && !file.getName().matches("^.+\\.(?i)(PDF)$")
            && !originalFileName.matches("^.+\\.(?i)(pdf)$")
            && !originalFileName.matches("^.+\\.(?i)(PDF)$")) {
        return "文件格式不正确!";
    }

    try {
        byte[] bytes = file.getBytes();
        LogUtil.appLog.info("上传PDF文件:" + originalFileName);
        // 生成文件路径
        String filePath = basic_path + "readme.pdf";
        File dest = new File(filePath);
        // 判断文件父目录是否存在
        if (!dest.getParentFile().exists()) {
        	dest.getParentFile().mkdir();
        }
        // 把MultipartFile转换成File
        file.transferTo(dest);
        // 相关数据保存到数据库
        vo.setFileName(originalFileName);
        vo.setFilePath(filePath);
        vo.setCreateUserName(operateUserName);
        service.insert(vo);
        LogUtil.appLog.info("上传PDF文件成功");
        return "上传PDF文件成功";
    } catch (Exception e) {
        LogUtil.appLog.info("上传PDF文件异常:" + e);
        e.printStackTrace();
    }
    return "上传PDF文件失败!";
}

过程中出现几个问题

1.上传文件名出现中文乱码

将获取的文件名做编码处理

originalFileName = new String(file.getOriginalFilename().getBytes("ISO-8859-1"), "UTF-8");

2.同名文件出现上传报错

already exists and could not be deleted

本来想的是,上传之前判断是否存在同名文件,有就删除

if (dest.getParentFile().exists()) {
	// 删除文件
	LogUtil.appLog.info("删除文件:" + dest.getName());
	dest.delete();
}				

结果发现,还是报错。原因是transferTo()方法后面的操作引起流未关闭

解决方法

  1. 保留上传文件,直接在上传文件的实际文件名上面加时间戳,使每次上传的文件名都不一样
  2. 不保存上传文件,可以以流的形式读取文件内容

我这里不需要保留文件,所以将transferTo()方法替换掉了

FileUtils.writeByteArrayToFile(dest, bytes);

修改之后代码如下:

/**
 * 上传pdf文件
 *
 * @param file
 * @return
 */
@RequestMapping(value = "/upload")
public String upload(MultipartFile file) {
    FdPdfInfoVO vo = new FdPdfInfoVO();
    String operateUserName = UserSessionContext.getLoginUserName();
    // 文件名
    String originalFileName = file.getOriginalFilename().toString();
    if (!file.getName().matches("^.+\\.(?i)(pdf)$")
            && !file.getName().matches("^.+\\.(?i)(PDF)$")
            && !originalFileName.matches("^.+\\.(?i)(pdf)$")
            && !originalFileName.matches("^.+\\.(?i)(PDF)$")) {
        return "文件格式不正确!";
    }

    try {
        byte[] bytes = file.getBytes();
        originalFileName = new String(file.getOriginalFilename().getBytes("ISO-8859-1"), "UTF-8");
        LogUtil.appLog.info("上传PDF文件:" + originalFileName);
        // 生成文件路径
        String filePath = basic_path + "readme.pdf";
        File dest = new File(filePath);
        // 判断文件父目录是否存在
        if (!dest.getParentFile().exists()) {
        	dest.getParentFile().mkdir();
        }
     	FileUtils.writeByteArrayToFile(dest, bytes);
        // 相关数据保存到数据库
        vo.setFileName(originalFileName);
        vo.setFilePath(filePath);
        vo.setCreateUserName(operateUserName);
        service.insert(vo);
        LogUtil.appLog.info("上传PDF文件成功");
        return "上传PDF文件成功";
    } catch (Exception e) {
        LogUtil.appLog.info("上传PDF文件异常:" + e);
        e.printStackTrace();
    }
    return "上传PDF文件失败!";
}

2022-08-10更新

发现上传文件过大的话,等待时间有点久,所以觉得加一个loading动画。

这里通过ajax的beforeSend方法设置预加载动画加强用户体验

新增代码css、js

<div id="preloader">
    <span></span>
    <span></span>
    <span></span>
    <span></span>
    <span></span>
</div>
#preloader_1{
    left: 185px;
    top: 80px;
    position:relative;
}
#preloader_1 span{
    display:block;
    bottom:0px;
    width: 9px;
    height: 5px;
    background:#9b59b6;
    position:absolute;
    animation: preloader_1 1.5s  infinite ease-in-out;
}
#preloader_1 span:nth-child(2){
    left:11px;
    animation-delay: .2s;
}
#preloader_1 span:nth-child(3){
    left:22px;
    animation-delay: .4s;
}
#preloader_1 span:nth-child(4){
    left:33px;
    animation-delay: .6s;
}
#preloader_1 span:nth-child(5){
    left:44px;
    animation-delay: .8s;
}
@keyframes preloader_1 {
    0% {height:5px;transform:translateY(0px);background:#9b59b6;}
    25% {height:30px;transform:translateY(15px);background:#3498db;}
    50% {height:5px;transform:translateY(0px);background:#9b59b6;}
    100% {height:5px;transform:translateY(0px);background:#9b59b6;}
}
function reShow(){
    $('#file').val($('#file_upload').val());
}

function submitForm() {
    let form = new FormData();
    form.append("file", $('#file_upload')[0].files[0]);
    $.ajax({
        type: "POST",
        url: contextPath + "/scenic/fdPdfInfo/upload",
        beforeSend: function (XMLHttpRequest) {
            XMLHttpRequest.setRequestHeader("Token", localStorage.token);
            $('#preloader').show();
        },
        data: form,
        contentType: false,
        processData: false,
        success: function (r) {
        	$('#preloader').hide();
            if (r.apiResult.code === 0) {
                messageSuccess("上传成功");
            } else {
                message("错误", r.apiResult.message);
            }
        },
        complete: function complete(XMLHttpRequest, textStatus){
            $('#preloader').hide();
        }
    });
}

实现效果

总结

以上就是今天要讲的内容,本文仅仅简单介绍了pdf预览、上传功能的实现,以上方法亲测有效,希望能给大家一个参考。

创作不易,关注💖、点赞👍、收藏🎉就是对作者最大的鼓励👏,欢迎在下方评论留言🧐

posted on 2022-08-02 10:38  猫的树kireCat  阅读(2878)  评论(0编辑  收藏  举报