news 2026/9/15 6:26:55

Flask+MySQL租房后台系统实战:支付宝支付与部署全解析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Flask+MySQL租房后台系统实战:支付宝支付与部署全解析

简介:基于Flask+MySQL打造的租房后台系统完整项目包,面向Python Web初学者与需要快速搭建管理后台的开发者,提供可直接运行的源码、部署文档及全套数据资料,并集成支付宝支付功能,覆盖用户管理、房源管理、订单支付等典型业务场景。资源共37个文件,以Python源码(19个py)为核心,辅以SQL数据库脚本、Markdown部署文档、PEM证书及界面预览图,压缩包仅478KB,轻量且结构清晰,适合本地部署与二次学习。已有104人学习使用,说明该项目具备一定参考价值。通过部署文档可完成环境配置与依赖安装,配合完整数据库结构和功能模块,读者能快速理解Flask蓝图、MySQL交互、支付接口对接等关键知识点,也可在此基础上扩展功能用于毕业设计或个人项目。整个项目代码结构清晰、模块划分明确,适合作为课程设计或面试项目参考。

1. 租房后台系统,Flask+MySQL 为什么是稳妥组合

租房管理后台往往比预想的更吃数据一致性:房源状态要实时、合同租期关联着每期账单、每次支付宝支付回调都要落到流水表里。大多数小团队选型时会纠结用 Django 的一站式方案还是 Flask 的自由组合,实际上看完这份项目标题里的结构就知道,Flask 负责接口快速落地,MySQL 承担所有账目数据,支付宝支付用官方 SDK 接进来,整体控制在 2000 行上下,维护成本比想象中低。

这类项目非常适合三类人:一是想用真实业务练手 Python 后端的开发者,二是在做智慧公寓、短租 SaaS 的产品团队,三是需要把一套可运行源码快速部署到服务器上的运维工程师。标题里明确了“部署文档”和“全部数据资料”,说明项目不是只有光秃秃的代码,还包括表结构和测试数据,这对本地复现很关键。下面按一个后台系统的标准图纸,依次拆解表结构、接口实现、支付链路和部署步骤。

2. Flask+MySQL 租房后台的目录结构与数据表设计

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.py

2.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_key=True, autoincrement=True) contract_id = db.Column(db.Integer, db.ForeignKey('contract.id'), nullable=False, index=True) period = db.Column(db.String(20), nullable=False, comment='账期,格式如 2025-06') amount = db.Column(db.Numeric(10, 2), nullable=False, comment='应缴金额,单位元') status = db.Column(db.SmallInteger, default=0, comment='0待支付 1已支付 2已退款') pay_time = db.Column(db.DateTime, nullable=True) created_at = db.Column(db.DateTime, default=datetime.now) # 账单和流水是一对多关系 payment_flows = db.relationship('PaymentFlow', backref='bill', lazy='dynamic')

这段代码里有几个细节值得注意。金额用 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(username=data.get('username')).first() if admin and check_password_hash(admin.password_hash, data.get('password')): login_user(admin, remember=True) 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(day=1) while period_start <= contract.end_date: bill = Bill( contract_id=contract.id, period=period_start.strftime('%Y-%m'), amount=contract.monthly_rent, status=0 ) bills.append(bill) period_start += relativedelta(months=1) db.session.add_all(bills) db.session.commit()

封装好之后,在合同创建接口里调用即可。注意replace(day=1)是为了让循环按月递增时不产生错位,如果直接用合同开始时间,遇到 1 月 31 日加一个月会直接跳进 3 月。这个坑在账单日靠近月底时特别容易触发,测试时建议用 3 月 31 日、5 月 31 日这类日期验证。

3.4 列表页查询,必带分页和筛选

后台列表页最常见的死法是全量查询内存直接打爆。租房业务单表也许只有几万条数据,但带着关联查询后性能会掉得很快。分页和筛选参数是必须的。

@bill_bp.route('/bills') @login_required def bill_list(): page = request.args.get('page', 1, type=int) per_page = request.args.get('per_page', 20, type=int) status = request.args.get('status', type=int) 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(page=page, per_page=per_page, error_out=False) 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}

pageper_pageargs.get的第二个参数传入默认值,同时指定type=int,避免恶意请求传非数字导致 500。多表筛选时用query.join连过去,注意字段名冲突时要用表名前缀。error_out=False表示页码超出范围时返回空列表而不是抛 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( appid=current_app.config['ALIPAY_APP_ID'], app_notify_url=current_app.config['ALIPAY_NOTIFY_URL'], app_private_key_string=current_app.config['ALIPAY_APP_PRIVATE_KEY'], alipay_public_key_string=current_app.config['ALIPAY_PUBLIC_KEY'], sign_type='RSA2', debug=True # 沙箱环境 ) @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_no=f'{bill.id}-{int(time.time())}', total_amount=str(bill.amount), subject=subject, return_url='https://admin.example.com/#/bill', notify_url=current_app.config['ALIPAY_NOTIFY_URL'] ) return {'code': 0, 'url': f'https://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_no=trade_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_id=bill.id, trade_no=out_trade_no, alipay_trade_no=trade_no, amount=amount )) 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/simple

5.2 数据库创建与数据导入顺序

MySQL 8.0 的密码认证插件是 caching_sha2_password,Python 的 PyMySQL 需要较新版本才能适配。先用 root 创建库和账号,再导入数据:

mysql -uroot -p CREATE DATABASE renting_db DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; CREATE USER 'renting_user'@'localhost' IDENTIFIED BY 'YourPassword123!'; GRANT ALL PRIVILEGES ON renting_db.* TO 'renting_user'@'localhost'; 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.log

5.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 同时发送多台服务器请求,不要用单机数据库锁去硬抗,正确做法是在流水表上建唯一索引,让冲突插入直接报错,再靠应用层的幂等判断兜底。这套方案的可靠性在租房长租场景里足够应对一年几万笔的交易量了。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/15 6:26:47

AI助力体制内材料写作:5类核心文档高效生成方法

1. 体制内材料写作的痛点与AI解决方案体制内材料写作向来是让不少从业者头疼的工作。从年度总结到汇报材料&#xff0c;从调研报告到领导讲话稿&#xff0c;这些文档往往有着严格的格式要求和特定的表达风格。我接触过不少在体制内工作的朋友&#xff0c;他们最常抱怨的就是&qu…

作者头像 李华
网站建设 2026/9/15 6:26:31

JVM垃圾回收导致服务假死?一次完整GC停滞诊断与调优实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/15 6:25:08

3D-BAT:轻量级多模态点云图像协同标注工具

简介&#xff1a;这是一套基于JavaScript开发的3D边界框标注工具&#xff08;3D-BAT&#xff09;&#xff0c;面向自动驾驶、计算机视觉及点云处理领域的开发者与研究人员&#xff0c;用于高效完成点云与图像协同的3D目标标注任务。资源包共269个文件&#xff0c;包含48个核心J…

作者头像 李华
网站建设 2026/9/15 6:24:28

豆包+SiteNative:把AI网页封装成原生桌面应用的三种实战玩法

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/15 6:24:26

基于PPO的A股自动交易策略实战:状态设计、奖励函数与回测全流程解析

简介&#xff1a;面向计算机相关专业学生与算法爱好者&#xff0c;这份基于深度强化学习的A股自动交易智能体源码包&#xff0c;完整覆盖从数据读取、特征构造、智能体交互环境搭建&#xff0c;到PPO模型训练、策略回测与结果可视化的主流流程&#xff0c;适合课程设计、期末大…

作者头像 李华
网站建设 2026/9/15 6:22:47

SpringBoot药店管理系统毕设:从需求建模到答辩演示的完整指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华