拓冰建站拓冰建站
首页 / 资讯中心 / 正文

微信小程序全栈开发实战:从Flask权重随机算法到WXML渲染与部署

简介面向微信小程序开发与Python后端入门人群这份源码包提供了一套完整的“今天吃什么”每日菜品推荐解决方案。项目包含微信小程序客户端、Python服务端以及配套说明文档实现随机推荐、口味匹配等功能可帮助开发者快速上手小程序与后端API联调。压缩包共收录1654个文件、大小约14.99MB以js、png、html、css、py、wxml、wxss等类型为主其中wxml/wxss负责页面结构与样式js处理交互逻辑py脚本完成后台推荐服务png/jpg等图片资源则用于菜品展示另有doc/docx说明与sql数据库脚本便于部署与二次开发。资源目前已吸引1850人学习目录结构清晰便于按需查阅。配套的操作说明与配置文档能有效降低部署门槛读者可结合源码理解微信小程序如何调用Python服务掌握从菜品数据处理到随机推荐接口的完整链路。1. 一个选择困难症患者的自我救赎“今天吃什么”可能是继“你吃了吗”之后最让人焦虑的问题。微信小程序“今天吃什么菜”的源码包里藏着一套完整的解法Python 后端用权重随机算法给答案小程序端用 WXML/WXSS 渲染菜品卡片再用 AdminLTE 搭一个管理后台做数据维护。整个项目跨了小程序端、Web 管理后台和 Python API 三层很适合想完整走一遍全栈小程序开发的从业者研究也适合把“菜品推荐”模式迁移到设备报修、库房盘点这类偏决策型的内部工具。接下来按数据流方向把三层拆开每一层给出可复现的代码和参数说明。2. 工程目录里的三层架构与数据流设计打开源码包根目录最先注意到一份changelog和一堆样式文件bootstrap.css、bootstrap.min.css、AdminLTE.css、AdminLTE.min.css、animate.css、style.css、samples.css。没仔细看的人会以为这是个纯前端项目实际上这些样式全是为管理后台服务的微信小程序端根本不引用它们。2.1 AdminLTE 在这个项目里的真实身份AdminLTE 是基于 Bootstrap 的后台管理模板在这里的角色是菜品数据的运营界面不面向 C 端用户。AdminLTE.css提供侧边栏、导航、盒子的框架布局bootstrap.css负责栅格系统和基础组件animate.css承担菜品切换时的过渡动效。samples.css和style.css则是对模板的定制覆盖把默认的冷色主题改成餐饮产品常见的暖色调。如果你打算二次开发改后台样式时优先覆盖style.css不要直接动 AdminLTE 源文件否则后续升级模板时你的改动会被覆盖掉。2.2 数据流从小程序按下按钮到回显结果微信小程序 (WXML/WXSS/JavaScript) → wx.request 发起 HTTPS 请求 → Python Flask API 接收参数 → 查询 SQLite / JSON 菜品库 → 权重随机算法选出一道菜 → 返回 JSON 给小程序端 → setData 渲染到视图层这条链路里有个容易被忽略的细节小程序端不直接连数据库所有数据操作都走后端接口。好处是菜品的增删改全部收敛到管理后台小程序端只负责展示和提交用户行为。后续想加“不喜欢这道菜”的反馈功能只需在后端加一张反馈表小程序端不用发版即可生效。这个设计模式叫前后端分离在微信小程序里不仅是规范更是硬性约束——小程序无法直连 MySQL只能通过 HTTPS 访问合法域名下的接口。2.3 为什么选 Flask 而不是 Django源码包里的WhatToEat核心脚本从命名上就能看出是轻量实现。Flask 比 Django 更贴合这个体量——不需要 admin 站点、ORM、迁移框架这些重武器一个app.py加两个路由就能跑通。实际开发中我会用 Flask-RESTful 扩展规范接口路由但核心逻辑原生 Flask 已足够。选型判断标准很简单如果你只需要增删改查加一个自定义算法Flask 的开发效率远高于 Django如果你需要用户体系、权限分级、内容管理这些现成模块Django 的开箱即用优势才值得那套学习成本。# WhatToEat/app.py from flask import Flask, jsonify, request from random import choice app Flask(__name__) # 菜品库实际项目会从数据库读取 DISHES [ {id: 1, name: 番茄炒蛋, tag: 家常, weight: 80}, {id: 2, name: 水煮鱼, tag: 川菜, weight: 50}, {id: 3, name: 清炒时蔬, tag: 清淡, weight: 70}, ] app.route(/api/recommend, methods[GET]) def recommend(): mode request.args.get(mode, random) # random / weight if mode weight: # 按权重随机权重越高的菜被选中概率越大 pool [] for dish in DISHES: pool.extend([dish] * dish[weight]) result choice(pool) else: result choice(DISHES) return jsonify({code: 0, data: result}) if __name__ __main__: app.run(host0.0.0.0, port5000, debugFalse)这段代码的关键参数有三个mode控制推荐模式weight字段决定菜品被选中的概率权重host0.0.0.0表示监听所有网卡地址让局域网内的开发者工具能够访问到服务。把每道菜的weight轮流追加到池子里再随机取实现的是“带权重的随机”比直接choice(DISHES)更符合真实场景——晚餐时段用户大概率想吃重口味午餐则偏向清淡。如果要接数据库把DISHES列表换成 SQLAlchemy 查询结果即可算法部分完全不用改动。注意debugTrue仅限本地调试部署到服务器必须改成False。调试模式下 Werkzeug 会提供交互式调试台远程攻击者可能借此直接操作服务器。3. 服务端推荐逻辑从简单随机到带条件的权重筛选这一层是整个小程序的“大脑”。用户在小程序端点一下按钮背后是权重随机算法加业务规则的综合判断。源码里的WhatToEat脚本把推荐逻辑收敛在几个函数里拆开看核心逻辑不算复杂重点在于边界条件的处理。3.1 接口协议与参数约定小程序端与后端走的是标准 JSON over HTTP。接口路径/api/recommend接受GET请求参数设计上我建议保持这个风格参数名类型必填说明modestring否random纯随机weight按权重随机excludestring否排除的菜品 ID逗号分隔如“1,3”tagstring否按标签过滤如“川菜”“家常”这三个参数的设计逻辑很直接exclude解决“今天不想吃昨天那道菜”的诉求小程序端把用户昨天选中的菜品 ID 存到wx.setStorageSync今天请求时带上即可。tag参数给后续“按口味筛选”功能预留扩展位前端暂未接入但后端先实现可以避免二次返工。注意exclude在传输层是字符串后端接收时要拆分成整数列表这个类型转换是联调阶段最容易出 bug 的地方。3.2 带排除功能的推荐函数# WhatToEat/recommender.py import random from typing import List, Dict, Optional class DishRecommender: def __init__(self, dishes: List[Dict]): # 浅拷贝菜品数据避免修改外部原数据 self._dishes [dish.copy() for dish in dishes] def recommend(self, exclude: Optional[List[int]] None, tag: Optional[str] None) - Dict: candidates self._dishes # 先按标签过滤 if tag: candidates [d for d in candidates if d.get(tag) tag] # 再排除用户明确不吃的菜 if exclude: candidates [d for d in candidates if d[id] not in exclude] if not candidates: # 兜底候选集为空时返回提示文案而非异常 return {id: 0, name: 今天休息一下点外卖吧, tag: system} # 累计权重区间随机法 total_weight sum(d.get(weight, 1) for d in candidates) r random.uniform(0, total_weight) upto 0 for dish in candidates: upto dish.get(weight, 1) if upto r: return dish return candidates[-1]这段实现的精妙之处在“累计权重区间法”。相比把每道菜按权重重复塞进一个大列表再随机取区间法只做一次遍历既不产生巨额中间列表也能精确控制概率分布。random.uniform(0, total_weight)取一个浮点数看它落在哪个菜品的累计区间就选哪道菜。当菜品库扩展到几千道、权重差异很大时列表展开法的内存占用可能是区间法的几十倍。边界条件是candidates为空——用户把所有菜都排除的场景此时抛异常不如返回一条提示文案小程序端能直接渲染用户也不会意识到系统出了状况。3.3 配置文档里隐藏的部署线索源码包里的两份文档值得细读。今天吃什么程序使用说明.doc是从用户视角写的操作手册讲怎么选菜、怎么换一换程序配置说明.docx是给开发者看的部署指引包含环境依赖和启动步骤。结合两份文档与目录结构常规配置流程是先在 Python 环境执行pip install flask安装依赖启动WhatToEat服务确认接口返回 JSON再到微信开发者工具里导入小程序目录把app.js里的baseUrl改成后端服务器的域名或局域网 IP。// miniprogram/config.js module.exports { // 本地联调用 127.0.0.1真机预览必须改成已备案的 HTTPS 域名 baseUrl: https://your.domain.com, timeout: 5000 }把接口地址单独抽到config.js文件里而不是散落在各个页面的wx.request调用中是我在这个项目中坚持的实践。这样后端地址变更时只改一个文件不用全局搜索替换。小程序没有环境变量机制常规做法就是用不同文件区分开发/测试/生产环境config.dev.js、config.prod.js之间切换打包时手动替换引用即可。4. 小程序端 WXML 渲染、交互逻辑与接口联调小程序端核心页面就两个首页随机推荐展示和菜品列表面。源码里的samples.css虽然没有被小程序端引用但它的动效思路完全可以迁移到小程序的自定义动画里省掉重新设计的时间。4.1 首页的 WXML 结构与数据绑定!-- miniprogram/pages/index/index.wxml -- view classcontainer view classdish-card bindtaponCardTap image src{{dish.image}} modeaspectFill classdish-image/ view classdish-info text classdish-name{{dish.name}}/text text classdish-tag{{dish.tag}}/text /view /view button classaction-btn bindtapfetchRecommend loading{{loading}} {{loading ? 思考中... : 换一道}} /button /view页面的交互核心是fetchRecommend事件处理函数。用户每点一次“换一道”小程序就往后端发一次推荐请求拿到结果后更新dish数据对象视图层自动重新渲染。loading状态防连续点击造成重复请求弱网环境下尤其重要——没有这个保护用户连续点五次就会产生五个并发请求后端可能返回同一道菜体验很不好。4.2 请求封装与本地缓存策略// miniprogram/pages/index/index.js const config require(../config.js) Page({ data: { dish: { name: 点下面按钮开始, tag: }, loading: false, yesterdayId: 0 }, onLoad() { // 从本地缓存读取昨天推荐的菜品 ID const yesterdayId wx.getStorageSync(yesterdayDishId) this.setData({ yesterdayId }) }, fetchRecommend() { if (this.data.loading) return // 防重复点击 this.setData({ loading: true }) wx.request({ url: ${config.baseUrl}/api/recommend, method: GET, data: { mode: weight, exclude: this.data.yesterdayId ? [this.data.yesterdayId] : [] }, success: (res) { if (res.data.code 0) { this.setData({ dish: res.data.data }) // 把新推荐的菜 ID 存为“已吃”下次打开自动排除 wx.setStorageSync(yesterdayDishId, res.data.data.id) } }, fail: (err) { wx.showToast({ title: 网络开小差了, icon: none }) }, complete: () { this.setData({ loading: false }) } }) } })“排除昨天吃过的菜”这个需求用本地缓存就实现了没有占用任何后端存储。exclude参数只传一个 ID但后端接口定义了List[int]所以这里用数组包裹。有个容易踩的坑wx.request的success回调里不能用普通function声明否则this指向的不是 Page 实例setData会报错。上面用的是箭头函数this继承外层作用域。如果你在老项目里看到var that this的写法那是基础库兼容低版本的做法新项目用箭头函数更干净。4.3 菜品切换的动画过渡方案原生小程序没有 Vue 的transition标签动画要依赖样式类的切换。在 WXSS 里定义两个状态类在fetchRecommend成功回调里先把视图清空再通过wx.nextTick填充新数据配合动画类触发淡入效果。.dish-card { transition: opacity 0.3s ease-in-out; } .dish-card.fade-out { opacity: 0; } .dish-card.fade-in { opacity: 1; }// 在 fetchRecommend 的 success 回调里 this.setData({ dish.fadeOut: true }) // 先执行淡出 setTimeout(() { this.setData({ dish: res.data.data, dish.fadeOut: false }) }, 300)需要注意transition属性在原生小程序 WXSS 里只对opacity和transform生效height、width的动画会触发重排导致掉帧。如果切换菜品时要换图不要在图片容器上做height动画改用scale或者直接淡入淡出。另外wx.nextTick是异步的要保证先清空旧的视图层再填充新数据否则可能出现新旧菜品闪烁的视觉问题。5. 部署联调中的域名校验、真机踩坑与接口验证技巧开发小程序最让新人头痛的不是写代码而是“开发者工具里跑得好好的手机上一片白”。微信对小程序网络请求的管控很严格这一章把实际部署时遇过的坑按层级排一遍。5.1 request 合法域名与 HTTPS 证书校验微信要求小程序端发起的网络请求必须是 HTTPS 且域名已备案。登录微信公众平台在“开发管理 → 开发设置 → 服务器域名”里把后端接口域名填进request合法域名列表。这里有个隐蔽限制域名不能带端口号https://your.domain.com:8443这种带端口的形式会被微信直接拦截。生产环境建议用 Nginx 反代把 443 端口的请求转发给 Flask 的 5000 端口。注意开发阶段勾选开发者工具右上角“详情 → 本地设置 → 不校验合法域名”可以跳过校验但真机预览时该选项不生效必须走正规域名。5.2 用 Nginx 反代 Flask 服务的标准配置不用买服务器也能用真机调试内网穿透工具可以把本地 5000 端口暴露成 HTTPS 域名。但免费版的稳定性很难保证排查问题时分不清是穿透服务的问题还是后端的问题。更稳妥的做法是买一台轻量云服务器按下面配置部署# /etc/nginx/sites-available/whattoeat server { listen 443 ssl; server_name your.domain.com; ssl_certificate /etc/nginx/ssl/fullchain.pem; ssl_certificate_key /etc/nginx/ssl/privkey.pem; location /api/ { proxy_pass http://127.0.0.1:5000; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; # 小程序端不需要 CORS但运营后台要 add_header Access-Control-Allow-Origin *; add_header Access-Control-Allow-Methods GET, POST, OPTIONS; } }这段配置把/api/路径的请求转发给 Flask。add_header指令主要给 AdminLTE 管理后台用的wx.request本身不受浏览器同源策略限制但后台页面通过浏览器访问管理界面时必须有 CORS 头不然 jQuery 的 AJAX 请求会被浏览器拦截。证书验证的排错思路真机提示“证书无效”时先用手机浏览器访问https://your.domain.com/api/recommend浏览器不报错但小程序报错通常是证书链不完整把中间证书合进fullchain.pem就能解决。5.3 一套可复现的接口验证流程部署完成后用curl按三步验证接口链路。先验证 Flask 进程再验证 Nginx 转发最后验证业务参数。# 第一步验证 Flask 进程是否监听 5000 端口 ss -tlnp | grep 5000 # 第二步通过 Nginx 访问推荐接口模拟小程序请求 curl -s https://your.domain.com/api/recommend?modeweight | python -m json.tool # 第三步验证 exclude 排除参数是否生效 curl -s https://your.domain.com/api/recommend?exclude1,2 | python -m json.tool第二步返回的 JSON 中code为0才说明链路通了继续看data.name是不是一道菜名。第三步用exclude1,2验证返回结果里id不能是 1 或 2如果出现了排除的 ID回到recommender.py检查exclude参数的类型转换——传进来的是字符串1,2必须先用split(,)再逐项int()否则列表里的元素是字符串dish[id] not in exclude永远成立排除逻辑完全失效。5.4 一个实用的验证技巧用 Storage 手动触发边界分支最后一个实用技巧是在开发者工具 Console 里手动写入 Storage 数据直接触发不同逻辑分支。输入wx.setStorageSync(yesterdayDishId, 1)后重启页面点“换一道”观察 Network 面板请求参数里应该带exclude[1]。再把值改成999后端候选集非空只是排除 ID 不存在此时正常返回随机菜接着把后端DISHES临时清空重新请求后端应该返回兜底文案“今天休息一下点外卖吧”页面正常渲染而不报错。这个方法比反复改代码验证边界条件要快得多也方便排查线上问题时快速复现用户状态。至此从工程目录里的 AdminLTE 后台、Python Flask 权重随机算法到小程序端的 WXML 渲染与请求封装再到 Nginx 部署和边界验证这套“今天吃什么菜”小程序的全链路就算彻底跑通了。本文还有配套的精品资源点击获取
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门