首页 > 其他分享 >NestJs 使用 RabbitMQ

NestJs 使用 RabbitMQ

时间:2023-05-08 19:11:15浏览次数:47  
标签:队列 winston rabbitmq NestJs 使用 import RabbitMQ nestjs

既然是使用 RabbitMQ 那先不管其他的 把 RabbitMQ 装上再说

RabbitMQ 安装

这里直接找他们官网就行

https://www.rabbitmq.com/download.html

这里我们选择使用 docker 安装 快捷方便

这里直接参考:

https://juejin.cn/post/7198430801850105916

我们要站在巨人的肩膀上,快速学习,具体命令

RabbitMQ docker方式安装

# 下载最新的代码 management 的镜像
docker pull rabbitmq:management
# 创建数据卷
docker volume create rabbitmq-home
# 启动容器
docker run -id --name=rabbitmq -v rabbitmq-home:/var/lib/rabbitmq -p 15672:15672 -p 5672:5672 -e RABBITMQ_DEFAULT_USER=admin -e RABBITMQ_DEFAULT_PASS=admin rabbitmq:management

这里除了挂载数据卷之外,还暴露了两个端口,以及设定了两个环境变量:

  • 15672端口:RabbitMQ的管理页面端口
  • 5672端口:RabbitMQ的消息接收端口
  • RABBITMQ_DEFAULT_USER环境变量:指定RabbitMQ的用户名,这里我指定为admin,大家部署时替换成自己定义的
  • RABBITMQ_DEFAULT_PASS环境变量:指定RabbitMQ的密码,这里我指定为admin,大家部署时替换成自己定义的

这样容器就部署完成了!在浏览器访问你的服务器地址:15672即可访问到RabbitMQ的管理界面,用户名和密码即为刚刚指定的环境变量的配置值。

这里没有指定LANG=C.UTF-8,是因为RabbitMQ容器默认就是这个语言环境,无需我们再设定。

image-20230506164331483

访问管理页面

http://localhost:15672/
用户名:admin
密码:admin

image-20230506164730603

可以看到已经进去了

前置知识

RabbitMQ的exchange、bindingkey、routingkey的关系

https://zhuanlan.zhihu.com/p/37198933 原文

https://www.cnblogs.com/makalochen/p/17378002.html 转载

总之:

从 AMQP 协议可以看出,Queue、Exchange 和 Binding 构成了 AMQP 协议的核心

  • Producer:消息生产者,即投递消息的程序。

  • Broker:消息队列服务器实体。

    • Exchange:消息交换机,它指定消息按什么规则,路由到哪个队列。
    • Binding:绑定,它的作用就是把 Exchange 和 Queue 按照路由规则绑定起来。
    • Queue:消息队列载体,每个消息都会被投入到一个或多个队列。
  • Consumer:消息消费者,即接受消息的程序。

Binding 表示 Exchange 与 Queue 之间的关系,

我们也可以简单的认为队列对该交换机上的消息感兴趣,

绑定可以附带一个额外的参数 RoutingKey。

Exchange 就是根据这个 RoutingKey 和当前 Exchange 所有绑定的 Binding 做匹配,

如果满足匹配,就往 Exchange 所绑定的 Queue 发送消息,

这样就解决了我们向 RabbitMQ 发送一次消息,可以分发到不同的 Queue。

RoutingKey 的意义依赖于交换机的类型。

amqb api 文档

https://amqp-node.github.io/amqplib/channel_api.html

只有看了官方文档才能更正确的使用

NesJs 使用 mq 文档

https://docs.nestjs.cn/9/microservices?id=rabbitmq

日志依赖

https://www.npmjs.com/package/winston

https://github.com/winstonjs/winston

https://github.com/gremo/nest-winston

https://docs.nestjs.cn/9/techniques?id=日志

https://juejin.cn/post/7187910528918880311

npm install --save nest-winston winston winston-daily-rotate-file

NestJs 中使用

安装依赖包

npm i --save amqplib amqp-connection-manager @nestjs/microservices

上面三个包基础包,这里还有方便的包

https://github.com/golevelup/nestjs/blob/master/packages/rabbitmq/README.md

所以完整的安装依赖应该为

npm i --save amqplib amqp-connection-manager @nestjs/microservices @golevelup/nestjs-rabbitmq

创建 发布消息模块

nest g mo mqPublist
nest g s mqPublist

image-20230506175359587

这样使用cli 工具就自动给我们 将 service 和 module 关联起来了,并在 全局模块中注册了

连接RabbitMQ

在写其他代码之前我们首先要保证,连接正常

全局注册模块

首先保证我们的 MqPublistModule模块在全局注册

app.module.ts

import { MiddlewareConsumer, Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { CatsController } from './cats/cats.controller';
import { MakaloModule } from './makalo/makalo.module';
import { UploadModule } from './upload/upload.module';
import { UserModule } from './user/user.module';
import { Module1Module } from './module1/module1.module';
import { ConfigModule } from './config/config.module';
import { PModule } from './p/p.module';

import { MqPublistModule } from './mq-publist/mq-publist.module';
// 日志模块
import { WinstonModule } from 'nest-winston';
import * as winston from 'winston';
import 'winston-daily-rotate-file';
@Module({
  imports: [MakaloModule, UploadModule, UserModule, Module1Module,
    ConfigModule.forRoot({ path: '/makalo' }),
    PModule,
    MqPublistModule,
    // 日志模块
    WinstonModule.forRoot({
      transports: [
        new winston.transports.DailyRotateFile({
          dirname: `logs`, // 日志保存的目录
          filename: '%DATE%.log', // 日志名称,占位符 %DATE% 取值为 datePattern 值。
          datePattern: 'YYYY-MM-DD', // 日志轮换的频率,此处表示每天。
          zippedArchive: true, // 是否通过压缩的方式归档被轮换的日志文件。
          maxSize: '20m', // 设置日志文件的最大大小,m 表示 mb 。
          maxFiles: '14d', // 保留日志文件的最大天数,此处表示自动删除超过 14 天的日志文件。
          // 记录时添加时间戳信息
          format: winston.format.combine(
            winston.format.timestamp({
              format: 'YYYY-MM-DD HH:mm:ss',
            }),
            winston.format.json(),
          ),
        }),
      ],
    }),
  ],
  controllers: [AppController, CatsController],
  providers: [AppService],
})
export class AppModule { }

image-20230508154800610

MqPublistModule 模块的RabbitMQ 配置

import { Module } from '@nestjs/common';
import { RabbitMQModule, MessageHandlerErrorBehavior } from '@golevelup/nestjs-rabbitmq';
import { MqPublistService } from './mq-publist.service';

@Module({
  imports: [
    RabbitMQModule.forRootAsync(RabbitMQModule, {
      useFactory: () => {
        return {
          // 交换机配置
          exchanges: [
            {
              // 交换机名称
              name: `exchanges_test`,
              /**
               * 交换机类型
               * direct: 直连交换机,根据消息的路由键(routing key)将消息发送到一个或多个绑定的队列。
                  fanout: 扇形交换机,将消息广播到所有绑定的队列,无需指定路由键。
                  topic: 主题交换机,根据消息的路由键模式匹配将消息发送到一个或多个绑定的队列。
                  headers: 头交换机,根据消息的头部信息将消息发送到一个或多个绑定的队列。
               */
              type: 'direct',
              // 其他选项
              // 持久化(Durable): 指定交换机、队列或消息是否需要在服务器重启后保留
              options: { durable: false },
            },
          ],
          // 连接的url
          uri: 'amqp://admin:admin@localhost:5672',
          /**
           * 用于配置 RabbitMQ 连接的选项。它是一个对象,可以包含以下属性:
            wait: 一个布尔值,表示是否等待连接成功后才开始启动应用程序。默认为 true。
            rejectUnauthorized: 一个布尔值,表示是否拒绝不受信任的 SSL 证书。默认为 true。
            timeout: 一个数字,表示连接超时时间(以毫秒为单位)。默认为 10000 毫秒。
            heartbeatIntervalInSeconds: 一个数字,表示心跳间隔时间(以秒为单位)。默认为 60 秒。
            channelMax: 一个数字,表示最大通道数。默认为 65535。
            这些选项将影响 RabbitMQ 连接的行为和性能。您可以根据需要进行调整
           */
          connectionInitOptions: { wait: false },
          /**
           * 用于启用直接回复模式。当设置为 true 时,
           * 生产者将使用 replyTo 和 correlationId 字段指定的队列和标识符来接收响应,
           * 而不是使用默认生成的匿名队列。这使得消费者可以将响应直接发送到请求者所在的队列,
           * 从而避免了性能上的开销和消息传递中断的问题。
           * 
           * 这里设置为false
           */
          enableDirectReplyTo: false,
          // 通道的默认预取计数。
          prefetchCount: 300,
          /**
          用于配置 RabbitMQ 消费者订阅的默认错误处理行为选项。
          当消费者处理消息时出现错误时,可以使用该选项来指定消费者应如何处理这些错误。
            MessageHandlerErrorBehavior.ACK 表示在发生错误时自动确认消息并从队列中删除
            以避免消息反复传递和死信队列的问题。
            如果您想要更多的控制权来处理错误,可以将其设置为 
            MessageHandlerErrorBehavior.NACK,然后手动决定是否重新排队或丢弃该消息。
           */
          defaultSubscribeErrorBehavior: MessageHandlerErrorBehavior.ACK,
        };
      },
    }),
  ],
  providers: [MqPublistService],
  exports: [MqPublistService],
})
export class MqPublistModule {}

image-20230508143349824

MqPublistService 中的 基本设置

import { AmqpConnection } from '@golevelup/nestjs-rabbitmq';
import { Inject, Injectable, OnModuleInit } from '@nestjs/common';
import { WINSTON_MODULE_PROVIDER } from 'nest-winston';
import { Logger } from 'winston';

@Injectable()
export class MqPublistService implements OnModuleInit {
  constructor(
    @Inject(WINSTON_MODULE_PROVIDER) private readonly logger: Logger,
    private readonly amqp: AmqpConnection
  ) {}

  /**
   * onModuleInit 是 NestJS 中一个生命周期钩子方法,
   * 它是 @nestjs/common 模块提供的 OnModuleInit 接口的一部分。
   * 实现了该接口并实现了 onModuleInit 方法的类,在模块加载时会自动执行该方法
   */
  async onModuleInit() {
    // 启动监听
    this.monitorConn();
  }

  /**
   * rabbitmq连接监听
   */
  monitorConn() {
    const conn = this.amqp.managedConnection;
    if (conn) {
      conn.on('connect', () => {
        this.logger.info('rabbitmq broker connect');
      });
      conn.on('disconnect', () => {
        this.logger.error('rabbitmq broker disconnect');
      });
    }
    const chan = this.amqp.managedChannel;
    if (chan) {
      chan.on('connect', () => {
        this.logger.info('rabbitmq channel connect');
      });
      chan.on('error', () => {
        this.logger.error('rabbitmq channel error');
      });
      chan.on('close', () => {
        this.logger.error('rabbitmq channel close');
      });
    }
  }
}

image-20230508143536357

启动

npm run start:dev

image-20230508154847518

这时候我们查看 RabbitMQ 的管理面板,会发现我们配置的交换机出现了

image-20230508155039170

NestJs RabbitMQ 发送队列消息_案例

如果你看过前置知识,你就知道最重要的三个东西

exchange、routingkey, Queue

上面在NestJs 中已经配置了默认的 交换姬 img

但是 routingkey, Queue 他们之间的绑定关系还没得呢,这时候我们手动设置一下

打开RabbitMQ 的 管理页面

http://localhost:15672/

设置 routingkey, Queue 绑定关系

找到这个交换机,点进去

image-20230508170832877

设置 队列名 和 Routing Key 点击绑定

image-20230508171157557

image-20230508171232762

这时候 我们就将 exchange、routingkey, Queue 关联起来了

全局模块注册

app.module.ts

import { MiddlewareConsumer, Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { CatsController } from './cats/cats.controller';
import { MakaloModule } from './makalo/makalo.module';
import { UploadModule } from './upload/upload.module';
import { UserModule } from './user/user.module';
import { Module1Module } from './module1/module1.module';
import { ConfigModule } from './config/config.module';
import { PModule } from './p/p.module';

import { MqPublistModule } from './mq-publist/mq-publist.module';
// 日志模块
import { WinstonModule } from 'nest-winston';
import * as winston from 'winston';
import 'winston-daily-rotate-file';
@Module({
  imports: [MakaloModule, UploadModule, UserModule, Module1Module,
    ConfigModule.forRoot({ path: '/makalo' }),
    PModule,
    MqPublistModule,
    // 日志模块
    WinstonModule.forRoot({
      transports: [
        new winston.transports.DailyRotateFile({
          dirname: `logs`, // 日志保存的目录
          filename: '%DATE%.log', // 日志名称,占位符 %DATE% 取值为 datePattern 值。
          datePattern: 'YYYY-MM-DD', // 日志轮换的频率,此处表示每天。
          zippedArchive: true, // 是否通过压缩的方式归档被轮换的日志文件。
          maxSize: '20m', // 设置日志文件的最大大小,m 表示 mb 。
          maxFiles: '14d', // 保留日志文件的最大天数,此处表示自动删除超过 14 天的日志文件。
          // 记录时添加时间戳信息
          format: winston.format.combine(
            winston.format.timestamp({
              format: 'YYYY-MM-DD HH:mm:ss',
            }),
            winston.format.json(),
          ),
        }),
      ],
    }),
  ],
  controllers: [AppController, CatsController],
  providers: [AppService],
})
export class AppModule { }

image-20230508172830549

MqPublistModule 模块配置

mq-publist.module.ts

import { Module } from '@nestjs/common';
import { RabbitMQModule, MessageHandlerErrorBehavior } from '@golevelup/nestjs-rabbitmq';
import { MqPublistService } from './mq-publist.service';

@Module({
  imports: [
    RabbitMQModule.forRootAsync(RabbitMQModule, {
      useFactory: () => {
        return {
          // 交换机配置
          exchanges: [
            {
              // 交换机名称
              name: `exchanges_test`,
              /**
               * 交换机类型
               * direct: 直连交换机,根据消息的路由键(routing key)将消息发送到一个或多个绑定的队列。
                  fanout: 扇形交换机,将消息广播到所有绑定的队列,无需指定路由键。
                  topic: 主题交换机,根据消息的路由键模式匹配将消息发送到一个或多个绑定的队列。
                  headers: 头交换机,根据消息的头部信息将消息发送到一个或多个绑定的队列。
               */
              type: 'direct',
              // 其他选项
              // 持久化(Durable): 指定交换机、队列或消息是否需要在服务器重启后保留
              options: { durable: false },
            },
          ],
          // 连接的url
          uri: 'amqp://admin:admin@localhost:5672',
          /**
           * 用于配置 RabbitMQ 连接的选项。它是一个对象,可以包含以下属性:
            wait: 一个布尔值,表示是否等待连接成功后才开始启动应用程序。默认为 true。
            rejectUnauthorized: 一个布尔值,表示是否拒绝不受信任的 SSL 证书。默认为 true。
            timeout: 一个数字,表示连接超时时间(以毫秒为单位)。默认为 10000 毫秒。
            heartbeatIntervalInSeconds: 一个数字,表示心跳间隔时间(以秒为单位)。默认为 60 秒。
            channelMax: 一个数字,表示最大通道数。默认为 65535。
            这些选项将影响 RabbitMQ 连接的行为和性能。您可以根据需要进行调整
           */
          connectionInitOptions: { wait: false },
          /**
           * 用于启用直接回复模式。当设置为 true 时,
           * 生产者将使用 replyTo 和 correlationId 字段指定的队列和标识符来接收响应,
           * 而不是使用默认生成的匿名队列。这使得消费者可以将响应直接发送到请求者所在的队列,
           * 从而避免了性能上的开销和消息传递中断的问题。
           * 
           * 这里设置为false
           */
          enableDirectReplyTo: false,
          // 通道的默认预取计数。
          prefetchCount: 300,
          /**
          用于配置 RabbitMQ 消费者订阅的默认错误处理行为选项。
          当消费者处理消息时出现错误时,可以使用该选项来指定消费者应如何处理这些错误。
            MessageHandlerErrorBehavior.ACK 表示在发生错误时自动确认消息并从队列中删除
            以避免消息反复传递和死信队列的问题。
            如果您想要更多的控制权来处理错误,可以将其设置为 
            MessageHandlerErrorBehavior.NACK,然后手动决定是否重新排队或丢弃该消息。
           */
          defaultSubscribeErrorBehavior: MessageHandlerErrorBehavior.ACK,
        };
      },
    }),
  ],
  providers: [MqPublistService],
  exports: [MqPublistService],
})
export class MqPublistModule {}

image-20230508173027708

MqPublistService 封装

mq-publist.service.ts

import { AmqpConnection } from '@golevelup/nestjs-rabbitmq';
import { Inject, Injectable, OnModuleInit } from '@nestjs/common';
import { WINSTON_MODULE_NEST_PROVIDER, WINSTON_MODULE_PROVIDER } from 'nest-winston';
import { Logger } from 'winston';

@Injectable()
export class MqPublistService implements OnModuleInit {
  constructor(
    @Inject(WINSTON_MODULE_PROVIDER) private readonly logger: Logger,
    private readonly amqp: AmqpConnection
  ) {}

  /**
   * onModuleInit 是 NestJS 中一个生命周期钩子方法,
   * 它是 @nestjs/common 模块提供的 OnModuleInit 接口的一部分。
   * 实现了该接口并实现了 onModuleInit 方法的类,在模块加载时会自动执行该方法
   */
  async onModuleInit() {
    // 启动监听
    this.monitorConn();
  }

  /**
   * rabbitmq连接监听
   */
  monitorConn() {
    const conn = this.amqp.managedConnection;
    if (conn) {
      conn.on('connect', () => {
        this.logger.info('rabbitmq broker connect');
      });
      conn.on('disconnect', () => {
        this.logger.error('rabbitmq broker disconnect');
      });
    }
    const chan = this.amqp.managedChannel;
    if (chan) {
      chan.on('connect', () => {
        this.logger.info('rabbitmq channel connect');
      });
      chan.on('error', () => {
        this.logger.error('rabbitmq channel error');
      });
      chan.on('close', () => {
        this.logger.error('rabbitmq channel close');
      });
    }
  }

  // exchange
  private readonly exc_test = `exchanges_test`;
   // routingKey
  private readonly routingKey_test = 'routingKey_test';

  /**
   * rabbitmq发送消息
   * @param message
   */
  async pubMQMsgTest(message: any): Promise<void> {
    await this.amqp.publish(this.exc_test, this.routingKey_test, message);
    this.logger.info(
      `amqp publish message -> exchange : ${this.exc_test}, routingKey : ${this.routingKey_test},message : ${JSON.stringify(
        message,
      )}`,
    );
  }
}

image-20230508173153066

其他模块中使用

import { MqPublistService } from '../mq-publist/mq-publist.service';

constructor(
    private readonly mqPublishService: MqPublistService,
  ) { }

// 发送 RabbitMQ 消息
this.mqPublishService.pubMQMsgTest('test send push RabbitMQ');

image-20230508173505452

RabbitMQ 管理页面中查看

image-20230508173558842

单击队列名直接跳转到对应的队列

image-20230508173634690

image-20230508173756729

NestJs RabbitMQ 订阅队列消息_案例

nest g mo mqSubscribe
nest g s mqSubscribe

image-20230508175258258

image-20230508185755082

MqSubscribeModule

mq-subscribe.module.ts

import { Module } from '@nestjs/common';
import { MqSubscribeService } from './mq-subscribe.service';

@Module({
  providers: [MqSubscribeService]
})
export class MqSubscribeModule {}

image-20230508185850275

MqSubscribeService

mq-subscribe.service.ts

import { Inject, Injectable } from '@nestjs/common';
import { WINSTON_MODULE_PROVIDER } from 'nest-winston';
import { RabbitSubscribe } from '@golevelup/nestjs-rabbitmq';
import { Logger } from 'winston';

@Injectable()
export class MqSubscribeService {
  constructor(
    @Inject(WINSTON_MODULE_PROVIDER) private readonly logger: Logger,
  ) { }

  @RabbitSubscribe({
    // 交换机
    exchange: `exchanges_test`,
    routingKey: [
      'routingKey_test',
    ],
    // 队列
    queue: `queue_test`,
    // 持久化配置
    queueOptions: { durable: true },
  })
  // 收到队列的订阅消息自动调用该方法
  async subscribe(data: any): Promise<any> {
    const routingKey = arguments[1].fields.routingKey;
    console.log('arguments[1].fields.exchange :', arguments[1].fields.exchange);
    console.log('routingKey :', routingKey);
    console.log('data:', data);
    this.logger.info(
      `amqp receive msg,exchange is ${arguments[1].fields.exchange},routingKey is ${routingKey},msg is ${JSON.stringify(
        data,
      )}`,
    );
  }
}

image-20230508185924832

使用上面的发送消息再次访问

http://localhost:3000/p

image-20230508190030734

标签:队列,winston,rabbitmq,NestJs,使用,import,RabbitMQ,nestjs
From: https://www.cnblogs.com/makalochen/p/17382858.html

相关文章

  • 实践|如何使用云函数去实现短信验证码功能
    目前,短信验证码广泛应用于用户注册、密码找回、登录保护、身份认证、随机密码、交易确认等应用场景。本文以使用云函数开发一个短信验证码登录注册服务为例,帮助您了解如何实现短信验证码功能。准备工作已 注册腾讯云 账号,并完成 企业实名认证。已购买短信套餐包。准备短信......
  • 【Oracle】使用xmlagg(xmlparse(content()).getclobval()拼接信息
    使用xmlagg(xmlparse(content()).getclobval()拼接信息简单来说格式如下xmlagg(xmlparse(content(内容||分割符)).getclobval()内容就是使用显示的数据部分,分隔符不同效果不同,分隔符可以使用chr()函数无分隔符xmlagg(xmlparse(content('这是一个字段:'||v.supercode||'、......
  • Java 中的机器学习正在加速图像处理 Java 开发人员可以使用预训练的机器学习模型快速
    来源: https://www.infoworld.com/article/3601711/machine-learning-in-java-is-speeding-image-processing.html 近年来,人们对机器学习的兴趣稳步增长。具体来说,企业现在在各种用例中使用机器学习进行图像识别。在 汽车行业、 医疗保健、 安全、 零售、 仓库中的自动化......
  • 关于Curl命令的使用
    最常用的curl命令1、发送GET请求curlURL例:curlURL?a=1&b=nihao2、发送POST请求curl-XPOST-d'a=1&b=nihao'URL3、发送json格式请求curl-H"Content-Type:application/json"-XPOST-d'{"abc":123,"bcd":"nihao"}......
  • 2023最新版——新手使用mybatis-plus 3.5.2并使用器代码生成器
    最新版——新手使用mybatis-plus3.5.2并使用器代码生成器第一步,pom文件引入依赖主要引入mybatis-plus和代码生成器需要使用的freemaker依赖<dependency> <groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>3.5.2</vers......
  • Linux使用源安装nginx
    1.安装依赖包##一键安装上面四个依赖yum-yinstallgcczlibzlib-develpcre-developensslopenssl-devel2.下载并解压安装包//创建一个文件夹cd/usr/localmkdirnginxcdnginx//下载tar包wgethttp://nginx.org/download/nginx-1.21.6.tar.gztar-xvfnginx-1.21.......
  • 使用Webstrom自动编译SASS/SCSS为CSS
    sass基于Ruby语言开发而成,因此安装sass前需要安装Ruby。(注:mac下自带Ruby无需在安装Ruby!)window下安装SASS首先需要安装Ruby,先从官网下载Ruby并安装。安装过程中请注意勾选AddRubyexecutablestoyourPATH添加到系统环境变量。Ruby官方下载地址安装完成后需测试安装有没有......
  • ChatGpt 使用体验
    一.回答随机性太高由于问题的答案天生具有多样性,因此对于不同的ChatGPT客户端,温度参数不一致导致返回的答案可能大相径庭,实际使用中的例子如下:prompt我的前端项目采用vue3+vite+element-plus开发,在一个el-table中有一列属性是创建人,关键代码如下,,我希望当我用鼠标点击到这个值......
  • nas各种共享访问协议的使用(smb,nfs,ftp,ftps,sftp,afp,webdav)
    使用群晖、UNRAID、FREENAS等NAS系统的小伙伴肯定会有传输文件的需求,无论是在本地局域网还是远端设备,这种情况下当然可以使用群晖的WEB管理界面中FileStation,但是这种方式便捷性不够,于是nas与本地设备文件的传输最好的方式是挂载群晖的空间,挂载群晖空间的方式有非常多。主要有......
  • 更新macOS系统后,使用gcc/g++命令,提示错误xcrun: error: invalid active developer pat
      更新macOS系统后,使用gcc/g++命令编译程序,提示错误xcrun:error:invalidactivedeveloperpath(/Library/Developer/CommandLineTools),missingxcrunat:/Library/Developer/CommandLineTools/usr/bin/xcrun解决方法:重新安装CommandLineTools,一般安装完成后问题就能......