仪表盘
卡密管理
| ID | 卡密前缀 | 类型 | 天数 | 备注 | 绑定用户 | 状态 | 创建时间 | 操作 |
|---|
软件管理
| ID | 应用名称 | 版本号 | 公告 | 策略 | 状态 | 创建时间 | 操作 |
|---|
代理管理
| ID | 账号 | 备注 | 配额 | 已用 | 子代理数 | 管理权限 | 分润比例 | 状态 | 创建时间 | 操作 |
|---|
设备管理
| ID | 设备指纹 | 绑定用户 | 设备信息 | 状态 | 最后心跳 | 操作 |
|---|
远程脚本配置
| ID | 设备指纹 | 脚本哈希 | 状态 | 下发时间 | 执行结果 |
|---|
审计日志
| ID | 用户ID | 操作 | 目标 | IP地址 | 时间 |
|---|
系统设置
短信服务配置
邮件服务配置(SMTP)
使用说明
- • 验证码由系统自动生成(6位随机数字),有效期5分钟。
- • 短信服务:阿里云短信需安装
@alicloud/dysmsapi,腾讯云短信需安装tencentcloud-sdk-nodejs。 - • 邮件服务:推荐使用 QQ邮箱 SMTP(免费),在 QQ邮箱设置 → 账户 → 生成授权码。
- • 未配置任何服务商时,验证码会打印在控制台日志中,方便开发调试。
- • 模板中使用
{code}作为验证码占位符,系统会自动替换。
中转状态概览(B)
http://{B的IP}:8113/api/relay
使用文档
架构说明(A/B/C 模型)
本系统采用 纯转发模式,中转端 B转发所有请求,C 的 IP 对 A完全透明不暴露。
C(服务端)心跳 → B(中转端)
C 每5秒向 B 发送心跳请求,B 自动记录 C 的来源 IP。
心跳只需带上 绑定名称,不需要规则单词。同一绑定下的所有规则共享同一个 IP:
# 例:绑定 app1(C 向 B 发送心跳)
curl -X POST http://B的IP:8113/api/relay/heartbeat/app1
# Python 示例(C 端运行)
import requests, time
while True:
r = requests.post("http://B的IP:8113/api/relay/heartbeat/app1")
print(r.json())
time.sleep(5)
A(客户端)转发请求 → B(中转端)
A 向 B 发送请求,B 自动转发到 C 的对应端口,C 的 IP对 A 完全透明:
# A 向 B 发送请求,B 转发到 C(A 端运行)
import requests
# 例:绑定 app1,规则 game1,请求 /download/file.zip
r = requests.get("http://B的IP:8113/api/relay/proxy/app1/game1/download/file.zip")
print(r.text)
# POST 请求同样支持
# r = requests.post("http://B的IP:8113/api/relay/proxy/app1/game1/api/data",
# json={"key": "value"})
流量说明
- • A → B → C:A 的所有请求都经过 B 转发到 C,C 的响应也经 B 返回给 A
- • C → B:每5秒一次心跳包(极小流量)
- • C 的 IP 完全隐藏:A 不知道 C 的真实 IP,只能通过 B 中转
- • B(中转端)需要处理所有数据流量,请确保带宽充足
注意事项
- • C 的 IP 变化后,最多等待5秒(心跳间隔)B 即可自动更新转发目标
- • 如果 C 超过30秒未发送心跳,B 会标记为离线,转发会返回 502
- • 心跳接口和转发接口无需认证,请勿对外公开 B 的中转地址
- • 建议 C 使用
systemd或supervisor守护心跳进程
新增绑定
新增规则
编辑规则设置
代码案例
心跳请求
转发请求
心跳请求
转发请求
B的IP 替换为实际的中转端 B 的地址
在线设备
| 设备指纹 | 用户ID | 设备信息 | 状态 | 最后心跳 | 操作 |
|---|
封禁记录
| 设备指纹 | 用户ID | 设备信息 | 状态 | 最后心跳 | 操作 |
|---|
下发记录
| ID | 设备指纹 | 脚本哈希 | 状态 | 下发时间 | 执行结果 |
|---|
注册管理
| ID | 用户名 | 手机号 | 邮箱 | 应用 | 角色 | 状态 | 注册时间 |
|---|
操作文档
基础信息
服务器地址:http://localhost:3001
请求头:所有接口需携带 Content-Type: application/json
认证方式:JWT Bearer Token,登录后获取 accessToken,在请求头中携带 Authorization: Bearer <token>
Token 有效期:accessToken 30分钟,refreshToken 7天
验证码:6位数字,有效期5分钟,存储在 Redis
响应格式:
{
"code": 0, // 0=成功,非0=失败
"message": "...", // 提示信息
"data": {...} // 响应数据
}
错误码说明:
400 参数错误
401 未认证
403 无权限/封禁
404 资源不存在
429 请求太频繁
500 服务器错误
认证接口
管理员/代理/用户 用户名密码登录
请求参数:
{"username":"admin","password":"admin123456"}
响应示例:
{
"code": 0,
"data": {
"user": {"id":1,"username":"admin","role":"admin"},
"accessToken": "eyJ...",
"refreshToken": "eyJ...",
"expiresIn": "30m"
}
}
管理员/代理创建用户账号
{"username":"newuser","password":"123456","role":"user"}
刷新 accessToken(refreshToken 有效期7天)
{"refreshToken":"eyJ..."}
注销(将当前 token 加入黑名单)
需携带 Authorization 头,无需请求体
获取当前登录用户信息
需携带 Authorization 头
验证码注册/登录
发送验证码到手机或邮箱(需先在系统设置中配置短信/邮件服务)
请求参数:
{"identifier":"13800138000","type":"phone"}
type 可选值:phone(手机号)、email(邮箱)
手机号 + 验证码注册,注册后自动登录返回 token
{"phone":"13800138000","code":"123456","password":"可选密码"}
邮箱 + 验证码注册,注册后自动登录返回 token
{"email":"user@example.com","code":"123456","password":"可选密码"}
手机号 + 验证码登录(无需密码)
{"phone":"13800138000","code":"123456"}
邮箱 + 验证码登录(无需密码)
{"email":"user@example.com","code":"123456"}
卡密与设备客户端接口
卡密激活 — 客户端使用卡密授权,绑定用户和设备
请求参数:
{
"rawKey": "XXXX-XXXX-XXXX",
"deviceFp": "fp_xxx",
"deviceInfo": "Windows 10 Chrome 120"
}
响应示例:
{
"code": 0,
"message": "卡密激活成功",
"data": {
"cardKey": "XXXX-XXXX-XXXX",
"expiresAt": "2026-09-23T00:00:00.000Z",
"deviceFp": "fp_xxx"
}
}
验证授权状态 — 客户端定期调用,检测授权是否有效
{"deviceFp":"fp_xxx"}
响应示例:
{
"code": 0,
"data": {
"valid": true,
"expiresAt": "2026-09-23T00:00:00.000Z",
"daysRemaining": 30
}
}
单设备心跳上报 — 建议每30秒调用一次,更新 Redis 在线状态
{"deviceFp":"fp_xxx"}
批量心跳上报 — 单次最多100台设备,使用 Redis pipeline 高速写入
{"devices":[{"deviceFp":"fp_001"},{"deviceFp":"fp_002"}]}
解绑设备 — 将指定设备与卡密解绑,释放设备绑定名额
{"deviceFp":"fp_xxx","cardId":123}
远程脚本接口
客户端拉取并执行远程脚本 — 客户端定期轮询此接口获取待执行脚本
请求参数:
{"deviceFp":"fp_xxx","appId":1}
响应示例:
{
"code": 0,
"data": {
"hasScript": true,
"scriptId": 456,
"content": "console.log('hello');",
"hash": "a1b2c3d4"
}
}
客户端上报脚本执行结果
请求参数:
{
"deviceFp": "fp_xxx",
"scriptId": 456,
"status": "success",
"output": "Script executed successfully"
}
管理后台接口
获取仪表盘概览数据(在线设备数、卡密总数、代理数、今日激活数)
需管理员权限,携带 Authorization 头
获取卡密列表(支持分页、筛选、搜索)
查询参数:page, pageSize, appId, cardType, keyword, agentId
批量生成卡密
{"count":10,"cardType":"premium","days":30,"remark":"测试","appId":1}
单卡生成
{"cardType":"premium","days":30,"remark":"单卡备注","appId":1}
卡密续期
{"cardId":123,"extraDays":30}
封禁/解封卡密
{"cardId":123,"ban":true}
获取软件列表
创建新软件
{
"name": "MyApp",
"version": "1.0.0",
"announcement": "欢迎使用",
"banIp": false,
"bindIp": false,
"bindDevice": true
}
更新软件信息
删除软件
获取设备列表(支持分页、筛选、搜索)
查询参数:page, pageSize, appId, keyword
管理员解绑设备
{"deviceFp":"fp_xxx"}
获取脚本列表
下发脚本到指定设备
{"deviceFp":"fp_xxx","content":"console.log('hello');","appId":1}
获取脚本下发历史记录
获取注册用户列表
获取审计日志
获取系统设置
保存系统设置(短信/邮件配置)
代理接口
获取代理列表(支持分页、按主账号查看下级代理)
查询参数:page, pageSize, agentId(切换视图时使用)
创建代理账号
{
"username": "agent01",
"password": "pass123",
"remark": "一级代理",
"maxSubAgents": 5,
"canManageAgents": false,
"quota2h": 100,
"quotaDaily": 500,
"quotaWeekly": 3000,
"quotaMonthly": 10000,
"quotaHalfYear": 50000,
"quotaYearly": 100000,
"quotaSms": 200,
"quotaEmail": 200
}
更新代理配额
{
"agentId": 5,
"quota2h": 200,
"quotaDaily": 1000,
"quotaWeekly": 5000
}
封禁/解封代理账号
{"agentId":5,"ban":true}
获取代理下拉列表(用于视图切换)
多语言对接示例
Python 示例
import requests
import json
BASE_URL = "http://localhost:3001"
TOKEN = ""
# 登录获取 token
def login():
r = requests.post(f"{BASE_URL}/api/auth/login", json={
"username": "admin",
"password": "admin123456"
})
data = r.json()
TOKEN = data["data"]["accessToken"]
return TOKEN
# 卡密激活
def activate_card(raw_key, device_fp):
r = requests.post(f"{BASE_URL}/api/client/card/activate", json={
"rawKey": raw_key,
"deviceFp": device_fp,
"deviceInfo": "Python SDK v1.0"
})
return r.json()
# 心跳上报
def heartbeat(device_fp):
r = requests.post(f"{BASE_URL}/api/client/heartbeat", json={
"deviceFp": device_fp
})
return r.json()
# 批量生成卡密(需管理员token)
def generate_cards(token, count=10, card_type="premium", days=30, app_id=1):
headers = {"Authorization": f"Bearer {token}"}
r = requests.post(f"{BASE_URL}/api/admin/card/generate",
json={"count": count, "cardType": card_type, "days": days, "appId": app_id},
headers=headers)
return r.json()
Node.js 示例
const BASE_URL = "http://localhost:3001";
async function login() {
const res = await fetch(`${BASE_URL}/api/auth/login`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ username: "admin", password: "admin123456" })
});
const data = await res.json();
return data.data.accessToken;
}
async function activateCard(rawKey, deviceFp) {
const res = await fetch(`${BASE_URL}/api/client/card/activate`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ rawKey, deviceFp, deviceInfo: "Node.js SDK" })
});
return res.json();
}
async function heartbeat(deviceFp) {
const res = await fetch(`${BASE_URL}/api/client/heartbeat`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ deviceFp })
});
return res.json();
}
C# 示例
using System;
using System.Net.Http;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
class KmsClient
{
private static readonly HttpClient client = new HttpClient();
private const string BaseUrl = "http://localhost:3001";
public static async Task<string> LoginAsync()
{
var payload = JsonSerializer.Serialize(new { username = "admin", password = "admin123456" });
var content = new StringContent(payload, Encoding.UTF8, "application/json");
var response = await client.PostAsync($"{BaseUrl}/api/auth/login", content);
var json = await response.Content.ReadAsStringAsync();
return JsonDocument.Parse(json).RootElement
.GetProperty("data").GetProperty("accessToken").GetString();
}
public static async Task<string> ActivateCardAsync(string rawKey, string deviceFp)
{
var payload = JsonSerializer.Serialize(new {
rawKey, deviceFp, deviceInfo = "C# SDK v1.0"
});
var content = new StringContent(payload, Encoding.UTF8, "application/json");
var response = await client.PostAsync($"{BaseUrl}/api/client/card/activate", content);
return await response.Content.ReadAsStringAsync();
}
}
curl 命令行示例
# 登录
curl -X POST http://localhost:3001/api/auth/login \
-H "Content-Type: application/json" \
-d '{"username":"admin","password":"admin123456"}'
# 卡密激活
curl -X POST http://localhost:3001/api/client/card/activate \
-H "Content-Type: application/json" \
-d '{"rawKey":"XXXX-XXXX-XXXX","deviceFp":"fp_xxx","deviceInfo":"curl"}'
# 心跳上报
curl -X POST http://localhost:3001/api/client/heartbeat \
-H "Content-Type: application/json" \
-d '{"deviceFp":"fp_xxx"}'
# 批量生成卡密(需替换 TOKEN)
curl -X POST http://localhost:3001/api/admin/card/generate \
-H "Content-Type: application/json" \
-H "Authorization: Bearer TOKEN" \
-d '{"count":10,"cardType":"premium","days":30,"appId":1}'
# 查看仪表盘
curl -X GET http://localhost:3001/api/admin/dashboard \
-H "Authorization: Bearer TOKEN"