当前位置: 首页 > news >正文

Sentinel不使用控制台基于注解限流,热点参数限流

目录

一、maven依赖

二、控制台

三、基于注解限流

四、热点参数限流

五、使用JMeter验证


一、maven依赖

需要注意,使用的版本需要和你的SpringBoot版本匹配!!

Spring-Cloud直接添加如下依赖即可,baba已经帮你指定好版本了。

当然可以点进去spring-cloud-starter-alibaba-sentinel搜索sentinel-core对应的版本

<dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>

如果是SpringBoot的话,需要手动导入包。

我这里使用的是SpringBoot Version 2.2.5.RELEASE,对应的版本是1.7.1 (你要根据自己SpringBoot版本查询对应版本,如果版本不对应的话根本用不了)

<sentinel.version>1.7.1</sentinel.version><!--sentinel核心包-->
<dependency><groupId>com.alibaba.csp</groupId><artifactId>sentinel-core</artifactId>
<version>${sentinel.version}</version>
</dependency>
<!--sentinel注解支持模块-->
<dependency><groupId>com.alibaba.csp</groupId><artifactId>sentinel-annotation-aspectj</artifactId><version>${sentinel.version}</version>
</dependency>
<!--sentinel与控制台交互-->
<dependency><groupId>com.alibaba.csp</groupId><artifactId>sentinel-transport-simple-http</artifactId>
<version>${sentinel.version}</version>
</dependency>

二、控制台

这里也介绍下控制台的使用方法,但我的项目是没运维给我安装这个东西,就没法用它。。

下载地址:Release v1.7.1 · alibaba/Sentinel · GitHub

使用命令启动jar包

java -jar sentinel-dashboard-17.1.jar

前端访问:localhost:8080, 账号密码 默认都是 sentinel

但是但是!!!!!!!界面是空的!!!!!!!!!

启动命令需要指定你的项目的端口,以及名称才行:

java -Dserver.port=8080 -Dcsp.sentinel.dashboard.server=localhost:8080 -Dcsp.sentinel.api.port=8998 -Dproject.name=sentinel-dashboard -jar sentinel-dashboard-1.7.1.jar

以下是各个命令的介绍:

-Dserver.port=9000  运行端口

-Dcsp.sentinel.dashboard.server=localhost:9000  浏览器访问地址

-Dproject.name=sentinel-dashboard     项目名称

-Dcsp.sentinel.api.port=8092  sentinel客户端端口

-Dsentinel.dashboard.auth.username=username,设置用户名

-Dsentinel.dashboard.auth.password=password,设置访问密码

三、基于注解限流

我的项目没法使用控制台进行动态修改规则,只能在数据库表里面写好规则,系统启动的时候进行读取(当然可以动态修改规则无须重启)

1、maven引入依赖:

<!--sentinel核心包-->
<dependency><groupId>com.alibaba.csp</groupId><artifactId>sentinel-core</artifactId><version>${sentinel.version}</version>
</dependency>
<!--sentinel注解支持模块-->
<dependency><groupId>com.alibaba.csp</groupId><artifactId>sentinel-annotation-aspectj</artifactId><version>${sentinel.version}</version>
</dependency>

2、创建SentinelConfig类,启用注解

import com.alibaba.csp.sentinel.annotation.aspectj.SentinelResourceAspect;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;@Slf4j
@Configuration
public class SentinelConfig {@Beanpublic SentinelResourceAspect sentinelResourceAspect() {return new SentinelResourceAspect();}
}

3、创建表 system_sentinel_config 规则配置表

DROP TABLE IF EXISTS `system_sentinel_config`;
CREATE TABLE `system_sentinel_config` (`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '自增id',`module_name` varchar(100) NOT NULL COMMENT '资源名称',`resource` varchar(100) NOT NULL COMMENT '资源名称(唯一), 对应{@link SentinelResName}',`desc` varchar(100) NOT NULL COMMENT '资源描述',`grade` tinyint(1) NOT NULL DEFAULT 1 COMMENT '阈值类型, 0: 线程数, 1: QPS',`count` bigint(20) NOT NULL COMMENT '如果grade为线程数, 表示每秒最高线程数。如果grade为QPS, 表示每秒最高访问量。',`strategy` tinyint(1) NOT NULL DEFAULT 0 COMMENT '流控模式。0:直接, 1: 关联, 2:链路',`control_behavior` tinyint(1) NOT NULL DEFAULT 0 COMMENT '流控效果。0:默认(直接拒绝), 1: Warm Up(冷启动), 2:匀速排队 3: 冷启动+匀速排队',`max_queueing_time_ms` int DEFAULT NULL COMMENT '流控效果为匀速排队时,队列的排队时间(单位:毫秒),需要注意这里类型是int, 并且单位是毫秒',`warm_up_period_sec` int DEFAULT NULL COMMENT '冷启动到达最大值的时间(单位:秒)',`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP  COMMENT '创建时间',`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP  COMMENT '更新时间',`create_user` VARCHAR(50) DEFAULT NULL COMMENT '创建人' ,`update_user` VARCHAR(50) DEFAULT NULL COMMENT '更新人' ,PRIMARY KEY (`id`),UNIQUE KEY resource_key(`resource`)
) ENGINE=InnoDB AUTO_INCREMENT = 1 DEFAULT CHARSET=utf8 COMMENT='sentinel限流配置表';-- 标准对接--档案类
INSERT INTO `system_sentinel_config` (`module_name`, `resource`, `desc`, `grade`, `count`, `strategy`, `control_behavior`, `max_queueing_time_ms`, `warm_up_period_sec`) VALUES
('标准对接--档案类', 'standard:structure:add',     '新增/编辑建筑类节点(片区/楼栋/单元/住家)', 1, 20, 0, 0, null, null),
('标准对接--档案类', 'standard:structure:delete',  '删除建筑类节点(片区/楼栋/单元/住家)', 1, 20, 0, 0, null, null),
('标准对接--档案类', 'standard:file:upload',       '上传图片', 1, 20, 0, 0, null, null),
('标准对接--档案类', 'standard:check:face',        '人脸质量校验', 1, 20, 0, 0, null, null),
('标准对接--档案类', 'standard:household:add',     '新增/编辑住户(业主,租户)', 1, 20, 0, 0, null, null),
('标准对接--档案类', 'standard:household:delete',  '删除住户', 1, 20, 0, 0, null, null),
('标准对接--档案类', 'standard:visitor:add',       '新增/编辑访客', 1, 20, 0, 0, null, null),
('标准对接--档案类', 'standard:visitor:delete',    '删除访客', 1, 20, 0, 0, null, null);-- 标准对接--其它
INSERT INTO `system_sentinel_config` (`module_name`, `resource`, `desc`, `grade`, `count`, `strategy`, `control_behavior`, `max_queueing_time_ms`, `warm_up_period_sec`) VALUES
('标准对接--其它', 'standard:remote:open',         '第三方远程开门', 1, 20, 0, 0, null, null),
('标准对接--其它', 'standard:smart:lock:control',  '第三方锁门禁权限冻结/解冻', 1, 20, 0, 0, null, null),
('标准对接--其它', 'standard:smart:lock:set:temporary:pwd',    '第三方锁临时密码下发', 1, 20, 0, 0, null, null);

4、对应的SystemSentinelConfigDO对象

import com.alibaba.csp.sentinel.slots.block.RuleConstant;
import com.whysu.scd.base.common.constant.sentinel.SentinelResName;
import lombok.Data;
import lombok.EqualsAndHashCode;/*** sentinel限流配置表(SystemSentinelConfig)实体类*/
@Data
public class SystemSentinelConfigDO {/*** 主键id*/private Long id;/*** 资源名称*/private String moduleName;/*** 资源名称(唯一), 对应{@link SentinelResName}*/private String resource;/*** 资源描述*/private String desc;/*** 阈值类型* 0: 线程数 {@link RuleConstant#FLOW_GRADE_THREAD},* 1: QPS {@link RuleConstant#FLOW_GRADE_QPS}*/private Integer grade;/*** 如果grade为线程数, 表示每秒最高线程数。* 如果grade为QPS, 表示每秒最高访问量。*/private Long count;/*** 流控模式。* 0:直接{@link RuleConstant#STRATEGY_DIRECT},* 1:关联{@link RuleConstant#STRATEGY_RELATE},* 2:链路{@link RuleConstant#STRATEGY_CHAIN},*/private Integer strategy;/*** 流控效果。* 0:默认(直接拒绝){@link RuleConstant#CONTROL_BEHAVIOR_DEFAULT},* 1: Warm Up(冷启动){@link RuleConstant#CONTROL_BEHAVIOR_WARM_UP},* 2:匀速排队{@link RuleConstant#CONTROL_BEHAVIOR_RATE_LIMITER},* 3: 冷启动+匀速排队{@link RuleConstant#CONTROL_BEHAVIOR_WARM_UP_RATE_LIMITER},*/private Integer controlBehavior;/*** 流控效果为匀速排队时,队列的排队时间(单位:毫秒), 需要注意这里类型是int, 并且单位是毫秒*/private Integer maxQueueingTimeMs;/*** 冷启动到达最大值的时间(单位:秒)*/private Integer warmUpPeriodSec;/*** 创建时间*/private Date createTime;/*** 更新时间*/private Date updateTime;/*** 创建人(手机号码)*/private String createUser;/*** 更新人(手机号码)*/private String updateUser;}

5、系统启动的时候,进行加载。这里的主要方法是。

FlowRuleManager.loadRules(rules);

   public void initFlowRule() {// 查询配置List<SystemSentinelConfigDO> list = systemSentinelConfigService.selectList();if (CollectionUtils.isEmpty(list)) {log.error("sentinel限流配置为空!!!!");return;}List<FlowRule> rules = new ArrayList<>();for (SystemSentinelConfigDO configDO : list) {rules.add(SentinelUtil.getFlowRule(configDO));}// 加载FlowRuleManager.loadRules(rules);}

SentinelUtil工具类:

public class SentinelUtil {public static void update(List<SystemSentinelConfigDO> list) {List<FlowRule> rules = new ArrayList<>();for (SystemSentinelConfigDO configDO : list) {rules.add(SentinelUtil.getFlowRule(configDO));}// 加载FlowRuleManager.loadRules(rules);}public static List<FlowRule> getFlowRuleList() {return FlowRuleManager.getRules();}public static FlowRule getFlowRule(SystemSentinelConfigDO configDO) {// 限流规则FlowRule rateRule = new FlowRule();// 设置资源名,即流量控制规则的作用对象rateRule.setResource(configDO.getResource());// 设置限流阈值rateRule.setCount(configDO.getCount());// 限流阈值类型rateRule.setGrade(configDO.getGrade());// 流控模式rateRule.setStrategy(configDO.getStrategy());// 流控效果rateRule.setControlBehavior(configDO.getControlBehavior());// 流控效果为匀速排队时,队列的排队时间(单位:毫秒), 需要注意这里类型是int, 并且单位是毫秒if (configDO.getMaxQueueingTimeMs() != null && configDO.getMaxQueueingTimeMs() != 0) {rateRule.setMaxQueueingTimeMs(configDO.getMaxQueueingTimeMs());}// 冷启动到达最大值的时间(单位:秒)if (configDO.getWarmUpPeriodSec() != null && configDO.getWarmUpPeriodSec() != 0) {rateRule.setWarmUpPeriodSec(configDO.getWarmUpPeriodSec());}return rateRule;}
}

6、通过update方法动态更新:

    @Transactional@Overridepublic void update(SystemSentinelConfigDO configDO) {ValidateUtils.checkBlank(configDO.getResource());SystemSentinelConfigDO dbDO = systemSentinelConfigDao.selectByResource(configDO.getResource());if (dbDO != null) {configDO.setId(dbDO.getId());systemSentinelConfigDao.update(configDO);}// 全部更新SentinelUtil.update(systemSentinelConfigDao.selectList(null));}

7、定义限流通用返回, 这里的名称是standardLimit

@Slf4j
public class StandardBlockHandler {/*** 标准对接, 触发限流时返回*/public static ThirdResponseDTO<Object> standardLimit(BlockException ex) {return ResponseThirdHelper.failResponse(ResultCodeThirdEnum.THIRD_STANDARD_RATE_LIMIT);}
}

8、Controller接口通过@SentinelResource指定资源名称,以及触发限流时的返回消息

@RestController
@RequestMapping("/third/archives")
@Slf4j
@RequiredArgsConstructor
public class StandardThirdArchivesController {private final StandardArchivesService standardArchivesService;/*** 新增/编辑建筑类节点(片区/楼栋/单元/住家)*/@PostMapping("/structure/addEdit")@SentinelResource(value = "standard:structure:add", blockHandler = "standardLimit", blockHandlerClass = {StandardBlockHandler.class})public ThirdResponseDTO<StandardStructureAddRes> addEditStructure(@RequestBody @Validated ThirdRequestDTO<StandardStructureAddReq> req) {return ResponseThirdHelper.successResponse(standardArchivesService.addEditStructure(req.getData(), req.getNeighNo()));}
}

四、热点参数限流

很遗憾,前面说的第三点【基于注解限流】的方式,没法具体到热点参数,因为我的项目的“入参”是有些复杂的对象,但热点参数只支持【基本类型】的入参。。。

1、第三点说的【基于注解限流】直接作废,重新来过

2、maven引入依赖

<!--sentinel核心包-->
<dependency><groupId>com.alibaba.csp</groupId><artifactId>sentinel-core</artifactId><version>${sentinel.version}</version>
</dependency>
<!--sentinel热点参数限流-->
<dependency><groupId>com.alibaba.csp</groupId><artifactId>sentinel-parameter-flow-control</artifactId><version>${sentinel.version}</version>
</dependency>

 3、我这里的入参 ThirdRequestDTO 里面有个字段 clientId,是我需要限流的热点参数:

@Data
public class ThirdRequestDTO<T> {/*** 授权分配的clientId*/private String clientId;}

4、因为热点参数只支持QPS,于是可以配置的选项就很少了:

以下是配置表:standard_sentinel_config (需要注意sql查询的时候,desc, count要加单引号。你也可以给这2个参数重命名)

这里表示在duration_in_sec(秒)内,访问resource(资源)超过count (次数) 就进行限流,

如果是热点参数的话,则表示:

在duration_in_sec(秒)内,访问resource(资源)超过client_id_count(次数) 就进行限流,

DROP TABLE IF EXISTS `standard_sentinel_config`;
CREATE TABLE `standard_sentinel_config` (`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '自增id',`resource` varchar(100) NOT NULL COMMENT '资源名称{@link SentinelResName}',`desc` varchar(100) NOT NULL COMMENT '资源描述',`duration_in_sec` int NOT NULL COMMENT '限流的单位时间(秒)',`count` int NOT NULL COMMENT '限流参数',`client_id_count` int NOT NULL COMMENT '针对clientId限流参数',`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP  COMMENT '创建时间',`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP  COMMENT '更新时间',create_user VARCHAR(50) DEFAULT NULL COMMENT '创建人' ,update_user VARCHAR(50) DEFAULT NULL COMMENT '更新人' ,PRIMARY KEY (`id`),UNIQUE KEY `resource_key`(`resource`)
) ENGINE=InnoDB AUTO_INCREMENT = 1 DEFAULT CHARSET=utf8 COMMENT='标准对接限流';-- 公共字典
INSERT INTO `standard_sentinel_config` (`resource`, `desc`, `duration_in_sec`, `count`, `client_id_count`) VALUES
('standard:archives',        '标准对接--档案类', 1, 60, 10),
('standard:query:archives',  '标准对接--查询档案类', 1, 60, 10),
('standard:other',           '标准对接--其它', 1, 60, 10);

5、StandardSentinelConfigDO对象

@Data
public class StandardSentinelConfigDO {/*** 主键id*/private Long id;/*** 资源名称{@link SentinelResName}*/private String resource;/*** 资源描述*/private String desc;/*** 限流的单位时间(秒)*/private Integer durationInSec;/*** 限流参数*/private Integer count;/*** 针对clientId限流参数*/private Integer clientIdCount;/*** 创建时间*/private Date createTime;/*** 更新时间*/private Date updateTime;/*** 创建人(手机号码)*/private String createUser;/*** 更新人(手机号码)*/private String updateUser;}

6、系统启动的时候,加载规则:(主要是通过ParamFlowRuleManager.loadRules)

import com.alibaba.csp.sentinel.slots.block.RuleConstant;
import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowItem;
import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowRule;
import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowRuleManager;
import com.whysu.scd.base.common.enums.ResultCodeEnum;
import com.whysu.scd.base.common.exception.BusinessException;
import com.whysu.scd.base.common.util.ValidateUtils;
import com.whysu.scd.module.standard.dao.StandardPlatformInfoDao;
import com.whysu.scd.module.standard.dao.StandardSentinelConfigDao;
import com.whysu.scd.module.standard.entity.StandardSentinelConfigDO;
import com.whysu.scd.module.standard.service.StandardSentinelService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.stereotype.Service;import java.util.ArrayList;
import java.util.List;/*** 标准对接--限流*/
@Slf4j
@Service
@RequiredArgsConstructor
public class StandardSentinelServiceImpl implements StandardSentinelService {private final StandardPlatformInfoDao standardPlatformInfoDao;private final StandardSentinelConfigDao standardSentinelConfigDao;@Overridepublic void init() {// 查询sentinel配置List<StandardSentinelConfigDO> configList = standardSentinelConfigDao.selectList(null);if (CollectionUtils.isEmpty(configList)) {log.error("标准对接sentinel配置为空!");return;}// 查询所有的clientIdList<String> clientIdList = standardPlatformInfoDao.selectClientIdList2();List<ParamFlowRule> ruleList = new ArrayList<>();for (StandardSentinelConfigDO configDO : configList) {// 资源名ParamFlowRule rule = new ParamFlowRule(configDO.getResource());// 指定当前 rule 对应的热点参数索引rule.setParamIdx(0);// 限流的维度,该策略针对 QPS 限流rule.setGrade(RuleConstant.FLOW_GRADE_QPS);// 限流的单位时间(秒)rule.setDurationInSec(configDO.getDurationInSec());// 未使用指定热点参数时,该资源限流大小为50rule.setCount(configDO.getCount());// 热点参数限流if (CollectionUtils.isNotEmpty(clientIdList)) {List<ParamFlowItem> itemList = new ArrayList<>();for (String clientId : clientIdList) {// item1 设置了clientId的限流,单位时间(DurationInSec)内只能访问10次ParamFlowItem item1 = new ParamFlowItem().setObject(clientId) // 热点参数 value.setClassType(String.class.getName()) // 热点参数数据类型.setCount(configDO.getClientIdCount()); // 针对该value的限流值itemList.add(item1);}rule.setParamFlowItemList(itemList);}ruleList.add(rule);}// 加载ParamFlowRuleManager.loadRules(ruleList);}@Overridepublic void update(StandardSentinelConfigDO configDO) {ValidateUtils.checkBlank(configDO.getResource());// 查询StandardSentinelConfigDO dbDO = standardSentinelConfigDao.selectByResource(configDO.getResource());if (dbDO == null) {throw new BusinessException(ResultCodeEnum.PUTIAN_PARAM_ERROR);}// 更新configDO.setId(dbDO.getId());standardSentinelConfigDao.update(configDO);// 重新初始化init();}@Overridepublic List<ParamFlowRule> getRuleList() {return ParamFlowRuleManager.getRules();}@Overridepublic List<ParamFlowRule> getRuleOfResource(String resource) {return ParamFlowRuleManager.getRulesOfResource(resource);}
}

7、定义了一个注解:StandardSignature 

@Documented
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface StandardSignature {/*** 签名算法版本 2.0*/String version() default ThirdConstant.SignatureVersion.TWO;/*** 接口权限*/StandardOpenTypeEnum[] openType();/*** data参数是否不能为空(默认为true表示不能为空)*/boolean dataNotNull() default true;
}

8、针对该注解切面:

这里的核心代码是:

Entry entry = null;
try {// 调用限流entry = SphU.entry(resourceName, EntryType.IN, 1, clientId);// 业务代码...return proceedingJoinPoint.proceed();
} catch (BlockException e) {// 接口限流return ResponseThirdHelper.failResponse(ResultCodeThirdEnum.THIRD_STANDARD_RATE_LIMIT);
} finally {if (entry != null) {entry.exit(1, clientId);}
}

有2个入参:resourceName和clientId

其中clientId是入参ThirdRequestDTO里面的。

resourceName是通过注解指定的StandardSignature里的opernType参数,查询出来的:

/*** key: 标准对接接口,value: sentinel限流资源名称{@link SentinelResName}*/public static final HashMap<StandardOpenTypeEnum, String> SENTINEL_RESOURCE = new HashMap<StandardOpenTypeEnum, String>() {{// 标准对接-档案类put(StandardOpenTypeEnum.ARCHIVES, SentinelResName.STANDARD_ARCHIVES);put(StandardOpenTypeEnum.ARCHIVES_GET_CALL_NO_FIRST4, SentinelResName.STANDARD_ARCHIVES);put(StandardOpenTypeEnum.ARCHIVES_STRUCT_ADD, SentinelResName.STANDARD_ARCHIVES);put(StandardOpenTypeEnum.ARCHIVES_STRUCT_DELETE, SentinelResName.STANDARD_ARCHIVES);put(StandardOpenTypeEnum.ARCHIVES_UPLOAD_FACE_FILE, SentinelResName.STANDARD_ARCHIVES);put(StandardOpenTypeEnum.ARCHIVES_CHECK_FACE, SentinelResName.STANDARD_ARCHIVES);put(StandardOpenTypeEnum.ARCHIVES_HOUSEHOLD_ADD, SentinelResName.STANDARD_ARCHIVES);put(StandardOpenTypeEnum.ARCHIVES_HOUSEHOLD_DELETE, SentinelResName.STANDARD_ARCHIVES);put(StandardOpenTypeEnum.ARCHIVES_VISITOR_ADD, SentinelResName.STANDARD_ARCHIVES);put(StandardOpenTypeEnum.ARCHIVES_VISITOR_DELETE, SentinelResName.STANDARD_ARCHIVES);// 标准对接-查询档案类put(StandardOpenTypeEnum.QUERY_ARCHIVES, SentinelResName.STANDARD_QUERY_ARCHIVES);put(StandardOpenTypeEnum.QUERY_ARCHIVES_NEIGH, SentinelResName.STANDARD_QUERY_ARCHIVES);put(StandardOpenTypeEnum.QUERY_ARCHIVES_TREE_WHOLE, SentinelResName.STANDARD_QUERY_ARCHIVES);put(StandardOpenTypeEnum.QUERY_ARCHIVES_TREE_PAGE, SentinelResName.STANDARD_QUERY_ARCHIVES);put(StandardOpenTypeEnum.QUERY_ARCHIVES_AREA, SentinelResName.STANDARD_QUERY_ARCHIVES);put(StandardOpenTypeEnum.QUERY_ARCHIVES_BUILDING, SentinelResName.STANDARD_QUERY_ARCHIVES);put(StandardOpenTypeEnum.QUERY_ARCHIVES_UNIT, SentinelResName.STANDARD_QUERY_ARCHIVES);put(StandardOpenTypeEnum.QUERY_ARCHIVES_HOUSE, SentinelResName.STANDARD_QUERY_ARCHIVES);put(StandardOpenTypeEnum.QUERY_ARCHIVES_DEVICE, SentinelResName.STANDARD_QUERY_ARCHIVES);put(StandardOpenTypeEnum.QUERY_ARCHIVES_DEVICE_STATUS, SentinelResName.STANDARD_QUERY_ARCHIVES);put(StandardOpenTypeEnum.QUERY_ARCHIVES_PARKING_INFO, SentinelResName.STANDARD_QUERY_ARCHIVES);put(StandardOpenTypeEnum.QUERY_ARCHIVES_PARKING_ROAD_INFO, SentinelResName.STANDARD_QUERY_ARCHIVES);put(StandardOpenTypeEnum.QUERY_ARCHIVES_PARKING_CAR_INFO, SentinelResName.STANDARD_QUERY_ARCHIVES);put(StandardOpenTypeEnum.QUERY_ARCHIVES_PERSON_INFO, SentinelResName.STANDARD_QUERY_ARCHIVES);put(StandardOpenTypeEnum.QUERY_ARCHIVES_DEVICE_INFO, SentinelResName.STANDARD_QUERY_ARCHIVES);// 标准对接-其它put(StandardOpenTypeEnum.OTHER, SentinelResName.STANDARD_OTHER);put(StandardOpenTypeEnum.OTHER_REMOTE_OPEN, SentinelResName.STANDARD_OTHER);put(StandardOpenTypeEnum.OTHER_BLOCK_CONTROL, SentinelResName.STANDARD_OTHER);put(StandardOpenTypeEnum.OTHER_SEND_TEMPORARY_PWD, SentinelResName.STANDARD_OTHER);}};

其中SentinelResName (对应standard_sentinel_config表的resources字段)

public class SentinelResName {/*** 标准对接--档案类*/public static final String STANDARD_ARCHIVES = "standard:archives";/*** 标准对接--查询档案类*/public static final String STANDARD_QUERY_ARCHIVES = "standard:query:archives";/*** 标准对接--其它*/public static final String STANDARD_OTHER = "standard:other";}

OpenType是用的枚举:

import com.whysu.scd.module.standard.dto.StandardTreeNode;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.experimental.Accessors;import java.util.ArrayList;
import java.util.List;/*** 允许第三方调用的接口类型集合*/
@Getter
@Accessors(fluent = true)
@AllArgsConstructor
public enum StandardOpenTypeEnum {// 全部接口ALL(1, 0, "全部接口"),// 二级ARCHIVES(2, ALL.type, "档案类"),QUERY_ARCHIVES(3, ALL.type, "查询档案类"),QUERY_RECORD(4, ALL.type, "查询记录"),OTHER(5, ALL.type, "其它接口"),// 档案类,取值范围:100到199ARCHIVES_GET_CALL_NO_FIRST4(100, ARCHIVES.type, "获取呼叫号码前四位"),ARCHIVES_STRUCT_ADD(101, ARCHIVES.type, "新增/编辑片区/楼栋/单元/住家"),ARCHIVES_STRUCT_DELETE(102, ARCHIVES.type, "删除片区/楼栋/单元/住家"),ARCHIVES_UPLOAD_FACE_FILE(103, ARCHIVES.type, "上传人脸"),ARCHIVES_CHECK_FACE(104, ARCHIVES.type, "人脸质量校验"),ARCHIVES_HOUSEHOLD_ADD(105, ARCHIVES.type, "新增/编辑住户(会进行人脸质量校验)"),ARCHIVES_HOUSEHOLD_DELETE(106, ARCHIVES.type, "删除住户"),ARCHIVES_VISITOR_ADD(107, ARCHIVES.type, "新增/编辑访客(会进行人脸质量校验)"),ARCHIVES_VISITOR_DELETE(108, ARCHIVES.type, "删除访客"),// 查询档案类,取值范围:200到299QUERY_ARCHIVES_NEIGH(200, QUERY_ARCHIVES.type, "查询小区档案"),QUERY_ARCHIVES_TREE_WHOLE(201, QUERY_ARCHIVES.type, "查询树结构(全部)"),QUERY_ARCHIVES_TREE_PAGE(202, QUERY_ARCHIVES.type, "查询树结构(分页)(小区片区楼栋单元住家地点)"),QUERY_ARCHIVES_AREA(203, QUERY_ARCHIVES.type, "查询片区档案(分页)"),QUERY_ARCHIVES_BUILDING(204, QUERY_ARCHIVES.type, "查询楼栋档案(分页)"),QUERY_ARCHIVES_UNIT(205, QUERY_ARCHIVES.type, "查询单元档案(分页)"),QUERY_ARCHIVES_HOUSE(206, QUERY_ARCHIVES.type, "查询房屋档案(分页)"),QUERY_ARCHIVES_DEVICE(207, QUERY_ARCHIVES.type, "查询设备档案(分页)"),QUERY_ARCHIVES_DEVICE_STATUS(208, QUERY_ARCHIVES.type, "查询设备在离线状态(分页)"),QUERY_ARCHIVES_PARKING_INFO(209, QUERY_ARCHIVES.type, "查询车场档案"),QUERY_ARCHIVES_PARKING_ROAD_INFO(210, QUERY_ARCHIVES.type, "查询车道档案(分页)"),QUERY_ARCHIVES_PARKING_CAR_INFO(211, QUERY_ARCHIVES.type, "查询车辆档案(分页)"),QUERY_ARCHIVES_PERSON_INFO(212, QUERY_ARCHIVES.type, "查询人员档案(住户/访客/工作人员)(分页)"),QUERY_ARCHIVES_DEVICE_INFO(213, QUERY_ARCHIVES.type, "查询设备详情"),// 查询记录类,取值范围:300到399QUERY_RECORD_ACCESS_PAGE(300, QUERY_RECORD.type, "查询门禁记录(分页,不能跨月查询)"),QUERY_RECORD_TALK_PAGE(301, QUERY_RECORD.type, "查询对讲记录(分页,不能跨月查询)"),QUERY_RECORD_CAR_PAGE(302, QUERY_RECORD.type, "查询车辆出入记录(分页,不能跨月查询)"),// 其它接口,取值范围:400到499OTHER_REMOTE_OPEN(400, OTHER.type, "第三方远程开门"),OTHER_BLOCK_CONTROL(401, OTHER.type, "锁门禁权限冻结/解冻"),OTHER_SEND_TEMPORARY_PWD(402, OTHER.type, "锁临时密码下发"),;/*** 允许第三方调用的接口类型*/private final int type;/*** 父节点接口类型*/private final int parentType;/*** 描述*/private final String desc;}

完整的切面代码如下:

import com.alibaba.csp.sentinel.Entry;
import com.alibaba.csp.sentinel.EntryType;
import com.alibaba.csp.sentinel.SphU;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.whysu.scd.base.common.constant.i18n.third.standard.MessageThirdStandard;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.HashSet;
import java.util.List;
import java.util.Set;/*** 标准对接-数字签名校验切面*/
@Aspect
@Component
@Slf4j
@Configuration
public class StandardSignatureValidateAspect {@Pointcut(value = "@annotation(com.whysu.scd.module.standard.annotation.StandardSignature)")public void signaturePointcut() {}@Around("signaturePointcut()")public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {MethodSignature signature = (MethodSignature) proceedingJoinPoint.getSignature();StandardSignature annotation = signature.getMethod().getAnnotation(StandardSignature.class);Object[] args = proceedingJoinPoint.getArgs();for (Object arg : args) {if (arg instanceof ThirdRequestDTO) {ThirdRequestDTO requestDTO = (ThirdRequestDTO) arg;String clientId = requestDTO.getClientId();// 根据openType获取资源名称Set<String> resourceSet = new HashSet<>();for (StandardOpenTypeEnum openTypeEnum : annotation.openType()) {String resourceName = StandardMap.SENTINEL_RESOURCE.get(openTypeEnum);if (StringUtils.isNotBlank(resourceName)) {resourceSet.add(resourceName);}}// 限流判断for (String resourceName : resourceSet) {Entry entry = null;try {// 调用限流entry = SphU.entry(resourceName, EntryType.IN, 1, clientId);// 业务代码...return proceedingJoinPoint.proceed();} catch (BlockException e) {// 接口限流return ResponseThirdHelper.failResponse(ResultCodeThirdEnum.THIRD_STANDARD_RATE_LIMIT);} finally {if (entry != null) {entry.exit(1, clientId);}}}}}return null;}@AfterReturning(returning = "ret", pointcut = "signaturePointcut()")public void doAfterReturning(Object ret) {// 处理完请求,返回内容log.info("标准对接请求scd, 响应参数为:{}", JacksonUtils.toJson(ret));}}

9、在Controller接口指定注解:

    /*** 新增/编辑建筑类节点(片区/楼栋/单元/住家)*/@StandardSignature(openType = StandardOpenTypeEnum.ARCHIVES_STRUCT_ADD)@PostMapping("/structure/addEdit")public ThirdResponseDTO<StandardStructureAddRes> addEditStructure(@RequestBody @Validated ThirdRequestDTO<StandardStructureAddReq> req) {return ResponseThirdHelper.successResponse(standardArchivesService.addEditStructure(req.getData(), req.getNeighNo()));}

五、使用JMeter验证

首先声明这个工具我不大会用哈哈哈

1、先下载http://jmeter.apache.org/download_jmeter.cgi

2、解压,进入bin目录下执行jmeter.bat即可启动成功。

3、默认语言是英文,进入bin目录修改 jmeter.properties,添加如下配置即可改成中文

language=zh_CN

4、右键添加--线程(用户)-线程组

配置1秒并发100次

5、线程组右键:添加--取样器--HTTP请求

指定请求协议,地址,端口,方式,路径。可以直接在【消息体数据】里面复制json对象。

6、HTTP请求右键:添加--配置元件--HTTP信息头管理器

添加请求头:Content-Type   application/json;charset=utf-8

7、HTTP请求右键:添加--断言--响应断言

我这里也不知道咋写,但是我知道我项目的响应码3011是对应限流。

我这里就设置成,响应文本,包含3011,就表示成功

8、线程组右键:添加--监听器--察看结果树

9、线程组右键:启动

查看结果树,被限流了哈哈哈哈^_^、、、。。。

相关文章:

Sentinel不使用控制台基于注解限流,热点参数限流

目录 一、maven依赖 二、控制台 三、基于注解限流 四、热点参数限流 五、使用JMeter验证 一、maven依赖 需要注意&#xff0c;使用的版本需要和你的SpringBoot版本匹配&#xff01;&#xff01; Spring-Cloud直接添加如下依赖即可&#xff0c;baba已经帮你指定好版本了。…...

HTML做成一个端午节炫酷页面

做成端午节页面之前&#xff0c;先了解一下端午节的由来&#xff1a; 1.起源与历史&#xff1a; 端午节起源于中国&#xff0c;始于春秋战国时期&#xff0c;至今已有2000多年历史。 最初是古代百越地区&#xff08;长江中下游及以南一带&#xff09;崇拜龙图腾的部族举行图…...

解决Ubuntu系统/usr/lib/xorg/Xorg占用显卡内存问题原创

在Ubuntu系统中&#xff0c;/usr/lib/xorg/Xorg进程占用显卡内存的问题可能会影响系统性能&#xff0c;特别是在使用GPU进行计算任务时。以下是一些解决方法&#xff0c;可以帮助你减少或解决这个问题&#xff1a; 1. 更新显卡驱动 首先&#xff0c;确保你使用的是最新版本的…...

【Activiti7系列】基于Spring Security的Activiti7工作流管理系统简介及实现(附源码)(下篇)

作者&#xff1a;后端小肥肠 上篇&#xff1a;【Activiti7系列】基于Spring Security的Activiti7工作流管理系统简介及实现&#xff08;上篇&#xff09;_spring security activiti7-CSDN博客 目录 1.前言 2. 核心代码 2.1. 流程定义模型管理 2.1.1. 新增流程定义模型数据 …...

解密Spring Boot:深入理解条件装配与条件注解

文章目录 一、条件装配概述1.1 条件装配的基本原理1.2 条件装配的作用 二、常用注解2.1 ConditionalOnClass2.2 ConditionalOnBean2.3 ConditionalOnProperty2.4 ConditionalOnExpression2.5 ConditionalOnMissingBean 三、条件装配的实现原理四、实际案例 一、条件装配概述 1…...

【数据结构与算法】使用数组实现栈:原理、步骤与应用

&#x1f493; 博客主页&#xff1a;倔强的石头的CSDN主页 &#x1f4dd;Gitee主页&#xff1a;倔强的石头的gitee主页 ⏩ 文章专栏&#xff1a;《数据结构与算法》 期待您的关注 ​ 目录 一、引言 &#x1f384;栈&#xff08;Stack&#xff09;是什么&#xff1f; &#x1…...

cell的复用机制和自定义cell

cell的复用机制和自定义cell UITableView 在学习cell之前&#xff0c;我们需要先了解UITableView。UITableView继承于UIScrollView&#xff0c;拥有两个两个相关协议 UITableViewDelegate和UITableViewDataSource&#xff0c;前者用于显示单元格&#xff0c;设置行高以及对单…...

Redis 双写一致原理篇

前言 我们都知道,redis一般的作用是顶在mysql前面做一个"带刀侍卫"的角色,可以缓解mysql的服务压力,但是我们如何保证数据库的数据和redis缓存中的数据的双写一致呢,我们这里先说一遍流程,然后以流程为切入点来谈谈redis和mysql的双写一致性是如何保证的吧 流程 首先…...

《软件定义安全》之四:什么是软件定义安全

第4章 什么是软件定义安全 1.软件定义安全的含义 1.1 软件定义安全的提出 虚拟化、云计算、软件定义架构的出现&#xff0c;对安全体系提出了新的挑战。如果要跟上网络演进的步伐和业务快速创新的速度&#xff0c;安全体系应该朝以下方向演变。 &#x1d7ed; 安全机制软件…...

将AIRNet集成到yolov8中,实现端到端训练与推理

AIRNet是一个图像修复网络,支持对图像进行去雾、去雨、去噪声的修复。其基于对比的退化编码器(CBDE),将各种退化类型统一到同一嵌入空间;然后,基于退化引导恢复网络(DGRN)将嵌入空间修复为目标图像。可以将AIRNet的输出与yolov8进行端到端集成,实现部署上的简化。 本博…...

hcache缓存查看工具

1、hcache概述 hcache是基于pcstat的&#xff0c;pcstat可以查看某个文件是否被缓存和根据进程pid来查看都缓存了哪些文件。hcache在其基础上增加了查看整个操作系统Cache和根据使用Cache大小排序的特性。官网:https://github.com/silenceshell/hcache 2、hcache安装 2.1下载…...

Java 数据类型 -- Java 语言的 8 种基本数据类型、字符串与数组

大家好&#xff0c;我是栗筝i&#xff0c;这篇文章是我的 “栗筝i 的 Java 技术栈” 专栏的第 004 篇文章&#xff0c;在 “栗筝i 的 Java 技术栈” 这个专栏中我会持续为大家更新 Java 技术相关全套技术栈内容。专栏的主要目标是已经有一定 Java 开发经验&#xff0c;并希望进…...

kafka-生产者事务-数据传递语义事务介绍事务消息发送(SpringBoot整合Kafka)

文章目录 1、kafka数据传递语义2、kafka生产者事务3、事务消息发送3.1、application.yml配置3.2、创建生产者监听器3.3、创建生产者拦截器3.4、发送消息测试3.5、使用Java代码创建主题分区副本3.6、屏蔽 kafka debug 日志 logback.xml3.7、引入spring-kafka依赖3.8、控制台日志…...

免费!GPT-4o发布,实时语音视频丝滑交互

We’re announcing GPT-4o, our new flagship model that can reason across audio, vision, and text in real time. 5月14日凌晨&#xff0c;OpenAI召开了春季发布会&#xff0c;发布会上公布了新一代旗舰型生成式人工智能大模型【GPT-4o】&#xff0c;并表示该模型对所有免费…...

DevOps的原理及应用详解(四)

本系列文章简介: 在当今快速变化的商业环境中,企业对于软件交付的速度、质量和安全性要求日益提高。传统的软件开发和运维模式已经难以满足这些需求,因此,DevOps(Development和Operations的组合)应运而生,成为了解决这些问题的有效方法。 DevOps是一种强调软件开发人员(…...

关于选择,关于处事

一个人选择应该选择的是勇敢&#xff0c;选择不应该选择的是无奈。放弃&#xff0c;不该放弃的是懦夫&#xff0c;不放弃应该放弃的是睿智。所以&#xff0c;碰到事的时候要先静&#xff0c;先不管什么事&#xff0c;先静下来&#xff0c;先淡定&#xff0c;先从容。在生活里要…...

大话设计模式解读02-策略模式

本篇文章&#xff0c;来解读《大话设计模式》的第2章——策略模式。并通过Qt和C代码实现实例代码的功能。 1 策略模式 策略模式作为一种软件设计模式&#xff0c;指对象有某个行为&#xff0c;但是在不同的场景中&#xff0c;该行为有不同的实现算法。 策略模式的特点&#…...

展会邀请 | 龙智即将亮相2024上海国际嵌入式展,带来安全合规、单一可信数据源、可追溯、高效协同的嵌入式开发解决方案

2024年6月12日至14日&#xff0c;备受全球嵌入式系统产业和社群瞩目的2024上海国际嵌入式展&#xff08;embedded world china 2024&#xff09;即将盛大开幕&#xff0c;龙智将携行业领先的嵌入式开发解决方案亮相 640展位 。 此次参展&#xff0c;龙智将全面展示专为嵌入式行…...

codeforce round951 div2

A guess the maximum 问题&#xff1a; 翻译一下就是求所有相邻元素中max - 1的最小值 代码&#xff1a; #include <iostream> #include <algorithm>using namespace std;const int N 5e4;int a[N]; int n;void solve() {cin >> n;int ans 0x3f3f3f3f;…...

arcgis开发记录

目录 文章目录 [toc]**arcgis JavaScript API安装**1. arcgisAPI下载地址&#xff1a;https://developers.arcgis.com/downloads/2. 4.4版本API&#xff1a;本地配置3. 3.18版本修改方法 **angular2中加载arcgis JS API**** arcgis加载图层 并显示图层上点的信息****使用图层上…...

RPA-UiBot6.0数据整理机器人—杂乱数据秒变报表

前言 友友们是否常常因为杂乱的数据而烦恼?数据分类、排序、筛选这些繁琐的任务是否占据了友友们的大部分时间?这篇博客将为友友们带来一个新的解决方案,让我们共同学习如何运用RPA数据整理机器人,实现杂乱数据的快速整理,为你的工作减负增效! 在这里,友友们将了…...

Application UI

本节包含关于如何用DevExpress控件模拟许多流行的应用程序ui的教程。 Windows 11 UI Windows 11和最新一代微软Office产品启发的UI。 Office Inspired UI Word、Excel、PowerPoint和Visio等微软Office应用程序启发的UI。 如何&#xff1a;手动构建Office风格的UI 本教程演示…...

关于 Redis 中集群

哨兵机制中总结到&#xff0c;它并不能解决存储容量不够的问题&#xff0c;但是集群能。 广义的集群&#xff1a;只要有多个机器&#xff0c;构成了分布式系统&#xff0c;都可以称之为一个“集群”&#xff0c;例如主从结构中的哨兵模式。 狭义的集群&#xff1a;redis 提供的…...

C++必修:探索C++的内存管理

✨✨ 欢迎大家来到贝蒂大讲堂✨✨ &#x1f388;&#x1f388;养成好习惯&#xff0c;先赞后看哦~&#x1f388;&#x1f388; 所属专栏&#xff1a;C学习 贝蒂的主页&#xff1a;Betty’s blog 1. C/C的内存分布 我们首先来看一段代码及其相关问题 int globalVar 1; static…...

python列表---基本语法(浅拷贝,深拷贝等)

文章目录 引言:列表的注意事项1 list中的浅拷贝与深拷贝1.1浅拷贝(Shallow Copy)浅拷贝的方法浅拷贝的效果1.2深拷贝(Deep Copy)深拷贝的方法深拷贝的效果1.3 总结:浅拷贝 vs 深拷贝1.4 为什么浅拷贝顶层元素如果是不可变数据就不能共享,不是传的是引用就相当于传的是地…...

go语言接口之sort.Interface接口

排序操作和字符串格式化一样是很多程序经常使用的操作。尽管一个最短的快排程序只要15 行就可以搞定&#xff0c;但是一个健壮的实现需要更多的代码&#xff0c;并且我们不希望每次我们需要的时候 都重写或者拷贝这些代码。 幸运的是&#xff0c;sort包内置的提供了根据一些排序…...

android:text 总为大写字母的原因

当设置某个 Button 的 text 为英文时&#xff0c;界面上显示的是该英文的大写形式&#xff08;uppercase&#xff09;。例如&#xff1a; <Buttonandroid:id"id/btn"android:layout_width"wrap_content"android:layout_height"wrap_content"…...

CISCN2024 初赛 wp 部分复现(Re)

Misc 1. 火锅链观光打卡 答题即可 Re 1. asm_re 感谢智谱清言&#xff0c;可以读出大致加密算法 这是输入 这是加密部分 这里判断 找到疑似密文的部分&#xff0c;手动改一下端序 #asm_wp def dec(char):return (((char - 0x1E) ^ 0x4D) - 0x14) // 0x50 #return (ord(cha…...

YOLOv10、YOLOv9 和 YOLOv8 在实际视频中的对比

引言 目标检测技术是计算机视觉领域的核心任务之一&#xff0c;YOLO&#xff08;You Only Look Once&#xff09;系列模型凭借其高效的检测速度和准确率成为了业界的宠儿。本文将详细对比YOLOv10、YOLOv9和YOLOv8在实际视频中的表现&#xff0c;探讨它们在性能、速度和实际应用…...

热题系列章节5

169. 多数元素 给定一个大小为 n 的数组&#xff0c;找到其中的多数元素。多数元素是指在数组中出现次数大于 ⌊ n/2 ⌋ 的元素。 你可以假设数组是非空的&#xff0c;并且给定的数组总是存在多数元素。 示例 1: 输入: [3,2,3] 输出: 3 示例 2: 输入: [2,2,1,1,1,2,2] 输出:…...

如今做那个网站致富/今日国内新闻头条15条

#define MAXLINE 4096/**************程序流程*******************1 用socket()函数创建一个socket2 用bind()绑定到一个本地的地址&#xff0c;这样其他的socket可以用connect()连接上去3 用listen()指出愿意接收连接并指定进来的连接的队列限制4 用accept()函数来接收连接***…...

物流网站建设公司哪家好/企业邮箱账号

各语言的注释符一览 作者:greation 出处&#xff1a;http://hi.baidu.com/greation/blog/item/e0a7f4d3da900ddda8ec9a59.html 为便于今后重新理解和利于其他人员理解代码&#xff0c;各语言都有其特定的注释方式&#xff0c;本文将一起看看个语言的注释符的写法&#xff0c;…...

空间网站建设/百度推广登陆后台

ping命令是一种测试两台设备之间网络连通性的命令。在Linux操作系统中&#xff0c;使用ping命令可以测试本机与其他设备之间的网络连通性。 基本语法&#xff1a; ping [options] [destination] 常用选项&#xff1a; -c count&#xff1a;指定ping命令的发送次数&#xff…...

整形网站专题素材/公司网站建设流程

JAVA面向对象基础 1、说一下什么是面向对象 面向对象是一种编程思想&#xff0c;它是相对于面向过程而言的&#xff0c;从执行者变为了指挥者&#xff0c;通过这种思想来将生活中复杂的事情简单化 面向对象就是把一个对象抽象成类&#xff0c;具体来说就是把一个对象的静态特…...

政务公开与网站建设的矛盾/品牌推广的方式

现在的人抄袭成风啊&#xff0c;都不带大脑思考的。 跑马灯的效果&#xff0c;我看到好多文章就想笑。很多文章都有一句&#xff0c;文字不能比TextView长。我了个去&#xff0c;有点脑子的都知道&#xff0c;文字没有TextView长的话还需要跑马吗&#xff1f;全显示出来了还需…...

广西新增疫情最新消息今天封城了/国际站seo优化是什么意思

给中国学生的第三封信——成功、自信、快乐<?xml:namespace prefix o ns "urn:schemas-microsoft-com:office:office" />李开复2004 年5 月此前&#xff0c;我和中国学生的多次交流都是围绕如何达到优秀和卓越、如何成为领导人才而展开的。最近&#xff0c;…...