
简介基于FlaskMySQL打造的租房后台系统完整项目包面向Python Web初学者与需要快速搭建管理后台的开发者提供可直接运行的源码、部署文档及全套数据资料并集成支付宝支付功能覆盖用户管理、房源管理、订单支付等典型业务场景。资源共37个文件以Python源码19个py为核心辅以SQL数据库脚本、Markdown部署文档、PEM证书及界面预览图压缩包仅478KB轻量且结构清晰适合本地部署与二次学习。已有104人学习使用说明该项目具备一定参考价值。通过部署文档可完成环境配置与依赖安装配合完整数据库结构和功能模块读者能快速理解Flask蓝图、MySQL交互、支付接口对接等关键知识点也可在此基础上扩展功能用于毕业设计或个人项目。整个项目代码结构清晰、模块划分明确适合作为课程设计或面试项目参考。1. 租房后台系统FlaskMySQL 为什么是稳妥组合租房管理后台往往比预想的更吃数据一致性房源状态要实时、合同租期关联着每期账单、每次支付宝支付回调都要落到流水表里。大多数小团队选型时会纠结用 Django 的一站式方案还是 Flask 的自由组合实际上看完这份项目标题里的结构就知道Flask 负责接口快速落地MySQL 承担所有账目数据支付宝支付用官方 SDK 接进来整体控制在 2000 行上下维护成本比想象中低。这类项目非常适合三类人一是想用真实业务练手 Python 后端的开发者二是在做智慧公寓、短租 SaaS 的产品团队三是需要把一套可运行源码快速部署到服务器上的运维工程师。标题里明确了“部署文档”和“全部数据资料”说明项目不是只有光秃秃的代码还包括表结构和测试数据这对本地复现很关键。下面按一个后台系统的标准图纸依次拆解表结构、接口实现、支付链路和部署步骤。2. FlaskMySQL 租房后台的目录结构与数据表设计2.1 先立业务边界房源、合同、账单、支付流水四张核心表租房后台和普通内容管理系统的差异在于“账要能对上”。一套房源从挂牌到退租中间要经过合同签署、按周期生成账单、租客扫码付款、后台确认到账这几个状态。为了支撑这个流转我一般会把数据模型拆成四张主表外加一张管理员表。表名核心字段作用roomid, room_no, property_name, floor, area, rent_price, status房源基本信息status 区分空置/已租/下架contractid, room_id, tenant_name, tenant_phone, start_date, end_date, deposit一份合同只绑定一个房间记录租期和押金billid, contract_id, period, amount, status, pay_time按合同周期生成的账单status 表示待支付/已支付/已退款payment_flowid, bill_id, trade_no, alipay_trade_no, amount, result支付宝每笔回调的原始记录用于对账之所以把账单和支付流水分开是为了防止回调幂等处理时丢数据。支付宝的异步通知可能重复推送如果没有流水表做唯一约束重复回调会把订单状态改乱。业务上“账单已支付”和“支付流水存在”是两件事分开记录最安全。2.2 Flask 项目的常见目录拆法怎么拆才能支撑后续扩展拿到一份带支付功能的 Flask 项目目录结构基本决定你能多快上手。我常用的拆法是“应用工厂 蓝图 models 分文件”而不是把所有路由堆在一个 app.py 里。这样拆的好处是房源、合同、支付各自的代码边界清晰后续加权限模块或者对接其他支付渠道时不需要动主文件。renting_admin/ ├── app/ │ ├── __init__.py # create_app() 工厂函数 │ ├── extensions.py # db, migrate 等扩展实例化 │ ├── models/ │ │ ├── __init__.py │ │ ├── room.py │ │ ├── contract.py │ │ ├── bill.py │ │ └── payment.py │ ├── api/ │ │ ├── auth.py # 管理员登录 │ │ ├── room.py # 房源管理接口 │ │ ├── contract.py # 合同接口 │ │ └── pay.py # 支付宝下单/回调 │ ├── services/ │ │ └── alipay_service.py │ └── utils/ │ └── decorators.py # 登录校验装饰器 ├── deploy/ │ ├── nginx.conf │ ├── gunicorn.conf.py │ └── init.sql ├── requirements.txt └── run.py2.3 SQLAlchemy 模型定义注意字段类型和索引设计数据表设计里最容易出问题的是金额字段类型。支付宝金额精确到分但业务上习惯了用元这里保持一致即可关键是不能用 Float 存金额。Float 在 MySQL 里是近似值多期账单累计后会出现 0.01 的误差对账时会非常头疼。from datetime import datetime from app.extensions import db class Bill(db.Model): __tablename__ bill id db.Column(db.Integer, primary_keyTrue, autoincrementTrue) contract_id db.Column(db.Integer, db.ForeignKey(contract.id), nullableFalse, indexTrue) period db.Column(db.String(20), nullableFalse, comment账期格式如 2025-06) amount db.Column(db.Numeric(10, 2), nullableFalse, comment应缴金额单位元) status db.Column(db.SmallInteger, default0, comment0待支付 1已支付 2已退款) pay_time db.Column(db.DateTime, nullableTrue) created_at db.Column(db.DateTime, defaultdatetime.now) # 账单和流水是一对多关系 payment_flows db.relationship(PaymentFlow, backrefbill, lazydynamic)这段代码里有几个细节值得注意。金额用 Numeric(10,2) 而不是 Float存整数部分 8 位、小数 2 位对租房场景足够contract_id 加了 index因为账单列表页最常见的查询条件是“按合同查历史账单”不加索引百万级数据后全表扫描会很慢。status 用 SmallInteger 而不是字符串枚举省空间且查询快。2.4 初始化数据的导入路径项目标题里包含“全部数据资料”通常指三类内容建表 SQL、管理员账号初始数据、模拟房源与合同记录。如果拿到的是 .sql 文件导入方式很简单mysql -uroot -p renting_db deploy/init.sql如果是 Flask-Migrate 生成的迁移脚本则走另一条路flask db upgrade python scripts/init_admin.py导入完成后建议立刻验证表是否齐全看看payment_flow有没有唯一约束。很多项目省略了这笔流水表的唯一索引结果生产环境里同一笔支付宝交易被回调两次订单重复置为已支付这是最典型的支付业务事故。3. 后台核心接口登录鉴权、房源状态机与账单生成3.1 管理员登录用 Flask-Login 还是 JWT租房后台通常没有对外开放注册只有内部管理员账号所以不需要复杂的注册找回流程。两种方案都可以传统服务端渲染用 Flask-Login Session前后端分离用 JWT。如果只是后台管理页面我倾向 Flask-Login理由是对新手友好、退出登录直接清 session后端接口用装饰器控制权限直观。from flask_login import LoginManager, login_user, logout_user, login_required from app.models.admin import Admin login_manager LoginManager() login_manager.login_view auth.login login_manager.login_message 请先登录后台 login_manager.user_loader def load_user(admin_id): return Admin.query.get(int(admin_id)) auth_bp.route(/login, methods[POST]) def login(): data request.get_json() admin Admin.query.filter_by(usernamedata.get(username)).first() if admin and check_password_hash(admin.password_hash, data.get(password)): login_user(admin, rememberTrue) return {code: 0, msg: ok} return {code: 1, msg: 用户名或密码错误}装饰器login_required可以直接挂在房源管理、账单管理等所有需要登录的视图上比在 JS 里拦路由可靠得多。密码校验用的是 Werkzeug 的check_password_hash即使数据表泄露也无法反推明文密码。3.2 房源管理接口状态流转要有约束房源最常见的状态是空置、已租、维修、已下架。很多后台系统用普通字段随意改比如租客退租后直接删除合同也不管房间里还有没有未结清账单。我一般会约束房源只有“空置”才能修改租金或者下架否则数据会各种对不上。room_bp.route(/rooms/int:room_id/shelve, methods[POST]) login_required def shelve_room(room_id): room Room.query.get_or_404(room_id) if room.status ! ROOM_STATUS_EMPTY: return {code: 1, msg: 只有空置房源才能下架}, 400 # 检查是否有关联未结清账单 active_bill (Bill.query .join(Contract, Bill.contract_id Contract.id) .filter(Contract.room_id room_id, Bill.status 0) .first()) if active_bill: return {code: 1, msg: 该房源存在未结清账单暂不能下架}, 400 room.status ROOM_STATUS_OFF db.session.commit() return {code: 0, msg: ok}这段逻辑的关键在于房源下架前既看自身状态又查关联账单。如果是“已租”状态不允许下架这是一个必要条件而即使房间已经空置历史欠费没结清也不允许操作。后端多做一层校验前端不管怎么改都不影响数据正确性。3.3 账单生成按合同租期自动滚出清单每份合同签署后通常不需要手工在后台一笔笔录账单。常见做法是提供“按合同生成账单”的按钮后台根据起止日期按月生成账单记录同时排除掉没有居住的时间段。这里最值得写清楚的是累计逻辑。def generate_bills(contract): from dateutil.relativedelta import relativedelta bills [] period_start contract.start_date.replace(day1) while period_start contract.end_date: bill Bill( contract_idcontract.id, periodperiod_start.strftime(%Y-%m), amountcontract.monthly_rent, status0 ) bills.append(bill) period_start relativedelta(months1) db.session.add_all(bills) db.session.commit()封装好之后在合同创建接口里调用即可。注意replace(day1)是为了让循环按月递增时不产生错位如果直接用合同开始时间遇到 1 月 31 日加一个月会直接跳进 3 月。这个坑在账单日靠近月底时特别容易触发测试时建议用 3 月 31 日、5 月 31 日这类日期验证。3.4 列表页查询必带分页和筛选后台列表页最常见的死法是全量查询内存直接打爆。租房业务单表也许只有几万条数据但带着关联查询后性能会掉得很快。分页和筛选参数是必须的。bill_bp.route(/bills) login_required def bill_list(): page request.args.get(page, 1, typeint) per_page request.args.get(per_page, 20, typeint) status request.args.get(status, typeint) room_no request.args.get(room_no, ).strip() query Bill.query if status is not None: query query.filter(Bill.status status) if room_no: query (query.join(Contract) .join(Room) .filter(Room.room_no.like(f%{room_no}%))) pagination query.paginate(pagepage, per_pageper_page, error_outFalse) items [{ id: b.id, period: b.period, amount: str(b.amount), status: b.status, pay_time: b.pay_time.strftime(%Y-%m-%d %H:%M) if b.pay_time else None } for b in pagination.items] return {code: 0, data: items, total: pagination.total}page和per_page用args.get的第二个参数传入默认值同时指定typeint避免恶意请求传非数字导致 500。多表筛选时用query.join连过去注意字段名冲突时要用表名前缀。error_outFalse表示页码超出范围时返回空列表而不是抛 404。4. 支付宝支付在 Flask 里的完整链路预下单、回调验签、退款4.1 接入准备与密钥配置支付宝开放平台接入时需要四个关键配置APP_ID、应用私钥、支付宝公钥、回调地址。在开发阶段使用沙箱环境配置同样适用。密钥相关配置建议放在 Flask 配置对象里不要硬编码到业务代码中。class Config: ALIPAY_APP_ID 2021003126000000 ALIPAY_APP_PRIVATE_KEY open(keys/app_private_key.pem).read() ALIPAY_PUBLIC_KEY open(keys/alipay_public_key.pem).read() ALIPAY_NOTIFY_URL https://admin.example.com/api/pay/alipay/notify ALIPAY_GATEWAY https://openapi-sandbox.dl.alipaydev.com/gateway.do私钥文件不要提交到 Git 仓库部署时单独放在目录外。沙箱网关地址与正式环境的 distinction 在配置里体现切换时只需改一个变量。4.2 支付宝预下单接口的封装from alipay import AliPay def get_alipay(): return AliPay( appidcurrent_app.config[ALIPAY_APP_ID], app_notify_urlcurrent_app.config[ALIPAY_NOTIFY_URL], app_private_key_stringcurrent_app.config[ALIPAY_APP_PRIVATE_KEY], alipay_public_key_stringcurrent_app.config[ALIPAY_PUBLIC_KEY], sign_typeRSA2, debugTrue # 沙箱环境 ) pay_bp.route(/pay/int:bill_id, methods[POST]) login_required def create_order(bill_id): bill Bill.query.get_or_404(bill_id) if bill.status ! 0: return {code: 1, msg: 该账单不在待支付状态} alipay get_alipay() subject f{bill.period}房租-{bill.id} order_string alipay.api_alipay_trade_page_pay( out_trade_nof{bill.id}-{int(time.time())}, total_amountstr(bill.amount), subjectsubject, return_urlhttps://admin.example.com/#/bill, notify_urlcurrent_app.config[ALIPAY_NOTIFY_URL] ) return {code: 0, url: fhttps://openapi-sandbox.dl.alipaydev.com/gateway.do?{order_string}}这里有几个参数的讲究。out_trade_no 是商户订单号长度有限制不能超过 64 个字符把账单 ID 拼上时间戳能保证唯一性。total_amount 必须转成字符串类型金额最多两位小数否则支付宝网关会拒收。return_url 是同步跳转地址一般只做提示业务状态更新必须依赖异步通知。4.3 异步回调验签最容易做错的一步支付宝支付成功的判定完全依赖异步通知。前端跳转回来不代表支付成功后端必须以 notify 回调为准。回调验签的方式是拿到支付宝 POST 过来的表单数据去除 sign 和 sign_type 字段按字典序排序后拼接然后用支付宝公钥验签。pay_bp.route(/pay/alipay/notify, methods[POST]) def alipay_notify(): data request.form.to_dict() alipay get_alipay() success alipay.verify(data, data.pop(sign, None)) if not success: return failure trade_status data.get(trade_status) if trade_status TRADE_SUCCESS: out_trade_no data.get(out_trade_no) trade_no data.get(trade_no) # 支付宝交易号 amount data.get(total_amount) # 先查流水再做幂等更新 flow PaymentFlow.query.filter_by(alipay_trade_notrade_no).first() if flow: return success bill_id int(out_trade_no.split(-)[0]) bill Bill.query.get(bill_id) if bill and bill.status 0 and str(bill.amount) amount: bill.status 1 bill.pay_time datetime.now() db.session.add(PaymentFlow( bill_idbill.id, trade_noout_trade_no, alipay_trade_notrade_no, amountamount )) db.session.commit() return success return failure回调接口必须返回字符串 “success” 给支付宝否则它会按失败策略重试。幂等处理是关键中的关键用 alipay_trade_no 先查流水表已存在就直接返回 success 中断避免重复更新。金额校验也不能省略假如数据库里的账单金额被篡改回调里的 total_amount 跟它不一致时不能标记支付成功。4.4 对账与退款操作后台财务人员最需要的是一个对账页面列出支付流水和账单状态不一致的记录。最简单的方式是写一个查询接口把当日支付流水和账单已支付记录做比对SELECT p.alipay_trade_no, p.amount, b.status FROM payment_flow p LEFT JOIN bill b ON p.bill_id b.id WHERE p.created_at 2025-06-01 00:00:00 AND p.created_at 2025-07-01 00:00:00;退款在租房业务里不常见但押金退回、错缴金额纠正都需要用到。支付宝提供 refund 接口封装方式与下单类似。退款前需要检查当前账单是否确实已支付否则会因余额不足报错。通常还要在后台日志记录“谁在什么时间申请了退款”保留审计线索。5. 部署文档实操从环境搭建到 Nginx 上线的完整步骤5.1 Linux 环境准备与 Python 版本选择部署这类项目推荐使用 Ubuntu 22.04 LTS 搭配 Python 3.10 和 MySQL 8.0。Python 版本低于 3.8 时 Flask 2.x 无法正常安装而 MySQL 5.7 对 JSON 数据类型支持不完善同样建议升到 8.0。sudo apt update sudo apt install -y python3-dev default-libmysqlclient-dev build-essential wget https://registry.npmmirror.com/-/binary/python/3.10.11/Python-3.10.11.tgz tar -xzf Python-3.10.11.tgz cd Python-3.10.11 ./configure --enable-optimizations make -j2 sudo make install如果项目自带 requirements.txt安装依赖前先确认 pip 源。很多项目默认用的官方源在国内下载慢超时后会误判安装失败pip3 install -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple5.2 数据库创建与数据导入顺序MySQL 8.0 的密码认证插件是 caching_sha2_passwordPython 的 PyMySQL 需要较新版本才能适配。先用 root 创建库和账号再导入数据mysql -uroot -p CREATE DATABASE renting_db DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; CREATE USER renting_userlocalhost IDENTIFIED BY YourPassword123!; GRANT ALL PRIVILEGES ON renting_db.* TO renting_userlocalhost; FLUSH PRIVILEGES;utf8mb4 字符集是必选项否则租客姓名里出现 emoji 或者特殊符号时保存会报错。导入 SQL 文件时注意文件里有没有跨库引用使用USE renting_db;先切库再执行。5.3 Gunicorn Supervisor 守护进程Flask 自带的开发服务器只能用于本地调试线上必须换 Gunicorn 这类 WSGI 服务器。创建gunicorn.conf.py显式指定绑定地址和工作进程数workers 2 threads 4 bind 127.0.0.1:8000 timeout 30 accesslog /var/log/renting_admin/access.log errorlog /var/log/renting_admin/error.log工作进程数一般设置为 CPU 核心数 1不需要盲目调大。Gunicorn 用 Supervisor 托管避免进程崩溃后没人拉起来。Supervisor 配置文件的 sample 写法如下[program:renting_admin] command /usr/local/bin/gunicorn -c /srv/renting_admin/gunicorn.conf.py run:app directory /srv/renting_admin user www-data autostart true autorestart true stderr_logfile /var/log/renting_admin_supervisor.err.log stdout_logfile /var/log/renting_admin_supervisor.out.log5.4 Nginx 反向代理配置Nginx 不需要处理 Python 请求只需要把 HTTP 请求转发给本机的 Gunicorn。同时需要放行支付宝回调的 POST 请求注意默认的proxy_read_timeout如果太短大表单可能会被截断。server { listen 80; server_name admin.example.com; location / { proxy_pass http://127.0.0.1:8000; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_read_timeout 30s; } location /static { alias /srv/renting_admin/app/static; expires 7d; } }5.5 部署时最常见的三个报错第一个是ModuleNotFoundError: No module named MySQLdb这是没装 PyMySQL 或者没在__init__.py里完成兼容注册。解决办法是在运行前执行import pymysql pymysql.install_as_MySQLdb()第二个是支付宝回调failure常见原因是 Nginx 对回调请求做了重定向丢失了原始 POST 参数。建议在 Nginx 配置里检查有没有return 301之类的规则回调地址必须用最终实际地址。第三个是数据库连接报Authentication plugin caching_sha2_password cannot be loaded那是 PyMySQL 版本过旧。升级依赖后重试即可。6. 支付宝支付联调的十个检查点与故障定位技巧支付模块上线前建议拿着这份清单过一遍沙箱测试每一条都有对应的真实事故案例检查点通过标准out_trade_no 唯一性连续生成 100 个订单号无重复金额精度分转元后与支付宝回调一致无浮点误差异步回调幂等同一笔通知重复发送订单状态不变验签失败处理返回 failure支付宝可重试回调地址公网可访问沙箱里配置的 notify_url 能从外网 POST 通支付成功后页面跳转return_url 返回页面显示已支付待确认部分退款场景退款后 bill 状态变为已退款账单与流水对账按月对比无差账密钥文件权限服务器上私钥文件权限为 600日志记录完整度每笔回调都有 trace_id 可追溯排查故障时先看三个日志Gunicorn 的 error.log 看应用层报错Nginx 的 access.log 看有没有收到支付宝的 POST 请求支付宝开放平台的沙箱控制台看在线的通知记录。三者对照基本能定位绝大部分问题。最后提一个容易被忽略的细节支付宝回调时会对同一条 notify_url 同时发送多台服务器请求不要用单机数据库锁去硬抗正确做法是在流水表上建唯一索引让冲突插入直接报错再靠应用层的幂等判断兜底。这套方案的可靠性在租房长租场景里足够应对一年几万笔的交易量了。本文还有配套的精品资源点击获取