每年毕业设计高峰期,后台总有学弟学妹问我要“能打”的选题——最好技术栈全、有亮点、还能快速跑起来。今天分享的这套基于Python的家庭亲子在线购物服务系统,就是我从选题到答辩完整走下来的实战项目:后端Django提供稳定可靠的业务接口,前端Vue构建交互流畅的购物体验,再叠加可视化报表和数据分析模块,最后接入DeepSeek大模型Agent做智能导购。整个工程覆盖电商全链路,又能作为“AI+传统业务”的典型范例,无论你是准备毕设还是想积累项目经验,都值得先收藏。
这篇文章不聊虚的,直接从架构设计、数据库建模、核心业务代码,到AI能力接入、部署避坑一条龙讲透。全程用我实际踩过的坑和调通后的最终方案说话,你照着做就能少走弯路。
1. 项目整体设计与思路拆解
1.1 这个毕业设计到底做了什么
很多同学看到“家庭亲子在线购物服务系统”这种题目,第一反应就是“又一个淘宝克隆”。其实这种垂直电商系统和通用电商平台有本质区别:它的核心用户是家庭群体,购买决策往往不是一个人完成,而是“父母决策、孩子参与”的协同过程。
系统里我设计了家庭成员账号体系——一个家庭可以有多个成员账号,不同成员拥有不同权限和购物偏好。妈妈可以管理家庭采购清单,孩子可以在“心愿单”里添加想要的玩具或绘本,爸爸负责最终结算。这种模式让项目在业务逻辑上比普通电商系统更有辨识度,也更好写创新点。
整套系统的功能闭环包括:用户注册登录、商品分类浏览与搜索、购物车管理、订单生成与状态流转、商品收藏与评价、家庭采购清单共享、销售数据分析大屏、以及基于DeepSeek的智能导购助手。从用户端到管理端,从前台交互到后台统计,每一条链路都是完整的。
1.2 为什么选Django和Vue这套组合
技术选型是论文和答辩中必问的问题,这部分我帮你把思路理清楚。
后端选Django不是因为它“老”,而是因为它“稳”。Django自带Admin后台、ORM、认证系统、安全防护,这些内置能力对一个毕业设计来说实在太实用了。开发效率极高,不用从零搭建基础设施。尤其它的ORM对象关系映射,让我操作数据库时完全不用写原生SQL,这对快速迭代非常友好。
最关键的是,Django的REST Framework(DRF)生态非常成熟,写API接口效率极高。配合SimpleJWT做身份认证,前后端分离的开发模式非常顺畅。
前端选Vue的核心理由是渐进式框架、学习曲线平缓、生态完善。用Vue Router做页面路由、Pinia管理全局状态、Axios请求后端接口、ECharts绘制图表,这一套组合在社区里有大量现成方案可以借鉴。相比React全家桶,Vue更贴近国内开发者的使用习惯,遇到问题也更容易搜到解决方案。
1.3 功能模块如何划分
整个系统拆成两大端:用户端和管理端。
用户端面向家庭用户,包括:首页商品推荐、分类检索、商品详情、购物车、下单结算、订单管理、收藏夹、家庭共享清单、个人中心、智能导购对话窗口。
管理端面向系统运营人员,包括:商品上下架管理、库存调整、分类维护、订单处理、用户管理、数据统计大屏(销售趋势、品类占比、用户增长)、AI助手配置。
这套模块划分遵循“高内聚低耦合”原则,每个模块之间通过接口通信,后续想扩展促销模块、优惠券模块都很方便。
2. 技术栈选型与核心依赖解析
2.1 后端技术栈清单与版本选择
环境版本是毕业设计最容易踩坑的地方,版本不匹配会导致各种诡异问题。我实际调通的版本组合如下:
| 组件 | 版本 | 说明 |
|---|---|---|
| Python | 3.10+ | 建议用3.10或3.11,兼容性最好 |
| Django | 4.2 LTS | 长期维护版,稳定 |
| Django REST Framework | 3.14+ | 构建RESTful API |
| djangorestframework-simplejwt | 5.3+ | JWT用户认证 |
| django-cors-headers | 4.3+ | 解决跨域请求 |
| PyMySQL | 1.1+ | Python连接MySQL |
| Redis | 5.0+ | 缓存热点数据 |
| Celery | 5.3+ | 异步任务队列 |
| openai | 1.x | DeepSeek兼容OpenAI协议调用 |
这里有个非常实用的经验:DeepSeek的API兼容OpenAI格式,所以直接用openai库的OpenAI类,把base_url换成DeepSeek的地址就行,不用额外装复杂SDK。
2.2 前端技术栈与可视化方案
前端部分我用了标准Vue3全家桶:
- Vue 3+Vite构建工具,Vite相比Webpack启动和热更新速度简直天壤之别
- Vue Router 4做前端路由,配置懒加载优化首屏速度
- Pinia替代Vuex做全局状态管理,API更加简洁
- Element Plus作为UI组件库,表格、表单、弹窗、分页等组件开箱即用
- ECharts 5做数据可视化,折线图、柱状图、饼图、雷达图覆盖所有报表需求
有一个经验值得分享:ECharts官方文档里各种图表的配置项非常全,但毕业设计数据大屏不需要花哨的3D效果,把基础的折线图、柱状图、饼图用干净、配色统一的风格呈现,反而更符合系统设计的专业感。图表配色最好跟主站风格一致,从一套色板里取色,避免红绿蓝紫乱搭配。
2.3 可视化数据大屏的意义
指导老师通常最看重两个东西:业务闭环和数据价值。纯电商CRUD功能只体现了前者,而可视化数据分析模块恰好补上后者。
系统里我做了“家庭消费分析”和“商品销售分析”两个大屏页面。前者分析单个家庭的品类偏好、消费频次、成员贡献度;后者从全站维度分析销售趋势、热销商品、滞销库存。这些数据全部来自真实数据库的订单流水,经过聚合运算后在图表中呈现,这种“从数据到洞察”的过程,正是评审老师希望看到的能力展示。
3. 数据库设计与核心模型实现
3.1 用户与家庭体系的数据模型
数据库设计是电商系统的大动脉。最开始我设计的用户表只有username、password这种基本字段,但做到家庭成员共享清单功能时发现远远不够。
最终用户模块拆成了三张表:
# users/models.py class UserProfile(AbstractUser): """用户基础信息扩展""" avatar = models.CharField(max_length=255, blank=True, null=True) phone = models.CharField(max_length=20, blank=True, null=True) birthday = models.DateField(blank=True, null=True) gender = models.CharField(max_length=10, choices=[('M', '男'), ('F', '女'), ('U', '保密')], default='U') user_type = models.CharField(max_length=10, choices=[('parent', '家长'), ('child', '孩子')], default='parent') class Family(models.Model): """家庭组,一个用户可创建一个家庭并邀请成员""" name = models.CharField(max_length=50) creator = models.ForeignKey(UserProfile, on_delete=models.CASCADE, related_name='created_family') created_at = models.DateTimeField(auto_now_add=True) class FamilyMemberShip(models.Model): """家庭成员关系表,多对多关系加额外字段""" family = models.ForeignKey(Family, on_delete=models.CASCADE, related_name='memberships') user = models.ForeignKey(UserProfile, on_delete=models.CASCADE, related_name='family_memberships') role = models.CharField(max_length=20, choices=[('admin', '管理员'), ('member', '普通成员')], default='member') joined_at = models.DateTimeField(auto_now_add=True)我的设计思路是:UserProfile存用户基本信息,Family独立成表,FamilyMemberShip作为中间表存角色关系。这样后续扩展“家庭成员共享购物车”“家庭共同账单”时都有预留。实际开发中,共享购物车和共享清单就是这么顺带做出来的——查询某个家庭的所有成员,再取他们添加的商品即可。
3.2 商品与分类模型设计
商品表是电商系统的核心,需要覆盖前台展示、后台管理、搜索排序、库存变更等多维度需求。
我设计的Product表包含三十多个字段,这里列几个最关键的:
# goods/models.py class Category(BaseModel): """商品分类,支持多级""" name = models.CharField(max_length=50) parent = models.ForeignKey('self', on_delete=models.CASCADE, null=True, blank=True, verbose_name='上级分类') sort_order = models.IntegerField(default=0) class Product(BaseModel): """商品信息表""" category = models.ForeignKey(Category, on_delete=models.SET_NULL, null=True, related_name='products') name = models.CharField(max_length=200) subtitle = models.CharField(max_length=255, blank=True, default='') main_image = models.ImageField(upload_to='products/%Y/%m/', blank=True) price = models.DecimalField(max_digits=10, decimal_places=2, default=0) original_price = models.DecimalField(max_digits=10, decimal_places=2, default=0) stock = models.IntegerField(default=0) sales_count = models.IntegerField(default=0) is_active = models.BooleanField(default=True) detail_html = models.TextField(blank=True, default='')分类表使用自关联实现多级分类,应对“母婴用品>奶粉>一段”这种层级场景。商品状态通过is_active软删除而非物理删除,保证历史订单的商品信息可追溯。detail_html字段存富文本内容,这也是管理后台富文本编辑器首选方案,注意Django的XSS防御机制需要对该字段做安全清洗。
3.3 购物车与订单的关联表设计
订单相关的核心是购物车/订单/订单明细三张表。
购物车为什么不直接存数据库而用Redis?因为购物车高频读写,放在Redis里不仅速度快,还能保存未登录状态下的“临时购物车”。登录后将Redis中缓存合并到数据库,这一套逻辑做下来,答辩时也是个亮点。
订单表设计时要注意:订单总金额和订单明细中的商品快照信息必须冗余存储。商品价格调整后,历史订单仍要保持当时的成交价,不能动态去查商品当前价格。
# orders/models.py class Order(BaseModel): """订单主表""" order_no = models.CharField(max_length=64, unique=True, verbose_name='订单编号') user = models.ForeignKey('users.UserProfile', on_delete=models.CASCADE, related_name='orders') total_amount = models.DecimalField(max_digits=10, decimal_places=2, verbose_name='总金额') pay_amount = models.DecimalField(max_digits=10, decimal_places=2, verbose_name='实付金额') status = models.CharField(max_length=20, choices=ORDER_STATUS, default='pending', verbose_name='订单状态') receiver_name = models.CharField(max_length=50) receiver_phone = models.CharField(max_length=20) receiver_address = models.CharField(max_length=255) class OrderItem(BaseModel): """订单明细表""" order = models.ForeignKey(Order, on_delete=models.CASCADE, related_name='items') product = models.ForeignKey(Product, on_delete=models.SET_NULL, null=True) product_name = models.CharField(max_length=200, verbose_name='商品名称快照') product_image = models.CharField(max_length=255, verbose_name='商品图片快照') price = models.DecimalField(max_digits=10, decimal_places=2, verbose_name='成交单价快照') quantity = models.IntegerField(default=1, verbose_name='数量') total_price = models.DecimalField(max_digits=10, decimal_places=2, verbose_name='小计金额')这里Product用SET_NULL防止商品删除连带删除订单记录。订单状态机我定义为“待付款→待发货→待收货→已完成→已取消”,再加“售后处理中”状态。状态变更必须走统一接口,不允许直接改库,这是后面做数据分析时数据可信度的基本保障。
4. Django核心业务实现与重点功能拆解
4.1 项目结构与自定义配置
用Django创建的project我命名为family_shop,内部的apps按业务拆成了五个:users、goods、orders、analytics、ai_service。
family_shop/ ├── family_shop/ │ ├── settings.py # 核心配置 │ ├── urls.py # 总路由 │ └── __init__.py ├── apps/ │ ├── users/ # 用户与家庭 │ ├── goods/ # 商品与搜索 │ ├── orders/ # 购物车与订单 │ ├── analytics/ # 数据分析与报表 │ └── ai_service/ # DeepSeek智能助手 ├── utils/ # 通用工具函数 ├── scripts/ # 初始化脚本 └── manage.pysettings.py中有几个容易漏掉的配置项特别提醒一下:ALLOWED_HOSTS必须加上你的域名或IP,DEBUG在生产环境必须改成False,STATIC_ROOT和MEDIA_ROOT两个路径必须提前创建好目录并允许写入,否则上传商品图片时必报错。
4.2 JWT认证替代Session认证
前后端分离架构下Session方案不再适用,我使用SimpleJWT实现双Token认证:Access Token有效期2小时,Refresh Token有效期7天。前端在Axios拦截器中携带Authorization请求头,收到401状态码时自动用Refresh Token刷新,业务无感知。
# settings.py REST_FRAMEWORK = { 'DEFAULT_AUTHENTICATION_CLASSES': [ 'rest_framework_simplejwt.authentication.JWTAuthentication', ], 'DEFAULT_PAGINATION_CLASS': 'utils.pagination.StandardPagination', 'PAGE_SIZE': 10, } SIMPLE_JWT = { 'ACCESS_TOKEN_LIFETIME': timedelta(hours=2), 'REFRESH_TOKEN_LIFETIME': timedelta(days=7), 'ROTATE_REFRESH_TOKENS': True, # 刷新时自动更新refresh token 'BLACKLIST_AFTER_ROTATION': True, # 旧refresh进入黑名单 }ROTATE_REFRESH_TOKENS和BLACKLIST_AFTER_ROTATION这组配置强烈建议打开。虽然会增加一次刷新请求,但能有效防止Token泄露后的重放攻击,答辩时也可以作为安全设计的细节加分点。
4.3 商品搜索与筛选接口实现
为了让搜索“在大数据量下依然快”,我一开始天真地直接写ORM查询加模糊匹配。测试时数十万条数据,这种查询慢到每秒才几十次请求。
解决办法是引入Redis缓存:把搜索关键词当作key,商品列表JSON当作value,缓存5分钟。同时用拼音首字母索引辅助搜索“baby”这种英文关键词匹配中文商品。
# goods/views.py def search_products(request): keyword = request.GET.get('keyword', '') category_id = request.GET.get('category_id') sort = request.GET.get('sort', 'default') cache_key = f'search:{keyword}:{category_id}:{sort}:{page}' cached_data = cache.get(cache_key) if cached_data: return Response(cached_data) products = Product.objects.filter(is_active=True) if keyword: products = products.filter( Q(name__icontains=keyword) | Q(subtitle__icontains=keyword) ) if category_id: products = products.filter(category_id=category_id) # 排序处理 sort_map = { 'price_asc': 'price', 'price_desc': '-price', 'sales': '-sales_count', 'new': '-created_at', } if sort in sort_map: products = products.order_by(sort_map[sort]) # 分页后写入缓存 page = int(request.GET.get('page', 1)) paginator = Paginator(products, 20) page_data = paginator.get_page(page) # 序列化、返回,同时cache.set(cache_key, result, timeout=300)4.4 Redis缓存购物车
购物车是写入密集、并发高的数据,我把它放在Redis的Hash结构中。key是user_id,field是product_id,value是数量。为什么用Hash而不用String?因为Hash支持一次获取用户全部购物车项,而且可以单独修改某一个商品的数量,操作粒度刚刚好。
# orders/services.py import redis r = redis.Redis(host='localhost', port=6379, db=0) def add_to_cart(user_id, product_id, quantity): key = f'cart:{user_id}' r.hincrby(key, product_id, quantity) def get_cart(user_id): key = f'cart:{user_id}' return r.hgetall(key)下单结算时,从Redis读取购物车数据,校验库存和价格,生成订单后删除对应的购物车缓存。这套方案实现简单、性能好,答辩时还能讲出“为什么不用数据库存购物车”的工程考量。
5. Vue前端与数据可视化实现
5.1 前端页面架构与路由设计
Vue项目我使用Vite构建,src目录下的结构很清晰:views放页面组件,components放可复用组件,api放接口请求,stores放Pinia状态,router放路由配置。
两个关键路由配置经验分享:
// router/index.js { path: '/product/:id', name: 'ProductDetail', component: () => import('@/views/ProductDetail.vue'), meta: { title: '商品详情' } }, { path: '/analytics/family', name: 'FamilyAnalytics', component: () => import('@/views/analytics/FamilyAnalytics.vue'), meta: { requiresAuth: true, title: '家庭消费分析' } }路由懒加载让页面按需加载,首屏尺寸大幅减小。meta字段存标题和权限要求,前端路由守卫中做登录判断,这种模式逻辑集中、维护方便。
5.2 核心页面组件设计思路
商品列表页是整个系统的门面。顶部是分类Tab和搜索栏,左侧是筛选面板(品类、价格区间、品牌),中间是瀑布流商品卡片,底部是分页。筛选条件用Pinia统一管理,选择筛选条件后自动请求接口。
商品详情页的核心是“加购”和“立即购买”两个动作。加购后不跳转,而是弹出MiniCart组件,这个交互模式极大提升了购买转化率,实际用户反馈也非常好。
订单确认页需要注意的细节是收货地址的缓存与回填。地址放在localStorage,用户首次填写后自动记忆,下次直接带出。
5.3 ECharts实现数据可视化
数据分析模块是可视化主战场。我用ECharts实现了四个代表性图表,每个都有值得单独讲的细节。
折线图看销售趋势,用日期作为x轴数据。ECharts要求x轴数据是时间序列,后端返回时我用format将日期格式统一成web标准格式。
// FamilyAnalytics.vue 关键片段 const trendOption = { xAxis: { type: 'category', data: trendData.dates // ['2025-06-01', '2025-06-02', ...] }, yAxis: { type: 'value', name: '金额(元)' }, series: [{ name: '销售额', type: 'line', smooth: true, areaStyle: { color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [ { offset: 0, color: 'rgba(64, 158, 255, 0.5)' }, { offset: 1, color: 'rgba(64, 158, 255, 0.05)' } ]) }, data: trendData.values }] }平滑曲线加渐变面积,观感上比直折线专业很多。
柱状图看销售排名,展示前十热销商品。横坐标商品名过长时自动省略,用tooltip显示全称,这是非常实用的小技巧。
饼图看品类占比,配置中加radius数组调成环形图,视觉上比实心饼更清爽。图例用legend配置成滚动模式,分类多时依然能完整展示。
雷达图看家庭消费画像,五个维度分别是:母婴用品占比、食品生鲜占比、家居百货占比、图书玩具占比、消费频次指数。雷达图的坐标轴名和最大值配置要统一,否则五个维度的数值没有可比性。
5.4 数据大屏的布局技巧
大屏页面真正的难点不在图表本身,而在布局。多个图表如何自适应排布是常见卡点。
我的方案是CSS Grid布局,三行两列,每行高度按比例分配。外层容器用100vh撑满视口,内部图表用flex:1自适应。页面加轻微的rem响应式适配,调整窗口大小图表依然保持比例。
为了让数据图表联动,我还采用Pinia维护一组全局日期筛选条件。顶部日期选择器修改后,所有图表触发各自的接口请求并setOption。这套联动逻辑是大屏的核心亮点,实际操作中注意在切换日期时先清空旧数据,避免图表出现残影。
6. 数据分析模块与DeepSeek大模型Agent融合
6.1 数据来源与统计分析思路
很多同学以为数据分析模块就是加载静态CSV文件——这完全不够。我把数据分析建立在实际业务数据的实时聚合之上,通过Django ORM的annotate聚合查询实现。
# analytics/views.py from django.db.models import Sum, Count, F from django.db.models.functions import TruncMonth def sales_trend(request): """按月统计销售额""" orders = Order.objects.filter( status='completed', created_at__gte=start_date, created_at__lte=end_date ) trend = orders.annotate( month=TruncMonth('created_at') ).values('month').annotate( total=Sum('pay_amount'), count=Count('id') ).order_by('month') # 前八年不冗述,返回JsonResponseTruncMonth是Django内置的数据库函数,能把时间字段截断到月份粒度,再配合annotate做聚合。这一套是数据分析的标准范式,务必掌握。
6.2 数据大屏的联动分析
为了让数据大屏不只是一个“展示页”,我在分析模块做了三类联动:
时间维度联动:销售趋势图选择6月,其他所有图表都只展示6月数据。实现方式是用Pinia的state存储全局筛选条件,所有图表组件watch这个条件后重新请求数据。
品类维度联动:点击品类占比图中的“玩具”扇区,下方热销商品列表瞬间变为玩具类目排行。
用户维度联动:家庭消费分析中切换家庭成员,右侧雷达图和明细报表同步刷新。
这三个联动的代码模型是一样的,本质都是“全局状态变更→各组件响应式拉取数据→echarts实例setOption”。把这个模式吃透,数据大屏的进阶就通了。
一个重要的性能经验:接口聚合一次返回,而不是N个接口各查各的。我在大屏后端接口里一次性返回所有图表的JSON数据,前端解析后分别渲染。接口从8个减少到1个,首页打开速度从3秒降到1秒以内。评审老师看到这种细节,印象分立刻上来了。
6.3 DeepSeek Agent接入电商场景
这一部分是整个系统最大的亮点,也是最容易让答辩老师眼睛一亮的设计。DeepSeek在电商中的角色不只是一个对话机器人,而是一个Shopping Agent。
我的设计思路是:用户在商城里看到一个“智能导购”悬浮窗,点击后进入对话页面。用户可以用自然语言表达需求,比如“帮我推荐适合3岁宝宝的积木,预算200以内”,Agent收到问题后执行两步操作:
第一步,调用DeepSeek的NLP能力理解意图,提取出关键词:品类=玩具,年龄=3岁,预算=200。
核心实现代码:
# ai_service/services.py from openai import OpenAI client = OpenAI( api_key="你的DeepSeek API Key", base_url="https://api.deepseek.com" ) def parse_intent(message): response = client.chat.completions.create( model="deepseek-chat", messages=[ {"role": "system", "content": "你是一个电商导购助手。用户会输入购物需求," "你需要提取结构化参数," "输出JSON格式,包含category、price_max、price_min、keywords等字段。"}, {"role": "user", "content": message} ], response_format={"type": "json_object"} ) return json.loads(response.choices[0].message.content)第二步,拿到结构化参数后,代码去数据库查询真实的商品数据,然后把结果再交给DeepSeek生成自然语言的推荐说明。这个“大模型+数据库”的组合模式,业界叫Retrieval-Augmented Generation(RAG)。跟直接让大模型编答案的区别是,商品信息全部来自真实数据库,推荐内容真实可靠。
def shop_agent_reply(user_message, user_id=None): # 意图解析 intent = parse_intent(user_message) # 查询商品 products = Product.objects.filter( is_active=True, name__contains=intent.get('keywords', '') ).filter(price__lte=intent.get('price_max', 99999)) # 组装prompt product_desc = "\n".join( f"{p.name},价格{p.price}元,销量{p.sales_count}" for p in products[:5] ) prompt = f"""根据以下真实商品信息,为用户做个性化推荐。 用户需求:{user_message} 可选商品: {product_desc} 请用亲切友好的口吻推荐最匹配的3个商品,并说明推荐理由。 """ response = client.chat.completions.create( model="deepseek-chat", messages=[{"role": "user", "content": prompt}] ) return response.choices[0].message.content这套程序的精髓在于把大模型当“大脑”而不是“数据库”。大模型负责理解用户、生成文案、组织语言,真正的事实数据还是从业务系统里来。这样既发挥了AI的交互优势,又保证了推荐结果的准确性。
6.4 Agent除了推荐还能做什么
既然接了Agent,就不只是做一个推荐功能。我在系统里又扩展了三个实用场景:
场景一:智能育儿问答。用户问“宝宝咳嗽可以吃什么辅食”,Agent先用预设的医学提示词做安全限制,再结合商品库推荐相关辅食商品。注意这里加一层安全兜底很重要,建议语料中注明“如有严重症状请及时就医”,避免合规风险。
场景二:家庭采购清单智能生成。用户输入“这周日家庭烧烤需要准备什么”,DeepSeek生成一份清单,系统自动匹配商品库中对应商品并加入购物车。这一步展示的是Agent的“行动力”——它不只是说话,还能调用系统功能完成真实任务。
场景三:订单状态查询。用户说“我的奶粉订单到哪了”,Agent先调用后端接口查用户最近的订单状态,再把结果用自然语言答复。这是典型的工具调用(Function Calling)模式,大模型负责判断该调用什么函数,系统负责执行函数并返回结果。
7. 部署上线与常见问题排查
7.1 本地开发环境搭建完整流程
这套系统部署分为三步走,每一步都给出可以直接复制的命令。
第一步:后端环境初始化
# 创建虚拟环境 python -m venv venv source venv/bin/activate # Windows使用 venv\Scripts\activate # 安装依赖 pip install -r requirements.txt # 初始化数据库 python manage.py makemigrations python manage.py migrate # 创建超级管理员 python manage.py createsuperuser # 启动开发服务器 python manage.py runserver 0.0.0.0:8000第二步:前端依赖安装与运行
cd frontend npm install npm run dev第三步:启动缓存与异步任务
# 启动Redis(macOS用brew,linux用systemctl) redis-server /usr/local/etc/redis.conf # 启动Celery Worker(处理异步任务,如订单超时自动关闭) celery -A family_shop worker -l info7.2 跨域与页面空白问题
前后端分离的第一个大坑就是跨域。前端运行在localhost:5173,后端在localhost:8000,端口不同,浏览器直接拦截请求。
解决跨域用django-cors-headers:
# settings.py CORS_ALLOWED_ORIGINS = [ "http://localhost:5173", "http://127.0.0.1:5173", ] CORS_ALLOW_CREDENTIALS = True页面空白问题多半是路由配置错误或接口404。用Vue DevTools检查路由,在Network面板看接口返回值。一定要开启Vue DevTools插件,这在调试阶段能救你无数次。
7.3 高频报错实录与解决手册
把我在开发过程中遇到的高频报错整理成速查表,你遇到类似问题直接对照处理:
| 报错信息 | 原因 | 解决办法 |
|---|---|---|
| ImproperlyConfigured: ... SECRET_KEY | settings中缺少密钥配置 | 用django.core.management.utils.get_random_secret_key生成 |
| 1146 (42S02): Table doesn't exist | 迁移未执行 | 执行migrate命令,注意迁移顺序 |
| Failed to load module script | Vite构建路径问题 | 设置base: './'相对路径 |
| 400 Bad Request: JWT token无效 | Token过期或格式错误 | 检查Axios拦截器请求头携带方式 |
| (2003, Can't connect to MySQL) | MySQL服务未启动 | 启动MySQL服务并检查配置 |
| 502 Bad Gateway | Nginx代理配置错误 | 检查upstream地址和端口 |
| Module not found: 'crypto' (Node.js新版本) | webpack兼容性问题 | package.json中添加polyfill相关依赖 |
| Error: listen EADDRINUSE: address already in use :8000 | 端口被占用 | 换端口或终止占用进程 |
有个印象最深的坑是Django的ImageField上传图片后访问不到。检查后发现MEDIA_URL和MEDIA_ROOT配置正确,但主urls.py里缺少静态文件服务路由。解决方法是必须在开发模式下加上static()路由:
from django.conf import settings from django.conf.urls.static import static urlpatterns = [ # ... ] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)7.4 部署到云服务器的经验
毕设答辩通常需要在线演示,本地跑通还不够,我建议部署到云服务器。核心是用Gunicorn + Nginx这套经典组合。
# 安装gunicorn pip install gunicorn # 启动后端服务,绑定8000端口 gunicorn family_shop.wsgi:application -w 4 -b 0.0.0.0:8000 --timeout 60Nginx反向代理配置注意两点:一是location /static/指向Django的STATIC_ROOT目录,二是前端打包后的dist目录作为Nginx的root指向。
server { listen 80; server_name 你的域名或IP; # 前端页面 root /home/ubuntu/family_shop/frontend/dist; index index.html; # API反向代理到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; } # 静态文件 location /static/ { alias /home/ubuntu/family_shop/staticfiles/; } location /media/ { alias /home/ubuntu/family_shop/media/; } }部署中有个非常容易踩的坑:打包前端项目前,要把API请求地址改成云服务器的公网域名或IP。我在.env.local里配置VITE_API_BASE_URL,打包时区分开发环境和生产环境。如果忘记改,会出现线上页面打不开、接口全挂的尴尬情况。
8. 毕设答辩准备的几点实战心得
8.1 演示用例要提前设计好
很多同学答辩时临时演示,页面数据是空的,或是操作半天才展示一个功能,效果极差。我的建议是提前构造“演示剧本”:用真实数据填充数据库,准备一个包含10个以上商品的分类、6个以上订单、购买行为跨3个月的演示账号。演示时按“浏览商品→加入购物车→结算下单→查看数据分析大屏→AI推荐商品”这个顺序走,逻辑顺畅又有说服力。
8.2 从“工具人”到“决策者”的表述升级
答辩时不要只说“我用了Django和Vue实现功能”,要用“为什么选这个方案、不选另一个方案”的角度回答。比如“为什么购物车用Redis而不是MySQL”,答“Redis支持Hash结构,操作单个商品数量比关系型数据库更高效,且能承担高并发写入”,这就是技术决策能力的体现。
8.3 扩展方向预留
毕设不是交付就结束,扩展方向也是加分项。这块系统我已经预留了三个扩展点:第一是接入支付网关(支付宝沙箱)完成真实支付闭环;第二是推荐算法从规则升级为协同过滤或点击率预估模型;第三是DeepSeek Agent介入售后客服,处理退款和物流咨询。你把扩展方向说清楚,老师会觉得你的系统有完整的持续演进思路。
最后分享一个我个人多次踩坑后的体会:做这种全栈项目,最大的阻碍不是功能复杂,而是“串起来”的过程。后端接口都通、前端页面都跑,但联调时Auth认证失效、跨域拦截、字段命名不一致,这些琐碎问题反而最耗时间。所以我的建议是,一定不要最后一个星期才开始写代码,数据库设计和接口契约尽量在前两周就定下来,后面只是照着实现而已。这个项目从立项到能完整演示,我实际花了大概四周,每天两三个小时,希望你们能比我更从容。