diff --git a/后端 b/后端 new file mode 100644 index 0000000..7373187 --- /dev/null +++ b/后端 @@ -0,0 +1,229 @@ +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}`); +}); \ No newline at end of file