项目背景:

接入私有OSS以后,需要实现图片上传/访问,但因仓库私有,也不像ali-oss等云服务可以提供生成临时共有访问地址,只能以流的方式从OSS读取数据并渲染到前端。

引入依赖:

1
2
3
4
5
6
7
<!--引入AWS EDS对象存储依赖-->
<dependency>
<groupId>com.amazonaws</groupId>
<artifactId>aws-java-sdk-s3</artifactId>
<version>1.11.490</version>
</dependency>

代码实现:

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
package com.hsyk.bright.client.controller;

import cn.dev33.satoken.util.SaResult;
import com.hsyk.bright.client.utils.AwsS3Component;
import io.swagger.annotations.Api;
import org.apache.commons.lang3.StringUtils;
import org.springframework.util.AntPathMatcher;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.HandlerMapping;

import javax.annotation.Resource;
import javax.imageio.ImageIO;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.swing.*;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.*;

@RestController
@RequestMapping("/aws")
@Api(tags = "aws文件上传")
public class AwsOssController {

@Resource
AwsS3Component awsS3Component;

@Resource
HttpServletResponse response;

@PostMapping("/file/upload")
public SaResult upload(MultipartFile file,String folder) throws Exception {
return SaResult.data(awsS3Component.upload(file,folder));
}


@GetMapping("/get/{address}/**")
public void get(@PathVariable("address") String address, HttpServletRequest request) throws IOException {
//处理请求参数中带目录/的参数
final String pathQ = request.getAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE).toString();
final String bestMatchingPattern = request.getAttribute(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE).toString();
String arguments = new AntPathMatcher().extractPathWithinPattern(bestMatchingPattern, pathQ);
String moduleName;
if (StringUtils.isNotBlank(arguments)) {
moduleName = address + '/' + arguments;
} else {
moduleName = address;
}
String[] split = moduleName.split("\\.");
String extend = split[1];
OutputStream os = null;
try {
InputStream inputStream = null;
inputStream = awsS3Component.getInputStream(moduleName);
BufferedImage image = ImageIO.read(inputStream);
response.setContentType("image/" + extend);
os = response.getOutputStream();
if(image != null){
ImageIO.write(image,extend,os);
}
} catch (IOException e) {
e.printStackTrace();
}finally {
if(os != null){
os.flush();
os.close();
}
}
}

}

注意 response.setContentType(“image/“ + extend); 必须设置返回的文件类型,浏览器才可以直接渲染展示

用到了工具类AwsS3Component和FileMd5Util

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
package com.hsyk.bright.client.utils;

import com.amazonaws.ClientConfiguration;
import com.amazonaws.Protocol;
import com.amazonaws.auth.AWSStaticCredentialsProvider;
import com.amazonaws.auth.BasicAWSCredentials;
import com.amazonaws.client.builder.AwsClientBuilder;
import com.amazonaws.regions.Regions;
import com.amazonaws.services.s3.AmazonS3;
import com.amazonaws.services.s3.AmazonS3ClientBuilder;
import com.amazonaws.services.s3.model.*;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.Resource;
import javax.imageio.ImageIO;
import javax.swing.*;
import java.awt.*;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.Date;
import java.util.concurrent.TimeUnit;

@Component
@Slf4j
@RefreshScope
public class AwsS3Component implements InitializingBean {


@Value("${aws.accessKey}")
private String accessKey;
@Value("${aws.secretKey}")
private String accessSecret;
@Value("${aws.bucket}")
private String bucket;
@Value("${aws.endpoint}")
private String endpoint;

private AmazonS3 client;

@Resource
private FileMd5Util fileMd5Util;

@Override
public void afterPropertiesSet() throws Exception {
ClientConfiguration config = new ClientConfiguration();
config.setProtocol(Protocol.HTTP);
config.disableSocketProxy();
this.client = AmazonS3ClientBuilder
.standard()
.withClientConfiguration(config)
.withCredentials(new AWSStaticCredentialsProvider(new BasicAWSCredentials(accessKey, accessSecret)))
.withEndpointConfiguration(new AwsClientBuilder.EndpointConfiguration(endpoint, Regions.CN_NORTH_1.getName()))
.enablePathStyleAccess()
.build();
}


/**
* 执行文件上传
*
* @param file 要上传的文件的路径
* @param key 存储文件的路径
* @return 文件路径
*/
public String upload(File file, String key) {
client.putObject(new PutObjectRequest(bucket, key, file).withCannedAcl(CannedAccessControlList.PublicRead));
GeneratePresignedUrlRequest urlRequest = new GeneratePresignedUrlRequest(bucket, key);
URL url = client.generatePresignedUrl(urlRequest);
return url.toString();
}

/**
* 文件流执行文件上传
* @param input
* @param key
* @return
* @throws IOException
*/
public String upload(MultipartFile file,String folder) throws IOException {
//获取文件md5值
String fileMd5 = fileMd5Util.getFileMd5Value(file);
String fileName = file.getOriginalFilename();
String prefix = fileName.substring(fileName.lastIndexOf(".") + 1);
StringBuffer tempFileName = new StringBuffer(fileMd5);
tempFileName.append(".").append(prefix);
String localFileName = tempFileName.toString();
ObjectMetadata metadata = new ObjectMetadata();
metadata.setContentLength(file.getInputStream().available());
localFileName = folder + "/" + localFileName;
client.putObject(new PutObjectRequest(bucket, localFileName, file.getInputStream(), metadata).withCannedAcl(CannedAccessControlList.PublicRead));
GeneratePresignedUrlRequest urlRequest = new GeneratePresignedUrlRequest(bucket, localFileName);
URL generatePresignedUrl = client.generatePresignedUrl(urlRequest);
log.info("generatePresignedUrl========================={}",generatePresignedUrl);
//文件访问路径
String url = endpoint + "/"+ bucket + "/" + localFileName;
log.info("文件---[{}]---上传到S3服务器成功!",url);
return localFileName;
}

/**
* 获取文件的stream对象
* @param url
* @return
*/
public InputStream getInputStream(String url){
S3Object s3Object = client.getObject(new GetObjectRequest(bucket, url));
InputStream inputStream = s3Object.getObjectContent();
return inputStream;
}


}

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
package com.hsyk.bright.client.utils;

import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;

import java.io.IOException;
import java.io.InputStream;
import java.security.MessageDigest;

@Slf4j
@Component
public class FileMd5Util {

/**
* 文件md5值
* @param multipartFile
* @return
*/
public String getFileMd5Value(MultipartFile multipartFile) {
InputStream in = null;
try {
in = multipartFile.getInputStream();
byte[] buffer = new byte[2048];
MessageDigest digest = MessageDigest.getInstance("MD5");
while (true) {
int len = in.read(buffer, 0, 2048);
if (len != -1) {
digest.update(buffer, 0, len);
} else {
break;
}
}
in.close();
byte[] md5Bytes = digest.digest();
StringBuffer hexValue = new StringBuffer();
for (int i = 0; i < md5Bytes.length; i++) {
int val = ((int) md5Bytes[i]) & 0xff;
if (val < 16) {
hexValue.append("0");
}
hexValue.append(Integer.toHexString(val));
}
return hexValue.toString();
} catch (Exception e) {
log.error("获取文件md5失败");
} finally {
try {
in.close();

} catch (IOException ex) {
log.error("关闭流异常", ex);
}
}
return null;
}

}

在渲染图片时需自定义配置注入BufferedImageHttpMessageConverter

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package com.hsyk.bright.client.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.http.converter.BufferedImageHttpMessageConverter;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

import java.util.List;

@Configuration
public class WebMvcConfig implements WebMvcConfigurer {

/**
* 增加图片转换器
* @param converters
*/
@Override
public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.add(new BufferedImageHttpMessageConverter());
}
}

在使用@PathVariable注解接受地址栏携带”/“的参数时需注意以下配置

请求地址示例:

1
https://xxxxxxxx.com/aws/get/image/fx/fb9265fa192edc8cac0daff888c91f9d.jpeg

其中image/fx/fb9265fa192edc8cac0daff888c91f9d.jpeg支持桶下多级目录

在配置请求地址时参数后面加上/**

1
@GetMapping("/get/{address}/**")

在获取地址参数时使用以下方法

1
2
3
4
5
6
7
8
9
10
11
//处理请求参数中带目录/的参数
final String pathQ = request.getAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE).toString();
final String bestMatchingPattern = request.getAttribute(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE).toString();
String arguments = new AntPathMatcher().extractPathWithinPattern(bestMatchingPattern, pathQ);
// moduleName为想要的参数
String moduleName;
if (StringUtils.isNotBlank(arguments)) {
moduleName = address + '/' + arguments;
} else {
moduleName = address;
}