Initial clean project import

This commit is contained in:
cat-shark
2026-06-19 18:41:41 +08:00
commit a13b804c7a
1306 changed files with 220568 additions and 0 deletions
+19
View File
@@ -0,0 +1,19 @@
{
"name": "activation-server",
"version": "1.0.0",
"description": "Local activation code server for development",
"main": "server.js",
"scripts": {
"start": "node server.js",
"dev": "node server.js"
},
"keywords": ["activation", "license"],
"author": "",
"license": "MIT",
"dependencies": {
"express": "^4.18.0",
"cors": "^2.8.5",
"sqlite3": "^5.1.6",
"body-parser": "^1.20.0"
}
}
+318
View File
@@ -0,0 +1,318 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>激活码管理后台</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; background: #f5f5f5; }
.container { max-width: 1200px; margin: 0 auto; padding: 20px; }
.header { background: #2c3e50; color: white; padding: 20px; border-radius: 4px; margin-bottom: 20px; }
.header h1 { font-size: 24px; margin-bottom: 10px; }
.stats { display: grid; grid-template-columns: repeat(4, 1fr); gap: 10px; margin-bottom: 20px; }
.stat { background: white; padding: 15px; border-radius: 4px; box-shadow: 0 1px 3px rgba(0,0,0,0.1); }
.stat-value { font-size: 24px; font-weight: bold; color: #2c3e50; }
.stat-label { font-size: 12px; color: #666; margin-top: 5px; }
.section { background: white; padding: 20px; border-radius: 4px; box-shadow: 0 1px 3px rgba(0,0,0,0.1); margin-bottom: 20px; }
.section h2 { font-size: 18px; margin-bottom: 15px; border-bottom: 2px solid #2c3e50; padding-bottom: 10px; }
.form-group { margin-bottom: 15px; }
.form-group label { display: block; margin-bottom: 5px; font-weight: bold; font-size: 14px; }
.form-group input, .form-group select { width: 100%; padding: 8px; border: 1px solid #ddd; border-radius: 4px; font-size: 14px; }
.form-group button { background: #3498db; color: white; padding: 8px 16px; border: none; border-radius: 4px; cursor: pointer; font-weight: bold; }
.form-group button:hover { background: #2980b9; }
table { width: 100%; border-collapse: collapse; margin-top: 15px; font-size: 13px; }
th, td { padding: 12px; text-align: left; border-bottom: 1px solid #ddd; }
th { background: #f5f5f5; font-weight: bold; }
tr:hover { background: #f9f9f9; }
.badge { padding: 4px 8px; border-radius: 3px; font-size: 12px; font-weight: bold; display: inline-block; }
.badge-active { background: #d4edda; color: #155724; }
.badge-inactive { background: #f8d7da; color: #721c24; }
.badge-used { background: #cfe2ff; color: #084298; }
.badge-unused { background: #e2e3e5; color: #383d41; }
.action-btn { padding: 4px 8px; margin: 0 2px; font-size: 12px; cursor: pointer; border: none; border-radius: 3px; color: white; font-weight: bold; }
.btn-disable { background: #ff7675; }
.btn-enable { background: #00b894; }
.btn-delete { background: #d63031; }
.btn-copy { background: #0984e3; }
.form-inline { display: grid; grid-template-columns: 1fr 100px 100px 100px 100px; gap: 10px; align-items: flex-end; }
.message { padding: 12px; border-radius: 4px; margin-bottom: 15px; display: none; font-weight: bold; }
.message.show { display: block; }
.message.success { background: #d4edda; color: #155724; border: 1px solid #c3e6cb; }
.message.error { background: #f8d7da; color: #721c24; border: 1px solid #f5c6cb; }
code { background: #f0f0f0; padding: 2px 6px; border-radius: 3px; font-family: monospace; }
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>🔐 激活码管理后台 (开发版)</h1>
<p>本地激活服务器 - http://localhost:3001</p>
</div>
<div class="stats" id="stats">
<div class="stat"><div class="stat-value">0</div><div class="stat-label">总激活码数</div></div>
<div class="stat"><div class="stat-value">0</div><div class="stat-label">未使用</div></div>
<div class="stat"><div class="stat-value">0</div><div class="stat-label">已激活</div></div>
<div class="stat"><div class="stat-value">0</div><div class="stat-label">已停用</div></div>
</div>
<div class="section">
<h2>生成激活码</h2>
<div id="message" class="message"></div>
<div class="form-inline">
<div>
<label>过期类型:</label>
<select id="type" style="width: 100%;">
<option value="minute">分钟</option>
<option value="hour">小时</option>
<option value="day" selected></option>
<option value="month"></option>
<option value="year"></option>
</select>
</div>
<div style="grid-column: 2">
<label>数量:</label>
<input type="number" id="duration" value="1" min="1" style="width: 100%;">
</div>
<button onclick="generateCode()" style="width: 100%; padding: 8px; grid-column: 3">生成</button>
<button onclick="refreshList()" style="width: 100%; padding: 8px; background: #95a5a6; grid-column: 4">刷新</button>
<button onclick="location.reload()" style="width: 100%; padding: 8px; background: #7f8c8d; grid-column: 5">重载</button>
</div>
</div>
<div class="section">
<h2>激活码列表</h2>
<table id="codeTable">
<thead>
<tr>
<th>激活码</th>
<th>类型</th>
<th>过期时间</th>
<th>状态</th>
<th>激活</th>
<th>生成时间</th>
<th>最后启动时间</th>
<th>操作</th>
</tr>
</thead>
<tbody id="codeList">
<tr><td colspan="8" style="text-align: center; color: #999;">暂无激活码</td></tr>
</tbody>
</table>
</div>
</div>
<script>
const API_URL = 'http://localhost:3001/api'
async function generateCode() {
const type = document.getElementById('type').value
const duration = parseInt(document.getElementById('duration').value)
if (duration < 1) {
showMessage('数量必须大于0', 'error')
return
}
try {
const response = await fetch(`${API_URL}/codes/generate`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ type, duration })
})
const data = await response.json()
if (data.success) {
showMessage(`✅ 激活码已生成: ${data.code}`, 'success')
document.getElementById('duration').value = '1'
setTimeout(refreshList, 500)
} else {
showMessage(`❌ 生成失败: ${data.error}`, 'error')
}
} catch (error) {
showMessage(`❌ 错误: ${error.message}`, 'error')
}
}
async function refreshList() {
try {
const response = await fetch(`${API_URL}/codes/list`)
const data = await response.json()
if (data.success) {
const tbody = document.getElementById('codeList')
tbody.innerHTML = ''
if (data.codes.length === 0) {
tbody.innerHTML = '<tr><td colspan="8" style="text-align: center; color: #999;">暂无激活码</td></tr>'
return
}
data.codes.forEach(code => {
const row = document.createElement('tr')
const expiresAt = new Date(code.expires_at)
const now = new Date()
const isExpired = now > expiresAt
const statusBadge = code.is_active ?
'<span class="badge badge-active">✅ 可用</span>' :
'<span class="badge badge-inactive">❌ 已停用</span>'
const usedBadge = code.is_used ?
'<span class="badge badge-used">已激活</span>' :
'<span class="badge badge-unused">未使用</span>'
const typeLabel = { minute: '分钟', hour: '小时', day: '天', month: '月', year: '年' }[code.type] || code.type
const timeDisplay = isExpired ? `<span style="color: #d63031;">已过期</span>` : `<span style="color: #00b894;">${expiresAt.toLocaleString()}</span>`
// 最后启动时间
const lastCheckinDisplay = code.last_checkin ?
`<span style="color: #3498db; font-size: 12px;">${new Date(code.last_checkin).toLocaleString()}<br/>(${code.last_checkin_device || 'unknown'})</span>` :
'<span style="color: #999;">未启动</span>'
row.innerHTML = `
<td><code>${code.code}</code></td>
<td>${typeLabel} (${code.duration})</td>
<td>${timeDisplay}</td>
<td>${statusBadge}</td>
<td>${usedBadge}</td>
<td>${new Date(code.created_at).toLocaleString()}</td>
<td>${lastCheckinDisplay}</td>
<td>
<button class="action-btn btn-copy" onclick="copyCode('${code.code}')">复制</button>
${code.is_active ?
`<button class="action-btn btn-disable" onclick="disableCode('${code.code}')">停用</button>` :
`<button class="action-btn btn-enable" onclick="enableCode('${code.code}')">启用</button>`
}
<button class="action-btn btn-delete" onclick="deleteCode('${code.code}')">删除</button>
</td>
`
tbody.appendChild(row)
})
updateStats()
}
} catch (error) {
showMessage(`❌ 获取列表失败: ${error.message}`, 'error')
}
}
async function disableCode(code) {
try {
const response = await fetch(`${API_URL}/codes/disable`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ code })
})
const data = await response.json()
if (data.success) {
showMessage(`✅ 激活码 ${code} 已停用`, 'success')
refreshList()
} else {
showMessage(`❌ 停用失败: ${data.error}`, 'error')
}
} catch (error) {
showMessage(`❌ 错误: ${error.message}`, 'error')
}
}
async function enableCode(code) {
try {
const response = await fetch(`${API_URL}/codes/enable`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ code })
})
const data = await response.json()
if (data.success) {
showMessage(`✅ 激活码 ${code} 已启用`, 'success')
refreshList()
} else {
showMessage(`❌ 启用失败: ${data.error}`, 'error')
}
} catch (error) {
showMessage(`❌ 错误: ${error.message}`, 'error')
}
}
async function deleteCode(code) {
if (!confirm(`确定要删除激活码 ${code} 吗?`)) return
try {
const response = await fetch(`${API_URL}/codes/delete`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ code })
})
const data = await response.json()
if (data.success) {
showMessage(`✅ 激活码已删除`, 'success')
refreshList()
} else {
showMessage(`❌ 删除失败: ${data.error}`, 'error')
}
} catch (error) {
showMessage(`❌ 错误: ${error.message}`, 'error')
}
}
function copyCode(code) {
navigator.clipboard.writeText(code)
showMessage(`✅ 已复制: ${code}`, 'success')
}
async function updateStats() {
try {
const response = await fetch(`${API_URL}/codes/stats`)
const data = await response.json()
if (data.success) {
const stats = data.stats
const statsDiv = document.getElementById('stats')
statsDiv.innerHTML = `
<div class="stat">
<div class="stat-value">${stats.total || 0}</div>
<div class="stat-label">总激活码数</div>
</div>
<div class="stat">
<div class="stat-value">${stats.unused || 0}</div>
<div class="stat-label">未使用</div>
</div>
<div class="stat">
<div class="stat-value">${stats.used || 0}</div>
<div class="stat-label">已激活</div>
</div>
<div class="stat">
<div class="stat-value">${stats.disabled || 0}</div>
<div class="stat-label">已停用</div>
</div>
`
}
} catch (error) {
console.error('更新统计信息失败:', error)
}
}
function showMessage(message, type) {
const messageEl = document.getElementById('message')
messageEl.textContent = message
messageEl.className = `message show ${type}`
setTimeout(() => {
messageEl.classList.remove('show')
}, 3000)
}
// 页面加载时刷新列表
window.addEventListener('load', () => {
refreshList()
})
// 每 3 秒自动刷新一次列表(方便观察过期)
setInterval(refreshList, 3000)
</script>
</body>
</html>
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

+313
View File
@@ -0,0 +1,313 @@
const express = require('express')
const cors = require('cors')
const sqlite3 = require('sqlite3').verbose()
const path = require('path')
const app = express()
app.use(cors())
app.use(express.json())
app.use(express.static(path.join(__dirname, 'public')))
// SQLite 数据库初始化
const db = new sqlite3.Database(path.join(__dirname, 'activation.db'), (err) => {
if (err) console.error('❌ 数据库连接失败:', err)
else console.log('✅ SQLite 数据库已连接')
})
// 创建表
db.serialize(() => {
db.run(`CREATE TABLE IF NOT EXISTS activation_codes (
id INTEGER PRIMARY KEY AUTOINCREMENT,
code TEXT UNIQUE NOT NULL,
type TEXT NOT NULL,
duration INTEGER NOT NULL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
expires_at DATETIME,
is_used BOOLEAN DEFAULT 0,
is_active BOOLEAN DEFAULT 1,
activated_at DATETIME,
device_fingerprint TEXT,
last_checkin DATETIME,
last_checkin_device TEXT
)`)
db.run(`CREATE TABLE IF NOT EXISTS activation_logs (
id INTEGER PRIMARY KEY AUTOINCREMENT,
code TEXT NOT NULL,
action TEXT NOT NULL,
message TEXT,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
)`)
// 尝试添加新的列(如果表已存在且列不存在)
db.run(`ALTER TABLE activation_codes ADD COLUMN last_checkin DATETIME`, (err) => {
if (err && !err.message.includes('duplicate column name')) {
console.warn('⚠️ 添加 last_checkin 列失败:', err.message)
}
})
db.run(`ALTER TABLE activation_codes ADD COLUMN last_checkin_device TEXT`, (err) => {
if (err && !err.message.includes('duplicate column name')) {
console.warn('⚠️ 添加 last_checkin_device 列失败:', err.message)
}
})
})
// 生成激活码
function generateCode() {
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'
let code = ''
for (let i = 0; i < 16; i++) {
if (i > 0 && i % 4 === 0) code += '-'
code += chars.charAt(Math.floor(Math.random() * chars.length))
}
return code
}
// 计算过期时间
function calculateExpiresAt(type, duration) {
const now = new Date()
let expiresAt = new Date(now)
if (type === 'minute') {
expiresAt.setMinutes(expiresAt.getMinutes() + duration)
} else if (type === 'hour') {
expiresAt.setHours(expiresAt.getHours() + duration)
} else if (type === 'day') {
expiresAt.setDate(expiresAt.getDate() + duration)
} else if (type === 'month') {
expiresAt.setMonth(expiresAt.getMonth() + duration)
} else if (type === 'year') {
expiresAt.setFullYear(expiresAt.getFullYear() + duration)
}
return expiresAt.toISOString()
}
// API: 生成激活码
app.post('/api/codes/generate', (req, res) => {
const { type = 'day', duration = 7 } = req.body
const code = generateCode()
const expiresAt = calculateExpiresAt(type, duration)
db.run(
'INSERT INTO activation_codes (code, type, duration, expires_at) VALUES (?, ?, ?, ?)',
[code, type, duration, expiresAt],
(err) => {
if (err) {
return res.json({ success: false, error: err.message })
}
res.json({
success: true,
code: code,
type: type,
duration: duration,
expiresAt: expiresAt
})
}
)
})
// API: 激活激活码
app.post('/api/activate', (req, res) => {
const { code, device_fingerprint } = req.body
db.get('SELECT * FROM activation_codes WHERE code = ?', [code], (err, row) => {
if (err || !row) {
db.run('INSERT INTO activation_logs (code, action, message) VALUES (?, ?, ?)',
[code, 'activate_failed', '激活码不存在'])
return res.json({ success: false, message: '激活码不存在' })
}
if (!row.is_active) {
return res.json({ success: false, message: '激活码已停用' })
}
const now = new Date()
const expiresAt = new Date(row.expires_at)
if (now > expiresAt) {
return res.json({ success: false, message: '激活码已过期' })
}
if (row.is_used) {
return res.json({ success: false, message: '激活码已被使用' })
}
// 标记为已使用
db.run(
'UPDATE activation_codes SET is_used = 1, activated_at = ?, device_fingerprint = ? WHERE code = ?',
[new Date().toISOString(), device_fingerprint, code],
(err) => {
if (err) {
return res.json({ success: false, error: err.message })
}
db.run('INSERT INTO activation_logs (code, action, message) VALUES (?, ?, ?)',
[code, 'activate_success', '激活成功'])
res.json({
success: true,
code: code,
type: row.type,
duration: row.duration,
activatedAt: new Date().toISOString(),
expiresAt: row.expires_at
})
}
)
})
})
// API: 验证激活码
app.post('/api/validate', (req, res) => {
const { code, device_fingerprint } = req.body
db.get('SELECT * FROM activation_codes WHERE code = ?', [code], (err, row) => {
if (err || !row) {
return res.json({ valid: false, message: '激活码不存在' })
}
if (!row.is_active) {
return res.json({ valid: false, message: '激活码已停用' })
}
const now = new Date()
const expiresAt = new Date(row.expires_at)
if (now > expiresAt) {
return res.json({
valid: false,
message: '激活码已过期',
isExpired: true
})
}
if (!row.is_used) {
return res.json({ valid: false, message: '激活码未被激活' })
}
const remainingMs = expiresAt - now
const remainingDays = Math.ceil(remainingMs / (1000 * 60 * 60 * 24))
const remainingHours = Math.ceil(remainingMs / (1000 * 60 * 60))
const remainingMinutes = Math.ceil(remainingMs / (1000 * 60))
res.json({
valid: true,
code: code,
type: row.type,
duration: row.duration,
activatedAt: row.activated_at,
expiresAt: row.expires_at,
remainingMs: remainingMs,
remainingDays: remainingDays,
remainingHours: remainingHours,
remainingMinutes: remainingMinutes,
isExpired: false
})
})
})
// API: 获取所有激活码
app.get('/api/codes/list', (req, res) => {
db.all('SELECT * FROM activation_codes ORDER BY created_at DESC', [], (err, rows) => {
if (err) {
return res.json({ success: false, error: err.message })
}
res.json({ success: true, codes: rows })
})
})
// API: 停用激活码
app.post('/api/codes/disable', (req, res) => {
const { code } = req.body
db.run('UPDATE activation_codes SET is_active = 0 WHERE code = ?', [code], (err) => {
if (err) {
return res.json({ success: false, error: err.message })
}
db.run('INSERT INTO activation_logs (code, action, message) VALUES (?, ?, ?)',
[code, 'disable', '激活码已停用'])
res.json({ success: true, message: '激活码已停用' })
})
})
// API: 启用激活码
app.post('/api/codes/enable', (req, res) => {
const { code } = req.body
db.run('UPDATE activation_codes SET is_active = 1 WHERE code = ?', [code], (err) => {
if (err) {
return res.json({ success: false, error: err.message })
}
db.run('INSERT INTO activation_logs (code, action, message) VALUES (?, ?, ?)',
[code, 'enable', '激活码已启用'])
res.json({ success: true, message: '激活码已启用' })
})
})
// API: 删除激活码
app.post('/api/codes/delete', (req, res) => {
const { code } = req.body
db.run('DELETE FROM activation_codes WHERE code = ?', [code], (err) => {
if (err) {
return res.json({ success: false, error: err.message })
}
res.json({ success: true, message: '激活码已删除' })
})
})
// API: 设备签到(记录最后启动时间)
app.post('/api/device/checkin', (req, res) => {
const { code, device_fingerprint } = req.body
db.run(
'UPDATE activation_codes SET last_checkin = ?, last_checkin_device = ? WHERE code = ?',
[new Date().toISOString(), device_fingerprint || 'unknown', code],
(err) => {
if (err) {
console.error('❌ 设备签到失败:', err)
return res.json({ success: false, error: err.message })
}
db.run('INSERT INTO activation_logs (code, action, message) VALUES (?, ?, ?)',
[code, 'device_checkin', `设备 ${device_fingerprint} 启动`])
res.json({ success: true, message: '设备签到成功' })
}
)
})
// API: 获取统计信息
app.get('/api/codes/stats', (req, res) => {
db.all(`
SELECT
COUNT(*) as total,
SUM(CASE WHEN is_used = 0 THEN 1 ELSE 0 END) as unused,
SUM(CASE WHEN is_used = 1 THEN 1 ELSE 0 END) as used,
SUM(CASE WHEN is_active = 0 THEN 1 ELSE 0 END) as disabled
FROM activation_codes
`, [], (err, rows) => {
if (err) {
return res.json({ success: false, error: err.message })
}
res.json({ success: true, stats: rows[0] })
})
})
// Web 管理后台路由
app.get('/admin', (req, res) => {
res.sendFile(path.join(__dirname, 'public', 'admin.html'))
})
const PORT = 3002
app.listen(PORT, () => {
console.log(`\n✅ 激活码服务器已启动`)
console.log(`📋 管理后台: http://localhost:${PORT}/admin`)
console.log(`📡 API 地址: http://localhost:${PORT}/api\n`)
})