SchoolDash Alpha冲刺随笔2 - Day 3
课程与作业信息
所属课程:软件工程实践
作业要求来源:第五次作业——Alpha冲刺
本篇目标:记录冲刺第3天进度,展示燃尽图、运行效果、成员工作成果
-
项目燃尽图(Burn-up Chart)

已完成需求的75% -
本日冲刺整体进展
完成用户注册、登录功能,后端JWT验证,前端多角色Token存储。
解决角色权限问题:实现不同角色(用户、骑手、管理员)独立Token管理。
- 项目最新运行效果
用户端注册界面

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

骑手端登录界面

- 今日工作成果
(后端开发)
认证路由与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
- 本日小结与明日计划
今日总结:认证模块稳定,支持多角色。
存在问题:跨域配置需完善
明日计划:商品列表与分类模块
进度稳健,继续冲!

被折叠的 条评论
为什么被折叠?



