diff --git a/dockerfile b/Dockerfile similarity index 85% rename from dockerfile rename to Dockerfile index 3d585900..a3339e2b 100644 --- a/dockerfile +++ b/Dockerfile @@ -5,7 +5,7 @@ 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/target/*.jar sop/sop-website/sop-website.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 diff --git a/README.md b/README.md index 1ee3b8dd..cab9272a 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,6 @@ 一个开放平台解决方案项目,基于Spring Cloud实现,目标让用户快速搭建自己的开放平台。 -SOP提供了两种接口调用方式,分别是:[支付宝开放平台](https://docs.open.alipay.com/api)的调用方式和[淘宝开放平台](http://open.taobao.com/api.htm?docId=285&docType=2)的调用方式。 通过简单的配置后,你的项目就具备了和支付宝开放平台的一样的接口提供能力。 SOP封装了开放平台大部分功能包括:签名验证、统一异常处理、统一返回内容 、业务参数验证(JSR-303)、秘钥管理等,未来还会实现更多功能。 @@ -78,6 +77,7 @@ System.out.println(responseData); > 如上图所示,整个系统运行后,开发者只需关注微服务中的业务代码,接口变更后重新部署微服务应用即可 + ## 已完成列表 - 签名验证 @@ -86,20 +86,19 @@ System.out.println(responseData); - session管理 - 秘钥管理 - 微服务端自动验证(JSR-303) -- 支持Spring Cloud Gateway - Admin管理平台,统一管理微服务配置,管理路由管理,微服务上下线 +- 门户网站,提供用户注册账号 - 接入方管理+秘钥管理 - 接口权限分配 - 文件上传/下载 -- 提供基础SDK(含:Java,C#,Python,Go) +- 提供基础SDK(含:Java,C++,C#,Python,Go,Rust,Nodejs) - 接口限流 - 文档整合 - 应用授权 - 监控日志 -- 支持nacos +- 注册中心支持nacos/eureka - 网关动态修改参数 - 预发布/灰度环境切换 -- 支持eureka注册中心 ## 界面预览 @@ -111,9 +110,11 @@ System.out.println(responseData); ![秘钥信息](https://images.gitee.com/uploads/images/2019/0711/174921_bd817533_332975.png "秘钥信息") -![API文档](https://images.gitee.com/uploads/images/2019/0711/174939_97886883_332975.png "API文档") +- 门户网站 + +![首页2](https://images.gitee.com/uploads/images/2021/0318/195935_1d610da8_332975.png "portal-vue.png") -![沙箱环境](https://images.gitee.com/uploads/images/2019/0711/175226_3f69346a_332975.png "沙箱环境") +![文档页](https://images.gitee.com/uploads/images/2020/1107/104342_d44849a9_332975.png "portal1.png") ## 工程说明 @@ -124,16 +125,19 @@ System.out.println(responseData); - sop-gateway:网关,统一访问入口,含`Spring Cloud Zuul`和`Spring Cloud Gateway`实现 - sop-example:微服务示例,含springboot,springmvc示例 - sop-website:开放平台对应网站,提供文档API、沙箱测试等内容 -- sop-auth:应用授权服务 +- sop-auth:应用授权服务示例 - sop-admin:后台管理 - sop-sdk:基础sdk,含Java、C#版本 - sop-test:接口调用测试用例 ## 分支说明 -- master:发版分支(当前为4.0版本) +- master:发版分支 - develop:日常开发分支 - eureka:使用eureka注册中心 +- pr:接受PR的分支,提交PR请提交到此分支 + +[更新说明](./changelog.md) ## 相关文档 @@ -141,6 +145,6 @@ System.out.println(responseData); ## 沟通交流 -Q群:167643071 +- QQ群 -![SOP2群](https://images.gitee.com/uploads/images/2020/0203/103119_bdf84eb6_332975.png "SOP2群.png") +1群:167643071(满)、2群:167643071(满)、3群:565894770 diff --git a/changelog.md b/changelog.md index e29b698c..2efc0a3d 100644 --- a/changelog.md +++ b/changelog.md @@ -1,5 +1,65 @@ # changelog +## 4.3.2 + +- 修复微服务方法获取不到OpenContext问题 + +## 4.3.1 + +- 修复serviceId有大小写出现404问题 +- 修复路有监控错误面板分页问题 + +## 4.3.0 + +- 升级`spring-boot/spring-cloud/spring-cloud-alibaba`版本 +- 修复`DataBufferLimitException: Exceeded limit on max bytes to buffer : 262144`问题 +- 修复压测出现`ClosedChannelException`,`Connection has been closed BEFORE response`问题 + +## 4.2.7 + +- 修复两个微服务相同path问题 +- 修复Python-SDK下参数传递问题 + +## 4.2.6 + +- 优化网关超时处理 + +## 4.2.5 + +- 修复restful负载均衡问题 + +## 4.2.4 + +- 修复sop-auth模块启动失败问题 + +## 4.2.3 + +- 修复nacos的group不生效问题 + +## 4.2.2 + +- 优化版本号 + +## 4.2.1 + +- 可指定nacos的group,(`spring.cloud.nacos.discovery.group=xx`) +- 修复spring循环依赖问题 + +## 4.2.0 + +需要执行`sop-upgrade-4.2.0.sql` + +- 新增ISV用户平台 +- 新增门户网站(portal) +- 新增`C++`,`Rust`语言SDK + +## 4.1.0 + +需要执行`sop-upgrade-4.1.0.sql` + +- 重构路由监控功能 +- 升级SpringBoot,SpringCloud,SpringCloudAlibaba版本 + ## 4.0.3 - 可定义业务错误码(见`@Open`注解中的`bizCode`属性) diff --git a/doc/docs/README.md b/doc/docs/README.md index af857fe0..aa2509da 100644 --- a/doc/docs/README.md +++ b/doc/docs/README.md @@ -2,6 +2,6 @@ Git地址:[SOP](https://gitee.com/durcframework/SOP) -Q群:167643071 +- QQ群 -![SOP2群](https://images.gitee.com/uploads/images/2020/0203/103119_bdf84eb6_332975.png "SOP2群.png") +1群:167643071(满)、2群:167643071(满)、3群:565894770 \ No newline at end of file diff --git a/doc/docs/_sidebar.md b/doc/docs/_sidebar.md index 88780f43..4ab6e4e6 100644 --- a/doc/docs/_sidebar.md +++ b/doc/docs/_sidebar.md @@ -1,34 +1,36 @@ -* [首页](/?t=1595931646391) +* [首页](/?t=1616211903021) * 开发文档 - * [快速体验](files/10010_快速体验.md?t=1595931646394) - * [项目接入到SOP](files/10011_项目接入到SOP.md?t=1595931646412) - * [新增接口](files/10020_新增接口.md?t=1595931646412) - * [开发流程](files/10021_开发流程.md?t=1595931646412) - * [业务参数校验](files/10030_业务参数校验.md?t=1595931646412) - * [错误处理](files/10040_错误处理.md?t=1595931646412) - * [编写文档](files/10041_编写文档.md?t=1595931646412) - * [接口交互详解](files/10050_接口交互详解.md?t=1595931646412) - * [使用签名校验工具](files/10080_使用签名校验工具.md?t=1595931646413) - * [ISV管理](files/10085_ISV管理.md?t=1595931646413) - * [自定义返回结果](files/10087_自定义返回结果.md?t=1595931646413) - * [自定义过滤器](files/10088_自定义过滤器.md?t=1595931646413) - * [自定义校验token](files/10089_自定义校验token.md?t=1595931646413) - * [网关拦截器](files/10090_网关拦截器.md?t=1595931646413) - * [路由授权](files/10090_路由授权.md?t=1595931646413) - * [接口限流](files/10092_接口限流.md?t=1595931646413) - * [路由监控](files/10093_路由监控.md?t=1595931646413) - * [SDK开发](files/10095_SDK开发.md?t=1595931646413) - * [应用授权](files/10097_应用授权.md?t=1595931646414) - * [提供restful接口](files/10100_提供restful接口.md?t=1595931646414) - * [文件上传](files/10104_文件上传.md?t=1595931646414) - * [配置Sleuth链路追踪](files/10109_配置Sleuth链路追踪.md?t=1595931646414) - * [预发布灰度发布](files/10110_预发布灰度发布.md?t=1595931646414) - * [动态修改请求参数](files/10111_动态修改请求参数.md?t=1595931646414) - * [使用eureka](files/10112_使用eureka.md?t=1595931646414) + * [快速体验](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=1595931646414) - * [原理分析之如何存储路由](files/90011_原理分析之如何存储路由.md?t=1595931646415) - * [原理分析之如何路由](files/90012_原理分析之如何路由.md?t=1595931646415) - * [原理分析之文档归纳](files/90013_原理分析之文档归纳.md?t=1595931646415) - * [原理分析之预发布灰度发布](files/90014_原理分析之预发布灰度发布.md?t=1595931646415) - * [常见问题](files/90100_常见问题.md?t=1595931646415) + * [网关性能测试](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 index a28b020e..edcce36c 100644 --- a/doc/docs/files/10010_快速体验.md +++ b/doc/docs/files/10010_快速体验.md @@ -38,11 +38,15 @@ 登录账号:admin/123456 -## 启动文档中心 +## 启动门户网站 + +见:`sop-website/sop-portal/README.md` + +## 启动文档中心/ISV后台 文档中心代码在sop-website工程中 - 确保注册中心、网关、微服务正常启动 -- 修改sop-website下的application-dev.properties相关配置 +- 修改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 index fb6acfbd..e1138abc 100644 --- a/doc/docs/files/10011_项目接入到SOP.md +++ b/doc/docs/files/10011_项目接入到SOP.md @@ -5,15 +5,13 @@ - pom.xml添加版本配置 ```xml - - - 2.2.5.RELEASE - - Hoxton.SR3 - - - 2.2.1.RELEASE - + +2.3.2.RELEASE + +Hoxton.SR8 + + +2.2.5.RELEASE ``` - pom.xml添加``控制版本 @@ -95,6 +93,8 @@ public class OpenServiceConfig extends AlipayServiceConfiguration { php应用提供的接口需要返回如下json内容: +假设请求的接口为:`http://open.xxx.com/get_routes` + ```json { "serviceId": "goods-service", @@ -159,7 +159,7 @@ instance.setServiceName("goods-service"); instance.setIp("192.168.0.11"); instance.setPort(8080); // 在nacos的metadata中指定接口路径 -instance.getMetadata().put("sop.routes.path", "/goods/list_goods"); +instance.getMetadata().put("sop.routes.path", "http://open.xxx.com/get_routes"); namingService.registerInstance(serviceId, instance); ``` diff --git a/doc/docs/files/10020_新增接口.md b/doc/docs/files/10020_新增接口.md index 692608f5..bd31cc62 100644 --- a/doc/docs/files/10020_新增接口.md +++ b/doc/docs/files/10020_新增接口.md @@ -44,6 +44,8 @@ public StoryResult getStory() { 其中biz_content部分是我们想要的,在方法上申明一个对象,对应biz_content中的内容即可完成参数绑定,并且对参数进行JSR-303校验。 +**注意:接口方法必须有且只有一个对象参数,如果申明多个会出现参数绑定失败** + ```java @Open("goods.add") @RequestMapping("/goods/add") diff --git a/doc/docs/files/10021_开发流程.md b/doc/docs/files/10021_开发流程.md index d4cad179..439f8e34 100644 --- a/doc/docs/files/10021_开发流程.md +++ b/doc/docs/files/10021_开发流程.md @@ -9,12 +9,12 @@ - 你的项目接入到SOP,参考[项目接入到SOP](10011_项目接入到SOP.md),在微服务端开发接口,编写swagger注解文档 - 接口开发完成,启动微服务,注册到注册中心。 -- 【可选】编写sdk +- 【可选】编写sdk,在`SDK管理`页发布SDK,提供下载地址 ## ISV对接流程 假设接口开发完毕,开始对接ISV,大致步骤如下: -1. 在admin新建一个ISV,然后`导出秘钥`,文件给到对方 -2. 告知对方接口文档,开始接口调用。 -3. 如果有SDK,把SDK给到对方,让其直接调用 \ No newline at end of file +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 new file mode 100644 index 00000000..c94f2fc2 --- /dev/null +++ b/doc/docs/files/10022_用户注册.md @@ -0,0 +1,11 @@ +# 用户(ISV)注册 + +新增ISV有两种方式 + +- 方式1 + +启动sop-admin,在admin后台`ISV管理添加` + +- 方式2 + +启动`sop-website-server`,用户访问自主注册 diff --git a/doc/docs/files/10087_自定义返回结果.md b/doc/docs/files/10087_自定义返回结果.md index 123cf503..e5609bea 100644 --- a/doc/docs/files/10087_自定义返回结果.md +++ b/doc/docs/files/10087_自定义返回结果.md @@ -44,7 +44,7 @@ return method.replace('.', '_') + "_response"; ```java @Configuration -public class ZuulConfig extends AlipayZuulConfiguration { +public class MyConfig { static { ... @@ -84,23 +84,18 @@ public class ZuulConfig extends AlipayZuulConfiguration { 如果想要对微服务结果做更深一步处理,步骤如下: -1. 新增一个类,继承`ZuulResultExecutor.java`,并重写`public String merge(T exchange, JSONObject responseData)`方法 - -方法merge参数说明如下: - - exchange:RequestContext对象 - responseData:微服务端返回的结果 - -方法返回最终结果 +1. 新增一个类,继承`GatewayResultExecutor.java`,并重写`String mergeResult(T request, String serviceResult)`方法 2. 配置自定义类 ```java -public class SopGatewayApplication { - - public static void main(String[] args) { - ApiConfig.getInstance().setZuulResultExecutor(new MyzuulResultExecutor()); - SpringApplication.run(SopGatewayApplication.class, args); +@Configuration +public class MyConfig { + + static { + ... + ApiConfig.getInstance().setGatewayResultExecutor(new MyGatewayResultExecutor()); + ... } } @@ -109,17 +104,7 @@ public class SopGatewayApplication { ## 不合并结果 -如果不希望对结果进行合并,可设置`ApiConfig.getInstance().setMergeResult(false);` - -```java -public class SopGatewayApplication { - - public static void main(String[] args) { - ApiConfig.getInstance().setMergeResult(false); - SpringApplication.run(SopGatewayApplication.class, args); - } +如果不希望对结果进行合并,可在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 index b95b2d44..853dc57b 100644 --- a/doc/docs/files/10088_自定义过滤器.md +++ b/doc/docs/files/10088_自定义过滤器.md @@ -1,103 +1,39 @@ # 自定义过滤器 -## zuul过滤器 - -zuul过滤器列表如下: - -| 类型 | 顺序 | 过滤器 | 功能 | -| ----- | ---- | ----------------------- | ---------------------------- | -| pre | -1000 | PreValidateFilter (SOP自带) | 校验签名 | -| pre | -998 | PreLimitFilter (SOP自带) | 限流拦截器 | -| pre | -3 | ServletDetectionFilter | 标记处理 Servlet 的类型 | -| pre | -2 | Servlet30WrapperFilter | 包装 HttpServletRequest 请求 | -| pre | -1 | FormBodyWrapperFilter | 包装请求体 Servlet30WrapperFilter | -| pre | 1 | DebugFilter | 标记调试标志 | -| pre | 5 | PreDecorationFilter | 决定路由转发过滤器 | -| route | 10 | RibbonRoutingFilter | serviceId 请求转发 | -| route | 100 | SimpleHostRoutingFilter | url 请求转发 | -| route | 500 | SendForwardFilter | forward 请求转发 | -| post | 0 | SendErrorFilter | 处理有错误的请求响应 | -| post | 1000 | SendResponseFilter | 处理正常的请求响应 | - -顺序值小的优先执行,`-3`之前是sop自带的过滤器,`-3`开始是zuul自带的过滤器。 - -创建自定义过滤器可以从`-500`开始(-1000 ~ -501留给SOP)。下面是一个自定义过虑器的例子: - -```java -public class PreXXXFilter extends BaseZuulFilter { - @Override - protected FilterType getFilterType() { - return FilterType.PRE; - } - - @Override - protected int getFilterOrder() { - return -500; - } - - @Override - protected Object doRun(RequestContext requestContext) throws ZuulException { - HttpServletRequest request = requestContext.getRequest(); - ApiParam apiParam = ZuulContext.getApiParam(); - String appKey = apiParam.fetchAppKey(); - // ...业务处理 - - // 固定返回null - return null; - } -} -``` - -过滤器编写完毕后,在Config中使用: - -```java -@Configuration -public class ZuulConfig extends AlipayZuulConfiguration { - - ... - - @Bean - PreXXXFilter preXXXFilter() { - return new PreXXXFilter(); - } - ... - -} -``` - -## spring cloud gateway - -跟zuul同理 +演示在网关追加一个header ```java -public class XXXFilter implements GlobalFilter, Ordered { +public class CustomFilter implements GlobalFilter, Ordered { @Override public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { - ApiParam apiParam = (ApiParam)exchange.getAttribute(SopConstants.CACHE_API_PARAM); - String appKey = apiParam.fetchAppKey(); - // ...业务处理 - ... - return chain.filter(exchange); + // 演示在网关追加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() { - return -500; + // 自定义过滤器可以从0开始 + return 0; } } ``` -使用过滤器: +使用过滤器,在sop-gateway中找到MyConfig,添加: ```java @Configuration -public class GatewayConfig extends AlipayGatewayConfiguration { +public class MyConfig { ... @Bean - XXXFilter xxxFilter() { - return new XXXFilter(); + CustomFilter customFilter() { + return new CustomFilter(); } ... } diff --git a/doc/docs/files/10090_路由授权.md b/doc/docs/files/10091_路由授权.md similarity index 86% rename from doc/docs/files/10090_路由授权.md rename to doc/docs/files/10091_路由授权.md index 30e88502..9823f89c 100644 --- a/doc/docs/files/10090_路由授权.md +++ b/doc/docs/files/10091_路由授权.md @@ -12,9 +12,9 @@ 默认情况下,接口访问时公开的,ISV都能访问。如果要设置某个接口访问权限,在`@Open`注解中指定permission=true。 如:`@Open(value = "permission.story.get", permission = true)`。这样该接口是需要经过授权给ISV才能访问的。 -重启服务后,登录admin,服务管理-路由列表界面中,操作操作列会出现一个授权按钮,点击出现授权窗口,勾选对应的角色即可完成授权。 +重启服务后,登录admin,服务管理-路由列表界面中,`访问权限`列会出现一个点击授权,点击出现授权窗口,勾选对应的角色即可完成授权。 -- 点击`授权`按钮,进行角色授权 +- `点击授权`,进行角色授权 ![admin预览](images/10090_1.png "10090_1.png") diff --git a/doc/docs/files/10093_路由监控.md b/doc/docs/files/10093_路由监控.md index b9ad73d2..b0e56a87 100644 --- a/doc/docs/files/10093_路由监控.md +++ b/doc/docs/files/10093_路由监控.md @@ -1,15 +1,6 @@ # 路由监控 -路由监控功能可以查看各个接口的调用情况,监控信息收集采用拦截器实现。 - -- 统计各个接口的调用次数、耗时等信息 -- 错误日志统一在网关负责收集 -- 只收集未知类型的错误日志,开发人员主动throw的异常不收集 -- 收集的日志存放在内存中,重启网关日志会消失 - -## 永久保存日志 - -默认收集的日志存放在内存中,重启网关日志会消失(见:`com.gitee.sop.gatewaycommon.monitor.MonitorManager.java`)。如果要永久保存日志内容,需要自己修改`MonitorManager` +路由监控功能可以查看各个接口的调用情况,监控信息收集采用拦截器实现,前往【服务管理】-【路由监控】查看 - 后台预览 @@ -17,6 +8,20 @@ ![监控日志](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.gatewaycommon.interceptor.MonitorRouteInterceptor +- com.gitee.sop.gateway.interceptor.MonitorRouteInterceptor diff --git a/doc/docs/files/10095_SDK开发.md b/doc/docs/files/10095_SDK开发.md index 7ba038b5..0b970c3c 100644 --- a/doc/docs/files/10095_SDK开发.md +++ b/doc/docs/files/10095_SDK开发.md @@ -332,4 +332,12 @@ private static void TestCommon() response.Code, response.Msg, response.SubCode, response.SubMsg); } } -``` \ No newline at end of file +``` + +## 发布SDK + +将编写好的SDK打包后上传到云服务器,如百度网盘 + +前往sop-admin,点击`SDK管理`菜单,点击`发布SDK`,填写SDK语言、版本、下载地址(网盘地址)、调用示例 + +保存后,ISV端会看到发布的SDK \ No newline at end of file diff --git a/doc/docs/files/10110_预发布灰度发布.md b/doc/docs/files/10110_预发布灰度发布.md index 6b469196..4d5a0cb3 100644 --- a/doc/docs/files/10110_预发布灰度发布.md +++ b/doc/docs/files/10110_预发布灰度发布.md @@ -40,6 +40,14 @@ pre.domain=openpre.domain.com ## 使用灰度发布 +- 灰度接口定义 + +接口名相同,版本号不同。比如接口`user.get`,version:1.0 + +新建一个接口`user.get`,version:2.0 + +那么这个2.0接口就是灰度接口 + 灰度发布可允许指定的用户访问灰度服务器,其它用户还是走正常流程。 登录SOP-Admin,前往`服务列表`。 diff --git a/doc/docs/files/10113_超时设置.md b/doc/docs/files/10113_超时设置.md new file mode 100644 index 00000000..89a1081e --- /dev/null +++ b/doc/docs/files/10113_超时设置.md @@ -0,0 +1,14 @@ +# 超时设置 + +当微服务处理业务逻辑时间过长,网关会报超时错误,默认等待时间是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 index b684c20f..334299ec 100644 --- a/doc/docs/files/90001_网关性能测试.md +++ b/doc/docs/files/90001_网关性能测试.md @@ -133,8 +133,8 @@ Transfer/sec: 226.50KB `restart.sh` ```bash -echo "Stopping sop-gateway-4.0.3-SNAPSHOT.jar" -pid=`ps -ef | grep sop-gateway-4.0.3-SNAPSHOT.jar | grep -v grep | awk '{print $2}'` +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 @@ -142,7 +142,7 @@ then 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.0.3-SNAPSHOT.jar\ + -XX:+UseConcMarkSweepGC sop-gateway-4.2.4-SNAPSHOT.jar\ --spring.profiles.active=dev --server.port=8081 & tail -f nohup.out diff --git a/doc/docs/files/90012_原理分析之如何路由.md b/doc/docs/files/90012_原理分析之如何路由.md index cc14416e..da7d95be 100644 --- a/doc/docs/files/90012_原理分析之如何路由.md +++ b/doc/docs/files/90012_原理分析之如何路由.md @@ -1,77 +1,33 @@ # 原理分析之如何路由 -## zuul如何路由 +Spring Cloud Gateway通过一系列的Filter来进行数据的传输,如下图所示: -SOP网关默认使用zuul,当然也默认使用了zuul提供的路由功能。zuul默认使用过滤器来实现路由转发, -我们看下zuul中自带的过滤器: +![流程图](images/90012_1.png) -| 类型 | 顺序 | 过滤器 | 功能 | -| ----- | ---- | ----------------------- | ---------------------------- | -| pre | -3 | ServletDetectionFilter | 标记处理 Servlet 的类型 | -| pre | -2 | Servlet30WrapperFilter | 包装 HttpServletRequest 请求 | -| pre | -1 | FormBodyWrapperFilter | 包装请求体 | -| pre | 1 | DebugFilter | 标记调试标志 | -| pre | 5 | PreDecorationFilter | 决定路由转发过滤器 | -| route | 10 | RibbonRoutingFilter | serviceId 请求转发 | -| route | 100 | SimpleHostRoutingFilter | url 请求转发 | -| route | 500 | SendForwardFilter | forward 请求转发 | -| post | 0 | SendErrorFilter | 处理有错误的请求响应 | -| post | 1000 | SendResponseFilter | 处理正常的请求响应 | +SOP网关在此基础上新增了几个Filter用来处理自己的逻辑,如:前置校验、结果返回。 -上图就是zuul提供的默认过滤器,可在org.springframework.cloud.netflix.zuul.filters下查看。 +| 过滤器 | 类型 | Order | 功能 | +| ----- | ---- | ----------------------- | ---------------------------- | +|IndexFilter| `自定义` | -2147483648 | 入口过滤器,获取参数、签名校验 | +|ParameterFormatterFilter | `自定义` | -2147482647 | 格式化参数 | +|LimitFilter|`自定义`|-2147482447|限流| +|ForwardPathFilter|系统自带|0 |设置转发的path| +|RouteToRequestUrlFilter|系统自带|10000|设置转发host| +|SopLoadBalancerClientFilter|`自定义`|10100|LoadBalance获取转发实例| +|NettyRoutingFilter|系统自带|2147483647|获取httpclient发送请求| +|ForwardRoutingFilter|系统自带|2147483647|请求分发| +|GatewayModifyResponseGatewayFilter|`自定义`|-2|处理响应结果| -zuul的过滤器顺序值小的优先执行,其中的`PreDecorationFilter`是我们重点关注的类,由它来决定路由转发去向。 +一个完整的请求会自上而下经过这些Filter,下面讲解如何动态设置路由。 -打开PreDecorationFilter类,看到类注释有一句话:`that determines where and how to route based on the supplied` +## 动态设置路由 -翻译过来就是说,决定从哪里获取路由,然后怎样去路由。 +网关启动后会从注册中心拉取微服务实例,然后请求微服务提供的一个接口(`/sop/routes`),获取开放接口信息(被`@Open`注解的接口)。 -PreDecorationFilter类的核心方法是run()方法。找到run方法中这一句代码: +监听处理类在:`com.gitee.sop.bridge.route.NacosRegistryListener` -`Route route = this.routeLocator.getMatchingRoute(requestURI);` +获取到路由信息后,将路由信息缓存到本地,并保存到数据库,代码在:`com.gitee.sop.gatewaycommon.gateway.route.GatewayRouteCache.load` -这句代码很重要,表示路由从哪里获取,如果我们能够重写getMatchingRoute方法那就可以返回自己定义的路由了。 +然后动态设置Gateway路由,代码在:`com.gitee.sop.gatewaycommon.gateway.route.GatewayRouteRepository.refresh` -接下来找到RouteLocator类的定义,发现是通过构造方法传进来的,那么我们就去找使用构造方法的类。(IDEA下右键构造方法--Find Usage) - -在org.springframework.cloud.netflix.zuul.ZuulProxyAutoConfiguration类中找到了定义 - -```java -// pre filters -@Bean -@ConditionalOnMissingBean(PreDecorationFilter.class) -public PreDecorationFilter preDecorationFilter(RouteLocator routeLocator, ProxyRequestHelper proxyRequestHelper) { - return new PreDecorationFilter(routeLocator, this.server.getServlet().getContextPath(), this.zuulProperties, - proxyRequestHelper); -} -``` - -方法默认注入了RouteLocator类,默认注入的实现是CompositeRouteLocator类(通过打断点可以查看)。 - -同时方法上用了`@ConditionalOnMissingBean`注解,表示如果其它地方没有声明,则默认使用这个。 - -因此我们可以自己声明一个PreDecorationFilter,然后注入自定义的RouteLocator就行了。 - -SOP自定义的RouteLocator为:`com.gitee.sop.gatewaycommon.zuul.route.SopRouteLocator`,可自行前往查看。 - -然后再我们的Config中定义: - -```java -/** - * 选取路由 - * @param zuulRouteRepository - * @param proxyRequestHelper - * @return - */ -@Bean -public PreDecorationFilter preDecorationFilter(ZuulRouteRepository zuulRouteRepository, ProxyRequestHelper proxyRequestHelper) { - // 自定义路由 - RouteLocator routeLocator = new SopRouteLocator(zuulRouteRepository); - return new PreDecorationFilter(routeLocator, - this.server.getServlet().getContextPath(), - this.zuulProperties, - proxyRequestHelper); -} -``` - -到此,我们只需要实现RouteLocator接口,就能使用zuul默认的路由功能,非常方便。 +当有微服务重新启动时,网关会监听到微服务实例有变更,会重复上述步骤,确保网关存有最新的路由。 diff --git a/doc/docs/files/90100_常见问题.md b/doc/docs/files/90100_常见问题.md index 0995c8a4..917a2974 100644 --- a/doc/docs/files/90100_常见问题.md +++ b/doc/docs/files/90100_常见问题.md @@ -1,5 +1,28 @@ # 常见问题 +## 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中获取请求参数 diff --git a/doc/docs/files/images/10090_1.png b/doc/docs/files/images/10090_1.png index 493950c0..db577156 100644 Binary files a/doc/docs/files/images/10090_1.png and b/doc/docs/files/images/10090_1.png differ diff --git a/doc/docs/files/images/10090_2.png b/doc/docs/files/images/10090_2.png index ff3c76cf..3ec5b9b8 100644 Binary files a/doc/docs/files/images/10090_2.png and b/doc/docs/files/images/10090_2.png differ diff --git a/doc/docs/files/images/10093_1.png b/doc/docs/files/images/10093_1.png deleted file mode 100644 index e51da6f4..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 b23cacde..00000000 Binary files a/doc/docs/files/images/10093_2.png and /dev/null differ diff --git a/doc/docs/files/images/90012_1.png b/doc/docs/files/images/90012_1.png new file mode 100644 index 00000000..1f94dca9 Binary files /dev/null and b/doc/docs/files/images/90012_1.png differ diff --git a/doc/pom.xml b/doc/pom.xml index 6aa1365a..1d034539 100644 --- a/doc/pom.xml +++ b/doc/pom.xml @@ -3,7 +3,7 @@ 4.0.0 com.gitee.sop doc - 4.0.3-SNAPSHOT + 4.3.2-SNAPSHOT diff --git a/pom.xml b/pom.xml index f17a2d60..7e1cc792 100644 --- a/pom.xml +++ b/pom.xml @@ -5,14 +5,13 @@ org.springframework.boot spring-boot-starter-parent - 2.3.4.RELEASE + 2.3.2.RELEASE - 4.0.0 com.gitee.sop sop-parent - 4.0.3-SNAPSHOT + 4.3.2-SNAPSHOT pom 一个开放平台解决方案项目,基于Spring Cloud实现,目标是能够让用户快速得搭建起自己的开放平台 @@ -36,17 +35,16 @@ 1.8 - 2.3.4.RELEASE + 2.3.2.RELEASE Hoxton.SR8 - 2.2.3.RELEASE + 2.2.5.RELEASE 1.2.3 - 4.11 @@ -58,16 +56,16 @@ 3.8.1 1.11 1.2 - - + 2.0.1.Final + 6.0.13.Final 1.9.1 - 2.3.0.RELEASE 29.0-jre 1.5.21 2.9.2 2.9.2 1.16.9 6.2 + 5.2.0 @@ -135,6 +133,12 @@ ${easyopen.version} + + com.squareup.okhttp3 + okhttp + 3.14.7 + + net.oschina.durcframework easyopen-spring-boot-starter @@ -158,12 +162,6 @@ ${logback.version} - - org.springframework.data - spring-data-redis - ${spring-data-redis.version} - - org.apache.commons @@ -202,6 +200,12 @@ ${asm.version} + + com.github.pagehelper + pagehelper + ${pagehelper.version} + + org.projectlombok lombok @@ -217,9 +221,9 @@ - spring-milestones - Spring Milestones - https://repo.spring.io/milestone + aliyun + aliyun + https://maven.aliyun.com/repository/public diff --git a/sop-admin/pom.xml b/sop-admin/pom.xml index e8c318f7..a655036d 100644 --- a/sop-admin/pom.xml +++ b/sop-admin/pom.xml @@ -6,13 +6,12 @@ com.gitee.sop sop-parent - 4.0.3-SNAPSHOT + 4.3.2-SNAPSHOT ../pom.xml 4.0.0 sop-admin - 4.0.3-SNAPSHOT pom diff --git a/sop-admin/sop-admin-server/pom.xml b/sop-admin/sop-admin-server/pom.xml index 24c97fa0..f7df6225 100644 --- a/sop-admin/sop-admin-server/pom.xml +++ b/sop-admin/sop-admin-server/pom.xml @@ -5,13 +5,13 @@ com.gitee.sop sop-parent - 4.0.3-SNAPSHOT + 4.3.2-SNAPSHOT ../../pom.xml 4.0.0 sop-admin-server - 4.0.3-SNAPSHOT + 4.3.2-SNAPSHOT jar @@ -21,11 +21,6 @@ - - com.gitee.sop - sop-bridge-nacos - 4.0.3-SNAPSHOT - @@ -64,6 +59,11 @@ commons-lang3 + + com.github.pagehelper + pagehelper + + com.alibaba.nacos @@ -76,6 +76,14 @@ spring-boot-starter-test test + + + junit + junit + 4.13 + test + + org.projectlombok lombok diff --git a/sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/api/isv/param/IsvKeysFormUpdate.java b/sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/api/isv/param/IsvKeysFormUpdate.java index 652db61e..a9d2903b 100644 --- a/sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/api/isv/param/IsvKeysFormUpdate.java +++ b/sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/api/isv/param/IsvKeysFormUpdate.java @@ -29,14 +29,6 @@ public class IsvKeysFormUpdate { @Max(value = 2, message = "秘钥格式错误") private Byte keyFormat; - /** 开发者生成的公钥, 数据库字段:public_key_isv */ - @ApiDocField(description = "开发者生成的公钥") - private String publicKeyIsv; - - /** 开发者生成的私钥(交给开发者), 数据库字段:private_key_isv */ - @ApiDocField(description = "开发者生成的私钥") - private String privateKeyIsv; - /** 平台生成的公钥(交给开发者), 数据库字段:public_key_platform */ @ApiDocField(description = "平台生成的公钥") private String publicKeyPlatform; diff --git a/sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/api/isv/result/IsvInfoVO.java b/sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/api/isv/result/IsvInfoVO.java index 00873b31..fdc0b0c2 100644 --- a/sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/api/isv/result/IsvInfoVO.java +++ b/sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/api/isv/result/IsvInfoVO.java @@ -29,6 +29,8 @@ public class IsvInfoVO { @ApiDocField(description = "备注") private String remark; + private Long userId; + /** 数据库字段:gmt_create */ @ApiDocField(description = "添加时间") private Date gmtCreate; diff --git a/sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/api/service/MonitorApi.java b/sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/api/service/MonitorApi.java index 9fac96a3..4b1e9d91 100644 --- a/sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/api/service/MonitorApi.java +++ b/sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/api/service/MonitorApi.java @@ -29,8 +29,11 @@ import java.util.stream.Collectors; /** * @author tanghc + * @deprecated use com.gitee.sop.adminserver.api.service.MonitorNewApi + * @see MonitorNewApi */ -@ApiService +@Deprecated +//@ApiService @ApiDoc("服务管理-监控") @Slf4j public class MonitorApi { diff --git a/sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/api/service/MonitorNewApi.java b/sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/api/service/MonitorNewApi.java new file mode 100644 index 00000000..b21f6afc --- /dev/null +++ b/sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/api/service/MonitorNewApi.java @@ -0,0 +1,80 @@ +package com.gitee.sop.adminserver.api.service; + +import com.gitee.easyopen.annotation.Api; +import com.gitee.easyopen.annotation.ApiService; +import com.gitee.fastmybatis.core.query.Query; +import com.gitee.fastmybatis.core.query.Sort; +import com.gitee.fastmybatis.core.support.PageEasyui; +import com.gitee.fastmybatis.core.util.MapperUtil; +import com.gitee.sop.adminserver.api.service.param.InstanceMonitorSearchParam; +import com.gitee.sop.adminserver.api.service.param.MonitorErrorMsgParam; +import com.gitee.sop.adminserver.api.service.param.MonitorInfoErrorSolveParam; +import com.gitee.sop.adminserver.api.service.param.MonitorSearchParam; +import com.gitee.sop.adminserver.bean.RouteErrorCount; +import com.gitee.sop.adminserver.entity.MonitorInfoError; +import com.gitee.sop.adminserver.entity.MonitorSummary; +import com.gitee.sop.adminserver.mapper.MonitorInfoErrorMapper; +import com.gitee.sop.adminserver.mapper.MonitorInfoMapper; +import com.github.pagehelper.PageHelper; +import com.github.pagehelper.PageInfo; +import org.springframework.beans.factory.annotation.Autowired; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +/** + * 路由监控 + * @author tanghc + */ +@ApiService +public class MonitorNewApi { + + @Autowired + private MonitorInfoMapper monitorInfoMapper; + + @Autowired + private MonitorInfoErrorMapper monitorInfoErrorMapper; + + @Api(name = "monitornew.data.page") + PageInfo listMonitor(MonitorSearchParam param) { + Query query = Query.build(param); + query.orderby("errorCount", Sort.DESC) + .orderby("avgTime", Sort.DESC); + return PageHelper.offsetPage(query.getStart(), query.getLimit()) + .doSelectPage(() -> monitorInfoMapper.listMonitorSummary(query)) + .toPageInfo(); + } + + @Api(name = "monitornew.routeid.data.get") + List listInstanceMonitor(InstanceMonitorSearchParam param) { + Query query = Query.build(param); + query.orderby("errorCount", Sort.DESC) + .orderby("avgTime", Sort.DESC); + return monitorInfoMapper.listInstanceMonitorInfo(query); + } + + private Map getRouteErrorCount() { + List routeErrorCounts = monitorInfoErrorMapper.listRouteErrorCount(); + return routeErrorCounts.stream() + .collect(Collectors.toMap(RouteErrorCount::getRouteId, RouteErrorCount::getCount)); + } + + @Api(name = "monitornew.error.page") + PageEasyui listError(MonitorErrorMsgParam param) { + Query query = param.toQuery() + .orderby("gmt_modified", Sort.DESC); + return MapperUtil.queryForEasyuiDatagrid(monitorInfoErrorMapper, query); + } + + @Api(name = "monitornew.error.solve") + void solve(MonitorInfoErrorSolveParam param) { + Query query = Query.build(param); + Map set = new HashMap<>(4); + set.put("is_deleted", 1); + set.put("count", 0); + monitorInfoErrorMapper.updateByMap(set, query); + } + +} diff --git a/sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/api/service/ResourceApi.java b/sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/api/service/ResourceApi.java new file mode 100644 index 00000000..ca605491 --- /dev/null +++ b/sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/api/service/ResourceApi.java @@ -0,0 +1,54 @@ +package com.gitee.sop.adminserver.api.service; + +import com.gitee.easyopen.annotation.Api; +import com.gitee.easyopen.annotation.ApiService; +import com.gitee.sop.adminserver.api.system.param.IspPageParam; +import com.gitee.sop.adminserver.api.system.param.IspResourceParam; +import com.gitee.sop.adminserver.common.CopyUtil; +import com.gitee.sop.adminserver.entity.IspResource; +import com.gitee.sop.adminserver.mapper.IspResourceMapper; +import org.springframework.beans.factory.annotation.Autowired; + +import javax.validation.constraints.NotNull; +import java.util.List; + +/** + * @author tanghc + */ +@ApiService +public class ResourceApi { + + public static final byte RESOURCE_TYPE_SDK = (byte) 0; + + @Autowired + private IspResourceMapper ispResourceMapper; + + + @Api(name = "isp.sdk.list") + List list(IspPageParam param) { + return ispResourceMapper.list(param.toQuery()); + } + + @Api(name = "isp.sdk.add") + void addSdk(IspResourceParam param) { + IspResource ispResource = CopyUtil.copyBean(param, IspResource::new); + ispResource.setType(RESOURCE_TYPE_SDK); + ispResourceMapper.saveIgnoreNull(ispResource); + } + + @Api(name = "isp.sdk.update") + void updateSdk(IspResourceParam param) { + IspResource resource = ispResourceMapper.getById(param.getId()); + CopyUtil.copyProperties(param, resource); + ispResourceMapper.update(resource); + } + + @Api(name = "isp.sdk.delete") + void deleteSdk(@NotNull Long id) { + IspResource resource = ispResourceMapper.getById(id); + if (resource != null) { + ispResourceMapper.deleteById(id); + } + } + +} diff --git a/sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/api/service/param/InstanceMonitorSearchParam.java b/sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/api/service/param/InstanceMonitorSearchParam.java new file mode 100644 index 00000000..a0545cde --- /dev/null +++ b/sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/api/service/param/InstanceMonitorSearchParam.java @@ -0,0 +1,15 @@ +package com.gitee.sop.adminserver.api.service.param; + +import com.gitee.fastmybatis.core.query.annotation.Condition; +import lombok.Getter; +import lombok.Setter; + +/** + * @author tanghc + */ +@Getter +@Setter +public class InstanceMonitorSearchParam { + @Condition(column = "t.route_id") + private String routeId; +} diff --git a/sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/api/service/param/MonitorErrorMsgParam.java b/sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/api/service/param/MonitorErrorMsgParam.java new file mode 100644 index 00000000..9df9d4b9 --- /dev/null +++ b/sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/api/service/param/MonitorErrorMsgParam.java @@ -0,0 +1,21 @@ +package com.gitee.sop.adminserver.api.service.param; + +import com.gitee.fastmybatis.core.query.annotation.Condition; +import com.gitee.fastmybatis.core.query.param.PageParam; +import lombok.Getter; +import lombok.Setter; + +import javax.validation.constraints.NotBlank; + +/** + * @author tanghc + */ +@Getter +@Setter +public class MonitorErrorMsgParam extends PageParam { + @NotBlank(message = "routeId不能为空") + private String routeId; + + @Condition(ignoreEmptyString = true) + private String instanceId; +} diff --git a/sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/api/service/param/MonitorInfoErrorSolveParam.java b/sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/api/service/param/MonitorInfoErrorSolveParam.java new file mode 100644 index 00000000..14a38950 --- /dev/null +++ b/sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/api/service/param/MonitorInfoErrorSolveParam.java @@ -0,0 +1,19 @@ +package com.gitee.sop.adminserver.api.service.param; + +import com.gitee.fastmybatis.core.query.annotation.Condition; +import lombok.Data; + +import javax.validation.constraints.NotBlank; + +/** + * @author tanghc + */ +@Data +public class MonitorInfoErrorSolveParam { + /** 错误id,md5(error_msg), 数据库字段:error_id */ + @NotBlank + @Condition(index = 1) + private String errorId; + + +} diff --git a/sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/api/service/param/MonitorSearchParam.java b/sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/api/service/param/MonitorSearchParam.java new file mode 100644 index 00000000..9d046140 --- /dev/null +++ b/sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/api/service/param/MonitorSearchParam.java @@ -0,0 +1,23 @@ +package com.gitee.sop.adminserver.api.service.param; + +import com.gitee.easyopen.doc.annotation.ApiDocField; +import com.gitee.fastmybatis.core.query.Operator; +import com.gitee.fastmybatis.core.query.annotation.Condition; +import com.gitee.fastmybatis.core.query.param.PageParam; +import lombok.Getter; +import lombok.Setter; + +/** + * @author tanghc + */ +@Getter +@Setter +public class MonitorSearchParam extends PageParam { + @ApiDocField(description = "服务名serviceId") + @Condition(column = "service_id", operator = Operator.like, ignoreEmptyString = true) + private String serviceId; + + @ApiDocField(description = "路由id") + @Condition(column = "route_id", operator = Operator.like, ignoreEmptyString = true) + private String routeId; +} diff --git a/sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/api/service/result/MonitorInfoErrorMsgResult.java b/sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/api/service/result/MonitorInfoErrorMsgResult.java new file mode 100644 index 00000000..7b85322b --- /dev/null +++ b/sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/api/service/result/MonitorInfoErrorMsgResult.java @@ -0,0 +1,26 @@ +package com.gitee.sop.adminserver.api.service.result; + +import lombok.Data; + +import java.util.Date; + +/** + * @author tanghc + */ +@Data +public class MonitorInfoErrorMsgResult { + + private String routeId; + + private String errorId; + + /** 错误信息, 数据库字段:error_msg */ + private String errorMsg; + + private Integer errorStatus; + + private Integer count; + + /** 数据库字段:gmt_modified */ + private Date gmtModified; +} diff --git a/sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/api/system/param/IspPageParam.java b/sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/api/system/param/IspPageParam.java new file mode 100644 index 00000000..ecba947c --- /dev/null +++ b/sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/api/system/param/IspPageParam.java @@ -0,0 +1,9 @@ +package com.gitee.sop.adminserver.api.system.param; + +import com.gitee.fastmybatis.core.query.param.PageParam; + +/** + * @author tanghc + */ +public class IspPageParam extends PageParam { +} diff --git a/sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/api/system/param/IspResourceParam.java b/sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/api/system/param/IspResourceParam.java new file mode 100644 index 00000000..36391e1a --- /dev/null +++ b/sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/api/system/param/IspResourceParam.java @@ -0,0 +1,27 @@ +package com.gitee.sop.adminserver.api.system.param; + +import lombok.Data; + + +/** + * 表名:isp_resource + * 备注:ISP资源表 + * + * @author tanghc + */ +@Data +public class IspResourceParam { + + private Long id; + + /** 资源名称, 数据库字段:name */ + private String name; + + private String version; + + /** 资源内容(URL), 数据库字段:content */ + private String content; + + private String extContent; + +} diff --git a/sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/api/system/result/IspResourceResult.java b/sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/api/system/result/IspResourceResult.java new file mode 100644 index 00000000..88c38f76 --- /dev/null +++ b/sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/api/system/result/IspResourceResult.java @@ -0,0 +1,27 @@ +package com.gitee.sop.adminserver.api.system.result; + +import lombok.Data; + + +/** + * 表名:isp_resource + * 备注:ISP资源表 + * + * @author tanghc + */ +@Data +public class IspResourceResult { + + private Long id; + + /** 资源名称, 数据库字段:name */ + private String name; + + private String version; + + /** 资源内容(URL), 数据库字段:content */ + private String content; + + private String extContent; + +} diff --git a/sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/bean/RouteErrorCount.java b/sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/bean/RouteErrorCount.java new file mode 100644 index 00000000..b425504e --- /dev/null +++ b/sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/bean/RouteErrorCount.java @@ -0,0 +1,13 @@ +package com.gitee.sop.adminserver.bean; + +import lombok.Data; + +/** + * @author tanghc + */ +@Data +public class RouteErrorCount { + + private String routeId; + private Integer count; +} diff --git a/sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/common/CopyUtil.java b/sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/common/CopyUtil.java new file mode 100644 index 00000000..76c068fb --- /dev/null +++ b/sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/common/CopyUtil.java @@ -0,0 +1,75 @@ +package com.gitee.sop.adminserver.common; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.BeanUtils; + +import java.util.Collections; +import java.util.List; +import java.util.Objects; +import java.util.function.Consumer; +import java.util.function.Supplier; +import java.util.stream.Collectors; + +/** + * @author tanghc + */ +@Slf4j +public class CopyUtil { + + public static void copyProperties(Object from, Object to) { + BeanUtils.copyProperties(from, to); + } + + public static T copyBean(Object from, Supplier supplier) { + Objects.requireNonNull(from); + T to = supplier.get(); + BeanUtils.copyProperties(from, to); + return to; + } + + public static T copyBeanNullable(Object from, Supplier supplier) { + if (from == null) { + return supplier.get(); + } + T to = supplier.get(); + BeanUtils.copyProperties(from, to); + return to; + } + + public static T copyBean(Object from, Supplier supplier, Consumer after) { + if (from == null) { + return null; + } + T to = supplier.get(); + BeanUtils.copyProperties(from, to); + after.accept(to); + return to; + } + + public static List copyList(List fromList, Supplier toElement) { + if (fromList == null) { + return Collections.emptyList(); + } + return fromList.stream() + .map(source -> { + T target = toElement.get(); + BeanUtils.copyProperties(source, target); + return target; + }) + .collect(Collectors.toList()); + } + + public static List copyList(List fromList, Supplier toElement, Consumer after) { + if (fromList == null) { + return Collections.emptyList(); + } + return fromList.stream() + .map(source -> { + T target = toElement.get(); + BeanUtils.copyProperties(source, target); + after.accept(target); + return target; + }) + .collect(Collectors.toList()); + } +} diff --git a/sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/entity/IspResource.java b/sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/entity/IspResource.java new file mode 100644 index 00000000..021f3e5d --- /dev/null +++ b/sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/entity/IspResource.java @@ -0,0 +1,50 @@ +package com.gitee.sop.adminserver.entity; + +import lombok.Data; + +import javax.persistence.Column; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.Table; +import java.util.Date; + + +/** + * 表名:isp_resource + * 备注:ISP资源表 + * + * @author tanghc + */ +@Table(name = "isp_resource") +@Data +public class IspResource { + /** 数据库字段:id */ + @Id + @Column(name = "id") + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + /** 资源名称, 数据库字段:name */ + private String name; + + private String version; + + /** 资源内容(URL), 数据库字段:content */ + private String content; + + private String extContent; + + /** 资源类型:0:SDK链接, 数据库字段:type */ + private Byte type; + + /** 数据库字段:is_deleted */ + @com.gitee.fastmybatis.core.annotation.LogicDelete + private Byte isDeleted; + + /** 数据库字段:gmt_create */ + private Date gmtCreate; + + /** 数据库字段:gmt_modified */ + private Date gmtModified; +} diff --git a/sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/entity/IsvInfo.java b/sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/entity/IsvInfo.java index 9e1b85dd..d44330a4 100644 --- a/sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/entity/IsvInfo.java +++ b/sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/entity/IsvInfo.java @@ -31,6 +31,9 @@ public class IsvInfo { /** 1启用,2禁用, 数据库字段:status */ private Byte status; + /** user_account.id */ + private Long userId; + /** 备注,数据库字段:remark */ private String remark; diff --git a/sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/entity/MonitorInfo.java b/sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/entity/MonitorInfo.java new file mode 100644 index 00000000..5b721309 --- /dev/null +++ b/sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/entity/MonitorInfo.java @@ -0,0 +1,66 @@ +package com.gitee.sop.adminserver.entity; + +import lombok.Data; + +import javax.persistence.Column; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.Table; +import java.util.Date; + + +/** + * 表名:monitor_info + * 备注:接口监控信息 + * + * @author tanghc + */ +@Table(name = "monitor_info") +@Data +public class MonitorInfo { + /** 数据库字段:id */ + @Id + @Column(name = "id") + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + /** 路由id, 数据库字段:route_id */ + private String routeId; + + /** 接口名, 数据库字段:name */ + private String name; + + /** 版本号, 数据库字段:version */ + private String version; + + /** 数据库字段:service_id */ + private String serviceId; + + /** 数据库字段:instance_id */ + private String instanceId; + + /** 请求耗时最长时间, 数据库字段:max_time */ + private Integer maxTime; + + /** 请求耗时最小时间, 数据库字段:min_time */ + private Integer minTime; + + /** 总时长,毫秒, 数据库字段:total_time */ + private Long totalTime; + + /** 总调用次数, 数据库字段:total_request_count */ + private Long totalRequestCount; + + /** 成功次数, 数据库字段:success_count */ + private Long successCount; + + /** 失败次数(业务主动抛出的异常算作成功,如参数校验,未知的错误算失败), 数据库字段:error_count */ + private Long errorCount; + + /** 数据库字段:gmt_create */ + private Date gmtCreate; + + /** 数据库字段:gmt_modified */ + private Date gmtModified; +} diff --git a/sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/entity/MonitorInfoError.java b/sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/entity/MonitorInfoError.java new file mode 100644 index 00000000..69dcba9e --- /dev/null +++ b/sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/entity/MonitorInfoError.java @@ -0,0 +1,54 @@ +package com.gitee.sop.adminserver.entity; + +import lombok.Data; + +import javax.persistence.Column; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.Table; +import java.util.Date; + + +/** + * 表名:monitor_info_error + * + * @author tanghc + */ +@Table(name = "monitor_info_error") +@Data +public class MonitorInfoError { + /** 数据库字段:id */ + @Id + @Column(name = "id") + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + /** 错误id,md5Hex(instanceId + routeId + errorMsg), 数据库字段:error_id */ + private String errorId; + + /** 实例id, 数据库字段:instance_id */ + private String instanceId; + + /** 数据库字段:route_id */ + private String routeId; + + /** 数据库字段:error_msg */ + private String errorMsg; + + /** http status,非200错误, 数据库字段:error_status */ + private Integer errorStatus; + + /** 错误次数, 数据库字段:count */ + private Integer count; + + /** 数据库字段:is_deleted */ + @com.gitee.fastmybatis.core.annotation.LogicDelete + private Byte isDeleted; + + /** 数据库字段:gmt_create */ + private Date gmtCreate; + + /** 数据库字段:gmt_modified */ + private Date gmtModified; +} diff --git a/sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/entity/MonitorSummary.java b/sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/entity/MonitorSummary.java new file mode 100644 index 00000000..3df26d02 --- /dev/null +++ b/sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/entity/MonitorSummary.java @@ -0,0 +1,61 @@ +package com.gitee.sop.adminserver.entity; + +import lombok.Data; + +import javax.persistence.Column; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.Table; +import java.util.Collections; +import java.util.Date; +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; + + +/** + * @author tanghc + */ +@Data +public class MonitorSummary { + + private static final AtomicInteger i = new AtomicInteger(); + + private Integer id = i.incrementAndGet(); + + private String routeId; + + private String name; + private String version; + + /** 数据库字段:service_id */ + private String serviceId; + + /** 数据库字段:instance_id */ + private String instanceId; + + /** 请求耗时最长时间, 数据库字段:max_time */ + private Integer maxTime; + + /** 请求耗时最小时间, 数据库字段:min_time */ + private Integer minTime; + + /** 总时长,毫秒, 数据库字段:total_time */ + private Long totalTime; + + /** 总调用次数, 数据库字段:total_request_count */ + private Long totalRequestCount; + + /** 成功次数, 数据库字段:success_count */ + private Long successCount; + + /** 失败次数(业务主动抛出的异常算作成功,如参数校验,未知的错误算失败), 数据库字段:error_count */ + private Long errorCount; + + /** 未解决的错误数量 */ + private Long unsolvedErrorCount; + + private Float avgTime; + + private Boolean hasChildren; +} diff --git a/sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/mapper/IspResourceMapper.java b/sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/mapper/IspResourceMapper.java new file mode 100644 index 00000000..22524566 --- /dev/null +++ b/sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/mapper/IspResourceMapper.java @@ -0,0 +1,11 @@ +package com.gitee.sop.adminserver.mapper; + +import com.gitee.fastmybatis.core.mapper.CrudMapper; +import com.gitee.sop.adminserver.entity.IspResource; + + +/** + * @author tanghc + */ +public interface IspResourceMapper extends CrudMapper { +} diff --git a/sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/mapper/MonitorInfoErrorMapper.java b/sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/mapper/MonitorInfoErrorMapper.java new file mode 100644 index 00000000..98a7be9d --- /dev/null +++ b/sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/mapper/MonitorInfoErrorMapper.java @@ -0,0 +1,22 @@ +package com.gitee.sop.adminserver.mapper; + +import com.gitee.fastmybatis.core.mapper.CrudMapper; +import com.gitee.sop.adminserver.bean.RouteErrorCount; +import com.gitee.sop.adminserver.entity.MonitorInfoError; +import org.apache.ibatis.annotations.Select; + +import java.util.List; + + +/** + * @author tanghc + */ +public interface MonitorInfoErrorMapper extends CrudMapper { + + + @Select("SELECT route_id routeId, count(*) `count` FROM monitor_info_error \n" + + "WHERE is_deleted=0 \n" + + "GROUP BY route_id") + List listRouteErrorCount(); + +} diff --git a/sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/mapper/MonitorInfoMapper.java b/sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/mapper/MonitorInfoMapper.java new file mode 100644 index 00000000..fc5de394 --- /dev/null +++ b/sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/mapper/MonitorInfoMapper.java @@ -0,0 +1,19 @@ +package com.gitee.sop.adminserver.mapper; + +import com.gitee.fastmybatis.core.mapper.CrudMapper; +import com.gitee.fastmybatis.core.query.Query; +import com.gitee.sop.adminserver.entity.MonitorInfo; +import com.gitee.sop.adminserver.entity.MonitorSummary; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + + +/** + * @author tanghc + */ +public interface MonitorInfoMapper extends CrudMapper { + List listMonitorSummary(@Param("query") Query query); + + List listInstanceMonitorInfo(@Param("query") Query query); +} diff --git a/sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/service/ConfigPushService.java b/sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/service/ConfigPushService.java index c2b0e943..892a98f8 100644 --- a/sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/service/ConfigPushService.java +++ b/sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/service/ConfigPushService.java @@ -37,9 +37,6 @@ public class ConfigPushService { @Autowired private ServerService serverService; - @Value("${gateway.host:}") - private String gatewayHost; - @Value("${sop.secret}") private String secret; diff --git a/sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/service/impl/RegistryServiceNacosImpl.java b/sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/service/impl/RegistryServiceNacosImpl.java index 3e7e123d..21a2afd4 100644 --- a/sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/service/impl/RegistryServiceNacosImpl.java +++ b/sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/service/impl/RegistryServiceNacosImpl.java @@ -36,6 +36,9 @@ public class RegistryServiceNacosImpl implements RegistryService { @Value("${nacos.discovery.namespace:${spring.cloud.nacos.discovery.namespace:}}") private String nacosNamespace; + @Value("${nacos.discovery.group:${spring.cloud.nacos.discovery.group:DEFAULT_GROUP}}") + private String nacosGroup; + private NamingService namingService; @PostConstruct @@ -53,13 +56,13 @@ public class RegistryServiceNacosImpl implements RegistryService { @Override public List listAllService(int pageNo, int pageSize) throws Exception { - ListView servicesOfServer = namingService.getServicesOfServer(pageNo, pageSize); + ListView servicesOfServer = namingService.getServicesOfServer(pageNo, pageSize, nacosGroup); List serverList = servicesOfServer.getData(); List serviceInfoList = new ArrayList<>(); for (String serviceName : serverList) { ServiceInfo serviceInfo = new ServiceInfo(); serviceInfo.setServiceId(serviceName); - List instanceList = namingService.getAllInstances(serviceName); + List instanceList = namingService.getAllInstances(serviceName, nacosGroup); if (CollectionUtils.isEmpty(instanceList)) { serviceInfo.setInstances(Collections.emptyList()); } else { diff --git a/sop-admin/sop-admin-server/src/main/resources/META-INF/sop-admin.properties b/sop-admin/sop-admin-server/src/main/resources/META-INF/sop-admin.properties index 50f585f0..f315ba05 100644 --- a/sop-admin/sop-admin-server/src/main/resources/META-INF/sop-admin.properties +++ b/sop-admin/sop-admin-server/src/main/resources/META-INF/sop-admin.properties @@ -30,6 +30,7 @@ spring.datasource.hikari.pool-name=HikariCP spring.datasource.hikari.max-lifetime=500000 # 固定不用改 +mybatis.config-location=classpath:mybatis/mybatisConfig.xml mybatis.mapper-locations=classpath:mybatis/mapper/*.xml easyopen.show-doc=false easyopen.ignore-validate=true diff --git a/sop-admin/sop-admin-server/src/main/resources/mybatis/mapper/MonitorInfoMapper.xml b/sop-admin/sop-admin-server/src/main/resources/mybatis/mapper/MonitorInfoMapper.xml new file mode 100644 index 00000000..2255cba6 --- /dev/null +++ b/sop-admin/sop-admin-server/src/main/resources/mybatis/mapper/MonitorInfoMapper.xml @@ -0,0 +1,63 @@ + + + + + + + + + + + diff --git a/sop-admin/sop-admin-server/src/main/resources/mybatis/mybatisConfig.xml b/sop-admin/sop-admin-server/src/main/resources/mybatis/mybatisConfig.xml new file mode 100644 index 00000000..81ac0637 --- /dev/null +++ b/sop-admin/sop-admin-server/src/main/resources/mybatis/mybatisConfig.xml @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/sop-admin/sop-admin-server/src/main/resources/public/index.html b/sop-admin/sop-admin-server/src/main/resources/public/index.html index 661b755c..ce71e6d3 100644 --- a/sop-admin/sop-admin-server/src/main/resources/public/index.html +++ b/sop-admin/sop-admin-server/src/main/resources/public/index.html @@ -1 +1 @@ -SOP Admin
\ No newline at end of file +SOP Admin
\ No newline at end of file diff --git a/sop-admin/sop-admin-server/src/main/resources/public/static/js/app.7297829f.js b/sop-admin/sop-admin-server/src/main/resources/public/static/js/app.7297829f.js new file mode 100644 index 00000000..cbfb70e1 --- /dev/null +++ b/sop-admin/sop-admin-server/src/main/resources/public/static/js/app.7297829f.js @@ -0,0 +1 @@ +(window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["app"],{0:function(e,t,n){e.exports=n("56d7")},"0a7b":function(e,t,n){},"0cb8":function(e,t,n){},"186a":function(e,t,n){"use strict";var a=n("dc52"),i=n.n(a);i.a},"18f0":function(e,t,n){"use strict";n.r(t);var a=n("e017"),i=n.n(a),o=n("21a1"),r=n.n(o),c=new i.a({id:"icon-link",use:"icon-link-usage",viewBox:"0 0 128 128",content:''});r.a.add(c);t["default"]=c},2536:function(e,t,n){},"2a3d":function(e,t,n){"use strict";n.r(t);var a=n("e017"),i=n.n(a),o=n("21a1"),r=n.n(o),c=new i.a({id:"icon-password",use:"icon-password-usage",viewBox:"0 0 128 128",content:''});r.a.add(c);t["default"]=c},"30c3":function(e,t,n){"use strict";n.r(t);var a=n("e017"),i=n.n(a),o=n("21a1"),r=n.n(o),c=new i.a({id:"icon-example",use:"icon-example-usage",viewBox:"0 0 128 128",content:''});r.a.add(c);t["default"]=c},"34c8":function(e,t,n){"use strict";var a=n("88a1"),i=n.n(a);i.a},"405a":function(e,t,n){"use strict";var a=n("4a83"),i=n.n(a);i.a},"47f1":function(e,t,n){"use strict";n.r(t);var a=n("e017"),i=n.n(a),o=n("21a1"),r=n.n(o),c=new i.a({id:"icon-table",use:"icon-table-usage",viewBox:"0 0 128 128",content:''});r.a.add(c);t["default"]=c},"4a83":function(e,t,n){},"4df5":function(e,t,n){"use strict";n.r(t);var a=n("e017"),i=n.n(a),o=n("21a1"),r=n.n(o),c=new i.a({id:"icon-eye",use:"icon-eye-usage",viewBox:"0 0 128 64",content:''});r.a.add(c);t["default"]=c},"50be":function(e,t,n){"use strict";var a=n("2536"),i=n.n(a);i.a},"51ff":function(e,t,n){var a={"./dashboard.svg":"f782","./example.svg":"30c3","./eye-open.svg":"d7ec","./eye.svg":"4df5","./form.svg":"eb1b","./link.svg":"18f0","./nested.svg":"dcf8","./password.svg":"2a3d","./table.svg":"47f1","./tree.svg":"93cd","./user.svg":"b3b5"};function i(e){var t=o(e);return n(t)}function o(e){var t=a[e];if(!(t+1)){var n=new Error("Cannot find module '"+e+"'");throw n.code="MODULE_NOT_FOUND",n}return t}i.keys=function(){return Object.keys(a)},i.resolve=o,e.exports=i,i.id="51ff"},"56d7":function(e,t,n){"use strict";n.r(t);n("cadf"),n("551c"),n("f751"),n("097d");var a=n("2b0e"),i=(n("f5df"),n("5c96")),o=n.n(i),r=(n("0fae"),n("f0d9")),c=n.n(r),s=(n("b20f"),function(){var e=this,t=e.$createElement,n=e._self._c||t;return n("div",{attrs:{id:"app"}},[n("router-view")],1)}),u=[],l={name:"App"},d=l,m=n("2877"),h=Object(m["a"])(d,s,u,!1,null,null,null),f=h.exports,p=n("2f62"),v=(n("7f7f"),{sidebar:function(e){return e.app.sidebar},device:function(e){return e.app.device},token:function(e){return e.user.token},avatar:function(e){return e.user.avatar},name:function(e){return e.user.name}}),b=v,g=n("a78e"),w=n.n(g),x={sidebar:{opened:!w.a.get("sidebarStatus")||!!+w.a.get("sidebarStatus"),withoutAnimation:!1},device:"desktop"},y={TOGGLE_SIDEBAR:function(e){e.sidebar.opened=!e.sidebar.opened,e.sidebar.withoutAnimation=!1,e.sidebar.opened?w.a.set("sidebarStatus",1):w.a.set("sidebarStatus",0)},CLOSE_SIDEBAR:function(e,t){w.a.set("sidebarStatus",0),e.sidebar.opened=!1,e.sidebar.withoutAnimation=t},TOGGLE_DEVICE:function(e,t){e.device=t}},k={toggleSideBar:function(e){var t=e.commit;t("TOGGLE_SIDEBAR")},closeSideBar:function(e,t){var n=e.commit,a=t.withoutAnimation;n("CLOSE_SIDEBAR",a)},toggleDevice:function(e,t){var n=e.commit;n("TOGGLE_DEVICE",t)}},C={namespaced:!0,state:x,mutations:y,actions:k},_=n("83d6"),O=n.n(_),S=O.a.showSettings,z=O.a.fixedHeader,B=O.a.sidebarLogo,M={showSettings:S,fixedHeader:z,sidebarLogo:B},H={CHANGE_SETTING:function(e,t){var n=t.key,a=t.value;e.hasOwnProperty(n)&&(e[n]=a)}},T={changeSetting:function(e,t){var n=e.commit;n("CHANGE_SETTING",t)}},E={namespaced:!0,state:M,mutations:H,actions:T},L=n("bc3a"),$=n.n(L),A=n("5f87"),V=$.a.create({baseURL:"",withCredentials:!0,timeout:5e3});V.interceptors.request.use(function(e){return bt.getters.token&&(e.headers["X-Token"]=Object(A["a"])()),e},function(e){return console.log(e),Promise.reject(e)}),V.interceptors.response.use(function(e){var t=e.data;return 2e4!==t.code?(Object(i["Message"])({message:t.message||"error",type:"error",duration:5e3}),50008!==t.code&&50012!==t.code&&50014!==t.code||i["MessageBox"].confirm("You have been logged out, you can cancel to stay on this page, or log in again","Confirm logout",{confirmButtonText:"Re-Login",cancelButtonText:"Cancel",type:"warning"}).then(function(){bt.dispatch("user/resetToken").then(function(){location.reload()})}),Promise.reject(t.message||"error")):t},function(e){return console.log("err"+e),Object(i["Message"])({message:e.message,type:"error",duration:5e3}),Promise.reject(e)});var j=V;function I(e){return j({url:"/user/login",method:"post",data:e})}function P(e){return j({url:"/user/info",method:"get",params:{token:e}})}function N(){return j({url:"/user/logout",method:"post"})}var D=n("8c4f"),R=function(){var e=this,t=e.$createElement,n=e._self._c||t;return n("div",{staticClass:"app-wrapper",class:e.classObj},["mobile"===e.device&&e.sidebar.opened?n("div",{staticClass:"drawer-bg",on:{click:e.handleClickOutside}}):e._e(),e._v(" "),n("sidebar",{staticClass:"sidebar-container"}),e._v(" "),n("div",{staticClass:"main-container"},[n("div",{class:{"fixed-header":e.fixedHeader}},[n("navbar")],1),e._v(" "),n("app-main")],1)],1)},q=[],G=function(){var e=this,t=e.$createElement,n=e._self._c||t;return n("div",{staticClass:"navbar"},[n("hamburger",{staticClass:"hamburger-container",attrs:{"is-active":e.sidebar.opened},on:{toggleClick:e.toggleSideBar}}),e._v(" "),n("breadcrumb",{staticClass:"breadcrumb-container"}),e._v(" "),n("div",{staticClass:"right-menu"},[n("el-button",{staticStyle:{"margin-right":"10px"},attrs:{type:"text"},on:{click:e.doLogout}},[e._v("退出")])],1)],1)},F=[],U=n("db72"),K=function(){var e=this,t=e.$createElement,n=e._self._c||t;return n("el-breadcrumb",{staticClass:"app-breadcrumb",attrs:{separator:"/"}},[n("transition-group",{attrs:{name:"breadcrumb"}},e._l(e.levelList,function(t,a){return n("el-breadcrumb-item",{key:t.path},["noRedirect"===t.redirect||a==e.levelList.length-1?n("span",{staticClass:"no-redirect"},[e._v(e._s(t.meta.title))]):n("a",{on:{click:function(n){return n.preventDefault(),e.handleLink(t)}}},[e._v(e._s(t.meta.title))])])}),1)],1)},W=[],J=n("bd11"),X=n.n(J),Y={data:function(){return{levelList:null}},watch:{$route:function(){this.getBreadcrumb()}},created:function(){this.getBreadcrumb()},methods:{getBreadcrumb:function(){var e=this.$route.matched.filter(function(e){return e.meta&&e.meta.title}),t=e[0];this.isDashboard(t)||(e=[{path:"/dashboard",meta:{title:"Dashboard"}}].concat(e)),this.levelList=e.filter(function(e){return e.meta&&e.meta.title&&!1!==e.meta.breadcrumb})},isDashboard:function(e){var t=e&&e.name;return!!t&&t.trim().toLocaleLowerCase()==="Dashboard".toLocaleLowerCase()},pathCompile:function(e){var t=this.$route.params,n=X.a.compile(e);return n(t)},handleLink:function(e){var t=e.redirect,n=e.path;t?this.$router.push(t):this.$router.push(this.pathCompile(n))}}},Q=Y,Z=(n("34c8"),Object(m["a"])(Q,K,W,!1,null,"62cc9144",null)),ee=Z.exports,te=function(){var e=this,t=e.$createElement,n=e._self._c||t;return n("div",{staticStyle:{padding:"0 15px"},on:{click:e.toggleClick}},[n("svg",{staticClass:"hamburger",class:{"is-active":e.isActive},attrs:{viewBox:"0 0 1024 1024",xmlns:"http://www.w3.org/2000/svg",width:"64",height:"64"}},[n("path",{attrs:{d:"M408 442h480c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8H408c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8zm-8 204c0 4.4 3.6 8 8 8h480c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8H408c-4.4 0-8 3.6-8 8v56zm504-486H120c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm0 632H120c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zM142.4 642.1L298.7 519a8.84 8.84 0 0 0 0-13.9L142.4 381.9c-5.8-4.6-14.4-.5-14.4 6.9v246.3a8.9 8.9 0 0 0 14.4 7z"}})])])},ne=[],ae={name:"Hamburger",props:{isActive:{type:Boolean,default:!1}},methods:{toggleClick:function(){this.$emit("toggleClick")}}},ie=ae,oe=(n("186a"),Object(m["a"])(ie,te,ne,!1,null,"49e15297",null)),re=oe.exports,ce={components:{Breadcrumb:ee,Hamburger:re},computed:Object(U["a"])({},Object(p["b"])(["sidebar","avatar"])),methods:{toggleSideBar:function(){this.$store.dispatch("app/toggleSideBar")},doLogout:function(){this.logout()}}},se=ce,ue=(n("405a"),Object(m["a"])(se,G,F,!1,null,"1eff5c58",null)),le=ue.exports,de=function(){var e=this,t=e.$createElement,n=e._self._c||t;return n("div",{class:{"has-logo":e.showLogo}},[e.showLogo?n("logo",{attrs:{collapse:e.isCollapse}}):e._e(),e._v(" "),n("el-scrollbar",{attrs:{"wrap-class":"scrollbar-wrapper"}},[n("el-menu",{attrs:{"default-active":e.activeMenu,collapse:e.isCollapse,"background-color":e.variables.menuBg,"text-color":e.variables.menuText,"unique-opened":!1,"active-text-color":e.variables.menuActiveText,"collapse-transition":!1,"default-openeds":e.opened,mode:"vertical"}},e._l(e.routes,function(e){return n("sidebar-item",{key:e.path,attrs:{item:e,"base-path":e.path}})}),1)],1)],1)},me=[],he=function(){var e=this,t=e.$createElement,n=e._self._c||t;return n("div",{staticClass:"sidebar-logo-container",class:{collapse:e.collapse}},[n("transition",{attrs:{name:"sidebarLogoFade"}},[e.collapse?n("router-link",{key:"collapse",staticClass:"sidebar-logo-link",attrs:{to:"/"}},[e.logo?n("img",{staticClass:"sidebar-logo",attrs:{src:e.logo}}):n("h1",{staticClass:"sidebar-title"},[e._v(e._s(e.title)+" ")])]):n("router-link",{key:"expand",staticClass:"sidebar-logo-link",attrs:{to:"/"}},[e.logo?n("img",{staticClass:"sidebar-logo",attrs:{src:e.logo}}):e._e(),e._v(" "),n("h1",{staticClass:"sidebar-title"},[e._v(e._s(e.title)+" ")])])],1)],1)},fe=[],pe={name:"SidebarLogo",props:{collapse:{type:Boolean,required:!0}},data:function(){return{title:"SOP Admin",logo:"https://wpimg.wallstcn.com/69a1c46c-eb1c-4b46-8bd4-e9e686ef5251.png"}}},ve=pe,be=(n("7c27"),Object(m["a"])(ve,he,fe,!1,null,"b905289c",null)),ge=be.exports,we=function(){var e=this,t=e.$createElement,n=e._self._c||t;return e.item.hidden?e._e():n("div",{staticClass:"menu-wrapper"},[!e.hasOneShowingChild(e.item.children,e.item)||e.onlyOneChild.children&&!e.onlyOneChild.noShowingChildren||e.item.alwaysShow?n("el-submenu",{ref:"subMenu",attrs:{index:e.resolvePath(e.item.path),"popper-append-to-body":""}},[n("template",{slot:"title"},[e.item.meta?n("item",{attrs:{icon:e.item.meta&&e.item.meta.icon,title:e.item.meta.title}}):e._e()],1),e._v(" "),e._l(e.item.children,function(t){return n("sidebar-item",{key:t.path,staticClass:"nest-menu",attrs:{"is-nest":!0,item:t,"base-path":e.resolvePath(t.path)}})})],2):[e.onlyOneChild.meta?n("app-link",{attrs:{to:e.resolvePath(e.onlyOneChild.path)}},[n("el-menu-item",{class:{"submenu-title-noDropdown":!e.isNest},attrs:{index:e.resolvePath(e.onlyOneChild.path)}},[n("item",{attrs:{icon:e.onlyOneChild.meta.icon||e.item.meta&&e.item.meta.icon,title:e.onlyOneChild.meta.title}})],1)],1):e._e()]],2)},xe=[],ye=n("df7c"),ke=n.n(ye);function Ce(e){return/^(https?:|mailto:|tel:)/.test(e)}var _e,Oe,Se={name:"MenuItem",functional:!0,props:{icon:{type:String,default:""},title:{type:String,default:""}},render:function(e,t){var n=t.props,a=n.icon,i=n.title,o=[];return a&&o.push(e("svg-icon",{attrs:{"icon-class":a}})),i&&o.push(e("span",{slot:"title"},[i])),o}},ze=Se,Be=Object(m["a"])(ze,_e,Oe,!1,null,null,null),Me=Be.exports,He=function(){var e=this,t=e.$createElement,n=e._self._c||t;return n("component",e._b({},"component",e.linkProps(e.to),!1),[e._t("default")],2)},Te=[],Ee={props:{to:{type:String,required:!0}},methods:{linkProps:function(e){return Ce(e)?{is:"a",href:e,target:"_blank",rel:"noopener"}:{is:"router-link",to:e}}}},Le=Ee,$e=Object(m["a"])(Le,He,Te,!1,null,null,null),Ae=$e.exports,Ve={computed:{device:function(){return this.$store.state.app.device}},mounted:function(){this.fixBugIniOS()},methods:{fixBugIniOS:function(){var e=this,t=this.$refs.subMenu;if(t){var n=t.handleMouseleave;t.handleMouseleave=function(t){"mobile"!==e.device&&n(t)}}}}},je={name:"SidebarItem",components:{Item:Me,AppLink:Ae},mixins:[Ve],props:{item:{type:Object,required:!0},isNest:{type:Boolean,default:!1},basePath:{type:String,default:""}},data:function(){return this.onlyOneChild=null,{}},methods:{hasOneShowingChild:function(){var e=this,t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:[],n=arguments.length>1?arguments[1]:void 0,a=t.filter(function(t){return!t.hidden&&(e.onlyOneChild=t,!0)});return 1===a.length||0===a.length&&(this.onlyOneChild=Object(U["a"])({},n,{path:"",noShowingChildren:!0}),!0)},resolvePath:function(e){return Ce(e)?e:Ce(this.basePath)?this.basePath:ke.a.resolve(this.basePath,e)}}},Ie=je,Pe=Object(m["a"])(Ie,we,xe,!1,null,null,null),Ne=Pe.exports,De=n("cf1e"),Re=n.n(De),qe={components:{SidebarItem:Ne,Logo:ge},computed:Object(U["a"])({},Object(p["b"])(["sidebar"]),{routes:function(){return this.$router.options.routes},opened:function(){return this.routes.filter(function(e){return e.meta&&e.meta.open}).map(function(e){return e.path})},activeMenu:function(){var e=this.$route,t=e.meta,n=e.path;return t.activeMenu?t.activeMenu:n},showLogo:function(){return this.$store.state.settings.sidebarLogo},variables:function(){return Re.a},isCollapse:function(){return!this.sidebar.opened}})},Ge=qe,Fe=Object(m["a"])(Ge,de,me,!1,null,null,null),Ue=Fe.exports,Ke=function(){var e=this,t=e.$createElement,n=e._self._c||t;return n("section",{staticClass:"app-main"},[n("transition",{attrs:{name:"fade-transform",mode:"out-in"}},[n("router-view",{key:e.key})],1)],1)},We=[],Je={name:"AppMain",computed:{key:function(){return this.$route.fullPath}}},Xe=Je,Ye=(n("50be"),Object(m["a"])(Xe,Ke,We,!1,null,"43c24f68",null)),Qe=Ye.exports,Ze=document,et=Ze.body,tt=992,nt={watch:{$route:function(e){"mobile"===this.device&&this.sidebar.opened&&bt.dispatch("app/closeSideBar",{withoutAnimation:!1})}},beforeMount:function(){window.addEventListener("resize",this.$_resizeHandler)},beforeDestroy:function(){window.removeEventListener("resize",this.$_resizeHandler)},mounted:function(){var e=this.$_isMobile();e&&(bt.dispatch("app/toggleDevice","mobile"),bt.dispatch("app/closeSideBar",{withoutAnimation:!0}))},methods:{$_isMobile:function(){var e=et.getBoundingClientRect();return e.width-1'});r.a.add(c);t["default"]=c},"9f2b":function(e,t,n){"use strict";var a=n("bf90"),i=n.n(a);i.a},b20f:function(e,t,n){e.exports={menuText:"#bfcbd9",menuActiveText:"#409EFF",subMenuActiveText:"#f4f4f5",menuBg:"#304156",menuHover:"#263445",subMenuBg:"#1f2d3d",subMenuHover:"#001528",sideBarWidth:"210px"}},b3b5:function(e,t,n){"use strict";n.r(t);var a=n("e017"),i=n.n(a),o=n("21a1"),r=n.n(o),c=new i.a({id:"icon-user",use:"icon-user-usage",viewBox:"0 0 130 130",content:''});r.a.add(c);t["default"]=c},bf90:function(e,t,n){},cf1e:function(e,t,n){e.exports={menuText:"#bfcbd9",menuActiveText:"#409EFF",subMenuActiveText:"#f4f4f5",menuBg:"#304156",menuHover:"#263445",subMenuBg:"#1f2d3d",subMenuHover:"#001528",sideBarWidth:"210px"}},d7ec:function(e,t,n){"use strict";n.r(t);var a=n("e017"),i=n.n(a),o=n("21a1"),r=n.n(o),c=new i.a({id:"icon-eye-open",use:"icon-eye-open-usage",viewBox:"0 0 1024 1024",content:''});r.a.add(c);t["default"]=c},dc52:function(e,t,n){},dcf8:function(e,t,n){"use strict";n.r(t);var a=n("e017"),i=n.n(a),o=n("21a1"),r=n.n(o),c=new i.a({id:"icon-nested",use:"icon-nested-usage",viewBox:"0 0 128 128",content:''});r.a.add(c);t["default"]=c},eb1b:function(e,t,n){"use strict";n.r(t);var a=n("e017"),i=n.n(a),o=n("21a1"),r=n.n(o),c=new i.a({id:"icon-form",use:"icon-form-usage",viewBox:"0 0 128 128",content:''});r.a.add(c);t["default"]=c},f782:function(e,t,n){"use strict";n.r(t);var a=n("e017"),i=n.n(a),o=n("21a1"),r=n.n(o),c=new i.a({id:"icon-dashboard",use:"icon-dashboard-usage",viewBox:"0 0 128 100",content:''});r.a.add(c);t["default"]=c}},[[0,"runtime","chunk-elementUI","chunk-libs"]]]); \ No newline at end of file diff --git a/sop-admin/sop-admin-server/src/main/resources/public/static/js/app.f323bdd7.js b/sop-admin/sop-admin-server/src/main/resources/public/static/js/app.f323bdd7.js deleted file mode 100644 index 0ef6965e..00000000 --- a/sop-admin/sop-admin-server/src/main/resources/public/static/js/app.f323bdd7.js +++ /dev/null @@ -1 +0,0 @@ -(window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["app"],{0:function(e,t,n){e.exports=n("56d7")},"0a7b":function(e,t,n){},"0cb8":function(e,t,n){},"186a":function(e,t,n){"use strict";var a=n("dc52"),i=n.n(a);i.a},"18f0":function(e,t,n){"use strict";n.r(t);var a=n("e017"),i=n.n(a),o=n("21a1"),r=n.n(o),c=new i.a({id:"icon-link",use:"icon-link-usage",viewBox:"0 0 128 128",content:''});r.a.add(c);t["default"]=c},2536:function(e,t,n){},"2a3d":function(e,t,n){"use strict";n.r(t);var a=n("e017"),i=n.n(a),o=n("21a1"),r=n.n(o),c=new i.a({id:"icon-password",use:"icon-password-usage",viewBox:"0 0 128 128",content:''});r.a.add(c);t["default"]=c},"30c3":function(e,t,n){"use strict";n.r(t);var a=n("e017"),i=n.n(a),o=n("21a1"),r=n.n(o),c=new i.a({id:"icon-example",use:"icon-example-usage",viewBox:"0 0 128 128",content:''});r.a.add(c);t["default"]=c},"34c8":function(e,t,n){"use strict";var a=n("88a1"),i=n.n(a);i.a},"405a":function(e,t,n){"use strict";var a=n("4a83"),i=n.n(a);i.a},"47f1":function(e,t,n){"use strict";n.r(t);var a=n("e017"),i=n.n(a),o=n("21a1"),r=n.n(o),c=new i.a({id:"icon-table",use:"icon-table-usage",viewBox:"0 0 128 128",content:''});r.a.add(c);t["default"]=c},"4a83":function(e,t,n){},"4df5":function(e,t,n){"use strict";n.r(t);var a=n("e017"),i=n.n(a),o=n("21a1"),r=n.n(o),c=new i.a({id:"icon-eye",use:"icon-eye-usage",viewBox:"0 0 128 64",content:''});r.a.add(c);t["default"]=c},"50be":function(e,t,n){"use strict";var a=n("2536"),i=n.n(a);i.a},"51ff":function(e,t,n){var a={"./dashboard.svg":"f782","./example.svg":"30c3","./eye-open.svg":"d7ec","./eye.svg":"4df5","./form.svg":"eb1b","./link.svg":"18f0","./nested.svg":"dcf8","./password.svg":"2a3d","./table.svg":"47f1","./tree.svg":"93cd","./user.svg":"b3b5"};function i(e){var t=o(e);return n(t)}function o(e){var t=a[e];if(!(t+1)){var n=new Error("Cannot find module '"+e+"'");throw n.code="MODULE_NOT_FOUND",n}return t}i.keys=function(){return Object.keys(a)},i.resolve=o,e.exports=i,i.id="51ff"},"56d7":function(e,t,n){"use strict";n.r(t);n("cadf"),n("551c"),n("f751"),n("097d");var a=n("2b0e"),i=(n("f5df"),n("5c96")),o=n.n(i),r=(n("0fae"),n("f0d9")),c=n.n(r),s=(n("b20f"),function(){var e=this,t=e.$createElement,n=e._self._c||t;return n("div",{attrs:{id:"app"}},[n("router-view")],1)}),u=[],l={name:"App"},d=l,m=n("2877"),h=Object(m["a"])(d,s,u,!1,null,null,null),f=h.exports,p=n("2f62"),v=(n("7f7f"),{sidebar:function(e){return e.app.sidebar},device:function(e){return e.app.device},token:function(e){return e.user.token},avatar:function(e){return e.user.avatar},name:function(e){return e.user.name}}),b=v,g=n("a78e"),w=n.n(g),x={sidebar:{opened:!w.a.get("sidebarStatus")||!!+w.a.get("sidebarStatus"),withoutAnimation:!1},device:"desktop"},y={TOGGLE_SIDEBAR:function(e){e.sidebar.opened=!e.sidebar.opened,e.sidebar.withoutAnimation=!1,e.sidebar.opened?w.a.set("sidebarStatus",1):w.a.set("sidebarStatus",0)},CLOSE_SIDEBAR:function(e,t){w.a.set("sidebarStatus",0),e.sidebar.opened=!1,e.sidebar.withoutAnimation=t},TOGGLE_DEVICE:function(e,t){e.device=t}},k={toggleSideBar:function(e){var t=e.commit;t("TOGGLE_SIDEBAR")},closeSideBar:function(e,t){var n=e.commit,a=t.withoutAnimation;n("CLOSE_SIDEBAR",a)},toggleDevice:function(e,t){var n=e.commit;n("TOGGLE_DEVICE",t)}},C={namespaced:!0,state:x,mutations:y,actions:k},_=n("83d6"),O=n.n(_),S=O.a.showSettings,z=O.a.fixedHeader,B=O.a.sidebarLogo,M={showSettings:S,fixedHeader:z,sidebarLogo:B},H={CHANGE_SETTING:function(e,t){var n=t.key,a=t.value;e.hasOwnProperty(n)&&(e[n]=a)}},T={changeSetting:function(e,t){var n=e.commit;n("CHANGE_SETTING",t)}},E={namespaced:!0,state:M,mutations:H,actions:T},L=n("bc3a"),$=n.n(L),A=n("5f87"),V=$.a.create({baseURL:"/api",withCredentials:!0,timeout:5e3});V.interceptors.request.use(function(e){return bt.getters.token&&(e.headers["X-Token"]=Object(A["a"])()),e},function(e){return console.log(e),Promise.reject(e)}),V.interceptors.response.use(function(e){var t=e.data;return 2e4!==t.code?(Object(i["Message"])({message:t.message||"error",type:"error",duration:5e3}),50008!==t.code&&50012!==t.code&&50014!==t.code||i["MessageBox"].confirm("You have been logged out, you can cancel to stay on this page, or log in again","Confirm logout",{confirmButtonText:"Re-Login",cancelButtonText:"Cancel",type:"warning"}).then(function(){bt.dispatch("user/resetToken").then(function(){location.reload()})}),Promise.reject(t.message||"error")):t},function(e){return console.log("err"+e),Object(i["Message"])({message:e.message,type:"error",duration:5e3}),Promise.reject(e)});var j=V;function I(e){return j({url:"/user/login",method:"post",data:e})}function P(e){return j({url:"/user/info",method:"get",params:{token:e}})}function N(){return j({url:"/user/logout",method:"post"})}var D=n("8c4f"),R=function(){var e=this,t=e.$createElement,n=e._self._c||t;return n("div",{staticClass:"app-wrapper",class:e.classObj},["mobile"===e.device&&e.sidebar.opened?n("div",{staticClass:"drawer-bg",on:{click:e.handleClickOutside}}):e._e(),e._v(" "),n("sidebar",{staticClass:"sidebar-container"}),e._v(" "),n("div",{staticClass:"main-container"},[n("div",{class:{"fixed-header":e.fixedHeader}},[n("navbar")],1),e._v(" "),n("app-main")],1)],1)},q=[],G=function(){var e=this,t=e.$createElement,n=e._self._c||t;return n("div",{staticClass:"navbar"},[n("hamburger",{staticClass:"hamburger-container",attrs:{"is-active":e.sidebar.opened},on:{toggleClick:e.toggleSideBar}}),e._v(" "),n("breadcrumb",{staticClass:"breadcrumb-container"}),e._v(" "),n("div",{staticClass:"right-menu"},[n("el-button",{staticStyle:{"margin-right":"10px"},attrs:{type:"text"},on:{click:e.doLogout}},[e._v("退出")])],1)],1)},F=[],U=n("db72"),K=function(){var e=this,t=e.$createElement,n=e._self._c||t;return n("el-breadcrumb",{staticClass:"app-breadcrumb",attrs:{separator:"/"}},[n("transition-group",{attrs:{name:"breadcrumb"}},e._l(e.levelList,function(t,a){return n("el-breadcrumb-item",{key:t.path},["noRedirect"===t.redirect||a==e.levelList.length-1?n("span",{staticClass:"no-redirect"},[e._v(e._s(t.meta.title))]):n("a",{on:{click:function(n){return n.preventDefault(),e.handleLink(t)}}},[e._v(e._s(t.meta.title))])])}),1)],1)},W=[],J=n("bd11"),X=n.n(J),Y={data:function(){return{levelList:null}},watch:{$route:function(){this.getBreadcrumb()}},created:function(){this.getBreadcrumb()},methods:{getBreadcrumb:function(){var e=this.$route.matched.filter(function(e){return e.meta&&e.meta.title}),t=e[0];this.isDashboard(t)||(e=[{path:"/dashboard",meta:{title:"Dashboard"}}].concat(e)),this.levelList=e.filter(function(e){return e.meta&&e.meta.title&&!1!==e.meta.breadcrumb})},isDashboard:function(e){var t=e&&e.name;return!!t&&t.trim().toLocaleLowerCase()==="Dashboard".toLocaleLowerCase()},pathCompile:function(e){var t=this.$route.params,n=X.a.compile(e);return n(t)},handleLink:function(e){var t=e.redirect,n=e.path;t?this.$router.push(t):this.$router.push(this.pathCompile(n))}}},Q=Y,Z=(n("34c8"),Object(m["a"])(Q,K,W,!1,null,"62cc9144",null)),ee=Z.exports,te=function(){var e=this,t=e.$createElement,n=e._self._c||t;return n("div",{staticStyle:{padding:"0 15px"},on:{click:e.toggleClick}},[n("svg",{staticClass:"hamburger",class:{"is-active":e.isActive},attrs:{viewBox:"0 0 1024 1024",xmlns:"http://www.w3.org/2000/svg",width:"64",height:"64"}},[n("path",{attrs:{d:"M408 442h480c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8H408c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8zm-8 204c0 4.4 3.6 8 8 8h480c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8H408c-4.4 0-8 3.6-8 8v56zm504-486H120c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm0 632H120c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zM142.4 642.1L298.7 519a8.84 8.84 0 0 0 0-13.9L142.4 381.9c-5.8-4.6-14.4-.5-14.4 6.9v246.3a8.9 8.9 0 0 0 14.4 7z"}})])])},ne=[],ae={name:"Hamburger",props:{isActive:{type:Boolean,default:!1}},methods:{toggleClick:function(){this.$emit("toggleClick")}}},ie=ae,oe=(n("186a"),Object(m["a"])(ie,te,ne,!1,null,"49e15297",null)),re=oe.exports,ce={components:{Breadcrumb:ee,Hamburger:re},computed:Object(U["a"])({},Object(p["b"])(["sidebar","avatar"])),methods:{toggleSideBar:function(){this.$store.dispatch("app/toggleSideBar")},doLogout:function(){this.logout()}}},se=ce,ue=(n("405a"),Object(m["a"])(se,G,F,!1,null,"1eff5c58",null)),le=ue.exports,de=function(){var e=this,t=e.$createElement,n=e._self._c||t;return n("div",{class:{"has-logo":e.showLogo}},[e.showLogo?n("logo",{attrs:{collapse:e.isCollapse}}):e._e(),e._v(" "),n("el-scrollbar",{attrs:{"wrap-class":"scrollbar-wrapper"}},[n("el-menu",{attrs:{"default-active":e.activeMenu,collapse:e.isCollapse,"background-color":e.variables.menuBg,"text-color":e.variables.menuText,"unique-opened":!1,"active-text-color":e.variables.menuActiveText,"collapse-transition":!1,mode:"vertical"}},e._l(e.routes,function(e){return n("sidebar-item",{key:e.path,attrs:{item:e,"base-path":e.path}})}),1)],1)],1)},me=[],he=function(){var e=this,t=e.$createElement,n=e._self._c||t;return n("div",{staticClass:"sidebar-logo-container",class:{collapse:e.collapse}},[n("transition",{attrs:{name:"sidebarLogoFade"}},[e.collapse?n("router-link",{key:"collapse",staticClass:"sidebar-logo-link",attrs:{to:"/"}},[e.logo?n("img",{staticClass:"sidebar-logo",attrs:{src:e.logo}}):n("h1",{staticClass:"sidebar-title"},[e._v(e._s(e.title)+" ")])]):n("router-link",{key:"expand",staticClass:"sidebar-logo-link",attrs:{to:"/"}},[e.logo?n("img",{staticClass:"sidebar-logo",attrs:{src:e.logo}}):e._e(),e._v(" "),n("h1",{staticClass:"sidebar-title"},[e._v(e._s(e.title)+" ")])])],1)],1)},fe=[],pe={name:"SidebarLogo",props:{collapse:{type:Boolean,required:!0}},data:function(){return{title:"SOP Admin",logo:"https://wpimg.wallstcn.com/69a1c46c-eb1c-4b46-8bd4-e9e686ef5251.png"}}},ve=pe,be=(n("7c27"),Object(m["a"])(ve,he,fe,!1,null,"b905289c",null)),ge=be.exports,we=function(){var e=this,t=e.$createElement,n=e._self._c||t;return e.item.hidden?e._e():n("div",{staticClass:"menu-wrapper"},[!e.hasOneShowingChild(e.item.children,e.item)||e.onlyOneChild.children&&!e.onlyOneChild.noShowingChildren||e.item.alwaysShow?n("el-submenu",{ref:"subMenu",attrs:{index:e.resolvePath(e.item.path),"popper-append-to-body":""}},[n("template",{slot:"title"},[e.item.meta?n("item",{attrs:{icon:e.item.meta&&e.item.meta.icon,title:e.item.meta.title}}):e._e()],1),e._v(" "),e._l(e.item.children,function(t){return n("sidebar-item",{key:t.path,staticClass:"nest-menu",attrs:{"is-nest":!0,item:t,"base-path":e.resolvePath(t.path)}})})],2):[e.onlyOneChild.meta?n("app-link",{attrs:{to:e.resolvePath(e.onlyOneChild.path)}},[n("el-menu-item",{class:{"submenu-title-noDropdown":!e.isNest},attrs:{index:e.resolvePath(e.onlyOneChild.path)}},[n("item",{attrs:{icon:e.onlyOneChild.meta.icon||e.item.meta&&e.item.meta.icon,title:e.onlyOneChild.meta.title}})],1)],1):e._e()]],2)},xe=[],ye=n("df7c"),ke=n.n(ye);function Ce(e){return/^(https?:|mailto:|tel:)/.test(e)}var _e,Oe,Se={name:"MenuItem",functional:!0,props:{icon:{type:String,default:""},title:{type:String,default:""}},render:function(e,t){var n=t.props,a=n.icon,i=n.title,o=[];return a&&o.push(e("svg-icon",{attrs:{"icon-class":a}})),i&&o.push(e("span",{slot:"title"},[i])),o}},ze=Se,Be=Object(m["a"])(ze,_e,Oe,!1,null,null,null),Me=Be.exports,He=function(){var e=this,t=e.$createElement,n=e._self._c||t;return n("component",e._b({},"component",e.linkProps(e.to),!1),[e._t("default")],2)},Te=[],Ee={props:{to:{type:String,required:!0}},methods:{linkProps:function(e){return Ce(e)?{is:"a",href:e,target:"_blank",rel:"noopener"}:{is:"router-link",to:e}}}},Le=Ee,$e=Object(m["a"])(Le,He,Te,!1,null,null,null),Ae=$e.exports,Ve={computed:{device:function(){return this.$store.state.app.device}},mounted:function(){this.fixBugIniOS()},methods:{fixBugIniOS:function(){var e=this,t=this.$refs.subMenu;if(t){var n=t.handleMouseleave;t.handleMouseleave=function(t){"mobile"!==e.device&&n(t)}}}}},je={name:"SidebarItem",components:{Item:Me,AppLink:Ae},mixins:[Ve],props:{item:{type:Object,required:!0},isNest:{type:Boolean,default:!1},basePath:{type:String,default:""}},data:function(){return this.onlyOneChild=null,{}},methods:{hasOneShowingChild:function(){var e=this,t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:[],n=arguments.length>1?arguments[1]:void 0,a=t.filter(function(t){return!t.hidden&&(e.onlyOneChild=t,!0)});return 1===a.length||0===a.length&&(this.onlyOneChild=Object(U["a"])({},n,{path:"",noShowingChildren:!0}),!0)},resolvePath:function(e){return Ce(e)?e:Ce(this.basePath)?this.basePath:ke.a.resolve(this.basePath,e)}}},Ie=je,Pe=Object(m["a"])(Ie,we,xe,!1,null,null,null),Ne=Pe.exports,De=n("cf1e"),Re=n.n(De),qe={components:{SidebarItem:Ne,Logo:ge},computed:Object(U["a"])({},Object(p["b"])(["sidebar"]),{routes:function(){return this.$router.options.routes},activeMenu:function(){var e=this.$route,t=e.meta,n=e.path;return t.activeMenu?t.activeMenu:n},showLogo:function(){return this.$store.state.settings.sidebarLogo},variables:function(){return Re.a},isCollapse:function(){return!this.sidebar.opened}})},Ge=qe,Fe=Object(m["a"])(Ge,de,me,!1,null,null,null),Ue=Fe.exports,Ke=function(){var e=this,t=e.$createElement,n=e._self._c||t;return n("section",{staticClass:"app-main"},[n("transition",{attrs:{name:"fade-transform",mode:"out-in"}},[n("router-view",{key:e.key})],1)],1)},We=[],Je={name:"AppMain",computed:{key:function(){return this.$route.fullPath}}},Xe=Je,Ye=(n("50be"),Object(m["a"])(Xe,Ke,We,!1,null,"43c24f68",null)),Qe=Ye.exports,Ze=document,et=Ze.body,tt=992,nt={watch:{$route:function(e){"mobile"===this.device&&this.sidebar.opened&&bt.dispatch("app/closeSideBar",{withoutAnimation:!1})}},beforeMount:function(){window.addEventListener("resize",this.$_resizeHandler)},beforeDestroy:function(){window.removeEventListener("resize",this.$_resizeHandler)},mounted:function(){var e=this.$_isMobile();e&&(bt.dispatch("app/toggleDevice","mobile"),bt.dispatch("app/closeSideBar",{withoutAnimation:!0}))},methods:{$_isMobile:function(){var e=et.getBoundingClientRect();return e.width-1'});r.a.add(c);t["default"]=c},"9f2b":function(e,t,n){"use strict";var a=n("bf90"),i=n.n(a);i.a},b20f:function(e,t,n){e.exports={menuText:"#bfcbd9",menuActiveText:"#409EFF",subMenuActiveText:"#f4f4f5",menuBg:"#304156",menuHover:"#263445",subMenuBg:"#1f2d3d",subMenuHover:"#001528",sideBarWidth:"210px"}},b3b5:function(e,t,n){"use strict";n.r(t);var a=n("e017"),i=n.n(a),o=n("21a1"),r=n.n(o),c=new i.a({id:"icon-user",use:"icon-user-usage",viewBox:"0 0 130 130",content:''});r.a.add(c);t["default"]=c},bf90:function(e,t,n){},cf1e:function(e,t,n){e.exports={menuText:"#bfcbd9",menuActiveText:"#409EFF",subMenuActiveText:"#f4f4f5",menuBg:"#304156",menuHover:"#263445",subMenuBg:"#1f2d3d",subMenuHover:"#001528",sideBarWidth:"210px"}},d7ec:function(e,t,n){"use strict";n.r(t);var a=n("e017"),i=n.n(a),o=n("21a1"),r=n.n(o),c=new i.a({id:"icon-eye-open",use:"icon-eye-open-usage",viewBox:"0 0 1024 1024",content:''});r.a.add(c);t["default"]=c},dc52:function(e,t,n){},dcf8:function(e,t,n){"use strict";n.r(t);var a=n("e017"),i=n.n(a),o=n("21a1"),r=n.n(o),c=new i.a({id:"icon-nested",use:"icon-nested-usage",viewBox:"0 0 128 128",content:''});r.a.add(c);t["default"]=c},eb1b:function(e,t,n){"use strict";n.r(t);var a=n("e017"),i=n.n(a),o=n("21a1"),r=n.n(o),c=new i.a({id:"icon-form",use:"icon-form-usage",viewBox:"0 0 128 128",content:''});r.a.add(c);t["default"]=c},f782:function(e,t,n){"use strict";n.r(t);var a=n("e017"),i=n.n(a),o=n("21a1"),r=n.n(o),c=new i.a({id:"icon-dashboard",use:"icon-dashboard-usage",viewBox:"0 0 128 100",content:''});r.a.add(c);t["default"]=c}},[[0,"runtime","chunk-elementUI","chunk-libs"]]]); \ No newline at end of file diff --git a/sop-admin/sop-admin-server/src/main/resources/public/static/js/chunk-25908fca.cc77b01c.js b/sop-admin/sop-admin-server/src/main/resources/public/static/js/chunk-25908fca.cc77b01c.js new file mode 100644 index 00000000..ddf9e410 --- /dev/null +++ b/sop-admin/sop-admin-server/src/main/resources/public/static/js/chunk-25908fca.cc77b01c.js @@ -0,0 +1 @@ +(window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["chunk-25908fca"],{"021d":function(e,t,a){},"0bac":function(e,t,a){"use strict";var s=a("021d"),o=a.n(s);o.a},cb56:function(e,t,a){"use strict";a.r(t);var s=function(){var e=this,t=e.$createElement,a=e._self._c||t;return a("div",{staticClass:"app-container"},[a("el-form",{staticClass:"demo-form-inline",attrs:{inline:!0,model:e.searchFormData,size:"mini"}},[a("el-form-item",{attrs:{label:"AppId"}},[a("el-input",{staticStyle:{width:"250px"},attrs:{clearable:!0,placeholder:"AppId"},model:{value:e.searchFormData.appKey,callback:function(t){e.$set(e.searchFormData,"appKey",t)},expression:"searchFormData.appKey"}})],1),e._v(" "),a("el-form-item",[a("el-button",{attrs:{type:"primary",icon:"el-icon-search"},on:{click:e.onSearchTable}},[e._v("查询")])],1)],1),e._v(" "),a("el-button",{staticStyle:{"margin-bottom":"10px"},attrs:{type:"primary",size:"mini",icon:"el-icon-plus"},on:{click:e.onAdd}},[e._v("新增ISV")]),e._v(" "),a("el-table",{attrs:{data:e.pageInfo.list,border:"",fit:"","highlight-current-row":""}},[a("el-table-column",{attrs:{prop:"appKey",label:"AppId",width:"250"}}),e._v(" "),a("el-table-column",{attrs:{prop:"",label:"秘钥",width:"80"},scopedSlots:e._u([{key:"default",fn:function(t){return[a("el-button",{attrs:{type:"text",size:"mini"},on:{click:function(a){return e.onShowKeys(t.row)}}},[e._v("查看")])]}}])}),e._v(" "),a("el-table-column",{attrs:{prop:"roleList",label:"角色","show-overflow-tooltip":!0},scopedSlots:e._u([{key:"default",fn:function(t){return[a("span",{domProps:{innerHTML:e._s(e.roleRender(t.row))}})]}}])}),e._v(" "),a("el-table-column",{attrs:{prop:"userId",label:"注册用户",width:"100"},scopedSlots:e._u([{key:"default",fn:function(t){return[t.row.userId?a("span",{staticStyle:{"font-weight":"bold"}},[e._v("是")]):a("span",[e._v("否")])]}}])},[a("template",{slot:"header"},[e._v("\n 注册用户\n "),a("el-tooltip",{attrs:{content:"注册用户自行管理秘钥",placement:"top"}},[a("i",{staticClass:"el-icon-question",staticStyle:{cursor:"pointer"}})])],1)],2),e._v(" "),a("el-table-column",{attrs:{prop:"status",label:"状态",width:"80"},scopedSlots:e._u([{key:"default",fn:function(t){return[1===t.row.status?a("span",{staticStyle:{color:"#67C23A"}},[e._v("启用")]):e._e(),e._v(" "),2===t.row.status?a("span",{staticStyle:{color:"#F56C6C"}},[e._v("禁用")]):e._e()]}}])}),e._v(" "),a("el-table-column",{attrs:{prop:"gmtCreate",label:"添加时间",width:"160"}}),e._v(" "),a("el-table-column",{attrs:{prop:"remark",label:"备注","show-overflow-tooltip":!0}}),e._v(" "),a("el-table-column",{attrs:{label:"操作",width:"200"},scopedSlots:e._u([{key:"default",fn:function(t){return[a("el-button",{attrs:{type:"text",size:"mini"},on:{click:function(a){return e.onTableUpdate(t.row)}}},[e._v("修改")]),e._v(" "),t.row.userId?e._e():a("el-button",{attrs:{type:"text",size:"mini"},on:{click:function(a){return e.onKeysUpdate(t.row)}}},[e._v("秘钥管理")]),e._v(" "),t.row.userId?e._e():a("el-button",{attrs:{type:"text",size:"mini"},on:{click:function(a){return e.onExportKeys(t.row)}}},[e._v("导出秘钥")])]}}])})],1),e._v(" "),a("el-pagination",{staticStyle:{"margin-top":"5px"},attrs:{background:"","current-page":e.searchFormData.pageIndex,"page-size":e.searchFormData.pageSize,"page-sizes":[5,10,20,40],total:e.pageInfo.total,layout:"total, sizes, prev, pager, next"},on:{"size-change":e.onSizeChange,"current-change":e.onPageIndexChange}}),e._v(" "),a("el-dialog",{attrs:{title:e.isvDialogTitle,visible:e.isvDialogVisible,"close-on-click-modal":!1},on:{"update:visible":function(t){e.isvDialogVisible=t},close:e.onIsvDialogClose}},[a("el-form",{ref:"isvForm",attrs:{rules:e.rulesIsvForm,model:e.isvDialogFormData,"label-width":"120px",size:"mini"}},[a("el-form-item",{attrs:{label:"appId"}},[0===e.isvDialogFormData.id?a("span",{staticStyle:{color:"gray"}},[e._v("(系统自动生成)")]):a("span",[e._v(e._s(e.isvDialogFormData.appKey))])]),e._v(" "),a("el-form-item",{attrs:{label:"角色"}},[a("el-checkbox-group",{model:{value:e.isvDialogFormData.roleCode,callback:function(t){e.$set(e.isvDialogFormData,"roleCode",t)},expression:"isvDialogFormData.roleCode"}},e._l(e.roles,function(t){return a("el-checkbox",{key:t.roleCode,attrs:{label:t.roleCode}},[e._v(e._s(t.description))])}),1)],1),e._v(" "),a("el-form-item",{attrs:{label:"备注",prop:"remark"}},[a("el-input",{attrs:{type:"textarea"},model:{value:e.isvDialogFormData.remark,callback:function(t){e.$set(e.isvDialogFormData,"remark",t)},expression:"isvDialogFormData.remark"}})],1),e._v(" "),a("el-form-item",{attrs:{label:"状态"}},[a("el-radio-group",{model:{value:e.isvDialogFormData.status,callback:function(t){e.$set(e.isvDialogFormData,"status",t)},expression:"isvDialogFormData.status"}},[a("el-radio",{attrs:{label:1,name:"status"}},[e._v("启用")]),e._v(" "),a("el-radio",{attrs:{label:2,name:"status"}},[e._v("禁用")])],1)],1)],1),e._v(" "),a("div",{staticClass:"dialog-footer",attrs:{slot:"footer"},slot:"footer"},[a("el-button",{on:{click:function(t){e.isvDialogVisible=!1}}},[e._v("取 消")]),e._v(" "),a("el-button",{attrs:{type:"primary",disabled:e.isSaveButtonDisabled},on:{click:e.onIsvDialogSave}},[e._v("保 存")])],1)],1),e._v(" "),a("el-dialog",{attrs:{title:"秘钥信息",visible:e.isvKeysDialogVisible},on:{"update:visible":function(t){e.isvKeysDialogVisible=t},close:function(t){return e.resetForm("isvKeysFrom")}}},[a("el-form",{ref:"isvKeysFrom",staticClass:"key-view",attrs:{model:e.isvKeysFormData,"label-width":"160px",size:"mini"}},[a("el-form-item",{attrs:{label:"appId"}},[a("span",[e._v(e._s(e.isvKeysFormData.appKey))])]),e._v(" "),a("el-form-item",{directives:[{name:"show",rawName:"v-show",value:e.showKeys(),expression:"showKeys()"}],attrs:{label:"秘钥格式"}},[1===e.isvKeysFormData.keyFormat?a("span",[e._v("PKCS8(JAVA适用)")]):e._e(),e._v(" "),2===e.isvKeysFormData.keyFormat?a("span",[e._v("PKCS1(非JAVA适用)")]):e._e()]),e._v(" "),a("el-form-item",{directives:[{name:"show",rawName:"v-show",value:2===e.isvKeysFormData.signType,expression:"isvKeysFormData.signType === 2"}],attrs:{label:"secret"}},[a("span",[e._v(e._s(e.isvKeysFormData.secret))])]),e._v(" "),a("el-tabs",{directives:[{name:"show",rawName:"v-show",value:e.showKeys(),expression:"showKeys()"}],staticClass:"keyTabs",attrs:{type:"card"},model:{value:e.activeName,callback:function(t){e.activeName=t},expression:"activeName"}},[a("el-tab-pane",{attrs:{label:"ISV公私钥",name:"first"}},[a("el-form-item",{attrs:{label:"ISV公钥"}},[a("el-input",{attrs:{type:"textarea",placeholder:"未上传",readonly:""},model:{value:e.isvKeysFormData.publicKeyIsv,callback:function(t){e.$set(e.isvKeysFormData,"publicKeyIsv",t)},expression:"isvKeysFormData.publicKeyIsv"}})],1),e._v(" "),a("el-form-item",{directives:[{name:"show",rawName:"v-show",value:0===e.isvKeysFormData.userId,expression:"isvKeysFormData.userId === 0"}],attrs:{label:"ISV私钥"}},[a("el-input",{attrs:{type:"textarea",readonly:""},model:{value:e.isvKeysFormData.privateKeyIsv,callback:function(t){e.$set(e.isvKeysFormData,"privateKeyIsv",t)},expression:"isvKeysFormData.privateKeyIsv"}})],1)],1),e._v(" "),a("el-tab-pane",{attrs:{label:"平台公私钥",name:"second"}},[a("el-form-item",{attrs:{label:"平台公钥"}},[a("el-input",{attrs:{type:"textarea",readonly:""},model:{value:e.isvKeysFormData.publicKeyPlatform,callback:function(t){e.$set(e.isvKeysFormData,"publicKeyPlatform",t)},expression:"isvKeysFormData.publicKeyPlatform"}})],1),e._v(" "),a("el-form-item",{attrs:{prop:"privateKeyPlatform",label:"平台私钥"}},[a("el-input",{attrs:{type:"textarea",readonly:""},model:{value:e.isvKeysFormData.privateKeyPlatform,callback:function(t){e.$set(e.isvKeysFormData,"privateKeyPlatform",t)},expression:"isvKeysFormData.privateKeyPlatform"}})],1)],1)],1)],1),e._v(" "),a("span",{staticClass:"dialog-footer",attrs:{slot:"footer"},slot:"footer"},[a("el-button",{on:{click:function(t){e.isvKeysDialogVisible=!1}}},[e._v("关 闭")])],1)],1)],1)},o=[],i={data:function(){return{searchFormData:{appKey:"",pageIndex:1,pageSize:10},pageInfo:{list:[],total:0},roles:[],isvDialogVisible:!1,isvDialogTitle:"新增ISV",isvDialogFormData:{id:0,status:1,remark:"",roleCode:[]},rulesIsvForm:{remark:[{min:0,max:100,message:"长度在 1 到 100 个字符",trigger:"blur"}]},activeName:"first",isSaveButtonDisabled:!1,isvKeysDialogVisible:!1,isvKeysFormData:{appKey:"",secret:"",publicKeyIsv:"",privateKeyIsv:"",publicKeyPlatform:"",privateKeyPlatform:"",signType:"",userId:0}}},created:function(){this.loadTable(),this.loadRouteRole()},methods:{loadTable:function(){this.post("isv.info.page",this.searchFormData,function(e){this.pageInfo=e.data})},loadRouteRole:function(){0===this.roles.length&&this.post("role.listall",{},function(e){this.roles=e.data})},onShowKeys:function(e){this.post("isv.keys.get",{appKey:e.appKey},function(t){var a=this;this.isvKeysDialogVisible=!0,this.$nextTick(function(){Object.assign(a.isvKeysFormData,t.data),a.isvKeysFormData.userId=e.userId})})},onSearchTable:function(){this.loadTable()},onTableUpdate:function(e){var t=this;this.isvDialogTitle="修改ISV",this.isvDialogVisible=!0,this.$nextTick(function(){t.post("isv.info.get",{id:e.id},function(e){for(var t=e.data,a=t.roleList,s=[],o=0;o0?a("el-link",{staticStyle:{"text-decoration":"underline"},attrs:{underline:!1,type:"danger"},on:{click:function(a){return t.onShowErrorDetail(e.row)}}},[t._v("\n "+t._s(e.row.errorCount)+"\n ")]):t._e(),t._v(" "),0===e.row.errorCount?a("span",[t._v("0")]):t._e()]}}])},[a("template",{slot:"header"},[t._v("\n 失败次数\n "),a("el-tooltip",{attrs:{effect:"dark",content:"只统计微服务返回的未知错误,JSR-303验证错误算作成功",placement:"top-end"}},[a("i",{staticClass:"el-icon-question",staticStyle:{cursor:"pointer"}})])],1)],2)],1),t._v(" "),a("el-dialog",{attrs:{title:"错误详情",visible:t.logDetailVisible,width:"60%"},on:{"update:visible":function(e){t.logDetailVisible=e}}},[a("div",{staticStyle:{"overflow-x":"auto"},domProps:{innerHTML:t._s(t.errorMsgDetail)}}),t._v(" "),a("div",{staticClass:"dialog-footer",attrs:{slot:"footer"},slot:"footer"},[a("el-button",{attrs:{type:"primary"},on:{click:function(e){t.logDetailVisible=!1}}},[t._v("关 闭")])],1)])],1)},o=[],r={data:function(){return{searchFormData:{routeId:""},tableData:[],logDetailVisible:!1,errorMsgDetail:""}},created:function(){this.loadTable()},methods:{loadTable:function(){this.post("monitor.data.list",this.searchFormData,function(t){var e=t.data;this.tableData=e.monitorInfoData})},onShowErrorDetail:function(t){var e=t.errorMsgList;this.errorMsgDetail=e.length>0?e.join("
"):"无内容",this.logDetailVisible=!0}}},i=r,n=a("2877"),s=Object(n["a"])(i,l,o,!1,null,null,null);e["default"]=s.exports}}]); \ No newline at end of file diff --git a/sop-admin/sop-admin-server/src/main/resources/public/static/js/chunk-2d0d6219.18813e69.js b/sop-admin/sop-admin-server/src/main/resources/public/static/js/chunk-2d0d6219.18813e69.js new file mode 100644 index 00000000..35387add --- /dev/null +++ b/sop-admin/sop-admin-server/src/main/resources/public/static/js/chunk-2d0d6219.18813e69.js @@ -0,0 +1 @@ +(window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["chunk-2d0d6219"],{"70f0":function(t,e,o){"use strict";o.r(e);var a=function(){var t=this,e=t.$createElement,o=t._self._c||e;return o("div",{staticClass:"app-container"},[o("el-form",{attrs:{size:"mini"}},[o("el-form-item",[o("el-button",{attrs:{type:"primary",icon:"el-icon-upload"},on:{click:t.onAddSdk}},[t._v("发布SDK")])],1)],1),t._v(" "),o("el-table",{attrs:{data:t.list,border:""}},[o("el-table-column",{attrs:{prop:"name",label:"SDK",width:"120"}}),t._v(" "),o("el-table-column",{attrs:{prop:"version",label:"版本",width:"120"}}),t._v(" "),o("el-table-column",{attrs:{prop:"content",label:"下载地址"},scopedSlots:t._u([{key:"default",fn:function(e){return[o("el-link",{attrs:{type:"primary",href:e.row.content,target:"_blank"}},[t._v(t._s(e.row.content))])]}}])}),t._v(" "),o("el-table-column",{attrs:{label:"操作",width:"150",align:"center"},scopedSlots:t._u([{key:"default",fn:function(e){return[o("el-button",{attrs:{type:"text",size:"mini"},on:{click:function(o){return t.onSdkUpdate(e.row)}}},[t._v("编辑")]),t._v(" "),o("el-button",{attrs:{type:"text",size:"mini"},on:{click:function(o){return t.onSdkDelete(e.row)}}},[t._v("删除")])]}}])})],1),t._v(" "),o("el-dialog",{attrs:{title:t.sdkDlgTitle,visible:t.sdkDlgAddShow,"close-on-click-modal":!1},on:{"update:visible":function(e){t.sdkDlgAddShow=e},close:function(e){return t.resetForm("sdkAddForm")}}},[o("el-form",{ref:"sdkAddForm",attrs:{model:t.sdkFormAddData,rules:t.sdkFormRule,"label-width":"100px"}},[o("el-form-item",{attrs:{prop:"name",label:"选择语言"}},[o("el-select",{attrs:{placeholder:"请选择"},model:{value:t.sdkFormAddData.name,callback:function(e){t.$set(t.sdkFormAddData,"name",e)},expression:"sdkFormAddData.name"}},t._l(t.sdkConfigs,function(t){return o("el-option",{key:t.name,attrs:{label:t.name,value:t.name}})}),1)],1),t._v(" "),o("el-form-item",{attrs:{prop:"version",label:"版本"}},[o("el-input",{attrs:{maxlength:"30","show-word-limit":"",placeholder:"如:1.0"},model:{value:t.sdkFormAddData.version,callback:function(e){t.$set(t.sdkFormAddData,"version",e)},expression:"sdkFormAddData.version"}})],1),t._v(" "),o("el-form-item",{attrs:{prop:"content",label:"下载地址"}},[o("el-input",{attrs:{maxlength:"100","show-word-limit":""},model:{value:t.sdkFormAddData.content,callback:function(e){t.$set(t.sdkFormAddData,"content",e)},expression:"sdkFormAddData.content"}})],1),t._v(" "),o("el-form-item",{attrs:{prop:"extContent",label:"调用示例"}},[o("el-input",{attrs:{type:"textarea",rows:12,placeholder:"填写SDK调用示例代码,支持markdown语法"},model:{value:t.sdkFormAddData.extContent,callback:function(e){t.$set(t.sdkFormAddData,"extContent",e)},expression:"sdkFormAddData.extContent"}})],1)],1),t._v(" "),o("div",{staticClass:"dialog-footer",attrs:{slot:"footer"},slot:"footer"},[o("el-button",{on:{click:function(e){t.sdkDlgAddShow=!1}}},[t._v("取 消")]),t._v(" "),o("el-button",{attrs:{type:"primary"},on:{click:t.onSubmitForm}},[t._v("保 存")])],1)],1)],1)},n=[],d=(o("7f7f"),function(){return{id:0,name:"",version:"",content:"",extContent:""}}),s={data:function(){return{searchFormData:{},sdkDownloadConfig:[],sdkConfigs:[],sdkDlgTitle:"",sdkDlgAddShow:!1,sdkFormUpdateData:d(),sdkFormAddData:d(),sdkFormLoading:!1,sdkFormRule:{name:[{required:!0,message:"请选择语言",trigger:"blur"}],version:[{required:!0,message:"请填版本",trigger:"blur"}],content:[{required:!0,message:"请填写URL",trigger:"blur"}],extContent:[{required:!0,message:"请填写调用示例",trigger:"blur"}]},downloadUrl:"",list:[]}},created:function(){this.loadLangSelector(),this.loadTable()},methods:{loadLangSelector:function(){var t=this;this.getFile("static/sdkConfig.json?q=".concat((new Date).getTime()),function(e){t.sdkConfigs=e.langList})},loadTable:function(){var t=this;this.post("isp.sdk.list",this.searchFormData,function(e){t.list=e.data})},onSizeChange:function(t){this.searchFormData.pageSize=t,this.loadTable()},onPageIndexChange:function(t){this.searchFormData.pageIndex=t,this.loadTable()},onAddSdk:function(){this.sdkDlgTitle="添加SDK",this.sdkFormAddData=d(),this.sdkDlgAddShow=!0},onSdkUpdate:function(t){this.sdkDlgTitle="修改SDK",this.sdkFormAddData=d(),Object.assign(this.sdkFormAddData,t),this.sdkDlgAddShow=!0},onSdkDelete:function(t){var e=this;this.confirm("确认要删除【".concat(t.name,"】吗?"),function(o){e.post("isp.sdk.delete",{id:t.id},function(t){o(),e.tip("删除成功"),e.loadTable()})})},onSubmitForm:function(){var t=this;this.$refs.sdkAddForm.validate(function(e){if(e){var o=t.sdkFormAddData.id?"isp.sdk.update":"isp.sdk.add";t.post(o,t.sdkFormAddData,function(){this.sdkDlgAddShow=!1,this.loadTable()})}})}}},l=s,r=o("2877"),i=Object(r["a"])(l,a,n,!1,null,null,null);e["default"]=i.exports}}]); \ No newline at end of file diff --git a/sop-admin/sop-admin-server/src/main/resources/public/static/js/chunk-2d238661.23693be8.js b/sop-admin/sop-admin-server/src/main/resources/public/static/js/chunk-2d238661.23693be8.js new file mode 100644 index 00000000..fc21ef98 --- /dev/null +++ b/sop-admin/sop-admin-server/src/main/resources/public/static/js/chunk-2d238661.23693be8.js @@ -0,0 +1 @@ +(window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["chunk-2d238661"],{fee1:function(e,t,a){"use strict";a.r(t);var r=function(){var e=this,t=e.$createElement,a=e._self._c||t;return a("div",{staticClass:"app-container"},[a("el-form",{staticClass:"demo-form-inline",attrs:{inline:!0,model:e.searchFormData,size:"mini"},nativeOn:{submit:function(e){e.preventDefault()}}},[a("el-form-item",{attrs:{label:"接口名"}},[a("el-input",{staticStyle:{width:"250px"},attrs:{clearable:!0},model:{value:e.searchFormData.routeId,callback:function(t){e.$set(e.searchFormData,"routeId",t)},expression:"searchFormData.routeId"}})],1),e._v(" "),a("el-form-item",{attrs:{label:"serviceId"}},[a("el-input",{staticStyle:{width:"250px"},attrs:{clearable:!0},model:{value:e.searchFormData.serviceId,callback:function(t){e.$set(e.searchFormData,"serviceId",t)},expression:"searchFormData.serviceId"}})],1),e._v(" "),a("el-form-item",[a("el-button",{attrs:{type:"primary",icon:"el-icon-search","native-type":"submit"},on:{click:e.loadTable}},[e._v("查询")])],1)],1),e._v(" "),a("el-table",{attrs:{data:e.pageInfo.list,"row-key":"id",lazy:"","empty-text":"无数据",load:e.loadInstanceMonitorInfo}},[a("el-table-column",{attrs:{fixed:"",prop:"instanceId",label:"网关实例",width:"200"},scopedSlots:e._u([{key:"default",fn:function(t){return[t.row.children?e._e():a("span",[e._v(e._s(t.row.instanceId))])]}}])}),e._v(" "),a("el-table-column",{attrs:{fixed:"",prop:"name",label:"接口名 (版本号)",width:"200"},scopedSlots:e._u([{key:"default",fn:function(t){return[e._v("\n "+e._s(t.row.name+(t.row.version?" ("+t.row.version+")":""))+"\n ")]}}])}),e._v(" "),a("el-table-column",{attrs:{prop:"serviceId",label:"serviceId",width:"150"}}),e._v(" "),a("el-table-column",{attrs:{prop:"maxTime",label:"最大耗时(ms)"}},[a("template",{slot:"header"},[e._v("\n 最大耗时(ms)\n "),a("el-tooltip",{attrs:{content:"耗时计算:签名验证成功后开始,应用返回结果后结束",placement:"top"}},[a("i",{staticClass:"el-icon-question",staticStyle:{cursor:"pointer"}})])],1)],2),e._v(" "),a("el-table-column",{attrs:{prop:"minTime",label:"最小耗时(ms)"}}),e._v(" "),a("el-table-column",{attrs:{prop:"avgTime",label:"平均耗时(ms)"},scopedSlots:e._u([{key:"default",fn:function(t){return[e._v("\n "+e._s(t.row.avgTime.toFixed(1))+"\n ")]}}])}),e._v(" "),a("el-table-column",{attrs:{prop:"totalRequestCount",label:"总调用次数"}}),e._v(" "),a("el-table-column",{attrs:{prop:"successCount",label:"成功次数"}}),e._v(" "),a("el-table-column",{attrs:{prop:"errorCount",label:"失败次数"}}),e._v(" "),a("el-table-column",{attrs:{prop:"unsolvedErrorCount",label:"未解决错误"},scopedSlots:e._u([{key:"default",fn:function(t){return[t.row.unsolvedErrorCount>0?a("el-link",{staticStyle:{"text-decoration":"underline"},attrs:{underline:!1,type:"danger"},on:{click:function(a){return e.onShowErrorDetail(t.row)}}},[e._v("\n "+e._s(t.row.unsolvedErrorCount)+"\n ")]):e._e(),e._v(" "),0===t.row.unsolvedErrorCount?a("span",[e._v("0")]):e._e()]}}])})],1),e._v(" "),a("el-pagination",{staticStyle:{"margin-top":"5px"},attrs:{background:"","current-page":e.searchFormData.pageIndex,"page-size":e.searchFormData.pageSize,"page-sizes":[5,10,20,40],total:e.pageInfo.total,layout:"total, sizes, prev, pager, next"},on:{"size-change":e.onSizeChange,"current-change":e.onPageIndexChange}}),e._v(" "),a("el-dialog",{attrs:{title:e.errorMsgData.title,visible:e.logDetailVisible,"close-on-click-modal":!1,width:"70%"},on:{"update:visible":function(t){e.logDetailVisible=t},close:e.onCloseErrorDlg}},[a("el-alert",{staticClass:"el-alert-tip",attrs:{title:"修复错误后请标记解决",closable:!1}}),e._v(" "),a("el-table",{attrs:{data:e.errorMsgData.pageInfo.rows,"empty-text":"无错误日志"}},[a("el-table-column",{attrs:{type:"expand"},scopedSlots:e._u([{key:"default",fn:function(t){return[a("el-input",{attrs:{type:"textarea",rows:8,readonly:""},model:{value:t.row.errorMsg,callback:function(a){e.$set(t.row,"errorMsg",a)},expression:"props.row.errorMsg"}})]}}])}),e._v(" "),a("el-table-column",{attrs:{prop:"errorMsg",label:"错误内容"},scopedSlots:e._u([{key:"default",fn:function(t){return[t.row.errorMsg.length>50?a("span",[e._v(e._s(t.row.errorMsg.substring(0,50))+"...")]):a("span",[e._v(e._s(t.row.errorMsg))])]}}])}),e._v(" "),a("el-table-column",{attrs:{prop:"instanceId",label:"实例ID",width:"150px"}}),e._v(" "),a("el-table-column",{attrs:{prop:"count",label:"报错次数",width:"80px"}}),e._v(" "),a("el-table-column",{attrs:{prop:"gmtModified",label:"报错时间",width:"160px"}}),e._v(" "),a("el-table-column",{attrs:{label:"操作",width:"120"},scopedSlots:e._u([{key:"default",fn:function(t){return[a("el-link",{attrs:{type:"primary"},on:{click:function(a){return e.onSolve(t.row)}}},[e._v("标记解决")])]}}])})],1),e._v(" "),a("el-pagination",{staticStyle:{"margin-top":"5px"},attrs:{background:"","current-page":e.errorMsgFormData.pageIndex,"page-size":e.errorMsgFormData.pageSize,"page-sizes":[5,10,20,40],total:e.errorMsgData.pageInfo.total,layout:"total, sizes, prev, pager, next"},on:{"size-change":e.onErrorSizeChange,"current-change":e.onErrorPageIndexChange}}),e._v(" "),a("div",{staticClass:"dialog-footer",attrs:{slot:"footer"},slot:"footer"},[a("el-button",{attrs:{type:"primary"},on:{click:function(t){e.logDetailVisible=!1}}},[e._v("关 闭")])],1)],1)],1)},o=[],n=(a("7f7f"),{data:function(){return{searchFormData:{routeId:"",serviceId:"",pageIndex:1,pageSize:20},pageInfo:{list:[],total:0},logDetailVisible:!1,errorMsgFormData:{routeId:"",instanceId:"",pageIndex:1,pageSize:5},errorMsgData:{title:"",name:"",version:"",pageInfo:{rows:[],total:0}}}},created:function(){this.loadTable()},methods:{loadTable:function(){this.post("monitornew.data.page",this.searchFormData,function(e){this.pageInfo=e.data})},loadErrorData:function(){this.post("monitornew.error.page",this.errorMsgFormData,function(e){this.errorMsgData.pageInfo=e.data,this.logDetailVisible=!0})},loadInstanceMonitorInfo:function(e,t,a){this.post("monitornew.routeid.data.get",{routeId:e.routeId},function(t){var r=t.data;e.children=r,a(r)})},onShowErrorDetail:function(e){this.errorMsgData.title="错误日志 ".concat(e.name,"(").concat(e.version,")"),this.errorMsgData.name=e.name,this.errorMsgData.version=e.version,this.errorMsgFormData.routeId=e.routeId,this.errorMsgFormData.instanceId=e.instanceId,this.loadErrorData()},onSolve:function(e){this.confirm("确认标记为已解决吗?",function(t){this.post("monitornew.error.solve",{routeId:e.routeId,errorId:e.errorId},function(e){t(),this.errorMsgFormData.pageIndex=1,this.loadErrorData()})})},onCloseErrorDlg:function(){this.loadTable()},onErrorSizeChange:function(e){this.errorMsgFormData.pageSize=e,this.loadErrorData()},onErrorPageIndexChange:function(e){this.errorMsgFormData.pageIndex=e,this.loadErrorData()},onSizeChange:function(e){this.searchFormData.pageSize=e,this.loadTable()},onPageIndexChange:function(e){this.searchFormData.pageIndex=e,this.loadTable()},onAdd:function(){this.dialogTitle="新增IP",this.dialogVisible=!0,this.dialogFormData.id=0}}}),l=n,s=a("2877"),i=Object(s["a"])(l,r,o,!1,null,null,null);t["default"]=i.exports}}]); \ No newline at end of file diff --git a/sop-admin/sop-admin-server/src/main/resources/public/static/sdkConfig.json b/sop-admin/sop-admin-server/src/main/resources/public/static/sdkConfig.json new file mode 100644 index 00000000..bdd7e592 --- /dev/null +++ b/sop-admin/sop-admin-server/src/main/resources/public/static/sdkConfig.json @@ -0,0 +1,24 @@ +{ + "langList": [ + { + "name": "Java" + }, + { + "name": "C#" + }, + { + "name": "C++" + }, + { + "name": "Go" + },{ + "name": "NodeJS" + }, + { + "name": "Python" + }, + { + "name": "Rust" + } + ] +} diff --git a/sop-admin/sop-admin-server/src/test/java/com/gitee/sop/adminserver/AccountTest.java b/sop-admin/sop-admin-server/src/test/java/com/gitee/sop/adminserver/AccountTest.java index a9427f7a..dda03836 100644 --- a/sop-admin/sop-admin-server/src/test/java/com/gitee/sop/adminserver/AccountTest.java +++ b/sop-admin/sop-admin-server/src/test/java/com/gitee/sop/adminserver/AccountTest.java @@ -2,18 +2,16 @@ package com.gitee.sop.adminserver; import junit.framework.TestCase; import org.apache.commons.codec.digest.DigestUtils; -import org.junit.jupiter.api.Test; /** * @author tanghc */ -public class AccountTest extends TestCase { +public class AccountTest extends TestCase { /* 生成密码 */ - @Test - public void genPwd() { + public void testGen() { String username = "admin"; String password = "123456"; String save_to_db = DigestUtils.md5Hex(username + DigestUtils.md5Hex(password) + username); diff --git a/sop-admin/sop-admin-server/src/test/java/com/gitee/sop/adminserver/SopAdminServerApplicationTests.java b/sop-admin/sop-admin-server/src/test/java/com/gitee/sop/adminserver/SopAdminServerApplicationTests.java index 02573978..deb93a38 100644 --- a/sop-admin/sop-admin-server/src/test/java/com/gitee/sop/adminserver/SopAdminServerApplicationTests.java +++ b/sop-admin/sop-admin-server/src/test/java/com/gitee/sop/adminserver/SopAdminServerApplicationTests.java @@ -1,6 +1,5 @@ package com.gitee.sop.adminserver; -import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; @@ -9,8 +8,4 @@ import org.springframework.test.context.junit4.SpringRunner; @SpringBootTest public class SopAdminServerApplicationTests { - @Test - public void contextLoads() { - } - } diff --git a/sop-admin/sop-admin-vue/.env.development b/sop-admin/sop-admin-vue/.env.development index 852fb60f..ea860330 100644 --- a/sop-admin/sop-admin-vue/.env.development +++ b/sop-admin/sop-admin-vue/.env.development @@ -1,8 +1,8 @@ # just a flag ENV = 'development' -# base api -VUE_APP_BASE_API = 'http://localhost:8082/api' +# base api,末尾没有/ +VUE_APP_BASE_API = 'http://localhost:8082' # vue-cli uses the VUE_CLI_BABEL_TRANSPILE_MODULES environment variable, # to control whether the babel-plugin-dynamic-import-node plugin is enabled. diff --git a/sop-admin/sop-admin-vue/.env.production b/sop-admin/sop-admin-vue/.env.production index 07d391ea..9c39985d 100644 --- a/sop-admin/sop-admin-vue/.env.production +++ b/sop-admin/sop-admin-vue/.env.production @@ -1,6 +1,8 @@ # just a flag ENV = 'production' -# base api -VUE_APP_BASE_API = '/api' +# base api, +# 如果要前后端分离,这里填 http(s)://ip:port 末尾没有/ +# 如:http://www.aaa.com, http://www.bbb.com:7777 +VUE_APP_BASE_API = '' diff --git a/sop-admin/sop-admin-vue/README.md b/sop-admin/sop-admin-vue/README.md index c2deebeb..7bae156a 100644 --- a/sop-admin/sop-admin-vue/README.md +++ b/sop-admin/sop-admin-vue/README.md @@ -1,5 +1,7 @@ # SOP Admin 前端vue实现 +此工程基于前端脚手架:`https://github.com/PanJiaChen/vue-element-admin` + 前提:先安装好npm,[npm安装教程](https://blog.csdn.net/zhangwenwu2/article/details/52778521) 1. 启动服务端程序,运行`SopAdminServerApplication.java` @@ -14,5 +16,11 @@ 如果想要把vue打包放到服务端,步骤如下: -- 执行`npm run build:prod`进行打包,结果在dest下 -- 打包完成后,把dest中的所有文件,放到`sop-admin-server/src/main/resources/public`下 \ No newline at end of file +- 执行`npm run build:prod`进行打包,结果在`dist`下 +- 打包完成后,把dest中的所有文件,放到`sop-admin-server/src/main/resources/public`下 + +## 前后端分离部署 + +- 修改`.env.production`,指定服务端地址 +- 执行`npm run build:prod`进行打包,结果在`dist`下 +- 把`dist`中的文件放到静态服务器上 \ No newline at end of file diff --git a/sop-admin/sop-admin-vue/public/static/sdkConfig.json b/sop-admin/sop-admin-vue/public/static/sdkConfig.json new file mode 100644 index 00000000..bdd7e592 --- /dev/null +++ b/sop-admin/sop-admin-vue/public/static/sdkConfig.json @@ -0,0 +1,24 @@ +{ + "langList": [ + { + "name": "Java" + }, + { + "name": "C#" + }, + { + "name": "C++" + }, + { + "name": "Go" + },{ + "name": "NodeJS" + }, + { + "name": "Python" + }, + { + "name": "Rust" + } + ] +} diff --git a/sop-admin/sop-admin-vue/src/layout/components/Sidebar/index.vue b/sop-admin/sop-admin-vue/src/layout/components/Sidebar/index.vue index da39034f..c40c203c 100644 --- a/sop-admin/sop-admin-vue/src/layout/components/Sidebar/index.vue +++ b/sop-admin/sop-admin-vue/src/layout/components/Sidebar/index.vue @@ -10,6 +10,7 @@ :unique-opened="false" :active-text-color="variables.menuActiveText" :collapse-transition="false" + :default-openeds="opened" mode="vertical" > @@ -33,6 +34,13 @@ export default { routes() { return this.$router.options.routes }, + opened() { + return this.routes.filter(route => { + return route.meta && route.meta.open + }).map(route => { + return route.path + }) + }, activeMenu() { const route = this.$route const { meta, path } = route diff --git a/sop-admin/sop-admin-vue/src/router/index.js b/sop-admin/sop-admin-vue/src/router/index.js index 5476d797..86591ba6 100644 --- a/sop-admin/sop-admin-vue/src/router/index.js +++ b/sop-admin/sop-admin-vue/src/router/index.js @@ -59,7 +59,7 @@ export const constantRoutes = [ path: '/service', component: Layout, name: 'Service', - meta: { title: '服务管理', icon: 'example' }, + meta: { title: '服务管理', icon: 'example', open: true }, children: [ { path: 'list', @@ -76,7 +76,7 @@ export const constantRoutes = [ { path: 'monitor', name: 'Monitor', - component: () => import('@/views/service/monitor'), + component: () => import('@/views/service/monitorNew'), meta: { title: '路由监控' } }, { @@ -90,6 +90,12 @@ export const constantRoutes = [ name: 'Blacklist', component: () => import('@/views/service/ipBlacklist'), meta: { title: 'IP黑名单' } + }, + { + path: 'sdk', + name: 'Sdk', + component: () => import('@/views/service/sdk'), + meta: { title: 'SDK管理' } } ] }, @@ -98,7 +104,7 @@ export const constantRoutes = [ path: '/isv', component: Layout, name: 'Isv', - meta: { title: 'ISV管理', icon: 'user' }, + meta: { title: 'ISV管理', icon: 'user', open: true }, children: [ { path: 'list', diff --git a/sop-admin/sop-admin-vue/src/utils/global.js b/sop-admin/sop-admin-vue/src/utils/global.js index 46a95251..9e2a7781 100644 --- a/sop-admin/sop-admin-vue/src/utils/global.js +++ b/sop-admin/sop-admin-vue/src/utils/global.js @@ -7,7 +7,7 @@ import { getToken, removeToken } from './auth' // 创建axios实例 const client = axios.create({ - baseURL: process.env.VUE_APP_BASE_API, // api 的 base_url + baseURL: process.env.VUE_APP_BASE_API + '/api', // api 的 base_url timeout: 60000 // 请求超时时间,60秒 }) @@ -94,6 +94,17 @@ Object.assign(Vue.prototype, { } }).catch(function() {}) }, + /** + * 文件必须放在public下面 + * @param path 相对于public文件夹路径,如文件在public/static/sign.md,填:static/sign.md + * @param callback 回调函数,函数参数是文件内容 + */ + getFile: function(path, callback) { + axios.get(path) + .then(function(response) { + callback.call(this, response.data) + }) + }, downloadText(filename, text) { const element = document.createElement('a') element.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(text)) diff --git a/sop-admin/sop-admin-vue/src/views/isv/index.vue b/sop-admin/sop-admin-vue/src/views/isv/index.vue index 1c4e519c..acab263f 100644 --- a/sop-admin/sop-admin-vue/src/views/isv/index.vue +++ b/sop-admin/sop-admin-vue/src/views/isv/index.vue @@ -32,13 +32,28 @@ + + + + @@ -133,33 +147,26 @@ size="mini" class="key-view" > - - - - + {{ isvKeysFormData.appKey }} PKCS8(JAVA适用) PKCS1(非JAVA适用) - + {{ isvKeysFormData.secret }} - + - + - + @@ -221,7 +228,8 @@ export default { privateKeyIsv: '', publicKeyPlatform: '', privateKeyPlatform: '', - signType: '' + signType: '', + userId: 0 } } }, @@ -247,6 +255,7 @@ export default { this.isvKeysDialogVisible = true this.$nextTick(() => { Object.assign(this.isvKeysFormData, resp.data) + this.isvKeysFormData.userId = row.userId }) }) }, @@ -319,9 +328,6 @@ export default { this.isvDialogFormData.status = 1 this.isvDialogFormData.roleCode = [] }, - selfLabel: function(lab) { - return '★ ' + lab - }, roleRender: function(row) { const html = [] const roleList = row.roleList diff --git a/sop-admin/sop-admin-vue/src/views/service/monitorNew.vue b/sop-admin/sop-admin-vue/src/views/service/monitorNew.vue new file mode 100644 index 00000000..35b2d6f9 --- /dev/null +++ b/sop-admin/sop-admin-vue/src/views/service/monitorNew.vue @@ -0,0 +1,283 @@ + + + diff --git a/sop-admin/sop-admin-vue/src/views/service/sdk.vue b/sop-admin/sop-admin-vue/src/views/service/sdk.vue new file mode 100644 index 00000000..477d6e71 --- /dev/null +++ b/sop-admin/sop-admin-vue/src/views/service/sdk.vue @@ -0,0 +1,179 @@ + + diff --git a/sop-auth/pom.xml b/sop-auth/pom.xml index 6d97264e..14e1721e 100644 --- a/sop-auth/pom.xml +++ b/sop-auth/pom.xml @@ -5,7 +5,7 @@ com.gitee.sop sop-parent - 4.0.3-SNAPSHOT + 4.3.2-SNAPSHOT ../pom.xml @@ -25,7 +25,7 @@ com.gitee.sop sop-service-common - 4.0.3-SNAPSHOT + ${project.version} @@ -38,7 +38,7 @@ com.gitee.sop sdk-java - 4.0.3-SNAPSHOT + 4.3.2-SNAPSHOT diff --git a/sop-common/pom.xml b/sop-common/pom.xml index 094b1ac6..6f715011 100644 --- a/sop-common/pom.xml +++ b/sop-common/pom.xml @@ -6,7 +6,7 @@ com.gitee.sop sop-parent - 4.0.3-SNAPSHOT + 4.3.2-SNAPSHOT ../pom.xml diff --git a/sop-common/sop-bridge-eureka/pom.xml b/sop-common/sop-bridge-eureka/pom.xml index 5b8c7098..a0690049 100644 --- a/sop-common/sop-bridge-eureka/pom.xml +++ b/sop-common/sop-bridge-eureka/pom.xml @@ -4,21 +4,18 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> com.gitee.sop - sop-parent - 4.0.3-SNAPSHOT - ../../pom.xml + sop-common + 4.3.2-SNAPSHOT + ../pom.xml - 4.0.0 - 4.0.3-SNAPSHOT - sop-bridge-eureka com.gitee.sop sop-gateway-common - 4.0.3-SNAPSHOT + ${project.version} diff --git a/sop-common/sop-bridge-nacos/pom.xml b/sop-common/sop-bridge-nacos/pom.xml index bf8f911d..8e44c1ad 100644 --- a/sop-common/sop-bridge-nacos/pom.xml +++ b/sop-common/sop-bridge-nacos/pom.xml @@ -4,21 +4,18 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> com.gitee.sop - sop-parent - 4.0.3-SNAPSHOT - ../../pom.xml + sop-common + 4.3.2-SNAPSHOT + ../pom.xml - 4.0.0 - 4.0.3-SNAPSHOT - sop-bridge-nacos com.gitee.sop sop-gateway-common - 4.0.3-SNAPSHOT + ${project.version} diff --git a/sop-common/sop-bridge-nacos/src/main/java/com/gitee/sop/bridge/route/NacosRegistryListener.java b/sop-common/sop-bridge-nacos/src/main/java/com/gitee/sop/bridge/route/NacosRegistryListener.java index 48058ad4..7dc641d4 100644 --- a/sop-common/sop-bridge-nacos/src/main/java/com/gitee/sop/bridge/route/NacosRegistryListener.java +++ b/sop-common/sop-bridge-nacos/src/main/java/com/gitee/sop/bridge/route/NacosRegistryListener.java @@ -13,6 +13,7 @@ import com.gitee.sop.gatewaycommon.route.ServiceHolder; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang.math.NumberUtils; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; import org.springframework.context.ApplicationEvent; import org.springframework.util.CollectionUtils; @@ -34,6 +35,9 @@ public class NacosRegistryListener extends BaseRegistryListener { private volatile Set cacheServices = new HashSet<>(); + @Value("${nacos.discovery.group:${spring.cloud.nacos.discovery.group:DEFAULT_GROUP}}") + private String nacosGroup; + @Autowired private NacosDiscoveryProperties nacosDiscoveryProperties; @@ -81,7 +85,7 @@ public class NacosRegistryListener extends BaseRegistryListener { NamingService namingService = nacosDiscoveryProperties.namingServiceInstance(); ListView servicesOfServer = null; try { - servicesOfServer = namingService.getServicesOfServer(1, Integer.MAX_VALUE); + servicesOfServer = namingService.getServicesOfServer(1, Integer.MAX_VALUE, nacosGroup); } catch (NacosException e) { log.error("namingService.getServicesOfServer()错误", e); } @@ -95,7 +99,7 @@ public class NacosRegistryListener extends BaseRegistryListener { List allInstances; try { // 获取服务实例 - allInstances = namingService.getAllInstances(serviceName); + allInstances = namingService.getAllInstances(serviceName, nacosGroup); } catch (NacosException e) { log.error("namingService.getAllInstances(serviceName)错误,serviceName:{}", serviceName, e); return null; diff --git a/sop-common/sop-gateway-common/pom.xml b/sop-common/sop-gateway-common/pom.xml index c00149a7..36383cae 100644 --- a/sop-common/sop-gateway-common/pom.xml +++ b/sop-common/sop-gateway-common/pom.xml @@ -4,14 +4,13 @@ com.gitee.sop - sop-parent - 4.0.3-SNAPSHOT - ../../pom.xml + sop-common + 4.3.2-SNAPSHOT + ../pom.xml 4.0.0 sop-gateway-common - 4.0.3-SNAPSHOT jar @@ -54,6 +53,11 @@ fastjson + + org.springframework.cloud + spring-cloud-gateway-webflux + + com.alibaba.cloud diff --git a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/bean/ApiConfig.java b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/bean/ApiConfig.java index 4607b73b..93659911 100644 --- a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/bean/ApiConfig.java +++ b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/bean/ApiConfig.java @@ -28,12 +28,12 @@ import com.gitee.sop.gatewaycommon.result.ResultExecutorForGateway; import com.gitee.sop.gatewaycommon.secret.CacheIsvManager; import com.gitee.sop.gatewaycommon.secret.IsvManager; import com.gitee.sop.gatewaycommon.validate.ApiEncrypter; -import com.gitee.sop.gatewaycommon.validate.ApiSigner; import com.gitee.sop.gatewaycommon.validate.ApiValidator; import com.gitee.sop.gatewaycommon.validate.Encrypter; import com.gitee.sop.gatewaycommon.validate.Signer; import com.gitee.sop.gatewaycommon.validate.TokenValidator; import com.gitee.sop.gatewaycommon.validate.Validator; +import com.gitee.sop.gatewaycommon.validate.alipay.AlipaySigner; import lombok.Data; import org.apache.commons.lang3.StringUtils; @@ -74,7 +74,7 @@ public class ApiConfig { /** * 签名工具 */ - private Signer signer = new ApiSigner(); + private Signer signer = new AlipaySigner(); /** * 验证 diff --git a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/bean/GatewayPredicateDefinition.java b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/bean/GatewayPredicateDefinition.java index 06a6c0e5..db9b4d62 100644 --- a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/bean/GatewayPredicateDefinition.java +++ b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/bean/GatewayPredicateDefinition.java @@ -3,7 +3,6 @@ package com.gitee.sop.gatewaycommon.bean; import lombok.Data; import org.springframework.util.StringUtils; -import javax.validation.ValidationException; import java.util.LinkedHashMap; import java.util.Map; @@ -24,7 +23,7 @@ public class GatewayPredicateDefinition { public GatewayPredicateDefinition(String text) { int eqIdx = text.indexOf(61); if (eqIdx <= 0) { - throw new ValidationException("Unable to parse GatewayPredicateDefinition text '" + text + "', must be of the form name=value"); + throw new RuntimeException("Unable to parse GatewayPredicateDefinition text '" + text + "', must be of the form name=value"); } else { this.setName(text.substring(0, eqIdx)); String[] params = StringUtils.tokenizeToStringArray(text.substring(eqIdx + 1), ","); diff --git a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/bean/LRUCache.java b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/bean/LRUCache.java new file mode 100644 index 00000000..9ea136d7 --- /dev/null +++ b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/bean/LRUCache.java @@ -0,0 +1,43 @@ +package com.gitee.sop.gatewaycommon.bean; + +import java.util.Collection; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.function.Function; + +/** + * LRU缓存 + * LinkedHashMap 本身内部有一个触发条件则自动执行的方法:删除最老元素(最近最少使用的元素) + * 由于最近最少使用元素是 LinkedHashMap 内部处理 + * 故我们不再需要维护 最近访问元素放在链尾,get 时直接访问/ put 时直接存储 + * created by Ethan-Walker on 2019/2/16 + */ +public class LRUCache { + private final Map map; + + public LRUCache(int capacity) { + map = new LinkedHashMap(capacity, 0.75f, true) { + @Override + protected boolean removeEldestEntry(Map.Entry eldest) { + // 容量大于capacity 时就删除 + return size() > capacity; + } + }; + } + public V get(K key) { + return map.get(key); + } + + public V computeIfAbsent(K key, + Function mappingFunction) { + return map.computeIfAbsent(key, mappingFunction); + } + + public V put(K key, V value) { + return map.put(key, value); + } + + public Collection values() { + return map.values(); + } +} \ No newline at end of file diff --git a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/bean/TargetRoute.java b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/bean/TargetRoute.java index 32ea5569..a2dfa393 100644 --- a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/bean/TargetRoute.java +++ b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/bean/TargetRoute.java @@ -19,4 +19,6 @@ public interface TargetRoute { */ RouteDefinition getRouteDefinition(); + String getFullPath(); + } diff --git a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/config/AbstractConfiguration.java b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/config/AbstractConfiguration.java index 170db99b..e00b1d56 100644 --- a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/config/AbstractConfiguration.java +++ b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/config/AbstractConfiguration.java @@ -5,7 +5,6 @@ import com.gitee.sop.gatewaycommon.bean.ApiContext; import com.gitee.sop.gatewaycommon.bean.BeanInitializer; import com.gitee.sop.gatewaycommon.bean.SpringContext; import com.gitee.sop.gatewaycommon.gateway.loadbalancer.NacosServerIntrospector; -import com.gitee.sop.gatewaycommon.interceptor.MonitorRouteInterceptor; import com.gitee.sop.gatewaycommon.interceptor.RouteInterceptor; import com.gitee.sop.gatewaycommon.limit.LimitManager; import com.gitee.sop.gatewaycommon.manager.EnvGrayManager; @@ -17,17 +16,20 @@ import com.gitee.sop.gatewaycommon.manager.LimitConfigManager; import com.gitee.sop.gatewaycommon.manager.RouteConfigManager; import com.gitee.sop.gatewaycommon.manager.RouteRepositoryContext; import com.gitee.sop.gatewaycommon.message.ErrorFactory; +import com.gitee.sop.gatewaycommon.monitor.MonitorManager; import com.gitee.sop.gatewaycommon.param.ParameterFormatter; import com.gitee.sop.gatewaycommon.route.RegistryListener; import com.gitee.sop.gatewaycommon.route.ServiceListener; import com.gitee.sop.gatewaycommon.route.ServiceRouteListener; import com.gitee.sop.gatewaycommon.secret.IsvManager; +import com.gitee.sop.gatewaycommon.sync.SopAsyncConfigurer; import com.gitee.sop.gatewaycommon.util.RouteInterceptorUtil; import com.gitee.sop.gatewaycommon.validate.SignConfig; import com.gitee.sop.gatewaycommon.validate.Validator; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.BeansException; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.ApplicationArguments; import org.springframework.boot.ApplicationRunner; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; @@ -156,6 +158,17 @@ public class AbstractConfiguration implements ApplicationContextAware, Applicati return ApiConfig.getInstance().getParameterFormatter(); } + @Bean + public SopAsyncConfigurer sopAsyncConfigurer(@Value("${sop.monitor-route-interceptor.thread-pool-size:4}") + int threadPoolSize) { + return new SopAsyncConfigurer("gatewayAsync", threadPoolSize); + } + + @Bean + @ConditionalOnMissingBean + public MonitorManager monitorManager() { + return new MonitorManager(); + } /** * 跨域过滤器,gateway采用react形式,需要使用reactive包下的UrlBasedCorsConfigurationSource @@ -231,7 +244,6 @@ public class AbstractConfiguration implements ApplicationContextAware, Applicati protected void initRouteInterceptor() { Map routeInterceptorMap = applicationContext.getBeansOfType(RouteInterceptor.class); Collection routeInterceptors = new ArrayList<>(routeInterceptorMap.values()); - routeInterceptors.add(new MonitorRouteInterceptor()); RouteInterceptorUtil.addInterceptors(routeInterceptors); } diff --git a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/config/SopGatewayEnvironmentPostProcessor.java b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/config/SopGatewayEnvironmentPostProcessor.java index 6f12412c..6161ecb8 100644 --- a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/config/SopGatewayEnvironmentPostProcessor.java +++ b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/config/SopGatewayEnvironmentPostProcessor.java @@ -36,4 +36,5 @@ public class SopGatewayEnvironmentPostProcessor implements EnvironmentPostProces throw new IllegalStateException("加载配置文件失败" + resource, ex); } } + } diff --git a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/gateway/ServerWebExchangeUtil.java b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/gateway/ServerWebExchangeUtil.java index e73688bf..303650ae 100644 --- a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/gateway/ServerWebExchangeUtil.java +++ b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/gateway/ServerWebExchangeUtil.java @@ -3,7 +3,6 @@ package com.gitee.sop.gatewaycommon.gateway; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; import com.gitee.sop.gatewaycommon.bean.SopConstants; -import com.gitee.sop.gatewaycommon.gateway.codec.MessageReaderFactory; import com.gitee.sop.gatewaycommon.gateway.common.FileUploadHttpServletRequest; import com.gitee.sop.gatewaycommon.gateway.common.RequestContentDataExtractor; import com.gitee.sop.gatewaycommon.gateway.common.SopServerHttpRequestDecorator; @@ -17,7 +16,7 @@ import org.apache.commons.lang3.StringUtils; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.http.MediaType; -import org.springframework.http.codec.HttpMessageReader; +import org.springframework.http.codec.ServerCodecConfigurer; import org.springframework.http.converter.FormHttpMessageConverter; import org.springframework.http.server.reactive.ServerHttpRequest; import org.springframework.util.MultiValueMap; @@ -47,8 +46,6 @@ public class ServerWebExchangeUtil { private static final FormHttpMessageConverter formHttpMessageConverter = new FormHttpMessageConverter(); - private static final List> messageReaders = MessageReaderFactory.build(); - /** * 重定向 * @@ -61,7 +58,7 @@ public class ServerWebExchangeUtil { .mutate(); ServerHttpRequest newRequest = builder .header(ParamNames.HEADER_VERSION_NAME, forwardInfo.getVersion()) - .path(forwardInfo.getPath()).build(); + .path(forwardInfo.getFullPath()).build(); return exchange.mutate().request(newRequest).build(); } @@ -70,10 +67,11 @@ public class ServerWebExchangeUtil { * 构建一个接受请求体的request * * @param exchange exchange + * @param codecConfigurer codecConfigurer * @return 返回ServerRequest */ - public static ServerRequest createReadBodyRequest(ServerWebExchange exchange) { - return ServerRequest.create(exchange, messageReaders); + public static ServerRequest createReadBodyRequest(ServerWebExchange exchange, ServerCodecConfigurer codecConfigurer) { + return ServerRequest.create(exchange, codecConfigurer.getReaders()); } public static String getRestfulPath(String path, String prefix) { diff --git a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/gateway/codec/MessageReaderFactory.java b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/gateway/codec/MessageReaderFactory.java deleted file mode 100644 index 95d6ff56..00000000 --- a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/gateway/codec/MessageReaderFactory.java +++ /dev/null @@ -1,46 +0,0 @@ -package com.gitee.sop.gatewaycommon.gateway.codec; - -import com.gitee.sop.gatewaycommon.manager.EnvironmentKeys; -import org.springframework.core.codec.AbstractDataBufferDecoder; -import org.springframework.http.codec.HttpMessageReader; -import org.springframework.util.ReflectionUtils; -import org.springframework.web.reactive.function.server.HandlerStrategies; - -import java.lang.reflect.Method; -import java.util.List; - -/** - * @author tanghc - */ -public class MessageReaderFactory { - - public static final String METHOD_SET_MAX_IN_MEMORY_SIZE = "setMaxInMemorySize"; - public static final String METHOD_GET_DECODER = "getDecoder"; - public static final int DEFAULT_SIZE = 256 * 1024; - - public static List> build() { - String maxInMemorySizeValueStr = EnvironmentKeys.MAX_IN_MEMORY_SIZE.getValue(); - int maxInMemorySizeValue = Integer.parseInt(maxInMemorySizeValueStr); - List> messageReaders = HandlerStrategies.withDefaults().messageReaders(); - if (DEFAULT_SIZE == maxInMemorySizeValue) { - return messageReaders; - } - // 设置POST缓存大小 - for (HttpMessageReader httpMessageReader : messageReaders) { - Method[] methods = ReflectionUtils.getDeclaredMethods(httpMessageReader.getClass()); - for (Method method : methods) { - String methodName = method.getName(); - if (METHOD_SET_MAX_IN_MEMORY_SIZE.equals(methodName)) { - ReflectionUtils.invokeMethod(method, httpMessageReader, maxInMemorySizeValue); - } else if (METHOD_GET_DECODER.equals(methodName)) { - Object decoder = ReflectionUtils.invokeMethod(method, httpMessageReader); - if (decoder instanceof AbstractDataBufferDecoder) { - AbstractDataBufferDecoder bufferDecoder = (AbstractDataBufferDecoder) decoder; - bufferDecoder.setMaxInMemorySize(maxInMemorySizeValue); - } - } - } - } - return messageReaders; - } -} diff --git a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/gateway/configuration/AlipayGatewayConfiguration.java b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/gateway/configuration/AlipayGatewayConfiguration.java index b84b6670..33ceeef3 100644 --- a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/gateway/configuration/AlipayGatewayConfiguration.java +++ b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/gateway/configuration/AlipayGatewayConfiguration.java @@ -1,8 +1,5 @@ package com.gitee.sop.gatewaycommon.gateway.configuration; -import com.gitee.sop.gatewaycommon.bean.ApiContext; -import com.gitee.sop.gatewaycommon.validate.alipay.AlipaySigner; - /** * 具备支付宝开放平台能力配置 https://docs.open.alipay.com/api * @@ -10,7 +7,4 @@ import com.gitee.sop.gatewaycommon.validate.alipay.AlipaySigner; */ public class AlipayGatewayConfiguration extends BaseGatewayConfiguration { - static { - ApiContext.getApiConfig().setSigner(new AlipaySigner()); - } } diff --git a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/gateway/configuration/BaseGatewayConfiguration.java b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/gateway/configuration/BaseGatewayConfiguration.java index c39466b1..168194bf 100644 --- a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/gateway/configuration/BaseGatewayConfiguration.java +++ b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/gateway/configuration/BaseGatewayConfiguration.java @@ -1,10 +1,6 @@ package com.gitee.sop.gatewaycommon.gateway.configuration; import com.gitee.sop.gatewaycommon.bean.ApiConfig; -import com.gitee.sop.gatewaycommon.gateway.controller.ConfigChannelController; -import com.gitee.sop.gatewaycommon.gateway.controller.ErrorLogController; -import com.gitee.sop.gatewaycommon.gateway.controller.GatewayController; -import com.gitee.sop.gatewaycommon.gateway.controller.GatewayMonitorController; import com.gitee.sop.gatewaycommon.gateway.filter.GatewayModifyResponseGatewayFilter; import com.gitee.sop.gatewaycommon.gateway.filter.IndexFilter; import com.gitee.sop.gatewaycommon.gateway.filter.LimitFilter; @@ -50,26 +46,6 @@ public class BaseGatewayConfiguration extends AbstractConfiguration { return new IndexFilter(); } -// @Bean -// public GatewayController gatewayErrorController() { -// return new GatewayController(); -// } -// -// @Bean -// public ConfigChannelController configChannelController() { -// return new ConfigChannelController(); -// } -// -// @Bean -// public ErrorLogController errorLogController() { -// return new ErrorLogController(); -// } -// -// @Bean -// public GatewayMonitorController gatewayMonitorController() { -// return new GatewayMonitorController(); -// } - /** * 自定义异常处理[@@]注册Bean时依赖的Bean,会从容器中直接获取,所以直接注入即可 * @@ -80,7 +56,7 @@ public class BaseGatewayConfiguration extends AbstractConfiguration { @Bean @Order(Ordered.HIGHEST_PRECEDENCE) public ErrorWebExceptionHandler sopErrorWebExceptionHandler(ObjectProvider> viewResolversProvider, - ServerCodecConfigurer serverCodecConfigurer) { + ServerCodecConfigurer serverCodecConfigurer) { GatewayExceptionHandler jsonExceptionHandler = new GatewayExceptionHandler(); jsonExceptionHandler.setViewResolvers(viewResolversProvider.getIfAvailable(Collections::emptyList)); diff --git a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/gateway/controller/ConfigChannelController.java b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/gateway/controller/ConfigChannelController.java index 1ccc64e1..47877065 100644 --- a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/gateway/controller/ConfigChannelController.java +++ b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/gateway/controller/ConfigChannelController.java @@ -14,7 +14,9 @@ import com.gitee.sop.gatewaycommon.manager.RouteConfigManager; import com.gitee.sop.gatewaycommon.secret.IsvManager; import com.gitee.sop.gatewaycommon.util.RequestUtil; import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.codec.ServerCodecConfigurer; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.reactive.function.server.ServerRequest; @@ -45,9 +47,12 @@ public class ConfigChannelController { @Value("${sop.secret}") private String secret; + @Autowired + private ServerCodecConfigurer codecConfigurer; + @PostMapping("/sop/configChannelMsg") public Mono configChannel(ServerWebExchange exchange) { - ServerRequest serverRequest = ServerWebExchangeUtil.createReadBodyRequest(exchange); + ServerRequest serverRequest = ServerWebExchangeUtil.createReadBodyRequest(exchange, codecConfigurer); // 读取请求体中的内容 return serverRequest.bodyToMono(String.class) .flatMap(requestJson -> { diff --git a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/gateway/controller/ErrorLogController.java b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/gateway/controller/ErrorLogController.java deleted file mode 100644 index 978e533a..00000000 --- a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/gateway/controller/ErrorLogController.java +++ /dev/null @@ -1,21 +0,0 @@ -package com.gitee.sop.gatewaycommon.gateway.controller; - -import com.gitee.sop.gatewaycommon.bean.BaseErrorLogController; -import com.gitee.sop.gatewaycommon.param.ApiParam; -import org.springframework.web.bind.annotation.RestController; -import org.springframework.web.server.ServerWebExchange; - -import java.util.Map; - -/** - * @author tanghc - */ -@RestController -public class ErrorLogController extends BaseErrorLogController { - - @Override - protected ApiParam getApiParam(ServerWebExchange request) { - Map params = request.getRequest().getQueryParams().toSingleValueMap(); - return ApiParam.build(params); - } -} diff --git a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/gateway/controller/GatewayController.java b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/gateway/controller/GatewayController.java index 8836ad13..6b5ba31a 100644 --- a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/gateway/controller/GatewayController.java +++ b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/gateway/controller/GatewayController.java @@ -5,6 +5,8 @@ import com.gitee.sop.gatewaycommon.exception.ApiException; import com.gitee.sop.gatewaycommon.gateway.ServerWebExchangeUtil; import com.gitee.sop.gatewaycommon.message.ErrorEnum; import com.gitee.sop.gatewaycommon.result.ResultExecutor; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.server.ServerWebExchange; @@ -28,6 +30,7 @@ public class GatewayController { // 合并微服务传递过来的结果,变成最终结果 ResultExecutor resultExecutor = ApiContext.getApiConfig().getGatewayResultExecutor(); String gatewayResult = resultExecutor.buildErrorResult(exchange, throwable); + exchange.getResponse().getHeaders().add(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE); return Mono.just(gatewayResult); } @@ -36,6 +39,7 @@ public class GatewayController { ApiException exception = ErrorEnum.ISV_INVALID_METHOD.getErrorMeta().getException(); ResultExecutor resultExecutor = ApiContext.getApiConfig().getGatewayResultExecutor(); String gatewayResult = resultExecutor.buildErrorResult(exchange, exception); + exchange.getResponse().getHeaders().add(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE); return Mono.just(gatewayResult); } diff --git a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/gateway/controller/GatewayMonitorController.java b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/gateway/controller/GatewayMonitorController.java deleted file mode 100644 index 910c874c..00000000 --- a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/gateway/controller/GatewayMonitorController.java +++ /dev/null @@ -1,21 +0,0 @@ -package com.gitee.sop.gatewaycommon.gateway.controller; - -import com.gitee.sop.gatewaycommon.support.BaseMonitorController; -import com.gitee.sop.gatewaycommon.param.ApiParam; -import org.springframework.web.bind.annotation.RestController; -import org.springframework.web.server.ServerWebExchange; - -import java.util.Map; - -/** - * @author tanghc - */ -@RestController -public class GatewayMonitorController extends BaseMonitorController { - - @Override - protected ApiParam getApiParam(ServerWebExchange request) { - Map params = request.getRequest().getQueryParams().toSingleValueMap(); - return ApiParam.build(params); - } -} diff --git a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/gateway/controller/RestfulController.java b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/gateway/controller/RestfulController.java new file mode 100644 index 00000000..6f04187e --- /dev/null +++ b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/gateway/controller/RestfulController.java @@ -0,0 +1,58 @@ +package com.gitee.sop.gatewaycommon.gateway.controller; + +import com.gitee.sop.gatewaycommon.bean.SpringContext; +import com.gitee.sop.gatewaycommon.gateway.loadbalancer.SopLoadBalancerClient; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.cloud.client.ServiceInstance; +import org.springframework.cloud.gateway.webflux.ProxyExchange; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Controller; +import org.springframework.util.StringUtils; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.server.ServerWebExchange; +import reactor.core.publisher.Mono; + +/** + * 处理restful请求 + * @author tanghc + */ +@Controller +public class RestfulController { + + @Value("${sop.restful.path:/rest}") + private String prefix; + + @RequestMapping("${sop.restful.path:/rest}/**") + public Mono> proxy(ProxyExchange proxy, ServerWebExchange exchange) { + String path = proxy.path(); + String serviceId = getServiceId(path); + String targetPath = getTargetPath(serviceId, path); + String rawQuery = exchange.getRequest().getURI().getRawQuery(); + if (StringUtils.hasLength(rawQuery)) { + targetPath = targetPath + "?" + rawQuery; + } + // 负载均衡 + ServiceInstance serviceInstance = SpringContext.getBean(SopLoadBalancerClient.class).choose(serviceId, exchange); + String uri = "http://" + serviceInstance.getHost() + ":" + serviceInstance.getPort() + targetPath; + return proxy.uri(uri).forward(); + } + + /** + * 从path中解析出serviceId + * @param path 格式:/rest/ + * @return 返回serviceId + */ + private String getServiceId(String path) { + int length = prefix.length() + 1; + path = path.substring(length); + int index = path.indexOf('/'); + path = path.substring(0, index); + return path; + } + + private String getTargetPath(String serviceId, String path) { + int length = prefix.length() + 1; + int len = length + serviceId.length(); + return path.substring(len); + } +} diff --git a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/gateway/filter/GatewayModifyResponseGatewayFilter.java b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/gateway/filter/GatewayModifyResponseGatewayFilter.java index e6a13901..ca45c1ac 100644 --- a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/gateway/filter/GatewayModifyResponseGatewayFilter.java +++ b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/gateway/filter/GatewayModifyResponseGatewayFilter.java @@ -5,43 +5,57 @@ import com.gitee.sop.gatewaycommon.bean.SopConstants; import com.gitee.sop.gatewaycommon.result.ResultExecutor; import org.apache.commons.lang3.StringUtils; import org.reactivestreams.Publisher; -import org.springframework.beans.factory.annotation.Value; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cloud.gateway.filter.GatewayFilterChain; import org.springframework.cloud.gateway.filter.GlobalFilter; import org.springframework.cloud.gateway.filter.NettyWriteResponseFilter; import org.springframework.cloud.gateway.filter.factory.rewrite.CachedBodyOutputMessage; +import org.springframework.cloud.gateway.filter.factory.rewrite.MessageBodyEncoder; import org.springframework.cloud.gateway.support.BodyInserterContext; -import org.springframework.cloud.gateway.support.DefaultClientResponse; import org.springframework.core.Ordered; -import org.springframework.core.codec.AbstractDataBufferDecoder; -import org.springframework.core.codec.Decoder; import org.springframework.core.io.buffer.DataBuffer; +import org.springframework.core.io.buffer.DataBufferFactory; +import org.springframework.core.io.buffer.DataBufferUtils; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseCookie; import org.springframework.http.client.reactive.ClientHttpResponse; -import org.springframework.http.codec.DecoderHttpMessageReader; -import org.springframework.http.codec.HttpMessageReader; +import org.springframework.http.codec.ServerCodecConfigurer; +import org.springframework.http.server.reactive.ServerHttpResponse; import org.springframework.http.server.reactive.ServerHttpResponseDecorator; import org.springframework.util.MultiValueMap; import org.springframework.web.reactive.function.BodyInserter; import org.springframework.web.reactive.function.BodyInserters; -import org.springframework.web.reactive.function.client.ExchangeStrategies; +import org.springframework.web.reactive.function.client.ClientResponse; import org.springframework.web.server.ServerWebExchange; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; +import reactor.core.scheduler.Schedulers; +import javax.annotation.PostConstruct; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +import static java.util.function.Function.identity; import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.ORIGINAL_RESPONSE_CONTENT_TYPE_ATTR; /** * 修改返回结果 + * * @author tanghc */ public class GatewayModifyResponseGatewayFilter implements GlobalFilter, Ordered { - @Value("${spring.codec.max-in-memory-size:262144}") - private int maxInMemorySize; + @Autowired + private ServerCodecConfigurer codecConfigurer; + @Autowired + private Set bodyEncoders; + + private Map messageBodyEncoders; @Override @SuppressWarnings("unchecked") @@ -62,12 +76,14 @@ public class GatewayModifyResponseGatewayFilter implements GlobalFilter, Ordered } Class inClass = String.class; Class outClass = String.class; + HttpHeaders httpHeaders = new HttpHeaders(); - //explicitly add it in this way instead of 'httpHeaders.setContentType(originalResponseContentType)' - //this will prevent exception in case of using non-standard media types like "Content-Type: image" + // explicitly add it in this way instead of + // 'httpHeaders.setContentType(originalResponseContentType)' + // this will prevent exception in case of using non-standard media + // types like "Content-Type: image" httpHeaders.add(HttpHeaders.CONTENT_TYPE, originalResponseContentType); - ResponseAdapter responseAdapter = new ResponseAdapter(body, httpHeaders); - DefaultClientResponse clientResponse = new DefaultClientResponse(responseAdapter, getExchangeStrategies()); + ClientResponse clientResponse = prepareClientResponse(exchange, body, httpHeaders); //TODO: flux or mono Mono modifiedBody = clientResponse.bodyToMono(inClass) @@ -80,16 +96,21 @@ public class GatewayModifyResponseGatewayFilter implements GlobalFilter, Ordered return Mono.just(ret); }); - BodyInserter bodyInserter = BodyInserters.fromPublisher(modifiedBody, outClass); - CachedBodyOutputMessage outputMessage = new CachedBodyOutputMessage(exchange, exchange.getResponse().getHeaders()); + BodyInserter bodyInserter = BodyInserters.fromPublisher(modifiedBody, + outClass); + CachedBodyOutputMessage outputMessage = new CachedBodyOutputMessage(exchange, + exchange.getResponse().getHeaders()); return bodyInserter.insert(outputMessage, new BodyInserterContext()) .then(Mono.defer(() -> { - Flux messageBody = outputMessage.getBody(); + Mono messageBody = writeBody(getDelegate(), + outputMessage, outClass); HttpHeaders headers = getDelegate().getHeaders(); - if (!headers.containsKey(HttpHeaders.TRANSFER_ENCODING)) { - messageBody = messageBody.doOnNext(data -> headers.setContentLength(data.readableByteCount())); + if (!headers.containsKey(HttpHeaders.TRANSFER_ENCODING) + || headers.containsKey(HttpHeaders.CONTENT_LENGTH)) { + messageBody = messageBody.doOnNext(data -> headers + .setContentLength(data.readableByteCount())); } - //TODO: use isStreamingMediaType? + // TODO: fail if isStreamingMediaType? return getDelegate().writeWith(messageBody); })); } @@ -104,20 +125,40 @@ public class GatewayModifyResponseGatewayFilter implements GlobalFilter, Ordered return chain.filter(exchange.mutate().response(responseDecorator).build()); } - private ExchangeStrategies getExchangeStrategies() { - ExchangeStrategies exchangeStrategies = ExchangeStrategies.withDefaults(); - // 修复返回大文本数据报org.springframework.core.io.buffer.DataBufferLimitException: Exceeded limit on max bytes to buffer : 262144 - for (HttpMessageReader messageReader : exchangeStrategies.messageReaders()) { - if (messageReader instanceof DecoderHttpMessageReader) { - DecoderHttpMessageReader reader = (DecoderHttpMessageReader) messageReader; - Decoder decoder = reader.getDecoder(); - if (decoder instanceof AbstractDataBufferDecoder) { - AbstractDataBufferDecoder dataBufferDecoder = (AbstractDataBufferDecoder)decoder; - dataBufferDecoder.setMaxInMemorySize(maxInMemorySize); - } + private ClientResponse prepareClientResponse( + ServerWebExchange exchange, + Publisher body, + HttpHeaders httpHeaders + ) { + ClientResponse.Builder builder; + builder = ClientResponse.create(exchange.getResponse().getStatusCode(), codecConfigurer.getReaders()); + return builder.headers(headers -> headers.putAll(httpHeaders)) + .body(Flux.from(body)).build(); + } + + private Mono writeBody(ServerHttpResponse httpResponse, + CachedBodyOutputMessage message, Class outClass) { + Mono response = DataBufferUtils.join(message.getBody()); + if (byte[].class.isAssignableFrom(outClass)) { + return response; + } + + List encodingHeaders = httpResponse.getHeaders() + .getOrEmpty(HttpHeaders.CONTENT_ENCODING); + for (String encoding : encodingHeaders) { + MessageBodyEncoder encoder = messageBodyEncoders.get(encoding); + if (encoder != null) { + DataBufferFactory dataBufferFactory = httpResponse.bufferFactory(); + response = response.publishOn(Schedulers.parallel()).map(buffer -> { + byte[] encodedResponse = encoder.encode(buffer); + DataBufferUtils.release(buffer); + return encodedResponse; + }).map(dataBufferFactory::wrap); + break; } } - return exchangeStrategies; + + return response; } @Override @@ -164,4 +205,10 @@ public class GatewayModifyResponseGatewayFilter implements GlobalFilter, Ordered return null; } } + + @PostConstruct + public void after() { + this.messageBodyEncoders = bodyEncoders == null ? Collections.emptyMap() : bodyEncoders.stream() + .collect(Collectors.toMap(MessageBodyEncoder::encodingType, identity())); + } } \ No newline at end of file diff --git a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/gateway/filter/IndexFilter.java b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/gateway/filter/IndexFilter.java index b21621b1..0c261675 100644 --- a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/gateway/filter/IndexFilter.java +++ b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/gateway/filter/IndexFilter.java @@ -20,6 +20,7 @@ import org.springframework.core.annotation.Order; import org.springframework.core.io.buffer.DataBuffer; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; +import org.springframework.http.codec.ServerCodecConfigurer; import org.springframework.http.server.reactive.ServerHttpRequest; import org.springframework.http.server.reactive.ServerHttpRequestDecorator; import org.springframework.web.reactive.function.BodyInserter; @@ -45,6 +46,9 @@ import java.util.Objects; @Order(Ordered.HIGHEST_PRECEDENCE) public class IndexFilter implements WebFilter { + @Autowired + private ServerCodecConfigurer codecConfigurer; + /** 路径白名单 */ private static final List PATH_WHITE_LIST = Arrays.asList( "/sop", "/actuator" @@ -72,17 +76,16 @@ public class IndexFilter implements WebFilter { return chain.filter(exchange); } // 如果是restful请求,直接转发 + // see:com.gitee.sop.gatewaycommon.gateway.controller.RestfulController if (enableRestful && path.startsWith(EnvironmentKeys.SOP_RESTFUL_PATH.getValue())) { - exchange.getAttributes().put(SopConstants.RESTFUL_REQUEST, true); - String restfulPath = ServerWebExchangeUtil.getRestfulPath(path, EnvironmentKeys.SOP_RESTFUL_PATH.getValue()); - ServerWebExchange newExchange = ServerWebExchangeUtil.getForwardExchange(exchange, restfulPath); - return chain.filter(newExchange); + return chain.filter(exchange); } - if (Objects.equals(path, indexPath)) { + if (Objects.equals(path, indexPath) || "".equals(path)) { if (request.getMethod() == HttpMethod.POST) { - ServerRequest serverRequest = ServerWebExchangeUtil.createReadBodyRequest(exchange); + ServerRequest serverRequest = ServerWebExchangeUtil.createReadBodyRequest(exchange, codecConfigurer); // 读取请求体中的内容 Mono modifiedBody = serverRequest.bodyToMono(byte[].class) + .switchIfEmpty(Mono.just("".getBytes())) .flatMap(data -> { // 构建ApiParam ApiParam apiParam = ServerWebExchangeUtil.getApiParam(exchange, data); @@ -141,9 +144,10 @@ public class IndexFilter implements WebFilter { validator.validate(apiParam); this.afterValidate(exchange, apiParam); } catch (ApiException e) { - log.error("验证失败,url:{}, ip:{}, params:{}, errorMsg:{}", + log.error("验证失败, errorMsg:{},url:{}, ip:{}, params:{}", + e.getMessage(), exchange.getRequest().getURI().toString(), - apiParam.fetchIp(), apiParam.toJSONString(), e.getMessage()); + apiParam.fetchIp(), apiParam.toJSONString()); ServerWebExchangeUtil.setThrowable(exchange, e); } } diff --git a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/gateway/result/GatewayResultExecutor.java b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/gateway/result/GatewayResultExecutor.java index b745a3f5..25f46393 100644 --- a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/gateway/result/GatewayResultExecutor.java +++ b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/gateway/result/GatewayResultExecutor.java @@ -14,6 +14,7 @@ import com.gitee.sop.gatewaycommon.result.BaseExecutorAdapter; import com.gitee.sop.gatewaycommon.result.ResultExecutorForGateway; import lombok.extern.slf4j.Slf4j; import org.springframework.cloud.client.ServiceInstance; +import org.springframework.cloud.gateway.support.TimeoutException; import org.springframework.http.HttpStatus; import org.springframework.util.CollectionUtils; import org.springframework.util.StringUtils; @@ -39,7 +40,7 @@ public class GatewayResultExecutor extends BaseExecutorAdapter errorCodeList = exchange.getResponse().getHeaders().get(SopConstants.X_SERVICE_ERROR_CODE); if (!CollectionUtils.isEmpty(errorCodeList)) { String errorCode = errorCodeList.get(0); - responseStatus = Integer.valueOf(errorCode); + responseStatus = Integer.parseInt(errorCode); } return responseStatus; } @@ -84,7 +85,9 @@ public class GatewayResultExecutor extends BaseExecutorAdapter routeDefinitionList = routes.values() - .stream() - .map(GatewayTargetRoute::getRouteDefinition) - .collect(Collectors.toList()); - routeDefinitionList.forEach(routeDefinition -> builder.route(routeDefinition.getId(), - r -> r.path(routeDefinition.getPath()) - .uri(routeDefinition.getUri()))); + routes.values().forEach(gatewayTargetRoute -> { + RouteDefinition routeDefinition = gatewayTargetRoute.getRouteDefinition(); + RewritePathGatewayFilterFactory rewritePathGatewayFilterFactory = new RewritePathGatewayFilterFactory(); + RewritePathGatewayFilterFactory.Config config = new RewritePathGatewayFilterFactory.Config(); + config.setRegexp(gatewayTargetRoute.getFullPath()); + config.setReplacement(routeDefinition.getPath()); + builder.route(routeDefinition.getId(), + r -> r.path(routeDefinition.getPath()) + // path匹配 + .filters(gatewayFilterSpec -> gatewayFilterSpec.filter(rewritePathGatewayFilterFactory.apply(config))) + .uri(routeDefinition.getUri()) + ); + }); this.routeLocator = builder.build(); - // 触发 applicationContext.publishEvent(new RefreshRoutesEvent(new Object())); } diff --git a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/gateway/route/GatewayTargetRoute.java b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/gateway/route/GatewayTargetRoute.java index 8249da2a..6f33b835 100644 --- a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/gateway/route/GatewayTargetRoute.java +++ b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/gateway/route/GatewayTargetRoute.java @@ -3,6 +3,7 @@ package com.gitee.sop.gatewaycommon.gateway.route; import com.gitee.sop.gatewaycommon.bean.RouteDefinition; import com.gitee.sop.gatewaycommon.bean.ServiceDefinition; import com.gitee.sop.gatewaycommon.bean.TargetRoute; +import org.springframework.util.StringUtils; /** * @author tanghc @@ -18,6 +19,13 @@ public class GatewayTargetRoute implements TargetRoute { this.routeDefinition = routeDefinition; } + @Override + public String getFullPath() { + String serviceId = serviceDefinition.getServiceId(); + String path = StringUtils.trimLeadingCharacter(routeDefinition.getPath(), '/'); + return "/" + serviceId + "/" + path; + } + @Override public ServiceDefinition getServiceDefinition() { return serviceDefinition; diff --git a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/interceptor/RouteInterceptorContext.java b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/interceptor/RouteInterceptorContext.java index 9eaa8fcc..fbb3d307 100644 --- a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/interceptor/RouteInterceptorContext.java +++ b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/interceptor/RouteInterceptorContext.java @@ -1,6 +1,5 @@ package com.gitee.sop.gatewaycommon.interceptor; -import com.gitee.sop.gatewaycommon.bean.SopConstants; import com.gitee.sop.gatewaycommon.param.ApiParam; import org.springframework.cloud.client.ServiceInstance; import org.springframework.http.HttpStatus; @@ -88,6 +87,6 @@ public interface RouteInterceptorContext { */ default boolean isSuccessRequest() { int responseStatus = getResponseStatus(); - return responseStatus == HttpStatus.OK.value() || responseStatus == SopConstants.BIZ_ERROR_STATUS; + return responseStatus == HttpStatus.OK.value(); } } diff --git a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/loadbalancer/LoadBalanceConfig.java b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/loadbalancer/LoadBalanceConfig.java deleted file mode 100644 index 77f6c009..00000000 --- a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/loadbalancer/LoadBalanceConfig.java +++ /dev/null @@ -1,8 +0,0 @@ -package com.gitee.sop.gatewaycommon.loadbalancer; - -/** - * @author tanghc - */ -public class LoadBalanceConfig { - -} diff --git a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/manager/EnvGrayManager.java b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/manager/EnvGrayManager.java index e58c70b8..8cf8ff6d 100644 --- a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/manager/EnvGrayManager.java +++ b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/manager/EnvGrayManager.java @@ -8,8 +8,6 @@ import com.gitee.sop.gatewaycommon.loadbalancer.ServiceGrayConfig; */ public interface EnvGrayManager extends BeanInitializer { - String ENV_GRAY = "sop.env-gray"; - /** * 保存灰度配置 * @param serviceGrayConfig 灰度配置 diff --git a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/manager/EnvironmentKeys.java b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/manager/EnvironmentKeys.java index c5410563..8ba239cb 100644 --- a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/manager/EnvironmentKeys.java +++ b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/manager/EnvironmentKeys.java @@ -33,8 +33,7 @@ public enum EnvironmentKeys { /** * post请求body缓存大小 */ - MAX_IN_MEMORY_SIZE("spring.codec.max-in-memory-size", "262144") - + MAX_IN_MEMORY_SIZE("spring.codec.max-in-memory-size", "262144"), ; diff --git a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/message/ErrorEnum.java b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/message/ErrorEnum.java index 9676d547..65e91deb 100644 --- a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/message/ErrorEnum.java +++ b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/message/ErrorEnum.java @@ -12,6 +12,8 @@ public enum ErrorEnum { ISP_UNKNOWN_ERROR(Codes.CODE_UNKNOWN, "isp.unknown-error"), /** 服务不可用,路由被禁用 */ ISP_API_DISABLED(Codes.CODE_UNKNOWN, "isp.service-not-available"), + /** 网关响应超时 */ + ISP_GATEWAY_RESPONSE_TIMEOUT(Codes.CODE_UNKNOWN, "isp.gateway-response-timeout"), /** 限流处理 */ ISV_REQUEST_LIMIT(Codes.CODE_UNKNOWN, "isv.service-busy"), diff --git a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/message/ErrorFactory.java b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/message/ErrorFactory.java index 98d33f98..f0236808 100644 --- a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/message/ErrorFactory.java +++ b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/message/ErrorFactory.java @@ -7,6 +7,7 @@ import org.springframework.context.support.ResourceBundleMessageSource; import org.springframework.util.Assert; import org.springframework.util.StringUtils; +import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.List; @@ -33,6 +34,8 @@ public class ErrorFactory { private static Map errorCache = new HashMap<>(64); + private static List localeList = Arrays.asList(Locale.ENGLISH, Locale.SIMPLIFIED_CHINESE); + /** * 错误信息的国际化信息 */ @@ -70,6 +73,9 @@ public class ErrorFactory { if (locale == null) { locale = Locale.SIMPLIFIED_CHINESE; } + if (!localeList.contains(locale)) { + locale = Locale.ENGLISH; + } String key = errorMeta.getModulePrefix() + errorMeta.getCode() + errorMeta.getSubCode() + locale.toString(); Error error = errorCache.get(key); if (error == null) { diff --git a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/message/ErrorMeta.java b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/message/ErrorMeta.java index 6b3aff65..87555b4d 100644 --- a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/message/ErrorMeta.java +++ b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/message/ErrorMeta.java @@ -13,11 +13,9 @@ import java.util.Locale; @Getter public class ErrorMeta { - private static final Locale ZH_CN = Locale.SIMPLIFIED_CHINESE; - - private String modulePrefix; - private String code; - private String subCode; + private final String modulePrefix; + private final String code; + private final String subCode; public ErrorMeta(String modulePrefix, String code, String subCode) { this.modulePrefix = modulePrefix; diff --git a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/monitor/MonitorDTO.java b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/monitor/MonitorDTO.java new file mode 100644 index 00000000..2115c32e --- /dev/null +++ b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/monitor/MonitorDTO.java @@ -0,0 +1,63 @@ +package com.gitee.sop.gatewaycommon.monitor; + +import lombok.Data; + +import java.util.Collection; + +/** + * 每个接口 总调用流量,最大时间,最小时间,总时长,平均时长,调用次数,成功次数,失败次数,错误查看。 + * + * @author tanghc + */ +@Data +public class MonitorDTO { + + /** + * 路由id + */ + private String routeId; + + /** + * 接口名 + */ + private String name; + /** + * 版本号 + */ + private String version; + /** + * serviceId + */ + private String serviceId; + + /** + * 实例id + */ + private String instanceId; + + /** 请求耗时最长时间, 数据库字段:max_time */ + private Integer maxTime; + + /** 请求耗时最小时间, 数据库字段:min_time */ + private Integer minTime; + + /** + * 总时长 + */ + private Long totalTime; + + /** 总调用次数, 数据库字段:total_request_count */ + private Long totalRequestCount; + + /** 成功次数, 数据库字段:success_count */ + private Long successCount; + + /** 失败次数(业务主动抛出的异常算作成功,如参数校验,未知的错误算失败), 数据库字段:error_count */ + private Long errorCount; + + /** + * 错误信息 + */ + private Collection errorMsgList; + +} diff --git a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/monitor/MonitorData.java b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/monitor/MonitorData.java new file mode 100644 index 00000000..e957c873 --- /dev/null +++ b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/monitor/MonitorData.java @@ -0,0 +1,106 @@ +package com.gitee.sop.gatewaycommon.monitor; + +import com.gitee.sop.gatewaycommon.bean.LRUCache; +import lombok.Data; +import org.apache.commons.codec.digest.DigestUtils; + +import java.time.LocalDateTime; +import java.util.Map; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * 每个接口 总调用流量,最大时间,最小时间,总时长,平均时长,调用次数,成功次数,失败次数,错误查看。 + * + * @author tanghc + */ +@Data +public class MonitorData { + + public static final int LIMIT_SIZE = 50; + + + private String routeId; + + /** + * 接口名 + */ + private String name; + /** + * 版本号 + */ + private String version; + /** + * serviceId + */ + private String serviceId; + + private String instanceId; + + /** + * 请求耗时最长时间 + */ + private Integer maxTime; + /** + * 请求耗时最小时间 + */ + private Integer minTime; + /** + * 总时长 + */ + private AtomicInteger totalTime; + /** + * 总调用次数 + */ + private AtomicInteger totalRequestCount; + /** + * 成功次数 + */ + private AtomicInteger successCount; + /** + * 失败次数(业务主动抛出的异常算作成功,如参数校验,未知的错误算失败) + */ + private AtomicInteger errorCount; + + /** + * 错误信息,key: errorId + */ + private LRUCache monitorErrorMsgMap; + + /** + * 下一次刷新到数据库的时间 + */ + private LocalDateTime flushTime; + + public synchronized void storeMaxTime(int spendTime) { + if (spendTime > maxTime) { + maxTime = spendTime; + } + } + + public synchronized void storeMinTime(int spendTime) { + if (minTime == 0 || spendTime < minTime) { + minTime = spendTime; + } + } + + public void addErrorMsg(String errorMsg, int httpStatus) { + if (errorMsg == null || "".equals(errorMsg)) { + return; + } + synchronized (this) { + String errorId = DigestUtils.md5Hex(instanceId + routeId + errorMsg); + MonitorErrorMsg monitorErrorMsg = monitorErrorMsgMap.computeIfAbsent(errorId, (k) -> { + MonitorErrorMsg value = new MonitorErrorMsg(); + value.setErrorId(errorId); + value.setInstanceId(instanceId); + value.setRouteId(routeId); + value.setErrorMsg(errorMsg); + value.setErrorStatus(httpStatus); + value.setCount(0); + return value; + }); + monitorErrorMsg.setCount(monitorErrorMsg.getCount() + 1); + } + } + +} diff --git a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/monitor/MonitorErrorMsg.java b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/monitor/MonitorErrorMsg.java new file mode 100644 index 00000000..a14799da --- /dev/null +++ b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/monitor/MonitorErrorMsg.java @@ -0,0 +1,30 @@ +package com.gitee.sop.gatewaycommon.monitor; + +import lombok.Data; + +/** + * @author thc + */ +@Data +public class MonitorErrorMsg { + /** 错误id,md5(error_msg), 数据库字段:error_id */ + private String errorId; + + /** 实例id, 数据库字段:instance_id */ + private String instanceId; + + /** 数据库字段:route_id */ + private String routeId; + + /** 数据库字段:isp_id */ + private Long ispId; + + /** 数据库字段:error_msg */ + private String errorMsg; + + /** http status,非200错误 */ + private Integer errorStatus; + + /** 错误次数, 数据库字段:count */ + private Integer count; +} \ No newline at end of file diff --git a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/monitor/MonitorManager.java b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/monitor/MonitorManager.java index a181da7e..15865dea 100644 --- a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/monitor/MonitorManager.java +++ b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/monitor/MonitorManager.java @@ -9,13 +9,13 @@ import java.util.function.Function; */ public class MonitorManager { - private static Map monitorMap = new ConcurrentHashMap<>(128); + private static Map monitorMap = new ConcurrentHashMap<>(128); - public Map getMonitorData() { + public Map getMonitorData() { return monitorMap; } - public MonitorInfo getMonitorInfo(String routeId, Function createFun) { + public MonitorData getMonitorInfo(String routeId, Function createFun) { return monitorMap.computeIfAbsent(routeId, createFun); } diff --git a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/monitor/RouteErrorCount.java b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/monitor/RouteErrorCount.java new file mode 100644 index 00000000..2ff46699 --- /dev/null +++ b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/monitor/RouteErrorCount.java @@ -0,0 +1,13 @@ +package com.gitee.sop.gatewaycommon.monitor; + +import lombok.Data; + +/** + * @author tanghc + */ +@Data +public class RouteErrorCount { + + private String routeId; + private Integer count; +} diff --git a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/param/ApiUploadContext.java b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/param/ApiUploadContext.java index 7ed85155..52c018e4 100644 --- a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/param/ApiUploadContext.java +++ b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/param/ApiUploadContext.java @@ -17,15 +17,16 @@ public class ApiUploadContext implements UploadContext { /** * key: 表单name */ - private Map fileMap; + private Map> fileMap; private List allFile; - public ApiUploadContext(Map map) { + public ApiUploadContext(Map> map) { if (map == null) { map = Collections.emptyMap(); } this.fileMap = map; - this.allFile = new ArrayList<>(map.values()); + this.allFile = new ArrayList<>(); + map.values().forEach(list -> this.allFile.addAll(list)); } @Override @@ -34,7 +35,7 @@ public class ApiUploadContext implements UploadContext { } @Override - public MultipartFile getFile(String name) { + public List getFile(String name) { return fileMap.get(name); } diff --git a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/param/UploadContext.java b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/param/UploadContext.java index bb72a2d7..cfdf6aac 100644 --- a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/param/UploadContext.java +++ b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/param/UploadContext.java @@ -25,7 +25,7 @@ public interface UploadContext { * 表单名称 * @return 返回上传文件信息 */ - MultipartFile getFile(String name); + List getFile(String name); /** * 获取所有的上传文件 diff --git a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/result/BaseExecutorAdapter.java b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/result/BaseExecutorAdapter.java index f3b3f329..76c68aec 100644 --- a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/result/BaseExecutorAdapter.java +++ b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/result/BaseExecutorAdapter.java @@ -119,7 +119,7 @@ public abstract class BaseExecutorAdapter implements ResultExecutor defaultRouteInterceptorContext.setServiceResult(serviceResult); defaultRouteInterceptorContext.setFinishTimeMillis(System.currentTimeMillis()); defaultRouteInterceptorContext.setResponseDataSize(serviceResult.length()); - if (responseStatus != HttpStatus.OK.value() && responseStatus != SopConstants.BIZ_ERROR_STATUS) { + if (responseStatus != HttpStatus.OK.value()) { String responseErrorMessage = getResponseErrorMessage(requestContext); if (StringUtils.isEmpty(responseErrorMessage)) { responseErrorMessage = serviceResult; diff --git a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/route/ForwardInfo.java b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/route/ForwardInfo.java index e822712e..f08c089e 100644 --- a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/route/ForwardInfo.java +++ b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/route/ForwardInfo.java @@ -19,6 +19,10 @@ public class ForwardInfo { this.targetRoute = targetRoute; } + public String getFullPath() { + return targetRoute.getFullPath(); + } + public String getPath() { return targetRoute.getRouteDefinition().getPath(); } @@ -41,6 +45,11 @@ public class ForwardInfo { super(targetRoute); } + @Override + public String getFullPath() { + return getPath(); + } + @Override public String getPath() { return VALIDATE_ERROR_PATH; diff --git a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/route/ServiceRouteListener.java b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/route/ServiceRouteListener.java index 68a1f07f..c029a1cc 100644 --- a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/route/ServiceRouteListener.java +++ b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/route/ServiceRouteListener.java @@ -45,13 +45,25 @@ public class ServiceRouteListener extends BaseServiceListener { ResponseEntity responseEntity = getRestTemplate().getForEntity(url, String.class); if (responseEntity.getStatusCode() == HttpStatus.OK) { String body = responseEntity.getBody(); - ServiceRouteInfo serviceRouteInfo = JSON.parseObject(body, ServiceRouteInfo.class); + ServiceRouteInfo serviceRouteInfo; + try { + serviceRouteInfo = this.parseServiceRouteInfo(body); + } catch (Exception e) { + log.error("解析路由配置错误,body:{}", body, e); + return; + } gatewayRouteCache.load(serviceRouteInfo, callback -> routesProcessor.saveRoutes(serviceRouteInfo, instance)); } else { log.error("拉取路由配置异常,url: {}, status: {}, body: {}", url, responseEntity.getStatusCodeValue(), responseEntity.getBody()); } } + private ServiceRouteInfo parseServiceRouteInfo(String body) { + ServiceRouteInfo serviceRouteInfo = JSON.parseObject(body, ServiceRouteInfo.class); + serviceRouteInfo.setServiceId(serviceRouteInfo.getServiceId().toLowerCase()); + return serviceRouteInfo; + } + protected HttpEntity getHttpEntity() { HttpHeaders headers = new HttpHeaders(); return new HttpEntity<>(headers); diff --git a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/sync/MyNamedThreadFactory.java b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/sync/MyNamedThreadFactory.java new file mode 100644 index 00000000..267af1d1 --- /dev/null +++ b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/sync/MyNamedThreadFactory.java @@ -0,0 +1,37 @@ +package com.gitee.sop.gatewaycommon.sync; + +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * @author thc + */ +public class MyNamedThreadFactory implements ThreadFactory { + private static final AtomicInteger POOL_NUMBER = new AtomicInteger(1); + private final AtomicInteger threadNumber = new AtomicInteger(1); + private final ThreadGroup group; + private final String namePrefix; + + public MyNamedThreadFactory(String name) { + + SecurityManager s = System.getSecurityManager(); + group = (s != null) ? s.getThreadGroup() : Thread.currentThread().getThreadGroup(); + if (null == name || name.isEmpty()) { + name = "pool"; + } + + namePrefix = name + "-" + POOL_NUMBER.getAndIncrement() + "-thread-"; + } + + @Override + public Thread newThread(Runnable r) { + Thread t = new Thread(group, r, namePrefix + threadNumber.getAndIncrement()); + if (t.isDaemon()) { + t.setDaemon(false); + } + if (t.getPriority() != Thread.NORM_PRIORITY) { + t.setPriority(Thread.NORM_PRIORITY); + } + return t; + } +} \ No newline at end of file diff --git a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/sync/SopAsyncConfigurer.java b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/sync/SopAsyncConfigurer.java new file mode 100644 index 00000000..eb609c4e --- /dev/null +++ b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/sync/SopAsyncConfigurer.java @@ -0,0 +1,41 @@ +package com.gitee.sop.gatewaycommon.sync; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler; +import org.springframework.scheduling.annotation.AsyncConfigurer; + +import java.lang.reflect.Method; +import java.util.Arrays; +import java.util.concurrent.Executor; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; + +/** + * 异步执行配置 + * + * @author tanghc + */ +@Slf4j +public class SopAsyncConfigurer implements AsyncConfigurer { + + private final ThreadPoolExecutor threadPoolExecutor; + + public SopAsyncConfigurer(String threadName, int poolSize) { + threadPoolExecutor = new ThreadPoolExecutor(poolSize, poolSize, + 0L, TimeUnit.MILLISECONDS, + new LinkedBlockingQueue<>(), new MyNamedThreadFactory(threadName)); + } + + @Override + public Executor getAsyncExecutor() { + return threadPoolExecutor; + } + + @Override + public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() { + return (Throwable e, Method method, Object... args) -> { + log.error("异步运行方法出错, method:{}, args:{}, message:{}", method, Arrays.deepToString(args), e.getMessage(), e); + }; + } +} diff --git a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/util/CopyUtil.java b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/util/CopyUtil.java new file mode 100644 index 00000000..724ca63f --- /dev/null +++ b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/util/CopyUtil.java @@ -0,0 +1,79 @@ +package com.gitee.sop.gatewaycommon.util; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.BeanUtils; + +import java.util.Collections; +import java.util.List; +import java.util.Objects; +import java.util.function.Consumer; +import java.util.function.Supplier; +import java.util.stream.Collectors; + +/** + * @author tanghc + */ +@Slf4j +public class CopyUtil { + + public static void copyPropertiesIgnoreNull(Object from, Object to) { + MyBeanUtil.copyPropertiesIgnoreNull(from, to); + } + + public static void copyProperties(Object from, Object to) { + BeanUtils.copyProperties(from, to); + } + + public static T copyBean(Object from, Supplier supplier) { + Objects.requireNonNull(from); + T to = supplier.get(); + BeanUtils.copyProperties(from, to); + return to; + } + + public static T copyBeanNullable(Object from, Supplier supplier) { + if (from == null) { + return supplier.get(); + } + T to = supplier.get(); + BeanUtils.copyProperties(from, to); + return to; + } + + public static T copyBean(Object from, Supplier supplier, Consumer after) { + if (from == null) { + return null; + } + T to = supplier.get(); + BeanUtils.copyProperties(from, to); + after.accept(to); + return to; + } + + public static List copyList(List fromList, Supplier toElement) { + if (fromList == null) { + return Collections.emptyList(); + } + return fromList.stream() + .map(source -> { + T target = toElement.get(); + BeanUtils.copyProperties(source, target); + return target; + }) + .collect(Collectors.toList()); + } + + public static List copyList(List fromList, Supplier toElement, Consumer after) { + if (fromList == null) { + return Collections.emptyList(); + } + return fromList.stream() + .map(source -> { + T target = toElement.get(); + BeanUtils.copyProperties(source, target); + after.accept(target); + return target; + }) + .collect(Collectors.toList()); + } +} diff --git a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/util/RequestUtil.java b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/util/RequestUtil.java index 7abe5c31..1919ca51 100644 --- a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/util/RequestUtil.java +++ b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/util/RequestUtil.java @@ -15,6 +15,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.http.HttpHeaders; import org.springframework.http.server.reactive.ServerHttpRequest; +import org.springframework.util.MultiValueMap; import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.commons.CommonsMultipartFile; import org.springframework.web.multipart.support.StandardMultipartHttpServletRequest; @@ -36,7 +37,6 @@ import java.util.Locale; import java.util.Map; import java.util.Optional; import java.util.Set; -import java.util.function.Function; import java.util.stream.Collectors; @@ -298,9 +298,9 @@ public class RequestUtil { } } if (multipartFileList.size() > 0) { - Map multipartFileMap = multipartFileList + Map> multipartFileMap = multipartFileList .stream() - .collect(Collectors.toMap(MultipartFile::getName, Function.identity())); + .collect(Collectors.groupingBy(MultipartFile::getName)); uploadContext = new ApiUploadContext(multipartFileMap); } uploadInfo.setUploadParams(uploadParams); @@ -315,8 +315,14 @@ public class RequestUtil { UploadInfo uploadInfo = new UploadInfo(); Map uploadParams = new HashMap<>(16); request.getParameterMap().forEach((key, value)-> uploadParams.put(key, value[0])); - - Map multipartFileMap = request.getMultiFileMap().toSingleValueMap(); + MultiValueMap multiFileMap = request.getMultiFileMap(); + List multipartFileList = new ArrayList<>(10); + for (String key : multiFileMap.keySet()) { + multipartFileList.addAll(multiFileMap.get(key)); + } + Map> multipartFileMap = multipartFileList + .stream() + .collect(Collectors.groupingBy(MultipartFile::getName)); UploadContext uploadContext = new ApiUploadContext(multipartFileMap); uploadInfo.setUploadParams(uploadParams); diff --git a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/util/ResponseUtil.java b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/util/ResponseUtil.java index 496addca..1e7bc24c 100644 --- a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/util/ResponseUtil.java +++ b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/util/ResponseUtil.java @@ -27,12 +27,12 @@ public class ResponseUtil { private static Logger log = LoggerFactory.getLogger(ResponseUtil.class); public static void writeJson(HttpServletResponse response, Object result) { - response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE); + response.setContentType(MediaType.APPLICATION_JSON_VALUE); response.setCharacterEncoding(UTF_8); try { response.getWriter().write(result instanceof String ? (String)result : JSON.toJSONString(result)); } catch (IOException e) { - log.error("doWriter", e); + log.error("writeJson error", e); } } diff --git a/sop-common/sop-gateway-common/src/main/resources/i18n/open/error_en.properties b/sop-common/sop-gateway-common/src/main/resources/i18n/open/error_en.properties index ea35bb30..7f414951 100644 --- a/sop-common/sop-gateway-common/src/main/resources/i18n/open/error_en.properties +++ b/sop-common/sop-gateway-common/src/main/resources/i18n/open/error_en.properties @@ -1,13 +1,14 @@ -# 网关错误配置 +# \u7F51\u5173\u9519\u8BEF\u914D\u7F6E open.error_10000=Success -# 格式:前缀 + 网关错误码 + "_"+ 子错误码 -# open.error_(前缀)20000(网关错误码)_isp.unknow-error(子错误码) +# \u683C\u5F0F\uFF1A\u524D\u7F00 + \u7F51\u5173\u9519\u8BEF\u7801 + "_"+ \u5B50\u9519\u8BEF\u7801 +# open.error_\uFF08\u524D\u7F00\uFF0920000\uFF08\u7F51\u5173\u9519\u8BEF\u7801\uFF09_isp.unknow-error\uFF08\u5B50\u9519\u8BEF\u7801\uFF09 open.error_20000=Service is temporarily unavailable open.error_20000_isp.unknown-error=Service is temporarily unavailable open.error_20000_aop.unknown-error=Service is temporarily unavailable open.error_20000_isp.service-not-available=Service is temporarily unavailable +open.error_20000_isp.gateway-response-timeout=Gateway response timeout open.error_20000_isv.service-busy=service busy open.error_20001=Insufficient authorization authority diff --git a/sop-common/sop-gateway-common/src/main/resources/i18n/open/error_zh_CN.properties b/sop-common/sop-gateway-common/src/main/resources/i18n/open/error_zh_CN.properties index 57df73b9..5fa2aef1 100644 --- a/sop-common/sop-gateway-common/src/main/resources/i18n/open/error_zh_CN.properties +++ b/sop-common/sop-gateway-common/src/main/resources/i18n/open/error_zh_CN.properties @@ -1,118 +1,119 @@ -# 网关错误配置 +# \u7F51\u5173\u9519\u8BEF\u914D\u7F6E -#open.error_20000=服务不可用 -#open.error_20000_isp.unknow-error=服务暂不可用 -#open.error_20000_aop.unknow-error=服务暂不可用 -#open.error_20000_isp.service-not-available=服务暂不可用 +#open.error_20000=\u670D\u52A1\u4E0D\u53EF\u7528 +#open.error_20000_isp.unknow-error=\u670D\u52A1\u6682\u4E0D\u53EF\u7528 +#open.error_20000_aop.unknow-error=\u670D\u52A1\u6682\u4E0D\u53EF\u7528 +#open.error_20000_isp.service-not-available=\u670D\u52A1\u6682\u4E0D\u53EF\u7528 # -#open.error_20001=授权权限不足 -#open.error_20001_aop.invalid-auth-token=无效的访问令牌 -#open.error_20001_aop.auth-token-time-out=访问令牌已过期 -#open.error_20001_aop.invalid-app-auth-token=无效的应用授权令牌 -#open.error_20001_aop.invalid-app-auth-token-no-api=商户未授权当前接口 -#open.error_20001_aop.app-auth-token-time-out=应用授权令牌已过期 -#open.error_20001_aop.no-product-reg-by-partner=商户未签约任何产品 +#open.error_20001=\u6388\u6743\u6743\u9650\u4E0D\u8DB3 +#open.error_20001_aop.invalid-auth-token=\u65E0\u6548\u7684\u8BBF\u95EE\u4EE4\u724C +#open.error_20001_aop.auth-token-time-out=\u8BBF\u95EE\u4EE4\u724C\u5DF2\u8FC7\u671F +#open.error_20001_aop.invalid-app-auth-token=\u65E0\u6548\u7684\u5E94\u7528\u6388\u6743\u4EE4\u724C +#open.error_20001_aop.invalid-app-auth-token-no-api=\u5546\u6237\u672A\u6388\u6743\u5F53\u524D\u63A5\u53E3 +#open.error_20001_aop.app-auth-token-time-out=\u5E94\u7528\u6388\u6743\u4EE4\u724C\u5DF2\u8FC7\u671F +#open.error_20001_aop.no-product-reg-by-partner=\u5546\u6237\u672A\u7B7E\u7EA6\u4EFB\u4F55\u4EA7\u54C1 # -#open.error_40001=缺少必选参数 -#open.error_40001_isv.missing-method=缺少方法名参数 -#open.error_40001_isv.missing-signature=缺少签名参数 -#open.error_40001_isv.missing-signature-type=缺少签名类型参数 -#open.error_40001_isv.missing-signature-key=缺少签名配置 -#open.error_40001_isv.missing-app-id=缺少appId参数 -#open.error_40001_isv.missing-timestamp=缺少时间戳参数 -#open.error_40001_isv.missing-version=缺少版本参数 -#open.error_40001_isv.decryption-error-missing-encrypt-type=解密出错, 未指定加密算法 +#open.error_40001=\u7F3A\u5C11\u5FC5\u9009\u53C2\u6570 +#open.error_40001_isv.missing-method=\u7F3A\u5C11\u65B9\u6CD5\u540D\u53C2\u6570 +#open.error_40001_isv.missing-signature=\u7F3A\u5C11\u7B7E\u540D\u53C2\u6570 +#open.error_40001_isv.missing-signature-type=\u7F3A\u5C11\u7B7E\u540D\u7C7B\u578B\u53C2\u6570 +#open.error_40001_isv.missing-signature-key=\u7F3A\u5C11\u7B7E\u540D\u914D\u7F6E +#open.error_40001_isv.missing-app-id=\u7F3A\u5C11appId\u53C2\u6570 +#open.error_40001_isv.missing-timestamp=\u7F3A\u5C11\u65F6\u95F4\u6233\u53C2\u6570 +#open.error_40001_isv.missing-version=\u7F3A\u5C11\u7248\u672C\u53C2\u6570 +#open.error_40001_isv.decryption-error-missing-encrypt-type=\u89E3\u5BC6\u51FA\u9519, \u672A\u6307\u5B9A\u52A0\u5BC6\u7B97\u6CD5 # -#open.error_40002=非法的参数 -#open.error_40002_isv.invalid-parameter=参数无效 -#open.error_40002_isv.upload-fail=文件上传失败 -#open.error_40002_isv.invalid-file-extension=文件扩展名无效 -#open.error_40002_isv.invalid-file-size={0}文件大小无效,单文件不得超过{1} -#open.error_40002_isv.invalid-method=不存在的方法名 -#open.error_40002_isv.invalid-format=无效的数据格式 -#open.error_40002_isv.invalid-signature-type=无效的签名类型 -#open.error_40002_isv.invalid-signature=无效签名 -#open.error_40002_isv.invalid-encrypt-type=无效的加密类型 -#open.error_40002_isv.invalid-encrypt=解密异常 -#open.error_40002_isv.invalid-app-id=无效的appId参数 -#open.error_40002_isv.invalid-timestamp=非法的时间戳参数 -#open.error_40002_isv.invalid-charset=字符集错误 -#open.error_40002_isv.invalid-digest=摘要错误 -#open.error_40002_isv.decryption-error-not-valid-encrypt-type=解密出错,不支持的加密算法 -#open.error_40002_isv.decryption-error-not-valid-encrypt-key=解密出错, 未配置加密密钥或加密密钥格式错误 -#open.error_40002_isv.decryption-error-unknown=解密出错,未知异常 -#open.error_40002_isv.missing-signature-config=验签出错, 未配置对应签名算法的公钥或者证书 -#open.error_40002_isv.not-support-app-auth=本接口不支持第三方代理调用 -#open.error_40002_isv.suspected-attack=可疑的攻击请求 -#open.error_40002_isv.invalid-content-type=无效的content-type +#open.error_40002=\u975E\u6CD5\u7684\u53C2\u6570 +#open.error_40002_isv.invalid-parameter=\u53C2\u6570\u65E0\u6548 +#open.error_40002_isv.upload-fail=\u6587\u4EF6\u4E0A\u4F20\u5931\u8D25 +#open.error_40002_isv.invalid-file-extension=\u6587\u4EF6\u6269\u5C55\u540D\u65E0\u6548 +#open.error_40002_isv.invalid-file-size={0}\u6587\u4EF6\u5927\u5C0F\u65E0\u6548\uFF0C\u5355\u6587\u4EF6\u4E0D\u5F97\u8D85\u8FC7{1} +#open.error_40002_isv.invalid-method=\u4E0D\u5B58\u5728\u7684\u65B9\u6CD5\u540D +#open.error_40002_isv.invalid-format=\u65E0\u6548\u7684\u6570\u636E\u683C\u5F0F +#open.error_40002_isv.invalid-signature-type=\u65E0\u6548\u7684\u7B7E\u540D\u7C7B\u578B +#open.error_40002_isv.invalid-signature=\u65E0\u6548\u7B7E\u540D +#open.error_40002_isv.invalid-encrypt-type=\u65E0\u6548\u7684\u52A0\u5BC6\u7C7B\u578B +#open.error_40002_isv.invalid-encrypt=\u89E3\u5BC6\u5F02\u5E38 +#open.error_40002_isv.invalid-app-id=\u65E0\u6548\u7684appId\u53C2\u6570 +#open.error_40002_isv.invalid-timestamp=\u975E\u6CD5\u7684\u65F6\u95F4\u6233\u53C2\u6570 +#open.error_40002_isv.invalid-charset=\u5B57\u7B26\u96C6\u9519\u8BEF +#open.error_40002_isv.invalid-digest=\u6458\u8981\u9519\u8BEF +#open.error_40002_isv.decryption-error-not-valid-encrypt-type=\u89E3\u5BC6\u51FA\u9519\uFF0C\u4E0D\u652F\u6301\u7684\u52A0\u5BC6\u7B97\u6CD5 +#open.error_40002_isv.decryption-error-not-valid-encrypt-key=\u89E3\u5BC6\u51FA\u9519, \u672A\u914D\u7F6E\u52A0\u5BC6\u5BC6\u94A5\u6216\u52A0\u5BC6\u5BC6\u94A5\u683C\u5F0F\u9519\u8BEF +#open.error_40002_isv.decryption-error-unknown=\u89E3\u5BC6\u51FA\u9519\uFF0C\u672A\u77E5\u5F02\u5E38 +#open.error_40002_isv.missing-signature-config=\u9A8C\u7B7E\u51FA\u9519, \u672A\u914D\u7F6E\u5BF9\u5E94\u7B7E\u540D\u7B97\u6CD5\u7684\u516C\u94A5\u6216\u8005\u8BC1\u4E66 +#open.error_40002_isv.not-support-app-auth=\u672C\u63A5\u53E3\u4E0D\u652F\u6301\u7B2C\u4E09\u65B9\u4EE3\u7406\u8C03\u7528 +#open.error_40002_isv.suspected-attack=\u53EF\u7591\u7684\u653B\u51FB\u8BF7\u6C42 +#open.error_40002_isv.invalid-content-type=\u65E0\u6548\u7684content-type # -#open.error_40004=业务处理失败 -#open.error_40004_=业务处理失败 +#open.error_40004=\u4E1A\u52A1\u5904\u7406\u5931\u8D25 +#open.error_40004_=\u4E1A\u52A1\u5904\u7406\u5931\u8D25 # -#open.error_40006=权限不足 -#open.error_40006_isv.insufficient-isv-permissions=请检查配置的账户是否有当前接口权限 -#open.error_40006_isv.insufficient-user-permissions=代理的商户没有当前接口权限 -#open.error_40006_isv.route-no-permissions=没有当前接口权限 -#open.error_40006_isv.access-forbidden=无权访问 +#open.error_40006=\u6743\u9650\u4E0D\u8DB3 +#open.error_40006_isv.insufficient-isv-permissions=\u8BF7\u68C0\u67E5\u914D\u7F6E\u7684\u8D26\u6237\u662F\u5426\u6709\u5F53\u524D\u63A5\u53E3\u6743\u9650 +#open.error_40006_isv.insufficient-user-permissions=\u4EE3\u7406\u7684\u5546\u6237\u6CA1\u6709\u5F53\u524D\u63A5\u53E3\u6743\u9650 +#open.error_40006_isv.route-no-permissions=\u6CA1\u6709\u5F53\u524D\u63A5\u53E3\u6743\u9650 +#open.error_40006_isv.access-forbidden=\u65E0\u6743\u8BBF\u95EE open.error_10000=Success -# 格式:前缀 + 网关错误码 + "_"+ 子错误码 -# open.error_(前缀)20000(网关错误码)_isp.unknow-error(子错误码) -open.error_20000=\u670d\u52a1\u4e0d\u53ef\u7528 -open.error_20000_isp.unknown-error=\u670d\u52a1\u6682\u4e0d\u53ef\u7528 -open.error_20000_aop.unknown-error=\u670d\u52a1\u6682\u4e0d\u53ef\u7528 -open.error_20000_isp.service-not-available=\u670d\u52a1\u6682\u4e0d\u53ef\u7528 -open.error_20000_isv.service-busy=\u670d\u52a1\u5668\u5fd9 +# \u683C\u5F0F\uFF1A\u524D\u7F00 + \u7F51\u5173\u9519\u8BEF\u7801 + "_"+ \u5B50\u9519\u8BEF\u7801 +# open.error_\uFF08\u524D\u7F00\uFF0920000\uFF08\u7F51\u5173\u9519\u8BEF\u7801\uFF09_isp.unknow-error\uFF08\u5B50\u9519\u8BEF\u7801\uFF09 +open.error_20000=\u670D\u52A1\u4E0D\u53EF\u7528 +open.error_20000_isp.unknown-error=\u670D\u52A1\u6682\u4E0D\u53EF\u7528 +open.error_20000_aop.unknown-error=\u670D\u52A1\u6682\u4E0D\u53EF\u7528 +open.error_20000_isp.service-not-available=\u670D\u52A1\u6682\u4E0D\u53EF\u7528 +open.error_20000_isp.gateway-response-timeout=\u7F51\u5173\u54CD\u5E94\u8D85\u65F6 +open.error_20000_isv.service-busy=\u670D\u52A1\u5668\u5FD9 -open.error_20001=\u6388\u6743\u6743\u9650\u4e0d\u8db3 -open.error_20001_aop.invalid-auth-token=\u65e0\u6548\u7684\u8bbf\u95ee\u4ee4\u724c -open.error_20001_aop.auth-token-time-out=\u8bbf\u95ee\u4ee4\u724c\u5df2\u8fc7\u671f -open.error_20001_aop.invalid-app-auth-token=\u65e0\u6548\u7684\u5e94\u7528\u6388\u6743\u4ee4\u724c -open.error_20001_aop.invalid-app-auth-token-no-api=\u5546\u6237\u672a\u6388\u6743\u5f53\u524d\u63a5\u53e3 -open.error_20001_aop.app-auth-token-time-out=\u5e94\u7528\u6388\u6743\u4ee4\u724c\u5df2\u8fc7\u671f -open.error_20001_aop.no-product-reg-by-partner=\u5546\u6237\u672a\u7b7e\u7ea6\u4efb\u4f55\u4ea7\u54c1 +open.error_20001=\u6388\u6743\u6743\u9650\u4E0D\u8DB3 +open.error_20001_aop.invalid-auth-token=\u65E0\u6548\u7684\u8BBF\u95EE\u4EE4\u724C +open.error_20001_aop.auth-token-time-out=\u8BBF\u95EE\u4EE4\u724C\u5DF2\u8FC7\u671F +open.error_20001_aop.invalid-app-auth-token=\u65E0\u6548\u7684\u5E94\u7528\u6388\u6743\u4EE4\u724C +open.error_20001_aop.invalid-app-auth-token-no-api=\u5546\u6237\u672A\u6388\u6743\u5F53\u524D\u63A5\u53E3 +open.error_20001_aop.app-auth-token-time-out=\u5E94\u7528\u6388\u6743\u4EE4\u724C\u5DF2\u8FC7\u671F +open.error_20001_aop.no-product-reg-by-partner=\u5546\u6237\u672A\u7B7E\u7EA6\u4EFB\u4F55\u4EA7\u54C1 -open.error_40001=\u7f3a\u5c11\u5fc5\u9009\u53c2\u6570 -open.error_40001_isv.missing-method=\u7f3a\u5c11\u65b9\u6cd5\u540d\u53c2\u6570 -open.error_40001_isv.missing-signature=\u7f3a\u5c11\u7b7e\u540d\u53c2\u6570 -open.error_40001_isv.missing-signature-type=\u7f3a\u5c11\u7b7e\u540d\u7c7b\u578b\u53c2\u6570 -open.error_40001_isv.missing-signature-key=\u7f3a\u5c11\u7b7e\u540d\u914d\u7f6e -open.error_40001_isv.missing-app-id=\u7f3a\u5c11appId\u53c2\u6570 -open.error_40001_isv.missing-timestamp=\u7f3a\u5c11\u65f6\u95f4\u6233\u53c2\u6570 -open.error_40001_isv.missing-version=\u7f3a\u5c11\u7248\u672c\u53c2\u6570 -open.error_40001_isv.decryption-error-missing-encrypt-type=\u89e3\u5bc6\u51fa\u9519, \u672a\u6307\u5b9a\u52a0\u5bc6\u7b97\u6cd5 +open.error_40001=\u7F3A\u5C11\u5FC5\u9009\u53C2\u6570 +open.error_40001_isv.missing-method=\u7F3A\u5C11\u65B9\u6CD5\u540D\u53C2\u6570 +open.error_40001_isv.missing-signature=\u7F3A\u5C11\u7B7E\u540D\u53C2\u6570 +open.error_40001_isv.missing-signature-type=\u7F3A\u5C11\u7B7E\u540D\u7C7B\u578B\u53C2\u6570 +open.error_40001_isv.missing-signature-key=\u7F3A\u5C11\u7B7E\u540D\u914D\u7F6E +open.error_40001_isv.missing-app-id=\u7F3A\u5C11appId\u53C2\u6570 +open.error_40001_isv.missing-timestamp=\u7F3A\u5C11\u65F6\u95F4\u6233\u53C2\u6570 +open.error_40001_isv.missing-version=\u7F3A\u5C11\u7248\u672C\u53C2\u6570 +open.error_40001_isv.decryption-error-missing-encrypt-type=\u89E3\u5BC6\u51FA\u9519, \u672A\u6307\u5B9A\u52A0\u5BC6\u7B97\u6CD5 -open.error_40002=\u975e\u6cd5\u7684\u53c2\u6570 -open.error_40002_isv.invalid-parameter=\u53c2\u6570\u65e0\u6548 -open.error_40002_isv.upload-fail=\u6587\u4ef6\u4e0a\u4f20\u5931\u8d25 -open.error_40002_isv.invalid-file-extension=\u6587\u4ef6\u6269\u5c55\u540d\u65e0\u6548 -open.error_40002_isv.invalid-file-size={0}\u6587\u4ef6\u5927\u5c0f\u65e0\u6548\uff0c\u5355\u6587\u4ef6\u4e0d\u5f97\u8d85\u8fc7{1} -open.error_40002_isv.invalid-method=\u4e0d\u5b58\u5728\u7684\u65b9\u6cd5\u540d -open.error_40002_isv.invalid-format=\u65e0\u6548\u7684\u6570\u636e\u683c\u5f0f -open.error_40002_isv.invalid-signature-type=\u65e0\u6548\u7684\u7b7e\u540d\u7c7b\u578b -open.error_40002_isv.invalid-signature=\u65e0\u6548\u7b7e\u540d -open.error_40002_isv.invalid-encrypt-type=\u65e0\u6548\u7684\u52a0\u5bc6\u7c7b\u578b -open.error_40002_isv.invalid-encrypt=\u89e3\u5bc6\u5f02\u5e38 -open.error_40002_isv.invalid-app-id=\u65e0\u6548\u7684appId\u53c2\u6570 -open.error_40002_isv.invalid-timestamp=\u975e\u6cd5\u7684\u65f6\u95f4\u6233\u53c2\u6570 -open.error_40002_isv.invalid-charset=\u5b57\u7b26\u96c6\u9519\u8bef -open.error_40002_isv.invalid-digest=\u6458\u8981\u9519\u8bef -open.error_40002_isv.decryption-error-not-valid-encrypt-type=\u89e3\u5bc6\u51fa\u9519\uff0c\u4e0d\u652f\u6301\u7684\u52a0\u5bc6\u7b97\u6cd5 -open.error_40002_isv.decryption-error-not-valid-encrypt-key=\u89e3\u5bc6\u51fa\u9519, \u672a\u914d\u7f6e\u52a0\u5bc6\u5bc6\u94a5\u6216\u52a0\u5bc6\u5bc6\u94a5\u683c\u5f0f\u9519\u8bef -open.error_40002_isv.decryption-error-unknown=\u89e3\u5bc6\u51fa\u9519\uff0c\u672a\u77e5\u5f02\u5e38 -open.error_40002_isv.missing-signature-config=\u9a8c\u7b7e\u51fa\u9519, \u672a\u914d\u7f6e\u5bf9\u5e94\u7b7e\u540d\u7b97\u6cd5\u7684\u516c\u94a5\u6216\u8005\u8bc1\u4e66 -open.error_40002_isv.not-support-app-auth=\u672c\u63a5\u53e3\u4e0d\u652f\u6301\u7b2c\u4e09\u65b9\u4ee3\u7406\u8c03\u7528 -open.error_40002_isv.suspected-attack=\u53ef\u7591\u7684\u653b\u51fb\u8bf7\u6c42 -open.error_40002_isv.invalid-content-type=\u65e0\u6548\u7684content-type +open.error_40002=\u975E\u6CD5\u7684\u53C2\u6570 +open.error_40002_isv.invalid-parameter=\u53C2\u6570\u65E0\u6548 +open.error_40002_isv.upload-fail=\u6587\u4EF6\u4E0A\u4F20\u5931\u8D25 +open.error_40002_isv.invalid-file-extension=\u6587\u4EF6\u6269\u5C55\u540D\u65E0\u6548 +open.error_40002_isv.invalid-file-size={0}\u6587\u4EF6\u5927\u5C0F\u65E0\u6548\uFF0C\u5355\u6587\u4EF6\u4E0D\u5F97\u8D85\u8FC7{1} +open.error_40002_isv.invalid-method=\u4E0D\u5B58\u5728\u7684\u65B9\u6CD5\u540D +open.error_40002_isv.invalid-format=\u65E0\u6548\u7684\u6570\u636E\u683C\u5F0F +open.error_40002_isv.invalid-signature-type=\u65E0\u6548\u7684\u7B7E\u540D\u7C7B\u578B +open.error_40002_isv.invalid-signature=\u65E0\u6548\u7B7E\u540D +open.error_40002_isv.invalid-encrypt-type=\u65E0\u6548\u7684\u52A0\u5BC6\u7C7B\u578B +open.error_40002_isv.invalid-encrypt=\u89E3\u5BC6\u5F02\u5E38 +open.error_40002_isv.invalid-app-id=\u65E0\u6548\u7684appId\u53C2\u6570 +open.error_40002_isv.invalid-timestamp=\u975E\u6CD5\u7684\u65F6\u95F4\u6233\u53C2\u6570 +open.error_40002_isv.invalid-charset=\u5B57\u7B26\u96C6\u9519\u8BEF +open.error_40002_isv.invalid-digest=\u6458\u8981\u9519\u8BEF +open.error_40002_isv.decryption-error-not-valid-encrypt-type=\u89E3\u5BC6\u51FA\u9519\uFF0C\u4E0D\u652F\u6301\u7684\u52A0\u5BC6\u7B97\u6CD5 +open.error_40002_isv.decryption-error-not-valid-encrypt-key=\u89E3\u5BC6\u51FA\u9519, \u672A\u914D\u7F6E\u52A0\u5BC6\u5BC6\u94A5\u6216\u52A0\u5BC6\u5BC6\u94A5\u683C\u5F0F\u9519\u8BEF +open.error_40002_isv.decryption-error-unknown=\u89E3\u5BC6\u51FA\u9519\uFF0C\u672A\u77E5\u5F02\u5E38 +open.error_40002_isv.missing-signature-config=\u9A8C\u7B7E\u51FA\u9519, \u672A\u914D\u7F6E\u5BF9\u5E94\u7B7E\u540D\u7B97\u6CD5\u7684\u516C\u94A5\u6216\u8005\u8BC1\u4E66 +open.error_40002_isv.not-support-app-auth=\u672C\u63A5\u53E3\u4E0D\u652F\u6301\u7B2C\u4E09\u65B9\u4EE3\u7406\u8C03\u7528 +open.error_40002_isv.suspected-attack=\u53EF\u7591\u7684\u653B\u51FB\u8BF7\u6C42 +open.error_40002_isv.invalid-content-type=\u65E0\u6548\u7684content-type -open.error_40004=\u4e1a\u52a1\u5904\u7406\u5931\u8d25 -open.error_40004_=\u4e1a\u52a1\u5904\u7406\u5931\u8d25 +open.error_40004=\u4E1A\u52A1\u5904\u7406\u5931\u8D25 +open.error_40004_=\u4E1A\u52A1\u5904\u7406\u5931\u8D25 -open.error_40006=\u6743\u9650\u4e0d\u8db3 -open.error_40006_isv.insufficient-isv-permissions=\u8bf7\u68c0\u67e5\u914d\u7f6e\u7684\u8d26\u6237\u662f\u5426\u6709\u5f53\u524d\u63a5\u53e3\u6743\u9650 -open.error_40006_isv.insufficient-user-permissions=\u4ee3\u7406\u7684\u5546\u6237\u6ca1\u6709\u5f53\u524d\u63a5\u53e3\u6743\u9650 -open.error_40006_isv.route-no-permissions=\u6ca1\u6709\u5f53\u524d\u63a5\u53e3\u6743\u9650 -open.error_40006_isv.access-forbidden=\u65e0\u6743\u8bbf\u95ee -open.error_40006_isv.ip-forbidden=IP\u65e0\u6743\u8bbf\u95ee +open.error_40006=\u6743\u9650\u4E0D\u8DB3 +open.error_40006_isv.insufficient-isv-permissions=\u8BF7\u68C0\u67E5\u914D\u7F6E\u7684\u8D26\u6237\u662F\u5426\u6709\u5F53\u524D\u63A5\u53E3\u6743\u9650 +open.error_40006_isv.insufficient-user-permissions=\u4EE3\u7406\u7684\u5546\u6237\u6CA1\u6709\u5F53\u524D\u63A5\u53E3\u6743\u9650 +open.error_40006_isv.route-no-permissions=\u6CA1\u6709\u5F53\u524D\u63A5\u53E3\u6743\u9650 +open.error_40006_isv.access-forbidden=\u65E0\u6743\u8BBF\u95EE +open.error_40006_isv.ip-forbidden=IP\u65E0\u6743\u8BBF\u95EE diff --git a/sop-common/sop-service-common/pom.xml b/sop-common/sop-service-common/pom.xml index 7b06defb..1e7a1bde 100644 --- a/sop-common/sop-service-common/pom.xml +++ b/sop-common/sop-service-common/pom.xml @@ -5,14 +5,14 @@ com.gitee.sop - sop-parent - 4.0.3-SNAPSHOT - ../../pom.xml + sop-common + 4.3.2-SNAPSHOT + ../pom.xml 4.0.0 sop-service-common - 4.0.3-SNAPSHOT + 4.3.2-SNAPSHOT jar @@ -65,7 +65,6 @@ org.springframework spring-webmvc - true diff --git a/sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/configuration/AlipayServiceConfiguration.java b/sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/configuration/AlipayServiceConfiguration.java index 9525532b..7da6a510 100644 --- a/sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/configuration/AlipayServiceConfiguration.java +++ b/sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/configuration/AlipayServiceConfiguration.java @@ -1,28 +1,9 @@ package com.gitee.sop.servercommon.configuration; -import com.gitee.sop.servercommon.bean.ServiceConfig; -import com.gitee.sop.servercommon.param.SopHandlerMethodArgumentResolver; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter; - /** * 具备支付宝开放平台服务提供能力 * @author tanghc */ public class AlipayServiceConfiguration extends BaseServiceConfiguration { - static { - // 默认版本号为1.0 - ServiceConfig.getInstance().setDefaultVersion("1.0"); - } - - @Autowired - private RequestMappingHandlerAdapter requestMappingHandlerAdapter; - - @Override - protected void doAfter() { - super.doAfter(); - SopHandlerMethodArgumentResolver sopHandlerMethodArgumentResolver = ServiceConfig.getInstance().getMethodArgumentResolver(); - sopHandlerMethodArgumentResolver.setRequestMappingHandlerAdapter(requestMappingHandlerAdapter); - } } diff --git a/sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/configuration/GlobalExceptionHandler.java b/sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/configuration/GlobalExceptionHandler.java index 610fc271..a5c3b3fc 100644 --- a/sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/configuration/GlobalExceptionHandler.java +++ b/sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/configuration/GlobalExceptionHandler.java @@ -22,6 +22,7 @@ import java.nio.charset.StandardCharsets; @Slf4j public class GlobalExceptionHandler { + /** * 与网关约定好的状态码,表示业务出错 */ @@ -33,10 +34,15 @@ public class GlobalExceptionHandler { private static final int SYSTEM_ERROR_CODE = 5050; /** - * 返回错误信息headerName + * header中的错误code */ private static final String X_SERVICE_ERROR_HEADER_NAME = "x-service-error-code"; + /** + * header中的错误信息 + */ + private static final String X_SERVICE_ERROR_MESSAGE = "x-service-error-message"; + /** * 捕获手动抛出的异常 * @@ -63,7 +69,6 @@ public class GlobalExceptionHandler { @ExceptionHandler(Exception.class) @ResponseBody public Object exceptionHandler(HttpServletRequest request, HttpServletResponse response, Exception exception) { - response.addHeader(X_SERVICE_ERROR_HEADER_NAME, String.valueOf(SYSTEM_ERROR_CODE)); log.error("系统错误", exception); StringBuilder msg = new StringBuilder(); msg.append(exception.getMessage()); @@ -72,9 +77,12 @@ public class GlobalExceptionHandler { int lineCount = 5; for (int i = 0; i < stackTrace.length && i < lineCount; i++) { StackTraceElement stackTraceElement = stackTrace[i]; - msg.append("
at ").append(stackTraceElement.toString()); + msg.append("\n at ").append(stackTraceElement.toString()); } - response.setHeader("x-service-error-message", UriUtils.encode(msg.toString(), StandardCharsets.UTF_8)); + // 需要设置两个值,这样网关会收到错误信息 + // 并且会统计到监控当中 + response.setHeader(X_SERVICE_ERROR_HEADER_NAME, String.valueOf(SYSTEM_ERROR_CODE)); + response.setHeader(X_SERVICE_ERROR_MESSAGE, UriUtils.encode(msg.toString(), StandardCharsets.UTF_8)); return this.processError(request, response, new ServiceException("系统繁忙")); } diff --git a/sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/configuration/ServiceConfiguration.java b/sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/configuration/ServiceConfiguration.java index 07294768..460497da 100644 --- a/sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/configuration/ServiceConfiguration.java +++ b/sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/configuration/ServiceConfiguration.java @@ -9,7 +9,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.context.annotation.Bean; import org.springframework.core.env.Environment; -import org.springframework.scheduling.TaskScheduler; +import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler; import java.util.Map; @@ -22,7 +22,12 @@ public class ServiceConfiguration extends SpringmvcConfiguration { @Bean @ConditionalOnMissingBean @ConditionalOnProperty("spring.cloud.nacos.discovery.server-addr") - public NacosWatch nacosWatch(NacosDiscoveryProperties nacosDiscoveryProperties, ObjectProvider taskScheduler, Environment environment, NacosServiceManager nacosServiceManager) { + public NacosWatch nacosWatch( + NacosServiceManager nacosServiceManager, + NacosDiscoveryProperties nacosDiscoveryProperties, + ObjectProvider taskScheduler, + Environment environment + ) { Map metadata = nacosDiscoveryProperties.getMetadata(); String contextPath = environment.getProperty(METADATA_SERVER_CONTEXT_PATH); // 将context-path信息加入到metadata中 diff --git a/sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/configuration/SpringmvcConfiguration.java b/sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/configuration/SpringmvcConfiguration.java index 4162f0f8..dd0405ed 100644 --- a/sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/configuration/SpringmvcConfiguration.java +++ b/sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/configuration/SpringmvcConfiguration.java @@ -3,8 +3,11 @@ package com.gitee.sop.servercommon.configuration; import com.gitee.sop.servercommon.bean.ServiceConfig; import com.gitee.sop.servercommon.interceptor.ServiceContextInterceptor; import com.gitee.sop.servercommon.message.ServiceErrorFactory; +import com.gitee.sop.servercommon.param.SopHandlerMethodArgumentResolver; import com.gitee.sop.servercommon.route.ServiceRouteController; import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.config.BeanPostProcessor; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.context.annotation.Bean; import org.springframework.http.converter.HttpMessageConverter; @@ -12,6 +15,7 @@ import org.springframework.http.converter.StringHttpMessageConverter; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; +import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter; import javax.annotation.PostConstruct; import java.nio.charset.StandardCharsets; @@ -21,14 +25,28 @@ import java.util.List; * @author tanghc */ @Slf4j -public class SpringmvcConfiguration implements WebMvcConfigurer { +public class SpringmvcConfiguration implements WebMvcConfigurer, BeanPostProcessor { public static final String METADATA_SERVER_CONTEXT_PATH = "server.servlet.context-path"; + static { + ServiceConfig.getInstance().setDefaultVersion("1.0"); + } + public SpringmvcConfiguration() { ServiceConfig.getInstance().getI18nModules().add("i18n/isp/bizerror"); } + @Override + public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { + if (bean instanceof RequestMappingHandlerAdapter) { + RequestMappingHandlerAdapter requestMappingHandlerAdapter = (RequestMappingHandlerAdapter) bean; + SopHandlerMethodArgumentResolver sopHandlerMethodArgumentResolver = ServiceConfig.getInstance().getMethodArgumentResolver(); + sopHandlerMethodArgumentResolver.setRequestMappingHandlerAdapter(requestMappingHandlerAdapter); + } + return bean; + } + @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { // 支持swagger-bootstrap-ui首页 diff --git a/sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/interceptor/ServiceContextInterceptor.java b/sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/interceptor/ServiceContextInterceptor.java index f02e54ed..f32a32c9 100644 --- a/sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/interceptor/ServiceContextInterceptor.java +++ b/sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/interceptor/ServiceContextInterceptor.java @@ -1,6 +1,11 @@ package com.gitee.sop.servercommon.interceptor; +import com.alibaba.fastjson.JSONObject; +import com.gitee.sop.servercommon.annotation.Open; +import com.gitee.sop.servercommon.bean.OpenContextImpl; import com.gitee.sop.servercommon.bean.ServiceContext; +import com.gitee.sop.servercommon.util.OpenUtil; +import org.springframework.web.method.HandlerMethod; import org.springframework.web.servlet.handler.HandlerInterceptorAdapter; import javax.servlet.http.HttpServletRequest; @@ -16,6 +21,7 @@ public class ServiceContextInterceptor extends HandlerInterceptorAdapter { ServiceContext context = ServiceContext.getCurrentContext(); context.setRequest(request); context.setResponse(response); + this.initOpenContextWithNoParam(context, request, handler); return true; } @@ -23,4 +29,26 @@ public class ServiceContextInterceptor extends HandlerInterceptorAdapter { public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { ServiceContext.getCurrentContext().unset(); } + + /** + * 修复没有参数的情况下无法获取OpenContext + * @param context ServiceContext + * @param request HttpServletRequest + * @param handler HandlerMethod + */ + private void initOpenContextWithNoParam(ServiceContext context, HttpServletRequest request, Object handler) { + if (handler instanceof HandlerMethod) { + HandlerMethod handlerMethod = (HandlerMethod) handler; + Open open = handlerMethod.getMethodAnnotation(Open.class); + if (open != null && getArrayLength(handlerMethod.getMethodParameters()) == 0) { + JSONObject requestParams = OpenUtil.getRequestParams(request); + OpenContextImpl openContext = new OpenContextImpl(requestParams); + context.setOpenContext(openContext); + } + } + } + + private static int getArrayLength(T[] arr) { + return arr == null ? 0 : arr.length; + } } diff --git a/sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/param/ApiArgumentResolver.java b/sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/param/ApiArgumentResolver.java index bed09eaf..abb95648 100644 --- a/sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/param/ApiArgumentResolver.java +++ b/sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/param/ApiArgumentResolver.java @@ -56,6 +56,8 @@ public class ApiArgumentResolver implements SopHandlerMethodArgumentResolver { private final Map argumentResolverCache = new ConcurrentHashMap<>(256); + private static List NEED_INIT_OPEN_CONTEXT = new ArrayList<>(16); + private ParamValidator paramValidator = new ServiceParamValidator(); private static Class pushBuilder; @@ -89,8 +91,8 @@ public class ApiArgumentResolver implements SopHandlerMethodArgumentResolver { return false; } Class paramType = methodParameter.getParameterType(); - // 排除的 - boolean exclude = ( + // 特殊参数 + boolean special = ( WebRequest.class.isAssignableFrom(paramType) || ServletRequest.class.isAssignableFrom(paramType) || MultipartRequest.class.isAssignableFrom(paramType) || @@ -107,12 +109,17 @@ public class ApiArgumentResolver implements SopHandlerMethodArgumentResolver { OutputStream.class.isAssignableFrom(paramType) || Writer.class.isAssignableFrom(paramType) ); - // 除此之外都匹配 - boolean support = !exclude; - if (support) { - this.wrapSingleParam(methodParameter, open); + // 特殊参数只需要初始化OpenContext + if (special) { + NEED_INIT_OPEN_CONTEXT.add(methodParameter); } - return support; + this.wrapSingleParam(methodParameter, open); + return true; + } + + + private boolean isOnlyInitOpenContext(MethodParameter methodParameter) { + return NEED_INIT_OPEN_CONTEXT.contains(methodParameter); } /** @@ -136,6 +143,20 @@ public class ApiArgumentResolver implements SopHandlerMethodArgumentResolver { , NativeWebRequest nativeWebRequest , WebDataBinderFactory webDataBinderFactory ) throws Exception { + // 特殊参数只需要初始化OpenContext + if (isOnlyInitOpenContext(methodParameter)) { + this.initOpenContextImpl(nativeWebRequest); + HandlerMethodArgumentResolver resolver = getOtherArgumentResolver(methodParameter); + if (resolver != null) { + return resolver.resolveArgument( + methodParameter + , modelAndViewContainer + , nativeWebRequest + , webDataBinderFactory + ); + } + return null; + } nativeWebRequest = new SopServletWebRequest( (HttpServletRequest) nativeWebRequest.getNativeRequest(), (HttpServletResponse) nativeWebRequest.getNativeResponse() @@ -143,7 +164,6 @@ public class ApiArgumentResolver implements SopHandlerMethodArgumentResolver { Object paramObj = this.getParamObject(methodParameter, nativeWebRequest); if (paramObj != null) { // JSR-303验证 - if (paramObj instanceof SingleParameterWrapper) { SingleParameterWrapper parameterWrapper = (SingleParameterWrapper) paramObj; paramValidator.validateBizParam(parameterWrapper.getWrapperObject()); @@ -171,6 +191,14 @@ public class ApiArgumentResolver implements SopHandlerMethodArgumentResolver { return null; } + private OpenContextImpl initOpenContextImpl(NativeWebRequest nativeWebRequest) { + HttpServletRequest request = (HttpServletRequest) nativeWebRequest.getNativeRequest(); + ServiceContext currentContext = ServiceContext.getCurrentContext(); + JSONObject requestParams = OpenUtil.getRequestParams(request); + OpenContextImpl openContext = new OpenContextImpl(requestParams); + currentContext.setOpenContext(openContext); + return openContext; + } /** * 获取参数对象,将request中的参数绑定到实体对象中去 @@ -181,11 +209,10 @@ public class ApiArgumentResolver implements SopHandlerMethodArgumentResolver { */ protected Object getParamObject(MethodParameter methodParameter, NativeWebRequest nativeWebRequest) { HttpServletRequest request = (HttpServletRequest) nativeWebRequest.getNativeRequest(); - ServiceContext currentContext = ServiceContext.getCurrentContext(); - JSONObject requestParams = OpenUtil.getRequestParams(request); - OpenContextImpl openContext = new OpenContextImpl(requestParams); - currentContext.setOpenContext(openContext); - String bizContent = requestParams.getString(ParamNames.BIZ_CONTENT_NAME); + OpenContextImpl openContext = initOpenContextImpl(nativeWebRequest); + Map requestParams = openContext.getParameterMap(); + Object bizObj = requestParams.get(ParamNames.BIZ_CONTENT_NAME); + String bizContent = bizObj == null ? null : bizObj.toString(); if (bizContent == null) { return null; } @@ -203,10 +230,10 @@ public class ApiArgumentResolver implements SopHandlerMethodArgumentResolver { return singleParameterWrapper; } Object param; - // 如果是json字符串 - if (JSONValidator.from(bizContent).validate()) { + try { param = JSON.parseObject(bizContent, parameterType); - } else { + } catch (Exception e) { + log.error("非json参数, \nbiz_content:{}\nrequestParams:{}", bizContent, requestParams, e); // 否则认为是 aa=1&bb=33 形式 Map query = OpenUtil.parseQueryToMap(bizContent); param = new JSONObject(query).toJavaObject(parameterType); diff --git a/sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/param/ValidationAnnotationDefinitionFactory.java b/sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/param/ValidationAnnotationDefinitionFactory.java index 83bf8428..699e9804 100644 --- a/sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/param/ValidationAnnotationDefinitionFactory.java +++ b/sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/param/ValidationAnnotationDefinitionFactory.java @@ -17,6 +17,8 @@ import org.hibernate.validator.constraints.SafeHtml; import org.hibernate.validator.constraints.ScriptAssert; import org.hibernate.validator.constraints.URL; import org.hibernate.validator.constraints.UniqueElements; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestParam; import javax.validation.constraints.AssertFalse; import javax.validation.constraints.AssertTrue; @@ -40,7 +42,9 @@ import javax.validation.constraints.Positive; import javax.validation.constraints.PositiveOrZero; import javax.validation.constraints.Size; import java.lang.annotation.Annotation; +import java.util.Arrays; import java.util.HashMap; +import java.util.List; import java.util.Map; /** @@ -50,6 +54,8 @@ public class ValidationAnnotationDefinitionFactory { private static final Map, ValidationAnnotationBuilder> store = new HashMap<>(64); + private static final List> excludeList = Arrays.asList(RequestParam.class, PathVariable.class); + static { new BaseValidationAnnotationBuilder(){}; @@ -107,6 +113,9 @@ public class ValidationAnnotationDefinitionFactory { public static ValidationAnnotationDefinition build(Annotation annotation) { Class jsr303Anno = annotation.annotationType(); + if (excludeList.contains(jsr303Anno)) { + return null; + } ValidationAnnotationBuilder validationAnnotationBuilder = store.get(jsr303Anno); if (validationAnnotationBuilder == null) { return null; diff --git a/sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/route/ServiceRouteController.java b/sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/route/ServiceRouteController.java index 3a6289e4..b80ea520 100644 --- a/sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/route/ServiceRouteController.java +++ b/sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/route/ServiceRouteController.java @@ -2,7 +2,6 @@ package com.gitee.sop.servercommon.route; import com.gitee.sop.servercommon.bean.ServiceApiInfo; import com.gitee.sop.servercommon.util.OpenUtil; -import lombok.Getter; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.env.Environment; @@ -17,12 +16,10 @@ import javax.servlet.http.HttpServletResponse; * @author tanghc */ @Slf4j -@Getter @RestController public class ServiceRouteController { private static final String SECRET = "a3d9sf!1@odl90zd>fkASwq"; - private static final String HEADER_RESTFUL = "restful"; private static final ApiMetaBuilder apiMetaBuilder = new ApiMetaBuilder(); @@ -42,10 +39,6 @@ public class ServiceRouteController { return getServiceRouteInfo(request, response); } - protected ApiMetaBuilder getApiMetaBuilder() { - return new ApiMetaBuilder(); - } - protected ServiceRouteInfo getServiceRouteInfo(HttpServletRequest request, HttpServletResponse response) { String serviceId = environment.getProperty("spring.application.name"); if (serviceId == null) { diff --git a/sop-example/pom.xml b/sop-example/pom.xml index a1ebdf03..aa73aaa8 100644 --- a/sop-example/pom.xml +++ b/sop-example/pom.xml @@ -5,7 +5,7 @@ com.gitee.sop sop-parent - 4.0.3-SNAPSHOT + 4.3.2-SNAPSHOT ../pom.xml diff --git a/sop-example/sop-springmvc/pom.xml b/sop-example/sop-springmvc/pom.xml index 95201329..b3e274be 100644 --- a/sop-example/sop-springmvc/pom.xml +++ b/sop-example/sop-springmvc/pom.xml @@ -5,7 +5,7 @@ com.gitee.sop sop-parent - 4.0.3-SNAPSHOT + 4.3.2-SNAPSHOT ../../pom.xml @@ -26,7 +26,7 @@ com.gitee.sop sop-service-common - 4.0.3-SNAPSHOT + ${project.version} diff --git a/sop-example/sop-springmvc/src/main/java/com/gitee/app/config/OpenServiceConfig.java b/sop-example/sop-springmvc/src/main/java/com/gitee/app/config/OpenServiceConfig.java index f9ddde68..3ff8e33b 100644 --- a/sop-example/sop-springmvc/src/main/java/com/gitee/app/config/OpenServiceConfig.java +++ b/sop-example/sop-springmvc/src/main/java/com/gitee/app/config/OpenServiceConfig.java @@ -6,7 +6,6 @@ import com.alibaba.nacos.api.naming.NamingService; import com.alibaba.nacos.api.naming.pojo.Instance; import com.alibaba.nacos.client.naming.utils.NetUtils; import com.alibaba.nacos.spring.context.annotation.discovery.EnableNacosDiscovery; -import com.gitee.sop.servercommon.bean.ServiceConfig; import com.gitee.sop.servercommon.configuration.SpringmvcConfiguration; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; @@ -27,10 +26,6 @@ public class OpenServiceConfig extends SpringmvcConfiguration { public static final String SERVER_PORT = "server.port"; public static final String METADATA_TIME_STARTUP = "server.startup-time"; - static { - ServiceConfig.getInstance().setDefaultVersion("1.0"); - } - @Value("${spring.application.name}") private String serviceId; @Value("${server.port}") diff --git a/sop-example/sop-springmvc/src/main/java/com/gitee/app/controller/HomeController.java b/sop-example/sop-springmvc/src/main/java/com/gitee/app/controller/HomeController.java index 2500930b..8141a359 100644 --- a/sop-example/sop-springmvc/src/main/java/com/gitee/app/controller/HomeController.java +++ b/sop-example/sop-springmvc/src/main/java/com/gitee/app/controller/HomeController.java @@ -1,6 +1,7 @@ package com.gitee.app.controller; import com.gitee.app.model.Goods; +import com.gitee.app.model.StoryParam; import com.gitee.sop.servercommon.annotation.Open; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; @@ -56,4 +57,12 @@ public class HomeController { return goods; } + @Open("springmvc.path.same") + @RequestMapping("iam_same_path") + @ResponseBody + public Object iam_same_path(StoryParam param) { + param.setName(param.getName() + " mvc.."); + return param; + } + } diff --git a/sop-example/sop-springmvc/src/main/java/com/gitee/app/model/StoryParam.java b/sop-example/sop-springmvc/src/main/java/com/gitee/app/model/StoryParam.java new file mode 100644 index 00000000..5fcebfff --- /dev/null +++ b/sop-example/sop-springmvc/src/main/java/com/gitee/app/model/StoryParam.java @@ -0,0 +1,23 @@ +package com.gitee.app.model; + +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import org.hibernate.validator.constraints.Length; + +import javax.validation.constraints.Max; +import javax.validation.constraints.NotBlank; + +@Data +public class StoryParam { + @ApiModelProperty(value = "故事ID", example = "111") + private int id; + + @NotBlank(message = "name不能为空") + @Length(max = 20, message = "name长度不能超过20") + @ApiModelProperty(value = "故事名称", required = true, example = "白雪公主", position = 3) + private String name; + + @ApiModelProperty(value = "备注 (第二)", example = "xx", position = 2) + @Length(max = 64, message = "长度不能超过64") + private String remark; +} \ No newline at end of file diff --git a/sop-example/sop-springmvc/src/main/webapp/WEB-INF/spring/appServlet/servlet-context.xml b/sop-example/sop-springmvc/src/main/webapp/WEB-INF/spring/appServlet/servlet-context.xml index e8140fce..b5893103 100644 --- a/sop-example/sop-springmvc/src/main/webapp/WEB-INF/spring/appServlet/servlet-context.xml +++ b/sop-example/sop-springmvc/src/main/webapp/WEB-INF/spring/appServlet/servlet-context.xml @@ -31,12 +31,7 @@ - - - - - - + diff --git a/sop-example/sop-story/pom.xml b/sop-example/sop-story/pom.xml index 2f5c0f33..52fc6d81 100644 --- a/sop-example/sop-story/pom.xml +++ b/sop-example/sop-story/pom.xml @@ -4,7 +4,7 @@ com.gitee.sop sop-parent - 4.0.3-SNAPSHOT + 4.3.2-SNAPSHOT ../../pom.xml @@ -19,7 +19,7 @@ com.gitee.sop sop-service-common - 4.0.3-SNAPSHOT + ${project.version} @@ -53,7 +53,7 @@ com.github.xiaoymin swagger-bootstrap-ui - 1.9.5 + 1.9.6 io.springfox diff --git a/sop-example/sop-story/src/main/java/com/gitee/sop/storyweb/controller/Example1001_BaseController.java b/sop-example/sop-story/src/main/java/com/gitee/sop/storyweb/controller/Example1001_BaseController.java index 238e2370..9745245c 100644 --- a/sop-example/sop-story/src/main/java/com/gitee/sop/storyweb/controller/Example1001_BaseController.java +++ b/sop-example/sop-story/src/main/java/com/gitee/sop/storyweb/controller/Example1001_BaseController.java @@ -7,22 +7,28 @@ import com.gitee.sop.servercommon.annotation.Open; import com.gitee.sop.servercommon.bean.OpenContext; import com.gitee.sop.servercommon.bean.ServiceContext; import com.gitee.sop.servercommon.exception.ServiceException; +import com.gitee.sop.storyweb.controller.param.ArrayElementParam; import com.gitee.sop.storyweb.controller.param.CategoryParam; import com.gitee.sop.storyweb.controller.param.LargeTextParam; +import com.gitee.sop.storyweb.controller.param.MemberInfoGetParam; import com.gitee.sop.storyweb.controller.param.StoryParam; import com.gitee.sop.storyweb.controller.param.TypeEnum; import com.gitee.sop.storyweb.controller.result.CategoryResult; +import com.gitee.sop.storyweb.controller.result.MemberInfoGetResult; +import com.gitee.sop.storyweb.controller.result.MemberInfoGetResultMemberInfo; import com.gitee.sop.storyweb.controller.result.StoryResult; import com.gitee.sop.storyweb.controller.result.TestResult; import com.gitee.sop.storyweb.controller.result.TreeResult; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; import org.springframework.util.StringUtils; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; @@ -50,6 +56,9 @@ import java.util.Map; @Api(tags = "故事接口") public class Example1001_BaseController { + @Value("${server.port}") + private int port; + // http://localhost:2222/stroy/get // 原生的接口,可正常调用 @RequestMapping("/get") @@ -71,7 +80,7 @@ public class Example1001_BaseController { public StoryResult get_v1(StoryParam param) { StoryResult story = new StoryResult(); story.setId(1L); - story.setName("海底小纵队(story.get1.0), " + "param:" + param); + story.setName("海底小纵队(story.get1.0), " + "param:" + param + ", port:" + port); return story; } @@ -102,6 +111,27 @@ public class Example1001_BaseController { return result; } + @Open(value = "story.system.param.get2") + @GetMapping("/get/system/param/v2") + public StoryResult systemParam2() { + StoryResult result = new StoryResult(); + OpenContext openContext = ServiceContext.getCurrentContext().getOpenContext(); + String token = openContext.getAppAuthToken(); + result.setName(token); + return result; + } + + @Open(value = "story.system.param.get3") + @GetMapping("/get/system/param/v3") + public StoryResult systemParam3(HttpServletRequest request) { + System.out.println(request.getParameter("app_id")); + StoryResult result = new StoryResult(); + OpenContext openContext = ServiceContext.getCurrentContext().getOpenContext(); + String token = openContext.getAppAuthToken(); + result.setName(token); + return result; + } + // 参数绑定,少量参数可以这样写,参数多了建议放进类里面 @Open(value = "story.oneparam") @GetMapping("/oneParam/v1") @@ -134,7 +164,7 @@ public class Example1001_BaseController { // 参数绑定 @Open(value = "story.param.bind", mergeResult = false) @RequestMapping("/get/param/v1") - public StoryResult param(int id, String name) { + public StoryResult param(@RequestParam("id") Integer id, @RequestParam String name) { StoryResult result = new StoryResult(); result.setName("参数绑定:id:" + id + ", name:" + name); return result; @@ -143,7 +173,7 @@ public class Example1001_BaseController { // 忽略验证 @ApiOperation(value = "忽略签名验证", notes = "忽略签名验证") @Open(value = "story.get.ignore", ignoreValidate = true) - @PostMapping(value = "/get/ignore/v1") + @RequestMapping(value = "/get/ignore/v1") public StoryResult getStory21(@RequestBody StoryParam story) { StoryResult result = new StoryResult(); result.setId((long) story.getId()); @@ -151,6 +181,16 @@ public class Example1001_BaseController { return result; } + @ApiOperation(value = "接收数组", notes = "接收数组") + @Open(value = "story.listparam") + @PostMapping(value = "/get/listparam/v1") + public StoryResult listparam(@RequestBody ArrayElementParam story) { + StoryResult result = new StoryResult(); + result.setId(11L); + result.setName(JSON.toJSONString(story)); + return result; + } + @Open(value = "story.get.large") @RequestMapping("/get/large/v1") public StoryResult getStoryLarge(LargeTextParam param) { @@ -259,4 +299,17 @@ public class Example1001_BaseController { return map; } + @Open(value = "member.info.get") + @RequestMapping("/member/info/get") + public MemberInfoGetResult memberInfoGet(MemberInfoGetParam param) { + MemberInfoGetResult result = new MemberInfoGetResult(); + MemberInfoGetResultMemberInfo memberInfo = new MemberInfoGetResultMemberInfo(); + memberInfo.setIsVip((byte)1); + memberInfo.setVipEndtime(new Date()); + result.setName(JSON.toJSONString(param)); + result.setId(11); + result.setMemberInfo(memberInfo); + return result; + } + } diff --git a/sop-example/sop-story/src/main/java/com/gitee/sop/storyweb/controller/Example1002_FileUploadController.java b/sop-example/sop-story/src/main/java/com/gitee/sop/storyweb/controller/Example1002_FileUploadController.java index 815f1eb1..accfa90c 100644 --- a/sop-example/sop-story/src/main/java/com/gitee/sop/storyweb/controller/Example1002_FileUploadController.java +++ b/sop-example/sop-story/src/main/java/com/gitee/sop/storyweb/controller/Example1002_FileUploadController.java @@ -10,6 +10,8 @@ import io.swagger.annotations.ApiOperation; import io.swagger.annotations.Extension; import io.swagger.annotations.ExtensionProperty; import org.apache.commons.io.IOUtils; +import org.springframework.http.MediaType; +import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.multipart.MultipartFile; @@ -38,7 +40,7 @@ public class Example1002_FileUploadController { */ @ApiOperation(value = "文件上传例1", notes = "上传文件demo") @Open("file.upload") - @RequestMapping("file1") + @PostMapping(value = "file1", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) public FileUploadResult file1(FileUploadParam param) { // 获取上传的文件 MultipartFile file1 = param.getFile1(); @@ -64,7 +66,7 @@ public class Example1002_FileUploadController { // 多文件上传、不确定文件数量上传,必须申明下面这句,否则沙盒界面不会出现上传控件 , extensions = @Extension(properties = @ExtensionProperty(name = "multiple", value = "multiple"))) @Open("file.upload2") - @RequestMapping("file2") + @PostMapping(value = "file2", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) public FileUploadResult file2(FileUploadParam2 param, HttpServletRequest request) { System.out.println(param.getRemark()); FileUploadResult result = new FileUploadResult(); @@ -78,7 +80,7 @@ public class Example1002_FileUploadController { } @Open("file.upload3") - @RequestMapping("file3") + @PostMapping(value = "file3", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) public FileUploadResult file3(FileUploadParam2 param, HttpServletRequest request) { System.out.println(param.getRemark()); FileUploadResult result = new FileUploadResult(); diff --git a/sop-example/sop-story/src/main/java/com/gitee/sop/storyweb/controller/Example1008_RestfulController.java b/sop-example/sop-story/src/main/java/com/gitee/sop/storyweb/controller/Example1008_RestfulController.java index 36de18b0..2c7af9e7 100644 --- a/sop-example/sop-story/src/main/java/com/gitee/sop/storyweb/controller/Example1008_RestfulController.java +++ b/sop-example/sop-story/src/main/java/com/gitee/sop/storyweb/controller/Example1008_RestfulController.java @@ -4,6 +4,8 @@ import com.gitee.sop.servercommon.util.UploadUtil; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import lombok.Data; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.core.env.Environment; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; @@ -25,6 +27,8 @@ import java.util.Collection; @Api(tags = "食物接口") public class Example1008_RestfulController { + @Autowired + private Environment environment; // http://localhost:8081/rest/story-service/food/getFoodById?id=1 网关入口 // http://localhost:2222/food/getFoodById/?id=12 本地入口 @@ -33,7 +37,7 @@ public class Example1008_RestfulController { public Food getFoodById(Integer id) { Food food = new Food(); food.setId(id); - food.setName("香蕉"); + food.setName("香蕉, " + environment.getProperty("server.port")); food.setPrice(new BigDecimal(20.00)); return food; } diff --git a/sop-example/sop-story/src/main/java/com/gitee/sop/storyweb/controller/Example1009_TimeoutController.java b/sop-example/sop-story/src/main/java/com/gitee/sop/storyweb/controller/Example1009_TimeoutController.java new file mode 100644 index 00000000..0079893f --- /dev/null +++ b/sop-example/sop-story/src/main/java/com/gitee/sop/storyweb/controller/Example1009_TimeoutController.java @@ -0,0 +1,29 @@ +package com.gitee.sop.storyweb.controller; + +import com.gitee.sop.servercommon.annotation.Open; +import com.gitee.sop.storyweb.controller.param.StoryParam; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.concurrent.TimeUnit; + +/** + * 模拟超时设置 + * + * @author tanghc + */ +@RestController +public class Example1009_TimeoutController { + + @Open("goods.timeout") + @RequestMapping("timeoutTest") + public Object timeout(StoryParam param) { + // 模拟耗时操作,耗时10秒 + try { + TimeUnit.SECONDS.sleep(10); + } catch (InterruptedException e) { + e.printStackTrace(); + } + return param; + } +} diff --git a/sop-example/sop-story/src/main/java/com/gitee/sop/storyweb/controller/Example1010_SamePathController.java b/sop-example/sop-story/src/main/java/com/gitee/sop/storyweb/controller/Example1010_SamePathController.java new file mode 100644 index 00000000..11c1fc8c --- /dev/null +++ b/sop-example/sop-story/src/main/java/com/gitee/sop/storyweb/controller/Example1010_SamePathController.java @@ -0,0 +1,22 @@ +package com.gitee.sop.storyweb.controller; + +import com.gitee.sop.servercommon.annotation.Open; +import com.gitee.sop.storyweb.controller.param.StoryParam; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + * 模拟超时设置 + * + * @author tanghc + */ +@RestController +public class Example1010_SamePathController { + + @Open("story.path.same") + @RequestMapping("iam_same_path") + public Object iam_same_path(StoryParam param) { + param.setName(param.getName() + " story.."); + return param; + } +} diff --git a/sop-example/sop-story/src/main/java/com/gitee/sop/storyweb/controller/param/ArrayElementParam.java b/sop-example/sop-story/src/main/java/com/gitee/sop/storyweb/controller/param/ArrayElementParam.java new file mode 100644 index 00000000..0f2073bd --- /dev/null +++ b/sop-example/sop-story/src/main/java/com/gitee/sop/storyweb/controller/param/ArrayElementParam.java @@ -0,0 +1,20 @@ +package com.gitee.sop.storyweb.controller.param; + +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.util.List; + +/** + * @author tanghc + */ +@Data +public class ArrayElementParam { + + @ApiModelProperty(value = "名字", required = true, example = "白雪公主", position = 1) + private String name; + + @ApiModelProperty(value = "数组", required = true, dataType = "List", example = "白雪公主", position = 2) + private List list; + +} diff --git a/sop-example/sop-story/src/main/java/com/gitee/sop/storyweb/controller/param/MemberInfoGetParam.java b/sop-example/sop-story/src/main/java/com/gitee/sop/storyweb/controller/param/MemberInfoGetParam.java new file mode 100644 index 00000000..4f17974d --- /dev/null +++ b/sop-example/sop-story/src/main/java/com/gitee/sop/storyweb/controller/param/MemberInfoGetParam.java @@ -0,0 +1,34 @@ +package com.gitee.sop.storyweb.controller.param; + +/** + * 请求参数 + */ +public class MemberInfoGetParam { + private String name; + private Integer age; + private String address; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Integer getAge() { + return age; + } + + public void setAge(Integer age) { + this.age = age; + } + + public String getAddress() { + return address; + } + + public void setAddress(String address) { + this.address = address; + } +} diff --git a/sop-example/sop-story/src/main/java/com/gitee/sop/storyweb/controller/result/MemberInfoGetResult.java b/sop-example/sop-story/src/main/java/com/gitee/sop/storyweb/controller/result/MemberInfoGetResult.java new file mode 100644 index 00000000..5dd5c69f --- /dev/null +++ b/sop-example/sop-story/src/main/java/com/gitee/sop/storyweb/controller/result/MemberInfoGetResult.java @@ -0,0 +1,39 @@ +package com.gitee.sop.storyweb.controller.result; + +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * 接口返回结果 + */ +public class MemberInfoGetResult { + + private Integer id; + private String name; + + @JsonProperty("member_info") + private MemberInfoGetResultMemberInfo memberInfo; + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public MemberInfoGetResultMemberInfo getMemberInfo() { + return memberInfo; + } + + public void setMemberInfo(MemberInfoGetResultMemberInfo memberInfo) { + this.memberInfo = memberInfo; + } +} diff --git a/sop-example/sop-story/src/main/java/com/gitee/sop/storyweb/controller/result/MemberInfoGetResultMemberInfo.java b/sop-example/sop-story/src/main/java/com/gitee/sop/storyweb/controller/result/MemberInfoGetResultMemberInfo.java new file mode 100644 index 00000000..9b04cdd1 --- /dev/null +++ b/sop-example/sop-story/src/main/java/com/gitee/sop/storyweb/controller/result/MemberInfoGetResultMemberInfo.java @@ -0,0 +1,29 @@ +package com.gitee.sop.storyweb.controller.result; + +import com.fasterxml.jackson.annotation.JsonProperty; + +import java.util.Date; + +public class MemberInfoGetResultMemberInfo { + @JsonProperty("is_vip") + private Byte isVip; + + @JsonProperty("vip_endtime") + private Date vipEndtime; + + public Byte getIsVip() { + return isVip; + } + + public void setIsVip(Byte isVip) { + this.isVip = isVip; + } + + public Date getVipEndtime() { + return vipEndtime; + } + + public void setVipEndtime(Date vipEndtime) { + this.vipEndtime = vipEndtime; + } +} \ No newline at end of file diff --git a/sop-example/sop-story/src/main/resources/application-dev.properties b/sop-example/sop-story/src/main/resources/application-dev.properties index 5ce2d493..b54bc292 100644 --- a/sop-example/sop-story/src/main/resources/application-dev.properties +++ b/sop-example/sop-story/src/main/resources/application-dev.properties @@ -1,6 +1,9 @@ server.port=2222 spring.application.name=story-service register.url=127.0.0.1:8848 -# 注册中心 +# \u6CE8\u518C\u4E2D\u5FC3 spring.cloud.nacos.discovery.server-addr=${register.url} +# \u4E0A\u4F20\u6587\u4EF6\u6700\u5927\u503C +spring.servlet.multipart.max-file-size=20MB +spring.servlet.multipart.max-request-size=20MB \ No newline at end of file diff --git a/sop-gateway/pom.xml b/sop-gateway/pom.xml index 33ba62f9..a3704c76 100644 --- a/sop-gateway/pom.xml +++ b/sop-gateway/pom.xml @@ -4,7 +4,7 @@ com.gitee.sop sop-parent - 4.0.3-SNAPSHOT + 4.3.2-SNAPSHOT ../pom.xml @@ -20,11 +20,11 @@ sop-bridge-nacos : 使用nacos注册中心 sop-bridge-eureka : 使用eureka注册中心 --> - + com.gitee.sop - sop-bridge-nacos - - 4.0.3-SNAPSHOT + sop-bridge-nacos + + ${project.version} @@ -39,17 +39,7 @@ org.springframework.boot spring-boot-starter-actuator - - net.oschina.durcframework fastmybatis-spring-boot-starter diff --git a/sop-gateway/src/main/java/com/gitee/sop/gateway/entity/MonitorInfo.java b/sop-gateway/src/main/java/com/gitee/sop/gateway/entity/MonitorInfo.java new file mode 100644 index 00000000..a73b77f6 --- /dev/null +++ b/sop-gateway/src/main/java/com/gitee/sop/gateway/entity/MonitorInfo.java @@ -0,0 +1,67 @@ +package com.gitee.sop.gateway.entity; + +import lombok.Data; + +import javax.persistence.Column; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.Table; +import java.time.LocalDateTime; +import java.util.Date; + + +/** + * 表名:monitor_info + * 备注:接口监控信息 + * + * @author tanghc + */ +@Table(name = "monitor_info") +@Data +public class MonitorInfo { + /** 数据库字段:id */ + @Id + @Column(name = "id") + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + /** 路由id, 数据库字段:route_id */ + private String routeId; + + /** 接口名, 数据库字段:name */ + private String name; + + /** 版本号, 数据库字段:version */ + private String version; + + /** 数据库字段:service_id */ + private String serviceId; + + /** 数据库字段:instance_id */ + private String instanceId; + + /** 请求耗时最长时间, 数据库字段:max_time */ + private Integer maxTime; + + /** 请求耗时最小时间, 数据库字段:min_time */ + private Integer minTime; + + /** 总时长,毫秒, 数据库字段:total_time */ + private Long totalTime; + + /** 总调用次数, 数据库字段:total_request_count */ + private Long totalRequestCount; + + /** 成功次数, 数据库字段:success_count */ + private Long successCount; + + /** 失败次数(业务主动抛出的异常算作成功,如参数校验,未知的错误算失败), 数据库字段:error_count */ + private Long errorCount; + + /** 数据库字段:gmt_create */ + private Date gmtCreate; + + /** 数据库字段:gmt_modified */ + private Date gmtModified; +} diff --git a/sop-gateway/src/main/java/com/gitee/sop/gateway/entity/MonitorInfoError.java b/sop-gateway/src/main/java/com/gitee/sop/gateway/entity/MonitorInfoError.java new file mode 100644 index 00000000..1927770a --- /dev/null +++ b/sop-gateway/src/main/java/com/gitee/sop/gateway/entity/MonitorInfoError.java @@ -0,0 +1,55 @@ +package com.gitee.sop.gateway.entity; + +import lombok.Data; + +import javax.persistence.Column; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.Table; +import java.time.LocalDateTime; +import java.util.Date; + + +/** + * 表名:monitor_info_error + * + * @author tanghc + */ +@Table(name = "monitor_info_error") +@Data +public class MonitorInfoError { + /** 数据库字段:id */ + @Id + @Column(name = "id") + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + /** 错误id,md5Hex(instanceId + routeId + errorMsg), 数据库字段:error_id */ + private String errorId; + + /** 实例id, 数据库字段:instance_id */ + private String instanceId; + + /** 数据库字段:route_id */ + private String routeId; + + /** 数据库字段:error_msg */ + private String errorMsg; + + /** http status,非200错误, 数据库字段:error_status */ + private Integer errorStatus; + + /** 错误次数, 数据库字段:count */ + private Integer count; + + /** 数据库字段:is_deleted */ + @com.gitee.fastmybatis.core.annotation.LogicDelete + private Byte isDeleted; + + /** 数据库字段:gmt_create */ + private Date gmtCreate; + + /** 数据库字段:gmt_modified */ + private Date gmtModified; +} diff --git a/sop-gateway/src/main/java/com/gitee/sop/gateway/interceptor/MonitorRouteInterceptor.java b/sop-gateway/src/main/java/com/gitee/sop/gateway/interceptor/MonitorRouteInterceptor.java new file mode 100644 index 00000000..7d4d28b3 --- /dev/null +++ b/sop-gateway/src/main/java/com/gitee/sop/gateway/interceptor/MonitorRouteInterceptor.java @@ -0,0 +1,43 @@ +package com.gitee.sop.gateway.interceptor; + +import com.gitee.sop.gatewaycommon.interceptor.RouteInterceptor; +import com.gitee.sop.gatewaycommon.interceptor.RouteInterceptorContext; +import com.gitee.sop.gatewaycommon.param.ApiParam; +import com.gitee.sop.gatewaycommon.sync.SopAsyncConfigurer; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +/** + * 用于收集监控数据 + * + * @author tanghc + */ +@Component +@Slf4j +public class MonitorRouteInterceptor implements RouteInterceptor { + + @Autowired + SopAsyncConfigurer sopAsyncConfigurer; + + @Autowired + MonitorRouteInterceptorService monitorRouteInterceptorService; + + @Override + public void preRoute(RouteInterceptorContext context) { + } + + @Override + public void afterRoute(RouteInterceptorContext context) { + sopAsyncConfigurer.getAsyncExecutor().execute(()-> { + monitorRouteInterceptorService.storeRequestInfo(context); + }); + } + + @Override + public int getOrder() { + return -1000; + } + + +} diff --git a/sop-gateway/src/main/java/com/gitee/sop/gateway/interceptor/MonitorRouteInterceptorService.java b/sop-gateway/src/main/java/com/gitee/sop/gateway/interceptor/MonitorRouteInterceptorService.java new file mode 100644 index 00000000..1188ca6c --- /dev/null +++ b/sop-gateway/src/main/java/com/gitee/sop/gateway/interceptor/MonitorRouteInterceptorService.java @@ -0,0 +1,164 @@ +package com.gitee.sop.gateway.interceptor; + +import com.gitee.sop.gateway.mapper.DbMonitorInfoManager; +import com.gitee.sop.gatewaycommon.bean.LRUCache; +import com.gitee.sop.gatewaycommon.interceptor.RouteInterceptorContext; +import com.gitee.sop.gatewaycommon.monitor.MonitorDTO; +import com.gitee.sop.gatewaycommon.monitor.MonitorData; +import com.gitee.sop.gatewaycommon.monitor.MonitorManager; +import com.gitee.sop.gatewaycommon.param.ApiParam; +import com.gitee.sop.gatewaycommon.sync.MyNamedThreadFactory; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.cloud.client.ServiceInstance; +import org.springframework.stereotype.Service; + +import javax.annotation.PostConstruct; +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Random; +import java.util.concurrent.ScheduledThreadPoolExecutor; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * @author tanghc + */ +@Service +@Slf4j +public class MonitorRouteInterceptorService { + + /** + * 刷新到数据库时间间隔,秒 + */ + @Value("${sop.monitor.flush-period-seconds:30}") + int flushPeriodSeconds; + + /** + * 定时任务每n秒,执行一次 + */ + @Value("${sop.monitor.schedule-period-seconds:30}") + int schedulePeriodSeconds; + + /** + * 错误数量容量 + */ + @Value("${sop.monitor.error-count-capacity:50}") + int monitorErrorCapacity; + + @Autowired + DbMonitorInfoManager dbMonitorInfoManager; + + @Autowired + MonitorManager monitorManager; + + /** + * 记录接口调用流量,最大时间,最小时间,总时长,平均时长,调用次数,成功次数,失败次数. + * 需要考虑并发情况。 + */ + public synchronized void storeRequestInfo(RouteInterceptorContext context) { + ApiParam apiParam = context.getApiParam(); + ServiceInstance serviceInstance = context.getServiceInstance(); + String routeId = apiParam.getRouteId(); + int spendTime = (int)(context.getFinishTimeMillis() - context.getBeginTimeMillis()); + // 这步操作是线程安全的,底层调用了ConcurrentHashMap.computeIfAbsent + String key = getMonitorKey(routeId, serviceInstance.getInstanceId()); + MonitorData monitorData = monitorManager.getMonitorInfo(key, (k) -> this.createMonitorInfo(apiParam, serviceInstance)); + monitorData.storeMaxTime(spendTime); + monitorData.storeMinTime(spendTime); + monitorData.getTotalRequestCount().incrementAndGet(); + monitorData.getTotalTime().addAndGet(spendTime); + if (context.isSuccessRequest()) { + monitorData.getSuccessCount().incrementAndGet(); + } else { + monitorData.getErrorCount().incrementAndGet(); + String errorMsg = context.getServiceErrorMsg(); + monitorData.addErrorMsg(errorMsg, context.getResponseStatus()); + } + } + + private String getMonitorKey(String routeId, String instanceId) { + return routeId + instanceId; + } + + /** + * 刷新到数据库 + */ + private synchronized void flushDb() { + Map monitorData = monitorManager.getMonitorData(); + if (monitorData.isEmpty()) { + return; + } + LocalDateTime checkTime = LocalDateTime.now(); + List tobeRemoveKeys = new ArrayList<>(); + List tobeSaveBatch = new ArrayList<>(monitorData.size()); + monitorData.forEach((key, value) -> { + LocalDateTime flushTime = value.getFlushTime(); + if (flushTime.isEqual(checkTime) || flushTime.isBefore(checkTime)) { + log.debug("刷新监控数据到数据库, MonitorData:{}", value); + tobeRemoveKeys.add(key); + MonitorDTO monitorDTO = getMonitorDTO(value); + tobeSaveBatch.add(monitorDTO); + } + }); + dbMonitorInfoManager.saveMonitorInfoBatch(tobeSaveBatch); + + for (String key : tobeRemoveKeys) { + monitorData.remove(key); + } + } + + private MonitorDTO getMonitorDTO(MonitorData monitorData) { + MonitorDTO monitorDTO = new MonitorDTO(); + monitorDTO.setRouteId(monitorData.getRouteId()); + monitorDTO.setName(monitorData.getName()); + monitorDTO.setVersion(monitorData.getVersion()); + monitorDTO.setServiceId(monitorData.getServiceId()); + monitorDTO.setInstanceId(monitorData.getInstanceId()); + monitorDTO.setMaxTime(monitorData.getMaxTime()); + monitorDTO.setMinTime(monitorData.getMinTime()); + monitorDTO.setTotalTime(monitorData.getTotalTime().longValue()); + monitorDTO.setTotalRequestCount(monitorData.getTotalRequestCount().longValue()); + monitorDTO.setSuccessCount(monitorData.getSuccessCount().longValue()); + monitorDTO.setErrorCount(monitorData.getErrorCount().longValue()); + monitorDTO.setErrorMsgList(monitorData.getMonitorErrorMsgMap().values()); + return monitorDTO; + } + + private MonitorData createMonitorInfo(ApiParam apiParam, ServiceInstance serviceInstance) { + MonitorData monitorData = new MonitorData(); + monitorData.setRouteId(apiParam.getRouteId()); + monitorData.setName(apiParam.fetchName()); + monitorData.setVersion(apiParam.fetchVersion()); + monitorData.setServiceId(apiParam.fetchServiceId()); + monitorData.setInstanceId(serviceInstance.getInstanceId()); + monitorData.setTotalTime(new AtomicInteger()); + monitorData.setMaxTime(0); + monitorData.setMinTime(0); + monitorData.setSuccessCount(new AtomicInteger()); + monitorData.setTotalRequestCount(new AtomicInteger()); + monitorData.setErrorCount(new AtomicInteger()); + monitorData.setFlushTime(getFlushTime()); + monitorData.setMonitorErrorMsgMap(new LRUCache<>(monitorErrorCapacity)); + return monitorData; + } + + private LocalDateTime getFlushTime() { + return LocalDateTime.now() + .plusSeconds(flushPeriodSeconds); + } + + + @PostConstruct + public void after() { + // 每隔schedulePeriodSeconds秒执行一次 + ScheduledThreadPoolExecutor scheduledThreadPoolExecutor = new ScheduledThreadPoolExecutor(1, new MyNamedThreadFactory("monitorSchedule")); + // 延迟执行,随机5~14秒 + int delay = 5 + new Random().nextInt(10); + scheduledThreadPoolExecutor.scheduleWithFixedDelay(this::flushDb, delay, schedulePeriodSeconds, TimeUnit.SECONDS); + + } +} diff --git a/sop-gateway/src/main/java/com/gitee/sop/gateway/interceptor/MyRouteInterceptor.java b/sop-gateway/src/main/java/com/gitee/sop/gateway/interceptor/MyRouteInterceptor.java index efbcbcc2..32a4ba01 100644 --- a/sop-gateway/src/main/java/com/gitee/sop/gateway/interceptor/MyRouteInterceptor.java +++ b/sop-gateway/src/main/java/com/gitee/sop/gateway/interceptor/MyRouteInterceptor.java @@ -4,8 +4,10 @@ import com.gitee.sop.gatewaycommon.interceptor.RouteInterceptor; import com.gitee.sop.gatewaycommon.interceptor.RouteInterceptorContext; import com.gitee.sop.gatewaycommon.param.ApiParam; import lombok.extern.slf4j.Slf4j; -import org.springframework.cloud.client.ServiceInstance; import org.springframework.stereotype.Component; +import org.springframework.web.server.ServerWebExchange; + +import java.net.URI; /** * 演示拦截器 @@ -19,16 +21,19 @@ public class MyRouteInterceptor implements RouteInterceptor { @Override public void preRoute(RouteInterceptorContext context) { ApiParam apiParam = context.getApiParam(); - log.info("请求接口:{}, ip:{}", apiParam.fetchNameVersion(), apiParam.fetchIp()); + ServerWebExchange exchange = (ServerWebExchange) context.getRequestContext(); + URI uri = exchange.getRequest().getURI(); + log.info("请求URL:{}, 请求接口:{}, request_id:{}, app_id:{}, ip:{}", + uri, + apiParam.fetchNameVersion(), + apiParam.fetchRequestId(), + apiParam.fetchAppKey(), + apiParam.fetchIp() + ); } @Override public void afterRoute(RouteInterceptorContext context) { - ServiceInstance serviceInstance = context.getServiceInstance(); - log.info("请求成功,serviceId:{}({}:{}),微服务返回结果:{}", - serviceInstance.getServiceId(), - serviceInstance.getHost(),serviceInstance.getPort(), - context.getServiceResult()); } @Override diff --git a/sop-gateway/src/main/java/com/gitee/sop/gateway/mapper/DbMonitorInfoManager.java b/sop-gateway/src/main/java/com/gitee/sop/gateway/mapper/DbMonitorInfoManager.java new file mode 100644 index 00000000..aa206b51 --- /dev/null +++ b/sop-gateway/src/main/java/com/gitee/sop/gateway/mapper/DbMonitorInfoManager.java @@ -0,0 +1,62 @@ +package com.gitee.sop.gateway.mapper; + +import com.gitee.sop.gatewaycommon.monitor.MonitorDTO; +import com.gitee.sop.gatewaycommon.monitor.MonitorErrorMsg; +import com.gitee.sop.gatewaycommon.monitor.RouteErrorCount; +import org.apache.commons.collections.CollectionUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.stream.Collectors; + +/** + * @author tanghc + */ +@Service +public class DbMonitorInfoManager { + + @Autowired + private MonitorInfoMapper monitorInfoMapper; + + @Autowired + private MonitorInfoErrorMapper monitorInfoErrorMapper; + + @Value("${sop.monitor.error-count-capacity:50}") + int limitCount; + + public void saveMonitorInfoBatch(List list) { + if (CollectionUtils.isEmpty(list)) { + return; + } + monitorInfoMapper.saveMonitorInfoBatch(list); + this.saveMonitorInfoErrorBatch(list); + } + + private void saveMonitorInfoErrorBatch(List list) { + List routeErrorCounts = monitorInfoErrorMapper.listRouteErrorCountAll(); + // 路由id对应的错误次数,key:routeId,value:错误次数 + Map routeErrorCountsMap = routeErrorCounts.stream() + .collect(Collectors.toMap(RouteErrorCount::getRouteId, RouteErrorCount::getCount)); + List monitorErrorMsgList = list.stream() + .filter(monitorDTO -> CollectionUtils.isNotEmpty(monitorDTO.getErrorMsgList())) + .flatMap(monitorDTO -> { + int limit = limitCount - routeErrorCountsMap.getOrDefault(monitorDTO.getRouteId(), 0); + // 容量已满 + if (limit <= 0) { + return null; + } + // 截取剩余 + return monitorDTO.getErrorMsgList().stream().limit(limit); + }) + .filter(Objects::nonNull) + .collect(Collectors.toList()); + if (CollectionUtils.isNotEmpty(monitorErrorMsgList)) { + monitorInfoErrorMapper.saveMonitorInfoErrorBatch(monitorErrorMsgList); + } + } + +} diff --git a/sop-gateway/src/main/java/com/gitee/sop/gateway/mapper/MonitorInfoErrorMapper.java b/sop-gateway/src/main/java/com/gitee/sop/gateway/mapper/MonitorInfoErrorMapper.java new file mode 100644 index 00000000..c58dc524 --- /dev/null +++ b/sop-gateway/src/main/java/com/gitee/sop/gateway/mapper/MonitorInfoErrorMapper.java @@ -0,0 +1,36 @@ +package com.gitee.sop.gateway.mapper; + +import com.gitee.fastmybatis.core.mapper.CrudMapper; +import com.gitee.sop.gateway.entity.MonitorInfoError; +import com.gitee.sop.gatewaycommon.monitor.MonitorErrorMsg; +import com.gitee.sop.gatewaycommon.monitor.RouteErrorCount; +import org.apache.ibatis.annotations.Param; +import org.apache.ibatis.annotations.Select; +import org.apache.ibatis.annotations.Update; + +import java.util.List; + + +/** + * @author tanghc + */ +public interface MonitorInfoErrorMapper extends CrudMapper { + + @Update("UPDATE monitor_info_error " + + "SET is_deleted=0 " + + ",count=count + 1 " + + "WHERE route_id=#{routeId} AND error_id=#{errorId}") + int updateError(@Param("routeId") String routeId,@Param("errorId") String errorId); + + int saveMonitorInfoErrorBatch(@Param("list") List list); + + @Select("SELECT route_id routeId, count(*) `count` FROM monitor_info_error \n" + + "WHERE is_deleted=0 \n" + + "GROUP BY route_id") + List listRouteErrorCount(); + + @Select("SELECT route_id routeId, count(*) `count` FROM monitor_info_error \n" + + "WHERE is_deleted=0 \n" + + "GROUP BY route_id") + List listRouteErrorCountAll(); +} diff --git a/sop-gateway/src/main/java/com/gitee/sop/gateway/mapper/MonitorInfoMapper.java b/sop-gateway/src/main/java/com/gitee/sop/gateway/mapper/MonitorInfoMapper.java new file mode 100644 index 00000000..5d171f89 --- /dev/null +++ b/sop-gateway/src/main/java/com/gitee/sop/gateway/mapper/MonitorInfoMapper.java @@ -0,0 +1,39 @@ +package com.gitee.sop.gateway.mapper; + +import com.gitee.fastmybatis.core.mapper.CrudMapper; +import com.gitee.sop.gateway.entity.MonitorInfo; +import com.gitee.sop.gatewaycommon.monitor.MonitorDTO; +import org.apache.ibatis.annotations.Param; +import org.apache.ibatis.annotations.Update; + +import java.util.List; + + +/** + * @author tanghc + */ +public interface MonitorInfoMapper extends CrudMapper { + + /** + * 更新监控状态 + * + * @return 返回影响行数 + */ + @Update("UPDATE monitor_info " + + "set max_time=case when max_time < #{maxTime} then #{maxTime} else max_time end " + + ",min_time=case when min_time > #{minTime} then #{minTime} else min_time end " + + ",total_request_count=total_request_count + #{totalRequestCount} " + + ",total_time=total_time + #{totalTime} " + + ",success_count=success_count + #{successCount} " + + ",error_count=error_count + #{errorCount} " + + "where route_id=#{routeId}") + int updateMonitorInfo(MonitorDTO monitorDTO); + + + /** + * 批量插入监控数据 + * @param list 监控数据 + * @return 返回影响行数 + */ + int saveMonitorInfoBatch(@Param("list") List list); +} diff --git a/sop-gateway/src/main/resources/META-INF/gateway.properties b/sop-gateway/src/main/resources/META-INF/gateway.properties index 0f6da12f..09ddd169 100644 --- a/sop-gateway/src/main/resources/META-INF/gateway.properties +++ b/sop-gateway/src/main/resources/META-INF/gateway.properties @@ -1,209 +1,47 @@ -# 固定不变,不能改 +# \u56FA\u5B9A\u4E0D\u53D8\uFF0C\u4E0D\u80FD\u6539 spring.application.name=sop-gateway -# 不用改,如果要改,请全局替换修改 +# \u4E0D\u7528\u6539\uFF0C\u5982\u679C\u8981\u6539\uFF0C\u8BF7\u5168\u5C40\u66FF\u6362\u4FEE\u6539 sop.secret=MZZOUSTua6LzApIWXCwEgbBmxSzpzC -# 网关入口 +# \u6587\u4EF6\u4E0A\u4F20\u914D\u7F6E +spring.servlet.multipart.enabled=true +spring.servlet.multipart.max-file-size=10MB +# \u4E0D\u9650\u5236gateway\u8BF7\u6C42\u4F53\u5927\u5C0F +spring.codec.max-in-memory-size=-1 + +# \u7F51\u5173\u5165\u53E3 sop.gateway-index-path=/ -# 开启restful请求,默认开启 +# \u5F00\u542Frestful\u8BF7\u6C42\uFF0C\u9ED8\u8BA4\u5F00\u542F sop.restful.enable=true -# nacos cloud配置 +# restful\u8BF7\u6C42\u524D\u7F00 +sop.restful.path=/rest + +# nacos cloud\u914D\u7F6E spring.cloud.nacos.discovery.server-addr=${register.url} -# eureka地址 +# eureka\u5730\u5740 eureka.client.serviceUrl.defaultZone=${register.url} -# 数据库配置 +# \u6570\u636E\u5E93\u914D\u7F6E spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver spring.datasource.url=jdbc:mysql://${mysql.host}/sop?useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&serverTimezone=Asia/Shanghai spring.datasource.username=${mysql.username} spring.datasource.password=${mysql.password} -#连接池 +#\u8FDE\u63A5\u6C60 spring.datasource.hikari.pool-name=HikariCP spring.datasource.hikari.max-lifetime=500000 spring.cloud.gateway.discovery.locator.lower-case-service-id=true spring.cloud.gateway.discovery.locator.enabled=true +# \u54CD\u5E94\u8D85\u65F6\uFF0C\u9ED8\u8BA410\u79D2\uFF0810000\uFF09 +spring.cloud.gateway.httpclient.response-timeout=10000 +spring.cloud.gateway.httpclient.pool.max-idle-time=60000 -# 不用改 +# \u4E0D\u7528\u6539 +mybatis.mapper-locations=classpath:mybatis/mapper/*.xml mybatis.fill.com.gitee.fastmybatis.core.support.DateFillInsert=gmt_create mybatis.fill.com.gitee.fastmybatis.core.support.DateFillUpdate=gmt_modified -# POST请求最大缓存,默认是262144(256KB),即:256 * 1024 -# 如果需要传输大文本可以把值设置高一点 -spring.codec.max-in-memory-size=262144 - -# 文件上传配置 -spring.servlet.multipart.enabled=true -spring.servlet.multipart.max-file-size=10MB - - -###开启监控端点 -#终端WEB配置(WebEndpointProperties) -#应包含的终端ID或全部为''*''。 -management.endpoints.web.exposure.include=* -#应该排除的端点ID。 -#management.endpoints.web.exposure.exclude='*' -#Web端点的基本路径。相对于server.controller.context-path或management.server.controller.context-path,如果配置了management.server.port。 -#management.endpoints.web.base-path=/actuators -#端点ID和应该暴露它们的路径之间的映射。 -#management.endpoints.web.path-mapping: - -#ENDPOINTS CORS CONFIGURATION(CorsEndpointProperties) -#management.endpoints.web.cors.allow-credentials=#是否支持凭证。未设置时,不支持凭证。 -#在请求中允许使用逗号分隔的标题列表。''*''允许所有标题。 -management.endpoints.web.cors.allowed-headers='*' -#允许使用逗号分隔的方法列表。''*''允许所有方法。未设置时,默认为GET。 -management.endpoints.web.cors.allowed-methods=GET,POST -#逗号分隔的起源列表允许。''*''允许所有的来源。未设置时,CORS支持被禁用。 -management.endpoints.web.cors.allowed-origins='*' -#包含在响应中的逗号分隔的标题列表。 -#management.endpoints.web.cors.exposed-headers='*' -#客户端可以缓存飞行前请求的响应时间。如果未指定持续时间后缀,则将使用秒。 -management.endpoints.web.cors.max-age=1800s - -#审计事件ENDPOINT(AuditEventsEndpoint) -#响应可以被缓存的最长时间。 -management.endpoint.auditevents.cache.time-to-live=100ms -#是否启用auditevents端点。 -management.endpoint.auditevents.enabled=true - -#BEANS ENDPOINT(BeansEndpoint) -#可以缓存响应的最长时间。 -management.endpoint.beans.cache.time-to-live=60000ms -#是否启用bean端点。 -management.endpoint.beans.enabled=true - -#条件REPORT ENDPOINT(ConditionsReportEndpoint) -#可以缓存响应的最长时间。 -management.endpoint.conditions.cache.time-to-live=0ms -#是否启用条件端点。 -management.endpoint.conditions.enabled=true - -#配置属性报告ENDPOINT(ConfigurationPropertiesReportEndpoint,ConfigurationPropertiesReportEndpointProperties) -#响应可以被缓存的最大时间。 -management.endpoint.configprops.cache.time-to-live=60000ms -#是否启用configprops端点。 -management.endpoint.configprops.enabled=true -#应该清理的密钥。键可以是属性以正则表达式结尾的简单字符串。 -#management.endpoint.configprops.keys-to-sanitize:密码,密钥,密钥,令牌,'*'凭证。'*',vcap_services - -#ENVIRONMENT ENDPOINT(EnvironmentEndpoint,EnvironmentEndpointProperties) -#可以缓存响应的最长时间。 -management.endpoint.env.cache.time-to-live=60000ms -#是否启用env端点。 -management.endpoint.env.enabled=true -#management.endpoint.env.keys-to-sanitize:密码,秘密,密钥,令牌,'*'凭证。'*',vcap_services #应该清理的密钥。键可以是属性以正则表达式结尾的简单字符串。 - -#FLYWAY ENDPOINT(FlywayEndpoint) -#可以缓存响应的最长时间。 -management.endpoint.flyway.cache.time-to-live=0ms -#是否启用飞桥端点。 -management.endpoint.flyway.enabled=true - -#HEALTH ENDPOINT(HealthEndpoint,HealthEndpointProperties) -#可以缓存响应的最长时间。 -management.endpoint.health.cache.time-to-live=0ms -#是否启用运行状况端点。 -management.endpoint.health.enabled=true -#用于确定用户是否有权显示详细信息的角色。当为空时,所有认证的用户都被授权。 -management.endpoint.health.roles= -#何时显示完整健康详情。 -management.endpoint.health.show-details=always - -#HEAP DUMP ENDPOINT(HeapDumpWebEndpoint) -#响应可以被缓存的最长时间。 -management.endpoint.heapdump.cache.time-to-live=60000ms -#是否启用heapdump端点。 -management.endpoint.heapdump.enabled=true - -#HTTP TRACE ENDPOINT(HttpTraceEndpoint) -#响应可以被缓存的最大时间。 -management.endpoint.httptrace.cache.time-to-live=30000ms -#是否启用httptrace端点。 -management.endpoint.httptrace.enabled=true - -#INFO ENDPOINT(InfoEndpoint) -# 描述信息 -#响应可以被缓存的最大时间。 -management.endpoint.info.cache.time-to-live=18000ms -#是否启用信息端点。 -management.endpoint.info.enabled=true - -#JOLOKIA ENDPOINT(JolokiaProperties) -#Jolokia设置。有关更多详细信息,请参阅Jolokia的文档。 -#management.endpoint.jolokia.config.'*': -##是否启用jolokia端点。 -#management.endpoint.jolokia.enabled=true - -#LIQUIBASE ENDPOINT(LiquibaseEndpoint) -#可以缓存响应的最长时间。 -#management.endpoint.liquibase.cache.time-to-live=0ms -#是否启用liquibase端点。 -management.endpoint.liquibase.enabled=false - -#LOG FILE ENDPOINT(LogFileWebEndpoint,LogFileWebEndpointProperties) -#可以缓存响应的最长时间。 -management.endpoint.logfile.cache.time-to-live=0ms -#是否启用日志文件端点。 -management.endpoint.logfile.enabled=true -#要访问的外部日志文件。如果日志文件是由输出重定向写入的,而不是日志记录系统本身,则可以使用。 -#management.endpoint.logfile.external-file: - -#LOGGERS ENDPOINT(LoggersEndpoint) -#响应可以被缓存的最大时间。 -management.endpoint.loggers.cache.time-to-live=0ms -#是否启用记录器端点。 -management.endpoint.loggers.enabled=true - -#请求MAPPING ENDPOINT(MappingsEndpoint) -#可以缓存响应的最长时间。 -management.endpoint.mappings.cache.time-to-live=0ms -#是否启用映射端点。 -management.endpoint.mappings.enabled=true - -#METRICS ENDPOINT(MetricsEndpoint) -#响应可以被缓存的最大时间。 -management.endpoint.metrics.cache.time-to-live=10000ms -#是否启用度量标准端点。 -management.endpoint.metrics.enabled=true - -#PROMETHEUS ENDPOINT(PrometheusScrapeEndpoint) -#可以缓存响应的最长时间。 -management.endpoint.prometheus.cache.time-to-live=0ms -#是否启用普罗米修斯端点。 -management.endpoint.prometheus.enabled=true - -#SCHEDULED TASKS ENDPOINT(ScheduledTasksEndpoint) -#响应可以被缓存的最大时间。 -management.endpoint.scheduledtasks.cache.time-to-live=0ms -#是否启用scheduledtasks端点。 -management.endpoint.scheduledtasks.enabled=true - -#SESSIONS ENDPOINT(SessionsEndpoint) -#是否启用会话端点。 -management.endpoint.sessions.enabled=true - -#SHUTDOWN ENDPOINT(ShutdownEndpoint) -#是否启用关闭端点。 -management.endpoint.shutdown.enabled=false - -#THREAD DUMP ENDPOINT(ThreadDumpEndpoint) -#可以缓存响应的最长时间。 -management.endpoint.threaddump.cache.time-to-live=0ms -#是否启用线程转储端点。 -management.endpoint.threaddump.enabled=true - -#健康指标 -management.health.db.enabled=true -#是否启用数据库健康检查。 -management.health.defaults.enabled=true -#是否启用默认运行状况指示器。 -management.health.livenessstate.enabled=true -management.health.readynessstate.enabled=false -#management.endpoint.logfile.external-file=applogs/App_${POD_NAME:spring.application.name}_all.log -management.metrics.tags.application=${spring.application.name} - diff --git a/sop-gateway/src/main/resources/application-dev.properties b/sop-gateway/src/main/resources/application-dev.properties index 0ba27c4c..8afebce4 100644 --- a/sop-gateway/src/main/resources/application-dev.properties +++ b/sop-gateway/src/main/resources/application-dev.properties @@ -1,16 +1,18 @@ -# 更多配置,见:META-INF/gateway.properties +# \u66F4\u591A\u914D\u7F6E\uFF0C\u89C1\uFF1AMETA-INF/gateway.properties server.port=8081 -# mysql数据库配置 +# mysql\u6570\u636E\u5E93\u914D\u7F6E mysql.host=localhost:3306 mysql.username=root mysql.password=root -# nacos注册中心地址 +# nacos\u6CE8\u518C\u4E2D\u5FC3\u5730\u5740 register.url=127.0.0.1:8848 -# POST请求最大缓存,默认是262144(256KB),即:256 * 1024 -# 如果需要传输大文本可以把值设置高一点 -spring.codec.max-in-memory-size=262144 +# \u4E0A\u4F20\u6587\u4EF6\u6700\u5927\u5BB9\u91CF\uFF0C\u9ED8\u8BA410MB +spring.servlet.multipart.max-file-size=10MB -logging.level.com.gitee=debug \ No newline at end of file +# \u54CD\u5E94\u8D85\u65F6\uFF0C\u9ED8\u8BA410\u79D2\uFF0810000\uFF09 +#spring.cloud.gateway.httpclient.response-timeout=10000 + +logging.level.com.gitee=debug diff --git a/sop-gateway/src/main/resources/mybatis/mapper/MonitorInfoErrorMapper.xml b/sop-gateway/src/main/resources/mybatis/mapper/MonitorInfoErrorMapper.xml new file mode 100644 index 00000000..1655177b --- /dev/null +++ b/sop-gateway/src/main/resources/mybatis/mapper/MonitorInfoErrorMapper.xml @@ -0,0 +1,30 @@ + + + + + + + INSERT INTO `monitor_info_error` ( + `error_id`, + `instance_id`, + `route_id`, + `error_msg`, + `error_status`, + `count`) + VALUES + + (#{data.errorId}, + #{data.instanceId}, + #{data.routeId}, + #{data.errorMsg}, + #{data.errorStatus}, + #{data.count}) + + ON DUPLICATE KEY UPDATE + error_msg = VALUES(error_msg) + , error_status = VALUES(error_status) + , `count`= `count` + VALUES(count) + , is_deleted = 0 + + + diff --git a/sop-gateway/src/main/resources/mybatis/mapper/MonitorInfoMapper.xml b/sop-gateway/src/main/resources/mybatis/mapper/MonitorInfoMapper.xml new file mode 100644 index 00000000..b485dbf0 --- /dev/null +++ b/sop-gateway/src/main/resources/mybatis/mapper/MonitorInfoMapper.xml @@ -0,0 +1,43 @@ + + + + + + + INSERT INTO `monitor_info` ( + `route_id`, + `name`, + `version`, + `service_id`, + `instance_id`, + `max_time`, + `min_time`, + `total_time`, + `total_request_count`, + `success_count`, + `error_count`) + VALUES + + (#{data.routeId}, + #{data.name}, + #{data.version}, + #{data.serviceId}, + #{data.instanceId}, + #{data.maxTime}, + #{data.minTime}, + #{data.totalTime}, + #{data.totalRequestCount}, + #{data.successCount}, + #{data.errorCount}) + + VALUES(min_time) then VALUES(min_time) else min_time end + ,total_time = total_time + VALUES(total_time) + ,total_request_count = total_request_count + VALUES(total_request_count) + ,success_count = success_count + VALUES(success_count) + ,error_count = error_count + VALUES(error_count) + ]]> + + diff --git a/sop-mysql5.6以下版本.sql b/sop-mysql5.6以下版本.sql index 8e02579a..a608fa18 100644 --- a/sop-mysql5.6以下版本.sql +++ b/sop-mysql5.6以下版本.sql @@ -17,7 +17,10 @@ DROP TABLE IF EXISTS `config_gray`; DROP TABLE IF EXISTS `config_common`; DROP TABLE IF EXISTS `admin_user_info`; DROP TABLE IF EXISTS `config_service_route`; - +DROP TABLE IF EXISTS `monitor_info`; +DROP TABLE IF EXISTS `monitor_info_error`; +DROP TABLE IF EXISTS `user_account`; +DROP TABLE IF EXISTS `isp_resource`; CREATE TABLE `admin_user_info` ( `id` BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT, @@ -208,6 +211,69 @@ CREATE TABLE `config_service_route` ( KEY `idx_serviceid` (`service_id`) USING BTREE ) ENGINE=INNODB DEFAULT CHARSET=utf8 COMMENT='路由配置'; +CREATE TABLE `monitor_info` ( + `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, + `route_id` varchar(128) NOT NULL DEFAULT '' COMMENT '路由id', + `name` varchar(128) NOT NULL DEFAULT '' COMMENT '接口名', + `version` varchar(64) NOT NULL DEFAULT '' COMMENT '版本号', + `service_id` varchar(64) NOT NULL DEFAULT '', + `instance_id` varchar(128) NOT NULL DEFAULT '', + `max_time` int(11) NOT NULL DEFAULT '0' COMMENT '请求耗时最长时间', + `min_time` int(11) NOT NULL DEFAULT '0' COMMENT '请求耗时最小时间', + `total_time` bigint(20) NOT NULL DEFAULT '0' COMMENT '总时长,毫秒', + `total_request_count` bigint(20) NOT NULL DEFAULT '0' COMMENT '总调用次数', + `success_count` bigint(20) NOT NULL DEFAULT '0' COMMENT '成功次数', + `error_count` bigint(20) NOT NULL DEFAULT '0' COMMENT '失败次数(业务主动抛出的异常算作成功,如参数校验,未知的错误算失败)', + `gmt_create` datetime DEFAULT DEFAULT NULL, + `gmt_modified` datetime DEFAULT DEFAULT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `uk_routeid` (`route_id`,`instance_id`) USING BTREE, + KEY `idex_name` (`name`) USING BTREE +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='接口监控信息'; + +CREATE TABLE `monitor_info_error` ( + `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, + `error_id` varchar(64) NOT NULL DEFAULT '' COMMENT '错误id,md5Hex(instanceId + routeId + errorMsg)', + `instance_id` varchar(128) NOT NULL DEFAULT '' COMMENT '实例id', + `route_id` varchar(128) NOT NULL DEFAULT '', + `error_msg` text NOT NULL, + `error_status` int(11) NOT NULL DEFAULT '0' COMMENT 'http status,非200错误', + `count` int(11) NOT NULL DEFAULT '0' COMMENT '错误次数', + `is_deleted` tinyint(4) NOT NULL DEFAULT '0', + `gmt_create` DATETIME DEFAULT NULL, + `gmt_modified` DATETIME DEFAULT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `uk_errorid` (`error_id`) USING BTREE, + KEY `idx_routeid` (`route_id`) USING BTREE +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +ALTER TABLE `isv_info` ADD COLUMN `user_id` BIGINT NULL DEFAULT 0 COMMENT 'user_account.id' AFTER `remark`; + +CREATE INDEX `idx_userid` USING BTREE ON `isv_info` (`user_id`); + +CREATE TABLE `user_account` ( + `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, + `username` varchar(128) NOT NULL DEFAULT '' COMMENT '用户名(邮箱)', + `password` varchar(128) NOT NULL DEFAULT '' COMMENT '密码', + `status` tinyint(4) NOT NULL DEFAULT '1' COMMENT '2:邮箱未验证,1:启用,0:禁用', + `gmt_create` DATETIME DEFAULT NULL, + `gmt_modified` DATETIME DEFAULT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `uk_username` (`username`) USING BTREE +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='用户信息'; + +CREATE TABLE `isp_resource` ( + `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, + `name` varchar(64) NOT NULL DEFAULT '' COMMENT '资源名称', + `content` varchar(128) NOT NULL DEFAULT '' COMMENT '资源内容(URL)', + `ext_content` text, + `version` varchar(32) NOT NULL DEFAULT '' COMMENT '版本', + `type` tinyint(4) NOT NULL DEFAULT '0' COMMENT '资源类型:0:SDK链接', + `is_deleted` tinyint(4) NOT NULL DEFAULT '0', + `gmt_create` DATETIME DEFAULT NULL, + `gmt_modified` DATETIME DEFAULT NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='ISP资源表'; INSERT INTO `admin_user_info` (`id`, `username`, `password`, `status`, `gmt_create`, `gmt_modified`) VALUES (1,'admin','a62cd510fb9a8a557a27ef279569091f',1,'2019-04-02 19:55:26','2019-04-02 19:55:26'); @@ -275,3 +341,9 @@ INSERT INTO `user_info` (`id`, `username`, `password`, `nickname`, `gmt_create`, (1,'zhangsan','123456','张三','2019-04-27 08:32:57','2019-04-27 08:32:57'); +INSERT INTO `isp_resource` (`id`, `name`, `content`, `ext_content`, `version`, `type`, `is_deleted`, `gmt_create`, `gmt_modified`) VALUES + (1,'Java','http://www.baidu.com','```java\nString url = "http://localhost:8081";\nString appId = "2019032617262200001";\nString privateKey = "你的私钥";\n\n// 声明一个就行\nOpenClient client = new OpenClient(url, appId, privateKey);\n\n// 标准用法\n@Test\npublic void testGet() {\n // 创建请求对象\n GetStoryRequest request = new GetStoryRequest();\n // 请求参数\n GetStoryModel model = new GetStoryModel();\n model.setName("白雪公主");\n \n request.setBizModel(model);\n\n // 发送请求\n GetStoryResponse response = client.execute(request);\n\n if (response.isSuccess()) {\n // 返回结果\n System.out.println(response);\n } else {\n System.out.println(response);\n }\n}\n```','1.0',0,0,'2020-11-07 14:29:11','2020-11-07 14:29:11'), + (2,'C#','http://www.soso.com','```\nclass MainClass\n{\n static string url = "http://localhost:8081";\n static string appId = "2019032617262200001";\n // 平台提供的私钥\n 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=";\n\n\n // 声明一个就行\n static OpenClient client = new OpenClient(url, appId, privateKey);\n\n public static void Main(string[] args)\n {\n TestGet();\n }\n\n // 标准用法\n private static void TestGet()\n {\n // 创建请求对象\n GetStoryRequest request = new GetStoryRequest();\n // 请求参数\n GetStoryModel model = new GetStoryModel();\n model.Name = "白雪公主";\n request.BizModel = model;\n\n // 发送请求\n GetStoryResponse response = client.Execute(request);\n\n if (response.IsSuccess())\n {\n // 返回结果\n Console.WriteLine("成功!response:{0}\\n响应原始内容:{1}", JsonUtil.ToJSONString(response), response.Body);\n }\n else\n {\n Console.WriteLine("错误, code:{0}, msg:{1}, subCode:{2}, subMsg:{3}",\n response.Code, response.Msg, response.SubCode, response.SubMsg);\n }\n }\n\n \n}\n```','1.0',0,0,'2020-11-07 14:29:38','2020-11-07 14:29:38'), + (3,'Python','http://www.bilibili.com','```python\n # 创建请求\n request = MemberInfoGetRequest()\n # 请求参数\n model = MemberInfoGetModel()\n model.age = 22\n model.name = \'jim\'\n model.address = \'xx\'\n # 添加请求参数\n request.biz_model = model\n\n # 添加上传文件\n # files = {\n # \'file1\': open(\'aa.txt\', \'rb\'),\n # \'file2\': open(\'bb.txt\', \'rb\')\n # }\n # request.files = files\n\n # 调用请求\n response = self.client.execute(request)\n\n if response.is_success():\n print \'response: \', response\n print \'is_vip:\', response.get(\'member_info\').get(\'is_vip\', 0)\n else:\n print \'请求失败,code:%s, msg:%s, sub_code:%s, sub_msg:%s\' % \\\n (response.code, response.msg, response.sub_code, response.sub_msg)\n```','1.0',0,0,'2020-11-07 14:30:16','2020-11-07 14:31:41'), + (4,'Go','http://www.baidu.com','```go\n\n// 应用ID\nconst appId string = "xx"\n// 应用私钥\nconst privateKey string = "xx"\n// 请求地址\nconst url string = "http://localhost:7071/prod/gw68uy85"\n\n// 请求客户端\nvar openClient = common.OpenClient{AppId: appId, PrivateKey: privateKey, Url: url}\n\nfunc main() {\n // 创建请求\n memberInfoGetRequest := request.MemberInfoGetRequest{}\n // 请求参数\n memberInfoGetRequest.BizModel = model.MemberInfoGetModel{Name: "jim", Age: 22, Address: "xx"}\n \n // 添加上传文件\n //path, _ := os.Getwd()\n //files := []common.UploadFile{\n // {Name:"file1", Filepath:path + "/test/aa.txt"},\n // {Name:"file2", Filepath:path + "/test/bb.txt"},\n //}\n //memberInfoGetRequest.Files = files\n \n // 发送请求,返回json bytes\n var jsonBytes = openClient.Execute(memberInfoGetRequest)\n fmt.Printf("data:%s\\n", string(jsonBytes))\n // 转换结果\n var memberInfoGetResponse response.MemberInfoGetResponse\n response.ConvertResponse(jsonBytes, &memberInfoGetResponse)\n\n if memberInfoGetResponse.IsSuccess() {\n fmt.Printf("is_vip:%d, vip_endtime:%s\\n", memberInfoGetResponse.MemberInfo.IsVip, memberInfoGetResponse.MemberInfo.VipEndtime)\n } else {\n fmt.Printf("code:%s, msg:%s, subCode:%s, subMsg:%s\\n",\n memberInfoGetResponse.Code, memberInfoGetResponse.Msg, memberInfoGetResponse.SubCode, memberInfoGetResponse.SubMsg)\n }\n}\n```','1.0',0,0,'2020-11-07 14:31:21','2020-11-07 14:31:21'), + (5,'C++','http://pan.baidu.com','#include \n\n#include "common/OpenClient.h"\n#include "request/BaseRequest.h"\n#include "request/MemberInfoGetRequest.hpp"\n\n// 应用ID\nstring appId = "2020051325943082302177280";\n// 存放私钥的文件路径\nstring privateKeyFile = "/Users/thc/IdeaProjects/opc/opc-sdk/sdk-c++/privateEx.pem";\n// 请求接口\nstring url = "http://localhost:7071/prod/gw68uy85";\n\nOpenClient openClient(appId, privateKeyFile, url);\n\nint main() {\n // 创建请求\n MemberInfoGetRequest request;\n\n // 业务参数\n map bizModel;\n bizModel["name"] = "jim";\n bizModel["age"] = "22";\n bizModel["address"] = "xx";\n\n request.bizModel = bizModel;\n\n // 添加上传文件\n// request->setFiles({\n// FileInfo{"aa", "/Users/thc/IdeaProjects/opc/opc-sdk/sdk-c++/aa.txt"},\n// FileInfo{"bb", "/Users/thc/IdeaProjects/opc/opc-sdk/sdk-c++/bb.txt"}\n// });\n\n // 发送请求\n neb::CJsonObject jsonObj = openClient.execute(&request);\n std::cout << jsonObj.ToString() << std::endl;\n std::cout << "id:" << jsonObj["id"].ToString() << std::endl;\n std::cout << "is_vip:" << jsonObj["member_info"]["is_vip"].ToString() << std::endl;\n return 0;\n}\n\n','1.0',0,0,'2020-11-07 14:32:55','2020-11-07 14:32:55'); diff --git a/sop-sdk/pom.xml b/sop-sdk/pom.xml index ec6d5d2a..9d1822f5 100644 --- a/sop-sdk/pom.xml +++ b/sop-sdk/pom.xml @@ -6,13 +6,12 @@ com.gitee.sop sop-parent - 4.0.3-SNAPSHOT + 4.3.2-SNAPSHOT ../pom.xml 4.0.0 sop-sdk - 4.0.3-SNAPSHOT pom diff --git a/sop-sdk/sdk-c++/.gitignore b/sop-sdk/sdk-c++/.gitignore new file mode 100644 index 00000000..c23f2db9 --- /dev/null +++ b/sop-sdk/sdk-c++/.gitignore @@ -0,0 +1,27 @@ +/target/ +/cmake-build-debug/ +!.mvn/wrapper/maven-wrapper.jar + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +/build/ + diff --git a/sop-sdk/sdk-c++/CMakeLists.txt b/sop-sdk/sdk-c++/CMakeLists.txt new file mode 100644 index 00000000..c73938ef --- /dev/null +++ b/sop-sdk/sdk-c++/CMakeLists.txt @@ -0,0 +1,45 @@ +# cmake和CMakeLists.txt教程 +# https://www.jianshu.com/p/cb4f8136a265 +cmake_minimum_required(VERSION 3.16) +project(sdk_cxx) + +set(CMAKE_CXX_STANDARD 14) +set(LIB_SRC + common/OpenClient.cpp + common/OpenClient.h + common/RequestType.h + common/tool.h + common/sign.h + common/RSASign.h + common/RSASign.cpp + common/sha256.hpp + request/BaseRequest.h + request/MemberInfoGetRequest.hpp + thirdparty/base64/base64.h + thirdparty/base64/base64.cpp + common/sign.cpp common/tool.cpp common/sha256.cpp + request/BaseRequest.cpp) + +# openssl安装路径 +set(OPENSSL_INC_DIR /usr/local/opt/openssl/include) +set(OPENSSL_LINK_DIR /usr/local/opt/openssl/lib) + +include_directories(${OPENSSL_INC_DIR}) +link_directories(${OPENSSL_LINK_DIR}) +link_libraries(ssl crypto) + +# 添加类库 +add_library(lib ${LIB_SRC}) +# jsoncpp +add_library(jsoncpp SHARED IMPORTED) +set_target_properties(jsoncpp PROPERTIES + IMPORTED_LOCATION "/usr/local/lib/libjsoncpp.dylib" + INTERFACE_INCLUDE_DIRECTORIES "/usr/local/include/json" + ) + +# 添加可执行文件 +add_executable(sdk_cxx main.cpp) + +# 可执行文件依赖lib库 +target_link_libraries(sdk_cxx lib ssl) +target_link_libraries(sdk_cxx jsoncpp) \ No newline at end of file diff --git a/sop-sdk/sdk-c++/aa.txt b/sop-sdk/sdk-c++/aa.txt new file mode 100644 index 00000000..524527cb --- /dev/null +++ b/sop-sdk/sdk-c++/aa.txt @@ -0,0 +1 @@ +hello你好123 \ No newline at end of file diff --git a/sop-sdk/sdk-c++/bb.txt b/sop-sdk/sdk-c++/bb.txt new file mode 100644 index 00000000..85f8e329 --- /dev/null +++ b/sop-sdk/sdk-c++/bb.txt @@ -0,0 +1 @@ +文件bb的内容 \ No newline at end of file diff --git a/sop-sdk/sdk-c++/common/OpenClient.cpp b/sop-sdk/sdk-c++/common/OpenClient.cpp new file mode 100644 index 00000000..74c69ab3 --- /dev/null +++ b/sop-sdk/sdk-c++/common/OpenClient.cpp @@ -0,0 +1,137 @@ +#include +#include "OpenClient.h" +#include "httplib.h" +#include "tool.h" +#include "sign.h" + +httplib::Headers headers = { + {"Accept-Encoding", "identity"} +}; + +const string ERROR_NODE = "error_response"; + +OpenClient::OpenClient(const string &appId, const string &privateKeyFilePath, const string &url) { + this->appId = appId; + this->url = url; + this->privateKeyFilePath = privateKeyFilePath; +} + +Json::Value OpenClient::execute(BaseRequest *request) { + return this->execute(request, ""); +} + +Json::Value OpenClient::execute(BaseRequest *request, const string &token) { + string method = request->getMethod(); + string version = request->getVersion(); + RequestType requestType = request->getRequestType(); + map bizModel = request->bizModel; + // 创建HTTP请求客户端 +// httplib::Client cli(this->hostInfo.host, this->hostInfo.port); + httplib::Client cli(this->url.c_str()); + const char *url = this->url.c_str(); + // 构建请求参数 + map allParams = this->buildParams(request, token); + string responseBody; + // 如果有文件上传 + if (!request->getFiles().empty()) { + httplib::MultipartFormDataItems items = OpenClient::getMultipartFormDataItems( + allParams, request->getFiles()); + responseBody = cli.Post(url, headers, items)->body; + } else { + switch (requestType) { + case GET: { + string fullPath = this->url + "?" + OpenClient::getQuery(allParams); + responseBody = cli.Get(fullPath.c_str())->body; + break; + } + case POST_FORM: { + responseBody = cli.Post(url, headers, OpenClient::getParams(allParams))->body; + break; + } + case POST_JSON: { + string json = tool::mapToJson(allParams); + responseBody = cli.Post(url, json, "application/json")->body; + break; + } + case POST_FILE: { + httplib::MultipartFormDataItems items = OpenClient::getMultipartFormDataItems( + allParams, request->getFiles()); + responseBody = cli.Post(url, headers, items)->body; + } + } + } + return OpenClient::parseResponse(responseBody, request); +} + + +httplib::Params OpenClient::getParams(map allParams) { + httplib::Params params; + map::iterator it; + for (it = allParams.begin(); it != allParams.end(); ++it) { + params.emplace(it->first, it->second); + } + return params; +} + +string OpenClient::getQuery(map allParams) { + string query; + map::iterator it; + int i = 0; + for (it = allParams.begin(); it != allParams.end(); ++it) { + if (i++ > 0) { + query.append("&"); + } + query.append(it->first).append("=").append(tool::url_encode(it->second)); + } + return query; +} + +map OpenClient::buildParams(BaseRequest *request, const string &token) { + map allParams; + allParams["app_id"] = this->appId; + allParams["method"] = request->getMethod(); + allParams["charset"] = "UTF-8"; + allParams["sign_type"] = "RSA2"; + allParams["timestamp"] = tool::getTime(); + allParams["version"] = request->getVersion(); + allParams["biz_content"] = tool::mapToJson(request->bizModel); + + if (!token.empty()) { + allParams["app_auth_token"] = token; + } + + // 生成签名 + string sign = signutil::createSign(allParams, this->privateKeyFilePath, "RSA2"); + allParams["sign"] = sign; + return allParams; +} + +httplib::MultipartFormDataItems +OpenClient::getMultipartFormDataItems(map allParams, vector fileInfoList) { + httplib::MultipartFormDataItems items = {}; + map::iterator it; + for (it = allParams.begin(); it != allParams.end(); ++it) { + items.push_back({it->first, it->second, "", ""}); + } + // 添加上传文件 + vector::iterator vit; + for (vit = fileInfoList.begin(); vit != fileInfoList.end(); vit++) { + string content = tool::getFileContent(vit->filepath); + items.push_back({vit->name, content, tool::getFilename(vit->filepath), "application/octet-stream"}); + } + return items; +} + +Json::Value OpenClient::parseResponse(const string& responseBody, BaseRequest *request) { + Json::Value root; + Json::Reader reader; + reader.parse(responseBody, root, false); + + Json::Value data = root[ERROR_NODE]; + if (!data) { + string method = request->getMethod(); + string nodeName = tool::replace(method.c_str(),".","_") + "_response"; + data = root[nodeName]; + } + return data; +} diff --git a/sop-sdk/sdk-c++/common/OpenClient.h b/sop-sdk/sdk-c++/common/OpenClient.h new file mode 100644 index 00000000..2695feba --- /dev/null +++ b/sop-sdk/sdk-c++/common/OpenClient.h @@ -0,0 +1,61 @@ +#ifndef SDK_CXX_OPENCLIENT_H +#define SDK_CXX_OPENCLIENT_H + +#include +#include +#include "httplib.h" +#include "../request/BaseRequest.h" + +using namespace std; + +/** + * 请求客户端 + */ +class OpenClient { +private: + /** 应用id */ + string appId; + string url; + /** 私钥文件路径 */ + string privateKeyFilePath; + +public: + /** + * 创建客户端对象 + * @param appId 应用ID + * @param privateKeyFilePath 应用私钥路径 + * @param url 请求URL + */ + OpenClient(const string &appId, const string &privateKeyFilePath, const string &url); + + /** + * 发送请求 + * @param request 请求对象,BaseRequest的子类 + * @param token token + * @return 返回响应结果 + */ + Json::Value execute(BaseRequest *request, const string& token); + + /** + * 发送请求 + * @param request 请求对象,BaseRequest的子类 + * @return 返回响应结果 + */ + Json::Value execute(BaseRequest *request); + +private: + + map buildParams(BaseRequest *request, const string& token); + + static httplib::MultipartFormDataItems + getMultipartFormDataItems(map allParams, vector fileInfoList); + + static httplib::Params getParams(map params); + + static string getQuery(map params); + + static Json::Value parseResponse(const string& responseBody,BaseRequest *request); +}; + + +#endif //SDK_CXX_OPENCLIENT_H diff --git a/sop-sdk/sdk-c++/common/RSASign.cpp b/sop-sdk/sdk-c++/common/RSASign.cpp new file mode 100644 index 00000000..232270ab --- /dev/null +++ b/sop-sdk/sdk-c++/common/RSASign.cpp @@ -0,0 +1,94 @@ +#include "stdafx.h" +#include "RSASign.h" +#include "../thirdparty/base64/base64.h" + +RSA* GetPublicKeyEx(char* szPath) +{ + RSA *pubkey = RSA_new(); + + BIO *pubio; + + pubio = BIO_new_file(szPath, "rb"); + pubkey = PEM_read_bio_RSAPublicKey(pubio, &pubkey, NULL, NULL); + + BIO_free(pubio); + + return pubkey; +} + +RSA* GetPrivateKeyEx(char* szPath) +{ + RSA *prikey = RSA_new(); + + BIO *priio; + + priio = BIO_new_file(szPath, "rb"); + prikey = PEM_read_bio_RSAPrivateKey(priio, &prikey, NULL, NULL); + + BIO_free(priio); + + return prikey; +} + +bool RSASignAction(const string& strEnData, char *privateKeyFilePath, string &strSigned) +{ + int nlen = strEnData.length(); + RSA *prsa = NULL; + if (NULL == (prsa = GetPrivateKeyEx(privateKeyFilePath))) + { + RSA_free(prsa); + printf("获取私钥失败\n"); + return false; + } + + char szTmp[1024] = { 0 }; + //对待签名数据做SHA1摘要 + SHA256((const unsigned char*)strEnData.c_str(), nlen, (unsigned char*)szTmp); + int nLength; + unsigned int nLengthRet; + char szTmp1[1024] = { 0 }; //位数一定不能小于等于RSA的128位,没有‘\0’,数据会增加后面位数 +// unsigned char *szTmp1 = {0}; + nLength = RSA_sign(NID_sha256, (unsigned char *)szTmp, 32, (unsigned char *)szTmp1, &nLengthRet, prsa); + if (nLength != 1) + { + RSA_free(prsa); + return false; + } + strSigned = base64_encode((unsigned char *)szTmp1, strlen((const char *)szTmp1)); + RSA_free(prsa); + + return true; +} + +bool RSAVerifyAction(string strEnData, string &strSigned) +{ + int nlen = strEnData.length(); + printf("验签的原始数据:[%s]\n", strEnData.c_str()); + + RSA *prsa = NULL; + if (NULL == (prsa = GetPublicKeyEx(PUBLIC_KEY_FILE_EX))) + { + RSA_free(prsa); + printf("获取公钥失败\n"); + return -1; + } + + //对待签名数据做SHA1摘要 + char szTmp[1024] = { 0 }; + int nLen = strEnData.length(); + SHA256((const unsigned char*)strEnData.c_str(), nLen, (unsigned char*)szTmp); + + int nLength; + unsigned int nLengthRet = strSigned.length(); + printf("nLengthRet2 = %d\n", nLengthRet); + nLength = RSA_verify(NID_sha256, (unsigned char *)szTmp, 32, (unsigned char*)strSigned.c_str(), nLengthRet, prsa); + if (nLength != 1) + { + RSA_free(prsa); + return false; + } + + RSA_free(prsa); + + return true; +} \ No newline at end of file diff --git a/sop-sdk/sdk-c++/common/RSASign.h b/sop-sdk/sdk-c++/common/RSASign.h new file mode 100644 index 00000000..00bd7e6b --- /dev/null +++ b/sop-sdk/sdk-c++/common/RSASign.h @@ -0,0 +1,19 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +using namespace std; + +#define PUBLIC_KEY_FILE_EX "publicEx.pem" //公钥文件 +#define PRIVATE_KEY_FILE_EX "privateEx.pem" //私钥文件 + +RSA* GetPublicKeyEx(char* szPath); +RSA* GetPrivateKeyEx(char* szPath); + +bool RSASignAction(const string& strEnData,char *privateKeyFilePath, string &strSigned); +bool RSAVerifyAction(string strEnData, string &strSigned); diff --git a/sop-sdk/sdk-c++/common/RequestType.h b/sop-sdk/sdk-c++/common/RequestType.h new file mode 100644 index 00000000..0d4439c1 --- /dev/null +++ b/sop-sdk/sdk-c++/common/RequestType.h @@ -0,0 +1,11 @@ +#ifndef SDK_CXX_REQUESTTYPE_H +#define SDK_CXX_REQUESTTYPE_H + +enum RequestType { + GET, + POST_FORM, + POST_JSON, + POST_FILE +}; + +#endif //SDK_CXX_REQUESTTYPE_H diff --git a/sop-sdk/sdk-c++/common/httplib.h b/sop-sdk/sdk-c++/common/httplib.h new file mode 100644 index 00000000..c4037084 --- /dev/null +++ b/sop-sdk/sdk-c++/common/httplib.h @@ -0,0 +1,7464 @@ +// +// httplib.h +// +// Copyright (c) 2020 Yuji Hirose. All rights reserved. +// MIT License +// + +#ifndef CPPHTTPLIB_HTTPLIB_H +#define CPPHTTPLIB_HTTPLIB_H + +/* + * Configuration + */ + +#ifndef CPPHTTPLIB_KEEPALIVE_TIMEOUT_SECOND +#define CPPHTTPLIB_KEEPALIVE_TIMEOUT_SECOND 5 +#endif + +#ifndef CPPHTTPLIB_KEEPALIVE_MAX_COUNT +#define CPPHTTPLIB_KEEPALIVE_MAX_COUNT 5 +#endif + +#ifndef CPPHTTPLIB_CONNECTION_TIMEOUT_SECOND +#define CPPHTTPLIB_CONNECTION_TIMEOUT_SECOND 300 +#endif + +#ifndef CPPHTTPLIB_CONNECTION_TIMEOUT_USECOND +#define CPPHTTPLIB_CONNECTION_TIMEOUT_USECOND 0 +#endif + +#ifndef CPPHTTPLIB_READ_TIMEOUT_SECOND +#define CPPHTTPLIB_READ_TIMEOUT_SECOND 5 +#endif + +#ifndef CPPHTTPLIB_READ_TIMEOUT_USECOND +#define CPPHTTPLIB_READ_TIMEOUT_USECOND 0 +#endif + +#ifndef CPPHTTPLIB_WRITE_TIMEOUT_SECOND +#define CPPHTTPLIB_WRITE_TIMEOUT_SECOND 5 +#endif + +#ifndef CPPHTTPLIB_WRITE_TIMEOUT_USECOND +#define CPPHTTPLIB_WRITE_TIMEOUT_USECOND 0 +#endif + +#ifndef CPPHTTPLIB_IDLE_INTERVAL_SECOND +#define CPPHTTPLIB_IDLE_INTERVAL_SECOND 0 +#endif + +#ifndef CPPHTTPLIB_IDLE_INTERVAL_USECOND +#ifdef _WIN32 +#define CPPHTTPLIB_IDLE_INTERVAL_USECOND 10000 +#else +#define CPPHTTPLIB_IDLE_INTERVAL_USECOND 0 +#endif +#endif + +#ifndef CPPHTTPLIB_REQUEST_URI_MAX_LENGTH +#define CPPHTTPLIB_REQUEST_URI_MAX_LENGTH 8192 +#endif + +#ifndef CPPHTTPLIB_REDIRECT_MAX_COUNT +#define CPPHTTPLIB_REDIRECT_MAX_COUNT 20 +#endif + +#ifndef CPPHTTPLIB_PAYLOAD_MAX_LENGTH +#define CPPHTTPLIB_PAYLOAD_MAX_LENGTH ((std::numeric_limits::max)()) +#endif + +#ifndef CPPHTTPLIB_TCP_NODELAY +#define CPPHTTPLIB_TCP_NODELAY false +#endif + +#ifndef CPPHTTPLIB_RECV_BUFSIZ +#define CPPHTTPLIB_RECV_BUFSIZ size_t(4096u) +#endif + +#ifndef CPPHTTPLIB_COMPRESSION_BUFSIZ +#define CPPHTTPLIB_COMPRESSION_BUFSIZ size_t(16384u) +#endif + +#ifndef CPPHTTPLIB_THREAD_POOL_COUNT +#define CPPHTTPLIB_THREAD_POOL_COUNT \ + ((std::max)(8u, std::thread::hardware_concurrency() > 0 \ + ? std::thread::hardware_concurrency() - 1 \ + : 0)) +#endif + +#ifndef CPPHTTPLIB_RECV_FLAGS +#define CPPHTTPLIB_RECV_FLAGS 0 +#endif + +#ifndef CPPHTTPLIB_SEND_FLAGS +#define CPPHTTPLIB_SEND_FLAGS 0 +#endif + +/* + * Headers + */ + +#ifdef _WIN32 +#ifndef _CRT_SECURE_NO_WARNINGS +#define _CRT_SECURE_NO_WARNINGS +#endif //_CRT_SECURE_NO_WARNINGS + +#ifndef _CRT_NONSTDC_NO_DEPRECATE +#define _CRT_NONSTDC_NO_DEPRECATE +#endif //_CRT_NONSTDC_NO_DEPRECATE + +#if defined(_MSC_VER) +#ifdef _WIN64 +using ssize_t = __int64; +#else +using ssize_t = int; +#endif + +#if _MSC_VER < 1900 +#define snprintf _snprintf_s +#endif +#endif // _MSC_VER + +#ifndef S_ISREG +#define S_ISREG(m) (((m)&S_IFREG) == S_IFREG) +#endif // S_ISREG + +#ifndef S_ISDIR +#define S_ISDIR(m) (((m)&S_IFDIR) == S_IFDIR) +#endif // S_ISDIR + +#ifndef NOMINMAX +#define NOMINMAX +#endif // NOMINMAX + +#include +#include + +#include +#include + +#ifndef WSA_FLAG_NO_HANDLE_INHERIT +#define WSA_FLAG_NO_HANDLE_INHERIT 0x80 +#endif + +#ifdef _MSC_VER +#pragma comment(lib, "ws2_32.lib") +#pragma comment(lib, "crypt32.lib") +#pragma comment(lib, "cryptui.lib") +#endif + +#ifndef strcasecmp +#define strcasecmp _stricmp +#endif // strcasecmp + +using socket_t = SOCKET; +#ifdef CPPHTTPLIB_USE_POLL +#define poll(fds, nfds, timeout) WSAPoll(fds, nfds, timeout) +#endif + +#else // not _WIN32 + +#include +#include +#include +#include +#include +#ifdef __linux__ +#include +#endif +#include +#ifdef CPPHTTPLIB_USE_POLL +#include +#endif +#include +#include +#include +#include +#include + +using socket_t = int; +#define INVALID_SOCKET (-1) +#endif //_WIN32 + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT +#include +#include +#include +#include + +#if defined(_WIN32) && defined(OPENSSL_USE_APPLINK) +#include +#endif + +#include +#include + +#if OPENSSL_VERSION_NUMBER < 0x1010100fL +#error Sorry, OpenSSL versions prior to 1.1.1 are not supported +#endif + +#if OPENSSL_VERSION_NUMBER < 0x10100000L +#include +inline const unsigned char *ASN1_STRING_get0_data(const ASN1_STRING *asn1) { + return M_ASN1_STRING_data(asn1); +} +#endif +#endif + +#ifdef CPPHTTPLIB_ZLIB_SUPPORT +#include +#endif + +#ifdef CPPHTTPLIB_BROTLI_SUPPORT +#include +#include +#endif + +/* + * Declaration + */ +namespace httplib { + +namespace detail { + +/* + * Backport std::make_unique from C++14. + * + * NOTE: This code came up with the following stackoverflow post: + * https://stackoverflow.com/questions/10149840/c-arrays-and-make-unique + * + */ + +template +typename std::enable_if::value, std::unique_ptr>::type +make_unique(Args &&... args) { + return std::unique_ptr(new T(std::forward(args)...)); +} + +template +typename std::enable_if::value, std::unique_ptr>::type +make_unique(std::size_t n) { + typedef typename std::remove_extent::type RT; + return std::unique_ptr(new RT[n]); +} + +struct ci { + bool operator()(const std::string &s1, const std::string &s2) const { + return std::lexicographical_compare(s1.begin(), s1.end(), s2.begin(), + s2.end(), + [](unsigned char c1, unsigned char c2) { + return ::tolower(c1) < ::tolower(c2); + }); + } +}; + +} // namespace detail + +using Headers = std::multimap; + +using Params = std::multimap; +using Match = std::smatch; + +using Progress = std::function; + +struct Response; +using ResponseHandler = std::function; + +struct MultipartFormData { + std::string name; + std::string content; + std::string filename; + std::string content_type; +}; +using MultipartFormDataItems = std::vector; +using MultipartFormDataMap = std::multimap; + +class DataSink { +public: + DataSink() : os(&sb_), sb_(*this) {} + + DataSink(const DataSink &) = delete; + DataSink &operator=(const DataSink &) = delete; + DataSink(DataSink &&) = delete; + DataSink &operator=(DataSink &&) = delete; + + std::function write; + std::function done; + std::function is_writable; + std::ostream os; + +private: + class data_sink_streambuf : public std::streambuf { + public: + explicit data_sink_streambuf(DataSink &sink) : sink_(sink) {} + + protected: + std::streamsize xsputn(const char *s, std::streamsize n) { + sink_.write(s, static_cast(n)); + return n; + } + + private: + DataSink &sink_; + }; + + data_sink_streambuf sb_; +}; + +using ContentProvider = + std::function; + +using ContentProviderWithoutLength = + std::function; + +using ContentReceiverWithProgress = + std::function; + +using ContentReceiver = + std::function; + +using MultipartContentHeader = + std::function; + +class ContentReader { +public: + using Reader = std::function; + using MultipartReader = std::function; + + ContentReader(Reader reader, MultipartReader multipart_reader) + : reader_(std::move(reader)), + multipart_reader_(std::move(multipart_reader)) {} + + bool operator()(MultipartContentHeader header, + ContentReceiver receiver) const { + return multipart_reader_(std::move(header), std::move(receiver)); + } + + bool operator()(ContentReceiver receiver) const { + return reader_(std::move(receiver)); + } + + Reader reader_; + MultipartReader multipart_reader_; +}; + +using Range = std::pair; +using Ranges = std::vector; + +struct Request { + std::string method; + std::string path; + Headers headers; + std::string body; + + std::string remote_addr; + int remote_port = -1; + + // for server + std::string version; + std::string target; + Params params; + MultipartFormDataMap files; + Ranges ranges; + Match matches; + + // for client + ResponseHandler response_handler; + ContentReceiverWithProgress content_receiver; + Progress progress; +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + const SSL *ssl; +#endif + + bool has_header(const char *key) const; + std::string get_header_value(const char *key, size_t id = 0) const; + template + T get_header_value(const char *key, size_t id = 0) const; + size_t get_header_value_count(const char *key) const; + void set_header(const char *key, const char *val); + void set_header(const char *key, const std::string &val); + + bool has_param(const char *key) const; + std::string get_param_value(const char *key, size_t id = 0) const; + size_t get_param_value_count(const char *key) const; + + bool is_multipart_form_data() const; + + bool has_file(const char *key) const; + MultipartFormData get_file_value(const char *key) const; + + // private members... + size_t redirect_count_ = CPPHTTPLIB_REDIRECT_MAX_COUNT; + size_t content_length_ = 0; + ContentProvider content_provider_; + bool is_chunked_content_provider_ = false; + size_t authorization_count_ = 0; +}; + +struct Response { + std::string version; + int status = -1; + std::string reason; + Headers headers; + std::string body; + std::string location; // Redirect location + + bool has_header(const char *key) const; + std::string get_header_value(const char *key, size_t id = 0) const; + template + T get_header_value(const char *key, size_t id = 0) const; + size_t get_header_value_count(const char *key) const; + void set_header(const char *key, const char *val); + void set_header(const char *key, const std::string &val); + + void set_redirect(const char *url, int status = 302); + void set_redirect(const std::string &url, int status = 302); + void set_content(const char *s, size_t n, const char *content_type); + void set_content(const std::string &s, const char *content_type); + + void set_content_provider( + size_t length, const char *content_type, ContentProvider provider, + const std::function &resource_releaser = nullptr); + + void set_content_provider( + const char *content_type, ContentProviderWithoutLength provider, + const std::function &resource_releaser = nullptr); + + void set_chunked_content_provider( + const char *content_type, ContentProviderWithoutLength provider, + const std::function &resource_releaser = nullptr); + + Response() = default; + Response(const Response &) = default; + Response &operator=(const Response &) = default; + Response(Response &&) = default; + Response &operator=(Response &&) = default; + ~Response() { + if (content_provider_resource_releaser_) { + content_provider_resource_releaser_(); + } + } + + // private members... + size_t content_length_ = 0; + ContentProvider content_provider_; + std::function content_provider_resource_releaser_; + bool is_chunked_content_provider_ = false; +}; + +class Stream { +public: + virtual ~Stream() = default; + + virtual bool is_readable() const = 0; + virtual bool is_writable() const = 0; + + virtual ssize_t read(char *ptr, size_t size) = 0; + virtual ssize_t write(const char *ptr, size_t size) = 0; + virtual void get_remote_ip_and_port(std::string &ip, int &port) const = 0; + virtual socket_t socket() const = 0; + + template + ssize_t write_format(const char *fmt, const Args &... args); + ssize_t write(const char *ptr); + ssize_t write(const std::string &s); +}; + +class TaskQueue { +public: + TaskQueue() = default; + virtual ~TaskQueue() = default; + + virtual void enqueue(std::function fn) = 0; + virtual void shutdown() = 0; + + virtual void on_idle(){}; +}; + +class ThreadPool : public TaskQueue { +public: + explicit ThreadPool(size_t n) : shutdown_(false) { + while (n) { + threads_.emplace_back(worker(*this)); + n--; + } + } + + ThreadPool(const ThreadPool &) = delete; + ~ThreadPool() override = default; + + void enqueue(std::function fn) override { + std::unique_lock lock(mutex_); + jobs_.push_back(std::move(fn)); + cond_.notify_one(); + } + + void shutdown() override { + // Stop all worker threads... + { + std::unique_lock lock(mutex_); + shutdown_ = true; + } + + cond_.notify_all(); + + // Join... + for (auto &t : threads_) { + t.join(); + } + } + +private: + struct worker { + explicit worker(ThreadPool &pool) : pool_(pool) {} + + void operator()() { + for (;;) { + std::function fn; + { + std::unique_lock lock(pool_.mutex_); + + pool_.cond_.wait( + lock, [&] { return !pool_.jobs_.empty() || pool_.shutdown_; }); + + if (pool_.shutdown_ && pool_.jobs_.empty()) { break; } + + fn = pool_.jobs_.front(); + pool_.jobs_.pop_front(); + } + + assert(true == static_cast(fn)); + fn(); + } + } + + ThreadPool &pool_; + }; + friend struct worker; + + std::vector threads_; + std::list> jobs_; + + bool shutdown_; + + std::condition_variable cond_; + std::mutex mutex_; +}; + +using Logger = std::function; + +using SocketOptions = std::function; + +inline void default_socket_options(socket_t sock) { + int yes = 1; +#ifdef _WIN32 + setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, reinterpret_cast(&yes), + sizeof(yes)); + setsockopt(sock, SOL_SOCKET, SO_EXCLUSIVEADDRUSE, + reinterpret_cast(&yes), sizeof(yes)); +#else +#ifdef SO_REUSEPORT + setsockopt(sock, SOL_SOCKET, SO_REUSEPORT, reinterpret_cast(&yes), + sizeof(yes)); +#else + setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, reinterpret_cast(&yes), + sizeof(yes)); +#endif +#endif +} + +class Server { +public: + using Handler = std::function; + + using ExceptionHandler = + std::function; + + enum class HandlerResponse { + Handled, + Unhandled, + }; + using HandlerWithResponse = + std::function; + + using HandlerWithContentReader = std::function; + + using Expect100ContinueHandler = + std::function; + + Server(); + + virtual ~Server(); + + virtual bool is_valid() const; + + Server &Get(const char *pattern, Handler handler); + Server &Get(const char *pattern, size_t pattern_len, Handler handler); + Server &Post(const char *pattern, Handler handler); + Server &Post(const char *pattern, size_t pattern_len, Handler handler); + Server &Post(const char *pattern, HandlerWithContentReader handler); + Server &Post(const char *pattern, size_t pattern_len, + HandlerWithContentReader handler); + Server &Put(const char *pattern, Handler handler); + Server &Put(const char *pattern, size_t pattern_len, Handler handler); + Server &Put(const char *pattern, HandlerWithContentReader handler); + Server &Put(const char *pattern, size_t pattern_len, + HandlerWithContentReader handler); + Server &Patch(const char *pattern, Handler handler); + Server &Patch(const char *pattern, size_t pattern_len, Handler handler); + Server &Patch(const char *pattern, HandlerWithContentReader handler); + Server &Patch(const char *pattern, size_t pattern_len, + HandlerWithContentReader handler); + Server &Delete(const char *pattern, Handler handler); + Server &Delete(const char *pattern, size_t pattern_len, Handler handler); + Server &Delete(const char *pattern, HandlerWithContentReader handler); + Server &Delete(const char *pattern, size_t pattern_len, + HandlerWithContentReader handler); + Server &Options(const char *pattern, Handler handler); + Server &Options(const char *pattern, size_t pattern_len, Handler handler); + + bool set_base_dir(const char *dir, const char *mount_point = nullptr); + bool set_mount_point(const char *mount_point, const char *dir, + Headers headers = Headers()); + bool remove_mount_point(const char *mount_point); + Server &set_file_extension_and_mimetype_mapping(const char *ext, + const char *mime); + Server &set_file_request_handler(Handler handler); + + Server &set_error_handler(HandlerWithResponse handler); + Server &set_error_handler(Handler handler); + Server &set_exception_handler(ExceptionHandler handler); + Server &set_pre_routing_handler(HandlerWithResponse handler); + Server &set_post_routing_handler(Handler handler); + + Server &set_expect_100_continue_handler(Expect100ContinueHandler handler); + Server &set_logger(Logger logger); + + Server &set_tcp_nodelay(bool on); + Server &set_socket_options(SocketOptions socket_options); + + Server &set_keep_alive_max_count(size_t count); + Server &set_keep_alive_timeout(time_t sec); + Server &set_read_timeout(time_t sec, time_t usec = 0); + Server &set_write_timeout(time_t sec, time_t usec = 0); + Server &set_idle_interval(time_t sec, time_t usec = 0); + + Server &set_payload_max_length(size_t length); + + bool bind_to_port(const char *host, int port, int socket_flags = 0); + int bind_to_any_port(const char *host, int socket_flags = 0); + bool listen_after_bind(); + + bool listen(const char *host, int port, int socket_flags = 0); + + bool is_running() const; + void stop(); + + std::function new_task_queue; + +protected: + bool process_request(Stream &strm, bool close_connection, + bool &connection_closed, + const std::function &setup_request); + + std::atomic svr_sock_; + size_t keep_alive_max_count_ = CPPHTTPLIB_KEEPALIVE_MAX_COUNT; + time_t keep_alive_timeout_sec_ = CPPHTTPLIB_KEEPALIVE_TIMEOUT_SECOND; + time_t read_timeout_sec_ = CPPHTTPLIB_READ_TIMEOUT_SECOND; + time_t read_timeout_usec_ = CPPHTTPLIB_READ_TIMEOUT_USECOND; + time_t write_timeout_sec_ = CPPHTTPLIB_WRITE_TIMEOUT_SECOND; + time_t write_timeout_usec_ = CPPHTTPLIB_WRITE_TIMEOUT_USECOND; + time_t idle_interval_sec_ = CPPHTTPLIB_IDLE_INTERVAL_SECOND; + time_t idle_interval_usec_ = CPPHTTPLIB_IDLE_INTERVAL_USECOND; + size_t payload_max_length_ = CPPHTTPLIB_PAYLOAD_MAX_LENGTH; + +private: + using Handlers = std::vector>; + using HandlersForContentReader = + std::vector>; + + socket_t create_server_socket(const char *host, int port, int socket_flags, + SocketOptions socket_options) const; + int bind_internal(const char *host, int port, int socket_flags); + bool listen_internal(); + + bool routing(Request &req, Response &res, Stream &strm); + bool handle_file_request(const Request &req, Response &res, + bool head = false); + bool dispatch_request(Request &req, Response &res, const Handlers &handlers); + bool + dispatch_request_for_content_reader(Request &req, Response &res, + ContentReader content_reader, + const HandlersForContentReader &handlers); + + bool parse_request_line(const char *s, Request &req); + void apply_ranges(const Request &req, Response &res, + std::string &content_type, std::string &boundary); + bool write_response(Stream &strm, bool close_connection, const Request &req, + Response &res); + bool write_response_with_content(Stream &strm, bool close_connection, + const Request &req, Response &res); + bool write_response_core(Stream &strm, bool close_connection, + const Request &req, Response &res, + bool need_apply_ranges); + bool write_content_with_provider(Stream &strm, const Request &req, + Response &res, const std::string &boundary, + const std::string &content_type); + bool read_content(Stream &strm, Request &req, Response &res); + bool + read_content_with_content_receiver(Stream &strm, Request &req, Response &res, + ContentReceiver receiver, + MultipartContentHeader multipart_header, + ContentReceiver multipart_receiver); + bool read_content_core(Stream &strm, Request &req, Response &res, + ContentReceiver receiver, + MultipartContentHeader mulitpart_header, + ContentReceiver multipart_receiver); + + virtual bool process_and_close_socket(socket_t sock); + + struct MountPointEntry { + std::string mount_point; + std::string base_dir; + Headers headers; + }; + std::vector base_dirs_; + + std::atomic is_running_; + std::map file_extension_and_mimetype_map_; + Handler file_request_handler_; + Handlers get_handlers_; + Handlers post_handlers_; + HandlersForContentReader post_handlers_for_content_reader_; + Handlers put_handlers_; + HandlersForContentReader put_handlers_for_content_reader_; + Handlers patch_handlers_; + HandlersForContentReader patch_handlers_for_content_reader_; + Handlers delete_handlers_; + HandlersForContentReader delete_handlers_for_content_reader_; + Handlers options_handlers_; + HandlerWithResponse error_handler_; + ExceptionHandler exception_handler_; + HandlerWithResponse pre_routing_handler_; + Handler post_routing_handler_; + Logger logger_; + Expect100ContinueHandler expect_100_continue_handler_; + + bool tcp_nodelay_ = CPPHTTPLIB_TCP_NODELAY; + SocketOptions socket_options_ = default_socket_options; +}; + +enum Error { + Success = 0, + Unknown, + Connection, + BindIPAddress, + Read, + Write, + ExceedRedirectCount, + Canceled, + SSLConnection, + SSLLoadingCerts, + SSLServerVerification, + UnsupportedMultipartBoundaryChars, + Compression, +}; + +class Result { +public: + Result(std::unique_ptr &&res, Error err, + Headers &&request_headers = Headers{}) + : res_(std::move(res)), err_(err), + request_headers_(std::move(request_headers)) {} + // Response + operator bool() const { return res_ != nullptr; } + bool operator==(std::nullptr_t) const { return res_ == nullptr; } + bool operator!=(std::nullptr_t) const { return res_ != nullptr; } + const Response &value() const { return *res_; } + Response &value() { return *res_; } + const Response &operator*() const { return *res_; } + Response &operator*() { return *res_; } + const Response *operator->() const { return res_.get(); } + Response *operator->() { return res_.get(); } + + // Error + Error error() const { return err_; } + + // Request Headers + bool has_request_header(const char *key) const; + std::string get_request_header_value(const char *key, size_t id = 0) const; + template + T get_request_header_value(const char *key, size_t id = 0) const; + size_t get_request_header_value_count(const char *key) const; + +private: + std::unique_ptr res_; + Error err_; + Headers request_headers_; +}; + +class ClientImpl { +public: + explicit ClientImpl(const std::string &host); + + explicit ClientImpl(const std::string &host, int port); + + explicit ClientImpl(const std::string &host, int port, + const std::string &client_cert_path, + const std::string &client_key_path); + + virtual ~ClientImpl(); + + virtual bool is_valid() const; + + Result Get(const char *path); + Result Get(const char *path, const Headers &headers); + Result Get(const char *path, Progress progress); + Result Get(const char *path, const Headers &headers, Progress progress); + Result Get(const char *path, ContentReceiver content_receiver); + Result Get(const char *path, const Headers &headers, + ContentReceiver content_receiver); + Result Get(const char *path, ContentReceiver content_receiver, + Progress progress); + Result Get(const char *path, const Headers &headers, + ContentReceiver content_receiver, Progress progress); + Result Get(const char *path, ResponseHandler response_handler, + ContentReceiver content_receiver); + Result Get(const char *path, const Headers &headers, + ResponseHandler response_handler, + ContentReceiver content_receiver); + Result Get(const char *path, ResponseHandler response_handler, + ContentReceiver content_receiver, Progress progress); + Result Get(const char *path, const Headers &headers, + ResponseHandler response_handler, ContentReceiver content_receiver, + Progress progress); + + Result Get(const char *path, const Params ¶ms, const Headers &headers, + Progress progress = nullptr); + Result Get(const char *path, const Params ¶ms, const Headers &headers, + ContentReceiver content_receiver, Progress progress = nullptr); + Result Get(const char *path, const Params ¶ms, const Headers &headers, + ResponseHandler response_handler, ContentReceiver content_receiver, + Progress progress = nullptr); + + Result Head(const char *path); + Result Head(const char *path, const Headers &headers); + + Result Post(const char *path); + Result Post(const char *path, const char *body, size_t content_length, + const char *content_type); + Result Post(const char *path, const Headers &headers, const char *body, + size_t content_length, const char *content_type); + Result Post(const char *path, const std::string &body, + const char *content_type); + Result Post(const char *path, const Headers &headers, const std::string &body, + const char *content_type); + Result Post(const char *path, size_t content_length, + ContentProvider content_provider, const char *content_type); + Result Post(const char *path, ContentProviderWithoutLength content_provider, + const char *content_type); + Result Post(const char *path, const Headers &headers, size_t content_length, + ContentProvider content_provider, const char *content_type); + Result Post(const char *path, const Headers &headers, + ContentProviderWithoutLength content_provider, + const char *content_type); + Result Post(const char *path, const Params ¶ms); + Result Post(const char *path, const Headers &headers, const Params ¶ms); + Result Post(const char *path, const MultipartFormDataItems &items); + Result Post(const char *path, const Headers &headers, + const MultipartFormDataItems &items); + Result Post(const char *path, const Headers &headers, + const MultipartFormDataItems &items, const std::string &boundary); + + Result Put(const char *path); + Result Put(const char *path, const char *body, size_t content_length, + const char *content_type); + Result Put(const char *path, const Headers &headers, const char *body, + size_t content_length, const char *content_type); + Result Put(const char *path, const std::string &body, + const char *content_type); + Result Put(const char *path, const Headers &headers, const std::string &body, + const char *content_type); + Result Put(const char *path, size_t content_length, + ContentProvider content_provider, const char *content_type); + Result Put(const char *path, ContentProviderWithoutLength content_provider, + const char *content_type); + Result Put(const char *path, const Headers &headers, size_t content_length, + ContentProvider content_provider, const char *content_type); + Result Put(const char *path, const Headers &headers, + ContentProviderWithoutLength content_provider, + const char *content_type); + Result Put(const char *path, const Params ¶ms); + Result Put(const char *path, const Headers &headers, const Params ¶ms); + + Result Patch(const char *path); + Result Patch(const char *path, const char *body, size_t content_length, + const char *content_type); + Result Patch(const char *path, const Headers &headers, const char *body, + size_t content_length, const char *content_type); + Result Patch(const char *path, const std::string &body, + const char *content_type); + Result Patch(const char *path, const Headers &headers, + const std::string &body, const char *content_type); + Result Patch(const char *path, size_t content_length, + ContentProvider content_provider, const char *content_type); + Result Patch(const char *path, ContentProviderWithoutLength content_provider, + const char *content_type); + Result Patch(const char *path, const Headers &headers, size_t content_length, + ContentProvider content_provider, const char *content_type); + Result Patch(const char *path, const Headers &headers, + ContentProviderWithoutLength content_provider, + const char *content_type); + + Result Delete(const char *path); + Result Delete(const char *path, const Headers &headers); + Result Delete(const char *path, const char *body, size_t content_length, + const char *content_type); + Result Delete(const char *path, const Headers &headers, const char *body, + size_t content_length, const char *content_type); + Result Delete(const char *path, const std::string &body, + const char *content_type); + Result Delete(const char *path, const Headers &headers, + const std::string &body, const char *content_type); + + Result Options(const char *path); + Result Options(const char *path, const Headers &headers); + + bool send(Request &req, Response &res, Error &error); + Result send(const Request &req); + + size_t is_socket_open() const; + + void stop(); + + void set_default_headers(Headers headers); + + void set_tcp_nodelay(bool on); + void set_socket_options(SocketOptions socket_options); + + void set_connection_timeout(time_t sec, time_t usec = 0); + void set_read_timeout(time_t sec, time_t usec = 0); + void set_write_timeout(time_t sec, time_t usec = 0); + + void set_basic_auth(const char *username, const char *password); + void set_bearer_token_auth(const char *token); +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + void set_digest_auth(const char *username, const char *password); +#endif + + void set_keep_alive(bool on); + void set_follow_location(bool on); + + void set_compress(bool on); + + void set_decompress(bool on); + + void set_interface(const char *intf); + + void set_proxy(const char *host, int port); + void set_proxy_basic_auth(const char *username, const char *password); + void set_proxy_bearer_token_auth(const char *token); +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + void set_proxy_digest_auth(const char *username, const char *password); +#endif + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + void enable_server_certificate_verification(bool enabled); +#endif + + void set_logger(Logger logger); + +protected: + struct Socket { + socket_t sock = INVALID_SOCKET; +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + SSL *ssl = nullptr; +#endif + + bool is_open() const { return sock != INVALID_SOCKET; } + }; + + Result send_(Request &&req); + + virtual bool create_and_connect_socket(Socket &socket, Error &error); + + // All of: + // shutdown_ssl + // shutdown_socket + // close_socket + // should ONLY be called when socket_mutex_ is locked. + // Also, shutdown_ssl and close_socket should also NOT be called concurrently + // with a DIFFERENT thread sending requests using that socket. + virtual void shutdown_ssl(Socket &socket, bool shutdown_gracefully); + void shutdown_socket(Socket &socket); + void close_socket(Socket &socket); + + // Similar to shutdown_ssl and close_socket, this should NOT be called + // concurrently with a DIFFERENT thread sending requests from the socket + void lock_socket_and_shutdown_and_close(); + + bool process_request(Stream &strm, Request &req, Response &res, + bool close_connection, Error &error); + + bool write_content_with_provider(Stream &strm, const Request &req, + Error &error); + + void copy_settings(const ClientImpl &rhs); + + // Socket endoint information + const std::string host_; + const int port_; + const std::string host_and_port_; + + // Current open socket + Socket socket_; + mutable std::mutex socket_mutex_; + std::recursive_mutex request_mutex_; + + // These are all protected under socket_mutex + size_t socket_requests_in_flight_ = 0; + std::thread::id socket_requests_are_from_thread_ = std::thread::id(); + bool socket_should_be_closed_when_request_is_done_ = false; + + // Default headers + Headers default_headers_; + + // Settings + std::string client_cert_path_; + std::string client_key_path_; + + time_t connection_timeout_sec_ = CPPHTTPLIB_CONNECTION_TIMEOUT_SECOND; + time_t connection_timeout_usec_ = CPPHTTPLIB_CONNECTION_TIMEOUT_USECOND; + time_t read_timeout_sec_ = CPPHTTPLIB_READ_TIMEOUT_SECOND; + time_t read_timeout_usec_ = CPPHTTPLIB_READ_TIMEOUT_USECOND; + time_t write_timeout_sec_ = CPPHTTPLIB_WRITE_TIMEOUT_SECOND; + time_t write_timeout_usec_ = CPPHTTPLIB_WRITE_TIMEOUT_USECOND; + + std::string basic_auth_username_; + std::string basic_auth_password_; + std::string bearer_token_auth_token_; +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + std::string digest_auth_username_; + std::string digest_auth_password_; +#endif + + bool keep_alive_ = false; + bool follow_location_ = false; + + bool tcp_nodelay_ = CPPHTTPLIB_TCP_NODELAY; + SocketOptions socket_options_ = nullptr; + + bool compress_ = false; + bool decompress_ = true; + + std::string interface_; + + std::string proxy_host_; + int proxy_port_ = -1; + + std::string proxy_basic_auth_username_; + std::string proxy_basic_auth_password_; + std::string proxy_bearer_token_auth_token_; +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + std::string proxy_digest_auth_username_; + std::string proxy_digest_auth_password_; +#endif + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + bool server_certificate_verification_ = true; +#endif + + Logger logger_; + +private: + socket_t create_client_socket(Error &error) const; + bool read_response_line(Stream &strm, const Request &req, Response &res); + bool write_request(Stream &strm, Request &req, bool close_connection, + Error &error); + bool redirect(Request &req, Response &res, Error &error); + bool handle_request(Stream &strm, Request &req, Response &res, + bool close_connection, Error &error); + std::unique_ptr send_with_content_provider( + Request &req, + // const char *method, const char *path, const Headers &headers, + const char *body, size_t content_length, ContentProvider content_provider, + ContentProviderWithoutLength content_provider_without_length, + const char *content_type, Error &error); + Result send_with_content_provider( + const char *method, const char *path, const Headers &headers, + const char *body, size_t content_length, ContentProvider content_provider, + ContentProviderWithoutLength content_provider_without_length, + const char *content_type); + + virtual bool process_socket(const Socket &socket, + std::function callback); + virtual bool is_ssl() const; +}; + +class Client { +public: + // Universal interface + explicit Client(const char *scheme_host_port); + + explicit Client(const char *scheme_host_port, + const std::string &client_cert_path, + const std::string &client_key_path); + + // HTTP only interface + explicit Client(const std::string &host, int port); + + explicit Client(const std::string &host, int port, + const std::string &client_cert_path, + const std::string &client_key_path); + + ~Client(); + + bool is_valid() const; + + Result Get(const char *path); + Result Get(const char *path, const Headers &headers); + Result Get(const char *path, Progress progress); + Result Get(const char *path, const Headers &headers, Progress progress); + Result Get(const char *path, ContentReceiver content_receiver); + Result Get(const char *path, const Headers &headers, + ContentReceiver content_receiver); + Result Get(const char *path, ContentReceiver content_receiver, + Progress progress); + Result Get(const char *path, const Headers &headers, + ContentReceiver content_receiver, Progress progress); + Result Get(const char *path, ResponseHandler response_handler, + ContentReceiver content_receiver); + Result Get(const char *path, const Headers &headers, + ResponseHandler response_handler, + ContentReceiver content_receiver); + Result Get(const char *path, const Headers &headers, + ResponseHandler response_handler, ContentReceiver content_receiver, + Progress progress); + Result Get(const char *path, ResponseHandler response_handler, + ContentReceiver content_receiver, Progress progress); + + Result Get(const char *path, const Params ¶ms, const Headers &headers, + Progress progress = nullptr); + Result Get(const char *path, const Params ¶ms, const Headers &headers, + ContentReceiver content_receiver, Progress progress = nullptr); + Result Get(const char *path, const Params ¶ms, const Headers &headers, + ResponseHandler response_handler, ContentReceiver content_receiver, + Progress progress = nullptr); + + Result Head(const char *path); + Result Head(const char *path, const Headers &headers); + + Result Post(const char *path); + Result Post(const char *path, const char *body, size_t content_length, + const char *content_type); + Result Post(const char *path, const Headers &headers, const char *body, + size_t content_length, const char *content_type); + Result Post(const char *path, const std::string &body, + const char *content_type); + Result Post(const char *path, const Headers &headers, const std::string &body, + const char *content_type); + Result Post(const char *path, size_t content_length, + ContentProvider content_provider, const char *content_type); + Result Post(const char *path, ContentProviderWithoutLength content_provider, + const char *content_type); + Result Post(const char *path, const Headers &headers, size_t content_length, + ContentProvider content_provider, const char *content_type); + Result Post(const char *path, const Headers &headers, + ContentProviderWithoutLength content_provider, + const char *content_type); + Result Post(const char *path, const Params ¶ms); + Result Post(const char *path, const Headers &headers, const Params ¶ms); + Result Post(const char *path, const MultipartFormDataItems &items); + Result Post(const char *path, const Headers &headers, + const MultipartFormDataItems &items); + Result Post(const char *path, const Headers &headers, + const MultipartFormDataItems &items, const std::string &boundary); + Result Put(const char *path); + Result Put(const char *path, const char *body, size_t content_length, + const char *content_type); + Result Put(const char *path, const Headers &headers, const char *body, + size_t content_length, const char *content_type); + Result Put(const char *path, const std::string &body, + const char *content_type); + Result Put(const char *path, const Headers &headers, const std::string &body, + const char *content_type); + Result Put(const char *path, size_t content_length, + ContentProvider content_provider, const char *content_type); + Result Put(const char *path, ContentProviderWithoutLength content_provider, + const char *content_type); + Result Put(const char *path, const Headers &headers, size_t content_length, + ContentProvider content_provider, const char *content_type); + Result Put(const char *path, const Headers &headers, + ContentProviderWithoutLength content_provider, + const char *content_type); + Result Put(const char *path, const Params ¶ms); + Result Put(const char *path, const Headers &headers, const Params ¶ms); + Result Patch(const char *path); + Result Patch(const char *path, const char *body, size_t content_length, + const char *content_type); + Result Patch(const char *path, const Headers &headers, const char *body, + size_t content_length, const char *content_type); + Result Patch(const char *path, const std::string &body, + const char *content_type); + Result Patch(const char *path, const Headers &headers, + const std::string &body, const char *content_type); + Result Patch(const char *path, size_t content_length, + ContentProvider content_provider, const char *content_type); + Result Patch(const char *path, ContentProviderWithoutLength content_provider, + const char *content_type); + Result Patch(const char *path, const Headers &headers, size_t content_length, + ContentProvider content_provider, const char *content_type); + Result Patch(const char *path, const Headers &headers, + ContentProviderWithoutLength content_provider, + const char *content_type); + + Result Delete(const char *path); + Result Delete(const char *path, const Headers &headers); + Result Delete(const char *path, const char *body, size_t content_length, + const char *content_type); + Result Delete(const char *path, const Headers &headers, const char *body, + size_t content_length, const char *content_type); + Result Delete(const char *path, const std::string &body, + const char *content_type); + Result Delete(const char *path, const Headers &headers, + const std::string &body, const char *content_type); + + Result Options(const char *path); + Result Options(const char *path, const Headers &headers); + + bool send(Request &req, Response &res, Error &error); + Result send(const Request &req); + + size_t is_socket_open() const; + + void stop(); + + void set_default_headers(Headers headers); + + void set_tcp_nodelay(bool on); + void set_socket_options(SocketOptions socket_options); + + void set_connection_timeout(time_t sec, time_t usec = 0); + void set_read_timeout(time_t sec, time_t usec = 0); + void set_write_timeout(time_t sec, time_t usec = 0); + + void set_basic_auth(const char *username, const char *password); + void set_bearer_token_auth(const char *token); +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + void set_digest_auth(const char *username, const char *password); +#endif + + void set_keep_alive(bool on); + void set_follow_location(bool on); + + void set_compress(bool on); + + void set_decompress(bool on); + + void set_interface(const char *intf); + + void set_proxy(const char *host, int port); + void set_proxy_basic_auth(const char *username, const char *password); + void set_proxy_bearer_token_auth(const char *token); +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + void set_proxy_digest_auth(const char *username, const char *password); +#endif + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + void enable_server_certificate_verification(bool enabled); +#endif + + void set_logger(Logger logger); + + // SSL +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + void set_ca_cert_path(const char *ca_cert_file_path, + const char *ca_cert_dir_path = nullptr); + + void set_ca_cert_store(X509_STORE *ca_cert_store); + + long get_openssl_verify_result() const; + + SSL_CTX *ssl_context() const; +#endif + +private: + std::unique_ptr cli_; + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + bool is_ssl_ = false; +#endif +}; + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT +class SSLServer : public Server { +public: + SSLServer(const char *cert_path, const char *private_key_path, + const char *client_ca_cert_file_path = nullptr, + const char *client_ca_cert_dir_path = nullptr); + + SSLServer(X509 *cert, EVP_PKEY *private_key, + X509_STORE *client_ca_cert_store = nullptr); + + ~SSLServer() override; + + bool is_valid() const override; + +private: + bool process_and_close_socket(socket_t sock) override; + + SSL_CTX *ctx_; + std::mutex ctx_mutex_; +}; + +class SSLClient : public ClientImpl { +public: + explicit SSLClient(const std::string &host); + + explicit SSLClient(const std::string &host, int port); + + explicit SSLClient(const std::string &host, int port, + const std::string &client_cert_path, + const std::string &client_key_path); + + explicit SSLClient(const std::string &host, int port, X509 *client_cert, + EVP_PKEY *client_key); + + ~SSLClient() override; + + bool is_valid() const override; + + void set_ca_cert_path(const char *ca_cert_file_path, + const char *ca_cert_dir_path = nullptr); + + void set_ca_cert_store(X509_STORE *ca_cert_store); + + long get_openssl_verify_result() const; + + SSL_CTX *ssl_context() const; + +private: + bool create_and_connect_socket(Socket &socket, Error &error) override; + void shutdown_ssl(Socket &socket, bool shutdown_gracefully) override; + + bool process_socket(const Socket &socket, + std::function callback) override; + bool is_ssl() const override; + + bool connect_with_proxy(Socket &sock, Response &res, bool &success, + Error &error); + bool initialize_ssl(Socket &socket, Error &error); + + bool load_certs(); + + bool verify_host(X509 *server_cert) const; + bool verify_host_with_subject_alt_name(X509 *server_cert) const; + bool verify_host_with_common_name(X509 *server_cert) const; + bool check_host_name(const char *pattern, size_t pattern_len) const; + + SSL_CTX *ctx_; + std::mutex ctx_mutex_; + std::once_flag initialize_cert_; + + std::vector host_components_; + + std::string ca_cert_file_path_; + std::string ca_cert_dir_path_; + long verify_result_ = 0; + + friend class ClientImpl; +}; +#endif + +// ---------------------------------------------------------------------------- + +/* + * Implementation + */ + +namespace detail { + +inline bool is_hex(char c, int &v) { + if (0x20 <= c && isdigit(c)) { + v = c - '0'; + return true; + } else if ('A' <= c && c <= 'F') { + v = c - 'A' + 10; + return true; + } else if ('a' <= c && c <= 'f') { + v = c - 'a' + 10; + return true; + } + return false; +} + +inline bool from_hex_to_i(const std::string &s, size_t i, size_t cnt, + int &val) { + if (i >= s.size()) { return false; } + + val = 0; + for (; cnt; i++, cnt--) { + if (!s[i]) { return false; } + int v = 0; + if (is_hex(s[i], v)) { + val = val * 16 + v; + } else { + return false; + } + } + return true; +} + +inline std::string from_i_to_hex(size_t n) { + const char *charset = "0123456789abcdef"; + std::string ret; + do { + ret = charset[n & 15] + ret; + n >>= 4; + } while (n > 0); + return ret; +} + +inline size_t to_utf8(int code, char *buff) { + if (code < 0x0080) { + buff[0] = (code & 0x7F); + return 1; + } else if (code < 0x0800) { + buff[0] = static_cast(0xC0 | ((code >> 6) & 0x1F)); + buff[1] = static_cast(0x80 | (code & 0x3F)); + return 2; + } else if (code < 0xD800) { + buff[0] = static_cast(0xE0 | ((code >> 12) & 0xF)); + buff[1] = static_cast(0x80 | ((code >> 6) & 0x3F)); + buff[2] = static_cast(0x80 | (code & 0x3F)); + return 3; + } else if (code < 0xE000) { // D800 - DFFF is invalid... + return 0; + } else if (code < 0x10000) { + buff[0] = static_cast(0xE0 | ((code >> 12) & 0xF)); + buff[1] = static_cast(0x80 | ((code >> 6) & 0x3F)); + buff[2] = static_cast(0x80 | (code & 0x3F)); + return 3; + } else if (code < 0x110000) { + buff[0] = static_cast(0xF0 | ((code >> 18) & 0x7)); + buff[1] = static_cast(0x80 | ((code >> 12) & 0x3F)); + buff[2] = static_cast(0x80 | ((code >> 6) & 0x3F)); + buff[3] = static_cast(0x80 | (code & 0x3F)); + return 4; + } + + // NOTREACHED + return 0; +} + +// NOTE: This code came up with the following stackoverflow post: +// https://stackoverflow.com/questions/180947/base64-decode-snippet-in-c +inline std::string base64_encode(const std::string &in) { + static const auto lookup = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + + std::string out; + out.reserve(in.size()); + + int val = 0; + int valb = -6; + + for (auto c : in) { + val = (val << 8) + static_cast(c); + valb += 8; + while (valb >= 0) { + out.push_back(lookup[(val >> valb) & 0x3F]); + valb -= 6; + } + } + + if (valb > -6) { out.push_back(lookup[((val << 8) >> (valb + 8)) & 0x3F]); } + + while (out.size() % 4) { + out.push_back('='); + } + + return out; +} + +inline bool is_file(const std::string &path) { + struct stat st; + return stat(path.c_str(), &st) >= 0 && S_ISREG(st.st_mode); +} + +inline bool is_dir(const std::string &path) { + struct stat st; + return stat(path.c_str(), &st) >= 0 && S_ISDIR(st.st_mode); +} + +inline bool is_valid_path(const std::string &path) { + size_t level = 0; + size_t i = 0; + + // Skip slash + while (i < path.size() && path[i] == '/') { + i++; + } + + while (i < path.size()) { + // Read component + auto beg = i; + while (i < path.size() && path[i] != '/') { + i++; + } + + auto len = i - beg; + assert(len > 0); + + if (!path.compare(beg, len, ".")) { + ; + } else if (!path.compare(beg, len, "..")) { + if (level == 0) { return false; } + level--; + } else { + level++; + } + + // Skip slash + while (i < path.size() && path[i] == '/') { + i++; + } + } + + return true; +} + +inline std::string encode_query_param(const std::string &value) { + std::ostringstream escaped; + escaped.fill('0'); + escaped << std::hex; + + for (auto c : value) { + if (std::isalnum(static_cast(c)) || c == '-' || c == '_' || + c == '.' || c == '!' || c == '~' || c == '*' || c == '\'' || c == '(' || + c == ')') { + escaped << c; + } else { + escaped << std::uppercase; + escaped << '%' << std::setw(2) + << static_cast(static_cast(c)); + escaped << std::nouppercase; + } + } + + return escaped.str(); +} + +inline std::string encode_url(const std::string &s) { + std::string result; + + for (size_t i = 0; s[i]; i++) { + switch (s[i]) { + case ' ': result += "%20"; break; + case '+': result += "%2B"; break; + case '\r': result += "%0D"; break; + case '\n': result += "%0A"; break; + case '\'': result += "%27"; break; + case ',': result += "%2C"; break; + // case ':': result += "%3A"; break; // ok? probably... + case ';': result += "%3B"; break; + default: + auto c = static_cast(s[i]); + if (c >= 0x80) { + result += '%'; + char hex[4]; + auto len = snprintf(hex, sizeof(hex) - 1, "%02X", c); + assert(len == 2); + result.append(hex, static_cast(len)); + } else { + result += s[i]; + } + break; + } + } + + return result; +} + +inline std::string decode_url(const std::string &s, + bool convert_plus_to_space) { + std::string result; + + for (size_t i = 0; i < s.size(); i++) { + if (s[i] == '%' && i + 1 < s.size()) { + if (s[i + 1] == 'u') { + int val = 0; + if (from_hex_to_i(s, i + 2, 4, val)) { + // 4 digits Unicode codes + char buff[4]; + size_t len = to_utf8(val, buff); + if (len > 0) { result.append(buff, len); } + i += 5; // 'u0000' + } else { + result += s[i]; + } + } else { + int val = 0; + if (from_hex_to_i(s, i + 1, 2, val)) { + // 2 digits hex codes + result += static_cast(val); + i += 2; // '00' + } else { + result += s[i]; + } + } + } else if (convert_plus_to_space && s[i] == '+') { + result += ' '; + } else { + result += s[i]; + } + } + + return result; +} + +inline void read_file(const std::string &path, std::string &out) { + std::ifstream fs(path, std::ios_base::binary); + fs.seekg(0, std::ios_base::end); + auto size = fs.tellg(); + fs.seekg(0); + out.resize(static_cast(size)); + fs.read(&out[0], static_cast(size)); +} + +inline std::string file_extension(const std::string &path) { + std::smatch m; + static auto re = std::regex("\\.([a-zA-Z0-9]+)$"); + if (std::regex_search(path, m, re)) { return m[1].str(); } + return std::string(); +} + +inline bool is_space_or_tab(char c) { return c == ' ' || c == '\t'; } + +inline std::pair trim(const char *b, const char *e, size_t left, + size_t right) { + while (b + left < e && is_space_or_tab(b[left])) { + left++; + } + while (right > 0 && is_space_or_tab(b[right - 1])) { + right--; + } + return std::make_pair(left, right); +} + +inline std::string trim_copy(const std::string &s) { + auto r = trim(s.data(), s.data() + s.size(), 0, s.size()); + return s.substr(r.first, r.second - r.first); +} + +template void split(const char *b, const char *e, char d, Fn fn) { + size_t i = 0; + size_t beg = 0; + + while (e ? (b + i < e) : (b[i] != '\0')) { + if (b[i] == d) { + auto r = trim(b, e, beg, i); + if (r.first < r.second) { fn(&b[r.first], &b[r.second]); } + beg = i + 1; + } + i++; + } + + if (i) { + auto r = trim(b, e, beg, i); + if (r.first < r.second) { fn(&b[r.first], &b[r.second]); } + } +} + +// NOTE: until the read size reaches `fixed_buffer_size`, use `fixed_buffer` +// to store data. The call can set memory on stack for performance. +class stream_line_reader { +public: + stream_line_reader(Stream &strm, char *fixed_buffer, size_t fixed_buffer_size) + : strm_(strm), fixed_buffer_(fixed_buffer), + fixed_buffer_size_(fixed_buffer_size) {} + + const char *ptr() const { + if (glowable_buffer_.empty()) { + return fixed_buffer_; + } else { + return glowable_buffer_.data(); + } + } + + size_t size() const { + if (glowable_buffer_.empty()) { + return fixed_buffer_used_size_; + } else { + return glowable_buffer_.size(); + } + } + + bool end_with_crlf() const { + auto end = ptr() + size(); + return size() >= 2 && end[-2] == '\r' && end[-1] == '\n'; + } + + bool getline() { + fixed_buffer_used_size_ = 0; + glowable_buffer_.clear(); + + for (size_t i = 0;; i++) { + char byte; + auto n = strm_.read(&byte, 1); + + if (n < 0) { + return false; + } else if (n == 0) { + if (i == 0) { + return false; + } else { + break; + } + } + + append(byte); + + if (byte == '\n') { break; } + } + + return true; + } + +private: + void append(char c) { + if (fixed_buffer_used_size_ < fixed_buffer_size_ - 1) { + fixed_buffer_[fixed_buffer_used_size_++] = c; + fixed_buffer_[fixed_buffer_used_size_] = '\0'; + } else { + if (glowable_buffer_.empty()) { + assert(fixed_buffer_[fixed_buffer_used_size_] == '\0'); + glowable_buffer_.assign(fixed_buffer_, fixed_buffer_used_size_); + } + glowable_buffer_ += c; + } + } + + Stream &strm_; + char *fixed_buffer_; + const size_t fixed_buffer_size_; + size_t fixed_buffer_used_size_ = 0; + std::string glowable_buffer_; +}; + +inline int close_socket(socket_t sock) { +#ifdef _WIN32 + return closesocket(sock); +#else + return close(sock); +#endif +} + +template inline ssize_t handle_EINTR(T fn) { + ssize_t res = false; + while (true) { + res = fn(); + if (res < 0 && errno == EINTR) { continue; } + break; + } + return res; +} + +inline ssize_t select_read(socket_t sock, time_t sec, time_t usec) { +#ifdef CPPHTTPLIB_USE_POLL + struct pollfd pfd_read; + pfd_read.fd = sock; + pfd_read.events = POLLIN; + + auto timeout = static_cast(sec * 1000 + usec / 1000); + + return handle_EINTR([&]() { return poll(&pfd_read, 1, timeout); }); +#else +#ifndef _WIN32 + if (sock >= FD_SETSIZE) { return 1; } +#endif + + fd_set fds; + FD_ZERO(&fds); + FD_SET(sock, &fds); + + timeval tv; + tv.tv_sec = static_cast(sec); + tv.tv_usec = static_cast(usec); + + return handle_EINTR([&]() { + return select(static_cast(sock + 1), &fds, nullptr, nullptr, &tv); + }); +#endif +} + +inline ssize_t select_write(socket_t sock, time_t sec, time_t usec) { +#ifdef CPPHTTPLIB_USE_POLL + struct pollfd pfd_read; + pfd_read.fd = sock; + pfd_read.events = POLLOUT; + + auto timeout = static_cast(sec * 1000 + usec / 1000); + + return handle_EINTR([&]() { return poll(&pfd_read, 1, timeout); }); +#else +#ifndef _WIN32 + if (sock >= FD_SETSIZE) { return 1; } +#endif + + fd_set fds; + FD_ZERO(&fds); + FD_SET(sock, &fds); + + timeval tv; + tv.tv_sec = static_cast(sec); + tv.tv_usec = static_cast(usec); + + return handle_EINTR([&]() { + return select(static_cast(sock + 1), nullptr, &fds, nullptr, &tv); + }); +#endif +} + +inline bool wait_until_socket_is_ready(socket_t sock, time_t sec, time_t usec) { +#ifdef CPPHTTPLIB_USE_POLL + struct pollfd pfd_read; + pfd_read.fd = sock; + pfd_read.events = POLLIN | POLLOUT; + + auto timeout = static_cast(sec * 1000 + usec / 1000); + + auto poll_res = handle_EINTR([&]() { return poll(&pfd_read, 1, timeout); }); + + if (poll_res > 0 && pfd_read.revents & (POLLIN | POLLOUT)) { + int error = 0; + socklen_t len = sizeof(error); + auto res = getsockopt(sock, SOL_SOCKET, SO_ERROR, + reinterpret_cast(&error), &len); + return res >= 0 && !error; + } + return false; +#else +#ifndef _WIN32 + if (sock >= FD_SETSIZE) { return false; } +#endif + + fd_set fdsr; + FD_ZERO(&fdsr); + FD_SET(sock, &fdsr); + + auto fdsw = fdsr; + auto fdse = fdsr; + + timeval tv; + tv.tv_sec = static_cast(sec); + tv.tv_usec = static_cast(usec); + + auto ret = handle_EINTR([&]() { + return select(static_cast(sock + 1), &fdsr, &fdsw, &fdse, &tv); + }); + + if (ret > 0 && (FD_ISSET(sock, &fdsr) || FD_ISSET(sock, &fdsw))) { + int error = 0; + socklen_t len = sizeof(error); + return getsockopt(sock, SOL_SOCKET, SO_ERROR, + reinterpret_cast(&error), &len) >= 0 && + !error; + } + return false; +#endif +} + +class SocketStream : public Stream { +public: + SocketStream(socket_t sock, time_t read_timeout_sec, time_t read_timeout_usec, + time_t write_timeout_sec, time_t write_timeout_usec); + ~SocketStream() override; + + bool is_readable() const override; + bool is_writable() const override; + ssize_t read(char *ptr, size_t size) override; + ssize_t write(const char *ptr, size_t size) override; + void get_remote_ip_and_port(std::string &ip, int &port) const override; + socket_t socket() const override; + +private: + socket_t sock_; + time_t read_timeout_sec_; + time_t read_timeout_usec_; + time_t write_timeout_sec_; + time_t write_timeout_usec_; +}; + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT +class SSLSocketStream : public Stream { +public: + SSLSocketStream(socket_t sock, SSL *ssl, time_t read_timeout_sec, + time_t read_timeout_usec, time_t write_timeout_sec, + time_t write_timeout_usec); + ~SSLSocketStream() override; + + bool is_readable() const override; + bool is_writable() const override; + ssize_t read(char *ptr, size_t size) override; + ssize_t write(const char *ptr, size_t size) override; + void get_remote_ip_and_port(std::string &ip, int &port) const override; + socket_t socket() const override; + +private: + socket_t sock_; + SSL *ssl_; + time_t read_timeout_sec_; + time_t read_timeout_usec_; + time_t write_timeout_sec_; + time_t write_timeout_usec_; +}; +#endif + +class BufferStream : public Stream { +public: + BufferStream() = default; + ~BufferStream() override = default; + + bool is_readable() const override; + bool is_writable() const override; + ssize_t read(char *ptr, size_t size) override; + ssize_t write(const char *ptr, size_t size) override; + void get_remote_ip_and_port(std::string &ip, int &port) const override; + socket_t socket() const override; + + const std::string &get_buffer() const; + +private: + std::string buffer; + size_t position = 0; +}; + +inline bool keep_alive(socket_t sock, time_t keep_alive_timeout_sec) { + using namespace std::chrono; + auto start = steady_clock::now(); + while (true) { + auto val = select_read(sock, 0, 10000); + if (val < 0) { + return false; + } else if (val == 0) { + auto current = steady_clock::now(); + auto duration = duration_cast(current - start); + auto timeout = keep_alive_timeout_sec * 1000; + if (duration.count() > timeout) { return false; } + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + } else { + return true; + } + } +} + +template +inline bool +process_server_socket_core(socket_t sock, size_t keep_alive_max_count, + time_t keep_alive_timeout_sec, T callback) { + assert(keep_alive_max_count > 0); + auto ret = false; + auto count = keep_alive_max_count; + while (count > 0 && keep_alive(sock, keep_alive_timeout_sec)) { + auto close_connection = count == 1; + auto connection_closed = false; + ret = callback(close_connection, connection_closed); + if (!ret || connection_closed) { break; } + count--; + } + return ret; +} + +template +inline bool +process_server_socket(socket_t sock, size_t keep_alive_max_count, + time_t keep_alive_timeout_sec, time_t read_timeout_sec, + time_t read_timeout_usec, time_t write_timeout_sec, + time_t write_timeout_usec, T callback) { + return process_server_socket_core( + sock, keep_alive_max_count, keep_alive_timeout_sec, + [&](bool close_connection, bool &connection_closed) { + SocketStream strm(sock, read_timeout_sec, read_timeout_usec, + write_timeout_sec, write_timeout_usec); + return callback(strm, close_connection, connection_closed); + }); +} + +template +inline bool process_client_socket(socket_t sock, time_t read_timeout_sec, + time_t read_timeout_usec, + time_t write_timeout_sec, + time_t write_timeout_usec, T callback) { + SocketStream strm(sock, read_timeout_sec, read_timeout_usec, + write_timeout_sec, write_timeout_usec); + return callback(strm); +} + +inline int shutdown_socket(socket_t sock) { +#ifdef _WIN32 + return shutdown(sock, SD_BOTH); +#else + return shutdown(sock, SHUT_RDWR); +#endif +} + +template +socket_t create_socket(const char *host, int port, int socket_flags, + bool tcp_nodelay, SocketOptions socket_options, + BindOrConnect bind_or_connect) { + // Get address info + struct addrinfo hints; + struct addrinfo *result; + + memset(&hints, 0, sizeof(struct addrinfo)); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + hints.ai_flags = socket_flags; + hints.ai_protocol = 0; + + auto service = std::to_string(port); + + if (getaddrinfo(host, service.c_str(), &hints, &result)) { +#ifdef __linux__ + res_init(); +#endif + return INVALID_SOCKET; + } + + for (auto rp = result; rp; rp = rp->ai_next) { + // Create a socket +#ifdef _WIN32 + auto sock = WSASocketW(rp->ai_family, rp->ai_socktype, rp->ai_protocol, + nullptr, 0, WSA_FLAG_NO_HANDLE_INHERIT); + /** + * Since the WSA_FLAG_NO_HANDLE_INHERIT is only supported on Windows 7 SP1 + * and above the socket creation fails on older Windows Systems. + * + * Let's try to create a socket the old way in this case. + * + * Reference: + * https://docs.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-wsasocketa + * + * WSA_FLAG_NO_HANDLE_INHERIT: + * This flag is supported on Windows 7 with SP1, Windows Server 2008 R2 with + * SP1, and later + * + */ + if (sock == INVALID_SOCKET) { + sock = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol); + } +#else + auto sock = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol); +#endif + if (sock == INVALID_SOCKET) { continue; } + +#ifndef _WIN32 + if (fcntl(sock, F_SETFD, FD_CLOEXEC) == -1) { continue; } +#endif + + if (tcp_nodelay) { + int yes = 1; + setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, reinterpret_cast(&yes), + sizeof(yes)); + } + + if (socket_options) { socket_options(sock); } + + if (rp->ai_family == AF_INET6) { + int no = 0; + setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY, reinterpret_cast(&no), + sizeof(no)); + } + + // bind or connect + if (bind_or_connect(sock, *rp)) { + freeaddrinfo(result); + return sock; + } + + close_socket(sock); + } + + freeaddrinfo(result); + return INVALID_SOCKET; +} + +inline void set_nonblocking(socket_t sock, bool nonblocking) { +#ifdef _WIN32 + auto flags = nonblocking ? 1UL : 0UL; + ioctlsocket(sock, FIONBIO, &flags); +#else + auto flags = fcntl(sock, F_GETFL, 0); + fcntl(sock, F_SETFL, + nonblocking ? (flags | O_NONBLOCK) : (flags & (~O_NONBLOCK))); +#endif +} + +inline bool is_connection_error() { +#ifdef _WIN32 + return WSAGetLastError() != WSAEWOULDBLOCK; +#else + return errno != EINPROGRESS; +#endif +} + +inline bool bind_ip_address(socket_t sock, const char *host) { + struct addrinfo hints; + struct addrinfo *result; + + memset(&hints, 0, sizeof(struct addrinfo)); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + hints.ai_protocol = 0; + + if (getaddrinfo(host, "0", &hints, &result)) { return false; } + + auto ret = false; + for (auto rp = result; rp; rp = rp->ai_next) { + const auto &ai = *rp; + if (!::bind(sock, ai.ai_addr, static_cast(ai.ai_addrlen))) { + ret = true; + break; + } + } + + freeaddrinfo(result); + return ret; +} + +#if !defined _WIN32 && !defined ANDROID +#define USE_IF2IP +#endif + +#ifdef USE_IF2IP +inline std::string if2ip(const std::string &ifn) { + struct ifaddrs *ifap; + getifaddrs(&ifap); + for (auto ifa = ifap; ifa; ifa = ifa->ifa_next) { + if (ifa->ifa_addr && ifn == ifa->ifa_name) { + if (ifa->ifa_addr->sa_family == AF_INET) { + auto sa = reinterpret_cast(ifa->ifa_addr); + char buf[INET_ADDRSTRLEN]; + if (inet_ntop(AF_INET, &sa->sin_addr, buf, INET_ADDRSTRLEN)) { + freeifaddrs(ifap); + return std::string(buf, INET_ADDRSTRLEN); + } + } + } + } + freeifaddrs(ifap); + return std::string(); +} +#endif + +inline socket_t create_client_socket(const char *host, int port, + bool tcp_nodelay, + SocketOptions socket_options, + time_t timeout_sec, time_t timeout_usec, + const std::string &intf, Error &error) { + auto sock = create_socket( + host, port, 0, tcp_nodelay, std::move(socket_options), + [&](socket_t sock, struct addrinfo &ai) -> bool { + if (!intf.empty()) { +#ifdef USE_IF2IP + auto ip = if2ip(intf); + if (ip.empty()) { ip = intf; } + if (!bind_ip_address(sock, ip.c_str())) { + error = Error::BindIPAddress; + return false; + } +#endif + } + + set_nonblocking(sock, true); + + auto ret = + ::connect(sock, ai.ai_addr, static_cast(ai.ai_addrlen)); + + if (ret < 0) { + if (is_connection_error() || + !wait_until_socket_is_ready(sock, timeout_sec, timeout_usec)) { + close_socket(sock); + error = Error::Connection; + return false; + } + } + + set_nonblocking(sock, false); + error = Error::Success; + return true; + }); + + if (sock != INVALID_SOCKET) { + error = Error::Success; + } else { + if (error == Error::Success) { error = Error::Connection; } + } + + return sock; +} + +inline void get_remote_ip_and_port(const struct sockaddr_storage &addr, + socklen_t addr_len, std::string &ip, + int &port) { + if (addr.ss_family == AF_INET) { + port = ntohs(reinterpret_cast(&addr)->sin_port); + } else if (addr.ss_family == AF_INET6) { + port = + ntohs(reinterpret_cast(&addr)->sin6_port); + } + + std::array ipstr{}; + if (!getnameinfo(reinterpret_cast(&addr), addr_len, + ipstr.data(), static_cast(ipstr.size()), nullptr, + 0, NI_NUMERICHOST)) { + ip = ipstr.data(); + } +} + +inline void get_remote_ip_and_port(socket_t sock, std::string &ip, int &port) { + struct sockaddr_storage addr; + socklen_t addr_len = sizeof(addr); + + if (!getpeername(sock, reinterpret_cast(&addr), + &addr_len)) { + get_remote_ip_and_port(addr, addr_len, ip, port); + } +} + +inline constexpr unsigned int str2tag_core(const char *s, size_t l, + unsigned int h) { + return (l == 0) ? h + : str2tag_core(s + 1, l - 1, + (h * 33) ^ static_cast(*s)); +} + +inline unsigned int str2tag(const std::string &s) { + return str2tag_core(s.data(), s.size(), 0); +} + +namespace udl { + +inline constexpr unsigned int operator"" _(const char *s, size_t l) { + return str2tag_core(s, l, 0); +} + +} // namespace udl + +inline const char * +find_content_type(const std::string &path, + const std::map &user_data) { + auto ext = file_extension(path); + + auto it = user_data.find(ext); + if (it != user_data.end()) { return it->second.c_str(); } + + using udl::operator""_; + + switch (str2tag(ext)) { + default: return nullptr; + case "css"_: return "text/css"; + case "csv"_: return "text/csv"; + case "txt"_: return "text/plain"; + case "vtt"_: return "text/vtt"; + case "htm"_: + case "html"_: return "text/html"; + + case "apng"_: return "image/apng"; + case "avif"_: return "image/avif"; + case "bmp"_: return "image/bmp"; + case "gif"_: return "image/gif"; + case "png"_: return "image/png"; + case "svg"_: return "image/svg+xml"; + case "webp"_: return "image/webp"; + case "ico"_: return "image/x-icon"; + case "tif"_: return "image/tiff"; + case "tiff"_: return "image/tiff"; + case "jpg"_: + case "jpeg"_: return "image/jpeg"; + + case "mp4"_: return "video/mp4"; + case "mpeg"_: return "video/mpeg"; + case "webm"_: return "video/webm"; + + case "mp3"_: return "audio/mp3"; + case "mpga"_: return "audio/mpeg"; + case "weba"_: return "audio/webm"; + case "wav"_: return "audio/wave"; + + case "otf"_: return "font/otf"; + case "ttf"_: return "font/ttf"; + case "woff"_: return "font/woff"; + case "woff2"_: return "font/woff2"; + + case "7z"_: return "application/x-7z-compressed"; + case "atom"_: return "application/atom+xml"; + case "pdf"_: return "application/pdf"; + case "js"_: + case "mjs"_: return "application/javascript"; + case "json"_: return "application/json"; + case "rss"_: return "application/rss+xml"; + case "tar"_: return "application/x-tar"; + case "xht"_: + case "xhtml"_: return "application/xhtml+xml"; + case "xslt"_: return "application/xslt+xml"; + case "xml"_: return "application/xml"; + case "gz"_: return "application/gzip"; + case "zip"_: return "application/zip"; + case "wasm"_: return "application/wasm"; + } +} + +inline const char *status_message(int status) { + switch (status) { + case 100: return "Continue"; + case 101: return "Switching Protocol"; + case 102: return "Processing"; + case 103: return "Early Hints"; + case 200: return "OK"; + case 201: return "Created"; + case 202: return "Accepted"; + case 203: return "Non-Authoritative Information"; + case 204: return "No Content"; + case 205: return "Reset Content"; + case 206: return "Partial Content"; + case 207: return "Multi-Status"; + case 208: return "Already Reported"; + case 226: return "IM Used"; + case 300: return "Multiple Choice"; + case 301: return "Moved Permanently"; + case 302: return "Found"; + case 303: return "See Other"; + case 304: return "Not Modified"; + case 305: return "Use Proxy"; + case 306: return "unused"; + case 307: return "Temporary Redirect"; + case 308: return "Permanent Redirect"; + case 400: return "Bad Request"; + case 401: return "Unauthorized"; + case 402: return "Payment Required"; + case 403: return "Forbidden"; + case 404: return "Not Found"; + case 405: return "Method Not Allowed"; + case 406: return "Not Acceptable"; + case 407: return "Proxy Authentication Required"; + case 408: return "Request Timeout"; + case 409: return "Conflict"; + case 410: return "Gone"; + case 411: return "Length Required"; + case 412: return "Precondition Failed"; + case 413: return "Payload Too Large"; + case 414: return "URI Too Long"; + case 415: return "Unsupported Media Type"; + case 416: return "Range Not Satisfiable"; + case 417: return "Expectation Failed"; + case 418: return "I'm a teapot"; + case 421: return "Misdirected Request"; + case 422: return "Unprocessable Entity"; + case 423: return "Locked"; + case 424: return "Failed Dependency"; + case 425: return "Too Early"; + case 426: return "Upgrade Required"; + case 428: return "Precondition Required"; + case 429: return "Too Many Requests"; + case 431: return "Request Header Fields Too Large"; + case 451: return "Unavailable For Legal Reasons"; + case 501: return "Not Implemented"; + case 502: return "Bad Gateway"; + case 503: return "Service Unavailable"; + case 504: return "Gateway Timeout"; + case 505: return "HTTP Version Not Supported"; + case 506: return "Variant Also Negotiates"; + case 507: return "Insufficient Storage"; + case 508: return "Loop Detected"; + case 510: return "Not Extended"; + case 511: return "Network Authentication Required"; + + default: + case 500: return "Internal Server Error"; + } +} + +inline bool can_compress_content_type(const std::string &content_type) { + return (!content_type.find("text/") && content_type != "text/event-stream") || + content_type == "image/svg+xml" || + content_type == "application/javascript" || + content_type == "application/json" || + content_type == "application/xml" || + content_type == "application/xhtml+xml"; +} + +enum class EncodingType { None = 0, Gzip, Brotli }; + +inline EncodingType encoding_type(const Request &req, const Response &res) { + auto ret = + detail::can_compress_content_type(res.get_header_value("Content-Type")); + if (!ret) { return EncodingType::None; } + + const auto &s = req.get_header_value("Accept-Encoding"); + (void)(s); + +#ifdef CPPHTTPLIB_BROTLI_SUPPORT + // TODO: 'Accept-Encoding' has br, not br;q=0 + ret = s.find("br") != std::string::npos; + if (ret) { return EncodingType::Brotli; } +#endif + +#ifdef CPPHTTPLIB_ZLIB_SUPPORT + // TODO: 'Accept-Encoding' has gzip, not gzip;q=0 + ret = s.find("gzip") != std::string::npos; + if (ret) { return EncodingType::Gzip; } +#endif + + return EncodingType::None; +} + +class compressor { +public: + virtual ~compressor(){}; + + typedef std::function Callback; + virtual bool compress(const char *data, size_t data_length, bool last, + Callback callback) = 0; +}; + +class decompressor { +public: + virtual ~decompressor() {} + + virtual bool is_valid() const = 0; + + typedef std::function Callback; + virtual bool decompress(const char *data, size_t data_length, + Callback callback) = 0; +}; + +class nocompressor : public compressor { +public: + ~nocompressor(){}; + + bool compress(const char *data, size_t data_length, bool /*last*/, + Callback callback) override { + if (!data_length) { return true; } + return callback(data, data_length); + } +}; + +#ifdef CPPHTTPLIB_ZLIB_SUPPORT +class gzip_compressor : public compressor { +public: + gzip_compressor() { + std::memset(&strm_, 0, sizeof(strm_)); + strm_.zalloc = Z_NULL; + strm_.zfree = Z_NULL; + strm_.opaque = Z_NULL; + + is_valid_ = deflateInit2(&strm_, Z_DEFAULT_COMPRESSION, Z_DEFLATED, 31, 8, + Z_DEFAULT_STRATEGY) == Z_OK; + } + + ~gzip_compressor() { deflateEnd(&strm_); } + + bool compress(const char *data, size_t data_length, bool last, + Callback callback) override { + assert(is_valid_); + + auto flush = last ? Z_FINISH : Z_NO_FLUSH; + + strm_.avail_in = static_cast(data_length); + strm_.next_in = const_cast(reinterpret_cast(data)); + + int ret = Z_OK; + + std::array buff{}; + do { + strm_.avail_out = buff.size(); + strm_.next_out = reinterpret_cast(buff.data()); + + ret = deflate(&strm_, flush); + if (ret == Z_STREAM_ERROR) { return false; } + + if (!callback(buff.data(), buff.size() - strm_.avail_out)) { + return false; + } + } while (strm_.avail_out == 0); + + assert((last && ret == Z_STREAM_END) || (!last && ret == Z_OK)); + assert(strm_.avail_in == 0); + return true; + } + +private: + bool is_valid_ = false; + z_stream strm_; +}; + +class gzip_decompressor : public decompressor { +public: + gzip_decompressor() { + std::memset(&strm_, 0, sizeof(strm_)); + strm_.zalloc = Z_NULL; + strm_.zfree = Z_NULL; + strm_.opaque = Z_NULL; + + // 15 is the value of wbits, which should be at the maximum possible value + // to ensure that any gzip stream can be decoded. The offset of 32 specifies + // that the stream type should be automatically detected either gzip or + // deflate. + is_valid_ = inflateInit2(&strm_, 32 + 15) == Z_OK; + } + + ~gzip_decompressor() { inflateEnd(&strm_); } + + bool is_valid() const override { return is_valid_; } + + bool decompress(const char *data, size_t data_length, + Callback callback) override { + assert(is_valid_); + + int ret = Z_OK; + + strm_.avail_in = static_cast(data_length); + strm_.next_in = const_cast(reinterpret_cast(data)); + + std::array buff{}; + while (strm_.avail_in > 0) { + strm_.avail_out = buff.size(); + strm_.next_out = reinterpret_cast(buff.data()); + + ret = inflate(&strm_, Z_NO_FLUSH); + assert(ret != Z_STREAM_ERROR); + switch (ret) { + case Z_NEED_DICT: + case Z_DATA_ERROR: + case Z_MEM_ERROR: inflateEnd(&strm_); return false; + } + + if (!callback(buff.data(), buff.size() - strm_.avail_out)) { + return false; + } + } + + return ret == Z_OK || ret == Z_STREAM_END; + } + +private: + bool is_valid_ = false; + z_stream strm_; +}; +#endif + +#ifdef CPPHTTPLIB_BROTLI_SUPPORT +class brotli_compressor : public compressor { +public: + brotli_compressor() { + state_ = BrotliEncoderCreateInstance(nullptr, nullptr, nullptr); + } + + ~brotli_compressor() { BrotliEncoderDestroyInstance(state_); } + + bool compress(const char *data, size_t data_length, bool last, + Callback callback) override { + std::array buff{}; + + auto operation = last ? BROTLI_OPERATION_FINISH : BROTLI_OPERATION_PROCESS; + auto available_in = data_length; + auto next_in = reinterpret_cast(data); + + for (;;) { + if (last) { + if (BrotliEncoderIsFinished(state_)) { break; } + } else { + if (!available_in) { break; } + } + + auto available_out = buff.size(); + auto next_out = buff.data(); + + if (!BrotliEncoderCompressStream(state_, operation, &available_in, + &next_in, &available_out, &next_out, + nullptr)) { + return false; + } + + auto output_bytes = buff.size() - available_out; + if (output_bytes) { + callback(reinterpret_cast(buff.data()), output_bytes); + } + } + + return true; + } + +private: + BrotliEncoderState *state_ = nullptr; +}; + +class brotli_decompressor : public decompressor { +public: + brotli_decompressor() { + decoder_s = BrotliDecoderCreateInstance(0, 0, 0); + decoder_r = decoder_s ? BROTLI_DECODER_RESULT_NEEDS_MORE_INPUT + : BROTLI_DECODER_RESULT_ERROR; + } + + ~brotli_decompressor() { + if (decoder_s) { BrotliDecoderDestroyInstance(decoder_s); } + } + + bool is_valid() const override { return decoder_s; } + + bool decompress(const char *data, size_t data_length, + Callback callback) override { + if (decoder_r == BROTLI_DECODER_RESULT_SUCCESS || + decoder_r == BROTLI_DECODER_RESULT_ERROR) { + return 0; + } + + const uint8_t *next_in = (const uint8_t *)data; + size_t avail_in = data_length; + size_t total_out; + + decoder_r = BROTLI_DECODER_RESULT_NEEDS_MORE_OUTPUT; + + std::array buff{}; + while (decoder_r == BROTLI_DECODER_RESULT_NEEDS_MORE_OUTPUT) { + char *next_out = buff.data(); + size_t avail_out = buff.size(); + + decoder_r = BrotliDecoderDecompressStream( + decoder_s, &avail_in, &next_in, &avail_out, + reinterpret_cast(&next_out), &total_out); + + if (decoder_r == BROTLI_DECODER_RESULT_ERROR) { return false; } + + if (!callback(buff.data(), buff.size() - avail_out)) { return false; } + } + + return decoder_r == BROTLI_DECODER_RESULT_SUCCESS || + decoder_r == BROTLI_DECODER_RESULT_NEEDS_MORE_INPUT; + } + +private: + BrotliDecoderResult decoder_r; + BrotliDecoderState *decoder_s = nullptr; +}; +#endif + +inline bool has_header(const Headers &headers, const char *key) { + return headers.find(key) != headers.end(); +} + +inline const char *get_header_value(const Headers &headers, const char *key, + size_t id = 0, const char *def = nullptr) { + auto rng = headers.equal_range(key); + auto it = rng.first; + std::advance(it, static_cast(id)); + if (it != rng.second) { return it->second.c_str(); } + return def; +} + +template +inline T get_header_value(const Headers & /*headers*/, const char * /*key*/, + size_t /*id*/ = 0, uint64_t /*def*/ = 0) {} + +template <> +inline uint64_t get_header_value(const Headers &headers, + const char *key, size_t id, + uint64_t def) { + auto rng = headers.equal_range(key); + auto it = rng.first; + std::advance(it, static_cast(id)); + if (it != rng.second) { + return std::strtoull(it->second.data(), nullptr, 10); + } + return def; +} + +template +inline bool parse_header(const char *beg, const char *end, T fn) { + // Skip trailing spaces and tabs. + while (beg < end && is_space_or_tab(end[-1])) { + end--; + } + + auto p = beg; + while (p < end && *p != ':') { + p++; + } + + if (p == end) { return false; } + + auto key_end = p; + + if (*p++ != ':') { return false; } + + while (p < end && is_space_or_tab(*p)) { + p++; + } + + if (p < end) { + fn(std::string(beg, key_end), decode_url(std::string(p, end), false)); + return true; + } + + return false; +} + +inline bool read_headers(Stream &strm, Headers &headers) { + const auto bufsiz = 2048; + char buf[bufsiz]; + stream_line_reader line_reader(strm, buf, bufsiz); + + for (;;) { + if (!line_reader.getline()) { return false; } + + // Check if the line ends with CRLF. + if (line_reader.end_with_crlf()) { + // Blank line indicates end of headers. + if (line_reader.size() == 2) { break; } + } else { + continue; // Skip invalid line. + } + + // Exclude CRLF + auto end = line_reader.ptr() + line_reader.size() - 2; + + parse_header(line_reader.ptr(), end, + [&](std::string &&key, std::string &&val) { + headers.emplace(std::move(key), std::move(val)); + }); + } + + return true; +} + +inline bool read_content_with_length(Stream &strm, uint64_t len, + Progress progress, + ContentReceiverWithProgress out) { + char buf[CPPHTTPLIB_RECV_BUFSIZ]; + + uint64_t r = 0; + while (r < len) { + auto read_len = static_cast(len - r); + auto n = strm.read(buf, (std::min)(read_len, CPPHTTPLIB_RECV_BUFSIZ)); + if (n <= 0) { return false; } + + if (!out(buf, static_cast(n), r, len)) { return false; } + r += static_cast(n); + + if (progress) { + if (!progress(r, len)) { return false; } + } + } + + return true; +} + +inline void skip_content_with_length(Stream &strm, uint64_t len) { + char buf[CPPHTTPLIB_RECV_BUFSIZ]; + uint64_t r = 0; + while (r < len) { + auto read_len = static_cast(len - r); + auto n = strm.read(buf, (std::min)(read_len, CPPHTTPLIB_RECV_BUFSIZ)); + if (n <= 0) { return; } + r += static_cast(n); + } +} + +inline bool read_content_without_length(Stream &strm, + ContentReceiverWithProgress out) { + char buf[CPPHTTPLIB_RECV_BUFSIZ]; + uint64_t r = 0; + for (;;) { + auto n = strm.read(buf, CPPHTTPLIB_RECV_BUFSIZ); + if (n < 0) { + return false; + } else if (n == 0) { + return true; + } + + if (!out(buf, static_cast(n), r, 0)) { return false; } + r += static_cast(n); + } + + return true; +} + +inline bool read_content_chunked(Stream &strm, + ContentReceiverWithProgress out) { + const auto bufsiz = 16; + char buf[bufsiz]; + + stream_line_reader line_reader(strm, buf, bufsiz); + + if (!line_reader.getline()) { return false; } + + unsigned long chunk_len; + while (true) { + char *end_ptr; + + chunk_len = std::strtoul(line_reader.ptr(), &end_ptr, 16); + + if (end_ptr == line_reader.ptr()) { return false; } + if (chunk_len == ULONG_MAX) { return false; } + + if (chunk_len == 0) { break; } + + if (!read_content_with_length(strm, chunk_len, nullptr, out)) { + return false; + } + + if (!line_reader.getline()) { return false; } + + if (strcmp(line_reader.ptr(), "\r\n")) { break; } + + if (!line_reader.getline()) { return false; } + } + + if (chunk_len == 0) { + // Reader terminator after chunks + if (!line_reader.getline() || strcmp(line_reader.ptr(), "\r\n")) + return false; + } + + return true; +} + +inline bool is_chunked_transfer_encoding(const Headers &headers) { + return !strcasecmp(get_header_value(headers, "Transfer-Encoding", 0, ""), + "chunked"); +} + +template +bool prepare_content_receiver(T &x, int &status, + ContentReceiverWithProgress receiver, + bool decompress, U callback) { + if (decompress) { + std::string encoding = x.get_header_value("Content-Encoding"); + std::unique_ptr decompressor; + + if (encoding.find("gzip") != std::string::npos || + encoding.find("deflate") != std::string::npos) { +#ifdef CPPHTTPLIB_ZLIB_SUPPORT + decompressor = detail::make_unique(); +#else + status = 415; + return false; +#endif + } else if (encoding.find("br") != std::string::npos) { +#ifdef CPPHTTPLIB_BROTLI_SUPPORT + decompressor = detail::make_unique(); +#else + status = 415; + return false; +#endif + } + + if (decompressor) { + if (decompressor->is_valid()) { + ContentReceiverWithProgress out = [&](const char *buf, size_t n, + uint64_t off, uint64_t len) { + return decompressor->decompress(buf, n, + [&](const char *buf, size_t n) { + return receiver(buf, n, off, len); + }); + }; + return callback(std::move(out)); + } else { + status = 500; + return false; + } + } + } + + ContentReceiverWithProgress out = [&](const char *buf, size_t n, uint64_t off, + uint64_t len) { + return receiver(buf, n, off, len); + }; + return callback(std::move(out)); +} + +template +bool read_content(Stream &strm, T &x, size_t payload_max_length, int &status, + Progress progress, ContentReceiverWithProgress receiver, + bool decompress) { + return prepare_content_receiver( + x, status, std::move(receiver), decompress, + [&](const ContentReceiverWithProgress &out) { + auto ret = true; + auto exceed_payload_max_length = false; + + if (is_chunked_transfer_encoding(x.headers)) { + ret = read_content_chunked(strm, out); + } else if (!has_header(x.headers, "Content-Length")) { + ret = read_content_without_length(strm, out); + } else { + auto len = get_header_value(x.headers, "Content-Length"); + if (len > payload_max_length) { + exceed_payload_max_length = true; + skip_content_with_length(strm, len); + ret = false; + } else if (len > 0) { + ret = read_content_with_length(strm, len, std::move(progress), out); + } + } + + if (!ret) { status = exceed_payload_max_length ? 413 : 400; } + return ret; + }); +} + +inline ssize_t write_headers(Stream &strm, const Headers &headers) { + ssize_t write_len = 0; + for (const auto &x : headers) { + auto len = + strm.write_format("%s: %s\r\n", x.first.c_str(), x.second.c_str()); + if (len < 0) { return len; } + write_len += len; + } + auto len = strm.write("\r\n"); + if (len < 0) { return len; } + write_len += len; + return write_len; +} + +inline bool write_data(Stream &strm, const char *d, size_t l) { + size_t offset = 0; + while (offset < l) { + auto length = strm.write(d + offset, l - offset); + if (length < 0) { return false; } + offset += static_cast(length); + } + return true; +} + +template +inline bool write_content(Stream &strm, const ContentProvider &content_provider, + size_t offset, size_t length, T is_shutting_down, + Error &error) { + size_t end_offset = offset + length; + auto ok = true; + DataSink data_sink; + + data_sink.write = [&](const char *d, size_t l) { + if (ok) { + if (write_data(strm, d, l)) { + offset += l; + } else { + ok = false; + } + } + }; + + data_sink.is_writable = [&](void) { return ok && strm.is_writable(); }; + + while (offset < end_offset && !is_shutting_down()) { + if (!content_provider(offset, end_offset - offset, data_sink)) { + error = Error::Canceled; + return false; + } + if (!ok) { + error = Error::Write; + return false; + } + } + + error = Error::Success; + return true; +} + +template +inline bool write_content(Stream &strm, const ContentProvider &content_provider, + size_t offset, size_t length, + const T &is_shutting_down) { + auto error = Error::Success; + return write_content(strm, content_provider, offset, length, is_shutting_down, + error); +} + +template +inline bool +write_content_without_length(Stream &strm, + const ContentProvider &content_provider, + const T &is_shutting_down) { + size_t offset = 0; + auto data_available = true; + auto ok = true; + DataSink data_sink; + + data_sink.write = [&](const char *d, size_t l) { + if (ok) { + offset += l; + if (!write_data(strm, d, l)) { ok = false; } + } + }; + + data_sink.done = [&](void) { data_available = false; }; + + data_sink.is_writable = [&](void) { return ok && strm.is_writable(); }; + + while (data_available && !is_shutting_down()) { + if (!content_provider(offset, 0, data_sink)) { return false; } + if (!ok) { return false; } + } + return true; +} + +template +inline bool +write_content_chunked(Stream &strm, const ContentProvider &content_provider, + const T &is_shutting_down, U &compressor, Error &error) { + size_t offset = 0; + auto data_available = true; + auto ok = true; + DataSink data_sink; + + data_sink.write = [&](const char *d, size_t l) { + if (!ok) { return; } + + data_available = l > 0; + offset += l; + + std::string payload; + if (!compressor.compress(d, l, false, + [&](const char *data, size_t data_len) { + payload.append(data, data_len); + return true; + })) { + ok = false; + return; + } + + if (!payload.empty()) { + // Emit chunked response header and footer for each chunk + auto chunk = from_i_to_hex(payload.size()) + "\r\n" + payload + "\r\n"; + if (!write_data(strm, chunk.data(), chunk.size())) { + ok = false; + return; + } + } + }; + + data_sink.done = [&](void) { + if (!ok) { return; } + + data_available = false; + + std::string payload; + if (!compressor.compress(nullptr, 0, true, + [&](const char *data, size_t data_len) { + payload.append(data, data_len); + return true; + })) { + ok = false; + return; + } + + if (!payload.empty()) { + // Emit chunked response header and footer for each chunk + auto chunk = from_i_to_hex(payload.size()) + "\r\n" + payload + "\r\n"; + if (!write_data(strm, chunk.data(), chunk.size())) { + ok = false; + return; + } + } + + static const std::string done_marker("0\r\n\r\n"); + if (!write_data(strm, done_marker.data(), done_marker.size())) { + ok = false; + } + }; + + data_sink.is_writable = [&](void) { return ok && strm.is_writable(); }; + + while (data_available && !is_shutting_down()) { + if (!content_provider(offset, 0, data_sink)) { + error = Error::Canceled; + return false; + } + if (!ok) { + error = Error::Write; + return false; + } + } + + error = Error::Success; + return true; +} + +template +inline bool write_content_chunked(Stream &strm, + const ContentProvider &content_provider, + const T &is_shutting_down, U &compressor) { + auto error = Error::Success; + return write_content_chunked(strm, content_provider, is_shutting_down, + compressor, error); +} + +template +inline bool redirect(T &cli, Request &req, Response &res, + const std::string &path, const std::string &location, + Error &error) { + Request new_req = req; + new_req.path = path; + new_req.redirect_count_ -= 1; + + if (res.status == 303 && (req.method != "GET" && req.method != "HEAD")) { + new_req.method = "GET"; + new_req.body.clear(); + new_req.headers.clear(); + } + + Response new_res; + + auto ret = cli.send(new_req, new_res, error); + if (ret) { + req = new_req; + res = new_res; + res.location = location; + } + return ret; +} + +inline std::string params_to_query_str(const Params ¶ms) { + std::string query; + + for (auto it = params.begin(); it != params.end(); ++it) { + if (it != params.begin()) { query += "&"; } + query += it->first; + query += "="; + query += encode_query_param(it->second); + } + return query; +} + +inline std::string append_query_params(const char *path, const Params ¶ms) { + std::string path_with_query = path; + const static std::regex re("[^?]+\\?.*"); + auto delm = std::regex_match(path, re) ? '&' : '?'; + path_with_query += delm + params_to_query_str(params); + return path_with_query; +} + +inline void parse_query_text(const std::string &s, Params ¶ms) { + split(s.data(), s.data() + s.size(), '&', [&](const char *b, const char *e) { + std::string key; + std::string val; + split(b, e, '=', [&](const char *b2, const char *e2) { + if (key.empty()) { + key.assign(b2, e2); + } else { + val.assign(b2, e2); + } + }); + + if (!key.empty()) { + params.emplace(decode_url(key, true), decode_url(val, true)); + } + }); +} + +inline bool parse_multipart_boundary(const std::string &content_type, + std::string &boundary) { + auto pos = content_type.find("boundary="); + if (pos == std::string::npos) { return false; } + boundary = content_type.substr(pos + 9); + if (boundary.length() >= 2 && boundary.front() == '"' && + boundary.back() == '"') { + boundary = boundary.substr(1, boundary.size() - 2); + } + return !boundary.empty(); +} + +inline bool parse_range_header(const std::string &s, Ranges &ranges) try { + static auto re_first_range = std::regex(R"(bytes=(\d*-\d*(?:,\s*\d*-\d*)*))"); + std::smatch m; + if (std::regex_match(s, m, re_first_range)) { + auto pos = static_cast(m.position(1)); + auto len = static_cast(m.length(1)); + bool all_valid_ranges = true; + split(&s[pos], &s[pos + len], ',', [&](const char *b, const char *e) { + if (!all_valid_ranges) return; + static auto re_another_range = std::regex(R"(\s*(\d*)-(\d*))"); + std::cmatch cm; + if (std::regex_match(b, e, cm, re_another_range)) { + ssize_t first = -1; + if (!cm.str(1).empty()) { + first = static_cast(std::stoll(cm.str(1))); + } + + ssize_t last = -1; + if (!cm.str(2).empty()) { + last = static_cast(std::stoll(cm.str(2))); + } + + if (first != -1 && last != -1 && first > last) { + all_valid_ranges = false; + return; + } + ranges.emplace_back(std::make_pair(first, last)); + } + }); + return all_valid_ranges; + } + return false; +} catch (...) { return false; } + +class MultipartFormDataParser { +public: + MultipartFormDataParser() = default; + + void set_boundary(std::string &&boundary) { boundary_ = boundary; } + + bool is_valid() const { return is_valid_; } + + bool parse(const char *buf, size_t n, const ContentReceiver &content_callback, + const MultipartContentHeader &header_callback) { + + static const std::regex re_content_disposition( + "^Content-Disposition:\\s*form-data;\\s*name=\"(.*?)\"(?:;\\s*filename=" + "\"(.*?)\")?\\s*$", + std::regex_constants::icase); + static const std::string dash_ = "--"; + static const std::string crlf_ = "\r\n"; + + buf_.append(buf, n); // TODO: performance improvement + + while (!buf_.empty()) { + switch (state_) { + case 0: { // Initial boundary + auto pattern = dash_ + boundary_ + crlf_; + if (pattern.size() > buf_.size()) { return true; } + auto pos = buf_.find(pattern); + if (pos != 0) { return false; } + buf_.erase(0, pattern.size()); + off_ += pattern.size(); + state_ = 1; + break; + } + case 1: { // New entry + clear_file_info(); + state_ = 2; + break; + } + case 2: { // Headers + auto pos = buf_.find(crlf_); + while (pos != std::string::npos) { + // Empty line + if (pos == 0) { + if (!header_callback(file_)) { + is_valid_ = false; + return false; + } + buf_.erase(0, crlf_.size()); + off_ += crlf_.size(); + state_ = 3; + break; + } + + static const std::string header_name = "content-type:"; + const auto header = buf_.substr(0, pos); + if (start_with_case_ignore(header, header_name)) { + file_.content_type = trim_copy(header.substr(header_name.size())); + } else { + std::smatch m; + if (std::regex_match(header, m, re_content_disposition)) { + file_.name = m[1]; + file_.filename = m[2]; + } + } + + buf_.erase(0, pos + crlf_.size()); + off_ += pos + crlf_.size(); + pos = buf_.find(crlf_); + } + if (state_ != 3) { return true; } + break; + } + case 3: { // Body + { + auto pattern = crlf_ + dash_; + if (pattern.size() > buf_.size()) { return true; } + + auto pos = find_string(buf_, pattern); + + if (!content_callback(buf_.data(), pos)) { + is_valid_ = false; + return false; + } + + off_ += pos; + buf_.erase(0, pos); + } + { + auto pattern = crlf_ + dash_ + boundary_; + if (pattern.size() > buf_.size()) { return true; } + + auto pos = buf_.find(pattern); + if (pos != std::string::npos) { + if (!content_callback(buf_.data(), pos)) { + is_valid_ = false; + return false; + } + + off_ += pos + pattern.size(); + buf_.erase(0, pos + pattern.size()); + state_ = 4; + } else { + if (!content_callback(buf_.data(), pattern.size())) { + is_valid_ = false; + return false; + } + + off_ += pattern.size(); + buf_.erase(0, pattern.size()); + } + } + break; + } + case 4: { // Boundary + if (crlf_.size() > buf_.size()) { return true; } + if (buf_.compare(0, crlf_.size(), crlf_) == 0) { + buf_.erase(0, crlf_.size()); + off_ += crlf_.size(); + state_ = 1; + } else { + auto pattern = dash_ + crlf_; + if (pattern.size() > buf_.size()) { return true; } + if (buf_.compare(0, pattern.size(), pattern) == 0) { + buf_.erase(0, pattern.size()); + off_ += pattern.size(); + is_valid_ = true; + state_ = 5; + } else { + return true; + } + } + break; + } + case 5: { // Done + is_valid_ = false; + return false; + } + } + } + + return true; + } + +private: + void clear_file_info() { + file_.name.clear(); + file_.filename.clear(); + file_.content_type.clear(); + } + + bool start_with_case_ignore(const std::string &a, + const std::string &b) const { + if (a.size() < b.size()) { return false; } + for (size_t i = 0; i < b.size(); i++) { + if (::tolower(a[i]) != ::tolower(b[i])) { return false; } + } + return true; + } + + bool start_with(const std::string &a, size_t off, + const std::string &b) const { + if (a.size() - off < b.size()) { return false; } + for (size_t i = 0; i < b.size(); i++) { + if (a[i + off] != b[i]) { return false; } + } + return true; + } + + size_t find_string(const std::string &s, const std::string &pattern) const { + auto c = pattern.front(); + + size_t off = 0; + while (off < s.size()) { + auto pos = s.find(c, off); + if (pos == std::string::npos) { return s.size(); } + + auto rem = s.size() - pos; + if (pattern.size() > rem) { return pos; } + + if (start_with(s, pos, pattern)) { return pos; } + + off = pos + 1; + } + + return s.size(); + } + + std::string boundary_; + + std::string buf_; + size_t state_ = 0; + bool is_valid_ = false; + size_t off_ = 0; + MultipartFormData file_; +}; + +inline std::string to_lower(const char *beg, const char *end) { + std::string out; + auto it = beg; + while (it != end) { + out += static_cast(::tolower(*it)); + it++; + } + return out; +} + +inline std::string make_multipart_data_boundary() { + static const char data[] = + "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; + + // std::random_device might actually be deterministic on some + // platforms, but due to lack of support in the c++ standard library, + // doing better requires either some ugly hacks or breaking portability. + std::random_device seed_gen; + // Request 128 bits of entropy for initialization + std::seed_seq seed_sequence{seed_gen(), seed_gen(), seed_gen(), seed_gen()}; + std::mt19937 engine(seed_sequence); + + std::string result = "--cpp-httplib-multipart-data-"; + + for (auto i = 0; i < 16; i++) { + result += data[engine() % (sizeof(data) - 1)]; + } + + return result; +} + +inline std::pair +get_range_offset_and_length(const Request &req, size_t content_length, + size_t index) { + auto r = req.ranges[index]; + + if (r.first == -1 && r.second == -1) { + return std::make_pair(0, content_length); + } + + auto slen = static_cast(content_length); + + if (r.first == -1) { + r.first = (std::max)(static_cast(0), slen - r.second); + r.second = slen - 1; + } + + if (r.second == -1) { r.second = slen - 1; } + return std::make_pair(r.first, static_cast(r.second - r.first) + 1); +} + +inline std::string make_content_range_header_field(size_t offset, size_t length, + size_t content_length) { + std::string field = "bytes "; + field += std::to_string(offset); + field += "-"; + field += std::to_string(offset + length - 1); + field += "/"; + field += std::to_string(content_length); + return field; +} + +template +bool process_multipart_ranges_data(const Request &req, Response &res, + const std::string &boundary, + const std::string &content_type, + SToken stoken, CToken ctoken, + Content content) { + for (size_t i = 0; i < req.ranges.size(); i++) { + ctoken("--"); + stoken(boundary); + ctoken("\r\n"); + if (!content_type.empty()) { + ctoken("Content-Type: "); + stoken(content_type); + ctoken("\r\n"); + } + + auto offsets = get_range_offset_and_length(req, res.body.size(), i); + auto offset = offsets.first; + auto length = offsets.second; + + ctoken("Content-Range: "); + stoken(make_content_range_header_field(offset, length, res.body.size())); + ctoken("\r\n"); + ctoken("\r\n"); + if (!content(offset, length)) { return false; } + ctoken("\r\n"); + } + + ctoken("--"); + stoken(boundary); + ctoken("--\r\n"); + + return true; +} + +inline bool make_multipart_ranges_data(const Request &req, Response &res, + const std::string &boundary, + const std::string &content_type, + std::string &data) { + return process_multipart_ranges_data( + req, res, boundary, content_type, + [&](const std::string &token) { data += token; }, + [&](const char *token) { data += token; }, + [&](size_t offset, size_t length) { + if (offset < res.body.size()) { + data += res.body.substr(offset, length); + return true; + } + return false; + }); +} + +inline size_t +get_multipart_ranges_data_length(const Request &req, Response &res, + const std::string &boundary, + const std::string &content_type) { + size_t data_length = 0; + + process_multipart_ranges_data( + req, res, boundary, content_type, + [&](const std::string &token) { data_length += token.size(); }, + [&](const char *token) { data_length += strlen(token); }, + [&](size_t /*offset*/, size_t length) { + data_length += length; + return true; + }); + + return data_length; +} + +template +inline bool write_multipart_ranges_data(Stream &strm, const Request &req, + Response &res, + const std::string &boundary, + const std::string &content_type, + const T &is_shutting_down) { + return process_multipart_ranges_data( + req, res, boundary, content_type, + [&](const std::string &token) { strm.write(token); }, + [&](const char *token) { strm.write(token); }, + [&](size_t offset, size_t length) { + return write_content(strm, res.content_provider_, offset, length, + is_shutting_down); + }); +} + +inline std::pair +get_range_offset_and_length(const Request &req, const Response &res, + size_t index) { + auto r = req.ranges[index]; + + if (r.second == -1) { + r.second = static_cast(res.content_length_) - 1; + } + + return std::make_pair(r.first, r.second - r.first + 1); +} + +inline bool expect_content(const Request &req) { + if (req.method == "POST" || req.method == "PUT" || req.method == "PATCH" || + req.method == "PRI" || req.method == "DELETE") { + return true; + } + // TODO: check if Content-Length is set + return false; +} + +inline bool has_crlf(const char *s) { + auto p = s; + while (*p) { + if (*p == '\r' || *p == '\n') { return true; } + p++; + } + return false; +} + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT +template +inline std::string message_digest(const std::string &s, Init init, + Update update, Final final, + size_t digest_length) { + using namespace std; + + std::vector md(digest_length, 0); + CTX ctx; + init(&ctx); + update(&ctx, s.data(), s.size()); + final(md.data(), &ctx); + + stringstream ss; + for (auto c : md) { + ss << setfill('0') << setw(2) << hex << (unsigned int)c; + } + return ss.str(); +} + +inline std::string MD5(const std::string &s) { + return message_digest(s, MD5_Init, MD5_Update, MD5_Final, + MD5_DIGEST_LENGTH); +} + +inline std::string SHA_256(const std::string &s) { + return message_digest(s, SHA256_Init, SHA256_Update, SHA256_Final, + SHA256_DIGEST_LENGTH); +} + +inline std::string SHA_512(const std::string &s) { + return message_digest(s, SHA512_Init, SHA512_Update, SHA512_Final, + SHA512_DIGEST_LENGTH); +} +#endif + +#ifdef _WIN32 +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT +// NOTE: This code came up with the following stackoverflow post: +// https://stackoverflow.com/questions/9507184/can-openssl-on-windows-use-the-system-certificate-store +inline bool load_system_certs_on_windows(X509_STORE *store) { + auto hStore = CertOpenSystemStoreW((HCRYPTPROV_LEGACY)NULL, L"ROOT"); + + if (!hStore) { return false; } + + PCCERT_CONTEXT pContext = NULL; + while ((pContext = CertEnumCertificatesInStore(hStore, pContext)) != + nullptr) { + auto encoded_cert = + static_cast(pContext->pbCertEncoded); + + auto x509 = d2i_X509(NULL, &encoded_cert, pContext->cbCertEncoded); + if (x509) { + X509_STORE_add_cert(store, x509); + X509_free(x509); + } + } + + CertFreeCertificateContext(pContext); + CertCloseStore(hStore, 0); + + return true; +} +#endif + +class WSInit { +public: + WSInit() { + WSADATA wsaData; + WSAStartup(0x0002, &wsaData); + } + + ~WSInit() { WSACleanup(); } +}; + +static WSInit wsinit_; +#endif + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT +inline std::pair make_digest_authentication_header( + const Request &req, const std::map &auth, + size_t cnonce_count, const std::string &cnonce, const std::string &username, + const std::string &password, bool is_proxy = false) { + using namespace std; + + string nc; + { + stringstream ss; + ss << setfill('0') << setw(8) << hex << cnonce_count; + nc = ss.str(); + } + + auto qop = auth.at("qop"); + if (qop.find("auth-int") != std::string::npos) { + qop = "auth-int"; + } else { + qop = "auth"; + } + + std::string algo = "MD5"; + if (auth.find("algorithm") != auth.end()) { algo = auth.at("algorithm"); } + + string response; + { + auto H = algo == "SHA-256" + ? detail::SHA_256 + : algo == "SHA-512" ? detail::SHA_512 : detail::MD5; + + auto A1 = username + ":" + auth.at("realm") + ":" + password; + + auto A2 = req.method + ":" + req.path; + if (qop == "auth-int") { A2 += ":" + H(req.body); } + + response = H(H(A1) + ":" + auth.at("nonce") + ":" + nc + ":" + cnonce + + ":" + qop + ":" + H(A2)); + } + + auto field = "Digest username=\"" + username + "\", realm=\"" + + auth.at("realm") + "\", nonce=\"" + auth.at("nonce") + + "\", uri=\"" + req.path + "\", algorithm=" + algo + + ", qop=" + qop + ", nc=\"" + nc + "\", cnonce=\"" + cnonce + + "\", response=\"" + response + "\""; + + auto key = is_proxy ? "Proxy-Authorization" : "Authorization"; + return std::make_pair(key, field); +} +#endif + +inline bool parse_www_authenticate(const Response &res, + std::map &auth, + bool is_proxy) { + auto auth_key = is_proxy ? "Proxy-Authenticate" : "WWW-Authenticate"; + if (res.has_header(auth_key)) { + static auto re = std::regex(R"~((?:(?:,\s*)?(.+?)=(?:"(.*?)"|([^,]*))))~"); + auto s = res.get_header_value(auth_key); + auto pos = s.find(' '); + if (pos != std::string::npos) { + auto type = s.substr(0, pos); + if (type == "Basic") { + return false; + } else if (type == "Digest") { + s = s.substr(pos + 1); + auto beg = std::sregex_iterator(s.begin(), s.end(), re); + for (auto i = beg; i != std::sregex_iterator(); ++i) { + auto m = *i; + auto key = s.substr(static_cast(m.position(1)), + static_cast(m.length(1))); + auto val = m.length(2) > 0 + ? s.substr(static_cast(m.position(2)), + static_cast(m.length(2))) + : s.substr(static_cast(m.position(3)), + static_cast(m.length(3))); + auth[key] = val; + } + return true; + } + } + } + return false; +} + +// https://stackoverflow.com/questions/440133/how-do-i-create-a-random-alpha-numeric-string-in-c/440240#answer-440240 +inline std::string random_string(size_t length) { + auto randchar = []() -> char { + const char charset[] = "0123456789" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz"; + const size_t max_index = (sizeof(charset) - 1); + return charset[static_cast(std::rand()) % max_index]; + }; + std::string str(length, 0); + std::generate_n(str.begin(), length, randchar); + return str; +} + +class ContentProviderAdapter { +public: + explicit ContentProviderAdapter( + ContentProviderWithoutLength &&content_provider) + : content_provider_(content_provider) {} + + bool operator()(size_t offset, size_t, DataSink &sink) { + return content_provider_(offset, sink); + } + +private: + ContentProviderWithoutLength content_provider_; +}; + +} // namespace detail + +// Header utilities +inline std::pair make_range_header(Ranges ranges) { + std::string field = "bytes="; + auto i = 0; + for (auto r : ranges) { + if (i != 0) { field += ", "; } + if (r.first != -1) { field += std::to_string(r.first); } + field += '-'; + if (r.second != -1) { field += std::to_string(r.second); } + i++; + } + return std::make_pair("Range", std::move(field)); +} + +inline std::pair +make_basic_authentication_header(const std::string &username, + const std::string &password, + bool is_proxy = false) { + auto field = "Basic " + detail::base64_encode(username + ":" + password); + auto key = is_proxy ? "Proxy-Authorization" : "Authorization"; + return std::make_pair(key, std::move(field)); +} + +inline std::pair +make_bearer_token_authentication_header(const std::string &token, + bool is_proxy = false) { + auto field = "Bearer " + token; + auto key = is_proxy ? "Proxy-Authorization" : "Authorization"; + return std::make_pair(key, std::move(field)); +} + +// Request implementation +inline bool Request::has_header(const char *key) const { + return detail::has_header(headers, key); +} + +inline std::string Request::get_header_value(const char *key, size_t id) const { + return detail::get_header_value(headers, key, id, ""); +} + +template +inline T Request::get_header_value(const char *key, size_t id) const { + return detail::get_header_value(headers, key, id, 0); +} + +inline size_t Request::get_header_value_count(const char *key) const { + auto r = headers.equal_range(key); + return static_cast(std::distance(r.first, r.second)); +} + +inline void Request::set_header(const char *key, const char *val) { + if (!detail::has_crlf(key) && !detail::has_crlf(val)) { + headers.emplace(key, val); + } +} + +inline void Request::set_header(const char *key, const std::string &val) { + if (!detail::has_crlf(key) && !detail::has_crlf(val.c_str())) { + headers.emplace(key, val); + } +} + +inline bool Request::has_param(const char *key) const { + return params.find(key) != params.end(); +} + +inline std::string Request::get_param_value(const char *key, size_t id) const { + auto rng = params.equal_range(key); + auto it = rng.first; + std::advance(it, static_cast(id)); + if (it != rng.second) { return it->second; } + return std::string(); +} + +inline size_t Request::get_param_value_count(const char *key) const { + auto r = params.equal_range(key); + return static_cast(std::distance(r.first, r.second)); +} + +inline bool Request::is_multipart_form_data() const { + const auto &content_type = get_header_value("Content-Type"); + return !content_type.find("multipart/form-data"); +} + +inline bool Request::has_file(const char *key) const { + return files.find(key) != files.end(); +} + +inline MultipartFormData Request::get_file_value(const char *key) const { + auto it = files.find(key); + if (it != files.end()) { return it->second; } + return MultipartFormData(); +} + +// Response implementation +inline bool Response::has_header(const char *key) const { + return headers.find(key) != headers.end(); +} + +inline std::string Response::get_header_value(const char *key, + size_t id) const { + return detail::get_header_value(headers, key, id, ""); +} + +template +inline T Response::get_header_value(const char *key, size_t id) const { + return detail::get_header_value(headers, key, id, 0); +} + +inline size_t Response::get_header_value_count(const char *key) const { + auto r = headers.equal_range(key); + return static_cast(std::distance(r.first, r.second)); +} + +inline void Response::set_header(const char *key, const char *val) { + if (!detail::has_crlf(key) && !detail::has_crlf(val)) { + headers.emplace(key, val); + } +} + +inline void Response::set_header(const char *key, const std::string &val) { + if (!detail::has_crlf(key) && !detail::has_crlf(val.c_str())) { + headers.emplace(key, val); + } +} + +inline void Response::set_redirect(const char *url, int stat) { + if (!detail::has_crlf(url)) { + set_header("Location", url); + if (300 <= stat && stat < 400) { + this->status = stat; + } else { + this->status = 302; + } + } +} + +inline void Response::set_redirect(const std::string &url, int stat) { + set_redirect(url.c_str(), stat); +} + +inline void Response::set_content(const char *s, size_t n, + const char *content_type) { + body.assign(s, n); + + auto rng = headers.equal_range("Content-Type"); + headers.erase(rng.first, rng.second); + set_header("Content-Type", content_type); +} + +inline void Response::set_content(const std::string &s, + const char *content_type) { + set_content(s.data(), s.size(), content_type); +} + +inline void +Response::set_content_provider(size_t in_length, const char *content_type, + ContentProvider provider, + const std::function &resource_releaser) { + assert(in_length > 0); + set_header("Content-Type", content_type); + content_length_ = in_length; + content_provider_ = std::move(provider); + content_provider_resource_releaser_ = resource_releaser; + is_chunked_content_provider_ = false; +} + +inline void +Response::set_content_provider(const char *content_type, + ContentProviderWithoutLength provider, + const std::function &resource_releaser) { + set_header("Content-Type", content_type); + content_length_ = 0; + content_provider_ = detail::ContentProviderAdapter(std::move(provider)); + content_provider_resource_releaser_ = resource_releaser; + is_chunked_content_provider_ = false; +} + +inline void Response::set_chunked_content_provider( + const char *content_type, ContentProviderWithoutLength provider, + const std::function &resource_releaser) { + set_header("Content-Type", content_type); + content_length_ = 0; + content_provider_ = detail::ContentProviderAdapter(std::move(provider)); + content_provider_resource_releaser_ = resource_releaser; + is_chunked_content_provider_ = true; +} + +// Result implementation +inline bool Result::has_request_header(const char *key) const { + return request_headers_.find(key) != request_headers_.end(); +} + +inline std::string Result::get_request_header_value(const char *key, + size_t id) const { + return detail::get_header_value(request_headers_, key, id, ""); +} + +template +inline T Result::get_request_header_value(const char *key, size_t id) const { + return detail::get_header_value(request_headers_, key, id, 0); +} + +inline size_t Result::get_request_header_value_count(const char *key) const { + auto r = request_headers_.equal_range(key); + return static_cast(std::distance(r.first, r.second)); +} + +// Stream implementation +inline ssize_t Stream::write(const char *ptr) { + return write(ptr, strlen(ptr)); +} + +inline ssize_t Stream::write(const std::string &s) { + return write(s.data(), s.size()); +} + +template +inline ssize_t Stream::write_format(const char *fmt, const Args &... args) { + const auto bufsiz = 2048; + std::array buf; + +#if defined(_MSC_VER) && _MSC_VER < 1900 + auto sn = _snprintf_s(buf.data(), bufsiz - 1, buf.size() - 1, fmt, args...); +#else + auto sn = snprintf(buf.data(), buf.size() - 1, fmt, args...); +#endif + if (sn <= 0) { return sn; } + + auto n = static_cast(sn); + + if (n >= buf.size() - 1) { + std::vector glowable_buf(buf.size()); + + while (n >= glowable_buf.size() - 1) { + glowable_buf.resize(glowable_buf.size() * 2); +#if defined(_MSC_VER) && _MSC_VER < 1900 + n = static_cast(_snprintf_s(&glowable_buf[0], glowable_buf.size(), + glowable_buf.size() - 1, fmt, + args...)); +#else + n = static_cast( + snprintf(&glowable_buf[0], glowable_buf.size() - 1, fmt, args...)); +#endif + } + return write(&glowable_buf[0], n); + } else { + return write(buf.data(), n); + } +} + +namespace detail { + +// Socket stream implementation +inline SocketStream::SocketStream(socket_t sock, time_t read_timeout_sec, + time_t read_timeout_usec, + time_t write_timeout_sec, + time_t write_timeout_usec) + : sock_(sock), read_timeout_sec_(read_timeout_sec), + read_timeout_usec_(read_timeout_usec), + write_timeout_sec_(write_timeout_sec), + write_timeout_usec_(write_timeout_usec) {} + +inline SocketStream::~SocketStream() {} + +inline bool SocketStream::is_readable() const { + return select_read(sock_, read_timeout_sec_, read_timeout_usec_) > 0; +} + +inline bool SocketStream::is_writable() const { + return select_write(sock_, write_timeout_sec_, write_timeout_usec_) > 0; +} + +inline ssize_t SocketStream::read(char *ptr, size_t size) { + if (!is_readable()) { return -1; } + +#ifdef _WIN32 + if (size > static_cast((std::numeric_limits::max)())) { + return -1; + } + return recv(sock_, ptr, static_cast(size), CPPHTTPLIB_RECV_FLAGS); +#else + return handle_EINTR( + [&]() { return recv(sock_, ptr, size, CPPHTTPLIB_RECV_FLAGS); }); +#endif +} + +inline ssize_t SocketStream::write(const char *ptr, size_t size) { + if (!is_writable()) { return -1; } + +#ifdef _WIN32 + if (size > static_cast((std::numeric_limits::max)())) { + return -1; + } + return send(sock_, ptr, static_cast(size), CPPHTTPLIB_SEND_FLAGS); +#else + return handle_EINTR( + [&]() { return send(sock_, ptr, size, CPPHTTPLIB_SEND_FLAGS); }); +#endif +} + +inline void SocketStream::get_remote_ip_and_port(std::string &ip, + int &port) const { + return detail::get_remote_ip_and_port(sock_, ip, port); +} + +inline socket_t SocketStream::socket() const { return sock_; } + +// Buffer stream implementation +inline bool BufferStream::is_readable() const { return true; } + +inline bool BufferStream::is_writable() const { return true; } + +inline ssize_t BufferStream::read(char *ptr, size_t size) { +#if defined(_MSC_VER) && _MSC_VER <= 1900 + auto len_read = buffer._Copy_s(ptr, size, size, position); +#else + auto len_read = buffer.copy(ptr, size, position); +#endif + position += static_cast(len_read); + return static_cast(len_read); +} + +inline ssize_t BufferStream::write(const char *ptr, size_t size) { + buffer.append(ptr, size); + return static_cast(size); +} + +inline void BufferStream::get_remote_ip_and_port(std::string & /*ip*/, + int & /*port*/) const {} + +inline socket_t BufferStream::socket() const { return 0; } + +inline const std::string &BufferStream::get_buffer() const { return buffer; } + +} // namespace detail + +// HTTP server implementation +inline Server::Server() + : new_task_queue( + [] { return new ThreadPool(CPPHTTPLIB_THREAD_POOL_COUNT); }), + svr_sock_(INVALID_SOCKET), is_running_(false) { +#ifndef _WIN32 + signal(SIGPIPE, SIG_IGN); +#endif +} + +inline Server::~Server() {} + +inline Server &Server::Get(const char *pattern, Handler handler) { + return Get(pattern, strlen(pattern), handler); +} + +inline Server &Server::Get(const char *pattern, size_t pattern_len, + Handler handler) { + get_handlers_.push_back( + std::make_pair(std::regex(pattern, pattern_len), std::move(handler))); + return *this; +} + +inline Server &Server::Post(const char *pattern, Handler handler) { + return Post(pattern, strlen(pattern), handler); +} + +inline Server &Server::Post(const char *pattern, size_t pattern_len, + Handler handler) { + post_handlers_.push_back( + std::make_pair(std::regex(pattern, pattern_len), std::move(handler))); + return *this; +} + +inline Server &Server::Post(const char *pattern, + HandlerWithContentReader handler) { + return Post(pattern, strlen(pattern), handler); +} + +inline Server &Server::Post(const char *pattern, size_t pattern_len, + HandlerWithContentReader handler) { + post_handlers_for_content_reader_.push_back( + std::make_pair(std::regex(pattern, pattern_len), std::move(handler))); + return *this; +} + +inline Server &Server::Put(const char *pattern, Handler handler) { + return Put(pattern, strlen(pattern), handler); +} + +inline Server &Server::Put(const char *pattern, size_t pattern_len, + Handler handler) { + put_handlers_.push_back( + std::make_pair(std::regex(pattern, pattern_len), std::move(handler))); + return *this; +} + +inline Server &Server::Put(const char *pattern, + HandlerWithContentReader handler) { + return Put(pattern, strlen(pattern), handler); +} + +inline Server &Server::Put(const char *pattern, size_t pattern_len, + HandlerWithContentReader handler) { + put_handlers_for_content_reader_.push_back( + std::make_pair(std::regex(pattern, pattern_len), std::move(handler))); + return *this; +} + +inline Server &Server::Patch(const char *pattern, Handler handler) { + return Patch(pattern, strlen(pattern), handler); +} + +inline Server &Server::Patch(const char *pattern, size_t pattern_len, + Handler handler) { + patch_handlers_.push_back( + std::make_pair(std::regex(pattern, pattern_len), std::move(handler))); + return *this; +} + +inline Server &Server::Patch(const char *pattern, + HandlerWithContentReader handler) { + return Patch(pattern, strlen(pattern), handler); +} + +inline Server &Server::Patch(const char *pattern, size_t pattern_len, + HandlerWithContentReader handler) { + patch_handlers_for_content_reader_.push_back( + std::make_pair(std::regex(pattern, pattern_len), std::move(handler))); + return *this; +} + +inline Server &Server::Delete(const char *pattern, Handler handler) { + return Delete(pattern, strlen(pattern), handler); +} + +inline Server &Server::Delete(const char *pattern, size_t pattern_len, + Handler handler) { + delete_handlers_.push_back( + std::make_pair(std::regex(pattern, pattern_len), std::move(handler))); + return *this; +} + +inline Server &Server::Delete(const char *pattern, + HandlerWithContentReader handler) { + return Delete(pattern, strlen(pattern), handler); +} + +inline Server &Server::Delete(const char *pattern, size_t pattern_len, + HandlerWithContentReader handler) { + delete_handlers_for_content_reader_.push_back( + std::make_pair(std::regex(pattern, pattern_len), std::move(handler))); + return *this; +} + +inline Server &Server::Options(const char *pattern, Handler handler) { + return Options(pattern, strlen(pattern), handler); +} + +inline Server &Server::Options(const char *pattern, size_t pattern_len, + Handler handler) { + options_handlers_.push_back( + std::make_pair(std::regex(pattern, pattern_len), std::move(handler))); + return *this; +} + +inline bool Server::set_base_dir(const char *dir, const char *mount_point) { + return set_mount_point(mount_point, dir); +} + +inline bool Server::set_mount_point(const char *mount_point, const char *dir, + Headers headers) { + if (detail::is_dir(dir)) { + std::string mnt = mount_point ? mount_point : "/"; + if (!mnt.empty() && mnt[0] == '/') { + base_dirs_.push_back({mnt, dir, std::move(headers)}); + return true; + } + } + return false; +} + +inline bool Server::remove_mount_point(const char *mount_point) { + for (auto it = base_dirs_.begin(); it != base_dirs_.end(); ++it) { + if (it->mount_point == mount_point) { + base_dirs_.erase(it); + return true; + } + } + return false; +} + +inline Server & +Server::set_file_extension_and_mimetype_mapping(const char *ext, + const char *mime) { + file_extension_and_mimetype_map_[ext] = mime; + + return *this; +} + +inline Server &Server::set_file_request_handler(Handler handler) { + file_request_handler_ = std::move(handler); + + return *this; +} + +inline Server &Server::set_error_handler(HandlerWithResponse handler) { + error_handler_ = std::move(handler); + return *this; +} + +inline Server &Server::set_error_handler(Handler handler) { + error_handler_ = [handler](const Request &req, Response &res) { + handler(req, res); + return HandlerResponse::Handled; + }; + return *this; +} + +inline Server &Server::set_exception_handler(ExceptionHandler handler) { + exception_handler_ = std::move(handler); + return *this; +} + +inline Server &Server::set_pre_routing_handler(HandlerWithResponse handler) { + pre_routing_handler_ = std::move(handler); + return *this; +} + +inline Server &Server::set_post_routing_handler(Handler handler) { + post_routing_handler_ = std::move(handler); + return *this; +} + +inline Server &Server::set_logger(Logger logger) { + logger_ = std::move(logger); + + return *this; +} + +inline Server & +Server::set_expect_100_continue_handler(Expect100ContinueHandler handler) { + expect_100_continue_handler_ = std::move(handler); + + return *this; +} + +inline Server &Server::set_tcp_nodelay(bool on) { + tcp_nodelay_ = on; + + return *this; +} + +inline Server &Server::set_socket_options(SocketOptions socket_options) { + socket_options_ = std::move(socket_options); + + return *this; +} + +inline Server &Server::set_keep_alive_max_count(size_t count) { + keep_alive_max_count_ = count; + + return *this; +} + +inline Server &Server::set_keep_alive_timeout(time_t sec) { + keep_alive_timeout_sec_ = sec; + + return *this; +} + +inline Server &Server::set_read_timeout(time_t sec, time_t usec) { + read_timeout_sec_ = sec; + read_timeout_usec_ = usec; + + return *this; +} + +inline Server &Server::set_write_timeout(time_t sec, time_t usec) { + write_timeout_sec_ = sec; + write_timeout_usec_ = usec; + + return *this; +} + +inline Server &Server::set_idle_interval(time_t sec, time_t usec) { + idle_interval_sec_ = sec; + idle_interval_usec_ = usec; + + return *this; +} + +inline Server &Server::set_payload_max_length(size_t length) { + payload_max_length_ = length; + + return *this; +} + +inline bool Server::bind_to_port(const char *host, int port, int socket_flags) { + if (bind_internal(host, port, socket_flags) < 0) return false; + return true; +} +inline int Server::bind_to_any_port(const char *host, int socket_flags) { + return bind_internal(host, 0, socket_flags); +} + +inline bool Server::listen_after_bind() { return listen_internal(); } + +inline bool Server::listen(const char *host, int port, int socket_flags) { + return bind_to_port(host, port, socket_flags) && listen_internal(); +} + +inline bool Server::is_running() const { return is_running_; } + +inline void Server::stop() { + if (is_running_) { + assert(svr_sock_ != INVALID_SOCKET); + std::atomic sock(svr_sock_.exchange(INVALID_SOCKET)); + detail::shutdown_socket(sock); + detail::close_socket(sock); + } +} + +inline bool Server::parse_request_line(const char *s, Request &req) { + const static std::regex re( + "(GET|HEAD|POST|PUT|DELETE|CONNECT|OPTIONS|TRACE|PATCH|PRI) " + "(([^? ]+)(?:\\?([^ ]*?))?) (HTTP/1\\.[01])\r\n"); + + std::cmatch m; + if (std::regex_match(s, m, re)) { + req.version = std::string(m[5]); + req.method = std::string(m[1]); + req.target = std::string(m[2]); + req.path = detail::decode_url(m[3], false); + + // Parse query text + auto len = std::distance(m[4].first, m[4].second); + if (len > 0) { detail::parse_query_text(m[4], req.params); } + + return true; + } + + return false; +} + +inline bool Server::write_response(Stream &strm, bool close_connection, + const Request &req, Response &res) { + return write_response_core(strm, close_connection, req, res, false); +} + +inline bool Server::write_response_with_content(Stream &strm, + bool close_connection, + const Request &req, + Response &res) { + return write_response_core(strm, close_connection, req, res, true); +} + +inline bool Server::write_response_core(Stream &strm, bool close_connection, + const Request &req, Response &res, + bool need_apply_ranges) { + assert(res.status != -1); + + if (400 <= res.status && error_handler_ && + error_handler_(req, res) == HandlerResponse::Handled) { + need_apply_ranges = true; + } + + std::string content_type; + std::string boundary; + if (need_apply_ranges) { apply_ranges(req, res, content_type, boundary); } + + // Prepare additional headers + if (close_connection || req.get_header_value("Connection") == "close") { + res.set_header("Connection", "close"); + } else { + std::stringstream ss; + ss << "timeout=" << keep_alive_timeout_sec_ + << ", max=" << keep_alive_max_count_; + res.set_header("Keep-Alive", ss.str()); + } + + if (!res.has_header("Content-Type") && + (!res.body.empty() || res.content_length_ > 0 || res.content_provider_)) { + res.set_header("Content-Type", "text/plain"); + } + + if (!res.has_header("Content-Length") && res.body.empty() && + !res.content_length_ && !res.content_provider_) { + res.set_header("Content-Length", "0"); + } + + if (!res.has_header("Accept-Ranges") && req.method == "HEAD") { + res.set_header("Accept-Ranges", "bytes"); + } + + if (post_routing_handler_) { post_routing_handler_(req, res); } + + // Response line and headers + { + detail::BufferStream bstrm; + + if (!bstrm.write_format("HTTP/1.1 %d %s\r\n", res.status, + detail::status_message(res.status))) { + return false; + } + + if (!detail::write_headers(bstrm, res.headers)) { return false; } + + // Flush buffer + auto &data = bstrm.get_buffer(); + strm.write(data.data(), data.size()); + } + + // Body + auto ret = true; + if (req.method != "HEAD") { + if (!res.body.empty()) { + if (!strm.write(res.body)) { ret = false; } + } else if (res.content_provider_) { + if (!write_content_with_provider(strm, req, res, boundary, + content_type)) { + ret = false; + } + } + } + + // Log + if (logger_) { logger_(req, res); } + + return ret; +} + +inline bool +Server::write_content_with_provider(Stream &strm, const Request &req, + Response &res, const std::string &boundary, + const std::string &content_type) { + auto is_shutting_down = [this]() { + return this->svr_sock_ == INVALID_SOCKET; + }; + + if (res.content_length_ > 0) { + if (req.ranges.empty()) { + return detail::write_content(strm, res.content_provider_, 0, + res.content_length_, is_shutting_down); + } else if (req.ranges.size() == 1) { + auto offsets = + detail::get_range_offset_and_length(req, res.content_length_, 0); + auto offset = offsets.first; + auto length = offsets.second; + return detail::write_content(strm, res.content_provider_, offset, length, + is_shutting_down); + } else { + return detail::write_multipart_ranges_data( + strm, req, res, boundary, content_type, is_shutting_down); + } + } else { + if (res.is_chunked_content_provider_) { + auto type = detail::encoding_type(req, res); + + std::unique_ptr compressor; + if (type == detail::EncodingType::Gzip) { +#ifdef CPPHTTPLIB_ZLIB_SUPPORT + compressor = detail::make_unique(); +#endif + } else if (type == detail::EncodingType::Brotli) { +#ifdef CPPHTTPLIB_BROTLI_SUPPORT + compressor = detail::make_unique(); +#endif + } else { + compressor = detail::make_unique(); + } + assert(compressor != nullptr); + + return detail::write_content_chunked(strm, res.content_provider_, + is_shutting_down, *compressor); + } else { + return detail::write_content_without_length(strm, res.content_provider_, + is_shutting_down); + } + } +} + +inline bool Server::read_content(Stream &strm, Request &req, Response &res) { + MultipartFormDataMap::iterator cur; + if (read_content_core( + strm, req, res, + // Regular + [&](const char *buf, size_t n) { + if (req.body.size() + n > req.body.max_size()) { return false; } + req.body.append(buf, n); + return true; + }, + // Multipart + [&](const MultipartFormData &file) { + cur = req.files.emplace(file.name, file); + return true; + }, + [&](const char *buf, size_t n) { + auto &content = cur->second.content; + if (content.size() + n > content.max_size()) { return false; } + content.append(buf, n); + return true; + })) { + const auto &content_type = req.get_header_value("Content-Type"); + if (!content_type.find("application/x-www-form-urlencoded")) { + detail::parse_query_text(req.body, req.params); + } + return true; + } + return false; +} + +inline bool Server::read_content_with_content_receiver( + Stream &strm, Request &req, Response &res, ContentReceiver receiver, + MultipartContentHeader multipart_header, + ContentReceiver multipart_receiver) { + return read_content_core(strm, req, res, std::move(receiver), + std::move(multipart_header), + std::move(multipart_receiver)); +} + +inline bool Server::read_content_core(Stream &strm, Request &req, Response &res, + ContentReceiver receiver, + MultipartContentHeader mulitpart_header, + ContentReceiver multipart_receiver) { + detail::MultipartFormDataParser multipart_form_data_parser; + ContentReceiverWithProgress out; + + if (req.is_multipart_form_data()) { + const auto &content_type = req.get_header_value("Content-Type"); + std::string boundary; + if (!detail::parse_multipart_boundary(content_type, boundary)) { + res.status = 400; + return false; + } + + multipart_form_data_parser.set_boundary(std::move(boundary)); + out = [&](const char *buf, size_t n, uint64_t /*off*/, uint64_t /*len*/) { + /* For debug + size_t pos = 0; + while (pos < n) { + auto read_size = std::min(1, n - pos); + auto ret = multipart_form_data_parser.parse( + buf + pos, read_size, multipart_receiver, mulitpart_header); + if (!ret) { return false; } + pos += read_size; + } + return true; + */ + return multipart_form_data_parser.parse(buf, n, multipart_receiver, + mulitpart_header); + }; + } else { + out = [receiver](const char *buf, size_t n, uint64_t /*off*/, + uint64_t /*len*/) { return receiver(buf, n); }; + } + + if (req.method == "DELETE" && !req.has_header("Content-Length")) { + return true; + } + + if (!detail::read_content(strm, req, payload_max_length_, res.status, nullptr, + out, true)) { + return false; + } + + if (req.is_multipart_form_data()) { + if (!multipart_form_data_parser.is_valid()) { + res.status = 400; + return false; + } + } + + return true; +} + +inline bool Server::handle_file_request(const Request &req, Response &res, + bool head) { + for (const auto &entry : base_dirs_) { + // Prefix match + if (!req.path.compare(0, entry.mount_point.size(), entry.mount_point)) { + std::string sub_path = "/" + req.path.substr(entry.mount_point.size()); + if (detail::is_valid_path(sub_path)) { + auto path = entry.base_dir + sub_path; + if (path.back() == '/') { path += "index.html"; } + + if (detail::is_file(path)) { + detail::read_file(path, res.body); + auto type = + detail::find_content_type(path, file_extension_and_mimetype_map_); + if (type) { res.set_header("Content-Type", type); } + for (const auto &kv : entry.headers) { + res.set_header(kv.first.c_str(), kv.second); + } + res.status = req.has_header("Range") ? 206 : 200; + if (!head && file_request_handler_) { + file_request_handler_(req, res); + } + return true; + } + } + } + } + return false; +} + +inline socket_t +Server::create_server_socket(const char *host, int port, int socket_flags, + SocketOptions socket_options) const { + return detail::create_socket( + host, port, socket_flags, tcp_nodelay_, std::move(socket_options), + [](socket_t sock, struct addrinfo &ai) -> bool { + if (::bind(sock, ai.ai_addr, static_cast(ai.ai_addrlen))) { + return false; + } + if (::listen(sock, 5)) { // Listen through 5 channels + return false; + } + return true; + }); +} + +inline int Server::bind_internal(const char *host, int port, int socket_flags) { + if (!is_valid()) { return -1; } + + svr_sock_ = create_server_socket(host, port, socket_flags, socket_options_); + if (svr_sock_ == INVALID_SOCKET) { return -1; } + + if (port == 0) { + struct sockaddr_storage addr; + socklen_t addr_len = sizeof(addr); + if (getsockname(svr_sock_, reinterpret_cast(&addr), + &addr_len) == -1) { + return -1; + } + if (addr.ss_family == AF_INET) { + return ntohs(reinterpret_cast(&addr)->sin_port); + } else if (addr.ss_family == AF_INET6) { + return ntohs(reinterpret_cast(&addr)->sin6_port); + } else { + return -1; + } + } else { + return port; + } +} + +inline bool Server::listen_internal() { + auto ret = true; + is_running_ = true; + + { + std::unique_ptr task_queue(new_task_queue()); + + while (svr_sock_ != INVALID_SOCKET) { +#ifndef _WIN32 + if (idle_interval_sec_ > 0 || idle_interval_usec_ > 0) { +#endif + auto val = detail::select_read(svr_sock_, idle_interval_sec_, + idle_interval_usec_); + if (val == 0) { // Timeout + task_queue->on_idle(); + continue; + } +#ifndef _WIN32 + } +#endif + socket_t sock = accept(svr_sock_, nullptr, nullptr); + + if (sock == INVALID_SOCKET) { + if (errno == EMFILE) { + // The per-process limit of open file descriptors has been reached. + // Try to accept new connections after a short sleep. + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + continue; + } + if (svr_sock_ != INVALID_SOCKET) { + detail::close_socket(svr_sock_); + ret = false; + } else { + ; // The server socket was closed by user. + } + break; + } + +#if __cplusplus > 201703L + task_queue->enqueue([=, this]() { process_and_close_socket(sock); }); +#else + task_queue->enqueue([=]() { process_and_close_socket(sock); }); +#endif + } + + task_queue->shutdown(); + } + + is_running_ = false; + return ret; +} + +inline bool Server::routing(Request &req, Response &res, Stream &strm) { + if (pre_routing_handler_ && + pre_routing_handler_(req, res) == HandlerResponse::Handled) { + return true; + } + + // File handler + bool is_head_request = req.method == "HEAD"; + if ((req.method == "GET" || is_head_request) && + handle_file_request(req, res, is_head_request)) { + return true; + } + + if (detail::expect_content(req)) { + // Content reader handler + { + ContentReader reader( + [&](ContentReceiver receiver) { + return read_content_with_content_receiver( + strm, req, res, std::move(receiver), nullptr, nullptr); + }, + [&](MultipartContentHeader header, ContentReceiver receiver) { + return read_content_with_content_receiver(strm, req, res, nullptr, + std::move(header), + std::move(receiver)); + }); + + if (req.method == "POST") { + if (dispatch_request_for_content_reader( + req, res, std::move(reader), + post_handlers_for_content_reader_)) { + return true; + } + } else if (req.method == "PUT") { + if (dispatch_request_for_content_reader( + req, res, std::move(reader), + put_handlers_for_content_reader_)) { + return true; + } + } else if (req.method == "PATCH") { + if (dispatch_request_for_content_reader( + req, res, std::move(reader), + patch_handlers_for_content_reader_)) { + return true; + } + } else if (req.method == "DELETE") { + if (dispatch_request_for_content_reader( + req, res, std::move(reader), + delete_handlers_for_content_reader_)) { + return true; + } + } + } + + // Read content into `req.body` + if (!read_content(strm, req, res)) { return false; } + } + + // Regular handler + if (req.method == "GET" || req.method == "HEAD") { + return dispatch_request(req, res, get_handlers_); + } else if (req.method == "POST") { + return dispatch_request(req, res, post_handlers_); + } else if (req.method == "PUT") { + return dispatch_request(req, res, put_handlers_); + } else if (req.method == "DELETE") { + return dispatch_request(req, res, delete_handlers_); + } else if (req.method == "OPTIONS") { + return dispatch_request(req, res, options_handlers_); + } else if (req.method == "PATCH") { + return dispatch_request(req, res, patch_handlers_); + } + + res.status = 400; + return false; +} + +inline bool Server::dispatch_request(Request &req, Response &res, + const Handlers &handlers) { + for (const auto &x : handlers) { + const auto &pattern = x.first; + const auto &handler = x.second; + + if (std::regex_match(req.path, req.matches, pattern)) { + handler(req, res); + return true; + } + } + return false; +} + +inline void Server::apply_ranges(const Request &req, Response &res, + std::string &content_type, + std::string &boundary) { + if (req.ranges.size() > 1) { + boundary = detail::make_multipart_data_boundary(); + + auto it = res.headers.find("Content-Type"); + if (it != res.headers.end()) { + content_type = it->second; + res.headers.erase(it); + } + + res.headers.emplace("Content-Type", + "multipart/byteranges; boundary=" + boundary); + } + + auto type = detail::encoding_type(req, res); + + if (res.body.empty()) { + if (res.content_length_ > 0) { + size_t length = 0; + if (req.ranges.empty()) { + length = res.content_length_; + } else if (req.ranges.size() == 1) { + auto offsets = + detail::get_range_offset_and_length(req, res.content_length_, 0); + auto offset = offsets.first; + length = offsets.second; + auto content_range = detail::make_content_range_header_field( + offset, length, res.content_length_); + res.set_header("Content-Range", content_range); + } else { + length = detail::get_multipart_ranges_data_length(req, res, boundary, + content_type); + } + res.set_header("Content-Length", std::to_string(length)); + } else { + if (res.content_provider_) { + if (res.is_chunked_content_provider_) { + res.set_header("Transfer-Encoding", "chunked"); + if (type == detail::EncodingType::Gzip) { + res.set_header("Content-Encoding", "gzip"); + } else if (type == detail::EncodingType::Brotli) { + res.set_header("Content-Encoding", "br"); + } + } + } + } + } else { + if (req.ranges.empty()) { + ; + } else if (req.ranges.size() == 1) { + auto offsets = + detail::get_range_offset_and_length(req, res.body.size(), 0); + auto offset = offsets.first; + auto length = offsets.second; + auto content_range = detail::make_content_range_header_field( + offset, length, res.body.size()); + res.set_header("Content-Range", content_range); + if (offset < res.body.size()) { + res.body = res.body.substr(offset, length); + } else { + res.body.clear(); + res.status = 416; + } + } else { + std::string data; + if (detail::make_multipart_ranges_data(req, res, boundary, content_type, + data)) { + res.body.swap(data); + } else { + res.body.clear(); + res.status = 416; + } + } + + if (type != detail::EncodingType::None) { + std::unique_ptr compressor; + std::string content_encoding; + + if (type == detail::EncodingType::Gzip) { +#ifdef CPPHTTPLIB_ZLIB_SUPPORT + compressor = detail::make_unique(); + content_encoding = "gzip"; +#endif + } else if (type == detail::EncodingType::Brotli) { +#ifdef CPPHTTPLIB_BROTLI_SUPPORT + compressor = detail::make_unique(); + content_encoding = "br"; +#endif + } + + if (compressor) { + std::string compressed; + if (compressor->compress(res.body.data(), res.body.size(), true, + [&](const char *data, size_t data_len) { + compressed.append(data, data_len); + return true; + })) { + res.body.swap(compressed); + res.set_header("Content-Encoding", content_encoding); + } + } + } + + auto length = std::to_string(res.body.size()); + res.set_header("Content-Length", length); + } +} + +inline bool Server::dispatch_request_for_content_reader( + Request &req, Response &res, ContentReader content_reader, + const HandlersForContentReader &handlers) { + for (const auto &x : handlers) { + const auto &pattern = x.first; + const auto &handler = x.second; + + if (std::regex_match(req.path, req.matches, pattern)) { + handler(req, res, content_reader); + return true; + } + } + return false; +} + +inline bool +Server::process_request(Stream &strm, bool close_connection, + bool &connection_closed, + const std::function &setup_request) { + std::array buf{}; + + detail::stream_line_reader line_reader(strm, buf.data(), buf.size()); + + // Connection has been closed on client + if (!line_reader.getline()) { return false; } + + Request req; + Response res; + + res.version = "HTTP/1.1"; + +#ifdef _WIN32 + // TODO: Increase FD_SETSIZE statically (libzmq), dynamically (MySQL). +#else +#ifndef CPPHTTPLIB_USE_POLL + // Socket file descriptor exceeded FD_SETSIZE... + if (strm.socket() >= FD_SETSIZE) { + Headers dummy; + detail::read_headers(strm, dummy); + res.status = 500; + return write_response(strm, close_connection, req, res); + } +#endif +#endif + + // Check if the request URI doesn't exceed the limit + if (line_reader.size() > CPPHTTPLIB_REQUEST_URI_MAX_LENGTH) { + Headers dummy; + detail::read_headers(strm, dummy); + res.status = 414; + return write_response(strm, close_connection, req, res); + } + + // Request line and headers + if (!parse_request_line(line_reader.ptr(), req) || + !detail::read_headers(strm, req.headers)) { + res.status = 400; + return write_response(strm, close_connection, req, res); + } + + if (req.get_header_value("Connection") == "close") { + connection_closed = true; + } + + if (req.version == "HTTP/1.0" && + req.get_header_value("Connection") != "Keep-Alive") { + connection_closed = true; + } + + strm.get_remote_ip_and_port(req.remote_addr, req.remote_port); + req.set_header("REMOTE_ADDR", req.remote_addr); + req.set_header("REMOTE_PORT", std::to_string(req.remote_port)); + + if (req.has_header("Range")) { + const auto &range_header_value = req.get_header_value("Range"); + if (!detail::parse_range_header(range_header_value, req.ranges)) { + res.status = 416; + return write_response(strm, close_connection, req, res); + } + } + + if (setup_request) { setup_request(req); } + + if (req.get_header_value("Expect") == "100-continue") { + auto status = 100; + if (expect_100_continue_handler_) { + status = expect_100_continue_handler_(req, res); + } + switch (status) { + case 100: + case 417: + strm.write_format("HTTP/1.1 %d %s\r\n\r\n", status, + detail::status_message(status)); + break; + default: return write_response(strm, close_connection, req, res); + } + } + + // Rounting + bool routed = false; + try { + routed = routing(req, res, strm); + } catch (std::exception &e) { + if (exception_handler_) { + exception_handler_(req, res, e); + routed = true; + } else { + res.status = 500; + res.set_header("EXCEPTION_WHAT", e.what()); + } + } catch (...) { + res.status = 500; + res.set_header("EXCEPTION_WHAT", "UNKNOWN"); + } + + if (routed) { + if (res.status == -1) { res.status = req.ranges.empty() ? 200 : 206; } + return write_response_with_content(strm, close_connection, req, res); + } else { + if (res.status == -1) { res.status = 404; } + return write_response(strm, close_connection, req, res); + } +} + +inline bool Server::is_valid() const { return true; } + +inline bool Server::process_and_close_socket(socket_t sock) { + auto ret = detail::process_server_socket( + sock, keep_alive_max_count_, keep_alive_timeout_sec_, read_timeout_sec_, + read_timeout_usec_, write_timeout_sec_, write_timeout_usec_, + [this](Stream &strm, bool close_connection, bool &connection_closed) { + return process_request(strm, close_connection, connection_closed, + nullptr); + }); + + detail::shutdown_socket(sock); + detail::close_socket(sock); + return ret; +} + +// HTTP client implementation +inline ClientImpl::ClientImpl(const std::string &host) + : ClientImpl(host, 80, std::string(), std::string()) {} + +inline ClientImpl::ClientImpl(const std::string &host, int port) + : ClientImpl(host, port, std::string(), std::string()) {} + +inline ClientImpl::ClientImpl(const std::string &host, int port, + const std::string &client_cert_path, + const std::string &client_key_path) + // : (Error::Success), host_(host), port_(port), + : host_(host), port_(port), + host_and_port_(host_ + ":" + std::to_string(port_)), + client_cert_path_(client_cert_path), client_key_path_(client_key_path) {} + +inline ClientImpl::~ClientImpl() { lock_socket_and_shutdown_and_close(); } + +inline bool ClientImpl::is_valid() const { return true; } + +inline void ClientImpl::copy_settings(const ClientImpl &rhs) { + client_cert_path_ = rhs.client_cert_path_; + client_key_path_ = rhs.client_key_path_; + connection_timeout_sec_ = rhs.connection_timeout_sec_; + read_timeout_sec_ = rhs.read_timeout_sec_; + read_timeout_usec_ = rhs.read_timeout_usec_; + write_timeout_sec_ = rhs.write_timeout_sec_; + write_timeout_usec_ = rhs.write_timeout_usec_; + basic_auth_username_ = rhs.basic_auth_username_; + basic_auth_password_ = rhs.basic_auth_password_; + bearer_token_auth_token_ = rhs.bearer_token_auth_token_; +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + digest_auth_username_ = rhs.digest_auth_username_; + digest_auth_password_ = rhs.digest_auth_password_; +#endif + keep_alive_ = rhs.keep_alive_; + follow_location_ = rhs.follow_location_; + tcp_nodelay_ = rhs.tcp_nodelay_; + socket_options_ = rhs.socket_options_; + compress_ = rhs.compress_; + decompress_ = rhs.decompress_; + interface_ = rhs.interface_; + proxy_host_ = rhs.proxy_host_; + proxy_port_ = rhs.proxy_port_; + proxy_basic_auth_username_ = rhs.proxy_basic_auth_username_; + proxy_basic_auth_password_ = rhs.proxy_basic_auth_password_; + proxy_bearer_token_auth_token_ = rhs.proxy_bearer_token_auth_token_; +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + proxy_digest_auth_username_ = rhs.proxy_digest_auth_username_; + proxy_digest_auth_password_ = rhs.proxy_digest_auth_password_; +#endif +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + server_certificate_verification_ = rhs.server_certificate_verification_; +#endif + logger_ = rhs.logger_; +} + +inline socket_t ClientImpl::create_client_socket(Error &error) const { + if (!proxy_host_.empty() && proxy_port_ != -1) { + return detail::create_client_socket( + proxy_host_.c_str(), proxy_port_, tcp_nodelay_, socket_options_, + connection_timeout_sec_, connection_timeout_usec_, interface_, error); + } + return detail::create_client_socket( + host_.c_str(), port_, tcp_nodelay_, socket_options_, + connection_timeout_sec_, connection_timeout_usec_, interface_, error); +} + +inline bool ClientImpl::create_and_connect_socket(Socket &socket, + Error &error) { + auto sock = create_client_socket(error); + if (sock == INVALID_SOCKET) { return false; } + socket.sock = sock; + return true; +} + +inline void ClientImpl::shutdown_ssl(Socket & /*socket*/, + bool /*shutdown_gracefully*/) { + // If there are any requests in flight from threads other than us, then it's + // a thread-unsafe race because individual ssl* objects are not thread-safe. + assert(socket_requests_in_flight_ == 0 || + socket_requests_are_from_thread_ == std::this_thread::get_id()); +} + +inline void ClientImpl::shutdown_socket(Socket &socket) { + if (socket.sock == INVALID_SOCKET) { return; } + detail::shutdown_socket(socket.sock); +} + +inline void ClientImpl::close_socket(Socket &socket) { + // If there are requests in flight in another thread, usually closing + // the socket will be fine and they will simply receive an error when + // using the closed socket, but it is still a bug since rarely the OS + // may reassign the socket id to be used for a new socket, and then + // suddenly they will be operating on a live socket that is different + // than the one they intended! + assert(socket_requests_in_flight_ == 0 || + socket_requests_are_from_thread_ == std::this_thread::get_id()); + + // It is also a bug if this happens while SSL is still active +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + assert(socket.ssl == nullptr); +#endif + if (socket.sock == INVALID_SOCKET) { return; } + detail::close_socket(socket.sock); + socket.sock = INVALID_SOCKET; +} + +inline void ClientImpl::lock_socket_and_shutdown_and_close() { + std::lock_guard guard(socket_mutex_); + shutdown_ssl(socket_, true); + shutdown_socket(socket_); + close_socket(socket_); +} + +inline bool ClientImpl::read_response_line(Stream &strm, const Request &req, + Response &res) { + std::array buf; + + detail::stream_line_reader line_reader(strm, buf.data(), buf.size()); + + if (!line_reader.getline()) { return false; } + + const static std::regex re("(HTTP/1\\.[01]) (\\d{3})(?: (.*?))?\r\n"); + + std::cmatch m; + if (!std::regex_match(line_reader.ptr(), m, re)) { + return req.method == "CONNECT"; + } + res.version = std::string(m[1]); + res.status = std::stoi(std::string(m[2])); + res.reason = std::string(m[3]); + + // Ignore '100 Continue' + while (res.status == 100) { + if (!line_reader.getline()) { return false; } // CRLF + if (!line_reader.getline()) { return false; } // next response line + + if (!std::regex_match(line_reader.ptr(), m, re)) { return false; } + res.version = std::string(m[1]); + res.status = std::stoi(std::string(m[2])); + res.reason = std::string(m[3]); + } + + return true; +} + +inline bool ClientImpl::send(Request &req, Response &res, Error &error) { + std::lock_guard request_mutex_guard(request_mutex_); + + { + std::lock_guard guard(socket_mutex_); + // Set this to false immediately - if it ever gets set to true by the end of + // the request, we know another thread instructed us to close the socket. + socket_should_be_closed_when_request_is_done_ = false; + + auto is_alive = false; + if (socket_.is_open()) { + is_alive = detail::select_write(socket_.sock, 0, 0) > 0; + if (!is_alive) { + // Attempt to avoid sigpipe by shutting down nongracefully if it seems + // like the other side has already closed the connection Also, there + // cannot be any requests in flight from other threads since we locked + // request_mutex_, so safe to close everything immediately + const bool shutdown_gracefully = false; + shutdown_ssl(socket_, shutdown_gracefully); + shutdown_socket(socket_); + close_socket(socket_); + } + } + + if (!is_alive) { + if (!create_and_connect_socket(socket_, error)) { return false; } + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + // TODO: refactoring + if (is_ssl()) { + auto &scli = static_cast(*this); + if (!proxy_host_.empty() && proxy_port_ != -1) { + bool success = false; + if (!scli.connect_with_proxy(socket_, res, success, error)) { + return success; + } + } + + if (!scli.initialize_ssl(socket_, error)) { return false; } + } +#endif + } + + // Mark the current socket as being in use so that it cannot be closed by + // anyone else while this request is ongoing, even though we will be + // releasing the mutex. + if (socket_requests_in_flight_ > 1) { + assert(socket_requests_are_from_thread_ == std::this_thread::get_id()); + } + socket_requests_in_flight_ += 1; + socket_requests_are_from_thread_ = std::this_thread::get_id(); + } + + for (const auto &header : default_headers_) { + if (req.headers.find(header.first) == req.headers.end()) { + req.headers.insert(header); + } + } + + auto close_connection = !keep_alive_; + auto ret = process_socket(socket_, [&](Stream &strm) { + return handle_request(strm, req, res, close_connection, error); + }); + + // Briefly lock mutex in order to mark that a request is no longer ongoing + { + std::lock_guard guard(socket_mutex_); + socket_requests_in_flight_ -= 1; + if (socket_requests_in_flight_ <= 0) { + assert(socket_requests_in_flight_ == 0); + socket_requests_are_from_thread_ = std::thread::id(); + } + + if (socket_should_be_closed_when_request_is_done_ || close_connection || + !ret) { + shutdown_ssl(socket_, true); + shutdown_socket(socket_); + close_socket(socket_); + } + } + + if (!ret) { + if (error == Error::Success) { error = Error::Unknown; } + } + + return ret; +} + +inline Result ClientImpl::send(const Request &req) { + auto req2 = req; + return send_(std::move(req2)); +} + +inline Result ClientImpl::send_(Request &&req) { + auto res = detail::make_unique(); + auto error = Error::Success; + auto ret = send(req, *res, error); + return Result{ret ? std::move(res) : nullptr, error, std::move(req.headers)}; +} + +inline bool ClientImpl::handle_request(Stream &strm, Request &req, + Response &res, bool close_connection, + Error &error) { + if (req.path.empty()) { + error = Error::Connection; + return false; + } + + auto req_save = req; + + bool ret; + + if (!is_ssl() && !proxy_host_.empty() && proxy_port_ != -1) { + auto req2 = req; + req2.path = "http://" + host_and_port_ + req.path; + ret = process_request(strm, req2, res, close_connection, error); + req = req2; + req.path = req_save.path; + } else { + ret = process_request(strm, req, res, close_connection, error); + } + + if (!ret) { return false; } + + if (300 < res.status && res.status < 400 && follow_location_) { + req = req_save; + ret = redirect(req, res, error); + } + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + if ((res.status == 401 || res.status == 407) && + req.authorization_count_ < 5) { + auto is_proxy = res.status == 407; + const auto &username = + is_proxy ? proxy_digest_auth_username_ : digest_auth_username_; + const auto &password = + is_proxy ? proxy_digest_auth_password_ : digest_auth_password_; + + if (!username.empty() && !password.empty()) { + std::map auth; + if (detail::parse_www_authenticate(res, auth, is_proxy)) { + Request new_req = req; + new_req.authorization_count_ += 1; + auto key = is_proxy ? "Proxy-Authorization" : "Authorization"; + new_req.headers.erase(key); + new_req.headers.insert(detail::make_digest_authentication_header( + req, auth, new_req.authorization_count_, detail::random_string(10), + username, password, is_proxy)); + + Response new_res; + + ret = send(new_req, new_res, error); + if (ret) { res = new_res; } + } + } + } +#endif + + return ret; +} + +inline bool ClientImpl::redirect(Request &req, Response &res, Error &error) { + if (req.redirect_count_ == 0) { + error = Error::ExceedRedirectCount; + return false; + } + + auto location = detail::decode_url(res.get_header_value("location"), true); + if (location.empty()) { return false; } + + const static std::regex re( + R"(^(?:(https?):)?(?://([^:/?#]*)(?::(\d+))?)?([^?#]*(?:\?[^#]*)?)(?:#.*)?)"); + + std::smatch m; + if (!std::regex_match(location, m, re)) { return false; } + + auto scheme = is_ssl() ? "https" : "http"; + + auto next_scheme = m[1].str(); + auto next_host = m[2].str(); + auto port_str = m[3].str(); + auto next_path = m[4].str(); + + auto next_port = port_; + if (!port_str.empty()) { + next_port = std::stoi(port_str); + } else if (!next_scheme.empty()) { + next_port = next_scheme == "https" ? 443 : 80; + } + + if (next_scheme.empty()) { next_scheme = scheme; } + if (next_host.empty()) { next_host = host_; } + if (next_path.empty()) { next_path = "/"; } + + if (next_scheme == scheme && next_host == host_ && next_port == port_) { + return detail::redirect(*this, req, res, next_path, location, error); + } else { + if (next_scheme == "https") { +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + SSLClient cli(next_host.c_str(), next_port); + cli.copy_settings(*this); + return detail::redirect(cli, req, res, next_path, location, error); +#else + return false; +#endif + } else { + ClientImpl cli(next_host.c_str(), next_port); + cli.copy_settings(*this); + return detail::redirect(cli, req, res, next_path, location, error); + } + } +} + +inline bool ClientImpl::write_content_with_provider(Stream &strm, + const Request &req, + Error &error) { + auto is_shutting_down = []() { return false; }; + + if (req.is_chunked_content_provider_) { + // TODO: Brotli suport + std::unique_ptr compressor; +#ifdef CPPHTTPLIB_ZLIB_SUPPORT + if (compress_) { + compressor = detail::make_unique(); + } else +#endif + { + compressor = detail::make_unique(); + } + + return detail::write_content_chunked(strm, req.content_provider_, + is_shutting_down, *compressor, error); + } else { + return detail::write_content(strm, req.content_provider_, 0, + req.content_length_, is_shutting_down, error); + } +} // namespace httplib + +inline bool ClientImpl::write_request(Stream &strm, Request &req, + bool close_connection, Error &error) { + // Prepare additional headers + if (close_connection) { req.headers.emplace("Connection", "close"); } + + if (!req.has_header("Host")) { + if (is_ssl()) { + if (port_ == 443) { + req.headers.emplace("Host", host_); + } else { + req.headers.emplace("Host", host_and_port_); + } + } else { + if (port_ == 80) { + req.headers.emplace("Host", host_); + } else { + req.headers.emplace("Host", host_and_port_); + } + } + } + + if (!req.has_header("Accept")) { req.headers.emplace("Accept", "*/*"); } + + if (!req.has_header("User-Agent")) { + req.headers.emplace("User-Agent", "cpp-httplib/0.7"); + } + + if (req.body.empty()) { + if (req.content_provider_) { + if (!req.is_chunked_content_provider_) { + auto length = std::to_string(req.content_length_); + req.headers.emplace("Content-Length", length); + } + } else { + if (req.method == "POST" || req.method == "PUT" || + req.method == "PATCH") { + req.headers.emplace("Content-Length", "0"); + } + } + } else { + if (!req.has_header("Content-Type")) { + req.headers.emplace("Content-Type", "text/plain"); + } + + if (!req.has_header("Content-Length")) { + auto length = std::to_string(req.body.size()); + req.headers.emplace("Content-Length", length); + } + } + + if (!basic_auth_password_.empty()) { + req.headers.insert(make_basic_authentication_header( + basic_auth_username_, basic_auth_password_, false)); + } + + if (!proxy_basic_auth_username_.empty() && + !proxy_basic_auth_password_.empty()) { + req.headers.insert(make_basic_authentication_header( + proxy_basic_auth_username_, proxy_basic_auth_password_, true)); + } + + if (!bearer_token_auth_token_.empty()) { + req.headers.insert(make_bearer_token_authentication_header( + bearer_token_auth_token_, false)); + } + + if (!proxy_bearer_token_auth_token_.empty()) { + req.headers.insert(make_bearer_token_authentication_header( + proxy_bearer_token_auth_token_, true)); + } + + // Request line and headers + { + detail::BufferStream bstrm; + + const auto &path = detail::encode_url(req.path); + bstrm.write_format("%s %s HTTP/1.1\r\n", req.method.c_str(), path.c_str()); + + detail::write_headers(bstrm, req.headers); + + // Flush buffer + auto &data = bstrm.get_buffer(); + if (!detail::write_data(strm, data.data(), data.size())) { + error = Error::Write; + return false; + } + } + + // Body + if (req.body.empty()) { + return write_content_with_provider(strm, req, error); + } else { + return detail::write_data(strm, req.body.data(), req.body.size()); + } + + return true; +} + +inline std::unique_ptr ClientImpl::send_with_content_provider( + Request &req, + // const char *method, const char *path, const Headers &headers, + const char *body, size_t content_length, ContentProvider content_provider, + ContentProviderWithoutLength content_provider_without_length, + const char *content_type, Error &error) { + + // Request req; + // req.method = method; + // req.headers = headers; + // req.path = path; + + if (content_type) { req.headers.emplace("Content-Type", content_type); } + +#ifdef CPPHTTPLIB_ZLIB_SUPPORT + if (compress_) { req.headers.emplace("Content-Encoding", "gzip"); } +#endif + +#ifdef CPPHTTPLIB_ZLIB_SUPPORT + if (compress_ && !content_provider_without_length) { + // TODO: Brotli support + detail::gzip_compressor compressor; + + if (content_provider) { + auto ok = true; + size_t offset = 0; + DataSink data_sink; + + data_sink.write = [&](const char *data, size_t data_len) { + if (ok) { + auto last = offset + data_len == content_length; + + auto ret = compressor.compress( + data, data_len, last, [&](const char *data, size_t data_len) { + req.body.append(data, data_len); + return true; + }); + + if (ret) { + offset += data_len; + } else { + ok = false; + } + } + }; + + data_sink.is_writable = [&](void) { return ok && true; }; + + while (ok && offset < content_length) { + if (!content_provider(offset, content_length - offset, data_sink)) { + error = Error::Canceled; + return nullptr; + } + } + } else { + if (!compressor.compress(body, content_length, true, + [&](const char *data, size_t data_len) { + req.body.append(data, data_len); + return true; + })) { + error = Error::Compression; + return nullptr; + } + } + } else +#endif + { + if (content_provider) { + req.content_length_ = content_length; + req.content_provider_ = std::move(content_provider); + req.is_chunked_content_provider_ = false; + } else if (content_provider_without_length) { + req.content_length_ = 0; + req.content_provider_ = detail::ContentProviderAdapter( + std::move(content_provider_without_length)); + req.is_chunked_content_provider_ = true; + req.headers.emplace("Transfer-Encoding", "chunked"); + } else { + req.body.assign(body, content_length); + ; + } + } + + auto res = detail::make_unique(); + return send(req, *res, error) ? std::move(res) : nullptr; +} + +inline Result ClientImpl::send_with_content_provider( + const char *method, const char *path, const Headers &headers, + const char *body, size_t content_length, ContentProvider content_provider, + ContentProviderWithoutLength content_provider_without_length, + const char *content_type) { + Request req; + req.method = method; + req.headers = headers; + req.path = path; + + auto error = Error::Success; + + auto res = send_with_content_provider( + req, + // method, path, headers, + body, content_length, std::move(content_provider), + std::move(content_provider_without_length), content_type, error); + + return Result{std::move(res), error, std::move(req.headers)}; +} + +inline bool ClientImpl::process_request(Stream &strm, Request &req, + Response &res, bool close_connection, + Error &error) { + // Send request + if (!write_request(strm, req, close_connection, error)) { return false; } + + // Receive response and headers + if (!read_response_line(strm, req, res) || + !detail::read_headers(strm, res.headers)) { + error = Error::Read; + return false; + } + + if (req.response_handler) { + if (!req.response_handler(res)) { + error = Error::Canceled; + return false; + } + } + + // Body + if ((res.status != 204) && req.method != "HEAD" && req.method != "CONNECT") { + auto out = + req.content_receiver + ? static_cast( + [&](const char *buf, size_t n, uint64_t off, uint64_t len) { + auto ret = req.content_receiver(buf, n, off, len); + if (!ret) { error = Error::Canceled; } + return ret; + }) + : static_cast( + [&](const char *buf, size_t n, uint64_t /*off*/, + uint64_t /*len*/) { + if (res.body.size() + n > res.body.max_size()) { + return false; + } + res.body.append(buf, n); + return true; + }); + + auto progress = [&](uint64_t current, uint64_t total) { + if (!req.progress) { return true; } + auto ret = req.progress(current, total); + if (!ret) { error = Error::Canceled; } + return ret; + }; + + int dummy_status; + if (!detail::read_content(strm, res, (std::numeric_limits::max)(), + dummy_status, std::move(progress), std::move(out), + decompress_)) { + if (error != Error::Canceled) { error = Error::Read; } + return false; + } + } + + if (res.get_header_value("Connection") == "close" || + (res.version == "HTTP/1.0" && res.reason != "Connection established")) { + // TODO this requires a not-entirely-obvious chain of calls to be correct + // for this to be safe. Maybe a code refactor (such as moving this out to + // the send function and getting rid of the recursiveness of the mutex) + // could make this more obvious. + + // This is safe to call because process_request is only called by + // handle_request which is only called by send, which locks the request + // mutex during the process. It would be a bug to call it from a different + // thread since it's a thread-safety issue to do these things to the socket + // if another thread is using the socket. + lock_socket_and_shutdown_and_close(); + } + + // Log + if (logger_) { logger_(req, res); } + + return true; +} + +inline bool +ClientImpl::process_socket(const Socket &socket, + std::function callback) { + return detail::process_client_socket( + socket.sock, read_timeout_sec_, read_timeout_usec_, write_timeout_sec_, + write_timeout_usec_, std::move(callback)); +} + +inline bool ClientImpl::is_ssl() const { return false; } + +inline Result ClientImpl::Get(const char *path) { + return Get(path, Headers(), Progress()); +} + +inline Result ClientImpl::Get(const char *path, Progress progress) { + return Get(path, Headers(), std::move(progress)); +} + +inline Result ClientImpl::Get(const char *path, const Headers &headers) { + return Get(path, headers, Progress()); +} + +inline Result ClientImpl::Get(const char *path, const Headers &headers, + Progress progress) { + Request req; + req.method = "GET"; + req.path = path; + req.headers = headers; + req.progress = std::move(progress); + + return send_(std::move(req)); +} + +inline Result ClientImpl::Get(const char *path, + ContentReceiver content_receiver) { + return Get(path, Headers(), nullptr, std::move(content_receiver), nullptr); +} + +inline Result ClientImpl::Get(const char *path, + ContentReceiver content_receiver, + Progress progress) { + return Get(path, Headers(), nullptr, std::move(content_receiver), + std::move(progress)); +} + +inline Result ClientImpl::Get(const char *path, const Headers &headers, + ContentReceiver content_receiver) { + return Get(path, headers, nullptr, std::move(content_receiver), nullptr); +} + +inline Result ClientImpl::Get(const char *path, const Headers &headers, + ContentReceiver content_receiver, + Progress progress) { + return Get(path, headers, nullptr, std::move(content_receiver), + std::move(progress)); +} + +inline Result ClientImpl::Get(const char *path, + ResponseHandler response_handler, + ContentReceiver content_receiver) { + return Get(path, Headers(), std::move(response_handler), + std::move(content_receiver), nullptr); +} + +inline Result ClientImpl::Get(const char *path, const Headers &headers, + ResponseHandler response_handler, + ContentReceiver content_receiver) { + return Get(path, headers, std::move(response_handler), + std::move(content_receiver), nullptr); +} + +inline Result ClientImpl::Get(const char *path, + ResponseHandler response_handler, + ContentReceiver content_receiver, + Progress progress) { + return Get(path, Headers(), std::move(response_handler), + std::move(content_receiver), std::move(progress)); +} + +inline Result ClientImpl::Get(const char *path, const Headers &headers, + ResponseHandler response_handler, + ContentReceiver content_receiver, + Progress progress) { + Request req; + req.method = "GET"; + req.path = path; + req.headers = headers; + req.response_handler = std::move(response_handler); + req.content_receiver = + [content_receiver](const char *data, size_t data_length, + uint64_t /*offset*/, uint64_t /*total_length*/) { + return content_receiver(data, data_length); + }; + req.progress = std::move(progress); + + return send_(std::move(req)); +} + +inline Result ClientImpl::Get(const char *path, const Params ¶ms, + const Headers &headers, Progress progress) { + if (params.empty()) { return Get(path, headers); } + + std::string path_with_query = detail::append_query_params(path, params); + return Get(path_with_query.c_str(), headers, progress); +} + +inline Result ClientImpl::Get(const char *path, const Params ¶ms, + const Headers &headers, + ContentReceiver content_receiver, + Progress progress) { + return Get(path, params, headers, nullptr, content_receiver, progress); +} + +inline Result ClientImpl::Get(const char *path, const Params ¶ms, + const Headers &headers, + ResponseHandler response_handler, + ContentReceiver content_receiver, + Progress progress) { + if (params.empty()) { + return Get(path, headers, response_handler, content_receiver, progress); + } + + std::string path_with_query = detail::append_query_params(path, params); + return Get(path_with_query.c_str(), params, headers, response_handler, + content_receiver, progress); +} + +inline Result ClientImpl::Head(const char *path) { + return Head(path, Headers()); +} + +inline Result ClientImpl::Head(const char *path, const Headers &headers) { + Request req; + req.method = "HEAD"; + req.headers = headers; + req.path = path; + + return send_(std::move(req)); +} + +inline Result ClientImpl::Post(const char *path) { + return Post(path, std::string(), nullptr); +} + +inline Result ClientImpl::Post(const char *path, const char *body, + size_t content_length, + const char *content_type) { + return Post(path, Headers(), body, content_length, content_type); +} + +inline Result ClientImpl::Post(const char *path, const Headers &headers, + const char *body, size_t content_length, + const char *content_type) { + return send_with_content_provider("POST", path, headers, body, content_length, + nullptr, nullptr, content_type); +} + +inline Result ClientImpl::Post(const char *path, const std::string &body, + const char *content_type) { + return Post(path, Headers(), body, content_type); +} + +inline Result ClientImpl::Post(const char *path, const Headers &headers, + const std::string &body, + const char *content_type) { + return send_with_content_provider("POST", path, headers, body.data(), + body.size(), nullptr, nullptr, + content_type); +} + +inline Result ClientImpl::Post(const char *path, const Params ¶ms) { + return Post(path, Headers(), params); +} + +inline Result ClientImpl::Post(const char *path, size_t content_length, + ContentProvider content_provider, + const char *content_type) { + return Post(path, Headers(), content_length, std::move(content_provider), + content_type); +} + +inline Result ClientImpl::Post(const char *path, + ContentProviderWithoutLength content_provider, + const char *content_type) { + return Post(path, Headers(), std::move(content_provider), content_type); +} + +inline Result ClientImpl::Post(const char *path, const Headers &headers, + size_t content_length, + ContentProvider content_provider, + const char *content_type) { + return send_with_content_provider("POST", path, headers, nullptr, + content_length, std::move(content_provider), + nullptr, content_type); +} + +inline Result ClientImpl::Post(const char *path, const Headers &headers, + ContentProviderWithoutLength content_provider, + const char *content_type) { + return send_with_content_provider("POST", path, headers, nullptr, 0, nullptr, + std::move(content_provider), content_type); +} + +inline Result ClientImpl::Post(const char *path, const Headers &headers, + const Params ¶ms) { + auto query = detail::params_to_query_str(params); + return Post(path, headers, query, "application/x-www-form-urlencoded"); +} + +inline Result ClientImpl::Post(const char *path, + const MultipartFormDataItems &items) { + return Post(path, Headers(), items); +} + +inline Result ClientImpl::Post(const char *path, const Headers &headers, + const MultipartFormDataItems &items) { + return Post(path, headers, items, detail::make_multipart_data_boundary()); +} +inline Result ClientImpl::Post(const char *path, const Headers &headers, + const MultipartFormDataItems &items, + const std::string &boundary) { + for (size_t i = 0; i < boundary.size(); i++) { + char c = boundary[i]; + if (!std::isalnum(c) && c != '-' && c != '_') { + return Result{nullptr, Error::UnsupportedMultipartBoundaryChars}; + } + } + + std::string body; + + for (const auto &item : items) { + body += "--" + boundary + "\r\n"; + body += "Content-Disposition: form-data; name=\"" + item.name + "\""; + if (!item.filename.empty()) { + body += "; filename=\"" + item.filename + "\""; + } + body += "\r\n"; + if (!item.content_type.empty()) { + body += "Content-Type: " + item.content_type + "\r\n"; + } + body += "\r\n"; + body += item.content + "\r\n"; + } + + body += "--" + boundary + "--\r\n"; + + std::string content_type = "multipart/form-data; boundary=" + boundary; + return Post(path, headers, body, content_type.c_str()); +} + +inline Result ClientImpl::Put(const char *path) { + return Put(path, std::string(), nullptr); +} + +inline Result ClientImpl::Put(const char *path, const char *body, + size_t content_length, const char *content_type) { + return Put(path, Headers(), body, content_length, content_type); +} + +inline Result ClientImpl::Put(const char *path, const Headers &headers, + const char *body, size_t content_length, + const char *content_type) { + return send_with_content_provider("PUT", path, headers, body, content_length, + nullptr, nullptr, content_type); +} + +inline Result ClientImpl::Put(const char *path, const std::string &body, + const char *content_type) { + return Put(path, Headers(), body, content_type); +} + +inline Result ClientImpl::Put(const char *path, const Headers &headers, + const std::string &body, + const char *content_type) { + return send_with_content_provider("PUT", path, headers, body.data(), + body.size(), nullptr, nullptr, + content_type); +} + +inline Result ClientImpl::Put(const char *path, size_t content_length, + ContentProvider content_provider, + const char *content_type) { + return Put(path, Headers(), content_length, std::move(content_provider), + content_type); +} + +inline Result ClientImpl::Put(const char *path, + ContentProviderWithoutLength content_provider, + const char *content_type) { + return Put(path, Headers(), std::move(content_provider), content_type); +} + +inline Result ClientImpl::Put(const char *path, const Headers &headers, + size_t content_length, + ContentProvider content_provider, + const char *content_type) { + return send_with_content_provider("PUT", path, headers, nullptr, + content_length, std::move(content_provider), + nullptr, content_type); +} + +inline Result ClientImpl::Put(const char *path, const Headers &headers, + ContentProviderWithoutLength content_provider, + const char *content_type) { + return send_with_content_provider("PUT", path, headers, nullptr, 0, nullptr, + std::move(content_provider), content_type); +} + +inline Result ClientImpl::Put(const char *path, const Params ¶ms) { + return Put(path, Headers(), params); +} + +inline Result ClientImpl::Put(const char *path, const Headers &headers, + const Params ¶ms) { + auto query = detail::params_to_query_str(params); + return Put(path, headers, query, "application/x-www-form-urlencoded"); +} + +inline Result ClientImpl::Patch(const char *path) { + return Patch(path, std::string(), nullptr); +} + +inline Result ClientImpl::Patch(const char *path, const char *body, + size_t content_length, + const char *content_type) { + return Patch(path, Headers(), body, content_length, content_type); +} + +inline Result ClientImpl::Patch(const char *path, const Headers &headers, + const char *body, size_t content_length, + const char *content_type) { + return send_with_content_provider("PATCH", path, headers, body, + content_length, nullptr, nullptr, + content_type); +} + +inline Result ClientImpl::Patch(const char *path, const std::string &body, + const char *content_type) { + return Patch(path, Headers(), body, content_type); +} + +inline Result ClientImpl::Patch(const char *path, const Headers &headers, + const std::string &body, + const char *content_type) { + return send_with_content_provider("PATCH", path, headers, body.data(), + body.size(), nullptr, nullptr, + content_type); +} + +inline Result ClientImpl::Patch(const char *path, size_t content_length, + ContentProvider content_provider, + const char *content_type) { + return Patch(path, Headers(), content_length, std::move(content_provider), + content_type); +} + +inline Result ClientImpl::Patch(const char *path, + ContentProviderWithoutLength content_provider, + const char *content_type) { + return Patch(path, Headers(), std::move(content_provider), content_type); +} + +inline Result ClientImpl::Patch(const char *path, const Headers &headers, + size_t content_length, + ContentProvider content_provider, + const char *content_type) { + return send_with_content_provider("PATCH", path, headers, nullptr, + content_length, std::move(content_provider), + nullptr, content_type); +} + +inline Result ClientImpl::Patch(const char *path, const Headers &headers, + ContentProviderWithoutLength content_provider, + const char *content_type) { + return send_with_content_provider("PATCH", path, headers, nullptr, 0, nullptr, + std::move(content_provider), content_type); +} + +inline Result ClientImpl::Delete(const char *path) { + return Delete(path, Headers(), std::string(), nullptr); +} + +inline Result ClientImpl::Delete(const char *path, const Headers &headers) { + return Delete(path, headers, std::string(), nullptr); +} + +inline Result ClientImpl::Delete(const char *path, const char *body, + size_t content_length, + const char *content_type) { + return Delete(path, Headers(), body, content_length, content_type); +} + +inline Result ClientImpl::Delete(const char *path, const Headers &headers, + const char *body, size_t content_length, + const char *content_type) { + Request req; + req.method = "DELETE"; + req.headers = headers; + req.path = path; + + if (content_type) { req.headers.emplace("Content-Type", content_type); } + req.body.assign(body, content_length); + + return send_(std::move(req)); +} + +inline Result ClientImpl::Delete(const char *path, const std::string &body, + const char *content_type) { + return Delete(path, Headers(), body.data(), body.size(), content_type); +} + +inline Result ClientImpl::Delete(const char *path, const Headers &headers, + const std::string &body, + const char *content_type) { + return Delete(path, headers, body.data(), body.size(), content_type); +} + +inline Result ClientImpl::Options(const char *path) { + return Options(path, Headers()); +} + +inline Result ClientImpl::Options(const char *path, const Headers &headers) { + Request req; + req.method = "OPTIONS"; + req.headers = headers; + req.path = path; + + return send_(std::move(req)); +} + +inline size_t ClientImpl::is_socket_open() const { + std::lock_guard guard(socket_mutex_); + return socket_.is_open(); +} + +inline void ClientImpl::stop() { + std::lock_guard guard(socket_mutex_); + + // If there is anything ongoing right now, the ONLY thread-safe thing we can + // do is to shutdown_socket, so that threads using this socket suddenly + // discover they can't read/write any more and error out. Everything else + // (closing the socket, shutting ssl down) is unsafe because these actions are + // not thread-safe. + if (socket_requests_in_flight_ > 0) { + shutdown_socket(socket_); + + // Aside from that, we set a flag for the socket to be closed when we're + // done. + socket_should_be_closed_when_request_is_done_ = true; + return; + } + + // Otherwise, sitll holding the mutex, we can shut everything down ourselves + shutdown_ssl(socket_, true); + shutdown_socket(socket_); + close_socket(socket_); +} + +inline void ClientImpl::set_connection_timeout(time_t sec, time_t usec) { + connection_timeout_sec_ = sec; + connection_timeout_usec_ = usec; +} + +inline void ClientImpl::set_read_timeout(time_t sec, time_t usec) { + read_timeout_sec_ = sec; + read_timeout_usec_ = usec; +} + +inline void ClientImpl::set_write_timeout(time_t sec, time_t usec) { + write_timeout_sec_ = sec; + write_timeout_usec_ = usec; +} + +inline void ClientImpl::set_basic_auth(const char *username, + const char *password) { + basic_auth_username_ = username; + basic_auth_password_ = password; +} + +inline void ClientImpl::set_bearer_token_auth(const char *token) { + bearer_token_auth_token_ = token; +} + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT +inline void ClientImpl::set_digest_auth(const char *username, + const char *password) { + digest_auth_username_ = username; + digest_auth_password_ = password; +} +#endif + +inline void ClientImpl::set_keep_alive(bool on) { keep_alive_ = on; } + +inline void ClientImpl::set_follow_location(bool on) { follow_location_ = on; } + +inline void ClientImpl::set_default_headers(Headers headers) { + default_headers_ = std::move(headers); +} + +inline void ClientImpl::set_tcp_nodelay(bool on) { tcp_nodelay_ = on; } + +inline void ClientImpl::set_socket_options(SocketOptions socket_options) { + socket_options_ = std::move(socket_options); +} + +inline void ClientImpl::set_compress(bool on) { compress_ = on; } + +inline void ClientImpl::set_decompress(bool on) { decompress_ = on; } + +inline void ClientImpl::set_interface(const char *intf) { interface_ = intf; } + +inline void ClientImpl::set_proxy(const char *host, int port) { + proxy_host_ = host; + proxy_port_ = port; +} + +inline void ClientImpl::set_proxy_basic_auth(const char *username, + const char *password) { + proxy_basic_auth_username_ = username; + proxy_basic_auth_password_ = password; +} + +inline void ClientImpl::set_proxy_bearer_token_auth(const char *token) { + proxy_bearer_token_auth_token_ = token; +} + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT +inline void ClientImpl::set_proxy_digest_auth(const char *username, + const char *password) { + proxy_digest_auth_username_ = username; + proxy_digest_auth_password_ = password; +} +#endif + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT +inline void ClientImpl::enable_server_certificate_verification(bool enabled) { + server_certificate_verification_ = enabled; +} +#endif + +inline void ClientImpl::set_logger(Logger logger) { + logger_ = std::move(logger); +} + +/* + * SSL Implementation + */ +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT +namespace detail { + +template +inline SSL *ssl_new(socket_t sock, SSL_CTX *ctx, std::mutex &ctx_mutex, + U SSL_connect_or_accept, V setup) { + SSL *ssl = nullptr; + { + std::lock_guard guard(ctx_mutex); + ssl = SSL_new(ctx); + } + + if (ssl) { + set_nonblocking(sock, true); + auto bio = BIO_new_socket(static_cast(sock), BIO_NOCLOSE); + BIO_set_nbio(bio, 1); + SSL_set_bio(ssl, bio, bio); + + if (!setup(ssl) || SSL_connect_or_accept(ssl) != 1) { + SSL_shutdown(ssl); + { + std::lock_guard guard(ctx_mutex); + SSL_free(ssl); + } + set_nonblocking(sock, false); + return nullptr; + } + BIO_set_nbio(bio, 0); + set_nonblocking(sock, false); + } + + return ssl; +} + +inline void ssl_delete(std::mutex &ctx_mutex, SSL *ssl, + bool shutdown_gracefully) { + // sometimes we may want to skip this to try to avoid SIGPIPE if we know + // the remote has closed the network connection + // Note that it is not always possible to avoid SIGPIPE, this is merely a + // best-efforts. + if (shutdown_gracefully) { SSL_shutdown(ssl); } + + std::lock_guard guard(ctx_mutex); + SSL_free(ssl); +} + +template +bool ssl_connect_or_accept_nonblocking(socket_t sock, SSL *ssl, + U ssl_connect_or_accept, + time_t timeout_sec, + time_t timeout_usec) { + int res = 0; + while ((res = ssl_connect_or_accept(ssl)) != 1) { + auto err = SSL_get_error(ssl, res); + switch (err) { + case SSL_ERROR_WANT_READ: + if (select_read(sock, timeout_sec, timeout_usec) > 0) { continue; } + break; + case SSL_ERROR_WANT_WRITE: + if (select_write(sock, timeout_sec, timeout_usec) > 0) { continue; } + break; + default: break; + } + return false; + } + return true; +} + +template +inline bool +process_server_socket_ssl(SSL *ssl, socket_t sock, size_t keep_alive_max_count, + time_t keep_alive_timeout_sec, + time_t read_timeout_sec, time_t read_timeout_usec, + time_t write_timeout_sec, time_t write_timeout_usec, + T callback) { + return process_server_socket_core( + sock, keep_alive_max_count, keep_alive_timeout_sec, + [&](bool close_connection, bool &connection_closed) { + SSLSocketStream strm(sock, ssl, read_timeout_sec, read_timeout_usec, + write_timeout_sec, write_timeout_usec); + return callback(strm, close_connection, connection_closed); + }); +} + +template +inline bool +process_client_socket_ssl(SSL *ssl, socket_t sock, time_t read_timeout_sec, + time_t read_timeout_usec, time_t write_timeout_sec, + time_t write_timeout_usec, T callback) { + SSLSocketStream strm(sock, ssl, read_timeout_sec, read_timeout_usec, + write_timeout_sec, write_timeout_usec); + return callback(strm); +} + +#if OPENSSL_VERSION_NUMBER < 0x10100000L +static std::shared_ptr> openSSL_locks_; + +class SSLThreadLocks { +public: + SSLThreadLocks() { + openSSL_locks_ = + std::make_shared>(CRYPTO_num_locks()); + CRYPTO_set_locking_callback(locking_callback); + } + + ~SSLThreadLocks() { CRYPTO_set_locking_callback(nullptr); } + +private: + static void locking_callback(int mode, int type, const char * /*file*/, + int /*line*/) { + auto &lk = (*openSSL_locks_)[static_cast(type)]; + if (mode & CRYPTO_LOCK) { + lk.lock(); + } else { + lk.unlock(); + } + } +}; + +#endif + +class SSLInit { +public: + SSLInit() { +#if OPENSSL_VERSION_NUMBER < 0x1010001fL + SSL_load_error_strings(); + SSL_library_init(); +#else + OPENSSL_init_ssl( + OPENSSL_INIT_LOAD_SSL_STRINGS | OPENSSL_INIT_LOAD_CRYPTO_STRINGS, NULL); +#endif + } + + ~SSLInit() { +#if OPENSSL_VERSION_NUMBER < 0x1010001fL + ERR_free_strings(); +#endif + } + +private: +#if OPENSSL_VERSION_NUMBER < 0x10100000L + SSLThreadLocks thread_init_; +#endif +}; + +// SSL socket stream implementation +inline SSLSocketStream::SSLSocketStream(socket_t sock, SSL *ssl, + time_t read_timeout_sec, + time_t read_timeout_usec, + time_t write_timeout_sec, + time_t write_timeout_usec) + : sock_(sock), ssl_(ssl), read_timeout_sec_(read_timeout_sec), + read_timeout_usec_(read_timeout_usec), + write_timeout_sec_(write_timeout_sec), + write_timeout_usec_(write_timeout_usec) { + SSL_clear_mode(ssl, SSL_MODE_AUTO_RETRY); +} + +inline SSLSocketStream::~SSLSocketStream() {} + +inline bool SSLSocketStream::is_readable() const { + return detail::select_read(sock_, read_timeout_sec_, read_timeout_usec_) > 0; +} + +inline bool SSLSocketStream::is_writable() const { + return detail::select_write(sock_, write_timeout_sec_, write_timeout_usec_) > + 0; +} + +inline ssize_t SSLSocketStream::read(char *ptr, size_t size) { + if (SSL_pending(ssl_) > 0) { + return SSL_read(ssl_, ptr, static_cast(size)); + } else if (is_readable()) { + auto ret = SSL_read(ssl_, ptr, static_cast(size)); + if (ret < 0) { + auto err = SSL_get_error(ssl_, ret); + while (err == SSL_ERROR_WANT_READ) { + if (SSL_pending(ssl_) > 0) { + return SSL_read(ssl_, ptr, static_cast(size)); + } else if (is_readable()) { + ret = SSL_read(ssl_, ptr, static_cast(size)); + if (ret >= 0) { return ret; } + err = SSL_get_error(ssl_, ret); + } else { + return -1; + } + } + } + return ret; + } + return -1; +} + +inline ssize_t SSLSocketStream::write(const char *ptr, size_t size) { + if (is_writable()) { return SSL_write(ssl_, ptr, static_cast(size)); } + return -1; +} + +inline void SSLSocketStream::get_remote_ip_and_port(std::string &ip, + int &port) const { + detail::get_remote_ip_and_port(sock_, ip, port); +} + +inline socket_t SSLSocketStream::socket() const { return sock_; } + +static SSLInit sslinit_; + +} // namespace detail + +// SSL HTTP server implementation +inline SSLServer::SSLServer(const char *cert_path, const char *private_key_path, + const char *client_ca_cert_file_path, + const char *client_ca_cert_dir_path) { + ctx_ = SSL_CTX_new(SSLv23_server_method()); + + if (ctx_) { + SSL_CTX_set_options(ctx_, + SSL_OP_ALL | SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 | + SSL_OP_NO_COMPRESSION | + SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION); + + // auto ecdh = EC_KEY_new_by_curve_name(NID_X9_62_prime256v1); + // SSL_CTX_set_tmp_ecdh(ctx_, ecdh); + // EC_KEY_free(ecdh); + + if (SSL_CTX_use_certificate_chain_file(ctx_, cert_path) != 1 || + SSL_CTX_use_PrivateKey_file(ctx_, private_key_path, SSL_FILETYPE_PEM) != + 1) { + SSL_CTX_free(ctx_); + ctx_ = nullptr; + } else if (client_ca_cert_file_path || client_ca_cert_dir_path) { + // if (client_ca_cert_file_path) { + // auto list = SSL_load_client_CA_file(client_ca_cert_file_path); + // SSL_CTX_set_client_CA_list(ctx_, list); + // } + + SSL_CTX_load_verify_locations(ctx_, client_ca_cert_file_path, + client_ca_cert_dir_path); + + SSL_CTX_set_verify( + ctx_, + SSL_VERIFY_PEER | + SSL_VERIFY_FAIL_IF_NO_PEER_CERT, // SSL_VERIFY_CLIENT_ONCE, + nullptr); + } + } +} + +inline SSLServer::SSLServer(X509 *cert, EVP_PKEY *private_key, + X509_STORE *client_ca_cert_store) { + ctx_ = SSL_CTX_new(SSLv23_server_method()); + + if (ctx_) { + SSL_CTX_set_options(ctx_, + SSL_OP_ALL | SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 | + SSL_OP_NO_COMPRESSION | + SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION); + + if (SSL_CTX_use_certificate(ctx_, cert) != 1 || + SSL_CTX_use_PrivateKey(ctx_, private_key) != 1) { + SSL_CTX_free(ctx_); + ctx_ = nullptr; + } else if (client_ca_cert_store) { + + SSL_CTX_set_cert_store(ctx_, client_ca_cert_store); + + SSL_CTX_set_verify( + ctx_, + SSL_VERIFY_PEER | + SSL_VERIFY_FAIL_IF_NO_PEER_CERT, // SSL_VERIFY_CLIENT_ONCE, + nullptr); + } + } +} + +inline SSLServer::~SSLServer() { + if (ctx_) { SSL_CTX_free(ctx_); } +} + +inline bool SSLServer::is_valid() const { return ctx_; } + +inline bool SSLServer::process_and_close_socket(socket_t sock) { + auto ssl = detail::ssl_new( + sock, ctx_, ctx_mutex_, + [&](SSL *ssl) { + return detail::ssl_connect_or_accept_nonblocking( + sock, ssl, SSL_accept, read_timeout_sec_, read_timeout_usec_); + }, + [](SSL * /*ssl*/) { return true; }); + + bool ret = false; + if (ssl) { + ret = detail::process_server_socket_ssl( + ssl, sock, keep_alive_max_count_, keep_alive_timeout_sec_, + read_timeout_sec_, read_timeout_usec_, write_timeout_sec_, + write_timeout_usec_, + [this, ssl](Stream &strm, bool close_connection, + bool &connection_closed) { + return process_request(strm, close_connection, connection_closed, + [&](Request &req) { req.ssl = ssl; }); + }); + + // Shutdown gracefully if the result seemed successful, non-gracefully if + // the connection appeared to be closed. + const bool shutdown_gracefully = ret; + detail::ssl_delete(ctx_mutex_, ssl, shutdown_gracefully); + } + + detail::shutdown_socket(sock); + detail::close_socket(sock); + return ret; +} + +// SSL HTTP client implementation +inline SSLClient::SSLClient(const std::string &host) + : SSLClient(host, 443, std::string(), std::string()) {} + +inline SSLClient::SSLClient(const std::string &host, int port) + : SSLClient(host, port, std::string(), std::string()) {} + +inline SSLClient::SSLClient(const std::string &host, int port, + const std::string &client_cert_path, + const std::string &client_key_path) + : ClientImpl(host, port, client_cert_path, client_key_path) { + ctx_ = SSL_CTX_new(SSLv23_client_method()); + + detail::split(&host_[0], &host_[host_.size()], '.', + [&](const char *b, const char *e) { + host_components_.emplace_back(std::string(b, e)); + }); + if (!client_cert_path.empty() && !client_key_path.empty()) { + if (SSL_CTX_use_certificate_file(ctx_, client_cert_path.c_str(), + SSL_FILETYPE_PEM) != 1 || + SSL_CTX_use_PrivateKey_file(ctx_, client_key_path.c_str(), + SSL_FILETYPE_PEM) != 1) { + SSL_CTX_free(ctx_); + ctx_ = nullptr; + } + } +} + +inline SSLClient::SSLClient(const std::string &host, int port, + X509 *client_cert, EVP_PKEY *client_key) + : ClientImpl(host, port) { + ctx_ = SSL_CTX_new(SSLv23_client_method()); + + detail::split(&host_[0], &host_[host_.size()], '.', + [&](const char *b, const char *e) { + host_components_.emplace_back(std::string(b, e)); + }); + if (client_cert != nullptr && client_key != nullptr) { + if (SSL_CTX_use_certificate(ctx_, client_cert) != 1 || + SSL_CTX_use_PrivateKey(ctx_, client_key) != 1) { + SSL_CTX_free(ctx_); + ctx_ = nullptr; + } + } +} + +inline SSLClient::~SSLClient() { + if (ctx_) { SSL_CTX_free(ctx_); } + // Make sure to shut down SSL since shutdown_ssl will resolve to the + // base function rather than the derived function once we get to the + // base class destructor, and won't free the SSL (causing a leak). + SSLClient::shutdown_ssl(socket_, true); +} + +inline bool SSLClient::is_valid() const { return ctx_; } + +inline void SSLClient::set_ca_cert_path(const char *ca_cert_file_path, + const char *ca_cert_dir_path) { + if (ca_cert_file_path) { ca_cert_file_path_ = ca_cert_file_path; } + if (ca_cert_dir_path) { ca_cert_dir_path_ = ca_cert_dir_path; } +} + +inline void SSLClient::set_ca_cert_store(X509_STORE *ca_cert_store) { + if (ca_cert_store) { + if (ctx_) { + if (SSL_CTX_get_cert_store(ctx_) != ca_cert_store) { + // Free memory allocated for old cert and use new store `ca_cert_store` + SSL_CTX_set_cert_store(ctx_, ca_cert_store); + } + } else { + X509_STORE_free(ca_cert_store); + } + } +} + +inline long SSLClient::get_openssl_verify_result() const { + return verify_result_; +} + +inline SSL_CTX *SSLClient::ssl_context() const { return ctx_; } + +inline bool SSLClient::create_and_connect_socket(Socket &socket, Error &error) { + return is_valid() && ClientImpl::create_and_connect_socket(socket, error); +} + +// Assumes that socket_mutex_ is locked and that there are no requests in flight +inline bool SSLClient::connect_with_proxy(Socket &socket, Response &res, + bool &success, Error &error) { + success = true; + Response res2; + if (!detail::process_client_socket( + socket.sock, read_timeout_sec_, read_timeout_usec_, + write_timeout_sec_, write_timeout_usec_, [&](Stream &strm) { + Request req2; + req2.method = "CONNECT"; + req2.path = host_and_port_; + return process_request(strm, req2, res2, false, error); + })) { + // Thread-safe to close everything because we are assuming there are no + // requests in flight + shutdown_ssl(socket, true); + shutdown_socket(socket); + close_socket(socket); + success = false; + return false; + } + + if (res2.status == 407) { + if (!proxy_digest_auth_username_.empty() && + !proxy_digest_auth_password_.empty()) { + std::map auth; + if (detail::parse_www_authenticate(res2, auth, true)) { + Response res3; + if (!detail::process_client_socket( + socket.sock, read_timeout_sec_, read_timeout_usec_, + write_timeout_sec_, write_timeout_usec_, [&](Stream &strm) { + Request req3; + req3.method = "CONNECT"; + req3.path = host_and_port_; + req3.headers.insert(detail::make_digest_authentication_header( + req3, auth, 1, detail::random_string(10), + proxy_digest_auth_username_, proxy_digest_auth_password_, + true)); + return process_request(strm, req3, res3, false, error); + })) { + // Thread-safe to close everything because we are assuming there are + // no requests in flight + shutdown_ssl(socket, true); + shutdown_socket(socket); + close_socket(socket); + success = false; + return false; + } + } + } else { + res = res2; + return false; + } + } + + return true; +} + +inline bool SSLClient::load_certs() { + bool ret = true; + + std::call_once(initialize_cert_, [&]() { + std::lock_guard guard(ctx_mutex_); + if (!ca_cert_file_path_.empty()) { + if (!SSL_CTX_load_verify_locations(ctx_, ca_cert_file_path_.c_str(), + nullptr)) { + ret = false; + } + } else if (!ca_cert_dir_path_.empty()) { + if (!SSL_CTX_load_verify_locations(ctx_, nullptr, + ca_cert_dir_path_.c_str())) { + ret = false; + } + } else { +#ifdef _WIN32 + detail::load_system_certs_on_windows(SSL_CTX_get_cert_store(ctx_)); +#else + SSL_CTX_set_default_verify_paths(ctx_); +#endif + } + }); + + return ret; +} + +inline bool SSLClient::initialize_ssl(Socket &socket, Error &error) { + auto ssl = detail::ssl_new( + socket.sock, ctx_, ctx_mutex_, + [&](SSL *ssl) { + if (server_certificate_verification_) { + if (!load_certs()) { + error = Error::SSLLoadingCerts; + return false; + } + SSL_set_verify(ssl, SSL_VERIFY_NONE, nullptr); + } + + if (!detail::ssl_connect_or_accept_nonblocking( + socket.sock, ssl, SSL_connect, connection_timeout_sec_, + connection_timeout_usec_)) { + error = Error::SSLConnection; + return false; + } + + if (server_certificate_verification_) { + verify_result_ = SSL_get_verify_result(ssl); + + if (verify_result_ != X509_V_OK) { + error = Error::SSLServerVerification; + return false; + } + + auto server_cert = SSL_get_peer_certificate(ssl); + + if (server_cert == nullptr) { + error = Error::SSLServerVerification; + return false; + } + + if (!verify_host(server_cert)) { + X509_free(server_cert); + error = Error::SSLServerVerification; + return false; + } + X509_free(server_cert); + } + + return true; + }, + [&](SSL *ssl) { + SSL_set_tlsext_host_name(ssl, host_.c_str()); + return true; + }); + + if (ssl) { + socket.ssl = ssl; + return true; + } + + shutdown_socket(socket); + close_socket(socket); + return false; +} + +inline void SSLClient::shutdown_ssl(Socket &socket, bool shutdown_gracefully) { + if (socket.sock == INVALID_SOCKET) { + assert(socket.ssl == nullptr); + return; + } + if (socket.ssl) { + detail::ssl_delete(ctx_mutex_, socket.ssl, shutdown_gracefully); + socket.ssl = nullptr; + } + assert(socket.ssl == nullptr); +} + +inline bool +SSLClient::process_socket(const Socket &socket, + std::function callback) { + assert(socket.ssl); + return detail::process_client_socket_ssl( + socket.ssl, socket.sock, read_timeout_sec_, read_timeout_usec_, + write_timeout_sec_, write_timeout_usec_, std::move(callback)); +} + +inline bool SSLClient::is_ssl() const { return true; } + +inline bool SSLClient::verify_host(X509 *server_cert) const { + /* Quote from RFC2818 section 3.1 "Server Identity" + + If a subjectAltName extension of type dNSName is present, that MUST + be used as the identity. Otherwise, the (most specific) Common Name + field in the Subject field of the certificate MUST be used. Although + the use of the Common Name is existing practice, it is deprecated and + Certification Authorities are encouraged to use the dNSName instead. + + Matching is performed using the matching rules specified by + [RFC2459]. If more than one identity of a given type is present in + the certificate (e.g., more than one dNSName name, a match in any one + of the set is considered acceptable.) Names may contain the wildcard + character * which is considered to match any single domain name + component or component fragment. E.g., *.a.com matches foo.a.com but + not bar.foo.a.com. f*.com matches foo.com but not bar.com. + + In some cases, the URI is specified as an IP address rather than a + hostname. In this case, the iPAddress subjectAltName must be present + in the certificate and must exactly match the IP in the URI. + + */ + return verify_host_with_subject_alt_name(server_cert) || + verify_host_with_common_name(server_cert); +} + +inline bool +SSLClient::verify_host_with_subject_alt_name(X509 *server_cert) const { + auto ret = false; + + auto type = GEN_DNS; + + struct in6_addr addr6; + struct in_addr addr; + size_t addr_len = 0; + +#ifndef __MINGW32__ + if (inet_pton(AF_INET6, host_.c_str(), &addr6)) { + type = GEN_IPADD; + addr_len = sizeof(struct in6_addr); + } else if (inet_pton(AF_INET, host_.c_str(), &addr)) { + type = GEN_IPADD; + addr_len = sizeof(struct in_addr); + } +#endif + + auto alt_names = static_cast( + X509_get_ext_d2i(server_cert, NID_subject_alt_name, nullptr, nullptr)); + + if (alt_names) { + auto dsn_matched = false; + auto ip_mached = false; + + auto count = sk_GENERAL_NAME_num(alt_names); + + for (decltype(count) i = 0; i < count && !dsn_matched; i++) { + auto val = sk_GENERAL_NAME_value(alt_names, i); + if (val->type == type) { + auto name = (const char *)ASN1_STRING_get0_data(val->d.ia5); + auto name_len = (size_t)ASN1_STRING_length(val->d.ia5); + + switch (type) { + case GEN_DNS: dsn_matched = check_host_name(name, name_len); break; + + case GEN_IPADD: + if (!memcmp(&addr6, name, addr_len) || + !memcmp(&addr, name, addr_len)) { + ip_mached = true; + } + break; + } + } + } + + if (dsn_matched || ip_mached) { ret = true; } + } + + GENERAL_NAMES_free((STACK_OF(GENERAL_NAME) *)alt_names); + return ret; +} + +inline bool SSLClient::verify_host_with_common_name(X509 *server_cert) const { + const auto subject_name = X509_get_subject_name(server_cert); + + if (subject_name != nullptr) { + char name[BUFSIZ]; + auto name_len = X509_NAME_get_text_by_NID(subject_name, NID_commonName, + name, sizeof(name)); + + if (name_len != -1) { + return check_host_name(name, static_cast(name_len)); + } + } + + return false; +} + +inline bool SSLClient::check_host_name(const char *pattern, + size_t pattern_len) const { + if (host_.size() == pattern_len && host_ == pattern) { return true; } + + // Wildcard match + // https://bugs.launchpad.net/ubuntu/+source/firefox-3.0/+bug/376484 + std::vector pattern_components; + detail::split(&pattern[0], &pattern[pattern_len], '.', + [&](const char *b, const char *e) { + pattern_components.emplace_back(std::string(b, e)); + }); + + if (host_components_.size() != pattern_components.size()) { return false; } + + auto itr = pattern_components.begin(); + for (const auto &h : host_components_) { + auto &p = *itr; + if (p != h && p != "*") { + auto partial_match = (p.size() > 0 && p[p.size() - 1] == '*' && + !p.compare(0, p.size() - 1, h)); + if (!partial_match) { return false; } + } + ++itr; + } + + return true; +} +#endif + +// Universal client implementation +inline Client::Client(const char *scheme_host_port) + : Client(scheme_host_port, std::string(), std::string()) {} + +inline Client::Client(const char *scheme_host_port, + const std::string &client_cert_path, + const std::string &client_key_path) { + const static std::regex re(R"(^(?:([a-z]+)://)?([^:/?#]+)(?::(\d+))?)"); + + std::cmatch m; + if (std::regex_match(scheme_host_port, m, re)) { + auto scheme = m[1].str(); + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + if (!scheme.empty() && (scheme != "http" && scheme != "https")) { +#else + if (!scheme.empty() && scheme != "http") { +#endif + std::string msg = "'" + scheme + "' scheme is not supported."; + throw std::invalid_argument(msg); + return; + } + + auto is_ssl = scheme == "https"; + + auto host = m[2].str(); + + auto port_str = m[3].str(); + auto port = !port_str.empty() ? std::stoi(port_str) : (is_ssl ? 443 : 80); + + if (is_ssl) { +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + cli_ = detail::make_unique(host.c_str(), port, + client_cert_path, client_key_path); + is_ssl_ = is_ssl; +#endif + } else { + cli_ = detail::make_unique(host.c_str(), port, + client_cert_path, client_key_path); + } + } else { + cli_ = detail::make_unique(scheme_host_port, 80, + client_cert_path, client_key_path); + } +} + +inline Client::Client(const std::string &host, int port) + : cli_(detail::make_unique(host, port)) {} + +inline Client::Client(const std::string &host, int port, + const std::string &client_cert_path, + const std::string &client_key_path) + : cli_(detail::make_unique(host, port, client_cert_path, + client_key_path)) {} + +inline Client::~Client() {} + +inline bool Client::is_valid() const { + return cli_ != nullptr && cli_->is_valid(); +} + +inline Result Client::Get(const char *path) { return cli_->Get(path); } +inline Result Client::Get(const char *path, const Headers &headers) { + return cli_->Get(path, headers); +} +inline Result Client::Get(const char *path, Progress progress) { + return cli_->Get(path, std::move(progress)); +} +inline Result Client::Get(const char *path, const Headers &headers, + Progress progress) { + return cli_->Get(path, headers, std::move(progress)); +} +inline Result Client::Get(const char *path, ContentReceiver content_receiver) { + return cli_->Get(path, std::move(content_receiver)); +} +inline Result Client::Get(const char *path, const Headers &headers, + ContentReceiver content_receiver) { + return cli_->Get(path, headers, std::move(content_receiver)); +} +inline Result Client::Get(const char *path, ContentReceiver content_receiver, + Progress progress) { + return cli_->Get(path, std::move(content_receiver), std::move(progress)); +} +inline Result Client::Get(const char *path, const Headers &headers, + ContentReceiver content_receiver, Progress progress) { + return cli_->Get(path, headers, std::move(content_receiver), + std::move(progress)); +} +inline Result Client::Get(const char *path, ResponseHandler response_handler, + ContentReceiver content_receiver) { + return cli_->Get(path, std::move(response_handler), + std::move(content_receiver)); +} +inline Result Client::Get(const char *path, const Headers &headers, + ResponseHandler response_handler, + ContentReceiver content_receiver) { + return cli_->Get(path, headers, std::move(response_handler), + std::move(content_receiver)); +} +inline Result Client::Get(const char *path, ResponseHandler response_handler, + ContentReceiver content_receiver, Progress progress) { + return cli_->Get(path, std::move(response_handler), + std::move(content_receiver), std::move(progress)); +} +inline Result Client::Get(const char *path, const Headers &headers, + ResponseHandler response_handler, + ContentReceiver content_receiver, Progress progress) { + return cli_->Get(path, headers, std::move(response_handler), + std::move(content_receiver), std::move(progress)); +} +inline Result Client::Get(const char *path, const Params ¶ms, + const Headers &headers, Progress progress) { + return cli_->Get(path, params, headers, progress); +} +inline Result Client::Get(const char *path, const Params ¶ms, + const Headers &headers, + ContentReceiver content_receiver, Progress progress) { + return cli_->Get(path, params, headers, content_receiver, progress); +} +inline Result Client::Get(const char *path, const Params ¶ms, + const Headers &headers, + ResponseHandler response_handler, + ContentReceiver content_receiver, Progress progress) { + return cli_->Get(path, params, headers, response_handler, content_receiver, + progress); +} + +inline Result Client::Head(const char *path) { return cli_->Head(path); } +inline Result Client::Head(const char *path, const Headers &headers) { + return cli_->Head(path, headers); +} + +inline Result Client::Post(const char *path) { return cli_->Post(path); } +inline Result Client::Post(const char *path, const char *body, + size_t content_length, const char *content_type) { + return cli_->Post(path, body, content_length, content_type); +} +inline Result Client::Post(const char *path, const Headers &headers, + const char *body, size_t content_length, + const char *content_type) { + return cli_->Post(path, headers, body, content_length, content_type); +} +inline Result Client::Post(const char *path, const std::string &body, + const char *content_type) { + return cli_->Post(path, body, content_type); +} +inline Result Client::Post(const char *path, const Headers &headers, + const std::string &body, const char *content_type) { + return cli_->Post(path, headers, body, content_type); +} +inline Result Client::Post(const char *path, size_t content_length, + ContentProvider content_provider, + const char *content_type) { + return cli_->Post(path, content_length, std::move(content_provider), + content_type); +} +inline Result Client::Post(const char *path, + ContentProviderWithoutLength content_provider, + const char *content_type) { + return cli_->Post(path, std::move(content_provider), content_type); +} +inline Result Client::Post(const char *path, const Headers &headers, + size_t content_length, + ContentProvider content_provider, + const char *content_type) { + return cli_->Post(path, headers, content_length, std::move(content_provider), + content_type); +} +inline Result Client::Post(const char *path, const Headers &headers, + ContentProviderWithoutLength content_provider, + const char *content_type) { + return cli_->Post(path, headers, std::move(content_provider), content_type); +} +inline Result Client::Post(const char *path, const Params ¶ms) { + return cli_->Post(path, params); +} +inline Result Client::Post(const char *path, const Headers &headers, + const Params ¶ms) { + return cli_->Post(path, headers, params); +} +inline Result Client::Post(const char *path, + const MultipartFormDataItems &items) { + return cli_->Post(path, items); +} +inline Result Client::Post(const char *path, const Headers &headers, + const MultipartFormDataItems &items) { + return cli_->Post(path, headers, items); +} +inline Result Client::Post(const char *path, const Headers &headers, + const MultipartFormDataItems &items, + const std::string &boundary) { + return cli_->Post(path, headers, items, boundary); +} +inline Result Client::Put(const char *path) { return cli_->Put(path); } +inline Result Client::Put(const char *path, const char *body, + size_t content_length, const char *content_type) { + return cli_->Put(path, body, content_length, content_type); +} +inline Result Client::Put(const char *path, const Headers &headers, + const char *body, size_t content_length, + const char *content_type) { + return cli_->Put(path, headers, body, content_length, content_type); +} +inline Result Client::Put(const char *path, const std::string &body, + const char *content_type) { + return cli_->Put(path, body, content_type); +} +inline Result Client::Put(const char *path, const Headers &headers, + const std::string &body, const char *content_type) { + return cli_->Put(path, headers, body, content_type); +} +inline Result Client::Put(const char *path, size_t content_length, + ContentProvider content_provider, + const char *content_type) { + return cli_->Put(path, content_length, std::move(content_provider), + content_type); +} +inline Result Client::Put(const char *path, + ContentProviderWithoutLength content_provider, + const char *content_type) { + return cli_->Put(path, std::move(content_provider), content_type); +} +inline Result Client::Put(const char *path, const Headers &headers, + size_t content_length, + ContentProvider content_provider, + const char *content_type) { + return cli_->Put(path, headers, content_length, std::move(content_provider), + content_type); +} +inline Result Client::Put(const char *path, const Headers &headers, + ContentProviderWithoutLength content_provider, + const char *content_type) { + return cli_->Put(path, headers, std::move(content_provider), content_type); +} +inline Result Client::Put(const char *path, const Params ¶ms) { + return cli_->Put(path, params); +} +inline Result Client::Put(const char *path, const Headers &headers, + const Params ¶ms) { + return cli_->Put(path, headers, params); +} +inline Result Client::Patch(const char *path) { return cli_->Patch(path); } +inline Result Client::Patch(const char *path, const char *body, + size_t content_length, const char *content_type) { + return cli_->Patch(path, body, content_length, content_type); +} +inline Result Client::Patch(const char *path, const Headers &headers, + const char *body, size_t content_length, + const char *content_type) { + return cli_->Patch(path, headers, body, content_length, content_type); +} +inline Result Client::Patch(const char *path, const std::string &body, + const char *content_type) { + return cli_->Patch(path, body, content_type); +} +inline Result Client::Patch(const char *path, const Headers &headers, + const std::string &body, const char *content_type) { + return cli_->Patch(path, headers, body, content_type); +} +inline Result Client::Patch(const char *path, size_t content_length, + ContentProvider content_provider, + const char *content_type) { + return cli_->Patch(path, content_length, std::move(content_provider), + content_type); +} +inline Result Client::Patch(const char *path, + ContentProviderWithoutLength content_provider, + const char *content_type) { + return cli_->Patch(path, std::move(content_provider), content_type); +} +inline Result Client::Patch(const char *path, const Headers &headers, + size_t content_length, + ContentProvider content_provider, + const char *content_type) { + return cli_->Patch(path, headers, content_length, std::move(content_provider), + content_type); +} +inline Result Client::Patch(const char *path, const Headers &headers, + ContentProviderWithoutLength content_provider, + const char *content_type) { + return cli_->Patch(path, headers, std::move(content_provider), content_type); +} +inline Result Client::Delete(const char *path) { return cli_->Delete(path); } +inline Result Client::Delete(const char *path, const Headers &headers) { + return cli_->Delete(path, headers); +} +inline Result Client::Delete(const char *path, const char *body, + size_t content_length, const char *content_type) { + return cli_->Delete(path, body, content_length, content_type); +} +inline Result Client::Delete(const char *path, const Headers &headers, + const char *body, size_t content_length, + const char *content_type) { + return cli_->Delete(path, headers, body, content_length, content_type); +} +inline Result Client::Delete(const char *path, const std::string &body, + const char *content_type) { + return cli_->Delete(path, body, content_type); +} +inline Result Client::Delete(const char *path, const Headers &headers, + const std::string &body, + const char *content_type) { + return cli_->Delete(path, headers, body, content_type); +} +inline Result Client::Options(const char *path) { return cli_->Options(path); } +inline Result Client::Options(const char *path, const Headers &headers) { + return cli_->Options(path, headers); +} + +inline bool Client::send(Request &req, Response &res, Error &error) { + return cli_->send(req, res, error); +} + +inline Result Client::send(const Request &req) { return cli_->send(req); } + +inline size_t Client::is_socket_open() const { return cli_->is_socket_open(); } + +inline void Client::stop() { cli_->stop(); } + +inline void Client::set_default_headers(Headers headers) { + cli_->set_default_headers(std::move(headers)); +} + +inline void Client::set_tcp_nodelay(bool on) { cli_->set_tcp_nodelay(on); } +inline void Client::set_socket_options(SocketOptions socket_options) { + cli_->set_socket_options(std::move(socket_options)); +} + +inline void Client::set_connection_timeout(time_t sec, time_t usec) { + cli_->set_connection_timeout(sec, usec); +} +inline void Client::set_read_timeout(time_t sec, time_t usec) { + cli_->set_read_timeout(sec, usec); +} +inline void Client::set_write_timeout(time_t sec, time_t usec) { + cli_->set_write_timeout(sec, usec); +} + +inline void Client::set_basic_auth(const char *username, const char *password) { + cli_->set_basic_auth(username, password); +} +inline void Client::set_bearer_token_auth(const char *token) { + cli_->set_bearer_token_auth(token); +} +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT +inline void Client::set_digest_auth(const char *username, + const char *password) { + cli_->set_digest_auth(username, password); +} +#endif + +inline void Client::set_keep_alive(bool on) { cli_->set_keep_alive(on); } +inline void Client::set_follow_location(bool on) { + cli_->set_follow_location(on); +} + +inline void Client::set_compress(bool on) { cli_->set_compress(on); } + +inline void Client::set_decompress(bool on) { cli_->set_decompress(on); } + +inline void Client::set_interface(const char *intf) { + cli_->set_interface(intf); +} + +inline void Client::set_proxy(const char *host, int port) { + cli_->set_proxy(host, port); +} +inline void Client::set_proxy_basic_auth(const char *username, + const char *password) { + cli_->set_proxy_basic_auth(username, password); +} +inline void Client::set_proxy_bearer_token_auth(const char *token) { + cli_->set_proxy_bearer_token_auth(token); +} +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT +inline void Client::set_proxy_digest_auth(const char *username, + const char *password) { + cli_->set_proxy_digest_auth(username, password); +} +#endif + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT +inline void Client::enable_server_certificate_verification(bool enabled) { + cli_->enable_server_certificate_verification(enabled); +} +#endif + +inline void Client::set_logger(Logger logger) { cli_->set_logger(logger); } + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT +inline void Client::set_ca_cert_path(const char *ca_cert_file_path, + const char *ca_cert_dir_path) { + if (is_ssl_) { + static_cast(*cli_).set_ca_cert_path(ca_cert_file_path, + ca_cert_dir_path); + } +} + +inline void Client::set_ca_cert_store(X509_STORE *ca_cert_store) { + if (is_ssl_) { + static_cast(*cli_).set_ca_cert_store(ca_cert_store); + } +} + +inline long Client::get_openssl_verify_result() const { + if (is_ssl_) { + return static_cast(*cli_).get_openssl_verify_result(); + } + return -1; // NOTE: -1 doesn't match any of X509_V_ERR_??? +} + +inline SSL_CTX *Client::ssl_context() const { + if (is_ssl_) { return static_cast(*cli_).ssl_context(); } + return nullptr; +} +#endif + +// ---------------------------------------------------------------------------- + +} // namespace httplib + +#endif // CPPHTTPLIB_HTTPLIB_H diff --git a/sop-sdk/sdk-c++/common/sha256.cpp b/sop-sdk/sdk-c++/common/sha256.cpp new file mode 100644 index 00000000..41678ff4 --- /dev/null +++ b/sop-sdk/sdk-c++/common/sha256.cpp @@ -0,0 +1,270 @@ +/* +* Filename: sha256.cpp +* Author: L.Y. +* Brief: SHA256算法实现 +* Version: V1.0.0 +* Update log: +* 1)20191108-20191113 V1.0.0 +* 1、初次版本。 +* TODO: +* Attention: +* 1)输入信息中有中文时,得到的数字指纹与使用SHA256在线加密工具得到数字指纹可能不相同。 +* 原因是中文的编码方式不同。 +*/ + +#include "sha256.hpp" + +#include +#include +#include +#include + +namespace digest +{ + +//////////////////////////////// 静态数据成员初始化 ////////////////////////////////////////// + + std::vector Sha256::initial_message_digest_ = + { + 0x6a09e667, 0xbb67ae85, 0x3c6ef372, + 0xa54ff53a, 0x510e527f, 0x9b05688c, + 0x1f83d9ab, 0x5be0cd19 + }; + + std::vector Sha256::add_constant_ = + { + 0x428a2f98,0x71374491,0xb5c0fbcf,0xe9b5dba5,0x3956c25b,0x59f111f1,0x923f82a4,0xab1c5ed5, + 0xd807aa98,0x12835b01,0x243185be,0x550c7dc3,0x72be5d74,0x80deb1fe,0x9bdc06a7,0xc19bf174, + 0xe49b69c1,0xefbe4786,0x0fc19dc6,0x240ca1cc,0x2de92c6f,0x4a7484aa,0x5cb0a9dc,0x76f988da, + 0x983e5152,0xa831c66d,0xb00327c8,0xbf597fc7,0xc6e00bf3,0xd5a79147,0x06ca6351,0x14292967, + 0x27b70a85,0x2e1b2138,0x4d2c6dfc,0x53380d13,0x650a7354,0x766a0abb,0x81c2c92e,0x92722c85, + 0xa2bfe8a1,0xa81a664b,0xc24b8b70,0xc76c51a3,0xd192e819,0xd6990624,0xf40e3585,0x106aa070, + 0x19a4c116,0x1e376c08,0x2748774c,0x34b0bcb5,0x391c0cb3,0x4ed8aa4a,0x5b9cca4f,0x682e6ff3, + 0x748f82ee,0x78a5636f,0x84c87814,0x8cc70208,0x90befffa,0xa4506ceb,0xbef9a3f7,0xc67178f2 + }; + + +//////////////////////////////// 成员函数的定义 ////////////////////////////////////////// + + bool Sha256::encrypt(const std::vector& input_message, + std::vector* _digest) + { + if (!input_message.empty() && _digest) + { + //! 文本预处理 + std::vector message = input_message; + preprocessing(&message); + + //! 将文本分解成连续的64Byte大小的数据块 + std::vector> chunks; + breakTextInto64ByteChunks(message, &chunks); + + //! 由64Byte大小的数据块,构造出64个4Byte大小的字。然后进行循环迭代。 + std::vector message_digest(initial_message_digest_); // 初始化信息摘要 + + std::vector words; + for (const auto& chunk : chunks) + { + structureWords(chunk, &words); + transform(words, &message_digest); + } + + //! 获取最终结果 + produceFinalHashValue(message_digest, _digest); + + return true; + } + else + { + return false; + } + } + + std::string Sha256::getHexMessageDigest(const std::string& message) + { + if (!message.empty()) + { + std::vector __message; + for (auto it = message.begin(); it != message.end(); ++it) + { + __message.push_back(static_cast(*it)); + } + + std::vector digest; + encrypt(__message, &digest); + + std::ostringstream o_s; + o_s << std::hex << std::setiosflags(std::ios::uppercase); + for (auto it = digest.begin(); it != digest.end(); ++it) + { + o_s << std::setw(2) << std::setfill('0') + << static_cast(*it); + } + + return o_s.str(); + } + else + { + return ""; + } + } + + bool Sha256::preprocessing(std::vector* _message) const + { + if (_message) + { + const uint64_t original_bit_size = _message->size() * 8; + + //! 附加填充比特 + size_t remainder = _message->size() % 64; + if (remainder < 56) + { + _message->push_back(0x80); // ox80 == 10000000 + for (size_t i = 1; i < 56 - remainder; ++i) + { + _message->push_back(0x00); + } + } + else if (remainder == 56) + { + _message->push_back(0x80); + for (size_t i = 1; i < 64; ++i) + { + _message->push_back(0x00); + } + } + else + { + _message->push_back(0x80); + for (size_t i = 1; i < 64 - remainder + 56; ++i) + { + _message->push_back(0x00); + } + } + + //! 附加原始文本的长度值 + for (int i = 1; i <= 8; ++i) + { + uint8_t c = static_cast(original_bit_size >> (64 - 8 * i)); + _message->push_back(c); + } + + return true; + } + else + { + return false; + } + } + + bool Sha256::breakTextInto64ByteChunks(const std::vector& message, + std::vector>* _chunks) const + { + if (_chunks && 0 == message.size() % 64) + { + _chunks->clear(); // 清空输出buffer + + size_t quotient = message.size() / 64; + for (size_t i = 0; i < quotient; ++i) + { + std::vector temp(message.begin() + i * 64, message.begin() + (i + 1) * 64); + _chunks->push_back(temp); + } + return true; + } + else + { + return false; + } + } + + bool Sha256::structureWords(const std::vector& chunk, + std::vector* _words) const + { + if (_words && 64 == chunk.size()) + { + _words->resize(64); + + auto& words = *_words; + for (int i = 0; i < 16; ++i) + { + words[i] = (static_cast(chunk[i * 4]) << 24) + | (static_cast(chunk[i * 4 + 1]) << 16) + | (static_cast(chunk[i * 4 + 2]) << 8) + | static_cast(chunk[i * 4 + 3]); + } + for (int i = 16; i < 64; ++i) + { + words[i] = small_sigma1(words[i - 2]) + + words[i - 7] + + small_sigma0(words[i - 15]) + + words[i - 16]; + } + return true; + } + else + { + return false; + } + } + + bool Sha256::transform(const std::vector& words, + std::vector* _message_digest) const + { + if (_message_digest && 8 == _message_digest->size() && 64 == words.size()) + { + std::vector d = *_message_digest; + + for (int i = 0; i < 64; ++i) + { + uint32_t temp1 = d[7] + big_sigma1(d[4]) + ch(d[4], d[5], d[6]) + add_constant_[i] + words[i]; + uint32_t temp2 = big_sigma0(d[0]) + maj(d[0], d[1], d[2]); + + d[7] = d[6]; + d[6] = d[5]; + d[5] = d[4]; + d[4] = d[3] + temp1; + d[3] = d[2]; + d[2] = d[1]; + d[1] = d[0]; + d[0] = temp1 + temp2; + } + + for (int i = 0; i < 8; ++i) + { + (*_message_digest)[i] += d[i]; + } + + return true; + } + else + { + return false; + } + } + + bool Sha256::produceFinalHashValue(const std::vector& input, + std::vector* _output) const + { + if (_output) + { + _output->clear(); + + for (auto it = input.begin(); it != input.end(); ++it) + { + for (int i = 0; i < 4; i++) + { + _output->push_back(static_cast((*it) >> (24 - 8 * i))); + } + } + return true; + } + else + { + return false; + } + } + +} // namespace ly + + diff --git a/sop-sdk/sdk-c++/common/sha256.hpp b/sop-sdk/sdk-c++/common/sha256.hpp new file mode 100644 index 00000000..f2a825dd --- /dev/null +++ b/sop-sdk/sdk-c++/common/sha256.hpp @@ -0,0 +1,144 @@ +/* +* Filename: sha256.hpp +* Author: L.Y. +* Brief: SHA256算法实现 +* Version: V1.0.0 +* Update log: +* 1)20191108-20191113 V1.0.0 +* 1、初次版本。 +* TODO: +* Attention: +* 1)输入信息中有中文时,得到的数字指纹与使用SHA256在线加密工具得到数字指纹可能不相同。 +* 原因是中文的编码方式不同。 +*/ + +#ifndef SHA256_HPP +#define SHA256_HPP + +#include + +#include +#include + +namespace digest +{ + +// +// \brief: SHA256算法实现 +// + class Sha256 + { + public: + //! 默认构造函数 + Sha256() {} + + //! 析构函数 + virtual ~Sha256() {} + + /** @brief: 使用SHA256算法,获取输入信息的摘要(数字指纹) + @param[in] message: 输入信息 + @param[out] _digest: 摘要(数字指纹) + @return: 是否成功 + */ + bool encrypt(const std::vector& message, + std::vector* _digest); + + /** @brief: 获取十六进制表示的信息摘要(数字指纹) + @param[in] message: 输入信息 + @return: 十六进制表示的信息摘要(数字指纹) + */ + std::string getHexMessageDigest(const std::string& message); + + protected: + /////// SHA256算法中定义的6种逻辑运算 /////// + inline uint32_t ch(uint32_t x, uint32_t y, uint32_t z) const; + inline uint32_t maj(uint32_t x, uint32_t y, uint32_t z) const; + inline uint32_t big_sigma0(uint32_t x) const; + inline uint32_t big_sigma1(uint32_t x) const; + inline uint32_t small_sigma0(uint32_t x) const; + inline uint32_t small_sigma1(uint32_t x) const; + + /** @brief: SHA256算法对输入信息的预处理,包括“附加填充比特”和“附加长度值” + 附加填充比特: 在报文末尾进行填充,先补第一个比特为1,然后都补0,直到长度满足对512取模后余数是448。需要注意的是,信息必须进行填充。 + 附加长度值: 用一个64位的数据来表示原始消息(填充前的消息)的长度,并将其补到已经进行了填充操作的消息后面。 + @param[in][out] _message: 待处理的信息 + @return: 是否成功 + */ + bool preprocessing(std::vector* _message) const; + + /** @brief: 将信息分解成连续的64Byte大小的数据块 + @param[in] message: 输入信息,长度为64Byte的倍数 + @param[out] _chunks: 输出数据块 + @return: 是否成功 + */ + bool breakTextInto64ByteChunks(const std::vector& message, + std::vector>* _chunks) const; + + /** @brief: 由64Byte大小的数据块,构造出64个4Byte大小的字。 + 构造算法:前16个字直接由数据块分解得到,其余的字由如下迭代公式得到: + W[t] = small_sigma1(W[t-2]) + W[t-7] + small_sigma0(W[t-15]) + W[t-16] + @param[in] chunk: 输入数据块,大小为64Byte + @param[out] _words: 输出字 + @return: 是否成功 + */ + bool structureWords(const std::vector& chunk, + std::vector* _words) const; + + /** @breif: 基于64个4Byte大小的字,进行64次循环加密 + @param[in] words: 64个4Byte大小的字 + @param[in][out] _message_digest: 信息摘要 + @return: 是否成功 + */ + bool transform(const std::vector& words, + std::vector* _message_digest) const; + + /** @brief: 输出最终的哈希值(数字指纹) + @param[in] input: 步长为32bit的哈希值 + @param[out] _output: 步长为8bit的哈希值 + @return: 是否成功 + */ + bool produceFinalHashValue(const std::vector& input, + std::vector* _output) const; + + + private: + static std::vector initial_message_digest_; // 在SHA256算法中的初始信息摘要,这些常量是对自然数中前8个质数的平方根的小数部分取前32bit而来。 + static std::vector add_constant_; // 在SHA256算法中,用到64个常量,这些常量是对自然数中前64个质数的立方根的小数部分取前32bit而来。 + }; + + +///////////////////////////////// 内联函数&模版函数的定义 ///////////////////////////////////////// + + inline uint32_t Sha256::ch(uint32_t x, uint32_t y, uint32_t z) const + { + return (x & y) ^ ((~x) & z); + } + + inline uint32_t Sha256::maj(uint32_t x, uint32_t y, uint32_t z) const + { + return (x & y) ^ (x & z) ^ (y & z); + } + + inline uint32_t Sha256::big_sigma0(uint32_t x) const + { + return (x >> 2 | x << 30) ^ (x >> 13 | x << 19) ^ (x >> 22 | x << 10); + } + + inline uint32_t Sha256::big_sigma1(uint32_t x) const + { + return (x >> 6 | x << 26) ^ (x >> 11 | x << 21) ^ (x >> 25 | x << 7); + } + + inline uint32_t Sha256::small_sigma0(uint32_t x) const + { + return (x >> 7 | x << 25) ^ (x >> 18 | x << 14) ^ (x >> 3); + } + + inline uint32_t Sha256::small_sigma1(uint32_t x) const + { + return (x >> 17 | x << 15) ^ (x >> 19 | x << 13) ^ (x >> 10); + } + +} // namespace ly + +#endif // SHA256_HPP diff --git a/sop-sdk/sdk-c++/common/sign.cpp b/sop-sdk/sdk-c++/common/sign.cpp new file mode 100644 index 00000000..43df6c83 --- /dev/null +++ b/sop-sdk/sdk-c++/common/sign.cpp @@ -0,0 +1,118 @@ +#include + +#include "../thirdparty/base64/base64.h" +#include "sign.h" +#include "RSASign.h" +#include "tool.h" + +namespace signutil { + string createSign(map params, const string& privateKeyFilepath, const string& signType) { + string content = getSignContent(std::move(params)); + return sign(content, privateKeyFilepath, ""); + } + + string sign(const string& content, const string& privateKeyFilepath, const string& hash) { + BIO *bufio = NULL; + RSA *rsa = NULL; + EVP_PKEY *evpKey = NULL; + bool verify = false; + EVP_MD_CTX ctx; + int result = 0; + unsigned int size = 0; + char *sign = NULL; + std::string signStr = ""; + + //bufio = BIO_new_mem_buf((void*)private_key, -1); + //if (bufio == NULL) { + // ERR("BIO_new_mem_buf failed"); + // goto safe_exit; + //} + bufio = BIO_new(BIO_s_file()); + // 读取私钥文件 + BIO_read_filename(bufio, privateKeyFilepath.c_str()); + + // 私钥字符串转换成私钥对象 + rsa = PEM_read_bio_RSAPrivateKey(bufio, NULL, NULL, NULL); + if (rsa == NULL) { + ERR("PEM_read_bio_RSAPrivateKey failed"); + goto safe_exit; + } + + evpKey = EVP_PKEY_new(); + if (evpKey == NULL) { + ERR("EVP_PKEY_new failed"); + goto safe_exit; + } + + if ((result = EVP_PKEY_set1_RSA(evpKey, rsa)) != 1) { + ERR("EVP_PKEY_set1_RSA failed"); + goto safe_exit; + } + + EVP_MD_CTX_init(&ctx); + + // SHA256签名 + if (result == 1 && (result = EVP_SignInit_ex(&ctx, + EVP_sha256(), NULL)) != 1) { + ERR("EVP_SignInit_ex failed"); + } + + if (result == 1 && (result = EVP_SignUpdate(&ctx, + content.c_str(), content.size())) != 1) { + ERR("EVP_SignUpdate failed"); + } + + size = EVP_PKEY_size(evpKey); + sign = (char*)malloc(size+1); + memset(sign, 0, size+1); + + if (result == 1 && (result = EVP_SignFinal(&ctx, + (unsigned char*)sign, + &size, evpKey)) != 1) { + ERR("EVP_SignFinal failed"); + } + + if (result == 1) { + verify = true; + } else { + ERR("verify failed"); + } + + signStr = base64_encode((const unsigned char*)sign, size); + EVP_MD_CTX_cleanup(&ctx); + free(sign); + + safe_exit: + if (rsa != NULL) { + RSA_free(rsa); + rsa = NULL; + } + + if (evpKey != NULL) { + EVP_PKEY_free(evpKey); + evpKey = NULL; + } + + if (bufio != NULL) { + BIO_free_all(bufio); + bufio = NULL; + } + + return signStr; + + } + + string getSignContent(map params){ + map::iterator iter; + string content; + for(iter = params.begin(); iter != params.end(); iter++) { + content.append("&").append(iter->first + "=" + iter->second); + } + return content.substr(1); + } + + void ERR(const string &msg) { + throw msg; + } +} + diff --git a/sop-sdk/sdk-c++/common/sign.h b/sop-sdk/sdk-c++/common/sign.h new file mode 100644 index 00000000..9c21368e --- /dev/null +++ b/sop-sdk/sdk-c++/common/sign.h @@ -0,0 +1,30 @@ + + +#ifndef SDK_CXX_SIGN_H +#define SDK_CXX_SIGN_H + +#include +#include + +#include +#include +#include +#include +#include +#include + +using namespace std; + +namespace signutil { + + string createSign(map params, const string& privateKeyFilepath, const string& signType); + + string sign(const string& content, const string& privateKeyFilepath, const string& hash); + + string getSignContent(map params); + + void ERR(const string &msg); +} + + +#endif //SDK_CXX_SIGN_H diff --git a/sop-sdk/sdk-c++/common/stdafx.cpp b/sop-sdk/sdk-c++/common/stdafx.cpp new file mode 100644 index 00000000..46cd1889 --- /dev/null +++ b/sop-sdk/sdk-c++/common/stdafx.cpp @@ -0,0 +1,2 @@ + +#include "stdafx.h" diff --git a/sop-sdk/sdk-c++/common/stdafx.h b/sop-sdk/sdk-c++/common/stdafx.h new file mode 100644 index 00000000..14d514e8 --- /dev/null +++ b/sop-sdk/sdk-c++/common/stdafx.h @@ -0,0 +1,6 @@ +#pragma once + +#include "targetver.h" + + + diff --git a/sop-sdk/sdk-c++/common/targetver.h b/sop-sdk/sdk-c++/common/targetver.h new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/sop-sdk/sdk-c++/common/targetver.h @@ -0,0 +1 @@ + diff --git a/sop-sdk/sdk-c++/common/tool.cpp b/sop-sdk/sdk-c++/common/tool.cpp new file mode 100644 index 00000000..6b56a680 --- /dev/null +++ b/sop-sdk/sdk-c++/common/tool.cpp @@ -0,0 +1,130 @@ +#include "tool.h" +#include "json/json.h" +#include +#include +#include + +namespace tool { + bool endWith(const string &str, const string &tail) { + return str.compare(str.size() - tail.size(), tail.size(), tail) == 0; + } + + bool startWith(const string &str, const string &head) { + return str.compare(0, head.size(), head) == 0; + } + + string getTime() { + time_t timep; + time(&timep); + char tmp[64]; + strftime(tmp, sizeof(tmp), "%Y-%m-%d %H:%M:%S", localtime(&timep)); + return tmp; + } + + std::string url_encode(const std::string &str) { + std::string strTemp = ""; + size_t length = str.length(); + for (size_t i = 0; i < length; i++) { + if (isalnum((unsigned char) str[i]) || + (str[i] == '-') || + (str[i] == '_') || + (str[i] == '.') || + (str[i] == '~')) + strTemp += str[i]; + else if (str[i] == ' ') + strTemp += "%20"; + else { + strTemp += '%'; + strTemp += ToHex((unsigned char) str[i] >> 4); + strTemp += ToHex((unsigned char) str[i] % 16); + } + } + return strTemp; + } + + + std::string url_decode(const std::string &str) { + std::string strTemp = ""; + size_t length = str.length(); + for (size_t i = 0; i < length; i++) { + if (str[i] == '+') strTemp += ' '; + else if (str[i] == '%') { + assert(i + 2 < length); + unsigned char high = FromHex((unsigned char) str[++i]); + unsigned char low = FromHex((unsigned char) str[++i]); + strTemp += high * 16 + low; + } else strTemp += str[i]; + } + return strTemp; + } + + + unsigned char ToHex(unsigned char x) { + return x > 9 ? x + 55 : x + 48; + } + + unsigned char FromHex(unsigned char x) { + unsigned char y; + if (x >= 'A' && x <= 'Z') y = x - 'A' + 10; + else if (x >= 'a' && x <= 'z') y = x - 'a' + 10; + else if (x >= '0' && x <= '9') y = x - '0'; + else + assert(0); + return y; + } + + string mapToJson(std::map map_info) { + Json::Value jObject; + for (map::const_iterator iter = map_info.begin( ); iter != map_info.end( ); ++iter) + { + jObject[iter->first] = iter->second; + } + return jObject.toStyledString(); + } + + string getFilename(string filepath) { + { + if (filepath.empty()) { + return ""; + } + std::string::size_type iPos; +#ifdef Q_OS_WIN + iPos = strFullName.find_last_of('\\') + 1; +#else + iPos = filepath.find_last_of('/') + 1; +#endif + + return filepath.substr(iPos, filepath.length() - iPos); + } + } + + string getFileContent(string filepath) { + ifstream fin(filepath); + stringstream buffer; + buffer << fin.rdbuf(); + string fileContent(buffer.str()); + fin.close(); + return fileContent; + } + + std::string replace(const char *pszSrc, const char *pszOld, const char *pszNew) + { + std::string strContent, strTemp; + strContent.assign( pszSrc ); + std::string::size_type nPos = 0; + while( true ) + { + nPos = strContent.find(pszOld, nPos); + strTemp = strContent.substr(nPos+strlen(pszOld), strContent.length()); + if ( nPos == std::string::npos ) + { + break; + } + strContent.replace(nPos,strContent.length(), pszNew ); + strContent.append(strTemp); + nPos +=strlen(pszNew) - strlen(pszOld)+1; //防止重复替换 避免死循环 + } + return strContent; + } + +} \ No newline at end of file diff --git a/sop-sdk/sdk-c++/common/tool.h b/sop-sdk/sdk-c++/common/tool.h new file mode 100644 index 00000000..96cc49b7 --- /dev/null +++ b/sop-sdk/sdk-c++/common/tool.h @@ -0,0 +1,50 @@ + + +#ifndef SDK_CXX_TOOL_H +#define SDK_CXX_TOOL_H + +#include +#include + +using namespace std; + +namespace tool { + + bool endWith(const string &str, const string &tail); + + bool startWith(const string &str, const string &head); + + string getTime(); + + std::string url_encode(const std::string &szToEncode); + + std::string url_decode(const std::string &SRC); + + unsigned char ToHex(unsigned char x); + + unsigned char FromHex(unsigned char x); + + string mapToJson(std::map m); + + string getFilename(string filepath); + + string getFileContent(string filepath); + + + /** + * 函数: + * replace(替换字符串) + * 参数: + * pszSrc:源字符串 + * pszOld:需要替换的字符串 + * pszNew:新字符串 + * 返回值: + * 返回替换后的字符串 + * 备注: + * 需要添加#include 头文件 + * ssdwujianhua 2017/08/30 + */ + std::string replace(const char *pszSrc, const char *pszOld, const char *pszNew); +} + +#endif //SDK_CXX_TOOL_H diff --git a/sop-sdk/sdk-c++/main.cpp b/sop-sdk/sdk-c++/main.cpp new file mode 100644 index 00000000..6f21b488 --- /dev/null +++ b/sop-sdk/sdk-c++/main.cpp @@ -0,0 +1,42 @@ +#include + +#include "common/OpenClient.h" +#include "request/BaseRequest.h" +#include "request/MemberInfoGetRequest.hpp" + +// 应用ID +string appId = "201904035630907729292cpp"; +// 存放私钥的文件路径 +string privateKeyFile = "/Users/thc/IdeaProjects/SOP/sop-sdk/sdk-c++/privateEx.pem"; +// 请求接口 +string url = "http://localhost:8081"; + +OpenClient openClient(appId, privateKeyFile, url); + +int main() { + // 创建请求 + MemberInfoGetRequest request; + + // 业务参数 + map bizModel; + bizModel["name"] = "jim"; + bizModel["age"] = "22"; + bizModel["address"] = "xx"; + + request.bizModel = bizModel; + + // 添加上传文件 +// request->setFiles({ +// FileInfo{"aa", "/Users/thc/IdeaProjects/opc/opc-sdk/sdk-c++/aa.txt"}, +// FileInfo{"bb", "/Users/thc/IdeaProjects/opc/opc-sdk/sdk-c++/bb.txt"} +// }); + + // 发送请求 + Json::Value jsonObj = openClient.execute(&request); + std::cout << jsonObj << std::endl; + std::cout << "id:" << jsonObj["id"].asString() << std::endl; + std::cout << "name:" << jsonObj["name"].asString() << std::endl; + std::cout << "is_vip:" << jsonObj["member_info"]["is_vip"].asString() << std::endl; + return 0; +} + diff --git a/sop-sdk/sdk-c++/privateEx.pem b/sop-sdk/sdk-c++/privateEx.pem new file mode 100644 index 00000000..84769da7 --- /dev/null +++ b/sop-sdk/sdk-c++/privateEx.pem @@ -0,0 +1,15 @@ +-----BEGIN RSA PRIVATE KEY----- +MIICXAIBAAKBgQCHJlAPN+1dCbgc3HiahQkT2W/skecGWOCkSX4CPvEc8oIk6544 +xihEwShHnfrapiQdF2fndv5agrhg4FyOHheST42L5MnCk+4Km+mWm5GDvmFS7Sa2 +aZ5o3regY0MUoJ7D74dYjE3UYFuTujAXiXjGpAwa9qOcKotov5LCkSfUeQIDAQAB +AoGAB1cyw8LYRQSHQCUO9Wiaq730jPNHSrJW4EGAIz/XMYjv/fCgx0lnDEX4CbzI +UGoz/bME4R721YRyXoutJ0h14/cGrt/TEn/TMI0xnISzJHr8VSlyBkQEdfO/W3LO +qjs/UYq2Bz4+kJROJHreM+7d5hiIWLzLBlyI8cSU92ySmHECQQDwju2SoRu88kQP +1qr4seZyKQa8DHTVyCoa6LtPLXyJsdgWgY4KyqJHwMUumEC2Zhhu833CR0ZXbfta +uQDmwAVJAkEAj9M225jrPasaD5vPO7thxtEqgV4F/ZyNKH0Z8bDH27KaKkQ+8GMt +kxwKVckZXs2bMvg/6tCiDZkWAxawNrvFsQJBANmTrPWOmpQPW9gnhYRjA9gFm33C +lno2DT9BeQloTtgL7zKMA3lnRdg4VyCJvR48waS4vupVpR228D1iT5pl22ECQF1M +JUzkcM0rPheb+h2EW1QOgWU0Keyvbj4ykO7gv3T78dezN6TWoUzJpsapUiTWeXPh +6AyZ1FW/1bChOiP3QLECQGAbObmsYlN0bjzPYChwWYeYjErXuv51a44GZCNWinFw +GGiHU9ZAqF8RzmBVW4htwj0j/Yry/V1Sp0uoP0zu3uA= +-----END RSA PRIVATE KEY----- diff --git a/sop-sdk/sdk-c++/readme.md b/sop-sdk/sdk-c++/readme.md new file mode 100644 index 00000000..fffcf701 --- /dev/null +++ b/sop-sdk/sdk-c++/readme.md @@ -0,0 +1,5 @@ +# SDK for C++ + +本工程使用`CLion`进行开发,前提安装jsoncpp(Mac安装方式:`brew install jsoncpp`) + +使用方式见:main.cpp \ No newline at end of file diff --git a/sop-sdk/sdk-c++/request/BaseRequest.cpp b/sop-sdk/sdk-c++/request/BaseRequest.cpp new file mode 100644 index 00000000..80de6fdd --- /dev/null +++ b/sop-sdk/sdk-c++/request/BaseRequest.cpp @@ -0,0 +1,17 @@ + +#include "BaseRequest.h" + +#include + + +vector BaseRequest::getFiles() { + return this->files; +} + +void BaseRequest::addFile(const FileInfo& filepath) { + this->files.push_back(filepath); +} + +void BaseRequest::setFiles(vector files) { + this->files = std::move(files); +} diff --git a/sop-sdk/sdk-c++/request/BaseRequest.h b/sop-sdk/sdk-c++/request/BaseRequest.h new file mode 100644 index 00000000..519f9f15 --- /dev/null +++ b/sop-sdk/sdk-c++/request/BaseRequest.h @@ -0,0 +1,60 @@ +#ifndef SDK_CXX_BASEREQUEST_H +#define SDK_CXX_BASEREQUEST_H + +#include +#include +#include +#include "../common/RequestType.h" + +using namespace std; + +struct FileInfo { + /** 表单名称 */ + string name; + /** 文件完整路径 */ + string filepath; +}; + + +/** + * 请求类基类,其它请求类需要继承这个类 + */ +class BaseRequest { +public: + + /** + * 业务参数 + */ + map bizModel; + + /** + * 定义接口名称 + * @return 返回接口名称 + */ + virtual string getMethod() = 0; + + /** + * 定义接口版本号 + * @return 返回版本号 + */ + virtual string getVersion() = 0; + + /** + * 定义请求方式 + * @return 返回请求方式 + */ + virtual RequestType getRequestType() = 0; + + vector getFiles(); + + void setFiles(vector files); + +private: + vector files = {}; + + void addFile(const FileInfo& filepath); +}; + + + +#endif //SDK_CXX_BASEREQUEST_H diff --git a/sop-sdk/sdk-c++/request/MemberInfoGetRequest.hpp b/sop-sdk/sdk-c++/request/MemberInfoGetRequest.hpp new file mode 100644 index 00000000..09062626 --- /dev/null +++ b/sop-sdk/sdk-c++/request/MemberInfoGetRequest.hpp @@ -0,0 +1,29 @@ +#ifndef SDK_CXX_MEMBERINFOGETREQUEST_HPP +#define SDK_CXX_MEMBERINFOGETREQUEST_HPP + +#include +#include "BaseRequest.h" + +using namespace std; + +class MemberInfoGetRequest : public BaseRequest { + +public: + string getMethod() override; + string getVersion() override; + RequestType getRequestType() override; +}; + +string MemberInfoGetRequest::getMethod() { + return "member.info.get"; +} + +string MemberInfoGetRequest::getVersion() { + return "1.0"; +} + +RequestType MemberInfoGetRequest::getRequestType() { + return GET; +} + +#endif //SDK_CXX_MEMBERINFOGETREQUEST_HPP diff --git a/sop-sdk/sdk-c++/thirdparty/base64/base64.cpp b/sop-sdk/sdk-c++/thirdparty/base64/base64.cpp new file mode 100644 index 00000000..a0c583d6 --- /dev/null +++ b/sop-sdk/sdk-c++/thirdparty/base64/base64.cpp @@ -0,0 +1,103 @@ +// +// base64.cpp +// CPPWork +// +// Created by cocoa on 16/8/5. +// Copyright © 2016年 cc. All rights reserved. +// + +#include "base64.h" + +static const std::string base64_chars = + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + "0123456789+/"; + + +static inline bool is_base64(unsigned char c) { + return (isalnum(c) || (c == '+') || (c == '/')); +} + +std::string base64_encode(unsigned char const* bytes_to_encode, unsigned int in_len) { + std::string ret; + int i = 0; + int j = 0; + unsigned char char_array_3[3]; + unsigned char char_array_4[4]; + + while (in_len--) { + char_array_3[i++] = *(bytes_to_encode++); + if (i == 3) { + char_array_4[0] = (char_array_3[0] & 0xfc) >> 2; + char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4); + char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6); + char_array_4[3] = char_array_3[2] & 0x3f; + + for(i = 0; (i <4) ; i++) + ret += base64_chars[char_array_4[i]]; + i = 0; + } + } + + if (i) + { + for(j = i; j < 3; j++) + char_array_3[j] = '\0'; + + char_array_4[0] = (char_array_3[0] & 0xfc) >> 2; + char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4); + char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6); + char_array_4[3] = char_array_3[2] & 0x3f; + + for (j = 0; (j < i + 1); j++) + ret += base64_chars[char_array_4[j]]; + + while((i++ < 3)) + ret += '='; + + } + + return ret; + +} +std::string base64_decode(std::string const& encoded_string) { + int in_len = encoded_string.size(); + int i = 0; + int j = 0; + int in_ = 0; + unsigned char char_array_4[4], char_array_3[3]; + std::string ret; + + while (in_len-- && ( encoded_string[in_] != '=') && is_base64(encoded_string[in_])) { + char_array_4[i++] = encoded_string[in_]; in_++; + if (i ==4) { + for (i = 0; i <4; i++) + char_array_4[i] = base64_chars.find(char_array_4[i]); + + char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4); + char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2); + char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3]; + + for (i = 0; (i < 3); i++) + ret += char_array_3[i]; + i = 0; + } + } + + if (i) { + for (j = i; j <4; j++) + char_array_4[j] = 0; + + for (j = 0; j <4; j++) + char_array_4[j] = base64_chars.find(char_array_4[j]); + + char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4); + char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2); + char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3]; + + for (j = 0; (j < i - 1); j++) ret += char_array_3[j]; + } + + return ret; +} + diff --git a/sop-sdk/sdk-c++/thirdparty/base64/base64.h b/sop-sdk/sdk-c++/thirdparty/base64/base64.h new file mode 100755 index 00000000..19e29cfd --- /dev/null +++ b/sop-sdk/sdk-c++/thirdparty/base64/base64.h @@ -0,0 +1,10 @@ +#ifndef base64_h +#define base64_h + +#include + +std::string base64_encode(unsigned char const* bytes_to_encode, unsigned int in_len); +std::string base64_decode(std::string const& encoded_string); + + +#endif /* base64_h */ \ No newline at end of file diff --git a/sop-sdk/sdk-go/common/OpenClient.go b/sop-sdk/sdk-go/common/OpenClient.go index 23289c52..682daec7 100644 --- a/sop-sdk/sdk-go/common/OpenClient.go +++ b/sop-sdk/sdk-go/common/OpenClient.go @@ -66,13 +66,11 @@ func parseResponseResult(iRequest IRequest, response string) []byte { if err != nil { panic(err) } - requestId := responseRoot["request_id"].(string) var responseDataMap = responseRoot["error_response"] if responseDataMap == nil { dataName := strings.ReplaceAll(iRequest.GetMethod(), ".", "_") + "_response" responseDataMap = responseRoot[dataName] } - responseDataMap.(map[string]interface{})["request_id"] = requestId // json数据 dataJsonBytes, _ := json.Marshal(responseDataMap) return dataJsonBytes @@ -92,10 +90,12 @@ func (client OpenClient) buildParams(iRequest IRequest, params map[string]interf allParams["access_token"] = token } + bizParams := map[string]interface{}{} // 添加业务参数 for k, v := range params { - allParams[k] = ToString(v) + bizParams[k] = v } + allParams["biz_content"] = ToString(bizParams) // 构建sign sign := CreateSign(allParams, client.PrivateKey, "RSA2") diff --git a/sop-sdk/sdk-java/pom.xml b/sop-sdk/sdk-java/pom.xml index 5b0725d8..f5dbaa82 100644 --- a/sop-sdk/sdk-java/pom.xml +++ b/sop-sdk/sdk-java/pom.xml @@ -4,13 +4,13 @@ com.gitee.sop sop-parent - 4.0.3-SNAPSHOT + 4.3.2-SNAPSHOT ../../pom.xml 4.0.0 sdk-java - 4.0.3-SNAPSHOT + 4.3.2-SNAPSHOT diff --git a/sop-sdk/sdk-java/src/main/resources/pri.key b/sop-sdk/sdk-java/src/main/resources/pri.key deleted file mode 100644 index 1ea9ba4e..00000000 --- a/sop-sdk/sdk-java/src/main/resources/pri.key +++ /dev/null @@ -1 +0,0 @@ -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= \ No newline at end of file diff --git a/sop-sdk/sdk-nodejs/common/OpenClient.js b/sop-sdk/sdk-nodejs/common/OpenClient.js index 1707ddf9..3da27c36 100644 --- a/sop-sdk/sdk-nodejs/common/OpenClient.js +++ b/sop-sdk/sdk-nodejs/common/OpenClient.js @@ -130,12 +130,8 @@ const OpenClient = Class.create({ 'charset': 'UTF-8', 'sign_type': 'RSA2', 'timestamp': moment().format('YYYY-MM-DD HH:mm:ss'), - 'version': request.getVersion() - } - const bizModel = request.bizModel - - for (const key in bizModel) { - allParams[key] = bizModel[key] + 'version': request.getVersion(), + 'biz_content': JSON.stringify(request.bizModel) } if (token) { diff --git a/sop-sdk/sdk-nodejs/main.js b/sop-sdk/sdk-nodejs/main.js index 4745e990..f8d8987e 100644 --- a/sop-sdk/sdk-nodejs/main.js +++ b/sop-sdk/sdk-nodejs/main.js @@ -18,6 +18,7 @@ function test() { // 设置业务参数 request.bizModel = { + id: 111, name: 'jim' } diff --git a/sop-sdk/sdk-python/common/OpenClient.py b/sop-sdk/sdk-python/common/OpenClient.py index 821c5f3e..8f60464b 100644 --- a/sop-sdk/sdk-python/common/OpenClient.py +++ b/sop-sdk/sdk-python/common/OpenClient.py @@ -103,15 +103,13 @@ class OpenClient: 'charset': 'UTF-8', 'sign_type': 'RSA2', 'timestamp': time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()), - 'version': request.get_version() + 'version': request.get_version(), + 'biz_content': json.dumps(params) } if token is not None: all_params['access_token'] = token - # 添加业务参数 - all_params.update(params) - # 构建sign sign = SignUtil.create_sign(all_params, self.__private_key, 'RSA2') all_params['sign'] = sign diff --git a/sop-sdk/sdk-python/test/test.py b/sop-sdk/sdk-python/test/test.py index f98400cf..f249a3a0 100644 --- a/sop-sdk/sdk-python/test/test.py +++ b/sop-sdk/sdk-python/test/test.py @@ -48,7 +48,7 @@ class MyTestCase(unittest.TestCase): # for f in files.values(): # f.close() - if response.is_success(): + if response.get('code') == '10000': print 'response: ', response print 'is_vip:', response.get('member_info').get('is_vip', 0) else: diff --git a/sop-sdk/sdk-rust/readme.md b/sop-sdk/sdk-rust/readme.md new file mode 100644 index 00000000..a0d0632d --- /dev/null +++ b/sop-sdk/sdk-rust/readme.md @@ -0,0 +1,5 @@ +# sdk for rust + +使用方式见:`sdk-test/src/main.rs` + +封装步骤参考 `sdk/src/request/memberinfoget.rs` 和 `sdk/src/response/memberinfoget.rs` \ No newline at end of file diff --git a/sop-sdk/sdk-rust/sdk-test/.gitignore b/sop-sdk/sdk-rust/sdk-test/.gitignore new file mode 100644 index 00000000..ea8c4bf7 --- /dev/null +++ b/sop-sdk/sdk-rust/sdk-test/.gitignore @@ -0,0 +1 @@ +/target diff --git a/sop-sdk/sdk-rust/sdk-test/Cargo.lock b/sop-sdk/sdk-rust/sdk-test/Cargo.lock new file mode 100644 index 00000000..36a847ba --- /dev/null +++ b/sop-sdk/sdk-rust/sdk-test/Cargo.lock @@ -0,0 +1,1558 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +[[package]] +name = "aho-corasick" +version = "0.7.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "043164d8ba5c4c3035fec9bbee8647c0261d788f3474306f93bb65901cae0e86" +dependencies = [ + "memchr", +] + +[[package]] +name = "arc-swap" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d25d88fd6b8041580a654f9d0c581a047baee2b3efee13275f2fc392fc75034" + +[[package]] +name = "autocfg" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d49d90015b3c36167a20fe2810c5cd875ad504b39cff3d4eae7977e6b7c1cb2" + +[[package]] +name = "autocfg" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d" + +[[package]] +name = "base64" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3441f0f7b02788e948e47f457ca01f1d7e6d92c693bc132c22b087d3141c03ff" + +[[package]] +name = "bitflags" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" + +[[package]] +name = "block-buffer" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" +dependencies = [ + "generic-array", +] + +[[package]] +name = "bumpalo" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e8c087f005730276d1096a652e92a8bacee2e2472bcc9715a74d2bec38b5820" + +[[package]] +name = "byteorder" +version = "1.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08c48aae112d48ed9f069b33538ea9e3e90aa263cfa3d1c24309612b1f7472de" + +[[package]] +name = "bytes" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "118cf036fbb97d0816e3c34b2d7a1e8cfc60f68fcf63d550ddbe9bd5f59c213b" +dependencies = [ + "loom", +] + +[[package]] +name = "cc" +version = "1.0.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77c1f1d60091c1b73e2b1f4560ab419204b178e625fa945ded7b660becd2bd46" + +[[package]] +name = "cfg-if" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" + +[[package]] +name = "chrono" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80094f509cf8b5ae86a4966a39b3ff66cd7e2a3e594accec3743ff3fabeab5b2" +dependencies = [ + "num-integer", + "num-traits", + "time", +] + +[[package]] +name = "core-foundation" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57d24c7a13c43e870e37c1556b74555437870a04514f7685f5b354e090567171" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3a71ab494c0b5b860bdc8407ae08978052417070c2ced38573a9157ad75b8ac" + +[[package]] +name = "cpuid-bool" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d375c433320f6c5057ae04a04376eef4d04ce2801448cf8863a78da99107be4" + +[[package]] +name = "digest" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" +dependencies = [ + "generic-array", +] + +[[package]] +name = "dtoa" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "134951f4028bdadb9b84baf4232681efbf277da25144b9b0ad65df75946c422b" + +[[package]] +name = "encoding_rs" +version = "0.8.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8ac63f94732332f44fe654443c46f6375d1939684c17b0afb6cb56b0456e171" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + +[[package]] +name = "fuchsia-cprng" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" + +[[package]] +name = "fuchsia-zircon" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82" +dependencies = [ + "bitflags", + "fuchsia-zircon-sys", +] + +[[package]] +name = "fuchsia-zircon-sys" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7" + +[[package]] +name = "futures-channel" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f366ad74c28cca6ba456d95e6422883cfb4b252a83bed929c83abfdbbf2967d5" +dependencies = [ + "futures-core", +] + +[[package]] +name = "futures-core" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59f5fff90fd5d971f936ad674802482ba441b6f09ba5e15fd8b39145582ca399" + +[[package]] +name = "futures-io" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de27142b013a8e869c14957e6d2edeef89e97c289e69d042ee3a49acd8b51789" + +[[package]] +name = "futures-sink" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f2032893cb734c7a05d85ce0cc8b8c4075278e93b24b66f9de99d6eb0fa8acc" + +[[package]] +name = "futures-task" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bdb66b5f09e22019b1ab0830f7785bcea8e7a42148683f99214f73f8ec21a626" +dependencies = [ + "once_cell", +] + +[[package]] +name = "futures-util" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8764574ff08b701a084482c3c7031349104b07ac897393010494beaa18ce32c6" +dependencies = [ + "futures-core", + "futures-io", + "futures-task", + "memchr", + "pin-project", + "pin-utils", + "slab", +] + +[[package]] +name = "gcc" +version = "0.3.55" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f5f3913fa0bfe7ee1fd8248b6b9f42a5af4b9d65ec2dd2c3c26132b950ecfc2" + +[[package]] +name = "generator" +version = "0.6.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "add72f17bb81521258fcc8a7a3245b1e184e916bfbe34f0ea89558f440df5c68" +dependencies = [ + "cc", + "libc", + "log", + "rustc_version", + "winapi 0.3.9", +] + +[[package]] +name = "generic-array" +version = "0.14.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac746a5f3bbfdadd6106868134545e684693d54d9d44f6e9588a7d54af0bf980" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7abc8dd8451921606d809ba32e95b6111925cd2906060d2dcc29c070220503eb" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "h2" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79b7246d7e4b979c03fa093da39cfb3617a96bbeee6310af63991668d7e843ff" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http", + "indexmap", + "log", + "slab", + "tokio", + "tokio-util", +] + +[[package]] +name = "hermit-abi" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9586eedd4ce6b3c498bc3b4dd92fc9f11166aa908a914071953768066c67909" +dependencies = [ + "libc", +] + +[[package]] +name = "http" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d569972648b2c512421b5f2a405ad6ac9666547189d0c5477a3f200f3e02f9" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http-body" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13d5ff830006f7646652e057693569bfe0d51760c0085a071769d142a205111b" +dependencies = [ + "bytes", + "http", +] + +[[package]] +name = "httparse" +version = "1.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd179ae861f0c2e53da70d892f5f3029f9594be0c41dc5269cd371691b1dc2f9" + +[[package]] +name = "hyper" +version = "0.13.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6e7655b9594024ad0ee439f3b5a7299369dc2a3f459b47c696f9ff676f9aa1f" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "httparse", + "itoa", + "log", + "pin-project", + "socket2", + "time", + "tokio", + "tower-service", + "want", +] + +[[package]] +name = "hyper-tls" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3adcd308402b9553630734e9c36b77a7e48b3821251ca2493e8cd596763aafaa" +dependencies = [ + "bytes", + "hyper", + "native-tls", + "tokio", + "tokio-tls", +] + +[[package]] +name = "idna" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02e2673c30ee86b5b96a9cb52ad15718aa1f966f5ab9ad54a8b95d5ca33120a9" +dependencies = [ + "matches", + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "indexmap" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c398b2b113b55809ceb9ee3e753fcbac793f1956663f3c36549c1346015c2afe" +dependencies = [ + "autocfg 1.0.0", +] + +[[package]] +name = "iovec" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2b3ea6ff95e175473f8ffe6a7eb7c00d054240321b84c57051175fe3c1e075e" +dependencies = [ + "libc", +] + +[[package]] +name = "itoa" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc6f3ad7b9d11a0c00842ff8de1b60ee58661048eb8049ed33c73594f359d7e6" + +[[package]] +name = "js-sys" +version = "0.3.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4b9172132a62451e56142bff9afc91c8e4a4500aa5b847da36815b63bfda916" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "kernel32-sys" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" +dependencies = [ + "winapi 0.2.8", + "winapi-build", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +dependencies = [ + "spin", +] + +[[package]] +name = "libc" +version = "0.2.71" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9457b06509d27052635f90d6466700c65095fdf75409b3fbdd903e988b886f49" + +[[package]] +name = "libm" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7d73b3f436185384286bd8098d17ec07c9a7d2388a6599f824d8502b529702a" + +[[package]] +name = "log" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14b6052be84e6b71ab17edffc2eeabf5c2c3ae1fdb464aae35ac50c67a44e1f7" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "loom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ecc775857611e1df29abba5c41355cdf540e7e9d4acfdf0f355eefee82330b7" +dependencies = [ + "cfg-if", + "generator", + "scoped-tls", +] + +[[package]] +name = "matches" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08" + +[[package]] +name = "memchr" +version = "2.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3728d817d99e5ac407411fa471ff9800a778d88a24685968b36824eaf4bee400" + +[[package]] +name = "mime" +version = "0.3.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" + +[[package]] +name = "mime_guess" +version = "2.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2684d4c2e97d99848d30b324b00c8fcc7e5c897b7cbb5819b09e7c90e8baf212" +dependencies = [ + "mime", + "unicase", +] + +[[package]] +name = "mio" +version = "0.6.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fce347092656428bc8eaf6201042cb551b8d67855af7374542a92a0fbfcac430" +dependencies = [ + "cfg-if", + "fuchsia-zircon", + "fuchsia-zircon-sys", + "iovec", + "kernel32-sys", + "libc", + "log", + "miow 0.2.1", + "net2", + "slab", + "winapi 0.2.8", +] + +[[package]] +name = "mio-named-pipes" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5e374eff525ce1c5b7687c4cef63943e7686524a387933ad27ca7ec43779cb3" +dependencies = [ + "log", + "mio", + "miow 0.3.5", + "winapi 0.3.9", +] + +[[package]] +name = "mio-uds" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "afcb699eb26d4332647cc848492bbc15eafb26f08d0304550d5aa1f612e066f0" +dependencies = [ + "iovec", + "libc", + "mio", +] + +[[package]] +name = "miow" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c1f2f3b1cf331de6896aabf6e9d55dca90356cc9960cca7eaaf408a355ae919" +dependencies = [ + "kernel32-sys", + "net2", + "winapi 0.2.8", + "ws2_32-sys", +] + +[[package]] +name = "miow" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07b88fb9795d4d36d62a012dfbf49a8f5cf12751f36d31a9dbe66d528e58979e" +dependencies = [ + "socket2", + "winapi 0.3.9", +] + +[[package]] +name = "native-tls" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b0d88c06fe90d5ee94048ba40409ef1d9315d86f6f38c2efdaad4fb50c58b2d" +dependencies = [ + "lazy_static", + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + +[[package]] +name = "net2" +version = "0.2.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ba7c918ac76704fb42afcbbb43891e72731f3dcca3bef2a19786297baf14af7" +dependencies = [ + "cfg-if", + "libc", + "winapi 0.3.9", +] + +[[package]] +name = "num-bigint" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "090c7f9998ee0ff65aa5b723e4009f7b217707f1fb5ea551329cc4d6231fb304" +dependencies = [ + "autocfg 1.0.0", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-bigint-dig" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3d03c330f9f7a2c19e3c0b42698e48141d0809c78cd9b6219f85bd7d7e892aa" +dependencies = [ + "autocfg 0.1.7", + "byteorder", + "lazy_static", + "libm", + "num-integer", + "num-iter", + "num-traits", + "rand 0.7.3", + "serde", + "smallvec", + "zeroize", +] + +[[package]] +name = "num-integer" +version = "0.1.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d59457e662d541ba17869cf51cf177c0b5f0cbf476c66bdc90bf1edac4f875b" +dependencies = [ + "autocfg 1.0.0", + "num-traits", +] + +[[package]] +name = "num-iter" +version = "0.1.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a6e6b7c748f995c4c29c5f5ae0248536e04a5739927c74ec0fa564805094b9f" +dependencies = [ + "autocfg 1.0.0", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac267bcc07f48ee5f8935ab0d24f316fb722d7a1292e2913f0cc196b29ffd611" +dependencies = [ + "autocfg 1.0.0", +] + +[[package]] +name = "num_cpus" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "once_cell" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b631f7e854af39a1739f401cf34a8a013dfe09eac4fa4dba91e9768bd28168d" + +[[package]] +name = "opaque-debug" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" + +[[package]] +name = "openssl" +version = "0.10.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d575eff3665419f9b83678ff2815858ad9d11567e082f5ac1814baba4e2bcb4" +dependencies = [ + "bitflags", + "cfg-if", + "foreign-types", + "lazy_static", + "libc", + "openssl-sys", +] + +[[package]] +name = "openssl-probe" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77af24da69f9d9341038eba93a073b1fdaaa1b788221b00a69bce9e762cb32de" + +[[package]] +name = "openssl-sys" +version = "0.9.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a842db4709b604f0fe5d1170ae3565899be2ad3d9cbc72dedc789ac0511f78de" +dependencies = [ + "autocfg 1.0.0", + "cc", + "libc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "pem" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59698ea79df9bf77104aefd39cc3ec990cb9693fb59c3b0a70ddf2646fdffb4b" +dependencies = [ + "base64", + "once_cell", + "regex", +] + +[[package]] +name = "percent-encoding" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" + +[[package]] +name = "pin-project" +version = "0.4.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12e3a6cdbfe94a5e4572812a0201f8c0ed98c1c452c7b8563ce2276988ef9c17" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "0.4.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a0ffd45cf79d88737d7cc85bfd5d2894bee1139b356e616fe85dc389c61aaf7" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "pin-project-lite" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282adbf10f2698a7a77f8e983a74b2d18176c19a7fd32a45446139ae7b02b715" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "pkg-config" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05da548ad6865900e60eaba7f589cc0783590a92e940c26953ff81ddbab2d677" + +[[package]] +name = "ppv-lite86" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "237a5ed80e274dbc66f86bd59c1e25edc039660be53194b5fe0a482e0f2612ea" + +[[package]] +name = "proc-macro2" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "beae6331a816b1f65d04c45b078fd8e6c93e8071771f41b8163255bbd8d7c8fa" +dependencies = [ + "unicode-xid", +] + +[[package]] +name = "quote" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa563d17ecb180e500da1cfd2b028310ac758de548efdd203e18f283af693f37" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.3.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64ac302d8f83c0c1974bf758f6b041c6c8ada916fbb44a609158ca8b064cc76c" +dependencies = [ + "libc", + "rand 0.4.6", +] + +[[package]] +name = "rand" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "552840b97013b1a26992c11eac34bdd778e464601a4c2054b5f0bff7c6761293" +dependencies = [ + "fuchsia-cprng", + "libc", + "rand_core 0.3.1", + "rdrand", + "winapi 0.3.9", +] + +[[package]] +name = "rand" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" +dependencies = [ + "getrandom", + "libc", + "rand_chacha", + "rand_core 0.5.1", + "rand_hc", +] + +[[package]] +name = "rand_chacha" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" +dependencies = [ + "ppv-lite86", + "rand_core 0.5.1", +] + +[[package]] +name = "rand_core" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b" +dependencies = [ + "rand_core 0.4.2", +] + +[[package]] +name = "rand_core" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc" + +[[package]] +name = "rand_core" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" +dependencies = [ + "getrandom", +] + +[[package]] +name = "rand_hc" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" +dependencies = [ + "rand_core 0.5.1", +] + +[[package]] +name = "rdrand" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2" +dependencies = [ + "rand_core 0.3.1", +] + +[[package]] +name = "redox_syscall" +version = "0.1.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2439c63f3f6139d1b57529d16bc3b8bb855230c8efcc5d3a896c8bea7c3b1e84" + +[[package]] +name = "regex" +version = "1.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c3780fcf44b193bc4d09f36d2a3c87b251da4a046c87795a0d35f4f927ad8e6" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", + "thread_local", +] + +[[package]] +name = "regex-syntax" +version = "0.6.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26412eb97c6b088a6997e05f69403a802a92d520de2f8e63c2b65f9e0f47c4e8" + +[[package]] +name = "remove_dir_all" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" +dependencies = [ + "winapi 0.3.9", +] + +[[package]] +name = "reqwest" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b82c9238b305f26f53443e3a4bc8528d64b8d0bee408ec949eb7bf5635ec680" +dependencies = [ + "base64", + "bytes", + "encoding_rs", + "futures-core", + "futures-util", + "http", + "http-body", + "hyper", + "hyper-tls", + "js-sys", + "lazy_static", + "log", + "mime", + "mime_guess", + "native-tls", + "percent-encoding", + "pin-project-lite", + "serde", + "serde_json", + "serde_urlencoded", + "tokio", + "tokio-tls", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "winreg", +] + +[[package]] +name = "rsa" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3648b669b10afeab18972c105e284a7b953a669b0be3514c27f9b17acab2f9cd" +dependencies = [ + "byteorder", + "digest", + "lazy_static", + "num-bigint-dig", + "num-integer", + "num-iter", + "num-traits", + "pem", + "rand 0.7.3", + "sha2", + "simple_asn1", + "subtle", + "thiserror", + "zeroize", +] + +[[package]] +name = "rust-crypto" +version = "0.2.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f76d05d3993fd5f4af9434e8e436db163a12a9d40e1a58a726f27a01dfd12a2a" +dependencies = [ + "gcc", + "libc", + "rand 0.3.23", + "rustc-serialize", + "time", +] + +[[package]] +name = "rustc-serialize" +version = "0.3.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcf128d1287d2ea9d80910b5f1120d0b8eede3fbf1abe91c40d39ea7d51e6fda" + +[[package]] +name = "rustc_version" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" +dependencies = [ + "semver", +] + +[[package]] +name = "ryu" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e" + +[[package]] +name = "schannel" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f05ba609c234e60bee0d547fe94a4c7e9da733d1c962cf6e59efa4cd9c8bc75" +dependencies = [ + "lazy_static", + "winapi 0.3.9", +] + +[[package]] +name = "scoped-tls" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "332ffa32bf586782a3efaeb58f127980944bbc8c4d6913a86107ac2a5ab24b28" + +[[package]] +name = "sdk" +version = "0.1.0" +dependencies = [ + "base64", + "chrono", + "reqwest", + "rsa", + "rust-crypto", + "serde", + "serde_json", + "tokio", +] + +[[package]] +name = "sdk-test" +version = "0.1.0" +dependencies = [ + "sdk", +] + +[[package]] +name = "security-framework" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64808902d7d99f78eaddd2b4e2509713babc3dc3c85ad6f4c447680f3c01e535" +dependencies = [ + "bitflags", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17bf11d99252f512695eb468de5516e5cf75455521e69dfe343f3b74e4748405" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "semver" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" +dependencies = [ + "semver-parser", +] + +[[package]] +name = "semver-parser" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" + +[[package]] +name = "serde" +version = "1.0.114" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5317f7588f0a5078ee60ef675ef96735a1442132dc645eb1d12c018620ed8cd3" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.114" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a0be94b04690fbaed37cddffc5c134bf537c8e3329d53e982fe04c374978f8e" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3433e879a558dde8b5e8feb2a04899cf34fdde1fafb894687e52105fc1162ac3" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serde_urlencoded" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ec5d77e2d4c73717816afac02670d5c4f534ea95ed430442cad02e7a6e32c97" +dependencies = [ + "dtoa", + "itoa", + "serde", + "url", +] + +[[package]] +name = "sha2" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2933378ddfeda7ea26f48c555bdad8bb446bf8a3d17832dc83e380d444cfb8c1" +dependencies = [ + "block-buffer", + "cfg-if", + "cpuid-bool", + "digest", + "opaque-debug", +] + +[[package]] +name = "signal-hook-registry" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94f478ede9f64724c5d173d7bb56099ec3e2d9fc2774aac65d34b8b890405f41" +dependencies = [ + "arc-swap", + "libc", +] + +[[package]] +name = "simple_asn1" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b25ecba7165254f0c97d6c22a64b1122a03634b18d20a34daf21e18f892e618" +dependencies = [ + "chrono", + "num-bigint", + "num-traits", +] + +[[package]] +name = "slab" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8" + +[[package]] +name = "smallvec" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7cb5678e1615754284ec264d9bb5b4c27d2018577fd90ac0ceb578591ed5ee4" + +[[package]] +name = "socket2" +version = "0.3.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03088793f677dce356f3ccc2edb1b314ad191ab702a5de3faf49304f7e104918" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "winapi 0.3.9", +] + +[[package]] +name = "spin" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" + +[[package]] +name = "subtle" +version = "2.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "502d53007c02d7605a05df1c1a73ee436952781653da5d0bf57ad608f66932c1" + +[[package]] +name = "syn" +version = "1.0.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8d5d96e8cbb005d6959f119f773bfaebb5684296108fb32600c00cde305b2cd" +dependencies = [ + "proc-macro2", + "quote", + "unicode-xid", +] + +[[package]] +name = "synstructure" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b834f2d66f734cb897113e34aaff2f1ab4719ca946f9a7358dba8f8064148701" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "unicode-xid", +] + +[[package]] +name = "tempfile" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a6e24d9338a0a5be79593e2fa15a648add6138caa803e2d5bc782c371732ca9" +dependencies = [ + "cfg-if", + "libc", + "rand 0.7.3", + "redox_syscall", + "remove_dir_all", + "winapi 0.3.9", +] + +[[package]] +name = "thiserror" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dfdd070ccd8ccb78f4ad66bf1982dc37f620ef696c6b5028fe2ed83dd3d0d08" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd80fc12f73063ac132ac92aceea36734f04a1d93c1240c6944e23a3b8841793" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "thread_local" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d40c6d1b69745a6ec6fb1ca717914848da4b44ae29d9b3080cbee91d72a69b14" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "time" +version = "0.1.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca8a50ef2360fbd1eeb0ecd46795a87a19024eb4b53c5dc916ca1fd95fe62438" +dependencies = [ + "libc", + "winapi 0.3.9", +] + +[[package]] +name = "tinyvec" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53953d2d3a5ad81d9f844a32f14ebb121f50b650cd59d0ee2a07cf13c617efed" + +[[package]] +name = "tokio" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d099fa27b9702bed751524694adbe393e18b36b204da91eb1cbbbbb4a5ee2d58" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "iovec", + "lazy_static", + "libc", + "memchr", + "mio", + "mio-named-pipes", + "mio-uds", + "num_cpus", + "pin-project-lite", + "signal-hook-registry", + "slab", + "tokio-macros", + "winapi 0.3.9", +] + +[[package]] +name = "tokio-macros" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0c3acc6aa564495a0f2e1d59fab677cd7f81a19994cfc7f3ad0e64301560389" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tokio-tls" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a70f4fcd7b3b24fb194f837560168208f669ca8cb70d0c4b862944452396343" +dependencies = [ + "native-tls", + "tokio", +] + +[[package]] +name = "tokio-util" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be8242891f2b6cbef26a2d7e8605133c2c554cd35b3e4948ea892d6d68436499" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "log", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tower-service" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e987b6bf443f4b5b3b6f38704195592cca41c5bb7aedd3c3693c7081f8289860" + +[[package]] +name = "try-lock" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e604eb7b43c06650e854be16a2a03155743d3752dd1c943f6829e26b7a36e382" + +[[package]] +name = "typenum" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "373c8a200f9e67a0c95e62a4f52fbf80c23b4381c05a17845531982fa99e6b33" + +[[package]] +name = "unicase" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50f37be617794602aabbeee0be4f259dc1778fabe05e2d67ee8f79326d5cb4f6" +dependencies = [ + "version_check", +] + +[[package]] +name = "unicode-bidi" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49f2bd0c6468a8230e1db229cff8029217cf623c767ea5d60bfbd42729ea54d5" +dependencies = [ + "matches", +] + +[[package]] +name = "unicode-normalization" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fb19cf769fa8c6a80a162df694621ebeb4dafb606470b2b2fce0be40a98a977" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "unicode-xid" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564" + +[[package]] +name = "url" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "829d4a8476c35c9bf0bbce5a3b23f4106f79728039b726d292bb93bc106787cb" +dependencies = [ + "idna", + "matches", + "percent-encoding", +] + +[[package]] +name = "vcpkg" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6454029bf181f092ad1b853286f23e2c507d8e8194d01d92da4a55c274a5508c" + +[[package]] +name = "version_check" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5a972e5669d67ba988ce3dc826706fb0a8b01471c088cb0b6110b805cc36aed" + +[[package]] +name = "want" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0" +dependencies = [ + "log", + "try-lock", +] + +[[package]] +name = "wasi" +version = "0.9.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" + +[[package]] +name = "wasm-bindgen" +version = "0.2.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a634620115e4a229108b71bde263bb4220c483b3f07f5ba514ee8d15064c4c2" +dependencies = [ + "cfg-if", + "serde", + "serde_json", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e53963b583d18a5aa3aaae4b4c1cb535218246131ba22a71f05b518098571df" +dependencies = [ + "bumpalo", + "lazy_static", + "log", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dba48d66049d2a6cc8488702e7259ab7afc9043ad0dc5448444f46f2a453b362" +dependencies = [ + "cfg-if", + "js-sys", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fcfd5ef6eec85623b4c6e844293d4516470d8f19cd72d0d12246017eb9060b8" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9adff9ee0e94b926ca81b57f57f86d5545cdcb1d259e21ec9bdd95b901754c75" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f7b90ea6c632dd06fd765d44542e234d5e63d9bb917ecd64d79778a13bd79ae" + +[[package]] +name = "web-sys" +version = "0.3.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "863539788676619aac1a23e2df3655e96b32b0e05eb72ca34ba045ad573c625d" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "winapi" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-build" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "winreg" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0120db82e8a1e0b9fb3345a539c478767c0048d842860994d96113d5b667bd69" +dependencies = [ + "winapi 0.3.9", +] + +[[package]] +name = "ws2_32-sys" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d59cefebd0c892fa2dd6de581e937301d8552cb44489cdff035c6187cb63fa5e" +dependencies = [ + "winapi 0.2.8", + "winapi-build", +] + +[[package]] +name = "zeroize" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3cbac2ed2ba24cc90f5e06485ac8c7c1e5449fe8911aef4d8877218af021a5b8" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de251eec69fc7c1bc3923403d18ececb929380e016afe103da75f396704f8ca2" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] diff --git a/sop-sdk/sdk-rust/sdk-test/Cargo.toml b/sop-sdk/sdk-rust/sdk-test/Cargo.toml new file mode 100644 index 00000000..cfbadb30 --- /dev/null +++ b/sop-sdk/sdk-rust/sdk-test/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "sdk-test" +version = "0.1.0" +authors = ["thc"] +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +# 依赖sdk模块 +sdk = { path = "../sdk"} \ No newline at end of file diff --git a/sop-sdk/sdk-rust/sdk-test/aa.txt b/sop-sdk/sdk-rust/sdk-test/aa.txt new file mode 100644 index 00000000..524527cb --- /dev/null +++ b/sop-sdk/sdk-rust/sdk-test/aa.txt @@ -0,0 +1 @@ +hello你好123 \ No newline at end of file diff --git a/sop-sdk/sdk-rust/sdk-test/bb.txt b/sop-sdk/sdk-rust/sdk-test/bb.txt new file mode 100644 index 00000000..85f8e329 --- /dev/null +++ b/sop-sdk/sdk-rust/sdk-test/bb.txt @@ -0,0 +1 @@ +文件bb的内容 \ No newline at end of file diff --git a/sop-sdk/sdk-rust/sdk-test/src/main.rs b/sop-sdk/sdk-rust/sdk-test/src/main.rs new file mode 100644 index 00000000..759e6b93 --- /dev/null +++ b/sop-sdk/sdk-rust/sdk-test/src/main.rs @@ -0,0 +1,76 @@ +extern crate sdk; + +use std::collections::HashMap; + +use sdk::client::OpenClient; +use sdk::http::UploadFile; +use sdk::request::BaseRequest; +use sdk::request::memberinfoget::MemberInfoGetRequest; +use sdk::response::memberinfoget::MemberInfoGetResponse; +use std::env; + +fn main() { + // 创建请求客户端 + let client = OpenClient { + // 应用ID + app_id: "201904035630907729292csharp", + // 应用私钥 + private_key: "MIIEowIBAAKCAQEA5+OvJxeSzf44NxQ/cl7Ii+BzPg2k6sRcvH4ffOtU5Dzq1/oEvg02nxIhmwOHBZmjbmuUu0aLsfglUTAwqfXftfAKZidshsgj9NNh0/kxk0avRZ1UoljWGz/FxVZA0ogbxxhohPZ9jWcD+eBQcIwF2DtHfAJqWWZrYFnCMeHD8mPzxo2kwXSvDzi0vf9I2tKiYvNG26a9FqeYtPOoi81sdS3+70HOMdxP8ejXtyfnKpKz7Dx506LCIRS5moWS3Q5eTLV3NGX/1CSJ8wpQA2DAQTjVhX5eVu7Yqz12t8W+sjWM/tHUR6cgwYYR10p7tSCeCPzkigjGxKm4cYXWtATQJQIDAQABAoIBAHFDsgrrJca+NKEan77ycwx3jnKx4WrWjOF4zVKL9AQjiSYDNgvKknJyPb3kpC/lEoHdxGERHSzJoxib7DkoIqRQYhPxj73pxj5QfYk3P7LLJNNg/LTrpXDb3nL8JV9wIflGf87qQvstZTDJEyFWE4jBs7Hr0BxovWvri8InnzkmERJ1cbGJgNHe1Y3Zo2tw0yaHxQCxLuajP+notRZhD9bEp7uKeI0w9AvlW6k8m/7y10F0BK/TlyW8rQiEC391yOiRYoMcUh4hd2Q9bMx3jngZgX8PXIvZZcup4/pvWlv1alwhB2tsnLdazP62r1MO80vLyLunzGO+7WwCjEYlVaECgYEA+lQRFmbhKaPuAuXMtY31Fbga8nedka5TjnEV7+/kX+yowE2OlNujF+ZG8UTddTxAGv56yVNi/mjRlgD74j8z0eOsgvOq9mwbCrgLhLo51H9O/wAxtb+hBKtC5l50pBr4gER6d8W6EQNTSGojnMIaLXTkAZ5Qf6Z8e2HFVdOn0X0CgYEA7SSrTokwzukt5KldNu5ukyyd+C3D1i6orbg6qD73EP9CfNMfGSBn7dDv9wMSJH01+Ty+RgTROgtjGRDbMJWnfbdt/61NePr9ar5sb6Nbsf7/I0w7cZF5dsaFYgzaOfQYquzXPbLQHkpMT64bqpv/Mwy4F2lFvaYWY5fA4pC2uckCgYEAg75Ym9ybJaoTqky8ttQ2Jy8UZ4VSVQhVC0My02sCWwWXLlXi8y7An+Rec73Ve0yxREOn5WrQT6pkmzh7V/ABWrYi5WxODpCIjtSbo0fLBa3Wqle00b0/hdCITetqIa/cFs1zUrOqICgK3bKWeXqiAkhhcwSZwwSgwOKM04Wn7ZUCgYBvhHX2mbdVJfyJ8kc+hMOE/E9RHRxiBVEXWHJlGi8PVCqNDq8qHr4g7Mdbzprig+s0yKblwHAvrpkseWvKHiZEjVTyDipHgShY4TGXEigVvUd37uppTrLi8xpYcJjS9gH/px7VCdiq1d+q/MJP6coJ1KphgATm2UrgDMYNBWaYWQKBgEHRxrmER7btUF60/YgcqPHFc8RpYQB2ZZE0kyKGDqk2Data1XYUY6vsPAU28yRLAaWr/D2H17iyLkxP80VLm6QhifxCadv90Q/Wl1DFfOJQMW6avyQ0so6G0wFq/LJxaFK4iLXQn1RJnmTp6BYiJMmK2BhFbRzw8ssMoF6ad2rr", + // 请求地址 + url: "http://localhost:8081", + }; + // 业务参数 + let mut biz_model = HashMap::new(); + biz_model.insert("name", "jim".to_string()); + biz_model.insert("address", "xx".to_string()); + biz_model.insert("age", "22".to_string()); + + let mut files = vec![]; + + // 添加上传文件 + /*let mod_dir = env::current_dir().unwrap().display().to_string(); + files = vec![ + UploadFile { name:"file1", path: format!("{}/{}", &mod_dir, "aa.txt")}, + UploadFile { name:"file2", path: format!("{}/{}", &mod_dir, "bb.txt") }, + ];*/ + + + // 创建请求,设置业务参数 + let request = MemberInfoGetRequest { + base: BaseRequest { biz_model: biz_model, files: files } + }; + + // 发送请求 + let response:MemberInfoGetResponse = client.execute(request); + + // 成功 + if response.sub_code.len() == 0 { + println!("resp:{:#?}", response) + } else { + println!("error:{:#?}", response) + } +} + +#[cfg(test)] +mod tests { + use sdk::sign::{HashType, SignUtil}; + use sdk::http::HttpTool; + use std::collections::HashMap; + + #[test] + fn it_works() { + let content = "123"; + let private_key = "MIICXAIBAAKBgQCHJlAPN+1dCbgc3HiahQkT2W/skecGWOCkSX4CPvEc8oIk6544\nxihEwShHnfrapiQdF2fndv5agrhg4FyOHheST42L5MnCk+4Km+mWm5GDvmFS7Sa2\naZ5o3regY0MUoJ7D74dYjE3UYFuTujAXiXjGpAwa9qOcKotov5LCkSfUeQIDAQAB\nAoGAB1cyw8LYRQSHQCUO9Wiaq730jPNHSrJW4EGAIz/XMYjv/fCgx0lnDEX4CbzI\nUGoz/bME4R721YRyXoutJ0h14/cGrt/TEn/TMI0xnISzJHr8VSlyBkQEdfO/W3LO\nqjs/UYq2Bz4+kJROJHreM+7d5hiIWLzLBlyI8cSU92ySmHECQQDwju2SoRu88kQP\n1qr4seZyKQa8DHTVyCoa6LtPLXyJsdgWgY4KyqJHwMUumEC2Zhhu833CR0ZXbfta\nuQDmwAVJAkEAj9M225jrPasaD5vPO7thxtEqgV4F/ZyNKH0Z8bDH27KaKkQ+8GMt\nkxwKVckZXs2bMvg/6tCiDZkWAxawNrvFsQJBANmTrPWOmpQPW9gnhYRjA9gFm33C\nlno2DT9BeQloTtgL7zKMA3lnRdg4VyCJvR48waS4vupVpR228D1iT5pl22ECQF1M\nJUzkcM0rPheb+h2EW1QOgWU0Keyvbj4ykO7gv3T78dezN6TWoUzJpsapUiTWeXPh\n6AyZ1FW/1bChOiP3QLECQGAbObmsYlN0bjzPYChwWYeYjErXuv51a44GZCNWinFw\nGGiHU9ZAqF8RzmBVW4htwj0j/Yry/V1Sp0uoP0zu3uA="; + let sign = SignUtil::rsa_sign(content, private_key, HashType::Sha256); + println!("sign:{}", sign); + } + + #[test] + fn test_http() { + let mut map = HashMap::new(); + map.insert("aaa", "bbb"); + let response = HttpTool::get("http://baidu.com", &map, &HashMap::new()); + println!("response:{:#?}", response); + } + +} \ No newline at end of file diff --git a/sop-sdk/sdk-rust/sdk/.gitignore b/sop-sdk/sdk-rust/sdk/.gitignore new file mode 100644 index 00000000..ea8c4bf7 --- /dev/null +++ b/sop-sdk/sdk-rust/sdk/.gitignore @@ -0,0 +1 @@ +/target diff --git a/sop-sdk/sdk-rust/sdk/Cargo.lock b/sop-sdk/sdk-rust/sdk/Cargo.lock new file mode 100644 index 00000000..1f2c6d7f --- /dev/null +++ b/sop-sdk/sdk-rust/sdk/Cargo.lock @@ -0,0 +1,1551 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +[[package]] +name = "aho-corasick" +version = "0.7.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "043164d8ba5c4c3035fec9bbee8647c0261d788f3474306f93bb65901cae0e86" +dependencies = [ + "memchr", +] + +[[package]] +name = "arc-swap" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d25d88fd6b8041580a654f9d0c581a047baee2b3efee13275f2fc392fc75034" + +[[package]] +name = "autocfg" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d49d90015b3c36167a20fe2810c5cd875ad504b39cff3d4eae7977e6b7c1cb2" + +[[package]] +name = "autocfg" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d" + +[[package]] +name = "base64" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3441f0f7b02788e948e47f457ca01f1d7e6d92c693bc132c22b087d3141c03ff" + +[[package]] +name = "bitflags" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" + +[[package]] +name = "block-buffer" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" +dependencies = [ + "generic-array", +] + +[[package]] +name = "bumpalo" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e8c087f005730276d1096a652e92a8bacee2e2472bcc9715a74d2bec38b5820" + +[[package]] +name = "byteorder" +version = "1.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08c48aae112d48ed9f069b33538ea9e3e90aa263cfa3d1c24309612b1f7472de" + +[[package]] +name = "bytes" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "118cf036fbb97d0816e3c34b2d7a1e8cfc60f68fcf63d550ddbe9bd5f59c213b" +dependencies = [ + "loom", +] + +[[package]] +name = "cc" +version = "1.0.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77c1f1d60091c1b73e2b1f4560ab419204b178e625fa945ded7b660becd2bd46" + +[[package]] +name = "cfg-if" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" + +[[package]] +name = "chrono" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80094f509cf8b5ae86a4966a39b3ff66cd7e2a3e594accec3743ff3fabeab5b2" +dependencies = [ + "num-integer", + "num-traits", + "time", +] + +[[package]] +name = "core-foundation" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57d24c7a13c43e870e37c1556b74555437870a04514f7685f5b354e090567171" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3a71ab494c0b5b860bdc8407ae08978052417070c2ced38573a9157ad75b8ac" + +[[package]] +name = "cpuid-bool" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d375c433320f6c5057ae04a04376eef4d04ce2801448cf8863a78da99107be4" + +[[package]] +name = "digest" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" +dependencies = [ + "generic-array", +] + +[[package]] +name = "dtoa" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "134951f4028bdadb9b84baf4232681efbf277da25144b9b0ad65df75946c422b" + +[[package]] +name = "encoding_rs" +version = "0.8.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8ac63f94732332f44fe654443c46f6375d1939684c17b0afb6cb56b0456e171" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + +[[package]] +name = "fuchsia-cprng" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" + +[[package]] +name = "fuchsia-zircon" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82" +dependencies = [ + "bitflags", + "fuchsia-zircon-sys", +] + +[[package]] +name = "fuchsia-zircon-sys" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7" + +[[package]] +name = "futures-channel" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f366ad74c28cca6ba456d95e6422883cfb4b252a83bed929c83abfdbbf2967d5" +dependencies = [ + "futures-core", +] + +[[package]] +name = "futures-core" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59f5fff90fd5d971f936ad674802482ba441b6f09ba5e15fd8b39145582ca399" + +[[package]] +name = "futures-io" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de27142b013a8e869c14957e6d2edeef89e97c289e69d042ee3a49acd8b51789" + +[[package]] +name = "futures-sink" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f2032893cb734c7a05d85ce0cc8b8c4075278e93b24b66f9de99d6eb0fa8acc" + +[[package]] +name = "futures-task" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bdb66b5f09e22019b1ab0830f7785bcea8e7a42148683f99214f73f8ec21a626" +dependencies = [ + "once_cell", +] + +[[package]] +name = "futures-util" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8764574ff08b701a084482c3c7031349104b07ac897393010494beaa18ce32c6" +dependencies = [ + "futures-core", + "futures-io", + "futures-task", + "memchr", + "pin-project", + "pin-utils", + "slab", +] + +[[package]] +name = "gcc" +version = "0.3.55" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f5f3913fa0bfe7ee1fd8248b6b9f42a5af4b9d65ec2dd2c3c26132b950ecfc2" + +[[package]] +name = "generator" +version = "0.6.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "add72f17bb81521258fcc8a7a3245b1e184e916bfbe34f0ea89558f440df5c68" +dependencies = [ + "cc", + "libc", + "log", + "rustc_version", + "winapi 0.3.9", +] + +[[package]] +name = "generic-array" +version = "0.14.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac746a5f3bbfdadd6106868134545e684693d54d9d44f6e9588a7d54af0bf980" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7abc8dd8451921606d809ba32e95b6111925cd2906060d2dcc29c070220503eb" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "h2" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79b7246d7e4b979c03fa093da39cfb3617a96bbeee6310af63991668d7e843ff" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http", + "indexmap", + "log", + "slab", + "tokio", + "tokio-util", +] + +[[package]] +name = "hermit-abi" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9586eedd4ce6b3c498bc3b4dd92fc9f11166aa908a914071953768066c67909" +dependencies = [ + "libc", +] + +[[package]] +name = "http" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d569972648b2c512421b5f2a405ad6ac9666547189d0c5477a3f200f3e02f9" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http-body" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13d5ff830006f7646652e057693569bfe0d51760c0085a071769d142a205111b" +dependencies = [ + "bytes", + "http", +] + +[[package]] +name = "httparse" +version = "1.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd179ae861f0c2e53da70d892f5f3029f9594be0c41dc5269cd371691b1dc2f9" + +[[package]] +name = "hyper" +version = "0.13.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6e7655b9594024ad0ee439f3b5a7299369dc2a3f459b47c696f9ff676f9aa1f" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "httparse", + "itoa", + "log", + "pin-project", + "socket2", + "time", + "tokio", + "tower-service", + "want", +] + +[[package]] +name = "hyper-tls" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3adcd308402b9553630734e9c36b77a7e48b3821251ca2493e8cd596763aafaa" +dependencies = [ + "bytes", + "hyper", + "native-tls", + "tokio", + "tokio-tls", +] + +[[package]] +name = "idna" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02e2673c30ee86b5b96a9cb52ad15718aa1f966f5ab9ad54a8b95d5ca33120a9" +dependencies = [ + "matches", + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "indexmap" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c398b2b113b55809ceb9ee3e753fcbac793f1956663f3c36549c1346015c2afe" +dependencies = [ + "autocfg 1.0.0", +] + +[[package]] +name = "iovec" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2b3ea6ff95e175473f8ffe6a7eb7c00d054240321b84c57051175fe3c1e075e" +dependencies = [ + "libc", +] + +[[package]] +name = "itoa" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc6f3ad7b9d11a0c00842ff8de1b60ee58661048eb8049ed33c73594f359d7e6" + +[[package]] +name = "js-sys" +version = "0.3.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4b9172132a62451e56142bff9afc91c8e4a4500aa5b847da36815b63bfda916" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "kernel32-sys" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" +dependencies = [ + "winapi 0.2.8", + "winapi-build", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +dependencies = [ + "spin", +] + +[[package]] +name = "libc" +version = "0.2.71" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9457b06509d27052635f90d6466700c65095fdf75409b3fbdd903e988b886f49" + +[[package]] +name = "libm" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7d73b3f436185384286bd8098d17ec07c9a7d2388a6599f824d8502b529702a" + +[[package]] +name = "log" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14b6052be84e6b71ab17edffc2eeabf5c2c3ae1fdb464aae35ac50c67a44e1f7" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "loom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ecc775857611e1df29abba5c41355cdf540e7e9d4acfdf0f355eefee82330b7" +dependencies = [ + "cfg-if", + "generator", + "scoped-tls", +] + +[[package]] +name = "matches" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08" + +[[package]] +name = "memchr" +version = "2.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3728d817d99e5ac407411fa471ff9800a778d88a24685968b36824eaf4bee400" + +[[package]] +name = "mime" +version = "0.3.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" + +[[package]] +name = "mime_guess" +version = "2.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2684d4c2e97d99848d30b324b00c8fcc7e5c897b7cbb5819b09e7c90e8baf212" +dependencies = [ + "mime", + "unicase", +] + +[[package]] +name = "mio" +version = "0.6.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fce347092656428bc8eaf6201042cb551b8d67855af7374542a92a0fbfcac430" +dependencies = [ + "cfg-if", + "fuchsia-zircon", + "fuchsia-zircon-sys", + "iovec", + "kernel32-sys", + "libc", + "log", + "miow 0.2.1", + "net2", + "slab", + "winapi 0.2.8", +] + +[[package]] +name = "mio-named-pipes" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5e374eff525ce1c5b7687c4cef63943e7686524a387933ad27ca7ec43779cb3" +dependencies = [ + "log", + "mio", + "miow 0.3.5", + "winapi 0.3.9", +] + +[[package]] +name = "mio-uds" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "afcb699eb26d4332647cc848492bbc15eafb26f08d0304550d5aa1f612e066f0" +dependencies = [ + "iovec", + "libc", + "mio", +] + +[[package]] +name = "miow" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c1f2f3b1cf331de6896aabf6e9d55dca90356cc9960cca7eaaf408a355ae919" +dependencies = [ + "kernel32-sys", + "net2", + "winapi 0.2.8", + "ws2_32-sys", +] + +[[package]] +name = "miow" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07b88fb9795d4d36d62a012dfbf49a8f5cf12751f36d31a9dbe66d528e58979e" +dependencies = [ + "socket2", + "winapi 0.3.9", +] + +[[package]] +name = "native-tls" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b0d88c06fe90d5ee94048ba40409ef1d9315d86f6f38c2efdaad4fb50c58b2d" +dependencies = [ + "lazy_static", + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + +[[package]] +name = "net2" +version = "0.2.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ba7c918ac76704fb42afcbbb43891e72731f3dcca3bef2a19786297baf14af7" +dependencies = [ + "cfg-if", + "libc", + "winapi 0.3.9", +] + +[[package]] +name = "num-bigint" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "090c7f9998ee0ff65aa5b723e4009f7b217707f1fb5ea551329cc4d6231fb304" +dependencies = [ + "autocfg 1.0.0", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-bigint-dig" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3d03c330f9f7a2c19e3c0b42698e48141d0809c78cd9b6219f85bd7d7e892aa" +dependencies = [ + "autocfg 0.1.7", + "byteorder", + "lazy_static", + "libm", + "num-integer", + "num-iter", + "num-traits", + "rand 0.7.3", + "serde", + "smallvec", + "zeroize", +] + +[[package]] +name = "num-integer" +version = "0.1.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d59457e662d541ba17869cf51cf177c0b5f0cbf476c66bdc90bf1edac4f875b" +dependencies = [ + "autocfg 1.0.0", + "num-traits", +] + +[[package]] +name = "num-iter" +version = "0.1.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a6e6b7c748f995c4c29c5f5ae0248536e04a5739927c74ec0fa564805094b9f" +dependencies = [ + "autocfg 1.0.0", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac267bcc07f48ee5f8935ab0d24f316fb722d7a1292e2913f0cc196b29ffd611" +dependencies = [ + "autocfg 1.0.0", +] + +[[package]] +name = "num_cpus" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "once_cell" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b631f7e854af39a1739f401cf34a8a013dfe09eac4fa4dba91e9768bd28168d" + +[[package]] +name = "opaque-debug" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" + +[[package]] +name = "openssl" +version = "0.10.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d575eff3665419f9b83678ff2815858ad9d11567e082f5ac1814baba4e2bcb4" +dependencies = [ + "bitflags", + "cfg-if", + "foreign-types", + "lazy_static", + "libc", + "openssl-sys", +] + +[[package]] +name = "openssl-probe" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77af24da69f9d9341038eba93a073b1fdaaa1b788221b00a69bce9e762cb32de" + +[[package]] +name = "openssl-sys" +version = "0.9.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a842db4709b604f0fe5d1170ae3565899be2ad3d9cbc72dedc789ac0511f78de" +dependencies = [ + "autocfg 1.0.0", + "cc", + "libc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "pem" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59698ea79df9bf77104aefd39cc3ec990cb9693fb59c3b0a70ddf2646fdffb4b" +dependencies = [ + "base64", + "once_cell", + "regex", +] + +[[package]] +name = "percent-encoding" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" + +[[package]] +name = "pin-project" +version = "0.4.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12e3a6cdbfe94a5e4572812a0201f8c0ed98c1c452c7b8563ce2276988ef9c17" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "0.4.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a0ffd45cf79d88737d7cc85bfd5d2894bee1139b356e616fe85dc389c61aaf7" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "pin-project-lite" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282adbf10f2698a7a77f8e983a74b2d18176c19a7fd32a45446139ae7b02b715" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "pkg-config" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05da548ad6865900e60eaba7f589cc0783590a92e940c26953ff81ddbab2d677" + +[[package]] +name = "ppv-lite86" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "237a5ed80e274dbc66f86bd59c1e25edc039660be53194b5fe0a482e0f2612ea" + +[[package]] +name = "proc-macro2" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "beae6331a816b1f65d04c45b078fd8e6c93e8071771f41b8163255bbd8d7c8fa" +dependencies = [ + "unicode-xid", +] + +[[package]] +name = "quote" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa563d17ecb180e500da1cfd2b028310ac758de548efdd203e18f283af693f37" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.3.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64ac302d8f83c0c1974bf758f6b041c6c8ada916fbb44a609158ca8b064cc76c" +dependencies = [ + "libc", + "rand 0.4.6", +] + +[[package]] +name = "rand" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "552840b97013b1a26992c11eac34bdd778e464601a4c2054b5f0bff7c6761293" +dependencies = [ + "fuchsia-cprng", + "libc", + "rand_core 0.3.1", + "rdrand", + "winapi 0.3.9", +] + +[[package]] +name = "rand" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" +dependencies = [ + "getrandom", + "libc", + "rand_chacha", + "rand_core 0.5.1", + "rand_hc", +] + +[[package]] +name = "rand_chacha" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" +dependencies = [ + "ppv-lite86", + "rand_core 0.5.1", +] + +[[package]] +name = "rand_core" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b" +dependencies = [ + "rand_core 0.4.2", +] + +[[package]] +name = "rand_core" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc" + +[[package]] +name = "rand_core" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" +dependencies = [ + "getrandom", +] + +[[package]] +name = "rand_hc" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" +dependencies = [ + "rand_core 0.5.1", +] + +[[package]] +name = "rdrand" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2" +dependencies = [ + "rand_core 0.3.1", +] + +[[package]] +name = "redox_syscall" +version = "0.1.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2439c63f3f6139d1b57529d16bc3b8bb855230c8efcc5d3a896c8bea7c3b1e84" + +[[package]] +name = "regex" +version = "1.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c3780fcf44b193bc4d09f36d2a3c87b251da4a046c87795a0d35f4f927ad8e6" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", + "thread_local", +] + +[[package]] +name = "regex-syntax" +version = "0.6.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26412eb97c6b088a6997e05f69403a802a92d520de2f8e63c2b65f9e0f47c4e8" + +[[package]] +name = "remove_dir_all" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" +dependencies = [ + "winapi 0.3.9", +] + +[[package]] +name = "reqwest" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b82c9238b305f26f53443e3a4bc8528d64b8d0bee408ec949eb7bf5635ec680" +dependencies = [ + "base64", + "bytes", + "encoding_rs", + "futures-core", + "futures-util", + "http", + "http-body", + "hyper", + "hyper-tls", + "js-sys", + "lazy_static", + "log", + "mime", + "mime_guess", + "native-tls", + "percent-encoding", + "pin-project-lite", + "serde", + "serde_json", + "serde_urlencoded", + "tokio", + "tokio-tls", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "winreg", +] + +[[package]] +name = "rsa" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3648b669b10afeab18972c105e284a7b953a669b0be3514c27f9b17acab2f9cd" +dependencies = [ + "byteorder", + "digest", + "lazy_static", + "num-bigint-dig", + "num-integer", + "num-iter", + "num-traits", + "pem", + "rand 0.7.3", + "sha2", + "simple_asn1", + "subtle", + "thiserror", + "zeroize", +] + +[[package]] +name = "rust-crypto" +version = "0.2.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f76d05d3993fd5f4af9434e8e436db163a12a9d40e1a58a726f27a01dfd12a2a" +dependencies = [ + "gcc", + "libc", + "rand 0.3.23", + "rustc-serialize", + "time", +] + +[[package]] +name = "rustc-serialize" +version = "0.3.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcf128d1287d2ea9d80910b5f1120d0b8eede3fbf1abe91c40d39ea7d51e6fda" + +[[package]] +name = "rustc_version" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" +dependencies = [ + "semver", +] + +[[package]] +name = "ryu" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e" + +[[package]] +name = "schannel" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f05ba609c234e60bee0d547fe94a4c7e9da733d1c962cf6e59efa4cd9c8bc75" +dependencies = [ + "lazy_static", + "winapi 0.3.9", +] + +[[package]] +name = "scoped-tls" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "332ffa32bf586782a3efaeb58f127980944bbc8c4d6913a86107ac2a5ab24b28" + +[[package]] +name = "sdk" +version = "0.1.0" +dependencies = [ + "base64", + "chrono", + "reqwest", + "rsa", + "rust-crypto", + "serde", + "serde_json", + "tokio", +] + +[[package]] +name = "security-framework" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64808902d7d99f78eaddd2b4e2509713babc3dc3c85ad6f4c447680f3c01e535" +dependencies = [ + "bitflags", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17bf11d99252f512695eb468de5516e5cf75455521e69dfe343f3b74e4748405" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "semver" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" +dependencies = [ + "semver-parser", +] + +[[package]] +name = "semver-parser" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" + +[[package]] +name = "serde" +version = "1.0.114" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5317f7588f0a5078ee60ef675ef96735a1442132dc645eb1d12c018620ed8cd3" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.114" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a0be94b04690fbaed37cddffc5c134bf537c8e3329d53e982fe04c374978f8e" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3433e879a558dde8b5e8feb2a04899cf34fdde1fafb894687e52105fc1162ac3" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serde_urlencoded" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ec5d77e2d4c73717816afac02670d5c4f534ea95ed430442cad02e7a6e32c97" +dependencies = [ + "dtoa", + "itoa", + "serde", + "url", +] + +[[package]] +name = "sha2" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2933378ddfeda7ea26f48c555bdad8bb446bf8a3d17832dc83e380d444cfb8c1" +dependencies = [ + "block-buffer", + "cfg-if", + "cpuid-bool", + "digest", + "opaque-debug", +] + +[[package]] +name = "signal-hook-registry" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94f478ede9f64724c5d173d7bb56099ec3e2d9fc2774aac65d34b8b890405f41" +dependencies = [ + "arc-swap", + "libc", +] + +[[package]] +name = "simple_asn1" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b25ecba7165254f0c97d6c22a64b1122a03634b18d20a34daf21e18f892e618" +dependencies = [ + "chrono", + "num-bigint", + "num-traits", +] + +[[package]] +name = "slab" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8" + +[[package]] +name = "smallvec" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7cb5678e1615754284ec264d9bb5b4c27d2018577fd90ac0ceb578591ed5ee4" + +[[package]] +name = "socket2" +version = "0.3.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03088793f677dce356f3ccc2edb1b314ad191ab702a5de3faf49304f7e104918" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "winapi 0.3.9", +] + +[[package]] +name = "spin" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" + +[[package]] +name = "subtle" +version = "2.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "502d53007c02d7605a05df1c1a73ee436952781653da5d0bf57ad608f66932c1" + +[[package]] +name = "syn" +version = "1.0.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8d5d96e8cbb005d6959f119f773bfaebb5684296108fb32600c00cde305b2cd" +dependencies = [ + "proc-macro2", + "quote", + "unicode-xid", +] + +[[package]] +name = "synstructure" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b834f2d66f734cb897113e34aaff2f1ab4719ca946f9a7358dba8f8064148701" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "unicode-xid", +] + +[[package]] +name = "tempfile" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a6e24d9338a0a5be79593e2fa15a648add6138caa803e2d5bc782c371732ca9" +dependencies = [ + "cfg-if", + "libc", + "rand 0.7.3", + "redox_syscall", + "remove_dir_all", + "winapi 0.3.9", +] + +[[package]] +name = "thiserror" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dfdd070ccd8ccb78f4ad66bf1982dc37f620ef696c6b5028fe2ed83dd3d0d08" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd80fc12f73063ac132ac92aceea36734f04a1d93c1240c6944e23a3b8841793" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "thread_local" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d40c6d1b69745a6ec6fb1ca717914848da4b44ae29d9b3080cbee91d72a69b14" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "time" +version = "0.1.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca8a50ef2360fbd1eeb0ecd46795a87a19024eb4b53c5dc916ca1fd95fe62438" +dependencies = [ + "libc", + "winapi 0.3.9", +] + +[[package]] +name = "tinyvec" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53953d2d3a5ad81d9f844a32f14ebb121f50b650cd59d0ee2a07cf13c617efed" + +[[package]] +name = "tokio" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d099fa27b9702bed751524694adbe393e18b36b204da91eb1cbbbbb4a5ee2d58" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "iovec", + "lazy_static", + "libc", + "memchr", + "mio", + "mio-named-pipes", + "mio-uds", + "num_cpus", + "pin-project-lite", + "signal-hook-registry", + "slab", + "tokio-macros", + "winapi 0.3.9", +] + +[[package]] +name = "tokio-macros" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0c3acc6aa564495a0f2e1d59fab677cd7f81a19994cfc7f3ad0e64301560389" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tokio-tls" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a70f4fcd7b3b24fb194f837560168208f669ca8cb70d0c4b862944452396343" +dependencies = [ + "native-tls", + "tokio", +] + +[[package]] +name = "tokio-util" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be8242891f2b6cbef26a2d7e8605133c2c554cd35b3e4948ea892d6d68436499" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "log", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tower-service" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e987b6bf443f4b5b3b6f38704195592cca41c5bb7aedd3c3693c7081f8289860" + +[[package]] +name = "try-lock" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e604eb7b43c06650e854be16a2a03155743d3752dd1c943f6829e26b7a36e382" + +[[package]] +name = "typenum" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "373c8a200f9e67a0c95e62a4f52fbf80c23b4381c05a17845531982fa99e6b33" + +[[package]] +name = "unicase" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50f37be617794602aabbeee0be4f259dc1778fabe05e2d67ee8f79326d5cb4f6" +dependencies = [ + "version_check", +] + +[[package]] +name = "unicode-bidi" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49f2bd0c6468a8230e1db229cff8029217cf623c767ea5d60bfbd42729ea54d5" +dependencies = [ + "matches", +] + +[[package]] +name = "unicode-normalization" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fb19cf769fa8c6a80a162df694621ebeb4dafb606470b2b2fce0be40a98a977" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "unicode-xid" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564" + +[[package]] +name = "url" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "829d4a8476c35c9bf0bbce5a3b23f4106f79728039b726d292bb93bc106787cb" +dependencies = [ + "idna", + "matches", + "percent-encoding", +] + +[[package]] +name = "vcpkg" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6454029bf181f092ad1b853286f23e2c507d8e8194d01d92da4a55c274a5508c" + +[[package]] +name = "version_check" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5a972e5669d67ba988ce3dc826706fb0a8b01471c088cb0b6110b805cc36aed" + +[[package]] +name = "want" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0" +dependencies = [ + "log", + "try-lock", +] + +[[package]] +name = "wasi" +version = "0.9.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" + +[[package]] +name = "wasm-bindgen" +version = "0.2.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a634620115e4a229108b71bde263bb4220c483b3f07f5ba514ee8d15064c4c2" +dependencies = [ + "cfg-if", + "serde", + "serde_json", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e53963b583d18a5aa3aaae4b4c1cb535218246131ba22a71f05b518098571df" +dependencies = [ + "bumpalo", + "lazy_static", + "log", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dba48d66049d2a6cc8488702e7259ab7afc9043ad0dc5448444f46f2a453b362" +dependencies = [ + "cfg-if", + "js-sys", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fcfd5ef6eec85623b4c6e844293d4516470d8f19cd72d0d12246017eb9060b8" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9adff9ee0e94b926ca81b57f57f86d5545cdcb1d259e21ec9bdd95b901754c75" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f7b90ea6c632dd06fd765d44542e234d5e63d9bb917ecd64d79778a13bd79ae" + +[[package]] +name = "web-sys" +version = "0.3.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "863539788676619aac1a23e2df3655e96b32b0e05eb72ca34ba045ad573c625d" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "winapi" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-build" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "winreg" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0120db82e8a1e0b9fb3345a539c478767c0048d842860994d96113d5b667bd69" +dependencies = [ + "winapi 0.3.9", +] + +[[package]] +name = "ws2_32-sys" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d59cefebd0c892fa2dd6de581e937301d8552cb44489cdff035c6187cb63fa5e" +dependencies = [ + "winapi 0.2.8", + "winapi-build", +] + +[[package]] +name = "zeroize" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3cbac2ed2ba24cc90f5e06485ac8c7c1e5449fe8911aef4d8877218af021a5b8" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de251eec69fc7c1bc3923403d18ececb929380e016afe103da75f396704f8ca2" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] diff --git a/sop-sdk/sdk-rust/sdk/Cargo.toml b/sop-sdk/sdk-rust/sdk/Cargo.toml new file mode 100644 index 00000000..fe37e126 --- /dev/null +++ b/sop-sdk/sdk-rust/sdk/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "sdk" +version = "0.1.0" +authors = ["thc"] +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +# 时间api +chrono = "0.4" +# rsa库 +rsa = "0.3.0" +# 加密库 +rust-crypto = "^0.2" +# base64库 +base64 = "0.12.3" +# HTTP客户端 https://github.com/seanmonstar/reqwest +reqwest = { version = "0.10", features = ["blocking", "json"] } +tokio = { version = "0.2", features = ["full"] } +# 处理json https://serde.rs/ +serde = { version = "1.0.114", features = ["derive"] } +serde_json = "1.0.56" \ No newline at end of file diff --git a/sop-sdk/sdk-rust/sdk/src/client.rs b/sop-sdk/sdk-rust/sdk/src/client.rs new file mode 100644 index 00000000..a63078a7 --- /dev/null +++ b/sop-sdk/sdk-rust/sdk/src/client.rs @@ -0,0 +1,109 @@ +use std::collections::HashMap; + +use chrono::Local; +use serde::de::DeserializeOwned; + +use crate::http::HttpTool; +use crate::request::{Request, RequestType}; +use crate::sign::{SignType, SignUtil}; +use serde_json::Value; + +pub struct OpenClient { + /// 应用ID + pub app_id: &'static str, + /// 应用私钥,PKCS#1 + pub private_key: &'static str, + /// 请求url + pub url: &'static str, +} + +impl OpenClient { + + /// 发送请求 + /// + /// - request: 请求对象 + /// + /// 返回结果 + pub fn execute(&self, request: impl Request) -> T { + self.execute_token(request, "") + } + + ///发送请求 + /// + /// - request: 请求对象 + /// - token:token + /// + /// 返回结果 + pub fn execute_token(&self, request: impl Request, token: &'static str) -> T { + let struct_obj: T; + + let request_type = request.get_request_type(); + let headers = &OpenClient::get_default_headers(); + let all_params = &self.build_params(&request, token); + + if request.get_base().files.len() > 0 { + let base = request.get_base(); + let files = &base.files; + let resp = HttpTool::post_file(self.url, all_params, files, headers); + struct_obj = self.parse_response(resp, &request); + } else { + match request_type { + RequestType::Get => { + let resp = HttpTool::get(self.url, all_params, headers); + struct_obj = self.parse_response(resp, &request); + } + RequestType::PostForm => { + let resp = HttpTool::post_form(self.url, all_params, headers); + struct_obj = self.parse_response(resp, &request); + } + RequestType::PostJson => { + let resp = HttpTool::post_json(self.url, all_params, headers); + struct_obj = self.parse_response(resp, &request); + } + RequestType::PostFile => { + let base = request.get_base(); + let files = &base.files; + let resp = HttpTool::post_file(self.url, all_params, files, headers); + struct_obj = self.parse_response(resp, &request); + } + } + } + struct_obj + } + + fn get_default_headers() -> HashMap<&'static str, &'static str> { + let mut headers = HashMap::new(); + headers.insert("Accept-Encoding", "identity"); + headers + } + + fn parse_response(&self, resp: reqwest::blocking::Response, request: &impl Request) -> T { + let root: HashMap = resp.json().expect("error"); + request.parse_response(root) + } + + /// 构建请求参数 + fn build_params(&self, request: &impl Request, token: &str) -> HashMap<&'static str, String> { + let method = request.get_method(); + let version = request.get_version(); + + let mut all_params = HashMap::new(); + all_params.insert("app_id", self.app_id.to_string()); + all_params.insert("method", method.to_string()); + all_params.insert("sign_type", "RSA2".to_string()); + all_params.insert("charset", "UTF-8".to_string()); + all_params.insert("timestamp", Local::now().format("%Y-%m-%d %H:%M:%S").to_string()); + all_params.insert("version", version.to_string()); + all_params.insert("biz_content", serde_json::to_string(&request.get_base().biz_model).unwrap_or(String::from("{}"))); + + if !token.is_empty() { + all_params.insert("app_auth_token", token.to_string()); + } + + // 创建签名 + let sign = SignUtil::create_sign(&all_params, self.private_key, SignType::RSA2); + all_params.insert("sign", sign); + + all_params + } +} \ No newline at end of file diff --git a/sop-sdk/sdk-rust/sdk/src/http.rs b/sop-sdk/sdk-rust/sdk/src/http.rs new file mode 100644 index 00000000..8e0a2fe0 --- /dev/null +++ b/sop-sdk/sdk-rust/sdk/src/http.rs @@ -0,0 +1,158 @@ +/* +Cargo.toml: + +[dependencies] +reqwest = { version = "0.10", features = ["blocking", "json"] } +tokio = { version = "0.2", features = ["full"] } +serde = { version = "1.0.114", features = ["derive"] } +serde_json = "1.0.56" + */ +use std::collections::HashMap; + +use reqwest::blocking::Response; +use reqwest::header::{HeaderMap, HeaderValue, HeaderName}; +use std::str::FromStr; +use serde::Serialize; + +/// HTTP请求工具 +pub struct HttpTool {} + +/// 上传文件对象 +pub struct UploadFile { + /// 上传文件表单名称 + pub name: &'static str, + /// 文件全路径 + pub path: String, +} + +impl HttpTool { + + /// get请求 + /// + /// - url:请求url + /// - params:请求参数 + /// - headers:请求header + /// + /// # Example + /// + /// ```rust + /// let mut params = HashMap::new(); + /// params.insert("name", "Jim"); + /// params.insert("age", "12"); + /// let resp = HttpTool::get(url, ¶ms, &HashMap::new()); + /// ``` + /// return: Response对象 + pub fn get(url: &str, params: &T, headers: &HashMap<&str, &str>) -> Response { + let client = reqwest::blocking::Client::new(); + let res = client + .get(url) + .query(params) + .headers(super::http::HttpTool::get_headers_map(headers)) + .send(); + let resp = res.expect("request error"); + resp + } + + /// post模拟表单请求 + /// + /// - url:请求url + /// - params:请求参数 + /// - headers:请求header + /// + /// # Example + /// + /// ```rust + /// let mut params = HashMap::new(); + /// params.insert("name", "Jim"); + /// params.insert("age", "12"); + /// let resp = HttpTool::post_form(url, ¶ms, &HashMap::new()); + /// ``` + /// return: Response对象 + pub fn post_form(url: &str, params: &T, headers: &HashMap<&str, &str>) -> Response { + let client = reqwest::blocking::Client::new(); + let res = client + .post(url) + .form(params) + .headers(super::http::HttpTool::get_headers_map(headers)) + .send(); + let resp = res.expect("request error"); + resp + } + + /// post发送json + /// + /// - url:请求url + /// - params:请求参数 + /// - headers:请求header + /// + /// # Example + /// + /// ```rust + /// let mut params = HashMap::new(); + /// params.insert("name", "Jim"); + /// params.insert("age", "12"); + /// let resp = HttpTool::post_json(url, ¶ms, &HashMap::new()); + /// ``` + /// return: Response对象 + pub fn post_json(url: &str, params: &T, headers: &HashMap<&str, &str>) -> Response { + let client = reqwest::blocking::Client::new(); + let res = client + .post(url) + .json(params) + .headers(super::http::HttpTool::get_headers_map(headers)) + .send(); + let resp = res.expect("request error"); + resp + } + + /// post上传文件 + /// + /// - url:请求url + /// - params:请求参数 + /// - files:上传文件 + /// - headers:请求header + /// + /// # Example + /// + /// ```rust + /// let mut params = HashMap::new(); + /// params.insert("name", String::from("Jim")); + /// params.insert("age",String::from("12")); + /// + /// // 设置上传文件 + /// let mut files = vec![ + /// UploadFile { name:"file1", path: String::from("/User/xx/aa.txt") }, + /// UploadFile { name:"file2", path: String::from("/User/xx/bb.txt") }, + /// ]; + /// + /// let resp = HttpTool::post_file(url, ¶ms, &files, &HashMap::new()); + /// ``` + /// return: Response对象 + pub fn post_file(url: &str, params: &HashMap<&str, String>, files: &Vec, headers: &HashMap<&str, &str>) -> Response { + let client = reqwest::blocking::Client::new(); + let mut form = reqwest::blocking::multipart::Form::new(); + // 添加普通参数 + for entry in params { + form = form.text(entry.0.to_string(), entry.1.to_string()); + } + // 添加文件 + for file in files { + form = form.file(file.name.to_string(), file.path.to_string()).unwrap(); + } + let res = client + .post(url) + .multipart(form) + .headers(super::http::HttpTool::get_headers_map(headers)) + .send(); + let resp = res.expect("request error"); + resp + } + + fn get_headers_map(headers: &HashMap<&str, &str>) -> HeaderMap { + let mut header_map = HeaderMap::new(); + for entry in headers { + header_map.insert(HeaderName::from_str(entry.0).unwrap(), HeaderValue::from_str(entry.1).unwrap()); + } + header_map + } +} \ No newline at end of file diff --git a/sop-sdk/sdk-rust/sdk/src/lib.rs b/sop-sdk/sdk-rust/sdk/src/lib.rs new file mode 100644 index 00000000..7bebdbf5 --- /dev/null +++ b/sop-sdk/sdk-rust/sdk/src/lib.rs @@ -0,0 +1,14 @@ +pub mod sign; +pub mod client; +pub mod request; +pub mod response; +pub mod http; +extern crate chrono; + +/*#[cfg(test)] +mod tests { + #[test] + fn it_works() { + assert_eq!(2 + 2, 4); + } +}*/ \ No newline at end of file diff --git a/sop-sdk/sdk-rust/sdk/src/request/memberinfoget.rs b/sop-sdk/sdk-rust/sdk/src/request/memberinfoget.rs new file mode 100644 index 00000000..5b92e984 --- /dev/null +++ b/sop-sdk/sdk-rust/sdk/src/request/memberinfoget.rs @@ -0,0 +1,26 @@ +use crate::request::{BaseRequest, Request, RequestType}; + +pub struct MemberInfoGetRequest { + // 固定这么写 + pub base: BaseRequest +} + +impl Request for MemberInfoGetRequest { + + fn get_method(&self) -> &str { + "member.info.get" + } + + fn get_version(&self) -> &str { + "1.0" + } + + fn get_request_type(&self) -> RequestType { + RequestType::Get + } + + // 固定这么写 + fn get_base(&self) -> &BaseRequest { + &self.base + } +} diff --git a/sop-sdk/sdk-rust/sdk/src/request/mod.rs b/sop-sdk/sdk-rust/sdk/src/request/mod.rs new file mode 100644 index 00000000..de9cf5e5 --- /dev/null +++ b/sop-sdk/sdk-rust/sdk/src/request/mod.rs @@ -0,0 +1,45 @@ + +use std::collections::HashMap; + +use serde_json::Value; + +use crate::http::UploadFile; +use serde::de::DeserializeOwned; + +pub mod memberinfoget; + +pub enum RequestType { + Get, + PostJson, + PostForm, + PostFile, +} + +pub trait Request { + /// 返回接口名称 + fn get_method(&self) -> &str; + + /// 返回版本号 + fn get_version(&self) -> &str; + + /// 返回请求方式 + fn get_request_type(&self) -> RequestType; + + /// 返回base + fn get_base(&self) -> &BaseRequest; + + fn parse_response(&self, root: HashMap) -> T { + let mut data = root.get("error_response"); + if data.is_none() { + let data_name = self.get_method().replace(".", "_") + "_response"; + data = root.get(data_name.as_str()); + } + let value = serde_json::to_value(data.unwrap()).unwrap(); + serde_json::from_value(value).unwrap() + } +} + +pub struct BaseRequest { + pub biz_model: HashMap<&'static str, String>, + pub files: Vec +} diff --git a/sop-sdk/sdk-rust/sdk/src/response/memberinfoget.rs b/sop-sdk/sdk-rust/sdk/src/response/memberinfoget.rs new file mode 100644 index 00000000..72e5caaf --- /dev/null +++ b/sop-sdk/sdk-rust/sdk/src/response/memberinfoget.rs @@ -0,0 +1,36 @@ +extern crate serde; + +use serde::{Deserialize}; + +// 响应参数 +#[derive(Deserialize, Debug)] +pub struct MemberInfoGetResponse { + // ~~~ 固定部分 ~~~ + pub code: String, + pub msg: String, + // json中可能没有sub_code属性,因此需要加上 #[serde(default)] + // 详见:https://serde.rs/field-attrs.html + #[serde(default)] + pub sub_code: String, + #[serde(default)] + pub sub_msg: String, + // ~~~ 固定部分 ~~~ + + // 下面是业务字段 + #[serde(default)] + pub id: u32, + + #[serde(default)] + pub name: String, + + pub member_info: MemberInfo +} + +#[derive(Deserialize, Debug)] +pub struct MemberInfo { + #[serde(default)] + pub is_vip: i8, + + #[serde(default)] + pub vip_endtime: String +} \ No newline at end of file diff --git a/sop-sdk/sdk-rust/sdk/src/response/mod.rs b/sop-sdk/sdk-rust/sdk/src/response/mod.rs new file mode 100644 index 00000000..1abd561c --- /dev/null +++ b/sop-sdk/sdk-rust/sdk/src/response/mod.rs @@ -0,0 +1 @@ +pub mod memberinfoget; diff --git a/sop-sdk/sdk-rust/sdk/src/sign.rs b/sop-sdk/sdk-rust/sdk/src/sign.rs new file mode 100644 index 00000000..041705c6 --- /dev/null +++ b/sop-sdk/sdk-rust/sdk/src/sign.rs @@ -0,0 +1,108 @@ +extern crate rsa; +extern crate crypto; + +use std::collections::HashMap; + +use rsa::{RSAPrivateKey, PaddingScheme}; +use rsa::Hash; + +use crypto::sha2::Sha256; +use crypto::digest::Digest; +use std::iter::repeat; + +pub struct SignUtil {} + +pub enum SignType { + RSA, + RSA2, +} + +pub enum HashType { + Sha1, + Sha256 +} + +impl SignUtil { + + pub fn create_sign(all_params: &HashMap<&str, String>, private_key: &str, sign_type: SignType) -> String { + let content = SignUtil::get_sign_content(all_params); + // println!("content:{}", content); + let hash_type; + match sign_type { + SignType::RSA => hash_type = HashType::Sha1, + SignType::RSA2 => hash_type = HashType::Sha256, + } + + SignUtil::rsa_sign(content.as_str(), private_key, hash_type) + } + + /// RSA签名 + /// + /// - content: 签名内容 + /// - private_key: 私钥,PKCS#1 + /// - hash_type: hash类型 + /// + /// # Examples + /// + /// ``` + /// use sdk::sign::{SignUtil, HashType}; + /// + /// let content = "123"; + /// let private_key = "your private key"; + /// let sign = SignUtil::rsa_sign(content, private_key, HashType::Sha256); + /// + /// println!("sign:{}", sign); + /// ``` + /// return: 返回base64字符串 + pub fn rsa_sign(content: &str, private_key: &str, hash_type: HashType) -> String { + // 格式化私钥 + let der_encoded = private_key + .lines() + .filter(|line| !line.starts_with("-")) + .fold(String::new(), |mut data, line| { + data.push_str(&line); + data + }); + let der_bytes = base64::decode(der_encoded).expect("failed to decode base64 content"); + // 获取私钥对象 + let private_key = RSAPrivateKey::from_pkcs1(&der_bytes).expect("failed to parse key"); + + // 创建一个Sha256对象 + let mut hasher = Sha256::new(); + // 对内容进行摘要 + hasher.input_str(content); + // 将摘要结果保存到buf中 + let mut buf: Vec = repeat(0).take((hasher.output_bits()+7)/8).collect(); + hasher.result(&mut buf); + + // 对摘要进行签名 + let hash; + match hash_type { + HashType::Sha1 => hash = Hash::SHA1, + HashType::Sha256 => hash = Hash::SHA2_256 + } + let sign_result = private_key.sign(PaddingScheme::PKCS1v15Sign {hash: Option::from(hash) }, &buf); + // 签名结果转化为base64 + let vec = sign_result.expect("create sign error for base64"); + + base64::encode(vec) + } + + fn get_sign_content(all_params: &HashMap<&str, String>) -> String { + let mut content = Vec::new(); + let keys = all_params.keys(); + let mut sorted_keys = Vec::new(); + for key in keys { + sorted_keys.push(key); + } + // 排序 + sorted_keys.sort(); + for key in sorted_keys { + let val = all_params.get(key); + if val.is_some() { + content.push(key.to_string() + "=" + val.unwrap()); + } + } + content.join("&") + } +} \ No newline at end of file diff --git a/sop-test/pom.xml b/sop-test/pom.xml index b3269fee..85c3584c 100644 --- a/sop-test/pom.xml +++ b/sop-test/pom.xml @@ -5,7 +5,7 @@ com.gitee.sop sop-parent - 4.0.3-SNAPSHOT + 4.3.2-SNAPSHOT ../pom.xml diff --git a/sop-test/src/main/java/com/gitee/sop/test/Client.java b/sop-test/src/main/java/com/gitee/sop/test/Client.java index ec25a65d..1616d669 100644 --- a/sop-test/src/main/java/com/gitee/sop/test/Client.java +++ b/sop-test/src/main/java/com/gitee/sop/test/Client.java @@ -174,6 +174,8 @@ public class Client { public static class RequestBuilder { private static final String DEFAULT_VERSION = "1.0"; + private Map systemParam; + private String url; private String method; private String version = DEFAULT_VERSION; @@ -277,6 +279,20 @@ public class Client { return this; } + /** + * 添加系统参数 + * @param name 参数名 + * @param value 参数值 + * @return 返回RequestBuilder + */ + public RequestBuilder addSystemParam(String name, String value) { + if (systemParam == null) { + systemParam = new HashMap<>(8); + } + systemParam.put(name, value); + return this; + } + /** * 设置token * @@ -376,9 +392,14 @@ public class Client { params.put("charset", "utf-8"); params.put("sign_type", "RSA2"); params.put("timestamp", new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date())); + if (systemParam != null) { + params.putAll(systemParam); + } - // 业务参数 - params.put("biz_content", JSON.toJSONString(bizContent == null ? Collections.emptyMap() : bizContent)); + if (bizContent != null) { + // 业务参数 + params.put("biz_content", JSON.toJSONString(bizContent)); + } if (!ignoreSign) { String content = AlipaySignature.getSignContent(params); diff --git a/sop-test/src/main/java/com/gitee/sop/test/HttpTool.java b/sop-test/src/main/java/com/gitee/sop/test/HttpTool.java index b285a443..9530bc0c 100644 --- a/sop-test/src/main/java/com/gitee/sop/test/HttpTool.java +++ b/sop-test/src/main/java/com/gitee/sop/test/HttpTool.java @@ -75,15 +75,15 @@ public class HttpTool { /** * 请求超时时间 */ - private int connectTimeoutSeconds = 10; + private int connectTimeoutSeconds = 60; /** * http读取超时时间 */ - private int readTimeoutSeconds = 10; + private int readTimeoutSeconds = 60; /** * http写超时时间 */ - private int writeTimeoutSeconds = 10; + private int writeTimeoutSeconds = 60; } /** diff --git a/sop-test/src/main/resources/large_img.png b/sop-test/src/main/resources/large_img.png new file mode 100644 index 00000000..9d708881 Binary files /dev/null and b/sop-test/src/main/resources/large_img.png differ 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 dca10c54..dc8a9320 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 @@ -102,7 +102,7 @@ public class AllInOneTest extends TestBase { .version("1.0") .ignoreSign(true) .bizContent(new BizContent().add("id", "222").add("name", "忽略222")) - .httpMethod(HttpTool.HTTPMethod.POST); + .httpMethod(HttpTool.HTTPMethod.GET); client.execute(requestBuilder); } @@ -224,6 +224,29 @@ public class AllInOneTest extends TestBase { client.execute(requestBuilder); } + /** + * 演示大文件上传,先配置网关参数:spring.servlet.multipart.max-file-size=1MB + */ + public void testBigFile() { + Client client = new Client(url, appId, privateKey); + String root = System.getProperty("user.dir"); + Client.RequestBuilder requestBuilder = new Client.RequestBuilder() + .method("file.upload3") + .version("1.0") + .bizContent(new BizContent().add("remark", "test file upload")) + // 添加文件 + .addFile("image", new File(root + "/src/main/resources/large_img.png")) + .callback((requestInfo, responseData) -> { + System.out.println(responseData); + JSONObject jsonObject = JSON.parseObject(responseData); + JSONObject data = jsonObject.getJSONObject(requestInfo.getDataNode()); + Assert.assertEquals(data.getString("sub_code"), "isv.invalid-file-size"); + }) + ; + + client.execute(requestBuilder); + } + /** * 传递header */ @@ -330,6 +353,57 @@ public class AllInOneTest extends TestBase { client.execute(requestBuilder); } + /** + * 单个参数绑定 + */ + public void testBindParam() { + Client.RequestBuilder requestBuilder = new Client.RequestBuilder() + .method("story.param.bind") + .version("1.0") + .addSystemParam("id", "1") + .addSystemParam("name", "葫芦娃") + .httpMethod(HttpTool.HTTPMethod.GET) + .callback(((requestInfo, responseData) -> { + System.out.println(responseData); + Assert.assertTrue(responseData.contains("参数绑定:id:1, name:葫芦娃")); + })); + + client.execute(requestBuilder); + } + + /** + * 没有参数获取token + */ + public void testNoParamToken() { + String token = "asdfasdfadsf"; + Client.RequestBuilder requestBuilder = new Client.RequestBuilder() + .method("story.system.param.get2") + .version("1.0") + .appAuthToken(token) + .httpMethod(HttpTool.HTTPMethod.GET) + .callback((requestInfo, responseData) -> { + System.out.println(responseData); + Assert.assertTrue(responseData.contains(token)); + }); + + client.execute(requestBuilder); + } + + public void testParamSystem() { + String token = "asdfasdfadsf"; + Client.RequestBuilder requestBuilder = new Client.RequestBuilder() + .method("story.system.param.get3") + .version("1.0") + .appAuthToken(token) + .httpMethod(HttpTool.HTTPMethod.GET) + .callback((requestInfo, responseData) -> { + System.out.println(responseData); + Assert.assertTrue(responseData.contains(token)); + }); + + client.execute(requestBuilder); + } + static class BizContent extends HashMap { public BizContent add(String key, Object value) { this.put(key, value); diff --git a/sop-test/src/test/java/com/gitee/sop/test/ArrayTest.java b/sop-test/src/test/java/com/gitee/sop/test/ArrayTest.java new file mode 100644 index 00000000..748644a8 --- /dev/null +++ b/sop-test/src/test/java/com/gitee/sop/test/ArrayTest.java @@ -0,0 +1,63 @@ +package com.gitee.sop.test; + +import com.alibaba.fastjson.JSON; +import com.gitee.sop.test.alipay.AlipaySignature; +import org.junit.Test; + +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; + +/** + * 接收数组 + */ +public class ArrayTest extends TestBase { + + String url = "http://localhost:8081"; + 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 testGet() throws Exception { + + // 公共请求参数 + Map params = new HashMap(); + params.put("app_id", appId); + params.put("method", "story.listparam"); + 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())); + params.put("version", "1.0"); + + // 业务参数 + String bizContent = "{\n" + + " \"name\": \"jim\",\n" + + " \"list\": [{\n" + + " \"age\": 22,\n" + + " \"name\": \"jim\",\n" + + " \"address\": \"xx\"\n" + + " }]\n" + + "}"; + + params.put("biz_content", JSON.toJSONString(JSON.parseObject(bizContent))); + String content = AlipaySignature.getSignContent(params); + String sign = AlipaySignature.rsa256Sign(content, privateKey, "utf-8"); + params.put("sign", sign); + + System.out.println("----------- 请求信息 -----------"); + System.out.println("请求参数:" + buildParamQuery(params)); + System.out.println("商户秘钥:" + privateKey); + System.out.println("待签名内容:" + content); + System.out.println("签名(sign):" + sign); + System.out.println("URL参数:" + buildUrlQuery(params)); + + System.out.println("----------- 返回结果 -----------"); + String responseData = postJson(url, params);// 发送请求 + System.out.println(responseData); + } + + +} diff --git a/sop-test/src/test/java/com/gitee/sop/test/SamePathTest.java b/sop-test/src/test/java/com/gitee/sop/test/SamePathTest.java new file mode 100644 index 00000000..4dcde403 --- /dev/null +++ b/sop-test/src/test/java/com/gitee/sop/test/SamePathTest.java @@ -0,0 +1,96 @@ +package com.gitee.sop.test; + +import com.alibaba.fastjson.JSON; +import com.gitee.sop.test.alipay.AlipaySignature; +import org.junit.Assert; +import org.junit.Test; + +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; + +/** + * 超时测试 + */ +public class SamePathTest extends TestBase { + + String url = "http://localhost:8081"; + 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 testGet() throws Exception { + + // 公共请求参数 + Map params = new HashMap(); + params.put("app_id", appId); + params.put("method", "story.path.same"); + 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())); + params.put("version", "1.0"); + + // 业务参数 + Map bizContent = new HashMap<>(); + bizContent.put("id", "1"); + bizContent.put("name", "葫芦娃"); + + params.put("biz_content", JSON.toJSONString(bizContent)); + String content = AlipaySignature.getSignContent(params); + String sign = AlipaySignature.rsa256Sign(content, privateKey, "utf-8"); + params.put("sign", sign); + + System.out.println("----------- 请求信息 -----------"); + System.out.println("请求参数:" + buildParamQuery(params)); + System.out.println("商户秘钥:" + privateKey); + System.out.println("待签名内容:" + content); + System.out.println("签名(sign):" + sign); + System.out.println("URL参数:" + buildUrlQuery(params)); + + System.out.println("----------- 返回结果 -----------"); + String responseData = get(url, params);// 发送请求 + System.out.println(responseData); + Assert.assertTrue(responseData.contains("葫芦娃 story..")); + } + + @Test + public void testGet2() throws Exception { + + // 公共请求参数 + Map params = new HashMap(); + params.put("app_id", appId); + params.put("method", "springmvc.path.same"); + 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())); + params.put("version", "1.0"); + + // 业务参数 + Map bizContent = new HashMap<>(); + bizContent.put("id", "1"); + bizContent.put("name", "葫芦娃"); + + params.put("biz_content", JSON.toJSONString(bizContent)); + String content = AlipaySignature.getSignContent(params); + String sign = AlipaySignature.rsa256Sign(content, privateKey, "utf-8"); + params.put("sign", sign); + + System.out.println("----------- 请求信息 -----------"); + System.out.println("请求参数:" + buildParamQuery(params)); + System.out.println("商户秘钥:" + privateKey); + System.out.println("待签名内容:" + content); + System.out.println("签名(sign):" + sign); + System.out.println("URL参数:" + buildUrlQuery(params)); + + System.out.println("----------- 返回结果 -----------"); + String responseData = get(url, params);// 发送请求 + System.out.println(responseData); + Assert.assertTrue(responseData.contains("葫芦娃 mvc..")); + } + + +} diff --git a/sop-test/src/test/java/com/gitee/sop/test/TimeoutTest.java b/sop-test/src/test/java/com/gitee/sop/test/TimeoutTest.java new file mode 100644 index 00000000..cbd537fe --- /dev/null +++ b/sop-test/src/test/java/com/gitee/sop/test/TimeoutTest.java @@ -0,0 +1,65 @@ +package com.gitee.sop.test; + +import com.alibaba.fastjson.JSON; +import com.gitee.sop.test.alipay.AlipaySignature; +import org.junit.Test; + +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; + +/** + * 超时测试 + */ +public class TimeoutTest extends TestBase { + + String url = "http://localhost:8081"; + 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="; + + // sop-gateway/src/main/resources/application-dev.properties中配置参数: + // spring.cloud.gateway.httpclient.response-timeout=11000 + // 响应超时11秒 + // 业务逻辑处理10秒,见:com.gitee.sop.storyweb.controller.Example1009_TimeoutController + // 把spring.cloud.gateway.httpclient.response-timeout值改成5000网关会报错 + @Test + public void testGet() throws Exception { + + // 公共请求参数 + Map params = new HashMap(); + params.put("app_id", appId); + params.put("method", "goods.timeout"); + 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())); + params.put("version", "1.0"); + + // 业务参数 + Map bizContent = new HashMap<>(); + bizContent.put("id", "1"); + bizContent.put("name", "葫芦娃"); + + params.put("biz_content", JSON.toJSONString(bizContent)); + String content = AlipaySignature.getSignContent(params); + String sign = AlipaySignature.rsa256Sign(content, privateKey, "utf-8"); + params.put("sign", sign); + + System.out.println("----------- 请求信息 -----------"); + System.out.println("请求参数:" + buildParamQuery(params)); + System.out.println("商户秘钥:" + privateKey); + System.out.println("待签名内容:" + content); + System.out.println("签名(sign):" + sign); + System.out.println("URL参数:" + buildUrlQuery(params)); + + System.out.println("----------- 返回结果 -----------"); + long start = System.currentTimeMillis(); + String responseData = get(url, params);// 发送请求 + System.out.printf("耗时:%s秒%n", (System.currentTimeMillis() - start) / 1000.0); + System.out.println(responseData); + } + + +} diff --git a/sop-upgrade-4.1.0.sql b/sop-upgrade-4.1.0.sql new file mode 100644 index 00000000..e53736b6 --- /dev/null +++ b/sop-upgrade-4.1.0.sql @@ -0,0 +1,41 @@ +-- 4.1.0升级脚本 + +use sop; + +CREATE TABLE `monitor_info` ( + `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, + `route_id` varchar(128) NOT NULL DEFAULT '' COMMENT '路由id', + `name` varchar(128) NOT NULL DEFAULT '' COMMENT '接口名', + `version` varchar(64) NOT NULL DEFAULT '' COMMENT '版本号', + `service_id` varchar(64) NOT NULL DEFAULT '', + `instance_id` varchar(128) NOT NULL DEFAULT '', + `max_time` int(11) NOT NULL DEFAULT '0' COMMENT '请求耗时最长时间', + `min_time` int(11) NOT NULL DEFAULT '0' COMMENT '请求耗时最小时间', + `total_time` bigint(20) NOT NULL DEFAULT '0' COMMENT '总时长,毫秒', + `total_request_count` bigint(20) NOT NULL DEFAULT '0' COMMENT '总调用次数', + `success_count` bigint(20) NOT NULL DEFAULT '0' COMMENT '成功次数', + `error_count` bigint(20) NOT NULL DEFAULT '0' COMMENT '失败次数(业务主动抛出的异常算作成功,如参数校验,未知的错误算失败)', + `gmt_create` datetime DEFAULT CURRENT_TIMESTAMP, + `gmt_modified` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`id`), + UNIQUE KEY `uk_routeid` (`route_id`,`instance_id`) USING BTREE, + KEY `idex_name` (`name`) USING BTREE +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='接口监控信息'; + + + +CREATE TABLE `monitor_info_error` ( + `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, + `error_id` varchar(64) NOT NULL DEFAULT '' COMMENT '错误id,md5Hex(instanceId + routeId + errorMsg)', + `instance_id` varchar(128) NOT NULL DEFAULT '' COMMENT '实例id', + `route_id` varchar(128) NOT NULL DEFAULT '', + `error_msg` text NOT NULL, + `error_status` int(11) NOT NULL DEFAULT '0' COMMENT 'http status,非200错误', + `count` int(11) NOT NULL DEFAULT '0' COMMENT '错误次数', + `is_deleted` tinyint(4) NOT NULL DEFAULT '0', + `gmt_create` datetime DEFAULT CURRENT_TIMESTAMP, + `gmt_modified` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`id`), + UNIQUE KEY `uk_errorid` (`error_id`) USING BTREE, + KEY `idx_routeid` (`route_id`) USING BTREE +) ENGINE=InnoDB DEFAULT CHARSET=utf8; \ No newline at end of file diff --git a/sop-upgrade-4.2.0.sql b/sop-upgrade-4.2.0.sql new file mode 100644 index 00000000..7746e190 --- /dev/null +++ b/sop-upgrade-4.2.0.sql @@ -0,0 +1,30 @@ +use sop; + +ALTER TABLE `sop`.`isv_info` ADD COLUMN `user_id` BIGINT NULL DEFAULT 0 COMMENT 'user_account.id' AFTER `remark`; + +CREATE INDEX `idx_userid` USING BTREE ON `sop`.`isv_info` (`user_id`); + + +CREATE TABLE `user_account` ( + `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, + `username` varchar(128) NOT NULL DEFAULT '' COMMENT '用户名(邮箱)', + `password` varchar(128) NOT NULL DEFAULT '' COMMENT '密码', + `status` tinyint(4) NOT NULL DEFAULT '1' COMMENT '2:邮箱未验证,1:启用,0:禁用', + `gmt_create` datetime DEFAULT CURRENT_TIMESTAMP, + `gmt_modified` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`id`), + UNIQUE KEY `uk_username` (`username`) USING BTREE +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='用户信息'; + +CREATE TABLE `isp_resource` ( + `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, + `name` varchar(64) NOT NULL DEFAULT '' COMMENT '资源名称', + `content` varchar(128) NOT NULL DEFAULT '' COMMENT '资源内容(URL)', + `ext_content` text, + `version` varchar(32) NOT NULL DEFAULT '' COMMENT '版本', + `type` tinyint(4) NOT NULL DEFAULT '0' COMMENT '资源类型:0:SDK链接', + `is_deleted` tinyint(4) NOT NULL DEFAULT '0', + `gmt_create` datetime DEFAULT CURRENT_TIMESTAMP, + `gmt_modified` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='ISP资源表'; \ No newline at end of file diff --git a/sop-website/pom.xml b/sop-website/pom.xml index 3f07f14b..ee4d79f2 100644 --- a/sop-website/pom.xml +++ b/sop-website/pom.xml @@ -1,77 +1,20 @@ - - - - com.gitee.sop - sop-parent - 4.0.3-SNAPSHOT - ../pom.xml - - - 4.0.0 - sop-website - - - 1.8 - - - - - - com.gitee.sop - sop-bridge-nacos - - 4.0.3-SNAPSHOT - - - - org.springframework.boot - spring-boot-starter-web - - - - org.apache.commons - commons-lang3 - - - com.squareup.okhttp3 - okhttp - - - - com.alibaba - fastjson - - - org.apache.httpcomponents - httpclient - - - - org.projectlombok - lombok - 1.18.4 - provided - - - - org.springframework.boot - spring-boot-starter-test - test - - - - - - - org.springframework.boot - spring-boot-maven-plugin - - - - - + + + + com.gitee.sop + sop-parent + 4.3.2-SNAPSHOT + ../pom.xml + + + 4.0.0 + sop-website + pom + + + sop-website-server + + \ No newline at end of file diff --git a/sop-website/sop-portal-vue/.gitignore b/sop-website/sop-portal-vue/.gitignore new file mode 100644 index 00000000..ae3d36d3 --- /dev/null +++ b/sop-website/sop-portal-vue/.gitignore @@ -0,0 +1,17 @@ +yarn.lock +.DS_Store +node_modules/ +dist/ +npm-debug.log* +yarn-debug.log* +yarn-error.log* +package-lock.json +tests/**/coverage/ + +# Editor directories and files +.idea +.vscode +*.suo +*.ntvs* +*.njsproj +*.sln diff --git a/sop-website/sop-portal-vue/README.md b/sop-website/sop-portal-vue/README.md new file mode 100644 index 00000000..d24fe6c6 --- /dev/null +++ b/sop-website/sop-portal-vue/README.md @@ -0,0 +1,19 @@ +# 门户网站(推荐) + +基于文档:https://www.vuepress.cn/ + +- 初始化 + +npm install + +- 启动 + +npm run docs:dev + +- 部署 + +npm run docs:build + +结果在:`docs/.vuepress/dist` + +将`dist`文件夹放到静态服务器 \ No newline at end of file diff --git a/sop-website/sop-portal-vue/docs/.vuepress/config.js b/sop-website/sop-portal-vue/docs/.vuepress/config.js new file mode 100644 index 00000000..1391948a --- /dev/null +++ b/sop-website/sop-portal-vue/docs/.vuepress/config.js @@ -0,0 +1,31 @@ +module.exports = { + locales: { + // 键名是该语言所属的子路径 + // 作为特例,默认语言可以使用 '/' 作为其路径。 + '/': { + lang: 'zh-CN', // 将会被设置为 的 lang 属性 + title: 'XX开放平台', + description: 'XX开放平台' + }, + // '/zh/': { + // lang: 'zh-CN', + // title: 'VuePress', + // description: 'Vue 驱动的静态网站生成器' + // } + }, + // 主题配置 + themeConfig: { + logo: '/assets/images/logo.png', + search: false, + searchMaxSuggestions: 10, + // 导航按钮配置 + nav: [ + { text: '登录', link: 'http://localhost:8083/index.html#/login' }, + { text: '免费注册', link: 'http://localhost:8083/index.html#/isvReg' }, + ], + }, + // 派生主题,https://www.vuepress.cn/theme/inheritance.html#%E6%A6%82%E5%BF%B5 + extend: '@vuepress/theme-default', + // 插件 + plugins: ['@vuepress/back-to-top'] +} diff --git a/sop-website/sop-portal-vue/docs/.vuepress/public/assets/images/hero.jpeg b/sop-website/sop-portal-vue/docs/.vuepress/public/assets/images/hero.jpeg new file mode 100644 index 00000000..99334048 Binary files /dev/null and b/sop-website/sop-portal-vue/docs/.vuepress/public/assets/images/hero.jpeg differ diff --git a/sop-website/sop-portal-vue/docs/.vuepress/public/assets/images/logo.png b/sop-website/sop-portal-vue/docs/.vuepress/public/assets/images/logo.png new file mode 100644 index 00000000..b201d8c0 Binary files /dev/null and b/sop-website/sop-portal-vue/docs/.vuepress/public/assets/images/logo.png differ diff --git a/sop-website/sop-portal-vue/docs/.vuepress/public/favicon.ico b/sop-website/sop-portal-vue/docs/.vuepress/public/favicon.ico new file mode 100644 index 00000000..34b63ac6 Binary files /dev/null and b/sop-website/sop-portal-vue/docs/.vuepress/public/favicon.ico differ diff --git a/sop-website/sop-portal-vue/docs/.vuepress/styles/palette.styl b/sop-website/sop-portal-vue/docs/.vuepress/styles/palette.styl new file mode 100644 index 00000000..f4615ea8 --- /dev/null +++ b/sop-website/sop-portal-vue/docs/.vuepress/styles/palette.styl @@ -0,0 +1,21 @@ +// 这里修改主题颜色,详见:https://www.vuepress.cn/config/#palette-styl +// 颜色 +$accentColor = #409EFF +$textColor = #2c3e50 +$borderColor = #eaecef +$codeBgColor = #282c34 +$arrowBgColor = #ccc +$badgeTipColor = #42b983 +$badgeWarningColor = darken(#ffe564, 35%) +$badgeErrorColor = #DA5961 + +// 布局 +$navbarHeight = 3.6rem +$sidebarWidth = 20rem +$contentWidth = 740px +$homePageWidth = 960px + +// 响应式变化点 +$MQNarrow = 959px +$MQMobile = 719px +$MQMobileNarrow = 419px diff --git a/sop-website/sop-portal-vue/docs/.vuepress/theme/components/Footer.vue b/sop-website/sop-portal-vue/docs/.vuepress/theme/components/Footer.vue new file mode 100644 index 00000000..a1aa9840 --- /dev/null +++ b/sop-website/sop-portal-vue/docs/.vuepress/theme/components/Footer.vue @@ -0,0 +1,5 @@ + diff --git a/sop-website/sop-portal-vue/docs/.vuepress/theme/components/Home.vue b/sop-website/sop-portal-vue/docs/.vuepress/theme/components/Home.vue new file mode 100644 index 00000000..4cc8fd19 --- /dev/null +++ b/sop-website/sop-portal-vue/docs/.vuepress/theme/components/Home.vue @@ -0,0 +1,171 @@ + + + + + diff --git a/sop-website/sop-portal-vue/docs/.vuepress/theme/index.js b/sop-website/sop-portal-vue/docs/.vuepress/theme/index.js new file mode 100644 index 00000000..bd8dd662 --- /dev/null +++ b/sop-website/sop-portal-vue/docs/.vuepress/theme/index.js @@ -0,0 +1,4 @@ +module.exports = { + // 继承默认主题,https://www.vuepress.cn/theme/inheritance.html#%E5%8A%A8%E6%9C%BA + extend: '@vuepress/theme-default' +} diff --git a/sop-website/sop-portal-vue/docs/.vuepress/theme/util/index.js b/sop-website/sop-portal-vue/docs/.vuepress/theme/util/index.js new file mode 100644 index 00000000..92fcd3b3 --- /dev/null +++ b/sop-website/sop-portal-vue/docs/.vuepress/theme/util/index.js @@ -0,0 +1,244 @@ +export const hashRE = /#.*$/ +export const extRE = /\.(md|html)$/ +export const endingSlashRE = /\/$/ +export const outboundRE = /^[a-z]+:/i + +export function normalize (path) { + return decodeURI(path) + .replace(hashRE, '') + .replace(extRE, '') +} + +export function getHash (path) { + const match = path.match(hashRE) + if (match) { + return match[0] + } +} + +export function isExternal (path) { + return outboundRE.test(path) +} + +export function isMailto (path) { + return /^mailto:/.test(path) +} + +export function isTel (path) { + return /^tel:/.test(path) +} + +export function ensureExt (path) { + if (isExternal(path)) { + return path + } + const hashMatch = path.match(hashRE) + const hash = hashMatch ? hashMatch[0] : '' + const normalized = normalize(path) + + if (endingSlashRE.test(normalized)) { + return path + } + return normalized + '.html' + hash +} + +export function isActive (route, path) { + const routeHash = decodeURIComponent(route.hash) + const linkHash = getHash(path) + if (linkHash && routeHash !== linkHash) { + return false + } + const routePath = normalize(route.path) + const pagePath = normalize(path) + return routePath === pagePath +} + +export function resolvePage (pages, rawPath, base) { + if (isExternal(rawPath)) { + return { + type: 'external', + path: rawPath + } + } + if (base) { + rawPath = resolvePath(rawPath, base) + } + const path = normalize(rawPath) + for (let i = 0; i < pages.length; i++) { + if (normalize(pages[i].regularPath) === path) { + return Object.assign({}, pages[i], { + type: 'page', + path: ensureExt(pages[i].path) + }) + } + } + console.error(`[vuepress] No matching page found for sidebar item "${rawPath}"`) + return {} +} + +function resolvePath (relative, base, append) { + const firstChar = relative.charAt(0) + if (firstChar === '/') { + return relative + } + + if (firstChar === '?' || firstChar === '#') { + return base + relative + } + + const stack = base.split('/') + + // remove trailing segment if: + // - not appending + // - appending to trailing slash (last segment is empty) + if (!append || !stack[stack.length - 1]) { + stack.pop() + } + + // resolve relative path + const segments = relative.replace(/^\//, '').split('/') + for (let i = 0; i < segments.length; i++) { + const segment = segments[i] + if (segment === '..') { + stack.pop() + } else if (segment !== '.') { + stack.push(segment) + } + } + + // ensure leading slash + if (stack[0] !== '') { + stack.unshift('') + } + + return stack.join('/') +} + +/** + * @param { Page } page + * @param { string } regularPath + * @param { SiteData } site + * @param { string } localePath + * @returns { SidebarGroup } + */ +export function resolveSidebarItems (page, regularPath, site, localePath) { + const { pages, themeConfig } = site + + const localeConfig = localePath && themeConfig.locales + ? themeConfig.locales[localePath] || themeConfig + : themeConfig + + const pageSidebarConfig = page.frontmatter.sidebar || localeConfig.sidebar || themeConfig.sidebar + if (pageSidebarConfig === 'auto') { + return resolveHeaders(page) + } + + const sidebarConfig = localeConfig.sidebar || themeConfig.sidebar + if (!sidebarConfig) { + return [] + } else { + const { base, config } = resolveMatchingConfig(regularPath, sidebarConfig) + if (config === 'auto') { + return resolveHeaders(page) + } + return config + ? config.map(item => resolveItem(item, pages, base)) + : [] + } +} + +/** + * @param { Page } page + * @returns { SidebarGroup } + */ +function resolveHeaders (page) { + const headers = groupHeaders(page.headers || []) + return [{ + type: 'group', + collapsable: false, + title: page.title, + path: null, + children: headers.map(h => ({ + type: 'auto', + title: h.title, + basePath: page.path, + path: page.path + '#' + h.slug, + children: h.children || [] + })) + }] +} + +export function groupHeaders (headers) { + // group h3s under h2 + headers = headers.map(h => Object.assign({}, h)) + let lastH2 + headers.forEach(h => { + if (h.level === 2) { + lastH2 = h + } else if (lastH2) { + (lastH2.children || (lastH2.children = [])).push(h) + } + }) + return headers.filter(h => h.level === 2) +} + +export function resolveNavLinkItem (linkItem) { + return Object.assign(linkItem, { + type: linkItem.items && linkItem.items.length ? 'links' : 'link' + }) +} + +/** + * @param { Route } route + * @param { Array | Array | [link: string]: SidebarConfig } config + * @returns { base: string, config: SidebarConfig } + */ +export function resolveMatchingConfig (regularPath, config) { + if (Array.isArray(config)) { + return { + base: '/', + config: config + } + } + for (const base in config) { + if (ensureEndingSlash(regularPath).indexOf(encodeURI(base)) === 0) { + return { + base, + config: config[base] + } + } + } + return {} +} + +function ensureEndingSlash (path) { + return /(\.html|\/)$/.test(path) + ? path + : path + '/' +} + +function resolveItem (item, pages, base, groupDepth = 1) { + if (typeof item === 'string') { + return resolvePage(pages, item, base) + } else if (Array.isArray(item)) { + return Object.assign(resolvePage(pages, item[0], base), { + title: item[1] + }) + } else { + const children = item.children || [] + if (children.length === 0 && item.path) { + return Object.assign(resolvePage(pages, item.path, base), { + title: item.title + }) + } + return { + type: 'group', + path: item.path, + title: item.title, + sidebarDepth: item.sidebarDepth, + initialOpenGroupIndex: item.initialOpenGroupIndex, + children: children.map(child => resolveItem(child, pages, base, groupDepth + 1)), + collapsable: item.collapsable !== false + } + } +} diff --git a/sop-website/sop-portal-vue/docs/README.md b/sop-website/sop-portal-vue/docs/README.md new file mode 100644 index 00000000..4a3cce55 --- /dev/null +++ b/sop-website/sop-portal-vue/docs/README.md @@ -0,0 +1,17 @@ +--- +home: true +heroImage: /assets/images/hero.jpeg +heroText: 开放平台 +tagline: 一句话描述你的平台。。 +actionText: 开始使用 +actionLink: http://localhost:8083/index.html#/login +features: +- title: 更快 + details: 开放接口更快。。 +- title: 更高 + details: 开放接口更高。。 +- title: 更强 + details: 开放接口更强。。 +footer: null +search: false +--- diff --git a/sop-website/sop-portal-vue/package.json b/sop-website/sop-portal-vue/package.json new file mode 100644 index 00000000..5e855633 --- /dev/null +++ b/sop-website/sop-portal-vue/package.json @@ -0,0 +1,15 @@ +{ + "name": "open-portal", + "version": "1.0.0", + "main": "index.js", + "author": "tanghc", + "license": "MIT", + "devDependencies": { + "@vuepress/plugin-back-to-top": "^1.8.0", + "vuepress": "^1.8.0" + }, + "scripts": { + "docs:dev": "vuepress dev docs", + "docs:build": "vuepress build docs" + } +} diff --git a/sop-website/sop-portal/.babelrc b/sop-website/sop-portal/.babelrc new file mode 100644 index 00000000..42d28faf --- /dev/null +++ b/sop-website/sop-portal/.babelrc @@ -0,0 +1,22 @@ +{ + "plugins": [ + "transform-decorators-legacy", + "transform-class-properties", + "transform-object-rest-spread", + "transform-object-assign", + [ + "transform-runtime", + { + "helpers": false, + "polyfill": false, + "regenerator": true, + "moduleName": "babel-runtime" + } + ] + ], + "presets": [ + "react", + "stage-0", + "es2015" + ] +} diff --git a/sop-website/sop-portal/.docsite b/sop-website/sop-portal/.docsite new file mode 100644 index 00000000..e69de29b diff --git a/sop-website/sop-portal/.eslintrc b/sop-website/sop-portal/.eslintrc new file mode 100644 index 00000000..0e70ce4c --- /dev/null +++ b/sop-website/sop-portal/.eslintrc @@ -0,0 +1,28 @@ +{ + "extends": "eslint-config-ali/react", + "parser": "babel-eslint", + "env": { + "browser": true, + "node": true, + "mocha": true + }, + "globals": { + "AK_GLOBAL": true, + "dd": {}, + "_czc": {}, + "dplus": {}, + "salt": {}, + "_": true, + "homePageData": {}, + "Lang": {} + }, + "rules": { + "max-len": 0, + "new-cap": [2, { "newIsCap": true, "properties": false }], + "react/jsx-indent": 0, + "react/jsx-indent-props": 0, + "indent": 0, + "radix": ["error", "as-needed"], + "linebreak-style": [0 ,"error", "windows"] + } +} \ No newline at end of file diff --git a/sop-website/sop-portal/.gitignore b/sop-website/sop-portal/.gitignore new file mode 100644 index 00000000..4996fc3b --- /dev/null +++ b/sop-website/sop-portal/.gitignore @@ -0,0 +1,17 @@ +.DS_Store +node_modules/ +dist/ +build/ +npm-debug.log* +yarn-debug.log* +yarn-error.log* +package-lock.json +tests/**/coverage/ + +# Editor directories and files +.idea +.vscode +*.suo +*.ntvs* +*.njsproj +*.sln diff --git a/sop-website/sop-portal/.nojekyll b/sop-website/sop-portal/.nojekyll new file mode 100644 index 00000000..e69de29b diff --git a/sop-website/sop-portal/404.html b/sop-website/sop-portal/404.html new file mode 100644 index 00000000..2518eab7 --- /dev/null +++ b/sop-website/sop-portal/404.html @@ -0,0 +1,18 @@ + + + + + + + + + + diff --git a/sop-website/sop-portal/README.md b/sop-website/sop-portal/README.md new file mode 100644 index 00000000..02307731 --- /dev/null +++ b/sop-website/sop-portal/README.md @@ -0,0 +1,30 @@ +# 门户网站(不推荐,请使用sop-portal-vue) + +> 不推荐,请使用`sop-portal-vue` + +纯静态网站,作为开放平台基本介绍使用,可独立部署,logo制作:http://www.uugai.com/ + +# 初始化 + +安装node,建议安装11版本,12版本会有不兼容问题。 + +安装完毕执行: + +npm install + +# 启动 + +`docsite start`(windows系统执行:`npm run start`) + +# 打包 + +首先,需要配置下站点的根路径,修改site_config/site.js中的rootPath字段。规则如下: + +当部署根路径为/,则设置为''空字符串即可。 + +当部署根路径不为/,则设置为具体的根路径,注意需以/开头,但不能有尾/。 + +运行`build.sh` + +将dist内容上传到服务器 + diff --git a/sop-website/sop-portal/blog/en-us/blog1.md b/sop-website/sop-portal/blog/en-us/blog1.md new file mode 100644 index 00000000..506a3e14 --- /dev/null +++ b/sop-website/sop-portal/blog/en-us/blog1.md @@ -0,0 +1,10 @@ +--- +key1: hello +key2: world +--- + +# blog1 + +it supports the resolution of meta data,the text between `---`(at least three`-`)written in the format of `key:value`,will be resolved to `md_json/blog.json`,`filename` and `__html` are preserved. + +filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text \ No newline at end of file diff --git a/sop-website/sop-portal/blog/zh-cn/blog1.md b/sop-website/sop-portal/blog/zh-cn/blog1.md new file mode 100644 index 00000000..9537e4b0 --- /dev/null +++ b/sop-website/sop-portal/blog/zh-cn/blog1.md @@ -0,0 +1,10 @@ +--- +key1: hello +key2: world +--- + +# 博客1 + +支持元数据的解析,`---`(至少三个`-`)开头之间的数据按照`key:value`的形式,最终会被解析到`md_json/blog.json`中,其中`filename`和`__html`为保留字段,请勿使用。 + +博客内容填充,博客内容填充博客内容填充,博客内容填充博客内容填充,博客内容填充博客内容填充,博客内容填充博客内容填充,博客内容填充博客内容填充,博客内容填充博客内容填充,博客内容填充博客内容填充,博客内容填充博客内容填充,博客内容填充博客内容填充,博客内容填充博客内容填充,博客内容填充博客内容填充,博客内容填充博客内容填充,博客内容填充博客内容填充,博客内容填充博客内容填充,博客内容填充博客内容填充,博客内容填充博客内容填充,博客内容填充博客内容填充,博客内容填充博客内容填充,博客内容填充博客内容填充,博客内容填充博客内容填充,博客内容填充博客内容填充,博客内容填充博客内容填充,博客内容填充博客内容填充,博客内容填充博客内容填充,博客内容填充博客内容填充,博客内容填充博客内容填充,博客内容填充博客内容填充,博客内容填充博客内容填充,博客内容填充博客内容填充,博客内容填充博客内容填充,博客内容填充博客内容填充,博客内容填充博客内容填充,博客内容填充博客内容填充,博客内容填充博客内容填充,博客内容填充博客内容填充,博客内容填充博客内容填充,博客内容填充博客内容填充,博客内容填充博客内容填充,博客内容填充博客内容填充,博客内容填充博客内容填充,博客内容填充博客内容填充,博客内容填充博客内容填充,博客内容填充博客内容填充,博客内容填充博客内容填充,博客内容填充博客内容填充,博客内容填充博客内容填充,博客内容填充博客内容填充,博客内容填充博客内容填充,博客内容填充博客内容填充,博客内容填充博客内容填充,博客内容填充博客内容填充,博客内容填充博客内容填充,博客内容填充博客内容填充,博客内容填充博客内容填充,博客内容填充博客内容填充,博客内容填充博客内容填充,博客内容填充博客内容填充,博客内容填充博客内容填充,博客内容填充博客内容填充,博客内容填充博客内容填充,博客内容填充博客内容填充,博客内容填充博客内容填充,博客内容填充博客内容填充,博客内容填充博客内容填充,博客内容填充博客内容填充,博客内容填充博客内容填充,博客内容填充博客内容填充,博客内容填充博客内容填充,博客内容填充博客内容填充,博客内容填充博客内容填充,博客内容填充博客内容填充,博客内容填充博客内容填充,博客内容填充博客内容填充,博客内容填充博客内容填充,博客内容填充 \ No newline at end of file diff --git a/sop-website/sop-portal/build.sh b/sop-website/sop-portal/build.sh new file mode 100644 index 00000000..8517b27e --- /dev/null +++ b/sop-website/sop-portal/build.sh @@ -0,0 +1,20 @@ +#!/bin/sh + +echo "开始构建..." + +dist_dir="dist" + +if [ ! -d "$dist_dir" ]; then + mkdir $dist_dir +fi + +docsite build + +echo "复制文件到${dist_dir}目录" + +rm -rf $dist_dir/* +cp -r zh-cn/* $dist_dir +cp -r build $dist_dir/build +cp -r img $dist_dir/img + +echo "构建完毕" diff --git a/sop-website/sop-portal/docs/en-us/demo1.md b/sop-website/sop-portal/docs/en-us/demo1.md new file mode 100644 index 00000000..d3baa456 --- /dev/null +++ b/sop-website/sop-portal/docs/en-us/demo1.md @@ -0,0 +1,249 @@ +# example of footnote + +this is a demo of the usage of footnote. +this is footnote1 [^1]。click it and see what will happen。 + +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. + + + +[^1]: this is the explanation of footnote \ No newline at end of file diff --git a/sop-website/sop-portal/docs/en-us/demo2.md b/sop-website/sop-portal/docs/en-us/demo2.md new file mode 100644 index 00000000..5c626342 --- /dev/null +++ b/sop-website/sop-portal/docs/en-us/demo2.md @@ -0,0 +1,16 @@ +# demo for reference of md or image for relative path + +if you want to reference to another md, the path can be written in the format of relative path under the circumstance of file system. +click here: [demo1.md](./demo1.md)。 + +the same is to image. +![](./img/brhtqqzh.jpeg) + +# demo for math typesetting(katex) + +$E = mc^2$ + +$$x = a_{1}^n + a_{2}^n + a_{3}^n$$ + +$$\sqrt[3]{X}$$ +$$\sqrt{5 - x}$$ \ No newline at end of file diff --git a/sop-website/sop-portal/docs/en-us/dir/demo3.md b/sop-website/sop-portal/docs/en-us/dir/demo3.md new file mode 100644 index 00000000..a54d1405 --- /dev/null +++ b/sop-website/sop-portal/docs/en-us/dir/demo3.md @@ -0,0 +1,7 @@ +# demo for reference of md or image for relative path, across directory + +if you want to reference to another md, the path can be written in the format of relative path under the circumstance of file system. +click here: [demo1.md](../demo1.md)。 + +the same is to image. +![](../img/brhtqqzh.jpeg) \ No newline at end of file diff --git a/sop-website/sop-portal/docs/en-us/img/brhtqqzh.jpeg b/sop-website/sop-portal/docs/en-us/img/brhtqqzh.jpeg new file mode 100644 index 00000000..854369b8 Binary files /dev/null and b/sop-website/sop-portal/docs/en-us/img/brhtqqzh.jpeg differ diff --git a/sop-website/sop-portal/docs/zh-cn/demo1.md b/sop-website/sop-portal/docs/zh-cn/demo1.md new file mode 100644 index 00000000..ec856921 --- /dev/null +++ b/sop-website/sop-portal/docs/zh-cn/demo1.md @@ -0,0 +1,401 @@ +# 脚注的使用 + +这是一段示例文字,展示脚注的使用。 +这是脚注1 [^1]。点击脚注滚动至对应脚注处。 + +下面是一大段文字,填充开始。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 + + +[^1]: 这是脚注的说明 \ No newline at end of file diff --git a/sop-website/sop-portal/docs/zh-cn/demo2.md b/sop-website/sop-portal/docs/zh-cn/demo2.md new file mode 100644 index 00000000..cae6a14b --- /dev/null +++ b/sop-website/sop-portal/docs/zh-cn/demo2.md @@ -0,0 +1,17 @@ +# 文档之间跳转、图片引用的示例 + +文档之间的跳转链接按照文件目录之间的相对关系配置即可,推荐在同一语言目录下的不同文件夹间跳转,以缩短配置路径。 +点击跳转: [demo1.md](./demo1.md)。 + +图片的引用,支持绝对引用和相对引用,如果是相对引用,同文档之间的相互引用一样,是按照文件之间的相对路径进行处理的。比如 +![](./img/brhtqqzh.jpeg) + +# 数学公式的使用(katex) + +$E = mc^2$ + +$$x = a_{1}^n + a_{2}^n + a_{3}^n$$ + +$$\sqrt[3]{X}$$ +$$\sqrt{5 - x}$$ + diff --git a/sop-website/sop-portal/docs/zh-cn/dir/demo3.md b/sop-website/sop-portal/docs/zh-cn/dir/demo3.md new file mode 100644 index 00000000..9d0bddb9 --- /dev/null +++ b/sop-website/sop-portal/docs/zh-cn/dir/demo3.md @@ -0,0 +1,8 @@ +# 跨目录文档之间跳转、图片引用的示例 + +这是一段文字,将要跳转到demo1。 +文档之间的跳转链接按照文件目录之间的相对关系配置即可,推荐在同一语言目录下的不同文件夹间跳转,以缩短配置路径。 +点击跳转: [demo1.md](../demo1.md)。 + +图片的引用,支持绝对引用和相对引用,如果是相对引用,同文档之间的相互引用一样,是按照文件之间的相对路径进行处理的。比如 +![](../img/brhtqqzh.jpeg) \ No newline at end of file diff --git a/sop-website/sop-portal/docs/zh-cn/img/brhtqqzh.jpeg b/sop-website/sop-portal/docs/zh-cn/img/brhtqqzh.jpeg new file mode 100644 index 00000000..854369b8 Binary files /dev/null and b/sop-website/sop-portal/docs/zh-cn/img/brhtqqzh.jpeg differ diff --git a/sop-website/sop-portal/docsite.config.yml b/sop-website/sop-portal/docsite.config.yml new file mode 100644 index 00000000..69921ec2 --- /dev/null +++ b/sop-website/sop-portal/docsite.config.yml @@ -0,0 +1,35 @@ +pages: + # key is the dirname of pages in src/pages + home: + # 首页配置 + zh-cn: + title: 'XX开放平台' + keywords: '开放平台,Open,Api' + description: '一站式开放接口服务平台,可快速搭建起自己的开放平台' + # home config + en-us: + title: 'XX开放平台' + keywords: '开放平台,Open,Api' + description: '一站式开放接口服务平台,可快速搭建起自己的开放平台' + community: + # 社区页配置 + zh-cn: + title: '网页标签title' + keywords: '关键词1,关键词2' + description: '页面内容简介' + # community page config + en-us: + title: 'page title' + keywords: 'keyword1,keyword2' + description: 'page description' + blog: + # 博客列表页配置 + zh-cn: + title: '网页标签title' + keywords: '关键词1,关键词2' + description: '页面内容简介' + en-us: + # blog list page config + title: 'page title' + keywords: 'keyword1,keyword2' + description: 'page description' diff --git a/sop-website/sop-portal/en-us/blog/blog1.html b/sop-website/sop-portal/en-us/blog/blog1.html new file mode 100644 index 00000000..de746ffb --- /dev/null +++ b/sop-website/sop-portal/en-us/blog/blog1.html @@ -0,0 +1,26 @@ + + + + + + + + + + blog1 + + + + +

blog1

+

it supports the resolution of meta data,the text between ---(at least three-)written in the format of key:value,will be resolved to md_json/blog.jsonfilename and __html are preserved.

+

filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text

+
+ + + + + + diff --git a/sop-website/sop-portal/en-us/blog/blog1.json b/sop-website/sop-portal/en-us/blog/blog1.json new file mode 100644 index 00000000..367933f5 --- /dev/null +++ b/sop-website/sop-portal/en-us/blog/blog1.json @@ -0,0 +1,9 @@ +{ + "filename": "blog1.md", + "__html": "

blog1

\n

it supports the resolution of meta data,the text between ---(at least three-)written in the format of key:value,will be resolved to md_json/blog.jsonfilename and __html are preserved.

\n

filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text

\n", + "link": "/en-us/blog/blog1.html", + "meta": { + "key1": "hello", + "key2": "world" + } +} \ No newline at end of file diff --git a/sop-website/sop-portal/en-us/blog/blog2.html b/sop-website/sop-portal/en-us/blog/blog2.html new file mode 100644 index 00000000..cd295cbe --- /dev/null +++ b/sop-website/sop-portal/en-us/blog/blog2.html @@ -0,0 +1,26 @@ + + + + + + + + + + blog2 + + + + +

blog2

+

it supports the resolution of meta data,the text between ---(at least three-)written in the format of key:value,will be resolved to md_json/blog.jsonfilename and __html are preserved.

+

filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text

+
+ + + + + + diff --git a/sop-website/sop-portal/en-us/blog/blog2.json b/sop-website/sop-portal/en-us/blog/blog2.json new file mode 100644 index 00000000..56fb8512 --- /dev/null +++ b/sop-website/sop-portal/en-us/blog/blog2.json @@ -0,0 +1,9 @@ +{ + "filename": "blog2.md", + "__html": "

blog2

\n

it supports the resolution of meta data,the text between ---(at least three-)written in the format of key:value,will be resolved to md_json/blog.jsonfilename and __html are preserved.

\n

filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text

\n", + "link": "/en-us/blog/blog2.html", + "meta": { + "key1": "hello", + "key2": "world" + } +} \ No newline at end of file diff --git a/sop-website/sop-portal/en-us/blog/blog3.html b/sop-website/sop-portal/en-us/blog/blog3.html new file mode 100644 index 00000000..abb7e217 --- /dev/null +++ b/sop-website/sop-portal/en-us/blog/blog3.html @@ -0,0 +1,26 @@ + + + + + + + + + + blog3 + + + + +

blog3

+

it supports the resolution of meta data,the text between ---(at least three-)written in the format of key:value,will be resolved to md_json/blog.jsonfilename and __html are preserved.

+

filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text

+
+ + + + + + diff --git a/sop-website/sop-portal/en-us/blog/blog3.json b/sop-website/sop-portal/en-us/blog/blog3.json new file mode 100644 index 00000000..081163f9 --- /dev/null +++ b/sop-website/sop-portal/en-us/blog/blog3.json @@ -0,0 +1,9 @@ +{ + "filename": "blog3.md", + "__html": "

blog3

\n

it supports the resolution of meta data,the text between ---(at least three-)written in the format of key:value,will be resolved to md_json/blog.jsonfilename and __html are preserved.

\n

filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text

\n", + "link": "/en-us/blog/blog3.html", + "meta": { + "key1": "hello", + "key2": "world" + } +} \ No newline at end of file diff --git a/sop-website/sop-portal/en-us/blog/blog4.html b/sop-website/sop-portal/en-us/blog/blog4.html new file mode 100644 index 00000000..6463eb29 --- /dev/null +++ b/sop-website/sop-portal/en-us/blog/blog4.html @@ -0,0 +1,26 @@ + + + + + + + + + + blog4 + + + + +

blog4

+

it supports the resolution of meta data,the text between ---(at least three-)written in the format of key:value,will be resolved to md_json/blog.jsonfilename and __html are preserved.

+

filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text

+
+ + + + + + diff --git a/sop-website/sop-portal/en-us/blog/blog4.json b/sop-website/sop-portal/en-us/blog/blog4.json new file mode 100644 index 00000000..c81ca9b9 --- /dev/null +++ b/sop-website/sop-portal/en-us/blog/blog4.json @@ -0,0 +1,9 @@ +{ + "filename": "blog4.md", + "__html": "

blog4

\n

it supports the resolution of meta data,the text between ---(at least three-)written in the format of key:value,will be resolved to md_json/blog.jsonfilename and __html are preserved.

\n

filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text

\n", + "link": "/en-us/blog/blog4.html", + "meta": { + "key1": "hello", + "key2": "world" + } +} \ No newline at end of file diff --git a/sop-website/sop-portal/en-us/blog/blog5.html b/sop-website/sop-portal/en-us/blog/blog5.html new file mode 100644 index 00000000..532f4817 --- /dev/null +++ b/sop-website/sop-portal/en-us/blog/blog5.html @@ -0,0 +1,26 @@ + + + + + + + + + + blog5 + + + + +

blog5

+

it supports the resolution of meta data,the text between ---(at least three-)written in the format of key:value,will be resolved to md_json/blog.jsonfilename and __html are preserved.

+

filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text

+
+ + + + + + diff --git a/sop-website/sop-portal/en-us/blog/blog5.json b/sop-website/sop-portal/en-us/blog/blog5.json new file mode 100644 index 00000000..9c18fd2d --- /dev/null +++ b/sop-website/sop-portal/en-us/blog/blog5.json @@ -0,0 +1,9 @@ +{ + "filename": "blog5.md", + "__html": "

blog5

\n

it supports the resolution of meta data,the text between ---(at least three-)written in the format of key:value,will be resolved to md_json/blog.jsonfilename and __html are preserved.

\n

filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text

\n", + "link": "/en-us/blog/blog5.html", + "meta": { + "key1": "hello", + "key2": "world" + } +} \ No newline at end of file diff --git a/sop-website/sop-portal/en-us/blog/blog6.html b/sop-website/sop-portal/en-us/blog/blog6.html new file mode 100644 index 00000000..c56d2053 --- /dev/null +++ b/sop-website/sop-portal/en-us/blog/blog6.html @@ -0,0 +1,26 @@ + + + + + + + + + + blog6 + + + + +

blog6

+

it supports the resolution of meta data,the text between ---(at least three-)written in the format of key:value,will be resolved to md_json/blog.jsonfilename and __html are preserved.

+

filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text

+
+ + + + + + diff --git a/sop-website/sop-portal/en-us/blog/blog6.json b/sop-website/sop-portal/en-us/blog/blog6.json new file mode 100644 index 00000000..b6fae5ed --- /dev/null +++ b/sop-website/sop-portal/en-us/blog/blog6.json @@ -0,0 +1,9 @@ +{ + "filename": "blog6.md", + "__html": "

blog6

\n

it supports the resolution of meta data,the text between ---(at least three-)written in the format of key:value,will be resolved to md_json/blog.jsonfilename and __html are preserved.

\n

filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text

\n", + "link": "/en-us/blog/blog6.html", + "meta": { + "key1": "hello", + "key2": "world" + } +} \ No newline at end of file diff --git a/sop-website/sop-portal/en-us/blog/blog7.html b/sop-website/sop-portal/en-us/blog/blog7.html new file mode 100644 index 00000000..97652bf0 --- /dev/null +++ b/sop-website/sop-portal/en-us/blog/blog7.html @@ -0,0 +1,26 @@ + + + + + + + + + + blog7 + + + + +

blog7

+

it supports the resolution of meta data,the text between ---(at least three-)written in the format of key:value,will be resolved to md_json/blog.jsonfilename and __html are preserved.

+

filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text

+
+ + + + + + diff --git a/sop-website/sop-portal/en-us/blog/blog7.json b/sop-website/sop-portal/en-us/blog/blog7.json new file mode 100644 index 00000000..ceb0b04f --- /dev/null +++ b/sop-website/sop-portal/en-us/blog/blog7.json @@ -0,0 +1,9 @@ +{ + "filename": "blog7.md", + "__html": "

blog7

\n

it supports the resolution of meta data,the text between ---(at least three-)written in the format of key:value,will be resolved to md_json/blog.jsonfilename and __html are preserved.

\n

filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text filling text

\n", + "link": "/en-us/blog/blog7.html", + "meta": { + "key1": "hello", + "key2": "world" + } +} \ No newline at end of file diff --git a/sop-website/sop-portal/en-us/blog/index.html b/sop-website/sop-portal/en-us/blog/index.html new file mode 100644 index 00000000..2cdcfd77 --- /dev/null +++ b/sop-website/sop-portal/en-us/blog/index.html @@ -0,0 +1,23 @@ + + + + + + + + + + page title + + + + + + + + + + + diff --git a/sop-website/sop-portal/en-us/community/index.html b/sop-website/sop-portal/en-us/community/index.html new file mode 100644 index 00000000..3505c6e1 --- /dev/null +++ b/sop-website/sop-portal/en-us/community/index.html @@ -0,0 +1,23 @@ + + + + + + + + + + page title + + + + +
Community

Events & News

this is the title

this is the content

May 12nd,2018

this is the title

this is the content

May 12nd,2018

this is the title

this is the content

May 12nd,2018

this is the title

this is the content

May 12nd,2018

this is the title

this is the content

May 12nd,2018

this is the title

this is the content

May 12nd,2018

this is the title

this is the content

May 12nd,2018

Eco System

Eco1

Eco System1

Eco2

Eco System2

Talk To Us

Feel free to contact us via the following channel.

Contributor Guide

some description

Mailing List

this is the content

Issue

this is the content

Documents

this is the content

Pull Request

this is the content

+ + + + + + diff --git a/sop-website/sop-portal/en-us/docs/demo1.html b/sop-website/sop-portal/en-us/docs/demo1.html new file mode 100644 index 00000000..813a80cc --- /dev/null +++ b/sop-website/sop-portal/en-us/docs/demo1.html @@ -0,0 +1,273 @@ + + + + + + + + + + demo1 + + + + +
Documentation

example of footnote

+

this is a demo of the usage of footnote. +this is footnote1 [1]。click it and see what will happen。

+

this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text. +this is a long text.

+
+
+
    +
  1. this is the explanation of footnote ↩︎

    +
  2. +
+
+
+ + + + + + diff --git a/sop-website/sop-portal/en-us/docs/demo1.json b/sop-website/sop-portal/en-us/docs/demo1.json new file mode 100644 index 00000000..623aadb9 --- /dev/null +++ b/sop-website/sop-portal/en-us/docs/demo1.json @@ -0,0 +1,6 @@ +{ + "filename": "demo1.md", + "__html": "

example of footnote

\n

this is a demo of the usage of footnote.\nthis is footnote1 [1]。click it and see what will happen。

\n

this is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.\nthis is a long text.

\n
\n
\n
    \n
  1. this is the explanation of footnote ↩︎

    \n
  2. \n
\n
\n", + "link": "/en-us/docs/demo1.html", + "meta": {} +} \ No newline at end of file diff --git a/sop-website/sop-portal/en-us/docs/demo2.html b/sop-website/sop-portal/en-us/docs/demo2.html new file mode 100644 index 00000000..28922559 --- /dev/null +++ b/sop-website/sop-portal/en-us/docs/demo2.html @@ -0,0 +1,36 @@ + + + + + + + + + + demo2 + + + + +
Documentation

demo for reference of md or image for relative path

+

if you want to reference to another md, the path can be written in the format of relative path under the circumstance of file system. +click here: demo1.md

+

the same is to image. +

+

demo for math typesetting(katex)

+

E=mc2E = mc^2

+

x=a1n+a2n+a3nx = a_{1}^n + a_{2}^n + a_{3}^n +

+

X3\sqrt[3]{X} +

+

5x\sqrt{5 - x} +

+
+ + + + + + diff --git a/sop-website/sop-portal/en-us/docs/demo2.json b/sop-website/sop-portal/en-us/docs/demo2.json new file mode 100644 index 00000000..5b03c4c8 --- /dev/null +++ b/sop-website/sop-portal/en-us/docs/demo2.json @@ -0,0 +1,6 @@ +{ + "filename": "demo2.md", + "__html": "

demo for reference of md or image for relative path

\n

if you want to reference to another md, the path can be written in the format of relative path under the circumstance of file system.\nclick here: demo1.md

\n

the same is to image.\n\"\"

\n

demo for math typesetting(katex)

\n

E=mc2E = mc^2E=mc2

\n

x=a1n+a2n+a3nx = a_{1}^n + a_{2}^n + a_{3}^n\nx=a1n+a2n+a3n

\n

X3\\sqrt[3]{X}\n3X

\n

5x\\sqrt{5 - x}\n5x

\n", + "link": "/en-us/docs/demo2.html", + "meta": {} +} \ No newline at end of file diff --git a/sop-website/sop-portal/en-us/docs/dir/demo3.html b/sop-website/sop-portal/en-us/docs/dir/demo3.html new file mode 100644 index 00000000..dd3f1a7a --- /dev/null +++ b/sop-website/sop-portal/en-us/docs/dir/demo3.html @@ -0,0 +1,28 @@ + + + + + + + + + + demo3 + + + + +
Documentation

demo for reference of md or image for relative path, across directory

+

if you want to reference to another md, the path can be written in the format of relative path under the circumstance of file system. +click here: demo1.md

+

the same is to image. +

+
+ + + + + + diff --git a/sop-website/sop-portal/en-us/docs/dir/demo3.json b/sop-website/sop-portal/en-us/docs/dir/demo3.json new file mode 100644 index 00000000..6690dcef --- /dev/null +++ b/sop-website/sop-portal/en-us/docs/dir/demo3.json @@ -0,0 +1,6 @@ +{ + "filename": "demo3.md", + "__html": "

demo for reference of md or image for relative path, across directory

\n

if you want to reference to another md, the path can be written in the format of relative path under the circumstance of file system.\nclick here: demo1.md

\n

the same is to image.\n\"\"

\n", + "link": "/en-us/docs/dir/demo3.html", + "meta": {} +} \ No newline at end of file diff --git a/sop-website/sop-portal/en-us/index.html b/sop-website/sop-portal/en-us/index.html new file mode 100644 index 00000000..e222e7c1 --- /dev/null +++ b/sop-website/sop-portal/en-us/index.html @@ -0,0 +1,23 @@ + + + + + + + + + + XX开放平台 + + + + +

brandName

some description of product

Feature List

  • feature1

    feature description

  • feature2

    feature description

  • feature3

    feature description

  • feature4

    feature description

  • feature5

    feature description

  • feature6

    feature description

+ + + + + + diff --git a/sop-website/sop-portal/gulpfile.js b/sop-website/sop-portal/gulpfile.js new file mode 100644 index 00000000..bef72348 --- /dev/null +++ b/sop-website/sop-portal/gulpfile.js @@ -0,0 +1,60 @@ +require('babel-register')(); +const gulp = require('gulp'); +const gutil = require('gulp-util'); +const webpack = require('webpack'); +const opn = require('opn'); +const WebpackDevServer = require('webpack-dev-server'); +const siteConfig = require('./site_config/site').default; +const webpackConfig = require('./webpack.config.js'); + +const port = siteConfig.port || 8080; + +// The development server (the recommended option for development) +gulp.task('default', ['webpack-dev-server']); + +// Production build +gulp.task('build', ['webpack:build']); + +gulp.task('webpack-dev-server', () => { + // modify some webpack config options + const myConfig = Object.create(webpackConfig); + myConfig.plugins.push(new webpack.SourceMapDevToolPlugin({})); + // Start a webpack-dev-server + new WebpackDevServer(webpack(myConfig), { + publicPath: `http://127.0.0.1:${port}/build/`, + stats: { + colors: true, + }, + }).listen(port, '127.0.0.1', err => { + if (err) throw new gutil.PluginError('webpack-dev-server', err); + opn(`http://127.0.0.1:${port}/`); + gutil.log('[webpack-dev-server]', `http://127.0.0.1:${port}/webpack-dev-server/index.html`); + }); +}); + +gulp.task('webpack:build', callback => { + // modify some webpack config options + const myConfig = Object.create(webpackConfig); + myConfig.output.publicPath = `${siteConfig.rootPath}/build/`; + myConfig.plugins = myConfig.plugins.concat( + new webpack.DefinePlugin({ + 'process.env': { + // This has effect on the react lib size + NODE_ENV: JSON.stringify('production'), + }, + }), + new webpack.optimize.UglifyJsPlugin() + ); + + // run webpack + webpack(myConfig, (err, stats) => { + if (err) throw new gutil.PluginError('webpack:build', err); + gutil.log( + '[webpack:build]', + stats.toString({ + colors: true, + }) + ); + callback(); + }); +}); diff --git a/sop-website/sop-portal/img/alibaba.png b/sop-website/sop-portal/img/alibaba.png new file mode 100644 index 00000000..32f736b2 Binary files /dev/null and b/sop-website/sop-portal/img/alibaba.png differ diff --git a/sop-website/sop-portal/img/alibaba_hover.png b/sop-website/sop-portal/img/alibaba_hover.png new file mode 100644 index 00000000..bbc81d0e Binary files /dev/null and b/sop-website/sop-portal/img/alibaba_hover.png differ diff --git a/sop-website/sop-portal/img/architecture.png b/sop-website/sop-portal/img/architecture.png new file mode 100644 index 00000000..d010dfb0 Binary files /dev/null and b/sop-website/sop-portal/img/architecture.png differ diff --git a/sop-website/sop-portal/img/brhtqqzh.jpeg b/sop-website/sop-portal/img/brhtqqzh.jpeg new file mode 100755 index 00000000..df36b234 Binary files /dev/null and b/sop-website/sop-portal/img/brhtqqzh.jpeg differ diff --git a/sop-website/sop-portal/img/docsite.ico b/sop-website/sop-portal/img/docsite.ico new file mode 100644 index 00000000..224e8d20 Binary files /dev/null and b/sop-website/sop-portal/img/docsite.ico differ diff --git a/sop-website/sop-portal/img/documents.png b/sop-website/sop-portal/img/documents.png new file mode 100644 index 00000000..343c0d1b Binary files /dev/null and b/sop-website/sop-portal/img/documents.png differ diff --git a/sop-website/sop-portal/img/favicon.ico b/sop-website/sop-portal/img/favicon.ico new file mode 100644 index 00000000..5d417587 Binary files /dev/null and b/sop-website/sop-portal/img/favicon.ico differ diff --git a/sop-website/sop-portal/img/feature_hogh.png b/sop-website/sop-portal/img/feature_hogh.png new file mode 100644 index 00000000..e0b1a8b2 Binary files /dev/null and b/sop-website/sop-portal/img/feature_hogh.png differ diff --git a/sop-website/sop-portal/img/feature_loadbalances.png b/sop-website/sop-portal/img/feature_loadbalances.png new file mode 100644 index 00000000..05efb58b Binary files /dev/null and b/sop-website/sop-portal/img/feature_loadbalances.png differ diff --git a/sop-website/sop-portal/img/feature_maintenance.png b/sop-website/sop-portal/img/feature_maintenance.png new file mode 100644 index 00000000..f1ae0fdb Binary files /dev/null and b/sop-website/sop-portal/img/feature_maintenance.png differ diff --git a/sop-website/sop-portal/img/feature_runtime.png b/sop-website/sop-portal/img/feature_runtime.png new file mode 100644 index 00000000..c09b8627 Binary files /dev/null and b/sop-website/sop-portal/img/feature_runtime.png differ diff --git a/sop-website/sop-portal/img/feature_service.png b/sop-website/sop-portal/img/feature_service.png new file mode 100644 index 00000000..bb5be229 Binary files /dev/null and b/sop-website/sop-portal/img/feature_service.png differ diff --git a/sop-website/sop-portal/img/feature_transpart.png b/sop-website/sop-portal/img/feature_transpart.png new file mode 100644 index 00000000..bfea1349 Binary files /dev/null and b/sop-website/sop-portal/img/feature_transpart.png differ diff --git a/sop-website/sop-portal/img/home_1.png b/sop-website/sop-portal/img/home_1.png new file mode 100644 index 00000000..139a85c1 Binary files /dev/null and b/sop-website/sop-portal/img/home_1.png differ diff --git a/sop-website/sop-portal/img/home_2.png b/sop-website/sop-portal/img/home_2.png new file mode 100644 index 00000000..d5e7cff7 Binary files /dev/null and b/sop-website/sop-portal/img/home_2.png differ diff --git a/sop-website/sop-portal/img/home_3.png b/sop-website/sop-portal/img/home_3.png new file mode 100644 index 00000000..528117c8 Binary files /dev/null and b/sop-website/sop-portal/img/home_3.png differ diff --git a/sop-website/sop-portal/img/home_5.png b/sop-website/sop-portal/img/home_5.png new file mode 100644 index 00000000..01969feb Binary files /dev/null and b/sop-website/sop-portal/img/home_5.png differ diff --git a/sop-website/sop-portal/img/issue.png b/sop-website/sop-portal/img/issue.png new file mode 100644 index 00000000..e1a026d9 Binary files /dev/null and b/sop-website/sop-portal/img/issue.png differ diff --git a/sop-website/sop-portal/img/logo.png b/sop-website/sop-portal/img/logo.png new file mode 100644 index 00000000..b201d8c0 Binary files /dev/null and b/sop-website/sop-portal/img/logo.png differ diff --git a/sop-website/sop-portal/img/logo_old.png b/sop-website/sop-portal/img/logo_old.png new file mode 100644 index 00000000..bf8cda70 Binary files /dev/null and b/sop-website/sop-portal/img/logo_old.png differ diff --git a/sop-website/sop-portal/img/logo_old_white.png b/sop-website/sop-portal/img/logo_old_white.png new file mode 100644 index 00000000..a36bbc41 Binary files /dev/null and b/sop-website/sop-portal/img/logo_old_white.png differ diff --git a/sop-website/sop-portal/img/logo_white.png b/sop-website/sop-portal/img/logo_white.png new file mode 100644 index 00000000..a09738f8 Binary files /dev/null and b/sop-website/sop-portal/img/logo_white.png differ diff --git a/sop-website/sop-portal/img/mailinglist.png b/sop-website/sop-portal/img/mailinglist.png new file mode 100644 index 00000000..a219fe88 Binary files /dev/null and b/sop-website/sop-portal/img/mailinglist.png differ diff --git a/sop-website/sop-portal/img/mailinglist_hover.png b/sop-website/sop-portal/img/mailinglist_hover.png new file mode 100644 index 00000000..785705c9 Binary files /dev/null and b/sop-website/sop-portal/img/mailinglist_hover.png differ diff --git a/sop-website/sop-portal/img/pullrequest.png b/sop-website/sop-portal/img/pullrequest.png new file mode 100644 index 00000000..fc05edfb Binary files /dev/null and b/sop-website/sop-portal/img/pullrequest.png differ diff --git a/sop-website/sop-portal/img/quick_start.png b/sop-website/sop-portal/img/quick_start.png new file mode 100644 index 00000000..44b7069d Binary files /dev/null and b/sop-website/sop-portal/img/quick_start.png differ diff --git a/sop-website/sop-portal/img/segmentfault.png b/sop-website/sop-portal/img/segmentfault.png new file mode 100644 index 00000000..09d2f3da Binary files /dev/null and b/sop-website/sop-portal/img/segmentfault.png differ diff --git a/sop-website/sop-portal/img/segmentfault_hover.png b/sop-website/sop-portal/img/segmentfault_hover.png new file mode 100644 index 00000000..56707d02 Binary files /dev/null and b/sop-website/sop-portal/img/segmentfault_hover.png differ diff --git a/sop-website/sop-portal/img/system/arrow_down.png b/sop-website/sop-portal/img/system/arrow_down.png new file mode 100644 index 00000000..2924eb55 Binary files /dev/null and b/sop-website/sop-portal/img/system/arrow_down.png differ diff --git a/sop-website/sop-portal/img/system/arrow_right.png b/sop-website/sop-portal/img/system/arrow_right.png new file mode 100644 index 00000000..4555b12b Binary files /dev/null and b/sop-website/sop-portal/img/system/arrow_right.png differ diff --git a/sop-website/sop-portal/img/system/blog.png b/sop-website/sop-portal/img/system/blog.png new file mode 100644 index 00000000..579f759d Binary files /dev/null and b/sop-website/sop-portal/img/system/blog.png differ diff --git a/sop-website/sop-portal/img/system/community.png b/sop-website/sop-portal/img/system/community.png new file mode 100644 index 00000000..5836a4af Binary files /dev/null and b/sop-website/sop-portal/img/system/community.png differ diff --git a/sop-website/sop-portal/img/system/docs.png b/sop-website/sop-portal/img/system/docs.png new file mode 100644 index 00000000..f41db69e Binary files /dev/null and b/sop-website/sop-portal/img/system/docs.png differ diff --git a/sop-website/sop-portal/img/system/docs_hover.png b/sop-website/sop-portal/img/system/docs_hover.png new file mode 100644 index 00000000..3568312e Binary files /dev/null and b/sop-website/sop-portal/img/system/docs_hover.png differ diff --git a/sop-website/sop-portal/img/system/docs_normal.png b/sop-website/sop-portal/img/system/docs_normal.png new file mode 100644 index 00000000..4f31e506 Binary files /dev/null and b/sop-website/sop-portal/img/system/docs_normal.png differ diff --git a/sop-website/sop-portal/img/system/menu_gray.png b/sop-website/sop-portal/img/system/menu_gray.png new file mode 100644 index 00000000..9838a877 Binary files /dev/null and b/sop-website/sop-portal/img/system/menu_gray.png differ diff --git a/sop-website/sop-portal/img/system/menu_white.png b/sop-website/sop-portal/img/system/menu_white.png new file mode 100644 index 00000000..5943a141 Binary files /dev/null and b/sop-website/sop-portal/img/system/menu_white.png differ diff --git a/sop-website/sop-portal/img/system/next.png b/sop-website/sop-portal/img/system/next.png new file mode 100644 index 00000000..d65f97d3 Binary files /dev/null and b/sop-website/sop-portal/img/system/next.png differ diff --git a/sop-website/sop-portal/img/system/prev.png b/sop-website/sop-portal/img/system/prev.png new file mode 100644 index 00000000..a68639b5 Binary files /dev/null and b/sop-website/sop-portal/img/system/prev.png differ diff --git a/sop-website/sop-portal/img/users_alibaba.png b/sop-website/sop-portal/img/users_alibaba.png new file mode 100644 index 00000000..25c277d4 Binary files /dev/null and b/sop-website/sop-portal/img/users_alibaba.png differ diff --git a/sop-website/sop-portal/img/weibo.png b/sop-website/sop-portal/img/weibo.png new file mode 100644 index 00000000..89ffd326 Binary files /dev/null and b/sop-website/sop-portal/img/weibo.png differ diff --git a/sop-website/sop-portal/img/weibo_hover.png b/sop-website/sop-portal/img/weibo_hover.png new file mode 100644 index 00000000..9d800ff3 Binary files /dev/null and b/sop-website/sop-portal/img/weibo_hover.png differ diff --git a/sop-website/sop-portal/index.html b/sop-website/sop-portal/index.html new file mode 100644 index 00000000..2518eab7 --- /dev/null +++ b/sop-website/sop-portal/index.html @@ -0,0 +1,18 @@ + + + + + + + + + + diff --git a/sop-website/sop-portal/md_json/blog.json b/sop-website/sop-portal/md_json/blog.json new file mode 100644 index 00000000..464d75c0 --- /dev/null +++ b/sop-website/sop-portal/md_json/blog.json @@ -0,0 +1,22 @@ +{ + "en-us": [ + { + "filename": "blog1.md", + "link": "/en-us/blog/blog1.html", + "meta": { + "key1": "hello", + "key2": "world" + } + } + ], + "zh-cn": [ + { + "filename": "blog1.md", + "link": "/zh-cn/blog/blog1.html", + "meta": { + "key1": "hello", + "key2": "world" + } + } + ] +} \ No newline at end of file diff --git a/sop-website/sop-portal/md_json/docs.json b/sop-website/sop-portal/md_json/docs.json new file mode 100644 index 00000000..e00de912 --- /dev/null +++ b/sop-website/sop-portal/md_json/docs.json @@ -0,0 +1,36 @@ +{ + "en-us": [ + { + "filename": "demo1.md", + "link": "/en-us/docs/demo1.html", + "meta": {} + }, + { + "filename": "demo2.md", + "link": "/en-us/docs/demo2.html", + "meta": {} + }, + { + "filename": "demo3.md", + "link": "/en-us/docs/dir/demo3.html", + "meta": {} + } + ], + "zh-cn": [ + { + "filename": "demo1.md", + "link": "/zh-cn/docs/demo1.html", + "meta": {} + }, + { + "filename": "demo2.md", + "link": "/zh-cn/docs/demo2.html", + "meta": {} + }, + { + "filename": "demo3.md", + "link": "/zh-cn/docs/dir/demo3.html", + "meta": {} + } + ] +} \ No newline at end of file diff --git a/sop-website/sop-portal/package.json b/sop-website/sop-portal/package.json new file mode 100644 index 00000000..10f78e0d --- /dev/null +++ b/sop-website/sop-portal/package.json @@ -0,0 +1,53 @@ +{ + "name": "site", + "version": "0.0.1", + "private": true, + "scripts": { + "start": "docsite start", + "build": "docsite build" + }, + "devDependencies": { + "docsite": "^1.1.0", + "babel-core": "6.23.1", + "babel-eslint": "^6.0.5", + "babel-loader": "6.4.0", + "babel-plugin-transform-class-properties": "^6.24.1", + "babel-plugin-transform-decorators-legacy": "^1.3.4", + "babel-plugin-transform-object-assign": "^6.22.0", + "babel-plugin-transform-object-rest-spread": "^6.23.0", + "babel-plugin-transform-runtime": "^6.23.0", + "babel-preset-es2015": "6.22.0", + "babel-preset-react": "6.23.0", + "babel-preset-stage-0": "6.22.0", + "babel-register": "^6.26.0", + "css-loader": "0.6.12", + "eslint": "^3.6.0", + "eslint-config-ali": "^1.0.0", + "eslint-plugin-import": "*", + "eslint-plugin-react": "*", + "extract-text-webpack-plugin": "^2.1.2", + "gulp": "3.9.1", + "gulp-util": "2.2.20", + "json-loader": "*", + "node-libs-browser": "2.0.0", + "node-sass": "^4.7.2", + "opn": "^5.3.0", + "raw-loader": "^0.5.1", + "react": "16.5.2", + "react-dom": "16.5.2", + "sass-loader": "6.0.2", + "style-loader": "0.6.5", + "webpack": "^2.6.1", + "webpack-dev-server": "^2.4.5" + }, + "dependencies": { + "classnames": "^2.2.5", + "core-decorators": "^0.20.0", + "js-cookie": "^2.2.0", + "react": "^16.5.2", + "react-dom": "^16.5.2", + "react-scroll": "^1.7.9", + "react-tilt": "^0.1.4", + "whatwg-fetch": "^2.0.4" + } +} diff --git a/sop-website/sop-portal/redirect.ejs b/sop-website/sop-portal/redirect.ejs new file mode 100644 index 00000000..25f7b6be --- /dev/null +++ b/sop-website/sop-portal/redirect.ejs @@ -0,0 +1,18 @@ + + + + + + + + + + diff --git a/sop-website/sop-portal/site_config/blog.js b/sop-website/sop-portal/site_config/blog.js new file mode 100644 index 00000000..cecd311b --- /dev/null +++ b/sop-website/sop-portal/site_config/blog.js @@ -0,0 +1,112 @@ +export default { + 'en-us': { + barText: 'Blog', + postsTitle: 'All posts', + list: [ + { + title: 'The first blog', + author: 'author1', + dateStr: 'May 12nd,2018', + desc: 'Blog description, some text to sum up the main content of the blog', + link: '/en-us/blog/blog1.html', + }, + { + title: 'The second blog', + author: 'author2', + dateStr: 'May 2nd,2018', + desc: 'Blog description, some text to sum up the main content of the blog', + link: '/en-us/blog/blog2.html', + }, + { + title: 'The third blog', + author: 'author3', + dateStr: 'April 25th,2018', + desc: 'Blog description, some text to sum up the main content of the blog', + link: '/en-us/blog/blog3.html', + }, + { + title: 'The forth blog', + author: 'author4', + dateStr: 'April 22nd,2018', + desc: 'Blog description, some text to sum up the main content of the blog', + link: '/en-us/blog/blog4.html', + }, + { + title: 'The fifth blog', + author: 'author5', + dateStr: 'April 22nd,2018', + desc: 'Blog description, some text to sum up the main content of the blog', + link: '/en-us/blog/blog5.html', + }, + { + title: 'The sixth blog', + author: 'author6', + dateStr: 'April 22nd,2018', + desc: 'Blog description, some text to sum up the main content of the blog', + link: '/en-us/blog/blog6.html', + }, + { + title: 'The seventh blog', + author: 'author7', + dateStr: 'April 22nd,2018', + desc: 'Blog description, some text to sum up the main content of the blog', + link: '/en-us/blog/blog7.html', + }, + ], + }, + 'zh-cn': { + barText: '博客', + postsTitle: '所有文章', + list: [ + { + title: '第一篇博客', + author: 'author1', + dateStr: 'May 12nd,2018', + desc: '博客描述,总结博客的主体内容', + link: '/zh-cn/blog/blog1.html', + }, + { + title: '第二篇博客', + author: 'author2', + dateStr: 'May 2nd,2018', + desc: '博客描述,总结博客的主体内容', + link: '/zh-cn/blog/blog2.html', + }, + { + title: '第三篇博客', + author: 'author3', + dateStr: 'April 25th,2018', + desc: '博客描述,总结博客的主体内容', + link: '/zh-cn/blog/blog3.html', + }, + { + title: '第四篇博客', + author: 'author4', + dateStr: 'April 22nd,2018', + desc: '博客描述,总结博客的主体内容', + link: '/zh-cn/blog/blog4.html', + }, + { + title: '第五篇博客', + author: 'author5', + dateStr: 'April 22nd,2018', + desc: '博客描述,总结博客的主体内容', + link: '/zh-cn/blog/blog5.html', + }, + { + title: '第六篇博客', + author: 'author6', + dateStr: 'April 22nd,2018', + desc: '博客描述,总结博客的主体内容', + link: '/zh-cn/blog/blog6.html', + }, + { + title: '第七篇博客', + author: 'author7', + dateStr: 'April 22nd,2018', + desc: '博客描述,总结博客的主体内容', + link: '/zh-cn/blog/blog7.html', + }, + ], + }, +}; diff --git a/sop-website/sop-portal/site_config/community.jsx b/sop-website/sop-portal/site_config/community.jsx new file mode 100644 index 00000000..1affe896 --- /dev/null +++ b/sop-website/sop-portal/site_config/community.jsx @@ -0,0 +1,334 @@ +import React from 'react'; + +export default { + 'en-us': { + barText: 'Community', + events: { + title: 'Events & News', + list: [ + { + img: '/img/brhtqqzh.jpeg', + title: 'this is the title', + content: 'this is the content', + dateStr: 'May 12nd,2018', + link: '/en-us/blog/blog1.html', + }, + { + img: '/img/brhtqqzh.jpeg', + title: 'this is the title', + content: 'this is the content', + dateStr: 'May 12nd,2018', + link: '/en-us/blog/blog2.html', + }, + { + img: '/img/brhtqqzh.jpeg', + title: 'this is the title', + content: 'this is the content', + dateStr: 'May 12nd,2018', + link: '/en-us/blog/blog3.html', + }, + { + img: '/img/brhtqqzh.jpeg', + title: 'this is the title', + content: 'this is the content', + dateStr: 'May 12nd,2018', + link: '/en-us/blog/blog4.html', + }, + { + img: '/img/brhtqqzh.jpeg', + title: 'this is the title', + content: 'this is the content', + dateStr: 'May 12nd,2018', + link: '/en-us/blog/blog5.html', + }, + { + img: '/img/brhtqqzh.jpeg', + title: 'this is the title', + content: 'this is the content', + dateStr: 'May 12nd,2018', + link: '/en-us/blog/blog6.html', + }, + { + img: '/img/brhtqqzh.jpeg', + title: 'this is the title', + content: 'this is the content', + dateStr: 'May 12nd,2018', + link: '/en-us/blog/blog7.html', + }, + ] + }, + contacts: { + title: 'Talk To Us', + desc: 'Feel free to contact us via the following channel.', + list: [ + { + img: '/img/mailinglist.png', + imgHover: '/img/mailinglist_hover.png', + title: 'Mailing List', + link: '' + }, + { + img: '/img/alibaba.png', + imgHover: '/img/alibaba_hover.png', + title: 'gitter', + link: '', + }, + { + img: '/img/segmentfault.png', + imgHover: '/img/segmentfault_hover.png', + title: 'Segment Fault', + link: '' + }, + { + img: '/img/weibo.png', + imgHover: '/img/weibo_hover.png', + title: 'weibo', + link: '', + }, + ], + }, + contributorGuide: { + title: 'Contributor Guide', + desc: 'some description', + list: [ + { + img: '/img/mailinglist.png', + title: 'Mailing List', + content: this is the content, + }, + { + img: '/img/issue.png', + title: 'Issue', + content: this is the content, + }, + { + img: '/img/documents.png', + title: 'Documents', + content: this is the content, + }, + { + img: '/img/pullrequest.png', + title: 'Pull Request', + content: this is the content, + }, + ], + }, + ecos: { + title: 'Eco System', + list: [ + { + title: 'Eco1', + content: Eco System1, + tags: [ + { + text: 'tag1', + link: '', + bgColor: '#7A63FC', + }, + { + text: 'tag2', + link: '', + bgColor: '#00D0D9', + }, + { + text: 'tag3', + link: '', + bgColor: '#00D0D9', + }, + ], + }, + { + title: 'Eco2', + content: Eco System2, + tags: [ + { + text: 'tag1', + link: '', + bgColor: '#7A63FC', + }, + { + text: 'tag2', + link: '', + bgColor: '#00D0D9', + }, + { + text: 'tag3', + link: '/en-us/docs/demo1.html', + bgColor: '#00D0D9', + }, + { + text: 'tag4', + link: '', + bgColor: '#00D0D9', + }, + ], + }, + ], + }, + }, + 'zh-cn': { + barText: '社区', + events: { + title: '事件 & 新闻', + list: [ + { + img: '/img/brhtqqzh.jpeg', + title: '这是标题', + content: '这是内容', + dateStr: 'May 12nd,2018', + link: '/zh-cn/blog/blog1.html', + }, + { + img: '/img/brhtqqzh.jpeg', + title: '这是标题', + content: '这是内容', + dateStr: 'May 12nd,2018', + link: '/zh-cn/blog/blog2.html', + }, + { + img: '/img/brhtqqzh.jpeg', + title: '这是标题', + content: '这是内容', + dateStr: 'May 12nd,2018', + link: '/zh-cn/blog/blog3.html', + }, + { + img: '/img/brhtqqzh.jpeg', + title: '这是标题', + content: '这是内容', + dateStr: 'May 12nd,2018', + link: '/zh-cn/blog/blog4.html', + }, + { + img: '/img/brhtqqzh.jpeg', + title: '这是标题', + content: '这是内容', + dateStr: 'May 12nd,2018', + link: '/zh-cn/blog/blog5.html', + }, + { + img: '/img/brhtqqzh.jpeg', + title: '这是标题', + content: '这是内容', + dateStr: 'May 12nd,2018', + link: '/zh-cn/blog/blog6.html', + }, + { + img: '/img/brhtqqzh.jpeg', + title: '这是标题', + content: '这是内容', + dateStr: 'May 12nd,2018', + link: '/zh-cn/blog/blog7.html', + }, + ] + }, + contacts: { + title: '联系我们', + desc: '有问题需要反馈?请通过一下方式联系我们。', + list: [ + { + img: '/img/mailinglist.png', + imgHover: '/img/mailinglist_hover.png', + title: '邮件列表', + link: '' + }, + { + img: '/img/alibaba.png', + imgHover: '/img/alibaba_hover.png', + title: 'gitter', + link: '', + }, + { + img: '/img/segmentfault.png', + imgHover: '/img/segmentfault_hover.png', + title: 'Segment Fault', + link: '' + }, + { + img: '/img/weibo.png', + imgHover: '/img/weibo_hover.png', + title: '微博', + link: '', + }, + ], + }, + contributorGuide: { + title: '贡献指南', + desc: '一些描述', + list: [ + { + img: '/img/mailinglist.png', + title: '邮件列表', + content: 这是描述, + }, + { + img: '/img/issue.png', + title: '报告缺陷', + content: 这是描述, + }, + { + img: '/img/documents.png', + title: '文档', + content: 这是描述, + }, + { + img: '/img/pullrequest.png', + title: 'Pull Request', + content: 这是描述, + }, + ], + }, + ecos: { + title: '生态系统', + list: [ + { + title: '生态系统1', + content: 生态系统1, + tags: [ + { + text: '标签1', + link: '', + bgColor: '#7A63FC', + }, + { + text: '标签2', + link: '', + bgColor: '#00D0D9', + }, + { + text: '标签3', + link: '', + bgColor: '#00D0D9', + }, + ], + }, + { + title: '生态系统2', + content: 生态系统2, + tags: [ + { + text: '标签1', + link: '', + bgColor: '#7A63FC', + }, + { + text: '标签2', + link: '', + bgColor: '#00D0D9', + }, + { + text: '标签3', + link: '/zh-cn/docs/demo1.html', + bgColor: '#00D0D9', + }, + { + text: '标签4', + link: '', + bgColor: '#00D0D9', + }, + ], + }, + ], + }, + }, +}; diff --git a/sop-website/sop-portal/site_config/docs.js b/sop-website/sop-portal/site_config/docs.js new file mode 100644 index 00000000..95ecf679 --- /dev/null +++ b/sop-website/sop-portal/site_config/docs.js @@ -0,0 +1,58 @@ +export default { + 'en-us': { + sidemenu: [ + { + title: 'header title', + children: [ + { + title: 'demo1', + link: '/en-us/docs/demo1.html', + }, + { + title: 'demo2', + link: '/en-us/docs/demo2.html', + }, + { + title: 'dir', + opened: true, + children: [ + { + title: 'demo3', + link: '/en-us/docs/dir/demo3.html', + }, + ], + }, + ], + }, + ], + barText: 'Documentation', + }, + 'zh-cn': { + sidemenu: [ + { + title: '大标题', + children: [ + { + title: '示例1', + link: '/zh-cn/docs/demo1.html', + }, + { + title: '示例2', + link: '/zh-cn/docs/demo2.html', + }, + { + title: '目录', + opened: true, + children: [ + { + title: '示例3', + link: '/zh-cn/docs/dir/demo3.html', + }, + ], + }, + ], + }, + ], + barText: '文档', + }, +}; diff --git a/sop-website/sop-portal/site_config/home.jsx b/sop-website/sop-portal/site_config/home.jsx new file mode 100644 index 00000000..8115785a --- /dev/null +++ b/sop-website/sop-portal/site_config/home.jsx @@ -0,0 +1,170 @@ +import React from 'react'; + +export default { + 'zh-cn': { + brand: { + brandName: '开放平台', + briefIntroduction: 'XX开放平台,一句话介绍。。。', + buttons: [ + { + text: '开始使用', + link: 'http://localhost:8083/index.html#/login', + type: 'primary', + } + ], + }, + introduction: { + title: 'XX介绍', + desc: 'XX介绍描述文字', + img: '/img/home_1.png', + }, + // 介绍部分 + introductions: [ + { + title: '介绍部分1', + desc: '介绍文字。。。', + img: '/img/quick_start.png', + }, + { + title: '介绍部分2', + desc: '介绍文字。。。', + img: '/img/quick_start.png', + }], + features: { + title: '特性一览', + list: [ + { + img: '/img/feature_maintenance.png', + title: '特性1', + content: '描述文字。。', + }, + { + img: '/img/feature_loadbalances.png', + title: '特性2', + content: '描述文字。。', + } + ], + }, + start: { + title: '快速开始', + desc: '简单描述', + img: '/img/quick_start.png', + button: { + text: '阅读更多', + link: '/zh-cn/docs/demo1.html', + }, + }, + users: { + title: '用户', + desc: 简单描述, + list: [ + '/img/users_alibaba.png', + '/img/users_alibaba.png', + '/img/users_alibaba.png', + '/img/users_alibaba.png', + '/img/users_alibaba.png', + '/img/users_alibaba.png', + '/img/users_alibaba.png', + '/img/users_alibaba.png', + '/img/users_alibaba.png', + '/img/users_alibaba.png', + '/img/users_alibaba.png', + '/img/users_alibaba.png', + '/img/users_alibaba.png', + '/img/users_alibaba.png', + '/img/users_alibaba.png', + '/img/users_alibaba.png', + ], + }, + }, + 'en-us': { + brand: { + brandName: 'brandName', + briefIntroduction: 'some description of product', + buttons: [ + { + text: 'Quick Start', + link: '/en-us/docs/demo1.html', + type: 'primary', + }, + { + text: 'View on Github', + link: '', + type: 'normal', + }, + ], + }, + introduction: { + title: 'introduction title', + desc: 'some introduction of your product', + img: '/img/architecture.png', + }, + introductions: [], + features: { + title: 'Feature List', + list: [ + { + img: '/img/feature_transpart.png', + title: 'feature1', + content: 'feature description', + }, + { + img: '/img/feature_loadbalances.png', + title: 'feature2', + content: 'feature description', + }, + { + img: '/img/feature_service.png', + title: 'feature3', + content: 'feature description', + }, + { + img: '/img/feature_hogh.png', + title: 'feature4', + content: 'feature description', + }, + { + img: '/img/feature_runtime.png', + title: 'feature5', + content: 'feature description', + }, + { + img: '/img/feature_maintenance.png', + title: 'feature6', + content: 'feature description', + } + ] + }, + start: { + title: 'Quick start', + desc: 'some description text', + img: '/img/quick_start.png', + button: { + text: 'READ MORE', + link: '/en-us/docs/demo1.html', + }, + }, + users: { + title: 'users', + desc: some description, + list: [ + '/img/users_alibaba.png', + '/img/users_alibaba.png', + '/img/users_alibaba.png', + '/img/users_alibaba.png', + '/img/users_alibaba.png', + '/img/users_alibaba.png', + '/img/users_alibaba.png', + '/img/users_alibaba.png', + '/img/users_alibaba.png', + '/img/users_alibaba.png', + '/img/users_alibaba.png', + '/img/users_alibaba.png', + '/img/users_alibaba.png', + '/img/users_alibaba.png', + '/img/users_alibaba.png', + '/img/users_alibaba.png', + ], + }, + }, +}; diff --git a/sop-website/sop-portal/site_config/site.js b/sop-website/sop-portal/site_config/site.js new file mode 100644 index 00000000..0edcaad8 --- /dev/null +++ b/sop-website/sop-portal/site_config/site.js @@ -0,0 +1,116 @@ +// 全局的一些配置 +export default { + rootPath: '', // 发布到服务器的根目录,需以/开头但不能有尾/,如果只有/,请填写空字符串 + port: 8080, // 本地开发服务器的启动端口 + domain: 'localhost', // 站点部署域名,无需协议和path等 + defaultSearch: '', // 默认搜索引擎,baidu或者google + defaultLanguage: 'zh-cn', + 'en-us': { + pageMenu: [ + { + key: 'home', // 用作顶部菜单的选中 + text: 'HOME', + link: '/en-us/index.html', + }, + { + key: 'docs', + text: 'DOCS', + link: '/en-us/docs/demo1.html', + }, + { + key: 'blog', + text: 'BLOG', + link: '/en-us/blog/index.html', + }, + { + key: 'community', + text: 'COMMUNITY', + link: '/en-us/community/index.html', + }, + ], + disclaimer: { + title: 'Disclaimer', + content: 'the disclaimer content', + }, + documentation: { + title: 'Documentation', + list: [ + { + text: 'Overview', + link: '/en-us/docs/demo1.html', + }, + { + text: 'Quick start', + link: '/en-us/docs/demo2.html', + }, + { + text: 'Developer guide', + link: '/en-us/docs/dir/demo3.html', + }, + ], + }, + resources: { + title: 'Resources', + list: [ + { + text: 'Blog', + link: '/en-us/blog/index.html', + }, + { + text: 'Community', + link: '/en-us/community/index.html', + }, + ], + }, + copyright: 'Copyright © 2018 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx', + }, + 'zh-cn': { + pageMenu: [ + { + key: 'blog', + text: '登录', + link: 'http://localhost:8083/index.html#/login', + }, + { + key: 'community', + text: '免费注册', + link: 'http://localhost:8083/index.html#/isvReg', + }, + ], + disclaimer: { + title: 'XX', + content: 'XX', + }, + documentation: { + title: '文档', + list: [ + { + text: '概览', + link: '/zh-cn/docs/demo1.html', + }, + { + text: '快速开始', + link: '/zh-cn/docs/demo2.html', + }, + { + text: '开发者指南', + link: '/zh-cn/docs/dir/demo3.html', + }, + ], + }, + resources: { + title: '资源', + list: [ + { + text: '博客', + link: '/zh-cn/blog/index.html', + }, + { + text: '社区', + link: '/zh-cn/community/index.html', + }, + ], + }, + copyright: 'Copyright © 2018 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx', + }, +}; diff --git a/sop-website/sop-portal/src/components/bar/index.jsx b/sop-website/sop-portal/src/components/bar/index.jsx new file mode 100644 index 00000000..191e145e --- /dev/null +++ b/sop-website/sop-portal/src/components/bar/index.jsx @@ -0,0 +1,30 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import classnames from 'classnames'; +import { getLink } from '../../../utils'; +import './index.scss'; + +const propTypes = { + text: PropTypes.string.isRequired, // 显示的文案 + img: PropTypes.string.isRequired, // 显示的图片链接 +}; + +const Bar = (props) => { + const { text, img } = props; + const cls = classnames({ + bar: true, + }); + return ( +
+
+ + {text} + +
+
+ ); +}; + +Bar.propTypes = propTypes; + +export default Bar; diff --git a/sop-website/sop-portal/src/components/bar/index.scss b/sop-website/sop-portal/src/components/bar/index.scss new file mode 100644 index 00000000..ba890ed2 --- /dev/null +++ b/sop-website/sop-portal/src/components/bar/index.scss @@ -0,0 +1,59 @@ +@import '../../variables.scss'; + +.bar { + margin-top: $headerHeight; + background-image: linear-gradient(-90deg, $startColor 0%, $intermediateColor 51%, $endColor 100%); + .bar-body { + max-width: $contentWidth; + margin: 0 auto; + height: $barHeight; + line-height: $barHeight; + font-family: Avenir-Heavy; + font-size: 36px; + color: #FFF; + position: relative; + &::before { + content: ''; + height: 100%; + position: absolute; + left: 42px; + top: 0; + opacity: 0.3; + border-left: 1px solid #FFFFFF; + } + &::after { + content: ''; + height: 16px; + position: absolute; + left: 40px; + top: 50%; + margin: auto 0; + border-left: 4px solid #FFFFFF; + } + .front-img { + width: 80px; + height: 80px; + vertical-align: middle; + margin: 0 28px 0 70px; + } + .back-img { + width: 160px; + height: 160px; + position: absolute; + right: 168px; + bottom: 0; + opacity: 0.15; + } + @media screen and (max-width: $mobileWidth){ + &::before { + left: 22px; + } + &::after { + left: 20px; + } + .front-img { + margin-left: 50px; + } + } + } +} \ No newline at end of file diff --git a/sop-website/sop-portal/src/components/button/index.jsx b/sop-website/sop-portal/src/components/button/index.jsx new file mode 100644 index 00000000..49da2c04 --- /dev/null +++ b/sop-website/sop-portal/src/components/button/index.jsx @@ -0,0 +1,37 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import classnames from 'classnames'; +import { getLink } from '../../../utils'; +import './index.scss'; + +const propTypes = { + type: PropTypes.oneOf(['primary', 'normal']), + link: PropTypes.string, + target: PropTypes.string, +}; +const defaultProps = { + type: 'primary', + link: '', + target: '_self', +}; +const Button = (props) => { + return ( + + {props.children} + + ); +}; + +Button.propTypes = propTypes; +Button.defaultProps = defaultProps; + +export default Button; diff --git a/sop-website/sop-portal/src/components/button/index.scss b/sop-website/sop-portal/src/components/button/index.scss new file mode 100644 index 00000000..5c0d62cc --- /dev/null +++ b/sop-website/sop-portal/src/components/button/index.scss @@ -0,0 +1,20 @@ +.button { + box-sizing: border-box; + display: inline-block; + height: 48px; + line-height: 48px; + min-width: 140px; + font-family: Avenir-Heavy; + font-size: 16px; + color: #FFF; + text-align: center; + border-radius: 4px; + text-decoration: none; + &-primary { + background: #4190FF; + } + &-normal { + background: transparent; + border: 1px solid #fff; + } +} \ No newline at end of file diff --git a/sop-website/sop-portal/src/components/footer/index.jsx b/sop-website/sop-portal/src/components/footer/index.jsx new file mode 100644 index 00000000..ff560a51 --- /dev/null +++ b/sop-website/sop-portal/src/components/footer/index.jsx @@ -0,0 +1,60 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +// import siteConfig from '../../../site_config/site'; +// import { getLink } from '../../../utils'; +import './index.scss'; +import { getLink } from '../../../utils'; + +const propTypes = { + logo: PropTypes.string.isRequired, // logo地址 + language: PropTypes.oneOf(['zh-cn', 'en-us']), +}; + +const beianStyle = { + div: { + margin: '0 auto', + padding: '20px 0', + a: { + color: '#999', + paddingRight: '10px', + textDecoration: 'none', + height: '20px', + lineHeight: '20px', + img: { + float: 'left' + }, + p: { + float: 'left', + height: '20px', + lineHeight: '20px', + margin: '0px 0px 0px 5px', + color: '#939393' + } + } + } +} + +class Footer extends React.Component { + + render() { + // const { logo, language } = this.props; + // const dataSource = siteConfig[language]; + return ( + + ); + } +} + +Footer.propTypes = propTypes; + +export default Footer; diff --git a/sop-website/sop-portal/src/components/footer/index.scss b/sop-website/sop-portal/src/components/footer/index.scss new file mode 100644 index 00000000..2177fa36 --- /dev/null +++ b/sop-website/sop-portal/src/components/footer/index.scss @@ -0,0 +1,104 @@ +@import '../../variables.scss'; + +.footer-container { + background: #F8F8F8; + .footer-body { + max-width: $contentWidth; + margin: 0 auto; + box-sizing: border-box; + padding: 40px 40px 0; + @media screen and (max-width: $mobileWidth) { + padding-left: 20px; + padding-right: 20px; + } + //img { + // // display: block; + // width: 270px; + // margin-bottom: 28px; + // margin-right: 20px; + // vertical-align: middle; + //} + .cols-container { + margin-bottom: 60px; + .col { + display: inline-block; + box-sizing: border-box; + vertical-align: top; + } + .col-12 { + width: 50%; + padding-right: 125px; + } + .col-6 { + width: 25%; + } + h3 { + font-family: Avenir-Heavy; + font-size: 18px; + color: #333; + line-height: 18px; + margin-bottom: 20px; + } + p { + font-family: Avenir-Medium; + font-size: 12px; + color: #999; + line-height: 18px; + } + dl { + font-family: Avenir-Heavy; + line-height: 18px; + } + dt { + font-weight: bold; + font-size: 18px; + color: #333; + margin-bottom: 20px; + } + dd { + padding: 0; + margin: 0; + a { + text-decoration: none; + display: block; + font-size: 14px; + color: #999; + margin: 10px 0; + } + a:hover { + color: $brandColor; + } + } + } + .copyright { + border-top: 1px solid #ccc; + min-height: 60px; + line-height: 20px; + text-align: center; + font-family: Avenir-Medium; + font-size: 12px; + color: #999; + display: flex; + align-items: center; + span { + display: inline-block; + margin: 0 auto; + } + } + } +} + +@media screen and (max-width: $mobileWidth) { + .footer-container { + .footer-body { + .cols-container { + .col { + width: 100%; + text-align: center; + padding: 0; + } + } + } + } +} +.beian {color: #999;} diff --git a/sop-website/sop-portal/src/components/header/index.jsx b/sop-website/sop-portal/src/components/header/index.jsx new file mode 100644 index 00000000..a034a6e7 --- /dev/null +++ b/sop-website/sop-portal/src/components/header/index.jsx @@ -0,0 +1,190 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import classnames from 'classnames'; +import { autobind } from 'core-decorators'; +import siteConfig from '../../../site_config/site'; +import { getLink } from '../../../utils'; +import './index.scss'; + +const languageSwitch = [ + { + text: '中', + value: 'en-us', + }, + { + text: 'En', + value: 'zh-cn', + }, +]; +const searchSwitch = { + baidu: { + logo: 'https://img.alicdn.com/tfs/TB1n6DQayLaK1RjSZFxXXamPFXa-300-300.png', + url: 'https://www.baidu.com/s?wd=', + }, + google: { + logo: 'https://img.alicdn.com/tfs/TB1REfuaCzqK1RjSZFjXXblCFXa-300-300.jpg', + url: 'https://www.google.com/search?q=', + }, +}; +const noop = () => {}; +const propTypes = { + currentKey: PropTypes.string, + logo: PropTypes.string.isRequired, + type: PropTypes.oneOf(['primary', 'normal']), + language: PropTypes.oneOf(['en-us', 'zh-cn']), + onLanguageChange: PropTypes.func, +}; +const defaultProps = { + type: 'primary', + language: 'en-us', + onLanguageChange: noop, +}; + +@autobind +class Header extends React.Component { + constructor(props) { + super(props); + this.state = { + menuBodyVisible: false, + language: props.language, + search: siteConfig.defaultSearch, + searchValue: '', + inputVisible: false, + }; + } + + componentWillReceiveProps(nextProps) { + this.setState({ + language: nextProps.language, + }); + } + + toggleMenu() { + this.setState({ + menuBodyVisible: !this.state.menuBodyVisible, + }); + } + + switchLang() { + let language; + if (this.state.language === 'zh-cn') { + language = 'en-us'; + } else { + language = 'zh-cn'; + } + this.setState({ + language, + }); + this.props.onLanguageChange(language); + } + + switchSearch() { + let search; + if (this.state.search === 'baidu') { + search = 'google'; + } else { + search = 'baidu'; + } + this.setState({ + search, + }); + } + + toggleSearch() { + this.setState({ + searchVisible: !this.state.searchVisible, + }); + } + + onInputChange(e) { + this.setState({ + searchValue: e.target.value, + }); + } + + goSearch() { + const { search, searchValue } = this.state; + window.open(`${searchSwitch[search].url}${window.encodeURIComponent(`${searchValue} site:${siteConfig.domain}`)}`); + } + + onKeyDown(e) { + if (e.keyCode === 13) { + this.goSearch(); + } + } + + render() { + const { type, logo, onLanguageChange, currentKey } = this.props; + const { menuBodyVisible, language, search, searchVisible } = this.state; + return ( +
+
+ {siteConfig.name} + { + siteConfig.defaultSearch ? + ( +
+ + { + searchVisible ? + ( +
+ + +
+ ) : null + } +
+ ) : null + } + { + null + } +
+ +
    + {siteConfig[language].pageMenu.map(item => ( +
  • + {item.text} +
  • ))} +
+
+
+
+ ); + } +} + +Header.propTypes = propTypes; +Header.defaultProps = defaultProps; +export default Header; diff --git a/sop-website/sop-portal/src/components/header/index.scss b/sop-website/sop-portal/src/components/header/index.scss new file mode 100644 index 00000000..3c6cb9d5 --- /dev/null +++ b/sop-website/sop-portal/src/components/header/index.scss @@ -0,0 +1,249 @@ +@import '../../variables.scss'; + +.header-container { + position: fixed; + left: 0; + top: 0; + width: 100%; + z-index: 1000; + background-color: #fff; + &-primary { + background-color: transparent; + } + &-normal { + background-color: #fff; + box-shadow: 0 2px 10px 0 rgba(0,0,0,0.08); + } + .header-body { + max-width: $contentWidth; + margin: 0 auto; + height: $headerHeight; + line-height: $headerHeight; + .logo { + margin-left: 40px; + width: 180px; + vertical-align: sub; + } + .header-menu { + float: right; + .header-menu-toggle { + display: none; + width: 19px; + margin-right: 40px; + margin-top: $headerHeight / 2 - 15px; + cursor: pointer; + } + } + ul { + padding: 0; + margin: 0; + } + li { + display: inline-block; + margin-right: 40px; + } + .menu-item { + font-family: Avenir-Heavy; + font-size: 14px; + vertical-align: bottom; + } + .menu-item-primary { + a { + color: #fff; + opacity: 0.6; + font-family: Avenir-Medium; + } + &:hover { + a { + // font-family: Avenir-Heavy; + opacity: 1; + } + } + &-active { + a { + // font-family: Avenir-Heavy; + opacity: 1; + } + } + } + .menu-item-normal { + a { + color: #333; + opacity: 0.6; + font-family: Avenir-Medium; + } + &:hover { + a { + // font-family: Avenir-Heavy; + opacity: 1; + } + } + &-active { + a { + // font-family: Avenir-Heavy; + opacity: 1; + } + } + } + .language-switch { + float: right; + box-sizing: border-box; + width: 24px; + height: 24px; + line-height: 20px; + margin-top: $headerHeight / 2 - 12px; + margin-right: 40px; + text-align: center; + border-radius: 2px; + cursor: pointer; + font-family: PingFangSC-Medium; + font-size: 14px; + opacity: 0.6; + &:hover { + opacity: 1; + } + } + .language-switch-primary { + border: 1px solid #FFF; + color: #FFF; + } + .language-switch-normal { + border: 1px solid #333; + color: #333; + } + .search { + float: right; + width: 24px; + height: 24px; + margin-top: 21px; + margin-right: 40px; + line-height: normal; + position: relative; + .icon-search { + display: inline-block; + cursor: pointer; + width: 12px; + height: 12px; + border-radius: 50%; + border:2px solid; + position: relative; + &::before { + content: ''; + transform: rotate(45deg); + width:8px; + height: 2px; + position: absolute; + top:13px; + left:11px; + } + } + &-primary { + .icon-search { + border-color: #fff; + opacity: 0.6; + &::before { + background-color: #fff; + } + &:hover { + opacity: 1; + } + } + } + &-normal { + .icon-search { + border-color: #333; + opacity: 0.6; + &::before { + background-color: #333; + } + &:hover { + opacity: 1; + } + } + } + .search-input { + position: absolute; + left: -172px; + top: 28px; + background: #fff; + border-radius: 4px; + overflow: hidden; + box-shadow: 0 8px 16px 0 rgba(0, 0, 0, 0.05), 0 -8px 16px 0 rgba(0, 0, 0, 0.05); + img { + width: 28px; + height: 28px; + cursor: pointer; + vertical-align: middle; + } + input { + padding: 0 4px; + border: none; + outline: none; + width: 160px; + height: 24px; + vertical-align: middle; + } + } + } + } +} + +@media screen and (max-width: $mobileWidth) { + .header-container { + .header-body { + .logo { + margin-left: 20px; + width: 270px; + } + .language-switch { + margin-right: 20px; + } + .header-menu { + ul { + display: none; + } + .header-menu-toggle { + display: inline-block; + margin-right: 20px; + } + } + .header-menu-open { + ul { + background-color: $headerMenuBgColor; + display: inline-block; + position: absolute; + right: 0; + top: $headerHeight; + z-index: 100; + } + li { + width: 200px; + display: list-item; + padding-left: 30px; + list-style: none; + line-height: 40px; + margin-right: 0; + a { + color: #333; + display: inline-block; + width: 100%; + } + &:hover { + background: $endColor; + a { + color: #fff; + opacity: 1; + } + } + } + .menu-item-primary-active, .menu-item-normal-active { + background: $endColor; + a { + color: #fff; + opacity: 1; + } + } + } + } + } +} diff --git a/sop-website/sop-portal/src/components/language/index.jsx b/sop-website/sop-portal/src/components/language/index.jsx new file mode 100644 index 00000000..48053646 --- /dev/null +++ b/sop-website/sop-portal/src/components/language/index.jsx @@ -0,0 +1,37 @@ +import React from 'react'; +import { autobind } from 'core-decorators'; +import cookie from 'js-cookie'; +import siteConfig from '../../../site_config/site'; + +@autobind +class Language extends React.Component { + + onLanguageChange(language) { + const pathname = window.location.pathname; + let oldLang; + if (language === 'zh-cn') { + oldLang = 'en-us'; + } else { + oldLang = 'zh-cn'; + } + const newPathname = pathname.replace(`${window.rootPath}/${oldLang}`, `${window.rootPath}/${language}`); + cookie.set('docsite_language', language, { expires: 365}); + window.location = newPathname; + } + + getLanguage() { + const urlLang = window.location.pathname.replace(window.rootPath || '', '').split('/')[1]; + let language = this.props.lang || urlLang || cookie.get('docsite_language') || siteConfig.defaultLanguage; + // 防止链接被更改导致错误的cookie存储 + if (language !== 'en-us' && language !== 'zh-cn') { + language = siteConfig.defaultLanguage; + } + // 同步cookie语言版本 + if (language !== cookie.get('docsite_language')) { + cookie.set('docsite_language', language, { expires: 365 }); + } + return language; + } +} + +export default Language; diff --git a/sop-website/sop-portal/src/components/pageSlider/index.jsx b/sop-website/sop-portal/src/components/pageSlider/index.jsx new file mode 100644 index 00000000..c09a506c --- /dev/null +++ b/sop-website/sop-portal/src/components/pageSlider/index.jsx @@ -0,0 +1,142 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { autobind } from 'core-decorators'; +import classnames from 'classnames'; +import { throttle, getLink } from '../../../utils'; + +import './index.scss'; + +const propTypes = { + pageSize: PropTypes.number, // 每页最多显示的条数 + +}; +const defaultProps = { + pageSize: 5, +}; + +@autobind +class pageSlider extends React.Component { + constructor(props) { + super(props); + this.container = null; + this.state = { + page: 0, + pageWidth: 0, + }; + } + + componentDidMount() { + const pageWidth = this.container.getBoundingClientRect().width; + /* eslint-disable react/no-did-mount-set-state */ + this.setState({ + pageWidth, + }); + this.throttleAdjust = throttle(() => { + this.setState({ + pageWidth: this.container.getBoundingClientRect().width, + }); + }, 200); + window.addEventListener('resize', this.throttleAdjust); + } + + componentWillUnmount() { + window.removeEventListener('resize', this.throttleAdjust); + } + + changePage(i) { + this.setState({ + page: i, + }); + } + + renderSliderList() { + const { children, pageSize } = this.props; + const { page, pageWidth } = this.state; + const splitGroup = []; + const len = React.Children.count(children); + // 分成的屏数 + const splitNum = Math.ceil(len / pageSize); + /* eslint-disable no-plusplus*/ + for (let i = 0; i < splitNum; i++) { + splitGroup.push(Array.from(children).slice(i * pageSize, (i + 1) * pageSize)); + } + return ( +
+ {splitGroup.map((group, i) => { + return ( +
+ { + group.map((child, j) => ( +
+ {child} +
+ ) + ) + } +
+ ); + })} +
+ ); + } + + renderControl() { + const { children, pageSize } = this.props; + const { page } = this.state; + const len = React.Children.count(children); + // 分成的屏数 + const splitNum = Math.ceil(len / pageSize); + return ( +
+ + +
+ ); + } + + render() { + return ( +
{ this.container = node; }}> + {this.renderSliderList()} + {this.renderControl()} +
+ ); + } +} + +pageSlider.propTypes = propTypes; +pageSlider.defaultProps = defaultProps; + +export default pageSlider; diff --git a/sop-website/sop-portal/src/components/pageSlider/index.scss b/sop-website/sop-portal/src/components/pageSlider/index.scss new file mode 100644 index 00000000..07597412 --- /dev/null +++ b/sop-website/sop-portal/src/components/pageSlider/index.scss @@ -0,0 +1,34 @@ +@import '../../variables.scss'; + +.page-slider { + overflow: hidden; + .slider-list { + overflow: visible; + .slider-page { + overflow: hidden; + display: inline-block; + vertical-align: top; + } + } + .slider-control { + overflow: hidden; + img { + display: inline-block; + width: 52px; + height: 52px; + cursor: pointer; + } + .slider-control-prev { + float: left; + &-hidden { + display: none; + } + } + .slider-control-next { + float: right; + &-hidden { + display: none; + } + } + } +} \ No newline at end of file diff --git a/sop-website/sop-portal/src/components/sidemenu/index.jsx b/sop-website/sop-portal/src/components/sidemenu/index.jsx new file mode 100644 index 00000000..fc58d232 --- /dev/null +++ b/sop-website/sop-portal/src/components/sidemenu/index.jsx @@ -0,0 +1,59 @@ +import React from 'react'; +import classnames from 'classnames'; +import { autobind } from 'core-decorators'; +import Item from './item.jsx'; +import './index.scss'; + +@autobind +class SideMenu extends React.Component { + constructor(props) { + super(props); + this.state = { + menuBodyVisible: false, + }; + } + + toggleMenuBody() { + this.setState({ + menuBodyVisible: !this.state.menuBodyVisible, + }); + } + + render() { + const { dataSource } = this.props; + const { menuBodyVisible } = this.state; + const cls = classnames({ + sidemenu: true, + 'sidemenu-open': menuBodyVisible, + }); + const itemCls = classnames({ + 'menu-item': true, + 'menu-item-level-1': true, + }); + return ( +
+
+ +
+
    + { + dataSource.map((data, i) => { + return ( +
  • + + {data.title} + +
      + {data.children.map((item, j) => )} +
    +
  • + ); + }) + } +
+
+ ); + } +} + +export default SideMenu; diff --git a/sop-website/sop-portal/src/components/sidemenu/index.scss b/sop-website/sop-portal/src/components/sidemenu/index.scss new file mode 100644 index 00000000..44051ffd --- /dev/null +++ b/sop-website/sop-portal/src/components/sidemenu/index.scss @@ -0,0 +1,121 @@ +@import '../../variables.scss'; +.sidemenu { + background: $sideMenuBgColor; + width: $sideMenuWidth; + position: relative; + display: inline-block; + padding: 20px 0; + .sidemenu-toggle { + text-align: center; + cursor: pointer; + position: absolute; + top: 0; + width: 40px; + right: -40px; + height: 30px; + line-height: 30px; + background: $sideMenuBgColor; + border-radius: 0 4px 4px 0; + display: none; + img { + width: 16px; + text-align: center; + vertical-align: middle; + } + } + ul { + list-style: none; + padding: 0; + margin: 0; + } + li { + line-height: 0; + } + span, a { + box-sizing: border-box; + display: inline-block; + position: relative; + width: 100%; + overflow-x: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + .menu-item-selected { + a { + background: white; + } + a::before { + content: ''; + position: absolute; + left: 0; + top: 0; + width: 4px; + height: 100%; + background-image:linear-gradient(0deg, $startColor 0%, $intermediateColor 51%, $endColor 100%); + } + } + .menu-item-level-1 { + & > span { + font-family: Avenir-Heavy; + font-size: 18px; + color: #333; + padding-left: 20px; + height: 40px; + line-height: 40px; + } + } + + .menu-item-level-2, .menu-item-level-3 { + cursor: pointer; + & > span, & > a { + font-family: Avenir-Medium; + font-size: 14px; + color: #666; + &:hover { + background: white; + &::before { + content: ''; + position: absolute; + left: 0; + top: 0; + width: 4px; + height: 100%; + background-image:linear-gradient(0deg, $startColor 0%, $intermediateColor 51%, $endColor 100%); + } + } + } + } + + .menu-item-level-2 { + & > span, & > a { + padding-left: 40px; + height: 36px; + line-height: 36px; + img.menu-toggle { + float: right; + width: 13px; + height: 8px; + margin: 14px 20px 14px 0; + } + } + } + .menu-item-level-3 { + & > a { + padding-left: 60px; + height: 36px; + line-height: 36px; + } + } +} + +@media screen and (max-width: $mobileWidth) { + .sidemenu { + width: 0; + .sidemenu-toggle { + display: inline-block; + } + &.sidemenu-open { + width: $sideMenuWidth; + } + } +} \ No newline at end of file diff --git a/sop-website/sop-portal/src/components/sidemenu/item.jsx b/sop-website/sop-portal/src/components/sidemenu/item.jsx new file mode 100644 index 00000000..75204df6 --- /dev/null +++ b/sop-website/sop-portal/src/components/sidemenu/item.jsx @@ -0,0 +1,93 @@ +import React from 'react'; +import { autobind } from 'core-decorators'; +import classnames from 'classnames'; +import { getLink } from '../../../utils'; + +@autobind +class Item extends React.Component { + constructor(props) { + super(props); + const { item } = props; + const hasChildren = item.children && item.children.length; + let opened = props.item.opened; + if (hasChildren) { + if (opened === undefined) { + // 未配置展开,则是否展开由是否选中决定 + opened = item.children.find(child => getLink(child.link) === window.location.pathname); + } + } else { + opened = false; + } + this.state = { + opened, + }; + } + + onItemClick(e) { + this.props.toggleMenuBody(); + e.stopPropagation(); + } + + toggle() { + this.setState({ + opened: !this.state.opened, + }); + } + + renderSubMenu(data) { + return ( +
    + { + data.map((item, index) => ( +
  • + {item.title} +
  • + )) + } +
+ ); + } + + render() { + const { item } = this.props; + const hasChildren = item.children && item.children.length; + const { opened } = this.state; + const cls = classnames({ + 'menu-item': true, + 'menu-item-level-2': true, + 'menu-item-selected': getLink(item.link) === window.location.pathname, + }); + const style = { + height: opened ? 36 * (item.children.length + 1) : 36, + overflow: 'hidden', + }; + if (hasChildren) { + return ( +
  • + { + + {item.title} + + + } + {this.renderSubMenu(item.children)} +
  • + ); + } + return ( +
  • + {item.title} +
  • + ); + } +} + +export default Item; diff --git a/sop-website/sop-portal/src/components/slider/index.jsx b/sop-website/sop-portal/src/components/slider/index.jsx new file mode 100644 index 00000000..fb742f18 --- /dev/null +++ b/sop-website/sop-portal/src/components/slider/index.jsx @@ -0,0 +1,163 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; +import { autobind } from 'core-decorators'; +import classnames from 'classnames'; +import { throttle } from '../../../utils'; + +import './index.scss'; + +@autobind +class Slider extends React.Component { + constructor(props) { + super(props); + this.container = null; + this.state = { + screenIndex: 0, + visibleNum: 1, + }; + } + + componentDidMount() { + this.throttleAdjust = throttle(() => { + this.setState({ + visibleNum: this.getVisibleNum(), + }); + }, 200); + window.addEventListener('resize', this.throttleAdjust); + // 做重新布局 + /* eslint-disable react/no-did-mount-set-state */ + this.setState({ + visibleNum: this.getVisibleNum(), + }); + } + + componentWillUnmount() { + window.removeEventListener('resize', this.throttleAdjust); + } + + getVisibleNum() { + // 比较粗略的计算,假定一屏的子节点外边距之和不会超过一个子节点的宽度 + /* eslint-disable react/no-find-dom-node */ + let result = 1; + const containerWidth = this.container.getBoundingClientRect().width; + const childWidth = this.sliderItemChild0.getBoundingClientRect ? this.sliderItemChild0.getBoundingClientRect().width : ReactDOM.findDOMNode(this.sliderItemChild0).getBoundingClientRect().width; + if (containerWidth && childWidth) { + result = Math.floor(containerWidth / childWidth); + } + // 有可能result为0,下面的除法就会出现分母为0,从而导致得出Inifity,无限循环导致浏览器崩溃 + return result || 1; + } + + getListWidth() { + let width = 0; + const { children } = this.props; + const { visibleNum } = this.state; + const len = React.Children.count(children); + // 最终分成的屏数 + const splitNum = Math.ceil(len / visibleNum); + if (this.container) { + const containerWidth = this.container.getBoundingClientRect().width; + width = containerWidth * splitNum; + } + return width; + } + + changeScreen(i) { + const { screenIndex } = this.state; + // const total = React.Children.count(this.props.children); + if (i !== screenIndex) { + this.setState({ + screenIndex: i + }); + } + } + + renderSliderList() { + const { children } = this.props; + const { screenIndex, visibleNum } = this.state; + const splitGroup = []; + const len = React.Children.count(children); + // 分成的屏数 + const splitNum = Math.ceil(len / visibleNum); + /* eslint-disable no-plusplus*/ + for (let i = 0; i < splitNum; i++) { + splitGroup.push(Array.from(children).slice(i * visibleNum, (i + 1) * visibleNum)); + } + return ( +
    + {splitGroup.map((group, i) => { + return ( +
    { this[`sliderScreen${i}`] = node; }} + > + { + group.map((child, j) => ( +
    + {React.cloneElement(child, { + ref: (node) => { + this[`sliderItemChild${(i * visibleNum) + j}`] = node; + } + })} +
    + ) + ) + } +
    + ); + })} +
    + ); + } + + renderControl() { + const { children } = this.props; + const { screenIndex, visibleNum } = this.state; + const len = React.Children.count(children); + // 分成的屏数 + const splitNum = Math.ceil(len / visibleNum); + const temp = []; + for (let i = 0; i < splitNum; i++) { + temp.push(( + + )); + } + return ( +
    + {temp} +
    + ); + } + + render() { + return ( +
    { this.container = node; }}> + {this.renderSliderList()} + {this.renderControl()} +
    + ); + } +} + +export default Slider; diff --git a/sop-website/sop-portal/src/components/slider/index.scss b/sop-website/sop-portal/src/components/slider/index.scss new file mode 100644 index 00000000..f4bd9353 --- /dev/null +++ b/sop-website/sop-portal/src/components/slider/index.scss @@ -0,0 +1,30 @@ +@import '../../variables.scss'; + +.slider { + overflow: hidden; + .slider-list { + overflow: visible; + .slider-screen { + display: inline-flex; + justify-content: space-around; + overflow: hidden; + } + } + .slider-control { + text-align: center; + margin-top: 20px; + .slider-control-item { + cursor: pointer; + display: inline-block; + width: 20px; + height: 4px; + margin-right: 4px; + background: #ccc; + &-active { + width: 40px; + height: 6px; + background-image: linear-gradient(-90deg, $startColor 0%, $intermediateColor 51%, $endColor 100%); + } + } + } +} \ No newline at end of file diff --git a/sop-website/sop-portal/src/markdown.scss b/sop-website/sop-portal/src/markdown.scss new file mode 100644 index 00000000..fd30a16e --- /dev/null +++ b/sop-website/sop-portal/src/markdown.scss @@ -0,0 +1,1555 @@ +@font-face { + font-family: octicons-link; + src: url(data:font/woff;charset=utf-8;base64,d09GRgABAAAAAAZwABAAAAAACFQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABEU0lHAAAGaAAAAAgAAAAIAAAAAUdTVUIAAAZcAAAACgAAAAoAAQAAT1MvMgAAAyQAAABJAAAAYFYEU3RjbWFwAAADcAAAAEUAAACAAJThvmN2dCAAAATkAAAABAAAAAQAAAAAZnBnbQAAA7gAAACyAAABCUM+8IhnYXNwAAAGTAAAABAAAAAQABoAI2dseWYAAAFsAAABPAAAAZwcEq9taGVhZAAAAsgAAAA0AAAANgh4a91oaGVhAAADCAAAABoAAAAkCA8DRGhtdHgAAAL8AAAADAAAAAwGAACfbG9jYQAAAsAAAAAIAAAACABiATBtYXhwAAACqAAAABgAAAAgAA8ASm5hbWUAAAToAAABQgAAAlXu73sOcG9zdAAABiwAAAAeAAAAME3QpOBwcmVwAAAEbAAAAHYAAAB/aFGpk3jaTY6xa8JAGMW/O62BDi0tJLYQincXEypYIiGJjSgHniQ6umTsUEyLm5BV6NDBP8Tpts6F0v+k/0an2i+itHDw3v2+9+DBKTzsJNnWJNTgHEy4BgG3EMI9DCEDOGEXzDADU5hBKMIgNPZqoD3SilVaXZCER3/I7AtxEJLtzzuZfI+VVkprxTlXShWKb3TBecG11rwoNlmmn1P2WYcJczl32etSpKnziC7lQyWe1smVPy/Lt7Kc+0vWY/gAgIIEqAN9we0pwKXreiMasxvabDQMM4riO+qxM2ogwDGOZTXxwxDiycQIcoYFBLj5K3EIaSctAq2kTYiw+ymhce7vwM9jSqO8JyVd5RH9gyTt2+J/yUmYlIR0s04n6+7Vm1ozezUeLEaUjhaDSuXHwVRgvLJn1tQ7xiuVv/ocTRF42mNgZGBgYGbwZOBiAAFGJBIMAAizAFoAAABiAGIAznjaY2BkYGAA4in8zwXi+W2+MjCzMIDApSwvXzC97Z4Ig8N/BxYGZgcgl52BCSQKAA3jCV8CAABfAAAAAAQAAEB42mNgZGBg4f3vACQZQABIMjKgAmYAKEgBXgAAeNpjYGY6wTiBgZWBg2kmUxoDA4MPhGZMYzBi1AHygVLYQUCaawqDA4PChxhmh/8ODDEsvAwHgMKMIDnGL0x7gJQCAwMAJd4MFwAAAHjaY2BgYGaA4DAGRgYQkAHyGMF8NgYrIM3JIAGVYYDT+AEjAwuDFpBmA9KMDEwMCh9i/v8H8sH0/4dQc1iAmAkALaUKLgAAAHjaTY9LDsIgEIbtgqHUPpDi3gPoBVyRTmTddOmqTXThEXqrob2gQ1FjwpDvfwCBdmdXC5AVKFu3e5MfNFJ29KTQT48Ob9/lqYwOGZxeUelN2U2R6+cArgtCJpauW7UQBqnFkUsjAY/kOU1cP+DAgvxwn1chZDwUbd6CFimGXwzwF6tPbFIcjEl+vvmM/byA48e6tWrKArm4ZJlCbdsrxksL1AwWn/yBSJKpYbq8AXaaTb8AAHja28jAwOC00ZrBeQNDQOWO//sdBBgYGRiYWYAEELEwMTE4uzo5Zzo5b2BxdnFOcALxNjA6b2ByTswC8jYwg0VlNuoCTWAMqNzMzsoK1rEhNqByEyerg5PMJlYuVueETKcd/89uBpnpvIEVomeHLoMsAAe1Id4AAAAAAAB42oWQT07CQBTGv0JBhagk7HQzKxca2sJCE1hDt4QF+9JOS0nbaaYDCQfwCJ7Au3AHj+LO13FMmm6cl7785vven0kBjHCBhfpYuNa5Ph1c0e2Xu3jEvWG7UdPDLZ4N92nOm+EBXuAbHmIMSRMs+4aUEd4Nd3CHD8NdvOLTsA2GL8M9PODbcL+hD7C1xoaHeLJSEao0FEW14ckxC+TU8TxvsY6X0eLPmRhry2WVioLpkrbp84LLQPGI7c6sOiUzpWIWS5GzlSgUzzLBSikOPFTOXqly7rqx0Z1Q5BAIoZBSFihQYQOOBEdkCOgXTOHA07HAGjGWiIjaPZNW13/+lm6S9FT7rLHFJ6fQbkATOG1j2OFMucKJJsxIVfQORl+9Jyda6Sl1dUYhSCm1dyClfoeDve4qMYdLEbfqHf3O/AdDumsjAAB42mNgYoAAZQYjBmyAGYQZmdhL8zLdDEydARfoAqIAAAABAAMABwAKABMAB///AA8AAQAAAAAAAAAAAAAAAAABAAAAAA==) format('woff'); +} + +.markdown-body { + -ms-text-size-adjust: 100%; + -webkit-text-size-adjust: 100%; + line-height: 1.5; + color: #24292e; + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; + font-size: 16px; + line-height: 1.5; + word-wrap: break-word; +} + +.markdown-body .pl-c { + color: #6a737d; +} + +.markdown-body .pl-c1, +.markdown-body .pl-s .pl-v { + color: #005cc5; +} + +.markdown-body .pl-e, +.markdown-body .pl-en { + color: #6f42c1; +} + +.markdown-body .pl-smi, +.markdown-body .pl-s .pl-s1 { + color: #24292e; +} + +.markdown-body .pl-ent { + color: #22863a; +} + +.markdown-body .pl-k { + color: #d73a49; +} + +.markdown-body .pl-s, +.markdown-body .pl-pds, +.markdown-body .pl-s .pl-pse .pl-s1, +.markdown-body .pl-sr, +.markdown-body .pl-sr .pl-cce, +.markdown-body .pl-sr .pl-sre, +.markdown-body .pl-sr .pl-sra { + color: #032f62; +} + +.markdown-body .pl-v, +.markdown-body .pl-smw { + color: #e36209; +} + +.markdown-body .pl-bu { + color: #b31d28; +} + +.markdown-body .pl-ii { + color: #fafbfc; + background-color: #b31d28; +} + +.markdown-body .pl-c2 { + color: #fafbfc; + background-color: #d73a49; +} + +.markdown-body .pl-c2::before { + content: "^M"; +} + +.markdown-body .pl-sr .pl-cce { + font-weight: bold; + color: #22863a; +} + +.markdown-body .pl-ml { + color: #735c0f; +} + +.markdown-body .pl-mh, +.markdown-body .pl-mh .pl-en, +.markdown-body .pl-ms { + font-weight: bold; + color: #005cc5; +} + +.markdown-body .pl-mi { + font-style: italic; + color: #24292e; +} + +.markdown-body .pl-mb { + font-weight: bold; + color: #24292e; +} + +.markdown-body .pl-md { + color: #b31d28; + background-color: #ffeef0; +} + +.markdown-body .pl-mi1 { + color: #22863a; + background-color: #f0fff4; +} + +.markdown-body .pl-mc { + color: #e36209; + background-color: #ffebda; +} + +.markdown-body .pl-mi2 { + color: #f6f8fa; + background-color: #005cc5; +} + +.markdown-body .pl-mdr { + font-weight: bold; + color: #6f42c1; +} + +.markdown-body .pl-ba { + color: #586069; +} + +.markdown-body .pl-sg { + color: #959da5; +} + +.markdown-body .pl-corl { + text-decoration: underline; + color: #032f62; +} + +.markdown-body .octicon { + display: inline-block; + vertical-align: text-top; + fill: currentColor; +} + +.markdown-body a { + background-color: transparent; +} + +.markdown-body a:active, +.markdown-body a:hover { + outline-width: 0; +} + +.markdown-body strong { + font-weight: inherit; +} + +.markdown-body strong { + font-weight: bolder; +} + +.markdown-body h1 { + font-size: 2em; + margin: 0.67em 0; +} + +.markdown-body img { + border-style: none; +} + +.markdown-body code, +.markdown-body kbd, +.markdown-body pre { + font-family: monospace, monospace; + font-size: 1em; +} + +.markdown-body hr { + box-sizing: content-box; + height: 0; + overflow: visible; +} + +.markdown-body input { + font: inherit; + margin: 0; +} + +.markdown-body input { + overflow: visible; +} + +.markdown-body [type="checkbox"] { + box-sizing: border-box; + padding: 0; +} + +.markdown-body * { + box-sizing: border-box; +} + +.markdown-body input { + font-family: inherit; + font-size: inherit; + line-height: inherit; +} + +.markdown-body a { + color: #0366d6; + text-decoration: none; +} + +.markdown-body a:hover { + color: #0366d6; + text-decoration: underline; +} + +.markdown-body strong { + font-weight: 600; +} + +.markdown-body hr { + height: 0; + margin: 15px 0; + overflow: hidden; + background: transparent; + border: 0; + border-bottom: 1px solid #dfe2e5; +} + +.markdown-body hr::before { + display: table; + content: ""; +} + +.markdown-body hr::after { + display: table; + clear: both; + content: ""; +} + +.markdown-body table { + border-spacing: 0; + border-collapse: collapse; +} + +.markdown-body td, +.markdown-body th { + padding: 0; +} + +.markdown-body h1, +.markdown-body h2, +.markdown-body h3, +.markdown-body h4, +.markdown-body h5, +.markdown-body h6 { + margin-top: 0; + margin-bottom: 0; +} + +.markdown-body h1 { + font-size: 32px; + font-weight: 600; +} + +.markdown-body h2 { + font-size: 24px; + font-weight: 600; +} + +.markdown-body h3 { + font-size: 20px; + font-weight: 600; +} + +.markdown-body h4 { + font-size: 16px; + font-weight: 600; +} + +.markdown-body h5 { + font-size: 14px; + font-weight: 600; +} + +.markdown-body h6 { + font-size: 12px; + font-weight: 600; +} + +.markdown-body p { + margin-top: 0; + margin-bottom: 10px; +} + +.markdown-body blockquote { + margin: 0; +} + +.markdown-body ul, +.markdown-body ol { + padding-left: 0; + margin-top: 0; + margin-bottom: 0; +} + +.markdown-body ol ol, +.markdown-body ul ol { + list-style-type: lower-roman; +} + +.markdown-body ul ul ol, +.markdown-body ul ol ol, +.markdown-body ol ul ol, +.markdown-body ol ol ol { + list-style-type: lower-alpha; +} + +.markdown-body dd { + margin-left: 0; +} + +.markdown-body code { + font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, Courier, monospace; + font-size: 12px; +} + +.markdown-body pre { + margin-top: 0; + margin-bottom: 0; + font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, Courier, monospace; + font-size: 12px; +} + +.markdown-body .octicon { + vertical-align: text-bottom; +} + +.markdown-body .pl-0 { + padding-left: 0 !important; +} + +.markdown-body .pl-1 { + padding-left: 4px !important; +} + +.markdown-body .pl-2 { + padding-left: 8px !important; +} + +.markdown-body .pl-3 { + padding-left: 16px !important; +} + +.markdown-body .pl-4 { + padding-left: 24px !important; +} + +.markdown-body .pl-5 { + padding-left: 32px !important; +} + +.markdown-body .pl-6 { + padding-left: 40px !important; +} + +.markdown-body::before { + display: table; + content: ""; +} + +.markdown-body::after { + display: table; + clear: both; + content: ""; +} + +.markdown-body>*:first-child { + margin-top: 0 !important; +} + +.markdown-body>*:last-child { + margin-bottom: 0 !important; +} + +.markdown-body a:not([href]) { + color: inherit; + text-decoration: none; +} + +.markdown-body .anchor { + float: left; + padding-right: 4px; + margin-left: -20px; + line-height: 1; +} + +.markdown-body .anchor:focus { + outline: none; +} + +.markdown-body p, +.markdown-body blockquote, +.markdown-body ul, +.markdown-body ol, +.markdown-body dl, +.markdown-body table, +.markdown-body pre { + margin-top: 0; + margin-bottom: 16px; +} + +.markdown-body hr { + height: 0.25em; + padding: 0; + margin: 24px 0; + background-color: #e1e4e8; + border: 0; +} + +.markdown-body blockquote { + padding: 0 1em; + color: #6a737d; + border-left: 0.25em solid #dfe2e5; +} + +.markdown-body blockquote>:first-child { + margin-top: 0; +} + +.markdown-body blockquote>:last-child { + margin-bottom: 0; +} + +.markdown-body kbd { + display: inline-block; + padding: 3px 5px; + font-size: 11px; + line-height: 10px; + color: #444d56; + vertical-align: middle; + background-color: #fafbfc; + border: solid 1px #c6cbd1; + border-bottom-color: #959da5; + border-radius: 3px; + box-shadow: inset 0 -1px 0 #959da5; +} + +.markdown-body h1, +.markdown-body h2, +.markdown-body h3, +.markdown-body h4, +.markdown-body h5, +.markdown-body h6 { + margin-top: 24px; + margin-bottom: 16px; + font-weight: 600; + line-height: 1.25; +} + +.markdown-body h1 .octicon-link, +.markdown-body h2 .octicon-link, +.markdown-body h3 .octicon-link, +.markdown-body h4 .octicon-link, +.markdown-body h5 .octicon-link, +.markdown-body h6 .octicon-link { + color: #1b1f23; + vertical-align: middle; + visibility: hidden; +} + +.markdown-body h1:hover .anchor, +.markdown-body h2:hover .anchor, +.markdown-body h3:hover .anchor, +.markdown-body h4:hover .anchor, +.markdown-body h5:hover .anchor, +.markdown-body h6:hover .anchor { + text-decoration: none; +} + +.markdown-body h1:hover .anchor .octicon-link, +.markdown-body h2:hover .anchor .octicon-link, +.markdown-body h3:hover .anchor .octicon-link, +.markdown-body h4:hover .anchor .octicon-link, +.markdown-body h5:hover .anchor .octicon-link, +.markdown-body h6:hover .anchor .octicon-link { + visibility: visible; +} + +.markdown-body h1 { + padding-bottom: 0.3em; + font-size: 2em; + border-bottom: 1px solid #eaecef; +} + +.markdown-body h2 { + padding-bottom: 0.3em; + font-size: 1.5em; + border-bottom: 1px solid #eaecef; +} + +.markdown-body h3 { + font-size: 1.25em; +} + +.markdown-body h4 { + font-size: 1em; +} + +.markdown-body h5 { + font-size: 0.875em; +} + +.markdown-body h6 { + font-size: 0.85em; + color: #6a737d; +} + +.markdown-body ul, +.markdown-body ol { + padding-left: 2em; +} + +.markdown-body ul ul, +.markdown-body ul ol, +.markdown-body ol ol, +.markdown-body ol ul { + margin-top: 0; + margin-bottom: 0; +} + +.markdown-body li { + word-wrap: break-all; +} + +.markdown-body li>p { + margin-top: 16px; +} + +.markdown-body li+li { + margin-top: 0.25em; +} + +.markdown-body dl { + padding: 0; +} + +.markdown-body dl dt { + padding: 0; + margin-top: 16px; + font-size: 1em; + font-style: italic; + font-weight: 600; +} + +.markdown-body dl dd { + padding: 0 16px; + margin-bottom: 16px; +} + +.markdown-body table { + display: block; + width: 100%; + overflow: auto; +} + +.markdown-body table th { + font-weight: 600; +} + +.markdown-body table th, +.markdown-body table td { + padding: 6px 13px; + border: 1px solid #dfe2e5; +} + +.markdown-body table tr { + background-color: #fff; + border-top: 1px solid #c6cbd1; +} + +.markdown-body table tr:nth-child(2n) { + background-color: #f6f8fa; +} + +.markdown-body img { + max-width: 100%; + box-sizing: content-box; + background-color: #fff; +} + +.markdown-body img[align=right] { + padding-left: 20px; +} + +.markdown-body img[align=left] { + padding-right: 20px; +} + +.markdown-body code { + padding: 0.2em 0.4em; + margin: 0; + font-size: 85%; + background-color: rgba(27,31,35,0.05); + border-radius: 3px; +} + +.markdown-body pre { + word-wrap: normal; +} + +.markdown-body pre>code { + padding: 0; + margin: 0; + font-size: 100%; + word-break: normal; + white-space: pre; + background: transparent; + border: 0; +} + +.markdown-body .highlight { + margin-bottom: 16px; +} + +.markdown-body .highlight pre { + margin-bottom: 0; + word-break: normal; +} + +.markdown-body .highlight pre, +.markdown-body pre { + padding: 16px; + overflow: auto; + font-size: 85%; + line-height: 1.45; + background-color: #f6f8fa; + border-radius: 3px; +} + +.markdown-body pre code { + display: inline; + max-width: auto; + padding: 0; + margin: 0; + overflow: visible; + line-height: inherit; + word-wrap: normal; + background-color: transparent; + border: 0; +} + +.markdown-body .full-commit .btn-outline:not(:disabled):hover { + color: #005cc5; + border-color: #005cc5; +} + +.markdown-body kbd { + display: inline-block; + padding: 3px 5px; + font: 11px "SFMono-Regular", Consolas, "Liberation Mono", Menlo, Courier, monospace; + line-height: 10px; + color: #444d56; + vertical-align: middle; + background-color: #fafbfc; + border: solid 1px #d1d5da; + border-bottom-color: #c6cbd1; + border-radius: 3px; + box-shadow: inset 0 -1px 0 #c6cbd1; +} + +.markdown-body :checked+.radio-label { + position: relative; + z-index: 1; + border-color: #0366d6; +} + +.markdown-body .task-list-item { + list-style-type: none; +} + +.markdown-body .task-list-item+.task-list-item { + margin-top: 3px; +} + +.markdown-body .task-list-item input { + margin: 0 0.2em 0.25em -1.6em; + vertical-align: middle; +} + +.markdown-body hr { + border-bottom-color: #eee; +} + + /* 代码高亮 */ + + .markdown-body pre code { + display: block; + overflow-x: auto; + padding: 0.5em; + background: #f6f8fa; + color: #24292e; +} + +/* + +github.com style (c) Vasily Polovnyov + +*/ + +.hljs { + display: block; + overflow-x: auto; + padding: 0.5em; + color: #333; + background: #f8f8f8; +} + +.hljs-comment, +.hljs-quote { + color: #998; + font-style: italic; +} + +.hljs-keyword, +.hljs-selector-tag, +.hljs-subst { + color: #333; + font-weight: bold; +} + +.hljs-number, +.hljs-literal, +.hljs-variable, +.hljs-template-variable, +.hljs-tag .hljs-attr { + color: #008080; +} + +.hljs-string, +.hljs-doctag { + color: #d14; +} + +.hljs-title, +.hljs-section, +.hljs-selector-id { + color: #900; + font-weight: bold; +} + +.hljs-subst { + font-weight: normal; +} + +.hljs-type, +.hljs-class .hljs-title { + color: #458; + font-weight: bold; +} + +.hljs-tag, +.hljs-name, +.hljs-attribute { + color: #000080; + font-weight: normal; +} + +.hljs-regexp, +.hljs-link { + color: #009926; +} + +.hljs-symbol, +.hljs-bullet { + color: #990073; +} + +.hljs-built_in, +.hljs-builtin-name { + color: #0086b3; +} + +.hljs-meta { + color: #999; + font-weight: bold; +} + +.hljs-deletion { + background: #fdd; +} + +.hljs-addition { + background: #dfd; +} + +.hljs-emphasis { + font-style: italic; +} + +.hljs-strong { + font-weight: bold; +} + + +// katex 样式 +$katexPath: 'https://cdnjs.cloudflare.com/ajax/libs/KaTeX/0.5.1/'; +@font-face { + font-family:KaTeX_AMS; + src:url(#{$katexPath}fonts/KaTeX_AMS-Regular.eot); + src:url(#{$katexPath}fonts/KaTeX_AMS-Regular.eot#iefix) format('embedded-opentype'), url(#{$katexPath}fonts/KaTeX_AMS-Regular.woff2) format('woff2'), url(#{$katexPath}fonts/KaTeX_AMS-Regular.woff) format('woff'), url(#{$katexPath}fonts/KaTeX_AMS-Regular.ttf) format('ttf'); + font-weight:400; + font-style:normal +} +@font-face { + font-family:KaTeX_Caligraphic; + src:url(#{$katexPath}fonts/KaTeX_Caligraphic-Bold.eot); + src:url(#{$katexPath}fonts/KaTeX_Caligraphic-Bold.eot#iefix) format('embedded-opentype'), url(#{$katexPath}fonts/KaTeX_Caligraphic-Bold.woff2) format('woff2'), url(#{$katexPath}fonts/KaTeX_Caligraphic-Bold.woff) format('woff'), url(#{$katexPath}fonts/KaTeX_Caligraphic-Bold.ttf) format('ttf'); + font-weight:700; + font-style:normal +} +@font-face { + font-family:KaTeX_Caligraphic; + src:url(#{$katexPath}fonts/KaTeX_Caligraphic-Regular.eot); + src:url(#{$katexPath}fonts/KaTeX_Caligraphic-Regular.eot#iefix) format('embedded-opentype'), url(#{$katexPath}fonts/KaTeX_Caligraphic-Regular.woff2) format('woff2'), url(#{$katexPath}fonts/KaTeX_Caligraphic-Regular.woff) format('woff'), url(#{$katexPath}fonts/KaTeX_Caligraphic-Regular.ttf) format('ttf'); + font-weight:400; + font-style:normal +} +@font-face { + font-family:KaTeX_Fraktur; + src:url(#{$katexPath}fonts/KaTeX_Fraktur-Bold.eot); + src:url(#{$katexPath}fonts/KaTeX_Fraktur-Bold.eot#iefix) format('embedded-opentype'), url(#{$katexPath}fonts/KaTeX_Fraktur-Bold.woff2) format('woff2'), url(#{$katexPath}fonts/KaTeX_Fraktur-Bold.woff) format('woff'), url(#{$katexPath}fonts/KaTeX_Fraktur-Bold.ttf) format('ttf'); + font-weight:700; + font-style:normal +} +@font-face { + font-family:KaTeX_Fraktur; + src:url(#{$katexPath}fonts/KaTeX_Fraktur-Regular.eot); + src:url(#{$katexPath}fonts/KaTeX_Fraktur-Regular.eot#iefix) format('embedded-opentype'), url(#{$katexPath}fonts/KaTeX_Fraktur-Regular.woff2) format('woff2'), url(#{$katexPath}fonts/KaTeX_Fraktur-Regular.woff) format('woff'), url(#{$katexPath}fonts/KaTeX_Fraktur-Regular.ttf) format('ttf'); + font-weight:400; + font-style:normal +} +@font-face { + font-family:KaTeX_Main; + src:url(#{$katexPath}fonts/KaTeX_Main-Bold.eot); + src:url(#{$katexPath}fonts/KaTeX_Main-Bold.eot#iefix) format('embedded-opentype'), url(#{$katexPath}fonts/KaTeX_Main-Bold.woff2) format('woff2'), url(#{$katexPath}fonts/KaTeX_Main-Bold.woff) format('woff'), url(#{$katexPath}fonts/KaTeX_Main-Bold.ttf) format('ttf'); + font-weight:700; + font-style:normal +} +@font-face { + font-family:KaTeX_Main; + src:url(#{$katexPath}fonts/KaTeX_Main-Italic.eot); + src:url(#{$katexPath}fonts/KaTeX_Main-Italic.eot#iefix) format('embedded-opentype'), url(#{$katexPath}fonts/KaTeX_Main-Italic.woff2) format('woff2'), url(#{$katexPath}fonts/KaTeX_Main-Italic.woff) format('woff'), url(#{$katexPath}fonts/KaTeX_Main-Italic.ttf) format('ttf'); + font-weight:400; + font-style:italic +} +@font-face { + font-family:KaTeX_Main; + src:url(#{$katexPath}fonts/KaTeX_Main-Regular.eot); + src:url(#{$katexPath}fonts/KaTeX_Main-Regular.eot#iefix) format('embedded-opentype'), url(#{$katexPath}fonts/KaTeX_Main-Regular.woff2) format('woff2'), url(#{$katexPath}fonts/KaTeX_Main-Regular.woff) format('woff'), url(#{$katexPath}fonts/KaTeX_Main-Regular.ttf) format('ttf'); + font-weight:400; + font-style:normal +} +@font-face { + font-family:KaTeX_Math; + src:url(#{$katexPath}fonts/KaTeX_Math-Italic.eot); + src:url(#{$katexPath}fonts/KaTeX_Math-Italic.eot#iefix) format('embedded-opentype'), url(#{$katexPath}fonts/KaTeX_Math-Italic.woff2) format('woff2'), url(#{$katexPath}fonts/KaTeX_Math-Italic.woff) format('woff'), url(#{$katexPath}fonts/KaTeX_Math-Italic.ttf) format('ttf'); + font-weight:400; + font-style:italic +} +@font-face { + font-family:KaTeX_SansSerif; + src:url(#{$katexPath}fonts/KaTeX_SansSerif-Regular.eot); + src:url(#{$katexPath}fonts/KaTeX_SansSerif-Regular.eot#iefix) format('embedded-opentype'), url(#{$katexPath}fonts/KaTeX_SansSerif-Regular.woff2) format('woff2'), url(#{$katexPath}fonts/KaTeX_SansSerif-Regular.woff) format('woff'), url(#{$katexPath}fonts/KaTeX_SansSerif-Regular.ttf) format('ttf'); + font-weight:400; + font-style:normal +} +@font-face { + font-family:KaTeX_Script; + src:url(#{$katexPath}fonts/KaTeX_Script-Regular.eot); + src:url(#{$katexPath}fonts/KaTeX_Script-Regular.eot#iefix) format('embedded-opentype'), url(#{$katexPath}fonts/KaTeX_Script-Regular.woff2) format('woff2'), url(#{$katexPath}fonts/KaTeX_Script-Regular.woff) format('woff'), url(#{$katexPath}fonts/KaTeX_Script-Regular.ttf) format('ttf'); + font-weight:400; + font-style:normal +} +@font-face { + font-family:KaTeX_Size1; + src:url(#{$katexPath}fonts/KaTeX_Size1-Regular.eot); + src:url(#{$katexPath}fonts/KaTeX_Size1-Regular.eot#iefix) format('embedded-opentype'), url(#{$katexPath}fonts/KaTeX_Size1-Regular.woff2) format('woff2'), url(#{$katexPath}fonts/KaTeX_Size1-Regular.woff) format('woff'), url(#{$katexPath}fonts/KaTeX_Size1-Regular.ttf) format('ttf'); + font-weight:400; + font-style:normal +} +@font-face { + font-family:KaTeX_Size2; + src:url(#{$katexPath}fonts/KaTeX_Size2-Regular.eot); + src:url(#{$katexPath}fonts/KaTeX_Size2-Regular.eot#iefix) format('embedded-opentype'), url(#{$katexPath}fonts/KaTeX_Size2-Regular.woff2) format('woff2'), url(#{$katexPath}fonts/KaTeX_Size2-Regular.woff) format('woff'), url(#{$katexPath}fonts/KaTeX_Size2-Regular.ttf) format('ttf'); + font-weight:400; + font-style:normal +} +@font-face { + font-family:KaTeX_Size3; + src:url(#{$katexPath}fonts/KaTeX_Size3-Regular.eot); + src:url(#{$katexPath}fonts/KaTeX_Size3-Regular.eot#iefix) format('embedded-opentype'), url(#{$katexPath}fonts/KaTeX_Size3-Regular.woff2) format('woff2'), url(#{$katexPath}fonts/KaTeX_Size3-Regular.woff) format('woff'), url(#{$katexPath}fonts/KaTeX_Size3-Regular.ttf) format('ttf'); + font-weight:400; + font-style:normal +} +@font-face { + font-family:KaTeX_Size4; + src:url(#{$katexPath}fonts/KaTeX_Size4-Regular.eot); + src:url(#{$katexPath}fonts/KaTeX_Size4-Regular.eot#iefix) format('embedded-opentype'), url(#{$katexPath}fonts/KaTeX_Size4-Regular.woff2) format('woff2'), url(#{$katexPath}fonts/KaTeX_Size4-Regular.woff) format('woff'), url(#{$katexPath}fonts/KaTeX_Size4-Regular.ttf) format('ttf'); + font-weight:400; + font-style:normal +} +@font-face { + font-family:KaTeX_Typewriter; + src:url(#{$katexPath}fonts/KaTeX_Typewriter-Regular.eot); + src:url(#{$katexPath}fonts/KaTeX_Typewriter-Regular.eot#iefix) format('embedded-opentype'), url(#{$katexPath}fonts/KaTeX_Typewriter-Regular.woff2) format('woff2'), url(#{$katexPath}fonts/KaTeX_Typewriter-Regular.woff) format('woff'), url(#{$katexPath}fonts/KaTeX_Typewriter-Regular.ttf) format('ttf'); + font-weight:400; + font-style:normal +} +.katex-display { + display:block; + margin:1em 0; + text-align:center +} +.katex-display>.katex { + display:inline-block +} +.katex { + font:400 1.21em KaTeX_Main; + line-height:1.2; + white-space:nowrap; + text-indent:0 +} +.katex .katex-html { + display:inline-block +} +.katex .katex-mathml { + position:absolute; + clip:rect(1px, 1px, 1px, 1px); + padding:0; + border:0; + height:1px; + width:1px; + overflow:hidden +} +.katex .base, .katex .strut { + display:inline-block +} +.katex .mathit { + font-family:KaTeX_Math; + font-style:italic +} +.katex .mathbf { + font-family:KaTeX_Main; + font-weight:700 +} +.katex .amsrm, .katex .mathbb { + font-family:KaTeX_AMS +} +.katex .mathcal { + font-family:KaTeX_Caligraphic +} +.katex .mathfrak { + font-family:KaTeX_Fraktur +} +.katex .mathtt { + font-family:KaTeX_Typewriter +} +.katex .mathscr { + font-family:KaTeX_Script +} +.katex .mathsf { + font-family:KaTeX_SansSerif +} +.katex .mainit { + font-family:KaTeX_Main; + font-style:italic +} +.katex .textstyle>.mord+.mop { + margin-left:.16667em +} +.katex .textstyle>.mord+.mbin { + margin-left:.22222em +} +.katex .textstyle>.mord+.mrel { + margin-left:.27778em +} +.katex .textstyle>.mop+.mop, .katex .textstyle>.mop+.mord, .katex .textstyle>.mord+.minner { + margin-left:.16667em +} +.katex .textstyle>.mop+.mrel { + margin-left:.27778em +} +.katex .textstyle>.mop+.minner { + margin-left:.16667em +} +.katex .textstyle>.mbin+.minner, .katex .textstyle>.mbin+.mop, .katex .textstyle>.mbin+.mopen, .katex .textstyle>.mbin+.mord { + margin-left:.22222em +} +.katex .textstyle>.mrel+.minner, .katex .textstyle>.mrel+.mop, .katex .textstyle>.mrel+.mopen, .katex .textstyle>.mrel+.mord { + margin-left:.27778em +} +.katex .textstyle>.mclose+.mop { + margin-left:.16667em +} +.katex .textstyle>.mclose+.mbin { + margin-left:.22222em +} +.katex .textstyle>.mclose+.mrel { + margin-left:.27778em +} +.katex .textstyle>.mclose+.minner, .katex .textstyle>.minner+.mop, .katex .textstyle>.minner+.mord, .katex .textstyle>.mpunct+.mclose, .katex .textstyle>.mpunct+.minner, .katex .textstyle>.mpunct+.mop, .katex .textstyle>.mpunct+.mopen, .katex .textstyle>.mpunct+.mord, .katex .textstyle>.mpunct+.mpunct, .katex .textstyle>.mpunct+.mrel { + margin-left:.16667em +} +.katex .textstyle>.minner+.mbin { + margin-left:.22222em +} +.katex .textstyle>.minner+.mrel { + margin-left:.27778em +} +.katex .mclose+.mop, .katex .minner+.mop, .katex .mop+.mop, .katex .mop+.mord, .katex .mord+.mop, .katex .textstyle>.minner+.minner, .katex .textstyle>.minner+.mopen, .katex .textstyle>.minner+.mpunct { + margin-left:.16667em +} +.katex .reset-textstyle.textstyle { + font-size:1em +} +.katex .reset-textstyle.scriptstyle { + font-size:.7em +} +.katex .reset-textstyle.scriptscriptstyle { + font-size:.5em +} +.katex .reset-scriptstyle.textstyle { + font-size:1.42857em +} +.katex .reset-scriptstyle.scriptstyle { + font-size:1em +} +.katex .reset-scriptstyle.scriptscriptstyle { + font-size:.71429em +} +.katex .reset-scriptscriptstyle.textstyle { + font-size:2em +} +.katex .reset-scriptscriptstyle.scriptstyle { + font-size:1.4em +} +.katex .reset-scriptscriptstyle.scriptscriptstyle { + font-size:1em +} +.katex .style-wrap { + position:relative +} +.katex .vlist { + display:inline-block +} +.katex .vlist>span { + display:block; + height:0; + position:relative +} +.katex .vlist>span>span { + display:inline-block +} +.katex .vlist .baseline-fix { + display:inline-table; + table-layout:fixed +} +.katex .msupsub { + text-align:left +} +.katex .mfrac>span>span { + text-align:center +} +.katex .mfrac .frac-line { + width:100% +} +.katex .mfrac .frac-line:before { + border-bottom-style:solid; + border-bottom-width:1px; + content:""; + display:block +} +.katex .mfrac .frac-line:after { + border-bottom-style:solid; + border-bottom-width:.04em; + content:""; + display:block; + margin-top:-1px +} +.katex .mspace { + display:inline-block +} +.katex .mspace.negativethinspace { + margin-left:-.16667em +} +.katex .mspace.thinspace { + width:.16667em +} +.katex .mspace.mediumspace { + width:.22222em +} +.katex .mspace.thickspace { + width:.27778em +} +.katex .mspace.enspace { + width:.5em +} +.katex .mspace.quad { + width:1em +} +.katex .mspace.qquad { + width:2em +} +.katex .llap, .katex .rlap { + width:0; + position:relative +} +.katex .llap>.inner, .katex .rlap>.inner { + position:absolute +} +.katex .llap>.fix, .katex .rlap>.fix { + display:inline-block +} +.katex .llap>.inner { + right:0 +} +.katex .rlap>.inner { + left:0 +} +.katex .katex-logo .a { + font-size:.75em; + margin-left:-.32em; + position:relative; + top:-.2em +} +.katex .katex-logo .t { + margin-left:-.23em +} +.katex .katex-logo .e { + margin-left:-.1667em; + position:relative; + top:.2155em +} +.katex .katex-logo .x { + margin-left:-.125em +} +.katex .rule { + display:inline-block; + border-style:solid; + position:relative +} +.katex .overline .overline-line { + width:100% +} +.katex .overline .overline-line:before { + border-bottom-style:solid; + border-bottom-width:1px; + content:""; + display:block +} +.katex .overline .overline-line:after { + border-bottom-style:solid; + border-bottom-width:.04em; + content:""; + display:block; + margin-top:-1px +} +.katex .sqrt>.sqrt-sign { + position:relative +} +.katex .sqrt .sqrt-line { + width:100% +} +.katex .sqrt .sqrt-line:before { + border-bottom-style:solid; + border-bottom-width:1px; + content:""; + display:block +} +.katex .sqrt .sqrt-line:after { + border-bottom-style:solid; + border-bottom-width:.04em; + content:""; + display:block; + margin-top:-1px +} +.katex .sqrt>.root { + margin-left:.27777778em; + margin-right:-.55555556em +} +.katex .fontsize-ensurer, .katex .sizing { + display:inline-block +} +.katex .fontsize-ensurer.reset-size1.size1, .katex .sizing.reset-size1.size1 { + font-size:1em +} +.katex .fontsize-ensurer.reset-size1.size2, .katex .sizing.reset-size1.size2 { + font-size:1.4em +} +.katex .fontsize-ensurer.reset-size1.size3, .katex .sizing.reset-size1.size3 { + font-size:1.6em +} +.katex .fontsize-ensurer.reset-size1.size4, .katex .sizing.reset-size1.size4 { + font-size:1.8em +} +.katex .fontsize-ensurer.reset-size1.size5, .katex .sizing.reset-size1.size5 { + font-size:2em +} +.katex .fontsize-ensurer.reset-size1.size6, .katex .sizing.reset-size1.size6 { + font-size:2.4em +} +.katex .fontsize-ensurer.reset-size1.size7, .katex .sizing.reset-size1.size7 { + font-size:2.88em +} +.katex .fontsize-ensurer.reset-size1.size8, .katex .sizing.reset-size1.size8 { + font-size:3.46em +} +.katex .fontsize-ensurer.reset-size1.size9, .katex .sizing.reset-size1.size9 { + font-size:4.14em +} +.katex .fontsize-ensurer.reset-size1.size10, .katex .sizing.reset-size1.size10 { + font-size:4.98em +} +.katex .fontsize-ensurer.reset-size2.size1, .katex .sizing.reset-size2.size1 { + font-size:.71428571em +} +.katex .fontsize-ensurer.reset-size2.size2, .katex .sizing.reset-size2.size2 { + font-size:1em +} +.katex .fontsize-ensurer.reset-size2.size3, .katex .sizing.reset-size2.size3 { + font-size:1.14285714em +} +.katex .fontsize-ensurer.reset-size2.size4, .katex .sizing.reset-size2.size4 { + font-size:1.28571429em +} +.katex .fontsize-ensurer.reset-size2.size5, .katex .sizing.reset-size2.size5 { + font-size:1.42857143em +} +.katex .fontsize-ensurer.reset-size2.size6, .katex .sizing.reset-size2.size6 { + font-size:1.71428571em +} +.katex .fontsize-ensurer.reset-size2.size7, .katex .sizing.reset-size2.size7 { + font-size:2.05714286em +} +.katex .fontsize-ensurer.reset-size2.size8, .katex .sizing.reset-size2.size8 { + font-size:2.47142857em +} +.katex .fontsize-ensurer.reset-size2.size9, .katex .sizing.reset-size2.size9 { + font-size:2.95714286em +} +.katex .fontsize-ensurer.reset-size2.size10, .katex .sizing.reset-size2.size10 { + font-size:3.55714286em +} +.katex .fontsize-ensurer.reset-size3.size1, .katex .sizing.reset-size3.size1 { + font-size:.625em +} +.katex .fontsize-ensurer.reset-size3.size2, .katex .sizing.reset-size3.size2 { + font-size:.875em +} +.katex .fontsize-ensurer.reset-size3.size3, .katex .sizing.reset-size3.size3 { + font-size:1em +} +.katex .fontsize-ensurer.reset-size3.size4, .katex .sizing.reset-size3.size4 { + font-size:1.125em +} +.katex .fontsize-ensurer.reset-size3.size5, .katex .sizing.reset-size3.size5 { + font-size:1.25em +} +.katex .fontsize-ensurer.reset-size3.size6, .katex .sizing.reset-size3.size6 { + font-size:1.5em +} +.katex .fontsize-ensurer.reset-size3.size7, .katex .sizing.reset-size3.size7 { + font-size:1.8em +} +.katex .fontsize-ensurer.reset-size3.size8, .katex .sizing.reset-size3.size8 { + font-size:2.1625em +} +.katex .fontsize-ensurer.reset-size3.size9, .katex .sizing.reset-size3.size9 { + font-size:2.5875em +} +.katex .fontsize-ensurer.reset-size3.size10, .katex .sizing.reset-size3.size10 { + font-size:3.1125em +} +.katex .fontsize-ensurer.reset-size4.size1, .katex .sizing.reset-size4.size1 { + font-size:.55555556em +} +.katex .fontsize-ensurer.reset-size4.size2, .katex .sizing.reset-size4.size2 { + font-size:.77777778em +} +.katex .fontsize-ensurer.reset-size4.size3, .katex .sizing.reset-size4.size3 { + font-size:.88888889em +} +.katex .fontsize-ensurer.reset-size4.size4, .katex .sizing.reset-size4.size4 { + font-size:1em +} +.katex .fontsize-ensurer.reset-size4.size5, .katex .sizing.reset-size4.size5 { + font-size:1.11111111em +} +.katex .fontsize-ensurer.reset-size4.size6, .katex .sizing.reset-size4.size6 { + font-size:1.33333333em +} +.katex .fontsize-ensurer.reset-size4.size7, .katex .sizing.reset-size4.size7 { + font-size:1.6em +} +.katex .fontsize-ensurer.reset-size4.size8, .katex .sizing.reset-size4.size8 { + font-size:1.92222222em +} +.katex .fontsize-ensurer.reset-size4.size9, .katex .sizing.reset-size4.size9 { + font-size:2.3em +} +.katex .fontsize-ensurer.reset-size4.size10, .katex .sizing.reset-size4.size10 { + font-size:2.76666667em +} +.katex .fontsize-ensurer.reset-size5.size1, .katex .sizing.reset-size5.size1 { + font-size:.5em +} +.katex .fontsize-ensurer.reset-size5.size2, .katex .sizing.reset-size5.size2 { + font-size:.7em +} +.katex .fontsize-ensurer.reset-size5.size3, .katex .sizing.reset-size5.size3 { + font-size:.8em +} +.katex .fontsize-ensurer.reset-size5.size4, .katex .sizing.reset-size5.size4 { + font-size:.9em +} +.katex .fontsize-ensurer.reset-size5.size5, .katex .sizing.reset-size5.size5 { + font-size:1em +} +.katex .fontsize-ensurer.reset-size5.size6, .katex .sizing.reset-size5.size6 { + font-size:1.2em +} +.katex .fontsize-ensurer.reset-size5.size7, .katex .sizing.reset-size5.size7 { + font-size:1.44em +} +.katex .fontsize-ensurer.reset-size5.size8, .katex .sizing.reset-size5.size8 { + font-size:1.73em +} +.katex .fontsize-ensurer.reset-size5.size9, .katex .sizing.reset-size5.size9 { + font-size:2.07em +} +.katex .fontsize-ensurer.reset-size5.size10, .katex .sizing.reset-size5.size10 { + font-size:2.49em +} +.katex .fontsize-ensurer.reset-size6.size1, .katex .sizing.reset-size6.size1 { + font-size:.41666667em +} +.katex .fontsize-ensurer.reset-size6.size2, .katex .sizing.reset-size6.size2 { + font-size:.58333333em +} +.katex .fontsize-ensurer.reset-size6.size3, .katex .sizing.reset-size6.size3 { + font-size:.66666667em +} +.katex .fontsize-ensurer.reset-size6.size4, .katex .sizing.reset-size6.size4 { + font-size:.75em +} +.katex .fontsize-ensurer.reset-size6.size5, .katex .sizing.reset-size6.size5 { + font-size:.83333333em +} +.katex .fontsize-ensurer.reset-size6.size6, .katex .sizing.reset-size6.size6 { + font-size:1em +} +.katex .fontsize-ensurer.reset-size6.size7, .katex .sizing.reset-size6.size7 { + font-size:1.2em +} +.katex .fontsize-ensurer.reset-size6.size8, .katex .sizing.reset-size6.size8 { + font-size:1.44166667em +} +.katex .fontsize-ensurer.reset-size6.size9, .katex .sizing.reset-size6.size9 { + font-size:1.725em +} +.katex .fontsize-ensurer.reset-size6.size10, .katex .sizing.reset-size6.size10 { + font-size:2.075em +} +.katex .fontsize-ensurer.reset-size7.size1, .katex .sizing.reset-size7.size1 { + font-size:.34722222em +} +.katex .fontsize-ensurer.reset-size7.size2, .katex .sizing.reset-size7.size2 { + font-size:.48611111em +} +.katex .fontsize-ensurer.reset-size7.size3, .katex .sizing.reset-size7.size3 { + font-size:.55555556em +} +.katex .fontsize-ensurer.reset-size7.size4, .katex .sizing.reset-size7.size4 { + font-size:.625em +} +.katex .fontsize-ensurer.reset-size7.size5, .katex .sizing.reset-size7.size5 { + font-size:.69444444em +} +.katex .fontsize-ensurer.reset-size7.size6, .katex .sizing.reset-size7.size6 { + font-size:.83333333em +} +.katex .fontsize-ensurer.reset-size7.size7, .katex .sizing.reset-size7.size7 { + font-size:1em +} +.katex .fontsize-ensurer.reset-size7.size8, .katex .sizing.reset-size7.size8 { + font-size:1.20138889em +} +.katex .fontsize-ensurer.reset-size7.size9, .katex .sizing.reset-size7.size9 { + font-size:1.4375em +} +.katex .fontsize-ensurer.reset-size7.size10, .katex .sizing.reset-size7.size10 { + font-size:1.72916667em +} +.katex .fontsize-ensurer.reset-size8.size1, .katex .sizing.reset-size8.size1 { + font-size:.28901734em +} +.katex .fontsize-ensurer.reset-size8.size2, .katex .sizing.reset-size8.size2 { + font-size:.40462428em +} +.katex .fontsize-ensurer.reset-size8.size3, .katex .sizing.reset-size8.size3 { + font-size:.46242775em +} +.katex .fontsize-ensurer.reset-size8.size4, .katex .sizing.reset-size8.size4 { + font-size:.52023121em +} +.katex .fontsize-ensurer.reset-size8.size5, .katex .sizing.reset-size8.size5 { + font-size:.57803468em +} +.katex .fontsize-ensurer.reset-size8.size6, .katex .sizing.reset-size8.size6 { + font-size:.69364162em +} +.katex .fontsize-ensurer.reset-size8.size7, .katex .sizing.reset-size8.size7 { + font-size:.83236994em +} +.katex .fontsize-ensurer.reset-size8.size8, .katex .sizing.reset-size8.size8 { + font-size:1em +} +.katex .fontsize-ensurer.reset-size8.size9, .katex .sizing.reset-size8.size9 { + font-size:1.19653179em +} +.katex .fontsize-ensurer.reset-size8.size10, .katex .sizing.reset-size8.size10 { + font-size:1.43930636em +} +.katex .fontsize-ensurer.reset-size9.size1, .katex .sizing.reset-size9.size1 { + font-size:.24154589em +} +.katex .fontsize-ensurer.reset-size9.size2, .katex .sizing.reset-size9.size2 { + font-size:.33816425em +} +.katex .fontsize-ensurer.reset-size9.size3, .katex .sizing.reset-size9.size3 { + font-size:.38647343em +} +.katex .fontsize-ensurer.reset-size9.size4, .katex .sizing.reset-size9.size4 { + font-size:.43478261em +} +.katex .fontsize-ensurer.reset-size9.size5, .katex .sizing.reset-size9.size5 { + font-size:.48309179em +} +.katex .fontsize-ensurer.reset-size9.size6, .katex .sizing.reset-size9.size6 { + font-size:.57971014em +} +.katex .fontsize-ensurer.reset-size9.size7, .katex .sizing.reset-size9.size7 { + font-size:.69565217em +} +.katex .fontsize-ensurer.reset-size9.size8, .katex .sizing.reset-size9.size8 { + font-size:.83574879em +} +.katex .fontsize-ensurer.reset-size9.size9, .katex .sizing.reset-size9.size9 { + font-size:1em +} +.katex .fontsize-ensurer.reset-size9.size10, .katex .sizing.reset-size9.size10 { + font-size:1.20289855em +} +.katex .fontsize-ensurer.reset-size10.size1, .katex .sizing.reset-size10.size1 { + font-size:.20080321em +} +.katex .fontsize-ensurer.reset-size10.size2, .katex .sizing.reset-size10.size2 { + font-size:.2811245em +} +.katex .fontsize-ensurer.reset-size10.size3, .katex .sizing.reset-size10.size3 { + font-size:.32128514em +} +.katex .fontsize-ensurer.reset-size10.size4, .katex .sizing.reset-size10.size4 { + font-size:.36144578em +} +.katex .fontsize-ensurer.reset-size10.size5, .katex .sizing.reset-size10.size5 { + font-size:.40160643em +} +.katex .fontsize-ensurer.reset-size10.size6, .katex .sizing.reset-size10.size6 { + font-size:.48192771em +} +.katex .fontsize-ensurer.reset-size10.size7, .katex .sizing.reset-size10.size7 { + font-size:.57831325em +} +.katex .fontsize-ensurer.reset-size10.size8, .katex .sizing.reset-size10.size8 { + font-size:.69477912em +} +.katex .fontsize-ensurer.reset-size10.size9, .katex .sizing.reset-size10.size9 { + font-size:.8313253em +} +.katex .fontsize-ensurer.reset-size10.size10, .katex .sizing.reset-size10.size10 { + font-size:1em +} +.katex .delimsizing.size1 { + font-family:KaTeX_Size1 +} +.katex .delimsizing.size2 { + font-family:KaTeX_Size2 +} +.katex .delimsizing.size3 { + font-family:KaTeX_Size3 +} +.katex .delimsizing.size4 { + font-family:KaTeX_Size4 +} +.katex .delimsizing.mult .delim-size1>span { + font-family:KaTeX_Size1 +} +.katex .delimsizing.mult .delim-size4>span { + font-family:KaTeX_Size4 +} +.katex .nulldelimiter { + display:inline-block; + width:.12em +} +.katex .op-symbol { + position:relative +} +.katex .op-symbol.small-op { + font-family:KaTeX_Size1 +} +.katex .op-symbol.large-op { + font-family:KaTeX_Size2 +} +.katex .accent>.vlist>span, .katex .op-limits>.vlist>span { + text-align:center +} +.katex .accent .accent-body>span { + width:0 +} +.katex .accent .accent-body.accent-vec>span { + position:relative; + left:.326em +} +.katex .mtable .vertical-separator { + display:inline-block; + margin:0 -.025em; + border-right:.05em solid #000 +} +.katex .mtable .arraycolsep { + display:inline-block +} +.katex .mtable .col-align-c>.vlist { + text-align:center +} +.katex .mtable .col-align-l>.vlist { + text-align:left +} +.katex .mtable .col-align-r>.vlist { + text-align:right +} diff --git a/sop-website/sop-portal/src/pages/blog/blogItem.jsx b/sop-website/sop-portal/src/pages/blog/blogItem.jsx new file mode 100644 index 00000000..bd9eb5ee --- /dev/null +++ b/sop-website/sop-portal/src/pages/blog/blogItem.jsx @@ -0,0 +1,57 @@ +import React from 'react'; +import { autobind } from 'core-decorators'; +import { getLink } from '../../../utils'; + +import './blogItem.scss'; + +@autobind +class BlogItem extends React.Component { + constructor(props) { + super(props); + this.state = { + isHovered: false, + }; + } + + onMouseOver() { + this.setState({ + isHovered: true, + }); + } + + onMouseOut() { + this.setState({ + isHovered: false, + }); + } + + render() { + const { dataSource } = this.props; + const { link, target, title, author, companyIcon, companyIconHover, dateStr, desc } = dataSource; + const { isHovered } = this.state; + return ( + +
    + + {title} +
    +
    + {author} + { + companyIcon ? : null + } + {dateStr} +
    +

    {desc}

    +
    + ); + } +} + +export default BlogItem; diff --git a/sop-website/sop-portal/src/pages/blog/blogItem.scss b/sop-website/sop-portal/src/pages/blog/blogItem.scss new file mode 100644 index 00000000..c7b3b028 --- /dev/null +++ b/sop-website/sop-portal/src/pages/blog/blogItem.scss @@ -0,0 +1,57 @@ +.blog-item { + box-sizing: border-box; + display: block; + width: 100%; + padding: 20px; + margin-bottom: 40px; + background: #F8F8F8; + .title { + img { + width: 16px; + height: 20px; + margin-right: 8px; + } + span { + font-family: Avenir-Heavy; + font-size: 20px; + color: #666666; + } + } + .brief-info { + padding: 12px 0 20px; + .author { + font-family: Avenir-Heavy; + font-size: 14px; + color: #999; + margin-right: 8px; + } + img { + width: 14px; + height: 14px; + } + .date { + float: right; + font-family: Avenir-Medium; + font-size: 12px; + color: #999; + } + } + p { + font-family: Avenir-Medium; + font-size: 14px; + color: #666; + margin: 0; + } + &:hover { + .title { + span { + color: #333; + } + } + .brief-info { + .author { + color: #666; + } + } + } +} \ No newline at end of file diff --git a/sop-website/sop-portal/src/pages/blog/index.jsx b/sop-website/sop-portal/src/pages/blog/index.jsx new file mode 100644 index 00000000..1069de74 --- /dev/null +++ b/sop-website/sop-portal/src/pages/blog/index.jsx @@ -0,0 +1,58 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; +import Language from '../../components/language'; +import Header from '../../components/header'; +import Bar from '../../components/bar'; +import PageSlider from '../../components/pageSlider'; +import BlogItem from './blogItem'; +import Footer from '../../components/footer'; +import blogConfig from '../../../site_config/blog'; +import { getLink } from '../../../utils'; +import './index.scss'; + +class Blog extends Language { + + render() { + const language = this.getLanguage(); + const dataSource = blogConfig[language]; + const blogs = dataSource.list; + return ( +
    +
    + +
    +
    + + { + blogs.map((blog, i) => ( + + )) + } + +
    +
    +

    {dataSource.postsTitle}

    + +
    +
    +
    +
    + ); + } +} + +document.getElementById('root') && ReactDOM.render(, document.getElementById('root')); + +export default Blog; diff --git a/sop-website/sop-portal/src/pages/blog/index.scss b/sop-website/sop-portal/src/pages/blog/index.scss new file mode 100644 index 00000000..dd46ee16 --- /dev/null +++ b/sop-website/sop-portal/src/pages/blog/index.scss @@ -0,0 +1,55 @@ +@import '../../variables.scss'; +@import '../../reset.scss'; + +.blog-list-page { + .blog-container { + max-width: $contentWidth; + margin: 0 auto; + box-sizing: border-box; + padding: 50px 8% 80px; + .col { + display: inline-block; + box-sizing: border-box; + &-18 { + width: 75%; + border-right: 1px solid #CBCCCD; + padding-right: 6%; + } + &-6 { + width: 25%; + padding-left: 20px; + vertical-align: top; + h4 { + font-family: Avenir-Heavy; + font-size: 18px; + color: #333; + margin: 0 0 20px; + } + ul { + list-style: none; + margin: 0; + padding: 0; + li { + width: 100%; + margin-bottom: 14px; + span { + font-family: Avenir-Medium; + font-size: 12px; + color: #666; + } + } + } + } + } + @media screen and (max-width: $mobileWidth){ + .left-part { + width: 100%; + border-right: none; + padding-right: 0; + } + .right-part { + display: none; + } + } + } +} \ No newline at end of file diff --git a/sop-website/sop-portal/src/pages/blogDetail/index.jsx b/sop-website/sop-portal/src/pages/blogDetail/index.jsx new file mode 100644 index 00000000..93181677 --- /dev/null +++ b/sop-website/sop-portal/src/pages/blogDetail/index.jsx @@ -0,0 +1,109 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; +import { scroller } from 'react-scroll'; +import path from 'path'; +import 'whatwg-fetch'; // fetch polyfill +import Language from '../../components/language'; +import Header from '../../components/header'; +import Footer from '../../components/footer'; +import './index.scss'; + +// 锚点正则 +const anchorReg = /^#[^/]/; +// 相对地址正则,包括./、../、直接文件夹名称开头、直接文件开头 +const relativeReg = /^((\.{1,2}\/)|([\w-]+[/.]))/; +class BlogDetail extends Language { + + constructor(props) { + super(props); + this.state = { + __html: '', + }; + } + + componentDidMount() { + // 通过请求获取生成好的json数据,静态页和json文件在同一个目录下 + fetch(window.location.pathname.replace(/\.html$/i, '.json')) + .then(res => res.json()) + .then((md) => { + this.setState({ + __html: md && md.__html ? md.__html : '', + }); + }); + this.markdownContainer.addEventListener('click', (e) => { + const isAnchor = e.target.nodeName.toLowerCase() === 'a' && e.target.getAttribute('href') && anchorReg.test(e.target.getAttribute('href')); + if (isAnchor) { + e.preventDefault(); + const id = e.target.getAttribute('href').slice(1); + scroller.scrollTo(id, { + duration: 1000, + smooth: 'easeInOutQuint', + }); + } + }); + } + + componentDidUpdate() { + this.handleRelativeLink(); + this.handleRelativeImg(); + } + + handleRelativeLink() { + const language = this.getLanguage(); + // 获取当前文档所在文件系统中的路径 + // rootPath/en-us/blog/dir/hello.html => /blog/en-us/dir + const splitPart = window.location.pathname.replace(`${window.rootPath}/${language}`, '').split('/').slice(0, -1); + const filePath = splitPart.join('/'); + const alinks = Array.from(this.markdownContainer.querySelectorAll('a')); + alinks.forEach((alink) => { + const href = alink.getAttribute('href'); + if (relativeReg.test(href)) { + // 文档之间有中英文之分,md的相对地址要转换为对应HTML的地址 + alink.href = `${path.join(`${window.rootPath}/${language}`, filePath, href.replace(/\.(md|markdown)$/, '.html'))}`; + } + }); + } + + handleRelativeImg() { + const language = this.getLanguage(); + // 获取当前文档所在文件系统中的路径 + // rootPath/en-us/blog/dir/hello.html => /blog/en-us/dir + const splitPart = window.location.pathname.replace(`${window.rootPath}/${language}`, '').split('/').slice(0, -1); + splitPart.splice(2, 0, language); + const filePath = splitPart.join('/'); + const imgs = Array.from(this.markdownContainer.querySelectorAll('img')); + imgs.forEach((img) => { + const src = img.getAttribute('src'); + if (relativeReg.test(src)) { + // 图片无中英文之分 + img.src = `${path.join(window.rootPath, filePath, src)}`; + } + }); + } + + render() { + const language = this.getLanguage(); + const __html = this.props.__html || this.state.__html; + return ( +
    +
    +
    { this.markdownContainer = node; }} + dangerouslySetInnerHTML={{ __html }} + /> +
    +
    + ); + } +} + +document.getElementById('root') && ReactDOM.render(, document.getElementById('root')); + +export default BlogDetail; diff --git a/sop-website/sop-portal/src/pages/blogDetail/index.scss b/sop-website/sop-portal/src/pages/blogDetail/index.scss new file mode 100644 index 00000000..b2c9cd18 --- /dev/null +++ b/sop-website/sop-portal/src/pages/blogDetail/index.scss @@ -0,0 +1,11 @@ +@import '../../variables.scss'; +@import '../../reset.scss'; +@import '../../markdown.scss'; + +.blog-detail-page { + .blog-content { + padding: 80px 20%; + margin: $headerHeight auto 0; + max-width: 735px; + } +} \ No newline at end of file diff --git a/sop-website/sop-portal/src/pages/community/contactItem.jsx b/sop-website/sop-portal/src/pages/community/contactItem.jsx new file mode 100644 index 00000000..38b6a0ec --- /dev/null +++ b/sop-website/sop-portal/src/pages/community/contactItem.jsx @@ -0,0 +1,44 @@ +import React from 'react'; +import { autobind } from 'core-decorators'; +import { getLink } from '../../../utils'; + +@autobind +class ContactItem extends React.Component { + constructor(props) { + super(props); + this.state = { + img: props.contact.img, + }; + } + + onMouseOver() { + this.setState({ + img: this.props.contact.imgHover, + }); + } + + onMouseOut() { + this.setState({ + img: this.props.contact.img, + }); + } + + render() { + const { contact } = this.props; + const { img } = this.state; + return ( + + +
    {contact.title}
    +
    + ); + } +} + +export default ContactItem; diff --git a/sop-website/sop-portal/src/pages/community/contributorItem.jsx b/sop-website/sop-portal/src/pages/community/contributorItem.jsx new file mode 100644 index 00000000..9645eda8 --- /dev/null +++ b/sop-website/sop-portal/src/pages/community/contributorItem.jsx @@ -0,0 +1,16 @@ +import React from 'react'; +import { getLink } from '../../../utils'; + +const ContributorItem = (props) => { + const { contributor } = props; + const { img, title, content } = contributor; + return ( +
    + +
    {title}
    +

    {content}

    +
    + ); +}; + +export default ContributorItem; diff --git a/sop-website/sop-portal/src/pages/community/ecoItem.jsx b/sop-website/sop-portal/src/pages/community/ecoItem.jsx new file mode 100644 index 00000000..365d2091 --- /dev/null +++ b/sop-website/sop-portal/src/pages/community/ecoItem.jsx @@ -0,0 +1,30 @@ +import React from 'react'; +import { getLink } from '../../../utils'; + +const EcoItem = (props) => { + const { eco } = props; + return ( +
    +

    {eco.title}

    +

    {eco.content}

    +
    + { + eco.tags.map((tag, i) => ( + + { + tag.text + } + + )) + } +
    +
    + ); +}; + +export default EcoItem; diff --git a/sop-website/sop-portal/src/pages/community/eventCard.jsx b/sop-website/sop-portal/src/pages/community/eventCard.jsx new file mode 100644 index 00000000..5760d54c --- /dev/null +++ b/sop-website/sop-portal/src/pages/community/eventCard.jsx @@ -0,0 +1,25 @@ +import React from 'react'; +import { getLink } from '../../../utils'; + +class EventCard extends React.Component { + render() { + const { event } = this.props; + return ( +
    + + + +
    +

    {event.title}

    +

    {event.content}

    + + {event.dateStr} + + +
    +
    + ); + } +} + +export default EventCard; diff --git a/sop-website/sop-portal/src/pages/community/index.jsx b/sop-website/sop-portal/src/pages/community/index.jsx new file mode 100644 index 00000000..007c4a57 --- /dev/null +++ b/sop-website/sop-portal/src/pages/community/index.jsx @@ -0,0 +1,79 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; +import Language from '../../components/language'; +import Header from '../../components/header'; +import Bar from '../../components/bar'; +import Slider from '../../components/slider'; +import EventCard from './eventCard'; +import ContactItem from './contactItem'; +import ContributorItem from './contributorItem'; +import EcoItem from './ecoItem'; +import Footer from '../../components/footer'; +import communityConfig from '../../../site_config/community.jsx'; + +import './index.scss'; + +class Community extends Language { + + render() { + const language = this.getLanguage(); + const dataSource = communityConfig[language]; + return ( +
    +
    + +
    +

    {dataSource.events.title}

    + + {dataSource.events.list.map((event, i) => ( + + ))} + +
    +
    +

    {dataSource.ecos.title}

    +
    + { + dataSource.ecos.list.map((eco, i) => ( + + )) + } +
    +
    +
    +

    {dataSource.contacts.title}

    +

    {dataSource.contacts.desc}

    +
    + { + dataSource.contacts.list.map((contact, i) => ( + + )) + } +
    +
    +
    +

    {dataSource.contributorGuide.title}

    +

    {dataSource.contributorGuide.desc}

    +
    + { + dataSource.contributorGuide.list.map((contributor, i) => ( + + )) + } +
    +
    +
    +
    + ); + } +} + +document.getElementById('root') && ReactDOM.render(, document.getElementById('root')); + +export default Community; diff --git a/sop-website/sop-portal/src/pages/community/index.scss b/sop-website/sop-portal/src/pages/community/index.scss new file mode 100644 index 00000000..9823a671 --- /dev/null +++ b/sop-website/sop-portal/src/pages/community/index.scss @@ -0,0 +1,189 @@ +@import '../../variables.scss'; +@import '../../reset.scss'; + +.community-page { + .events-section { + max-width: $contentWidth; + margin: 0 auto; + box-sizing: border-box; + padding: 80px 40px 60px; + @media screen and (max-width: $mobileWidth) { + padding: 0; + } + h3 { + font-family: Avenir-Heavy; + font-size: 36px; + color: #333; + text-align: center; + margin: 0 0 40px; + } + .event-card { + width: 373px; + font-size: 0; + img { + width: 373px; + height: 209px; + } + @media screen and (max-width: $mobileWidth / 2) { + width: 320px; + img { + width: 320px; + height: 179px; + } + } + .event-introduction { + padding: 20px; + background: #F8F8F8; + h4 { + font-family: Avenir-Heavy; + font-size: 20px; + color: #333; + margin: 0 0 10px; + } + p { + font-family: Avenir-Medium; + font-size: 14px; + color: #666; + margin: 0; + } + a { + display: inline-block; + width: 100%; + font-family: Avenir-Medium; + font-size: 12px; + color: #999; + margin-top: 10px; + .arrow { + width: 8px; + height: 13px; + float: right; + } + } + } + } + } + .contact-section { + max-width: $contentWidth; + margin: 0 auto; + box-sizing: border-box; + padding: 60px 40px 40px; + @media screen and (max-width: $mobileWidth) { + padding-left: 20px; + padding-right: 20px; + } + h3 { + font-family: Avenir-Heavy; + font-size: 36px; + color: #333; + text-align: center; + margin: 0 0 12px; + } + p { + font-family: Avenir-Medium; + font-size: 14px; + color: #666; + text-align: center; + margin: 0 0 40px; + } + .contact-list { + display: flex; + justify-content: space-around; + flex-wrap: wrap; + .contact-item { + display: inline-block; + text-align: center; + padding: 0 20px; + // width: 82px; + font-family: Avenir-Heavy; + font-size: 18px; + color: #999; + &:hover { + color: $brandColor; + } + img { + width: 82px; + height: 86px; + } + } + } + } + .contributor-section { + max-width: $contentWidth; + margin: 0 auto; + box-sizing: border-box; + padding: 60px 40px 40px; + h3 { + font-family: Avenir-Heavy; + font-size: 36px; + color: #333; + text-align: center; + margin: 0 0 12px; + } + p { + font-family: Avenir-Medium; + font-size: 14px; + color: #666; + text-align: center; + margin: 0 0 40px; + } + .contributor-list { + display: flex; + justify-content: space-around; + flex-wrap: wrap; + .contributor-item { + display: inline-block; + width: 240px; + text-align: center; + font-family: Avenir-Heavy; + font-size: 18px; + color: #999; + img { + width: 82px; + height: 86px; + } + } + } + } + .eco-section { + padding: 60px 20% 0; + max-width: 735px; + margin: 0 auto; + h3 { + font-family: Avenir-Heavy; + font-size: 36px; + color: #333; + text-align: center; + margin-bottom: 12px; + } + .eco-item { + margin-bottom: 30px; + h4 { + font-family: Avenir-Heavy; + font-size: 18px; + color: #333; + margin: 0 0 10px; + } + p { + font-family: Avenir-Medium; + font-size: 14px; + color: #666; + text-align: justify; + margin: 12px 0; + } + .tags { + a { + font-family: Avenir-Medium; + font-size: 12px; + color: #fff; + display: inline-block; + height: 32px; + line-height: 32px; + padding: 0 16px; + border-radius: 2px; + text-align: center; + margin: 0 10px 10px 0; + } + } + } + } +} \ No newline at end of file diff --git a/sop-website/sop-portal/src/pages/documentation/index.jsx b/sop-website/sop-portal/src/pages/documentation/index.jsx new file mode 100644 index 00000000..fb53f715 --- /dev/null +++ b/sop-website/sop-portal/src/pages/documentation/index.jsx @@ -0,0 +1,118 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; +import { scroller } from 'react-scroll'; +import 'whatwg-fetch'; // fetch polyfill +import path from 'path'; +import Language from '../../components/language'; +import Header from '../../components/header'; +import Bar from '../../components/bar'; +import Sidemenu from '../../components/sidemenu'; +import Footer from '../../components/footer'; +import docsConfig from '../../../site_config/docs'; +import './index.scss'; + +// 锚点正则 +const anchorReg = /^#[^/]/; +// 相对地址正则,包括./、../、直接文件夹名称开头、直接文件开头 +const relativeReg = /^((\.{1,2}\/)|([\w-]+[/.]))/; + +class Documentation extends Language { + + constructor(props) { + super(props); + this.state = { + __html: '', + }; + } + + componentDidMount() { + // 通过请求获取生成好的json数据,静态页和json文件在同一个目录下 + fetch(window.location.pathname.replace(/\.html$/i, '.json')) + .then(res => res.json()) + .then((md) => { + this.setState({ + __html: md && md.__html ? md.__html : '', + }); + }); + this.markdownContainer.addEventListener('click', (e) => { + const isAnchor = e.target.nodeName.toLowerCase() === 'a' && e.target.getAttribute('href') && anchorReg.test(e.target.getAttribute('href')); + if (isAnchor) { + e.preventDefault(); + const id = e.target.getAttribute('href').slice(1); + scroller.scrollTo(id, { + duration: 1000, + smooth: 'easeInOutQuint', + }); + } + }); + } + + componentDidUpdate() { + this.handleRelativeLink(); + this.handleRelativeImg(); + } + + handleRelativeLink() { + const language = this.getLanguage(); + // 获取当前文档所在文件系统中的路径 + // rootPath/en-us/docs/dir/hello.html => /docs/en-us/dir + const splitPart = window.location.pathname.replace(`${window.rootPath}/${language}`, '').split('/').slice(0, -1); + const filePath = splitPart.join('/'); + const alinks = Array.from(this.markdownContainer.querySelectorAll('a')); + alinks.forEach((alink) => { + const href = alink.getAttribute('href'); + if (relativeReg.test(href)) { + // 文档之间有中英文之分,md的相对地址要转换为对应HTML的地址 + alink.href = `${path.join(`${window.rootPath}/${language}`, filePath, href.replace(/\.(md|markdown)$/, '.html'))}`; + } + }); + } + + handleRelativeImg() { + const language = this.getLanguage(); + // 获取当前文档所在文件系统中的路径 + // rootPath/en-us/docs/dir/hello.html => /docs/en-us/dir + const splitPart = window.location.pathname.replace(`${window.rootPath}/${language}`, '').split('/').slice(0, -1); + splitPart.splice(2, 0, language); + const filePath = splitPart.join('/'); + const imgs = Array.from(this.markdownContainer.querySelectorAll('img')); + imgs.forEach((img) => { + const src = img.getAttribute('src'); + if (relativeReg.test(src)) { + // 图片无中英文之分 + img.src = `${path.join(window.rootPath, filePath, src)}`; + } + }); + } + + render() { + const language = this.getLanguage(); + const dataSource = docsConfig[language]; + const __html = this.props.__html || this.state.__html; + return ( +
    +
    + +
    + +
    { this.markdownContainer = node; }} + dangerouslySetInnerHTML={{ __html }} + /> +
    +
    +
    + ); + } +} + +document.getElementById('root') && ReactDOM.render(, document.getElementById('root')); + +export default Documentation; diff --git a/sop-website/sop-portal/src/pages/documentation/index.scss b/sop-website/sop-portal/src/pages/documentation/index.scss new file mode 100644 index 00000000..a29ea511 --- /dev/null +++ b/sop-website/sop-portal/src/pages/documentation/index.scss @@ -0,0 +1,36 @@ +@import '../../variables.scss'; +@import '../../reset.scss'; +@import '../../markdown.scss'; + +.documentation-page { + .content-section { + max-width: $contentWidth; + margin: 0 auto; + box-sizing: border-box; + padding: 40px 40px 60px; + position: relative; + min-height: 1100px; + .doc-content { + display: inline-block; + vertical-align: top; + box-sizing: border-box; + padding: 20px 6% 0; + width: calc(100% - #{$sideMenuWidth}); + } + } + @media screen and (max-width: $mobileWidth) { + .content-section { + padding-left: 20px; + padding-right: 20px; + .doc-content { + width: 100%; + } + } + .sidemenu { + position: absolute; + z-index: 100; + left: 0; + top: 40px; + } + } +} \ No newline at end of file diff --git a/sop-website/sop-portal/src/pages/home/featureItem.jsx b/sop-website/sop-portal/src/pages/home/featureItem.jsx new file mode 100644 index 00000000..8164ec3c --- /dev/null +++ b/sop-website/sop-portal/src/pages/home/featureItem.jsx @@ -0,0 +1,17 @@ +import React from 'react'; +import { getLink } from '../../../utils'; + +const Item = (props) => { + const { feature } = props; + return ( +
  • + +
    +

    {feature.title}

    +

    {feature.content}

    +
    +
  • + ); +}; + +export default Item; diff --git a/sop-website/sop-portal/src/pages/home/index.jsx b/sop-website/sop-portal/src/pages/home/index.jsx new file mode 100644 index 00000000..f3fa29f9 --- /dev/null +++ b/sop-website/sop-portal/src/pages/home/index.jsx @@ -0,0 +1,98 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; +import { getScrollTop, getLink } from '../../../utils'; +import Header from '../../components/header'; +import Button from '../../components/button'; +import Footer from '../../components/footer'; +import Language from '../../components/language'; +import Item from './featureItem'; +import homeConfig from '../../../site_config/home'; +import './index.scss'; + +class Home extends Language { + + constructor(props) { + super(props); + this.state = { + headerType: 'primary', + }; + } + + componentDidMount() { + window.addEventListener('scroll', () => { + const scrollTop = getScrollTop(); + if (scrollTop > 66) { + this.setState({ + headerType: 'normal', + }); + } else { + this.setState({ + headerType: 'primary', + }); + } + }); + } + + render() { + const language = this.getLanguage(); + const dataSource = homeConfig[language]; + const { headerType } = this.state; + const headerLogo = headerType === 'primary' ? '/img/logo_white.png' : '/img/logo.png'; + return ( +
    +
    +
    +
    +
    +

    {dataSource.brand.brandName}

    +
    +

    {dataSource.brand.briefIntroduction}

    +
    + { + dataSource.brand.buttons.map(b => ) + } +
    +
    +
    +
    +
    +
    +
    +
    +
    + { + dataSource.introductions.map((item, i) => ( +
    +
    +

    {item.title}

    +

    {item.desc}

    +
    + +
    + ))} +
    +
    +

    {dataSource.features.title}

    +
      + { + dataSource.features.list.map((feature, i) => ( + + )) + } +
    +
    +
    +
    + ); + } +} + +document.getElementById('root') && ReactDOM.render(, document.getElementById('root')); + +export default Home; diff --git a/sop-website/sop-portal/src/pages/home/index.scss b/sop-website/sop-portal/src/pages/home/index.scss new file mode 100644 index 00000000..6ecbcda6 --- /dev/null +++ b/sop-website/sop-portal/src/pages/home/index.scss @@ -0,0 +1,472 @@ +@import '../../variables.scss'; +@import '../../reset.scss'; + +$animation1Height: 500px; +$animation2Height: 375px; +$animation3Height: 248px; +$animation4Height: 570px; +$animation5Height: 460px; + +@keyframes fallingStar1 { + 0% { + transform: translate3d(0, -100%, 0); + opacity: 1; + } + + 100% { + transform: translate3d(0, $animation1Height, 0); + opacity: 0; + } +} + +@keyframes fallingStar2 { + 0% { + transform: translate3d(0, -100%, 0); + opacity: 1; + } + + 100% { + transform: translate3d(0, $animation2Height, 0); + opacity: 0; + } +} + +@keyframes fallingStar3 { + 0% { + transform: translate3d(0, -100%, 0); + opacity: 1; + } + + 100% { + transform: translate3d(0, $animation3Height, 0); + opacity: 0; + } +} + +@keyframes fallingStar4 { + 0% { + transform: translate3d(0, -100%, 0); + opacity: 1; + } + + 100% { + transform: translate3d(0, $animation4Height, 0); + opacity: 0; + } +} + +@keyframes fallingStar5 { + 0% { + transform: translate3d(0, -100%, 0); + opacity: 1; + } + + 100% { + transform: translate3d(0, $animation5Height ,0); + opacity: 0; + } +} + +.home-page { + .top-section { + position: relative; + height: 720px; + background-image: linear-gradient(0deg, $startColor 0%, $intermediateColor 51%, $endColor 100%); + .animation { + position: absolute; + top: 0; + border-right: 1px solid rgba(255, 255, 255, 0.3); + transform: scaleX(0.5); + &::before { + content: ''; + height: 16px; + opacity: 1; + border-right: 4px solid #fff; + position: absolute; + left: -2px; + transform: translate3d(0,-100%,0); + } + &1 { + left: 15%; + height: $animation1Height; + &::before { + animation: fallingStar1 1.3s infinite ease-in-out; + } + } + &2 { + left: 35%; + height: $animation2Height; + &::before { + animation: fallingStar2 1.5s infinite ease-in-out; + } + } + &3 { + left: 52%; + height: $animation3Height; + &::before { + animation: fallingStar3 2s infinite ease-in-out; + } + } + &4 { + left: 65%; + height: $animation4Height; + &::before { + animation: fallingStar4 1.5s infinite ease-in-out; + } + } + &5 { + left: 85%; + height: $animation5Height; + &::before { + animation: fallingStar5 1.3s infinite ease-in-out; + } + } + } + .vertical-middle { + position: absolute; + left: 0; + top: 50%; + transform: translateY(-50%); + box-sizing: border-box; + width: 100%; + text-align: center; + padding: 0 20px; + } + .product-name { + position: relative; + display: inline-block; + h2 { + font-family: Avenir-Heavy; + font-size: 46px; + color: #FFF; + text-align: center; + word-break: break-word; + margin: 0; + } + } + .product-desc { + opacity: 0.6; + font-family: Avenir-Medium; + font-size: 24px; + color: #FFF; + text-align: center; + margin: 12px auto 0; + max-width: 730px; + } + .button-area { + text-align: center; + margin-top: 40px; + .button { + margin-right: 20px; + } + .button:last-child { + margin-right: 0; + } + } + } + .introduction-section { + background: #F9FAFA; + .introduction-body { + max-width: $contentWidth; + box-sizing: border-box; + margin: 0 auto; + min-height: 640px; + padding: 0 40px; + position: relative; + display: flex; + flex-wrap: wrap; + align-items: center; + justify-content: space-between; + &::before { + content: ''; + position: absolute; + top: 0; + left: 40px; + height: 165px; + opacity: 0.3; + border-right: 1px solid #666; + } + &::after { + content: ''; + position: absolute; + left: 39px; + top: 165px; + width: 3px; + height: 17px; + background-image: linear-gradient(0deg, $startColor 0%, $intermediateColor 51%, $endColor 100%); + } + .introduction { + display: inline-block; + width: calc(100% - 726px); + min-width: 300px; + max-width: 590px; + h3 { + font-family: Avenir-Heavy; + font-size: 36px; + color: #333; + margin-bottom: 20px; + word-break: break-word; + margin-left: 10px; + margin-top: 40px; + } + p { + opacity: 0.56; + font-family: Avenir-Medium; + font-size: 18px; + color: #999; + margin-left: 20px; + } + } + img { + //width: 562px; + //margin: 0 82px; + margin: 20px 20px; + max-width: 100%; + box-shadow: 0 8px 16px 0 rgba(0, 0, 0, 0.05); + } + @media screen and (max-width: 1106px){ + .introduction { + display: inline-block; + width: 100%; + max-width: 100%; + } + img { + margin: 0; + } + } + } + } + .feature-section { + box-sizing: border-box; + max-width: $contentWidth; + margin: 0 auto; + position: relative; + padding: 80px 40px 40px; + &::before { + content: ''; + position: absolute; + top: 0; + left: 50%; + transform: translateX(-50%); + margin: 0 auto; + height: 83px; + opacity: 0.3; + border-right: 1px solid #666; + } + &::after { + content: ''; + position: absolute; + top: 66px; + left: 50%; + transform: translateX(-50%); + width: 3px; + height: 17px; + background-image: linear-gradient(0deg, $startColor 0%, $intermediateColor 51%, $endColor 100%); + } + h3 { + font-family: Avenir-Heavy; + font-size: 36px; + color: #333; + text-align: center; + margin: 0 0 40px; + } + ul { + list-style: none; + padding: 0; + margin: 0; + li { + vertical-align: top; + display: inline-block; + margin-bottom: 40px; + width: 50%; + img { + vertical-align: top; + width: 60px; + height: 60px; + margin-right: 20px; + } + div { + display: inline-block; + width: 80%; + h4 { + font-family: Avenir-Heavy; + font-size: 20px; + color: #333; + margin-top: 10px; + } + p { + font-family: Avenir-Medium; + font-size: 14px; + line-height: 20px; + color: #999; + } + } + } + @media screen and (max-width: 768px){ + li { + width: 100%; + } + } + } + } + .start-section { + background-image: linear-gradient(0deg, $startColor 0%, $intermediateColor 51%, $endColor 100%); + .start-body { + max-width: $contentWidth; + margin: 0 auto; + box-sizing: border-box; + height: 260px; + padding: 35px 40px; + position: relative; + &::before { + content: ''; + position: absolute; + top: 0; + left: 20px; + height: 100%; + opacity: 0.3; + border-right: 1px solid #fff; + } + &::after { + content: ''; + position: absolute; + left: 19px; + top: 48px; + width: 3px; + height: 17px; + background: #fff; + } + .left-part { + display: inline-block; + width: 50%; + vertical-align: top; + h3 { + font-family: Avenir-Heavy; + font-size: 36px; + color: #FFF; + margin: 0; + } + p { + opacity: 0.8; + font-family: Avenir-Medium; + font-size: 18px; + color: #FFF; + line-height: 24px; + margin: 6px 0 12px; + } + a { + font-family: Avenir-Heavy; + font-size: 14px; + color: $brandColor; + text-align: center; + display: inline-block; + width: 140px; + height: 48px; + line-height: 48px; + background: #FFF; + border-radius: 4px; + } + } + .right-part { + display: inline-block; + width: 50%; + font-size: 0; + margin-top: 15px; + img { + margin-left: 5%; + width: 500px; + } + } + @media screen and (max-width: 1050px){ + & { + height: 474px; + } + .left-part { + width: 100%; + } + .right-part { + width: 100%; + margin-top: 38px; + img { + max-width: 100%; + margin-left: 0; + } + } + } + } + } + .users-section { + box-sizing: border-box; + max-width: $contentWidth; + margin: 0 auto; + padding: 80px 40px 40px; + position: relative; + &::before { + content: ''; + position: absolute; + top: 0; + left: 50%; + transform: translateX(-50%); + margin: 0 auto; + height: 83px; + opacity: 0.3; + border-right: 1px solid #666; + } + &::after { + content: ''; + position: absolute; + top: 66px; + left: 50%; + transform: translateX(-50%); + width: 3px; + height: 17px; + background-image: linear-gradient(0deg, $startColor 0%, $intermediateColor 51%, $endColor 100%); + } + h3 { + font-family: Avenir-Heavy; + font-size: 36px; + color: #333; + text-align: center; + margin-bottom: 40px; + } + p { + font-family: Avenir-Medium; + font-size: 14px; + color: #666; + text-align: center; + margin: 0 0 40px; + a { + color: #1e6bb8; + text-decoration: none; + } + } + .users { + // display: flex; + // flex-wrap: wrap; + // justify-content: space-between; + display: inline-block; + text-align: center; + img { + margin-right: 10px; + width: 140px; + height: 80px; + margin-bottom: 40px; + } + } + } + @media screen and (max-width: $mobileWidth) { + .introduction-section { + padding: 0 20px; + &::before { + left: 20px; + } + &::after { + left: 19px; + } + } + .feature-section, .users-section { + padding-left: 20px; + padding-right: 20px; + } + } +} diff --git a/sop-website/sop-portal/src/reset.scss b/sop-website/sop-portal/src/reset.scss new file mode 100644 index 00000000..f5c3568b --- /dev/null +++ b/sop-website/sop-portal/src/reset.scss @@ -0,0 +1,12 @@ +@import './markdown.scss'; + +* { + padding: 0; + margin: 0; +} +a { + text-decoration: none; +} +h1, h2, h3, h4, h5, h6 { + font-weight: 400; +} diff --git a/sop-website/sop-portal/src/variables.scss b/sop-website/sop-portal/src/variables.scss new file mode 100644 index 00000000..09e19d22 --- /dev/null +++ b/sop-website/sop-portal/src/variables.scss @@ -0,0 +1,24 @@ +// 品牌色 +$brandColor: #2DACEC; +// 渐变起始色 +$startColor: #03DDE4; +// 渐变中间色 +$intermediateColor: #30AFED; +// 渐变结束色 +$endColor: #8755FF; +// 侧边菜单背景色 +$sideMenuBgColor: #f8f8f8; +// 顶部悬浮菜单背景色,用于移动端 +$headerMenuBgColor: #f8f8f8; + + +// 头部高度 +$headerHeight: 66px; +// 侧边栏宽度 +$sideMenuWidth: 295px; +// bar高度 +$barHeight: 200px; +// 切换到移动端显示屏宽界限 +$mobileWidth: 640px; +// 页面主体最大宽度 +$contentWidth: 1280px; \ No newline at end of file diff --git a/sop-website/sop-portal/template.ejs b/sop-website/sop-portal/template.ejs new file mode 100644 index 00000000..9c26eb48 --- /dev/null +++ b/sop-website/sop-portal/template.ejs @@ -0,0 +1,23 @@ + + + + + + + + + + <%= title %> + + + + +
    <%- __html %>
    + + + + + + diff --git a/sop-website/sop-portal/utils/index.js b/sop-website/sop-portal/utils/index.js new file mode 100644 index 00000000..8a964069 --- /dev/null +++ b/sop-website/sop-portal/utils/index.js @@ -0,0 +1,36 @@ +/* eslint-disable import/prefer-default-export */ +export const throttle = (fn, delay) => { + let timer = null; + return function(...args) { + const context = this; + clearTimeout(timer); + timer = setTimeout(() => { + fn.apply(context, args); + }, delay); + }; +}; + +export const getScrollTop = () => { + let scrollTop = 0; + if (document.documentElement && document.documentElement.scrollTop) { + scrollTop = document.documentElement.scrollTop; + } else if (document.body) { + scrollTop = document.body.scrollTop; + } + return scrollTop; +}; + +export const getLink = (link) => { + if (`${link}`.length > 1 && /^\/[^/]/.test(`${link}`)) { + return `${window.rootPath}${link}`; + } + return link; +}; + +export const parseJSONStr = (str) => { + try { + return JSON.parse(str); + } catch (err) { + return str; + } +} diff --git a/sop-website/sop-portal/webpack.config.js b/sop-website/sop-portal/webpack.config.js new file mode 100644 index 00000000..3ac6c989 --- /dev/null +++ b/sop-website/sop-portal/webpack.config.js @@ -0,0 +1,55 @@ +const path = require('path'); +const fs = require('fs'); +const webpack = require('webpack'); +const ExtractTextPlugin = require('extract-text-webpack-plugin'); + +const entry = {}; +const targetPath = path.join(__dirname, './src/pages'); +fs.readdirSync(targetPath).forEach(page => { + if ( + fs.statSync(path.join(targetPath, page)).isDirectory() && + fs.existsSync(path.join(targetPath, page, 'index.jsx')) + ) { + entry[page] = path.join(targetPath, page, 'index.jsx'); + } +}); +module.exports = { + entry, + output: { + path: path.join(__dirname, 'build'), + filename: '[name].js', + }, + externals: { + react: 'React', + 'react-dom': 'ReactDOM', + }, + module: { + loaders: [ + { + test: /\.js|jsx$/, + exclude: [/node_modules/, /build\/lib/, /\.min\.js$/], + use: 'babel-loader', + }, + { + test: /\.(s)?css$/, + use: ExtractTextPlugin.extract({ + fallback: 'style-loader', + use: ['css-loader', 'sass-loader'], + }), + }, + { + test: /\.json?$/, + exclude: /node_modules/, + use: 'json-loader', + }, + ], + }, + resolve: { + extensions: ['.js', '.jsx', '.json'], + }, + plugins: [ + new webpack.NoEmitOnErrorsPlugin(), + new webpack.optimize.OccurrenceOrderPlugin(), + new ExtractTextPlugin('[name].css'), + ], +}; diff --git a/sop-website/sop-portal/zh-cn/blog/blog1.html b/sop-website/sop-portal/zh-cn/blog/blog1.html new file mode 100644 index 00000000..8fb0b850 --- /dev/null +++ b/sop-website/sop-portal/zh-cn/blog/blog1.html @@ -0,0 +1,26 @@ + + + + + + + + + + blog1 + + + + +

    博客1

    +

    支持元数据的解析,---(至少三个-)开头之间的数据按照key:value的形式,最终会被解析到md_json/blog.json中,其中filename__html为保留字段,请勿使用。

    +

    博客内容填充,博客内容填充博客内容填充,博客内容填充博客内容填充,博客内容填充博客内容填充,博客内容填充博客内容填充,博客内容填充博客内容填充,博客内容填充博客内容填充,博客内容填充博客内容填充,博客内容填充博客内容填充,博客内容填充博客内容填充,博客内容填充博客内容填充,博客内容填充博客内容填充,博客内容填充博客内容填充,博客内容填充博客内容填充,博客内容填充博客内容填充,博客内容填充博客内容填充,博客内容填充博客内容填充,博客内容填充博客内容填充,博客内容填充博客内容填充,博客内容填充博客内容填充,博客内容填充博客内容填充,博客内容填充博客内容填充,博客内容填充博客内容填充,博客内容填充博客内容填充,博客内容填充博客内容填充,博客内容填充博客内容填充,博客内容填充博客内容填充,博客内容填充博客内容填充,博客内容填充博客内容填充,博客内容填充博客内容填充,博客内容填充博客内容填充,博客内容填充博客内容填充,博客内容填充博客内容填充,博客内容填充博客内容填充,博客内容填充博客内容填充,博客内容填充博客内容填充,博客内容填充博客内容填充,博客内容填充博客内容填充,博客内容填充博客内容填充,博客内容填充博客内容填充,博客内容填充博客内容填充,博客内容填充博客内容填充,博客内容填充博客内容填充,博客内容填充博客内容填充,博客内容填充博客内容填充,博客内容填充博客内容填充,博客内容填充博客内容填充,博客内容填充博客内容填充,博客内容填充博客内容填充,博客内容填充博客内容填充,博客内容填充博客内容填充,博客内容填充博客内容填充,博客内容填充博客内容填充,博客内容填充博客内容填充,博客内容填充博客内容填充,博客内容填充博客内容填充,博客内容填充博客内容填充,博客内容填充博客内容填充,博客内容填充博客内容填充,博客内容填充博客内容填充,博客内容填充博客内容填充,博客内容填充博客内容填充,博客内容填充博客内容填充,博客内容填充博客内容填充,博客内容填充博客内容填充,博客内容填充博客内容填充,博客内容填充博客内容填充,博客内容填充博客内容填充,博客内容填充博客内容填充,博客内容填充博客内容填充,博客内容填充博客内容填充,博客内容填充博客内容填充,博客内容填充博客内容填充,博客内容填充博客内容填充,博客内容填充博客内容填充,博客内容填充

    +
    + + + + + + diff --git a/sop-website/sop-portal/zh-cn/blog/blog1.json b/sop-website/sop-portal/zh-cn/blog/blog1.json new file mode 100644 index 00000000..2f15a275 --- /dev/null +++ b/sop-website/sop-portal/zh-cn/blog/blog1.json @@ -0,0 +1,9 @@ +{ + "filename": "blog1.md", + "__html": "

    博客1

    \n

    支持元数据的解析,---(至少三个-)开头之间的数据按照key:value的形式,最终会被解析到md_json/blog.json中,其中filename__html为保留字段,请勿使用。

    \n

    博客内容填充,博客内容填充博客内容填充,博客内容填充博客内容填充,博客内容填充博客内容填充,博客内容填充博客内容填充,博客内容填充博客内容填充,博客内容填充博客内容填充,博客内容填充博客内容填充,博客内容填充博客内容填充,博客内容填充博客内容填充,博客内容填充博客内容填充,博客内容填充博客内容填充,博客内容填充博客内容填充,博客内容填充博客内容填充,博客内容填充博客内容填充,博客内容填充博客内容填充,博客内容填充博客内容填充,博客内容填充博客内容填充,博客内容填充博客内容填充,博客内容填充博客内容填充,博客内容填充博客内容填充,博客内容填充博客内容填充,博客内容填充博客内容填充,博客内容填充博客内容填充,博客内容填充博客内容填充,博客内容填充博客内容填充,博客内容填充博客内容填充,博客内容填充博客内容填充,博客内容填充博客内容填充,博客内容填充博客内容填充,博客内容填充博客内容填充,博客内容填充博客内容填充,博客内容填充博客内容填充,博客内容填充博客内容填充,博客内容填充博客内容填充,博客内容填充博客内容填充,博客内容填充博客内容填充,博客内容填充博客内容填充,博客内容填充博客内容填充,博客内容填充博客内容填充,博客内容填充博客内容填充,博客内容填充博客内容填充,博客内容填充博客内容填充,博客内容填充博客内容填充,博客内容填充博客内容填充,博客内容填充博客内容填充,博客内容填充博客内容填充,博客内容填充博客内容填充,博客内容填充博客内容填充,博客内容填充博客内容填充,博客内容填充博客内容填充,博客内容填充博客内容填充,博客内容填充博客内容填充,博客内容填充博客内容填充,博客内容填充博客内容填充,博客内容填充博客内容填充,博客内容填充博客内容填充,博客内容填充博客内容填充,博客内容填充博客内容填充,博客内容填充博客内容填充,博客内容填充博客内容填充,博客内容填充博客内容填充,博客内容填充博客内容填充,博客内容填充博客内容填充,博客内容填充博客内容填充,博客内容填充博客内容填充,博客内容填充博客内容填充,博客内容填充博客内容填充,博客内容填充博客内容填充,博客内容填充博客内容填充,博客内容填充博客内容填充,博客内容填充博客内容填充,博客内容填充博客内容填充,博客内容填充博客内容填充,博客内容填充博客内容填充,博客内容填充

    \n", + "link": "/zh-cn/blog/blog1.html", + "meta": { + "key1": "hello", + "key2": "world" + } +} \ No newline at end of file diff --git a/sop-website/sop-portal/zh-cn/blog/index.html b/sop-website/sop-portal/zh-cn/blog/index.html new file mode 100644 index 00000000..39ea8695 --- /dev/null +++ b/sop-website/sop-portal/zh-cn/blog/index.html @@ -0,0 +1,23 @@ + + + + + + + + + + 网页标签title + + + + + + + + + + + diff --git a/sop-website/sop-portal/zh-cn/community/index.html b/sop-website/sop-portal/zh-cn/community/index.html new file mode 100644 index 00000000..9b4694c2 --- /dev/null +++ b/sop-website/sop-portal/zh-cn/community/index.html @@ -0,0 +1,23 @@ + + + + + + + + + + 网页标签title + + + + +
    社区

    事件 & 新闻

    这是标题

    这是内容

    May 12nd,2018

    这是标题

    这是内容

    May 12nd,2018

    这是标题

    这是内容

    May 12nd,2018

    这是标题

    这是内容

    May 12nd,2018

    这是标题

    这是内容

    May 12nd,2018

    这是标题

    这是内容

    May 12nd,2018

    这是标题

    这是内容

    May 12nd,2018

    生态系统

    生态系统1

    生态系统1

    生态系统2

    生态系统2

    联系我们

    有问题需要反馈?请通过一下方式联系我们。

    贡献指南

    一些描述

    邮件列表

    这是描述

    报告缺陷

    这是描述

    文档

    这是描述

    Pull Request

    这是描述

    + + + + + + diff --git a/sop-website/sop-portal/zh-cn/docs/demo1.html b/sop-website/sop-portal/zh-cn/docs/demo1.html new file mode 100644 index 00000000..16c64385 --- /dev/null +++ b/sop-website/sop-portal/zh-cn/docs/demo1.html @@ -0,0 +1,426 @@ + + + + + + + + + + demo1 + + + + +
    文档

    脚注的使用

    +

    这是一段示例文字,展示脚注的使用。 +这是脚注1 [1]。点击脚注滚动至对应脚注处。

    +

    下面是一大段文字,填充开始。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。 +这是填充文字。

    +
    +
    +
      +
    1. 这是脚注的说明 ↩︎

      +
    2. +
    +
    +
    + + + + + + diff --git a/sop-website/sop-portal/zh-cn/docs/demo1.json b/sop-website/sop-portal/zh-cn/docs/demo1.json new file mode 100644 index 00000000..bb8f579f --- /dev/null +++ b/sop-website/sop-portal/zh-cn/docs/demo1.json @@ -0,0 +1,6 @@ +{ + "filename": "demo1.md", + "__html": "

    脚注的使用

    \n

    这是一段示例文字,展示脚注的使用。\n这是脚注1 [1]。点击脚注滚动至对应脚注处。

    \n

    下面是一大段文字,填充开始。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。\n这是填充文字。

    \n
    \n
    \n
      \n
    1. 这是脚注的说明 ↩︎

      \n
    2. \n
    \n
    \n", + "link": "/zh-cn/docs/demo1.html", + "meta": {} +} \ No newline at end of file diff --git a/sop-website/sop-portal/zh-cn/docs/demo2.html b/sop-website/sop-portal/zh-cn/docs/demo2.html new file mode 100644 index 00000000..1988be87 --- /dev/null +++ b/sop-website/sop-portal/zh-cn/docs/demo2.html @@ -0,0 +1,36 @@ + + + + + + + + + + demo2 + + + + +
    文档

    文档之间跳转、图片引用的示例

    +

    文档之间的跳转链接按照文件目录之间的相对关系配置即可,推荐在同一语言目录下的不同文件夹间跳转,以缩短配置路径。 +点击跳转: demo1.md

    +

    图片的引用,支持绝对引用和相对引用,如果是相对引用,同文档之间的相互引用一样,是按照文件之间的相对路径进行处理的。比如 +

    +

    数学公式的使用(katex)

    +

    E=mc2E = mc^2

    +

    x=a1n+a2n+a3nx = a_{1}^n + a_{2}^n + a_{3}^n +

    +

    X3\sqrt[3]{X} +

    +

    5x\sqrt{5 - x} +

    +
    + + + + + + diff --git a/sop-website/sop-portal/zh-cn/docs/demo2.json b/sop-website/sop-portal/zh-cn/docs/demo2.json new file mode 100644 index 00000000..beb267d2 --- /dev/null +++ b/sop-website/sop-portal/zh-cn/docs/demo2.json @@ -0,0 +1,6 @@ +{ + "filename": "demo2.md", + "__html": "

    文档之间跳转、图片引用的示例

    \n

    文档之间的跳转链接按照文件目录之间的相对关系配置即可,推荐在同一语言目录下的不同文件夹间跳转,以缩短配置路径。\n点击跳转: demo1.md

    \n

    图片的引用,支持绝对引用和相对引用,如果是相对引用,同文档之间的相互引用一样,是按照文件之间的相对路径进行处理的。比如\n\"\"

    \n

    数学公式的使用(katex)

    \n

    E=mc2E = mc^2E=mc2

    \n

    x=a1n+a2n+a3nx = a_{1}^n + a_{2}^n + a_{3}^n\nx=a1n+a2n+a3n

    \n

    X3\\sqrt[3]{X}\n3X

    \n

    5x\\sqrt{5 - x}\n5x

    \n", + "link": "/zh-cn/docs/demo2.html", + "meta": {} +} \ No newline at end of file diff --git a/sop-website/sop-portal/zh-cn/docs/dir/demo3.html b/sop-website/sop-portal/zh-cn/docs/dir/demo3.html new file mode 100644 index 00000000..27cc4945 --- /dev/null +++ b/sop-website/sop-portal/zh-cn/docs/dir/demo3.html @@ -0,0 +1,29 @@ + + + + + + + + + + demo3 + + + + +
    文档

    跨目录文档之间跳转、图片引用的示例

    +

    这是一段文字,将要跳转到demo1。 +文档之间的跳转链接按照文件目录之间的相对关系配置即可,推荐在同一语言目录下的不同文件夹间跳转,以缩短配置路径。 +点击跳转: demo1.md

    +

    图片的引用,支持绝对引用和相对引用,如果是相对引用,同文档之间的相互引用一样,是按照文件之间的相对路径进行处理的。比如 +

    +
    + + + + + + diff --git a/sop-website/sop-portal/zh-cn/docs/dir/demo3.json b/sop-website/sop-portal/zh-cn/docs/dir/demo3.json new file mode 100644 index 00000000..748f6bd8 --- /dev/null +++ b/sop-website/sop-portal/zh-cn/docs/dir/demo3.json @@ -0,0 +1,6 @@ +{ + "filename": "demo3.md", + "__html": "

    跨目录文档之间跳转、图片引用的示例

    \n

    这是一段文字,将要跳转到demo1。\n文档之间的跳转链接按照文件目录之间的相对关系配置即可,推荐在同一语言目录下的不同文件夹间跳转,以缩短配置路径。\n点击跳转: demo1.md

    \n

    图片的引用,支持绝对引用和相对引用,如果是相对引用,同文档之间的相互引用一样,是按照文件之间的相对路径进行处理的。比如\n\"\"

    \n", + "link": "/zh-cn/docs/dir/demo3.html", + "meta": {} +} \ No newline at end of file diff --git a/sop-website/sop-portal/zh-cn/index.html b/sop-website/sop-portal/zh-cn/index.html new file mode 100644 index 00000000..83c429cf --- /dev/null +++ b/sop-website/sop-portal/zh-cn/index.html @@ -0,0 +1,23 @@ + + + + + + + + + + XX开放平台 + + + + +

    开放平台

    XX开放平台,一句话介绍。。。

    介绍部分1

    介绍文字。。。

    介绍部分2

    介绍文字。。。

    特性一览

    • 特性1

      描述文字。。

    • 特性2

      描述文字。。

    + + + + + + diff --git a/sop-website/.gitignore b/sop-website/sop-website-server/.gitignore similarity index 100% rename from sop-website/.gitignore rename to sop-website/sop-website-server/.gitignore diff --git a/sop-website/sop-website-server/pom.xml b/sop-website/sop-website-server/pom.xml new file mode 100644 index 00000000..0b61ac90 --- /dev/null +++ b/sop-website/sop-website-server/pom.xml @@ -0,0 +1,98 @@ + + + + + com.gitee.sop + sop-website + 4.3.2-SNAPSHOT + ../pom.xml + + + 4.0.0 + sop-website-server + + + 1.8 + + + + + + com.gitee.sop + sop-bridge-nacos + + ${project.version} + + + + net.oschina.durcframework + fastmybatis-spring-boot-starter + + + + org.springframework.boot + spring-boot-starter-web + + + + org.apache.commons + commons-lang3 + + + com.squareup.okhttp3 + okhttp + + + + com.alibaba + fastjson + + + org.apache.httpcomponents + httpclient + + + + javax.validation + validation-api + + + org.hibernate.validator + hibernate-validator + + + + mysql + mysql-connector-java + + + + org.projectlombok + lombok + 1.18.4 + provided + + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + diff --git a/sop-website/readme.md b/sop-website/sop-website-server/readme.md similarity index 100% rename from sop-website/readme.md rename to sop-website/sop-website-server/readme.md diff --git a/sop-website/src/main/java/com/gitee/sop/websiteserver/WebsiteServerApplication.java b/sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/WebsiteServerApplication.java similarity index 100% rename from sop-website/src/main/java/com/gitee/sop/websiteserver/WebsiteServerApplication.java rename to sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/WebsiteServerApplication.java diff --git a/sop-website/src/main/java/com/gitee/sop/websiteserver/bean/BizCode.java b/sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/bean/BizCode.java similarity index 100% rename from sop-website/src/main/java/com/gitee/sop/websiteserver/bean/BizCode.java rename to sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/bean/BizCode.java diff --git a/sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/bean/ChannelMsg.java b/sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/bean/ChannelMsg.java new file mode 100644 index 00000000..32e598a9 --- /dev/null +++ b/sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/bean/ChannelMsg.java @@ -0,0 +1,30 @@ +package com.gitee.sop.websiteserver.bean; + +import lombok.Data; +import org.apache.commons.lang3.time.DateFormatUtils; + +import java.util.Date; + +/** + * @author tanghc + */ +@Data +public class ChannelMsg { + + private static final String TIME_PATTERN = "yyyy-MM-dd HH:mm:ss:SSS"; + + public ChannelMsg(ChannelOperation channelOperation, Object data) { + this.operation = channelOperation.getOperation(); + this.data = data; + this.timestamp = DateFormatUtils.format(new Date(), TIME_PATTERN); + } + + private String operation; + private Object data; + + /** + * 加个时间戳,格式yyyy-MM-dd HH:mm:ss:SSS,确保每次推送内容都不一样 + * nacos监听基于MD5值,如果每次推送的内容一样,则监听不会触发,因此必须确保每次推送的MD5不一样 + */ + private String timestamp; +} diff --git a/sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/bean/ChannelOperation.java b/sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/bean/ChannelOperation.java new file mode 100644 index 00000000..90d54c50 --- /dev/null +++ b/sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/bean/ChannelOperation.java @@ -0,0 +1,64 @@ +package com.gitee.sop.websiteserver.bean; + +/** + * @author tanghc + */ +public enum ChannelOperation { + /** + * 限流推送路由配置-修改 + */ + LIMIT_CONFIG_UPDATE("update"), + + /** + * 路由信息更新 + */ + ROUTE_CONFIG_UPDATE("update"), + + /** + * isv信息修改 + */ + ISV_INFO_UPDATE("update"), + + /** + * 黑名单消息类型:添加 + */ + BLACKLIST_ADD("add"), + /** + * 黑名单消息类型:删除 + */ + BLACKLIST_DELETE("delete"), + + /** + * 路由权限配置更新 + */ + ROUTE_PERMISSION_UPDATE("update"), + /** + * 路由权限加载 + */ + ROUTE_PERMISSION_RELOAD("reload"), + + /** + * 灰度发布设置 + */ + GRAY_USER_KEY_SET("set"), + /** + * 灰度发布-开启 + */ + GRAY_USER_KEY_OPEN("open"), + /** + * 灰度发布-关闭 + */ + GRAY_USER_KEY_CLOSE("close"), + + ; + + private String operation; + + ChannelOperation(String operation) { + this.operation = operation; + } + + public String getOperation() { + return operation; + } +} diff --git a/sop-website/src/main/java/com/gitee/sop/websiteserver/bean/DocInfo.java b/sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/bean/DocInfo.java similarity index 100% rename from sop-website/src/main/java/com/gitee/sop/websiteserver/bean/DocInfo.java rename to sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/bean/DocInfo.java diff --git a/sop-website/src/main/java/com/gitee/sop/websiteserver/bean/DocItem.java b/sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/bean/DocItem.java similarity index 98% rename from sop-website/src/main/java/com/gitee/sop/websiteserver/bean/DocItem.java rename to sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/bean/DocItem.java index 929d20ad..a30ea4cb 100644 --- a/sop-website/src/main/java/com/gitee/sop/websiteserver/bean/DocItem.java +++ b/sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/bean/DocItem.java @@ -10,6 +10,7 @@ import java.util.List; */ @Data public class DocItem { + private String id; private String module; private String name; private String version; diff --git a/sop-website/src/main/java/com/gitee/sop/websiteserver/bean/DocModule.java b/sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/bean/DocModule.java similarity index 100% rename from sop-website/src/main/java/com/gitee/sop/websiteserver/bean/DocModule.java rename to sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/bean/DocModule.java diff --git a/sop-website/src/main/java/com/gitee/sop/websiteserver/bean/DocParameter.java b/sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/bean/DocParameter.java similarity index 81% rename from sop-website/src/main/java/com/gitee/sop/websiteserver/bean/DocParameter.java rename to sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/bean/DocParameter.java index 658c36c8..7efe5de8 100644 --- a/sop-website/src/main/java/com/gitee/sop/websiteserver/bean/DocParameter.java +++ b/sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/bean/DocParameter.java @@ -5,6 +5,7 @@ import lombok.Data; import org.apache.commons.lang.StringUtils; import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; /** * 参数 类型 是否必填 最大长度 描述 示例值 @@ -12,6 +13,9 @@ import java.util.List; */ @Data public class DocParameter { + private static final AtomicInteger gen = new AtomicInteger(); + + private Integer id = gen.incrementAndGet(); private String module; private String name; private String type; diff --git a/sop-website/src/main/java/com/gitee/sop/websiteserver/bean/DocParserContext.java b/sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/bean/DocParserContext.java similarity index 100% rename from sop-website/src/main/java/com/gitee/sop/websiteserver/bean/DocParserContext.java rename to sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/bean/DocParserContext.java diff --git a/sop-website/src/main/java/com/gitee/sop/websiteserver/bean/EurekaApplication.java b/sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/bean/EurekaApplication.java similarity index 100% rename from sop-website/src/main/java/com/gitee/sop/websiteserver/bean/EurekaApplication.java rename to sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/bean/EurekaApplication.java diff --git a/sop-website/src/main/java/com/gitee/sop/websiteserver/bean/EurekaApplications.java b/sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/bean/EurekaApplications.java similarity index 100% rename from sop-website/src/main/java/com/gitee/sop/websiteserver/bean/EurekaApplications.java rename to sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/bean/EurekaApplications.java diff --git a/sop-website/src/main/java/com/gitee/sop/websiteserver/bean/EurekaApps.java b/sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/bean/EurekaApps.java similarity index 100% rename from sop-website/src/main/java/com/gitee/sop/websiteserver/bean/EurekaApps.java rename to sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/bean/EurekaApps.java diff --git a/sop-website/src/main/java/com/gitee/sop/websiteserver/bean/EurekaInstance.java b/sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/bean/EurekaInstance.java similarity index 100% rename from sop-website/src/main/java/com/gitee/sop/websiteserver/bean/EurekaInstance.java rename to sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/bean/EurekaInstance.java diff --git a/sop-website/src/main/java/com/gitee/sop/websiteserver/bean/EurekaUri.java b/sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/bean/EurekaUri.java similarity index 93% rename from sop-website/src/main/java/com/gitee/sop/websiteserver/bean/EurekaUri.java rename to sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/bean/EurekaUri.java index 8f4b2d1b..14beaab9 100644 --- a/sop-website/src/main/java/com/gitee/sop/websiteserver/bean/EurekaUri.java +++ b/sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/bean/EurekaUri.java @@ -5,6 +5,7 @@ import okhttp3.Request; import okhttp3.RequestBody; import okhttp3.internal.http.HttpMethod; import org.apache.commons.lang.ArrayUtils; +import org.springframework.util.StringUtils; import org.springframework.web.bind.annotation.RequestMethod; /** @@ -49,13 +50,13 @@ public enum EurekaUri { } public Request getRequest(String url, String... args) { + url = StringUtils.trimTrailingCharacter(url, '/'); String requestUrl = url + getUri(args); - Request request = this.getBuilder() + return this.getBuilder() .url(requestUrl) .addHeader("Content-Type", "application/json") .addHeader("Accept", "application/json") .build(); - return request; } public Request.Builder getBuilder() { diff --git a/sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/bean/GatewayPushDTO.java b/sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/bean/GatewayPushDTO.java new file mode 100644 index 00000000..1582caa1 --- /dev/null +++ b/sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/bean/GatewayPushDTO.java @@ -0,0 +1,22 @@ +package com.gitee.sop.websiteserver.bean; + +import lombok.Data; + +/** + * @author tanghc + */ +@Data +public class GatewayPushDTO { + private String dataId; + private String groupId; + private ChannelMsg channelMsg; + + public GatewayPushDTO() { + } + + public GatewayPushDTO(String dataId, String groupId, ChannelMsg channelMsg) { + this.dataId = dataId; + this.groupId = groupId; + this.channelMsg = channelMsg; + } +} diff --git a/sop-website/src/main/java/com/gitee/sop/websiteserver/bean/HttpTool.java b/sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/bean/HttpTool.java similarity index 94% rename from sop-website/src/main/java/com/gitee/sop/websiteserver/bean/HttpTool.java rename to sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/bean/HttpTool.java index 61623a8b..393979ee 100644 --- a/sop-website/src/main/java/com/gitee/sop/websiteserver/bean/HttpTool.java +++ b/sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/bean/HttpTool.java @@ -198,6 +198,11 @@ public class HttpTool { return paramBuilder.build(); } + public String requestFileString(String url, Map form, Map header, List files) throws IOException { + return requestFile(url, form, header, files).body().string(); + } + + /** * 提交表单,并且上传文件 * @@ -208,7 +213,7 @@ public class HttpTool { * @return * @throws IOException */ - public String requestFile(String url, Map form, Map header, List files) + public Response requestFile(String url, Map form, Map header, List files) throws IOException { // 创建MultipartBody.Builder,用于添加请求的数据 MultipartBody.Builder bodyBuilder = new MultipartBody.Builder(); @@ -236,14 +241,20 @@ public class HttpTool { addHeader(builder, header); Request request = builder.build(); - Response response = httpClient.newCall(request).execute(); - try { - return response.body().string(); - } finally { - response.close(); + return httpClient.newCall(request).execute(); + } + + public Response request(String url, Map form, Map header, HTTPMethod method, List files) throws IOException { + if (files != null && files.size() > 0) { + return requestFile(url, form, header, files); + } else { + return requestForResponse(url, form, header, method); } } + + + /** * 请求数据 * diff --git a/sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/bean/IdGen.java b/sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/bean/IdGen.java new file mode 100644 index 00000000..b0d88f26 --- /dev/null +++ b/sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/bean/IdGen.java @@ -0,0 +1,167 @@ +package com.gitee.sop.websiteserver.bean; + +import java.util.UUID; + +public class IdGen { + private static long workId = 0; + private static SnowflakeIdWorker worker = new SnowflakeIdWorker(workId++, 0); + + /** + * 生成唯一id + * @return + */ + public static String nextId() { + return String.valueOf(worker.nextId()); + } + + public static String uuid() { + return UUID.randomUUID().toString().replace("-", ""); + } + + /** + * Twitter_Snowflake
    + * SnowFlake的结构如下(每部分用-分开):
    + * 0 - 0000000000 0000000000 0000000000 0000000000 0 - 00000 - 00000 - 000000000000
    + * 1位标识,由于long基本类型在Java中是带符号的,最高位是符号位,正数是0,负数是1,所以id一般是正数,最高位是0
    + * 41位时间截(毫秒级),注意,41位时间截不是存储当前时间的时间截,而是存储时间截的差值(当前时间截 - 开始时间截) + * 得到的值),这里的的开始时间截,一般是我们的id生成器开始使用的时间,由我们程序来指定的(如下下面程序IdWorker类的startTime属性)。41位的时间截,可以使用69年,年T = (1L << 41) / (1000L * 60 * 60 * 24 * 365) = 69
    + * 10位的数据机器位,可以部署在1024个节点,包括5位datacenterId和5位workerId
    + * 12位序列,毫秒内的计数,12位的计数顺序号支持每个节点每毫秒(同一机器,同一时间截)产生4096个ID序号
    + * 加起来刚好64位,为一个Long型。
    + * SnowFlake的优点是,整体上按照时间自增排序,并且整个分布式系统内不会产生ID碰撞(由数据中心ID和机器ID作区分),并且效率较高,经测试,SnowFlake每秒能够产生26万ID左右。 + */ + public static class SnowflakeIdWorker { + + // ==============================Fields=========================================== + /** 开始时间截 (2015-01-01) */ + private final long twepoch = 1420041100000L; + + /** 机器id所占的位数 */ + private final long workerIdBits = 5L; + + /** 数据标识id所占的位数 */ + private final long datacenterIdBits = 5L; + + /** 支持的最大机器id,结果是31 (这个移位算法可以很快的计算出几位二进制数所能表示的最大十进制数) */ + private final long maxWorkerId = -1L ^ (-1L << workerIdBits); + + /** 支持的最大数据标识id,结果是31 */ + private final long maxDatacenterId = -1L ^ (-1L << datacenterIdBits); + + /** 序列在id中占的位数 */ + private final long sequenceBits = 12L; + + /** 机器ID向左移12位 */ + private final long workerIdShift = sequenceBits; + + /** 数据标识id向左移17位(12+5) */ + private final long datacenterIdShift = sequenceBits + workerIdBits; + + /** 时间截向左移22位(5+5+12) */ + private final long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits; + + /** 生成序列的掩码,这里为4095 (0b111111111111=0xfff=4095) */ + private final long sequenceMask = -1L ^ (-1L << sequenceBits); + + /** 工作机器ID(0~31) */ + private long workerId; + + /** 数据中心ID(0~31) */ + private long datacenterId; + + /** 毫秒内序列(0~4095) */ + private long sequence = 0L; + + /** 上次生成ID的时间截 */ + private long lastTimestamp = -1L; + + //==============================Constructors===================================== + /** + * 构造函数 + * @param workerId 工作ID (0~31) + * @param datacenterId 数据中心ID (0~31) + */ + public SnowflakeIdWorker(long workerId, long datacenterId) { + if (workerId > maxWorkerId || workerId < 0) { + throw new IllegalArgumentException(String.format("worker Id can't be greater than %d or less than 0", maxWorkerId)); + } + if (datacenterId > maxDatacenterId || datacenterId < 0) { + throw new IllegalArgumentException(String.format("datacenter Id can't be greater than %d or less than 0", maxDatacenterId)); + } + this.workerId = workerId; + this.datacenterId = datacenterId; + } + + // ==============================Methods========================================== + /** + * 获得下一个ID (该方法是线程安全的) + * @return SnowflakeId + */ + public synchronized long nextId() { + long timestamp = timeGen(); + + //如果当前时间小于上一次ID生成的时间戳,说明系统时钟回退过这个时候应当抛出异常 + if (timestamp < lastTimestamp) { + throw new RuntimeException( + String.format("Clock moved backwards. Refusing to generate id for %d milliseconds", lastTimestamp - timestamp)); + } + + //如果是同一时间生成的,则进行毫秒内序列 + if (lastTimestamp == timestamp) { + sequence = (sequence + 1) & sequenceMask; + //毫秒内序列溢出 + if (sequence == 0) { + //阻塞到下一个毫秒,获得新的时间戳 + timestamp = tilNextMillis(lastTimestamp); + } + } + //时间戳改变,毫秒内序列重置 + else { + sequence = 0L; + } + + //上次生成ID的时间截 + lastTimestamp = timestamp; + + //移位并通过或运算拼到一起组成64位的ID + return ((timestamp - twepoch) << timestampLeftShift) + | (datacenterId << datacenterIdShift) + | (workerId << workerIdShift) + | sequence; + } + + /** + * 阻塞到下一个毫秒,直到获得新的时间戳 + * @param lastTimestamp 上次生成ID的时间截 + * @return 当前时间戳 + */ + protected long tilNextMillis(long lastTimestamp) { + long timestamp = timeGen(); + while (timestamp <= lastTimestamp) { + timestamp = timeGen(); + } + return timestamp; + } + + /** + * 返回以毫秒为单位的当前时间 + * @return 当前时间(毫秒) + */ + protected long timeGen() { + return System.currentTimeMillis(); + } + + } + /*//==============================Test============================================= + */ + /** 测试 + public static void main(String[] args) { + SnowflakeIdWorker idWorker = new SnowflakeIdWorker(0, 0); + for (int i = 0; i < 100; i++) { + long id = idWorker.nextId(); + System.out.println("id:" + id); + System.out.println("toBinaryString:" + Long.toBinaryString(id)); + } + } + */ +} diff --git a/sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/bean/IsvDetailDTO.java b/sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/bean/IsvDetailDTO.java new file mode 100644 index 00000000..f1cae6b9 --- /dev/null +++ b/sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/bean/IsvDetailDTO.java @@ -0,0 +1,27 @@ +package com.gitee.sop.websiteserver.bean; + +import lombok.Data; + +/** + * @author tanghc + */ +@Data +public class IsvDetailDTO { + + /** appKey, 数据库字段:app_key */ + private String appKey; + + /** 0启用,1禁用, 数据库字段:status */ + private Byte status; + + /** secret, 数据库字段:secret */ + private String secret; + + /** 开发者生成的公钥, 数据库字段:public_key_isv */ + private String publicKeyIsv; + + /** 平台生成的私钥, 数据库字段:private_key_platform */ + private String privateKeyPlatform; + + private Byte signType; +} diff --git a/sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/bean/IsvKeysGenVO.java b/sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/bean/IsvKeysGenVO.java new file mode 100644 index 00000000..2334f16b --- /dev/null +++ b/sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/bean/IsvKeysGenVO.java @@ -0,0 +1,25 @@ +package com.gitee.sop.websiteserver.bean; + +import lombok.Data; + +/** + * @author tanghc + */ +@Data +public class IsvKeysGenVO { + /** secret, 数据库字段:secret */ + private String secret; + + /** 开发者生成的公钥, 数据库字段:public_key_isv */ + private String publicKeyIsv; + + /** 开发者生成的私钥(交给开发者), 数据库字段:private_key_isv */ + private String privateKeyIsv; + + /** 平台生成的公钥(交给开发者), 数据库字段:public_key_platform */ + private String publicKeyPlatform; + + /** 平台生成的私钥, 数据库字段:private_key_platform */ + private String privateKeyPlatform; + +} diff --git a/sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/bean/LoginUser.java b/sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/bean/LoginUser.java new file mode 100644 index 00000000..ed2642ed --- /dev/null +++ b/sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/bean/LoginUser.java @@ -0,0 +1,16 @@ +package com.gitee.sop.websiteserver.bean; + +import lombok.Data; + +/** + * @author tanghc + */ +@Data +public class LoginUser { + private Long userId; + /** appKey, 数据库字段:app_key */ + private String appKey; + + /** 开发者生成的私钥(交给开发者), 数据库字段:private_key_isv */ + private String privateKeyIsv; +} diff --git a/sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/bean/NoLogin.java b/sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/bean/NoLogin.java new file mode 100644 index 00000000..55c44d35 --- /dev/null +++ b/sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/bean/NoLogin.java @@ -0,0 +1,16 @@ +package com.gitee.sop.websiteserver.bean; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * @author tanghc + */ +@Target({ElementType.TYPE, ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface NoLogin { +} diff --git a/sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/bean/RSATool.java b/sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/bean/RSATool.java new file mode 100644 index 00000000..d1ce8e95 --- /dev/null +++ b/sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/bean/RSATool.java @@ -0,0 +1,417 @@ +package com.gitee.sop.websiteserver.bean; + +import org.apache.commons.codec.binary.Base64; +import org.bouncycastle.asn1.ASN1Encodable; +import org.bouncycastle.asn1.ASN1Primitive; +import org.bouncycastle.asn1.pkcs.PrivateKeyInfo; + +import javax.crypto.Cipher; +import java.security.KeyFactory; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.interfaces.RSAPrivateKey; +import java.security.interfaces.RSAPublicKey; +import java.security.spec.PKCS8EncodedKeySpec; +import java.security.spec.X509EncodedKeySpec; + +/** + * RSA加解密工具
    + * + * @author tanghc + */ +public class RSATool { + public static String RSA_ALGORITHM = "RSA"; + + private KeyFormat keyFormat; + private KeyLength keyLength; + + public RSATool(KeyFormat keyFormat, KeyLength keyLength) { + this.keyFormat = keyFormat; + this.keyLength = keyLength; + } + + /** + * 创建公钥私钥 + * + * @return 返回公私钥对 + * @throws Exception + */ + public KeyStore createKeys() throws Exception { + KeyPairGenerator keyPairGeno = KeyPairGenerator.getInstance(RSA_ALGORITHM); + keyPairGeno.initialize(keyLength.getLength()); + KeyPair keyPair = keyPairGeno.generateKeyPair(); + + RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic(); + RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate(); + + KeyStore keyStore = new KeyStore(); + if (this.keyFormat == KeyFormat.PKCS1) { + keyStore.setPublicKey(Base64.encodeBase64String(publicKey.getEncoded())); + keyStore.setPrivateKey(convertPkcs8ToPkcs1(privateKey.getEncoded())); + } else { + keyStore.setPublicKey(Base64.encodeBase64String(publicKey.getEncoded())); + keyStore.setPrivateKey(Base64.encodeBase64String(privateKey.getEncoded())); + } + return keyStore; + } + + /** + * 获取公钥对象 + * + * @param pubKeyData 公钥 + * @return 返回公钥对象 + * @throws Exception + */ + public RSAPublicKey getPublicKey(byte[] pubKeyData) throws Exception { + X509EncodedKeySpec keySpec = new X509EncodedKeySpec(pubKeyData); + KeyFactory keyFactory = KeyFactory.getInstance(RSA_ALGORITHM); + return (RSAPublicKey) keyFactory.generatePublic(keySpec); + } + + /** + * 获取公钥对象 + * + * @param pubKey 公钥 + * @return 返回私钥对象 + * @throws Exception + */ + public RSAPublicKey getPublicKey(String pubKey) throws Exception { + return getPublicKey(Base64.decodeBase64(pubKey)); + + } + + /** + * 获取私钥对象 + * + * @param priKey 私钥 + * @return 私钥对象 + * @throws Exception + */ + public RSAPrivateKey getPrivateKey(String priKey) throws Exception { + return getPrivateKey(Base64.decodeBase64(priKey)); + } + + /** + * 通过私钥byte[]将公钥还原,适用于RSA算法 + * + * @param keyBytes + * @return 返回私钥 + * @throws Exception + */ + public RSAPrivateKey getPrivateKey(byte[] keyBytes) throws Exception { + PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(keyBytes); + KeyFactory keyFactory = KeyFactory.getInstance(RSA_ALGORITHM); + return (RSAPrivateKey) keyFactory.generatePrivate(keySpec); + + } + + /** + * 公钥加密 + * + * @param data 待加密内容 + * @param publicKey 公钥 + * @return 返回密文 + * @throws Exception + */ + public String encryptByPublicKey(String data, RSAPublicKey publicKey) throws Exception { + Cipher cipher = Cipher.getInstance(keyFormat.getCipherAlgorithm()); + cipher.init(Cipher.ENCRYPT_MODE, publicKey); + // 模长 + int key_len = publicKey.getModulus().bitLength() / 8; + // 加密数据长度 <= 模长-11 + String[] datas = splitString(data, key_len - 11); + String mi = ""; + // 如果明文长度大于模长-11则要分组加密 + for (String s : datas) { + mi += bcd2Str(cipher.doFinal(s.getBytes())); + } + return mi; + } + + public String encryptByPrivateKey(String data, String privateKey) throws Exception { + return encryptByPrivateKey(data, getPrivateKey(privateKey)); + } + + /** + * 私钥加密 + * + * @param data 待加密数据 + * @param privateKey 私钥 + * @return 返回密文 + * @throws Exception + */ + public String encryptByPrivateKey(String data, RSAPrivateKey privateKey) throws Exception { + Cipher cipher = Cipher.getInstance(keyFormat.getCipherAlgorithm()); + cipher.init(Cipher.ENCRYPT_MODE, privateKey); + // 模长 + int key_len = privateKey.getModulus().bitLength() / 8; + // 加密数据长度 <= 模长-11 + String[] datas = splitString(data, key_len - 11); + String mi = ""; + // 如果明文长度大于模长-11则要分组加密 + for (String s : datas) { + mi += bcd2Str(cipher.doFinal(s.getBytes())); + } + return mi; + } + + public String decryptByPrivateKey(String data, String privateKey) throws Exception { + return decryptByPrivateKey(data, getPrivateKey(privateKey)); + } + + /** + * 私钥解密 + * + * @param data 待解密内容 + * @param privateKey 私钥 + * @return 返回明文 + * @throws Exception + */ + public String decryptByPrivateKey(String data, RSAPrivateKey privateKey) throws Exception { + Cipher cipher = Cipher.getInstance(keyFormat.getCipherAlgorithm()); + cipher.init(Cipher.DECRYPT_MODE, privateKey); + // 模长 + int key_len = privateKey.getModulus().bitLength() / 8; + byte[] bytes = data.getBytes(); + byte[] bcd = ASCII_To_BCD(bytes, bytes.length); + // 如果密文长度大于模长则要分组解密 + String ming = ""; + byte[][] arrays = splitArray(bcd, key_len); + for (byte[] arr : arrays) { + ming += new String(cipher.doFinal(arr)); + } + return ming; + } + + /** + * 公钥解密 + * + * @param data 待解密内容 + * @param rsaPublicKey 公钥 + * @return 返回明文 + * @throws Exception + */ + public String decryptByPublicKey(String data, RSAPublicKey rsaPublicKey) throws Exception { + Cipher cipher = Cipher.getInstance(keyFormat.getCipherAlgorithm()); + cipher.init(Cipher.DECRYPT_MODE, rsaPublicKey); + // 模长 + int key_len = rsaPublicKey.getModulus().bitLength() / 8; + byte[] bytes = data.getBytes(); + byte[] bcd = ASCII_To_BCD(bytes, bytes.length); + // 如果密文长度大于模长则要分组解密 + String ming = ""; + byte[][] arrays = splitArray(bcd, key_len); + for (byte[] arr : arrays) { + ming += new String(cipher.doFinal(arr)); + } + return ming; + } + + public static String convertPkcs8ToPkcs1(byte[] privateKeyData) throws Exception{ + PrivateKeyInfo pkInfo = PrivateKeyInfo.getInstance(privateKeyData); + ASN1Encodable encodable = pkInfo.parsePrivateKey(); + ASN1Primitive primitive = encodable.toASN1Primitive(); + byte[] privateKeyPKCS1 = primitive.getEncoded(); + return Base64.encodeBase64String(privateKeyPKCS1); + } + + + /** + * ASCII码转BCD码 + */ + public static byte[] ASCII_To_BCD(byte[] ascii, int asc_len) { + byte[] bcd = new byte[asc_len / 2]; + int j = 0; + for (int i = 0; i < (asc_len + 1) / 2; i++) { + bcd[i] = asc_to_bcd(ascii[j++]); + bcd[i] = (byte) (((j >= asc_len) ? 0x00 : asc_to_bcd(ascii[j++]) & 0xff) + (bcd[i] << 4)); + } + return bcd; + } + + public static byte asc_to_bcd(byte asc) { + byte bcd; + + if ((asc >= '0') && (asc <= '9')) { + bcd = (byte) (asc - '0'); + } else if ((asc >= 'A') && (asc <= 'F')) { + bcd = (byte) (asc - 'A' + 10); + } else if ((asc >= 'a') && (asc <= 'f')) { + bcd = (byte) (asc - 'a' + 10); + } else { + bcd = (byte) (asc - 48); + } + return bcd; + } + + /** + * BCD转字符串 + */ + public String bcd2Str(byte[] bytes) { + char[] temp = new char[bytes.length * 2]; + char val; + + for (int i = 0; i < bytes.length; i++) { + val = (char) (((bytes[i] & 0xf0) >> 4) & 0x0f); + temp[i * 2] = (char) (val > 9 ? val + 'A' - 10 : val + '0'); + + val = (char) (bytes[i] & 0x0f); + temp[i * 2 + 1] = (char) (val > 9 ? val + 'A' - 10 : val + '0'); + } + return new String(temp); + } + + /** + * 拆分字符串 + */ + public String[] splitString(String string, int len) { + int x = string.length() / len; + int y = string.length() % len; + int z = 0; + if (y != 0) { + z = 1; + } + String[] strings = new String[x + z]; + String str = ""; + for (int i = 0; i < x + z; i++) { + if (i == x + z - 1 && y != 0) { + str = string.substring(i * len, i * len + y); + } else { + str = string.substring(i * len, i * len + len); + } + strings[i] = str; + } + return strings; + } + + /** + * 拆分数组 + */ + public byte[][] splitArray(byte[] data, int len) { + int x = data.length / len; + int y = data.length % len; + int z = 0; + if (y != 0) { + z = 1; + } + byte[][] arrays = new byte[x + z][]; + byte[] arr; + for (int i = 0; i < x + z; i++) { + arr = new byte[len]; + if (i == x + z - 1 && y != 0) { + System.arraycopy(data, i * len, arr, 0, y); + } else { + System.arraycopy(data, i * len, arr, 0, len); + } + arrays[i] = arr; + } + return arrays; + } + + public enum KeyLength { + /** + * 秘钥长度:1024 + */ + LENGTH_1024(1024), + /** + * 秘钥长度:2048 + */ + LENGTH_2048(2048); + private int length; + + KeyLength(int length) { + this.length = length; + } + + public int getLength() { + return length; + } + } + + public static class KeyStore { + private String publicKey; + private String privateKey; + + public String getPublicKey() { + return publicKey; + } + + public void setPublicKey(String publicKey) { + this.publicKey = publicKey; + } + + public String getPrivateKey() { + return privateKey; + } + + public void setPrivateKey(String privateKey) { + this.privateKey = privateKey; + } + } + + public enum KeyFormat { + PKCS1("RSA/ECB/PKCS1Padding"), PKCS8("RSA"); + private String cipherAlgorithm; + + KeyFormat(String cipherAlgorithm) { + this.cipherAlgorithm = cipherAlgorithm; + } + + public String getCipherAlgorithm() { + return cipherAlgorithm; + } + } + + public KeyFormat getKeyFormat() { + return keyFormat; + } + + public KeyLength getKeyLength() { + return keyLength; + } + + + + /* ------------ Test ------------ + public static void main(String[] args) throws Exception { + + RSATool rsa_pkcs8_1024 = new RSATool(KeyFormat.PKCS8, KeyLength.LENGTH_1024); + RSATool rsa_pkcs8_2048 = new RSATool(KeyFormat.PKCS8, KeyLength.LENGTH_2048); + + doTest(rsa_pkcs8_1024); + doTest(rsa_pkcs8_2048); + + // PKCS在Java环境无法测试,可以生成一对,到非java平台测试,如C# + } + + private static void doTest(RSATool rsaTool) throws Exception { + System.out.println("秘钥格式:" + rsaTool.keyFormat.name() + ", 秘钥长度:" + rsaTool.keyLength.getLength()); + KeyStore keys = rsaTool.createKeys(); + String pubKey = keys.getPublicKey(); + System.out.println("pubKey:"); + System.out.println(pubKey); + String priKey = keys.getPrivateKey(); + System.out.println("priKey:"); + System.out.println(priKey); + System.out.println("--------"); + + String ming = "你好,abc123~!@="; + // 用公钥加密 + String mi = rsaTool.encryptByPublicKey(ming, rsaTool.getPublicKey(pubKey)); + System.out.println("mi : " + mi); + // 用私钥解密 + String ming2 = rsaTool.decryptByPrivateKey(mi, rsaTool.getPrivateKey(priKey)); + System.out.println("ming : " + ming2 + ", 结果:" + ming2.equals(ming)); + + // 用私钥加密 + String mi2 = rsaTool.encryptByPrivateKey(ming, rsaTool.getPrivateKey(priKey)); + + System.out.println("mi2 : " + mi2); + // 用公钥解密 + String ming3 = rsaTool.decryptByPublicKey(mi2, rsaTool.getPublicKey(pubKey)); + System.out.println("ming3 : " + ming3 + ", 结果:" + ming3.equals(ming)); + System.out.println("---------------------"); + } + */ + +} \ No newline at end of file diff --git a/sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/bean/Result.java b/sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/bean/Result.java new file mode 100644 index 00000000..cdf5bca8 --- /dev/null +++ b/sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/bean/Result.java @@ -0,0 +1,65 @@ +package com.gitee.sop.websiteserver.bean; + +public class Result { + + private static final String CODE_SUCCESS = "0"; + private static final String CODE_ERROR = "100"; + + private static Result ok = new Result(); + static { + ok.setCode(CODE_SUCCESS); + } + + private String code; + private T data; + private String msg; + + public static Result ok() { + return ok; + } + + public static Result ok(T data) { + Result result = new Result(); + result.setCode(CODE_SUCCESS); + result.setData(data); + return result; + } + + public static Result err(String msg) { + Result result = new Result(); + result.setCode(CODE_ERROR); + result.setMsg(msg); + return result; + } + + public static Result err(String code, String msg) { + Result result = new Result(); + result.setCode(code); + result.setMsg(msg); + return result; + } + + public String getCode() { + return code; + } + + public void setCode(String code) { + this.code = code; + } + + public T getData() { + return data; + } + + public void setData(T data) { + this.data = data; + } + + public String getMsg() { + return msg; + } + + public void setMsg(String msg) { + this.msg = msg; + } +} \ No newline at end of file diff --git a/sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/bean/SnowflakeIdWorker.java b/sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/bean/SnowflakeIdWorker.java new file mode 100644 index 00000000..54d96053 --- /dev/null +++ b/sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/bean/SnowflakeIdWorker.java @@ -0,0 +1,170 @@ +package com.gitee.sop.websiteserver.bean; + +/** + * Twitter_Snowflake
    + * SnowFlake的结构如下(每部分用-分开):
    + * 0 - 0000000000 0000000000 0000000000 0000000000 0 - 00000 - 00000 - 000000000000
    + * 1位标识,由于long基本类型在Java中是带符号的,最高位是符号位,正数是0,负数是1,所以id一般是正数,最高位是0
    + * 41位时间截(毫秒级),注意,41位时间截不是存储当前时间的时间截,而是存储时间截的差值(当前时间截 - 开始时间截) + * 得到的值),这里的的开始时间截,一般是我们的id生成器开始使用的时间,由我们程序来指定的(如下下面程序IdWorker类的startTime属性)。41位的时间截,可以使用69年,年T = (1L << 41) / (1000L * 60 * 60 * 24 * 365) = 69
    + * 10位的数据机器位,可以部署在1024个节点,包括5位datacenterId和5位workerId
    + * 12位序列,毫秒内的计数,12位的计数顺序号支持每个节点每毫秒(同一机器,同一时间截)产生4096个ID序号
    + * 加起来刚好64位,为一个Long型。
    + * SnowFlake的优点是,整体上按照时间自增排序,并且整个分布式系统内不会产生ID碰撞(由数据中心ID和机器ID作区分),并且效率较高,经测试,SnowFlake每秒能够产生26万ID左右。 + */ +public class SnowflakeIdWorker { + + // ==============================Fields=========================================== + /** + * 开始时间截 (2020-3-3) + */ + private final long twepoch = 1583164800000L; + + /** + * 机器id所占的位数 + */ + private final long workerIdBits = 5L; + + /** + * 数据标识id所占的位数 + */ + private final long datacenterIdBits = 5L; + + /** + * 支持的最大机器id,结果是31 (这个移位算法可以很快的计算出几位二进制数所能表示的最大十进制数) + */ + private final long maxWorkerId = -1L ^ (-1L << workerIdBits); + + /** + * 支持的最大数据标识id,结果是31 + */ + private final long maxDatacenterId = -1L ^ (-1L << datacenterIdBits); + + /** + * 序列在id中占的位数 + */ + private final long sequenceBits = 12L; + + /** + * 机器ID向左移12位 + */ + private final long workerIdShift = sequenceBits; + + /** + * 数据标识id向左移17位(12+5) + */ + private final long datacenterIdShift = sequenceBits + workerIdBits; + + /** + * 时间截向左移22位(5+5+12) + */ + private final long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits; + + /** + * 生成序列的掩码,这里为4095 (0b111111111111=0xfff=4095) + */ + private final long sequenceMask = -1L ^ (-1L << sequenceBits); + + /** + * 工作机器ID(0~31) + */ + private long workerId; + + /** + * 数据中心ID(0~31) + */ + private long datacenterId; + + /** + * 毫秒内序列(0~4095) + */ + private long sequence = 0L; + + /** + * 上次生成ID的时间截 + */ + private long lastTimestamp = -1L; + + //==============================Constructors===================================== + + /** + * 构造函数 + * + * @param workerId 工作ID (0~31) + * @param datacenterId 数据中心ID (0~31) + */ + public SnowflakeIdWorker(long workerId, long datacenterId) { + if (workerId > maxWorkerId || workerId < 0) { + throw new IllegalArgumentException(String.format("worker Id can't be greater than %d or less than 0", maxWorkerId)); + } + if (datacenterId > maxDatacenterId || datacenterId < 0) { + throw new IllegalArgumentException(String.format("datacenter Id can't be greater than %d or less than 0", maxDatacenterId)); + } + this.workerId = workerId; + this.datacenterId = datacenterId; + } + + // ==============================Methods========================================== + + /** + * 获得下一个ID (该方法是线程安全的) + * + * @return SnowflakeId + */ + public synchronized long nextId() { + long timestamp = timeGen(); + + //如果当前时间小于上一次ID生成的时间戳,说明系统时钟回退过这个时候应当抛出异常 + if (timestamp < lastTimestamp) { + throw new RuntimeException( + String.format("Clock moved backwards. Refusing to generate id for %d milliseconds", lastTimestamp - timestamp)); + } + + //如果是同一时间生成的,则进行毫秒内序列 + if (lastTimestamp == timestamp) { + sequence = (sequence + 1) & sequenceMask; + //毫秒内序列溢出 + if (sequence == 0) { + //阻塞到下一个毫秒,获得新的时间戳 + timestamp = tilNextMillis(lastTimestamp); + } + } + //时间戳改变,毫秒内序列重置 + else { + sequence = 0L; + } + + //上次生成ID的时间截 + lastTimestamp = timestamp; + + //移位并通过或运算拼到一起组成64位的ID + return ((timestamp - twepoch) << timestampLeftShift) + | (datacenterId << datacenterIdShift) + | (workerId << workerIdShift) + | sequence; + } + + /** + * 阻塞到下一个毫秒,直到获得新的时间戳 + * + * @param lastTimestamp 上次生成ID的时间截 + * @return 当前时间戳 + */ + protected long tilNextMillis(long lastTimestamp) { + long timestamp = timeGen(); + while (timestamp <= lastTimestamp) { + timestamp = timeGen(); + } + return timestamp; + } + + /** + * 返回以毫秒为单位的当前时间 + * + * @return 当前时间(毫秒) + */ + protected long timeGen() { + return System.currentTimeMillis(); + } + +} \ No newline at end of file diff --git a/sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/bean/UserContext.java b/sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/bean/UserContext.java new file mode 100644 index 00000000..7443403d --- /dev/null +++ b/sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/bean/UserContext.java @@ -0,0 +1,38 @@ +package com.gitee.sop.websiteserver.bean; + +import com.gitee.sop.gatewaycommon.bean.SpringContext; +import com.gitee.sop.websiteserver.manager.TokenManager; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; + +import javax.servlet.http.HttpServletRequest; + +/** + * @author tanghc + */ +public class UserContext { + + public static final String HEADER_TOKEN = "token"; + + public static LoginUser getLoginUser() { + return getLoginUser(getRequest()); + } + + public static LoginUser getLoginUser(HttpServletRequest request) { + String token = getToken(request); + return SpringContext.getBean(TokenManager.class).getUser(token); + } + + public static String getToken(HttpServletRequest request) { + return request.getHeader(HEADER_TOKEN); + } + + public static String getToken() { + return getToken(getRequest()); + } + + private static HttpServletRequest getRequest() { + return ((ServletRequestAttributes) (RequestContextHolder.currentRequestAttributes())).getRequest(); + } + +} diff --git a/sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/bean/Validates.java b/sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/bean/Validates.java new file mode 100644 index 00000000..722ff7bf --- /dev/null +++ b/sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/bean/Validates.java @@ -0,0 +1,24 @@ +package com.gitee.sop.websiteserver.bean; + +import com.gitee.sop.gatewaycommon.exception.ApiException; +import com.gitee.sop.gatewaycommon.message.ErrorMeta; +import org.apache.commons.lang.StringUtils; + +import java.util.function.Supplier; + +/** + * @author tanghc + */ +public class Validates { + + public static void isTrue(boolean expression, Supplier supplier) { + if (expression) { + throw supplier.get(); + } + } + + public static void isTrue(boolean expression, String msg) { + isTrue(expression, () -> new RuntimeException(msg)); + } + +} diff --git a/sop-website/src/main/java/com/gitee/sop/websiteserver/config/CorsConfig.java b/sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/config/CorsConfig.java similarity index 80% rename from sop-website/src/main/java/com/gitee/sop/websiteserver/config/CorsConfig.java rename to sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/config/CorsConfig.java index be284325..977bde20 100644 --- a/sop-website/src/main/java/com/gitee/sop/websiteserver/config/CorsConfig.java +++ b/sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/config/CorsConfig.java @@ -30,6 +30,10 @@ public class CorsConfig { corsConfiguration.addAllowedOrigin("*"); corsConfiguration.addAllowedHeader("*"); corsConfiguration.addAllowedMethod("*"); + corsConfiguration.addExposedHeader("target-response-headers"); + corsConfiguration.addExposedHeader("sendbox-params"); + corsConfiguration.addExposedHeader("sendbox-beforesign"); + corsConfiguration.addExposedHeader("sendbox-sign"); return corsConfiguration; } diff --git a/sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/config/LoginInterceptor.java b/sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/config/LoginInterceptor.java new file mode 100644 index 00000000..b350579a --- /dev/null +++ b/sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/config/LoginInterceptor.java @@ -0,0 +1,37 @@ +package com.gitee.sop.websiteserver.config; + +import com.gitee.sop.websiteserver.bean.NoLogin; +import com.gitee.sop.websiteserver.bean.UserContext; +import com.gitee.sop.websiteserver.exception.LoginFailureException; +import org.springframework.core.annotation.AnnotationUtils; +import org.springframework.web.method.HandlerMethod; +import org.springframework.web.servlet.handler.HandlerInterceptorAdapter; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +/** + * @author tanghc + */ +public class LoginInterceptor extends HandlerInterceptorAdapter { + + @Override + public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { + if (!request.getServletPath().startsWith("/portal")) { + return true; + } + HandlerMethod handlerMethod = (HandlerMethod) handler; + NoLogin noLogin = handlerMethod.getMethodAnnotation(NoLogin.class); + if (noLogin != null) { + return true; + } + noLogin = AnnotationUtils.findAnnotation(handlerMethod.getBeanType(), NoLogin.class); + if (noLogin != null) { + return true; + } + if (UserContext.getLoginUser(request) == null) { + throw new LoginFailureException(); + } + return true; + } +} diff --git a/sop-website/src/main/java/com/gitee/sop/websiteserver/config/WebsiteConfig.java b/sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/config/WebsiteConfig.java similarity index 58% rename from sop-website/src/main/java/com/gitee/sop/websiteserver/config/WebsiteConfig.java rename to sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/config/WebsiteConfig.java index d84c7fb5..995d9c21 100644 --- a/sop-website/src/main/java/com/gitee/sop/websiteserver/config/WebsiteConfig.java +++ b/sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/config/WebsiteConfig.java @@ -3,39 +3,53 @@ package com.gitee.sop.websiteserver.config; import com.alibaba.fastjson.serializer.SerializerFeature; import com.alibaba.fastjson.support.config.FastJsonConfig; import com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter; +import com.gitee.sop.gatewaycommon.bean.SpringContext; import com.gitee.sop.gatewaycommon.manager.EnvironmentContext; import com.gitee.sop.gatewaycommon.route.RegistryListener; import com.gitee.sop.gatewaycommon.route.ServiceListener; import com.gitee.sop.websiteserver.listener.ServiceDocListener; -import com.gitee.sop.websiteserver.manager.DocManager; +import com.gitee.sop.websiteserver.service.impl.EurekaServerService; +import com.gitee.sop.websiteserver.service.impl.NacosServerService; +import org.springframework.beans.BeansException; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.ApplicationArguments; -import org.springframework.boot.ApplicationRunner; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.autoconfigure.http.HttpMessageConverters; import org.springframework.cloud.client.discovery.event.HeartbeatEvent; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; import org.springframework.context.ApplicationEvent; +import org.springframework.context.EnvironmentAware; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.event.EventListener; import org.springframework.core.env.Environment; - -import javax.annotation.PostConstruct; +import org.springframework.web.servlet.config.annotation.InterceptorRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; /** * @author tanghc */ @Configuration -public class WebsiteConfig implements ApplicationRunner { - - @Autowired - DocManager docManager; +public class WebsiteConfig implements WebMvcConfigurer, EnvironmentAware, ApplicationContextAware { @Autowired private RegistryListener registryListener; - @Autowired - private Environment environment; + @Override + public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { + SpringContext.setApplicationContext(applicationContext); + } + + @Override + public void setEnvironment(Environment environment) { + EnvironmentContext.setEnvironment(environment); + } + + @Override + public void addInterceptors(InterceptorRegistry registry) { + registry.addInterceptor(new LoginInterceptor()); + } /** * 使用fastjson代替jackson @@ -52,6 +66,21 @@ public class WebsiteConfig implements ApplicationRunner { return new HttpMessageConverters(converter); } + @Bean + @ConditionalOnProperty(value = { + "eureka.client.serviceUrl.defaultZone", + "eureka.client.service-url.default-zone" + }) + public EurekaServerService eurekaServerService() { + return new EurekaServerService(); + } + + @Bean + @ConditionalOnProperty("spring.cloud.nacos.discovery.server-addr") + public NacosServerService nacosServerService() { + return new NacosServerService(); + } + /** * nacos事件监听 * @@ -68,16 +97,4 @@ public class WebsiteConfig implements ApplicationRunner { return new ServiceDocListener(); } - /** - * SpringBoot启动完毕执行 - */ - @Override - public void run(ApplicationArguments args) throws Exception { - - } - - @PostConstruct - public void after() { - EnvironmentContext.setEnvironment(environment); - } } diff --git a/sop-website/src/main/java/com/gitee/sop/websiteserver/controller/DocController.java b/sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/controller/DocController.java similarity index 100% rename from sop-website/src/main/java/com/gitee/sop/websiteserver/controller/DocController.java rename to sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/controller/DocController.java diff --git a/sop-website/src/main/java/com/gitee/sop/websiteserver/controller/IndexController.java b/sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/controller/IndexController.java similarity index 61% rename from sop-website/src/main/java/com/gitee/sop/websiteserver/controller/IndexController.java rename to sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/controller/IndexController.java index 05f85092..b12ff77c 100644 --- a/sop-website/src/main/java/com/gitee/sop/websiteserver/controller/IndexController.java +++ b/sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/controller/IndexController.java @@ -1,7 +1,10 @@ package com.gitee.sop.websiteserver.controller; import org.springframework.stereotype.Controller; -import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.GetMapping; + +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; /** * @author tanghc @@ -9,7 +12,7 @@ import org.springframework.web.bind.annotation.RequestMapping; @Controller public class IndexController { - @RequestMapping("/") + @GetMapping("/") public String index() { return "redirect:index.html"; } diff --git a/sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/controller/IsvController.java b/sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/controller/IsvController.java new file mode 100644 index 00000000..61795235 --- /dev/null +++ b/sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/controller/IsvController.java @@ -0,0 +1,215 @@ +package com.gitee.sop.websiteserver.controller; + +import com.gitee.fastmybatis.core.query.Query; +import com.gitee.sop.websiteserver.bean.DocInfo; +import com.gitee.sop.websiteserver.bean.DocItem; +import com.gitee.sop.websiteserver.bean.LoginUser; +import com.gitee.sop.websiteserver.bean.RSATool; +import com.gitee.sop.websiteserver.bean.Result; +import com.gitee.sop.websiteserver.bean.UserContext; +import com.gitee.sop.websiteserver.bean.Validates; +import com.gitee.sop.websiteserver.controller.param.IsvPublicKeyUploadParam; +import com.gitee.sop.websiteserver.controller.param.UpdatePasswordParam; +import com.gitee.sop.websiteserver.controller.result.DocVO; +import com.gitee.sop.websiteserver.controller.result.IsvInfoResult; +import com.gitee.sop.websiteserver.controller.result.IsvPublicKeyUploadResult; +import com.gitee.sop.websiteserver.controller.result.MenuDocItem; +import com.gitee.sop.websiteserver.controller.result.MenuModule; +import com.gitee.sop.websiteserver.controller.result.MenuProject; +import com.gitee.sop.websiteserver.entity.IspResource; +import com.gitee.sop.websiteserver.entity.IsvKeys; +import com.gitee.sop.websiteserver.entity.IsvPortal; +import com.gitee.sop.websiteserver.entity.UserAccount; +import com.gitee.sop.websiteserver.manager.DocManager; +import com.gitee.sop.websiteserver.mapper.IspResourceMapper; +import com.gitee.sop.websiteserver.service.UserService; +import com.gitee.sop.websiteserver.util.GenerateUtil; +import org.apache.commons.lang.BooleanUtils; +import org.apache.commons.lang.StringUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import javax.validation.Valid; +import java.util.Collection; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; + +/** + * @author tanghc + */ +@RestController +@RequestMapping("portal/isv") +public class IsvController { + + @Autowired + private UserService userService; + + @Autowired + DocManager docManager; + + @Autowired + IspResourceMapper ispResourceMapper; + + @Value("${api.url-test}") + String urlTest; + + @Value("${api.url-prod}") + String urlProd; + + @Value("${api.url-sandbox}") + String gatewayUrl; + + @GetMapping("/getIsvPortal") + public Result getIsvInfo() { + LoginUser loginUser = UserContext.getLoginUser(); + IsvInfoResult isvInfoResult = new IsvInfoResult(); + IsvPortal isvPortal = userService.getIsvPortal(loginUser.getUserId()); + boolean hasPublicKey = StringUtils.isNotBlank(isvPortal.getPublicKeyIsv()); + isvInfoResult.setIsUploadPublicKey(BooleanUtils.toInteger(hasPublicKey)); + isvInfoResult.setAppId(isvPortal.getAppId()); + return Result.ok(isvInfoResult); + } + + @GetMapping("/getPublicKey") + public Result getPublicKey() { + LoginUser loginUser = UserContext.getLoginUser(); + IsvKeys isvKeys = userService.getIsvKeys(loginUser.getAppKey()); + IsvPublicKeyUploadResult isvPublicKeyUploadResult = new IsvPublicKeyUploadResult(); + isvPublicKeyUploadResult.setIsUploadPublicKey(BooleanUtils.toInteger(isvKeys != null)); + isvPublicKeyUploadResult.setPublicKeyIsv(isvKeys == null ? "" : isvKeys.getPublicKeyIsv()); + isvPublicKeyUploadResult.setPublicKeyPlatform(isvKeys == null ? "" : isvKeys.getPublicKeyPlatform()); + return Result.ok(isvPublicKeyUploadResult); + } + + /** + * 上传公钥 + * @param param + * @return + */ + @PostMapping("/uploadPublicKey") + public Result uploadPublicKey(@RequestBody @Valid IsvPublicKeyUploadParam param) { + String publicKeyIsv = param.getPublicKeyIsv(); + LoginUser loginUser = UserContext.getLoginUser(); + String appKey = loginUser.getAppKey(); + IsvKeys isvKeys = userService.getIsvKeys(appKey); + RSATool rsaToolIsv = new RSATool(RSATool.KeyFormat.PKCS8, RSATool.KeyLength.LENGTH_2048); + RSATool.KeyStore keyStorePlatform; + try { + // 生成平台公私钥 + keyStorePlatform = rsaToolIsv.createKeys(); + } catch (Exception e) { + throw new RuntimeException("上传公钥失败", e); + } + String publicKeyPlatform = keyStorePlatform.getPublicKey(); + String privateKeyPlatform = keyStorePlatform.getPrivateKey(); + if (isvKeys == null) { + isvKeys = new IsvKeys(); + isvKeys.setAppKey(appKey); + // 私钥自己保存 + isvKeys.setPrivateKeyIsv(""); + isvKeys.setPublicKeyIsv(publicKeyIsv); + // 设置平台公私钥 + isvKeys.setPrivateKeyPlatform(privateKeyPlatform); + isvKeys.setPublicKeyPlatform(publicKeyPlatform); + userService.saveIsvKey(isvKeys); + } else { + isvKeys.setPrivateKeyPlatform(privateKeyPlatform); + isvKeys.setPublicKeyPlatform(publicKeyPlatform); + isvKeys.setPublicKeyIsv(publicKeyIsv); + userService.updateIsvKey(isvKeys); + } + IsvPublicKeyUploadResult isvPublicKeyUploadResult = new IsvPublicKeyUploadResult(); + isvPublicKeyUploadResult.setIsUploadPublicKey(BooleanUtils.toInteger(true)); + isvPublicKeyUploadResult.setPublicKeyIsv(isvKeys.getPublicKeyIsv()); + isvPublicKeyUploadResult.setPublicKeyPlatform(isvKeys.getPublicKeyPlatform()); + userService.sendChannelMsg(appKey); + return Result.ok(isvPublicKeyUploadResult); + } + + /** + * 修改密码 + * @param param + * @return + */ + @PostMapping("/updatePassword") + public Result updatePassword(@RequestBody @Valid UpdatePasswordParam param) { + long userId = UserContext.getLoginUser().getUserId(); + UserAccount userAccount = userService.getUserAccountByUserId(userId); + String oldPwdHex = GenerateUtil.getUserPassword(userAccount.getUsername(), param.getOldPassword()); + Validates.isTrue(!Objects.equals(oldPwdHex, userAccount.getPassword()), "旧密码错误"); + String newPwdHex = GenerateUtil.getUserPassword(userAccount.getUsername(), param.getPassword()); + userAccount.setPassword(newPwdHex); + userService.updateUserAccount(userAccount); + return Result.ok(); + } + + /** + * 获取文档页菜单数据 + * @return + */ + @GetMapping("/getDocMenus") + public Result getAllDoc() { + Collection docInfos = docManager.listAll(); + List menuProjects = docInfos.stream() + .map(docInfo -> { + MenuProject menuProject = new MenuProject(); + menuProject.setLabel(docInfo.getTitle()); + // 构建模块 + List menuModules = docInfo.getDocModuleList() + .stream() + .map(docModule -> { + MenuModule menuModule = new MenuModule(); + menuModule.setLabel(docModule.getModule()); + // 构建文档 + List docItems = docModule.getDocItems().stream() + .map(docItem -> { + MenuDocItem menuDocItem = new MenuDocItem(); + menuDocItem.setId(docItem.getId()); + menuDocItem.setLabel(docItem.getSummary()); + menuDocItem.setName(docItem.getName()); + menuDocItem.setVersion(docItem.getVersion()); + return menuDocItem; + }).collect(Collectors.toList()); + menuModule.setChildren(docItems); + return menuModule; + }).collect(Collectors.toList()); + menuProject.setChildren(menuModules); + return menuProject; + }).collect(Collectors.toList()); + DocVO docVO = new DocVO(); + LoginUser loginUser = UserContext.getLoginUser(); + docVO.setGatewayUrl(gatewayUrl); + docVO.setAppId(loginUser.getAppKey()); + docVO.setMenuProjects(menuProjects); + docVO.setUrlProd(urlProd); + docVO.setUrlTest(urlTest); + return Result.ok(docVO); + } + + /** + * 根据文档id查询文档内容 + * @param id 文档id + * @return + */ + @GetMapping("/getDocItem") + public Result getDocItem(String id) { + DocItem docItem = docManager.getDocItem(id); + return Result.ok(docItem); + } + + /** + * 获取SDK列表 + * @return + */ + @GetMapping("listSdk") + public Result> list() { + List resourceList = ispResourceMapper.list(new Query()); + return Result.ok(resourceList); + } +} diff --git a/sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/controller/LoginController.java b/sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/controller/LoginController.java new file mode 100644 index 00000000..452ce876 --- /dev/null +++ b/sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/controller/LoginController.java @@ -0,0 +1,58 @@ +package com.gitee.sop.websiteserver.controller; + +import com.gitee.sop.websiteserver.bean.LoginUser; +import com.gitee.sop.websiteserver.bean.NoLogin; +import com.gitee.sop.websiteserver.bean.Result; +import com.gitee.sop.websiteserver.bean.UserContext; +import com.gitee.sop.websiteserver.controller.param.LoginForm; +import com.gitee.sop.websiteserver.controller.result.LoginResult; +import com.gitee.sop.websiteserver.manager.TokenManager; +import com.gitee.sop.websiteserver.service.UserService; +import com.gitee.sop.websiteserver.util.GenerateUtil; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import javax.validation.Valid; +import java.util.Objects; +import java.util.UUID; + +/** + * @author tanghc + */ +@RestController +@RequestMapping("portal/common") +public class LoginController { + + @Autowired + private UserService userService; + + @Autowired + private TokenManager tokenManager; + + @PostMapping("login") + @NoLogin + public Result login(@RequestBody @Valid LoginForm param) { + String username = param.getUsername(); + String password = param.getPassword(); + password = GenerateUtil.getUserPassword(username, password); + LoginUser loginUser = userService.getLoginUser(username, password); + Objects.requireNonNull(loginUser, "用户名密码不正确"); + String token = UUID.randomUUID().toString(); + tokenManager.setUser(token, loginUser); + LoginResult loginResult = new LoginResult(); + loginResult.setToken(token); + return Result.ok(loginResult); + } + + @GetMapping("logout") + public Result logout() { + String token = UserContext.getToken(); + tokenManager.removeUser(token); + return Result.ok(); + } + +} diff --git a/sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/controller/RegController.java b/sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/controller/RegController.java new file mode 100644 index 00000000..b5758b4a --- /dev/null +++ b/sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/controller/RegController.java @@ -0,0 +1,44 @@ +package com.gitee.sop.websiteserver.controller; + +import com.gitee.sop.websiteserver.bean.NoLogin; +import com.gitee.sop.websiteserver.bean.Result; +import com.gitee.sop.websiteserver.bean.Validates; +import com.gitee.sop.websiteserver.controller.param.RegParam; +import com.gitee.sop.websiteserver.entity.UserAccount; +import com.gitee.sop.websiteserver.service.UserService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import javax.validation.Valid; + +/** + * @author tanghc + */ +@RestController +@RequestMapping("portal/common") +@NoLogin +public class RegController { + + @Autowired + private UserService userService; + + @PostMapping("/regIsv") + public Result reg(@RequestBody @Valid RegParam param) { + // 3. 校验邮箱是否存在 + this.checkEmail(param.getUsername()); + // 4. 创建用户 + UserAccount userInfo = userService.saveUser(param); + // 5. 创建ISV + userService.createIsv(userInfo); + return Result.ok(); + } + + private void checkEmail(String email) { + UserAccount username = userService.getUserAccountByUsername(email); + Validates.isTrue(username != null, "该邮箱已被注册"); + } + +} diff --git a/sop-website/src/main/java/com/gitee/sop/websiteserver/controller/SandboxController.java b/sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/controller/SandboxController.java similarity index 98% rename from sop-website/src/main/java/com/gitee/sop/websiteserver/controller/SandboxController.java rename to sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/controller/SandboxController.java index 7c04af0d..0649e064 100644 --- a/sop-website/src/main/java/com/gitee/sop/websiteserver/controller/SandboxController.java +++ b/sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/controller/SandboxController.java @@ -137,7 +137,7 @@ public class SandboxController { response.flushBuffer(); return null; } else if (!CollectionUtils.isEmpty(files)) { - responseData = httpTool.requestFile(url, params, Collections.emptyMap(), files); + responseData = httpTool.requestFileString(url, params, Collections.emptyMap(), files); } else { responseData = httpTool.request(url, params, Collections.emptyMap(), HttpTool.HTTPMethod.fromValue(httpMethod)); } @@ -208,6 +208,6 @@ public class SandboxController { for (Map.Entry entry : params.entrySet()) { sb.append("&").append(entry.getKey()).append("=").append(entry.getValue()); } - return sb.toString().substring(1); + return sb.substring(1); } } diff --git a/sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/controller/SandboxV2Controller.java b/sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/controller/SandboxV2Controller.java new file mode 100644 index 00000000..cccd6859 --- /dev/null +++ b/sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/controller/SandboxV2Controller.java @@ -0,0 +1,212 @@ +package com.gitee.sop.websiteserver.controller; + +import com.alibaba.fastjson.JSON; +import com.gitee.sop.websiteserver.bean.HttpTool; +import com.gitee.sop.websiteserver.sign.AlipayApiException; +import com.gitee.sop.websiteserver.sign.AlipaySignature; +import com.gitee.sop.websiteserver.util.UploadUtil; +import lombok.Data; +import lombok.extern.slf4j.Slf4j; +import okhttp3.Response; +import okhttp3.ResponseBody; +import org.apache.commons.io.IOUtils; +import org.apache.commons.lang.StringUtils; +import org.apache.http.NameValuePair; +import org.apache.http.client.entity.UrlEncodedFormEntity; +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClients; +import org.apache.http.message.BasicNameValuePair; +import org.apache.http.util.EntityUtils; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.util.Assert; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.multipart.MultipartFile; +import org.springframework.web.util.UriUtils; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.text.SimpleDateFormat; +import java.util.Collection; +import java.util.Collections; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.stream.Collectors; + + +/** + * 沙箱环境代理类 + * + * @author tanghc + */ +@Slf4j +@RestController +@RequestMapping("sandbox") +public class SandboxV2Controller { + + @Value("${api.url-sandbox}") + private String url; + + static HttpTool httpTool = new HttpTool(); + + @RequestMapping("/test_v2") + public void proxy( + @RequestParam(required = false) String gatewayUrl + , @RequestParam String appId + , @RequestParam String privateKey + , @RequestParam(required = false) String token + , @RequestParam String method + , @RequestParam String version + , @RequestParam String bizContent + , @RequestParam(defaultValue = "get") String httpMethod + , @RequestParam(defaultValue = "false") boolean isDownloadRequest + , HttpServletRequest request + , HttpServletResponse response + ) throws AlipayApiException, IOException { + + Assert.isTrue(StringUtils.isNotBlank(appId), "AppId不能为空"); + Assert.isTrue(StringUtils.isNotBlank(privateKey), "PrivateKey不能为空"); + Assert.isTrue(StringUtils.isNotBlank(method), "method不能为空"); + if (StringUtils.isEmpty(gatewayUrl)) { + gatewayUrl = url; + } + // 公共请求参数 + Map params = new HashMap(); + params.put("app_id", appId); + params.put("method", method); + 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())); + params.put("version", version); + + if (StringUtils.isNotBlank(token)) { + params.put("app_auth_token", token); + } + + // 业务参数 + params.put("biz_content", bizContent); + + String paramsQuery = buildParamQuery(params); + + String content = AlipaySignature.getSignContent(params); + + String sign = null; + try { + sign = AlipaySignature.rsa256Sign(content, privateKey, "utf-8"); + } catch (AlipayApiException e) { + log.error("构建签名失败", e); + throw new RuntimeException("构建签名失败", e); + } + + params.put("sign", sign); + + Collection uploadFiles = UploadUtil.getUploadFiles(request); + List files = uploadFiles.stream() + .map(multipartFile -> { + try { + return new HttpTool.UploadFile(multipartFile.getName(), multipartFile.getOriginalFilename(), multipartFile.getBytes()); + } catch (IOException e) { + log.error("封装文件失败", e); + return null; + } + }) + .filter(Objects::nonNull) + .collect(Collectors.toList()); + + try { + Response resp = httpTool.request(gatewayUrl, params, Collections.emptyMap(), HttpTool.HTTPMethod.fromValue(httpMethod), files); + ResponseBody body = resp.body(); + if (body == null) { + return; + } + Map> headersMap = resp.headers().toMultimap(); + Map targetHeaders = new HashMap<>(headersMap.size() * 2); + headersMap.forEach((key, value) -> { + String headerValue = String.join(",", value); + response.setHeader(key, headerValue); + targetHeaders.put(key, headerValue); + }); + response.addHeader("target-response-headers", JSON.toJSONString(targetHeaders)); + response.addHeader("sendbox-params", UriUtils.encode(paramsQuery, StandardCharsets.UTF_8)); + response.addHeader("sendbox-beforesign", UriUtils.encode(content, StandardCharsets.UTF_8)); + response.addHeader("sendbox-sign", UriUtils.encode(sign, StandardCharsets.UTF_8)); + IOUtils.copy(body.byteStream(), response.getOutputStream()); + response.flushBuffer(); + } catch (Exception e) { + log.error("请求失败", e); + throw new RuntimeException("请求失败"); + } + } + + @Data + public static class SandboxResult { + private String params; + private String beforeSign; + private String sign; + + private Object apiResult; + } + + + /** + * 发送get请求 + * + * @param url + * @return JSON或者字符串 + * @throws Exception + */ + public static String get(String url, Map params) { + CloseableHttpClient httpClient = null; + CloseableHttpResponse response = null; + try { + httpClient = HttpClients.createDefault(); + List nameValuePairs = params.entrySet() + .stream() + .map(entry -> new BasicNameValuePair(entry.getKey(), String.valueOf(entry.getValue()))) + .collect(Collectors.toList()); + /** + * 包装成一个Entity对象 + */ + UrlEncodedFormEntity entity = new UrlEncodedFormEntity(nameValuePairs, "UTF-8"); + //参数转换为字符串 + String paramsStr = EntityUtils.toString(entity); + url = url + "?" + paramsStr; + /** + * 创建一个post对象 + */ + HttpGet get = new HttpGet(url); + + /** + * 执行post请求 + */ + response = httpClient.execute(get); + /** + * 通过EntityUitls获取返回内容 + */ + return EntityUtils.toString(response.getEntity(), "UTF-8"); + } catch (Exception e) { + e.printStackTrace(); + } finally { + IOUtils.closeQuietly(httpClient); + IOUtils.closeQuietly(response); + } + return null; + } + + protected String buildParamQuery(Map params) { + StringBuilder sb = new StringBuilder(); + for (Map.Entry entry : params.entrySet()) { + sb.append("&").append(entry.getKey()).append("=").append(entry.getValue()); + } + return sb.substring(1); + } +} diff --git a/sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/controller/param/FindPasswordParam.java b/sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/controller/param/FindPasswordParam.java new file mode 100644 index 00000000..2e619010 --- /dev/null +++ b/sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/controller/param/FindPasswordParam.java @@ -0,0 +1,15 @@ +package com.gitee.sop.websiteserver.controller.param; + +import lombok.Data; +import org.hibernate.validator.constraints.Length; + +import javax.validation.constraints.NotBlank; + +/** + * @author tanghc + */ +@Data +public class FindPasswordParam { + @NotBlank @Length(max = 300) String username; + +} diff --git a/sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/controller/param/IsvPublicKeyUploadParam.java b/sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/controller/param/IsvPublicKeyUploadParam.java new file mode 100644 index 00000000..61d23b0c --- /dev/null +++ b/sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/controller/param/IsvPublicKeyUploadParam.java @@ -0,0 +1,16 @@ +package com.gitee.sop.websiteserver.controller.param; + +import lombok.Data; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; + +/** + * @author tanghc + */ +@Data +public class IsvPublicKeyUploadParam { + + @NotBlank + private String publicKeyIsv; +} diff --git a/sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/controller/param/LoginForm.java b/sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/controller/param/LoginForm.java new file mode 100644 index 00000000..77d2fc7e --- /dev/null +++ b/sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/controller/param/LoginForm.java @@ -0,0 +1,15 @@ +package com.gitee.sop.websiteserver.controller.param; + +import lombok.Data; + +import javax.validation.constraints.NotBlank; + +@Data +public class LoginForm { + @NotBlank(message = "用户名不能为空") + private String username; + + @NotBlank(message = "密码不能为空") + private String password; + +} \ No newline at end of file diff --git a/sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/controller/param/RegParam.java b/sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/controller/param/RegParam.java new file mode 100644 index 00000000..054c4fc4 --- /dev/null +++ b/sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/controller/param/RegParam.java @@ -0,0 +1,33 @@ +package com.gitee.sop.websiteserver.controller.param; + +import lombok.Data; +import org.hibernate.validator.constraints.Length; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; + +/** + * @author tanghc + */ +@Data +public class RegParam { + + @NotBlank(message = "username can not be null") + @Length(max = 128) + private String username; + + @NotBlank(message = "password can not be null") + @Length(max = 64) + private String password; + + @Length(max = 64) + private String namespace; + + @NotNull(message = "type can not be null") + private Byte type; + + @Length(max = 64) + private String company; + + +} diff --git a/sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/controller/param/RestPasswordParam.java b/sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/controller/param/RestPasswordParam.java new file mode 100644 index 00000000..5e0fe9ef --- /dev/null +++ b/sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/controller/param/RestPasswordParam.java @@ -0,0 +1,26 @@ +package com.gitee.sop.websiteserver.controller.param; + +import lombok.Data; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; + +/** + * @author tanghc + */ +@Data +public class RestPasswordParam { + + @NotBlank + private String email; + + @NotNull + private Long time; + + @NotBlank + private String sign; + + @NotBlank(message = "密码不能为空") + private String password; + +} diff --git a/sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/controller/param/UpdatePasswordParam.java b/sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/controller/param/UpdatePasswordParam.java new file mode 100644 index 00000000..0a184704 --- /dev/null +++ b/sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/controller/param/UpdatePasswordParam.java @@ -0,0 +1,19 @@ +package com.gitee.sop.websiteserver.controller.param; + +import lombok.Data; + +import javax.validation.constraints.NotBlank; + +/** + * @author tanghc + */ +@Data +public class UpdatePasswordParam { + + @NotBlank(message = "旧密码不能为空") + private String oldPassword; + + @NotBlank(message = "密码不能为空") + private String password; + +} diff --git a/sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/controller/result/DocVO.java b/sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/controller/result/DocVO.java new file mode 100644 index 00000000..c17ac9e0 --- /dev/null +++ b/sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/controller/result/DocVO.java @@ -0,0 +1,19 @@ +package com.gitee.sop.websiteserver.controller.result; + +import lombok.Getter; +import lombok.Setter; + +import java.util.Collection; + +/** + * @author tanghc + */ +@Getter +@Setter +public class DocVO { + private String gatewayUrl; + private String appId; + private String urlTest; + private String urlProd; + private Collection menuProjects; +} diff --git a/sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/controller/result/IsvInfoResult.java b/sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/controller/result/IsvInfoResult.java new file mode 100644 index 00000000..794ffb7d --- /dev/null +++ b/sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/controller/result/IsvInfoResult.java @@ -0,0 +1,12 @@ +package com.gitee.sop.websiteserver.controller.result; + +import lombok.Data; + +/** + * @author tanghc + */ +@Data +public class IsvInfoResult { + private String appId; + private Integer isUploadPublicKey; +} diff --git a/sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/controller/result/IsvPublicKeyUploadResult.java b/sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/controller/result/IsvPublicKeyUploadResult.java new file mode 100644 index 00000000..abf05376 --- /dev/null +++ b/sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/controller/result/IsvPublicKeyUploadResult.java @@ -0,0 +1,20 @@ +package com.gitee.sop.websiteserver.controller.result; + +import lombok.Data; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; + +/** + * @author tanghc + */ +@Data +public class IsvPublicKeyUploadResult { + + private String publicKeyIsv; + + private String publicKeyPlatform; + + private Integer isUploadPublicKey; + +} diff --git a/sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/controller/result/IsvResourceResult.java b/sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/controller/result/IsvResourceResult.java new file mode 100644 index 00000000..e04ec099 --- /dev/null +++ b/sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/controller/result/IsvResourceResult.java @@ -0,0 +1,27 @@ +package com.gitee.sop.websiteserver.controller.result; + +import lombok.Data; + + +/** + * 表名:isp_resource + * 备注:ISP资源表 + * + * @author tanghc + */ +@Data +public class IsvResourceResult { + + private Long id; + + /** 资源名称, 数据库字段:name */ + private String name; + + private String version; + + /** 资源内容(URL), 数据库字段:content */ + private String content; + + private String extContent; + +} diff --git a/sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/controller/result/LoginResult.java b/sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/controller/result/LoginResult.java new file mode 100644 index 00000000..e946c0ab --- /dev/null +++ b/sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/controller/result/LoginResult.java @@ -0,0 +1,11 @@ +package com.gitee.sop.websiteserver.controller.result; + +import lombok.Data; + +/** + * @author tanghc + */ +@Data +public class LoginResult { + private String token; +} diff --git a/sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/controller/result/MenuDocItem.java b/sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/controller/result/MenuDocItem.java new file mode 100644 index 00000000..f1f73bf1 --- /dev/null +++ b/sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/controller/result/MenuDocItem.java @@ -0,0 +1,14 @@ +package com.gitee.sop.websiteserver.controller.result; + +import lombok.Data; + +/** + * @author tanghc + */ +@Data +public class MenuDocItem { + private String id; + private String label; + private String name; + private String version; +} diff --git a/sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/controller/result/MenuModule.java b/sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/controller/result/MenuModule.java new file mode 100644 index 00000000..737f29e6 --- /dev/null +++ b/sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/controller/result/MenuModule.java @@ -0,0 +1,14 @@ +package com.gitee.sop.websiteserver.controller.result; + +import lombok.Data; + +import java.util.List; + +/** + * @author tanghc + */ +@Data +public class MenuModule { + private String label; + private List children; +} diff --git a/sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/controller/result/MenuProject.java b/sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/controller/result/MenuProject.java new file mode 100644 index 00000000..5d5552b1 --- /dev/null +++ b/sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/controller/result/MenuProject.java @@ -0,0 +1,14 @@ +package com.gitee.sop.websiteserver.controller.result; + +import lombok.Data; + +import java.util.List; + +/** + * @author tanghc + */ +@Data +public class MenuProject { + private String label; + private List children; +} diff --git a/sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/controller/result/RegResult.java b/sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/controller/result/RegResult.java new file mode 100644 index 00000000..6abf4d79 --- /dev/null +++ b/sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/controller/result/RegResult.java @@ -0,0 +1,11 @@ +package com.gitee.sop.websiteserver.controller.result; + +import lombok.Data; + +/** + * @author tanghc + */ +@Data +public class RegResult { + private Integer needVerifyEmail; +} diff --git a/sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/entity/IspResource.java b/sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/entity/IspResource.java new file mode 100644 index 00000000..c3048ada --- /dev/null +++ b/sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/entity/IspResource.java @@ -0,0 +1,50 @@ +package com.gitee.sop.websiteserver.entity; + +import lombok.Data; + +import javax.persistence.Column; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.Table; +import java.util.Date; + + +/** + * 表名:isp_resource + * 备注:ISP资源表 + * + * @author tanghc + */ +@Table(name = "isp_resource") +@Data +public class IspResource { + /** 数据库字段:id */ + @Id + @Column(name = "id") + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + /** 资源名称, 数据库字段:name */ + private String name; + + private String version; + + /** 资源内容(URL), 数据库字段:content */ + private String content; + + private String extContent; + + /** 资源类型:0:SDK链接, 数据库字段:type */ + private Byte type; + + /** 数据库字段:is_deleted */ + @com.gitee.fastmybatis.core.annotation.LogicDelete + private Byte isDeleted; + + /** 数据库字段:gmt_create */ + private Date gmtCreate; + + /** 数据库字段:gmt_modified */ + private Date gmtModified; +} diff --git a/sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/entity/IsvInfo.java b/sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/entity/IsvInfo.java new file mode 100644 index 00000000..84dfc922 --- /dev/null +++ b/sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/entity/IsvInfo.java @@ -0,0 +1,44 @@ +package com.gitee.sop.websiteserver.entity; + +import lombok.Data; + +import javax.persistence.Column; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.Table; +import java.util.Date; + + +/** + * 表名:isv_info + * 备注:isv信息表 + * + * @author tanghc + */ +@Table(name = "isv_info") +@Data +public class IsvInfo { + @Id + @Column(name = "id") + @GeneratedValue(strategy = GenerationType.IDENTITY) + /** 数据库字段:id */ + private Long id; + + /** appKey, 数据库字段:app_key */ + private String appKey; + + /** 1启用,2禁用, 数据库字段:status */ + private Byte status; + + /** 备注,数据库字段:remark */ + private String remark; + + private Long userId; + + /** 数据库字段:gmt_create */ + private Date gmtCreate; + + /** 数据库字段:gmt_modified */ + private Date gmtModified; +} diff --git a/sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/entity/IsvKeys.java b/sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/entity/IsvKeys.java new file mode 100644 index 00000000..c1a81ddc --- /dev/null +++ b/sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/entity/IsvKeys.java @@ -0,0 +1,57 @@ +package com.gitee.sop.websiteserver.entity; + +import lombok.Data; + +import javax.persistence.Column; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.Table; +import java.util.Date; + + +/** + * 表名:isv_keys + * 备注:ISV秘钥 + * + * @author tanghc + */ +@Table(name = "isv_keys") +@Data +public class IsvKeys { + @Id + @Column(name = "id") + @GeneratedValue(strategy = GenerationType.IDENTITY) + /** 数据库字段:id */ + private Long id; + + /** 数据库字段:app_key */ + private String appKey; + + /** 1:RSA2,2:MD5, 数据库字段:sign_type */ + private Byte signType; + + /** isv_info.sign_type=2时使用, 数据库字段:secret */ + private String secret; + + /** 秘钥格式,1:PKCS8(JAVA适用),2:PKCS1(非JAVA适用), 数据库字段:key_format */ + private Byte keyFormat; + + /** 开发者生成的公钥, 数据库字段:public_key_isv */ + private String publicKeyIsv; + + /** 开发者生成的私钥(交给开发者), 数据库字段:private_key_isv */ + private String privateKeyIsv; + + /** 平台生成的公钥(交给开发者), 数据库字段:public_key_platform */ + private String publicKeyPlatform; + + /** 平台生成的私钥, 数据库字段:private_key_platform */ + private String privateKeyPlatform; + + /** 数据库字段:gmt_create */ + private Date gmtCreate; + + /** 数据库字段:gmt_modified */ + private Date gmtModified; +} diff --git a/sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/entity/IsvPortal.java b/sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/entity/IsvPortal.java new file mode 100644 index 00000000..9faf6ba1 --- /dev/null +++ b/sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/entity/IsvPortal.java @@ -0,0 +1,19 @@ +package com.gitee.sop.websiteserver.entity; + +import lombok.Data; + +/** + * @author tanghc + */ +@Data +public class IsvPortal { + + private Long userId; + + /** isv appId */ + private String appId; + + /** isv上传的公钥, 用户验证客户端签名 */ + private transient String publicKeyIsv; + +} diff --git a/sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/entity/UserAccount.java b/sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/entity/UserAccount.java new file mode 100644 index 00000000..b36eb04b --- /dev/null +++ b/sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/entity/UserAccount.java @@ -0,0 +1,42 @@ +package com.gitee.sop.websiteserver.entity; + +import lombok.Data; + +import javax.persistence.Column; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.Table; +import java.util.Date; + + +/** + * 表名:user_account + * 备注:用户信息 + * + * @author tanghc + */ +@Table(name = "user_account") +@Data +public class UserAccount { + /** 数据库字段:id */ + @Id + @Column(name = "id") + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + /** 用户名(邮箱), 数据库字段:username */ + private String username; + + /** 密码, 数据库字段:password */ + private String password; + + /** 2:邮箱未验证,1:启用,0:禁用, 数据库字段:status */ + private Byte status; + + /** 数据库字段:gmt_create */ + private Date gmtCreate; + + /** 数据库字段:gmt_modified */ + private Date gmtModified; +} diff --git a/sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/exception/ErrorCode.java b/sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/exception/ErrorCode.java new file mode 100644 index 00000000..1b13dfa7 --- /dev/null +++ b/sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/exception/ErrorCode.java @@ -0,0 +1,24 @@ +package com.gitee.sop.websiteserver.exception; + +/** + * @author tanghc + */ +public enum ErrorCode { + LOGIN_FAIL("9", "登录失败"); + + ErrorCode(String code, String msg) { + this.code = code; + this.msg = msg; + } + + private final String code; + private final String msg; + + public String getCode() { + return code; + } + + public String getMsg() { + return msg; + } +} diff --git a/sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/exception/ExceptionCode.java b/sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/exception/ExceptionCode.java new file mode 100644 index 00000000..b9faf0ab --- /dev/null +++ b/sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/exception/ExceptionCode.java @@ -0,0 +1,8 @@ +package com.gitee.sop.websiteserver.exception; + +/** + * @author tanghc + */ +public interface ExceptionCode { + ErrorCode getCode(); +} diff --git a/sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/exception/ExceptionController.java b/sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/exception/ExceptionController.java new file mode 100644 index 00000000..c81dea3a --- /dev/null +++ b/sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/exception/ExceptionController.java @@ -0,0 +1,31 @@ +package com.gitee.sop.websiteserver.exception; + +import com.gitee.sop.websiteserver.bean.Result; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestControllerAdvice; + +/** + * 全局错误处理 + * + * @author tanghc + */ +@RestControllerAdvice +public class ExceptionController { + + private static final Logger logger = LoggerFactory.getLogger(ExceptionController.class); + + @ExceptionHandler(Exception.class) + public Object exceptionHandler(Exception e) { + if (e instanceof ExceptionCode) { + ExceptionCode exceptionCode = (ExceptionCode) e; + ErrorCode errorCode = exceptionCode.getCode(); + logger.error("报错,code:{}, msg:{}", errorCode.getCode(), errorCode.getMsg(), e); + return Result.err(errorCode.getCode(), errorCode.getMsg()); + } + logger.error("未知错误:", e); + return Result.err(e.getMessage()); + } + +} diff --git a/sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/exception/LoginFailureException.java b/sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/exception/LoginFailureException.java new file mode 100644 index 00000000..6ef7506e --- /dev/null +++ b/sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/exception/LoginFailureException.java @@ -0,0 +1,11 @@ +package com.gitee.sop.websiteserver.exception; + +/** + * @author tanghc + */ +public class LoginFailureException extends RuntimeException implements ExceptionCode { + @Override + public ErrorCode getCode() { + return ErrorCode.LOGIN_FAIL; + } +} diff --git a/sop-website/src/main/java/com/gitee/sop/websiteserver/listener/ServiceDocListener.java b/sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/listener/ServiceDocListener.java similarity index 96% rename from sop-website/src/main/java/com/gitee/sop/websiteserver/listener/ServiceDocListener.java rename to sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/listener/ServiceDocListener.java index 0f270a78..1a1652bf 100644 --- a/sop-website/src/main/java/com/gitee/sop/websiteserver/listener/ServiceDocListener.java +++ b/sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/listener/ServiceDocListener.java @@ -22,6 +22,7 @@ public class ServiceDocListener extends BaseServiceListener { @Override public void onRemoveService(String serviceId) { + log.info("服务下线,删除文档,serviceId: {}", serviceId); docManager.remove(serviceId); } diff --git a/sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/manager/DefaultTokenManager.java b/sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/manager/DefaultTokenManager.java new file mode 100644 index 00000000..24d09ff8 --- /dev/null +++ b/sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/manager/DefaultTokenManager.java @@ -0,0 +1,68 @@ +package com.gitee.sop.websiteserver.manager; + +import com.gitee.sop.websiteserver.bean.LoginUser; +import com.google.common.cache.CacheBuilder; +import com.google.common.cache.CacheLoader; +import com.google.common.cache.LoadingCache; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; +import org.springframework.util.StringUtils; + +import javax.annotation.PostConstruct; +import java.util.Optional; +import java.util.concurrent.TimeUnit; + +/** + * @author tanghc + */ +@Service +@Slf4j +public class DefaultTokenManager implements TokenManager { + + + @Value("${sop.website.token-timeout-seconds:600}") + private int accessTokenTimeoutSeconds; + + private LoadingCache> accessTokenCache; + + @Override + public LoginUser getUser(String token) { + if (StringUtils.isEmpty(token)) { + return null; + } + return accessTokenCache.getUnchecked(token).orElse(null); + } + + @Override + public void setUser(String token, LoginUser user) { + accessTokenCache.put(token, Optional.of(user)); + } + + @Override + public void removeUser(String token) { + if (token == null) { + return; + } + accessTokenCache.invalidate(token); + } + + private static LoadingCache> buildCache(int timeout) { + CacheBuilder cacheBuilder = CacheBuilder.newBuilder(); + if (timeout > 0) { + cacheBuilder.expireAfterAccess(timeout, TimeUnit.SECONDS); + } + return cacheBuilder + .build(new CacheLoader>() { + @Override + public Optional load(String key) throws Exception { + return Optional.empty(); + } + }); + } + + @PostConstruct + public void after() { + accessTokenCache = buildCache(accessTokenTimeoutSeconds); + } +} diff --git a/sop-website/src/main/java/com/gitee/sop/websiteserver/manager/DocManager.java b/sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/manager/DocManager.java similarity index 83% rename from sop-website/src/main/java/com/gitee/sop/websiteserver/manager/DocManager.java rename to sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/manager/DocManager.java index ddb5806d..d8bcc85f 100644 --- a/sop-website/src/main/java/com/gitee/sop/websiteserver/manager/DocManager.java +++ b/sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/manager/DocManager.java @@ -1,6 +1,7 @@ package com.gitee.sop.websiteserver.manager; import com.gitee.sop.websiteserver.bean.DocInfo; +import com.gitee.sop.websiteserver.bean.DocItem; import java.util.Collection; import java.util.function.Consumer; @@ -14,6 +15,8 @@ public interface DocManager { DocInfo getByTitle(String title); + DocItem getDocItem(String id); + Collection listAll(); void remove(String serviceId); diff --git a/sop-website/src/main/java/com/gitee/sop/websiteserver/manager/DocManagerImpl.java b/sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/manager/DocManagerImpl.java similarity index 76% rename from sop-website/src/main/java/com/gitee/sop/websiteserver/manager/DocManagerImpl.java rename to sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/manager/DocManagerImpl.java index d0ad7fd0..d067aa34 100644 --- a/sop-website/src/main/java/com/gitee/sop/websiteserver/manager/DocManagerImpl.java +++ b/sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/manager/DocManagerImpl.java @@ -4,6 +4,8 @@ import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; import com.alibaba.fastjson.parser.Feature; import com.gitee.sop.websiteserver.bean.DocInfo; +import com.gitee.sop.websiteserver.bean.DocItem; +import com.gitee.sop.websiteserver.bean.DocModule; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import org.springframework.util.DigestUtils; @@ -11,8 +13,10 @@ import org.springframework.util.DigestUtils; import java.nio.charset.StandardCharsets; import java.util.Collection; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.concurrent.ConcurrentHashMap; import java.util.function.Consumer; /** @@ -30,6 +34,9 @@ public class DocManagerImpl implements DocManager { */ private static final Map serviceIdMd5Map = new HashMap<>(); + // key: DocItem.id + private static final Map ITEM_MAP = new ConcurrentHashMap<>(128); + private static final DocParser swaggerDocParser = new SwaggerDocParser(); @Override @@ -44,6 +51,12 @@ public class DocManagerImpl implements DocManager { DocInfo docInfo = swaggerDocParser.parseJson(docRoot); docInfo.setServiceId(serviceId); docDefinitionMap.put(docInfo.getTitle(), docInfo); + List docModules = docInfo.getDocModuleList(); + docModules.forEach(docModule -> { + docModule.getDocItems().forEach(docItem -> { + ITEM_MAP.put(docItem.getId(), docItem); + }); + }); callback.accept(docInfo); } @@ -52,6 +65,11 @@ public class DocManagerImpl implements DocManager { return docDefinitionMap.get(title); } + @Override + public DocItem getDocItem(String id) { + return ITEM_MAP.get(id); + } + @Override public Collection listAll() { return docDefinitionMap.values(); diff --git a/sop-website/src/main/java/com/gitee/sop/websiteserver/manager/DocParser.java b/sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/manager/DocParser.java similarity index 100% rename from sop-website/src/main/java/com/gitee/sop/websiteserver/manager/DocParser.java rename to sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/manager/DocParser.java diff --git a/sop-website/src/main/java/com/gitee/sop/websiteserver/manager/SwaggerDocParser.java b/sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/manager/SwaggerDocParser.java similarity index 96% rename from sop-website/src/main/java/com/gitee/sop/websiteserver/manager/SwaggerDocParser.java rename to sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/manager/SwaggerDocParser.java index 0f41da00..321da8fa 100644 --- a/sop-website/src/main/java/com/gitee/sop/websiteserver/manager/SwaggerDocParser.java +++ b/sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/manager/SwaggerDocParser.java @@ -24,6 +24,7 @@ import java.util.Map; import java.util.Objects; import java.util.Optional; import java.util.Set; +import java.util.UUID; import java.util.stream.Collectors; /** @@ -117,6 +118,7 @@ public class SwaggerDocParser implements DocParser { return null; } DocItem docItem = new DocItem(); + docItem.setId(UUID.randomUUID().toString()); docItem.setName(apiName); docItem.setVersion(docInfo.getString("sop_version")); docItem.setSummary(docInfo.getString("summary")); @@ -158,8 +160,10 @@ public class SwaggerDocParser implements DocParser { JSONObject schema = fieldJson.getJSONObject("schema"); if (schema != null) { RefInfo refInfo = getRefInfo(schema); - List parameterList = this.buildDocParameters(refInfo.ref, docRoot, true); - docParameterList.addAll(parameterList); + if (refInfo != null) { + List parameterList = this.buildDocParameters(refInfo.ref, docRoot, true); + docParameterList.addAll(parameterList); + } } else { DocParameter docParameter = fieldJson.toJavaObject(DocParameter.class); docParameterList.add(docParameter); @@ -216,6 +220,7 @@ public class SwaggerDocParser implements DocParser { JSONObject responseObject = docRoot.getJSONObject("definitions").getJSONObject(ref); String className = responseObject.getString("title"); JSONObject extProperties = docRoot.getJSONObject(className); + JSONArray requiredProperties = responseObject.getJSONArray("required"); JSONObject properties = responseObject.getJSONObject("properties"); List docParameterList = new ArrayList<>(); if (properties == null) { @@ -232,6 +237,8 @@ public class SwaggerDocParser implements DocParser { JSONObject fieldInfo = properties.getJSONObject(fieldName); DocParameter docParameter = fieldInfo.toJavaObject(DocParameter.class); docParameter.setName(fieldName); + docParameter.setRequired( + !CollectionUtils.isEmpty(requiredProperties) && requiredProperties.contains(fieldName)); if (extProperties != null) { JSONObject prop = extProperties.getJSONObject(fieldName); if (prop != null) { diff --git a/sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/manager/TokenManager.java b/sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/manager/TokenManager.java new file mode 100644 index 00000000..b654aa23 --- /dev/null +++ b/sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/manager/TokenManager.java @@ -0,0 +1,30 @@ +package com.gitee.sop.websiteserver.manager; + +import com.gitee.sop.websiteserver.bean.LoginUser; + +/** + * @author tanghc + */ +public interface TokenManager { + + /** + * 根据token获取登录用户 + * @param token token + * @return 返回登录用户,没有返回null + */ + LoginUser getUser(String token); + + /** + * 返回token + * @param token + * @param user + * @return + */ + void setUser(String token, LoginUser user); + + /** + * 删除用户 + * @param token token + */ + void removeUser(String token); +} diff --git a/sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/mapper/IspResourceMapper.java b/sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/mapper/IspResourceMapper.java new file mode 100644 index 00000000..1ef279b0 --- /dev/null +++ b/sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/mapper/IspResourceMapper.java @@ -0,0 +1,11 @@ +package com.gitee.sop.websiteserver.mapper; + +import com.gitee.fastmybatis.core.mapper.CrudMapper; +import com.gitee.sop.websiteserver.entity.IspResource; + + +/** + * @author tanghc + */ +public interface IspResourceMapper extends CrudMapper { +} diff --git a/sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/mapper/IsvInfoMapper.java b/sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/mapper/IsvInfoMapper.java new file mode 100644 index 00000000..579a5aaa --- /dev/null +++ b/sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/mapper/IsvInfoMapper.java @@ -0,0 +1,61 @@ +package com.gitee.sop.websiteserver.mapper; + +import com.gitee.fastmybatis.core.mapper.CrudMapper; +import com.gitee.sop.websiteserver.bean.IsvDetailDTO; +import com.gitee.sop.websiteserver.bean.LoginUser; +import com.gitee.sop.websiteserver.entity.IsvInfo; +import com.gitee.sop.websiteserver.entity.IsvPortal; +import org.apache.ibatis.annotations.Param; +import org.apache.ibatis.annotations.Select; + +import java.util.List; + + +/** + * @author tanghc + */ +public interface IsvInfoMapper extends CrudMapper { + /** + * 获取isv详细信息 + * @param username username + * @param password password + * @return 返回登录信息,没有返回null + */ + @Select("SELECT " + + " t.id userId " + + " ,t2.app_key appKey " + + " ,t3.private_key_isv privateKeyIsv " + + "FROM user_account t " + + "LEFT JOIN isv_info t2 ON t.id = t2.user_id " + + "LEFT JOIN isv_keys t3 ON t2.app_key = t3.app_key " + + "WHERE t.username = #{username} AND t.password=#{password} ") + LoginUser getLoginUser(@Param("username") String username, @Param("password")String password); + + + @Select("SELECT " + + " t.id userId " + + " ,t2.app_key appId " + + " ,t3.public_key_isv publicKeyIsv " + + "FROM user_account t " + + "LEFT JOIN isv_info t2 ON t.id = t2.user_id " + + "LEFT JOIN isv_keys t3 ON t2.app_key = t3.app_key " + + "WHERE t.id = #{userId} ") + IsvPortal getIsvPortal(@Param("userId") long userId); + + /** + * 获取isv详细信息 + * @param appKey appKey + * @return 返回详细信息,没有返回null + */ + @Select("SELECT " + + " t.app_key appKey " + + " ,t.status " + + " ,t2.sign_type signType " + + " ,t2.secret " + + " ,t2.public_key_isv publicKeyIsv " + + " ,t2.private_key_platform privateKeyPlatform " + + "FROM isv_info t " + + "INNER JOIN isv_keys t2 ON t.app_key = t2.app_key " + + "WHERE t.app_key = #{appKey}") + IsvDetailDTO getIsvDetail(@Param("appKey") String appKey); +} diff --git a/sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/mapper/IsvKeysMapper.java b/sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/mapper/IsvKeysMapper.java new file mode 100644 index 00000000..f8b142c6 --- /dev/null +++ b/sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/mapper/IsvKeysMapper.java @@ -0,0 +1,11 @@ +package com.gitee.sop.websiteserver.mapper; + +import com.gitee.fastmybatis.core.mapper.CrudMapper; +import com.gitee.sop.websiteserver.entity.IsvKeys; + + +/** + * @author tanghc + */ +public interface IsvKeysMapper extends CrudMapper { +} diff --git a/sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/mapper/UserAccountMapper.java b/sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/mapper/UserAccountMapper.java new file mode 100644 index 00000000..6705ea1f --- /dev/null +++ b/sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/mapper/UserAccountMapper.java @@ -0,0 +1,11 @@ +package com.gitee.sop.websiteserver.mapper; + +import com.gitee.fastmybatis.core.mapper.CrudMapper; +import com.gitee.sop.websiteserver.entity.UserAccount; + + +/** + * @author tanghc + */ +public interface UserAccountMapper extends CrudMapper { +} diff --git a/sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/service/ConfigPushService.java b/sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/service/ConfigPushService.java new file mode 100644 index 00000000..788b1a64 --- /dev/null +++ b/sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/service/ConfigPushService.java @@ -0,0 +1,76 @@ +package com.gitee.sop.websiteserver.service; + +import com.alibaba.fastjson.JSON; +import com.gitee.sop.websiteserver.bean.ChannelMsg; +import com.gitee.sop.websiteserver.bean.GatewayPushDTO; +import com.gitee.sop.websiteserver.bean.HttpTool; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.codec.digest.DigestUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; + +import java.io.IOException; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; + +/** + * @author tanghc + */ +@Slf4j +@Service +public class ConfigPushService { + + private static final String GATEWAY_PUSH_URL = "http://%s/sop/configChannelMsg"; + private static final String API_GATEWAY_SERVICE_ID = "sop-gateway"; + + private static HttpTool httpTool = new HttpTool(); + + @Value("${sop.secret:MZZOUSTua6LzApIWXCwEgbBmxSzpzC}") + private String secret; + + @Autowired + private ServerService serverService; + + public void publishConfig(String dataId, String groupId, ChannelMsg channelMsg) { + GatewayPushDTO gatewayPushDTO = new GatewayPushDTO(dataId, groupId, channelMsg); + // 获取网关host,ip:port + Collection hostList = serverService.listServerHost(API_GATEWAY_SERVICE_ID); + this.pushByHost(hostList, gatewayPushDTO); + } + + private void pushByHost(Collection hosts, GatewayPushDTO gatewayPushDTO) { + for (String host : hosts) { + String url = String.format(GATEWAY_PUSH_URL, host); + log.info("推送配置, dataId={}, groupId={}, operation={}, url={}", + gatewayPushDTO.getDataId() + , gatewayPushDTO.getGroupId() + , gatewayPushDTO.getChannelMsg().getOperation() + , url); + try { + String requestBody = JSON.toJSONString(gatewayPushDTO); + Map header = new HashMap<>(8); + header.put("sign", buildRequestBodySign(requestBody, secret)); + String resp = httpTool.requestJson(url, requestBody, header); + if (!"ok".equals(resp)) { + throw new IOException(resp); + } + } catch (IOException e) { + log.error("nacos配置失败, dataId={}, groupId={}, operation={}, url={}", + gatewayPushDTO.getDataId() + , gatewayPushDTO.getGroupId() + , gatewayPushDTO.getChannelMsg().getOperation() + , url + , e); + throw new RuntimeException("推送配置失败"); + } + } + } + + public static String buildRequestBodySign(String requestBody, String secret) { + String signContent = secret + requestBody + secret; + return DigestUtils.md5Hex(signContent); + } + +} diff --git a/sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/service/ServerService.java b/sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/service/ServerService.java new file mode 100644 index 00000000..b760f07a --- /dev/null +++ b/sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/service/ServerService.java @@ -0,0 +1,15 @@ +package com.gitee.sop.websiteserver.service; + +import java.util.List; + +/** + * @author tanghc + */ +public interface ServerService { + /** + * 获取服务host,ip:port + * @param serviceId serviceId + * @return 返回host列表 + */ + List listServerHost(String serviceId); +} diff --git a/sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/service/UserService.java b/sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/service/UserService.java new file mode 100644 index 00000000..c85cc32a --- /dev/null +++ b/sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/service/UserService.java @@ -0,0 +1,158 @@ +package com.gitee.sop.websiteserver.service; + +import com.gitee.fastmybatis.core.query.Query; +import com.gitee.sop.gatewaycommon.bean.NacosConfigs; +import com.gitee.sop.gatewaycommon.sync.MyNamedThreadFactory; +import com.gitee.sop.gatewaycommon.util.CopyUtil; +import com.gitee.sop.websiteserver.bean.ChannelMsg; +import com.gitee.sop.websiteserver.bean.ChannelOperation; +import com.gitee.sop.websiteserver.bean.IdGen; +import com.gitee.sop.websiteserver.bean.IsvDetailDTO; +import com.gitee.sop.websiteserver.bean.IsvKeysGenVO; +import com.gitee.sop.websiteserver.bean.LoginUser; +import com.gitee.sop.websiteserver.bean.RSATool; +import com.gitee.sop.websiteserver.controller.param.RegParam; +import com.gitee.sop.websiteserver.entity.IsvInfo; +import com.gitee.sop.websiteserver.entity.IsvKeys; +import com.gitee.sop.websiteserver.entity.IsvPortal; +import com.gitee.sop.websiteserver.entity.UserAccount; +import com.gitee.sop.websiteserver.mapper.IsvInfoMapper; +import com.gitee.sop.websiteserver.mapper.IsvKeysMapper; +import com.gitee.sop.websiteserver.mapper.UserAccountMapper; +import com.gitee.sop.websiteserver.util.GenerateUtil; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; + +/** + * @author tanghc + */ +@Service +@Slf4j +public class UserService { + + public static final byte STATUS_ENABLE = 1; + + private final ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(4, 4, + 0L,TimeUnit.MILLISECONDS, + new LinkedBlockingQueue<>(), new MyNamedThreadFactory("push-thread")); + + @Autowired + private UserAccountMapper userAccountMapper; + + @Autowired + private IsvInfoMapper isvInfoMapper; + + @Autowired + private IsvKeysMapper isvKeysMapper; + + @Autowired + private ConfigPushService configPushService; + + public UserAccount getUserAccountByUsername(String username) { + return userAccountMapper.getByColumn("username", username); + } + + public UserAccount getUserAccountByUserId(long userId) { + return userAccountMapper.getByColumn("id", userId); + } + + public void updateUserAccount(UserAccount userAccount) { + userAccountMapper.updateIgnoreNull(userAccount); + } + + public UserAccount getUserInfoAppKey(String appKey) { + return userAccountMapper.getByColumn("app_key", appKey); + } + + public IsvPortal getIsvPortal(long userId) { + return isvInfoMapper.getIsvPortal(userId); + } + + public IsvKeys getIsvKeys(String appKey) { + return isvKeysMapper.getByColumn("app_key", appKey); + } + + public void saveIsvKey(IsvKeys isvKeys) { + isvKeysMapper.saveIgnoreNull(isvKeys); + } + + /** + * 保存用户秘钥 + * @param isvKeys + */ + public void updateIsvKey(IsvKeys isvKeys) { + isvKeysMapper.updateIgnoreNull(isvKeys); + } + + public void sendChannelMsg(String appKey) { + threadPoolExecutor.execute(() -> { + IsvDetailDTO isvDetail = isvInfoMapper.getIsvDetail(appKey); + if (isvDetail == null) { + return; + } + ChannelMsg channelMsg = new ChannelMsg(ChannelOperation.ISV_INFO_UPDATE, isvDetail); + configPushService.publishConfig(NacosConfigs.DATA_ID_ISV, NacosConfigs.GROUP_CHANNEL, channelMsg); + }); + } + + public LoginUser getLoginUser(String username, String password) { + return isvInfoMapper.getLoginUser(username, password); + } + + public UserAccount saveUser(RegParam param) { + UserAccount userInfo = new UserAccount(); + String password = GenerateUtil.getUserPassword(param.getUsername(), param.getPassword()); + userInfo.setUsername(param.getUsername()); + userInfo.setPassword(password); + userInfo.setStatus(STATUS_ENABLE); + userAccountMapper.saveIgnoreNull(userInfo); + return userInfo; + } + + public void createIsv(UserAccount userInfo) { + String appKey = new SimpleDateFormat("yyyyMMdd").format(new Date()) + IdGen.nextId(); + IsvInfo rec = new IsvInfo(); + rec.setAppKey(appKey); + rec.setUserId(userInfo.getId()); + rec.setStatus(STATUS_ENABLE); + isvInfoMapper.saveIgnoreNull(rec); +// IsvKeysGenVO isvKeysGenVO = null; +// try { +// isvKeysGenVO = this.createIsvKeys(); +// } catch (Exception e) { +// throw new RuntimeException("创建ISV秘钥失败", e); +// } +// IsvKeys isvKeys = new IsvKeys(); +// isvKeys.setAppKey(appKey); +// isvKeys.setSignType(SIGN_TYPE_RSA); +// CopyUtil.copyPropertiesIgnoreNull(isvKeysGenVO, isvKeys); +// isvKeysMapper.saveIgnoreNull(isvKeys); + +// this.sendChannelMsg(rec.getAppKey()); + } + + private IsvKeysGenVO createIsvKeys() throws Exception { + IsvKeysGenVO isvFormVO = new IsvKeysGenVO(); + String secret = IdGen.uuid(); + + isvFormVO.setSecret(secret); + + RSATool rsaToolIsv = new RSATool(RSATool.KeyFormat.PKCS8, RSATool.KeyLength.LENGTH_2048); + RSATool.KeyStore keyStoreIsv = rsaToolIsv.createKeys(); + isvFormVO.setPublicKeyIsv(keyStoreIsv.getPublicKey()); + isvFormVO.setPrivateKeyIsv(keyStoreIsv.getPrivateKey()); + + isvFormVO.setPublicKeyPlatform(""); + isvFormVO.setPrivateKeyPlatform(""); + return isvFormVO; + } + + +} diff --git a/sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/service/impl/EurekaServerService.java b/sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/service/impl/EurekaServerService.java new file mode 100644 index 00000000..03a67d2a --- /dev/null +++ b/sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/service/impl/EurekaServerService.java @@ -0,0 +1,70 @@ +package com.gitee.sop.websiteserver.service.impl; + +import com.alibaba.fastjson.JSON; +import com.gitee.sop.websiteserver.bean.EurekaApplication; +import com.gitee.sop.websiteserver.bean.EurekaApps; +import com.gitee.sop.websiteserver.bean.EurekaInstance; +import com.gitee.sop.websiteserver.bean.EurekaUri; +import com.gitee.sop.websiteserver.service.ServerService; +import lombok.extern.slf4j.Slf4j; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.Response; +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.annotation.Value; + +import java.io.IOException; +import java.util.Collections; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; + +/** + * @author tanghc + */ +@Slf4j +public class EurekaServerService implements ServerService { + + private OkHttpClient client = new OkHttpClient(); + + @Value("${eureka.client.serviceUrl.defaultZone:}") + private String eurekaUrl; + + @Override + public List listServerHost(String serviceId) { + if (StringUtils.isBlank(eurekaUrl)) { + throw new IllegalArgumentException("未指定eureka.client.serviceUrl.defaultZone参数"); + } + Objects.requireNonNull(serviceId, "serviceId can not be null"); + String json = null; + try { + json = this.requestEurekaServer(EurekaUri.QUERY_APPS); + } catch (IOException e) { + return Collections.emptyList(); + } + EurekaApps eurekaApps = JSON.parseObject(json, EurekaApps.class); + + List applicationList = eurekaApps.getApplications().getApplication(); + for (EurekaApplication eurekaApplication : applicationList) { + if (!serviceId.equalsIgnoreCase(eurekaApplication.getName())) { + continue; + } + List instanceList = eurekaApplication.getInstance(); + return instanceList.stream() + .map(eurekaInstance -> eurekaInstance.getIpAddr() + ":" + eurekaInstance.getPort()) + .collect(Collectors.toList()); + } + return Collections.emptyList(); + } + + private String requestEurekaServer(EurekaUri eurekaUri, String... args) throws IOException { + Request request = eurekaUri.getRequest(this.eurekaUrl, args); + Response response = client.newCall(request).execute(); + if (response.isSuccessful()) { + return response.body().string(); + } else { + log.error("操作失败,url:{}, msg:{}, code:{}", eurekaUri.getUri(args), response.message(), response.code()); + throw new RuntimeException("操作失败"); + } + } +} diff --git a/sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/service/impl/NacosServerService.java b/sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/service/impl/NacosServerService.java new file mode 100644 index 00000000..585dc988 --- /dev/null +++ b/sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/service/impl/NacosServerService.java @@ -0,0 +1,63 @@ +package com.gitee.sop.websiteserver.service.impl; + +import com.alibaba.nacos.api.PropertyKeyConst; +import com.alibaba.nacos.api.exception.NacosException; +import com.alibaba.nacos.api.naming.NamingFactory; +import com.alibaba.nacos.api.naming.NamingService; +import com.alibaba.nacos.api.naming.pojo.Instance; +import com.gitee.sop.websiteserver.service.ServerService; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.annotation.Value; + +import javax.annotation.PostConstruct; +import java.util.Collections; +import java.util.List; +import java.util.Objects; +import java.util.Properties; +import java.util.stream.Collectors; + +/** + * @author tanghc + */ +@Slf4j +public class NacosServerService implements ServerService { + + @Value("${nacos.discovery.server-addr:${spring.cloud.nacos.discovery.server-addr:}}") + private String nacosAddr; + + @Value("${nacos.discovery.namespace:${spring.cloud.nacos.discovery.namespace:}}") + private String nacosNamespace; + + @Value("${nacos.discovery.group:${spring.cloud.nacos.discovery.group:DEFAULT_GROUP}}") + private String nacosGroup; + + private NamingService namingService; + + @PostConstruct + public void after() throws NacosException { + if (StringUtils.isBlank(nacosAddr)) { + throw new IllegalArgumentException("请在配置文件中指定nacos.discovery.server-addr参数"); + } + Properties nacosProperties = new Properties(); + nacosProperties.put(PropertyKeyConst.SERVER_ADDR, nacosAddr); + if (StringUtils.isNotBlank(nacosNamespace)) { + nacosProperties.put(PropertyKeyConst.NAMESPACE, nacosNamespace); + } + namingService = NamingFactory.createNamingService(nacosProperties); + } + + @Override + public List listServerHost(String serviceId) { + Objects.requireNonNull(serviceId, "serviceId can not be null"); + List list = Collections.emptyList(); + try { + List allInstances = namingService.getAllInstances(serviceId, nacosGroup); + list = allInstances.stream().map(instance -> instance.getIp() + ":" + instance.getPort()) + .collect(Collectors.toList()); + } catch (NacosException e) { + log.error("获取实例host出错, serviceId:{}", serviceId, e); + } + return list; + } +} diff --git a/sop-website/src/main/java/com/gitee/sop/websiteserver/sign/AlipayApiException.java b/sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/sign/AlipayApiException.java similarity index 100% rename from sop-website/src/main/java/com/gitee/sop/websiteserver/sign/AlipayApiException.java rename to sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/sign/AlipayApiException.java diff --git a/sop-website/src/main/java/com/gitee/sop/websiteserver/sign/AlipayConstants.java b/sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/sign/AlipayConstants.java similarity index 100% rename from sop-website/src/main/java/com/gitee/sop/websiteserver/sign/AlipayConstants.java rename to sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/sign/AlipayConstants.java diff --git a/sop-website/src/main/java/com/gitee/sop/websiteserver/sign/AlipaySignature.java b/sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/sign/AlipaySignature.java similarity index 100% rename from sop-website/src/main/java/com/gitee/sop/websiteserver/sign/AlipaySignature.java rename to sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/sign/AlipaySignature.java diff --git a/sop-website/src/main/java/com/gitee/sop/websiteserver/sign/StreamUtil.java b/sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/sign/StreamUtil.java similarity index 100% rename from sop-website/src/main/java/com/gitee/sop/websiteserver/sign/StreamUtil.java rename to sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/sign/StreamUtil.java diff --git a/sop-website/src/main/java/com/gitee/sop/websiteserver/sign/StringUtils.java b/sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/sign/StringUtils.java similarity index 100% rename from sop-website/src/main/java/com/gitee/sop/websiteserver/sign/StringUtils.java rename to sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/sign/StringUtils.java diff --git a/sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/util/GenerateUtil.java b/sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/util/GenerateUtil.java new file mode 100644 index 00000000..755eef45 --- /dev/null +++ b/sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/util/GenerateUtil.java @@ -0,0 +1,56 @@ +package com.gitee.sop.websiteserver.util; + +import com.gitee.sop.websiteserver.bean.SnowflakeIdWorker; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.codec.digest.DigestUtils; +import org.springframework.util.Base64Utils; + +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.UUID; + +/** + * @author tanghc + */ +@Slf4j +public class GenerateUtil { + + private static long workerId = 0; + private static SnowflakeIdWorker appIdWorker = new SnowflakeIdWorker(workerId++, 0); + private static SnowflakeIdWorker secretWorker = new SnowflakeIdWorker(workerId++, 0); + private static SnowflakeIdWorker fileWorker = new SnowflakeIdWorker(workerId++, 0); + private static SnowflakeIdWorker orderNoWorker = new SnowflakeIdWorker(workerId++, 1); + + public static String generateServiceId() { + return getUUID(); + } + + public static String generateIsvAppId() { + return LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMdd")) + appIdWorker.nextId(); + } + + public static String generateNamespace() { + return RandomUtil.createRandom(); + } + + public static String generateSecret() { + byte[] bytes = DigestUtils.sha256(secretWorker.nextId() + getUUID()); + return Base64Utils.encodeToUrlSafeString(bytes).substring(1).replace("=", ""); + } + + public static String getUserPassword(String username, String password) { + return DigestUtils.sha256Hex(username + password + username); + } + + public static String generateVerifyFile() { + return DigestUtils.md5Hex(String.valueOf(fileWorker.nextId())); + } + + public static String getUUID() { + return UUID.randomUUID().toString().replace("-", ""); + } + + public static String generateOrderNo() { + return String.valueOf(orderNoWorker.nextId()); + } +} diff --git a/sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/util/RandomUtil.java b/sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/util/RandomUtil.java new file mode 100644 index 00000000..1b5908a9 --- /dev/null +++ b/sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/util/RandomUtil.java @@ -0,0 +1,194 @@ +package com.gitee.sop.websiteserver.util; + +import java.util.Random; + +/** + * @author tanghc + */ +public class RandomUtil { + + /** + * 创建密码 + * + * @return 返回8位字母+数组密码 + */ + public static String createRandom() { + return new Password().getRandomPassword(); + } + + /** + * 只随机数字 + * + * @return + */ + public static String randomNumber() { + return new Password(Password.StrengthLevel.ONLY_NUMBER, 10).getRandomPassword(); + } + + /** + * 创建密码,指定强度和长度 + * + * @param strengthLevel 强度等级 + * @param length 长度 + * @return 返回密码 + */ + public static String createRandom(Password.StrengthLevel strengthLevel, int length) { + return new Password(strengthLevel, length).getRandomPassword(); + } + + public static class Password { + + + public Password() { + this(StrengthLevel.NUMBER_LOWERCASE, 8); + } + + /** + * @param strengthLevel 强度等级 + * @param length 长度 + */ + public Password(StrengthLevel strengthLevel, int length) { + if (length <= 0) { + throw new IllegalArgumentException("length can not <= 0"); + } + this.strengthLevel = strengthLevel; + this.length = length; + } + + /** + * 强度等级 + */ + private final StrengthLevel strengthLevel; + + /** + * 需要的密码长度 + */ + private final int length; + + private static final Random RANDOM = new Random(); + + /** + * 因为伪随机数random在nextInt()的时候会出现向更小的数偏离的情况,所以用一个randomMax来修正 + */ + private static final int RANDOM_MAX = 100000000; + /** + * 因为数组下标从0开始 + */ + private static final int INDEX = 1; + /** + * 一共26个字母 + */ + private static final int NUMBER_OF_LETTER = 26; + /** + * ascii码表数字从48开始 + */ + private static final int NUMBER_INDEX = 48; + /** + * 数组下标0-9代表10个数字 + */ + private static final int NUMBER_MAX = 9; + /** + * 大写字母ascii码表从65开始 + */ + private static final int CAPITAL_INDEX = 65; + /** + * 小写字母ascii码表从65开始 + */ + private static final int LOWERCASE_INDEX = 97; + /** + * 特殊字符的起始ascii码表序号取第一个数字 + */ + private static final int SPECIAL = 0; + /** + * 特殊字符集,第一个数字代表ascii码表序号,第二个代表从这里开始一共使用多少个字符 + */ + private static final int[][] SPECIAL_CHARACTER = {{33, 14}, {58, 6}, {91, 5}, {123, 3}}; + + /** + * 取随机密码 + * + * @return 返回随机密码 + */ + public String getRandomPassword() { + StringBuilder builder = new StringBuilder(); + for (int i = 0; i < length; i++) { + builder.append((char) getNextChar()); + } + return builder.toString(); + } + + /** + * 取得下一个ascii编码 + * + * @return 返回ascii码 + */ + private int getNextChar() { + // ascii编码 + int x = 0; + // 伪字符ascii编码 + final int puppetLetter = RANDOM.nextInt(RANDOM_MAX) % NUMBER_OF_LETTER; + // 伪数字ascii编码 + final int puppetNumber = RANDOM.nextInt(RANDOM_MAX) % NUMBER_MAX + NUMBER_INDEX; + // 按照字符等级强度取ascii值 + final int levelIndex = RANDOM.nextInt(RANDOM_MAX) % strengthLevel.level; + // 特殊字符的随机集合下标(数组第一维) + final int specialType = RANDOM.nextInt(RANDOM_MAX) % SPECIAL_CHARACTER.length; + // 特殊字符的ascii编码 + final int specialInt = RANDOM.nextInt(RANDOM_MAX) % SPECIAL_CHARACTER[specialType][INDEX] + SPECIAL_CHARACTER[specialType][SPECIAL]; + // 根据密码强度等级获取随机ascii编码 + switch (strengthLevel) { + case ONLY_NUMBER: + x = puppetNumber; + break; + case NUMBER_LOWERCASE: + x = levelIndex == INDEX ? puppetNumber : puppetLetter + LOWERCASE_INDEX; + break; + case NUMBER_LOWERCASE_CAPITAL: + if (levelIndex == 0) { + x = puppetNumber; + } else if (levelIndex == INDEX) { + x = puppetLetter + LOWERCASE_INDEX; + } else { + x = puppetLetter + CAPITAL_INDEX; + } + break; + case NUMBER_LOWERCASE_CAPITAL_CHARACTER: + if (levelIndex == 0) { + x = puppetNumber; + } else if (levelIndex == INDEX) { + x = puppetLetter + LOWERCASE_INDEX; + } else if (levelIndex == INDEX * 2) { + x = puppetLetter + CAPITAL_INDEX; + } else { + x = specialInt; + } + break; + default: + } + return x; + } + + /** + * Password level config + * 1 level : only number + * 2 level : number and lowercase letters + * 3 level : number , lowercase letters , capital letters + * 4 level : number , lowercase letters , capital letters , special characters + */ + public enum StrengthLevel { + ONLY_NUMBER(1), + NUMBER_LOWERCASE(2), + NUMBER_LOWERCASE_CAPITAL(3), + NUMBER_LOWERCASE_CAPITAL_CHARACTER(4), + ; + + StrengthLevel(int level) { + this.level = level; + } + + private int level; + + } + } + +} diff --git a/sop-website/src/main/java/com/gitee/sop/websiteserver/util/UploadUtil.java b/sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/util/UploadUtil.java similarity index 100% rename from sop-website/src/main/java/com/gitee/sop/websiteserver/util/UploadUtil.java rename to sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/util/UploadUtil.java diff --git a/sop-website/src/main/java/com/gitee/sop/websiteserver/vo/DocBaseInfoVO.java b/sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/vo/DocBaseInfoVO.java similarity index 100% rename from sop-website/src/main/java/com/gitee/sop/websiteserver/vo/DocBaseInfoVO.java rename to sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/vo/DocBaseInfoVO.java diff --git a/sop-website/src/main/java/com/gitee/sop/websiteserver/vo/DocInfoVO.java b/sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/vo/DocInfoVO.java similarity index 100% rename from sop-website/src/main/java/com/gitee/sop/websiteserver/vo/DocInfoVO.java rename to sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/vo/DocInfoVO.java diff --git a/sop-website/src/main/java/com/gitee/sop/websiteserver/vo/ServiceInfoVO.java b/sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/vo/ServiceInfoVO.java similarity index 100% rename from sop-website/src/main/java/com/gitee/sop/websiteserver/vo/ServiceInfoVO.java rename to sop-website/sop-website-server/src/main/java/com/gitee/sop/websiteserver/vo/ServiceInfoVO.java diff --git a/sop-website/src/main/resources/META-INF/spring.factories b/sop-website/sop-website-server/src/main/resources/META-INF/spring.factories similarity index 100% rename from sop-website/src/main/resources/META-INF/spring.factories rename to sop-website/sop-website-server/src/main/resources/META-INF/spring.factories diff --git a/sop-website/sop-website-server/src/main/resources/application-dev.properties b/sop-website/sop-website-server/src/main/resources/application-dev.properties new file mode 100644 index 00000000..1b15a8d4 --- /dev/null +++ b/sop-website/sop-website-server/src/main/resources/application-dev.properties @@ -0,0 +1,40 @@ +server.port=8083 +spring.application.name=sop-website + +# ------- 需要改的配置 ------- + +# mysql数据库账号 +mysql.host=localhost:3306 +mysql.username=root +mysql.password=root +# 注册中心地址 +register.url=127.0.0.1:8848 + +# 网关内网地址 +# !!沙箱环境是提供给外部的测试环境,不能把正式环境当做沙箱环境来使用。 +gateway.url=http://localhost:8081 + +logging.level.com.gitee=debug +# ------- 需要改的配置end ------- + +## nacos cloud配置 +spring.cloud.nacos.discovery.server-addr=${register.url} + +# 页面上显示的测试环境地址 +api.url-test=http://open-test.yourdomain.com +# 沙箱环境 +api.url-sandbox=${gateway.url} +# 页面上显示的正式环境地址 +api.url-prod=http://open.yourdomain.com +api.pwd=doc#123 + +# 数据库配置 +spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver +spring.datasource.url=jdbc:mysql://${mysql.host}/sop?useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&serverTimezone=Asia/Shanghai +spring.datasource.username=${mysql.username} +spring.datasource.password=${mysql.password} + +mybatis.base-package=com.gitee.sop.websiteserver +mybatis.mapper-locations=classpath:mybatis/mapper/*.xml +mybatis.fill.com.gitee.fastmybatis.core.support.DateFillInsert=gmt_create +mybatis.fill.com.gitee.fastmybatis.core.support.DateFillUpdate=gmt_modified diff --git a/sop-website/src/main/resources/application.properties b/sop-website/sop-website-server/src/main/resources/application.properties similarity index 100% rename from sop-website/src/main/resources/application.properties rename to sop-website/sop-website-server/src/main/resources/application.properties diff --git a/sop-website/src/main/resources/public/assets/css/global.css b/sop-website/sop-website-server/src/main/resources/public/assets/css/global.css similarity index 100% rename from sop-website/src/main/resources/public/assets/css/global.css rename to sop-website/sop-website-server/src/main/resources/public/assets/css/global.css diff --git a/sop-website/src/main/resources/public/assets/image/auth.png b/sop-website/sop-website-server/src/main/resources/public/assets/image/auth.png similarity index 100% rename from sop-website/src/main/resources/public/assets/image/auth.png rename to sop-website/sop-website-server/src/main/resources/public/assets/image/auth.png diff --git a/sop-website/src/main/resources/public/assets/js/format.js b/sop-website/sop-website-server/src/main/resources/public/assets/js/format.js similarity index 100% rename from sop-website/src/main/resources/public/assets/js/format.js rename to sop-website/sop-website-server/src/main/resources/public/assets/js/format.js diff --git a/sop-website/src/main/resources/public/assets/js/inputcache.js b/sop-website/sop-website-server/src/main/resources/public/assets/js/inputcache.js similarity index 100% rename from sop-website/src/main/resources/public/assets/js/inputcache.js rename to sop-website/sop-website-server/src/main/resources/public/assets/js/inputcache.js diff --git a/sop-website/src/main/resources/public/assets/lib/jquery/3.2.1/jquery.min.js b/sop-website/sop-website-server/src/main/resources/public/assets/lib/jquery/3.2.1/jquery.min.js similarity index 100% rename from sop-website/src/main/resources/public/assets/lib/jquery/3.2.1/jquery.min.js rename to sop-website/sop-website-server/src/main/resources/public/assets/lib/jquery/3.2.1/jquery.min.js diff --git a/sop-website/src/main/resources/public/assets/lib/layui/css/layui.css b/sop-website/sop-website-server/src/main/resources/public/assets/lib/layui/css/layui.css similarity index 100% rename from sop-website/src/main/resources/public/assets/lib/layui/css/layui.css rename to sop-website/sop-website-server/src/main/resources/public/assets/lib/layui/css/layui.css diff --git a/sop-website/src/main/resources/public/assets/lib/layui/css/layui.mobile.css b/sop-website/sop-website-server/src/main/resources/public/assets/lib/layui/css/layui.mobile.css similarity index 100% rename from sop-website/src/main/resources/public/assets/lib/layui/css/layui.mobile.css rename to sop-website/sop-website-server/src/main/resources/public/assets/lib/layui/css/layui.mobile.css diff --git a/sop-website/src/main/resources/public/assets/lib/layui/css/modules/code.css b/sop-website/sop-website-server/src/main/resources/public/assets/lib/layui/css/modules/code.css similarity index 100% rename from sop-website/src/main/resources/public/assets/lib/layui/css/modules/code.css rename to sop-website/sop-website-server/src/main/resources/public/assets/lib/layui/css/modules/code.css diff --git a/sop-website/src/main/resources/public/assets/lib/layui/css/modules/laydate/default/laydate.css b/sop-website/sop-website-server/src/main/resources/public/assets/lib/layui/css/modules/laydate/default/laydate.css similarity index 100% rename from sop-website/src/main/resources/public/assets/lib/layui/css/modules/laydate/default/laydate.css rename to sop-website/sop-website-server/src/main/resources/public/assets/lib/layui/css/modules/laydate/default/laydate.css diff --git a/sop-website/src/main/resources/public/assets/lib/layui/css/modules/layer/default/layer.css b/sop-website/sop-website-server/src/main/resources/public/assets/lib/layui/css/modules/layer/default/layer.css similarity index 100% rename from sop-website/src/main/resources/public/assets/lib/layui/css/modules/layer/default/layer.css rename to sop-website/sop-website-server/src/main/resources/public/assets/lib/layui/css/modules/layer/default/layer.css diff --git a/sop-website/src/main/resources/public/assets/lib/layui/css/modules/layer/default/loading-0.gif b/sop-website/sop-website-server/src/main/resources/public/assets/lib/layui/css/modules/layer/default/loading-0.gif similarity index 100% rename from sop-website/src/main/resources/public/assets/lib/layui/css/modules/layer/default/loading-0.gif rename to sop-website/sop-website-server/src/main/resources/public/assets/lib/layui/css/modules/layer/default/loading-0.gif diff --git a/sop-website/src/main/resources/public/assets/lib/layui/css/modules/layer/default/loading-1.gif b/sop-website/sop-website-server/src/main/resources/public/assets/lib/layui/css/modules/layer/default/loading-1.gif similarity index 100% rename from sop-website/src/main/resources/public/assets/lib/layui/css/modules/layer/default/loading-1.gif rename to sop-website/sop-website-server/src/main/resources/public/assets/lib/layui/css/modules/layer/default/loading-1.gif diff --git a/sop-website/src/main/resources/public/assets/lib/layui/css/modules/layer/default/loading-2.gif b/sop-website/sop-website-server/src/main/resources/public/assets/lib/layui/css/modules/layer/default/loading-2.gif similarity index 100% rename from sop-website/src/main/resources/public/assets/lib/layui/css/modules/layer/default/loading-2.gif rename to sop-website/sop-website-server/src/main/resources/public/assets/lib/layui/css/modules/layer/default/loading-2.gif diff --git a/sop-website/src/main/resources/public/assets/lib/layui/font/iconfont.eot b/sop-website/sop-website-server/src/main/resources/public/assets/lib/layui/font/iconfont.eot similarity index 100% rename from sop-website/src/main/resources/public/assets/lib/layui/font/iconfont.eot rename to sop-website/sop-website-server/src/main/resources/public/assets/lib/layui/font/iconfont.eot diff --git a/sop-website/src/main/resources/public/assets/lib/layui/font/iconfont.svg b/sop-website/sop-website-server/src/main/resources/public/assets/lib/layui/font/iconfont.svg similarity index 100% rename from sop-website/src/main/resources/public/assets/lib/layui/font/iconfont.svg rename to sop-website/sop-website-server/src/main/resources/public/assets/lib/layui/font/iconfont.svg diff --git a/sop-website/src/main/resources/public/assets/lib/layui/font/iconfont.ttf b/sop-website/sop-website-server/src/main/resources/public/assets/lib/layui/font/iconfont.ttf similarity index 100% rename from sop-website/src/main/resources/public/assets/lib/layui/font/iconfont.ttf rename to sop-website/sop-website-server/src/main/resources/public/assets/lib/layui/font/iconfont.ttf diff --git a/sop-website/src/main/resources/public/assets/lib/layui/font/iconfont.woff b/sop-website/sop-website-server/src/main/resources/public/assets/lib/layui/font/iconfont.woff similarity index 100% rename from sop-website/src/main/resources/public/assets/lib/layui/font/iconfont.woff rename to sop-website/sop-website-server/src/main/resources/public/assets/lib/layui/font/iconfont.woff diff --git a/sop-website/src/main/resources/public/assets/lib/layui/images/face/0.gif b/sop-website/sop-website-server/src/main/resources/public/assets/lib/layui/images/face/0.gif similarity index 100% rename from sop-website/src/main/resources/public/assets/lib/layui/images/face/0.gif rename to sop-website/sop-website-server/src/main/resources/public/assets/lib/layui/images/face/0.gif diff --git a/sop-website/src/main/resources/public/assets/lib/layui/images/face/1.gif b/sop-website/sop-website-server/src/main/resources/public/assets/lib/layui/images/face/1.gif similarity index 100% rename from sop-website/src/main/resources/public/assets/lib/layui/images/face/1.gif rename to sop-website/sop-website-server/src/main/resources/public/assets/lib/layui/images/face/1.gif diff --git a/sop-website/src/main/resources/public/assets/lib/layui/images/face/10.gif b/sop-website/sop-website-server/src/main/resources/public/assets/lib/layui/images/face/10.gif similarity index 100% rename from sop-website/src/main/resources/public/assets/lib/layui/images/face/10.gif rename to sop-website/sop-website-server/src/main/resources/public/assets/lib/layui/images/face/10.gif diff --git a/sop-website/src/main/resources/public/assets/lib/layui/images/face/11.gif b/sop-website/sop-website-server/src/main/resources/public/assets/lib/layui/images/face/11.gif similarity index 100% rename from sop-website/src/main/resources/public/assets/lib/layui/images/face/11.gif rename to sop-website/sop-website-server/src/main/resources/public/assets/lib/layui/images/face/11.gif diff --git a/sop-website/src/main/resources/public/assets/lib/layui/images/face/12.gif b/sop-website/sop-website-server/src/main/resources/public/assets/lib/layui/images/face/12.gif similarity index 100% rename from sop-website/src/main/resources/public/assets/lib/layui/images/face/12.gif rename to sop-website/sop-website-server/src/main/resources/public/assets/lib/layui/images/face/12.gif diff --git a/sop-website/src/main/resources/public/assets/lib/layui/images/face/13.gif b/sop-website/sop-website-server/src/main/resources/public/assets/lib/layui/images/face/13.gif similarity index 100% rename from sop-website/src/main/resources/public/assets/lib/layui/images/face/13.gif rename to sop-website/sop-website-server/src/main/resources/public/assets/lib/layui/images/face/13.gif diff --git a/sop-website/src/main/resources/public/assets/lib/layui/images/face/14.gif b/sop-website/sop-website-server/src/main/resources/public/assets/lib/layui/images/face/14.gif similarity index 100% rename from sop-website/src/main/resources/public/assets/lib/layui/images/face/14.gif rename to sop-website/sop-website-server/src/main/resources/public/assets/lib/layui/images/face/14.gif diff --git a/sop-website/src/main/resources/public/assets/lib/layui/images/face/15.gif b/sop-website/sop-website-server/src/main/resources/public/assets/lib/layui/images/face/15.gif similarity index 100% rename from sop-website/src/main/resources/public/assets/lib/layui/images/face/15.gif rename to sop-website/sop-website-server/src/main/resources/public/assets/lib/layui/images/face/15.gif diff --git a/sop-website/src/main/resources/public/assets/lib/layui/images/face/16.gif b/sop-website/sop-website-server/src/main/resources/public/assets/lib/layui/images/face/16.gif similarity index 100% rename from sop-website/src/main/resources/public/assets/lib/layui/images/face/16.gif rename to sop-website/sop-website-server/src/main/resources/public/assets/lib/layui/images/face/16.gif diff --git a/sop-website/src/main/resources/public/assets/lib/layui/images/face/17.gif b/sop-website/sop-website-server/src/main/resources/public/assets/lib/layui/images/face/17.gif similarity index 100% rename from sop-website/src/main/resources/public/assets/lib/layui/images/face/17.gif rename to sop-website/sop-website-server/src/main/resources/public/assets/lib/layui/images/face/17.gif diff --git a/sop-website/src/main/resources/public/assets/lib/layui/images/face/18.gif b/sop-website/sop-website-server/src/main/resources/public/assets/lib/layui/images/face/18.gif similarity index 100% rename from sop-website/src/main/resources/public/assets/lib/layui/images/face/18.gif rename to sop-website/sop-website-server/src/main/resources/public/assets/lib/layui/images/face/18.gif diff --git a/sop-website/src/main/resources/public/assets/lib/layui/images/face/19.gif b/sop-website/sop-website-server/src/main/resources/public/assets/lib/layui/images/face/19.gif similarity index 100% rename from sop-website/src/main/resources/public/assets/lib/layui/images/face/19.gif rename to sop-website/sop-website-server/src/main/resources/public/assets/lib/layui/images/face/19.gif diff --git a/sop-website/src/main/resources/public/assets/lib/layui/images/face/2.gif b/sop-website/sop-website-server/src/main/resources/public/assets/lib/layui/images/face/2.gif similarity index 100% rename from sop-website/src/main/resources/public/assets/lib/layui/images/face/2.gif rename to sop-website/sop-website-server/src/main/resources/public/assets/lib/layui/images/face/2.gif diff --git a/sop-website/src/main/resources/public/assets/lib/layui/images/face/20.gif b/sop-website/sop-website-server/src/main/resources/public/assets/lib/layui/images/face/20.gif similarity index 100% rename from sop-website/src/main/resources/public/assets/lib/layui/images/face/20.gif rename to sop-website/sop-website-server/src/main/resources/public/assets/lib/layui/images/face/20.gif diff --git a/sop-website/src/main/resources/public/assets/lib/layui/images/face/21.gif b/sop-website/sop-website-server/src/main/resources/public/assets/lib/layui/images/face/21.gif similarity index 100% rename from sop-website/src/main/resources/public/assets/lib/layui/images/face/21.gif rename to sop-website/sop-website-server/src/main/resources/public/assets/lib/layui/images/face/21.gif diff --git a/sop-website/src/main/resources/public/assets/lib/layui/images/face/22.gif b/sop-website/sop-website-server/src/main/resources/public/assets/lib/layui/images/face/22.gif similarity index 100% rename from sop-website/src/main/resources/public/assets/lib/layui/images/face/22.gif rename to sop-website/sop-website-server/src/main/resources/public/assets/lib/layui/images/face/22.gif diff --git a/sop-website/src/main/resources/public/assets/lib/layui/images/face/23.gif b/sop-website/sop-website-server/src/main/resources/public/assets/lib/layui/images/face/23.gif similarity index 100% rename from sop-website/src/main/resources/public/assets/lib/layui/images/face/23.gif rename to sop-website/sop-website-server/src/main/resources/public/assets/lib/layui/images/face/23.gif diff --git a/sop-website/src/main/resources/public/assets/lib/layui/images/face/24.gif b/sop-website/sop-website-server/src/main/resources/public/assets/lib/layui/images/face/24.gif similarity index 100% rename from sop-website/src/main/resources/public/assets/lib/layui/images/face/24.gif rename to sop-website/sop-website-server/src/main/resources/public/assets/lib/layui/images/face/24.gif diff --git a/sop-website/src/main/resources/public/assets/lib/layui/images/face/25.gif b/sop-website/sop-website-server/src/main/resources/public/assets/lib/layui/images/face/25.gif similarity index 100% rename from sop-website/src/main/resources/public/assets/lib/layui/images/face/25.gif rename to sop-website/sop-website-server/src/main/resources/public/assets/lib/layui/images/face/25.gif diff --git a/sop-website/src/main/resources/public/assets/lib/layui/images/face/26.gif b/sop-website/sop-website-server/src/main/resources/public/assets/lib/layui/images/face/26.gif similarity index 100% rename from sop-website/src/main/resources/public/assets/lib/layui/images/face/26.gif rename to sop-website/sop-website-server/src/main/resources/public/assets/lib/layui/images/face/26.gif diff --git a/sop-website/src/main/resources/public/assets/lib/layui/images/face/27.gif b/sop-website/sop-website-server/src/main/resources/public/assets/lib/layui/images/face/27.gif similarity index 100% rename from sop-website/src/main/resources/public/assets/lib/layui/images/face/27.gif rename to sop-website/sop-website-server/src/main/resources/public/assets/lib/layui/images/face/27.gif diff --git a/sop-website/src/main/resources/public/assets/lib/layui/images/face/28.gif b/sop-website/sop-website-server/src/main/resources/public/assets/lib/layui/images/face/28.gif similarity index 100% rename from sop-website/src/main/resources/public/assets/lib/layui/images/face/28.gif rename to sop-website/sop-website-server/src/main/resources/public/assets/lib/layui/images/face/28.gif diff --git a/sop-website/src/main/resources/public/assets/lib/layui/images/face/29.gif b/sop-website/sop-website-server/src/main/resources/public/assets/lib/layui/images/face/29.gif similarity index 100% rename from sop-website/src/main/resources/public/assets/lib/layui/images/face/29.gif rename to sop-website/sop-website-server/src/main/resources/public/assets/lib/layui/images/face/29.gif diff --git a/sop-website/src/main/resources/public/assets/lib/layui/images/face/3.gif b/sop-website/sop-website-server/src/main/resources/public/assets/lib/layui/images/face/3.gif similarity index 100% rename from sop-website/src/main/resources/public/assets/lib/layui/images/face/3.gif rename to sop-website/sop-website-server/src/main/resources/public/assets/lib/layui/images/face/3.gif diff --git a/sop-website/src/main/resources/public/assets/lib/layui/images/face/30.gif b/sop-website/sop-website-server/src/main/resources/public/assets/lib/layui/images/face/30.gif similarity index 100% rename from sop-website/src/main/resources/public/assets/lib/layui/images/face/30.gif rename to sop-website/sop-website-server/src/main/resources/public/assets/lib/layui/images/face/30.gif diff --git a/sop-website/src/main/resources/public/assets/lib/layui/images/face/31.gif b/sop-website/sop-website-server/src/main/resources/public/assets/lib/layui/images/face/31.gif similarity index 100% rename from sop-website/src/main/resources/public/assets/lib/layui/images/face/31.gif rename to sop-website/sop-website-server/src/main/resources/public/assets/lib/layui/images/face/31.gif diff --git a/sop-website/src/main/resources/public/assets/lib/layui/images/face/32.gif b/sop-website/sop-website-server/src/main/resources/public/assets/lib/layui/images/face/32.gif similarity index 100% rename from sop-website/src/main/resources/public/assets/lib/layui/images/face/32.gif rename to sop-website/sop-website-server/src/main/resources/public/assets/lib/layui/images/face/32.gif diff --git a/sop-website/src/main/resources/public/assets/lib/layui/images/face/33.gif b/sop-website/sop-website-server/src/main/resources/public/assets/lib/layui/images/face/33.gif similarity index 100% rename from sop-website/src/main/resources/public/assets/lib/layui/images/face/33.gif rename to sop-website/sop-website-server/src/main/resources/public/assets/lib/layui/images/face/33.gif diff --git a/sop-website/src/main/resources/public/assets/lib/layui/images/face/34.gif b/sop-website/sop-website-server/src/main/resources/public/assets/lib/layui/images/face/34.gif similarity index 100% rename from sop-website/src/main/resources/public/assets/lib/layui/images/face/34.gif rename to sop-website/sop-website-server/src/main/resources/public/assets/lib/layui/images/face/34.gif diff --git a/sop-website/src/main/resources/public/assets/lib/layui/images/face/35.gif b/sop-website/sop-website-server/src/main/resources/public/assets/lib/layui/images/face/35.gif similarity index 100% rename from sop-website/src/main/resources/public/assets/lib/layui/images/face/35.gif rename to sop-website/sop-website-server/src/main/resources/public/assets/lib/layui/images/face/35.gif diff --git a/sop-website/src/main/resources/public/assets/lib/layui/images/face/36.gif b/sop-website/sop-website-server/src/main/resources/public/assets/lib/layui/images/face/36.gif similarity index 100% rename from sop-website/src/main/resources/public/assets/lib/layui/images/face/36.gif rename to sop-website/sop-website-server/src/main/resources/public/assets/lib/layui/images/face/36.gif diff --git a/sop-website/src/main/resources/public/assets/lib/layui/images/face/37.gif b/sop-website/sop-website-server/src/main/resources/public/assets/lib/layui/images/face/37.gif similarity index 100% rename from sop-website/src/main/resources/public/assets/lib/layui/images/face/37.gif rename to sop-website/sop-website-server/src/main/resources/public/assets/lib/layui/images/face/37.gif diff --git a/sop-website/src/main/resources/public/assets/lib/layui/images/face/38.gif b/sop-website/sop-website-server/src/main/resources/public/assets/lib/layui/images/face/38.gif similarity index 100% rename from sop-website/src/main/resources/public/assets/lib/layui/images/face/38.gif rename to sop-website/sop-website-server/src/main/resources/public/assets/lib/layui/images/face/38.gif diff --git a/sop-website/src/main/resources/public/assets/lib/layui/images/face/39.gif b/sop-website/sop-website-server/src/main/resources/public/assets/lib/layui/images/face/39.gif similarity index 100% rename from sop-website/src/main/resources/public/assets/lib/layui/images/face/39.gif rename to sop-website/sop-website-server/src/main/resources/public/assets/lib/layui/images/face/39.gif diff --git a/sop-website/src/main/resources/public/assets/lib/layui/images/face/4.gif b/sop-website/sop-website-server/src/main/resources/public/assets/lib/layui/images/face/4.gif similarity index 100% rename from sop-website/src/main/resources/public/assets/lib/layui/images/face/4.gif rename to sop-website/sop-website-server/src/main/resources/public/assets/lib/layui/images/face/4.gif diff --git a/sop-website/src/main/resources/public/assets/lib/layui/images/face/40.gif b/sop-website/sop-website-server/src/main/resources/public/assets/lib/layui/images/face/40.gif similarity index 100% rename from sop-website/src/main/resources/public/assets/lib/layui/images/face/40.gif rename to sop-website/sop-website-server/src/main/resources/public/assets/lib/layui/images/face/40.gif diff --git a/sop-website/src/main/resources/public/assets/lib/layui/images/face/41.gif b/sop-website/sop-website-server/src/main/resources/public/assets/lib/layui/images/face/41.gif similarity index 100% rename from sop-website/src/main/resources/public/assets/lib/layui/images/face/41.gif rename to sop-website/sop-website-server/src/main/resources/public/assets/lib/layui/images/face/41.gif diff --git a/sop-website/src/main/resources/public/assets/lib/layui/images/face/42.gif b/sop-website/sop-website-server/src/main/resources/public/assets/lib/layui/images/face/42.gif similarity index 100% rename from sop-website/src/main/resources/public/assets/lib/layui/images/face/42.gif rename to sop-website/sop-website-server/src/main/resources/public/assets/lib/layui/images/face/42.gif diff --git a/sop-website/src/main/resources/public/assets/lib/layui/images/face/43.gif b/sop-website/sop-website-server/src/main/resources/public/assets/lib/layui/images/face/43.gif similarity index 100% rename from sop-website/src/main/resources/public/assets/lib/layui/images/face/43.gif rename to sop-website/sop-website-server/src/main/resources/public/assets/lib/layui/images/face/43.gif diff --git a/sop-website/src/main/resources/public/assets/lib/layui/images/face/44.gif b/sop-website/sop-website-server/src/main/resources/public/assets/lib/layui/images/face/44.gif similarity index 100% rename from sop-website/src/main/resources/public/assets/lib/layui/images/face/44.gif rename to sop-website/sop-website-server/src/main/resources/public/assets/lib/layui/images/face/44.gif diff --git a/sop-website/src/main/resources/public/assets/lib/layui/images/face/45.gif b/sop-website/sop-website-server/src/main/resources/public/assets/lib/layui/images/face/45.gif similarity index 100% rename from sop-website/src/main/resources/public/assets/lib/layui/images/face/45.gif rename to sop-website/sop-website-server/src/main/resources/public/assets/lib/layui/images/face/45.gif diff --git a/sop-website/src/main/resources/public/assets/lib/layui/images/face/46.gif b/sop-website/sop-website-server/src/main/resources/public/assets/lib/layui/images/face/46.gif similarity index 100% rename from sop-website/src/main/resources/public/assets/lib/layui/images/face/46.gif rename to sop-website/sop-website-server/src/main/resources/public/assets/lib/layui/images/face/46.gif diff --git a/sop-website/src/main/resources/public/assets/lib/layui/images/face/47.gif b/sop-website/sop-website-server/src/main/resources/public/assets/lib/layui/images/face/47.gif similarity index 100% rename from sop-website/src/main/resources/public/assets/lib/layui/images/face/47.gif rename to sop-website/sop-website-server/src/main/resources/public/assets/lib/layui/images/face/47.gif diff --git a/sop-website/src/main/resources/public/assets/lib/layui/images/face/48.gif b/sop-website/sop-website-server/src/main/resources/public/assets/lib/layui/images/face/48.gif similarity index 100% rename from sop-website/src/main/resources/public/assets/lib/layui/images/face/48.gif rename to sop-website/sop-website-server/src/main/resources/public/assets/lib/layui/images/face/48.gif diff --git a/sop-website/src/main/resources/public/assets/lib/layui/images/face/49.gif b/sop-website/sop-website-server/src/main/resources/public/assets/lib/layui/images/face/49.gif similarity index 100% rename from sop-website/src/main/resources/public/assets/lib/layui/images/face/49.gif rename to sop-website/sop-website-server/src/main/resources/public/assets/lib/layui/images/face/49.gif diff --git a/sop-website/src/main/resources/public/assets/lib/layui/images/face/5.gif b/sop-website/sop-website-server/src/main/resources/public/assets/lib/layui/images/face/5.gif similarity index 100% rename from sop-website/src/main/resources/public/assets/lib/layui/images/face/5.gif rename to sop-website/sop-website-server/src/main/resources/public/assets/lib/layui/images/face/5.gif diff --git a/sop-website/src/main/resources/public/assets/lib/layui/images/face/50.gif b/sop-website/sop-website-server/src/main/resources/public/assets/lib/layui/images/face/50.gif similarity index 100% rename from sop-website/src/main/resources/public/assets/lib/layui/images/face/50.gif rename to sop-website/sop-website-server/src/main/resources/public/assets/lib/layui/images/face/50.gif diff --git a/sop-website/src/main/resources/public/assets/lib/layui/images/face/51.gif b/sop-website/sop-website-server/src/main/resources/public/assets/lib/layui/images/face/51.gif similarity index 100% rename from sop-website/src/main/resources/public/assets/lib/layui/images/face/51.gif rename to sop-website/sop-website-server/src/main/resources/public/assets/lib/layui/images/face/51.gif diff --git a/sop-website/src/main/resources/public/assets/lib/layui/images/face/52.gif b/sop-website/sop-website-server/src/main/resources/public/assets/lib/layui/images/face/52.gif similarity index 100% rename from sop-website/src/main/resources/public/assets/lib/layui/images/face/52.gif rename to sop-website/sop-website-server/src/main/resources/public/assets/lib/layui/images/face/52.gif diff --git a/sop-website/src/main/resources/public/assets/lib/layui/images/face/53.gif b/sop-website/sop-website-server/src/main/resources/public/assets/lib/layui/images/face/53.gif similarity index 100% rename from sop-website/src/main/resources/public/assets/lib/layui/images/face/53.gif rename to sop-website/sop-website-server/src/main/resources/public/assets/lib/layui/images/face/53.gif diff --git a/sop-website/src/main/resources/public/assets/lib/layui/images/face/54.gif b/sop-website/sop-website-server/src/main/resources/public/assets/lib/layui/images/face/54.gif similarity index 100% rename from sop-website/src/main/resources/public/assets/lib/layui/images/face/54.gif rename to sop-website/sop-website-server/src/main/resources/public/assets/lib/layui/images/face/54.gif diff --git a/sop-website/src/main/resources/public/assets/lib/layui/images/face/55.gif b/sop-website/sop-website-server/src/main/resources/public/assets/lib/layui/images/face/55.gif similarity index 100% rename from sop-website/src/main/resources/public/assets/lib/layui/images/face/55.gif rename to sop-website/sop-website-server/src/main/resources/public/assets/lib/layui/images/face/55.gif diff --git a/sop-website/src/main/resources/public/assets/lib/layui/images/face/56.gif b/sop-website/sop-website-server/src/main/resources/public/assets/lib/layui/images/face/56.gif similarity index 100% rename from sop-website/src/main/resources/public/assets/lib/layui/images/face/56.gif rename to sop-website/sop-website-server/src/main/resources/public/assets/lib/layui/images/face/56.gif diff --git a/sop-website/src/main/resources/public/assets/lib/layui/images/face/57.gif b/sop-website/sop-website-server/src/main/resources/public/assets/lib/layui/images/face/57.gif similarity index 100% rename from sop-website/src/main/resources/public/assets/lib/layui/images/face/57.gif rename to sop-website/sop-website-server/src/main/resources/public/assets/lib/layui/images/face/57.gif diff --git a/sop-website/src/main/resources/public/assets/lib/layui/images/face/58.gif b/sop-website/sop-website-server/src/main/resources/public/assets/lib/layui/images/face/58.gif similarity index 100% rename from sop-website/src/main/resources/public/assets/lib/layui/images/face/58.gif rename to sop-website/sop-website-server/src/main/resources/public/assets/lib/layui/images/face/58.gif diff --git a/sop-website/src/main/resources/public/assets/lib/layui/images/face/59.gif b/sop-website/sop-website-server/src/main/resources/public/assets/lib/layui/images/face/59.gif similarity index 100% rename from sop-website/src/main/resources/public/assets/lib/layui/images/face/59.gif rename to sop-website/sop-website-server/src/main/resources/public/assets/lib/layui/images/face/59.gif diff --git a/sop-website/src/main/resources/public/assets/lib/layui/images/face/6.gif b/sop-website/sop-website-server/src/main/resources/public/assets/lib/layui/images/face/6.gif similarity index 100% rename from sop-website/src/main/resources/public/assets/lib/layui/images/face/6.gif rename to sop-website/sop-website-server/src/main/resources/public/assets/lib/layui/images/face/6.gif diff --git a/sop-website/src/main/resources/public/assets/lib/layui/images/face/60.gif b/sop-website/sop-website-server/src/main/resources/public/assets/lib/layui/images/face/60.gif similarity index 100% rename from sop-website/src/main/resources/public/assets/lib/layui/images/face/60.gif rename to sop-website/sop-website-server/src/main/resources/public/assets/lib/layui/images/face/60.gif diff --git a/sop-website/src/main/resources/public/assets/lib/layui/images/face/61.gif b/sop-website/sop-website-server/src/main/resources/public/assets/lib/layui/images/face/61.gif similarity index 100% rename from sop-website/src/main/resources/public/assets/lib/layui/images/face/61.gif rename to sop-website/sop-website-server/src/main/resources/public/assets/lib/layui/images/face/61.gif diff --git a/sop-website/src/main/resources/public/assets/lib/layui/images/face/62.gif b/sop-website/sop-website-server/src/main/resources/public/assets/lib/layui/images/face/62.gif similarity index 100% rename from sop-website/src/main/resources/public/assets/lib/layui/images/face/62.gif rename to sop-website/sop-website-server/src/main/resources/public/assets/lib/layui/images/face/62.gif diff --git a/sop-website/src/main/resources/public/assets/lib/layui/images/face/63.gif b/sop-website/sop-website-server/src/main/resources/public/assets/lib/layui/images/face/63.gif similarity index 100% rename from sop-website/src/main/resources/public/assets/lib/layui/images/face/63.gif rename to sop-website/sop-website-server/src/main/resources/public/assets/lib/layui/images/face/63.gif diff --git a/sop-website/src/main/resources/public/assets/lib/layui/images/face/64.gif b/sop-website/sop-website-server/src/main/resources/public/assets/lib/layui/images/face/64.gif similarity index 100% rename from sop-website/src/main/resources/public/assets/lib/layui/images/face/64.gif rename to sop-website/sop-website-server/src/main/resources/public/assets/lib/layui/images/face/64.gif diff --git a/sop-website/src/main/resources/public/assets/lib/layui/images/face/65.gif b/sop-website/sop-website-server/src/main/resources/public/assets/lib/layui/images/face/65.gif similarity index 100% rename from sop-website/src/main/resources/public/assets/lib/layui/images/face/65.gif rename to sop-website/sop-website-server/src/main/resources/public/assets/lib/layui/images/face/65.gif diff --git a/sop-website/src/main/resources/public/assets/lib/layui/images/face/66.gif b/sop-website/sop-website-server/src/main/resources/public/assets/lib/layui/images/face/66.gif similarity index 100% rename from sop-website/src/main/resources/public/assets/lib/layui/images/face/66.gif rename to sop-website/sop-website-server/src/main/resources/public/assets/lib/layui/images/face/66.gif diff --git a/sop-website/src/main/resources/public/assets/lib/layui/images/face/67.gif b/sop-website/sop-website-server/src/main/resources/public/assets/lib/layui/images/face/67.gif similarity index 100% rename from sop-website/src/main/resources/public/assets/lib/layui/images/face/67.gif rename to sop-website/sop-website-server/src/main/resources/public/assets/lib/layui/images/face/67.gif diff --git a/sop-website/src/main/resources/public/assets/lib/layui/images/face/68.gif b/sop-website/sop-website-server/src/main/resources/public/assets/lib/layui/images/face/68.gif similarity index 100% rename from sop-website/src/main/resources/public/assets/lib/layui/images/face/68.gif rename to sop-website/sop-website-server/src/main/resources/public/assets/lib/layui/images/face/68.gif diff --git a/sop-website/src/main/resources/public/assets/lib/layui/images/face/69.gif b/sop-website/sop-website-server/src/main/resources/public/assets/lib/layui/images/face/69.gif similarity index 100% rename from sop-website/src/main/resources/public/assets/lib/layui/images/face/69.gif rename to sop-website/sop-website-server/src/main/resources/public/assets/lib/layui/images/face/69.gif diff --git a/sop-website/src/main/resources/public/assets/lib/layui/images/face/7.gif b/sop-website/sop-website-server/src/main/resources/public/assets/lib/layui/images/face/7.gif similarity index 100% rename from sop-website/src/main/resources/public/assets/lib/layui/images/face/7.gif rename to sop-website/sop-website-server/src/main/resources/public/assets/lib/layui/images/face/7.gif diff --git a/sop-website/src/main/resources/public/assets/lib/layui/images/face/70.gif b/sop-website/sop-website-server/src/main/resources/public/assets/lib/layui/images/face/70.gif similarity index 100% rename from sop-website/src/main/resources/public/assets/lib/layui/images/face/70.gif rename to sop-website/sop-website-server/src/main/resources/public/assets/lib/layui/images/face/70.gif diff --git a/sop-website/src/main/resources/public/assets/lib/layui/images/face/71.gif b/sop-website/sop-website-server/src/main/resources/public/assets/lib/layui/images/face/71.gif similarity index 100% rename from sop-website/src/main/resources/public/assets/lib/layui/images/face/71.gif rename to sop-website/sop-website-server/src/main/resources/public/assets/lib/layui/images/face/71.gif diff --git a/sop-website/src/main/resources/public/assets/lib/layui/images/face/8.gif b/sop-website/sop-website-server/src/main/resources/public/assets/lib/layui/images/face/8.gif similarity index 100% rename from sop-website/src/main/resources/public/assets/lib/layui/images/face/8.gif rename to sop-website/sop-website-server/src/main/resources/public/assets/lib/layui/images/face/8.gif diff --git a/sop-website/src/main/resources/public/assets/lib/layui/images/face/9.gif b/sop-website/sop-website-server/src/main/resources/public/assets/lib/layui/images/face/9.gif similarity index 100% rename from sop-website/src/main/resources/public/assets/lib/layui/images/face/9.gif rename to sop-website/sop-website-server/src/main/resources/public/assets/lib/layui/images/face/9.gif diff --git a/sop-website/src/main/resources/public/assets/lib/layui/lay/modules/carousel.js b/sop-website/sop-website-server/src/main/resources/public/assets/lib/layui/lay/modules/carousel.js similarity index 100% rename from sop-website/src/main/resources/public/assets/lib/layui/lay/modules/carousel.js rename to sop-website/sop-website-server/src/main/resources/public/assets/lib/layui/lay/modules/carousel.js diff --git a/sop-website/src/main/resources/public/assets/lib/layui/lay/modules/code.js b/sop-website/sop-website-server/src/main/resources/public/assets/lib/layui/lay/modules/code.js similarity index 100% rename from sop-website/src/main/resources/public/assets/lib/layui/lay/modules/code.js rename to sop-website/sop-website-server/src/main/resources/public/assets/lib/layui/lay/modules/code.js diff --git a/sop-website/src/main/resources/public/assets/lib/layui/lay/modules/colorpicker.js b/sop-website/sop-website-server/src/main/resources/public/assets/lib/layui/lay/modules/colorpicker.js similarity index 100% rename from sop-website/src/main/resources/public/assets/lib/layui/lay/modules/colorpicker.js rename to sop-website/sop-website-server/src/main/resources/public/assets/lib/layui/lay/modules/colorpicker.js diff --git a/sop-website/src/main/resources/public/assets/lib/layui/lay/modules/element.js b/sop-website/sop-website-server/src/main/resources/public/assets/lib/layui/lay/modules/element.js similarity index 100% rename from sop-website/src/main/resources/public/assets/lib/layui/lay/modules/element.js rename to sop-website/sop-website-server/src/main/resources/public/assets/lib/layui/lay/modules/element.js diff --git a/sop-website/src/main/resources/public/assets/lib/layui/lay/modules/flow.js b/sop-website/sop-website-server/src/main/resources/public/assets/lib/layui/lay/modules/flow.js similarity index 100% rename from sop-website/src/main/resources/public/assets/lib/layui/lay/modules/flow.js rename to sop-website/sop-website-server/src/main/resources/public/assets/lib/layui/lay/modules/flow.js diff --git a/sop-website/src/main/resources/public/assets/lib/layui/lay/modules/form.js b/sop-website/sop-website-server/src/main/resources/public/assets/lib/layui/lay/modules/form.js similarity index 100% rename from sop-website/src/main/resources/public/assets/lib/layui/lay/modules/form.js rename to sop-website/sop-website-server/src/main/resources/public/assets/lib/layui/lay/modules/form.js diff --git a/sop-website/src/main/resources/public/assets/lib/layui/lay/modules/jquery.js b/sop-website/sop-website-server/src/main/resources/public/assets/lib/layui/lay/modules/jquery.js similarity index 100% rename from sop-website/src/main/resources/public/assets/lib/layui/lay/modules/jquery.js rename to sop-website/sop-website-server/src/main/resources/public/assets/lib/layui/lay/modules/jquery.js diff --git a/sop-website/src/main/resources/public/assets/lib/layui/lay/modules/laydate.js b/sop-website/sop-website-server/src/main/resources/public/assets/lib/layui/lay/modules/laydate.js similarity index 100% rename from sop-website/src/main/resources/public/assets/lib/layui/lay/modules/laydate.js rename to sop-website/sop-website-server/src/main/resources/public/assets/lib/layui/lay/modules/laydate.js diff --git a/sop-website/src/main/resources/public/assets/lib/layui/lay/modules/layedit.js b/sop-website/sop-website-server/src/main/resources/public/assets/lib/layui/lay/modules/layedit.js similarity index 100% rename from sop-website/src/main/resources/public/assets/lib/layui/lay/modules/layedit.js rename to sop-website/sop-website-server/src/main/resources/public/assets/lib/layui/lay/modules/layedit.js diff --git a/sop-website/src/main/resources/public/assets/lib/layui/lay/modules/layer.js b/sop-website/sop-website-server/src/main/resources/public/assets/lib/layui/lay/modules/layer.js similarity index 100% rename from sop-website/src/main/resources/public/assets/lib/layui/lay/modules/layer.js rename to sop-website/sop-website-server/src/main/resources/public/assets/lib/layui/lay/modules/layer.js diff --git a/sop-website/src/main/resources/public/assets/lib/layui/lay/modules/laypage.js b/sop-website/sop-website-server/src/main/resources/public/assets/lib/layui/lay/modules/laypage.js similarity index 100% rename from sop-website/src/main/resources/public/assets/lib/layui/lay/modules/laypage.js rename to sop-website/sop-website-server/src/main/resources/public/assets/lib/layui/lay/modules/laypage.js diff --git a/sop-website/src/main/resources/public/assets/lib/layui/lay/modules/laytpl.js b/sop-website/sop-website-server/src/main/resources/public/assets/lib/layui/lay/modules/laytpl.js similarity index 100% rename from sop-website/src/main/resources/public/assets/lib/layui/lay/modules/laytpl.js rename to sop-website/sop-website-server/src/main/resources/public/assets/lib/layui/lay/modules/laytpl.js diff --git a/sop-website/src/main/resources/public/assets/lib/layui/lay/modules/mobile.js b/sop-website/sop-website-server/src/main/resources/public/assets/lib/layui/lay/modules/mobile.js similarity index 100% rename from sop-website/src/main/resources/public/assets/lib/layui/lay/modules/mobile.js rename to sop-website/sop-website-server/src/main/resources/public/assets/lib/layui/lay/modules/mobile.js diff --git a/sop-website/src/main/resources/public/assets/lib/layui/lay/modules/rate.js b/sop-website/sop-website-server/src/main/resources/public/assets/lib/layui/lay/modules/rate.js similarity index 100% rename from sop-website/src/main/resources/public/assets/lib/layui/lay/modules/rate.js rename to sop-website/sop-website-server/src/main/resources/public/assets/lib/layui/lay/modules/rate.js diff --git a/sop-website/src/main/resources/public/assets/lib/layui/lay/modules/slider.js b/sop-website/sop-website-server/src/main/resources/public/assets/lib/layui/lay/modules/slider.js similarity index 100% rename from sop-website/src/main/resources/public/assets/lib/layui/lay/modules/slider.js rename to sop-website/sop-website-server/src/main/resources/public/assets/lib/layui/lay/modules/slider.js diff --git a/sop-website/src/main/resources/public/assets/lib/layui/lay/modules/table.js b/sop-website/sop-website-server/src/main/resources/public/assets/lib/layui/lay/modules/table.js similarity index 100% rename from sop-website/src/main/resources/public/assets/lib/layui/lay/modules/table.js rename to sop-website/sop-website-server/src/main/resources/public/assets/lib/layui/lay/modules/table.js diff --git a/sop-website/src/main/resources/public/assets/lib/layui/lay/modules/tree.js b/sop-website/sop-website-server/src/main/resources/public/assets/lib/layui/lay/modules/tree.js similarity index 100% rename from sop-website/src/main/resources/public/assets/lib/layui/lay/modules/tree.js rename to sop-website/sop-website-server/src/main/resources/public/assets/lib/layui/lay/modules/tree.js diff --git a/sop-website/src/main/resources/public/assets/lib/layui/lay/modules/upload.js b/sop-website/sop-website-server/src/main/resources/public/assets/lib/layui/lay/modules/upload.js similarity index 100% rename from sop-website/src/main/resources/public/assets/lib/layui/lay/modules/upload.js rename to sop-website/sop-website-server/src/main/resources/public/assets/lib/layui/lay/modules/upload.js diff --git a/sop-website/src/main/resources/public/assets/lib/layui/lay/modules/util.js b/sop-website/sop-website-server/src/main/resources/public/assets/lib/layui/lay/modules/util.js similarity index 100% rename from sop-website/src/main/resources/public/assets/lib/layui/lay/modules/util.js rename to sop-website/sop-website-server/src/main/resources/public/assets/lib/layui/lay/modules/util.js diff --git a/sop-website/src/main/resources/public/assets/lib/layui/layui.all.js b/sop-website/sop-website-server/src/main/resources/public/assets/lib/layui/layui.all.js similarity index 100% rename from sop-website/src/main/resources/public/assets/lib/layui/layui.all.js rename to sop-website/sop-website-server/src/main/resources/public/assets/lib/layui/layui.all.js diff --git a/sop-website/src/main/resources/public/assets/lib/layui/layui.js b/sop-website/sop-website-server/src/main/resources/public/assets/lib/layui/layui.js similarity index 100% rename from sop-website/src/main/resources/public/assets/lib/layui/layui.js rename to sop-website/sop-website-server/src/main/resources/public/assets/lib/layui/layui.js diff --git a/sop-website/src/main/resources/public/assets/lib/layuiext/Form.js b/sop-website/sop-website-server/src/main/resources/public/assets/lib/layuiext/Form.js similarity index 100% rename from sop-website/src/main/resources/public/assets/lib/layuiext/Form.js rename to sop-website/sop-website-server/src/main/resources/public/assets/lib/layuiext/Form.js diff --git a/sop-website/src/main/resources/public/assets/lib/layuiext/module/treetable-lay/treetable.css b/sop-website/sop-website-server/src/main/resources/public/assets/lib/layuiext/module/treetable-lay/treetable.css similarity index 100% rename from sop-website/src/main/resources/public/assets/lib/layuiext/module/treetable-lay/treetable.css rename to sop-website/sop-website-server/src/main/resources/public/assets/lib/layuiext/module/treetable-lay/treetable.css diff --git a/sop-website/src/main/resources/public/assets/lib/layuiext/module/treetable-lay/treetable.js b/sop-website/sop-website-server/src/main/resources/public/assets/lib/layuiext/module/treetable-lay/treetable.js similarity index 100% rename from sop-website/src/main/resources/public/assets/lib/layuiext/module/treetable-lay/treetable.js rename to sop-website/sop-website-server/src/main/resources/public/assets/lib/layuiext/module/treetable-lay/treetable.js diff --git a/sop-website/src/main/resources/public/config/config.js b/sop-website/sop-website-server/src/main/resources/public/config/config.js similarity index 100% rename from sop-website/src/main/resources/public/config/config.js rename to sop-website/sop-website-server/src/main/resources/public/config/config.js diff --git a/sop-website/sop-website-server/src/main/resources/public/favicon.ico b/sop-website/sop-website-server/src/main/resources/public/favicon.ico new file mode 100644 index 00000000..224e8d20 Binary files /dev/null and b/sop-website/sop-website-server/src/main/resources/public/favicon.ico differ diff --git a/sop-website/sop-website-server/src/main/resources/public/index.html b/sop-website/sop-website-server/src/main/resources/public/index.html new file mode 100644 index 00000000..c78835a8 --- /dev/null +++ b/sop-website/sop-website-server/src/main/resources/public/index.html @@ -0,0 +1 @@ +开放平台
    \ No newline at end of file diff --git a/sop-website/src/main/resources/public/index.html b/sop-website/sop-website-server/src/main/resources/public/index_old.html similarity index 100% rename from sop-website/src/main/resources/public/index.html rename to sop-website/sop-website-server/src/main/resources/public/index_old.html diff --git a/sop-website/src/main/resources/public/pages/doc/auth.html b/sop-website/sop-website-server/src/main/resources/public/pages/doc/auth.html similarity index 100% rename from sop-website/src/main/resources/public/pages/doc/auth.html rename to sop-website/sop-website-server/src/main/resources/public/pages/doc/auth.html diff --git a/sop-website/src/main/resources/public/pages/doc/code.html b/sop-website/sop-website-server/src/main/resources/public/pages/doc/code.html similarity index 100% rename from sop-website/src/main/resources/public/pages/doc/code.html rename to sop-website/sop-website-server/src/main/resources/public/pages/doc/code.html diff --git a/sop-website/src/main/resources/public/pages/doc/doc.html b/sop-website/sop-website-server/src/main/resources/public/pages/doc/doc.html similarity index 99% rename from sop-website/src/main/resources/public/pages/doc/doc.html rename to sop-website/sop-website-server/src/main/resources/public/pages/doc/doc.html index b89b55a7..3492affa 100644 --- a/sop-website/src/main/resources/public/pages/doc/doc.html +++ b/sop-website/sop-website-server/src/main/resources/public/pages/doc/doc.html @@ -388,9 +388,9 @@ + -