Merge appactive module into main branch of 2.2.x. (#2758)

* feature: initial appactive module

* feature:appactive core logic

* feature:add appactive relevant examples

* fix: add appactive module in pom.xml

* fix: Reformat code

* feature: add AppactivePredicate

* refactor: delete duplicate codes and reformat them

* feature: using PredicateBasedRule default filter logic

* feature: deleted useless filed in AppactiveRule

* refactor: polish the code

* feat(appactive):doc init

* refactor: add rules and polish codes

* refactor: modified package name

* refactor: delete useless files

* feature: add readme-zh.md

* refactor: polish readme docs

* fix: fix appactive docs error

* refactor: polish code and docs

* refactor: adjust service type name

* refactor: delete useless files

Co-authored-by: 就砺 <jiuli.qk@alibaba-inc.com>
pull/2765/head
Steve Rao 2 years ago committed by GitHub
parent 115cc345ee
commit 375cbd6c26
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -200,7 +200,7 @@
<configuration>
<!-- Checkstyle rules inherited from spring-cloud-build -->
<suppressionsLocation>
${maven.multiModuleProjectDirectory}/eclipse/checkstyle-suppressions.xml
${session.executionRootDirectory}/eclipse/checkstyle-suppressions.xml
</suppressionsLocation>
<includeTestSourceDirectory>true</includeTestSourceDirectory>
<consoleOutput>true</consoleOutput>

@ -226,6 +226,12 @@
<version>${revision}</version>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-appactive</artifactId>
<version>${revision}</version>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-commons</artifactId>

@ -0,0 +1,332 @@
== Spring Cloud Alibaba AppActive
=== AppActive介绍
AppActive是一个面向业务应用构建云原生高可用多活容灾架构的开源中间件。它提供了应用多活容灾架构的标准、实现和 Demo适用于丰富的业务场景单 AZ、单 Region、单云、多 AZ、多 Region、多云、自建 IDC等
AppActive 建立在 阿里巴巴 使用 AHAS-MSHA 系统大规模运行生产应用系统的8年经验之上且结合了来自阿里云商业化服务的外部多家客户和社区的最佳实践具备高可靠、可拓展等特性。
AppActive 具备以下特性
==== 一、单元分流
===== 流量统一接入
对流量的把控能力来源于流量的统一接入我们将这一层称为接入层。接入层是单元所有流量的入口它能解析请求协议并按照设置的分流模式与分流粒度将请求流量正确引导到正确单元的相应应用服务SLB上。下文若无特殊说明请求协议默认HTTP。下图为接入层示意图。
===== 分流模式
多活场景下,流量会按照某个纬度切片,分流到不同的单元。多活支持多种分流模式,比如按比例分流,按业务属性分流,具体选择哪种分流模式还是取决于业务场景需要。分流模式最终体现在路由规则里,路由规则是一项配置,它定义了某个流量应该隶属于哪个单元。
1. 按比例分流。在某些业务场景下需要按用户分流或者按设备号分流。用户、设备等都可以抽象成一个ID那就可以将ID划为多个区间每个区间归属于某个单元。这种分流模式每个单元的流量比例就可以通过ID区间进行估算通过设置ID区间与单元容量的比例一致就可以做到全局的负载均衡。
2. 按业务属性分流。在某些业务场景下,需要按流量属性分流,比如将爬虫流量引入特定单元,或者按省份分流。爬虫、省份等都可以抽象成一个标签,那就可以将不同的标签值流量引入不同单元。这种分流模式,可以支持对不同业务属性的流量进行物理隔离。
===== 分流粒度
随着应用服务化当今应用早已不是单体架构提供用户访问入口的应用也可能成百上千。接入层将用户流量引入正确的应用支持域名与URI前缀两种粒度。
1. 按域名分流。按照不同域名区分不同应用。比如应用app1的域名是app1.example.comapp2的域名是app2.example.com。
2. 按URI前缀分流。按照不同URI前缀区分不同应用。比如应用app1与app2的域名都是app.example.com但是将URI中/app1前缀的流量引入app1将/app2前缀的流量引入app2。
===== 流量协议
接入层支持四层、七层丰富的流量请求协议,以满足用户互联网、物联网等多样化场景需求。
1. HTTP协议支持。接入层默认支持HTTP协议从HTTP协议中解析出域名与URI进行转发路由。
2. HTTPS协议支持。接入层支持HTTPS协议提供集中化的域名证书管理满足用户可靠传输、安全传输的要求。用户在接入层配置好域名证书则应用SLB无需再配置证书。
3. 其他协议支持。接入层除了支持HTTP与HTTPS协议还支持其他基于HTTP的协议比如SOAP等。接入层在协议上有很大的扩展性能以插件的形式迅速支持特殊协议比如物联网场景下的MQTTCOAP等。
===== 路由透传
为了确保流量能在单元内闭环接入层、应用层、数据层每一层都会进行路由纠错或单元保护。为了识别流量需要明确流量所属的单元化类型与分流id我们称之为路由参数以便通过路由规则计算出流量所属正确单元因此路由参数需要跟随请求路径一路传递我们称之为路由透传。
1. 接入层路由透传。当浏览器发起业务请求时需要在请求中携带路由参数。路由参数可以在cookie、head或body中建议cookie。接入层能解析HTTP请求拿到路由参数并路由到正确的应用SLB同时应用服务器仍然能从请求中拿到原生的路由参数。
2. 应用层路由透传。流量到达应用服务器多活提供插件从HTTP请求中提取路由参数并保存到上下文下一步应用可能会发起RPC调用或异步消息因此路由参数还需要在RPC与消息层面透传。
3. RPC路由透传。当应用发起RPC调用时RPC客户端能从上下文中取出路由参数并跟随RPC请求到远程服务提供者ProviderProvider客户端识别出Request中的路由参数亦保存到调用上下文。路由参数在RPC中的传递过程对用户透明。
4. 消息路由透传。MQ客户端在发送消息时会从当前上下文获取路由参数添加到消息属性中。MQ客户端消费消息时能从消息属性中取出路由参数亦保存到调用上下文。路由参数在MQ中的传递过程对用户透明。
5. 数据层路由透传。数据脏写会造成很严重的后果因此要保证数据落库到正确单元的DB。多活提供了 DRIVER 插件,对非本单元的请求拒绝。
==== 二、单元协同
在容灾场景中,理想场景中各个单元独立,但实际上会存在部分跨单元场景的业务场景,为了满足这些场景,产品需要提供单元协同的能力。
===== 中心调用
有些特定业务场景为保证数据强一致性特定服务只能在特定中心单元提供所有对中心服务的调用都会直接路由到中心单元来完成。异地多活产品通过CSB组件和RPC多活插件来完成服务单元间协同调用满足业务的完整性。
==== 三、单元保护
产品保证业务逻辑的全局正确性,不会因切流操作导致单元业务逻辑不一致问题。系统自上而下各层都有对错误流量的纠错保护能力,保证业务按单元化规则进行正确的流转。
===== 接入层纠错
流量打入接入层,接入层通过请求附加的路由参数判断流量归属单元,非本单元流量将被代理到正确的目标单元,保证了接入层入口流量的正确性。
===== RPC纠错
RPC服务调用时RPC多活Plugin在Consumer端会根据请求的单元信息对服务调用进行正确的路由选址对错误的流量服务调用RPC多活Plugin会计算出正确的目标单元跨单元调用目标单元服务保证服务流转逻辑的一致性。同时RPC多活Plugin 在Provider端会对过来的请求进行二次校验保证服务调用的正确。通过双重校验机制RPC多活Plugin实现对RPC调用的纠错保证服务调用的正确性。
==== 四、单元扩展
===== 水平扩展
当现有单元的业务承载量已达上限且无法扩容时,产品 提供简单快捷单元水平扩展能力:
1. 全国范围内扩展新单元不受地域限制
2. 扩展的新单元数量不受限制,单元的稳定性和性能不受单元数量影响
3. 提供两种单元扩展形式:独立 DB 的异地单元 、 共享 DB 的同城单元
=== 如何使用AppActive
==== 一、数据面
**前置条件**
- 需要你的应用服务基于 Java 实现,并且以 SpringCloud 实现服务调用
- 负载均衡支持 Ribbon暂不支持 SpringCloudBalancer
- 支持声明式http客户端Feign 和 RestTemplate暂不支持 原始Http客户端如 OkHttp 和 HttpClient
===== 入口应用
入口应用负责从流量中提取路由标,并设置到上下文中
**改造步骤**
1. 引入 maven 依赖
```
<dependency>
<groupId>com.alibaba.msha</groupId>
<artifactId>client-bridge-servlet</artifactId>
<version>0.2.1</version>
</dependency>
```
2. 引入 filter以 Spring 为例
```java
@Configuration
public class WebConfig {
@Bean
public FilterRegistrationBean<RequestFilter> appActiveFilter() {
FilterRegistrationBean<RequestFilter> filterRegistrationBean = new FilterRegistrationBean<>();
RequestFilter reqResFilter = new RequestFilter();
filterRegistrationBean.setFilter(reqResFilter);
filterRegistrationBean.addUrlPatterns("/*");
return filterRegistrationBean;
}
}
```
3. 当请求到来时,可以在应用中调用 `AppContextClient.getRouteId();` 来获取路由ID
===== 所有应用
**改造步骤**
1. 在 provider 和 consumer 都引入 maven 依赖
```
<dependency>
<groupId>com.alibaba.msha</groupId>
<artifactId>client-bridge-rpc-springcloud-common</artifactId>
<version>0.2.1</version>
</dependency>
```
对于 使用 Nacos 作为注册中心的应用,还应引入
```
<dependency>
<groupId>com.alibaba.msha</groupId>
<artifactId>client-bridge-rpc-springcloud-nacos</artifactId>
<version>0.2.1</version>
</dependency>
```
注意,不同的注册中心不能同时使用。
然后引入自动配置
`@Import({ConsumerAutoConfig.class, NacosAutoConfig.class})`
2. 在 consumer 的 maven 中引入切面,植入多活寻址逻辑
```
<build>
<plugins>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>aspectj-maven-plugin</artifactId>
<version>1.11</version>
<configuration>
<aspectLibraries>
<aspectLibrary>
<groupId>com.alibaba.msha</groupId>
<artifactId>client-bridge-rpc-springcloud-common</artifactId>
</aspectLibrary>
</aspectLibraries>
<source>${maven.compiler.source}</source>
<target>${maven.compiler.target}</target>
<complianceLevel>1.8</complianceLevel>
<forceAjcCompile>true</forceAjcCompile>
</configuration>
<executions>
<execution>
<id>compileId</id>
<phase>compile</phase>
<goals>
<goal>compile</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
```
3. 在 provider 中定义不同uri的属性支持 ant 模式的 uri举例
```
@Bean
public FilterRegistrationBean<UnitServiceFilter> appActiveUnitServiceFilter() {
FilterRegistrationBean<UnitServiceFilter> filterRegistrationBean = new FilterRegistrationBean<>();
UnitServiceFilter reqResFilter = new UnitServiceFilter();
filterRegistrationBean.setFilter(reqResFilter);
filterRegistrationBean.addUrlPatterns("/detailHidden/*","/detail/*");
return filterRegistrationBean;
}
@Bean
public FilterRegistrationBean<CenterServiceFilter> appActiveCenterServiceFilter() {
FilterRegistrationBean<CenterServiceFilter> filterRegistrationBean = new FilterRegistrationBean<>();
CenterServiceFilter reqResFilter = new CenterServiceFilter();
filterRegistrationBean.setFilter(reqResFilter);
filterRegistrationBean.addUrlPatterns("/buy/*");
return filterRegistrationBean;
}
```
不同服务类型如下
- center: 中心服务,强一致的业务(例如库存、金额等)的服务,强制路由到中心机房,使用 `GlobalServiceFilter` 过滤
- unit: 单元服务,基于规则,仅在本单元路由的服务,使用 `CoreServiceFilter` 过滤
- normal: 普通服务,不做多活改造,使用 `GeneralServiceFilter` 过滤,本类服务亦可不单独配置,除上述两种服务以外都认为是普通服务
===== 基本配置
凡是依赖 `appactive-java-api` 模块的应用,启动时候都要配置参数:
```
-Dappactive.channelTypeEnum=NACOS
-Dappactive.namespaceId=appactiveDemoNamespaceId
```
表征当前应用使用 Nacos 作为命令通道,并且使用 appactiveDemoNamespaceId空间。
该空间需要有一些几个 dataId下面管控面进行说明这些 dataId 的 groudId 必须一致,比如默认为 `appactive.groupId`
当然这些都可以在启动参数进行配置,如
```
-Dappactive.dataId.idSourceRulePath=someDataId
-Dappactive.dataId.transformerRulePath=otherDataId
......
-Dappactive.groupId=myGroupId
```
==== 二、管控面
在应用部署完成后要进行基线推送,在希望调整流量时进行切流。核心是规则的构造和推送,这里重点将几个规则进行说明。
- appactive.dataId.idSourceRulePath举例
```
{
"source": "arg,header,cookie",
"tokenKey": "r_id"
}
```
说明从http parameter、header、cookie 中按顺序寻找以r_id为key的value找到一个即终止寻找过程。
- appactive.dataId.transformerRulePath举例
```
{
"id": "userIdBetween",
"mod": "10000"
}
```
说明提取到路由标后按照10000取模作为最终路由标。
- appactive.dataId.trafficRouteRulePath举例
```
{
"itemType": "UnitRuleItem",
"items": [
{
"name": "unit",
"conditions": [
{
"@userIdBetween": [
"0~1999"
]
}
]
},
{
"name": "center",
"conditions": [
{
"@userIdBetween": [
"2000~9999"
]
}
]
}
]
}
```
说明 按 10000 取模后在 01999 范围内的路由标应该被路由到 unit
按 10000 取模后在 20009999 范围内的路由标应该被路由到 center
- appactive.dataId.forbiddenRulePath举例
假设我们希望将 2000~2999 从 unit 划分到 center则新的appactive.trafficRulePath如下
```
{
"itemType": "UnitRuleItem",
"items": [
{
"name": "unit",
"conditions": [
{
"@userIdBetween": [
"0~2999"
]
}
]
},
{
"name": "center",
"conditions": [
{
"@userIdBetween": [
"3000~9999"
]
}
]
}
]
}
```
对应的appactive.forbiddenRulePath 为
```
{
"itemType": "ForbiddenRuleItem",
"items": [
{
"name": "between",
"conditions": [
{
"@userIdBetween": [
"2000~2999"
]
}
]
}
]
}
```

@ -0,0 +1,330 @@
== Spring Cloud Alibaba AppActive
=== Introduction
AppActive is an open source middleware that builds a cloud-native, high-availability and multi-active disaster tolerance
architecture for business applications. It provides standards, implementations, and demos for applying the multi-active
disaster recovery architecture, which is suitable for rich business scenarios (single AZ, single region, single cloud,
multi AZ, multi region, multi-cloud, self-built IDC...).
AppActive is an internal open source project of AHAS-MSHA. It is based on Alibaba's nearly 9-year production disaster
recovery practice, and combines the best practices of many external customers and communities from Alibaba Cloud's
commercial services, and has the characteristics of high reliability and scalability.
AppActive has the following features:
==== One, unit diversion
===== Unified traffic access
The ability to control traffic comes from the unified access of traffic. We call this layer the access layer. The access layer is the entrance of all traffic of the unit. It can parse the request protocol and correctly guide the request traffic to the corresponding application service SLB of the correct unit according to the set split mode and split granularity. If there are no special instructions below, the request protocol defaults to HTTP. The figure below is a schematic diagram of the access layer.
===== Shunt mode
In the multi-active scenario, the traffic will be sliced according to a certain latitude and distributed to different units. Multi-active supports multiple diversion modes, such as proportional diversion and diversion according to business attributes. The specific diversion mode to choose depends on the needs of the business scenario. The shunt mode is finally reflected in the routing rules. The routing rule is a configuration that defines which unit a certain flow should belong to.
1. Diversion proportionally.In some business scenarios, you need to split traffic according to users or device numbers. Users, devices, etc. can all be abstracted into one ID, and then the ID can be divided into multiple sections, and each section belongs to a certain unit. In this shunt mode, the traffic ratio of each unit can be estimated through the ID interval. By setting the ratio of the ID interval to the unit capacity to be consistent, global load balancing can be achieved.
2. Diversion according to business attributes.In some business scenarios, it is necessary to split traffic according to traffic attributes, such as introducing crawler traffic to a specific unit, or splitting traffic by province. Crawlers, provinces, etc. can all be abstracted into one label, and then different label value traffic can be introduced into different units. This offloading mode can support physical isolation of traffic with different business attributes.
===== Shunt granularity
With the servicing of applications, today's applications are no longer a monolithic architecture, and there may be hundreds of applications that provide user access. The access layer introduces user traffic to the correct application, and supports two granularities of domain name and URI prefix.
1. Diversion by domain name.Different applications are distinguished according to different domain names. For example, the domain name of application app1 is app1.example.com, and the domain name of app2 is app2.example.com.
2. Diversion by URI prefix.Different applications are distinguished according to different URI prefixes. For example, the domain names of apps app1 and app2 are both app.example.com, but the traffic with the prefix /app1 in the URI is introduced into app1, and the traffic with the prefix /app2 is introduced into app2.
===== Traffic Protocol
The access layer supports four-layer and seven-layer rich traffic request protocols to meet the needs of users in diverse scenarios such as the Internet and the Internet of Things.
1. HTTP protocol support.The access layer supports the HTTP protocol by default, and the domain name and URI are parsed from the HTTP protocol for forwarding and routing.
2. HTTPS protocol support.The access layer supports the HTTPS protocol, provides centralized domain name certificate management, and meets the user's requirements for reliable and secure transmission. If the user configures the domain name certificate at the access layer, the application of SLB does not need to configure the certificate.
3. Other protocol support.In addition to supporting HTTP and HTTPS protocols, the access layer also supports other HTTP-based protocols, such as SOAP. The access layer has great scalability in terms of protocols, and can quickly support special protocols in the form of plug-ins, such as MQTT and COAP in the Internet of Things scenario.
===== Route transparent transmission
In order to ensure that the flow can be closed in the cell, each layer of the access layer, application layer, and data layer will perform routing error correction or cell protection. In order to identify the traffic, it is necessary to clarify the unitized type and branch id of the traffic, which we call routing parameters, so that the correct unit of the traffic can be calculated through routing rules. Therefore, the routing parameters need to be transmitted along the request path, which we call routing Penetrate.
1. Access layer routing transparent transmission.When a browser initiates a service request, it needs to carry routing parameters in the request. Routing parameters can be in cookie, head or body, cookie is recommended. The access layer can parse the HTTP request, get the routing parameters and route to the correct application SLB, while the application server can still get the native routing parameters from the request.
2. Application layer routing transparent transmission.When the traffic arrives at the application server, MultiLive provides a plug-in to extract routing parameters from the HTTP request and save them in the context. The next application may initiate RPC calls or asynchronous messages. Therefore, routing parameters need to be transparently transmitted at the RPC and message levels.
3. RPC routing transparent transmission.When an application initiates an RPC call, the RPC client can retrieve routing parameters from the context and follow the RPC request to the remote service provider Provider. The Provider client recognizes the routing parameters in the Request and saves them in the calling context. The process of routing parameters in RPC is transparent to users.
4. Message routing transparent transmission.When the MQ client sends a message, it will obtain routing parameters from the current context and add it to the message properties. When the MQ client consumes a message, it can retrieve routing parameters from the message attributes and save them in the calling context. The process of routing parameters in MQ is transparent to users.
5. Data layer routing transparent transmission.Dirty writing of data can cause serious consequences, so it is necessary to ensure that the data is stored in the correct unit of DB. Duohuo provides the DRIVER plug-in, and rejects requests from non-this unit.
==== Two, unit collaboration
In the disaster recovery scenario, each unit in the ideal scenario is independent, but in fact there will be some business scenarios that cross-unit scenarios. In order to meet these scenarios, the product needs to provide unit collaboration capabilities.
===== Center call
In some specific business scenarios, in order to ensure strong data consistency, specific services can only be provided in specific central units, and all calls to central services will be directly routed to the central unit for completion. Remote multi-active products use CSB components and RPC multi-active plug-ins to complete coordinated calls between service units to meet business integrity.
==== Three, unit protection
The product guarantees the global correctness of the business logic, and will not cause inconsistencies in the unit business logic due to cut-flow operations. Each layer of the system from top to bottom has error correction protection capabilities for error traffic to ensure that services are flowed correctly according to the unitized rules.
===== Access layer error correction
The traffic enters the access layer, and the access layer determines the unit of the traffic by requesting additional routing parameters, and the non-unit traffic will be proxied to the correct target unit, ensuring the correctness of the access layer ingress traffic.
===== RPC error correction
When the RPC service is called, the RPC Multi-Live Plugin will perform the correct routing of the service call based on the requested unit information on the Consumer side, and call the wrong traffic service. The RPC Multi-Live Plugin will calculate the correct target unit and call across units. Target unit services to ensure the consistency of service circulation logic. At the same time, the RPC Multi-Live Plugin will perform a second check on the incoming request on the Provider side to ensure the correct service call. Through the double check mechanism, RPC Multi-Live Plugin realizes the error correction of RPC calls to ensure the correctness of service calls.
==== Four, unit extension
===== Horizontal extension
When the business carrying capacity of the existing unit has reached the upper limit and cannot be expanded, the product provides simple and fast unit horizontal expansion capabilities:
1. Expansion of new units nationwide without geographic restrictions
2. The number of new units to be expanded is not limited, and the stability and performance of the unit are not affected by the number of units
3. Two types of unit expansion are provided: remote unit with independent DB and same-city unit with shared DB
=== AppActive: How to
==== A. Data Plane
===== Frontend application
The frontend application is responsible for extracting the routing beacon from the traffic and setting it in the context
1. Introduce maven dependency
```
<dependency>
<groupId>com.alibaba.msha</groupId>
<artifactId>client-bridge-servlet</artifactId>
<version>0.2.1</version>
</dependency>
```
2. import filterfor example
```java
@Configuration
public class WebConfig {
@Bean
public FilterRegistrationBean<RequestFilter> appActiveFilter() {
FilterRegistrationBean<RequestFilter> filterRegistrationBean = new FilterRegistrationBean<>();
RequestFilter reqResFilter = new RequestFilter();
filterRegistrationBean.setFilter(reqResFilter);
filterRegistrationBean.addUrlPatterns("/*");
return filterRegistrationBean;
}
}
```
3. When the request comes, you can call `AppContextClient.getRouteId();` in the application to get the route ID
===== All applications
1. Introduce maven dependency for both consumer and producer
```
<dependency>
<groupId>com.alibaba.msha</groupId>
<artifactId>client-bridge-rpc-springcloud-common</artifactId>
<version>0.2.1</version>
</dependency>
```
if you use Nacos as service registryyou should import
```
<dependency>
<groupId>com.alibaba.msha</groupId>
<artifactId>client-bridge-rpc-springcloud-nacos</artifactId>
<version>0.2.1</version>
</dependency>
```
It should be noted that you can not use 2 registry at the same time.
Then import auto config
`@Import({ConsumerAutoConfig.class, NacosAutoConfig.class})`
2. import aspect config for consumer
```
<build>
<plugins>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>aspectj-maven-plugin</artifactId>
<version>1.11</version>
<configuration>
<aspectLibraries>
<aspectLibrary>
<groupId>com.alibaba.msha</groupId>
<artifactId>client-bridge-rpc-springcloud-common</artifactId>
</aspectLibrary>
</aspectLibraries>
<source>${maven.compiler.source}</source>
<target>${maven.compiler.target}</target>
<complianceLevel>1.8</complianceLevel>
<forceAjcCompile>true</forceAjcCompile>
</configuration>
<executions>
<execution>
<id>compileId</id>
<phase>compile</phase>
<goals>
<goal>compile</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
```
In which we defined a multi-active routing policy
3. Define service type for uris in providers, such as
```
@Bean
public FilterRegistrationBean<UnitServiceFilter> appActiveUnitServiceFilter() {
FilterRegistrationBean<UnitServiceFilter> filterRegistrationBean = new FilterRegistrationBean<>();
UnitServiceFilter reqResFilter = new UnitServiceFilter();
filterRegistrationBean.setFilter(reqResFilter);
filterRegistrationBean.addUrlPatterns("/detailHidden/*","/detail/*");
return filterRegistrationBean;
}
@Bean
public FilterRegistrationBean<CenterServiceFilter> appActiveCenterServiceFilter() {
FilterRegistrationBean<CenterServiceFilter> filterRegistrationBean = new FilterRegistrationBean<>();
CenterServiceFilter reqResFilter = new CenterServiceFilter();
filterRegistrationBean.setFilter(reqResFilter);
filterRegistrationBean.addUrlPatterns("/buy/*");
return filterRegistrationBean;
}
```
- center: center service, which will only route within center idc, and is filtered by `GlobalServiceFilter`
- unit: unit service, which will only route within right unit according to multi-active rules, and is filtered by `CoreServiceFilter`
- normal: normal service, which requires no multi-active modification, and will route as it was, and is filtered by `GeneralServiceFilter`. Fyi, you can skip this service cause any service other than the above 2 types is considered as normal services.
===== 4.1 Basic configuration
All applications that rely on the `appactive-java-api` module must configure the parameters bellow
```
-Dappactive.channelTypeEnum=NACOS
-Dappactive.namespaceId=appactiveDemoNamespaceId
```
which indicate we use naocs as command channel and use namespace with the id "appactiveDemoNamespaceId".
The namespace must contains several dataIds(which will be described in controll plane section), which share one groupId( "appactive.groupId" by default).
Of course, all these parameters can be redefined,such as:
```
-Dappactive.dataId.idSourceRulePath=someDataId
-Dappactive.dataId.transformerRulePath=otherDataId
......
-Dappactive.groupId=myGroupId
```
==== B. Control Plane
After the application is deployed, the baseline is pushed, and the flow is switched when you want to adjust the traffic. The core is the construction and push of rules, here are a few rules to explain.
- appactive.dataId.idSourceRulePath, example:
```
{
"source": "arg,header,cookie",
"tokenKey": "r_id"
}
```
AppActive would extract value corresponding to the key r_id in the order of parameter, header, cookie.
- appactive.dataId.transformerRulePath, example:
```
{
"id": "userIdBetween",
"mod": "10000"
}
```
The final routing target would be decided by rawValue % 10000
- appactive.dataId.trafficRouteRulePath, example:
```
{
"itemType": "UnitRuleItem",
"items": [
{
"name": "unit",
"conditions": [
{
"@userIdBetween": [
"0~1999"
]
}
]
},
{
"name": "center",
"conditions": [
{
"@userIdBetween": [
"2000~9999"
]
}
]
}
]
}
```
Modulo by 10000, the routing targets within the range of 0-1999 should be routed to unit,
the routing targets within the range of 2000-9999 should be routed to the center
- appactive.dataId.forbiddenRulePath, example:
Suppose we would like to switch routing targets 2000~2999 from unit to center, the new value of appactive.trafficRulePath would be
```
{
"itemType": "UnitRuleItem",
"items": [
{
"name": "unit",
"conditions": [
{
"@userIdBetween": [
"0~2999"
]
}
]
},
{
"name": "center",
"conditions": [
{
"@userIdBetween": [
"3000~9999"
]
}
]
}
]
}
```
the corresponding value of appactive.forbiddenRulePath is
```
{
"itemType": "ForbiddenRuleItem",
"items": [
{
"name": "between",
"conditions": [
{
"@userIdBetween": [
"2000~2999"
]
}
]
}
]
}
```

@ -0,0 +1,88 @@
# sh baseline.sh 2 or sh baseline.sh 2 NACOS appactiveDemoNamespaceId
# sh baseline.sh 3
type=$1
channel=$2
tenant=$3
if [ ! -n "$channel" ] ;then
channel="FILE"
fi
echo "channel: ${channel}"
if [ `expr $type % 2` == 0 ]
then
if [ $channel = "FILE" ]
then
for file in $(ls ../data/); do
if [[ "$file" == *"path-address"* ]]; then
echo "continue"
continue
fi
echo "$(date "+%Y-%m-%d %H:%M:%S") 应用 ${file} 基线推送中";
cp -f ./rule/idSource.json "../data/$file/"
cp -f ./rule/transformerBetween.json "../data/$file/idTransformer.json"
cp -f ./rule/idUnitMapping.json "../data/$file/"
cp -f ./rule/dbProperty.json "../data/$file/mysql-product"
arr=(${file//-/ })
unitFlag=${arr[1]}
echo "{\"unitFlag\":\"${unitFlag}\"}" > "../data/$file/machine.json"
echo "$(date "+%Y-%m-%d %H:%M:%S") 应用 ${file} 基线推送完成"
done
elif [ $channel = "NACOS" ]
then
dataIdPrefix="appactive.dataId."
groupId="appactive.groupId"
idSourceRule=$(cat ./rule/idSource.json)
echo "$(date "+%Y-%m-%d %H:%M:%S") idSourceRule 推送结果: " \
&& curl -X POST "127.0.0.1:8848/nacos/v1/cs/configs" \
-d "tenant=${tenant}&dataId=${dataIdPrefix}idSourceRulePath&group=${groupId}&content=${idSourceRule}" \
&& echo ""
idTransformerRule=$(cat ./rule/transformerBetween.json)
echo "$(date "+%Y-%m-%d %H:%M:%S") idTransformerRule 推送结果: " \
&& curl -X POST "127.0.0.1:8848/nacos/v1/cs/configs" \
-d "tenant=${tenant}&dataId=${dataIdPrefix}transformerRulePath&group=${groupId}&content=${idTransformerRule}" \
&& echo ""
idUnitMappingRule=$(cat ./rule/idUnitMapping.json)
echo "$(date "+%Y-%m-%d %H:%M:%S") idUnitMappingRule 推送结果: " \
&& curl -X POST "127.0.0.1:8848/nacos/v1/cs/configs" \
-d "tenant=${tenant}&dataId=${dataIdPrefix}trafficRouteRulePath&group=${groupId}&content=${idUnitMappingRule}" \
&& echo ""
forbiddenRule=$(cat ./rule/forbiddenRuleEmpty.json)
echo "$(date "+%Y-%m-%d %H:%M:%S") forbiddenRule 推送结果: " \
&& curl -X POST "127.0.0.1:8848/nacos/v1/cs/configs" \
-d "tenant=${tenant}&dataId=${dataIdPrefix}forbiddenRulePath&group=${groupId}&content=${forbiddenRule}" \
&& echo ""
dataScopeRule=$(cat ./rule/dbProperty.json)
echo "$(date "+%Y-%m-%d %H:%M:%S") dataScopeRule 推送结果: " \
&& curl -X POST "127.0.0.1:8848/nacos/v1/cs/configs" \
-d "tenant=${tenant}&dataId=${dataIdPrefix}dataScopeRuleDirectoryPath_mysql-product&group=${groupId}&content=${dataScopeRule}" \
&& echo ""
else
echo "unsupported channel: ${channel}"
exit 1
fi
fi
if [ `expr $type % 3` == 0 ]
then
idSource=$(cat ./rule/idSource.json)
idTransformer=$(cat ./rule/idTransformer.json)
idUnitMapping=$(cat ./rule/idUnitMapping.json)
gatewayRule="{\"idSource\" : $idSource, \"idTransformer\" : $idTransformer, \"idUnitMapping\" : $idUnitMapping}"
data="{\"key\" : \"459236fc-ed71-4bc4-b46c-69fc60d31f18_test1122\", \"value\" : $gatewayRule}"
echo $data
echo "$(date "+%Y-%m-%d %H:%M:%S") gateway 基线推送结果: " && curl --header "Content-Type: application/json" \
--request POST \
--data "$data" \
127.0.0.1:8090/set
fi

@ -0,0 +1,62 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-examples</artifactId>
<version>${revision}</version>
<relativePath>../../pom.xml</relativePath>
</parent>
<artifactId>appactive-example-common</artifactId>
<name>Spring Cloud Starter Alibaba Appactive Example - Common Service</name>
<packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>5.3.18</version>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
<version>5.1.0.Final</version>
</dependency>
<dependency>
<groupId>com.alibaba.msha</groupId>
<artifactId>client-spi-metainfo</artifactId>
<version>0.2.1</version>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-appactive</artifactId>
</dependency>
</dependencies>
</project>

@ -0,0 +1,34 @@
/*
* Copyright 2013-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.cloud.example.common;
/**
* @author ChengPu raozihao
* @description
* @date 2022/8/15
*/
public final class Constants {
/**
* center flag.
*/
public static final String CENTER_FLAG = "center";
private Constants() {
}
}

@ -0,0 +1,30 @@
/*
* Copyright 2013-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.cloud.example.common;
/**
* @author raozihao, mageekchiu
* @author <a href="mailto:zihaorao@gmail.com">Steve</a>
*/
public enum RPCType {
/**
* Spring Cloud.
*/
SpringCloud
}

@ -0,0 +1,108 @@
/*
* Copyright 2013-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.cloud.example.common.entity;
import java.io.Serializable;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
/**
* @author ChengPu raozihao
* @description
* @date 2022/8/15
*/
@Entity
public class Product implements Serializable {
@Id
@Column(nullable = false, columnDefinition = "char(100)")
private String id;
@Column(nullable = false, columnDefinition = "char(100)")
private String name;
@Column(nullable = false, columnDefinition = "char(100)")
private String img;
@Column(nullable = false, columnDefinition = "varchar(2000)")
private String description;
private int price;
private int number;
public Product() {
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getImg() {
return img;
}
public void setImg(String img) {
this.img = img;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public int getPrice() {
return price;
}
public void setPrice(int price) {
this.price = price;
}
public int getNumber() {
return number;
}
public void setNumber(int number) {
this.number = number;
}
@Override
public String toString() {
return "Product{" + "id='" + id + '\'' + ", name='" + name + '\'' + ", img='"
+ img + '\'' + ", description='" + description + '\'' + ", price=" + price
+ ", number=" + number + '}';
}
}

@ -0,0 +1,103 @@
/*
* Copyright 2013-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.cloud.example.common.entity;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
public class ResultHolder<T> implements Serializable {
List<Node> chain = new ArrayList<>();
private T result;
/**
* Default constructor.
*/
public ResultHolder() {
}
public ResultHolder(T result) {
this.result = result;
}
public T getResult() {
return result;
}
public void setResult(T result) {
this.result = result;
}
public List<Node> getChain() {
return chain;
}
public void setChain(List<Node> chain) {
this.chain = chain;
}
public void addChain(String app, String unitFlag) {
chain.add(new Node(app, unitFlag));
}
@Override
public String toString() {
return "ResultHolder{" + "result=" + result + ", chain=" + chain + '}';
}
static class Node implements Serializable {
private String app;
private String unitFlag;
Node() {
}
Node(String app, String unitFlag) {
this.app = app;
this.unitFlag = unitFlag;
}
public String getApp() {
return app;
}
public void setApp(String app) {
this.app = app;
}
public String getUnitFlag() {
return unitFlag;
}
public void setUnitFlag(String unitFlag) {
this.unitFlag = unitFlag;
}
@Override
public String toString() {
return "Node{" + "app='" + app + '\'' + ", unitFlag='" + unitFlag + '\''
+ '}';
}
}
}

@ -0,0 +1,55 @@
/*
* Copyright 2013-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.cloud.example.common.filter;
import com.alibaba.cloud.example.common.entity.ResultHolder;
import io.appactive.support.log.LogUtil;
import io.appactive.support.sys.JvmPropertyUtil;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.slf4j.Logger;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class ChainAspect {
private static final Logger logger = LogUtil.getLogger();
/**
* Add tag information in result.
* @param joinPoint joinPoint
* @param result result
*/
@AfterReturning(
pointcut = "execution(* com.alibaba.cloud.example.frontend.service.*.*(..)) || "
+ "execution(* com.alibaba.cloud.example.product.service.*.*(..)) || "
+ "execution(* com.alibaba.cloud.example.storage.service.*.*(..)) || "
+ "execution(* com.alibaba.cloud.example.common.service.*.*(..))",
returning = "result")
public void afterRunning(JoinPoint joinPoint, Object result) {
if (result instanceof ResultHolder) {
ResultHolder resultHolder = (ResultHolder) result;
resultHolder.addChain(JvmPropertyUtil.getJvmAndEnvValue("appactive.app"),
JvmPropertyUtil.getJvmAndEnvValue("appactive.unit"));
logger.info("ChainAspect: " + resultHolder);
}
}
}

@ -0,0 +1,47 @@
/*
* Copyright 2013-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.cloud.example.common.service;
import com.alibaba.cloud.example.common.entity.ResultHolder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.stereotype.Service;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
@Service
public class OrderDAO {
@Autowired
private OrderService orderService;
public ResultHolder<String> buy(String rId, String pId, Integer number) {
return orderService.buy(rId, pId, number);
}
@FeignClient(name = "storage")
public interface OrderService {
@GetMapping("/buy/")
ResultHolder<String> buy(@RequestParam(name = "rId") String rId,
@RequestParam(name = "id") String id,
@RequestParam(name = "number") Integer number);
}
}

@ -0,0 +1,98 @@
/*
* Copyright 2013-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.cloud.example.common.service;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import com.alibaba.cloud.example.common.RPCType;
import com.alibaba.cloud.example.common.entity.Product;
import com.alibaba.cloud.example.common.entity.ResultHolder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.stereotype.Service;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.client.RestTemplate;
@Service
public class ProductDAO {
@Autowired(required = false)
RestTemplate restTemplate;
@Autowired
private ProductService productService;
public ResultHolder<List<Product>> list() {
return productService.list();
}
public ResultHolder<Product> detail(String rId, String pId) {
return productService.detail(rId, pId);
}
public ResultHolder<Product> detailHidden(String pId) {
return productService.detailHidden(pId);
}
public ResultHolder<String> buy(String rId, String pId, Integer number) {
return productService.buy(RPCType.SpringCloud.name(), rId, pId, number);
}
public ResultHolder<List<Product>> listTemplate() {
if (restTemplate != null) {
return restTemplate.getForObject("http://product/list", ResultHolder.class);
}
return productService.list();
}
public ResultHolder<Product> detailTemplate(String rId, String pId) {
if (restTemplate != null) {
Map<String, String> params = new HashMap<>(2);
params.put("rId", rId);
params.put("pId", pId);
return restTemplate.getForObject("http://product/detail", ResultHolder.class,
params);
}
return productService.detail(rId, pId);
}
@FeignClient(name = "product")
public interface ProductService {
@RequestMapping("/list/")
ResultHolder<List<Product>> list();
@RequestMapping("/detail/")
ResultHolder<Product> detail(@RequestParam(name = "rId") String rId,
@RequestParam(name = "pId") String pId);
@RequestMapping("/detailHidden/")
ResultHolder<Product> detailHidden(@RequestParam(name = "pId") String pId);
@RequestMapping("/buy/")
ResultHolder<String> buy(@RequestParam(name = "rpcType") String rpcType,
@RequestParam(name = "rId") String rId,
@RequestParam(name = "pId") String pId,
@RequestParam(name = "number") Integer number);
}
}

@ -0,0 +1,28 @@
/*
* Copyright 2013-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.cloud.example.common.service;
import java.util.List;
import com.alibaba.cloud.example.common.entity.Product;
import com.alibaba.cloud.example.common.entity.ResultHolder;
public interface ProductServiceNormal {
ResultHolder<List<Product>> list();
}

@ -0,0 +1,26 @@
/*
* Copyright 2013-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.cloud.example.common.service;
import com.alibaba.cloud.example.common.entity.Product;
import com.alibaba.cloud.example.common.entity.ResultHolder;
public interface ProductServiceUnit {
ResultHolder<Product> detail(String rId, String pId);
}

@ -0,0 +1,26 @@
/*
* Copyright 2013-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.cloud.example.common.service;
import com.alibaba.cloud.example.common.entity.Product;
import com.alibaba.cloud.example.common.entity.ResultHolder;
public interface ProductServiceUnitHidden {
ResultHolder<Product> detail(String pId);
}

@ -0,0 +1,27 @@
version: "3"
services:
nacos:
container_name: nacos
hostname: nacos
image: nacos/nacos-server:2.0.3
environment:
- PREFER_HOST_MODE=hostname
- MODE=standalone
ports:
- "8848:8848"
- "9848:9848"
- "9849:9849"
mysql:
container_name: mysql
hostname: mysql
image: mysql:5.7
environment:
MYSQL_ROOT_PASSWORD: root
MYSQL_DATABASE: product
ports:
- "3306:3306"
command: [
--character-set-server=utf8mb4,
--collation-server=utf8mb4_unicode_ci
]

@ -0,0 +1,86 @@
channel=$1
tenant=$2
waitTime=$3
if [ ! -n "$channel" ] ;then
channel="FILE"
fi
if [ ! -n "$waitTime" ] ;then
waitTime=3
fi
echo "channel: ${channel}"
dataIdPrefix="appactive.dataId."
groupId="appactive.groupId"
forbiddenFile="forbiddenRule.json"
forbiddenFileEmpty="forbiddenRuleEmpty.json"
idUnitMappingNextFile="idUnitMappingNext.json"
if [ $channel = "FILE" ]
then
for file in $(ls ../data/); do
if [[ "$file" == *"path-address"* ]]; then
echo "continue"
continue
fi
echo "$(date "+%Y-%m-%d %H:%M:%S") 应用 ${file} 禁写规则推送中)"
cp -f ./rule/$forbiddenFile "../data/$file/forbiddenRule.json"
echo "$(date "+%Y-%m-%d %H:%M:%S") 应用 ${file} 禁写规则推送完成"
done
elif [ $channel = "NACOS" ]
then
forbiddenRule=$(cat ./rule/forbiddenRule.json)
echo "$(date "+%Y-%m-%d %H:%M:%S") forbiddenRule 推送结果: " \
&& curl -X POST "127.0.0.1:8848/nacos/v1/cs/configs" \
-d "tenant=${tenant}&dataId=${dataIdPrefix}forbiddenRulePath&group=${groupId}&content=${forbiddenRule}" \
&& echo ""
else
echo "unsupported channel: ${channel}"
exit 1
fi
idSource=$(cat ./rule/idSource.json)
idTransformer=$(cat ./rule/idTransformer.json)
idUnitMapping=$(cat ./rule/$idUnitMappingNextFile)
gatewayRule="{\"idSource\" : $idSource, \"idTransformer\" : $idTransformer, \"idUnitMapping\" : $idUnitMapping}"
data="{\"key\" : \"459236fc-ed71-4bc4-b46c-69fc60d31f18_test1122\", \"value\" : $gatewayRule}"
echo $data
echo "$(date "+%Y-%m-%d %H:%M:%S") gateway 新规则推送结果: " && curl --header "Content-Type: application/json" \
--request POST \
--data "$data" \
127.0.0.1:8090/set
echo "等待数据追平......"
sleep "${waitTime}s"
echo "数据已经追平,下发新规则......"
if [ $channel = "FILE" ]
then
for file in $(ls ../data/); do
if [[ "$file" == *"path-address"* ]]; then
echo "continue"
continue
fi
echo "$(date "+%Y-%m-%d %H:%M:%S") 应用 ${file} 新规则推送中"
cp -f ./rule/$idUnitMappingNextFile "../data/$file/idUnitMapping.json"
echo "$(date "+%Y-%m-%d %H:%M:%S") 应用 ${file} 新规则推送完成"
echo "$(date "+%Y-%m-%d %H:%M:%S") 应用 ${file} 清除禁写规则推送中)"
cp -f ./rule/$forbiddenFileEmpty "../data/$file/forbiddenRule.json"
echo "$(date "+%Y-%m-%d %H:%M:%S") 应用 ${file} 清除禁写规则推送完成"
done
elif [ $channel = "NACOS" ]
then
idUnitMappingRule=$(cat ./rule/idUnitMappingNext.json)
echo "$(date "+%Y-%m-%d %H:%M:%S") idUnitMappingRule 推送结果: " \
&& curl -X POST "127.0.0.1:8848/nacos/v1/cs/configs" \
-d "tenant=${tenant}&dataId=${dataIdPrefix}trafficRouteRulePath&group=${groupId}&content=${idUnitMappingRule}" \
&& echo ""
forbiddenRule=$(cat ./rule/forbiddenRuleEmpty.json)
echo "$(date "+%Y-%m-%d %H:%M:%S") forbiddenRule 推送结果: " \
&& curl -X POST "127.0.0.1:8848/nacos/v1/cs/configs" \
-d "tenant=${tenant}&dataId=${dataIdPrefix}forbiddenRulePath&group=${groupId}&content=${forbiddenRule}" \
&& echo ""
else
echo "unsupported channel: ${channel}"
exit 1
fi

@ -0,0 +1,19 @@
FROM openjdk:8-jdk-alpine
WORKDIR /app
COPY target/frontend-0.2.1.jar /app
# 设置文件夹操作权限
RUN chown -R root:root /app/* && \
chmod a+rw -R /app/*
#EXPOSE 8088
USER root:root
ARG UNITFLAG=default
ENV TZ=Asia/Shanghai \
UNITFLAG=$UNITFLAG \
LANG="en_US.UTF-8"
ENTRYPOINT ["sh", "-c"]
CMD ["java -jar /app/frontend-0.2.1.jar"]
#CMD ["sleep 10m"]

@ -0,0 +1,60 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ Copyright 1999-2022 Alibaba Group Holding Ltd.
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-examples</artifactId>
<version>${revision}</version>
<relativePath>../../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>frontend</artifactId>
<name>Spring Cloud Starter Alibaba Appactive Example - Frontend Service</name>
<packaging>jar</packaging>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<maven.deploy.skip>true</maven.deploy.skip>
</properties>
<dependencies>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>appactive-example-common</artifactId>
<version>${revision}</version>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
</dependencies>
</project>

@ -0,0 +1,36 @@
/*
* Copyright 2013-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.cloud.example.frontend;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.context.annotation.ComponentScan;
@SpringBootApplication(exclude = { DataSourceAutoConfiguration.class })
@ComponentScan(basePackages = { "com.alibaba.cloud.example" })
@EnableDiscoveryClient
@EnableFeignClients(basePackages = { "com.alibaba.cloud.example" })
public class FrontendApplication {
public static void main(String[] args) {
SpringApplication.run(FrontendApplication.class, args);
}
}

@ -0,0 +1,172 @@
/*
* Copyright 2013-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.cloud.example.frontend.controller;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import com.alibaba.cloud.example.common.RPCType;
import com.alibaba.cloud.example.common.entity.Product;
import com.alibaba.cloud.example.common.entity.ResultHolder;
import com.alibaba.cloud.example.common.service.ProductDAO;
import com.alibaba.fastjson.JSON;
import io.appactive.java.api.base.AppContextClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.CookieValue;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller("/")
public class FrontController {
@Resource
private ProductDAO productDAO;
@Value("${spring.application.name}")
private String appName;
@Autowired
private Environment env;
private Map<String, String[]> metaData;
@RequestMapping("/")
public String index() {
return "redirect:/listProduct";
}
@RequestMapping("/echo")
@ResponseBody
public ResultHolder<String> echo(@RequestParam(required = false,
defaultValue = "echo content") String content) {
return new ResultHolder<>(appName + " : " + content);
}
@RequestMapping("/check")
@ResponseBody
public String check() {
return "OK From " + appName;
}
@RequestMapping("/show")
@ResponseBody
public String show() {
return "routerId: " + AppContextClient.getRouteId();
}
@ModelAttribute("metaData")
public Map<String, String[]> getMetaData() {
return metaData;
}
@PostConstruct
public void parseMetaData() {
String unitList = env.getProperty("io.appactive.demo.unitlist");
String appList = env.getProperty("io.appactive.demo.applist");
metaData = new HashMap<>(2);
metaData.put("unitList", unitList.split(","));
metaData.put("appList", appList.split(","));
}
@RequestMapping("/meta")
@ResponseBody
public ResultHolder<Object> meta() {
return new ResultHolder<>(metaData);
}
@GetMapping("/listProduct")
public String listProduct(
@CookieValue(value = "rpc_type", required = false,
defaultValue = "Dubbo") RPCType rpcType,
@RequestParam(required = false, defaultValue = "feign") String call,
Model model) {
// normal
ResultHolder<List<Product>> resultHolder = (call.equals("feign")
? productDAO.list() : productDAO.listTemplate());
model.addAttribute("result", JSON.toJSONString(resultHolder.getResult()));
model.addAttribute("products", resultHolder.getResult());
model.addAttribute("chain", JSON.toJSONString(resultHolder.getChain()));
model.addAttribute("current", "listProduct");
return "index.html";
}
@GetMapping("/detailProduct")
public String detailProduct(
@CookieValue(value = "rpc_type", required = false,
defaultValue = "Dubbo") RPCType rpcType,
@RequestParam(required = false, defaultValue = "12") String id,
@RequestParam(required = false, defaultValue = "false") Boolean hidden,
@RequestParam(required = false, defaultValue = "feign") String call,
Model model) {
// unit
ResultHolder<Product> resultHolder = getProductResultHolder(rpcType, id, hidden,
call);
model.addAttribute("result", JSON.toJSONString(resultHolder.getResult()));
model.addAttribute("product", resultHolder.getResult());
model.addAttribute("chain", JSON.toJSONString(resultHolder.getChain()));
model.addAttribute("current", "detailProduct");
return "detail.html";
}
private ResultHolder<Product> getProductResultHolder(RPCType rpcType, String id,
Boolean hidden, String call) {
ResultHolder<Product> resultHolder;
resultHolder = hidden ? productDAO.detailHidden(id)
: (call.equals("feign")
? productDAO.detail(AppContextClient.getRouteId(), id)
: productDAO.detailTemplate(AppContextClient.getRouteId(), id));
return resultHolder;
}
@RequestMapping("/buyProduct")
public String buyProduct(
@CookieValue(value = "rpc_type", required = false,
defaultValue = "Dubbo") RPCType rpcType,
@RequestParam(required = false, defaultValue = "12") String pId,
@RequestParam(required = false, defaultValue = "1") Integer number,
@RequestParam(required = false, defaultValue = "feign") String call,
Model model) {
// unit
ResultHolder<String> resultHolder = productDAO.buy(AppContextClient.getRouteId(),
pId, number);
ResultHolder<Product> productHolder = getProductResultHolder(rpcType, pId, false,
call);
model.addAttribute("result", JSON.toJSONString(resultHolder.getResult()));
model.addAttribute("msg", resultHolder.getResult());
model.addAttribute("product", productHolder.getResult());
model.addAttribute("chain", JSON.toJSONString(resultHolder.getChain()));
model.addAttribute("current", "buyProduct");
return "buy.html";
}
}

@ -0,0 +1,49 @@
/*
* Copyright 2013-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.cloud.example.frontend.filter;
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletResponse;
// @Component
public class ChainFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
HttpServletResponse res = (HttpServletResponse) response;
chain.doFilter(request, response);
}
@Override
public void destroy() {
}
}

@ -0,0 +1,18 @@
server.port=8089
spring.application.name=frontend
appactive.unit=center
appactive.app=frontend
io.appactive.demo.unitlist=center,unit
io.appactive.demo.applist=frontend,product,storage
spring.thymeleaf.mode=LEGACYHTML5
spring.thymeleaf.encoding=UTF-8
spring.thymeleaf.servlet.content-type=text/html
spring.thymeleaf.suffix=.html
spring.thymeleaf.cache=false
spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848
feign.client.config.default.connectTimeout=8000
feign.client.config.default.readTimeout=8000
spring.cloud.appactive.filter.general-path=/*
product.ribbon.NFLoadBalancerRuleClassName=com.alibaba.cloud.appactive.consumer.AppactiveRule

File diff suppressed because one or more lines are too long

@ -0,0 +1,69 @@
<!--
~ Copyright 2013-2019 the original author or authors.
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.w3.org/1999/xhtml">
<head th:replace="header.html :: commonHead">
</head>
<body>
<script type="text/javascript">
$(window).on('load', function () {
$('#resultModal').modal('show');
});
</script>
<header th:replace="header.html :: pageHeader"></header>
<div class="container" id="welcome-page">
<div class="modal" id="resultModal" role="dialog" tabindex="-1">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">购买结果</h5>
<button aria-label="Close" class="close" data-dismiss="modal" type="button">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<p th:text="${msg}"></p>
</div>
<div class="modal-footer">
<button class="btn btn-secondary" data-dismiss="modal" type="button">关闭</button>
</div>
</div>
</div>
</div>
<div class="row align-items-center m-auto">
<div class="col m-auto">
<div class="card m-auto" style="width: 8rem; align-self: center">
<img class="card-img-top" th:src="${product.img}" width="90%">
<div class="card-body" th:id="${product.id}">
<h5 class="card-title" style="text-align: center" th:text="${product.name}"></h5>
<p class="card-text" style="text-align: center" th:text="${product.description}"></p>
<p class="card-text">
<span>价格: </span><span class="badge text-bg-success" th:text="${product.price}"></span>
<span>库存: </span><span class="badge text-bg-info" th:text="${product.number}"></span>
<a class="btn btn-primary" style="text-align: center"
th:href="@{'/buyProduct?pId=' + ${product.id}}">
购买
</a>
</p>
</div>
</div>
</div>
</div>
</div>
<header th:replace="header.html :: pageTraffic"></header>
</body>
</html>

@ -0,0 +1,41 @@
<!--
~ Copyright 2013-2019 the original author or authors.
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.w3.org/1999/xhtml">
<head th:replace="header.html :: commonHead"></head>
<body>
<header th:replace="header.html :: pageHeader"></header>
<div class="container" id="welcome-page">
<div class="row align-items-center m-auto">
<div class="col m-auto">
<div class="card m-auto" style="width: 8rem; align-self: center">
<img class="card-img-top" th:src="${product.img}" width="90%">
<div class="card-body" th:id="${product.id}">
<h5 class="card-title" style="text-align: center" th:text="${product.name}"></h5>
<p class="card-text" style="text-align: center" th:text="${product.description}"></p>
<p class="card-text">
<span>价格: </span><span class="badge text-bg-success" th:text="${product.price}"></span>
<span>库存: </span><span class="badge text-bg-info" th:text="${product.number}"></span>
</p>
</div>
</div>
</div>
</div>
</div>
<header th:replace="header.html :: pageTraffic"></header>
</body>
</html>

@ -0,0 +1,258 @@
<!--
~ Copyright 2013-2019 the original author or authors.
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.w3.org/1999/xhtml">
<head th:fragment="commonHead">
<meta charset="UTF-8">
<title>Appactive Demo</title>
<link href="/css/bootstrap.min.css" rel="stylesheet">
<script src="/js/jquery-3.2.1.slim.min.js"></script>
<script src="/js/popper.min.js"></script>
<script src="/js/bootstrap.min.js"></script>
<meta content="width=device-width, initial-scale=1.0, shrink-to-fit=no" name="viewport">
<meta content="ie=edge" http-equiv="X-UA-Compatible">
<style>
ul li.active > a {
color: #00CED1 !important;
}
.card-body {
position: relative;
}
.card-body .card-body-badge {
position: absolute;
display: none;
top: -10px;
left: -30px;
padding: 5px;
background: #00CC66;
color: white;
transform: rotate(-20deg);
}
</style>
<script>
window.onload = function () {
setMenu();
hideGateway();
refresh(undefined, undefined);
drawArrow();
};
// function drawArrow() {
// let activeColor = "#00CC66";
//
// let chainNode = $("#chain");
// let chain = JSON.parse(chainNode.val());
// let num = chain.length;
//
// $("#center-gateway").css({backgroundColor: activeColor});
//
// chain.forEach((val)=>{
// let elem = $("#" + val.unitFlag + "-" + val.app);
// elem.css({backgroundColor: activeColor});
// });
// }
function drawArrow() {
let activeColor = "#FFCC66";
activeColor = "#00CC66";
let interval = 700;
let chainNode = $("#chain");
let chain = JSON.parse(chainNode.val());
let num = chain.length;
lightUp({"unitFlag": "center", "app": "gateway"}, activeColor, 0);
for (let i = 0; i < num; i++) {
(function (i) {
setTimeout(function () {
let val = chain[i];
lightUp(val, activeColor, num - i)
}, interval * (num - i))
})(i)
}
}
function lightUp(val, activeColor, i) {
let id = val.unitFlag + "-" + val.app;
console.log(id)
let elem = $("#" + id);
elem.css({backgroundColor: activeColor});
let id1 = val.unitFlag + "T" + val.app
let elem1 = $("#" + id1);
elem1.text("第 " + (i) + " 跳");
elem1.show();
}
function hideGateway() {
let host = window.location.host;
if ("demo.appactive.io" !== host) {
$("#domainArea").hide();
}
}
function setMenu() {
let currentNode = $("#current");
let current = currentNode.val();
let elem = $("#" + current);
elem.addClass("active");
}
function refresh(routerIdNew, rpcTypeNew) {
let routerId = routerIdNew === undefined ? getRouterId() : routerIdNew;
let rpcType = rpcTypeNew === undefined ? getRpcType() : rpcTypeNew;
let element = document.getElementById("routerId");
element.value = routerId;
element = document.getElementById("rpcType");
element.value = rpcType;
document.cookie = ("rpc_type=" + rpcType + "; path=/");
document.cookie = ("r_id=" + routerId + "; path=/");
}
function setMeta() {
let element = document.getElementById("routerId");
let routerId = element.value;
let element1 = document.getElementById("rpcType");
let rpcType = element1.value;
console.log(routerId + "," + rpcType)
// 整个网站生效 用 /
refresh(routerId, rpcType);
}
function getRouterId() {
let routerId = getQuery("r_id")
if (routerId === undefined) {
routerId = getCookie("r_id")
}
if (routerId === undefined) {
routerId = 24
}
return routerId;
}
function getRpcType() {
let rpcType = getQuery("rpc_type")
if (rpcType === undefined) {
rpcType = getCookie("rpc_type")
}
if (rpcType === undefined) {
rpcType = "SpringCloud"
}
return rpcType;
}
function getCookie(cname) {
let name = cname + "=";
let ca = document.cookie.split(';');
for (let i = 0; i < ca.length; i++) {
let c = ca[i].trim();
if (c.indexOf(name) === 0) return c.substring(name.length, c.length);
}
return undefined;
}
function getQuery(variable) {
let query = window.location.search.substring(1);
let vars = query.split("&");
for (let i = 0; i < vars.length; i++) {
let pair = vars[i].split("=");
if (pair[0] === variable) {
return pair[1];
}
}
return undefined;
}
</script>
</head>
<body>
<header th:fragment="pageHeader">
<nav class="navbar navbar-light" style="background-color: #e3f2fd;">
<div class="container-fluid">
<div class="navbar-header">
<a class="navbar-brand" href="#">Appactive Demo</a>
</div>
<ul class="nav">
<li id="listProduct"><a class="nav-link" th:href="@{'/listProduct'}">列表(普通服务)</a></li>
<li id="detailProduct"><a class="nav-link" th:href="@{'/detailProduct'}">详情(单元服务)</a></li>
<li id="buyProduct"><a class="nav-link" th:href="@{'/buyProduct'}">下单(中心服务)</a></li>
<select class="form-control" id="rpcType" name="rpcType" onchange="setMeta()" style="width:150px">
<option value="SpringCloud">SpringCloud</option>
</select>
<li><input class="form-control" id="routerId" onblur="setMeta()" onchange="setMeta()" placeholder="路由ID"
type="text"></li>
</ul>
</div>
</nav>
<input hidden id="result" th:value="${result}"/>
<input hidden id="chain" th:value="${chain}"/>
<input hidden id="current" th:value="${current}"/>
<br>
</header>
<header th:fragment="pageTraffic">
<br>
<div class="row">
<div class="col">
<hr>
</div>
<div class="col-auto">绿色格子-流量路径</div>
<div class="col">
<hr>
</div>
</div>
<div class="container m-auto">
<div class="row">
<div class="col-md-3">
<div class="row">
<div class="col-md-4" id="domainArea">
<div class="card" style="width: 8rem">
<img class="card-img-top" src="/img/gateway.png">
<div class="card-body" id="center-gateway">
<h5 class="card-title" style="text-align: center">center</h5>
<h5 class="card-title" style="text-align: center">gateway</h5>
<div class="card-body-badge" id="centerTgateway"></div>
</div>
</div>
</div>
</div>
</div>
<div class="col-md-9">
<div class="row" th:each="unit: ${metaData.unitList}">
<div class="col-md-4" th:each="app: ${metaData.appList}">
<div class="card" style="width: 8rem">
<img class="card-img-top" th:src="'/img/' + ${app} + '.png'">
<div class="card-body" th:id="@{${unit} + '-' + ${app}}">
<h5 class="card-title" style="text-align: center" th:text="${unit}"></h5>
<h5 class="card-title" style="text-align: center" th:text="${app}"></h5>
<div class="card-body-badge" th:id="@{${unit} + 'T' + ${app}}"></div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</header>
</body>
</html>

@ -0,0 +1,39 @@
<!--
~ Copyright 2013-2019 the original author or authors.
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.w3.org/1999/xhtml">
<head th:replace="header.html :: commonHead"></head>
<body>
<header th:replace="header.html :: pageHeader"></header>
<div class="container" id="welcome-page">
<div class="row">
<div class="col-md-3" th:each="product: ${products}">
<div class="card" style="width: 8rem">
<img class="card-img-top" th:src="${product.img}">
<div class="card-body" th:id="${product.id}">
<h5 class="card-title" style="text-align: center">
<a th:href="@{'/detailProduct?id=' + ${product.id}}" th:text="${product.name}"></a>
</h5>
<p class="card-text" style="text-align: center" th:text="${product.description}"></p>
</div>
</div>
</div>
</div>
</div>
<header th:replace="header.html :: pageTraffic"></header>
</body>
</html>

@ -0,0 +1,18 @@
FROM openjdk:8-jdk-alpine
WORKDIR /app
COPY target/product-0.2.1.jar /app
# 设置文件夹操作权限
RUN chown -R root:root /app/* && \
chmod a+rw -R /app/*
#EXPOSE 8089
USER root:root
ARG UNITFLAG=default
ENV TZ=Asia/Shanghai \
UNITFLAG=$UNITFLAG \
LANG="en_US.UTF-8"
ENTRYPOINT ["sh", "-c"]
CMD ["java -jar /app/product-0.2.1.jar"]

@ -0,0 +1,59 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ Copyright 1999-2022 Alibaba Group Holding Ltd.
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-examples</artifactId>
<version>${revision}</version>
<relativePath>../../pom.xml</relativePath>
</parent>
<artifactId>product</artifactId>
<name>Spring Cloud Starter Alibaba Appactive Example - Product Service</name>
<packaging>jar</packaging>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<maven.deploy.skip>true</maven.deploy.skip>
</properties>
<dependencies>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>appactive-example-common</artifactId>
<version>${revision}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.40</version>
</dependency>
</dependencies>
</project>

@ -0,0 +1,128 @@
/*
* Copyright 2013-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.cloud.example.product;
import java.util.List;
import com.alibaba.cloud.example.common.RPCType;
import com.alibaba.cloud.example.common.entity.Product;
import com.alibaba.cloud.example.common.entity.ResultHolder;
import com.alibaba.cloud.example.common.service.OrderDAO;
import com.alibaba.cloud.example.common.service.ProductServiceNormal;
import com.alibaba.cloud.example.common.service.ProductServiceUnit;
import com.alibaba.cloud.example.common.service.ProductServiceUnitHidden;
import io.appactive.java.api.base.AppContextClient;
import io.appactive.support.log.LogUtil;
import org.slf4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.domain.EntityScan;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
@SpringBootApplication
@ComponentScan(basePackages = { "com.alibaba.cloud.example" })
@EntityScan("com.alibaba.cloud.example.*")
@Controller
@RequestMapping("/")
@EnableDiscoveryClient
@EnableFeignClients(basePackages = { "com.alibaba.cloud.example" })
public class ProductApplication {
private static final Logger logger = LogUtil.getLogger();
@Autowired
OrderDAO orderDAO;
@Value("${spring.application.name}")
private String appName;
@Autowired
private ProductServiceNormal productServiceNormal;
@Autowired
private ProductServiceUnit productServiceUnit;
@Autowired
private ProductServiceUnitHidden productServiceUnitHidden;
public static void main(String[] args) {
SpringApplication.run(ProductApplication.class, args);
}
@RequestMapping("/echo")
@ResponseBody
public String echo(
@RequestParam(required = false, defaultValue = "jack") String user) {
String s = String.valueOf(user);
return String.format("%s get %s", s, productServiceNormal.list().toString());
}
@RequestMapping("/list")
@ResponseBody
public ResultHolder<List<Product>> list() {
return productServiceNormal.list();
}
@RequestMapping("/detailHidden")
@ResponseBody
public ResultHolder<Product> detailHidden(
@RequestParam(required = false, defaultValue = "12") String pId) {
// unit
logger.info("detailHidden, routerId: {}, pId: {}", AppContextClient.getRouteId(),
pId);
return productServiceUnitHidden.detail(pId);
}
@RequestMapping("/detail")
@ResponseBody
public ResultHolder<Product> detail(
@RequestParam(required = false, defaultValue = "12") String rId,
@RequestParam(required = false, defaultValue = "12") String pId) {
// unit
logger.info("detail, routerId: {}, pId: {}", AppContextClient.getRouteId(), pId);
return productServiceUnit.detail(rId, pId);
}
@RequestMapping("/buy")
@ResponseBody
public ResultHolder<String> buy(
@RequestParam(required = false, defaultValue = "Dubbo") RPCType rpcType,
@RequestParam(required = false, defaultValue = "12") String rId,
@RequestParam(required = false, defaultValue = "12") String pId,
@RequestParam(required = false, defaultValue = "5") Integer number) {
logger.info("buy, routerId: {}, rpcType: {}", AppContextClient.getRouteId(),
rpcType);
return orderDAO.buy(rId, pId, number);
}
@RequestMapping("/check")
@ResponseBody
public String check() {
return "OK From " + appName;
}
}

@ -0,0 +1,96 @@
/*
* Copyright 2013-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.cloud.example.product.init;
import java.util.ArrayList;
import java.util.List;
import javax.annotation.Resource;
import com.alibaba.cloud.example.common.entity.Product;
import com.alibaba.cloud.example.product.repository.ProductRepository;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.stereotype.Component;
import static com.alibaba.cloud.example.common.Constants.CENTER_FLAG;
@Component
public class InitData implements ApplicationRunner {
@Resource
ProductRepository productRepository;
@Value("${appactive.unit}")
private String unit;
@Override
public void run(ApplicationArguments args) throws Exception {
if (!CENTER_FLAG.equals(unit)) {
return;
}
List<Product> products = new ArrayList<>(4);
Product p1 = new Product();
p1.setId("12");
p1.setName("书包");
p1.setImg("/img/backpack.png");
p1.setDescription("好用的书包");
p1.setPrice(300);
p1.setNumber(10);
products.add(p1);
Product p2 = new Product();
p2.setId("14");
p2.setName("球拍");
p2.setImg("/img/badminton.png");
p2.setDescription("好用的球拍");
p2.setPrice(200);
p2.setNumber(20);
products.add(p2);
Product p3 = new Product();
p3.setId("16");
p3.setName("键盘");
p3.setImg("/img/keyboard.png");
p3.setDescription("好用的键盘");
p3.setPrice(800);
p3.setNumber(50);
products.add(p3);
Product p4 = new Product();
p4.setId("18");
p4.setName("茶杯");
p4.setImg("/img/cup.png");
p4.setDescription("好用的茶杯");
p4.setPrice(100);
p4.setNumber(60);
products.add(p4);
try {
productRepository.deleteAll();
productRepository.saveAll(products);
}
catch (Exception e) {
e.printStackTrace();
}
}
}

@ -0,0 +1,25 @@
/*
* Copyright 2013-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.cloud.example.product.repository;
import com.alibaba.cloud.example.common.entity.Product;
import org.springframework.data.jpa.repository.JpaRepository;
public interface ProductRepository extends JpaRepository<Product, String> {
}

@ -0,0 +1,44 @@
/*
* Copyright 2013-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.cloud.example.product.service;
import java.util.List;
import com.alibaba.cloud.example.common.entity.Product;
import com.alibaba.cloud.example.common.entity.ResultHolder;
import com.alibaba.cloud.example.common.service.ProductServiceNormal;
import com.alibaba.cloud.example.product.repository.ProductRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
@Service
public class ProductServiceNormalImpl implements ProductServiceNormal {
@Autowired
ProductRepository productRepository;
@Value("${appactive.unit}")
private String unit;
@Override
public ResultHolder<List<Product>> list() {
return new ResultHolder<>(productRepository.findAll());
}
}

@ -0,0 +1,49 @@
/*
* Copyright 2013-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.cloud.example.product.service;
import com.alibaba.cloud.example.common.entity.Product;
import com.alibaba.cloud.example.common.entity.ResultHolder;
import com.alibaba.cloud.example.common.service.ProductServiceUnitHidden;
import com.alibaba.cloud.example.product.repository.ProductRepository;
import io.appactive.java.api.base.AppContextClient;
import io.appactive.support.log.LogUtil;
import org.slf4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
@Service
public class ProductServiceUnitHiddenImpl implements ProductServiceUnitHidden {
private static final Logger logger = LogUtil.getLogger();
@Autowired
ProductRepository productRepository;
@Value("${appactive.unit}")
private String unit;
@Override
public ResultHolder<Product> detail(String pId) {
String rId = AppContextClient.getRouteId();
logger.info("detail: " + pId + ",rId " + rId);
return new ResultHolder<>(productRepository.findById(pId).orElse(new Product()));
}
}

@ -0,0 +1,47 @@
/*
* Copyright 2013-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.cloud.example.product.service;
import com.alibaba.cloud.example.common.entity.Product;
import com.alibaba.cloud.example.common.entity.ResultHolder;
import com.alibaba.cloud.example.common.service.ProductServiceUnit;
import com.alibaba.cloud.example.product.repository.ProductRepository;
import io.appactive.support.log.LogUtil;
import org.slf4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
@Service
public class ProductServiceUnitImpl implements ProductServiceUnit {
private static final Logger logger = LogUtil.getLogger();
@Autowired
ProductRepository productRepository;
@Value("${appactive.unit}")
private String unit;
@Override
public ResultHolder<Product> detail(String rId, String pId) {
// unit
return new ResultHolder<>(productRepository.findById(pId).orElse(new Product()));
}
}

@ -0,0 +1,20 @@
server.port=8087
spring.application.name=product
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/product?characterEncoding=utf8&useSSL=false&serverTimezone=GMT
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.jpa.database-platform=org.hibernate.dialect.MySQL5InnoDBDialect
spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=false
appactive.unit=center
appactive.app=product
spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848
feign.client.config.default.connectTimeout=5000
feign.client.config.default.readTimeout=5000
spring.cloud.appactive.filter.core-path=/detailHidden/*,/detail/*
spring.cloud.appactive.filter.global-path=/buy/*
spring.cloud.appactive.filter.general-path=/*
storage.ribbon.NFLoadBalancerRuleClassName=com.alibaba.cloud.appactive.consumer.AppactiveRule

File diff suppressed because one or more lines are too long

@ -0,0 +1,27 @@
<!--
~ Copyright 1999-2022 Alibaba Group Holding Ltd.
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.w3.org/1999/xhtml">
<head th:replace="header.html :: commonHead"></head>
<body>
<header th:replace="header.html :: pageHeader"></header>
<div id="welcome-page">
</div>
</body>
</html>

@ -0,0 +1,27 @@
<!--
~ Copyright 1999-2022 Alibaba Group Holding Ltd.
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.w3.org/1999/xhtml">
<head th:replace="header.html :: commonHead"></head>
<body>
<header th:replace="header.html :: pageHeader"></header>
<div id="welcome-page">
</div>
</body>
</html>

@ -0,0 +1,195 @@
<!--
~ Copyright 1999-2022 Alibaba Group Holding Ltd.
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.w3.org/1999/xhtml">
<head th:fragment="commonHead">
<meta charset="UTF-8">
<title>AppActive Demo</title>
<link href="/css/bootstrap.min.css" rel="stylesheet">
<script src="/js/jquery-3.2.1.slim.min.js"></script>
<script src="/js/popper.min.js"></script>
<script src="/js/bootstrap.min.js"></script>
<meta content="width=device-width, initial-scale=1.0, shrink-to-fit=no" name="viewport">
<meta content="ie=edge" http-equiv="X-UA-Compatible">
<script>
window.onload = function () {
setMenu();
hideGateway();
refresh();
drawArrow();
};
// function drawArrow() {
// let activeColor = "#00CC66";
//
// let chainNode = $("#chain");
// let chain = JSON.parse(chainNode.val());
// let num = chain.length;
//
// $("#center-gateway").css({backgroundColor: activeColor});
//
// chain.forEach((val)=>{
// let elem = $("#" + val.unitFlag + "-" + val.app);
// elem.css({backgroundColor: activeColor});
// });
// }
function drawArrow() {
let activeColor = "#FFCC66";
activeColor = "#00CC66";
let interval = 700;
let chainNode = $("#chain");
let chain = JSON.parse(chainNode.val());
let num = chain.length;
$("#center-gateway").css({backgroundColor: activeColor});
for (let i = 0; i < num; i++) {
(function (i) {
setTimeout(function () {
let val = chain[i];
lightUp(val, activeColor)
}, interval * (num - i))
})(i)
}
}
function lightUp(val, activeColor) {
let id = val.unitFlag + "-" + val.app
console.log(id)
let elem = $("#" + id);
elem.css({backgroundColor: activeColor});
}
function hideGateway() {
let host = window.location.host;
if ("demo.appactive.io" !== host) {
$("#domainArea").hide();
}
}
function setMenu() {
let currentNode = $("#current");
let current = currentNode.val();
let elem = $("#" + current);
elem.addClass("active");
}
function refresh(routerIdNew) {
let routerId = routerIdNew === undefined ? getRouterId() : routerIdNew;
// let element = document.getElementById("routerId");
// element.value = routerId;
document.cookie = "r_id=" + routerId + "; path=/";
}
function setRouterId() {
let element = document.getElementById("r_id");
let routerId = element.value;
// 整个网站生效 用 /
refresh(routerId);
}
function getRouterId() {
let routerId = getQuery("r_id")
if (routerId === undefined) {
routerId = getCookie("r_id")
}
if (routerId === undefined) {
routerId = 24
}
return routerId;
}
function getCookie(cname) {
let name = cname + "=";
let ca = document.cookie.split(';');
for (let i = 0; i < ca.length; i++) {
let c = ca[i].trim();
if (c.indexOf(name) === 0) return c.substring(name.length, c.length);
}
return undefined;
}
function getQuery(variable) {
let query = window.location.search.substring(1);
let vars = query.split("&");
for (let i = 0; i < vars.length; i++) {
let pair = vars[i].split("=");
if (pair[0] === variable) {
return pair[1];
}
}
return undefined;
}
</script>
</head>
<body>
<header th:fragment="pageHeader">
<div class="container">
<nav class="navbar navbar-light" style="background-color: #e3f2fd;">
<div class="container-fluid">
<div class="navbar-header">
<a class="navbar-brand" href="#">App-Active Demo</a>
</div>
<ul class="nav navbar-nav">
<li id="listProduct"><a class="nav-link" th:href="@{'/listProduct'}">列表</a></li>
<li id="detailProduct"><a class="nav-link" th:href="@{'/detailProduct'}">详情</a></li>
<li id="buyProduct"><a class="nav-link" th:href="@{'/buyProduct'}">下单</a></li>
</ul>
</div>
</nav>
<div class="row">
<div class="col-md-3">
<div class="row">
<div class="col-md-4" id="domainArea">
<div class="card" style="width: 8rem">
<img class="card-img-top" src="/img/gateway.png">
<div class="card-body" id="center-gateway">
<h5 class="card-title" style="text-align: center">center</h5>
<h5 class="card-title" style="text-align: center">gateway</h5>
</div>
</div>
</div>
</div>
</div>
<div class="col-md-9">
<div class="row" th:each="unit: ${metaData.unitList}">
<div class="col-md-4" th:each="app: ${metaData.appList}">
<div class="card" style="width: 8rem">
<img class="card-img-top" th:src="'/img/' + ${app} + '.png'">
<div class="card-body" th:id="@{${unit} + '-' + ${app}}">
<h5 class="card-title" style="text-align: center" th:text="${unit}"></h5>
<h5 class="card-title" style="text-align: center" th:text="${app}"></h5>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<input hidden id="result" th:value="${result}"/>
<input hidden id="chain" th:value="${chain}"/>
<input hidden id="current" th:value="${current}"/>
</header>
</body>
</html>

@ -0,0 +1,27 @@
<!--
~ Copyright 1999-2022 Alibaba Group Holding Ltd.
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.w3.org/1999/xhtml">
<head th:replace="header.html :: commonHead"></head>
<body>
<header th:replace="header.html :: pageHeader"></header>
<div id="welcome-page">
</div>
</body>
</html>

@ -0,0 +1,217 @@
# AppActive Example
## 项目说明
[![vTlxsA.jpg](https://s1.ax1x.com/2022/09/04/vTlxsA.jpg)](https://imgse.com/i/vTlxsA)
本 demo 整体架构如图。
注:
- 应用共同依赖的注册中心 Nacos 和 数据库 MySQL 未在图中展示出来。
- 本 demo 的命令通道依赖于 Nacos。
### 核心概念
异地多活的思想类比于日常中的鸡蛋不要放在一个篮子里面,通过对业务应用进行单元化拆分部署,使得一个单元的故障影响面限定在特定单元内。在基于 AppActive 做应用多活方案中,可根据应用属性将应用分为全局、核心和一般服务 3 类,其又可归属到中心单元和普通单元 2 类单元中,单元一般用来指代机房。
3 类服务:
- 全局服务:强一致性的服务(例如库存、金额等),无法做异地多活单元化拆分,其需要在中心单元进行服务读写。
- 核心服务:做单元化拆分的业务应用,根据预设的多活规则,根据请求信息在特定单元进行读写的业务,核心业务的拆分是异地多活系统建设中的核心。
- 普通服务:属于系统非核心链路上的业务,对数据一致性要求较低的应用,一般出于成本考虑不做单元化拆分。
2 类单元:
- 中心单元:中心单元,也可称为中心机房,可承载全局、核心和普通服务 3 类,其一般在机房硬件配置上较一般单元高。
- 一般单元:其他非中心单元的单元,用来承载非全局服务以外的其他服务,也可称为一般机房。
本 example 中共有 3 个应用,按照距离(调用链路)终端用户由近及远分别为:
- frontend: 前端应用,接受用户请求,请求到实际数据后返回
- product: 产品应用,提供三个服务:
- 产品列表: 普通服务
- 产品详情: 单元服务
- 产品下单: 中心服务
- storage: 库存应用,供下单服务扣减库存
应用在中心单元Center和 普通单元unit 各部署一套(对等部署)。
图中绿色格子代表了本次请求的调用链路。
## 示例
### 快速接入
在启动示例进行演示之前,我们先了解一下 Spring Cloud 应用如何使用 AppActive 所提供的异地多活能力。
**注意 本章节只是为了便于您理解接入方式,本示例代码中已经完成接入工作,您无需再进行修改。**
1. 首先,修改 pom.xml 文件,在 provider 和 consumer 已添加最新 `spring-cloud-alibaba-dependencies` 的基础上添加以下 maven 依赖。
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-appactive</artifactId>
</dependency>
2. 在 Provider 应用的 `application.properties` 配置文件中给特定接口配置分流策略。其中后缀 `core-path` 用于配置核心服务,`global-path` 用于配置全局服务,`general-path` 用于配置一般服务,比如 demo 中的 product 应用分流策略配置如下:
spring.cloud.appactive.filter.core-path=/detailHidden/*,/detail/*
spring.cloud.appactive.filter.global-path=/buy/*
spring.cloud.appactive.filter.general-path=/*
3. 在 Consumer 应用的 `application.properties` 配置客户端负载均衡为 AppActive 所提供的负载均衡算法,配置方式如下,注意需要将`[service-name]`替换成具体的待消费服务名。
[service-name].ribbon.NFLoadBalancerRuleClassName =com.alibaba.cloud.appactive.consumer.AppactiveRule
### 快速启动
1. 启动 Nacos, MySQL, 并往 Nacos 中推送多活规则:
- 在 `appactive-example` 目录下,执行:`docker-compose -f component-quickstart.yml up -d` 启动 Nacos, MySQL。
- 执行以下命令:`curl -X POST 'http://127.0.0.1:8848/nacos/v1/console/namespaces' -d 'customNamespaceId=appactiveDemoNamespaceId&namespaceName=appactiveDemoNamespaceName&namespaceDesc=appactiveDemoNamespaceDesc'` 在 Nacos 配置中心中创建一个演示用命名空间 appactiveDemoNamespaceId。
- 执行以下命令:`sh baseline.sh 2 NACOS appactiveDemoNamespaceId`,往命名空间中推送多活规则。多活规则说明如下:
- `appactive.dataId.idSourceRulePath`: 描述如何从 http 流量中提取路由标
- `appactive.dataId.transformerRulePath`: 描述如何解析路由标
- `appactive.dataId.trafficRouteRulePath`: 描述路由标和单元的映射关系
- `appactive.dataId.dataScopeRuleDirectoryPath_mysql-product`: 描述数据库的属性
2. 启动 5 套应用,启动参数分别为:
- frontend
```
-Dappactive.channelTypeEnum=NACOS
-Dappactive.namespaceId=appactiveDemoNamespaceId
-Dappactive.unit=unit
-Dappactive.app=frontend
-Dio.appactive.demo.unitlist=center,unit
-Dio.appactive.demo.applist=frontend,product,storage
-Dserver.port=8875
```
- product
```
-Dappactive.channelTypeEnum=NACOS
-Dappactive.namespaceId=appactiveDemoNamespaceId
-Dappactive.unit=center
-Dappactive.app=product
-Dspring.datasource.url=jdbc:mysql://127.0.0.1:3306/product?characterEncoding=utf8&useSSL=false&serverTimezone=GMT&activeInstanceId=mysql&activeDbName=product
-Dserver.port=8883
```
```
-Dappactive.channelTypeEnum=NACOS
-Dappactive.namespaceId=appactiveDemoNamespaceId
-Dappactive.unit=unit
-Dappactive.app=product
-Dspring.datasource.url=jdbc:mysql://127.0.0.1:3306/product?characterEncoding=utf8&useSSL=false&serverTimezone=GMT&activeInstanceId=mysql&activeDbName=product
-Dserver.port=8873
```
- storage
```
-Dappactive.channelTypeEnum=NACOS
-Dappactive.namespaceId=appactiveDemoNamespaceId
-Dappactive.unit=center
-Dappactive.app=storage
-Dspring.datasource.url=jdbc:mysql://127.0.0.1:3306/product?characterEncoding=utf8&useSSL=false&serverTimezone=GMT
-Dserver.port=8881
```
```
-Dappactive.channelTypeEnum=NACOS
-Dappactive.namespaceId=appactiveDemoNamespaceId
-Dappactive.unit=unit
-Dappactive.app=storage
-Dspring.datasource.url=jdbc:mysql://127.0.0.1:3306/product?characterEncoding=utf8&useSSL=false&serverTimezone=GMT
-Dserver.port=8871
```
## 效果演示
1. 归属于一般Unit单元的普通应用服务调用演示。在浏览器中输入`http://127.0.0.1:8079/listProduct` 地址,可见请求通过 frontend 应用被发送给了 product。
[![vTlxsA.jpg](https://s1.ax1x.com/2022/09/04/vTlxsA.jpg)](https://imgse.com/i/vTlxsA)
由于上述路径中的 `/listProduct` 在 product 应用中匹配到的是 `/*` 路径规则根据规则内容该服务属于普通应用做了未做单元化拆分所以frontend 在从注册中心获取的 product 地址列表中不存在倾向性,会随机选择地址进行请求发送。因此多次请求上述路径,会看到请求在 product 的一般Unit)和 中心center单元应用中来回切换。
2. 归属于 unit 单元的核心应用服务调用演示。在浏览器中输入:`http://127.0.0.1:8079/detailProduct` 路径,由于上述路径中的 `/detailProduct` 在 product 应用中匹配到的是 `/detail/*` 路径规则,根据规则内容,该服务属于核心应用做了单元会拆分,其会根据请求中 Header, Cookie 或请求参数中的变量具体的值去判断该请求的下游单元类型,由于事先配置如下切流规则(具体可见 rule 目录下的 idUnitMapping.json 文件内容):
```
{
"itemType": "UnitRuleItem",
"items": [
{
"name": "unit",
"conditions": [
{
"@userIdBetween": [
"0~1999"
]
}
]
},
{
"name": "center",
"conditions": [
{
"@userIdBetween": [
"2000~9999"
]
}
]
}
]
}
```
上述规则表示用户Id为 0 ~ 1999 的请求将发送给下游提供者中的一般Unit单元中的核心应用实例用户Id为 2000 ~ 9999 的请求将发送给下游提供者中的中心Center单元全局应用实例。
如下图模拟一个用户Id为 1999 的请求,可见请求通过 frontend 发送到了下游中 product 的一般Unit单元中的核心应用实例。
[![1xnI7.jpg](https://s1.328888.xyz/2022/09/05/1xnI7.jpg)](https://imgloc.com/i/1xnI7)
如下图模拟一个用户Id为 2000 的请求,可见请求通过 frontend 发送到了下游中 product 的中心center单元中的全局应用实例。
[![1xAHk.jpg](https://s1.328888.xyz/2022/09/05/1xAHk.jpg)](https://imgloc.com/i/1xAHk)
3. 归属于中心Center单元的全局应用服务调用演示。在浏览器中输入`http://127.0.0.1:8079/buyProduct` 路径,由于上述路径中的 `/buyProduct` 在 product 和 storage 应用中匹配到的是 `/buy/*` 路径规则根据规则内容该服务属于全局应用未做单元会拆分其会直接将请求发送到下游的中心Center单元中全局应用实例。
[![1s4Oi.jpg](https://s1.328888.xyz/2022/09/04/1s4Oi.jpg)](https://imgloc.com/i/1s4Oi)
4. 切流演示。切流时主要做了如下几件事:
- 构建新的映射关系规则和禁写规则(手动)
- 将禁写规则推送给应用
- 等待数据追平后将新的映射关系规则推送给应用
接下来演示的切流规则会将用户Id为 0 ~ 2999 的请求将发送给下游提供者中的一般Unit单元中的核心应用实例用户Id为 3000 ~ 9999 的请求将发送给下游提供者中的中心Center单元中的全局应用实例。具体的规则详情见 idUnitMappingNext.json
```
{
"itemType": "UnitRuleItem",
"items": [
{
"name": "unit",
"conditions": [
{
"@userIdBetween": [
"0~2999"
]
}
]
},
{
"name": "center",
"conditions": [
{
"@userIdBetween": [
"3000~9999"
]
}
]
}
]
}
```
如下图模拟一个用户Id为 2999 的请求,可见请求通过 frontend 发送到了下游中 product 的 unit 单元中的核心应用实例,切流规则生效。
[![1xUnd.jpg](https://s1.328888.xyz/2022/09/05/1xUnd.jpg)](https://imgloc.com/i/1xUnd)
如下图模拟一个用户Id为 3000 的请求,可见请求通过 frontend 发送到了下游中 product 的 center 单元中的全局应用实例,切流规则生效。
[![1xpgr.jpg](https://s1.328888.xyz/2022/09/05/1xpgr.jpg)](https://imgloc.com/i/1xpgr)

@ -0,0 +1,216 @@
# AppActive Example
## Introduction
[![vTlxsA.jpg](https://s1.ax1x.com/2022/09/04/vTlxsA.jpg)](https://imgse.com/i/vTlxsA)
The overall structure of this demo is shown above.
Note:
- The registry Nacos and database MySQL that the application depends on are not shown in the figure.
- The demo uses Nacos as a command channel
### Core Concept
concept
The idea of multi-activity in different places is analogous to that eggs in daily life should not be placed in one basket. By splitting and deploying business applications in units, the impact of a unit's failure is limited to a specific unit. In the application multi-activity scheme based on AppActive, applications can be divided into three categories: global business, core business and shared business according to application attributes, which can be classified into two types of units: center unit and general unit. Units are generally used to refer to server room.
3 types applications:
- Global application: business applications (such as inventory, amount, etc.) with strong consistency cannot be split between multiple activities in different places, and they need to read and write services in the center unit.
- Core application: an application that is divided into units, an application that reads and writes in a specific unit according to the preset multi-active rules and request information.
- General application: It belongs to the business on the non-core link of the system, has low requirements on data consistency, and is not divided into units.
2 types units:
- Center unit: also known as the center server room, can carry three types of global, core and general services, and generally has higher hardware configuration than the normal unit in the server room.
- Normal unit: other non-central units, used to carry services other than non-global services, can also be called general server rooms.
There are three applications in demo, according to the distance (call link) of the end user from near and far:
- frontend: frontend application, accept user requests, and return after requesting actual data.
- product: product application, providing three services:
- product List: General Service
- product Details: Unit Service
- product order: central service, relying on inventory application
- storage: storage application, it provide create orders service for users.
The applications are deployed in each of the center and normal unit.
The green grid in the figure represents the call link of the request.
## Instructions for use
### Quick start
Before starting the example for demonstration, let's take a look at how Spring Cloud applications use the remote multi-active capabilities provided by AppActive.
**Note, this chapter is only for your understanding of the access method. The access work has been completed in this examples, and you do not need to modify it.**
1. First, modify the pom.xml file to add the following maven dependencies based on the latest `spring-cloud-alibaba-dependencies` added to the provider and consumer.
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-appactive</artifactId>
</dependency>
2. Configure offload policies for specific interfaces in the `application.properties` configuration file of the Provider application. The suffix `core-path` is used to configure core services, `global-path` is used to configure global services, and `general-path` is used to configure general services. For example, the product application distribution strategy in the demo is configured as follows
spring.cloud.appactive.filter.core-path=/detailHidden/*,/detail/*
spring.cloud.appactive.filter.global-path=/buy/*
spring.cloud.appactive.filter.general-path=/*
3. In the `application.properties` of the Consumer application, configure the client load balancing as the load balancing algorithm provided by AppActive. The configuration method is as follows. Note that `[service-name]` needs to be replaced with the specific service name to be consumed.
[service-name].ribbon.NFLoadBalancerRuleClassName =com.alibaba.cloud.appactive.consumer.AppactiveRule
### Presentation preparation
1. Start Nacos, MySQL, and push multi-active rules to Nacos:
- In the `appactive-example` directory, execute: `docker-compose -f component-quickstart.yml up -d` to start Nacos, MySQL.
- Execute the following command: `curl -X POST 'http://127.0.0.1:8848/nacos/v1/console/namespaces' -d 'customNamespaceId=appactiveDemoNamespaceId&namespaceName=appactiveDemoNamespaceName&namespaceDesc=appactiveDemoNamespaceDesc'` Create a demo named in Nacos configuration center Space appactiveDemoNamespaceId.
- Execute the following command: `sh baseline.sh 2 NACOS appactiveDemoNamespaceId` to push the multi-active rule to the namespace. The multi-live rules are described as follows:
- `appactive.dataId.idSourceRulePath`: Describes how to extract routing tokens from http traffic
- `appactive.dataId.transformerRulePath`: Describes how to parse routing tokens
- `appactive.dataId.trafficRouteRulePath`: Describes the mapping between routing labels and units
- `appactive.dataId.dataScopeRuleDirectoryPath_mysql-product`: Properties that describe the database
2. Start five sets of applications, the startup parameters are:
- frontend
```
-Dappactive.channelTypeEnum=NACOS
-Dappactive.namespaceId=appactiveDemoNamespaceId
-Dappactive.unit=unit
-Dappactive.app=frontend
-Dio.appactive.demo.unitlist=center,unit
-Dio.appactive.demo.applist=frontend,product,storage
-Dserver.port=8875
```
- product
```
-Dappactive.channelTypeEnum=NACOS
-Dappactive.namespaceId=appactiveDemoNamespaceId
-Dappactive.unit=center
-Dappactive.app=product
-Dspring.datasource.url=jdbc:mysql://127.0.0.1:3306/product?characterEncoding=utf8&useSSL=false&serverTimezone=GMT&activeInstanceId=mysql&activeDbName=product
-Dserver.port=8883
```
```
-Dappactive.channelTypeEnum=NACOS
-Dappactive.namespaceId=appactiveDemoNamespaceId
-Dappactive.unit=unit
-Dappactive.app=product
-Dspring.datasource.url=jdbc:mysql://127.0.0.1:3306/product?characterEncoding=utf8&useSSL=false&serverTimezone=GMT&activeInstanceId=mysql&activeDbName=product
-Dserver.port=8873
```
- storage
```
-Dappactive.channelTypeEnum=NACOS
-Dappactive.namespaceId=appactiveDemoNamespaceId
-Dappactive.unit=center
-Dappactive.app=storage
-Dspring.datasource.url=jdbc:mysql://127.0.0.1:3306/product?characterEncoding=utf8&useSSL=false&serverTimezone=GMT
-Dserver.port=8881
```
```
-Dappactive.channelTypeEnum=NACOS
-Dappactive.namespaceId=appactiveDemoNamespaceId
-Dappactive.unit=unit
-Dappactive.app=storage
-Dspring.datasource.url=jdbc:mysql://127.0.0.1:3306/product?characterEncoding=utf8&useSSL=false&serverTimezone=GMT
-Dserver.port=8871
```
## Demonstration process
1. Demonstration of general service calls belonging to the normal unit. Typing: `http://127.0.0.1:8079/listProduct` address in the browser, it can be seen that the request is sent to the product through the frontend application.
[![vTlxsA.jpg](https://s1.ax1x.com/2022/09/04/vTlxsA.jpg)](https://imgse.com/i/vTlxsA)
Since `/listProduct` in the above path matches the `/*` path rule in the product application, which corresponds to the normal unit, frontend does not have a tendency in the product address list obtained from the registry, and will randomly select an address for request sending. So requesting the above path multiple times will see the request switch back and forth between the normal and center units of the product.
2. Demonstration of core service calls belonging to the different unit by request information. Typing: `http://127.0.0.1:8079/detailProduct` in the browser, because the `/detailProduct` in the above path matches the `/detail/*` path rule in the product application, corresponding to the normal(unit) unit, it will be based on the request The specific value of the variable in the Header, Cookie or request parameter is used to determine the downstream unit type of the request, because the following flow switching rules are configured in advance (for details, see the content of the idUnitMapping.json file in the rule directory):
```
{
"itemType": "UnitRuleItem",
"items": [
{
"name": "unit",
"conditions": [
{
"@userIdBetween": [
"0~1999"
]
}
]
},
{
"name": "center",
"conditions": [
{
"@userIdBetween": [
"2000~9999"
]
}
]
}
]
}
```
The above rules mean that requests with user IDs of 0 ~ 1999 will be sent to the noraml(unit) in the downstream provider, and requests with user IDs of 2000 ~ 9999 will be sent to the center unit of the downstream provider.
As shown in the figure below, a request with a user ID of 1999 is simulated. It can be seen that the request is sent to the normal unit of product in the downstream through the frontend.
[![1xnI7.jpg](https://s1.328888.xyz/2022/09/05/1xnI7.jpg)](https://imgloc.com/i/1xnI7)
As shown in the figure below, a request with a user ID of 2000 is simulated. It can be seen that the request is sent to the center unit node of the product in the downstream through the frontend.
[![1xAHk.jpg](https://s1.328888.xyz/2022/09/05/1xAHk.jpg)](https://imgloc.com/i/1xAHk)
3. Demonstration of global service invocation belonging to the center unit. Typing: `http://127.0.0.1:8079/buyProduct` path in the browser, because the `/buyProduct` in the above path matches the `/buy/*` path rule in the product and storage applications, corresponding to the center unit, it will directly send the request to the downstream center unit node.
[![1s4Oi.jpg](https://s1.328888.xyz/2022/09/04/1s4Oi.jpg)](https://imgloc.com/i/1s4Oi)
4. Cut flow demo. The main things to do when cutting flow are as follows:
- Build new mapping relationship rules and write prohibition rules (manually).
- Push the write prohibition rules to the application.
- Push the new mapping relationship rules to the application after waiting for the data to equalize.
The streaming rule demonstrated next will send requests with user IDs 0 ~ 2999 to the normal(called unit) unit in the downstream provider, and requests with user IDs 3000 ~ 9999 will be sent to the center(called center) unit in the downstream provider. For specific rules, see idUnitMappingNext.json:
```
{
"itemType": "UnitRuleItem",
"items": [
{
"name": "unit",
"conditions": [
{
"@userIdBetween": [
"0~2999"
]
}
]
},
{
"name": "center",
"conditions": [
{
"@userIdBetween": [
"3000~9999"
]
}
]
}
]
}
```
As shown in the figure below, a request with a user ID of 2999 is simulated. It can be seen that the request is sent to the unit node of the product in the downstream through the frontend, and the flow switching rule takes effect.
[![1xUnd.jpg](https://s1.328888.xyz/2022/09/05/1xUnd.jpg)](https://imgloc.com/i/1xUnd)
As shown in the figure below, a request with a user ID of 3000 is simulated. It can be seen that the request is sent to the center unit node of the product in the downstream through the frontend, and the cut flow rule takes effect.
[![1xpgr.jpg](https://s1.328888.xyz/2022/09/05/1xpgr.jpg)](https://imgloc.com/i/1xpgr)

@ -0,0 +1,15 @@
{
"itemType": "ForbiddenRuleItem",
"items": [
{
"name": "between",
"conditions": [
{
"@userIdBetween": [
"2000~2999"
]
}
]
}
]
}

@ -0,0 +1,4 @@
{
"itemType": "ForbiddenRuleItem",
"items": []
}

@ -0,0 +1,4 @@
{
"source": "arg,header,cookie",
"tokenKey": "r_id"
}

@ -0,0 +1,7 @@
[
{
"id": "userIdBetween",
"mod": "10000"
}
]

@ -0,0 +1,25 @@
{
"itemType": "UnitRuleItem",
"items": [
{
"name": "unit",
"conditions": [
{
"@userIdBetween": [
"0~1999"
]
}
]
},
{
"name": "center",
"conditions": [
{
"@userIdBetween": [
"2000~9999"
]
}
]
}
]
}

@ -0,0 +1,25 @@
{
"itemType": "UnitRuleItem",
"items": [
{
"name": "unit",
"conditions": [
{
"@userIdBetween": [
"0~2999"
]
}
]
},
{
"name": "center",
"conditions": [
{
"@userIdBetween": [
"3000~9999"
]
}
]
}
]
}

@ -0,0 +1,18 @@
FROM openjdk:8-jdk-alpine
WORKDIR /app
COPY target/storage-0.2.1.jar /app
# 设置文件夹操作权限
RUN chown -R root:root /app/* && \
chmod a+rw -R /app/*
#EXPOSE 8090
USER root:root
ARG UNITFLAG=default
ENV TZ=Asia/Shanghai \
UNITFLAG=$UNITFLAG \
LANG="en_US.UTF-8"
ENTRYPOINT ["sh", "-c"]
CMD ["java -jar /app/storage-0.2.1.jar"]

@ -0,0 +1,45 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-examples</artifactId>
<version>${revision}</version>
<relativePath>../../pom.xml</relativePath>
</parent>
<artifactId>storage</artifactId>
<name>Spring Cloud Starter Alibaba Appactive Example - Storage Service</name>
<packaging>jar</packaging>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<maven.deploy.skip>true</maven.deploy.skip>
</properties>
<dependencies>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>appactive-example-common</artifactId>
<version>${revision}</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.40</version>
</dependency>
<dependency>
<groupId>com.alibaba.msha</groupId>
<artifactId>client-bridge-db-mysql</artifactId>
<version>0.2.1</version>
</dependency>
</dependencies>
</project>

@ -0,0 +1,86 @@
/*
* Copyright 2013-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.cloud.example.storage;
import com.alibaba.cloud.example.common.entity.ResultHolder;
import com.alibaba.cloud.example.storage.service.OrderServiceImpl;
import io.appactive.java.api.base.AppContextClient;
import io.appactive.support.log.LogUtil;
import org.slf4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.domain.EntityScan;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
@SpringBootApplication
@EntityScan("com.alibaba.cloud.example.*")
@ComponentScan(basePackages = { "com.alibaba.cloud.example" })
@RestController("/")
@EnableDiscoveryClient
@EnableFeignClients(basePackages = { "com.alibaba.cloud.example" })
public class StorageApplication {
private static final Logger logger = LogUtil.getLogger();
@Autowired
OrderServiceImpl orderService;
@Value("${spring.application.name}")
private String appName;
public static void main(String[] args) {
SpringApplication.run(StorageApplication.class, args);
}
/**
* buy1 is just for bypassing center service protection.
* @param rId rId
* @param id id
* @param number number
* @return resultHolder resultHolder
*/
@RequestMapping({ "/buy", "/buy1" })
@ResponseBody
public ResultHolder<String> buy(
@RequestParam(required = false, defaultValue = "jack") String rId,
@RequestParam(required = false, defaultValue = "12") String id,
@RequestParam(required = false, defaultValue = "1") Integer number) {
String routerId = AppContextClient.getRouteId();
logger.info("buy, routerId: {}, pid: {}, number: {}", routerId, id, number);
ResultHolder<String> resultHolder = orderService.buy(rId, id, number);
resultHolder
.setResult(String.format("routerId %s bought %d of item %s, result: %s",
routerId, number, id, resultHolder.getResult()));
return resultHolder;
}
@RequestMapping("/check")
@ResponseBody
public String check() {
return "OK From " + appName;
}
}

@ -0,0 +1,25 @@
/*
* Copyright 2013-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.cloud.example.storage.repository;
import com.alibaba.cloud.example.common.entity.Product;
import org.springframework.data.jpa.repository.JpaRepository;
public interface ProductRepository extends JpaRepository<Product, String> {
}

@ -0,0 +1,77 @@
/*
* Copyright 2013-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.cloud.example.storage.service;
import java.util.Optional;
import com.alibaba.cloud.example.common.entity.Product;
import com.alibaba.cloud.example.common.entity.ResultHolder;
import com.alibaba.cloud.example.common.service.OrderDAO;
import com.alibaba.cloud.example.storage.repository.ProductRepository;
import io.appactive.support.log.LogUtil;
import org.slf4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
@Service
public class OrderServiceImpl implements OrderDAO.OrderService {
private static final Logger logger = LogUtil.getLogger();
@Autowired
ProductRepository repository;
@Value("${appactive.unit}")
private String unit;
@Override
public ResultHolder<String> buy(String rId, String pId, Integer number) {
String result = null;
try {
Optional<Product> op = repository.findById(pId);
if (op.isPresent()) {
// todo 扣库存,应该强校验
Product p = op.get();
int oldNum = p.getNumber();
int left = oldNum - number;
if (left >= 0) {
p.setNumber(left);
p = repository.save(p);
if (p.getNumber() + number != oldNum) {
result = "storage not consist";
}
else {
result = "success";
}
}
else {
result = "sold out";
}
}
else {
result = "no such product";
}
}
catch (Throwable e) {
result = e.getCause().getCause().getMessage();
}
return new ResultHolder<>(result);
}
}

@ -0,0 +1,16 @@
server.port=8085
spring.application.name=storage
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/product?characterEncoding=utf8&useSSL=false&serverTimezone=GMT
spring.datasource.username=root
spring.datasource.password=root
#spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.driver-class-name=io.appactive.db.mysql.driver.Driver
spring.jpa.database-platform=org.hibernate.dialect.MySQL5InnoDBDialect
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL5InnoDBDialect
spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=false
appactive.unit=center
appactive.app=storage
spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848
spring.cloud.appactive.filter.global-path=/buy/*
spring.cloud.appactive.filter.general-path=/*

@ -1,68 +1,72 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<groupId>com.alibaba.cloud</groupId>
<artifactId>nacos-discovery-example</artifactId>
<version>${revision}</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.alibaba.cloud</groupId>
<artifactId>nacos-discovery-example</artifactId>
<version>${revision}</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>nacos-discovery-consumer-example</artifactId>
<name>Spring Cloud Starter Alibaba Nacos Discovery Consumer Example</name>
<description>Example demonstrating how to use nacos discovery</description>
<packaging>jar</packaging>
<artifactId>nacos-discovery-consumer-example</artifactId>
<name>Spring Cloud Starter Alibaba Nacos Discovery Consumer Example</name>
<description>Example demonstrating how to use nacos discovery</description>
<packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-appactive</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
</dependencies>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-deploy-plugin</artifactId>
<version>${maven-deploy-plugin.version}</version>
<configuration>
<skip>true</skip>
</configuration>
</plugin>
</plugins>
</build>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-deploy-plugin</artifactId>
<version>${maven-deploy-plugin.version}</version>
<configuration>
<skip>true</skip>
</configuration>
</plugin>
</plugins>
</build>
</project>

@ -3,19 +3,17 @@ server.port=18083
management.endpoints.web.exposure.include=*
spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848
spring.cloud.nacos.discovery.fail-fast=true
spring.cloud.nacos.username=nacos
spring.cloud.nacos.password=nacos
feign.sentinel.enabled=true
spring.cloud.sentinel.transport.dashboard=localhost:8080
spring.cloud.sentinel.eager=true
spring.cloud.sentinel.datasource.ds1.file.file=classpath: flowrule.json
spring.cloud.sentinel.datasource.ds1.file.data-type=json
spring.cloud.sentinel.datasource.ds1.file.rule-type=flow
spring.cloud.sentinel.datasource.ds2.file.file=classpath: degraderule.json
spring.cloud.sentinel.datasource.ds2.file.data-type=json
spring.cloud.sentinel.datasource.ds2.file.rule-type=degrade
#service-provider.ribbon.NFLoadBalancerRuleClassName=com.netflix.loadbalancer.AvailabilityFilteringRule
service-provider.ribbon.NFLoadBalancerRuleClassName=com.alibaba.cloud.appactive.consumer.AppactiveRule

@ -23,6 +23,10 @@
<module>sentinel-example/sentinel-webflux-example</module>
<module>sentinel-example/sentinel-spring-cloud-gateway-example</module>
<module>sentinel-example/sentinel-zuul-example</module>
<module>appactive-example/common</module>
<module>appactive-example/frontend</module>
<module>appactive-example/product</module>
<module>appactive-example/storage</module>
<module>nacos-example/nacos-discovery-example</module>
<module>nacos-example/nacos-config-example</module>
<module>nacos-example/nacos-gateway-example</module>

@ -25,6 +25,7 @@
<module>spring-cloud-starter-alibaba-sentinel</module>
<module>spring-cloud-alibaba-sentinel-datasource</module>
<module>spring-cloud-alibaba-sentinel-gateway</module>
<module>spring-cloud-starter-alibaba-appactive</module>
<module>spring-cloud-alibaba-commons</module>
</modules>

@ -0,0 +1,33 @@
HELP.md
target/
!.mvn/wrapper/maven-wrapper.jar
!**/src/main/**/target/
!**/src/test/**/target/
### 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/
!**/src/main/**/build/
!**/src/test/**/build/
### VS Code ###
.vscode/

@ -0,0 +1,98 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-starters</artifactId>
<version>${revision}</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>spring-cloud-starter-alibaba-appactive</artifactId>
<name>Spring Cloud Starter Alibaba Appactive</name>
<properties>
<appactive.version>0.2.1</appactive.version>
</properties>
<dependencies>
<dependency>
<groupId>com.alibaba.msha</groupId>
<artifactId>appactive-java-api</artifactId>
<version>${appactive.version}</version>
</dependency>
<dependency>
<groupId>com.alibaba.msha</groupId>
<artifactId>client-rule</artifactId>
<version>${appactive.version}</version>
</dependency>
<dependency>
<groupId>com.alibaba.msha</groupId>
<artifactId>client-bridge-rpc-base</artifactId>
<version>${appactive.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-openfeign-core</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-java8</artifactId>
<version>9.5.1</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.msha</groupId>
<artifactId>client-bridge-servlet</artifactId>
<version>0.2.1</version>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.1</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<version>2.4.0</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
<version>${spring-boot.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>31.1-jre</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>2.0.11</version>
</dependency>
</dependencies>
</project>

@ -0,0 +1,94 @@
/*
* Copyright 2013-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.cloud.appactive;
import javax.annotation.PostConstruct;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.core.env.Environment;
import org.springframework.util.StringUtils;
/**
* @author ChengPu raozihao
* @description
* @date 2022/8/15
*/
@ConfigurationProperties("spring.cloud.appactive.filter")
public class AppactiveProperties {
private String[] corePath;
private String[] globalPath;
private String[] generalPath;
@Autowired
private Environment environment;
public String[] getCorePath() {
return corePath;
}
void setCorePath(String[] corePath) {
this.corePath = corePath;
}
public String[] getGlobalPath() {
return globalPath;
}
void setGlobalPath(String[] globalPath) {
this.globalPath = globalPath;
}
public String[] getGeneralPath() {
return generalPath;
}
void setGeneralPath(String[] generalPath) {
this.generalPath = generalPath;
}
@PostConstruct
public void init() {
this.overrideFromEnv(environment);
}
public void overrideFromEnv(Environment env) {
if (StringUtils.isEmpty(this.getCorePath())) {
String coreValue = env
.resolvePlaceholders("${spring.cloud.appactive.filter.core-path:}");
String[] cores = coreValue.split(",");
this.setCorePath(cores);
}
if (StringUtils.isEmpty(this.getGlobalPath())) {
String globalValue = env
.resolvePlaceholders("${spring.cloud.appactive.filter.global-path:}");
String[] globals = globalValue.split(",");
this.setGlobalPath(globals);
}
if (StringUtils.isEmpty(this.getGeneralPath())) {
String generalValue = env.resolvePlaceholders(
"${spring.cloud.appactive.filter.general-path:}");
String[] generals = generalValue.split(",");
this.setGeneralPath(generals);
}
}
}

@ -0,0 +1,64 @@
/*
* Copyright 2013-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.cloud.appactive.common;
/**
* @author raozihao, mageekchiu
* @author <a href="mailto:zihaorao@gmail.com">Steve</a>
*/
public class ServiceMeta implements Comparable<ServiceMeta> {
private String uriPrefix;
private String ra;
public ServiceMeta() {
}
public ServiceMeta(String uriPrefix, String ra) {
this.uriPrefix = uriPrefix;
this.ra = ra;
}
public String getUriPrefix() {
return uriPrefix;
}
public void setUriPrefix(String uriPrefix) {
this.uriPrefix = uriPrefix;
}
public String getRa() {
return ra;
}
public void setRa(String ra) {
this.ra = ra;
}
@Override
public String toString() {
return "ServiceMeta{" + "uriPrefix='" + uriPrefix + '\'' + ", ra=" + ra + '}';
}
@Override
public int compareTo(ServiceMeta o) {
int pre = this.uriPrefix.compareTo(o.getUriPrefix());
return pre == 0 ? this.ra.compareTo(o.getRa()) : pre;
}
}

@ -0,0 +1,74 @@
/*
* Copyright 2013-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.cloud.appactive.common;
import java.util.List;
/**
* @author raozihao, mageekchiu
* @author <a href="mailto:zihaorao@gmail.com">Steve</a>
*/
public class ServiceMetaObject {
private List<ServiceMeta> serviceMetaList;
/**
* string of serviceMetaList.
*/
private String meta;
private String md5OfList;
public ServiceMetaObject(List<ServiceMeta> serviceMetaList, String md5OfList) {
this.serviceMetaList = serviceMetaList;
this.md5OfList = md5OfList;
}
public ServiceMetaObject() {
}
public List<ServiceMeta> getServiceMetaList() {
return serviceMetaList;
}
public void setServiceMetaList(List<ServiceMeta> serviceMetaList) {
this.serviceMetaList = serviceMetaList;
}
public String getMd5OfList() {
return md5OfList;
}
public void setMd5OfList(String md5OfList) {
this.md5OfList = md5OfList;
}
public String getMeta() {
return meta;
}
public void setMeta(String meta) {
this.meta = meta;
}
@Override
public String toString() {
return "ServiceMetaObject{" + "serviceMetaList=" + serviceMetaList
+ ", md5OfList='" + md5OfList + '\'' + '}';
}
}

@ -0,0 +1,42 @@
/*
* Copyright 2013-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.cloud.appactive.common;
/**
* @author raozihao, mageekchiu
* @author <a href="mailto:zihaorao@gmail.com">Steve</a>
*/
public final class UriContext {
private UriContext() {
}
private static final ThreadLocal<String> URI_INFO = new ThreadLocal<String>();
public static void clearContext() {
URI_INFO.remove();
}
public static String getUriPath() {
return URI_INFO.get();
}
public static void setUriPath(String targetUnit) {
URI_INFO.set(targetUnit);
}
}

@ -0,0 +1,76 @@
/*
* Copyright 2013-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.cloud.appactive.config;
import com.alibaba.cloud.appactive.AppactiveProperties;
import com.alibaba.cloud.appactive.provider.CoreServiceFilter;
import com.alibaba.cloud.appactive.provider.GlobalServiceFilter;
import io.appactive.servlet.RequestFilter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @author ChengPu raozihao
* @description
* @date 2022/8/15
*/
@Configuration
public class FilterAutoConfiguration {
@Autowired
private AppactiveProperties appactiveProperties;
@Bean
public FilterRegistrationBean<GlobalServiceFilter> appActiveCenterServiceFilter() {
if (appactiveProperties.getGlobalPath() == null) {
return null;
}
FilterRegistrationBean<GlobalServiceFilter> filterRegistrationBean = new FilterRegistrationBean<>();
GlobalServiceFilter reqResFilter = new GlobalServiceFilter();
filterRegistrationBean.setFilter(reqResFilter);
filterRegistrationBean.addUrlPatterns(appactiveProperties.getGlobalPath());
return filterRegistrationBean;
}
@Bean
public FilterRegistrationBean<CoreServiceFilter> appActiveUnitServiceFilter() {
if (appactiveProperties.getCorePath() == null) {
return null;
}
FilterRegistrationBean<CoreServiceFilter> filterRegistrationBean = new FilterRegistrationBean<>();
CoreServiceFilter reqResFilter = new CoreServiceFilter();
filterRegistrationBean.setFilter(reqResFilter);
filterRegistrationBean.addUrlPatterns(appactiveProperties.getCorePath());
return filterRegistrationBean;
}
@Bean
public FilterRegistrationBean<RequestFilter> appActiveNormalServiceFilter() {
if (appactiveProperties.getGeneralPath() == null) {
return null;
}
FilterRegistrationBean<RequestFilter> filterRegistrationBean = new FilterRegistrationBean<>();
RequestFilter reqResFilter = new RequestFilter();
filterRegistrationBean.setFilter(reqResFilter);
filterRegistrationBean.addUrlPatterns(appactiveProperties.getGeneralPath());
return filterRegistrationBean;
}
}

@ -0,0 +1,37 @@
/*
* Copyright 2013-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.cloud.appactive.config;
import com.alibaba.cloud.appactive.AppactiveProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @author ChengPu raozihao
* @description
* @date 2022/8/15
*/
@Configuration
public class FilterPropertiesAutoConfiguration {
@Bean
public AppactiveProperties appactiveProperties() {
return new AppactiveProperties();
}
}

@ -0,0 +1,33 @@
/*
* Copyright 2013-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.cloud.appactive.constant;
/**
* @author raozihao, mageekchiu
* @author <a href="mailto:zihaorao@gmail.com">Steve</a>
*/
public final class Constants {
/**
* Router Id header key.
*/
public static final String ROUTER_ID_HEADER_KEY = "appactive-router-id";
private Constants() {
}
}

@ -0,0 +1,124 @@
/*
* Copyright 2013-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.cloud.appactive.consumer;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import com.alibaba.cloud.appactive.common.ServiceMeta;
import com.alibaba.cloud.appactive.common.UriContext;
import com.alibaba.cloud.nacos.ribbon.NacosServer;
import com.alibaba.fastjson.JSONObject;
import com.netflix.client.config.IClientConfig;
import com.netflix.loadbalancer.AbstractServerPredicate;
import com.netflix.loadbalancer.IRule;
import com.netflix.loadbalancer.PredicateKey;
import io.appactive.java.api.base.AppContextClient;
import io.appactive.java.api.base.constants.AppactiveConstant;
import io.appactive.java.api.base.constants.ResourceActiveType;
import io.appactive.java.api.rule.traffic.TrafficRouteRuleService;
import io.appactive.java.api.utils.lang.StringUtils;
import io.appactive.rule.ClientRuleService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.AntPathMatcher;
/**
* @author ChengPu raozihao
* @description
* @date 2022/8/20
*/
public class AppactivePredicate extends AbstractServerPredicate {
private static final Logger logger = LoggerFactory
.getLogger(AppactivePredicate.class);
private final TrafficRouteRuleService trafficRouteRuleService = ClientRuleService
.getTrafficRouteRuleService();
private final AntPathMatcher antPathMatcher = new AntPathMatcher();
public AppactivePredicate(IRule rule, IClientConfig clientConfig) {
super(rule, clientConfig);
}
public AppactivePredicate(IRule rule) {
super(rule);
}
@Override
public boolean apply(PredicateKey predicateKey) {
// Just support Nacos Registry now, if it's a NacosServer, return true directly.
if (!(predicateKey.getServer() instanceof NacosServer)) {
return true;
}
NacosServer server = (NacosServer) predicateKey.getServer();
// uriPath of the request.
String uriPath = UriContext.getUriPath();
Map<String, String> metadata = server.getMetadata();
// zone
String unitType = metadata.get("ut");
String svcMeta = metadata.get("svc_meta");
String version = metadata.get("svc_meta_v");
if (unitType == null || svcMeta == null || version == null) {
return true;
}
String serviceType = null;
List<ServiceMeta> serviceMetas = JSONObject.parseArray(svcMeta,
ServiceMeta.class);
Map<String, String> matchingPatterns = new HashMap<>();
for (ServiceMeta sm : serviceMetas) {
if (antPathMatcher.match(sm.getUriPrefix(), uriPath)) {
matchingPatterns.put(sm.getUriPrefix(), sm.getRa());
}
}
Comparator<String> patternComparator = antPathMatcher
.getPatternComparator(uriPath);
if (!matchingPatterns.isEmpty()) {
List<String> urls = new ArrayList<>(matchingPatterns.keySet());
urls.sort(patternComparator);
serviceType = matchingPatterns.get(urls.get(0));
}
if (!StringUtils.isBlank(serviceType) && ResourceActiveType.CENTER_RESOURCE_TYPE
.equalsIgnoreCase(serviceType)) {
return AppactiveConstant.CENTER_FLAG.equalsIgnoreCase(unitType);
}
else if (!StringUtils.isBlank(serviceType)
&& ResourceActiveType.UNIT_RESOURCE_TYPE.equalsIgnoreCase(serviceType)) {
// routeId of the request.
String routeId = AppContextClient.getRouteId();
if (routeId == null) {
return false;
}
// targetUnit setting by users, such as unit0,unit1,...,unitn.
String targetUnitByRouteId = trafficRouteRuleService
.getUnitByRouteId(routeId);
return !StringUtils.isBlank(targetUnitByRouteId)
&& targetUnitByRouteId.equalsIgnoreCase(unitType);
}
return true;
}
}

@ -0,0 +1,49 @@
/*
* Copyright 2013-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.cloud.appactive.consumer;
import com.netflix.loadbalancer.AbstractServerPredicate;
import com.netflix.loadbalancer.CompositePredicate;
import com.netflix.loadbalancer.ILoadBalancer;
import com.netflix.loadbalancer.PredicateBasedRule;
/**
* @description
* @author ChengPu raozihao
* @date 2022/8/21
*/
public class AppactiveRule extends PredicateBasedRule {
private AbstractServerPredicate predicate;
public AppactiveRule() {
super();
predicate = CompositePredicate.withPredicate(new AppactivePredicate(this, null))
.addFallbackPredicate(AbstractServerPredicate.alwaysTrue()).build();
}
@Override
public void setLoadBalancer(ILoadBalancer lb) {
super.setLoadBalancer(lb);
}
@Override
public AbstractServerPredicate getPredicate() {
return predicate;
}
}

@ -0,0 +1,95 @@
/*
* Copyright 2013-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.cloud.appactive.consumer;
import java.util.ArrayList;
import java.util.List;
import javax.annotation.PostConstruct;
import feign.RequestInterceptor;
import feign.codec.Decoder;
import feign.optionals.OptionalDecoder;
import io.appactive.support.lang.CollectionUtils;
import io.appactive.support.log.LogUtil;
import org.slf4j.Logger;
import org.springframework.beans.factory.ObjectFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.http.HttpMessageConverters;
import org.springframework.cloud.openfeign.support.ResponseEntityDecoder;
import org.springframework.cloud.openfeign.support.SpringDecoder;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.ClientHttpRequestInterceptor;
import org.springframework.web.client.RestTemplate;
/**
* @author raozihao, mageekchiu
* @author <a href="mailto:zihaorao@gmail.com">Steve</a>
*/
@Configuration
public class ConsumerAutoConfig {
private static final Logger logger = LogUtil.getLogger();
@Autowired
ApplicationContext context;
@Autowired(required = false)
RestTemplate restTemplate;
@Autowired
private ObjectFactory<HttpMessageConverters> messageConverters;
@Bean
@ConditionalOnMissingBean
public Decoder appActiveFeignDecoder() {
return new OptionalDecoder(
new ResponseEntityDecoder(new SpringDecoder(this.messageConverters)));
}
@Bean
public BeanPostProcessor feignDecoderPostProcessor() {
return new FeignDecoderPostProcessor(context);
}
@Bean
public RequestInterceptor routerIdTransmissionRequestInterceptor() {
return new RouterIdTransmissionRequestInterceptor();
}
@PostConstruct
public void init() {
if (restTemplate != null) {
List<ClientHttpRequestInterceptor> interceptors = restTemplate
.getInterceptors();
if (CollectionUtils.isEmpty(interceptors)) {
interceptors = new ArrayList<>();
}
interceptors.add(new ReqResInterceptor());
logger.info(
"ConsumerAutoConfig adding interceptor for restTemplate[{}]......",
restTemplate.getClass());
restTemplate.setInterceptors(interceptors);
}
}
}

@ -0,0 +1,78 @@
/*
* Copyright 2013-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.cloud.appactive.consumer;
import feign.codec.Decoder;
import io.appactive.support.log.LogUtil;
import org.slf4j.Logger;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.context.ApplicationContext;
/**
* @author raozihao, mageekchiu
* @author <a href="mailto:zihaorao@gmail.com">Steve</a>
*/
public class FeignDecoderPostProcessor implements BeanPostProcessor {
private static final Logger logger = LogUtil.getLogger();
final ApplicationContext context;
public FeignDecoderPostProcessor(ApplicationContext context) {
this.context = context;
}
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName)
throws BeansException {
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName)
throws BeansException {
// there does`t have to be a Decoder(when using default), so we added a default
if (bean instanceof Decoder) {
if ("appActiveFeignDecoder".equals(beanName)) {
logger.info(
"FeignDecoderPostProcessor replacing defaultDecoder {} ......",
beanName);
}
else {
logger.info(
"FeignDecoderPostProcessor replacing customizedDecoder {} ......",
beanName);
}
Decoder decoder = (Decoder) bean;
// wrap original decoder
return new ResponseInterceptor(decoder);
/// another way
// Object proxy = Proxy.newProxyInstance(bean.getClass().getClassLoader(),
// bean.getClass().getInterfaces(),
// (proxy1, method, args) -> {
// String result = (String) method.invoke(bean, args);
// return result.toUpperCase();
// });
// return proxy;
}
return bean;
}
}

@ -0,0 +1,56 @@
/*
* Copyright 2013-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.cloud.appactive.consumer;
import java.io.IOException;
import com.alibaba.cloud.appactive.common.UriContext;
import com.alibaba.cloud.appactive.constant.Constants;
import io.appactive.java.api.base.AppContextClient;
import io.appactive.support.log.LogUtil;
import org.slf4j.Logger;
import org.springframework.http.HttpRequest;
import org.springframework.http.client.ClientHttpRequestExecution;
import org.springframework.http.client.ClientHttpRequestInterceptor;
import org.springframework.http.client.ClientHttpResponse;
/**
* @author raozihao, mageekchiu
* @author <a href="mailto:zihaorao@gmail.com">Steve</a>
*/
public class ReqResInterceptor implements ClientHttpRequestInterceptor {
private static final Logger logger = LogUtil.getLogger();
@Override
public ClientHttpResponse intercept(HttpRequest request, byte[] body,
ClientHttpRequestExecution execution) throws IOException {
request.getHeaders().add(Constants.ROUTER_ID_HEADER_KEY,
AppContextClient.getRouteId());
UriContext.setUriPath(request.getURI().getPath());
ClientHttpResponse response = execution.execute(request, body);
logger.info("ReqResInterceptor uri {} for request {} got cleared",
UriContext.getUriPath(), request.getURI());
UriContext.clearContext();
return response;
}
}

@ -0,0 +1,55 @@
/*
* Copyright 2013-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.cloud.appactive.consumer;
import java.io.IOException;
import java.lang.reflect.Type;
import java.util.Objects;
import com.alibaba.cloud.appactive.common.UriContext;
import feign.FeignException;
import feign.Response;
import feign.codec.Decoder;
import io.appactive.support.log.LogUtil;
import org.slf4j.Logger;
/**
* @author raozihao, mageekchiu
* @author <a href="mailto:zihaorao@gmail.com">Steve</a>
*/
public class ResponseInterceptor implements Decoder {
private static final Logger logger = LogUtil.getLogger();
final Decoder delegate;
public ResponseInterceptor(Decoder delegate) {
Objects.requireNonNull(delegate, "Decoder must not be null. ");
this.delegate = delegate;
}
@Override
public Object decode(Response response, Type type)
throws IOException, FeignException {
Object object = delegate.decode(response, type);
logger.info("ResponseInterceptor uri {} for request {} got cleared by {}",
UriContext.getUriPath(), response.request().url(), delegate.getClass());
UriContext.clearContext();
return object;
}
}

@ -0,0 +1,53 @@
/*
* Copyright 2013-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.cloud.appactive.consumer;
import javax.servlet.http.HttpServletRequest;
import com.alibaba.cloud.appactive.common.UriContext;
import com.alibaba.cloud.appactive.constant.Constants;
import feign.RequestInterceptor;
import feign.RequestTemplate;
import io.appactive.java.api.base.AppContextClient;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
/**
* @author raozihao, mageekchiu
* @author <a href="mailto:zihaorao@gmail.com">Steve</a>
*/
public class RouterIdTransmissionRequestInterceptor implements RequestInterceptor {
@Override
public void apply(RequestTemplate requestTemplate) {
ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder
.getRequestAttributes();
if (requestAttributes == null) {
return;
}
HttpServletRequest request = requestAttributes.getRequest();
if (request == null) {
return;
}
requestTemplate.header(Constants.ROUTER_ID_HEADER_KEY,
AppContextClient.getRouteId());
// store uri for routing filter
UriContext.setUriPath(requestTemplate.url());
}
}

@ -0,0 +1,31 @@
/*
* Copyright 2013-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.cloud.appactive.consumer;
import java.util.Map;
import com.netflix.loadbalancer.Server;
/**
* @author raozihao, mageekchiu
* @author <a href="mailto:zihaorao@gmail.com">Steve</a>
*/
public interface ServerMeta {
Map<String, String> getMetaMap(Server server);
}

@ -0,0 +1,100 @@
/*
* Copyright 2013-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.cloud.appactive.provider;
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.alibaba.cloud.appactive.constant.Constants;
import io.appactive.java.api.base.AppContextClient;
import io.appactive.java.api.base.constants.AppactiveConstant;
import io.appactive.java.api.bridge.servlet.ServletService;
import io.appactive.java.api.rule.TrafficMachineService;
import io.appactive.java.api.rule.machine.AbstractMachineUnitRuleService;
import io.appactive.java.api.rule.traffic.TrafficRouteRuleService;
import io.appactive.java.api.utils.lang.StringUtils;
import io.appactive.rule.ClientRuleService;
import io.appactive.support.log.LogUtil;
import org.springframework.http.HttpStatus;
import org.springframework.web.server.ResponseStatusException;
/**
* @author raozihao, mageekchiu
* @author <a href="mailto:zihaorao@gmail.com">Steve</a>
*/
public class CoreServiceFilter implements Filter {
private final TrafficRouteRuleService trafficRouteRuleService = ClientRuleService
.getTrafficRouteRuleService();
private final AbstractMachineUnitRuleService machineUnitRuleService = ClientRuleService
.getMachineUnitRuleService();
private final TrafficMachineService trafficMachineService = new TrafficMachineService(
trafficRouteRuleService, machineUnitRuleService);
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
if (!(request instanceof HttpServletRequest
&& response instanceof HttpServletResponse)) {
chain.doFilter(request, response);
return;
}
HttpServletRequest httpRequest = (HttpServletRequest) request;
String routerId = ServletService.getRouteIdFromHeader(httpRequest,
Constants.ROUTER_ID_HEADER_KEY);
if (StringUtils.isBlank(routerId)) {
throw new ResponseStatusException(HttpStatus.FORBIDDEN,
"no routerId provided for this request");
}
if (!trafficMachineService.isInCurrentUnit(routerId)) {
throw new ResponseStatusException(HttpStatus.FORBIDDEN,
"routerId " + routerId + " does not belong in core : "
+ machineUnitRuleService.getCurrentUnit());
}
AppContextClient.setUnitContext(routerId);
LogUtil.info(AppactiveConstant.PROJECT_NAME + "-routerIdFilter-doFilter-header:"
+ AppContextClient.getRouteId());
chain.doFilter(request, response);
clear();
}
@Override
public void destroy() {
}
private void clear() {
AppContextClient.clearUnitContext();
}
}

@ -0,0 +1,51 @@
/*
* Copyright 2013-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.cloud.appactive.provider;
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
/**
* @author raozihao, mageekchiu
* @author <a href="mailto:zihaorao@gmail.com">Steve</a>
*/
public class GeneralServiceFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
// todo
chain.doFilter(request, response);
}
@Override
public void destroy() {
}
}

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save