学习目标:
- vue自定义组件-文件上传后端接口
学习内容:
准备工作:
后端环境:JAVA-Springboot项目
数据库表(这里使用psql数据库):sys_file_record保存上传文件的信息,sys_file_zone_record保存文件分片信息。
/*
Navicat Premium Data Transfer
Source Server : lofserver
Source Server Type : PostgreSQL
Source Server Version : 100010
Source Host : 119.3.173.86:7007
Source Catalog : lof_ipm
Source Schema : sys
Target Server Type : PostgreSQL
Target Server Version : 100010
File Encoding : 65001
Date: 01/04/2022 15:29:31
*/
-- ----------------------------
-- Table structure for sys_file_record
-- ----------------------------
DROP TABLE IF EXISTS "sys"."sys_file_record";
CREATE TABLE "sys"."sys_file_record" (
"id" varchar(40) COLLATE "pg_catalog"."default" NOT NULL,
"org_name" varchar(1000) COLLATE "pg_catalog"."default",
"server_local_name" varchar(100) COLLATE "pg_catalog"."default",
"server_local_path" varchar(1000) COLLATE "pg_catalog"."default",
"network_path" varchar(1000) COLLATE "pg_catalog"."default",
"upload_type" int4,
"md5_value" varchar(120) COLLATE "pg_catalog"."default",
"file_size" int8,
"is_merge" int4,
"is_zone" int4,
"zone_total" int4,
"zone_date" timestamp(6),
"zone_merge_date" timestamp(6),
"file_type" varchar(100) COLLATE "pg_catalog"."default",
"upload_device" varchar(1000) COLLATE "pg_catalog"."default",
"upload_ip" varchar(100) COLLATE "pg_catalog"."default",
"upload_count" int8,
"download_count" int8,
"storage_date" timestamp(6),
"create_by" varchar(40) COLLATE "pg_catalog"."default",
"create_time" timestamp(6),
"del_flag" int4,
"file_code" varchar(64) COLLATE "pg_catalog"."default"
)
;
COMMENT ON COLUMN "sys"."sys_file_record"."id" IS '记录ID';
COMMENT ON COLUMN "sys"."sys_file_record"."org_name" IS '源文件名';
COMMENT ON COLUMN "sys"."sys_file_record"."server_local_name" IS '服务器生成的文件名';
COMMENT ON COLUMN "sys"."sys_file_record"."server_local_path" IS '服务器储存路径';
COMMENT ON COLUMN "sys"."sys_file_record"."network_path" IS '网络路径,生成的文件夹+系统生成文件名';
COMMENT ON COLUMN "sys"."sys_file_record"."upload_type" IS '上传类型';
COMMENT ON COLUMN "sys"."sys_file_record"."md5_value" IS '文件MD5值';
COMMENT ON COLUMN "sys"."sys_file_record"."file_size" IS '文件大小';
COMMENT ON COLUMN "sys"."sys_file_record"."is_merge" IS '是否合并';
COMMENT ON COLUMN "sys"."sys_file_record"."is_zone" IS '是否分片 0 否 1是';
COMMENT ON COLUMN "sys"."sys_file_record"."zone_total" IS '分片总数';
COMMENT ON COLUMN "sys"."sys_file_record"."zone_date" IS '分片时间';
COMMENT ON COLUMN "sys"."sys_file_record"."zone_merge_date" IS '分片合并时间';
COMMENT ON COLUMN "sys"."sys_file_record"."file_type" IS '文件类型';
COMMENT ON COLUMN "sys"."sys_file_record"."upload_device" IS '设备信息';
COMMENT ON COLUMN "sys"."sys_file_record"."upload_ip" IS '上传设备IP';
COMMENT ON COLUMN "sys"."sys_file_record"."upload_count" IS '上传统计';
COMMENT ON COLUMN "sys"."sys_file_record"."download_count" IS '下载统计';
COMMENT ON COLUMN "sys"."sys_file_record"."storage_date" IS '储存日期';
COMMENT ON COLUMN "sys"."sys_file_record"."create_by" IS '上传人员';
COMMENT ON COLUMN "sys"."sys_file_record"."create_time" IS '上传日期';
COMMENT ON COLUMN "sys"."sys_file_record"."del_flag" IS '删除标记 1正常 -1删除';
COMMENT ON COLUMN "sys"."sys_file_record"."file_code" IS '文件编码';
COMMENT ON TABLE "sys"."sys_file_record" IS '文件上传记录';
-- ----------------------------
-- Table structure for sys_file_zone_record
-- ----------------------------
DROP TABLE IF EXISTS "sys"."sys_file_zone_record";
CREATE TABLE "sys"."sys_file_zone_record" (
"id" varchar(40) COLLATE "pg_catalog"."default" NOT NULL,
"zone_name" varchar(100) COLLATE "pg_catalog"."default",
"zone_path" varchar(1000) COLLATE "pg_catalog"."default",
"zone_md5" varchar(100) COLLATE "pg_catalog"."default",
"zone_record_date" timestamp(6),
"zone_check_date" timestamp(6),
"zone_total_count" int4,
"zone_total_size" int8,
"zone_start_size" int8,
"zone_end_size" int8,
"zone_total_md5" varchar(100) COLLATE "pg_catalog"."default",
"zone_now_index" int4,
"zone_suffix" varchar(100) COLLATE "pg_catalog"."default",
"file_record_id" varchar(40) COLLATE "pg_catalog"."default"
)
;
COMMENT ON COLUMN "sys"."sys_file_zone_record"."id" IS '分片ID';
COMMENT ON COLUMN "sys"."sys_file_zone_record"."zone_name" IS '分片名称';
COMMENT ON COLUMN "sys"."sys_file_zone_record"."zone_path" IS '分片的文件路径';
COMMENT ON COLUMN "sys"."sys_file_zone_record"."zone_md5" IS '分片MD5';
COMMENT ON COLUMN "sys"."sys_file_zone_record"."zone_record_date" IS '分片记录MD5值';
COMMENT ON COLUMN "sys"."sys_file_zone_record"."zone_check_date" IS '上传完成校验日期';
COMMENT ON COLUMN "sys"."sys_file_zone_record"."zone_total_count" IS '总的分片数';
COMMENT ON COLUMN "sys"."sys_file_zone_record"."zone_total_size" IS '总的文件大小';
COMMENT ON COLUMN "sys"."sys_file_zone_record"."zone_start_size" IS '分片起始位置';
COMMENT ON COLUMN "sys"."sys_file_zone_record"."zone_end_size" IS '分片结束位置';
COMMENT ON COLUMN "sys"."sys_file_zone_record"."zone_total_md5" IS '总文件的MD5值';
COMMENT ON COLUMN "sys"."sys_file_zone_record"."zone_now_index" IS '当前分片索引';
COMMENT ON COLUMN "sys"."sys_file_zone_record"."zone_suffix" IS '分片文件后缀';
COMMENT ON COLUMN "sys"."sys_file_zone_record"."file_record_id" IS '文件记录ID';
COMMENT ON TABLE "sys"."sys_file_zone_record" IS '文件分片记录';
-- ----------------------------
-- Primary Key structure for table sys_file_record
-- ----------------------------
ALTER TABLE "sys"."sys_file_record" ADD CONSTRAINT "sys_file_record_pkey" PRIMARY KEY ("id");
-- ----------------------------
-- Primary Key structure for table sys_file_zone_record
-- ----------------------------
ALTER TABLE "sys"."sys_file_zone_record" ADD CONSTRAINT "sys_file_zone_record_pkey" PRIMARY KEY ("id");
项目代码
1.controller层
/**
* 分片上传
*/
@Api(description = "文件上传")
@RestController
@RequestMapping("/upload/fileRecord")
public class SysFileRecordController {
@Autowired
private FileRecordService fileRecordService;
@Autowired
private FileUploadConfig fileUploadConfig;
/**
* 分页列表
*
* @param fileRecord
* @param pageIndex
* @param pageSize
* @return
*/
@GetMapping
public ServerResponse listByPage(FileRecord fileRecord,
@RequestParam(defaultValue = "1") Integer pageIndex,
@RequestParam(defaultValue = "10") Integer pageSize) {
PageInfo<FileRecord> list = fileRecordService.listByPage(fileRecord, pageIndex, pageSize);
return ServerResponse.success(list);
}
/***
* 根据ID查找
*/
@GetMapping("/{id}")
public ServerResponse findById(@PathVariable("id") String id) {
FileRecord fileRecord = fileRecordService.getById(id);
if (ObjectUtil.isNotEmpty(fileRecord)) {
File file = new File(fileRecord.getServerLocalPath());
fileRecord.setLocalFileExist(file.exists());
}
return ServerResponse.success(fileRecord);
}
/***
* 保存数据
* id存在就更新
*/
@PostMapping("/save")
public ServerResponse save(@RequestBody FileRecord fileRecord) {
boolean b = fileRecordService.saveOrUpdate(fileRecord);
return b ? ServerResponse.success() : ServerResponse.fail();
}
/***
* 根据ID删除数据
*/
@Log(title = "文件上传管理", businessType = BusinessType.DELETE)
@DeleteMapping("/delById/{id}")
public ServerResponse delById(@PathVariable("id") String id) {
if ("false".equals(fileUploadConfig.getKeepDocumentsOnLocal())) {
FileRecord fileRecord = fileRecordService.getById(id);
if (ObjectUtil.isNotEmpty(fileRecord)) {
String serverLocalPath = fileRecord.getServerLocalPath();
File file = new File(serverLocalPath);
if (file.exists()) {
file.delete();
}
}
}
boolean b = fileRecordService.removeById(id);
return b ? ServerResponse.success() : ServerResponse.fail();
}
/***
* 根据多个ID删除(批量删除)
*/
@Log(title = "文件上传管理", businessType = BusinessType.DELETE)
@DeleteMapping("/delByIds/{ids}")
public ServerResponse delByIds(@PathVariable("ids") String ids) {
ArrayList<String> idList = new ArrayList<>(Arrays.asList(ids.split(",")));
if ("false".equals(fileUploadConfig.getKeepDocumentsOnLocal())) {
for (String id : idList) {
FileRecord fileRecord = fileRecordService.getById(id);
if (ObjectUtil.isNotEmpty(fileRecord)) {
String serverLocalPath = fileRecord.getServerLocalPath();
File file = new File(serverLocalPath);
if (file.exists()) {
file.delete();
}
}
}
}
boolean b = fileRecordService.removeByIds(idList);
return b ? ServerResponse.success() : ServerResponse.fail();
}
@Log(title = "文件上传管理", businessType = BusinessType.UPLOAD)
@ApiOperation(value = "单文件上传(<5M)", notes = "单文件上传(<5M)")
@PostMapping("/upload")
public ServerResponse upload(HttpServletRequest request, Integer uploadType, Integer storageYear, String fileCode) {
if (ObjectUtil.isEmpty(uploadType)) {
uploadType = 1;
}
if (ObjectUtil.isEmpty(storageYear)) {
storageYear = 100;
}
return fileRecordService.upload(request, uploadType, storageYear, fileCode);
}
@CrossOrigin
@Log(title = "文件上传管理", businessType = BusinessType.UPLOAD)
@ApiOperation(value = "大文件分片上传", notes = "大文件分片上传")
@PostMapping("/zone/upload")
public ServerResponse zoneUpload(HttpServletRequest request, String contentType, FileZoneRecord fileZoneRecord, String fileCode) {
fileZoneRecord.setFileCode(fileCode);
return fileRecordService.zoneUpload(request, contentType, fileZoneRecord);
}
@CrossOrigin
@ApiOperation(value = "校验MD5", notes = "校验MD5")
@PostMapping("/zone/upload/md5Check")
public ServerResponse md5Check(FileZoneRecord fileZoneRecord, Integer checkType, String contentType, String fileCode, HttpServletRequest request) {
fileZoneRecord.setFileCode(fileCode);
return fileRecordService.md5Check(fileZoneRecord, checkType, contentType, request);
}
@CrossOrigin
@ApiOperation(value = "合并文件", notes = "合并文件")
@PostMapping("/zone/upload/merge/{totalmd5}")
public ServerResponse mergeZoneFile(@PathVariable("totalmd5") String totalmd5, HttpServletRequest request) {
return fileRecordService.mergeZoneFile(totalmd5, request);
}
@CrossOrigin
@ApiOperation(value = "删除文件分片", notes = "删除文件分片")
@PostMapping("/zone/upload/del/{totalmd5}")
public ServerResponse delZoneFile(@PathVariable("totalmd5") String totalmd5) {
return fileRecordService.delZoneFile(totalmd5);
}
@ApiOperation(value = "判断文件是否存在", notes = "判断文件是否存在")
@GetMapping("/download/exists/{fileId}")
public ServerResponse existsFile(HttpServletRequest request, HttpServletResponse response, @PathVariable("fileId") String fileId) {
String filePath = request.getParameter("filePath");
File file;
if (StrUtil.isNotEmpty(fileId)) {
FileRecord fileRecorddb = fileRecordService.getById(fileId);
if (ObjectUtil.isNotEmpty(fileRecorddb)) {
filePath = fileRecorddb.getServerLocalPath();
file = new File(filePath);
if (file.exists()) {
return ServerResponse.success(1);
}
}
}
if (StrUtil.isEmpty(filePath)) {
return ServerResponse.failMsg("文件路径不存在");
}
file = new File(filePath);
if (!file.exists()) {
return ServerResponse.failMsg("文件不存在");
}
return ServerResponse.success(0);
}
@Log(title = "文件上传管理", businessType = BusinessType.DOWNLOAD)
@ApiOperation(value = "文件下载", notes = "文件下载")
@GetMapping("/download/{fileId}")
public void downloadFile(HttpServletRequest request, HttpServletResponse response, @PathVariable("fileId") String fileId) {
FileRecord fileRecorddb = fileRecordService.getById(fileId);
fileRecorddb.setDownloadCount(fileRecorddb.getDownloadCount() + 1);
fileRecordService.updateById(fileRecorddb);
String filePath = fileRecorddb.getServerLocalPath();// 设置文件名,根据业务需要替换成要下载的文件名
String fileName = fileRecorddb.getOrgName();
//设置文件路径
File file = new File(filePath);
fileRecordService.downLoadFile(file, fileName, request, response);
}
@Log(title = "文件上传管理", businessType = BusinessType.DOWNLOAD)
@ApiOperation(value = "文件下载", notes = "文件下载")
@GetMapping("/download/path")
public void downloadFile2(HttpServletRequest request, HttpServletResponse response) {
String filePath = request.getParameter("filePath");
String fileName = request.getParameter("fileName");
//设置文件路径
File file = new File(filePath);
fileRecordService.downLoadFile(file, fileName, request, response);
}
/**
* 通过流获取图片
*
* @param path
* @return
* @throws IOException
*/
@GetMapping("/getFileByPath")
public ServerResponse getInputStream(@RequestParam(value = "path",required = false) String path,
@RequestParam(value = "type",required = false) String type) throws IOException {
if (StrUtil.isEmpty(path)) {
return ServerResponse.success();
}
String basePath = System.getProperties().getProperty("user.dir") +"/"+ fileUploadConfig.getLocalPath().replace("/","");
if ("iot".equals(type)) {
basePath = fileUploadConfig.getUploadFolder();
} else {
path = path.replace("/storage/", "/");
}
path = basePath + path;
File file = new File(path);
if (file.exists()) {
InputStream is = new FileInputStream(file);
ByteArrayOutputStream bArrOutputStream = new ByteArrayOutputStream();
int ch;
while ((ch = is.read()) != -1) {
bArrOutputStream.write(ch);
}
byte[] data = bArrOutputStream.toByteArray();
bArrOutputStream.close();
return ServerResponse.success(data);
}
return ServerResponse.success();
}
}
2.service层
public interface FileRecordService extends IService<FileRecord> {
ServerResponse zoneUpload(HttpServletRequest request, String contentType, FileZoneRecord fileZoneRecord);
ServerResponse md5Check(FileZoneRecord fileZoneRecord, Integer checkType, String contentType, HttpServletRequest request);
ServerResponse mergeZoneFile(String totalmd5, HttpServletRequest request);
ServerResponse delZoneFile(String totalmd5);
ServerResponse upload(HttpServletRequest request, Integer uploadType, Integer storageYear, String fileCode);
void downLoadFile(File file, String fileName, HttpServletRequest request, HttpServletResponse response);
PageInfo<FileRecord> listByPage(FileRecord fileRecord, Integer pageIndex, Integer pageSize);
}
@Service
public class FileRecordServiceImpl extends ServiceImpl<FileRecordMapper, FileRecord>
implements FileRecordService {
private String basePath = System.getProperties().getProperty("user.dir");
@Autowired
private FileUploadConfig fileUploadConfig;
@Autowired
private FileRecordMapper fileRecordMapper;
@Autowired
private IFileZoneRecordService fileZoneRecordService;
@Autowired
private IdWorker idWorker;
@Autowired
private SecurityUtils securityUtils;
@Override
public ServerResponse upload(HttpServletRequest request, Integer uploadType, Integer storageYear, String fileCode) {
long nowtime = System.currentTimeMillis();
MultipartHttpServletRequest multipartRequest = (MultipartHttpServletRequest) request;
Map<String, MultipartFile> files = multipartRequest.getFileMap();
for (Map.Entry<String, MultipartFile> entry : files.entrySet()) {
MultipartFile multipartFile = entry.getValue();
if (multipartFile.isEmpty()) {
return ServerResponse.failMsg("文件不能为空");
}
String contentType = multipartFile.getContentType();
String fileName = multipartFile.getOriginalFilename();
Long size = multipartFile.getSize();
System.out.println(fileName + "-->" + size);
String saticAccess = fileUploadConfig.getStaticAccessPath().replace("*", "");
try {
//本地路径 1 头像 2 图片
uploadType = uploadType == 1 ? uploadType : 2;
//计算MD5值
String filemd5 = DigestUtils.md5DigestAsHex(multipartFile.getInputStream());
//查询数据库是否已经有了,有直接写入,没有,写入磁盘
FileRecord fileRecorddb = selByMD5AndUpType(filemd5, uploadType);
if (fileRecorddb != null) {
File file = new File(fileRecorddb.getServerLocalPath());
if (!file.exists()) {
fileRecordMapper.deleteById(fileRecorddb.getId());
fileRecorddb = null;
}
}
Map<String, String> map = new HashMap<>();
String fileType = contentType.split("/")[0];
if (fileRecorddb == null) {
String pathTypeDir = (uploadType == 1 ? fileUploadConfig.getUserHeaderPicPath() : fileUploadConfig.getArchivesFilePath()) + fileType + "/";
// 年月日/时分 如果短时间内,上传并发量大,还可分得更细 秒 毫秒 等等
String path_date = DateUtil.format(new Date(), "yyyyMMdd");
String localPath = getUploadFolder() + pathTypeDir + path_date;
//随机生成服务器本地路径
String fileSuffix = getFileSuffix(fileName);
String serverFileName = idWorker.nextId() + fileSuffix;
FileHandleUtil.upload(multipartFile.getInputStream(), localPath, serverFileName);
String netWorkPath = "/" + saticAccess + pathTypeDir + path_date + "/" + serverFileName;
map.put("network", netWorkPath);
FileRecord fileRecord = new FileRecord();
fileRecord.setDownloadCount(0);
fileRecord.setUploadCount(1);
fileRecord.setIsMerge(1);//单文件,完整文件
fileRecord.setIsZone(0);
fileRecord.setFileSize(size);
fileRecord.setFileType(fileType);
fileRecord.setMd5Value(filemd5);
fileRecord.setOrgName(fileName);
fileRecord.setUploadIp(IpUtils.getIpAddr(ServletUtils.getRequest()));
fileRecord.setUploadDevice(UserAgentUtils.getBorderName(UserAgentUtils.getUserAgent(ServletUtils.getRequest())));
fileRecord.setUploadType(uploadType);
fileRecord.setServerLocalName(serverFileName);
fileRecord.setServerLocalPath(localPath + serverFileName);
fileRecord.setNetworkPath(netWorkPath);
fileRecord.setStorageDate(getDateToYear(storageYear));//默认一百年
fileRecord.setFileCode(fileCode);
String fileId = saveFileRecord(request, fileRecord);
map.put("fileId", fileId);
map.put("network", fileRecord.getNetworkPath());
} else {
String fileId = saveFileRecord(request, fileRecorddb);
map.put("fileId", fileId);
map.put("network", fileRecorddb.getNetworkPath());
}
System.out.println("耗时: " + (System.currentTimeMillis() - nowtime) + " ms");
return ServerResponse.success(map);
} catch (IllegalStateException e) {
// TODO Auto-generated catch block
e.printStackTrace();
return ServerResponse.failMsg("文件上传错误,错误消息:" + e.getMessage());
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
return ServerResponse.failMsg("文件上传错误,错误消息:" + e.getMessage());
} catch (Exception e) {
e.printStackTrace();
return ServerResponse.failMsg("文件上传错误,错误消息:" + e.getMessage());
}
}
return ServerResponse.failMsg("文件上传错误");
}
/**
* 下载文件
*
* @param file
* @param fileName
* @param request
* @param response
*/
@Override
public void downLoadFile(File file, String fileName, HttpServletRequest request, HttpServletResponse response) {
response.setContentType("application/force-download");// 设置强制下载不打开
response.addHeader("Content-Disposition", "attachment;fileName=" + fileName);// 设置文件名
response.setContentType("multipart/form-data;charset=UTF-8");//也可以明确的设置一下UTF-8,测试中不设置也可以。
try {
response.setHeader("Content-Disposition", "attachment;fileName=" + new String(fileName.getBytes("GB2312"), "ISO-8859-1"));
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
byte[] buffer = new byte[1024];
FileInputStream fis = null;
BufferedInputStream bis = null;
try {
fis = new FileInputStream(file);
bis = new BufferedInputStream(fis);
OutputStream os = response.getOutputStream();
int i = bis.read(buffer);
while (i != -1) {
os.write(buffer, 0, i);
os.flush();
i = bis.read(buffer);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if (bis != null) {
try {
bis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (fis != null) {
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
/**
* 分页列表
*
* @param fileRecord
* @param pageIndex
* @param pageSize
* @return
*/
@Override
public PageInfo<FileRecord> listByPage(FileRecord fileRecord, Integer pageIndex, Integer pageSize) {
QueryWrapper<FileRecord> wrapper = new QueryWrapper<>();
PageHelper.startPage(pageIndex, pageSize);
List<FileRecord> list = fileRecordMapper.selectList(wrapper);
if (BasicDataUtil.listIsNotEmpty(list)) {
for (FileRecord record : list) {
File file = new File(record.getServerLocalPath());
record.setLocalFileExist(file.exists());
}
}
return new PageInfo<>(list);
}
@Override
public ServerResponse zoneUpload(HttpServletRequest request, String contentType, FileZoneRecord fileZoneRecord) {
if (StrUtil.isNotEmpty(fileZoneRecord.getFileCode())) {
fileUploadConfig.setArchivesFilePath(fileZoneRecord.getFileCode() + "/");
}
MultipartHttpServletRequest multipartRequest = (MultipartHttpServletRequest) request;
Map<String, MultipartFile> files = multipartRequest.getFileMap();
for (Map.Entry<String, MultipartFile> entry : files.entrySet()) {
MultipartFile multipartFile = entry.getValue();
if (multipartFile.isEmpty()) {
return ServerResponse.failMsg("请选择文件");
}
String fileName = multipartFile.getOriginalFilename();
if ("blob".equals(fileName)) {
fileName = fileZoneRecord.getZoneName();
}
// String contentType = multipartFile.getContentType();
System.out.println("contentType");
// if(contentType==null||!isFileter(contentType)){
// return new Result(ResultCode.FILENOTSUPPORT);
// }
String fileType = contentType.split("/")[0];
Long size = multipartFile.getSize();
System.out.println(fileName + "-->" + size);
try {
Map<String, Object> map = new HashMap<>();
synchronized (UUID.randomUUID()) {
//查询数据库是否已经有了,有直接写入,没有,写入磁盘
FileZoneRecord fileZoneRecorddb = fileZoneRecordService.selByMD5AndZoneTotalMd5(fileZoneRecord.getZoneMd5(), fileZoneRecord.getZoneTotalMd5());
if (fileZoneRecorddb == null) {
String pathTypeDir = fileUploadConfig.getArchivesFilePath();
// 年月日/时分 如果短时间内,上传并发量大,还可分得更细 秒 毫秒 等等
//写入临时目录,用总文件MD5值做文件夹
String localPath = "";
//随机生成服务器本地路径
String fileSuffix = getFileSuffix(fileName);
//分片文件MD5,如果前端没有计,算一下
String filemd5 = "";
if (fileZoneRecord.getZoneMd5() == null || fileZoneRecord.getZoneMd5().trim().length() == 0) {
filemd5 = DigestUtils.md5DigestAsHex(multipartFile.getInputStream());
fileZoneRecord.setZoneMd5(filemd5);
} else {
filemd5 = fileZoneRecord.getZoneMd5();
}
String serverFileName = filemd5 + fileSuffix + ".temp";
String fileRecordId = "";
FileRecord fileRecorddb = null;
synchronized (UUID.randomUUID().toString()) {
//记录 已经文件存在,就不更新了,否则新增一条记录,合并时用
fileRecorddb = this.selByMD5AndUpType(fileZoneRecord.getZoneTotalMd5(), 2);
}
if (fileRecorddb == null) {
localPath = getUploadFolder() + pathTypeDir + "temp/" + fileZoneRecord.getZoneTotalMd5();
FileRecord fileRecord = new FileRecord();
fileRecord.setFileSize(fileZoneRecord.getZoneTotalSize());
fileRecord.setFileType(fileType);
fileRecord.setMd5Value(fileZoneRecord.getZoneTotalMd5());
fileRecord.setOrgName(fileName);
fileRecord.setUploadType(2);
fileRecord.setServerLocalPath(localPath);
fileRecord.setStorageDate(getDateToYear(100));//默认一百年
fileRecord.setIsZone(1);
fileRecord.setIsMerge(0);//没有合并
fileRecord.setDownloadCount(0);
fileRecord.setUploadCount(1);
fileRecord.setFileCode(fileZoneRecord.getFileCode());
System.out.println("fileRecord:" + fileRecord);
fileRecord.setZoneTotal(fileZoneRecord.getZoneTotalCount());
fileRecord.setZoneDate(new Date());
fileRecordId = saveFileRecord(request, fileRecord);
saveFileRecord(request, fileRecord);
} else {
//分片且已经合并过了,就不再往下执行,否则继续
if (fileRecorddb.getIsZone() == 1 && fileRecorddb.getIsMerge() == 1) {//如果文件已经合并过了,直接返回
return ServerResponse.failMsg("文件已经上传");
}
fileRecordId = fileRecorddb.getId();
localPath = fileRecorddb.getServerLocalPath();
}
// }
//将文件写入目录
FileHandleUtil.upload(multipartFile.getInputStream(), localPath, serverFileName);
//记录分片文件
fileZoneRecord.setId(idWorker.nextId() + "");
fileZoneRecord.setZoneMd5(filemd5);//计算当前分片MD5
fileZoneRecord.setFileRecordId(fileRecordId);
fileZoneRecord.setZoneName(serverFileName);
fileZoneRecord.setZonePath(localPath);//只存文件夹,合并时,直接读取这个文件所有文件
fileZoneRecord.setZoneRecordDate(new Date());
fileZoneRecord.setZoneSuffix(fileSuffix);
fileZoneRecordService.save(fileZoneRecord);
map.put("fileZone", fileZoneRecord);
map.put("isExist", false);//不存在
map.put("zoneNowIndex", fileZoneRecord.getZoneNowIndex());
} else {
map.put("fileZone", fileZoneRecorddb);
map.put("isExist", true);//存在
map.put("zoneNowIndex", fileZoneRecorddb.getZoneNowIndex());
}
}
return ServerResponse.success(map);
} catch (IllegalStateException e) {
// TODO Auto-generated catch block
e.printStackTrace();
return ServerResponse.failMsg("文件上传错误,错误消息:" + e.getMessage());
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
return ServerResponse.failMsg("文件上传错误,错误消息:" + e.getMessage());
}
}
return ServerResponse.failMsg("文件上传失败");
}
@Override
public ServerResponse md5Check(FileZoneRecord fileZoneRecord, Integer checkType, String contentType, HttpServletRequest request) {
if (StrUtil.isNotEmpty(fileZoneRecord.getFileCode())) {
fileUploadConfig.setArchivesFilePath(fileZoneRecord.getFileCode() + "/");
}
if (checkType == 1) {//校验文件
// if(contentType==null||!isFileter(contentType)){
// return new Result(ResultCode.FILENOTSUPPORT);
// }
FileRecord fileRecordb = this.selByMD5AndUpType(fileZoneRecord.getZoneTotalMd5(), 2);
if (fileRecordb != null) {
File file = new File(fileRecordb.getServerLocalPath());
if (!file.exists()) {
fileRecordMapper.deleteById(fileRecordb.getId());
fileRecordb = null;
}
}
if (fileRecordb != null) {
fileRecordb.setFileCode(fileZoneRecord.getFileCode());
saveFileRecord(request, fileRecordb);
}
return fileRecordb != null && fileRecordb.getIsMerge() == 1 ? ServerResponse.success(fileRecordb) : ServerResponse.failMsg("请选择文件上传");
} else {
FileZoneRecord fileZoneRecordb = fileZoneRecordService.selByMD5AndZoneTotalMd5(fileZoneRecord.getZoneMd5(), fileZoneRecord.getZoneTotalMd5());
return fileZoneRecordb != null ? ServerResponse.success(fileZoneRecordb) : ServerResponse.failMsg("分片文件不存在,继续上传");
}
}
@Override
public ServerResponse mergeZoneFile(String totalmd5, HttpServletRequest request) {
//查询所有的分片文件
if (totalmd5 != null && totalmd5.trim().length() > 0) {
FileRecord fileRecordb = this.selByMD5AndUpType(totalmd5, 2);
Map<String, Object> map = new HashMap<>();
if (fileRecordb.getIsZone() == 1 && fileRecordb.getIsMerge() == 1) {
map.put("netWorkPath", fileRecordb.getNetworkPath());
map.put("fileId", fileRecordb.getId());
map.put("fileInfo", fileRecordb);
map.put("message", "文件已经上传成功了,文件路径:" + fileRecordb.getNetworkPath());
return ServerResponse.success(map);
}
String fileType = fileRecordb.getFileType();
List<FileZoneRecord> fileZoneRecords = fileZoneRecordService.selByTotalMd5(totalmd5);
if (BasicDataUtil.listIsNotEmpty(fileZoneRecords)) {
// 年月日/时分 如果短时间内,上传并发量大,还可分得更细 秒 毫秒 等等
String path_date = DateUtil.format(new Date(), "yyyyMMdd") + "/";
String pathTypeDir = fileUploadConfig.getArchivesFilePath() + fileType + "/";
String localPath = getUploadFolder() + pathTypeDir + path_date;
//随机生成服务器本地路径
String fileSuffix = getFileSuffix(fileRecordb.getOrgName());
String serverFileName = idWorker.nextId() + fileSuffix;
String saticAccess = fileUploadConfig.getStaticAccessPath().replace("*", "");
String netWorkPath = "/" + saticAccess + pathTypeDir + path_date + serverFileName;
fileRecordb.setServerLocalName(serverFileName);
fileRecordb.setServerLocalPath(localPath + serverFileName);
fileRecordb.setNetworkPath(netWorkPath);
FileOutputStream destTempfos = null;
try {
String zonePath = fileZoneRecords.get(0).getZonePath();
File parentFileDir = new File(zonePath);//得到上级文件夹
if (parentFileDir.exists() && parentFileDir.isDirectory()) {
FileHandleUtil.createDirIfNotExists(localPath);
File destTempFile = new File(localPath, serverFileName);
if (!destTempFile.exists()) {
//先得到文件的上级目录,并创建上级目录,在创建文件,
destTempFile.getParentFile().mkdir();
try {
//创建文件
destTempFile.createNewFile(); //上级目录没有创建,这里会报错
} catch (IOException e) {
e.printStackTrace();
}
}
System.out.println(parentFileDir.listFiles().length);
List<String> ids = new ArrayList<>();
for (FileZoneRecord fileZoneR : fileZoneRecords) {
File partFile = new File(parentFileDir, fileZoneR.getZoneName());
destTempfos = new FileOutputStream(destTempFile, true);
//遍历"所有分片文件"到"最终文件"中
if (partFile.exists()) {
FileUtils.copyFile(partFile, destTempfos);
}
destTempfos.close();
ids.add(fileZoneR.getId());
}
// 删除临时目录中的分片文件
String tempDir = getUploadFolder() + fileUploadConfig.getArchivesFilePath() + "/temp/" + fileRecordb.getMd5Value();
FileHandleUtil.deleteFolder(tempDir);
fileZoneRecordService.removeByIds(ids);//删除分片信息
fileRecordb.setZoneMergeDate(new Date());
fileRecordb.setIsMerge(1);//更新已经合并
this.updateById(fileRecordb);//更新到文件记录
String fileId = saveFileRecord(request, fileRecordb);
map.put("netWorkPath", netWorkPath);
map.put("fileInfo", fileRecordb);
map.put("fileId", fileId);
return ServerResponse.success(map);
}
} catch (Exception e) {
e.printStackTrace();
return ServerResponse.failMsg("操作失败,原因:" + e.getMessage());
} finally {
try {
if (destTempfos != null) {
destTempfos.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
return ServerResponse.failMsg("合并错误");
}
@Override
public ServerResponse delZoneFile(String totalmd5) {
//查询所有的分片文件
List<FileZoneRecord> fileZoneRecords = fileZoneRecordService.selByTotalMd5(totalmd5);
if (BasicDataUtil.listIsNotEmpty(fileZoneRecords)) {
String zonePath = fileZoneRecords.get(0).getZonePath();
File parentFileDir = new File(zonePath);//得到上级文件夹
if (parentFileDir.exists() && parentFileDir.isDirectory()) {
List<String> ids = fileZoneRecords.stream().map(FileZoneRecord::getId).collect(Collectors.toList());
// 删除临时目录中的分片文件
String tempDir = getUploadFolder() + fileUploadConfig.getArchivesFilePath() + "/temp/" + totalmd5;
FileHandleUtil.deleteFolder(tempDir);
fileZoneRecordService.removeByIds(ids);//删除分片信息
}
}
return ServerResponse.success();
}
public FileRecord selByMD5AndUpType(String md5, Integer uploadType) {
QueryWrapper<FileRecord> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("md5_value", md5);
queryWrapper.eq("upload_type", uploadType);
List<FileRecord> list = fileRecordMapper.selectList(queryWrapper);
if (list.size() > 0) {
return list.get(0);
}
return null;
}
/***
* 获取文件后缀
* @param fileName
* @return
*/
private String getFileSuffix(String fileName) {
if (fileName == null || fileName.length() == 0) {
return "";
}
return fileName.substring(fileName.lastIndexOf("."));
}
/**
* 将当前时间往后推一年
*/
public Date getDateToYear(Integer year) {
if (year == null) {//默认一百年
year = 100;
}
//获取时间加一年
Date date = new Date();
Calendar cal = Calendar.getInstance();
cal.setTime(date);//设置起时间
cal.add(Calendar.YEAR, year);//增加year年
return cal.getTime();
}
public String getUploadFolder() {
// String uploadFolderdb = configService.getValueByKey("uploadFolder");
// System.out.println("uploadFolderdb:"+uploadFolderdb);
// if(uploadFolderdb==null||uploadFolderdb.trim().length()==0){
// return fileUploadConfig.getUploadFolder();
// }
// return uploadFolderdb;
return basePath + "/" + fileUploadConfig.getLocalPath();
}
/**
* 文件保存记录
*/
@Transactional(rollbackFor = Exception.class)
public String saveFileRecord(HttpServletRequest request, FileRecord fileRecord) {
fileRecord.setDelFlag(1);
if (fileRecord.getUploadCount() == 1 && fileRecord.getId() == null) {
fileRecord.setCreateTime(new Date());
fileRecord.setCreateBy(securityUtils.getUserName(null));
this.save(fileRecord);
}
fileRecord.setUploadCount(fileRecord.getId() == null ? 1 : fileRecord.getUploadCount() + 1);
this.updateById(fileRecord);
return fileRecord.getId();
}
}
FileZoneRecord
public interface IFileZoneRecordService extends IService<FileZoneRecord> {
FileZoneRecord selByMD5AndZoneTotalMd5(String zoneMd5, String zoneTotalMd5);
List<FileZoneRecord> selByTotalMd5(String totalmd5);
}
3.dao层
public interface FileRecordMapper extends BaseMapper<FileRecord> {
}
4.entity
/**
* 文件上传记录
*/
@Data
@TableName("sys_file_record")
public class FileRecord extends Model<FileRecord> {
private static final long serialVersionUID = 1L;
/**
* 记录ID
*/
@TableId(value = "id", type = IdType.ID_WORKER_STR)
private String id;
/**
* 源文件名
*/
@TableField("org_name")
private String orgName;
/**
* 服务器生成的文件名
*/
@TableField("server_local_name")
private String serverLocalName;
/**
* 服务器储存路径
*/
@TableField("server_local_path")
private String serverLocalPath;
/**
* 网络路径,生成的文件夹+系统生成文件名
*/
@TableField("network_path")
private String networkPath;
/**
* 上传类型
*/
@TableField("upload_type")
private Integer uploadType;
/**
* 文件MD5值
*/
@TableField("md5_value")
private String md5Value;
/**
* 文件大小
*/
@TableField("file_size")
private Long fileSize;
/**
* 是否分片 0 否 1是
*/
@TableField("is_zone")
private Integer isZone;
/**
* 分片总数
*/
@TableField("zone_total")
private Integer zoneTotal;
/**
* 分片时间
*/
@TableField("zone_date")
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Date zoneDate;
/**
* 分片合并时间
*/
@TableField("zone_merge_date")
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Date zoneMergeDate;
/**
* 文件类型
*/
@TableField("file_type")
private String fileType;
/**
* 设备信息
*/
@TableField("upload_device")
private String uploadDevice;
/**
* 上传设备IP
*/
@TableField("upload_ip")
private String uploadIp;
/**
* 储存日期
*/
@TableField("storage_date")
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Date storageDate;
/**
* 下载统计
*/
@TableField("download_count")
private Integer downloadCount;
/**
* 上传统计
*/
@TableField("upload_count")
private Integer uploadCount;
/**
* 是否合并
*/
@TableField("is_merge")
private Integer isMerge;
/**
* 上传人员
*/
@TableField("create_by")
private String createBy;
/**
* 上传日期
*/
@TableField("create_time")
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Date createTime;
/**
* 删除标记 1正常 -1删除
*/
@TableField("del_flag")
@TableLogic
private Integer delFlag;
/**
* 文件编码
*/
@TableField("file_code")
private String fileCode;
@TableField(exist = false)
private boolean localFileExist;
@Override
protected Serializable pkVal() {
return this.id;
}
}
/**
* 文件分片记录
*/
@Data
@TableName("sys_file_zone_record")
@ApiModel(value = "文件分片记录,FileZoneRecord")
public class FileZoneRecord extends Model<FileZoneRecord> {
private static final long serialVersionUID = 1L;
/**
* 分片ID
*/
@TableId(value = "id", type = IdType.ID_WORKER_STR)
@ApiModelProperty(value = "分片ID")
private String id;
/**
* 分片名称
*/
@TableField("zone_name")
@ApiModelProperty(value = "分片名称")
private String zoneName;
/**
* 分片的文件路径
*/
@TableField("zone_path")
@ApiModelProperty(value = "分片的文件路径")
private String zonePath;
/**
* 分片MD5
*/
@TableField("zone_md5")
@ApiModelProperty(value = "分片MD5")
private String zoneMd5;
/**
* 分片记录MD5值
*/
@TableField("zone_record_date")
@ApiModelProperty(value = "分片记录MD5值")
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Date zoneRecordDate;
/**
* 上传完成校验日期
*/
@TableField("zone_check_date")
@ApiModelProperty(value = "上传完成校验日期")
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Date zoneCheckDate;
/**
* 总的分片数
*/
@TableField("zone_total_count")
@ApiModelProperty(value = "总的分片数")
private Integer zoneTotalCount;
/**
* 总文件的MD5值
*/
@TableField("zone_total_md5")
@ApiModelProperty(value = "总文件的MD5值")
private String zoneTotalMd5;
/**
* 当前分片索引
*/
@TableField("zone_now_index")
@ApiModelProperty(value = "当前分片索引")
private Integer zoneNowIndex;
/**
* 文件开始位置
*/
@TableField("zone_start_size")
@ApiModelProperty(value = "文件开始位置")
private Long zoneStartSize;
/**
* 文件结束位置
*/
@TableField("zone_end_size")
@ApiModelProperty(value = "文件结束位置")
private Long zoneEndSize;
/**
* 文件总大小
*/
@TableField("zone_total_size")
@ApiModelProperty(value = "文件总大小")
private Long zoneTotalSize;
/**
* 分片文件后缀
*/
@TableField("zone_suffix")
@ApiModelProperty(value = "分片文件后缀")
private String zoneSuffix;
/**
* 文件记录ID
*/
@TableField("file_record_id")
@ApiModelProperty(value = "文件记录ID")
private String fileRecordId;
@TableField(exist = false)
private String fileCode;
@Override
protected Serializable pkVal() {
return this.id;
}
}
/**
* @Description 文件上传配置
***/
@Getter
@Setter
@ConfigurationProperties("fileupload.config")
public class FileUploadConfig {
private String uploadFolder;
private String staticAccessPath;
private String localPath;
private String userHeaderPicPath;
private String archivesFilePath;
private String domain;
private String keepDocumentsOnLocal;
}
public class IdWorker {
// 时间起始标记点,作为基准,一般取系统的最近时间(一旦确定不能变动)
private final static long twepoch = 1288834974657L;
// 机器标识位数
private final static long workerIdBits = 5L;
// 数据中心标识位数
private final static long datacenterIdBits = 5L;
// 机器ID最大值
private final static long maxWorkerId = -1L ^ (-1L << workerIdBits);
// 数据中心ID最大值
private final static long maxDatacenterId = -1L ^ (-1L << datacenterIdBits);
// 毫秒内自增位
private final static long sequenceBits = 12L;
// 机器ID偏左移12位
private final static long workerIdShift = sequenceBits;
// 数据中心ID左移17位
private final static long datacenterIdShift = sequenceBits + workerIdBits;
// 时间毫秒左移22位
private final static long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits;
private final static long sequenceMask = -1L ^ (-1L << sequenceBits);
/* 上次生产id时间戳 */
private static long lastTimestamp = -1L;
// 0,并发控制
private long sequence = 0L;
private final long workerId;
// 数据标识id部分
private final long datacenterId;
public IdWorker(){
this.datacenterId = getDatacenterId(maxDatacenterId);
this.workerId = getMaxWorkerId(datacenterId, maxWorkerId);
}
/**
* @param workerId
* 工作机器ID
* @param datacenterId
* 序列号
*/
public IdWorker(long workerId, long datacenterId) {
if (workerId > maxWorkerId || workerId < 0) {
throw new IllegalArgumentException(String.format("worker Id can't be greater than %d or less than 0", maxWorkerId));
}
if (datacenterId > maxDatacenterId || datacenterId < 0) {
throw new IllegalArgumentException(String.format("datacenter Id can't be greater than %d or less than 0", maxDatacenterId));
}
this.workerId = workerId;
this.datacenterId = datacenterId;
}
/**
* 获取下一个ID
*
* @return
*/
public synchronized long nextId() {
long timestamp = timeGen();
if (timestamp < lastTimestamp) {
throw new RuntimeException(String.format("Clock moved backwards. Refusing to generate id for %d milliseconds", lastTimestamp - timestamp));
}
if (lastTimestamp == timestamp) {
// 当前毫秒内,则+1
sequence = (sequence + 1) & sequenceMask;
if (sequence == 0) {
// 当前毫秒内计数满了,则等待下一秒
timestamp = tilNextMillis(lastTimestamp);
}
} else {
sequence = 0L;
}
lastTimestamp = timestamp;
// ID偏移组合生成最终的ID,并返回ID
long nextId = ((timestamp - twepoch) << timestampLeftShift)
| (datacenterId << datacenterIdShift)
| (workerId << workerIdShift) | sequence;
return nextId;
}
private long tilNextMillis(final long lastTimestamp) {
long timestamp = this.timeGen();
while (timestamp <= lastTimestamp) {
timestamp = this.timeGen();
}
return timestamp;
}
private long timeGen() {
return System.currentTimeMillis();
}
/**
* <p>
* 获取 maxWorkerId
* </p>
*/
protected static long getMaxWorkerId(long datacenterId, long maxWorkerId) {
StringBuffer mpid = new StringBuffer();
mpid.append(datacenterId);
String name = ManagementFactory.getRuntimeMXBean().getName();
if (!name.isEmpty()) {
/*
* GET jvmPid
*/
mpid.append(name.split("@")[0]);
}
/*
* MAC + PID 的 hashcode 获取16个低位
*/
return (mpid.toString().hashCode() & 0xffff) % (maxWorkerId + 1);
}
/**
* <p>
* 数据标识id部分
* </p>
*/
protected static long getDatacenterId(long maxDatacenterId) {
long id = 0L;
try {
InetAddress ip = InetAddress.getLocalHost();
NetworkInterface network = NetworkInterface.getByInetAddress(ip);
if (network == null) {
id = 1L;
} else {
byte[] mac = network.getHardwareAddress();
id = ((0x000000FF & (long) mac[mac.length - 1])
| (0x0000FF00 & (((long) mac[mac.length - 2]) << 8))) >> 6;
id = id % (maxDatacenterId + 1);
}
} catch (Exception e) {
System.out.println(" getDatacenterId: " + e.getMessage());
}
return id;
}
}
5.配置文件
#文件上传配置
fileupload:
config:
# 文件上传目录
#uploadFolder: /usr/local/upload
uploadFolder: C:/uploadPath
#静态资源对外暴露的访问路径(访问图片的路径)
staticAccessPath: storage/**
# 上传文件夹
localPath: upload/
#用户相关图片存放
userHeaderPicPath: user/
#档案文件存放
archivesFilePath: fileData/
#访问域名和端口
domain: http://localhost:${server.port}/
#删除文件时同数据库数据与服务器上文件
keepDocumentsOnLocal: false
6.启动类加载
@Bean
public IdWorker idWorker() {
return new IdWorker(1, 1);
}
@Bean
public FileUploadConfig fileUploadConfig() {
return new FileUploadConfig();
}
学习时间:
- 2022-04-01
学习总结:
- 功能待完善。。。