tanghc 5 years ago
parent 709186b0da
commit 55ca508905
  1. 60
      doc/docs/_sidebar.md
  2. 83
      doc/docs/files/10100_传统web开发.md
  3. 21
      sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/util/MappingUtil.java
  4. 5
      sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/bean/ServiceConfig.java
  5. 1
      sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/configuration/WebappServiceConfiguration.java
  6. 5
      sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/manager/DefaultRequestMappingEvent.java
  7. 12
      sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/mapping/MappingUtil.java
  8. 10
      sop-example/sop-story/sop-story-web/src/main/java/com/gitee/sop/storyweb/controller/TraditionalWebappController.java
  9. 3
      sop-gateway/src/main/java/com/gitee/sop/gateway/SopGatewayApplication.java
  10. 11
      sop-gateway/src/main/java/com/gitee/sop/gateway/controller/RedirectController.java
  11. 44
      sop-gateway/src/main/java/com/gitee/sop/gateway/controller/RestServlet.java

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

@ -1,5 +1,7 @@
# 传统web开发
SOP既可以作为网关服务开发,又可以作为传统的webapp服务开发,传统web开发意思是像普通的web开发那样提供restful接口,没有签名校验功能。
本篇介绍如何使用SOP进行传统web服务开发,即对接前端应用(H5、小程序、App)。
- 网关ZuulConfig继承WebappZuulConfiguration类
@ -35,6 +37,51 @@ public class OpenServiceConfig extends WebappServiceConfiguration {
其它内容不变
- 前端app请求网关
请求格式为:`http://ip:port/rest/your_path`,其中`http://ip:port/rest/`为固定部分,后面跟微服务请求路径。
注意:为了确保各个微服务路径不冲突,必须保证类上方定义的`@RequestMapping`内容唯一,不与其它微服务重复。
下面是一个微服务的接口例子
```java
@RestController
@RequestMapping("food")
public class TraditionalWebappController {
@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;
}
// 加版本号
@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;
}
}
```
这是一个`食品服务`例子,假设网关ip为10.0.1.11,端口8081;食品服务ip为10.0.1.22,端口2222
1. 网关访问:`http://10.0.1.11:8081/rest/food/getFoodById?id=2`。加版本号:`http://localhost:8081/rest/food/getFoodById?id=2&version=1.1`
2. 本地访问:`http://10.0.1.22:2222/food/getFoodById/?id=2`
更多例子,可查看源码类:`TraditionalWebappController.java`
由此可见,对于前端调用者来说,它把网关看做一个大服务,只访问网关提供的请求,不需要关心网关后面的路由转发。网关后面各个微服务独自管理,
微服务之间的调用可以使用dubbo或feign,有了版本号的管理,可以做到服务的平滑升级,对用户来说都是无感知的。结合SOP-Admin提供的上下线功能,
可实现预发布环境功能。
- 封装请求工具【可选】
封装请求,方便调用,针对vue的封装如下:
@ -46,24 +93,20 @@ import axios from 'axios'
const client = axios.create({
baseURL: process.env.BASE_API, // api 的 base_url
timeout: 5000, // 请求超时时间
headers: { 'Content-Type': 'application/x-www-form-urlencoded' }
headers: { 'Content-Type': 'application/json' }
})
const RequestUtil = {
/**
* 请求接口
* @param method 接口名,如:goods.get,goods.get
* @param version 版本号,如:1.0
* @param url 请求路径,如http://localhost:8081/rest/food/getFoodById
* @param data 请求数据,json格式
* @param callback 成功回调
* @param errorCallback 失败回调
*/
post: function(method, version, data, callback, errorCallback) {
client.post(''/* 这里不用填 */, {
method: method,
version: version,
biz_content: JSON.stringify(data)
}).then(function(response) {
post: function(url, data, callback, errorCallback) {
client.post(url, data)
.then(function(response) {
const resp = response.result
const code = resp.code
// 成功,网关正常且业务正常
@ -94,23 +137,18 @@ jQuery版本如下:
var RequestUtil = {
/**
* 请求接口
* @param method 接口名,如:goods.get,goods.get
* @param version 版本号,如:1.0
* @param url 请求路径,如http://localhost:8081/rest/food/getFoodById
* @param data 请求数据,json格式
* @param callback 成功回调
* @param errorCallback 失败回调
*/
post: function(method, version, data, callback, errorCallback) {
post: function(url, data, callback, errorCallback) {
$.ajax({
url: 'http://localhost:8081/api' // 网关url
, type: 'post'
, headers: { 'Content-Type': 'application/x-www-form-urlencoded' }
, data: {
method: method,
version: version,
biz_content: JSON.stringify(data)
}
,success:function(response){
, headers: { 'Content-Type': 'application/json' }
, data: data
,success:function(response) {
var resp = response.result
var code = resp.code
// 成功,网关正常且业务正常
@ -137,13 +175,8 @@ $(function () {
id: 1
,name: '葫芦娃'
}
RequestUtil.post('alipay.story.get', '1.0', data, function (result) {
RequestUtil.post('http://localhost:8081/rest/food/getFoodById', data, function (result) {
console.log(result)
});
})
```
1.7.1开始支持接口名版本号放在url后面,规则:`http://host:port/{method}/{version}/`(最后的`/`不能少),如:`http://localhost:8081/story.demo.get/1.0/`
等同于:`http://localhost:8081/api?method=story.demo.get&version=1.0`。
把接口名版本号放在url后面的好处是调用接口一目了然,在浏览器F12调试的时候特别有用,可以一眼看到调用了哪些接口,否则将会看到全部都是api请求,需要点开查看request header才能知道到底调用了哪个接口

@ -0,0 +1,21 @@
package com.gitee.sop.gatewaycommon.util;
import org.springframework.util.StringUtils;
public class MappingUtil {
public static final char SEPARATOR_CHAR = '/';
/**
* 将springmvc接口路径转换成SOP方法名
* @param path springmvc路径/a/b,/goods/listGoods
* @return 返回接口方法名/goods/listGoods -> goods.listGoods
*/
public static String buildApiName(String path) {
path = StringUtils.trimLeadingCharacter(path, SEPARATOR_CHAR);
path = StringUtils.trimTrailingCharacter(path, SEPARATOR_CHAR);
path = path.replace(SEPARATOR_CHAR, '.');
return path;
}
}

@ -70,4 +70,9 @@ public class ServiceConfig {
*/
private boolean permission;
/**
* 是否传统web开发模式
*/
private boolean webappMode;
}

@ -10,5 +10,6 @@ public class WebappServiceConfiguration extends BaseServiceConfiguration {
static {
// 默认版本号为1.0
ServiceConfig.getInstance().setDefaultVersion("1.0");
ServiceConfig.getInstance().setWebappMode(true);
}
}

@ -1,6 +1,7 @@
package com.gitee.sop.servercommon.manager;
import com.gitee.sop.servercommon.bean.ServiceApiInfo;
import com.gitee.sop.servercommon.bean.ServiceConfig;
import com.gitee.sop.servercommon.mapping.ApiMappingInfo;
import com.gitee.sop.servercommon.mapping.ApiMappingRequestCondition;
import com.gitee.sop.servercommon.mapping.MappingUtil;
@ -83,9 +84,10 @@ public class DefaultRequestMappingEvent implements RequestMappingEvent {
ApiMappingInfo apiMappingInfo = condition.getApiMappingInfo();
String name = apiMappingInfo.getName();
String version = apiMappingInfo.getVersion();
// 方法完整的path,如: /goods/listGoods,/users/user/get
String path = patterns.iterator().next();
// 不是ApiMapping注解的接口,name属性是null
if (name == null) {
if (name == null || ServiceConfig.getInstance().isWebappMode()) {
name = buildName(path);
}
this.checkApiName(name);
@ -104,6 +106,7 @@ public class DefaultRequestMappingEvent implements RequestMappingEvent {
}
}
protected String buildName(String path) {
return MappingUtil.buildApiName(path);
}

@ -6,15 +6,19 @@ import org.springframework.util.StringUtils;
* @author tanghc
*/
public class MappingUtil {
public static final char SEPARATOR_CHAR = '/';
/**
* 将springmvc接口路径转换成SOP方法名
* @param path springmvc路径/a/b,/goods/listGoods
* @return
* @return 返回接口方法名/goods/listGoods -> goods.listGoods
*/
public static String buildApiName(String path) {
path = StringUtils.trimLeadingCharacter(path, '/');
path = StringUtils.trimTrailingCharacter(path, '/');
path = path.replace("/", ".");
path = StringUtils.trimLeadingCharacter(path, SEPARATOR_CHAR);
path = StringUtils.trimTrailingCharacter(path, SEPARATOR_CHAR);
path = path.replace(SEPARATOR_CHAR, '.');
return path;
}
}

@ -22,7 +22,7 @@ import java.util.Collection;
public class TraditionalWebappController {
// http://localhost:8081/getFoodById?id=1 网关入口
// http://localhost:8081/rest/food/getFoodById?id=1 网关入口
// http://localhost:2222/food/getFoodById/?id=12 本地入口
@ApiMapping(value = "getFoodById", method = RequestMethod.GET)
public Food getFoodById(Integer id) {
@ -33,7 +33,7 @@ public class TraditionalWebappController {
return food;
}
// http://localhost:8081/getFoodById?id=2&version=1.1 网关入口
// http://localhost:8081/rest/food/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) {
@ -44,13 +44,13 @@ public class TraditionalWebappController {
return food;
}
// http://localhost:8081/getFoodByObj?id=2
@ApiMapping(value = "getFoodByObj", method = RequestMethod.GET)
// http://localhost:8081/rest/food/get/getFoodByObj?id=2
@ApiMapping(value = "/get/getFoodByObj", method = RequestMethod.GET)
public Food getFoodByObj(Food food) {
return food;
}
// http://localhost:8081/saveFood
// http://localhost:8081/rest/food/saveFood
@ApiMapping(value = "saveFood", method = RequestMethod.POST)
public Food saveFood(@RequestBody Food food) {
food.setId(3);

@ -2,10 +2,13 @@ package com.gitee.sop.gateway;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.ServletComponentScan;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
// 开启网关功能
@EnableZuulProxy
// 扫描自定义的servlet(类上标注@WebServle)
@ServletComponentScan
@SpringBootApplication
public class SopGatewayApplication {

@ -32,15 +32,4 @@ 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);
}
}

@ -0,0 +1,44 @@
package com.gitee.sop.gateway.controller;
import com.gitee.sop.gatewaycommon.bean.SopConstants;
import com.gitee.sop.gatewaycommon.param.ParamNames;
import com.gitee.sop.gatewaycommon.util.MappingUtil;
import org.springframework.beans.factory.annotation.Value;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@WebServlet(urlPatterns = "/rest/*")
public class RestServlet extends HttpServlet {
private static final String REST_PATH = "/rest";
@Value("${zuul.servlet-path}")
private String path;
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doPost(request, response);
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String url = request.getRequestURL().toString();
int index = url.indexOf(REST_PATH);
// 取/rest的后面部分
String path = url.substring(index + REST_PATH.length());
String method = MappingUtil.buildApiName(path);
String version = request.getParameter(ParamNames.VERSION_NAME);
if (version == null) {
version = "1.0";
}
request.setAttribute(SopConstants.REDIRECT_METHOD_KEY, method);
request.setAttribute(SopConstants.REDIRECT_VERSION_KEY, version);
request.getRequestDispatcher(this.path).forward(request, response);
}
}
Loading…
Cancel
Save