【AgileTC】_图片上传方法总结
1.直接保存到本地文件夹(AgileTC开源版)
在项目配置文件application-dev.properties中配置图片保存地址:
1
=/Users/didi/littleforest/haha/
编写图片上传接口,请求方式为Post,请求参数为文件数组,响应体为文件保存路径;
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//加载配置文件中的图片保存地址
("${web.upload-path}")
private String uploadPath;
(value = "/uploadAttachment", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public String uploadAttachment(@RequestParam MultipartFile file, HttpServletRequest request) {
//将日期以文件路径的形式添加到保存路径中,使得每天上传的文件放在不同文件夹中。
SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd/");
String format = sdf.format(new Date());
File folder = new File(uploadPath + format);
//如果不存在该文件夹,则创建该文件夹
if (!folder.isDirectory()) {
folder.mkdirs();
}
//用UUID工具类对上传的文件重命名,避免文件重名
String oldName = file.getOriginalFilename();
String newName = UUID.randomUUID().toString()
+ oldName.substring(oldName.lastIndexOf("."), oldName.length());
try {
// 文件保存
file.transferTo(new File(folder, newName));
// 返回上传文件的访问路径
String filePath = request.getScheme() + "://" + request.getServerName()
+ ":" + request.getServerPort() + "/" + format + newName;
return filePath;
} catch (IOException e) {
LOGGER.error("上传文件失败, 请重试。", e);
return "null";
}
}
2.保存到FastDFS(乐优商城)
1.先在Linux系统上搭建FastDFS服务器。
2.针对FastDFS系统的上传接口有很多开源的java工具,这些工具的作用就类似于RestTemplate,对http客户端进行了封装,直接通过pom.xml引入相关依赖包即可使用:
1
2
3
4
5<dependency>
<groupId>com.github.tobato</groupId>
<artifactId>fastdfs-client</artifactId>
<version>1.26.2</version>
</dependency>3.创建fastdfs-client配置类:
1
2
3
4
5
6
7
(FdfsClientConfig.class)
// 解决jmx重复注册bean的问题
@EnableMBeanExport(registration = RegistrationPolicy.IGNORE_EXISTING)
public class FastClientImporter {
}4.在application.yml文件中配置servlet传输文件大小限制和FastDFS的相关使用属性:
1
2
3
4
5
6
7
8
9
10
11
12
13spring:
servlet:
multipart:
max-file-size: 5MB # 限制文件上传的大小
fdfs:
so-timeout: 1501 # 超时时间
connect-timeout: 601 # 连接超时时间
thumb-image: # 缩略图
width: 60
height: 60
tracker-list: # tracker地址:ip地址+端口(默认是22122)
- 192.168.56.101:221225.Controller层,请求方式为Post,请求参数为单个文件(如果想接受文件数组,添加相关逻辑即可),响应体中包含文件保存路径:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
("upload")
public class UploadController {
private UploadService uploadService;
("image")
public ResponseEntity<String> uploadImage(@RequestParam("file") MultipartFile file){
String url = this.uploadService.upload(file);
if (StringUtils.isBlank(url)) {
return ResponseEntity.badRequest().build();
}
return ResponseEntity.status(HttpStatus.CREATED).body(url);
}
}6.Service层,主要分四步来创建一个http请求上传图片:校验文件类型->校验文件内容->使用fastdfs-client对象发送请求消息并接受响应消息:
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
public class UploadService {
//注入fastdfs-client
private FastFileStorageClient storageClient;
//设置合法文件类型,此处设置只接受jpg和gif格式的文件
private static final List<String> CONTENT_TYPES = Arrays.asList("image/jpeg", "image/gif");
private static final Logger LOGGER = LoggerFactory.getLogger(UploadService.class);
public String upload(MultipartFile file) {
String originalFilename = file.getOriginalFilename();
// 校验文件的类型
String contentType = file.getContentType();
if (!CONTENT_TYPES.contains(contentType)){
// 文件类型不合法,直接返回null
LOGGER.info("文件类型不合法:{}", originalFilename);
return null;
}
try {
// 运用ImageIO工具类校验文件的内容,防止是修改后缀名的非图片文件
BufferedImage bufferedImage = ImageIO.read(file.getInputStream());
if (bufferedImage == null){
LOGGER.info("文件内容不合法:{}", originalFilename);
return null;
}
// 保存到服务器
String ext = StringUtils.substringAfterLast(originalFilename, ".");
StorePath storePath = this.storageClient.uploadFile(file.getInputStream(),
file.getSize(), ext, null);
// 生成url地址,返回
return "http://192.168.56.101:22122/" + storePath.getFullPath();
} catch (IOException e) {
LOGGER.info("服务器内部错误:{}", originalFilename);
e.printStackTrace();
}
return null;
}
}
3.保存到Gift(AgileTC滴滴内部版)
使用httpClient发送http请求完成图片上传
Gift提供了封装好的上传接口,我们先自己编写使用httpClient发送http请求完成图片上传的方法。(其实也可以只用Spring提供的RestTemplate模板工具类,该工具类对HttpClient等http客户端进行了封装,更加方便,不过我们这里并没有使用。)
1.在项目配置文件application-dev.properties中配置图片保存地址,该地址是在滴滴Gift服务器中申请的:
1
2=http://100.69.238.155:8000/resource/
=chefu_qa_file2.Controller层,请求方式为Post,请求参数为文件数组,响应体中包含文件保存路径:
1
2
3
4
5
6
7
8
9
10
11
12(value = "/upload", method = RequestMethod.POST)
public Response<?> uploadFileToGift(("files") List<MultipartFile> multipartFiles) {
try {
return Response.success(fileService.uploadFile(multipartFiles));
} catch (CaseServerException e) {
throw new CaseServerException(e.getLocalizedMessage(), e.getStatus());
} catch (Exception e) {
LOGGER.error("[上传文件出错]{}", e.getMessage());
e.printStackTrace();
return Response.build(StatusCode.FILE_IMPORT_ERROR.getStatus(), StatusCode.FILE_IMPORT_ERROR.getMsg());
}
}响应体格式如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16{
"code": 200,
"msg": "服务运行成功",
"data": [
{
"uuid": "15ff038b-74d7-41cf-b0be-7b21ef725f61",
"url": "http://100.69.238.155:8002/static/chefu_qa_file/69e3e391-081b-436c-86d9-7395b4bf01cf",
"name": "截屏2021-05-08 下午5.29.44.png"
},
{
"uuid": "ef2073a9-3a42-46c5-a8ed-fba40a9aaf8b",
"url": "http://100.69.238.155:8002/static/chefu_qa_file/2731b02a-76a6-421b-8a19-e44fcd848391",
"name": "截屏2021-05-08 下午5.29.54.png"
}
]
}3.Service层,主要分四步来创建一个http请求上传图片:创建HttpClient对象->创建传参体并塞入请求参数->创建HttpPost连接体并塞入传参体->使用HttpClient对象发送请求消息并接受响应消息:
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("${gift.namespace.value}")
private String nameSpace;
("${gift.upload.url}")
private String giftUrl;
public List<FileMeta> uploadFile(List<MultipartFile> multipartFiles) throws Exception {
// 传进来不仅要确保!=null 还要确保不为空
if (CollectionUtils.isEmpty(multipartFiles)) {
return new ArrayList<>();
}
List<FileMeta> list = new ArrayList<>();
for(MultipartFile file : multipartFiles) {
list.add(upload(file.getInputStream(), file.getOriginalFilename()));
}
return list;
}
private FileMeta upload(InputStream inputStream, String fileName) {
// 每个文件都开一次http,很浪费网络资源,如果对时间有要求,可以优化为传一堆过去
GiftResponse response = sendRequest(inputStream, fileName, GiftResponse.class);
return FileMeta.buildResponse(response, fileName);
}
private <V> V sendRequest(InputStream inputStream, String fileName, Class<V> clazz) {
try (final CloseableHttpClient httpClient = HttpClients.createDefault()) {
// 构建传参体
MultipartEntityBuilder builder = MultipartEntityBuilder.create();
builder.setMode(HttpMultipartMode.RFC6532);
builder.addPart("filecontent", new InputStreamBody(inputStream, fileName));
// 构建http连接体
HttpPost request = new HttpPost(giftUrl + nameSpace + "/" + UUID.randomUUID().toString());
request.setEntity(builder.build());
HttpResponse response = httpClient.execute(request);
// 给一个报错的机会
if (response == null || response.getStatusLine().getStatusCode() != HttpStatus.SC_OK) {
throw new CaseServerException("与Gift交互失败", StatusCode.FILE_UPLOAD_ERROR);
}
V ret = getResponseEntity(response.getEntity(), clazz);
// 这里不要直接return getResponseEntity 因为jvm会先去执行finally(在这里是隐式httpClient.close()) http被关掉了,httpEntity也没了
// 也就是说HttpEntity随着HttpClient域的销毁而销毁,在销毁之前一定要消费掉内部数据
return ret;
} catch (Exception e) {
e.printStackTrace();
throw new CaseServerException("传直Gift失败,文件名=" + fileName, StatusCode.FILE_UPLOAD_ERROR);
}
}
使用SpringCloud集成的微服务调用工具Feign完成图片上传
使用SpringCloud提供的Feign工具其实本质也是帮我们封装好了HttpClient,并且可以自动实现被调用微服务集群的状态检测、负载均衡等高级功能,但是这也有一个前提,需要创建一个Gift微服务注册到本项目的注册中心上来。
1.使用Feign注入Gift微服务中的图片上传接口
2.在Controller层直接调用Gift接口
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(value = "/uploadAttachment")
public JSONObject uploadAttachment(MultipartHttpServletRequest request) throws IOException {
JSONObject retJson = new JSONObject();
List<MultipartFile> multipartFileList = request.getFiles("file");
JSONArray fileArray = new JSONArray();
for (MultipartFile multipartFile : multipartFileList) {
GiftResult giftResult = null;
String fileName = new File(multipartFile.getOriginalFilename()).getName();
try {
InputStream fin = multipartFile.getInputStream();
String fileKey = UUID.randomUUID().toString();
giftResult = giftService.upload("chefu_qa_file", fileKey, fileName, fin);
fin.close();
} catch (IOException e) {
e.printStackTrace();
retJson.put("success", 0);
}
FileMeta fileMeta = new FileMeta();
fileMeta.setUrl(giftResult.getDownloadUrl());
fileMeta.setName(fileName);
fileArray.add(JSON.toJSON(fileMeta));
}
retJson.put("success", 1);
retJson.put("data", fileArray);
return retJson;
}
4.发现一个重要问题:不用Eureka也可以使用Feign
不用Eureka也可以使用Feign,据说dubbo使用的就是这种形式。以下代码步骤非常重要,都是我自己写的,没有将微服务注册到注册中心,却通过Feign成功访问了Gift微服务;
1.在项目配置文件application-dev.properties中配置图片保存地址,该地址是在滴滴Gift服务器中申请的:
1
2=http://100.69.238.155:8000/resource/
=chefu_qa_file2.在pom.xml文件中配置feign-httpclient依赖,因为feign默认采用jdk原生HttpURLConnection来发起http请求,会有很多输入输出问题,所以需要改用HttpClient来发送http请求。注意io.github.openfeign:feign-httpclient依赖版本一定要与io.github.openfeign:feign-core依赖版本相同:
1
2
3
4
5<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-httpclient</artifactId>
<version>10.7.4</version>
</dependency>3.Api接口,根据Gift开发团队提供的接口文档可知http请求四件套:
- url:http://100.69.238.155:8000/resource/chefu_qa_file;
- 请求方式:POST,且Content-Type必须为multipart/form-data;
- 请求参数:参数名为”filecontent”的单个MultipartFile类型文件;
- 响应体:json格式的map,我们需要的属性主要是文件保存路径”download_url”;
1
2
3
4
5
6
7
8(name = "gift", url = "${gift.upload.url}")
public interface GiftService {
//consumes用于指定Content-Type
(value="${gift.namespace.value}", consumes= MediaType.MULTIPART_FORM_DATA_VALUE, method = RequestMethod.POST)
Map<String,Object> upload(@RequestPart("filecontent") MultipartFile file);
}4.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
32
GiftService giftService;
/**
* 无注册中心使用feign
*/
(value = "/uploadFeignNoEureka", method = RequestMethod.POST)
public Response<?> uploadAttachment(("files") List<MultipartFile> multipartFiles) {
try {
// 传进来不仅要确保!=null 还要确保不为空
if (CollectionUtils.isEmpty(multipartFiles)) {
return Response.build(10086,"图片列表为空");
}
List<String> list = new ArrayList<>();
for (MultipartFile file : multipartFiles) {
Map<String,Object> giftResult = giftService.upload(file);
list.add(giftResult.get("download_url").toString());
}
return Response.success(list);
} catch (CaseServerException e) {
throw new CaseServerException(e.getLocalizedMessage(), e.getStatus());
} catch (Exception e) {
LOGGER.error("[上传文件出错]{}", e.getMessage());
e.printStackTrace();
return Response.build(StatusCode.FILE_IMPORT_ERROR.getStatus(), StatusCode.FILE_IMPORT_ERROR.getMsg());
}
}