SpringBoot(七)

③SpringBoot整合ActiveMQ

老古董产品,目前市面上用的很少

windows版安装包下载地址:https://activemq.apache.org/components/classic/download/

运行bin目录下的win32或win64目录下的activemq.bat命令即可,根据自己的操作系统选择即可,默认对外服务端口61616。

web管理服务默认端口8161

  1. 导入springboot整合ActiveMQ的starter

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-activemq</artifactId>
    </dependency>
    
  2. 配置ActiveMQ的服务器地址

    spring:
      activemq:
        broker-url: tcp://localhost:61616
    
  3. 使用JmsMessagingTemplate操作ActiveMQ

    @Service
    public class MessageServiceActivemqImpl implements MessageService {
        @Autowired
        private JmsMessagingTemplate messagingTemplate;
    
        @Override
        public void sendMessage(String id) {
            //发送消息需要先将消息的类型转换成字符串,然后再发送,所以是convertAndSend,定义消息发送的位置,和具体的消息内容,此处使用id作为消息内容。
            System.out.println("待发送短信的订单已纳入处理队列,id:"+id);
            messagingTemplate.convertAndSend("order.queue.id",id);
        }
    
        @Override
        public String doMessage() {
            //接收消息需要先将消息接收到,然后再转换成指定的数据类型,所以是receiveAndConvert,接收消息除了提供读取的位置,还要给出转换后的数据的具体类型。
            String id = messagingTemplate.receiveAndConvert("order.queue.id",String.class);
            System.out.println("已完成短信发送业务,id:"+id);
            return id;
        }
    }
    
  4. 使用消息监听器在服务器启动后,监听指定位置,当消息出现后,立即消费消息

    @Component
    public class MessageListener {
        //使用注解@JmsListener定义当前方法监听ActiveMQ中指定名称的消息队列。
        @JmsListener(destination = "order.queue.id")
        //如果当前消息队列处理完还需要继续向下传递当前消息到另一个队列中使用注解@SendTo即可,这样即可构造连续执行的顺序消息队列。
        @SendTo("order.other.queue.id")
        public String receive(String id){
            System.out.println("已完成短信发送业务,id:"+id);
            return "new:"+id;
        }
    }
    
  5. 切换消息模型由点对点模型到发布订阅模型,修改jms配置即可

    spring:
      activemq:
        broker-url: tcp://localhost:61616
      jms:
      #pub-sub-domain默认值为false,即点对点模型,修改为true后就是发布订阅模型。
        pub-sub-domain: true
    

④SpringBoot整合RabbitMQ

RabbitMQ是MQ产品中的目前较为流行的产品之一,它遵从AMQP协议。RabbitMQ的底层实现语言使用的是Erlang,所以安装RabbitMQ需要先安装Erlang。

安装

Erlang的windows版安装包下载地址:https😕/www.erlang.org/downloads

RabbitMQ的windows版安装包下载地址:https://rabbitmq.com/install-windows.html

  1. 启动和停止服务器

    rabbitmq-service.bat start		# 启动服务
    rabbitmq-service.bat stop		# 停止服务
    rabbitmqctl status				# 查看服务状态
    
  2. 访问web管理服务

    1. RabbitMQ也提供有web控制台服务,但是此功能是一个插件,需要先启用才可以使用。

      rabbitmq-plugins.bat list							# 查看当前所有插件的运行状态
      rabbitmq-plugins.bat enable rabbitmq_management		# 启动rabbitmq_management插件
      
    2. 启动插件后可以在插件运行状态中查看是否运行,运行后通过浏览器即可打开服务后台管理界面

      #web管理服务默认端口15672
      http://localhost:15672
      
    3. 用户名和密码都为guest,能成功进入管理后台界面即为成功

整合(direct模型)

RabbitMQ满足AMQP协议,因此不同的消息模型对应的制作不同

  1. 导入springboot整合amqp的starter,amqp协议默认实现为rabbitmq方案

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-amqp</artifactId>
    </dependency>
    
  2. 配置RabbitMQ的服务器地址

    spring:
      rabbitmq:
        host: localhost
        port: 5672
    
  3. 初始化直连模式系统设置

    //由于RabbitMQ不同模型要使用不同的交换机,因此需要先初始化RabbitMQ相关的对象,例如队列,交换机等
    @Configuration
    public class RabbitConfigDirect {
        @Bean
        public Queue directQueue(){
            return new Queue("direct_queue");
        }
        @Bean
        public Queue directQueue2(){
            return new Queue("direct_queue2");
        }
        @Bean
        public DirectExchange directExchange(){
            return new DirectExchange("directExchange");
        }
        @Bean
        public Binding bindingDirect(){
            return BindingBuilder.bind(directQueue()).to(directExchange()).with("direct");
        }
        @Bean
        public Binding bindingDirect2(){
            return BindingBuilder.bind(directQueue2()).to(directExchange()).with("direct2");
        }
    }
    //队列Queue与直连交换机DirectExchange创建后,还需要绑定他们之间的关系Binding,这样就可以通过交换机操作对应队列。
    
  4. 使用AmqpTemplate操作RabbitMQ

    //amqp协议中的操作API接口名称看上去和jms规范的操作API接口很相似,但是传递参数差异很大。
    @Service
    public class MessageServiceRabbitmqDirectImpl implements MessageService {
        @Autowired
        private AmqpTemplate amqpTemplate;
    
        @Override
        public void sendMessage(String id) {
            System.out.println("待发送短信的订单已纳入处理队列(rabbitmq direct),id:"+id);
            amqpTemplate.convertAndSend("directExchange","direct",id);
        }
    }
    
  5. 使用消息监听器在服务器启动后,监听指定位置,当消息出现后,立即消费消息

    //使用注解@RabbitListener定义当前方法监听RabbitMQ中指定名称的消息队列。
    @Component
    public class MessageListener {
        @RabbitListener(queues = "direct_queue")
        public void receive(String id){
            System.out.println("已完成短信发送业务(rabbitmq direct),id:"+id);
        }
    }
    
整合(topic模型)
  1. 导入springboot整合amqp的starter,amqp协议默认实现为rabbitmq方案

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-amqp</artifactId>
    </dependency>
    
  2. 配置RabbitMQ的服务器地址

    spring:
      rabbitmq:
        host: localhost
        port: 5672
    
  3. 初始化主题模式系统设置

    @Configuration
    public class RabbitConfigTopic {
        @Bean
        public Queue topicQueue(){
            return new Queue("topic_queue");
        }
        @Bean
        public Queue topicQueue2(){
            return new Queue("topic_queue2");
        }
        @Bean
        public TopicExchange topicExchange(){
            return new TopicExchange("topicExchange");
        }
        @Bean
        public Binding bindingTopic(){
            return BindingBuilder.bind(topicQueue()).to(topicExchange()).with("topic.*.id");
        }
        @Bean
        public Binding bindingTopic2(){
            return BindingBuilder.bind(topicQueue2()).to(topicExchange()).with("topic.orders.*");
        }
    }
    

    主题模式支持routingKey匹配模式,*表示匹配一个单词,#表示匹配任意内容,这样就可以通过主题交换机将消息分发到不同的队列中

    匹配键 topic.*.* topic.#
    topic.order.id true true
    order.topic.id false false
    topic.sm.order.id false true
    topic.sm.id false true
    topic.id.order true true
    topic.id false true
    topic.order false true
  4. 使用AmqpTemplate操作RabbitMQ

    //发送消息后,根据当前提供的routingKey与绑定交换机时设定的routingKey进行匹配,规则匹配成功消息才会进入到对应的队列中。
    @Service
    public class MessageServiceRabbitmqTopicImpl implements MessageService {
        @Autowired
        private AmqpTemplate amqpTemplate;
    
        @Override
        public void sendMessage(String id) {
            System.out.println("待发送短信的订单已纳入处理队列(rabbitmq topic),id:"+id);
            amqpTemplate.convertAndSend("topicExchange","topic.orders.id",id);
        }
    }
    
  5. 使用消息监听器在服务器启动后,监听指定队列

    //使用注解@RabbitListener定义当前方法监听RabbitMQ中指定名称的消息队列。
    @Component
    public class MessageListener {
        @RabbitListener(queues = "topic_queue")
        public void receive(String id){
            System.out.println("已完成短信发送业务(rabbitmq topic 1),id:"+id);
        }
        @RabbitListener(queues = "topic_queue2")
        public void receive2(String id){
            System.out.println("已完成短信发送业务(rabbitmq topic 22222222),id:"+id);
        }
    }
    

⑤SpringBoot整合RocketMQ

RocketMQ由阿里研发,后捐赠给apache基金会,目前是apache基金会顶级项目之一,也是目前市面上的MQ产品中较为流行的产品之一,它遵从AMQP协议。

windows版安装包下载地址:https://rocketmq.apache.org/

默认对外服务端口9876。

  1. 导入springboot整合RocketMQ的starter,此坐标不由springboot维护版本

    <dependency>
        <groupId>org.apache.rocketmq</groupId>
        <artifactId>rocketmq-spring-boot-starter</artifactId>
        <version>2.2.1</version>
    </dependency>
    
  2. 配置RocketMQ的服务器地址

    rocketmq:
      name-server: localhost:9876
      producer:
      #设置默认的生产者消费者所属组group。
        group: group_rocketmq
    
  3. 使用RocketMQTemplate操作RocketMQ

    //使用asyncSend方法发送异步消息。
    @Service
    public class MessageServiceRocketmqImpl implements MessageService {
        @Autowired
        private RocketMQTemplate rocketMQTemplate;
    
        @Override
        public void sendMessage(String id) {
            System.out.println("待发送短信的订单已纳入处理队列(rocketmq),id:"+id);
            SendCallback callback = new SendCallback() {
                @Override
                public void onSuccess(SendResult sendResult) {
                    System.out.println("消息发送成功");
                }
                @Override
                public void onException(Throwable e) {
                    System.out.println("消息发送失败!!!!!");
                }
            };
            rocketMQTemplate.asyncSend("order_id",id,callback);
        }
    }
    
  4. 使用消息监听器在服务器启动后,监听指定位置,当消息出现后,立即消费消息

    //ocketMQ的监听器必须按照标准格式开发,实现RocketMQListener接口,泛型为消息类型。
    @Component
    @RocketMQMessageListener(topic = "order_id",consumerGroup = "group_rocketmq")
    public class MessageListener implements RocketMQListener<String> {
        @Override
        public void onMessage(String id) {
            System.out.println("已完成短信发送业务(rocketmq),id:"+id);
        }
    }
    //使用注解@RocketMQMessageListener定义当前类监听RabbitMQ中指定组、指定名称的消息队列。
    

⑥SpringBoot整合Kafka

windows版安装包下载地址:https://kafka.apache.org/downloads

  • 运行bin目录下的windows目录下的zookeeper-server-start命令即可启动注册中心,默认对外服务端口2181。
  • 运行bin目录下的windows目录下的kafka-server-start命令即可启动kafka服务器,默认对外服务端口9092。
基本使用
  1. 启动服务器

    • kafka服务器的功能相当于RocketMQ中的broker,kafka运行还需要一个类似于命名服务器的服务。在kafka安装目录中自带一个类似于命名服务器的工具,叫做zookeeper,它的作用是注册中心

      zookeeper-server-start.bat ..\..\config\zookeeper.properties		# 启动zookeeper
      kafka-server-start.bat ..\..\config\server.properties				# 启动kafka
      
    • 运行bin目录下的windows目录下的zookeeper-server-start命令即可启动注册中心,默认对外服务端口2181。

    • 运行bin目录下的windows目录下的kafka-server-start命令即可启动kafka服务器,默认对外服务端口9092。

  2. 创建主题

    • 和之前操作其他MQ产品相似,kakfa也是基于主题操作,操作之前需要先初始化topic。

      # 创建topic
      kafka-topics.bat --create --zookeeper localhost:2181 --replication-factor 1 --partitions 1 --topic itheima
      # 查询topic
      kafka-topics.bat --zookeeper 127.0.0.1:2181 --list					
      # 删除topic
      kafka-topics.bat --delete --zookeeper localhost:2181 --topic itheima
      
  3. 测试服务器启动状态

    • Kafka提供有一套测试服务器功能的测试程序,运行bin目录下的windows目录下的命令即可使用。

      kafka-console-producer.bat --broker-list localhost:9092 --topic itheima							# 测试生产消息
      kafka-console-consumer.bat --bootstrap-server localhost:9092 --topic itheima --from-beginning	# 测试消息消费
      
整合
  1. 导入springboot整合Kafka的starter,此坐标由springboot维护版本

    <dependency>
        <groupId>org.springframework.kafka</groupId>
        <artifactId>spring-kafka</artifactId>
    </dependency>
    
  2. 配置Kafka的服务器地址

    spring:
      kafka:
        bootstrap-servers: localhost:9092
        consumer:
        #设置默认的生产者消费者所属组id。
          group-id: order
    
  3. 使用KafkaTemplate操作Kafka

    @Service
    public class MessageServiceKafkaImpl implements MessageService {
        @Autowired
        private KafkaTemplate<String,String> kafkaTemplate;
    
        @Override
        public void sendMessage(String id) {
            System.out.println("待发送短信的订单已纳入处理队列(kafka),id:"+id);
            //使用send方法发送消息,需要传入topic名称。
            kafkaTemplate.send("itheima2022",id);
        }
    }
    
  4. 使用消息监听器在服务器启动后,监听指定位置,当消息出现后,立即消费消息

    @Component
    public class MessageListener {
        //使用注解@KafkaListener定义当前方法监听Kafka中指定topic的消息,接收到的消息封装在对象ConsumerRecord中,获取数据从ConsumerRecord对象中获取即可。
        @KafkaListener(topics = "itheima2022")
        public void onMessage(ConsumerRecord<String,String> record){
            System.out.println("已完成短信发送业务(kafka),id:"+record.value());
        }
    }
    

6.监控

通过软件的方式展示另一个软件的运行情况,运行的情况则通过各种各样的指标数据反馈给监控人员。

6.1监控的四大指标

  1. 监控服务状态是否处理宕机状态:现在的互联网程序大部分都是基于微服务的程序,一个程序的运行需要若干个服务来保障,因此第一个要监控的指标就是服务是否正常运行。
  2. 监控服务运行指标:互联网程序服务的客户量是巨大的,当客户的请求在短时间内集中达到服务器后,就会出现各种程序运行指标的波动。
  3. 监控程序运行日志:需要在不停机的情况下,监控系统运行情况,日志是一个不错的手段。如果在众多日志中找到开发者或运维人员所关注的日志信息,简单快速有效的过滤出要看的日志也是监控系统需要考虑的问题。
  4. 管理服务状态:由于突发情况的出现,例如服务器被攻击、服务器内存溢出等情况造成了服务器宕机,此时当前服务不能满足使用需要,就要将其重启甚至关闭,如何快速控制服务器的启停也是程序运行过程中不可回避的问题。

以上这些是从大的方面来思考监控这个问题,还有很多的细节点,例如上线了一个新功能,定时提醒用户续费,这种功能不是上线后马上就运行的,但是当前功能是否真的启动,如果快速的查询到这个功能已经开启,这也是监控中要解决的问题。

6.2可视化监控平台

springboot抽取了大部分监控系统的常用指标,提出了监控的总思想。然后就有大佬根据监控的总思想,制作了一个通用性很强的监控系统,因为是基于springboot监控的核心思想制作的,所以这个程序被命名为Spring Boot Admin

Spring Boot Admin,这是一个开源社区项目,用于管理和监控SpringBoot应用程序。这个项目中包含有客户端和服务端两部分,而监控平台指的就是服务端。我们做的程序如果需要被监控,将我们做的程序制作成客户端,然后配置服务端地址后,服务端就可以通过HTTP请求的方式从客户端获取对应的信息,并通过UI界面展示对应信息。

下面就来开发这套监控程序,先制作服务端,其实服务端可以理解为是一个web程序,收到一些信息后展示这些信息。

①服务端开发

  1. 导入springboot admin对应的starter,版本与当前使用的springboot版本保持一致,并将其配置成web工程

    <dependency>
        <groupId>de.codecentric</groupId>
        <artifactId>spring-boot-admin-starter-server</artifactId>
        <version>2.5.4</version>
    </dependency>
    
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <!-- 也可以通过创建项目时勾选:Ops→选择带server的那个 -->
    
  2. 在引导类上添加注解@EnableAdminServer,声明当前应用启动后作为SpringBootAdmin的服务器使用

    @SpringBootApplication
    @EnableAdminServer
    public class Springboot25AdminServerApplication {
        public static void main(String[] args) {
            SpringApplication.run(Springboot25AdminServerApplication.class, args);
        }
    }
    //直接启动访问即可:localhost:8080
    

②客户端开发

  1. 导入springboot admin对应的starter,版本与当前使用的springboot版本保持一致,并将其配置成web工程

    <dependency>
        <groupId>de.codecentric</groupId>
        <artifactId>spring-boot-admin-starter-client</artifactId>
        <version>2.5.4</version>
    </dependency>
    
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <!-- 也可以通过创建项目时勾选:Ops→选择带client的那个 -->
    
  2. 设置当前客户端将信息上传到哪个服务器上,通过yml文件配置

    spring:
      boot:
        admin:
          client:
            url: http://localhost:8080
    #开放指定信息给服务器看
    management:
      endpoint:
        health:
          show-details: always
    #允许服务器以HTTP请求的方式获取对应的信息
      endpoints:
        web:
          exposure:
            include: "*"
    #直接启动访问即可
    

③配置多个客户端

可以通过配置客户端的方式在其他的springboot程序中添加客户端坐标,这样当前服务器就可以监控多个客户端程序了。每个客户端展示不同的监控信息。

6.3监控原理

通过查阅监控中的映射指标,可以看到当前系统中可以运行的所有请求路径,其中大部分路径以/actuator开头

监控中显示的信息实际上是通过发送请求后得到json数据,然后展示出来。

  • Actuator,可以称为端点,描述了一组监控信息,SpringBootAdmin提供了多个内置端点,通过访问端点就可以获取对应的监控信息,也可以根据需要自定义端点信息。

  • 通过发送请求路劲/actuator可以访问应用所有端点信息,如果端点中还有明细信息可以发送请求/actuator/端点名称来获取详细信息。

    ID 描述 默认启用
    auditevents 暴露当前应用程序的审计事件信息。
    beans 显示应用程序中所有 Spring bean 的完整列表。
    caches 暴露可用的缓存。
    conditions 显示在配置和自动配置类上评估的条件以及它们匹配或不匹配的原因。
    configprops 显示所有 @ConfigurationProperties 的校对清单。
    env 暴露 Spring ConfigurableEnvironment 中的属性。
    flyway 显示已应用的 Flyway 数据库迁移。
    health 显示应用程序健康信息
    httptrace 显示 HTTP 追踪信息(默认情况下,最后 100 个 HTTP 请求/响应交换)。
    info 显示应用程序信息。
    integrationgraph 显示 Spring Integration 图。
    loggers 显示和修改应用程序中日志记录器的配置。
    liquibase 显示已应用的 Liquibase 数据库迁移。
    metrics 显示当前应用程序的指标度量信息。
    mappings 显示所有 @RequestMapping 路径的整理清单。
    scheduledtasks 显示应用程序中的调度任务。
    sessions 允许从 Spring Session 支持的会话存储中检索和删除用户会话。当使用 Spring Session 的响应式 Web 应用程序支持时不可用。
    shutdown 正常关闭应用程序。
    threaddump 执行线程 dump。
    heapdump 返回一个 hprof 堆 dump 文件。
    jolokia 通过 HTTP 暴露 JMX bean(当 Jolokia 在 classpath 上时,不适用于 WebFlux)。
    logfile 返回日志文件的内容(如果已设置 logging.file 或 logging.path 属性)。支持使用 HTTP Range 头来检索部分日志文件的内容。
    prometheus 以可以由 Prometheus 服务器抓取的格式暴露指标。
    • 上述端点每一项代表被监控的指标,如果对外开放则监控平台可以查询到对应的端点信息,如果未开放则无法查询对应的端点信息。通过配置可以设置端点是否对外开放功能。使用enable属性控制端点是否对外开放。其中health端点为默认端点,不能关闭。

      management:
        endpoint:
          health:						# 端点名称
            show-details: always
          info:						# 端点名称
            enabled: true				# 是否开放
      
  • 为了方便开发者快速配置端点,springboot admin设置了13个较为常用的端点作为默认开放的端点,如果需要控制默认开放的端点的开放状态,可以通过配置设置

    management:
      endpoints:
        enabled-by-default: true	# 是否开启默认端点,默认值true
    
    • 上述端点开启后,就可以通过端点对应的路径查看对应的信息了。但是此时还不能通过HTTP请求查询此信息,还需要开启通过HTTP请求查询的端点名称,使用“*”可以简化配置成开放所有端点的WEB端HTTP请求权限。

      management:
        endpoints:
          web:
            exposure:
              include: "*"
      
  • 整体上来说,对于端点的配置有两组信息,一组是endpoints开头的,对所有端点进行配置,一组是endpoint开头的,对具体端点进行配置。

    management:
      endpoint:		# 具体端点的配置
        health:
          show-details: always
        info:
          enabled: true
      endpoints:	# 全部端点的配置
        web:
          exposure:
            include: "*"
        enabled-by-default: true
    

6.4自定义监控指标

①INFO端点

info端点描述了当前应用的基本信息,可以通过两种形式快速配置info端点的信息

  1. 配置形式:在yml文件中通过设置info节点的信息就可以快速配置端点信息

    info:
      appName: @project.artifactId@
      version: @project.version@
      company: 传智教育
      author: itheima
    
  2. 编程形式:通过配置的形式只能添加固定的数据,如果需要动态数据还可以通过配置bean的方式为info端点添加信息,此信息与配置信息共存

    @Component
    public class InfoConfig implements InfoContributor {
        @Override
        public void contribute(Info.Builder builder) {
            builder.withDetail("runTime",System.currentTimeMillis());		//添加单个信息
            Map infoMap = new HashMap();		
            infoMap.put("buildTime","2006");
            builder.withDetails(infoMap);									//添加一组信息
        }
    }
    

②Health端点

  • health端点描述当前应用的运行健康指标,即应用的运行是否成功。通过编程的形式可以扩展指标信息。

    @Component
    public class HealthConfig extends AbstractHealthIndicator {
        @Override
        protected void doHealthCheck(Health.Builder builder) throws Exception {
            boolean condition = true;
            if(condition) {
                builder.status(Status.UP);					//设置运行状态为启动状态
                builder.withDetail("runTime", System.currentTimeMillis());
                Map infoMap = new HashMap();
                infoMap.put("buildTime", "2006");
                builder.withDetails(infoMap);
            }else{
                builder.status(Status.OUT_OF_SERVICE);		//设置运行状态为不在服务状态
                builder.withDetail("上线了吗?","你做梦");
            }
        }
    }
    

③Metrics端点

  • metrics端点描述了性能指标,除了系统自带的监控性能指标,还可以自定义性能指标。

    @Service
    public class BookServiceImpl extends ServiceImpl<BookDao, Book> implements IBookService {
        @Autowired
        private BookDao bookDao;
    
        private Counter counter;
    
        public BookServiceImpl(MeterRegistry meterRegistry){
            counter = meterRegistry.counter("用户付费操作次数:");
        }
    
        @Override
        public boolean delete(Integer id) {
            //每次执行删除业务等同于执行了付费业务
            counter.increment();
            return bookDao.deleteById(id) > 0;
        }
    }
    

④自定义端点

  • 可以根据业务需要自定义端点,方便业务监控

    @Component
    @Endpoint(id="pay",enableByDefault = true)
    public class PayEndpoint {
        @ReadOperation
        public Object getPay(){
            Map payMap = new HashMap();
            payMap.put("level 1","300");
            payMap.put("level 2","291");
            payMap.put("level 3","666");
            return payMap;
        }
    }
    
  • 由于此端点数据spirng boot admin无法预知该如何展示,所以通过界面无法看到此数据,通过HTTP请求路径可以获取到当前端点的信息,但是需要先开启当前端点对外功能,或者设置当前端点为默认开发的端点。

posted @   22-10-21  阅读(24)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
· 字符编码:从基础到乱码解决
· 提示词工程——AI应用必不可少的技术
点击右上角即可分享
微信分享提示