☰
Python+Flask+SQLite:从零搭建好友挑战测试Web应用
2026/10/15 15:26:15 网站建设 项目流程

“揭秘假闺蜜!5大好友挑战测出谁是真朋友”,听起来是一个偏娱乐向的视频选题。但如果把它当作一个开发需求,其实很有意思:你需要做一个“好友挑战测试”线上小应用,让用户创建问卷、邀请好友参与、收集评分数据,然后通过算法判断谁更符合“真朋友”特征。

这次我们不依赖现成的第三方平台,直接用 Python + Flask + SQLite 从零搭一个可本地运行的“好友挑战测试”小项目。它麻雀虽小,但包含了 Web 后端、问卷逻辑、数据存储、接口 API、批量邀请链接生成、结果聚合统计这些常见工程点。跑通之后,你可以继续扩展到小程序、公众号或企业内部团建工具。

1. 核心能力速览

先看整个项目的功能边界和运行门槛。

能力项说明
项目类型轻量 Web 应用:好友问卷挑战 / 社交关系测试
核心玩法创建“5大好友挑战”,邀请好友匿名/实名作答,系统打分并给出“真朋友指数”
技术栈Python 3 + Flask + SQLite + HTML/CSS/JS
硬件要求普通电脑即可,CPU 占用低,内存约 200MB 以内
显存要求无,纯 CPU 应用
启动方式命令行启动 Flask 服务,浏览器访问
是否支持 API支持,提供创建问卷、提交答案、查询结果、批量名单导入接口
是否支持批量任务支持批量导入好友名单、批量生成邀请链接
数据存储SQLite,单文件存储,无需额外安装数据库
适合场景社群运营、团队破冰、朋友聚会、Web 入门练习、Flask 项目模板参考

这个项目不需要 GPU,不需要模型文件,只要你电脑上有 Python 3.8 以上环境就能跑。它更适合用来练习 Web 后端、问卷系统设计和接口封装,也可以作为二次开发的起点。

2. 适用场景与使用边界

2.1 适合谁

  • 刚学完 Python 基础,想做一个完整 Web 项目的开发者。
  • 需要给社群、班级、公司团建做“好友默契/信任测试”活动的运营同学。
  • 想把玩具项目从小程序、公众号等载体扩展的前端或全栈开发者。

2.2 能解决什么问题

  • 省去手工统计问卷结果的麻烦。
  • 把“5大好友挑战”这类玩法变成一个可复用、可配置的线上工具。
  • 通过接口批量生成邀请链接,方便在群里分发。
  • 通过结果聚合页,快速看到谁的平均分最高、谁被标记为“需要观察”。

2.3 不适合什么场景

  • 不适合真正用于情感判断、人品鉴定或人际关系决策,它本质上是一个趣味游戏。
  • 不适合大规模高并发场景,SQLite 在小流量下可用,高并发场景需要切换 PostgreSQL/MySQL。
  • 不适合完全不懂 Python 的用户,因为本文给出的是源码级别的搭建方案,不是一键安装包。

2.4 合规与边界提醒

这个应用涉及人际关系测试,必须注意以下几点:

  • 邀请好友参与前,需要明确告知这是趣味测试,不采集身份证号、住址、银行卡等敏感信息。
  • 如果设置为实名模式,默认只能保存昵称;如果设置为匿名模式,不要允许填写真名。
  • 不要让测试结果成为公开嘲讽、攻击他人的素材。
  • 如果后续接入人脸识别、声纹识别、聊天记录分析等能力,需要取得明确授权,并遵守相关法律法规。

3. 环境准备与部署前检查

这是一个标准 Flask 项目,环境准备非常简单。下面是通用检查清单,按顺序执行即可。

3.1 安装 Python

建议使用 Python 3.8 至 3.11 之间的版本,过低或过高版本都可能导致依赖解析问题。安装完成后,在终端确认版本:

python --version pip --version

如果电脑里同时存在 Python 2,可能需要使用python3和pip3。

3.2 创建项目目录

建议把项目隔离在独立目录中,方便管理和后续打包。

mkdir friend-challenge cd friend-challenge

3.3 准备虚拟环境(可选但推荐)

虚拟环境可以避免把 Flask 等依赖装到全局 Python 中。

python -m venv venv

Windows 激活方式:

venv\Scripts\activate

macOS / Linux 激活方式:

source venv/bin/activate

激活后,终端提示符前会出现(venv)。

3.4 编写依赖文件

在项目根目录创建requirements.txt,内容如下:

flask>=2.3.0,<3.0

这个项目只用 Flask,不需要其他重量级框架。后续如果要用 MySQL、Redis、大模型 API,可以再按需添加。

3.5 安装依赖

pip install -r requirements.txt

安装完成后,可以查看 Flask 版本确认成功:

pip show flask

3.6 检查端口占用

Flask 默认使用 5000 端口。启动前建议检查端口是否被占用。

Windows:

netstat -ano | findstr :5000

macOS / Linux:

lsof -i :5000

如果端口被占用,可以在启动命令中指定其他端口,例如--port 8000。

4. 项目结构设计

在动手写代码前,先规划好目录结构。推荐使用下面的组织结构:

friend-challenge/ ├── app.py # Flask 主应用,包含路由和 API ├── models.py # 数据库模型初始化与查询函数 ├── templates/ │ ├── create.html # 创建挑战页面 │ ├── fill.html # 好友作答页面 │ └── result.html # 结果展示页面 ├── static/ │ ├── style.css # 简单样式 │ └── app.js # 前端交互 ├── requirements.txt └── data/ └── challenge.db # SQLite 数据库文件,自动生成

对于小项目来说,这样拆分已经足够清晰。如果后续功能增多,可以把路由拆成blueprints,把数据库操作拆成repository层。

5. 数据库设计与核心逻辑

5.1 数据表设计

这个应用需要存储三类数据:挑战问卷、挑战题目、作答记录。

challenge表:存储一次挑战的基本信息。

字段类型说明
idINTEGER 主键挑战ID
creatorTEXT创建者昵称
titleTEXT挑战名称
created_atTEXT创建时间
statusINTEGER0 进行中,1 已关闭

question表:存储“5大好友挑战”中的具体题目和分值。

字段类型说明
idINTEGER 主键题目ID
challenge_idINTEGER所属挑战ID
contentTEXT题目内容
scoreINTEGER该题满分,默认 10

answer表:存储每个好友对其他人的评价。

字段类型说明
idINTEGER 主键作答ID
challenge_idINTEGER所属挑战ID
voterTEXT评价者昵称
targetTEXT被评价者昵称
question_idINTEGER题目ID
scoreINTEGER本题打分

这里的关键逻辑是:每个参与者既是评价者,也是被评价者。好友 A 给好友 B 打分,同时也会收到其他好友的打分。最终“真朋友指数”通过平均分聚合计算得出。

5.2 Flask 应用核心代码

创建app.py,先写基础框架,包括数据库初始化和首页路由。

import os import sqlite3 from flask import Flask, render_template, request, jsonify, redirect, url_for, g BASE_DIR = os.path.dirname(os.path.abspath(__file__)) DB_PATH = os.path.join(BASE_DIR, "data", "challenge.db") app = Flask(__name__) def get_db(): """获取数据库连接,每个请求独立连接""" if "db" not in g: os.makedirs(os.path.dirname(DB_PATH), exist_ok=True) g.db = sqlite3.connect(DB_PATH) g.db.row_factory = sqlite3.Row return g.db @app.teardown_appcontext def close_db(exception): db = g.pop("db", None) if db is not None: db.close() def init_db(): """初始化数据库表结构""" db = sqlite3.connect(DB_PATH) db.executescript( """ CREATE TABLE IF NOT EXISTS challenge ( id INTEGER PRIMARY KEY AUTOINCREMENT, creator TEXT NOT NULL, title TEXT NOT NULL, created_at TEXT DEFAULT CURRENT_TIMESTAMP, status INTEGER DEFAULT 0 ); CREATE TABLE IF NOT EXISTS question ( id INTEGER PRIMARY KEY AUTOINCREMENT, challenge_id INTEGER NOT NULL, content TEXT NOT NULL, score INTEGER DEFAULT 10 ); CREATE TABLE IF NOT EXISTS answer ( id INTEGER PRIMARY KEY AUTOINCREMENT, challenge_id INTEGER NOT NULL, voter TEXT NOT NULL, target TEXT NOT NULL, question_id INTEGER NOT NULL, score INTEGER NOT NULL ); """ ) db.commit() db.close() @app.route("/") def index(): return render_template("create.html") if __name__ == "__main__": init_db() app.run(host="0.0.0.0", port=5000, debug=True)

这个基础版本已经可以启动,但还没有业务路由。接下来我们补齐创建挑战、提交答案、查看结果三个核心功能。

6. 功能测试与效果验证

6.1 创建好友挑战

创建挑战的页面需要填写创建者昵称、挑战标题,以及 5 道题目。这里把 5 道题作为默认模板,用户可以自行修改。

在后端增加一个创建挑战的接口,接收创建者信息、标题和题目列表,写入数据库后跳转到邀请页面。

@app.route("/challenge/create", methods=["POST"]) def create_challenge(): creator = request.form.get("creator", "").strip() title = request.form.get("title", "").strip() if not creator or not title: return "创建者昵称和挑战标题不能为空", 400 question_contents = request.form.getlist("questions") question_clean = [q.strip() for q in question_contents if q.strip()] if len(question_clean) < 1: return "至少需要 1 道题目", 400 db = get_db() cursor = db.execute( "INSERT INTO challenge (creator, title) VALUES (?, ?)", (creator, title), ) challenge_id = cursor.lastrowid for q in question_clean[:5]: db.execute( "INSERT INTO question (challenge_id, content, score) VALUES (?, ?, 10)", (challenge_id, q), ) db.commit() return redirect(url_for("detail", challenge_id=challenge_id))

创建成功后,需要展示一个带链接的邀约页面。邀请链接就是/challenge/<id>的访问地址,任何人打开后都可以填写。

6.2 好友作答页面

作答页面的核心设计是:每个参与者先填自己的昵称,然后选择“给谁打分”,再逐题打分。

前端表单可以一次性展示所有好友名单,让参与者依次为每个人打分。更轻量的做法是:参与者先选目标好友,再显示 5 道题。

后端接收作答数据的接口如下:

@app.route("/challenge/<int:challenge_id>/submit", methods=["POST"]) def submit_answer(challenge_id): voter = request.form.get("voter", "").strip() target = request.form.get("target", "").strip() if not voter or not target: return "评价者和被评价者昵称不能为空", 400 db = get_db() questions = db.execute( "SELECT id FROM question WHERE challenge_id = ?", (challenge_id,) ).fetchall() for q in questions: score_key = f"q_{q['id']}" score_str = request.form.get(score_key, "0") try: score = max(0, min(10, int(score_str))) except ValueError: score = 0 db.execute( "INSERT INTO answer (challenge_id, voter, target, question_id, score) VALUES (?, ?, ?, ?, ?)", (challenge_id, voter, target, q["id"], score), ) db.commit() return "提交成功"

前台页面在提交成功后,提示参与者继续为下一位好友打分,或者返回到挑战首页。

6.3 结果聚合与真朋友指数

当所有好友都完成打分后,需要聚合每个人被其他好友评价的平均分。

@app.route("/challenge/<int:challenge_id>/result") def result(challenge_id): db = get_db() rows = db.execute( """ SELECT target, COUNT(*) as answer_count, SUM(score) as score_sum, AVG(score) as score_avg FROM answer WHERE challenge_id = ? GROUP BY target ORDER BY score_avg DESC """, (challenge_id,), ).fetchall() results = [] for r in rows: avg_score = round(r["score_avg"], 2) if r["score_avg"] else 0 level = "真朋友倾向明显" if avg_score >= 8 else "表现不错,继续观察" if avg_score >= 6 else "这次挑战得分偏低,建议聊一聊" results.append( { "target": r["target"], "answer_count": r["answer_count"], "score_avg": avg_score, "level": level, } ) return render_template("result.html", results=results)

这里用平均分作为“真朋友指数”的简化计算方式。实际使用中,可以给每题设置不同的权重,或者加入“极端评分去除”的防刷机制。

6.4 测试用例示例

测试步骤输入/操作预期结果判断标准
创建挑战填写创建者昵称、标题、5道题跳转到邀请详情页URL 中出现挑战ID,页面显示邀请链接
好友提交打分选择目标好友,给5道题打分并提交提示“提交成功”数据库 answer 表新增5条记录
查看结果打开结果页显示参与好友按平均分排序平均分最高的人排第一位
重复提交同一位好友重复提交同一目标数据库记录重复可接受,实际部署建议加“每人对同一目标只评一次”的限制

需要特别说明的是,这个示例项目没有做“同一用户只能提交一次”的身份校验,生产环境建议引入微信群昵称绑定、邀请码一次性使用等机制,避免刷分。

7. 接口 API 与批量任务设计

如果不想在浏览器上点击,而是把邀请链接批量分发给好友,或者把统计数据接到已有的运营后台,就需要提供一套简单的 JSON API。

7.1 API 接口说明

接口方法功能
/api/challengePOST创建挑战,返回挑战ID和邀请链接
/api/challenge/<id>GET获取挑战详情、题目列表
/api/answerPOST提交一份作答
/api/result/<id>GET获取挑战结果聚合数据
/api/importPOST批量导入好友名单,返回生成结果

这些接口和页面路由共用同一个数据库,所以接入成本很低。

7.2 创建挑战接口示例

使用curl调用创建接口:

curl -X POST http://127.0.0.1:5000/api/challenge \ -H "Content-Type: application/json" \ -d '{ "creator": "小明", "title": "谁是真正的朋友", "questions": ["经常主动联系我", "愿意在我困难时帮忙", "聊天时认真听我讲", "不会背后说我", "愿意陪我一起做无聊的事"] }'

为了让这个接口生效,需要修改创建逻辑,同时支持 JSON 输入:

@app.route("/api/challenge", methods=["POST"]) def api_create_challenge(): data = request.get_json(force=True) creator = data.get("creator", "").strip() title = data.get("title", "").strip() questions = data.get("questions", []) if not creator or not title or not questions: return jsonify({"error": "参数不完整"}), 400 db = get_db() cursor = db.execute( "INSERT INTO challenge (creator, title) VALUES (?, ?)", (creator, title), ) challenge_id = cursor.lastrowid for q in questions: db.execute( "INSERT INTO question (challenge_id, content, score) VALUES (?, ?, 10)", (challenge_id, q), ) db.commit() invite_link = f"http://127.0.0.1:5000/challenge/{challenge_id}" return jsonify({"challenge_id": challenge_id, "invite_link": invite_link}), 200

7.3 批量导入好友名单

批量任务的核心是:避免人工一条一条发送链接,而是提供一个名单文件,程序自动为每位好友生成唯一的问候链接。

这里做一种简化设计:批量导入好友昵称列表,为每个昵称生成一条带参数voter=昵称的邀请链接,并在结果页通过target参数自动定位到对应好友的数据。

示例接口逻辑如下:

  • 接收一个names数组。
  • 遍历每个昵称,生成邀请链接http://127.0.0.1:5000/challenge/<id>?voter=<昵称>。
  • 将链接列表返回,方便运营人员批量复制。

Python 调用示例:

import requests url = "http://127.0.0.1:5000/api/import" payload = { "challenge_id": 1, "names": ["阿花", "小绿", "大熊"] } res = requests.post(url, json=payload, timeout=10) print(res.json())

如果接口返回每个好友对应的专属链接,就可以直接粘贴到聊天群中,并告之每位好友点击自己的专属链接作答。这样可以避免数据串号。

7.4 API 调用注意事项

  • 生产环境必须给 API 加身份验证,最简单的方案是请求头带一个静态 Token。
  • 批量导入接口要控制单次数量,建议每次不超过 50 个昵称。
  • 接口返回的邀请链接要使用真实域名,不能写死为127.0.0.1。

8. 资源占用与性能观察

这个项目是纯 CPU 应用,不涉及 GPU 和显存。启动后可以通过任务管理器或top命令观察内存占用。

8.1 本地资源占用参考

  • 空闲状态:Flask 进程内存约 30MB 到 80MB。
  • 有并发请求时:内存小幅上升,SQLite 读写成为主要性能瓶颈。
  • 磁盘占用:项目本身不到 5MB,数据库文件根据参与人数增长,1000 条作答记录大概占用不到 2MB。

需要注意,不同操作系统和 Python 版本下资源占用会有差异,以上仅是参考范围。

8.2 性能瓶颈在哪里

  • SQLite 的写入锁:同时大量提交作答时,可能会出现database is locked错误。
  • 结果页的AVG聚合查询:数据量在几万条以内通常没有问题,超过后需要加索引。
  • 模板渲染:如果参与人数很多,结果页一次性展示所有好友会比较慢,建议分批加载或只展示前 20 名。

8.3 降低占用与优化方向

  • 给answer表添加联合索引,例如(challenge_id, target)。
  • 把 SQLite 连接的超时时间调高。
  • 如果是在服务器部署,建议增加threaded=True参数。
  • 后续如果人数增长,可以把数据库切换为 MySQL,或者使用 Redis 缓存结果页。

9. 常见问题与排查方法

问题现象可能原因排查方式解决方案
启动后页面打不开端口被占用,或服务未启动查看终端日志,检查端口占用更换端口:python app.py --port 8000,在代码中传入端口
pip install失败网络问题或 Python 版本不兼容查看完整报错信息更换 pip 镜像源;确认 Python 版本在 3.8 到 3.11 之间
创建挑战后看不到题目题目列表没有正确写入数据库打开 SQLite 查看 question 表检查getlist("questions")表单字段名是否和前端一致
提交答案后结果为空没有给任何目标好友打分查看 answer 表记录确保前端提交了target字段
中文显示乱码终端编码问题打印请求数据观察Windows 使用chcp 65001切换 UTF-8 编码
批量导入接口返回 400没有传names数组或challenge_id不存在打印请求体检查 JSON 字段名和挑战ID
database is locked报错多并发写入 SQLite查看日志中的具体位置增加连接超时,或改用 MySQL/PostgreSQL
邀请链接打开后提示 404挑战ID不存在查看数据库 challenge 表检查链接中的ID是否正确
数据被别人任意修改未做权限控制观察异常提交记录为邀请链接加入一次性 token,或限制每个昵称每天只能提交一次

10. 最佳实践与使用建议

10.1 第一次先小范围测试

建议不要直接发给全部好友。先自己创建一个挑战,打开无痕浏览器窗口,用两个虚拟昵称各提交一次,确认结果页正常后再分享。

10.2 保持最小可运行配置

这个项目的核心依赖只有 Flask,不要在一开始就加入大型前端框架、数据库中间件和大模型 API。先把基础流程跑通,再逐步扩展。

10.3 目录与数据管理

建议把data/challenge.db加入.gitignore,避免把测试数据提交到代码仓库。如果要在服务器部署,定时备份数据库文件即可。

10.4 批量任务必须加日志

批量导入接口建议打印日志,记录每次导入的挑战ID、数量、生成链接数。这样出了问题可以快速定位。

10.5 权限与安全

如果部署到公网,一定要做访问控制。最简单的做法是在app.py中增加一个请求前置检查,只有带正确 Token 的请求才能访问 API 接口。

10.6 合规提醒

  • 只收集昵称和打分数据,不收集手机号、身份证号、通讯录等个人敏感信息。
  • 参与测试前,明确告知这是趣味互动。
  • 不把测试结果用于公开排名、恶意嘲讽或任何商业决策。
  • 如果接入任何 AI 情感分析、人脸识别、声音识别能力,必须取得参与者明确授权。

11. 可以继续扩展的方向

这个骨架项目可以继续往上加东西:

  • 接入大模型 API,对好友提交的开放性问题进行情绪倾向分析。
  • 做成小程序:前端用微信小程序,后端仍用 Flask API。
  • 增加可视化图表:用 ECharts 展示所有好友的得分雷达图。
  • 加入邀请码机制:每个挑战生成唯一码,好友凭码参与。
  • 改成匿名模式:系统随机生成代号,保护参与者隐私。
  • 增加定时关闭:超过设定时间自动停止收集答案。

对你来说,最快验证整个功能的方式是:先跑通app.py,创建一个包含 5 个经典题目的挑战,然后自己提交几组答案,最后打开结果页看排名是否正确。这个流程只要能顺利走通,剩下的事情就是按自己的具体场景去改界面和逻辑了。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询