Initial commit

This commit is contained in:
2026-02-06 14:29:29 +08:00
commit e98dbcb0f4
11 changed files with 2574 additions and 0 deletions

407
routes/serials.js Normal file
View File

@@ -0,0 +1,407 @@
const express = require('express');
const QRCode = require('qrcode');
const db = require('../utils/database');
const { authenticateToken, requireAdmin } = require('../middleware/auth');
const router = express.Router();
// 生成序列号
router.post('/generate', authenticateToken, requireAdmin, async (req, res) => {
try {
const { companyName, quantity = 1, validDays = 365 } = req.body;
if (!companyName) {
return res.status(400).json({ error: '企业名称不能为空' });
}
if (quantity < 1 || quantity > 100) {
return res.status(400).json({ error: '生成数量必须在1-100之间' });
}
// 计算有效期
const validUntil = new Date();
validUntil.setDate(validUntil.getDate() + validDays);
// 确保企业存在
const existingCompany = await db.get('SELECT * FROM companies WHERE company_name = ?', [companyName]);
if (!existingCompany) {
await db.run('INSERT INTO companies (company_name, is_active) VALUES (?, 1)', [companyName]);
}
// 生成序列号
const serials = [];
const prefix = 'BF';
const datePart = new Date().getFullYear().toString().substr(2);
// 批量插入序列号
const insertPromises = [];
for (let i = 0; i < quantity; i++) {
// 使用随机数生成序列号,避免重复
const randomPart = Math.floor(Math.random() * 1000000).toString().padStart(6, '0');
const serialNumber = `${prefix}${datePart}${randomPart}`;
insertPromises.push(
db.run(
'INSERT INTO serials (serial_number, company_name, valid_until, created_by) VALUES (?, ?, ?, ?)',
[serialNumber, companyName, validUntil.toISOString().slice(0, 19).replace('T', ' '), req.user.id]
)
);
serials.push({
serialNumber,
companyName,
validUntil: validUntil.toISOString(),
createdAt: new Date().toISOString()
});
}
await Promise.all(insertPromises);
res.json({
message: `成功生成${quantity}个序列号`,
serials
});
} catch (error) {
console.error('生成序列号错误:', error);
res.status(500).json({ error: '服务器内部错误' });
}
});
// 生成二维码
router.post('/:serialNumber/qrcode', authenticateToken, async (req, res) => {
try {
const { serialNumber } = req.params;
let { baseUrl } = req.body;
if (!serialNumber) {
return res.status(400).json({ error: '序列号不能为空' });
}
// 验证序列号是否存在
const serial = await db.get(
'SELECT s.*, u.name as created_by_name FROM serials s LEFT JOIN users u ON s.created_by = u.id WHERE s.serial_number = ?',
[serialNumber.toUpperCase()]
);
if (!serial) {
return res.status(404).json({ error: '序列号不存在' });
}
if (!serial.is_active) {
return res.status(400).json({ error: '序列号已被禁用' });
}
// 检查是否过期
if (serial.valid_until && new Date(serial.valid_until) < new Date()) {
return res.status(400).json({ error: '序列号已过期' });
}
// 生成查询URL
if (!baseUrl) {
baseUrl = `${req.protocol}://${req.get('host')}/query.html`;
}
const queryUrl = baseUrl.includes('?')
? `${baseUrl}&serial=${serial.serial_number}`
: `${baseUrl}?serial=${serial.serial_number}`;
// 生成二维码
const qrCodeData = await QRCode.toDataURL(queryUrl, {
width: 200,
color: {
dark: '#165DFF',
light: '#ffffff'
}
});
res.json({
message: '二维码生成成功',
qrCodeData,
queryUrl,
serialNumber: serial.serial_number,
companyName: serial.company_name,
validUntil: serial.valid_until
});
} catch (error) {
console.error('生成二维码错误:', error);
res.status(500).json({ error: '服务器内部错误' });
}
});
// 查询序列号
router.get('/:serialNumber/query', async (req, res) => {
try {
const { serialNumber } = req.params;
if (!serialNumber) {
return res.status(400).json({ error: '序列号不能为空' });
}
// 查询序列号
const serial = await db.get(
'SELECT s.*, u.name as created_by_name FROM serials s LEFT JOIN users u ON s.created_by = u.id WHERE s.serial_number = ?',
[serialNumber.toUpperCase()]
);
if (!serial) {
return res.status(404).json({ error: '序列号不存在' });
}
// 检查是否过期
if (serial.valid_until && new Date(serial.valid_until) < new Date()) {
return res.status(400).json({ error: '序列号已过期' });
}
res.json({
message: '查询成功',
serial: {
serialNumber: serial.serial_number,
companyName: serial.company_name,
validUntil: serial.valid_until,
status: serial.is_active ? 'active' : 'disabled',
isActive: serial.is_active,
createdAt: serial.created_at,
createdBy: serial.created_by_name
}
});
} catch (error) {
console.error('查询序列号错误:', error);
res.status(500).json({ error: '服务器内部错误' });
}
});
// 获取序列号列表
router.get('/', authenticateToken, async (req, res) => {
try {
const { page = 1, limit = 20, search = '' } = req.query;
const offset = (page - 1) * limit;
let query = `
SELECT s.*, u.name as created_by_name
FROM serials s
LEFT JOIN users u ON s.created_by = u.id
`;
let countQuery = 'SELECT COUNT(*) as total FROM serials s';
let params = [];
if (search) {
query += ' WHERE s.serial_number LIKE ? OR s.company_name LIKE ?';
countQuery += ' WHERE s.serial_number LIKE ? OR s.company_name LIKE ?';
const searchParam = `%${search}%`;
params.push(searchParam, searchParam);
}
query += ' ORDER BY s.created_at DESC LIMIT ? OFFSET ?';
params.push(parseInt(limit), parseInt(offset));
const [serials, countResult] = await Promise.all([
db.all(query, params),
db.get(countQuery, params.slice(0, -2))
]);
const total = countResult ? countResult.total : 0;
const totalPages = Math.ceil(total / limit);
res.json({
message: '获取序列号列表成功',
data: serials.map(s => ({
serialNumber: s.serial_number,
companyName: s.company_name,
validUntil: s.valid_until,
isActive: s.is_active,
createdAt: s.created_at,
createdBy: s.created_by_name
})),
pagination: {
page: parseInt(page),
limit: parseInt(limit),
total,
totalPages
}
});
} catch (error) {
console.error('获取序列号列表错误:', error);
res.status(500).json({ error: '服务器内部错误' });
}
});
// 更新序列号
router.patch('/:serialNumber', authenticateToken, requireAdmin, async (req, res) => {
try {
const { serialNumber } = req.params;
const { companyName, validUntil, isActive } = req.body;
if (!serialNumber) {
return res.status(400).json({ error: '序列号不能为空' });
}
// 检查序列号是否存在
const existingSerial = await db.get('SELECT * FROM serials WHERE serial_number = ?', [serialNumber.toUpperCase()]);
if (!existingSerial) {
return res.status(404).json({ error: '序列号不存在' });
}
// 构建更新字段
const updateFields = [];
const params = [];
if (companyName !== undefined) {
updateFields.push('company_name = ?');
params.push(companyName);
}
if (validUntil !== undefined) {
updateFields.push('valid_until = ?');
params.push(validUntil);
}
if (isActive !== undefined) {
updateFields.push('is_active = ?');
params.push(isActive ? 1 : 0);
}
if (updateFields.length === 0) {
return res.status(400).json({ error: '没有提供更新字段' });
}
updateFields.push('updated_at = CURRENT_TIMESTAMP');
params.push(serialNumber.toUpperCase());
await db.run(
`UPDATE serials SET ${updateFields.join(', ')} WHERE serial_number = ?`,
params
);
// 获取更新后的序列号信息
const updatedSerial = await db.get(
'SELECT s.*, u.name as created_by_name FROM serials s LEFT JOIN users u ON s.created_by = u.id WHERE s.serial_number = ?',
[serialNumber.toUpperCase()]
);
res.json({
message: '序列号更新成功',
serial: {
serialNumber: updatedSerial.serial_number,
companyName: updatedSerial.company_name,
validUntil: updatedSerial.valid_until,
isActive: updatedSerial.is_active,
createdAt: updatedSerial.created_at,
updatedAt: updatedSerial.updated_at,
createdBy: updatedSerial.created_by_name
}
});
} catch (error) {
console.error('更新序列号错误:', error);
res.status(500).json({ error: '服务器内部错误' });
}
});
// 吊销序列号
router.post('/:serialNumber/revoke', authenticateToken, requireAdmin, async (req, res) => {
try {
const { serialNumber } = req.params;
if (!serialNumber) {
return res.status(400).json({ error: '序列号不能为空' });
}
// 检查序列号是否存在
const existingSerial = await db.get(
'SELECT * FROM serials WHERE serial_number = ?',
[serialNumber.toUpperCase()]
);
if (!existingSerial) {
return res.status(404).json({ error: '序列号不存在' });
}
// 如果已经吊销,返回提示
if (!existingSerial.is_active) {
return res.status(400).json({ error: '序列号已被吊销' });
}
// 吊销序列号(将 is_active 设为 0
await db.run(
'UPDATE serials SET is_active = 0, updated_at = CURRENT_TIMESTAMP WHERE serial_number = ?',
[serialNumber.toUpperCase()]
);
res.json({
message: '序列号已吊销',
data: {
serialNumber: serialNumber.toUpperCase()
}
});
} catch (error) {
console.error('吊销序列号错误:', error);
res.status(500).json({ error: '服务器内部错误' });
}
});
// 自定义前缀生成序列号(管理员权限)
router.post('/generate-with-prefix', authenticateToken, requireAdmin, async (req, res) => {
try {
const { companyName, quantity = 1, validDays = 365, serialPrefix } = req.body;
if (!companyName) {
return res.status(400).json({ error: '企业名称不能为空' });
}
if (!serialPrefix || serialPrefix.length > 10) {
return res.status(400).json({ error: '自定义前缀不能为空且不能超过10个字符' });
}
if (quantity < 1 || quantity > 100) {
return res.status(400).json({ error: '生成数量必须在1-100之间' });
}
// 计算有效期
const validUntil = new Date();
validUntil.setDate(validUntil.getDate() + validDays);
// 生成序列号
const serials = [];
const prefix = serialPrefix.toUpperCase().replace(/[^A-Z0-9]/g, '');
if (!prefix) {
return res.status(400).json({ error: '自定义前缀包含无效字符,只能包含字母和数字' });
}
// 批量插入序列号
const insertPromises = [];
for (let i = 0; i < quantity; i++) {
// 使用随机数生成序列号,避免重复
const randomPart = Math.floor(Math.random() * 1000000).toString().padStart(6, '0');
const serialNumber = `${prefix}${randomPart}`;
insertPromises.push(
db.run(
'INSERT INTO serials (serial_number, company_name, valid_until, created_by) VALUES (?, ?, ?, ?)',
[serialNumber, companyName, validUntil.toISOString(), req.user.id]
)
);
serials.push({
serialNumber,
companyName,
validUntil: validUntil.toISOString(),
createdAt: new Date().toISOString()
});
}
await Promise.all(insertPromises);
res.json({
message: `成功生成${quantity}个序列号`,
serials
});
} catch (error) {
console.error('生成序列号错误:', error);
res.status(500).json({ error: '服务器内部错误' });
}
});
module.exports = router;