Flask酒类购物系统毕业设计:从数据库到前后端全实现 1. 为什么选“Flask 酒类购物系统”作为毕业设计每年到了毕业季总有人来问我购物系统怎么做。今天聊的这个Flask酒类购物系统项目编号30576是我整理过的一套完整毕业设计源码。前后台都做了用户能注册、登录、逛酒水、加购物车、下单、看订单管理员能管商品、管分类、管库存、处理订单、看销售统计。这套系统用Flask作为Web框架数据库用的SQLAlchemy模板用的是Jinja2前端用Bootstrap做基础样式整体是典型的轻量级全栈项目。对于计算机、软件工程、电子商务专业的学生来说这是一个“稳妥又出效果”的选题方向既能覆盖基本Web开发流程又能在答辩时讲出深度。很多人觉得“购物系统”已经做烂了为什么我还愿意拿酒类作为切入点原因很简单酒类商品有大量属性可以设计筛选和详情展示比如度数、容量、年份、产地、香型、酿造工艺比单纯卖衣服、卖数码产品更容易做出数据模型的层次感。再加上酒类商品客单价偏高订单状态流转的演示效果更好从“待付款”到“已发货”再到“已完成”每一步都有真实感。当然做毕业设计不是真的开店数据都是测试数据不过设计思路完全可以参考真实电商。这套系统适合谁来参考一是准备做Web方向的毕业设计、还没定题的学生二是已经选定“电商系统”方向但不知道如何组织代码的同学三是想在现有功能上扩展亮点、提升答辩分数的同学。如果你只是想要一个“能跑”的Demo拿源码直接改个名字就交那这篇文章对你帮助有限。我更想说的是怎么把一个购物系统拆开、讲清楚让评委觉得你是真懂而不是背代码。1.1 选Flask而不是Django/FastAPI我的取舍理由我第一次拿到“酒类购物系统”这个题目时第一反应不是做功能而是问自己能不能做出来答辩时能不能讲明白。很多同学喜欢直接挑战大厂那套技术栈但毕业设计周期有限目标是按时交付、清晰讲解。Flask恰好是那种“麻雀虽小、五脏俱全”的框架一个HTTP请求进来后怎么被路由匹配、怎么进视图函数、怎么调用模型、最后怎么渲染模板整个链路非常短你很容易向评委讲清楚。Django功能非常全自带Admin后台、ORM、认证体系但大量默认约定和项目结构会掩盖你对核心逻辑的理解。一旦答辩老师问“你的用户登录状态怎么保持的”你用Django的话可能只回答“用了自带auth”这不是不好而是显得缺少主动设计。FastAPI性能好、自带OpenAPI文档写接口很舒服但很多院校课程里还没有把它作为主流框架答辩时你还要多解释“什么是ASGI”“为什么用Pydantic”反而分散了重点。所以选Flask不是因为它最强而是因为它是“最容易讲清楚”的框架。它的可扩展性也够用蓝图可以把用户模块、商品模块、订单模块拆开SQLAlchemy可以随时切换SQLite和MySQLJinja2模板可以配合前端做服务端渲染。做毕业设计框架不是越新越好而是越可控越好。你能解释清楚每一个环节这就是加分项。1.2 酒类品类的特殊性正好让系统有真实感选择“酒类”而不是“通用商品”是我觉得这个题目的聪明之处。普通购物系统的商品表通常就是“商品名、价格、图片、描述”但酒类商品有大量结构化属性。同样是葡萄酒可以有干红、半干、甜型按度数可以分低度、中度、高度按产地可以分国内、法国、澳洲按年份还能做老酒和新酒的概念。这些属性放到系统里就成了天然的多条件筛选需求也能让商品详情页看起来更专业。另外酒类购物系统天然带“合规”话题。做毕业设计时我们可以加入年龄验证逻辑、购买数量限制、页面底部的“适量饮酒”提示甚至在登录注册页面加入是否已满18周岁的确认框。这些细节在答辩时是很好的安全设计案例能体现开发者不只是写功能还在思考真实业务约束。评委如果问“你这个系统直接上线能用吗”你可以从业务合规角度去回答而不是只说“还缺支付”。当然也要强调一点作为毕业设计数据全部是模拟数据不要真的去卖酒。如果未来要商用必须取得酒类销售相关资质并遵守当地法规这不是本文讨论范围。我之所以把这一点写出来是想提醒大家选题虽然叫“酒类”但核心还是电商系统的通用能力。2. 整体设计与数据库建模2.1 功能模块怎么划分我把系统分成两个端前台用户端和后台管理端。前端用户端主要围绕“逛”和“买”展开用户注册登录、浏览商品列表、按分类查看酒水、按关键词搜索、进入商品详情、加入购物车、调整购物车数量、提交订单、查看订单列表、取消未发货订单、对已完成的商品进行评价。后台管理端则围绕“管”展开管理员登录后能管理商品分类、发布和编辑商品、维护库存、查看订单列表并更新订单状态、管理用户状态、查看销售统计数据。这个划分方式不是随便拍的。它遵循了一个很实际的原则“用户只操作自己的数据管理员操作全局数据”。用户下单时系统只允许操作用户自己的购物车管理员改订单时系统要校验管理员身份和数据范围。把权限边界想清楚代码结构才不会乱。功能清单如下用户注册、登录、退出密码使用哈希存储。商品分类管理支持一级分类也可以扩展为二级分类。商品列表页支持分页、按分类筛选、按价格/度数排序。商品详情页展示参数表格、库存状态、简介。购物车支持加入、修改数量、删除、批量结算。订单支持从购物车生成订单、生成唯一订单号、支付状态模拟、订单状态流转。后台Dashboard展示商品总数、订单总数、销售额、最近订单。库存管理下单扣减库存取消订单回补库存。我没有给系统加真实的第三方支付接口因为毕业设计里接入支付容易遇到资质、密钥和回调问题反而拖慢进度。我用“模拟支付”来替代订单创建后默认“待支付”用户点击“立即支付”后系统把状态改为“已支付”再等待管理员发货。这套状态机已经足够展示电商流程而且不依赖外部环境。2.2 数据库表设计数据库是整个系统最值得花时间设计的地方。我的表结构分为用户表、分类表、商品表、购物车表、订单表、订单明细表、评价表、库存变动日志表。下面给出核心表的字段设计以SQLAlchemy模型的角度来说明。表名关键字段说明userid, username, password_hash, role, created_at角色用普通用户/管理员区分categoryid, name, parent_id, sort_order预留二级分类productid, name, category_id, brand, origin, degree, volume, vintage, description, price, stock, sales, image_url, status酒类特有属性放在商品主表暂不拆SKUcartid, user_id, product_id, quantity登录用户的购物车也可用session实现orderid, order_no, user_id, total_amount, status, address, receiver, phone, remark, created_at状态机见下文order_itemid, order_id, product_id, product_name_snapshot, price_snapshot, quantity保存下单时快照防止商品改价影响历史订单reviewid, order_item_id, user_id, rating, content, created_at评价与订单明细关联stock_logid, product_id, change, reason, created_at记录库存变化简单解释两个容易被忽视的设计。第一是order_item中保存“商品名称快照”和“价格快照”这是真实电商的常用做法因为商品信息会变但历史订单必须保留当时的信息。第二是stock_log表每次扣减或回补库存时写一条日志这样答辩现场可以边说边演示“我去后台改一下库存然后看日志”。商品表没有拆SKU因为酒类虽然有度数、容量等属性但同一个商品ID对应一种规格已经足够毕业设计演示。如果你觉得不够高级可以把重量、容量拆成规格表但在代码量和讲解成本上会明显增加需要自己权衡。外键和索引方面我建议给category_id、order.user_id、order.status、order_item.order_id加上索引。订单表的status会被频繁查询加了索引之后演示“按状态筛选订单”时会快很多。在SQLite里可能感觉不明显但换成MySQL之后就知道好处了。2.3 购物车为什么可以存在Session里购物车存数据库还是Session这是答辩时很容易被问的问题。我的方案是用户登录后用购物车表存数据同时未登录用户可以用Session临时保存。但如果你想要最简单且不容易出错的方案可以直接把匿名购物车放到Session登录后自动合并进数据库。Session购物车的原理是Flask把session数据签名后发给浏览器用户再次请求时携带Cookie服务器读取后得到购物车字典。因为酒类购物系统是毕设项目不要求用户在多台设备同步购物车所以Session方案足够。它最大的好处是不用频繁写数据库用户体验很流畅。但Session方案有一个硬伤浏览器关闭或Cookie过期购物车就没了。如果想要“跨设备同步购物车”必须用数据库方案。我的建议是代码里同时支持两种未登录时用Session登录后把Session中的记录写入cart表。这个逻辑并不复杂但能在答辩时展示你对Web状态管理的理解。3. 核心函数和关键代码实现3.1 用户注册登录与角色权限用户模块不建议自己写密码加密算法直接用Werkzeug自带的密码哈希函数。下面这段代码是注册和登录时最核心的部分from werkzeug.security import generate_password_hash, check_password_hash from flask_login import LoginManager, UserMixin, login_user, login_required, current_user class User(db.Model, UserMixin): id db.Column(db.Integer, primary_keyTrue) username db.Column(db.String(64), uniqueTrue, nullableFalse) password_hash db.Column(db.String(256), nullableFalse) role db.Column(db.String(16), defaultuser) def set_password(self, password): self.password_hash generate_password_hash(password) def check_password(self, password): return check_password_hash(self.password_hash, password) bp.route(/register, methods[GET, POST]) def register(): if request.method POST: username request.form.get(username) password request.form.get(password) if User.query.filter_by(usernameusername).first(): flash(用户名已存在) return redirect(url_for(auth.register)) user User(usernameusername) user.set_password(password) db.session.add(user) db.session.commit() return redirect(url_for(auth.login)) return render_template(auth/register.html)登录逻辑类似用username查用户再调用check_password校验密码。通过后调用login_user(user)建立登录会话。管理员判断也很简单在需要管理员权限的视图函数上加一个自定义装饰器from functools import wraps def admin_required(f): wraps(f) def wrapper(*args, **kwargs): if not current_user.is_authenticated or current_user.role ! admin: flash(需要管理员权限) return redirect(url_for(auth.login)) return f(*args, **kwargs) return wrapper这里的关键是永远不要以明文形式保存密码。哪怕只是毕业设计代码里也要体现出安全习惯。答辩时老师看到generate_password_hash至少知道你不是门外汉。3.2 商品搜索、分类筛选与分页商品列表页是前台流量的入口要支持按分类、按关键词、按价格范围和度数排序。使用SQLAlchemy的filter和order_by可以拼出查询条件bp.route(/products) def product_list(): page request.args.get(page, 1, typeint) category_id request.args.get(category_id, typeint) keyword request.args.get(keyword, ) min_price request.args.get(min_price, typefloat) max_price request.args.get(max_price, typefloat) sort request.args.get(sort, default) query Product.query.filter_by(statuson) if category_id: query query.filter(Product.category_id category_id) if keyword: query query.filter(Product.name.contains(keyword)) if min_price is not None: query query.filter(Product.price min_price) if max_price is not None: query query.filter(Product.price max_price) if sort price_asc: query query.order_by(Product.price.asc()) elif sort price_desc: query query.order_by(Product.price.desc()) elif sort sales: query query.order_by(Product.sales.desc()) pagination query.paginate(pagepage, per_page12) return render_template(product_list.html, paginationpagination)这里最需要注意的是防止空条件把SQL搞乱。所有filter都加在判断里而不是直接拼字符串能有效避免SQL注入风险。Flask的paginate方法会返回Pagination对象模板里可以通过pagination.items取当前页商品pagination.iter_pages生成翻页按钮。我在做这个系统时还做了一件事商品列表页左侧的筛选区全部从数据库实时读取分类而不是硬编码。这样管理员新增分类后前台马上就能看到不用改代码。3.3 购物车功能实现细节购物车我建议把数据结构设计成字典key是商品IDvalue是数量。这样在Session里存取非常方便def get_cart(): return session.get(cart, {}) def add_to_cart(product_id, quantity1): cart session.get(cart, {}) cart[str(product_id)] cart.get(str(product_id), 0) quantity session[cart] cart session.modified Truesession.modified True 很容易漏掉。如果你直接修改session里的可变对象比如cart字典Flask不一定会认为session变了。这个坑我在第一次测试时踩过加了数量之后页面刷新还是原来的值。解决办法就是显式标记modified。购物车页面展示时不能只显示ID还需要把商品的名称、价格、图片查出来bp.route(/cart) def cart_page(): cart get_cart() items [] total 0 for pid, qty in cart.items(): product Product.query.get(int(pid)) if product: subtotal product.price * qty total subtotal items.append({ product: product, quantity: qty, subtotal: subtotal }) return render_template(cart.html, itemsitems, totaltotal)这里要注意如果商品被管理员下架或者库存为零购物车中仍然可能出现。我建议在展示时加一个valid字段后台自动判断商品状态把失效商品高亮显示而不是直接删除。这样用户就知道为什么结算不了。3.4 下单与库存扣减重点创建订单是整个系统最核心的环节需要同时完成从购物车取商品、校验库存、计算总价、生成订单和订单明细、扣减库存、清空购物车。这些操作必须在一个数据库事务里完成任何一个步骤失败都不能留下脏数据。我给出的实现思路如下from sqlalchemy import select from sqlalchemy.orm import with_for_update bp.route(/checkout, methods[POST]) login_required def checkout(): cart get_cart() if not cart: flash(购物车为空) return redirect(url_for(shop.cart_page)) # create order but do not commit yet order Order( order_nogenerate_order_no(), user_idcurrent_user.id, total_amount0, statuspending_payment ) db.session.add(order) db.session.flush() total 0 for pid, qty in cart.items(): product db.session.execute( select(Product).where(Product.id int(pid)).with_for_update() ).scalar_one_or_none() if not product or product.stock qty: db.session.rollback() flash(商品库存不足) return redirect(url_for(shop.cart_page)) product.stock - qty product.sales qty total product.price * qty db.session.add(OrderItem( order_idorder.id, product_idproduct.id, product_name_snapshotproduct.name, price_snapshotproduct.price, quantityqty )) db.session.add(StockLog( product_idproduct.id, change-qty, reasonf订单{order.order_no}下单 )) order.total_amount total db.session.commit() session[cart] {} session.modified True return redirect(url_for(order.order_detail, order_idorder.id))with_for_update()是行级锁在高并发场景下能防止多个请求同时读到同一个库存值导致超卖。SQLite对行锁支持有限但放到MySQL里效果就很明显。答辩时能主动提出这个细节绝对加分。生成订单号的函数我采用时间戳加随机数的方案import time import random def generate_order_no(): return time.strftime(%Y%m%d%H%M%S) str(random.randint(1000, 9999))如果同时并发的订单很多时间戳四位随机数可能有小概率重复。但在毕设演示规模下完全够用。如果想更严谨可以加上用户ID后几位或者使用UUID短码。3.5 后台统计和图表展示后台Dashboard别只显示一个登录框加上统计数字和简单图表项目质感会提升一个档次。我用SQLAlchemy的func聚合函数查询最近七天的销售额from sqlalchemy import func bp.route(/admin/dashboard) login_required admin_required def dashboard(): today date.today() seven_days_ago today - timedelta(days6) sales_by_day db.session.query( func.date(Order.created_at).label(day), func.sum(Order.total_amount).label(total) ).filter( Order.status.in_([paid, shipped, completed]), Order.created_at seven_days_ago ).group_by(func.date(Order.created_at)).all() top_products db.session.query( Product.name, func.sum(OrderItem.quantity).label(total_qty) ).join(OrderItem, OrderItem.product_id Product.id)\ .group_by(Product.id)\ .order_by(func.sum(OrderItem.quantity).desc())\ .limit(10).all() return render_template(admin/dashboard.html, sales_by_daysales_by_day, top_productstop_products)模板里可以自己用Chart.js把数据接进去。其实核心不是画图而是让老师看到你有“统计报表”这个意识。很多购物系统毕设不做统计你做了哪怕只有一个折线图也会让整体完成度高出一截。4. 项目运行与部署实操4.1 本地环境搭建拿到源码后不要直接双击run.py先建立虚拟环境。我建议的步骤是python -m venv venv source venv/bin/activate # Windows: venv\Scripts\activate pip install -r requirements.txtrequirements.txt里面至少要有这些依赖Flask3.0.3 Flask-SQLAlchemy3.1.1 Flask-Login0.6.3 Flask-WTF1.2.1 Werkzeug3.0版本不必完全固定但不要用太老的版本。Flask 3.x和Flask 2.x有些细微差别如果你用的是跟着旧教程写的代码要注意app.route和endpoint的兼容性。在运行之前一定要设置两个环境变量SECRET_KEY和DATABASE_URL。SECRET_KEY是给session和CSRF加密用的不设置的话每个进程重启后session失效登录状态也保不住。DATABASE_URL如果不设置就默认SQLite。export SECRET_KEYyour-random-secret export DATABASE_URLsqlite:///db.sqlite3Windows下用set而不是export这个细节我见过很多人报错。4.2 数据库初始化和样例数据项目初始化时我建议在models.py中直接创建所有表也可以通过Flask-Migrate做迁移。对于毕设来说直接用db.create_all()是最不容易出错的方式from app import app, db from models import Category, Product with app.app_context(): db.create_all() # 检查是否已有分类避免重复写入 if Category.query.count() 0: c1 Category(name红葡萄酒) c2 Category(name白葡萄酒) c3 Category(name精酿啤酒) c4 Category(name威士忌) db.session.add_all([c1, c2, c3, c4]) db.session.commit()种子数据非常关键。很多同学把项目跑起来后前台空空如也连商品图片都没有感觉像是一个“空壳”。我建议至少插入8到10个测试商品名称可以叫“智利干红葡萄酒2020”“苏格兰单一麦芽威士忌”“比利时白啤”这类不要用真实品牌避免侵权。商品图片可以用静态目录里的演示图也可以直接用外链占位图但外链不稳定建议下载到本地。4.3 启动开发服务器启动命令很简单flask --app run.py run --debug --host0.0.0.0 --port5000使用--host0.0.0.0后同一局域网内的同学可以通过你的IP访问答辩演示时如果只在一台电脑上展示也可以访问后台。不过需要确认防火墙没有拦截5000端口。如果端口被占可以用--port5001这个属于基本操作。开发模式下开启--debug修改代码会自动重载对调试非常友好。但要注意debug模式绝对不能用于生产环境它会在页面直接输出代码和异常信息安全隐患非常大。打开浏览器后前台访问/products后台访问/admin。管理员账号通常写成admin/admin123但第一次登录后要改成你自己的密码。4.4 生产部署要点虽然毕业设计答辩通常在本机演示但也有老师会问“如果上线怎么部署”。最简单的回答是用Gunicorn加Nginx。Gunicorn是Python WSGI服务器生产环境不能直接让Flask开发服务器裸奔。安装并启动pip install gunicorn gunicorn -w 4 -b 127.0.0.1:8000 run:app这里run:app是Python文件里Flask实例的名称。然后用Nginx做反向代理把80端口请求转发到8000端口。静态文件目录交给Nginx处理减轻Flask进程压力。Nginx配置片段可以参考server { listen 80; server_name example.com; location /static { alias /path/to/project/static; } location / { proxy_pass http://127.0.0.1:8000; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } }部署时还要设置环境变量用systemd或者supervisor管理Gunicorn进程保证服务器重启后服务自动拉起。如果你没做过这些也不用慌把“开发环境、生产环境”的区别讲清楚老师就知道你理解Web应用的完整生命周期。5. 常见问题与排查实录5.1 典型故障和解决办法我在调试这套系统时遇到的坑基本都集中在下面几个地方。现象原因解决办法页面500错误模板文件名称写错或数据库表未创建先看日志再检查render_template路径最后确认已执行db.create_all登录后跳回登录页SECRET_KEY未设置或session过期设置SECRET_KEY并检查登录路由redirect地址静态文件加载不出来使用了绝对路径而不是url_for模板中改成{{ url_for(static, filenamecss/style.css) }}表单提交400 CSRF错误Flask-WTF开启CSRF但表单没加csrf_token在模板form内添加{{ csrf_token() }}或{{ form.hidden_tag() }}购物车数量改了不生效修改session对象后没有标记modified每次修改session[cart]后设置session.modified True中文乱码数据库连接字符集问题SQLite一般不存在MySQL要在DATABASE_URL加?charsetutf8mb4后台显示“需要管理员权限”admin账号role字段没有改为admin直接在数据库执行UPDATE或提供管理员注册隐藏入口第一个500错误是最常见的。很多人看到浏览器上的“Internal Server Error”就慌了其实服务器终端里已经打印出完整堆栈先看最后几行问题基本一目了然。学会读报错日志比找任何人帮忙都有用。5.2 库存超卖与并发处理线下演示单用户操作没压力但答辩老师可能会问“如果两个人同时买最后一个库存怎么办”。这是并发控制问题。我的方案是下单时用with_for_update()锁行在事务结束前其他事务修改同一行库存时必须等待。真实MySQL环境里库存扣减可以写成一条原子更新语句UPDATE product SET stock stock - 1 WHERE id ? AND stock 1;这种写法依靠数据库行锁和条件更新避免了先查后改之间的竞态。在代码里即使没有使用事务只要这条SQL执行成功就代表库存扣减成功执行失败说明库存不足。用ORM表达时可以组合成一个带条件的update操作result db.session.execute( update(Product) .where(Product.id product_id, Product.stock quantity) .values(stockProduct.stock - quantity) ) if result.rowcount 0: # 库存不足 pass这种方式比先查后改更严谨。答辩时能说出“扣减库存必须是一种原子操作”很多老师会认可。5.3 答辩现场可能被追问的问题不管代码写得多好答辩前都要把几个关键问题过一遍。我整理了几个高频追问并给出参考回答方向。“购物车为什么要用Session不用数据库”可以参考前面第2.3节重点讲匿名体验和减少数据库压力同时说明登录后会合并到数据库。“密码存的是明文吗”不是用了Werkzeug的generate_password_hash每次校验用check_password_hash。“订单金额是怎么算的”从购物车逐项计算保存每个商品的价格快照避免后续改价影响历史订单。“如果用户下单后不支付怎么办”订单状态是待支付可以添加超时取消机制。这属于扩展点不一定实现但要能说出方案。“数据库表为什么这样设计”从减少数据冗余、保留历史快照、支持订单状态查询三个角度回答。“用了什么前端框架”用Bootstrap和Jinja2模板。如果用了Chart.js就说为了展示统计图表。5.4 一个很有用的排错习惯我在调试时发现很多人喜欢直接在视图函数里print这是可以的但更好的方式是用Flask内置的app.logger。比如在下单函数里记录关键节点app.logger.info(用户%s创建订单商品数%d, current_user.username, len(cart))日志会输出到终端并且不会像print一样容易被忽略。把关键操作写进日志排查问题时非常高效。另外遇到异常不要只打一个“出错了”应该记录异常堆栈import traceback traceback.print_exc()在交付源码时把这些注释保留下来代码看起来更像真实生产环境而不是纯粹的教学Demo。6. 论文、演示和扩展建议6.1 演示脚本怎么安排答辩演示建议按照“用户逛店—下单—管理员发货—统计展示”这条主线走时间控制在8分钟以内。具体顺序可以这样先用普通用户身份注册或登录展示首页热门商品点进一个红酒商品详情重点提一下度数、容量、年份这些酒类特有参数加入购物车再到购物车页面截图式展示下单一瓶酒模拟支付显示订单状态变成已支付切换管理员账号在后台看到这笔订单把状态更新为已发货最后打开Dashboard展示最近七天的销售曲线和热销商品列表。演示时要提前把所有账号密码写在一张纸上避免现场输入错误。还要提前关掉浏览器里可能弹广告的插件开一个无痕窗口这样session状态干净不会出现“上一秒是管理员下一秒还是用户”的混乱。这一条是我自己答辩时踩过坑后总结的比临时救场有用得多。6.2 论文目录和写作要点论文建议按标准软件开发文档结构写绪论背景、意义、国内外现状、需求分析功能需求、非功能需求、可行性分析、总体设计架构图、功能模块图、数据库设计、详细设计与实现各模块代码和截图、系统测试测试环境、测试用例、结果分析、总结与展望。很多学生的论文写得像流水账主要是因为缺少“测试”部分。哪怕系统功能没做完也要把测试表格写完整登录测试、注册重复用户名测试、商品搜索测试、购物车增删测试、下单测试、库存扣减测试、管理员权限测试。每一条都要写预期结果和实际结果格式一致。这会让论文的完整性提升一大截。另外数据库设计部分要放ER图不要只贴建表语句。用MySQL Workbench或者在线工具画一个简单的实体关系图标注主键和外键这几乎是答辩评委最爱看的内容。画清楚一张ER图胜过写十页空话。6.3 让我觉得“惊艳”的三个扩展点如果时间和精力允许我建议在基础系统上做这三个扩展哪一个都很加分。第一是增加简单的推荐功能。只要基于当前商品分类或度数区间推荐同分类下价格最接近的4款商品用一条SQL就能完成。推荐机制不需要多智能哪怕就是“同产区商品”也能在答辩时引出“协同过滤”的话题。第二是订单导出功能把订单列表导出成Excel文件后台加一个按钮即可。第三是前端用Vue3或者原生JavaScript对购物车做无刷新更新减少整页刷新带来的卡顿感。这些扩展不会破坏原有主体逻辑但会让项目看起来有“工程化”的味道。如果你时间比较紧我建议只做第二个或第三个因为第一个推荐功能要做好需要一定算法理解。宁可把已有功能打磨完善也不要写出几个半成品功能反而暴露短板。最后分享一个小经验。我在完成这套系统后又花了一个晚上故意把MySQL换成SQLite、把图片路径写错、把session过期时间调成1分钟专门看系统怎么报错。这些“故意破坏”的测试让我理解了框架内部很多机制。答辩的时候评委突然问一个异常场景别人还在愣着你已经能说出解决方案了。毕业设计不是“跑起来就行”而是让你真正理解手里这套代码。源码包30576拿到手后建议你从数据库表开始读按“先模型、再表单、后视图”的顺序不要一上来就翻模板那样会越看越乱。我的体会是只要把订单和库存这块吃透这套系统就已经达到优秀毕设的水平了。