// server.js —— 启动服务 + 鉴权中间件 + 3 个接口 require('dotenv').config() const express = require('express') const jwt = require('jsonwebtoken') const bcrypt = require('bcryptjs') const { pool, initDB } = require('./db') const app = express() app.use(express.json()) // 解析 application/json 请求体 // ============ 鉴权中间件 ============ // 验证请求头里的 Bearer token,把解析出来的用户挂到 req.user function auth(req, res, next) { const header = req.headers.authorization || '' const token = header.replace(/^Bearer\s+/i, '') if (!token) { return res.status(401).json({ code: 401, message: '未登录' }) } try { req.user = jwt.verify(token, process.env.JWT_SECRET) next() } catch (e) { return res.status(401).json({ code: 401, message: 'token 无效或已过期' }) } } // ============ 管理员校验中间件 ============ // 必须排在 auth 之后使用(依赖 req.user) function requireAdmin(req, res, next) { if (!req.user || req.user.role !== 'admin') { return res.status(403).json({ code: 403, message: '需要管理员权限' }) } next() } // ============ POST /api/login ============ app.post('/api/user/login', async (req, res) => { const { username, password } = req.body || {} if (!username || !password) { return res.status(400).json({ code: 400, message: '用户名和密码必填' }) } try { const [rows] = await pool.query( 'SELECT id, username, password, nickname, avatar, role FROM users WHERE username = ?', [username] ) // 用户不存在 或 密码错 —— 都用同一条提示,避免被枚举账号 if (rows.length === 0) { return res.status(400).json({ code: 400, message: '账号或密码错误' }) } const user = rows[0] const ok = await bcrypt.compare(password, user.password) if (!ok) { return res.status(400).json({ code: 400, message: '账号或密码错误' }) } // 签 token:payload 里放 id / username / role const token = jwt.sign( { id: user.id, username: user.username, role: user.role }, process.env.JWT_SECRET, { expiresIn: process.env.JWT_EXPIRES_IN || '2h' } ) res.status(200).json({ code: 200, message: 'ok', data: { token, userInfo: { id: user.id, username: user.username, nickname: user.nickname, avatar: user.avatar, role: user.role, }, }, }) } catch (e) { console.error('[login] error:', e) res.status(500).json({ code: 500, message: e.message }) } }) // ============ GET /api/info ============ // 需要登录后访问,前端刷新页面时调用,用来拿最新的用户信息 app.get('/api/user/info', auth, async (req, res) => { try { const [rows] = await pool.query( 'SELECT id, username, nickname, avatar, role FROM users WHERE id = ?', [req.user.id] ) if (rows.length === 0) { return res.status(404).json({ code: 404, message: '用户不存在' }) } res.json({ code: 0, message: 'ok', data: rows[0] }) } catch (e) { console.error('[info] error:', e) res.status(500).json({ code: 500, message: e.message }) } }) // ============ POST /api/logout ============ // JWT 是无状态的:后端没法"作废"token,主流做法是前端清掉 localStorage 里的 token // 这里只是给前端一个明确的应答,告诉你"我收到了" app.post('/api/user/logout', auth, (req, res) => { res.json({ code: 0, message: 'ok' }) }) // ============ POST /api/user/create ============ // 仅管理员可调用:在 users 表里新增一行 app.post('/api/user/create', auth, requireAdmin, async (req, res) => { //role不填默认user const { username, password, nickname = '', role = 'user' } = req.body || {} // 1) 入参校验 if (!username || !password) { return res.status(400).json({ code: 400, message: '用户名和密码必填' }) } if (typeof username !== 'string' || username.length < 3 || username.length > 50) { return res.status(400).json({ code: 400, message: '用户名长度需在 3-50 之间' }) } if (typeof password !== 'string' || password.length < 6) { return res.status(400).json({ code: 400, message: '密码至少 6 位' }) } try { // 2) 密码哈希(10 轮 salt,与 db.js initDB 的 admin 同强度) const hash = await bcrypt.hash(password, 10) // 3) 写入数据库;role 不强制 'user',允许前端传来的 role const [result] = await pool.query( 'INSERT INTO users (username, password, nickname, role) VALUES (?, ?, ?, ?)', [username, hash, nickname, role] ) // 4) 把新行的关键信息回给前端(不包含 password 哈希) res.json({ code: 0, message: 'ok', data: { id: result.insertId, username, nickname, role: role }, }) } catch (e) { // users.username 是 UNIQUE,重复时 MySQL 抛 ER_DUP_ENTRY (errno 1062) if (e.code === 'ER_DUP_ENTRY') { return res.status(409).json({ code: 409, message: '用户名已存在' }) } console.error('[create user] error:', e) res.status(500).json({ code: 500, message: e.message }) } }) // ============ 启动 ============ const PORT = Number(process.env.PORT) || 3000 initDB() .then(() => { app.listen(PORT, () => { console.log(`[server] 已启动 → http://127.0.0.1:${PORT}`) console.log(`[test] curl -X POST http://127.0.0.1:${PORT}/api/login \\`) console.log(` -H "Content-Type: application/json" \\`) console.log(` -d '{"username":"admin","password":"123456"}'`) }) }) .catch((err) => { console.error('[init] 数据库初始化失败:', err) process.exit(1) })