ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

基于Python Flask与Vue3的校园租房房屋租赁系统设计与实现

基于Python Flask与Vue3的校园租房房屋租赁系统设计与实现 最近不少同学在后台问我毕业设计或者课程项目该选什么题目我一般都会推荐这种“业务逻辑完整、技术栈主流、可扩展性高”的全栈项目。今天要拆解的这个校园租房房屋租赁系统就是非常典型的代表后端用 Python前端用 Vue 3前后端分离覆盖了从用户注册登录、房源发布管理、在线预约看房到订单状态流转的完整闭环。无论你是准备做毕设、积累项目经验还是想入门前后端分离开发这套系统的设计思路和踩坑记录都值得参考。为什么“校园租房”这个场景特别适合练手因为它比普通的 CRUD 多了业务约束房源有归属人、订单有时间窗口、不同身份有不同权限这些规则逼着你去设计合理的数据库结构和接口规范。而 Python 加 Vue 3 的组合刚好是当下中小型系统开发里性价比最高的搭配之一Python 写后端逻辑简洁快速Vue 3 配合组合式 API 和生态组件库能高效搭建交互界面两边联动起来整条开发链路都顺。1. 项目整体设计与功能拆解1.1 核心需求解析校园租房的真实业务场景我见很多新手拿到“租房系统”这个需求第一反应就是“做个列表增删改查”但这恰恰是不够的。校园租房的特殊性在于用户群体高度集中学生/老师房源位置相对固定校内及周边租期通常与学期绑定而且对“看房预约”和“合同周期”有比较明确的要求。这意味着系统不能只是房源展示还要处理发布者与租客之间的交互流程。我在设计这套系统的时候把用户重新划分成了三个角色普通用户租客浏览房源、搜索筛选、收藏房源、发起预约看房、提交租房申请。房东也可以是学生宿舍转租发布房源、管理自己的房源上下架、处理预约和租房申请。管理员审核房东身份和房源信息、管理所有订单、处理投诉反馈、统计系统数据。有了角色划分功能模块自然就清晰了。前端页面上需要区分用户端和管理后台后端接口也要围绕角色做权限控制。如果一开始不做这个分层后面改起来会非常痛苦别问我是怎么知道的。1.2 技术选型为什么是 Python Vue 3而不是别的组合关于后端我选的是 Flask。理由很简单校园租房系统虽然业务完整但复杂度没到需要 Django Admin 那种全家桶的程度。Flask 轻量、灵活路由和蓝图机制让代码组织非常清晰配合 SQLAlchemy 做 ORM写起来既快又不容易出错。当然如果你对未来扩展有更强需求换成 FastAPI 也完全可行但 Flask 的生态更成熟教程多遇到问题好搜。前端选 Vue 3 而不是 Vue 2除了 Vue 2 已经停止维护这个硬性原因之外更重要的是 Vue 3 的组合式 APIComposition API在写复杂业务逻辑时组织性更强。配合 Vite 做构建工具启动速度和热更新体验比 Webpack 时代快了几个量级。组件库方面我推荐 Element Plus它对 Vue 3 的支持非常完善表格、表单、弹窗这些后台管理常用的组件开箱即用。这种组合的优势打个比方来说就是Python 像一位经验丰富的老会计算账处理业务逻辑稳Vue 3 像一位熟练的装修师傅能把界面用户交互收拾得井井有条。两边通过 RESTful API 沟通分工明确各司其职。1.3 功能模块划分从前端页面到后端接口的映射我把整个系统拆成了几个核心模块每个模块对应一套 API 和至少一个前端页面。用户模块注册、登录、个人信息维护、密码修改。前端有登录页、注册页和个人中心。房源模块房源发布、编辑、上下架、列表分页搜索、详情展示。前端有房源列表页、房源详情页、房源发布/编辑页。预约与订单模块租客发起看房预约房东确认或拒绝租客提交租房申请房东生成订单并确认。前端有预约管理列表、订单列表。收藏模块租客收藏房源在个人中心查看。管理后台用户管理、房源审核、订单管理、数据统计。前端走独立路由懒加载加载后台页面。这里有一个设计重点预约和订单是两个概念不要混在一起。预约是“我想去看看房子”订单是“我确定要租了”。很多项目把这两个混为一谈业务逻辑就容易乱。哪怕预约确认了后面也可能因为房子实际情况不满意而不转成订单所以状态机要分开设计。2. 数据库设计与核心接口规划2.1 数据库表结构5 张核心表与字段规划数据库是这类系统的地基表结构设计得好后面写代码会顺畅很多。我用的 MySQL 8.0字符集选 utf8mb4避免中文和 emoji 表情出问题。核心表我设计了 5 张用户表、房源表、预约表、订单表、收藏表。用户表字段比较简单username唯一索引、password存哈希值不要存明文、role角色标识、avatar、phone、create_time。密码加密我用的 Werkzeug 自带的 generate_password_hash不需要额外引库安全性有保障。房源表是信息最密集的一张表字段包括title标题建议 50 字以内、description详细描述用 TEXT 类型、cover_url封面图、images多图用 JSON 字段或逗号分隔字符串、price月租金、area面积、address位置、house_type户型、status0 待审核、1 已上架、2 已下架、3 已租出、owner_id外键关联用户表、create_time。这里有个小技巧把审核状态和上架状态分开如果用同一个字段表示管理员下架和房东下架会混淆。预约表user_id、house_id、appointment_time预约看房时间、status0 待确认、1 已确认、2 已取消、3 已完成、remark备注。订单表order_no订单编号随机生成、user_id、house_id、deposit押金、rent_duration租期、start_date、end_date、status0 待支付、1 已支付、2 已取消、3 已结束、create_time。其中 start_date 和 end_date 用于计算租金总价也可以在支付阶段做业务校验。2.2 API 设计规范RESTful 风格与统一返回格式后端接口设计我遵循 RESTful 原则资源用名词复数动作交给 HTTP 方法区分。比如获取房源列表是 GET /api/houses发布房源是 POST /api/houses更新房源是 PUT /api/houses/ 。每个接口的返回格式统一封装为{ code: 200, message: success, data: {} }这样前端 axios 拦截器只需要判断 code 即可不需要每个请求单独处理异常。出错时 code 用非 200 的值比如 400 参数错误、401 未登录、403 无权限、500 服务器异常。前端根据 code 统一弹出错误提示。需要特别注意的是接口路径中不要暴露用户角色这类敏感信息权限控制放在后端校验逻辑里。比如删除房源的操作后端判断当前登录用户的 id 是否等于房源 owner_id而不是前端把房源 id 传过来就直接删。2.3 登录认证机制JWT 无状态鉴权的落地前端 Vue 3 项目里登录状态我用的 JWT 方案。用户登录成功后后端生成一个包含用户 id 和角色信息的 token 返回给前端。前端把 token 存在 localStorage 里每次请求通过 axios 请求拦截器在 header 中携带Authorization: Bearer token。后端写一个装饰器来校验 token接口上需要登录才能访问的就加上这个装饰器。这里有一个关键点管理员接口和普通用户接口要分开校验管理员接口需要额外判断 token 里的 role 是否为 admin。我在实际开发中封装了三个装饰器login_required登录即可、owner_required必须是资源所有者、admin_required必须是管理员极大地减少了重复代码。前端路由守卫也做了配合在 beforeEach 中检查目标路由的 meta 字段是否需要登录如果需要但没有 token就跳转到登录页并带上 redirect 参数登录成功后自动跳回原页面。3. 核心功能实现从后端到前端的完整链路3.1 后端基础框架搭建与配置我先说后端因为它是整个系统的逻辑核心。Flask 项目结构我推荐按模块分包而不是所有代码堆在一个 app.py 里。flask-backend/ ├── app.py # 应用入口 ├── config.py # 配置文件 ├── extensions.py # 扩展对象初始化db, jwt, cors ├── models/ # 数据库模型 │ ├── __init__.py │ ├── user.py │ ├── house.py │ ├── appointment.py │ ├── order.py │ └── favorite.py ├── api/ # 蓝图模块 │ ├── __init__.py │ ├── auth.py │ ├── houses.py │ ├── appointments.py │ ├── orders.py │ └── user.py ├── utils/ # 工具函数 │ ├── __init__.py │ ├── decorators.py │ └── response.py └── requirements.txt数据库连接我用的 Flask-SQLAlchemy配置在 config.py 里统一管理。开发环境用本机 MySQL生产环境改成云数据库只需要换连接字符串。SQLAlchemy 的模型定义建议每个模型单独一个文件保持代码整洁避免上百行挤在一起。3.2 核心表模型代码示例以房源表和订单表为例房源表模型我把多图处理成了一个 JSON 字段存入的是图片路径列表。这里踩过一个坑直接把 list 存入 SQLAlchemy 的 JSON 字段后取出来是字符串而不是数组需要在模型里写一个辅助方法做转换。后来了解了这个字段本来就支持自动序列化问题是出在数据库驱动版本上。class House(db.Model): __tablename__ houses id db.Column(db.Integer, primary_keyTrue) title db.Column(db.String(100), nullableFalse) description db.Column(db.Text, nullableFalse) cover_url db.Column(db.String(255)) images db.Column(db.JSON) # 存放图片路径列表 price db.Column(db.Numeric(10, 2), nullableFalse) area db.Column(db.Float, nullableTrue) address db.Column(db.String(255), nullableFalse) house_type db.Column(db.String(50)) # 一室一厅/两室一厅等 status db.Column(db.Integer, default0) # 0审核 1上架 2下架 3已租 owner_id db.Column(db.Integer, db.ForeignKey(users.id)) create_time db.Column(db.DateTime, defaultdatetime.utcnow)订单表模型重点在状态字段和租期的关联逻辑。租期相关的两个日期字段是业务的核心后续所有统计和校验都依赖它们。class Order(db.Model): __tablename__ orders id db.Column(db.Integer, primary_keyTrue) order_no db.Column(db.String(32), uniqueTrue, nullableFalse) user_id db.Column(db.Integer, db.ForeignKey(users.id)) house_id db.Column(db.Integer, db.ForeignKey(houses.id)) deposit db.Column(db.Numeric(10, 2), default0) start_date db.Column(db.Date, nullableFalse) end_date db.Column(db.Date, nullableFalse) total_amount db.Column(db.Numeric(10, 2), nullableFalse) status db.Column(db.Integer, default0) # 0待支付 1已支付 2已取消 3已结束 create_time db.Column(db.DateTime, defaultdatetime.utcnow)3.3 关键接口实现房源发布与预约看房房源发布接口需要做权限校验只有登录用户才能发布。我在这里加了一个逻辑新发布的房源默认 status0待审核管理员在后台审核通过后才会在前台展示。这样可以过滤掉一些明显无效或违规的房源信息提升平台公信力。对用户来说发布后看到“审核中”的状态说明体验上是容易接受的。再看预约看房接口这是比较能体现业务逻辑的地方。用户提交预约前我需要确认两件事一是房源状态必须是已上架status1二是当前时间不能在预约时间之前。第一个判断防止用户预约到下架或已租出的房子第二个判断防止非法时间请求。另外还要防止同一用户对同一房源重复发起预约我在代码里加了查询校验appointment Appointment.query.filter_by( user_idcurrent_user.id, house_idhouse_id, status0 ).first() if appointment: return error_response(您已预约过该房源请勿重复预约)预约成功后房东端会看到此条预约记录并可以确认或拒绝。确认之后系统才会给用户展示“可发起租房申请”的按钮整个流程环环相扣。3.4 Vue3 前端页面实现组合式 API 与组件拆分前端我用 Vite 创建项目命令是npm create vitelatest frontend -- --template vue。创建完成后安装路由、状态管理和 UI 组件库npm install vue-router4 pinia element-plus axios这里有个细节Element Plus 按需引入可以显著降低打包体积。我用官方推荐的 unplugin-auto-import 和 unplugin-vue-components 插件配置好 vite.config.js 之后组件和 API 会自动按需引入不用手动在 main.js 里全局注册。前端页面我是按模块拆分的每个页面一个目录包含 index.vue 和对应的子组件。比如房源列表页我会拆成三个组件搜索筛选栏SearchBar.vue、房源卡片列表HouseCard.vue、分页器Pagination.vue。这样拆完以后列表页的代码量大幅减少逻辑也清晰。组合式 API 的好处在这里体现得淋漓尽致我在每个组件里用 setup 语法糖把响应式状态和业务函数写在一起不像选项式 API 那样 data、methods、computed 要分散在三个区域。3.5 前后端联调Axios 封装与环境变量配置联调阶段最重要的就是 axios 的封装。我在 frontend/src/utils/request.js 里创建了一个 axios 实例设置基础路径、超时时间并加上请求和响应拦截器。请求拦截器统一从 localStorage 取 token 加到 header响应拦截器统一处理 code遇到 401 就清除 token 并跳转登录页。开发环境联调时需要处理跨域问题。我推荐两种方案一是用 Vite 的 proxy 代理在 vite.config.js 中配置/api前缀转发到后端地址二是让后端开启 CORS。两种方案我实际操作中更推荐用 Vite proxy因为前端代码里可以直接用相对路径/api不用写死后端地址后续部署到不同环境也方便。但后端我也会启用 CORS 作为兜底保证调试工具的请求也能通。为了区分接口环境我在项目根目录创建了.env.development和.env.production两个文件分别配置 VITE_API_BASE_URL。生产环境的接口地址可以指向已部署的后端域名这样前端打包后直接可用。3.6 文件上传与图片处理头像和房源多图的实现细节文件上传是这个系统里容易被忽略但实际很影响体验的环节。我的方案是后端提供 /api/upload 接口接收 multipart/form-data 格式的图片文件校验类型和大小后保存到服务器的 uploads 目录再把访问路径返回给前端。校验规则我要重点提一下只允许图片格式jpg、png、webp、gif大小限制在 5MB 以内。Flask 默认处理上传文件大小没有限制需要在 config.py 里显式设置 MAX_CONTENT_LENGTH。另外不要信任用户传入的文件名后端要重新生成随机文件名防止路径穿越和重名覆盖。import os import uuid from flask import request, current_app ALLOWED_EXTENSIONS {png, jpg, jpeg, gif, webp} def allowed_file(filename): return . in filename and filename.rsplit(., 1)[1].lower() in ALLOWED_EXTENSIONS app.route(/api/upload, methods[POST]) login_required def upload_file(): file request.files.get(file) if not file or not allowed_file(file.filename): return error_response(仅支持图片文件) ext file.filename.rsplit(., 1)[1].lower() filename f{uuid.uuid4().hex}.{ext} save_path os.path.join(current_app.config[UPLOAD_FOLDER], filename) file.save(save_path) url f/uploads/{filename} return success_response({url: url})Nginx 部署时需要在配置里做一个 location 指向 uploads 目录保证图片能正常被访问。开发环境 Flask 自带静态文件处理直接访问 /uploads/xxx.png 就能看到图片。4. 常见问题与排查技巧实录4.1 跨域请求失败CORS 配置的两个坑前后端分离开发第一个遇到的基本都是跨域。我用 Flask-CORS 扩展解决问题第一次只配置了CORS(app)发现带 token 的请求还是会失败。后来排查发现需要显式支持 Authorization 请求头和 credentials否则浏览器拦截了预检请求。from flask_cors import CORS CORS(app, supports_credentialsTrue, allow_headers[Content-Type, Authorization])另外一个坑是如果你用 Vite proxy 代理那么前端代码里请求的是/api路径浏览器根本不会触发跨域后端可以完全不用配置 CORS。这时候如果你两边都配置了反而可能出现一些奇怪的预检请求问题。我的建议是开发环境优先用 Vite proxy后端只留一个宽松 CORS 配置作为本地调试兜底。4.2 JWT Token 过期与用户信息刷新问题用户登录后token 默认有效期我设的是 24 小时。如果用户一直在使用24 小时后突然退出登录体验很不友好。解决方案有两个一是把过期时间设置长一些比如 7 天二是做 refresh token 机制也就是双 token 策略。校园租房系统用前者就够了但如果想做更规范可以加 refresh token 接口前端拦截器收到 401 后自动尝试刷新。还有一个实际项目中很常见的坑用户修改了头像或昵称前端显示的数据没变。因为用户信息存在 localStorage 里不主动更新就不会同步。我的解决方案是登录时只存 token不存用户信息每次页面刷新或者进入个人中心时调 /api/user/info 获取最新信息。这样虽然多了几次请求但保证数据一致性用户体验反而更好。4.3 图片上传成功后无法显示路径问题的三种情况图片上传成功但页面显示 404这个问题我排查过很多次原因大概有三种。第一种是前端请求路径写错了。如果上传接口返回的是/uploads/xxx.png前端 img 标签直接用这个相对路径开发环境需要 Vite proxy 把/uploads也转发到后端。我当时就漏了这一步图片一直 404。在 vite.config.js 里加上/uploads的代理后解决。第二种是后端保存路径和访问路径不一致。Flask 中 current_app.root_path 是应用根目录如果你把文件保存到项目根目录下的 static/uploads但访问路径写的是 /uploads就会因为静态文件夹未配置而找不到。我用 send_from_directory 自定义了静态文件路由或者把文件保存路径和访问路径严格对应起来。第三种是 Nginx 部署后没有配置 uploads 的 location。这种最好排查看 Nginx 错误日志就能看出来。配置文件里加一行location /uploads/ { alias /var/www/your-project/uploads/; }就搞定了。4.4 预约时间冲突与租期重叠的校验逻辑到项目后期有同学反馈同一个房源可以被多个用户同时预约看房、甚至出现两笔订单租期重叠的情况。这个问题很典型租房系统必须要有“并发控制”的概念。我的解决方案分两层。第一层预约看房时同一房源的同一天只能有一个确认状态的预约。第二层生成订单前检查该房源已生效的订单status0 或 1是否与本次租期存在日期重叠。查询逻辑是conflict Order.query.filter( Order.house_id house_id, Order.status.in_([0, 1]), Order.start_date new_end_date, Order.end_date new_start_date ).first()这个逻辑类似会议室预定系统的冲突检测核心思想是两条租期冲突的条件是“新开始日期 旧结束日期”且“新结束日期 旧开始日期”。只要抓到一条冲突记录就拒绝生成订单。4.5 Vue3 响应式数据丢失reactive 与 ref 的误用写 Vue 3 时容易遇到的坑是响应式数据变成普通对象更新了但页面不刷新。最常见的原因是使用 reactive 包裹了从接口返回的数据但后续给对象赋值时用整体替换的方式比如state.houseList response.data这个操作其实是打破了对原对象的响应式引用。我的建议是列表数据这类后期需要整体替换的用 ref 而不是 reactive。ref 存储的是值替换整个数据时只需要houseList.value response.data响应性不会丢失。而表单这类需要深度操作的对象才适合用 reactive。理解了 ref 和 reactive 的本质区别很多前端 bug 都能避免。5. 部署上线与后续扩展建议5.1 前后端分离部署实操Nginx Gunicorn项目做完要落地部署是绕不开的一步。校园租房系统这种体量没必要上 Docker 容器编排简单粗暴的云服务器 Nginx Gunicorn 的方式最可靠。后端部分Gunicorn 作为 WSGI 服务器代替 Flask 自带的开发服务器配置三个 worker 和六个线程就够用了。命令参考gunicorn -w 3 --threads 6 -b 127.0.0.1:5000 app:app注意这里监听的是 127.0.0.1 而不是 0.0.0.0因为对外流量由 Nginx 转发后端不需要直接暴露公网端口。如果后端与前端不在同一台机器上还需要调整绑定地址为内网 IP 或公网 IP并配置安全组规则。前端构建命令是npm run build生成 dist 目录里面的文件放到 Nginx 的 html 目录下。Nginx 配置里需要区分静态资源和 API 请求server { listen 80; server_name example.com; location / { root /var/www/dist; index index.html; try_files $uri $uri/ /index.html; } location /api/ { proxy_pass http://127.0.0.1:5000; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } location /uploads/ { alias /var/www/uploads/; } }try_files那一行很关键它的作用是让 Vue Router 的 history 模式在刷新页面时不至于 404所有路由都回退到 index.html 由前端自己处理。5.2 从毕设项目进化到商业项目几个可扩展方向这套系统跑通之后如果你想让它更有商业价值有几个方向可以扩展。第一是接入地图服务在房源列表和详情页显示房源地理位置。前端可以集成一些地图组件后端只需要在房源表里增加经度和纬度字段前端用标记点展示即可。这块的难点在于地图组件的初始化时机和销毁时机容易出现首次进入正常、二次进入空白的问题解决思路是确保组件卸载时调用销毁方法释放地图实例。第二是增加即时通讯功能让租客和房东可以在线聊天。如果不想自研可以直接集成第三方 IM 服务前端封装一个聊天组件后端只需要处理用户身份与 IM 账号的绑定关系。这块会显著增加系统复杂度但也是最能提升用户粘性的功能。第三是支付功能。校园租房涉及押金和租金支付接微信或支付宝的 H5 支付接口是可行的。后端需要生成支付订单、处理回调通知、同步订单状态。做这个功能时建议先用沙箱环境测试把回调验签逻辑写扎实再切生产。5.3 我个人的一些体会把一套校园租房系统从零搭到部署完成我最大的收获不是记住了多少 API 用法而是养成了“先设计、后编码”的习惯。很多项目失败不是因为代码写得差而是因为前期没想清楚业务规则数据库字段没设计好导致后期反复改表、改接口、改前端。就拿订单和预约两个概念来说如果你在数据库设计阶段就把它们分清楚后面写代码会顺畅很多反之等前端页面都做完你才发现需要从预约表里拆一个订单表出来那改动的成本是成倍的。另外一点我建议初学者不要只看教程要真动手把项目跑起来。从创建数据库到启动后端、再到启动前端这个过程里你会遇到各种教程里没提到的问题比如端口被占用、Python 依赖安装失败、Node 版本不匹配等等。解决这些问题本身就是巨大的提升。等项目跑通了再尝试改动一些功能比如加一个“求租”模块、或者给房源加一个评分系统比重复看十篇教程都管用。最后再分享一个小技巧开发过程中建议每完一个模块就提交一次 Git。我见过太多同学项目做了一半因为改了某个功能把之前的代码弄坏了又没法回退只能手动找问题。合理的 Git 使用习惯能让你的开发过程从容很多。
返回列表