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.rhkb.cn/news/181902.html

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

相关文章

【算法】单调栈 每日温度 接雨水

文章目录 例题739. 每日温度42. 接雨水 相关练习1475. 商品折扣后的最终价格901. 股票价格跨度1019. 链表中的下一个更大节点84. 柱状图中最大的矩形 单调栈【基础算法精讲 26】 例题 739. 每日温度 https://leetcode.cn/problems/daily-temperatures/description/ 提示&a…

Git 的基本操作 ——命令行

Git 的工作流程 详解如下&#xff1a; 本地仓库&#xff1a;是在开发人员自己电脑上的Git仓库,存放我们的代码(.git 隐藏文件夹就是我们的本地仓库) 远程仓库&#xff1a;是在远程服务器上的Git仓库,存放代码(可以是github.com或者gitee.com 上的仓库,或者自己该公司的服务器…

php去除字符串两边空格空字符串换行方法

在PHP中&#xff0c;可以使用以下几种方法去除字符串两边的空格、空字符串和换行符&#xff1a; 使用trim()函数去除字符串两边的空格和空字符串&#xff0c;例如&#xff1a; $str " Hello World! "; $trimmed trim($str); echo $trimmed; 使用preg_replace(…

Angular-07:组件生命周期

三个阶段&#xff1a; ① 挂载阶段1.1 constructor1.2 ngOnInit ② 更新阶段2.1 ngOnChanges2.2 ngAfterViewInit2.3 ngAfterContentInit2.4 ngDoCheck ③ 卸载阶段3.1 onOnDestroy ④ 在组件中添加所有方法并打印 该表按照执行顺序编写 编号函数名实现名说明1constructorcons…

Java 开发常用的 Linux 命令

基本操作 Linux关机,重启 # 关机 shutdown -h now# 重启 shutdown -r now查看系统,CPU信息 # 查看系统内核信息 uname -a# 查看系统内核版本 cat /proc/version# 查看当前用户环境变量 envcat /proc/cpuinfo# 查看有几个逻辑cpu, 包括cpu型号 cat /proc/cpuinfo | grep name …

【Spring Boot 源码学习】JedisConnectionConfiguration 详解

Spring Boot 源码学习系列 JedisConnectionConfiguration 详解 引言往期内容主要内容1. RedisConnectionFactory1.1 单机连接1.2 集群连接1.3 哨兵连接 2. JedisConnectionConfiguration2.1 RedisConnectionConfiguration2.2 导入自动配置2.3 相关注解介绍2.4 redisConnectionF…

FreeRTOS笔记【一】 任务的创建(动态方法和静态方法)

一、任务创建和删除API函数 函数描述xTaskCreate()使用动态的方法创建一个任务xTaskCreateStatic()使用静态的方法创建一个任务xTaskCreateRestricted()创建一个使用MPU进行限制的任务&#xff0c;相关内存使用动态内存分配vTaskDelete()删除一个任务 二、动态创建任务 2.1 …

C语言——选择排序

完整代码&#xff1a; //选择排序 // 选择排序是一种简单直观的排序算法。它的工作原理如下:首先在未排序序列中找到最小&#xff08;大&#xff09;元素&#xff0c;存放到排序序列的起始位置&#xff0c;然后&#xff0c;再从剩余未排序元素中继续寻找最小&#xff08;大&am…

已完结,给小白的《50讲Python自动化办公》

大家好&#xff0c;这里是程序员晚枫&#xff0c;小红薯也叫这个名。 写在前面 上个周末去成都参加了第8届中国开源年会&#xff0c;认识了很多行业前辈和优秀的同龄人。 我发现在工作之外还能有一番事业的人&#xff0c;都有一个让我羡慕的共同点&#xff1a;有一个拿得出手…

Ubantu安装教程(其实和之前CentOS差不多)

文章目录 VM安装见下方参考链接Ubuntu安装我的是Ubuntu22.04.3官网下载我下载的桌面版LTS代表长期支持-这意味着五年的免费安全和维护更新选好版本点击下载就好&#xff08;注意桌面版和服务器版&#xff09; 搭建虚拟机个性化名字自定义安装位置不知道就先默认就好&#xff0c…

Java与Redis的集成

目录 Java连接Redis 导入依赖 Redis服务器准备 建立连接 Java操作Redis常用类型数据 Redis字符串(String) Redis哈希(Hash) Redis列表&#xff08;List&#xff09; Redis集合&#xff08;Set&#xff09; Redis有序集合&#xff08;Sorted Set&#xff09; Redis在项目应用…

类和对象解析

导言&#xff1a; Java是一门纯面向对象的语言&#xff0c;在面对对象的世界里&#xff0c;一切皆为对象。而对象的创建又和类的定义息息相关。本文主要阐述了类和对象的使用与理解。解释类的定义方式以及对象的实例化&#xff0c;类中的成员变量和成员方法的使用&#xff0c;…

Python基础入门例程45-NP45 禁止重复注册(条件语句)

最近的博文&#xff1a; Python基础入门例程44-NP44 判断列表是否为空&#xff08;条件语句&#xff09;-CSDN博客 Python基础入门例程43-NP43 判断布尔值&#xff08;条件语句&#xff09;-CSDN博客 Python基础入门例程42-NP42 公式计算器&#xff08;运算符&#xff09;-C…

吴恩达《机器学习》6-1->6-3:分类问题、假设陈述、决策界限

一、什么是分类问题&#xff1f; 在分类问题中&#xff0c;我们试图预测的变量&#x1d466;是离散的值&#xff0c;通常表示某种类别或标签。这些类别可以是二元的&#xff0c;也可以是多元的。分类问题的示例包括&#xff1a; 判断一封电子邮件是否是垃圾邮件&#xff08;二…

基于鹰栖息算法的无人机航迹规划-附代码

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

汽车标定技术(二)--基于XCP的标定测量实战

目录 1.工程创建 1.1 新建工程 1.2 设备配置 1.3 标定观测 1.4 刷写 2.原始hex文件与标定文件的合并 2.1 修改memory segment file 2.2 标定量地址偏移 ​编辑 2.3 标定后与原始hex文件合并 2.4 标定后直接merge 2.5 不用对ram地址进行偏移实现hex文件合并 本文使用…

linux 安装 elasticsearch 全教程

一、去 elasticsearch官网找到Linux版本的下载链接 地址https://www.elastic.co/cn/downloads/elasticsearch 二、在linux 中用wget下载 wget https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-8.10.4-linux-x86_64.tar.gz三、下载成功后解压文件 tar -x…

Quartz之JDBC-JobStoreTX配置

一、前言 上篇 《Quartz介绍》中使用的是RAMJobStored存储调度信息&#xff0c;当进程终止调度信息会丢失&#xff0c;本篇我们介绍使用JDBCJobStore来存储调度信息&#xff08;jobs、Triggers和日历&#xff09;。 二、Quartz 表结构 可以从官网&#xff08;http://www.qua…

java入门-JDK下载与安装

1、下载jdk Java 的产品叫JDK&#xff08;Java Development Kit: Java开发者工具包&#xff09;&#xff0c;必须安装JDK才能使用java 1、官网地址 https://www.oracle.com/java/ https://www.oracle.com/java/technologies/downloads/ 目前比较稳定的版本为 JDK17. 我们就安…

node教程(五)接口+会话

文章目录 一.接口1.1接口是什么?1.2接口的作用1.3接口的开发与调用1.4接口的组成 一.接口 1.1接口是什么? 接口是前后端通信的桥梁 1.2接口的作用 实现前后端通信 1.3接口的开发与调用 大多数接口都是由后端工程师开发的&#xff0c;开发语言不限 一般情况下接口都是由…