当前位置: 首页 > news >正文

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";}
    }
    

相关文章:

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

一、上传文件基本实现 1、前端效果图展示&#xff0c;这里使用element-ui plus来展示样式效果 2、基础代码如下 <template><div><el-uploadref"uploadRef"class"upload-demo":limit"1":on-change"handleExceed":auto-…...

【ICN综述】信息中心网络隐私安全

ICN基本原理&#xff1a; 信息中心网络也是需要实现在不可信环境下可靠的信息交换和身份认证 信息中心网络采用以数据内容为中心的传输方式代替现有IP 网络中以主机为中心的通信方式&#xff0c;淡化信息数据物理或逻辑位置的重要性&#xff0c;以内容标识为代表实现数据的查找…...

基于STC12C5A60S2系列1T 8051单片机EEPROM应用

基于STC12C5A60S2系列1T 8051单片机EEPROM应用 STC12C5A60S2系列1T 8051单片机管脚图STC12C5A60S2系列1T 8051单片机I/O口各种不同工作模式及配置STC12C5A60S2系列1T 8051单片机I/O口各种不同工作模式介绍STC12C5A60S2系列1T 8051单片机EEPROM介绍基于STC12C5A60S2系列1T 8051单…...

手撕排序之直接选择排序

前言&#xff1a; 直接选择排序是排序中比较简单的排序&#xff0c;同时也是时间复杂度不是很优的排序。 思想&#xff1a; 本文主要讲解直接选择排序的优化版本。 我们经过一次遍历直接将该数列中最大的和最小的值挑选出来&#xff0c;如果是升序&#xff0c;就将最小的和…...

洛谷 P1359 租用游艇

题目链接 P1359 租用游艇 普及 题目描述 长江游艇俱乐部在长江上设置了 n n n 个游艇出租站 1 , 2 , 3 , . . . , n 1,2,3,...,n 1,2,3,...,n&#xff0c;游客可在这些游艇出租站租用游艇&#xff0c;并在下游的任何一个游艇出租站归还游艇。游艇出租站 i i i 到游艇出租站…...

springboot中没有主清单属性解决办法

在执行一个 spring boot 启动类时&#xff0c;提示 没有主清单属性 一般这个问题是没加 spring-boot-maven-plugin 插件的问题&#xff0c;但是项目中已经加了 <build><plugins><plugin><groupId>org.springframework.boot</groupId><artifa…...

C/C++ static关键字详解(最全解析,static是什么,static如何使用,static的常考面试题)

目录 一、前言 二、static关键字是什么&#xff1f; 三、static关键字修饰的对象是什么&#xff1f; 四、C 语言中的 static &#x1f34e;static的C用法 &#x1f349;static的重点概念 &#x1f350;static修饰局部变量 &#x1f4a6;static在修饰局部变量和函数的作用 &a…...

windwos10搭建我的世界服务器,并通过内网穿透实现联机游戏Minecraft

文章目录 1. Java环境搭建2.安装我的世界Minecraft服务3. 启动我的世界服务4.局域网测试连接我的世界服务器5. 安装cpolar内网穿透6. 创建隧道映射内网端口7. 测试公网远程联机8. 配置固定TCP端口地址8.1 保留一个固定tcp地址8.2 配置固定tcp地址 9. 使用固定公网地址远程联机 …...

【实战Flask API项目指南】之七 用JWT进行用户认证与授权

实战Flask API项目指南之 用JWT进行用户认证与授权 本系列文章将带你深入探索实战Flask API项目指南&#xff0c;通过跟随小菜的学习之旅&#xff0c;你将逐步掌握 Flask 在实际项目中的应用。让我们一起踏上这个精彩的学习之旅吧&#xff01; 前言 当小菜踏入Flask后端开发…...

鸿蒙LiteOs读源码教程+向LiteOS中添加一个简单的基于线程运行时的短作业优先调度策略

【⭐据说点赞收藏的都会收获好运哦&#x1f44d;】 一、鸿蒙Liteos读源码教程 鸿蒙的源码是放在openharmony文件夹下&#xff0c;openharmony下的kernel文件夹存放操作系统内核的相关代码和实现。 内核是操作系统的核心部分&#xff0c;所以像负责&#xff1a;资源管理、任…...

axios的使用与封装详细教程

目录 一、axios使用方式二、axios在main.js配置 一、axios使用方式 在 Spring Boot Vue 的项目中使用 Axios&#xff0c;你需要在 Vue 项目中安装 Axios 库&#xff0c;因为 Axios 是一个前端 JavaScript 库&#xff0c;用于发送 HTTP 请求和处理响应数据&#xff0c;而与 Sp…...

C++二叉搜索树

本章主要是二叉树的进阶部分&#xff0c;学习搜索二叉树可以更好理解后面的map和set的特性。 1.二叉搜索树概念 二叉搜索树的递归定义为&#xff1a;非空左子树所有元素都小于根节点的值&#xff0c;非空右子树所有元素都大于根节点的值&#xff0c;而左右子树也是二叉搜索树…...

elasticsearch索引按日期拆分

1.索引拆分原因 如果单个索引数据量过大会导致搜索变慢&#xff0c;而且不方便清理历史数据。 例如日志数据每天量很大&#xff0c;而且需要定期清理以往日志数据。例如原索引为sc_all_system_log&#xff0c;现按天拆分索引sc_all_system_log20220902&#xff0c;sc_all_syste…...

纯python实现大漠图色功能

大漠图色是一种自动化测试工具&#xff0c;可以用于识别屏幕上的图像并执行相应的操作。在Python中&#xff0c;可以使用第三方库pyautogui来实现大漠图色功能。具体步骤如下&#xff1a; 安装pyautogui库&#xff1a;在命令行中输入pip install pyautogui。导入pyautogui库&a…...

debounce and throtlle

debounce // 核心&#xff1a;单位时间内触发>1 则只执行最后一次。//excutioner 可以认为是执行器。执行器存在则清空&#xff0c;再赋值新的执行器。function debounce(fn, delay 500) {let excutioner null;return function () {let context this;let args arguments…...

四、数据库系统

数据库系统&#xff08;Database System&#xff09;&#xff0c;是由数据库及其管理软件组成的系统。数据库系统是为适应数据处理的需要而发展起来的一种较为理想的数据处理系统&#xff0c;也是一个为实际可运行的存储、维护和应用系统提供数据的软件系统&#xff0c;是存储介…...

Linux中的高级IO

文章目录 1.IO1.1基本介绍1.2基础io的低效性1.3如何提高IO效率1.4五种IO模型1.5非阻塞模式的设置 2.IO多路转接之Select2.1函数的基本了解2.2fd_set理解2.3完整例子代码&#xff08;会在代码中进行讲解&#xff09;2.4优缺点 3.多路转接之poll3.1poll函数的介绍3.2poll服务器3.…...

项目管理之如何估算项目工作成本

在项目管理中&#xff0c;如何估算项目工作成本是一个关键问题。为了解决这个问题&#xff0c;我们可以采用自上而下的成本限额估算法和自下而上的成本汇总估算法。这两种方法各有优缺点&#xff0c;但都可以帮助我们准确地估算项目工作成本。 自上而下的成本限额估算法 自上…...

Redis主从复制基础概念

Redis主从复制&#xff1a;提高数据可用性和性能的策略 一、概述 Redis主从复制是一种常用的高可用性策略&#xff0c;通过将数据从一个Redis服务器复制到另一个或多个Redis服务器上&#xff0c;以提高数据的可用性和读取性能。当主服务器出现故障时&#xff0c;可以快速地切…...

图数据库Neo4j概念、应用场景、安装及CQL的使用

一、图数据库概念 引用Seth Godin的说法&#xff0c;企业需要摒弃仅仅收集数据点的做法&#xff0c;开始着手建立数据之间的关联关系。数据点之间的关系甚至比单个点本身更为重要。 传统的**关系数据库管理系统(RDBMS)**并不擅长处理数据之间的关系&#xff0c;那些表状数据模…...

测试微信模版消息推送

进入“开发接口管理”--“公众平台测试账号”&#xff0c;无需申请公众账号、可在测试账号中体验并测试微信公众平台所有高级接口。 获取access_token: 自定义模版消息&#xff1a; 关注测试号&#xff1a;扫二维码关注测试号。 发送模版消息&#xff1a; import requests da…...

调用支付宝接口响应40004 SYSTEM_ERROR问题排查

在对接支付宝API的时候&#xff0c;遇到了一些问题&#xff0c;记录一下排查过程。 Body:{"datadigital_fincloud_generalsaas_face_certify_initialize_response":{"msg":"Business Failed","code":"40004","sub_msg…...

【JVM】- 内存结构

引言 JVM&#xff1a;Java Virtual Machine 定义&#xff1a;Java虚拟机&#xff0c;Java二进制字节码的运行环境好处&#xff1a; 一次编写&#xff0c;到处运行自动内存管理&#xff0c;垃圾回收的功能数组下标越界检查&#xff08;会抛异常&#xff0c;不会覆盖到其他代码…...

PL0语法,分析器实现!

简介 PL/0 是一种简单的编程语言,通常用于教学编译原理。它的语法结构清晰,功能包括常量定义、变量声明、过程(子程序)定义以及基本的控制结构(如条件语句和循环语句)。 PL/0 语法规范 PL/0 是一种教学用的小型编程语言,由 Niklaus Wirth 设计,用于展示编译原理的核…...

leetcodeSQL解题:3564. 季节性销售分析

leetcodeSQL解题&#xff1a;3564. 季节性销售分析 题目&#xff1a; 表&#xff1a;sales ---------------------- | Column Name | Type | ---------------------- | sale_id | int | | product_id | int | | sale_date | date | | quantity | int | | price | decimal | -…...

大模型多显卡多服务器并行计算方法与实践指南

一、分布式训练概述 大规模语言模型的训练通常需要分布式计算技术,以解决单机资源不足的问题。分布式训练主要分为两种模式: 数据并行:将数据分片到不同设备,每个设备拥有完整的模型副本 模型并行:将模型分割到不同设备,每个设备处理部分模型计算 现代大模型训练通常结合…...

IoT/HCIP实验-3/LiteOS操作系统内核实验(任务、内存、信号量、CMSIS..)

文章目录 概述HelloWorld 工程C/C配置编译器主配置Makefile脚本烧录器主配置运行结果程序调用栈 任务管理实验实验结果osal 系统适配层osal_task_create 其他实验实验源码内存管理实验互斥锁实验信号量实验 CMISIS接口实验还是得JlINKCMSIS 简介LiteOS->CMSIS任务间消息交互…...

Maven 概述、安装、配置、仓库、私服详解

目录 1、Maven 概述 1.1 Maven 的定义 1.2 Maven 解决的问题 1.3 Maven 的核心特性与优势 2、Maven 安装 2.1 下载 Maven 2.2 安装配置 Maven 2.3 测试安装 2.4 修改 Maven 本地仓库的默认路径 3、Maven 配置 3.1 配置本地仓库 3.2 配置 JDK 3.3 IDEA 配置本地 Ma…...

九天毕昇深度学习平台 | 如何安装库?

pip install 库名 -i https://pypi.tuna.tsinghua.edu.cn/simple --user 举个例子&#xff1a; 报错 ModuleNotFoundError: No module named torch 那么我需要安装 torch pip install torch -i https://pypi.tuna.tsinghua.edu.cn/simple --user pip install 库名&#x…...

关于uniapp展示PDF的解决方案

在 UniApp 的 H5 环境中使用 pdf-vue3 组件可以实现完整的 PDF 预览功能。以下是详细实现步骤和注意事项&#xff1a; 一、安装依赖 安装 pdf-vue3 和 PDF.js 核心库&#xff1a; npm install pdf-vue3 pdfjs-dist二、基本使用示例 <template><view class"con…...