All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.onetwo.common.apiclient.ApiClientMethod Maven / Gradle / Ivy

package org.onetwo.common.apiclient;

import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import org.apache.commons.lang3.StringUtils;
import org.onetwo.common.annotation.FieldName;
import org.onetwo.common.apiclient.ApiClientMethod.ApiClientMethodParameter;
import org.onetwo.common.apiclient.ApiErrorHandler.DefaultErrorHandler;
import org.onetwo.common.apiclient.CustomResponseHandler.NullHandler;
import org.onetwo.common.apiclient.annotation.ApiClientInterceptor;
import org.onetwo.common.apiclient.annotation.InjectProperties;
import org.onetwo.common.apiclient.annotation.ResponseHandler;
import org.onetwo.common.apiclient.interceptor.ApiInterceptor;
import org.onetwo.common.apiclient.utils.ApiClientConstants.ApiClientErrors;
import org.onetwo.common.apiclient.utils.ApiClientUtils;
import org.onetwo.common.exception.ApiClientException;
import org.onetwo.common.exception.BaseException;
import org.onetwo.common.proxy.AbstractMethodResolver;
import org.onetwo.common.proxy.BaseMethodParameter;
import org.onetwo.common.reflect.BeanToMapConvertor;
import org.onetwo.common.reflect.ReflectUtils;
import org.onetwo.common.spring.SpringUtils;
import org.onetwo.common.spring.rest.RestUtils;
import org.onetwo.common.spring.utils.EnhanceBeanToMapConvertor.EnhanceBeanToMapBuilder;
import org.onetwo.common.utils.LangUtils;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.io.Resource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ValueConstants;
import org.springframework.web.client.RestClientException;
import org.springframework.web.multipart.MultipartFile;

/**
 * 支持  @PathVariable @RequestBody @RequestParam 注解
 * @PathVariable:解释路径参数
 * @RequestBody:body,一般会转为json,一次请求 只允许一个requestbody
 * @RequestParam:转化为queryString参数
 * 没有注解的方法参数:如果为get请求,则所有参数都转为queryString参数,效果和使用了@RequestParam一样;
 * 					 如果为post请求,则自动包装为类型为Map的requestBody
 * 
 * 如果没有指定requestBody,则根据规则查找可以作为requestBody的参数
 * 方法多于一个参数时,使用参数名称作为参数前缀;
 * 只有一个参数的时候,除非用@RequestParam等注解指定了参数名称前缀,否则前缀为空,直接把对象转化为map作为键值对参数
 * 
 * 
 * get请求忽略requestBody
 * post请求会把非url参数转化为requestBody
 * 
 * consumes -> contentType,指定提交请求的convertor,详见:HttpEntityRequestCallback
 * produces -> acceptHeader,指定accept header,从而通过response的contentType头指定读取响应数据的convertor,详见:ResponseEntityResponseExtractor
 * 
 * 值转换器:ValueConvertor
 * HttpEntityRequestCallback
 * 
 * @author wayshall
 * 
*/ public class ApiClientMethod extends AbstractMethodResolver { public static BeanToMapConvertor getBeanToMapConvertor() { return beanToMapConvertor; } final static private BeanToMapConvertor beanToMapConvertor = EnhanceBeanToMapBuilder.enhanceBuilder() .enableFieldNameAnnotation() .enableJsonPropertyAnnotation() .valueConvertor(new RestApiValueConvertor()) .flatableObject(obj->{ boolean flatable = BeanToMapConvertor.DEFAULT_FLATABLE.apply(obj); return flatable && !Resource.class.isInstance(obj) && !ApiClientMethodConfig.class.isInstance(obj) && !byte[].class.isInstance(obj) && !MultipartFile.class.isInstance(obj); }) .build(); // final static private BeanToMapConvertor beanToMapConvertor = BeanToMapBuilder.newBuilder() // .enableFieldNameAnnotation() // .valueConvertor(new ValueConvertor()) // .flatableObject(obj->{ // boolean flatable = BeanToMapConvertor.DEFAULT_FLATABLE.apply(obj); // return flatable && // !Resource.class.isInstance(obj) && // !byte[].class.isInstance(obj) && // !MultipartFile.class.isInstance(obj); // }) // .build(); // final private AnnotationAttributes requestMappingAttributes; private RequestMethod requestMethod; private String path; private Class componentType; private List acceptHeaders; private List consumeMediaTypes; /*** * resttemplate会根据contentType(consumes)决定采用什么样的httpMessageConvertor * 详见HttpEntityRequestCallback#doWithRequest -> requestContentType */ private Optional contentType; private String[] headers; private int apiHeaderCallbackIndex = -1; private int headerParameterIndex = -1; private CustomResponseHandler customResponseHandler; private ApiErrorHandler apiErrorHandler; private List interceptors; /*** * 如果返回了业务错误代码,是否自动抛错 */ private boolean autoThrowIfErrorCode = true; // private int methodConfigIndex = -1; public ApiClientMethod(Method method) { super(method); componentType = (Class)ReflectUtils.getGenricType(method.getGenericReturnType(), 0, null); } public void initialize(){ if(!ApiClientUtils.isRequestMappingPresent()){ throw new ApiClientException(ApiClientErrors.REQUEST_MAPPING_NOT_FOUND); } Optional requestMapping = SpringUtils.getAnnotationAttributes(method, RequestMapping.class); if(!requestMapping.isPresent()){ throw new ApiClientException(ApiClientErrors.REQUEST_MAPPING_NOT_FOUND, method, null); } AnnotationAttributes reqestMappingAttrs = requestMapping.get(); //SpringMvcContract#parseAndValidateMetadata this.acceptHeaders = Arrays.asList(reqestMappingAttrs.getStringArray("produces")); String[] consumes = reqestMappingAttrs.getStringArray("consumes"); this.contentType = LangUtils.getFirstOptional(consumes); this.consumeMediaTypes = Stream.of(consumes).map(MediaType::parseMediaType).collect(Collectors.toList()); this.headers = reqestMappingAttrs.getStringArray("headers"); String[] paths = reqestMappingAttrs.getStringArray("value"); path = LangUtils.isEmpty(paths)?"":paths[0]; RequestMethod[] methods = (RequestMethod[])reqestMappingAttrs.get("method"); requestMethod = LangUtils.isEmpty(methods)?RequestMethod.GET:methods[0]; findParameterByType(ApiHeaderCallback.class).ifPresent(p->{ this.apiHeaderCallbackIndex = p.getParameterIndex(); }); findParameterByType(HttpHeaders.class).ifPresent(p->{ this.headerParameterIndex = p.getParameterIndex(); }); // findParameterByType(ApiClientMethodConfig.class).ifPresent(p->{ // this.methodConfigIndex = p.getParameterIndex(); // }); this.initHandlers(); this.initInterceptors(); } private void initInterceptors() { ApiClientInterceptor interceptorAnno = findAnnotation(ApiClientInterceptor.class); if (interceptorAnno==null) { this.interceptors = Collections.emptyList(); } else { this.interceptors = Stream.of(interceptorAnno.value()).map(cls -> { return (ApiInterceptor)createAndInitComponent(cls); }) .collect(Collectors.toList()); } } public List getInterceptors() { return interceptors; } public boolean isAutoThrowIfErrorCode() { return autoThrowIfErrorCode; } public void setAutoThrowIfErrorCode(boolean autoThrowIfErrorCode) { this.autoThrowIfErrorCode = autoThrowIfErrorCode; } /**** * 先从方法上查找注解,没有则从类上查找 * @author weishao zeng * @param annoClass * @return public T findAnnotation(Class annoClass) { T annoInst = AnnotatedElementUtils.getMergedAnnotation(getMethod(), annoClass); if (annoInst==null) { annoInst = AnnotatedElementUtils.getMergedAnnotation(getDeclaringClass(), annoClass); } return annoInst; } */ private void initHandlers() { // 需要加@Inherited才能继承 ResponseHandler resHandler = AnnotatedElementUtils.getMergedAnnotation(getMethod(), ResponseHandler.class); if (resHandler==null){ resHandler = AnnotatedElementUtils.getMergedAnnotation(getDeclaringClass(), ResponseHandler.class); } if (resHandler!=null){ Class> handlerClass = resHandler.value(); if (handlerClass!=NullHandler.class) { CustomResponseHandler customHandler = createAndInitComponent(resHandler.value()); this.customResponseHandler = customHandler; } Class errorHandlerClass = resHandler.errorHandler(); if (errorHandlerClass==DefaultErrorHandler.class) { this.apiErrorHandler = obtainDefaultApiErrorHandler(); } else { this.apiErrorHandler = createAndInitComponent(errorHandlerClass); } } else { this.apiErrorHandler = obtainDefaultApiErrorHandler(); } } protected ApiErrorHandler obtainDefaultApiErrorHandler() { return null; } public CustomResponseHandler getCustomResponseHandler() { return customResponseHandler; } public Optional getApiErrorHandler() { return Optional.ofNullable(apiErrorHandler); } public Optional getApiHeaderCallback(Object[] args){ return apiHeaderCallbackIndex<0?Optional.empty():Optional.ofNullable((ApiHeaderCallback)args[apiHeaderCallbackIndex]); } public Optional getHttpHeaders(Object[] args){ return headerParameterIndex<0?Optional.empty():Optional.ofNullable((HttpHeaders)args[headerParameterIndex]); } // public Optional getApiClientMethodConfig(Object[] args){ // return methodConfigIndex<0?Optional.empty():Optional.ofNullable((ApiClientMethodConfig)args[headerParameterIndex]); // } public String[] getHeaders() { return headers; } public List getAcceptHeaders() { return acceptHeaders; } public List getProduceMediaTypes() { return acceptHeaders.stream().map(accept -> { return MediaType.parseMediaType(accept); }).collect(Collectors.toList()); } public List getConsumeMediaTypes() { return consumeMediaTypes; } public Optional getContentType() { return contentType; } public Map getUriVariables(Object[] args){ if(LangUtils.isEmpty(args)){ return Collections.emptyMap(); } List urlVariableParameters = parameters.stream() .filter(p->{ return isUriVariables(p); }) .collect(Collectors.toList()); // boolean parameterNameAsPrefix = urlVariableParameters.size()>1; Map values = toMap(urlVariableParameters, args).toSingleValueMap(); return values; } public Map getQueryStringParameters(Object[] args){ if(LangUtils.isEmpty(args)){ return Collections.emptyMap(); } List queryParameters = parameters.stream() .filter(p->isQueryStringParameters(p)) .collect(Collectors.toList()); boolean parameterNameAsPrefix = queryParameters.size()>1; Map values = toMap(queryParameters, args, parameterNameAsPrefix).toSingleValueMap(); return values; } /*** * 在http规范任何方法都能发送请求实体。他们报文时没有任何区别的,但是在浏览器中,因为XMLHttpRequest规范的限制,浏览器中ajax发送的http请求,get,delete请求不能携带实体。 * 这里在判断是否queryString参数时,遵循浏览器规则: * 1. get和delete请求时,只要不是url变量和特定参数,都当做queryString参数处理 * 2. get和delete请求时,只要不是url变量和特定参数,都当做queryString参数处理 * @author weishao zeng * @param p * @return */ protected boolean isQueryStringParameters(ApiClientMethodParameter p){ // if(requestMethod==RequestMethod.POST || requestMethod==RequestMethod.PATCH){ // //post方法,使用了RequestParam才转化为queryString // return p.hasParameterAnnotation(RequestParam.class); // } if(requestMethod!=RequestMethod.GET && requestMethod!=RequestMethod.DELETE) { // 非get,delete请求时,只有指定了@RequestParam的参数,才是queryString参数 return p.hasParameterAnnotation(RequestParam.class); } // get和delete请求时,只要不是url变量和特定参数,都当做queryString参数处理 return !isUriVariables(p) && !isSpecalPemerater(p); } protected boolean isUriVariables(ApiClientMethodParameter p){ return p.hasParameterAnnotation(PathVariable.class); } public Map getPathVariables(Object[] args){ if(LangUtils.isEmpty(args)){ return Collections.emptyMap(); } List pathVariables = parameters.stream() .filter(p->{ return p.hasParameterAnnotation(PathVariable.class); }) .collect(Collectors.toList()); /*Object pvalue = null; for(ApiClientMethodParameter parameter : pathVariables){ pvalue = args[parameter.getParameterIndex()]; handleArg(values, parameter, pvalue); }*/ Map values = toMap(pathVariables, args).toSingleValueMap(); return values; } public ApiClientMethodConfig getApiClientMethodConfig(Object[] args){ for (Object arg : args) { if (arg instanceof ApiClientMethodConfigProvider) { ApiClientMethodConfigProvider provider = (ApiClientMethodConfigProvider) arg; return provider.toApiClientMethodConfig(); } } return null; } public Object getRequestBody(Object[] args){ if(!RestUtils.isRequestBodySupportedMethod(requestMethod)){ throw new RestClientException("unsupported request body method: " + method); } List requestBodyParameters = parameters.stream() .filter(p->{ return p.hasParameterAnnotation(RequestBody.class); }) .collect(Collectors.toList()); if(requestBodyParameters.isEmpty()){ //如果没有使用RequestBody注解的参数 // Object values = toMap(parameters, args); Object values = null; if(LangUtils.isEmpty(args)){ return null; } //如果没有指定requestBody,则根据规则查找可以作为requestBody的参数 List bodyableParameters = parameters.stream() .filter(p->!isSpecalPemerater(p) && !isQueryStringParameters(p)) .collect(Collectors.toList()); // List bodyableParameters = parameters; if(LangUtils.isEmpty(bodyableParameters)){ return null; } // boolean parameterNameAsPrefix = bodyableParameters.size()>1; //大于一个参数时,使用参数名称作为参数前缀 boolean parameterNameAsPrefix = false; //修改为默认不使用前缀 //没有requestBody注解时,根据contentType做简单的转换策略 if(getContentType().isPresent()){ String contentType = getContentType().get(); MediaType consumerMediaType = MediaType.parseMediaType(contentType); if(MediaType.APPLICATION_FORM_URLENCODED.equals(consumerMediaType) || MediaType.MULTIPART_FORM_DATA.equals(consumerMediaType)){ //form的话,需要转成multipleMap values = toMap(bodyableParameters, args, parameterNameAsPrefix); }else{ //如contentType为json之类,则转成单值的map // values = args.length==1?args[0]:toMap(parameters, args).toSingleValueMap(); values = args.length==1?args[0]:toMap(bodyableParameters, args, parameterNameAsPrefix).toSingleValueMap(); // values = args.length==1?args[0]:toMap(parameters, args); // values = toMap(parameters, args); } }else{ //默认为form values = toMap(bodyableParameters, args, parameterNameAsPrefix); } return values; }else if(requestBodyParameters.size()==1){ return args[requestBodyParameters.get(0).getParameterIndex()]; }else{ throw new ApiClientException(ApiClientErrors.REQUEST_BODY_ONLY_ONCE, method, null); } /*if(requestBodyParameter.isPresent()){ return args[requestBodyParameter.get().getParameterIndex()]; } return null;*/ } protected MultiValueMap toMap(List methodParameters, Object[] args){ return toMap(methodParameters, args, true); } protected MultiValueMap toMap(List methodParameters, Object[] args, boolean parameterNameAsPrefix){ MultiValueMap values = new LinkedMultiValueMap<>(methodParameters.size()); for(ApiClientMethodParameter parameter : methodParameters){ //忽略特殊参数 if(isSpecalPemerater(parameter)){ continue; } Object pvalue = args[parameter.getParameterIndex()]; handleArg(values, parameter, pvalue, parameterNameAsPrefix); } return values; } protected boolean isSpecalPemerater(ApiClientMethodParameter parameter){ return parameter.getParameterIndex()==apiHeaderCallbackIndex || parameter.getParameterIndex()==headerParameterIndex; // parameter.getParameterIndex()==methodConfigIndex; } protected void handleArg(MultiValueMap values, ApiClientMethodParameter mp, final Object pvalue, boolean parameterNameAsPrefix){ if(pvalue instanceof ApiArgumentTransformer){ Object val = ((ApiArgumentTransformer)pvalue).asApiValue(); values.add(mp.getParameterName(), val); return ; } String prefix = ""; Object paramValue = pvalue; //下列情况,强制使用名称作为前缀 if(mp.hasParameterAnnotation(RequestParam.class)){ RequestParam params = mp.getParameterAnnotation(RequestParam.class); if(pvalue==null && params.required() && (paramValue=params.defaultValue())==ValueConstants.DEFAULT_NONE){ throw new BaseException("parameter["+params.name()+"] must be required : " + mp.getParameterName()); } parameterNameAsPrefix = true; }else if(isUriVariables(mp) || mp.hasParameterAnnotation(FieldName.class)){ parameterNameAsPrefix = true; }else if(beanToMapConvertor.isMappableValue(pvalue)){//可直接映射为值的参数 parameterNameAsPrefix = true; } if(parameterNameAsPrefix){ prefix = mp.getParameterName(); } beanToMapConvertor.flatObject(prefix, paramValue, (k, v, ctx)->{ // 忽略这些参数 if (v==null || v instanceof ApiClientMethodConfig) { return ; } values.add(k, v); }); /*if(falatable){ // beanToMapConvertor.flatObject(mp.getParameterName(), paramValue, (k, v, ctx)->{ beanToMapConvertor.flatObject(mp.getParameterName(), paramValue, (k, v, ctx)->{ if(v instanceof Enum){ Enum e = (Enum)v; if(e instanceof ValueEnum){ v = ((ValueEnum)e).getValue(); }else{//默认使用name v = e.name(); } }else if(v instanceof Resource){ //ignore,忽略,不转为string }else{ v = v.toString(); } if(ctx!=null){ // System.out.println("ctx.getName():"+ctx.getName()); values.add(ctx.getName(), v); }else{ values.add(k, v); } // values.add(k, v); }); }else{ values.add(mp.getParameterName(), pvalue); }*/ } public String getPath() { return path; } public RequestMethod getRequestMethod() { return requestMethod; } public Class getComponentType() { return componentType; } @Override protected ApiClientMethodParameter createMethodParameter(Method method, int parameterIndex, Parameter parameter) { return new ApiClientMethodParameter(method, parameter, parameterIndex); } public static class ApiClientMethodParameter extends BaseMethodParameter { private boolean injectProperties; public ApiClientMethodParameter(Method method, Parameter parameter, int parameterIndex) { super(method, parameter, parameterIndex); this.injectProperties = parameter.isAnnotationPresent(InjectProperties.class); } @Override public String obtainParameterName() { /*String pname = super.getParameterName(); if(StringUtils.isNotBlank(pname)){ return pname; }*/ String pname = getOptionalParameterAnnotation(RequestParam.class).map(rp->rp.value()).orElseGet(()->{ // return getOptionalParameterAnnotation(PathVariable.class).map(pv->pv.value()).orElse(null); return getOptionalParameterAnnotation(PathVariable.class).map(pv->pv.value()).orElseGet(()->{ return getOptionalParameterAnnotation(FieldName.class).map(fn->fn.value()).orElse(null); }); }); // 如果没有找到命名的注解 if(StringUtils.isBlank(pname)){ /*pname = getOptionalParameterAnnotation(PathVariable.class).map(pv->pv.value()).orElse(null); if(StringUtils.isNotBlank(pname)){ return pname; }*/ if(parameter!=null && parameter.isNamePresent()){ pname = parameter.getName(); }else{ pname = String.valueOf(getParameterIndex()); } } // this.setParameterName(pname); return pname; } public boolean isInjectProperties() { return injectProperties; } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy