ARTICLE DETAIL

资讯详情

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

网上鲜花销售系统毕设实战:从数据库设计到Docker部署全流程

网上鲜花销售系统毕设实战:从数据库设计到Docker部署全流程 简介这是一套基于Django与Vue的网上鲜花销售系统毕业设计资源面向计算机相关专业学生、开发者及小微商家用于完成课程设计、毕业设计或快速搭建在线花店。系统覆盖鲜花展示、购物车、订单管理、用户管理等核心模块并包含用户注册登录、鲜花分类浏览、花语信息展示、购物车增删改、订单状态跟踪等典型电商流程。后端采用Django框架前端使用Vue框架配套PycharmVscode开发环境可在Windows、Linux及macOS上运行。资源包共231个文件约10.91MB包含93个Python源码文件后端逻辑与接口、22个HTML与11个CSS及22个JS文件前端页面与交互、32个PNG和12个JPG图片界面素材与效果图以及8个PSD设计稿、XML、JSON、TTF字体等辅助文件目录按功能模块划分便于对照学习。已有194人浏览学习适合需要参考完整前后端分离项目实现、理解订单与购物车业务流程的读者。1. 网上鲜花销售系统毕设的边界在哪里毕业设计网上鲜花销售系统第一反应是“电商项目”但只做成商品展示加购物车就太单薄了。真正的难点在于把一条完整业务链跑通用户注册登录、按分类浏览和搜索鲜花、加入购物车、提交订单、模拟支付、后台发货以及每个环节里库存和状态的联动。很多同学的代码在本地能点通答辩时被问到“库存怎么防止超卖”“订单状态由谁更新”“后端接口如何鉴权”就露馅。这套方案会从数据库字段设计讲到前后端联调再落到云服务部署每步都是可以照着做的代码和命令适合想做出一个能演示、能扛住追问的毕设项目的人。2. 网上鲜花销售系统的技术选型与数据库设计2.1 技术栈对比为什么 Spring Boot Vue 更容易过答辩毕设选题固定后下一步是选技术组合。常见做法有四种各有各的代价。JSP Servlet JDBC 最传统代码都堆在 JSP 里逻辑讲不清SSH 配置繁琐容易陷入 XMLSpring Boot Vue 前后端分离接口清晰答辩时可以展示接口设计、事务、部署三条主线Django Vue 适合熟 Python 的同学。我一般推荐 Spring Boot 加 Vue因为 Spring Boot 内置 Tomcat不需要理解复杂的容器配置前端由 Vue 做交互后端只提供 JSON联调时双方关注点也单一。如果学校不允许前后端分离可以用 Thymeleaf 直接渲染页面后端业务代码几乎不用改。这里给一个对比表方便答辩时说明你做过选型方案上手曲线答辩表现维护成本JSP Servlet JDBC平缓代码堆在页面难点讲不清高Spring SpringMVC MyBatis中等配置多容易被追问细节中Spring Boot Vue MySQL中等前后端职责清晰低Django Vue平缓admin 后台演示效果好低选型理由要落在一句话优先保证在有限时间内做出完整闭环而不是炫技术。Spring Boot 生态里 MyBatis-Plus、Spring Validation、JWT 都有现成 starter网上示例和踩坑记录也多真卡住时更容易搜到答案。2.2 六张表打通商品到订单的库表设计网上鲜花销售系统的核心数据落在六张表分类表、鲜花商品表、用户表、购物车表、订单主表、订单明细表。下方是 MySQL 8 建表脚本字段命名统一用下划线便于 MyBatis-Plus 自动驼峰映射。CREATE TABLE category ( id BIGINT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(50) NOT NULL COMMENT 分类名如玫瑰、百合、绿植, sort INT DEFAULT 0 COMMENT 排序权重越小越靠前, create_time DATETIME DEFAULT CURRENT_TIMESTAMP ) COMMENT 鲜花分类; CREATE TABLE flower ( id BIGINT PRIMARY KEY AUTO_INCREMENT, category_id BIGINT NOT NULL, name VARCHAR(100) NOT NULL COMMENT 商品名如红玫瑰礼盒, price DECIMAL(10,2) NOT NULL COMMENT 售价单位元, stock INT NOT NULL DEFAULT 0 COMMENT 库存数量, image_url VARCHAR(255) COMMENT 图片地址, status TINYINT DEFAULT 1 COMMENT 1上架 0下架, sales INT DEFAULT 0 COMMENT 销量用于列表排序, create_time DATETIME DEFAULT CURRENT_TIMESTAMP ) COMMENT 鲜花商品表; CREATE TABLE user ( id BIGINT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL UNIQUE, password VARCHAR(100) NOT NULL COMMENT 存 bcrypt 哈希不存明文, phone VARCHAR(20), address VARCHAR(255), create_time DATETIME DEFAULT CURRENT_TIMESTAMP ) COMMENT 用户表; CREATE TABLE cart ( id BIGINT PRIMARY KEY AUTO_INCREMENT, user_id BIGINT NOT NULL, flower_id BIGINT NOT NULL, quantity INT NOT NULL DEFAULT 1 COMMENT 加购数量, checked TINYINT DEFAULT 1 COMMENT 勾选结算, update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, UNIQUE KEY uk_user_flower (user_id, flower_id) ) COMMENT 购物车表; CREATE TABLE orders ( id BIGINT PRIMARY KEY AUTO_INCREMENT, order_no VARCHAR(32) NOT NULL UNIQUE COMMENT 业务订单号要有时序, user_id BIGINT NOT NULL, total_price DECIMAL(10,2) NOT NULL COMMENT 订单总价快照, status TINYINT DEFAULT 0 COMMENT 0待付款 1已付款 2已发货 3已完成 4已取消, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, pay_time DATETIME NULL ) COMMENT 订单主表; CREATE TABLE order_item ( id BIGINT PRIMARY KEY AUTO_INCREMENT, order_id BIGINT NOT NULL, flower_id BIGINT NOT NULL, flower_name VARCHAR(100) NOT NULL COMMENT 商品名称快照, price DECIMAL(10,2) NOT NULL COMMENT 下单时价格快照, quantity INT NOT NULL ) COMMENT 订单明细表;订单表和明细表里保存flower_name和price快照是特意为之鲜花价格会随节日波动历史订单必须保留下单时的信息。直接在详情页查商品表虽然省事但商品改价后订单对不上属于明显的数据设计失误。另一个值得注意的点是cart表的唯一键(user_id, flower_id)它让同一用户对同一鲜花的加购在数据库层面只有一条记录应用层不需要再写“如果存在就 update否则 insert”的复杂判断。2.3 外键、索引和字段取舍的边界我建议不要建物理外键只保留逻辑关联。毕设阶段会频繁清表和导入测试数据物理外键会让DELETE FROM flower报错影响演示节奏真正生产环境里分库分表也要求逻辑外键。答辩时可以主动解释“外键约束放在应用层数据库用索引和唯一键保证核心约束。”索引方面flower表的常用查询条件有status、category_id、price可以建联合索引(category_id, status, price)orders表的查询入口是user_id status建议建普通索引(user_id, status)。订单号order_no需要唯一索引前端生成重复随机数时数据库会兜底报错不会产生脏订单。使用DECIMAL(10,2)存价格而不是FLOAT避免二进制浮点带来的 0.1 精度问题。3. 网上鲜花销售系统的后端核心商品、购物车与订单接口3.1 接口设计与统一返回体后端接口在设计阶段就把路径定死前后端都省心。下面这张表是网上鲜花销售系统最核心的四个接口答辩前一定要能说清每一个的入参和出参接口方法关键参数作用/api/flowerGETpage, size, categoryId, keyword商品分页筛选/api/cartGET无当前用户购物车/api/cartPOSTflowerId, quantity加入购物车/api/orderPOSTcartIds提交订单并扣库存后端接口如果每个方法返回不同结构前端联调会非常痛苦。我一般先定义一个ResultTData public class ResultT { private Integer code; // 0 成功非 0 业务异常 private String msg; private T data; public static T ResultT ok(T data) { ResultT r new Result(); r.code 0; r.msg success; r.data data; return r; } public static T ResultT error(String msg) { ResultT r new Result(); r.code 1; r.msg msg; return r; } }这是所有接口的统一返回体。前端拿到code 0才取dataHTTP 状态码只表示传输层结果。这样“库存不足”“未登录”这类业务失败可以走同一个弹窗逻辑不会被打进 axios 的error分支。再配一个RestControllerAdvice兜住 RuntimeException代码量能省很多。3.2 商品分页与条件筛选接口鲜花首页需要支持分页、分类筛选、关键词搜索、按销量排序。用 MyBatis-Plus 的分页插件Controller 代码GetMapping(/api/flower) public ResultPageFlower list( RequestParam(defaultValue 1) long page, RequestParam(defaultValue 12) long size, RequestParam(required false) Long categoryId, RequestParam(required false) String keyword) { LambdaQueryWrapperFlower wrapper new LambdaQueryWrapper(); wrapper.eq(Flower::getStatus, 1); if (categoryId ! null) { wrapper.eq(Flower::getCategoryId, categoryId); } if (StringUtils.hasText(keyword)) { wrapper.like(Flower::getName, keyword); } wrapper.orderByDesc(Flower::getSales); return Result.ok(flowerService.page(new Page(page, size), wrapper)); }参数说明page从 1 开始size控制在 12 到 20 之间一次返回太多会拖慢移动端渲染。keyword使用LIKE在几千条测试数据下没有问题但答辩时要补一句“生产环境会用全文索引或 Elasticsearch”显得你知道边界。orderByDesc(Flower::getSales)让销量高的鲜花排前面符合鲜花礼赠场景的挑选习惯。3.3 购物车接口累加与商品状态校验加购接口的常见问题是后端不校验商品状态导致下架商品残留在购物车。完成校验后再做“已存在则加数量”操作public ResultVoid addCart(Long userId, Long flowerId, Integer quantity) { Flower flower flowerMapper.selectById(flowerId); if (flower null || flower.getStatus() ! 1) { return Result.error(鲜花已下架); } if (quantity null || quantity 1 || quantity 99) { return Result.error(数量需在1-99之间); } Cart cart cartMapper.selectOne( new LambdaQueryWrapperCart() .eq(Cart::getUserId, userId) .eq(Cart::getFlowerId, flowerId)); if (cart null) { cart new Cart(); cart.setUserId(userId); cart.setFlowerId(flowerId); cart.setQuantity(quantity); cartMapper.insert(cart); } else { cart.setQuantity(cart.getQuantity() quantity); cartMapper.updateById(cart); } return Result.ok(null); }临界参数说明quantity上限 99 防止手滑也可以取Math.min(quantity, flower.getStock())但不要在购物车阶段锁库存。库存扣减的真正时机是下单事务购物车阶段只做基本校验这符合电商系统的普遍做法。3.4 下单事务乐观锁扣库存与订单快照下单是毕设里最容易翻车的点因为它同时触达购物车校验、总价计算、库存扣减、订单生成、购物车清理五件事。必须用Transactional把它们包进一个事务Transactional(rollbackFor Exception.class) public ResultOrderVO submit(Long userId, ListLong cartIds) { ListCart carts cartMapper.selectBatchIds(cartIds); long total 0; ListOrderItem items new ArrayList(); for (Cart cart : carts) { if (!cart.getUserId().equals(userId)) { throw new BizException(购物车数据异常); } Flower flower flowerMapper.selectById(cart.getFlowerId()); if (flower.getStock() cart.getQuantity()) { throw new BizException(库存不足); } int updated flowerMapper.deductStock( flower.getId(), cart.getQuantity(), flower.getStock()); if (updated 0) { throw new BizException(库存已被其他用户修改请重试); } total flower.getPrice() * cart.getQuantity(); OrderItem item new OrderItem(); item.setFlowerId(flower.getId()); item.setFlowerName(flower.getName()); item.setPrice(flower.getPrice()); item.setQuantity(cart.getQuantity()); items.add(item); } Order order new Order(); order.setOrderNo(UUID.randomUUID().toString().replace(-, )); order.setUserId(userId); order.setTotalPrice(total); order.setStatus(0); orderMapper.insert(order); items.forEach(item - item.setOrderId(order.getId())); orderItemService.saveBatch(items); cartMapper.deleteBatchIds(cartIds); return Result.ok(order); }对应的 SQL 更新方法UPDATE flower SET stock stock - #{quantity}, sales sales #{quantity} WHERE id #{id} AND stock #{quantity} AND stock #{oldStock}扣库存 SQL 里的stock #{quantity}是防超卖的底线stock #{oldStock}是乐观锁条件避免两人同时读到同一个旧库存后叠加覆盖。把它放在事务里后MySQL 会对该行加锁第二个请求的更新会等到第一个事务提交影响行数为 0 时直接抛异常回滚。这一层讲到“行级锁乐观锁”就已经超过大多数毕设的深度了。注意Transactional只对 Spring 代理对象生效在同一个类内部调用submit会绕过事务必须从 Controller 调用 Service 的 public 方法。4. 网上鲜花销售系统的前端页面与 API 联调4.1 初始化 Vue 项目并解决跨域前端采用 Vue 3 Vite Pinia 是较稳妥的组合。创建项目后安装依赖npm create vuelatest flower-shop-front npm install npm install axios pinia开发环境里前端跑在 5173 端口后端跑在 8080 端口直接请求会出现跨域报错。不要为了省事在 Spring Boot 上加CrossOrigin(*)那会把接口开放给所有站点不符合基本安全习惯。正确做法是 Vite 代理// vite.config.js export default defineConfig({ server: { port: 5173, proxy: { /api: { target: http://localhost:8080, changeOrigin: true } } } })这里没有写rewrite意思是后端接口必须带有/api前缀。changeOrigin会把请求头里的Host改成目标地址防止后端容器基于 Host 做校验如果你的后端 Controller 没有/api前缀需要自己用rewrite去掉但这个动作会让浏览器看到的 URL 和后端实际路径不一致排错时容易糊涂我一般选择后端统一加前缀。4.2 商品列表加载与搜索防抖商品列表是纯展示页面数据流简单。在FlowerListView.vue里定义筛选条件并请求接口const page ref(1) const size ref(12) const categoryId ref() const keyword ref() const list ref([]) async function loadFlowers() { const { data } await axios.get(/api/flower, { params: { page: page.value, size: size.value, categoryId: categoryId.value, keyword: keyword.value } }) list.value data.data.records }搜索框的v-model每次输入都会触发下拉如果每次下拉都发一次请求演示时会看到明显的请求抖动画线。我用lodash的debounce包装一下watch(keyword, debounce(() { page.value 1 loadFlowers() }, 300))防抖不是毕设硬性要求但这是前端工程素养的体现。300ms 是常用值输入停顿超过 300ms 才请求既不会让人觉得延迟又能过滤连续打字产生的无效请求。4.3 购物车状态管理与提交订单后的清理购物车数据我放在 Pinia 里统一维护避免“商城页加购后购物车页刷新才看到”的割裂感。初始化时调一次/api/cart/list加购成功后更新本地 store再重新拉取数量。提交订单成功后的处理顺序是清空 store 中的已购项、刷新购物车角标、跳转订单详情页。如果只调用后端删除而忘记更新本地状态页面顶部购物车数量会用上一次的缓存这个小瑕疵很容易被答辩老师看到。联调频率最高的一个坑是后端主键用雪花 ID 时JavaScript 解析 Long 丢精度。假设后端返回flowerId是1616837925357731842前端拿到后末尾变成...1800再传到后端就匹配不到数据。解决办法是在 Spring Boot 里对所有Long类型的 ID 字段加JsonSerialize(using ToStringSerializer.class)或统一配置 Jackson 把Long序列化为字符串。这是一个“不发现在生产就发现在答辩”的经典问题。4.4 联调必查的 4 类报错联调阶段的报错绝大多数可以归类为四种记住排查顺序能省一半时间。下面这张表按现象分列现象原因处理方式控制台报 CORS 错误Vite 代理未生效或路径前缀不一致检查 vite.config.js 和后端实际路径请求返回 401JWT 缺失、过期或格式不对检查 Authorization 请求头后端返回日期带 TLocalDateTime 默认序列化配置 Jackson 或前端用 dayjs 格式化参数接收不到请求体格式与 RequestBody 不匹配统一用 JSON.stringify第一类问题看请求 URL 是否为/api再看 Vite 终端有没有代理日志第二类检查Authorization是否以Bearer开头以及登录接口返回的 token 有没有存进 Pinia第三类比较隐蔽需要前后端约定日期格式第四类在post方法里没有设置Content-Type: application/json时最常见。把这四类提前写进测试用例联调会顺利很多。5. 用 Docker Compose 部署网上鲜花销售系统到云服务器5.1 最小可用的 Compose 与 Nginx 配置答辩时有一个公网可访问的演示地址是明显的加分项。这里用 Docker Compose 编排 MySQL、后端和 Nginx前端dist静态文件交给 Nginx 托管。目录结构保持backend/、frontend/dist、nginx/flower-shop.conf三个单元即可。docker-compose.yml核心内容services: mysql: image: mysql:8.0 environment: MYSQL_ROOT_PASSWORD: change-me MYSQL_DATABASE: flower_shop backend: image: openjdk:17-jdk-slim volumes: - ./backend:/app command: java -jar /app/flower-shop.jar ports: [8080:8080] frontend: image: nginx:1.25 volumes: - ./frontend/dist:/usr/share/nginx/html - ./nginx/flower-shop.conf:/etc/nginx/conf.d/default.conf ports: [80:80]Nginx 配置location / { root /usr/share/nginx/html; try_files $uri $uri/ /index.html; } location /api { proxy_pass http://backend:8080; }参数说明ports用列表写法减少重复键backend不暴露给宿主以外的网络。try_files必须保留否则 Vue Router 的 history 模式刷新子页面会 404。proxy_pass后端地址用服务名backendDocker 内部 DNS 会自动解析。5.2 部署后的一个验证技巧部署成功可以先用docker compose ps确认三个容器都在运行再curl http://localhost/api/flower看是否返回 JSON如果 404先检查/api转发的路径和后端前缀是否一致。最后把 MySQL 初始化脚本放进数据卷演示完执行docker compose down -v docker compose up -d恢复初始环境。这个重置动作控制在五分钟内能应对评委现场要求重新演示的突发情况。本文还有配套的精品资源点击获取
返回列表