3281 lines
144 KiB
Markdown
3281 lines
144 KiB
Markdown
# 软件授权系统 - 综合设计方案
|
||
|
||
## 一、需求分析
|
||
|
||
### 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
|
||
- [ ] 卡密管理 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)**:
|
||
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 张卡密 │
|
||
│ │
|
||
│ 批次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 配置读取服务(后端)
|
||
|
||
```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)?**
|
||
A:SDK 会自动生成,无需手动获取。如需自定义:
|
||
|
||
```csharp
|
||
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 # 错误日志
|
||
```
|
||
|
||
**查看实时日志:**
|
||
```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 常见问题排查
|
||
|
||
#### 问题1:API无法访问
|
||
|
||
```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;"
|
||
```
|
||
|
||
#### 问题3:Redis连接失败
|
||
|
||
```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`
|
||
- [ ] 清理过期卡密和日志
|
||
- [ ] 审计管理员操作记录
|