SpringBoot + Vue 实现云端图片上传与回显(基于OSS等云存储)

前言

在实际生产环境中,我们通常会将图片等静态资源存储在云端对象存储服务(如阿里云OSS、七牛云、腾讯云COS等)上。本文将介绍如何改造之前的本地存储方案,实现基于云端存储的图片上传与回显功能。

一、技术选型

  • 云存储服务:阿里云OSS(其他云服务类似)
  • 后端:SpringBoot 2.x + OSS SDK
  • 前端:Vue 2.x + Element UI

二、阿里云OSS准备

  1. 开通OSS服务
  2. 创建Bucket(存储空间)
  3. 获取AccessKey(访问密钥)

三、后端改造

1. 添加OSS依赖

xml 复制代码
<!-- 阿里云OSS -->
<dependency>
    <groupId>com.aliyun.oss</groupId>
    <artifactId>aliyun-sdk-oss</artifactId>
    <version>3.10.2</version>
</dependency>

2. 配置OSS参数

application.properties中:

properties 复制代码
# 阿里云OSS配置
aliyun.oss.endpoint=your-oss-endpoint
aliyun.oss.accessKeyId=your-access-key-id
aliyun.oss.accessKeySecret=your-access-key-secret
aliyun.oss.bucketName=your-bucket-name
aliyun.oss.urlPrefix=https://your-bucket-name.oss-cn-hangzhou.aliyuncs.com/

3. 创建OSS配置类

java 复制代码
import com.aliyun.oss.OSS;
import com.aliyun.oss.OSSClientBuilder;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class OssConfig {
    
    @Value("${aliyun.oss.endpoint}")
    private String endpoint;
    
    @Value("${aliyun.oss.accessKeyId}")
    private String accessKeyId;
    
    @Value("${aliyun.oss.accessKeySecret}")
    private String accessKeySecret;
    
    @Bean
    public OSS ossClient() {
        return new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);
    }
}

4. 创建OSS上传工具类

java 复制代码
import com.aliyun.oss.OSS;
import com.aliyun.oss.model.PutObjectRequest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;

import java.io.IOException;
import java.util.UUID;

@Component
public class OssUploadUtil {
    
    @Autowired
    private OSS ossClient;
    
    @Value("${aliyun.oss.bucketName}")
    private String bucketName;
    
    @Value("${aliyun.oss.urlPrefix}")
    private String urlPrefix;
    
    public String upload(MultipartFile file) throws IOException {
        // 生成唯一文件名
        String originalFilename = file.getOriginalFilename();
        String fileExt = originalFilename.substring(originalFilename.lastIndexOf("."));
        String newFileName = "images/" + UUID.randomUUID().toString() + fileExt;
        
        // 上传到OSS
        ossClient.putObject(new PutObjectRequest(bucketName, newFileName, file.getInputStream()));
        
        // 返回完整的访问URL
        return urlPrefix + newFileName;
    }
}

5. 修改控制器

java 复制代码
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

@RestController
@RequestMapping("/api/image")
public class ImageController {
    
    @Autowired
    private OssUploadUtil ossUploadUtil;
    
    @PostMapping("/upload")
    public String uploadImage(@RequestParam("file") MultipartFile file) {
        if (file.isEmpty()) {
            return "请选择文件";
        }
        
        try {
            String imageUrl = ossUploadUtil.upload(file);
            return imageUrl; // 直接返回完整的图片URL
        } catch (IOException e) {
            e.printStackTrace();
            return "上传失败";
        }
    }
}

四、前端改造

前端几乎不需要修改,因为OSS上传后返回的是可以直接访问的URL,比本地存储更简单。

1. 修改上传成功处理

javascript 复制代码
handleUploadSuccess(response, file) {
  if (response && response.startsWith('http')) {
    this.imageUrl = response; // 直接使用返回的完整URL
    this.$message.success('上传成功');
  } else {
    this.$message.error('上传失败');
  }
}

2. 移除静态资源代理配置

因为图片URL已经是完整的HTTP地址,不再需要代理:

javascript 复制代码
module.exports = {
  devServer: {
    proxy: {
      '/api': {
        target: 'http://localhost:8080', // 后端地址
        changeOrigin: true,
        pathRewrite: {
          '^/api': ''
        }
      }
      // 移除/images的代理配置
    }
  }
};

五、安全增强

1. 后端签名直传(推荐)

更安全的做法是前端从后端获取签名,然后直接上传到OSS,不经过后端服务器:

后端添加签名接口
java 复制代码
@GetMapping("/oss/policy")
public Map<String, String> getOssPolicy() {
    // 设置上传策略
    long expireTime = 30;
    long expireEndTime = System.currentTimeMillis() + expireTime * 1000;
    Date expiration = new Date(expireEndTime);
    
    // 设置文件路径和大小限制
    String dir = "images/";
    long maxSize = 10 * 1024 * 1024; // 10MB
    
    // 生成策略
    PolicyConditions policyConds = new PolicyConditions();
    policyConds.addConditionItem(PolicyConditions.COND_CONTENT_LENGTH_RANGE, 0, maxSize);
    policyConds.addConditionItem(PolicyConditions.COND_KEY, PolicyConditions.COND_STARTS_WITH, dir);
    
    // 生成签名
    String postPolicy = ossClient.generatePostPolicy(expiration, policyConds);
    byte[] binaryData = postPolicy.getBytes(StandardCharsets.UTF_8);
    String encodedPolicy = BinaryUtil.toBase64String(binaryData);
    String postSignature = ossClient.calculatePostSignature(postPolicy);
    
    // 返回给前端
    Map<String, String> respMap = new HashMap<>();
    respMap.put("accessid", accessKeyId);
    respMap.put("policy", encodedPolicy);
    respMap.put("signature", postSignature);
    respMap.put("dir", dir);
    respMap.put("host", urlPrefix);
    respMap.put("expire", String.valueOf(expireEndTime / 1000));
    
    return respMap;
}
前端改造
vue 复制代码
<template>
  <el-upload
    class="upload-demo"
    :action="ossUploadUrl"
    :data="ossData"
    :before-upload="beforeUpload"
    :on-success="handleUploadSuccess"
  >
    <!-- 上传按钮 -->
  </el-upload>
</template>

<script>
export default {
  data() {
    return {
      ossUploadUrl: '', // OSS上传地址
      ossData: {}      // OSS上传参数
    };
  },
  methods: {
    async getOssPolicy() {
      const res = await this.$axios.get('/api/image/oss/policy');
      this.ossUploadUrl = res.data.host;
      this.ossData = {
        key: res.data.dir + '${filename}', // OSS文件路径
        policy: res.data.policy,
        OSSAccessKeyId: res.data.accessid,
        signature: res.data.signature,
        success_action_status: '200' // 成功返回状态码
      };
    },
    beforeUpload(file) {
      // 每次上传前获取新的签名
      return this.getOssPolicy().then(() => {
        const isImage = /\.(jpg|jpeg|png|gif)$/i.test(file.name);
        const isLt10M = file.size / 1024 / 1024 < 10;
        
        if (!isImage) {
          this.$message.error('只能上传图片文件!');
        }
        if (!isLt10M) {
          this.$message.error('图片大小不能超过10MB!');
        }
        return isImage && isLt10M;
      });
    },
    handleUploadSuccess(res, file) {
      // 拼接完整URL
      this.imageUrl = this.ossUploadUrl + '/' + file.name;
      this.$message.success('上传成功');
    }
  }
};
</script>

六、总结

云端存储相比本地存储有以下优势:

  1. 高可用性:云服务提供99.9%以上的可用性
  2. 高扩展性:存储空间可无限扩展
  3. 高性能:CDN加速全球访问
  4. 低成本:按量付费,无需自建存储服务器
  5. 安全性:提供多种安全防护机制

本文详细介绍了基于阿里云OSS的图片上传方案,其他云存储服务实现方式类似。签名直传方案既能保证安全性,又能减轻服务器压力,是生产环境推荐的做法。

最重要的啊,也是本人和朋友写代码发现的

就是controller类里面的那个@RequestParam("file")

这个file ,之前我们用的是image 因为这个一个词,改了不下10词代码,最后也是突然醒悟。

下面是正确的方法,可以按照之前的步骤来,大概是不会错了。

java 复制代码
 @PostMapping("/upload")
    public String uploadImage(@RequestParam("file") MultipartFile file) {
        if (file.isEmpty()) {
            return "请选择文件";
        }
        
        try {
            String imageUrl = ossUploadUtil.upload(file);
            return imageUrl; // 直接返回完整的图片URL
        } catch (IOException e) {
            e.printStackTrace();
            return "上传失败";
        }
    }
}

希望这篇文章对大家有帮助!

相关推荐
一只鹿鹿鹿37 分钟前
【测试文档】项目测试文档,测试管理规程,测试计划,测试文档模版,软件测试报告书(Word)
数据库·后端·spring·单元测试
jstart千语1 小时前
【SpringBoot】HttpServletRequest获取使用及失效问题(包含@Async异步执行方案)
java·前端·spring boot·后端·spring
冯诺一没有曼1 小时前
Java记账系统项目实战 | Spring Boot + MyBatis Plus + Layui 前后端整合开发
java·spring boot·mybatis
Asthenia04121 小时前
Java线程池任务完成检测的多种方法及面试深度剖析
后端
bing_1581 小时前
在 Spring Boot 项目中怎么识别和优化慢 SQL ?
spring boot·优化慢sql·识别慢sql
Goboy1 小时前
SQL面试实战,30分钟征服美女面试官
后端·面试·架构
RainbowSea2 小时前
通用型产品发布解决方案(基于分布式微服务技术栈:SpringBoot+SpringCloud+Spring CloudAlibaba+Vue+ElementUI
java·spring boot·后端
苹果酱05672 小时前
Vue3 源码解析(六):响应式原理与 reactive
java·vue.js·spring boot·mysql·课程设计