项目初始化
This commit is contained in:
19
backend/src/config/database.ts
Normal file
19
backend/src/config/database.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import { DataSource } from 'typeorm';
|
||||
import dotenv from 'dotenv';
|
||||
import path from 'path';
|
||||
|
||||
dotenv.config({ path: path.join(__dirname, '../../.env.development') });
|
||||
|
||||
export const AppDataSource = new DataSource({
|
||||
type: 'mysql',
|
||||
host: process.env.DB_HOST || '127.0.0.1',
|
||||
port: parseInt(process.env.DB_PORT || '3306'),
|
||||
username: process.env.DB_USERNAME || 'root',
|
||||
password: process.env.DB_PASSWORD || '',
|
||||
database: process.env.DB_DATABASE || 'mhxy_web_vue',
|
||||
synchronize: false,
|
||||
logging: process.env.NODE_ENV === 'development',
|
||||
entities: [path.join(__dirname, '../models/**/*.ts')],
|
||||
migrations: [path.join(__dirname, '../migrations/**/*.ts')],
|
||||
subscribers: [path.join(__dirname, '../subscribers/**/*.ts')],
|
||||
});
|
||||
176
backend/src/controllers/adminAuthController.ts
Normal file
176
backend/src/controllers/adminAuthController.ts
Normal file
@@ -0,0 +1,176 @@
|
||||
// 导入Express的Request和Response类型
|
||||
import { Request, Response } from 'express';
|
||||
// 导入bcrypt用于密码加密和验证
|
||||
import bcrypt from 'bcrypt';
|
||||
// 导入jsonwebtoken用于生成和验证JWT令牌
|
||||
import jwt from 'jsonwebtoken';
|
||||
// 导入数据源配置
|
||||
import { AppDataSource } from '../config/database';
|
||||
// 导入管理员用户模型
|
||||
import { AdminUser } from '../models/AdminUser';
|
||||
|
||||
/**
|
||||
* 管理员认证控制器
|
||||
* 处理管理员登录、登出和获取当前用户信息等认证相关操作
|
||||
*/
|
||||
export class AdminAuthController {
|
||||
/**
|
||||
* 管理员登录
|
||||
* 验证用户名和密码,生成JWT令牌并设置到cookie中
|
||||
* @param req - Express请求对象,包含用户名和密码
|
||||
* @param res - Express响应对象
|
||||
*/
|
||||
async login(req: Request, res: Response) {
|
||||
try {
|
||||
// 从请求体中获取用户名和密码
|
||||
const { username, password } = req.body;
|
||||
|
||||
// 验证用户名和密码是否为空
|
||||
if (!username || !password) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '用户名和密码不能为空'
|
||||
});
|
||||
}
|
||||
|
||||
// 获取管理员用户仓库
|
||||
const adminUserRepository = AppDataSource.getRepository(AdminUser);
|
||||
// 根据用户名查找管理员用户
|
||||
const adminUser = await adminUserRepository.findOne({
|
||||
where: { username }
|
||||
});
|
||||
|
||||
// 用户不存在
|
||||
if (!adminUser) {
|
||||
return res.status(401).json({
|
||||
success: false,
|
||||
message: '用户名或密码错误'
|
||||
});
|
||||
}
|
||||
|
||||
// 验证密码是否正确
|
||||
const isPasswordValid = await bcrypt.compare(password, adminUser.passwordHash);
|
||||
|
||||
// 密码错误
|
||||
if (!isPasswordValid) {
|
||||
return res.status(401).json({
|
||||
success: false,
|
||||
message: '用户名或密码错误'
|
||||
});
|
||||
}
|
||||
|
||||
// 检查账号状态是否被禁用
|
||||
if (adminUser.status !== 1) {
|
||||
return res.status(403).json({
|
||||
success: false,
|
||||
message: '账号已被禁用'
|
||||
});
|
||||
}
|
||||
|
||||
// 生成JWT令牌,包含用户ID、用户名和角色ID
|
||||
const token = jwt.sign(
|
||||
{
|
||||
id: adminUser.id,
|
||||
username: adminUser.username,
|
||||
roleId: adminUser.roleId
|
||||
},
|
||||
process.env.JWT_SECRET || 'your-secret-key',
|
||||
{ expiresIn: '2h' }
|
||||
);
|
||||
|
||||
// 将令牌设置到cookie中,配置安全选项
|
||||
res.cookie('admin_token', token, {
|
||||
httpOnly: true, // 仅HTTP访问,防止XSS攻击
|
||||
secure: process.env.NODE_ENV === 'production', // 生产环境仅HTTPS
|
||||
sameSite: 'strict', // 防止CSRF攻击
|
||||
maxAge: 2 * 60 * 60 * 1000 // 2小时过期
|
||||
});
|
||||
|
||||
// 返回登录成功响应
|
||||
return res.json({
|
||||
success: true,
|
||||
message: '登录成功',
|
||||
data: {
|
||||
token,
|
||||
user: {
|
||||
id: adminUser.id,
|
||||
username: adminUser.username,
|
||||
realName: adminUser.realName,
|
||||
roleId: adminUser.roleId
|
||||
}
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('管理员登录失败:', error);
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
message: '服务器内部错误'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 管理员登出
|
||||
* 清除cookie中的令牌
|
||||
* @param req - Express请求对象
|
||||
* @param res - Express响应对象
|
||||
*/
|
||||
async logout(req: Request, res: Response) {
|
||||
try {
|
||||
// 清除cookie中的令牌
|
||||
res.clearCookie('admin_token');
|
||||
return res.json({
|
||||
success: true,
|
||||
message: '退出登录成功'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('管理员退出登录失败:', error);
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
message: '服务器内部错误'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前登录用户信息
|
||||
* 根据请求中的管理员ID查询并返回用户信息
|
||||
* @param req - Express请求对象,包含已认证的管理员信息
|
||||
* @param res - Express响应对象
|
||||
*/
|
||||
async getCurrentUser(req: any, res: Response) {
|
||||
try {
|
||||
// 获取管理员用户仓库
|
||||
const adminUserRepository = AppDataSource.getRepository(AdminUser);
|
||||
// 根据请求中的管理员ID查询用户信息
|
||||
const adminUser = await adminUserRepository.findOne({
|
||||
where: { id: req.admin.id }
|
||||
});
|
||||
|
||||
// 用户不存在
|
||||
if (!adminUser) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: '用户不存在'
|
||||
});
|
||||
}
|
||||
|
||||
// 返回用户信息
|
||||
return res.json({
|
||||
success: true,
|
||||
data: {
|
||||
id: adminUser.id,
|
||||
username: adminUser.username,
|
||||
realName: adminUser.realName,
|
||||
roleId: adminUser.roleId
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('获取用户信息失败:', error);
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
message: '服务器内部错误'
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
142
backend/src/controllers/playerAuthController.ts
Normal file
142
backend/src/controllers/playerAuthController.ts
Normal file
@@ -0,0 +1,142 @@
|
||||
import { Request, Response } from 'express';
|
||||
import axios from 'axios';
|
||||
|
||||
export class PlayerAuthController {
|
||||
private readonly gameServerUrl: string;
|
||||
|
||||
constructor() {
|
||||
this.gameServerUrl = process.env.GAME_SERVER_PROXY_URL || 'http://127.0.0.1:8080/tool/http';
|
||||
}
|
||||
|
||||
async login(req: Request, res: Response) {
|
||||
try {
|
||||
const { username, password } = req.body;
|
||||
|
||||
if (!username || !password) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '用户名和密码不能为空'
|
||||
});
|
||||
}
|
||||
|
||||
const response = await axios.post(
|
||||
`${this.gameServerUrl}?code=auth/login`,
|
||||
{
|
||||
username,
|
||||
password
|
||||
},
|
||||
{
|
||||
proxy: false
|
||||
}
|
||||
);
|
||||
|
||||
if (response.data.success && response.data.code === 200) {
|
||||
return res.json({
|
||||
success: true,
|
||||
message: '登录成功',
|
||||
data: response.data.data
|
||||
});
|
||||
} else {
|
||||
return res.status(401).json({
|
||||
success: false,
|
||||
message: response.data.message || '登录失败'
|
||||
});
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.error('玩家登录失败:', error);
|
||||
if (error.response) {
|
||||
return res.status(error.response.status || 500).json({
|
||||
success: false,
|
||||
message: error.response.data?.message || '登录失败'
|
||||
});
|
||||
}
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
message: '服务器内部错误'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async logout(req: any, res: Response) {
|
||||
try {
|
||||
const token = req.headers?.authorization?.replace('Bearer ', '');
|
||||
|
||||
if (!token) {
|
||||
return res.status(401).json({
|
||||
success: false,
|
||||
message: '未授权'
|
||||
});
|
||||
}
|
||||
|
||||
await axios.post(
|
||||
`${this.gameServerUrl}?code=auth/out_login`,
|
||||
{},
|
||||
{
|
||||
headers: {
|
||||
Authorization: token
|
||||
},
|
||||
proxy: false
|
||||
}
|
||||
);
|
||||
|
||||
return res.json({
|
||||
success: true,
|
||||
message: '退出登录成功'
|
||||
});
|
||||
} catch (error: any) {
|
||||
console.error('玩家退出登录失败:', error);
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
message: '退出登录失败'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async getAccountInfo(req: any, res: Response) {
|
||||
try {
|
||||
const token = req.headers?.authorization?.replace('Bearer ', '');
|
||||
|
||||
if (!token) {
|
||||
return res.status(401).json({
|
||||
success: false,
|
||||
message: '未授权'
|
||||
});
|
||||
}
|
||||
|
||||
const response = await axios.post(
|
||||
`${this.gameServerUrl}?code=account/get_account`,
|
||||
{},
|
||||
{
|
||||
headers: {
|
||||
Authorization: token
|
||||
},
|
||||
proxy: false
|
||||
}
|
||||
);
|
||||
|
||||
if (response.data.success && response.data.code === 200) {
|
||||
return res.json({
|
||||
success: true,
|
||||
data: response.data.data
|
||||
});
|
||||
} else {
|
||||
return res.status(401).json({
|
||||
success: false,
|
||||
message: response.data.message || '获取账号信息失败'
|
||||
});
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.error('获取账号信息失败:', error);
|
||||
if (error.response) {
|
||||
return res.status(error.response.status || 500).json({
|
||||
success: false,
|
||||
message: error.response.data?.message || '获取账号信息失败'
|
||||
});
|
||||
}
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
message: '服务器内部错误'
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
60
backend/src/index.ts
Normal file
60
backend/src/index.ts
Normal file
@@ -0,0 +1,60 @@
|
||||
import express, { Express, Request, Response } from 'express';
|
||||
import cors from 'cors';
|
||||
import dotenv from 'dotenv';
|
||||
import { AppDataSource } from './config/database';
|
||||
import adminRoutes from './routes/adminRoutes';
|
||||
import playerRoutes from './routes/playerRoutes';
|
||||
|
||||
dotenv.config();
|
||||
|
||||
const app: Express = express();
|
||||
const PORT = process.env.PORT || 3000;
|
||||
|
||||
app.use(cors({
|
||||
origin: process.env.CORS_ORIGIN || 'http://localhost:5173',
|
||||
credentials: true
|
||||
}));
|
||||
|
||||
app.use(express.json());
|
||||
app.use(express.urlencoded({ extended: true }));
|
||||
|
||||
app.use('/api/admin', adminRoutes);
|
||||
app.use('/api/player', playerRoutes);
|
||||
|
||||
app.get('/', (req: Request, res: Response) => {
|
||||
res.json({
|
||||
message: '梦幻西游一站式运营管理平台 API 服务',
|
||||
version: '1.0.0'
|
||||
});
|
||||
});
|
||||
|
||||
app.use((req: Request, res: Response) => {
|
||||
res.status(404).json({
|
||||
success: false,
|
||||
message: '接口不存在'
|
||||
});
|
||||
});
|
||||
|
||||
app.use((err: any, req: Request, res: Response, next: any) => {
|
||||
console.error('服务器错误:', err);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '服务器内部错误'
|
||||
});
|
||||
});
|
||||
|
||||
const startServer = async () => {
|
||||
try {
|
||||
await AppDataSource.initialize();
|
||||
console.log('数据库连接成功');
|
||||
|
||||
app.listen(PORT, () => {
|
||||
console.log(`服务器运行在 http://localhost:${PORT}`);
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('服务器启动失败:', error);
|
||||
process.exit(1);
|
||||
}
|
||||
};
|
||||
|
||||
startServer();
|
||||
38
backend/src/middleware/adminAuth.ts
Normal file
38
backend/src/middleware/adminAuth.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
import { Request, Response, NextFunction } from 'express';
|
||||
import jwt from 'jsonwebtoken';
|
||||
|
||||
export interface AuthRequest extends Request {
|
||||
admin?: {
|
||||
id: number;
|
||||
username: string;
|
||||
roleId: number;
|
||||
};
|
||||
}
|
||||
|
||||
export const adminAuthMiddleware = (req: AuthRequest, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const token = req.cookies?.admin_token || req.headers?.authorization?.replace('Bearer ', '');
|
||||
|
||||
if (!token) {
|
||||
return res.status(401).json({
|
||||
success: false,
|
||||
message: '未授权,请先登录'
|
||||
});
|
||||
}
|
||||
|
||||
const decoded = jwt.verify(token, process.env.JWT_SECRET || 'your-secret-key') as any;
|
||||
|
||||
req.admin = {
|
||||
id: decoded.id,
|
||||
username: decoded.username,
|
||||
roleId: decoded.roleId
|
||||
};
|
||||
|
||||
next();
|
||||
} catch (error) {
|
||||
return res.status(401).json({
|
||||
success: false,
|
||||
message: 'Token无效或已过期'
|
||||
});
|
||||
}
|
||||
};
|
||||
31
backend/src/middleware/playerAuth.ts
Normal file
31
backend/src/middleware/playerAuth.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
import { Request, Response, NextFunction } from 'express';
|
||||
|
||||
export interface PlayerAuthRequest extends Request {
|
||||
player?: {
|
||||
token: string;
|
||||
};
|
||||
}
|
||||
|
||||
export const playerAuthMiddleware = (req: PlayerAuthRequest, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const token = req.headers?.authorization?.replace('Bearer ', '');
|
||||
|
||||
if (!token) {
|
||||
return res.status(401).json({
|
||||
success: false,
|
||||
message: '未授权,请先登录'
|
||||
});
|
||||
}
|
||||
|
||||
req.player = {
|
||||
token
|
||||
};
|
||||
|
||||
next();
|
||||
} catch (error) {
|
||||
return res.status(401).json({
|
||||
success: false,
|
||||
message: 'Token无效'
|
||||
});
|
||||
}
|
||||
};
|
||||
28
backend/src/models/AdminUser.ts
Normal file
28
backend/src/models/AdminUser.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, UpdateDateColumn } from 'typeorm';
|
||||
|
||||
@Entity('admin_users')
|
||||
export class AdminUser {
|
||||
@PrimaryGeneratedColumn('increment')
|
||||
id: number;
|
||||
|
||||
@Column({ type: 'varchar', length: 64, unique: true })
|
||||
username: string;
|
||||
|
||||
@Column({ type: 'varchar', length: 255, name: 'password_hash' })
|
||||
passwordHash: string;
|
||||
|
||||
@Column({ type: 'varchar', length: 50, nullable: true, name: 'real_name' })
|
||||
realName: string;
|
||||
|
||||
@Column({ type: 'int', default: 1, name: 'role_id' })
|
||||
roleId: number;
|
||||
|
||||
@Column({ type: 'tinyint', default: 1 })
|
||||
status: number;
|
||||
|
||||
@CreateDateColumn({ name: 'created_at' })
|
||||
createdAt: Date;
|
||||
|
||||
@UpdateDateColumn({ name: 'updated_at' })
|
||||
updatedAt: Date;
|
||||
}
|
||||
14
backend/src/routes/adminRoutes.ts
Normal file
14
backend/src/routes/adminRoutes.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import { Router } from 'express';
|
||||
import { AdminAuthController } from '../controllers/adminAuthController';
|
||||
import { adminAuthMiddleware, AuthRequest } from '../middleware/adminAuth';
|
||||
|
||||
const router = Router();
|
||||
const adminAuthController = new AdminAuthController();
|
||||
|
||||
router.post('/login', (req, res) => adminAuthController.login(req, res));
|
||||
|
||||
router.post('/logout', (req, res) => adminAuthController.logout(req, res));
|
||||
|
||||
router.get('/me', adminAuthMiddleware, (req: AuthRequest, res) => adminAuthController.getCurrentUser(req, res));
|
||||
|
||||
export default router;
|
||||
14
backend/src/routes/playerRoutes.ts
Normal file
14
backend/src/routes/playerRoutes.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import { Router } from 'express';
|
||||
import { PlayerAuthController } from '../controllers/playerAuthController';
|
||||
import { playerAuthMiddleware, PlayerAuthRequest } from '../middleware/playerAuth';
|
||||
|
||||
const router = Router();
|
||||
const playerAuthController = new PlayerAuthController();
|
||||
|
||||
router.post('/login', (req, res) => playerAuthController.login(req, res));
|
||||
|
||||
router.post('/logout', (req, res) => playerAuthController.logout(req, res));
|
||||
|
||||
router.get('/account', playerAuthMiddleware, (req: PlayerAuthRequest, res) => playerAuthController.getAccountInfo(req, res));
|
||||
|
||||
export default router;
|
||||
Reference in New Issue
Block a user