ARTICLE DETAIL

资讯详情

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

Django产品订单管理系统毕业设计实战:从数据库设计到答辩全攻略

Django产品订单管理系统毕业设计实战:从数据库设计到答辩全攻略 Django产品订单管理系统毕业设计实战从数据库设计到答辩拿得出手如果你正在为毕业设计选题发愁或者已经拿到了一个“产品订单管理系统”的需求却不知道从哪下手这篇文章应该能帮到你。我用Django从零搭了一套完整的产品订单管理系统源码也整理好了。这篇文章不是那种贴一堆代码、然后让你自己看的技术文档而是把我整个设计思路、数据库怎么建、订单状态怎么流转、哪些后台功能必须做、以及最后怎么在答辩现场把项目讲清楚全部拆开讲给你听。这套系统看起来是个典型的CRUD项目但真正写起来涉及的东西并不少用户认证、产品分类与库存、购物车会话管理、订单状态机、后台数据可视化、部署环境配置每一个点都能单独拿出来问。我尽量把每个环节怎么做、为什么这么做、踩过哪些坑都写清楚适合正在做Django课设或毕设的同学参考也适合想快速上手Django项目实战的初学者。1. 项目整体设计与模块拆解1.1 为什么选Django做订单管理系统先说选型。订单管理系统这种业务本质上就是对“用户、产品、订单、支付”这几个核心对象做增删改查外加一些状态流转和统计报表。这类系统的特点是数据模型清晰、表单交互多、后台管理需求重。我选Django而不是Flask或Spring Boot主要看重三点。第一Django自带Admin后台产品管理和订单查看这类功能几乎不用自己写页面能省下大把时间。第二Django的ORM做得非常成熟一对一、一对多、多对多的关系映射配合迁移工具改模型不用手动操作数据库对做课设的同学特别友好。第三Django的用户认证体系是完整的登录、注册、会话、权限控制都给你封装好了直接能用不用自己造轮子。当然Django也不是没缺点它的模板渲染方式比较传统前后端不分离做那种高度交互的页面会比较吃力。但对毕业设计来说这个“缺点”反而让学生能把精力集中在业务逻辑上而不是和前端框架较劲。1.2 功能模块规划别一上来就写代码很多同学拿到题目就开始建app、写models这是最容易翻车的做法。我建议先花一天时间把功能模块画清楚用思维导图或者简单的表格都行确保整个系统“有什么页面、谁在用什么功能”是清晰的。我的模块规划是这样的模块核心功能面向用户用户认证注册、登录、退出、个人信息所有用户产品展示产品列表、分类筛选、产品详情所有用户购物车加入购物车、修改数量、删除、结算登录用户订单管理创建订单、订单列表、订单详情、取消/收货登录用户后台管理产品录入、库存修改、订单状态变更、数据统计管理员这里有一个非常关键的取舍订单状态变更发货、完成放在哪边。我的做法是前台用户只能做“取消订单”和“确认收货”这两个动作发货操作放到Django Admin后台完成。这个设计符合真实电商场景而且答辩的时候你可以非常有底气地说权限分离普通用户不可越权操作发货。模块规划完了再拆页面需求和URL路由最后才是写代码。2. 数据库设计与核心模型实现2.1 订单状态流转用状态机思维设计字段订单系统最核心的表是订单表而这个表最核心的字段是“状态”。我见过不少同学把订单状态设计成普通的CharField然后存“待支付”“已支付”“已发货”这样的中文看起来直观但后续做统计、做筛选的时候非常尴尬——你得用精确的中文去匹配稍微多一个字就查不出来。正确做法是用一个整数状态码配合choices参数定义可读标签。Django的Model里直接支持这个写法我最终的Order模型状态字段是这样的class Order(models.Model): STATUS_CHOICES [ (0, 待支付), (1, 已支付/待发货), (2, 已发货), (3, 已完成), (4, 已取消), ] status models.IntegerField(choicesSTATUS_CHOICES, default0, verbose_name订单状态)这样做的好处不只是查询方便更重要的是帮你想清楚“订单一生中会有哪些状态变化”。我画了一张状态流转表当前状态允许操作下一个状态待支付用户取消 / 模拟支付已取消 / 已支付待发货已支付待发货管理员发货已发货已发货用户确认收货已完成已完成无-已取消无-这个表在答辩时很好用——你直接说订单模块我使用了状态机模型每个状态转换都有明确的业务含义和权限控制比单纯说“我写了一个订单表”高好几个档次。2.2 外键关系设计订单与产品之间加了中间表订单和产品之间的关系要特别注意。最简单的做法是订单直接外键关联产品一个订单只能买一个商品。但现实的订单是一单多品的所以需要一个订单项OrderItem表作为中间桥梁。我的模型关系是这样的User 与 Order一对多一个用户可以有多张订单。Order 与 OrderItem一对多一张订单包含多个订单项。OrderItem 与 Product多对一一个订单项对应一个产品但注意这里我做了快照。什么是快照就是订单项里的产品名称、价格这些字段不能直接通过外键去取当前的产品表数据而是在下单那一刻把“商品名称、商品单价、购买数量”原样拷贝一份存到订单项里。为什么要这么做因为产品价格是会变的如果用户下单的时候是99元后来管理员把价格改成199元用户再去查历史订单显示的应该是下单时的99而不是后来的199。这个细节很多同学会忽略但它是订单系统里一个非常重要的业务常识。我在答辩的时候主动讲了这一条评审老师明显对这个设计很认可。2.3 购物车表用匿名会话还是数据库表购物车也是一个需要想清楚的点。购物车本质是“用户还没下单时暂存的商品清单”有几种实现方案存在Session里、存在Cookie里、建一张购物车表存在数据库里。我选择了数据库表方案。原因很简单Session和Cookie方案在用户换设备或清浏览器之后就丢了而且没法在后台看到所有用户的购物车数据。建一张CartItem表设计成可选关联用户用户在未登录状态下也能往购物车里面放商品登录后如果想同步还能做合并处理。整个购物车表长这样class CartItem(models.Model): user models.ForeignKey(User, on_deletemodels.CASCADE, nullTrue, blankTrue) product models.ForeignKey(Product, on_deletemodels.CASCADE) quantity models.PositiveIntegerField(default1) selected models.BooleanField(defaultTrue) # 控制是否选中结算这里有一个值得注意的小技巧selected字段。很多同学的购物车是没有勾选功能的只能整单结算但真实系统里用户经常希望只结算其中的部分商品。加了这个字段前端展示勾选框下单时只统计selected为True的商品整个功能就完整了。3. 核心功能实操实现与关键代码3.1 用户认证直接用Django自带认证还是自定义用户认证是我纠结比较久的部分。Django自带的User模型已经包含用户名、密码、邮箱、注册时间等常用字段功能上完全够用。但问题在于如果你想把手机号、性别、头像这些信息存进去就需要新建一个Profile表去关联扩展。我最终的方案是注册和登录直接用Django的User模型和认证方法同时建一个UserProfile表存储额外信息。这样开发效率高扩展又不冲突。注册的逻辑在views里大概长这样用的是类视图配合Django自带的UserCreationForm做二次封装class RegisterView(View): def get(self, request): form UserCreationForm() return render(request, register.html, {form: form}) def post(self, request): form UserCreationForm(request.POST) if form.is_valid(): user form.save() # 同步创建Profile方便后续扩展 UserProfile.objects.create(useruser, phone, avatar) login(request, user) return redirect(product_list) return render(request, register.html, {form: form})这里有一个细节很多初学者会在还是匿名用户的时候就去访问购物车或者创建订单然后在ORM查询里报“AnonymousUser对象没有user属性”之类的错。解决办法是在需要登录的视图函数上加login_required装饰器类视图则在URL配置里用LoginRequiredMixin。我是在dispath方法里统一做了一次用户登录状态判断减少重复代码。3.2 产品列表与分类筛选ORM链式查询怎么写才优雅产品模块相对简单但它是一个展示你ORM功底的好地方。我实现了按分类筛选、按价格排序、按销量排序、以及一个关键字模糊搜索。筛选逻辑用Django的Q对象和链式查询可以一行搞定products Product.objects.filter(is_activeTrue) if category_id: products products.filter(category_idcategory_id) if keyword: products products.filter( Q(name__icontainskeyword) | Q(description__icontainskeyword) ) if sort_by price_asc: products products.order_by(price) elif sort_by price_desc: products products.order_by(-price)这里要注意的是filter返回的是惰性查询集这个链式调用不会真正执行SQL直到你遍历或者去取长度时才执行。也就是说条件分支随便加性能损耗可以接受。但如果页面要展示分页记得在最终结果上做分页切分不要一次性把几百条数据全渲染到模板里。分页用Django内置的Paginator简单可靠paginator Paginator(products, 8) page_number request.GET.get(page) page_obj paginator.get_page(page_number)模板里面循环page_obj就能分页渲染底部加上页码导航这部分代码量不大但很能体现系统完整度。3.3 购物车与订单创建用事务保证数据一致性购物车转订单是整个系统里最容易出bug的地方。这个流程涉及的操作至少有这些读取购物车里选中的商品、计算总价、创建订单主表记录、批量创建订单项记录、扣减商品库存、清空选中的购物车项。这些操作必须保证“要么全成功要么全不成功”。比如订单创建成功了但库存扣减失败就会出现超卖这在订单系统里是绝对不允许的。Django处理这个问题的方式是transaction.atomic()将整个下单过程放进一个数据库事务。用一个代码块看完整的下单逻辑from django.db import transaction def create_order(request): cart_items CartItem.objects.filter(userrequest.user, selectedTrue) if not cart_items.exists(): return JsonResponse({code: 1, msg: 购物车为空}) with transaction.atomic(): order Order.objects.create( userrequest.user, total_amountsum(item.product.price * item.quantity for item in cart_items), status0 ) for item in cart_items: if item.product.stock item.quantity: # 抛出异常整个事务回滚 raise ValueError(库存不足) OrderItem.objects.create( orderorder, productitem.product, product_nameitem.product.name, product_priceitem.product.price, quantityitem.quantity ) item.product.stock - item.quantity item.product.save() # 清空购物车已选商品 cart_items.delete() return JsonResponse({code: 0, order_id: order.id})这段代码里有一个关键操作先检查库存再扣减是在同一个事务里完成的。这样就不会存在两笔订单同时看到有库存、同时扣成负数的情况。库存扣减用减法操作虽然简单但真正高并发场景下应该用F()表达式做原子更新我在这个项目里用的是先检查后更新的方式因为单机部署的课设场景完全够用。这个下单逻辑是整个系统的核心亮点答辩的时候老师最可能问的就是这里。你要能说清楚事务保证原子性、库存校验防止超卖、订单项做数据快照的原因这三个点讲明白了订单这块基本就稳了。3.4 支付模块不接第三方支付的替代方案真实订单系统肯定要对接微信支付、支付宝之类的第三方支付平台但毕业设计如果真去申请商户号、走完整的支付流程光是审核和证书配置就能耗掉你两三周时间。我的方案是做“模拟支付”订单创建后在订单列表页放一个“去支付”按钮点击后跳到一个模拟支付页面会展示订单信息和应付金额点确认支付按钮就把订单状态从待支付改成已支付待发货。同时记录一个支付流水提升系统完整度。支付流水表也很简单class PaymentRecord(models.Model): order models.ForeignKey(Order, on_deletemodels.CASCADE) pay_type models.CharField(max_length20, defaultalipay) pay_time models.DateTimeField(auto_now_addTrue) amount models.DecimalField(max_digits10, decimal_places2) trade_no models.CharField(max_length64, uniqueTrue)模拟支付的意义在于它帮你把“支付完成后的回调处理”这个环节在代码层面走通了将来想接入真实支付只需要在确认支付的视图里调用支付平台的接口然后接收异步通知改动量非常小。这个思路在答辩时说给老师听他只会觉得你想得周到不会质疑你没做真实支付。3.5 后台管理用Admin还是自己造页面Django自带的Admin后台是它最大的杀手锏但如果你直接裸用默认界面整个项目会显得太“菜”。我的做法是保留Admin框架但做了两处定制第一注册Order模型到Admin时使用list_display和list_filter让订单管理界面一目了然admin.register(Order) class OrderAdmin(admin.ModelAdmin): list_display [order_no, user, total_amount, status, created_at] list_filter [status, created_at] search_fields [order_no, user__username] actions [mark_as_shipped] admin.action(description标记选中订单为已发货) def mark_as_shipped(self, request, queryset): queryset.update(status2)第二Admin页面里加了一个统计看板页展示今日订单数、总销售额、最近一周的订单趋势用简单的聚合查询Count和Sum就能做出来。这一块是很多同学的盲区——后台功能只做了增删改查完全没做统计分析但订单系统的核心价值恰恰在数据统计上。统计看板的实现不复杂写法类似这样from django.db.models import Sum, Count today_orders Order.objects.filter(created_at__datetimezone.now().date()).count() total_sales Order.objects.filter(status__in[1, 2, 3]).aggregate(totalSum(total_amount))4. 常见问题与排查技巧实录4.1 数据库迁移报错的三种典型场景Django的makemigrations和migrate命令在所有项目里都会用到但什么时候容易出错很多新手完全没有概念。我遇到过的典型场景排名第一的是修改模型字段后又加了非空约束导致迁移时提示“字段缺少默认值”。解决办法是给字段设置默认值或者允许为空。比如我加了一个“支付时间”字段刚开始是DateTimeField()且不允许为空历史数据表里没有这一列的值迁移就会卡住。改成nullTrue, blankTrue之后就顺利通过了。排名第二的场景是外键关联的模型没定义好on_delete参数。老版本Django里默认是CASCADE新版本强制要求显式声明。我统一选择了on_deletemodels.CASCADE因为订单项、购物车项这类从属数据主表删除时它们应该一起删掉。产品表被订单项引用时我更想保留订单历史数据所以把订单项里的产品外键改成on_deletemodels.SET_NULL, nullTrue避免产品被删除后订单本身还能正常展示。排名第三的场景是多个app的迁移文件执行顺序互相依赖。解决办法没什么技巧就是按顺序把依赖的app先迁移或者干脆用一条python manage.py migrate让Django自己判断依赖关系它通常能排对。4.2 CSRF校验失败表单提交和Ajax提交的处理方式Django默认开启了CSRF防护表单POST提交时必须在模板里加{% csrf_token %}。但如果用Ajax提交这个token不会自动出现在请求头里你会反复收到403错误。我的解决办法是在前端写一个公共方法把Cookie里的csrftoken取出来塞进所有Ajax请求的请求头function getCookie(name) { let cookieValue null; if (document.cookie document.cookie ! ) { const cookies document.cookie.split(;); for (let i 0; i cookies.length; i) { const cookie cookies[i].trim(); if (cookie.substring(0, name.length 1) (name )) { cookieValue decodeURIComponent(cookie.substring(name.length 1)); break; } } } return cookieValue; } const csrftoken getCookie(csrftoken); $.ajaxSetup({ headers: { X-CSRFToken: csrftoken } });把这个代码放进一个全局的js文件里就再也不用单个接口单独处理CSRF了。4.3 时区问题订单创建时间差了8小时Django项目的settings.py里默认的TIME_ZONE是UTC如果你不管它订单创建时间会比北京时间慢8小时。在做订单统计的时候就会出现“今天没有订单”的诡异结果。正确做法是把TIME_ZONE改成Asia/Shanghai同时把USE_TZ设为True。这样数据库里存的时间是UTC格式但模板渲染时Django会转换成当前时区的时间。我建议统一使用Django提供的timezone.localtime()方法去转换订单时间而不是直接在模板里硬算。这个时区问题看似小但在答辩现场如果被问到“为什么你的订单时间不对”没有准备就会很尴尬。4.4 列表页查询性能N1问题与select_related订单列表页通常要展示订单里的用户、商品等信息如果你在模板里循环order.orderitem_set.all每一条订单都单独查一次数据库整个页面会发起几十条SQL查询肉眼可见地卡顿。解决办法是在查询订单时用select_related和prefetch_related把关联数据提前查好orders Order.objects.filter(userrequest.user)\ .select_related(user)\ .prefetch_related(orderitem_set__product)select_related适合处理外键的一对一关系prefetch_related适合处理一对多和多对多关系两者组合使用查询次数从几十条降到了个位数。这个优化思路值得写进你的实验报告里属于典型的性能优化加分点。5. 部署与答辩场景准备5.1 本地演示环境准备清单毕业设计答辩前环境准备这一步千万不能拉胯现场翻车的基本都是环境问题。我列一下我当时的准备清单你可以照抄Python 3.10环境Django版本锁定在4.2 LTS不要装Django 5.0及以上有些教程和第三方库还不兼容。数据库直接用SQLite零配置、单文件答辩时不用额外启动数据库服务省心。项目根目录下用requirements.txt锁定全部依赖方便在任何一台机器上快速重建环境。准备两个账号一个普通用户账号购物车、下单、支付功能都测试过一遍一个管理员账号能登录Admin后台看到订单管理界面。准备几份Demo数据产品至少要覆盖两个分类每个分类下3-5个商品其中最好有一个商品库存故意设置为0这样你可以现场演示“库存不足”时的错误提示。5.2 答辩时被问到的高频问题答辩测试不仅仅是演示功能老师会问设计思路与业务深度。根据我的经验这几类问题出现的概率极高提前准备好答案问为什么选择Django答Django适合快速开发中小型管理类系统自带ORM和Admin后台它的MTV架构让前后端职责分离清晰适合课程设计场景国内资料多、部署案例多遇到问题容易排查。问订单状态是怎么设计的答用整数状态码配合choices常量实现状态机状态流转有明确的业务边界和权限控制用户侧的操作和管理员侧的操作分开避免越权。问如何防止库存超卖答在使用数据库事务的前提下下单时先检查库存后扣减库存更严谨的项目还可以用行级锁或者乐观锁做并发控制当前项目是单机部署事务在应用层就保证了安全性。问如果系统上线哪些地方还需要改进答支付模块需要对接真实第三方支付平台下单流程可以引入消息队列做削峰库存扣减可以改用RedisLua脚本当前是前后端不分离的渲染模式可以后期演进为前后端分离架构。这些问题提前组织好语言比临场硬想的效果好得多。6. 写在最后整个项目从环境搭建到功能实现再到打包文档差不多花了两个完整星期。核心代码集中在models.py和views.py这两个文件里老实说代码量并不大但把每个模块的边界想清楚、把每种状态流转的规则定下来才是这个项目里最费功夫的事。如果你正准备复现这套系统我建议你不要急着把源码复制粘贴就跑而是先拿着这篇文章里的设计思路把数据库表结构自己画一遍把订单状态机画一遍再对照着源码去看具体实现。这样下来就算老师现场问一些细节问题你也能应对。做毕业设计这件事目标不是“写完一个系统”而是“讲清楚一个系统”。产品订单管理系统的业务链路天然清晰好好梳理一下是很好出成果的选题。
返回列表