秒杀系统这个选题,在Java后端相关项目里算是常青树了。很多同学毕业设计选它,想着无非就是商品秒杀、生成订单、定时开抢,结果一做才发现远没那么简单。不带夸张地说,凡是做秒杀系统翻车的,八成都是栽在库存超卖和高并发处理上。这次我把整套项目的从零实现思路、表结构设计、核心代码怎么做、前后端怎么联调、最后怎么部署上线,连答辩时老师大概率会问的几个坑位都一并讲清楚,给正在做毕设或者想用这个项目练手的朋友一条可以直接走完的路。
1. 项目整体设计与技术选型
1.1 秒杀系统的核心需求拆解
先别急着敲代码,把业务理清楚比什么都重要。秒杀系统从用户视角来看,流程就是:浏览秒杀商品列表、进入商品详情页、等待倒计时、点击秒杀按钮、生成订单、付款。但站在技术视角,真正的核心需求只有三个:高并发请求处理、库存的绝对准确、接口的防刷控制。
高并发请求处理指的是商品在开抢的那一瞬间,可能同时涌来几千甚至上万的请求,你的系统不能让Tomcat线程池被打满,更不能让数据库直接宕掉。库存准确意味着卖出100件绝不能实际扣掉101件,这是秒杀系统最核心的底线。防刷则是没法回避的业务问题,真实场景里有很多人会写脚本抢货,在你的接口上疯狂点击,你需要在系统层面限制每个用户的下单频率,不然正常用户根本抢不到。
功能模块上,典型的秒杀系统至少包含用户模块(登录与鉴权)、商品模块(秒杀商品列表与详情)、秒杀模块(倒计时、下单、库存扣减)、订单模块(订单生成、状态查询、支付模拟)。如果项目还带后台管理功能,可以再做一套商品上下架与库存设置的页面,但这属于加分项,核心业务跑通是优先级最高的事情。
1.2 技术栈选型背后的取舍
这套项目选择Java + SpringBoot + Vue这样的组合,核心原因很直接:生态成熟、资料好找、面试和答辩都有话说。SpringBoot作为后端基础框架,内置Tomcat,配合SpringMVC做接口开发非常顺畅,项目打包成jar直接就能跑,部署成本低。Vue负责前端页面,配合Element UI或者Vant组件库,能做到比较精致的页面效果,可视化和演示体验都不错。
需要仔细权衡的是中间件部分。秒杀系统避不开Redis,因为你需要把热点商品信息放入缓存来抗压,还需要用Redis做库存预减和分布式锁。Redis这里建议选,安装简单,Windows和Linux都有可用的版本,毕业设计足够用。消息队列(RabbitMQ)如果用了,异步下单削峰的效果会让项目档次高很多,但引入的复杂度也很明显:需要额外安装Erlang环境、维护消息队列状态、处理MQ宕机之后的降级逻辑。如果时间紧张,项目做到Redis这一步其实已经能拿到不错的分数,消息队列作为扩展点写进报告里、在答辩时讲清楚“如果加入MQ会怎么设计”,效果不差。这是很多老师认可的差异化策略,不是所有方案都得落在代码里。
数据库方面,MySQL 5.7或者8.0都可以,建库建表全部用SQL脚本完成。考虑到毕设答辩的演示环境,库存量不会设置得特别大,MySQL配合合理的事务隔离级别完全可以支撑。Redis + MySQL的二级缓存结构已经足够应对大多数并发场景。
1.3 秒杀系统的整体架构
整个系统的架构可以这样理解:浏览器访问Vue前端页面,前端通过axios调用后端SpringBoot提供的RESTful接口。后端接收到请求后,先走拦截器做登录校验和限流判断,然后根据接口类型判断是读取数据还是写入数据。读取类请求(比如商品列表、商品详情)优先查Redis,查不到再回源查MySQL,并且把结果回填到Redis。写入类请求(比如秒杀下单)是最核心的一环,流程上先通过Redis预扣库存,预扣成功后才允许创建订单,订单数据先写入Redis缓存,再异步或者同步落库到MySQL。
这套结构的好处是,数据库只承担最终的数据落盘工作,不直接面对高并发流量,从而避免大流量直接把数据库打挂。前端和后端之间的跨域问题通过SpringBoot配置CORS或者生产环境由Nginx统一转发解决。整个链路在项目中的依赖关系很清晰:Vue只依赖后端接口,后端依赖Redis和MySQL,Redis处理热点数据与库存,MySQL负责最终一致性。
这套设计不是凭空想出来的,和目前电商行业里秒杀场景的主流方案思路一致:少部分优化到极致,大部分流量在入口被拦截或降级,真正走到数据库的请求其实是少数。
2. 数据库设计与核心业务实现
2.1 秒杀商品表的设计思路与建表SQL
数据库设计是秒杀系统的地基,表建得不合理,后面索引、事务都很难优化到位。核心需要四张表:用户表(seckill_user)、秒杀商品表(seckill_goods)、订单表(seckill_order)、秒杀商品明细表(seckill_goods_detail,也可以合并到商品表里)。这里的关键点在于,秒杀商品表与普通商品表要分离。普通商品信息(名称、图片、描述)放在商品主表里,而秒杀专属字段(秒杀价、秒杀库存、开始时间、结束时间)放在秒杀商品表中。为什么要拆表?因为秒杀库存和普通库存的更新频率完全不同,拆开后秒杀表的锁粒度更小,写并发时不容易把普通商品的更新也堵住。
建表时几个容易踩坑的地方:秒杀商品表的库存字段建议用int,不要用decimal;秒杀价字段保留两位小数即可;开始时间和结束时间用datetime类型,在数据库层面做判断,where条件里直接带时间范围可以有效避免后端代码里的时间判断漏洞;订单表需要有一个唯一业务编号,常见做法是“用户ID + 商品ID + 时间戳”的组合,或者直接用数据库自增ID配一个全局唯一的order_no。用户表只需要最简单的id、用户名、密码、手机号字段,密码使用MD5加盐或者BCrypt加密存储,示范项目没必要做得太复杂,但密码明文存储属于答辩时容易被挑刺的硬伤,要注意避免。
一段可用的秒杀商品表结构参考:
CREATE TABLE `seckill_goods` ( `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '秒杀商品ID', `goods_id` bigint(20) NOT NULL COMMENT '商品ID', `seckill_price` decimal(10,2) NOT NULL COMMENT '秒杀价', `stock_count` int(11) NOT NULL COMMENT '剩余库存', `start_time` datetime NOT NULL COMMENT '秒杀开始时间', `end_time` datetime NOT NULL COMMENT '秒杀结束时间', `version` int(11) NOT NULL DEFAULT '0' COMMENT '乐观锁版本号', PRIMARY KEY (`id`), KEY `idx_goods_id` (`goods_id`) ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COMMENT='秒杀商品表';看到这里的注意点,我在version字段上加了乐观锁版本号。这个字段不是摆设,是后续解决超卖问题的重要依据。库存扣减时不会简单地对stock_count减1,而是会在update语句里带上version条件,保证扣减操作是原子性的,避免并发更新的脏数据问题。
2.2 库存扣减的核心逻辑:一个SQL避免超卖
超卖问题,用一句话概括就是:两个用户同时抢到最后一件商品,两个人都显示秒杀成功。如果代码写成了先select查询剩余库存,判断大于0后执行update扣减,那么在并发环境下,两个请求可能同时读到同一个库存值,然后都通过判断,最后数据库里实际扣了两次,库存变成负数。这就是经典的超卖场景。
解决超卖最直接的方式,是用一条带条件的update语句把“查询”和“扣减”合并成一步:
UPDATE seckill_goods SET stock_count = stock_count - 1 WHERE id = ? AND stock_count > 0这条SQL的作用是:只有当当前库存大于0时才允许扣减,并且整个操作在数据库层面是原子性的。MySQL的InnoDB引擎在执行update时会自动对命中的记录行加锁,后面的请求必须等待前面的更新结束才能继续执行,这就从源头杜绝了超卖。执行这条update后,通过返回的影响行数判断扣减是否成功,受影响行数为1说明库存扣减成功,为0说明库存已经没了或者商品不在秒杀时间内,给前端返回“已抢光”即可。
但这里有一个性能隐患,就是并发请求全部串行化地落在数据库的行锁上,吞吐量上不去。所以在真正的高并发场景下,还会叠加一层Redis预扣库存方案,就是接下来要说的内容。
2.3 防止超卖的三种方案横向对比
防止超卖一共三种主流做法,各有利弊,根据项目深度选择。
第一种是数据库where条件更新,也就是上一节说的带stock_count > 0条件的update语句。优点是逻辑最简单,不需要额外依赖,在小并发下效果很好;缺点是行锁导致数据库压力大,并发极高时性能急剧下降。
第二种是乐观锁,给商品表加version字段,扣减时要求version匹配:
UPDATE seckill_goods SET stock_count = stock_count - 1, version = version + 1 WHERE id = ? AND stock_count > 0 AND version = ?乐观锁的并发控制能力比第一种更强,适合对一致性要求极高的场景。缺点是如果冲突频繁,很多请求会获取不到更新机会,需要配合重试机制,用户体验上会有影响。
第三种是Redis预扣库存,这也是目前工业界用得最多的秒杀方案。秒杀开始前把库存预先扣减到Redis中,真正的高并发请求全部打到Redis上,利用Redis单线程执行命令的天然原子性完成扣减。Redis扣减成功的请求才允许继续走后续的下单流程,Redis扣减失败的直接返回已抢光。MySQL中的库存作为最终数据一致性保证,异步扣减即可。
对于毕业设计来说,建议采用“Redis预扣库存 + MySQL数据库兜底扣减”的组合。Redis扛住流量,MySQL保证最终准确,两个环节都能在答辩时讲出设计思路。实现上可以用Redis的Lua脚本或者直接使用decr命令,注意在Redis扣减前需要先初始化库存。
Redis扣减的示例代码:
// 秒杀开始前,将库存初始化到Redis stringRedisTemplate.opsForValue().set("seckill_stock:" + goodsId, String.valueOf(stockCount), Duration.ofHours(24)); // 秒杀时,先Redis预扣减 Long result = stringRedisTemplate.opsForValue().decrement("seckill_stock:" + goodsId); if (result != null && result >= 0) { // 扣减成功,继续下单流程 } else { // 扣减失败,库存不足 return Result.error("已抢光"); }这里有个细节要说清楚,decrement命令返回负数也要做标记当前用户已经秒杀过,否则同一个用户会反复下单。处理方式是在Redis里用一个用户维度标记,例如setnx命令设置seckill_user_flag:{userId}:{goodsId},设置成功说明第一次秒杀,设置失败说明重复请求,直接拒绝。
2.4 订单表、用户表的索引优化策略
订单表的查询需求主要是两类:根据订单号查订单详情、根据用户ID查订单列表。基于这个特点,订单表的主键建议使用业务订单号,而不是自增ID,这样在分库分表时不容易冲突,语义也更清晰。但考虑到示范项目的简洁性,使用自增ID做物理主键,同时对order_no单独建唯一索引也可以。这里关键的原则是,查询要用到的条件字段必须有索引,否则随着数据量增长,全表扫描会让系统性能急剧恶化。
用户表的登录鉴权建议使用JWT(JSON Web Token)机制,后端登录成功后生成token返回给前端,前端存储在localStorage中,每次请求在请求头带上token。拦截器从token中解析出userId,放入ThreadLocal或者请求上下文中,后续业务逻辑直接取用,不再查询用户表。这样的设计避免了每次请求都查一遍数据库用户表,降低了数据库压力。另一个好处是,拦截器可以统一处理token过期和非法请求,接口安全性更高。
3. 前后端实现:Java + Vue 开发实录
3.1 后端SpringBoot接口设计
后端接口设计直接决定项目的答辩观感,好的接口设计不仅到字段级别清晰,还能让对方一眼看出你的模块划分能力。建议的接口如下:
- GET /api/goods/list:获取秒杀商品列表,返回进行中和未开始的秒杀商品
- GET /api/goods/detail/{id}:获取单个秒杀商品的详情,含倒计时时间
- POST /api/seckill/doSeckill:秒杀下单核心接口,参数是goodsId,内部完成登录校验、Redis预扣库存、防重复标记、生成订单
- GET /api/order/detail/{orderNo}:查询订单详情,用于前端轮询确认订单状态
- POST /api/order/pay:模拟支付,传入orderNo完成支付状态更新
- GET /api/user/info:获取当前登录用户信息(从token解析)
接口返回统一使用Result 包装类,包含code、message、data三个字段,成功时code为200,业务失败时code自定义为5001(库存不足)、5002(重复秒杀)、5003(活动未开始)等。统一返回值的好处是前端axios拦截器可以统一处理错误码,不用每个接口都写重复的判断逻辑。
秒杀下单接口是核心,它的实现顺序非常重要。第一步验证登录状态,第二步判断秒杀是否在有效时间段内,第三步检查当前用户是否已经秒杀过当前商品(Redis标记),第四步做Redis库存预扣减,预扣成功后第五步创建订单。整个过程要控制在一个方法里,逻辑清晰可读。订单状态在创建时设置为“待支付”,支付接口调用后更新为“已支付”,并异步更新数据库中的库存值。
3.2 前端Vue页面与交互细节
前端页面采用Vue 2 + Element UI/Vant的组合,页面数量不用太多,核心页面是四个:登录页、商品列表页、商品详情页、订单列表页。登录页负责调用登录接口并保存token,商品列表页以卡片方式展示所有秒杀商品,每张卡片显示商品图片、名称、秒杀价、库存、状态按钮。商品详情页核心展示倒计时和秒杀按钮,订单列表页展示当前用户的历史订单和状态。
倒计时逻辑是整个前端最容易出现细节问题的部分。推荐的做法是:进入详情页时后端返回当前服务器时间和秒杀结束时间,前端用两个时间差初始化倒计时,然后用setInterval每秒减1。不要用前端本地时间做倒计时,因为用户本地时间可以被修改,倒计时就会不准。服务端时间对比的代码细节:
// 后端返回数据: { startTime: '2025-12-01 10:00:00', endTime: '2025-12-01 11:00:00', now: '2025-12-01 09:59:30' } // 计算当前活动还剩多少毫秒 let remainTime = new Date(endTime).getTime() - new Date(now).getTime()按钮的状态有三种:未开始(灰色置灰,显示“即将开始”)、进行中(高亮可点击,显示“立即秒杀”)、已结束(灰色置灰,显示“已结束”)。实现上在mounted中拉取详情数据初始化三种状态,然后每隔一秒刷新剩余时间,当剩余时间小于等于0时,自动把按钮状态切换为“立即秒杀”。秒杀完成后,页面要给出成功或失败的反馈,成功反馈中包含订单号信息,引导用户去订单列表页查看。这里前端要注意防止用户通过抓包绕过按钮状态,直接调用秒杀接口,因此后端秒杀接口内部还必须再判断一次时间有效性,绝对不能只在按钮上控制。
3.3 订单状态流转与支付模拟
订单状态机建议设计成四个状态:0-待支付、1-已支付、2-已取消、3-已失效。待支付状态下,如果超过一定时间(例如15分钟)未支付,需要做订单超时取消操作。最简洁的实现方式是秒杀成功后把订单信息放入Redis,同时设置15分钟过期时间,Redis监听key过期时把对应的数据库订单置为已取消。这种方式比使用定时任务扫描订单表更实时,也比MQ延时消息更轻量,适合毕设体量。
支付模拟不用对接任何真实支付渠道,页面提供一个“模拟支付”按钮,点击后调用后端支付接口,后端把订单状态从待支付改为已支付,同时更新数据库的库存,并清除Redis中的相关标记。需要注意,数据库库存扣减和订单状态更新必须放在同一个事务里,用@Transactional控制,否则会出现订单已支付但库存没扣减成功的数据不一致问题。
3.4 前后端联调与跨域处理
本地开发时前端运行在8080端口(或3000端口),后端运行在8081端口,必然面临CORS跨域问题。后端的跨域解决方式最简单,是在SpringBoot中实现WebMvcConfigurer接口,配置addCorsMappings方法,允许所有来源和所有接口跨域。生产环境推荐由Nginx统一代理,前端请求/api路径时由Nginx转发到后端服务,前端页面请求就变成了同源请求,不需要再额外处理跨域。
本地联调还有一个高频踩坑点:axios请求默认不带cookie,如果后端登录鉴权依赖session就会有问题。但本项目使用的是JWT token方案,不存在session跨域问题,这又是选择JWT的一个好处。联调时建议先在浏览器控制台手动测试单个接口,确认返回数据结构和字段名与页面绑定的数据一致,再连页面调试,定位问题速度会快很多。经验是大部分联调问题来自字段名不一致或者空值处理,比如后端返回null字段,前端组件直接渲染空白,看起来像是没数据显示。
4. 高并发优化与部署实战
4.1 本地部署完整步骤
如果你拿到的是已经写好的秒杀项目代码,部署过程可以分为环境准备、数据库初始化、后端启动、前端启动四步。环境方面需要JDK8+、Maven 3.6+、Node.js 14+、MySQL 5.7+、Redis 6.x,五个工具缺一不可。JDK和Maven需要配置环境变量,Node.js一般安装后直接可用。
数据库初始化的时候,把项目里提供的seckill.sql脚本导入到MySQL中。导入前先创建数据库,例如CREATE DATABASE seckill DEFAULT CHARACTER SET utf8mb4; 然后use seckill; source seckill.sql; 导入完成后检查几张表是否正常生成,数据是否在,避免脚本执行出错但没留意。脚本导入后,打开后端项目里的application.yml或者application.properties,修改数据库用户名密码、Redis连接地址,确认Redis服务已经启动。
后端启动有两种方式。第一种是开发环境直接运行SpringBoot的启动类,右键run即可。第二种是生产环境打包启动,在项目根目录执行mvn clean package -DskipTests,打包后在target目录下生成jar包,执行java -jar seckill.jar启动。前端启动更简单,npm install安装依赖后,npm run serve启动开发模式,浏览器访问对应端口。如果npm install速度慢,可以使用国内镜像源。
整套部署大概半小时内可以完成,需要提示的是,启动后端之前一定要保证Redis已经启动,否则后端启动时或者第一个请求进来时会直接报连接失败,这个错误信息比较明确,但如果数据库没建好,报错信息可能会混淆排查方向。部署完成后,用系统自带的账号密码登录,完整流程跑一遍秒杀。
4.2 性能优化:从普通接口到能抗压
本地能跑通和真正能扛住并发是两码事,从这个项目想拿高分,性能优化至少要做到几个方面。第一是数据缓存,商品列表和详情接口的响应数据尽量不直接查数据库,而是把查询结果对象序列化后存入Redis,下次请求直接返回缓存,省去数据库查询和对象转换的开销。淘汰策略可以使用定时刷新,也可以手动在后台修改商品后主动删缓存。第二是接口限流,秒杀接口加上限流组件,可以考虑使用Guava RateLimiter或者Redis计数器限流,最简单的做法是限制每个用户每秒最多请求一次秒杀接口,直接拒绝超频请求。第三是在入口做静态化,Vue项目打包后的静态文件交给Nginx托管,Nginx处理静态文件的效率比SpringBoot里Resource映射高得多。
压测时可以使用JMeter来验证效果。本地压测不要开太高并发,线程数200、循环10次就已经能暴露很多问题。我自己压测的时候,第一次压测直接出现几十个库存扣减失败的订单记录,排查后发现是Redis和数据库的数据没有保持一致,后来在事务边界上做了调整,问题才消失。压测时重点观察两个指标:接口响应时间P99和系统错误率。P99在500毫秒以内、错误率接近于零就说明系统基本健康。
4.3 部署到服务器:Linux环境实操
如果有条件和需求,可以把项目部署到阿里云或者腾讯云的轻量应用服务器上,一般2核4G配置的服务器足够跑完整套系统。Linux环境部署相比本地区别不大,但有几个点需要注意。MySQL和Redis的安装在Linux上推荐使用包管理器,省时省力。MySQL装好后要设置好密码和远程访问权限,否则后端连不上数据库。Redis默认只监听127.0.0.1,如果后端在同一台服务器上就不用改配置。
SpringBoot的jar包启动后默认前台运行,关闭终端服务就停了,一定用nohup方式启动:
nohup java -jar seckill.jar --spring.profiles.active=prod > seckill.log 2>&1 &日志输出到seckill.log,排查问题时直接tail -f seckill.log查看实时日志。前端打包后的dist目录使用Nginx配置站点指向,同时把/api开头的请求代理到后端的8081端口。Nginx的配置如下:
server { listen 80; server_name yourdomain.com; location / { root /opt/seckill-web/dist; index index.html; try_files $uri $uri/ /index.html; } location /api/ { proxy_pass http://127.0.0.1:8081; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } }try_files那行不加的话,前端路由在刷新页面时会直接404,这个是SPA部署的经典坑,一定要写上去。配置完成后访问服务器公网IP就能看到完整系统了。数据库菜单和Redis状态检查是上线后必须做的自检步骤,如果MySQL或者Redis没接入,页面会正常打开但数据加载失败,排查半天才发现是后端连不上数据库。
5. 常见问题与排查技巧实录
5.1 秒杀项目高频异常速查表
做这个项目,有几个问题是新手基本都会遇到的,提前说明白能少踩很多坑。我整理了一个速查表:
| 现象 | 可能原因 | 排查步骤 |
|---|---|---|
| 秒杀按钮一直灰色,倒计时不动 | 后端返回时间格式不对,前端解析失败 | 检查后端返回的startTime格式是否为yyyy-MM-dd HH:mm:ss |
| 接口报超时 | 数据库连接池耗尽 | 查看后端日志有没有连接池等待超时异常,加大连接数配置 |
| 库存显示负数 | 扣减逻辑没有带stock_count > 0条件 | 检查update语句是否缺少库存判断条件 |
| 同一用户秒杀多次成功 | 缺少Redis防重复标记 | 检查下单前是否执行setnx操作并判断返回结果 |
| 前端跨域访问失败 | CORS配置缺失 | 后端增加WebMvcConfigurer的跨域配置 |
| 部署后页面刷新404 | 缺少try_files配置 | Nginx补充try_files $uri $uri/ /index.html |
| Redis缓存和数据库数据不一致 | 缓存更新逻辑遗漏 | 商品修改、下架时同步删除相关缓存 |
| 秒杀接口被人恶意刷请求 | 缺少限流 | 增加用户维度的频率限制,如每秒最多1次 |
这些异常中,极其容易遗漏的是模拟支付后缓存没有清理。有些同学秒杀成功后去模拟支付,查询订单状态时发现一直是待支付,日志也没有报错,查了很久才发现是订单状态数据库更新了,但Redis里的订单缓存还是旧值。在代码里做查询时优先查缓存会读到脏数据,解决办法是在支付成功后的逻辑里主动删除订单缓存。
5.2 答辩准备:导师最常追问的6个问题
答辩阶段,系统做出来只是基础,能把设计思路讲明白才是拿高分的关键。根据我个人的观察和总结,导师对秒杀系统的高频提问集中在下面六个方向上:
第一个问题:你是怎么解决超卖问题的?回答思路要分两层,一是Redis预扣库存前置拦截超卖请求,二是MySQL中的条件更新兜底库存精度,两层配合。第二个问题:如果Redis宕机了怎么办?这个问题是陷阱题,回答时说明Redis宕机前已经有库存加载和持久化,宕机后系统自动降级到数据库直连模式,性能下降但数据不丢,宕机恢复后重新加载库存。第三个问题:消息队列在你的系统里能起到什么作用?回答异步下单、流量削峰,并要求说明如果没有MQ时你的接口如何处理峰值流量,可以用限流+同步下单来说明。第四个问题:为什么订单状态要设计成待支付、已支付、已取消?回答要说明防止用户无限占用库存、超时释放库存的设计。第五个问题:你的系统能扛多少并发?这时候用压测数据说话,比如JMeter压测下QPS能到多少,服务器配置如何。第六个问题:缓存和数据库的一致性如何保证?回答先更新数据库再删除缓存,读请求查缓存未命中再回源数据库并回填缓存,这个模式叫Cache Aside Pattern。
回答问题的核心原则是:先说结论,再讲过程,最后补充优化方向。比如超卖问题,先声明确实解决了超卖,然后讲Redis预扣减和数据库条件更新的具体实现,最后说明可以考虑加入消息队列进一步提升性能,形成完整答题链路。
5.3 报告撰写要点
项目报告是答辩前必须交的材料,结构上建议按照摘要、绪论、需求分析、系统设计、系统实现、系统测试、总结与展望的顺序来写。其中系统设计部分要包含详细的功能模块图和数据表结构说明,对应到项目里的每个接口和数据库表。系统实现部分配上核心代码截图和关键接口日志截图,这里注意代码截图不要贴长段源码,而是截取最关键那几行然后加注释说明,让阅读者快速抓住重点。
报告中常见的问题是直接网上下载模板或者截图与自己系统明显不符,答辩时导师一眼就看出来。比如报告里写着使用RabbitMQ异步下单,但实际系统里根本没有安装RabbitMQ,这样会给答辩造成极大的信任危机。建议报告内容与项目的实际实现严格对齐,扩展性的东西可以写进总结与展望里,不要写进已实现部分。
测试部分的报告,除了功能测试用例之外,一定要加上并发压测的内容,记录并发线程数、吞吐量、平均响应时间、错误率,用表格呈现。这一部分往往是最能体现项目完成度的地方。
5.4 答辩前的系统自查清单
答辩前两三天不要改代码加功能,把时间花在自查上。清单整理如下:确认所有页面可以正常打开,秒杀完整流程跑通,订单能够超时关闭,退出登录后不能访问受保护接口,后端日志无异常堆栈,数据库无脏数据,Redis缓存能正常回填,Nginx静态资源访问正常,服务器上的jar包和dist目录是最新版本。有余力的情况下,准备一个备用账号,避免现场登录失败导致整个演示卡住。
答辩当天演示的浏览器推荐提前准备好,包括登录态的保持,最好把需要演示的核心路径提前跑一遍,确认倒计时安全,不要现场展示到一半商品已经被抢完导致尴尬。实际上,最稳妥的做法是答辩前重置数据库到秒杀还未开始的状态,这样现场演示倒计时和秒杀按钮的切换会非常自然。
写在最后
做秒杀系统这个项目,我从最初只会写增删改查,到最终能完整理解Redis预扣库存、数据库兜底、乐观锁防超卖这一整套高并发思路,收获的不仅是代码量,更重要的是一种“流量来了系统会怎么扛”的思维方式。如果这篇文章能帮你少熬几个通宵,或者让你在答辩时多一份底气,那我这些经验就没白分享。最后再提醒一句,项目能跑通很容易,能讲清楚很难,多花点时间想想每行代码为什么这么写,才是这个项目真正值钱的地方。