Files
kami/软件授权系统-综合方案.md
2026-01-04 23:00:21 +08:00

3281 lines
144 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 软件授权系统 - 综合设计方案
## 一、需求分析
### 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项目表
```sql
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卡密表
```sql
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设备表
```sql
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访问日志表
```sql
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统计表
```sql
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管理员表
```sql
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代理商表
```sql
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代理商额度流水
```sql
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卡密操作日志
```sql
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系统配置表 - 配置驱动核心)
```sql
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 卡密激活(乐观锁)
```sql
-- 使用乐观锁防止并发激活
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
```sql
-- 设备绑定使用 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 心跳更新(批量优化)
```sql
-- 批量更新心跳时间(减少数据库压力)
UPDATE Devices
SET LastHeartbeat = NOW()
WHERE Id = ANY(:deviceIds) AND DeletedAt IS NULL;
```
#### 3.11.5 软删除查询模式
```sql
-- 所有查询默认过滤已删除数据
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 幂等性支持表
```sql
-- 幂等性键存储(防止重复请求)
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 统一响应格式
```json
{
"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) 机器码生成(硬件指纹)
```csharp
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) 请求签名
```csharp
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) 防篡改检测
```csharp
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) 心跳机制
```csharp
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登录 | 自动封禁,需申诉 |
**风控规则实现示例:**
```csharp
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 服务端加密实现示例
```csharp
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 客户端解密实现示例
```csharp
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 生成逻辑
```csharp
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 完整配置
```yaml
# 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 环境变量配置
```bash
# .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 配置
```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 数据库备份脚本
```bash
#!/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"
```
添加定时任务:
```bash
# crontab -e
0 3 * * * /opt/license/scripts/backup.sh >> /var/log/license-backup.log 2>&1
```
### 8.7 快速部署步骤
```bash
# 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
- [ ] 卡密管理 APICRUD + 批量生成)
- [ ] 设备绑定 + 心跳验证
- [ ] 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**
1. 卡密生成/验证/绑定
2. 基础管理后台(项目+卡密)
3. 简单客户端(登录+启动)
4. 基础安全(签名+限流)
**可后期迭代**
1. 代理商系统
2. 统计报表
3. 自动更新
4. 高级风控
---
## 十、风险与局限
### 10.1 防护的现实边界
**必须承认:**
> 没有绝对安全的客户端防护。任何运行在用户机器上的代码,理论上都可以被破解。
**合理目标:**
- 提高破解成本,让破解难度 > 软件价值
- 防止大规模盗版(允许个别破解案例存在)
- 及时发现异常行为并封禁
- 通过服务端校验,让破解者需要持续维护
### 10.2 安全建议
1. **持续更新** - 频繁更新客户端,让破解者跟不上
2. **服务端校验** - 核心逻辑放服务端,客户端只做展示
3. **在线验证** - 必须联网才能使用(牺牲体验换安全)
4. **法律手段** - 用户协议明确禁止逆向,配合法律追责
### 10.3 成本对比
| 方案 | 成本 | 安全等级 | 建议 |
|------|------|----------|------|
| 无保护 | ¥0 | ⭐ | 不推荐 |
| 开源混淆 | ¥0 | ⭐⭐ | 个人项目 |
| 混淆+反调试 | ¥0 | ⭐⭐⭐ | 中小型项目 |
| 商业加壳 | $100-500/年 | ⭐⭐⭐⭐ | 商业项目 |
| 硬件狗 | $20+/个 | ⭐⭐⭐⭐⭐ | 高价值软件 |
---
## 十一、后续扩展
1. **多租户支持** - 允许第三方开发者注册使用
2. **代理商系统** - 支持多级代理分销
3. **卡密续费** - 到期后续费延长
4. **使用时长限制** - 按小时/天数计费
5. **移动端** - Android/iOS 授权验证
6. **Web 版** - 支持浏览器内使用WebAssembly
7. **数据分析** - 用户行为分析、转化漏斗
---
## 十二、管理后台页面设计
### 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 张卡密 │
│ │
│ 批次IDbatch_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 配置读取服务(后端)
```csharp
// 配置服务 - 从数据库读取,无硬编码
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获取项目信息
在管理后台创建项目后,会获得以下信息:
```json
{
"projectId": "PROJ_001",
"projectKey": "abcd1234efgh5678",
"projectSecret": "xyz999aaa888bbb777",
"apiEndpoint": "https://api.example.com"
}
```
#### 步骤 2安装 SDK
```bash
# NuGet 包
Install-Package YourCompany.License.Sdk
# 或 .NET CLI
dotnet add package YourCompany.License.Sdk
```
### 14.2 基础集成代码
```csharp
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 应用完整示例
```csharp
// 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 控制台应用示例
```csharp
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 接口说明
#### 验证接口
```http
POST /api/auth/verify
Content-Type: application/json
{
"projectId": "PROJ_001",
"keyCode": "A3D7-K2P9-M8N1-Q4W6",
"deviceId": "",
"timestamp": 1735000000,
"signature": "HMAC-SHA256"
}
```
#### 心跳接口
```http
POST /api/auth/heartbeat
Authorization: Bearer {access_token}
Content-Type: application/json
{
"accessToken": "jwt_token",
"deviceId": ""
}
```
### 14.6 常见问题
**Q如何获取机器码DeviceId**
ASDK 会自动生成,无需手动获取。如需自定义:
```csharp
var deviceId = MachineCode.GetDeviceId();
```
**Q心跳失败会怎样**
A连续 3 次心跳失败(约 3 分钟),会触发 `LicenseExpired` 事件。
**Q如何处理网络断开**
ASDK 内置重试机制,短暂网络波动不会影响使用。
**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 # 错误日志
```
**查看实时日志:**
```bash
# Docker日志
docker compose logs -f api
# 应用日志
tail -f /opt/license/data/logs/app-$(date +%Y%m%d).log
```
**日志清理(自动):**
```bash
# 添加到crontab保留30天日志
0 4 * * * find /opt/license/data/logs -name "*.log" -mtime +30 -delete
```
### 15.3 备份与恢复
**手动备份:**
```bash
# 备份数据库
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/
```
**恢复数据库:**
```bash
# 停止API服务
docker compose stop api
# 恢复数据库
cat backup_20251230.sql | docker exec -i license-db psql -U license license
# 启动API服务
docker compose start api
```
**恢复完整数据:**
```bash
docker compose down
tar -xzvf license_backup_20251230.tar.gz -C /
docker compose up -d
```
### 15.4 常见问题排查
#### 问题1API无法访问
```bash
# 检查容器状态
docker compose ps
# 查看API日志
docker compose logs api --tail 100
# 检查端口
netstat -tlnp | grep 39256
# 检查健康状态
curl http://127.0.0.1:39256/health/ready
```
#### 问题2数据库连接失败
```bash
# 检查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;"
```
#### 问题3Redis连接失败
```bash
# 检查Redis状态
docker exec license-redis redis-cli ping
# 查看内存使用
docker exec license-redis redis-cli info memory
```
#### 问题4卡密验证失败
```bash
# 查看验证日志
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 性能监控
**基础监控脚本:**
```bash
#!/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"
```
**添加监控告警(简单版):**
```bash
#!/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
```
```bash
# 每分钟检查一次
* * * * * /opt/license/scripts/alert.sh
```
### 15.6 版本升级流程
```bash
# 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`
- [ ] 清理过期卡密和日志
- [ ] 审计管理员操作记录