Flask与Vue构建社区运动场馆预约平台
技术栈概览
服务端选用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