目录
一、概述
二、 开发前的准备
三、 配置阿里云 OSS 存储相关属性
四、工具类相关方法编写
五、 Controller 层编写相关测试方法
六、 上传图片相关前端页面
七、 测试我们的图床
一、概述
这是一篇之前在公众号写的关于一篇 SpringBoot 整合阿里云OSS 文章,最近公司有上传服务需求,便把以前的OSS上传功能做了一下封装,封装组件模板,可以提供各个服务之间调用。该文章包含了上传文件自带进度条功能、多文件上传、下载、删除和 获取文件列表功能。
上传流程架构图
介绍一下OSS
阿里云对象存储服务,是阿里云提供的海量、安全、低成本、高可靠的云存储服务。它具有与平台无关的RESTful API接口,您可以在任何应用、任何时间、任何地点存储和访问任意类型的数据 您可以使用阿里云提供的API/SDK接口或者OSS迁移工具轻松地将海量数据移入或移出阿里云OSS。数据存储到阿里云OSS以后,您可以选择标准类型(Standard)的阿里云OSS服务作为移动应用、大型网站、图片分享或热点音视频的主要存储方式,也可以选择成本更低、存储期限更长的低频访问类型(Infrequent Access)和归档类型(Archive)的阿里云OSS服务作为不经常访问数据的备份和归档。
阿里云OSS官方介绍地址
https://help.aliyun.com/document_detail/31947.html
本篇文章会介绍到 SpringBoot 整合阿里云OSS 存储服务实现文件上传下载以及简单的查看。其实今天将的应该算的上是一个简单的小案例了,涉及到的知识点还算是比较多
二、 开发前的准备
1. 前置知识
具有 Java 基础以及SpringBoot 简单基础知识即可
2. 环境参数
- 开发工具:IDEA
- 基础工具:Maven+JDK8
- 所用技术:SpringBoot+阿里云OSS 存储服务 Java 相关API
- SpringBoot版本:2.1.2
3. 能学到什么
- SpringBoot 整合 阿里云OSS 存储服务并编写相关工具类
- Swagger接口文档
- SpringBoot 整合 thymeleaf 并实现前后端传值
- SpringBoot 从配置文件读取值并注入到类中
- 如何自己搭建一个图床使用(通过前端选择图片,支持预览,但不支持修改图片)
4. 项目结构
5. 配置 pom.xml 文件中的相关依赖
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.2.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- Thymeleaf-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<!-- 阿里云OSS-->
<dependency>
<groupId>com.aliyun.oss</groupId>
<artifactId>aliyun-sdk-oss</artifactId>
<version>2.4.0</version>
</dependency>
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.3.1</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.12</version>
</dependency>
<!--hutool-all-->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.3.5</version>
</dependency>
<!--整合Knife4j-->
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-spring-boot-starter</artifactId>
<version>2.0.4</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
三、 配置阿里云 OSS 存储相关属性
我在项目中使用properties
配置,然后@ConfigurationProperties
注解注入到类中。
1 通过Properties注入配置文件
@Data
@Component("ossProperties")
@ConfigurationProperties(prefix = "oss")
public class OSSProperties {
/**
* 默认的存储桶名称
*/
private String bucketname;
/**
* 地域节点
*/
private String endpoint;
/**
* 你的AccessKeyID
*/
private String accessKeyId;
/**
* 秘钥
*/
private String accessKeySecret;
}
到阿里云 OSS 控制台:https://oss.console.aliyun.com/overview 获取上述相关信息:如图
获取AccessKey ID和Access Key Secret第一步:如图
获取AccessKey ID 和 Access Key Secret 第二步:如图
- SpringBoot 整合 阿里云OSS 存储服务并编写相关工具类
- SpringBoot 整合 thymeleaf 并实现前后端传值
- SpringBoot 从配置文件读取值并注入到类中
- 如何自己搭建一个图床使用(通过前端选择图片,支持预览,但不支持修改图片)
四、工具类相关方法编写
该工具类主要提供了基本上传方法:如果需要增加新的方法,参考阿里云官方提供的相关文档来根据自己的需求来优化。
Java API文档地址如下:
https://help.aliyun.com/document_detail/32008.html?spm=a2c4g.11186623.6.703.238374b4PsMzWf
整体搭建OSS思路
- 从properties配置文件中获取OSS配置信息
- 封装一个配置类来组装OSS客户端,只初始化一次
- 封装一个OSS上传文件模板提供外部调用
1. OSSConfiguration
@Configuration
@EnableConfigurationProperties(OSSProperties.class)
public class OSSConfiguration
{
@Resource(name = "ossProperties")
private OSSProperties ossProperties;
@Bean("ossClient")
public OSSClient ossClient() {
@SuppressWarnings("deprecation")
OSSClient client = new OSSClient(
ossProperties.getEndpoint(),
ossProperties.getAccessKeyId(),
ossProperties.getAccessKeySecret());
return client;
}
@Bean
@ConditionalOnBean(OSSClient.class)
@ConditionalOnMissingBean(OSSTemplate.class)
public OSSTemplate ossTemplate(OSSClient ossClient) {
return new OSSTemplate(ossClient);
}
@Slf4j
public class OSSTemplate {
@Resource
private OSSClient ossClient;
public OSSTemplate(OSSClient ossClient) {
this.ossClient = ossClient;
}
/**
* 目前所有文件都存放在该命名空间下
*/
private final static String bucketName = OSSConstant.BUCKE_NAME;
/**
* 上传文件
* @param bucketName 仓库名称
* @param fileUrl 生成文件url
* @param input InputStream
* @date: 2021/3/19 10:39
* @return: void
*/
@SneakyThrows
public void putObject(String bucketName, String fileUrl, InputStream input) {
ossClient.putObject(bucketName, fileUrl, input);
}
/**
* 上传文件带进度条
* @param bucketName 仓库名称
* @param fileUrl 生成文件url
* @param file File
* @param uploadNo 上传文件编号,为了获文件进度条(唯一标识)
* @param fileSize 文件大小(单位 KB)
* @date: 2021/3/19 10:36
* @return: void
*/
@SneakyThrows
public void putObject(String bucketName, String fileUrl, File file,String uploadNo,
Long fileSize) {
ossClient.putObject(new PutObjectRequest(bucketName,
fileUrl, file).withProgressListener(new PutObjectProgressListener(
uploadNo, fileSize)));
}
/**
* 生成过期URL链接地址
* @param bucketName 仓库名称
* @param key
* @date: 2021/3/19 10:37
* @return: java.lang.String
*/
@SneakyThrows
public String generatePresignedUrl(String bucketName, String key) {
Date expiration = new Date(new Date().getTime() + 3600l * 1000 * 24 * 365 * 10);
return ossClient.generatePresignedUrl(bucketName, key, expiration).toString();
}
/**
* 创建仓库
* @param bucketName 仓库名称
* @date: 2021/3/19 10:39
* @return: void
*/
@SneakyThrows
public void createBucket(String bucketName) {
ossClient.createBucket(bucketName);
}
/**
* 删除仓库
* @param bucketName 仓库名称
* @date: 2021/3/19 10:39
* @return: void
*/
@SneakyThrows
public void deleteBucket(String bucketName) {
ossClient.deleteBucket(bucketName);
}
/**
* 删除OSS 文件
* @param delFileUrl 删除指定文件URL
*/
@SneakyThrows
public void delFile(String delFileUrl){
// key是文件名
ossClient.deleteObject(bucketName, delFileUrl);
// ossClient.shutdown();//关闭连接
}
/**
* 下载文件
* @param downFileUrl
* @date: 2021/3/18 17:30
* @return: com.aliyun.oss.model.OSSObject
*/
public void getObject(String downFileUrl, HttpServletResponse response) {
OSSObject ossObject = ossClient.getObject(bucketName, downFileUrl);
BufferedInputStream bis = null;
try {
String filename=System.currentTimeMillis()+downFileUrl.substring(downFileUrl.lastIndexOf("."));
response.setContentType("application/octet-stream");
response.setHeader("Content-disposition", "attachment; filename=" + new String(filename.getBytes("utf-8"), "ISO8859-1"));
bis = new BufferedInputStream(ossObject.getObjectContent());
OutputStream os = response.getOutputStream();
byte[] buffer = new byte[2048];
int i = bis.read(buffer);
while (i != -1) {
os.write(buffer, 0, i);
i = bis.read(buffer);
}
} catch (IOException e) {
e.printStackTrace();
}finally {
if (bis != null) {
try {
bis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
// 关闭OSSClient。
ossClient.shutdown();
}
/**
* 通过文件名下载文件到本地
*
* @param objectName 要下载的文件名
* @param localFileName 本地要创建的文件名
*/
public void downloadFile(String objectName, String localFileName) {
// 下载OSS文件到本地文件。如果指定的本地文件存在会覆盖,不存在则新建。
ossClient.getObject(new GetObjectRequest(bucketName, objectName), new File(localFileName+StrUtil.DOT+"jpg"));
// 关闭OSSClient。
// ossClient.shutdown();
}
/**
* 列举文件下所有的文件根据目录
*/
public List<String> listFile(String filePath) {
List<String> fileList = new ArrayList<>(12);
// 构造ListObjectsRequest请求。
ListObjectsRequest listObjectsRequest = new ListObjectsRequest(bucketName);
// 设置prefix参数来获取fun目录下的所有文件。
listObjectsRequest.setPrefix(filePath+ StrUtil.SLASH);
// 列出文件。
ObjectListing listing = ossClient.listObjects(listObjectsRequest);
// 遍历所有文件。
for (OSSObjectSummary objectSummary : listing.getObjectSummaries()) {
fileList.add(objectSummary.getKey());
}
log.info("遍历所有commonPrefix:");
for (String commonPrefix : listing.getCommonPrefixes()) {
System.out.println(commonPrefix);
fileList.add(commonPrefix);
}
return fileList;
}
}
监听上传文件进度条
@Slf4j
public class PutObjectProgressListener implements ProgressListener {
private String uploadNo;
private long bytesWritten = 0;
private long totalBytes = -1;
private boolean succeed = false;
private BigDecimal percent = new BigDecimal(0.00);
Long sum = 0L;
public PutObjectProgressListener(String uploadNo, Long sum) {
this.uploadNo = uploadNo;
this.sum = sum;
}
@Override
public void progressChanged(ProgressEvent progressEvent) {
long bytes = progressEvent.getBytes();
ProgressEventType eventType = progressEvent.getEventType();
switch (eventType) {
case TRANSFER_STARTED_EVENT:
// logger.info("Start to upload......");
break;
case REQUEST_CONTENT_LENGTH_EVENT:
this.totalBytes = bytes;
// logger.info(this.totalBytes +
// " bytes in total will be uploaded to OSS");
break;
case REQUEST_BYTE_TRANSFER_EVENT:
this.bytesWritten += bytes;
if (this.totalBytes != -1) {
double progress = this.bytesWritten * 100.0 / this.totalBytes;
percent = new BigDecimal(progress).setScale(2, BigDecimal.ROUND_HALF_UP);
// 将进度percent放入session中
CacheUpload.putProgressBar(uploadNo,percent);
}
break;
case TRANSFER_COMPLETED_EVENT:
this.succeed = true;
break;
case TRANSFER_FAILED_EVENT:
break;
default:
break;
}
//图片进度打印控制台
log.info("percent:" + percent);
}
public boolean isSucceed() {
return succeed;
}
}
五、 Controller 层编写相关测试方法
注意将下面的相关路径改成自己的,不然会报错!!!
/**
*
* @description: 测试OSS功能
* @author: ZouLiPing
* @contact: 请关注微信公众号“给自己一个smile”里面有详细电商项目文档,weixin:zlp865391093
* @date: 2019年3月29日15:18:33
* @version V1.0
*/
@Slf4j
@RestController
@RequestMapping("/file")
@Api(value = "file", tags = "文件模块")
public class FileController {
@Autowired
private FileService fileService;
//仓库中的某个文件夹
public static final String FILE_HOST = "test";
/**
* 测试上传文件到阿里云OSS存储(不支持上传文件,推荐作为图床使用)
*
* @return
*/
@PostMapping("/testUpload")
@ApiOperation(value = "测试上传文件")
public String testUpload() {
String picPath = "C:\\Users\\Administrator\\Desktop\\WeChat_20210319223645.jpg";
MultipartFile file = FileUtil.getMulFileByPath(picPath);
String url = fileService.uploadFile(FILE_HOST,file);
return url;
}
/**
* 文件上传
*/
@PostMapping("/uploadFile")
@ApiOperation(value = "文件上传")
public String uploadFile(@RequestParam("file") MultipartFile file) {
String url = fileService.uploadFile(FILE_HOST, file);
return url;
}
@PostMapping("/uploadFileProgress")
@ApiOperation(value = "文件上传带进度条")
public UploadResp uploadFileProgress(@RequestParam("file") MultipartFile file,
@RequestParam("uploadNo") String uploadNo
){
long fileSize = (file.getSize()/1024*100)/100;
UploadResp uploadResp = fileService.uploadFileProgress(file,FILE_HOST,fileSize,uploadNo);
return uploadResp;
}
/**
* 获取进度条
*/
@GetMapping(value = "/getProgressBar")
@ApiOperation(value = "获取进度条")
public UploadVO getProgressBar(@RequestParam("uploadNo") String uploadNo) {
UploadVO uploadVO ;
for (;;) {
uploadVO = CacheUpload.getProgressBar(uploadNo);
if (Objects.nonNull(uploadVO)) {
log.info("getProgressBar.resp uploadVO={}", uploadVO.toString());
if (uploadVO.getProgressBar().compareTo(new BigDecimal(100.00)) == 0) {
break;
}
}
}
CacheUpload.removeProgressBar(uploadNo);
return uploadVO;
}
/**
* 列出某个文件夹下的所有文件
*/
@GetMapping("/testListFile")
@ApiOperation(value = "列出某个文件夹下的所有文件")
public List<String> testListFile(@RequestParam("filePath") String filePath) {
return fileService.listFile(filePath);
}
/**
* 删除文件
*/
@GetMapping("/delFile")
@ApiOperation(value = "删除文件")
public String delFile(@RequestParam("filePath") String delFileUrl) {
fileService.delFile(delFileUrl);
return "success";
}
}
六、 上传图片相关前端页面
注意引入jquery ,避免前端出错。
index.html
JS 的内容主要是让我们上传的图片可以预览,就像我们在网站更换头像的时候一样
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>基于阿里云OSS存储的图床</title>
<script th:src="@{/js/jquery-3.3.1.js}"></script>
<style>
* {
margin: 0;
padding: 0;
}
#submit {
margin-left: 15px;
}
.preview_box img {
width: 200px;
}
</style>
</head>
<body>
<form action="/oss/uploadFile" enctype="multipart/form-data" method="post">
<div class="form-group" id="group">
<input type="file" id="img_input" name="file" accept="image/*">
<label for="img_input" ></label>
</div>
<button type="submit" id="submit">上传</button>
<!--预览图片-->
<div class="preview_box"></div>
</form>
<script type="text/javascript">
$("#img_input").on("change", function (e) {
var file = e.target.files[0]; //获取图片资源
// 只选择图片文件
if (!file.type.match('image.*')) {
return false;
}
var reader = new FileReader();
reader.readAsDataURL(file); // 读取文件
// 渲染文件
reader.onload = function (arg) {
var img = '<img class="preview" src="' + arg.target.result + '" alt="preview"/>';
$(".preview_box").empty().append(img);
}
});
</script>
</body>
</html>
success.html
通过 <span th:text="${url}"></span>
引用后端传过来的值。
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>上传结果</title>
</head>
<body>
<h1>上传成功!</h1>
图片地址为:<span th:text="${url}"></span>
</body>
</html>
七、 测试我们的图床
访问 :http://localhost:8080/oss
① 上传图片
② 图片上传成功返回图片地址
③ 通过图片 URL 访问图片
swagger文档测试