spring-boot中实现分片上传文件

一、上传文件基本实现

  • 1、前端效果图展示,这里使用element-ui plus来展示样式效果

在这里插入图片描述

  • 2、基础代码如下

    <template><div><el-uploadref="uploadRef"class="upload-demo":limit="1":on-change="handleExceed":auto-upload="false"><template #trigger><el-button type="primary">选择文件</el-button></template><el-button style="margin-left: 30px" type="success" @click="submitUpload"> 上传 </el-button></el-upload></div>
    </template><script setup>import { ref } from 'vue';import axios from 'axios';const fileRef = ref(null);const handleExceed = (files) => {console.log(files);fileRef.value = files;};const submitUpload = () => {console.log('开始上传文件', JSON.stringify(fileRef.value));const formData = new FormData();formData.append('file', fileRef.value.raw);axios.post('http://localhost:9002/file/upload', formData).then((res) => {console.log(fileRef.value, '??');console.log('上传成功');});};
    </script><style lang="scss" scoped></style>
  • 3、定义后端接口,并且处理好跨域(关于跨域处理,自己百度处理)

    package com.course.file.controller;import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.web.bind.annotation.PostMapping;
    import org.springframework.web.bind.annotation.RequestParam;
    import org.springframework.web.bind.annotation.RestController;
    import org.springframework.web.multipart.MultipartFile;@RestController
    public class UploadController {private static final Logger LOG = LoggerFactory.getLogger(UploadController.class);@PostMapping("/upload")public String uploadApi(@RequestParam MultipartFile file) {this.LOG.info("上传文件开始");this.LOG.info(file.getOriginalFilename());this.LOG.info(String.valueOf(file.getSize()));return "上传成功";}
    }
    
  • 4、保存文件到本地文件

    package com.course.file.controller;import com.course.file.utils.UuidUtil;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.web.bind.annotation.PostMapping;
    import org.springframework.web.bind.annotation.RequestParam;
    import org.springframework.web.bind.annotation.RestController;
    import org.springframework.web.multipart.MultipartFile;import java.io.File;
    import java.io.IOException;@RestController
    public class UploadController {private static final Logger LOG = LoggerFactory.getLogger(UploadController.class);@PostMapping("/upload")public String uploadApi(@RequestParam MultipartFile file) throws IOException {this.LOG.info("上传文件开始");this.LOG.info(file.getOriginalFilename());this.LOG.info(String.valueOf(file.getSize()));// 保存文件到本地String fileName = file.getOriginalFilename();String key = UuidUtil.getShortUuid();// 需要先本地创建一个file文件夹String fullPath = "E:/file/" + key + "-" + fileName;File dest = new File(fullPath);file.transferTo(dest);return "上传成功";}
    }
    

二、配置静态目录

  • 1、在FileApplication.java旁边添加一个SpringMvcConfig.java的文件

    package com.course.file;import org.springframework.context.annotation.Configuration;
    import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
    import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;@Configuration
    public class SpringMvcConfig implements WebMvcConfigurer {@Overridepublic void addResourceHandlers(ResourceHandlerRegistry registry) {registry.addResourceHandler("/f/**").addResourceLocations("file:E:/file/");}// http://localhost:9002/file/f/FdXMQJdF-xx.png
    }
    
  • 2、直接浏览器上直接访问上面的地址

  • 3、上传地址返回给前端

    @RestController
    public class UploadController {private static final Logger LOG = LoggerFactory.getLogger(UploadController.class);@PostMapping("/upload")public String uploadApi(@RequestParam MultipartFile file) throws IOException {this.LOG.info("上传文件开始");this.LOG.info(file.getOriginalFilename());this.LOG.info(String.valueOf(file.getSize()));// 保存文件到本地String fileName = file.getOriginalFilename();String key = UuidUtil.getShortUuid();// 需要先本地创建一个file文件夹String fullPath = "E:/file/" + key + "-" + fileName;File dest = new File(fullPath);file.transferTo(dest);return "http://localhost:9002/file/f/" + key + "-" + fileName;}
    }
    
  • 4、将上面几个固定的配置写到配置文件中

    file.path=E:/file/
    file.domain=http://localhost:9002/file/
    # 修改上传文件大小(不设置大文件可能上传失败)
    spring.servlet.multipart.max-file-size=50MB
    spring.servlet.multipart.max-request-size=50MB
    
  • 5、在控制器中使用

    public class UploadController {private static final Logger LOG = LoggerFactory.getLogger(UploadController.class);@Value("${file.path}")private String FILE_PATH;@Value("${file.domain}")private String FILE_DOMAIN;
    }
    

三、断点续传

  • 1、主要原理

    • 前端将大文件根据文件大小来切割成小片段,使用递归的方式调用后端接口,将文件上传到服务器端
    • 服务器端接收到前端上传片段,存储到服务器上
    • 等前端最后一个上传完成后,将全部的文件合并成一个文件
    • 合并完成后,返回一个url地址,将之前的分片上传的文件删除
  • 2、手动演示前端分段上传

    const submitUpload = () => {console.log('开始上传文件', JSON.stringify(fileRef.value));const formData = new FormData();const file = fileRef.value.raw;// 文件分配let shardSize = 5 * 1024 * 1024; // 以5MB为一个分片let shardIndex = 0; // 分片索引let start = shardIndex * shardSize; // 开始位置let end = Math.min(file.size, start + shardSize); // 结束位置let fileShard = file.slice(start, end); // 每次上传的分片数据formData.append('file', fileShard);axios.post('http://localhost:9002/file/upload', formData).then((res) => {console.log(fileRef.value, '??');console.log('上传成功');});};
    
  • 3、第二次的时候将shardIndex改为1

  • 4、查看本地文件夹下的文件

    在这里插入图片描述

  • 5、手动创建一个接口来尝试合并文件

    @GetMapping("merge")public String merge() throws FileNotFoundException {// 最终合成后的视频文件名称File newFile = new File(FILE_PATH + "test.mp4");FileOutputStream outputStream = new FileOutputStream(newFile, true);FileInputStream fileInputStream = null;byte[] bytes = new byte[5 * 1024 * 1024];int len;try {// 读取第一段fileInputStream = new FileInputStream(new File(FILE_PATH + "/pN0EoOny-blob"));while ((len = fileInputStream.read(bytes)) != -1) {outputStream.write(bytes, 0, len);}// 读取第二段fileInputStream = new FileInputStream(new File(FILE_PATH + "/f5oeIEDW-blob"));while ((len = fileInputStream.read(bytes)) != -1) {outputStream.write(bytes, 0, len);}// 读取第三段fileInputStream = new FileInputStream(new File(FILE_PATH + "/qsm8n03q-blob"));while ((len = fileInputStream.read(bytes)) != -1) {outputStream.write(bytes, 0, len);}} catch (IOException e) {LOG.error("合并分片失败", e);} finally {try {if (fileInputStream != null) {fileInputStream.close();}outputStream.close();LOG.info("IO流关闭");} catch (IOException e) {LOG.error("IO流关闭", e);}}return "合并视频成功";}
    

四、使用数据库来实现分片上传

  • 1、数据表字段

    在数据库中涉及key只跟文件有关,跟上传多少片没关系的,当已经上传的分片数和分片总数一样的时候就合并文件

    drop table if exists `file`;
    create table `file` (`id` int(11) not null PRIMARY key auto_increment comment '主键id',`path` varchar(100) not null comment '相对路径',`name` varchar(100) comment '文件名',`suffix` varchar(10) comment '后缀',`size` int(11) comment '大小|字节B',`shard_index` int(11) DEFAULT 0 COMMENT '已上传分片',`shard_size` int(11) DEFAULT 0 COMMENT '分片大小',`shard_total` int(11) DEFAULT 0 COMMENT '分片总数',`key` VARCHAR(100) DEFAULT NULL COMMENT '文件标识',`created_at` timestamp(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) COMMENT '创建时间',`updated_at` timestamp(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6) COMMENT '更新时间',`deleted_at` timestamp(6) NULL DEFAULT NULL COMMENT '软删除时间'
    ) engine=innodb default charset=utf8mb4 comment='文件';
    
  • 2、在pom.xml文件中添加mybatis-plus的依赖包

    <!--    配置连接到数据库    -->
    <dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>8.0.26</version>
    </dependency>
    <!--   mybatis plus 依赖包  -->
    <dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>3.2.0</version>
    </dependency><!--    mybatis 代码生成器    -->
    <dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-generator</artifactId><version>3.2.0</version>
    </dependency>
    
  • 3、application.properties添加配置

    # mysql数据库链接
    spring.datasource.url=jdbc:mysql://localhost:3306/beego?characterEncoding=utf-8&serverTimezone=GMT%2B8
    spring.datasource.username=root
    spring.datasource.password=123456# 配置mybatis-plus
    # 开启下划线转驼峰
    mybatis-plus.configuration.map-underscore-to-camel-case=true 
    mybatis-plus.configuration.auto-mapping-behavior=full
    mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
    # mapping的路径
    mybatis-plus.mapper-locations=classpath*:mapper/**/*Mapper.xml
    #主键类型  AUTO:"数据库ID自增", INPUT:"用户输入ID", ID_WORKER:"全局唯一ID (数字类型唯一ID)", UUID:"全局唯一ID UUID";
    mybatis-plus.global-config.db-config.id-type=AUTO
    # 逻辑删除(软删除)
    mybatis-plus.global-config.db-config.logic-delete-value=NOW()
    mybatis-plus.global-config.db-config.logic-not-delete-value=NULL
    
  • 4、使用模板生成器生成代码

  • 5、前端使用递归的方式来分片上传文件

    <script setup>import { ref } from 'vue';import { genFileId } from 'element-plus';import axios from 'axios';import md5 from 'js-md5';const fileRef = ref(null);const handleExceed = (files) => {console.log(files);fileRef.value = files;};const updateFile = (shardIndex) => {const formData = new FormData();const file = fileRef.value.raw;// 文件分配let shardSize = 5 * 1024 * 1024; // 以5MB为一个分片// let shardIndex = 0; // 分片索引let start = (shardIndex - 1) * shardSize; // 开始位置let end = Math.min(file.size, start + shardSize); // 结束位置let fileShard = file.slice(start, end); // 每次上传的分片数据// 前端多上传参数let size = file.size;let shardTotal = Math.ceil(size / shardSize); // 总片数const suffix = file.name.substring(file.name.lastIndexOf('.') + 1);formData.append('shard', fileShard);formData.append('shardIndex', shardIndex);formData.append('shardSize', shardSize);formData.append('shardTotal', shardTotal);formData.append('name', file.name);formData.append('size', size);formData.append('suffix', suffix); formData.append('key', md5(`${file.name}_${file.size}_${file.type}`));axios.post('http://localhost:9002/file/upload1', formData).then((res) => {// 判断如果当前的shardIndex < shardTotal的时候递归上传if (shardIndex < shardTotal) {updateFile(++shardIndex);} else {console.log('上传成功');}});};const submitUpload = () => {console.log('开始上传文件', JSON.stringify(fileRef.value));// 开始上传文件updateFile(1);};
    </script>
    
  • 6、后端对文件上传处理,存储到本地和入库操作

    package com.course.file.controller;import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
    import com.course.file.model.FileEntity;
    import com.course.file.service.IFileService;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.web.bind.annotation.PostMapping;
    import org.springframework.web.bind.annotation.RequestParam;
    import org.springframework.web.bind.annotation.RestController;
    import org.springframework.web.multipart.MultipartFile;import java.io.File;
    import java.io.IOException;@RestController
    public class FileController {@Autowiredprivate IFileService fileService;private static final Logger LOG = LoggerFactory.getLogger(FileController.class);@Value("${file.path}")private String FILE_PATH;@Value("${file.domain}")private String FILE_DOMAIN;@PostMapping("upload1")public String upload1(@RequestParam MultipartFile shard,Integer shardIndex,Integer shardSize,Integer shardTotal,String name,String suffix,Integer size,String key) throws IOException {this.LOG.info("开始上传文件");System.out.println("当前分片:" + shardIndex);System.out.println("当前分片大小:" + shardSize);System.out.println("当前分片总数:" + shardTotal);System.out.println("文件名称:" + name);System.out.println("文件后缀名:" + suffix);System.out.println("文件大小:" + size);System.out.println("文件唯一的key:" + key);// 文件保存到本地目录下String localPath = this.FILE_PATH + key + "." + suffix + "." + shardIndex;File dest = new File(localPath);shard.transferTo(dest);LOG.info(dest.getAbsolutePath());// 数据入库操作LambdaQueryWrapper<FileEntity> queryWrapper = new LambdaQueryWrapper<>();queryWrapper.eq(FileEntity::getFileKey, key);FileEntity fileEntity = new FileEntity();if (this.fileService.getOne(queryWrapper) != null) {// 说明不是第一次上传,需要更新当前上传的分片数量fileEntity.setShardIndex(shardIndex);if (this.fileService.update(fileEntity, queryWrapper)) {return "上传成功";} else {return "上传失败";}} else {// 第一次上传创建String path = this.FILE_PATH + key + "." + suffix;fileEntity.setFileKey(key);fileEntity.setName(name);fileEntity.setPath(path);fileEntity.setShardIndex(shardIndex);fileEntity.setSuffix(suffix);fileEntity.setShardSize(shardSize);fileEntity.setShardTotal(shardTotal);fileEntity.setSize(size);if (this.fileService.save(fileEntity)) {return "上传成功";} else {return "上传失败";}}}
    }
    
  • 7、查看本地目录是否生成分片文件

    在这里插入图片描述

  • 8、对本地分片文件合并操作,当shardIndex=shardTotal的时候进行合并操作

    @RestController
    public class FileController {@Autowiredprivate IFileService fileService;private static final Logger LOG = LoggerFactory.getLogger(FileController.class);@Value("${file.path}")private String FILE_PATH;@Value("${file.domain}")private String FILE_DOMAIN;@PostMapping("upload1")public String upload1(@RequestParam MultipartFile shard,Integer shardIndex,Integer shardSize,Integer shardTotal,String name,String suffix,Integer size,String key) throws IOException {this.LOG.info("开始上传文件");System.out.println("当前分片:" + shardIndex);System.out.println("当前分片大小:" + shardSize);System.out.println("当前分片总数:" + shardTotal);System.out.println("文件名称:" + name);System.out.println("文件后缀名:" + suffix);System.out.println("文件大小:" + size);System.out.println("文件唯一的key:" + key);// 文件保存到本地目录下String localPath = this.FILE_PATH + key + "." + suffix + "." + shardIndex;File dest = new File(localPath);shard.transferTo(dest);LOG.info(dest.getAbsolutePath());// 合并文件操作if (Objects.equals(shardIndex, shardTotal)) {this.merge(key, suffix, shardTotal);}// 数据入库操作LambdaQueryWrapper<FileEntity> queryWrapper = new LambdaQueryWrapper<>();queryWrapper.eq(FileEntity::getFileKey, key);FileEntity fileEntity = new FileEntity();if (this.fileService.getOne(queryWrapper) != null) {// 说明不是第一次上传,需要更新当前上传的分片数量fileEntity.setShardIndex(shardIndex);if (this.fileService.update(fileEntity, queryWrapper)) {return "上传成功";} else {return "上传失败";}} else {// 第一次上传创建String path = this.FILE_PATH + key + "." + suffix;fileEntity.setFileKey(key);fileEntity.setName(name);fileEntity.setPath(path);fileEntity.setShardIndex(shardIndex);fileEntity.setSuffix(suffix);fileEntity.setShardSize(shardSize);fileEntity.setShardTotal(shardTotal);fileEntity.setSize(size);if (this.fileService.save(fileEntity)) {return "上传成功";} else {return "上传失败";}}}/*** 对上传的文件片段合并操作* @param key* @param suffix* @param shardTotal* @throws FileNotFoundException*/private void merge(String key, String suffix, Integer shardTotal) throws FileNotFoundException {this.LOG.info("=====开始合并切片操作=====");String path = this.FILE_PATH + key + "." + suffix;File newFile = new File(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++) {fileInputStream = new FileInputStream(new File(this.FILE_PATH + key + "." + suffix + "." + (i + 1)));while ((len = fileInputStream.read(byt)) != -1) {outputStream.write(byt, 0, len);}}} catch (Exception e) {this.LOG.error("分片合并异常", e);} finally {try {if (fileInputStream != null) {fileInputStream.close();}outputStream.close();LOG.info("IO流关闭");} catch (Exception e) {LOG.error("IO流关闭", e);}}this.LOG.info("=====结束合并切片操作=====");}
    }
    
  • 9、当合并后可以对之前的分片进行删除操作,避免占用更的磁盘空间

    private void merge(String key, String suffix, Integer shardTotal) throws FileNotFoundException, InterruptedException {// ...this.LOG.info("=====结束合并切片操作=====");System.gc();Thread.sleep(1000);// 删除分片LOG.info("删除分片开始");for (int i = 0; i < shardTotal; i++) {String filePath = path + "." + (i + 1);File file = new File(filePath);boolean result = file.delete();this.LOG.info("删除{},{}", filePath, result ? "成功" : "失败");}LOG.info("删除分片结束");
    }
    

五、前端使用BS64提交数据

  • 1、使用bs64提交后端可以直接定义一个字符串接收数据,将接收到的数据转换为图片

  • 2、前端将上传的文件转换为bs64

    // 使用BS64上传const bs64UploadHandler = () => {const file = fileRef.value.raw;let fileReader = new FileReader();fileReader.onload = function (e) {const base64 = e.target.result;console.log('base64', base64);const suffix = file.name.substring(file.name.lastIndexOf('.') + 1);axios.post('http://localhost:9002/file/bs64Upload', {fileName: md5(`${file.name}_${file.size}_${file.type}`) + '.' + suffix,fileBs64: base64,}).then((res) => {console.log(res);});};fileReader.readAsDataURL(file);};
    
  • 3、后端定义一个方法,将字符串转换为MultipartFile数据类型

    package com.course.file.utils;import org.springframework.web.multipart.MultipartFile;
    import sun.misc.BASE64Decoder;import java.io.*;public class Base64ToMultipartFile implements MultipartFile {private final byte[] imgContent;private final String header;public Base64ToMultipartFile(byte[] imgContent, String header) {this.imgContent = imgContent;this.header = header.split(";")[0];}@Overridepublic String getName() {// TODO - implementation depends on your requirementsreturn System.currentTimeMillis() + Math.random() + "." + header.split("/")[1];}@Overridepublic String getOriginalFilename() {// TODO - implementation depends on your requirementsreturn System.currentTimeMillis() + (int) Math.random() * 10000 + "." + header.split("/")[1];}@Overridepublic String getContentType() {// TODO - implementation depends on your requirementsreturn header.split(":")[1];}@Overridepublic boolean isEmpty() {return imgContent == null || imgContent.length == 0;}@Overridepublic long getSize() {return imgContent.length;}@Overridepublic byte[] getBytes() throws IOException {return imgContent;}@Overridepublic InputStream getInputStream() throws IOException {return new ByteArrayInputStream(imgContent);}@Overridepublic void transferTo(File dest) throws IOException, IllegalStateException {new FileOutputStream(dest).write(imgContent);}public static MultipartFile base64ToMultipart(String base64) {try {String[] baseStrs = base64.split(",");BASE64Decoder decoder = new BASE64Decoder();byte[] b = new byte[0];b = decoder.decodeBuffer(baseStrs[1]);for(int i = 0; i < b.length; ++i) {if (b[i] < 0) {b[i] += 256;}}return new Base64ToMultipartFile(b, baseStrs[0]);} catch (IOException e) {e.printStackTrace();return null;}}
    }
    
  • 4、直接保存文件,这里就不做分片上传

    @PostMapping("bs64Upload")
    public String bs64Upload(@RequestBody FileDTO req) throws IOException {// 1.将上传的bs64转为图片String bs64 = req.getFileBs64();MultipartFile shard = Base64ToMultipartFile.base64ToMultipart(bs64);// 图片保存到本地String localPath = this.FILE_PATH + req.getFileName() ;File dest = new File(localPath);shard.transferTo(dest);LOG.info(dest.getAbsolutePath());return this.FILE_DOMAIN + req.getFileName();
    }
    

六、断点续传

  • 1、断点续传主要原谅,前端在点击上传按钮的时候先调用后端一个接口,判断之前是否有上传过记录,如果有就返回之前上传的分片shardIndex,前端就继续以这个分片来上传,如果没有就返回0表示从0开始上传

  • 2、后端定义一个接口根据key来查询数据库是否已经有上传过记录

    @GetMapping("{key}")
    public Integer getShardIndexByKeyApi(@PathVariable String key) {LambdaQueryWrapper<FileEntity> queryWrapper = new LambdaQueryWrapper<>();queryWrapper.eq(FileEntity::getFileKey, key).select(FileEntity::getShardIndex);FileEntity fileEntity = this.fileService.getOne(queryWrapper);return fileEntity.getShardIndex();
    }
    
  • 3、前端在使用上传前先调用上面的接口

     const submitUpload = () => {console.log('开始上传文件', JSON.stringify(fileRef.value));const file = fileRef.value.raw;const key = md5(`${file.name}_${file.size}_${file.type}`);axios.get(`http://localhost:9002/file/${key}`).then((response) => {if (response.data > 0) {// 历史上传updateFile(response.data + 1);} else {// 首次上传updateFile(1);}});};
    

七、秒传功能

  • 1、当前端使用MD5加密后提交的名字在数据库已经存在,则直接拼接url返回就可以,不需要再次上传

八、上传到阿里OSS

  • 1、配置依赖包

    <!--    阿里云oss存储    -->
    <dependency><groupId>com.aliyun.oss</groupId><artifactId>aliyun-sdk-oss</artifactId><version>3.10.2</version>
    </dependency>
    
  • 2、封装方法使用

    
    import com.aliyun.oss.OSS;
    import com.aliyun.oss.OSSClientBuilder;
    import com.aliyun.oss.model.ObjectMetadata;
    import com.aliyun.oss.model.PutObjectResult;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.stereotype.Component;
    import org.springframework.web.multipart.MultipartFile;import java.io.IOException;
    import java.io.InputStream;
    import java.net.URL;
    import java.util.Date;
    import java.util.List;
    import java.util.UUID;/*** 阿里云上传文件*/
    @Component
    public class OssUtil {@Value("${aliyun.oss.endpoint}")private String endpoint;@Value("${aliyun.oss.accessKeyId}")private String accessKeyId;@Value("${aliyun.oss.accessKeySecret}")private String accessKeySecret;@Value("${aliyun.oss.bucketName}")private String bucketName;//文件存储目录(自定义阿里云的)private String fileDir = "clouFile/";/*** 单个文件上传** @param file* @return*/public String uploadFile(MultipartFile file) {// 调用封装好的上传文件方法String fileUrl = uploadImg2Oss(file);// 返回完整的路径String str = getFileUrl(fileUrl);return str.trim();}/*** 单个文件上传(指定文件名需要后缀名)** @param file* @param fileName* @return*/public String uploadFile(MultipartFile file, String fileName) {try {InputStream inputStream = file.getInputStream();this.uploadFile2OSS(inputStream, fileName);return fileName;} catch (Exception e) {return "上传失败";}}/*** 多个文件的上传,返回路径用,分割** @param fileList* @return*/public String uploadFile(List<MultipartFile> fileList) {String fileUrl = "";String str = "";String photoUrl = "";for (int i = 0; i < fileList.size(); i++) {fileUrl = uploadImg2Oss(fileList.get(i));str = getFileUrl(fileUrl);if (i == 0) {photoUrl = str;} else {photoUrl += "," + str;}}return photoUrl.trim();}/*** 获取完整的路径名** @param fileUrl* @return*/private String getFileUrl(String fileUrl) {if (fileUrl != null && fileUrl.length() > 0) {String[] split = fileUrl.split("/");String url = this.getUrl(this.fileDir + split[split.length - 1]);return url;}return null;}/*** 获取去掉参数的完整路径** @param url* @return*/private String getShortUrl(String url) {String[] imgUrls = url.split("\\?");return imgUrls[0].trim();}/*** 获取url地址** @param key* @return*/private String getUrl(String key) {// 设置URL过期时间为20年  3600l* 1000*24*365*20Date expiration = new Date(new Date().getTime() + 3600l * 1000 * 24 * 365 * 20);// 生成URLOSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);URL url = ossClient.generatePresignedUrl(bucketName, key, expiration);if (url != null) {return getShortUrl(url.toString());}return null;}private String uploadImg2Oss(MultipartFile file) {//1、限制最大文件为20Mif (file.getSize() > 1024 * 1024 * 20) {return "图片太大";}//2、重命名文件String fileName = file.getOriginalFilename();String suffix = fileName.substring(fileName.lastIndexOf(".")).toLowerCase(); //文件后缀String uuid = UUID.randomUUID().toString();String name = uuid + suffix;try {InputStream inputStream = file.getInputStream();this.uploadFile2OSS(inputStream, name);return name;} catch (Exception e) {return "上传失败";}}/*** 使用阿里云上传文件** @param inStream 输入流* @param fileName 文件名称* @return*/private String uploadFile2OSS(InputStream inStream, String fileName) {String ret = "";try {//创建上传Object的MetadataObjectMetadata objectMetadata = new ObjectMetadata();objectMetadata.setContentLength(inStream.available());objectMetadata.setCacheControl("no-cache");objectMetadata.setHeader("Pragma", "no-cache");objectMetadata.setContentType(getContentType(fileName.substring(fileName.lastIndexOf("."))));objectMetadata.setContentDisposition("inline;filename=" + fileName);//上传文件OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);PutObjectResult putResult = ossClient.putObject(bucketName, fileDir + fileName, inStream, objectMetadata);ret = putResult.getETag();} catch (IOException e) {System.out.println(e.getMessage());} finally {try {if (inStream != null) {inStream.close();}} catch (IOException e) {e.printStackTrace();}}return ret;}/*** 获取文件类型** @param FilenameExtension* @return*/private static String getContentType(String FilenameExtension) {if (FilenameExtension.equalsIgnoreCase(".bmp")) {return "image/bmp";}if (FilenameExtension.equalsIgnoreCase(".gif")) {return "image/gif";}if (FilenameExtension.equalsIgnoreCase(".jpeg") ||FilenameExtension.equalsIgnoreCase(".jpg") ||FilenameExtension.equalsIgnoreCase(".png")) {return "image/jpeg";}if (FilenameExtension.equalsIgnoreCase(".html")) {return "text/html";}if (FilenameExtension.equalsIgnoreCase(".txt")) {return "text/plain";}if (FilenameExtension.equalsIgnoreCase(".vsd")) {return "application/vnd.visio";}if (FilenameExtension.equalsIgnoreCase(".pptx") ||FilenameExtension.equalsIgnoreCase(".ppt")) {return "application/vnd.ms-powerpoint";}if (FilenameExtension.equalsIgnoreCase(".docx") ||FilenameExtension.equalsIgnoreCase(".doc")) {return "application/msword";}if (FilenameExtension.equalsIgnoreCase(".xml")) {return "text/xml";}//PDFif (FilenameExtension.equalsIgnoreCase(".pdf")) {return "application/pdf";}return "image/jpeg";}
    }
    

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/132154.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

JavaEE-博客系统3(功能设计)

本部分内容为&#xff1a;实现登录功能&#xff1b;强制要求用户登录&#xff1b;实现显示用户信息&#xff1b;退出登录&#xff1b;发布博客 该部分的后端代码如下&#xff1a; Overrideprotected void doPost(HttpServletRequest req, HttpServletResponse resp) throws Ser…

mysql 中!= 到底走不走索引?

mysql 中! 到底走不走索引&#xff1f; 很多人疑惑! 到底走不走索引&#xff0c; 这里可以肯定的说该操作是可以走索引的&#xff0c;但实际情况中都为啥都不走索引呢&#xff1f; 首先我们要知道走索引与数据量和数据趋势&#xff08;cardinality&#xff09;有很大的关系&…

bug: https://aip.baidubce.com/oauth/2.0/token报错blocked by CORS policy

还是跟以前一样&#xff0c;我们先看报错点&#xff1a;&#xff08;注意小编这里是H5解决跨域的&#xff0c;不过解决跨域的原理都差不多&#xff09; Access to XMLHttpRequest at https://aip.baidubce.com/oauth/2.0/token from origin http://localhost:8000 has been blo…

设计模式之装饰模式

一、概念 装饰模式是一种结构型设计模式&#xff0c;允许你通过将对象放入包含行为的特殊封装对象中来为原对象绑定新的行为。 二、构造 部件&#xff08;Component&#xff09;声明封装器和被封装对象的公用接口。 具体部件&#xff08;Concrete Component&#xff09;类是…

随机森林在生物信息中的应用

今天与大家分享一项强大的机器学习算法随机森林。这个算法不仅在数据科学领域广泛应用&#xff0c;还在生物信息学中发挥了巨大的作用。 让我们一起探索随机森林的原理、优缺点以及它在生物信息领域的实际应用场景&#xff0c;本文将给出R语言进行应用的实际方法&#xff0c;利…

一个可以自动把微信聊天收到的二维码图片实时提取出来并分类的软件

10-1 如果你有需要实时地、自动地把微信各个群收到的二维码图片提取出来的需求&#xff0c;那本文章适合你&#xff0c;本文章的主要内容是教你如何实现自动提取微信收到的二维码图片&#xff0c;助你快速扫码&#xff0c;永远比别人领先一步。 首先需要准备好的材料&#xf…

SpringBoot + Vue2项目打包部署到服务器后,使用Nginx配置SSL证书,配置访问HTTP协议转HTTPS协议

配置nginx.conf文件&#xff0c;这个文件一般在/etc/nginx/...中&#xff0c;由于每个人的体质不一样&#xff0c;也有可能在别的路径里&#xff0c;自己找找... # 配置工作进程的最大连接数 events {worker_connections 1024; }# 配置HTTP服务 http {# 导入mime.types配置文件…

信驰达RF-DG-52PAS Zigbee 3.0协调器Home Assistant上手指南

一、使用前准备 RF-DG-52PAS是信驰达科技基于美国 TI CC2652P和CP2102为核心设计的Zigbee3.0 USB Dongle,可烧录 Z-Stack 3.x.0协调器固件&#xff0c;可以直接连接到计算机或树莓派&#xff0c;通过ZHA或 Zigbee2MQTT连接到 Home Assistant或其他开源物联网平台。还可以烧录…

c++实现观察者模式

前言 我觉得这是最有意思的模式&#xff0c;其中一个动&#xff0c;另外的自动跟着动。发布-订阅&#xff0c;我觉得很巧妙。 代码 头文件 #pragma once #include<vector> #include<string> #include<iostream>// 抽象观察者 class Aobserver { public:v…

python如何使用gspread读取google在线excel数据?

一、背景 公司使用google在线excel管理测试用例&#xff0c;为了方便把手工测试用到的测试数据用来做自动化用例测试数据&#xff0c;所以就想使用python读取在线excel数据&#xff0c;通过数据驱动方式&#xff0c;完成自动化回归测试&#xff0c;提升手动复制&#xff0c;粘…

【服务器使用】vscode winscp进行服务器容器连接(含修改初始密码)

1&#xff1a;获取docker的登陆信息 例如节点&#xff08;host&#xff09;、端口&#xff08;port&#xff09;、密码&#xff08;passwd&#xff09;等信息&#xff0c;这个自己找组内的前辈获取即可 2&#xff1a;配置config文件 找到vscode里面ssh处的config文件 人工找…

Vue Vue3

1、创建VUE3工程 使用vue-cli创建&#xff1a; ## 查看vue/cli版本&#xff0c;确保vue/cli版本在4.5.0以上 vue --version ## 安装或者升级你的vue/cli npm install -g vue/cli ## 创建 vue create vue_test ## 启动 cd vue_test npm run serve 使用vite创建&#xff1a; …

07、vue : 无法加载文件 C:\Users\JH\AppData\Roaming\npm\vue.ps1,因为在此系统上禁止运行脚本。

目录 问题解决&#xff1a; 问题 vue : 无法加载文件 C:\Users\JH\AppData\Roaming\npm\vue.ps1&#xff0c;因为在此系统上禁止运行脚本。 在使用 VSCode 时&#xff0c;创建 Vue 项目报的错 创建不了 Vue 项目 解决&#xff1a; 因为在此系统上禁止运行该脚本&#xff0…

从行车记录仪恢复已删除/丢失视频的方法

“我的车里有行车记录仪。几天前&#xff0c;当我下班回家时&#xff0c;一辆卡车不知从哪里冒出来撞向了我。我们的两辆车都损坏了&#xff0c;但幸运的是&#xff0c;没有人受伤。我曾与卡车司机就修理我的汽车进行过会面&#xff0c;但他说我有错。我需要查看我的行车记录仪…

【每日一题】移除链表元素(C语言)

移除链表元素&#xff0c;链接奉上 目录 思路&#xff1a;代码实现&#xff1a;链表题目小技巧&#xff1a; 思路&#xff1a; 在正常情况&#xff1a; 下我们移除链表元素时&#xff0c;需要该位置的前结点与后节点&#xff0c; 在特别情况时&#xff1a; 例如 我们发现&…

基于金枪鱼群算法的无人机航迹规划-附代码

基于金枪鱼群算法的无人机航迹规划 文章目录 基于金枪鱼群算法的无人机航迹规划1.金枪鱼群搜索算法2.无人机飞行环境建模3.无人机航迹规划建模4.实验结果4.1地图创建4.2 航迹规划 5.参考文献6.Matlab代码 摘要&#xff1a;本文主要介绍利用金枪鱼群算法来优化无人机航迹规划。 …

vue项目npm install报错解决

一、报错信息 node-sass4.14.1 postinstall: node scripts/build.js 二、解决方式 &#xff08;1&#xff09;删除未成功安装的 node_modules 文件&#xff1b; &#xff08;2&#xff09;为 node-sass 单独设置镜像源&#xff1b; npm config set sass_binary_sitehttps:/…

项目级asp.net框架的LIMS实验室管理系统源码

LIMS可用于管理完整的实验程序&#xff0c;从样品登记到检验、校核、审核到最终批准报告&#xff0c;建立在过程质量控制的基础上&#xff0c;对检测流程进行有效全面的管理&#xff0c;对影响质量的人、机、料、法、环因素加以控制&#xff0c;同时为质量改进提供数据依据。进…

Cassandra介绍(一)

1.1. 概念 Apache Cassandra 是高度可扩展的&#xff0c;高性能的分布式 NoSQL 数据库。 Cassandra 旨在处理许 多商品服务器上的大量数据&#xff0c;提供高可用性而无需担心单点故障。 Cassandra 具有能够处理大量数据的分布式架构。 数据放置在具有多个复制因子的不同机器…

Luminar Neo Mac/Windows中文版:引领AI图像编辑的革命性时代

Luminar Neo运用先进的AI技术&#xff0c;能够自动化地完成许多繁琐的编辑任务&#xff0c;如色彩校正、噪点消除、人脸识别等。这不仅大大提高了工作效率&#xff0c;同时也降低了对专业知识和技能的要求。无论你是专业摄影师&#xff0c;还是摄影爱好者&#xff0c;甚至是一个…