大家好,我是二哥呀。技术派的教程中一直缺一篇,图片如何上传至阿里云的 OSS 服务器,虽然是一篇很基础的内容,但总这么缺着总有球友找我要,所以还是写一篇吧。
但我会结合技术派 admin 端的业务来写,比如说:
- 图片如何复制粘贴即可完成上传?
- 图片如何自动转链(外链转为内链,否则无法访问)?
- 图片如何防止 30s 内重复上传?
- 服务端如何利用 Guava Cache 提高图片上传的效率?
- 配置文件更新时自动初始化阿里云 Client?
- 如何通过开关自由切换本地图片服务还是阿里云 OSS 服务?
什么是 OSS?
OSS 也就是 Object Storage Service,是阿里云提供的一套对象存储服务,国内的竞品还有七牛云的 Kodo 和腾讯云的 COS。
由于技术派最新的服务器是腾讯云的香港服务器,为了提升服务器到 OSS 之间的传输效率,我本来是打算使用腾讯云的 COS,但开通后发现用起来很麻烦,不如阿里云的 OSS 来得方便,所以也就没有迁移。
之所以要迁移,是因为阿里云上有这么一个规定:
OSS 的 Bucket 在华东 1 (杭州),客户端(比如说技术派的服务器)所在地域为中国香港,这类场景客户端会受到跨墙连路,速度就会比较慢。
通过后台 StopWatch 日志(后面专门出篇教程来讲)打印观察到,技术派的图片上传瓶颈就是因为这个原因,很扯淡(😂)。
但不能不用!
OSS 存储比服务器端存储还是要方便很多,并且容易管理。即便是服务器迁移了,OSS 依然还能用,技术派的服务器就做过这么一个迁移。
阿里云丐版服务器(三年 200 多人民币屯的)→ 亚马逊服务器(太贵,一个月近 1000 人民币)→ 腾讯云香港地区(三年 3300 多人民币)
开通 OSS
OSS 本身还算是比较便宜的,一个月可能也就几块钱吧,我这边 100GB 中国大陆 标准版 一个月 续费价格是 11 块,不过我一般都是直接续费一年。
OK,开通 OSS 资源包后,直接进入 OSS 的管理控制台,点击「Bucket 列表」,点击「创建 Bucket」。
Bucket 的词面意思是桶,这里指存储空间,就是用于存储对象的容器。注意读写权限为“公共读”(如果配置 CDN 的话,可以设置为私有),也就是允许互联网用户访问云空间上的图片。
点击「确定」就算是开通成功了。
开通之后,记得从 RAM(Resource Access Management) 访问控制这里拿到 accesskey ID 和 accesskey secret,这两个是访问阿里云 API 钥匙,有这两个就可以访问阿里云账户的所有权限,所以要妥善保管,千万不要泄露。
我之前做编程喵的时候,为了方便球友们使用 OSS 就直接给了个别球友,结果被泄露了出去(惨,大家都没有这个安全意识),但好就好在,RAM 用户创建后可以销毁重新创建一个新的,旧的就不起效了。
注意拿到这两个关键配置后,还需要再拿到另外两个配置:Endpoint(地域节点) 和 Bucket(桶名)。
那到这,OSS 的前期准备就完成了。
新增 OSS 配置文件
技术派的图片配置文件是在 application-image.yml 文件中,这样可以和其他配置信息很好的隔离开。
这里只解释 OSS 的:
- image.oss.type:ali 为阿里云 OSS,local 为本地存储,rest 为阿里云 OSS 的中间转存服务,解决前面我们提到的 OSS 跨域限制问题
- image.oss.prefix:上传文件的前缀路径
- image.oss.endpoint:前面提到的地域节点
- image.oss.ak:前面提到的 accesskey ID
- image.oss.sk:前面提到的 accesskey secret
- image.oss.bucket:前面提到的桶名
- image.oss.host:后面要用到的 CDN 域名
本地图片我们之前讲过,在这里:
对应的 JavaBean 也不能少,用了 lombok 的 Data 注解。
@Data
public class OssProperties {
/**
* 上传文件前缀路径
*/
private String prefix;
/**
* oss类型
*/
private String type;
/**
* 下面几个是oss的配置参数
*/
private String endpoint;
private String ak;
private String sk;
private String bucket;
private String host;
}
上一层的图片配置文件类 ImageProperties。
@Setter
@Getter
@Component
@ConfigurationProperties(prefix = "image")
public class ImageProperties {
private OssProperties oss;
public String buildImgUrl(String url) {
if (!url.startsWith(cdnHost)) {
return cdnHost + url;
}
return url;
}
}
Spring Boot 的 @ConfigurationProperties 注解使得类能够方便地将配置文件(如 application.yml)中的属性绑定到类的字段上。
Spring 的注解 @Component 用于将此类标记为 Spring 容器的组件,这意味着 Spring 将在启动时自动创建 ImageProperties 的实例,并将其纳入 Spring 容器进行管理。意味着我们在其他地方可以直接这样使用。
@Autowired
private ImageProperties properties;
@ConfigurationProperties(prefix = "image")
用于将配置文件中前缀为 image 的属性绑定到该类的字段上,也就是前面提到的那个 application-image.yml 文件中的属性。
在 pom.xml 文件中添加 OSS 依赖包
先是 version 版本:
<aliyun-sdk-oss.version>3.17.2</aliyun-sdk-oss.version>
可通过这个链接查看最新版本。
https://mvnrepository.com/artifact/com.aliyun.oss/aliyun-sdk-oss
usages 最多的一般都比较稳定。
然后是依赖项:
<dependency>
<groupId>com.aliyun.oss</groupId>
<artifactId>aliyun-sdk-oss</artifactId>
<version>${aliyun-sdk-oss.version}</version>
</dependency>
注意 pom.xml 文件的位置。
新增图片上传接口
更了方便切换图片上传的的服务,比如说是本地还是 OSS,我们新增一个 ImageUploader 接口,提供两个关键的方法,一个是 upload (图片上传用),一个是 uploadIgnore(图片转链用)。
public interface ImageUploader {
String DEFAULT_FILE_TYPE = "txt";
Set<MediaType> STATIC_IMG_TYPE = new HashSet<>(Arrays.asList(MediaType.ImagePng, MediaType.ImageJpg, MediaType.ImageWebp, MediaType.ImageGif));
/**
* 文件上传
*
* @param input
* @param fileType
* @return
*/
String upload(InputStream input, String fileType);
/**
* 判断外网图片是否依然需要处理
*
* @param fileUrl
* @return true 表示忽略,不需要转存
*/
boolean uploadIgnore(String fileUrl);
/**
* 获取文件类型
*
* @param input
* @param fileType
* @return
*/
default String getFileType(ByteArrayInputStream input, String fileType) {
if (StringUtils.isNotBlank(fileType)) {
return fileType;
}
MediaType type = MediaType.typeOfMagicNum(FileReadUtil.getMagicNum(input));
if (STATIC_IMG_TYPE.contains(type)) {
return type.getExt();
}
return DEFAULT_FILE_TYPE;
}
}
默认的 getFileType 方法相信大家一看就知道是干啥用的,获取文件的后缀名,主要用来限定我们的图片只能是 png、jpg、webp、gif 等。
WebP 是由 Google 开发的一种现代图像格式,它提供了比传统格式(如 JPEG 和 PNG)更有效的图像数据压缩。使用 WebP,网站和应用程序可以创建更小、更快且更美观的图像,同时减少带宽和加载时间。
新增 OSS 实现类
AliOssWrapper 这个实现类的注释和 log 比较多,我就直接截图再做说明,在 paicoding-service 包下,用于实现与 OSS 的交互。
@Slf4j 注解和 @Component 注解我就不再赘述了,一个是为了方便获取 log 对象,一个前面说过为了注入 Spring 的容器,方便创建对象。
@ConditionalOnExpression 这个 Spring 注解使得这个 Bean
回复