简介
阿里云对象存储OSS(Object Storage Service)是一款海量、安全、低成本、高可靠的云存储服务,可提供99.9999999999%(12个9)的数据持久性,99.995%的数据可用性。多种存储类型供选择,全面优化存储成本。
参考资料
阿里 OSS
简介
- 阿里云对象存储OSS(Object Storage Service)为您提供基于网络的数据存取服务。使用OSS,您可以通过网络随时存储和调用包括文本、图片、音视频在内的各类数据文件
- 创建Bucket,并将文件上传至Bucket。上传完成后,将文件(Object)下载至本地或者通过生成签名URL的方式将文件分享给第三方,供其下载或预览
入口
- 登录阿里云控制台 【产品】-【存储】-【对象存储 OSS】
开通 OSS
价格
- 当前截图时价格,我主要是做一些头像照片的存储,可以选低频的,一个月估计不会到 1 个 G,8 分钱就能解决
前置准备
创建 Bucket
- 地域:这里建议选择和自己服务器节点近的地域
- 存储类型:选择低频访问(便宜点,这个根据实际情况,如果访问量比较大的话还是选标准)
- 同城冗余存储:不开通要收钱,OSS 将您的数据以冗余的方式存储在同一区域(Region)的 3 个可用区(Zone)中。提供机房级容灾能力,能提高您的数据可用性
- 版本控制:选择不开通,这个是针对需要回滚的才选择开通,需要额外收费(我们这里涉及不到)
- 读写权限尽量选择公共读写
上传体验
- 在刚才的Bucket 下新建一个目录用于存放图片(为资源归类)
权限控制
- Access Key:阿里系下有非常多的产品,所有产品的控制权限通过主账户
Access Key
进行控制,该账户可以访问所有的产品
- RAM:
RAM
可以有多个,可单独为RAM
配置拥有的产品控制权限,基于安全考虑我们用 RAM
创建用户组
- 通过 RAM 入口进入,创建用户组,然后给用户组分配权限,最后再创建用户给用户分配用户组
用户组分配权限
- 给刚才创建的用户组指定可以对阿里云的那些产品进行权限控制,这里就用我们的 OSS 相关内容
创建用户
- 刚才已经准备好了用户组,现在需要准备用一个用户,然后把用户的权限指定为刚才的用户组,这样该用户就拥有了 OSS 相关的权限了
- 创建后记得下载 CSV 文件保存好账户信息
分配用户组
- 这一步的目的是将用户绑定到之前的用户组里面,这样用户就能使用用户组的权限了(也可直接单独给用户分配权限,这里不做记录了,我个人还是推荐以用户组进行维护)
基础配置
导入依赖
- 导入的版本根据官方给的文档进行导入,最好不要私自修改任意版本号 我尝试的把 sdk 升级到最新的后面 做 Bucket 空间判断的时候有返回错误(可能是版本于其它地方有不匹配的)
- Gradle导入:
implementation("com.aliyun.oss:aliyun-sdk-oss:3.10.2")
- 如果使用的是Java 9及以上的版本,则需要添加jaxb相关依赖
1 2 3
| implementation("javax.xml.bind:jaxb-api:2.3.1") implementation("javax.activation:activation:1.1.1") implementation("org.glassfish.jaxb:jaxb-runtime:2.3.3")
|
配置 yml
1 2 3 4 5 6 7 8
|
alioss: endpoint: yourEndpoint accessKeyId: yourAccessKeyId accessKeySecret: yourAccessKeySecret bucketName: yourBucketName url: https://${alioss.bucketName}.${alioss.endpoint}/
|
创建实体
- 将配置文件中的内容读取到实体中
- ConfigurationProperties 用于读取 yml 配置文件 prefix = “alioss” 用于指定前置
- 实体中的属性名需要和 yml 中保持一致
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| package com.adalucky.utils.alioss.entity;
import lombok.Data; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.stereotype.Component;
@ConfigurationProperties(prefix = "alioss") @Component @Data public class OssEntity { private String endpoint; private String accessKeyId; private String accessKeySecret; private String bucketName; private String url; }
|
验证
- 创建一个单元测试类,验证一下实体对象有没有把 yml 中的值赋值到属性上
- 有可能单元测试读取不到 yml 配置文件,我是直接再单元测试中新建了一个 application.yml 和 main 下面的内容一致
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| package com.adalucky.utils.alioss;
import com.adalucky.utils.alioss.entity.OssEntity; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest class OssEntityTest { @Autowired OssEntity ossEntity;
@Test void ossEntity() { System.out.println(ossEntity); } }
|
AIP 控制
- 参考阿里官方的 OSS 开发手册,里面有非常详细的示例和说明
- 接口中定义方法,实现类具体实现
- 实现类需要加上 @Service 注解
- 实现类也实现了 InitializingBean,实现 afterPropertiesSet 方法,创建这个 Bean 后对参数进行赋值方便后面统一使用
创建 Bucket
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73
| package com.adalucky.utils.alioss.service.impl;
import com.adalucky.handler.BusinessException; import com.adalucky.response.Result; import com.adalucky.response.ResultCode; import com.adalucky.utils.alioss.entity.OssEntity; import com.adalucky.utils.alioss.service.OssService; import com.adalucky.utils.logger.Slf4j; import com.aliyun.oss.OSS; import com.aliyun.oss.OSSClientBuilder; import com.aliyun.oss.model.CreateBucketRequest; import com.aliyun.oss.model.StorageClass; import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.web.multipart.MultipartFile;
@Service("OssServiceImpl") public class OssServiceImpl implements OssService, InitializingBean { @Autowired OssEntity ossEntity; private String endpoint; private String accessKeyId; private String accessKeySecret; private String bucketName; private String url;
@Override public void afterPropertiesSet() throws Exception { endpoint = ossEntity.getEndpoint(); accessKeyId = ossEntity.getAccessKeyId(); accessKeySecret = ossEntity.getAccessKeySecret(); bucketName = ossEntity.getBucketName(); url = ossEntity.getUrl(); }
@Override public Result createBucket() { OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret); if (ossClient.doesBucketExist(bucketName)) { Slf4j.log().error("Bucket【" + bucketName + "】已存在,无法重复创建"); throw new BusinessException( ResultCode.BUCKET_ALREADY_EXIST.getCode(), ResultCode.BUCKET_ALREADY_EXIST.getMessage() ); } else { CreateBucketRequest createBucketRequest = new CreateBucketRequest(bucketName); createBucketRequest.setStorageClass(StorageClass.IA); ossClient.createBucket(createBucketRequest); ossClient.shutdown(); return Result.ok().data("bucketName", bucketName).data("StorageClass",StorageClass.IA); } } }
|
上传文件
配置
- 上传文件的时候有大小限制需要调整一下,不然SpringBoot默认的是单个文件 1MB,整体是 10MB
1 2 3 4 5 6
| spring: servlet: multipart: max-file-size: 50MB max-request-size: 100MB
|
实现类
- OssService 接口中先定义方法
- 实现类 url 用于返回给前端
- url 是在 yml 中变量组合起来的
url: https://${alioss.bucketName}.${alioss.endpoint}/
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81
| package com.adalucky.utils.alioss.service.impl;
import com.adalucky.handler.BusinessException; import com.adalucky.response.Result; import com.adalucky.response.ResultCode; import com.adalucky.utils.alioss.entity.OssEntity; import com.adalucky.utils.alioss.service.OssService; import com.adalucky.utils.logger.Slf4j; import com.aliyun.oss.OSS; import com.aliyun.oss.OSSClientBuilder; import com.aliyun.oss.model.CreateBucketRequest; import com.aliyun.oss.model.StorageClass; import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
@Service("OssServiceImpl") public class OssServiceImpl implements OssService, InitializingBean { @Autowired OssEntity ossEntity; private String endpoint; private String accessKeyId; private String accessKeySecret; private String bucketName; private String url;
@Override public void afterPropertiesSet() throws Exception { endpoint = ossEntity.getEndpoint(); accessKeyId = ossEntity.getAccessKeyId(); accessKeySecret = ossEntity.getAccessKeySecret(); bucketName = ossEntity.getBucketName(); url = ossEntity.getUrl(); }
@Override public Result upload(MultipartFile file) { String objectName = setObjectName(file); OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret); try { ossClient.putObject(bucketName, objectName, file.getInputStream()); } catch (IOException e) { Slf4j.log().error("上传的文件流获取异常"); e.printStackTrace(); } finally { if (ossClient != null) { ossClient.shutdown(); } } return Result.ok().message("上传成功").data("url", url+objectName); } String setObjectName(MultipartFile file) { String[] split = file.getContentType().split("/"); String directory = split[0]; return directory + "/" + file.getOriginalFilename(); } }
|
Controller
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| package com.adalucky.utils.alioss.controller;
import com.adalucky.response.Result; import com.adalucky.utils.alioss.service.OssService; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.multipart.MultipartFile;
@RestController @RequestMapping("/oss") @Api(tags = "OSS") public class OssController { @Autowired OssService ossService;
@ApiOperation(value = "上传文件", notes = "上传文件到阿里云 OSS 上") @PostMapping("/upload") Result upload(MultipartFile file) { return ossService.upload(file); } }
|
图片无法预览
- 针对上面上传的图片无法进行预览,每次访问连接都是下载图片,主要有两种方式进行解决:①绑定一个自己的域名 ②上传的时候指定文件的类型 Content-Type
- 这里为了方便直接绑定了自己的域名(如果没有域名的只能用第二种方式了)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
| ObjectMetadata objectMetadata = new ObjectMetadata(); objectMetadata.setObjectAcl(CannedAccessControlList.PublicRead); objectMetadata.setContentType(getcontentType(fileType)); ossClient.putObject(bucketName, fileName, inputStream, objectMetadata);
public static String getcontentType(String FilenameExtension) { if (FilenameExtension.equalsIgnoreCase(".bmp")) { return "image/bmp"; } if (FilenameExtension.equalsIgnoreCase(".gif")) { return "image/gif"; } if (FilenameExtension.equalsIgnoreCase(".jpeg") || FilenameExtension.equalsIgnoreCase(".jpg") || FilenameExtension.equalsIgnoreCase(".png")) { return "image/jpg"; } if (FilenameExtension.equalsIgnoreCase(".html")) { return "text/html"; } if (FilenameExtension.equalsIgnoreCase(".txt")) { return "text/plain"; } if (FilenameExtension.equalsIgnoreCase(".vsd")) { return "application/vnd.visio"; } if (FilenameExtension.equalsIgnoreCase(".pptx") || FilenameExtension.equalsIgnoreCase(".ppt")) { return "application/vnd.ms-powerpoint"; } if (FilenameExtension.equalsIgnoreCase(".docx") || FilenameExtension.equalsIgnoreCase(".doc")) { return "application/msword"; } if (FilenameExtension.equalsIgnoreCase(".xml")) { return "text/xml"; } return "image/jpg"; }
|
绑定域名
- Bucket 开启自定义域名(只需要对其中一个文件开启后全局生效)
- 开启自定义域名后可以看到就会多了类型 image 这样就可以通过 url 进行预览了,而不是下载
SSL 证书
- 阿里云有免费的证书一年 20 个
- 先免费购买 -> 创建 -> 申请 -> 域名解析 -> 证书托管
下载文件
- 我自己其实并没事去实现,因为当前我的项目还没涉及到 OSS 对象需要下载,先做个示例记录下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
|
@Override public void download(String fileName) throws IOException { String objectName = fileName;
OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);
OSSObject ossObject = ossClient.getObject(bucketName, objectName); InputStream content = ossObject.getObjectContent(); if (content != null) { BufferedReader reader = new BufferedReader(new InputStreamReader(content)); while (true) { String line = reader.readLine(); if (line == null) { break; } System.out.println("\n" + line); } content.close(); } ossClient.shutdown(); }
|
列举文件
- 我自己其实并没事去实现,因为当前我的项目还没涉及到 OSS 对象需要列举展示,先做个示例记录下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
|
@Override public void listFile() { OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);
ObjectListing objectListing = ossClient.listObjects(bucketName); for (OSSObjectSummary objectSummary : objectListing.getObjectSummaries()) { System.out.println(" - " + objectSummary.getKey() + " " + "(size = " + objectSummary.getSize() + ")"); }
ossClient.shutdown(); }
|
删除文件
1 2 3 4 5
| @ApiOperation(value = "删除文件", notes = "根据文件名删除 Bucket 内的文件") @DeleteMapping("/delete") Result delete(@RequestParam String fileName) { return ossService.deleteFile(fileName); }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
|
@Override public Result deleteFile(String fileName) { if (fileName.isBlank()) { throw new BusinessException( ResultCode.PARAM_IS_BLANK.getCode(), ResultCode.PARAM_IS_BLANK.getMessage() ); } else { OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret); try { ossClient.deleteObject(bucketName, fileName); } catch (OSSException e) { Slf4j.log().error("确认文件名是否存在"); e.printStackTrace(); } finally { ossClient.shutdown(); } return Result.ok().message("删除文件成功").data("fileName", fileName); } }
|