├── .nvmrc
├── docs
├── plugins
│ ├── WxLogsPlugin.md
│ ├── index.md
│ └── IframePlugin.md
├── internal
│ ├── termui.png
│ ├── internal.png
│ ├── logs_sys_dev.md
│ ├── termui.md
│ └── index.md
├── operators
│ ├── resizeObserver.md
│ ├── task.md
│ ├── newsTime.md
│ ├── debounceTime.md
│ ├── throttleTime.md
│ ├── map.md
│ ├── interval.md
│ └── index.md
├── nav.md
├── other.md
└── unmq.md
├── src
├── plugins
│ ├── router
│ │ └── index.ts
│ ├── pipeline
│ │ └── index.ts
│ ├── websocket
│ │ └── index.ts
│ ├── process
│ │ └── index.ts
│ ├── tabs
│ │ └── index.ts
│ ├── index.ts
│ ├── storage
│ │ ├── StorageAdapterAbstract.ts
│ │ ├── StorageSignAbstract.ts
│ │ ├── storageTypeof.ts
│ │ └── index.ts
│ ├── store
│ │ └── index.ts
│ ├── wx-logs
│ │ ├── config.ts
│ │ ├── listener.ts
│ │ ├── proxyApi.ts
│ │ └── index.ts
│ └── iframe
│ │ └── index.ts
├── adapter
│ ├── VuexStorageAdapter.ts
│ └── PiniaStorageAdapter.ts
├── utils
│ ├── types.ts
│ └── tools.ts
├── operators
│ ├── task
│ │ └── index.ts
│ ├── filter
│ │ └── index.ts
│ ├── of
│ │ └── index.ts
│ ├── newsTime
│ │ └── index.ts
│ ├── map
│ │ └── index.ts
│ ├── instant
│ │ └── index.ts
│ ├── removeDuplicates
│ │ └── index.ts
│ ├── index.ts
│ ├── resizeObserver
│ │ └── index.ts
│ ├── throttleTime
│ │ └── index.ts
│ ├── debounceTime
│ │ └── index.ts
│ └── interval
│ │ └── index.ts
├── internal
│ ├── News.ts
│ ├── Queue
│ │ ├── operators.ts
│ │ └── index.ts
│ ├── Consumer.ts
│ ├── Exchange.ts
│ └── Logs.ts
├── index.ts
└── core
│ ├── ExchangeCollectionHandle.ts
│ ├── QueueCollectionHandle.ts
│ ├── SingleUNodeMQ.ts
│ ├── QuickUNodeMQ.ts
│ ├── UNodeMQ.ts
│ └── Collection.ts
├── _config.yml
├── .eslintignore
├── .vscode
├── extensions.json
├── settings.json
└── launch.json
├── .gitignore
├── termui
├── src
│ ├── util
│ │ └── util.go
│ ├── view
│ │ ├── tabsView.go
│ │ ├── header.go
│ │ ├── newsView.go
│ │ ├── consumerView.go
│ │ ├── exchangeView.go
│ │ ├── queueView.go
│ │ └── view.go
│ ├── data
│ │ ├── abstract.go
│ │ ├── newsTable.go
│ │ ├── consumerTable.go
│ │ ├── queueTable.go
│ │ └── exchangeTable.go
│ └── controller
│ │ └── controller.go
├── main.go
├── go.mod
└── go.sum
├── jest.config.js
├── script
├── clear.js
├── dts-bundle-generator.js
├── build.js
└── publish.js
├── .eslintrc.cjs
├── test
├── operators
│ ├── instant.test.ts
│ ├── of.test.ts
│ ├── task.test.ts
│ ├── filter.test.ts
│ ├── map.test.ts
│ ├── removeDuplicates.test.ts
│ ├── newsTime.test.ts
│ ├── debounceTime.test.ts
│ ├── throttleTime.test.ts
│ └── interval.test.ts
├── SingleUNodeMQ.test.ts
├── QuickUNodeMQ.test.ts
└── UNodeMQ.test.ts
├── .prettierrc
├── .github
└── ISSUE_TEMPLATE
│ ├── feature_request.md
│ └── bug_report.md
├── tsconfig.json
├── LICENSE
├── todo.md
├── package.json
├── README.md
└── CODE_OF_CONDUCT.md
/.nvmrc:
--------------------------------------------------------------------------------
1 | v16
2 |
--------------------------------------------------------------------------------
/docs/plugins/WxLogsPlugin.md:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/plugins/router/index.ts:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/plugins/pipeline/index.ts:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/plugins/websocket/index.ts:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/_config.yml:
--------------------------------------------------------------------------------
1 | theme: jekyll-theme-architect
--------------------------------------------------------------------------------
/src/plugins/process/index.ts:
--------------------------------------------------------------------------------
1 | console.log("test");
2 |
--------------------------------------------------------------------------------
/src/plugins/tabs/index.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * TODO:使用BroadcastChannel进行浏览器tabs同域通信
3 | */
--------------------------------------------------------------------------------
/docs/internal/termui.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Juaoie/u-node-mq/HEAD/docs/internal/termui.png
--------------------------------------------------------------------------------
/src/adapter/VuexStorageAdapter.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * 适配vuex 的storage存储
3 | * 返回modules 集成进去
4 | */
5 |
--------------------------------------------------------------------------------
/docs/internal/internal.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Juaoie/u-node-mq/HEAD/docs/internal/internal.png
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | .git
2 | .vscode
3 | dist
4 | docs
5 | test-node
6 | node_modules
7 | u-node-mq
8 | packages
--------------------------------------------------------------------------------
/.vscode/extensions.json:
--------------------------------------------------------------------------------
1 | {
2 | "recommendations": [
3 | "dbaeumer.vscode-eslint",
4 | "esbenp.prettier-vscode",
5 | ]
6 | }
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | dist
3 | index.js
4 | index.js.map
5 | u-node-mq
6 | *.tgz
7 | vendor
8 | *.log
9 | *.exe
10 | u-node-mq-termui
--------------------------------------------------------------------------------
/termui/src/util/util.go:
--------------------------------------------------------------------------------
1 | package util
2 |
3 | import "time"
4 |
5 | var CstSh, _ = time.LoadLocation("Asia/Shanghai") //上海
6 | var FormatStamp = "15:04:05.000"
7 |
--------------------------------------------------------------------------------
/jest.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import('ts-jest/dist/types').InitialOptionsTsJest} */
2 |
3 | export default {
4 | preset: "ts-jest",
5 | testEnvironment: "jsdom",
6 | timers: "fake",
7 | };
8 |
--------------------------------------------------------------------------------
/docs/operators/resizeObserver.md:
--------------------------------------------------------------------------------
1 |
🚲 resizeObserver 监听 Element 的内容区域或 SVGElement的边界框改变
2 |
3 | ```javascript
4 | const singleUnmq = createSingleUnmq().add(resizeObserver());
5 | ```
6 |
--------------------------------------------------------------------------------
/docs/operators/task.md:
--------------------------------------------------------------------------------
1 | 🏆 task 设置队列能加入消息的数量
2 |
3 | ```javascript
4 | const quickUnmq = createQuickUnmq(new Exchange({ routes: ["qu1"] }), {
5 | qu1: new Queue().add(task(2)),
6 | });
7 | ```
8 |
--------------------------------------------------------------------------------
/termui/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "u-node-mq-termui/src/controller"
5 | "u-node-mq-termui/src/view"
6 | )
7 |
8 | func main() {
9 | go controller.SetQueueData()
10 | view.Init()
11 | }
12 |
--------------------------------------------------------------------------------
/docs/operators/newsTime.md:
--------------------------------------------------------------------------------
1 | 🚲 newsTime 设置消息最长存活时长
2 |
3 | ```javascript
4 | const quickUnmq = createQuickUnmq(new Exchange({ routes: ["qu1"] }), {
5 | qu1: new Queue().add(newsTime(3000)),
6 | });
7 | ```
8 |
--------------------------------------------------------------------------------
/docs/operators/debounceTime.md:
--------------------------------------------------------------------------------
1 | 🚴 debounceTime 防抖功能
2 |
3 | ```javascript
4 | const quickUnmq = createQuickUnmq(new Exchange({ routes: ["qu1"] }), {
5 | qu1: new Queue().add(debounceTime(1000, true)),
6 | });
7 | ```
8 |
--------------------------------------------------------------------------------
/docs/operators/throttleTime.md:
--------------------------------------------------------------------------------
1 | 🎾 throttleTime 节流功能
2 |
3 | ```javascript
4 | const quickUnmq = createQuickUnmq(new Exchange({ routes: ["qu1"] }), {
5 | qu1: new Queue().add(throttleTime(1000, true)),
6 | });
7 | ```
8 |
--------------------------------------------------------------------------------
/termui/src/view/tabsView.go:
--------------------------------------------------------------------------------
1 | package view
2 |
3 | import (
4 | ui "github.com/gizak/termui/v3"
5 | )
6 |
7 | //渲染tabs表格
8 | func renderTabsTable() {
9 | w, _ := ui.TerminalDimensions()
10 | //tabs切换
11 | tabs.Border = false
12 | tabs.SetRect(0, 1, w, 2)
13 | ui.Render(tabs)
14 | }
15 |
--------------------------------------------------------------------------------
/src/utils/types.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * 定时器id类型
3 | * 需要兼容dom,node,小程序环境
4 | */
5 | export type IntTime = number;
6 |
7 | /**
8 | * 五个组件名称的枚举
9 | */
10 | export enum ComponentEnum {
11 | "EXCHANGE" = "exchange",
12 | "QUEUE" = "queue",
13 | "NEWS" = "news",
14 | "CONSUMER" = "consumer",
15 | "LOGS" = "logs",
16 | }
17 |
--------------------------------------------------------------------------------
/docs/operators/map.md:
--------------------------------------------------------------------------------
1 | 🌞 map 对队列消息进行映射
2 |
3 | ```javascript
4 | import UNodeMQ, { Exchange, Queue, ConsumMode, createQuickUnmq, map } from "u-node-mq";
5 |
6 | const quickUnmq = createQuickUnmq(new Exchange({ routes: ["qu1"] }), {
7 | qu1: new Queue().add(map((value, index) => value * 10)),
8 | });
9 | ```
10 |
--------------------------------------------------------------------------------
/src/operators/task/index.ts:
--------------------------------------------------------------------------------
1 | import { Operator } from "../..";
2 |
3 | /**
4 | * task 控制队列能存入几条消息
5 | * @param count
6 | * @returns
7 | */
8 | export default function task(count: number): Operator {
9 | let seen = 0;
10 | return {
11 | beforeAddNews() {
12 | return ++seen <= count;
13 | },
14 | };
15 | }
16 |
--------------------------------------------------------------------------------
/script/clear.js:
--------------------------------------------------------------------------------
1 | // const chalk = require("chalk");
2 | // const fs = require("fs-extra");
3 |
4 | import chalk from "chalk";
5 | import fs from "fs-extra";
6 |
7 | (async () => {
8 | await Promise.all([fs.remove("u-node-mq"), fs.remove("dist"), fs.remove("termui/u-node-mq-termui.exe")]);
9 | console.log(chalk.blue("缓存清除成功!"));
10 | })();
11 |
--------------------------------------------------------------------------------
/src/operators/filter/index.ts:
--------------------------------------------------------------------------------
1 | import { Operator, News } from "../..";
2 |
3 | /**
4 | * filter 过滤
5 | * @param fun
6 | * @returns boolean 返回值控制是否加入队列
7 | */
8 | export default function filter(fun: (res: D) => boolean | Promise): Operator {
9 | return {
10 | beforeAddNews(res: News) {
11 | return fun(res.content);
12 | },
13 | };
14 | }
15 |
--------------------------------------------------------------------------------
/src/operators/of/index.ts:
--------------------------------------------------------------------------------
1 | import { Operator, Queue } from "../..";
2 |
3 | /**
4 | * 提前发射数据,可用来设置默认值或者用来快速测试
5 | * @param args
6 | * @returns
7 | */
8 | export default function of(...args: D[]): Operator {
9 | return {
10 | mounted(queue: Queue) {
11 | args.forEach(item => {
12 | queue.pushContent(item);
13 | });
14 | },
15 | };
16 | }
17 |
--------------------------------------------------------------------------------
/src/operators/newsTime/index.ts:
--------------------------------------------------------------------------------
1 | import { Operator } from "../..";
2 | import News from "../../internal/News";
3 |
4 | /**
5 | * newsTime 设置消息存活时长
6 | * @param time 存活时长毫秒数
7 | * @returns
8 | */
9 | export default function newsTime(time: number): Operator {
10 | return {
11 | ejectNews(news: News) {
12 | return new Date().getTime() < time + news.createdTime;
13 | },
14 | };
15 | }
16 |
--------------------------------------------------------------------------------
/src/plugins/index.ts:
--------------------------------------------------------------------------------
1 | import IframePlugin from "./iframe/index";
2 | import WxLogsPlugin from "./wx-logs/index";
3 | export { IframePlugin, WxLogsPlugin };
4 |
5 | /**
6 | * 安装插件的方法
7 | */
8 | export type PluginInstallFunction = (unmq: any, ...options: any[]) => void;
9 | export type Plugin =
10 | | (PluginInstallFunction & { install?: PluginInstallFunction })
11 | | {
12 | install: PluginInstallFunction;
13 | };
14 |
--------------------------------------------------------------------------------
/termui/src/view/header.go:
--------------------------------------------------------------------------------
1 | package view
2 |
3 | import (
4 | ui "github.com/gizak/termui/v3"
5 | )
6 |
7 | //渲染头部
8 | func renderHeaderTable() {
9 | w, _ := ui.TerminalDimensions()
10 | //头部提示
11 |
12 | header.Text = `q:退出 <-:tabs左移 ->:tabs右移 c:清空 ctrl+c:清空所有 监听端口号:9090`
13 | header.SetRect(0, 0, w, 1)
14 | header.Border = false
15 | header.TextStyle.Bg = ui.ColorBlue
16 |
17 | ui.Render(header)
18 | }
19 |
--------------------------------------------------------------------------------
/src/operators/map/index.ts:
--------------------------------------------------------------------------------
1 | import { Operator } from "../..";
2 | /**
3 | * map 方法返回的数据类型和队列一致
4 | * @param project
5 | * @returns
6 | */
7 |
8 | export default function map(project: (value: D, index: number) => D): Operator {
9 | let index = 0;
10 | return {
11 | beforeAddNews(num) {
12 | num.content = project(num.content, index);
13 | index++;
14 | return true;
15 | },
16 | };
17 | }
18 |
--------------------------------------------------------------------------------
/src/operators/instant/index.ts:
--------------------------------------------------------------------------------
1 | import { Operator, Queue, ConsumMode } from "../../index";
2 |
3 | /**
4 | * 扩展为观察者模式,即在同一事件循环内消费所有消息
5 | * 使用off方法可以移除此属性,开发时请注意协调
6 | * @returns
7 | */
8 | export default function instant(): Operator {
9 | return {
10 | mounted(that: Queue) {
11 | if (that.mode !== ConsumMode.All) throw `${that.name} 队列 mode 需要设置成All`;
12 | that.pushConsume(() => true);
13 | },
14 | };
15 | }
16 |
--------------------------------------------------------------------------------
/termui/src/data/abstract.go:
--------------------------------------------------------------------------------
1 | package data
2 |
3 | //表接口
4 | type TableOper interface {
5 | add(TableField) bool
6 | dele()
7 | set()
8 | }
9 |
10 | //公共字段
11 | type BaseLogData struct {
12 | Id string `json:"id"`
13 | CreatedTime int64 `json:"createdTime"` //毫秒时间戳
14 | Name string `json:"name"`
15 | }
16 |
17 | //表公共字段
18 | type TableField struct {
19 | BaseLogData
20 | UpdateTime int64 `json:"updateTime"` //最后更新时间
21 | }
22 |
--------------------------------------------------------------------------------
/src/plugins/storage/StorageAdapterAbstract.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * 内存存储的中间适配器
3 | */
4 | export default abstract class StorageAdapterAbstract {
5 | /**
6 | * 初始化数据
7 | * @param o
8 | */
9 | abstract init(o: Record): void;
10 | /**
11 | * 获取数据
12 | * @param key
13 | */
14 | abstract getData(key: string): string;
15 | /**
16 | * 设置数据
17 | * @param key
18 | * @param value
19 | */
20 | abstract setData(key: string, value: string): void;
21 | }
22 |
--------------------------------------------------------------------------------
/src/plugins/storage/StorageSignAbstract.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * storage加密适配器
3 | */
4 | export default abstract class StorageSignAbstract {
5 | /**
6 | * 加密名称
7 | * @param plaintext
8 | */
9 | abstract encryptName(plaintext: string): string;
10 | /**
11 | * 加密值
12 | * @param plaintext
13 | */
14 | abstract encryptValue(plaintext: string): string;
15 | /**
16 | * 解密值
17 | * @param ciphertext
18 | */
19 | abstract decryptValue(ciphertext: string): string;
20 | }
21 |
--------------------------------------------------------------------------------
/.eslintrc.cjs:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | env: {
3 | node: false,
4 | browser: true,
5 | },
6 | extends: ["plugin:@typescript-eslint/recommended", "plugin:prettier/recommended"], //定义文件继承的子规范
7 | parser: "@typescript-eslint/parser", //定义ESLint的解析器
8 | plugins: ["@typescript-eslint"], //定义了该eslint文件所依赖的插件
9 |
10 | globals: {},
11 |
12 | rules: {
13 | "@typescript-eslint/no-explicit-any": "off", //允许使用any类型
14 | },
15 | // parserOptions: {
16 | // project: "./tsconfig.json",
17 | // },
18 | };
19 |
--------------------------------------------------------------------------------
/src/plugins/store/index.ts:
--------------------------------------------------------------------------------
1 | import UNodeMQ, { Exchange, Queue } from "../../index";
2 |
3 | export default class StorePlugin>, Record>>> {
4 | private unmq: D | null = null;
5 | private data: Record = {};
6 | defineStore(name: string, defaultValue: T) {
7 | if (this.unmq === null) throw "StorePlugin 未安装";
8 | if (this.data[name]) {
9 | }
10 | }
11 | install(unmq: D) {
12 | this.unmq = unmq;
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/test/operators/instant.test.ts:
--------------------------------------------------------------------------------
1 | import { Queue } from "../../src/index";
2 | import instant from "../../src/operators/instant";
3 |
4 | import { expect, test } from "@jest/globals";
5 |
6 | test("快速unmq,instant测试", function (done) {
7 | const qu1 = new Queue()
8 | //使用 operate
9 | .add(instant());
10 | setTimeout(() => {
11 | expect(qu1.getNews().length).toEqual(0);
12 | done();
13 | }, 100);
14 | [1, 2, 3, 4, 5, 6].forEach(res => {
15 | qu1.pushContent(res);
16 | });
17 | });
18 |
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "arrowParens": "avoid",
3 | "bracketSpacing": true,
4 | "embeddedLanguageFormatting": "auto",
5 | "htmlWhitespaceSensitivity": "ignore",
6 | "insertPragma": false,
7 | "jsxBracketSameLine": true,
8 | "jsxSingleQuote": false,
9 | "printWidth": 150,
10 | "proseWrap": "preserve",
11 | "quoteProps": "preserve",
12 | "requirePragma": false,
13 | "semi": true,
14 | "singleQuote": false,
15 | "tabWidth": 2,
16 | "trailingComma": "all",
17 | "useTabs": false,
18 | "endOfLine": "auto"
19 | }
20 |
--------------------------------------------------------------------------------
/docs/operators/interval.md:
--------------------------------------------------------------------------------
1 | ## interval
2 |
3 | 在项目中过度使用 setInterval 可能导致一些死循环或者没有 clearInterval 的问题
4 |
5 | 建议在项目中使用 unmq 全局设置一个 interval,再使用 throttleTime 去为每个需要定时循环的的地方做定时数据分发
6 |
7 | 将 interval 第二个参数设置为 true,可以在没有消费者的时候停止循环
8 |
9 | ```javascript
10 | import UNodeMQ, { Exchange, Queue, ConsumMode, createQuickUnmq,interval } from "u-node-mq";
11 |
12 | const quickUnmq = createQuickUnmq(new Exchange(), {
13 | qu1: new Queue()
14 | //使用 operate
15 | .add(interval(1000, false)),
16 | });
17 | ```
18 |
--------------------------------------------------------------------------------
/src/operators/removeDuplicates/index.ts:
--------------------------------------------------------------------------------
1 | import { Operator, News } from "../..";
2 |
3 | /**
4 | * removeDuplicates 去重
5 | * @param fun 根据fun 返回的id进行判断是否需要去重
6 | * @returns
7 | */
8 | export default function removeDuplicates(fun: (res: any) => any): Operator {
9 | const list: any[] = [];
10 | return {
11 | beforeAddNews(res: News) {
12 | const id = fun(res.content);
13 | if (list.indexOf(id) === -1) {
14 | list.push(id);
15 | return true;
16 | } else return false;
17 | },
18 | };
19 | }
20 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "jest.autoRun": "off",
3 | "jest.showCoverageOnLoad": false,
4 | // Use the project's typescript version
5 | "typescript.tsdk": "node_modules/typescript/lib",
6 |
7 |
8 | // Use prettier to format typescript, javascript and JSON files
9 | "[typescript]": {
10 | "editor.defaultFormatter": "esbenp.prettier-vscode"
11 | },
12 | "[javascript]": {
13 | "editor.defaultFormatter": "esbenp.prettier-vscode"
14 | },
15 | "[json]": {
16 | "editor.defaultFormatter": "esbenp.prettier-vscode"
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/operators/index.ts:
--------------------------------------------------------------------------------
1 | import debounceTime from "./debounceTime";
2 | import filter from "./filter";
3 | import instant from "./instant";
4 | import interval from "./interval";
5 | import map from "./map";
6 | import newsTime from "./newsTime";
7 | import of from "./of";
8 | import removeDuplicates from "./removeDuplicates";
9 | import resizeObserver from "./resizeObserver";
10 | import task from "./task";
11 | import throttleTime from "./throttleTime";
12 |
13 | export { debounceTime, filter, instant, interval, map, newsTime, of, removeDuplicates, resizeObserver, task, throttleTime };
14 |
--------------------------------------------------------------------------------
/script/dts-bundle-generator.js:
--------------------------------------------------------------------------------
1 | import { generateDtsBundle } from "dts-bundle-generator";
2 | import fs from "fs-extra";
3 |
4 | const options = [
5 | {
6 | filePath: "src/index.ts",
7 | outFile: "u-node-mq/index.d.ts",
8 | output: {
9 | sortNodes: true,
10 | exportReferencedTypes: false,
11 | },
12 | },
13 | ];
14 |
15 | //generateDtsBundle 不能自动输出,需要手动输出
16 | const res = generateDtsBundle(options, {
17 | preferredConfigPath: "./tsconfig.json",
18 | });
19 |
20 | options.forEach((item, index) => {
21 | fs.outputFile(item.outFile, res[index]);
22 | });
23 |
--------------------------------------------------------------------------------
/test/operators/of.test.ts:
--------------------------------------------------------------------------------
1 | import { Exchange, Queue, createQuickUnmq } from "../../src/index";
2 | import of from "../../src/operators/of";
3 |
4 | import { expect, test } from "@jest/globals";
5 |
6 | test("快速unmq,of测试", function (done) {
7 | const quickUnmq = createQuickUnmq(new Exchange(), {
8 | qu1: new Queue()
9 | //使用 operate
10 | .add(of(1, 2, 3)),
11 | });
12 | let num = "";
13 | quickUnmq.on("qu1", (res: number) => {
14 | num += res;
15 | });
16 | setTimeout(() => {
17 | expect(num).toEqual("123");
18 | done();
19 | });
20 | });
21 |
--------------------------------------------------------------------------------
/docs/nav.md:
--------------------------------------------------------------------------------
1 | # 导航预览
2 |
3 | ## 基础组件和工作原理
4 |
5 | 开发者需要先了解`u-node-mq`中的五大基础组件和工作原理,了解工作原理是灵活使用此工具的关键;
6 |
7 | [点击跳转组件介绍](./internal/index.md)
8 |
9 | ## 使用方法
10 |
11 | 通过对基础组件的封装能够让开发者快速实现需求功能;
12 |
13 | 如果需要一些更加灵活的使用操作,也可以单独去组合组件去实现;
14 |
15 | [点击跳转快速开发介绍](./unmq.md)
16 |
17 | ## 插件功能
18 |
19 | 插件是扩展或者集成`UNodeMQ`类的组件,例如`IframePlugin`实际是集成`UNodeMQ`类的发布订阅模型的工具,所以这里的插件理解为可以快速集成发布订阅模型的工具;
20 |
21 | [点击跳转插件功能](./plugins/index.md)
22 |
23 | ## 管道符操作方法
24 |
25 | 管道符操作方法实际是对队列中的数据进行操作的方法,在消息进入队列和被弹出队列中的生命周期过程中对消息数据进行处理,一些同步操作和异步操作的生命周期极便利的扩展操作符的可操作空间,内置的操作符集合提供了大多数常见的对消息数据进行操作的方法;
26 |
27 | ## 其他示例
28 |
29 | 更多简单示例代码可以在`jest`的测试方法中找到
30 |
--------------------------------------------------------------------------------
/test/operators/task.test.ts:
--------------------------------------------------------------------------------
1 | import { Exchange, Queue, createQuickUnmq } from "../../src/index";
2 | import task from "../../src/operators/task";
3 |
4 | import { expect, test } from "@jest/globals";
5 |
6 | test("快速unmq,task测试", function (done) {
7 | const quickUnmq = createQuickUnmq(new Exchange({ routes: ["qu1"] }), {
8 | qu1: new Queue()
9 | //使用 operate
10 | .add(task(2)),
11 | });
12 | let num = "";
13 | quickUnmq.on("qu1", (res: number) => {
14 | num += res;
15 | });
16 | quickUnmq.emit(1, 2, 3, 4);
17 | setTimeout(() => {
18 | expect(num).toEqual("12");
19 | done();
20 | });
21 | });
22 |
--------------------------------------------------------------------------------
/test/operators/filter.test.ts:
--------------------------------------------------------------------------------
1 | import { Exchange, Queue, createQuickUnmq } from "../../src/index";
2 | import filter from "../../src/operators/filter";
3 |
4 | import { expect, test } from "@jest/globals";
5 |
6 | test("快速unmq,filter测试", function (done) {
7 | const quickUnmq = createQuickUnmq(new Exchange({ routes: ["qu1"] }), {
8 | qu1: new Queue()
9 | //使用 operate
10 | .add(filter(res => res > 3)),
11 | });
12 | let n = "";
13 | quickUnmq.on("qu1", (res: number) => {
14 | n += res;
15 | });
16 | setTimeout(() => {
17 | expect(n).toEqual("456");
18 | done();
19 | }, 100);
20 | quickUnmq.emit(1, 2, 3, 4, 5, 6);
21 | });
22 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature request
3 | about: Suggest an idea for this project
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Is your feature request related to a problem? Please describe.**
11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
12 |
13 | **Describe the solution you'd like**
14 | A clear and concise description of what you want to happen.
15 |
16 | **Describe alternatives you've considered**
17 | A clear and concise description of any alternative solutions or features you've considered.
18 |
19 | **Additional context**
20 | Add any other context or screenshots about the feature request here.
21 |
--------------------------------------------------------------------------------
/test/operators/map.test.ts:
--------------------------------------------------------------------------------
1 | import { Exchange, Queue, createQuickUnmq } from "../../src/index";
2 | import map from "../../src/operators/map";
3 |
4 | import { expect, test } from "@jest/globals";
5 |
6 | test("快速unmq,map测试", function (done) {
7 | const quickUnmq = createQuickUnmq(new Exchange({ routes: ["qu1"] }), {
8 | qu1: new Queue()
9 | //使用 operate
10 | .add(
11 | map((value, index) => {
12 | expect(index).toEqual(0);
13 | return value * 10;
14 | }),
15 | ),
16 | });
17 | quickUnmq.on("qu1", (res: number) => {
18 | expect(res).toEqual(20);
19 | done();
20 | });
21 | quickUnmq.emit(2);
22 | });
23 |
--------------------------------------------------------------------------------
/src/plugins/storage/storageTypeof.ts:
--------------------------------------------------------------------------------
1 | import { isString } from "../../index";
2 | const stringType = "s###";
3 | const nostringType = "n###";
4 |
5 | //编码
6 | export function envalue(value: any): string {
7 | if (isString(value)) return stringType + value;
8 | else return nostringType + JSON.stringify(value);
9 | }
10 |
11 | //解密
12 | export function devalue(value: string) {
13 | const type = value.slice(0, 4);
14 | if (type === stringType) {
15 | return value.slice(4);
16 | } else if (type === nostringType) {
17 | try {
18 | return JSON.parse(value.slice(4));
19 | } catch (error) {
20 | console.log("JSON.pares error");
21 | return "";
22 | }
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/adapter/PiniaStorageAdapter.ts:
--------------------------------------------------------------------------------
1 | import { defineStore, StoreDefinition } from "pinia";
2 | import StorageAdapterAbstract from "../plugins/storage/StorageAdapterAbstract";
3 | /**
4 | * 实现简单状态管理
5 | */
6 |
7 | export default class VueStorageAdapter implements StorageAdapterAbstract {
8 | private storeDefinition: StoreDefinition;
9 | init(o: Record) {
10 | this.storeDefinition = defineStore("__storage", {
11 | state: () => o,
12 | });
13 | }
14 | getData(key: string) {
15 | const store = this.storeDefinition();
16 | return store[key];
17 | }
18 | setData(key: string, value: any) {
19 | const store = this.storeDefinition();
20 | store[key] = value;
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/test/operators/removeDuplicates.test.ts:
--------------------------------------------------------------------------------
1 | import { Exchange, Queue, createQuickUnmq } from "../../src/index";
2 | import removeDuplicates from "../../src/operators/removeDuplicates";
3 |
4 | import { expect, test } from "@jest/globals";
5 |
6 | test("快速unmq,removeDuplicates测试", function (done) {
7 | const quickUnmq = createQuickUnmq(new Exchange({ routes: ["qu1"] }), {
8 | qu1: new Queue()
9 | //使用 operate
10 | .add(removeDuplicates(res => res)),
11 | });
12 | let n = "";
13 | quickUnmq.on("qu1", (res: number) => {
14 | n += res;
15 | });
16 | setTimeout(() => {
17 | expect(n).toEqual("1234");
18 | done();
19 | }, 100);
20 | quickUnmq.emit(1, 2, 3, 3, 4, 4, 4, 4, 4, 4, 1);
21 | });
22 |
--------------------------------------------------------------------------------
/src/plugins/wx-logs/config.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * 日志级别,日志类型,暂就这些类型,不可动态添加
3 | */
4 | export enum LOG_LEVEL {
5 | Info = "info",
6 | Warn = "warn",
7 | Error = "error",
8 | }
9 |
10 | /**
11 | * 输出类型
12 | *
13 | */
14 | export enum OUTPUT_TYPE {
15 | //控制台
16 | Console = "console",
17 | //实时日志
18 | Realtime = "realtime",
19 | //xhr请求
20 | Request = "request",
21 | }
22 |
23 | /**
24 | * 不同日志级别对应日志输出类型的配置
25 | */
26 | export type LevelOutputOption = {
27 | [P in `${LOG_LEVEL}`]: Array<`${OUTPUT_TYPE}`>;
28 | };
29 |
30 | export const defaultOption: LevelOutputOption = {
31 | [LOG_LEVEL.Info]: [OUTPUT_TYPE.Console],
32 | [LOG_LEVEL.Warn]: [OUTPUT_TYPE.Console, OUTPUT_TYPE.Realtime],
33 | [LOG_LEVEL.Error]: [OUTPUT_TYPE.Console, OUTPUT_TYPE.Request, OUTPUT_TYPE.Realtime],
34 | };
35 |
--------------------------------------------------------------------------------
/src/internal/News.ts:
--------------------------------------------------------------------------------
1 | import { random } from "src/utils/tools";
2 | import { ComponentEnum } from "../utils/types";
3 | import Logs from "./Logs";
4 |
5 | export default class News {
6 | [k: string]: any;
7 | /**
8 | * id
9 | */
10 | private readonly id: string = random();
11 | getId() {
12 | return this.id;
13 | }
14 | /**
15 | * 消费者创建时间戳
16 | */
17 | readonly createdTime: number;
18 | /**
19 | * 消息内容
20 | */
21 | content: D;
22 | /**
23 | * 剩余可重复消费次数
24 | */
25 | consumedTimes = -1;
26 |
27 | constructor(content: D) {
28 | this.createdTime = new Date().getTime();
29 | this.content = content;
30 | Logs.getLogsInstance()?.setLogs(ComponentEnum.NEWS, { id: this.getId(), createdTime: this.createdTime });
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/test/operators/newsTime.test.ts:
--------------------------------------------------------------------------------
1 | import { Exchange, Queue, createQuickUnmq } from "../../src/index";
2 | import newsTime from "../../src/operators/newsTime";
3 |
4 | import { expect, test } from "@jest/globals";
5 |
6 | test("快速unmq,newsTime测试", function (done) {
7 | const qu1 = new Queue().add(newsTime(1000)).add(newsTime(1200));
8 | const quickUnmq = createQuickUnmq(new Exchange({ routes: ["qu1"] }), {
9 | qu1,
10 | });
11 | let str = "";
12 | setTimeout(() => {
13 | quickUnmq.once("qu1", () => (str += "a"));
14 | }, 500);
15 | setTimeout(() => {
16 | quickUnmq.once("qu1", () => (str += "b"));
17 | }, 1500);
18 | setTimeout(() => {
19 | expect(str).toEqual("a");
20 | expect(qu1.getNews().length).toEqual(0);
21 | done();
22 | }, 2000);
23 | quickUnmq.emit(1, 2, 3);
24 | });
25 |
--------------------------------------------------------------------------------
/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | // 使用 IntelliSense 了解相关属性。
3 | // 悬停以查看现有属性的描述。
4 | // 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387
5 | "version": "0.2.0",
6 | "configurations": [
7 | {
8 | "type": "node",
9 | "name": "vscode-jest-tests.v2",
10 | "request": "launch",
11 | "args": [
12 | "--runInBand",
13 | "--watchAll=false",
14 | "--testNamePattern",
15 | "${jest.testNamePattern}",
16 | "--runTestsByPath",
17 | "${jest.testFile}"
18 | ],
19 | "cwd": "${workspaceFolder}",
20 | "console": "integratedTerminal",
21 | "internalConsoleOptions": "neverOpen",
22 | "program": "${workspaceFolder}/node_modules/.bin/jest",
23 | "windows": {
24 | "program": "${workspaceFolder}/node_modules/jest/bin/jest"
25 | }
26 | }
27 | ]
28 | }
29 |
--------------------------------------------------------------------------------
/termui/src/view/newsView.go:
--------------------------------------------------------------------------------
1 | package view
2 |
3 | import (
4 | "time"
5 | "u-node-mq-termui/src/data"
6 | "u-node-mq-termui/src/util"
7 |
8 | ui "github.com/gizak/termui/v3"
9 | )
10 |
11 | //渲染news表格
12 | func renderNewsTable() {
13 | list := data.N.FindList()
14 |
15 | arr := [][]string{
16 | {"ID", "CreatedTime", "UpdateTime"},
17 | }
18 |
19 | for _, v := range list {
20 | arr = append(arr, []string{
21 | v.Id,
22 | time.UnixMilli(v.CreatedTime).Format(util.FormatStamp),
23 | time.UnixMilli(v.UpdateTime).Format(util.FormatStamp),
24 | })
25 | }
26 |
27 | w, h := ui.TerminalDimensions()
28 | //交换机表格
29 | newsTable.Border = false
30 | newsTable.ColumnWidths = []int{20, 20, w - 40}
31 | newsTable.Rows = arr
32 | newsTable.TextStyle = ui.NewStyle(ui.ColorWhite)
33 |
34 | newsTable.SetRect(0, 2, w, h)
35 | ui.Render(newsTable)
36 | }
37 |
--------------------------------------------------------------------------------
/test/operators/debounceTime.test.ts:
--------------------------------------------------------------------------------
1 | import { createSingleUnmq } from "../../src/index";
2 | import debounceTime from "../../src/operators/debounceTime";
3 | import { expect, test, jest } from "@jest/globals";
4 | import { promiseSetTimeout } from "../../src/utils/tools";
5 |
6 | jest.useFakeTimers();
7 |
8 | test("debounceTime", async function () {
9 | const singleUnomq1 = createSingleUnmq({ operators: [debounceTime(1000, false)] });
10 |
11 | const callback = jest.fn();
12 | singleUnomq1.emit(1, 2, 3);
13 | // setInterval(() => {
14 | // }, 100);
15 |
16 | singleUnomq1.on(() => {
17 | console.log("-------------");
18 | callback();
19 | });
20 |
21 | await promiseSetTimeout();
22 |
23 | expect(callback).not.toBeCalled();
24 |
25 | jest.runAllTimers();
26 |
27 | expect(callback).toBeCalled();
28 | expect(callback).toHaveBeenCalledTimes(1);
29 | });
30 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "include": [
3 | "src/index.ts",
4 | "src/core/**/*",
5 | "src/internal/**/*",
6 | "src/operators/**/*",
7 | "src/plugins/iframe/**/*",
8 | "src/plugins/wx-logs/**/*"
9 | ],
10 | "exclude": [
11 | "node_modules"
12 | ],
13 | "compilerOptions": {
14 | "skipLibCheck": true,
15 | "target": "ES6",
16 | "module": "ES6",
17 | "moduleResolution": "Node",
18 | "diagnostics": true,
19 | "outDir": "./dist/",
20 | "lib": [
21 | "DOM",
22 | "ESNext"
23 | ],
24 | "declaration": true,
25 | "removeComments": false,
26 | "esModuleInterop": true,
27 | "baseUrl": "./", // 设置基准目录
28 | "paths": {
29 | "@/*": [
30 | "src/*"
31 | ]
32 | },
33 | "strict": true,
34 | "downlevelIteration": true,
35 | "types": [
36 | "miniprogram-api-typings"
37 | ]
38 | }
39 | }
--------------------------------------------------------------------------------
/termui/src/view/consumerView.go:
--------------------------------------------------------------------------------
1 | package view
2 |
3 | import (
4 | "strconv"
5 | "time"
6 | "u-node-mq-termui/src/data"
7 | "u-node-mq-termui/src/util"
8 |
9 | ui "github.com/gizak/termui/v3"
10 | )
11 |
12 | //渲染Consumer表格
13 | func renderConsumerTable() {
14 | list := data.C.FindList()
15 |
16 | arr := [][]string{
17 | {"ID", "CreatedTime", "UpdateTime", "AcceptedCount"},
18 | }
19 |
20 | for _, v := range list {
21 | arr = append(arr, []string{
22 | v.Id,
23 | time.UnixMilli(v.CreatedTime).Format(util.FormatStamp),
24 | time.UnixMilli(v.UpdateTime).Format(util.FormatStamp),
25 | strconv.Itoa(v.AcceptedCount),
26 | })
27 | }
28 |
29 | w, h := ui.TerminalDimensions()
30 | //交换机表格
31 | consumerTable.Border = false
32 | consumerTable.ColumnWidths = []int{20, 20, 20, w - 60}
33 | consumerTable.Rows = arr
34 | consumerTable.TextStyle = ui.NewStyle(ui.ColorWhite)
35 |
36 | consumerTable.SetRect(0, 2, w, h)
37 | ui.Render(consumerTable)
38 | }
39 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: Create a report to help us improve
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Describe the bug**
11 | A clear and concise description of what the bug is.
12 |
13 | **To Reproduce**
14 | Steps to reproduce the behavior:
15 | 1. Go to '...'
16 | 2. Click on '....'
17 | 3. Scroll down to '....'
18 | 4. See error
19 |
20 | **Expected behavior**
21 | A clear and concise description of what you expected to happen.
22 |
23 | **Screenshots**
24 | If applicable, add screenshots to help explain your problem.
25 |
26 | **Desktop (please complete the following information):**
27 | - OS: [e.g. iOS]
28 | - Browser [e.g. chrome, safari]
29 | - Version [e.g. 22]
30 |
31 | **Smartphone (please complete the following information):**
32 | - Device: [e.g. iPhone6]
33 | - OS: [e.g. iOS8.1]
34 | - Browser [e.g. stock browser, safari]
35 | - Version [e.g. 22]
36 |
37 | **Additional context**
38 | Add any other context about the problem here.
39 |
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * 主包
3 | */
4 | import UNodeMQ, { createUnmq } from "./core/UNodeMQ";
5 | export default UNodeMQ;
6 | export { createUnmq };
7 | /**
8 | * 扩展包
9 | */
10 | import SingleUNodeMQ, { createSingleUnmq } from "./core/SingleUNodeMQ";
11 | import QuickUNodeMQ, { createQuickUnmq } from "./core/QuickUNodeMQ";
12 | export { SingleUNodeMQ, createSingleUnmq, QuickUNodeMQ, createQuickUnmq };
13 | /**
14 | * 组件
15 | */
16 | import Exchange, { ExchangeOption } from "./internal/Exchange";
17 | import Queue, { ConsumMode, QueueOption } from "./internal/Queue/index";
18 | import { Operator } from "./internal/Queue/operators";
19 | import Consumer from "./internal/Consumer";
20 | import News from "./internal/News";
21 | import Logs from "./internal/Logs";
22 | export { Exchange, Queue, Consumer, News, Logs };
23 | export { ConsumMode, Operator, ExchangeOption, QueueOption };
24 |
25 | /**
26 | * 管道符
27 | */
28 | export * from "./operators";
29 |
30 | /**
31 | * 插件
32 | */
33 | export * from "./plugins";
34 |
--------------------------------------------------------------------------------
/src/plugins/wx-logs/listener.ts:
--------------------------------------------------------------------------------
1 | // export default function listener() {}
2 | import WxLogsPlugin from "./index";
3 | import { LOG_LEVEL } from "./config";
4 | import { wxApi } from "./proxyApi";
5 |
6 | const t = {
7 | [LOG_LEVEL.Info]: [wxApi("onThemeChange"), wxApi("onNetworkWeakChange"), wxApi("onNetworkStatusChange")],
8 | [LOG_LEVEL.Warn]: [wxApi("onAudioInterruptionEnd"), wxApi("onAudioInterruptionBegin"), wxApi("onMemoryWarning")],
9 | [LOG_LEVEL.Error]: [wxApi("onUnhandledRejection"), wxApi("onPageNotFound"), wxApi("onLazyLoadError"), wxApi("onError")],
10 | };
11 | /**
12 | * 监听系统变化事件
13 | * @param this
14 | */
15 | export function onListener(this: WxLogsPlugin) {
16 | t[LOG_LEVEL.Info].forEach(fun => {
17 | if (fun !== null) fun(this[LOG_LEVEL.Info].bind(this));
18 | });
19 |
20 | t[LOG_LEVEL.Warn].forEach(fun => {
21 | if (fun !== null) fun(this[LOG_LEVEL.Warn].bind(this));
22 | });
23 |
24 | t[LOG_LEVEL.Error].forEach(fun => {
25 | if (fun !== null) fun(this[LOG_LEVEL.Error].bind(this));
26 | });
27 | }
28 |
--------------------------------------------------------------------------------
/docs/other.md:
--------------------------------------------------------------------------------
1 | # 其他知识介绍
2 |
3 | 对其他知识的总结和对项目中的疑问的解答
4 |
5 | ## 方法和函数的区别
6 |
7 | 函数是一段在经过封装在单独作用域中的可执行代码;
8 |
9 | 方法是一段在对象上下文作用域中封装的可执行代码;
10 |
11 | ## 观察者模式和发布订阅模型的区别
12 |
13 | 总是有人问`观察者模式`和`发布订阅模式`有什么区别,网上也有一大堆解释,但是从来都没有统一、官方或者权威的描述,下面将就着我在这方面知识的理解做具体的解释;
14 |
15 | 不是`发布订阅模式`,实际上是`发布订阅模型`,它并不是一种`设计模式`;
16 |
17 | `模型`是一种构造系统架构的具体实施方案,`设计模式`则是一种架构抽象的思维方式;
18 |
19 | **观察者模式**
20 |
21 | `观察者模式`由其名一样,是指 `观察者` 主动观察到 `被观察者` 发生变化以后 `观察者` 自己做出相应操作的设计模式;其中`观察者`仅仅是观察(监听)`被观察者`,并不会干扰`被观察者`的任何状态;
22 |
23 | 例如:工厂里机器操作记录员,记录员仅仅是远远的看着(观察)机器运行,然后将机器的操作记录下来,记录员的任何操作都不会干扰机器的执行,但是机器的不同操作会导致记录员记录下不同的数据;
24 |
25 | **发布订阅模型**
26 |
27 | `发布订阅模型`其中是重点是订阅,`消费者`去订阅消息,`生产者`负责生产消息,通过第三方组件去存储、分发消息到`消费者`;
28 |
29 | 例如:人们去订阅报纸,报社生产报纸,而邮局这样的第三方则来临时存储报纸和准确分发不同的报纸到不同的人家中;
30 |
31 | 结论:观察者模式和发布订阅模型还是有很大区别的,观察者模式被观察者往往是具体的数据,且由观察者直接进行观察,发布订阅模型中的消费者具体的消费方法或者消费函数,并且消费的消息由第三方组件进行分发;
32 |
33 | **简单例子:**
34 |
35 | `观察者模式`:在vue中使用watch中的方法对data中的数据进行观察,在观察到数据变化以后执行相应的代码;
36 |
37 | `发布订阅模型`:使用u-node-mq中的emit发生数据到队列,再使用on方法订阅队列中的数据;
38 |
39 | ## 节流和防抖的区别
40 |
41 | `节流`和`防抖`
--------------------------------------------------------------------------------
/src/operators/resizeObserver/index.ts:
--------------------------------------------------------------------------------
1 | import { isString } from "../../utils/tools";
2 | import { Operator, Queue } from "../..";
3 |
4 | /**
5 | * 使用ResizeObserver订阅内容区域大小变化
6 | * https://developer.mozilla.org/zh-CN/docs/Web/API/ResizeObserver
7 | * @param arg 需要订阅目标元素的id或者dom节点,默认为html元素
8 | * @returns
9 | */
10 | export default function resizeObserver(arg?: string | HTMLElement): Operator {
11 | let dom: HTMLElement | null = null;
12 | if (arg === undefined) {
13 | dom = document.documentElement;
14 | } else if (isString(arg)) {
15 | dom = document.getElementById(arg);
16 | if (dom === null) throw `id:${arg}不存在`;
17 | } else {
18 | dom = arg;
19 | }
20 | return {
21 | mounted(queue: Queue) {
22 | if (dom === null) return;
23 | const resizeObserver = new ResizeObserver(entries => {
24 | for (const entry of entries) {
25 | queue.pushContent(entry);
26 | }
27 | });
28 | resizeObserver.observe(dom);
29 | },
30 | };
31 | }
32 |
--------------------------------------------------------------------------------
/src/operators/throttleTime/index.ts:
--------------------------------------------------------------------------------
1 | import { Operator } from "../..";
2 | import { IntTime } from "../../utils/types";
3 |
4 | /**
5 | * throttleTime 节流函数
6 | * @param duration 节流间隔时间,单位毫秒
7 | * @param immediate 是否立即执行,默认为false:
8 | * 如果为false,则会拿第一次触发的参数到结束时间执行,而不是拿结束时间之前的最后一次触发的参数去执行;
9 | * 如果为true,则会拿第一次触发的参数立即执行
10 | * @returns
11 | */
12 | export default function throttleTime(duration: number, immediate?: boolean): Operator {
13 | let now = 0;
14 | let timeId: IntTime | null = null;
15 | return {
16 | beforeAddNews() {
17 | const t = new Date().getTime();
18 | if (immediate) {
19 | //立即执行
20 | if (t <= now + duration) return false;
21 | now = t;
22 | return true;
23 | } else {
24 | //
25 | if (timeId !== null) return false;
26 |
27 | return new Promise(resolve => {
28 | timeId = setTimeout(() => {
29 | timeId = null;
30 | resolve(true);
31 | }, duration);
32 | });
33 | }
34 | },
35 | };
36 | }
37 |
--------------------------------------------------------------------------------
/src/plugins/wx-logs/proxyApi.ts:
--------------------------------------------------------------------------------
1 | import { isFunction } from "@/utils/tools";
2 |
3 | type I =
4 | | "canIUse"
5 | | "onThemeChange"
6 | | "onNetworkWeakChange"
7 | | "onNetworkStatusChange"
8 | | "onAudioInterruptionEnd"
9 | | "onAudioInterruptionBegin"
10 | | "onMemoryWarning"
11 | | "onUnhandledRejection"
12 | | "onPageNotFound"
13 | | "onLazyLoadError"
14 | | "onError"
15 | | "getWindowInfo"
16 | | "getSystemSetting"
17 | | "getSkylineInfoSync"
18 | | "getDeviceInfo"
19 | | "getAppBaseInfo"
20 | | "getAppAuthorizeSetting"
21 | | "getLaunchOptionsSync"
22 | | "getApiCategory"
23 | | "getRealtimeLogManager"
24 | | "getAccountInfoSync";
25 |
26 | type OptionWx = Pick;
27 |
28 | /**
29 | * 代理请求wx api ,需要基础库最低为1.1.1
30 | * @returns
31 | */
32 | function proxyWxApi() {
33 | if (!isFunction(wx["canIUse"])) throw new Error("基础库低于 1.1.1");
34 | //
35 | return function (w: J): OptionWx[J] | null {
36 | if (!isFunction(wx[w])) return null;
37 |
38 | return wx[w];
39 | };
40 | }
41 |
42 | export const wxApi = proxyWxApi();
43 |
--------------------------------------------------------------------------------
/script/build.js:
--------------------------------------------------------------------------------
1 | import esbuild from "esbuild";
2 | import chalk from "chalk";
3 | import fs from "fs-extra";
4 | import { execa } from "execa";
5 | const _package = JSON.parse(fs.readFileSync("./package.json"));
6 | const platform = "neutral";
7 | const now = new Date().getTime();
8 | // const operatorsDirList = fs.readdirSync("src/operators");
9 |
10 | async function buildMain() {
11 | const minify = _package.version.search("beta") === -1;
12 | //清除缓存
13 | // await execa("pnpm", ["clr"]);
14 |
15 | // 构建core
16 | const unmq = esbuild.build({
17 | entryPoints: ["src/index.ts"],
18 | outfile: "u-node-mq/index.js",
19 | platform,
20 | bundle: true,
21 | minify,
22 | sourcemap: true,
23 | });
24 |
25 | await Promise.all([
26 | unmq,
27 | execa("pnpm", ["gdts"]),
28 | fs.copy("package.json", "u-node-mq/package.json"),
29 | fs.copy("LICENSE", "u-node-mq/LICENSE"),
30 | fs.copy("README.md", "u-node-mq/README.md"),
31 | ]);
32 | console.log(chalk.cyanBright("执行时长:" + (new Date().getTime() - now) / 1000 + "秒"));
33 | }
34 | buildMain();
35 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2022 松子
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/termui/src/view/exchangeView.go:
--------------------------------------------------------------------------------
1 | package view
2 |
3 | import (
4 | "strconv"
5 | "time"
6 | "u-node-mq-termui/src/data"
7 | "u-node-mq-termui/src/util"
8 |
9 | ui "github.com/gizak/termui/v3"
10 | )
11 |
12 | //渲染交换机表格
13 | func renderExchangeTable() {
14 | list := data.E.FindList()
15 | arr := [][]string{
16 | {"ID", "Name", "CreatedTime", "UpdateTime", "AcceptedCount", "SendCount", "QueueNames"},
17 | }
18 |
19 | for _, v := range list {
20 | arr = append(arr, []string{
21 | v.Id,
22 | v.Name,
23 | time.UnixMilli(v.CreatedTime).Format(util.FormatStamp),
24 | time.UnixMilli(v.UpdateTime).Format(util.FormatStamp),
25 | strconv.Itoa(v.AcceptedCount),
26 | strconv.Itoa(v.SendCount),
27 | paseString(v.QueueNames),
28 | })
29 | }
30 |
31 | w, h := ui.TerminalDimensions()
32 | //交换机表格
33 | exchangeTable.Border = false
34 | exchangeTable.ColumnWidths = []int{20, 20, 20, 20, 20, 20, w - 120}
35 | exchangeTable.Rows = arr
36 | exchangeTable.TextStyle = ui.NewStyle(ui.ColorWhite)
37 |
38 | exchangeTable.SetRect(0, 2, w, h)
39 | ui.Render(exchangeTable)
40 | }
41 |
--------------------------------------------------------------------------------
/script/publish.js:
--------------------------------------------------------------------------------
1 | // const _package = require("../package.json");
2 | import chalk from "chalk";
3 | import fs from "fs-extra";
4 | import { execaSync } from "execa";
5 |
6 | const _package = JSON.parse(fs.readFileSync("./package.json"));
7 |
8 | const now = new Date().getTime();
9 | /**
10 | * 发布项目文件
11 | */
12 |
13 | execaSync("pnpm", ["build"]);
14 | console.log(chalk.blue("build成功!"));
15 |
16 | const { stdout } = execaSync("npm", ["view", "u-node-mq", "versions"]);
17 | console.log(chalk.blue("预发布版本号:", _package.version));
18 | if (-1 !== stdout.indexOf(_package.version)) {
19 | console.log(chalk.redBright("版本号已存在!"));
20 | process.exit(1);
21 | }
22 |
23 | //生成包
24 | const data = execaSync("npm", ["pack", "./u-node-mq"]);
25 | try {
26 | //发布正式包
27 | if (_package.version.search("beta") === -1) execaSync("npm", ["publish", data.stdout, "--tag", "latest"]);
28 | //发布测试包
29 | else execaSync("npm", ["publish", data.stdout, "--tag", "beta"]);
30 | } finally {
31 | fs.removeSync(data.stdout);
32 | }
33 |
34 | console.log(chalk.blue("publish成功!"));
35 |
36 | console.log(chalk.cyanBright("执行时长:" + (new Date().getTime() - now) / 1000 + "秒"));
37 |
--------------------------------------------------------------------------------
/docs/plugins/index.md:
--------------------------------------------------------------------------------
1 | # 插件介绍
2 |
3 | `u-node-mq`提供一些内置插件,用来解决前端开发场景下异步通信问题,也可以由开发者自行开发插件进行集成;
4 |
5 | ## Plugin
6 |
7 |
8 | 在您准备集成插件之前,请确保对UNodeMQ的工作流程有深入的了解。我们假设您对UNodeMQ的工作流程已经非常熟悉。
9 |
10 | 如果您已经多次使用过UNodeMQ类及其相关功能,您可能已经意识到,实际上UNodeMQ、QuickUNodeMQ和SingleUNodeMQ在应用中是与业务逻辑无关的,它们是独立于业务流程运行的。这种设计带来许多优点,例如易于扩展、跨平台兼容性和降低代码耦合度。
11 |
12 | 插件的出现是因为在某些情况下,将功能与业务逻辑结合使用能够更好地扩展功能。因此,通常情况下,每个UNodeMQ只能集成一个插件。由于插件引入了业务逻辑,它改变了UNodeMQ每个组件的本质,使每个组件能够模拟业务中的实际元素。您可以通过点击下面的链接,进入每个插件的介绍页面,了解它们与业务的对应关系。
13 |
14 |
15 | 开发插件
16 |
17 | ```javascript
18 | import UNodeMQ, { PluginInstallFunction } from "u-node-mq";
19 |
20 | const plugin: PluginInstallFunction = (unmq: UNodeMQ) => {
21 | //插件功能
22 | };
23 |
24 | //or
25 |
26 | const plugin: { install: PluginInstallFunction } = {
27 | install: (unmq: UNodeMQ) => {
28 | //插件功能
29 | },
30 | };
31 | ```
32 |
33 | 使用插件
34 |
35 | ```javascript
36 | import UNodeMQ,{plugin} from "u-node-mq";
37 |
38 | const unmq = new UNodeMQ(ExchangeCollection, QueueCollection);
39 | unmq.use(plugin);
40 | ```
41 |
42 |
43 | ## [【IframePlugin】一个跨iframe通信的插件](./IframePlugin.md)
44 |
45 | ## [【WxLogsPlugin】一个微信小程序日志插件](./WxLogsPlugin.md)
46 |
--------------------------------------------------------------------------------
/src/operators/debounceTime/index.ts:
--------------------------------------------------------------------------------
1 | import { Operator } from "../..";
2 | import { IntTime } from "../../utils/types";
3 |
4 | /**
5 | * debounceTime 防抖函数
6 | * @param dueTime 抖动间隔时间,单位毫秒
7 | * @param immediate 是否立即执行,默认为false
8 | * @returns
9 | * 立即执行代表第一个回调函数会立马执行,防抖函数一般不需要立即执行
10 | */
11 | export default function debounceTime(dueTime: number, immediate?: boolean): Operator {
12 | let now = 0;
13 | let timeId: IntTime | null = null;
14 | let res: (value: boolean | PromiseLike) => void;
15 | return {
16 | beforeAddNews() {
17 | const t = new Date().getTime();
18 | if (immediate) {
19 | //立即执行
20 | const n = now;
21 | now = t;
22 | return t > n + dueTime;
23 | } else {
24 | //
25 | if (timeId !== null && t <= now + dueTime) {
26 | res(false);
27 | clearTimeout(timeId);
28 | }
29 | now = t;
30 | return new Promise(resolve => {
31 | res = resolve;
32 | timeId = setTimeout(() => {
33 | timeId = null;
34 | resolve(true);
35 | }, dueTime);
36 | });
37 | }
38 | },
39 | };
40 | }
41 |
--------------------------------------------------------------------------------
/docs/operators/index.md:
--------------------------------------------------------------------------------
1 | # 🎨 operators
2 |
3 | Queue类提供的钩子函数可以集成operators对消息和消费者进行操作;
4 |
5 | - [map](./map.md)
6 | - [task](./task.md)
7 | - [debounceTime](./debounceTime.md)
8 | - [throttleTime](./throttleTime.md)
9 | - [newsTime](./newsTime.md)
10 | - [of](./of.md)
11 | - [interval](./interval.md)
12 | - [filter](./filter.md)
13 | - [removeDuplicates](./removeDuplicates.md)
14 | - [instant](./instant.md)
15 |
16 | **operators 异步钩子函数说明**
17 |
18 | 异步钩子函数不会影响队列执行
19 |
20 | | 名称 | 参数 | 返回 | 说明 |
21 | | --------------- | ---------- | ------- | ------------------------ |
22 | | mounted | Queue | unknown | operate 安装成功以后执行 |
23 | | addedNews | News | unknown | 消息加入队列以后执行 |
24 | | addedConsumer | Consumer | unknown | 消费者订阅队列以后执行 |
25 | | removedConsumer | Consumer[] | unknown | 消费者成功被移除以后执行 |
26 |
27 | **operators 同步钩子函数说明**
28 |
29 | 同步的钩子函数返回值会影响队列执行
30 |
31 | | 名称 | 参数 | 返回 | 说明 |
32 | | ------------- | ---- | --------------------------- | ------------------------------------------------ |
33 | | beforeAddNews | News | boolean \| Promise | 消息加入队列之前执行,通过返回值控制是否加入队列 |
34 | | ejectNews | News | boolean \| Promise | 消息弹出来以后执行,返回值用于控制消息是否被丢弃 |
35 |
--------------------------------------------------------------------------------
/todo.md:
--------------------------------------------------------------------------------
1 | **基础功能**
2 |
3 | - 完成 IframeMessage vue 页面
4 |
5 | - 实现 logs 输出 include 和 exclude 功能
6 |
7 | - 暂时只打包 window 平台,后面使用 gox 打包其他平台 termui
8 |
9 | - termui 使用键盘上下键选中数据功能
10 |
11 | - 实现 WebSocket plugin
12 |
13 | - 集成 Intersection Observer api
14 |
15 | - QuickUNodeMQ 和 SingleUNodeMQ 添加 plugins
16 |
17 | ---
18 |
19 | **测试功能**
20 |
21 | - 完成 IframeMessage 测试代码功能
22 |
23 | - 完成 core 文件代码的测试代码
24 |
25 | - 补充 SingleUNodeMQ、QuickUNodeMQ 的测试文件
26 |
27 | - 完成 ts 类型测试
28 |
29 | ---
30 |
31 | **文档补充**
32 |
33 | - 完成 operators 新文档介绍
34 |
35 | - 补充 SingleUNodeMQ、QuickUNodeMQ 的文档
36 |
37 | ---
38 |
39 | **版本管理**
40 |
41 | ### 3.6.8
42 |
43 | - ~~修复autoSize bug~~
44 |
45 | ### 3.6.7
46 |
47 | - ~~iframe 添加 autoSize 参数~~
48 |
49 | - ~~添加 SingleUNodeMQ 的 fork 功能~~
50 |
51 | ### 3.6.5
52 |
53 | - ~~修复 resizeObserver operators bug~~
54 |
55 | ### 3.6.4-beta.1
56 |
57 | - ~~添加.nvmrc 文件~~
58 |
59 | - ~~修复 News id 固定的 bug~~
60 |
61 | - ~~添加 resizeObserver operators~~
62 |
63 | - ~~修改 tools.ts 文字错误~~
64 |
65 | ### 3.6.3
66 |
67 | - ~~配置使用自动生成.d.ts 文件的工具~~
68 |
69 | - ~~兼容不使用 new 关键字创建组件~~
70 |
71 | - ~~配置 prettier~~
72 |
73 | - ~~termui 名称数据展示使用分隔符~~
74 |
75 | ### 3.6.2
76 |
77 | - ~~完成 logs 控制台服务的 go 的开发和文档完善~~
78 |
79 | - ~~termui 数据展示排序~~
80 |
81 | - ~~完成 logs mackdown 文档功能~~
82 |
--------------------------------------------------------------------------------
/termui/src/view/queueView.go:
--------------------------------------------------------------------------------
1 | package view
2 |
3 | import (
4 | "strconv"
5 | "time"
6 | "u-node-mq-termui/src/data"
7 | "u-node-mq-termui/src/util"
8 |
9 | ui "github.com/gizak/termui/v3"
10 | )
11 |
12 | func paseString(list []string) string {
13 | s := ""
14 | l := len(list)
15 | for i, v := range list {
16 | s += v
17 | if i != l-1 {
18 | s += ", "
19 | }
20 | }
21 | return s
22 | }
23 |
24 | //渲染队列表格
25 | func renderQueueTable() {
26 | //队列表格
27 | list := data.Q.FindList()
28 |
29 | arr := [][]string{
30 | {"ID", "Name", "CreatedTime", "UpdateTime", "NewsNum", "NewsIds", "ConsumerNum", "ConsumerIds"},
31 | }
32 |
33 | for _, v := range list {
34 | arr = append(arr, []string{
35 | v.Id,
36 | v.Name,
37 | time.UnixMilli(v.CreatedTime).Format(util.FormatStamp),
38 | time.UnixMilli(v.UpdateTime).Format(util.FormatStamp),
39 | strconv.Itoa(v.NewsNum),
40 | paseString(v.NewsIds),
41 | strconv.Itoa(v.ConsumerNum),
42 | paseString(v.ConsumerIds),
43 | })
44 | }
45 |
46 | w, h := ui.TerminalDimensions()
47 | queueTable.Border = false
48 | queueTable.ColumnWidths = []int{20, 20, 20, 20, 20, 20, 20, w - 140}
49 | queueTable.Rows = arr
50 |
51 | queueTable.TextStyle = ui.NewStyle(ui.ColorWhite)
52 |
53 | queueTable.SetRect(0, 2, w, h)
54 | ui.Render(queueTable)
55 | }
56 |
--------------------------------------------------------------------------------
/termui/go.mod:
--------------------------------------------------------------------------------
1 | module u-node-mq-termui
2 |
3 | go 1.18
4 |
5 | require (
6 | github.com/gin-gonic/gin v1.8.1
7 | github.com/gizak/termui/v3 v3.1.0
8 | )
9 |
10 | require (
11 | github.com/gin-contrib/sse v0.1.0 // indirect
12 | github.com/go-playground/locales v0.14.0 // indirect
13 | github.com/go-playground/universal-translator v0.18.0 // indirect
14 | github.com/go-playground/validator/v10 v10.10.0 // indirect
15 | github.com/goccy/go-json v0.9.7 // indirect
16 | github.com/json-iterator/go v1.1.12 // indirect
17 | github.com/leodido/go-urn v1.2.1 // indirect
18 | github.com/mattn/go-isatty v0.0.14 // indirect
19 | github.com/mattn/go-runewidth v0.0.2 // indirect
20 | github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7 // indirect
21 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect
22 | github.com/modern-go/reflect2 v1.0.2 // indirect
23 | github.com/nsf/termbox-go v0.0.0-20190121233118-02980233997d // indirect
24 | github.com/pelletier/go-toml/v2 v2.0.1 // indirect
25 | github.com/ugorji/go/codec v1.2.7 // indirect
26 | golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 // indirect
27 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110 // indirect
28 | golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069 // indirect
29 | golang.org/x/text v0.3.6 // indirect
30 | google.golang.org/protobuf v1.28.0 // indirect
31 | gopkg.in/yaml.v2 v2.4.0 // indirect
32 | )
33 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "u-node-mq",
3 | "version": "4.0.0",
4 | "author": "hugaojie ",
5 | "description": "基于发布订阅模型的消息通信插件",
6 | "keywords": [
7 | "发布订阅",
8 | "通信",
9 | "队列",
10 | "交换机",
11 | "消费者",
12 | "观察者"
13 | ],
14 | "bin": {
15 | "u-node-mq": "bin/u-node-mq-termui.exe"
16 | },
17 | "type": "module",
18 | "main": "index.js",
19 | "types": "index.d.ts",
20 | "repository": "https://github.com/Juaoie/u-node-mq.git",
21 | "homepage": "https://github.com/Juaoie/u-node-mq/",
22 | "license": "MIT",
23 | "scripts": {
24 | "build": "node ./script/build.js",
25 | "gobuild": "cd termui && go build",
26 | "test": "jest",
27 | "eslint": "eslint",
28 | "clr": "node ./script/clear.js",
29 | "pub": "node ./script/publish.js",
30 | "gdts": "node ./script/dts-bundle-generator.js"
31 | },
32 | "devDependencies": {
33 | "@jest/globals": "27.5.1",
34 | "@jest/types": "27.5.1",
35 | "@types/fs-extra": "^9.0.13",
36 | "@typescript-eslint/eslint-plugin": "^5.27.1",
37 | "@typescript-eslint/parser": "^5.27.1",
38 | "chalk": "^5.0.1",
39 | "dts-bundle-generator": "^6.12.0",
40 | "esbuild": "^0.14.34",
41 | "eslint": "^8.17.0",
42 | "eslint-config-prettier": "^8.5.0",
43 | "eslint-plugin-prettier": "^4.2.1",
44 | "execa": "^6.1.0",
45 | "fs-extra": "^10.1.0",
46 | "jest": "^27.5.1",
47 | "miniprogram-api-typings": "^3.12.2",
48 | "prettier": "^2.6.2",
49 | "ts-jest": "27.1.5",
50 | "typescript": "^4.5.5"
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/docs/plugins/IframePlugin.md:
--------------------------------------------------------------------------------
1 | # IframeMessage
2 |
3 | - `IframeMessage` 是用来解决同一个 `tabs` 下 `iframe` 通信的 `u-node-mq` 插件;
4 | - `u-node-mq` 集成 `IframeMessage` 以后,`unmq` 的每个 `Exchange` 将对应一个 `iframe` 容器,且非当前容器的 `Exchange` 路由和中继器将会被重写;
5 | - 一个 `iframe` 应用一般情况下应该只注册一个 IframeMessage 插件;
6 | - 被集成了 `IframeMessage` 插件的 `unmq`,开发者只需要维护自己 `Exchange` 下的队列;
7 | - 可以在其他 `Exchange` 应用上添加 `origin` 用来验证 `iframe` 的 `url`;
8 | - `new IframeMessage` 可以传递参数 `autoSize` 来控制当前`iframe`容器是否和父元素是否一致,默认`iframe`容器大小是不受父元素影响的
9 |
10 | ## IframeMessage 基本使用方法
11 |
12 | **iframe1 应用**
13 |
14 | ```javascript
15 | // https://iframeName1.com
16 | import UNodeMQ,{IframeMessage} from "u-node-mq";
17 | const unmq = new UNodeMQ(
18 | {
19 | iframeName1: new Exchange({ routes: ["qu1"] }),
20 | //约束iframeName2的origin必须为https://iframeName2.com
21 | iframeName2: new Exchange({ origin: "https://iframeName2.com" }),
22 | },
23 | {
24 | qu1: new Queue(),
25 | },
26 | );
27 | unmq.use(new IframeMessage("iframeName1",{autoSize:true}));
28 | unmq.emit("iframeName2", "发送给iframeName2的消息");
29 | ```
30 |
31 | **iframe2 应用**
32 |
33 | ```javascript
34 | // https://iframeName2.com
35 | import UNodeMQ,{IframeMessage} from "u-node-mq";
36 | const unmq = new UNodeMQ(
37 | {
38 | iframeName1: new Exchange(),
39 | iframeName2: new Exchange({ routes: ["qu2"] }),
40 | },
41 | {
42 | qu2: new Queue(),
43 | },
44 | );
45 | unmq.use(new IframeMessage("iframeName2"));
46 | unmq.on("qu2", res => {
47 | console.log("接受来自其他iframe容器的消息", res);
48 | });
49 | ```
50 |
--------------------------------------------------------------------------------
/termui/src/controller/controller.go:
--------------------------------------------------------------------------------
1 | package controller
2 |
3 | import (
4 | "io"
5 | "net/http"
6 | "os"
7 | "u-node-mq-termui/src/data"
8 |
9 | "github.com/gin-gonic/gin"
10 | )
11 |
12 | func SetQueueData() {
13 | // 记录到文件。
14 | f, _ := os.Create("unmq-termui.log")
15 | gin.DefaultWriter = io.MultiWriter(f)
16 |
17 | r := gin.Default()
18 | r.POST("/queue", queue)
19 | r.POST("/exchange", exchange)
20 | r.POST("/news", news)
21 | r.POST("/consumer", consumer)
22 | r.Run(":9090")
23 |
24 | }
25 |
26 | func queue(c *gin.Context) {
27 | q := data.QueueLogData{}
28 | c.BindJSON(&q)
29 | if q.Id == "" {
30 | c.JSON(http.StatusBadRequest, gin.H{"message": "fail"})
31 | } else {
32 | data.Q.Set(q)
33 | c.JSON(http.StatusOK, gin.H{"message": "ok!", "code": 200})
34 | }
35 | }
36 |
37 | func exchange(c *gin.Context) {
38 | e := data.ExchangeLogData{}
39 | c.BindJSON(&e)
40 | if e.Id == "" {
41 | c.JSON(http.StatusBadRequest, gin.H{"message": "fail"})
42 | } else {
43 | data.E.Set(e)
44 | c.JSON(http.StatusOK, gin.H{"message": "ok!", "code": 200})
45 | }
46 | }
47 |
48 | func news(c *gin.Context) {
49 | n := data.NewsLogData{}
50 | c.BindJSON(&n)
51 | if n.Id == "" {
52 | c.JSON(http.StatusBadRequest, gin.H{"message": "fail"})
53 | } else {
54 | data.N.Set(n)
55 | c.JSON(http.StatusOK, gin.H{"message": "ok!", "code": 200})
56 | }
57 | }
58 |
59 | func consumer(c *gin.Context) {
60 | cc := data.ConsumerLogData{}
61 | c.BindJSON(&cc)
62 | if cc.Id == "" {
63 | c.JSON(http.StatusBadRequest, gin.H{"message": "fail"})
64 | } else {
65 | data.C.Set(cc)
66 | c.JSON(http.StatusOK, gin.H{"message": "ok!", "code": 200})
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/src/operators/interval/index.ts:
--------------------------------------------------------------------------------
1 | import { Operator, Queue } from "../..";
2 | import { IntTime } from "../../utils/types";
3 |
4 | /**
5 | * setinterval发射数据,发射内容为从0开始的数字
6 | * @param period 1000 间隔时长
7 | * @param optimal true 是否在没有消费者的时候暂停发射数据,有消费者则会自动开启发射
8 | * @returns
9 | */
10 |
11 | export default function interval(period = 1000, optimal = true): Operator {
12 | if (period < 0) period = 0;
13 | let num = 0;
14 | let id: IntTime | null = null;
15 | let interval = {
16 | go: () => {
17 | //
18 | },
19 | stop: () => {
20 | //
21 | },
22 | };
23 | let queue: Queue;
24 |
25 | return {
26 | mounted(that: Queue) {
27 | // (function () { })();
28 | queue = that;
29 | interval = {
30 | go() {
31 | id = setInterval(() => {
32 | num++;
33 | queue.pushContent(num);
34 | }, period);
35 | },
36 | stop() {
37 | if (id === null) return;
38 | clearInterval(id);
39 | id = null;
40 | },
41 | };
42 |
43 | if (queue.getConsumerList.length > 0) {
44 | //有默认消费者
45 | interval.go();
46 | } else if (!optimal) {
47 | interval.go();
48 | }
49 | },
50 | addedConsumer() {
51 | //如果不启用该属性则直接退出
52 | if (!optimal) return;
53 | //判断是否以及在循环执行了
54 | if (id !== null) return;
55 |
56 | interval.go();
57 | },
58 | removedConsumer() {
59 | if (!optimal) return;
60 | //判断是否以及在循环执行了
61 | if (id === null) return;
62 |
63 | if (queue.getConsumerList.length === 0) interval.stop();
64 | return "";
65 | },
66 | };
67 | }
68 |
--------------------------------------------------------------------------------
/termui/src/data/newsTable.go:
--------------------------------------------------------------------------------
1 | package data
2 |
3 | import (
4 | "sort"
5 | "sync"
6 | "time"
7 | "u-node-mq-termui/src/util"
8 | )
9 |
10 | //前端传递的json
11 | type NewsLogData struct {
12 | BaseLogData
13 | }
14 |
15 | //表json
16 | type NewsTableField struct {
17 | TableField
18 | }
19 |
20 | type NewsTable struct {
21 | lock sync.Mutex
22 | list []NewsTableField
23 | State bool
24 | }
25 |
26 | var (
27 | N = NewsTable{}
28 | )
29 |
30 | //添加数据
31 | //一个id的组件只会创建一次
32 | func (nt *NewsTable) Add(NewsLogData NewsLogData) {
33 | n := NewsTableField{}
34 | n.Id = NewsLogData.Id
35 | n.CreatedTime = NewsLogData.CreatedTime
36 | n.UpdateTime = time.Now().In(util.CstSh).UnixMilli()
37 | nt.list = append(nt.list, n)
38 | nt.State = true
39 |
40 | }
41 |
42 | //删除list
43 | func (nt *NewsTable) DeleAll() {
44 | nt.list = []NewsTableField{}
45 | nt.State = true
46 |
47 | }
48 |
49 | //设置值,如果id不存在,就添加一条
50 | func (nt *NewsTable) Set(NewsLogData NewsLogData) {
51 | n := nt.Find(NewsLogData.Id)
52 | //是否需要加锁呢?
53 | nt.lock.Lock()
54 | if n.Id == "" {
55 | nt.Add(NewsLogData)
56 | } else {
57 |
58 | n.UpdateTime = time.Now().In(util.CstSh).UnixMilli()
59 |
60 | }
61 | nt.State = true
62 |
63 | nt.lock.Unlock()
64 | }
65 |
66 | //通过id查找一条数据,返回一条数据的指针
67 | func (nt *NewsTable) Find(id string) *NewsTableField {
68 | for i := 0; i < len(nt.list); i++ {
69 | if id == nt.list[i].Id {
70 | return &nt.list[i]
71 | }
72 | }
73 | return &NewsTableField{}
74 | }
75 |
76 | func (nt *NewsTable) FindList() []NewsTableField {
77 | sort.Slice(nt.list, func(i, j int) bool {
78 | return nt.list[i].UpdateTime > nt.list[j].UpdateTime
79 | })
80 |
81 | return nt.list
82 | }
83 |
--------------------------------------------------------------------------------
/src/internal/Queue/operators.ts:
--------------------------------------------------------------------------------
1 | import { News, Queue, Consumer } from "../..";
2 | /**
3 | * 会异步执行的运算方法
4 | * 不需要通过返回值控制是否继续执行流程
5 | */
6 | interface AsyncOperator {
7 | /**
8 | * 操作挂载执行方法
9 | * 多个操作符的同个钩子函数会同时执行,所以应该谨慎操作数据,避免产生异步操作数据的问题
10 | */
11 | mounted?: (that: Queue) => unknown;
12 | /**
13 | * 消息成功添加到队列以后
14 | */
15 | addedNews?: (news: News) => unknown;
16 |
17 | /**
18 | * 消费者成功加入到队列以后
19 | */
20 | addedConsumer?: (consumer: Consumer) => unknown;
21 | /**
22 | * 消费者成功被移除以后
23 | * consumerList 被删除的消费列表
24 | */
25 | removedConsumer?: (consumerList: Consumer[]) => unknown;
26 | }
27 |
28 | /**
29 | * 会同步的执行的运算符方法
30 | * 每个运算符方法列表都会同步执行,且一个返回false,后面则不会继续执行,用于控制流程是否继续
31 | */
32 | interface SyncOperator {
33 | /**
34 | * 将消息添加到队列之前
35 | * 返回的boolean控制消息是否加入队列
36 | */
37 | beforeAddNews?: (news: News) => boolean | Promise;
38 | /**
39 | * 加入消费者之前
40 | * 返回的boolean控制消费者是否能加入队列
41 | */
42 | // beforeAddConsumer?: (consumer: Consumer) => boolean | Promise;
43 |
44 | /**
45 | * 控制消息是否可以被弹出,为false则移除消息
46 | */
47 | ejectNews?: (news: News) => boolean | Promise;
48 | }
49 |
50 | /**
51 | * 异步运算符
52 | * @param arg
53 | * @returns
54 | */
55 | export function isAsyncOperator(arg: keyof Operator): arg is keyof AsyncOperator {
56 | return ["mounted", "addedNews", "addedConsumer", "removedConsumer"].indexOf(arg) !== -1;
57 | }
58 | /**
59 | * 同步运算符
60 | * @param arg
61 | * @returns
62 | */
63 | export function isSyncOperator(arg: keyof Operator): arg is keyof SyncOperator {
64 | return ["beforeAddNews", "ejectNews"].indexOf(arg) !== -1;
65 | }
66 | /**
67 | * 队列和消息在队列中的生命周期
68 | */
69 | export type Operator = AsyncOperator & SyncOperator;
70 |
--------------------------------------------------------------------------------
/src/core/ExchangeCollectionHandle.ts:
--------------------------------------------------------------------------------
1 | import { Exchange } from "../index";
2 |
3 | export default class ExchangeCollectionHandle {
4 | /**
5 | * 交换机集合
6 | */
7 | private exchangeCollection = new Map>();
8 | /**
9 | * 通过名称判断交换机是否存在
10 | * @param exchangeName
11 | * @returns 返回是否存在
12 | */
13 | has(exchangeName: string) {
14 | if (this.exchangeCollection.has(exchangeName)) return true;
15 | else {
16 | return false;
17 | }
18 | }
19 | /**
20 | * 设置交换机集合
21 | * @param exchangeCollection
22 | */
23 | setExchangeCollection(exchangeCollection: Record>) {
24 | this.exchangeCollection = new Map(Object.entries(exchangeCollection));
25 | }
26 | /**
27 | * 添加单条交换机
28 | * @param exchangeName
29 | * @param exchange
30 | * @returns 返回新的交换机集合
31 | */
32 | addExchage(exchangeName: string, exchange: Exchange) {
33 | exchange.name = exchangeName;
34 | return this.exchangeCollection.set(exchangeName, exchange);
35 | }
36 | /**
37 | * 获取单个交换机
38 | * @param exchangeName
39 | * @returns 返回交换机名称对应的交换机
40 | */
41 | getExchange(exchangeName: string): Exchange | null {
42 | const exchange = this.exchangeCollection.get(exchangeName);
43 | if (exchange === undefined) {
44 | return null;
45 | }
46 | return exchange;
47 | }
48 | /**
49 | * 获取所有交换机
50 | * @returns
51 | */
52 | getExchangeList() {
53 | return [...this.exchangeCollection.values()];
54 | }
55 | /**
56 | * 根据交换机名称获取队列名称列表
57 | * @param exchangeName
58 | * @param content
59 | * @returns
60 | */
61 | async getQueueNameList(exchangeName: string, content: D) {
62 | const exchagne = this.getExchange(exchangeName);
63 | if (exchagne === null) return [];
64 | return exchagne.getQueueNameList(content);
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/test/operators/throttleTime.test.ts:
--------------------------------------------------------------------------------
1 | import { Exchange, Queue, createQuickUnmq } from "../../src/index";
2 | import throttleTime from "../../src/operators/throttleTime";
3 |
4 | import { expect, test } from "@jest/globals";
5 |
6 | test("快速unmq,throttleTime测试,立即执行", function (done) {
7 | const quickUnmq = createQuickUnmq(new Exchange({ routes: ["qu1"] }), {
8 | qu1: new Queue().add(throttleTime(1000, true)),
9 | });
10 | let str = "";
11 | quickUnmq.on("qu1", (res: number) => {
12 | str += res;
13 | });
14 | quickUnmq.emit(2, 3, 4);
15 | setTimeout(() => {
16 | quickUnmq.emit(5);
17 | }, 1200);
18 | setTimeout(() => {
19 | expect(str).toEqual("25");
20 | done();
21 | }, 1500);
22 | });
23 |
24 | test("queue,throttleTime测试,立即执行", function (done) {
25 | const queue = new Queue().add(throttleTime(400, true));
26 | let str = "";
27 | queue.pushConsume((res: number) => {
28 | str += res;
29 | });
30 | queue.pushContent(2);
31 | setTimeout(() => {
32 | queue.pushContent(3);
33 | }, 200);
34 | setTimeout(() => {
35 | queue.pushContent(4);
36 | }, 500);
37 | setTimeout(() => {
38 | queue.pushContent(5);
39 | }, 1000);
40 | setTimeout(() => {
41 | expect(str).toEqual("245");
42 | done();
43 | }, 1500);
44 | });
45 |
46 | test("queue,throttleTime测试,最后执行", function (done) {
47 | const queue = new Queue().add(throttleTime(400));
48 | let str = "";
49 | queue.pushConsume((res: number) => {
50 | str += res;
51 | });
52 | queue.pushContent(2);
53 | setTimeout(() => {
54 | queue.pushContent(3);
55 | }, 200);
56 | setTimeout(() => {
57 | queue.pushContent(4);
58 | }, 500);
59 | setTimeout(() => {
60 | queue.pushContent(5);
61 | }, 1000);
62 | setTimeout(() => {
63 | expect(str).toEqual("245");
64 | done();
65 | }, 1500);
66 | });
67 |
--------------------------------------------------------------------------------
/termui/src/data/consumerTable.go:
--------------------------------------------------------------------------------
1 | package data
2 |
3 | import (
4 | "sort"
5 | "sync"
6 | "time"
7 | "u-node-mq-termui/src/util"
8 | )
9 |
10 | //前端传递的json
11 | type ConsumerLogData struct {
12 | BaseLogData
13 | Accepted int `json:"accepted"` //接收消息的数量,
14 | Message string `json:"message"` //文本说明日志,error 也会使用 message 字段输出
15 | }
16 |
17 | //表json
18 | type ConsumerTableField struct {
19 | TableField
20 | AcceptedCount int
21 | }
22 |
23 | type ConsumerTable struct {
24 | lock sync.Mutex
25 | list []ConsumerTableField
26 | State bool //控制是否需要重绘视图
27 | }
28 |
29 | var (
30 | C = ConsumerTable{}
31 | )
32 |
33 | //添加数据
34 | //一个id的组件只会创建一次
35 | func (ct *ConsumerTable) Add(ConsumerLogData ConsumerLogData) {
36 | c := ConsumerTableField{}
37 | c.Id = ConsumerLogData.Id
38 | c.CreatedTime = ConsumerLogData.CreatedTime
39 | c.UpdateTime = time.Now().In(util.CstSh).UnixMilli()
40 | c.AcceptedCount += ConsumerLogData.Accepted
41 | ct.list = append(ct.list, c)
42 | ct.State = true
43 |
44 | }
45 |
46 | //删除list
47 | func (ct *ConsumerTable) DeleAll() {
48 | ct.list = []ConsumerTableField{}
49 | ct.State = true
50 | }
51 |
52 | //设置值,如果id不存在,就添加一条
53 | func (ct *ConsumerTable) Set(ConsumerLogData ConsumerLogData) {
54 | c := ct.Find(ConsumerLogData.Id)
55 | //是否需要加锁呢?
56 | ct.lock.Lock()
57 | if c.Id == "" {
58 | ct.Add(ConsumerLogData)
59 | } else {
60 | c.AcceptedCount += ConsumerLogData.Accepted
61 |
62 | c.UpdateTime = time.Now().In(util.CstSh).UnixMilli()
63 |
64 | }
65 | ct.State = true
66 |
67 | ct.lock.Unlock()
68 | }
69 |
70 | //通过id查找一条数据,返回一条数据的指针
71 | func (ct *ConsumerTable) Find(id string) *ConsumerTableField {
72 | for i := 0; i < len(ct.list); i++ {
73 | if id == ct.list[i].Id {
74 | return &ct.list[i]
75 | }
76 | }
77 | return &ConsumerTableField{}
78 | }
79 |
80 | func (ct *ConsumerTable) FindList() []ConsumerTableField {
81 | sort.Slice(ct.list, func(i, j int) bool {
82 | return ct.list[i].UpdateTime > ct.list[j].UpdateTime
83 | })
84 |
85 | return ct.list
86 | }
87 |
--------------------------------------------------------------------------------
/docs/internal/logs_sys_dev.md:
--------------------------------------------------------------------------------
1 | # 自定义日志系统开发
2 |
3 | **CustomLogFunction 参数说明**
4 |
5 | `CustomLogFunction` 是用户自定义处理日志的方法,如需将日志输出到外部服务器,需先了解埋点在组件中的输出日志的方法提供的数据类型,开发者可根据日志数据类型自定义进行服务端日志系统构建;
6 |
7 | | 参数 | 类型 | 说明 |
8 | | ---- | ------- | ------------ |
9 | | name | string | 组件名称 |
10 | | data | LogData | 单条日志数据 |
11 |
12 | ## `LogData`类型说明
13 |
14 | `LogData`为四个组件关键点埋点日志的类型,分别为`ExchangeLogData`、`QueueLogData`、`NewsLogData`、`ConsumerLogData`;
15 |
16 | **BaseLogData 公共类型说明**
17 |
18 | | 名称 | 类型 | 说明 |
19 | | ----------- | ------ | --------------------------------------------------------- |
20 | | id | string | 组件的唯一 id,每条日志都会输出 |
21 | | createdTime | number | 组件的创建时间戳,每条日志都会输出 |
22 | | name | number | `Exchange`和`Queue`组件可能存在 name,如有 name,则会输出 |
23 | | message | string | 文本说明日志,error 也会使用 message 字段输出 |
24 |
25 | **ExchangeLogData 类型说明**
26 |
27 | | 名称 | 类型 | 说明 |
28 | | ---------- | -------- | ----------------------------------------------------------------------------------- |
29 | | accepted | number | 接收消息的数量,仅当有消息传入进来时才会输出 1 ,开发者可根据此字段统计接收消息总量 |
30 | | send | number | 消息路由到队列的队列数量 ,开发者可根据此字段统计路由总次数 |
31 | | queueNames | string[] | 消息路由到队列的队列名称数组,开发者可对此字段进行统计分组 |
32 |
33 | **QueueLogData 类型说明**
34 |
35 | | 名称 | 类型 | 说明 |
36 | | ----------- | -------- | -------------------- |
37 | | newsNum | number | 当前队列消息总数 |
38 | | newsIds | string[] | 当前队列消息 id 数组 |
39 | | consumerNum | number | 当前消费者数量 |
40 | | consumerIds | string[] | 当前消费者 id 数组 |
41 |
42 | **ConsumerLogData 类型说明**
43 |
44 | | 名称 | 类型 | 说明 |
45 | | -------- | ------ | ------------------------------------------------------------------------------- |
46 | | accepted | number | 消费消息的数量,仅当消费时才会输出 1 ,开发者可根据此字段统计单个消费者消费总数 |
47 |
--------------------------------------------------------------------------------
/docs/internal/termui.md:
--------------------------------------------------------------------------------
1 | # termui 开发工具使用
2 |
3 | 
4 |
5 | `termui`是使用`go`开发的日志查看工具,皆在方便开发者查看组件之间数据交互情况;目前仅支持window使用;
6 |
7 | ## 安装使用
8 |
9 | 在安装`u-node-mq`以后,可在`package.json`中配置以下启动命令
10 |
11 | ```json
12 | "scripts": {
13 | "unmq": "u-node-mq",
14 | }
15 | ```
16 |
17 | 然后在代码中开启自定义日志输出,并实现 CustomLogFunction 方法,`termui`默认监听端口号为`9090`,下面为示例代码;
18 |
19 | ```javascript
20 | import { Logs } from "u-node-mq";
21 | Logs.setLogsConfig({
22 | logs: true, //建议只在开发环境开启
23 | types: ["custom", "console"],
24 | customFunction: (name, data) => {
25 | //发送数据到termui 的 9090端口
26 | http.post("http://localhost:9090/" + name, {
27 | data,
28 | header: {
29 | "content-type": "application/json;charset=utf-8",
30 | },
31 | });
32 | },
33 | });
34 | ```
35 |
36 | **Queue 日志说明**
37 |
38 | | 名称 | 说明 |
39 | | ----------- | ------------------------------ |
40 | | ID | 队列 id |
41 | | name | 队列名称,创建队列名称可能为空 |
42 | | createdTime | 创建时间 |
43 | | updateTime | 最近更新时间 |
44 | | NewsNum | 队列内消息总数 |
45 | | NewsIds | 队列内消息 Id 数组 |
46 | | ConsumerNum | 监听队列的消费者总数 |
47 | | ConsumerIds | 监听队列的消费者 Id 数组 |
48 |
49 | **Exchange 日志说明**
50 |
51 | | 名称 | 说明 |
52 | | ------------- | ---------------------------------- |
53 | | ID | 交换机 id |
54 | | name | 交换机名称,创建交换机名称可能为空 |
55 | | createdTime | 创建时间 |
56 | | updateTime | 最近更新时间 |
57 | | acceptedCount | 接收到消息的总数 |
58 | | sendCount | 发送消息到队列的总次数 |
59 | | queueNames | 发送消息到队列的队列名称数组 |
60 |
61 | **News 日志说明**
62 |
63 | | 名称 | 说明 |
64 | | ----------- | ------------ |
65 | | ID | 交换机 id |
66 | | createdTime | 创建时间 |
67 | | updateTime | 最近更新时间 |
68 |
69 | **Consumer 日志说明**
70 |
71 | | 名称 | 说明 |
72 | | ------------- | ---------------- |
73 | | ID | 交换机 id |
74 | | createdTime | 创建时间 |
75 | | updateTime | 最近更新时间 |
76 | | AcceptedCount | 消费消息的总数量 |
77 |
78 | - q 键退出
79 | - 键盘左右键切换 tabs
80 | - c 建清空当前页数据
81 | - ctrl+c 清空所有数据
82 |
--------------------------------------------------------------------------------
/src/core/QueueCollectionHandle.ts:
--------------------------------------------------------------------------------
1 | import { isFunction } from "../utils/tools";
2 | import { Queue, News } from "../index";
3 | import { Consume } from "../internal/Consumer";
4 |
5 | /**
6 | * 队列集合
7 | */
8 | export default class QueueCollectionHandle {
9 | /**
10 | * 队列集合
11 | */
12 | private queueCollection = new Map>();
13 | /**
14 | * 根据队列名称判断队列是否存在
15 | * @param queueName
16 | * @returns
17 | */
18 | has(queueName: string) {
19 | if (this.queueCollection.has(queueName)) return true;
20 | else {
21 | return false;
22 | }
23 | }
24 | /**
25 | * 设置队列集合
26 | * @param queueCollection
27 | */
28 | setQueueCollection(queueCollection: Record>) {
29 | this.queueCollection = new Map(Object.entries(queueCollection));
30 | }
31 | /**
32 | * 根据队列名称获取单个队列对象
33 | * @param queueName
34 | * @returns
35 | */
36 | getQueue(queueName: string): Queue | null {
37 | const queue = this.queueCollection.get(queueName);
38 | if (queue === undefined) {
39 | return null;
40 | }
41 | return queue;
42 | }
43 | /**
44 | * 获取所有队列
45 | * @returns
46 | */
47 | getQueueList() {
48 | return [...this.queueCollection.values()];
49 | }
50 | /**
51 | * 添加单条队列
52 | * @param queue
53 | */
54 | addQueue(queueName: string, queue: Queue) {
55 | queue.name = queueName;
56 | return this.queueCollection.set(queueName, queue);
57 | }
58 | /**
59 | * 向指定队列添加数据
60 | * @param queueName
61 | * @param news
62 | */
63 | pushNewsToQueue(queueName: string, news: News) {
64 | this.getQueue(queueName)?.pushNews(news);
65 | }
66 | /**
67 | * 添加一个消息内容到队列
68 | * @param queueName
69 | * @param content
70 | * @returns
71 | */
72 | pushContentToQueue(queueName: string, content: D) {
73 | this.getQueue(queueName)?.pushContent(content);
74 | }
75 | /**
76 | * 订阅队列
77 | * @param queueName
78 | * @param consume
79 | * @param payload
80 | * @returns
81 | */
82 | subscribeQueue(queueName: string, consume: Consume, payload?: any) {
83 | this.getQueue(queueName)?.pushConsume(consume, payload);
84 | }
85 | /**
86 | * 取消订阅队列
87 | * @param queueName
88 | * @param consume
89 | */
90 | unsubscribeQueue(queueName: string, consume?: Consume): boolean {
91 | if (!this.has(queueName)) return false;
92 | if (isFunction(consume)) {
93 | return !!this.getQueue(queueName)?.removeConsumer(consume);
94 | } else {
95 | return !!this.getQueue(queueName)?.removeAllConsumer();
96 | }
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/src/internal/Consumer.ts:
--------------------------------------------------------------------------------
1 | import { ComponentEnum } from "../utils/types";
2 | import { isPromise, random } from "../utils/tools";
3 | import Logs from "./Logs";
4 | import News from "./News";
5 | export type Next = (value?: boolean) => void;
6 |
7 | export interface Consume {
8 | (content: D, next?: Next, payload?: any): Promise | any;
9 | (content: D, payload?: any): any;
10 | }
11 | type ThenParameter = (isOk: boolean) => void;
12 | interface Payload {
13 | then: (res: ThenParameter) => void;
14 | }
15 | export default class Consumer {
16 | [k: string]: any;
17 | /**
18 | * id
19 | */
20 | private readonly id: string = random();
21 | getId() {
22 | return this.id;
23 | }
24 | /**
25 | * 消费者创建时间戳
26 | */
27 | createdTime: number;
28 | /**
29 | * 消费方法
30 | */
31 | consume: Consume;
32 | /**
33 | * 固定参数
34 | */
35 | payload?: any;
36 | constructor(consume: Consume, payload?: any) {
37 | this.createdTime = new Date().getTime();
38 | this.consume = consume;
39 | this.payload = payload;
40 | Logs.getLogsInstance()?.setLogs(ComponentEnum.CONSUMER, { id: this.getId(), createdTime: this.createdTime });
41 | }
42 | /**
43 | * 消费消息
44 | * @param news
45 | * @param ask
46 | * @returns
47 | */
48 | consumption(news: News, ask: boolean): Payload {
49 | Logs.getLogsInstance()?.setLogs(ComponentEnum.CONSUMER, { id: this.getId(), createdTime: this.createdTime, accepted: 1 });
50 | const then = (thenParameter: ThenParameter) => {
51 | //不加入任务队列,会导致消费失败的数据重写到队列失败
52 | try {
53 | if (!ask) {
54 | //不需要确认的消费方法
55 | this.consume(news.content, this.payload);
56 | return thenParameter(true);
57 | }
58 | //构建消息确认的方法
59 | const confirm: Next = (value = true) => thenParameter(value);
60 | //需要确认的消费方法
61 | const res = this.consume(news.content, confirm, this.payload);
62 | //如果消息需要确认,且返回的内容为Promise
63 | if (isPromise(res)) {
64 | res
65 | .then(onfulfilled => {
66 | thenParameter(Boolean(onfulfilled));
67 | })
68 | .catch(() => {
69 | thenParameter(false);
70 | });
71 | } else if (typeof res === "boolean") {
72 | thenParameter(res);
73 | }
74 | } catch (error) {
75 | Logs.getLogsInstance()?.setLogs(ComponentEnum.CONSUMER, { id: this.getId(), createdTime: this.createdTime, message: JSON.stringify(error) });
76 | thenParameter(!ask);
77 | }
78 | };
79 | return {
80 | then,
81 | };
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/termui/src/data/queueTable.go:
--------------------------------------------------------------------------------
1 | package data
2 |
3 | import (
4 | "sort"
5 | "sync"
6 | "time"
7 | "u-node-mq-termui/src/util"
8 | )
9 |
10 | //前端传递的json
11 | type QueueLogData struct {
12 | BaseLogData
13 | QueueBaseField
14 | Message string `json:"message"` //文本说明日志,error 也会使用 message 字段输出
15 | }
16 |
17 | //表json
18 | type QueueTableField struct {
19 | TableField
20 | QueueBaseField
21 | }
22 |
23 | //队列公共字段
24 | type QueueBaseField struct {
25 | NewsNum int `json:"newsNum"` //当前队列消息总数
26 | NewsIds []string `json:"newsIds"` //当前队列消息 id 数组
27 | ConsumerNum int `json:"consumerNum"` //当前消费者数量
28 | ConsumerIds []string `json:"consumerIds"` //当前消费者 id 数组
29 | }
30 |
31 | type QueueTable struct {
32 | lock sync.Mutex
33 | list []QueueTableField
34 | State bool
35 | }
36 |
37 | var (
38 | Q = QueueTable{}
39 | )
40 |
41 | //添加数据
42 | //一个id的组件只会创建一次
43 | func (qt *QueueTable) Add(queueLogData QueueLogData) {
44 | q := QueueTableField{}
45 | q.Id = queueLogData.Id
46 | q.CreatedTime = queueLogData.CreatedTime
47 | q.Name = queueLogData.Name
48 | q.UpdateTime = time.Now().In(util.CstSh).UnixMilli()
49 | q.NewsNum = queueLogData.NewsNum
50 | q.NewsIds = queueLogData.NewsIds
51 | q.ConsumerNum = queueLogData.ConsumerNum
52 | q.ConsumerIds = queueLogData.ConsumerIds
53 | qt.list = append(qt.list, q)
54 | qt.State = true
55 |
56 | }
57 |
58 | //删除list
59 | func (qt *QueueTable) DeleAll() {
60 | qt.list = []QueueTableField{}
61 | qt.State = true
62 |
63 | }
64 |
65 | //设置值,如果id不存在,就添加一条
66 | func (qt *QueueTable) Set(queueLogData QueueLogData) {
67 | q := qt.Find(queueLogData.Id)
68 | //是否需要加锁呢?
69 | qt.lock.Lock()
70 | if q.Id == "" {
71 | qt.Add(queueLogData)
72 | } else {
73 | q.Name = queueLogData.Name
74 | q.NewsNum = queueLogData.NewsNum
75 | q.NewsIds = queueLogData.NewsIds
76 | q.ConsumerNum = queueLogData.ConsumerNum
77 | q.ConsumerIds = queueLogData.ConsumerIds
78 |
79 | q.UpdateTime = time.Now().In(util.CstSh).UnixMilli()
80 |
81 | }
82 | qt.State = true
83 |
84 | qt.lock.Unlock()
85 | }
86 |
87 | //通过id查找一条数据,返回一条数据的指针
88 | func (qt *QueueTable) Find(id string) *QueueTableField {
89 | for i := 0; i < len(qt.list); i++ {
90 | if id == qt.list[i].Id {
91 | return &qt.list[i]
92 | }
93 | }
94 | return &QueueTableField{}
95 | }
96 |
97 | func (qt *QueueTable) FindList() []QueueTableField {
98 | sort.Slice(qt.list, func(i, j int) bool {
99 | return qt.list[i].UpdateTime > qt.list[j].UpdateTime
100 | })
101 |
102 | return qt.list
103 | }
104 |
--------------------------------------------------------------------------------
/termui/src/view/view.go:
--------------------------------------------------------------------------------
1 | package view
2 |
3 | import (
4 | "log"
5 | "time"
6 | "u-node-mq-termui/src/data"
7 |
8 | ui "github.com/gizak/termui/v3"
9 | "github.com/gizak/termui/v3/widgets"
10 | )
11 |
12 | var (
13 | //头部提示
14 | header = widgets.NewParagraph()
15 | //tabs切换
16 | tabs = widgets.NewTabPane("Queue", "Exchange", "News", "Consumer")
17 | //队列表格
18 | queueTable = widgets.NewTable()
19 | //交换机表格
20 | exchangeTable = widgets.NewTable()
21 | //news表格
22 | newsTable = widgets.NewTable()
23 | //consumer表格
24 | consumerTable = widgets.NewTable()
25 | )
26 |
27 | func Init() {
28 | if err := ui.Init(); err != nil {
29 | log.Fatalf("failed to initialize termui: %v", err)
30 | }
31 | //延迟执行语句,会在函数退出时候执行
32 | defer ui.Close()
33 |
34 | //循环更新
35 | UpdateHead()
36 | go Repaint()
37 | uiEvents := ui.PollEvents()
38 | for {
39 | e := <-uiEvents
40 | switch e.ID {
41 | case "q":
42 | return
43 | case "":
44 | tabs.FocusRight()
45 | UpdateView()
46 | case "":
47 | tabs.FocusLeft()
48 | UpdateView()
49 | case "c":
50 | DelComponentView()
51 | UpdateView()
52 | case "":
53 | DelCompoentsView()
54 | UpdateView()
55 | }
56 |
57 | }
58 | }
59 |
60 | //更新头部样式
61 | func UpdateHead() {
62 | renderHeaderTable()
63 | renderTabsTable()
64 | }
65 |
66 | //重绘,数据更新就美隔一秒同步数据
67 | func Repaint() {
68 | w, h := ui.TerminalDimensions()
69 | for {
70 | time.Sleep(1 * time.Second)
71 | nw, nh := ui.TerminalDimensions()
72 | //如果一秒前后宽高发生变化就重绘
73 | if w != nw || h != nh || data.Q.State || data.E.State || data.N.State || data.C.State {
74 | data.Q.State = false
75 | data.E.State = false
76 | data.N.State = false
77 | data.C.State = false
78 | w = nw
79 | h = nh
80 | UpdateView()
81 | }
82 |
83 | }
84 | }
85 |
86 | //清空 组件视图方法
87 | func DelComponentView() {
88 | switch tabs.ActiveTabIndex {
89 | case 0:
90 | data.Q.DeleAll()
91 | case 1:
92 | data.E.DeleAll()
93 | case 2:
94 | data.N.DeleAll()
95 | case 3:
96 | data.C.DeleAll()
97 | }
98 | }
99 |
100 | //清空所有组件数据
101 | func DelCompoentsView() {
102 | data.Q.DeleAll()
103 | data.E.DeleAll()
104 | data.N.DeleAll()
105 | data.C.DeleAll()
106 |
107 | }
108 |
109 | //tabs切换方法
110 | func UpdateView() {
111 | ui.Clear()
112 | UpdateHead()
113 | switch tabs.ActiveTabIndex {
114 | case 0:
115 | renderQueueTable()
116 | case 1:
117 | renderExchangeTable()
118 | case 2:
119 | renderNewsTable()
120 | case 3:
121 | renderConsumerTable()
122 | }
123 |
124 | }
125 |
--------------------------------------------------------------------------------
/termui/src/data/exchangeTable.go:
--------------------------------------------------------------------------------
1 | package data
2 |
3 | import (
4 | "sort"
5 | "sync"
6 | "time"
7 | "u-node-mq-termui/src/util"
8 | )
9 |
10 | //控制器,前端传递的json
11 | type ExchangeLogData struct {
12 | BaseLogData
13 | Message string `json:"message"` //文本说明日志,error 也会使用 message 字段输出
14 | Accepted int `json:"accepted"` //接收消息的数量,
15 | Send int `json:"send"` //消息路由到队列的队列数量
16 | QueueNames []string `json:"queueNames"` //消息路由到队列的队列名称数组
17 | }
18 |
19 | //表json
20 | type ExchangeTableField struct {
21 | TableField
22 | //下面是统计数据
23 | AcceptedCount int
24 | SendCount int
25 | QueueNames []string
26 | }
27 |
28 | type ExchangeTable struct {
29 | lock sync.Mutex
30 | list []ExchangeTableField
31 | State bool
32 | }
33 |
34 | var (
35 | E = ExchangeTable{}
36 | )
37 |
38 | //添加数据
39 | //一个id的组件只会创建一次
40 | func (et *ExchangeTable) Add(ExchangeLogData ExchangeLogData) {
41 | e := ExchangeTableField{}
42 | e.Id = ExchangeLogData.Id
43 | e.Name = ExchangeLogData.Name
44 | e.CreatedTime = ExchangeLogData.CreatedTime
45 | e.UpdateTime = time.Now().In(util.CstSh).UnixMilli()
46 | e.AcceptedCount += ExchangeLogData.Accepted
47 | e.SendCount += ExchangeLogData.Send
48 | e.QueueNames = ExchangeLogData.QueueNames
49 | et.list = append(et.list, e)
50 | et.State = true
51 |
52 | }
53 |
54 | //删除list
55 | func (et *ExchangeTable) DeleAll() {
56 | et.list = []ExchangeTableField{}
57 | et.State = true
58 |
59 | }
60 |
61 | //设置值,如果id不存在,就添加一条
62 | func (et *ExchangeTable) Set(ExchangeLogData ExchangeLogData) {
63 | e := et.Find(ExchangeLogData.Id)
64 | //是否需要加锁呢?
65 | et.lock.Lock()
66 | if e.Id == "" {
67 | et.Add(ExchangeLogData)
68 | } else {
69 | e.Name = ExchangeLogData.Name
70 | e.AcceptedCount += ExchangeLogData.Accepted
71 | e.SendCount += ExchangeLogData.Send
72 | //驱虫,
73 | for _, v1 := range ExchangeLogData.QueueNames {
74 | isRest := false
75 | for _, v2 := range e.QueueNames {
76 | if v1 == v2 {
77 | isRest = true
78 | break
79 | }
80 | }
81 | if !isRest {
82 | e.QueueNames = append(e.QueueNames, v1)
83 | }
84 | }
85 |
86 | e.UpdateTime = time.Now().In(util.CstSh).UnixMilli()
87 |
88 | }
89 | et.State = true
90 |
91 | et.lock.Unlock()
92 | }
93 |
94 | //通过id查找一条数据,返回一条数据的指针
95 | func (et *ExchangeTable) Find(id string) *ExchangeTableField {
96 | for i := 0; i < len(et.list); i++ {
97 | if id == et.list[i].Id {
98 | return &et.list[i]
99 | }
100 | }
101 | return &ExchangeTableField{}
102 | }
103 |
104 | func (et *ExchangeTable) FindList() []ExchangeTableField {
105 | sort.Slice(et.list, func(i, j int) bool {
106 | return et.list[i].UpdateTime > et.list[j].UpdateTime
107 | })
108 |
109 | return et.list
110 | }
111 |
--------------------------------------------------------------------------------
/src/core/SingleUNodeMQ.ts:
--------------------------------------------------------------------------------
1 | import { isFunction } from "../utils/tools";
2 | import { Operator, Queue } from "../index";
3 | import { Consume, Next } from "../internal/Consumer";
4 | import { QueueOption } from "../internal/Queue";
5 |
6 | /**
7 | * 创建SingleUNodeMQ函数
8 | * @param x
9 | */
10 | function createSingleUnmq(x?: QueueOption | Queue) {
11 | return new SingleUNodeMQ(x);
12 | }
13 | export { createSingleUnmq };
14 | /**
15 | * 单Queue的UNodeMQ类
16 | */
17 | export default class SingleUNodeMQ {
18 | private queue: Queue;
19 |
20 | constructor(x?: QueueOption | Queue) {
21 | if (x instanceof Queue) this.queue = x;
22 | else this.queue = new Queue(x);
23 | }
24 | /**
25 | * 发送消息
26 | * @param contentList
27 | * @returns
28 | */
29 | emit(...contentList: D[]) {
30 | for (const content of contentList) {
31 | this.queue.pushContent(content);
32 | }
33 | return this;
34 | }
35 | /**
36 | * 订阅消息
37 | * @param consume
38 | * @param payload
39 | * @returns
40 | */
41 | on(consume: Consume, payload?: any) {
42 | this.queue.pushConsume(consume, payload);
43 | return () => this.off(consume);
44 | }
45 | /**
46 | * 移除消费者
47 | * @param consume
48 | */
49 | off(consume: Consume): this;
50 | off(): this;
51 | off(x?: Consume): this {
52 | if (isFunction(x)) this.queue.removeConsumer(x);
53 | else this.queue.removeAllConsumer();
54 | return this;
55 | }
56 | /**
57 | * 订阅一条消息
58 | * @param consume
59 | * @param payload
60 | */
61 | once(consume: Consume, payload?: any): this;
62 | once(): Promise;
63 | once(consume?: Consume, payload?: any) {
64 | if (isFunction(consume)) {
65 | const consumeProxy = (content: any, next?: Next, payload?: any) => {
66 | this.off(consumeProxy);
67 | return consume(content, next, payload);
68 | };
69 | this.on(consumeProxy, payload);
70 | return this;
71 | } else {
72 | return new Promise(resolve => {
73 | const consumeProxy = (content: any) => {
74 | this.off(consumeProxy);
75 | resolve(content);
76 | return true;
77 | };
78 | this.on(consumeProxy, payload);
79 | });
80 | }
81 | }
82 | /**
83 | * 添加operators
84 | * @param operators
85 | * @returns
86 | */
87 | add(...operators: Operator[]) {
88 | this.queue.add(...operators);
89 | return this;
90 | }
91 | /**
92 | * fork一份队列,用于监听当前队列数据输出
93 | * @param x
94 | * @returns
95 | */
96 | fork(x?: QueueOption | Queue) {
97 | const csu = createSingleUnmq(x);
98 | this.on(res => {
99 | csu.emit(res);
100 | return true;
101 | });
102 | return csu;
103 | }
104 | }
105 |
--------------------------------------------------------------------------------
/src/internal/Exchange.ts:
--------------------------------------------------------------------------------
1 | import { ComponentEnum } from "../utils/types";
2 | import { random } from "../utils/tools";
3 | import Logs from "./Logs";
4 | /**
5 | * 中继器类型
6 | */
7 | type Repeater = (content: D) => Promise | string[];
8 |
9 | export type ExchangeOption = {
10 | routes?: string[];
11 | repeater?: Repeater;
12 | name?: string;
13 | [k: string]: any;
14 | };
15 | /**
16 | * 交换机
17 | */
18 | export default class Exchange {
19 | [k: string]: any;
20 | name?: string;
21 | /**
22 | * 创建时间戳
23 | */
24 | readonly createdTime: number;
25 | /**
26 | * id
27 | */
28 | private readonly id: string = random();
29 | getId() {
30 | return this.id;
31 | }
32 | /**
33 | * 静态路由
34 | */
35 | private routes: string[] = [];
36 | getRoutes() {
37 | return this.routes;
38 | }
39 | pushRoutes(routes: string[]) {
40 | this.routes = Array.from(new Set(this.routes.concat(routes)));
41 | }
42 | setRoutes(routes: string[]) {
43 | this.routes = routes;
44 | }
45 | /**
46 | * 动态路由(中继器)
47 | */
48 | private repeater: Repeater = () => this.getRoutes();
49 | getRepeater() {
50 | return this.repeater;
51 | }
52 | setRepeater(repeater: Repeater) {
53 | this.repeater = repeater;
54 | }
55 |
56 | constructor(option?: ExchangeOption) {
57 | Object.assign(this, option);
58 | this.createdTime = new Date().getTime();
59 | Logs.getLogsInstance()?.setLogs(ComponentEnum.EXCHANGE, { id: this.getId(), name: this.name, createdTime: this.createdTime });
60 | }
61 |
62 | /**
63 | * 删除routes
64 | * @param routes
65 | */
66 | removeRoutes(routes?: string[]) {
67 | if (routes === undefined) this.routes = [];
68 | else this.routes = this.routes.filter(item => routes.indexOf(item) !== -1);
69 | }
70 |
71 | /**
72 | * 获取队列名称列表
73 | * @param content
74 | * @returns
75 | */
76 | async getQueueNameList(content: D): Promise {
77 | Logs.getLogsInstance()?.setLogs(ComponentEnum.EXCHANGE, { id: this.getId(), accepted: 1, name: this.name, createdTime: this.createdTime });
78 | try {
79 | //中继器模式
80 | const queueNames = await this.repeater(content);
81 | Logs.getLogsInstance()?.setLogs(ComponentEnum.EXCHANGE, {
82 | id: this.getId(),
83 | send: queueNames.length,
84 | queueNames,
85 | name: this.name,
86 | createdTime: this.createdTime,
87 | });
88 | return queueNames;
89 | } catch (error) {
90 | Logs.getLogsInstance()?.setLogs(ComponentEnum.EXCHANGE, {
91 | id: this.getId(),
92 | name: this.name,
93 | createdTime: this.createdTime,
94 | message: JSON.stringify(error),
95 | });
96 | return [];
97 | }
98 | }
99 | }
100 |
--------------------------------------------------------------------------------
/test/operators/interval.test.ts:
--------------------------------------------------------------------------------
1 | import { Exchange, Queue, createQuickUnmq } from "../../src/index";
2 | import interval from "../../src/operators/interval";
3 | import throttleTime from "../../src/operators/throttleTime";
4 |
5 | import { expect, test } from "@jest/globals";
6 |
7 | test("interval测试,一直循环", function (done) {
8 | const quickUnmq = createQuickUnmq(new Exchange(), {
9 | qu1: new Queue()
10 | //使用 operate
11 | .add(interval(1000, false)),
12 | });
13 |
14 | let num = "";
15 | quickUnmq.on("qu1", (res: number) => {
16 | num += res;
17 | });
18 |
19 | const qu2 = new Queue().add(interval(1000, false));
20 |
21 | let num2 = "";
22 | qu2.pushConsume((res: number) => {
23 | num2 += res;
24 | });
25 | setTimeout(() => qu2.removeAllConsumer(), 2500);
26 | setTimeout(() => {
27 | expect(num).toEqual("1234");
28 | expect(num2).toEqual("12");
29 | expect(qu2.getNews().length).toEqual(2);
30 | done();
31 | }, 4500);
32 | });
33 |
34 | test("interval测试,优化版", function (done) {
35 | const qu2 = new Queue().add(interval(100));
36 |
37 | let num2 = "";
38 | qu2.pushConsume((res: number) => {
39 | num2 += res;
40 | });
41 | setTimeout(() => qu2.removeAllConsumer(), 250);
42 |
43 | setTimeout(() => {
44 | qu2.pushConsume((res: number) => {
45 | num2 += res;
46 | });
47 | }, 750);
48 | setTimeout(() => {
49 | expect(num2).toEqual("1234");
50 | expect(qu2.getNews().length).toEqual(0);
51 | done();
52 | }, 1050);
53 | });
54 |
55 | test("interval测试,简单组合使用技巧", function (done) {
56 | const qu1 = new Queue().add(interval(100)).add(throttleTime(200, true));
57 |
58 | let num2 = "";
59 | qu1.pushConsume((res: number) => {
60 | num2 += res;
61 | });
62 | setTimeout(() => {
63 | expect(num2).toEqual("13579");
64 | expect(qu1.getNews().length).toEqual(0);
65 | done();
66 | }, 1050);
67 | });
68 |
69 | test("interval测试,组合使用技巧", function (done) {
70 | const qu1 = new Queue().add(interval(100));
71 | const qu2 = new Queue().add(throttleTime(200, true));
72 | //将qu1的内容发射到qu2上,并在qu2上做其他业务操作
73 | qu1.pushConsume(qu2.pushContent.bind(qu2));
74 |
75 | let num2 = "";
76 | qu2.pushConsume((res: number) => {
77 | num2 += res;
78 | });
79 | setTimeout(() => {
80 | expect(num2).toEqual("13579");
81 | expect(qu2.getNews().length).toEqual(0);
82 | done();
83 | }, 1050);
84 | });
85 |
86 | test("interval测试,组合使用技巧quickUnmq版本", function (done) {
87 | const quickUnmq = createQuickUnmq(new Exchange(), {
88 | qu1: new Queue().add(interval(100)),
89 | });
90 | const qu2 = new Queue().add(throttleTime(200, true));
91 | //将qu1的内容发射到qu2上,并在qu2上做其他业务操作
92 | quickUnmq.on("qu1", (res: number) => {
93 | qu2.pushContent(res);
94 | });
95 |
96 | let num2 = "";
97 | qu2.pushConsume((res: number) => {
98 | num2 += res;
99 | });
100 | setTimeout(() => {
101 | expect(num2).toEqual("13579");
102 | expect(qu2.getNews().length).toEqual(0);
103 | done();
104 | }, 1050);
105 | });
106 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 | ## 文档目录结构
12 |
13 | - [README.md](./README.md)
14 | - [todo.md](./todo.md)
15 | - docs
16 | - [nav.md](./docs/nav.md) 导航预览
17 | - [unmq.md](./docs/unmq.md) 快速开发
18 | - [other.md](./docs/other.md) 其他
19 | - internal
20 | - [index.md](./docs/internal/index.md) 组件介绍
21 | - [logs_sys_dev.md](./docs/internal/logs_sys_dev.md) 自定义日志系统开发
22 | - [termui.md](./docs/internal/termui.md) termui 使用
23 | - operators
24 | - [index.md](./docs/operators/index.md) 操作符介绍
25 | - plugins
26 | - [index.md](./docs/plugins/index.md) 插件介绍
27 | - [IframePlugin.md](./docs/plugins/IframePlugin.md) Iframe 通信插件
28 |
29 | ## 文档内容
30 |
31 | ### `u-node-mq` 是什么?
32 |
33 | `u-node-mq`是用来解决前端项目中数据异步通信问题的工具,可以准确的将一个模块的数据传到另一个模块,就像`rabbitMQ`使用发布订阅模型的中间件一样,使用`u-node-mq`可以完全解耦前端模块的耦合;
34 |
35 | ### 其他
36 |
37 | - `u-node-mq`在文档和代码注释中有时也会写成简写`unmq`;
38 |
39 | - `u-node-mq`中的`u`是标识词;`node`是最初创建项目的执行环境是 `node`,但是后面经过使用 `ts` 升级和重构,现在已经升级到可以在所有 `js` 环境中执行;`mq`是`message queue`的简写;
40 |
41 | - [其他信息](./docs/other.md)
42 |
43 | ### npm 安装
44 |
45 | `pnpm add u-node-mq`
46 |
47 | or
48 |
49 | `yarn add u-node-mq`
50 |
51 | or
52 |
53 | `npm install u-node-mq`
54 |
55 | ### `u-node-mq` 基本使用方法
56 |
57 | **unmq.js**
58 |
59 | ```javascript
60 | import UNodeMQ, { Exchange, Queue } from "u-node-mq";
61 |
62 | //声明交换机ex1,以及队列qu1
63 | const unmq = new UNodeMQ({ ex1: new Exchange({ routes: ["qu1"] }) }, { qu1: new Queue() });
64 |
65 | export default unmq;
66 |
67 | //可以挂到抬手就摸得到的位置
68 |
69 | // Vue.prototype.unmq = unmq; //(Vue 2.x)
70 |
71 | // const app = createApp({})
72 | // app.config.globalProperties.unmq = unmq //(Vue 3.x)
73 | ```
74 |
75 | **模块 A.js**
76 |
77 | ```javascript
78 | import unmq from "unmq.js";
79 |
80 | //发送数据
81 | unmq.emit("ex1", "消息内容1", "消息内容2");
82 | ```
83 |
84 | **模块 B.js**
85 |
86 | ```javascript
87 | import unmq from "unmq.js";
88 |
89 | //接收并消费数据
90 | unmq.on("qu1", getData);
91 |
92 | function getData(data) {
93 | console.log(data);
94 | }
95 | ```
96 |
97 | ### [了解更多详细内容](./docs/nav.md)
98 |
99 | ### [TODO](./todo.md)
100 |
--------------------------------------------------------------------------------
/src/utils/tools.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * 获取随机数
3 | * @returns
4 | */
5 | export const random = (): string => String(Math.round(Math.random() * 10000000000));
6 |
7 | /**
8 | * 获取uuid
9 | * @returns
10 | */
11 | export function getUUID() {
12 | return "xxxxxxxx-xxxx-xxxx-yxxx-xxxxxxxxxxxx"
13 | .replace(/[xy]/g, function (c) {
14 | const r = (Math.random() * 16) | 0;
15 | const v = c == "x" ? r : (r & 0x3) | 0x8;
16 | return v.toString(16);
17 | })
18 | .toUpperCase();
19 | }
20 |
21 | export const promiseSetTimeout = (time = 0) => new Promise(resolve => setTimeout(resolve, time));
22 |
23 | /**
24 | * 获取格式化时间
25 | * @param time
26 | * @returns
27 | */
28 | export const getTimeFormat = (time?: string | number): string => {
29 | let now = null;
30 | if (time) now = new Date(time);
31 | else now = new Date();
32 |
33 | const year = now.getFullYear(); //年
34 | const month = now.getMonth() + 1; //月
35 | const day = now.getDate(); //日
36 |
37 | const hh = now.getHours(); //时
38 | const mm = now.getMinutes(); //分
39 |
40 | let clock = year + "-";
41 |
42 | if (month < 10) clock += "0";
43 | clock += month + "-";
44 |
45 | if (day < 10) clock += "0";
46 | clock += day + " ";
47 |
48 | if (hh < 10) clock += "0";
49 | clock += hh + ":";
50 | if (mm < 10) clock += "0";
51 | clock += mm;
52 | return clock;
53 | };
54 | /**
55 | * //字符编码数值对应的存储长度:
56 | //UCS-2编码(16进制) UTF-8 字节流(二进制)
57 | //0000 - 007F 0xxxxxxx (1字节)
58 | //0080 - 07FF 110xxxxx 10xxxxxx (2字节)
59 | //0800 - FFFF 1110xxxx 10xxxxxx 10xxxxxx (3字节)
60 | * @param str
61 | * @returns
62 | */
63 | export const memorySize = (str: string): string => {
64 | let totalLength = 0;
65 | let charCode;
66 | for (let i = 0; i < str.length; i++) {
67 | charCode = str.charCodeAt(i);
68 | if (charCode < 0x007f) {
69 | totalLength++;
70 | } else if (0x0080 <= charCode && charCode <= 0x07ff) {
71 | totalLength += 2;
72 | } else if (0x0800 <= charCode && charCode <= 0xffff) {
73 | totalLength += 3;
74 | } else {
75 | totalLength += 4;
76 | }
77 | }
78 | if (totalLength >= 1024 * 1024) return (totalLength / (1024 * 1024)).toFixed(2) + "MB";
79 | if (totalLength >= 1024 && totalLength < 1024 * 1024) return (totalLength / 1024).toFixed(2) + "KB";
80 | else return totalLength + "B";
81 | };
82 |
83 | /**
84 | * 转换
85 | */
86 | export const extend = Object.assign;
87 | export const objectToString = Object.prototype.toString;
88 | export const toTypeString = (value: unknown): string => objectToString.call(value);
89 |
90 | /**
91 | * 类型判断
92 | */
93 | export const isArray = Array.isArray;
94 | export const isMap = (val: unknown): val is Map => toTypeString(val) === "[object Map]";
95 | export const isSet = (val: unknown): val is Set => toTypeString(val) === "[object Set]";
96 | export const isDate = (val: unknown): val is Date => val instanceof Date;
97 | // eslint-disable-next-line @typescript-eslint/ban-types
98 | export const isFunction = (val: unknown): val is Function => typeof val === "function";
99 | export const isString = (val: unknown): val is string => typeof val === "string";
100 | export const isNumber = (val: unknown): val is number => typeof val === "number";
101 | export const isBoolean = (val: unknown): val is boolean => typeof val === "boolean";
102 | export const isSymbol = (val: unknown): val is symbol => typeof val === "symbol";
103 | export const isObject = (val: unknown): val is Record => val !== null && typeof val === "object";
104 | export const isPromise = (val: unknown): val is Promise => {
105 | return isObject(val) && isFunction(val.then) && isFunction(val.catch);
106 | };
107 | //获取构造函数第一个参数的类型
108 | export type ConstructorParameter any> = T extends new (args: infer P) => any ? P : never;
109 |
--------------------------------------------------------------------------------
/src/internal/Logs.ts:
--------------------------------------------------------------------------------
1 | import { Operator, Queue } from "../index";
2 | import { ComponentEnum } from "../utils/types";
3 | /**
4 | * 应该尽量避免生产环境中把日志混入代码,减少代码量,除非生产环境中需要使用到日志
5 | * unmq:{
6 | * log:false //默认false
7 | * type:'http'|'console' //默认'console'
8 | * components:['Exchange','Queue','News','Consumer'] //默认*
9 | *
10 | * }
11 | */
12 | /**
13 | * 使用operator记录queue日志
14 | * @returns
15 | */
16 | export function queueLogsOperator(): Operator {
17 | let queueInstance: Queue;
18 | function addQueueData() {
19 | Logs.getLogsInstance()?.setLogs(ComponentEnum.QUEUE, {
20 | name: queueInstance.name,
21 | createdTime: queueInstance.createdTime,
22 | id: queueInstance.getId(),
23 | newsNum: queueInstance.getNews().length,
24 | newsIds: queueInstance.getNews().map(item => item.getId()),
25 | consumerNum: queueInstance.getConsumerList().length,
26 | consumerIds: queueInstance.getConsumerList().map(item => item.getId()),
27 | message: "queue ok!",
28 | });
29 | }
30 | return {
31 | mounted(queue) {
32 | queueInstance = queue;
33 | addQueueData();
34 | },
35 | addedNews() {
36 | addQueueData();
37 | },
38 | ejectNews() {
39 | addQueueData();
40 | return true;
41 | },
42 | addedConsumer() {
43 | addQueueData();
44 | },
45 | removedConsumer() {
46 | addQueueData();
47 | },
48 | };
49 | }
50 | enum LogsEnum {
51 | "CUSTOM" = "custom",
52 | "CONSOLE" = "console",
53 | }
54 | type LogsType = LogsEnum.CUSTOM | LogsEnum.CONSOLE;
55 | type LogsComponent = ComponentEnum.EXCHANGE | ComponentEnum.QUEUE | ComponentEnum.NEWS | ComponentEnum.CONSUMER;
56 | interface LogsConfig {
57 | logs: boolean;
58 | types?: LogsType[];
59 | logsComponents?: LogsComponent[];
60 | customFunction?: (name: LogsComponent, data: D) => void;
61 | }
62 | interface BaseLogData {
63 | id: string; //id
64 | createdTime: number; //创建时间戳
65 | name?: string; //名称
66 | message?: string; //描述日志
67 | }
68 | interface ExchangeLogData {
69 | accepted?: number; //当前接收写入交换机的消息数量
70 | send?: number; //当前发送给队列的消息数量,一条消费发送给两个队列,send则为2
71 | queueNames?: string[]; //已发送给队列的队列名称列表
72 | }
73 | interface QueueLogData {
74 | newsNum: number; //当前消息数量
75 | newsIds: string[]; //当前消息的id列表
76 | consumerNum: number; //当前消费者数量
77 | consumerIds: string[]; //当前消费者id列表
78 | }
79 | // interface NewsLogData {}
80 | interface ConsumerLogData {
81 | accepted?: number; //当前消费数量
82 | }
83 |
84 | interface LogDataTypes {
85 | [ComponentEnum.EXCHANGE]: BaseLogData & ExchangeLogData;
86 | [ComponentEnum.QUEUE]: BaseLogData & QueueLogData;
87 | [ComponentEnum.NEWS]: BaseLogData;
88 | [ComponentEnum.CONSUMER]: BaseLogData & ConsumerLogData;
89 | }
90 | type CustomLogFunction = (name: LogsComponent, data: LogDataTypes[K]) => unknown;
91 | /**
92 | * 全局日志组件
93 | */
94 | export default class Logs {
95 | private static logs = false;
96 | private static types: LogsType[];
97 | private static logsComponents: LogsComponent[];
98 | private static customFunction: CustomLogFunction;
99 | static setLogsConfig(logsConfig: LogsConfig) {
100 | this.logs = logsConfig.logs;
101 | this.types = logsConfig.types ?? [LogsEnum.CONSOLE];
102 | this.logsComponents = logsConfig.logsComponents ?? [ComponentEnum.EXCHANGE, ComponentEnum.QUEUE, ComponentEnum.NEWS, ComponentEnum.CONSUMER];
103 | this.customFunction =
104 | logsConfig.customFunction ??
105 | function (res) {
106 | console.log(res);
107 | };
108 | }
109 | /**
110 | * 获取日志实例
111 | * @returns
112 | */
113 | static getLogsInstance() {
114 | if (!this.logs) return;
115 | return {
116 | setLogs: this.setLogs.bind(this),
117 | };
118 | }
119 | /**
120 | * 设置日志
121 | * @param name
122 | * @param data
123 | * @returns
124 | */
125 | private static setLogs(name: LogsComponent, data: LogDataTypes[K]) {
126 | if (this.logsComponents.indexOf(name) === -1) return;
127 |
128 | this.types.forEach(type => {
129 | if (type === LogsEnum.CUSTOM) {
130 | this.customFunction(name, data);
131 | } else if (type === LogsEnum.CONSOLE) {
132 | console.log(name, data);
133 | }
134 | });
135 | }
136 | }
137 |
--------------------------------------------------------------------------------
/src/core/QuickUNodeMQ.ts:
--------------------------------------------------------------------------------
1 | import { isFunction } from "../utils/tools";
2 | import { Exchange, Queue, Plugin } from "../index";
3 | import { Consume, Next } from "../internal/Consumer";
4 | import { ExchangeOption } from "../internal/Exchange";
5 |
6 | /**
7 | * 创建QuickUNodeMQ函数
8 | * @param x
9 | * @param y
10 | * @returns
11 | */
12 | function createQuickUnmq>>(x: ExchangeOption | Exchange, y: QueueCollection) {
13 | return new QuickUNodeMQ(x, y);
14 | }
15 | export { createQuickUnmq };
16 | /**
17 | * 单交换机的UNodeMQ类
18 | */
19 | export default class QuickUNodeMQ>> {
20 | private exchange: Exchange;
21 | private queueCollection: QueueCollection;
22 | private readonly installedPlugins: Set = new Set();
23 | use(plugin: Plugin, ...options: any[]) {
24 | if (this.installedPlugins.has(plugin)) {
25 | console.log(`Plugin has already been applied to target unmq.`);
26 | } else if (plugin && isFunction(plugin.install)) {
27 | this.installedPlugins.add(plugin);
28 | plugin.install(this, ...options);
29 | } else if (isFunction(plugin)) {
30 | this.installedPlugins.add(plugin);
31 | plugin(this, ...options);
32 | }
33 | return this;
34 | }
35 |
36 | constructor(x: ExchangeOption | Exchange, y: QueueCollection) {
37 | if (x instanceof Exchange) this.exchange = x;
38 | else this.exchange = new Exchange(x);
39 |
40 | for (const name in y) {
41 | y[name].name = name;
42 | }
43 | this.queueCollection = y;
44 | }
45 | /**
46 | * 发射数据到交换机
47 | * @param contentList 消息体列表
48 | * @returns
49 | */
50 | emit(...contentList: D[]) {
51 | for (const content of contentList) {
52 | this.exchange.getQueueNameList(content).then((queueNameList: string[]) => {
53 | for (const queueName of queueNameList) {
54 | if (this.queueCollection[queueName] === undefined) continue;
55 | this.queueCollection[queueName].pushContent(content);
56 | }
57 | });
58 | }
59 | return this;
60 | }
61 | /**
62 | * 发射数据到队列
63 | * @param queueName
64 | * @param contentList
65 | * @returns
66 | */
67 | emitToQueue(queueName: Q, ...contentList: D[]) {
68 | for (const content of contentList) {
69 | this.queueCollection[queueName].pushContent(content);
70 | }
71 | return this;
72 | }
73 | /**
74 | * 订阅队列消息
75 | * @param queueName 队列名称
76 | * @param consume 消费方法
77 | * @param payload 固定参数,有效载荷,在每次消费的时候都传给消费者
78 | * @returns
79 | */
80 | on(queueName: Q, consume: Consume, payload?: any) {
81 | this.queueCollection[queueName].pushConsume(consume, payload);
82 | return () => this.off(queueName, consume);
83 | }
84 |
85 | /**
86 | * 移除消费者
87 | * @param queueName
88 | * @param consume
89 | */
90 | off(queueName: Q, consume?: Consume): this {
91 | if (isFunction(consume)) {
92 | this.queueCollection[queueName].removeConsumer(consume);
93 | } else this.queueCollection[queueName].removeAllConsumer();
94 | return this;
95 | }
96 |
97 | /**
98 | * 订阅一条消息
99 | * @param queueName
100 | * @param consume
101 | * @param payload
102 | * @returns
103 | */
104 | once(queueName: Q, consume: Consume, payload?: any): this;
105 | once(queueName: Q): Promise;
106 | once(queueName: Q, consume?: Consume, payload?: any) {
107 | if (!isFunction(consume)) {
108 | return new Promise(resolve => {
109 | const consumeProxy = (content: any) => {
110 | this.off(queueName, consumeProxy);
111 | resolve(content);
112 | return true;
113 | };
114 | this.on(queueName, consumeProxy, payload);
115 | });
116 | } else {
117 | const consumeProxy = (content: any, next?: Next, payload?: any) => {
118 | this.off(queueName, consumeProxy);
119 | return consume(content, next, payload);
120 | };
121 | this.on(queueName, consumeProxy, payload);
122 | return this;
123 | }
124 | }
125 | }
126 |
--------------------------------------------------------------------------------
/docs/unmq.md:
--------------------------------------------------------------------------------
1 | # 快速开发
2 |
3 | 开发中常用的两种方式
4 |
5 | - `UNodeMQ` 一个`UNodeMQ`类中集合了多个交换机和多个队列,使用场景为多个交换机中有队列进行数据交叉路由的情况;`createUnmq`为`UNodeMQ` 类的函数式方法;
6 |
7 | - `QuickUNodeMQ` 包含一个交换机和多个队列的类,在单一路由的情况中可以快速开发使用;`createQuickUnmq`为`QuickUNodeMQ` 类的函数式方法;
8 |
9 | ## 1、UNodeMQ
10 |
11 | ```javascript
12 | import UNodeMQ, { createUnmq } from "u-node-mq";
13 | const unmq = new UNodeMQ(ExchangeCollection, QueueCollection);
14 | //or
15 | const unmq = createUnmq(ExchangeCollection, QueueCollection);
16 | ```
17 |
18 | 创建模块
19 |
20 | **UNodeMQ constructor 参数说明**
21 |
22 | | 名称 | 类型 | 必填 | 说明 |
23 | | ------------------ | --------------------- | ---- | ---------- |
24 | | ExchangeCollection | { string : Exchange } | 是 | 交换机集合 |
25 | | QueueCollection | { string : Queue } | 是 | 队列集合 |
26 |
27 | **unmq 方法说明**
28 |
29 | | 名称 | 参数类型 | 说明 |
30 | | ----------- | ---------------------------------- | -------------------------------------------------------------- |
31 | | emit | (ExchangeName , ...消息) | 发送数据到交换机,返回 this |
32 | | emitToQueue | (QueueName , ...消息) | 发送数据到交换机,返回 this |
33 | | on | (QueueName , 消费方法 , ?载荷消息) | 订阅队列消息,载荷信息每次都会发送给消费者,返回取消订阅的函数 |
34 | | off | (QueueName , ?消费方法) | 移除队列上的指定消费者或者移除队列上所有消费者,返回 this |
35 | | once | (QueueName , 消费方法 , ?载荷消息) | 只消费一条消息,返回 this |
36 | | 更多 | 未知 | 更多的内部方法 |
37 |
38 | ## 2、QuickUNodeMQ
39 |
40 | ```javascript
41 | import { QuickUNodeMQ, createQuickUnmq, ExchangeOption, Exchange } from "u-node-mq";
42 | const quickUnmq = new QuickUNodeMQ(ExchangeOption | Exchange, QueueCollection);
43 | //or
44 | const quickUnmq = createQuickUnmq(ExchangeOption | Exchange, QueueCollection);
45 | ```
46 |
47 | 创建模块
48 |
49 | **QuickUNodeMQ constructor 参数说明**
50 |
51 | | 名称 | 类型 | 必填 | 说明 |
52 | | --------------- | ------------------ | ---- | -------------- |
53 | | ExchangeOption | QueueCollection | 是 | 交互机配置参数 |
54 | | Exchange | Exchange | 是 | 交换机 |
55 | | QueueCollection | { string : Queue } | 是 | 队列集合 |
56 |
57 | **quickUnmq 方法说明**
58 |
59 | | 名称 | 参数类型 | 说明 |
60 | | ----------- | ---------------------------------- | -------------------------------------------------------------- |
61 | | emit | (ExchangeName , ...消息) | 发送数据到交换机,返回 this |
62 | | emitToQueue | (QueueName , ...消息) | 发送数据到交换机,返回 this |
63 | | on | (QueueName , 消费方法 , ?载荷消息) | 订阅队列消息,载荷信息每次都会发送给消费者,返回取消订阅的函数 |
64 | | off | (QueueName , ?消费方法) | 移除队列上的指定消费者或者移除队列上所有消费者,返回 this |
65 | | once | (QueueName , 消费方法 , ?载荷消息) | 只消费一条消息,返回 this |
66 |
67 | ## 3、SingleUNodeMQ
68 |
69 | ```javascript
70 | import { SingleUNodeMQ, createSingleUnmq, QueueOption, Queue } from "u-node-mq";
71 | const singleUnmq = new SingleUNodeMQ(QueueOption | Queue);
72 | //or
73 | const singleUnmq = createSingleUnmq(QueueOption | Queue);
74 | ```
75 |
76 | 创建模块
77 |
78 | **SingleUNodeMQ constructor 参数说明**
79 |
80 | | 名称 | 类型 | 必填 | 说明 |
81 | | ----------- | -------- | ---- | ------------ |
82 | | QueueOption | Exchange | 是 | 队列配置参数 |
83 | | Queue | Exchange | 是 | 队列实例 |
84 |
85 | **singleUnmq 方法说明**
86 |
87 | | 名称 | 参数类型 | 说明 |
88 | | ---- | ---------------------------------------------------------- | -------------------------------------------------------------- |
89 | | emit | ( ...消息) | 发送数据到队列,返回 this |
90 | | on | ( 消费方法 , ?载荷消息) | 订阅队列消息,载荷信息每次都会发送给消费者,返回取消订阅的函数 |
91 | | off | ( ?消费方法) | 移除队列上的指定消费者或者移除队列上所有消费者,返回 this |
92 | | once | ( 消费方法 , ?载荷消息) | 只消费一条消息,返回 this |
93 | | add | 向当前队列添加 operators | 返回 this |
94 | | fork | 在当前队列队尾添加一个新的队列,使当前队列和新队列数据连通 | 返回新的队列 |
95 |
--------------------------------------------------------------------------------
/src/core/UNodeMQ.ts:
--------------------------------------------------------------------------------
1 | import { isFunction } from "../utils/tools";
2 | import { Exchange, Queue } from "../index";
3 | import { Consume, Next } from "../internal/Consumer";
4 | import Collection from "./Collection";
5 | import { Plugin } from "@/plugins/index";
6 |
7 | /**
8 | * 获取队列名称返回的promise导致消费事件加入微任务队列延迟消费(ios属于加入普通任务队列)
9 | * 这样同时保证了在观察者模式中数据能准确分发
10 | */
11 |
12 | export type ReturnPanShapeExchange = T extends Exchange ? U : never;
13 | export type ReturnPanShapeQueue = T extends Queue ? U : never;
14 |
15 | /**
16 | * 使用普通函数创建unmq
17 | * @param exchangeCollection
18 | * @param queueCollection
19 | * @returns
20 | */
21 | export function createUnmq>, QueueCollection extends Record>>(
22 | exchangeCollection: ExchangeCollection,
23 | queueCollection: QueueCollection,
24 | ) {
25 | return new UNodeMQ(exchangeCollection, queueCollection);
26 | }
27 | /**
28 | * UNodeMQ 发布订阅模型
29 | * 从3.7.0版本开始,一个u-node-mq仅支持一种消息类型
30 | */
31 | export default class UNodeMQ<
32 | D,
33 | ExchangeCollection extends Record>,
34 | QueueCollection extends Record>,
35 | > extends Collection {
36 | constructor(exchangeCollection: ExchangeCollection, queueCollection: QueueCollection) {
37 | super(exchangeCollection, queueCollection);
38 | }
39 | private readonly installedPlugins: Set = new Set();
40 | use(plugin: Plugin, ...options: any[]) {
41 | if (this.installedPlugins.has(plugin)) {
42 | console.log(`Plugin has already been applied to target unmq.`);
43 | } else if (plugin && isFunction(plugin.install)) {
44 | this.installedPlugins.add(plugin);
45 | plugin.install(this, ...options);
46 | } else if (isFunction(plugin)) {
47 | this.installedPlugins.add(plugin);
48 | plugin(this, ...options);
49 | }
50 | return this;
51 | }
52 | /**
53 | * 发射数据到交换机
54 | * @param contentList 消息体列表
55 | * @returns
56 | */
57 | emit(exchangeName: E, ...contentList: ReturnPanShapeExchange[]) {
58 | super.pushContentListToExchange(exchangeName, ...contentList);
59 | return this;
60 | }
61 | /**
62 | * 发射数据到队列
63 | * @param queueName
64 | * @param contentList
65 | * @returns
66 | */
67 | emitToQueue(queueName: Q, ...contentList: ReturnPanShapeQueue[]) {
68 | super.pushContentListToQueue(queueName, ...contentList);
69 | return this;
70 | }
71 |
72 | /**
73 | * 订阅队列消息
74 | * @param queueName 队列名称
75 | * @param consume 消费方法
76 | * @param payload 固定参数,有效载荷,在每次消费的时候都传给消费者
77 | * @returns
78 | */
79 | on(queueName: Q, consume: Consume>, payload?: any) {
80 | super.subscribeQueue(queueName, consume, payload);
81 | return () => this.off(queueName, consume);
82 | }
83 |
84 | /**
85 | * 移除消费者
86 | * @param queueName
87 | * @param consume
88 | */
89 | off(queueName: Q, consume?: Consume>): this {
90 | super.unsubscribeQueue(queueName, consume);
91 | return this;
92 | }
93 |
94 | /**
95 | * 订阅一条消息
96 | * 不传入消费方法,则返回Promise
97 | * @param queueName
98 | * @param consume
99 | * @param payload
100 | * @returns
101 | */
102 | once(queueName: Q, consume: Consume>, payload?: any): this;
103 | once(queueName: Q): Promise>;
104 | once(queueName: Q, consume?: Consume>, payload?: any) {
105 | if (!isFunction(consume)) {
106 | return new Promise(resolve => {
107 | const consumeProxy = (content: any) => {
108 | this.off(queueName, consumeProxy);
109 | resolve(content);
110 | return true;
111 | };
112 | this.on(queueName, consumeProxy, payload);
113 | });
114 | } else {
115 | const consumeProxy = (content: any, next?: Next, payload?: any) => {
116 | this.off(queueName, consumeProxy);
117 | return consume(content, next, payload);
118 | };
119 | this.on(queueName, consumeProxy, payload);
120 | return this;
121 | }
122 | }
123 | }
124 |
--------------------------------------------------------------------------------
/docs/internal/index.md:
--------------------------------------------------------------------------------
1 | # 工作原理
2 |
3 | 下面是一张工作原理的简图;
4 |
5 |
6 |
7 | ## 五大基础组件
8 |
9 | - `Exchange` 交换机,每个交换机就是一个分发数据到队列的路由;
10 |
11 | - `Queue` 队列,队列是一个能存储数据和配合 operators 处理数据的组件,丰富的配置和管道操作符可以实现更复杂的功能;
12 |
13 | - `News` 消息,存储内容的组件;
14 |
15 | - `Consumer` 消费者,处理消息的业务逻辑代码;
16 |
17 | - `Logs` 日志消息,方便调试开发,也可将日志发送到服务器;
18 |
19 | ## 1、Exchange
20 |
21 | ```javascript
22 | const exchange = new Exchange(Option);
23 | ```
24 |
25 | 创建交换机
26 |
27 | **Option 参数说明**
28 |
29 | | 名称 | 类型 | 必填 | 说明 |
30 | | -------- | -------- | ---- | ---------------------------------------- |
31 | | name | string | 否 | 交换机名称 |
32 | | routes | string[] | 否 | 需要匹配的队列名称 |
33 | | repeater | Function | 否 | 自定义路由函数,填写该参数 routes 将失效 |
34 |
35 | ## 2、Queue
36 |
37 | ```javascript
38 | const queue = new Option(Option);
39 | ```
40 |
41 | 创建队列
42 |
43 | **Option 参数说明**
44 |
45 | | 名称 | 类型 | 必填 | 默认 | 说明 |
46 | | ------- | ----------------- | ---- | ----- | --------------------------------------------------------------------------- |
47 | | name | string | 否 | | 队列名称 |
48 | | mode | "Random" \| "All" | 否 | "All" | 消费模式,Random 代表随机抽取一个消费者消费,All 代表所有消费者都会消费消息 |
49 | | ask | boolean | 否 | false | 是否需要消息确认,为 true,则需要手动确认消息 |
50 | | rcn | number | 否 | 3 | 消费失败后可重复消费次数 |
51 | | async | noolean | 否 | false | 是否是异步队列,为 false 则会一条消息消费完成或者失败才会消费下一条消息 |
52 | | maxTime | number | 否 | 3000 | 最长消费时长,单位毫秒,小于 0 代表不限时长 |
53 |
54 | ## 3、News
55 |
56 | ```javascript
57 | const news = new News(Any);
58 | ```
59 |
60 | 创建消息
61 |
62 | **news 属性说明**
63 |
64 | | 名称 | 类型 | 说明 |
65 | | ------------- | ------ | ------------------ |
66 | | createTime | number | 消息创建时间戳 |
67 | | content | Any | 消息内容 |
68 | | consumedTimes | number | 剩余可重复消费次数 |
69 |
70 | ## 4、Consumer
71 |
72 | ```javascript
73 | const consumer = new Consumer(Consume, PayLoad);
74 | ```
75 |
76 | 创建消费者
77 |
78 | **Consume 参数说明**
79 |
80 | | 参数 | 类型 | 说明 |
81 | | ------ | ------- | --------------------------------------------------------------- |
82 | | 参数 1 | D | 消息内容 |
83 | | 参数 2 | next | 是否确认消费,执行 next 默认为确认消费,传 false 则代表消费失败 |
84 | | 参数 3 | payload | 固定消费内容,每次消费都会传递 |
85 |
86 | **consumer 属性说明**
87 |
88 | | 名称 | 类型 | 说明 |
89 | | ---------- | -------- | ---------------- |
90 | | createTime | number | 消费者创建时间戳 |
91 | | consume | Function | 消费方法 |
92 | | payload | any | 固定载荷 |
93 |
94 | ## 5、Logs
95 |
96 | Logs 是一个不可实例化的静态类,开发者可以使用`setLogsConfig`对日志输出进行配置;
97 |
98 | ```javascript
99 | import { Logs } from "u-node-mq";
100 | Logs.setLogsConfig(LogsConfig);
101 | ```
102 |
103 | **LogsConfig 对象说明**
104 |
105 | | 参数 | 类型 | 默认值 | 说明 |
106 | | -------------- | --------------- | ----------------------------------------- | --------------------------------------------------------------------- |
107 | | logs | boolean | false | 控制是否输出日志 |
108 | | types | LogsType[] | ["console"] | LogsType 为 "custom" 或者 "console" |
109 | | logsComponents | LogsComponent[] | ["Exchange", "Queue", "News", "Consumer"] | 需要输出日志的组件 |
110 | | customFunction | Function | CustomLogFunction | 需要自定义处理日志的功能函数 |
111 | | include | string[] | [] | 包含要输出 Exchange 和 Queue 日志的 name ,为空数组代表包含所有组件 |
112 | | exclude | string[] | [] | 过滤要输出 Exchange 和 Queue 日志的 name ,为空数组代表不过滤任何组件 |
113 |
114 | 如需开发服务端日志系统,开发者需要实现`CustomLogFunction`方法,[点击查看`CustomLogFunction`实现细节](./logs_sys_dev.md)
115 |
116 | ## go 控制台快速开发日志
117 |
118 | `unmq`内置了`go`开发的控制台日志输出,开发者可快速响应开发;[点击查看使用方法](./termui.md)
119 |
--------------------------------------------------------------------------------
/src/core/Collection.ts:
--------------------------------------------------------------------------------
1 | import { Exchange, Queue, News } from "../index";
2 | import { Consume } from "../internal/Consumer";
3 | import ExchangeCollectionHandle from "./ExchangeCollectionHandle";
4 | import QueueCollectionHandle from "./QueueCollectionHandle";
5 | export default class Collection>, QueueCollection extends Record>> {
6 | /**
7 | * 交换机集合
8 | */
9 | private readonly exchangeCollectionHandle = new ExchangeCollectionHandle();
10 | /**
11 | * 队列集合
12 | */
13 | private readonly queueCollectionHandle = new QueueCollectionHandle();
14 |
15 | constructor(exchangeCollection: ExchangeCollection, queueCollection: QueueCollection) {
16 | for (const name in exchangeCollection) {
17 | exchangeCollection[name].name = name;
18 | }
19 | for (const name in queueCollection) {
20 | queueCollection[name].name = name;
21 | }
22 | this.exchangeCollectionHandle.setExchangeCollection(exchangeCollection);
23 | this.queueCollectionHandle.setQueueCollection(queueCollection);
24 | }
25 | /**
26 | * 根据交换机名称获取交换机
27 | * @param exchangeName
28 | * @returns
29 | */
30 | getExchange(exchangeName: E) {
31 | return this.exchangeCollectionHandle.getExchange(exchangeName);
32 | }
33 | /**
34 | * 获取交换机集合列表
35 | * @returns
36 | */
37 | getExchangeList() {
38 | return this.exchangeCollectionHandle.getExchangeList();
39 | }
40 | /**
41 | * 添加交换机
42 | * @param exchangeName
43 | * @param exchange
44 | * @returns
45 | */
46 | addExchage(exchangeName: string, exchange: Exchange) {
47 | return this.exchangeCollectionHandle.addExchage(exchangeName, exchange);
48 | }
49 | /**
50 | * 根据
51 | * @param queueName
52 | * @returns
53 | */
54 | getQueue(queueName: Q) {
55 | return this.queueCollectionHandle.getQueue(queueName);
56 | }
57 | /**
58 | * 获取队列集合列表
59 | * @returns
60 | */
61 | getQueueList() {
62 | return this.queueCollectionHandle.getQueueList();
63 | }
64 | /**
65 | * 添加一个队列到队列集合
66 | * @param queurName
67 | * @param queue
68 | * @returns
69 | */
70 | addQueue(queurName: string, queue: Queue) {
71 | return this.queueCollectionHandle.addQueue(queurName, queue);
72 | }
73 | /**
74 | * 发送消息到交换机
75 | * @param exchangeName
76 | * @param news
77 | */
78 | pushNewsListToExchange(exchangeName: E, ...news: News[]) {
79 | for (const newsItem of news) {
80 | //分别发送每一条消息
81 | this.exchangeCollectionHandle.getQueueNameList(exchangeName, newsItem.content).then(queueNameList => {
82 | for (const queueName in queueNameList) {
83 | this.pushNewsListToQueue(queueName, newsItem);
84 | }
85 | });
86 | }
87 | }
88 | /**
89 | * 发送消息到队列
90 | * @param queueName
91 | * @param news
92 | */
93 | pushNewsListToQueue(queueName: Q, ...news: News[]) {
94 | for (const newsItem of news) {
95 | //分别发送每一条消息
96 | this.queueCollectionHandle.pushNewsToQueue(queueName, newsItem);
97 | }
98 | }
99 | /**
100 | * 发送消息内容到交换机
101 | * @param exchangeName
102 | * @param contentList
103 | */
104 | pushContentListToExchange(exchangeName: E, ...contentList: D[]) {
105 | for (const content of contentList) {
106 | //分别发送每一条消息
107 | this.exchangeCollectionHandle.getQueueNameList(exchangeName, content).then(queueNameList => {
108 | for (const queueName of queueNameList) {
109 | this.pushContentListToQueue(queueName, content);
110 | }
111 | });
112 | }
113 | }
114 | /**
115 | * 发送消息内容到队列
116 | * @param queueName
117 | * @param contentList
118 | */
119 | pushContentListToQueue(queueName: Q, ...contentList: D[]) {
120 | for (const content of contentList) {
121 | //分别发送每一条消息
122 | this.queueCollectionHandle.pushContentToQueue(queueName, content);
123 | }
124 | }
125 | /**
126 | * 订阅队列
127 | * @param queueName
128 | * @param consume
129 | * @param payload
130 | */
131 | subscribeQueue(queueName: Q, consume: Consume, payload?: any) {
132 | this.queueCollectionHandle.subscribeQueue(queueName, consume, payload);
133 | }
134 | /**
135 | * 取消订阅队列
136 | */
137 | unsubscribeQueue(queueName: Q, consume?: Consume) {
138 | this.queueCollectionHandle.unsubscribeQueue(queueName, consume);
139 | }
140 | }
141 |
--------------------------------------------------------------------------------
/src/plugins/wx-logs/index.ts:
--------------------------------------------------------------------------------
1 | import { LevelOutputOption, defaultOption, LOG_LEVEL, OUTPUT_TYPE } from "./config";
2 | import { getUUID } from "@/utils/tools";
3 | import { onListener } from "./listener";
4 | import UNodeMQ, { Exchange, Queue } from "@/index";
5 | import { wxApi } from "./proxyApi";
6 |
7 | function proxyConsle(this: WxLogsPlugin, content: Message) {
8 | console.log("----consle 日志---");
9 | console[content.type](content);
10 | }
11 |
12 | function proxyRealtime(this: WxLogsPlugin, content: Message) {
13 | console.log("----realtime 日志---");
14 | this.addRealtimeLog(content.type, content);
15 | }
16 |
17 | function proxyRequest(this: WxLogsPlugin, content: Message) {
18 | console.log("----request 日志---");
19 | console.log(content);
20 | }
21 |
22 | const ProxyList = {
23 | [OUTPUT_TYPE.Console]: proxyConsle,
24 | [OUTPUT_TYPE.Realtime]: proxyRealtime,
25 | [OUTPUT_TYPE.Request]: proxyRequest,
26 | };
27 |
28 | function getMiniprogramInfo() {
29 | return {
30 | windowInfo: wxApi("getWindowInfo")?.(),
31 | skylineInfoSync: wxApi("getSkylineInfoSync")?.(),
32 | deviceInfo: wxApi("getDeviceInfo")?.(),
33 | appBaseInfo: wxApi("getAppBaseInfo")?.(),
34 | appAuthorizeSetting: wxApi("getAppAuthorizeSetting")?.(),
35 | launchOptionsSync: wxApi("getLaunchOptionsSync")?.(),
36 | apiCategory: wxApi("getApiCategory")?.(),
37 | accountInfoSync: wxApi("getAccountInfoSync")?.(),
38 | };
39 | }
40 | type Message = {
41 | type: LOG_LEVEL;
42 | uuid: string;
43 | content?: unknown;
44 | };
45 | /**
46 | * 微信小程序日志监控
47 | */
48 | export default class WxLogsPlugin {
49 | private unmq: UNodeMQ>, Record>> | null = null;
50 | /**
51 | * 实时日志
52 | *
53 |
54 | 注意事项
55 | 由于后台资源限制,“实时日志”使用规则如下:
56 |
57 | 为了定位问题方便,日志是按页面划分的,某一个页面,在一定时间内(最短为5秒,最长为页面从显示到隐藏的时间间隔)打的日志,会聚合成一条日志上报,并且在小程序管理后台上可以根据页面路径搜索出该条日志。
58 | 每个小程序账号,We分析基础版每天限制5000条日志,We分析专业版为50000条,且支持购买配置升级或购买额外的上报扩充包。日志根据版本配置,会保留7天/14天/30天不等,建议遇到问题及时定位。
59 | 一条日志的上限是5KB,最多包含200次打印日志函数调用(info、warn、error调用都算),所以要谨慎打日志,避免在循环里面调用打日志接口,避免直接重写console.log的方式打日志。
60 | 意见反馈里面的日志,可根据OpenID搜索日志。
61 | setFilterMsg和addFilterMsg 可设置类似日志tag的过滤字段。如需添加多个关键字,建议使用addFilterMsg。例如addFilterMsg('scene1'), addFilterMsg('scene2'),addFilterMsg('scene3'),设置后在小程序管理后台可随机组合三个关键字进行检索,如:“scene1 scene2 scene3”、“scene1 scene2”、 “scene1 scene3” 或 “scene2”等(以空格分隔,故addFilterMsg不能带空格)。以上几种检索方法均可检索到该条日志,检索条件越多越精准。
62 | 目前为了方便做日志分析,插件端实时日志只支持 key-value 格式。
63 | 实时日志目前只支持在手机端测试。工具端的接口可以调用,但不会上报到后台。
64 | 开发版、体验版的实时日志,不计入相关quota,即无使用上限。
65 |
66 | */
67 | private readonly realtimeLog = wxApi("getRealtimeLogManager")?.();
68 | /**
69 | * 初始化获取当前系统信息
70 | */
71 | private readonly systemInfo = getMiniprogramInfo();
72 | /**
73 | * uuid
74 | */
75 | private readonly uuid = getUUID();
76 | /**
77 | *
78 | * @param option
79 | */
80 | constructor(private readonly option: LevelOutputOption = defaultOption) {}
81 | /**
82 | *
83 | * @param unmq
84 | */
85 | install(unmq: UNodeMQ>, Record>>) {
86 | //
87 | /**
88 | * 初始化交换机
89 | */
90 | Object.entries(LOG_LEVEL).forEach(([, value]) => {
91 | unmq.addExchage(value, new Exchange());
92 | });
93 |
94 | /**
95 | * 初始化队列
96 | */
97 | Object.entries(OUTPUT_TYPE).forEach(([, value]) => {
98 | unmq.addQueue(value, new Queue());
99 | });
100 |
101 | /**
102 | * 设置交换机路由
103 | */
104 | Object.entries(this.option).forEach(item => {
105 | const e = unmq.getExchange(item[0] as LOG_LEVEL);
106 | if (e === null) return e;
107 | e.setRoutes(item[1]);
108 | });
109 |
110 | /**
111 | * 添加默认的监听器
112 | */
113 | Object.values(OUTPUT_TYPE).forEach(item => {
114 | unmq.on(item, ProxyList[item].bind(this));
115 | });
116 |
117 | this.unmq = unmq;
118 |
119 | onListener.apply(this);
120 |
121 | this[LOG_LEVEL.Warn](this.systemInfo);
122 | }
123 | /**
124 | * 添加日志到实时日志
125 | * @param logType
126 | * @param msg
127 | */
128 | addRealtimeLog(logType: LOG_LEVEL, msg: Message) {
129 | this.realtimeLog?.[logType](msg);
130 | }
131 |
132 | [LOG_LEVEL.Info](content: unknown) {
133 | if (this.unmq === null) return false;
134 | this.unmq.emit(LOG_LEVEL.Info, {
135 | type: LOG_LEVEL.Info,
136 | uuid: this.uuid,
137 | content,
138 | });
139 | return true;
140 | }
141 | [LOG_LEVEL.Warn](content: unknown) {
142 | if (this.unmq === null) return false;
143 | this.unmq.emit(LOG_LEVEL.Warn, {
144 | type: LOG_LEVEL.Warn,
145 | uuid: this.uuid,
146 | content,
147 | });
148 | return true;
149 | }
150 | [LOG_LEVEL.Error](content: unknown) {
151 | if (this.unmq === null) return false;
152 | this.unmq.emit(LOG_LEVEL.Error, {
153 | type: LOG_LEVEL.Error,
154 | uuid: this.uuid,
155 | content,
156 | });
157 | return true;
158 | }
159 | }
160 |
--------------------------------------------------------------------------------
/test/SingleUNodeMQ.test.ts:
--------------------------------------------------------------------------------
1 | import { Queue, SingleUNodeMQ, Logs, createSingleUnmq } from "../src/index";
2 | import { expect, test, describe } from "@jest/globals";
3 | import { promiseSetTimeout } from "../src/utils/tools";
4 | Logs.setLogsConfig({ logs: false });
5 | describe("QuickUNodeMQ", () => {
6 | /**
7 | * 创建测试list的方法
8 | * @returns
9 | */
10 | function createListEqual() {
11 | const list: T[] = [];
12 | return {
13 | list,
14 | equal(list: T[], time = 0, callback?: () => void) {
15 | setTimeout(() => {
16 | expect(this.list).toEqual(list);
17 | if (callback) callback();
18 | }, time);
19 | },
20 | equalDisorder() {
21 | //TODO:
22 | },
23 | };
24 | }
25 |
26 | test("SingleUNodeMQ.emit", function (done) {
27 | type T = number;
28 | const singleUnmq = new SingleUNodeMQ(new Queue());
29 |
30 | singleUnmq.emit(1);
31 | singleUnmq.emit(2, 3, 4, 5);
32 |
33 | const listEqual = createListEqual();
34 | singleUnmq.on((res: T) => listEqual.list.push(res));
35 | listEqual.equal([1, 2, 3, 4, 5], 0, done);
36 | });
37 | test("SingleUNodeMQ.on", function (done) {
38 | expect.assertions(10);
39 |
40 | type T = number;
41 | const singleUnmq1 = new SingleUNodeMQ(new Queue());
42 |
43 | singleUnmq1.emit(1, 2, 3, 4, 5);
44 |
45 | singleUnmq1.on((res: T, payload: any) => {
46 | expect([1, 2, 3, 4, 5].indexOf(res) !== -1).toBe(true);
47 | expect(payload).toEqual("payload");
48 | }, "payload");
49 |
50 | setTimeout(done);
51 | });
52 | test("SingleUNodeMQ.once", async function () {
53 | expect.assertions(5);
54 |
55 | type T = number;
56 | const singleUnmq1 = new SingleUNodeMQ(new Queue());
57 |
58 | singleUnmq1.emit(1, 2, 3);
59 |
60 | const res1 = await singleUnmq1.once();
61 | expect(res1).toEqual(1);
62 | /**
63 |
64 |
65 | 当队列设置为同步消费的时候
66 |
67 | 由于消费者在消费一条消息以后,队列并不会立马收到回调,所以会在下次事件循环将队列的消费方法放开,所以会积累一次事件循环的消费者进入队列
68 |
69 | 因此下次两次once方法实际上是订阅的同一条消息
70 |
71 |
72 | */
73 |
74 | /**
75 |
76 |
77 | jest内部使用try catch捕获不到异步情况下的断言错误,所以异步代码即使断言错误报错了,只会阻塞下面的代码执行,不会被jest捕获到
78 |
79 | 因此这里需要将下面的once方法手动封装成promise方法
80 |
81 |
82 | ~~~typescript
83 |
84 | quickUnmq1
85 | .once("qu1", (res2: T) => {
86 | expect(res2).toEqual(2);
87 | quickUnmq1.once("qu1", (res3: T) => {
88 | expect(res3).toEqual(4);
89 | done();
90 | });
91 | })
92 | .once(
93 | "qu1",
94 | (res2: T, payload: any) => {
95 | expect(res2).toEqual(2);
96 | expect(payload).toEqual("payload");
97 | },
98 | "payload",
99 | );
100 | ~~~
101 |
102 | */
103 |
104 | const [res2_1, res2_2] = await Promise.all([
105 | new Promise(res => {
106 | singleUnmq1.once((res2: T) => {
107 | res(res2);
108 | });
109 | }),
110 | new Promise(res => {
111 | singleUnmq1.once((res2: T, payload) => {
112 | res({ res2, payload });
113 | }, "payload");
114 | }),
115 | ]);
116 |
117 | expect(res2_1).toEqual(2);
118 | expect(res2_2.res2).toEqual(2);
119 | expect(res2_2.payload).toEqual("payload");
120 |
121 | const res3 = await new Promise(res => {
122 | singleUnmq1.once((res3: T) => {
123 | res(res3);
124 | });
125 | });
126 |
127 | expect(res3).toEqual(3);
128 |
129 | //
130 | });
131 |
132 | test("SingleUNodeMQ.off", async function () {
133 | expect.assertions(2);
134 |
135 | type T = number;
136 | const singleUnmq1 = new SingleUNodeMQ(new Queue({ async: false }));
137 | const singleUnmq2 = new SingleUNodeMQ(new Queue({ async: true }));
138 |
139 | singleUnmq1.emit(1, 2);
140 | const res = await new Promise(res => {
141 | singleUnmq1.on((data: T) => {
142 | res(data);
143 | singleUnmq1.off();
144 | });
145 | });
146 | expect(res).toEqual(1);
147 |
148 | singleUnmq2.emit(3, 4, 5);
149 | await promiseSetTimeout(0);
150 | const res_a = await new Promise(res => {
151 | const a: any[] = [];
152 | singleUnmq2.on((data: T) => {
153 | a.push(data);
154 | })();
155 | setTimeout(() => {
156 | res(a);
157 | });
158 | });
159 | expect(res_a).toEqual([3, 4, 5]);
160 | });
161 | });
162 |
163 | describe("createSingleUnmq", () => {
164 | //
165 | test("createSingleUnmq_init", function () {
166 | //
167 |
168 | expect(createSingleUnmq() instanceof SingleUNodeMQ).toBeTruthy();
169 | expect(createSingleUnmq({}) instanceof SingleUNodeMQ).toBeTruthy();
170 | expect(createSingleUnmq(new SingleUNodeMQ()) instanceof SingleUNodeMQ).toBeTruthy();
171 | });
172 |
173 | /**
174 | * 测试一个消息返回promise false重复消费
175 | */
176 |
177 | test("createSingleUnmq_reset", function () {
178 | const t = createSingleUnmq({ ask: true, rcn: 10 });
179 | let i = 0;
180 | t.on(async () => {
181 | await promiseSetTimeout(100);
182 | i++;
183 | return false;
184 | });
185 | setTimeout(() => {
186 | expect(i).toEqual(10);
187 | }, 1200);
188 | });
189 | });
190 |
--------------------------------------------------------------------------------
/src/plugins/storage/index.ts:
--------------------------------------------------------------------------------
1 | import StorageAdapterAbstract from "./StorageAdapterAbstract";
2 | import UNodeMQ, { isString, isObject, Exchange, Queue, PluginInstallFunction } from "../../index";
3 | import StorageSignAbstract from "./StorageSignAbstract";
4 | import { devalue, envalue } from "./storageTypeof";
5 |
6 | //TODO:实现storage存储超出警告
7 |
8 | export enum StorageType {
9 | SESSION = "session",
10 | LOCAL = "local",
11 | }
12 |
13 | type StorageConfig = {
14 | storageType?: StorageType;
15 | storageMemory?: StorageAdapterAbstract;
16 | storageSign?: StorageSignAbstract;
17 | };
18 |
19 | class StorageMemory implements StorageAdapterAbstract {
20 | private memoryData: Record = {};
21 | init(o: Record): void {
22 | this.memoryData = o;
23 | }
24 | getData(key: string): string {
25 | return this.memoryData[key];
26 | }
27 | setData(key: string, value: string): void {
28 | this.memoryData[key] = value;
29 | }
30 | }
31 |
32 | export type Plugin = {
33 | install: PluginInstallFunction;
34 | };
35 |
36 |
37 | /**
38 | * storage plugin
39 | * StorageTypeList
40 | */
41 | export default class StoragePlugin implements Plugin {
42 | private storageMemory: StorageAdapterAbstract = new StorageMemory(); //代理访问内存
43 | private storageSign: StorageSignAbstract | null = null; //加密方法
44 | private unmq: UNodeMQ>, Record>> | null = null;
45 | storage: Record = {};
46 | constructor( storageType: Record, storageConfig?: StorageConfig) {
47 | if (storageConfig?.storageMemory) this.storageMemory = storageConfig.storageMemory;
48 | if (storageConfig?.storageSign) this.storageSign = storageConfig.storageSign;
49 | if (storageConfig?.storageType) this.storageType = storageConfig.storageType;
50 | this.storage = storageType;
51 | //init之前先直接从缓存中取
52 | for (const name in storage) {
53 | Object.defineProperty(storage, name, {
54 | get() {
55 | return this.getStorageSync(name);
56 | },
57 | set(value: any) {
58 | this.setStorageSync(name, value);
59 | },
60 | });
61 | }
62 | }
63 | init() {
64 | //内存缓存初始化之前从storage里面获取
65 | if (this.unmq === null) throw "storage plugin 未安装";
66 | const queueNameList = this.unmq.getQueueList().map((item) => item.name);
67 | for (const name of queueNameList) {
68 | if (name === undefined) continue;
69 | this.storageMemory.setData(name, this.getStorageSync(name));
70 | Object.defineProperty(__storage, name, {
71 | get() {
72 | return this.storageMemory.getData(name);
73 | },
74 | set(value: any) {
75 | this.setStorageSync(name, value);
76 | this.storageMemory.setData(name, value);
77 | },
78 | });
79 | }
80 | }
81 | install>, QueueCollection extends Record>>(
82 | // install(
83 | unmq: UNodeMQ,
84 | ...options: any[]
85 | ) {
86 | this.unmq = unmq;
87 | type B = {
88 | [K in keyof T]: unknown;
89 | };
90 | this.storage = {} as B;
91 | const queueNameList = unmq.getQueueList().map((item) => item.name);
92 | for (const name of queueNameList) {
93 | if (name === undefined) continue;
94 | this.storage[name as keyof B] = {};
95 | Object.defineProperty(this.storage, name, {
96 | get() {
97 | return this.getStorageSync(name);
98 | },
99 | set(value: any) {
100 | this.setStorageSync(name, value);
101 | },
102 | });
103 | }
104 | }
105 | /**
106 | *
107 | * @param name
108 | * @returns
109 | */
110 | private getStorageSync(name: string) {
111 | let value = null;
112 | const list = { [StorageType.SESSION]: sessionStorage, [StorageType.LOCAL]: localStorage };
113 | if (this.storageSign) {
114 | const storage = list[this.storageType].getItem(this.storageSign.encryptName(name));
115 | if (storage) value = this.storageSign.decryptValue(storage);
116 | } else value = list[this.storageType].getItem(name);
117 | //数据类型编码解码
118 | if (value) return devalue(value);
119 | else return null;
120 | }
121 | /**
122 | * 同步设置缓存
123 | * @param key
124 | * @param type
125 | * @param value
126 | */
127 | private setStorageSync(name: string, value: any) {
128 | if (value === null || value === undefined) return this.removeStorageSync(name);
129 | const list = { [StorageType.SESSION]: sessionStorage, [StorageType.LOCAL]: localStorage };
130 | value = envalue(value);
131 | if (this.storageSign)
132 | list[this.storageType].setItem(this.storageSign.encryptName(name), this.storageSign.encryptValue(value));
133 | else list[this.storageType].setItem(name, value);
134 | }
135 | /**
136 | *
137 | * @param name
138 | */
139 | private removeStorageSync(name: string) {
140 | const list = { [StorageType.SESSION]: sessionStorage, [StorageType.LOCAL]: localStorage };
141 | if (this.storageSign) list[this.storageType].removeItem(this.storageSign.encryptName(name));
142 | else list[this.storageType].removeItem(name);
143 | }
144 | }
145 |
146 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Contributor Covenant Code of Conduct
2 |
3 | ## Our Pledge
4 |
5 | We as members, contributors, and leaders pledge to make participation in our
6 | community a harassment-free experience for everyone, regardless of age, body
7 | size, visible or invisible disability, ethnicity, sex characteristics, gender
8 | identity and expression, level of experience, education, socio-economic status,
9 | nationality, personal appearance, race, religion, or sexual identity
10 | and orientation.
11 |
12 | We pledge to act and interact in ways that contribute to an open, welcoming,
13 | diverse, inclusive, and healthy community.
14 |
15 | ## Our Standards
16 |
17 | Examples of behavior that contributes to a positive environment for our
18 | community include:
19 |
20 | * Demonstrating empathy and kindness toward other people
21 | * Being respectful of differing opinions, viewpoints, and experiences
22 | * Giving and gracefully accepting constructive feedback
23 | * Accepting responsibility and apologizing to those affected by our mistakes,
24 | and learning from the experience
25 | * Focusing on what is best not just for us as individuals, but for the
26 | overall community
27 |
28 | Examples of unacceptable behavior include:
29 |
30 | * The use of sexualized language or imagery, and sexual attention or
31 | advances of any kind
32 | * Trolling, insulting or derogatory comments, and personal or political attacks
33 | * Public or private harassment
34 | * Publishing others' private information, such as a physical or email
35 | address, without their explicit permission
36 | * Other conduct which could reasonably be considered inappropriate in a
37 | professional setting
38 |
39 | ## Enforcement Responsibilities
40 |
41 | Community leaders are responsible for clarifying and enforcing our standards of
42 | acceptable behavior and will take appropriate and fair corrective action in
43 | response to any behavior that they deem inappropriate, threatening, offensive,
44 | or harmful.
45 |
46 | Community leaders have the right and responsibility to remove, edit, or reject
47 | comments, commits, code, wiki edits, issues, and other contributions that are
48 | not aligned to this Code of Conduct, and will communicate reasons for moderation
49 | decisions when appropriate.
50 |
51 | ## Scope
52 |
53 | This Code of Conduct applies within all community spaces, and also applies when
54 | an individual is officially representing the community in public spaces.
55 | Examples of representing our community include using an official e-mail address,
56 | posting via an official social media account, or acting as an appointed
57 | representative at an online or offline event.
58 |
59 | ## Enforcement
60 |
61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be
62 | reported to the community leaders responsible for enforcement at
63 | uaoie@qq.com.
64 | All complaints will be reviewed and investigated promptly and fairly.
65 |
66 | All community leaders are obligated to respect the privacy and security of the
67 | reporter of any incident.
68 |
69 | ## Enforcement Guidelines
70 |
71 | Community leaders will follow these Community Impact Guidelines in determining
72 | the consequences for any action they deem in violation of this Code of Conduct:
73 |
74 | ### 1. Correction
75 |
76 | **Community Impact**: Use of inappropriate language or other behavior deemed
77 | unprofessional or unwelcome in the community.
78 |
79 | **Consequence**: A private, written warning from community leaders, providing
80 | clarity around the nature of the violation and an explanation of why the
81 | behavior was inappropriate. A public apology may be requested.
82 |
83 | ### 2. Warning
84 |
85 | **Community Impact**: A violation through a single incident or series
86 | of actions.
87 |
88 | **Consequence**: A warning with consequences for continued behavior. No
89 | interaction with the people involved, including unsolicited interaction with
90 | those enforcing the Code of Conduct, for a specified period of time. This
91 | includes avoiding interactions in community spaces as well as external channels
92 | like social media. Violating these terms may lead to a temporary or
93 | permanent ban.
94 |
95 | ### 3. Temporary Ban
96 |
97 | **Community Impact**: A serious violation of community standards, including
98 | sustained inappropriate behavior.
99 |
100 | **Consequence**: A temporary ban from any sort of interaction or public
101 | communication with the community for a specified period of time. No public or
102 | private interaction with the people involved, including unsolicited interaction
103 | with those enforcing the Code of Conduct, is allowed during this period.
104 | Violating these terms may lead to a permanent ban.
105 |
106 | ### 4. Permanent Ban
107 |
108 | **Community Impact**: Demonstrating a pattern of violation of community
109 | standards, including sustained inappropriate behavior, harassment of an
110 | individual, or aggression toward or disparagement of classes of individuals.
111 |
112 | **Consequence**: A permanent ban from any sort of public interaction within
113 | the community.
114 |
115 | ## Attribution
116 |
117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage],
118 | version 2.0, available at
119 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
120 |
121 | Community Impact Guidelines were inspired by [Mozilla's code of conduct
122 | enforcement ladder](https://github.com/mozilla/diversity).
123 |
124 | [homepage]: https://www.contributor-covenant.org
125 |
126 | For answers to common questions about this code of conduct, see the FAQ at
127 | https://www.contributor-covenant.org/faq. Translations are available at
128 | https://www.contributor-covenant.org/translations.
129 |
--------------------------------------------------------------------------------
/test/QuickUNodeMQ.test.ts:
--------------------------------------------------------------------------------
1 | import { Exchange, Queue, QuickUNodeMQ, Logs, createQuickUnmq } from "../src/index";
2 | import { expect, test, describe } from "@jest/globals";
3 | import { promiseSetTimeout } from "../src/utils/tools";
4 | Logs.setLogsConfig({ logs: false });
5 | describe("QuickUNodeMQ", () => {
6 | /**
7 | * 创建测试list的方法
8 | * @returns
9 | */
10 | function createListEqual() {
11 | const list: T[] = [];
12 | return {
13 | list,
14 | equal(list: T[], time = 0, callback?: () => void) {
15 | setTimeout(() => {
16 | expect(this.list).toEqual(list);
17 | if (callback) callback();
18 | }, time);
19 | },
20 | equalDisorder() {
21 | //TODO:
22 | },
23 | };
24 | }
25 |
26 | test("QuickUNodeMQ.emit", function (done) {
27 | type T = number;
28 | const quickUnmq = new QuickUNodeMQ(new Exchange({ routes: ["qu1"] }), { qu1: new Queue() });
29 |
30 | quickUnmq.emit(1);
31 | quickUnmq.emit(2, 3, 4, 5);
32 |
33 | const listEqual = createListEqual();
34 | quickUnmq.on("qu1", (res: T) => listEqual.list.push(res));
35 | listEqual.equal([1, 2, 3, 4, 5], 0, done);
36 | });
37 | test("QuickUNodeMQ.emitToQueue", function (done) {
38 | type T = number;
39 | const queColl = { qu1: new Queue() };
40 | const quickUnmq = new QuickUNodeMQ({ routes: ["qu1"] }, queColl);
41 |
42 | quickUnmq.emitToQueue("qu1", 1);
43 | quickUnmq.emitToQueue("qu1", 2, 3, 4, 5);
44 |
45 | const listEqual = createListEqual();
46 | quickUnmq.on("qu1", (res: T) => listEqual.list.push(res));
47 | listEqual.equal([1, 2, 3, 4, 5], 0, done);
48 |
49 | setTimeout(done);
50 | });
51 | test("QuickUNodeMQ.on", function (done) {
52 | expect.assertions(10);
53 |
54 | type T = number;
55 | const quickUnmq1 = new QuickUNodeMQ(new Exchange({ routes: ["qu1"] }), { qu1: new Queue() });
56 |
57 | quickUnmq1.emit(1, 2, 3, 4, 5);
58 |
59 | quickUnmq1.on(
60 | "qu1",
61 | (res: T, payload: any) => {
62 | expect([1, 2, 3, 4, 5].indexOf(res) !== -1).toBe(true);
63 | expect(payload).toEqual("payload");
64 | },
65 | "payload",
66 | );
67 |
68 | setTimeout(done);
69 | });
70 | test("QuickUNodeMQ.once", async function () {
71 | expect.assertions(5);
72 |
73 | type T = number;
74 | const quickUnmq1 = new QuickUNodeMQ(new Exchange({ routes: ["qu1"] }), { qu1: new Queue() });
75 |
76 | quickUnmq1.emit(1, 2, 3);
77 |
78 | const res1 = await quickUnmq1.once("qu1");
79 | expect(res1).toEqual(1);
80 | /**
81 |
82 |
83 | 当队列设置为同步消费的时候
84 |
85 | 由于消费者在消费一条消息以后,队列并不会立马收到回调,所以会在下次事件循环将队列的消费方法放开,所以会积累一次事件循环的消费者进入队列
86 |
87 | 因此下次两次once方法实际上是订阅的同一条消息
88 |
89 |
90 | */
91 |
92 | /**
93 |
94 |
95 | jest内部使用try catch捕获不到异步情况下的断言错误,所以异步代码即使断言错误报错了,只会阻塞下面的代码执行,不会被jest捕获到
96 |
97 | 因此这里需要将下面的once方法手动封装成promise方法
98 |
99 |
100 | ~~~typescript
101 |
102 | quickUnmq1
103 | .once("qu1", (res2: T) => {
104 | expect(res2).toEqual(2);
105 | quickUnmq1.once("qu1", (res3: T) => {
106 | expect(res3).toEqual(4);
107 | done();
108 | });
109 | })
110 | .once(
111 | "qu1",
112 | (res2: T, payload: any) => {
113 | expect(res2).toEqual(2);
114 | expect(payload).toEqual("payload");
115 | },
116 | "payload",
117 | );
118 | ~~~
119 |
120 | */
121 |
122 | const [res2_1, res2_2] = await Promise.all([
123 | new Promise(res => {
124 | quickUnmq1.once("qu1", (res2: T) => {
125 | res(res2);
126 | });
127 | }),
128 | new Promise(res => {
129 | quickUnmq1.once(
130 | "qu1",
131 | (res2: T, payload) => {
132 | res({ res2, payload });
133 | },
134 | "payload",
135 | );
136 | }),
137 | ]);
138 |
139 | expect(res2_1).toEqual(2);
140 | expect(res2_2.res2).toEqual(2);
141 | expect(res2_2.payload).toEqual("payload");
142 |
143 | const res3 = await new Promise(res => {
144 | quickUnmq1.once("qu1", (res3: T) => {
145 | res(res3);
146 | });
147 | });
148 |
149 | expect(res3).toEqual(3);
150 |
151 | //
152 | });
153 |
154 | test("QuickUNodeMQ.off", async function () {
155 | expect.assertions(2);
156 |
157 | type T = number;
158 | const quickUnmq1 = new QuickUNodeMQ(new Exchange({ routes: ["qu1"] }), { qu1: new Queue({ async: false }) });
159 | const quickUnmq2 = new QuickUNodeMQ(new Exchange({ routes: ["qu1"] }), { qu1: new Queue({ async: true }) });
160 |
161 | quickUnmq1.emit(1, 2);
162 | const res = await new Promise(res => {
163 | quickUnmq1.on("qu1", (data: T) => {
164 | res(data);
165 | quickUnmq1.off("qu1");
166 | });
167 | });
168 | expect(res).toEqual(1);
169 |
170 | quickUnmq2.emit(3, 4, 5);
171 | await promiseSetTimeout(0);
172 | const res_a = await new Promise(res => {
173 | const a: any[] = [];
174 | quickUnmq2.on("qu1", (data: T) => {
175 | a.push(data);
176 | })();
177 | setTimeout(() => {
178 | res(a);
179 | });
180 | });
181 | expect(res_a).toEqual([3, 4, 5]);
182 | });
183 | });
184 |
185 | describe("createQuickUnmq", () => {
186 | //
187 | test("createQuickUnmq_init", function () {
188 | //
189 |
190 | expect(createQuickUnmq({ routes: ["qu1"] }, { qu1: new Queue() }) instanceof QuickUNodeMQ).toBeTruthy();
191 | expect(createQuickUnmq(new Exchange({ routes: ["qu1"] }), { qu1: new Queue() }) instanceof QuickUNodeMQ).toBeTruthy();
192 | });
193 | });
194 |
--------------------------------------------------------------------------------
/termui/go.sum:
--------------------------------------------------------------------------------
1 | github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
2 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
3 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
4 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
5 | github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
6 | github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
7 | github.com/gin-gonic/gin v1.8.1 h1:4+fr/el88TOO3ewCmQr8cx/CtZ/umlIRIs5M4NTNjf8=
8 | github.com/gin-gonic/gin v1.8.1/go.mod h1:ji8BvRH1azfM+SYow9zQ6SZMvR8qOMZHmsCuWR9tTTk=
9 | github.com/gizak/termui/v3 v3.1.0 h1:ZZmVDgwHl7gR7elfKf1xc4IudXZ5qqfDh4wExk4Iajc=
10 | github.com/gizak/termui/v3 v3.1.0/go.mod h1:bXQEBkJpzxUAKf0+xq9MSWAvWZlE7c+aidmyFlkYTrY=
11 | github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A=
12 | github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
13 | github.com/go-playground/locales v0.14.0 h1:u50s323jtVGugKlcYeyzC0etD1HifMjqmJqb8WugfUU=
14 | github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs=
15 | github.com/go-playground/universal-translator v0.18.0 h1:82dyy6p4OuJq4/CByFNOn/jYrnRPArHwAcmLoJZxyho=
16 | github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA=
17 | github.com/go-playground/validator/v10 v10.10.0 h1:I7mrTYv78z8k8VXa/qJlOlEXn/nBh+BF8dHX5nt/dr0=
18 | github.com/go-playground/validator/v10 v10.10.0/go.mod h1:74x4gJWsvQexRdW8Pn3dXSGrTK4nAUsbPlLADvpJkos=
19 | github.com/goccy/go-json v0.9.7 h1:IcB+Aqpx/iMHu5Yooh7jEzJk1JZ7Pjtmys2ukPr7EeM=
20 | github.com/goccy/go-json v0.9.7/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
21 | github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
22 | github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
23 | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
24 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
25 | github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
26 | github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
27 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
28 | github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
29 | github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
30 | github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
31 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
32 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
33 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
34 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
35 | github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w=
36 | github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY=
37 | github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
38 | github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
39 | github.com/mattn/go-runewidth v0.0.2 h1:UnlwIPBGaTZfPQ6T1IGzPI0EkYAQmT9fAEJ/poFC63o=
40 | github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
41 | github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7 h1:DpOJ2HYzCv8LZP15IdmG+YdwD2luVPHITV96TkirNBM=
42 | github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo=
43 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc=
44 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
45 | github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
46 | github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
47 | github.com/nsf/termbox-go v0.0.0-20190121233118-02980233997d h1:x3S6kxmy49zXVVyhcnrFqxvNVCBPb2KZ9hV2RBdS840=
48 | github.com/nsf/termbox-go v0.0.0-20190121233118-02980233997d/go.mod h1:IuKpRQcYE1Tfu+oAQqaLisqDeXgjyyltCfsaoYN18NQ=
49 | github.com/pelletier/go-toml/v2 v2.0.1 h1:8e3L2cCQzLFi2CR4g7vGFuFxX7Jl1kKX8gW+iV0GUKU=
50 | github.com/pelletier/go-toml/v2 v2.0.1/go.mod h1:r9LEWfGN8R5k0VXJ+0BkIe7MYkRdwZOjgMj2KwnJFUo=
51 | github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
52 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
53 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
54 | github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
55 | github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8=
56 | github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
57 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
58 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
59 | github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
60 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
61 | github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY=
62 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
63 | github.com/ugorji/go v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6M=
64 | github.com/ugorji/go/codec v1.2.7 h1:YPXUKf7fYbp/y8xloBqZOw2qaVggbfwMlI8WM3wZUJ0=
65 | github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY=
66 | golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 h1:/UOmuWzQfxxo9UtlXMwuQU8CMgg1eZXqTRwkSQJWKOI=
67 | golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
68 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110 h1:qWPm9rbaAMKs8Bq/9LRpbMqxWRVUAQwMI9fVrssnTfw=
69 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
70 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
71 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
72 | golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
73 | golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069 h1:siQdpVirKtzPhKl3lZWozZraCFObP8S1v6PRp0bLrtU=
74 | golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
75 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
76 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
77 | golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
78 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
79 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
80 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
81 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
82 | google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
83 | google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw=
84 | google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
85 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
86 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
87 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
88 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
89 | gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
90 | gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
91 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
92 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
93 | gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
94 | gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
95 |
--------------------------------------------------------------------------------
/src/internal/Queue/index.ts:
--------------------------------------------------------------------------------
1 | import { News, Consumer } from "../..";
2 | import { Consume } from "../Consumer";
3 | import { random } from "../../utils/tools";
4 | import { queueLogsOperator } from "../Logs";
5 | import { Operator, isAsyncOperator, isSyncOperator } from "./operators";
6 |
7 | export interface QueueOption