ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

微信小程序+Django+Vue+MySQL全栈实战:家庭大厨菜谱应用开发详解

微信小程序+Django+Vue+MySQL全栈实战:家庭大厨菜谱应用开发详解 简介一份面向毕业设计场景的家庭大厨微信小程序项目资源基于微信小程序、Django、Vue与MySQL实现前后端分离适合计算机相关专业学生作为毕设参考或练手项目。包内完整提供源码、数据库脚本、毕业论文与视频教程覆盖管理员后台、店铺与用户注册登录、菜品信息与订单管理等核心功能能直观演示前后端交互与后台数据管理流程。整套资源共718个文件其中包含161个svg、129个vue、125个png、82个js、55个py等基本对应图标素材、前端页面、交互逻辑与Django后端模块另有mp4演示视频和sql脚本辅助部署压缩包总大小44.78MB目录结构较清晰。目前已有194人浏览学习适合希望快速上手同类小程序开发或需要论文与代码配套参考的学生按文档部署后即可运行并理解各层实现。1. 家庭大厨小程序为什么值得用微信小程序DjangoVueMySQL来写做毕设选“家庭大厨”这个方向最怕的不是不会写代码而是写完像个“作业”不像“系统”。微信小程序做用户端、Vue 做后台管理、Django 出接口、MySQL 存数据这套前后端分离的链路恰好把一个菜谱类应用拆成四个可独立讲清的部分数据库建模、REST API、C 端体验、B 端管理。它能解决“今天吃什么”的真实痛点——家庭成员在小程序里按分类浏览菜谱、收藏、上传自己的拿手菜管理员在 Vue 后台维护菜谱和分类Django 统一对两个端提供数据服务。这个选题适合计算机、软件工程相关专业需要展示全栈能力又不愿意把精力耗在复杂中间件上的毕业设计类型也是“前后端分离项目实战”方向里最容易被评委接受的一套组合。2. 先把架构定下来Django、Vue、微信小程序各自只干一件事2.1 前后端分离分的是什么看一次请求怎么走家庭大厨小程序这个选题里前后端分离不是把代码仓库拆开那么简单。一次完整请求是这样走的小程序页面发起wx.request请求到达 Django 的 DRF 视图视图再通过 ORM 查询 MySQL序列化器把模型转成 JSON 返回小程序拿到数据后setData渲染Vue 管理后台走的也是同一条链路只是请求发起方从wx.request换成了 axios。为什么前端不能直连 MySQL我在评审别人的毕设时见过不少反例有人图省事把数据库账号密码写在 Vue 的配置文件里相当于把家门钥匙挂在门口。更关键的是业务逻辑必须放在后端比如“这道菜当前用户是否收藏过”要在 Django 里根据 Header 里的 token 判断用户身份再联合查询收藏表前端直连数据库既不安全也没法表达这种逻辑。所以“前后端分离”真正分的是职责前端只负责 UI 和交互Django 只负责把数据库变成 JSON 接口MySQL 只负责存储三者通过 HTTP 通信。开发时两个前端项目各有开发服务器部署时再合并到同一个反向代理后面。这是整个项目最核心的架构判断。2.2 为什么是 DjangoVue原生小程序而不是一套框架走到底选 Django 的核心理由是 ORM 和自带 Admin。家庭大厨的大多数接口都是单表或两表关联查询Django 写一套 Model 就能同步到 MySQLDjango Admin 甚至不需要额外写页面就能先维护分类数据这对中期调试和论文里的“系统测试”章节非常省力。如果换 FlaskORM 和认证体系要自己拼换成 Node 的 Express序列化和鉴权也要重新搭性价比不高。Vue 的理由更直白中文资料多vue-router、axios、Element Plus 这些生态齐全新手跟步骤走就行不需要太深的前端工程化基础。小程序端我建议用原生而不是 uni-app。原生小程序的 API 最稳定开发者工具集成度高而且论文题目里写着“微信小程序”时评委默认你会原生开发如果写 uni-app答辩时要多解释一层框架的取舍没必要。MySQL 放在最后说SQLite 在 Django 里几乎零配置但毕设用 SQLite 容易被问“为什么不用企业常见的关系型数据库”。MySQL 5.7 或 8.0 都行重点是把字符集统一成 utf8mb4后面建表时我会给出具体配置。这套选型和“微信小程序DjangoVueMySql”这种技术栈组合在答辩时也好讲每个组件都有明确角色没有凑数。2.3 最小项目骨架源码、脚本、文档各自放在哪个目录我一般把项目按下面结构组织这个结构本身可以直接画在论文架构图里family_cook/ ├── database/ │ └── family_cook.sql # 数据库脚本建库建表加初始分类 ├── docs/ # 毕业论文与开题报告 ├── backend/ # Django 后端工程 │ ├── manage.py │ ├── requirements.txt │ └── app/ # 核心业务 app │ ├── models.py │ ├── views.py │ ├── serializers.py │ └── urls.py ├── admin_web/ # Vue 管理后台独立构建 │ ├── package.json │ └── src/ │ ├── router/ # 路由配置 │ ├── api/ # axios 封装 │ └── views/ # 页面 └── miniprogram/ # 微信小程序原生工程 ├── app.js ├── app.json ├── utils/ │ └── request.js # 请求封装 └── pages/ ├── index/ # 首页菜谱列表 └── dish/ # 菜谱详情与发布backend、admin_web、miniprogram 三部分是三个独立工程因为一个是 Python 的 venv 环境一个是 Node 的 npm 依赖一个是微信开发者工具工程构建工具完全不同硬凑一个目录只会互相干扰。数据库脚本和毕业论文放项目根目录是惯例方便评审一次性看到全部交付物视频教程文件比较大建议独立存网盘不要在项目里留一份。环境版本方面Django 用 3.2 或 4.xPython 用 3.8 到 3.10Node 用 16 以上MySQL 用 8.0 的 Windows 安装包。太新的 Python 版本遇到 mysqlclient 这类 C 扩展容易编译报错这是我反复踩过的坑属于典型的“环境比代码难搞”问题。3. Django 后端与 MySQL从建库到 JWT 登录一次打通3.1 MySQL 建库与 Django 配置先用 utf8mb4别等写满数据再改家庭大厨的数据量不大但菜谱标题、用户昵称里难保以后有人插 emoji所以建库第一步就是指定字符集CREATE DATABASE family_cook CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; -- 指定 utf8mb4避免中文和 emoji 乱码 创建完成后在 Django 的 settings.py 里配置连接DATABASES { default: { ENGINE: django.db.backends.mysql, NAME: family_cook, USER: root, PASSWORD: 改成你自己的密码, HOST: 127.0.0.1, PORT: 3306, OPTIONS: {charset: utf8mb4}, } }ENGINE 指定 MySQL 驱动NAME 是刚建的库名HOST 和 PORT 本地开发保持默认。OPTIONS 里的 charset 一定要带只改 CREATE DATABASE 而不改这里Django 连接时仍可能用默认字符集中文乱码问题就是这样来的。Windows 下安装 mysqlclient 经常编译失败常见做法是在项目app/__init__.py里写pymysql.install_as_MySQLdb()配合pip install pymysql。这不是偷懒是为了在毕设环境里能稳定复现省掉折腾 VC 编译器的半天时间。3.2 四张核心表与模型Category、Dish、Favorite、UserProfile四张表能覆盖家庭大厨的核心功能也能在论文数据库设计章节画出清晰的 ER 图表名用途关键字段说明Category菜谱分类id, name如家常菜、汤羹、凉菜Dish菜谱主表id, title, image, ingredients, steps, category, author关联 Category 和 UserProfileFavorite收藏关系id, user, dish联合唯一约束UserProfile用户扩展表id, openid, nickname, phoneopenid 来自微信登录用户表不用 Django 默认 User 的原因小程序用户的 openid 不是用户名密码体系直接塞进 auth_user 会破坏认证模型用 UserProfile 单独一张表再和默认 User 做一对一扩展是更常规的做法。下面是模型代码from django.db import models class Category(models.Model): name models.CharField(分类名, max_length32) class UserProfile(models.Model): openid models.CharField(微信openid, max_length64, uniqueTrue) nickname models.CharField(昵称, max_length32, blankTrue) phone models.CharField(手机号, max_length16, blankTrue) class Dish(models.Model): title models.CharField(菜名, max_length64) image models.CharField(图片URL, max_length255, blankTrue) ingredients models.TextField(食材) steps models.TextField(做法步骤) category models.ForeignKey(Category, on_deletemodels.CASCADE) author models.ForeignKey(UserProfile, on_deletemodels.CASCADE) created_at models.DateTimeField(auto_now_addTrue) class Favorite(models.Model): user models.ForeignKey(UserProfile, on_deletemodels.CASCADE) dish models.ForeignKey(Dish, on_deletemodels.CASCADE) class Meta: constraints [ models.UniqueConstraint(fields[user, dish], nameunique_favorite) ]CharField 的 max_length 一定要写MySQL 下不写会按默认值处理后面改表结构麻烦TextField 用于食材和步骤因为内容长度不可控。Favorite 的 UniqueConstraint 保证同一用户不能重复收藏同一道菜这是接口幂等的基础。执行python manage.py makemigrations和migrate后表就建好了。Django 里删除对象有自己的写法很多没做过 Django 项目的新手会去写原生 DELETE SQL。删除一行是Dish.objects.get(id1).delete()或Favorite.objects.filter(useruser).delete()前者删单条后者批量删掉查询集。注意 delete() 返回的是一个元组第一个元素是受影响行数外键关联的数据会被 CASCADE 一起删掉删 Dish 前要想清楚 Favorite 是否也要一起清。3.3 序列化与视图把 Django 模型变成 JSON 接口DRF 的 ModelSerializer 能把模型字段直接映射成 JSON并在嵌套场景把外键显示成具体名称。比如菜谱列表希望返回 category_name 而不是 category_idfrom rest_framework import serializers from .models import Dish class DishSerializer(serializers.ModelSerializer): category_name serializers.CharField(sourcecategory.name, read_onlyTrue) class Meta: model Dish fields [id, title, image, ingredients, steps, category, category_name, author, created_at]source 参数指定取值路径read_onlyTrue 表示只输出、不参与写入。POST 创建菜谱时前端只需要传 category 的 id序列化器会在写入时自动解析外键。视图层用 ViewSet 加分页from rest_framework import viewsets, pagination class DishViewSet(viewsets.ModelViewSet): queryset Dish.objects.select_related(category, author).all() serializer_class DishSerializer pagination_class pagination.PageNumberPagination # settings.py 中统一配置分页 REST_FRAMEWORK { PAGE_SIZE: 10, PAGE_SIZE_QUERY_PARAM: page_size, MAX_PAGE_SIZE: 50, }select_related 能把外键查询合并成一条 SQL避免列表接口的 N1 次查询数据量几百条时就能感觉到差别。分页参数 page_size 由前端控制默认 10最大 50小程序端“列表加载更多”就是靠这个接口的 next 字段判断是否还有下一页。列表默认排序建议在 Dish 的 Meta 里加ordering [-created_at]保证新发布的菜谱排前面这个细节比前端调 sort 更干净。3.4 登录与 JWT小程序 code 换 openidVue 管理端走账号密码小程序端登录用微信静默登录小程序wx.login()拿到临时 code传给 Django后端拿 code 去微信接口换 openid。这里的关键是 appsecret 绝不能放在小程序代码里否则等于公开密钥。import requests from rest_framework.decorators import api_view from rest_framework.response import Response from .models import UserProfile WX_APPID 你的appid WX_SECRET 你的secret api_view([POST]) def wx_login(request): code request.data.get(code) resp requests.get( https://api.weixin.qq.com/sns/jscode2session, params{ appid: WX_APPID, secret: WX_SECRET, js_code: code, grant_type: authorization_code }, timeout5 ) data resp.json() openid data.get(openid) if not openid: return Response({code: 401, msg: 登录失败code 无效或已过期}) user, created UserProfile.objects.get_or_create(openidopenid) token get_token_for_user(user) return Response({code: 0, token: str(token.access_token), user_id: user.id})code 有效期只有五分钟且一次性不需要缓存直接请求微信服务器。timeout 参数必须写否则微信接口异常时请求会一直挂着小程序端表现为登录按钮无响应。get_or_create 是“存在则取不存在则建”的快捷方式配合 openid 的 unique 约束保证同一微信号只产生一条用户记录。签发的 token 我一般用django-rest-framework-simplejwt和 DRF 的认证机制衔接自然from rest_framework_simplejwt.tokens import RefreshToken def get_token_for_user(user): refresh RefreshToken.for_user(user) return refresh前端后续请求在 Header 带Authorization: Bearer tokenDRF 就能解析出当前用户。Vue 管理后台则用普通用户名密码登录Django 自带用户表在 Vue 登录页调用一个obtain_token视图拿到同样的 JWT两端的认证机制统一论文里也好写。关于“微信小程序登录获取手机号”补充一句现在小程序通过button open-typegetPhoneNumber获取手机号要求企业主体且短信按条计费个人开发者毕设通常不碰。常见做法是小程序端只做 openid 登录再把手机号绑定做成一个可填字段论文里写成“openid 匿名登录 手机号自愿绑定”答辩时解释起来最为顺畅。4. 微信小程序端登录、请求封装、列表分页与顶部导航栏4.1 request.js统一带 token401 自动跳登录小程序页面多如果每个页面都裸调 wx.request改接口地址时要满项目找。我习惯把请求层收敛到一个文件// utils/request.js const BASE_URL http://127.0.0.1:8000/api // 开发环境地址 function request({ url, method GET, data {} }) { return new Promise((resolve, reject) { const token wx.getStorageSync(token) wx.request({ url: ${BASE_URL}${url}, method, data, header: { Authorization: token ? Bearer ${token} : , Content-Type: application/json }, success: (res) { if (res.statusCode 200) { resolve(res.data) } else if (res.statusCode 401) { wx.removeStorageSync(token) wx.navigateTo({ url: /pages/login/index }) reject(res) } else { reject(res) } }, fail: reject }) }) } module.exports { request }BASE_URL 单独定义开发时用本地地址配合开发者工具“不校验合法域名”。封装成 Promise 后页面里可以用 async/await 写逻辑比 success 回调清晰很多。401 统一处理 token 过期跳登录这样每个页面都不需要重复写鉴权判断。4.2 登录页面wx.login 拿 code 再交换 token小程序端不能直接调微信的 jscode2session 接口因为 secret 一旦打进小程序包就泄露了。正确流程是小程序拿 code 交给 Django。原生 wx.login 是回调风格我先包一层 Promise// pages/login/index.js const { request } require(../../utils/request) function getCode() { return new Promise((resolve) { wx.login({ success: (res) resolve(res.code) }) }) } async function login() { const code await getCode() const res await request({ url: /auth/wx_login/, method: POST, data: { code } }) wx.setStorageSync(token, res.token) wx.setStorageSync(userId, res.user_id) }wx.login 得到的 code 每次都不同长期有效的是后端下发的 JWT token。token 存进 Storage 后后续所有接口请求都会由 request.js 自动带上。如果以后要做手机号绑定就在登录成功后弹窗让用户输入手机号调一个/user/bind_phone/接口把 UserProfile 的 phone 字段补上即可。4.3 首页列表加载更多onReachBottom 与分页参数配合列表页最容易翻车的点是“下一页”的实现。简单做法是页面滑到底部时 page然后请求新数据并追加到数组// pages/index/index.js Page({ data: { list: [], page: 1, finished: false }, async loadList(reset false) { if (reset) { this.setData({ page: 1, finished: false }) } if (this.data.finished) return const res await request({ url: /dish/list/?page${this.data.page}page_size10 }) const newList reset ? res.results : this.data.list.concat(res.results) this.setData({ list: newList, page: this.data.page 1, finished: !res.next }) }, onReachBottom() { this.loadList(false) } })两个细节值得强调。第一接口返回的 next 字段是 DRF 分页自带的信息为 null 说明没有更多数据用 finished 标记挡住重复请求否则快速滑到底会触发好几次相同请求。第二新数据用 concat 拼接而不是直接赋值list res.results这是配合上拉加载的固定写法直接赋值会导致列表每次刷新都闪一下。这一节就是“微信小程序页面列表加载更多”的标准答案分页在后端做小程序端只记录当前页码并判断 next。加载更多事件在 onReachBottom 里触发不是点击按钮。4.4 顶部导航栏高度与分类筛选适配不同机型“微信小程序顶部导航栏高度”被反复搜索是因为 iPhone 刘海屏和 Android 状态栏高度不一致写死 64px 或 80px 会在某些机器上出现内容被遮挡。标准做法是用胶囊按钮坐标反推const menuRect wx.getMenuButtonBoundingClientRect() const statusBarHeight wx.getSystemInfoSync().statusBarHeight const navBarHeight (menuRect.top - statusBarHeight) * 2 menuRect.height这段代码放在 app.js 全局计算一次存到 globalData页面通过自定义组件或 getApp() 取用。原理是胶囊按钮永远垂直居中于导航栏用它相对状态栏的偏移去反推导航栏真实高度能同时适配 iPhone 和 Android。新版基础库也可以换wx.getWindowInfo()获取状态栏高度但上面这段写法兼容性最好照抄能跑。分类筛选用 radio 或 checkbox 都可以我的建议是首页顶部横向排一行分类单选切换更接近菜谱 App 的交互。数据来自 Django 的 Category 列表接口切换时把 category 参数拼进请求地址后端按外键过滤。这也是“微信小程序单选框”在真实项目里最常见的用法不单独做表单页而是作为列表筛选条件。5. 避坑毕业设计全栈最容易翻车的 5 个地方5.1 环境类小程序请求不到本地 Django先查域名校验现象开发者工具里 wx.request 报url not in domain list或者真机预览时请求一直转圈。原因微信小程序有合法域名限制。开发者工具默认勾选“校验合法域名”本地地址 127.0.0.1 不在任何合法域名列表里真机上如果没有配置服务器域名请求会被直接拦截。解决开发阶段在“详情-本地设置”里勾选“不校验合法域名、web-view业务域名、TLS 版本以及 HTTPS 证书”。真机演示时让后端跑在局域网 IP并在 Django 的ALLOWED_HOSTS加入该 IPALLOWED_HOSTS [192.168.1.10, 127.0.0.1, localhost]如果换了电脑或换了手机网络IP 会变记得同步更新。上线前再去微信公众平台配置 request 合法域名且必须是备案的 HTTPS 域名否则正式用户无法访问。这条我几乎每次带毕设都会遇到属于环境问题不是代码问题。5.2 数据库类MySQL 8 认证插件导致 Django 连接失败现象启动 Django 后连接数据库报错Authentication plugin caching_sha2_password cannot be loaded。原因MySQL 8.0 默认认证插件是 caching_sha2_password老版本 mysqlclient 驱动不认识。解决不需要降级到 MySQL 5.7改掉当前用户的认证插件即可ALTER USER rootlocalhost IDENTIFIED WITH mysql_native_password BY 你的密码; FLUSH PRIVILEGES;重启 MySQL 服务Django 再 migrate 就能连上。如果你是用 docker 拉 MySQL 镜像失败多半是 3306 端口被本机 MySQL 占用或容器卷权限问题这种环境坑不值得深究直接把本机 MySQL 停掉再起容器或者卸载干净用安装包重装五分钟搞定。毕设时间有限不要在环境搭建上熬两个通宵这是血泪经验。5.3 跨域类Vue 管理后台请求 Django 被 CORS 拦现象Vue 跑在 localhost:5173Django 跑在 localhost:8000浏览器控制台全是Access-Control-Allow-Origin报错。原因浏览器同源策略前端端口和后端端口不同属于跨域请求。解决用 django-cors-headers 统一处理不需要在 Vue 里配代理。settings.py 加三处INSTALLED_APPS [ # ... corsheaders, ] MIDDLEWARE [ # ... corsheaders.middleware.CorsMiddleware, ] CORS_ALLOW_ALL_ORIGINS TrueCORS_ALLOW_ALL_ORIGINS True只适合开发环境部署到正式环境要改成CORS_ALLOWED_ORIGINS [https://admin.example.com]白名单。特别注意 CorsMiddleware 的位置要在 CommonMiddleware 前面否则请求顺序不对时仍然报跨域这个细节容易忽略。排查接口问题时我会打开抓包工具看请求 Header 是否带了 Authorization、返回体是什么状态码比在代码里打 log 快得多。5.4 小程序端并发一次请求太多导致列表渲染失败现象首页图片多的菜谱卡片同时加载时部分请求直接失败控制台提示并发数超限。原因微信小程序对同域名的并发请求数有限制页面一次性请求十几张图片时就会触发。解决分两层处理。第一后端接口用分页首页只返回 10 条第二小程序端image标签加lazy-load属性可视区外的图片不加载。分页参数在第四章已经写过page_size10onReachBottom 再拉下一页配合 finished 标记。如果列表必须一次展示很多项图片还要压缩或走 CDN但毕设场景里分页已经足够。真机预览时网络更慢图片加载问题更容易暴露所以演示前一定要先用真机走一遍不要只在开发者工具里看效果。5.5 数据脚本与清库不一致导入脚本后主键和模型对不上现象按数据库脚本导入后运行项目报错“table dish_dish has no column named xxx”或者清空数据后自增 id 不从 1 开始。原因脚本里的 CREATE TABLE 表结构可能和本地 Django models 版本不一致比如 models 加了字段而脚本没更新。另一个常见原因是 ORM 的 delete() 执行的是 DELETE 语句只删行不重置自增计数器。解决开发阶段不要盲目依赖脚本。先对比脚本里表字段和 models.py不一致就以 models 为准用 migrate 生成表脚本只用来导入初始分类数据。想彻底清空并重置自增用 TRUNCATETRUNCATE TABLE dish_favorite; -- 先清子表再清主表 TRUNCATE TABLE dish_dish;TRUNCATE 不能直接用于被外键引用的父表必须先清 Favorite 再清 Dish如果仍报错用SET FOREIGN_KEY_CHECKS0临时关闭外键检查清完再打开。牢记“django执行查询-删除对象”时先想清楚子表与外键否则联调阶段数据不一致会搞得焦头烂额。6. Vue 管理后台与部署最后一公里怎么跑管理端我按 Vue 3 Vite Element Plus 搭这也是目前前后端分离项目实战里最常见的组合。npm create vitelatest admin_web -- --template vue建项目接着安装element-plus axios vue-router。页面不用多三张够演示菜谱管理、分类管理、用户列表。路由用 hash 模式const router createRouter({ history: createWebHashHistory(), routes: [ { path: /, component: DishList }, { path: /category, component: CategoryManage } ] })hash 模式比 history 少踩一个大坑打包后部署到 Nginx刷新页面不会 404。axios 拦截器里从 localStorage 取 token 拼到 Authorization逻辑和小程序 request.js 一致等于同一套认证模型在两个前端上复用。增删改查接口直接调用第 3 章的 ViewSet不需要为后台另写一套 API这正是把业务逻辑全放在 Django 里的收益。后端部署到云服务器后Nginx 做一层反向代理把管理端静态文件和 API 请求合并到同一个入口server { listen 80; server_name your-domain.com; root /var/www/admin_web/dist; index index.html; location /api/ { proxy_pass http://127.0.0.1:8000/api/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } }location /api/ 把前端请求转发给 Django小程序端也请求这个域名三端就统一了。注意 proxy_pass 末尾的/api/要写对否则路径拼接出错接口直接 404。验证我习惯按顺序走五步先在开发者工具里跑完整流程再用 Vue 后台做一次菜谱新增和修改然后用抓包工具确认关键接口返回 200、401 逻辑正确接着关掉开发者工具的“不校验合法域名”用真机预览最后把数据目录清空重新导脚本验证可复现。答辩问“为什么不用云开发”时回答方向是云开发省了后端但掩盖了数据库设计与接口实现毕设要展示全栈能力Django 加 MySQL 这套才扛得住追问。这个验证习惯帮我少熬了很多夜也让我每次交付前都更有底。希望帮到你。本文还有配套的精品资源点击获取
返回列表