Flask SQLAlchemy新增预约时多条件联合校验防重复问题排查
问题原因
你的多条件重复校验逻辑核心错误是:将需要匹配同一条预约记录的多个校验条件拆成了多个独立的数据库exists()查询,每个查询单独执行、单独判断全表是否存在符合单个条件的记录,没有要求车位/车牌、时间范围这些条件同时命中同一条数据。
举个实际场景:如果库里已经有A车位9点-10点的预约,同时又有B车位11点-12点的预约,你提交A车位11点-12点的新预约时,代码会查到“存在A车位的记录”、“存在11点-12点区间的记录”,就会误判冲突,但实际上这两条是完全不相关的预约,根本没有冲突。
另外你写的时间重叠判断逻辑也有遗漏,没有覆盖新预约完全包含已有预约、已有预约完全包含新预约等全量重叠场景,判断规则写得过于冗余还不全。
原代码第一个校验分支末尾还多写了一个右括号,属于语法错误,运行时会直接报错。
修正方法
把两个冲突校验分支的多个独立查询合并为单条查询,用and_()把所有针对同一条记录的匹配条件包裹起来,让数据库直接查找是否存在同时满足所有条件的单条预约记录;同时简化时间重叠判断逻辑,用通用的区间重叠规则覆盖所有冲突场景。
修正后的完整代码如下:
from flask import Flask, render_template, url_for, request, redirect, flash from flask_sqlalchemy import SQLAlchemy from sqlalchemy.sql import and_ from datetime import datetime, timedelta app = Flask(__name__) app.config['SESSION_TYPE'] = 'memcached' app.config['SECRET_KEY'] = 'super secret key' app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///parking_booking.db' db = SQLAlchemy(app) # 建议把Parking模型的start、end字段改为DateTime类型,避免字符串隐式转换的比较问题 class Parking(db.Model): id = db.Column(db.Integer, primary_key=True) spot_number = db.Column(db.String(2)) license_plate = db.Column(db.String(10)) start = db.Column(db.DateTime) # 原定义为String(20),建议修改 end = db.Column(db.DateTime) # 原定义为String(20),建议修改 date_created = db.Column(db.DateTime, default=datetime.utcnow) def __repr__(self): return '<Parking %r>' % self.id @app.route('/', methods=['POST', 'GET']) def index(): if request.method == 'POST': booking_spot_number = str(request.form['spot_number']) booking_license_plate = str(request.form['license_plate'].replace(' ', '').upper()) booking_start = datetime(*[int(v) for v in request.form['start'].replace('T', '-').replace(':', '-').split('-')]) booking_end = datetime(*[int(v) for v in request.form['end'].replace('T', '-').replace(':', '-').split('-')]) if booking_end < booking_start: flash('End time has to be later than start time', category='error') elif booking_start < datetime.now(): flash('Start time can\'t be in the past.', category='error') elif (booking_end - booking_start) < timedelta(minutes=10): flash('Minimum booking time is 10 minutes.', category='error') # 同车位时间冲突校验:所有条件放在同一个and_中,匹配同一条记录 elif db.session.query(db.exists().where( and_( Parking.spot_number == booking_spot_number, Parking.start < booking_end, Parking.end > booking_start ) )).scalar(): flash(f'Spot {booking_spot_number} has already been booked for the selected time. Choose another parking spot or different time.', category='error' ) # 同车牌时间冲突校验:所有条件放在同一个and_中,匹配同一条记录 elif db.session.query(db.exists().where( and_( Parking.license_plate == booking_license_plate, Parking.start < booking_end, Parking.end > booking_start ) )).scalar(): flash(f'A car with license plate {booking_license_plate} has booked another spot for selected time.', category='error' ) else: new_booking = Parking(spot_number = booking_spot_number, license_plate = booking_license_plate, start = booking_start, end = booking_end) db.session.add(new_booking) db.session.commit() flash('New booking has been added', category='success') bookings = Parking.query.order_by(Parking.start.asc()).all() return render_template('index.html', bookings = bookings) if __name__ == '__main__': app.run(debug=True)
注:时间区间重叠判断规则
已有记录开始时间 < 新记录结束时间 AND 已有记录结束时间 > 新记录开始时间是通用的无遗漏判断逻辑,可以覆盖两个区间部分重叠、完全包含、端点相接不冲突等所有场景,不需要拆分多个or分支判断。
内容的提问来源于stack exchange,提问作者Marreco
相关产品推荐
相关产品推荐

