尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

周边贩卖系统毕业设计实战:Flask+MySQL电商全流程解析

周边贩卖系统毕业设计实战:Flask+MySQL电商全流程解析 看到这个题目我就想起前几年带过的几届毕业设计周边贩卖系统几乎每年都会出现在选题列表里。它本质上是一个电商系统但套上了周边这个具体场景后反而比空泛的商城系统更好落地也更容易在答辩时讲出特色。这篇博文就围绕这个题目的完整实现展开从技术选型、数据库设计到核心代码、常见坑位再到配套的LW文档毕业论文/设计文档怎么写把我实际做过、带过学生做过的经验一次说清楚。1. 一个贩卖快乐的课题周边贩卖系统到底在做什么先把这个题目拆开看。周边可以是动漫周边、游戏周边、明星应援周边、赛事纪念品甚至校园文创。它的特点是品类多、规格杂、更新快、常常有预售和限量。而贩卖系统落到毕设层面就是一套完整的Web交易系统让用户能在网页上浏览商品、加购物车、下单付款模拟或真实对接让管理员能在后台维护商品、处理订单、查看销售数据。从这个角度说这个题目的核心价值在于它覆盖了一条完整的业务链路。和学生管理系统图书管理系统那种纯增删改查的题目相比周边贩卖系统多出了购物车、库存扣减、订单状态流转这些电商特有逻辑难度适中但是内容饱满。和大型分布式商城那种过度的题目相比它又足够收敛一个人完整做下来不会失控。我见过太多人选了很宏大的课题最后代码堆了几万行答辩时自己都讲不清模块之间的关系——这个题目就完全不会有这种问题。毕设评审老师看重的往往是三件事一是系统能不能完整跑起来二是代码结构是否清楚三是文档和代码是否对得上。周边贩卖系统恰好在这三方面都很友好。前端页面用模板渲染就能做得像模像样后端用Flask或Django都能驾驭数据库几张大表就够用文档部分也有成熟的写作框架可以参考。所以如果你是今年毕业、正在为选题发愁或者已经在做这个题目但摸不清方向这篇文章适合你从头到尾读一遍如果你是懒得动手想直接抄源码那也建议至少把数据库设计和订单流程看明白不然答辩时老师问两句就露馅了。接下来的内容我就按我自己做这套系统的顺序来讲先定技术栈再建数据库然后写核心代码最后补文档。2. 技术选型Flask MySQL 模板渲染为什么我劝你别炫技2.1 框架选择Flask比Django更适合这个题目在带毕设的过程中经常有学生问我老师用Django是不是显得更专业我的回答通常很直接如果你有三个月时间并且对Python已经很熟Django没问题但如果你是边学边做Flask会更合适。原因有三点。第一Flask足够轻一个主文件加几个蓝图就能组织起整个后端逻辑对毕设这种规模的系统来说代码量刚好写起来不会觉得在做体力活。第二Flask的请求处理链路更直观路由、视图、模板的关系一目了然答辩时你能清晰地讲出用户点了这个按钮之后发生了什么这是评审老师最愿意听到的东西。第三Django自带Admin后台虽然方便但恰恰因为太方便很多学生做完都不知道后台的增删改查是怎么实现的一旦老师让现场加一个字段就卡住了。2.2 为什么不建议做前后端分离另外一个常见误区是现在业界都用Vue Spring Boot我毕设也搞前后端分离吧。说实话这个想法本身没错但对大多数本科毕设来说属于给自己挖坑。前后端分离意味着你要同时维护两套代码、处理跨域、设计接口文档、处理Token认证。这些工作不是不能做而是会大量挤占你本应该投入到业务逻辑和文档写作的时间。毕设的评判标准和真实项目不一样它更看重你能否完整地讲清楚一个系统的设计与实现。Flask的Jinja2模板渲染 Bootstrap渲染出来的页面在视觉效果上并不差开发效率却高得多。你完全可以用一行render_template就把数据库里的商品信息拼到页面上不用写一行Ajax代码。2.3 完整的技术栈清单我实际用到的技术栈是这样的你可以直接照抄层面选型说明后端框架Flask 2.x路由简单生态成熟ORMFlask-SQLAlchemy不用手写SQL模型类映射到表数据库MySQL 5.7/8.0毕设经典搭配千万别用SQLite糊弄前端Bootstrap 5 Jinja2模板继承栅格布局开箱即用页面交互原生JavaScript jQuery购物车数量增减、弹出确认框等小功能开发工具VS Code记得在VS Code里配置好Python解释器对应很多人在搜的vscode python环境配置问题关于Python环境本身我再多说一句新手第一步往往会栽在这里。Windows安装Python时一定要勾选Add Python to PATH装完后在终端里执行python --version能正确输出版本号才算成功。然后用python -m venv venv创建虚拟环境激活后pip install flask flask-sqlalchemy pymysql。如果这一步很顺利后面至少能少踩一半的坑。3. 数据库设计把周边这个品类拆成六张表3.1 表结构设计的前置思考不要一上来就建表。先想清楚业务上有哪些角色、哪些动作。这个系统里有两类角色普通用户买周边的人和管理员运营周边店的人。用户能做的动作是注册、登录、浏览商品、加入购物车、下单、付款、查看自己的订单管理员能做的动作是维护商品、上下架、处理订单发货、查看用户列表。把这些动作映射到数据上核心表就浮出来了用户表、分类表、商品表、购物车表、订单表、订单明细表。六张表足矣。我见过有些学生为了追求设计感硬加了一张用户收货地址表其实也不是不行但毕设阶段建议控制范围把地址字段直接塞进订单表更省事因为一个订单对应一个收货地址就够了。3.2 核心表的字段设计下面这几张表的结构是我实际用过的字段不算多但足够撑起完整的业务流。我挑重点讲。用户表user字段名类型说明idINT 自增主键用户IDusernameVARCHAR(50)用户名唯一passwordVARCHAR(255)密码哈希值不要存明文nicknameVARCHAR(50)昵称phoneVARCHAR(20)手机号avatarVARCHAR(255)头像路径created_atDATETIME注册时间密码存明文是毕设里最容易被老师挑刺的问题。用Flask自带的generate_password_hash或Python标准库hashlib做哈希都可以别直接拿MD5存至少要加盐这点在文档里写出来很加分。商品表product字段名类型说明idINT 自增主键商品IDnameVARCHAR(100)商品名称category_idINT分类外键priceDECIMAL(10,2)价格stockINT库存数量salesINT销量用于热门排序image_urlVARCHAR(255)商品主图descriptionTEXT商品详情statusTINYINT0下架 1上架is_hotTINYINT是否推荐/热门周边商品的规格问题我提一句。同一个角色的徽章可能有不同尺寸同款手办可能有普通版和限定版。要不要做SKU作为毕设我建议不要。你可以在商品名称里体现差异比如XX角色 Q版亚克力挂件 蓝色款这样既绕开了复杂的规格维度又能让商品列表显得丰富。如果非要做规格至少单独建一张规格表工作量会明显上涨。购物车表cart_item字段名类型说明idINT 自增主键主键user_idINT用户外键product_idINT商品外键quantityINT数量checkedTINYINT是否勾选可选字段购物车有两种实现方式数据库表存或者存在Session里。我在后面代码部分会展开讲两种方式的优劣。如果选数据库方式建议加一个user_id product_id的唯一约束防止同一个用户重复添加同一商品时产生两行数据。订单表orders字段名类型说明idINT 自增主键订单IDorder_noVARCHAR(32)订单号业务唯一user_idINT下单用户total_amountDECIMAL(10,2)订单总金额receiver_nameVARCHAR(50)收货人receiver_phoneVARCHAR(20)收货电话receiver_addressVARCHAR(255)收货地址statusTINYINT0待支付 1已支付 2已发货 3已完成 4已取消pay_timeDATETIME支付时间created_atDATETIME下单时间订单明细表order_item字段名类型说明idINT 自增主键主键order_idINT订单外键product_idINT商品外键product_nameVARCHAR(100)商品快照名称priceDECIMAL(10,2)商品快照单价quantityINT购买数量为什么要做快照因为订单生成之后商品名称、价格随时可能被管理员修改。如果直接关联商品表你查历史订单时会发现金额对不上。把名称和单价冗余到明细表里订单就变成了一旦生成就不受商品变更影响的历史事实。这个设计细节写进论文里很能体现你对数据一致性的理解。分类表category就不用多说了id name sort_order三四个字段就够了。商品表通过category_id关联分类前台按分类筛选时直接查。3.3 表之间的关联关系关系其实很简单一个用户有多个订单一个订单有多个明细一个用户购物车有多条记录每条记录关联一个商品一个分类下有多个商品。用SQLAlchemy定义模型时把这些关系用db.relationship声明好查询时就方便得多。需要注意外键字段在数据库里要建索引这个SQLAlchemy默认会处理但你心里要清楚。数据库字符集一定要用utf8mb4。我之前帮学生排过一个诡异的问题商品描述里放了emoji表情存进数据库变成问号页面显示乱码。根源就是数据库用的utf8而不是utf8mb4。MySQL安装时默认可能不是这个字符集建库时执行一下CREATE DATABASE merch_shop DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci一劳永逸。4. 核心模块实现从注册登录到模拟支付的全链路代码4.1 项目的目录结构与蓝图划分代码组织建议用Flask的蓝图Blueprint功能把路由按模块拆开。一个简单清晰的目录长这样merch_shop/ ├── app.py # 应用入口注册蓝图 ├── config.py # 配置数据库连接、SECRET_KEY ├── models.py # 所有ORM模型 ├── extensions.py # db对象 ├── views/ │ ├── auth.py # 注册登录 │ ├── main.py # 首页、商品列表、商品详情 │ ├── cart.py # 购物车 │ └── order.py # 下单、支付、订单列表 ├── templates/ │ ├── base.html │ ├── index.html │ ├── cart.html │ └── ... └── static/ ├── css/ ├── js/ └── images/这么做的好处是每个模块的代码都不会太长答辩时老师问登录逻辑在哪个文件你能脱口而出。我强烈建议把models单独放一个文件而不是放在app.py里否则后期所有模型堆在一起会长到你不想看。4.2 登录验证装饰器这是几乎所有需要登录的页面都会用到的东西。装饰器的作用是在请求进入视图函数之前先检查Session里有没有用户登录标记没有就直接跳转到登录页。from functools import wraps from flask import session, redirect, url_for def login_required(func): wraps(func) def wrapper(*args, **kwargs): if not session.get(user_id): return redirect(url_for(auth.login, nextrequest.path)) return func(*args, **kwargs) return wrapper登录成功后把用户ID写进Sessionsession[user_id] user.id session[username] user.username这个方案虽然简单但对毕设完全够用。登录状态用Session保存Flask默认用浏览器Cookie保存Session内容为了安全必须设置app.config[SECRET_KEY]这个键随便填一个长字符串就行。4.3 注册逻辑与密码加密注册视图的核心就是创建用户记录。注意用户名查重以及密码哈希from werkzeug.security import generate_password_hash auth_bp.route(/register, methods[GET, POST]) def register(): if request.method POST: username request.form.get(username).strip() password request.form.get(password) user_exists User.query.filter_by(usernameusername).first() if user_exists: return render_template(register.html, error用户名已被注册) user User(usernameusername, passwordgenerate_password_hash(password)) db.session.add(user) db.session.commit() return redirect(url_for(auth.login)) return render_template(register.html)登录校验时用check_password_hash(user.password, password)比对别把哈希后的密码直接拿来和输入比较。这个细节很多学生忽略却在答辩时被老师一眼看穿。4.4 商品列表与分页商品列表页要支持分类筛选、关键字搜索、分页。SQLAlchemy的分页方法非常省事main_bp.route(/) def index(): page request.args.get(page, 1, typeint) keyword request.args.get(keyword, ).strip() category_id request.args.get(category_id, typeint) query Product.query.filter_by(status1) if category_id: query query.filter_by(category_idcategory_id) if keyword: query query.filter(Product.name.contains(keyword)) pagination query.order_by(Product.is_hot.desc(), Product.sales.desc()).paginate(pagepage, per_page12) return render_template(index.html, paginationpagination)模板里用pagination.items遍历商品用pagination.iter_pages()渲染页码。Bootstrap的卡片组件来做商品展示很合适图片、名称、价格、库存状态一放就是一个像模像样的商城页面。4.5 购物车数据库方案和Session方案怎么选购物车是这类系统里最值得展开的部分。两种常见实现Session购物车不建表直接把{商品ID: 数量}存进Session。优点是无须读写数据库实现快缺点是换设备购物车就没了且无法跨端同步。数据库购物车建cart_item表数据持久化任何一个端登录都能看到自己的购物车。毕设推荐数据库方案因为它在文档里看起来业务逻辑完整且多了一张表的数据流可以讲。数据库购物车添加商品的核心逻辑是有则更新数量无则创建记录cart_bp.route(/add/int:product_id) login_required def add_to_cart(product_id): product Product.query.get_or_404(product_id) if product.status ! 1 or product.stock 0: flash(商品已下架或库存不足) return redirect(request.referrer or url_for(main.index)) cart_item CartItem.query.filter_by(user_idsession[user_id], product_idproduct_id).first() if cart_item: cart_item.quantity 1 else: cart_item CartItem(user_idsession[user_id], product_idproduct_id, quantity1) db.session.add(cart_item) db.session.commit() return redirect(url_for(cart.show_cart))购物车页面要做两件事展示勾选商品、计算总价。总价可以在Python端算遍历购物车记录item.product.price * item.quantity累加。展示时注意一个性能问题每个CartItem都要取关联的Product如果一次查出几十条记录直接用cart_item.product.price就能触发ORM懒加载SQL量可控不用过度优化。4.6 下单流程库存校验是重中之重从购物车生成订单是整个系统里最关键的一段逻辑因为涉及多步写操作。我的标准流程是获取购物车中勾选的记录校验每件商品的状态和库存库存不足则中断并提示创建orders主记录状态为待支付遍历购物车记录逐条生成order_item明细扣减库存这一步要在这里做防止超卖清空购物车中已下单的记录统一提交事务值得单独说的是第5步库存扣减。在并发场景下如果读到库存是1两个用户同时下单可能都通过校验然后都扣成负数。毕设里用with_for_update()做行锁就够了product Product.query.filter_by(idproduct_id).with_for_update().first() if product.stock quantity: db.session.rollback() return {code: 1, msg: f{product.name} 库存不足} product.stock - quantity product.sales quantity这段代码的意思是对product表对应行加写锁事务提交前其他事务的更新操作会等待。在MySQL的InnoDB引擎下这个策略是有效的。如果你用的SQLite行锁的支持很差这也是我前面建议用MySQL的原因之一。4.7 模拟支付与订单状态流转支付环节我建议做成模拟支付页面而不是真接入支付宝。原因很简单账单、资质、回调地址这些配置在毕设阶段既耗时又容易出错而且很多老师并不要求你对接真实支付渠道他们更关心你能否讲清楚支付成功后订单状态如何变化。模拟支付的交互可以做成点击去支付进入一个支付确认页展示订单号和金额输入一个测试支付密码比如随便六个1点击确认后走支付成功逻辑order_bp.route(/pay/int:order_id, methods[POST]) login_required def pay_order(order_id): order Order.query.filter_by(idorder_id, user_idsession[user_id]).first_or_404() if order.status ! 0: flash(订单状态不正确) return redirect(url_for(order.detail, order_idorder.id)) order.status 1 order.pay_time datetime.now() db.session.commit() return redirect(url_for(order.detail, order_idorder.id))订单状态机的流转其实就是一次更新一个字段但你要在代码和文档里把这个状态的关系画清楚待支付可以取消待支付支付后变已支付已支付由管理员发货后变已发货已发货后用户确认收货或管理员直接确认后变已完成。每一种状态转换都要校验当前状态防止用户绕过程序直接访问某个URL把状态改乱。4.8 后台管理的思路后台管理用一个独立的前缀/admin管理员登录后可以进入。功能上集中在三个地方商品管理增删改查、上下架、调整库存订单管理查看订单、修改状态、发货用户管理查看用户列表。后台页面建议复用前台的base.html模板导航栏区分用户端和管理员端。不需要做权限控制的RBAC只要判断当前登录用户是不是管理员即可在User模型里加一个is_admin字段后台所有视图检查这个字段不是就返回403或跳回首页。5. 最容易翻车的五个坑从Python环境到答辩现场断网5.1 Python环境问题安装、虚拟环境、解释器选择这一年带的学生里至少三分之一的人在环境配置上卡过。Windows上最常见的错误是没有勾选Add Python to PATH导致终端输入python没反应还有的是电脑里装了多个Python版本pip装包装到了别的版本的目录里代码运行起来报ModuleNotFoundError。建议的解决路径在python官网下载安装包时勾选Add to PATH装完后重启终端验证。创建虚拟环境python -m venv venv激活Windows的venv\Scripts\activatemacOS/Linux的source venv/bin/activate再pip install。如果你用VS Code写代码按CtrlShiftP找到Python: Select Interpreter选择刚刚创建的虚拟环境底部的解释器路径必须指向venv里的python.exe。这几步看起来很基础但做对了后面的问题会少很多。5.2 数据库连接报错pymysql和编码最常见的报错是ModuleNotFoundError: No module named pymysql或者连接串写错。SQLAlchemy连接MySQL的URI格式是SQLALCHEMY_DATABASE_URI mysqlpymysql://用户名:密码localhost:3306/merch_shop?charsetutf8mb4注意末尾的charsetutf8mb4少了它之后往数据库写中文和emoji都可能出问题。如果创建数据库时已经指定了utf8mb4这个参数依然建议写上双保险。如果提示Access denied for user先检查用户名密码是否正确再确认MySQL服务是否真的在运行。Windows上可以在服务里查MySQL服务状态。5.3 模板里引用静态资源别用相对路径Jinja2模板里引用CSS和JS一定要用url_for(static, filenamecss/bootstrap.min.css)这种方式它会自动生成正确的静态文件路径。有些学生图省事写href../static/css/bootstrap.min.css页面在多级路由下就会出现资源404。另外一个经常被忽略的问题Bootstrap框架文件是下载到本地static目录还是用CDN链接我强烈建议下载到本地。很多学校的答辩教室网络不稳定甚至没有网络一旦CDN加载不出来整个页面会变成一个纯文字界面视觉效果大打折扣。提前把Bootstrap的css和js文件放进static目录答辩时就永远不会被断网坑到。5.4 Session和flash的坑如果没有配置SECRET_KEY直接使用Session会在运行时抛异常。配置方法app.config[SECRET_KEY] 随便写一个足够长的字符串还有一个体验问题用flash()传递提示信息时base.html模板里必须加上闪现消息的渲染逻辑{% with messages get_flashed_messages() %} {% if messages %} div classalert alert-info {% for message in messages %} p{{ message }}/p {% endfor %} /div {% endif %} {% endwith %}不然后端写了flash(操作成功)前端一点反应都没有学生会反复怀疑是逻辑写错了其实是模板没渲染。5.5 商品图片显示问题商品图片一般有两种处理方式上传到本地static/uploads目录或者存一张外链URL。毕设推荐用本地上传因为答辩时数据库里如果全是别人的图片链接一旦无网或者对方服务器挂了商品图就是一片空白。图片上传功能用Flask的request.files接收保存时用werkzeug.utils.secure_filename处理文件名防止路径穿越。商品表里存的是相对路径模板里用url_for(static, filenameproduct.image_url)拼接。上传文件大小可以限制一下app.config[MAX_CONTENT_LENGTH] 5 * 1024 * 1024超过5MB的图片直接拒绝省得有学生传十几MB的图把页面拖垮。6. LW文档怎么和源码对得上毕业设计文档的写作顺序6.1 文档不是最后才写而是每做完一个模块就写一节很多人的毕业论文都是代码写完熬几天通宵赶出来的这样写出来的文档和实际代码之间一定有出入。老师的文档查重和代码核对不一定很严格但答辩提问时你一定会露馅——你文档里画的数据流图和你代码里实际跑的流程对不上被追问两句就交代了。我的建议是每完成一个功能模块马上在文档里记录它的设计思路和实现要点。具体来说写完用户表结构就去写数据库设计章节写完购物车就去写购物车模块的详细设计。这样到最后只需要补一个摘要和结论压力会小得多。一篇LW文档的核心章节顺序一般是摘要、选题背景与意义、可行性分析、需求分析、系统概要设计架构图模块划分、数据库设计、系统详细设计每个模块怎么实现的、系统测试功能测试用例、总结与展望。6.2 文档里的图和代码要能讲出来毕设文档里放图是必须的。架构图用Visio或draw.io画模块分层结构ER图实体关系图放数据库关系时序图或流程图画出下单和支付的流程。但记住一点图中每一个模块、每一条数据流你都要能对着源代码指出来在哪一行实现。有一次模拟答辩我让学生讲订单流程他图里画的是支付回调触发的支付成功但代码里写的是同步跳转支付成功页这就是典型的图和代码脱节。所以画图时尽量画你代码里确实存在的东西。比如下单流程就是购物车页面点结算、进入订单确认页、提交订单生成待支付订单、进入支付页、点确认支付、订单状态变为已支付。把每一步的方法数和文件路径标注在旁边这份文档不仅老师看了觉得扎实你自己答辩前复习也方便。6.3 关于毕设源码和文档的打包买源码也好、自己写也好最终交付的成果通常是一个压缩包加一份Word文档。源码包里要有清晰的README写明Python版本、依赖库、数据库初始化脚本、运行步骤。数据库脚本里至少包含建库建表的SQL和几条测试数据。这里有个细节如果数据库里没有测试数据老师第一眼看不到商品、分类、订单的展示效果印象分会受影响。备份一份带测试数据的SQL文件并在README里说明导入方式。文档的篇幅没有硬性要求本科毕业设计一般建议在8000到15000字之间。内容讲究的是完整而不是字数凑数。需求分析要和功能模块一一对应测试部分要有测试用例表用例编号、测试步骤、预期结果、实际结果、是否通过这些表格形式的内容在文档里比大段文字更有说服力。6.4 答辩时可能会被问到的问题最后说几个答辩时高频出现的问题提前准备好就不会慌库存扣减怎么防止超卖回答用了with_for_update()行锁在事务中先锁行再扣减。密码存的是明文吗回答用了Werkzeug的哈希算法存的是加盐哈希值不存明文。为什么订单明细里还要冗余商品名称和价格回答因为订单需要保存下单时的快照避免商品后续修改影响历史数据。整个系统用了哪些表表之间关系是什么回答六张表用户一对多订单订单一对多明细用户一对多购物车分类一对多商品。如果并发多个用户同时抢一件库存只有1的商品怎么办回答通过数据库行锁和事务保证只有一个事务能成功扣减到1其他事务重试或返回库存不足。这些问题全部指向你代码里真实存在的东西只要文档是配合代码写的回答起来就是水到渠成的事。这个题目做完之后我自己最大的感受是它把电商系统的核心链路浓缩得刚刚好每个模块都能展开每个模块又不会失控。如果你正在做这个题目别急着堆功能先把用户、商品、购物车、订单这条主线跑通再往上加推荐、评论、优惠券这些加分项。主线稳了这个毕设就稳了。
返回列表