当前位置: 首页 > news >正文

SchoolDash Alpha冲刺随笔2 - Day 3

SchoolDash Alpha冲刺随笔2 - Day 3
课程与作业信息

所属课程:软件工程实践
作业要求来源:第五次作业——Alpha冲刺
本篇目标:记录冲刺第3天进度,展示燃尽图、运行效果、成员工作成果

  1. 项目燃尽图(Burn-up Chart)

    已完成需求的75%

  2. 本日冲刺整体进展

完成用户注册、登录功能,后端JWT验证,前端多角色Token存储。
解决角色权限问题:实现不同角色(用户、骑手、管理员)独立Token管理。

  1. 项目最新运行效果

用户端注册界面

用户端登录界面
管理员端登录界面

骑手端登录界面

  1. 今日工作成果

(后端开发)
认证路由与JWT实现

const express = require('express'); const router = express.Router(); const bcrypt = require('bcryptjs'); const jwt = require('jsonwebtoken'); const User = require('../models/User'); // 注册接口(前端请求的 /api/auth/register 对应这里) router.post('/register', async (req, res) => { try { const { username, password, phone } = req.body; // 校验参数 if (!username || !password) { return res.status(400).json({ code: 400, msg: '用户名和密码不能为空' }); } // 检查用户名是否重复 const existingUser = await User.findOne({ where: { username } }); if (existingUser) { return res.status(400).json({ code: 400, msg: '用户名已存在' }); } // 创建用户(密码由User模型自动加密) const user = await User.create({ username, password: password, // 传递明文密码,模型会自动加密 phone: phone || '', role: 'user' // 默认注册为普通用户 }); // 返回成功结果 res.status(201).json({ code: 200, msg: '注册成功', data: { id: user.id, username: user.username, role: user.role } }); } catch (error) { console.error('注册接口报错:', error); res.status(500).json({ code: 500, msg: '服务器错误,注册失败' }); } }); // 登录接口(配套前端登录功能) router.post('/login', async (req, res) => { try { const { username, password } = req.body; // 查找用户 const user = await User.findOne({ where: { username } }); if (!user) { return res.status(400).json({ code: 400, msg: '用户名不存在,请检查输入或前往注册' }); } // 验证密码 const isPasswordValid = bcrypt.compareSync(password, user.password); if (!isPasswordValid) { return res.status(400).json({ code: 400, msg: '密码不正确,请重新输入' }); } // 生成 JWT Token const token = jwt.sign( { id: user.id, role: user.role }, process.env.JWT_SECRET || 'school_dash_jwt_secret_2025', { expiresIn: '24h' } ); // 返回登录结果 res.json({ code: 200, msg: '登录成功', data: { token, username: user.username, role: user.role } }); } catch (error) { console.error('登录接口报错:', error); res.status(500).json({ code: 500, msg: '服务器繁忙,请稍后再试' }); } }); // 管理员登录接口 router.post('/admin/login', async (req, res) => { try { const { username, password } = req.body; const user = await User.findOne({ where: { username } }); if (!user) { return res.status(400).json({ code: 400, msg: '管理员账号不存在,请联系系统管理员' }); } if (user.role !== 'admin') { return res.status(403).json({ code: 403, msg: '该账号没有管理员权限,请使用管理员账号登录' }); } const isPasswordValid = bcrypt.compareSync(password, user.password); if (!isPasswordValid) { return res.status(400).json({ code: 400, msg: '管理员密码不正确,请重新输入' }); } const token = jwt.sign( { id: user.id, role: user.role }, process.env.JWT_SECRET || 'school_dash_jwt_secret_2025', { expiresIn: '24h' } ); res.json({ code: 200, msg: '管理员登录成功', data: { token, username: user.username, role: user.role } }); } catch (error) { console.error('管理员登录接口报错:', error); res.status(500).json({ code: 500, msg: '服务器繁忙,请稍后再试' }); } }); // 骑手登录接口 router.post('/rider/login', async (req, res) => { try { const { username, password } = req.body; const user = await User.findOne({ where: { username } }); if (!user) { return res.status(400).json({ code: 400, msg: '骑手账号不存在,请联系管理员开通账号' }); } if (user.role !== 'rider') { return res.status(403).json({ code: 403, msg: '该账号没有骑手权限,请使用骑手账号登录' }); } const isPasswordValid = bcrypt.compareSync(password, user.password); if (!isPasswordValid) { return res.status(400).json({ code: 400, msg: '骑手密码不正确,请重新输入' }); } const token = jwt.sign( { id: user.id, role: user.role }, process.env.JWT_SECRET || 'school_dash_jwt_secret_2025', { expiresIn: '24h' } ); res.json({ code: 200, msg: '骑手登录成功', data: { token, username: user.username, role: user.role } }); } catch (error) { console.error('骑手登录接口报错:', error); res.status(500).json({ code: 500, msg: '服务器繁忙,请稍后再试' }); } }); module.exports = router;

这是基于 Express 的用户认证路由,提供普通用户注册(/register)及普通用户、管理员、骑手三类专属登录接口,通过 bcrypt 加密校验密码,JWT 生成 24 小时身份令牌,同时校验角色权限并处理异常,接口路径统一挂载在 /api/auth/ 下。

(前端开发)

用户登录界面

<template> <div class="login-page"> <div class="back-button" @click="$router.push('/')"> <font-awesome-icon icon="fa-solid fa-arrow-left" class="back-icon" /> </div> <div class="login-container"> <!-- Logo区域 --> <div class="school-dash-logo"> <font-awesome-icon icon="fa-solid fa-graduation-cap" class="logo-icon" /> <div class="logo-text">School Dash</div> </div> <!-- 标题 --> <div class="login-title">用户登录</div> <!-- 用户名输入框 --> <div class="form-item"> <label class="form-label">用户名</label> <input type="text" class="form-input" v-model="username" placeholder="请输入用户名" @keyup.enter="handleLogin" /> </div> <!-- 密码输入框 --> <div class="form-item"> <label class="form-label">密码</label> <input type="password" class="form-input" v-model="password" placeholder="请输入密码" @keyup.enter="handleLogin" /> </div> <!-- 登录按钮 --> <button class="login-btn" @click="handleLogin">登录</button> <!-- 注册链接 --> <div class="register-link"> 还没有账号?<span @click="$router.push('/user/register')">立即注册</span> </div> </div> </div> </template> <script setup> import { ref } from 'vue'; import { useRouter } from 'vue-router'; import { ElMessage } from 'element-plus'; import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome'; import { faGraduationCap, faArrowLeft } from '@fortawesome/free-solid-svg-icons'; import request from '../../utils/request'; // 路由实例 const router = useRouter(); // 表单数据绑定 const username = ref(''); const password = ref(''); // 登录核心逻辑 const handleLogin = async () => { if (!username.value) return ElMessage.warning('请输入用户名'); if (!password.value) return ElMessage.warning('请输入密码'); try { const res = await request({ url: '/auth/login', method: 'POST', data: { username: username.value.trim(), password: password.value } }); if (!res) { return ElMessage.error('登录失败,服务器无响应'); } if (res.code === 200) { ElMessage.success('登录成功!'); // 关键1:存储后端返回的真实token和用户信息 localStorage.setItem('token', res.data.token); localStorage.setItem('role', res.data.role); localStorage.setItem('username', res.data.username); // 关键2:跳转到用户首页 router.push('/user'); } else { ElMessage.error(res.msg || '登录失败,请检查账号密码'); } } catch (error) { console.error('登录请求异常:', error); const errMsg = error.message || '网络异常,请稍后重试'; ElMessage.error(errMsg); } }; </script> <style scoped> /* 页面整体布局 */ .login-page { min-height: 100vh; background-color: #f5f5f5; display: flex; align-items: center; justify-content: center; padding: 20px; box-sizing: border-box; position: relative; } /* 返回按钮样式 */ .back-button { position: absolute; top: 20px; left: 20px; width: 40px; height: 40px; background-color: #ffffff; border-radius: 50%; display: flex; align-items: center; justify-content: center; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); cursor: pointer; transition: all 0.2s ease; z-index: 10; } .back-button:hover { background-color: #f0f0f0; transform: translateY(-2px); box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); } .back-icon { font-size: 18px; color: #4299e1; } /* 登录容器 */ .login-container { width: 100%; max-width: 350px; background-color: #ffffff; padding: 32px; border-radius: 12px; box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08); box-sizing: border-box; } /* Logo样式 */ .school-dash-logo { text-align: center; margin-bottom: 24px; } .logo-icon { font-size: 48px; color: #4299e1; margin-bottom: 12px; } .logo-text { font-size: 24px; font-weight: 600; color: #333333; } /* 标题样式 */ .login-title { font-size: 20px; font-weight: 600; color: #333333; margin-bottom: 24px; text-align: center; } /* 表单项目样式 */ .form-item { margin-bottom: 20px; } .form-label { display: block; font-size: 14px; color: #333333; margin-bottom: 8px; text-align: left; } /* 输入框样式 */ .form-input { width: 100%; padding: 12px 16px; border: 1px solid #e5e7eb; border-radius: 6px; font-size: 14px; outline: none; box-sizing: border-box; transition: all 0.2s ease; } .form-input:focus { border-color: #4299e1; box-shadow: 0 0 0 2px rgba(66, 153, 225, 0.1); } .form-input::placeholder { color: #999999; } /* 登录按钮样式 */ .login-btn { width: 100%; padding: 14px; background-color: #4299e1; color: #ffffff; border: none; border-radius: 6px; font-size: 16px; cursor: pointer; transition: background-color 0.2s ease; margin-bottom: 16px; } .login-btn:hover { background-color: #3a86cf; } .login-btn:active { background-color: #3182ce; } .login-btn:disabled { background-color: #a7c0ff; cursor: not-allowed; } /* 注册链接样式 */ .register-link { text-align: center; font-size: 14px; color: #666666; } .register-link span { color: #4299e1; cursor: pointer; margin-left: 4px; } .register-link span:hover { text-decoration: underline; } </style>

管理员登陆界面

<template> <div class="admin-login-page"> <div class="back-button" @click="$router.push('/')"> <font-awesome-icon icon="fa-solid fa-arrow-left" class="back-icon" /> </div> <div class="login-container"> <div class="school-dash-logo"> <font-awesome-icon icon="fa-solid fa-graduation-cap" class="logo-icon" /> <div class="logo-text">School Dash 管理后台</div> </div> <div class="login-title">管理员登录</div> <div class="form-item"> <label class="form-label">用户名</label> <input type="text" class="form-input" v-model="username" placeholder="请输入管理员用户名" @keyup.enter="handleLogin" /> </div> <div class="form-item"> <label class="form-label">密码</label> <input type="password" class="form-input" v-model="password" placeholder="请输入管理员密码" @keyup.enter="handleLogin" /> </div> <button class="login-btn" @click="handleLogin">登录</button> </div> </div> </template> <script setup> import { ref } from 'vue'; import { useRouter } from 'vue-router'; import { ElMessage } from 'element-plus'; import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome'; import { faGraduationCap, faArrowLeft } from '@fortawesome/free-solid-svg-icons'; import request from '../../utils/request'; const router = useRouter(); const username = ref(''); const password = ref(''); const handleLogin = async () => { // 表单校验 if (!username.value.trim()) return ElMessage.warning('请输入用户名'); if (!password.value.trim()) return ElMessage.warning('请输入密码'); try { const res = await request({ url: '/auth/admin/login', method: 'POST', data: { username: username.value.trim(), password: password.value.trim() } }); if (res.code === 200) { // 存储管理员token和名称 localStorage.setItem('adminToken', res.data.token); // 后端返回的是 data.username(非 data.user.username) localStorage.setItem('adminName', res.data.username); ElMessage.success('登录成功'); router.push('/admin/dashboard'); } else { ElMessage.error(res.msg); } } catch (error) { ElMessage.error('登录失败,请检查后端服务'); console.error('登录报错:', error); } }; </script> <style scoped> .admin-login-page { min-height: 100vh; background-color: #f5f5f5; display: flex; align-items: center; justify-content: center; padding: 20px; box-sizing: border-box; position: relative; } /* 返回按钮样式 */ .back-button { position: absolute; top: 20px; left: 20px; width: 40px; height: 40px; background-color: #ffffff; border-radius: 50%; display: flex; align-items: center; justify-content: center; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); cursor: pointer; transition: all 0.2s ease; z-index: 10; } .back-button:hover { background-color: #f0f0f0; transform: translateY(-2px); box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); } .back-icon { font-size: 18px; color: #4299e1; } .login-container { width: 100%; max-width: 350px; background-color: #ffffff; padding: 32px; border-radius: 12px; box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08); box-sizing: border-box; } .school-dash-logo { text-align: center; margin-bottom: 24px; } .logo-icon { font-size: 48px; color: #4299e1; margin-bottom: 12px; } .logo-text { font-size: 24px; font-weight: 600; color: #333; } .login-title { font-size: 20px; font-weight: 600; color: #333; margin-bottom: 24px; text-align: center; } .form-item { margin-bottom: 20px; } .form-label { display: block; font-size: 14px; color: #333; margin-bottom: 8px; text-align: left; } .form-input { width: 100%; padding: 12px 16px; border: 1px solid #e5e7eb; border-radius: 6px; font-size: 14px; outline: none; box-sizing: border-box; transition: all 0.2s ease; } .form-input:focus { border-color: #4299e1; box-shadow: 0 0 0 2px rgba(66, 153, 225, 0.1); } .login-btn { width: 100%; padding: 14px; background-color: #4299e1; color: #ffffff; border: none; border-radius: 6px; font-size: 16px; cursor: pointer; transition: background-color 0.2s ease; margin-bottom: 16px; } .login-btn:hover { background-color: #3a86cf; } </style>

骑手登陆界面

<template> <div class="rider-login-page"> <div class="back-button" @click="$router.push('/')"> <font-awesome-icon icon="fa-solid fa-arrow-left" class="back-icon" /> </div> <div class="login-container"> <div class="school-dash-logo"> <font-awesome-icon icon="fa-solid fa-motorcycle" class="logo-icon" /> <div class="logo-text">School Dash 骑手端</div> </div> <div class="login-title">骑手登录</div> <div class="form-item"> <label class="form-label">用户名</label> <input type="text" class="form-input" v-model="username" placeholder="请输入用户名" @keyup.enter="handleLogin" /> </div> <div class="form-item"> <label class="form-label">密码</label> <input type="password" class="form-input" v-model="password" placeholder="请输入密码" @keyup.enter="handleLogin" /> </div> <button class="login-btn" @click="handleLogin">登录</button> </div> </div> </template> <script setup> import { ref } from 'vue'; import { useRouter } from 'vue-router'; import { ElMessage } from 'element-plus'; import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome'; import { faMotorcycle, faArrowLeft } from '@fortawesome/free-solid-svg-icons'; import request from '../../utils/request'; const router = useRouter(); const username = ref(''); const password = ref(''); const handleLogin = async () => { // 表单校验 if (!username.value.trim()) return ElMessage.warning('请输入用户名'); if (!password.value.trim()) return ElMessage.warning('请输入密码'); try { const res = await request({ url: '/auth/rider/login', method: 'POST', data: { username: username.value.trim(), password: password.value.trim() } }); if (res.code === 200) { // 存储骑手token和名称 localStorage.setItem('riderToken', res.data.token); // 后端返回的是 data.username localStorage.setItem('riderName', res.data.username); ElMessage.success('登录成功'); router.push('/rider/dashboard'); } else { ElMessage.error(res.msg); } } catch (error) { ElMessage.error('登录失败,请检查后端服务'); console.error('登录报错:', error); } }; </script> <style scoped> .rider-login-page { min-height: 100vh; background-color: #f5f5f5; display: flex; align-items: center; justify-content: center; padding: 20px; box-sizing: border-box; position: relative; } /* 返回按钮样式 */ .back-button { position: absolute; top: 20px; left: 20px; width: 40px; height: 40px; background-color: #ffffff; border-radius: 50%; display: flex; align-items: center; justify-content: center; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); cursor: pointer; transition: all 0.2s ease; z-index: 10; } .back-button:hover { background-color: #f0f0f0; transform: translateY(-2px); box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); } .back-icon { font-size: 18px; color: #4299e1; } .login-container { width: 100%; max-width: 350px; background-color: #ffffff; padding: 32px; border-radius: 12px; box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08); box-sizing: border-box; } .school-dash-logo { text-align: center; margin-bottom: 24px; } .logo-icon { font-size: 48px; color: #4299e1; margin-bottom: 12px; } .logo-text { font-size: 24px; font-weight: 600; color: #333; } .login-title { font-size: 20px; font-weight: 600; color: #333; margin-bottom: 24px; text-align: center; } .form-item { margin-bottom: 20px; } .form-label { display: block; font-size: 14px; color: #333; margin-bottom: 8px; text-align: left; } .form-input { width: 100%; padding: 12px 16px; border: 1px solid #e5e7eb; border-radius: 6px; font-size: 14px; outline: none; box-sizing: border-box; transition: all 0.2s ease; } .form-input:focus { border-color: #4299e1; box-shadow: 0 0 0 2px rgba(66, 153, 225, 0.1); } .login-btn { width: 100%; padding: 14px; background-color: #4299e1; color: #ffffff; border: none; border-radius: 6px; font-size: 16px; cursor: pointer; transition: background-color 0.2s ease; margin-bottom: 16px; } .login-btn:hover { background-color: #3a86cf; } </style>

(测试)

任务:登录功能测试
成果:完成测试(正常登录、错误密码、角色切换),无bug

  1. 本日小结与明日计划

今日总结:认证模块稳定,支持多角色。
存在问题:跨域配置需完善
明日计划:商品列表与分类模块

进度稳健,继续冲!

http://www.cnnetsun.cn/news/105531.html

相关文章:

  • 免费视频增强神器:字节跳动SeedVR2让老视频秒变4K超清画质
  • VSCode Azure QDK 调试失败?(90%开发者都忽略的配置细节)
  • hsweb-framework Easy-ORM终极指南:企业级数据访问实战手册
  • 8、量子计算与技术发展:从理论根源到实际应用
  • 30、RTA API 详解:功能、使用与错误处理
  • 【量子开发效率提升10倍】:VSCode + Azure QDK标准项目模板深度解读
  • ExoPlayer直播优化终极指南:从卡顿诊断到性能提升的完整解决方案
  • 企微SCRM源码分享:源雀SCRM
  • 手把手带你打通Docker Scout+GitHub Actions集成测试全流程
  • 紧急通知:Azure QDK重大版本变更来袭,你的VSCode准备好了吗?
  • 【Azure CLI量子作业资源统计全攻略】:掌握高效资源监控的5大核心命令
  • Docker中调试Vercel AI SDK的3个隐藏技巧,90%开发者都不知道
  • VSCode + Qiskit 环境配置验证全攻略(从零到运行仅需8分钟)
  • 语雀文档备份完整指南:5分钟学会离线文档制作
  • LinearDesign深度解析:5大核心优势助力mRNA序列优化革命
  • Docker Offload任务分配实战精要(附高并发场景调优案例)
  • 窗口置顶功能:打造高效多任务工作环境
  • Docker权限校验全攻略,守护AI模型最后一道防线
  • 3步掌握APKMirror:终极安卓应用下载完全指南
  • 一维卡尔曼滤波实战指南:从理论到代码的完整实现
  • CAD_Sketcher深度解析:基于约束的几何草图系统技术揭秘
  • 玩转macOS光标:Mousecape终极定制指南
  • mpv.net媒体播放器使用指南:打造极致观影体验的完整教程
  • 实战指南:零基础构建智能对话数字人Live2D系统
  • 基于Python+django的大学生自习室预约系统
  • 如何快速掌握Obsidian标题自动编号:笔记爱好者的完整指南
  • VSCode端口映射避坑指南(99%新手都会忽略的关键细节)
  • 终极越狱教程:iPhone 7完美解锁iOS 15+系统权限
  • 26、UNIX与Linux系统的安全、卸载及其他实用知识
  • 终极指南:5步构建企业级Next.js仪表板认证系统