Sentinel(五)整合Nacos实现动态规则配置持久化、双向同步

这里写目录标题
1、为什么整合Nacos
2、效果演示
3、源码拉取
3、创建公共配置
4、控制台规则配置
4.1、流程规则
4.2、降级规则
4.3、热点规则
4.4、系统规则
4.5、授权规则
5、网关控制台规则配置
5.1、API管理
5.2、流程规则
5.3、降级规则
5.4、系统规则
5、打包部署
6、源码包下载
1、为什么整合Nacos
默认情况下Sentinel配置的规则是储存的内存中,在重新Sentinel服务后,配置会显示,我们通过整合第三方中间件实现,配置的持久化,比如使用Nacos;

我们要实现Sentinel与Nacos的双向同步持久化,就需要对sentinel-dashboard的源码包进行修改。

2、效果演示
我们以流控规则为例,演示一个数据同步持久化的操作;

1、nacos同步到sentinel:

在nacos中,新增配置文件,文件的DataId为sentinel,内容为:

[
    {
        "app":"user-service",// 服务名称
        "resource": "/list", //资源名称
        "count": 1, //阀值
        "grade": 1, //阀值类型,0表示线程数,1表示QPS;
        "limitApp": "default", //来源应用    
        "strategy": 0,// 流控模式,0表示直接,1表示关联,2表示链路;
        "controlBehavior": 0 //流控效果,0表示快速失败,1表示Warm Up,2表示排队等待
    }
]
1
2
3
4
5
6
7
8
9
10
11

查看Sentinel控制台:数据已经实现了同步


2、sentinel同步到nacos:

我们在sentinel控制台,建立任意流控规则,如下:


查看Nacos控制台:配置数据已经实现了同步


3、源码拉取
1、下载源码压缩包
在Sentinel-github下载需要版本的压缩包,比如Sentinel-1.8.1.zip

2、加载源码
将下载好的Sentinel-1.8.1.zip解压,使用IDE工具,打开sentinel-dashboard工程


3、修改pom

将sentinel-datasource-nacos的scope标签注释掉

<dependency>
    <groupId>com.alibaba.csp</groupId>
    <artifactId>sentinel-datasource-nacos</artifactId>
    <!--<scope>test</scope>-->
</dependency>

1
2
3
4
5
6
3、创建公共配置
在进行规则代码修改之前需要创建Nacos配置文件,在com.alibaba.csp.sentinel.dashboard.rule包下创建nacos包,并且在包下创建四个类:RuleNacosConfig、RuleNacosProvider、RuleNacosPublisher、RuleNacosConstants


RuleNacosConfig:

@Configuration
public class RuleNacosConfig{
    @Bean
    public ConfigService nacosConfigService() throws Exception {
        Properties properties = new Properties();
        properties.put(PropertyKeyConst.SERVER_ADDR, "112.15.11.18:8848");
        // properties.put(PropertyKeyConst.NAMESPACE, "xxx"); 命名空间
        // properties.put(PropertyKeyConst.USERNAME, "xxx"); 用户名
        // properties.put(PropertyKeyConst.PASSWORD, "xxx"); 密码
        return ConfigFactory.createConfigService(properties);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
RuleNacosProvider:

@Component
public class RuleNacosProvider {

    @Autowired
    private ConfigService configService;

    public String getRules(String dataId, String app) throws Exception {
        // 将服务名称设置为GroupId
        return configService.getConfig(dataId, app, 3000);
    }
}
1
2
3
4
5
6
7
8
9
10
11
RuleNacosPublisher:

@Component
public class RuleNacosPublisher {

    @Autowired
    private ConfigService configService;

    public void publish(String dataId, String app, String rules) throws Exception {
        AssertUtil.notEmpty(app, "app name cannot be empty");
        if (rules == null) {
            return;
        }
        // 将服务名称设置为GroupId
        configService.publishConfig(dataId, app, rules);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
RuleNacosConstants :

public class RuleNacosConstants {
    public static final String FLOW_DATA_ID = "sentinel.rule.flow";
    public static final String DEGRADE_DATA_ID = "sentinel.rule.degrade";
    public static final String SYSTEM_DATA_ID = "sentinel.rule.system";
    public static final String PARAM_DATA_ID = "sentinel.rule.param";
    public static final String AUTHORITY_DATA_ID = "sentinel.rule.authority";
    public static final String GATEWAY_API_DATA_ID = "sentinel.rule.gateway.api";
    public static final String GATEWAY_FLOW_DATA_ID = "sentinel.rule.gateway.flow";
}
1
2
3
4
5
6
7
8
9
4、控制台规则配置
通过修改源码,实现流控规则、降级规则、热点规则、系统规则、授权规则的持久化操作;

4.1、流程规则
1、修改sidebar.html:

<!--将dashboard.flowV1 修改为dashboard.flow -->
<li ui-sref-active="active">
    <a ui-sref="dashboard.flowV1({app: entry.app})">
        <i class="glyphicon glyphicon-filter"></i>&nbsp;&nbsp;流控规则
    </a>
</li>

<!--修改后代码-->
<li ui-sref-active="active">
    <a ui-sref="dashboard.flow({app: entry.app})">
        <i class="glyphicon glyphicon-filter"></i>&nbsp;&nbsp;流控规则
    </a>
</li>
1
2
3
4
5
6
7
8
9
10
11
12
13
2、修改FlowControllerV2:
将RuleNacosProvider和RuleNacosPublisher注入到FlowControllerV2中

// 修改位置如下:
@Autowired
@Qualifier("flowRuleDefaultProvider")
private DynamicRuleProvider<List<FlowRuleEntity>> ruleProvider;
@Autowired
@Qualifier("flowRuleDefaultPublisher")
private DynamicRulePublisher<List<FlowRuleEntity>> rulePublisher;

// 将上面代码修改为以下代码:
@Autowired
private RuleNacosProvider ruleProvider;
@Autowired
private RuleNacosPublisher rulePublisher;
1
2
3
4
5
6
7
8
9
10
11
12
13
修改读取逻辑:

// 修改位置如下:
List<FlowRuleEntity> rules = ruleProvider.getRules(app);
if (rules != null && !rules.isEmpty()) {
    for (FlowRuleEntity entity : rules) {
         entity.setApp(app);
         if (entity.getClusterConfig() != null && entity.getClusterConfig().getFlowId() != null) {
             entity.setId(entity.getClusterConfig().getFlowId());
          }
     }
}

// 将上面代码修改为以下代码:
String ruleStr = ruleProvider.getRules(RuleNacosConstants.FLOW_DATA_ID, app);
List<FlowRuleEntity> rules = new ArrayList<>();
if (ruleStr != null) {
    rules = JSON.parseArray(ruleStr, FlowRuleEntity.class);
    if (rules != null && !rules.isEmpty()) {
        for (FlowRuleEntity entity : rules) {
            entity.setApp(app);
        }
    }
 }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
修改推送逻辑:

// 修改位置如下:
private void publishRules(/*@NonNull*/ String app) throws Exception {
    List<FlowRuleEntity> rules = repository.findAllByApp(app);
    rulePublisher.publish(app, rules);
}

// 将上面代码修改为以下代码:
private void publishRules(String app) {
  try {
       List<FlowRuleEntity> rules = repository.findAllByApp(app);
       String ruleStr = JSON.toJSONString(rules);
       rulePublisher.publish(RuleNacosConstants.FLOW_DATA_ID, app, ruleStr);
  } catch (Exception e) {
      e.printStackTrace();
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
4.2、降级规则
修改DegradeController:
将RuleNacosProvider和RuleNacosPublisher注入到DegradeController中

// 加入以下代码:
@Autowired
private RuleNacosProvider ruleProvider;
@Autowired
private RuleNacosPublisher rulePublisher;
1
2
3
4
5
修改读取逻辑:

// 修改位置如下:
List<DegradeRuleEntity> rules = sentinelApiClient.fetchDegradeRuleOfMachine(app, ip, port);

// 将上面代码修改为以下代码:
String ruleStr = ruleProvider.getRules(RuleNacosConstants.DEGRADE_DATA_ID, app);
List<DegradeRuleEntity> rules = new ArrayList<>();
if (ruleStr != null) {
    rules = JSON.parseArray(ruleStr, DegradeRuleEntity.class);
    if (rules != null && !rules.isEmpty()) {
       for (DegradeRuleEntity entity : rules) {
           entity.setApp(app);
       }
   }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
修改推送逻辑:

// 1、修改位置如下:
private boolean publishRules(String app, String ip, Integer port) {
   List<DegradeRuleEntity> rules = repository.findAllByMachine(MachineInfo.of(app, ip, port));
   return sentinelApiClient.setDegradeRuleOfMachine(app, ip, port, rules);
}

// 将上面代码修改为以下代码:
private void publishRules(String app) {
  try {
      List<DegradeRuleEntity> rules = repository.findAllByApp(app);
      String ruleStr = JSON.toJSONString(rules);
      rulePublisher.publish(RuleNacosConstants.DEGRADE_DATA_ID, app, ruleStr);
  } catch (Exception e) {
     e.printStackTrace();
  }
}
=======================================================================================

// 2、修改位置如下:有两处
if (!publishRules(entity.getApp(), entity.getIp(), entity.getPort())) {
     logger.warn("Publish degrade rules failed, app={}", entity.getApp());
}

// 将上面代码修改为以下代码:
publishRules(entity.getApp());
=======================================================================================

// 3、修改位置如下:
if (!publishRules(oldEntity.getApp(), oldEntity.getIp(), oldEntity.getPort())) {
   logger.warn("Publish degrade rules failed, app={}", oldEntity.getApp());
}

// 将上面代码修改为以下代码:
publishRules(oldEntity.getApp());
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
4.3、热点规则
修改ParamRuleController:
将RuleNacosProvider和RuleNacosPublisher注入到ParamFlowRuleController中

// 加入以下代码:
@Autowired
private RuleNacosProvider ruleProvider;
@Autowired
private RuleNacosPublisher rulePublisher;
1
2
3
4
5
修改读取逻辑:

// 修改位置如下:
return sentinelApiClient.fetchParamFlowRulesOfMachine(app, ip, port)
                .thenApply(repository::saveAll)
                .thenApply(Result::ofSuccess)
                .get();

// 将上面代码修改为以下代码:
String ruleStr = ruleProvider.getRules(RuleNacosConstants.PARAM_DATA_ID, app);
List<ParamFlowRuleEntity> rules = new ArrayList<>();
if (ruleStr != null) {
    rules = JSON.parseArray(ruleStr, ParamFlowRuleEntity.class);
    if (rules != null && !rules.isEmpty()) {
        for (ParamFlowRuleEntity entity : rules) {
           entity.setApp(app);
           }
    }
}
rules = repository.saveAll(rules);
return Result.ofSuccess(rules);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
修改推送逻辑:

// 1、修改位置如下:
private CompletableFuture<Void> publishRules(String app, String ip, Integer port) {
    List<ParamFlowRuleEntity> rules = repository.findAllByMachine(MachineInfo.of(app, ip, port));
    return sentinelApiClient.setParamFlowRuleOfMachine(app, ip, port, rules);
}

// 将上面代码修改为以下代码:
private void publishRules(String app) {
   try {
       List<ParamFlowRuleEntity> rules = repository.findAllByApp(app);
       String ruleStr = JSON.toJSONString(rules);
       rulePublisher.publish(RuleNacosConstants.PARAM_DATA_ID, app, ruleStr);
   } catch (Exception e) {
     e.printStackTrace();
   }
}
=======================================================================================

// 2、修改位置如下:有两处
try {
   entity = repository.save(entity);
   publishRules(entity.getApp(), entity.getIp(), entity.getPort()).get();
   return Result.ofSuccess(entity);
} catch (ExecutionException ex) {
    ....
}
// 将上面代码修改为以下代码:
try {
   entity = repository.save(entity);
   publishRules(entity.getApp());
   return Result.ofSuccess(entity);
} catch (Exception ex) {
    ....
}
=======================================================================================

// 3、修改位置如下:
try {
     repository.delete(id);
    publishRules(oldEntity.getApp(), oldEntity.getIp(), oldEntity.getPort()).get();
    return Result.ofSuccess(id);
} catch (ExecutionException ex) {
    ....
}

// 将上面代码修改为以下代码:
try {
     repository.delete(id);
    publishRules(oldEntity.getApp());
    return Result.ofSuccess(id);
} catch (Exception ex) {
    ....
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
4.4、系统规则
修改SystemController:
将RuleNacosProvider和RuleNacosPublisher注入到SystemController中

// 加入以下代码:
@Autowired
private RuleNacosProvider ruleProvider;
@Autowired
private RuleNacosPublisher rulePublisher;
1
2
3
4
5
修改读取逻辑:

// 修改位置如下:
List<SystemRuleEntity> rules = sentinelApiClient.fetchSystemRuleOfMachine(app, ip, port);

// 将上面代码修改为以下代码:
String ruleStr = ruleProvider.getRules(RuleNacosConstants.SYSTEM_DATA_ID, app);
List<SystemRuleEntity> rules = new ArrayList<>();
if (ruleStr != null) {
    rules = JSON.parseArray(ruleStr, SystemRuleEntity.class);
    if (rules != null && !rules.isEmpty()) {
        for (SystemRuleEntity entity : rules) {
             entity.setApp(app);
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
修改推送逻辑:

// 1、修改位置如下:
private boolean publishRules(String app, String ip, Integer port) {
   List<SystemRuleEntity> rules = repository.findAllByMachine(MachineInfo.of(app, ip, port));
   return sentinelApiClient.setSystemRuleOfMachine(app, ip, port, rules);
}

// 将上面代码修改为以下代码:
private void publishRules(String app) {
  try {
       List<SystemRuleEntity> rules = repository.findAllByApp(app);
       String ruleStr = JSON.toJSONString(rules);
       rulePublisher.publish(RuleNacosConstants.SYSTEM_DATA_ID, app, ruleStr);
   } catch (Exception e) {
       e.printStackTrace();
  }
}
=======================================================================================

// 2、修改位置如下
if (!publishRules(app, ip, port)) {
    logger.warn("Publish system rules fail after rule add");
}

// 将上面代码修改为以下代码:
publishRules(entity.getApp());
=======================================================================================

// 3、修改位置如下
if (!publishRules(entity.getApp(), entity.getIp(), entity.getPort())) {
    logger.info("publish system rules fail after rule update");
}

// 将上面代码修改为以下代码:
publishRules(entity.getApp());
=======================================================================================

// 4、修改位置如下:
if (!publishRules(oldEntity.getApp(), oldEntity.getIp(), oldEntity.getPort())) {
    logger.info("publish system rules fail after rule delete");
}

// 将上面代码修改为以下代码:
publishRules(oldEntity.getApp());
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
4.5、授权规则
修改AuthorityRuleController:
将RuleNacosPublisher和RuleNacosProvider注入到AuthorityRuleController中

// 加入以下代码:
@Autowired
private RuleNacosProvider ruleProvider;
@Autowired
private RuleNacosPublisher rulePublisher;
1
2
3
4
5
修改读取逻辑:

// 修改位置如下:
List<AuthorityRuleEntity> rules = sentinelApiClient.fetchAuthorityRulesOfMachine(app, ip, port);

// 将上面代码修改为以下代码:
 String ruleStr = ruleProvider.getRules(RuleNacosConstants.AUTHORITY_DATA_ID, app);
List<AuthorityRuleEntity> rules = new ArrayList<>();
if (ruleStr != null) {
     rules = JSON.parseArray(ruleStr, AuthorityRuleEntity.class);
     if (rules != null && !rules.isEmpty()) {
         for (AuthorityRuleEntity entity : rules) {
              entity.setApp(app);
         }
    }
}
  
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
修改推送逻辑:

// 1、修改位置如下:
private boolean publishRules(String app, String ip, Integer port) {
    List<AuthorityRuleEntity> rules = repository.findAllByMachine(MachineInfo.of(app, ip, port));
    return sentinelApiClient.setAuthorityRuleOfMachine(app, ip, port, rules);
}

// 将上面代码修改为以下代码:
private void publishRules(String app) {
   try {
       List<AuthorityRuleEntity> rules = repository.findAllByApp(app);
       String ruleStr = JSON.toJSONString(rules);
       rulePublisher.publish(RuleNacosConstants.AUTHORITY_DATA_ID, app, ruleStr);
   } catch (Exception e) {
       e.printStackTrace();
   }
}
=======================================================================================

// 2、修改位置如下:有两处
if (!publishRules(entity.getApp(), entity.getIp(), entity.getPort())) {
    logger.info("Publish authority rules failed after rule update");
}

// 将上面代码修改为以下代码:
publishRules(entity.getApp());
=======================================================================================

// 3、修改位置如下:
if (!publishRules(oldEntity.getApp(), oldEntity.getIp(), oldEntity.getPort())) {
   logger.error("Publish authority rules failed after rule delete");
}

// 将上面代码修改为以下代码:
publishRules(oldEntity.getApp());
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
5、网关控制台规则配置
配置网关控制台规则,在启动网关时需要加上参数:-Dcsp.sentinel.app.type=1

5.1、API管理
修改GatewayApiController:
将RuleNacosPublisher和RuleNacosProvider注入到GatewayApiController中

// 加入以下代码:
@Autowired
private RuleNacosProvider ruleProvider;
@Autowired
private RuleNacosPublisher rulePublisher;
1
2
3
4
5
修改读取逻辑:

// 修改位置如下:
List<ApiDefinitionEntity> apis = sentinelApiClient.fetchApis(app, ip, port).get();

// 将上面代码修改为以下代码:
String ruleStr = ruleProvider.getRules(RuleNacosConstants.GATEWAY_API_DATA_ID, app);
List<ApiDefinitionEntity> apis = new ArrayList<>();
if (ruleStr != null) {
    apis = JSON.parseArray(ruleStr, ApiDefinitionEntity.class);
    if (apis != null && !apis.isEmpty()) {
        for (ApiDefinitionEntity entity : apis) {
             entity.setApp(app);
        }
    }
}
  
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
修改推送逻辑:

// 1、修改位置如下:
private boolean publishApis(String app, String ip, Integer port) {
   List<ApiDefinitionEntity> apis = repository.findAllByMachine(MachineInfo.of(app, ip, port));
   return sentinelApiClient.modifyApis(app, ip, port, apis);
}

// 将上面代码修改为以下代码:
private void publishApi(String app) {
  try {
         List<ApiDefinitionEntity> apis= repository.findAllByApp(app);
         String ruleStr = JSON.toJSONString(apis);
         rulePublisher.publish(RuleNacosConstants.GATEWAY_API_DATA_ID, app, ruleStr);
     } catch (Exception e) {
         e.printStackTrace();
   }
}
=======================================================================================

// 2、修改位置如下
if (!publishApis(app, ip, port)) {
     logger.warn("publish gateway apis fail after add");
}

// 将上面代码修改为以下代码:
publishApi(entity.getApp());
=======================================================================================

// 3、修改位置如下
if (!publishApis(app, entity.getIp(), entity.getPort())) {
    logger.warn("publish gateway apis fail after update");
}

// 将上面代码修改为以下代码:
publishApi(entity.getApp());
=======================================================================================

// 4、修改位置如下:
if (!publishApis(oldEntity.getApp(), oldEntity.getIp(), oldEntity.getPort())) {
    logger.warn("publish gateway apis fail after delete");
}

// 将上面代码修改为以下代码:
publishApi(oldEntity.getApp());
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
5.2、流程规则
修改GatewayFlowRuleController:
将RuleNacosPublisher和RuleNacosProvider注入到GatewayFlowRuleController中

// 加入以下代码:
@Autowired
private RuleNacosProvider ruleProvider;
@Autowired
private RuleNacosPublisher rulePublisher;
1
2
3
4
5
修改读取逻辑:

// 修改位置如下:
List<GatewayFlowRuleEntity> rules = sentinelApiClient.fetchGatewayFlowRules(app, ip, port).get();

// 将上面代码修改为以下代码:
String ruleStr = ruleProvider.getRules(RuleNacosConstants.GATEWAY_FLOW_DATA_ID, app);
List<GatewayFlowRuleEntity> rules = new ArrayList<>();
if (ruleStr != null) {
    rules = JSON.parseArray(ruleStr, GatewayFlowRuleEntity.class);
    if (rules != null && !rules.isEmpty()) {
       for (GatewayFlowRuleEntity entity : rules) {
           entity.setApp(app);
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
修改推送逻辑:

// 1、修改位置如下:
private boolean publishRules(String app, String ip, Integer port) {
   List<GatewayFlowRuleEntity> rules = repository.findAllByMachine(MachineInfo.of(app, ip, port));
   return sentinelApiClient.modifyGatewayFlowRules(app, ip, port, rules);
}

// 将上面代码修改为以下代码:
private void publishRules(String app) {
  try {
      List<GatewayFlowRuleEntity> rules = repository.findAllByApp(app);
      String ruleStr = JSON.toJSONString(rules);
      rulePublisher.publish(RuleNacosConstants.GATEWAY_FLOW_DATA_ID, app, ruleStr);
   } catch (Exception e) {
      e.printStackTrace();
   }
}
=======================================================================================

// 2、修改位置如下
if (!publishRules(app, ip, port)) {
    logger.warn("publish gateway flow rules fail after add");
}

// 将上面代码修改为以下代码:
publishRules(entity.getApp());
=======================================================================================

// 3、修改位置如下
if (!publishRules(app, entity.getIp(), entity.getPort())) {
    logger.warn("publish gateway flow rules fail after update");
}


// 将上面代码修改为以下代码:
publishRules(entity.getApp());
=======================================================================================


// 4、修改位置如下:
if (!publishRules(oldEntity.getApp(), oldEntity.getIp(), oldEntity.getPort())) {
    logger.warn("publish gateway flow rules fail after delete");
}

// 将上面代码修改为以下代码:
publishRules(oldEntity.getApp());
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
5.3、降级规则
使用的是控制台规则配置中的降级规则接口,无需在做操作,参考《4.2、降级规则》。

5.4、系统规则
使用的是控制台规则配置中的降级规则接口,无需在做操作,参考《4.4、系统规则》。

5、打包部署
进入到sentinel-dashboard所在目的,通过mvn clean install package -DskipTests=true进行打包。

部署jar参考:
《Linux搭建Sentinel 控制台环境》
《Docker搭建Sentinel 控制台环境》

6、源码包下载
对于上述修改的代码,源码下载地址:https://download.csdn.net/download/zhuocailing3390/83337923,将下载的Controller和Nacos配置代码直接拷贝到源码中即可使用。
 

  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值