优化api接口说明

This commit is contained in:
xuncha
2026-02-05 18:32:49 +08:00
committed by xuncha
parent 64d5e721af
commit e4ce9a3bd7
7 changed files with 557 additions and 111 deletions

3
.gitignore vendored
View File

@@ -57,4 +57,5 @@ Thumbs.db
wcdb/
*info
*.md
概述.md
chatlab-format.md

View File

@@ -38,6 +38,22 @@ WeFlow 是一个**完全本地**的微信**实时**聊天记录查看、分析
- 统计分析与群聊画像
- 年度报告与可视化概览
- 导出聊天记录为 HTML 等格式
- HTTP API 接口(供开发者集成)
## HTTP API
> [!WARNING]
> 此功能目前处于早期阶段,接口可能会有变动,请等待后续更新完善。
WeFlow 提供本地 HTTP API 服务,支持通过接口查询消息数据,可用于与其他工具集成或二次开发。
- **启用方式**:设置 → API 服务 → 启动服务
- **默认端口**5031
- **访问地址**`http://127.0.0.1:5031`
- **支持格式**:原始 JSON 或 [ChatLab](https://chatlab.fun/) 标准格式
📖 完整接口文档:[docs/HTTP-API.md](docs/HTTP-API.md)
## 快速开始

312
docs/HTTP-API.md Normal file
View File

@@ -0,0 +1,312 @@
# WeFlow HTTP API 接口文档
WeFlow 提供 HTTP API 服务,支持通过 HTTP 接口查询消息数据,支持 [ChatLab](https://github.com/nichuanfang/chatlab-format) 标准化格式输出。
## 启用 API 服务
在设置页面 → API 服务 → 点击「启动服务」按钮。
默认端口:`5031`
## 基础地址
```
http://127.0.0.1:5031
```
---
## 接口列表
### 1. 健康检查
检查 API 服务是否正常运行。
**请求**
```
GET /health
```
**响应**
```json
{
"status": "ok"
}
```
---
### 2. 获取消息列表
获取指定会话的消息,支持 ChatLab 格式输出。
**请求**
```
GET /api/v1/messages
```
**参数**
| 参数名 | 类型 | 必填 | 说明 |
|--------|------|------|------|
| `talker` | string | ✅ | 会话 IDwxid 或群 ID |
| `limit` | number | ❌ | 返回数量限制,默认 100 |
| `offset` | number | ❌ | 偏移量,用于分页,默认 0 |
| `start` | string | ❌ | 开始时间,格式 YYYYMMDD |
| `end` | string | ❌ | 结束时间,格式 YYYYMMDD |
| `chatlab` | string | ❌ | 设为 `1` 则输出 ChatLab 格式 |
| `format` | string | ❌ | 输出格式:`json`(默认)或 `chatlab` |
**示例请求**
```bash
# 获取消息(原始格式)
GET http://127.0.0.1:5031/api/v1/messages?talker=wxid_xxx&limit=50
# 获取消息ChatLab 格式)
GET http://127.0.0.1:5031/api/v1/messages?talker=wxid_xxx&chatlab=1
# 带时间范围查询
GET http://127.0.0.1:5031/api/v1/messages?talker=wxid_xxx&start=20260101&end=20260205&limit=100
```
**响应(原始格式)**
```json
{
"success": true,
"talker": "wxid_xxx",
"count": 50,
"hasMore": true,
"messages": [
{
"localId": 123,
"talker": "wxid_xxx",
"type": 1,
"content": "消息内容",
"createTime": 1738713600000,
"isSelf": false,
"sender": "wxid_sender"
}
]
}
```
**响应ChatLab 格式)**
```json
{
"chatlab": {
"version": "0.0.2",
"exportedAt": 1738713600000,
"generator": "WeFlow",
"description": "Exported from WeFlow"
},
"meta": {
"name": "会话名称",
"platform": "wechat",
"type": "private",
"ownerId": "wxid_me"
},
"members": [
{
"platformId": "wxid_xxx",
"accountName": "用户名",
"groupNickname": "群昵称"
}
],
"messages": [
{
"sender": "wxid_xxx",
"accountName": "用户名",
"timestamp": 1738713600000,
"type": 0,
"content": "消息内容"
}
]
}
```
---
### 3. 获取会话列表
获取所有会话列表。
**请求**
```
GET /api/v1/sessions
```
**参数**
| 参数名 | 类型 | 必填 | 说明 |
|--------|------|------|------|
| `keyword` | string | ❌ | 搜索关键词,匹配会话名或 ID |
| `limit` | number | ❌ | 返回数量限制,默认 100 |
**示例请求**
```bash
GET http://127.0.0.1:5031/api/v1/sessions
GET http://127.0.0.1:5031/api/v1/sessions?keyword=工作群&limit=20
```
**响应**
```json
{
"success": true,
"count": 50,
"total": 100,
"sessions": [
{
"username": "wxid_xxx",
"displayName": "用户名",
"lastMessage": "最后一条消息",
"lastTime": 1738713600000,
"unreadCount": 0
}
]
}
```
---
### 4. 获取联系人列表
获取所有联系人信息。
**请求**
```
GET /api/v1/contacts
```
**参数**
| 参数名 | 类型 | 必填 | 说明 |
|--------|------|------|------|
| `keyword` | string | ❌ | 搜索关键词 |
| `limit` | number | ❌ | 返回数量限制,默认 100 |
**示例请求**
```bash
GET http://127.0.0.1:5031/api/v1/contacts
GET http://127.0.0.1:5031/api/v1/contacts?keyword=张三
```
**响应**
```json
{
"success": true,
"count": 50,
"contacts": [
{
"userName": "wxid_xxx",
"alias": "微信号",
"nickName": "昵称",
"remark": "备注名"
}
]
}
```
---
## ChatLab 格式说明
ChatLab 是一种标准化的聊天记录交换格式,版本 0.0.2。
### 消息类型映射
| ChatLab Type | 值 | 说明 |
|--------------|-----|------|
| TEXT | 0 | 文本消息 |
| IMAGE | 1 | 图片 |
| VOICE | 2 | 语音 |
| VIDEO | 3 | 视频 |
| FILE | 4 | 文件 |
| EMOJI | 5 | 表情 |
| LINK | 7 | 链接 |
| LOCATION | 8 | 位置 |
| RED_PACKET | 20 | 红包 |
| TRANSFER | 21 | 转账 |
| CALL | 23 | 通话 |
| SYSTEM | 80 | 系统消息 |
| RECALL | 81 | 撤回消息 |
| OTHER | 99 | 其他 |
---
## 使用示例
### PowerShell
```powershell
# 健康检查
Invoke-RestMethod http://127.0.0.1:5031/health
# 获取会话列表
Invoke-RestMethod http://127.0.0.1:5031/api/v1/sessions
# 获取消息
Invoke-RestMethod "http://127.0.0.1:5031/api/v1/messages?talker=wxid_xxx&limit=10"
# 获取 ChatLab 格式
Invoke-RestMethod "http://127.0.0.1:5031/api/v1/messages?talker=wxid_xxx&chatlab=1" | ConvertTo-Json -Depth 10
```
### cURL
```bash
# 健康检查
curl http://127.0.0.1:5031/health
# 获取会话列表
curl http://127.0.0.1:5031/api/v1/sessions
# 获取消息ChatLab 格式)
curl "http://127.0.0.1:5031/api/v1/messages?talker=wxid_xxx&chatlab=1"
```
### Python
```python
import requests
BASE_URL = "http://127.0.0.1:5031"
# 获取会话列表
sessions = requests.get(f"{BASE_URL}/api/v1/sessions").json()
print(sessions)
# 获取消息
messages = requests.get(f"{BASE_URL}/api/v1/messages", params={
"talker": "wxid_xxx",
"limit": 100,
"chatlab": 1
}).json()
print(messages)
```
### JavaScript / Node.js
```javascript
const BASE_URL = "http://127.0.0.1:5031";
// 获取会话列表
const sessions = await fetch(`${BASE_URL}/api/v1/sessions`).then(r => r.json());
console.log(sessions);
// 获取消息ChatLab 格式)
const messages = await fetch(`${BASE_URL}/api/v1/messages?talker=wxid_xxx&chatlab=1`)
.then(r => r.json());
console.log(messages);
```
---
## 注意事项
1. API 仅监听本地地址 `127.0.0.1`,不对外网开放
2. 需要先连接数据库才能查询数据
3. 时间参数格式为 `YYYYMMDD`(如 20260205
4. 支持 CORS可从浏览器前端直接调用

4
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{
"name": "weflow",
"version": "1.5.2",
"version": "1.5.3",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "weflow",
"version": "1.5.2",
"version": "1.5.3",
"hasInstallScript": true,
"dependencies": {
"better-sqlite3": "^12.5.0",

View File

@@ -1,6 +1,6 @@
{
"name": "weflow",
"version": "1.5.2",
"version": "1.5.3",
"description": "WeFlow",
"main": "dist-electron/main.js",
"author": "cc",

View File

@@ -1191,6 +1191,109 @@
}
}
// 通用弹窗覆盖层
.modal-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.5);
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
animation: fadeIn 0.2s ease;
}
// API 警告弹窗
.api-warning-modal {
width: 420px;
background: var(--bg-primary);
border-radius: 16px;
overflow: hidden;
box-shadow: 0 16px 48px rgba(0, 0, 0, 0.2);
animation: slideUp 0.25s ease;
.modal-header {
display: flex;
align-items: center;
gap: 10px;
padding: 20px 24px;
border-bottom: 1px solid var(--border-color);
svg {
color: var(--warning, #f59e0b);
}
h3 {
margin: 0;
font-size: 16px;
font-weight: 600;
color: var(--text-primary);
}
}
.modal-body {
padding: 20px 24px;
.warning-text {
margin: 0 0 16px;
font-size: 14px;
color: var(--text-secondary);
line-height: 1.6;
}
.warning-list {
display: flex;
flex-direction: column;
gap: 10px;
.warning-item {
display: flex;
align-items: flex-start;
gap: 8px;
font-size: 13px;
color: var(--text-secondary);
line-height: 1.5;
.bullet {
color: var(--warning, #f59e0b);
font-weight: bold;
}
}
}
}
.modal-footer {
display: flex;
justify-content: flex-end;
gap: 10px;
padding: 16px 24px;
border-top: 1px solid var(--border-color);
background: var(--bg-secondary);
}
}
@keyframes fadeIn {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
@keyframes slideUp {
from {
opacity: 0;
transform: translateY(10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
// 协议弹窗
.agreement-overlay {
@@ -1857,6 +1960,23 @@
}
}
// API 地址显示样式
.api-url-display {
display: flex;
gap: 8px;
margin-top: 8px;
input {
flex: 1;
font-family: 'SF Mono', 'Consolas', monospace;
font-size: 13px;
}
.btn {
flex-shrink: 0;
}
}
// API 服务设置样式
.status-badge {
display: inline-flex;

View File

@@ -143,6 +143,7 @@ function SettingsPage() {
const [httpApiPort, setHttpApiPort] = useState(5031)
const [httpApiRunning, setHttpApiRunning] = useState(false)
const [isTogglingApi, setIsTogglingApi] = useState(false)
const [showApiWarning, setShowApiWarning] = useState(false)
const isClearingCache = isClearingAnalyticsCache || isClearingImageCache || isClearingAllCache
@@ -1861,21 +1862,37 @@ function SettingsPage() {
// HTTP API 服务控制
const handleToggleApi = async () => {
if (isTogglingApi) return
// 启动时显示警告弹窗
if (!httpApiRunning) {
setShowApiWarning(true)
return
}
setIsTogglingApi(true)
try {
if (httpApiRunning) {
await window.electronAPI.http.stop()
setHttpApiRunning(false)
showMessage('API 服务已停止', true)
await window.electronAPI.http.stop()
setHttpApiRunning(false)
showMessage('API 服务已停止', true)
} catch (e: any) {
showMessage(`操作失败: ${e}`, false)
} finally {
setIsTogglingApi(false)
}
}
// 确认启动 API 服务
const confirmStartApi = async () => {
setShowApiWarning(false)
setIsTogglingApi(true)
try {
const result = await window.electronAPI.http.start(httpApiPort)
if (result.success) {
setHttpApiRunning(true)
if (result.port) setHttpApiPort(result.port)
showMessage(`API 服务已启动,端口 ${result.port}`, true)
} else {
const result = await window.electronAPI.http.start(httpApiPort)
if (result.success) {
setHttpApiRunning(true)
if (result.port) setHttpApiPort(result.port)
showMessage(`API 服务已启动,端口 ${result.port}`, true)
} else {
showMessage(`启动失败: ${result.error}`, false)
}
showMessage(`启动失败: ${result.error}`, false)
}
} catch (e: any) {
showMessage(`操作失败: ${e}`, false)
@@ -1893,113 +1910,93 @@ function SettingsPage() {
const renderApiTab = () => (
<div className="tab-content">
<div className="form-group">
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<div>
<label>HTTP API </label>
<span className="form-hint"> HTTP </span>
</div>
<div style={{ display: 'flex', alignItems: 'center', gap: 12 }}>
<span className={`status-badge ${httpApiRunning ? 'running' : 'stopped'}`}>
{httpApiRunning ? '运行中' : '已停止'}
</span>
<button
className={`btn ${httpApiRunning ? 'btn-danger' : 'btn-primary'}`}
onClick={handleToggleApi}
<label>HTTP API </label>
<span className="form-hint"> HTTP 访</span>
<div className="log-toggle-line">
<span className="log-status">
{httpApiRunning ? '运行中' : '已停止'}
</span>
<label className="switch">
<input
type="checkbox"
checked={httpApiRunning}
onChange={handleToggleApi}
disabled={isTogglingApi}
>
{isTogglingApi ? '处理中...' : (httpApiRunning ? '停止服务' : '启动服务')}
</button>
</div>
/>
<span className="switch-slider" />
</label>
</div>
</div>
<div className="divider" />
<div className="form-group">
<label></label>
<span className="form-hint">API </span>
<div style={{ display: 'flex', gap: 10, marginTop: 10 }}>
<input
type="number"
className="field-input"
value={httpApiPort}
onChange={(e) => setHttpApiPort(parseInt(e.target.value, 10) || 5031)}
disabled={httpApiRunning}
style={{ width: 120 }}
min={1024}
max={65535}
/>
<span className="form-hint" style={{ alignSelf: 'center' }}>
{httpApiRunning ? '停止服务后可修改端口' : '建议使用 1024-65535 之间的端口'}
</span>
</div>
<span className="form-hint">API 1024-65535</span>
<input
type="number"
className="field-input"
value={httpApiPort}
onChange={(e) => setHttpApiPort(parseInt(e.target.value, 10) || 5031)}
disabled={httpApiRunning}
style={{ width: 120 }}
min={1024}
max={65535}
/>
</div>
<div className="divider" />
<div className="form-group">
<label>API </label>
<span className="form-hint">使访 API</span>
<div style={{ display: 'flex', gap: 10, marginTop: 10, alignItems: 'center' }}>
<code className="api-url">http://127.0.0.1:{httpApiPort}</code>
<button className="btn btn-secondary btn-sm" onClick={handleCopyApiUrl}>
<Copy size={14} />
</button>
</div>
</div>
<div className="divider" />
<div className="form-group">
<label></label>
<span className="form-hint"> API </span>
<div className="api-docs" style={{ marginTop: 12 }}>
<div className="api-item">
<div className="api-endpoint">
<span className="method get">GET</span>
<code>/api/v1/messages</code>
</div>
<p className="api-desc"> ChatLab </p>
<div className="api-params">
<span className="param"><code>talker</code> - ID</span>
<span className="param"><code>limit</code> - </span>
<span className="param"><code>start</code> - (YYYYMMDD)</span>
<span className="param"><code>end</code> - (YYYYMMDD)</span>
<span className="param"><code>chatlab=1</code> - ChatLab </span>
</div>
</div>
<div className="api-item">
<div className="api-endpoint">
<span className="method get">GET</span>
<code>/api/v1/sessions</code>
</div>
<p className="api-desc"></p>
</div>
<div className="api-item">
<div className="api-endpoint">
<span className="method get">GET</span>
<code>/api/v1/contacts</code>
</div>
<p className="api-desc"></p>
</div>
<div className="api-item">
<div className="api-endpoint">
<span className="method get">GET</span>
<code>/health</code>
</div>
<p className="api-desc"></p>
{httpApiRunning && (
<div className="form-group">
<label>API </label>
<span className="form-hint">使访 API</span>
<div className="api-url-display">
<input
type="text"
className="field-input"
value={`http://127.0.0.1:${httpApiPort}`}
readOnly
/>
<button className="btn btn-secondary" onClick={handleCopyApiUrl} title="复制">
<Copy size={16} />
</button>
</div>
</div>
</div>
)}
<div className="divider" />
<div className="form-group">
<label></label>
<div className="code-block" style={{ marginTop: 10 }}>
<code>GET http://127.0.0.1:{httpApiPort}/api/v1/messages?talker=wxid_xxx&limit=100&chatlab=1</code>
{/* API 安全警告弹窗 */}
{showApiWarning && (
<div className="modal-overlay" onClick={() => setShowApiWarning(false)}>
<div className="api-warning-modal" onClick={(e) => e.stopPropagation()}>
<div className="modal-header">
<ShieldCheck size={20} />
<h3></h3>
</div>
<div className="modal-body">
<p className="warning-text"> HTTP API 访</p>
<div className="warning-list">
<div className="warning-item">
<span className="bullet"></span>
<span></span>
</div>
<div className="warning-item">
<span className="bullet"></span>
<span>使</span>
</div>
<div className="warning-item">
<span className="bullet"></span>
<span>使</span>
</div>
</div>
</div>
<div className="modal-footer">
<button className="btn btn-secondary" onClick={() => setShowApiWarning(false)}>
</button>
<button className="btn btn-primary" onClick={confirmStartApi}>
</button>
</div>
</div>
</div>
</div>
)}
</div>
)