1.Feign框架需要集成模块feign-form才能支持文件上传的消息体格式。
2.不论是独立使用Feign,还是使用Spring Cloud Feign,下载文件时的返回值都必须为feign.Response类型。
添加依赖
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>3.3.1</version>
</dependency>
复杂文件上传接口
@ApiOperation("文件上传")
@PostMapping(value = "/upload", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
@ResponseBody
public ResultEx<InnerUploadVo> tokenUpload(@RequestPart(value = "uploadFile") MultipartFile uploadFile, @ModelAttribute InnerUploadDto dto) {
CommonUploadDto commonUploadDto = new CommonUploadDto();
commonUploadDto.setApp(dto.getApp());
commonUploadDto.setUserId(loginUserInfoHelper.getUserId(false));
commonUploadDto.setBizKey(dto.getBizKey());
commonUploadDto.setFileType(dto.getFileType());
ResultEx<InnerUploadVo> innerUploadVoResultEx = fileInfoFacade.upload(uploadFile, commonUploadDto);
if (innerUploadVoResultEx.isFailed()) {
return innerUploadVoResultEx;
}
InnerUploadVo innerUploadVo = innerUploadVoResultEx.getData();
innerUploadVo.setFileUrl(contentProperties.getBaseUrl() + File.separator + innerUploadVo.getFile());
return ResultEx.ok(innerUploadVo);
}
OKHttp实现文件上传
OkHttpClient client = new OkHttpClient().newBuilder()
.build();
MediaType mediaType = MediaType.parse("multipart/form-data");
RequestBody body = new MultipartBody.Builder().setType(MultipartBody.FORM)
.addFormDataPart("uploadFile", "D:/112.jpg",
RequestBody.create(MediaType.parse("application/octet-stream"),
new File("D:/112.jpg")))
.build();
Request request = new Request.Builder()
.url("http://ip:port/inner_file/upload?app=CIPS&fileType=ID-CARD-FRONT&bizKey=salary")
.method("POST", body)
.addHeader("Content-Type", "multipart/form-data")
.build();
Response response = client.newCall(request).execute();
System.err.println("response: " + response.body().string());
文件服务下载功能
@ApiOperation("文件下载")
@RequestMapping(value = "/download/{fileNo}", method = {RequestMethod.POST, RequestMethod.GET})
public ResultEx download(@ApiParam(name = "文件编号") @PathVariable("fileNo") String fileNo, HttpServletResponse response, HttpServletRequest request) {
String realFileNo = getRealFileNo(fileNo);
FileInfo fileInfo = fileInfoFacade.getByFileNo(realFileNo);
if (ObjectUtil.isEmpty(fileInfo)) {
return ResultEx.makeResult(ResultCode.DATA_NOT_EXISTS);
}
response.setCharacterEncoding(request.getCharacterEncoding());
ContentTypeEnum contentTypeEnum = ApiEnum.parse(ContentTypeEnum.class, fileInfo.getSuffix().toLowerCase());
if (ObjectUtil.isEmpty(contentTypeEnum)) {
contentTypeEnum = ContentTypeEnum.DEFAULT;
}
response.setContentType(contentTypeEnum.getText());
FileInputStream fis = null;
try {
if (contentTypeEnum.getValue().equals(ContentTypeEnum.DEFAULT.getValue())) {
response.setHeader("Content-Disposition", "attachment; filename=" + URLEncoder.encode(fileInfo.getFileName(), "utf-8"));
} else {
response.setHeader("Content-Disposition", "inline; filename=" + URLEncoder.encode(fileInfo.getFileName(), "utf-8"));
}
File file = new File(fileInfo.getFileUri());
fis = new FileInputStream(file);
IOUtils.copy(fis, response.getOutputStream());
response.flushBuffer();
} catch (Exception e) {
e.printStackTrace();
return ResultEx.fail();
} finally {
if (fis != null) {
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return ResultEx.ok();
}
/**
* 获取文件真实编号
*/
private String getRealFileNo(String fileNo) {
if (fileNo.contains(".")) {
return fileNo.substring(0, fileNo.lastIndexOf("."));
}
return fileNo;
}
OKHttp实现文件下载
public static void main(String[] args) throws IOException {
// 全是OKhttp中的类定义
OkHttpClient client = new OkHttpClient().newBuilder()
.build();
MediaType mediaType = MediaType.parse("text/plain");
RequestBody body = RequestBody.create(mediaType, "");
Request request = new Request.Builder()
.url("http://ip:port/inner_file/download/2cb5005324b8726ab9f02cba18a5bf51")
.method("POST", body)
.build();
Response response = client.newCall(request).execute();
byte[] bytes = response.body().bytes();
String filePath = "E:/";
String fileName = "test.jpg";
fileFromBytes(bytes, filePath, fileName);
String value = "1.0";
if (StringUtils.isNotEmpty(value)) {
System.err.println(value.matches("^([1-9][0-9]*)+(.[0-9]{1,2})?$"));
}
}
public static File fileFromBytes(byte[] bytes, String filePath, String fileName) {
BufferedOutputStream bos = null;
FileOutputStream fos = null;
File file = null;
try {
file = new File(filePath + fileName);
if (!file.getParentFile().exists()) {
//文件夹不存在 生成
file.getParentFile().mkdirs();
}
fos = new FileOutputStream(file);
bos = new BufferedOutputStream(fos);
bos.write(bytes);
} catch (Exception e) {
e.printStackTrace();
} finally {
if (bos != null) {
try {
bos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (fos != null) {
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return file;
}
文件信息类
import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.Version;
import com.baomidou.mybatisplus.annotation.TableId;
import java.time.LocalDateTime;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.TableLogic;
import com.baomidou.mybatisplus.annotation.TableField;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
@Data
@EqualsAndHashCode(callSuper = true)
@Accessors(chain = true)
@TableName("file_info")
public class FileInfo extends DataObjectBase {
private static final long serialVersionUID = 1L;
/**
* 唯一性Id
*/
@TableId(value = "id", type = IdType.AUTO)
private Long id;
/**
* 业务编码CODE
*/
@TableField("biz_type")
private String bizType;
/**
* 业务KEY
*/
@TableField("biz_key")
private String bizKey;
/**
* 文件类型
*/
@TableField("file_type")
private String fileType;
/**
* 附件编号
*/
@TableField("file_no")
private String fileNo;
/**
* 附件后缀
*/
private String suffix;
/**
* 附件URI
*/
@TableField("file_uri")
private String fileUri;
/**
* 文件名
*/
@TableField("file_name")
private String fileName;
/**
* 文件大小
*/
private Long size;
/**
* 文件来源应用CODE
*/
@TableField("app_code")
private String appCode;
/**
* 文件存放渠道
*/
@TableField("channel_code")
private String channelCode;
/**
* 创建时间
*/
@TableField(value = "create_time", fill = FieldFill.INSERT)
private LocalDateTime createTime;
/**
* 更新时间
*/
@TableField(value = "update_time", fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;
/**
* 删除标识
*/
@TableLogic
private boolean deleted;
}
类DataObjectBase
public class DataObjectBase implements Serializable {
public DataObjectBase() {
}
public String toString() {
return JSONObject.toJSONString(this);
}
}
feign声明
@FeignClient(name = "CNT-CONTENT-SERVICE", configuration = FeignConfig.class, fallbackFactory = ContentFallbackFactory.class)
public interface ContentFeign {
/**
* 下载
*/
@GetMapping(value = "/inner_file/download/{fileNo}", consumes = MediaType.APPLICATION_JSON_UTF8_VALUE)
Response download(@PathVariable("fileNo") String fileNo);
}
callback
@Component
@Slf4j
public class ContentFallbackFactory implements FallbackFactory<ContentFeign> {
private static final Logger LOGGER = LoggerFactory.getLogger(ContentFallbackFactory.class);
@Override
public ContentFeign create(Throwable cause) {
return new ContentFeign(){
@Override
public Response download(String fileNo) {
// 回调策略实现
return null;
}
};
}
}
其他微服务单元(第三方服务)实现
@RequestMapping(value = "/credential/download", method = {RequestMethod.POST, RequestMethod.GET})
@ApiOperation("下載文件")
public void downloadFile(@RequestParam("fileNo") String fileNo, @RequestParam(value = "suffix", name = "文件后缀,包括点") String suffix,HttpServletResponse response) throws UnsupportedEncodingException {
Response download = contentFeign.download(fileNo);
List<String> suffixes = Arrays.asList(".jpg", ".pdf");
if (!suffixes.contains(suffix.toLowerCase())) {
throw new RuntimeException("无法下载,不支持的文件类型!");
}
response.setHeader("content-type", "multipart/form-data");
response.setContentType("application/binary;charset=UTF-8");
response.setHeader("Content-Disposition", "attachment; filename=" + URLEncoder.encode(fileNo + suffix, "UTF-8"));
response.setHeader("Pragma", "no-cache");
response.setHeader("Cache-Control", "no-cache");
response.setDateHeader("Expires", 0);
byte[] buff = new byte[1024];
//创建缓冲输入流
BufferedInputStream bis = null;
OutputStream outputStream = null;
try {
outputStream = response.getOutputStream();
bis = new BufferedInputStream(download.body().asInputStream());
int read = bis.read(buff);
// 通过while循环写入到指定了的文件中
while (read != -1) {
outputStream.write(buff, 0, buff.length);
outputStream.flush();
read = bis.read(buff);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (bis != null) {
try {
bis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (outputStream != null) {
try {
outputStream.close();
} catch (IOException e) {
e.printStackTrace();
//出现异常返回给页面失败的信息
} finally {
if (bis != null) {
try {
bis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
try {
outputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
在本地测试,生产环境对应的服务都有使用
OKHTTP 上传文件 formdata
/**
* 写注释是个好习惯
*
* @param mFile
* @param accountIndex
* @param exportType
* @param clear
* @param email
* @param dimensions
* @return
* @throws IOException
*/
public String upload(MultipartFile mFile, Integer accountIndex, String exportType,
Boolean clear, String email, String dimensions) throws IOException {
// 这里是MultipartFile转File的过程
File file = new File(Objects.requireNonNull(mFile.getOriginalFilename()));
FileUtils.copyInputStreamToFile(mFile.getInputStream(), file);
// url接口路径
String url = "http://localhost:8080/upload";
// file是要上传的文件 File() 这边我上传的是excel,其他类型可以自己改这个parse
RequestBody fileBody = RequestBody.create(MediaType.parse("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"), file);//这边是把file写进来,也有写路径的,但我这边是写file文件,parse不行的话可以直接改这个"multipart/form-data"
// 创建OkHttpClient实例,设置超时时间
OkHttpClient okHttpClient = new OkHttpClient.Builder()
.connectTimeout(60L, TimeUnit.SECONDS)
.writeTimeout(60L, TimeUnit.SECONDS)
.readTimeout(60L, TimeUnit.SECONDS)
.build();
// 不仅可以支持传文件,还可以在传文件的同时,传参数
MultipartBody requestBody = new MultipartBody.Builder()
.setType(MultipartBody.FORM) // 设置传参为form-data格式
.addFormDataPart("account_index", String.valueOf(accountIndex))
.addFormDataPart("export_type", exportType)
.addFormDataPart("clear", String.valueOf(clear))
.addFormDataPart("email", email)
.addFormDataPart("dimensions", dimensions)
.addFormDataPart("file", file.getName(), fileBody) // 中间参数为文件名
.build();
// 构建request请求体,有需要传请求头自己加
Request request = new Request.Builder()
.url(url)
.post(requestBody)
.build();
Response response = null;
String result = "";
try {
// 发送请求
response = okHttpClient.newCall(request).execute();
result = response.body().string();
log.info(url + "发送请求结果:" + result);
if (!response.isSuccessful()) {
log.info("请求失败");
return "请求失败";
}
response.body().close();
} catch (IOException e) {
log.error(e.getMessage());
}
// 会在本地产生临时文件,用完后需要删除
if (file.exists()) {
file.delete();
}
return result;
}
然后controller层的传参需要用@RequestParam或者直接一个请求的实体类,如果使用实体类,千万不要加@RequestBody,不然结合上传文件会失效,上传文件使用@RequestPart("file") MultipartFile file进行传参
(@RequestPart("file") MultipartFile file,
@RequestParam("accountIndex") Integer accountIndex,
@RequestParam("exportType") String exportType,
@RequestParam(value = "clear", required = false) Boolean clear,
@RequestParam("email") String email,
@RequestParam(value = "dimensions", required = false) String dimensions)
入参:示例如上,或者
(@RequestPart("file") MultipartFile file, RequestVo req)
okhttp response leak
WARNING: A connection to https://help.helpling.com/ was leaked. Did you forget to close a response body?
中文response,header中乱码处理
response.setHeader("filename", java.net.URLEncoder.encode(simpleName, "UTF-8"));
解码:
fileName = java.net.URLDecoder.decode(fileName, "UTF-8");