如何在Feign Client拦截器中修改form-data格式请求体
Feign 拦截器修改form-data类型请求体问题
问题描述
我有部分接口的请求Content-Type为form-data类型,所有请求的请求体中都需要携带若干公共字段,当前请求实体类代码如下:
package com.example.demo.request; import com.fasterxml.jackson.annotation.JsonProperty; import java.math.BigDecimal; public class CreateNhanhProductRequest { private String nuctk; private Long storeId; private String name; private Integer typeId; private String parentIdName; private Long parentId; private String code; private String barcode; private BigDecimal importPrice; private BigDecimal vat; private BigDecimal price; private BigDecimal wholesalePrice; private BigDecimal oldPrice; private Integer status; private Object multiUnitItems; private Object comboItems; private Long categoryId; private Long internalCategoryId; private Long brandId; private Double shippingWeight; private String unit; private Integer length; private Integer width; private Integer height; private Long countryId; private Long warrantyAddress; private String warrantyPhone; private Long warranty; private String warrantyContent; private Integer firstRemain; private Integer importType; private Long depotId; private String supplierName; private Long supplierId; private Integer checkcopyImg; private Integer attributeCombinated; private String metaTitle; private String metaDescription; private Object metaKeywords; private Object tags; @JsonProperty("tag-suggest") private Object tagSuggest; public String getNuctk() { return nuctk; } public void setNuctk(String nuctk) { this.nuctk = nuctk; } public Long getStoreId() { return storeId; } public void setStoreId(Long storeId) { this.storeId = storeId; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Integer getTypeId() { return typeId; } public void setTypeId(Integer typeId) { this.typeId = typeId; } public String getParentIdName() { return parentIdName; } public void setParentIdName(String parentIdName) { this.parentIdName = parentIdName; } public Long getParentId() { return parentId; } public void setParentId(Long parentId) { this.parentId = parentId; } public String getCode() { return code; } public void setCode(String code) { this.code = code; } public String getBarcode() { return barcode; } public void setBarcode(String barcode) { this.barcode = barcode; } public BigDecimal getImportPrice() { return importPrice; } public void setImportPrice(BigDecimal importPrice) { this.importPrice = importPrice; } public BigDecimal getVat() { return vat; } public void setVat(BigDecimal vat) { this.vat = vat; } public BigDecimal getPrice() { return price; } public void setPrice(BigDecimal price) { this.price = price; } public BigDecimal getWholesalePrice() { return wholesalePrice; } public void setWholesalePrice(BigDecimal wholesalePrice) { this.wholesalePrice = wholesalePrice; } public BigDecimal getOldPrice() { return oldPrice; } public void setOldPrice(BigDecimal oldPrice) { this.oldPrice = oldPrice; } public Integer getStatus() { return status; } public void setStatus(Integer status) { this.status = status; } public Object getMultiUnitItems() { return multiUnitItems; } public void setMultiUnitItems(Object multiUnitItems) { this.multiUnitItems = multiUnitItems; } public Object getComboItems() { return comboItems; } public void setComboItems(Object comboItems) { this.comboItems = comboItems; } public Long getCategoryId() { return categoryId; } public void setCategoryId(Long categoryId) { this.categoryId = categoryId; } public Long getInternalCategoryId() { return internalCategoryId; } public void setInternalCategoryId(Long internalCategoryId) { this.internalCategoryId = internalCategoryId; } public Long getBrandId() { return brandId; } public void setBrandId(Long brandId) { this.brandId = brandId; } public Double getShippingWeight() { return shippingWeight; } public void setShippingWeight(Double shippingWeight) { this.shippingWeight = shippingWeight; } public String getUnit() { return unit; } public void setUnit(String unit) { this.unit = unit; } public Integer getLength() { return length; } public void setLength(Integer length) { this.length = length; } public Integer getWidth() { return width; } public void setWidth(Integer width) { this.width = width; } public Integer getHeight() { return height; } public void setHeight(Integer height) { this.height = height; } public Long getCountryId() { return countryId; } public void setCountryId(Long countryId) { this.countryId = countryId; } public Long getWarrantyAddress() { return warrantyAddress; } public void setWarrantyAddress(Long warrantyAddress) { this.warrantyAddress = warrantyAddress; } public String getWarrantyPhone() { return warrantyPhone; } public void setWarrantyPhone(String warrantyPhone) { this.warrantyPhone = warrantyPhone; } public Long getWarranty() { return warranty; } public void setWarranty(Long warranty) { this.warranty = warranty; } public String getWarrantyContent() { return warrantyContent; } public void setWarrantyContent(String warrantyContent) { this.warrantyContent = warrantyContent; } public Integer getFirstRemain() { return firstRemain; } public void setFirstRemain(Integer firstRemain) { this.firstRemain = firstRemain; } public Integer getImportType() { return importType; } public void setImportType(Integer importType) { this.importType = importType; } public Long getDepotId() { return depotId; } public void setDepotId(Long depotId) { this.depotId = depotId; } public String getSupplierName() { return supplierName; } public void setSupplierName(String supplierName) { this.supplierName = supplierName; } public Long getSupplierId() { return supplierId; } public void setSupplierId(Long supplierId) { this.supplierId = supplierId; } public Integer getCheckcopyImg() { return checkcopyImg; } public void setCheckcopyImg(Integer checkcopyImg) { this.checkcopyImg = checkcopyImg; } public Integer getAttributeCombinated() { return attributeCombinated; } public void setAttributeCombinated(Integer attributeCombinated) { this.attributeCombinated = attributeCombinated; } public String getMetaTitle() { return metaTitle; } public void setMetaTitle(String metaTitle) { this.metaTitle = metaTitle; } public String getMetaDescription() { return metaDescription; } public void setMetaDescription(String metaDescription) { this.metaDescription = metaDescription; } public Object getMetaKeywords() { return metaKeywords; } public void setMetaKeywords(Object metaKeywords) { this.metaKeywords = metaKeywords; } public Object getTags() { return tags; } public void setTags(Object tags) { this.tags = tags; } public Object getTagSuggest() { return tagSuggest; } public void setTagSuggest(Object tagSuggest) { this.tagSuggest = tagSuggest; } @Override public String toString() { return "CreateNhanhProductRequest{" + "nuctk='" + nuctk + '\'' + ", storeId=" + storeId + ", name='" + name + '\'' + ", typeId=" + typeId + ", parentIdName='" + parentIdName + '\'' + ", parentId=" + parentId + ", code='" + code + '\'' + ", barcode='" + barcode + '\'' + ", importPrice=" + importPrice + ", vat=" + vat + ", price=" + price + ", wholesalePrice=" + wholesalePrice + ", oldPrice=" + oldPrice + ", status=" + status + ", multiUnitItems=" + multiUnitItems + ", comboItems=" + comboItems + ", categoryId=" + categoryId + ", internalCategoryId=" + internalCategoryId + ", brandId=" + brandId + ", shippingWeight=" + shippingWeight + ", unit='" + unit + '\'' + ", length=" + length + ", width=" + width + ", height=" + height + ", countryId=" + countryId + ", warrantyAddress=" + warrantyAddress + ", warrantyPhone='" + warrantyPhone + '\'' + ", warranty=" + warranty + ", warrantyContent='" + warrantyContent + '\'' + ", firstRemain=" + firstRemain + ", importType=" + importType + ", depotId=" + depotId + ", supplierName='" + supplierName + '\'' + ", supplierId=" + supplierId + ", checkcopyImg=" + checkcopyImg + ", attributeCombinated=" + attributeCombinated + ", metaTitle='" + metaTitle + '\'' + ", metaDescription='" + metaDescription + '\'' + ", metaKeywords=" + metaKeywords + ", tags=" + tags + ", tagSuggest=" + tagSuggest + '}'; } // private Multi imageUpload; }
我需要为每一个请求的请求体统一添加相同的nuctk字段值,但由于请求使用formData格式,无法在拦截器中将请求体转换为对象进行修改。
当前Feign Client代码如下:
package com.example.demo.client; import com.example.demo.config.NhanhPageFeignClientInterceptor; import com.example.demo.request.CreateNhanhProductRequest; import org.springframework.cloud.openfeign.FeignClient; import org.springframework.http.MediaType; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; @FeignClient( value = "nhanhPage", url = "https://nhanh.vn/product", configuration = NhanhPageFeignClientInterceptor.class ) public interface NhanhProductV1Client { @PostMapping(value = "/item/add", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) String createProduct(@RequestBody CreateNhanhProductRequest request); }
当前编写的拦截器代码如下:
package com.example.demo.config; import com.example.demo.request.CreateNhanhProductRequest; import com.fasterxml.jackson.databind.ObjectMapper; import feign.RequestInterceptor; import feign.form.FormData; import feign.form.FormEncoder; import feign.form.spring.SpringFormEncoder; import org.springframework.context.annotation.Bean; import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; import java.io.IOException; public class NhanhPageFeignClientInterceptor { @Bean public RequestInterceptor requestInterceptor() { return requestTemplate -> { String bodyTemplate = requestTemplate.bodyTemplate(); byte[] body = requestTemplate.body(); FormData formData = new FormData(MediaType.MULTIPART_FORM_DATA_VALUE, "data" , body); requestTemplate.header(HttpHeaders.COOKIE, "Some cookie"); }; } }
需要解决的问题:如何在Feign Client拦截器中正确修改form-data类型的请求体?
解决方案
不建议在RequestInterceptor里直接修改已经编码完成的multipart请求体:拦截器执行时,请求体已经被SpringFormEncoder序列化为带boundary分隔符的二进制字节流,强行解析、拼接字节很容易破坏格式,导致服务端解析失败。
推荐用自定义编码器的方案实现,在编码阶段就注入公共字段,稳定性最高:
- 继承
SpringFormEncoder自定义表单编码器,在编码前给请求对象统一赋值公共字段
import com.example.demo.request.CreateNhanhProductRequest; import feign.RequestTemplate; import feign.codec.EncodeException; import feign.form.spring.SpringFormEncoder; import java.lang.reflect.Type; public class NhanhFormEncoder extends SpringFormEncoder { public NhanhFormEncoder() { super(); } @Override public void encode(Object object, Type bodyType, RequestTemplate template) throws EncodeException { // 匹配需要处理的请求对象,注入公共字段 if (object instanceof CreateNhanhProductRequest) { CreateNhanhProductRequest req = (CreateNhanhProductRequest) object; // 公共nuctk值可以从配置、线程上下文等位置读取 req.setNuctk("你的公共nuctk固定值"); } // 调用原生编码逻辑完成form-data序列化 super.encode(object, bodyType, template); } }
- 在Feign配置类中注册自定义编码器,替换默认的表单编码器
import feign.RequestInterceptor; import feign.codec.Encoder; import org.springframework.context.annotation.Bean; import org.springframework.http.HttpHeaders; public class NhanhPageFeignClientInterceptor { @Bean public RequestInterceptor requestInterceptor() { return requestTemplate -> { // 保留原有的cookie设置逻辑 requestTemplate.header(HttpHeaders.COOKIE, "Some cookie"); }; } @Bean public Encoder feignFormEncoder() { return new NhanhFormEncoder(); } }
如果有多个请求对象需要注入公共字段,可以抽离公共接口判断类型,不用每个对象单独写判断逻辑。
注意:不要尝试直接拼接二进制字节修改multipart请求,multipart格式对分隔符、换行符要求非常严格,手动拼接大概率出现格式错误。
内容的提问来源于stack exchange,提问作者Nguyễn Minh
相关产品推荐
相关产品推荐

