222 lines
5.9 KiB
TypeScript
222 lines
5.9 KiB
TypeScript
import { Request, Response } from 'express';
|
|
import bcrypt from 'bcryptjs';
|
|
import jwt from 'jsonwebtoken';
|
|
import { prisma } from '../config/database';
|
|
import { logger } from '../utils/logger';
|
|
import { createAuditLog } from '../utils/audit';
|
|
|
|
export const adminController = {
|
|
// 管理员登录
|
|
async login(req: Request, res: Response): Promise<void> {
|
|
try {
|
|
const { username, password } = req.body;
|
|
|
|
// 验证管理员凭据
|
|
const admin = await prisma.user.findFirst({
|
|
where: {
|
|
username,
|
|
isAdmin: true
|
|
}
|
|
});
|
|
|
|
if (!admin) {
|
|
// 记录管理员登录失败审计日志
|
|
await createAuditLog({
|
|
userId: null,
|
|
action: 'ADMIN_LOGIN_FAILED',
|
|
resource: 'ADMIN',
|
|
details: JSON.stringify({ username, reason: '管理员不存在' }),
|
|
ipAddress: req.ip ?? null,
|
|
userAgent: req.get('User-Agent') ?? null
|
|
});
|
|
|
|
res.status(401).json({
|
|
success: false,
|
|
message: '管理员凭据无效'
|
|
});
|
|
return;
|
|
}
|
|
|
|
// 验证密码
|
|
const isValidPassword = await bcrypt.compare(password, admin.password);
|
|
if (!isValidPassword) {
|
|
// 记录管理员登录失败审计日志
|
|
await createAuditLog({
|
|
userId: admin.id,
|
|
action: 'ADMIN_LOGIN_FAILED',
|
|
resource: 'ADMIN',
|
|
details: JSON.stringify({ username, reason: '密码错误' }),
|
|
ipAddress: req.ip ?? null,
|
|
userAgent: req.get('User-Agent') ?? null
|
|
});
|
|
|
|
res.status(401).json({
|
|
success: false,
|
|
message: '管理员凭据无效'
|
|
});
|
|
return;
|
|
}
|
|
|
|
// 生成JWT token
|
|
const token = jwt.sign(
|
|
{
|
|
userId: admin.id,
|
|
username: admin.username,
|
|
role: admin.isAdmin ? 'admin' : 'user'
|
|
},
|
|
"pandora",
|
|
{ expiresIn: '24h' }
|
|
);
|
|
|
|
// 创建审计日志
|
|
await createAuditLog({
|
|
userId: admin.id,
|
|
action: 'ADMIN_LOGIN',
|
|
resource: 'ADMIN',
|
|
details: { username: admin.username },
|
|
ipAddress: req.ip ?? null,
|
|
userAgent: req.get('User-Agent') ?? null
|
|
});
|
|
|
|
res.json({
|
|
success: true,
|
|
message: '管理员登录成功',
|
|
token,
|
|
admin: {
|
|
id: admin.id,
|
|
username: admin.username,
|
|
role: admin.isAdmin ? 'admin' : 'user'
|
|
}
|
|
});
|
|
} catch (error) {
|
|
logger.error('Admin login error:', error);
|
|
res.status(500).json({
|
|
success: false,
|
|
message: '服务器内部错误'
|
|
});
|
|
}
|
|
},
|
|
|
|
// 获取统计数据
|
|
async getStats(req: Request, res: Response): Promise<void> {
|
|
try {
|
|
// 获取用户总数
|
|
const totalUsers = await prisma.user.count({
|
|
where: { isAdmin: false }
|
|
});
|
|
|
|
// 获取账号总数
|
|
const totalAccounts = await prisma.websiteAccount.count();
|
|
|
|
// 获取今日访问数(基于会话)
|
|
const today = new Date();
|
|
today.setHours(0, 0, 0, 0);
|
|
const todayVisits = await prisma.session.count({
|
|
where: {
|
|
createdAt: {
|
|
gte: today
|
|
}
|
|
}
|
|
});
|
|
|
|
// 获取系统告警数(基于审计日志中的错误)
|
|
const alerts = await prisma.auditLog.count({
|
|
where: {
|
|
action: { contains: 'ERROR' },
|
|
createdAt: {
|
|
gte: new Date(Date.now() - 24 * 60 * 60 * 1000) // 最近24小时
|
|
}
|
|
}
|
|
});
|
|
|
|
console.log('统计数据:', { totalUsers, totalAccounts, todayVisits, alerts });
|
|
|
|
res.json({
|
|
success: true,
|
|
data: {
|
|
totalUsers,
|
|
totalAccounts,
|
|
todayVisits,
|
|
alerts
|
|
}
|
|
});
|
|
} catch (error) {
|
|
logger.error('Get stats error:', error);
|
|
res.status(500).json({
|
|
success: false,
|
|
message: '获取统计数据失败'
|
|
});
|
|
}
|
|
},
|
|
|
|
// 获取最近活动
|
|
async getRecentActivities(req: Request, res: Response): Promise<void> {
|
|
try {
|
|
const activities = await prisma.auditLog.findMany({
|
|
take: 10,
|
|
orderBy: {
|
|
createdAt: 'desc'
|
|
},
|
|
include: {
|
|
user: {
|
|
select: {
|
|
id: true,
|
|
username: true
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
const formattedActivities = activities.map((activity: any) => {
|
|
let description = '';
|
|
|
|
if (activity.action === 'USER_LOGIN_FAILED' || activity.action === 'ADMIN_LOGIN_FAILED') {
|
|
// 解析失败原因
|
|
let reason = '登录失败';
|
|
let username = '未知用户';
|
|
|
|
if (activity.details) {
|
|
try {
|
|
const details = JSON.parse(activity.details);
|
|
reason = details.reason || '登录失败';
|
|
username = details.username || '未知用户';
|
|
} catch (e) {
|
|
console.error('解析活动详情失败:', e);
|
|
}
|
|
}
|
|
|
|
description = `${username} ${reason}`;
|
|
} else if (activity.user) {
|
|
description = `${activity.user.username} ${activity.action}`;
|
|
} else {
|
|
description = `系统 ${activity.action}`;
|
|
}
|
|
|
|
return {
|
|
id: activity.id,
|
|
description,
|
|
time: activity.createdAt,
|
|
details: activity.details,
|
|
ipAddress: activity.ipAddress,
|
|
userAgent: activity.userAgent,
|
|
action: activity.action
|
|
};
|
|
});
|
|
|
|
console.log('最近活动:', formattedActivities);
|
|
|
|
res.json({
|
|
success: true,
|
|
data: {
|
|
activities: formattedActivities
|
|
}
|
|
});
|
|
} catch (error) {
|
|
logger.error('Get recent activities error:', error);
|
|
res.status(500).json({
|
|
success: false,
|
|
message: '获取最近活动失败'
|
|
});
|
|
}
|
|
}
|
|
};
|