首页 / 资讯中心 / 文章详情

Flask+Vue房屋租赁系统开发全流程:从选型到上线避坑指南

Flask+Vue房屋租赁系统开发全流程:从选型到上线避坑指南 ★ FEATURED ARTICLE
前阵子接了个小需求给一个做长租公寓运营的小团队做一套房屋租赁后台。对方发来的需求文档里技术栈写得很有意思——“Flask Vue PyCharm”第二天又追了一条“听说Django自带管理后台你们评估一下要用哪个。”就这一句话基本把这类系统从决策到落地的真实状态暴露出来了需求边界模糊技术栈犹豫看起来“都能做”但真动手起来处处是取舍。这篇文章就当一份完整复盘来写。如果你正在做毕业设计、课程设计或者真的在给某个中小租赁团队搭建类似系统内容会从需求梳理、技术选型、数据库设计、前后端实现一直聊到上线前的坑位。不一定是最标准答案但都是我实际跑通过、也踩过的路。1. 需求盘点这个“房屋租赁系统”到底要管什么1.1 从一句“需求很简单”开始的需求梳理对方最初给我的描述只有一句话“能录房子能收房租就行。”但真到整理需求时牵扯出来的东西远比这句话多房源状态怎么流转、合同到期怎么提醒、押金和滞纳金怎么算、租客的身份证和联系方式放哪、运营人员和管理员权限怎么区分、首页要不要放出租率统计。每一件单独拿出来都不难堆在一起就变成了一个项目的核心工作量。我习惯把这类需求先切成四块来梳理管房、管人、管合同、管钱。对应到页面就是房源管理、租客管理、合同管理、账单管理最外面再加一个登录和首页统计。拆成功能清单大概是这样的房源管理房源的增删改查、主图上传、租金区间筛选、状态筛选未租/已租/维护中租客管理租客档案新增、身份信息登记、跟当前合同的关联、历史租住记录合同管理新建租房合同、到期时间自动计算、到期提醒、续租和退租操作账单管理按月生成租金账单、水电费录入、缴费状态更新、欠费列表用户与权限管理员登录、租客小程序端查看自己合同和账单Web端先做管理后台这个清单不是一次聊出来的而是聊了两次、又让对方填了一张“每天到底要做什么”的表格后才定下来的。需求定得越具体后面写表结构和接口就越省力。1.2 角色模型与功能边界这个系统里我只设计了三种角色管理员、房东、租客。但在实际操作中运营人员通常既是管理员也是房东侧的操作者所以后端的权限并没有做得特别重管理员可以看全部数据和所有操作入口房东只能管理自己名下的房源和合同租客在系统里更多是“被管理对象”前台只提供查询自己的合同和账单入口。权限模型上我用的是一个用户表加一个 role 字段而不是给每种角色单独建表。说实话这种体量的系统完全没有必要拆成多张用户表拆了反而增加联查成本。User.role存admin、landlord、tenant三种字符串后端接口里用装饰器做角色校验前端路由里再用meta.roles控制菜单显隐这套组合已经够用了。1.3 这版系统砍掉了什么同样重要的是明确哪些东西不做。这版系统明确不做App不做线上支付不做电子合同签署不做在线聊天。砍掉这些不是因为我做不出来而是因为一个小团队根本维护不起。举线上支付为例接微信或者支付宝需要商户号资质、需要支付回调、需要对账个人开发者或者小团队只跑一个 Web 后台光资质就能卡一个月而且每笔订单的回调处理都是额外的工作量。最后支付确认做成了“运营人员在后台手动标记缴费状态”虽然看着不够“智能”但足够可靠也完全满足对方现在的业务节奏。我在这个项目里的体会是功能边界砍得越早项目交付越快后期返工的也越少。2. 标题里的Flask和Django技术选型纠结过后的结论2.1 Flask与Django的取舍依据标题里同时出现了 flask 和 django我猜最初构思的人是被“Django自带admin后台”吸引又被“Flask轻量灵活”诱惑两边反复横跳。我先说结论这种体量的项目选 Flask 更舒服。对比维度FlaskDjango路由方式装饰器直接定义灵活直观URLconf 集中配置结构规范但偏重自带后台没有可用 Flask-Admin 或自写自带 admin但强业务场景基本要重写ORMFlask-SQLAlchemy轻量够用Django ORM 功能强和框架耦合也更紧认证体系按需组合 Flask-JWT-Extended 等扩展自带 User、Session、Permission学习成本半天能上手中间件、APP、DRF 等概念要多一些适配场景API 为主、快速迭代的小系统中大型内容系统、全栈后台我不想否定 Django。如果是做一个内容管理平台或者业务模型非常成熟、未来还会加很多后台功能Django 的 admin 和全家桶确实能省不少事。但房屋租赁这种强业务交互、需求还在不断调整的小系统Django 自带的东西很多用不上用不上的东西还得想办法绕开这就不值得了。2.2 为什么说这个体量用Flask更舒服我一开始也动过用 Django 的念头理由和大多数人说的一样自带 admin省得自己写管理界面。但真去看要做的页面时我发现房屋管理这种业务跟 Django admin 的模式根本不匹配。admin 适合那种“管理员直接改数据库记录”的场景而租赁系统里签约、退租、生成账单、确认缴费每一个动作背后都有业务逻辑签约后房源状态要改成已租退租后状态要改回未租缴费后账单状态要联动更新。这些逻辑放在 admin 里根本不是“直接改记录”能解决的最后也得自己写视图和模板。既然兜兜转转还是要自己写那不如从一开始就用更轻、更好控制代码结构的 Flask。另外一个现实原因是现在前后端分离的做法很普遍后端更多是充当 API 服务。Flask Flask-SQLAlchemy 的组合非常清爽路由、模型、序列化都由自己控制没有框架强加的目录约定。PyCharm 里建好虚拟环境、装几个依赖就可以开工这对一个人或者两三个人的小团队来说效率是最高的。2.3 PyCharm下的项目初始化和运行链路标题里特意写了 PyCharm我整个项目也确实是在 PyCharm 里完成的这里把初始化链路完整记录一下新手可以照着操作。先用命令行创建虚拟环境并安装依赖python -m venv venv source venv/bin/activate pip install flask flask-sqlalchemy flask-cors flask-jwt-extended python-dotenv再手动建一个frontend目录用 Vite 初始化 Vue 3 项目npm create vitelatest frontend -- --template vue cd frontend npm install axios vue-router pinia element-plusPyCharm 里需要做的就是在 Settings 里把 Project Interpreter 指向venv目录下的 Python 解释器然后把 Flask 项目的入口文件配置成 Run Configuration。前端和后端两个进程同时跑Flask 开发服务跑在 5000 端口Vue dev server 跑在 8080 端口联调时通过 Vite 的 proxy 把/api转发到后端。这里有一个新手非常容易踩的坑如果前后端都用同一个端口代理配不好就会出现“前端能打开、接口全部 404”。我的做法是 Vite 配置里统一加一道/api前缀的代理后端蓝图也统一挂载在/api路径下两边各管各的端口开发体验顺畅很多。3. 数据库设计把“房子、租客、钱”用表结构串起来3.1 核心表结构数据库是整个系统的地基。我把核心表定成了六张用户表、房源表、租客表、合同表、账单表、看房预约表。下面直接用 SQLAlchemy 模型描述重点看字段之间的关系。from datetime import datetime from extensions import db class User(db.Model): __tablename__ users id db.Column(db.Integer, primary_keyTrue) username db.Column(db.String(64), uniqueTrue, nullableFalse) password_hash db.Column(db.String(128), nullableFalse) role db.Column(db.String(16), defaultlandlord) created_at db.Column(db.DateTime, defaultdatetime.now) class House(db.Model): __tablename__ houses id db.Column(db.Integer, primary_keyTrue) title db.Column(db.String(128), nullableFalse) address db.Column(db.String(255), nullableFalse) area db.Column(db.Numeric(10, 2), default0) price db.Column(db.Numeric(10, 2), nullableFalse) status db.Column(db.String(16), defaultvacant) owner_id db.Column(db.Integer, db.ForeignKey(users.id)) image_url db.Column(db.String(255), default) description db.Column(db.Text, default) created_at db.Column(db.DateTime, defaultdatetime.now) class Tenant(db.Model): __tablename__ tenants id db.Column(db.Integer, primary_keyTrue) name db.Column(db.String(32), nullableFalse) phone db.Column(db.String(20), nullableFalse) id_card db.Column(db.String(18), uniqueTrue) remark db.Column(db.String(255), default) created_at db.Column(db.DateTime, defaultdatetime.now) class Contract(db.Model): __tablename__ contracts id db.Column(db.Integer, primary_keyTrue) house_id db.Column(db.Integer, db.ForeignKey(houses.id)) tenant_id db.Column(db.Integer, db.ForeignKey(tenants.id)) landlord_id db.Column(db.Integer, db.ForeignKey(users.id)) rent db.Column(db.Numeric(10, 2), nullableFalse) deposit db.Column(db.Numeric(10, 2), default0) start_date db.Column(db.Date, nullableFalse) end_date db.Column(db.Date, nullableFalse) status db.Column(db.String(16), defaultactive) created_at db.Column(db.DateTime, defaultdatetime.now) class Bill(db.Model): __tablename__ bills id db.Column(db.Integer, primary_keyTrue) contract_id db.Column(db.Integer, db.ForeignKey(contracts.id)) period db.Column(db.String(16), nullableFalse) amount db.Column(db.Numeric(10, 2), nullableFalse) status db.Column(db.String(16), defaultunpaid) paid_at db.Column(db.DateTime, nullableTrue) created_at db.Column(db.DateTime, defaultdatetime.now)这六张表的关系想清楚之后整个系统的脉络就清晰了Contract把House、Tenant、User三者关联起来Bill挂在Contract之下按月生成。后续不管是做统计本月应收多少、已收多少、出租率如何还是做提醒合同三天后到期、账单逾期两天都只用在这几张表上做查询。3.2 金额字段为什么用Decimal不用float租金、押金、水电费这些字段我全部使用了Numeric(10, 2)也就是数据库里的DECIMAL类型而不是 Python 里的 float。原因很简单浮点数在计算机里存在精度问题。0.1 0.2在浮点运算里算出来是0.30000000000000004单看一次影响不大但如果做账单累计、对账、滞纳金计算误差会被不断放大。房租这种一分钱都不能错的地方从一开始就用Numeric是最稳妥的。这也是很多没做过真实业务的人容易忽略的地方。状态字段我用字符串而不是整数比如房源状态用vacant、rented、maintenance账单状态用unpaid、paid、overdue。虽然字符串存储空间比整数大一点但换来的是代码里可读性更好过滤数据时一眼就能看出状态含义前端展示时也不用再维护一份编号映射表。这个取舍在小项目里是完全值得的。3.3 时间字段与时区陷阱时间字段这里我踩过一次实实在在的坑。合同起止日期是“自然日”的概念比如 6 月 1 号到 8 月 31 号这种字段用db.Date类型不要存成DateTime。如果存成带时分秒的 DateTime跟“当天”做比较时会出现 8 小时的时区偏移到期提醒就可能提前或者延后一天。最开始我把end_date设计成 DateTime结果测试时发现合同明明还有一天到期系统却已经发出了到期提醒排查半天才发现是时区问题。后来的规矩是跟业务日期相关的字段比如start_date、end_date一律用 Date跟操作日志相关的字段比如created_at、paid_at用 DateTime。前端展示时再按用户本地时区格式化后端不做多余的时间转换。这套规则定下来之后时间相关的 bug 基本绝迹了。4. Flask后端从“会写接口”到“能上线”的完整链路4.1 项目目录与蓝图组织Flask 项目很小的时候所有逻辑塞进一个app.py确实能跑。但只要接口数量超过十个单文件维护起来就是灾难。我用的是按资源拆分的蓝图方式目录结构如下app/ __init__.py extensions.py models.py api/ __init__.py auth.py houses.py tenants.py contracts.py bills.py utils/ decorators.py response.py config.py run.pyextensions.py单独放db、jwt、cors这些扩展实例避免app.py里出现循环导入。每个api下的模块对应一组业务接口比如houses.py只管房源相关路由。这样组织的好处是后面加功能时直接新增一个模块不需要在既有代码里东拼西凑。蓝图注册的代码大致是这样from flask import Flask from config import Config from extensions import db, jwt, cors from app.api.auth import auth_bp from app.api.houses import houses_bp from app.api.contracts import contracts_bp def create_app(): app Flask(__name__) app.config.from_object(Config) db.init_app(app) jwt.init_app(app) cors.init_app(app, resources{r/api/*: {origins: *}}) app.register_blueprint(auth_bp, url_prefix/api/auth) app.register_blueprint(houses_bp, url_prefix/api/houses) app.register_blueprint(contracts_bp, url_prefix/api/contracts) return app4.2 认证与权限控制登录接口用 Flask-JWT-Extended 颁发 token后续接口通过jwt_required()保护。登录逻辑非常标准但有几个细节值得注意密码一定要用哈希存储不能明文登录成功后返回的 token 要设置过期时间管理后台我一般设 12 小时。from flask import jsonify, request from flask_jwt_extended import create_access_token, jwt_required, get_jwt_identity from app.models import User from werkzeug.security import check_password_hash from app.api import auth_bp auth_bp.post(/login) def login(): data request.get_json() user User.query.filter_by(usernamedata.get(username)).first() if not user or not check_password_hash(user.password_hash, data.get(password, )): return jsonify(code1, message用户名或密码错误), 401 token create_access_token(identitystr(user.id), additional_claims{role: user.role}) return jsonify(code0, messageok, data{token: token, role: user.role})角色权限我用一个自定义装饰器实现比如“房东只能操作自己的房源”from functools import wraps from flask import jsonify from flask_jwt_extended import get_jwt_identity from app.models import User def require_role(*roles): def decorator(fn): wraps(fn) def wrapper(*args, **kwargs): user_id get_jwt_identity() user User.query.get(user_id) if not user or user.role not in roles: return jsonify(code403, message无权限访问), 403 return fn(*args, **kwargs) return wrapper return decorator使用方式就是在需要权限控制的接口上叠加houses_bp.get() jwt_required() require_role(admin, landlord) def house_list(): pass这里有一个我在实践中反复调整的经验装饰器的顺序很重要。jwt_required()要放在require_role的外层也就是先验证 token 再验证角色否则get_jwt_identity()拿不到身份信息。4.3 核心接口实现房源列表、合同签约、账单生成房源列表接口是整个系统里最典型的查询接口包含了分页、筛选、关键词搜索。我实现的大致逻辑from flask import request, jsonify from app.models import House houses_bp.get() jwt_required() def house_list(): page request.args.get(page, 1, typeint) size request.args.get(size, 10, typeint) status request.args.get(status, ) keyword request.args.get(keyword, ) query House.query if status: query query.filter_by(statusstatus) if keyword: query query.filter(House.title.contains(keyword) | House.address.contains(keyword)) pagination query.paginate(pagepage, per_pagesize, error_outFalse) items [{ id: h.id, title: h.title, address: h.address, area: float(h.area), price: float(h.price), status: h.status, image_url: h.image_url, } for h in pagination.items] return jsonify(code0, messageok, data{ items: items, total: pagination.total, page: page, size: size, })查询接口的数据返回需要直接给出前端需要的结构不要把 SQLAlchemy 模型原样丢给前端。Decimal类型字段先转成float再放进字典避免序列化时报错。合同签约接口可以说是整个系统里业务逻辑最重的接口不只是插入一条合同记录这么简单。签完约之后要同步做三件事合同状态置为 active房源状态从 vacant 改为 rented同时生成当月的第一笔租金账单。这个事务性操作必须放在同一个事务里防止出现“合同建了但房源还是空着”的不一致状态。from datetime import date from app import db from app.models import Contract, House, Bill contracts_bp.post() jwt_required() require_role(admin, landlord) def create_contract(): data request.get_json() house House.query.get(data[house_id]) if not house or house.status ! vacant: return jsonify(code1, message该房源不可签约), 400 contract Contract( house_iddata[house_id], tenant_iddata[tenant_id], landlord_iddata[landlord_id], rentdata[rent], depositdata[deposit], start_datedate.fromisoformat(data[start_date]), end_datedate.fromisoformat(data[end_date]), ) db.session.add(contract) db.session.flush() house.status rented bill Bill( contract_idcontract.id, perioddate.today().strftime(%Y-%m), amountdata[rent], statusunpaid, ) db.session.add(bill) db.session.commit() return jsonify(code0, messageok, data{contract_id: contract.id})我在这类接口里有一个一直坚持的习惯只要涉及多张表的修改就一定要保证中间任何一步失败时整个操作回滚。上面代码里db.session.flush()是为了拿到自增主键contract.id去生成账单如果这里不 flush后面关联外键时会因为拿不到主键而出错。账单生成逻辑并不复杂关键是确定账单周期。我的实现是签约时生成当月账单之后每月 25 号通过任务脚本扫描所有 active 状态的合同为下个月生成账单。脚本在本地开发时手动执行上线后交给系统的定时任务调度。4.4 统一返回格式与全局异常处理前后端联调时最怕的就是返回结构不统一。有的接口返回的是{code: 0, data: ...}有的直接返回数组有的报错时返回的是 Django 风格的 HTML 页面前端拦截器写起来非常痛苦。我的解决方案是全局统一成{code, message, data}三件套code0表示成功非 0 表示业务错误message是给用户看的提示data是真正的业务数据。同时注册全局异常处理器兜住所有意料之外的异常。from flask import jsonify def register_error_handlers(app): app.errorhandler(404) def not_found(e): return jsonify(code404, message请求资源不存在, dataNone), 404 app.errorhandler(500) def internal_error(e): db.session.rollback() return jsonify(code500, message服务器内部错误, dataNone), 500这样的好处是前端完全可以依赖code来做逻辑判断而不是每次都要解析不同的错误结构。后面 Vue 那边的 axios 拦截器基本上就是围绕这个统一结构来写的。5. Vue前端不只是套壳交互设计要贴合租赁业务5.1 页面结构与路由权限前端用的是 Vue 3 Vue Router Pinia Element Plus构建工具是 Vite。页面结构按业务模块划分登录页首页统计出租房源数、在租合同数、本月应收、欠费数房源管理房源列表、新增/编辑房源租客管理租客列表、租客详情合同管理合同列表、新建合同、续租/退租操作账单管理账单列表、缴费确认路由权限通过meta.roles控制。比如租客详情页只允许管理员和房东访问路由守卫里拦截router.beforeEach((to) { const token localStorage.getItem(token) if (!token to.path ! /login) { return /login } if (token to.path /login) { return / } const role localStorage.getItem(role) if (to.meta.roles !to.meta.roles.includes(role)) { return /403 } })这套权限控制思路很直白就是“没有 token 一律去登录角色不匹配一律去无权限页”。对于内部管理后台来说够用且好维护。5.2 axios封装与跨域联调axios 封装是前后端联调的核心。我在src/utils/request.js里统一创建实例请求拦截器自动携带 token响应拦截器统一处理后端返回结构。import axios from axios import { ElMessage } from element-plus import router from ../router const request axios.create({ baseURL: /api, timeout: 10000 }) request.interceptors.request.use((config) { const token localStorage.getItem(token) if (token) { config.headers.Authorization Bearer ${token} } return config }) request.interceptors.response.use( (response) { const res response.data if (res.code ! 0) { ElMessage.error(res.message) return Promise.reject(new Error(res.message)) } return res }, (error) { if (error.response?.status 401) { localStorage.clear() router.push(/login) } else { ElMessage.error(error.response?.data?.message || 网络异常) } return Promise.reject(error) } ) export default request这里最关键的一点是401 时清空本地存储并跳回登录页否则 token 过期后用户会一直卡在某个操作上报错提示也不明不白。跨域问题我选择的方案是 Vite proxy而不是在后端开启允许所有域的 CORS。开发环境下前端发到/api的请求被 Vite 转发到http://localhost:5000这样浏览器看到的是同源请求不涉及跨域。生产环境则通过反向代理统一处理。两种方式都行但相比之下 Vite proxy 在开发时更省心也不需要在 Flask 里暴露过宽的 CORS 策略。5.3 列表、签约、账单更新的交互细节房源列表是管理后台的流量入口我用卡片加筛选栏的布局。筛选栏包含状态选择、租金区间、关键词搜索每次筛选条件变化时重新请求列表并同步更新 URL 上的 query 参数。这样做的好处是运营人员刷新页面后筛选条件还在不用重新选一遍。签约流程的交互我花了不少功夫。新建合同弹窗里有四个关键字段选择房源、选择租客、起止日期、租金押金。选择房源后自动带出房源信息和月租金选择起止日期后自动计算租期提交前需要二次确认。这里的前端校验不能只依赖后端返回错误比如“该房源已被租”这种状态冲突应该在提交前先由前端判断一次给用户即时反馈后端再做一次权威校验两道坎并行走。function submitContract() { formRef.value.validate(async (valid) { if (!valid) return const res await request.post(/contracts, form.value) if (res.code 0) { ElMessage.success(签约成功) // 刷新合同列表并把当前房源状态标记为已租 loadContracts() loadHouses() } }) }账单更新是另一个交互重点。账单列表里每行有一个“确认已缴”按钮点击后调接口更新账单状态成功后就地把该行的状态改为已缴不做整页刷新。这里要注意的是更新后对应的合同状态、统计数字也要一起刷新否则会出现账单状态变了、首页统计还是旧数据的尴尬。这类细节虽然不起眼但直接决定了这个后台真正交给运营人员使用时是不是好用。我更愿意在前端多花一点时间把这些交互理顺而不是搞一堆看起来很炫但用不上的特效。6. 上线前的坑那些不跑一遍根本发现不了的问题6.1 图片上传base64还是文件流房源图片上传是最容易被低估的坑。我最初的表情是偷懒直接把图片转成 base64 字符串存到数据库的image_url字段里。小图没问题但当运营上传一张 500KB 的实拍照片时接口响应立刻变得很慢数据库字段也被撑得很大。后来改成了文件流上传方案前端用multipart/form-data上传Flask 端把文件保存到项目下的uploads/目录数据库只存访问路径。import os from flask import request, jsonify from werkzeug.utils import secure_filename UPLOAD_DIR os.path.join(os.path.dirname(__file__), .., uploads) houses_bp.post(/upload) jwt_required() def upload_image(): file request.files.get(file) if not file: return jsonify(code1, message未选择文件), 400 filename secure_filename(file.filename) save_path os.path.join(UPLOAD_DIR, filename) file.save(save_path) url f/uploads/{filename} return jsonify(code0, messageok, data{url: url})注意两点文件名必须经过secure_filename处理否则用户上传一个带特殊符号的文件名可能产生路径穿越问题另外生产环境里这个/uploads静态路径需要让反向代理直接托管不要把压力全压在 Flask 进程上。6.2 SQLite的并发写入限制项目开发阶段我用的是 SQLite方便省事一个文件就是整个数据库。但随着运营开始真实使用我很快发现了一个问题当两个管理员同时操作、其中一个正在写数据库时另一个写操作会报database is locked。SQLite 不是不能做并发只是它在并发写入上的能力非常有限。对于单用户开发、或者并发量极低的小团队内部系统SQLite 完全够用。但一旦同时操作的人多起来就必须把数据库切换到 MySQL 或 PostgreSQL。切换过程其实不复杂无非是改一下SQLALCHEMY_DATABASE_URI然后做一次数据迁移。如果前期已经用了 SQLAlchemy 模型换数据库的成本主要是数据和字段类型差异而不是代码重写。所以我要给的提醒很简单开发阶段 SQLite 没问题但上线前要评估并发量如果同时使用后台的人数可能超过五个人建议直接用 MySQL省得后期迁移。6.3 部署时的配置切换开发环境跑得好好的上生产环境就出幺蛾子十有八九是配置没分开。我在config.py里做了开发和生产两套配置通过环境变量切换import os class Config: SECRET_KEY os.getenv(SECRET_KEY, dev-secret-key) SQLALCHEMY_DATABASE_URI os.getenv(DATABASE_URL, sqlite:///rental.db) SQLALCHEMY_TRACK_MODIFICATIONS False JWT_ACCESS_TOKEN_EXPIRES 12 * 60 * 60 class ProductionConfig(Config): DEBUG False SQLALCHEMY_DATABASE_URI os.getenv(DATABASE_URL, mysqlpymysql://user:passhost/rental)生产环境必须把DEBUG设为 False否则一旦出错控制台会把完整的调用栈打到浏览器上这不仅是体验问题更是安全隐患。SECRET_KEY也不能再用开发默认值要通过环境变量注入。这些配置如果在上线前不检查一遍后面会一个一个冒出来。6.4 定时提醒与账单对账最后说一个很容易被忽略但业务上非常需要的功能定时提醒和账单对账。房租类业务天然依赖时间节点合同什么时候到期、什么时候该生成下月账单、账单逾期多少天。我用了 APScheduler 在 Flask 里加了一个每日任务每天凌晨 3 点执行找出 3 天内到期的合同给管理员推送待办消息找出状态为 active 且下月账单还没生成的合同生成下月账单找出 unpaid 且超过缴费日的账单把状态改成 overdue这个任务的实现没有你想象中复杂核心就是几条 SQL 查询加一个生成动作。但由于它每天自动跑一旦写错就是批量数据错误所以我单独放在services/tasks.py里并且所有写操作都加了事务保护。上线前我专门拿测试数据连续跑了三天确认账单生成和状态更新的结果没有问题后才放心。做完这个功能之后对方运营终于不用每个月手动对着 Excel 算租金了。这也是整套系统里我认为最有业务价值的一块虽然它不像房源列表那样显眼但真正解决了“管钱”这件事的核心痛点。最后再分享一个我的个人经验如果你也在做类似系统不要急着写代码先把“房子、租客、钱”这三者的业务链路画清楚尤其是签约和账单这条线。技术选型上Flask 不是唯一答案但在这个体量下它足够让你把业务逻辑牢牢掌控在自己手里。PyCharm Flask Vue 这套组合跑通之后你会发现真正花时间的从来不是写接口而是想清楚每一步操作到底动了哪些数据。想清楚了代码只是一个表达过程而已。
阅读完成 · 觉得有帮助?
咨询建站