资讯详情

基于Flask的网络相亲系统开发实战:从注册到匹配的完整实现

📅 2026/9/16 5:09:06 | 华诺云谱 👁 阅读
基于Flask的网络相亲系统开发实战:从注册到匹配的完整实现
年初整理电脑的时候翻出一个大学时写的练手项目——基于 Python Flask 框架搭的网络相亲系统。名字起得随意代号叫“帅小伙网络相亲系统”但功能比名字认真不少从注册、填资料、传照片到按条件筛人、算匹配分、发私信一圈做下来Flask 写 Web 应用的常规招式基本都过了手。这篇文章就把整个项目的设计思路、核心代码、踩坑记录全部拆开讲一遍给想用 Python 做 Web 开发入门、或者正在做课设/毕设的朋友一份能直接抄作业的参考。1. 需求解剖相亲系统到底在做什么很多人看到“相亲系统”第一反应是界面复杂、功能庞大实际上从一个开发者的视角切入这类产品本质上就是“用户系统 信息展示 匹配推荐 站内互动”的组合体。把需求一层层剥开代码量和难度都远低于想象非常适合作为 Python Web 入门后的第一个完整项目。1.1 为什么选 Flask 而不是 Django项目技术栈定的是 Python Flask这是当时最顺手的选择。Django 虽然自带 Admin 后台、ORM、表单体系功能非常全但对一个训练项目来说太重而且“框架替你决定太多事”很多底层逻辑反而不容易理解。Flask 则是一个微框架核心只有路由和模板渲染数据库、表单、登录状态这些组件都可以按需集成像搭积木一样逐个装进去。对相亲系统这类业务来说Flask 的轻量灵活是优势注册登录、资料展示、匹配推荐都可以拆成独立的蓝图模块每个模块只依赖自己需要的扩展出现问题排查起来路径很短。更重要的是Flask 官方的文档和社区示例非常丰富遇到报错几乎都能搜到现成答案对新手极其友好。1.2 功能模块拆解完整的相亲系统应该包含以下模块用户模块注册、登录、退出登录、个人中心资料模块基本资料填写昵称、年龄、城市、职业、身高、学历、择偶要求设置照片模块头像上传、相册管理匹配模块推荐列表展示、匹配分数计算、筛选条件过滤互动模块喜欢/不喜欢操作、站内私信管理模块后台数据统计、用户列表查看这个功能清单对于一个练手项目来说已经是标准配置既能覆盖 Flask 的常见用法又不至于大到一个月做不完。1.3 数据库表结构设计数据库是整个系统的地基表结构设计得好不好直接决定后续代码好不好写。当时选择 SQLite 做开发数据库零配置、单文件、开箱即用本地调试非常方便。部署上线时再切换到 MySQLSQLAlchemy 的 ORM 层让这种切换成本变得很低。核心表设计如下用户表users存储登录凭证和基础信息资料表profiles存储相亲展示用的扩展信息照片表photos头像和相册图片路径喜欢表likes记录用户之间的喜欢/不喜欢行为私信表messages站内信内容和状态用 SQLAlchemy 定义模型时用户表和资料表是一对一关系用户表和照片表是一对多关系喜欢表和私信表都通过外键关联到用户表。这种模型关系在 Flask-SQLAlchemy 里写起来很直观完全可以照着官方文档推导出来。2. 环境与工程初始化把地基打稳很多初学者喜欢一上来就写业务代码跳过环境准备和工程初始化结果跑到一半发现依赖冲突、目录混乱、代码和配置缠成一团。Flask 项目虽然轻但规范的项目结构能让你在后面加功能时少掉头发。2.1 Python 环境安装与虚拟环境项目基于 Python 3.10 开发。如果你的电脑还没装 Python直接从官网下载安装包即可安装时记得勾选“Add Python to PATH”。装完后在终端输入python --version能看到版本号说明安装成功。接下来是虚拟环境。这一步建议不要跳过虚拟环境可以把当前项目的依赖隔离起来避免和系统其他 Python 项目互相干扰。创建虚拟环境的命令很简单# 在项目根目录创建虚拟环境 python -m venv venv # Windows 激活 venv\Scripts\activate # Linux / macOS 激活 source venv/bin/activate激活后命令行的前面会出现(venv)标志就说明已经进入虚拟环境了。之后所有 pip 安装的包都会装在这个独立环境里不会污染全局。2.2 安装 Flask 和核心扩展相亲系统需要的依赖主要包含以下几个pip install flask pip install flask-sqlalchemy pip install flask-wtf pip install flask-login pip install pillow简单说明一下每个扩展的用途。Flask-SQLAlchemy 管理数据库 ORM避免手写原生 SQL开发效率高很多。Flask-WTF 提供表单保护和 CSRF 校验这是 Web 应用安全的基本门槛。Flask-Login 负责登录状态管理session 的创建和销毁都封装好了。Pillow 用来处理用户上传的图片生成缩略图避免原图过大影响页面加载速度。依赖安装好后用pip freeze requirements.txt生成依赖清单文件部署的时候一条命令就能装回所有依赖这是项目规范化的重要一步。2.3 项目结构规划Flask 官方的教程喜欢把所有代码放在一个 app.py 里但实际项目一旦功能多起来单文件就会变得又臭又长。我采用的应用工厂模式配合蓝图目录结构如下match_system/ ├── app.py # 程序入口 ├── config.py # 配置文件 ├── requirements.txt # 依赖清单 ├── venv/ # 虚拟环境 ├── uploads/ # 上传图片目录 ├── apps/ │ ├── __init__.py # 应用工厂 │ ├── models.py # 数据库模型 │ ├── auth/ │ │ ├── __init__.py # 认证蓝图 │ │ └── views.py # 注册登录视图 │ ├── profile/ │ │ ├── __init__.py # 资料蓝图 │ │ └── views.py # 资料编辑视图 │ ├── match/ │ │ ├── __init__.py # 匹配蓝图 │ │ └── views.py # 推荐逻辑视图 │ └── message/ │ ├── __init__.py # 私信蓝图 │ └── views.py # 站内信视图 └── templates/ ├── base.html # 基础模板 ├── auth/ ├── profile/ ├── match/ └── message/蓝图Blueprint的作用是让不同功能模块各管各的路由和模板代码组织起来了团队协作时也不容易冲突。应用工厂模式则是把 app 的创建过程封装成一个函数测试和部署时都可以随时创建新实例灵活性比全局 app 对象好很多。3. 核心功能落地注册登录、资料卡与匹配算法功能模块里最核心的是用户认证、资料展示和匹配推荐。这三个部分也是面试或答辩时最容易被追问的环节值得花最多精力打磨。3.1 注册登录密码存储与登录态管理用户密码是最敏感的数据绝对不能明文存储。早期很多项目用 MD5 加盐方案但现在更推荐用 Werkzeug 自带的密码哈希函数它基于 bcrypt 算法哈希计算耗时较长能有效抵御暴力破解。Flask 依赖 Werkzeug所以不需要额外安装包直接导入就行from werkzeug.security import generate_password_hash, check_password_hash # 注册时生成密码哈希 password_hash generate_password_hash(form.password.data) # 登录时校验密码 check_password_hash(user.password_hash, form.password.data)登录态管理用了 Flask-Login它会把当前登录用户的 ID 存进 session后续每次请求自动加载用户对象。User 模型需要继承UserMixin它提供了is_authenticated、is_active、get_id等 Flask-Login 需要的接口方法。注册视图的逻辑很简单先验证表单再检查用户名和邮箱是否已存在最后创建用户记录。有一个细节要注意写入数据库后要立刻提交事务否则后续取不到自增的 ID这在关联照片时很容易踩坑。3.2 资料卡设计与照片上传资料卡是相亲系统的门面展示内容包含昵称、年龄、城市、职业、身高、学历、个人介绍和择偶要求。这些字段在资料表里用单独的列存储插入时做非空校验。表单使用 Flask-WTF 定义能够自动渲染模板并处理 CSRF 令牌class ProfileForm(FlaskForm): nickname StringField(昵称, validators[DataRequired(), Length(max30)]) age IntegerField(年龄, validators[DataRequired(), NumberRange(18, 80)]) city StringField(所在城市, validators[DataRequired(), Length(max50)]) occupation StringField(职业, validators[Length(max50)]) height IntegerField(身高(cm), validators[NumberRange(120, 230)]) education SelectField(学历, choices[(大专, 大专), (本科, 本科), (硕士, 硕士), (博士, 博士)]) self_intro TextAreaField(自我介绍, validators[Length(max500)]) requirement TextAreaField(择偶要求, validators[Length(max500)])图片上传是一个容易出问题的环节主要坑点是文件类型校验和存储路径处理。为了安全不能只依赖前端校验后端必须再次检查扩展名和文件内容。当时用 Pillow 重绘图片来剔除伪装成图片的可执行文件同时生成了固定尺寸的缩略图代码大致如下from PIL import Image import os import uuid def save_avatar(file_storage): # 生成唯一文件名防止覆盖和路径穿越 ext file_storage.filename.rsplit(., 1)[-1].lower() filename str(uuid.uuid4()) . ext save_path os.path.join(app.config[UPLOAD_FOLDER], filename) # Pillow 打开并重绘忽略原图可能携带的恶意内容 image Image.open(file_storage) image.thumbnail((400, 400)) image.save(save_path, optimizeTrue) return filename使用uuid4生成随机文件名能有效避免用户上传同名文件互相覆盖也防止了路径穿越攻击。上传目录必须独立于静态目录并且不能被 Flask 直接当作模板渲染路径。3.3 匹配推荐从“刷人”到“算分”匹配推荐是最有意思的部分。最呆的办法是把所有异性用户列出来让用户翻但这体验太差了。稍微好一点的做法是“过滤 打分排序”先根据硬性条件过滤掉不符合要求的用户再按匹配度分数从高到低展示。过滤条件通常是性别相反、年龄在择偶范围内、城市匹配。打分则考虑多个维度当时的实现是城市相同加 40 分年龄差在 3 岁以内加 30 分6 岁以内加 15 分兴趣标签每重合一个加 10 分上限 30 分学历符合要求加 20 分打分逻辑单独写在match_score函数里方便后续调整权重也便于写单元测试。推荐的查询语句如下from sqlalchemy import or_ # 先做基础过滤 candidates User.query.join(Profile).filter( User.gender ! current_user.gender, Profile.age condition.age_min, Profile.age condition.age_max ).all() # 再算匹配分按分数倒序 ranked sorted(candidates, keylambda c: match_score(current_user, c), reverseTrue)这样写的好处是逻辑清晰容易读。数据量大了以后可以改成 SQL 端排序但对于几千用户的系统Python 端排序完全够用没必要提前优化。3.4 站内私信最简单的互动闭环私信模块没有做成复杂的实时聊天而是用站内信的形式用户进入对方资料页点击“发送私信”填写内容提交对方在“消息中心”查看。这样做既避免了 WebSocket 的复杂度又满足了相亲场景下的沟通需求。消息模型包含发送者、接收者、内容、发送时间、是否已读五个字段。列表查询时按会话维度聚合也就是把 A 发给 B 和 B 发给 A 的消息归为一组按最后一条消息时间倒序排列。这个 SQL 用原生写法比较绕用 ORM 的or_查询后再在 Python 端分组也能解决代码量不大。4. 前端模板与交互打磨Flask 的后端逻辑再完整前端展示不好看也是白搭。相亲系统的竞争点是“第一印象”页面必须干净清晰信息层级分明操作按钮显眼。4.1 模板继承与基础布局Jinja2 模板引擎是 Flask 的标配它的模板继承机制能让公共部分只写一次。基础模板base.html包含导航栏、页脚、消息闪现区域子模板只需在自己的块里填内容!-- base.html 核心结构 -- !DOCTYPE html html langzh-CN head meta charsetUTF-8 meta nameviewport contentwidthdevice-width, initial-scale1.0 title{% block title %}帅小伙相亲系统{% endblock %}/title link relstylesheet href{{ url_for(static, filenamecss/bootstrap.min.css) }} /head body nav classnavbar navbar-expand-lg navbar-light bg-light !-- 导航内容 -- /nav main classcontainer mt-4 {% with messages get_flashed_messages(with_categoriestrue) %} {% for category, message in messages %} div classalert alert-{{ category }}{{ message }}/div {% endfor %} {% endwith %} {% block content %}{% endblock %} /main script src{{ url_for(static, filenamejs/jquery.min.js) }}/script script src{{ url_for(static, filenamejs/bootstrap.bundle.min.js) }}/script {% block scripts %}{% endblock %} /body /html用 Bootstrap 做响应式布局好处是照片卡片在手机端和桌面端都能自适应展示。推荐列表用卡片网格布局每张卡片包含头像、昵称、年龄、城市、匹配分数底部放“喜欢”和“跳过”两个按钮。4.2 推荐页与个人主页推荐页是整个系统的核心流量入口所以页面不能拥挤。每张卡片固定高度文字超出部分用省略号截断鼠标悬停时出现轻微阴影效果这些细节能让页面质感提升不少。个人主页则是展示信息的集中地顶部是头像和昵称中间是基本资料表格下方是相册和自我介绍。页面底部固定一个悬浮按钮点击后弹出私信发送框用 Bootstrap Modal 实现不跳转页面就能完成互动流程。4.3 AJAX 异步交互点喜欢不刷新刚开始做推荐页时点击喜欢按钮是提交整个表单页面刷新后重新加载推荐列表体验很割裂。后来改成 AJAX 请求点击按钮后只发送一个 POST 请求无刷新更新按钮状态。前端代码用 jQuery 实现$(.like-btn).click(function() { var targetId $(this).data(target-id); var clickedBtn $(this); $.post(/match/like, { target_id: targetId, action: like }).done(function(resp) { if (resp.status success) { clickedBtn.removeClass(btn-outline-primary).addClass(btn-primary); clickedBtn.text(已喜欢); } }); });后端的视图函数返回 JSON 数据用jsonify序列化。这里要注意 Flask-WTF 的 CSRF 校验对 POST 请求默认生效所以 AJAX 提交时必须把 CSRF token 放到请求头或表单数据里否则会一直报 400 错误。当时的处理是在 meta 标签中渲染 token然后在 jQuery 全局配置中带上它$.ajaxSetup({ beforeSend: function(xhr, settings) { if (!/^(GET|HEAD|OPTIONS|TRACE)$/.test(settings.type)) { xhr.setRequestHeader(X-CSRFToken, $(meta[namecsrf-token]).attr(content)); } } });5. 部署上线与排坑实录项目写完后本地跑得再欢也只是一个本地项目真正把它部署到 Linux 服务器上才算完整。这个过程遇到的坑比写代码时多得多也是最能长经验的部分。5.1 本地开发常见问题先整理一下开发过程中最容易踩的几个坑基本都是新手高频问题。虚拟环境未激活导致模块找不到运行pip install flask后仍提示ModuleNotFoundError排查后发现是当前终端没进入虚拟环境包装到了全局 Python 里数据库迁移字段不同步直接改模型类而不执行迁移命令导致查询时报字段不存在。推荐从一开始就使用 Flask-Migrate 管理数据库迁移文件上传后路径错乱保存时使用相对路径但在部署后工作目录发生变化导致图片加载不出来。建议保存绝对路径到数据库或者通过配置项动态拼接表单验证不通过但没提示Jinja2 模板中需要显式渲染form.errors否则浏览器里看不到任何报错信息SQLite 并发写入报错本地调试时多个请求同时写库可能触发database is locked生产环境切换到 MySQL 后这个问题就消失了5.2 Linux 服务器部署流程部署方案选了 Gunicorn Nginx 的经典组合。Gunicorn 作为 Python WSGI 服务器负责运行 Flask 应用Nginx 负责静态文件处理和反向代理把外部请求转发给 Gunicorn。部署时首先把代码用 git 拉到服务器创建虚拟环境并安装依赖。然后安装 Gunicornpip install gunicorn gunicorn -w 3 -b 127.0.0.1:8000 app:app-w 3表示启动 3 个 worker 进程-b指定监听地址和端口。接下来配置 Nginx 反向代理server { listen 80; server_name yourdomain.com; location /static { alias /path/to/project/static/; expires 7d; } location /uploads { alias /path/to/project/uploads/; } 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; } }上传目录要单独配置一个 location否则 Nginx 会把上传文件的请求也转发给 Flask静态文件交给 Nginx 处理能够显著减少 Python 进程的负载。实际生产环境还需要用 systemd 管理 Gunicorn 进程这样服务器重启后服务能自动拉起进程意外退出也会自动重启。如果想让 Flask 获取到用户真实 IP需要在 Nginx 里设置 X-Real-IP 和 X-Forwarded-For同时 Flask 端用 ProxyFix 中间件处理代理头信息否则获取不到正确的客户端 IP。5.3 安全与性能基础加固部署上线后做了一次安全自查发现几个问题值得大家注意。调试模式必须关掉Flask 的debugTrue在调试时很爽但生产环境开着会让攻击者直接看到详细报错信息和部分源码Werkzeug 还自带交互式调试终端危险系数非常高加请求大小限制上传图片不做大小限制攻击者可能通过上传超大文件拖垮服务器。通过MAX_CONTENT_LENGTH配置限制请求体大小比如 16MB密码哈希参数调高Werkzeug 默认的哈希参数可能不够高可以在生成哈希时手动指定methodscrypt提高计算成本增加破解难度数据库备份SQLite 单文件复制即可完成备份切到 MySQL 后要配置定时任务自动备份关于性能小规模场景下最重要的优化是给数据库查询加索引。用户表和资料表中高频查询字段比如性别、年龄、城市都可以加上索引。拍照功能生成的缩略图能显著减少传输流量实测同样一张 3MB 的照片缩略图只有 30KB 左右页面加载速度快了一个量级。6. 常见问题速查表整理了一份排错速查表基本覆盖了这个项目所有核心模块的典型问题以后遇到类似的情况可以先对照排查一遍。症状可能原因排查思路与解法ModuleNotFoundError: No module named flask当前不在虚拟环境或未安装依赖执行pip list查看已装包激活虚拟环境后重新安装依赖表单提交后 400 Bad RequestCSRF token 缺失或过期表单中添加hidden_tag()AJAX 请求在头中携带 X-CSRFToken图片上传成功但页面无法显示上传目录配置错误或权限不足检查配置的 UPLOAD_FOLDER 是绝对路径运行用户有写权限登录后刷新就掉线SECRET_KEY 未配置或配置不稳定在 config.py 中固定 SECRET_KEY不要用随机生成值部署后静态文件 404Nginx 静态目录 alias 路径错误对照 Nginx 日志检查 alias 路径对应的实际文件是否存在数据库查询报no such column模型改动未执行数据库迁移使用 Flask-Migrate 执行迁移生产环境先备份数据推荐列表出现已喜欢的人查询逻辑未排除已交互用户在推荐查询中使用NOT IN子查询排除 likes 表中的记录上传中文文件名报错文件名编码处理不当保存时统一用 uuid 重命名丢弃原始文件名页面加载慢且图片多原图未压缩使用 Pillow 生成缩略图Nginx 开启 gzip 压缩服务器 CPU 长时间 100%Gunicorn worker 数过少或代码死循环检查 worker 数量和带宽占用查看访问日志定位异常请求做匹配功能时最容易被忽略的一个问题是“重复推荐”。用户刷新推荐页如果查询结果没排除已经操作过的人同一个用户会反复出现。这个问题在逻辑上很简单但开发时很容易漏掉因为本地调试的数据量太小问题不明显。解决方式是在推荐查询中添加排除条件把 likes 表里已存在交互记录的用户 ID 全部过滤掉。7. 项目经验复盘相亲系统教会我的事最后聊聊这个项目做完以后的感受。技术上Flask 让我明白了一个框架的“哲学”不强行规定你应该怎么做而是给你最核心的工具其余自由组合。这带来很高的学习价值因为你需要自己思考数据库表怎么设计、蓝图怎么划分、模块间怎么通信。这些思路迁移到任何其他 Web 框架都是通用的。项目本身也踩了不少坑。现在回头看最想给后来人两个建议第一从一开始就规范化项目结构、打虚拟环境、做依赖管理不要图省事全塞一个文件里后面扩展的时候会哭。第二表单和数据库字段的校验一定要前后端同时做不要信任任何来自浏览器的数据。安全这种东西不是项目上线才考虑而是每一行代码都要有意识。后续如果还要扩展可以考虑接入 Redis 做实时在线状态、用 WebSocket 做即时聊天、加推荐算法做更精准的匹配也可以把前端换成 Vue 或 React 做前后端分离。不过现阶段这个 Flask 版本已经足够完整地展示一个 Web 产品从零到一的过程了。对我个人来说写完这个项目最大的感觉是原来一个完整的、能被别人使用的 Web 应用离一个初级开发者并没有想象中那么远。
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。