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. 本日小结与明日计划

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

进度稳健,继续冲!

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值