diff --git a/Dockerfile b/Dockerfile index a3339e2b..15a4455b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -4,10 +4,8 @@ VOLUME /sop # 将所有应用放到一个镜像当中 ADD sop-gateway/target/*.jar sop/sop-gateway/sop-gateway.jar -ADD sop-admin/sop-admin-server/target/*.jar sop/sop-admin/sop-admin.jar -ADD sop-website/sop-website-server/target/*.jar sop/sop-website/sop-website.jar -ADD sop-auth/target/*.jar sop/sop-auth/sop-auth.jar -ADD sop-example/sop-story/target/*.jar sop/sop-story/sop-story.jar +ADD sop-admin/sop-admin-backend/backend-boot/target/*.jar sop/sop-admin/sop-admin.jar +ADD sop-example/example-story/target/*.jar sop/sop-story/sop-story.jar # 拷贝启动脚本 COPY docker-entrypoint.sh /usr/local/bin/ diff --git a/doc/.gitignore b/doc/.gitignore deleted file mode 100644 index 1739d49e..00000000 --- a/doc/.gitignore +++ /dev/null @@ -1,27 +0,0 @@ -/target/ -!.mvn/wrapper/maven-wrapper.jar - -### STS ### -.apt_generated -.classpath -.factorypath -.project -.settings -.springBeans -.sts4-cache - -### IntelliJ IDEA ### -.idea -*.iws -*.iml -*.ipr -*.png - -### NetBeans ### -/nbproject/private/ -/nbbuild/ -/dist/ -/nbdist/ -/.nb-gradle/ -/build/ - diff --git a/doc/README.md b/doc/README.md deleted file mode 100644 index e6955bf6..00000000 --- a/doc/README.md +++ /dev/null @@ -1,11 +0,0 @@ -# 开发文档 - -文档放在docs/files下,写完文档记得执行下`SidebarTest.main()`方法 - -配合gitee pages服务使用,gitee pages服务指定docs目录。 - -## 本地查看开发文档 - -- 前提:先安装好npm,[npm安装教程](https://blog.csdn.net/zhangwenwu2/article/details/52778521)。建议使用淘宝镜像。 -- 安装docsify,执行npm命令`npm i docsify-cli -g --registry=https://registry.npm.taobao.org` -- cd到当前目录,运行命令`docsify serve docs`,然后访问:`http://localhost:3000`即可查看。 diff --git a/doc/docs/README.md b/doc/docs/README.md deleted file mode 100644 index 386a1291..00000000 --- a/doc/docs/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# SOP开发文档 - -Git地址:[SOP](https://gitee.com/durcframework/SOP) diff --git a/doc/docs/_config.yml b/doc/docs/_config.yml deleted file mode 100644 index aa7fd0af..00000000 --- a/doc/docs/_config.yml +++ /dev/null @@ -1 +0,0 @@ -include: [_navbar,_sidebar] \ No newline at end of file diff --git a/doc/docs/_coverpage.md b/doc/docs/_coverpage.md deleted file mode 100644 index 55969331..00000000 --- a/doc/docs/_coverpage.md +++ /dev/null @@ -1,12 +0,0 @@ -![logo](_media/icon.svg) - -# docsify 4.6.10 - -> A magical documentation site generator. - -* Simple and lightweight (~19kB gzipped) -* No statically built html files -* Multiple themes - -[GitHub](https://github.com/QingWei-Li/docsify/) -[Get Started](#docsify) diff --git a/doc/docs/_navbar.md b/doc/docs/_navbar.md deleted file mode 100644 index deff3845..00000000 --- a/doc/docs/_navbar.md +++ /dev/null @@ -1,3 +0,0 @@ -- 关于 - - [帮助](/zh-cn/) - - [API](/) diff --git a/doc/docs/_sidebar.md b/doc/docs/_sidebar.md deleted file mode 100644 index 4ab6e4e6..00000000 --- a/doc/docs/_sidebar.md +++ /dev/null @@ -1,36 +0,0 @@ -* [首页](/?t=1616211903021) -* 开发文档 - * [快速体验](files/10010_快速体验.md?t=1616211903027) - * [项目接入到SOP](files/10011_项目接入到SOP.md?t=1616211903048) - * [新增接口](files/10020_新增接口.md?t=1616211903048) - * [开发流程](files/10021_开发流程.md?t=1616211903048) - * [用户注册](files/10022_用户注册.md?t=1616211903049) - * [业务参数校验](files/10030_业务参数校验.md?t=1616211903049) - * [错误处理](files/10040_错误处理.md?t=1616211903049) - * [编写文档](files/10041_编写文档.md?t=1616211903049) - * [接口交互详解](files/10050_接口交互详解.md?t=1616211903049) - * [使用签名校验工具](files/10080_使用签名校验工具.md?t=1616211903049) - * [ISV管理](files/10085_ISV管理.md?t=1616211903050) - * [自定义返回结果](files/10087_自定义返回结果.md?t=1616211903050) - * [自定义过滤器](files/10088_自定义过滤器.md?t=1616211903050) - * [自定义校验token](files/10089_自定义校验token.md?t=1616211903050) - * [网关拦截器](files/10090_网关拦截器.md?t=1616211903050) - * [路由授权](files/10091_路由授权.md?t=1616211903050) - * [接口限流](files/10092_接口限流.md?t=1616211903051) - * [路由监控](files/10093_路由监控.md?t=1616211903051) - * [SDK开发](files/10095_SDK开发.md?t=1616211903051) - * [应用授权](files/10097_应用授权.md?t=1616211903051) - * [提供restful接口](files/10100_提供restful接口.md?t=1616211903051) - * [文件上传](files/10104_文件上传.md?t=1616211903051) - * [配置Sleuth链路追踪](files/10109_配置Sleuth链路追踪.md?t=1616211903052) - * [预发布灰度发布](files/10110_预发布灰度发布.md?t=1616211903052) - * [动态修改请求参数](files/10111_动态修改请求参数.md?t=1616211903052) - * [使用eureka](files/10112_使用eureka.md?t=1616211903052) - * [超时设置](files/10113_超时设置.md?t=1616211903052) -* 原理分析 - * [网关性能测试](files/90001_网关性能测试.md?t=1616211903052) - * [原理分析之如何存储路由](files/90011_原理分析之如何存储路由.md?t=1616211903052) - * [原理分析之如何路由](files/90012_原理分析之如何路由.md?t=1616211903052) - * [原理分析之文档归纳](files/90013_原理分析之文档归纳.md?t=1616211903052) - * [原理分析之预发布灰度发布](files/90014_原理分析之预发布灰度发布.md?t=1616211903052) - * [常见问题](files/90100_常见问题.md?t=1616211903053) diff --git a/doc/docs/files/10010_快速体验.md b/doc/docs/files/10010_快速体验.md deleted file mode 100644 index edcce36c..00000000 --- a/doc/docs/files/10010_快速体验.md +++ /dev/null @@ -1,52 +0,0 @@ -# 快速体验 - -## 方式1 - -> 运行环境:JDK8,Maven3,[Nacos](https://nacos.io/zh-cn/docs/what-is-nacos.html),Mysql - -- 安装并启动Nacos,[安装教程](https://nacos.io/zh-cn/docs/quick-start.html) -- 执行Mysql脚本`sop.sql`(Mysql版本5.6+),5.6以下运行`sop-mysql5.6以下版本.sql` -- IDE安装lombok插件,然后打开项目(IDEA下可以打开根pom.xml,然后open as project) -- 启动网关:打开sop-gateway下的`application-dev.properties` - 1. 修改数据库`username/password` - 2. 指定nacos地址,如果nacos安装在本机则不用改 - 3. 运行`SopGatewayApplication.java` -- 启动微服务:打开`sop-example/sop-story`下的`application-dev.properties`文件 - 1. 指定nacos地址,如果nacos安装在本机则不用改 - 2. 运行`SopStoryApplication.java` -- 找到sop-test,运行`com.gitee.sop.test.AlipayClientPostTest.testGet`进行接口调用测试 - -## 方式2(docker) - -> 前提:安装好docker - -- 安装并启动Nacos,[安装教程](https://nacos.io/zh-cn/docs/quick-start.html) -- 执行Mysql脚本`sop.sql`(Mysql版本5.6+),5.6以下运行`sop-mysql5.6以下版本.sql` -- 打开`docker-entrypoint.sh`,修改mysql,nacos配置 -- 执行`docker-build.sh` -- 找到sop-test,运行`com.gitee.sop.test.AlipayClientPostTest.testGet`进行接口调用测试 - - -> - admin地址:http://ip:8082 登录账号:admin/123456 -> - 文档地址:http://ip:8083/ - -## 使用admin - -- 找到`sop-admin/sop-admin-server`工程,打开sop-admin-server下的`application-dev.properties`,修改相关配置 -- 运行`SopAdminServerApplication.java` -- 访问:`http://localhost:8082` - -登录账号:admin/123456 - -## 启动门户网站 - -见:`sop-website/sop-portal/README.md` - -## 启动文档中心/ISV后台 - -文档中心代码在sop-website工程中 - -- 确保注册中心、网关、微服务正常启动 -- 修改sop-website-server下的application-dev.properties相关配置 -- 运行WebsiteServerApplication.java -- 访问http://localhost:8083 diff --git a/doc/docs/files/10011_项目接入到SOP.md b/doc/docs/files/10011_项目接入到SOP.md deleted file mode 100644 index adb7bcb3..00000000 --- a/doc/docs/files/10011_项目接入到SOP.md +++ /dev/null @@ -1,217 +0,0 @@ -# 项目接入到SOP - -以springboot项目为例,完整项目可参考sop-example下的sop-story - -- pom.xml添加版本配置 - -```xml - -2.6.15 - -2021.0.5 - - -2021.0.5.0 -``` - -- pom.xml添加``控制版本 - -```xml - - - - org.springframework.boot - spring-boot-dependencies - ${spring-boot.version} - pom - import - - - org.springframework.cloud - spring-cloud-dependencies - ${spring-cloud.version} - pom - import - - - com.alibaba.cloud - spring-cloud-alibaba-dependencies - ${spring-cloud-alibaba.version} - pom - import - - - -``` - -- pom.xml依赖sop-service-common和nacos服务发现 - -```xml - - - com.gitee.sop - sop-service-common - 最新版本 - - - - com.alibaba.cloud - spring-cloud-starter-alibaba-nacos-discovery - -``` - -- application.properties配置文件添加 - -```properties -server.port=2222 -spring.application.name=story-service -# nacos注册中心 -spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848 -``` - -- 在springboot启动类上添加`@EnableDiscoveryClient` -- 新增一个配置类,继承`AlipayServiceConfiguration.java`,内容为空 - -```java -@Configuration -public class OpenServiceConfig extends AlipayServiceConfiguration { - -} -``` - -- 全局异常处理 - -在微服务项目的全局异常处理中添加一句:`ExceptionHolder.hold(request, response, exception);` - -```java - @ExceptionHandler(Exception.class) - @ResponseBody - public Object exceptionHandler(HttpServletRequest request, HttpServletResponse response, Exception exception) { - ... - // 在返回前加这一句 - ExceptionHolder.hold(request, response, exception); - ... - return ..; - } -``` - -如果没有配置全局异常,可参考下面配置 - -```java -@ControllerAdvice -@Slf4j -public class StoryGlobalExceptionHandler { - - - /** - * 捕获手动抛出的异常 - * - * @param request request - * @param response response - * @param exception 异常信息 - * @return 返回提示信息 - */ - @ExceptionHandler(Exception.class) - @ResponseBody - public Object exceptionHandler(HttpServletRequest request, HttpServletResponse response, Exception exception) { - // 在返回前加这一句 - ExceptionHolder.hold(request, response, exception); - // 下面可以实现自己的全局异常处理 - return new ErrorResult(500, exception.getMessage()); - } - - @Data - @AllArgsConstructor - public static class ErrorResult { - private int code; - private String msg; - } -} -``` - -到此准备工作就完成了,接下来可前往`新增接口`查看如何新增接口。 - -## 非Java项目接入 - -如果是非Java项目接入,如php,需要做到以下几点: - -> 1. 提供路由配置接口 -> 2. 服务注册到nacos,并在nacos的metadata中指定接口路径,metadata的key为:`sop.routes.path` - - -- 提供路由配置接口 - -php应用提供的接口需要返回如下json内容: - -假设请求的接口为:`http://open.xxx.com/get_routes` - -```json -{ - "serviceId": "goods-service", - "createTime": 1568603471646, - "updateTime": 1568603471646, - "description": null, - "routeDefinitionList": [ - { - "id": "goods.list1.0", - "name": "goods.list", - "version": "1.0", - "uri": "lb://goods-service", - "path": "/goods/list_goods", - "order": 0, - "ignoreValidate": 0, - "status": 1, - "mergeResult": 1, - "permission": 0 - }, - ... - ] -} -``` - -json参数说明 - -|参数名|是否必填|说明| -|:----|:----|:----| -|serviceId |是|serviceId,服务id | -|createTime |是|创建时间,Unix timestamp,毫秒 | -|updateTime |是|修改时间,Unix timestamp,毫秒 | -|description|否|描述| -|routeDefinitionList元素参数说明|是|路由配置,见:routeDefinitionList元素参数说明| - -routeDefinitionList元素参数说明 - -|参数名|是否必填|说明| -|:----|:----|:----| -|id |是|路由id,全局唯一,格式:接口名+版本号 | -|name|是|接口名称| -|version|是|版本号| -|uri|是|格式:lb:// + serviceId,如:lb://goods-service| -|path|是|接口path,填端口号后面的path,如你的接口为`http://open.domain.com:8080/goods/list_goods`,填:`/goods/list_goods`| -|order|是|固定填:0| -|ignoreValidate|是|忽略签名验证,1:是,0:否| -|status|是|启用状态,1:启用,2:禁用| -|mergeResult|是|是否统一返回结果,1:是,0:否| -|permission|是|是否需要权限访问,1:是,0:否| - -- 服务注册到nacos - -可前往nacos官网,参考[open-api](https://nacos.io/zh-cn/docs/open-api.html),使用nacos提供的接口完成服务注册 - - -- 在nacos的metadata中指定接口路径 - -伪代码如下: - -```java -Instance instance = new Instance(); -instance.setServiceName("goods-service"); -instance.setIp("192.168.0.11"); -instance.setPort(8080); -// 在nacos的metadata中指定接口路径 -instance.getMetadata().put("sop.routes.path", "http://open.xxx.com/get_routes"); - -namingService.registerInstance(serviceId, instance); -``` - -完成以上步骤后,php服务注册到nacos,网关会触发监听事件,获取新注册的服务,然后会向你的服务拉取路由配置。 diff --git a/doc/docs/files/10020_新增接口.md b/doc/docs/files/10020_新增接口.md deleted file mode 100644 index aed9cba9..00000000 --- a/doc/docs/files/10020_新增接口.md +++ /dev/null @@ -1,147 +0,0 @@ -# 新增接口 - -假设要对下面这个接口提供开放能力。 - -```java -@RestController -public class StoryDemoController { - - @RequestMapping("/story/get") - public StoryResult getStory() { - StoryResult result = new StoryResult(); - result.setId(1L); - result.setName("海底小纵队"); - return result; - } -} -``` - -只需要在方法上新增一个`@Open`注解,指定接口名即可 - -```java -// 添加一个@Open注解 -@Open("story.demo.get") -@RequestMapping("/story/get") -public StoryResult getStory() { - StoryResult result = new StoryResult(); - result.setId(1L); - result.setName("海底小纵队"); - return result; -} -``` - -如果要加上版本号,指定`version`参数:`@Open(value = "story.demo.get", version = "2.0")` - -- 重启服务,这样接口就可以使用了。 - -## 绑定业务参数 - -网关校验通过后,请求参数会传递到微服务上来,完整的参数如下所示: - -``` -请求参数:charset=utf-8&biz_content={"goods_remark":"iphone6"}&method=goods.add&format=json&app_id=2019032617262200001&sign_type=RSA2&version=1.0×tamp=2019-04-29 19:18:38 -``` - -其中biz_content部分是我们想要的,在方法上申明一个对象,对应biz_content中的内容即可完成参数绑定,并且对参数进行JSR-303校验。 - -**注意:接口方法必须有且只有一个对象参数,如果申明多个会出现参数绑定失败** - -```java -@Open("goods.add") -@RequestMapping("/goods/add") -public Object addGoods(GoodsParam param) { - return param; -} - -@Data -public class GoodsParam { - @NotEmpty(message = "不能为空") // 支持JSR-303校验 - private String goods_remark; -} -``` - -一般情况下,只需要获取业务参数即可,如果想要获取更多的参数,可在后面跟一个`HttpServletRequest`对象。 - -```java -@Open("goods.add") -@RequestMapping("/goods/add") -public Object addGoods(GoodsParam param, HttpServletRequest request) { - System.out.println(request.getParameter("method")); - return param; -} -``` - - -## 接口命名 - -接口命名没有做强制要求,但我们还是推荐按照下面的方式进行命名: - -接口名的命名规则为:`服务模块.业务模块.功能模块.行为`,如: - -- mini.user.userinfo.get 小程序服务.用户模块.用户信息.获取 -- member.register.total.get 会员服务.注册模块.注册总数.获取 - -如果觉得命名规则有点长可以精简为:`服务模块.功能模块.行为`,如`member.usercount.get`,前提是确保前缀要有所区分,不和其它服务冲突。 - -## 测试接口 - -- 在sop-test工程下新建一个测试用例,`StoryDemoTest`,继承TestBase - -```java -public class StoryDemoTest extends TestBase { - - String url = "http://localhost:8081/api"; - String appId = "2019032617262200001"; - // 私钥 - String privateKey = "MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCXJv1pQFqWNA/++OYEV7WYXwexZK/J8LY1OWlP9X0T6wHFOvxNKRvMkJ5544SbgsJpVcvRDPrcxmhPbi/sAhdO4x2PiPKIz9Yni2OtYCCeaiE056B+e1O2jXoLeXbfi9fPivJZkxH/tb4xfLkH3bA8ZAQnQsoXA0SguykMRZntF0TndUfvDrLqwhlR8r5iRdZLB6F8o8qXH6UPDfNEnf/K8wX5T4EB1b8x8QJ7Ua4GcIUqeUxGHdQpzNbJdaQvoi06lgccmL+PHzminkFYON7alj1CjDN833j7QMHdPtS9l7B67fOU/p2LAAkPMtoVBfxQt9aFj7B8rEhGCz02iJIBAgMBAAECggEARqOuIpY0v6WtJBfmR3lGIOOokLrhfJrGTLF8CiZMQha+SRJ7/wOLPlsH9SbjPlopyViTXCuYwbzn2tdABigkBHYXxpDV6CJZjzmRZ+FY3S/0POlTFElGojYUJ3CooWiVfyUMhdg5vSuOq0oCny53woFrf32zPHYGiKdvU5Djku1onbDU0Lw8w+5tguuEZ76kZ/lUcccGy5978FFmYpzY/65RHCpvLiLqYyWTtaNT1aQ/9pw4jX9HO9NfdJ9gYFK8r/2f36ZE4hxluAfeOXQfRC/WhPmiw/ReUhxPznG/WgKaa/OaRtAx3inbQ+JuCND7uuKeRe4osP2jLPHPP6AUwQKBgQDUNu3BkLoKaimjGOjCTAwtp71g1oo+k5/uEInAo7lyEwpV0EuUMwLA/HCqUgR4K9pyYV+Oyb8d6f0+Hz0BMD92I2pqlXrD7xV2WzDvyXM3s63NvorRooKcyfd9i6ccMjAyTR2qfLkxv0hlbBbsPHz4BbU63xhTJp3Ghi0/ey/1HQKBgQC2VsgqC6ykfSidZUNLmQZe3J0p/Qf9VLkfrQ+xaHapOs6AzDU2H2osuysqXTLJHsGfrwVaTs00ER2z8ljTJPBUtNtOLrwNRlvgdnzyVAKHfOgDBGwJgiwpeE9voB1oAV/mXqSaUWNnuwlOIhvQEBwekqNyWvhLqC7nCAIhj3yvNQKBgQCqYbeec56LAhWP903Zwcj9VvG7sESqXUhIkUqoOkuIBTWFFIm54QLTA1tJxDQGb98heoCIWf5x/A3xNI98RsqNBX5JON6qNWjb7/dobitti3t99v/ptDp9u8JTMC7penoryLKK0Ty3bkan95Kn9SC42YxaSghzqkt+uvfVQgiNGQKBgGxU6P2aDAt6VNwWosHSe+d2WWXt8IZBhO9d6dn0f7ORvcjmCqNKTNGgrkewMZEuVcliueJquR47IROdY8qmwqcBAN7Vg2K7r7CPlTKAWTRYMJxCT1Hi5gwJb+CZF3+IeYqsJk2NF2s0w5WJTE70k1BSvQsfIzAIDz2yE1oPHvwVAoGAA6e+xQkVH4fMEph55RJIZ5goI4Y76BSvt2N5OKZKd4HtaV+eIhM3SDsVYRLIm9ZquJHMiZQGyUGnsvrKL6AAVNK7eQZCRDk9KQz+0GKOGqku0nOZjUbAu6A2/vtXAaAuFSFx1rUQVVjFulLexkXR3KcztL1Qu2k5pB6Si0K/uwQ="; - - - @Test - public void testDemo() throws Exception { - // 公共请求参数 - Map params = new HashMap(); - params.put("app_id", appId); - // 这里对应@Open.value属性 - params.put("method", "story.demo.get"); - params.put("format", "json"); - params.put("charset", "utf-8"); - params.put("sign_type", "RSA2"); - params.put("timestamp", new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date())); - // 这里对应@Open.version属性 - params.put("version", "1.0"); - - // 业务参数 - Map bizContent = new HashMap<>(); - - params.put("biz_content", JSON.toJSONString(bizContent)); - - System.out.println("----------- 请求信息 -----------"); - System.out.println("请求参数:" + buildParamQuery(params)); - System.out.println("商户秘钥:" + privateKey); - String content = AlipaySignature.getSignContent(params); - System.out.println("待签名内容:" + content); - String sign = AlipaySignature.rsa256Sign(content, privateKey, "utf-8"); - System.out.println("签名(sign):" + sign); - - params.put("sign", sign); - - System.out.println("----------- 返回结果 -----------"); - String responseData = post(url, params);// 发送请求 - System.out.println(responseData); - } - -} -``` - -- 请求成功后,控制台会打印: - -``` ------------ 请求信息 ----------- -请求参数:charset=utf-8&biz_content={}&method=story.demo.get&format=json&app_id=alipay_test&sign_type=RSA2&version=1.0×tamp=2019-03-23 15:41:22 -商户秘钥:MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCXJv1pQFqWNA/++OYEV7WYXwexZK/J8LY1OWlP9X0T6wHFOvxNKRvMkJ5544SbgsJpVcvRDPrcxmhPbi/sAhdO4x2PiPKIz9Yni2OtYCCeaiE056B+e1O2jXoLeXbfi9fPivJZkxH/tb4xfLkH3bA8ZAQnQsoXA0SguykMRZntF0TndUfvDrLqwhlR8r5iRdZLB6F8o8qXH6UPDfNEnf/K8wX5T4EB1b8x8QJ7Ua4GcIUqeUxGHdQpzNbJdaQvoi06lgccmL+PHzminkFYON7alj1CjDN833j7QMHdPtS9l7B67fOU/p2LAAkPMtoVBfxQt9aFj7B8rEhGCz02iJIBAgMBAAECggEARqOuIpY0v6WtJBfmR3lGIOOokLrhfJrGTLF8CiZMQha+SRJ7/wOLPlsH9SbjPlopyViTXCuYwbzn2tdABigkBHYXxpDV6CJZjzmRZ+FY3S/0POlTFElGojYUJ3CooWiVfyUMhdg5vSuOq0oCny53woFrf32zPHYGiKdvU5Djku1onbDU0Lw8w+5tguuEZ76kZ/lUcccGy5978FFmYpzY/65RHCpvLiLqYyWTtaNT1aQ/9pw4jX9HO9NfdJ9gYFK8r/2f36ZE4hxluAfeOXQfRC/WhPmiw/ReUhxPznG/WgKaa/OaRtAx3inbQ+JuCND7uuKeRe4osP2jLPHPP6AUwQKBgQDUNu3BkLoKaimjGOjCTAwtp71g1oo+k5/uEInAo7lyEwpV0EuUMwLA/HCqUgR4K9pyYV+Oyb8d6f0+Hz0BMD92I2pqlXrD7xV2WzDvyXM3s63NvorRooKcyfd9i6ccMjAyTR2qfLkxv0hlbBbsPHz4BbU63xhTJp3Ghi0/ey/1HQKBgQC2VsgqC6ykfSidZUNLmQZe3J0p/Qf9VLkfrQ+xaHapOs6AzDU2H2osuysqXTLJHsGfrwVaTs00ER2z8ljTJPBUtNtOLrwNRlvgdnzyVAKHfOgDBGwJgiwpeE9voB1oAV/mXqSaUWNnuwlOIhvQEBwekqNyWvhLqC7nCAIhj3yvNQKBgQCqYbeec56LAhWP903Zwcj9VvG7sESqXUhIkUqoOkuIBTWFFIm54QLTA1tJxDQGb98heoCIWf5x/A3xNI98RsqNBX5JON6qNWjb7/dobitti3t99v/ptDp9u8JTMC7penoryLKK0Ty3bkan95Kn9SC42YxaSghzqkt+uvfVQgiNGQKBgGxU6P2aDAt6VNwWosHSe+d2WWXt8IZBhO9d6dn0f7ORvcjmCqNKTNGgrkewMZEuVcliueJquR47IROdY8qmwqcBAN7Vg2K7r7CPlTKAWTRYMJxCT1Hi5gwJb+CZF3+IeYqsJk2NF2s0w5WJTE70k1BSvQsfIzAIDz2yE1oPHvwVAoGAA6e+xQkVH4fMEph55RJIZ5goI4Y76BSvt2N5OKZKd4HtaV+eIhM3SDsVYRLIm9ZquJHMiZQGyUGnsvrKL6AAVNK7eQZCRDk9KQz+0GKOGqku0nOZjUbAu6A2/vtXAaAuFSFx1rUQVVjFulLexkXR3KcztL1Qu2k5pB6Si0K/uwQ= -待签名内容:app_id=alipay_test&biz_content={}&charset=utf-8&format=json&method=story.demo.get&sign_type=RSA2×tamp=2019-03-23 15:41:22&version=1.0 -签名(sign):YMbxTPdovi6htcn1K3USTS6/Tbg6MOAMigG6x/kG0kQFCYH8ljvxXzcY86UT056nUG3OXxnj0xkw07eV6E03HMlu7bn3/jrT3PCcV3YguhA92aWz720x2xJWdfXY13OUPS9VOCC9zIVxu6EBD+PoZ7ojYChYvOfCR5I8bR/oOc0ZLjK63PWTBdf0eFS4sybXzRf81uNLMROsMhmBDDy0Fhml3ml77qzWBIpsmq5ECZ+89rMPbkNhAUcnFAe7ik7xZIL6WcUhAOhKVa8ZQK1GMjoGnAbGRed1FbuOHZGubgffg4/vMqrY10Bcy6h9jt/zK5w9L3HVgK3aPgQlfP16Gg== ------------ 返回结果 ----------- -{"story_demo_get_response":{"msg":"Success","code":"10000","name":"白雪公主","id":1},"sign":"YMbxTPdovi6htcn1K3USTS6/Tbg6MOAMigG6x/kG0kQFCYH8ljvxXzcY86UT056nUG3OXxnj0xkw07eV6E03HMlu7bn3/jrT3PCcV3YguhA92aWz720x2xJWdfXY13OUPS9VOCC9zIVxu6EBD+PoZ7ojYChYvOfCR5I8bR/oOc0ZLjK63PWTBdf0eFS4sybXzRf81uNLMROsMhmBDDy0Fhml3ml77qzWBIpsmq5ECZ+89rMPbkNhAUcnFAe7ik7xZIL6WcUhAOhKVa8ZQK1GMjoGnAbGRed1FbuOHZGubgffg4/vMqrY10Bcy6h9jt/zK5w9L3HVgK3aPgQlfP16Gg=="} -``` - diff --git a/doc/docs/files/10021_开发流程.md b/doc/docs/files/10021_开发流程.md deleted file mode 100644 index 439f8e34..00000000 --- a/doc/docs/files/10021_开发流程.md +++ /dev/null @@ -1,20 +0,0 @@ -# 开发流程 - -如果您打算使用SOP做开放平台开发,流程大致如下: - -- cd到/SOP/sop-common目录,执行命令`mvn clean deploy`,把jar上传到maven私服,如果没有maven私服,可以打包到本地`mvn clean install` -- 打包`sop-gateway`(网关)、`sop-admin`(后台管理)、`sop-website`(文档),部署到服务器上 - -以上服务是固定的,启动一次即可,后续不用做改动。 - -- 你的项目接入到SOP,参考[项目接入到SOP](10011_项目接入到SOP.md),在微服务端开发接口,编写swagger注解文档 -- 接口开发完成,启动微服务,注册到注册中心。 -- 【可选】编写sdk,在`SDK管理`页发布SDK,提供下载地址 - -## ISV对接流程 - -假设接口开发完毕,开始对接ISV,大致步骤如下: - -1. ISV访问门户网站,注册账号并登陆 -2. 上传应用公钥 -3. 如果有SDK,下载SDK,进行接口调用 \ No newline at end of file diff --git a/doc/docs/files/10022_用户注册.md b/doc/docs/files/10022_用户注册.md deleted file mode 100644 index c94f2fc2..00000000 --- a/doc/docs/files/10022_用户注册.md +++ /dev/null @@ -1,11 +0,0 @@ -# 用户(ISV)注册 - -新增ISV有两种方式 - -- 方式1 - -启动sop-admin,在admin后台`ISV管理添加` - -- 方式2 - -启动`sop-website-server`,用户访问自主注册 diff --git a/doc/docs/files/10030_业务参数校验.md b/doc/docs/files/10030_业务参数校验.md deleted file mode 100644 index 267f70fc..00000000 --- a/doc/docs/files/10030_业务参数校验.md +++ /dev/null @@ -1,88 +0,0 @@ -# 业务参数校验 - -业务参数校验采用JSR-303方式,关于JSR-303介绍可以参考这篇博文:[JSR 303 - Bean Validation 介绍及最佳实践](https://www.ibm.com/developerworks/cn/java/j-lo-jsr303/) - -在参数中使用注解即可,框架会自动进行验证。如下面一个添加商品接口,它的参数是GoodsParam - -```java -@Open("goods.add") -@RequestMapping("/goods/add") -public void addGoods(GoodsParam param) { - ... -} -``` -在GoodsParam中添加JSR-303注解: - -```java -@Data -public class GoodsParam { - @NotEmpty(message = "商品名称不能为空") - private String goods_name; -} -``` - -如果不传商品名称则返回 - -``` -{"goods_add_response":{"msg":"Success","code":"10000","sub_msg":"商品名称不能为空","sub_code":"isv.invalid-parameter"},"sign":"Eh3Z5CxDCHsb4MyYFVxsPSmBpwVi1LISJdOkrzglxXoqG7RVyEOt4ef1kNpznUvMI3FDQU1suR7Rsmx6NjGdEVS6NSH2Kt0d8TFBRpLhWz8hApnxOtgzqMqbYeMuJie7X5gF6m8hTnvuuxF21IrkixMe+lyBcXw7dk0C3w1SwdEZkHQ+xC+M4bLqAZt5/3kl79/FWSMFJWHiZmg5YeEi8e8XhYCNcz+xlJRJL0x2Y87fFxqSY0UYWNxbQHgdVI8xRfn1n31nzkcLxiAtTh4LPtNRrG7w7absK/C1Oi/vczuBlFeq2EWUsYVWOVpKiJifUwvYVUUsztSLElzplzOjbg=="} - -``` - -- 校验顺序 - -如果存在多个注解,可以指定groups来控制校验顺序,如下代码所示: - -```java -@NotBlank(message = "NotBlank", groups = Group1.class) -// 优先校验Group2 -// 可交换下面Group2,Group3,看下校验顺序 -@Length(min = 2, max = 20, message = "length must 10~20", groups = Group2.class) -@Pattern(regexp = "[a-zA-Z]*", message = "name must letters", groups = Group3.class) -private String name; -``` - -优先校验`@Length`,通过后再校验`@Pattern` - -## 参数校验国际化 - -国际化的配置方式如下: - -```java -@NotEmpty(message = "{goods.remark.notNull}") -private String goods_remark; -``` - -国际化资源文件`bizerror_en.properties`中添加: -``` -goods.remark.notNull=The goods_remark can not be null -``` - -bizerror_zh_CN.properties中添加: - -``` -# 商品备注不能为空 -goods.remark.notNull=\u5546\u54c1\u5907\u6ce8\u4e0d\u80fd\u4e3a\u7a7a -``` - -## 参数校验国际化传参 - -下面校验商品评论的长度,要求大于等于3且小于等于20。数字3和20要填充到国际化资源中去。 - -``` -// 传参的格式:{xxx}=value1,value2... -@Length(min = 3, max = 20, message = "{goods.comment.length}=3,20") -private String goods_comment; -``` - -bizerror_en.properties: -``` -goods.comment.length=The goods_comment length must >= {0} and <= {1} -``` - -bizerror_zh_CN.properties中添加: - -``` -# 商品评论长度必须在{0}和{1}之间 -goods.comment.length=\u5546\u54c1\u8bc4\u8bba\u957f\u5ea6\u5fc5\u987b\u5728{0}\u548c{1}\u4e4b\u95f4 -``` -这样value1,value2会分别填充到{0},{1}中 diff --git a/doc/docs/files/10040_错误处理.md b/doc/docs/files/10040_错误处理.md deleted file mode 100644 index e2c3df63..00000000 --- a/doc/docs/files/10040_错误处理.md +++ /dev/null @@ -1,91 +0,0 @@ -# 错误处理 - -SOP对错误处理已经封装好了,简单做法是`throw ServiceException`,在最顶层的Controller会做统一处理。例如: - -```java -if(StringUtils.isEmpty(param.getGoods_name())) { - throw new ServiceException("goods_name不能为null"); -} -``` - -为了保证编码风格的一致性,推荐统一使用ServiceException - -## i18n国际化 - -SOP支持国际化消息。通过Request对象中的getLocale()来决定具体返回那种语言,客户端通过设置Accept-Language头部来决定返回哪种语言,中文是zh,英文是en。 - -SOP通过模块化来管理国际化消息,这样做的好处结构清晰,维护方便。下面就来讲解如何设置国际化消息。 - -以story服务为例,假设我们要对商品模块进行设置,步骤如下: - -- 在`resource/i18n/isp`目录下新建goods_error_zh_CN.properties属性文件 - -属性文件的文件名有规律, **i18n/isp/goods_error** 表示模块路径, **_zh_CN.properties** 表示中文错误消息。如果要使用英文错误,则新建一个`goods_error_en.properties`即可。 - -- 在goods_error_zh_CN.properties中配置错误信息 - -``` -# 商品名字不能为空 -isp.goods_error_100=\u5546\u54C1\u540D\u79F0\u4E0D\u80FD\u4E3A\u7A7A -``` - -isp.goods_error_为固定前缀,100为错误码,这两个值后续会用到。 - -接下来是把属性文件加载到国际化容器当中。 - -- 添加国际化配置,在OpenServiceConfig中的static块中添加,代码如下: - -```java -@Configuration -public class OpenServiceConfig extends AlipayServiceConfiguration { - - static { - ServiceConfig.getInstance().getI18nModules().add("i18n/isp/goods_error"); - } -} -``` - -- 新建一个枚举用来定义错误 - -```java -// 按模块来定义异常消息,团队开发可以分开进行 -public enum GoodsErrorEnum { - /** 参数错误 */ - NO_GOODS_NAME("100"), - ; - private ServiceErrorMeta errorMeta; - - StoryErrorEnum(String subCode) { - this.errorMeta = new ServiceErrorMeta("isp.goods_error_", subCode); - } - - public ServiceErrorMeta getErrorMeta() { - return errorMeta; - } -} - -``` - -接下来就可以使用了 - -```java -if (StringUtils.isEmpty(param.getGoods_name())) { - throw GoodsErrorEnum.NO_GOODS_NAME.getErrorMeta().getException(); -} -``` - -### 国际化消息传参 - -即代码中变量传入到properties文件中去,做法是采用{0},{1}占位符。0代表第一个参数,1表示第二个参数。 - -``` -# 商品名称太短,不能小于{0}个字 -isp.goods_error_101=\u5546\u54C1\u540D\u79F0\u592A\u77ED\uFF0C\u4E0D\u80FD\u5C0F\u4E8E{0}\u4E2A\u5B57 -``` - -```java -if (param.getGoods_name().length() <= 3) { - throw GoodsErrorEnum.LESS_GOODS_NAME_LEN.getErrorMeta().getException(3); -} -``` -直接放进getException(Object... params)方法参数中,因为是可变参数,可随意放。 \ No newline at end of file diff --git a/doc/docs/files/10041_编写文档.md b/doc/docs/files/10041_编写文档.md deleted file mode 100644 index cc389727..00000000 --- a/doc/docs/files/10041_编写文档.md +++ /dev/null @@ -1,126 +0,0 @@ -# 编写文档 - -作为开放平台,必须要提供API文档。 - -SOP采用微服务架构实现,因此文档应该由各个微服务各自实现。难点就是如何统一归纳各个微服务端提供的文档信息,并且统一展示。 - -写完接口后使用swagger注解来定义自己的文档信息。步骤如下: - -- maven添加swagger - -```xml - - - io.springfox - springfox-swagger2 - 2.9.2 - - - com.github.xiaoymin - swagger-bootstrap-ui - 1.9.5 - - -``` - -- 在config中添加swagger配置 - -```java -@Configuration -public class OpenServiceConfig extends AlipayServiceConfiguration { - /** - * 开启文档,本地微服务文档地址:http://localhost:2222/doc.html - * http://ip:port/v2/api-docs - */ - @Configuration - @EnableSwagger2 - public static class Swagger2 extends SwaggerSupport { - @Override - protected String getDocTitle() { - return "故事API"; - } - } -} -``` - -其中`getDocTitle()`返回文档模块名,不能和其它微服务重复。比如订单服务返回:`订单API`;库存服务返回:`库存API` - -- 编写swagger注解 - -分别在请求参数和返回结果类中编写`@ApiModelProperty` - -```java -// 请求参数 -@Data -public class StoryParam { - @ApiModelProperty(value = "故事ID", example = "111") - private int id; - - @ApiModelProperty(value = "故事名称", required = true, example = "白雪公主") - private String name; -} - -// 返回结果 -@Data -public class StoryResult { - @ApiModelProperty(value = "故事ID", example = "1") - private Long id; - - @ApiModelProperty(value = "故事名称", example = "海底小纵队") - private String name; - - @ApiModelProperty(value = "创建时间", example = "2019-04-14 19:02:12") - private Date gmt_create; -} -``` - -- 在接口方法上编写`@ApiOperation`注解 - -```java -/** - * 参数绑定 - * - * @param story 对应biz_content中的内容,并自动JSR-303校验 - * @return - */ -@ApiOperation(value = "获取故事信息", notes = "说明接口的详细信息,介绍,用途,注意事项等。") -@Open(value = "alipay.story.find", bizCode = { - // 定义业务错误码,用于文档显示 - @BizCode(code = "100001", msg = "姓名错误", solution = "填写正确的姓名"), - @BizCode(code = "100002", msg = "备注错误", solution = "填写正确备注"), - }) -public StoryResult getStory2(StoryParam story) { - log.info("获取故事信息参数, story: {}", story); - // 获取其它参数 - OpenContext openContext = ServiceContext.getCurrentContext().getOpenContext(); - String app_id = openContext.getAppId(); - StoryResult result = new StoryResult(); - result.setName("白雪公主, app_id:" + app_id); - result.setGmt_create(new Date()); - return result; -} -``` - -其中`value`属性填接口名称,简明扼要。`notes`填写接口的详细信息,介绍,用途,注意事项等。 - -## 查看文档 - -- 确保注册中心、网关、微服务正常启动 -- 运行WebsiteServerApplication.java -- 访问http://localhost:8083 - -效果图如下 - -![预览](images/10041_1.png "10041_1.png") - -## 注解对应关系 - -swagger注解和文档界面显示关系如下图所示: - -![预览](images/10041_2.png "10041_2.png") - - -![预览](images/10041_3.png "10041_3.png") - - -![预览](images/10041_4.png "10041_4.png") \ No newline at end of file diff --git a/doc/docs/files/10050_接口交互详解.md b/doc/docs/files/10050_接口交互详解.md deleted file mode 100644 index c83a04f5..00000000 --- a/doc/docs/files/10050_接口交互详解.md +++ /dev/null @@ -1,61 +0,0 @@ -# 接口交互详解 - -开放平台所提供的接口有几十个到几百个不等,同样支持的服务也是多个的。就拿[支付宝开放平台](https://docs.open.alipay.com/api)来说 -它所提供的服务有,支付服务、会员服务、店铺服务、芝麻信用服务等。相信这些服务接口肯定不是写在同一个项目中,但是它的接口地址只有一个:https://openapi.alipay.com/gateway.do -从地址信息中可以看到,这是一个网关服务。也就是说,网关是所有请求的入口,然后通过请求分发的方式,把请求路由到具体某个服务中去。 -虽然支付宝开放平台的实现方式我们不得而知,但是这种思路是可行的。 - -SOP也是采用这种方式实现,大致步骤如下: - -- 每个服务注册到nacos -- 网关启动时同样注册到nacos,然后从各服务中拉取路由信息 -- 网关收到客户端请求后,先进行签名校验,通过之后根据接口信息找到对应的服务,然后进行路由 -- 网关对返回结果进行处理(或不处理),返回给客户端。 - -如何通过接口参数找到对应的服务呢? - -在网关定义一个`Map routeMap = ...`,key为接口名+版本号。 - -网关启动时,从各微服务中获取路由信息,并保存到routeMap中 - -```java -routeMap = requestFormServices(); -``` - -接口请求进来后,根据`方法名+版本号`获取路由信息,然后进行路由转发。 - -```java -String method = request.getParameter("method"); -String version = request.getParameter("version"); - -RouteInfo routeInfo = routeMap.get(method + version); - -doRoute(routeInfo); -``` - -因为nacos需要拉取各个微服务的路由信息,接口名有可能会冲突,因此需要确保接口名唯一,即`method`全局唯一。 - -我们推荐接口名的命名规则应该是:`服务模块.业务模块.功能模块.行为`,如: - -mini.user.userinfo.get 小程序服务.用户模块.用户信息.获取 - -member.register.total.get 会员服务.注册模块.注册总数.获取 - -如果觉得命名规则有点长可以精简为:`服务模块.功能模块.行为`,如`member.usercount.get`,前提是确保前缀要有所区分,不和其它服务冲突。 - -得益于Spring Cloud的注册中心和和网关功能,我们能很方便的进行接口路由,并且还能实现LoadBalance,不需要自己再去实现。 - -整个SOP的架构如下图所示: - -![架构图](https://images.gitee.com/uploads/images/2019/1227/145216_c9b45109_332975.png "sop2.png") - -- 完整请求路线 - -``` -客户端生成签名串 → 客户端发送请求 →【网关签名校验 → 权限校验 → 限流处理 → 路由转发】→ {微服务端业务参数校验 → 处理业务逻辑 → 微服务端返回结果} - ↓ -客户端业务处理 ← 客户端验证服务端签名 ← 客户端收到结果 ← -------------【网关返回最终结果 ← 生成服务端签名 ← 网关处理结果】← 结果返回到网关 - -【】:表示网关处理 -{}:表示微服务端处理 -``` \ No newline at end of file diff --git a/doc/docs/files/10080_使用签名校验工具.md b/doc/docs/files/10080_使用签名校验工具.md deleted file mode 100644 index 26c13dea..00000000 --- a/doc/docs/files/10080_使用签名校验工具.md +++ /dev/null @@ -1,41 +0,0 @@ -# 使用签名校验工具 - -## 生成公私钥 - -SOP默认签名算法仿照的是支付宝开放平台,因此我们可以使用支付宝开放平台提供的密钥生成工具,[下载地址](https://docs.open.alipay.com/291/105971/) - -工具下载完后,运行工具 - -- 秘钥格式选择:PKCS8(JAVA适用) -- 秘钥长度:2048 - -然后点击`生成秘钥`,下面文本框会生成,公私钥,如下图所示: - -![示例图](https://gw.alipayobjects.com/zos/skylark/6dbc42cc-6b9b-4691-83f1-e7b875e1a602/2018/png/e6b725d0-8257-4a71-b7a0-f5479c9d43d0.png) - -sop-admin创建一个新ISV,将公私钥放入对应文本框中,保存。 - -接着私钥放入客户端进行调用。参见AlipayClientPostTest类 - -## 签名校验 - -验证工具切换到`签名`tab页 - -例如执行com.gitee.sop.AlipayClientPostTest.testPost()方法,控制台会打印如下信息: - -``` ------------ 请求信息 ----------- -请求参数:charset=utf-8&biz_content={"name":"葫芦娃","id":"1"}&method=alipay.story.get&format=json&app_id=2019032617262200001&sign_type=RSA2&version=1.0×tamp=2019-03-26 17:37:41 -商户秘钥:MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCXJv1pQFqWNA/++OYEV7WYXwexZK/J8LY1OWlP9X0T6wHFOvxNKRvMkJ5544SbgsJpVcvRDPrcxmhPbi/sAhdO4x2PiPKIz9Yni2OtYCCeaiE056B+e1O2jXoLeXbfi9fPivJZkxH/tb4xfLkH3bA8ZAQnQsoXA0SguykMRZntF0TndUfvDrLqwhlR8r5iRdZLB6F8o8qXH6UPDfNEnf/K8wX5T4EB1b8x8QJ7Ua4GcIUqeUxGHdQpzNbJdaQvoi06lgccmL+PHzminkFYON7alj1CjDN833j7QMHdPtS9l7B67fOU/p2LAAkPMtoVBfxQt9aFj7B8rEhGCz02iJIBAgMBAAECggEARqOuIpY0v6WtJBfmR3lGIOOokLrhfJrGTLF8CiZMQha+SRJ7/wOLPlsH9SbjPlopyViTXCuYwbzn2tdABigkBHYXxpDV6CJZjzmRZ+FY3S/0POlTFElGojYUJ3CooWiVfyUMhdg5vSuOq0oCny53woFrf32zPHYGiKdvU5Djku1onbDU0Lw8w+5tguuEZ76kZ/lUcccGy5978FFmYpzY/65RHCpvLiLqYyWTtaNT1aQ/9pw4jX9HO9NfdJ9gYFK8r/2f36ZE4hxluAfeOXQfRC/WhPmiw/ReUhxPznG/WgKaa/OaRtAx3inbQ+JuCND7uuKeRe4osP2jLPHPP6AUwQKBgQDUNu3BkLoKaimjGOjCTAwtp71g1oo+k5/uEInAo7lyEwpV0EuUMwLA/HCqUgR4K9pyYV+Oyb8d6f0+Hz0BMD92I2pqlXrD7xV2WzDvyXM3s63NvorRooKcyfd9i6ccMjAyTR2qfLkxv0hlbBbsPHz4BbU63xhTJp3Ghi0/ey/1HQKBgQC2VsgqC6ykfSidZUNLmQZe3J0p/Qf9VLkfrQ+xaHapOs6AzDU2H2osuysqXTLJHsGfrwVaTs00ER2z8ljTJPBUtNtOLrwNRlvgdnzyVAKHfOgDBGwJgiwpeE9voB1oAV/mXqSaUWNnuwlOIhvQEBwekqNyWvhLqC7nCAIhj3yvNQKBgQCqYbeec56LAhWP903Zwcj9VvG7sESqXUhIkUqoOkuIBTWFFIm54QLTA1tJxDQGb98heoCIWf5x/A3xNI98RsqNBX5JON6qNWjb7/dobitti3t99v/ptDp9u8JTMC7penoryLKK0Ty3bkan95Kn9SC42YxaSghzqkt+uvfVQgiNGQKBgGxU6P2aDAt6VNwWosHSe+d2WWXt8IZBhO9d6dn0f7ORvcjmCqNKTNGgrkewMZEuVcliueJquR47IROdY8qmwqcBAN7Vg2K7r7CPlTKAWTRYMJxCT1Hi5gwJb+CZF3+IeYqsJk2NF2s0w5WJTE70k1BSvQsfIzAIDz2yE1oPHvwVAoGAA6e+xQkVH4fMEph55RJIZ5goI4Y76BSvt2N5OKZKd4HtaV+eIhM3SDsVYRLIm9ZquJHMiZQGyUGnsvrKL6AAVNK7eQZCRDk9KQz+0GKOGqku0nOZjUbAu6A2/vtXAaAuFSFx1rUQVVjFulLexkXR3KcztL1Qu2k5pB6Si0K/uwQ= -待签名内容:app_id=2019032617262200001&biz_content={"name":"葫芦娃","id":"1"}&charset=utf-8&format=json&method=alipay.story.get&sign_type=RSA2×tamp=2019-03-26 17:37:41&version=1.0 -签名(sign):JCZMSFkXSjw/4TokyM9/9shyrMl7KxQGIZDHIm7+Bvl49Z816/iF/xXLYjUiPXWAXYfp+HlEs3VVQp1Kjh4tIKuKX/i1+exNVs+ICcqVGBewPSZwiWHGpZTfEUiYOoPyUL/eoRIj7Mvlaow0sI9uP7NXNo0kxEFjUOMCzZA7eKm/pu2FHRXt4OhgXq2Go30K5a9oCbbMc/2xcQCc2+zwvOgV3o0A6eMyeAXDJW+eQ2KLhtlqPQvbRV+xyfSut7TkwYSEuNXVVQAfN2lwAS3ru9CQIs8Uz7lK1ITkLu80yLapZVL7tS1PdxK0e3QYToCWD43Wtuoow4ZdDwwzir90HQ== ------------ 返回结果 ----------- -{"alipay_story_get_response":{"msg":"Success","code":"10000","name":"海底小纵队(alipay.story.get)","id":1},"sign":"JCZMSFkXSjw/4TokyM9/9shyrMl7KxQGIZDHIm7+Bvl49Z816/iF/xXLYjUiPXWAXYfp+HlEs3VVQp1Kjh4tIKuKX/i1+exNVs+ICcqVGBewPSZwiWHGpZTfEUiYOoPyUL/eoRIj7Mvlaow0sI9uP7NXNo0kxEFjUOMCzZA7eKm/pu2FHRXt4OhgXq2Go30K5a9oCbbMc/2xcQCc2+zwvOgV3o0A6eMyeAXDJW+eQ2KLhtlqPQvbRV+xyfSut7TkwYSEuNXVVQAfN2lwAS3ru9CQIs8Uz7lK1ITkLu80yLapZVL7tS1PdxK0e3QYToCWD43Wtuoow4ZdDwwzir90HQ=="} -``` - -字符集选UTF-8,签名方式RSA2 - -把控制台中的`请求参数`和`商户秘钥`填入文本框中,然后点击`开始签名`,下方会出现待签名内容和sign。 - -通过比对判断签名过程是否正确。 - diff --git a/doc/docs/files/10085_ISV管理.md b/doc/docs/files/10085_ISV管理.md deleted file mode 100644 index e6334502..00000000 --- a/doc/docs/files/10085_ISV管理.md +++ /dev/null @@ -1,24 +0,0 @@ -# ISV管理 - -ISV:独立软体开发商(independent software vendor),即接入方或者说接口调用者,在SOP中称为ISV。 - ---- - -在1.1.0版本中新增了ISV管理功能,在sop-admin中ISV管理模块下。功能如下: - -- 基本信息的增查改 -- 设置对应角色 - -界面如下图所示: - -![admin预览](images/10085_1.png "10085_1.png") - -## 秘钥管理 - -点击操作列的`秘钥管理`,可对ISV的秘钥进行设置。 - -- 如果采用淘宝开放平台签名方式,签名方式选择`MD5`,如果采用支付宝开放平台签名方式,选择`RSA` -- 如果对接的开发者使用非Java语言,秘钥格式选择`PKCS1` -- 带 ★ 的分配给开发者 - -![admin预览](images/10085_2.png "10085_2.png") diff --git a/doc/docs/files/10087_自定义返回结果.md b/doc/docs/files/10087_自定义返回结果.md deleted file mode 100644 index e5609bea..00000000 --- a/doc/docs/files/10087_自定义返回结果.md +++ /dev/null @@ -1,110 +0,0 @@ -# 自定义返回结果 - -网关默认对业务结果进行合并,然后返回统一的格式。 - -针对`alipay.story.find`接口,微服务端返回结果如下: - -```json -{ - "name": "白雪公主", - "id": 1, - "gmtCreate": 1554193987378 -} -``` - -网关合并后,最终结果如下 - -```json -{ - "alipay_story_find_response": { - "msg": "Success", - "code": "10000", - "name": "白雪公主", - "id": 1, - "gmtCreate": 1554193987378 - }, - "sign": "xxxxx" -} -``` - -其中`alipay_story_find_response`是它的数据节点。规则是: - -> 将接口名中的点`.`转换成下划线`_`,后面加上`_response` - -代码实现如下: - -```java -String method = "alipay.story.find"; -return method.replace('.', '_') + "_response"; -``` - -详见`DefaultDataNameBuilder.java` - -如果要更改数据节点,比如`result`,可使用`CustomDataNameBuilder.java`。 - -```java -@Configuration -public class MyConfig { - - static { - ... - ApiConfig.getInstance().setDataNameBuilder(new CustomDataNameBuilder()); - ... - } - -} -``` - -设置后,网关统一的返回结果如下: - -```json -{ - "result": { - ... - }, - "sign": "xxxxx" -} -``` - -此外,构造方法可指定自定义字段名称:`new CustomDataNameBuilder("data");`。 -设置后,数据节点将变成`data` - -```json -{ - "data": { - ... - }, - "sign": "xxxxx" -} -``` - -**注**:网关设置了CustomDataNameBuilder后,SDK也要做相应的更改:`OpenConfig.dataNameBuilder = new CustomDataNameBuilder();` - -## 自定义结果处理 - -如果想要对微服务结果做更深一步处理,步骤如下: - -1. 新增一个类,继承`GatewayResultExecutor.java`,并重写`String mergeResult(T request, String serviceResult)`方法 - -2. 配置自定义类 - -```java -@Configuration -public class MyConfig { - - static { - ... - ApiConfig.getInstance().setGatewayResultExecutor(new MyGatewayResultExecutor()); - ... - } - -} -``` - - -## 不合并结果 - -如果不希望对结果进行合并,可在application.properties中设置`sop.api-config.merge-result=false` - - -这样,网关最终返回结果即为微服务端的返回结果。 \ No newline at end of file diff --git a/doc/docs/files/10088_自定义过滤器.md b/doc/docs/files/10088_自定义过滤器.md deleted file mode 100644 index 853dc57b..00000000 --- a/doc/docs/files/10088_自定义过滤器.md +++ /dev/null @@ -1,40 +0,0 @@ -# 自定义过滤器 - -演示在网关追加一个header - -```java -public class CustomFilter implements GlobalFilter, Ordered { - - @Override - public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { - // 演示在网关追加header - ApiParam apiParam = ServerWebExchangeUtil.getApiParam(exchange); - String token = apiParam.fetchAccessToken(); - ServerWebExchange serverWebExchange = ServerWebExchangeUtil.addHeaders(exchange, httpHeaders -> { - httpHeaders.add("token", token); - }); - return chain.filter(serverWebExchange); - } - - @Override - public int getOrder() { - // 自定义过滤器可以从0开始 - return 0; - } -} -``` - -使用过滤器,在sop-gateway中找到MyConfig,添加: - -```java -@Configuration -public class MyConfig { - - ... - @Bean - CustomFilter customFilter() { - return new CustomFilter(); - } - ... -} -``` \ No newline at end of file diff --git a/doc/docs/files/10089_自定义校验token.md b/doc/docs/files/10089_自定义校验token.md deleted file mode 100644 index 6716ec55..00000000 --- a/doc/docs/files/10089_自定义校验token.md +++ /dev/null @@ -1,52 +0,0 @@ -# 自定义校验token - -在`@Open`注解中有一个属性`needToken`,用来告诉网关是否校验token - -```java -/** -* 是否需要appAuthToken,设置为true,网关端会校验token是否存在 -*/ -boolean needToken() default false; -``` - -使用方式: - -```java -@ApiOperation(value="传递token", notes = "传递token") -@Open(value = "story.get.token", needToken = true/* 设置true,网关会校验token是否存在 */) -@RequestMapping("token") -public StoryResult token(StoryParam story, HttpServletRequest request) { - OpenContext openContext = ServiceContext.getCurrentContext().getOpenContext(); - StoryResult result = new StoryResult(); - result.setName("appAuthToken:" + openContext.getAppAuthToken()); - return result; -} -``` - -指定了needToken=true后,网关会判断客户端是否传了`app_auth_token`参数,没有传则返回错误信息。 - -网关默认简单校验参数值是否存在,如果要校验有效性,需要自己实现。 - -自己实现步骤: - -- 设置`ApiConfig中的tokenValidator属性` - -`TokenValidator`是一个函数式接口,可以直接使用Lambda表达式,示例代码如下: - -```java -@Component -public class MyConfig { - - @PostConstruct - public void after() { - ApiConfig.getInstance().setTokenValidator(apiParam -> { - // 获取客户端传递过来的token - String token = apiParam.fetchAccessToken(); - return !StringUtils.isBlank(token); - // TODO: 校验token有效性,可以从redis中读取 - - // 返回true表示这个token真实、有效 - }); - } -} -``` diff --git a/doc/docs/files/10090_网关拦截器.md b/doc/docs/files/10090_网关拦截器.md deleted file mode 100644 index ee086054..00000000 --- a/doc/docs/files/10090_网关拦截器.md +++ /dev/null @@ -1,57 +0,0 @@ -# 网关拦截器 - -从3.1.0开始新增了网关拦截器,使用该拦截器可做一些数据统计,日志记录等工作。 - -使用方法如下: - -- 在sop-gateway工程下新增一个类,实现`RouteInterceptor`接口,实现接口中的方法。别忘了加`@Component` - -```java -@Component -public class MyRouteInterceptor implements RouteInterceptor { - @Override - public void preRoute(RouteInterceptorContext context) { - ApiParam apiParam = context.getApiParam(); - System.out.println("请求接口:" + apiParam.fetchNameVersion()); - } - - @Override - public void afterRoute(RouteInterceptorContext context) { - System.out.println("请求成功,微服务返回结果:" + context.getServiceResult()); - } - - @Override - public int getOrder() { - return 0; - } -} -``` - -RouteInterceptor接口方法说明: - -- `public void preRoute(RouteInterceptorContext context)` - -路由转发前执行,在签名验证通过之后会立即执行这个方法。 - -- `public void afterRoute(RouteInterceptorContext context)` - -路由转发完成后,即拿到微服务返回结果后执行这个方法 - -- `public int getOrder()` - -指定拦截执行顺序,数字小的优先执行,建议从0开始。 - -- `default boolean match(RouteInterceptorContext context)` - -是否匹配,返回true执行拦截器,默认true - -RouteInterceptorContext参数存放了各类参数信息。 - -参考类: - -- `com.gitee.sop.gatewaycommon.interceptor.RouteInterceptor` 拦截器接口 -- `com.gitee.sop.gatewaycommon.interceptor.RouteInterceptorContext` 拦截器上下文 -- `com.gitee.sop.gatewaycommon.interceptor.MonitorRouteInterceptor` 默认实现的拦截器,用于收集监控数据 - - - diff --git a/doc/docs/files/10091_路由授权.md b/doc/docs/files/10091_路由授权.md deleted file mode 100644 index 9823f89c..00000000 --- a/doc/docs/files/10091_路由授权.md +++ /dev/null @@ -1,25 +0,0 @@ -# 路由授权 - -1.1.0版本新增了路由授权功能,采用RBAC权限管理方式实现。 - -- 每个ISV(appKey)对应一个或多个角色 -- 每个角色分配多个路由权限 - -接口跟角色相关联,ISV拥有哪些角色,就具有角色对应的接口访问权限。 - -假设把路由a,b,c分配给了`VIP角色`,那么具有VIP角色的ISV可以访问a,b,c三个路由。 - -默认情况下,接口访问时公开的,ISV都能访问。如果要设置某个接口访问权限,在`@Open`注解中指定permission=true。 -如:`@Open(value = "permission.story.get", permission = true)`。这样该接口是需要经过授权给ISV才能访问的。 - -重启服务后,登录admin,服务管理-路由列表界面中,`访问权限`列会出现一个点击授权,点击出现授权窗口,勾选对应的角色即可完成授权。 - -- `点击授权`,进行角色授权 - -![admin预览](images/10090_1.png "10090_1.png") - -- 勾选对应角色,点击保存 - -![admin预览](images/10090_2.png "10090_2.png") - -这里演示的是:具有普通权限的ISV能够访问`permission.story.get`接口,运行`PermissionDemoPostTest`测试用例进行验证 diff --git a/doc/docs/files/10092_接口限流.md b/doc/docs/files/10092_接口限流.md deleted file mode 100644 index 4a6cb5f2..00000000 --- a/doc/docs/files/10092_接口限流.md +++ /dev/null @@ -1,80 +0,0 @@ -# 接口限流 - -SOP提供了简单的接口限流策略: - -- 窗口策略:每秒处理固定数量的请求,超出请求返回错误信息。 -- 令牌桶策略:每秒放置固定数量的令牌数,每个请求进来后先去拿令牌,拿到了令牌才能继续,拿不到则等候令牌重新生成了再拿。 - -如果一个接口设置了窗口策略,假设接口每秒可处理5个请求,一秒内同时有6个请求进来,前5个接口是能够访问的,第六个请求将返回错误信息。 - -如果设置了令牌桶策略,桶的容量是5,那么每秒中生成5个令牌,同一时间有6个请求进来,那么前5个能成功拿到令牌继续,第六个则等待,令牌重新生成了再拿。 - -默认情况下接口的限流功能是关闭的,可在sop admin中配置并开启。功能在`路由管理-->限流管理`下。 - -## 多维度限流 - -- 可针对接口进行限流,所有访问该接口的请求都被限流 -- 可针对appKey进行限流,某个appKey请求过来后,对他限流 -- 可针对IP进行限流,某个IP请求过来后,对他限流 - -此外还可以进行组合 - -- 可针对接口+appKey进行限流,这个appKey调用某个接口比较频繁,可以将它限制住 -- 可针对接口+IP进行限流,某个ip在频繁调用接口,可以将它限制住 - -由于存在组合情况,一个接口可能会配置多个限流规则。在这种情况下会优先取排序值小的那一条,如果排序值一样,则默认取第一条。 - -假设有下面几个限流规则: - -- 接口:`goods.get`, 排序值:1, 每秒可处理请求数:10 -- 接口:`goods.get`, appKey:xxxx, 排序值:0, 每秒可处理请求数:5 -- 接口:`goods.get`, ip:172.1.2.2, 排序值:2, 每秒可处理请求数:6 - -客户端调用接口:`http://open.domain.com?method=goods.get&app_id=xxxx`,客户端IP为`172.1.2.2` - -这种情况下上面三条限流规则都命中了,由于排序值小优先执行,因此第二条规则命中. - - -具体设置方式可在sop admin中配置,功能在`服务管理-->限流管理`下。执行`com.gitee.sop.test.LimitTest`测试用例验证限流情况 - -![限流配置](images/10092_1.png "10092_1.png") - -![限流配置](images/10092_2.png "10092_2.png") - -## 分布式限流 - -默认的限流方式是单机的,如果要部署多台网关实例,需要使用分布式限流 - -SOP使用redis进行分布式限流(只支持窗口策略),操作步骤如下: - -- sop-gateway/pom.xml添加redis依赖 - -```xml - - org.springframework.boot - spring-boot-starter-data-redis - -``` - -- sop-gateway下的application-dev.properties文件添加redis配置 - -```properties -# redis -spring.redis.database=0 -spring.redis.host=127.0.0.1 -spring.redis.port=6379 -``` - -- 在`com.gitee.sop.gateway.config.MyConfig`中添加如下代码: - -```java -@Autowired -private RedisTemplate redisTemplate; - -@PostConstruct -public void after() { - ApiConfig.getInstance().setLimitManager(new RedisLimitManager(redisTemplate)); - ... -} -``` - diff --git a/doc/docs/files/10093_路由监控.md b/doc/docs/files/10093_路由监控.md deleted file mode 100644 index b0e56a87..00000000 --- a/doc/docs/files/10093_路由监控.md +++ /dev/null @@ -1,27 +0,0 @@ -# 路由监控 - -路由监控功能可以查看各个接口的调用情况,监控信息收集采用拦截器实现,前往【服务管理】-【路由监控】查看 - -- 后台预览 - -![监控日志](images/10093_1.png "10093_1.png") - -![监控日志](images/10093_2.png "10093_2.png") - - -- 注意事项 - -处理完错误后,请及时`标记解决`,一个接口默认保存50条错误信息,采用LRU机制,淘汰老的。标记解决后则会空出一个位置存放新的错误信息。 - -重复的错误只会存放一条记录,然后累加错误次数,重复错误定义如下: - -`instanceId + routeId + errorMsg`,即一个实例 + 路由id + 错误信息确定一个错误 - -可在网关设置`sop.monitor.error-count-capacity=50`参数调整错误容量 - -考虑到数据库压力,网关收到错误信息后并不会立即保存到数据库,而是先保存到内容中,然后定时保存到时间,默认时间隔为30秒 -可通过`sop.monitor.flush-period-seconds=30`调整间隔时间。 - -相关类: - -- com.gitee.sop.gateway.interceptor.MonitorRouteInterceptor diff --git a/doc/docs/files/10095_SDK开发.md b/doc/docs/files/10095_SDK开发.md deleted file mode 100644 index 91093f6c..00000000 --- a/doc/docs/files/10095_SDK开发.md +++ /dev/null @@ -1,343 +0,0 @@ -# 开发SDK - -开放平台把接口开发完毕后,一般需要开发对应的SDK,提供给ISV。SOP提供了一个基础的SDK开发包 - -开发者可以在此基础上做开发,就拿sdk-java来说,具体步骤如下: - -## sdk-java - -SDK依赖了三个jar包 - -- okhttp.jar 用于网络请求 -- fastjson.jar 用于json处理 -- commons-logging.jar 日志处理 - -### 接口封装步骤 - -比如获取故事信息接口 - -- 接口名:alipay.story.find -- 版本号:1.0 -- 参数:name 故事名称 -- 返回信息 - -```json -{ - "alipay_story_find_response": { - "msg": "Success", - "code": "10000", - "name": "白雪公主", - "id": 1, - "gmtCreate": 1554193987378 - }, - "sign": "xxxxx" -} -``` - -针对这个接口,封装步骤如下: - -1.在`model`包下新建一个类,定义业务参数 - - -```java -@Data -public class GetStoryModel { - - @JSONField(name = "name") - private String name; -} -``` - -2.在`response`包下新建一个返回类GetStoryResponse,继承`BaseResponse` - -里面填写返回的字段 - -``` -@Data -public class GetStoryResponse extends BaseResponse { - private Long id; - private String name; - private Date gmtCreate; -} -``` - -3.在`request`包下新建一个请求类,继承`BaseRequest` - -BaseRequest中有个泛型参数,填`GetStoryResponse`类,表示这个请求对应的返回类。 -重写`method()`方法,填接口名。 - -如果要指定版本号,可重写`version()`方法,或者后续使用`request.setVersion(version)`进行设置 - -```java -public class GetStoryRequest extends BaseRequest { - @Override - protected String method() { - return "alipay.story.find"; - } -} -``` - -可重写`getRequestMethod()`方法指定HTTP请求method,默认是POST。 - -```java -@Override -protected RequestMethod getRequestMethod() { - return RequestMethod.GET; -} -``` - -**建议读请求用GET,写请求用POST**, - -### 使用方式 - -```java -String url = "http://localhost:8081/api"; -String appId = "2019032617262200001"; -String privateKey = "你的私钥"; - -// 声明一个就行 -OpenClient client = new OpenClient(url, appId, privateKey); - -// 标准用法 -@Test -public void testGet() { - // 创建请求对象 - GetStoryRequest request = new GetStoryRequest(); - // 请求参数 - GetStoryModel model = new GetStoryModel(); - model.setName("白雪公主"); - - request.setBizModel(model); - - // 发送请求 - GetStoryResponse response = client.execute(request); - - if (response.isSuccess()) { - // 返回结果 - System.out.println(String.format("成功!response:%s\n响应原始内容:%s", - JSON.toJSONString(response), response.getBody())); - } else { - System.out.println("错误,subCode:" + response.getSubCode() + ", subMsg:" + response.getSubMsg()); - } -} -``` - -### 使用方式2(懒人版) - -如果不想添加Request,Response,Model。可以用这种方式,返回body部分是字符串,后续自己处理 - -body对应的是alipay_story_find_response部分 - -```java -@Test -public void testLazy() { - // 创建请求对象 - CommonRequest request = new CommonRequest("alipay.story.find"); - // 请求参数 - Map bizModel = new HashMap<>(); - bizModel.put("name", "白雪公主"); - request.setBizModel(bizModel); - - // 发送请求 - CommonResponse response = client.execute(request); - - if (response.isSuccess()) { - // 返回结果,body对应的是alipay_story_find_response部分 - String body = response.getBody(); - JSONObject jsonObject = JSON.parseObject(body); - System.out.println(jsonObject); - } else { - System.out.println(response); - } -} -``` - -## sdk-.net - - -### 接口封装步骤 - -比如获取故事信息接口 - -- 接口名:alipay.story.find -- 版本号:1.0 -- 参数:name 故事名称 -- 返回信息 - -``` -{ - "alipay_story_find_response": { - "msg": "Success", - "code": "10000", - "name": "白雪公主", - "id": 1, - "gmtCreate": 1554193987378 - }, - "sign": "xxxxx" -} -``` - -针对这个接口,封装步骤如下: - -1.在`Model`包下新建一个类,定义业务参数 - -``` -namespace SDKCSharp.Model -{ - public class GetStoryModel - { - /// - /// 故事名称 - /// - /// The name. - [JsonProperty("name")] - public string Name { get; set; } - } -} -``` - -`[JsonProperty("name")]`是Newtonsoft.Json组件中的类,用于Json序列化,括号中的是参数名称。 -类似于Java中的注解,`@JSONField(name = "xx")` - -2.在`Response`包下新建一个返回类GetStoryResponse,继承`BaseResponse` - -里面填写返回的字段 - -``` -namespace SDKCSharp.Response -{ - public class GetStoryResponse: BaseResponse - { - [JsonProperty("id")] - public int Id { get; set; } - - [JsonProperty("name")] - public string Name { get; set; } - - [JsonProperty("gmt_create")] - public string GmtCreate { get; set; } - - } -} - -``` - -3.在`Request`文件夹下新建一个请求类,继承`BaseRequest` - -BaseRequest中有个泛型参数,填`GetStoryResponse`类,表示这个请求对应的返回类。 -重写`GetMethod()`方法,填接口名。 - -如果要指定版本号,可重写`GetVersion()`方法,或者后续使用`request.Version = version`进行设置 - -``` -namespace SDKCSharp.Request -{ - public class GetStoryRequest : BaseRequest - { - public override string GetMethod() - { - return "alipay.story.find"; - } - } - -} -``` - - - -### 使用方式 - -``` -class MainClass -{ - static string url = "http://localhost:8081/api"; - static string appId = "2019032617262200001"; - // 平台提供的私钥 - static string privateKey = "MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCXJv1pQFqWNA/++OYEV7WYXwexZK/J8LY1OWlP9X0T6wHFOvxNKRvMkJ5544SbgsJpVcvRDPrcxmhPbi/sAhdO4x2PiPKIz9Yni2OtYCCeaiE056B+e1O2jXoLeXbfi9fPivJZkxH/tb4xfLkH3bA8ZAQnQsoXA0SguykMRZntF0TndUfvDrLqwhlR8r5iRdZLB6F8o8qXH6UPDfNEnf/K8wX5T4EB1b8x8QJ7Ua4GcIUqeUxGHdQpzNbJdaQvoi06lgccmL+PHzminkFYON7alj1CjDN833j7QMHdPtS9l7B67fOU/p2LAAkPMtoVBfxQt9aFj7B8rEhGCz02iJIBAgMBAAECggEARqOuIpY0v6WtJBfmR3lGIOOokLrhfJrGTLF8CiZMQha+SRJ7/wOLPlsH9SbjPlopyViTXCuYwbzn2tdABigkBHYXxpDV6CJZjzmRZ+FY3S/0POlTFElGojYUJ3CooWiVfyUMhdg5vSuOq0oCny53woFrf32zPHYGiKdvU5Djku1onbDU0Lw8w+5tguuEZ76kZ/lUcccGy5978FFmYpzY/65RHCpvLiLqYyWTtaNT1aQ/9pw4jX9HO9NfdJ9gYFK8r/2f36ZE4hxluAfeOXQfRC/WhPmiw/ReUhxPznG/WgKaa/OaRtAx3inbQ+JuCND7uuKeRe4osP2jLPHPP6AUwQKBgQDUNu3BkLoKaimjGOjCTAwtp71g1oo+k5/uEInAo7lyEwpV0EuUMwLA/HCqUgR4K9pyYV+Oyb8d6f0+Hz0BMD92I2pqlXrD7xV2WzDvyXM3s63NvorRooKcyfd9i6ccMjAyTR2qfLkxv0hlbBbsPHz4BbU63xhTJp3Ghi0/ey/1HQKBgQC2VsgqC6ykfSidZUNLmQZe3J0p/Qf9VLkfrQ+xaHapOs6AzDU2H2osuysqXTLJHsGfrwVaTs00ER2z8ljTJPBUtNtOLrwNRlvgdnzyVAKHfOgDBGwJgiwpeE9voB1oAV/mXqSaUWNnuwlOIhvQEBwekqNyWvhLqC7nCAIhj3yvNQKBgQCqYbeec56LAhWP903Zwcj9VvG7sESqXUhIkUqoOkuIBTWFFIm54QLTA1tJxDQGb98heoCIWf5x/A3xNI98RsqNBX5JON6qNWjb7/dobitti3t99v/ptDp9u8JTMC7penoryLKK0Ty3bkan95Kn9SC42YxaSghzqkt+uvfVQgiNGQKBgGxU6P2aDAt6VNwWosHSe+d2WWXt8IZBhO9d6dn0f7ORvcjmCqNKTNGgrkewMZEuVcliueJquR47IROdY8qmwqcBAN7Vg2K7r7CPlTKAWTRYMJxCT1Hi5gwJb+CZF3+IeYqsJk2NF2s0w5WJTE70k1BSvQsfIzAIDz2yE1oPHvwVAoGAA6e+xQkVH4fMEph55RJIZ5goI4Y76BSvt2N5OKZKd4HtaV+eIhM3SDsVYRLIm9ZquJHMiZQGyUGnsvrKL6AAVNK7eQZCRDk9KQz+0GKOGqku0nOZjUbAu6A2/vtXAaAuFSFx1rUQVVjFulLexkXR3KcztL1Qu2k5pB6Si0K/uwQ="; - - - // 声明一个就行 - static OpenClient client = new OpenClient(url, appId, privateKey); - - public static void Main(string[] args) - { - TestGet(); - } - - // 标准用法 - private static void TestGet() - { - // 创建请求对象 - GetStoryRequest request = new GetStoryRequest(); - // 请求参数 - GetStoryModel model = new GetStoryModel(); - model.Name = "白雪公主"; - request.BizModel = model; - - // 发送请求 - GetStoryResponse response = client.Execute(request); - - if (response.IsSuccess()) - { - // 返回结果 - Console.WriteLine("故事名称:{0}", response.Name); - } - else - { - Console.WriteLine("错误, code:{0}, msg:{1}, subCode:{2}, subMsg:{3}", - response.Code, response.Msg, response.SubCode, response.SubMsg); - } - } - - -} -``` - -### 使用方式2(懒人版) - -如果不想添加Request,Response,Model。可以用这种方式,返回data部分是Dictionary,后续自己处理 - -``` -// 懒人版,如果不想添加Request,Response,Model。可以用这种方式,返回Dictionary,后续自己处理 -private static void TestCommon() -{ - // 创建请求对象 - CommonRequest request = new CommonRequest("alipay.story.find"); - // 请求参数 - Dictionary bizModel = new Dictionary - { - ["name"] = "白雪公主" - }; - - request.BizModel = bizModel; - - // 发送请求 - CommonResponse response = client.Execute(request); - - if (response.IsSuccess()) - { - // 返回结果 - string body = response.Body; - Dictionary dict = JsonUtil.ParseToDictionary(body); - - Console.WriteLine("Dictionary内容:"); - foreach (var item in dict) - { - Console.WriteLine("{0}:{1}", item.Key, item.Value); - } - } - else - { - Console.WriteLine("错误, code:{0}, msg:{1}, subCode:{2}, subMsg:{3}", - response.Code, response.Msg, response.SubCode, response.SubMsg); - } -} -``` - -## 发布SDK - -将编写好的SDK打包后上传到云服务器,如百度网盘 - -前往sop-admin,点击`SDK管理`菜单,点击`发布SDK`,填写SDK语言、版本、下载地址(网盘地址)、调用示例 - -保存后,ISV端会看到发布的SDK diff --git a/doc/docs/files/10097_应用授权.md b/doc/docs/files/10097_应用授权.md deleted file mode 100644 index b6e447a9..00000000 --- a/doc/docs/files/10097_应用授权.md +++ /dev/null @@ -1,117 +0,0 @@ -# 应用授权 - -## 概述 - -- 1、用户对开发者进行应用授权后,开发者可以帮助用户完成相应的业务逻辑。 -- 2、授权采用标准的OAuth 2.0流程。 - -## 授权流程 - -![授权流程](images/10097_1.png "10097_1.png") - -## 快速接入 - -- 第一步:应用授权URL拼装 - -拼接规则: - -http://openauth.yourdomain.com/oauth2/appToAppAuth?app_id=2019032617262200001&redirect_uri=http%3a%2f%2flocalhost%3a8087%2foauth2callback - -参数说明: - -| 参数 | 参数名称 | 类型 | 必填 | 描述 | 范例 | -|--------------|-------------|--------|----|---------------|--------------------------| -| app_id | 开发者应用的AppId | String | 是 | 开发者应用的AppId | 2015101400446982 | -| redirect_uri | 回调页面 | String | 是 | 参数需要UrlEncode | http%3A%2F%2Fexample.com | - - -- 第二步:获取code - -授权成功后,会跳转至开发者定义的回调页面(即redirect_uri参数对应的url),在回调页面请求中会带上当次授权的授权码code和开发者的app_id,示例如下: - -http://www.xxx.com/oauth2callback?app_id=2015101400446982&code=ca34ea491e7146cc87d25fca24c4cD11 - - -- 第三步:使用code换取app_auth_token - -接口名称:open.auth.token.app - -开发者通过code可以换取app_auth_token、授权用户的userId。 - -**注意**:应用授权的code唯一,code使用一次后失效,有效期24小时; app_auth_token永久有效。 - -**请求参数说明** - -| 参数 | 参数名称 | 类型 | 必填 | 描述 | 范例 | -|---------------|------|--------|----|---------------------------------------------------------------------------------|------------------------------------------| -| grant_type | 授权类型 | String | 是 | 如果使用code换取token,则为authorization_code,如果使用refresh_token换取新的token,则为refresh_token | authorization_code | -| code | 授权码 | String | 否 | 与refresh_token二选一,用户对应用授权后得到,即第一步中开发者获取到的code值 | bf67d8d5ed754af297f72cc482287X62 | -| refresh_token | 刷新令牌 | String | 否 | 与code二选一,可为空,刷新令牌时使用 | 201510BB0c409dd5758b4d939d4008a525463X62 | - -**接口请求SDK示例** - -```java -@GetMapping("oauth2callback") -@ResponseBody -public String callback(HttpServletRequest servletRequest, HttpServletResponse servletResponse) { - String app_id = servletRequest.getParameter("app_id"); - String code = servletRequest.getParameter("code"); - OpenAuthTokenAppRequest request = new OpenAuthTokenAppRequest(); - OpenAuthTokenAppModel model = new OpenAuthTokenAppModel(); - model.setCode(code); - model.setGrant_type("authorization_code"); - request.setBizModel(model); - // 根据code获取token - OpenAuthTokenAppResponse response = openClient.execute(request); - if (response.isSuccess()) { - // 成功拿到token,开发者在这里保存token信息 - // 后续使用token进行接口访问 - log.info("授权成功,body:{}", response.getBody()); - } - return response.getBody(); -} -``` - -**同步响应参数说明** - -| 参数 | 参数名称 | 类型 | 必填 | 描述 | 范例 | -|-------------------|---------|--------|----|----------------------------------------------------------|----------------------------------| -| app_auth_token | 用户授权令牌 | String | 是 | 通过该令牌来帮助用户发起请求,完成业务 | 856faf8d77d3b985c1073557ce6ea724 | -| user_id | 授权用户的ID | String | 是 | 授权者id | 1 | -| expires_in | 令牌有效期 | Long | 是 | 负值表示永久有效 | -1 | -| re_expires_in | 刷新令牌有效期 | Long | 是 | 负值表示永久有效 | -3 | -| app_refresh_token | 刷新令牌时使用 | String | 是 | 刷新令牌后,我们会保证老的app_auth_token从刷新开始10分钟内可继续使用,请及时替换为最新token | 88e68196d2359667f0dc8136e6c86803 | - - -**同步响应结果示例** - -```json -{ - "open_auth_token_app_response": { - "code": "10000", - "msg": "Success", - "app_auth_token": "88e68196d2359667f0dc8136e6c86803", - "app_refresh_token": "856faf8d77d3b985c1073557ce6ea724", - "expires_in": -1, - "re_expires_in": -3, - "user_id": "1" - }, - "sign": "xxx" -} -``` - -**刷新token** - -```java -OpenAuthTokenAppRequest request = new OpenAuthTokenAppRequest(); -OpenAuthTokenAppModel model = new OpenAuthTokenAppModel(); -model.setGrant_type("refresh_token"); -model.setRefresh_token("856faf8d77d3b985c1073557ce6ea724"); -request.setBizModel(model); -OpenAuthTokenAppResponse response = openClient.execute(request); -if (response.isSuccess()) { - // 成功拿到token,开发者在这里保存token信息 - // 后续使用token进行接口访问 - log.info("换取token成功,body:{}", response.getBody()); -} -``` \ No newline at end of file diff --git a/doc/docs/files/10100_提供restful接口.md b/doc/docs/files/10100_提供restful接口.md deleted file mode 100644 index 13ee9393..00000000 --- a/doc/docs/files/10100_提供restful接口.md +++ /dev/null @@ -1,147 +0,0 @@ -# 提供restful接口 - -有些接口没有被开放,但是也想要通过网关来访问,SOP提供一个固定的请求格式来访问。 - -请求格式: - -`http://ip:port/rest/服务id/your_path`,其中`http://ip:port/rest/`为固定部分,后面跟微服务请求路径。 - -下面是一个微服务的接口例子 - -```java -@RestController -@RequestMapping("food") -public class TraditionalWebappController { - @RequestMapping(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; - } -} -``` - -这是一个`食品服务`例子,serviceId为`food-service`,假设网关ip为10.0.1.11,端口8081;食品服务ip为10.0.1.22,端口2222 - -1. 网关访问:`http://10.0.1.11:8081/rest/food-service/food/getFoodById?id=2` - -2. 本地访问:`http://10.0.1.22:2222/food/getFoodById/?id=2` - - -由此可见,对于前端调用者来说,它把网关看做一个大服务,只访问网关提供的请求,不需要关心网关后面的路由转发。网关后面各个微服务独自管理, -微服务之间的调用可以使用dubbo或feign,有了版本号的管理,可以做到服务的平滑升级,对用户来说都是无感知的。结合SOP-Admin提供的上下线功能, -可实现预发布环境功能。 - -默认情况下,`http://ip:port/rest/`为固定部分,如果想要更改`rest`,可在网关配置文件指定:`sop.restful.path=/aaa` - -请求前缀将变成:`http://ip:port/aaa/` - -- 关闭restful功能 - -如果不想用该功能,在配置文件指定: - -```properties -# 开启restful请求,默认开启 -sop.restful.enable=false -``` - -- 封装请求工具【可选】 - -封装请求,方便调用,针对vue的封装如下: - -```js -import axios from 'axios' - -// 创建axios实例 -const client = axios.create({ - baseURL: process.env.BASE_API, // api 的 base_url - timeout: 5000, // 请求超时时间 - headers: { 'Content-Type': 'application/json' } -}) - -const RequestUtil = { - /** - * 请求接口 - * @param url 请求路径,如http://localhost:8081/rest/food-service/food/getFoodById - * @param data 请求数据,json格式 - * @param callback 成功回调 - * @param errorCallback 失败回调 - */ - post: function(url, data, callback, errorCallback) { - client.post(url, data) - .then(function(response) { - const resp = response.result - const code = resp.code - // 成功,网关正常且业务正常 - if (code === '10000' && !resp.sub_code) { - callback(resp) - } else { - // 报错 - Message({ - message: resp.msg, - type: 'error', - duration: 5 * 1000 - }) - } - }) - .catch(function(error) { - console.log('err' + error) // for debug - errorCallback && errorCallback(error) - }) - } -} - -export default RequestUtil -``` - -jQuery版本如下: - -```js -var RequestUtil = { - /** - * 请求接口 - * @param url 请求路径,如http://localhost:8081/rest/food-service/food/getFoodById - * @param data 请求数据,json格式 - * @param callback 成功回调 - * @param errorCallback 失败回调 - */ - post: function(url, data, callback, errorCallback) { - $.ajax({ - url: 'http://localhost:8081' // 网关url - , type: 'post' - , headers: { 'Content-Type': 'application/json' } - , data: data - ,success:function(response) { - var resp = response.result - var code = resp.code - // 成功,网关正常且业务正常 - if (code === '10000' && !resp.sub_code) { - callback(resp) - } else { - // 报错 - alert(resp.msg); - } - } - , error: function(error) { - errorCallback && errorCallback(error) - } - }); - } -} -``` - -jQuery调用示例: - -```js -$(function () { - var data = { - id: 1 - ,name: '葫芦娃' - } - RequestUtil.post('http://localhost:8081/rest/food-service/food/getFoodById', data, function (result) { - console.log(result) - }); -}) -``` diff --git a/doc/docs/files/10104_文件上传.md b/doc/docs/files/10104_文件上传.md deleted file mode 100644 index ac60ab36..00000000 --- a/doc/docs/files/10104_文件上传.md +++ /dev/null @@ -1,111 +0,0 @@ -# 文件上传 - -请求接口时带上文件 - -## 客户端调用 - -```java -DemoFileUploadRequest request = new DemoFileUploadRequest(); - -DemoFileUploadModel model = new DemoFileUploadModel(); -model.setRemark("上传文件参数"); -request.setBizModel(model); - -List files = new ArrayList<>(); -String root = System.getProperty("user.dir"); -System.out.println(root); -// 这里演示将resources下的两个文件上传到服务器 -files.add(new UploadFile("file1", new File(root + "/src/main/resources/file1.txt"))); -files.add(new UploadFile("file2", new File(root + "/src/main/resources/file2.txt"))); -request.setFiles(files); - -DemoFileUploadResponse response = client.execute(request); - -System.out.println("--------------------"); -if (response.isSuccess()) { - List responseFiles = response.getFiles(); - System.out.println("您上传的文件信息:"); - responseFiles.stream().forEach(file->{ - System.out.println(file); - }); -} else { - System.out.println("errorCode:" + response.getCode() + ",errorMsg:" + response.getMsg()); -} -System.out.println("--------------------"); -``` - -客户端使用`UploadFile`添加上传文件 - -```java -/** - * @param name 表单名称,不能重复 - * @param file 文件 - * @throws IOException - */ -public UploadFile(String name, File file) throws IOException { - this(name, file.getName(), FileUtil.toBytes(file)); -} -``` - -其中`name`表示字段名称,相当于``中的name - -源码详见sop-sdk - -## 服务端接收文件 - -- 方式1 - -```java -/** - * 方式1:将文件写在参数中,可直接获取。好处是可以校验是否上传 - * @param param - * @return - */ -@Open("file.upload") -@RequestMapping("file1") -public FileUploadVO file1(FileUploadParam param) { - System.out.println(param.getRemark()); - // 获取上传的文件 - MultipartFile file1 = param.getFile1(); - MultipartFile file2 = param.getFile2(); - ... - return vo; -} - -@Data -public class FileUploadParam { - private String remark; - - // 上传文件,字段名称对应表单中的name属性值 - @NotNull(message = "文件1不能为空") - private MultipartFile file1; - - @NotNull(message = "文件2不能为空") - private MultipartFile file2; -} -``` - -方式1的好处是可以使用`JSR-303`校验文件是否上传 - -- 方式2 - -```java -/** - * 方式2:从request中获取上传文件 - * - * @param param - * @return - */ -@Open("file.upload2") -@RequestMapping("file2") -public FileUploadVO file2(FileUploadParam2 param, HttpServletRequest request) { - System.out.println(param.getRemark()); - FileUploadVO vo = new FileUploadVO(); - // 获取上传的文件 - Collection uploadFiles = UploadUtil.getUploadFiles(request); - ... - return vo; -} -``` - -微服务端源码参考`FileUploadDemoController.java` \ No newline at end of file diff --git a/doc/docs/files/10109_配置Sleuth链路追踪.md b/doc/docs/files/10109_配置Sleuth链路追踪.md deleted file mode 100644 index 794bba25..00000000 --- a/doc/docs/files/10109_配置Sleuth链路追踪.md +++ /dev/null @@ -1,104 +0,0 @@ -# 配置Sleuth链路追踪 - -配置了Sleuth可以很方便查看微服务的调用路线图,可快速定位问题。 - -SOP基于SpringCloud,因此只要整合[Spring Cloud Sleuth](https://spring.io/projects/spring-cloud-sleuth)即可。 -除此之外,还需要支持dubbo的链路的跟踪,Sleuth在2.0已经对dubbo做了支持,详见:[brave-instrumentation-dubbo-rpc](https://github.com/openzipkin/brave/tree/master/instrumentation/dubbo-rpc) - -接入Spring Cloud Sleuth步骤如下: - -- 下载zipkin服务器 - -以mac环境为例,执行下面命令,下载jar并启动zipkin服务 - -``` -curl -sSL https://zipkin.io/quickstart.sh | bash -s -java -jar zipkin.jar -``` - -默认端口是9411,更多安装方式详见:[quickstart](https://zipkin.io/pages/quickstart.html) - -- sop-gateway/pom.xml添加依赖 - -```xml - - - org.springframework.cloud - spring-cloud-starter-zipkin - -``` - -配置文件新增 - -```properties -# zipkin服务跟踪 -spring.zipkin.base-url=http://127.0.0.1:9411/ -# 设置sleuth收集信息的比率,默认0.1,最大是1,数字越大越耗性能 -spring.sleuth.sampler.probability=1 -``` -重启sop-gateway - -- 打开sop-story-web/pom.xml - -添加依赖: - -```xml - - - org.springframework.cloud - spring-cloud-starter-zipkin - - - - io.zipkin.brave - brave-instrumentation-dubbo-rpc - 5.6.6 - -``` - -配置文件新增: - -```properties -# zipkin服务跟踪 -spring.zipkin.base-url=http://127.0.0.1:9411/ -# 设置sleuth收集信息的比率,默认0.1,最大是1,数字越大越耗性能 -spring.sleuth.sampler.probability=1 -# dubbo使用zipkin过滤器 -dubbo.provider.filter=tracing -dubbo.consumer.filter=tracing -``` - -重启服务 - -- 打开sop-book/sop-book-web/pom.xml - -步骤同上 - -- 运行DubboDemoTest.java单元测试 - -运行完毕看控制台,找到日志信息 - -```text -2019-07-18 16:22:04.438 INFO [story-service,59dae98250b276bd,60828035658f175f,true] 90553 --- [:12345-thread-2] c.g.s.s.service.DefaultDemoService : dubbo provider, param: DemoParam(id=222) -``` - -日志内容多了`[story-service,59dae98250b276bd,60828035658f175f,true]`部分,这些是zipkin加进去的,说明如下: - -```text -story-service:服务名称 -59dae98250b276bd:traceId -60828035658f175f:spanId -true:是否上传到zipkin服务器 -``` - -查看各个服务的控制台,可以发现traceId是一致的。 - -- 浏览器打开:http://127.0.0.1:9411/ - -将traceId复制黏贴到右上角文本框进行查询,可看到服务调用链。 - - -![预览](images/10109_1.png "10109_1.png") - - - diff --git a/doc/docs/files/10110_预发布灰度发布.md b/doc/docs/files/10110_预发布灰度发布.md deleted file mode 100644 index 4d5a0cb3..00000000 --- a/doc/docs/files/10110_预发布灰度发布.md +++ /dev/null @@ -1,71 +0,0 @@ -# 预发布灰度发布 - -从1.14.0开始支持预发布、灰度发布,可登陆`SOP-Admin`,然后选择`服务列表`进行操作。 - -## 使用预发布 - -SOP中预发布的思路如下: - -假设网关工程sop-gateway在阿里云负载均衡有两台服务器,域名分别为: - -|域名|说明| -|:---- |:---- | -|open1.domain.com |网关服务器1 | -|openpre.domain.com | 网关服务器2,作为预发布请求入口| - -SLB对外域名为:`open.domain.com`,即开放平台入口为:`http://open.domain.com` - -访问`open.domain.com`会负载均衡到`open1.domain.com`和`openpre.domain.com`这两台实例 - -如果单独从`openpre.domain.com`访问,则会访问到预发布微服务。 - -SOP开启预发布步骤如下: - -修改网关工程配置文件,指定预发布域名 - -```properties -# 预发布网关域名,多个用英文逗号(,)隔开 -pre.domain=openpre.domain.com -``` - -重启网关 - - -微服务启动参数添加:`--spring.cloud.nacos.discovery.metadata.env=pre`(eureka下是:`--eureka.instance.metadata-map.env=pre`)。 -建议线上配两套启动脚本,其中预发布启动脚本添加启动参数`--spring.cloud.nacos.discovery.metadata.env=pre` - -登录SOP-Admin,在服务列表中点击预发布。 - -从`openpre.domain.com`请求进来的用户都会进预发布服务器,从SLB域名进来请求路由到非预发服务器 - -## 使用灰度发布 - -- 灰度接口定义 - -接口名相同,版本号不同。比如接口`user.get`,version:1.0 - -新建一个接口`user.get`,version:2.0 - -那么这个2.0接口就是灰度接口 - -灰度发布可允许指定的用户访问灰度服务器,其它用户还是走正常流程。 - -登录SOP-Admin,前往`服务列表`。 - -- 先设置灰度参数,指定灰度appId和灰度接口 -- 服务器实例开启灰度 - - -参考类: - -- LoadBalanceServerChooser.java 预发布/灰度发布服务实例选择 - -### 自定义判断灰度用户 - -默认根据`appId`和`IP`来判断灰度用户,如果要通过其它维度来判断是否是灰度用户,可实现GrayUserBuilder接口, -然后在springboot main方法中调用如下方法 - -```java -ApiConfig.getInstance().addGrayUserBuilder(new XXGrayUserBuilder()); -``` -参考:com.gitee.sop.gatewaycommon.loadbalancer.builder.AppIdGrayUserBuilder.java \ No newline at end of file diff --git a/doc/docs/files/10111_动态修改请求参数.md b/doc/docs/files/10111_动态修改请求参数.md deleted file mode 100644 index 64f000ab..00000000 --- a/doc/docs/files/10111_动态修改请求参数.md +++ /dev/null @@ -1,33 +0,0 @@ -# 动态修改请求参数 - -自1.14.0开始,zuul网关支持动态修改请求参数。即在网关修改客户端传递过来的参数,然后发送到微服务端。 - -``` -客户端参数{"name": "jim"} --> zuul中修改为{"name": "Lucy"} --> 微服务端将收到{"name": "Lucy"} -``` - -使用场景:客户端请求参数经过加密,在网关解密后,再次发送明文参数给微服务端 - -- 如何使用 - -在网关springboot启动函数中添加如下代码 - -```java -public static void main(String[] args) { - ApiConfig.getInstance().setParameterFormatter(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 diff --git a/doc/docs/files/10112_使用eureka.md b/doc/docs/files/10112_使用eureka.md deleted file mode 100644 index 64bc2fe9..00000000 --- a/doc/docs/files/10112_使用eureka.md +++ /dev/null @@ -1,3 +0,0 @@ -# 使用eureka - -切换到`eureka`分支 diff --git a/doc/docs/files/10113_超时设置.md b/doc/docs/files/10113_超时设置.md deleted file mode 100644 index 89a1081e..00000000 --- a/doc/docs/files/10113_超时设置.md +++ /dev/null @@ -1,14 +0,0 @@ -# 超时设置 - -当微服务处理业务逻辑时间过长,网关会报超时错误,默认等待时间是5秒。 - -可在网关指定`spring.cloud.gateway.httpclient.response-timeout`参数设置超时时间,单位毫秒 - -```properties -# 设置响应超时10秒 -spring.cloud.gateway.httpclient.response-timeout=10000 -``` - -更多配置参见:`org.springframework.cloud.gateway.config.HttpClientProperties` - -测试用例参见:`com.gitee.sop.test.TimeoutTest` diff --git a/doc/docs/files/90001_网关性能测试.md b/doc/docs/files/90001_网关性能测试.md deleted file mode 100644 index 334299ec..00000000 --- a/doc/docs/files/90001_网关性能测试.md +++ /dev/null @@ -1,149 +0,0 @@ -# 网关性能测试 - -> 注意:记得关闭限流功能 - -**测试环境** - -- 测试工具:[wrk](https://github.com/wg/wrk),[安装教程](https://www.cnblogs.com/quanxiaoha/p/10661650.html) -- 服务器:CentOS7(虚拟机,宿主机:macbookpro),内存:2G,CPU:1,核数:2核 -- 运行环境:Java8、Mysql-5.7、Nacos-1.1.3 -- 网关启动参数: - -``` --verbose:gc -XX:+PrintGCDetails -XX:+PrintHeapAtGC -Xloggc:gc-zuul.log \ - -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=128m -Xms1024m -Xmx1024m -Xmn512m -Xss256k -XX:SurvivorRatio=8 \ - -XX:+UseConcMarkSweepGC -``` - -- zuul配置(仅针对zuul,Spring Cloud Gateway没有做优化配置): - -```properties -# 不校验时间,这样一个链接可以一直进行测试 -sop.api-config.timeout-seconds=0 -sop.restful.enable=true - -logging.level.com.gitee=info - -# zuul调优 -zuul.host.max-per-route-connections=5000 -zuul.host.max-total-connections=5000 -zuul.semaphore.max-semaphores=5000 - -ribbon.ReadTimeout=5000 -hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds=13000 - -logging.file=sop-gateway.log -``` - -CentOS允许最大连接数 -``` -$ ulimit -n -65535 -``` - -## 调用开放接口 - -- wrk命令: - -``` -wrk -t8 -c200 -d30s "http://10.1.31.227:8081/?charset=utf-8&biz_content=%7B%22name%22%3A%22%E8%91%AB%E8%8A%A6%E5%A8%83%22%2C%22id%22%3A%221%22%7D&method=alipay.story.get&format=json&sign=RjK%2FThnzAJQOw%2BfoVLS18PgWZAR%2B25SI2XdONFhS%2BmS8vsv2jNT3rygFoh%2ByX1AJbMgIEfcBzkQyqrs29jjd5dcwHVkcf7vxXshyfcEgl0fbMF6Ihycnz7rqSqkW3lzAWx4NuWUfkPnTX8Ffuf%2BhYRaI0NCpNv%2FV300HvsvmUjS6ZzS4YHaT1peSq0agfUhwRPd97aYMnUwRZDzxNfc5wuXA7OQ1o%2FPYIuIb%2FajVfwNP5ysitc%2FKtYEqt9rNAuzkcFmsw71d2RRnrPLsDN%2BuBXnIEh482f%2FbMj2Rj4%2FMq%2B0PEtlTRbg3rYnxyfowymfX%2BNmI4gNRUt70D4a%2FL3Qiug%3D%3D&app_id=2019032617262200001&sign_type=RSA2&version=1.0×tamp=2020-01-19+13%3A34%3A12" -``` - -- Spring Cloud Gateway测试结果 - -``` - 8 threads and 200 connections - Thread Stats Avg Stdev Max +/- Stdev - Latency 139.74ms 69.39ms 617.14ms 69.82% - Req/Sec 182.12 55.74 343.00 66.24% - 43391 requests in 30.09s, 11.96MB read -Requests/sec: 1441.96 -Transfer/sec: 406.96KB -``` - -结果说明,下同: - -``` - 8 threads and 200 connections (共8个测试线程,200个连接) - (平均值) (标准差) (最大值)(正负一个标准差所占比例) - Thread Stats Avg Stdev Max +/- Stdev - (延迟) - Latency 139.74ms 69.39ms 617.14ms 69.82% - (每秒请求数) - Req/Sec 182.12 55.74 343.00 66.24% - 43391 requests in 30.09s, 11.96MB read (30.09秒内处理了43391个请求,耗费流量11.96MB) -Requests/sec: 1441.96 (QPS 1441.96,即平均每秒处理请求数为1441.96) -Transfer/sec: 406.96KB (平均每秒流量406.96KB) -``` - -- Spring Cloud Zuul测试结果 - -``` - 8 threads and 200 connections - Thread Stats Avg Stdev Max +/- Stdev - Latency 230.14ms 331.27ms 2.00s 86.98% - Req/Sec 141.69 51.04 323.00 66.99% - 33945 requests in 30.09s, 9.88MB read - Socket errors: connect 0, read 0, write 0, timeout 385 -Requests/sec: 1128.05 -Transfer/sec: 336.15KB -``` - -## 调用restful请求 - -- wrk命令: - -``` -wrk -t8 -c200 -d30s "http://10.1.31.227:8081/rest/story-service/food/getFoodById?id=2" -``` - -线程数为 8,模拟 200 个并发请求,持续 30 秒 - -- Spring Cloud Gateway测试结果 - -``` - 8 threads and 200 connections - Thread Stats Avg Stdev Max +/- Stdev - Latency 120.14ms 58.30ms 513.85ms 67.47% - Req/Sec 210.47 54.26 770.00 69.37% - 50301 requests in 30.10s, 7.53MB read -Requests/sec: 1670.97 -Transfer/sec: 256.21KB -``` - - -- Spring Cloud Zuul测试结果 - -``` - 8 threads and 200 connections - Thread Stats Avg Stdev Max +/- Stdev - Latency 185.86ms 285.65ms 1.99s 88.55% - Req/Sec 167.75 55.60 460.00 68.05% - 40070 requests in 30.09s, 6.65MB read - Socket errors: connect 0, read 0, write 0, timeout 466 -Requests/sec: 1331.81 -Transfer/sec: 226.50KB -``` - - -综上所述,Spring Cloud Gateway在没有优化的情况下,压测表现比zuul好,但zuul的数据表现也不差,但是出现超时现象,总的来说还是Spring Cloud Gateway具有优势。 - -附启动脚本: - -`restart.sh` - -```bash -echo "Stopping sop-gateway-4.2.4-SNAPSHOT.jar" -pid=`ps -ef | grep sop-gateway-4.2.4-SNAPSHOT.jar | grep -v grep | awk '{print $2}'` -if [ -n "$pid" ] -then - echo "kill -9 的id:" $pid - kill -9 $pid -fi -nohup java -jar -verbose:gc -XX:+PrintGCDetails -XX:+PrintHeapAtGC -Xloggc:gc.log \ - -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=128m -Xms1024m -Xmx1024m -Xmn256m -Xss256k -XX:SurvivorRatio=8\ - -XX:+UseConcMarkSweepGC sop-gateway-4.2.4-SNAPSHOT.jar\ - --spring.profiles.active=dev --server.port=8081 & - -tail -f nohup.out -``` \ No newline at end of file diff --git a/doc/docs/files/90011_原理分析之如何存储路由.md b/doc/docs/files/90011_原理分析之如何存储路由.md deleted file mode 100644 index 575e5f82..00000000 --- a/doc/docs/files/90011_原理分析之如何存储路由.md +++ /dev/null @@ -1,46 +0,0 @@ -# 原理分析之如何存储路由 - -SOP基于spring cloud,因此会涉及到网关路由。但是开发者不用去配置文件定义路由的隐射关系,SOP帮你解决了这个问题。 - -## 获取路由信息 - -网关启动成后会触发一个事件,代码见:`com.gitee.sop.gatewaycommon.config.AbstractConfiguration.listenEvent` - -这个事件会取拉取微服务中提供的路由信息 - -下面以nacos为例,介绍拉取路由过程 - -1.从nacos中获取微服务实例 - -入口代码:`com.gitee.sop.bridge.route.NacosRegistryListener.onEvent` - -2.拿到微服务信息,调用微服务提供的接口拉取路由数据 - -入口代码:`com.gitee.sop.gatewaycommon.route.BaseRegistryListener.pullRoutes` - -最终落实到:`com.gitee.sop.gatewaycommon.route.ServiceRouteListener.onAddInstance` - -微服务提供一个url:`http://ip:port/sop/routes`,对应Controller在:`com.gitee.sop.servercommon.route.ServiceRouteController.listRoutes` - -微服务找到被`@Open`注解的方法,然后封装成一个路由对象,放到List中,最后返回给网关。 - -3.网关拿到路由信息,经过处理,转化成网关路由配置 - -关联方法: - -`com.gitee.sop.gatewaycommon.gateway.route.GatewayRouteCache.add` - -`com.gitee.sop.gatewaycommon.gateway.route.GatewayRouteCache.refresh` - - -路由的存储方式是一个Map,key为路由id,即接口名+版本号。 - -```java -/** - * key:nameVersion - */ -private static final Map routes = synchronizedMap(new LinkedHashMap<>()); -``` - -因为客户端调用接口都会传递一个接口名和版本号,因此通过这两个字段能够很快查询出路由信息,然后进行路由转发操作。 - diff --git a/doc/docs/files/90012_原理分析之如何路由.md b/doc/docs/files/90012_原理分析之如何路由.md deleted file mode 100644 index da7d95be..00000000 --- a/doc/docs/files/90012_原理分析之如何路由.md +++ /dev/null @@ -1,33 +0,0 @@ -# 原理分析之如何路由 - -Spring Cloud Gateway通过一系列的Filter来进行数据的传输,如下图所示: - -![流程图](images/90012_1.png) - -SOP网关在此基础上新增了几个Filter用来处理自己的逻辑,如:前置校验、结果返回。 - -| 过滤器 | 类型 | Order | 功能 | -| ----- | ---- | ----------------------- | ---------------------------- | -|IndexFilter| `自定义` | -2147483648 | 入口过滤器,获取参数、签名校验 | -|ParameterFormatterFilter | `自定义` | -2147482647 | 格式化参数 | -|LimitFilter|`自定义`|-2147482447|限流| -|ForwardPathFilter|系统自带|0 |设置转发的path| -|RouteToRequestUrlFilter|系统自带|10000|设置转发host| -|SopLoadBalancerClientFilter|`自定义`|10100|LoadBalance获取转发实例| -|NettyRoutingFilter|系统自带|2147483647|获取httpclient发送请求| -|ForwardRoutingFilter|系统自带|2147483647|请求分发| -|GatewayModifyResponseGatewayFilter|`自定义`|-2|处理响应结果| - -一个完整的请求会自上而下经过这些Filter,下面讲解如何动态设置路由。 - -## 动态设置路由 - -网关启动后会从注册中心拉取微服务实例,然后请求微服务提供的一个接口(`/sop/routes`),获取开放接口信息(被`@Open`注解的接口)。 - -监听处理类在:`com.gitee.sop.bridge.route.NacosRegistryListener` - -获取到路由信息后,将路由信息缓存到本地,并保存到数据库,代码在:`com.gitee.sop.gatewaycommon.gateway.route.GatewayRouteCache.load` - -然后动态设置Gateway路由,代码在:`com.gitee.sop.gatewaycommon.gateway.route.GatewayRouteRepository.refresh` - -当有微服务重新启动时,网关会监听到微服务实例有变更,会重复上述步骤,确保网关存有最新的路由。 diff --git a/doc/docs/files/90013_原理分析之文档归纳.md b/doc/docs/files/90013_原理分析之文档归纳.md deleted file mode 100644 index b782dd8e..00000000 --- a/doc/docs/files/90013_原理分析之文档归纳.md +++ /dev/null @@ -1,26 +0,0 @@ -# 原理分析之文档归纳 - -作为开放平台,必须要提供API文档。 - -SOP采用微服务架构实现,因此文档应该由各个微服务各自实现。难点是如何归纳各个微服务端提供的文档信息,并统一展示。 - -SOP的解决思路如下: - -- 各微服务使用swagger定义自己的接口信息 -- sop-website项目在启动时向注册中心获取所有服务实例,分别调用各个服务提供的swagger文档信息,保存到本地 -- sop-website前端页面负责展示swagger提供的文档信息 - -由于注册中心的存在,可以很方便的获取每个微服务提供的接口,因此可以获取到swagger提供的文档信息。 - -如此一来的好处是,各微服务不用关心文档该怎么展示,只需要写好swagger注解即可;文档信息展示统一交给另外一个工程来维护,各司其职。 - -SOP设计初衷亦是如此,微服务只管写业务代码,其它的都交给SOP来处理。 - -文档归纳原理图: - -![文档归纳原理图](images/90013_1.png "10090_1.png") - -- sop-website服务启动时向各微服务获取接口信息,保存到本地 -- 用户访问website页面,website提供对应的接口文档并展示 - - diff --git a/doc/docs/files/90014_原理分析之预发布灰度发布.md b/doc/docs/files/90014_原理分析之预发布灰度发布.md deleted file mode 100644 index 04bc5350..00000000 --- a/doc/docs/files/90014_原理分析之预发布灰度发布.md +++ /dev/null @@ -1,92 +0,0 @@ -# 原理分析之预发布灰度发布 - -SOP网关采用`自定义负载均衡策略`来实现对预发布/灰度发布服务器实例的选择。 - -spring cloud gateway默认的负载均衡实现类在:`org.springframework.cloud.gateway.filter.LoadBalancerClientFilter.java`中 - -这个类主要做了几件事情: - -1. 解析出请求路径中的scheme -2. 如果scheme不是以`lb`协议开头直接跳过 -3. 如果scheme以`lb`协议开头,则说明需要进行负载均衡,选出一台微服务实例 -4. 将`lb`协议解析成`http://ip:port`,继续向下请求 - -其中第4步是由`org.springframework.cloud.netflix.ribbon.RibbonLoadBalancerClient`来完成的, -我们只要分别继承`LoadBalancerClientFilter`和`RibbonLoadBalancerClient`,然后重写其中的方法就能完成自定义负载均衡。 - -SOP中的重写类是:`SopLoadBalancerClientFilter`和`SopLoadBalancerClient`,核心代码委托给了`LoadBalanceServerChooser`处理,核心代码如下: - -```java - /** - * 选择服务器 - * - * @param serviceId serviceId,仅gateway网关有作用 - * @param exchange 请求上下文 - * @param loadBalancer loadBalancer - * @param superChooser 父类默认的选择 - * @param serverChooserFunction 执行选择操作 - * @return 返回服务器实例,没有选到则返回null - */ - public R choose( - String serviceId - , T exchange - , ILoadBalancer loadBalancer - , Supplier superChooser - , Function, R> serverChooserFunction) { - // 获取所有服务实例 - List servers = loadBalancer.getReachableServers(); - - // 存放预发服务器 - List preServers = new ArrayList<>(4); - // 存放灰度发布服务器 - List grayServers = new ArrayList<>(4); - // 存放非预发服务器 - List notPreServers = new ArrayList<>(4); - - for (Server server : servers) { - // 获取实例metadata - Map metadata = getMetadata(serviceId, server); - // 是否开启了预发模式 - if (this.isPreServer(metadata)) { - preServers.add(server); - } else if (this.isGrayServer(metadata)) { - grayServers.add(server); - } else { - notPreServers.add(server); - } - } - notPreServers.addAll(grayServers); - // 如果没有开启预发布服务和灰度发布,直接用默认的方式 - if (preServers.isEmpty() && grayServers.isEmpty()) { - return superChooser.get(); - } - // 如果是从预发布域名访问过来,则认为是预发布请求,选出预发服务器 - if (this.isRequestFromPreDomain(exchange)) { - return serverChooserFunction.apply(preServers); - } - // 如果是灰度请求,则认为是灰度用户,选出灰度服务器 - if (this.isRequestGrayServer(exchange)) { - return serverChooserFunction.apply(grayServers); - } - - // 到这里说明不能访问预发/灰度服务器,则需要路由到非预发服务器 - // 注意:这里允许走灰度服务器,如果不允许走,注释notPreServers.addAll(grayServers);这行 - return serverChooserFunction.apply(notPreServers); - } -``` - -其业务逻辑如下: - -1. 选出`serviceId`对应的所有服务器实例 -2. 将服务器实例进行分类,分别放进`预发布List`,`灰度List`,`非预发布List`中 -3. 如果`预发布List`,`灰度List`都为空,表示没有开启任何预发/灰度服务,直接使用父类的负载均衡策略 -4. 如果是从预发布域名访问过来,则认为是预发布请求,选出预发服务器 -5. 如果是灰度请求,则认为是灰度用户,选出灰度服务器 -6. 最后剩下的是正常用户,正常用户不能走预发环境 - -## 参考类 - -- com.gitee.sop.gatewaycommon.gateway.filter.SopLoadBalancerClientFilter -- com.gitee.sop.gatewaycommon.gateway.loadbalancer.SopLoadBalancerClient -- com.gitee.sop.gatewaycommon.gateway.loadbalancer.GatewayLoadBalanceServerChooser - diff --git a/doc/docs/files/90100_常见问题.md b/doc/docs/files/90100_常见问题.md deleted file mode 100644 index 917a2974..00000000 --- a/doc/docs/files/90100_常见问题.md +++ /dev/null @@ -1,125 +0,0 @@ -# 常见问题 - -## Connection has been closed BEFORE send operation - -压测时出现`Connection has been closed BEFORE send operation`,`java.nio.channels.ClosedChannelException`之类的错误 - -- 解决办法 - -尝试调大内存分配1G或2G,`-Xms1g -Xmx1g` - -## spring cloud gateway [DataBufferLimitException: Exceeded limit on max bytes to buffer : 262144] - -如果POST请求body内容太大可能会报这个错误 - -- 解决版办法 - -网关指定配置`spring.codec.max-in-memory-size=xx`,xx默认是262144,即256K(262144=256*1024) - -## primordials is not defined - -node版本太高导致与gulp版本不兼容,解决方法:node退回11版本。参考https://blog.csdn.net/zxxzxx23/article/details/103000393 - -## Nacos指定group - -可在配置文件中添加:`spring.cloud.nacos.discovery.group=xxx`指定group,不加默认是:DEFAULT_GROUP - -## 在SpringCloudGateway中获取请求参数 - -```java -ApiParam apiParam = ServerWebExchangeUtil.getApiParam(exchange); -``` - -## 微服务端如何获取appId等参数 - -```java -OpenContext openContext = ServiceContext.getCurrentContext().getOpenContext(); -System.out.println("app_id:" + openContext.getAppId()); -System.out.println("token:" + openContext.getAppAuthToken()); -``` - - -## 如何关闭签名验证 - -- 针对某一个接口关闭签名验证 -`@Open(value = "alipay.story.get", ignoreValidate = true)` - - -## 注册到eureka显示hostname,非ip - -```properties -eureka.instance.prefer-ip-address=true -eureka.instance.instance-id=${spring.cloud.client.ip-address}:${server.port} -``` - -```xml - - org.springframework.cloud - spring-cloud-commons - -``` - -参考:https://www.jianshu.com/p/5ad8317961b7 - -## 直接访问服务的swagger-ui.html ,提示access forbidden - -找到微服务的`OpenServiceConfig.java`,重写内部类Swagger2中的swaggerAccessProtected()方法,返回false。线上请设置成true - -```java -// 开启文档 -@Configuration -@EnableSwagger2 -public static class Swagger2 extends SwaggerSupport { - @Override - protected String getDocTitle() { - return "故事API"; - } - - @Override - protected boolean swaggerAccessProtected() { - return false; - } -} -``` - -## 调试网关出现服务不可用 - -打断点调试,网关出现Read Timeout - -参考:https://blog.csdn.net/qq_36872046/article/details/81058045 - -yml添加: - -```properties -# https://blog.csdn.net/qq_36872046/article/details/81058045 -# 路由转发超时时间,毫秒,默认值1000,详见:RibbonClientConfiguration.DEFAULT_READ_TIMEOUT。 -# 如果微服务端 处理时间过长,会导致ribbon read超时,解决办法将这个值调大一点 -ribbon.ReadTimeout= 60000 -``` - - -## 其它微服务没有开放接口,需要排除 - -在sop-gateway项目中配置 - -```properties -# 排除服务,多个用,隔开 -sop.service.exclude=your-serviceId1,your-serviceId2 -``` - -或者使用正则: - -```properties -# 排除以"test-"开头的 -# 多个正则用英文分号(;)隔开 -sop.service.exclude-regex=test\\-.* -``` - -## ISV公私钥 & 平台公私钥 - -```java -ISV私钥(必须):ISV保存,用来生成签名 --> ISV公钥(必须):平台保存,用来校验签名是否正确 -平台私钥(非必须):平台保存,对返回结果生成签名 --> 平台公钥(非必须):ISV保存,用来校验签名是否正确 -``` - -总结:私钥负责加密生成签名,公钥负责校验签名是否正确 diff --git a/doc/docs/files/images/10041_1.png b/doc/docs/files/images/10041_1.png deleted file mode 100644 index 406a760e..00000000 Binary files a/doc/docs/files/images/10041_1.png and /dev/null differ diff --git a/doc/docs/files/images/10041_2.png b/doc/docs/files/images/10041_2.png deleted file mode 100644 index 49885e16..00000000 Binary files a/doc/docs/files/images/10041_2.png and /dev/null differ diff --git a/doc/docs/files/images/10041_3.png b/doc/docs/files/images/10041_3.png deleted file mode 100644 index 7dffd9b7..00000000 Binary files a/doc/docs/files/images/10041_3.png and /dev/null differ diff --git a/doc/docs/files/images/10041_4.png b/doc/docs/files/images/10041_4.png deleted file mode 100644 index 89f55a40..00000000 Binary files a/doc/docs/files/images/10041_4.png and /dev/null differ diff --git a/doc/docs/files/images/10085_1.png b/doc/docs/files/images/10085_1.png deleted file mode 100644 index 83ebecb7..00000000 Binary files a/doc/docs/files/images/10085_1.png and /dev/null differ diff --git a/doc/docs/files/images/10085_2.png b/doc/docs/files/images/10085_2.png deleted file mode 100644 index ab7fd094..00000000 Binary files a/doc/docs/files/images/10085_2.png and /dev/null differ diff --git a/doc/docs/files/images/10086_1.png b/doc/docs/files/images/10086_1.png deleted file mode 100644 index d5da23b6..00000000 Binary files a/doc/docs/files/images/10086_1.png and /dev/null differ diff --git a/doc/docs/files/images/10086_2.png b/doc/docs/files/images/10086_2.png deleted file mode 100644 index de61367b..00000000 Binary files a/doc/docs/files/images/10086_2.png and /dev/null differ diff --git a/doc/docs/files/images/10090_1.png b/doc/docs/files/images/10090_1.png deleted file mode 100644 index db577156..00000000 Binary files a/doc/docs/files/images/10090_1.png and /dev/null differ diff --git a/doc/docs/files/images/10090_2.png b/doc/docs/files/images/10090_2.png deleted file mode 100644 index 3ec5b9b8..00000000 Binary files a/doc/docs/files/images/10090_2.png and /dev/null differ diff --git a/doc/docs/files/images/10092_1.png b/doc/docs/files/images/10092_1.png deleted file mode 100644 index f8dc032f..00000000 Binary files a/doc/docs/files/images/10092_1.png and /dev/null differ diff --git a/doc/docs/files/images/10092_2.png b/doc/docs/files/images/10092_2.png deleted file mode 100644 index 7c00d359..00000000 Binary files a/doc/docs/files/images/10092_2.png and /dev/null differ diff --git a/doc/docs/files/images/10093_1.png b/doc/docs/files/images/10093_1.png deleted file mode 100644 index e9537dd0..00000000 Binary files a/doc/docs/files/images/10093_1.png and /dev/null differ diff --git a/doc/docs/files/images/10093_2.png b/doc/docs/files/images/10093_2.png deleted file mode 100644 index 5f52c578..00000000 Binary files a/doc/docs/files/images/10093_2.png and /dev/null differ diff --git a/doc/docs/files/images/10097_1.png b/doc/docs/files/images/10097_1.png deleted file mode 100644 index 6965e7e9..00000000 Binary files a/doc/docs/files/images/10097_1.png and /dev/null differ diff --git a/doc/docs/files/images/10109_1.png b/doc/docs/files/images/10109_1.png deleted file mode 100644 index 6dc46da9..00000000 Binary files a/doc/docs/files/images/10109_1.png and /dev/null differ diff --git a/doc/docs/files/images/90012_1.png b/doc/docs/files/images/90012_1.png deleted file mode 100644 index 1f94dca9..00000000 Binary files a/doc/docs/files/images/90012_1.png and /dev/null differ diff --git a/doc/docs/files/images/90013_1.png b/doc/docs/files/images/90013_1.png deleted file mode 100644 index aac955d9..00000000 Binary files a/doc/docs/files/images/90013_1.png and /dev/null differ diff --git a/doc/docs/index.html b/doc/docs/index.html deleted file mode 100644 index 951b1933..00000000 --- a/doc/docs/index.html +++ /dev/null @@ -1,68 +0,0 @@ - - - - - - SOP开发文档 - - - - - - - - - - - -
加载中 ...
- - - - - - - - - - - \ No newline at end of file diff --git a/doc/docs/请不要删除其它文件.txt b/doc/docs/请不要删除其它文件.txt deleted file mode 100644 index e69de29b..00000000 diff --git a/doc/pom.xml b/doc/pom.xml deleted file mode 100644 index 0b0f0832..00000000 --- a/doc/pom.xml +++ /dev/null @@ -1,49 +0,0 @@ - - 4.0.0 - com.gitee.sop - doc - 5.0.0-SNAPSHOT - - - - 1.8 - UTF-8 - UTF-8 - - - - - junit - junit - 4.8 - test - - - - - - - - - org.apache.maven.plugins - maven-surefire-plugin - 2.18.1 - - true - - - - org.apache.maven.plugins - maven-compiler-plugin - 2.3.2 - - ${java.version} - ${java.version} - ${project.build.sourceEncoding} - - - - - - diff --git a/doc/run.sh b/doc/run.sh deleted file mode 100644 index 8354ccf3..00000000 --- a/doc/run.sh +++ /dev/null @@ -1 +0,0 @@ -docsify serve docs diff --git a/doc/server.js b/doc/server.js deleted file mode 100644 index 785e4575..00000000 --- a/doc/server.js +++ /dev/null @@ -1,55 +0,0 @@ -const liveServer = require('live-server') -const isSSR = !!process.env.SSR -const middleware = [] - -if (isSSR) { - const Renderer = require('./packages/docsify-server-renderer/build.js') - const renderer = new Renderer({ - template: ` - - - - - docsify - - - - - - - - - `, - config: { - name: 'docsify', - repo: 'docsifyjs/docsify', - basePath: 'https://docsify.js.org/', - loadNavbar: true, - loadSidebar: true, - subMaxLevel: 3, - auto2top: true, - alias: { - '/de-de/changelog': '/changelog', - '/zh-cn/changelog': '/changelog', - '/changelog': - 'https://raw.githubusercontent.com/docsifyjs/docsify/master/CHANGELOG' - } - }, - path: './' - }) - - middleware.push(function(req, res, next) { - if (/\.(css|js)$/.test(req.url)) { - return next() - } - renderer.renderToString(req.url).then(html => res.end(html)) - }) -} - -const params = { - port: 3000, - watch: ['lib', 'docs', 'themes'], - middleware -} - -liveServer.start(params) diff --git a/doc/src/main/java/com/gitee/sop/doc/SidebarTest.java b/doc/src/main/java/com/gitee/sop/doc/SidebarTest.java deleted file mode 100644 index 34614d58..00000000 --- a/doc/src/main/java/com/gitee/sop/doc/SidebarTest.java +++ /dev/null @@ -1,99 +0,0 @@ -package com.gitee.sop.doc; - -import java.io.File; -import java.io.FileOutputStream; -import java.io.IOException; -import java.util.Comparator; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -/** - * 生成_sidebar.md文件,直接运行即可 - * - * @author 六如 - */ -public class SidebarTest { - - static String format = " * [%s](files/%s?t=%s)\r\n"; - static Map levelMap = new HashMap<>(8); - - static { - int i = 0; - levelMap.put("1", new Menu("* 开发文档\n", i++)); - levelMap.put("9", new Menu("* 原理分析\n", i++)); - } - - public static void main(String[] args) throws Exception { - String path = SidebarTest.class.getClassLoader().getResource("").getPath(); - String root = path.substring(0, path.indexOf("doc")) + "doc"; - String fileDir = root + "/docs/files"; - File dir = new File(fileDir); - File[] files = dir.listFiles(); - Stream filesStream = Stream.of(files); - Map> menuMap = filesStream - .sorted(Comparator.comparing(File::getName)) - .map(file -> { - if (file.isDirectory()) { - return null; - } - FileExt fileExt = new FileExt(); - fileExt.menu = file.getName().substring(0, 1); - fileExt.file = file; - return fileExt; - }) - .filter(Objects::nonNull) - .collect(Collectors.groupingBy(FileExt::getMenu)); - - StringBuilder output = new StringBuilder(); - output.append("* [首页](/?t=" + System.currentTimeMillis() + ")\n"); - for (Map.Entry> entry : menuMap.entrySet()) { - Menu menu = levelMap.get(entry.getKey()); - if (menu == null) { - continue; - } - output.append(menu.parentName); - for (FileExt fileExt : entry.getValue()) { - String filename = fileExt.file.getName(); - String title = filename.substring(filename.indexOf("_") + 1, filename.length() - 3); - String line = String.format(format, title, filename, System.currentTimeMillis()); - output.append(line); - } - } - - System.out.println(output); - - String sidebarFilepath = root + "/docs/_sidebar.md"; - - FileOutputStream out = new FileOutputStream(new File(sidebarFilepath)); - out.write(output.toString().getBytes()); - out.close(); - } - - static class Menu { - String parentName; - int order; - - public Menu(String parentName, int order) { - this.parentName = parentName; - this.order = order; - } - - } - - static class FileExt { - File file; - String menu; - - public File getFile() { - return file; - } - - public String getMenu() { - return menu; - } - } -} diff --git a/docker-entrypoint.sh b/docker-entrypoint.sh index e0b60768..6ae17cb7 100644 --- a/docker-entrypoint.sh +++ b/docker-entrypoint.sh @@ -3,11 +3,9 @@ JAVA_OPTS="-Xms128m -Xmx128m" # mysql, nacos配置 -args="--mysql.host=10.1.30.110:3306 --mysql.username=root --mysql.password=root --register.url=10.1.30.110:8848" +args="--mysql.host=10.1.30.110:3306 --mysql.username=root --mysql.password=root --dubbo.registry.address=10.1.30.110:8848" java $JAVA_OPTS -Djava.security.egd=file:/dev/./urandom -jar /sop/sop-gateway/sop-gateway.jar $args --logging.file.path=/sop/sop-gateway/log & java $JAVA_OPTS -Djava.security.egd=file:/dev/./urandom -jar /sop/sop-admin/sop-admin.jar $args --logging.file.path=/sop/sop-admin/log & -java $JAVA_OPTS -Djava.security.egd=file:/dev/./urandom -jar /sop/sop-website/sop-website.jar $args --logging.file.path=/sop/sop-website/log & -java $JAVA_OPTS -Djava.security.egd=file:/dev/./urandom -jar /sop/sop-auth/sop-auth.jar $args --logging.file.path=/sop/sop-auth/log & # 最后一条没有& -java $JAVA_OPTS -Djava.security.egd=file:/dev/./urandom -jar /sop/sop-story/sop-story.jar $args --logging.file.path=/sop/sop-story/log \ No newline at end of file +java $JAVA_OPTS -Djava.security.egd=file:/dev/./urandom -jar /sop/sop-story/sop-story.jar $args --logging.file.path=/sop/sop-story/log diff --git a/pom.xml b/pom.xml index 1a63b4c6..3967de0a 100644 --- a/pom.xml +++ b/pom.xml @@ -16,7 +16,6 @@ 一个开放平台解决方案项目,基于Dubbo实现,目标是能够让用户快速得搭建起自己的开放平台 - doc sop-example sop-admin sop-test diff --git a/sop-admin/sop-admin-frontend/public/favicon.ico b/sop-admin/sop-admin-frontend/public/favicon.ico index bef93d4b..91e1b1d9 100644 Binary files a/sop-admin/sop-admin-frontend/public/favicon.ico and b/sop-admin/sop-admin-frontend/public/favicon.ico differ diff --git a/sop-admin/sop-admin-frontend/public/logo.png b/sop-admin/sop-admin-frontend/public/logo.png new file mode 100644 index 00000000..3b1be259 Binary files /dev/null and b/sop-admin/sop-admin-frontend/public/logo.png differ diff --git a/sop-admin/sop-admin-frontend/public/logo.svg b/sop-admin/sop-admin-frontend/public/logo.svg deleted file mode 100644 index a63d2b1a..00000000 --- a/sop-admin/sop-admin-frontend/public/logo.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/sop-admin/sop-admin-frontend/src/layout/hooks/useNav.ts b/sop-admin/sop-admin-frontend/src/layout/hooks/useNav.ts index 3d1341aa..0628249b 100644 --- a/sop-admin/sop-admin-frontend/src/layout/hooks/useNav.ts +++ b/sop-admin/sop-admin-frontend/src/layout/hooks/useNav.ts @@ -140,7 +140,7 @@ export function useNav() { /** 获取`logo` */ function getLogo() { - return new URL("/logo.svg", import.meta.url).href; + return new URL("/logo.png", import.meta.url).href; } return { diff --git a/sop-example/example-payment/src/main/java/com/gitee/sop/payment/open/req/PayTradeWapPayRequest.java b/sop-example/example-payment/src/main/java/com/gitee/sop/payment/open/req/PayTradeWapPayRequest.java index 9b97eae3..94b4948a 100644 --- a/sop-example/example-payment/src/main/java/com/gitee/sop/payment/open/req/PayTradeWapPayRequest.java +++ b/sop-example/example-payment/src/main/java/com/gitee/sop/payment/open/req/PayTradeWapPayRequest.java @@ -21,13 +21,13 @@ public class PayTradeWapPayRequest { @ApiModelProperty(value = "商户网站唯一订单号", required = true, example = "70501111111S001111119") @Length(max = 64) - @NotBlank - private String out_trade_no; + @NotBlank(message = "商户网站唯一订单号必填") + private String outTradeNo; @ApiModelProperty(value = "订单总金额.单位为元,精确到小数点后两位,取值范围:[0.01,100000000] ", required = true, example = "9.00") - @NotNull - private BigDecimal total_amount; + @NotNull(message = "订单总金额不能为空") + private BigDecimal totalAmount; @ApiModelProperty( @@ -36,7 +36,7 @@ public class PayTradeWapPayRequest { example = "大乐透" ) @Length(max = 256) - @NotBlank + @NotBlank(message = "订单标题不能为空") private String subject; @ApiModelProperty( @@ -44,9 +44,9 @@ public class PayTradeWapPayRequest { required = true, example = "QUICK_WAP_WAY" ) - @NotBlank + @NotBlank(message = "销售产品码不能为空") @Length(max = 64) - private String product_code; + private String productCode; @ApiModelProperty( @@ -54,7 +54,7 @@ public class PayTradeWapPayRequest { example = "appopenBb64d181d0146481ab6a762c00714cC27" ) @Length(max = 40) - private String auth_token; + private String authToken; @ApiModelProperty( value = "用户付款中途退出返回商户网站的地址", @@ -66,21 +66,21 @@ public class PayTradeWapPayRequest { @ApiModelProperty( value = "订单包含的商品列表信息,json格式,其它说明详见商品明细说明" ) - private List goods_detail; + private List goodsDetail; @ApiModelProperty( value = "绝对超时时间,格式为yyyy-MM-dd HH:mm:ss。超时时间范围:1m~15d。", example = "2016-12-31 10:05:00" ) @Length(max = 32) - private String time_expire; + private String timeExpire; @ApiModelProperty( value = "商户传入业务信息,具体值要和支付平台约定,应用于安全,营销等参数直传场景,格式为json格式", example = "{\"mc_create_trade_ip\":\"127.0.0.1\"}" ) @Length(max = 512) - private String business_params; + private String businessParams; @ApiModelProperty( @@ -88,14 +88,14 @@ public class PayTradeWapPayRequest { example = "merchantBizType%3d3C%26merchantBizNo%3d2016010101111" ) @Length(max = 512) - private String passback_params; + private String passbackParams; @ApiModelProperty( value = "商户原始订单号,最大长度限制32位", example = "{\"mc_create_trade_ip\":\"127.0.0.1\"}" ) @Length(max = 32) - private String merchant_order_no; + private String merchantOrderNo; // --- @@ -108,7 +108,7 @@ public class PayTradeWapPayRequest { ) @NotBlank @Length(max = 64) - private String goods_id; + private String goodsId; @ApiModelProperty( @@ -118,7 +118,7 @@ public class PayTradeWapPayRequest { ) @NotBlank @Length(max = 256) - private String goods_name; + private String goodsName; @ApiModelProperty( value = "商品数量", @@ -141,21 +141,21 @@ public class PayTradeWapPayRequest { example = "20010001" ) @Length(max = 32) - private String alipay_goods_id; + private String alipayGoodsId; @ApiModelProperty( value = "商品类目", example = "34543238" ) @Length(max = 24) - private String goods_category; + private String goodsCategory; @ApiModelProperty( value = "商品类目树,从商品类目根节点到叶子节点的类目id组成,类目id值使用|分割", example = "124868003|126232002|126252004" ) @Length(max = 128) - private String categories_tree; + private String categoriesTree; @ApiModelProperty( value = "商品描述信息", @@ -169,7 +169,7 @@ public class PayTradeWapPayRequest { example = "http://www.alipay.com/xxx.jpg" ) @Length(max = 400) - private String show_url; + private String showUrl; } diff --git a/sop-example/example-story/src/main/java/com/gitee/sop/storyweb/impl/OpenStoryImpl.java b/sop-example/example-story/src/main/java/com/gitee/sop/storyweb/impl/OpenStoryImpl.java index f505c4bf..927ea0e7 100644 --- a/sop-example/example-story/src/main/java/com/gitee/sop/storyweb/impl/OpenStoryImpl.java +++ b/sop-example/example-story/src/main/java/com/gitee/sop/storyweb/impl/OpenStoryImpl.java @@ -2,7 +2,7 @@ package com.gitee.sop.storyweb.impl; import com.gitee.sop.storyweb.message.StoryMessageEnum; import com.gitee.sop.storyweb.open.OpenStory; -import com.gitee.sop.storyweb.open.req.StorySaveDTO; +import com.gitee.sop.storyweb.open.req.StorySaveRequest; import com.gitee.sop.storyweb.open.resp.StoryResponse; import com.gitee.sop.support.context.OpenContext; import com.gitee.sop.support.dto.CommonFileData; @@ -29,13 +29,13 @@ public class OpenStoryImpl implements OpenStory { @Override - public Integer save(StorySaveDTO storySaveDTO) { + public Integer save(StorySaveRequest storySaveDTO) { return 1; } @Override - public Integer update(Integer id, StorySaveDTO storySaveDTO) { + public Integer update(Integer id, StorySaveRequest storySaveDTO) { System.out.println("update, id:" + id + ", storySaveDTO=" + storySaveDTO); return 1; } @@ -84,7 +84,7 @@ public class OpenStoryImpl implements OpenStory { } @Override - public StoryResponse upload(StorySaveDTO storySaveDTO, FileData file) { + public StoryResponse upload(StorySaveRequest storySaveDTO, FileData file) { System.out.println("getName:" + file.getName()); System.out.println("getOriginalFilename:" + file.getOriginalFilename()); checkFile(Arrays.asList(file)); @@ -97,7 +97,7 @@ public class OpenStoryImpl implements OpenStory { @Override - public StoryResponse upload2(StorySaveDTO storySaveDTO, FileData idCardFront, FileData idCardBack) { + public StoryResponse upload2(StorySaveRequest storySaveDTO, FileData idCardFront, FileData idCardBack) { List list = new ArrayList<>(); System.out.println("upload:" + storySaveDTO); checkFile(Arrays.asList(idCardFront, idCardBack)); @@ -109,7 +109,7 @@ public class OpenStoryImpl implements OpenStory { } @Override - public StoryResponse upload3(StorySaveDTO storySaveDTO, List files) { + public StoryResponse upload3(StorySaveRequest storySaveDTO, List files) { List list = new ArrayList<>(); list.add("upload:" + storySaveDTO); checkFile(files); diff --git a/sop-example/example-story/src/main/java/com/gitee/sop/storyweb/open/OpenStory.java b/sop-example/example-story/src/main/java/com/gitee/sop/storyweb/open/OpenStory.java index df2e9f45..f7ce89dc 100644 --- a/sop-example/example-story/src/main/java/com/gitee/sop/storyweb/open/OpenStory.java +++ b/sop-example/example-story/src/main/java/com/gitee/sop/storyweb/open/OpenStory.java @@ -1,6 +1,6 @@ package com.gitee.sop.storyweb.open; -import com.gitee.sop.storyweb.open.req.StorySaveDTO; +import com.gitee.sop.storyweb.open.req.StorySaveRequest; import com.gitee.sop.storyweb.open.resp.StoryResponse; import com.gitee.sop.support.annotation.Open; import com.gitee.sop.support.context.OpenContext; @@ -18,10 +18,10 @@ import java.util.List; public interface OpenStory { @Open("story.save") - Integer save(StorySaveDTO storySaveDTO); + Integer save(StorySaveRequest storySaveRequest); @Open("story.update") - Integer update(Integer id, StorySaveDTO storySaveDTO); + Integer update(Integer id, StorySaveRequest storySaveRequest); // 演示抛出异常 @Open("story.updateError") @@ -48,12 +48,12 @@ public interface OpenStory { // 演示单文件上传 @Open("story.upload") - StoryResponse upload(StorySaveDTO storySaveDTO, FileData file); + StoryResponse upload(StorySaveRequest storySaveRequest, FileData file); // 演示多文件上传 @Open("story.upload.more") StoryResponse upload2( - StorySaveDTO storySaveDTO, + StorySaveRequest storySaveRequest, @NotNull(message = "身份证正面必填") FileData idCardFront, @NotNull(message = "身份证背面必填") FileData idCardBack ); @@ -61,7 +61,7 @@ public interface OpenStory { // 演示多文件上传 @Open("story.upload.list") StoryResponse upload3( - StorySaveDTO storySaveDTO, + StorySaveRequest storySaveRequest, @Size(min = 2, message = "最少上传2个文件") List files ); diff --git a/sop-example/example-story/src/main/java/com/gitee/sop/storyweb/open/req/StorySaveDTO.java b/sop-example/example-story/src/main/java/com/gitee/sop/storyweb/open/req/StorySaveRequest.java similarity index 89% rename from sop-example/example-story/src/main/java/com/gitee/sop/storyweb/open/req/StorySaveDTO.java rename to sop-example/example-story/src/main/java/com/gitee/sop/storyweb/open/req/StorySaveRequest.java index 995ae1c7..d59f8ccf 100644 --- a/sop-example/example-story/src/main/java/com/gitee/sop/storyweb/open/req/StorySaveDTO.java +++ b/sop-example/example-story/src/main/java/com/gitee/sop/storyweb/open/req/StorySaveRequest.java @@ -11,7 +11,7 @@ import java.util.Date; * @author 六如 */ @Data -public class StorySaveDTO implements Serializable { +public class StorySaveRequest implements Serializable { private static final long serialVersionUID = -1214422742659231037L; @NotBlank(message = "故事名称必填") diff --git a/sop-gateway/src/main/java/com/gitee/sop/gateway/config/ApiConfig.java b/sop-gateway/src/main/java/com/gitee/sop/gateway/config/ApiConfig.java index b66cdac1..04f2e4e1 100644 --- a/sop-gateway/src/main/java/com/gitee/sop/gateway/config/ApiConfig.java +++ b/sop-gateway/src/main/java/com/gitee/sop/gateway/config/ApiConfig.java @@ -88,7 +88,7 @@ public class ApiConfig { private String zoneId = "Asia/Shanghai"; /** - * 返回结果字段小写形式 + * 字段下划线小写形式 */ - private Boolean fieldLowercase = false; + private Boolean fieldSnakeCase = false; } diff --git a/sop-gateway/src/main/java/com/gitee/sop/gateway/config/GatewayConfig.java b/sop-gateway/src/main/java/com/gitee/sop/gateway/config/GatewayConfig.java index efa140e8..32f520e4 100644 --- a/sop-gateway/src/main/java/com/gitee/sop/gateway/config/GatewayConfig.java +++ b/sop-gateway/src/main/java/com/gitee/sop/gateway/config/GatewayConfig.java @@ -1,13 +1,12 @@ package com.gitee.sop.gateway.config; import com.gitee.sop.gateway.service.ParamExecutor; -import com.gitee.sop.gateway.service.impl.ParamExecutorImpl; import com.gitee.sop.gateway.service.RouteService; -import com.gitee.sop.gateway.service.impl.RouteServiceImpl; import com.gitee.sop.gateway.service.Serde; +import com.gitee.sop.gateway.service.impl.ParamExecutorImpl; +import com.gitee.sop.gateway.service.impl.RouteServiceImpl; import com.gitee.sop.gateway.service.impl.SerdeGsonImpl; import com.gitee.sop.gateway.service.impl.SerdeImpl; -import com.gitee.sop.gateway.service.interceptor.internal.ResultRouteInterceptor; import com.gitee.sop.gateway.service.manager.ApiManager; import com.gitee.sop.gateway.service.manager.IsvApiPermissionManager; import com.gitee.sop.gateway.service.manager.IsvManager; @@ -99,13 +98,6 @@ public class GatewayConfig { return new SerdeGsonImpl(); } - // DEFAULT ROUTE INTERCEPTOR - @Bean - @ConditionalOnMissingBean - public ResultRouteInterceptor resultRouteInterceptor() { - return new ResultRouteInterceptor(); - } - @Bean @ConditionalOnMissingBean public ParamExecutor paramExecutor() { diff --git a/sop-gateway/src/main/java/com/gitee/sop/gateway/service/interceptor/RouteInterceptor.java b/sop-gateway/src/main/java/com/gitee/sop/gateway/interceptor/RouteInterceptor.java similarity index 84% rename from sop-gateway/src/main/java/com/gitee/sop/gateway/service/interceptor/RouteInterceptor.java rename to sop-gateway/src/main/java/com/gitee/sop/gateway/interceptor/RouteInterceptor.java index 42a21b5b..521ddf88 100644 --- a/sop-gateway/src/main/java/com/gitee/sop/gateway/service/interceptor/RouteInterceptor.java +++ b/sop-gateway/src/main/java/com/gitee/sop/gateway/interceptor/RouteInterceptor.java @@ -1,4 +1,4 @@ -package com.gitee.sop.gateway.service.interceptor; +package com.gitee.sop.gateway.interceptor; import com.gitee.sop.gateway.common.ApiInfoDTO; import com.gitee.sop.gateway.request.ApiRequestContext; @@ -12,6 +12,9 @@ public interface RouteInterceptor { /** * 在路由转发前执行,签名校验通过后会立即执行此方法 + *
+     * 在这个方法中抛出异常会中断接口执行,直接返回错误信息
+     * 
* * @param context context * @param apiInfoDTO 接口信息 @@ -24,7 +27,7 @@ public interface RouteInterceptor { * * @param context context * @param apiInfoDTO 接口信息 - * @param result 返回结果,通常是HashMap + * @param result 业务返回结果,通常是HashMap * @return 返回格式化后的结果, 可对原结果进行修改 */ default Object afterRoute(ApiRequestContext context, ApiInfoDTO apiInfoDTO, Object result) { diff --git a/sop-gateway/src/main/java/com/gitee/sop/gateway/interceptor/RouteInterceptorOrders.java b/sop-gateway/src/main/java/com/gitee/sop/gateway/interceptor/RouteInterceptorOrders.java new file mode 100644 index 00000000..96e4196f --- /dev/null +++ b/sop-gateway/src/main/java/com/gitee/sop/gateway/interceptor/RouteInterceptorOrders.java @@ -0,0 +1,10 @@ +package com.gitee.sop.gateway.interceptor; + +/** + * @author 六如 + */ +public class RouteInterceptorOrders { + + public static final int RESULT_INTERCEPTOR = -1000; + +} diff --git a/sop-gateway/src/main/java/com/gitee/sop/gateway/service/interceptor/internal/ResultRouteInterceptor.java b/sop-gateway/src/main/java/com/gitee/sop/gateway/interceptor/internal/ResultRouteInterceptor.java similarity index 90% rename from sop-gateway/src/main/java/com/gitee/sop/gateway/service/interceptor/internal/ResultRouteInterceptor.java rename to sop-gateway/src/main/java/com/gitee/sop/gateway/interceptor/internal/ResultRouteInterceptor.java index af6489ca..c999fb6b 100644 --- a/sop-gateway/src/main/java/com/gitee/sop/gateway/service/interceptor/internal/ResultRouteInterceptor.java +++ b/sop-gateway/src/main/java/com/gitee/sop/gateway/interceptor/internal/ResultRouteInterceptor.java @@ -1,11 +1,12 @@ -package com.gitee.sop.gateway.service.interceptor.internal; +package com.gitee.sop.gateway.interceptor.internal; import com.gitee.sop.gateway.common.ApiInfoDTO; import com.gitee.sop.gateway.request.ApiRequestContext; -import com.gitee.sop.gateway.service.interceptor.RouteInterceptor; -import com.gitee.sop.gateway.service.interceptor.RouteInterceptorOrders; +import com.gitee.sop.gateway.interceptor.RouteInterceptor; +import com.gitee.sop.gateway.interceptor.RouteInterceptorOrders; import com.gitee.sop.support.dto.CommonFileData; import com.gitee.sop.support.dto.FileData; +import org.springframework.stereotype.Component; import java.util.HashMap; import java.util.Map; @@ -17,6 +18,7 @@ import java.util.Objects; * * @author 六如 */ +@Component public class ResultRouteInterceptor implements RouteInterceptor { private static final String CLASS = "class"; diff --git a/sop-gateway/src/main/java/com/gitee/sop/gateway/response/ApiResponse.java b/sop-gateway/src/main/java/com/gitee/sop/gateway/response/ApiResponse.java index 2f900bd8..c71e9882 100644 --- a/sop-gateway/src/main/java/com/gitee/sop/gateway/response/ApiResponse.java +++ b/sop-gateway/src/main/java/com/gitee/sop/gateway/response/ApiResponse.java @@ -3,7 +3,6 @@ package com.gitee.sop.gateway.response; import com.gitee.sop.gateway.message.ErrorEnum; import com.gitee.sop.gateway.message.IError; import lombok.Data; -import lombok.EqualsAndHashCode; import java.util.Locale; @@ -53,19 +52,41 @@ import java.util.Locale; * * @author 六如 */ -@EqualsAndHashCode(callSuper = true) @Data -public class ApiResponse extends BaseResponse { +public class ApiResponse implements Response { + + public static final String SUCCESS_CODE = "0"; + public static final String SUCCESS_MSG = "success"; + + /** + * 网关异常码,范围0~100 成功返回"0" + */ + private String code = SUCCESS_CODE; + + /** + * 网关异常信息 + */ + private String msg = ""; + + /** + * 返回对象 + */ + private Object data; /** * 业务异常码 */ - private String subCode = ""; + private String sub_code = ""; /** * 业务异常信息 */ - private String subMsg = ""; + private String sub_msg = ""; + + /** + * 解决方案 + */ + private String solution; public static ApiResponse success(Object data) { ApiResponse apiResponse = new ApiResponse(); @@ -86,8 +107,8 @@ public class ApiResponse extends BaseResponse { ApiResponse apiResponse = new ApiResponse(); apiResponse.setCode(error.getCode()); apiResponse.setMsg(error.getMsg()); - apiResponse.setSubCode(subCode); - apiResponse.setSubMsg(subMsg); + apiResponse.setSub_code(subCode); + apiResponse.setSub_msg(subMsg); apiResponse.setSolution(solution); return apiResponse; } @@ -99,8 +120,8 @@ public class ApiResponse extends BaseResponse { public static ApiResponse error(IError error) { ApiResponse apiResponse = new ApiResponse(); - apiResponse.setSubCode(error.getSubCode()); - apiResponse.setSubMsg(error.getSubMsg()); + apiResponse.setSub_code(error.getSubCode()); + apiResponse.setSub_msg(error.getSubMsg()); apiResponse.setCode(error.getCode()); apiResponse.setMsg(error.getMsg()); apiResponse.setSolution(error.getSolution()); @@ -109,18 +130,7 @@ public class ApiResponse extends BaseResponse { private static ApiResponse error(IError error, String subMsg) { ApiResponse response = error(error); - response.setSubMsg(subMsg); + response.setSub_msg(subMsg); return response; } - - public Response toLower() { - ApiResponseLower apiResponseLower = new ApiResponseLower(); - apiResponseLower.setSub_code(this.subCode); - apiResponseLower.setSub_msg(this.subMsg); - apiResponseLower.setCode(this.subCode); - apiResponseLower.setMsg(this.subMsg); - apiResponseLower.setData(this.getData()); - apiResponseLower.setSolution(this.getSolution()); - return apiResponseLower; - } } diff --git a/sop-gateway/src/main/java/com/gitee/sop/gateway/response/ApiResponseLower.java b/sop-gateway/src/main/java/com/gitee/sop/gateway/response/ApiResponseLower.java deleted file mode 100644 index e7a95101..00000000 --- a/sop-gateway/src/main/java/com/gitee/sop/gateway/response/ApiResponseLower.java +++ /dev/null @@ -1,108 +0,0 @@ -package com.gitee.sop.gateway.response; - -import com.gitee.sop.gateway.exception.ApiException; -import com.gitee.sop.gateway.message.ErrorEnum; -import com.gitee.sop.gateway.message.IError; -import lombok.Data; -import lombok.EqualsAndHashCode; - -import java.util.Locale; - - -/** - * 默认的结果封装类. - *
- *
- * xml返回结果:
- * 
- *     50
- *     Remote service error
- *     isv.invalid-parameter
- *     非法参数
- * 
- * 成功情况:
- * 
- *     0
- *     成功消息
- *     
- *         ...返回内容
- *     
- * 
- *
- * json返回格式:
- * {
- *  "code":"50",
- * 	"msg":"Remote service error",
- * 	"sub_code":"isv.invalid-parameter",
- * 	"sub_msg":"非法参数"
- * }
- * 成功情况:
- * {
- *  "code":"0",
- * 	"msg":"成功消息内容。。。",
- * 	"data":{
- * 	    ...返回内容
- *    }
- * }
- * 
- *

- * 字段说明: - * code:网关异常码
- * msg:网关异常信息
- * sub_code:业务异常码
- * sub_msg:业务异常信息
- * - * @author 六如 - */ -@EqualsAndHashCode(callSuper = true) -@Data -public class ApiResponseLower extends BaseResponse { - - /** - * 业务异常码 - */ - private String sub_code = ""; - - /** - * 业务异常信息 - */ - private String sub_msg = ""; - - public static ApiResponseLower success(Object data) { - ApiResponseLower apiResponse = new ApiResponseLower(); - apiResponse.setCode(SUCCESS_CODE); - apiResponse.setMsg(SUCCESS_MSG); - apiResponse.setData(data); - return apiResponse; - } - - - public static ApiResponseLower error(ApiException e) { - IError error = e.getError(); - return error(error); - } - - public static ApiResponseLower error(ErrorEnum errorEnum, Locale locale, String subMsg) { - IError error = errorEnum.getError(locale); - return error(error, subMsg); - } - - public static ApiResponseLower error(ErrorEnum errorEnum, Locale locale) { - IError error = errorEnum.getError(locale); - return error(error); - } - - public static ApiResponseLower error(IError error) { - return error(error, error.getSubMsg()); - } - - public static ApiResponseLower error(IError error, String subMsg) { - ApiResponseLower apiResponse = new ApiResponseLower(); - apiResponse.setSub_code(error.getSubCode()); - apiResponse.setSub_msg(error.getSubMsg()); - apiResponse.setCode(error.getCode()); - apiResponse.setMsg(subMsg); - return apiResponse; - } - -} diff --git a/sop-gateway/src/main/java/com/gitee/sop/gateway/response/BaseResponse.java b/sop-gateway/src/main/java/com/gitee/sop/gateway/response/BaseResponse.java deleted file mode 100644 index 44d28bcc..00000000 --- a/sop-gateway/src/main/java/com/gitee/sop/gateway/response/BaseResponse.java +++ /dev/null @@ -1,33 +0,0 @@ -package com.gitee.sop.gateway.response; - -import lombok.Data; - - -/** - * @author 六如 - */ -@Data -public class BaseResponse implements Response { - - public static final String SUCCESS_CODE = "0"; - public static final String SUCCESS_MSG = "success"; - /** - * 网关异常码,范围0~100 成功返回"0" - */ - private String code = SUCCESS_CODE; - - /** - * 网关异常信息 - */ - private String msg = ""; - - /** - * 返回对象 - */ - private Object data; - - /** - * 解决方案 - */ - private String solution; -} diff --git a/sop-gateway/src/main/java/com/gitee/sop/gateway/response/NoCommonResponse.java b/sop-gateway/src/main/java/com/gitee/sop/gateway/response/NoCommonResponse.java index 2ec1a428..5e5bb79c 100644 --- a/sop-gateway/src/main/java/com/gitee/sop/gateway/response/NoCommonResponse.java +++ b/sop-gateway/src/main/java/com/gitee/sop/gateway/response/NoCommonResponse.java @@ -5,7 +5,7 @@ package com.gitee.sop.gateway.response; * * @author 六如 */ -public class NoCommonResponse extends BaseResponse { +public class NoCommonResponse extends ApiResponse { public static NoCommonResponse success(Object data) { NoCommonResponse apiResponse = new NoCommonResponse(); diff --git a/sop-gateway/src/main/java/com/gitee/sop/gateway/service/Serde.java b/sop-gateway/src/main/java/com/gitee/sop/gateway/service/Serde.java index c7952002..d1e765fd 100644 --- a/sop-gateway/src/main/java/com/gitee/sop/gateway/service/Serde.java +++ b/sop-gateway/src/main/java/com/gitee/sop/gateway/service/Serde.java @@ -1,14 +1,25 @@ package com.gitee.sop.gateway.service; +import com.alibaba.fastjson2.JSONObject; + +import java.util.Map; + /** - * 序列化 + * 序列化/反序列化 * * @author 六如 */ public interface Serde { - String toJSONString(Object object); + String toJson(Object object); String toXml(Object object); + Map parseJson(String json); + + default JSONObject parseObject(String json) { + Map jsonObj = parseJson(json); + return jsonObj instanceof JSONObject ? (JSONObject) jsonObj : new JSONObject(jsonObj); + } + } diff --git a/sop-gateway/src/main/java/com/gitee/sop/gateway/service/impl/ParamExecutorImpl.java b/sop-gateway/src/main/java/com/gitee/sop/gateway/service/impl/ParamExecutorImpl.java index 1805a985..47705b43 100644 --- a/sop-gateway/src/main/java/com/gitee/sop/gateway/service/impl/ParamExecutorImpl.java +++ b/sop-gateway/src/main/java/com/gitee/sop/gateway/service/impl/ParamExecutorImpl.java @@ -8,6 +8,7 @@ import com.gitee.sop.gateway.request.ApiRequest; import com.gitee.sop.gateway.request.ApiRequestContext; import com.gitee.sop.gateway.request.RequestFormatEnum; import com.gitee.sop.gateway.request.UploadContext; +import com.gitee.sop.gateway.response.NoCommonResponse; import com.gitee.sop.gateway.response.Response; import com.gitee.sop.gateway.service.ParamExecutor; import com.gitee.sop.gateway.service.Serde; @@ -120,7 +121,7 @@ public class ParamExecutorImpl implements ParamExecutor params = new ArrayList<>(); for (ParamInfoDTO paramInfoDTO : paramInfoList) { String type = paramInfoDTO.getType(); @@ -141,10 +149,13 @@ public class RouteServiceImpl implements RouteService { } else { if (ClassUtil.isPrimitive(type)) { String paramName = paramInfoDTO.getName(); + Object value = null; try { - Object value = jsonObject.getObject(paramName, ClassUtils.forName(type)); + if (jsonObject != null) { + value = jsonObject.getObject(paramName, ClassUtils.forName(type)); + jsonObject.remove(paramName); + } params.add(value); - jsonObject.remove(paramName); } catch (ClassNotFoundException e) { log.error("找不到参数class, paramInfoDTO={}, apiRequest={}", paramInfoDTO, apiRequest, e); throw new RuntimeException("找不到class:" + type, e); diff --git a/sop-gateway/src/main/java/com/gitee/sop/gateway/service/impl/SerdeGsonImpl.java b/sop-gateway/src/main/java/com/gitee/sop/gateway/service/impl/SerdeGsonImpl.java index 6a1b1088..c7b0879f 100644 --- a/sop-gateway/src/main/java/com/gitee/sop/gateway/service/impl/SerdeGsonImpl.java +++ b/sop-gateway/src/main/java/com/gitee/sop/gateway/service/impl/SerdeGsonImpl.java @@ -3,7 +3,12 @@ package com.gitee.sop.gateway.service.impl; import com.alibaba.nacos.shaded.com.google.gson.Gson; import com.alibaba.nacos.shaded.com.google.gson.GsonBuilder; +import java.util.LinkedHashMap; +import java.util.Map; + /** + * 序列化/反序列化 gson实现 + * * @author 六如 */ public class SerdeGsonImpl extends SerdeImpl { @@ -11,12 +16,22 @@ public class SerdeGsonImpl extends SerdeImpl { Gson gson; @Override - public String toJSONString(Object object) { + public String toJson(Object object) { return gson.toJson(object); } @Override - protected void doInit() { - gson = new GsonBuilder().setDateFormat(dateFormat).create(); + public Map parseJson(String json) { + return gson.fromJson(json, LinkedHashMap.class); } + + + @Override + protected void doInit() { + gson = new GsonBuilder() + .setDateFormat(dateFormat) + .create(); + } + + } diff --git a/sop-gateway/src/main/java/com/gitee/sop/gateway/service/impl/SerdeImpl.java b/sop-gateway/src/main/java/com/gitee/sop/gateway/service/impl/SerdeImpl.java index 1859cf1a..376063f5 100644 --- a/sop-gateway/src/main/java/com/gitee/sop/gateway/service/impl/SerdeImpl.java +++ b/sop-gateway/src/main/java/com/gitee/sop/gateway/service/impl/SerdeImpl.java @@ -1,27 +1,35 @@ package com.gitee.sop.gateway.service.impl; import com.alibaba.fastjson2.JSON; +import com.alibaba.fastjson2.JSONObject; import com.alibaba.fastjson2.JSONWriter; import com.fasterxml.jackson.core.JsonProcessingException; +import com.gitee.sop.gateway.config.ApiConfig; import com.gitee.sop.gateway.service.Serde; import com.gitee.sop.gateway.util.XmlUtil; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import javax.annotation.PostConstruct; +import java.util.Collections; +import java.util.Map; /** * @author 六如 */ public class SerdeImpl implements Serde { - static JSONWriter.Context context; + static JSONWriter.Context WRITE_CONTEXT; + + @Autowired + protected ApiConfig apiConfig; @Value("${gateway.serialize.date-format}") protected String dateFormat; @Override - public String toJSONString(Object object) { - return JSON.toJSONString(object, context); + public String toJson(Object object) { + return JSON.toJSONString(object); } @Override @@ -33,10 +41,15 @@ public class SerdeImpl implements Serde { } } + @Override + public Map parseJson(String json) { + return JSON.parseObject(json); + } + @PostConstruct public void init() { - context = new JSONWriter.Context(); - context.setDateFormat(dateFormat); + WRITE_CONTEXT = new JSONWriter.Context(); + WRITE_CONTEXT.setDateFormat(dateFormat); this.doInit(); } diff --git a/sop-gateway/src/main/java/com/gitee/sop/gateway/service/interceptor/RouteInterceptorOrders.java b/sop-gateway/src/main/java/com/gitee/sop/gateway/service/interceptor/RouteInterceptorOrders.java deleted file mode 100644 index 1c9a3d9e..00000000 --- a/sop-gateway/src/main/java/com/gitee/sop/gateway/service/interceptor/RouteInterceptorOrders.java +++ /dev/null @@ -1,11 +0,0 @@ -package com.gitee.sop.gateway.service.interceptor; - -/** - * @author 六如 - */ -public class RouteInterceptorOrders { - - public static final int RESULT_INTERCEPTOR = -1000; - public static final int RESULT_WRAPPER_INTERCEPTOR = RESULT_INTERCEPTOR + 1; - -} diff --git a/sop-gateway/src/main/java/com/gitee/sop/gateway/service/interceptor/internal/ResultWrapperInterceptor.java b/sop-gateway/src/main/java/com/gitee/sop/gateway/service/interceptor/internal/ResultWrapperInterceptor.java deleted file mode 100644 index 215d035a..00000000 --- a/sop-gateway/src/main/java/com/gitee/sop/gateway/service/interceptor/internal/ResultWrapperInterceptor.java +++ /dev/null @@ -1,38 +0,0 @@ -package com.gitee.sop.gateway.service.interceptor.internal; - -import com.gitee.sop.gateway.common.ApiInfoDTO; -import com.gitee.sop.gateway.config.ApiConfig; -import com.gitee.sop.gateway.request.ApiRequestContext; -import com.gitee.sop.gateway.response.ApiResponse; -import com.gitee.sop.gateway.response.ApiResponseLower; -import com.gitee.sop.gateway.service.interceptor.RouteInterceptor; -import com.gitee.sop.gateway.service.interceptor.RouteInterceptorOrders; -import org.springframework.beans.factory.annotation.Autowired; - -import java.util.Objects; - - -/** - * 对结果进行包裹 - * - * @author 六如 - */ -public class ResultWrapperInterceptor implements RouteInterceptor { - - @Autowired - private ApiConfig apiConfig; - - @Override - public Object afterRoute(ApiRequestContext context, ApiInfoDTO apiInfoDTO, Object result) { - if (Objects.equals(apiConfig.getFieldLowercase(), true)) { - return ApiResponseLower.success(result); - } else { - return ApiResponse.success(result); - } - } - - @Override - public int getOrder() { - return RouteInterceptorOrders.RESULT_WRAPPER_INTERCEPTOR; - } -} diff --git a/sop-gateway/src/main/java/com/gitee/sop/gateway/util/FieldUtil.java b/sop-gateway/src/main/java/com/gitee/sop/gateway/util/FieldUtil.java new file mode 100644 index 00000000..1ea675ae --- /dev/null +++ b/sop-gateway/src/main/java/com/gitee/sop/gateway/util/FieldUtil.java @@ -0,0 +1,46 @@ +package com.gitee.sop.gateway.util; + +/** + * @author 六如 + */ +public class FieldUtil { + + private static final String REGEX = "([a-z])([A-Z])"; + private static final String REGEX_VAL = "$1_$2"; + private static final char UNDERLINE = '_'; + + + /** + * 驼峰转下划线 + * + * @return 返回下划线 + */ + public static String camelCaseToSnakeCase(String name) { + return name.replaceAll(REGEX, REGEX_VAL).toLowerCase(); + } + + /** + * 下划线转驼峰 + * + * @param param 内容 + * @return 返回转换后的字符串 + */ + public static String snakeCaseToCamelCase(String param) { + if (param == null || param.trim().isEmpty()) { + return ""; + } + int len = param.length(); + StringBuilder sb = new StringBuilder(len); + for (int i = 0; i < len; i++) { + char c = param.charAt(i); + if (c == UNDERLINE) { + if (++i < len) { + sb.append(Character.toUpperCase(param.charAt(i))); + } + } else { + sb.append(c); + } + } + return sb.toString(); + } +} diff --git a/sop-gateway/src/main/java/com/gitee/sop/gateway/util/JsonUtil.java b/sop-gateway/src/main/java/com/gitee/sop/gateway/util/JsonUtil.java index 9a41ed96..d639f5c5 100644 --- a/sop-gateway/src/main/java/com/gitee/sop/gateway/util/JsonUtil.java +++ b/sop-gateway/src/main/java/com/gitee/sop/gateway/util/JsonUtil.java @@ -34,4 +34,5 @@ public class JsonUtil { return JSON.parseArray(value, clazz); } + } diff --git a/sop-gateway/src/main/resources/application.properties b/sop-gateway/src/main/resources/application.properties index 660bcce3..a435e472 100644 --- a/sop-gateway/src/main/resources/application.properties +++ b/sop-gateway/src/main/resources/application.properties @@ -1,5 +1,5 @@ spring.profiles.active=dev -spring.application.name=sop-index +spring.application.name=sop-gateway server.port=8081 ####### gateway config ####### @@ -40,8 +40,6 @@ api.timeout-seconds=300 api.timestamp-pattern=yyyy-MM-dd HH:mm:ss # default zone api.zone-id=Asia/Shanghai -# if true, response field name all lowercase, such as : sub_code, sub_msg -api.field-lowercase=false ####### dubbo config ####### dubbo.protocol.name=dubbo diff --git a/sop-sdk/sdk-csharp/SDKCSharp/Client/OpenClient.cs b/sop-sdk/sdk-csharp/SDKCSharp/Client/OpenClient.cs index f4a7ac67..3d4ef298 100644 --- a/sop-sdk/sdk-csharp/SDKCSharp/Client/OpenClient.cs +++ b/sop-sdk/sdk-csharp/SDKCSharp/Client/OpenClient.cs @@ -177,7 +177,7 @@ namespace SDKCSharp.Client /// 返回的Response类 /// 请求对象 /// 返回Response类 - public virtual T Execute(BaseRequest request) where T : BaseResponse + public virtual Result Execute(BaseRequest request) { return this.Execute(request, null); } @@ -189,7 +189,7 @@ namespace SDKCSharp.Client /// 请求对象 /// accessToken /// 返回Response类 - public virtual T Execute(BaseRequest request, string accessToken) where T : BaseResponse + public virtual Result Execute(BaseRequest request, string accessToken) { RequestForm requestForm = request.CreateRequestForm(this.openConfig); Dictionary form = requestForm.Form; @@ -225,18 +225,12 @@ namespace SDKCSharp.Client /// 服务器响应内容 /// 请求Request /// 返回Response - protected virtual T ParseResponse(string resp, BaseRequest request) where T: BaseResponse + protected virtual Result ParseResponse(string resp, BaseRequest request) { string method = request.Method; string rootNodeName = this.dataNameBuilder.Build(method); - string errorRootNode = openConfig.ErrorResponseName; Dictionary responseData = JsonUtil.ParseToDictionary(resp); - bool errorResponse = responseData.ContainsKey(errorRootNode); - if (errorResponse) - { - rootNodeName = errorRootNode; - } - object data = responseData[rootNodeName]; + object data = responseData.GetValueOrDefault(rootNodeName, null); responseData.TryGetValue(openConfig.SignName, out object sign); if (sign != null && !string.IsNullOrEmpty(publicKeyPlatform)) { @@ -247,10 +241,8 @@ namespace SDKCSharp.Client data = JsonUtil.ToJSONString(checkSignErrorResponse); } } - string jsonData = data == null ? "{}" : data.ToString(); - T t = JsonUtil.ParseObject(jsonData); - t.Body = jsonData; - return t; + Result result = JsonUtil.ParseObject>(resp); + return result; } ///

diff --git a/sop-sdk/sdk-csharp/SDKCSharp/Common/CustomDataNameBuilder.cs b/sop-sdk/sdk-csharp/SDKCSharp/Common/CustomDataNameBuilder.cs index 8509468a..21b622d9 100644 --- a/sop-sdk/sdk-csharp/SDKCSharp/Common/CustomDataNameBuilder.cs +++ b/sop-sdk/sdk-csharp/SDKCSharp/Common/CustomDataNameBuilder.cs @@ -7,7 +7,7 @@ namespace SDKCSharp.Common public class CustomDataNameBuilder: DataNameBuilder { - private string dataName = "result"; + private string dataName = "data"; public CustomDataNameBuilder() { diff --git a/sop-sdk/sdk-csharp/SDKCSharp/Common/OpenConfig.cs b/sop-sdk/sdk-csharp/SDKCSharp/Common/OpenConfig.cs index 7eee7261..0fe32d19 100644 --- a/sop-sdk/sdk-csharp/SDKCSharp/Common/OpenConfig.cs +++ b/sop-sdk/sdk-csharp/SDKCSharp/Common/OpenConfig.cs @@ -11,7 +11,7 @@ namespace SDKCSharp.Common { public class OpenConfig { - public static DataNameBuilder DATA_NAME_BUILDER = new DefaultDataNameBuilder(); + public static DataNameBuilder DATA_NAME_BUILDER = new CustomDataNameBuilder(); /// /// 返回码成功值 diff --git a/sop-sdk/sdk-csharp/SDKCSharp/Program.cs b/sop-sdk/sdk-csharp/SDKCSharp/Program.cs index 3591af77..7c3f4592 100644 --- a/sop-sdk/sdk-csharp/SDKCSharp/Program.cs +++ b/sop-sdk/sdk-csharp/SDKCSharp/Program.cs @@ -33,9 +33,7 @@ namespace SDKTest { TestGet(); Console.WriteLine("--------------------"); - TestCommon(); - Console.WriteLine("--------------------"); - TestUpload(); + //TestUpload(); } // 标准用法 @@ -49,54 +47,20 @@ namespace SDKTest request.BizModel = model; // 发送请求 - GetStoryResponse response = client.Execute(request); + Result result = client.Execute(request); - if (response.IsSuccess()) + if (result.IsSuccess()) { // 返回结果 - Console.WriteLine("成功!response:{0}\n响应原始内容:{1}", JsonUtil.ToJSONString(response), response.Body); + Console.WriteLine("成功!response:{0}\n响应原始内容:{1}", JsonUtil.ToJSONString(result), result.Data); } else { Console.WriteLine("错误, code:{0}, msg:{1}, subCode:{2}, subMsg:{3}", - response.Code, response.Msg, response.SubCode, response.SubMsg); + result.Code, result.Msg, result.SubCode, result.SubMsg); } } - // 懒人版,如果不想添加Request,Response,Model。可以用这种方式,返回Dictionary,后续自己处理 - private static void TestCommon() - { - // 创建请求对象 - CommonRequest request = new CommonRequest("alipay.story.find"); - // 请求参数 - Dictionary bizModel = new Dictionary - { - ["name"] = "白雪公主" - }; - - request.BizModel = bizModel; - - // 发送请求 - CommonResponse response = client.Execute(request); - - if (response.IsSuccess()) - { - // 返回结果 - string body = response.Body; - Dictionary dict = JsonUtil.ParseToDictionary(body); - - Console.WriteLine("Dictionary内容:"); - foreach (var item in dict) - { - Console.WriteLine("{0}:{1}", item.Key, item.Value); - } - } - else - { - Console.WriteLine("错误, code:{0}, msg:{1}, subCode:{2}, subMsg:{3}", - response.Code, response.Msg, response.SubCode, response.SubMsg); - } - } private static void TestUpload() { @@ -119,10 +83,10 @@ namespace SDKTest request.AddFile(new UploadFile("file1", root + "/file1.txt")); request.AddFile(new UploadFile("file2", root + "/file2.txt")); - DemoFileUploadResponse response = client.Execute(request); - if (response.IsSuccess()) + Result result = client.Execute(request); + if (result.IsSuccess()) { - List responseFiles = response.Files; + List responseFiles = result.Data.Files; Console.WriteLine("您上传的文件信息:"); responseFiles.ForEach(file => { @@ -133,7 +97,7 @@ namespace SDKTest else { Console.WriteLine("错误, code:{0}, msg:{1}, subCode:{2}, subMsg:{3}", - response.Code, response.Msg, response.SubCode, response.SubMsg); + result.Code, result.Msg, result.SubCode, result.SubMsg); } } } diff --git a/sop-sdk/sdk-csharp/SDKCSharp/Response/GetStoryResponse.cs b/sop-sdk/sdk-csharp/SDKCSharp/Response/GetStoryResponse.cs index d97a0d50..8150e06a 100644 --- a/sop-sdk/sdk-csharp/SDKCSharp/Response/GetStoryResponse.cs +++ b/sop-sdk/sdk-csharp/SDKCSharp/Response/GetStoryResponse.cs @@ -3,7 +3,7 @@ using Newtonsoft.Json; namespace SDKCSharp.Response { - public class GetStoryResponse: BaseResponse + public class GetStoryResponse { [JsonProperty("id")] public int Id { get; set; } diff --git a/sop-sdk/sdk-csharp/SDKCSharp/SDKCSharp.csproj b/sop-sdk/sdk-csharp/SDKCSharp/SDKCSharp.csproj index 6e99da70..967612fe 100644 --- a/sop-sdk/sdk-csharp/SDKCSharp/SDKCSharp.csproj +++ b/sop-sdk/sdk-csharp/SDKCSharp/SDKCSharp.csproj @@ -2,7 +2,7 @@ Exe - netcoreapp2.0 + netcoreapp6.0 diff --git a/sop-test/src/test/java/com/gitee/sop/test/AlipayClientPostTest.java b/sop-test/src/test/java/com/gitee/sop/test/AlipayClientPostTest.java index fe98fc52..d8b3d67c 100644 --- a/sop-test/src/test/java/com/gitee/sop/test/AlipayClientPostTest.java +++ b/sop-test/src/test/java/com/gitee/sop/test/AlipayClientPostTest.java @@ -48,10 +48,10 @@ public class AlipayClientPostTest extends TestBase { // 业务参数 Map bizContent = new HashMap<>(); - bizContent.put("out_trade_no", "70501111111S001111119"); - bizContent.put("total_amount", "9.00"); + bizContent.put("outTradeNo", "70501111111S001111119"); + bizContent.put("totalAmount", "9.00"); bizContent.put("subject", "衣服"); - bizContent.put("product_code", "QUICK_WAP_WAY"); + bizContent.put("productCode", "QUICK_WAP_WAY"); params.put("biz_content", JSON.toJSONString(bizContent)); String content = AlipaySignature.getSignContent(params); 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 5e708154..94c2a86f 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 @@ -146,7 +146,7 @@ public class AllInOneTest extends TestBase { System.out.println(responseData); String node = requestInfo.getDataNode(); JSONObject jsonObject = JSON.parseObject(responseData).getJSONObject(node); - Assert.assertEquals("isv.route-no-permissions", jsonObject.getString("subCode")); + Assert.assertEquals("isv.route-no-permissions", jsonObject.getString("sub_code")); }); //client.execute(requestBuilder); @@ -231,7 +231,7 @@ public class AllInOneTest extends TestBase { .callback((requestInfo, responseData) -> { System.out.println(responseData); JSONObject jsonObject = JSON.parseObject(responseData); - Assert.assertEquals(jsonObject.getString("subCode"), "isv.invalid-file-size"); + Assert.assertEquals(jsonObject.getString("sub_code"), "isv.invalid-file-size"); }); client.execute(requestBuilder); @@ -252,7 +252,7 @@ public class AllInOneTest extends TestBase { .callback((requestInfo, responseData) -> { System.out.println(responseData); JSONObject jsonObject = JSON.parseObject(responseData); - Assert.assertEquals(jsonObject.getString("subCode"), "isv.invalid-file-size"); + Assert.assertEquals(jsonObject.getString("sub_code"), "isv.invalid-file-size"); }); for (int i = 0; i < 20; i++) { @@ -330,7 +330,7 @@ public class AllInOneTest extends TestBase { .callback((requestInfo, responseData) -> { System.out.println(responseData); JSONObject jsonObject = JSON.parseObject(responseData); - Assert.assertEquals("Nonexistent method name", jsonObject.getString("subMsg")); + Assert.assertEquals("Nonexistent method name", jsonObject.getString("sub_msg")); }); client.execute(requestBuilder); diff --git a/sop-website/sop-website-frontend/public/favicon.ico b/sop-website/sop-website-frontend/public/favicon.ico index bef93d4b..91e1b1d9 100644 Binary files a/sop-website/sop-website-frontend/public/favicon.ico and b/sop-website/sop-website-frontend/public/favicon.ico differ diff --git a/sop-website/sop-website-frontend/public/logo.png b/sop-website/sop-website-frontend/public/logo.png new file mode 100644 index 00000000..3b1be259 Binary files /dev/null and b/sop-website/sop-website-frontend/public/logo.png differ diff --git a/sop-website/sop-website-frontend/public/logo.svg b/sop-website/sop-website-frontend/public/logo.svg deleted file mode 100644 index a63d2b1a..00000000 --- a/sop-website/sop-website-frontend/public/logo.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/sop-website/sop-website-frontend/src/layout/hooks/useNav.ts b/sop-website/sop-website-frontend/src/layout/hooks/useNav.ts index 3d1341aa..0628249b 100644 --- a/sop-website/sop-website-frontend/src/layout/hooks/useNav.ts +++ b/sop-website/sop-website-frontend/src/layout/hooks/useNav.ts @@ -140,7 +140,7 @@ export function useNav() { /** 获取`logo` */ function getLogo() { - return new URL("/logo.svg", import.meta.url).href; + return new URL("/logo.png", import.meta.url).href; } return {