新增系统配置页(运营管理系统后台)

This commit is contained in:
Stev_Wang
2026-01-05 16:04:21 +08:00
parent 0ec73cca94
commit 468d24c3bb
24 changed files with 2966 additions and 26 deletions

View File

@@ -8,6 +8,8 @@ import jwt from 'jsonwebtoken';
import { AppDataSource } from '../config/database';
// 导入管理员用户模型
import { AdminUser } from '../models/AdminUser';
// 导入验证码服务
import { CaptchaService } from '../services/captchaService';
/**
* 管理员认证控制器
@@ -22,8 +24,8 @@ export class AdminAuthController {
*/
async login(req: Request, res: Response) {
try {
// 从请求体中获取用户名密码
const { username, password } = req.body;
// 从请求体中获取用户名密码和验证码信息
const { username, password, captchaId, captchaCode } = req.body;
// 验证用户名和密码是否为空
if (!username || !password) {
@@ -33,6 +35,33 @@ export class AdminAuthController {
});
}
// 创建验证码服务实例
const captchaService = new CaptchaService();
// 检查是否启用了验证码功能
const captchaEnabled = await captchaService.checkCaptchaEnabled();
// 如果启用了验证码,则验证验证码
if (captchaEnabled) {
// 验证验证码参数是否完整
if (!captchaId || !captchaCode) {
return res.status(400).json({
success: false,
message: '请输入验证码'
});
}
// 验证验证码是否正确
const captchaVerify = captchaService.verifyCaptcha(captchaId, captchaCode);
if (!captchaVerify.success) {
return res.status(400).json({
success: false,
message: captchaVerify.message || '验证码错误或已过期'
});
}
}
// 获取管理员用户仓库
const adminUserRepository = AppDataSource.getRepository(AdminUser);
// 根据用户名查找管理员用户

View File

@@ -0,0 +1,89 @@
import { Request, Response } from 'express';
import { CaptchaService } from '../services/captchaService';
/**
* 验证码控制器
* 处理验证码的生成和验证
*/
export class CaptchaController {
private captchaService: CaptchaService;
constructor() {
this.captchaService = new CaptchaService();
}
/**
* 生成验证码
* @param req - Express请求对象
* @param res - Express响应对象
*/
async generateCaptcha(req: Request, res: Response) {
try {
const { captchaId, svg } = this.captchaService.generateCaptcha();
return res.json({
success: true,
data: {
captchaId,
svg
}
});
} catch (error) {
console.error('生成验证码失败:', error);
return res.status(500).json({
success: false,
message: '生成验证码失败'
});
}
}
/**
* 验证验证码
* @param req - Express请求对象
* @param res - Express响应对象
*/
async verifyCaptcha(req: Request, res: Response) {
try {
const { captchaId, code } = req.body;
const result = this.captchaService.verifyCaptcha(captchaId, code);
if (!result.success) {
return res.status(400).json(result);
}
return res.json({
success: true,
message: '验证码正确'
});
} catch (error) {
console.error('验证验证码失败:', error);
return res.status(500).json({
success: false,
message: '服务器内部错误'
});
}
}
/**
* 检查验证码是否启用
* @param req - Express请求对象
* @param res - Express响应对象
*/
async checkCaptchaEnabled(req: Request, res: Response) {
try {
const enabled = await this.captchaService.checkCaptchaEnabled();
return res.json({
success: true,
data: { enabled }
});
} catch (error) {
console.error('检查验证码状态失败:', error);
return res.status(500).json({
success: false,
message: '服务器内部错误'
});
}
}
}

View File

@@ -0,0 +1,261 @@
import { Request, Response } from 'express';
import { AppDataSource } from '../config/database';
import { Config } from '../models/Config';
import { EnvHelper } from '../utils/envHelper';
import nodemailer from 'nodemailer';
/**
* 系统配置控制器
* 处理系统配置相关的操作,包括获取配置、更新配置、测试邮件等
*/
export class ConfigController {
/**
* 获取所有配置
* 按配置类型分组返回
* @param req - Express请求对象
* @param res - Express响应对象
*/
async getAllConfigs(req: Request, res: Response) {
try {
const configRepository = AppDataSource.getRepository(Config);
const configs = await configRepository.find({
order: { configType: 'ASC', id: 'ASC' }
});
const groupedConfigs: Record<string, any[]> = {
basic: [],
security: [],
game: [],
payment: [],
email: []
};
configs.forEach(config => {
if (groupedConfigs[config.configType]) {
groupedConfigs[config.configType].push({
id: config.id,
configKey: config.configKey,
configValue: config.configValue,
description: config.description
});
}
});
return res.json({
success: true,
data: groupedConfigs
});
} catch (error) {
console.error('获取配置失败:', error);
return res.status(500).json({
success: false,
message: '服务器内部错误'
});
}
}
/**
* 更新配置
* 批量更新配置,同时更新数据库和.env文件
* @param req - Express请求对象
* @param res - Express响应对象
*/
async updateConfigs(req: Request, res: Response) {
try {
const { configs } = req.body;
if (!configs || !Array.isArray(configs)) {
return res.status(400).json({
success: false,
message: '配置数据格式错误'
});
}
const configRepository = AppDataSource.getRepository(Config);
const envUpdates: Record<string, string> = {};
for (const config of configs) {
if (!config.configKey || config.configValue === undefined) {
continue;
}
const existingConfig = await configRepository.findOne({
where: { configKey: config.configKey }
});
if (existingConfig) {
existingConfig.configValue = String(config.configValue);
await configRepository.save(existingConfig);
}
const envKey = this.mapConfigKeyToEnvKey(config.configKey);
if (envKey) {
envUpdates[envKey] = String(config.configValue);
}
}
const envFilePath = EnvHelper.getEnvFilePath(process.env.NODE_ENV || 'development');
const updateSuccess = EnvHelper.updateEnvFile(envFilePath, envUpdates);
if (!updateSuccess) {
return res.status(500).json({
success: false,
message: '更新.env文件失败'
});
}
return res.json({
success: true,
message: '配置更新成功'
});
} catch (error) {
console.error('更新配置失败:', error);
return res.status(500).json({
success: false,
message: '服务器内部错误'
});
}
}
/**
* 测试邮件发送
* @param req - Express请求对象
* @param res - Express响应对象
*/
async testEmail(req: Request, res: Response) {
try {
const { to } = req.body;
if (!to || !this.isValidEmail(to)) {
return res.status(400).json({
success: false,
message: '请输入有效的邮箱地址'
});
}
const configRepository = AppDataSource.getRepository(Config);
const mailConfigs = await configRepository.find({
where: [
{ configKey: 'mail_from' },
{ configKey: 'mail_smtp_host' },
{ configKey: 'mail_smtp_port' },
{ configKey: 'mail_smtp_user' },
{ configKey: 'mail_smtp_password' }
]
});
const configMap = new Map(mailConfigs.map(c => [c.configKey, c.configValue]));
const mailFrom = configMap.get('mail_from');
const smtpHost = configMap.get('mail_smtp_host');
const smtpPort = configMap.get('mail_smtp_port');
const smtpUser = configMap.get('mail_smtp_user');
const smtpPassword = configMap.get('mail_smtp_password');
if (!mailFrom || !smtpHost || !smtpPort || !smtpUser || !smtpPassword) {
return res.status(400).json({
success: false,
message: '邮件配置不完整,请先配置邮件信息'
});
}
const transporter = nodemailer.createTransport({
host: smtpHost,
port: Number(smtpPort),
secure: Number(smtpPort) === 465,
auth: {
user: smtpUser,
pass: smtpPassword
}
});
const mailOptions = {
from: mailFrom,
to: to,
subject: '梦幻西游运营管理系统 - 邮件测试',
text: '这是一封测试邮件,如果您收到此邮件,说明邮件配置正确。'
};
await transporter.sendMail(mailOptions);
return res.json({
success: true,
message: '邮件发送成功'
});
} catch (error) {
console.error('测试邮件发送失败:', error);
return res.status(500).json({
success: false,
message: '邮件发送失败,请检查配置'
});
}
}
/**
* 将配置键映射到.env文件中的环境变量键
* @param configKey - 配置键
* @returns 环境变量键如果不映射则返回null
*/
private mapConfigKeyToEnvKey(configKey: string): string | null {
const mapping: Record<string, string> = {
'backend_host': 'BACKEND_HOST',
'backend_port': 'PORT',
'log_level': 'LOG_LEVEL',
'cors_origin': 'CORS_ORIGIN',
'jwt_secret': 'JWT_SECRET',
'jwt_expires_in': 'JWT_EXPIRES_IN',
'game_server_proxy_url': 'GAME_SERVER_PROXY_URL',
'game_server_psk': 'GAME_PSK'
};
return mapping[configKey] || null;
}
/**
* 验证邮箱格式
* @param email - 邮箱地址
* @returns 是否有效
*/
private isValidEmail(email: string): boolean {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return emailRegex.test(email);
}
/**
* 检查玩家服务中心状态
* @param req - Express请求对象
* @param res - Express响应对象
*/
async checkPlayerServiceStatus(req: Request, res: Response) {
try {
const configRepository = AppDataSource.getRepository(Config);
const playerServiceEnabledConfig = await configRepository.findOne({
where: { configKey: 'player_service_enabled' }
});
const playerServiceCloseMsgConfig = await configRepository.findOne({
where: { configKey: 'player_service_close_msg' }
});
const enabled = playerServiceEnabledConfig?.configValue === 'true';
const closeMsg = playerServiceCloseMsgConfig?.configValue || '玩家服务中心系统维护中';
return res.json({
success: true,
data: {
enabled,
closeMsg
}
});
} catch (error) {
console.error('检查玩家服务中心状态失败:', error);
return res.status(500).json({
success: false,
message: '服务器内部错误'
});
}
}
}

View File

@@ -1,5 +1,6 @@
import { Request, Response } from 'express';
import axios from 'axios';
import { CaptchaService } from '../services/captchaService';
export class PlayerAuthController {
private readonly gameServerUrl: string;
@@ -10,7 +11,7 @@ export class PlayerAuthController {
async login(req: Request, res: Response) {
try {
const { username, password } = req.body;
const { username, password, captchaId, captchaCode } = req.body;
if (!username || !password) {
return res.status(400).json({
@@ -19,6 +20,33 @@ export class PlayerAuthController {
});
}
// 创建验证码服务实例
const captchaService = new CaptchaService();
// 检查是否启用了验证码功能
const captchaEnabled = await captchaService.checkCaptchaEnabled();
// 如果启用了验证码,则验证验证码
if (captchaEnabled) {
// 验证验证码参数是否完整
if (!captchaId || !captchaCode) {
return res.status(400).json({
success: false,
message: '请输入验证码'
});
}
// 验证验证码是否正确
const captchaVerify = captchaService.verifyCaptcha(captchaId, captchaCode);
if (!captchaVerify.success) {
return res.status(400).json({
success: false,
message: captchaVerify.message || '验证码错误或已过期'
});
}
}
const response = await axios.post(
`${this.gameServerUrl}?code=auth/login`,
{