Polish code and document of sentinel-sofa-rpc-adapter
Signed-off-by: Eric Zhao <sczyh16@gmail.com>
This commit is contained in:
parent
18acb1d154
commit
d07d17b4ca
|
|
@ -28,6 +28,7 @@ consumerConfig.setParameter("sofa.rpc.sentinel.enabled", "false");
|
||||||
```
|
```
|
||||||
|
|
||||||
or add setting in `rpc-config.json` file, and its priority is lower than above.
|
or add setting in `rpc-config.json` file, and its priority is lower than above.
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"sofa.rpc.sentinel.enabled": true
|
"sofa.rpc.sentinel.enabled": true
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,23 @@
|
||||||
|
/*
|
||||||
|
* Copyright 1999-2020 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
|
||||||
|
*
|
||||||
|
* 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.csp.sentinel.adapter.sofa.rpc;
|
package com.alibaba.csp.sentinel.adapter.sofa.rpc;
|
||||||
|
|
||||||
import com.alibaba.csp.sentinel.Entry;
|
import com.alibaba.csp.sentinel.Entry;
|
||||||
import com.alibaba.csp.sentinel.Tracer;
|
import com.alibaba.csp.sentinel.Tracer;
|
||||||
import com.alibaba.csp.sentinel.adapter.sofa.rpc.config.SofaRpcConfig;
|
|
||||||
import com.alipay.sofa.rpc.common.RpcConfigs;
|
import com.alipay.sofa.rpc.common.RpcConfigs;
|
||||||
import com.alipay.sofa.rpc.common.utils.StringUtils;
|
import com.alipay.sofa.rpc.common.utils.StringUtils;
|
||||||
import com.alipay.sofa.rpc.config.AbstractInterfaceConfig;
|
import com.alipay.sofa.rpc.config.AbstractInterfaceConfig;
|
||||||
|
|
@ -19,14 +34,14 @@ abstract class AbstractSofaRpcFilter extends Filter {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean needToLoad(FilterInvoker invoker) {
|
public boolean needToLoad(FilterInvoker invoker) {
|
||||||
AbstractInterfaceConfig config = invoker.getConfig();
|
AbstractInterfaceConfig<?, ?> config = invoker.getConfig();
|
||||||
|
|
||||||
String enabled = config.getParameter(SofaRpcConfig.SOFA_RPC_SENTINEL_ENABLED);
|
String enabled = config.getParameter(SentinelConstants.SOFA_RPC_SENTINEL_ENABLED);
|
||||||
if (StringUtils.isNotBlank(enabled)) {
|
if (StringUtils.isNotBlank(enabled)) {
|
||||||
return Boolean.valueOf(enabled);
|
return Boolean.parseBoolean(enabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
return RpcConfigs.getOrDefaultValue(SofaRpcConfig.SOFA_RPC_SENTINEL_ENABLED, true);
|
return RpcConfigs.getOrDefaultValue(SentinelConstants.SOFA_RPC_SENTINEL_ENABLED, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void traceResponseException(SofaResponse response, Entry interfaceEntry, Entry methodEntry) {
|
protected void traceResponseException(SofaResponse response, Entry interfaceEntry, Entry methodEntry) {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,27 @@
|
||||||
|
/*
|
||||||
|
* Copyright 1999-2020 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
|
||||||
|
*
|
||||||
|
* 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.csp.sentinel.adapter.sofa.rpc;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author cdfive
|
||||||
|
* @since 1.7.2
|
||||||
|
*/
|
||||||
|
public final class SentinelConstants {
|
||||||
|
|
||||||
|
public static final String SOFA_RPC_SENTINEL_ENABLED = "sofa.rpc.sentinel.enabled";
|
||||||
|
|
||||||
|
private SentinelConstants() {}
|
||||||
|
}
|
||||||
|
|
@ -18,6 +18,7 @@ package com.alibaba.csp.sentinel.adapter.sofa.rpc;
|
||||||
import com.alibaba.csp.sentinel.*;
|
import com.alibaba.csp.sentinel.*;
|
||||||
import com.alibaba.csp.sentinel.adapter.sofa.rpc.fallback.SofaRpcFallbackRegistry;
|
import com.alibaba.csp.sentinel.adapter.sofa.rpc.fallback.SofaRpcFallbackRegistry;
|
||||||
import com.alibaba.csp.sentinel.slots.block.BlockException;
|
import com.alibaba.csp.sentinel.slots.block.BlockException;
|
||||||
|
|
||||||
import com.alipay.sofa.rpc.common.RpcConstants;
|
import com.alipay.sofa.rpc.common.RpcConstants;
|
||||||
import com.alipay.sofa.rpc.core.exception.SofaRpcException;
|
import com.alipay.sofa.rpc.core.exception.SofaRpcException;
|
||||||
import com.alipay.sofa.rpc.core.request.SofaRequest;
|
import com.alipay.sofa.rpc.core.request.SofaRequest;
|
||||||
|
|
@ -59,7 +60,8 @@ public class SentinelSofaRpcConsumerFilter extends AbstractSofaRpcFilter {
|
||||||
Entry methodEntry = null;
|
Entry methodEntry = null;
|
||||||
try {
|
try {
|
||||||
interfaceEntry = SphU.entry(interfaceResourceName, ResourceTypeConstants.COMMON_RPC, EntryType.OUT);
|
interfaceEntry = SphU.entry(interfaceResourceName, ResourceTypeConstants.COMMON_RPC, EntryType.OUT);
|
||||||
methodEntry = SphU.entry(methodResourceName, ResourceTypeConstants.COMMON_RPC, EntryType.OUT, getMethodArguments(request));
|
methodEntry = SphU.entry(methodResourceName, ResourceTypeConstants.COMMON_RPC,
|
||||||
|
EntryType.OUT, getMethodArguments(request));
|
||||||
|
|
||||||
SofaResponse response = invoker.invoke(request);
|
SofaResponse response = invoker.invoke(request);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@ import com.alibaba.csp.sentinel.*;
|
||||||
import com.alibaba.csp.sentinel.adapter.sofa.rpc.fallback.SofaRpcFallbackRegistry;
|
import com.alibaba.csp.sentinel.adapter.sofa.rpc.fallback.SofaRpcFallbackRegistry;
|
||||||
import com.alibaba.csp.sentinel.context.ContextUtil;
|
import com.alibaba.csp.sentinel.context.ContextUtil;
|
||||||
import com.alibaba.csp.sentinel.slots.block.BlockException;
|
import com.alibaba.csp.sentinel.slots.block.BlockException;
|
||||||
|
|
||||||
import com.alipay.sofa.rpc.common.RpcConstants;
|
import com.alipay.sofa.rpc.common.RpcConstants;
|
||||||
import com.alipay.sofa.rpc.core.exception.SofaRpcException;
|
import com.alipay.sofa.rpc.core.exception.SofaRpcException;
|
||||||
import com.alipay.sofa.rpc.core.request.SofaRequest;
|
import com.alipay.sofa.rpc.core.request.SofaRequest;
|
||||||
|
|
@ -58,17 +59,18 @@ public class SentinelSofaRpcProviderFilter extends AbstractSofaRpcFilter {
|
||||||
return invoker.invoke(request);
|
return invoker.invoke(request);
|
||||||
}
|
}
|
||||||
|
|
||||||
String applicationName = getApplicationName(request);
|
String callerApp = getApplicationName(request);
|
||||||
String interfaceResourceName = getInterfaceResourceName(request);
|
String interfaceResourceName = getInterfaceResourceName(request);
|
||||||
String methodResourceName = getMethodResourceName(request);
|
String methodResourceName = getMethodResourceName(request);
|
||||||
|
|
||||||
Entry interfaceEntry = null;
|
Entry interfaceEntry = null;
|
||||||
Entry methodEntry = null;
|
Entry methodEntry = null;
|
||||||
try {
|
try {
|
||||||
ContextUtil.enter(methodResourceName, applicationName);
|
ContextUtil.enter(methodResourceName, callerApp);
|
||||||
|
|
||||||
interfaceEntry = SphU.entry(interfaceResourceName, ResourceTypeConstants.COMMON_RPC, EntryType.IN);
|
interfaceEntry = SphU.entry(interfaceResourceName, ResourceTypeConstants.COMMON_RPC, EntryType.IN);
|
||||||
methodEntry = SphU.entry(methodResourceName, ResourceTypeConstants.COMMON_RPC, EntryType.IN, getMethodArguments(request));
|
methodEntry = SphU.entry(methodResourceName, ResourceTypeConstants.COMMON_RPC,
|
||||||
|
EntryType.IN, getMethodArguments(request));
|
||||||
|
|
||||||
SofaResponse response = invoker.invoke(request);
|
SofaResponse response = invoker.invoke(request);
|
||||||
|
|
||||||
|
|
@ -82,11 +84,9 @@ public class SentinelSofaRpcProviderFilter extends AbstractSofaRpcFilter {
|
||||||
if (methodEntry != null) {
|
if (methodEntry != null) {
|
||||||
methodEntry.exit(1, getMethodArguments(request));
|
methodEntry.exit(1, getMethodArguments(request));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (interfaceEntry != null) {
|
if (interfaceEntry != null) {
|
||||||
interfaceEntry.exit();
|
interfaceEntry.exit();
|
||||||
}
|
}
|
||||||
|
|
||||||
ContextUtil.exit();
|
ContextUtil.exit();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -56,4 +56,6 @@ public class SofaRpcUtils {
|
||||||
public static Object[] getMethodArguments(SofaRequest request) {
|
public static Object[] getMethodArguments(SofaRequest request) {
|
||||||
return request.getMethodArgs();
|
return request.getMethodArgs();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private SofaRpcUtils() {}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,9 +0,0 @@
|
||||||
package com.alibaba.csp.sentinel.adapter.sofa.rpc.config;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author cdfive
|
|
||||||
*/
|
|
||||||
public class SofaRpcConfig {
|
|
||||||
|
|
||||||
public static final String SOFA_RPC_SENTINEL_ENABLED = "sofa.rpc.sentinel.enabled";
|
|
||||||
}
|
|
||||||
|
|
@ -15,6 +15,8 @@
|
||||||
*/
|
*/
|
||||||
package com.alibaba.csp.sentinel.adapter.sofa.rpc.fallback;
|
package com.alibaba.csp.sentinel.adapter.sofa.rpc.fallback;
|
||||||
|
|
||||||
|
import com.alibaba.csp.sentinel.util.AssertUtil;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Global Sentinel fallback registry for SOFARPC services.
|
* Global Sentinel fallback registry for SOFARPC services.
|
||||||
*
|
*
|
||||||
|
|
@ -30,6 +32,7 @@ public final class SofaRpcFallbackRegistry {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void setProviderFallback(SofaRpcFallback providerFallback) {
|
public static void setProviderFallback(SofaRpcFallback providerFallback) {
|
||||||
|
AssertUtil.notNull(providerFallback, "providerFallback cannot be null");
|
||||||
SofaRpcFallbackRegistry.providerFallback = providerFallback;
|
SofaRpcFallbackRegistry.providerFallback = providerFallback;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -38,6 +41,7 @@ public final class SofaRpcFallbackRegistry {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void setConsumerFallback(SofaRpcFallback consumerFallback) {
|
public static void setConsumerFallback(SofaRpcFallback consumerFallback) {
|
||||||
|
AssertUtil.notNull(consumerFallback, "consumerFallback cannot be null");
|
||||||
SofaRpcFallbackRegistry.consumerFallback = consumerFallback;
|
SofaRpcFallbackRegistry.consumerFallback = consumerFallback;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
package com.alibaba.csp.sentinel.adapter.sofa.rpc;
|
package com.alibaba.csp.sentinel.adapter.sofa.rpc;
|
||||||
|
|
||||||
import com.alibaba.csp.sentinel.adapter.sofa.rpc.config.SofaRpcConfig;
|
|
||||||
import com.alipay.sofa.rpc.codec.Serializer;
|
import com.alipay.sofa.rpc.codec.Serializer;
|
||||||
import com.alipay.sofa.rpc.common.RpcConfigs;
|
import com.alipay.sofa.rpc.common.RpcConfigs;
|
||||||
import com.alipay.sofa.rpc.config.ConsumerConfig;
|
import com.alipay.sofa.rpc.config.ConsumerConfig;
|
||||||
|
|
@ -23,12 +22,12 @@ public class AbstractSofaRpcFilterTest {
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void setUp() {
|
public void setUp() {
|
||||||
removeRpcConfig(SofaRpcConfig.SOFA_RPC_SENTINEL_ENABLED);
|
removeRpcConfig(SentinelConstants.SOFA_RPC_SENTINEL_ENABLED);
|
||||||
}
|
}
|
||||||
|
|
||||||
@After
|
@After
|
||||||
public void cleanUp() {
|
public void cleanUp() {
|
||||||
removeRpcConfig(SofaRpcConfig.SOFA_RPC_SENTINEL_ENABLED);
|
removeRpcConfig(SentinelConstants.SOFA_RPC_SENTINEL_ENABLED);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
@ -40,13 +39,13 @@ public class AbstractSofaRpcFilterTest {
|
||||||
FilterInvoker invoker = new FilterInvoker(null, null, providerConfig);
|
FilterInvoker invoker = new FilterInvoker(null, null, providerConfig);
|
||||||
assertTrue(providerFilter.needToLoad(invoker));
|
assertTrue(providerFilter.needToLoad(invoker));
|
||||||
|
|
||||||
providerConfig.setParameter(SofaRpcConfig.SOFA_RPC_SENTINEL_ENABLED, "false");
|
providerConfig.setParameter(SentinelConstants.SOFA_RPC_SENTINEL_ENABLED, "false");
|
||||||
assertFalse(providerFilter.needToLoad(invoker));
|
assertFalse(providerFilter.needToLoad(invoker));
|
||||||
|
|
||||||
providerConfig.setParameter(SofaRpcConfig.SOFA_RPC_SENTINEL_ENABLED, "");
|
providerConfig.setParameter(SentinelConstants.SOFA_RPC_SENTINEL_ENABLED, "");
|
||||||
assertTrue(providerFilter.needToLoad(invoker));
|
assertTrue(providerFilter.needToLoad(invoker));
|
||||||
|
|
||||||
RpcConfigs.putValue(SofaRpcConfig.SOFA_RPC_SENTINEL_ENABLED, "false");
|
RpcConfigs.putValue(SentinelConstants.SOFA_RPC_SENTINEL_ENABLED, "false");
|
||||||
assertFalse(providerFilter.needToLoad(invoker));
|
assertFalse(providerFilter.needToLoad(invoker));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -59,13 +58,13 @@ public class AbstractSofaRpcFilterTest {
|
||||||
FilterInvoker invoker = new FilterInvoker(null, null, consumerConfig);
|
FilterInvoker invoker = new FilterInvoker(null, null, consumerConfig);
|
||||||
assertTrue(consumerFilter.needToLoad(invoker));
|
assertTrue(consumerFilter.needToLoad(invoker));
|
||||||
|
|
||||||
consumerConfig.setParameter(SofaRpcConfig.SOFA_RPC_SENTINEL_ENABLED, "false");
|
consumerConfig.setParameter(SentinelConstants.SOFA_RPC_SENTINEL_ENABLED, "false");
|
||||||
assertFalse(consumerFilter.needToLoad(invoker));
|
assertFalse(consumerFilter.needToLoad(invoker));
|
||||||
|
|
||||||
consumerConfig.setParameter(SofaRpcConfig.SOFA_RPC_SENTINEL_ENABLED, "");
|
consumerConfig.setParameter(SentinelConstants.SOFA_RPC_SENTINEL_ENABLED, "");
|
||||||
assertTrue(consumerFilter.needToLoad(invoker));
|
assertTrue(consumerFilter.needToLoad(invoker));
|
||||||
|
|
||||||
RpcConfigs.putValue(SofaRpcConfig.SOFA_RPC_SENTINEL_ENABLED, "false");
|
RpcConfigs.putValue(SentinelConstants.SOFA_RPC_SENTINEL_ENABLED, "false");
|
||||||
assertFalse(consumerFilter.needToLoad(invoker));
|
assertFalse(consumerFilter.needToLoad(invoker));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -85,14 +84,14 @@ public class AbstractSofaRpcFilterTest {
|
||||||
FilterInvoker consumerInvoker = new FilterInvoker(null, null, consumerConfig);
|
FilterInvoker consumerInvoker = new FilterInvoker(null, null, consumerConfig);
|
||||||
assertTrue(consumerFilter.needToLoad(consumerInvoker));
|
assertTrue(consumerFilter.needToLoad(consumerInvoker));
|
||||||
|
|
||||||
providerConfig.setParameter(SofaRpcConfig.SOFA_RPC_SENTINEL_ENABLED, "false");
|
providerConfig.setParameter(SentinelConstants.SOFA_RPC_SENTINEL_ENABLED, "false");
|
||||||
assertFalse(providerFilter.needToLoad(providerInvoker));
|
assertFalse(providerFilter.needToLoad(providerInvoker));
|
||||||
assertTrue(consumerFilter.needToLoad(consumerInvoker));
|
assertTrue(consumerFilter.needToLoad(consumerInvoker));
|
||||||
|
|
||||||
providerConfig.setParameter(SofaRpcConfig.SOFA_RPC_SENTINEL_ENABLED, "");
|
providerConfig.setParameter(SentinelConstants.SOFA_RPC_SENTINEL_ENABLED, "");
|
||||||
assertTrue(providerFilter.needToLoad(providerInvoker));
|
assertTrue(providerFilter.needToLoad(providerInvoker));
|
||||||
|
|
||||||
RpcConfigs.putValue(SofaRpcConfig.SOFA_RPC_SENTINEL_ENABLED, "false");
|
RpcConfigs.putValue(SentinelConstants.SOFA_RPC_SENTINEL_ENABLED, "false");
|
||||||
assertFalse(providerFilter.needToLoad(providerInvoker));
|
assertFalse(providerFilter.needToLoad(providerInvoker));
|
||||||
assertFalse(consumerFilter.needToLoad(consumerInvoker));
|
assertFalse(consumerFilter.needToLoad(consumerInvoker));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ Sentinel 提供了与 SOFARPC 整合的模块 - `sentinel-sofa-rpc-adapter`,
|
||||||
|
|
||||||
引入此依赖后,SOFARPC 的服务接口和方法(包括调用端和服务端)就会成为 Sentinel 中的资源,在配置了规则后就可以自动享受到 Sentinel 的防护能力。
|
引入此依赖后,SOFARPC 的服务接口和方法(包括调用端和服务端)就会成为 Sentinel 中的资源,在配置了规则后就可以自动享受到 Sentinel 的防护能力。
|
||||||
|
|
||||||
> **注:若希望接入 Dashboard,请参考demo中的注释添加VM参数,只引入`sentinel-sofa-rpc-adapter`无法接入控制台!**
|
> **注:若希望接入 Dashboard,请参考 demo 中的注释添加启动参数。只引入 `sentinel-sofa-rpc-adapter` 依赖无法接入控制台!**
|
||||||
|
|
||||||
若不希望开启 Sentinel SOFARPC Adapter 中的某个 Filter,可以手动关闭对应的 Filter,比如:
|
若不希望开启 Sentinel SOFARPC Adapter 中的某个 Filter,可以手动关闭对应的 Filter,比如:
|
||||||
|
|
||||||
|
|
@ -21,7 +21,7 @@ providerConfig.setParameter("sofa.rpc.sentinel.enabled", "false");
|
||||||
consumerConfig.setParameter("sofa.rpc.sentinel.enabled", "false");
|
consumerConfig.setParameter("sofa.rpc.sentinel.enabled", "false");
|
||||||
```
|
```
|
||||||
|
|
||||||
或者在`rpc-config.json`文件中设置,它的优先级要低一些。
|
或者在 `rpc-config.json` 文件中设置,它的优先级要低一些。
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
|
|
@ -29,14 +29,14 @@ consumerConfig.setParameter("sofa.rpc.sentinel.enabled", "false");
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
# 运行Demo
|
## 运行 Demo
|
||||||
|
|
||||||
1. 启动控制台,运行`DashboardApplication`
|
1. 启动控制台,运行 `DashboardApplication`
|
||||||
|
|
||||||
2. 启动Provider,运行`DemoProvider`(VM参数:`-Dproject.name=DemoProvider -Dcsp.sentinel.dashboard.server=localhost:8080`)
|
2. 启动 Provider,运行 `DemoProvider`(VM参数:`-Dproject.name=DemoProvider -Dcsp.sentinel.dashboard.server=localhost:8080`)
|
||||||
|
|
||||||
3. 启动Consumer,运行`DemoConsumer`(VM参数:`-Dproject.name=DemoConsumer -Dcsp.sentinel.dashboard.server=localhost:8080`)
|
3. 启动 Consumer,运行 `DemoConsumer`(VM参数:`-Dproject.name=DemoConsumer -Dcsp.sentinel.dashboard.server=localhost:8080`)
|
||||||
|
|
||||||
通过控制台实时监控、簇点链路菜单观察接口调用、资源情况;对资源设置不同流控规则,进行观察和调试。
|
通过控制台实时监控、簇点链路菜单观察接口调用、资源情况;对资源设置不同流控规则,进行观察和调试。
|
||||||
|
|
||||||
参考:[Sentinel控制台](https://github.com/alibaba/Sentinel/wiki/控制台).
|
参考:[Sentinel 控制台文档](https://github.com/alibaba/Sentinel/wiki/控制台).
|
||||||
Loading…
Reference in New Issue