请添加图片描述

一、系统文档

1. 需求分析

1.1 功能需求
角色 核心功能 具体描述
学生 账号管理 注册、登录、密码重置(绑定校园邮箱)
学生 自习室查询 按楼层、座位类型(靠窗/靠电源)筛选,查看座位占用状态
学生 预约操作 选择日期、时间段(1小时/2小时)预约座位,预约后30分钟未到自动释放
学生 预约管理 查看个人预约记录,取消未开始的预约
管理员 自习室管理 添加/删除自习室,编辑座位信息(编号、状态、是否有电源)
管理员 预约审核 处理异常预约(如重复预约),手动释放长期占用座位
管理员 数据统计 查看每日/每周预约量、座位使用率报表
1.2 非功能需求
  • 性能:单页面加载时间≤2秒,支持500人同时在线操作
  • 安全:学生账号与校园统一身份认证关联,密码加密存储(MD5)
  • 兼容性:支持Chrome、Edge等主流浏览器,适配电脑端/平板端

2. 系统设计

2.1 技术栈
  • 后端:Python 3.9 + Django 4.2(MVT架构)
  • 前端:HTML + CSS(Bootstrap 5) + JavaScript(JQuery)
  • 数据库:MySQL 8.0
  • 部署:本地开发(Python manage.py runserver)
2.2 数据库设计(ER图核心实体)
  • User(用户表):id(主键)、username(学号)、password(加密密码)、email(校园邮箱)、role(角色:student/admin)
  • Classroom(自习室表):id(主键)、floor(楼层)、room_num(房间号)、total_seats(总座位数)
  • Seat(座位表):id(主键)、classroom_id(外键关联自习室)、seat_num(座位号)、has_power(是否有电源:0/1)、status(状态:0-空闲/1-占用)
  • Reservation(预约表):id(主键)、user_id(外键关联用户)、seat_id(外键关联座位)、reserve_date(预约日期)、start_time(开始时间)、end_time(结束时间)、is_valid(是否有效:0-已取消/1-有效)
2.3 系统架构
高校自习室预约系统
├── 前端层(用户界面)
│   ├── 学生端(登录/预约/查询)
│   └── 管理员端(管理/统计)
├── 后端层(Django核心)
│   ├── 视图层(views.py:处理请求逻辑)
│   ├── 模型层(models.py:数据库映射)
│   ├── 路由层(urls.py:URL分发)
│   └── 表单层(forms.py:数据验证)
└── 数据层(MySQL)
    └── 4张核心数据表(User/Classroom/Seat/Reservation)

3. 测试与部署

3.1 功能测试用例
测试模块 测试步骤 预期结果
座位预约 1. 学生登录
2. 选择3楼自习室、靠窗有电源座位
3. 预约次日14:00-16:00
预约成功,座位状态变为“占用”,个人中心显示预约记录
预约取消 1. 进入“我的预约”
2. 选择未开始的预约
3. 点击“取消预约”
预约状态变为“已取消”,座位状态恢复“空闲”
3.2 部署步骤
  1. 安装依赖:pip install django==4.2 mysqlclient==2.2.0
  2. 配置数据库:修改settings.pyDATABASES参数(数据库名、用户名、密码)
  3. 数据迁移:python manage.py makemigrationspython manage.py migrate
  4. 创建管理员:python manage.py createsuperuser(设置账号密码)
  5. 启动系统:python manage.py runserver 0.0.0.0:8000
  6. 访问地址:学生端http://localhost:8000/student/,管理员端http://localhost:8000/admin/

二、核心代码实现

1. 模型层(models.py)

from django.db import models
from django.contrib.auth.models import AbstractBaseUser, BaseUserManager

# 自定义用户管理器
class UserManager(BaseUserManager):
    def create_user(self, username, email, password=None):
        if not email:
            raise ValueError("必须填写校园邮箱")
        user = self.model(
            username=username,
            email=self.normalize_email(email)
        )
        user.set_password(password)  # 自动加密密码
        user.save(using=self._db)
        return user

    def create_superuser(self, username, email, password):
        user = self.create_user(
            username=username,
            email=email,
            password=password
        )
        user.role = 'admin'  # 管理员角色
        user.save(using=self._db)
        return user

# 用户表
class User(AbstractBaseUser):
    ROLE_CHOICES = (
        ('student', '学生'),
        ('admin', '管理员'),
    )
    username = models.CharField(max_length=20, unique=True, verbose_name="学号")
    email = models.EmailField(max_length=50, unique=True, verbose_name="校园邮箱")
    role = models.CharField(max_length=10, choices=ROLE_CHOICES, default='student', verbose_name="角色")
    is_active = models.BooleanField(default=True, verbose_name="是否激活")

    objects = UserManager()

    USERNAME_FIELD = 'username'  # 登录字段(学号)
    REQUIRED_FIELDS = ['email']  # 创建用户必填字段

    def __str__(self):
        return self.username

# 自习室表
class Classroom(models.Model):
    floor = models.IntegerField(verbose_name="楼层")
    room_num = models.CharField(max_length=10, verbose_name="房间号")
    total_seats = models.IntegerField(verbose_name="总座位数")

    def __str__(self):
        return f"{self.floor}楼-{self.room_num}室"

# 座位表
class Seat(models.Model):
    classroom = models.ForeignKey(Classroom, on_delete=models.CASCADE, related_name="seats", verbose_name="所属自习室")
    seat_num = models.CharField(max_length=10, verbose_name="座位号")
    has_power = models.BooleanField(default=False, verbose_name="是否有电源")
    status = models.BooleanField(default=False, verbose_name="状态(0-空闲/1-占用)")

    def __str__(self):
        return f"{self.classroom}-{self.seat_num}号"

# 预约表
class Reservation(models.Model):
    user = models.ForeignKey(User, on_delete=models.CASCADE, related_name="reservations", verbose_name="预约用户")
    seat = models.ForeignKey(Seat, on_delete=models.CASCADE, related_name="reservations", verbose_name="预约座位")
    reserve_date = models.DateField(verbose_name="预约日期")
    start_time = models.TimeField(verbose_name="开始时间")
    end_time = models.TimeField(verbose_name="结束时间")
    is_valid = models.BooleanField(default=True, verbose_name="是否有效")
    create_time = models.DateTimeField(auto_now_add=True, verbose_name="创建时间")

    def __str__(self):
        return f"{self.user.username}-{self.seat}-{self.reserve_date}"

2. 视图层(学生端预约功能,views.py)

from django.shortcuts import render, redirect, get_object_or_404
from django.contrib.auth import authenticate, login
from django.contrib import messages
from .models import User, Classroom, Seat, Reservation
from .forms import ReservationForm
from datetime import date, datetime

# 学生登录
def student_login(request):
    if request.method == "POST":
        username = request.POST.get("username")
        password = request.POST.get("password")
        user = authenticate(request, username=username, password=password)
        if user and user.role == "student":
            login(request, user)
            return redirect("student:seat_list")  # 跳转至座位列表页
        else:
            messages.error(request, "学号或密码错误,或非学生账号")
    return render(request, "student/login.html")

# 座位列表查询
def seat_list(request):
    if not request.user.is_authenticated or request.user.role != "student":
        return redirect("student:login")
    
    # 筛选条件:楼层、是否有电源
    floor = request.GET.get("floor")
    has_power = request.GET.get("has_power")
    
    seats = Seat.objects.all()
    if floor:
        seats = seats.filter(classroom__floor=floor)
    if has_power in ["0", "1"]:
        seats = seats.filter(has_power=bool(int(has_power)))
    
    return render(request, "student/seat_list.html", {
        "seats": seats,
        "floors": Classroom.objects.values_list("floor", flat=True).distinct()  # 所有楼层选项
    })

# 座位预约
def reserve_seat(request, seat_id):
    if not request.user.is_authenticated or request.user.role != "student":
        return redirect("student:login")
    
    seat = get_object_or_404(Seat, id=seat_id)
    today = date.today()
    
    if request.method == "POST":
        form = ReservationForm(request.POST)
        if form.is_valid():
            reserve_date = form.cleaned_data["reserve_date"]
            start_time = form.cleaned_data["start_time"]
            end_time = form.cleaned_data["end_time"]
            
            # 校验:预约日期不能小于今天,开始时间小于结束时间
            if reserve_date < today:
                messages.error(request, "预约日期不能小于今天")
                return render(request, "student/reserve.html", {"form": form, "seat": seat})
            if start_time >= end_time:
                messages.error(request, "开始时间必须早于结束时间")
                return render(request, "student/reserve.html", {"form": form, "seat": seat})
            
            # 校验:该时间段座位是否已被预约
            existing_reserve = Reservation.objects.filter(
                seat=seat,
                reserve_date=reserve_date,
                is_valid=True,
                start_time__lt=end_time,  # 避免时间重叠
                end_time__gt=start_time
            ).exists()
            if existing_reserve:
                messages.error(request, "该时间段座位已被预约,请选择其他时间")
                return render(request, "student/reserve.html", {"form": form, "seat": seat})
            
            # 创建预约记录
            Reservation.objects.create(
                user=request.user,
                seat=seat,
                reserve_date=reserve_date,
                start_time=start_time,
                end_time=end_time
            )
            # 更新座位状态为占用
            seat.status = True
            seat.save()
            
            messages.success(request, "预约成功!请在预约时间前30分钟到达")
            return redirect("student:reserve_record")
    else:
        form = ReservationForm(initial={"reserve_date": today})  # 默认预约今天
    
    return render(request, "student/reserve.html", {"form": form, "seat": seat})

# 个人预约记录
def reserve_record(request):
    if not request.user.is_authenticated or request.user.role != "student":
        return redirect("student:login")
    
    # 只显示当前用户的有效预约,按预约日期排序
    records = Reservation.objects.filter(
        user=request.user,
        is_valid=True
    ).order_by("reserve_date", "start_time")
    
    return render(request, "student/reserve_record.html", {"records": records})

# 取消预约
def cancel_reserve(request, record_id):
    if not request.user.is_authenticated or request.user.role != "student":
        return redirect("student:login")
    
    record = get_object_or_404(Reservation, id=record_id, user=request.user)
    # 校验:只能取消未开始的预约
    now = datetime.now()
    reserve_datetime = datetime.combine(record.reserve_date, record.start_time)
    if reserve_datetime < now:
        messages.error(request, "只能取消未开始的预约")
        return redirect("student:reserve_record")
    
    # 取消预约:更新状态
    record.is_valid = False
    record.save()
    # 恢复座位空闲状态(需确认该座位无其他有效预约)
    seat = record.seat
    has_other_reserve = Reservation.objects.filter(
        seat=seat,
        is_valid=True,
        reserve_date__gte=date.today()
    ).exists()
    if not has_other_reserve:
        seat.status = False
        seat.save()
    
    messages.success(request, "预约已取消")
    return redirect("student:reserve_record")

3. 前端页面(座位列表页,seat_list.html)

{% extends "student/base.html" %}
{% block content %}
<div class="container mt-4">
    <h2>自习室座位查询</h2>
    <!-- 筛选表单 -->
    <form method="get" class="row g-3 mt-3">
        <div class="col-md-4">
            <label class="form-label">楼层</label>
            <select name="floor" class="form-select" onchange="this.form.submit()">
                <option value="">全部楼层</option>
                {% for floor in floors %}
                <option value="{{ floor }}" {% if request.GET.floor == floor|stringformat:"i" %}selected{% endif %}>
                    {{ floor }}楼
                </option>
                {% endfor %}
            </select>
        </div>
        <div class="col-md-4">
            <label class="form-label">座位类型</label>
            <select name="has_power" class="form-select" onchange="this.form.submit()">
                <option value="">全部类型</option>
                <option value="1" {% if request.GET.has_power == "1" %}selected{% endif %}>有电源</option>
                <option value="0" {% if request.GET.has_power == "0" %}selected{% endif %}>无电源</option>
            </select>
        </div>
    </form>

    <!-- 座位列表 -->
    <div class="row mt-4">
        {% for seat in seats %}
        <div class="col-md-3 mb-3">
            <div class="card {% if seat.status %}bg-secondary text-white{% else %}bg-light{% endif %}">
                <div class="card-body">
                    <h5 class="card-title">{{ seat }}</h5>
                    <p class="card-text">
                        状态:{% if seat.status %}已占用{% else %}空闲{% endif %}<br>
                        电源:{% if seat.has_power %}有{% else %}无{% endif %}
                    </p>
                    {% if not seat.status %}
                    <a href="{% url 'student:reserve_seat' seat.id %}" class="btn btn-primary">立即预约</a>
                    {% endif %}
                </div>
            </div>
        </div>
        {% empty %}
        <div class="col-12">暂无符合条件的座位</div>
        {% endfor %}
    </div>
</div>
{% endblock %}

4. 表单验证(forms.py)

from django import forms
from .models import Reservation
from datetime import date

class ReservationForm(forms.ModelForm):
    class Meta:
        model = Reservation
        fields = ["reserve_date", "start_time", "end_time"]
        widgets = {
            "reserve_date": forms.DateInput(attrs={"type": "date", "class": "form-control"}),
            "start_time": forms.TimeInput(attrs={"type": "time", "class": "form-control"}),
            "end_time": forms.TimeInput(attrs={"type": "time", "class": "form-control"}),
        }
    
    # 自定义验证:预约日期不能超过7天(最多预约下周)
    def clean_reserve_date(self):
        reserve_date = self.cleaned_data.get("reserve_date")
        if reserve_date > date.today():
            delta = reserve_date - date.today()
            if delta.days > 7:
                raise forms.ValidationError("最多只能预约7天内的座位")
        return reserve_date

三、系统功能演示说明

  1. 学生登录:输入学号(如“2023001”)和密码,登录后进入座位查询页;
  2. 座位筛选:选择“3楼”和“有电源”,筛选出符合条件的空闲座位;
  3. 预约操作:点击“立即预约”,选择日期(如今天)、时间段(14:00-16:00),提交后完成预约;
  4. 记录管理:在“我的预约”中查看已预约记录,若需取消
Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐