备注
为了方便测试引入了vue.js和axios.js;
文件访问路径
http://localhost:1011/static/files/87badbee40b746dba924beae49f702b8.md
static/ 后面的是数据库存储的路径
数据库表
CREATE TABLE `file_entity` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`file_name` varchar(64) DEFAULT NULL COMMENT '文件名称',
`file_url` varchar(255) DEFAULT NULL COMMENT '文件路径',
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
`update_time` datetime DEFAULT NULL COMMENT '修改时间',
`type` int(11) DEFAULT NULL COMMENT '文件类型',
`del` bit(1) DEFAULT b'0' COMMENT '状态',
`shard_index` bigint(20) DEFAULT NULL COMMENT '已上传分片',
`shard_total` bigint(20) DEFAULT NULL COMMENT '分片总数',
`file_key` varchar(255) DEFAULT NULL COMMENT '文件标识',
`suffix` varchar(64) DEFAULT NULL COMMENT '文件名后缀',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
java代码
pom 依赖
<!-- tkMybatis -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.35</version>
</dependency>
<dependency>
<groupId>tk.mybatis</groupId>
<artifactId>mapper-spring-boot-starter</artifactId>
<version>2.1.2</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.78</version>
</dependency>
配置类
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
/**
* @author zhaohanqing
* @createTime 2022-03-02 16:19
* @description
*/
@Configuration
public class FilePathConfig implements WebMvcConfigurer {
@Value("${file.local.path}")
private String fileLocalPath;
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/static/**").addResourceLocations("file:" + fileLocalPath);
}
}
文件上传路径类
public class UploadPathConstant {
/** 文件上传路径 */
public static final String FILE_VIDEO = "files/";
}
yml配置
file: #文件上传路径配置文件
local:
path: D:/files/
实体类
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.Data;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import java.io.Serializable;
import java.util.Date;
/**
* @author zhaohanqing
* @createTime 2022-04-18 14:26
* @description
*/
@Data
public class FileEntity implements Serializable {
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue(generator = "JDBC")
private Long id;
private String fileName;
/**
* 文件路径
*/
private String fileUrl;
/**
* 创建时间
*/
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Date createTime = new Date();
/**
* 修改时间
*/
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Date updateTime = new Date();
/**
* 文件类型 1 图片,2 视频
*/
private Integer type;
/**
* 状态0:正常, 1:禁用
*/
@JsonIgnore
private Boolean del = false;
/**
* 已上传分片
*/
private Long shardIndex;
/**
* 分片总数
*/
private Long shardTotal;
/**
* 文件标识
*/
@JsonIgnore
private String fileKey;
/**
* 文件名后缀
*/
@JsonIgnore
private String suffix;
}
mapper
import com.store.entity.FileEntity;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import tk.mybatis.mapper.common.Mapper;
import java.util.List;
/**
* @author zhaohanqing
* @createTime 2022-04-18 17:23
* @description
*/
public interface FileEntityMapper extends Mapper<FileEntity> {
/**
* 根据fileKey修改上传下标
* @author zhaohanqing
* @createTime 2022/4/19 15:46
* @param fileEntity
* @return
*/
void updateShardIndexByFileKey(FileEntity fileEntity);
/**
* 查询所有fileKey
* @author zhaohanqing
* @createTime 2022/4/19 15:49
* @param
* @return
*/
@Select("SELECT file_key from file_entity where del = 0")
List<String> selectFileKey();
/**
* 根据fileKey查询数据
* @author zhaohanqing
* @createTime 2022/4/19 15:41
* @param fileKey
* @return
*/
@Select("SELECT COUNT(*) FROM file_entity WHERE del = 0 AND file_key = #{fileKey}")
int selectByFileKey(@Param("fileKey") String fileKey);
}
mapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.store.mapper.FileEntityMapper">
<update id="updateShardIndexByFileKey">
update file_entity
set shard_index = #{shardIndex},
update_time = #{updateTime}
where file_key = #{fileKey}
</update>
service
import com.store.entity.FileEntity;
import com.store.enums.FileTypeEnum;
import com.store.enums.HttpCodeEnum;
import com.store.mapper.FileEntityMapper;
import com.store.service.IFileEntityService;
import com.store.utils.ResultUtil;
import com.store.utils.UUIDUtil;
import org.apache.commons.collections4.CollectionUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import javax.annotation.Resource;
import java.io.*;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* @author zhaohanqing
* @createTime 2022-04-18 14:31
* @description
*/
@Service
public class FileEntityServiceImpl {
private final Logger logger = LoggerFactory.getLogger(FileEntityServiceImpl.class);
@Value("${file.local.path}")
public String fileLocalPath;
@Resource
private FileEntityMapper fileEntityMapper;
/**
*
* @author zhaohanqing
* @createTime 2022/4/19 13:58
* @param fileEntity
* @param uploadFile
* @return
*/
public ResultUtil addFileEntity(FileEntity fileEntity, MultipartFile uploadFile) throws Exception {
if (uploadFile == null && null == fileEntity.getFileKey() && null == fileEntity.getShardIndex() && null == fileEntity.getSuffix()) {
return ResultUtil.error(HttpCodeEnum.ILLEGAL_PARAMS);
}
String localFileName = fileEntity.getFileKey() + "." + fileEntity.getShardIndex();
File targetFile = new File(this.fileLocalPath + UploadPathConstant.FILE_VIDEO, localFileName);
/*
* 判断是否存在文件夹,不存在则新建
*/
if (!targetFile.getParentFile().exists()) {
targetFile.getParentFile().mkdirs();
}
uploadFile.transferTo(targetFile);
fileEntity.setFileUrl(UploadPathConstant.FILE_VIDEO + fileEntity.getFileKey() + "." + fileEntity.getSuffix());
/*
* 将数据持久化到数据库
*/
save(fileEntity);
/*
* 最后一片的时候合并分片
*/
if (fileEntity.getShardIndex().equals(fileEntity.getShardTotal())) {
merge(fileEntity);
}
return ResultUtil.success(HttpCodeEnum.OK);
}
/**
* 将数据写入数据库
* @author zhaohanqing
* @createTime 2022/4/19 15:24
* @param fileEntity
* @return
*/
private void save(FileEntity fileEntity) {
int byFileKey = this.fileEntityMapper.selectByFileKey(fileEntity.getFileKey());
fileEntity.setUpdateTime(new Date());
if (byFileKey == 0) {
fileEntity.setCreateTime(new Date());
this.fileEntityMapper.insert(fileEntity);
return;
}
this.fileEntityMapper.updateShardIndexByFileKey(fileEntity);
}
/**
* 合并分片
* @author zhaohanqing
* @createTime 2022/4/19 15:35
* @param fileEntity
* @return
*/
private void mergeFileEntity(FileEntity fileEntity) throws Exception {
String fileName = fileEntity.getFileKey();
Long shardTotal = fileEntity.getShardTotal();
File newFile = new File(this.fileLocalPath + UploadPathConstant.FILE_VIDEO + fileName + "." + fileEntity.getSuffix());
FileOutputStream outputStream = new FileOutputStream(newFile, true);
FileInputStream fileInputStream = null;
byte[] byt = new byte[10485760];
try {
for (int i = 0; i < shardTotal.longValue(); i++) {
fileInputStream = new FileInputStream(new File(this.fileLocalPath + UploadPathConstant.FILE_VIDEO + fileName + "." + (i + 1)));
int len;
while ((len = fileInputStream.read(byt)) != -1) {
outputStream.write(byt, 0, len);
}
}
} catch (IOException e) {
logger.error("", e);
} finally {
try {
if (fileInputStream != null) {
fileInputStream.close();
}
outputStream.close();
logger.info("close");
} catch (Exception e) {
logger.error("Exception", e);
}
}
System.gc();
Thread.sleep(100L);
for (int i = 0; i < shardTotal; i++) {
String filePath = this.fileLocalPath + UploadPathConstant.FILE_VIDEO + fileName + "." + (i + 1);
File file = new File(filePath);
file.delete();
}
}
controller
import com.store.entity.FileEntity;
import com.store.enums.HttpCodeEnum;
import com.store.service.FileEntityServiceImpl;
import com.store.utils.ResultUtil;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import javax.annotation.Resource;
/**
* @author zhaohanqing
* @createTime 2022-04-18 14:24
* @description 文件管理
*/
@RestController
@RequestMapping("/fileEntity")
public class FileEntityController extends BaseController {
@Resource
private FileEntityServiceImpl fileEntityService;
/**
* 文件分片上传
* @author zhaohanqing
* @createTime 2022/4/18 14:33
* @param fileEntity
* @param uploadFile
* @return
*/
@PostMapping("addFileEntity")
public ResultUtil addFileEntity(FileEntity fileEntity, MultipartFile uploadFile) throws Exception {
if (null == fileEntity || fileEntity.getType() == null) {
return ResultUtil.error(HttpCodeEnum.ILLEGAL_PARAMS);
}
return fileEntityService.addFileEntity(fileEntity, uploadFile);
}
}
前端代码
html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title></title>
<script src="https://zhaohq.life/js/vue.js"></script>
<script src="https://zhaohq.life/js/axios.js"></script>
<script src="https://zhaohq.life/js/uuidv4.min.js"></script>
</head>
<body>
<div id="app">
<input type="file" @change="addFile($event)" />
<div @click="addClick">上传</div>
</div>
</body>
<script>
let app = new Vue({
el: '#app',
data: {
name: 'vue',
fileList: []
},
methods: {
test(videoTestName, shardIndex, key) {
//获取表单中的file
var file = this.fileList[0];
//文件分片 以20MB去分片
var shardSize = 20 * 1024 * 1024;
//定义分片的起始位置
var start = (shardIndex - 1) * shardSize;
//定义分片结束的位置 file哪里来的?
var end = Math.min(file.size, start + shardSize);
//从文件中截取当前的分片数据
var fileShard = file.slice(start, end);
//分片的大小
var size = file.size;
//总片数
var shardTotal = Math.ceil(size / shardSize);
//文件的后缀名
var fileName = file.name;
var suffix = fileName.substring(fileName.lastIndexOf(".") + 1, fileName.length).toLowerCase();
let params = new FormData(); // 创建form对象
params.append('type',2)
params.append('uploadFile', fileShard);
params.append('shardIndex', shardIndex);
params.append('shardTotal', shardTotal);
params.append('fileKey', key);
params.append('suffix', suffix);
axios.post('http://127.0.0.1:18080/webadmin/fileEntity/addFileEntity', params).then(res => {
if (shardIndex < shardTotal) {
var index = shardIndex + 1;
this.test(videoTestName, index,key);
} else {
console.log("上传成功");
}
});
},
addClick() {
console.log("点击了");
// 发送 POST 请求
let a = uuidv4().replace(/-/g, "");
this.test("文件名", 1, a)
},
addFile(e) {
console.log(e.target.files);
this.fileList = [...e.target.files]
}
}
})
</script>
</html>