ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

Flask+微信小程序:前后端分离看图猜成语开发实战

Flask+微信小程序:前后端分离看图猜成语开发实战 简介这是一份基于Python Flask后端与微信小程序前端实现的看图猜成语完整项目适合正在学习Python服务端开发、事件驱动机制及前后端协作的初中级开发者也可作为毕设或课程设计的参考案例。项目覆盖用户界面搭建、图片展示与成语答案校验逻辑并附带程序使用说明与程序配置说明两份文档可据此快速跑通运行环境、配置依赖并理解整体目录结构。压缩包共40个文件约559KB以py后端与wxml、wxss、js前端页面为主辅以json配置、sql数据库脚本、png图片及md说明目录划分清晰。当前已有116人浏览学习尤其适合希望通过完整小游戏案例掌握Flask接口编写、小程序交互与数据传递的读者。通过阅读源码与文档可系统梳理从界面事件响应到后端答案比对的完整链路并学习文件读写、异常处理与基础数据结构设计。1. 一个 Python 压缩包背后的两层架构解压这个 zip 后会发现里面同时出现了flask、weapp-idiom、.idea和README.md。weapp-idiom是微信小程序端代码flask是后端 API 服务README.md则介绍了启动方式。很多初学者以为“看图猜成语”是纯前端游戏图片和答案都写死在页面里就够了但这个项目把题库、图片映射和校验逻辑全放在了 Flask 后端小程序端只负责展示和交互。这种前后端分离的做法意味着换题库、加图片、统计答题数据都只需要改后端小程序端不用重新提审发布。对于正在做课程设计、想入门 Python 后端接口开发、或者要接小程序外包的开发者这个资源都能当一份最小可用的参考模板。接下来按“后端接口 → 小程序端 → 环境排错 → 联调验证”的顺序拆开讲每个环节都给出可以直接抄走的代码。2. Flask 后端设计的成语接口与图片路由2.1 为什么不用 Tkinter 而选 Flask摘要里提到 GUI 编程很多人第一反应是用 Tkinter 或 PyQt 写一个桌面程序双击 exe 就能玩。但这个项目的目录结构说明作者选了另一条路用 Flask 起一个本地 Web 服务小程序通过 HTTP 请求获取题目和图片。原因不难理解微信小程序的运行环境是沙箱不能直接读取电脑上的图片文件只能通过wx.request访问后端接口拿数据。Flask 恰好是 Python 里最轻量的选择——不需要像 Django 那样生成一堆脚手架一个app.py就能跑通接口、静态图片、跨域请求三件事。此外这个选择对学习也有好处把“请求参数”“JSON 序列化”“静态资源映射”这几个 Web 开发基础概念一次性带了出来。桌面 GUI 方案里这些概念几乎接触不到但实际工作中 Web API 反而是最通用的能力。2.2 成语题库的数据组织与 /api/idioms 接口先看压缩包里的flask目录典型的 Flask 工程结构。将成语数据用 Python 列表加字典组织每个成语包含编号、答案、图片文件名。为了演示项目里通常会内置几十条成语结构如下# app.py from flask import Flask, jsonify, request, send_from_directory import random, os app Flask(__name__, static_folderimages, static_url_path/images) IDIOMS [ {id: 1, answer: 画蛇添足, image: huashe.jpg}, {id: 2, answer: 对牛弹琴, image: duiniu.jpg}, {id: 3, answer: 守株待兔, image: shouzhudait.png}, # 更多成语... ] app.route(/api/idioms, methods[GET]) def get_idiom(): 随机返回一道题目不携带答案 item random.choice(IDIOMS) return jsonify({ id: item[id], image_url: f/images/{item[image]}, hint: f这题有 {len(item[answer])} 个字 })static_folderimages将images目录暴露为静态资源根路径static_url_path/images则让浏览器可以通过/images/huashe.jpg直接访问图片。接口返回时不带answer字段避免被小程序端直接解析出答案——虽然前端能看到图片但至少不把答案明文暴露在接口里。随机选用random.choice(IDIOMS)好处是每次刷新题目都不同。如果你不想随机想按顺序出题可以把random.choice换成带索引的取模方式INDEX 0 app.route(/api/idioms, methods[GET]) def get_idiom_by_index(): global INDEX item IDIOMS[INDEX % len(IDIOMS)] INDEX 1 return jsonify({id: item[id], image_url: f/images/{item[image]}})这样每次调用按顺序返回适合测试时复现同一个 bug。接口参数方面GET请求默认不带参数但后端预留了id参数选项便于指定返回某一题/api/idioms?id3。实现时可以在函数里通过request.args.get(id, typeint)接收并做边界判断。接口方法参数返回/api/idiomsGET无id、image_url、hint/api/idioms?id3GETidid、image_url、hint/api/checkPOSTid、answerresult、correct_answer2.3 静态图片路由与 /api/check 校验逻辑图片如果放在 Flask 的static_folder下send_from_directory是更稳妥的显示方式。尤其是图片文件名包含中文时直接拼 URL 会乱码用这个函数可以避免app.route(/images/path:filename) def get_image(filename): return send_from_directory(images, filename)答案校验接口是另一个重点。小程序端拿到用户输入后POST 到/api/check后端将输入与标准答案比较app.route(/api/check, methods[POST]) def check_answer(): data request.get_json() idiom_id data.get(id) user_answer data.get(answer, ).strip() idiom next((x for x in IDIOMS if x[id] idiom_id), None) if not idiom: return jsonify({result: False, message: 题目不存在}), 404 correct user_answer idiom[answer] return jsonify({ result: correct, correct_answer: idiom[answer] if not correct else None })这个接口的逻辑并不复杂但注意两个细节。第一user_answer做了strip()去除用户输入首尾空格第二next加默认值None避免了遍历不到时抛异常。如果用户答错后端返回正确答案小程序端可以弹窗提示“正确答案是画蛇添足”。这样设计比在前端存答案更符合真实项目规范——题库和校验逻辑都在服务端客户端永远只拿到图片和提示。3. 微信小程序端从加载页到答题页的交互实现3.1 加载页数据获取与渲染时序小程序端有两个页面加载页pages/load/load和答题页pages/index/index。很多人会忽略“修改刚进入的加载页面”这个需求直接在小程序启动时跳转到首页然后首页onLoad里请求接口结果出现短暂白屏。常见做法是单独保留加载页// pages/load/load.js Page({ data: { question: null, loading: true }, onLoad() { this.fetchIdiom() }, fetchIdiom() { wx.request({ url: http://127.0.0.1:5000/api/idioms, // 真机调试时这里要改成局域网 IP success: (res) { if (res.statusCode 200) { this.setData({ question: res.data, loading: false }) } else { wx.showToast({ title: 加载失败, icon: none }) } }, fail: () { wx.showToast({ title: 请求失败检查后端, icon: none }) } }) } })这里url是后端地址真机上不能写127.0.0.1要改成开发机的局域网 IP。setData是小程序专有的数据更新方法它会把data中的字段同步到视图层同时触发页面重新渲染。如果直接在onLoad里同步调用this.data.question大概率拿到的是null因为wx.request是异步的。loading字段用于控制加载状态。WXML 里用wx:if判断view wx:if{{loading}}加载中.../view view wx:elif{{question}} image src{{question.image_url}} modeaspectFit/image text{{question.hint}}/text /viewmodeaspectFit让图片按比例缩放并完整显示不会出现裁剪。小程序端加载页从进入就触发请求后端没启动时这里会直接走到fail回调页面显示“检查后端”。3.2 动态标题、题目切换与外部跳转链接题库接口不返回 number 字段时前端可以根据返回的id动态设置标题。小程序提供wx.setNavigationBarTitlewx.setNavigationBarTitle({ title: 第 this.data.question.id 题 })这个 API 必须在页面onShow之后调用放在success回调里是安全的。如果搜索热词提到的“小程序动态设置标题”指的就是这个——不要试图直接修改app.json里的navigationBarTitleText那是一个静态配置运行时改标题只能走setNavigationBarTitle。题目切换用wx.redirectTo而不是wx.navigateTowx.redirectTo({ url: /pages/index/index?id this.data.question.id })两者区别在于navigateTo会保留当前页面栈用户可以返回redirectTo会关闭当前页面防止用户在加载页和答题页之间来回跳转导致答题状态混乱。答题页在onLoad里通过options.id拿到题目编号再根据编号请求对应图片。如果项目里涉及跳转小程序外部业务链接比如weixin://dl/business这类 scheme常见做法是后端下发一个目标地址前端通过wx.navigateToMiniProgram或者 web-view 组件打开。这里需要注意的是业务链接域名必须提前在小程序后台配置白名单否则真机上点击会直接报invalid url。生成链接的流程一般是运营后台根据业务参数拼接完整链接小程序端只负责接收和触发不要在端上手工拼参数避免路径转义出错。3.3 答案校验与输入容错答题页拿到用户输入后先做本地基本校验再调后端接口// pages/index/index.js Page({ data: { id: null, input: , image_url: }, onLoad(options) { this.setData({ id: options.id }) this.loadImage(options.id) }, loadImage(id) { wx.request({ url: http://127.0.0.1:5000/images/${id}, success: (res) { // 实际上图片 URL 在前一页已经拿到了 } }) }, submitAnswer() { if (!this.data.input.trim()) { wx.showToast({ title: 不能为空, icon: none }) return } wx.request({ url: http://127.0.0.1:5000/api/check, method: POST, data: { id: this.data.id, answer: this.data.input }, success: (res) { const result res.data.result wx.showToast({ title: result ? 正确 : 错误 res.data.correct_answer, icon: result ? success : none }) } }) } })submitAnswer里先做了空值拦截避免无效请求打到后端。用户输入的成语可能存在全角和半角差异比如用户输入中文括号和英文括号简单的比较会误判。比较稳妥的处理是在提交前统一转换const normalized this.data.input .trim() .replace(/[]/g, () .replace(/[]/g, ))这种字符串规范化处理在真实项目中很常见因为小程序键盘在部分手机上会自动把标点转成全角。操作对应 API触发时机获取题目wx.request GET /api/idioms加载页 onLoad设置标题wx.setNavigationBarTitle数据返回 success 回调页面跳转wx.redirectTo加载完成后提交答案wx.request POST /api/check点击按钮提示信息wx.showToast校验结果返回4. Python 环境配置、依赖安装与运行排错4.1 Python 安装与 VSCode 环境配置这个项目依赖 Python 3 环境。如果是 Linux 系统安装 Python直接apt install python3有时装出来版本偏旧建议用python3 --version确认版本低于 3.6 的话部分语法可能不兼容。Windows 下 python 安装教程已经很多核心就一个点安装时勾选“Add Python to PATH”否则命令行敲python会提示找不到命令。装完后建议在 VSCode 里打开项目目录按Ctrl Shift P选择“Python: Select Interpreter”指定虚拟环境或系统 Python。这一步不做的话VSCode 可能默认用 conda 的 Python而依赖装在系统 Python 里运行 Flask 时报ModuleNotFoundError但命令行却正常。验证环境是否就绪python --version pip --version提示pip在部分 Linux 发行版中需要写pip3优先使用python -m pip更通用。4.2 Flask 依赖安装与跨域配置在flask目录下安装依赖cd flask python -m pip install flask flask-corsflask-cors不是 Flask 自带的但小程序端请求后端时浏览器控制台经常会报跨域错误。如果不在后端处理跨域wx.request在开发者工具里会失败。最简单的做法是from flask_cors import CORS CORS(app)这会允许所有来源跨域访问。如果只在开发环境用这没问题部署到线上时要收紧配置origins白名单CORS(app, resources{r/api/*: {origins: [https://yourdomain.com]}})依赖安装完后启动后端python app.pyFlask 默认端口是 5000。如果被占用可以通过环境变量改端口export FLASK_RUN_PORT5001 flask runWindows 下用set FLASK_RUN_PORT5001。4.3 高频报错排查清单运行过程中以下几类报错出现频率最高按症状和根因整理成清单报错可能原因处理方式ModuleNotFoundError: No module named flaskFlask 未安装或解释器选择错误python -m pip install flaskVSCode 里切换解释器OSError: [Errno 48] Address already in use5000 端口被占用lsof -i :5000查 PIDkill 掉或换端口Cross-Origin Request Blocked后端未启用 CORS加CORS(app)404 Not Found: /images/xxx.jpg图片路径不对或文件名中文未编码检查static_folder路径改用send_from_directoryTypeError: string indices must be integers后端返回的不是 JSON 数组而是字符串检查 IDIOMS 数据结构确认jsonify使用正确最常见的问题其实出在小程序端的url上。电脑上127.0.0.1能通但手机预览时连不上这是没有把127.0.0.1改成局域网 IP。VSCode 里直接改app.js的baseUrl变量即可不同页面引用同一个配置不要各处写死 URL// config.js module.exports { baseUrl: http://192.168.1.100:5000 }页面里改为const config require(../../config.js) wx.request({ url: config.baseUrl /api/idioms })另外注意 Flask 的调试模式。app.run(debugTrue)会在代码改动后自动重载但也意味着只要有语法错误页面会直接抛异常。真机调试时建议关掉 debug否则频繁重载会导致小程序请求超时。5. 批量加载图片与成语数据的联调验证在日常开发小程序时我习惯把联调验证固化成一条命令每次改动后端代码都先跑一遍而不是打开页面手动点。先用curl验证接口# 验证取题目接口 curl http://127.0.0.1:5000/api/idioms # 验证图片访问 curl -I http://127.0.0.1:5000/images/huashe.jpg # 验证答案校验接口 curl -X POST http://127.0.0.1:5000/api/check \ -H Content-Type: application/json \ -d {id:1, answer:画蛇添足}curl -I只请求响应头能快速判断图片是否存在返回200 OK说明 Flask 正确找到了静态文件。再配合浏览器直接访问http://127.0.0.1:5000/api/idioms检查 JSON 里的中文字符有没有乱码——如果answer字段出现\u753b\u86c7\u6dfb\u8db3这种转义序列是因为jsonify默认启用了ensure_ascii。处理方法是让 Flask 输出原生的 UTF-8 中文app.config[JSON_AS_ASCII] False批量加载图片时小程序端的image组件会并发加载多张图片Flask 的静态文件服务默认是单线程的图片多时可能卡顿。改进方法是用send_from_directory加上conditionalTrue让浏览器使用缓存from flask import send_from_directory app.route(/images/path:filename) def get_image(filename): return send_from_directory(images, filename, conditionalTrue)conditionalTrue会让 Flask 检查请求头里的If-Modified-Since图片没变化时返回304 Not Modified下一次加载直接从本地缓存读取响应体不再传输。这在小程序端扫码打开时感受尤其明显——非首次进入几乎秒开。另一个常被忽略的坑是图片文件名的空格和中文。小程序端渲染image src/images/守株待兔.png时URL 里的中文会在真机上被自动转义但 Flask 端拿到的路径可能匹配不上。解决办法是不直接使用文件名而是在 Flask 里维护文件名到图片路径的映射或者统一将图片命名为拼音加编号避免所有非 ASCII 字符。若确实要用中文文件名路由改为app.route(/images/path:filename) def get_image(filename): from urllib.parse import unquote filename unquote(filename) return send_from_directory(images, filename)unquote会把 URL 编码后的中文字符还原成原始文件名解决真机上的 404 问题。这样处理后接口返回的image_url不需要任何额外转码直接交给image组件即可。本文还有配套的精品资源点击获取
返回列表