Initial clean project import
This commit is contained in:
@@ -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`)
|
||||
})
|
||||
Reference in New Issue
Block a user