news 2026/10/3 9:23:18

校园跑腿小程序实战:Flask+uni-app全栈开发与避坑指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
校园跑腿小程序实战:Flask+uni-app全栈开发与避坑指南

最近被问得最多的一个项目,就是校园跑腿加食堂超市的小程序。我帮学校信息中心搭了一套,后端用 Python 的 Flask,前端用 uni-app 编译成微信小程序,跑了一个学期,高峰期每天几百单,整体还算稳。这篇文章把整个项目的选型思路、数据库设计、接口权限、前端适配、打包上架和排障过程完整整理一遍,给正准备做校园 O2O 项目、或者毕业设计选类似题目的朋友一个可以直接抄作业的参考。

这类项目看起来只是“下单 + 配送”,但真做起来要同时搞定用户端、跑腿员端、商家端和管理后台,还牵扯微信支付、订单状态流转、多端打包这些问题。我会尽量把关键环节和踩过的坑写细,少让你走弯路。

1. 项目整体设计与技术选型

1.1 需求拆解:从“跑腿”到“食堂超市”的业务链路

刚接到这个需求的时候,第一反应不是急着写代码,而是先把业务流程理顺。校园跑腿听起来很简单,但如果同时包含食堂点餐、超市代购、快递代取三种业务,后台逻辑会差挺多。食堂点餐是商家备餐后骑手去取,超市代购是骑手拿着购物清单去逛超市,快递代取是纯跑腿,前端页面和订单字段都不一样。

最终我把业务拆成四个角色:学生用户、跑腿员、商家或档口、管理员。学生用户能浏览食堂档口和超市商品,下单后选择自取还是配送;配送单会被跑腿员抢单,骑手完成取货送达后订单进入完成状态;商家可以在商家端 App 上架商品、接单出餐;管理员在后台做用户管理、订单干预、佣金比例和退款处理。

这里面最容易忽略的是“配送距离和费用”的计算。如果做成固定配送费,跑腿员不愿意接远单;如果全部按实际距离算,又要引入地图 API。我们第一期选择了“按楼栋区域区分”:把学校宿舍区、教学区拆成若干区域,每个区域固定配送费,比如同栋 3 元、跨区 5 元,后续再做距离计价。这个决定省了大量开发时间,也符合学生习惯。

1.2 技术选型:为什么是 Flask 和 uni-app

后端语言我选了 Python + Flask,没有选 Django。原因很简单:这个项目接口量不大,大概三十多个接口,Flask 的蓝图和 RESTful 风格足够组织清晰;而且 Flask 部署轻量,在一台 2C4G 的学生服务器上用 gunicorn 就能跑起来。Django 自带 admin 和 ORM 确实香,但杀鸡用牛刀,很多配置反而成了学习成本。

前端用 uni-app 不是因为它比原生小程序好,而是因为多端能力强。当时学校还要求做一个供商家和跑腿员用的 App,如果用原生微信小程序写一版,再要求安卓 App,工作量直接乘 2。uni-app 是 Vue 语法,写一套代码能编译到小程序、H5 和 Android App。虽然有些坑,后面排障部分会讲,但对团队就两三个人、还要做管理端的项目来说,性价比很高。

选了 uni-app 还有一个隐性收益:组件生态。uview-plus 这种 UI 库对表单、表格、列表分页支持不错,比自己写原生组件快很多。关于组件导入踩坑,我放到第二章讲。

1.3 项目目录规划:一眼能看懂的工程结构

项目分两个仓库,后端是 flask-server,前端是 uniapp-client。我习惯按功能模块而不是按文件类型划分,后端放 api、models、services、utils,前端放页面、组件、公共样式。大概结构长这样:

flask-server/ app/ api/ # 蓝图:auth.py / shop.py / order.py / user.py models/ # SQLAlchemy 模型 services/ # 业务逻辑:订单创建、支付回调、对账 utils/ # 装饰器、状态码、微信工具 config.py run.py requirements.txt uniapp-client/ pages/ # index/ order/ user/ rider/ merchant/ components/ # 自定义组件 utils/ # request.js / auth.js static/ # tabbar 图标等 pages.json manifest.json

目录结构看起来普通,但对后续维护很重要。尤其后端的订单创建逻辑,我坚持放 service 而不是 controller 里,这样微信支付回调也要调同一套逻辑,避免同样的代码写两遍。前端 utils/request.js 统一封装了 token 注入和 401 跳转,后面所有页面调接口只关心业务数据。项目做到一半你会发现:好的目录不是给代码看的,是给人省脑力的。

2. Flask 后端:数据库、登录态和订单状态机

2.1 数据库表设计:一张表讲清人和钱的关系

业务跑得顺不顺,数据库设计起一半作用。我建了七张核心表,基本够覆盖校园跑腿和食堂超市场景,先看表格:

表名用途核心字段备注
user用户和角色id, openid, phone, role, balance, statusrole 区分 student/rider/admin
merchant商家或档口id, user_id, name, category, status, image每个商家绑定一个用户账号
product商品id, merchant_id, name, price, stock, sales, image超市商品和食堂菜品共用
address收货地址id, user_id, region, detail, tag按楼栋区域配送
order订单主表id, order_no, user_id, rider_id, merchant_id, status, amount, delivery_type, create_time配送费单列
order_item订单商品明细id, order_id, product_id, quantity, price高并发下单价快照
wallet_log钱包流水id, user_id, amount, type, balance_after, remark下单、退款、佣金都要记

核心是 order 和 order_item 分离。比如用户点了三个菜加一桶泡面,order 表记录订单总额和配送费,order_item 记录每个商品当时的单价和数量。为什么必须有 order_item?因为商品价格会变,如果订单表里只存一个 total_amount,商家改价后订单历史就说不清了。我在写第二版时还加了 product_snapshot 字段,把商品名称、图片、规格、单价整段 JSON 存进去,退款对账时直接回看。

钱包流水表很多初学者会忽略,但跑腿项目一定要有。骑手每完成一单,佣金进入钱包;学生退款时金额从钱包扣回,每一笔金额变动都必须有流水。我规定所有金额操作一律通过 wallet_log 记录,禁止直接改 user.balance 字段,这样即使对账不平也能靠流水反查。

2.2 微信登录与 JWT 权限:别把 openid 当登录态

微信小程序登录的原理不复杂:前端调用 wx.login 拿到一个临时 code,后端拿这个 code 去微信接口换 openid 和 session_key。openid 是用户在小程序里的唯一身份,但你不能把 openid 直接返回给前端当 token,因为它只是标识,不带过期和校验信息,而且 session_key 是敏感信息,绝不能被前端拿到。我这边是后端用 JWT 签发一个带 user_id 和过期时间的 token。

后端登录接口的关键代码如下:

@auth_bp.route('/api/auth/wx_login', methods=['POST']) def wx_login(): data = request.get_json() code = data.get('code', '') params = { "appid": app.config["WX_APPID"], "secret": app.config["WX_SECRET"], "js_code": code, "grant_type": "authorization_code" } wx_resp = requests.get( "https://api.weixin.qq.com/sns/jscode2session", params=params, timeout=5 ).json() if "openid" not in wx_resp: return jsonify({"code": -1, "message": "微信登录失败"}) openid = wx_resp["openid"] user = User.query.filter_by(openid=openid).first() if not user: user = User(openid=openid, nickname="新用户", role="student", balance=0) db.session.add(user) db.session.commit() payload = {"user_id": user.id, "exp": int(time.time()) + 7200} token = jwt.encode(payload, app.config["SECRET_KEY"], algorithm="HS256") return jsonify({"code": 0, "data": {"token": token, "user_info": user.to_dict()}})

注意两个坑:第一,code 只能用一次,前端不要重复发登录请求,否则第二次会报 invalid code。第二,requests 请求微信接口要设置超时,不要用默认阻塞,我习惯加 timeout=5,微信接口偶尔抽风不能拖垮整个接口。

JWT 校验装饰器我用在几乎所有业务接口上。为了让代码可读,我用 flask 的 g 对象保存当前用户,不在 request 上塞自定义属性:

from functools import wraps from flask import g, request, jsonify import jwt def login_required(fn): @wraps(fn) def wrapper(*args, **kwargs): token = request.headers.get("Authorization", "") if token.startswith("Bearer "): token = token[7:] try: payload = jwt.decode(token, app.config["SECRET_KEY"], algorithms=["HS256"]) g.user_id = payload["user_id"] except jwt.ExpiredSignatureError: return jsonify({"code": 401, "message": "登录已过期"}) except jwt.InvalidTokenError: return jsonify({"code": 401, "message": "无效凭证"}) return fn(*args, **kwargs) return wrapper

后续接口里拿 g.user_id 再去查 User,这样每个接口不需要重复解析 token 逻辑。

2.3 订单状态机:从待支付到已完成有哪些分支

订单是这类项目里最容易出 bug 的地方,所以我专门整理了一套状态流转。订单状态我定义为:

状态值含义触发动作
pending_payment已下单待支付用户点击提交订单,货锁 10 分钟
paid已支付待接单支付回调成功后
accepted跑腿员已接单抢单或管理员指派
delivering配送中跑腿员点击取货完成
completed已送达或已收货学生确认或超时自动确认
cancelled已取消支付前用户取消,管理员强制取消
refunding退款中售后申请后

状态流转不是所有方向都允许。比如 pending_payment 只能到 paid 或 cancelled,paid 只能到 accepted 或 cancelled,accepted 不能直接跳到 completed,必须先 delivering。我写接口时没有用 if 满地乱写,而是维护了一个允许流转字典:

ORDER_STATUS_TRANSITION = { "pending_payment": ["paid", "cancelled"], "paid": ["accepted", "cancelled"], "accepted": ["delivering", "cancelled"], "delivering": ["completed"], "completed": [], "cancelled": [], "refunding": ["completed", "cancelled"] }

每次更新订单状态前先判断前一步是否在允许列表里,不在就直接报“状态异常”。这个设计省了后面排查并发乱改状态的力气。还有一点:学生支付后订单里的“锁货”状态要释放,最简单做法是创建订单时给订单写一个 pay_expire 字段,定时任务扫描超时未支付订单,把库存加回去并置为超时取消。

2.4 支付回调的幂等处理

优惠券和红包如果业务不复杂,可以先不做,但支付回调的幂等处理必须一开始就做好。微信支付回调可能会因为网络原因重试,同一个订单如果回调处理两遍,余额和库存就双倍扣减。我当时的做法:回调处理函数第一步查询订单,如果订单已经处于 paid,直接返回 success,不再重复处理。只有待支付订单才允许进入支付成功后的流水扣减、库存扣减、日志写入。这个判断看起来简单,却是保证财务数据准确的底线。

具体在 Flask 里处理支付回调时,还需要对微信返回的 xml 和 sign 做校验。验签失败直接记录日志并返回错误,避免伪造回调。出于篇幅不贴太多代码,但“先查订单状态,再改数据,最后回执 success”这个顺序一定要保证。

3. uni-app 前端:页面、导航栏和列表加载更多

3.1 初始化项目:插件市场导入 uview-plus 比 npm 省心

前端我用 HBuilderX 创建 uni-app 项目,模板选 Vue3。创建完之后第一件事是引入 UI 组件库,我用的 uview-plus。在 HBuilderX 里切换到插件市场,直接找到 uview-plus,选择“下载插件并导入项目”,这个方式比 npm 安装省心,因为 npm 版本经常和 HBuilderX 的编译链有兼容问题。我踩过一次坑,Vue3 项目用了 uview 老版本,一编译就报找不到组件。

导入之后要配 pages.json 里的 easycom 规则,才能直接在模板中用 u-button、u-input 这类组件而不用手动 import。配置如下:

"easycom": { "autoscan": true, "custom": { "^u--(.*)": "uview-plus/components/u-$1/u-$1.vue", "^up-(.*)": "uview-plus/components/u-$1/u-$1.vue" } }

然后在 main.js 里注册:

import { createSSRApp } from 'vue' import uviewPlus from 'uview-plus' import App from './App.vue' export function createApp() { const app = createSSRApp(App) app.use(uviewPlus) return { app } }

uview-plus 的按需引入在 HBuilderX 里需要手动开启分包配置,如果项目超过 2MB 也可以上微信分包,但早期别折腾,直接全量注册更稳。

3.2 tabbar 配置和顶部导航栏高度适配

小程序底部 tabbar 必须在 pages.json 里声明,不能用普通页面里写死一个 div 模拟,否则底部栏不会跟随页面切换。tabbar 的 iconPath 和 selectedIconPath 只支持本地路径,不能放网络 URL,很多新手在这里被坑。我们 tabbar 三个页面:首页、订单、我的,配置参考如下:

"tabBar": { "color": "#808080", "selectedColor": "#1E82D4", "backgroundColor": "#ffffff", "list": [ { "pagePath": "pages/index/index", "text": "首页", "iconPath": "static/tab/home.png", "selectedIconPath": "static/tab/home-active.png" }, { "pagePath": "pages/order/order", "text": "订单", "iconPath": "static/tab/order.png", "selectedIconPath": "static/tab/order-active.png" }, { "pagePath": "pages/user/user", "text": "我的", "iconPath": "static/tab/user.png", "selectedIconPath": "static/tab/user-active.png" } ] }

顶部导航栏高度是另一个麻烦。微信小程序的导航栏区域由状态栏和胶囊按钮组成,不同机型胶囊位置不一样;如果用自定义导航栏,不能直接写死 44px。我封装了一个工具方法,在 App.vue 里计算:

export function getNavBarInfo() { const systemInfo = uni.getSystemInfoSync() const statusBarHeight = systemInfo.statusBarHeight const menuBtn = uni.getMenuButtonBoundingClientRect ? uni.getMenuButtonBoundingClientRect() : null let navBarHeight = 44 if (menuBtn) { navBarHeight = (menuBtn.top - statusBarHeight) * 2 + menuBtn.height } return { statusBarHeight, navBarHeight } }

拿到数据后放到 globalData,自定义导航栏组件里用 padding-top 把状态栏高度撑开,标题高度用 navBarHeight 控制。实测不同机型差距很大,有的胶囊顶部离状态栏底部只有 12px,有的超过 18px,写死会歪。

3.3 列表加载更多:小程序分页的正确打开方式

食堂点餐页和订单列表都涉及“下拉加载更多”。最好的实现是页面滚动到底部时触发的 onReachBottom 生命周期。这里有个要点:分页状态下要区分“第一页”和“加载中”,不能在请求还没回来时再次触发,否则大量重复请求会把后端打挂。我的 loadList 模板:

data() { return { pageNum: 1, pageSize: 10, total: 0, list: [], loading: false, finished: false } }, async loadList() { if (this.loading || this.finished) return this.loading = true try { const res = await request({ url: '/api/order/list', data: { pageNum: this.pageNum, pageSize: this.pageSize } }) this.total = res.data.total this.list = this.pageNum === 1 ? res.data.list : this.list.concat(res.data.list) this.finished = this.list.length >= this.total } finally { this.loading = false } }, onReachBottom() { if (this.finished) return this.pageNum += 1 this.loadList() }, onPullDownRefresh() { this.pageNum = 1 this.finished = false this.loadList().finally(() => uni.stopPullDownRefresh()) }

注意:onReachBottom 并不是所有页面默认开启,需要在 pages.json 对应页面配置里开启“下拉刷新”才能配合 onPullDownRefresh。实际中列表页还要处理好空态:“暂无订单”的占位不要用文字居中,最好配一张小图和重新加载按钮,用户体验差别很大。另外小程序列表页的数据量不要贪多,pageSize 我通常设 10,超过 20 在某些低端安卓机上滚动会卡。

3.4 订单状态轮询、自定义分享和小程序生命周期

订单状态是动态的,理论上用 WebSocket 最实时,但小程序 WebSocket 在切后台后很容易断开,重连逻辑要写很多。我们项目最终妥协成轮询:订单详情页进入后立即拉一次,然后每 15 秒用 setInterval 拉状态,页面 onUnload 或 onHide 时 clearInterval。实测对几百单规模完全够用,用户感知不到 15 秒延迟。

分享功能是校园项目的重要传播渠道。小程序需要用 onShareAppMessage 回调配置分享内容,微信小程序还支持 onShareTimeline 分享到朋友圈。自定义分享消息:

onShareAppMessage() { return { title: '帮同学带饭跑腿,接单快人一步', path: `/pages/index/index?invite=${this.myUserId}`, imageUrl: 'https://your-domain.com/share-banner.png' } }

监听用户离开小程序则在 App.vue 里写 onHide,我们用它做一件事:如果用户停留在下单编辑页超过时间,离开小程序时自动保存草稿,避免订单内容丢失。但要注意 onHide 在切后台和跳转其他小程序都会触发,不能在这里做需要网络返回的强操作,只适合发一个轻量级保存请求。

3.5 单选框、表单校验和页面权限跳转

食堂点餐里的“自取/配送”选择,小程序原生可以用 radio-group,但 uview-plus 的 u-radio 在视觉和事件处理上更一致。要注意三点:radio 的 value 是字符串,和数字比较要 toString;表单提交前要校验地址和配送区域,不然骑手会送到错误位置;商家端页面要在 onShow 里校验用户角色,不是管理员就 redirect 到登录页,不能只隐藏入口不拦截接口。

我用 uview-plus 的 u--form 组件做表单校验,rules 里的 type 不要和数据结构冲突。比如配送费选中的是数字,但组件返回字符串,提交前必须 Number() 转换,否则后端模板匹配的 int 类型会报错。这些细节虽然小,但在真机测试时最容易发现。

4. 从 HBuilderX 到微信小程序:打包、审核与上架

4.1 manifest.json 配置和小程序运行调试

一个 uni-app 项目对应一套 manifest.json,这里集中配置不同端的 AppID、权限、SDK 等。微信小程序端关键配置是“微信小程序 AppID”,没有它只能预览不能真机上传。第一次运行前,先确保 HBuilderX 顶部“运行”菜单可以下载微信开发者工具调试服务,然后在微信开发者工具里把“服务端口”打开,HBuilderX 才能把编译产物传过去。

manifest 里还有权限声明,比如获取位置、相册、摄像头。但注意:微信小程序端权限其实在微信公众平台后台的“开发管理 - 接口设置”里申请,不是 manifest 里勾了就行。我做项目初期花了半天以为 manifest 配置好定位就能用,结果微信开发者工具里一直报 auth denied,后来去公众平台把 wx.getLocation 权限申请下来才解决。

4.2 体验版分发:怎么把小程序发给朋友试用

把小程序发给别人试用,不是直接把项目打包发微信。常规流程是:在微信开发者工具点击“上传”,填版本号和备注,然后到微信公众平台后台“版本管理”里把它设为体验版,生成体验版二维码。关键是必须先在“成员管理”里把测试微信账号添加为体验成员,否则扫码也打不开。一般上传后等 1-2 分钟,设置体验版后立刻能扫。

这里有个技巧:体验版请求后端接口用的是真实服务器地址,如果后端还没配置 https 证书,微信开发者工具会报“无效的域名”。可以临时在开发者工具里勾选“不校验合法域名”,但体验版手机扫码就没办法绕过,必须把合法域名加到小程序后台,而且要确保是 https。我建议后端一开始就配好域名和 SSL 证书,不要等上线前再补。

4.3 安卓应用市场的上架经验

如果要打包成安卓 App 再上各应用市场,第一步是在 HBuilderX 里使用“发行 - 原生 App - 云打包”,需要配置 Android 打包证书。证书用一个 .jks 文件,设置好 keystore 密码和 alias,这个文件必须留存好,后面更新版本要用同一个证书签名,丢了就永远没法覆盖安装。

我这次也用同一套 uni-app 代码打过安卓包,踩了两个坑:一是云打包的包名和 manifest 里 AppID 不一致,导致推送不能用;二是上架时各市场要求提供软件著作权、隐私政策说明,跑腿类 App 通常还需要企业主体资质。如果只是个人开发者,上架难度不小,建议直接走微信小程序先用起来,再考虑 App。

微信小程序有“年审”概念,主要是企业主体认证每年要审核一次。校园项目挂在学院名下时,认证到期前一个月后台会提醒,记得安排学校管理员接口处理,否则小程序会被暂停线上服务。个人主体的小程序不需要走年审,但企业主体需要。这块属于运营事项,技术侧别等白屏了才知道。

5. 常见问题与排障实录

5.1 uni-app 不打印日志信息?先看编译环境

很多朋友用 HBuilderX 真机运行 uni-app 时发现 console.log 不输出,第一反应是代码写错了。其实最常见原因是 HBuilderX 控制台的日志级别被过滤掉了,或者在“运行到手机或模拟器”时没有打开日志级别的 Verbose。我习惯在代码里封装一个统一的 log 方法:

export function log(...args) { if (process.env.NODE_ENV === 'development') { console.log('[app]', ...args) } }

发布后会自己消失,不用删。如果真机还是看不到,试试用 uni.showToast 输出关键变量,虽然丑,但定位问题很快。另外微信开发者工具里的 console 和 HBuilderX 控制台是两套,别混着看。

5.2 真机抓包:用 Charles 看微信小程序请求

小程序里所有接口都是 HTTPS,真机调试时用微信开发者工具能看到一部分请求,但那是在开发工具环境里,真机上有些问题复现不了,这时候就要抓包。我用的工具是 Charles,步骤不复杂:手机和电脑连同一个局域网,手机 WiFi 代理设到电脑 IP 的 8888 端口,安装 Charles 根证书到手机上,就能看到小程序发出的 HTTPS 请求内容。但要注意:小程序中的合法域名校验和证书信任机制会让部分请求在代理下直接失败,这是正常现象。

抓包最常解决的问题是:真机上接口偶发超时、并发请求被阻塞、cookie 和 token 头没带上,这些在开发工具模拟器里很难复现,抓包一看就明白。我每次排查接口问题基本都是这个顺序:先看后端日志,再看 Charles 请求头,然后看返回体,最后才会怀疑前端页面逻辑。

5.3 输入法顶起、tabbar 错位和视频旋转等兼容问题

安卓机上页面里的输入框聚焦时,整个页面包括 tabbar 都会被输入法顶起,原因是默认 adjust-position 把页面整体上移。如果你用的是自定义 tabbar,这个问题更明显。解决办法是把 input 的 adjust-position 设为 false,然后自己监听键盘高度计算输入框位置。uni-app 里 input 组件有 cursor-spacing 属性,可以撑开输入框与输入法之间的距离。但有时候并不生效,最彻底的做法是弹出一个独立的下单抽屉,原生输入框不用键盘时收起,这样底栏不参与布局。我们最后是改了布局才彻底解决。

视频旋转是另一个安卓机上的怪问题,部分安卓机型拍摄视频时记录的旋转角度和播放器解析方式不一致。我用 uni-app 的 chooseVideo 拿到临时文件后,需要读视频的 orientation 字段,如果非 up 方向就调用服务端 ffmpeg 旋转或前端 canvas 重新编码。在校园跑腿场景里,视频主要用于跑腿员上传取货照片和视频留证,这个小坑很烦人,后来直接要求骑手只拍照不录视频,业务上更合理,技术上少一个坑。如果必须录视频,建议用 video 组件的 show-center-play-btn 配置,或者集成原生插件,不要试图在小程序里转码,性能撑不住。

5.4 H5 唤起微信小程序链接无法访问

很多项目会把校园跑腿服务同时挂到微信公众号 H5 里,用户通过 H5 链接唤起小程序,但这个链接有时报“无法访问”。原因是微信的开放标签 wx-open-launch-weapp 必须在 H5 页面里处于微信公众号的 JS 接口安全域名下,且 H5 链接和要打开的小程序必须绑定在同一个开放平台账号下。你在普通浏览器或非微信内置浏览器打开,链接本来就不会生效。

排查顺序:确认微信浏览器、确认公众号和小程序是否绑定同一开放平台、确认 JS-SDK 签名没失效。我们最后干脆不在 H5 里走跳转,直接展示“复制口令到微信小程序搜索”,复杂问题用土办法解决。

5.5 Flask 部署和 Python 环境问题速查

后端部署这步虽然项目标题里已经写了 Flask,但很多新手会卡在 Python 环境上。最稳的组合是:服务器 Linux + Python3 虚拟环境 + gunicorn + Nginx 反代。基本命令:

python3 -m venv venv source venv/bin/activate pip install -r requirements.txt gunicorn -w 4 -b 127.0.0.1:8000 run:app

然后用 Nginx 把 80/443 端口转到 8000,并配上 SSL 证书。如果你用 pip 安装某个库报“无法安装”,先说三件事:确认当前在虚拟环境里,确认 pip 是 python3 的 pip,确认 requirements.txt 里没有把包名写错。有一回我们 orders 表有中文,MySQL 连接串少了一个 charset=utf8mb4 参数,导致商品名全部乱码,后来在 config 里加了一行才解决。这种问题不像报错那么醒目,全靠翻日志。

如果你是刚装 Python,记得官网下载安装包时勾选 Add to PATH,否则后面在命令行敲 python 会提示不是内部命令。Flask 的 debug 模式只能用在开发环境,线上用 debug=True 会暴露调试器,还可能造成代码热重载混乱。我用 gunicorn 启动时根本不会把 debug 带过去,但很多同学本地 run.py 里写的 debug=True,部署时不改就直接跑起来,一旦崩溃页面会跳出一堆敏感信息。

5.6 常见问题速查表

把上面这些坑和排查建议整理成一张速查表,方便你直接对号入座:

问题可能原因解决方案
uni-app 控制台没有 console 输出HBuilderX 日志级别过滤或发布版开发环境用 console.log,封装 log 方法
自定义导航栏错位没有获取状态栏高度用 getMenuButtonBoundingClientRect 计算
列表加载重复请求没有加 loading 锁请求前置 if (loading/finished) return
tabbar 被键盘顶起adjust-position 默认开启输入框 adjust-position=false,或用弹出层
微信登录 code 无效code 重复使用登录请求只发一次,后端缓存 code
支付回调重复处理未做幂等回调先查订单状态,已支付直接返回 success
视频旋转安卓 orientation 异常换图片,或服务端 ffmpeg 转码
H5 打开小程序失败未绑定同一开放平台或非微信浏览器检查开放平台绑定和 JS-SDK
Flask 部署后中文乱码数据库连接没有 charset=utf8mb4配置 SQLAlchemy 连接串
小程序真机请求失败域名未加入合法域名或未配置 https后台配置 request 合法域名

这些坑不是一次踩完的,前前后后花了大概一周时间在联调和真机测试上。如果再做一遍,我会更早做真机预发环境,而不是等到开发完才集中测。有一点可以肯定:校园跑腿类项目的核心不是炫技,而是把订单、支付、配送这几个流程搞得足够扎实,前端 UI 丑一点没关系,订单状态错乱一天,口碑就崩了。

最后再分享一个小技巧:上线后每天定时把订单流水、钱包流水、退款记录三张表导出一次,放在一个独立目录里,方便财务对账,也能在出纠纷时快速还原现场。这个需求听起来不高级,但真到了同学来投诉“没收到退款”的时候,你会庆幸有这堆流水。如果你正准备做类似项目,别纠结于技术栈是不是最前沿,先保证业务流程闭环,再用好 Flask 和 uni-app 这两个趁手工具,跑起来比什么都重要。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/3 9:22:39

GelSight触觉传感器:把接触变成图像,让机器人看见触觉

我第一次把GelSight传感器接到电脑上,看到实时画面时愣了两秒:屏幕里是一块泛着银色光泽的软橡胶,边缘透着红、绿、蓝三色光斑。它不像我熟悉的力传感器,更像一个微型摄像头。但这恰恰是GelSight最反直觉的地方——它把“接触”变…

作者头像 李华
网站建设 2026/10/3 9:22:30

机器学习网络入侵检测:从数据集到99.5%正确率的完整链路

简介:基于机器学习实现的网络入侵检测源码项目,面向高校学生和入门开发者,覆盖数据处理、模型构建到性能评估的完整流程,可直接用于课程设计或期末大作业,正确率可达99.5%。压缩包共十六个文件,总大小为17.…

作者头像 李华
网站建设 2026/10/3 9:21:49

基于Python与PyQt5的二手房价预测系统开发实战

简介:基于Python和PyQt5实现的二手房价分析与预测系统,是一份面向高校学生的毕业设计、期末大作业与课程设计高分项目源码。系统覆盖房价数据清洗、统计分析与预测建模等完整流程,界面基于PyQt5构建,操作直观。资源共17个文件&…

作者头像 李华
网站建设 2026/10/3 9:20:52

Matlab机器学习工业级实战:从算法原理到可部署工作流

简介:本资源是一套面向机器学习初学者与Matlab实践者的算法实现合集,聚焦线性回归、逻辑回归、决策树、随机森林、SVM、KNN、神经网络及K-means等常用算法的原理验证与工程落地。压缩包共35个文件,含26个核心MATLAB脚本(.m&#x…

作者头像 李华
网站建设 2026/10/3 9:20:32

JSP毕业设计实战:书香羲园笔记展评系统从0到1

毕业设计答辩季又到了,后台不少学弟学妹来问我“书香羲园”最美笔记展评管理系统该怎么做。说实话,这个题目在JSP类毕业设计里属于“看着简单,做起来绕”的类型。表面上是展示和评分的业务,实际涉及用户角色权限、文件上传处理、评…

作者头像 李华
网站建设 2026/10/3 9:19:57

SNAP哨兵2植被反演实战:10m/20m分辨率与缺失波段处理全解析

做植被参数反演的人,十有八九在SNAP里遇到过这种尴尬:导入一景哨兵2的L1C影像,兴冲冲打开Band Math准备算NDRE,结果发现B5、B6、B7这些红边波段是20m分辨率,B8和B4却是10m,直接混着算出来的图怎么看怎么别扭…

作者头像 李华