简介:本资源是一套完整的毕业设计实战项目,面向计算机专业本科生及Web全栈初学者,聚焦家庭餐饮服务场景,提供从微信小程序前端到Django+Vue前后端分离架构的全流程实现方案。资源包含718个文件,涵盖129个Vue组件、55个Python后端逻辑文件、50个JPG/PNG素材图、82个JS交互脚本、2个SQL数据库建表与初始化脚本,以及3个MP4视频教程,压缩包大小为44.78MB。已有194人学习下载,适合用于课程设计参考、毕设开题与答辩支撑。用户可直接获取可运行源码、结构清晰的MySQL数据库脚本、完整毕业论文(含系统分析、可行性论证、功能模块设计与ER图)、配套安装/运行/构建批处理脚本(.bat),以及后台管理界面与小程序双端协同的操作逻辑说明,显著降低环境配置与调试门槛。
1. 项目概述与核心价值
最近几年,带过的学生和身边不少朋友都在问,有没有一个能完整串联起微信小程序、Django、Vue和MySQL这些主流技术的实战项目,最好还是前后端分离的架构,能直接拿来学习甚至作为毕业设计。说实话,这种“全家桶”式的项目,网上要么是只讲前端,要么后端一笔带过,能把前后端分离、数据库设计、部署上线全流程讲透的,确实不多。正好,我之前指导过一个“家庭大厨”小程序的项目,从构思到上线,踩了不少坑,也积累了一整套成熟的方案。这个项目不仅包含了完整的前后端源码、数据库脚本,连毕业论文的框架和详细的视频教程都整理好了。今天,我就把这个项目的核心设计思路、技术选型背后的考量,以及那些教程里不会写的实操细节,毫无保留地分享出来。无论你是正在寻找毕业设计课题的学生,还是想深入理解前后端分离全栈开发的中级开发者,这个项目都能给你提供一个清晰的、可复现的“地图”。
“家庭大厨”小程序的核心定位,是解决家庭场景下的食谱管理、食材采购和烹饪记录问题。用户可以在小程序上浏览、收藏菜谱,根据菜谱生成购物清单,上传自己的烹饪成果。后端则负责用户、菜谱、订单等所有核心数据的管理与接口提供。选择微信小程序作为前端入口,是因为它的用户触达成本极低,无需下载安装,特别适合这种轻量级、高频使用的工具类应用。而Django+Vue的前后端分离架构,则是为了应对未来可能的功能扩展(比如增加Web管理后台)和团队协作的清晰分工。MySQL作为久经考验的关系型数据库,在数据一致性和复杂查询方面有着天然优势,非常适合这种业务关系明确的项目。
2. 技术栈选型与架构设计思路
2.1 为什么是Django + Vue,而不是Spring Boot或纯Django模板?
很多新手会纠结,后端用Python的Django,前端用Vue,是不是多此一举?直接用Django的模板引擎渲染页面,或者用更“企业级”的Java Spring Boot不行吗?这里面的选择,是基于项目阶段、团队技能和长期维护的综合考量。
首先,Django的优势在于“开箱即用”和开发效率。对于“家庭大厨”这类业务模型清晰(用户、菜谱、食材、订单)的项目,Django自带的Admin后台、强大的ORM(对象关系映射)和清晰的MVT(模型-视图-模板)模式,能让我们在极短时间内搭建起可用的后端数据管理和API基础。例如,定义一个菜谱模型(Recipe),Django ORM几乎自动就处理了数据库表的创建、增删改查的SQL生成。这对于毕业设计或快速原型验证阶段,效率是惊人的。
注意:虽然Django的模板引擎也能做全栈开发,但在前后端分离成为主流的今天,让后端只专注于API和数据逻辑,前端(小程序和可能的Web管理端)独立开发,是更清晰、更利于协作的模式。这避免了前后端逻辑在模板中混杂,也便于未来iOS、Android等多端复用同一套API。
其次,Vue.js的引入,是为了构建现代化的Web管理后台。虽然小程序是用户端,但一个完整的系统必然需要一个功能强大的管理后台,供管理员审核菜谱、管理用户、查看数据等。Vue的响应式数据绑定和组件化开发,能极大地提升这类交互复杂的管理后台的开发体验和效率。我们采用Vue来构建这个管理后台,通过Axios调用Django提供的RESTful API,实现了前后端的彻底解耦。
最后,不选Spring Boot的原因主要是技术栈统一和学习曲线。如果团队主力是Python技术栈,强行引入Java会增加学习成本和环境复杂度。Django Rest Framework (DRF) 在构建API方面同样功能强大、生态成熟,完全能满足项目需求。整个技术栈保持了Python/JavaScript的主流搭配,降低了团队的认知负担。
2.2 微信小程序作为客户端的必然性与挑战
微信小程序是此项目的绝对前端核心。它的优势显而易见:跨平台(iOS/Android)、即用即走、依托微信生态(分享、登录)。对于“家庭大厨”这种生活类工具,小程序是最佳载体。
但挑战也随之而来:
- 网络请求:小程序并非运行在浏览器环境,其网络请求API (
wx.request) 与Web的fetch或Axios不同,需要封装适配。 - 登录体系:必须接入微信登录,流程涉及小程序端获取
code,发送至后端,后端用code、appid、secret向微信服务器换openid和session_key。这个流程的安全性设计是关键,绝不能在前端存储敏感信息。 - 数据绑定与生命周期:小程序的WXML(模板)、WXSS(样式)、JS(逻辑)分离,其数据绑定和生命周期管理与Vue/React有相似之处,但也有自己的特点,需要单独学习。
在架构设计上,我们明确了分工:微信小程序(用户端)和Vue管理后台(管理端)共同消费由Django后端提供的一组统一的RESTful API。数据库使用MySQL,所有业务数据均存储于此。这样的架构清晰、职责分明,也便于单独部署和扩展。
3. 后端(Django)核心实现详解
3.1 项目初始化与关键配置
首先,使用django-admin startproject family_chef_backend创建项目。一个关键的、容易被忽略的配置是跨域资源共享(CORS)。因为前端(小程序、Vue后台)运行在不同的域名或端口下,调用后端API时浏览器会因同源策略而拦截。我们需要安装django-cors-headers库并正确配置。
pip install django-cors-headers在settings.py中:
INSTALLED_APPS = [ ... 'corsheaders', ... ] MIDDLEWARE = [ 'corsheaders.middleware.CorsMiddleware', # 尽量放在最前 'django.middleware.common.CommonMiddleware', ... ] # 配置允许的源。小程序端需要配置,Vue开发服务器也需要。 # 注意:上线后应替换为具体的域名,切勿在生产环境使用 ‘*‘ CORS_ALLOWED_ORIGINS = [ “https://你的小程序域名“, # 小程序请求的域名(需在小程序后台配置) “http://localhost:8080“, # Vue开发服务器地址 ] # 如果前端请求携带Cookie(如管理后台登录),还需要设置以下两项 CORS_ALLOW_CREDENTIALS = True实操心得:CORS问题在前后端分离开发中极其常见。一个调试技巧是,打开浏览器开发者工具的“网络(Network)”选项卡,查看被拦截的请求,其响应头中通常会缺少
Access-Control-Allow-Origin。确保CorsMiddleware顺序靠前,并且配置的源地址完全匹配(包括协议http/https和端口)。
3.2 数据模型(Models)设计与Django ORM精要
模型是项目的基石。“家庭大厨”的核心模型包括:用户(UserProfile, 扩展Django自带的User)、菜谱(Recipe)、食材(Ingredient)、菜谱-食材关联(RecipeIngredient)、收藏(Favorite)、烹饪记录(CookLog)等。
这里以Recipe(菜谱) 和RecipeIngredient(关联表) 为例,展示Django ORM在处理多对多关系时的优雅之处。
from django.db import models from django.contrib.auth.models import User class Recipe(models.Model): CATEGORY_CHOICES = ( (‘breakfast‘, ‘早餐‘), (‘lunch‘, ‘午餐‘), (‘dinner‘, ‘晚餐‘), (‘dessert‘, ‘甜点‘), ) title = models.CharField(‘菜谱名称‘, max_length=200) creator = models.ForeignKey(User, on_delete=models.CASCADE, verbose_name=‘创建者‘) category = models.CharField(‘分类‘, max_length=20, choices=CATEGORY_CHOICES) description = models.TextField(‘描述‘, blank=True) steps = models.TextField(‘制作步骤‘) # 可以存储为JSON或换行文本 cover_image = models.ImageField(‘封面图‘, upload_to=‘recipe_covers/‘, blank=True) difficulty = models.IntegerField(‘难度系数‘, choices=((1, ‘简单‘), (2, ‘中等‘), (3, ‘困难‘))) prep_time = models.IntegerField(‘准备时间(分钟)‘) cook_time = models.IntegerField(‘烹饪时间(分钟)‘) created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) def __str__(self): return self.title class Ingredient(models.Model): name = models.CharField(‘食材名称‘, max_length=100, unique=True) unit = models.CharField(‘单位‘, max_length=20, default=‘克‘) def __str__(self): return f“{self.name} ({self.unit})“ class RecipeIngredient(models.Model): “““ 菜谱与食材的关联表,包含用量信息 “”“ recipe = models.ForeignKey(Recipe, on_delete=models.CASCADE, related_name=‘ingredients_used‘) ingredient = models.ForeignKey(Ingredient, on_delete=models.CASCADE) quantity = models.DecimalField(‘用量‘, max_digits=10, decimal_places=2) # 可以添加备注,如‘适量‘、‘少许‘ note = models.CharField(‘备注‘, max_length=50, blank=True) class Meta: unique_together = (‘recipe‘, ‘ingredient‘) # 防止重复添加同一食材为什么使用显式的中间表RecipeIngredient,而不是Django的ManyToManyField?因为单纯的ManyToManyField只能表示关系,而我们的业务中,关系本身有属性(quantity用量和note备注)。使用显式的中间模型,可以存储这些额外信息,查询也更灵活。例如,我们可以通过recipe.ingredients_used.all()获取这道菜的所有用料详情。
3.3 Django Rest Framework (DRF) 构建API接口
DRF是构建REST API的神器。我们为Recipe模型创建序列化器(Serializer)和视图集(ViewSet)。
# serializers.py from rest_framework import serializers from .models import Recipe, RecipeIngredient, Ingredient class IngredientSerializer(serializers.ModelSerializer): class Meta: model = Ingredient fields = ‘__all__‘ class RecipeIngredientSerializer(serializers.ModelSerializer): ingredient = IngredientSerializer(read_only=True) ingredient_id = serializers.PrimaryKeyRelatedField( queryset=Ingredient.objects.all(), source=‘ingredient‘, write_only=True ) class Meta: model = RecipeIngredient fields = (‘id‘, ‘ingredient‘, ‘ingredient_id‘, ‘quantity‘, ‘note‘) class RecipeSerializer(serializers.ModelSerializer): creator_username = serializers.CharField(source=‘creator.username‘, read_only=True) ingredients_used = RecipeIngredientSerializer(many=True, source=‘recipeingredient_set‘) class Meta: model = Recipe fields = (‘id‘, ‘title‘, ‘creator‘, ‘creator_username‘, ‘category‘, ‘description‘, ‘steps‘, ‘cover_image‘, ‘difficulty‘, ‘prep_time‘, ‘cook_time‘, ‘created_at‘, ‘ingredients_used‘) read_only_fields = (‘creator‘, ‘created_at‘) def create(self, validated_data): # 处理嵌套的食材数据 ingredients_data = validated_data.pop(‘recipeingredient_set‘, []) # 从请求上下文中获取当前登录用户 validated_data[‘creator‘] = self.context[‘request‘].user recipe = Recipe.objects.create(**validated_data) for ing_data in ingredients_data: RecipeIngredient.objects.create(recipe=recipe, **ing_data) return recipe def update(self, instance, validated_data): ingredients_data = validated_data.pop(‘recipeingredient_set‘, None) # 更新菜谱基本字段 for attr, value in validated_data.items(): setattr(instance, attr, value) instance.save() # 更新关联食材(先删除旧的,再创建新的,简单处理) if ingredients_data is not None: instance.recipeingredient_set.all().delete() for ing_data in ingredients_data: RecipeIngredient.objects.create(recipe=instance, **ing_data) return instance# views.py from rest_framework import viewsets, permissions, filters from rest_framework.decorators import action from rest_framework.response import Response from .models import Recipe from .serializers import RecipeSerializer class RecipeViewSet(viewsets.ModelViewSet): queryset = Recipe.objects.all().order_by(‘-created_at‘) serializer_class = RecipeSerializer filter_backends = [filters.SearchFilter, filters.OrderingFilter] search_fields = [‘title‘, ‘description‘, ‘category‘] ordering_fields = [‘created_at‘, ‘difficulty‘, ‘prep_time‘] def perform_create(self, serializer): # 在创建时自动关联当前用户,序列化器中已处理,此处可留空或用于其他逻辑 serializer.save() # 自定义动作:获取当前用户创建的菜谱 @action(detail=False, methods=[‘get‘]) def my_recipes(self, request): queryset = self.get_queryset().filter(creator=request.user) page = self.paginate_queryset(queryset) if page is not None: serializer = self.get_serializer(page, many=True) return self.get_paginated_response(serializer.data) serializer = self.get_serializer(queryset, many=True) return Response(serializer.data)在urls.py中,使用DRF的DefaultRouter自动注册路由:
from rest_framework.routers import DefaultRouter from .views import RecipeViewSet router = DefaultRouter() router.register(r‘recipes‘, RecipeViewSet) urlpatterns = [ path(‘api/‘, include(router.urls)), # ... 其他路径,如用户认证 ]这样,我们就自动获得了一组标准的API端点:/api/recipes/(GET列表, POST创建),/api/recipes/<id>/(GET详情, PUT更新, DELETE删除),以及自定义的/api/recipes/my_recipes/。
3.4 用户认证与微信登录集成
这是小程序项目的关键。我们不使用Django自带的Session认证,而是采用更适用于API的Token认证(如JWT)或DRF自带的TokenAuthentication。但针对微信小程序,流程需要定制。
微信登录流程:
- 小程序端调用
wx.login()获取临时凭证code。 - 小程序端将
code发送给我们的Django后端。 - Django后端用
code、小程序的AppID和AppSecret,向微信接口服务https://api.weixin.qq.com/sns/jscode2session发起请求。 - 微信返回
openid(用户唯一标识) 和session_key(会话密钥)。 - 后端根据
openid查找或创建对应用户,并生成一个自定义的Token(可以是随机字符串,也可以使用JWT),将Token和用户信息返回给小程序。 - 小程序后续请求在Header中携带此Token进行认证。
Django后端实现关键代码:
# views.py import requests from django.contrib.auth.models import User from rest_framework.decorators import api_view, permission_classes from rest_framework.permissions import AllowAny from rest_framework.response import Response from rest_framework.authtoken.models import Token # 使用DRF的Token @api_view([‘POST‘]) @permission_classes([AllowAny]) def wechat_login(request): code = request.data.get(‘code‘) if not code: return Response({‘error‘: ‘Code is required‘}, status=400) # 配置你的小程序AppID和AppSecret(务必从环境变量读取,不要硬编码!) APP_ID = settings.WECHAT_APP_ID APP_SECRET = settings.WECHAT_APP_SECRET WECHAT_API_URL = f“https://api.weixin.qq.com/sns/jscode2session?appid={APP_ID}&secret={APP_SECRET}&js_code={code}&grant_type=authorization_code“ response = requests.get(WECHAT_API_URL) data = response.json() openid = data.get(‘openid‘) session_key = data.get(‘session_key‘) if not openid: return Response({‘error‘: ‘WeChat login failed‘, ‘details‘: data}, status=400) # 根据openid查找或创建用户 try: user = User.objects.get(username=openid) # 这里用openid作为username except User.DoesNotExist: # 创建新用户,密码可以随机设置一个,因为后续都用Token认证 user = User.objects.create_user(username=openid, password=User.objects.make_random_password()) # 可以同时创建一个扩展的用户信息表(UserProfile)来存储昵称、头像等 # UserProfile.objects.create(user=user, openid=openid, ...) # 获取或创建用户的Token token, created = Token.objects.get_or_create(user=user) # 返回Token和必要的用户信息(注意:绝不能返回session_key!) return Response({ ‘token‘: token.key, ‘user_id‘: user.id, ‘username‘: user.username, # 可以从UserProfile中返回昵称、头像等 })安全警告:
AppSecret是最高机密,必须通过环境变量(如os.environ.get(‘WECHAT_APP_SECRET’))或配置文件读取,绝不能写入代码提交到版本库。session_key同样敏感,应只在服务器端使用(用于后续解密用户手机号等敏感信息),绝不能传给前端。
4. 前端(微信小程序)核心模块实现
4.1 项目结构与小程序基础配置
一个清晰的项目结构是良好开发的开始。建议的目录结构如下:
miniprogram/ ├── pages/ # 小程序页面 │ ├── index/ # 首页 │ ├── recipeDetail/ # 菜谱详情页 │ ├── profile/ # 个人中心 │ └── ... ├── components/ # 自定义组件(如菜谱卡片、搜索框) ├── utils/ # 工具函数 │ ├── api.js # 封装的网络请求模块 │ ├── auth.js # 登录认证相关 │ └── util.js # 通用工具 ├── app.js # 小程序入口文件 ├── app.json # 全局配置 ├── app.wxss # 全局样式 └── project.config.json # 项目配置文件在app.json中,我们需要配置页面路径、窗口样式、网络超时等,并引入必要的自定义组件或分包。
{ “pages“: [ “pages/index/index“, “pages/recipeDetail/recipeDetail“, “pages/profile/profile“ ], “window“: { “navigationBarTitleText“: “家庭大厨“, “navigationBarBackgroundColor“: “#f8f9fa“, “navigationBarTextStyle“: “black“ }, “networkTimeout“: { “request“: 10000 }, “style“: “v2“, “sitemapLocation“: “sitemap.json“ }4.2 网络请求封装与全局状态管理
小程序原生的wx.request功能较弱,我们需要封装一个带Token、统一错误处理的请求模块。
// utils/api.js const BASE_URL = ‘https://your-backend-domain.com/api‘; // 替换为你的后端地址 const request = (url, method = ‘GET‘, data = {}) => { return new Promise((resolve, reject) => { const token = wx.getStorageSync(‘token‘); // 从本地存储获取Token const header = { ‘Content-Type‘: ‘application/json‘ }; if (token) { header[‘Authorization‘] = `Token ${token}`; // DRF Token认证格式 } wx.request({ url: `${BASE_URL}${url}`, method: method, data: method === ‘GET‘ ? data : JSON.stringify(data), header: header, success: (res) => { if (res.statusCode >= 200 && res.statusCode < 300) { resolve(res.data); } else { // 处理HTTP错误,如401未授权 if (res.statusCode === 401) { // Token过期或无效,跳转到登录页 wx.showToast({ title: ‘登录已过期,请重新登录‘, icon: ‘none‘ }); wx.removeStorageSync(‘token‘); wx.reLaunch({ url: ‘/pages/login/login‘ }); } reject(new Error(`请求失败: ${res.statusCode}`)); } }, fail: (err) => { wx.showToast({ title: ‘网络连接失败‘, icon: ‘none‘ }); reject(err); } }); }); }; // 导出常用的方法 export const get = (url, data) => request(url, ‘GET‘, data); export const post = (url, data) => request(url, ‘POST‘, data); export const put = (url, data) => request(url, ‘PUT‘, data); export const del = (url) => request(url, ‘DELETE‘);对于简单的状态管理(如用户信息),可以使用小程序的getApp().globalData或本地存储wx.setStorageSync。对于复杂应用,可以考虑使用mobx-miniprogram这类状态管理库。
4.3 首页菜谱列表与详情页实现
首页通常是一个菜谱瀑布流或列表。我们使用封装的get方法从后端API获取数据。
// pages/index/index.js import { get } from ‘../../utils/api.js‘; Page({ data: { recipeList: [], page: 1, hasMore: true, isLoading: false }, onLoad() { this.loadRecipes(); }, // 加载菜谱列表 loadRecipes() { if (this.data.isLoading || !this.data.hasMore) return; this.setData({ isLoading: true }); const { page } = this.data; get(`/recipes/?ordering=-created_at&page=${page}`).then(res => { const newList = page === 1 ? res.results : [...this.data.recipeList, ...res.results]; this.setData({ recipeList: newList, hasMore: !!res.next, // 根据DRF分页响应判断是否有下一页 page: page + 1, isLoading: false }); }).catch(err => { console.error(err); this.setData({ isLoading: false }); wx.showToast({ title: ‘加载失败‘, icon: ‘none‘ }); }); }, // 跳转到详情页 goToDetail(e) { const recipeId = e.currentTarget.dataset.id; wx.navigateTo({ url: `/pages/recipeDetail/recipeDetail?id=${recipeId}` }); }, // 下拉刷新 onPullDownRefresh() { this.setData({ page: 1, hasMore: true }); this.loadRecipes().finally(() => { wx.stopPullDownRefresh(); }); }, // 上拉触底加载更多 onReachBottom() { this.loadRecipes(); } });对应的WXML模板:
<!-- pages/index/index.wxml --> <view class=“container“> <view wx:for=“{{recipeList}}“ wx:key=“id“ class=“recipe-card“ bindtap=“goToDetail“>// utils/auth.js import { post } from ‘./api.js‘; export const login = () => { return new Promise((resolve, reject) => { wx.login({ success: (loginRes) => { if (loginRes.code) { // 发送code到后端 post(‘/wechat_login/‘, { code: loginRes.code }).then(res => { // 登录成功,存储token和用户信息 wx.setStorageSync(‘token‘, res.token); wx.setStorageSync(‘userInfo‘, { id: res.user_id, username: res.username }); // 更新全局状态 const app = getApp(); if (app && app.globalData) { app.globalData.userInfo = { id: res.user_id, username: res.username }; } resolve(res); }).catch(err => { reject(err); }); } else { reject(new Error(‘获取登录code失败:‘ + loginRes.errMsg)); } }, fail: (err) => { reject(err); } }); }); }; // 检查登录状态 export const checkLogin = () => { const token = wx.getStorageSync(‘token‘); if (!token) { // 未登录,可以引导用户去登录 return false; } // 这里可以增加一个轻量级的Token有效性验证,比如调用一个简单的验证接口 return true; };在需要用户登录才能操作的页面(如发布菜谱、收藏),可以在onLoad或按钮点击事件中调用checkLogin(),如果未登录则跳转到登录页或弹出登录模态框。
5. 前端(Vue管理后台)搭建要点
管理后台使用Vue CLI创建,采用Element Plus作为UI组件库,结构清晰。核心在于与Django API的交互和页面路由管理。
5.1 Vue项目初始化与路由配置
vue create family-chef-admin cd family-chef-admin vue add router npm install element-plus axios在src/router/index.js中配置路由,并设置路由守卫进行登录拦截。
import { createRouter, createWebHistory } from ‘vue-router‘ import Login from ‘../views/Login.vue‘ import Dashboard from ‘../views/Dashboard.vue‘ import RecipeList from ‘../views/recipe/List.vue‘ import { getToken } from ‘@/utils/auth‘ // 假设有一个从localStorage获取token的工具函数 const routes = [ { path: ‘/login‘, name: ‘Login‘, component: Login }, { path: ‘/‘, component: Dashboard, meta: { requiresAuth: true }, // 需要认证的元信息 children: [ { path: ‘recipes‘, name: ‘RecipeList‘, component: RecipeList }, // ... 其他子路由 ] } ] const router = createRouter({ history: createWebHistory(), routes }) // 全局前置路由守卫 router.beforeEach((to, from, next) => { const hasToken = getToken() if (to.meta.requiresAuth && !hasToken) { // 如果需要认证且没有token,跳转到登录页 next(‘/login‘) } else if (to.path === ‘/login‘ && hasToken) { // 如果已登录又访问登录页,跳转到首页 next(‘/‘) } else { next() } }) export default router5.2 使用Axios与Django API通信
创建src/utils/request.js封装Axios实例,统一处理请求拦截(添加Token)、响应拦截(处理错误)和基础URL。
import axios from ‘axios‘ import { ElMessage } from ‘element-plus‘ import router from ‘@/router‘ const service = axios.create({ baseURL: process.env.VUE_APP_BASE_API, // 在.env.development等文件中配置 timeout: 10000 }) // 请求拦截器 service.interceptors.request.use( config => { const token = localStorage.getItem(‘token‘) if (token) { config.headers[‘Authorization‘] = `Token ${token}` } return config }, error => { console.error(‘Request Error:‘, error) return Promise.reject(error) } ) // 响应拦截器 service.interceptors.response.use( response => { const res = response.data // 可以根据后端返回的code进行更精细的处理 return res }, error => { console.error(‘Response Error:‘, error.response) if (error.response && error.response.status === 401) { ElMessage.error(‘登录已过期,请重新登录‘) localStorage.removeItem(‘token‘) router.push(‘/login‘) } else { ElMessage.error(error.response?.data?.message || ‘请求失败‘) } return Promise.reject(error) } ) export default service在组件中,引入此request实例进行API调用,管理后台的菜谱列表、编辑、删除等功能即可顺畅实现。
6. 数据库设计与部署上线
6.1 MySQL数据库脚本与优化建议
Django的python manage.py makemigrations和migrate命令会自动根据模型生成数据库表。但为了项目文档的完整性和部署方便,我们通常也会导出SQL脚本。
-- 示例:核心表结构 CREATE TABLE `recipes_recipe` ( `id` bigint NOT NULL AUTO_INCREMENT, `title` varchar(200) NOT NULL, `creator_id` int NOT NULL, `category` varchar(20) NOT NULL, `description` longtext, `steps` longtext NOT NULL, `cover_image` varchar(100), `difficulty` int NOT NULL, `prep_time` int NOT NULL, `cook_time` int NOT NULL, `created_at` datetime(6) NOT NULL, `updated_at` datetime(6) NOT NULL, PRIMARY KEY (`id`), KEY `recipes_recipe_creator_id_idx` (`creator_id`), CONSTRAINT `recipes_recipe_creator_id_fk` FOREIGN KEY (`creator_id`) REFERENCES `auth_user` (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE `recipes_ingredient` (...); CREATE TABLE `recipes_recipeingredient` (...);优化建议:
- 索引:在经常用于查询和关联的字段上建立索引,如
creator_id,category,title(可考虑全文索引)。 - 图片存储:
cover_image字段存储的是图片在服务器上的路径。强烈建议将图片上传至对象存储(如阿里云OSS、腾讯云COS),数据库只存URL。这能极大减轻服务器存储压力和带宽消耗。 - 分表考虑:如果
cook_log(烹饪记录)这类日志表数据量增长极快,初期可按月或按用户哈希进行分表。
6.2 前后端分离项目部署实战
部署是毕业设计的最后一步,也是展示成果的关键。
后端(Django)部署(以Linux + Nginx + uWSGI为例):
- 服务器准备:购买云服务器(如腾讯云轻量应用服务器),安装Python3、MySQL、Nginx。
- 项目上传与依赖安装:将代码上传至服务器,在项目目录下创建虚拟环境并安装依赖:
pip install -r requirements.txt。 - 数据库配置:在MySQL中创建数据库,修改Django
settings.py中的DATABASES配置,然后运行python manage.py migrate。 - 收集静态文件:运行
python manage.py collectstatic,将静态文件收集到指定目录供Nginx服务。 - 配置uWSGI:创建
uwsgi.ini配置文件,指定项目路径、socket文件、进程数等。 - 配置Nginx:编辑Nginx站点配置,将动态请求代理到uWSGI socket,静态文件直接由Nginx处理。
- 启动服务:启动uWSGI进程和Nginx服务。
前端(微信小程序):直接在微信开发者工具中上传代码,提交审核即可。注意在微信小程序后台配置服务器域名(request合法域名、uploadFile合法域名等)。
前端(Vue管理后台)部署:
- 构建生产版本:在项目目录下运行
npm run build,生成dist文件夹。 - 部署静态文件:将
dist文件夹内的所有文件,上传到你的Web服务器(可以是专门的静态文件服务器,也可以是Nginx配置的一个静态站点目录)。 - Nginx配置:为管理后台配置一个子域名(如
admin.yourdomain.com),指向dist目录。
部署避坑指南:
- 环境变量:
SECRET_KEY、数据库密码、微信AppSecret等敏感信息务必使用环境变量或配置文件管理,不要写入代码。- 静态文件404:确保Nginx配置中
alias或root路径正确,且文件权限允许Nginx进程读取。- 跨域问题再现:生产环境务必在Django的
CORS_ALLOWED_ORIGINS中配置小程序前端和Vue后台的确切域名,而不是‘*‘。- 数据库连接失败:检查MySQL是否允许远程连接(生产环境建议只允许本地连接),以及Django配置中的HOST、PORT、USER、PASSWORD是否正确。
- 媒体文件服务:如果用户上传了图片,需要配置Nginx或CDN来提供这些媒体文件的访问服务。
7. 常见问题排查与项目总结
7.1 开发与部署中的典型问题速查表
| 问题现象 | 可能原因 | 排查步骤与解决方案 |
|---|---|---|
小程序请求后端API失败,报错request:fail url not in domain list | 小程序后台未配置服务器域名 | 1. 登录微信小程序后台。 2. 进入“开发”->“开发管理”->“开发设置”。 3. 在“服务器域名”中,将你的后端API域名(如 api.yourdomain.com)添加到request合法域名列表中。 |
Django后端报错CSRF verification failed | Django默认开启CSRF保护,而API请求通常不需要 | 1. 对于使用Session认证的视图,需要处理CSRF。 2. 对于使用Token/JWT认证的API视图,最直接的方法是在该视图上使用 @csrf_exempt装饰器,或者更推荐的是,在API视图集中使用DRF的认证类,它默认不检查CSRF。 |
| Vue管理后台访问API出现CORS错误 | 后端CORS配置不正确或未生效 | 1. 检查Djangosettings.py中CORS_ALLOWED_ORIGINS是否包含了Vue前端的地址(如http://localhost:8080)。2. 确保 CorsMiddleware在中间件列表中的位置尽量靠前。3. 生产环境检查Nginx配置,确保没有添加额外的CORS头导致冲突。 |
| 图片上传后无法访问,返回403或404 | 媒体文件权限问题或Nginx未配置静态文件服务 | 1. 检查Django中MEDIA_URL和MEDIA_ROOT设置。2. 检查上传文件的目录权限(确保Web服务器进程有读写权限)。 3. 在Nginx配置中添加一个 location块来处理媒体文件请求,将其映射到MEDIA_ROOT目录。 |
| 微信登录成功,但后端无法创建/找到用户 | openid获取失败或用户匹配逻辑错误 | 1. 检查小程序AppID和AppSecret是否正确,且未在微信后台重置。2. 打印微信接口返回的完整响应,确认包含 openid。3. 检查后端根据 openid查找用户的逻辑(如username=openid),确保唯一性。 |
| Vue页面刷新后路由丢失或404 | 前端路由使用了history模式,但Nginx未正确配置 | 1. 在Vue Router中检查是否使用了createWebHistory。2. 在Nginx配置中,添加一个 try_files指令,将非静态文件请求重定向到index.html:location / { try_files $uri $uri/ /index.html; } |
7.2 项目扩展方向与个人心得
这个“家庭大厨”项目作为一个毕业设计或全栈入门项目,已经具备了完整的骨架。但它的潜力远不止于此,你可以根据自己的兴趣进行深度扩展:
- 智能推荐:基于用户的收藏、浏览记录,使用协同过滤或内容推荐算法,在首页增加“猜你喜欢”模块。
- 社交功能:增加关注、点赞、评论、菜谱分享到朋友圈等功能,增强用户互动。
- 食材电商对接:与生鲜电商API对接,根据菜谱食材清单一键加入购物车。
- 视频教程:支持用户上传烹饪短视频,集成视频播放与处理能力。
- 数据可视化:在管理后台增加仪表盘,用图表展示用户增长、热门菜谱等数据。
从我指导这个项目的经验来看,最大的挑战往往不在于某个具体技术的实现,而在于整个系统的协同与调试。前后端分离后,接口联调、数据格式约定、错误处理的一致性变得至关重要。建议在开发初期,就使用Postman或Apifox等工具仔细定义和测试每一个API接口,前后端共同维护一份API文档。另外,错误处理一定要做在前头,无论是网络异常、用户输入校验还是后端服务错误,前端都要有友好的提示和降级方案,这样才能给用户一个稳定的体验。
最后,关于源码、数据库脚本和论文框架,我的建议是:理解远比复制粘贴重要。你可以参考项目的结构和代码逻辑,但一定要自己动手从头搭建一遍,过程中遇到的每一个报错都是宝贵的学习机会。把部署流程走通,把功能点一个个实现,这个过程中积累的经验和解决问题的能力,才是这个项目带给你的最大财富。
本文还有配套的精品资源,点击获取