314 lines
9.1 KiB
JavaScript
314 lines
9.1 KiB
JavaScript
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`)
|
|
})
|