144 KiB
144 KiB
软件授权系统 - 综合设计方案
一、需求分析
1.1 核心功能
| 模块 | 功能描述 |
|---|---|
| 启动器客户端 | 卡密登录 → 验证 → 下载软件 → 启动软件 |
| 管理后台 | 卡密生成/管理、多项目管理、统计报表 |
| API 服务 | 认证、授权、软件分发、心跳验证 |
| SDK(可选) | 供第三方软件集成,实现授权验证 |
1.2 用户角色
| 角色 | 描述 | 权限 |
|---|---|---|
| 超级管理员 | 系统最高权限 | 全部功能 + 代理管理 |
| 管理员 | 项目管理、卡密管理 | 所属项目的完整权限 |
| 代理商 | 销售卡密、管理额度 | 限制生成数量、查看销售统计 |
| 最终用户 | 使用卡密登录启动器 | 仅使用软件 |
| 软件开发者 | 集成 SDK 开发授权软件 | 查看开发文档 |
1.3 功能清单
┌─────────────────────────────────────────────────────────────────┐
│ 管理后台功能 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 📊 仪表盘 │
│ ├─ 实时统计(今日/本周/本月) │
│ ├─ 活跃用户趋势图 │
│ ├─ 项目分布饼图 │
│ └─ 最近活动日志 │
│ │
│ 📁 项目管理 │
│ ├─ 创建项目(自动生成 ProjectKey/Secret) │
│ ├─ 编辑项目信息 │
│ ├─ 上传软件包(自动加密) │
│ ├─ 查看项目统计 │
│ ├─ 查看开发文档(可编辑) │
│ └─ 删除/禁用项目 │
│ │
│ 🔑 卡密管理 │
│ ├─ 批量生成卡密 │
│ │ ├─ 选择项目 │
│ │ ├─ 选择类型(天卡/周卡/月卡/年卡/永久) │
│ │ ├─ 设置数量 │
│ │ ├─ 批量导出(TXT/CSV/Excel) │
│ │ └─ 预览生成结果 │
│ ├─ 卡密列表 │
│ │ ├─ 搜索(卡密/备注) │
│ │ ├─ 筛选(状态/类型/项目) │
│ │ ├─ 批量操作 │
│ │ └─ 导出 │
│ ├─ 卡密详情 │
│ │ ├─ 基本信息 │
│ │ ├─ 使用记录 │
│ │ ├─ 设备信息 │
│ │ ├─ 操作日志 │
│ │ └─ 编辑/封禁/删除 │
│ └─ 卡密操作 │
│ ├─ 封禁卡密 │
│ ├─ 解封卡密 │
│ ├─ 延长有效期 │
│ ├─ 修改备注 │
│ ├─ 重置设备绑定 │
│ └─ 删除卡密 │
│ │
│ 👥 代理商管理 │
│ ├─ 创建代理商 │
│ │ ├─ 设置账号密码 │
│ │ ├─ 设置初始额度 │
│ │ ├─ 设置折扣比例 │
│ │ └─ 分配可见项目 │
│ ├─ 代理商列表 │
│ ├─ 额度管理 │
│ │ ├─ 充值额度 │
│ │ ├─ 扣减额度 │
│ │ └─ 额度流水记录 │
│ ├─ 销售统计 │
│ └─ 禁用/删除代理商 │
│ │
│ 🖥️ 设备管理 │
│ ├─ 设备列表 │
│ ├─ 在线设备监控 │
│ ├─ 强制下线 │
│ └─ 解绑设备 │
│ │
│ 📝 日志审计 │
│ ├─ 操作日志 │
│ ├─ 登录日志 │
│ ├─ 验证日志 │
│ └─ 异常告警 │
│ │
│ 📖 开发文档 │
│ ├─ SDK 集成指南 │
│ ├─ API 接口文档 │
│ ├─ 代码示例 │
│ └─ 常见问题 │
│ │
│ ⚙️ 系统设置 │
│ ├─ 管理员账号管理 │
│ ├─ 系统配置 │
│ └─ 数据备份 │
│ │
└─────────────────────────────────────────────────────────────────┘
二、系统架构
┌─────────────────────────────────────────────────────────────────┐
│ 管理后台前端 │
│ (Vue3 + Element Plus) │
│ 项目管理 | 卡密生成 | 统计报表 | 日志审计 │
└─────────────────────────────┬───────────────────────────────────┘
│ HTTP/HTTPS
▼
┌─────────────────────────────────────────────────────────────────┐
│ 后端 API 服务 │
│ (ASP.NET Core Web API) │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ 卡密管理 │ │ 项目管理 │ │ 统计分析 │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ 验证服务 │ │ 日志审计 │ │ 风控引擎 │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
└─────────────────────────────┬───────────────────────────────────┘
│
┌─────────────────────────────┴───────────────────────────────────┐
│ 数据层 │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ PostgreSQL │ │ Redis │ │ 文件存储 │ │
│ │ (持久化) │ │ (缓存/锁) │ │ (软件包) │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
└─────────────────────────────────────────────────────────────────┘
▲
│ API 调用
┌─────────────────────────────┴───────────────────────────────────┐
│ │
│ ┌──────────────────┐ ┌──────────────────┐ │
│ │ 启动器客户端 │ │ 授权软件 │ │
│ │ (C# WPF) │ │ (C# + SDK) │ │
│ │ - 卡密验证 │ │ - 心跳验证 │ │
│ │ - 软件下载 │ │ - 机器码绑定 │ │
│ └──────────────────┘ └──────────────────┘ │
│ │
└──────────────────────────────────────────────────────────────────┘
三、数据库设计
3.1 Projects(项目表)
CREATE TABLE Projects (
Id SERIAL PRIMARY KEY,
ProjectId VARCHAR(32) UNIQUE NOT NULL, -- 前端展示的项目ID
ProjectKey VARCHAR(64) NOT NULL, -- 项目密钥(HMAC签名用)
ProjectSecret VARCHAR(64) NOT NULL, -- 项目密钥(服务端验证用)
Name VARCHAR(100) NOT NULL,
Description TEXT,
IconUrl VARCHAR(500), -- 项目图标
MaxDevices INT DEFAULT 1, -- 每卡密最大设备数
AutoUpdate BOOLEAN DEFAULT TRUE, -- 是否支持自动更新
IsEnabled BOOLEAN DEFAULT TRUE,
CreatedBy INT, -- 创建人ID
CreatedAt TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
UpdatedAt TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- 项目卡密定价表
CREATE TABLE ProjectPricing (
Id SERIAL PRIMARY KEY,
ProjectId VARCHAR(32) REFERENCES Projects(ProjectId) ON DELETE CASCADE,
CardType VARCHAR(20) NOT NULL, -- day/week/month/year/lifetime
DurationDays INT NOT NULL,
OriginalPrice DECIMAL(10,2) NOT NULL, -- 原价
IsEnabled BOOLEAN DEFAULT TRUE,
CreatedAt TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
UpdatedAt TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
UNIQUE(ProjectId, CardType, DurationDays)
);
-- 软件版本表
CREATE TABLE SoftwareVersions (
Id SERIAL PRIMARY KEY,
ProjectId VARCHAR(32) REFERENCES Projects(ProjectId) ON DELETE CASCADE,
Version VARCHAR(20) NOT NULL,
FileUrl VARCHAR(500) NOT NULL, -- 加密后的文件地址
FileSize BIGINT, -- 文件大小(字节)
FileHash VARCHAR(64), -- SHA256哈希
EncryptionKey VARCHAR(256), -- 加密密钥(RSA加密后存储)
Changelog TEXT, -- 更新日志
IsForceUpdate BOOLEAN DEFAULT FALSE, -- 是否强制更新
IsStable BOOLEAN DEFAULT TRUE, -- 是否稳定版本
PublishedAt TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
CreatedBy INT,
CreatedAt TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
UNIQUE(ProjectId, Version)
);
3.2 CardKeys(卡密表)
CREATE TABLE CardKeys (
Id SERIAL PRIMARY KEY,
ProjectId VARCHAR(32) REFERENCES Projects(ProjectId) ON DELETE SET NULL,
KeyCode VARCHAR(32) UNIQUE NOT NULL, -- 卡密(格式:XXXX-XXXX-XXXX-XXXX)
CardType VARCHAR(20) NOT NULL, -- day/week/month/year/lifetime
DurationDays INT NOT NULL,
ExpireTime TIMESTAMP, -- 过期时间
MaxDevices INT DEFAULT 1, -- 可单独覆盖项目配置
MachineCode VARCHAR(64), -- 首次激活后绑定的机器码
Status VARCHAR(20) DEFAULT 'unused', -- unused/active/expired/banned
ActivateTime TIMESTAMP,
LastUsedAt TIMESTAMP, -- 最后使用时间
UsedDuration BIGINT DEFAULT 0, -- 已使用时长(秒)
GeneratedBy INT REFERENCES Admins(Id), -- 生成人
AgentId INT REFERENCES Agents(Id), -- 所属代理商(可空)
SoldPrice DECIMAL(10,2), -- 实际售价(代理商生成时记录)
Note VARCHAR(200), -- 备注
BatchId VARCHAR(36), -- 批次ID(用于批量管理)
Version INT DEFAULT 1, -- 乐观锁版本号
CreatedAt TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
DeletedAt TIMESTAMP -- 软删除时间(NULL=未删除)
);
-- 基础索引
CREATE INDEX idx_card_keys_project ON CardKeys(ProjectId);
CREATE INDEX idx_card_keys_code ON CardKeys(KeyCode);
CREATE INDEX idx_card_keys_status ON CardKeys(Status);
-- 复合索引优化查询
CREATE INDEX idx_card_keys_project_status_created ON CardKeys(ProjectId, Status, CreatedAt DESC) WHERE DeletedAt IS NULL;
CREATE INDEX idx_card_keys_expire ON CardKeys(ExpireTime) WHERE Status = 'active' AND DeletedAt IS NULL;
CREATE INDEX idx_card_keys_agent ON CardKeys(AgentId) WHERE DeletedAt IS NULL;
CREATE INDEX idx_card_keys_batch ON CardKeys(BatchId);
3.3 Devices(设备表)
CREATE TABLE Devices (
Id SERIAL PRIMARY KEY,
CardKeyId INT REFERENCES CardKeys(Id) ON DELETE CASCADE,
DeviceId VARCHAR(64) NOT NULL, -- 机器码(硬件指纹)
DeviceName VARCHAR(100), -- 设备名称
OsInfo VARCHAR(100), -- 操作系统信息
LastHeartbeat TIMESTAMP, -- 最后心跳时间
IpAddress VARCHAR(45), -- 最后登录IP
Location VARCHAR(100), -- IP归属地(省市)
IsActive BOOLEAN DEFAULT TRUE,
FirstLoginAt TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
DeletedAt TIMESTAMP -- 软删除时间
);
CREATE INDEX idx_devices_card_key ON Devices(CardKeyId);
CREATE INDEX idx_devices_device_id ON Devices(DeviceId);
CREATE INDEX idx_devices_heartbeat ON Devices(LastHeartbeat) WHERE IsActive = true AND DeletedAt IS NULL;
3.4 AccessLogs(访问日志表)
CREATE TABLE AccessLogs (
Id SERIAL PRIMARY KEY,
ProjectId VARCHAR(32),
CardKeyId INT,
DeviceId VARCHAR(64),
Action VARCHAR(50), -- login/verify/download/launch/heartbeat
IpAddress INET,
UserAgent TEXT,
ResponseCode INT, -- 响应码(200/403等)
ResponseTime INT, -- 响应时间(毫秒)
CreatedAt TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX idx_logs_project ON AccessLogs(ProjectId);
CREATE INDEX idx_logs_created ON AccessLogs(CreatedAt);
CREATE INDEX idx_logs_action_created ON AccessLogs(Action, CreatedAt DESC);
CREATE INDEX idx_logs_card_key ON AccessLogs(CardKeyId);
-- 日志表建议按月分区(数据量大时)
-- CREATE TABLE AccessLogs_202501 PARTITION OF AccessLogs FOR VALUES FROM ('2025-01-01') TO ('2025-02-01');
3.5 Statistics(统计表)
CREATE TABLE Statistics (
Id SERIAL PRIMARY KEY,
ProjectId VARCHAR(32),
Date DATE,
ActiveUsers INT DEFAULT 0,
NewUsers INT DEFAULT 0,
TotalDownloads INT DEFAULT 0,
TotalDuration BIGINT DEFAULT 0, -- 使用时长(秒)
Revenue DECIMAL(10,2) DEFAULT 0, -- 收入
UNIQUE(ProjectId, Date)
);
3.6 Admins(管理员表)
CREATE TABLE Admins (
Id SERIAL PRIMARY KEY,
Username VARCHAR(50) UNIQUE NOT NULL,
PasswordHash VARCHAR(255) NOT NULL, -- bcrypt
Email VARCHAR(100),
Role VARCHAR(20) DEFAULT 'admin', -- super_admin/admin
Permissions TEXT, -- JSON 数组,权限列表
Status VARCHAR(20) DEFAULT 'active', -- active/disabled
LastLoginAt TIMESTAMP,
LastLoginIp VARCHAR(45),
CreatedAt TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
UpdatedAt TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
3.7 Agents(代理商表)
CREATE TABLE Agents (
Id SERIAL PRIMARY KEY,
AdminId INT REFERENCES Admins(Id) ON DELETE SET NULL,
AgentCode VARCHAR(20) UNIQUE NOT NULL, -- 代理商代码
CompanyName VARCHAR(100),
ContactPerson VARCHAR(50),
ContactPhone VARCHAR(20),
ContactEmail VARCHAR(100),
PasswordHash VARCHAR(255) NOT NULL, -- 代理商登录密码
Balance DECIMAL(10,2) DEFAULT 0, -- 额度余额
Discount DECIMAL(5,2) DEFAULT 100.00, -- 折扣比例(100=原价)
CreditLimit DECIMAL(10,2) DEFAULT 0, -- 信用额度(负数=允许透支)
MaxProjects INT DEFAULT 0, -- 0=全部,否则限制可见项目数
AllowedProjects TEXT, -- JSON 数组,可见的项目ID列表
Status VARCHAR(20) DEFAULT 'active', -- active/disabled
LastLoginAt TIMESTAMP,
CreatedAt TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
UpdatedAt TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
3.8 AgentTransactions(代理商额度流水)
CREATE TABLE AgentTransactions (
Id SERIAL PRIMARY KEY,
AgentId INT REFERENCES Agents(Id) ON DELETE CASCADE,
Type VARCHAR(20) NOT NULL, -- recharge/consume/refund
Amount DECIMAL(10,2) NOT NULL, -- 正数=增加,负数=扣减
BalanceBefore DECIMAL(10,2) NOT NULL, -- 操作前余额
BalanceAfter DECIMAL(10,2) NOT NULL, -- 操作后余额
CardKeyId INT REFERENCES CardKeys(Id) ON DELETE SET NULL,
Remark VARCHAR(200),
CreatedBy INT REFERENCES Admins(Id) ON DELETE SET NULL,
CreatedAt TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
3.9 CardKeyLogs(卡密操作日志)
CREATE TABLE CardKeyLogs (
Id SERIAL PRIMARY KEY,
CardKeyId INT REFERENCES CardKeys(Id) ON DELETE CASCADE,
Action VARCHAR(50) NOT NULL, -- create/activate/ban/unban/extend/reset_device
OperatorId INT, -- 操作人(可为空,系统操作时)
OperatorType VARCHAR(20), -- admin/agent/system
Details TEXT, -- JSON 格式详情
IpAddress VARCHAR(45),
CreatedAt TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
3.10 SystemConfigs(系统配置表 - 配置驱动核心)
CREATE TABLE SystemConfigs (
Id SERIAL PRIMARY KEY,
ConfigKey VARCHAR(50) UNIQUE NOT NULL,
ConfigValue TEXT,
ValueType VARCHAR(20) DEFAULT 'string', -- string/bool/number/json
Category VARCHAR(50) DEFAULT 'general', -- 配置分类
DisplayName VARCHAR(100), -- 前端显示名称
Description VARCHAR(500), -- 说明
Options TEXT, -- JSON,可选值(枚举类型用)
IsPublic BOOLEAN DEFAULT FALSE, -- 是否可被客户端读取
UpdatedAt TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
配置分类 + 前端开关控制:
| 分类 | 配置键 | 默认值 | 前端控制类型 | 说明 |
|---|---|---|---|---|
| 功能开关 | ||||
| 功能 | feature.heartbeat |
true | 开关 | 是否启用心跳验证 |
| 功能 | feature.device_bind |
true | 开关 | 是否启用设备绑定 |
| 功能 | feature.auto_update |
true | 开关 | 是否启用自动更新 |
| 功能 | feature.force_update |
false | 开关 | 是否强制更新 |
| 功能 | feature.agent_system |
true | 开关 | 是否启用代理商系统 |
| 功能 | feature.card_renewal |
true | 开关 | 是否启用卡密续费 |
| 功能 | feature.trial_mode |
false | 开关 | 是否启用试用模式 |
| 验证规则 | ||||
| 验证 | auth.max_devices |
1 | 数字 | 单卡密最大设备数 |
| 验证 | auth.allow_multi_device |
false | 开关 | 是否允许多设备同时在线 |
| 验证 | auth.need_activate |
true | 开关 | 卡密是否需要激活 |
| 验证 | auth.expire_type |
activate |
选择 | 过期类型:activate/fix |
| 心跳配置 | ||||
| 心跳 | heartbeat.enabled |
true | 开关 | 是否启用心跳 |
| 心跳 | heartbeat.interval |
60 | 数字 | 心跳间隔(秒) |
| 心跳 | heartbeat.timeout |
180 | 数字 | 心跳超时(秒) |
| 心跳 | heartbeat.offline_action |
exit |
选择 | 掉线后行为:exit/warning/none |
| 限流配置 | ||||
| 限流 | ratelimit.enabled |
true | 开关 | 是否启用限流 |
| 限流 | ratelimit.ip_per_minute |
100 | 数字 | IP每分钟请求限制 |
| 限流 | ratelimit.device_per_minute |
50 | 数字 | 设备每分钟请求限制 |
| 限流 | ratelimit.block_duration |
5 | 数字 | 封禁时长(分钟) |
| 风控配置 | ||||
| 风控 | risk.enabled |
true | 开关 | 是否启用风控 |
| 风控 | risk.check_location |
true | 开关 | 异地登录检测 |
| 风控 | risk.check_device_change |
true | 开关 | 设备变更检测 |
| 风控 | risk.auto_ban |
false | 开关 | 自动封禁异常 |
| 客户端显示 | ||||
| 显示 | client.notice_title |
"" | 文本 | 公告标题 |
| 显示 | client.notice_content |
"" | 文本域 | 公告内容 |
| 显示 | client.contact_url |
"" | 文本 | 联系方式URL |
| 显示 | client.help_url |
"" | 文本 | 帮助文档URL |
| 显示 | client.show_balance |
false | 开关 | 是否在客户端显示余额 |
| 其他 | ||||
| 系统 | system.site_name |
"软件授权系统" | 文本 | 系统名称 |
| 系统 | system.logo_url |
"" | 文本 | Logo地址 |
| 系统 | system.enable_register |
false | 开关 | 是否开放用户注册 |
| 系统 | log.retention_days |
90 | 数字 | 日志保留天数 |
### 3.11 并发安全与事务处理
#### 3.11.1 代理商扣款(行级锁)
```sql
-- 代理商生成卡密时扣款(必须使用事务+行级锁)
BEGIN;
-- 1. 获取行级锁,防止并发扣款
SELECT Balance FROM Agents WHERE Id = :agentId FOR UPDATE;
-- 2. 检查余额是否充足
-- (在应用层判断,不足则 ROLLBACK)
-- 3. 扣款
UPDATE Agents
SET Balance = Balance - :amount, UpdatedAt = NOW()
WHERE Id = :agentId AND Balance >= :amount;
-- 4. 记录流水
INSERT INTO AgentTransactions (AgentId, Type, Amount, BalanceBefore, BalanceAfter, Remark, CreatedAt)
VALUES (:agentId, 'consume', -:amount, :balanceBefore, :balanceAfter, :remark, NOW());
-- 5. 生成卡密
INSERT INTO CardKeys (...) VALUES (...);
COMMIT;
3.11.2 卡密激活(乐观锁)
-- 使用乐观锁防止并发激活
UPDATE CardKeys
SET Status = 'active',
ActivateTime = NOW(),
MachineCode = :machineCode,
Version = Version + 1
WHERE KeyCode = :keyCode
AND Status = 'unused'
AND Version = :expectedVersion
AND DeletedAt IS NULL;
-- 检查受影响行数,0表示已被其他请求激活或版本冲突
-- 应用层需要处理这种情况
3.11.3 设备绑定(唯一约束+UPSERT)
-- 设备绑定使用 UPSERT 避免并发插入冲突
INSERT INTO Devices (CardKeyId, DeviceId, DeviceName, OsInfo, IpAddress, LastHeartbeat, FirstLoginAt)
VALUES (:cardKeyId, :deviceId, :deviceName, :osInfo, :ipAddress, NOW(), NOW())
ON CONFLICT (CardKeyId, DeviceId)
DO UPDATE SET
LastHeartbeat = NOW(),
IpAddress = :ipAddress,
DeviceName = COALESCE(:deviceName, Devices.DeviceName);
3.11.4 心跳更新(批量优化)
-- 批量更新心跳时间(减少数据库压力)
UPDATE Devices
SET LastHeartbeat = NOW()
WHERE Id = ANY(:deviceIds) AND DeletedAt IS NULL;
3.11.5 软删除查询模式
-- 所有查询默认过滤已删除数据
SELECT * FROM CardKeys WHERE ProjectId = :projectId AND DeletedAt IS NULL;
-- 软删除操作
UPDATE CardKeys SET DeletedAt = NOW() WHERE Id = :id;
-- 恢复删除
UPDATE CardKeys SET DeletedAt = NULL WHERE Id = :id;
-- 清理30天前的已删除数据(定时任务)
DELETE FROM CardKeys WHERE DeletedAt < NOW() - INTERVAL '30 days';
3.12 幂等性支持表
-- 幂等性键存储(防止重复请求)
CREATE TABLE IdempotencyKeys (
Id SERIAL PRIMARY KEY,
IdempotencyKey VARCHAR(64) UNIQUE NOT NULL, -- 幂等键(UUID)
RequestPath VARCHAR(200) NOT NULL, -- 请求路径
RequestHash VARCHAR(64), -- 请求体哈希
ResponseCode INT, -- 响应状态码
ResponseBody TEXT, -- 响应内容(JSON)
CreatedAt TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
ExpiresAt TIMESTAMP NOT NULL -- 过期时间
);
CREATE INDEX idx_idempotency_key ON IdempotencyKeys(IdempotencyKey);
CREATE INDEX idx_idempotency_expires ON IdempotencyKeys(ExpiresAt);
-- 定时清理过期的幂等键
DELETE FROM IdempotencyKeys WHERE ExpiresAt < NOW();
四、API 接口设计
4.0 统一响应格式与错误码
4.0.1 统一响应格式
{
"code": 200,
"message": "success",
"data": {},
"timestamp": 1735000000
}
4.0.2 错误码规范
| 错误码 | 错误消息 | HTTP状态码 | 说明 |
|---|---|---|---|
| 成功类 | |||
| 200 | success | 200 | 请求成功 |
| 客户端错误(4xxx) | |||
| 400 | bad_request | 400 | 请求参数错误 |
| 401 | unauthorized | 401 | 未授权,需要登录 |
| 403 | forbidden | 403 | 无权限访问 |
| 404 | not_found | 404 | 资源不存在 |
| 1001 | card_invalid | 400 | 卡密格式错误或不存在 |
| 1002 | card_expired | 403 | 卡密已过期 |
| 1003 | card_banned | 403 | 卡密已被封禁 |
| 1004 | card_already_used | 400 | 卡密已被使用 |
| 1005 | device_limit_exceeded | 403 | 设备数量超过限制 |
| 1006 | device_not_found | 404 | 设备未找到 |
| 1007 | signature_invalid | 403 | 签名验证失败 |
| 1008 | timestamp_expired | 400 | 时间戳过期 |
| 1009 | rate_limit_exceeded | 429 | 请求过于频繁 |
| 1010 | invalid_version | 400 | 客户端版本过低 |
| 1011 | project_disabled | 403 | 项目已被禁用 |
| 1012 | force_update_required | 426 | 需要强制更新 |
| 服务端错误(5xxx) | |||
| 500 | internal_error | 500 | 服务器内部错误 |
| 5001 | database_error | 500 | 数据库错误 |
| 5002 | cache_error | 500 | 缓存服务错误 |
| 5003 | storage_error | 500 | 文件存储错误 |
| 5004 | encryption_error | 500 | 加密服务错误 |
4.0.3 幂等性设计
对于可能产生副作用的接口(如生成卡密、扣款),支持幂等性请求:
请求头:
X-Idempotency-Key: <UUID> # 唯一请求标识,24小时内有效
示例:
POST /api/admin/cards/generate
Headers:
Authorization: Bearer <token>
X-Idempotency-Key: 550e8400-e29b-41d4-a716-446655440000
重复请求同一 Idempotency-Key 将返回首次请求的响应结果。
4.0.4 分页参数规范
请求参数:
page: 页码,从1开始(默认1)
pageSize: 每页条数(默认20,最大100)
响应格式:
{
"code": 200,
"data": {
"items": [...],
"pagination": {
"page": 1,
"pageSize": 20,
"total": 1000,
"totalPages": 50
}
}
}
4.0.5 健康检查接口
# 存活检查(用于K8s/Docker健康检查)
GET /health/live
Response: { "status": "ok" }
# 就绪检查(检查数据库、Redis连接)
GET /health/ready
Response: {
"status": "ok",
"checks": {
"database": "ok",
"redis": "ok"
}
}
# 失败时返回 503:
Response: {
"status": "error",
"checks": {
"database": "ok",
"redis": "error: connection refused"
}
}
4.1 客户端/SDK 接口
4.1.1 卡密验证
POST /api/auth/verify
Request: {
"projectId": "PROJ_001",
"keyCode": "A3D7-K2P9-M8N1",
"deviceId": "SHA256硬件指纹",
"clientVersion": "1.0.0",
"timestamp": 1735000000,
"signature": "HMAC-SHA256签名"
}
Response: {
"code": 200,
"message": "success",
"data": {
"valid": true,
"expireTime": "2025-12-31T23:59:59Z",
"remainingDays": 30,
"downloadUrl": "https://cdn.example.com/software_v1.0.bin",
"fileHash": "sha256:xxxxx",
"version": "1.0.0",
"heartbeatInterval": 60,
"accessToken": "jwt_token"
}
}
4.1.2 心跳验证
POST /api/auth/heartbeat
Request: {
"accessToken": "jwt_token",
"deviceId": "xxx",
"timestamp": 1735000000,
"signature": "xxx"
}
Response: {
"code": 200,
"data": {
"valid": true,
"remainingDays": 30,
"serverTime": 1735000000
}
}
错误响应:
{
"code": 403,
"message": "card_expired",
"data": {
"reason": "卡密已过期,请续费",
"kick": true
}
}
4.1.3 版本检查(云更新)
POST /api/software/check-update
Request: {
"projectId": "PROJ_001",
"currentVersion": "1.0.0",
"platform": "windows"
}
Response: {
"code": 200,
"data": {
"hasUpdate": true,
"latestVersion": "1.2.0",
"forceUpdate": false,
"downloadUrl": "https://cdn.example.com/software_v1.2.0.bin",
"fileSize": 52428800,
"fileHash": "sha256:xxxxx",
"changelog": "- 修复若干bug\n- 新增xxx功能"
}
}
4.1.4 软件下载
GET /api/software/download?version=1.2.0&token=jwt_token
Response: 加密的二进制文件
Headers:
X-File-Hash: sha256校验值
X-File-Size: 文件大小
X-Encryption-Method: AES-256-GCM
X-Encryption-Key: RSA加密的AES密钥(Base64)
X-Encryption-Nonce: 随机nonce
4.2 管理后台接口
4.2.1 认证接口
POST /api/admin/login
Request: {
"username": "admin",
"password": "password",
"captcha": "验证码"
}
Response: {
"code": 200,
"data": {
"token": "jwt_token",
"user": {
"id": 1,
"username": "admin",
"role": "super_admin",
"permissions": ["*"]
}
}
}
POST /api/admin/logout
GET /api/admin/profile
PUT /api/admin/profile
POST /api/admin/change-password
4.2.2 项目管理
POST /api/admin/projects
Request: {
"name": "我的软件",
"description": "软件描述",
"maxDevices": 1,
"autoUpdate": true
}
Response: {
"code": 200,
"data": {
"id": 1,
"projectId": "PROJ_001", -- 自动生成
"projectKey": "abcd1234...", -- 自动生成
"projectSecret": "secret123..." -- 自动生成(仅显示一次)
}
}
GET /api/admin/projects
GET /api/admin/projects/{id}
PUT /api/admin/projects/{id}
DELETE /api/admin/projects/{id}
GET /api/admin/projects/{id}/stats
GET /api/admin/projects/{id}/docs
PUT /api/admin/projects/{id}/docs
# 价格管理
GET /api/admin/projects/{id}/pricing
Response: {
"code": 200,
"data": [
{
"id": 1,
"cardType": "day",
"durationDays": 1,
"originalPrice": 1.00,
"isEnabled": true
},
{
"id": 2,
"cardType": "week",
"durationDays": 7,
"originalPrice": 5.00,
"isEnabled": true
},
{
"id": 3,
"cardType": "month",
"durationDays": 30,
"originalPrice": 15.00,
"isEnabled": true
},
{
"id": 4,
"cardType": "year",
"durationDays": 365,
"originalPrice": 100.00,
"isEnabled": true
},
{
"id": 5,
"cardType": "lifetime",
"durationDays": 36500,
"originalPrice": 299.00,
"isEnabled": true
}
]
}
POST /api/admin/projects/{id}/pricing
Request: {
"cardType": "month",
"durationDays": 30,
"originalPrice": 15.00
}
PUT /api/admin/projects/{id}/pricing/{priceId}
Request: {
"originalPrice": 20.00,
"isEnabled": true
}
DELETE /api/admin/projects/{id}/pricing/{priceId}
# 版本管理
GET /api/admin/projects/{id}/versions
Response: {
"code": 200,
"data": [
{
"id": 1,
"version": "1.2.0",
"fileSize": 52428800,
"isStable": true,
"isForceUpdate": false,
"publishedAt": "2025-12-20T10:00:00Z"
},
{
"id": 2,
"version": "1.1.0",
"fileSize": 51200000,
"isStable": true,
"isForceUpdate": false,
"publishedAt": "2025-12-10T10:00:00Z"
}
]
}
POST /api/admin/projects/{id}/versions
Request: {
"version": "1.3.0",
"file": "上传的文件",
"changelog": "更新内容",
"isForceUpdate": false,
"isStable": true
}
Response: {
"code": 200,
"data": {
"versionId": 3,
"version": "1.3.0",
"fileUrl": "https://cdn.example.com/software_v1.3.0.bin",
"fileHash": "sha256:xxxxx",
"encryptionKey": "RSA加密的密钥(仅首次返回)"
}
}
PUT /api/admin/projects/{id}/versions/{versionId}
Request: {
"isForceUpdate": true,
"isStable": false,
"changelog": "更新内容"
}
DELETE /api/admin/projects/{id}/versions/{versionId}
4.2.3 卡密管理
# 批量生成卡密
POST /api/admin/cards/generate
Request: {
"projectId": "PROJ_001",
"cardType": "month", -- day/week/month/year/lifetime
"durationDays": 30,
"quantity": 100,
"note": "批次备注"
}
Response: {
"code": 200,
"data": {
"batchId": "batch_xxx",
"keys": [
"A3D7-K2P9-M8N1-Q4W6",
"B4E8-L3X0-N9O2-P5Y7",
...
],
"count": 100
}
}
# 卡密列表
GET /api/admin/cards?projectId=PROJ_001&status=active&page=1&pageSize=20
Response: {
"code": 200,
"data": {
"total": 1000,
"items": [
{
"id": 1,
"keyCode": "A3D7-K2P9-M8N1-Q4W6",
"cardType": "month",
"status": "active",
"activateTime": "2025-12-01T10:00:00Z",
"expireTime": "2025-12-31T23:59:59Z",
"machineCode": "abc123...",
"note": "备注",
"createdAt": "2025-12-01T00:00:00Z"
},
...
]
}
}
# 卡密详情
GET /api/admin/cards/{id}
Response: {
"code": 200,
"data": {
"id": 1,
"keyCode": "A3D7-K2P9-M8N1-Q4W6",
"project": {
"id": 1,
"name": "我的软件"
},
"cardType": "month",
"status": "active",
"activateTime": "2025-12-01T10:00:00Z",
"expireTime": "2025-12-31T23:59:59Z",
"machineCode": "abc123...",
"devices": [
{
"deviceId": "abc123...",
"deviceName": "DESKTOP-ABC",
"ipAddress": "1.2.3.4",
"lastHeartbeat": "2025-12-28T10:30:00Z",
"isActive": true
}
],
"logs": [...],
"createdAt": "2025-12-01T00:00:00Z"
}
}
# 卡密操作
PUT /api/admin/cards/{id} -- 更新备注等
POST /api/admin/cards/{id}/ban -- 封禁
Request: { "reason": "违规使用" }
POST /api/admin/cards/{id}/unban -- 解封
POST /api/admin/cards/{id}/extend -- 延长有效期
Request: { "days": 30 }
POST /api/admin/cards/{id}/reset-device -- 重置设备绑定
DELETE /api/admin/cards/{id} -- 删除卡密
# 批量操作
POST /api/admin/cards/ban-batch -- 批量封禁
Request: { "ids": [1, 2, 3], "reason": "批量封禁" }
POST /api/admin/cards/unban-batch -- 批量解封
DELETE /api/admin/cards/batch -- 批量删除
# 导出
GET /api/admin/cards/export?format=excel -- 导出卡密
# 批量导入(Excel/CSV)
POST /api/admin/cards/import
Content-Type: multipart/form-data
Request: {
"file": <Excel/CSV文件>,
"projectId": "PROJ_001"
}
Response: {
"code": 200,
"data": {
"total": 100,
"success": 95,
"failed": 5,
"errors": [
{ "row": 3, "keyCode": "XXX", "reason": "格式错误" },
{ "row": 7, "keyCode": "YYY", "reason": "已存在" }
]
}
}
# 卡密日志
GET /api/admin/cards/{id}/logs -- 卡密操作日志
4.2.4 代理商管理
# 代理商登录(独立登录入口)
POST /api/agent/login
Request: {
"agentCode": "AGENT001",
"password": "password"
}
Response: {
"code": 200,
"data": {
"token": "jwt_token",
"agent": {
"id": 1,
"agentCode": "AGENT001",
"companyName": "某某科技公司",
"balance": 5000.00,
"discount": 80.00
},
"allowedProjects": [
{ "projectId": "PROJ_001", "projectName": "项目A" },
{ "projectId": "PROJ_002", "projectName": "项目B" }
]
}
}
POST /api/agent/logout
GET /api/agent/profile
PUT /api/agent/profile
POST /api/agent/change-password
# 创建代理商(超管操作)
POST /api/admin/agents
Request: {
"adminId": 5, -- 关联的管理员ID
"agentCode": "AGENT001",
"companyName": "某某科技公司",
"contactPerson": "张三",
"contactPhone": "13800138000",
"contactEmail": "contact@example.com",
"initialBalance": 1000.00, -- 初始额度
"discount": 80.00, -- 折扣(80=8折)
"creditLimit": 500.00, -- 信用额度
"allowedProjects": ["PROJ_001", "PROJ_002"]
}
# 代理商列表
GET /api/admin/agents?status=active&page=1
Response: {
"code": 200,
"data": {
"total": 10,
"items": [
{
"id": 1,
"agentCode": "AGENT001",
"companyName": "某某科技公司",
"contactPerson": "张三",
"contactPhone": "13800138000",
"balance": 500.00,
"discount": 80.00,
"status": "active",
"createdAt": "2025-12-01T00:00:00Z"
}
]
}
}
# 代理商详情
GET /api/admin/agents/{id}
Response: {
"code": 200,
"data": {
"id": 1,
"agentCode": "AGENT001",
"companyName": "某某科技公司",
"balance": 500.00,
"discount": 80.00,
"stats": {
"totalCards": 1000,
"activeCards": 800,
"totalRevenue": 50000.00
},
"transactions": [...]
}
}
# 额度管理
POST /api/admin/agents/{id}/recharge -- 充值
Request: { "amount": 1000.00, "remark": "充值" }
POST /api/admin/agents/{id}/deduct -- 扣款
Request: { "amount": 100.00, "remark": "扣款" }
GET /api/admin/agents/{id}/transactions -- 额度流水
# 代理商操作
PUT /api/admin/agents/{id}
POST /api/admin/agents/{id}/disable
POST /api/admin/agents/{id}/enable
DELETE /api/admin/agents/{id}
4.2.5 设备管理
GET /api/admin/devices?projectId=PROJ_001&isActive=true
DELETE /api/admin/devices/{id} -- 解绑设备
POST /api/admin/devices/{id}/kick -- 强制下线
4.2.6 统计报表
GET /api/admin/stats/dashboard
Response: {
"code": 200,
"data": {
"overview": {
"totalProjects": 10,
"totalCards": 10000,
"activeCards": 6000,
"activeDevices": 4500,
"todayRevenue": 5000.00,
"monthRevenue": 150000.00
},
"trend": {
"dates": ["2025-12-01", "2025-12-02", ...],
"activeUsers": [100, 120, ...],
"newUsers": [10, 15, ...],
"revenue": [500, 600, ...]
},
"projectDistribution": [
{ "project": "PROJ_001", "count": 3000 },
{ "project": "PROJ_002", "count": 2000 }
]
}
}
GET /api/admin/stats/projects
GET /api/admin/stats/agents
GET /api/admin/stats/logs
GET /api/admin/stats/export
4.2.7 日志审计
GET /api/admin/logs?action=login&page=1
GET /api/admin/logs/{id}
4.2.8 系统设置
GET /api/admin/settings
PUT /api/admin/settings
GET /api/admin/admins
POST /api/admin/admins
PUT /api/admin/admins/{id}
DELETE /api/admin/admins/{id}
五、安全方案(防破解)
⚠️ 重要认知:客户端防护的局限性
任何运行在用户机器上的代码,理论上都可以被破解。客户端防护(混淆、加壳、反调试)只能提高破解成本,不能阻止所有破解行为。
核心防线是服务端验证:
- 关键功能必须在服务端完成验证后才能使用
- 心跳机制确保持续在线验证
- 风控引擎检测异常行为
合理预期:
- ✅ 防止大规模盗版分发
- ✅ 让破解成本高于软件价格
- ✅ 通过服务端及时封禁破解版
- ❌ 无法阻止个别技术高手的破解
5.1 多层防护体系
┌─────────────────────────────────────────────────────────────┐
│ 客户端防护层 │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ 代码混淆 │ │ 加壳保护 │ │ 反调试检测 │ │
│ │ (ConfuserEx) │ │ (Themida/ │ │ (Debugger/ │ │
│ │ │ │ VMP可选) │ │ VM检测) │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
│ ┌──────────────┐ ┌──────────────┐ │
│ │ 完整性校验 │ │ 硬件绑定 │ │
│ │ (文件Hash) │ │ (机器指纹) │ │
│ └──────────────┘ └──────────────┘ │
└─────────────────────────────────────────────────────────────┘
│
┌─────────────────────────────────────────────────────────────┐
│ 通信防护层 │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ HTTPS │ │ 请求签名 │ │ 时间戳验证 │ │
│ │ TLS 1.3 │ │ HMAC-SHA256 │ │ 防重放攻击 │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
└─────────────────────────────────────────────────────────────┘
│
┌─────────────────────────────────────────────────────────────┐
│ 服务端防护层 │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ 机器码绑定 │ │ 频率限制 │ │ 风控引擎 │ │
│ │ (硬件指纹) │ │ (Rate Limit)│ │ (异常检测) │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
└─────────────────────────────────────────────────────────────┘
5.2 客户端防护代码示例
1) 机器码生成(硬件指纹)
public static class MachineCode
{
public static string GetDeviceId()
{
var factors = new List<string>
{
GetCpuId(), // CPU 处理器信息
GetBoardSerial(), // 主板序列号
GetDiskSerial(), // 硬盘序列号
GetMacAddress() // MAC 地址(取第一个非虚拟网卡)
};
var combined = string.Join("|", factors.Where(x => !string.IsNullOrEmpty(x)));
using var sha = SHA256.Create();
var hash = sha.ComputeHash(Encoding.UTF8.GetBytes(combined));
return Convert.ToHexString(hash).ToLower();
}
private static string GetCpuId()
{
var cpuInfo = string.Empty;
try
{
using var searcher = new ManagementObjectSearcher("SELECT ProcessorId FROM Win32_Processor");
foreach (var obj in searcher.Get())
{
cpuInfo = obj["ProcessorId"]?.ToString();
break;
}
}
catch { /* 忽略错误 */ }
return cpuInfo ?? "unknown_cpu";
}
// ... 其他硬件信息获取方法
}
2) 请求签名
public static class RequestSigner
{
public static string Sign(string projectId, string deviceId, long timestamp, string secret)
{
var payload = $"{projectId}|{deviceId}|{timestamp}";
using var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(secret));
var hash = hmac.ComputeHash(Encoding.UTF8.GetBytes(payload));
return Convert.ToHexString(hash).ToLower();
}
public static bool Verify(string projectId, string deviceId, long timestamp,
string signature, string secret)
{
// 时间戳验证(5分钟有效期)
var now = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
if (Math.Abs(now - timestamp) > 300)
return false;
var expected = Sign(projectId, deviceId, timestamp, secret);
return signature.Equals(expected, StringComparison.OrdinalIgnoreCase);
}
}
3) 防篡改检测
public static class IntegrityChecker
{
private static readonly string ExpectedHash = "嵌入的正确Hash值";
public static bool Verify()
{
// 1. 文件完整性校验
var currentHash = ComputeFileHash(Assembly.GetExecutingAssembly().Location);
if (!currentHash.Equals(ExpectedHash, StringComparison.OrdinalIgnoreCase))
return false;
// 2. 调试器检测
if (IsDebuggerAttached())
return false;
// 3. 虚拟机检测(可选)
if (IsRunningInVM())
Environment.Exit(0);
return true;
}
private static bool IsDebuggerAttached()
{
return Debugger.IsAttached
|| CheckRemoteDebuggerPresent()
|| IsDebuggerPresentAPI();
}
private static bool IsRunningInVM()
{
// 检测常见虚拟机特征
var vmKeys = new[] { "VBOX", "VMWARE", "QEMU", "VIRTUAL" };
try
{
using var searcher = new ManagementObjectSearcher("SELECT * FROM Win32_ComputerSystem");
foreach (var obj in searcher.Get())
{
var manufacturer = obj["Manufacturer"]?.ToString()?.ToUpper() ?? "";
var model = obj["Model"]?.ToString()?.ToUpper() ?? "";
return vmKeys.Any(k => manufacturer.Contains(k) || model.Contains(k));
}
}
catch { }
return false;
}
}
4) 心跳机制
public class HeartbeatService : BackgroundService
{
private readonly HttpClient _httpClient;
private readonly string _accessToken;
private readonly string _deviceId;
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stopping.IsCancellationRequested)
{
try
{
var response = await SendHeartbeat(stoppingToken);
if (!response.Valid)
{
// 卡密失效,退出软件
MessageBox.Show("授权已失效,软件将退出");
Environment.Exit(0);
}
}
catch
{
// 网络错误,允许短暂失败
}
await Task.Delay(60000, stoppingToken); // 60秒心跳
}
}
private async Task<HeartbeatResponse> SendHeartbeat(CancellationToken ct)
{
var timestamp = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
var signature = RequestSigner.Sign(_projectId, _deviceId, timestamp, _secret);
var payload = new
{
accessToken = _accessToken,
deviceId = _deviceId,
timestamp,
signature
};
var response = await _httpClient.PostAsJsonAsync("/api/auth/heartbeat", payload, ct);
return await response.Content.ReadFromJsonAsync<HeartbeatResponse>(ct);
}
}
5.3 服务端风控规则
基础风控规则:
| 风控规则 | 触发条件 | 处理措施 |
|---|---|---|
| 异地登录 | 同一卡密1小时内IP跨省/跨运营商 | 要求重新验证,发送告警 |
| 频繁请求 | 单IP 1分钟 > 100次请求 | 临时封禁5分钟 |
| 机器码变更 | 已绑定卡密请求新机器码 | 拒绝并记录,需管理员解绑 |
| 多机登录 | 同一卡密同时 > 最大设备数 | 踢出最早的设备 |
| 心跳超时 | 连续3分钟无心跳 | 标记离线,下次拒绝 |
| 批量注册 | 同一设备24小时内尝试 > 10个卡密 | 拉黑设备ID |
高级风控规则(推荐):
| 风控规则 | 触发条件 | 处理措施 |
|---|---|---|
| 卡密爆破 | 同一IP 1小时内尝试 > 100个不同卡密 | 拉黑IP 24小时 |
| 自动化检测 | 请求间隔完全均匀(如每60.00秒) | 标记可疑,增加验证频率 |
| 设备指纹伪造 | 同一设备ID硬件参数变化 | 拒绝并告警 |
| 代理IP检测 | IP属于已知代理池/数据中心 | 增加验证难度(验证码) |
| 异常时段 | 凌晨2-6点批量操作 | 需要额外验证 |
| 卡密共享 | 同一卡密24小时内 > 5个不同IP登录 | 自动封禁,需申诉 |
风控规则实现示例:
public class RiskControl
{
private readonly IRedisCache _cache;
public async Task<RiskResult> CheckAsync(VerifyRequest request)
{
var rules = new List<Func<Task<RiskResult>>>
{
() => CheckRateLimit(request.IpAddress),
() => CheckDeviceLimit(request.KeyCode, request.DeviceId),
() => CheckLocationChange(request.KeyCode, request.IpAddress),
() => CheckBruteForce(request.IpAddress),
};
foreach (var rule in rules)
{
var result = await rule();
if (!result.Passed) return result;
}
return RiskResult.Pass();
}
private async Task<RiskResult> CheckRateLimit(string ip)
{
var key = $"ratelimit:ip:{ip}";
var count = await _cache.IncrAsync(key);
if (count == 1) await _cache.ExpireAsync(key, TimeSpan.FromMinutes(1));
if (count > 100)
return RiskResult.Block("rate_limit_exceeded", "请求过于频繁,请稍后重试");
return RiskResult.Pass();
}
}
5.4 已知攻击与应对
| 攻击手段 | 描述 | 应对措施 |
|---|---|---|
| 破解验证逻辑 | 修改跳过验证的 patch | 代码混淆 + 关键逻辑服务端验证 + 心跳机制 |
| 抓包重放 | 录制请求后重放 | 时间戳 + Nonce + 签名 |
| 注入 DLL | Hook 关键函数 | 完整性校验 + 反调试 |
| 虚拟机调试 | 在虚拟环境中分析 | 反虚拟机检测 |
| 中间人攻击 | 代理篡改响应 | 证书固定 + 响应签名验证 |
| 内存补丁 | 直接修改内存 | 关键数据多次读取 + 服务端校验 |
| 脱壳爆破 | 去除加壳保护 | 多层保护 + Native AOT 编译 |
5.5 软件加密分发详细流程(方案2+方案3结合)
┌─────────────────────────────────────────────────────────────────┐
│ 软件加密与分发完整流程 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 【服务端 - 软件上传与加密】 │
│ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ 1. 管理员上传原始软件包 │ │
│ │ POST /api/admin/projects/{id}/versions │ │
│ │ file: software.exe │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ 2. 服务端加密处理 │ │
│ │ │ │
│ │ a) 生成随机 AES-256-GCM 密钥 │ │
│ │ b) 用 AES 密钥加密软件文件 │ │
│ │ c) 用客户端 RSA 公钥加密 AES 密钥 │ │
│ │ d) 计算 SHA256 哈希 │ │
│ │ e) 上传加密文件到 CDN/对象存储 │ │
│ │ │ │
│ │ 存储记录: │ │
│ │ - SoftwareVersions 表: │ │
│ │ FileUrl = CDN地址 │ │
│ │ FileHash = SHA256值 │ │
│ │ EncryptionKey = RSA加密后的AES密钥 │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
│ 【客户端 - 验证与下载】 │
│ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ 3. 启动器卡密验证 │ │
│ │ POST /api/auth/verify │ │
│ │ │ │
│ │ 返回数据: │ │
│ │ { │ │
│ │ "downloadUrl": "https://cdn.../v1.2.0.bin", │ │
│ │ "fileHash": "sha256:xxxxx", │ │
│ │ "version": "1.2.0", │ │
│ │ "accessToken": "jwt_token" │ │
│ │ } │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ 4. 下载加密软件包 │ │
│ │ GET /api/software/download?token=xxx&version=1.2.0 │ │
│ │ │ │
│ │ 响应头: │ │
│ │ X-File-Hash: sha256校验值 │ │
│ │ X-File-Size: 文件大小 │ │
│ │ X-Encryption-Key: RSA加密的AES密钥(Base64) │ │
│ │ X-Encryption-Nonce: 随机 nonce │ │
│ │ │ │
│ │ Response: 二进制加密数据 │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ 5. 客户端解密 │ │
│ │ │ │
│ │ a) 用内置 RSA 私钥解密得到 AES 密钥 │ │
│ │ b) 用 AES 密钥解密软件数据 │ │
│ │ c) 验证 SHA256 哈希 │ │
│ │ d) 保存到临时文件或内存加载 │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ 6. 启动主程序 │ │
│ │ │ │
│ │ Process.Start(tempFile) │ │
│ │ 或 Assembly.Load(decryptedBytes) │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │ │
│ │ 7. 主程序自验证(SDK,双重保险) │ │
│ │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │ │
│ │ │ │
│ │ - 向服务器发送自验证请求 │ │
│ │ - 确认卡密仍有效 │ │
│ │ - 启动心跳线程(每 30-60 秒) │ │
│ │ - 心跳失败 → 自动退出 │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
5.6 服务端加密实现示例
public class SoftwareEncryptionService
{
private readonly IStorageService _storage;
private readonly RSA _clientRsa; // 客户端公钥
public async Task<SoftwareVersion> UploadAndEncryptAsync(
string projectId, IFormFile file, string version)
{
// 1. 生成随机 AES 密钥
var aesKey = RandomNumberGenerator.GetBytes(32);
var nonce = RandomNumberGenerator.GetBytes(12);
// 2. 读取并加密文件
using var input = file.OpenReadStream();
using var output = new MemoryStream();
using var aes = new AesGcm(aesKey);
var fileData = await.ReadAllBytesAsync(input);
var encryptedData = new byte[fileData.Length];
var tag = new byte[16];
aes.Encrypt(nonce, fileData, 0, fileData.Length,
encryptedData, tag);
// 3. 组合 nonce + tag + ciphertext
var finalData = new byte[nonce.Length + tag.Length + encryptedData.Length];
Buffer.BlockCopy(nonce, 0, finalData, 0, nonce.Length);
Buffer.BlockCopy(tag, 0, finalData, nonce.Length, tag.Length);
Buffer.BlockCopy(encryptedData, 0, finalData,
nonce.Length + tag.Length, encryptedData.Length);
// 4. 上传到存储
var filename = $"{projectId}_{version}.bin";
var url = await _storage.UploadAsync(filename, finalData);
// 5. 用客户端 RSA 公钥加密 AES 密钥
var encryptedKey = _clientRsa.Encrypt(aesKey,
RSAEncryptionPadding.OaepSHA256);
// 6. 计算哈希
var hash = SHA256.HashData(finalData);
// 7. 保存到数据库
return new SoftwareVersion
{
ProjectId = projectId,
Version = version,
FileUrl = url,
FileSize = fileData.Length,
FileHash = Convert.ToHexString(hash),
EncryptionKey = Convert.ToBase64String(encryptedKey),
PublishedAt = DateTime.UtcNow
};
}
}
5.7 客户端解密实现示例
public class SoftwareDownloader
{
private readonly RSA _privateKey; // 内置的客户端私钥
public async Task<string> DownloadAndDecryptAsync(
string downloadUrl, string encryptedKeyB64,
string expectedHash, string tempPath)
{
// 1. 下载加密文件
using var client = new HttpClient();
var encryptedData = await client.GetByteArrayAsync(downloadUrl);
// 2. 解密 AES 密钥
var encryptedKey = Convert.FromBase64String(encryptedKeyB64);
var aesKey = _privateKey.Decrypt(encryptedKey,
RSAEncryptionPadding.OaepSHA256);
// 3. 解析数据:nonce(12) + tag(16) + ciphertext
var nonce = encryptedData[..12];
var tag = encryptedData[12..28];
var ciphertext = encryptedData[28..];
// 4. AES-GCM 解密
using var aes = new AesGcm(aesKey);
var decryptedData = new byte[ciphertext.Length];
aes.Decrypt(nonce, ciphertext, tag, decryptedData);
// 5. 验证哈希
var actualHash = Convert.ToHexString(SHA256.HashData(encryptedData));
if (!actualHash.Equals(expectedHash, StringComparison.OrdinalIgnoreCase))
throw new Exception("文件完整性校验失败");
// 6. 写入临时文件
var tempFile = Path.Combine(tempPath,
$"app_{Guid.NewGuid()}.exe");
await File.WriteAllBytesAsync(tempFile, decryptedData);
return tempFile;
}
}
5.8 防杀软误报处理
报毒原因分析
| 类型 | 说明 | 示例 |
|---|---|---|
| 代码特征 | 特定代码序列与已知恶意软件相似 | 混淆器特征码 |
| 行为特征 | 可疑行为模式 | 注入、反调试、隐藏窗口 |
| 启发式 | AI 识别出可疑模式 | 加壳、加密、网络通信 |
| 声誉检测 | 文件签名未知/新文件 | 未签名的二进制 |
判断与处理流程
┌─────────────────────────────────────────────────────────────┐
│ 杀软误报处理流程 │
├─────────────────────────────────────────────────────────────┤
│ │
│ Step 1: 检查报毒类型 │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ 上传到 VirusTotal (https://www.virustotal.com/) │ │
│ │ 查看杀软检测情况和报毒类型 │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │ │
│ ┌────────────────┬────────────────┬──────────────┐ │
│ ▼ ▼ ▼ │ │
│ 【仅哈希检测】 【代码特征】 【行为特征】 │ │
│ 重编译即可 需要改代码 需要改代码 │ │
│ │
│ Step 2: 尝试简单修复(仅哈希检测有效) │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ • 修改版本号 │ │
│ │ • 重新编译 │ │
│ │ • 更换时间戳 │ │
│ │ • 调整编译选项 │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ Step 3: 代码调整(如简单修复无效) │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ 【去除触发特征】 │ │
│ │ • 去掉或简化反调试代码 │ │
│ │ • 去掉注入、Hook 等敏感操作 │ │
│ │ • 减少混淆强度或更换混淆工具 │ │
│ │ • 正常的窗口行为,不隐藏主窗口 │ │
│ │ │ │
│ │ 【加壳相关】 │ │
│ │ • 更换加壳工具或参数 │ │
│ │ • 尝试不加壳,仅用混淆 │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ Step 4: 申请白名单(长期方案) │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ • 购买代码签名证书($150-600/年) │ │
│ │ - DigiCert: $400-600/年 │ │
│ │ - Sectigo: $150-200/年 │ │
│ │ - 国内CA: ¥500-1500/年 │ │
│ │ • 向杀软厂商申请白名单 │ │
│ │ • 提供软件说明和证明 │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘
快速判断表
| 现象 | 原因 | 解决方法 |
|---|---|---|
| 仅 1-2 个杀软报毒,其他正常 | 哈希碰撞/误报 | 重编译即可 |
| 多个杀软报「Heur/Trojan」 | 启发式检测 | 去掉反调试/反虚拟机代码 |
| 多个杀软报「Packed」 | 加壳检测 | 更换加壳工具或去掉加壳 |
| 全部报毒 | 代码行为可疑 | 检查是否有注入/Hook等操作 |
| 报「Confuser」特征 | 混淆器特征 | 更换混淆工具或调整参数 |
| 报「Themida」特征 | 加壳工具特征 | 更换加壳工具或参数 |
预防措施(开发阶段)
┌─────────────────────────────────────────────────────────────┐
│ 开发阶段预防措施 │
├─────────────────────────────────────────────────────────────┤
│ ✅ 避免使用被黑的混淆器/加壳工具 │
│ ✅ 减少反调试、反虚拟机检测(或设为可选) │
│ ✅ 不要注入其他进程 │
│ ✅ 正常的窗口行为,不隐藏主窗口 │
│ ✅ 使用代码签名证书 │
│ ✅ 定期在 VirusTotal 上测试 │
└─────────────────────────────────────────────────────────────┘
总结
- 重编译可能解决问题(10-20% 情况)
- 大部分情况需要调整代码(去掉反调试等)
- 长期方案是购买代码签名证书
六、卡密生成算法
6.1 卡密格式
格式: XXXX-XXXX-XXXX-XXXX (4组,每组4字符)
示例: A3D7-K2P9-M8N1-Q4W6
编码方式: Base32(避免混淆字符)
排除字符: 0/O, 1/I/l
字符集: 23456789ABCDEFGHJKMNPQRSTUVWXYZ
6.2 生成逻辑
public static class CardKeyGenerator
{
private static readonly string Base32Chars = "23456789ABCDEFGHJKMNPQRSTUVWXYZ";
public static string Generate(string projectId, CardType type, int durationDays)
{
// 1. 生成随机数据
var randomBytes = new byte[12];
RandomNumberGenerator.Fill(randomBytes);
// 2. 添加类型和时长信息
var payload = new byte[16];
Array.Copy(randomBytes, 0, payload, 0, 12);
payload[12] = (byte)type;
Array.Copy(BitConverter.GetBytes(durationDays), 0, payload, 13, 2);
// 3. 计算校验码
var crc = Crc32.Compute(payload);
var checksum = BitConverter.GetBytes(crc).Take(2).ToArray();
// 4. Base32 编码
var fullPayload = payload.Concat(checksum).ToArray();
var encoded = Base32Encode(fullPayload);
// 5. 格式化输出
return FormatKey(encoded);
}
public static bool Validate(string keyCode)
{
// 1. 格式校验
if (!Regex.IsMatch(keyCode, @"^[2-9A-HJ-NP-Z]{4}-[2-9A-HJ-NP-Z]{4}-[2-9A-HJ-NP-Z]{4}-[2-9A-HJ-NP-Z]{4}$"))
return false;
// 2. 校验码验证
var raw = keyCode.Replace("-", "");
var payload = Base32Decode(raw);
var receivedCrc = BitConverter.ToUInt16(payload.AsSpan(^2));
var computedCrc = Crc32.Compute(payload[..(^2)]);
return receivedCrc == computedCrc;
}
private static string FormatKey(string encoded)
{
var parts = new List<string>();
for (int i = 0; i < encoded.Length; i += 4)
{
parts.Add(encoded.Substring(i, Math.Min(4, encoded.Length - i)));
}
return string.Join("-", parts.Take(4));
}
}
七、技术栈
7.1 后端
| 技术 | 版本 | 用途 |
|---|---|---|
| ASP.NET Core | 8.0 | Web API 框架 |
| PostgreSQL | 16+ | 数据库 |
| Redis | 7+ | 缓存 + 分布式锁 |
| JWT | - | Token 生成 |
| Serilog | - | 日志记录 |
| Polly | - | 重试策略 |
7.2 前端(管理后台)
| 技术 | 版本 | 用途 |
|---|---|---|
| Vue | 3.4+ | 前端框架 |
| Element Plus | Latest | UI 组件库 |
| Pinia | Latest | 状态管理 |
| Axios | Latest | HTTP 请求 |
| ECharts | 5+ | 数据可视化 |
7.3 客户端
| 技术 | 版本 | 用途 |
|---|---|---|
| .NET | 8.0 | 运行时 |
| WPF / WinUI 3 | - | UI 框架 |
| RestSharp | - | HTTP 客户端 |
| Microsoft.Extensions.Hosting | - | 后台服务(心跳) |
7.4 安全工具
| 工具 | 类型 | 用途 |
|---|---|---|
| ConfuserEx | 开源免费 | 代码混淆 |
| Obfuscar | 开源免费 | 另一种混淆选择 |
| Themida | 商业 $ | 强力加壳,反调试 |
| VMProtect | 商业 $ | 虚拟化保护 |
| dnSpy | 免费测试 | 反编译测试(验证防护效果) |
八、部署方案(单服务器)
本方案针对小型项目,采用单服务器 Docker Compose 部署,简单可靠。
8.1 单服务器架构
┌─────────────────────────────────────────────────────────┐
│ 单服务器 (4核8G) │
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Nginx │ │
│ │ (反向代理 + SSL) │ │
│ │ :80 :443 │ │
│ └───────────────────────┬─────────────────────────────┘ │
│ │ │
│ ┌───────────────────────┴─────────────────────────────┐ │
│ │ Docker Compose │ │
│ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ │
│ │ │ API │ │PostgreSQL│ │ Redis │ │ │
│ │ │ :39256 │ │ :5432 │ │ :6379 │ │ │
│ │ └──────────┘ └──────────┘ └──────────┘ │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 本地文件存储 │ │
│ │ /data/uploads (软件包) │ │
│ │ /data/backups (数据库备份) │ │
│ └─────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────┘
8.2 服务器要求
| 配置 | 最低配置 | 推荐配置 | 说明 |
|---|---|---|---|
| CPU | 2核 | 4核 | 并发100以内2核足够 |
| 内存 | 4GB | 8GB | PostgreSQL需要较多内存 |
| 硬盘 | 40GB SSD | 100GB SSD | 预留日志和备份空间 |
| 带宽 | 5Mbps | 10Mbps | 软件下载需要带宽 |
8.3 Docker Compose 完整配置
# docker-compose.yml
version: '3.8'
services:
api:
build: ./backend
container_name: license-api
restart: always
environment:
- ASPNETCORE_ENVIRONMENT=Production
- ConnectionStrings__DefaultConnection=Host=db;Port=5432;Database=license;Username=license;Password=${DB_PASSWORD}
- Redis__ConnectionString=redis:6379
- Jwt__Secret=${JWT_SECRET}
- Jwt__Issuer=license-system
- Jwt__ExpireMinutes=1440
volumes:
- ./data/uploads:/app/uploads
- ./data/logs:/app/logs
ports:
- "127.0.0.1:39256:39256"
depends_on:
db:
condition: service_healthy
redis:
condition: service_started
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:39256/health/live"]
interval: 30s
timeout: 10s
retries: 3
db:
image: postgres:16-alpine
container_name: license-db
restart: always
environment:
POSTGRES_DB: license
POSTGRES_USER: license
POSTGRES_PASSWORD: ${DB_PASSWORD}
volumes:
- ./data/postgres:/var/lib/postgresql/data
- ./scripts/init.sql:/docker-entrypoint-initdb.d/init.sql
ports:
- "127.0.0.1:5432:5432"
healthcheck:
test: ["CMD-SHELL", "pg_isready -U license"]
interval: 10s
timeout: 5s
retries: 5
redis:
image: redis:7-alpine
container_name: license-redis
restart: always
command: redis-server --appendonly yes --maxmemory 256mb --maxmemory-policy allkeys-lru
volumes:
- ./data/redis:/data
ports:
- "127.0.0.1:6379:6379"
volumes:
postgres_data:
redis_data:
8.4 环境变量配置
# .env 文件(不要提交到Git)
DB_PASSWORD=your_strong_password_here
JWT_SECRET=your_jwt_secret_at_least_32_chars
ADMIN_PASSWORD=initial_admin_password
8.5 Nginx 配置
# /etc/nginx/sites-available/license.conf
server {
listen 80;
server_name api.yourdomain.com;
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl http2;
server_name api.yourdomain.com;
ssl_certificate /etc/letsencrypt/live/yourdomain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/yourdomain.com/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256;
ssl_prefer_server_ciphers off;
# 安全头
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
# API代理
location /api/ {
proxy_pass http://127.0.0.1:39256;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# 超时设置
proxy_connect_timeout 60s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
}
# 健康检查
location /health/ {
proxy_pass http://127.0.0.1:39256;
access_log off;
}
# 软件下载(大文件)
location /api/software/download {
proxy_pass http://127.0.0.1:39256;
proxy_read_timeout 300s;
proxy_buffering off;
}
# 限流配置
limit_req_zone $binary_remote_addr zone=api:10m rate=10r/s;
location /api/auth/ {
limit_req zone=api burst=20 nodelay;
proxy_pass http://127.0.0.1:39256;
}
}
8.6 数据库备份脚本
#!/bin/bash
# /opt/license/scripts/backup.sh
BACKUP_DIR="/opt/license/data/backups"
DATE=$(date +%Y%m%d_%H%M%S)
KEEP_DAYS=7
# 创建备份目录
mkdir -p $BACKUP_DIR
# 备份PostgreSQL
docker exec license-db pg_dump -U license license | gzip > $BACKUP_DIR/db_$DATE.sql.gz
# 备份Redis
docker exec license-redis redis-cli BGSAVE
sleep 5
cp /opt/license/data/redis/dump.rdb $BACKUP_DIR/redis_$DATE.rdb
# 删除旧备份
find $BACKUP_DIR -type f -mtime +$KEEP_DAYS -delete
echo "Backup completed: $DATE"
添加定时任务:
# crontab -e
0 3 * * * /opt/license/scripts/backup.sh >> /var/log/license-backup.log 2>&1
8.7 快速部署步骤
# 1. 安装Docker和Docker Compose
curl -fsSL https://get.docker.com | sh
apt install docker-compose-plugin
# 2. 创建项目目录
mkdir -p /opt/license/{data,scripts}
cd /opt/license
# 3. 配置环境变量
cp .env.example .env
nano .env # 修改密码
# 4. 启动服务
docker compose up -d
# 5. 检查状态
docker compose ps
docker compose logs -f api
# 6. 配置Nginx和SSL
apt install nginx certbot python3-certbot-nginx
certbot --nginx -d api.yourdomain.com
# 7. 访问测试
curl https://api.yourdomain.com/health/ready
九、实施步骤(小项目工期估算)
团队配置:2-3人(1后端 + 1前端 + 0.5客户端) 总工期:8-10周(可根据功能优先级裁剪)
Phase 1: 基础架构(2周)
第1周:后端框架
- 项目初始化(ASP.NET Core 8.0)
- 数据库表结构搭建 + 迁移脚本
- 基础认证(JWT)
- 卡密生成/验证服务
第2周:核心API
- 项目管理 API
- 卡密管理 API(CRUD + 批量生成)
- 设备绑定 + 心跳验证
- Docker 环境配置
Phase 2: 管理后台(2-3周)
第3周:基础页面
- Vue3 项目初始化 + 路由
- 登录/权限模块
- 项目管理页面
- 卡密列表 + 生成页面
第4周:功能完善
- 卡密详情 + 操作(封禁/解绑等)
- 代理商管理(可选,如不需要可跳过)
- 系统设置页面
第5周(可选):增强功能
- 统计报表(ECharts)
- 日志审计
- 批量导入/导出
Phase 3: 客户端启动器(1-2周)
第6周:基础功能
- WPF 界面(登录 + 主界面)
- 卡密验证逻辑
- 机器码生成
- 软件下载与启动
第7周(可选):增强
- 自动更新检测
- 心跳服务
- 离线缓存
Phase 4: 安全与测试(2周)
第8周:安全加固
- 请求签名验证
- 风控规则(限流 + 异常检测)
- ConfuserEx 混淆配置
- 基础反调试
第9周:测试与部署
- 功能测试(手动 + 自动化)
- 压力测试(JMeter/K6)
- 生产环境部署
- 监控配置
工期对照表
| 功能模块 | MVP版本 | 完整版本 | 备注 |
|---|---|---|---|
| 后端API | 2周 | 3周 | 核心必须 |
| 管理后台 | 2周 | 3周 | 可先做基础版 |
| 客户端 | 1周 | 2周 | 可后期迭代 |
| 安全加固 | 1周 | 2周 | 服务端优先 |
| 测试部署 | 1周 | 1周 | 不可省略 |
| 总计 | 7周 | 11周 | - |
MVP优先级建议
必须有(MVP):
- 卡密生成/验证/绑定
- 基础管理后台(项目+卡密)
- 简单客户端(登录+启动)
- 基础安全(签名+限流)
可后期迭代:
- 代理商系统
- 统计报表
- 自动更新
- 高级风控
十、风险与局限
10.1 防护的现实边界
必须承认:
没有绝对安全的客户端防护。任何运行在用户机器上的代码,理论上都可以被破解。
合理目标:
- 提高破解成本,让破解难度 > 软件价值
- 防止大规模盗版(允许个别破解案例存在)
- 及时发现异常行为并封禁
- 通过服务端校验,让破解者需要持续维护
10.2 安全建议
- 持续更新 - 频繁更新客户端,让破解者跟不上
- 服务端校验 - 核心逻辑放服务端,客户端只做展示
- 在线验证 - 必须联网才能使用(牺牲体验换安全)
- 法律手段 - 用户协议明确禁止逆向,配合法律追责
10.3 成本对比
| 方案 | 成本 | 安全等级 | 建议 |
|---|---|---|---|
| 无保护 | ¥0 | ⭐ | 不推荐 |
| 开源混淆 | ¥0 | ⭐⭐ | 个人项目 |
| 混淆+反调试 | ¥0 | ⭐⭐⭐ | 中小型项目 |
| 商业加壳 | $100-500/年 | ⭐⭐⭐⭐ | 商业项目 |
| 硬件狗 | $20+/个 | ⭐⭐⭐⭐⭐ | 高价值软件 |
十一、后续扩展
- 多租户支持 - 允许第三方开发者注册使用
- 代理商系统 - 支持多级代理分销
- 卡密续费 - 到期后续费延长
- 使用时长限制 - 按小时/天数计费
- 移动端 - Android/iOS 授权验证
- Web 版 - 支持浏览器内使用(WebAssembly)
- 数据分析 - 用户行为分析、转化漏斗
十二、管理后台页面设计
12.1 页面结构
┌─────────────────────────────────────────────────────────────────┐
│ 管理后台 │
├─────────────────────────────────────────────────────────────────┤
│ ┌─────────┐ │
│ │ Logo │ 软件授权管理系统 管理员 ▼ │
│ └─────────┘ │
├──┬──────────────────────────────────────────────────────────────┤
│ │ │
│ │ 📊 仪表盘 📁 项目 🔑 卡密 👥 代理 🖥️ 设备 │
│ │ │
│ ├──────────────────────────────────────────────────────────────┤
│ │ │
│ │ 【页面内容区】 │
│ │ │
│ │ │
│ │ │
│ │ │
│ └──────────────────────────────────────────────────────────────┘
└─────────────────────────────────────────────────────────────────┘
12.2 关键页面原型
12.2.1 仪表盘页面
┌─────────────────────────────────────────────────────────────────┐
│ 📊 仪表盘 [刷新] │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ 总卡密数 │ │ 活跃卡密 │ │ 在线设备 │ │
│ │ 10,234 │ │ 6,789 │ │ 4,521 │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
│ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ 今日收入 │ │ 本月收入 │ │ 今日新增 │ │
│ │ ¥3,280 │ │ ¥89,500 │ │ 156 │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ 活跃用户趋势 │ │
│ │ ▁▂▃▅▆▇█▇▆▅▃▂▁ (ECharts 折线图) │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
│ ┌────────────────────┐ ┌──────────────────────────────────┐ │
│ │ 项目分布 │ │ 最近活动 │ │
│ │ (饼图) │ │ • 代理商A生成100张卡密 10:00 │ │
│ │ │ │ • 用户B激活卡密 09:58 │ │
│ │ │ │ • 代理商C充值¥5000 09:30 │ │
│ └────────────────────┘ └──────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
12.2.2 卡密生成页面
┌─────────────────────────────────────────────────────────────────┐
│ 🔑 批量生成卡密 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 项目: [下拉选择 ▼] (必填) │
│ 卡密类型: ○ 天卡 ○ 周卡 ● 月卡 ○ 年卡 ○ 永久 │
│ 有效期: [30] 天 │
│ 生成数量: [100] (1-10000) │
│ 备注: [_______________________________] │
│ │
│ 预计消耗额度:¥300.00 (当前余额:¥5,000.00) │
│ │
│ [取消] [生成卡密] │
│ │
└─────────────────────────────────────────────────────────────────┘
│ 生成后 ▼
┌─────────────────────────────────────────────────────────────────┐
│ ✓ 生成成功! │
├─────────────────────────────────────────────────────────────────┤
│ 已生成 100 张卡密 │
│ │
│ 批次ID:batch_20251228_001 │
│ 项目:我的软件 (PROJ_001) │
│ 类型:月卡 (30天) │
│ │
│ [复制全部] [导出TXT] [导出CSV] [导出Excel] [关闭] │
│ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ A3D7-K2P9-M8N1-Q4W6 │ │
│ │ B4E8-L3X0-N9O2-P5Y7 │ │
│ │ C5F9-M4Y1-P0Q3-R6T8 │ │
│ │ ... (共100条) │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
12.2.3 卡密列表页面
┌─────────────────────────────────────────────────────────────────┐
│ 🔑 卡密管理 │
├─────────────────────────────────────────────────────────────────┤
│ 项目: [全部 ▼] 状态: [全部 ▼] 类型: [全部 ▼] │
│ 搜索: [__________________] [搜索] │
│ │
│ ☑ 批量操作: [封禁] [解封] [删除] [导出] │
│ │
│ ┌──┬────────────┬────┬──────┬────────┬────────┬──────┬────┐ │
│ │☑│ 卡密 │类型│ 状态 │ 激活时间│ 过期时间│ 备注 │操作│ │
│ ├──┼────────────┼────┼──────┼────────┼────────┼──────┼────┤ │
│ │☑│A3D7-K2P9.. │月卡│ ●活跃│12-01 │12-31 │ │详情│ │
│ │☑│B4E8-L3X0.. │月卡│ ○未用│ - │ - │ │编辑│ │
│ │☑│C5F9-M4Y1.. │月卡│ ✗封禁│12-01 │12-31 │违规 │解封│ │
│ │☑│... │... │ ... │ ... │ ... │ ... │... │ │
│ └──┴────────────┴────┴──────┴────────┴────────┴──────┴────┘ │
│ │
│ 共 1000 条 [< 1 2 3 4 5 ... 20 >] 每页 [20]▼ 条 │
│ │
└─────────────────────────────────────────────────────────────────┘
12.2.4 卡密详情页面
┌─────────────────────────────────────────────────────────────────┐
│ 🔑 卡密详情 [返回列表] │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 基本信息 │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ 卡密: A3D7-K2P9-M8N1-Q4W6 │ │
│ │ 项目: 我的项目 (PROJ_001) │ │
│ │ 类型: 月卡 (30天) │ │
│ │ 状态: ● 活跃 │ │
│ │ 激活时间: 2025-12-01 10:30:00 │ │
│ │ 过期时间: 2025-12-31 23:59:59 (剩余 3 天) │ │
│ │ 备注: 批次20251228 │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
│ 设备信息 │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ 设备ID: abc123def456... │ │
│ │ 设备名: DESKTOP-ABC123 │ │
│ │ 最后心跳: 2025-12-28 10:30:00 (● 在线) │ │
│ │ 登录IP: 1.2.3.4 (中国-广东-深圳) │ │
│ │ 首次登录: 2025-12-01 10:30:00 │ │
│ │ [强制下线] [解绑设备] │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
│ 操作日志 │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ 2025-12-28 10:30 心跳验证 系统 1.2.3.4 │ │
│ │ 2025-12-01 10:30 激活卡密 用户 1.2.3.4 │ │
│ │ 2025-11-28 00:00 生成卡密 管理员 admin │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
│ [封禁卡密] [延长30天] [修改备注] [删除] │
│ │
└─────────────────────────────────────────────────────────────────┘
12.2.5 项目管理页面
┌─────────────────────────────────────────────────────────────────┐
│ 📁 项目管理 [+ 新建项目] │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ 我的项目 v1.0 [编辑][删除] │ │
│ │ PROJ_001 | 活跃卡密: 1234 | 总卡密: 5000 │ │
│ │ -------------------------------------------------------- │ │
│ │ ProjectKey: abcd1234efgh5678 │ │
│ │ ProjectSecret: **** [显示/复制] │ │
│ │ 软件版本: v1.0.0 │ │
│ │ 软件地址: https://cdn.example.com/app.zip │ │
│ │ 最大设备数: 1 │ │
│ │ │ │
│ │ [查看统计] [管理卡密] [开发文档] [上传新版本] │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ 另一个项目 v2.0 [编辑][删除] │ │
│ │ ... │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
12.2.6 代理商管理页面
┌─────────────────────────────────────────────────────────────────┐
│ 👥 代理商管理 [+ 新建代理] │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ 某某科技公司 (AGENT001) [编辑][删除] │ │
│ │ -------------------------------------------------------- │ │
│ │ 联系人:张三 | 电话:13800138000 | 邮箱:... │ │
│ │ 余额:¥5,000.00 | 折扣:80% | 状态:● 活跃 │ │
│ │ -------------------------------------------------------- │ │
│ │ 统计:总卡密 1000 | 活跃 800 | 收入 ¥50,000 │ │
│ │ │ │
│ │ [充值] [查看明细] [销售统计] [额度流水] │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
12.2.7 开发文档页面
┌─────────────────────────────────────────────────────────────────┐
│ 📖 开发文档 - 我的项目 [编辑文档] │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 目录 │
│ ├── 1. 快速开始 │
│ ├── 2. 项目配置 │
│ ├── 3. SDK 集成 │
│ ├── 4. API 接口 │
│ ├── 5. 代码示例 │
│ └── 6. 常见问题 │
│ │
│ ──────────────────────────────────────────────────────────── │
│ │
│ 1. 快速开始 │
│ │
│ 本文档将指导您如何在软件中集成授权验证功能。 │
│ │
│ 2. 项目配置 │
│ │
│ 您的项目信息如下: │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ ProjectId: PROJ_001 │ │
│ │ ProjectKey: abcd1234efgh5678 [复制] │ │
│ │ ProjectSecret: xyz999aaa888bbb777 [复制] │ │
│ │ API地址: https://api.example.com │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
│ ⚠️ 重要:ProjectSecret 仅在此显示一次,请妥善保管! │
│ │
│ 3. SDK 集成 │
│ │
│ 步骤1:安装 NuGet 包 │
│ ```bash │
│ Install-Package YourCompany.License.SDK │
│ ``` │
│ │
│ 步骤2:在 Program.cs 中初始化 │
│ ```csharp │
│ using YourCompany.License.SDK; │
│ │ │
│ var licenseClient = new LicenseClient(new LicenseConfig { │
│ ProjectId = "PROJ_001", │
│ ProjectKey = "abcd1234efgh5678", │
│ ProjectSecret = "xyz999aaa888bbb777", │
│ ApiEndpoint = "https://api.example.com" │
│ }); │
│ │ │
│ var result = await licenseClient.VerifyAsync(cardKey); │
│ if (!result.IsValid) { │
│ MessageBox.Show("卡密验证失败:" + result.Message); │
│ Application.Current.Shutdown(); │
│ } │
│ ``` │
│ │
│ [继续阅读下一页 →] │
│ │
└─────────────────────────────────────────────────────────────────┘
12.3 权限控制
| 角色 | 仪表盘 | 项目 | 卡密 | 代理 | 设备 | 日志 | 设置 |
|---|---|---|---|---|---|---|---|
| 超级管理员 | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| 管理员 | ✅ | ✅ | ✅ | ❌ | ✅ | ✅ | ❌ |
| 代理商 | ✅ | 限 | 限 | ❌ | ✅ | 限 | ❌ |
12.4 超管/管理员后台 vs 代理商后台
┌─────────────────────────────────────────────────────────────────┐
│ 超管/管理员后台菜单 │
├─────────────────────────────────────────────────────────────────┤
│ 📊 仪表盘 ✅ ──► 完整统计 │
│ 📁 项目管理 ✅ ──► 全部项目 │
│ 🔑 卡密管理 ✅ ──► 完整CRUD + 批量操作 │
│ 👥 代理商 ✅ ──► 创建/管理代理商(仅超管) │
│ 🖥️ 设备管理 ✅ ──► 所有设备 │
│ 📝 日志审计 ✅ ──► 完整日志 │
│ ⚙️ 系统设置 ✅ ──► 系统配置 │
└─────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ 代理商后台菜单 │
├─────────────────────────────────────────────────────────────────┤
│ 📊 仪表盘 ✅ ──► 仅显示自己的数据 │
│ 📁 项目管理 限 ──► 仅显示授权的项目 │
│ 🔑 卡密管理 限 ──► 仅能生成/管理自己生成的卡密 │
│ 👥 代理商 ❌ ──► 不可见 │
│ 🖥️ 设备管理 限 ──► 仅自己卡密的设备 │
│ 📝 日志审计 限 ──► 仅自己的操作日志 │
│ ⚙️ 系统设置 ❌ ──► 不可见 │
│ 💰 额度管理 ✅ ──► 查看余额、充值记录(代理商专属) │
└─────────────────────────────────────────────────────────────────┘
12.5 代理商后台页面
┌─────────────────────────────────────────────────────────────────┐
│ 💰 额度中心 余额: ¥5,000 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │
│ │ 当前余额 │ │ 本月消耗 │ │ 充值记录 │ │
│ │ ¥5,000.00 │ │ ¥3,000.00 │ │ 15 次 │ │
│ └─────────────────┘ └─────────────────┘ └─────────────────┘ │
│ │
│ ──────────────────────────────────────────────────────────── │
│ │
│ 💡 我的折扣:80%(8折) │
│ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ 项目价格(折扣后) │ │
│ │ │ │
│ │ 项目A - 我的项目 │ │
│ │ 天卡 ¥0.80 | 周卡 ¥4.00 | 月卡 ¥12.00 │ │
│ │ 年卡 ¥80.00 | 永久卡 ¥239.20 │ │
│ │ │ │
│ │ 项目B - 另一个项目 │ │
│ │ 天卡 ¥1.00 | 周卡 ¥5.00 | 月卡 ¥15.00 │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
│ ──────────────────────────────────────────────────────────── │
│ │
│ 📊 充值/消费流水 │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ 2025-12-28 10:30 充值 +¥1000.00 余额 ¥5000.00 │ │
│ │ 2025-12-27 15:20 消费 -¥15.00 余额 ¥4000.00 │ │
│ │ 2025-12-25 09:00 充值 +¥5000.00 余额 ¥4015.00 │ │
│ │ ... │ │
│ │ │ │
│ │ [查看更多记录 →] │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
十三、系统设置(前端配置驱动)
13.1 设置页面设计
┌─────────────────────────────────────────────────────────────────┐
│ ⚙️ 系统设置 [保存更改] │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ 🔧 功能开关 │ │
│ │ │ │
│ │ ┌─────────────────────────────────────────────────────┐ │ │
│ │ │ [✓] 启用心跳验证 说明:客户端需定期验证 │ │ │
│ │ │ [✓] 启用设备绑定 说明:单卡密绑定单设备 │ │ │
│ │ │ [✓] 启用自动更新 说明:客户端自动检测更新 │ │ │
│ │ │ [ ] 启用强制更新 说明:旧版本强制升级 │ │ │
│ │ │ [✓] 启用代理商系统 说明:允许代理商销售 │ │ │
│ │ │ [✓] 启用卡密续费 说明:用户可续费延长 │ │ │
│ │ │ [ ] 启用试用模式 说明:未激活可试用X天 │ │ │
│ │ └─────────────────────────────────────────────────────┘ │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ 🔐 验证规则 │ │
│ │ │ │
│ │ 单卡密最大设备数: [1] (1-10) │ │
│ │ [ ] 允许多设备同时在线 │ │
│ │ [✓] 卡密需要激活 │ │
│ │ 过期类型: (•)激活后计算 ( )固定时间 │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ 💓 心跳配置 │ │
│ │ │ │
│ │ [✓] 启用心跳验证 │ │
│ │ 心跳间隔: [60] 秒 (10-300) │ │
│ │ 心跳超时: [180] 秒 (30-600) │ │
│ │ 掉线后行为: (•)退出程序 ( )警告提示 ( )忽略 │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ 🛡️ 限流配置 │ │
│ │ │ │
│ │ [✓] 启用请求限流 │ │
│ │ IP每分钟限制: [100] 次 │ │
│ │ 设备每分钟限制: [50] 次 │ │
│ │ 封禁时长: [5] 分钟 │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ ⚠️ 风控配置 │ │
│ │ │ │
│ │ [✓] 启用风控检测 │ │
│ │ [✓] 异地登录检测 │ │
│ │ [✓] 设备变更检测 │ │
│ │ [ ] 自动封禁异常 │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ 📢 客户端显示 │ │
│ │ │ │
│ │ 系统名称: [软件授权管理系统 ] │ │
│ │ Logo地址: [https://... ] │ │
│ │ │ │
│ │ 公告标题: [系统维护通知 ] │ │
│ │ 公告内容: [将于今晚23:00进行维护... ] │ │
│ │ [✓] 显示公告 │ │
│ │ │ │
│ │ 联系方式URL: [https://support.example.com] │ │
│ │ 帮助文档URL: [https://docs.example.com ] │ │
│ │ [ ] 在客户端显示余额 │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ 📊 其他配置 │ │
│ │ │ │
│ │ [ ] 开放用户注册 │ │
│ │ 日志保留天数: [90] 天 │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
13.2 配置 API
# 获取所有配置(分组)
GET /api/admin/configs
Response: {
"code": 200,
"data": {
"feature": [
{ "key": "heartbeat", "value": "true", "displayName": "启用心跳验证" },
{ "key": "device_bind", "value": "true", "displayName": "启用设备绑定" },
...
],
"auth": [
{ "key": "max_devices", "value": "1", "displayName": "最大设备数" },
...
],
...
}
}
# 批量更新配置
PUT /api/admin/configs
Request: {
"feature.heartbeat": "false",
"auth.max_devices": "2",
"heartbeat.interval": "90"
}
Response: { "code": 200, "message": "配置已更新" }
# 获取客户端可读的公开配置
GET /api/client/config
Response: {
"code": 200,
"data": {
"heartbeat": true,
"autoUpdate": true,
"noticeTitle": "系统维护通知",
"noticeContent": "将于今晚23:00进行维护...",
"helpUrl": "https://docs.example.com"
}
}
13.3 配置读取服务(后端)
// 配置服务 - 从数据库读取,无硬编码
public class ConfigService
{
private readonly IMemoryCache _cache;
private readonly AppDbContext _db;
public async Task<string> GetAsync(string key, string defaultValue = "")
{
// 先从缓存读取
var cached = await _cache.GetAsync<string>($"config:{key}");
if (cached != null) return cached;
// 从数据库读取
var config = await _db.SystemConfigs
.Where(c => c.ConfigKey == key)
.FirstOrDefaultAsync();
var value = config?.ConfigValue ?? defaultValue;
// 缓存5分钟
await _cache.SetAsync($"config:{key}", value, TimeSpan.FromMinutes(5));
return value;
}
public async Task<bool> IsEnabledAsync(string featureKey)
{
var value = await GetAsync($"feature.{featureKey}", "false");
return value.Equals("true", StringComparison.OrdinalIgnoreCase);
}
public async Task<int> GetIntAsync(string key, int defaultValue = 0)
{
var value = await GetAsync(key, defaultValue.ToString());
return int.TryParse(value, out var result) ? result : defaultValue;
}
}
// 使用示例
public class AuthService
{
private readonly ConfigService _config;
public async Task<bool> VerifyAsync(string cardKey)
{
// 检查功能开关 - 是否需要设备绑定
var needBind = await _config.IsEnabledAsync("device_bind");
if (needBind)
{
// 执行设备绑定验证...
}
// 检查功能开关 - 是否启用心跳
var enableHeartbeat = await _config.IsEnabledAsync("heartbeat");
if (enableHeartbeat)
{
// 返回心跳配置给客户端...
}
return true;
}
}
13.4 功能开关说明
| 开关 | 关闭后行为 | 使用场景 |
|---|---|---|
feature.heartbeat |
客户端不需要发送心跳 | 离线环境、调试 |
feature.device_bind |
卡密可在任意设备使用 | 临时测试、多设备需求 |
feature.auto_update |
客户端不检查更新 | 固定版本部署 |
feature.agent_system |
隐藏代理商相关菜单 | 无代理商场景 |
feature.card_renewal |
不显示续费功能 | 一次性买断 |
feature.trial_mode |
允许未激活卡密试用X天 | 营销推广 |
十四、SDK 集成开发文档(管理后台内置)
14.1 快速开始
步骤 1:获取项目信息
在管理后台创建项目后,会获得以下信息:
{
"projectId": "PROJ_001",
"projectKey": "abcd1234efgh5678",
"projectSecret": "xyz999aaa888bbb777",
"apiEndpoint": "https://api.example.com"
}
步骤 2:安装 SDK
# NuGet 包
Install-Package YourCompany.License.Sdk
# 或 .NET CLI
dotnet add package YourCompany.License.Sdk
14.2 基础集成代码
using YourCompany.License.Sdk;
// 1. 初始化客户端
var licenseClient = new LicenseClient(new LicenseConfig
{
ProjectId = "PROJ_001",
ProjectKey = "abcd1234efgh5678",
ProjectSecret = "xyz999aaa888bbb777",
ApiEndpoint = "https://api.example.com"
});
// 2. 验证卡密(在软件启动时调用)
var result = await licenseClient.VerifyAsync(userInputCardKey);
if (!result.IsValid)
{
MessageBox.Show($"验证失败:{result.Message}");
Application.Current.Shutdown();
return;
}
// 3. 启动心跳(后台服务)
await licenseClient.StartHeartbeatAsync(result.AccessToken);
// 4. 软件正常运行...
14.3 WPF 应用完整示例
// App.xaml.cs
public partial class App : Application
{
private LicenseClient _licenseClient;
private ILicenseInfo _licenseInfo;
protected override async void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
// 显示登录窗口
var loginWindow = new LoginWindow();
if (loginWindow.ShowDialog() != true)
{
Shutdown();
return;
}
var cardKey = loginWindow.CardKey;
// 初始化并验证
_licenseClient = new LicenseClient(new LicenseConfig
{
ProjectId = "PROJ_001",
ProjectKey = "abcd1234efgh5678",
ProjectSecret = "xyz999aaa888bbb777",
ApiEndpoint = "https://api.example.com"
});
var result = await _licenseClient.VerifyAsync(cardKey);
if (!result.IsValid)
{
MessageBox.Show($"授权验证失败:{result.Message}", "错误",
MessageBoxButton.OK, MessageBoxImage.Error);
Shutdown();
return;
}
_licenseInfo = result;
// 启动心跳
_licenseClient.LicenseExpired += (s, e) =>
{
Dispatcher.Invoke(() =>
{
MessageBox.Show("授权已过期,软件将退出", "提示",
MessageBoxButton.OK, MessageBoxImage.Warning);
Shutdown();
});
};
await _licenseClient.StartHeartbeatAsync(result.AccessToken);
// 显示主窗口
var mainWindow = new MainWindow();
mainWindow.Closed += async (s, args) =>
{
await _licenseClient.StopHeartbeatAsync();
};
mainWindow.Show();
}
}
14.4 控制台应用示例
class Program
{
static async Task Main(string[] args)
{
Console.Write("请输入卡密:");
var cardKey = Console.ReadLine();
var licenseClient = new LicenseClient(new LicenseConfig
{
ProjectId = "PROJ_001",
ProjectKey = "abcd1234efgh5678",
ProjectSecret = "xyz999aaa888bbb777",
ApiEndpoint = "https://api.example.com"
});
var result = await licenseClient.VerifyAsync(cardKey);
if (!result.IsValid)
{
Console.WriteLine($"验证失败:{result.Message}");
return;
}
Console.WriteLine($"验证成功!到期时间:{result.ExpireTime}");
Console.WriteLine($"剩余天数:{result.RemainingDays}");
// 使用 CancellationToken 处理授权失效
var cts = new CancellationTokenSource();
licenseClient.LicenseExpired += (s, e) =>
{
Console.WriteLine("授权已失效,程序将退出...");
cts.Cancel();
};
await licenseClient.StartHeartbeatAsync(result.AccessToken);
// 主程序逻辑
while (!cts.Token.IsCancellationRequested)
{
// 你的业务代码
await Task.Delay(1000);
}
}
}
14.5 API 接口说明
验证接口
POST /api/auth/verify
Content-Type: application/json
{
"projectId": "PROJ_001",
"keyCode": "A3D7-K2P9-M8N1-Q4W6",
"deviceId": "硬件指纹",
"timestamp": 1735000000,
"signature": "HMAC-SHA256签名"
}
心跳接口
POST /api/auth/heartbeat
Authorization: Bearer {access_token}
Content-Type: application/json
{
"accessToken": "jwt_token",
"deviceId": "硬件指纹"
}
14.6 常见问题
Q:如何获取机器码(DeviceId)? A:SDK 会自动生成,无需手动获取。如需自定义:
var deviceId = MachineCode.GetDeviceId();
Q:心跳失败会怎样?
A:连续 3 次心跳失败(约 3 分钟),会触发 LicenseExpired 事件。
Q:如何处理网络断开? A:SDK 内置重试机制,短暂网络波动不会影响使用。
Q:可以离线使用吗? A:不可以,必须联网验证。这是安全保障的一部分。
十五、运维指南
15.1 环境变量说明
| 变量名 | 必填 | 默认值 | 说明 |
|---|---|---|---|
DB_PASSWORD |
✅ | - | PostgreSQL密码 |
JWT_SECRET |
✅ | - | JWT签名密钥(≥32字符) |
ADMIN_PASSWORD |
❌ | admin123 | 初始管理员密码 |
ASPNETCORE_ENVIRONMENT |
❌ | Production | 运行环境 |
Redis__ConnectionString |
❌ | redis:6379 | Redis连接字符串 |
Jwt__ExpireMinutes |
❌ | 1440 | Token过期时间(分钟) |
RateLimit__IpPerMinute |
❌ | 100 | IP每分钟请求限制 |
15.2 日志管理
日志位置:
/opt/license/data/logs/
├── app-20251230.log # 应用日志(按天滚动)
├── access-20251230.log # 访问日志
└── error-20251230.log # 错误日志
查看实时日志:
# Docker日志
docker compose logs -f api
# 应用日志
tail -f /opt/license/data/logs/app-$(date +%Y%m%d).log
日志清理(自动):
# 添加到crontab,保留30天日志
0 4 * * * find /opt/license/data/logs -name "*.log" -mtime +30 -delete
15.3 备份与恢复
手动备份:
# 备份数据库
docker exec license-db pg_dump -U license license > backup_$(date +%Y%m%d).sql
# 备份完整数据目录
tar -czvf license_backup_$(date +%Y%m%d).tar.gz /opt/license/data/
恢复数据库:
# 停止API服务
docker compose stop api
# 恢复数据库
cat backup_20251230.sql | docker exec -i license-db psql -U license license
# 启动API服务
docker compose start api
恢复完整数据:
docker compose down
tar -xzvf license_backup_20251230.tar.gz -C /
docker compose up -d
15.4 常见问题排查
问题1:API无法访问
# 检查容器状态
docker compose ps
# 查看API日志
docker compose logs api --tail 100
# 检查端口
netstat -tlnp | grep 39256
# 检查健康状态
curl http://127.0.0.1:39256/health/ready
问题2:数据库连接失败
# 检查PostgreSQL状态
docker exec license-db pg_isready -U license
# 查看数据库日志
docker compose logs db --tail 50
# 测试连接
docker exec -it license-db psql -U license -c "SELECT 1;"
问题3:Redis连接失败
# 检查Redis状态
docker exec license-redis redis-cli ping
# 查看内存使用
docker exec license-redis redis-cli info memory
问题4:卡密验证失败
# 查看验证日志
docker compose logs api 2>&1 | grep -i "verify"
# 检查卡密状态
docker exec license-db psql -U license -c "SELECT * FROM CardKeys WHERE KeyCode = 'XXXX-XXXX-XXXX-XXXX';"
# 检查设备绑定
docker exec license-db psql -U license -c "SELECT * FROM Devices WHERE CardKeyId = 123;"
15.5 性能监控
基础监控脚本:
#!/bin/bash
# /opt/license/scripts/monitor.sh
echo "=== System Status ==="
echo "CPU: $(top -bn1 | grep 'Cpu(s)' | awk '{print $2}')%"
echo "Memory: $(free -m | awk 'NR==2{printf "%s/%sMB (%.2f%%)", $3,$2,$3*100/$2 }')"
echo "Disk: $(df -h / | awk 'NR==2{print $5}')"
echo ""
echo "=== Docker Status ==="
docker compose ps
echo ""
echo "=== API Health ==="
curl -s http://127.0.0.1:39256/health/ready | jq .
echo ""
echo "=== Recent Errors ==="
tail -5 /opt/license/data/logs/error-$(date +%Y%m%d).log 2>/dev/null || echo "No errors today"
添加监控告警(简单版):
#!/bin/bash
# /opt/license/scripts/alert.sh
# 检查API健康状态
if ! curl -sf http://127.0.0.1:39256/health/live > /dev/null; then
# 发送告警(可替换为钉钉/邮件通知)
echo "[ALERT] License API is down at $(date)" >> /var/log/license-alert.log
# 尝试重启
docker compose restart api
fi
# 每分钟检查一次
* * * * * /opt/license/scripts/alert.sh
15.6 版本升级流程
# 1. 备份当前数据
/opt/license/scripts/backup.sh
# 2. 拉取新版本代码
cd /opt/license
git pull origin main
# 3. 重新构建镜像
docker compose build api
# 4. 滚动更新(无停机)
docker compose up -d --no-deps api
# 5. 检查新版本状态
docker compose logs -f api
# 6. 如有问题,回滚
docker compose down
docker compose up -d --build api # 使用上一个镜像
15.7 安全检查清单
定期检查(每周):
- 检查磁盘空间
df -h - 检查备份是否正常执行
- 检查异常登录日志
- 更新系统安全补丁
定期检查(每月):
- 检查SSL证书有效期
certbot certificates - 检查数据库表膨胀
VACUUM ANALYZE - 清理过期卡密和日志
- 审计管理员操作记录