docs: 更新项目文档,完善需求和技术细节
This commit is contained in:
265
backend/routes/admin.js
Normal file
265
backend/routes/admin.js
Normal file
@@ -0,0 +1,265 @@
|
||||
const express = require('express');
|
||||
const dbConnector = require('../utils/dbConnector');
|
||||
const { adminRequired } = require('../middlewares/auth');
|
||||
const { asyncHandler } = require('../middlewares/errorHandler');
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
/**
|
||||
* 获取系统统计概览
|
||||
*/
|
||||
router.get('/statistics/overview', adminRequired, asyncHandler(async (req, res) => {
|
||||
// 获取总用户数
|
||||
const totalUsersResult = await dbConnector.query(
|
||||
'SELECT COUNT(*) as total FROM users WHERE status = 1'
|
||||
);
|
||||
|
||||
// 获取今日新增用户数
|
||||
const newUsersTodayResult = await dbConnector.query(
|
||||
'SELECT COUNT(*) as total FROM users WHERE status = 1 AND DATE(created_at) = CURDATE()'
|
||||
);
|
||||
|
||||
// 获取总订单数
|
||||
const totalOrdersResult = await dbConnector.query(
|
||||
'SELECT COUNT(*) as total FROM orders'
|
||||
);
|
||||
|
||||
// 获取总收入
|
||||
const totalRevenueResult = await dbConnector.query(
|
||||
'SELECT COALESCE(SUM(total_amount), 0) as total FROM orders WHERE payment_status = 1'
|
||||
);
|
||||
|
||||
// 获取总识别次数
|
||||
const totalIdentificationsResult = await dbConnector.query(
|
||||
'SELECT COUNT(*) as total FROM identifications'
|
||||
);
|
||||
|
||||
// 获取今日活跃用户数
|
||||
const activeUsersTodayResult = await dbConnector.query(
|
||||
`SELECT COUNT(DISTINCT user_id) as total FROM (
|
||||
SELECT user_id FROM orders WHERE DATE(created_at) = CURDATE()
|
||||
UNION
|
||||
SELECT user_id FROM identifications WHERE DATE(created_at) = CURDATE()
|
||||
) as active_users`
|
||||
);
|
||||
|
||||
res.json({
|
||||
code: 200,
|
||||
message: '获取成功',
|
||||
data: {
|
||||
total_users: totalUsersResult[0].total,
|
||||
new_users_today: newUsersTodayResult[0].total,
|
||||
total_orders: totalOrdersResult[0].total,
|
||||
total_revenue: parseFloat(totalRevenueResult[0].total),
|
||||
total_identifications: totalIdentificationsResult[0].total,
|
||||
active_users_today: activeUsersTodayResult[0].total
|
||||
}
|
||||
});
|
||||
}));
|
||||
|
||||
/**
|
||||
* 获取趋势数据
|
||||
*/
|
||||
router.get('/statistics/trend', adminRequired, asyncHandler(async (req, res) => {
|
||||
const { days = 7, type = 'users' } = req.query;
|
||||
const dayCount = parseInt(days);
|
||||
|
||||
let query = '';
|
||||
let dataKey = '';
|
||||
|
||||
switch (type) {
|
||||
case 'users':
|
||||
query = `
|
||||
SELECT DATE(created_at) as date, COUNT(*) as count
|
||||
FROM users
|
||||
WHERE created_at >= DATE_SUB(CURDATE(), INTERVAL ? DAY)
|
||||
GROUP BY DATE(created_at)
|
||||
ORDER BY date
|
||||
`;
|
||||
dataKey = 'new_users';
|
||||
break;
|
||||
case 'orders':
|
||||
query = `
|
||||
SELECT DATE(created_at) as date, COUNT(*) as count
|
||||
FROM orders
|
||||
WHERE created_at >= DATE_SUB(CURDATE(), INTERVAL ? DAY)
|
||||
GROUP BY DATE(created_at)
|
||||
ORDER BY date
|
||||
`;
|
||||
dataKey = 'new_orders';
|
||||
break;
|
||||
case 'revenue':
|
||||
query = `
|
||||
SELECT DATE(created_at) as date, COALESCE(SUM(total_amount), 0) as amount
|
||||
FROM orders
|
||||
WHERE payment_status = 1 AND created_at >= DATE_SUB(CURDATE(), INTERVAL ? DAY)
|
||||
GROUP BY DATE(created_at)
|
||||
ORDER BY date
|
||||
`;
|
||||
dataKey = 'revenue';
|
||||
break;
|
||||
case 'identifications':
|
||||
query = `
|
||||
SELECT DATE(created_at) as date, COUNT(*) as count
|
||||
FROM identifications
|
||||
WHERE created_at >= DATE_SUB(CURDATE(), INTERVAL ? DAY)
|
||||
GROUP BY DATE(created_at)
|
||||
ORDER BY date
|
||||
`;
|
||||
dataKey = 'identifications';
|
||||
break;
|
||||
default:
|
||||
return res.status(400).json({
|
||||
code: 400,
|
||||
message: '不支持的统计类型',
|
||||
data: null
|
||||
});
|
||||
}
|
||||
|
||||
const trendData = await dbConnector.query(query, [dayCount]);
|
||||
|
||||
res.json({
|
||||
code: 200,
|
||||
message: '获取成功',
|
||||
data: {
|
||||
type,
|
||||
days: dayCount,
|
||||
trend: trendData,
|
||||
total: trendData.reduce((sum, item) => sum + (item.count || item.amount || 0), 0)
|
||||
}
|
||||
});
|
||||
}));
|
||||
|
||||
/**
|
||||
* 获取系统配置列表
|
||||
*/
|
||||
router.get('/config', adminRequired, asyncHandler(async (req, res) => {
|
||||
const configs = await dbConnector.query(
|
||||
'SELECT config_key, config_value, config_type, description FROM system_config ORDER BY config_key'
|
||||
);
|
||||
|
||||
res.json({
|
||||
code: 200,
|
||||
message: '获取成功',
|
||||
data: configs
|
||||
});
|
||||
}));
|
||||
|
||||
/**
|
||||
* 获取单个系统配置
|
||||
*/
|
||||
router.get('/config/:key', adminRequired, asyncHandler(async (req, res) => {
|
||||
const { key } = req.params;
|
||||
|
||||
const configs = await dbConnector.query(
|
||||
'SELECT config_key, config_value, config_type, description FROM system_config WHERE config_key = ?',
|
||||
[key]
|
||||
);
|
||||
|
||||
if (configs.length === 0) {
|
||||
return res.status(404).json({
|
||||
code: 404,
|
||||
message: '配置项不存在',
|
||||
data: null
|
||||
});
|
||||
}
|
||||
|
||||
res.json({
|
||||
code: 200,
|
||||
message: '获取成功',
|
||||
data: configs[0]
|
||||
});
|
||||
}));
|
||||
|
||||
/**
|
||||
* 更新系统配置
|
||||
*/
|
||||
router.put('/config/:key', adminRequired, asyncHandler(async (req, res) => {
|
||||
const { key } = req.params;
|
||||
const { value } = req.body;
|
||||
|
||||
if (value === undefined) {
|
||||
return res.status(400).json({
|
||||
code: 400,
|
||||
message: '配置值不能为空',
|
||||
data: null
|
||||
});
|
||||
}
|
||||
|
||||
const result = await dbConnector.query(
|
||||
'UPDATE system_config SET config_value = ?, updated_at = CURRENT_TIMESTAMP WHERE config_key = ?',
|
||||
[value, key]
|
||||
);
|
||||
|
||||
if (result.affectedRows === 0) {
|
||||
return res.status(404).json({
|
||||
code: 404,
|
||||
message: '配置项不存在',
|
||||
data: null
|
||||
});
|
||||
}
|
||||
|
||||
res.json({
|
||||
code: 200,
|
||||
message: '更新成功',
|
||||
data: null
|
||||
});
|
||||
}));
|
||||
|
||||
/**
|
||||
* 获取操作日志
|
||||
*/
|
||||
router.get('/logs', adminRequired, asyncHandler(async (req, res) => {
|
||||
const { page = 1, limit = 20, module, action, username } = req.query;
|
||||
const offset = (page - 1) * limit;
|
||||
|
||||
let whereClause = 'WHERE 1=1';
|
||||
let queryParams = [];
|
||||
|
||||
if (module) {
|
||||
whereClause += ' AND module = ?';
|
||||
queryParams.push(module);
|
||||
}
|
||||
|
||||
if (action) {
|
||||
whereClause += ' AND action = ?';
|
||||
queryParams.push(action);
|
||||
}
|
||||
|
||||
if (username) {
|
||||
whereClause += ' AND username LIKE ?';
|
||||
queryParams.push(`%${username}%`);
|
||||
}
|
||||
|
||||
// 获取日志列表
|
||||
const logs = await dbConnector.query(
|
||||
`SELECT id, user_id, username, user_type, module, action, target_id,
|
||||
description, ip_address, user_agent, created_at
|
||||
FROM operation_logs ${whereClause}
|
||||
ORDER BY created_at DESC
|
||||
LIMIT ? OFFSET ?`,
|
||||
[...queryParams, parseInt(limit), offset]
|
||||
);
|
||||
|
||||
// 获取总数
|
||||
const totalResult = await dbConnector.query(
|
||||
`SELECT COUNT(*) as total FROM operation_logs ${whereClause}`,
|
||||
queryParams
|
||||
);
|
||||
|
||||
res.json({
|
||||
code: 200,
|
||||
message: '获取成功',
|
||||
data: {
|
||||
logs,
|
||||
pagination: {
|
||||
page: parseInt(page),
|
||||
limit: parseInt(limit),
|
||||
total: totalResult[0].total,
|
||||
pages: Math.ceil(totalResult[0].total / limit)
|
||||
}
|
||||
}
|
||||
});
|
||||
}));
|
||||
|
||||
module.exports = router;
|
||||
@@ -66,7 +66,7 @@ router.post('/register', async (req, res, next) => {
|
||||
|
||||
// 创建用户
|
||||
const result = await dbConnector.query(
|
||||
'INSERT INTO users (username, password_hash, phone, email, user_type) VALUES (?, ?, ?, ?, ?)',
|
||||
'INSERT INTO users (username, password, phone, email, user_type) VALUES (?, ?, ?, ?, ?)',
|
||||
[username, hashedPassword, phone, email, user_type]
|
||||
);
|
||||
|
||||
@@ -127,7 +127,7 @@ router.post('/login', async (req, res, next) => {
|
||||
const userData = user[0];
|
||||
|
||||
// 验证密码
|
||||
const isValidPassword = await bcrypt.compare(password, userData.password_hash);
|
||||
const isValidPassword = await bcrypt.compare(password, userData.password);
|
||||
if (!isValidPassword) {
|
||||
return res.status(401).json({
|
||||
code: 401,
|
||||
|
||||
Reference in New Issue
Block a user