commit
						d166e425c0
					
				@ -0,0 +1,33 @@ | 
				
			||||
# 动态修改请求参数 | 
				
			||||
 | 
				
			||||
自1.14.0开始,zuul网关支持动态修改请求参数。即在网关修改客户端传递过来的参数,然后发送到微服务端。 | 
				
			||||
 | 
				
			||||
``` | 
				
			||||
客户端参数{"name": "jim"} --> zuul中修改为{"name": "Lucy"} --> 微服务端将收到{"name": "Lucy"} | 
				
			||||
``` | 
				
			||||
 | 
				
			||||
使用场景:客户端请求参数经过加密,在网关解密后,再次发送明文参数给微服务端 | 
				
			||||
 | 
				
			||||
- 如何使用 | 
				
			||||
 | 
				
			||||
在网关springboot启动函数中添加如下代码 | 
				
			||||
 | 
				
			||||
```java | 
				
			||||
public static void main(String[] args) { | 
				
			||||
    ApiConfig.getInstance().setZuulParameterFormatter(requestParams -> { | 
				
			||||
        // 获取biz_content | 
				
			||||
        JSONObject jsonObject = requestParams.getJSONObject(ParamNames.BIZ_CONTENT_NAME); | 
				
			||||
        // 修改biz_content中的值 | 
				
			||||
        jsonObject.put("name", "name修改了111"); | 
				
			||||
        jsonObject.put("remark", "remark修改了222"); | 
				
			||||
        // 重新设置biz_content | 
				
			||||
        requestParams.put(ParamNames.BIZ_CONTENT_NAME, jsonObject); | 
				
			||||
    }); | 
				
			||||
    SpringApplication.run(SopGatewayApplication.class, args); | 
				
			||||
} | 
				
			||||
 | 
				
			||||
``` | 
				
			||||
 | 
				
			||||
其中requestParams是客户端传递过来的参数,直接修改其中的值即可。 | 
				
			||||
 | 
				
			||||
更多参考:com.gitee.sop.gatewaycommon.zuul.filter.PreParameterFormatterFilter.java | 
				
			||||
@ -0,0 +1,18 @@ | 
				
			||||
package com.gitee.sop.gatewaycommon.manager; | 
				
			||||
 | 
				
			||||
import java.util.Map; | 
				
			||||
 | 
				
			||||
/** | 
				
			||||
 * 参数格式化 | 
				
			||||
 * | 
				
			||||
 * @author tanghc | 
				
			||||
 */ | 
				
			||||
public interface ParameterFormatter<T extends Map<String, Object>> { | 
				
			||||
 | 
				
			||||
    /** | 
				
			||||
     * 参数格式化,即动态修改请求参数 | 
				
			||||
     * | 
				
			||||
     * @param requestParams 原始请求参数,在此基础上追加或修改参数 | 
				
			||||
     */ | 
				
			||||
    void format(T requestParams); | 
				
			||||
} | 
				
			||||
@ -0,0 +1,42 @@ | 
				
			||||
package com.gitee.sop.gatewaycommon.zuul.filter; | 
				
			||||
 | 
				
			||||
import com.gitee.sop.gatewaycommon.param.ApiParam; | 
				
			||||
import com.gitee.sop.gatewaycommon.param.ParamNames; | 
				
			||||
import com.gitee.sop.gatewaycommon.zuul.ZuulContext; | 
				
			||||
import com.gitee.sop.gatewaycommon.zuul.param.ZuulParameterFormatter; | 
				
			||||
import com.gitee.sop.gatewaycommon.zuul.param.ZuulParameterUtil; | 
				
			||||
import com.netflix.zuul.context.RequestContext; | 
				
			||||
import com.netflix.zuul.exception.ZuulException; | 
				
			||||
import org.springframework.beans.factory.annotation.Autowired; | 
				
			||||
 | 
				
			||||
/** | 
				
			||||
 * 参数格式化过滤器,动态修改参数,此过滤器放在前面校验后面 | 
				
			||||
 * | 
				
			||||
 * @author tanghc | 
				
			||||
 */ | 
				
			||||
public class PreParameterFormatterFilter extends BaseZuulFilter { | 
				
			||||
 | 
				
			||||
    @Autowired(required = false) | 
				
			||||
    private ZuulParameterFormatter zuulParameterFormatter; | 
				
			||||
 | 
				
			||||
    @Override | 
				
			||||
    protected FilterType getFilterType() { | 
				
			||||
        return FilterType.PRE; | 
				
			||||
    } | 
				
			||||
 | 
				
			||||
    @Override | 
				
			||||
    protected int getFilterOrder() { | 
				
			||||
        return PRE_PARAMETER_FORMATTER_FILTER_ORDER; | 
				
			||||
    } | 
				
			||||
 | 
				
			||||
    @Override | 
				
			||||
    protected Object doRun(RequestContext requestContext) throws ZuulException { | 
				
			||||
        ApiParam apiParam = ZuulContext.getApiParam(); | 
				
			||||
        // 校验成功后进行参数转换
 | 
				
			||||
        if (zuulParameterFormatter != null) { | 
				
			||||
            ZuulParameterUtil.format(apiParam, zuulParameterFormatter::format); | 
				
			||||
            requestContext.addZuulRequestHeader(ParamNames.HEADER_VERSION_NAME, apiParam.fetchVersion()); | 
				
			||||
        } | 
				
			||||
        return null; | 
				
			||||
    } | 
				
			||||
} | 
				
			||||
@ -0,0 +1,10 @@ | 
				
			||||
package com.gitee.sop.gatewaycommon.zuul.param; | 
				
			||||
 | 
				
			||||
import com.alibaba.fastjson.JSONObject; | 
				
			||||
import com.gitee.sop.gatewaycommon.manager.ParameterFormatter; | 
				
			||||
 | 
				
			||||
/** | 
				
			||||
 * @author tanghc | 
				
			||||
 */ | 
				
			||||
public interface ZuulParameterFormatter extends ParameterFormatter<JSONObject> { | 
				
			||||
} | 
				
			||||
@ -0,0 +1,183 @@ | 
				
			||||
package com.gitee.sop.gatewaycommon.zuul.param; | 
				
			||||
 | 
				
			||||
import com.alibaba.fastjson.JSON; | 
				
			||||
import com.alibaba.fastjson.JSONObject; | 
				
			||||
import com.gitee.sop.gatewaycommon.bean.SopConstants; | 
				
			||||
import com.gitee.sop.gatewaycommon.util.RequestUtil; | 
				
			||||
import com.netflix.zuul.context.RequestContext; | 
				
			||||
import com.netflix.zuul.http.HttpServletRequestWrapper; | 
				
			||||
import com.netflix.zuul.http.ServletInputStreamWrapper; | 
				
			||||
import lombok.extern.slf4j.Slf4j; | 
				
			||||
import org.apache.commons.lang.StringUtils; | 
				
			||||
import org.springframework.cloud.netflix.zuul.util.RequestContentDataExtractor; | 
				
			||||
import org.springframework.http.HttpHeaders; | 
				
			||||
import org.springframework.http.HttpMethod; | 
				
			||||
import org.springframework.http.HttpOutputMessage; | 
				
			||||
import org.springframework.http.MediaType; | 
				
			||||
import org.springframework.http.converter.FormHttpMessageConverter; | 
				
			||||
import org.springframework.util.MultiValueMap; | 
				
			||||
import org.springframework.web.multipart.MultipartHttpServletRequest; | 
				
			||||
import org.springframework.web.multipart.commons.CommonsMultipartResolver; | 
				
			||||
 | 
				
			||||
import javax.servlet.ServletInputStream; | 
				
			||||
import javax.servlet.http.HttpServletRequest; | 
				
			||||
import java.io.ByteArrayOutputStream; | 
				
			||||
import java.io.IOException; | 
				
			||||
import java.io.OutputStream; | 
				
			||||
import java.io.UnsupportedEncodingException; | 
				
			||||
import java.net.URLEncoder; | 
				
			||||
import java.nio.charset.StandardCharsets; | 
				
			||||
import java.util.ArrayList; | 
				
			||||
import java.util.Collection; | 
				
			||||
import java.util.Collections; | 
				
			||||
import java.util.HashMap; | 
				
			||||
import java.util.List; | 
				
			||||
import java.util.Map; | 
				
			||||
import java.util.function.Consumer; | 
				
			||||
import java.util.stream.Collectors; | 
				
			||||
 | 
				
			||||
/** | 
				
			||||
 * zuul参数工具 | 
				
			||||
 * @author tanghc | 
				
			||||
 */ | 
				
			||||
@Slf4j | 
				
			||||
public class ZuulParameterUtil { | 
				
			||||
 | 
				
			||||
    private static FormHttpMessageConverter formHttpMessageConverter = new FormHttpMessageConverter(); | 
				
			||||
 | 
				
			||||
    /** | 
				
			||||
     * 格式化参数 | 
				
			||||
     * @param apiParam 请求的参数 | 
				
			||||
     * @param consumer 修改参数 | 
				
			||||
     * @param <T> 参数类型 | 
				
			||||
     */ | 
				
			||||
    public static <T extends Map<String, Object>> void format(T apiParam, Consumer<T> consumer) { | 
				
			||||
        RequestContext requestContext = RequestContext.getCurrentContext(); | 
				
			||||
        consumer.accept(apiParam); | 
				
			||||
        HttpServletRequest request = requestContext.getRequest(); | 
				
			||||
        String contentType = request.getContentType(); | 
				
			||||
        if (StringUtils.containsIgnoreCase(contentType, MediaType.APPLICATION_JSON_VALUE)) { | 
				
			||||
            String json = (apiParam instanceof JSONObject) ? | 
				
			||||
                    ((JSONObject) apiParam).toJSONString() | 
				
			||||
                    : JSON.toJSONString(apiParam); | 
				
			||||
            byte[] bytes = json.getBytes(StandardCharsets.UTF_8); | 
				
			||||
            requestContext.setRequest(new ChangeParamsHttpServletRequestWrapper(request, bytes)); | 
				
			||||
        } else if(StringUtils.containsIgnoreCase(contentType, MediaType.APPLICATION_FORM_URLENCODED_VALUE)) { | 
				
			||||
            List<String> list = new ArrayList<>(apiParam.size()); | 
				
			||||
            try { | 
				
			||||
                for (Map.Entry<String, Object> entry : apiParam.entrySet()) { | 
				
			||||
                    String key = entry.getKey(); | 
				
			||||
                    Object value = entry.getValue(); | 
				
			||||
                    if (value instanceof Collection) { | 
				
			||||
                        Collection collection = (Collection) value; | 
				
			||||
                        for (Object el : collection) { | 
				
			||||
                            list.add(key + "=" + URLEncoder.encode(String.valueOf(el), SopConstants.UTF8)); | 
				
			||||
                        } | 
				
			||||
                    } else { | 
				
			||||
                        list.add(key + "=" + URLEncoder.encode(String.valueOf(value), SopConstants.UTF8)); | 
				
			||||
                    } | 
				
			||||
                } | 
				
			||||
            } catch (UnsupportedEncodingException e) { | 
				
			||||
                log.error("字符集不支持", e); | 
				
			||||
            } | 
				
			||||
            String paramsStr = StringUtils.join(list, "&"); | 
				
			||||
            byte[] data = paramsStr.getBytes(StandardCharsets.UTF_8); | 
				
			||||
            requestContext.setRequest(new ChangeParamsHttpServletRequestWrapper(request, data)); | 
				
			||||
        } else if(RequestUtil.isMultipart(request)) { | 
				
			||||
            FormHttpOutputMessage outputMessage = new FormHttpOutputMessage(); | 
				
			||||
            try { | 
				
			||||
                // 转成MultipartRequest
 | 
				
			||||
                if (!(request instanceof MultipartHttpServletRequest)) { | 
				
			||||
                    CommonsMultipartResolver commonsMultipartResolver = new CommonsMultipartResolver(request.getServletContext()); | 
				
			||||
                    request = commonsMultipartResolver.resolveMultipart(request); | 
				
			||||
                } | 
				
			||||
                // 重写新的值
 | 
				
			||||
                MultiValueMap<String, Object> builder = RequestContentDataExtractor.extract(request); | 
				
			||||
                for (Map.Entry<String, Object> entry : apiParam.entrySet()) { | 
				
			||||
                    Object value = entry.getValue(); | 
				
			||||
                    if (value instanceof List) { | 
				
			||||
                        builder.put(entry.getKey(), (List)value); | 
				
			||||
                    } else { | 
				
			||||
                        builder.put(entry.getKey(), Collections.singletonList(String.valueOf(value))); | 
				
			||||
                    } | 
				
			||||
                } | 
				
			||||
                MediaType mediaType = MediaType.valueOf(request.getContentType()); | 
				
			||||
                // 将字段以及上传文件重写写入到流中
 | 
				
			||||
                formHttpMessageConverter.write(builder, mediaType, outputMessage); | 
				
			||||
                // 获取新的上传文件流
 | 
				
			||||
                byte[] data = outputMessage.getInput(); | 
				
			||||
 | 
				
			||||
                requestContext.setRequest(new ChangeParamsHttpServletRequestWrapper(request, data)); | 
				
			||||
                // 必须要重新指定content-type,因为此时的boundary已经发生改变
 | 
				
			||||
                requestContext.getZuulRequestHeaders().put("content-type", outputMessage.getHeaders().getContentType().toString()); | 
				
			||||
            } catch (Exception e) { | 
				
			||||
                log.error("修改上传文件请求参数失败, apiParam:{}", apiParam, e); | 
				
			||||
            } | 
				
			||||
        } else if(HttpMethod.GET.name().equalsIgnoreCase(request.getMethod())) { | 
				
			||||
            Map<String, List<String>> newParams = new HashMap<>(); | 
				
			||||
            for (Map.Entry<String, Object> entry : apiParam.entrySet()) { | 
				
			||||
                Object value = entry.getValue(); | 
				
			||||
                if (value instanceof List) { | 
				
			||||
                    List<String> valueList = ((List<?>) value).stream().map(String::valueOf).collect(Collectors.toList()); | 
				
			||||
                    newParams.put(entry.getKey(), valueList); | 
				
			||||
                } else { | 
				
			||||
                    newParams.put(entry.getKey(), Collections.singletonList(String.valueOf(value))); | 
				
			||||
                } | 
				
			||||
            } | 
				
			||||
            requestContext.setRequestQueryParams(newParams); | 
				
			||||
        } | 
				
			||||
    } | 
				
			||||
 | 
				
			||||
 | 
				
			||||
    private static class FormHttpOutputMessage implements HttpOutputMessage { | 
				
			||||
 | 
				
			||||
        private HttpHeaders headers = new HttpHeaders(); | 
				
			||||
        private ByteArrayOutputStream output = new ByteArrayOutputStream(); | 
				
			||||
 | 
				
			||||
        @Override | 
				
			||||
        public HttpHeaders getHeaders() { | 
				
			||||
            return this.headers; | 
				
			||||
        } | 
				
			||||
 | 
				
			||||
        @Override | 
				
			||||
        public OutputStream getBody() throws IOException { | 
				
			||||
            return this.output; | 
				
			||||
        } | 
				
			||||
 | 
				
			||||
        public byte[] getInput() throws IOException { | 
				
			||||
            this.output.flush(); | 
				
			||||
            return this.output.toByteArray(); | 
				
			||||
        } | 
				
			||||
 | 
				
			||||
    } | 
				
			||||
 | 
				
			||||
    private static class ChangeParamsHttpServletRequestWrapper extends HttpServletRequestWrapper { | 
				
			||||
        private byte[] data; | 
				
			||||
 | 
				
			||||
        public ChangeParamsHttpServletRequestWrapper(HttpServletRequest request, byte[] data) { | 
				
			||||
            super(request); | 
				
			||||
            this.data = data; | 
				
			||||
        } | 
				
			||||
 | 
				
			||||
        @Override | 
				
			||||
        public ServletInputStream getInputStream() throws IOException { | 
				
			||||
            return new ServletInputStreamWrapper(data); | 
				
			||||
        } | 
				
			||||
 | 
				
			||||
        @Override | 
				
			||||
        public byte[] getContentData() { | 
				
			||||
            return data; | 
				
			||||
        } | 
				
			||||
 | 
				
			||||
        @Override | 
				
			||||
        public int getContentLength() { | 
				
			||||
            return data.length; | 
				
			||||
        } | 
				
			||||
 | 
				
			||||
        @Override | 
				
			||||
        public long getContentLengthLong() { | 
				
			||||
            return data.length; | 
				
			||||
        } | 
				
			||||
    } | 
				
			||||
 | 
				
			||||
} | 
				
			||||
@ -1,24 +1,16 @@ | 
				
			||||
package com.gitee.sop.registryapi.bean; | 
				
			||||
 | 
				
			||||
import lombok.Getter; | 
				
			||||
import lombok.Setter; | 
				
			||||
import lombok.Data; | 
				
			||||
 | 
				
			||||
import java.util.Collections; | 
				
			||||
import java.util.List; | 
				
			||||
 | 
				
			||||
/** | 
				
			||||
 * @author tanghc | 
				
			||||
 */ | 
				
			||||
@Getter | 
				
			||||
@Setter | 
				
			||||
@Data | 
				
			||||
public class ServiceInfo { | 
				
			||||
    /** 服务名称 */ | 
				
			||||
    private String serviceId; | 
				
			||||
    /** 实例列表 */ | 
				
			||||
    private List<ServiceInstance> instances = Collections.emptyList(); | 
				
			||||
 | 
				
			||||
    @Override | 
				
			||||
    public String toString() { | 
				
			||||
        return "服务名称: " + serviceId + ", 实例数:" + instances.size(); | 
				
			||||
    } | 
				
			||||
    private List<ServiceInstance> instances; | 
				
			||||
} | 
				
			||||
 | 
				
			||||
					Loading…
					
					
				
		Reference in new issue