资讯动态

JavaWeb购物商城源码跑通、下单事务与二次开发避坑

发布时间:2026/10/26 6:26:34 来源:尧图企业网站定制
简介这份JavaWeb购物商城源码包面向计算机专业学生与JavaWeb初学者适合课程设计、毕业设计及框架入门实践围绕用户注册登录、商品浏览、购物车管理、订单提交与后台管理等典型电商流程帮助读者理解JSP视图、Servlet控制器与MySQL持久化的协作方式。压缩包共270个文件、约7.08MB包含31个Java源文件与31个编译类、9个JSP页面、21个XML配置、10个JAR依赖以及CSS、JS、图片等静态资源和SQL数据库脚本结构上覆盖表现层、控制层、业务层与数据访问层便于对照学习分层设计与请求处理链路。已有1288人学习下载。读者可从中获得可运行的商城项目源码、数据库建表脚本、DAO与Service实现示例、订单和购物车业务代码以及JSP页面与Servlet交互的完整参考适合在此基础上做功能扩展与二次开发。1. 一套跑不起来的购物商城源码问题往往不在业务代码拿到的 JavaWeb 购物商城源码十个里有八个卡在同一处数据库连不上、页面 404、下单之后库存没动。业务逻辑写得其实不差商品列表、购物车、下单、后台管理这几条主链路都是通的真正把人挡在门外的是环境、编码、事务这些外围的东西。这份工程适合三类人想拿一套完整 CRUD 链路练手的学生、要快速搭一个电商原型验证业务的产品或创业团队、以及接私活需要一个商城底座的后端。它不适合直接上生产——但把它跑通、改明白比看十篇教程都实在。下面按跑起来 → 看懂主链路 → 改得动 → 不踩坑的顺序拆。2. 环境与配置把购物商城从压缩包跑到登录页先把结论摆前面整套东西能不能启动90% 取决于 JDK 版本、MySQL 版本和连接串这三样是否自洽。很多人一上来就翻 Controller 看业务结果控制台第一屏就是Communications link failure白白耗掉半天。这一章按装环境 → 建库 → 改配置 → 验证链路四步走每一步都给可抄的命令。2.1 JDK、Maven、MySQL 的版本对齐这套工程是典型的三层结构Spring Boot 起容器、MyBatis 做持久层、MySQL 存数据页面层可能是 Thymeleaf 也可能是 JSP具体看src/main/resources/templates或src/main/webapp目录哪个存在。版本上不需要追新追稳组件建议版本说明JDK8 或 11工程用javax.*就别上 17jakarta迁移会报一堆包找不到Maven3.6 及以上低版本对 HTTPS 私服支持差拉依赖容易卡MySQL5.7 或 8.08.0 默认caching_sha2_password老驱动连不上见 5.1连接池工程自带Druid 或 HikariCP看 pom 里引了哪个判断 JDK 要求最快的办法不是看 README是看pom.xml里的java.version和父工程版本再扫一眼有没有javax.servlet的 import。有就是 Spring Boot 2.x 时代的东西JDK 8/11 最省事。Maven 这边建议先配好镜像不然第一次mvn能卡到你怀疑网络。~/.m2/settings.xml里加一段国内镜像然后执行# 先只做依赖解析不启动看是否有拉不下来的包 mvn -U clean dependency:resolve -DskipTests # 依赖齐了再编译这一步能提前暴露 JDK 版本不匹配 mvn clean package -DskipTests第一条命令的-U是强制更新快照dependency:resolve只下依赖不干活出问题时代码无关纯粹是仓库或版本的事排查范围小很多。第二条package会走编译如果 JDK 版本过高这里就会报程序包 javax.servlet 不存在或者无效的目标发行版比等到启动时再懵要早得多。常见做法是给这个工程单独指定 JDK不去动全局环境变量。IDE 里在 Project Structure 把 SDK 设成 8Maven 的 Runner 里把 JRE 也设成 8两处不一致是最隐蔽的坑——IDE 里编译过了命令行mvn又挂。2.2 建库、导 SQL、改连接串数据库这一步别偷懒用图形工具点用命令行走一遍出错信息看得清楚。先看工程里有没有sql目录或者doc/db.sql多数商城工程会带一份建表脚本加几条测试数据# 登录注意 -p 后面不要跟空格再跟密码交互输入更安全 mysql -u root -p # 建库字符集一定用 utf8mb4商品名里带表情或生僻字不会炸 CREATE DATABASE shop DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; # 退出后导入--default-character-set 是关键不加这一步中文必乱码 mysql -u root -p --default-character-setutf8mb4 shop doc/db.sql # 验证看表数量和数据量别只看有没有报错 mysql -u root -p -e USE shop; SHOW TABLES; SELECT COUNT(*) FROM goods;--default-character-setutf8mb4这个参数是血泪经验。SQL 文件本身如果是 UTF-8 编码而客户端默认按 latin1 解析导进去的中文全是问号而且不报错等你打开页面才发现商品名叫???回头再改就得重导。建完库改配置文件。Spring Boot 工程看application.yml或application.propertiesSSM 老工程看jdbc.properties或mybatis-config.xml有的还把数据源写死在spring-dao.xml里搜一下jdbc:mysql就能定位spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://127.0.0.1:3306/shop?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/ShanghaiallowPublicKeyRetrievaltrue username: root password: 你的密码 # Druid 的话下面这段保留Hikari 就删掉 type: com.alibaba.druid.pool.DruidDataSource逐项说清楚这几行每年都要坑一批人driver-class-nameMySQL 8 用com.mysql.cj.jdbc.Driver5.x 用com.mysql.jdbc.Driver。写错直接启动失败报Cannot load driver class。characterEncodingutf8这是连接层的和库上的utf8mb4配合缺一个都可能乱码。serverTimezoneAsia/Shanghai不写的话8.0 驱动可能把时间按 UTC 存页面上的下单时间永远差 8 小时。allowPublicKeyRetrievaltrue8.0 默认认证插件需要它不加会报Public Key Retrieval is not allowed。useSSLfalse本地开发关掉不然控制台一堆警告有时还连不上。端口也别忽略。工程里server.port默认 8080如果本机起了别的东西启动会报Port 8080 was already in use。改端口是最快的解法但改完记得同步前端页面里写死的接口地址如果有否则页面能打开、数据全是空的。2.3 启动后先验证这四条链路启动成功不等于跑通。控制台出现Started Application in x seconds只是容器起来了业务链路一条都还没验证。按下面顺序点从静态到动态、从读 to 写出问题能立刻定位到层首页/或/index—— 验证模板引擎和静态资源是否正常。页面能出来但样式全丢是静态资源映射问题见 5.3。商品列表/goods/list或/list—— 验证数据源、Mapper、分页插件。这里报错基本就是数据库连不上或 SQL 写错。注册/登录 —— 验证 Session、密码加密、拦截器。登录后刷新页面掉登录态是 Session 配置或拦截器放行规则的问题。加购物车 → 下单 —— 验证事务和库存。这一条最容易看起来成功见第 3 章和 5.2。第 4 步建议手动盯三个数下单前goods.stock、order表新增行数、order_item表新增行数。三个数对不上说明事务或者关联写入有问题别急着往下测。提示第一次跑通之后立刻把数据库导出一份shop_ok.sql存好。后面改代码改崩了回滚数据库比回滚代码快得多这就是我说的后悔药。3. 下单事务购物车到订单这条链路到底发生了什么商城系统的核心不是商品列表是点结算到订单落库这几百毫秒。这段代码写得好不好决定了这套源码是能改改就用还是只能当练习。这一章拆三件事购物车存在哪儿、下单事务怎么组织、库存怎么扣才不超卖。三件事任意一件做错前面跑得再顺也是白搭。3.1 购物车放 Session 还是放表源码里常见的两套实现差别比想象中大方案实现方式优点代价Session 购物车存HttpSession里的 Map不用建表未登录也能用换浏览器就丢多实例部署要粘性会话持久化购物车建cart_item表登录后跨端同步可做降价提醒每次加减都要读写库要处理并发判断工程用的是哪种看有没有cart或cart_item表以及 Controller 里是不是直接session.setAttribute(cart, ...)。两套都存在的情况也有——未登录走 Session登录后合并进表。合并逻辑最容易出 bug同一个商品在 Session 里有 2 件、表里有 3 件合并成 3 还是 5多数实现是数量相加但库存上限校验经常漏掉合并完直接超库存。我一般建议把它改成持久化优先理由是下单时你需要拿到一个稳定的、可校验的购物车快照。Session 里的东西在支付回调、异步通知这些场景根本拿不到等于给后面埋雷。如果坚持用 Session至少把购物车项定义成可序列化的对象并且在用户登录成功的那一刻做一次合并落库之后一律读库。3.2 下单事务的代码骨架这是整篇文章最该抄的一段。下单要干四件事校验购物车、算钱、写订单主表、写订单明细并扣库存。四件事必须在一个事务里任何一步失败全回滚。Service public class OrderServiceImpl implements OrderService { Resource private OrderMapper orderMapper; Resource private OrderItemMapper orderItemMapper; Resource private GoodsMapper goodsMapper; Resource private CartService cartService; /** * rollbackFor Exception.class 必须写。 * 默认只对 RuntimeException 回滚很多业务异常继承的是 Exception不写就吞了。 */ Override Transactional(rollbackFor Exception.class) public Long submit(Long userId, ListLong cartIds) { // 1. 重新查一遍购物车价格、库存、上下架状态一律以库为准 // 前端传来的 price 只用于展示绝对不能参与计算 ListCartItem items cartService.listForSubmit(userId, cartIds); if (items null || items.isEmpty()) { throw new BizException(购物车为空或商品已下架); } // 2. 算钱全程 BigDecimal最后统一保留两位、四舍五入 BigDecimal total BigDecimal.ZERO; for (CartItem it : items) { if (it.getNum() null || it.getNum() 0) { throw new BizException(商品数量非法 it.getGoodsId()); } BigDecimal line it.getPrice().multiply(BigDecimal.valueOf(it.getNum())); total total.add(line); } total total.setScale(2, RoundingMode.HALF_UP); // 3. 先落订单主表拿到自增 id 再写明细避免明细找不到父单 Order order new Order(); order.setOrderNo(OrderNoGenerator.next(userId)); // 业务单号和主键分开 order.setUserId(userId); order.setTotalAmount(total); order.setStatus(OrderStatus.UNPAID.getCode()); order.setCreateTime(new Date()); orderMapper.insert(order); // 4. 逐条扣库存 写明细。扣减失败立刻抛异常整单回滚 for (CartItem it : items) { int affected goodsMapper.deductStock(it.getGoodsId(), it.getNum()); if (affected 0) { // 影响行数为 0 说明库存不足或商品被并发改过 throw new BizException(库存不足 it.getGoodsName()); } OrderItem oi new OrderItem(); oi.setOrderId(order.getId()); oi.setGoodsId(it.getGoodsId()); oi.setGoodsName(it.getGoodsName()); oi.setPrice(it.getPrice()); oi.setNum(it.getNum()); orderItemMapper.insert(oi); } // 5. 清购物车放在最后前面任何一步抛异常这里都不会执行 cartService.removeItems(userId, cartIds); return order.getId(); } }几个参数的取舍说明rollbackFor Exception.class这是必写项。我见过太多工程只写了Transactional然后自定义的BizException extends Exception扣库存失败抛出去事务照常提交订单生成了、库存没动页面还提示下单成功。这就是最典型的黑匣子故障。setScale(2, RoundingMode.HALF_UP)电商场景金额统一两轮用HALF_UP而不是HALF_EVEN财务对账时更符合直觉。计算过程中不要提前 setScale最后一步做减少累计误差。orderNo单独生成不要拿自增主键当业务单号一是暴露订单量二是分库分表后没法用。常见做法是日期 用户 id 后四位 随机数或者雪花算法只要保证唯一即可。清购物车放最后这是顺序问题不是逻辑问题。放前面的话扣库存失败回滚了购物车却被清空了用户得重新加一遍体验灾难。3.3 库存扣减的三种写法与选型上面代码里的deductStock是整个下单链路最关键的一条 SQL。三种常见写法差别很大-- 写法一先查后改最差并发下必超卖 SELECT stock FROM goods WHERE id #{goodsId}; -- 应用层判断 stock num 后再 UPDATE UPDATE goods SET stock #{newStock} WHERE id #{goodsId}; -- 写法二条件更新推荐一条 SQL 搞定天然防超卖 UPDATE goods SET stock stock - #{num} WHERE id #{goodsId} AND stock #{num}; -- 写法三乐观锁版本号需要重试适合有复杂校验的场景 UPDATE goods SET stock stock - #{num}, version version 1 WHERE id #{goodsId} AND stock #{num} AND version #{oldVersion};写法一的问题在于查和改之间有时间窗两个请求同时查到库存 1都判断通过最后都减 1库存变成 -1。这就是超卖的经典成因。写法二用AND stock #{num}把判断塞进 SQL靠 InnoDB 的行锁保证原子性affected 0就是扣减失败。绝大多数商城场景用这个就够了简单、无重试、无额外字段。写法三多一个version好处是能区分库存不足和被别人抢先改过代价是失败要重试。只有当你需要在扣库存前做比较复杂的校验比如限购、会员价计算才值得上。注意乐观锁一定要配重试次数上限不然高并发下会一直转。对应的 Mapper 接口就一行Update(UPDATE goods SET stock stock - #{num} WHERE id #{goodsId} AND stock #{num}) int deductStock(Param(goodsId) Long goodsId, Param(num) Integer num);Param两个都要写只写一个的话 MyBatis 在部分版本下解析不出参数名报Parameter num not found。这类错误信息不直观但原因很蠢。注意库存字段加个CHECK (stock 0)约束不能替代上面的条件更新它只会在扣成负数时抛异常异常信息对用户不友好而且部分老版本 MySQL 根本不让加。4. 列表与搜索把商品页做得不像个 Demo商城首页和搜索页是用户接触最多的页面也是最容易做成能用但很卡的地方。这一章解决两个具体问题分页怎么写才不拖库关键词搜索怎么做才不像全表扫描。两个问题在测试数据只有二十条时完全看不出来一旦数据上万页面直接转圈到超时。4.1 分页查询与联合索引分页的写法本身不复杂坑在索引。先看典型实现-- 商品列表按分类 上架状态过滤按 id 倒序 SELECT id, name, price, cover, stock, create_time FROM goods WHERE category_id #{cid} AND status 1 ORDER BY id DESC LIMIT #{offset}, #{size};offset (pageNo - 1) * pageSize这个换算在 Java 层做别在 SQL 里算。LIMIT的两个参数是跳过多少行和取多少行深翻页比如第 1000 页时 MySQL 要先把前面 20000 行扫出来再丢掉这就是深分页慢的根因。优化手段按性价比排建联合索引让WHERE和ORDER BY走同一个索引-- 字段顺序有讲究等值条件在前排序字段在后 ALTER TABLE goods ADD INDEX idx_cat_status_id (category_id, status, id);这个索引能同时覆盖category_id ?、status 1和ORDER BY id DESC避免 filesort。如果你的列表还带价格排序那就得再加一个(category_id, status, price)两个排序方向不能共用同一个索引。深分页改游标只允许下一页用上一页最后一条的 id 做条件。SELECT id, name, price, cover FROM goods WHERE category_id #{cid} AND status 1 AND id #{lastId} ORDER BY id DESC LIMIT #{size};lastId是上一页返回的最后一条记录的 id。这种写法不管翻到第几页扫描量都是size行。后台管理页面做跳页时不适合但面向用户的列表页完全够用何况大部分人根本翻不到第十页。总数查询单独走缓存。SELECT COUNT(*)在大表上比列表查询还慢如果前端只需要是否有下一页直接查size 1条多出来一条就说明还有。用 MyBatis-Plus 的话PageGoods对象传进去分页插件自动拼LIMIT但要注意两点一是PaginationInnerInterceptor必须注册不注册的话total永远是 0二是它默认会执行 count 查询深分页场景建议手动关掉自己写 count。4.2 关键词搜索LIKE 的边界在哪搜索框是最容易写歪的地方。默认实现基本都是这一行SELECT id, name, price FROM goods WHERE name LIKE CONCAT(%, #{kw}, %) LIMIT 20;能用但有两个硬伤%开头导致索引失效必然全表扫描数据量上去以后这个查询会拖慢整个库。数据量在五千条以内、QPS 不高的情况下这么写完全没问题别过度设计。超过这个量级再考虑升级方案一MySQL 全文索引5.7 支持 ngram 分词中文可用-- ngram 分词长度默认 2也就是按两字切分符合中文习惯 ALTER TABLE goods ADD FULLTEXT INDEX ft_goods_name (name) WITH PARSER ngram; -- 查询用 MATCH ... AGAINST能走索引 SELECT id, name, price FROM goods WHERE MATCH(name) AGAINST (#{kw} IN BOOLEAN MODE) LIMIT 20;注意IN BOOLEAN MODE下表示必须包含、-表示排除、*表示前缀匹配和自然语言模式行为不同。词长小于ngram_token_size默认 2的单字搜不到这是设计限制不是 bug。方案二独立搜索引擎。商品量大、需要分词、高亮、拼写纠错时再上代价是多一个中间件要维护。我的判断标准很简单搜索是核心功能比如垂直电商就早上只是辅助筛选就先用 MySQL 扛着。很多项目一上来就堆中间件结果运维成本比业务代码还高。方案三如果只是分类筛选 排序压根不需要搜索框。把分类、价格区间、销量排序做成固定条件全部能走索引性能和体验都比模糊搜索好。提示搜索词一定要做长度限制比如 20 字以内和特殊字符过滤%、_在 LIKE 里是通配符用户输个%进去就是全表扫描这是最便宜的拒绝服务。5. 避坑与排查这套购物商城最容易翻车的五处下面五条都是看起来成功、实际错了的类型比直接报错更折磨人。每条按现象、原因、解决写遇到对得上的直接照方抓药。5.1 启动与环境类问题现象一控制台报Unknown database shop或Access denied for user rootlocalhost。原因分两种库没建成或者连接串里的库名/账号密码和实际不一致。还有一种隐蔽情况——application.yml和application-dev.yml同时存在工程激活的是 dev 那个 profile你改的是主配置改了等于没改。解决先SHOW DATABASES;确认库存在再SELECT user, host FROM mysql.user;看账号的 host 是不是%或localhost。然后搜一遍工程里所有jdbc:mysql出现的位置确认只改在生效的那份配置上。启动日志里搜The following profiles are active看激活的是哪个。现象二启动报Public Key Retrieval is not allowed或者连接直接超时。原因MySQL 8.0 默认认证插件是caching_sha2_password老版本 JDBC 驱动不认。连接串没加allowPublicKeyRetrievaltrue也会报这个。解决两条路一是升级mysql-connector-java到 8.x 并补上连接参数二是把账号改成mysql_native_passwordALTER USER rootlocalhost IDENTIFIED WITH mysql_native_password BY 密码;然后FLUSH PRIVILEGES;。生产环境优先走第一条改认证插件是权宜之计。5.2 业务与数据类问题现象三下单接口返回成功订单表有数据但库存一点没减。原因事务没生效。最常见的是Transactional加在了 private 方法上或者同一个类里 A 方法直接调 B 方法自调用Spring 的代理根本没进去。还有一种是把异常 catch 住了没往外抛事务管理器认为一切正常。解决确认注解方法public、通过注入的 bean 调用而不是this.调用、异常往上抛。排查时在方法首尾各打一条日志配合logging.level.org.springframework.jdbcdebug看有没有Rollback或Commit输出。最直接的办法是在扣库存后手动throw new RuntimeException(test)看订单有没有回滚——不回滚就说明事务没管上。现象四金额出现99.98999999999999或者几分钱对不上账。原因price字段在 Java 里用了double/float或者数据库用FLOAT。浮点数本来就没法精确表示 0.1 这类值。解决数据库金额字段一律DECIMAL(10,2)Java 里一律BigDecimalMyBatis 映射到BigDecimal类型。构造BigDecimal不要用new BigDecimal(0.1)要用new BigDecimal(0.1)或者BigDecimal.valueOf()前者会带入 double 的误差。比较大小用compareTo()而不是equals()因为1.0和1.00的equals是 falsescale不同。现象五页面样式全丢F12 里 CSS/JS 全是 404或者本地正常打成 jar 后全部 404。原因拦截器把静态资源请求也拦了登录拦截器配了/**却没排除/css/**、/js/**、/static/**。打成 jar 之后还多一层JSP 在 Spring Boot 内嵌容器里默认不解析webapp目录也不会被打进 jar。解决拦截器addPathPatterns(/**)之后必须excludePathPatterns(/static/**, /css/**, /js/**, /images/**, /favicon.ico)。如果页面用的是 JSP要么改用 Thymeleaf要么老老实实打 war 包部署到外部容器。用 Thymeleaf 的话模板放src/main/resources/templates静态资源放src/main/resources/static这两个路径是框架约定别自己另起炉灶。注意还有一种本地正常、部署后 404是大小写问题。Windows 文件系统不区分大小写Linux 区分index.HTML和index.html在服务器上是两个东西。部署前统一检查一遍文件名。6. 二次开发把商城改成你自己的样子跑通之后多数人第一件事是改界面第二件事是加字段第三件事是发现自己改乱了。这一章讲怎么在不动主干的前提下做出差异化以及改完怎么验证没改坏。先看一套最小改动清单按风险从低到高排序建议严格按顺序做每做完一步提交一次改动内容涉及位置风险验证方式换配色、Logo、轮播图static/、模板文件低逐页刷新看布局加商品字段如品牌、规格表结构、实体、Mapper、表单中新增一条商品看全链路改订单号规则OrderNoGenerator中连续下 10 单查唯一性加订单状态如已发货状态枚举、前端筛选、后台操作高走完整状态流转接入支付回调新增 Controller 签名校验高沙箱环境模拟回调加字段这块有个固定套路照着做基本不会漏先在数据库加列给默认值别设 NOT NULL 又无默认值老数据会炸再改实体类再改 Mapper 的resultMap和insert语句再改前端表单最后改列表页展示。五处任何一处漏掉表现都是保存成功但看不见或者看得见但保存不上。一个具体的技巧给订单状态加流转校验而不是在页面上控制按钮显隐。页面上把发货按钮藏起来防不住手工构造请求真正的防线在 Service 层private static final MapInteger, ListInteger FLOW new HashMap(); static { // key 是当前状态value 是允许流转到的状态列表 FLOW.put(OrderStatus.UNPAID.getCode(), Arrays.asList(OrderStatus.PAID.getCode(), OrderStatus.CANCELED.getCode())); FLOW.put(OrderStatus.PAID.getCode(), Arrays.asList(OrderStatus.SHIPPED.getCode(), OrderStatus.REFUNDING.getCode())); FLOW.put(OrderStatus.SHIPPED.getCode(), Arrays.asList(OrderStatus.FINISHED.getCode())); FLOW.put(OrderStatus.REFUNDING.getCode(), Arrays.asList(OrderStatus.REFUNDED.getCode(), OrderStatus.PAID.getCode())); } public void changeStatus(Long orderId, Integer target) { Order order orderMapper.selectById(orderId); if (order null) { throw new BizException(订单不存在); } ListInteger allowed FLOW.getOrDefault(order.getStatus(), Collections.emptyList()); // 不在白名单里的流转一律拒绝前端按钮藏没藏都不影响 if (!allowed.contains(target)) { throw new BizException(非法状态流转 order.getStatus() - target); } orderMapper.updateStatus(orderId, target, new Date()); }FLOW这张表把业务规则收敛到一个地方加状态只改这里不用满工程搜if (status 1)。getOrDefault返回空列表而不是 null避免新状态忘了配的时候 NPE直接变成一律不允许这是安全的失败方式。验证方法也别只靠手点。改完核心链路后写一个不依赖容器的集成测试把下单成功和库存不足回滚两个场景各跑一遍Test Transactional // 测试完自动回滚不污染开发库 public void testSubmitWhenStockNotEnough() { // 把某个商品库存改成 1然后尝试下单 2 件 goodsMapper.updateStock(1L, 1); ListLong cartIds Arrays.asList(1001L); // 断言抛异常且订单表没有新增记录 assertThrows(BizException.class, () - orderService.submit(1L, cartIds)); assertEquals(1, goodsMapper.selectById(1L).getStock()); }这行Transactional在测试类上是回滚的意思和业务代码里的语义相反很多人第一次见会懵。它的作用是测试结束后把数据还原省得每次跑测试都要重新导库。最后说一个习惯。我以前改这类工程改完只在浏览器里点一遍主流程看着没问题就提交结果上线后发现并发下单会超卖——因为手工点根本模拟不出并发。从那以后我每次动到下单、库存、支付这三块都强制走一遍改前跑一次压测脚本、改后再跑一次的流程哪怕只是加了个字段。用几十行 JMeter 或者一段CountDownLatch起 50 个线程同时下单比肉眼盯一天都管用。这套购物商城源码本身不复杂真正决定它能不能用的是你愿不愿意在每次改动后把验证做实。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑