From 2647de4455cd1ba07ebb7fd9df91e7e43b8d1d94 Mon Sep 17 00:00:00 2001 From: luozhj33 Date: Thu, 19 Mar 2026 14:06:19 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E7=94=A8=E6=88=B7=E6=B3=A8=E5=86=8C?= =?UTF-8?q?=E3=80=81=E7=99=BB=E9=99=86=E3=80=81=E9=80=80=E5=87=BA=E6=8E=A5?= =?UTF-8?q?=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- back/src/controllers/user.js | 144 +++++++++++++++++++++++++++++++++++ back/src/routes/user.js | 15 ++++ 2 files changed, 159 insertions(+) create mode 100644 back/src/controllers/user.js create mode 100644 back/src/routes/user.js diff --git a/back/src/controllers/user.js b/back/src/controllers/user.js new file mode 100644 index 0000000..b4c5b1f --- /dev/null +++ b/back/src/controllers/user.js @@ -0,0 +1,144 @@ +const mysql = require('../config/mysql'); +const { generateToken, addTokenToBlacklist } = require('../middleware/jwt'); +const { standardResponse } = require('../utils/standardResponse'); +const crypto = require('crypto'); + +// 用户注册 +async function register(req, res) { + console.log('POST /api/register'); // TODO + const { username, password, email, phone } = req.body; + + // 参数验证 + if (username == null || username === '') { + return res.status(400).json(standardResponse(400, '用户名不能为空')); + } + if (password == null || password === '') { + return res.status(400).json(standardResponse(400, '密码不能为空')); + } + if ((email == null || email === '') && (phone == null || phone === '')) { + return res.status(400).json(standardResponse(400, '邮箱或手机号不能为空')); + } + + + try { + // 检查用户名是否已存在 + const existingUser = await mysql.query( + 'SELECT id FROM users WHERE username = ? AND is_deleted = 0', + [username] + ); + if (existingUser.length > 0) { + return res.status(400).json(standardResponse(400, '用户名已存在')); + } + + // 插入新用户 + const user_id = generateUserId(); + const salt = generateSalt(); + const hash = hashPassword(password, salt); + await mysql.query( + 'INSERT INTO users (user_id, username, password, salt, email, phone) VALUES (?, ?, ?, ?, ?, ?)', + [user_id, username, hash, salt, email || null, phone || null] + ); + + return res.status(200).json( + standardResponse(200, '注册成功') + ); + } catch (error) { + console.error('注册失败:', error); // TODO + return res.status(500).json( + standardResponse(500, '注册失败') + ); + } +} + +// 用户登录 +async function login(req, res) { + console.log('POST /api/login'); // TODO + const { username, password } = req.body; + + // 参数验证 + if (username == null || username === '') { + return res.status(400).json(standardResponse(400, '用户名不能为空')); + } + if (password == null || password === '') { + return res.status(400).json(standardResponse(400, '密码不能为空')); + } + + try { + // 查询用户(仅支持用户名登录) + const users = await mysql.query( + 'SELECT user_id, username, password, salt FROM users WHERE username = ? AND is_deleted = 0', + [username] + ); + if (users.length === 0) { + return res.status(400).json(standardResponse(400, '用户不存在')); + } + + // 验证密码 + const user = users[0]; + const isValid = verifyPassword(password, user.salt, user.password); + if (!isValid) { + return res.status(400).json(standardResponse(400, '密码错误')); + } + + // 生成 jwt_token + const token = generateToken( + { user_id: user.user_id, username: user.username }, + '24h' + ); + + return res.status(200) + .set('Authorization', `Bearer ${token}`) + .json(standardResponse(200, '登录成功', { + user_id: user.user_id, + username: user.username, + email: user.email, + phone: user.phone + }) + ); + } catch (error) { + console.error('登录失败:', error); // TODO + return res.status(500).json(standardResponse(500, '登录失败')); + } +} + +// 用户退出 +async function logout(req, res) { + console.log('POST /api/logout'); // TODO + try { + // 将 token 加入黑名单 + const token = req.token; + await addTokenToBlacklist(token, 86400); // 24 小时 + return res.status(200).json(standardResponse(200, '退出成功')); + } catch (error) { + console.error('退出失败:', error); // TODO + return res.status(500).json(standardResponse(500, '退出失败')); + } +} + + +// 生成 user_id,通过时间和随机数生成,几乎不存在碰撞 +function generateUserId() { + return 'id_' + Date.now().toString(36) + '_' + Math.random().toString(36).slice(4, 8);; +} + +// 生成随机 salt +function generateSalt() { + return crypto.randomBytes(16).toString('hex'); +} + +// SHA256 + salt 加密密码 +function hashPassword(password, salt) { + return crypto.createHash('sha256').update(salt + password).digest('hex'); +} + +// 验证密码 +function verifyPassword(rawPassword, salt, databasePassword) { + const hash = hashPassword(rawPassword, salt); + return hash === databasePassword; +} + +module.exports = { + register, + login, + logout +}; \ No newline at end of file diff --git a/back/src/routes/user.js b/back/src/routes/user.js new file mode 100644 index 0000000..cd130ea --- /dev/null +++ b/back/src/routes/user.js @@ -0,0 +1,15 @@ +const express = require('express'); +const router = express.Router(); +const { register, login, logout } = require('../controllers/user'); +const { jwtAuth } = require('../middleware/jwt'); + +// 用户注册 +router.post('/user/register', register); + +// 用户登录 +router.post('/user/login', login); + +// 用户退出 +router.post('/user/logout', jwtAuth, logout); + +module.exports = router; \ No newline at end of file