当前位置:首页 > 技术 > 正文内容

Flask与Vue构建社区运动场馆预约平台

访客 技术 2026年6月12日 1

技术栈概览

服务端选用Python Flask微框架,配合SQLAlchemy ORM进行数据持久化;客户端采用Vue 3组合式API构建单页应用;持久层支持MySQL或PostgreSQL;IDE推荐PyCharm专业版。

功能架构设计

系统划分为四大核心域:

  • 身份域:会员注册、多角色鉴权(居民/管理员/运营人员)
  • 资源域:场馆类型管理(羽毛球/篮球/乒乓球)、时段库存控制
  • 订单域:时段锁定、支付状态流转、退订规则引擎
  • 运营域:预约数据看板、黑名单机制、公告推送

数据模型层

from flask_sqlalchemy import SQLAlchemy
db = SQLAlchemy()

class Resident(db.Model):
    __tablename__ = 'residents'
    rid = db.Column(db.Integer, primary_key=True)
    mobile = db.Column(db.String(11), unique=True, nullable=False)
    encrypted_pwd = db.Column(db.String(256))
    estate_addr = db.Column(db.String(200))
    credit_score = db.Column(db.Integer, default=100)

class Venue(db.Model):
    __tablename__ = 'venues'
    vid = db.Column(db.Integer, primary_key=True)
    category = db.Column(db.Enum('badminton', 'basketball', 'table_tennis'))
    position = db.Column(db.String(100))
    hourly_rate = db.Column(db.Numeric(10, 2))
    is_active = db.Column(db.Boolean, default=True)

class Reservation(db.Model):
    __tablename__ = 'reservations'
    oid = db.Column(db.Integer, primary_key=True)
    resident_id = db.Column(db.Integer, db.ForeignKey('residents.rid'))
    venue_id = db.Column(db.Integer, db.ForeignKey('venues.vid'))
    start_at = db.Column(db.DateTime)
    duration = db.Column(db.Integer)  # 单位:小时
    order_status = db.Column(db.Enum('pending', 'confirmed', 'cancelled', 'completed'))
    created_on = db.Column(db.DateTime, server_default=db.func.now())

RESTful接口实现

采用Blueprint模块化组织路由,使用Marshmallow进行序列化:

from flask import Blueprint, request, jsonify
from marshmallow import Schema, fields, validate

booking_bp = Blueprint('booking', __name__, url_prefix='/v1')

class SlotSchema(Schema):
    venue = fields.Integer(required=True)
    begin_time = fields.DateTime(required=True)
    hours = fields.Integer(validate=validate.Range(min=1, max=4))

@booking_bp.route('/slots', methods=['POST'])
def occupy_slot():
    payload = request.get_json()
    schema = SlotSchema()
    errors = schema.validate(payload)
    if errors:
        return jsonify(code=400, msg="参数校验失败", detail=errors), 400
    
    # 乐观锁防止超卖
    result = db.session.execute("""
        UPDATE venues SET available_count = available_count - 1 
        WHERE vid = :vid AND available_count > 0
    """, {"vid": payload['venue']})
    
    if result.rowcount == 0:
        return jsonify(code=409, msg="该时段已满"), 409
    
    new_order = Reservation(
        resident_id=g.current_user.rid,
        venue_id=payload['venue'],
        start_at=payload['begin_time'],
        duration=payload['hours']
    )
    db.session.add(new_order)
    db.session.commit()
    
    return jsonify(code=201, data={"order_no": new_order.oid}), 201

前端状态管理

Vue端使用Pinia管理预约流程状态,Axios封装请求拦截器:

import { defineStore } from 'pinia'
import { httpClient } from '@/utils/request'

export const useBookingStore = defineStore('booking', {
  state: () => ({
    selectedVenue: null,
    timeRange: [],
    currentStep: 1
  }),
  
  actions: {
    async submitReservation(formData) {
      try {
        const resp = await httpClient.post('/v1/slots', {
          venue: this.selectedVenue.id,
          begin_time: formData.date + ' ' + formData.startHour,
          hours: formData.period
        })
        this.currentStep = 3
        return resp.data
      } catch (err) {
        if (err.response?.status === 409) {
          throw new Error('该时段已被预约,请选择其他时间')
        }
        throw err
      }
    }
  }
})

安全认证机制

采用双Token策略(Access Token + Refresh Token),使用PyJWT签发:

import jwt
from datetime import datetime, timedelta

def issue_token_pair(user_identity):
    now = datetime.utcnow()
    access_payload = {
        "sub": user_identity,
        "iat": now,
        "exp": now + timedelta(minutes=30),
        "type": "access"
    }
    refresh_payload = {
        "sub": user_identity,
        "iat": now,
        "exp": now + timedelta(days=7),
        "type": "refresh"
    }
    return {
        "access_token": jwt.encode(access_payload, SECRET_KEY, algorithm="HS256"),
        "refresh_token": jwt.encode(refresh_payload, SECRET_KEY, algorithm="HS256")
    }

性能优化策略

  • 缓存层:Redis缓存场馆未来7天可用时段,TTL设置5分钟
  • 连接池:SQLAlchemy配置连接池复用,避免频繁创建连接
  • 异步处理:Celery处理预约成功后的短信通知和邮件提醒
  • WebSocket:Socket.IO实现管理员端的实时预约动态推送

生产环境部署

Docker Compose编排服务栈:

version: '3.8'
services:
  api:
    build: ./backend
    command: gunicorn -w 4 -b 0.0.0.0:8000 "app:create_app()"
    environment:
      - DATABASE_URL=mysql+pymysql://root:pass@db/estate_db
      - REDIS_URL=redis://cache:6379/0
  nginx:
    image: nginx:alpine
    volumes:
      - ./nginx.conf:/etc/nginx/conf.d/default.conf
      - ./frontend/dist:/usr/share/nginx/html
    ports:
      - "80:80"
  db:
    image: mysql:8.0
    environment:
      MYSQL_ROOT_PASSWORD: pass
      MYSQL_DATABASE: estate_db

相关文章

Linux crontab 详解

1) crontab 是什么cron 是 Linux 的定时任务守护进程;crontab 是用来编辑/查看“按时间周期执行命令”的表(cron table)。常见两类:用户 crontab:每个用户一份(crontab -e 编辑)系统级 crontab / cron.d:可指定执行用户(/etc/crontab、/etc/cron.d/*)2) crontab 时间...

富文本里可以允许的 HTML 属性

一、所有标签默认允许的安全属性(极少)class        (可选)id           (通常建议禁用)title️ 注意:id 容易被滥用做锚点注入,很多系统直接禁用class 允许的话最好只允许固定前缀(如 editor-*)二、a 标签允许属性<a href="" t...

Mac 安装 Node.js 指南

方法一:通过官网安装包(最简单,适合初学者)如果你只是想快速安装并开始使用,这是最直接的方法。访问 Node.js 官网。页面会显示两个版本:LTS (Recommended For Most Users):长期支持版,最稳定。建议选这个。Current:最新特性版,包含最新功能但可能不够稳定。下载 .pkg 安装包并运行。按照安装向导点击“下一步”即可完成。方法二:使用 Homebrew 安装(...

Dom\HTML_NO_DEFAULT_NS 的副作用:自动加闭合标签

在使用Dom\HTMLDocument时,Dom\HTML_NO_DEFAULT_NS 将禁止在解析过程中设置元素的命名空间, 此设置是为了与DOMDocument向后兼容而存在的。当使用它时,已知的一个副作用就是:自动加闭合标签例如 </img> 为什么会这样?当你使用:Dom\HTML_NO_DEFAULT_NS文档会变成 无命名空间模式,此时内部更接近 XML...

Laravel 事件和监听器创建

在 Laravel 中,使用 Artisan 命令创建 Events(事件) 和 Listeners(监听器) 是非常高效的。你可以通过以下几种方式来实现:1. 手动创建单个 Event如果你只想创建一个事件类,可以使用 make:event 命令:Bashphp artisan make:event UserRegistered执行后,文件将生成在 app/Even...

自定义域名解析神器 dnsmasq

什么是 dnsmasq?dnsmasq 是一个轻量级、功能强大的网络服务工具,专为小型和中等规模网络设计。它是一个综合的网络基础设施解决方案[1]。dnsmasq 能做什么?功能说明应用场景DNS 转发与缓存将 DNS 查询转发到上游服务器(ISP、Google DNS 等),并在本地缓存结果加快 DNS 查询速度,减少外部 DNS 流量本地 DNS解析本地网络设备的主机名,无需编辑&n...

发表评论

访客

◎欢迎参与讨论,请在这里发表您的看法和观点。