一、OSS介绍
在Web项目中,一些常见的功能,比如展示图片,修改头像等,都需要进行图片的上传操作,但是如果是存储在Web服务器中,在读取图片的时候会占用比较多的资源,影响服务器的性能。
常见的方式是使用OSS(Object Storage Service)存储图片或视频。
用户会先将图片上传至服务器,服务器这里担任的是中转的角色,前端界面发送请求到后端,后端需要保存数据(例如,图片的访问链接),返回给前端,后续需要浏览图片的时候,前端通过访问后端所返回的响应体中的链接到OSS中进行访问。
这种方式可以有效的节省服务器所需的资源,减轻带宽压力。
这里我们是使用七牛云OSS进行服务器直传(数据流的方式)的展示(有一定的免费存储空间),
详情可以参考操作文档:Java SDK_SDK 下载_对象存储 - 七牛开发者中心 (qiniu.com)
二、演示案例
2.1 引入Maven依赖
<dependency><groupId>com.qiniu</groupId><artifactId>qiniu-java-sdk</artifactId><version>[7.13.0, 7.13.99]</version>
</dependency>
2.2 默认提供的代码
//构造一个带指定 Region 对象的配置类Configuration cfg = new Configuration(Region.region0());cfg.resumableUploadAPIVersion = Configuration.ResumableUploadAPIVersion.V2;// 指定分片上传版本//...其他参数参考类注释UploadManager uploadManager = new UploadManager(cfg);//...生成上传凭证,然后准备上传String accessKey = "your access key";String secretKey = "your secret key";String bucket = "your bucket name";//默认不指定key的情况下,以文件内容的hash值作为文件名String key = null;try {byte[] uploadBytes = "hello qiniu cloud".getBytes("utf-8");ByteArrayInputStream byteInputStream=new ByteArrayInputStream(uploadBytes);Auth auth = Auth.create(accessKey, secretKey);String upToken = auth.uploadToken(bucket);try {Response response = uploadManager.put(byteInputStream,key,upToken,null, null);//解析上传成功的结果DefaultPutRet putRet = new Gson().fromJson(response.bodyString(), DefaultPutRet.class);System.out.println(putRet.key);System.out.println(putRet.hash);} catch (QiniuException ex) {ex.printStackTrace();if (ex.response != null) {System.err.println(ex.response);try {String body = ex.response.toString();System.err.println(body);} catch (Exception ignored) {}}}} catch (UnsupportedEncodingException ex) {//ignore}
这里我们可以先对代码进行简单的修改,使其实现我们的功能,之后我们再一步步的优化,
2.2.1 修改Region配置类
通过观察,我们需要修改Region对象的配置类:
文档:
这里我们设置为autoRegion,它会自动帮我们绑定我们在七牛云上创建的OSS仓库的地址。
2.2.2 AK,SK,bucket name的查看和修改
显而易见,还需要修改密钥,即AK,SK,和 bucket name。
AK,SK,在密钥管理处查看:
密钥管理界面如下:
修改的话,这边是利用 @ConfigurationProperties从配置文件中获取,需要注意的是,使用改注解的时候,还需要给这些属性,配置对应的set方法才行:
当然,你也可以使用@Value从配置文件中获取属性。
yml配置文件如下:
2.2.3 修改存储到OSS的文件名,存储的文件
到此,其实就可以实现一个简单的文件上传了:
2.2.4 代码实现
package com.fox;import com.google.gson.Gson;
import com.qiniu.common.QiniuException;
import com.qiniu.http.Response;
import com.qiniu.storage.Configuration;
import com.qiniu.storage.Region;
import com.qiniu.storage.UploadManager;
import com.qiniu.storage.model.DefaultPutRet;
import com.qiniu.util.Auth;
import org.junit.jupiter.api.Test;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.stereotype.Component;import java.io.ByteArrayInputStream;
import java.io.FileInputStream;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;@SpringBootTest
@ConfigurationProperties(prefix = "oss")
public class OSSTest {private String accessKey;private String secretKey;private String bucket;public void setAccessKey(String accessKey) {this.accessKey = accessKey;}public void setSecretKey(String secretKey) {this.secretKey = secretKey;}public void setBucket(String bucket) {this.bucket = bucket;}@Testpublic void testOss() {//构造一个带指定 Region 对象的配置类Configuration cfg = new Configuration(Region.autoRegion());cfg.resumableUploadAPIVersion = Configuration.ResumableUploadAPIVersion.V2;// 指定分片上传版本//...其他参数参考类注释UploadManager uploadManager = new UploadManager(cfg);//...生成上传凭证,然后准备上传//默认不指定key的情况下,以文件内容的hash值作为文件名String key = "2022/fox.jpg";try {
// byte[] uploadBytes = "hello qiniu cloud".getBytes("utf-8");
// ByteArrayInputStream byteInputStream=new ByteArrayInputStream(uploadBytes);InputStream inputStream = new FileInputStream("C:\\Users\\86136\\Desktop\\Snipaste_2024-02-03_17-30-29.png");Auth auth = Auth.create(accessKey, secretKey);String upToken = auth.uploadToken(bucket);try {Response response = uploadManager.put(inputStream,key,upToken,null, null);//解析上传成功的结果DefaultPutRet putRet = new Gson().fromJson(response.bodyString(), DefaultPutRet.class);System.out.println(putRet.key);System.out.println(putRet.hash);} catch (QiniuException ex) {ex.printStackTrace();if (ex.response != null) {System.err.println(ex.response);try {String body = ex.response.toString();System.err.println(body);} catch (Exception ignored) {}}}} catch (Exception ex) {//ignore}}
}
三、实际项目中的OSS案例
其实跟上述过程差不多,只是将一些写死的数据,演变为可变的,并且返回响应给前端工程即可:
3.1 改进1:利用Lombok注解代替set方法
3.2 改进2:利用工具类动态生成文件名称
接受图片方法如下,该方法会返回给前端访问该图片的链接,ResponseResult为我自己定义的统一格式响应体。
@Overridepublic ResponseResult uploadImg(MultipartFile img) {//获取原始文件名String originalFilename = img.getOriginalFilename();//对原始文件名进行判断if (!originalFilename.endsWith(".png") && !originalFilename.endsWith(".jpg")) {throw new SystemException(AppHttpCodeEnum.FILE_TYPE_ERROR);}//如果判断通过那么就上传文件到OSSString filePath = PathUtils.generateFilePath(originalFilename);String url = uploadOss(img,filePath);return ResponseResult.okResult(url);}
PathUtils工具类如下,用于动态生成文件名:
package com.fox.utils;import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.UUID;public class PathUtils {public static String generateFilePath(String fileName){//根据日期生成路径 2022/1/15/SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd/");String datePath = sdf.format(new Date());//uuid作为文件名String uuid = UUID.randomUUID().toString().replaceAll("-", "");//后缀和文件后缀一致int index = fileName.lastIndexOf(".");// test.jpg -> .jpgString fileType = fileName.substring(index);return new StringBuilder().append(datePath).append(uuid).append(fileType).toString();}
}
以下为uploadOss方法,key即为文件名,我们需要返回给前端的url,就是外链访问路径加上文件名即可:
bucket绑定的域名(外链访问域名获取)如下:
3.3 总结:
代码实现:
package com.fox.service.impl;import com.fox.domain.ResponseResult;
import com.fox.enums.AppHttpCodeEnum;
import com.fox.exception.SystemException;
import com.fox.service.UploadService;
import com.fox.utils.PathUtils;
import com.google.gson.Gson;
import com.qiniu.common.QiniuException;
import com.qiniu.http.Response;
import com.qiniu.storage.Configuration;
import com.qiniu.storage.Region;
import com.qiniu.storage.UploadManager;
import com.qiniu.storage.model.DefaultPutRet;
import com.qiniu.util.Auth;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;import java.io.InputStream;@Service
@Data
@ConfigurationProperties(prefix = "oss")
public class OssUploadServiceImpl implements UploadService {private String accessKey;private String secretKey;private String bucket;@Overridepublic ResponseResult uploadImg(MultipartFile img) {//获取原始文件名String originalFilename = img.getOriginalFilename();//对原始文件名进行判断if (!originalFilename.endsWith(".png") && !originalFilename.endsWith(".jpg")) {throw new SystemException(AppHttpCodeEnum.FILE_TYPE_ERROR);}//如果判断通过那么就上传文件到OSSString filePath = PathUtils.generateFilePath(originalFilename);String url = uploadOss(img,filePath);return ResponseResult.okResult(url);}private String uploadOss(MultipartFile imgFile, String filePath) {//构造一个带指定 Region 对象的配置类Configuration cfg = new Configuration(Region.autoRegion());cfg.resumableUploadAPIVersion = Configuration.ResumableUploadAPIVersion.V2;// 指定分片上传版本//...其他参数参考类注释UploadManager uploadManager = new UploadManager(cfg);//...生成上传凭证,然后准备上传//默认不指定key的情况下,以文件内容的hash值作为文件名String key = filePath;try {
// byte[] uploadBytes = "hello qiniu cloud".getBytes("utf-8");
// ByteArrayInputStream byteInputStream=new ByteArrayInputStream(uploadBytes);InputStream inputStream = imgFile.getInputStream();Auth auth = Auth.create(accessKey, secretKey);String upToken = auth.uploadToken(bucket);try {Response response = uploadManager.put(inputStream,key,upToken,null, null);//解析上传成功的结果DefaultPutRet putRet = new Gson().fromJson(response.bodyString(), DefaultPutRet.class);System.out.println(putRet.key);System.out.println(putRet.hash);return "图片外链除去文件名的url地址"+key;} catch (QiniuException ex) {ex.printStackTrace();if (ex.response != null) {System.err.println(ex.response);try {String body = ex.response.toString();System.err.println(body);} catch (Exception ignored) {}}}} catch (Exception ex) {//ignore}return "2";}}