You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
229 lines
7.0 KiB
229 lines
7.0 KiB
1 month ago
|
const express = require('express');
|
||
|
const cors = require('cors');
|
||
|
const bodyParser = require('body-parser');
|
||
|
const bcrypt = require('bcrypt');
|
||
|
const { body, validationResult } = require('express-validator');
|
||
|
const morgan = require('morgan');
|
||
|
const timeout = require('connect-timeout');
|
||
|
const multer = require('multer');
|
||
|
const path = require('path');
|
||
|
const fs = require('fs');
|
||
|
const rateLimit = require('express-rate-limit');
|
||
|
|
||
|
// 创建 Express 应用实例
|
||
|
const app = express();
|
||
|
const PORT = 3000; // 可以根据需要修改端口
|
||
|
const uploadDir = 'uploads/';
|
||
|
|
||
|
// 使用中间件
|
||
|
app.use(cors({
|
||
|
origin: 'http://your-frontend-domain.com', // 修改为你前端的域名
|
||
|
methods: ['GET', 'POST'],
|
||
|
allowedHeaders: ['Content-Type']
|
||
|
}));
|
||
|
app.use(express.json()); // 解析 JSON 格式的请求体
|
||
|
app.use(morgan('combined')); // 记录 HTTP 请求日志
|
||
|
|
||
|
// 请求限制
|
||
|
const limiter = rateLimit({
|
||
|
windowMs: 15 * 60 * 1000, // 15 分钟
|
||
|
max: 100 // 每个 IP 限制 100 次请求
|
||
|
});
|
||
|
app.use(limiter); // 应用限制
|
||
|
|
||
|
// 检查并创建上传目录
|
||
|
if (!fs.existsSync(uploadDir)) {
|
||
|
fs.mkdirSync(uploadDir, { recursive: true });
|
||
|
}
|
||
|
|
||
|
// 设置文件存储配置
|
||
|
const storage = multer.diskStorage({
|
||
|
destination: (req, file, cb) => {
|
||
|
cb(null, uploadDir);
|
||
|
},
|
||
|
filename: (req, file, cb) => {
|
||
|
cb(null, Date.now() + path.extname(file.originalname));
|
||
|
}
|
||
|
});
|
||
|
|
||
|
// 创建上传实例
|
||
|
const upload = multer({
|
||
|
storage,
|
||
|
limits: { fileSize: 5 * 1024 * 1024 }, // 限制文件大小为 5MB
|
||
|
fileFilter: (req, file, cb) => {
|
||
|
const filetypes = /jpeg|jpg|png|gif|pdf/;
|
||
|
const mimetype = filetypes.test(file.mimetype);
|
||
|
const extname = filetypes.test(path.extname(file.originalname).toLowerCase());
|
||
|
|
||
|
if (mimetype && extname) {
|
||
|
return cb(null, true);
|
||
|
}
|
||
|
cb(new Error('只允许上传 JPEG、PNG、GIF 和 PDF 文件'));
|
||
|
}
|
||
|
});
|
||
|
|
||
|
// 模拟的用户数据(在实际应用中,这通常来自数据库)
|
||
|
let users = []; // 应该替换为实际的数据库
|
||
|
|
||
|
// 注册接口
|
||
|
app.post('/api/register', [
|
||
|
body('username').notEmpty().withMessage('用户名不能为空'),
|
||
|
body('password').notEmpty().withMessage('密码不能为空'),
|
||
|
body('confirm_password').custom((value, { req }) => {
|
||
|
if (value !== req.body.password) {
|
||
|
throw new Error('密码不匹配');
|
||
|
}
|
||
|
return true;
|
||
|
})
|
||
|
], async (req, res) => {
|
||
|
const errors = validationResult(req);
|
||
|
if (!errors.isEmpty()) {
|
||
|
return res.status(400).json({ errors: errors.array() });
|
||
|
}
|
||
|
|
||
|
const { username, password } = req.body;
|
||
|
const existingUser = users.find(user => user.username === username);
|
||
|
if (existingUser) {
|
||
|
return res.status(400).send('用户名已被注册');
|
||
|
}
|
||
|
|
||
|
const hashedPassword = await bcrypt.hash(password, 10);
|
||
|
users.push({ username, password: hashedPassword });
|
||
|
res.status(201).send('注册成功');
|
||
|
});
|
||
|
|
||
|
// 登录接口
|
||
|
app.post('/api/login', [
|
||
|
body('username').isString().notEmpty(),
|
||
|
body('password').isString().notEmpty()
|
||
|
], (req, res) => {
|
||
|
const errors = validationResult(req);
|
||
|
if (!errors.isEmpty()) {
|
||
|
return res.status(400).json({ errors: errors.array() });
|
||
|
}
|
||
|
|
||
|
const { username, password } = req.body;
|
||
|
const user = users.find(u => u.username === username);
|
||
|
|
||
|
if (user && bcrypt.compareSync(password, user.password)) {
|
||
|
return res.status(200).json({ message: 'Login successful!' });
|
||
|
} else {
|
||
|
return res.status(401).json({ message: 'Invalid username or password.' });
|
||
|
}
|
||
|
});
|
||
|
|
||
|
// 获取数据接口
|
||
|
app.get('/api/data', (req, res) => {
|
||
|
return res.status(200).json({ title: "欢迎使用系统", description: "这是一个模拟的系统,用于展示数据与交互" });
|
||
|
});
|
||
|
app.use(cors({
|
||
|
origin: 'http://your-frontend-domain.com', // 指定允许的源
|
||
|
methods: ['GET', 'POST'],
|
||
|
}));
|
||
|
app.use(express.json());
|
||
|
app.use(express.static(path.join(__dirname, 'public'))); // 提供静态文件
|
||
|
app.use(morgan('combined')); // 记录请求日志
|
||
|
|
||
|
// 创建上传目录
|
||
|
const uploadsDir = path.join(__dirname, 'uploads');
|
||
|
if (!fs.existsSync(uploadsDir)) {
|
||
|
fs.mkdirSync(uploadsDir, { recursive: true });
|
||
|
}
|
||
|
|
||
|
// Multer setup for file uploads
|
||
|
const storage = multer.diskStorage({
|
||
|
destination: (req, file, cb) => {
|
||
|
cb(null, uploadsDir); // 确保上传目录存在
|
||
|
},
|
||
|
filename: (req, file, cb) => {
|
||
|
cb(null, Date.now() + path.extname(file.originalname)); // Append timestamp to avoid name collisions
|
||
|
}
|
||
|
});
|
||
|
|
||
|
const upload = multer({
|
||
|
storage,
|
||
|
limits: {
|
||
|
fileSize: 5 * 1024 * 1024 // 限制文件大小为5MB
|
||
|
},
|
||
|
fileFilter: (req, file, cb) => {
|
||
|
const filetypes = /csv|txt|json/; // 允许的文件类型
|
||
|
const extname = filetypes.test(path.extname(file.originalname).toLowerCase());
|
||
|
const mimetype = filetypes.test(file.mimetype);
|
||
|
|
||
|
if (mimetype && extname) {
|
||
|
return cb(null, true);
|
||
|
}
|
||
|
cb(new Error('Error: File type not supported!'));
|
||
|
}
|
||
|
});
|
||
|
|
||
|
// Endpoint to handle file import
|
||
|
app.post('/import', upload.single('file'), (req, res) => {
|
||
|
if (!req.file) {
|
||
|
return res.status(400).send('No file uploaded.');
|
||
|
}
|
||
|
console.log(req.file);
|
||
|
res.send('File imported successfully!');
|
||
|
});
|
||
|
|
||
|
// Endpoint to handle file export
|
||
|
app.get('/export', (req, res) => {
|
||
|
const filePath = path.join(__dirname, 'exports', 'sample-data.csv'); // 确保该文件存在
|
||
|
res.download(filePath, 'sample-data.csv', (err) => {
|
||
|
if (err) {
|
||
|
console.error(err);
|
||
|
res.status(500).send('Error exporting file.');
|
||
|
}
|
||
|
});
|
||
|
});
|
||
|
|
||
|
|
||
|
// 提交成绩接口
|
||
|
app.post('/api/submit-grade', [
|
||
|
body('grade').isNumeric()
|
||
|
], (req, res) => {
|
||
|
const errors = validationResult(req);
|
||
|
if (!errors.isEmpty()) {
|
||
|
return res.status(400).json({ errors: errors.array() });
|
||
|
}
|
||
|
|
||
|
const { grade } = req.body;
|
||
|
console.log(`Received grade: ${grade}`);
|
||
|
return res.status(200).json({ message: 'Grade submitted successfully!', grade });
|
||
|
});
|
||
|
|
||
|
// 文件上传接口
|
||
|
app.post('/api/upload-file', upload.single('file'), (req, res) => {
|
||
|
if (!req.file) {
|
||
|
return res.status(400).json({ message: '没有文件上传' });
|
||
|
}
|
||
|
res.status(200).json({ message: '文件上传成功', filename: req.file.filename });
|
||
|
});
|
||
|
|
||
|
// 新增 Snum-data 接口
|
||
|
app.post('/api/Snum-data', (req, res) => {
|
||
|
const { Number } = req.body;
|
||
|
// 检查请求体中的数据
|
||
|
if (typeof Number === 'undefined') {
|
||
|
return res.status(400).json({ message: 'Number is required' });
|
||
|
}
|
||
|
console.log('Received data:', Number);
|
||
|
|
||
|
res.status(200).json({ message: 'Data received successfully', data: { Number } });
|
||
|
});
|
||
|
|
||
|
// 全局错误处理
|
||
|
app.use((err, req, res, next) => {
|
||
|
console.error(err.stack);
|
||
|
res.status(500).send('服务器内部错误');
|
||
|
});
|
||
|
// 路由
|
||
|
app.get('/api/hello', (req, res) => {
|
||
|
res.send('Hello, world!');
|
||
|
});
|
||
|
|
||
|
|
||
|
// 启动服务器
|
||
|
app.listen(PORT, () => {
|
||
|
console.log(`Server is running on http://localhost:${PORT}`);
|
||
|
});
|