前言
在实际生产环境中,我们通常会将图片等静态资源存储在云端对象存储服务(如阿里云OSS、七牛云、腾讯云COS等)上。本文将介绍如何改造之前的本地存储方案,实现基于云端存储的图片上传与回显功能。
一、技术选型
- 云存储服务:阿里云OSS(其他云服务类似)
- 后端:SpringBoot 2.x + OSS SDK
- 前端:Vue 2.x + Element UI
二、阿里云OSS准备
- 开通OSS服务
- 创建Bucket(存储空间)
- 获取AccessKey(访问密钥)
三、后端改造
1. 添加OSS依赖
<!-- 阿里云OSS -->
<dependency>
<groupId>com.aliyun.oss</groupId>
<artifactId>aliyun-sdk-oss</artifactId>
<version>3.10.2</version>
</dependency>
2. 配置OSS参数
在application.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配置类
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上传工具类
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. 修改控制器
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. 修改上传成功处理
handleUploadSuccess(response, file) {
if (response && response.startsWith('http')) {
this.imageUrl = response; // 直接使用返回的完整URL
this.$message.success('上传成功');
} else {
this.$message.error('上传失败');
}
}
2. 移除静态资源代理配置
因为图片URL已经是完整的HTTP地址,不再需要代理:
module.exports = {
devServer: {
proxy: {
'/api': {
target: 'http://localhost:8080', // 后端地址
changeOrigin: true,
pathRewrite: {
'^/api': ''
}
}
// 移除/images的代理配置
}
}
};
五、安全增强
1. 后端签名直传(推荐)
更安全的做法是前端从后端获取签名,然后直接上传到OSS,不经过后端服务器:
后端添加签名接口
@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;
}
前端改造
<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>
六、总结
云端存储相比本地存储有以下优势:
- 高可用性:云服务提供99.9%以上的可用性
- 高扩展性:存储空间可无限扩展
- 高性能:CDN加速全球访问
- 低成本:按量付费,无需自建存储服务器
- 安全性:提供多种安全防护机制
本文详细介绍了基于阿里云OSS的图片上传方案,其他云存储服务实现方式类似。签名直传方案既能保证安全性,又能减轻服务器压力,是生产环境推荐的做法。
最重要的啊,也是本人和朋友写代码发现的
就是controller
类里面的那个@RequestParam("file")
这个file
,之前我们用的是image
因为这个一个词,改了不下10词代码,最后也是突然醒悟。
下面是正确的方法,可以按照之前的步骤来,大概是不会错了。
@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 "上传失败";
}
}
}
希望这篇文章对大家有帮助!