业务需求:前端集成pdf.js 实现在线阅读pdf 文件,但pdf 文件过大时(大于100M)会出现浏览器内存溢出导出程序崩溃的场景发生。针对这个情况,后端给出的解决方案是:分片加载pdf 文件流。
约定:与前端约定在header 头部参数中追加rang 参数:表示需要加载文件片数,后台动态计算起始字节位置,流式输出指定文件内容。
SpringBoot源码:
pom.xml:
4.0.0 com.single Single0.0.1-SNAPSHOT org.springframework.boot spring-boot-starter-parent2.1.9.RELEASE 1.8 3.1.1 org.springframework.boot spring-boot-starter-weborg.springframework.boot spring-boot-starter-testtest org.springframework.boot spring-boot-maven-plugin
application 程序启动窗口
package com.single;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class SingleApplication {
public static void main(String[] args) {
// TODO Auto-generated method stub
SpringApplication.run(SingleApplication.class, args);
}
}
Controller
package com.single.controller;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
@Controller
@RequestMapping("/api/file")
public class FileController {
public static final Logger logger = LoggerFactory.getLogger(FileController.class);
@RequestMapping(value = "/showpdf", method = RequestMethod.GET)
public void showpdf(HttpServletRequest req, HttpServletResponse res) {
try {
this.download(req, res);
} catch (Exception e) {
// e.printStackTrace();
logger.error("错误信息:{}", e.getMessage(), e);
}
}
public void download(HttpServletRequest request, HttpServletResponse response) throws IOException {
// 从文件存储服务器下载文件到本地
File file = new File("D:/test.pdf");
BufferedInputStream bis = null;
OutputStream os = null;
BufferedOutputStream bos = null;
InputStream is = null;
try {
is = new FileInputStream(file);
bis = new BufferedInputStream(is);
os = response.getOutputStream();
bos = new BufferedOutputStream(os);
// 下载的字节范围
int startByte, endByte, totalByte;
if (request != null && request.getHeader("range") != null) {
// 起始字节大小
String[] range = request.getHeader("range").replaceAll("[^0-9\-]", "").split("-");
// 文件总大小
totalByte = is.available();
// 下载起始位置
startByte = Integer.parseInt(range[0]);
// 下载结束位置
if (range.length > 1) {
endByte = Integer.parseInt(range[1]);
} else {
endByte = totalByte - 1;
}
// 返回http状态
response.setStatus(206);
} else {
// 正常下载
// 文件总大小
totalByte = is.available();
// 下载起始位置
startByte = 0;
// 下载结束位置
endByte = totalByte - 1;
// 返回http状态
response.setHeader("Accept-Ranges", "bytes");
response.setStatus(206);
}
// 需要下载字节数
int length = endByte - startByte;
// 响应头
response.setHeader("Accept-Ranges", "bytes");
response.setHeader("Content-Range", "bytes " + startByte + "-" + endByte + "/" + totalByte);
response.setContentType("Content-Type: application/octet-stream");
response.setContentLength(length);
// 响应内容
bis.skip(startByte);
int len = 0;
byte[] buff = new byte[1024 * 64];
while ((len = bis.read(buff, 0, buff.length)) != -1) {
if (length <= len) {
bos.write(buff, 0, length);
break;
} else {
length -= len;
bos.write(buff, 0, len);
}
}
} catch (IOException e) {
System.out.println("下载中断!");
logger.error("错误信息:{}", e.getMessage(), e);
} finally {
bos.close();
os.close();
bis.close();
is.close();
}
}
}
Filter
package com.single.filter;
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;
@Component
public class CROSFilter implements Filter {
public static final Logger logger = LoggerFactory.getLogger(CROSFilter.class);
@Override
public void init(FilterConfig filterConfig) throws ServletException {
// TODO Auto-generated method stub
}
@Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
HttpServletResponse response = (HttpServletResponse) res;
HttpServletRequest reqs = (HttpServletRequest) req;
response.setHeader("Access-Control-Allow-Origin","*");
response.setHeader("Access-Control-Allow-Credentials", "true");
response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE");
response.setHeader("Access-Control-Max-Age", "3600");
response.setHeader("Access-Control-Allow-Headers", "Content-Type");
if (((HttpServletRequest) req).getMethod().equals("OPTIONS")) {
response.getWriter().println("ok");
return;
}
chain.doFilter(req, res);
}
@Override
public void destroy() {
// TODO Auto-generated method stub
}
}
application.properties
# 指定服务端口 server.port=9092 # 指定日志文件配置 logging.config=classpath:logback.xml
logback.xml
%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger -%msg%n ERROR ACCEPT DENY ${log_dir}/%d{yyyy-MM-dd}/error.log ${maxHistory} %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger - %msg%n WARN ACCEPT DENY ${log_dir}/%d{yyyy-MM-dd}/warn.log ${maxHistory} %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger - %msg%n INFO ACCEPT DENY ${log_dir}/%d{yyyy-MM-dd}/info.log ${maxHistory} %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger - %msg%n DEBUG ACCEPT DENY ${log_dir}/%d{yyyy-MM-dd}/debug.log ${maxHistory} %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger - %msg%n
PostMan 模拟工具效果展示:
基本功能已经完成,静待前端工程师的对接。



