ARTICLE DETAIL

资讯详情

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

农产品预售平台开发实战:SpringBoot+Vue全栈解析

农产品预售平台开发实战:SpringBoot+Vue全栈解析 1. 农产品预售平台到底在解决什么问题先说个我自己的观察。这几年做农业信息化项目发现一个特别普遍的现象很多农户或者合作社手里有优质农产品但销售渠道基本靠批发商上门收货价格被压得死死的另一方面城里消费者想吃真正靠谱的产地直供农产品却找不到可信的购买渠道。两边信息严重不对称中间商层层加价最后农户没多赚消费者也没少花。所以当我接到这个“基于SpringBootVue的农产品预售平台管理系统”的开发需求时第一反应就是这不是一个普通的电商系统它最核心的业务逻辑是“预售”。预售和普通秒杀、普通下单完全不一样它要先有预售活动、用户预付定金、等到农产品成熟或采摘后再支付尾款、最后发货。这个流程里涉及到定金规则、尾款结算、库存锁定、订单状态流转稍有不慎就会出账目混乱。这个项目用到的技术栈是SpringBootVueJavaMySQLMyBatis是当下Java Web开发里非常主流、也特别适合做毕业设计或中小型企业级项目的组合。SpringBoot负责后端快速搭建Vue负责前端页面交互MySQL存数据MyBatis处理数据库操作。整套系统包含了用户端小程序风格/Web端均可、商家端、管理后台三套界面核心功能覆盖预售活动管理、商品管理、订单管理、支付模拟、物流跟踪、数据统计等。说白了这就是一个可以直接拿去演示、答辩、二次开发的完整系统。适合谁来参考如果你是Java后端方向的在校生想做一个有业务深度、能讲清楚技术亮点的毕业设计或者你是中小型农业企业、创业团队的技术负责人想快速搭一套农产品预售的MVP系统再或者你正在复习SpringBoot、MyBatis、Vue的整合开发想找一个完整的真实案例练手——这篇文章都能帮上忙。我会把这个系统从需求拆解、数据库设计、后端接口、前端页面到部署排错一条线全部讲透包括那些踩过才知道的坑。2. 项目整体设计与技术选型思路2.1 为什么用SpringBoot而不是SSH或SSM很多人在选技术栈时会纠结既然数据库操作用了MyBatis那跟传统的SSMSpringSpringMVCMyBatis有什么区别直接说结论SpringBoot就是SSM的“开箱即用版”。它把Spring、SpringMVC的繁琐XML配置几乎全干掉用自动配置和起步依赖帮你把项目骨架搭好。尤其是做这种预期时间紧张、需要快速看到效果的项目SpringBoot能让你把精力集中到业务逻辑上而不是泡在配置文件里。就拿这个农产品预售平台来说支付回调、定时任务比如预售到期自动关闭、文件上传、邮件通知这些功能在SSM里每一步都要写一堆配置在SpringBoot里一个EnableScheduling注解就能跑定时任务一个spring.mail配置块就能发邮件。实际开发下来我至少省了三分之一的时间。2.2 Vue选型Vue2还是Vue3配Element UI还是Element Plus前端这块我建议根据你的实际环境来选。如果你是在校生做毕设很多学校机房或老教程还停留在Vue2Element UI那为了稳妥可以选Vue2如果是新项目或企业应用直接上Vue3ViteElement Plus性能和开发体验都好很多。这个农产品预售平台我做了两套适配方案代码逻辑基本一致。为什么用Vue而不是JSP或者Thymeleaf因为前后端分离之后前端和后端可以并行开发后端只需要提供JSON接口前端用Axios去调页面渲染和交互都放在浏览器端。这样后期如果想把管理后台换成小程序端后端完全不用动只要重新写一套前端就行。实际上我后来确实给一个客户扩展过微信小程序端后端接口几乎零修改这就是前后端分离最大的价值。2.3 MyBatis还是MyBatis-Plus标题里写的是MyBatis但如果是你自己做二次开发我强烈建议在项目里引入MyBatis-Plus。MyBatis-Plus是MyBatis的增强工具单表CRUD都不用手写SQL直接继承BaseMapper就行。尤其像这种系统的用户表、日志表、轮播图表全是单表操作用MyBatis-Plus能省掉大量重复的Mapper XML。不过要注意如果题目或答辩要求里明确写了“MyBatis”你可以在核心业务比如订单、预售活动上用手写的动态SQL既能体现你对MyBatis的掌握又能享受MyBatis-Plus的开发效率。这样技术亮点反而更突出。我后面讲代码时会说清楚哪些地方用了原生SQL哪些地方用了Plus。2.4 MySQL选型与数据库连接池数据库用MySQL 5.7或8.0都可以。如果你是用MySQL 8.0要注意驱动名从com.mysql.jdbc.Driver变成了com.mysql.cj.jdbc.Driver时区也要在连接串里加serverTimezoneAsia/Shanghai不然会有8小时时差。连接池我用的HikariCPSpringBoot 2.x以上默认就是它性能比Druid好但Druid自带监控页面如果毕设想展示“SQL监控”这类功能可以换Druid各有优势。3. 数据库设计预售系统的表结构拆解3.1 核心表有哪些我把整个系统按业务域拆成六大模块对应数据库里十几张表。你不需要一次性建完先建核心表再根据功能迭代补充。第一类用户相关。user用户表、role角色表、user_role用户角色关联表。因为系统里有普通消费者、农场主/商家、管理员三类角色用RBAC模型管理权限最合理。不用Spring Security也行自己写个拦截器校验Session或Token够用就好。第二类商品与预售相关。category商品分类、product农产品表、presale_activity预售活动表、presale_rule预售规则表比如定金比例、尾款支付截止时间。第三类订单相关。order订单主表、order_item订单明细表、payment_record支付流水表。注意订单表里要区分“定金支付”和“尾款支付”两个阶段。第四类营销与内容相关。banner首页轮播图、notice公告、coupon优惠券可选。第五类物流相关。delivery发货记录。预售通常有预计发货时间所以要加expected_ship_date和actual_ship_date。第六类日志与统计。operation_log操作日志、visit_log访问统计可选。3.2 预售活动表怎么设计才合理这是整个系统的灵魂。我先给出一份我实际验证过的建表SQL结构你可以直接抄。CREATE TABLE presale_activity ( id bigint(20) NOT NULL AUTO_INCREMENT, title varchar(100) NOT NULL COMMENT 预售活动标题, product_id bigint(20) NOT NULL COMMENT 关联商品ID, seller_id bigint(20) NOT NULL COMMENT 商家ID, presale_price decimal(10,2) NOT NULL COMMENT 预售价格, deposit decimal(10,2) NOT NULL COMMENT 定金, final_payment decimal(10,2) NOT NULL COMMENT 尾款, total_stock int(11) NOT NULL COMMENT 预售总库存, sold_count int(11) DEFAULT 0 COMMENT 已售数量, start_time datetime NOT NULL COMMENT 预售开始时间, end_time datetime NOT NULL COMMENT 预售结束时间, final_pay_start_time datetime DEFAULT NULL COMMENT 尾款支付开始时间, final_pay_end_time datetime DEFAULT NULL COMMENT 尾款支付截止时间, status tinyint(4) DEFAULT 0 COMMENT 状态0未开始 1进行中 2已结束 3已取消, is_del tinyint(4) DEFAULT 0 COMMENT 逻辑删除, create_time datetime DEFAULT CURRENT_TIMESTAMP, update_time datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY idx_product_id (product_id), KEY idx_seller_id (seller_id) ) ENGINEInnoDB AUTO_INCREMENT1 DEFAULT CHARSETutf8mb4 COMMENT预售活动表;这里有几个设计细节第一presale_price、deposit、final_payment三个金额字段不能只存两个。因为存在一种情况尾款不一定等于预售价格减去定金。比如某款车厘子预售价180元定金50元尾款活动期间只要支付120元但如果逾期未支付尾款系统要按130元收取违约金。所以把三段金额分开存计算时才不会出错。第二total_stock和sold_count这两个字段一起用。每次用户下单支付定金时要校验sold_count total_stock否则提示“预售已售罄”。这个逻辑放在一个事务里配合UPDATE ... SET sold_count sold_count 1 WHERE id ? AND sold_count total_stock这种乐观锁写法能避免并发超卖。第三状态字段用tinyint而不是varchar。虽然写代码时状态码不如英文直观但存储空间小、查询快。为了可读性我会在Java枚举或常量类里定义状态码含义而不是在数据库里存中文。3.3 订单表设计怎么同时容纳定金和尾款订单表是另一个容易踩坑的地方。很多人一上来就设计成“一个订单一条记录”但预售订单必须能表达“已付定金待付尾款”这种中间状态。我的设计如下CREATE TABLE order ( id bigint(20) NOT NULL AUTO_INCREMENT, order_no varchar(32) NOT NULL COMMENT 订单编号, user_id bigint(20) NOT NULL COMMENT 用户ID, activity_id bigint(20) NOT NULL COMMENT 预售活动ID, product_id bigint(20) NOT NULL COMMENT 商品ID, product_name varchar(100) NOT NULL COMMENT 商品名称快照, product_image varchar(255) DEFAULT NULL COMMENT 商品图片快照, quantity int(11) NOT NULL COMMENT 购买数量, presale_price decimal(10,2) NOT NULL COMMENT 预售价快照, deposit decimal(10,2) NOT NULL COMMENT 定金快照, final_payment decimal(10,2) NOT NULL COMMENT 尾款快照, total_amount decimal(10,2) NOT NULL COMMENT 成交总价, paid_deposit tinyint(1) DEFAULT 0 COMMENT 定金是否已支付, paid_final tinyint(1) DEFAULT 0 COMMENT 尾款是否已支付, status tinyint(4) DEFAULT 0 COMMENT 订单状态0待付定金 1已付定金 2待付尾款 3已完成 4已取消 5已退款, receiver_name varchar(50) NOT NULL COMMENT 收货人, receiver_phone varchar(20) NOT NULL COMMENT 收货电话, receiver_address varchar(255) NOT NULL COMMENT 收货地址, delivery_company varchar(50) DEFAULT NULL COMMENT 物流公司, delivery_no varchar(50) DEFAULT NULL COMMENT 物流单号, remark varchar(255) DEFAULT NULL COMMENT 买家备注, create_time datetime DEFAULT CURRENT_TIMESTAMP, update_time datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_order_no (order_no), KEY idx_user_id (user_id), KEY idx_activity_id (activity_id) ) ENGINEInnoDB AUTO_INCREMENT1 DEFAULT CHARSETutf8mb4 COMMENT订单表;这里几个设计意图一是冗余了product_name和product_image。因为商品信息后续可能被商家修改但订单里的商品名称、图片、价格必须保持下单那一刻的快照否则用户查历史订单时商品已经下架或改名就会显示异常。这是电商领域非常经典的做法叫“数据快照”。二是通过paid_deposit和paid_final两个布尔字段配合status字段能快速筛选出“待支付尾款”的订单定时任务扫描起来效率很高。比如每天凌晨检查所有status1且当前时间大于final_pay_end_time的订单自动把它们置为status4已取消并释放库存。三是order_no设计成32位的字符串我用的是“日期时间随机数用户ID后四位”的拼接方式。比如20250607153012345678901234目的是保证订单号在并发时也不会重复。别用数据库自增ID当订单号展示给用户因为会暴露销量也容易被爬虫遍历。4. 后端核心实现SpringBoot里的业务层逻辑4.1 项目初始化与依赖引入我建项目时习惯用Spring Initializrstart.spring.io选Java 8或Java 11依赖勾选Spring Web、MyBatis Framework、MySQL Driver、Validation另外手动加一个lombok能省大量getter/setter和hutool工具类库生成订单号、加密等都方便。pom.xml里比较关键的依赖片段如下dependency groupIdorg.mybatis.spring.boot/groupId artifactIdmybatis-spring-boot-starter/artifactId version2.3.0/version /dependency dependency groupIdcom.baomidou/groupId artifactIdmybatis-plus-boot-starter/artifactId version3.5.3.1/version /dependency dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId scoperuntime/scope /dependency很多人会疑惑既然用了MyBatis-Plus为什么还要引入mybatis-spring-boot-starter其实MyBatis-Plus依赖里已经包含MyBatis核心包但为了让SpringBoot的自动配置能扫描到Mapper接口还是需要加其中一个starter。我两个都加过实测没有冲突但如果你只加MyBatis-Plus需要确认版本兼容性。稳妥起见直接用mybatis-plus-boot-starter一个就够了我在写这篇文章的源码里就是只加了Plus的依赖。4.2 统一返回结果与全局异常处理前后端分离项目里接口返回格式必须统一。我定义了一个Result类Data public class ResultT { private Integer code; private String message; private T data; public static T ResultT success(T data) { ResultT result new Result(); result.setCode(200); result.setMessage(success); result.setData(data); return result; } public static T ResultT error(String message) { ResultT result new Result(); result.setCode(500); result.setMessage(message); return result; } }统一返回还有一个好处前端Axios响应拦截器只用判断code是不是200不需要每次处理异常结构。配合全局异常处理器把业务异常比如库存不足、活动未开始和系统异常分别处理前端能拿到友好的错误提示。4.3 预售下单接口事务与防超卖这个接口是整个后端最重要的接口没有之一。先看代码逻辑Transactional(rollbackFor Exception.class) public ResultOrderVO createPresaleOrder(OrderCreateDTO dto) { // 1. 校验用户登录 User user userService.getById(dto.getUserId()); if (user null) { throw new BusinessException(用户不存在); } // 2. 查询预售活动加锁防止状态变化 PresaleActivity activity activityService.getById(dto.getActivityId()); if (activity null || activity.getIsDel() 1) { throw new BusinessException(预售活动不存在); } Date now new Date(); if (now.before(activity.getStartTime()) || now.after(activity.getEndTime())) { throw new BusinessException(当前不在预售时间内); } // 3. 扣减库存乐观锁 int stockResult activityMapper.decreaseStock( activity.getId(), dto.getQuantity(), activity.getSoldCount()); if (stockResult 0) { throw new BusinessException(预售库存不足); } // 4. 生成订单状态为“待付定金” Order order new Order(); order.setOrderNo(generateOrderNo()); // 省略其他字段赋值 order.setPaidDeposit(false); order.setPaidFinal(false); order.setStatus(0); orderService.save(order); return Result.success(convertToVO(order)); }decreaseStock对应的XML如下update iddecreaseStock UPDATE presale_activity SET sold_count sold_count #{quantity} WHERE id #{id} AND is_del 0 AND sold_count #{quantity} lt; total_stock /update这个写法的核心思想就是乐观锁让数据库自己判断库存是否足够如果不够UPDATE影响行数为0Java代码里就能感知到。有些人喜欢先SELECT再UPDATE但在高并发下会出现两个线程同时读到库存还有1件都去下单最后超卖。用上面的条件更新方式可以彻底避免。下单之后就是支付定金。我提供一个模拟支付的接口前端页面里点击“去支付”会调用/api/payment/payDeposit传入订单号和支付金额。后端会创建一条支付流水并更新订单状态为“已付定金”。这里有一个特别容易忽略的点支付回调必须做幂等处理。也就是说同一笔订单如果支付接口被调用两次不能把订单状态从“已付定金”再改一次也不能生成两条支付流水。我实现时是在payment_record表上加了order_no pay_type的唯一索引插入失败就说明重复支付了直接返回成功就行。4.4 定时任务自动关闭未支付订单和过期未付尾款订单电商预售系统里定时任务是标配。比如用户下单后15分钟未支付定金订单自动取消预售结束后用户有3天时间支付尾款超时自动取消订单并释放库存。我在SpringBoot里用Scheduled注解实现Component public class OrderTimeoutTask { Scheduled(cron 0 */5 * * * ?) public void cancelUnpaidDepositOrders() { // 扫描下单时间超过15分钟且status0的订单 // 将status置为4已取消并释放预售库存 } Scheduled(cron 0 0 2 * * ?) public void cancelUnpaidFinalOrders() { // 扫描final_pay_end_time已过的status1订单 // 将status置为4并释放预售库存 } }注意定时任务里如果在循环中执行更新操作一定要控制批量大小或者用分页查询一次处理100条避免一次性加载太多数据导致内存溢出。另外Scheduled默认是单线程执行的如果任务耗时较长会影响其他定时任务建议在配置里设置一个TaskScheduler线程池。5. 前端实现Vue页面与交互细节5.1 前端项目搭建前端我用的Vue CLI或Vite创建项目。如果使用Vue 2vue create presale-front选择RouterAxios安装Element UInpm install element-ui axios vue-router3如果用Vue 3则安装Element Plus和Vue Router 4。前端目录结构建议这样划分src ├── api │ ├── user.js │ ├── product.js │ ├── presale.js │ └── order.js ├── router │ └── index.js ├── store │ └── modules │ ├── user.js │ └── cart.js ├── views │ ├── home │ ├── presale │ ├── order │ └── admin └── utils └── request.js5.2 预售活动列表与倒计时用户端最核心的页面是预售活动列表。用户进入页面就能看到所有正在预售的农产品每张卡片显示商品图、预售价格、定金、剩余库存等底部是一个倒计时。倒计时这个功能看起来简单做起来有坑。如果直接用后端返回的endTime减去前端本地时间会因为浏览器时区或用户电脑时间不准导致误差。我的做法是后端接口返回serverTime和endTime前端计算endTime - serverTime得到剩余毫秒数再每秒执行一次setInterval递减。这样只要请求那一刻的服务器时间是准确的倒计时就是准的。5.3 下单与支付页面逻辑用户点击“立即预售”按钮跳转到确认订单页回显商品信息、预售价格、定金、尾款。提交订单后前端携带活动ID、数量、收货地址等调用后端接口。支付环节如果是模拟支付我会做一个“演示收银台”页面用户点击“确认支付”后前端调用模拟支付接口后端直接返回支付成功。如果是真实场景可对接支付宝或微信支付的沙箱环境后端需要处理异步回调这里就不展开说了。毕设用模拟支付完全够用答辩时把支付流程图讲清楚就很有说服力。5.4 管理后台用户管理、商品管理、预售管理管理后台和用户端是两套布局。用户端以电商风格为主管理后台则用侧边栏顶栏布局。Element UI的el-menu做侧边栏非常顺手配合el-table展示数据再配合el-dialog做新增编辑弹窗。我在管理后台里给预售活动管理增加了一个“活动状态”列状态用不同颜色的Tag展示并在操作列提供“上架/下架”、“编辑”、“查看订单”按钮。商家创建预售活动时表单里要有商品选择、定金金额、尾款金额、预售总量、开始结束时间的设置。这里要注意定金不能大于预售价尾款必须是正数后端和前端都要校验一遍不能只信前端。6. 核心流程串联从用户下单到订单完成的全链路6.1 正常流程商家/管理员在后台创建预售活动设置商品、价格、定金、尾款、库存和时间。用户在前端浏览活动点击“立即预售”。后端创建订单状态为“待付定金”同时扣减库存锁定份额。用户支付定金后端更新订单状态为“已付定金”支付流水记录完成。预售时间结束后系统进入尾款支付阶段。用户在我的订单页面看到“待支付尾款”按钮点击后支付尾款。用户支付尾款订单状态更新为“已完成”或“待发货”后端通知商家准备发货。商家在后台发货填写物流公司和运单号订单状态更新为“已发货”。用户确认收货订单状态更新为“已完成”。整个交易闭环结束。6.2 异常流程如果用户在支付定金前反悔可以在“待付定金”下取消订单后端释放库存。如果用户支付定金后想退款需要走退款申请流程。因为预售商品通常是农产品非质量问题可能不允许退定金所以这个流程要配置规则。我在系统里设计了一个“定金退款申请”功能需要管理员审核审核通过后原路退回定金订单取消。这个流程做出来在答辩时可以当作亮点讲因为很多同类系统根本没有完善的退款机制。如果预售结束后用户忘记支付尾款定时任务会自动取消订单并释放库存。这一点在下一节会详细说明。7. 常见问题与排查技巧实录7.1 数据库时区问题导致时间错乱现象前端页面显示的活动时间比实际时间早了8小时或者后端存的时间对不上。原因MySQL连接串没有配置serverTimezone或者配置错了。在MySQL 8.0以下驱动默认用系统时区而Java默认是GMT8如果你的服务器时区是UTC就会差8小时。解决在application.yml中修改连接串jdbc:mysql://localhost:3306/presale?serverTimezoneAsia/ShanghaiuseSSLfalsecharacterEncodingutf87.2 前端跨域问题现象前端访问后端接口报Access to XMLHttpRequest at http://localhost:8080/api/... from origin http://localhost:8081 has been blocked by CORS policy。解决在SpringBoot里写一个CORS配置类或者简单地在Controller类上加CrossOrigin。但要注意CrossOrigin加在类上只能控制这个控制器最好统一实现WebMvcConfigurerConfiguration public class CorsConfig implements WebMvcConfigurer { Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping(/**) .allowedOriginPatterns(*) .allowedMethods(GET, POST, PUT, DELETE, OPTIONS) .allowedHeaders(*) .allowCredentials(true) .maxAge(3600); } }7.3 MyBatis Mapper XML文件扫描不到现象启动时报Invalid bound statement (not found)。原因pom.xml中没有把src/main/resources/mapper目录下的XML文件打包进classpath或者MyBatis配置里没有指定mapper-locations。解决在application.yml里加上mybatis-plus: mapper-locations: classpath*:mapper/**/*.xml type-aliases-package: com.presale.entity同时检查pom.xml里是否把src/main/resources纳入构建资源build resources resource directorysrc/main/java/directory includes include**/*.xml/include /includes /resource resource directorysrc/main/resources/directory /resource /resources /build7.4 部署上线时MyBatis打印SQL的坑实际生产环境我不建议打印SQL但开发阶段打印SQL很有用。在application.yml配置mybatis-plus: configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl这样控制台会输出每条SQL带参数的值调试起来非常直观。7.5 定时任务里释放库存与订单状态不同步我在第一次测试时遇到过一个问题订单超时被取消了但库存没加回去导致前台显示库存越来越小。原因是取消订单和释放库存是两个操作如果第二步失败订单已经取消但库存没释放。解决必须把“取消订单释放库存”放在同一个事务里。我是这么做的先查出需要取消的订单列表然后逐个执行update order set status4 where id? and status0同时执行update presale_activity set sold_count sold_count - #{quantity} where id? and sold_count #{quantity}这两个SQL放在同一个Transactional方法里。还要注意如果订单被用户在取消的同时也发了取消请求要保证只有一次生效所以update的条件要带上原状态值防止重复操作。7.6 前端打包后刷新404现象前端用vue-router的history模式打包部署到Nginx刷新页面时出现404。原因history模式下路由是前端的但Nginx找不到对应的静态文件路径。解决在Nginx配置中添加location / { try_files $uri $uri/ /index.html; }也可以把路由改成hash模式虽然URL里会多一个#但部署最简单不用额外配置Nginx。8. 部署与运行从源码到能跑通全流程8.1 本地环境准备JDK 1.8及以上Maven 3.6以上MySQL 5.7/8.0Node.js 14以上IDE推荐IntelliJ IDEA后端和VS Code前端8.2 后端启动步骤用IDE打开后端工程等待Maven下载依赖。在MySQL中创建数据库CREATE DATABASE presale DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;执行项目里的sql/init.sql脚本初始化表结构和基础数据。修改application.yml中的数据库用户名、密码。启动PresaleApplication.java看到“Started PresaleApplication”日志表示启动成功。8.3 前端启动步骤在终端进入frontend目录。执行npm install安装依赖。检查src/utils/request.js中的baseURL是否指向后端地址比如http://localhost:8080。执行npm run serve浏览器访问http://localhost:8081。8.4 内置账号初始化脚本中会创建三类账号角色账号密码说明管理员admin123456管理后台全部权限商家farmer123456发布预售活动和商品用户user123456浏览商品、下单支付9. 写在项目之外几点实际开发心得做这个系统时我最大的体会是一个项目能不能顺利跑完往往不是看技术多高深而是看那些边界情况有没有处理好。比如预售活动的库存扣减、支付回调的幂等性、定时任务的事务控制这些才是真正体现工程能力的地方。把这几块吃透了你再去面Java开发岗时可以把“预售秒杀场景的防超卖方案”“订单状态的流转设计”拿出来讲会比单纯背八股文有说服力得多。另外如果你拿这个项目当毕设或就业项目建议在答辩时主动讲一下数据库表设计里的“快照字段”和“乐观锁扣库存”这两个点。这两个细节是很多初学项目的通病你能主动做对并解释清楚面试官会立刻把你和普通选手区别开。最后再分享一个小技巧刷数据时不要只登录一个账号下单你可以开两个浏览器无痕窗口模拟两个用户同时抢同一个预售活动测试一下库存会不会超卖。我第一次测的时候还真发现了问题就是在decreaseStock那一步少加了一个sold_count #{quantity} total_stock的条件。这种问题在普通测试里根本看不出来但在并发场景下就是致命的。现在把方法写在这里了希望大家能一步跨过这个坑。
返回列表