【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()方法后面的操作引起流未关闭。
解决方法
- 保留上传文件,直接在上传文件的实际文件名上面加时间戳,使每次上传的文件名都不一样
- 不保存上传文件,可以以流的形式读取文件内容
我这里不需要保留文件,所以将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预览、上传功能的实现,以上方法亲测有效,希望能给大家一个参考。
创作不易,关注💖、点赞👍、收藏🎉就是对作者最大的鼓励👏,欢迎在下方评论留言🧐