Java微服务RestTemplate跨服务文件上传下载

说明:微服务架构使用springcloud

需求1:A服务是业务,B服务是功能【文件上传】,用户通过浏览器上传文件file,上传访问A服务,A服务调用B服务的上传功能

需求2:A服务是业务,B服务是功能【文件下载】,用户通过浏览器下载文件file,下载访问A服务,A服务调用B服务的下载功能

话不多说,上代码:

A服务【A调用B】:

import com.sys.common.Result;
import io.swagger.annotations.*;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.multipart.MultipartFile;

import javax.imageio.ImageIO;
import javax.servlet.http.HttpServletResponse;
import java.awt.image.BufferedImage;
import java.io.*;
import java.util.Objects;


/**
 * 文件服务
 * @author 李庆伟
 * @date 2021/8/31 14:53
 */
@Slf4j
@RequestMapping("web/files")
@RestController
@Api(value = "文件服务", tags = "文件服务")
public class FilesWebController {

    @Autowired
    private RestTemplate restTemplate;

    private static final String SERVER_TRILATERAL_9400 = "http://SERVER-TRILATERAL-9400";

    /**
     * 文件上传
     * [id]
     * @return {@link Result}
     * @throws
     * @author 李庆伟
     * @date 2021/7/29 9:56
     */
    @PostMapping("upFile")
    @ApiOperation(value = "文件上传", notes = "文件上传", produces = "application/json")
    @ApiResponses({
            @ApiResponse(code = 0, message = "id:文件id"),
    })//@ApiParam(required = true, value = "上传的文件") @RequestParam(value = "file" ,required = true)
    public Result upFile(MultipartFile file){
        // 封装参数,千万不要替换为Map与HashMap,否则参数无法传递
        MultiValueMap<String, Object> paramMap = new LinkedMultiValueMap<String, Object>();
        paramMap.add("file",file.getResource());
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.MULTIPART_FORM_DATA);
        //headers.add();在请求头中设置传递信息
        HttpEntity<MultiValueMap<String, Object>> httpEntity = new HttpEntity<MultiValueMap<String, Object>>(paramMap,headers);
        Result result = restTemplate.postForObject(SERVER_TRILATERAL_9400+"/files/upFile",httpEntity,Result.class);
        return result;
    }


    /**
     * 图片展示[跨服务调用]
     * [id]
     * @return {@link Result}
     * @throws
     * @author 李庆伟
     * @date 2021/9/6 19:15
     */
    @GetMapping("downFileCross")
    public void downFileCross(@ApiParam(required = true, value = "图片id") @RequestParam String id, HttpServletResponse response){
        // 封装参数,千万不要替换为Map与HashMap,否则参数无法传递
        ResponseEntity<byte[]> rsp = restTemplate.getForEntity(SERVER_TRILATERAL_9400+"/files/downFileCross?id="+id,byte[].class);
        BufferedInputStream bis = null;
        if (Objects.nonNull(rsp)) {
            byte[] body = rsp.getBody();
            if (Objects.nonNull(body)) {
                try {
                    InputStream is = new ByteArrayInputStream(body);
                    BufferedImage image = ImageIO.read(is);
                    //判断是否是图片 1.是预览  2.下载
                    if(image!=null){
                        //打开本地文件流
                        //激活下载操作
                        response.setContentType("image/jpeg");
                        ImageIO.write(image, "JPEG", response.getOutputStream());
                    } else {
                        log.info("文件大小==>:{}", body.length);
                        String filename = "李庆伟.jpg";//这里应该查询出文件名称,为了方便就随便命名了
                        response.setContentType("application/octet-stream");
                        response.setHeader("Content-disposition", "attachment; filename=" + new String(filename.getBytes("utf-8"), "ISO8859-1"));

                        bis = new BufferedInputStream(is);
                        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();
                        }
                    }
                }
            }
        }
    }

}

B服务【被A调用】

控制层:
/**
 * 文件上传
 * [file, request]
 * @return {@link Result}
 * @throws
 * @author 李庆伟
 * @date 2021/9/4 18:05
 */
@PostMapping("upFile")
@ApiOperation(value = "文件上传", notes = "文件上传", produces = "application/json")
@ApiResponses({
        @ApiResponse(code = 0, message = "id:文件id"),
})
public Result upFile(@ApiParam(required = true, value = "上传的文件") @RequestParam(value = "file" ,required = true) MultipartFile file){
    CClientFile cClientFile = fileService.upFile(file);
    return Result.success(cClientFile);//返回上传成功文件信息
}
/**
 * 下载和预览[支持跨服务调用]
 * [id]
 * @return {@link Result}
 * @throws
 * @author 李庆伟
 * @date 2021/9/4 20:15
 */
@GetMapping("downFileCross")
@ApiOperation(value = "文件下载[支持跨服务调用]", notes = "文件下载[支持跨服务调用]", produces = "application/json")
public ResponseEntity<byte[]> downFileCross(@ApiParam(required = true, value = "文件id") @RequestParam(value = "id" ,required = true) String id){
    return fileService.downFileCross(id);//返回上传成功文件信息
}

实现类:

/**
 * 文件上传业务
 * @return {@link String}
 * @throws
 * @author 李庆伟
 * @date 2021/9/4 19:23
 */
@Transactional
public CClientFile upFile(MultipartFile file) {
    CClientFile cClientFile = FileUtil.upFile(file);
    if(cClientFile == null){
        throw new MyException(2001,"文件为空文件");
    }
    String id = IdWorker.makeIdWorker();
    cClientFile.setId(id);
    cClientFile.setCreateDate(new Date());
    fileMapper.insert(cClientFile);
    return cClientFile;
}
/**
 * 文件下载[支持微服务]
 * [id]
 * @return {@link ResponseEntity<byte[]>}
 * @throws
 * @author 李庆伟
 * @date 2021/9/7 16:40
 */
public ResponseEntity<byte[]> downFileCross(String id) {
    CClientFile cClientFile = fileMapper.selectById(id);
    if(cClientFile != null){
        return FileUtil.downFileCross(cClientFile);
    }
    return null;
}

工具类:

import com.files.model.CClientFile;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.multipart.MultipartFile;

import javax.imageio.ImageIO;
import javax.servlet.http.HttpServletResponse;
import java.awt.image.BufferedImage;
import java.io.*;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.UUID;


/**
 * @author 李庆伟
 * @date 2021/9/4 18:08
 */
@Slf4j
public class FileUtil {

    private static final String rootUtl = System.getProperty("os.name").toLowerCase().startsWith("win")?"D:/file_db/":"/file_db/";

    /**
     * 文件上传
     * [file]
     * @return {@link String}
     * @throws
     * @author 李庆伟
     * @date 2021/9/4 18:15
     */
    public static CClientFile upFile(MultipartFile file){
        CClientFile cClientFile = new CClientFile();
        //如果文件不为空
        if (!file.isEmpty()) {
            String oldFileName = file.getOriginalFilename();

            String realPath = rootUtl + new SimpleDateFormat("yyyy/MM/dd/hh/mm/ss").format(new Date())+"/"+ UUID.randomUUID().toString().replaceAll("-", "");
            File f = new File(realPath);
            if(!f.exists()){
                f.mkdirs();//如果目录不存在,创建目录
            }
            try {
                file.transferTo(new File(realPath + File.separator + oldFileName));//把文件写入目标文件地址
                //可以在这完成数据库路径的存储
            } catch (Exception e) {
                e.printStackTrace();
            }
            cClientFile.setFileName(oldFileName);//封装文件名称
            cClientFile.setFileUrl(realPath + "/" + oldFileName);//封装文件路径
            cClientFile.setFileSize(file.getSize());//封装文件大小
            return cClientFile;//返回到成功页面
        }else {
            return null;//返回到失败的页面
        }
    }
    /**
     *
     * 跨服务下载
     * @return {@link }
     * @throws
     * @author 李庆伟
     * @date 2021/9/7 16:26
     */
    public static ResponseEntity<byte[]> downFileCross(CClientFile cClientFile){
        String path = cClientFile.getFileUrl();
        HttpHeaders headers = new HttpHeaders();
        ResponseEntity<byte[]> entity = null;
        BufferedInputStream bis;
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        byte[] buf = new byte[10240];
        try {
            bis = new BufferedInputStream(new FileInputStream(path));
            while (true) {
                int len = bis.read(buf);
                if (len < 0) {
                    break;
                }
                bos.write(buf, 0, len);
            }
            //将输出流转为字节数组,通过ResponseEntity<byte[]>返回
            byte[] b = bos.toByteArray();
            log.info("接收到的文件大小为:" + b.length);
            HttpStatus status = HttpStatus.OK;
            String fileName = cClientFile.getFileName();
            headers.setContentType(MediaType.ALL.APPLICATION_OCTET_STREAM);
            headers.add("Content-Disposition", "attachment;filename=" + fileName);
            entity = new ResponseEntity<>(b, headers, status);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (bos != null) {
                try {
                    bos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return entity;


    }
}

文件实体类:

import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;

import java.io.Serializable;
import java.util.Date;

/**
 * 文件实体
 * @author 李庆伟
 * @date 2021/7/19 17:00
 */
@Data
@TableName("c_client_file")
public class CClientFile implements Serializable {

    private static final long serialVersionUID = 1L;

    @TableId
    private String id;//主键
    @ApiModelProperty(value = "文件名称")
    private String fileName;//文件名称
    @ApiModelProperty(value = "文件地址")
    private String fileUrl;//文件地址
    @ApiModelProperty(value = "文件大小")
    private long fileSize;//文件大小
    private String createUser;//创建人
    private Date createDate;//创建时间

    //@TableField(exist = false)
}

在说明一下Result是返回的结果集,这里就不写了,之前的文章中有结果集说明

最后完。。。。。。。。。。

<think>好的,我现在要解决用户的问题:使用RestTemplate上传文件时,中文文件名出现乱码的情况。用户提到在Spring环境下,如何解决这个问题。首先,我需要回忆一下RestTemplate的基本使用和可能涉及的编码问题。 首先,用户提到通过Zuul上传文件时,虽然微服务接收到的文件名没有乱码,但到了文件中心又出现乱码。这可能涉及多个环节的编码处理,比如请求的构建、传输过程中的编码设置,以及接收方的解码方式。需要逐步排查每个环节。 接下来,用户提供的引用内容中提到,在RestTemplate的doExecute方法中可能存在问题。这说明可能需要自定义RestTemplate的配置,特别是与HTTP请求相关的部分,比如消息转换器和编码设置。 首先,我应该考虑RestTemplate在发送请求时使用的默认编码。通常,默认的字符编码可能不是UTF-8,导致中文文件名在传输过程中变成乱码。需要检查MultipartFile上传时的Content-Type是否正确设置了charset=UTF-8。如果Content-Type没有正确指定编码,接收方可能无法正确解析文件名。 然后,引用[2]和[3]提到了解决方案,包括设置请求头、修改消息转换器以及处理字符串编码。例如,使用StringHttpMessageConverter时,默认的字符集可能不是UTF-8,需要显式设置为UTF-8。同时,在构建MultipartBody时,需要确保每个部分的Content-Disposition头正确包含filename*=UTF-8''这样的格式,以指定UTF-8编码。 可能的解决方案步骤包括: 1. 自定义RestTemplate的配置,替换默认的StringHttpMessageConverter,设置字符集为UTF-8。 2. 在构建MultiValueMap时,手动设置Content-Disposition头部,使用UTF-8编码的文件名。 3. 确保接收方的服务也正确支持UTF-8解码,可能需要检查接收方的处理逻辑,比如使用@RequestPart注解并配置字符集。 需要验证这些步骤是否覆盖了所有可能导致乱码的环节,比如请求头、消息转换器、多部分请求的编码等。同时,还要考虑是否需要在Zuul网关进行额外的编码配置,确保请求在通过网关时不会改变编码。 另外,可能需要处理文件名的URL编码,使用MimeUtility.encodeText()方法对文件名进行编码,确保特殊字符和中文字符被正确传输。这可以避免因特殊字符导致的解析错误或乱码。 最后,建议用户检查接收方的服务配置,比如是否设置了CharacterEncodingFilter,或者是否在application.properties中配置了spring.http.encoding.force-response=true等参数,确保整个请求链的编码一致。 总结起来,解决方案需要从RestTemplate的配置、多部分请求的构建、编码设置以及接收方的处理几个方面入手,确保每个环节都使用UTF-8编码,从而避免中文文件名乱码的问题。</think>### RestTemplate上传文件中文名乱码解决方案 #### 问题原因分析 1. **编码不匹配**:RestTemplate默认使用`ISO-8859-1`编码处理请求头,导致中文字符无法正确解析[^2]。 2. **多部分请求构建问题**:文件上传时若未显式指定`filename*`的UTF-8编码格式,接收方可能无法识别[^1]。 --- #### 解决方案步骤 ##### 1. 自定义RestTemplate配置 通过注入`RestTemplate`并替换默认的`StringHttpMessageConverter`,强制使用UTF-8编码: ```java @Bean public RestTemplate restTemplate() { RestTemplate restTemplate = new RestTemplate(); // 替换StringHttpMessageConverter为UTF-8 restTemplate.getMessageConverters().removeIf(c -> c instanceof StringHttpMessageConverter); restTemplate.getMessageConverters().add(0, new StringHttpMessageConverter(StandardCharsets.UTF_8)); return restTemplate; } ``` ##### 2. 手动设置多部分请求编码 在构建文件上传请求时,**显式指定文件名编码**: ```java MultiValueMap<String, Object> body = new LinkedMultiValueMap<>(); body.add("file", new ByteArrayResource(file.getBytes()) { @Override public String getFilename() { // 使用RFC 5987规范编码文件名 return "filename*=UTF-8''" + URLEncoder.encode(originalFilename, StandardCharsets.UTF_8); } }); ``` ##### 3. 接收方统一解码 确保接收方服务配置UTF-8编码过滤器: ```java @Bean public FilterRegistrationBean<CharacterEncodingFilter> encodingFilter() { FilterRegistrationBean<CharacterEncodingFilter> bean = new FilterRegistrationBean<>(); bean.setFilter(new CharacterEncodingFilter("UTF-8", true)); return bean; } ``` --- #### 验证方法 1. **抓包检查请求头**:使用Wireshark或浏览器开发者工具,确认`Content-Disposition`头包含`filename*=UTF-8''`前缀。 2. **日志输出**:在接收方打印文件名,确认乱码已修复。 --- #### 扩展优化 - **全局编码配置**:在`application.properties`中添加: ```properties spring.http.encoding.charset=UTF-8 spring.http.encoding.enabled=true spring.http.encoding.force=true ``` - **Zuul网关兼容**:若通过Zuul代理,需同步配置Zuul的编码过滤器。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值