ARTICLE DETAIL

资讯详情

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

基于Python+Vue的美食分享系统:Django与Flask混合架构实战解析

基于Python+Vue的美食分享系统:Django与Flask混合架构实战解析 做项目这些年我有个挺深的体会一个看着挺完整的系统拆开来往往没那么多高深东西能跑通上线靠的全是细节上的把关和踩坑后的复盘。这次写的是一个基于Python和Vue的美食分享系统技术栈涵盖了Pycharm、django和flask。说实话一开始看到这个项目的时候我第一反应是有点困惑的——为什么一个系统里会同时出现django和flask这不就是别人常说的“重复造轮子”吗但真正把项目做完之后我才发现这种组合本身就是一种很实在的工程决策。今天就把这个项目的完整搭建过程、技术取舍、以及我在实际开发中碰到的问题从头到尾整理出来。不管你是正在用django写后端、刚接触Vue想做前后端分离还是单纯想看看一个美食分享系统该怎么从零落地这篇文章应该都能给你一些有用的参考。1. 为什么一个系统里会同时出现django和flask先回答开头那个问题。这个美食分享系统的核心后端确实是基于django做的因为项目里有用户注册登录、菜谱发布、评论互动、收藏点赞还有后台管理这类经典且有状态的功能模块。django的ORM、Admin后台、表单校验、认证体系这些东西在开发这种业务系统的时候优势非常明显能帮我把大量重复的CRUD逻辑直接压到最低。那flask在这个项目里干了什么呢答案是推荐模块和部分轻量级接口。美食分享系统里有一个高频功能基于用户点赞和浏览记录返回一个“你可能喜欢”的菜品列表。这个推荐算法本身是个纯计算服务不依赖数据库事务和用户体系我希望能单独把它拆出来跑。flask作为微框架足够轻写一个推荐接口只需要几十行代码启动压力小部署的时候也可以相对独立。而django负责的是重业务逻辑和数据库操作这样两个框架各管一摊在工程上反而清晰很多。这种组合在真实项目中并不少见。很多团队会在django之外挂一个flask或者fastapi服务专门跑算法、爬虫回调或者webhook之类的轻逻辑。拿我来说做一个简单的类似度计算只需要在flask里构建一个食材标签向量再用余弦相似度去匹配整个过程不需要经过django的模型层。这种架构让我在写业务接口的时候完全不用被推荐服务的代码干扰两套逻辑互不污染。再说回工具链。Pycharm是这个项目的主开发IDE两个框架的代码都在同一个项目目录里管理。Pycharm对django和flask都有模板支持创建项目、启动服务、Debug的时候非常顺手尤其是它对数据库查询的调试能力在排查ORM生成SQL的问题时特别管用。后面我会单独讲Pycharm环境的配置细节这里面坑其实也不少。2. 环境准备里容易被忽略的几个关键点2.1 Python版本和虚拟环境不要凭感觉选我见过太多人一上来直接装最新版Python然后项目跑不起来就开始怀疑代码。这个美食分享系统我建议使用Python 3.10.x原因很简单django的LTS版本在3.10上兼容性最稳flask的所有常用扩展也都能正常安装不会碰到某些依赖库预编译wheel版本缺位的问题。虚拟环境我强烈建议用虚拟环境不要图省事直接全局安装。因为django和flask的某些依赖比如Werkzeug可能会在版本上打架。两个框架并存的时候锁版本比什么都重要。我在项目根目录下建了一个venv目录激活后先统一把基础依赖装齐后面再分别装django和flask的扩展包。另外一个容易忽略的问题是Pycharm的Python解释器指向。很多人创建项目的时候选了解释器但后面用命令行执行迁移命令的时候用的却是系统全局的Python环境结果就是解释器版本不一致导致ORM查询报错。这里有个小技巧在Pycharm的终端里激活虚拟环境后先执行一下python --version再执行python -c import django; print(django.__version__)确认当前终端里能正确导入django再跑迁移命令。2.2 Django项目结构怎么组织才不混乱在Pycharm里新建一个django项目后默认生成的结构其实还算清晰但对于美食分享系统来说直接把代码写死在django项目目录里后续会乱。我选择把核心业务拆成几个子app每个app负责独立的业务域accounts用户注册、登录、个人信息posts菜谱发布、菜品列表、菜品详情comments评论和回复favorites收藏和点赞recommend调用flask服务获取推荐结果每个子app里我建议都自带独立的 models.py、views.py、serializers.py。这样后续维护的时候改评论逻辑不用去翻菜谱的代码对新手理解项目结构也有很大帮助。2.3 Vue环境配置的常见绊脚石Vue这边的环境配置也是新手的重灾区。热搜词里都提到vue安装及环境配置这个确实值得单独说说。我用的Vue版本是Vue 3配合Vite构建工具而不是Vue 2的webpack方式原因后面会讲。需要先装Node.js版本最好在16以上装完之后用npm命令安装Vite脚手架npm create vitelatest food-frontend -- --template vue cd food-frontend npm install npm run dev这里有几个容易栽跟头的地方第一是npm镜像特别慢甚至安装一半卡死。解决办法是换成淘宝镜像之前踩过好多次。设置方式很简单npm config set registry https://registry.npmmirror.com第二是Node版本问题。Vite 3以上版本要求Node 14.18或者16如果你的机器装的是旧Node版本启动dev服务器的时候会直接报错提示版本不支持。这个层面没什么好讨论的装新版本就好。第三是Pycharm和Vue的配合问题。Pycharm虽然对前端代码也有支持但Vue单文件组件的语法高亮和格式化我建议还是单独用VS Code来写前端Pycharm专心管Python后端。分工明确体验会好很多。如果你非要用Pycharm写Vue需要装Vue.js插件同时把Vite的dev-server端口默认5173跑在本地然后用浏览器直接访问这样也可以调试。3. Django后端的核心功能实现与踩坑笔记3.1 用户注册登录从自定义User模型开始做美食分享系统用户账号体系是第一个绕不开的模块。我强烈建议你在项目一开始就自定义用户模型哪怕表面上看起来用django的默认User也行。因为默认的User模型没有手机号、头像、个人简介这些字段如果项目跑到一半再想加字段就要做数据库迁移的操作非常折腾。自定义用户模型最简单的方式是继承AbstractUserfrom django.contrib.auth.models import AbstractUser from django.db import models class User(AbstractUser): avatar models.ImageField(upload_toavatars/, blankTrue, nullTrue) phone models.CharField(max_length11, blankTrue, nullTrue) bio models.TextField(max_length200, blankTrue) created_at models.DateTimeField(auto_now_addTrue) class Meta: db_table users然后在settings.py里指定AUTH_USER_MODEL accounts.User这里有个很重要的坑如果你在之后做了数据库迁移再改AUTH_USER_MODEL会非常痛苦因为django的权限表和用户表已经关联了。所以这个决策必须在项目最开始就做好。注册接口我选择了基于jwt的认证方案因为前端是Vue做的单页应用用token认证比session更自然。登录接口返回access token和refresh token前端存起来后续请求带上Authorization头就行。jwt库用的是djangorestframework-simplejwt配置起来不复杂但这个过程中也踩了几个坑我一个个说。3.2 菜谱发布图片上传和富文本处理的方案菜谱发布是系统的核心模块。用户需要填菜名、食材列表、制作步骤还可以上传成品图。图片上传这块django的ImageField本身非常好用但是需要配合MEDIA_ROOT和MEDIA_URL来配置存储路径。在settings.py里加上MEDIA_URL /media/ MEDIA_ROOT BASE_DIR / media然后在项目的urls.py里处理静态媒体文件from django.conf import settings from django.conf.urls.static import static urlpatterns static(settings.MEDIA_URL, document_rootsettings.MEDIA_ROOT)上传图片的时候前端用FormData提交后端接收文件对象存到指定目录然后把文件的URL存进数据库。这一步要看清楚的一点是ImageField保存的是相对路径真正线上访问的时候需要拼接完整的域名。开发阶段直接返回request.build_absolute_uri(instance.image.url)就行。食材列表这种列表型数据不要用逗号拼接存字符串虽然实现简单但后面想按食材搜索的时候就后悔了。我建了一个食材表菜谱和食材之间用多对多关联这样用户在搜索的时候就可以直接通过食材名反向查询菜谱效率高得多。这就是django的QuerySet和ORM的便利之处。3.3 Django执行查询特别是删除对象的注意事项在热搜词里反复出现的“django执行查询-删除对象”这是新手几乎都会踩的坑。django删除对象有两种方式# 方式一删除单个实例 food Food.objects.get(id1) food.delete() # 方式二批量删除 Food.objects.filter(category_id3).delete()删除单个对象的时候没问题。但批量删除的时候要特别注意django默认的delete()方法是会遍历所有关联对象把这些对象也一并删掉的。比如我删除一个分类对象的时候属于这个分类的所有菜谱会被级联删除。如果你的业务逻辑不想要这种级联效果可以在外键里设置on_deletemodels.SET_NULL或者on_deletemodels.PROTECT。还有一个小细节是get()方法如果查不到对象会抛DoesNotExist异常查到多个会抛MultipleObjectsReturned异常所以实际开发中更稳妥的写法是food Food.objects.filter(id1).first() if food is not None: food.delete()用filter().first()代替get()就不会因为没查到数据而中断服务了。这个习惯建议早点养成能帮你少接很多报错。3.4 Django的Admin后台自定义轻松管理菜谱数据django自带的后台管理是这个框架绕不开的亮点之一。美食分享系统里运营需要审核用户发布的菜谱所以后台的可用性很重要。默认的Admin界面虽然信息全但看起来不够友好而且关联关系、图片预览这些都需要额外配置。我重写了一个简单的FoodAdminfrom django.contrib import admin from .models import Food, Ingredient class IngredientInline(admin.TabularInline): model Food.ingredients.through extra 1 class FoodAdmin(admin.ModelAdmin): list_display (title, user, category, created_at) list_filter (category, created_at) search_fields (title, ingredients__name) inlines [IngredientInline] fieldsets ( (基本信息, { fields: (title, cover, category, user) }), (内容详情, { fields: (steps, ingredients) }), ) admin.site.register(Food, FoodAdmin)后台这里有一个典型的问题搜索字段如果关联到多对多字段比如食材django会在后台搜索的时候做JOIN查询数据量大的时候会有性能开销。不过普通项目的数据量级完全不用过度优化等真正出现性能瓶颈再考虑Elasticsearch或者缓存也不迟。4. Vue前端搭建美食分享界面的核心逻辑4.1 Vue 3和Vite的组合为什么更香刚接触Vue时很多人还在用Vue 2和vue-cli但新项目我觉得完全没有理由再选Vue 2了。Vue 3的组合式APIComposition API让逻辑复用变得舒服很多尤其是同一个功能需要用到多个状态的时候不用再为了一个mixin绞尽脑汁。Vite启动速度比webpack快太多了因为它是用浏览器原生ES模块加载方式开发阶段几乎秒开。Vue 3的工程里路由用vue-router 4状态管理用pinia分别替代了vue-router 3和vuex。这个组合是现在Vue生态的标准答案。pinia相比vuex的明显优势是类型提示好store定义简单不需要写各种mutations、actions的样板代码。4.2 菜谱列表页和详情页的路由设计首页展示菜谱列表详情页展示菜谱内容这个交互流程可以用vue-router的动态路由来搭。import { createRouter, createWebHistory } from vue-router import HomePage from ../views/HomePage.vue import FoodDetail from ../views/FoodDetail.vue const routes [ { path: /, name: home, component: HomePage }, { path: /food/:id, name: food-detail, component: FoodDetail } ] const router createRouter({ history: createWebHistory(), routes }) export default router详情页里通过route.params.id获取菜品ID再调用后端接口拿到详细数据。在这个过程中有一个很典型的坑在Vue 3里如果同一个路由组件内只变化了参数比如从/food/1切到/food/2组件实例会被复用onMounted的请求不会重新执行。解决办法是在组件里监听路由变化或者用watch重新拉取数据不然切换详情页的时候页面内容不会更新。import { watch } from vue import { useRoute } from vue-router const route useRoute() watch(() route.params.id, (newId) { fetchFoodDetail(newId) }, { immediate: true })4.3 与后端交互的API封装方式前后端分离的项目API请求模块的封装决定了后续开发的舒服程度。我用axios做请求库封装了一个request实例统一处理baseURL、超时时间和请求拦截。import axios from axios import { useUserStore } from ../stores/user const request axios.create({ baseURL: /api, timeout: 10000 }) request.interceptors.request.use(config { const userStore useUserStore() if (userStore.token) { config.headers.Authorization Bearer ${userStore.token} } return config }) request.interceptors.response.use( response response.data, error { if (error.response?.status 401) { // 跳转登录页 } return Promise.reject(error) } )这里我建议配置Vite的proxy代理开发阶段把/api开头的请求全部转发到django后端这样就不存在跨域问题了。具体在vite.config.js里export default { server: { proxy: { /api: { target: http://127.0.0.1:8000, changeOrigin: true } } } }这个方案比在后端配CORS要干净很多生产环境再用nginx统一代理整个链路就顺了。4.4 图片懒加载和列表优化美食类网站的列表页通常会有大量图片如果不做懒加载页面首次加载的图片请求数会非常多体感会很差。我用的方案是第三方库v-lazy同时也比较推荐原生IntersectionObserver来实现其实根本不复杂。const imageObserver new IntersectionObserver((entries) { entries.forEach(entry { if (entry.isIntersecting) { const img entry.target img.src img.dataset.src imageObserver.unobserve(img) } }) }) // 在每个img元素上设置>app.directive(lazy, { mounted(el, binding) { const observer new IntersectionObserver((entries) { if (entries[0].isIntersecting) { el.src binding.value observer.unobserve(el) } }) observer.observe(el) } })然后在模板里用v-lazyitem.cover_url替代v-bind:src就行。列表接口做分页或者无限滚动的时候配合这个懒加载指令滚动浏览的体验就和正常App差不多了。5. 热搜词里藏着的坑m3u8播放、websocket推送、flask部署5.1 Vue播放m3u8视频的兼容性处理这里有个挺有意思的现象美食分享系统本身并不需要做视频播放但很多搜Vue播放m3u8的人其实都是在折腾各类系统里的视频功能。项目里如果要嵌入做菜过程的视频视频源一般会走m3u8格式这样的话就要在Vue里处理HLS流播放。m3u8本质是Apple的HTTP Live Streaming协议。普通浏览器的video标签不会直接支持m3u8格式只有Safari能原生播放。所以在Vue项目里播放m3u8最简单的方案是用hls.js这个库。import Hls from hls.js if (Hls.isSupported()) { const video document.getElementById(video-player) const hls new Hls() hls.loadSource(videoUrl) hls.attachMedia(video) hls.on(Hls.Events.MANIFEST_PARSED, () { video.play() }) }如果是在Chrome和Firefox上这段代码可以直接跑通。如果浏览器不支持hls.js比如老的IE就fallback到原生video标签播放。对于美食教程这种短视频内容更建议直接在服务端转成mp4格式再输出处理起来省心很多。这个功能我另外提一句如果我做的是视频分享转码这块会特别吃服务器资源不做点播平台的话完全没必要上m3u8老老实实用通用格式反而轻量。5.2 Django WebSocket后端有数据时前端自动推送热搜词里有一句很长的“django python websocket实现后台有数据前端推送”看到这个关键词基本可以判断是在做一个实时通知功能我的美食分享系统也有类似需求用户发布菜谱之后他的粉丝收到一条新作品通知这个场景用WebSocket正合适。Django里做WebSocket推荐用Django Channels它在django的WSGI之外加了一层ASGI支持可以和传统HTTP接口并存。安装pip install channels channels-redis然后在项目settings.py里注册channels并配置ASGI applicationINSTALLED_APPS [ daphne, channels, # ... ] ASGI_APPLICATION food_project.asgi.application CHANNEL_LAYERS { default: { BACKEND: channels_redis.core.RedisChannelLayer, CONFIG: { hosts: [(127.0.0.1, 6379)], }, }, }在asgi.py里配置import os from django.core.asgi import get_asgi_application from channels.routing import ProtocolTypeRouter, URLRouter from channels.auth import AuthMiddlewareStack from .websocket_urls import websocket_urlpatterns os.environ.setdefault(DJANGO_SETTINGS_MODULE, food_project.settings) application ProtocolTypeRouter({ http: get_asgi_application(), websocket: AuthMiddlewareStack( URLRouter(websocket_urlpatterns) ), })WebSocket连接的路由可以定义在单独的文件里from django.urls import path from .consumers import NotificationConsumer websocket_urlpatterns [ path(ws/notifications/, NotificationConsumer.as_asgi()), ]Consumer里的核心逻辑就是当某个用户触发新菜谱发布时通过channel layer向对应粉丝组推送消息class NotificationConsumer(AsyncWebsocketConsumer): async def connect(self): self.group_name fuser_{self.scope[user].id} await self.channel_layer.group_add(self.group_name, self.channel_name) await self.accept() async def receive(self, text_data): # 接收前端发来的消息根据业务需求处理 pass async def send_notification(self, event): await self.send(text_dataevent[content])在view里发消息的时候用async_to_sync(channel_layer.group_send)就能把通知推给对应群组。这个过程中最需要注意的就是Redis服务必须提前启动不然channel layer连不上会报错。另外如果用的是Windows系统Redis装起来比Linux麻烦这一点在项目里需要提前确认好。5.3 Flask部署别再守着开发服务器不放flask在系统里的角色是推荐服务我单独部署在一个端口比如5001上。但很多人在部署flask时会直接跑app.run()默认启动的是Werkzeug开发服务器这种服务器性能非常有限完全不适合线上环境。建议部署flask时用gunicornLinux系统下的经典搭配。项目根目录写一个wsgi.pyfrom app import app as application if __name__ __main__: application.run()然后通过gunicorn启动gunicorn -w 4 -b 0.0.0.0:5001 wsgi:application-w 4表示启动4个worker进程具体的数量一般根据CPU核数来定推荐是2 * CPU核数 1的公式。如果是Windows服务器环境gunicorn装不了可以用waitress也是一个比较成熟的纯Python WSGI服务器配置方式类似。线上推荐服务的调用是django后端通过requests库或httpx去请求flask的/recommend接口拿到菜品列表再返回给前端。这个链路因为多了一层HTTP调用做超时控制和异常兜底就特别重要import httpx try: response httpx.get( http://127.0.0.1:5001/recommend, params{user_id: user_id}, timeout2.0 ) data response.json() except httpx.RequestError: # 推荐服务挂了返回热门菜谱兜底 data Food.objects.order_by(-view_count)[:6]这里有个经验可以分享和算法服务交互时宁可拿一个旧的热门列表顶上去也不能让用户的请求因为推荐服务不可用而失败。线上系统里暴露出“推荐挂了”比“页面打不开”要好得多。5.4 另外一个框架相关的隐藏知识点flask如何绑定到网页元素热搜词里有个“flask如何绑定到网页元素”初看有点莫名其妙但结合上下文就明白了——很多刚开始学flask的人不知道怎么把后端的Python变量传到前端HTML里渲染。在flask中这个问题的核心是Jinja2模板引擎。后端通过render_template传递变量from flask import Flask, render_template app Flask(__name__) app.route(/) def index(): food_list [宫保鸡丁, 麻婆豆腐, 回锅肉] return render_template(index.html, foodsfood_list)在模板文件里就能这样渲染ul {% for food in foods %} li{{ food }}/li {% endfor %} /ul不过要说明白如果项目前端是Vue的话flask就纯当API服务用返回JSON数据根本不需要模板渲染这一步。这就是为什么我前面强调要区分清楚两个框架的职责边界——混用模板渲染和前端框架是最容易让项目变成一锅粥的地方。6. 前后端联调阶段的高频问题和解决记录6.1 跨域不是所有场景都需要后端开CORS做前后端分离项目跨域问题是逃不开的。开发阶段我用Vite的proxy代理解决了大部分接口的跨域。这种方法的好处是前端请求的是本地相对路径/api/xxx由Vite开发服务器转发到后端浏览器根本感知不到跨域因此也不需要后端配CORS中间件。但是如果你不是用Vite或者前端请求的是别的端口域名那后端就需要配CORS了。django里用django-cors-headers这个库安装后把它加到INSTALLED_APPS中间件也需要注册MIDDLEWARE [ corsheaders.middleware.CorsMiddleware, # ... ] CORS_ALLOWED_ORIGINS [ http://localhost:5173, ]这个配置只允许来自5173端口的前端页面访问接口。如果配置不正确或者漏了浏览器控制台会明确报跨域错误开发者很轻松就能定位到。6.2 Token存储在哪个位置更安全登录后前端拿到的JWT token到底应该放localStorage还是sessionStorage甚至放cookie这个问题我专门纠结过也是踩坑比较多的地方。localStorage的缺点是万一被XSS攻击攻击者可以拿到token伪造请求风险比较大。但如果完全不用token用HttpOnly cookie前后端分离的CSRF防护会变得复杂。实际项目中我用的是一个折中方案把token放在内存pinia store里页面刷新的时候调一个/auth/refresh接口重新获取token。这个方案安全性相对好一些但实现复杂度稍微高一点。如果你为了省事直接把token放localStorage也不是不行但前提要保证前端对XSS的防范做到位尤其是不能把用户评论内容用v-html直接渲染。我在美食分享系统里所有用户的菜谱步骤和评论内容都统一走纯文本渲染这样能把XSS风险降到最低。6.3 接口返回结构的统一设计前后端联调最费时间的还真不是接口写不出来是每个接口返回的数据结构不一样前端解析起来东一块西一块。在项目里我约定了统一的返回格式{ code: 0, message: success, data: {} }所有接口都按这个格式返回。错误情景下code是非0值message描述错误信息。后端封装一个统一响应函数def ok(dataNone, messagesuccess): return JsonResponse({ code: 0, message: message, data: data }) def fail(messageerror, code1): return JsonResponse({ code: code, message: message, data: None })前端axios的响应拦截器里统一判断code的数值这样只要一层的逻辑就能处理所有异常分支。很多新手在开发接口时一个接口返回数组另一个接口直接返回对象前端代码里全是一个个的特判越到后面越难维护。7. 数据表设计和查询性能的实操建议7.1 核心表关系和字段规划美食分享系统最核心的几张表用户表、菜谱表、食材表、菜谱-食材关联表、评论表、收藏表、点赞表。用户表前面已经说过了这里重点看菜谱表class Food(models.Model): title models.CharField(max_length100) cover models.ImageField(upload_tocovers/) steps models.TextField() user models.ForeignKey(User, on_deletemodels.CASCADE, related_namefoods) category models.ForeignKey(Category, on_deletemodels.SET_NULL, nullTrue) view_count models.PositiveIntegerField(default0) created_at models.DateTimeField(auto_now_addTrue)创建索引的问题多念叨一句外键字段和经常查询的字段要加db_indexTrue默认情况下外键本身就会建索引但比如view_count按人气排序、created_at按时间排序这些场景也都值得建一下。数据量小的时候感知不到但等积攒了几万条菜谱数据之后索引差别的效果就明显出来了。7.2 减少N1查询的关键写法在前端展示菜谱列表的时候需要同时显示发布者头像和分类名称。如果代码这样写foods Food.objects.filter(status1).order_by(-created_at)[:20]那么当遍历这个列表每访问一个food的user或者category属性时django的ORM又会执行一次额外的数据库查询。这个就是N1查询问题列表渲染20条就会产生120条SQL。解决办法是用select_related和prefetch_relatedfoods Food.objects.filter(status1) \ .select_related(user, category) \ .prefetch_related(ingredients) \ .order_by(-created_at)[:20]外键关联用select_related多对多关联用prefetch_related。这样拿回列表之后访问关联对象不会再触发SQL查询。这两个方法看着是ORM的基础知识点但实际开发中能压掉一大半查询量。7.3 分页方案接口和前端配合好美食列表的分页我用的是最传统也最稳妥的页码分页。django REST framework自带的PageNumberPaginationclass FoodPagination(PageNumberPagination): page_size 12 page_size_query_param page_size max_page_size 50前端在滚动到底的时候把当前页码加1继续请求下一页数据。这里要注意的是返回数据里的total总数和current_page信息一定要从接口里带回来前端判断“没有更多数据”的条件就靠这些。8. 部署上线Linux服务器上从头跑通整个系统8.1 Django项目的线上启动方式在Linux服务器上部署django不能说用python manage.py runserver就直接应付过去那是开发环境的使用方式性能不够也不安全。我用的是gunicorn配合nginx的方式和flask推荐服务的部署保持一致的风格。先安装gunicorn然后在项目根目录创建一个gunicorn.conf.py配置文件workers 4 bind 0.0.0.0:8000 timeout 60 accesslog /var/log/food_project/gunicorn_access.log errorlog /var/log/food_project/gunicorn_error.log启动gunicorn -c gunicorn.conf.py food_project.wsgi:application8.2 nginx配置静态资源和反向代理前后端分离的部署逻辑是nginx监听80端口Vue构建产物dist目录作为静态文件直接由nginx托管同时把/api路径反向代理到django的8000端口把/recommend路径代理到flask的5001端口。nginx的关键配置server { listen 80; server_name your_domain.com; # Vue前端静态文件 root /var/www/food_frontend/dist; index index.html; location / { try_files $uri $uri/ /index.html; } # Django后端接口 location /api/ { proxy_pass http://127.0.0.1:8000; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } # Flask推荐服务 location /recommend { proxy_pass http://127.0.0.1:5001; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } # Django后台静态文件 location /static/ { alias /var/www/food_backend/static/; } # 媒体文件 location /media/ { alias /var/www/food_backend/media/; } }location /里那个try_files非常关键因为Vue路由是前端路由直接访问/food/3这样的路径时nginx服务器上并不存在这个文件必须让它回退到index.html然后交给前端路由处理。不写这个线上刷新任何一个二级页面都会404。8.3 Flask的独立部署和调用链路的可用性保障flask推荐服务的部署方式和django类似也用gunicorn。因为服务简单2个worker就够了。生产环境里django调用flask的接口我建议特别关注超时控制和降级逻辑前面已经给了代码示例这里再强调一下推荐模块挂掉不应该影响到核心的菜品浏览功能。8.4 数据库迁移和静态文件的坑在服务器上跑迁移命令的时候用这两句话是常规操作python manage.py makemigrations python manage.py migrate但是有个很容易踩的坑如果本地的数据库是SQLite服务器上换成了MySQL那本地生成的迁移文件在服务器上执行可能没问题可如果你在本地加了依赖特定数据库的字段比如JSONField在旧版本MySQL上可能不支持迁移的时候就会报错。所以生产环境数据库最好从第一天就用MySQL或者PostgreSQL不要到了部署阶段再切换临时切换的适配成本会让人崩溃。静态文件在部署的时候需要执行python manage.py collectstatic把所有app里的静态资源汇总到指定的STATIC_ROOT目录。settings.py里要配好STATIC_URL /static/ STATIC_ROOT BASE_DIR / staticfiles8.5 使用Systemd托管微服务的完整配置为了能开机自启以及崩溃自动重启用systemd管理django、flask、nginx这三个服务是比较标准的做法。新建一个/etc/systemd/system/food-backend.service[Unit] DescriptionFood Project Django Backend Afternetwork.target [Service] Userwww-data WorkingDirectory/var/www/food_backend ExecStart/var/www/food_backend/venv/bin/gunicorn -c gunicorn.conf.py food_project.wsgi:application Restartalways RestartSec3 [Install] WantedBymulti-user.target然后执行systemctl daemon-reload systemctl enable food-backend systemctl start food-backendflask推荐服务也照葫芦画瓢换个服务和执行路径。这套管理方式非常可靠服务挂了系统会在几秒内自动拉起来不用专门写守护脚本。9. 开发调试提速Pycharm和Vue DevTools的使用心得9.1 Pycharm里的Python路径和远程调试配置Pycharm里配置Python解释器要特别注意项目的虚拟环境。在Settings里选择Project Interpreter点Add Interpreter选Existing然后找到venv目录里的python.exe。这一步做对了Pycharm的Terminal和Run按钮都会自动用虚拟环境启动。调试django的时候直接在Pycharm里配置一个Django Server运行配置Debug模式可以直接在ORM查询处打断点然后查看完整的SQL语句和变量状态能省下非常多时间。DEBUG模式下config的断点命中完全无延迟这一点比打log方便太多。9.2 Vue DevTools检查前端组件和数据流前端的调试Vue DevTools是一件利器。装上浏览器插件之后Vue 3项目可以在插件里直观看到组件树、props、state以及pinia store里的所有数据。我做列表页的时候怀疑某个组件的状态更新逻辑有问题利用DevTools直接修改store里的数据页面实时变动很快就定位到了问题比反复加console.log效率高很多。9.3 前后端并行开发的调试姿势如果前后端要并行开发Vite的代理转发支持直接配置。后端同学把自己写的接口部署到测试环境前端把proxy的target指到测试环境地址两边开发互不阻塞。我比较推荐Master分支和Dev分支分开管理前端和后端各自在功能分支上开发再统一合并测试。10. 项目功能的扩展思路和优化空间10.1 从单一接口到推荐服务升级的路径现在推荐服务是比较基础的口味标签匹配将来如果用户行为数据积累多了可以升级成协同过滤算法或者直接用Python的机器学习库训练排序模型。因为flask已经独立成一个服务了算法替换这部分不会影响核心业务代码这是一个很好的演进基础。10.2 图片和静态资源上对象存储当用户量变大本地的/media目录会占用大量磁盘空间而且备份困难。把图片上传迁移到云对象存储是必然的方向。django里写一个自定义storage类或者用第三方插件切换完之后ImageField完全不用改业务代码无感知。10.3 引入消息队列处理热门菜谱排名如果后期要把“热门榜单”做实时的那就有必要引入Redis缓存加队列了。用户每次点开菜谱详情不直接写数据库而是把事件推送到Redis里异步任务定期把浏览量落库并更新排行榜。这样设计能明显降低数据库压力还能做出实时TopN的效果。不过作为一个美食分享项目我个人的建议是不要一开始就上太重的架构。先把基础功能做扎实跑起来等用户量和数据量真上来了再按需演进。过早引入复杂的中间件只会让开发效率变慢对早期项目来说是弊大于利。我在这次项目里最大的体会是一个项目中同时用django和flask并不代表技术栈混乱关键在于你愿不愿意事先想清楚“什么场景用哪个框架”。django管重逻辑的爽快和flask做轻量服务的灵活搭档好了是能实实在在提升开发效率的。另外不管是Python还是Vue环境配置这块真的不要怕麻烦一次性把虚拟环境、端口代理、状态管理这些地基打好后面开发起来才会顺畅。希望这篇内容能帮你少走一点弯路也欢迎你在实操中碰到问题的时候回来多交流。
返回列表