文件上传
package com.example.demo.base.file;
import java.io.*;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.FilenameUtils;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.HttpClients;
import org.apache.tomcat.util.http.fileupload.FileItem;
import org.apache.tomcat.util.http.fileupload.RequestContext;
import org.apache.tomcat.util.http.fileupload.disk.DiskFileItemFactory;
import org.apache.tomcat.util.http.fileupload.servlet.ServletFileUpload;
import org.springframework.stereotype.Controller;
import org.springframework.util.ResourceUtils;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
/**
* @author CQO
* @since 2023/03/04 18:34
*/
@RestController
@RequestMapping("fileUpload")
public class FileController {
@PostMapping("upload")
public String upload(MultipartFile file) throws IOException {
// 获取上传文件的原始名称
String originalFilename = file.getOriginalFilename();
// 获取文件后缀
String extension = ".".concat(FilenameUtils.getExtension(originalFilename));
// 生成新的文件名称
String newFile = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmssSSS")).concat(extension);
// 获取文件大小
long size = file.getSize();
// 判断文件类型
String contentType = file.getContentType();
// 根据日期生成目录
String realPath = ResourceUtils.getURL("classpath:").getPath() + "/static/files";
// 文件夹路径
String format = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMdd"));
String dataDirPath = realPath.concat("/").concat(format);
File fileDir = new File(dataDirPath);
// 如果文件夹不存在,则创建文件夹
if (!fileDir.exists()) {
fileDir.mkdirs();
}
file.transferTo(new File(fileDir, newFile));
// 返回文件路径和文件名称,用于下载
return "Ok";
}
private final static String utf8 = "utf-8";
// 前端组件是以及webUpload
/**
* 分片上传
*
* @param httpServletRequest
* @param httpServletResponse
* @return
* @throws Exception
*/
@PostMapping("chunkUpload")
public void chunkUpload(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse)
throws Exception {
// 分片
httpServletResponse.setCharacterEncoding(utf8);
// 当前分片
Integer chunk = null;
// 分片总数
Integer chunks = null;
String name = null;
// 上传路径
String uploadPath = "C:\\Users\\ChenQ\\Pictures\\TestFile";
BufferedOutputStream os = null;
try {
DiskFileItemFactory diskFileItemFactory = new DiskFileItemFactory();
// 设置缓冲区,暂时写入内存,不写入硬盘,1MB=1024KB=1048576字节
diskFileItemFactory.setSizeThreshold(1048576);
// 设置存储文件地址
diskFileItemFactory.setRepository(new File(uploadPath));
ServletFileUpload upload = new ServletFileUpload(diskFileItemFactory);
// 设置文件上传分片大小
upload.setFileSizeMax(5L * 1024L * 1024L * 1024L);
// 设置文件上传总大小
upload.setSizeMax(10L * 1024L * 1024L * 1024L);
List<FileItem> fileItems = upload.parseRequest((RequestContext)httpServletRequest);
// 获取文件属性赋值给变量
for (FileItem item : fileItems) {
if (!item.isFormField()) {
if ("chunk".equals(item.getFieldName())) {
chunk = Integer.parseInt(item.getString(utf8));
}
if ("chunks".equals(item.getFieldName())) {
chunks = Integer.parseInt(item.getString(utf8));
}
if ("name".equals(item.getFieldName())) {
name = item.getString(utf8);
}
}
}
for (FileItem item : fileItems) {
if (!item.isFormField()) {
String temFileName = name;
if (name != null) {
if (chunk != null) {
temFileName = chunk + "_" + name;
}
// 判断文件是否存在
File temFile = new File(uploadPath, temFileName);
if (!temFile.exists()) {
// 分片断点续传
item.write(temFile);
}
}
}
}
// 分片合并
if (chunk != null && chunk == chunks - 1) {
File tempFile = new File(uploadPath, name);
os = new BufferedOutputStream(new FileOutputStream(tempFile));
for (int i = 0; i < chunks; i++) {
File file = new File(uploadPath, i + "_" + name);
while (!file.exists()) {
Thread.sleep(100);
}
byte[] bytes = FileUtils.readFileToByteArray(file);
os.write(bytes);
os.flush();
file.delete();
}
os.flush();
}
httpServletResponse.getWriter().write("上传成功" + name);
} finally {
try {
if (os != null) {
os.close();
}
} catch (Exception exception) {
exception.printStackTrace();
}
}
}
/**
* 文件分片下载服务端
* @param request
* @param response
* @throws IOException
*/
@RequestMapping("/down")
public void down(HttpServletRequest request, HttpServletResponse response) throws IOException {
response.setCharacterEncoding(utf8);
// 定义文件路径
File file = new File("D:\\File\\a.mp4");
InputStream is = null;
OutputStream os = null;
try {
// 分片下载
long fSize = file.length();// 获取长度
response.setContentType("application/x-download");
String fileName = URLEncoder.encode(file.getName(), utf8);
response.addHeader("Content-Disposition", "attachment;filename=" + fileName);
// 根据前端传来的Range 判断支不支持分片下载
response.setHeader("Accept-Range", "bytes");
// 获取文件大小
response.setHeader("fSize", String.valueOf(fSize));
response.setHeader("fName", fileName);
// 定义断点
long pos = 0, last = fSize - 1, sum = 0;
// 判断前端需不需要分片下载
if (null != request.getHeader("Range")) {
response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);
String numRange = request.getHeader("Range").replaceAll("bytes=", "");
String[] strRange = numRange.split("-");
if (strRange.length == 2) {
pos = Long.parseLong(strRange[0].trim());
last = Long.parseLong(strRange[1].trim());
// 若结束字节超出文件大小 取文件大小
if (last > fSize - 1) {
last = fSize - 1;
}
} else {
// 若只给一个长度 开始位置一直到结束
pos = Long.parseLong(numRange.replaceAll("-", "").trim());
}
}
long rangeLenght = last - pos + 1;
String contentRange =
new StringBuffer("bytes").append(pos).append("-").append(last).append("/").append(fSize).toString();
response.setHeader("Content-Range", contentRange);
response.setHeader("Content-Lenght", String.valueOf(rangeLenght));
os = new BufferedOutputStream(response.getOutputStream());
is = new BufferedInputStream(new FileInputStream(file));
is.skip(pos);// 跳过已读的文件
byte[] buffer = new byte[1024];
int lenght = 0;
// 相等证明读完
while (sum < rangeLenght) {
lenght =
is.read(buffer, 0, (rangeLenght - sum) <= buffer.length ? (int)(rangeLenght - sum) : buffer.length);
sum = sum + lenght;
os.write(buffer, 0, lenght);
}
System.out.println("下载完成");
} finally {
if (is != null) {
is.close();
}
if (os != null) {
os.close();
}
}
}
private final static long per_page = 1024l * 1024l * 50l;
// 分片存储临时目录 当分片下载完后在目录中找到文件合并
private final static String down_path = "D:\\File";
// 多线程下载
ExecutorService pool = Executors.newFixedThreadPool(10);
// 文件大小 分片数量 文件名称
// 使用探测 获取变量
// 使用多线程分片下载
// 最后一个分片下载完 开始合并
@RequestMapping("/downloadFile")
public String downloadFile() throws IOException {
FileInfo fileInfo = download(0, 10, -1, null);
if (fileInfo != null) {
long pages = fileInfo.fSize / per_page;
for (int i = 0; i <= pages; i++) {
pool.submit(new Download(i * per_page, (i + 1) * per_page - 1, i, fileInfo.fName));
}
}
return "成功";
}
class Download implements Runnable {
long start;
long end;
long page;
String fName;
public Download(long start, long end, long page, String fName) {
this.start = start;
this.end = end;
this.page = page;
this.fName = fName;
}
@Override
public void run() {
try {
FileInfo fileInfo = download(start, end, page, fName);
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* 客户端分片下载,指定固定文件
* @param start
* @param end
* @param page
* @param fName
* @return
* @throws IOException
*/
@RequestMapping("/download")
private FileInfo download(long start, long end, long page, String fName) throws IOException {
// 断点下载 文件存在不需要下载
File file = new File(down_path, page + "-" + fName);
// 探测必须放行 若下载分片只下载一半就锻炼需要重新下载所以需要判断文件是否完整
if (file.exists() && page != -1 && file.length() == per_page) {
return null;
}
// 需要知道 开始-结束 = 分片大小
HttpClient client = HttpClients.createDefault();
// httpclient进行请求
HttpGet httpGet = new HttpGet("http://127.0.0.1:8080/down");
// 告诉服务端做分片下载
httpGet.setHeader("Range", "bytes=" + start + "-" + end);
HttpResponse response = client.execute(httpGet);
String fSize = response.getFirstHeader("fSize").getValue();
fName = URLDecoder.decode(response.getFirstHeader("fName").getValue(), "utf-8");
HttpEntity entity = response.getEntity();// 获取文件流对象
InputStream is = entity.getContent();
// 临时存储分片文件
FileOutputStream fos = new FileOutputStream(file);
byte[] buffer = new byte[1024];// 定义缓冲区
int ch;
while ((ch = is.read(buffer)) != -1) {
fos.write(buffer, 0, ch);
}
is.close();
fos.flush();
fos.close();
// 判断是不是最后一个分片
if (end - Long.valueOf(fSize) > 0) {
// 合并
try {
mergeFile(fName, page);
} catch (Exception e) {
e.printStackTrace();
}
}
return new FileInfo(Long.valueOf(fSize), fName);
}
private void mergeFile(String fName, long page) throws Exception {
// 归并文件位置
File file = new File(down_path, fName);
BufferedOutputStream os = new BufferedOutputStream(new FileOutputStream(file));
for (int i = 0; i <= page; i++) {
File tempFile = new File(down_path, i + "-" + fName);
// 分片没下载或者没下载完需要等待
while (!file.exists() || (i != page && tempFile.length() < per_page)) {
Thread.sleep(100);
}
byte[] bytes = FileUtils.readFileToByteArray(tempFile);
os.write(bytes);
os.flush();
tempFile.delete();
}
File file1 = new File(down_path, -1 + "-null");
file1.delete();
os.flush();
os.close();
}
// 使用内部类实现
class FileInfo {
long fSize;
String fName;
public FileInfo(long fSize, String fName) {
this.fSize = fSize;
this.fName = fName;
}
}
}