原文链接:https://blog.csdn.net/weixin_39973810/article/details/86545054
前提:
图片的压缩大致有两种,一种是将图片的尺寸压缩小,另一种是尺寸不变,将压缩质量,一般对于项目我们需要第一种,即用户上传一张分辨率为3840 × 2160的图片,通过上传图片接口后上传到OSS上的图片分辨率会变成1920×1080(如3840 × 2160的图片大小为11.4M,上传后的图片大概会为1.9M),此时上传后到OSS的图片和原图质量上一致,也就是说看上去只的大小的区别,清晰度上没有任何区别,如还希望图片再小点再进行质量压缩。
整体思路:
用户点击上传图片按钮,调用上传接口,通过springboot的MultipartFile接口接收到文件,再将MultipartFile转化成一个文件放到项目中待使用(路径自己指定)。
通过指定文件路径获取到该文件(contextPath是步骤1中转存图片的文件路径),使用Thumbnails对图片进行尺寸压缩和格式转换(将图片转为jpg),然后将压缩后的图片替换步骤1中的图片。
再次通过File tempFile = new File(contextPath)获取压缩后的图片文件,将图片转为inputStream流上传至OSS,将项目中的图片文件删除,完成。
测试效果:
调用上传图片接口:
接口返回结果:
{
"success": true,
"status": "200",
"msg": "上传图片成功",
"data": "bookService/pictureUploadTest/b42dd004ddae4b329b91de0472b26163/原图11.4M(3840×2160.jpg"
}
1
2
3
4
5
6
OSS上压缩后的图片
项目代码:
控制器
/**
* 上传图片(通用)
*
* @param file 文件
* @param id 图书id
* @param folderSecond 设置二级图片路径(例bookUpload/)
* @return
*/
@RequestMapping(value = "pictureFile", method = RequestMethod.POST)
@ResponseBody
public JsonResult uploadPicture(MultipartFile file, String id, String folderSecond) {
try {
String pictureUrl = uploadService.uploadPicture(file, id, folderSecond);
log.info("上传后的图片地址:" + pictureUrl);
return renderSuccess("上传图片成功", pictureUrl);
} catch (ProgramException p) {
log.error("上传图片失败." + p.getMessage());
return renderError(p.getMessage());
} catch (Exception e) {
log.error("上传图片失败." + e.getMessage());
return renderError("上传图片失败");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
实现类:
注意:使用Thumbnailator进行格式转换的时候如果图片格式是png,在转成jpg后会出现图片变红的bug,所以此处的处理是不处理,也就是如果图片是后缀是png的话,不进行格式转换,只进行大小压缩
@Override
public String uploadPicture(MultipartFile multipartfile, String id, String folderSecond) throws Exception {
if (multipartfile == null || id == null || folderSecond == null) {
throw new ProgramException("上传图片参数不合法");
}
if (multipartfile.getSize() > 50 * 1024 * 1024) {
throw new ProgramException("上传图片大小不能超过50M!");
}
//设置统一图片后缀名
String suffixName;
//获取图片文件名(不带扩展名的文件名)
String prefixName = getFileNameWithoutEx(multipartfile.getOriginalFilename());
//获取图片后缀名,判断如果是png的话就不进行格式转换,因为Thumbnails存在转png->jpg图片变红bug
String suffixNameOrigin = getExtensionName(multipartfile.getOriginalFilename());
if ("png".equals(suffixNameOrigin)) {
suffixName = "png";
} else {
suffixName = "jpg";
}
//图片存储文件夹
String filePath = "web/src/main/resources/";
//图片在项目中的地址(项目位置+图片名,带后缀名)
String contextPath = filePath + prefixName + "." + suffixName;
//存的项目的中模版图片
File tempFile = null;
//上传时从项目中拿到的图片
File f = null;
InputStream inputStream = null;
try {
//图片在项目中的地址(项目位置+图片名,带后缀名)
tempFile = new File(contextPath);
if (!tempFile.exists()) {
//生成图片文件
FileUtils.copyInputStreamToFile(multipartfile.getInputStream(), tempFile);
}
/*
* size(width,height) 若图片横比1920小,高比1080小,不变
* 若图片横比1920小,高比1080大,高缩小到1080,图片比例不变 若图片横比1920大,高比1080小,横缩小到1920,图片比例不变
* 若图片横比1920大,高比1080大,图片按比例缩小,横为1920或高为1080
* 图片格式转化为jpg,质量不变
*/
BufferedImage image = ImageIO.read(multipartfile.getInputStream());
if (image.getHeight() > 1080 || image.getWidth() > 1920) {
if (!"png".equals(suffixName)) {
Thumbnails.of(contextPath).size(1920, 1080).outputQuality(1f).outputFormat("jpg").toFile(contextPath);
} else {
Thumbnails.of(contextPath).size(1920, 1080).outputQuality(1f).toFile(contextPath);
}
} else {
if (!"png".equals(suffixName)) {
Thumbnails.of(contextPath).outputQuality(1f).scale(1f).outputFormat("jpg").toFile(contextPath);
} else {
Thumbnails.of(contextPath).outputQuality(1f).scale(1f).toFile(contextPath);
}
}
//获取压缩后的图片
f = new File(contextPath);
inputStream = new FileInputStream(f);
//设置三级文件夹名
String folderThird = id + "/";
//设置OSS上的二级文件目录
String folderPath = folderSecond + folderThird;
//设置图片存储在oss上的名字
String fileName = prefixName + "." + suffixName;
//上传图片到OSS,返回图书路径
String resultUrl = AliyunOSSClientUtil.uploadImg2Oss(inputStream, folderPath, fileName);
return resultUrl;
} catch (Exception e) {
throw new ProgramException("图片上传失败");
} finally {
//将临时文件删除
tempFile.delete();
f.delete();
inputStream.close();
}
}
/**
* 获取文件扩展名
*
* @param filename 文件名
* @return
*/
public static String getExtensionName(String filename) {
if ((filename != null) && (filename.length() > 0)) {
int dot = filename.lastIndexOf('.');
if ((dot > -1) && (dot < (filename.length() - 1))) {
return filename.substring(dot + 1);
}
}
return filename;
}
//删除oss上的文件(该博客没有涉及到该方法,可以跳过)
@Override
public boolean deleteOssFile(String id) throws Exception {
if (id == null) {
throw new ProgramException("删除OSS文件参数不合法");
}
//设置文件位置的二级文件夹名
String folderSecond = "bookUpload/";
//设置三级文件夹名
String folderThird = id;
//设置OSS上要删除的的二级文件目录(例:client/45e233d07c664b93b7bb35331285a8d8)
String folderPath = folderSecond + folderThird;
//删除OSS上的文件(文件夹+文件)
return AliyunOSSClientUtil.deleteFile2Oss(folderPath);
}
/**
* 获取不带扩展名的文件名
*
* @param filename 文件
* @return
*/
private static String getFileNameWithoutEx(String filename) {
if ((filename != null) && (filename.length() > 0)) {
int dot = filename.lastIndexOf('.');
if ((dot > -1) && (dot < (filename.length()))) {
return filename.substring(0, dot);
}
}
return 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
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
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
自定义OSS工具类
package com.welsee.tools;
import com.aliyun.oss.OSSClient;
import com.aliyun.oss.model.*;
import com.welsee.exception.ProgramException;
import lombok.extern.slf4j.Slf4j;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
/**
* 阿里云OSS相关java API
*
* @author lixinyu
*/
@Slf4j
public class AliyunOSSClientUtil {
//阿里云API的内或外网域名
private static String ENDPOINT;
//阿里云API的密钥Access Key ID
private static String ACCESS_KEY_ID;
//阿里云API的密钥Access Key Secret
private static String ACCESS_KEY_SECRET;
//阿里云API的bucket名称
private static String BUCKET_NAME;
//阿里云API的文件夹名称
private static String FOLDER;
//初始化属性
static {
ENDPOINT = OSSClientConstants.ENDPOINT;
ACCESS_KEY_ID = OSSClientConstants.ACCESS_KEY_ID;
ACCESS_KEY_SECRET = OSSClientConstants.ACCESS_KEY_SECRET;
BUCKET_NAME = OSSClientConstants.BUCKET_NAME;
FOLDER = OSSClientConstants.FOLDER;
}
/**
* 获取阿里云OSS客户端对象
*
* @return ossClient
*/
private static OSSClient getOSSClient() {
// log.info("实例化阿里云OSS对象===============" + ENDPOINT + "," + ACCESS_KEY_ID + "," + ACCESS_KEY_SECRET);
return new OSSClient(ENDPOINT, ACCESS_KEY_ID, ACCESS_KEY_SECRET);
}
/**
* 通过文件名判断并获取OSS服务文件上传时文件的contentType
*
* @param fileName 文件名
* @return 文件的contentType
*/
private static String getContentType(String fileName) {
//文件的后缀名
String fileExtension = fileName.substring(fileName.lastIndexOf("."));
if (".bmp".equalsIgnoreCase(fileExtension)) {
return "image/bmp";
}
if (".gif".equalsIgnoreCase(fileExtension)) {
return "image/gif";
}
if (".jpeg".equalsIgnoreCase(fileExtension) || ".jpg".equalsIgnoreCase(fileExtension) || ".png".equalsIgnoreCase(fileExtension)) {
return "image/jpeg";
}
if (".html".equalsIgnoreCase(fileExtension)) {
return "text/html";
}
if (".txt".equalsIgnoreCase(fileExtension)) {
return "text/plain";
}
if (".vsd".equalsIgnoreCase(fileExtension)) {
return "application/vnd.visio";
}
if (".ppt".equalsIgnoreCase(fileExtension) || "pptx".equalsIgnoreCase(fileExtension)) {
return "application/vnd.ms-powerpoint";
}
if (".doc".equalsIgnoreCase(fileExtension) || "docx".equalsIgnoreCase(fileExtension)) {
return "application/msword";
}
if (".xml".equalsIgnoreCase(fileExtension)) {
return "text/xml";
}
//默认返回类型
return "image/jpeg";
}
/**
* 封装上传到OSS服务器方法 如果同名文件会覆盖服务器上的
*
* @param inputStream 文件流
* @param folderPath OSS目录下的二级文件名
* @param fileName 文件名称 包括后缀名
* @return 出错返回"" ,唯一MD5数字签名
*/
public static String uploadImg2Oss(InputStream inputStream, String folderPath, String fileName) throws Exception {
String result = "";
// 创建上传Object的Metadata
ObjectMetadata metadata = new ObjectMetadata();
metadata.setContentLength(inputStream.available());
//指定该Object被下载时的网页的缓存行为
metadata.setCacheControl("no-cache");
//指定该Object下设置Header
metadata.setHeader("Pragma", "no-cache");
//文件的MIME,定义文件的类型及网页编码,决定浏览器将以什么形式、什么编码读取文件。如果用户没有指定则根据Key或文件名的扩展名生成,
//如果没有扩展名则填默认值application/octet-stream
metadata.setContentType(getContentType(fileName));
//指定该Object被下载时的名称(指示MINME用户代理如何显示附加的文件,打开或下载,及文件名称)
metadata.setContentDisposition("inline;filename=" + fileName);
String resultUrl = FOLDER + folderPath + fileName;
// 上传文件
PutObjectResult putResult = getOSSClient().putObject(BUCKET_NAME, resultUrl, inputStream, metadata);
// 设置文件的访问权限为公共读。
getOSSClient().setObjectAcl(BUCKET_NAME, resultUrl, CannedAccessControlList.PublicRead);
if (!"".equals(putResult.getETag())) {
result = resultUrl;
log.info("上传后的图片MD5数字唯一签名:" + putResult.getETag()); //可以用来验证上传的资源是否为同一个(暂时没用到)
log.info("上传阿里云OSS服务器成功");
} else {
log.error("上传阿里云OSS服务器异常");
}
inputStream.close();
getOSSClient().shutdown();
return result;
}
/**
* 删除oss上的文件,文件夹+文件夹里面的所的文件(该博客没有涉及到该方法,可以跳过)
*
* @param folderPath 设置OSS上要删除的的二级文件目录(例:(client/45e233d07c664b93b7bb35331285a8d8))
* @return
*/
public static boolean deleteFile2Oss(String folderPath) {
//oss项目名+图书位置(bookService/bookUpload/45e233d07c664b93b7bb35331285a8d8)
String prefix = FOLDER + folderPath;
// 列举文件。 如果不设置KeyPrefix,则列举存储空间下所有的文件。KeyPrefix,则列举包含指定前缀的文件。
ObjectListing objectListing = getOSSClient().listObjects(new ListObjectsRequest(BUCKET_NAME).withPrefix(prefix));
List<String> keys = new ArrayList<>();
List<OSSObjectSummary> sums = objectListing.getObjectSummaries();
for (OSSObjectSummary s : sums) {
keys.add(s.getKey());
}
//如果OSS文件上有文件的话删除,没有直接跳过
if (keys.size() > 0) {
// 删除文件夹内的文件。
DeleteObjectsResult deleteObjectsResult = getOSSClient().deleteObjects(new DeleteObjectsRequest(BUCKET_NAME).withKeys(keys));
deleteObjectsResult.getDeletedObjects();
//删除文件夹
List<String> key = new ArrayList<>();
key.add(prefix);
getOSSClient().deleteObjects(new DeleteObjectsRequest(BUCKET_NAME).withKeys(key));
}
// 关闭OSSClient。
getOSSClient().shutdown();
return true;
}
/**
* 删除oss一个文件夹中的无用图片,保留一个图片(该博客没有涉及到该方法,可以跳过)
*
* @param picturePath 有用的图片路径
* @return
*/
public static boolean deletePictureUseless(String picturePath) throws Exception {
if (picturePath == null) {
throw new ProgramException("删除oss无用图片参数不合法");
}
//文件夹路径(bookService/bookUpload/0c6fd6fbac33466e8b26e73115f80edd)
String filePath = picturePath.substring(0, picturePath.lastIndexOf("/"));
//列举所有的图片
ObjectListing objectListing = getOSSClient().listObjects(new ListObjectsRequest(BUCKET_NAME).withPrefix(filePath));
List<String> keys = new ArrayList<>();
List<OSSObjectSummary> sums = objectListing.getObjectSummaries();
for (OSSObjectSummary s : sums) {
//判断是否是图片,只删无用图片, 不删图书
if ("jpg".equals(s.getKey().substring(s.getKey().lastIndexOf(".") + 1))) {
if (!picturePath.equals(s.getKey())) {
keys.add(s.getKey());
}
}
}
log.info("要删除的图片为===============" + keys.toString());
// 删除无用图片
if (keys.size() > 0) {
DeleteObjectsResult deleteObjectsResult = getOSSClient().deleteObjects(new DeleteObjectsRequest(BUCKET_NAME).withKeys(keys));
deleteObjectsResult.getDeletedObjects();
}
return true;
}
}
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
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
OSS配置文件:
package com.welsee.tools;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
/**
* @author lixinyu
* @class:OSSClientConstants
* @descript:阿里云注册用户基本常量
* @date:2018年11月06日 下午5:52:34
*/
@Configuration
public class OSSClientConstants {
//阿里云API的外网域名
public static String ENDPOINT;
//阿里云API的密钥Access Key ID
public static String ACCESS_KEY_ID;
//阿里云API的密钥Access Key Secret
public static String ACCESS_KEY_SECRET;
//阿里云API的bucket名称
public static String BUCKET_NAME;
//阿里云API的文件夹名称
public static String FOLDER;
@Value("${OSS_ENDPOINT}")
public void setENDPOINT(String ENDPOINT) {
OSSClientConstants.ENDPOINT = ENDPOINT;
}
@Value("${OSS_ACCESS_KEY_ID}")
public void setAccessKeyId(String accessKeyId) {
OSSClientConstants.ACCESS_KEY_ID = accessKeyId;
}
@Value("${OSS_ACCESS_KEY_SECRET}")
public void setAccessKeySecret(String accessKeySecret) {
OSSClientConstants.ACCESS_KEY_SECRET = accessKeySecret;
}
@Value("${OSS_BUCKET_NAME}")
public void setBucketName(String bucketName) {
BUCKET_NAME = bucketName;
}
@Value("${OSS_FOLDER}")
public void setFOLDER(String FOLDER) {
OSSClientConstants.FOLDER = FOLDER;
}
}