pulse-mq 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pulse_mq-0.1.0/.claude/settings.json +5 -0
- pulse_mq-0.1.0/.env.example +48 -0
- pulse_mq-0.1.0/.gitignore +10 -0
- pulse_mq-0.1.0/.idea/.gitignore +10 -0
- pulse_mq-0.1.0/.idea/inspectionProfiles/Project_Default.xml +19 -0
- pulse_mq-0.1.0/.idea/inspectionProfiles/profiles_settings.xml +6 -0
- pulse_mq-0.1.0/.idea/modules.xml +8 -0
- pulse_mq-0.1.0/.idea/pulse-mq.iml +10 -0
- pulse_mq-0.1.0/.idea/vcs.xml +6 -0
- pulse_mq-0.1.0/.idea/workspace.xml +113 -0
- pulse_mq-0.1.0/PKG-INFO +182 -0
- pulse_mq-0.1.0/README.md +159 -0
- pulse_mq-0.1.0/alembic/env.py +37 -0
- pulse_mq-0.1.0/alembic/versions/.gitkeep +0 -0
- pulse_mq-0.1.0/alembic.ini +36 -0
- pulse_mq-0.1.0/docs/superpowers/plans/2026-05-28-pulse-mq.md +3920 -0
- pulse_mq-0.1.0/docs/superpowers/specs/2026-05-28-pulse-mq-design.md +524 -0
- pulse_mq-0.1.0/pyproject.toml +41 -0
- pulse_mq-0.1.0/src/pulse_mq/__init__.py +6 -0
- pulse_mq-0.1.0/src/pulse_mq/auth.py +117 -0
- pulse_mq-0.1.0/src/pulse_mq/cli.py +90 -0
- pulse_mq-0.1.0/src/pulse_mq/client.py +178 -0
- pulse_mq-0.1.0/src/pulse_mq/config.py +69 -0
- pulse_mq-0.1.0/src/pulse_mq/database.py +40 -0
- pulse_mq-0.1.0/src/pulse_mq/models.py +66 -0
- pulse_mq-0.1.0/src/pulse_mq/protocol.py +115 -0
- pulse_mq-0.1.0/src/pulse_mq/router.py +200 -0
- pulse_mq-0.1.0/src/pulse_mq/server.py +93 -0
- pulse_mq-0.1.0/src/pulse_mq/stats.py +87 -0
- pulse_mq-0.1.0/src/pulse_mq/web/__init__.py +0 -0
- pulse_mq-0.1.0/src/pulse_mq/web/app.py +25 -0
- pulse_mq-0.1.0/src/pulse_mq/web/deps.py +35 -0
- pulse_mq-0.1.0/src/pulse_mq/web/routes/__init__.py +0 -0
- pulse_mq-0.1.0/src/pulse_mq/web/routes/auth.py +28 -0
- pulse_mq-0.1.0/src/pulse_mq/web/routes/dashboard.py +99 -0
- pulse_mq-0.1.0/src/pulse_mq/web/routes/permissions.py +61 -0
- pulse_mq-0.1.0/src/pulse_mq/web/routes/topics.py +78 -0
- pulse_mq-0.1.0/src/pulse_mq/web/routes/users.py +83 -0
- pulse_mq-0.1.0/src/pulse_mq/web/static/app.js +1 -0
- pulse_mq-0.1.0/src/pulse_mq/web/static/style.css +84 -0
- pulse_mq-0.1.0/src/pulse_mq/web/templates/__init__.py +0 -0
- pulse_mq-0.1.0/src/pulse_mq/web/templates/base.html +14 -0
- pulse_mq-0.1.0/src/pulse_mq/web/templates/dashboard.html +121 -0
- pulse_mq-0.1.0/src/pulse_mq/web/templates/login.html +42 -0
- pulse_mq-0.1.0/src/pulse_mq/web/templates/permissions.html +82 -0
- pulse_mq-0.1.0/src/pulse_mq/web/templates/topic_detail.html +70 -0
- pulse_mq-0.1.0/src/pulse_mq/web/templates/topics.html +82 -0
- pulse_mq-0.1.0/src/pulse_mq/web/templates/user_detail.html +58 -0
- pulse_mq-0.1.0/src/pulse_mq/web/templates/users.html +96 -0
- pulse_mq-0.1.0/tests/__init__.py +0 -0
- pulse_mq-0.1.0/tests/conftest.py +18 -0
- pulse_mq-0.1.0/tests/test_auth.py +92 -0
- pulse_mq-0.1.0/tests/test_client.py +169 -0
- pulse_mq-0.1.0/tests/test_config.py +30 -0
- pulse_mq-0.1.0/tests/test_integration.py +101 -0
- pulse_mq-0.1.0/tests/test_models.py +67 -0
- pulse_mq-0.1.0/tests/test_protocol.py +120 -0
- pulse_mq-0.1.0/tests/test_router.py +229 -0
- pulse_mq-0.1.0/tests/test_server.py +22 -0
- pulse_mq-0.1.0/tests/test_stats.py +45 -0
- pulse_mq-0.1.0/tests/web/__init__.py +0 -0
- pulse_mq-0.1.0/tests/web/conftest.py +45 -0
- pulse_mq-0.1.0/tests/web/test_auth_routes.py +30 -0
- pulse_mq-0.1.0/tests/web/test_dashboard.py +33 -0
- pulse_mq-0.1.0/tests/web/test_permissions.py +34 -0
- pulse_mq-0.1.0/tests/web/test_topics.py +31 -0
- pulse_mq-0.1.0/tests/web/test_users.py +45 -0
- pulse_mq-0.1.0/uv.lock +1510 -0
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# ============================================================
|
|
2
|
+
# PulseMQ 服务端配置
|
|
3
|
+
# 将此文件复制为 .env 并根据实际环境修改
|
|
4
|
+
# ============================================================
|
|
5
|
+
|
|
6
|
+
# ---- HTTP 服务 ----
|
|
7
|
+
# 管理后台和 API 的监听地址
|
|
8
|
+
PULSE_HOST=0.0.0.0
|
|
9
|
+
# 管理后台和 API 的端口
|
|
10
|
+
PULSE_HTTP_PORT=8080
|
|
11
|
+
|
|
12
|
+
# ---- ZMQ 消息服务 ----
|
|
13
|
+
# ZMQ ROUTER 端口(客户端连接入口,处理认证、订阅、消息下发)
|
|
14
|
+
PULSE_ZMQ_ROUTER_PORT=5555
|
|
15
|
+
# ZMQ PULL 端口(接收客户端推送的消息)
|
|
16
|
+
PULSE_ZMQ_PULL_PORT=5556
|
|
17
|
+
|
|
18
|
+
# ---- 数据库 ----
|
|
19
|
+
# 数据库类型:sqlite 或 mysql
|
|
20
|
+
PULSE_DB_TYPE=sqlite
|
|
21
|
+
# SQLite 文件路径(仅 sqlite 模式)
|
|
22
|
+
PULSE_DB_PATH=./pulse_mq.db
|
|
23
|
+
# MySQL 连接 URL(仅 mysql 模式,格式:mysql+pymysql://user:password@host:port/database)
|
|
24
|
+
# PULSE_DB_URL=mysql+pymysql://root:password@localhost:3306/pulse_mq
|
|
25
|
+
|
|
26
|
+
# ---- 消息队列 ----
|
|
27
|
+
# 每个 topic 在内存中保留的最大消息条数(超出后丢弃最旧消息)
|
|
28
|
+
PULSE_QUEUE_SIZE=1000
|
|
29
|
+
|
|
30
|
+
# ---- 认证 ----
|
|
31
|
+
# 客户端 auth 缓存从数据库刷新的间隔(秒)
|
|
32
|
+
PULSE_AUTH_CACHE_TTL=60
|
|
33
|
+
# JWT 签名密钥(留空则首次启动自动生成)
|
|
34
|
+
PULSE_JWT_SECRET=
|
|
35
|
+
# JWT Token 过期时间(小时)
|
|
36
|
+
PULSE_JWT_EXPIRE_HOURS=24
|
|
37
|
+
# admin 初始密码(留空则首次启动自动生成,输出到控制台和 admin_credentials.txt)
|
|
38
|
+
PULSE_ADMIN_PASSWORD=
|
|
39
|
+
|
|
40
|
+
# ---- 心跳 ----
|
|
41
|
+
# 客户端心跳发送间隔(秒)
|
|
42
|
+
PULSE_HEARTBEAT_INTERVAL=30
|
|
43
|
+
# 服务端判定客户端断线的超时时间(秒)
|
|
44
|
+
PULSE_HEARTBEAT_TIMEOUT=60
|
|
45
|
+
|
|
46
|
+
# ---- 统计 ----
|
|
47
|
+
# 消息统计数据保留天数(超过此天数的每分钟统计数据自动清理)
|
|
48
|
+
PULSE_STATS_RETENTION_DAYS=7
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
<component name="InspectionProjectProfileManager">
|
|
2
|
+
<profile version="1.0">
|
|
3
|
+
<option name="myName" value="Project Default" />
|
|
4
|
+
<inspection_tool class="PyPackageRequirementsInspection" enabled="true" level="WARNING" enabled_by_default="true">
|
|
5
|
+
<option name="ignoredPackages">
|
|
6
|
+
<list>
|
|
7
|
+
<option value="pydantic" />
|
|
8
|
+
<option value="pandas" />
|
|
9
|
+
<option value="numpy" />
|
|
10
|
+
<option value="requests" />
|
|
11
|
+
<option value="python-dateutil" />
|
|
12
|
+
<option value="loguru" />
|
|
13
|
+
<option value="duckdb" />
|
|
14
|
+
<option value="python-dotenv" />
|
|
15
|
+
</list>
|
|
16
|
+
</option>
|
|
17
|
+
</inspection_tool>
|
|
18
|
+
</profile>
|
|
19
|
+
</component>
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<module type="PYTHON_MODULE" version="4">
|
|
3
|
+
<component name="NewModuleRootManager">
|
|
4
|
+
<content url="file://$MODULE_DIR$">
|
|
5
|
+
<excludeFolder url="file://$MODULE_DIR$/.venv" />
|
|
6
|
+
</content>
|
|
7
|
+
<orderEntry type="inheritedJdk" />
|
|
8
|
+
<orderEntry type="sourceFolder" forTests="false" />
|
|
9
|
+
</component>
|
|
10
|
+
</module>
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<project version="4">
|
|
3
|
+
<component name="AutoImportSettings">
|
|
4
|
+
<option name="autoReloadType" value="ALL" />
|
|
5
|
+
</component>
|
|
6
|
+
<component name="ChangeListManager">
|
|
7
|
+
<list default="true" id="6d42cb57-a997-45ab-99f6-b975145f6d31" name="Changes" comment="" />
|
|
8
|
+
<option name="SHOW_DIALOG" value="false" />
|
|
9
|
+
<option name="HIGHLIGHT_CONFLICTS" value="true" />
|
|
10
|
+
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
|
|
11
|
+
<option name="LAST_RESOLUTION" value="IGNORE" />
|
|
12
|
+
</component>
|
|
13
|
+
<component name="Git.Settings">
|
|
14
|
+
<option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" />
|
|
15
|
+
</component>
|
|
16
|
+
<component name="ProjectColorInfo"><![CDATA[{
|
|
17
|
+
"associatedIndex": 8,
|
|
18
|
+
"fromUser": false
|
|
19
|
+
}]]></component>
|
|
20
|
+
<component name="ProjectId" id="3ELrM5vPLVXDlgl9JT4LYp5Ekw5" />
|
|
21
|
+
<component name="ProjectViewState">
|
|
22
|
+
<option name="hideEmptyMiddlePackages" value="true" />
|
|
23
|
+
<option name="showLibraryContents" value="true" />
|
|
24
|
+
</component>
|
|
25
|
+
<component name="PropertiesComponent"><![CDATA[{
|
|
26
|
+
"keyToString": {
|
|
27
|
+
"ModuleVcsDetector.initialDetectionPerformed": "true",
|
|
28
|
+
"RunOnceActivity.ShowReadmeOnStart": "true",
|
|
29
|
+
"RunOnceActivity.TerminalTabsStorage.copyFrom.TerminalArrangementManager.252": "true",
|
|
30
|
+
"RunOnceActivity.typescript.service.memoryLimit.init": "true",
|
|
31
|
+
"codeWithMe.voiceChat.enabledByDefault": "false",
|
|
32
|
+
"git-widget-placeholder": "master",
|
|
33
|
+
"last_opened_file_path": "D:/Workspace/pulse-mq",
|
|
34
|
+
"node.js.detected.package.eslint": "true",
|
|
35
|
+
"node.js.detected.package.tslint": "true",
|
|
36
|
+
"node.js.selected.package.eslint": "(autodetect)",
|
|
37
|
+
"node.js.selected.package.tslint": "(autodetect)",
|
|
38
|
+
"nodejs_package_manager_path": "npm",
|
|
39
|
+
"vue.rearranger.settings.migration": "true"
|
|
40
|
+
}
|
|
41
|
+
}]]></component>
|
|
42
|
+
<component name="RunManager">
|
|
43
|
+
<configuration name="main" type="PythonConfigurationType" factoryName="Python" nameIsGenerated="true">
|
|
44
|
+
<module name="pulse-mq" />
|
|
45
|
+
<option name="ENV_FILES" value="" />
|
|
46
|
+
<option name="INTERPRETER_OPTIONS" value="" />
|
|
47
|
+
<option name="PARENT_ENVS" value="true" />
|
|
48
|
+
<envs>
|
|
49
|
+
<env name="PYTHONUNBUFFERED" value="1" />
|
|
50
|
+
</envs>
|
|
51
|
+
<option name="SDK_HOME" value="" />
|
|
52
|
+
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$" />
|
|
53
|
+
<option name="IS_MODULE_SDK" value="true" />
|
|
54
|
+
<option name="ADD_CONTENT_ROOTS" value="true" />
|
|
55
|
+
<option name="ADD_SOURCE_ROOTS" value="true" />
|
|
56
|
+
<option name="DEBUG_JUST_MY_CODE" value="false" />
|
|
57
|
+
<EXTENSION ID="PythonCoverageRunConfigurationExtension" runner="coverage.py" />
|
|
58
|
+
<option name="RUN_TOOL" value="" />
|
|
59
|
+
<option name="SCRIPT_NAME" value="$PROJECT_DIR$/main.py" />
|
|
60
|
+
<option name="PARAMETERS" value="" />
|
|
61
|
+
<option name="SHOW_COMMAND_LINE" value="false" />
|
|
62
|
+
<option name="EMULATE_TERMINAL" value="false" />
|
|
63
|
+
<option name="MODULE_MODE" value="false" />
|
|
64
|
+
<option name="REDIRECT_INPUT" value="false" />
|
|
65
|
+
<option name="INPUT_FILE" value="" />
|
|
66
|
+
<method v="2" />
|
|
67
|
+
</configuration>
|
|
68
|
+
</component>
|
|
69
|
+
<component name="SharedIndexes">
|
|
70
|
+
<attachedChunks>
|
|
71
|
+
<set>
|
|
72
|
+
<option value="bundled-js-predefined-d6986cc7102b-31caf2ab9e3c-JavaScript-PY-261.23567.174" />
|
|
73
|
+
<option value="bundled-python-sdk-024ed6589d45-c2ffad84badb-com.jetbrains.pycharm.pro.sharedIndexes.bundled-PY-261.23567.174" />
|
|
74
|
+
</set>
|
|
75
|
+
</attachedChunks>
|
|
76
|
+
</component>
|
|
77
|
+
<component name="TaskManager">
|
|
78
|
+
<task active="true" id="Default" summary="Default task">
|
|
79
|
+
<changelist id="6d42cb57-a997-45ab-99f6-b975145f6d31" name="Changes" comment="" />
|
|
80
|
+
<created>1779968640985</created>
|
|
81
|
+
<option name="number" value="Default" />
|
|
82
|
+
<option name="presentableId" value="Default" />
|
|
83
|
+
<updated>1779968640985</updated>
|
|
84
|
+
<workItem from="1779968642081" duration="6531000" />
|
|
85
|
+
</task>
|
|
86
|
+
<servers />
|
|
87
|
+
</component>
|
|
88
|
+
<component name="TypeScriptGeneratedFilesManager">
|
|
89
|
+
<option name="version" value="3" />
|
|
90
|
+
</component>
|
|
91
|
+
<component name="Vcs.Log.Tabs.Properties">
|
|
92
|
+
<option name="TAB_STATES">
|
|
93
|
+
<map>
|
|
94
|
+
<entry key="MAIN">
|
|
95
|
+
<value>
|
|
96
|
+
<State />
|
|
97
|
+
</value>
|
|
98
|
+
</entry>
|
|
99
|
+
</map>
|
|
100
|
+
</option>
|
|
101
|
+
</component>
|
|
102
|
+
<component name="XDebuggerManager">
|
|
103
|
+
<breakpoint-manager>
|
|
104
|
+
<breakpoints>
|
|
105
|
+
<line-breakpoint enabled="true" suspend="THREAD" type="python-line">
|
|
106
|
+
<url>file://$PROJECT_DIR$/main.py</url>
|
|
107
|
+
<line>8</line>
|
|
108
|
+
<option name="timeStamp" value="1" />
|
|
109
|
+
</line-breakpoint>
|
|
110
|
+
</breakpoints>
|
|
111
|
+
</breakpoint-manager>
|
|
112
|
+
</component>
|
|
113
|
+
</project>
|
pulse_mq-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pulse-mq
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: 基于 ZeroMQ 的轻量级消息队列系统
|
|
5
|
+
Requires-Python: >=3.10
|
|
6
|
+
Requires-Dist: alembic>=1.13.0
|
|
7
|
+
Requires-Dist: fastapi>=0.104.0
|
|
8
|
+
Requires-Dist: jinja2>=3.1.0
|
|
9
|
+
Requires-Dist: msgpack>=1.0.0
|
|
10
|
+
Requires-Dist: passlib[bcrypt]>=1.7.4
|
|
11
|
+
Requires-Dist: pyarrow>=14.0.0
|
|
12
|
+
Requires-Dist: pyjwt>=2.8.0
|
|
13
|
+
Requires-Dist: python-dotenv>=1.0.0
|
|
14
|
+
Requires-Dist: python-multipart>=0.0.6
|
|
15
|
+
Requires-Dist: pyzmq>=25.0.0
|
|
16
|
+
Requires-Dist: sqlalchemy>=2.0.0
|
|
17
|
+
Requires-Dist: uvicorn[standard]>=0.24.0
|
|
18
|
+
Provides-Extra: dev
|
|
19
|
+
Requires-Dist: httpx>=0.25.0; extra == 'dev'
|
|
20
|
+
Requires-Dist: pytest-asyncio>=0.23.0; extra == 'dev'
|
|
21
|
+
Requires-Dist: pytest>=7.0.0; extra == 'dev'
|
|
22
|
+
Description-Content-Type: text/markdown
|
|
23
|
+
|
|
24
|
+
# PulseMQ
|
|
25
|
+
|
|
26
|
+
基于 ZeroMQ 的轻量级消息队列系统,配套 Grafana 风格的 Web 管理后台。
|
|
27
|
+
|
|
28
|
+
## 特性
|
|
29
|
+
|
|
30
|
+
- **双 Socket 架构** — ROUTER + PULL,消息延迟低
|
|
31
|
+
- **多格式支持** — 字符串、msgpack、PyArrow
|
|
32
|
+
- **Web 管理后台** — Grafana 暗色主题,ECharts 监控图表
|
|
33
|
+
- **权限管理** — 用户 token 认证,按 topic 控制推送/订阅权限
|
|
34
|
+
- **单进程部署** — 一条命令启动全部服务
|
|
35
|
+
- **PyPI 安装** — `pip install pulse-mq` 即可使用
|
|
36
|
+
|
|
37
|
+
## 快速安装
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
pip install pulse-mq
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## 快速开始
|
|
44
|
+
|
|
45
|
+
### 启动服务端
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
# 复制配置文件
|
|
49
|
+
cp .env.example .env
|
|
50
|
+
|
|
51
|
+
# 启动服务
|
|
52
|
+
pulse-mq-server
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
首次启动会自动生成 admin 密码,输出到控制台和 `admin_credentials.txt`。
|
|
56
|
+
|
|
57
|
+
管理后台默认地址: `http://localhost:8080`
|
|
58
|
+
|
|
59
|
+
### 客户端使用
|
|
60
|
+
|
|
61
|
+
```python
|
|
62
|
+
from pulse_mq import PulseMQClient
|
|
63
|
+
|
|
64
|
+
client = PulseMQClient(
|
|
65
|
+
host="localhost",
|
|
66
|
+
port=5555,
|
|
67
|
+
username="your-username",
|
|
68
|
+
token="your-token",
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
# 订阅消息(回调方式)
|
|
72
|
+
def on_message(msg):
|
|
73
|
+
print(f"Topic: {msg.topic}, Data: {msg.data}, Format: {msg.format}")
|
|
74
|
+
|
|
75
|
+
client.subscribe("metrics/cpu", callback=on_message)
|
|
76
|
+
|
|
77
|
+
# 推送消息
|
|
78
|
+
client.publish("metrics/cpu", data=42.5, format="string")
|
|
79
|
+
|
|
80
|
+
# 运行(阻塞,处理心跳和消息接收)
|
|
81
|
+
client.run()
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### 服务端 SDK
|
|
85
|
+
|
|
86
|
+
```python
|
|
87
|
+
from pulse_mq import PulseMQServer
|
|
88
|
+
|
|
89
|
+
server = PulseMQServer() # 自动读取 .env 配置
|
|
90
|
+
server.start()
|
|
91
|
+
|
|
92
|
+
# 服务端直接推送消息
|
|
93
|
+
server.publish("alerts/system", data="CPU 过高", format="string")
|
|
94
|
+
server.publish("metrics/batch", data=df, format="pyarrow") # DataFrame
|
|
95
|
+
|
|
96
|
+
server.stop()
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
## 消息格式
|
|
100
|
+
|
|
101
|
+
| 格式 | 说明 | 示例 |
|
|
102
|
+
|------|------|------|
|
|
103
|
+
| `string` | 原生字符串,最长 4096 字节 | `client.publish("t/d", data="hello", format="string")` |
|
|
104
|
+
| `msgpack` | MessagePack 序列化 | `client.publish("t/d", data={"key": "val"}, format="msgpack")` |
|
|
105
|
+
| `pyarrow` | PyArrow IPC 格式(支持 DataFrame) | `client.publish("t/d", data=df, format="pyarrow")` |
|
|
106
|
+
|
|
107
|
+
## Topic 命名规则
|
|
108
|
+
|
|
109
|
+
格式: `group/topic`(强制一层层级)
|
|
110
|
+
|
|
111
|
+
- 允许字符: `a-z A-Z 0-9 _ - .`
|
|
112
|
+
- 每段长度: 1-128 字符
|
|
113
|
+
- 示例: `metrics/cpu`, `logs/app-error`, `data_1/test.topic`
|
|
114
|
+
- 客户端推送/订阅时,topic 不存在会自动创建
|
|
115
|
+
|
|
116
|
+
## 配置项
|
|
117
|
+
|
|
118
|
+
所有配置通过 `.env` 文件或环境变量设置:
|
|
119
|
+
|
|
120
|
+
| 环境变量 | 默认值 | 说明 |
|
|
121
|
+
|---------|--------|------|
|
|
122
|
+
| `PULSE_HOST` | `0.0.0.0` | HTTP 服务监听地址 |
|
|
123
|
+
| `PULSE_HTTP_PORT` | `8080` | HTTP 服务端口 |
|
|
124
|
+
| `PULSE_ZMQ_ROUTER_PORT` | `5555` | ZMQ ROUTER 端口 |
|
|
125
|
+
| `PULSE_ZMQ_PULL_PORT` | `5556` | ZMQ PULL 端口 |
|
|
126
|
+
| `PULSE_DB_TYPE` | `sqlite` | 数据库类型 (sqlite/mysql) |
|
|
127
|
+
| `PULSE_DB_PATH` | `./pulse_mq.db` | SQLite 文件路径 |
|
|
128
|
+
| `PULSE_DB_URL` | - | MySQL 连接 URL |
|
|
129
|
+
| `PULSE_QUEUE_SIZE` | `1000` | 每 topic 内存队列大小 |
|
|
130
|
+
| `PULSE_AUTH_CACHE_TTL` | `60` | auth 缓存刷新间隔(秒) |
|
|
131
|
+
| `PULSE_JWT_SECRET` | 自动生成 | JWT 签名密钥 |
|
|
132
|
+
| `PULSE_JWT_EXPIRE_HOURS` | `24` | JWT 过期时间(小时) |
|
|
133
|
+
| `PULSE_ADMIN_PASSWORD` | 自动生成 | admin 初始密码 |
|
|
134
|
+
| `PULSE_HEARTBEAT_INTERVAL` | `30` | 客户端心跳间隔(秒) |
|
|
135
|
+
| `PULSE_HEARTBEAT_TIMEOUT` | `60` | 服务端断线超时(秒) |
|
|
136
|
+
| `PULSE_STATS_RETENTION_DAYS` | `7` | 统计数据保留天数 |
|
|
137
|
+
|
|
138
|
+
## 管理后台
|
|
139
|
+
|
|
140
|
+
启动服务后访问 `http://localhost:8080`,使用 admin 账号登录。
|
|
141
|
+
|
|
142
|
+
- **Dashboard** — 概览统计、Topic 监控图表、系统配置、快速开始代码
|
|
143
|
+
- **Topics** — Topic 列表、今日消息量、最近消息时间、7 天趋势图
|
|
144
|
+
- **Users** — 用户管理、Token 生成/复制、启停状态
|
|
145
|
+
- **Permissions** — 权限矩阵,按用户/Topic 控制推送(P)和订阅(S)权限
|
|
146
|
+
|
|
147
|
+
## 数据库
|
|
148
|
+
|
|
149
|
+
默认使用 SQLite,可通过配置切换到 MySQL:
|
|
150
|
+
|
|
151
|
+
```bash
|
|
152
|
+
# SQLite(默认)
|
|
153
|
+
PULSE_DB_TYPE=sqlite
|
|
154
|
+
PULSE_DB_PATH=./pulse_mq.db
|
|
155
|
+
|
|
156
|
+
# MySQL
|
|
157
|
+
PULSE_DB_TYPE=mysql
|
|
158
|
+
PULSE_DB_URL=mysql+pymysql://user:password@host:3306/pulse_mq
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
## 开发
|
|
162
|
+
|
|
163
|
+
```bash
|
|
164
|
+
# 克隆项目
|
|
165
|
+
git clone https://github.com/your-username/pulse-mq.git
|
|
166
|
+
cd pulse-mq
|
|
167
|
+
|
|
168
|
+
# 创建虚拟环境
|
|
169
|
+
uv venv
|
|
170
|
+
source .venv/bin/activate # Linux/macOS
|
|
171
|
+
# 或 .venv\Scripts\activate # Windows
|
|
172
|
+
|
|
173
|
+
# 安装依赖
|
|
174
|
+
uv pip install -e ".[dev]"
|
|
175
|
+
|
|
176
|
+
# 运行测试
|
|
177
|
+
pytest tests/ -v
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
## 许可证
|
|
181
|
+
|
|
182
|
+
MIT License
|
pulse_mq-0.1.0/README.md
ADDED
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
# PulseMQ
|
|
2
|
+
|
|
3
|
+
基于 ZeroMQ 的轻量级消息队列系统,配套 Grafana 风格的 Web 管理后台。
|
|
4
|
+
|
|
5
|
+
## 特性
|
|
6
|
+
|
|
7
|
+
- **双 Socket 架构** — ROUTER + PULL,消息延迟低
|
|
8
|
+
- **多格式支持** — 字符串、msgpack、PyArrow
|
|
9
|
+
- **Web 管理后台** — Grafana 暗色主题,ECharts 监控图表
|
|
10
|
+
- **权限管理** — 用户 token 认证,按 topic 控制推送/订阅权限
|
|
11
|
+
- **单进程部署** — 一条命令启动全部服务
|
|
12
|
+
- **PyPI 安装** — `pip install pulse-mq` 即可使用
|
|
13
|
+
|
|
14
|
+
## 快速安装
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
pip install pulse-mq
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## 快速开始
|
|
21
|
+
|
|
22
|
+
### 启动服务端
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
# 复制配置文件
|
|
26
|
+
cp .env.example .env
|
|
27
|
+
|
|
28
|
+
# 启动服务
|
|
29
|
+
pulse-mq-server
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
首次启动会自动生成 admin 密码,输出到控制台和 `admin_credentials.txt`。
|
|
33
|
+
|
|
34
|
+
管理后台默认地址: `http://localhost:8080`
|
|
35
|
+
|
|
36
|
+
### 客户端使用
|
|
37
|
+
|
|
38
|
+
```python
|
|
39
|
+
from pulse_mq import PulseMQClient
|
|
40
|
+
|
|
41
|
+
client = PulseMQClient(
|
|
42
|
+
host="localhost",
|
|
43
|
+
port=5555,
|
|
44
|
+
username="your-username",
|
|
45
|
+
token="your-token",
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
# 订阅消息(回调方式)
|
|
49
|
+
def on_message(msg):
|
|
50
|
+
print(f"Topic: {msg.topic}, Data: {msg.data}, Format: {msg.format}")
|
|
51
|
+
|
|
52
|
+
client.subscribe("metrics/cpu", callback=on_message)
|
|
53
|
+
|
|
54
|
+
# 推送消息
|
|
55
|
+
client.publish("metrics/cpu", data=42.5, format="string")
|
|
56
|
+
|
|
57
|
+
# 运行(阻塞,处理心跳和消息接收)
|
|
58
|
+
client.run()
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### 服务端 SDK
|
|
62
|
+
|
|
63
|
+
```python
|
|
64
|
+
from pulse_mq import PulseMQServer
|
|
65
|
+
|
|
66
|
+
server = PulseMQServer() # 自动读取 .env 配置
|
|
67
|
+
server.start()
|
|
68
|
+
|
|
69
|
+
# 服务端直接推送消息
|
|
70
|
+
server.publish("alerts/system", data="CPU 过高", format="string")
|
|
71
|
+
server.publish("metrics/batch", data=df, format="pyarrow") # DataFrame
|
|
72
|
+
|
|
73
|
+
server.stop()
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## 消息格式
|
|
77
|
+
|
|
78
|
+
| 格式 | 说明 | 示例 |
|
|
79
|
+
|------|------|------|
|
|
80
|
+
| `string` | 原生字符串,最长 4096 字节 | `client.publish("t/d", data="hello", format="string")` |
|
|
81
|
+
| `msgpack` | MessagePack 序列化 | `client.publish("t/d", data={"key": "val"}, format="msgpack")` |
|
|
82
|
+
| `pyarrow` | PyArrow IPC 格式(支持 DataFrame) | `client.publish("t/d", data=df, format="pyarrow")` |
|
|
83
|
+
|
|
84
|
+
## Topic 命名规则
|
|
85
|
+
|
|
86
|
+
格式: `group/topic`(强制一层层级)
|
|
87
|
+
|
|
88
|
+
- 允许字符: `a-z A-Z 0-9 _ - .`
|
|
89
|
+
- 每段长度: 1-128 字符
|
|
90
|
+
- 示例: `metrics/cpu`, `logs/app-error`, `data_1/test.topic`
|
|
91
|
+
- 客户端推送/订阅时,topic 不存在会自动创建
|
|
92
|
+
|
|
93
|
+
## 配置项
|
|
94
|
+
|
|
95
|
+
所有配置通过 `.env` 文件或环境变量设置:
|
|
96
|
+
|
|
97
|
+
| 环境变量 | 默认值 | 说明 |
|
|
98
|
+
|---------|--------|------|
|
|
99
|
+
| `PULSE_HOST` | `0.0.0.0` | HTTP 服务监听地址 |
|
|
100
|
+
| `PULSE_HTTP_PORT` | `8080` | HTTP 服务端口 |
|
|
101
|
+
| `PULSE_ZMQ_ROUTER_PORT` | `5555` | ZMQ ROUTER 端口 |
|
|
102
|
+
| `PULSE_ZMQ_PULL_PORT` | `5556` | ZMQ PULL 端口 |
|
|
103
|
+
| `PULSE_DB_TYPE` | `sqlite` | 数据库类型 (sqlite/mysql) |
|
|
104
|
+
| `PULSE_DB_PATH` | `./pulse_mq.db` | SQLite 文件路径 |
|
|
105
|
+
| `PULSE_DB_URL` | - | MySQL 连接 URL |
|
|
106
|
+
| `PULSE_QUEUE_SIZE` | `1000` | 每 topic 内存队列大小 |
|
|
107
|
+
| `PULSE_AUTH_CACHE_TTL` | `60` | auth 缓存刷新间隔(秒) |
|
|
108
|
+
| `PULSE_JWT_SECRET` | 自动生成 | JWT 签名密钥 |
|
|
109
|
+
| `PULSE_JWT_EXPIRE_HOURS` | `24` | JWT 过期时间(小时) |
|
|
110
|
+
| `PULSE_ADMIN_PASSWORD` | 自动生成 | admin 初始密码 |
|
|
111
|
+
| `PULSE_HEARTBEAT_INTERVAL` | `30` | 客户端心跳间隔(秒) |
|
|
112
|
+
| `PULSE_HEARTBEAT_TIMEOUT` | `60` | 服务端断线超时(秒) |
|
|
113
|
+
| `PULSE_STATS_RETENTION_DAYS` | `7` | 统计数据保留天数 |
|
|
114
|
+
|
|
115
|
+
## 管理后台
|
|
116
|
+
|
|
117
|
+
启动服务后访问 `http://localhost:8080`,使用 admin 账号登录。
|
|
118
|
+
|
|
119
|
+
- **Dashboard** — 概览统计、Topic 监控图表、系统配置、快速开始代码
|
|
120
|
+
- **Topics** — Topic 列表、今日消息量、最近消息时间、7 天趋势图
|
|
121
|
+
- **Users** — 用户管理、Token 生成/复制、启停状态
|
|
122
|
+
- **Permissions** — 权限矩阵,按用户/Topic 控制推送(P)和订阅(S)权限
|
|
123
|
+
|
|
124
|
+
## 数据库
|
|
125
|
+
|
|
126
|
+
默认使用 SQLite,可通过配置切换到 MySQL:
|
|
127
|
+
|
|
128
|
+
```bash
|
|
129
|
+
# SQLite(默认)
|
|
130
|
+
PULSE_DB_TYPE=sqlite
|
|
131
|
+
PULSE_DB_PATH=./pulse_mq.db
|
|
132
|
+
|
|
133
|
+
# MySQL
|
|
134
|
+
PULSE_DB_TYPE=mysql
|
|
135
|
+
PULSE_DB_URL=mysql+pymysql://user:password@host:3306/pulse_mq
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
## 开发
|
|
139
|
+
|
|
140
|
+
```bash
|
|
141
|
+
# 克隆项目
|
|
142
|
+
git clone https://github.com/your-username/pulse-mq.git
|
|
143
|
+
cd pulse-mq
|
|
144
|
+
|
|
145
|
+
# 创建虚拟环境
|
|
146
|
+
uv venv
|
|
147
|
+
source .venv/bin/activate # Linux/macOS
|
|
148
|
+
# 或 .venv\Scripts\activate # Windows
|
|
149
|
+
|
|
150
|
+
# 安装依赖
|
|
151
|
+
uv pip install -e ".[dev]"
|
|
152
|
+
|
|
153
|
+
# 运行测试
|
|
154
|
+
pytest tests/ -v
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
## 许可证
|
|
158
|
+
|
|
159
|
+
MIT License
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
from logging.config import fileConfig
|
|
4
|
+
|
|
5
|
+
from sqlalchemy import engine_from_config, pool
|
|
6
|
+
from alembic import context
|
|
7
|
+
|
|
8
|
+
# Add src to path so pulse_mq can be imported
|
|
9
|
+
sys.path.insert(0, str(Path(__file__).parent.parent / "src"))
|
|
10
|
+
|
|
11
|
+
from pulse_mq.models import Base
|
|
12
|
+
|
|
13
|
+
config = context.config
|
|
14
|
+
if config.config_file_name is not None:
|
|
15
|
+
fileConfig(config.config_file_name)
|
|
16
|
+
|
|
17
|
+
target_metadata = Base.metadata
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def run_migrations_offline() -> None:
|
|
21
|
+
context.configure(url=config.get_main_option("sqlalchemy.url"), target_metadata=target_metadata, literal_binds=True)
|
|
22
|
+
with context.begin_transaction():
|
|
23
|
+
context.run_migrations()
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def run_migrations_online() -> None:
|
|
27
|
+
connectable = engine_from_config(config.get_section(config.config_ini_section, {}), prefix="sqlalchemy.", poolclass=pool.NullPool)
|
|
28
|
+
with connectable.connect() as connection:
|
|
29
|
+
context.configure(connection=connection, target_metadata=target_metadata)
|
|
30
|
+
with context.begin_transaction():
|
|
31
|
+
context.run_migrations()
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
if context.is_offline_mode():
|
|
35
|
+
run_migrations_offline()
|
|
36
|
+
else:
|
|
37
|
+
run_migrations_online()
|
|
File without changes
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
[alembic]
|
|
2
|
+
script_location = alembic
|
|
3
|
+
sqlalchemy.url = sqlite:///./pulse_mq.db
|
|
4
|
+
|
|
5
|
+
[loggers]
|
|
6
|
+
keys = root,sqlalchemy,alembic
|
|
7
|
+
|
|
8
|
+
[handlers]
|
|
9
|
+
keys = console
|
|
10
|
+
|
|
11
|
+
[formatters]
|
|
12
|
+
keys = generic
|
|
13
|
+
|
|
14
|
+
[logger_root]
|
|
15
|
+
level = WARN
|
|
16
|
+
handlers = console
|
|
17
|
+
|
|
18
|
+
[logger_sqlalchemy]
|
|
19
|
+
level = WARN
|
|
20
|
+
handlers =
|
|
21
|
+
qualname = sqlalchemy.engine
|
|
22
|
+
|
|
23
|
+
[logger_alembic]
|
|
24
|
+
level = INFO
|
|
25
|
+
handlers =
|
|
26
|
+
qualname = alembic
|
|
27
|
+
|
|
28
|
+
[handler_console]
|
|
29
|
+
class = StreamHandler
|
|
30
|
+
args = (sys.stderr,)
|
|
31
|
+
level = NOTSET
|
|
32
|
+
formatter = generic
|
|
33
|
+
|
|
34
|
+
[formatter_generic]
|
|
35
|
+
format = %(levelname)-5.5s [%(name)s] %(message)s
|
|
36
|
+
datefmt = %H:%M:%S
|