{"id":23790035,"url":"https://github.com/blackglory/mq","last_synced_at":"2026-05-08T19:31:31.974Z","repository":{"id":44964920,"uuid":"320822524","full_name":"BlackGlory/mq","owner":"BlackGlory","description":"🌳 Request/Reply 持久化消息队列微服务","archived":false,"fork":false,"pushed_at":"2026-01-22T12:12:56.000Z","size":2583,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2026-01-23T03:53:43.314Z","etag":null,"topics":["docker-image","esm","microservice","nodejs","typescript"],"latest_commit_sha":null,"homepage":"https://hub.docker.com/r/blackglory/mq","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/BlackGlory.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2020-12-12T12:12:21.000Z","updated_at":"2026-01-22T12:12:59.000Z","dependencies_parsed_at":"2025-02-21T12:43:28.046Z","dependency_job_id":"f0399cb0-ce41-43e0-b224-fe02f2870793","html_url":"https://github.com/BlackGlory/mq","commit_stats":null,"previous_names":[],"tags_count":29,"template":false,"template_full_name":null,"purl":"pkg:github/BlackGlory/mq","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/BlackGlory%2Fmq","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/BlackGlory%2Fmq/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/BlackGlory%2Fmq/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/BlackGlory%2Fmq/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/BlackGlory","download_url":"https://codeload.github.com/BlackGlory/mq/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/BlackGlory%2Fmq/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32794558,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-08T08:22:46.396Z","status":"ssl_error","status_checked_at":"2026-05-08T08:22:45.650Z","response_time":54,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["docker-image","esm","microservice","nodejs","typescript"],"created_at":"2025-01-01T17:18:11.587Z","updated_at":"2026-05-08T19:31:31.968Z","avatar_url":"https://github.com/BlackGlory.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# MQ\n提供以WebSocket为通讯协议的持久化消息队列.\n\n## Quickstart\n```sh\ndocker run \\\n  --detach \\\n  --publish 8080:8080 \\\n  blackglory/mq\n```\n\n## Install\n### 从源代码运行\n```sh\ngit clone https://github.com/BlackGlory/mq\ncd mq\nyarn install\nyarn build\nyarn bundle\nyarn --silent start\n```\n\n### 从源代码构建\n```sh\ngit clone https://github.com/BlackGlory/mq\ncd mq\nyarn install\nyarn docker:build\n```\n\n### Recipes\n#### docker-compose.yml\n```yaml\nversion: '3.8'\n\nservices:\n  mq:\n    image: 'blackglory/mq'\n    restart: always\n    volumes:\n      - 'mq-data:/data'\n    ports:\n      - '8080:8080'\n\nvolumes:\n  mq-data:\n```\n\n## API\n```ts\n/**\n * 区分`oredred`和`active`是因为取出消息id的和处理消息的可能是两个不同的实体,\n * 这种细粒度的状态设计可以更有效地利用带宽等资源.\n */\nenum MessageState {\n  Drafting = 0\n, Waiting = 1\n, Ordered = 2\n, Active = 3\n, Failed = 4\n, Completed = 5\n, Abandoned = 6\n}\n\nenum AdditionalBehavior {\n  None\n\n  /**\n   * 删除消息.\n   * \n   * 删除消息本身不会对统计数据造成影响.\n   * 当队列要求消息唯一时, 该行为将导致相同的消息可以再次入列.\n   */\n, RemoveMessage\n\n  /**\n   * 删除插槽以节省存储空间.\n   */\n, RemoveAllSlots\n}\n\ninterface IQueueConfig extends JSONObject {\n  /**\n   * 是否维持消息的唯一性.\n   * \n   * 启用此选项后, 该消息队列的写入性能将会因为去重而大幅降低.\n   */\n  unique: boolean\n\n  /**\n   * 允许消息处于`drafting`状态的毫秒数.\n   */\n  draftingTimeout: number\n\n  /**\n   * 允许消息处于`ordered`状态的毫秒数.\n   */\n  orderedTimeout: number\n\n  /**\n   * 允许消息处于`active`状态的毫秒数.\n   */\n  activeTimeout: number\n\n  /**\n   * 允许处于`ordered`和`active`状态的消息数量.\n   * \n   * `null`表示无限.\n   */\n  concurrency: number | null\n\n  behaviorWhenCompleted: AdditionalBehavior\n  behaviorWhenAbandoned: AdditionalBehavior\n}\n\ninterface IMessage {\n  slots: Record\u003cstring, JSONValue\u003e\n  priority: number | null\n  state: MessageState\n}\n\ninterface IQueueStats {\n  drafting: number\n  waiting: number\n  ordered: number\n  active: number\n  failed: number\n  completed: number\n  abandoned: number\n}\n\ninterface IAPI {\n  getAllQueueIds(): string[]\n\n  getQueue(queueId: string): IQueueConfig | null\n\n  /**\n   * 队列已存在的情况下, 调用该方法会更新队列的配置, 但有以下注意事项:\n   * - 由于旧消息缺乏相应的字段, 旧消息不会因为`unique`启用而去重, 且新消息仍可能会与旧消息的内容重复.\n   * - 已经处于`completed`状态的消息不受`behaviorWhenCompleted`的改变影响.\n   * - 已经处于`abandoned`状态的消息不受`behaviorWhenAbandoned`的改变影响.\n   */\n  setQueue(queueId: string, config: IQueueConfig): null\n\n  removeQueue(queueId: string): null\n\n  getQueueStats(queueId: string): IQueueStats | null\n\n  /**\n   * 重置队列:\n   * - 清空队列中的消息.\n   * - 重置统计数据.\n   */\n  resetQueue(queueId: string): null\n\n  /**\n   * 产生一个处于`drafting`状态的消息.\n   * \n   * @param priority 消息的优先级, 有符号整数, 值越大则优先级越大.\n   * `null`为特殊值, 表示无优先级, 代表优先级最低.\n   * 如果需要设置优先级, 推荐做法是将`0`视作默认优先级, 在此基础上调整优先级.\n   * @throws {QueueNotFound}\n   * @throws {DuplicateMessageId}\n   */\n  draftMessage(\n    queueId: string\n  , priority: number | null\n  , slotNames: NonEmptyArray\u003cstring\u003e\n  , messageId?: string\n  ): string\n\n  /**\n   * 当消息的每个slot都有值时, 消息将从`drafting`转为`waiting`状态.\n   * \n   * @throws {QueueNotFound}\n   * @throws {MessageNotFound}\n   * @throws {SlotNotFound}\n   * @throws {BadMessageState}\n   * @throws {DuplicateMessage}\n   */\n  setMessageSlot(\n    queueId: string\n  , messageId: string\n  , slotName: string\n  , value: JSONValue\n  ): null\n\n  /**\n   * 从队列中取出一个处于`waiting`状态的消息的id.\n   * 该消息将转为`ordered`状态.\n   * \n   * @throws {QueueNotFound}\n   * @throws {AbortError}\n   */\n  orderMessage(queueId: string): Promise\u003cstring\u003e\n\n  /**\n   * 获取一个消息.\n   * 对于处于`ordered`状态的消息, 该方法存在副作用, 将导致该消息转为`active`状态.\n   * \n   * @throws {QueueNotFound}\n   */\n  getMessage(queueId: string, messageId: string): IMessage | null\n\n  /**\n   * 无副作用地获取一个消息.\n   * \n   * @throws {QueueNotFound}\n   */\n  peekMessage(queueid: string, messageId: string): IMessage | null\n\n  /**\n   * 将一个处于`active`状态的消息转为`completed`状态.\n   * \n   * @throws {QueueNotFound}\n   * @throws {MessageNotFound}\n   * @throws {BadMessageState}\n   */\n  completeMessage(queueId: string, messageId: string): null\n\n  /**\n   * 将一个处于`active`状态的消息转为`failed`状态.\n   * \n   * @throws {QueueNotFound}\n   * @throws {MessageNotFound}\n   * @throws {BadMessageState}\n   */\n  failMessage(queueId: string, messageId: string): null\n\n  /**\n   * 将一个处于`renew`状态的消息转为`waiting`状态.\n   * \n   * @throws {QueueNotFound}\n   * @throws {MessageNotFound}\n   * @throws {BadMessageState}\n   */\n  renewMessage(queueId: string, messageId: string): null\n\n  /**\n   * 将一个消息转为`abandoned`状态.\n   * \n   * @throws {QueueNotFound}\n   * @throws {MessageNotFound}\n   */\n  abandonMessage(queueId: string, messageId: string): null\n\n  /**\n   * @throws {QueueNotFound}\n   */\n  removeMessage(queueId: string, messageId: string): null\n\n  /**\n   * @throws {QueueNotFound}\n   */\n  abandonAllFailedMessages(queueId: string): null\n\n  /**\n   * @throws {QueueNotFound}\n   */\n  renewAllFailedMessages(queueId: string): null\n\n  /**\n   * @throws {QueueNotFound}\n   */\n  getMessageIdsByState(queueId: string, state: MessageState): string[]\n\n  /**\n   * 根据状态清空队列中的消息.\n   * 统计数据的对应项目将减去删除的消息数量.\n   * \n   * @throws {QueueNotFound}\n   */\n  clearMessagesByState(queueId: string, state: MessageState): null\n}\n\nclass QueueNotFound extends CustomError {}\nclass MessageNotFound extends CustomError {}\nclass SlotNotFound extends CustomError {}\nclass DuplicateMessage extends CustomError {}\nclass DuplicateMessageId extends CustomError {}\nclass BadMessageState extends CustomError {}\n```\n\n## 环境变量\n### `MQ_HOST`, `MQ_PORT`\n通过环境变量`MQ_HOST`和`MQ_PORT`决定服务器监听的地址和端口,\n默认值为`localhost`和`8080`.\n\n### `MQ_WS_HEARTBEAT_INTERVAL`\n通过环境变量`MQ_WS_HEARTBEAT_INTERVAL`可以设置WS心跳包(ping帧)的发送间隔, 单位为毫秒.\n在默认情况下, 服务不会发送心跳包,\n半开连接的检测依赖于服务端和客户端的运行平台的TCP Keepalive配置.\n\n当`MQ_WS_HEARTBEAT_INTERVAL`大于零时,\n服务会通过WS的ping帧按间隔发送心跳包.\n\n## 客户端\n- JavaScript/TypeScript(Node.js, Browser): \u003chttps://github.com/BlackGlory/mq-js\u003e\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fblackglory%2Fmq","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fblackglory%2Fmq","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fblackglory%2Fmq/lists"}