logo 范 · 拾光录
网址收集 关于作者 Github Gitee
杂文随笔4
Hexo博客:基础使用Hexo博客:Next主题Hexo博客:Next进阶使用Hexo博客:Next高级配置
前端编程3
前端基础知识16
UniApp15
Vue框架19
Java编程7
Java基础知识14
SpringBoot31
SpringMVC14
MyBatis8
SpringCloud15
RocketMQ1
MyCat1
数据库4
MySQL6
Redis7
MongoDB10
H21
Java技术栈4
知识整理分布式锁的实现方案MySQL事务MySQL的锁
软件工具箱14
IDEAGitMavenGradleNginx安装Nginx配置JMeter压测OllamaPicGoRustFSVSCodeDockerObsidianObs录制
Linux知识11
树莓派安装及使用ArchLinux:常用软件ArchLinux:基础系统安装ArchLinux:深度优化ArchLInux:图形化界面安装ArchLinux:Nirifrp内网穿透Jar启动脚本Linux常用命令VirtualBox安装CentOSVirtualBox安装Ubuntu
Python编程6
Python基础知识Python语法yolo目标检测OpenCV的使用及树莓派平台condauv
创意设计2
Blender:入门知识UI设计基础知识
AI相关9
Claude CodeHermes AgentOpenAI基本使用OpenAI工具调用OpenAI记忆管理OpenAI推理执行OpenAI开发框架Langchainllama.cpp

解决问题

适用于大文件上传,直接用户对接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" />
解决问题
依赖
Java代码
Vue配合ElementPlus
代码
使用