数据库设计
授权系统共 12 张表,按功能分为:用户表、管理员表、产品表、授权表、订单表、设置表、日志表、辅助表。所有表统一前缀 lic_。
ER 关系图
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ lic_users │──┐ │ lic_products │──┐ │ lic_orders │
│ (终端用户) │ │ │ (产品) │ │ │ (订单) │
└─────────────┘ │ └─────────────┘ │ └─────────────┘
│ │ │ │
│ 1:N │ 1:N │ N:1 │
▼ ▼ │ │
┌─────────────────────────┐ │ │
│ lic_licenses │◀┘ │
│ (授权码) │───────────────┘
└─────────────────────────┘ 1:1 (license_id)
│ │
│ │ N:1
│ ▼
│ ┌─────────────┐
│ │ lic_products│
│ └─────────────┘
│
│ N:1
▼
┌─────────────┐
│ lic_users │
└─────────────┘
核心表结构
lic_users — 终端用户表
CREATE TABLE lic_users (
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(50) NOT NULL,
email VARCHAR(100) NOT NULL,
password VARCHAR(255) NOT NULL, -- password_hash 加密
phone VARCHAR(20) DEFAULT NULL,
status ENUM('active', 'banned') DEFAULT 'active',
last_login_at DATETIME DEFAULT NULL,
last_login_ip VARCHAR(45) DEFAULT '',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
UNIQUE KEY uk_username (username),
UNIQUE KEY uk_email (email)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
关键设计:
- 用户名、邮箱都加唯一索引
- 密码用
password_hash($password, PASSWORD_BCRYPT)存储,永远不存明文 last_login_at用于显示"上次登录时间"和异常检测
lic_products — 产品表
CREATE TABLE lic_products (
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(100) NOT NULL, -- "夸克网盘二维码插件"
slug VARCHAR(50) NOT NULL, -- "shiguang-quark-qrcode"
description TEXT DEFAULT NULL, -- 简介
detail_md MEDIUMTEXT DEFAULT NULL, -- 详细介绍(Markdown)
price DECIMAL(10,2) DEFAULT 0.00, -- 0=免费
validity_days INT DEFAULT 0, -- 0=永久
max_sites INT DEFAULT 1, -- 授权站点数
license_mode ENUM('system','custom') DEFAULT 'system',
product_secret VARCHAR(64) DEFAULT '', -- 32 字符 API 签名密钥
status TINYINT(1) DEFAULT 1, -- 1=上架 0=下架
sort_order INT DEFAULT 0,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
UNIQUE KEY uk_slug (slug)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
slug 是关键字段:插件代码里硬编码这个值,授权验证时传给 API。所以改 slug 等于发布新产品,要谨慎。
lic_licenses — 授权码表
CREATE TABLE lic_licenses (
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
license_key VARCHAR(32) NOT NULL, -- LIC-XXXXX-XXXXX-XXXXX-XXXXX
user_id INT UNSIGNED NOT NULL,
product_id INT UNSIGNED NOT NULL,
domain VARCHAR(255) DEFAULT '', -- 标准化后的域名
status ENUM('active','suspended','expired') DEFAULT 'active',
type VARCHAR(20) DEFAULT 'standard', -- standard/pro/trial
expires_at DATETIME DEFAULT NULL, -- null=永久
max_sites INT DEFAULT 1,
verify_count INT UNSIGNED DEFAULT 0, -- 累计验证次数
last_verify_at DATETIME DEFAULT NULL,
last_verify_ip VARCHAR(45) DEFAULT '',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
UNIQUE KEY uk_license_key (license_key),
UNIQUE KEY uniq_user_product_domain (user_id, product_id, domain),
INDEX idx_user (user_id),
INDEX idx_product (product_id),
INDEX idx_status (status),
INDEX idx_expires (expires_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
关键索引:
uk_license_key:授权码全局唯一(避免重复)uniq_user_product_domain:防止同一用户重复购买同一产品的同一域名(数据库级约束)idx_status+idx_expires:加速过期清理任务
lic_orders — 订单表
CREATE TABLE lic_orders (
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
order_no VARCHAR(32) NOT NULL, -- 业务订单号
user_id INT UNSIGNED NOT NULL,
product_id INT UNSIGNED NOT NULL,
domain VARCHAR(255) NOT NULL, -- 用户购买时填的域名
amount DECIMAL(10,2) DEFAULT 0.00,
pay_type ENUM('alipay','wechat','wxpay','qqpay') DEFAULT 'alipay',
pay_status ENUM('pending','paid','completed','failed','refunded') DEFAULT 'pending',
pay_trade_no VARCHAR(64) DEFAULT '', -- 支付网关返回的交易号
license_id INT UNSIGNED DEFAULT NULL, -- 关联的授权码
mode ENUM('new','renew') DEFAULT 'new',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
paid_at DATETIME DEFAULT NULL,
completed_at DATETIME DEFAULT NULL,
expired_at DATETIME DEFAULT NULL, -- 订单超时时间
UNIQUE KEY uk_order_no (order_no),
INDEX idx_user (user_id),
INDEX idx_product (product_id),
INDEX idx_pay_status (pay_status),
INDEX idx_created (created_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
lic_settings — 系统设置
采用 KV 存储,所有动态配置都在这里:
CREATE TABLE lic_settings (
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
`key` VARCHAR(100) NOT NULL,
value TEXT DEFAULT NULL,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
UNIQUE KEY uk_key (`key`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
常用 key 示例:
| key | 说明 | 默认值 |
|---|---|---|
site_name | 站点名称 | License Server |
site_logo | 站点 Logo URL | — |
epay_url | 易支付网关 | 空 |
epay_pid | 易支付商户 ID | 空 |
epay_key | 易支付商户密钥 | 空 |
epay1_pay_type | 第一通道支付类型(alipay/wechat/none) | alipay |
epay2_enabled | 是否启用第二通道 | 0 |
epay2_pay_type | 第二通道支付类型 | |
order_timeout_minutes | 订单超时时间(分钟) | 30 |
remember_login_days | 记住登录有效期(天) | 7 |
lic_remember_tokens — 记住登录
CREATE TABLE lic_remember_tokens (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
user_id INT UNSIGNED NOT NULL,
token_hash CHAR(64) NOT NULL, -- SHA256(token)
expires_at DATETIME NOT NULL,
created_ip VARCHAR(45) DEFAULT '',
last_used_at DATETIME DEFAULT NULL,
INDEX idx_user (user_id),
INDEX idx_hash (token_hash),
INDEX idx_expires (expires_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
关键:只存哈希,不存明文 token。即使数据库泄露,攻击者也无法伪造 cookie。
性能优化
- 覆盖索引:
idx_user_product可加速查询某用户某产品的所有授权 - 分区表:订单表/日志表可按时间分区(订单数据量大时建议)
- 慢查询监控:定期
EXPLAIN检查大表查询
数据迁移
新版本发布时,可能涉及表结构变更。建议:
- 在
install.php中用CREATE TABLE IF NOT EXISTS兼容旧库 - 用
ALTER TABLE+ 条件判断实现"幂等迁移" - 迁移前务必备份数据库
<?php
// 幂等迁移示例
$db->exec("CREATE TABLE IF NOT EXISTS lic_new_table (
id INT PRIMARY KEY AUTO_INCREMENT,
...
) ENGINE=InnoDB");
// 检查列是否存在再添加
$columns = $db->query("SHOW COLUMNS FROM lic_users")->fetchAll(PDO::FETCH_COLUMN);
if (!in_array('phone', $columns)) {
$db->exec("ALTER TABLE lic_users ADD COLUMN phone VARCHAR(20) DEFAULT NULL AFTER email");
}