Midway.js 使用RabbitMQ的使用过程
前言
这段时间在做公司的一个项目,这个项目是基于midway.js为架构的一个项目。 主要是基于typescript的一个nodejs的web框架。 因为要用到rabbitmq,所以,在midway官网上调试了rabbitmq相关的demo,发现demo会出现生成者能够正常生产消息,但是消费者订阅后并没有收到生产者发出的消息的bug,所以经过我的的思考和查阅了相当大的一部分资料,顺利的吧把demo跑通了。以下就是我解决的一下方法和思路。
1. 安装
前提是已经部署好了rabbitmq服务和midway 1.1 安装依赖
$ cnpm i @midwayjs/rabbitmq amqplib --save
$ cnpm i @types/amqplib --save-dev
1.2 创建服务要使用的文件
以上 server/index.js 消费者 用来监听rabbitmq中的队列, 直接用node运行
src/consumer/userConsumer.ts 不过没有用到这个文件
src/service/rabbitmq.ts rabbitmq服务
src/controller/home.ts 本身文件就有, 用这个文件来发起请求
src/config 配置文件,配置rabbitmq的链接信息
2. 使用
server/index.js
const amqp = require("amqplib/callback_api")
// 创建连接
amqp.connect('amqp://localhost', (err, conn) => {
// 创建通道
conn.createChannel((err, ch) => {
// 定义队列的名称
const q = 'task_queue'
// Declare the queue 声明队列
ch.assertQueue(q, { durable: true })
// Wait for Queue Messages 监听消息
console.log(` [*] Waiting for messages in ${q}. To exit press CTRL+C`)
ch.consume( q, msg => {
console.log(` [x] Received ${msg.content.toString()}`)
}, { noAck: true }
)
})
})
src/consumer/userConsumer.ts
没啥用,现在这里埋个坑
src/service/rabbitmq.ts
这里我没有根据官方的demo去写,自己重新定义了一个类
import {Provide, Scope, ScopeEnum, Init, Config} from "@midwayjs/decorator";
import { Connection, Channel, Message, Options } from 'amqplib';
import * as rabbitmq from 'amqplib';
interface IRabbitConf {
hostname: string;
prot: number;
username: string;
password: string;
queueName: string[];
}
@Scope(ScopeEnum.Singleton) // singleon 单例模式 全局唯一 ,进程级别
@Provide('rabbitmqService')
export class RabbitmqService {
public config: Options.Connect;
public connection: Connection;
public channel: Channel;
private isAsert: Record<string, boolean>;
private queueNames: string[];
private isCheck: boolean;
// 注意这个地方, 使用的是config文件中的配置,所以记得要在config中配置
@Config('rabbit')
rabbitConf: IRabbitConf;
@Init()
async connect() {
console.log("正在连接rabbitmq")
this.config = Object.assign(
{
protocol: 'amqp',
hostname: 'localhost',
port: 5672,
username: 'fgc1101',
password: 'fgc19941030',
frameMax: 0,
heartbeat: 0,
vhost: '/',
},
this.rabbitConf
);
console.log(this.rabbitConf)
this.connection = await rabbitmq.connect(this.config);
await this.creatChannel();
this.queueNames = this.rabbitConf.queueName || [];
this.isAsert = {};
this.isCheck = false;
}
async checkQueue() {
if (!this.channel) {
await this.creatChannel();
}
for (const queueName of this.queueNames) {
const checkQueue = await this.channel.checkQueue(queueName);
if (checkQueue?.queue === queueName) this.isAsert[queueName] = true;
}
this.isCheck = true;
}
close() {
this.connection.close();
}
async creatChannel() {
this.channel = await this.connection.createChannel();
}
async push(
queueName: string,
msg: string,
option?: { priority?: number; durable?: boolean; siId?: string }
) {
const options = {
priority: 0,
durable: true,
headers: { time: 0, siId: option?.siId },
...option,
};
if (!this.queueNames.includes(queueName)){
console.log(`you did not define default queueName ${queueName}`)
return new Error(`you did not define default queueName ${queueName}`);
}
if (!this.isCheck) {
await this.checkQueue();
}
if (!this.isAsert[queueName]) {
this.channel.assertQueue(queueName, { durable: options.durable });
this.isAsert[queueName] = true;
}
this.channel.sendToQueue(queueName, Buffer.from(msg), options);
}
async pull(queueName: string): Promise<false | Message> {
if (!this.queueNames.includes(queueName))
new Error(`you did not default queueName ${queueName}`);
if (!this.isCheck) {
await this.checkQueue();
}
if (!this.isAsert[queueName]) {
await this.channel.assertQueue(queueName, { durable: true });
await this.channel.prefetch(1);
this.isAsert[queueName] = true;
}
return this.channel.get(queueName, { noAck: false });
}
async repush(msg: Message) {
const { fields, properties, content } = msg;
if (!this.isCheck) {
await this.checkQueue();
}
if (!this.isAsert[fields.routingKey]) {
this.channel.assertQueue(fields.routingKey, { durable: true });
this.channel.prefetch(1);
this.isAsert[fields.routingKey] = true;
}
const { headers } = properties;
this.channel.ack(msg);
headers.time++;
this.channel.sendToQueue(fields.routingKey, content, properties);
}
ack(msg: Message) {
this.channel.ack(msg);
}
nack(msg: Message) {
this.channel.nack(msg);
}
}
src/controller/home.ts
import {Controller, Get, Inject, Provide} from '@midwayjs/decorator';
import {RabbitmqService} from "../service/rabbitmq";
@Provide()
@Controller('/')
export class HomeController {
@Inject()
rabbitmqService : RabbitmqService
@Get('/')
async home() {
console.log("测试连接rabbitmq服务")
await this.rabbitmqService.push('task_queue', 'world typeScript');
return {success: true, message: 'OK', data: {}};
}
}
src/config/config.default.ts
export default (appInfo: EggAppInfo) => {
// 数据库配置
config.sequelize = {
...
}
config.rabbit = {
hostname: '127.0.0.1',
prot: 5672,
username: 'fgc1101',
password: 'fgc19941030',
queueName: ['task_queue']
}
return config;
}
3. 注意点
3.1 配置文件不要忘记写
3.2 监听队列的文件可以直接用js写
4. 测试
4.1 启动服务
npm run dev
4.2 根据控制器访问路径
4.3 进入的Server目录下执行消费者服务的代码
node index.js