一、上传文件基本实现
- 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";} }