网上购物系统论文+源码:事务一致性与库存扣减实战

发布时间:2026/11/4 16:37:33
网上购物系统论文+源码:事务一致性与库存扣减实战 简介面向计算机专业毕业设计、课程设计及Web开发初学者这份资料围绕基于Web的网上购物系统提供论文文档与完整源码帮助读者理解从需求分析、系统设计到前后端交互、数据库存取及后台配置的实现流程。压缩包共2000个文件、约200.04MB以693个JavaScript、275个CSS、247个HTML和75个ASP.NET页面为主辅以137个C#源码、252个GIF及91个PNG/72个JPG界面素材并含DLL依赖、配置文件、项目解决方案、Access数据库文件与论文文档覆盖前端展示、异步请求、服务端逻辑和资源素材等层次。内容可见验证码、AJAX提交、后台管理与用户中心等模块适合对照论文梳理模块划分、接口调用和页面组织。已有82人学习可作为选题参考、源码阅读与二次开发的基础材料。1. 别急着双击解压一套「论文源码」的网上购物系统真正值钱的是它的一致性去年帮人看一份课设代码能跑页面挺漂亮答辩时被问了一句“你下单的时候库存是怎么扣的”当场卡住。基于 Web 的网上购物系统的设计与实现这类题目在毕业设计里出现频率极高难的不是把页面画出来难的是商品、购物车、订单、库存这四条链路在数据库层面能不能对得上。这份「论文源码」压缩包的价值一半在源码里那套可运行的最小电商闭环一半在论文里那些 E-R 图、时序图、模块划分——它们给了你一个可以逐行比对的参照物。适合三类人要交课程设计、想把论文和代码对齐的初中级后端需要一套完整业务骨架做二次开发的以及想拿真实业务场景练手事务和并发的人。2. 拆包与栈判定从目录结构到跑起来只要六步拿到压缩包的第一反应不该是双击启动类而是先花五分钟搞清楚它用了什么栈。这类题目常见的组合是 Spring Boot MyBatis MySQL Thymeleaf 或 Layui 前端也有相当一部分是 SSM 老结构或者 PHP 版本。栈判断错了后面每一步都是白费。2.1 四个文件定乾坤先看 pom、application、sql、webapp解压后不要递归展开所有目录直接在根目录找四类东西。第一是构建文件pom.xml说明是 Maven 的 Java 项目build.gradle是 Gradlecomposer.json是 PHPpackage.json加vite.config.js则是前后端分离的 Node 项目。第二是配置文件application.yml、application.properties、config.php里面藏着端口、数据源、上传路径。第三是.sql脚本通常叫shop.sql、db.sql、init.sql这是能不能跑起来的关键。第四是视图层目录src/main/resources/templates是 Thymeleafwebapp/WEB-INF/jsp是 JSP独立的frontend/目录则说明前端单独构建。看到的文件判断结论后续动作pom.xmltemplates/*.htmlSpring Boot ThymeleafJDK 8/11 Maven直接mvn spring-boot:runpom.xmlwebapp/WEB-INFSSM 或 Spring MVC需要外置 Tomcat注意 Servlet 版本composer.jsonthink目录PHP 框架项目装 PHP 7.x Composer配伪静态根目录frontend/backend/前后端分离两端分别启动注意跨域和代理顺带看一眼pom.xml里的java.version和spring-boot-starter-parent版本号。我一般会先把 JDK 切到项目声明的那个大版本Spring Boot 2.x 配 JDK 17 能跑但偶尔会在反射和字节码上翻车JDK 8 的项目用 11 更稳。2.2 建库、导数据、改连接串最容易卡住的三十分钟六步走完就算落地解压、判栈、建库、导脚本、改配置、启动。前两步上面说了重点在后四步。建库的时候字符集必须是utf8mb4不是utf8。商品标题里带个生僻字或者表情符号utf8三字节存不下插入直接报Incorrect string value这个坑后面还会提。# 1. 建库字符集和排序规则一次到位别用默认的 latin1 mysql -uroot -p -e CREATE DATABASE shop_db DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; # 2. 导入脚本注意脚本里如果自带 CREATE DATABASE 就先看一眼库名对不对 mysql -uroot -p shop_db sql/shop.sql # 3. 确认表数量和几张大表有没有数据 mysql -uroot -p shop_db -e SHOW TABLES; SELECT COUNT(*) FROM t_goods;导入完成后一定核对三件事表数量是否和论文里 E-R 图的实体数量对得上、商品表有没有初始数据空表意味着首页一片空白你会误以为是代码问题、管理员账号是不是脚本里预置的那条。很多包的管理员密码是明文存的登录不上不是代码坏了是脚本里的密码字段和你以为的不一样。改配置这一步application.yml里要动的地方通常只有数据源和上传目录server: port: 8080 servlet: context-path: /shop # 有这行的话访问地址要带前缀别直接开 localhost:8080 spring: datasource: url: jdbc:mysql://127.0.0.1:3306/shop_db?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/ShanghaiuseSSLfalseallowPublicKeyRetrievaltrue username: shop password: shop123 driver-class-name: com.mysql.cj.jdbc.Driver servlet: multipart: max-file-size: 5MB # 后台上传商品图超过这个值会抛 MaxUploadSizeExceededException max-request-size: 10MB mybatis: mapper-locations: classpath:mapper/*.xml configuration: map-underscore-to-camel-case: true # 库里的 goods_id 自动映射成 goodsId关掉会一片 null shop: upload-path: /data/shop/upload/ # 绝对路径Windows 下记得改成 D:/shop/upload/serverTimezone和characterEncoding这两个参数是血泪经验缺一个就会出现时间差八小时或者中文变问号而且报错信息完全不指向这两个参数。map-underscore-to-camel-case关掉的话MyBatis 查出来的实体字段全是 null页面显示有数据、后端拿到是空排查起来很折磨。启动命令按栈来# Maven 项目跳过测试启动更快首次启动会自动下载依赖 mvn clean spring-boot:run -DskipTests # 打成 jar 再跑适合反复重启的场景 mvn clean package -DskipTests java -jar target/shop-0.0.1-SNAPSHOT.jar # 如果 8080 被占临时换端口比去改配置文件快 java -jar target/shop-0.0.1-SNAPSHOT.jar --server.port80812.3 三个自检点能登录、能加购、能下单看到控制台打印Started Application in x seconds只说明容器起来了不说明业务通了。我会按顺序做三个自检任何一个失败就先修它别往下走。第一用脚本里的管理员账号登录后台能看到商品列表。这一步验证了数据源、MyBatis 映射、登录拦截器三件事。第二前台首页能加载商品图加购物车后购物车角标变化。这一步验证了静态资源映射和 Session。第三用一个普通账号完整走一遍下单然后去数据库里SELECT * FROM t_orders ORDER BY id DESC LIMIT 1确认订单表真的落了数据而不是页面上弹了个“下单成功”的假提示。注意不少包的前台下单是纯前端模拟alert(下单成功)之后就没了。这类情况不用惊讶它本来就是演示型实现你要做的是把它补成真落库——后面的章节讲的正是这块。3. 库表设计把论文里的 E-R 图翻译成能约束住业务的 SQL论文里那张 E-R 图通常是十几个实体连在一起但真正跑业务的只有六张。搞清楚哪六张以及哪些字段是冗余快照比背下整张图有用得多。3.1 六张核心表以及三个必须冗余的字段用户表、商品表、分类表、购物车表、订单主表、订单明细表这六张构成了最小闭环。地址表可以并进用户表也可以独立看论文怎么写。真正决定这套设计水平的是三个冗余字段订单明细里的goods_title和goods_price。很多人第一反应是“订单明细只存 goods_id价格去商品表 join 不就行了”这是最典型的新手写法。商品改价之后历史订单金额跟着变对账直接崩。合格的做法是在下单那一刻把商品名和单价快照进明细表之后商品怎么改都不影响历史订单。订单主表的total_amount。同理不要在展示时用明细表 sum 出来下单时算一次存下来退款、优惠、运费才有地方挂。购物车表的唯一索引。(user_id, goods_id)建唯一键重复加购走ON DUPLICATE KEY UPDATE quantity quantity 1比先查再插少一次往返也天然防了并发重复插入。3.2 建表脚本索引和约束一次建对-- 商品主表status 和 cat_id 建联合索引列表页按分类筛上架商品是最热查询 CREATE TABLE t_goods ( id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, cat_id INT NOT NULL DEFAULT 0 COMMENT 分类ID, title VARCHAR(120) NOT NULL COMMENT 商品名, price DECIMAL(10,2) NOT NULL COMMENT 售价单位元, stock INT NOT NULL DEFAULT 0 COMMENT 可售库存, cover VARCHAR(255) DEFAULT NULL COMMENT 主图相对路径, status TINYINT NOT NULL DEFAULT 1 COMMENT 1上架 0下架, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY idx_cat_status (cat_id, status) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT商品主表; -- 订单主表order_no 唯一索引是后面做幂等的关键别省 CREATE TABLE t_orders ( id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, order_no VARCHAR(32) NOT NULL COMMENT 业务单号对外展示, user_id BIGINT UNSIGNED NOT NULL, total_amount DECIMAL(10,2) NOT NULL COMMENT 下单时算好的总额, status TINYINT NOT NULL DEFAULT 0 COMMENT 0待付款 1已付款 2已发货 3已完成 4已取消, receiver VARCHAR(32) NOT NULL COMMENT 收货人快照, phone VARCHAR(20) NOT NULL, address VARCHAR(200) NOT NULL COMMENT 收货地址快照, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_order_no (order_no), KEY idx_user_created (user_id, created_at) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT订单主表; -- 订单明细商品名和单价在这里是快照商品表后续改价不影响历史单 CREATE TABLE t_order_item ( id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, order_id BIGINT UNSIGNED NOT NULL, goods_id BIGINT UNSIGNED NOT NULL, goods_title VARCHAR(120) NOT NULL COMMENT 下单时的商品名快照, goods_price DECIMAL(10,2) NOT NULL COMMENT 下单时的单价快照, quantity INT NOT NULL, PRIMARY KEY (id), KEY idx_order (order_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT订单明细; -- 购物车唯一键保证一个用户一个商品只有一行 CREATE TABLE t_cart ( id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, user_id BIGINT UNSIGNED NOT NULL, goods_id BIGINT UNSIGNED NOT NULL, quantity INT NOT NULL DEFAULT 1, updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_user_goods (user_id, goods_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT购物车;索引不是越多越好。t_goods上idx_cat_status是为了分类页t_orders上idx_user_created是为了“我的订单”倒序分页。如果论文里画了索引说明照着对一遍图上写了代码里没建答辩时会被追问。3.3 金额字段DECIMAL 而不是 FLOAT这条没有商量余地price DECIMAL(10,2)表示总共十位、两位小数最大能存到九千九百多万对课设绰绰有余。换成FLOAT或DOUBLE0.1 0.2 会算出 0.30000000000000004订单总额和明细之和对不上这种问题在小数点后第六位肉眼看不出来压力测试时才会暴露。还有一种做法是用INT存“分”前端展示时除以 100。金融系统确实这么干好处是彻底杜绝浮点误差、计算快。但对课设来说业务代码里到处除以 100 反而容易漏写错一个地方金额差一百倍。我一般建议数据库用DECIMALJava 侧用BigDecimal并且强制用BigDecimal.valueOf(double)或者字符串构造别用new BigDecimal(0.1)——那个构造函数会把 double 的误差原封不动带进去。// 正确字符串或 valueOf 构造scale 显式指定 BigDecimal price new BigDecimal(19.90); BigDecimal total price.multiply(BigDecimal.valueOf(qty)).setScale(2, RoundingMode.HALF_UP); // 错误new BigDecimal(19.90) 会得到 19.89999999999999857891452847979962825775146484375金额比较也不能用equals19.9和19.90的 scale 不同equals返回 false用compareTo(...) 0。这几行代码不长但它是所有“订单金额对不上”问题的根。4. 下单链路库存扣减的三种写法和一个事务边界从购物车点到“提交订单”中间隔着校验、算价、扣库存、写订单、清购物车五件事。这五件事要么全成要么全不成靠的就是Transactional画出来的那个圈。4.1 四步流程和它的一致性要求顺序上是这样先查购物车拿商品清单然后逐条校验商品是否上架、库存是否够接着算总额写订单主表和明细最后扣库存并清空购物车。校验必须在扣库存之前因为扣完再校验失败就得回滚白白消耗一次行锁。事务边界要包住“写订单 扣库存 清购物车”这三步但不能包住任何一次远程调用或者文件 IO。有些包在事务里发邮件、生成 PDF 发票一旦这些操作超时数据库连接会被长期占用高并发下连接池很快打满表现是“系统卡住不动”日志里没有任何异常——典型的黑匣子故障。还有一个容易忽略的点Transactional默认只对RuntimeException回滚你自定义的业务异常如果继承的是Exception抛出去事务照样提交库存扣了订单没写。统一写Transactional(rollbackFor Exception.class)这一个参数能省掉半天排查。4.2 扣库存的三种写法条件更新是性价比最高的那个第一种是先SELECT stock再UPDATE stock 新值。这是教科书式的错误示范两个线程同时读到 10各自减 1 写回 9实际卖了 2 件库存还有 9超卖就是这么来的。第二种是SELECT ... FOR UPDATE加行锁。能解决问题但锁的粒度是整行而且必须在事务里持锁时间长商品详情页但凡有点慢查询就会排队。第三种是条件更新把判断塞进WHERE!-- GoodsMapper.xml一行 SQL 同时完成判断和扣减影响行数为 0 就代表库存不够 -- update iddeductStock UPDATE t_goods SET stock stock - #{qty} WHERE id #{goodsId} AND stock #{qty} AND status 1 /update数据库对UPDATE语句本身加行锁stock qty这个条件和减法在同一个原子操作里完成不存在读写的窗口期。调用方只需要看返回的影响行数。Service public class OrderService { Resource private GoodsMapper goodsMapper; Resource private OrderMapper orderMapper; Resource private OrderItemMapper itemMapper; Resource private CartMapper cartMapper; // rollbackFor 必须写否则自定义的受检异常不会触发回滚 Transactional(rollbackFor Exception.class) public String submit(Long userId, ListCartItem items) { if (items null || items.isEmpty()) { throw new BizException(购物车为空); } BigDecimal total BigDecimal.ZERO; String orderNo OrderNoGenerator.next(userId); // 用户ID 时间戳 随机位 // 1. 先落订单主表拿到自增主键给明细用 Order order new Order(); order.setOrderNo(orderNo); order.setUserId(userId); order.setStatus(0); order.setTotalAmount(BigDecimal.ZERO); // 先占位算完再更新 orderMapper.insert(order); // 需要 useGeneratedKeystrue // 2. 逐行扣库存 写明细快照 for (CartItem it : items) { int rows goodsMapper.deductStock(it.getGoodsId(), it.getQuantity()); if (rows 0) { // 抛异常让整个事务回滚前面已经扣掉的库存自动还原 throw new BizException(库存不足或商品已下架 it.getGoodsId()); } Goods g goodsMapper.selectById(it.getGoodsId()); OrderItem oi new OrderItem(); oi.setOrderId(order.getId()); oi.setGoodsId(g.getId()); oi.setGoodsTitle(g.getTitle()); // 快照不是引用 oi.setGoodsPrice(g.getPrice()); // 快照单价 oi.setQuantity(it.getQuantity()); itemMapper.insert(oi); total total.add(g.getPrice().multiply(BigDecimal.valueOf(it.getQuantity()))); } // 3. 回填总额 清购物车仍在同一个事务里 orderMapper.updateAmount(order.getId(), total.setScale(2, RoundingMode.HALF_UP)); cartMapper.deleteByUserAndGoods(userId, items); return orderNo; } }这段代码里有三个参数值得单独说。useGeneratedKeystrue和keyPropertyid必须配在订单的 insert 上否则order.getId()是 null明细表写进去全是空 order_id页面查订单详情一条都查不出来。status 1这个条件是为了防止下架商品被下单配合前端的置灰双保险。total.setScale(2, RoundingMode.HALF_UP)是显式四舍五入不写的话某些数据库驱动会静默截断。还有一种乐观锁写法给商品表加version字段UPDATE ... SET stock stock - ?, version version 1 WHERE id ? AND version ?。它在“读多写少、冲突少”的场景下比条件更新更友好因为不用锁行等待。但购物车下单是典型的写冲突场景条件更新更直接。两种都能用别混着用。4.3 幂等用户手抖点两下别给他生成两个订单下单接口最常见的线上问题是重复提交。用户网络慢点了两下按钮两个请求几乎同时到达两次都扣了库存。前端置灰按钮只防君子防不住重放和并发。低成本方案是令牌加唯一索引。进入结算页时后端生成一个requestId存进 Session 或 Redis提交时带上服务端用SETNX抢占这个 key抢不到直接返回“请勿重复提交”。更稳的一层是给订单号建唯一索引order_no由用户 ID、时间戳、随机位拼成两个并发请求即使都进来了第二条插入也会因为uk_order_no冲突而失败整个事务回滚。// 令牌校验业务侧第一道闸 String key order:token: requestId; Boolean ok redisTemplate.opsForValue().setIfAbsent(key, 1, 30, TimeUnit.SECONDS); if (Boolean.FALSE.equals(ok)) { throw new BizException(请勿重复提交); }提示令牌的过期时间别设太长30 秒到 2 分钟够用。设成一小时用户下单失败想重试会被自己的令牌挡住。5. 避坑与排查跑不起来、图片 404、下单 500 的定位清单下面五条是我在别人的包上真实遇到过、并且每次都有人问的。每条按现象、原因、解决写遇到对得上的直接照做。5.1 启动阶段的两个高频报错现象一控制台刷出java.sql.SQLException: Access denied for user rootlocalhost或者连上了但所有查询报Unknown database shop。原因基本是两个配置文件里的库名和脚本里CREATE DATABASE的名字不一致或者 MySQL 8 的默认认证插件caching_sha2_password老驱动不认。解决办法是先SHOW DATABASES;核对库名然后确认连接串带了allowPublicKeyRetrievaltrueuseSSLfalse如果还是不行给这个账号单独改认证方式ALTER USER shop% IDENTIFIED WITH mysql_native_password BY shop123;改完FLUSH PRIVILEGES;。现象二Port 8080 was already in use。别急着去杀进程先确认是不是上一次启动的残留实例还挂着。Windows 上netstat -ano | findstr :8080找 PIDLinux/Mac 用lsof -i:8080确认是自己的 java 进程再kill。嫌麻烦就直接用--server.port8081起注意如果context-path配了/shop访问地址是http://localhost:8081/shop少写一段会 404很多人在这里怀疑代码坏了。5.2 运行阶段的三个诡异现象现象三页面能打开商品图全是裂图浏览器控制台一堆 404。原因通常有两个一是上传目录写死成了原作者机器上的绝对路径比如D:/work/shop/upload/你本地根本没这个目录二是静态资源被登录拦截器拦了。解决把shop.upload-path改成你本地存在的目录并确保它有读写权限同时检查拦截器的excludePathPatterns里有没有把/static/**、/upload/**放行。// 拦截器配置里必须放行静态资源否则连登录页的 CSS 都加载不出来 registry.addInterceptor(new LoginInterceptor()) .addPathPatterns(/**) .excludePathPatterns(/static/**, /upload/**, /login, /css/**, /js/**);现象四下单报 500日志里是Data too long for column address或者Incorrect string value: \xF0\x9F...。前者是收货地址字段长度不够VARCHAR(100)存不下长地址改成 200。后者是utf8mb4没配到位链条上任何一环是utf8都会炸库、表、列、JDBC 连接串的characterEncoding四处必须统一。改完记得ALTER TABLE t_goods CONVERT TO CHARACTER SET utf8mb4;把已有表也转过去。现象五压力测试下库存变成负数或者订单数比库存减少数少。这是第 4 章那个SELECT再UPDATE的写法在并发下的必然结果不是玄学。定位方法是开两个终端同时打同一个下单接口看t_goods.stock的最终值。解决办法就是改条件更新WHERE stock #{qty}一行 SQL 解决别去加分布式锁——课设的体量用不上加了反而引入新的不稳定因素。注意改完任何一处配置或 SQL先重启再测。热部署工具对 MyBatis 的 mapper XML 支持并不可靠改完不生效会让你误判问题没修好。6. 论文和代码对不上答辩演示路线与二次开发的切入点论文里的时序图、E-R 图、模块图答辩老师最爱拿去和代码对。我一般会先做一张对照表把论文里的每张图钉到具体文件上对不上的地方要么改图要么改代码绝不能留着。常见的有三处E-R 图里画了评价表但代码没有、时序图画了支付回调但实现是直接改状态、模块图把购物车和订单拆成两个服务但实际是一个 Controller。演示路线控制在五分钟内后台登录看商品列表前台搜索加购物车提交订单后在订单列表看到待付款进数据库查一次t_orders和t_order_item证明是真实落库最后展示库存从 100 变成 98。这条线走完商品、购物车、订单、库存四条链路就都覆盖了。二次开发最好从三个切入点挑一个加支付用状态机把待付款 → 已付款补上加一个超时取消的定时任务、加库存预警定时扫stock 10的商品推给后台、加订单状态流转日志一张t_order_log表每次状态变更插一条。第三个改动最小、收益最明显答辩时也有话说。从那以后我每次拿到这类「论文源码」的包都会先花十分钟把论文里的图挨个对照到文件再动手跑——省下的返工时间远比这十分钟多。希望帮到你。本文还有配套的精品资源点击获取

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询