【AgileTC】_图片上传方法总结

【AgileTC】_图片上传方法总结

1.直接保存到本地文件夹(AgileTC开源版)

  1. 在项目配置文件application-dev.properties中配置图片保存地址:

    1
    web.upload-path=/Users/didi/littleforest/haha/
  2. 编写图片上传接口,请求方式为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
    //加载配置文件中的图片保存地址
    @Value("${web.upload-path}")
    private String uploadPath;

    @PostMapping(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
    @Configuration
    @Import(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
    13
    spring:
    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:22122
  • 5.Controller层,请求方式为Post,请求参数为单个文件(如果想接受文件数组,添加相关逻辑即可),响应体中包含文件保存路径:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    @Controller
    @RequestMapping("upload")
    public class UploadController {

    @Autowired
    private UploadService uploadService;

    @PostMapping("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
    @Service
    public class UploadService {

    //注入fastdfs-client
    @Autowired
    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
    gift.upload.url=http://100.69.238.155:8000/resource/
    gift.namespace.value=chefu_qa_file
  • 2.Controller层,请求方式为Post,请求参数为文件数组,响应体中包含文件保存路径:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    @RequestMapping(value = "/upload", method = RequestMethod.POST)
    public Response<?> uploadFileToGift(@RequestParam("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
    @Value("${gift.namespace.value}")
    private String nameSpace;

    @Value("${gift.upload.url}")
    private String giftUrl;

    @Override
    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
    @RequestMapping(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
    gift.upload.url=http://100.69.238.155:8000/resource/
    gift.namespace.value=chefu_qa_file
  • 2.在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
    @FeignClient(name = "gift", url = "${gift.upload.url}")
    public interface GiftService {

    //consumes用于指定Content-Type
    @RequestMapping(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
    @Autowired
    GiftService giftService;

    /**
    * 无注册中心使用feign
    */
    @RequestMapping(value = "/uploadFeignNoEureka", method = RequestMethod.POST)
    public Response<?> uploadAttachment(@RequestParam("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());
    }
    }