做了大半年个人项目,终于把一个纠结很久的想法落地了:用 Python 写后端,做一个围绕健康饮食和日常养生的微信小程序。这个项目叫“食养记”,核心就一件事——帮普通用户把“今天吃了什么”变成一句能看懂的话:热量大概多少,蛋白够不够,蔬菜吃了多少种,最近一周是不是总点外卖。
先说说它解决了什么实际问题。很多想养生的人不是不想吃好,而是不知道从哪下手。网上信息太多,今天说低碳水,明天说轻断食,用户一刷反而焦虑。我做的这个小程序不做医疗诊断,也不给极端方案,只做两件事:一是记录饮食,二是基于记录给出温和、可持续的调整建议。比如你今天早餐只吃了一个包子,系统会提示“蛋白质偏少,建议加个鸡蛋或一杯牛奶”,这种程度的建议已经足够落地。整个项目也特别适合两类人参考:一类是准备做毕业设计或者作品集的学生,需要把 Python 和后端技术串起来;另一类是已经会写 Python,但想在微信生态里做点实际产品的开发者。这篇文章我会把项目从想法到代码到上线的完整链路拆开讲清楚。
1. 项目的真实定位:不是给“病号”做菜谱,而是做好饮食记录员
1.1 “养生”这个词在这个项目里意味着什么
做菜谱类小程序的人很多,但多数做一个“菜谱大全”App 出来,用户搜“红烧肉”,出一屏做法,收藏完就不再打开。食养记不想做这种流量型产品,我理解的“养生”更接近日常习惯管理:吃什么、吃多少、什么时候吃、搭配是否均衡。它不需要用户懂得营养学,只需要用户做一件低门槛的事——拍照或者勾选自己吃了什么。
所以整个产品层面前面只有几个界面:首页是健康打卡入口,中间是按餐次记录、营养摄入分析、推荐搭配,后面是个人档案。用户第一次进入会做一个三分钟的饮食偏好问卷,包括口味、忌口、作息、过敏源,以及是否有减重、增肌、控糖这样的大方向目标。这些信息会影响后续的推荐结果。我特别设置了一个边界:不做任何疾病治疗相关的断言。比如高血压患者,系统提示的只会是“建议减少腌制食品”,不会出现“本食谱可代替药物”这种话。这不是为了免责,而是作为一个工具产品必须有的克制。
1.2 为什么不是做网页,而是做微信小程序
我本来也想过做网页版,毕竟 Python 后端出接口,React 写页面对我来说更快。但认真想了想,饮食记录最重要的场景是高频、碎片化、发生在一顿饭前后。用户不会打开电脑记一顿早餐,但他饭后抬手很可能会打开微信。微信小程序比 App 和网页都更有优势:第一,不用安装,扫码或者搜索就能打开,用完即走,没有留存负担;第二,微信生态里有订阅通知能力,可以定时提醒用户“到午饭时间了,记得记录”;第三,对于毕设场景来说,小程序天然自带演示效果,老师手机上就能看到成品,不用配环境。
技术选型上,前端用的原生微信小程序框架,没有上 uni-app。很多人觉得原生小程序写起来麻烦,但我的实际经验是:如果项目不做多端发布,原生框架最稳,组件更新最快,出 bug 也最容易搜到解决方案。后端用了 Python 的 Flask,搭配 SQLite 做本地开发,部署上云以后换成了 MySQL。为什么用 Python?因为做数据处理和分析是 Python 的强项,食材库、营养计算、推荐规则后面全都要跑数据,用 Flask 可以直接通过 pandas 读表分析,不用在多种语言之间来回切换。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 系统设计思路:从“记录”到“建议”的闭环
2.1 先画清楚用户旅程,再开始写代码
我大概花了两张 A4 纸的时间把用户旅程画清楚了。一个用户从打开小程序到形成习惯,要经过五个环节:欢迎问卷、记录饮食、查看报告、获得推荐、形成下一餐的行动指令。在技术上,每个环节对应一组接口和一张数据库表。
首屏不是直接让用户选食物,而是先让他设定个人档案。我把身高、体重、年龄、性别、活动强度这些字段收集齐,因为后面的所有热量估算都以这些为基础。用户填写完档案以后,系统会给出一个每日参考摄入区间,这个区间会显示在记录页的顶部,像一个进度条一样告诉用户今天已经吃了多少。真正的记录功能分两种方式:一是手动搜索食物名称,二是扫条形码。手动搜索用的是我内置的食材库,大概收录了接近两千条常见食材和菜品。扫条形码则通过调用第三方条码库,匹配到具体商品后读取营养成分表数据。
2.2 记录之后的“营养分析报告”是怎么设计的
用户记录完一天三餐以后,当晚定时生成一份当日分析。这个报告不是一份冷冰冰的数据表格,而是三段式:第一段是总览,告诉他今天摄入总热量、蛋白质、脂肪、碳水各占多少;第二段是趋势,用过去七天的曲线表示他的饮食波动;第三段是建议,把数据翻译成人话。比如“本周蔬菜种类偏少,平均每天只有 2 种,建议逐步增加到 4 种以上”这个建议,落在代码里其实是一段 if-else 规则,不是人工智能。
我一直认为,做健康类产品最容易犯的错误就是过度设计。当你做一个推荐系统时,不需要上来就跑神经网络,先用规则引擎把场景覆盖掉,用户量大了再考虑算法。这套项目里,推荐模块用的就是一种最朴素的“反馈式”逻辑:如果今晚报告显示蛋白质不足,明天首页的推荐位就会出现“早餐加一个蛋或一杯豆浆”;如果蔬菜摄入连续两天偏低,推荐位会换成简单易做的蔬菜快手菜。这套逻辑虽然简单,但用户反馈说“它比我想得更周到”,原因是它触发在了具体的场景里,而不是进去就看到一堆固定菜谱。
2.3 数据表设计背后的考量
数据库没有设计得很复杂,核心表一共五张:用户表 users、食物表 foods、用户饮食记录表 diet_logs、用户健康档案表 health_profiles、推荐记录表 recommendations。
食物表是最先建的一张表。每一条食物记录包含字段:食物名称、别名、分类(谷薯类/蔬菜类/水果类/肉蛋类/奶类/豆类/油脂类),以及每100克可食部分的热量、蛋白质、脂肪、碳水化合物、膳食纤维、钠含量。这里有一个很关键的操作细节:食物表里的数量单位我另建了一张表,叫 food_units,用来维护“一份”的概念。很多开发新手会直接把所有食物都按克来记录,但用户根本不知道自己中午吃的青菜是多少克。系统中预置了常见的计量方式,比如“一碗米饭约200克”“一个鸡蛋约50克”“一掌心肉类约100克”,记录时用户只需要选“一碗”“一个”“一份”这样的单位,后端会自动换算成克数。别小看这个换算逻辑,它决定了用户愿不愿意长期坚持记录。如果每次记录前还要先拿秤去称食物,产品就已经失败了。
3. Python 后端开发实录:接口、计算与推荐逻辑
3.1 用 Flask 做接口层,一个模块解决一个业务
后端工程结构没有用复杂的微服务架构,按功能把接口拆成几个蓝图:auth(登录鉴权)、profile(用户档案)、food(食材数据)、record(饮食记录)、report(营养分析)、recommend(推荐)。写代码时最容易乱的部分是营养分析,因为它涉及大量聚合计算,如果你一个接口里既查数据库又做计算又拼返回格式,代码很快就会失控。我采取的做法是分三层:视图层只做参数校验和 JSON 返回,服务层做业务计算,数据访问层只负责查表。
来看一个具体的接口实现。记录一条饮食记录的后端代码大致是这样:
python复制from flask import Blueprint, request, jsonify, g
from service.record_service import RecordService
record_bp = Blueprint('record', __name__)
@record_bp.route('/add', methods=['POST'])
def add_record():
data = request.get_json()
user_id = g.user_id
required = ['food_id', 'meal_type', 'amount_value', 'amount_unit']
if not all(k in data for k in required):
return jsonify({'code': 400, 'msg': '参数缺失'}), 400
record_id = RecordService.add_record(
user_id,
data['food_id'],
data['meal_type'],
data['amount_value'],
data['amount_unit']
)
return jsonify({'code': 0, 'data': {'record_id': record_id}})
这段代码的要点是把 food_id 和用户自报的份量传给服务层,由服务层去查食物表、做单位换算、计算实际摄入营养。接口层保持干净以后,后面写单元测试也好写,改业务逻辑时不会碰到 HTTP 相关的东西。
3.2 核心公式:先算每日推荐摄入量,再算实际摄入量
营养计算不能上来就拍脑袋给一个固定值,我使用的“每日参考摄入”计算方法是比较经典的 BMR(基础代谢率)公式叠加活动系数。我用的是 Mifflin-St Jeor 公式,在库和临床上运用较广:
python复制def calculate_bmr(gender, weight_kg, height_cm, age):
base = 10 * weight_kg + 6.25 * height_cm - 5 * age
if gender == 'male':
return base + 5
return base - 161
算出 BMR 之后,再乘活动系数。久坐为主的办公人群系数给 1.2,每周低强度运动三次左右给 1.375,经常运动人群给 1.55。这样能得出估算的每日总消耗,再根据用户在档案里设定的“保持体重”“轻微减重”“轻微增重”做正负 10% 左右的调整。
这里必须注意一点:这套计算给的是一个参考区间,不是精确处方。在做页面展示时,我写的不是“你每天应该吃 1800 千卡”,而是“你目前的每日摄入参考区间为 1600-1800 千卡”。一方面体重和身高本身有测量误差,公式本身也只是回归模型,另一方面不能把用户锻炼成对热量数字过度焦虑。所以界面上更多用项目符号展示“蛋白质是充足”“脂肪偏高”“膳食纤维需要加强”这样的结论,而不是只给一串数字。
3.3 推荐规则为什么不走机器学习
很多人一看到“推荐”两个字,就想到机器学习模型。我做这个项目的体会是:没有行为数据积累的冷启动阶段,再牛的模型也比不上简单规则。项目刚开始时,用户一共没几条记录,你要拿什么去训练一个个性化模型?所以我选择先用可解释的规则引擎。
规则的逻辑大致是这样:拉取用户最近三天的饮食记录,计算各项营养素的完成率,如果某类营养素的完成率连续两天低于 70%,就在推荐位里找含有该营养素丰富的食材,再根据用户档案的忌口项做过滤,最终从菜谱库中召回 3 道菜。推荐内容不需要太花哨,反而要稳定、可预期。今天推荐了鸡蛋,明天推荐了牛奶,用户会觉得你在意他的蛋白质状况,而不是今天推一个没吃过的异国料理,明天又推一个沙拉,让人摸不着头脑。小程序端拿到推荐数据后,每张卡片上都会写一句简单理由,比如“您最近蛋白质摄入偏少,建议早餐增加奶蛋类食物”,这不仅让推荐看起来有温度,也方便用户理解。
4. 微信小程序前端:界面要轻,交互要顺
4.1 原生小程序的基本配置与页面结构
微信公众平台上注册小程序、拿到 AppID 之后,项目需要在开发者工具中创建。AppID 是关键的东西,本地调试可以用测试号,但如果后边要真机预览、获取用户信息,需要注册真实的小程序号。我最初在测试号里开发,后端也用的局域网地址,跑通功能后改成真实 AppID。
小程序的全局配置文件 app.json 里,我设置了四个页面入口:pages/index/index(今日概览)、pages/record/record(饮食打卡)、pages/report/report(营养分析)、pages/mine/mine(个人中心)。底部 tabBar 放了前三个入口,个人中心通过“我的”到达。需要注意,自定义 tabBar 需要注意与不同手机屏幕底部的安全距离适配,页面底部要预留足够的空间,我在这里踩过坑,后面会专门讲。
每个页面内部,最关键的一个是记录页面。为了让记录动作尽量快速,页面布局用了两个大区块:上半部分是“推荐今天可能想吃的东西”,来自后端推荐接口;下半部分是“按餐次记录”,默认展示早餐、午餐、晚餐、加餐四张卡片。用户点进“午餐”,会进入食物搜索页,搜索框支持模糊匹配。没有复杂的一步步表单,选中食物之后只需要选择吃了多少,所用的单位来自后台预先设定的 food_units。
4.2 页面间的数据传递与状态管理
小程序页面间跳转,可以通过 URL query 来传递少量参数。我一般只在跳转时传 food_id 或 report_date 这样的主键,具体的对象数据在目标页面用 onLoad 事件再调接口获取,避免 URL 过长、数据不同步的问题。比如从首页点进“今日报告”,URL 传一个日期字符串,目标页面根据日期去请求后端接口,这样即使报告数据在缓存中更新了,重新下拉页面也能取到最新内容。
全局状态我用的并非额外的状态管理库,而是小程序自带的 globalData。用户完成档案填写后,需要把身高、体重、每日目标热量这些高频信息缓存在 globalData 中,避免每个页面都重复读取。同时,在需要高频使用的页面上用 storage 做二级缓存,比如食物分类列表,一周更新一次就够了,没必要每次进页面都向服务器请求一次。小程序运行环境的加载速度直接关系到用户是否愿意再点开,减少不必要的网络请求是一本万利的优化。
4.3 “一周趋势图”手写还是用组件
营养分析页面要展示近七天的摄入趋势,最初我想直接引入 echarts 小程序版。后来发现,仅仅展示一个简单的折线趋势,占包体积大、加载慢,实在不划算。我最后用 canvas 手写了一个极简版本——把七个点的数据和一条平滑折线画出来,再加一层淡淡的面积填充。这套实现大约只有一百来行,且绘图逻辑很好懂,反而是个加分项。
在 x 轴上标周一到周日,在 y 轴上标热量参考区间的上下限,七天数据生成坐标点,连接起来就完成一个最基础的折线图。对于图上低于参考区间的日期,我额外画了一个小圆点,用户点它就能看到一天的简评。小程序 canvas 在部分安卓机型上会出现模糊的适配问题,关键一步是要按设备像素比 dpr 放大画布,当时查了很多资料才定位到是 canvas 尺寸单位的问题。
5. 实打实的踩坑记录:从域名到接口签名,再到数据库精度
5.1 微信小程序的合法域名校验问题
这是新手接触小程序时必定遇到的问题。小程序在手机上运行时,网络请求要求使用 HTTPS,而且域名必须要在微信公众平台后台配置为合法请求域名。开发时可以在开发者工具里勾选“不校验合法域名”,但真机预览一关掉调试模式就立刻失效。
我在云服务器上部署 Flask 之后,用 Nginx 做了一层反向代理。SSL 证书用的免费版,配置好以后用 https://api.xxx.com 能正常访问,但小程序请求仍是失败。排查之后才发现,我虽然把域名配置进后台了,但忽略了证书链信息不完整的问题。后来把 CA 证书配置到 Nginx 的 ssl_client_certificate 项里,把证书链补完整后才能正常请求。这个问题非常隐蔽,因为浏览器访问时有时会“通融”,小程序的校验机制却特别严格,证书链少一截都不行。
5.2 记录数据保存失败,居然是单位换算在“捣乱”
项目测试过程中出现过一种怪现象:用户记录“西兰花 一份”,保存成功;记录“米饭 一碗”,也成功;但只要记录“豆腐 半份”这种带小数的份量,前端就报错。后端完整日志打出来,发现是数据格式校验时把 0.5 判断成了无效数字。问题出在小程序端传给后端的 amount_value 我定义成 Decimal 类型,但有一条默认 strict 校验规则只允许整数。这提醒了我在做接口设计时,所有数字类型都要明确是 integer 还是 number,不能含糊。后来把这个字段统一改成 number 类型,并加了范围校验毕竟份量不能是负数。
5.3 自定义底部导航栏与 iPhone 底部安全区
小程序页面底部如果用默认 tabBar,样式比较死板,不好定制。我把主入口的 tabBar 改成了自定义组件,但是自定义意味着要自己算好“安全区”。iPhone X 及之后机型底部有 Home Indicator,如果不加适配,tabBar 的最下沿会被手势条遮住,看起来像被“吃”了一截。解决方法是读取 wx.getWindowInfo() 得到 safeArea 信息,给组件底部预留出对应的高度,同时页面内容也必须做相同的适配,否则记录列表最后一项会被 tabBar 挡住点不到。
5.4 “paused in debugger”带来的惊吓
开发过程中还有一次比较挫败的体验:小程序运行到一半突然卡住,控制台显示 paused in debugger。因为按官网指引在正式环境重新打开、关闭调试后,问题依旧存在。后来分析发现,这通常是代码中有 debugger 语句,或者是开发者工具开启的 Sources 断点状态没有释放。排查自己的代码后确认,不是业务代码写的问题,而是工具状态导致的,把开发者工具彻底重启、清掉所有断点之后恢复正常。遇到这种情况先别急,多是被调试状态干扰。
5.5 数据库精度问题:明明 100 千卡为什么显示 99.999
食物营养成分计算出来以后,前端展示会偶尔出现 99.999 这样的诡异小数。原因在于 Python 浮点数的二进制表示精度问题。很多做开发的人知道 0.1 + 0.2 不等于 0.3,但在业务展示中却容易忽略。解决方式是所有和“克”“千卡”相关的数值,在数据库层存 Decimal(10, 2),在 Python 计算时也统一用 Decimal 不要用 float,最终在展示的接口里做一次 round(值, 1)。这样不会出现问题,营养数据一错就是小数点后看不到的“蚊子肉”,但用户感知得到的却是“不专业”。
6. 上线后的几个细节迭代,让“养生”真正持久起来
6.1 从“记录工具”转向“习惯助手”
第一个月的数据显示,超过一半的用户只记录了一两天就不再使用。于是我在界面和逻辑上做了一次大迭代:把打卡路径从“先记录再得结论”改成“提醒在前,记录在后”。每天设置早饭、午饭、晚饭三个时间段的提醒,通过微信订阅消息发给用户。用户点击提醒卡片进入小程序后,默认定位到对应的餐次,直接把时间成本降到了最低。
同时增加了“连续记录天数”这样的隐形激励机制。我刻意没做分享排名那种社交压力机制,而是给用户自己展示:你本周比上周多记录了三次,饮食规律性提升中。要相信正向反馈比排名比拼更持久。
6.2 内容库的持续运营:不是一次建完就结束
食物库本身是个需要持续维护的内容型资产,我采用“管理后台”的方式,每天去审核用户上报的新食材。用户在小程序里搜不到某个食物时,可以直接提交食物名称和包装上的营养成分表照片,后台审核通过以后进入公共库。审核界面也是我自己用 Flask 和一个小网页拼凑的,每次桌上有一包零食,扫一下条形码发现库里没有,我就会顺手录入一条标准数据。两个月下来,库里关于外地菜、预包装零食的数据明显充实。
在“养生”这条内容线上,我会有意识地把内容聚焦到“应季”“本味”“烹饪方式温和”这些方向。比如春季会推春笋、荠菜,夏季推绿豆汤,但这些不是硬科普,而是结合食材库里的应季蔬菜做的一个“时令推荐”小模块,提醒用户可以优先选择当季食物。我认为这也有助于加强项目和“养生”的内在关联。
6.3 迭代时要盯好的三个数据指标
产品改了这么多轮以后,我最关注的指标也稳定下来:一是次日留存,代表用户第一回用完后还愿不愿意再来;二是成功记录率,就是用户点击“记录”按钮后,有多少比例真正完成了记录操作,这个指标偏低代表路径太长,可能食物搜索不好用;三是建议点击率,即报告或推荐位提示有多少次被点开,这个指标代表推荐对用户是否有价值。如果用户根本不点推荐位,我再做一百条规则也白搭。
随着这些指标的积累,后期我才能开始考虑更复杂一点的内容过滤策略。比如根据用户的历史搜索意图,推荐位可以更聪明地结合“时令”,而不是把库中所有高蛋白食物天天排着展示。
7. 从项目本身拔出来:这套打法能迁移到哪些场景
做健康饮食养生的小程序,表面上是做一个“食物记录器”,实际上它练习的能力跨越了好几个层次:微信生态业务开发、Python 后端接口设计、基础数据结构规划、外部 API 对接、还有移动端的适配与性能优化。对于学生来说,把这套代码和业务逻辑梳理清楚,放进简历或作品集里很有竞争力;对于已经工作的开发者,这套简易的分层架构和规则推荐也能迁移到其他类似项目,比如运动打卡、睡眠记录、习惯养成类小程序。
迁移到“运动打卡”场景,核心的每日消耗计算逻辑已经内置;迁移到“睡眠记录”场景,用户档案模块和订阅提醒模块可以直接复用;即便完全不做饮食,做任何“根据用户记录提供反馈型内容”的产品,都可以参考这套“记录—聚合—生成建议”的数据流。本项目核心价值是用最常见的 Web 技术栈,做了原本看起来“应该很复杂”的个性化推荐需求,而落地后会发现,大部分实用功能不需要堆模型,清晰的需求理解就能撑起一个不错的产品。
最后分享一个写这套代码时最深刻的体会:不要在产品早期就期待“用算法拽着用户走”,现阶段更应该做的是帮用户简单记录、及时反馈,让数据慢慢积累起来。用户是因为感受到你在帮他减少负担,才愿意一直用下去,单纯技术新或数据多并不能让产品走远。等到数据量级真的上来了,再谈模型优化也不迟。如果你正准备做类似的小程序项目,可以先从最小闭环搭起——一个能记录、能看结论、能收到提醒的小工具,就已经比大多数停留在 PPT 上的想法强很多了。
