|
@@ -27,6 +27,7 @@ import org.apache.commons.io.FileUtils;
|
|
import org.apache.commons.lang.StringUtils;
|
|
import org.apache.commons.lang.StringUtils;
|
|
import org.apache.pdfbox.pdmodel.PDDocument;
|
|
import org.apache.pdfbox.pdmodel.PDDocument;
|
|
import org.springblade.common.constant.CommonConstant;
|
|
import org.springblade.common.constant.CommonConstant;
|
|
|
|
+import org.springblade.common.utils.SystemUtils;
|
|
import org.springblade.core.cache.utils.CacheUtil;
|
|
import org.springblade.core.cache.utils.CacheUtil;
|
|
import org.springblade.core.oss.model.BladeFile;
|
|
import org.springblade.core.oss.model.BladeFile;
|
|
import org.springblade.core.secure.annotation.PreAuth;
|
|
import org.springblade.core.secure.annotation.PreAuth;
|
|
@@ -72,539 +73,539 @@ import java.util.concurrent.locks.ReentrantLock;
|
|
@Api(value = "大文件存储端点", tags = "大文件存储端点")
|
|
@Api(value = "大文件存储端点", tags = "大文件存储端点")
|
|
public class LargeFileEndpoint {
|
|
public class LargeFileEndpoint {
|
|
|
|
|
|
- /**
|
|
|
|
- * 对象存储构建类
|
|
|
|
- */
|
|
|
|
- private final OssBuilder ossBuilder;
|
|
|
|
-
|
|
|
|
- private final ILargeFileService iLargeFileService;
|
|
|
|
-
|
|
|
|
- private final Lock lock = new ReentrantLock();
|
|
|
|
-
|
|
|
|
- private final CommonFileClient commonFileClient;
|
|
|
|
-
|
|
|
|
- @Autowired
|
|
|
|
- StringRedisTemplate RedisTemplate;
|
|
|
|
-
|
|
|
|
- private final String bucketName = "bladex-chongqing-info";
|
|
|
|
-
|
|
|
|
- private final String endpoint = "http://oss-cn-hangzhou.aliyuncs.com";
|
|
|
|
-
|
|
|
|
- /**
|
|
|
|
- * 创建存储桶
|
|
|
|
- *
|
|
|
|
- * @param bucketName 存储桶名称
|
|
|
|
- * @return Bucket
|
|
|
|
- */
|
|
|
|
- @SneakyThrows
|
|
|
|
- @PostMapping("/make-bucket")
|
|
|
|
- @PreAuth(RoleConstant.HAS_ROLE_ADMIN)
|
|
|
|
- public R makeBucket(@RequestParam String bucketName) {
|
|
|
|
- ossBuilder.template().makeBucket(bucketName);
|
|
|
|
- return R.success("创建成功");
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- /**
|
|
|
|
- * 创建存储桶
|
|
|
|
- *
|
|
|
|
- * @param bucketName 存储桶名称
|
|
|
|
- * @return R
|
|
|
|
- */
|
|
|
|
- @SneakyThrows
|
|
|
|
- @PostMapping("/remove-bucket")
|
|
|
|
- @PreAuth(RoleConstant.HAS_ROLE_ADMIN)
|
|
|
|
- public R removeBucket(@RequestParam String bucketName) {
|
|
|
|
- ossBuilder.template().removeBucket(bucketName);
|
|
|
|
- return R.success("删除成功");
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
-
|
|
|
|
- /**
|
|
|
|
- * @return
|
|
|
|
- * @throws Exception
|
|
|
|
- **/
|
|
|
|
- @SneakyThrows
|
|
|
|
- @PostMapping("/upload-file1234")
|
|
|
|
- public R uploadByfile123(@RequestParam(value = "file", required = false) MultipartFile file,
|
|
|
|
- @RequestParam(value = "identifier", required = false) String identifier,
|
|
|
|
- @RequestParam(value = "chunkNumber", required = false) Integer chunkNumber,
|
|
|
|
- @RequestParam(value = "chunkSize", required = false) Integer chunkSize,
|
|
|
|
- @RequestParam(value = "currentChunkSize", required = false) String currentChunkSize,
|
|
|
|
- @RequestParam(value = "filename", required = false) String filename,
|
|
|
|
- @RequestParam(value = "relativePath", required = false) String relativePath,
|
|
|
|
- @RequestParam(value = "totalChunks", required = false) Integer totalChunks,
|
|
|
|
- @RequestParam(value = "totalSize", required = false) String totalSize,
|
|
|
|
- @RequestParam(value = "objectType", required = false) String objectType) {
|
|
|
|
-
|
|
|
|
- // 文件上传
|
|
|
|
- String taskKey = "upload/20221220/" + filename;
|
|
|
|
- UmsAdminLoginLogDO param = new UmsAdminLoginLogDO();
|
|
|
|
- if (file == null && totalChunks >= 0 && StringUtils.isNotEmpty(identifier)) {
|
|
|
|
- // 请求阿里云oss获取分片唯一ID
|
|
|
|
- String ossSlicesId = this.getUploadId(taskKey);
|
|
|
|
- RedisTemplate.opsForValue().set(identifier, ossSlicesId);
|
|
|
|
- return R.fail("没有文件");
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- String uploadId = RedisTemplate.opsForValue().get(identifier);
|
|
|
|
-
|
|
|
|
- UmsAdminLoginLogDO redisParam = (UmsAdminLoginLogDO) CacheUtil.get("oss", "PartETag", uploadId);
|
|
|
|
- if (redisParam != null) {
|
|
|
|
- param.setPartETags(redisParam.getPartETags());
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- Map<Integer, PartETag> partETags = param.getPartETags();
|
|
|
|
-
|
|
|
|
- UploadPartRequest request = new UploadPartRequest();
|
|
|
|
- request.setInputStream(file.getInputStream());
|
|
|
|
- request.setBucketName(bucketName);
|
|
|
|
- request.setPartNumber(chunkNumber);
|
|
|
|
- request.setPartSize(Long.parseLong(currentChunkSize));
|
|
|
|
- request.setKey(filename);
|
|
|
|
- request.setMd5Digest(identifier);
|
|
|
|
- request.setUploadId(uploadId);
|
|
|
|
-
|
|
|
|
- try {
|
|
|
|
- UploadPartResult uploadPartResult = ossBuilder.template().uploadPart(request);
|
|
|
|
- PartETag partETag = uploadPartResult.getPartETag();
|
|
|
|
- partETags.put(chunkNumber, partETag);
|
|
|
|
- //分片编号等于总片数的时候合并文件,如果符合条件则合并文件,否则继续等待
|
|
|
|
- if (chunkNumber == totalChunks) {
|
|
|
|
- //合并文件,注意:partETags必须是所有分片的所以必须存入redis,然后取出放入集合
|
|
|
|
- String url = this.completePartUploadFile(filename, uploadId,
|
|
|
|
- new ArrayList<>(partETags.values()));
|
|
|
|
- //oss地址返回后存入并清除redis
|
|
|
|
- RedisTemplate.delete(uploadId);
|
|
|
|
- return R.data(url);
|
|
|
|
- } else {
|
|
|
|
- RedisTemplate.opsForHash().putAll(uploadId, partETags);
|
|
|
|
- CacheUtil.put("oss", "PartETag", uploadId, param);
|
|
|
|
- }
|
|
|
|
- } catch (Exception e) {
|
|
|
|
- e.printStackTrace();
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- return R.data("");
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- /**
|
|
|
|
- * 分块上传完成获取结果
|
|
|
|
- */
|
|
|
|
- public String completePartUploadFile(String fileKey, String uploadId, List<PartETag> partETags) {
|
|
|
|
- CompleteMultipartUploadRequest request = new CompleteMultipartUploadRequest(bucketName, fileKey, uploadId,
|
|
|
|
- partETags);
|
|
|
|
- ossBuilder.template().completeMultipartUpload(request);
|
|
|
|
- String downLoadUrl = getDownloadUrl(fileKey, bucketName);
|
|
|
|
- return downLoadUrl;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- /**
|
|
|
|
- * 获取bucket文件的下载链接
|
|
|
|
- *
|
|
|
|
- * @param pathFile 首字母不带/的路径和文件
|
|
|
|
- * @param bucketName
|
|
|
|
- * @return 上报返回null, 成功返回地址
|
|
|
|
- */
|
|
|
|
- public String getDownloadUrl(String pathFile, String bucketName) {
|
|
|
|
- if (bucketName == null || "".equals(bucketName)) {
|
|
|
|
- bucketName = bucketName;
|
|
|
|
- }
|
|
|
|
- StringBuffer url = new StringBuffer();
|
|
|
|
- url.append("http://").append(bucketName).append(endpoint).append("/");
|
|
|
|
- if (pathFile != null && !"".equals(pathFile)) {
|
|
|
|
- url.append(pathFile);
|
|
|
|
- }
|
|
|
|
- return url.toString();
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- public String getUploadId(String fileKey) {
|
|
|
|
- InitiateMultipartUploadRequest request = new InitiateMultipartUploadRequest(bucketName, fileKey);
|
|
|
|
- // 初始化分片
|
|
|
|
- InitiateMultipartUploadResult unrest = ossBuilder.template().initiateMultipartUpload(request);
|
|
|
|
- // 返回uploadId,它是分片上传事件的唯一标识,您可以根据这个ID来发起相关的操作,如取消分片上传、查询分片上传等。
|
|
|
|
- String uploadId = unrest.getUploadId();
|
|
|
|
- return uploadId;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- /**
|
|
|
|
- * @return
|
|
|
|
- * @throws Exception
|
|
|
|
- **/
|
|
|
|
- @SneakyThrows
|
|
|
|
- @PostMapping("/upload-file")
|
|
|
|
- public R uploadByfile(@RequestParam(value = "file", required = false) MultipartFile file,
|
|
|
|
- @RequestParam(value = "identifier", required = false) String identifier,
|
|
|
|
- @RequestParam(value = "chunkNumber", required = false) Integer chunkNumber,
|
|
|
|
- @RequestParam(value = "chunkSize", required = false) Integer chunkSize,
|
|
|
|
- @RequestParam(value = "currentChunkSize", required = false) String currentChunkSize,
|
|
|
|
- @RequestParam(value = "filename", required = false) String filename,
|
|
|
|
- @RequestParam(value = "relativePath", required = false) String relativePath,
|
|
|
|
- @RequestParam(value = "totalChunks", required = false) Integer totalChunks,
|
|
|
|
- @RequestParam(value = "totalSize", required = false) String totalSize,
|
|
|
|
- @RequestParam(value = "objectType", required = false) String objectType) throws Exception {
|
|
|
|
- R result = new R();
|
|
|
|
- // 判断是否上传
|
|
|
|
- if (file == null) {
|
|
|
|
- result.setSuccess(false);
|
|
|
|
- result.setMsg("没有文件!");
|
|
|
|
- return result;
|
|
|
|
- }
|
|
|
|
- MultipartFileParam param = new MultipartFileParam();
|
|
|
|
- param.setFile(file);
|
|
|
|
- param.setIdentifier(identifier);
|
|
|
|
- param.setChunkNumber(chunkNumber);
|
|
|
|
- param.setChunkSize(chunkSize);
|
|
|
|
- param.setCurrentChunkSize(currentChunkSize);
|
|
|
|
- param.setFilename(filename);
|
|
|
|
- param.setRelativePath(relativePath);
|
|
|
|
- param.setTotalChunks(totalChunks);
|
|
|
|
- param.setTotalSize(totalSize);
|
|
|
|
- param.setObjectType(objectType);
|
|
|
|
- return uploadByMappedByteBuffer(param);
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- /**
|
|
|
|
- * 分块上传
|
|
|
|
- * 第一步:获取RandomAccessFile,随机访问文件类的对象
|
|
|
|
- * 第二步:调用RandomAccessFile的getChannel()方法,打开文件通道 FileChannel
|
|
|
|
- * 第三步:获取当前是第几个分块,计算文件的最后偏移量
|
|
|
|
- * 第四步:获取当前文件分块的字节数组,用于获取文件字节长度
|
|
|
|
- * 第五步:使用文件通道FileChannel类的 map()方法创建直接字节缓冲器 MappedByteBuffer
|
|
|
|
- * 第六步:将分块的字节数组放入到当前位置的缓冲区内 mappedByteBuffer.put(byte[] b);
|
|
|
|
- * 第七步:释放缓冲区
|
|
|
|
- * 第八步:检查文件是否全部完成上传
|
|
|
|
- */
|
|
|
|
-
|
|
|
|
- public R uploadByMappedByteBuffer(MultipartFileParam param) throws Exception {
|
|
|
|
- R result = new R();
|
|
|
|
- if (param.getIdentifier() == null || "".equals(param.getIdentifier())) {
|
|
|
|
- return result;
|
|
|
|
- }
|
|
|
|
- // 判断是否上传
|
|
|
|
- if (param.getFile() == null) {
|
|
|
|
- result.setSuccess(false);
|
|
|
|
- result.setMsg("没有文件!");
|
|
|
|
- return result;
|
|
|
|
- }
|
|
|
|
- if (checkMd5(param.getFile().getInputStream(), param.getIdentifier())) {
|
|
|
|
- result.setSuccess(false);
|
|
|
|
- result.setMsg("文件的Identifier对不上!");
|
|
|
|
- return result;
|
|
|
|
- }
|
|
|
|
- /**
|
|
|
|
- * 查询是否已经上传该分片
|
|
|
|
- * **/
|
|
|
|
- QueryWrapper<LargeFile> wrapper = new QueryWrapper<LargeFile>()
|
|
|
|
- .eq("file_key", param.getIdentifier()).eq("is_deleted", 0).eq("shard_index", param.getChunkNumber()).orderByDesc("shard_index");
|
|
|
|
- LargeFile list = iLargeFileService.getOne(wrapper);
|
|
|
|
- int state = 0;
|
|
|
|
-
|
|
|
|
- if (list != null) {
|
|
|
|
- if (StringUtils.isNotEmpty(list.getPath())) {
|
|
|
|
- File chfile = new File(list.getPath());
|
|
|
|
- if (chfile.exists()) {
|
|
|
|
- result.setSuccess(true);
|
|
|
|
- result.setMsg("该分片已上传!");
|
|
|
|
- result.setData(list.getShardIndex());
|
|
|
|
- result.setCode(200);
|
|
|
|
- return result;
|
|
|
|
- } else {
|
|
|
|
- iLargeFileService.remove(wrapper);
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
-
|
|
|
|
- // 文件名称
|
|
|
|
- String fileName = getFileName(param);
|
|
|
|
- // 临时文件名称
|
|
|
|
- String tempFileName = param.getIdentifier() + fileName.substring(fileName.lastIndexOf(".")) + "." + param.getChunkNumber();
|
|
|
|
- // 获取文件路径
|
|
|
|
- /**Windows文件路径要加在哪个盘**/
|
|
|
|
- String filePath = ParamCache.getValue(CommonConstant.SYS_LOCAL_URL) + "largeFile/";
|
|
|
|
- //String filePath ="/Users/hongchuangyanfa/Desktop/largeFile/";
|
|
|
|
-
|
|
|
|
- File tempFile = buildUploadFile(tempFileName);
|
|
|
|
-
|
|
|
|
- //为了防止win路径有时会抛异常,增加一个处理
|
|
|
|
- try {
|
|
|
|
- param.getFile().transferTo(tempFile);
|
|
|
|
- } catch (Exception e) {
|
|
|
|
- writeMultipartFileToFile(param, tempFile);
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
-
|
|
|
|
- /**
|
|
|
|
- * 以上意思是把每个分片都保存成本地一个文件,如 测试.mp4.1 最后合并
|
|
|
|
- * ===================================================
|
|
|
|
- * 以下注释是把分片存到一个文件,持续写入,感觉不太保险**/
|
|
|
|
-
|
|
|
|
- LargeFile largeFile = new LargeFile();
|
|
|
|
- largeFile.setFileKey(param.getIdentifier());
|
|
|
|
- largeFile.setName(tempFileName);
|
|
|
|
- largeFile.setPath(filePath + fileName);
|
|
|
|
- largeFile.setShardIndex(param.getChunkNumber());
|
|
|
|
- largeFile.setShardSize(param.getChunkSize());
|
|
|
|
- largeFile.setSize(Integer.valueOf(param.getCurrentChunkSize()));
|
|
|
|
- largeFile.setShardTotal(param.getTotalChunks());
|
|
|
|
- largeFile.setSuffix(param.getObjectType());
|
|
|
|
- iLargeFileService.save(largeFile);
|
|
|
|
-
|
|
|
|
- if ((param.getTotalChunks() + "").equals(param.getChunkNumber() + "")) {
|
|
|
|
- Thread.sleep(1000);
|
|
|
|
- //第八步 检查文件是否全部完成上传
|
|
|
|
- lock.lock();
|
|
|
|
- try {
|
|
|
|
- // 检测是否为最后一块分片
|
|
|
|
- QueryWrapper<LargeFile> wrapper1 = new QueryWrapper<LargeFile>()
|
|
|
|
- .eq("file_key", param.getIdentifier()).eq("is_deleted", 0);
|
|
|
|
-
|
|
|
|
- Integer count = Math.toIntExact(iLargeFileService.count(wrapper1));
|
|
|
|
- if (count.equals(param.getTotalChunks())) {
|
|
|
|
- /**每个文件保存到本地所使用的合并各个文件**/
|
|
|
|
- merge(largeFile, filePath);
|
|
|
|
- String path = largeFile.getPath(); //获取到的路径 没有.1 .2 这样的东西
|
|
|
|
-
|
|
|
|
- //截取视频所在的路径
|
|
|
|
- path = path.replace(filePath, "");
|
|
|
|
- File file = new File(filePath + path);
|
|
|
|
- //修改成原来的文件名
|
|
|
|
- renameFile(file, param.getFilename());
|
|
|
|
- FileInputStream inputStream = new FileInputStream(filePath + param.getFilename());
|
|
|
|
-// 上传oss
|
|
|
|
- long l = System.currentTimeMillis();
|
|
|
|
- System.out.println("kaishi====================================" + l);
|
|
|
|
- BladeFile bladeFile = ossBuilder.template().putFile(param.getFilename(), inputStream);
|
|
|
|
- System.out.println("param.getFilename()====================================" + param.getFilename());
|
|
|
|
- System.out.println("bladeFile====================================" + bladeFile.getOriginalName());
|
|
|
|
- long l1 = System.currentTimeMillis();
|
|
|
|
- System.out.println("jieshu====================================" + (l1 - l));
|
|
|
|
-
|
|
|
|
- File file1 = new File(filePath + param.getFilename());
|
|
|
|
- MultipartFile multipartFile = getMultipartFile(file1);
|
|
|
|
- NewBladeFile newBladeFile = new NewBladeFile();
|
|
|
|
- if (param.getFilename().contains("pdf")) {
|
|
|
|
-// FileInputStream inputStream1 = new FileInputStream(filePath + param.getFilename());
|
|
|
|
- try {
|
|
|
|
- //PDDocument document = PDDocument.load(multipartFile.getInputStream());
|
|
|
|
- PdfReader pdfReader = new PdfReader(multipartFile.getInputStream());
|
|
|
|
- int pages = pdfReader.getNumberOfPages();
|
|
|
|
- //获取文件页数
|
|
|
|
- newBladeFile.setPage(pages);
|
|
|
|
- } catch (IOException e) {
|
|
|
|
- e.printStackTrace();
|
|
|
|
- }
|
|
|
|
- //pdf的路径就是文件上传的路径
|
|
|
|
- newBladeFile.setPdfUrl(bladeFile.getLink());
|
|
|
|
- } else if (param.getFilename().contains("xlsx") || param.getFilename().contains("xls")) {
|
|
|
|
- newBladeFile = this.commonFileClient.excelToPdf(multipartFile);
|
|
|
|
- } else if (param.getFilename().contains("docx")) {
|
|
|
|
- newBladeFile = this.commonFileClient.wordToPdf(multipartFile);
|
|
|
|
- } else if (param.getFilename().contains("png") || param.getFilename().contains("jpg")) {
|
|
|
|
-
|
|
|
|
- newBladeFile = this.commonFileClient.pngOrJpgToPdf(multipartFile);
|
|
|
|
- }
|
|
|
|
- BeanUtils.copyProperties(bladeFile, newBladeFile);
|
|
|
|
- newBladeFile.setFileSize(multipartFile.getSize() / 1024);
|
|
|
|
- //删除本地文件
|
|
|
|
- file1.delete();
|
|
|
|
- iLargeFileService.updateLargeFileDeleted(param.getIdentifier());
|
|
|
|
- result.setSuccess(true);
|
|
|
|
- result.setMsg("上传成功!");
|
|
|
|
- result.setData(newBladeFile);
|
|
|
|
- result.setCode(200);
|
|
|
|
- return result;
|
|
|
|
- }
|
|
|
|
- } catch (FileNotFoundException e) {
|
|
|
|
- e.printStackTrace();
|
|
|
|
- } catch (InterruptedException e) {
|
|
|
|
- e.printStackTrace();
|
|
|
|
- } finally {
|
|
|
|
- lock.unlock();
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
- result.setCode(200);
|
|
|
|
- result.setSuccess(true);
|
|
|
|
- result.setMsg("上传成功!");
|
|
|
|
- result.setData(largeFile.getShardIndex());
|
|
|
|
- return result;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- public void writeMultipartFileToFile(MultipartFileParam param, File tempFile) throws IOException {
|
|
|
|
- byte[] data = param.getFile().getBytes(); // 获取二进制数据
|
|
|
|
- FileOutputStream outputStream = new FileOutputStream(tempFile);
|
|
|
|
- try {
|
|
|
|
- outputStream.write(data); // 将二进制数据写入到文件中
|
|
|
|
- } finally {
|
|
|
|
- outputStream.close(); // 关闭输出流
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- /**
|
|
|
|
- * file 转 MultipartFile
|
|
|
|
- **/
|
|
|
|
- public static MultipartFile getMultipartFile(File file) {
|
|
|
|
- DiskFileItem item = new DiskFileItem("file",
|
|
|
|
- MediaType.MULTIPART_FORM_DATA_VALUE,
|
|
|
|
- true,
|
|
|
|
- file.getName(), (int) file.length(), file.getParentFile());
|
|
|
|
- try {
|
|
|
|
- OutputStream os = item.getOutputStream();
|
|
|
|
- os.write(FileUtils.readFileToByteArray(file));
|
|
|
|
- } catch (IOException e) {
|
|
|
|
- e.printStackTrace();
|
|
|
|
- }
|
|
|
|
- return new CommonsMultipartFile(item);
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- /**
|
|
|
|
- * 构建上传目录和文件
|
|
|
|
- */
|
|
|
|
- private File buildUploadFile(String name) {
|
|
|
|
- String fileName = name;
|
|
|
|
- String fullDir = buildUploadDir();
|
|
|
|
- return new File(fullDir, fileName);
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- /**
|
|
|
|
- * 构建上传完整目录
|
|
|
|
- */
|
|
|
|
- private String buildUploadDir() {
|
|
|
|
- String fullDir = ParamCache.getValue(CommonConstant.SYS_LOCAL_URL) + "largeFile/";
|
|
|
|
- //String fullDir ="/Users/hongchuangyanfa/Desktop/largeFile/";
|
|
|
|
- File dir = new File(fullDir);
|
|
|
|
- if (!dir.exists()) {
|
|
|
|
- dir.mkdirs();
|
|
|
|
- }
|
|
|
|
- return fullDir;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- /**
|
|
|
|
- * md5校验
|
|
|
|
- */
|
|
|
|
- private boolean checkMd5(InputStream is, String md5) {
|
|
|
|
- String check = "";
|
|
|
|
- try {
|
|
|
|
- check = DigestUtils.md5DigestAsHex(is);
|
|
|
|
- } catch (IOException e) {
|
|
|
|
- e.printStackTrace();
|
|
|
|
- }
|
|
|
|
- if (!check.equalsIgnoreCase(md5)) {
|
|
|
|
- return false;
|
|
|
|
- }
|
|
|
|
- return true;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- /**
|
|
|
|
- * @author fengxinglie
|
|
|
|
- * 合并分页
|
|
|
|
- */
|
|
|
|
- private void merge(LargeFile largeFile, String basePath) throws FileNotFoundException, InterruptedException {
|
|
|
|
- //合并分片开始
|
|
|
|
- String path = largeFile.getPath(); //获取到的路径 没有.1 .2 这样的东西
|
|
|
|
- //截取视频所在的路径
|
|
|
|
- path = path.replace(basePath, "");
|
|
|
|
- Integer shardTotal = largeFile.getShardTotal();
|
|
|
|
- File newFile = new File(basePath + path);
|
|
|
|
- FileOutputStream outputStream = new FileOutputStream(newFile, true); // 文件追加写入
|
|
|
|
- FileInputStream fileInputStream = null; //分片文件
|
|
|
|
- byte[] byt = new byte[10 * 1024 * 1024];
|
|
|
|
- int len;
|
|
|
|
- try {
|
|
|
|
- for (int i = 0; i < shardTotal; i++) {
|
|
|
|
- // 读取第i个分片
|
|
|
|
- fileInputStream = new FileInputStream(new File(basePath + path + "." + (i + 1))); // course\6sfSqfOwzmik4A4icMYuUe.mp4.1
|
|
|
|
- while ((len = fileInputStream.read(byt)) != -1) {
|
|
|
|
- outputStream.write(byt, 0, len);
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
- } catch (IOException e) {
|
|
|
|
- } finally {
|
|
|
|
- try {
|
|
|
|
- if (fileInputStream != null) {
|
|
|
|
- fileInputStream.close();
|
|
|
|
- }
|
|
|
|
- outputStream.close();
|
|
|
|
- } catch (Exception e) {
|
|
|
|
|
|
+ /**
|
|
|
|
+ * 对象存储构建类
|
|
|
|
+ */
|
|
|
|
+ private final OssBuilder ossBuilder;
|
|
|
|
+
|
|
|
|
+ private final ILargeFileService iLargeFileService;
|
|
|
|
+
|
|
|
|
+ private final Lock lock = new ReentrantLock();
|
|
|
|
+
|
|
|
|
+ private final CommonFileClient commonFileClient;
|
|
|
|
+
|
|
|
|
+ @Autowired
|
|
|
|
+ StringRedisTemplate RedisTemplate;
|
|
|
|
+
|
|
|
|
+ private final String bucketName = "bladex-chongqing-info";
|
|
|
|
+
|
|
|
|
+ private final String endpoint = "http://oss-cn-hangzhou.aliyuncs.com";
|
|
|
|
+
|
|
|
|
+ /**
|
|
|
|
+ * 创建存储桶
|
|
|
|
+ *
|
|
|
|
+ * @param bucketName 存储桶名称
|
|
|
|
+ * @return Bucket
|
|
|
|
+ */
|
|
|
|
+ @SneakyThrows
|
|
|
|
+ @PostMapping("/make-bucket")
|
|
|
|
+ @PreAuth(RoleConstant.HAS_ROLE_ADMIN)
|
|
|
|
+ public R makeBucket(@RequestParam String bucketName) {
|
|
|
|
+ ossBuilder.template().makeBucket(bucketName);
|
|
|
|
+ return R.success("创建成功");
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /**
|
|
|
|
+ * 创建存储桶
|
|
|
|
+ *
|
|
|
|
+ * @param bucketName 存储桶名称
|
|
|
|
+ * @return R
|
|
|
|
+ */
|
|
|
|
+ @SneakyThrows
|
|
|
|
+ @PostMapping("/remove-bucket")
|
|
|
|
+ @PreAuth(RoleConstant.HAS_ROLE_ADMIN)
|
|
|
|
+ public R removeBucket(@RequestParam String bucketName) {
|
|
|
|
+ ossBuilder.template().removeBucket(bucketName);
|
|
|
|
+ return R.success("删除成功");
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ /**
|
|
|
|
+ * @return
|
|
|
|
+ * @throws Exception
|
|
|
|
+ **/
|
|
|
|
+ @SneakyThrows
|
|
|
|
+ @PostMapping("/upload-file1234")
|
|
|
|
+ public R uploadByfile123(@RequestParam(value = "file", required = false) MultipartFile file,
|
|
|
|
+ @RequestParam(value = "identifier", required = false) String identifier,
|
|
|
|
+ @RequestParam(value = "chunkNumber", required = false) Integer chunkNumber,
|
|
|
|
+ @RequestParam(value = "chunkSize", required = false) Integer chunkSize,
|
|
|
|
+ @RequestParam(value = "currentChunkSize", required = false) String currentChunkSize,
|
|
|
|
+ @RequestParam(value = "filename", required = false) String filename,
|
|
|
|
+ @RequestParam(value = "relativePath", required = false) String relativePath,
|
|
|
|
+ @RequestParam(value = "totalChunks", required = false) Integer totalChunks,
|
|
|
|
+ @RequestParam(value = "totalSize", required = false) String totalSize,
|
|
|
|
+ @RequestParam(value = "objectType", required = false) String objectType) {
|
|
|
|
+
|
|
|
|
+ // 文件上传
|
|
|
|
+ String taskKey = "upload/20221220/" + filename;
|
|
|
|
+ UmsAdminLoginLogDO param = new UmsAdminLoginLogDO();
|
|
|
|
+ if (file == null && totalChunks >= 0 && StringUtils.isNotEmpty(identifier)) {
|
|
|
|
+ // 请求阿里云oss获取分片唯一ID
|
|
|
|
+ String ossSlicesId = this.getUploadId(taskKey);
|
|
|
|
+ RedisTemplate.opsForValue().set(identifier, ossSlicesId);
|
|
|
|
+ return R.fail("没有文件");
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ String uploadId = RedisTemplate.opsForValue().get(identifier);
|
|
|
|
+
|
|
|
|
+ UmsAdminLoginLogDO redisParam = (UmsAdminLoginLogDO) CacheUtil.get("oss", "PartETag", uploadId);
|
|
|
|
+ if (redisParam != null) {
|
|
|
|
+ param.setPartETags(redisParam.getPartETags());
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ Map<Integer, PartETag> partETags = param.getPartETags();
|
|
|
|
+
|
|
|
|
+ UploadPartRequest request = new UploadPartRequest();
|
|
|
|
+ request.setInputStream(file.getInputStream());
|
|
|
|
+ request.setBucketName(bucketName);
|
|
|
|
+ request.setPartNumber(chunkNumber);
|
|
|
|
+ request.setPartSize(Long.parseLong(currentChunkSize));
|
|
|
|
+ request.setKey(filename);
|
|
|
|
+ request.setMd5Digest(identifier);
|
|
|
|
+ request.setUploadId(uploadId);
|
|
|
|
+
|
|
|
|
+ try {
|
|
|
|
+ UploadPartResult uploadPartResult = ossBuilder.template().uploadPart(request);
|
|
|
|
+ PartETag partETag = uploadPartResult.getPartETag();
|
|
|
|
+ partETags.put(chunkNumber, partETag);
|
|
|
|
+ //分片编号等于总片数的时候合并文件,如果符合条件则合并文件,否则继续等待
|
|
|
|
+ if (chunkNumber == totalChunks) {
|
|
|
|
+ //合并文件,注意:partETags必须是所有分片的所以必须存入redis,然后取出放入集合
|
|
|
|
+ String url = this.completePartUploadFile(filename, uploadId,
|
|
|
|
+ new ArrayList<>(partETags.values()));
|
|
|
|
+ //oss地址返回后存入并清除redis
|
|
|
|
+ RedisTemplate.delete(uploadId);
|
|
|
|
+ return R.data(url);
|
|
|
|
+ } else {
|
|
|
|
+ RedisTemplate.opsForHash().putAll(uploadId, partETags);
|
|
|
|
+ CacheUtil.put("oss", "PartETag", uploadId, param);
|
|
|
|
+ }
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
+ e.printStackTrace();
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ return R.data("");
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /**
|
|
|
|
+ * 分块上传完成获取结果
|
|
|
|
+ */
|
|
|
|
+ public String completePartUploadFile(String fileKey, String uploadId, List<PartETag> partETags) {
|
|
|
|
+ CompleteMultipartUploadRequest request = new CompleteMultipartUploadRequest(bucketName, fileKey, uploadId,
|
|
|
|
+ partETags);
|
|
|
|
+ ossBuilder.template().completeMultipartUpload(request);
|
|
|
|
+ String downLoadUrl = getDownloadUrl(fileKey, bucketName);
|
|
|
|
+ return downLoadUrl;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /**
|
|
|
|
+ * 获取bucket文件的下载链接
|
|
|
|
+ *
|
|
|
|
+ * @param pathFile 首字母不带/的路径和文件
|
|
|
|
+ * @param bucketName
|
|
|
|
+ * @return 上报返回null, 成功返回地址
|
|
|
|
+ */
|
|
|
|
+ public String getDownloadUrl(String pathFile, String bucketName) {
|
|
|
|
+ if (bucketName == null || "".equals(bucketName)) {
|
|
|
|
+ bucketName = bucketName;
|
|
|
|
+ }
|
|
|
|
+ StringBuffer url = new StringBuffer();
|
|
|
|
+ url.append("http://").append(bucketName).append(endpoint).append("/");
|
|
|
|
+ if (pathFile != null && !"".equals(pathFile)) {
|
|
|
|
+ url.append(pathFile);
|
|
|
|
+ }
|
|
|
|
+ return url.toString();
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ public String getUploadId(String fileKey) {
|
|
|
|
+ InitiateMultipartUploadRequest request = new InitiateMultipartUploadRequest(bucketName, fileKey);
|
|
|
|
+ // 初始化分片
|
|
|
|
+ InitiateMultipartUploadResult unrest = ossBuilder.template().initiateMultipartUpload(request);
|
|
|
|
+ // 返回uploadId,它是分片上传事件的唯一标识,您可以根据这个ID来发起相关的操作,如取消分片上传、查询分片上传等。
|
|
|
|
+ String uploadId = unrest.getUploadId();
|
|
|
|
+ return uploadId;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ /**
|
|
|
|
+ * @return
|
|
|
|
+ * @throws Exception-----------------------------------------------------------------------
|
|
|
|
+ **/
|
|
|
|
+ @SneakyThrows
|
|
|
|
+ @PostMapping("/upload-file")
|
|
|
|
+ public R uploadByfile(@RequestParam(value = "file", required = false) MultipartFile file,
|
|
|
|
+ @RequestParam(value = "identifier", required = false) String identifier,
|
|
|
|
+ @RequestParam(value = "chunkNumber", required = false) Integer chunkNumber,
|
|
|
|
+ @RequestParam(value = "chunkSize", required = false) Integer chunkSize,
|
|
|
|
+ @RequestParam(value = "currentChunkSize", required = false) String currentChunkSize,
|
|
|
|
+ @RequestParam(value = "filename", required = false) String filename,
|
|
|
|
+ @RequestParam(value = "relativePath", required = false) String relativePath,
|
|
|
|
+ @RequestParam(value = "totalChunks", required = false) Integer totalChunks,
|
|
|
|
+ @RequestParam(value = "totalSize", required = false) String totalSize,
|
|
|
|
+ @RequestParam(value = "objectType", required = false) String objectType) throws Exception {
|
|
|
|
+ R result = new R();
|
|
|
|
+ // 判断是否上传
|
|
|
|
+ if (file == null) {
|
|
|
|
+ result.setSuccess(false);
|
|
|
|
+ result.setMsg("没有文件!");
|
|
|
|
+ return result;
|
|
|
|
+ }
|
|
|
|
+ MultipartFileParam param = new MultipartFileParam();
|
|
|
|
+ param.setFile(file);
|
|
|
|
+ param.setIdentifier(identifier);
|
|
|
|
+ param.setChunkNumber(chunkNumber);
|
|
|
|
+ param.setChunkSize(chunkSize);
|
|
|
|
+ param.setCurrentChunkSize(currentChunkSize);
|
|
|
|
+ param.setFilename(filename);
|
|
|
|
+ param.setRelativePath(relativePath);
|
|
|
|
+ param.setTotalChunks(totalChunks);
|
|
|
|
+ param.setTotalSize(totalSize);
|
|
|
|
+ param.setObjectType(objectType);
|
|
|
|
+ return uploadByMappedByteBuffer(param);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ public static String getSysLocalFileUrl() {
|
|
|
|
+ String file_path = ParamCache.getValue(CommonConstant.SYS_LOCAL_URL);
|
|
|
|
+ if (SystemUtils.isMacOs()) {
|
|
|
|
+ file_path = "/Users/hongchuangyanfa/Desktop/";
|
|
|
|
+ } else if (SystemUtils.isWindows()) {
|
|
|
|
+ file_path = "C://upload";
|
|
|
|
+ }
|
|
|
|
+ return file_path;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /**
|
|
|
|
+ * 分块上传
|
|
|
|
+ * 第一步:获取RandomAccessFile,随机访问文件类的对象
|
|
|
|
+ * 第二步:调用RandomAccessFile的getChannel()方法,打开文件通道 FileChannel
|
|
|
|
+ * 第三步:获取当前是第几个分块,计算文件的最后偏移量
|
|
|
|
+ * 第四步:获取当前文件分块的字节数组,用于获取文件字节长度
|
|
|
|
+ * 第五步:使用文件通道FileChannel类的 map()方法创建直接字节缓冲器 MappedByteBuffer
|
|
|
|
+ * 第六步:将分块的字节数组放入到当前位置的缓冲区内 mappedByteBuffer.put(byte[] b);
|
|
|
|
+ * 第七步:释放缓冲区
|
|
|
|
+ * 第八步:检查文件是否全部完成上传
|
|
|
|
+ */
|
|
|
|
+
|
|
|
|
+ public R uploadByMappedByteBuffer(MultipartFileParam param) throws Exception {
|
|
|
|
+ R result = new R();
|
|
|
|
+
|
|
|
|
+ // 首次
|
|
|
|
+ if (param.getIdentifier() == null || "".equals(param.getIdentifier())) {
|
|
|
|
+ return result;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // 判断是否上传
|
|
|
|
+ if (param.getFile() == null) {
|
|
|
|
+ result.setSuccess(false);
|
|
|
|
+ result.setMsg("没有文件!");
|
|
|
|
+ return result;
|
|
|
|
+ }
|
|
|
|
+ if (checkMd5(param.getFile().getInputStream(), param.getIdentifier())) {
|
|
|
|
+ result.setSuccess(false);
|
|
|
|
+ result.setMsg("文件的Identifier对不上!");
|
|
|
|
+ return result;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // 获取文件路径
|
|
|
|
+ String filePath = this.getSysLocalFileUrl() + "largeFile/";
|
|
|
|
+ /**
|
|
|
|
+ * 查询是否已经上传该分片
|
|
|
|
+ * **/
|
|
|
|
+ QueryWrapper<LargeFile> wrapper = new QueryWrapper<LargeFile>()
|
|
|
|
+ .eq("file_key", param.getIdentifier()).eq("is_deleted", 0).eq("shard_index", param.getChunkNumber()).orderByDesc("shard_index");
|
|
|
|
+ LargeFile list = iLargeFileService.getOne(wrapper);
|
|
|
|
+ LargeFile largeFile = null;
|
|
|
|
+ if (list != null) { // 该分片已经上传
|
|
|
|
+ if (StringUtils.isNotEmpty(list.getPath())) {
|
|
|
|
+ File chfile = new File(list.getPath());
|
|
|
|
+ if (chfile.exists()) {
|
|
|
|
+ result.setSuccess(true);
|
|
|
|
+ result.setMsg("该分片已上传!");
|
|
|
|
+ result.setData(list.getShardIndex());
|
|
|
|
+ result.setCode(200);
|
|
|
|
+ } else {
|
|
|
|
+ iLargeFileService.remove(wrapper);
|
|
|
|
+ largeFile = addLocalFileOne(param, filePath);
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ } else { //没有上传时
|
|
|
|
+ largeFile = addLocalFileOne(param, filePath);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // 是否为最后一片
|
|
|
|
+ if ((param.getTotalChunks() + "").equals(param.getChunkNumber() + "")) {
|
|
|
|
+ // 检测是否为最后一块分片
|
|
|
|
+ for (int i = 0; i <= 2; i++) {
|
|
|
|
+ QueryWrapper<LargeFile> wrapper1 = new QueryWrapper<LargeFile>()
|
|
|
|
+ .eq("file_key", param.getIdentifier()).eq("is_deleted", 0);
|
|
|
|
+ Integer count = Math.toIntExact(iLargeFileService.count(wrapper1));
|
|
|
|
+ if (count.equals(param.getTotalChunks())) {
|
|
|
|
+ NewBladeFile newBladeFile = SaveOssInfo(param, largeFile, filePath);
|
|
|
|
+ result.setData(newBladeFile);
|
|
|
|
+ result.setCode(200);
|
|
|
|
+ result.setSuccess(true);
|
|
|
|
+ result.setMsg("操作成功!");
|
|
|
|
+ } else {
|
|
|
|
+ Thread.sleep(2000);
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ return result;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ public NewBladeFile SaveOssInfo(MultipartFileParam param, LargeFile largeFile, String filePath) {
|
|
|
|
+ lock.lock();
|
|
|
|
+ NewBladeFile newBladeFile = new NewBladeFile();
|
|
|
|
+ try {
|
|
|
|
+ /**每个文件保存到本地所使用的合并各个文件**/
|
|
|
|
+ merge(largeFile, filePath);
|
|
|
|
+ String path = largeFile.getPath(); //获取到的路径 没有.1 .2 这样的东西
|
|
|
|
+ //截取视频所在的路径
|
|
|
|
+ path = path.replace(filePath, "");
|
|
|
|
+ File file = new File(filePath + path);
|
|
|
|
+ //修改成原来的文件名
|
|
|
|
+ renameFile(file, param.getFilename());
|
|
|
|
+ FileInputStream inputStream = new FileInputStream(filePath + param.getFilename());
|
|
|
|
+ //上传oss
|
|
|
|
+ BladeFile bladeFile = ossBuilder.template().putFile(param.getFilename(), inputStream);
|
|
|
|
+ File file1 = new File(filePath + param.getFilename());
|
|
|
|
+ MultipartFile multipartFile = getMultipartFile(file1);
|
|
|
|
+ if (param.getFilename().contains("pdf")) {
|
|
|
|
+ try {
|
|
|
|
+ PdfReader pdfReader = new PdfReader(multipartFile.getInputStream());
|
|
|
|
+ int pages = pdfReader.getNumberOfPages();
|
|
|
|
+ //获取文件页数
|
|
|
|
+ newBladeFile.setPage(pages);
|
|
|
|
+ } catch (IOException e) {
|
|
|
|
+ e.printStackTrace();
|
|
|
|
+ }
|
|
|
|
+ //pdf的路径就是文件上传的路径
|
|
|
|
+ newBladeFile.setPdfUrl(bladeFile.getLink());
|
|
|
|
+ } else if (param.getFilename().contains("xlsx") || param.getFilename().contains("xls")) {
|
|
|
|
+ newBladeFile = this.commonFileClient.excelToPdf(multipartFile);
|
|
|
|
+ } else if (param.getFilename().contains("docx")) {
|
|
|
|
+ newBladeFile = this.commonFileClient.wordToPdf(multipartFile);
|
|
|
|
+ } else if (param.getFilename().contains("png") || param.getFilename().contains("jpg")) {
|
|
|
|
+
|
|
|
|
+ newBladeFile = this.commonFileClient.pngOrJpgToPdf(multipartFile);
|
|
|
|
+ }
|
|
|
|
+ BeanUtils.copyProperties(bladeFile, newBladeFile);
|
|
|
|
+ newBladeFile.setFileSize(multipartFile.getSize() / 1024);
|
|
|
|
+ //删除本地文件
|
|
|
|
+ file1.delete();
|
|
|
|
+ iLargeFileService.updateLargeFileDeleted(param.getIdentifier());
|
|
|
|
+ } catch (FileNotFoundException e) {
|
|
|
|
+ e.printStackTrace();
|
|
|
|
+ } catch (InterruptedException e) {
|
|
|
|
+ e.printStackTrace();
|
|
|
|
+ } finally {
|
|
|
|
+ lock.unlock();
|
|
|
|
+ }
|
|
|
|
+ return newBladeFile;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ public LargeFile addLocalFileOne(MultipartFileParam param, String filePath) throws IOException {
|
|
|
|
+ // 文件名称
|
|
|
|
+ String fileName = getFileName(param);
|
|
|
|
+ // 临时文件名称
|
|
|
|
+ String tempFileName = param.getIdentifier() + fileName.substring(fileName.lastIndexOf(".")) + "." + param.getChunkNumber();
|
|
|
|
+ File tempFile = buildUploadFile(tempFileName);
|
|
|
|
+ //为了防止win路径有时会抛异常,增加一个处理
|
|
|
|
+ try {
|
|
|
|
+ param.getFile().transferTo(tempFile);
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
+ writeMultipartFileToFile(param, tempFile);
|
|
|
|
+ }
|
|
|
|
+ LargeFile largeFile = new LargeFile();
|
|
|
|
+ largeFile.setFileKey(param.getIdentifier());
|
|
|
|
+ largeFile.setName(tempFileName);
|
|
|
|
+ largeFile.setPath(filePath + fileName);
|
|
|
|
+ largeFile.setShardIndex(param.getChunkNumber());
|
|
|
|
+ largeFile.setShardSize(param.getChunkSize());
|
|
|
|
+ largeFile.setSize(Integer.valueOf(param.getCurrentChunkSize()));
|
|
|
|
+ largeFile.setShardTotal(param.getTotalChunks());
|
|
|
|
+ largeFile.setSuffix(param.getObjectType());
|
|
|
|
+ iLargeFileService.save(largeFile);
|
|
|
|
+ return largeFile;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ public void writeMultipartFileToFile(MultipartFileParam param, File tempFile) throws IOException {
|
|
|
|
+ byte[] data = param.getFile().getBytes(); // 获取二进制数据
|
|
|
|
+ FileOutputStream outputStream = new FileOutputStream(tempFile);
|
|
|
|
+ try {
|
|
|
|
+ outputStream.write(data); // 将二进制数据写入到文件中
|
|
|
|
+ } finally {
|
|
|
|
+ outputStream.close(); // 关闭输出流
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /**
|
|
|
|
+ * file 转 MultipartFile
|
|
|
|
+ **/
|
|
|
|
+ public static MultipartFile getMultipartFile(File file) {
|
|
|
|
+ DiskFileItem item = new DiskFileItem("file",
|
|
|
|
+ MediaType.MULTIPART_FORM_DATA_VALUE,
|
|
|
|
+ true,
|
|
|
|
+ file.getName(), (int) file.length(), file.getParentFile());
|
|
|
|
+ try {
|
|
|
|
+ OutputStream os = item.getOutputStream();
|
|
|
|
+ os.write(FileUtils.readFileToByteArray(file));
|
|
|
|
+ } catch (IOException e) {
|
|
|
|
+ e.printStackTrace();
|
|
|
|
+ }
|
|
|
|
+ return new CommonsMultipartFile(item);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /**
|
|
|
|
+ * 构建上传目录和文件
|
|
|
|
+ */
|
|
|
|
+ private File buildUploadFile(String name) {
|
|
|
|
+ String fileName = name;
|
|
|
|
+ String fullDir = buildUploadDir();
|
|
|
|
+ return new File(fullDir, fileName);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /**
|
|
|
|
+ * 构建上传完整目录
|
|
|
|
+ */
|
|
|
|
+ private String buildUploadDir() {
|
|
|
|
+ String fullDir = getSysLocalFileUrl() + "largeFile/";
|
|
|
|
+ File dir = new File(fullDir);
|
|
|
|
+ if (!dir.exists()) {
|
|
|
|
+ dir.mkdirs();
|
|
|
|
+ }
|
|
|
|
+ return fullDir;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /**
|
|
|
|
+ * md5校验
|
|
|
|
+ */
|
|
|
|
+ private boolean checkMd5(InputStream is, String md5) {
|
|
|
|
+ String check = "";
|
|
|
|
+ try {
|
|
|
|
+ check = DigestUtils.md5DigestAsHex(is);
|
|
|
|
+ } catch (IOException e) {
|
|
|
|
+ e.printStackTrace();
|
|
|
|
+ }
|
|
|
|
+ if (!check.equalsIgnoreCase(md5)) {
|
|
|
|
+ return false;
|
|
|
|
+ }
|
|
|
|
+ return true;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /**
|
|
|
|
+ * @author fengxinglie
|
|
|
|
+ * 合并分页
|
|
|
|
+ */
|
|
|
|
+ private void merge(LargeFile largeFile, String basePath) throws FileNotFoundException, InterruptedException {
|
|
|
|
+ //合并分片开始
|
|
|
|
+ String path = largeFile.getPath(); //获取到的路径 没有.1 .2 这样的东西
|
|
|
|
+ //截取视频所在的路径
|
|
|
|
+ path = path.replace(basePath, "");
|
|
|
|
+ Integer shardTotal = largeFile.getShardTotal();
|
|
|
|
+ File newFile = new File(basePath + path);
|
|
|
|
+ FileOutputStream outputStream = new FileOutputStream(newFile, true); // 文件追加写入
|
|
|
|
+ FileInputStream fileInputStream = null; //分片文件
|
|
|
|
+ byte[] byt = new byte[10 * 1024 * 1024];
|
|
|
|
+ int len;
|
|
|
|
+ try {
|
|
|
|
+ for (int i = 0; i < shardTotal; i++) {
|
|
|
|
+ // 读取第i个分片
|
|
|
|
+ fileInputStream = new FileInputStream(new File(basePath + path + "." + (i + 1))); // course\6sfSqfOwzmik4A4icMYuUe.mp4.1
|
|
|
|
+ while ((len = fileInputStream.read(byt)) != -1) {
|
|
|
|
+ outputStream.write(byt, 0, len);
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ } catch (IOException e) {
|
|
|
|
+ } finally {
|
|
|
|
+ try {
|
|
|
|
+ if (fileInputStream != null) {
|
|
|
|
+ fileInputStream.close();
|
|
|
|
+ }
|
|
|
|
+ outputStream.close();
|
|
|
|
+ } catch (Exception e) {
|
|
// log.error("IO流关闭", e);
|
|
// log.error("IO流关闭", e);
|
|
- }
|
|
|
|
- }
|
|
|
|
- //告诉java虚拟机去回收垃圾 至于什么时候回收 这个取决于 虚拟机的决定
|
|
|
|
- System.gc();
|
|
|
|
- //等待100毫秒 等待垃圾回收去 回收完垃圾
|
|
|
|
- Thread.sleep(100);
|
|
|
|
- for (int i = 0; i < shardTotal; i++) {
|
|
|
|
- String filePath = basePath + path + "." + (i + 1);
|
|
|
|
- File file = new File(filePath);
|
|
|
|
- boolean result = file.delete();
|
|
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ //告诉java虚拟机去回收垃圾 至于什么时候回收 这个取决于 虚拟机的决定
|
|
|
|
+ System.gc();
|
|
|
|
+ //等待100毫秒 等待垃圾回收去 回收完垃圾
|
|
|
|
+ Thread.sleep(100);
|
|
|
|
+ for (int i = 0; i < shardTotal; i++) {
|
|
|
|
+ String filePath = basePath + path + "." + (i + 1);
|
|
|
|
+ File file = new File(filePath);
|
|
|
|
+ boolean result = file.delete();
|
|
// log.info("删除{},{}", filePath, result ? "成功" : "失败");
|
|
// log.info("删除{},{}", filePath, result ? "成功" : "失败");
|
|
- }
|
|
|
|
|
|
+ }
|
|
// log.info("删除分片结束");
|
|
// log.info("删除分片结束");
|
|
- }
|
|
|
|
-
|
|
|
|
- /**
|
|
|
|
- * 文件重命名
|
|
|
|
- *
|
|
|
|
- * @param toBeRenamed 将要修改名字的文件
|
|
|
|
- * @param toFileNewName 新的名字
|
|
|
|
- * @return
|
|
|
|
- */
|
|
|
|
- private static boolean renameFile(File toBeRenamed, String toFileNewName) {
|
|
|
|
- //检查要重命名的文件是否存在,是否是文件
|
|
|
|
- if (!toBeRenamed.exists() || toBeRenamed.isDirectory()) {
|
|
|
|
- return false;
|
|
|
|
- }
|
|
|
|
- String p = toBeRenamed.getParent();
|
|
|
|
- File newFile = new File(p + File.separatorChar + toFileNewName);
|
|
|
|
- //修改文件名
|
|
|
|
- return toBeRenamed.renameTo(newFile);
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
-
|
|
|
|
- /**
|
|
|
|
- * 在MappedByteBuffer释放后再对它进行读操作的话就会引发jvm crash,在并发情况下很容易发生
|
|
|
|
- * 正在释放时另一个线程正开始读取,于是crash就发生了。所以为了系统稳定性释放前一般需要检 查是否还有线程在读或写
|
|
|
|
- *
|
|
|
|
- * @param mappedByteBuffer
|
|
|
|
- */
|
|
|
|
- private static void freeMappedByteBuffer(final MappedByteBuffer mappedByteBuffer) {
|
|
|
|
- try {
|
|
|
|
- if (mappedByteBuffer == null) {
|
|
|
|
- return;
|
|
|
|
- }
|
|
|
|
- mappedByteBuffer.force();
|
|
|
|
- AccessController.doPrivileged(new PrivilegedAction<Object>() {
|
|
|
|
- @Override
|
|
|
|
- public Object run() {
|
|
|
|
- try {
|
|
|
|
- Method getCleanerMethod = mappedByteBuffer.getClass().getMethod("cleaner", new Class[0]);
|
|
|
|
- //可以访问private的权限
|
|
|
|
- getCleanerMethod.setAccessible(true);
|
|
|
|
- //在具有指定参数的 方法对象上调用此 方法对象表示的底层方法
|
|
|
|
- sun.misc.Cleaner cleaner = (sun.misc.Cleaner) getCleanerMethod.invoke(mappedByteBuffer,
|
|
|
|
- new Object[0]);
|
|
|
|
- cleaner.clean();
|
|
|
|
- } catch (Exception e) {
|
|
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /**
|
|
|
|
+ * 文件重命名
|
|
|
|
+ *
|
|
|
|
+ * @param toBeRenamed 将要修改名字的文件
|
|
|
|
+ * @param toFileNewName 新的名字
|
|
|
|
+ * @return
|
|
|
|
+ */
|
|
|
|
+ private static boolean renameFile(File toBeRenamed, String toFileNewName) {
|
|
|
|
+ //检查要重命名的文件是否存在,是否是文件
|
|
|
|
+ if (!toBeRenamed.exists() || toBeRenamed.isDirectory()) {
|
|
|
|
+ return false;
|
|
|
|
+ }
|
|
|
|
+ String p = toBeRenamed.getParent();
|
|
|
|
+ File newFile = new File(p + File.separatorChar + toFileNewName);
|
|
|
|
+ //修改文件名
|
|
|
|
+ return toBeRenamed.renameTo(newFile);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ /**
|
|
|
|
+ * 在MappedByteBuffer释放后再对它进行读操作的话就会引发jvm crash,在并发情况下很容易发生
|
|
|
|
+ * 正在释放时另一个线程正开始读取,于是crash就发生了。所以为了系统稳定性释放前一般需要检 查是否还有线程在读或写
|
|
|
|
+ *
|
|
|
|
+ * @param mappedByteBuffer
|
|
|
|
+ */
|
|
|
|
+ private static void freeMappedByteBuffer(final MappedByteBuffer mappedByteBuffer) {
|
|
|
|
+ try {
|
|
|
|
+ if (mappedByteBuffer == null) {
|
|
|
|
+ return;
|
|
|
|
+ }
|
|
|
|
+ mappedByteBuffer.force();
|
|
|
|
+ AccessController.doPrivileged(new PrivilegedAction<Object>() {
|
|
|
|
+ @Override
|
|
|
|
+ public Object run() {
|
|
|
|
+ try {
|
|
|
|
+ Method getCleanerMethod = mappedByteBuffer.getClass().getMethod("cleaner", new Class[0]);
|
|
|
|
+ //可以访问private的权限
|
|
|
|
+ getCleanerMethod.setAccessible(true);
|
|
|
|
+ //在具有指定参数的 方法对象上调用此 方法对象表示的底层方法
|
|
|
|
+ sun.misc.Cleaner cleaner = (sun.misc.Cleaner) getCleanerMethod.invoke(mappedByteBuffer,
|
|
|
|
+ new Object[0]);
|
|
|
|
+ cleaner.clean();
|
|
|
|
+ } catch (Exception e) {
|
|
// log.error("clean MappedByteBuffer error!!!", e);
|
|
// log.error("clean MappedByteBuffer error!!!", e);
|
|
- }
|
|
|
|
- return null;
|
|
|
|
- }
|
|
|
|
- });
|
|
|
|
- } catch (Exception e) {
|
|
|
|
- e.printStackTrace();
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- private static String getFileName(MultipartFileParam param) {
|
|
|
|
- String extension;
|
|
|
|
- if (ObjectUtil.isNotEmpty(param.getFile())) {
|
|
|
|
- String filename = param.getFile().getOriginalFilename();
|
|
|
|
- extension = filename.substring(filename.lastIndexOf("."));
|
|
|
|
- } else {
|
|
|
|
- extension = param.getFilename().substring(param.getFilename().lastIndexOf("."));
|
|
|
|
- }
|
|
|
|
- return param.getIdentifier() + extension;
|
|
|
|
- }
|
|
|
|
|
|
+ }
|
|
|
|
+ return null;
|
|
|
|
+ }
|
|
|
|
+ });
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
+ e.printStackTrace();
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ private static String getFileName(MultipartFileParam param) {
|
|
|
|
+ String extension;
|
|
|
|
+ if (ObjectUtil.isNotEmpty(param.getFile())) {
|
|
|
|
+ String filename = param.getFile().getOriginalFilename();
|
|
|
|
+ extension = filename.substring(filename.lastIndexOf("."));
|
|
|
|
+ } else {
|
|
|
|
+ extension = param.getFilename().substring(param.getFilename().lastIndexOf("."));
|
|
|
|
+ }
|
|
|
|
+ return param.getIdentifier() + extension;
|
|
|
|
+ }
|
|
|
|
|
|
|
|
|
|
}
|
|
}
|