Merge branch 'master' into registry-nacos

pull/1/head
tanghc 5 years ago
commit 3e49780fa2
  1. 3
      changelog.md
  2. 60
      doc/docs/_sidebar.md
  3. 35
      doc/docs/files/10100_传统web开发.md
  4. 2
      sop-admin/sop-admin-server/pom.xml
  5. 2
      sop-common/pom.xml
  6. 4
      sop-common/sop-gateway-common/pom.xml
  7. 51
      sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/util/RequestUtil.java
  8. 6
      sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/zuul/configuration/BaseZuulConfiguration.java
  9. 18
      sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/zuul/configuration/WebappZuulConfiguration.java
  10. 6
      sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/zuul/filter/BaseZuulFilter.java
  11. 29
      sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/zuul/filter/FormBodyWrapperFilterExt.java
  12. 31
      sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/zuul/filter/PreHttpServletRequestWrapperFilter.java
  13. 27
      sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/zuul/filter/Servlet30WrapperFilterExt.java
  14. 61
      sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/zuul/param/ZuulParamBuilder.java
  15. 2
      sop-common/sop-registry-api/pom.xml
  16. 4
      sop-common/sop-service-common/pom.xml
  17. 3
      sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/configuration/AlipayServiceConfiguration.java
  18. 14
      sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/configuration/WebappServiceConfiguration.java
  19. 10
      sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/manager/DefaultRequestMappingEvent.java
  20. 19
      sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/manager/ServiceZookeeperApiMetaManager.java
  21. 42
      sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/param/ApiArgumentResolver.java
  22. 5
      sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/param/SopHandlerMethodArgumentResolver.java
  23. 2
      sop-example/sop-auth/pom.xml
  24. 2
      sop-example/sop-book/sop-book-web/pom.xml
  25. 2
      sop-example/sop-easyopen/pom.xml
  26. 2
      sop-example/sop-springmvc/pom.xml
  27. 2
      sop-example/sop-story/sop-story-web/pom.xml
  28. 4
      sop-example/sop-story/sop-story-web/src/main/java/com/gitee/sop/storyweb/controller/AlipayController.java
  29. 94
      sop-example/sop-story/sop-story-web/src/main/java/com/gitee/sop/storyweb/controller/TraditionalWebappController.java
  30. 2
      sop-gateway/pom.xml
  31. 11
      sop-gateway/src/main/java/com/gitee/sop/gateway/controller/RedirectController.java
  32. 89
      sop-sdk/sdk-java/src/main/java/com/gitee/sop/sdk/client/OpenHttp.java
  33. 4
      sop-sdk/sdk-java/src/main/java/com/gitee/sop/sdk/client/OpenRequest.java
  34. 2
      sop-website/website-server/pom.xml
  35. 1
      sop-website/website-server/src/main/java/com/gitee/sop/websiteserver/bean/HttpTool.java

@ -1,5 +1,8 @@
# changelog
## 1.13.1
- 支持json方式请求(application/json)
## 1.13.0

@ -1,32 +1,32 @@
* [首页](/?t=1563439736156)
* [首页](/?t=1563956863130)
* 开发文档
* [快速体验](files/10010_快速体验.md?t=1563439736157)
* [项目接入到SOP](files/10011_项目接入到SOP.md?t=1563439736174)
* [新增接口](files/10020_新增接口.md?t=1563439736174)
* [业务参数校验](files/10030_业务参数校验.md?t=1563439736174)
* [错误处理](files/10040_错误处理.md?t=1563439736174)
* [编写文档](files/10041_编写文档.md?t=1563439736174)
* [接口交互详解](files/10050_接口交互详解.md?t=1563439736174)
* [easyopen支持](files/10070_easyopen支持.md?t=1563439736175)
* [使用签名校验工具](files/10080_使用签名校验工具.md?t=1563439736175)
* [ISV管理](files/10085_ISV管理.md?t=1563439736175)
* [自定义路由](files/10086_自定义路由.md?t=1563439736175)
* [路由授权](files/10090_路由授权.md?t=1563439736175)
* [接口限流](files/10092_接口限流.md?t=1563439736175)
* [监控日志](files/10093_监控日志.md?t=1563439736175)
* [SDK开发](files/10095_SDK开发.md?t=1563439736175)
* [使用SpringCloudGateway](files/10096_使用SpringCloudGateway.md?t=1563439736176)
* [应用授权](files/10097_应用授权.md?t=1563439736176)
* [更改数据节点名称](files/10099_更改数据节点名称.md?t=1563439736176)
* [对接前端](files/10100_对接前端.md?t=1563439736176)
* [自定义过滤器](files/10102_自定义过滤器.md?t=1563439736176)
* [文件上传](files/10104_文件上传.md?t=1563439736176)
* [nacos注册中心](files/10106_nacos注册中心.md?t=1563439736176)
* [扩展其它注册中心](files/10107_扩展其它注册中心.md?t=1563439736176)
* [配置Sleuth链路追踪](files/10109_配置Sleuth链路追踪.md?t=1563439736176)
* [快速体验](files/10010_快速体验.md?t=1563956863132)
* [项目接入到SOP](files/10011_项目接入到SOP.md?t=1563956863153)
* [新增接口](files/10020_新增接口.md?t=1563956863153)
* [业务参数校验](files/10030_业务参数校验.md?t=1563956863153)
* [错误处理](files/10040_错误处理.md?t=1563956863153)
* [编写文档](files/10041_编写文档.md?t=1563956863153)
* [接口交互详解](files/10050_接口交互详解.md?t=1563956863153)
* [easyopen支持](files/10070_easyopen支持.md?t=1563956863154)
* [使用签名校验工具](files/10080_使用签名校验工具.md?t=1563956863154)
* [ISV管理](files/10085_ISV管理.md?t=1563956863154)
* [自定义路由](files/10086_自定义路由.md?t=1563956863154)
* [路由授权](files/10090_路由授权.md?t=1563956863154)
* [接口限流](files/10092_接口限流.md?t=1563956863154)
* [监控日志](files/10093_监控日志.md?t=1563956863154)
* [SDK开发](files/10095_SDK开发.md?t=1563956863154)
* [使用SpringCloudGateway](files/10096_使用SpringCloudGateway.md?t=1563956863154)
* [应用授权](files/10097_应用授权.md?t=1563956863155)
* [更改数据节点名称](files/10099_更改数据节点名称.md?t=1563956863155)
* [传统web开发](files/10100_传统web开发.md?t=1563956863155)
* [自定义过滤器](files/10102_自定义过滤器.md?t=1563956863155)
* [文件上传](files/10104_文件上传.md?t=1563956863155)
* [nacos注册中心](files/10106_nacos注册中心.md?t=1563956863155)
* [扩展其它注册中心](files/10107_扩展其它注册中心.md?t=1563956863155)
* [配置Sleuth链路追踪](files/10109_配置Sleuth链路追踪.md?t=1563956863155)
* 原理分析
* [原理分析之@ApiMapping](files/90010_原理分析之@ApiMapping.md?t=1563439736177)
* [原理分析之路由存储](files/90011_原理分析之路由存储.md?t=1563439736177)
* [原理分析之如何路由](files/90012_原理分析之如何路由.md?t=1563439736177)
* [原理分析之文档归纳](files/90013_原理分析之文档归纳.md?t=1563439736177)
* [常见问题](files/90100_常见问题.md?t=1563439736177)
* [原理分析之@ApiMapping](files/90010_原理分析之@ApiMapping.md?t=1563956863156)
* [原理分析之路由存储](files/90011_原理分析之路由存储.md?t=1563956863156)
* [原理分析之如何路由](files/90012_原理分析之如何路由.md?t=1563956863156)
* [原理分析之文档归纳](files/90013_原理分析之文档归纳.md?t=1563956863156)
* [常见问题](files/90100_常见问题.md?t=1563956863156)

@ -1,41 +1,40 @@
# 对接前端
# 传统web开发
开放平台一般对接服务端应用,开发者使用SDK来调用接口。
理论上来说,只要是客户端程序,都可以调用网关接口。因此,同样可以对接前端应用(H5、小程序、App)。
本篇介绍如何使用SOP进行传统web服务开发,即对接前端应用(H5、小程序、App)。
针对H5页面,配置方式如下:
- 网关关闭签名校验。`ApiConfig.getInstance().setIgnoreValidate(true);`
由于是网页调用接口,把秘钥放在前端意义不大,干脆直接关闭签名校验。
- 设置统一的数据节点
- 网关ZuulConfig继承WebappZuulConfiguration类
```java
@Configuration
public class ZuulConfig extends AlipayZuulConfiguration {
public class ZuulConfig extends WebappZuulConfiguration {
static {
...
ApiConfig.getInstance().setIgnoreValidate(true);
ApiConfig.getInstance().setDataNameBuilder(new CustomDataNameBuilder());
...
new ManagerInitializer();
}
}
```
设置,网关统一的返回结果如下:
设置完毕,网关不在进行签名验证,网关统一的返回结果如下:
```json
{
"result": {
...
},
"sign": "xxxxx"
}
}
```
- 微服务OpenServiceConfig继承WebappServiceConfiguration类
```java
public class OpenServiceConfig extends WebappServiceConfiguration {
...
}
```
其它内容不变
- 封装请求工具【可选】
封装请求,方便调用,针对vue的封装如下:

@ -30,7 +30,7 @@
<dependency>
<groupId>com.gitee.sop</groupId>
<artifactId>sop-registry-api</artifactId>
<version>1.13.0-SNAPSHOT</version>
<version>1.13.1-SNAPSHOT</version>
</dependency>
<!-- easyopen starter -->

@ -5,7 +5,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>com.gitee.sop</groupId>
<artifactId>sop-common</artifactId>
<version>1.13.0-SNAPSHOT</version>
<version>1.13.1-SNAPSHOT</version>
<packaging>pom</packaging>
<properties>

@ -5,11 +5,11 @@
<parent>
<groupId>com.gitee.sop</groupId>
<artifactId>sop-common</artifactId>
<version>1.13.0-SNAPSHOT</version>
<version>1.13.1-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>sop-gateway-common</artifactId>
<version>1.13.0-SNAPSHOT</version>
<version>1.13.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>sop-gateway-common</name>

@ -1,5 +1,7 @@
package com.gitee.sop.gatewaycommon.util;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
@ -35,6 +37,9 @@ public class RequestUtil {
public static final String MULTIPART = "multipart/";
private static final String UTF8 = "UTF-8";
private static final String IP_UNKNOWN = "unknown";
private static final String IP_LOCAL = "127.0.0.1";
private static final int IP_LEN = 15;
/**
* 将get类型的参数转换成map
@ -114,6 +119,31 @@ public class RequestUtil {
return params;
}
/**
* 转换json请求到Map
* @param request 请求类型为application/json的Request
* @return 返回Map
*/
public static Map<String, String> convertJsonRequestToMap(HttpServletRequest request) {
try {
String text = getText(request);
JSONObject parseObject = JSON.parseObject(text);
Map<String, String> params = new HashMap<>(parseObject.size());
for (Map.Entry<String, Object> entry : parseObject.entrySet()) {
params.put(entry.getKey(), String.valueOf(entry.getValue()));
}
return params;
} catch (IOException e) {
log.error("解析json请求失败", e);
return Collections.emptyMap();
}
}
/**
* 是否是文件上传请求
* @param request 请求
* @return true
*/
public static boolean isMultipart(HttpServletRequest request) {
String contentType = request.getContentType();
// Don't use this filter on GET method
@ -130,29 +160,32 @@ public class RequestUtil {
public static String getIP(HttpServletRequest request) {
String ipAddress = request.getHeader("x-forwarded-for");
if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
if (ipAddress == null || ipAddress.length() == 0 || IP_UNKNOWN.equalsIgnoreCase(ipAddress)) {
ipAddress = request.getHeader("Proxy-Client-IP");
}
if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
if (ipAddress == null || ipAddress.length() == 0 || IP_UNKNOWN.equalsIgnoreCase(ipAddress)) {
ipAddress = request.getHeader("WL-Proxy-Client-IP");
}
if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
if (ipAddress == null || ipAddress.length() == 0 || IP_UNKNOWN.equalsIgnoreCase(ipAddress)) {
ipAddress = request.getRemoteAddr();
if ("127.0.0.1".equals(ipAddress)) {
if (IP_LOCAL.equals(ipAddress)) {
// 根据网卡取本机配置的IP
try {
InetAddress inet = InetAddress.getLocalHost();
ipAddress = inet.getHostAddress();
} catch (UnknownHostException var3) {
} catch (UnknownHostException e) {
// ignore
}
}
}
if (ipAddress != null && ipAddress.length() > 15 && ipAddress.indexOf(",") > 0) {
// 对于通过多个代理的情况,第一个IP为客户端真实IP,多个IP按照','分割
if (ipAddress != null && ipAddress.length() > IP_LEN) {
if (ipAddress.indexOf(",") > 0) {
ipAddress = ipAddress.substring(0, ipAddress.indexOf(","));
}
}
return ipAddress;
}
}

@ -6,6 +6,7 @@ import com.gitee.sop.gatewaycommon.manager.RouteRepositoryContext;
import com.gitee.sop.gatewaycommon.zuul.filter.ErrorFilter;
import com.gitee.sop.gatewaycommon.zuul.filter.FormBodyWrapperFilterExt;
import com.gitee.sop.gatewaycommon.zuul.filter.PostResultFilter;
import com.gitee.sop.gatewaycommon.zuul.filter.PreHttpServletRequestWrapperFilter;
import com.gitee.sop.gatewaycommon.zuul.filter.PreLimitFilter;
import com.gitee.sop.gatewaycommon.zuul.filter.PreValidateFilter;
import com.gitee.sop.gatewaycommon.zuul.filter.Servlet30WrapperFilterExt;
@ -43,6 +44,11 @@ public class BaseZuulConfiguration extends AbstractConfiguration {
return zuulRouteRepository;
}
@Bean
PreHttpServletRequestWrapperFilter preHttpServletRequestWrapperFilter() {
return new PreHttpServletRequestWrapperFilter();
}
@Bean
FormBodyWrapperFilterExt formBodyWrapperFilterExt() {
return new FormBodyWrapperFilterExt();

@ -0,0 +1,18 @@
package com.gitee.sop.gatewaycommon.zuul.configuration;
import com.gitee.sop.gatewaycommon.bean.ApiConfig;
import com.gitee.sop.gatewaycommon.result.CustomDataNameBuilder;
/**
* 支持传统webapp开发没有签名验证
*
* @author tanghc
*/
public class WebappZuulConfiguration extends BaseZuulConfiguration {
static {
ApiConfig.getInstance().setIgnoreValidate(true);
ApiConfig.getInstance().setDataNameBuilder(new CustomDataNameBuilder());
ApiConfig.getInstance().setShowReturnSign(false);
}
}

@ -14,6 +14,12 @@ public abstract class BaseZuulFilter extends ZuulFilter {
protected Logger log = LoggerFactory.getLogger(getClass());
public static final int HTTP_SERVLET_REQUEST_WRAPPER_FILTER_ORDER = -2000;
public static final int SERVLET_30_WRAPPER_FILTER_ORDER = HTTP_SERVLET_REQUEST_WRAPPER_FILTER_ORDER + 1;
public static final int FORM_BODY_WRAPPER_FILTER_ORDER = SERVLET_30_WRAPPER_FILTER_ORDER + 1;
/** 签名验证过滤 */
public static final int PRE_VALIDATE_FILTER_ORDER = -1000;

@ -1,26 +1,25 @@
package com.gitee.sop.gatewaycommon.zuul.filter;
import com.gitee.sop.gatewaycommon.util.RequestUtil;
import com.netflix.zuul.context.RequestContext;
import org.springframework.cloud.netflix.zuul.filters.pre.FormBodyWrapperFilter;
import javax.servlet.http.HttpServletRequest;
/**
* @author tanghc
*/
public class FormBodyWrapperFilterExt extends FormBodyWrapperFilter {
@Override
public boolean shouldFilter() {
RequestContext ctx = RequestContext.getCurrentContext();
HttpServletRequest request = ctx.getRequest();
// 如果是文件上传请求,不需要包装
if (RequestUtil.isMultipart(request)) {
return false;
} else {
return super.shouldFilter();
}
public int filterOrder() {
return BaseZuulFilter.FORM_BODY_WRAPPER_FILTER_ORDER;
}
// @Override
// public boolean shouldFilter() {
// RequestContext ctx = RequestContext.getCurrentContext();
// HttpServletRequest request = ctx.getRequest();
// // 如果是文件上传请求,不需要包装
// if (RequestUtil.isMultipart(request)) {
// return false;
// } else {
// return super.shouldFilter();
// }
// }
}

@ -0,0 +1,31 @@
package com.gitee.sop.gatewaycommon.zuul.filter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
import com.netflix.zuul.http.HttpServletRequestWrapper;
import javax.servlet.http.HttpServletRequest;
/**
* 包装一下Request使得request.getInputStream()方法可以调用多次
* @author tanghc
*/
public class PreHttpServletRequestWrapperFilter extends BaseZuulFilter {
@Override
protected FilterType getFilterType() {
return FilterType.PRE;
}
@Override
protected int getFilterOrder() {
return HTTP_SERVLET_REQUEST_WRAPPER_FILTER_ORDER;
}
@Override
protected Object doRun(RequestContext requestContext) throws ZuulException {
HttpServletRequest request = requestContext.getRequest();
HttpServletRequestWrapper wrapper = new HttpServletRequestWrapper(request);
requestContext.setRequest(wrapper);
return null;
}
}

@ -1,24 +1,25 @@
package com.gitee.sop.gatewaycommon.zuul.filter;
import com.gitee.sop.gatewaycommon.util.RequestUtil;
import com.netflix.zuul.context.RequestContext;
import org.springframework.cloud.netflix.zuul.filters.pre.Servlet30WrapperFilter;
import javax.servlet.http.HttpServletRequest;
/**
* @author tanghc
*/
public class Servlet30WrapperFilterExt extends Servlet30WrapperFilter {
@Override
public boolean shouldFilter() {
RequestContext ctx = RequestContext.getCurrentContext();
HttpServletRequest request = ctx.getRequest();
// 如果是文件上传请求,不需要包装
if (RequestUtil.isMultipart(request)) {
return false;
} else {
return super.shouldFilter();
}
public int filterOrder() {
return BaseZuulFilter.SERVLET_30_WRAPPER_FILTER_ORDER;
}
// @Override
// public boolean shouldFilter() {
// RequestContext ctx = RequestContext.getCurrentContext();
// HttpServletRequest request = ctx.getRequest();
// // 如果是文件上传请求,不需要包装
// if (RequestUtil.isMultipart(request)) {
// return false;
// } else {
// return super.shouldFilter();
// }
// }
}

@ -1,21 +1,15 @@
package com.gitee.sop.gatewaycommon.zuul.param;
import com.gitee.sop.gatewaycommon.bean.SopConstants;
import com.gitee.sop.gatewaycommon.message.ErrorEnum;
import com.gitee.sop.gatewaycommon.param.ApiParam;
import com.gitee.sop.gatewaycommon.param.BaseParamBuilder;
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.fileupload.servlet.ServletFileUpload;
import org.apache.commons.io.IOUtils;
import org.springframework.http.MediaType;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.util.Map;
/**
@ -33,32 +27,24 @@ public class ZuulParamBuilder extends BaseParamBuilder<RequestContext> {
@Override
public Map<String, String> buildRequestParams(RequestContext ctx) {
HttpServletRequest request = ctx.getRequest();
Map<String, String> params = null;
Map<String, String> params;
if (GET.equalsIgnoreCase(request.getMethod())) {
params = RequestUtil.convertRequestParamsToMap(request);
} else {
String contentType = request.getContentType();
if (contentType == null) {
contentType = "";
}
contentType = contentType.toLowerCase();
// json或者纯文本形式
if (contentType.contains(CONTENT_TYPE_JSON) || contentType.contains(CONTENT_TYPE_TEXT)) {
throw ErrorEnum.ISV_INVALID_CONTENT_TYPE.getErrorMeta().getException();
params = RequestUtil.convertJsonRequestToMap(request);
} else if (ServletFileUpload.isMultipartContent(request)) {
MultipartRequestWrapper wrapper = new MultipartRequestWrapper(request);
ctx.setRequest(wrapper);
ctx.getZuulRequestHeaders().put("content-type", wrapper.getContentType());
params = RequestUtil.convertMultipartRequestToMap(wrapper);
params = RequestUtil.convertMultipartRequestToMap(request);
} else {
params = RequestUtil.convertRequestParamsToMap(request);
}
}
return params;
}
@ -78,45 +64,4 @@ public class ZuulParamBuilder extends BaseParamBuilder<RequestContext> {
return apiParam;
}
public static class MultipartRequestWrapper extends HttpServletRequestWrapper {
private HttpServletRequest request;
private volatile byte[] contentData;
private MediaType contentType;
private int contentLength;
public MultipartRequestWrapper(HttpServletRequest request) {
super(request);
this.request = request;
try {
this.contentType = MediaType.valueOf(this.request.getContentType());
byte[] input = IOUtils.toByteArray(request.getInputStream());
this.contentData = input;
this.contentLength = input.length;
} catch (Exception e) {
throw new IllegalStateException("Cannot convert form data", e);
}
}
@Override
public String getContentType() {
return this.contentType.toString();
}
@Override
public int getContentLength() {
if (super.getContentLength() <= 0) {
return super.getContentLength();
}
return this.contentLength;
}
@Override
public ServletInputStream getInputStream() throws IOException {
return new ServletInputStreamWrapper(this.contentData);
}
}
}

@ -5,7 +5,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>com.gitee.sop</groupId>
<artifactId>sop-registry-api</artifactId>
<version>1.13.0-SNAPSHOT</version>
<version>1.13.1-SNAPSHOT</version>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>

@ -6,11 +6,11 @@
<parent>
<groupId>com.gitee.sop</groupId>
<artifactId>sop-common</artifactId>
<version>1.13.0-SNAPSHOT</version>
<version>1.13.1-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>sop-service-common</artifactId>
<version>1.13.0-SNAPSHOT</version>
<version>1.13.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>sop-service-common</name>

@ -33,7 +33,6 @@ public class AlipayServiceConfiguration extends BaseServiceConfiguration {
protected void doAfter() {
super.doAfter();
SopHandlerMethodArgumentResolver sopHandlerMethodArgumentResolver = ServiceConfig.getInstance().getMethodArgumentResolver();
List<HandlerMethodArgumentResolver> defaultArgumentResolvers = requestMappingHandlerAdapter.getArgumentResolvers();
sopHandlerMethodArgumentResolver.setResolvers(defaultArgumentResolvers);
sopHandlerMethodArgumentResolver.setRequestMappingHandlerAdapter(requestMappingHandlerAdapter);
}
}

@ -0,0 +1,14 @@
package com.gitee.sop.servercommon.configuration;
import com.gitee.sop.servercommon.bean.ServiceConfig;
/**
* 通用web
* @author tanghc
*/
public class WebappServiceConfiguration extends BaseServiceConfiguration {
static {
// 默认版本号为1.0
ServiceConfig.getInstance().setDefaultVersion("1.0");
}
}

@ -24,6 +24,9 @@ import java.util.Set;
@Getter
public class DefaultRequestMappingEvent implements RequestMappingEvent {
/** 接口名规则:允许字母、数字、点、下划线 */
private static final String REGEX_API_NAME = "^[a-zA-Z0-9\\.\\_\\-]+$";
private ApiMetaManager apiMetaManager;
private Environment environment;
@ -85,6 +88,7 @@ public class DefaultRequestMappingEvent implements RequestMappingEvent {
if (name == null) {
name = buildName(path);
}
this.checkApiName(name);
ServiceApiInfo.ApiMeta apiMeta = new ServiceApiInfo.ApiMeta(name, path, version);
apiMeta.setIgnoreValidate(BooleanUtils.toInteger(apiMappingInfo.isIgnoreValidate()));
apiMeta.setMergeResult(BooleanUtils.toInteger(apiMappingInfo.isMergeResult()));
@ -94,6 +98,12 @@ public class DefaultRequestMappingEvent implements RequestMappingEvent {
return null;
}
protected void checkApiName(String name) {
if (!name.matches(REGEX_API_NAME)) {
throw new IllegalArgumentException("接口名称只允许【字母、数字、点(.)、下划线(_)、减号(-)】,错误接口:" + name);
}
}
protected String buildName(String path) {
return MappingUtil.buildApiName(path);
}

@ -32,7 +32,7 @@ public class ServiceZookeeperApiMetaManager implements ApiMetaManager {
* zookeeper存放接口路由信息的根目录
*/
public static final String SOP_SERVICE_ROUTE_PATH = ServiceConstants.SOP_SERVICE_ROUTE_PATH;
public static final String PATH_START_CHAR = "/";
public static final String PATH_SPLIT = "/";
private final String routeRootPath = SOP_SERVICE_ROUTE_PATH;
@ -60,6 +60,7 @@ public class ServiceZookeeperApiMetaManager implements ApiMetaManager {
*/
protected void uploadServiceId(Environment environment) {
try {
this.checkZookeeperNode(serviceId, "serviceId(" + serviceId + ")不能有斜杠字符'/'");
ServiceRouteInfo serviceRouteInfo = this.buildServiceRouteInfo();
// 保存路径
String savePath = routeRootPath + "/" + serviceId;
@ -109,9 +110,11 @@ public class ServiceZookeeperApiMetaManager implements ApiMetaManager {
protected GatewayRouteDefinition buildGatewayRouteDefinition(ServiceApiInfo serviceApiInfo, ServiceApiInfo.ApiMeta apiMeta) {
GatewayRouteDefinition gatewayRouteDefinition = new GatewayRouteDefinition();
String routeId = apiMeta.fetchNameVersion();
this.checkZookeeperNode(routeId, "接口定义(" + routeId + ")不能有斜杠字符'/'");
// 唯一id规则:接口名 + 版本号
BeanUtils.copyProperties(apiMeta, gatewayRouteDefinition);
gatewayRouteDefinition.setId(apiMeta.fetchNameVersion());
gatewayRouteDefinition.setId(routeId);
gatewayRouteDefinition.setFilters(Collections.emptyList());
String uri = this.buildUri(serviceApiInfo, apiMeta);
String path = this.buildServletPath(serviceApiInfo, apiMeta);
@ -129,8 +132,8 @@ public class ServiceZookeeperApiMetaManager implements ApiMetaManager {
if (servletPath == null) {
servletPath = "";
}
if (!servletPath.startsWith(PATH_START_CHAR)) {
servletPath = PATH_START_CHAR + servletPath;
if (!servletPath.startsWith(PATH_SPLIT)) {
servletPath = PATH_SPLIT + servletPath;
}
return servletPath;
}
@ -181,11 +184,17 @@ public class ServiceZookeeperApiMetaManager implements ApiMetaManager {
List<GatewayRouteDefinition> routeDefinitionList = serviceRouteInfo.getRouteDefinitionList();
for (GatewayRouteDefinition routeDefinition : routeDefinitionList) {
// 父目录/子目录
String savePath = parentPath + PATH_START_CHAR + routeDefinition.getId();
String savePath = parentPath + PATH_SPLIT + routeDefinition.getId();
String routeDefinitionJson = JSON.toJSONString(routeDefinition);
log.info("上传路由配置到zookeeper,路径:{},路由数据:{}", savePath, routeDefinitionJson);
this.zookeeperTool.createOrUpdateData(savePath, routeDefinitionJson);
}
}
private void checkZookeeperNode(String path, String errorMsg) {
if (path.contains(PATH_SPLIT)) {
throw new IllegalArgumentException(errorMsg);
}
}
}

@ -11,9 +11,9 @@ import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.ModelAndViewContainer;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.support.StandardMultipartHttpServletRequest;
import org.springframework.web.multipart.MultipartHttpServletRequest;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
@ -25,16 +25,15 @@ import java.util.concurrent.ConcurrentHashMap;
*/
public class ApiArgumentResolver implements SopHandlerMethodArgumentResolver {
private final Map<MethodParameter, HandlerMethodArgumentResolver> argumentResolverCache =
new ConcurrentHashMap<>(256);
private final Map<MethodParameter, HandlerMethodArgumentResolver> argumentResolverCache = new ConcurrentHashMap<>(256);
private final ParamValidator paramValidator = new ServiceParamValidator();
private ParamValidator paramValidator = new ServiceParamValidator();
private List<HandlerMethodArgumentResolver> argumentResolvers = Collections.emptyList();
private RequestMappingHandlerAdapter requestMappingHandlerAdapter;
@Override
public void setResolvers(List<HandlerMethodArgumentResolver> resolvers) {
this.argumentResolvers = resolvers;
public void setRequestMappingHandlerAdapter(RequestMappingHandlerAdapter requestMappingHandlerAdapter) {
this.requestMappingHandlerAdapter = requestMappingHandlerAdapter;
}
@Override
@ -61,18 +60,19 @@ public class ApiArgumentResolver implements SopHandlerMethodArgumentResolver {
}
}
/**
* 获取参数对象将request中的参数绑定到实体对象中去
*
* @param methodParameter
* @param nativeWebRequest
* @return
* @return 没有返回null
*/
protected Object getParamObject(MethodParameter methodParameter, NativeWebRequest nativeWebRequest) {
String bizContent = nativeWebRequest.getParameter(ParamNames.BIZ_CONTENT_NAME);
Class<?> parameterType = methodParameter.getParameterType();
Object bizObj = null;
if (bizContent != null) {
Class<?> parameterType = methodParameter.getParameterType();
bizObj = JSON.parseObject(bizContent, parameterType);
}
this.bindUploadFile(bizObj, nativeWebRequest);
@ -80,7 +80,7 @@ public class ApiArgumentResolver implements SopHandlerMethodArgumentResolver {
}
/**
* 将文件绑定到
* 上传文件对象绑定到属性中
*
* @param bizObj 业务参数
* @param nativeWebRequest
@ -89,9 +89,9 @@ public class ApiArgumentResolver implements SopHandlerMethodArgumentResolver {
if (bizObj == null) {
return;
}
if (this.isMultipartRequest(nativeWebRequest)) {
Object nativeRequest = nativeWebRequest.getNativeRequest();
if (nativeRequest instanceof StandardMultipartHttpServletRequest) {
StandardMultipartHttpServletRequest request = (StandardMultipartHttpServletRequest) nativeRequest;
MultipartHttpServletRequest request = (MultipartHttpServletRequest) nativeRequest;
Class<?> bizClass = bizObj.getClass();
ReflectionUtils.doWithFields(bizClass, field -> {
ReflectionUtils.makeAccessible(field);
@ -102,10 +102,21 @@ public class ApiArgumentResolver implements SopHandlerMethodArgumentResolver {
}
}
protected boolean isMultipartRequest(NativeWebRequest nativeWebRequest) {
return nativeWebRequest.getNativeRequest() instanceof MultipartHttpServletRequest;
}
/**
* 获取其它的参数解析器
*
* @param parameter 业务参数
* @return 返回合适参数解析器没有返回null
*/
protected HandlerMethodArgumentResolver getOtherArgumentResolver(MethodParameter parameter) {
HandlerMethodArgumentResolver result = this.argumentResolverCache.get(parameter);
if (result == null) {
for (HandlerMethodArgumentResolver methodArgumentResolver : this.argumentResolvers) {
List<HandlerMethodArgumentResolver> argumentResolvers = this.requestMappingHandlerAdapter.getArgumentResolvers();
for (HandlerMethodArgumentResolver methodArgumentResolver : argumentResolvers) {
if (methodArgumentResolver instanceof SopHandlerMethodArgumentResolver) {
continue;
}
@ -119,4 +130,7 @@ public class ApiArgumentResolver implements SopHandlerMethodArgumentResolver {
return result;
}
public void setParamValidator(ParamValidator paramValidator) {
this.paramValidator = paramValidator;
}
}

@ -1,12 +1,11 @@
package com.gitee.sop.servercommon.param;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import java.util.List;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter;
/**
* @author tanghc
*/
public interface SopHandlerMethodArgumentResolver extends HandlerMethodArgumentResolver {
void setResolvers(List<HandlerMethodArgumentResolver> resolvers);
void setRequestMappingHandlerAdapter(RequestMappingHandlerAdapter requestMappingHandlerAdapter);
}

@ -26,7 +26,7 @@
<dependency>
<groupId>com.gitee.sop</groupId>
<artifactId>sop-service-common</artifactId>
<version>1.13.0-SNAPSHOT</version>
<version>1.13.1-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>

@ -24,7 +24,7 @@
<dependency>
<groupId>com.gitee.sop</groupId>
<artifactId>sop-service-common</artifactId>
<version>1.13.0-SNAPSHOT</version>
<version>1.13.1-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.gitee.sop</groupId>

@ -29,7 +29,7 @@
<dependency>
<groupId>com.gitee.sop</groupId>
<artifactId>sop-service-common</artifactId>
<version>1.13.0-SNAPSHOT</version>
<version>1.13.1-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>

@ -20,7 +20,7 @@
<dependency>
<groupId>com.gitee.sop</groupId>
<artifactId>sop-service-common</artifactId>
<version>1.13.0-SNAPSHOT</version>
<version>1.13.1-SNAPSHOT</version>
</dependency>
<!-- eureka 服务发现 -->
<dependency>

@ -24,7 +24,7 @@
<dependency>
<groupId>com.gitee.sop</groupId>
<artifactId>sop-service-common</artifactId>
<version>1.13.0-SNAPSHOT</version>
<version>1.13.1-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.gitee.sop</groupId>

@ -105,10 +105,10 @@ public class AlipayController {
public StoryVO getStory2(StoryParam story, HttpServletRequest request) {
log.info("获取故事信息参数, story: {}", story);
// 获取其它参数
System.out.println(request.getParameter("app_id"));
String app_id = request.getParameter("app_id");
StoryVO storyVO = new StoryVO();
storyVO.id = 1L;
storyVO.name = "白雪公主";
storyVO.name = "白雪公主, app_id:" + app_id;
storyVO.gmt_create = new Date();
return storyVO;
}

@ -0,0 +1,94 @@
package com.gitee.sop.storyweb.controller;
import com.gitee.sop.servercommon.annotation.ApiMapping;
import com.gitee.sop.servercommon.util.UploadUtil;
import lombok.Data;
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.RestController;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletRequest;
import java.math.BigDecimal;
import java.util.Collection;
/**
* 传统web开发实例
* @author tanghc
*/
@RestController
@RequestMapping("food")
public class TraditionalWebappController {
// http://localhost:8081/getFoodById?id=1 网关入口
// http://localhost:2222/food/getFoodById/?id=12 本地入口
@ApiMapping(value = "getFoodById", method = RequestMethod.GET)
public Food getFoodById(Integer id) {
Food food = new Food();
food.setId(id);
food.setName("香蕉");
food.setPrice(new BigDecimal(20.00));
return food;
}
// http://localhost:8081/getFoodById?id=2&version=1.1 网关入口
// http://localhost:2222/food/getFoodById/?id=12&version=1.1
@ApiMapping(value = "getFoodById", method = RequestMethod.GET, version = "1.1")
public Food getFoodById2(Integer id) {
Food food = new Food();
food.setId(id);
food.setName("香蕉2");
food.setPrice(new BigDecimal(22.00));
return food;
}
// http://localhost:8081/getFoodByObj?id=2
@ApiMapping(value = "getFoodByObj", method = RequestMethod.GET)
public Food getFoodByObj(Food food) {
return food;
}
// http://localhost:8081/saveFood
@ApiMapping(value = "saveFood", method = RequestMethod.POST)
public Food saveFood(@RequestBody Food food) {
food.setId(3);
return food;
}
@ApiMapping(value = "foodUpload", method = RequestMethod.POST)
public Food upload(Food food, HttpServletRequest request) {
// 获取上传的文件
Collection<MultipartFile> uploadFiles = UploadUtil.getUploadFiles(request);
StringBuilder sb = new StringBuilder();
for (MultipartFile multipartFile : uploadFiles) {
sb.append(multipartFile.getOriginalFilename());
}
food.setId(4);
food.setName("文件名称+" + sb.toString());
return food;
}
@ApiMapping(value = "foodUpload3", method = RequestMethod.POST)
public Food upload3(Food food, MultipartFile file) {
food.setId(5);
food.setName("文件名称+" + file.getOriginalFilename());
return food;
}
@RequestMapping(value = "foodUpload2", method = RequestMethod.POST)
public Food upload2(Food food, MultipartFile file) {
food.setId(4);
food.setName("文件名称2+" + file.getOriginalFilename());
return food;
}
@Data
public static class Food {
private Integer id;
private String name;
private BigDecimal price;
}
}

@ -23,7 +23,7 @@
<dependency>
<groupId>com.gitee.sop</groupId>
<artifactId>sop-gateway-common</artifactId>
<version>1.13.0-SNAPSHOT</version>
<version>1.13.1-SNAPSHOT</version>
</dependency>
<!-- ↓↓↓ 使用spring cloud zuul ↓↓↓ -->

@ -32,4 +32,15 @@ public class RedirectController {
request.getRequestDispatcher(path).forward(request, response);
}
@RequestMapping("/{method}")
public void redirect2(
@PathVariable("method") String method
, HttpServletRequest request
, HttpServletResponse response
) throws ServletException, IOException {
request.setAttribute(SopConstants.REDIRECT_METHOD_KEY, method);
request.setAttribute(SopConstants.REDIRECT_VERSION_KEY, "1.0");
request.getRequestDispatcher(path).forward(request, response);
}
}

@ -6,6 +6,7 @@ import okhttp3.Cookie;
import okhttp3.CookieJar;
import okhttp3.FormBody;
import okhttp3.HttpUrl;
import okhttp3.MediaType;
import okhttp3.MultipartBody;
import okhttp3.OkHttpClient;
import okhttp3.Request;
@ -22,13 +23,21 @@ import java.util.Set;
import java.util.concurrent.TimeUnit;
/**
* HTTP请求工具
*
* @author tanghc
*/
public class OpenHttp {
private static final MediaType MEDIA_TYPE_JSON = MediaType.parse("application/json; charset=utf-8");
private Map<String, List<Cookie>> cookieStore = new HashMap<String, List<Cookie>>();
private OkHttpClient httpClient;
public OpenHttp() {
this(new OpenConfig());
}
public OpenHttp(OpenConfig openConfig) {
this.initHttpClient(openConfig);
}
@ -68,6 +77,33 @@ public class OpenHttp {
return response.body().string();
}
/**
* 请求json数据contentType=application/json
* @param url 请求路径
* @param json json数据
* @param header header
* @return 返回响应结果
* @throws IOException
*/
public String requestJson(String url, String json, Map<String, String> header) throws IOException {
RequestBody body = RequestBody.create(MEDIA_TYPE_JSON, json);
Request.Builder requestBuilder = new Request.Builder()
.url(url)
.post(body);
// 添加header
addHeader(requestBuilder, header);
Request request = requestBuilder.build();
Response response = httpClient
.newCall(request)
.execute();
try {
return response.body().string();
} finally {
response.close();
}
}
/**
* 提交表单
*
@ -78,15 +114,8 @@ public class OpenHttp {
* @return
* @throws IOException
*/
public String requestFormBody(String url, Map<String, String> form, Map<String, String> header, String method) throws IOException {
FormBody.Builder paramBuilder = new FormBody.Builder(StandardCharsets.UTF_8);
for (Map.Entry<String, String> entry : form.entrySet()) {
paramBuilder.add(entry.getKey(), entry.getValue());
}
FormBody formBody = paramBuilder.build();
Request.Builder requestBuilder = new Request.Builder()
.url(url)
.method(method, formBody);
public String request(String url, Map<String, String> form, Map<String, String> header, String method) throws IOException {
Request.Builder requestBuilder = buildRequestBuilder(url, form, method);
// 添加header
addHeader(requestBuilder, header);
@ -101,6 +130,47 @@ public class OpenHttp {
}
}
public static Request.Builder buildRequestBuilder(String url, Map<String, String> form, String method) {
switch (method) {
case "get":
return new Request.Builder()
.url(buildHttpUrl(url, form))
.get();
case "head":
return new Request.Builder()
.url(buildHttpUrl(url, form))
.head();
case "put":
return new Request.Builder()
.url(url)
.put(buildFormBody(form));
case "delete":
return new Request.Builder()
.url(url)
.delete(buildFormBody(form));
default:
return new Request.Builder()
.url(url)
.post(buildFormBody(form));
}
}
public static HttpUrl buildHttpUrl(String url, Map<String, String> form) {
HttpUrl.Builder urlBuilder = HttpUrl.parse(url).newBuilder();
for (Map.Entry<String, String> entry : form.entrySet()) {
urlBuilder.addQueryParameter(entry.getKey(), entry.getValue());
}
return urlBuilder.build();
}
public static FormBody buildFormBody(Map<String, String> form) {
FormBody.Builder paramBuilder = new FormBody.Builder(StandardCharsets.UTF_8);
for (Map.Entry<String, String> entry : form.entrySet()) {
paramBuilder.add(entry.getKey(), entry.getValue());
}
return paramBuilder.build();
}
/**
* 提交表单并且上传文件
*
@ -162,4 +232,5 @@ public class OpenHttp {
this.httpClient = httpClient;
}
}

@ -42,7 +42,9 @@ public class OpenRequest {
}
return openHttp.get(url, header);
} else {
return openHttp.requestFormBody(url, form, header, requestMethod.name());
return openHttp.request(url, form, header, requestMethod.name());
// 下面这种方式也可以,以application/json方式请求
// return openHttp.requestJson(url, JSON.toJSONString(form), header);
}
}
} catch (IOException e) {

@ -25,7 +25,7 @@
<dependency>
<groupId>com.gitee.sop</groupId>
<artifactId>sop-registry-api</artifactId>
<version>1.13.0-SNAPSHOT</version>
<version>1.13.1-SNAPSHOT</version>
</dependency>
<dependency>

@ -24,7 +24,6 @@ import java.util.concurrent.TimeUnit;
* @author tanghc
*/
public class HttpTool {
private static final String METHOD_GET = "get";
private Map<String, List<Cookie>> cookieStore = new HashMap<String, List<Cookie>>();
private OkHttpClient httpClient;

Loading…
Cancel
Save