项目初始化

This commit is contained in:
Stev_Wang
2026-01-04 17:19:04 +08:00
commit 93aae460af
41 changed files with 6922 additions and 0 deletions

20
backend/.env.development Normal file
View File

@@ -0,0 +1,20 @@
# 服务器配置
PORT=3000
NODE_ENV=development
# 数据库配置
DB_HOST=127.0.0.1
DB_PORT=3306
DB_USERNAME=root
DB_PASSWORD=WANGjx064200
DB_DATABASE=mhxy_web_vue
# JWT密钥
JWT_SECRET=your-secret-key-change-in-production
JWT_EXPIRES_IN=2h
# 游戏服务端代理地址
GAME_SERVER_PROXY_URL=http://127.0.0.1:8080/tool/http
# CORS配置
CORS_ORIGIN=http://localhost:5173

20
backend/.env.production Normal file
View File

@@ -0,0 +1,20 @@
# 服务器配置
PORT=3000
NODE_ENV=production
# 数据库配置
DB_HOST=127.0.0.1
DB_PORT=3306
DB_USERNAME=root
DB_PASSWORD=WANGjx064200
DB_DATABASE=mhxy_web_vue
# JWT密钥生产环境请使用更安全的密钥
JWT_SECRET=your-production-secret-key-change-this
JWT_EXPIRES_IN=2h
# 游戏服务端代理地址
GAME_SERVER_PROXY_URL=http://127.0.0.1:8080/tool/http
# CORS配置生产环境请修改为实际域名
CORS_ORIGIN=http://your-frontend-domain.com

60
backend/database/init.sql Normal file
View File

@@ -0,0 +1,60 @@
-- ============================================
-- 梦幻西游一站式运营管理平台 - 数据库初始化脚本
-- MySQL 8.4 兼容版本
-- 创建日期2026-01-04
-- ============================================
-- 使用数据库
USE mhxy_web_vue;
-- 设置字符集
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ============================================
-- 表admin_users管理员用户表
-- ============================================
DROP TABLE IF EXISTS `admin_users`;
CREATE TABLE `admin_users` (
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`username` VARCHAR(64) NOT NULL COMMENT '用户名',
`password_hash` VARCHAR(255) NOT NULL COMMENT '密码哈希bcrypt加密',
`real_name` VARCHAR(50) NULL COMMENT '真实姓名',
`role_id` INT NOT NULL DEFAULT 1 COMMENT '角色ID1:超级管理员)',
`status` TINYINT NOT NULL DEFAULT 1 COMMENT '状态1:正常, 0:禁用)',
`created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`updated_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`id`),
UNIQUE INDEX `idx_username` (`username`),
INDEX `idx_status` (`status`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='管理员用户表';
-- ============================================
-- 插入默认管理员账号
-- 用户名admin
-- 密码admin123bcrypt加密10轮
-- ============================================
INSERT INTO `admin_users` (`username`, `password_hash`, `real_name`, `role_id`, `status`)
VALUES (
'admin',
'$2b$10$e1hDPxr/A9nbqbJNJFV9COVPfYt.b6REbrvh2rSrn29I1CEE9tski',
'超级管理员',
1,
1
);
-- ============================================
-- 验证数据
-- ============================================
SELECT
id,
username,
real_name,
role_id,
status,
created_at
FROM admin_users;
SET FOREIGN_KEY_CHECKS = 1;
-- 初始化完成

3078
backend/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

34
backend/package.json Normal file
View File

@@ -0,0 +1,34 @@
{
"name": "backend",
"version": "1.0.0",
"description": "",
"main": "dist/index.js",
"scripts": {
"dev": "nodemon --exec ts-node src/index.ts",
"build": "tsc",
"start": "node dist/index.js"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"axios": "^1.13.2",
"bcrypt": "^6.0.0",
"cors": "^2.8.5",
"dotenv": "^17.2.3",
"express": "^5.2.1",
"jsonwebtoken": "^9.0.3",
"mysql2": "^3.16.0",
"typeorm": "^0.3.28"
},
"devDependencies": {
"@types/bcrypt": "^6.0.0",
"@types/cors": "^2.8.19",
"@types/express": "^5.0.6",
"@types/jsonwebtoken": "^9.0.10",
"@types/node": "^25.0.3",
"nodemon": "^3.1.11",
"ts-node": "^10.9.2",
"typescript": "^5.9.3"
}
}

View 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')],
});

View 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: '服务器内部错误'
});
}
}
}

View 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
View 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();

View 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无效或已过期'
});
}
};

View 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无效'
});
}
};

View 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;
}

View 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;

View 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;

23
backend/tsconfig.json Normal file
View File

@@ -0,0 +1,23 @@
{
"compilerOptions": {
"target": "ES2020",
"module": "commonjs",
"lib": ["ES2020"],
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"moduleResolution": "node",
"declaration": true,
"declarationMap": true,
"sourceMap": true,
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
"strictPropertyInitialization": false
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}