每年到了三四月份,总有学弟学妹来找我看毕业设计,问得最多的就是“学长,有没有一套完整的、能直接跑起来的系统源码”。说实话,网上资源确实多,但能真正让你搞懂原理、能通过答辩、还能说得清每一行代码为什么这么写的,少之又少。今天我就把之前指导过的一个“基于Python+Vue的母婴商城管理系统”完整拆开来讲,从技术选型思路、数据库设计、前后端核心实现,到环境配置和运行步骤,全部过一遍。这个项目我实测跑通过,源码结构清晰,非常适合计算机科学与技术专业的大四学生拿来做毕业设计,或者作为后端开发入门的练手项目。
为什么选母婴商城这个方向?原因很简单:电商系统是经典业务模型,涵盖用户、商品、购物车、订单、支付等多个核心模块,业务逻辑完整度高。而母婴商品的特征明显,比如年龄段分类、安全等级、适用人群标签等,这些都可以在字段设计上体现出来,比做那种空泛的“图书管理”要有说服力得多。技术栈上,Python做后端开发效率高,Vue做前后端分离的前端体验好,这两个组合刚好覆盖了面试官最爱问的RESTful API、跨域、状态管理、路由守卫这些知识点。
1. 项目整体设计与技术选型思路
1.1 技术栈选型:为什么是Python + Vue 而不是别的组合
在给毕业设计做技术选型时,我一直强调一个原则:不要追新,要追稳。很多同学一上来就问“学长,用Spring Boot行不行”“用React行不行”,我的回答是行,但你得先想清楚你能不能在规定时间内把它跑通并讲明白。
Python这边,Django和Flask是最常见的两个选择。母婴商城这种业务相对完整的项目,我更推荐Django,因为它自带Admin后台、ORM、认证体系和迁移工具,像用户权限管理、数据库建表这些琐碎的活儿能省掉一大半。有些同学觉得Django太重了,想用Flask体现自己的掌控力,但毕业设计的核心是“完整能用+你能讲清楚”,而不是“框架特别轻量”。Django的ORM写起来非常直观,比如查“所有价格在100以上的奶粉”就是一条Product.objects.filter(category__name='奶粉', price__gt=100),从数据库到Python对象的映射几乎是零成本理解。
前端这边,Vue在国内的生态成熟度远超其他框架。Element UI(Vue 2)或Element Plus(Vue 3)组件库提供了现成的表格、表单、弹窗、分页组件,做后台管理界面就像是搭乐高。而且Vue的教程数量多、社区答疑快,遇到问题一搜就有答案,这对毕设周期紧的同学来说是巨大的优势。
1.2 前后端分离架构的核心思考
选择前后端分离架构,不是因为它“流行”,而是它有几个实打实的好处。第一,开发可以并行——你不需要等后端接口写完才能写前端页面,只要提前约定好接口返回的JSON结构,两边同时开工。第二,部署灵活——后端用Django跑在8000端口,前端用Vue开发服务器跑在8080端口,开发时通过代理转发来解决联调问题,这个过程中你自然会理解什么是跨域、什么是代理、什么是中间件。第三,答辩加分——当老师问你“为什么前后端要分离”时,你可以从“前后端关注点分离、团队协作效率、部署扩展性”三个角度回答,这种深度思考是非常加分的。
整个项目的前后端交互逻辑是这样的:Vue页面通过axios发起HTTP请求到Django后端的RESTful API,Django接收请求后通过ORM操作MySQL数据库,处理完业务逻辑后返回统一的JSON格式数据。整个过程是无状态的,后端不记录用户登录状态,而是通过JWT(JSON Web Token)来验证用户身份。这个设计在答辩时也是一个很好的亮点。
2. 数据库设计与核心功能模块拆解
2.1 六张核心表的字段设计思路
母婴商城的数据库设计是整个系统的地基,地基不牢,后面写多少代码都是白搭。我用Django的ORM模型来定义数据表,迁移时会自动在MySQL中建表,不需要手写SQL。下面是我实际项目中用的核心表结构,你可以直接参考:
用户表(User):继承Django自带的AbstractUser,额外增加phone(手机号)、avatar(头像)、gender(性别)、birth_date(宝宝的出生日期,用于推荐适龄商品)。为什么要加birth_date?这是母婴商城的业务特色,不同月龄的宝宝需要不同的商品,这个字段在做个性化推荐时会非常有用。
商品分类表(Category):字段包括name(分类名)、parent(父级分类,用自关联实现二级分类)、sort_order(排序权重)。母婴商品通常是二级分类,比如“奶粉”下面分“1段”“2段”“3段”,“纸尿裤”下面分“NB码”“S码”“M码”,自关联一张表就能搞定,不需要为每个层级建一张表。
商品表(Product):字段包括name(商品名)、category(外键关联分类表)、price(价格,用DecimalField而不是FloatField,避免精度问题)、stock(库存)、sales(销量)、image(主图URL)、detail(富文本详情)、is_active(上下架状态)、created_at(创建时间)。这里有个细节:价格一定要用DecimalField(max_digits=10, decimal_places=2),用Float的话会出现 0.1+0.2!=0.3 这种诡异问题,答辩时如果被问到精度问题答不上来会很尴尬。
购物车表(Cart):字段包括user(外键用户)、product(外键商品)、quantity(数量)、selected(是否勾选)。购物车表不用存商品快照,因为购物车是临时数据,用户随时可能改数量或删除。但订单里的商品信息必须存快照,因为商品价格会变。
订单表(Order):字段包括order_no(订单编号,用时间戳+随机数生成)、user(外键用户)、total_amount(总金额)、status(订单状态:待付款/已付款/已发货/已完成/已取消)、address(收货地址快照)、created_at。
订单明细表(OrderItem):字段包括order(外键订单)、product_name(商品名快照)、product_image(商品图快照)、price(成交单价快照)、quantity(数量)、subtotal(小计金额)。
注意:订单表和订单明细表为什么要拆成两张表?因为一个订单包含多个商品,如果不拆表,就只能把多个商品塞到一个字段里(比如用逗号拼接),查询时根本没法分析数据。拆开后,一张表负责订单整体信息,一张表负责订单里的每个商品,通过外键关联。这就是数据库设计中的“范式化”,答辩时老师很容易问到这个点。
2.2 母婴商城的业务特色如何体现在设计中
做完基础表之后,就要考虑母婴商城和其他电商的区别了。我在这套系统里加了两个特色维度,都是答辩时的加分项。
第一个是适龄推荐。通过用户在User表里存的宝宝出生日期,后端可以计算出宝宝当前月龄,在首页接口中按“0-6个月”“6-12个月”“1-3岁”的区间过滤商品。实现方式是在商品表加一个suitable_age字段,存的是JSON格式,比如 {"min": 6, "max": 12},查询时用ORM的过滤就能筛出合适商品。这个功能实现成本不高,但能明显体现出“你做的是母婴商城,而不是随便套了一个电商模板”。
第二个是安全属性标签。母婴用品的家长最在意什么?安全。所以我在商品表里设计了is_organic(有机认证)、is_imported(进口商品)、safety_level(安全等级:A/B/C)这几个布尔和选择字段。商品列表中可以通过标签筛选,商品详情页会醒目地展示这些标识。从产品角度讲,这符合母婴用户的购买决策心理;从技术角度讲,这只是几个字段+一个筛选接口的事,投入产出比极高。
2.3 订单状态机的流转逻辑
订单系统的核心不是“存数据”,而是“状态流转”。我用了最简单也最清晰的方案:用一个整数字段表示订单状态,0代表待付款、1代表已付款、2代表已发货、3代表已完成、4代表已取消。前端根据不同的状态展示不同的操作按钮——待付款显示“去支付”和“取消订单”,已付款显示“申请退款”,已发货显示“确认收货”。
后端接口要做的是状态校验,比如只有状态为0的订单才能执行支付操作,只有状态为2的订单才能确认收货。我在Django的视图函数里先查订单,再判断状态,顺序不能反。如果你用Django REST Framework,还可以在Serializer里做validated校验,但毕业设计的话直接在视图里写 if 判断就够了,反而更好讲清楚。
3. 后端核心接口设计与实现细节
3.1 基于Django REST Framework的接口分层
项目后端我使用的是Django REST Framework(DRF),这是一个基于Django的第三方应用,专门用来快速构建RESTful API。DRF的核心优势在于:序列化器(Serializer)可以自动将Python对象转为JSON,视图集(ViewSet) + 路由器(Router)可以自动生成URL路由,大大减少了重复代码。
整个后端的目录结构是这样组织的:
backend/ ├── manage.py ├── requirements.txt ├── momshop/ # 项目配置目录 │ ├── settings.py │ ├── urls.py │ └── wsgi.py ├── apps/ │ ├── users/ # 用户模块 │ │ ├── models.py │ │ ├── views.py │ │ ├── serializers.py │ │ └── urls.py │ ├── goods/ # 商品模块 │ ├── trade/ # 购物车与订单模块 │ └── utils/ # 公共工具(JWT、分页、响应格式)模块化分应用是Django的最佳实践,每个业务模块独立成文件夹,互不干扰。我在utils里封装了统一的响应格式,所有接口返回的数据结构都是{"code": 200, "data": {...}, "message": "success"},前端拿到这个结构后统一处理,不需要每个接口单独判断。虽然多写了一点代码,但会让整个项目显得非常规范。
3.2 用户认证:JWT 的完整接入流程
用户认证我选了JWT而不是Django自带的Session。原因有两点:一是前后端分离架构下,Session需要额外处理跨域Cookie的问题,而JWT是无状态的,前端把Token存在localStorage里,每次请求在header里带上Authorization: Bearer <token>即可;二是JWT的过期机制非常清晰,Token里携带了用户ID和过期时间,后端只需验证签名就能确定用户身份,不需要查数据库。
Django中接入JWT,我用的是djangorestframework-simplejwt这个库。配置非常简单,在settings.py里加一段:
REST_FRAMEWORK = { 'DEFAULT_AUTHENTICATION_CLASSES': [ 'rest_framework_simplejwt.authentication.JWTAuthentication', ], 'DEFAULT_PERMISSION_CLASSES': [ 'rest_framework.permissions.IsAuthenticated', ], } import datetime SIMPLE_JWT = { 'ACCESS_TOKEN_LIFETIME': datetime.timedelta(days=7), 'REFRESH_TOKEN_LIFETIME': datetime.timedelta(days=30), }登录接口在DRF里甚至不需要自己写视图,直接用SimpleJWT自带的TokenObtainPairView就能返回access和refresh两个Token。但为了和前端配合得更顺畅,我通常会重写这个视图,让它在返回Token的同时把用户的基本信息(用户名、头像、宝宝生日等)也一并返回,省得前端再调一次用户信息接口。
3.3 首页和商品列表的分页与筛选
商品列表接口是高频访问接口,需要注意两个问题:性能和筛选维度。性能方面,我使用DRF的分页类,每页返回12条商品数据,前端滚动加载时传入page参数。DRF的分页响应格式默认是{"count": 总数, "next": 下一页链接, "previous": 上一页链接, "results": 数据列表},前端配合Element UI的el-pagination组件,几乎可以无缝对接。
筛选维度包括关键词搜索、分类筛选、价格区间、排序方式。我在视图里用request.query_params逐个获取参数并拼接到查询集上:
def get_queryset(self): queryset = Product.objects.filter(is_active=True) keyword = self.request.query_params.get('keyword') category_id = self.request.query_params.get('category_id') min_price = self.request.query_params.get('min_price') max_price = self.request.query_params.get('max_price') ordering = self.request.query_params.get('ordering', 'default') if keyword: queryset = queryset.filter(name__icontains=keyword) if category_id: queryset = queryset.filter(category_id=category_id) if min_price: queryset = queryset.filter(price__gte=min_price) if max_price: queryset = queryset.filter(price__lte=max_price) if ordering == 'sales': queryset = queryset.order_by('-sales') elif ordering == 'price_asc': queryset = queryset.order_by('price') elif ordering == 'price_desc': queryset = queryset.order_by('-price') return queryset这段代码的每一个if都有对应的前端交互。比如价格筛选,前端是两个输入框+一个“确定”按钮;排序方式是点击下拉菜单切换ordering参数。你把这些逻辑搞清楚了,答辩时被问到“筛选功能怎么做”,就可以直接说“通过查询参数动态拼接QuerySet”,面试官一听就明白你是真做过而不是抄的。
3.4 购物车与订单的联动实现
购物车和订单的联动是电商业务的核心流程,也是技术上最容易出Bug的地方。我的实现思路是:
- 前端在商品详情页点击“加入购物车”,携带
product_id和quantity调用POST /api/cart/。 - 后端判断商品是否已在购物车中,如果已存在则累加数量,不存在则新增记录。
- 用户在购物车页面勾选商品后点击“去结算”,前端把选中的购物车记录ID列表传给后端。
- 后端
POST /api/orders/接收购物车ID列表、收货地址ID,先锁定购物车记录并校验库存,计算总金额,创建订单和订单明细,最后清空对应的购物车记录。
这里有一个关键点:创建订单和扣减库存必须放在同一个事务里。如果先创建订单再扣库存,万一扣库存失败,就会产生“订单有了但没扣库存”的数据不一致问题。Django里用transaction.atomic()装饰器就能解决:
from django.db import transaction @transaction.atomic def create_order(request): # 1. 校验参数 # 2. 查询购物车记录 # 3. 循环检查库存 # 4. 创建订单和明细 # 5. 扣减库存(product.stock -= quantity) # 6. 清空购物车 pass事务处理这块我在答辩时被老师重点追问过——为什么需要事务?原因是并发场景下如果两个用户同时买最后一个库存商品,不加事务就可能出现超卖。虽然毕设里没有真正的高并发,但在代码中体现这个意识,评委的好感度会明显提升。
4. 前端Vue项目的架构与页面实现
4.1 Vue项目目录结构与路由设计
前端这边我用的是Vue 2 + Vue Router 3 + Vuex 3 + Element UI的组合。为什么不直接上Vue 3?因为Element UI目前最成熟的版本还是配合Vue 2,而且能找到的教程和踩坑记录最多。如果你对自己的前端水平有信心,也可以选择Vue 3 + Element Plus + Pinia,但那是另一个故事了,为了稳妥我已Vue 2作为示例。
前端目录结构:
frontend/ ├── public/ │ └── index.html ├── src/ │ ├── main.js # 入口文件,注册Vue实例 │ ├── App.vue # 根组件 │ ├── router/ │ │ └── index.js # 路由配置 │ ├── store/ │ │ └── index.js # Vuex状态管理 │ ├── api/ # 封装axios请求 │ │ ├── request.js # axios实例 + 拦截器 │ │ ├── product.js # 商品相关接口 │ │ ├── cart.js # 购物车相关接口 │ │ └── order.js # 订单相关接口 │ ├── views/ # 页面组件 │ │ ├── Home.vue │ │ ├── ProductList.vue │ │ ├── ProductDetail.vue │ │ ├── Cart.vue │ │ ├── Checkout.vue │ │ ├── OrderList.vue │ │ ├── Login.vue │ │ ├── Register.vue │ │ └── admin/ │ │ ├── Dashboard.vue │ │ ├── ProductManage.vue │ │ ├── OrderManage.vue │ │ └── UserManage.vue │ ├── components/ # 公共组件 │ └── utils/ └── package.json路由设计分为两块:用户端和管理端。用户端路由包括首页、商品列表、商品详情、购物车、订单、登录注册,管理端路由包括数据概览、商品管理、订单管理、用户管理。在路由配置中,我用了路由守卫来实现登录校验和权限控制:
router.beforeEach((to, from, next) => { const token = localStorage.getItem('token') if (to.path.startsWith('/admin') && !token) { next('/login') } else if (to.path === '/login' && token) { next('/') } else { next() } })这段代码很简单,但它是前端的核心安全防线。在没有路由守卫的情况下,用户直接访问/admin/product也能看到后台页面,虽然拿不到数据,但体验上就不完整。有了守卫后,未登录的用户会被强制弹回登录页,这种细节在演示时很加分。
4.2 基于axios的请求封装与拦截器
前端所有接口请求我都统一封装在api/request.js里,而不是每个页面直接调axios。这样做最大的好处是:当接口地址或公共参数发生变化时,只改一个文件就行。
具体实现是用axios创建实例,设置基础URL为/api,然后在请求拦截器里给每个请求带上Token:
import axios from 'axios' import { Message } from 'element-ui' const service = axios.create({ baseURL: '/api', timeout: 10000 }) service.interceptors.request.use(config => { const token = localStorage.getItem('token') if (token) { config.headers['Authorization'] = 'Bearer ' + token } return config }) service.interceptors.response.use( response => { const res = response.data if (res.code !== 200) { Message.error(res.message || '请求失败') return Promise.reject(new Error(res.message)) } return res.data }, error => { if (error.response && error.response.status === 401) { localStorage.removeItem('token') window.location.href = '/login' } Message.error(error.message || '网络异常') return Promise.reject(error) } ) export default service这段代码里有几个细节值得注意。第一,响应拦截器返回的是res.data而非res,这样业务代码里直接productList.value = await getProductList()就能拿到数据,不需要每页都写.data.data这种重复嵌套。第二,对401状态的统一处理,Token过期时自动清空并跳回登录页,这是很多毕设项目容易忽略的点。
4.3 前端核心页面实现
首页做了两个模块:顶部的分类导航和下面的商品推荐流。分类导航的数据来源是接口GET /api/category/,返回一级分类列表,每个一级分类下面挂二级分类。商品推荐流则是通过商品接口的is_recommend=true参数来过滤,展示为卡片列表。
商品详情页是交互最多的地方。左侧是商品图片,右侧是价格、库存、销量、适龄标签和数量选择器。点击“加入购物车”按钮调用购物车接口,同时弹出成功提示。我在图片展示上用了Element UI的el-carousel轮播组件,支持多图切换,视觉效果比固定一张图好很多。
购物车页的核心是“编辑模式”和“结算模式”的切换。编辑模式下可以修改数量、删除商品,结算模式下勾选商品并展示总价。Vue的computed属性非常适合算总价——只要购物车列表是响应式的,总价就会自动更新,不需要手动刷新。这是Vue和原生JS比最舒服的一点。
订单结算页需要用户选择收货地址并确认商品清单。我预留了地址管理的接口和前端页面,但因为篇幅限制,在MVP版本里先用了默认地址。如果你想扩展,加一个“地址管理”模块非常快,就是一张address表加增删改查接口的事。
管理端页面用Element UI的el-container做侧边栏+顶栏的布局。商品管理页面有表格展示所有商品,支持搜索、新增、编辑、上架下架操作。通常这一步会直接调用同一个商品接口,只是路径和方法不同。
5. 从零到一:完整环境配置与运行步骤
5.1 本地开发环境的前置准备
在跑项目之前,需要确保你的电脑已经装好了以下环境。每一步我都会说明“为什么需要”和“怎么验证装好了”。
Python 3.8+:项目后端是基于Python开发的,推荐使用3.8~3.10版本,不要用最新的3.12,有些依赖包可能还没有兼容。安装完后在终端输入python --version,能正常输出版本号就说明没问题。Windows用户注意勾选安装器上的“Add Python to PATH”选项,否则后面在终端敲python会提示找不到命令。
MySQL 5.7或8.0:数据库使用MySQL。安装MySQL的教程网上有很多,这里提醒两点:一是记住你的root密码,后续配置数据库连接要用;二是确保MySQL服务已经启动,Windows可以通过“服务”管理器查看,macOS可以用brew services start mysql。
Node.js 14+:前端运行需要Node环境,npm是Node自带的包管理工具。安装完成后终端输入node -v和npm -v,两个都能正常输出版本号即可。
开发工具:后端推荐PyCharm或VS Code+Python插件,前端推荐VS Code+Vetur插件。如果你不是重度IDE用户,直接全程用VS Code就够。
5.2 后端运行的完整步骤
后端启动步骤如下:
第一步,创建虚拟环境并激活。我强烈建议用虚拟环境,每个项目一套独立的Python依赖,不会互相污染。在backend目录下执行:
python -m venv venvWindows下激活虚拟环境:
venv\Scripts\activatemacOS/Linux下激活虚拟环境:
source venv/bin/activate激活成功后,终端最前面会多一个(venv)前缀,说明已经进入虚拟环境。
第二步,安装项目依赖。项目根目录的requirements.txt文件里列出了所有第三方库,直接执行:
pip install -r requirements.txt主要的依赖包括Django、djangorestframework、djangorestframework-simplejwt、django-cors-headers、PyMySQL、Pillow。Pillow是处理图片上传的库,如果没有它,商品图片上传功能会报错。
第三步,配置数据库连接。在backend/momshop/settings.py中找到DATABASES配置,修改为你的MySQL账号密码和数据库名:
DATABASES = { 'default': { 'ENGINE': 'django.db.backends.mysql', 'NAME': 'momshop', 'USER': 'root', 'PASSWORD': '你的数据库密码', 'HOST': '127.0.0.1', 'PORT': '3306', } }第四步,生成数据库表并创建管理员账号。先在MySQL里手动创建数据库:
CREATE DATABASE momshop DEFAULT CHARACTER SET utf8mb4;然后在终端执行:
python manage.py makemigrations python manage.py migrate python manage.py createsuperusermakemigrations会根据models.py生成迁移脚本,migrate把迁移脚本应用到数据库,createsuperuser创建管理员账号用于登录Django Admin后台。
第五步,启动后端服务:
python manage.py runserver看到 “Starting development server at http://127.0.0.1:8000/” 就说明后端跑起来了。这时在浏览器访问 http://127.0.0.1:8000/admin/ 可以用管理员账号登录Django自带的后台,直接在这个后台里手动添加商品分类和商品数据,相当于一个简化版的后台管理。
5.3 前端运行的完整步骤
前端启动步骤如下:
第一步,安装依赖。在frontend目录下执行:
npm installnpm install会读取 package.json 里的依赖列表并下载安装到 node_modules 文件夹里。因为依赖包数量较多,这一步可能要花几分钟。如果速度极慢,可以设置npm为国内镜像源:
npm config set registry https://registry.npmmirror.com第二步,配置后端接口代理。在vue.config.js(或vite.config.js,取决于你用的脚手架)里配置devServer的代理,把前端的/api请求转发到后端的8000端口,解决开发环境下的跨域问题:
module.exports = { devServer: { port: 8080, proxy: { '/api': { target: 'http://127.0.0.1:8000', changeOrigin: true } } } }这个代理的原理是:你前端代码里请求的地址是
/api/product/list,浏览器看到的请求地址还是 8080 端口,但devServer会在服务端把这个请求转发到 8000 端口的Django服务。这样浏览器的同源策略就不会拦截,因为“表面上”所有请求都发给了同一个8080端口。
第三步,启动前端开发服务器:
npm run serve看到 “App running at: http://localhost:8080/” 说明前端启动成功。浏览器访问 http://localhost:8080 即可看到商城首页。
5.4 数据初始化的快捷方案
为了让系统在演示时有商品可看,我准备了两种数据初始化方式。第一种是用Django的 fixture 机制,在终端执行:
python manage.py loaddata initial_data.jsoninitial_data.json里定义好了分类数据和商品数据,执行后数据库会自动填充。第二种是直接在Django Admin后台手动添加,操作逻辑和普通后台一样。我强烈建议你掌握第二种方式,因为答辩时老师经常问“你们后台是怎么管理的”,你能现场登录Admin页面演示添加商品,说服力会强很多。
如果你想让首页商品图更真实,可以找一些母婴商品的免费图库图片,把URL填到商品的image字段里,或者通过Admin后台直接上传图片文件,效果会更好。
6. 运行时常见问题与排查技巧实录
6.1 常见运行问题速查表
我在帮学弟学妹调试这个项目时,整理了一份高频问题清单。你在跑的过程中如果遇到问题,先按这个表逐一排查:
| 问题现象 | 可能原因 | 解决方法 |
|---|---|---|
启动后端报ModuleNotFoundError: No module named 'pymysql' | 依赖没装全 | 执行pip install -r requirements.txt |
迁移时报Access denied for user 'root'@'localhost' | MySQL密码错误或权限不足 | 检查settings.py中的密码和账号权限 |
迁移时报Can't connect to MySQL server | MySQL服务未启动 | 启动MySQL服务,Windows在服务管理器启动,macOS用brew services start mysql |
前端npm run serve报Error: Cannot find module '../lib/utils/PM2' | npm缓存损坏或node_modules异常 | 删除node_modules文件夹后重新npm install |
前端页面能打开但接口报Network Error | 代理未配置或后端未启动 | 确认后端运行在8000端口,检查vue.config.js代理配置 |
登录接口返回401 Unauthorized | Token缺失或过期 | 清空localStorage,重新登录获取新Token |
| 商品图片不显示 | 图片URL是外链但未拼接域名 | 在Django的settings.py中配置MEDIA_URL和STATIC_URL |
| 注册接口提示验证码错误 | 未实现验证码但前端校验了 | 前端注释掉验证码校验逻辑,或后端增加验证码接口 |
6.2 踩过最深的坑:数据库编码与中文乱码
开发过程中最让我头疼的问题是MySQL的编码设置。默认情况下MySQL创建数据库的排序规则可能是latin1_swedish_ci,插入中文数据时会出现乱码或者报错Incorrect string value: '\xE5\xB9\xB4...'。
解决办法是在创建数据库时强制指定utf8mb4编码:
CREATE DATABASE momshop DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;如果已经建好了数据库,也可以通过修改数据库的默认编码来修复:
ALTER DATABASE momshop CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;另外,Django连接MySQL时也要在settings.py中设置编码:
OPTIONS = {'charset': 'utf8mb4'}加了这行配置,读取和写入中文就不会出问题了。utf8mb4和utf8的区别在于,utf8mb4能完整支持四字节的emoji字符,比如商品名称里带一个表情符号,用utf8mb4就能正常存储。现在是移动互联网时代,用户输入表情是常态,所以推荐统一用utf8mb4。
6.3 跨域问题的两种解决方式
前端跑在8080,后端跑在8000,两者端口不同,浏览器就会报跨域错误。除了我在前面讲的devServer代理方案,还有一种是后端直接使用django-cors-headers库来允许跨域请求。
具体做法是:安装库,在settings.py的INSTALLED_APPS中加入corsheaders,在MIDDLEWARE中加入corsheaders.middleware.CorsMiddleware,然后设置:
CORS_ALLOW_ALL_ORIGINS = True开发阶段这样设置是没问题的,但如果部署到生产环境,应该只允许指定的域名访问,否则任何人都能跨域调用你的接口,存在安全隐患。我一般开发时用代理方案(因为更符合生产环境的前后端同域部署模式),只在需要给外部演示时临时开启CORS。两种方案都掌握,面试时被问到“跨域怎么解决”就能答出两套方案。
6.4 前后端接口联调的最优顺序
我见过很多同学把前端页面全部写完才发现接口参数对不上,然后改起来非常痛苦。正确做法是按模块联调,一个模块通了再做下一个。推荐的顺序是:
- 先调通用户模块(注册、登录、获取用户信息),这个模块通了,JWT认证链路就验证过了,后面所有带Token的请求都能正常发起。
- 再调通商品模块(商品列表、分类筛选、商品详情),前端的首页和列表页就能展示真实数据。
- 接着是购物车模块,因为它的业务依赖用户登录状态和商品数据。
- 最后是订单模块,它依赖前面的所有模块。
按这个顺序联调,每次只需要面对一个小问题域,排查难度大大降低。前端API文件我也按模块拆分,每个模块一个文件,不要所有接口塞在一个文件里。我一开始图省事把所有接口写在api/index.js里,后来项目膨胀到几十个接口后,每次改动都要全局搜索定位,效率极低,重构后才舒服很多。
7. 写在最后:这套源码还能怎么扩展
母婴商城管理系统做到这里,已经是一个非常完整的毕业设计项目了。但如果你想让它在答辩时有更多亮点,或者往简历上写的时候更有含金量,我建议在这个基础版本上做下面几个方向的扩展:
接入支付宝沙箱支付。官方沙箱环境不需要企业资质,用个人账号就能申请。在结算流程中接入真实的支付页面对跳转,体验感会瞬间提升一个档次。技术上就是在后端增加一个alipay应用,前端在确认订单后调用后端生成支付链接,前端跳转到沙箱页面完成“支付”,后端通过回调接口更新订单状态。这个功能能体现出你对支付流程的理解,是加分项。
增加Redis缓存。把首页的商品推荐数据在Redis中缓存10分钟,减少数据库压力。虽然毕设访问量不大,不加缓存也完全没问题,但“我考虑过缓存设计”这个思路本身就能体现你对系统性能的思考。
增加简单的销量统计报表。管理端用ECharts做一个柱状图,按周展示销量前10的商品。数据源可以从订单明细表中聚合查询,ECharts非常容易集成到Vue里。这个功能做好了,管理的概念就完整了。
部署到云服务器。如果有预算,可以在阿里云或腾讯云买一台最便宜的轻量服务器,把前后端分别部署上去。前端构建后交给Nginx托管,后端用Gunicorn+Supervisor跑起来。部署完成的那一刻,你的简历上就可以写“独立完成系统设计、开发与部署上线”了,这话是很有分量的。
我在实际带项目的过程中最深的一点体会是:毕业设计的技术难度其实不高,真正拉开差距的,是你能不能把自己做的每一步都讲明白。这套系统的每一行代码我都能解释为什么这么写,每一个表字段都知道它的业务含义,这才是你的核心竞争力。希望这篇拆解能帮你把这个项目跑通、吃透,顺利通过答辩。