API 接口文档
API 同时支持:
- 兼容模式(legacy):
GET+MD5签名 - 推荐模式(v2):
POST+HMAC-SHA256+时间戳/随机数防重放 -
生产环境域名:
https://commu.fun/ -
通用返回格式:
| 字段名 | 说明 |
|---|---|
| code | 错误码,请求成功为 0 |
| msg | 返回的消息内容,一般为错误信息 |
| data | 接口返回数据 |
| encrypted_data | (可选) 加密后的数据,如果开启了加密模式 |
- 如果返回数据为加密数据:
enc_ver=2:使用 AES-GCM 解密encrypted_data- 未携带
enc_ver:视为 legacy AES-CBC(仅用于兼容旧客户端)
Lua 脚本对接教程
系统支持 Lua 脚本分发(普通模式/安全模式)。完整对接教程提供下载:
- Lua 脚本对接教程(下载)
- 入口概览:
- 普通模式:
POST /api/v1/activate - 安全模式:
GET /api/v2/secure/public_key、POST /api/v2/secure/verify、GET /api/v2/secure/download
验证卡密 / 激活
POST /api/v2/check(推荐)
GET /check
这个API 用于验证卡密状态、激活新卡密以及绑定机器码。
请求参数
| 参数名 | 说明 |
|---|---|
| key | 卡密字符串 (例如: KEY-XXXX) |
| hwid | 机器码 (设备唯一标识) |
| instance_id | 软件实例ID (Software Instance ID) |
| sign | 签名 (算法见下文) |
v2 版本改为 JSON Body:
{"key":"KEY-XXXX","hwid":"HWID-...","instance_id":"12345678"}
并携带请求头:
X-Timestamp: 秒级时间戳X-Nonce: 随机字符串(一次性)X-Signature:HMAC-SHA256签名
返回示例
{
"code": 0,
"msg": "success",
"data": {
"status": "valid",
"expire_date": "2026-12-31 23:59:59",
"hwid": "HWID-ABC-123",
"announcement": {
"title": "系统公告",
"content": "欢迎使用本系统!",
"time": "2026-01-01 12:00:00"
}
}
}
公告下发(Announcement)
公告不需要单独调用接口获取:系统会在以下接口的成功响应中自动携带 announcement 字段(可能为 null):
/api/v2/check、/check/api/v2/unbind、/unbind/api/v2/info、/info/cloudvar/log
后台如何配置公告
进入后台管理面板 → 公告管理:
- 标题:对应返回里的
announcement.title - 内容:对应返回里的
announcement.content - 启用:关闭后不会下发
- 所属实例:选择后为“实例公告”;留空为“全局公告”(通常仅系统管理员需要)
服务端选择规则(重要)
服务端会从公告表中取出最近一条启用公告作为下发内容:
- 过滤条件:
is_active = true且(software_id = 当前实例ID或software_id is null) - 排序规则:按
created_at倒序,取最新 1 条
也就是说:如果你后创建了一条全局公告,它会覆盖旧的实例公告(因为更“新”)。
心跳检测(卡密状态轮询)
POST /api/v2/heartbeat(推荐)
GET /heartbeat
客户端激活卡密后,定期调用此接口检测卡密是否仍然有效(过期/封禁/机器码不匹配等)。
与 /check 不同,心跳接口不会触发激活、不会绑定/换绑机器码、不写入事件日志,适合高频轮询。
请求参数
| 参数名 | 说明 |
|---|---|
| key | 卡密字符串 (例如: KEY-XXXX) |
| hwid | 机器码 (设备唯一标识) |
| instance_id | 软件实例ID (Software Instance ID) |
| sign | 签名 (算法见下文) |
v2 版本改为 JSON Body:
{"key":"KEY-XXXX","hwid":"HWID-...","instance_id":"12345678"}
并携带请求头:
X-Timestamp: 秒级时间戳X-Nonce: 随机字符串(一次性)X-Signature:HMAC-SHA256签名
返回示例
卡密有效:
{
"code": 0,
"msg": "success",
"data": {
"status": "valid",
"expire_date": "2026-12-31 23:59:59",
"remaining_seconds": 86400,
"announcement": null
}
}
永久卡密:
{
"code": 0,
"msg": "success",
"data": {
"status": "valid",
"expire_date": "永久",
"remaining_seconds": null,
"announcement": null
}
}
卡密已过期:
{"code": 4, "msg": "卡密已过期"}
与 /check 的区别
| 特性 | /check(/v2/check) |
/heartbeat(/v2/heartbeat) |
|---|---|---|
| 激活未使用卡密 | ✅ 会激活 | ❌ 返回 code=7 |
| HWID 绑定 | ✅ 自动绑定/换绑 | ❌ 仅校验是否匹配 |
| 事件日志 | ✅ 写入 EventLog | ❌ 不写入 |
| 更新 last_seen | ✅ | ✅ |
| 返回剩余秒数 | ❌ | ✅ remaining_seconds |
| 适用场景 | 首次激活 / 启动校验 | 运行中定期心跳 |
错误码(心跳专用)
| code | 说明 |
|---|---|
| 7 | 卡密未激活 |
其余错误码与 /check 一致(见下方错误码说明)。
解绑卡密
POST /api/v2/unbind(推荐)
GET /unbind
用于解绑卡密的机器码绑定。
请求参数
| 参数名 | 说明 |
|---|---|
| key | 卡密字符串 |
| hwid | 当前绑定的机器码 |
| instance_id | 软件实例ID |
| sign | 签名 |
返回示例
{
"code": 0,
"msg": "success",
"data": {
"status": "unbound_success"
}
}
行为说明:自动解绑并重绑(限次数)
当卡密已激活且新设备上的 hwid 与已绑定的不同:
- 若设置了解绑次数上限
unbind_limit,且尚未用尽: - 系统自动将绑定迁移到新设备(当前
hwid) - 同时将
unbind_count加 1 - 若已用尽次数:
- 返回
code=6,msg="当前卡密已绑定,无可换绑次数" - 需要管理员在后台手动解绑
说明:
- 每张卡密仅允许同时绑定一个机器码;服务端使用行级锁保证并发下的唯一性。
- 如未设置
unbind_limit或设置为负数,视为不限制;建议在后台为新卡密设置明确上限。
获取软件信息
POST /api/v2/info(推荐)
GET /info
获取软件的最新版本号和更新内容。
请求参数
| 参数名 | 说明 |
|---|---|
| instance_id | 软件实例ID |
| sign | 签名 |
返回示例
{
"code": 0,
"msg": "success",
"data": {
"version": "1.0.2",
"update_content": "1. 修复了已知BUG\n2. 优化了性能"
}
}
获取云变量
GET /cloudvar
获取软件配置的云端变量(配置项)。
请求参数
| 参数名 | 说明 |
|---|---|
| key | 变量名 (Key) |
| instance_id | 软件实例ID |
| sign | 签名 |
返回示例
{
"code": 0,
"msg": "success",
"data": {
"value": "这里是云变量的值",
"announcement": null
}
}
发送事件日志
GET /log
客户端上报自定义事件日志到后台。
请求参数
| 参数名 | 说明 |
|---|---|
| message | 日志内容 (需 URL 编码) |
| instance_id | 软件实例ID |
| sign | 签名 |
返回示例
{
"code": 0,
"msg": "success",
"data": {
"status": "logged",
"announcement": null
}
}
卡密批量启停(后台管理)
用于后台“卡密管理”页面对选中卡密进行批量停用/启用。该接口依赖后台登录的 Session Cookie,不用于客户端 SDK 对接。
POST /admin/card/command
请求 Body(JSON)
停用卡密:
{"action":"deactivate","keys":["KEY-1","KEY-2"]}
启用卡密:
{"action":"activate","keys":["KEY-1","KEY-2"]}
返回格式(标准化)
{
"code": 0,
"msg": "success",
"data": {
"action": "deactivate",
"affected_count": 2,
"requested_count": 2,
"missing_count": 0,
"results": [
{"key":"KEY-1","ok":true,"before":"unused","after":"banned"},
{"key":"KEY-2","ok":true,"before":"unused","after":"banned"}
]
}
}
维护模式
维护模式按实例独立:每个实例(instance_id)有自己的 maintenance_mode 开关和自定义公告,互不影响。
在以下入口开启:
- 网页管理后台 → 「实例管理」→ 编辑某实例 → 勾选「维护模式」并填写「维护公告」
- 桌面端管理工具 → 「系统设置」→ 「维护模式」面板 → 逐个实例开关
开启后,仅针对该实例:
- 以下接口将直接返回
HTTP 503,不执行任何业务逻辑: /api/check、/api/v2/check(卡密验证)/api/unbind、/api/v2/unbind(解绑)/api/heartbeat、/api/v2/heartbeat(心跳)- 以下接口不受影响(维护期间仍可正常使用):
/api/v2/info、/info(获取软件信息)/api/check-update(检查更新)/cloudvar(云变量)/log、/api/v2/log(日志上报)/api/packer_login、/api/verify_packer(管理端登录)- 所有
/api/desktop/*桌面管理端接口 - 其他未开启维护模式的实例的全部接口
503 响应示例
{"detail": "系统维护中,请稍后再试"}
detail 内容为管理员设置的自定义维护公告。
客户端建议处理方式
res = requests.post(url, json=body, headers=headers)
if res.status_code == 503:
msg = res.json().get("detail", "系统维护中")
show_maintenance_notice(msg)
return
错误码说明
| code | 说明 |
|---|---|
| 0 | success |
| 1 | 验证失败(或“卡密不存在”,当启用详细错误时) |
| 2 | 卡密不属于该实例(启用详细错误时) |
| 3 | 机器码不匹配(主动解绑接口) |
| 4 | 卡密已过期 |
| 5 | 卡密已封禁 |
| 6 | 当前卡密已绑定,无可换绑次数(自动重绑失败,需要人工处理) |
| 7 | 卡密未激活(心跳接口专用) |
签名算法 (Signature)
v2(推荐):HMAC-SHA256 + 防重放
- 取 Body 原始 JSON(建议紧凑格式
separators=(',', ':')) - 计算
body_sha256 = sha256(body_bytes) - 拼接签名串(换行分隔):
METHOD\nPATH\nTIMESTAMP\nNONCE\nBODY_SHA256
其中 PATH 为请求路径(不含域名,不含 query),例如:/api/v2/check。
signature = hmac_sha256(secret_key, message)(十六进制小写)- 放入请求头:
X-Timestamp/X-Nonce/X-Signature
legacy(兼容):MD5
所有 legacy 接口都需要 sign 参数,计算方法如下:
- 将所有请求参数(除了
sign本身)按照参数名的 ASCII 码从小到大排序。 - 将排序后的参数拼接成字符串:
key1=value1&key2=value2... - 在拼接好的字符串末尾直接加上后台设置的
Secret Key。 - 对最终字符串进行 MD5 运算,结果即为签名。
示例:
假设参数为 a=1, b=2,密钥为 secret。
- 排序并拼接:
a=1&b=2 - 加密钥:
a=1&b=2secret - MD5:
md5("a=1&b=2secret")->sign
接入代码示例
模块下载(Python)
- 下载地址:/static/docs/whisper_module.zip
- 内容包含:
- 模块/whisper_client.py(客户端)
- 模块/init.py
- 模块/whisper_config.py(硬编码配置示例,发布前请替换为你的真实值)
使用步骤
- 将
whisper_config.py中的API_BASE_URL/INSTANCE_ID/SECRET_KEY/ENCRYPT_KEY替换为你的真实值 - 解压后将
模块文件夹置于你的项目根目录或包路径下 - 安装依赖(如需解密):
pip install pycryptodome certifi- 代码示例:
from 模块 import get_default_client
client = get_default_client(verify_ssl=True)
res = client.check("KEY-XXXX...", "HWID-...")
print(res.ok, res.message, res.payload)
从响应中读取公告(若存在):
if res.ok:
data = (res.payload or {}).get("data") or {}
anno = data.get("announcement") or None
if anno:
print("公告标题:", anno.get("title"))
print("公告内容:", anno.get("content"))
读取云变量与上报日志:
v = client.cloudvar("some_key")
print(v.ok, v.payload)
r = client.log("client started")
print(r.ok, r.payload)
易语言 (EPL)
.版本 2
.支持库 spec
.子程序 生成签名, 文本型
.参数 参数表, 文本型
.局部变量 待签文本, 文本型
.局部变量 通信密钥, 文本型
通信密钥 = “YOUR_SECRET_KEY”
待签文本 = 参数表 + 通信密钥
返回 (取数据摘要 (到字节集 (待签文本)))
Python
import hashlib
def get_sign(params, secret_key):
sorted_keys = sorted([k for k in params.keys() if k != 'sign'])
param_str = "&".join([f"{k}={params[k]}" for k in sorted_keys])
raw = param_str + secret_key
return hashlib.md5(raw.encode('utf-8')).hexdigest()
Lua 脚本分发接口
如果您使用了系统的 Lua 脚本分发功能,请参考独立文档: Lua脚本分发系统说明 (LUA_SYSTEM_README.md)
安全文件下发
Whisper 提供了一套高安全性的文件下发机制,支持 RSA+AES 双重加密传输、一次性 Token 与内存流式解密,有效防御抓包、重放与中间人攻击。
核心特性
- 双重加密:请求体使用 AES 加密,AES 密钥使用 RSA 封装,确保传输安全。
- 一次性令牌:下载链接绑定 IP 与一次性 Token,防止链接泄露与盗链。
- 流式解密:文件内容通过 AES-GCM 流式加密传输,客户端在内存中解密,不落地明文文件。
Python 客户端示例
以下代码展示了如何进行安全握手、验证卡密并流式下载解密文件。
import requests
import base64
import json
import time
import os
from Crypto.Cipher import AES, PKCS1_OAEP
from Crypto.PublicKey import RSA
from Crypto.Random import get_random_bytes
from Crypto.Hash import SHA256
SERVER_URL = "http://localhost:8000"
# 请替换为您的真实卡密
CARD_KEY = "YOUR_CARD_KEY"
GCM_NONCE_SIZE = 12
GCM_TAG_SIZE = 16
def get_server_public_key():
resp = requests.get(f"{SERVER_URL}/api/v2/secure/public_key")
resp.raise_for_status()
return RSA.import_key(resp.json()["public_key"])
def encrypt_aes_gcm(key, plaintext):
cipher = AES.new(key, AES.MODE_GCM)
ciphertext, tag = cipher.encrypt_and_digest(plaintext)
return ciphertext, cipher.nonce, tag
def decrypt_aes_gcm(key, ciphertext, nonce, tag):
cipher = AES.new(key, AES.MODE_GCM, nonce=nonce)
return cipher.decrypt_and_verify(ciphertext, tag)
def main():
# 1. 获取公钥
pub_key = get_server_public_key()
# 2. 准备加密请求
aes_key = get_random_bytes(32)
payload = {
"card_key": CARD_KEY,
"timestamp": time.time(),
"nonce": os.urandom(8).hex()
}
# AES 加密数据
data_bytes = json.dumps(payload).encode()
ciphertext, nonce, tag = encrypt_aes_gcm(aes_key, data_bytes)
encrypted_data = base64.b64encode(nonce + tag + ciphertext).decode()
# RSA 加密 AES 密钥
cipher_rsa = PKCS1_OAEP.new(pub_key, hashAlgo=SHA256)
encrypted_key = base64.b64encode(cipher_rsa.encrypt(aes_key)).decode()
# 3. 发送验证请求
resp = requests.post(f"{SERVER_URL}/api/v2/secure/verify", json={
"encrypted_key": encrypted_key,
"encrypted_data": encrypted_data
})
if resp.status_code != 200:
print(f"[-] 验证失败: {resp.text}")
return
# 4. 解密响应
resp_json = resp.json()
enc_payload = base64.b64decode(resp_json["payload"])
nonce_resp = enc_payload[:GCM_NONCE_SIZE]
tag_resp = enc_payload[GCM_NONCE_SIZE:GCM_NONCE_SIZE+GCM_TAG_SIZE]
ciphertext_resp = enc_payload[GCM_NONCE_SIZE+GCM_TAG_SIZE:]
plaintext_resp = decrypt_aes_gcm(aes_key, ciphertext_resp, nonce_resp, tag_resp)
data = json.loads(plaintext_resp)
print(f"[+] 验证成功,文件版本: {data['file_info']['version']}")
# 5. 下载文件
token = data["token"]
file_key = bytes.fromhex(data["file_key"])
download_url = f"{SERVER_URL}{data['download_url'].split('?')[0]}"
print("[*] 开始安全下载...")
with requests.get(download_url, params={"token": token}, stream=True) as r:
# 流式解密逻辑...
pass
Whisper