【java】Spring Cloud --Spring Cloud Alibaba RocketMq 异步通信实现
文章目录
- 介绍
- RocketMQ特点
- Spring Cloud Stream
- Window搭建部署RocketMQ
- 下载
- 启动NameServer服务
- 启动Broker服务
- 示例
- 创建 RocketMQ 消息生产者
- 创建 RocketMQ 消息消费者
- 使用示例
- 示例关联项目
- 运行示例测试
介绍
RocketMQ 是一款开源的分布式消息系统,基于高可用分布式集群技术,提供低延时的、高可靠的消息发布与订阅服务,广泛应用于多个领域,包括异步通信解耦、企业解决方案、金融支付、电信、电子商务、快递物流、广告营销、社交、即时通信、移动应用、手游、视频、物联网、车联网等。
RocketMQ 是阿里巴巴在2012年开源的分布式消息中间件,目前已经捐赠给 Apache 软件基金会,并于2017年9月25日成为 Apache 的顶级项目。作为经历过多次阿里巴巴双十一这种“超级工程”的洗礼并有稳定出色表现的国产中间件,以其高性能、低延时和高可靠等特性近年来已经也被越来越多的国内企业使用。
RocketMQ特点
- 是一个队列模型的消息中间件,具有高性能、高可靠、高实时、分布式等特点
- Producer、Consumer、队列都可以分布式
- Producer 向一些队列轮流发送消息,队列集合称为 Topic,Consumer 如果做广播消费,则一个 Consumer 实例消费这个 Topic 对应的所有队列,如果做集群消费,则多个 Consumer 实例平均消费这个 Topic 对应的队列集合
- 能够保证严格的消息顺序
- 支持拉(pull)和推(push)两种消息模式
- 高效的订阅者水平扩展能力
- 实时的消息订阅机制
- 亿级消息堆积能力
- 支持多种消息协议,如 JMS、OpenMessaging 等
- 较少的依赖
Spring Cloud Stream
Spring Cloud Stream 是一个构建消息驱动微服务的框架。
Spring Cloud Stream 提供了消息中间件配置的统一抽象,推出了 pub/sub,consumer groups,semantics,stateful partition 这些统一的模型支持。
Spring Cloud Stream 核心构件有:Binders、Bindings和Message,应用程序通过 inputs 或者 outputs 来与 binder 交互,通过我们配置来 binding ,而 binder 负责与中间件交互,Message为数据交换的统一数据规范格式。
-
Binding: 包括 Input Binding 和 Output Binding。
Binding 在消息中间件与应用程序提供的 Provider 和 Consumer 之间提供了一个桥梁,实现了开发者只需使用应用程序的 Provider 或 Consumer 生产或消费数据即可,屏蔽了开发者与底层消息中间件的接触。 -
Binder: 跟外部消息中间件集成的组件,用来创建 Binding,各消息中间件都有自己的 Binder 实现。
比如 Kafka 的实现 KafkaMessageChannelBinder,RabbitMQ 的实现 RabbitMessageChannelBinder 以及 RocketMQ 的实现 RocketMQMessageChannelBinder。 -
Message:是 Spring Framework 中的一个模块,其作用就是统一消息的编程模型。
比如消息 Messaging 对应的模型就包括一个消息体 Payload 和消息头 Header。
spring-cloud-stream 官网
Window搭建部署RocketMQ
下载
当前最新版本为4.6.0
下载出来解压到:D:\rocketmq 目录,目录最好不要带空格和太深,否则服务运行可能会报错
启动NameServer服务
在启动之前需要配置系统环境,不然会报错。
Please set the ROCKETMQ_HOME variable in your environment!
系统环境变量名:ROCKETMQ_HOME
根据你解压的目录配置环境变量,比如我的变量值为:D:\rocketmq
进入window命令窗口,进入D:\rocketmq\bin目录下,执行
start mqnamesrv.cmd
如上则NameServer启动成功。使用期间,窗口不要关闭。
启动Broker服务
进入bin目录下,输入
start mqbroker.cmd -n localhost:9876
如上的 ip+port 是rocketmq的服务地址和端口。
运行如上命令,可能会报如下错误。找不到或无法加载主类
如果出此情况,打开bin–>runbroker.cmd,修改%CLASSPATH%成"%CLASSPATH%"
保存再次执行如上命令。执行成功后,提示boot success 代表成功。
示例
本示例实现三种消息的发布以及订阅接收。
创建 RocketMQ 消息生产者
创建 ali-rocketmq-producer 工程,端口为:28081
- pom.xml添加依赖
<?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"><parent><artifactId>cloud-alibaba</artifactId><groupId>com.easy</groupId><version>1.0.0</version></parent><modelVersion>4.0.0</modelVersion><artifactId>ali-rocketmq-producer</artifactId><packaging>jar</packaging><dependencies><!--rocketmq依赖--><dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-stream-rocketmq</artifactId></dependency><!--web依赖--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-actuator</artifactId></dependency></dependencies><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin></plugins></build></project>
- 配置 Output 的 Binding 信息并配合 @EnableBinding 注解使其生效
application.yml配置
server:port: 28081spring:application:name: ali-rocketmq-producercloud:stream:rocketmq:binder:# RocketMQ 服务器地址name-server: 127.0.0.1:9876bindings:output1: {destination: test-topic1, content-type: application/json}output2: {destination: test-topic2, content-type: application/json}management:endpoints:web:exposure:include: '*'endpoint:health:show-details: always
ArProduceApplication.java
@SpringBootApplication
@EnableBinding({MySource.class})
public class ArProduceApplication {public static void main(String[] args) {SpringApplication.run(ArProduceApplication.class, args);}
}
- 消息生产者服务
MySource.java
package com.easy.arProduce;import org.springframework.cloud.stream.annotation.Output;
import org.springframework.messaging.MessageChannel;public interface MySource {@Output("output1")MessageChannel output1();@Output("output2")MessageChannel output2();
}
SenderService.java
package com.easy.arProduce;import org.apache.rocketmq.spring.support.RocketMQHeaders;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageHeaders;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.stereotype.Service;
import org.springframework.util.MimeTypeUtils;@Service
public class SenderService {@Autowiredprivate MySource source;/*** 发送字符串** @param msg*/public void send(String msg) {Message message = MessageBuilder.withPayload(msg).build();source.output1().send(message);}/*** 发送带tag的字符串** @param msg* @param tag*/public void sendWithTags(String msg, String tag) {Message message = MessageBuilder.withPayload(msg).setHeader(RocketMQHeaders.TAGS, tag).build();source.output1().send(message);}/*** 发送对象** @param msg* @param tag* @param <T>*/public <T> void sendObject(T msg, String tag) {Message message = MessageBuilder.withPayload(msg).setHeader(RocketMQHeaders.TAGS, tag).setHeader(MessageHeaders.CONTENT_TYPE, MimeTypeUtils.APPLICATION_JSON).build();source.output2().send(message);}
}
编写 TestController.java 控制器方便测试
package com.easy.arProduce;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;@RestController
@RequestMapping(value = "test")
public class TestController {@AutowiredSenderService senderService;@RequestMapping(value = "/send", method = RequestMethod.GET)public String send(String msg) {senderService.send(msg);return "字符串消息发送成功!";}@RequestMapping(value = "/sendWithTags", method = RequestMethod.GET)public String sendWithTags(String msg) {senderService.sendWithTags(msg, "tagStr");return "带tag字符串消息发送成功!";}@RequestMapping(value = "/sendObject", method = RequestMethod.GET)public String sendObject(int index) {senderService.sendObject(new Foo(index, "foo"), "tagObj");return "Object对象消息发送成功!";}
}
创建 RocketMQ 消息消费者
创建 ali-rocketmq-consumer 工程,端口为:28082
- pom.xml添加依赖
<?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"><parent><artifactId>cloud-alibaba</artifactId><groupId>com.easy</groupId><version>1.0.0</version></parent><modelVersion>4.0.0</modelVersion><artifactId>ali-rocketmq-consumer</artifactId><packaging>jar</packaging><dependencies><dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-stream-rocketmq</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-actuator</artifactId></dependency></dependencies><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin></plugins></build>
</project>
-配置 Input 的 Binding 信息并配合 @EnableBinding 注解使其生效
application.yml配置
server:port: 28082spring:application:name: ali-rocketmq-consumercloud:stream:rocketmq:binder:name-server: 127.0.0.1:9876 #rocketmq 服务地址bindings:input1: {consumer.orderly: true} #是否排序input2: {consumer.tags: tagStr} #订阅 带tag值为tagStr的字符串input3: {consumer.tags: tagObj} #订阅 带tag值为tabObj的字符串bindings:input1: {destination: test-topic1, content-type: text/plain, group: test-group1, consumer.maxAttempts: 1}input2: {destination: test-topic1, content-type: application/plain, group: test-group2, consumer.maxAttempts: 1}input3: {destination: test-topic2, content-type: application/plain, group: test-group3, consumer.maxAttempts: 1}management:endpoints:web:exposure:include: '*'endpoint:health:show-details: always
ArConsumerApplication.java
package com.easy.arConsumer;import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.stream.annotation.EnableBinding;@SpringBootApplication
@EnableBinding({MySource.class})
public class ArConsumerApplication {public static void main(String[] args) {SpringApplication.run(ArConsumerApplication.class, args);}
}
- 消息消费者服务
MySource.java
package com.easy.arConsumer;import org.springframework.cloud.stream.annotation.Input;
import org.springframework.messaging.SubscribableChannel;public interface MySource {@Input("input1")SubscribableChannel input1();@Input("input2")SubscribableChannel input2();@Input("input3")SubscribableChannel input3();
}
ReceiveService.java
package com.easy.arConsumer;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.stream.annotation.StreamListener;
import org.springframework.messaging.handler.annotation.Payload;
import org.springframework.stereotype.Service;@Service
@Slf4j
public class ReceiveService {@StreamListener("input1")public void receiveInput1(String receiveMsg) {log.info("input1 接收到了消息:" + receiveMsg);}@StreamListener("input2")public void receiveInput2(String receiveMsg) {log.info("input2 接收到了消息:" + receiveMsg);}@StreamListener("input3")public void receiveInput3(@Payload Foo foo) {log.info("input3 接收到了消息:" + foo);}
}
使用示例
示例关联项目
本示例我们创建了两个项目实现
-
ali-rocketmq-producer:RocketMQ 消息服务生产者,服务名:ali-rocketmq-producer,端口:28081
-
ali-rocketmq-consumer:RocketMQ 消息服务消费者,服务名:ali-rocketmq-producer,端口:28082
运行示例测试
首先要启动ali-rocketmq-producer服务及ali-rocketmq-consumer服务
- 访问消息服务生产者地址: http://localhost:28081/test/send?msg=yuntian
查看服务消费者控制台,输出
2019-12-04 15:37:47.859 INFO 6356 --- [MessageThread_1] com.easy.arConsumer.ReceiveService : input1 接收到了消息:yuntian
2019-12-04 15:37:47.859 INFO 6356 --- [MessageThread_1] s.b.r.c.RocketMQListenerBindingContainer : consume C0A8096E200818B4AAC212CDA70E0014 cost: 1 ms
表示字符串消费成功被input1消费了
- 访问消息服务生产者地址: http://localhost:28081/test/sendWithTags?msg=tagyuntian
查看服务消费者控制台,输出
2019-12-04 15:38:09.586 INFO 6356 --- [MessageThread_1] com.easy.arConsumer.ReceiveService : input2 接收到了消息:tagyuntian
2019-12-04 15:38:09.592 INFO 6356 --- [MessageThread_1] com.easy.arConsumer.ReceiveService : input1 接收到了消息:tagyuntian
2019-12-04 15:38:09.592 INFO 6356 --- [MessageThread_1] s.b.r.c.RocketMQListenerBindingContainer : consume C0A8096E200818B4AAC212CDFCD30015 cost: 6 ms
表示带tag的字符串成功被input2和input1消费了,因为input1也订阅了test-topic1,并且没有我们没有加tag过滤,默认表示接收所有消息,所以也能成功接收tagyuntian字符串
- 访问消息服务生产者地址: http://localhost:28081/test/sendObject?index=1
查看服务消费者控制台,输出
2019-12-04 15:41:15.285 INFO 6356 --- [MessageThread_1] com.easy.arConsumer.ReceiveService : input3 接收到了消息:Foo{id=1, bar='foo'}
表示input3成功接收到了tag带tagObj的对象消息了,而input1却没有输出消息,这是因为sendObject发布的消息走的是test-topic2消息管道,所以不会发布给input1及input2订阅者
相关文章:

【java】Spring Cloud --Spring Cloud Alibaba RocketMq 异步通信实现
文章目录介绍RocketMQ特点Spring Cloud StreamWindow搭建部署RocketMQ下载启动NameServer服务启动Broker服务示例创建 RocketMQ 消息生产者创建 RocketMQ 消息消费者使用示例示例关联项目运行示例测试介绍 RocketMQ 是一款开源的分布式消息系统,基于高可用分布式集…...

玫瑰花变蚊子血,自动化无痕浏览器对比测试,新贵PlayWright Vs 老牌Selenium,基于Python3.10
也许每一个男子全都有过这样的两个女人,至少两个。娶了红玫瑰,久而久之,红的变了墙上的一抹蚊子血,白的还是床前明月光;娶了白玫瑰,白的便是衣服上沾的一粒饭黏子,红的却是心口上一颗朱砂痣。–…...

Spring Cloud入门篇 Hello World | Spring Cloud 1
一、专栏说明 Spring Cloud是一系列框架的有序集合。它利用Spring Boot的开发便利性巧妙地简化了分布式系统基础设施的开发,如:服务发现/注册、配置中心、消息总线、负载均衡、断路器、数据监控等,都可以用Spring Boot的开发风格做到一键启动和部署。 本文主要介绍Spring C…...

C++学习笔记-数据结构
结构 是C中另一种用户自定义的可用数据类型,允许存储不同类型的数据项。 C/C 数组允许定义可存储相同类型数据项的变量,但是结构是 C 中另一种用户自定义的可用的数据类型,它允许存储不同类型的数据项。 结构用于表示一条记录,假…...

【C++的OpenCV】第五课-OpenCV图像常用操作(二):OpenCV的基本绘图、平滑滤波(模糊)处理
让我们继续一、OpenCV基本绘图1.1 OpenCV关于绘图的操作1.1.1 cv::Point()1.1.2 cv::Scalar()1.1.3 cv::line()画线1.1.4 cv::rectangle()画矩形1.1.5 cv::circle()画圆二、图像的平滑滤波处理2.1 概念2.2 OpenCV关于图像模糊的操作2.2.1 常用滤波器的分类2.2.2 各种滤波方法具…...

[SSD固态硬盘技术 19] 谁是数据的守护神? 盘内RAID1/RAID5图文详解_盘内数据冗余保护
版权声明: 付费作品,禁止转载前言提到冗余保护,最容易想到的就是RAID(Redundant Arrays of Independent Disks) , 独立冗余磁盘阵列。它是一种把多块独立的物理硬盘按不同方式组合形成一个硬盘组,以此提供比单个硬盘更高的存储性能…...

linux相对于windows环境为啥相对来说更加具有安全性
linux相对于windows环境为啥相对来说更加具有安全性 文章目录linux相对于windows环境为啥相对来说更加具有安全性前言一、linux不需要防病毒软件1.1Linux 桌面的恶意软件很少见1.2Linux 的软件安装更安全1.3Linux 保护自己免受恶意软件的侵害1.4杀毒效果存疑1.5Linux 良好的安全…...

iOS开发笔记之九十七——关于Restful API的一些总结
*****阅读完此文,大概需要3分钟******一、什么是 Restful API?Restful(Representational State Transfer表现层状态转换)是目前最流行的接口设计规范。Restful API 是一种设计风格(是设计风格而不是标准)&a…...

Linux系统Nginx下载和安装
文章目录golang学习面试网站Linux启动nginx参考Linux启动nginx版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。 本文链接:https://blog.csdn.net/weixin_36755535/article/details/110…...

交叉编译 acl
交叉编译 acl 概述 访问控制列表(Access Control Lists,ACL)是应用在路由器接口的指令列表。在 Linux 系统中,ACL 用于设定用户针对文件的权限,而不是在交换路由器中用来控制数据访问的功能(类似于防火墙…...

wait/notify方法 等待唤醒机制
线程正在运行,调用这个线程的wait()方法,这个线程就会进入一个集合进行等待(这个集合的线程不会争抢cpu),此时线程的状态就是waiting 当有线程调用notify()方法的时候,就会从集合中挑选一个线程进入到排队队列里面 notifyAll就是…...

c++笔记之构造函数中的default作用
一、 举例: class Student {int ID;std::string sName; };Student s1; Student s2(s1); 在不定义任何构造函数的情况下,Student对象能定义成功,因为编译器会默认为我们设置几个构造函数,多的不说了,就说最简单的两个: (1) Student s1; 这个就是会调用编译器为我们…...

【代码随想录二刷】Day24-回溯-C++
代码随想录二刷Day24 今日任务 理论基础 77.组合 语言:C 理论基础 解决的问题 ① 组合问题:不考虑顺序 ② 切割问题 ③ 子集问题 ④ 排列问题:考虑顺序 ⑤ 棋盘问题:N皇后,解数独回溯法三部曲 ① 回溯函数模板返回…...

Kubernetes中YAML 文件简介
我们在安装 kubernetes 集群的时候使用了一些 YAML 文件来创建相关的资源,但是对 YAML 文件还是非常陌生。所以我们先来简单看一看 YAML 文件是如何工作的,并使用 YAML 文件来定义一个 kubernetes pod,然后再来定义一个 kubernetes deploymen…...

骨传导耳机是怎么发声的,骨传导耳机值得入手嘛
现在市面上除了我们平时比较常见的有线耳机、头戴耳机、真无线耳机,近两年还涌现出了一种有着黑科技之称的特别耳机——骨传导耳机,并且因其在运动场景下的优势过于明显而得到了众多运动爱好者的大力追捧。那么今天我们就来聊聊这款所谓的黑科技骨传导耳…...

会声会影2023官方新功能介绍
深入简单直观的视频编辑!使用 Corel VideoStudio会声会影2023,将您最美好的时刻和生活体验变成令人惊叹的电影,这是一款有趣且直观的视频编辑器,包含高级工具和高级效果。从自定义标题和过渡,到 Mask Creator、Color G…...

vue:pdf.js使用细节/隐藏按钮/设置、获取当前页码/记录阅读进度/切换语言(国际化)
需求描述 在网页中预览pdf时,希望实现3点需求:1、隐藏一些功能按钮(比如下载);2、打开pdf时自动定位到最后浏览的页(记录阅读进度);3、实现国际化(在代码中更改pdf插件使…...

RocketMQ实现延迟队列精确到秒级实现
前言篇:为了节约成本,决定通过自研来改造rocketmq,添加任意时间延迟的延时队列,开源版本的rocketmq只有支持18个等级的延迟时间,其实对于大部分的功能是够用了的,但是以前的项目,全部都是使用了…...

线性数据结构:数组 Array
一、前言数组是数据结构还是数据类型?数组只是个名称,它可以描述一组操作,也可以命名这组操作。数组的数据操作,是通过 idx->val 的方式来处理。它不是具体要求内存上要存储着连续的数据才叫数组,而是说,…...

大数据开发-Hive
1、hive简介 hive是基于Hadoop的一个数据仓库工具,用于分析数据的。可以将结构化数据文件映射为一张数据库表,并提供类SQL查询功能 注:hive-SQL or HQL or类SQL 和标准SQL还是有一点点区别的 本质是SQL转换为MapReduce程序 用途࿱…...

《程序员新声》-Tech Lead 如何带领团队
收听本期播客 谢谢收听程序员新声,这是一款来自思特沃克(Thoughtworks)的播客节目。在这里,我们不仅讨论软件和技术领域的现状和未来,更关注程序员的成长世界。如何学习,如何晋升,如何带领团队…...

每日算法面试题
🧝♂️算法题 实现 pow(x, n) ,即计算 x 的 n 次幂函数(即,xn)。 示例 1:输入:x = 2.00000, n = 10 输出:1024.00000示例 2:输入:x = 2.10000, n = 3 输出:9.26100示例 3:输入:x...

高质量前端之自动化测试
前端自动化测试:Testing Library 篇 引言 前端测试 静态测试 eslint、TypeScript 单元测试 jest、mocha 集成测试 enzyme、react-testing-library、mock 爬虫 前后端解耦 为什么要引入自动化测试 测试可以让开发者站在用户的角度考虑问题,通过测试的手…...

2023不伤人脉的全新商城分销,一劳永逸的消费分红
2023不伤人脉的全新商城分销,一劳永逸的消费分红 2023-02-24 11:52梦龙 2023不伤人脉的全新商城分销,一劳永逸的消费分红 如今是流量为王的时代,但是如何将流量转化为忠实客户是个难题。不再是单向的买卖关系,而是从对产品的关注…...

【代码随想录训练营】【Day21】第六章|二叉树|530.二叉搜索树的最小绝对差|501.二叉搜索树中的众数|236. 二叉树的最近公共祖先
二叉搜索树的最小绝对差 题目详细:LeetCode.530 这道题使我第一次了解到二叉树的双指针遍历法,详细可以先查看卡哥的讲解视频:《代码随想录 — 二叉搜索树中的众数》 利用二叉搜索树的特点: 中序遍历二叉搜索树得到一个有序序…...

leaflet 导出图片,打印图片(A4横版或竖版)
第093个 点击查看专栏目录 本示例的目的是介绍如何在vue+leaflet中打印图片导出图片。一个简单的leaflet插件示例,添加了一个图标来打印或导出地图。 直接复制下面的 vue+openlayers源代码,操作2分钟即可运行实现效果. 文章目录 示例效果配置方式示例源代码(共85行)安装插…...

Java面向对象:继承特性的学习
本文介绍了面向对象的继承特性: 什么是继承 继承的概念 Java中继承的语法 在继承下父类成员的访问 super和this关键字 父类和子类构造方法 在继承下类中出现初始化代码的执行顺序 父类成员的访问权限对子类的可见性 Java的继承关系 final关键字 认识继承和组合关系 继承特性的学…...

问答系统(QA)调研
引言 智能问答系统广泛用于回答人们以自然语言形式提出的问题,经典应用场景包括:智能语音交互、在线客服、知识获取、情感类聊天等。根据QA任务,可以将QA大致分为5大类,分别为: 文本问答(text-based QA&am…...

商务租车的三大优势吸引企业以租代购
随着社会机经济的高速发展,租车模式的日益盛行,租车不仅仅是受个体户的青睐,而作为环保经济的出行方式也让越来越多的企业开始选择以租代买,据调查统计,最早开始商务租车的群体是外企。而近几年,国内的很多…...

蓝桥杯的比赛流程和必考点
蓝桥杯的比赛流程和必考点 距省赛仅1个多月!蓝桥杯的比赛流程和必考点,你还不清楚? “巷子里的猫很自由,却没有归宿;围墙里的狗有归宿,终身都得低头。人生这道选择题,怎么选都会有遗憾。” 但不…...