解决问题
适用于大文件上传,直接用户对接OSS,减少用户上传服务器、服务器再上传OSS的时间
依赖
<!--阿里OSS-->
<dependency>
<groupId>com.aliyun.oss</groupId>
<artifactId>aliyun-sdk-oss</artifactId>
<version>3.16.3</version>
</dependency>
Java代码
package com.fan.controller;
import com.aliyun.oss.*;
import com.aliyun.oss.common.auth.CredentialsProviderFactory;
import com.aliyun.oss.common.comm.SignVersion;
import com.aliyun.oss.model.GeneratePresignedUrlRequest;
import com.fan.vo.ComResult;
import lombok.Data;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import java.net.URL;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Objects;
/**
* 文件上传
*/
@RestController
public class FileController {
/**
* 预签名请求响应对象
*/
@Data
public static class preSignedUrlVo {
private String fileName;
private String preUrl;
private String url;
}
/**
* 生成预签名
*/
@PostMapping("/manage/file/getPreSignedUrl")
public ComResult<preSignedUrlVo> getPreSignedUrl(@RequestBody preSignedUrlVo params) {
if (Objects.isNull(params.getFileName())) {
return ComResult.failMsg("文件名不能为空");
}
// 获取文件后缀
String suffix = params.getFileName().substring(params.getFileName().lastIndexOf("."));
// 生成文件夹,按月份进行归类
String folderName = new SimpleDateFormat("yyyyMM").format(new Date());
// 生成文件名称
String fileName = new SimpleDateFormat("yyyyMMddHHmmss").format(new Date());
// 随机数字6位数字,多机模式
String random = String.valueOf((int) (Math.random() * 1000000));
// 地区
String endpoint = "https://oss-cn-chengdu.aliyuncs.com";
// Bucket所在地域,只要cn-xxx
String region = "cn-chengdu";
// accessKeyId
String accessKeyId = "xxxxxxxxxxxxxx";
// accessKeySecret
String accessKeySecret = "xxxxxxxxxxxxxxx";
// Bucket
String bucketName = "fanmr";
// 完整路径
String objectName = "wws" + "/" + folderName + "/" + fileName + "_" + random + suffix;
// 创建OSSClient实例
ClientBuilderConfiguration clientBuilderConfiguration = new ClientBuilderConfiguration();
clientBuilderConfiguration.setSignatureVersion(SignVersion.V4);
// 使用直接提供的 AK 构建 CredentialsProvider
OSS ossClient = OSSClientBuilder.create()
.endpoint(endpoint)
.credentialsProvider(CredentialsProviderFactory.newDefaultCredentialProvider(accessKeyId, accessKeySecret))
.clientConfiguration(clientBuilderConfiguration)
.region(region)
.build();
try {
URL signedUrl;
// 指定生成的预签名URL过期时间,单位为毫秒。本示例以设置过期时间为1小时为例。
Date expiration = new Date(new Date().getTime() + 3600 * 1000L);
// 生成预签名URL。
GeneratePresignedUrlRequest request = new GeneratePresignedUrlRequest(bucketName, objectName, HttpMethod.PUT);
request.setContentType("application/octet-stream");
// 设置过期时间。
request.setExpiration(expiration);
// 通过HTTP PUT请求生成预签名URL。
signedUrl = ossClient.generatePresignedUrl(request);
// 打印预签名URL
System.out.println("文件上传预签名地址:" + signedUrl.toString());
// 返回地址
params.setPreUrl(signedUrl.toString());
params.setUrl(endpoint.replace("//", "//" + bucketName + ".") + "/" + objectName);
return ComResult.data(params);
} catch (OSSException e) {
System.out.println(e.getMessage());
}
return ComResult.failMsg("预签名失败");
}
}
Vue配合ElementPlus
代码
FileOneUploadComponent.vue,注意需要axios
<!-- 单文件上传 -->
<!-- 注意修改上传接口、Token -->
<template>
<div class="file-one-upload">
<el-upload
ref="upload"
:headers="{ token: store.token }"
:limit="1"
:http-request="imageUploadBefore"
:on-exceed="handleExceed">
<el-button type="primary" :icon="Upload" :disabled="progressData.show">点击上传</el-button>
</el-upload>
<div class="progress" v-if="progressData.show">
<el-progress :percentage="progressData.progress" />
</div>
</div>
</template>
<script setup>
import { ref, getCurrentInstance, watch, reactive } from 'vue'
// 注意store的位置,这里用的Pinia,注意名称
import { sysStore } from '../store'
import { Upload } from '@element-plus/icons-vue'
// 这里是多环境配置中的接口地址
let api = import.meta.env.env_http_api
// 名称需要与Pinia导出一致
const store = sysStore()
//
import http from '../http'
import axios from 'axios'
//
const props = defineProps({
modelValue: {
type: String,
default: ''
}
})
const mv = ref(props.modelValue)
const { emit } = getCurrentInstance()
watch(
() => props.modelValue,
() => {
mv.value = props.modelValue
}
)
// 文件上传前
let fileSize = ref('')
// 进度条
const progressData = reactive({
progress: 0,
show: false
})
async function imageUploadBefore(option) {
console.log(option)
let file = option.file
// 转换为MB
let size = (file.size / 1024 / 1024).toFixed(2)
fileSize = size + 'MB'
emit('update:fileSize', fileSize)
// 预签名
http
.post('/manage/file/getPreSignedUrl', {
fileName: file.name
})
.then((res) => {
if (res.data.code === 200) {
// 发送PUT请求上传文件,只能用原生axios,不能携带多的参数
progressData.progress = 0
progressData.show = true
axios
.put(res.data.data.preUrl, file, {
headers: {
'Content-Type': 'application/octet-stream'
},
onUploadProgress: function (progressEvent) {
// 对于大文件,可能需要计算百分比来显示上传进度
if (progressEvent.total > 0) {
const percentCompleted = Math.round(
(progressEvent.loaded * 100) / progressEvent.total
)
progressData.progress = percentCompleted
}
}
})
.then(() => {
emit('update:modelValue', res.data.data.url)
})
.catch((error) => {
console.error(error)
})
.finally(() => {
progressData.show = false
})
}
})
}
// 允许覆盖
const upload = ref()
const handleExceed = (files) => {
upload.value.clearFiles()
const file = files[0]
file.uid = genFileId()
upload.value.handleStart(file)
}
</script>
<style scoped lang="scss">
// 进度展示位置调整
.progress {
margin: 8px 0 12px 0;
}
/* 隐藏上传列表,无效提到全局 */
.file-one-upload .el-upload-list {
display: none !important;
}
</style>
使用
import FileOneUploadComponent from './FileOneUploadComponent.vue'
<FileOneUploadComponent
v-model="modifyData.req.file"
v-model:fileSize="modifyData.req.size" />