diff --git a/sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/bean/OpenContextImpl.java b/sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/bean/OpenContextImpl.java index ebe03d80..515b66e3 100644 --- a/sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/bean/OpenContextImpl.java +++ b/sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/bean/OpenContextImpl.java @@ -31,13 +31,15 @@ public class OpenContextImpl implements OpenContext { } public OpenContextImpl(JSONObject rootJsonObject, Class bizClass) { + if (rootJsonObject == null) { + throw new IllegalArgumentException("rootJsonObject can not be null"); + } this.rootJsonObject = rootJsonObject; if (bizClass != null) { - JSONObject bizJsonObj = this.rootJsonObject.getJSONObject(BIZ_CONTENT_NAME); - if (bizJsonObj == null) { - bizJsonObj = rootJsonObject; + String bizContent = getBizContent(); + if (bizContent != null) { + bizObject = (T) JSON.parseObject(bizContent, bizClass); } - bizObject = (T) bizJsonObj.toJavaObject(bizClass); } } @@ -106,10 +108,10 @@ public class OpenContextImpl implements OpenContext { if (bizObject != null && bizObject.getClass() == clazz) { return (E) bizObject; } - String bizJsonObj = this.rootJsonObject.getString(BIZ_CONTENT_NAME); - if (bizJsonObj == null) { + String bizContent = getBizContent(); + if (bizContent == null) { return null; } - return JSON.parseObject(bizJsonObj, clazz); + return JSON.parseObject(bizContent, clazz); } } diff --git a/sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/param/ApiArgumentResolver.java b/sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/param/ApiArgumentResolver.java index 2de4034d..d86f7887 100644 --- a/sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/param/ApiArgumentResolver.java +++ b/sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/param/ApiArgumentResolver.java @@ -9,6 +9,9 @@ import com.gitee.sop.servercommon.bean.ServiceContext; import com.gitee.sop.servercommon.util.OpenUtil; import lombok.extern.slf4j.Slf4j; import org.springframework.core.MethodParameter; +import org.springframework.http.HttpMethod; +import org.springframework.lang.Nullable; +import org.springframework.util.ClassUtils; import org.springframework.util.ReflectionUtils; import org.springframework.web.bind.support.WebDataBinderFactory; import org.springframework.web.context.request.NativeWebRequest; @@ -20,17 +23,25 @@ import org.springframework.web.multipart.MultipartHttpServletRequest; import org.springframework.web.multipart.MultipartRequest; import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter; import org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor; +import org.springframework.web.servlet.mvc.method.annotation.ServletRequestMethodArgumentResolver; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpSession; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.Reader; +import java.io.Writer; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; +import java.security.Principal; +import java.time.ZoneId; import java.util.ArrayList; import java.util.List; -import java.util.Map; +import java.util.Locale; import java.util.Objects; -import java.util.concurrent.ConcurrentHashMap; +import java.util.TimeZone; /** * 解析request参数中的业务参数,绑定到方法参数上 @@ -40,15 +51,23 @@ import java.util.concurrent.ConcurrentHashMap; @Slf4j public class ApiArgumentResolver implements SopHandlerMethodArgumentResolver { - private final Map argumentResolverCache = new ConcurrentHashMap<>(256); - private ParamValidator paramValidator = new ServiceParamValidator(); - private RequestMappingHandlerAdapter requestMappingHandlerAdapter; + @Nullable + private static Class pushBuilder; + + static { + try { + pushBuilder = ClassUtils.forName("javax.servlet.http.PushBuilder", + ServletRequestMethodArgumentResolver.class.getClassLoader()); + } catch (ClassNotFoundException ex) { + // Servlet 4.0 PushBuilder not found - not supported for injection + pushBuilder = null; + } + } @Override public void setRequestMappingHandlerAdapter(RequestMappingHandlerAdapter requestMappingHandlerAdapter) { - this.requestMappingHandlerAdapter = requestMappingHandlerAdapter; List argumentResolversNew = new ArrayList<>(64); // 先加自己 argumentResolversNew.add(this); @@ -64,14 +83,42 @@ public class ApiArgumentResolver implements SopHandlerMethodArgumentResolver { if (lastOne != null) { argumentResolversNew.add(lastOne); } - this.requestMappingHandlerAdapter.setArgumentResolvers(argumentResolversNew); + requestMappingHandlerAdapter.setArgumentResolvers(argumentResolversNew); } @Override public boolean supportsParameter(MethodParameter methodParameter) { - // 有注解 - return methodParameter.getMethodAnnotation(ApiMapping.class) != null - || methodParameter.getMethodAnnotation(ApiAbility.class) != null; + // 是否有注解 + boolean hasAnnotation = methodParameter.hasMethodAnnotation(ApiMapping.class) + || methodParameter.hasMethodAnnotation(ApiAbility.class); + if (!hasAnnotation) { + return false; + } + Class paramType = methodParameter.getParameterType(); + if (paramType == OpenContext.class) { + return true; + } + // 排除的 + boolean exclude = ( + paramType.isInterface() || + WebRequest.class.isAssignableFrom(paramType) || + ServletRequest.class.isAssignableFrom(paramType) || + MultipartRequest.class.isAssignableFrom(paramType) || + HttpSession.class.isAssignableFrom(paramType) || + (pushBuilder != null && pushBuilder.isAssignableFrom(paramType)) || + Principal.class.isAssignableFrom(paramType) || + InputStream.class.isAssignableFrom(paramType) || + Reader.class.isAssignableFrom(paramType) || + HttpMethod.class == paramType || + Locale.class == paramType || + TimeZone.class == paramType || + ZoneId.class == paramType || + ServletResponse.class.isAssignableFrom(paramType) || + OutputStream.class.isAssignableFrom(paramType) || + Writer.class.isAssignableFrom(paramType) + ); + // 除此之外都匹配 + return !exclude; } @Override @@ -81,80 +128,39 @@ public class ApiArgumentResolver implements SopHandlerMethodArgumentResolver { // JSR-303验证 paramValidator.validateBizParam(paramObj); return paramObj; - } else { - HandlerMethodArgumentResolver resolver = getOtherArgumentResolver(methodParameter); - if (resolver != null) { - return resolver.resolveArgument(methodParameter, modelAndViewContainer, nativeWebRequest, webDataBinderFactory); - } - return null; } + return null; } /** * 获取参数对象,将request中的参数绑定到实体对象中去 * - * @param methodParameter 方法参数 + * @param methodParameter 方法参数 * @param nativeWebRequest request * @return 返回参数绑定的对象,没有返回null */ protected Object getParamObject(MethodParameter methodParameter, NativeWebRequest nativeWebRequest) { - Class parameterType = methodParameter.getParameterType(); - // WebRequest / NativeWebRequest / ServletWebRequest - if (WebRequest.class.isAssignableFrom(parameterType)) { - if (!parameterType.isInstance(nativeWebRequest)) { - throw new IllegalStateException( - "Current request is not of type [" + parameterType.getName() + "]: " + nativeWebRequest); - } - return nativeWebRequest; - } - - // ServletRequest / HttpServletRequest / MultipartRequest / MultipartHttpServletRequest - if (ServletRequest.class.isAssignableFrom(parameterType) || MultipartRequest.class.isAssignableFrom(parameterType)) { - return resolveNativeRequest(nativeWebRequest, parameterType); - } - - // ServletResponse, HttpServletResponse - if (ServletResponse.class.isAssignableFrom(parameterType)) { - return resolveNativeResponse(nativeWebRequest, parameterType); - } - HttpServletRequest request = (HttpServletRequest) nativeWebRequest.getNativeRequest(); JSONObject requestParams = OpenUtil.getRequestParams(request); // 方法参数类型 + Class parameterType = methodParameter.getParameterType(); + boolean isOpenContextParam = parameterType == OpenContext.class; Class bizObjClass = parameterType; - boolean isOpenRequestParam = parameterType == OpenContext.class; // 参数是OpenRequest,则取OpenRequest的泛型参数类型 - if (isOpenRequestParam) { + if (isOpenContextParam) { bizObjClass = this.getOpenRequestGenericParameterClass(methodParameter); } OpenContext openContext = new OpenContextImpl(requestParams, bizObjClass); ServiceContext.getCurrentContext().setOpenContext(openContext); Object bizObj = openContext.getBizObject(); - this.bindUploadFile(bizObj, nativeWebRequest); - return isOpenRequestParam ? openContext : bizObj; - } - - private T resolveNativeRequest(NativeWebRequest webRequest, Class requiredType) { - T nativeRequest = webRequest.getNativeRequest(requiredType); - if (nativeRequest == null) { - throw new IllegalStateException( - "Current request is not of type [" + requiredType.getName() + "]: " + webRequest); - } - return nativeRequest; - } - - private T resolveNativeResponse(NativeWebRequest webRequest, Class requiredType) { - T nativeResponse = webRequest.getNativeResponse(requiredType); - if (nativeResponse == null) { - throw new IllegalStateException( - "Current response is not of type [" + requiredType.getName() + "]: " + webRequest); - } - return nativeResponse; + this.bindUploadFile(bizObj, request); + return isOpenContextParam ? openContext : bizObj; } /** * 获取泛型参数类型 + * * @param methodParameter 参数 * @return 返回泛型参数class */ @@ -173,16 +179,15 @@ public class ApiArgumentResolver implements SopHandlerMethodArgumentResolver { /** * 将上传文件对象绑定到属性中 * - * @param bizObj 业务参数 - * @param nativeWebRequest + * @param bizObj 业务参数 + * @param httpServletRequest */ - protected void bindUploadFile(Object bizObj, NativeWebRequest nativeWebRequest) { + protected void bindUploadFile(Object bizObj, HttpServletRequest httpServletRequest) { if (bizObj == null) { return; } - if (this.isMultipartRequest(nativeWebRequest)) { - Object nativeRequest = nativeWebRequest.getNativeRequest(); - MultipartHttpServletRequest request = (MultipartHttpServletRequest) nativeRequest; + if (this.isMultipartRequest(httpServletRequest)) { + MultipartHttpServletRequest request = (MultipartHttpServletRequest) httpServletRequest; Class bizClass = bizObj.getClass(); ReflectionUtils.doWithFields(bizClass, field -> { ReflectionUtils.makeAccessible(field); @@ -193,33 +198,10 @@ public class ApiArgumentResolver implements SopHandlerMethodArgumentResolver { } } - protected boolean isMultipartRequest(NativeWebRequest nativeWebRequest) { - return nativeWebRequest.getNativeRequest() instanceof MultipartHttpServletRequest; + protected boolean isMultipartRequest(HttpServletRequest request) { + return request instanceof MultipartRequest; } - /** - * 获取其它的参数解析器 - * - * @param parameter 业务参数 - * @return 返回合适参数解析器,没有返回null - */ - protected HandlerMethodArgumentResolver getOtherArgumentResolver(MethodParameter parameter) { - HandlerMethodArgumentResolver result = this.argumentResolverCache.get(parameter); - if (result == null) { - List argumentResolvers = this.requestMappingHandlerAdapter.getArgumentResolvers(); - for (HandlerMethodArgumentResolver methodArgumentResolver : argumentResolvers) { - if (methodArgumentResolver instanceof SopHandlerMethodArgumentResolver) { - continue; - } - if (methodArgumentResolver.supportsParameter(parameter)) { - result = methodArgumentResolver; - this.argumentResolverCache.put(parameter, result); - break; - } - } - } - return result; - } public void setParamValidator(ParamValidator paramValidator) { this.paramValidator = paramValidator; diff --git a/sop-example/sop-story/sop-story-web/src/main/java/com/gitee/sop/storyweb/controller/AlipayController.java b/sop-example/sop-story/sop-story-web/src/main/java/com/gitee/sop/storyweb/controller/AlipayController.java index 5d9cf5f3..b050af7d 100644 --- a/sop-example/sop-story/sop-story-web/src/main/java/com/gitee/sop/storyweb/controller/AlipayController.java +++ b/sop-example/sop-story/sop-story-web/src/main/java/com/gitee/sop/storyweb/controller/AlipayController.java @@ -17,6 +17,7 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RestController; +import javax.servlet.http.HttpServletRequest; import java.util.Arrays; import java.util.Date; import java.util.List; @@ -102,10 +103,19 @@ public class AlipayController { Story bizObject = openContext.getBizObject(); // 获取appid,更多方法查看OpenContext类 String appId = openContext.getAppId(); - System.out.println(appId); + bizObject.setName("appId:" + appId + ", " + bizObject.getName()); return bizObject; } + @ApiMapping(value = "story.get", version = "2.3") + public Story getStory23(Story story, HttpServletRequest request) { + OpenContext openContext = ServiceContext.getCurrentContext().getOpenContext(); + String appId = openContext.getAppId(); + System.out.println(appId); + story.setName("appId:" + appId + ", " + story.getName() + ",ip:" + request.getLocalAddr()); + return story; + } + // http://localhost:2222/getStory2 // 遗留接口具备开放平台能力 @ApiAbility diff --git a/sop-test/src/main/java/com/gitee/sop/test/Client.java b/sop-test/src/main/java/com/gitee/sop/test/Client.java index 1a014ab9..ce719139 100644 --- a/sop-test/src/main/java/com/gitee/sop/test/Client.java +++ b/sop-test/src/main/java/com/gitee/sop/test/Client.java @@ -344,6 +344,14 @@ public class Client { private Map form; private Map header; private HttpTool.HTTPMethod httpMethod; + + /** + * 返回json跟节点名称 + * @return 返回json跟节点名称 + */ + public String getDataNode() { + return method == null ? null : method.replace('.', '_') + "_response"; + } } } diff --git a/sop-test/src/test/java/com/gitee/sop/test/AllInOneTest.java b/sop-test/src/test/java/com/gitee/sop/test/AllInOneTest.java index 4415021e..6915533a 100644 --- a/sop-test/src/test/java/com/gitee/sop/test/AllInOneTest.java +++ b/sop-test/src/test/java/com/gitee/sop/test/AllInOneTest.java @@ -105,6 +105,39 @@ public class AllInOneTest extends TestBase { client.execute(requestBuilder); } + /** + * OpenContext参数绑定 + */ + public void testOpenContext() { + Client client = new Client(url, appId, privateKey); + Client.RequestBuilder requestBuilder = new Client.RequestBuilder() + .method("story.get") + .version("2.2") + .bizContent(new BizContent().add("id", "222").add("name", "openContext")) + .httpMethod(HttpTool.HTTPMethod.GET) + .callback((requestInfo, responseData) -> { + System.out.println(responseData); + JSONObject jsonObject = JSON.parseObject(responseData); + String name = jsonObject.getJSONObject(requestInfo.getDataNode()).getString("name"); + Assert.assertEquals(name, "appId:" + appId + ", openContext"); + }); + + client.execute(requestBuilder); + } + + /** + * 其它参数绑定 + */ + public void testOtherParam() { + Client.RequestBuilder requestBuilder = new Client.RequestBuilder() + .method("story.get") + .version("2.3") + .bizContent(new BizContent().add("id", "222").add("name", "request param")) + .httpMethod(HttpTool.HTTPMethod.GET); + + client.execute(requestBuilder); + } + /** * JSR-303参数校验 */ @@ -118,7 +151,7 @@ public class AllInOneTest extends TestBase { .callback((requestInfo, responseData) -> { System.out.println(responseData); JSONObject jsonObject = JSON.parseObject(responseData); - String sub_msg = jsonObject.getJSONObject("goods_add_response").getString("sub_msg"); + String sub_msg = jsonObject.getJSONObject(requestInfo.getDataNode()).getString("sub_msg"); Assert.assertEquals(sub_msg, "商品评论长度必须在3和20之间"); }); @@ -154,6 +187,7 @@ public class AllInOneTest extends TestBase { * 演示文件上传 */ public void testFile() { + Client client = new Client(url, appId, privateKey); String root = System.getProperty("user.dir"); Client.RequestBuilder requestBuilder = new Client.RequestBuilder() .method("demo.file.upload") @@ -162,6 +196,36 @@ public class AllInOneTest extends TestBase { // 添加文件 .addFile("file1", new File(root + "/src/main/resources/file1.txt")) .addFile("file2", new File(root + "/src/main/resources/file2.txt")) + .callback((requestInfo, responseData) -> { + JSONObject jsonObject = JSON.parseObject(responseData); + JSONObject data = jsonObject.getJSONObject(requestInfo.getDataNode()); + Assert.assertEquals(data.getString("code"), "10000"); + Assert.assertEquals(data.getJSONArray("files").size(), 2); + }) + ; + + client.execute(requestBuilder); + } + + /** + * 演示文件上传2 + */ + public void testFile2() { + Client client = new Client(url, appId, privateKey); + String root = System.getProperty("user.dir"); + Client.RequestBuilder requestBuilder = new Client.RequestBuilder() + .method("demo.file.upload2") + .version("1.0") + .bizContent(new BizContent().add("remark", "test file upload")) + // 添加文件 + .addFile("file1", new File(root + "/src/main/resources/file1.txt")) + .addFile("file2", new File(root + "/src/main/resources/file2.txt")) + .callback((requestInfo, responseData) -> { + JSONObject jsonObject = JSON.parseObject(responseData); + JSONObject data = jsonObject.getJSONObject(requestInfo.getDataNode()); + Assert.assertEquals(data.getString("code"), "10000"); + Assert.assertEquals(data.getJSONArray("files").size(), 2); + }) ; client.execute(requestBuilder); @@ -235,7 +299,7 @@ public class AllInOneTest extends TestBase { if (method == null) { return; } - String node = method.replace('.', '_') + "_response"; + String node = requestInfo.getDataNode(); JSONObject jsonObject = JSON.parseObject(responseData).getJSONObject(node); String code = Optional.ofNullable(jsonObject).map(json -> json.getString("code")).orElse("20000"); Assert.assertEquals("10000", code);