Spring Boot网上商城高并发与安全防护实战指南
简介本资源是一套完整的SpringBoot网上商城购物系统毕业设计实现方案面向计算机专业本科生及Java初学者聚焦电商核心功能开发与系统工程实践。压缩包含847个文件总大小29.32MB涵盖130个Java后端逻辑文件、153个JavaScript交互脚本、48个Vue前端组件、45个CSS样式文件、63个JPG/GIF商品图及SVG图标资源另有SQL建表语句、PPT答辩稿、完整论文含绪论、技术选型、需求与系统分析、管理员/用户/前台三大模块详细设计、测试用例等共6章以及3个批处理启动脚本结构清晰、开箱即用。目前已有49人学习下载适合课程设计、毕设参考或SpringBootMySQLVue全栈入门实战——可直接运行调试快速掌握B/S架构下购物车、商品管理、用户权限控制等电商核心业务的代码组织方式与前后端联调要点。1. 为什么用 Spring Boot 做网上商城不是“能跑就行”而是要扛住秒杀、防住 XSS、撑住并发写入——毕业设计里最容易被答辩老师当场叫停的三个硬伤你交上去的“SpringBoot 网上商城购物系统设计与实现.zip”压缩包里有源码、论文、PPT但答辩现场老师点开首页问一句“用户下单时库存扣减怎么保证原子性”你答“用了 Transactional”他再追问“那超卖呢Redis 和数据库双写一致性怎么兜底”你卡住——这已经不是代码能不能编译的问题而是整个系统架构在逻辑层面就存在断裂带。这不是毕设是生产级系统雏形。真正落地的 Spring Boot 网上商城必须直面三类真实压力高并发下单尤其限时抢购、富文本/文件上传带来的 XSS 与文件注入风险、多角色买家/卖家/管理员混合操作下的权限穿透与数据越权。本篇不讲“如何新建一个 Spring Boot 项目”而是带你从压缩包解压那一刻起逐层拆解这个 ZIP 包里实际能跑、能防、能扩、能过答辩的最小可行闭环它必须包含可验证的库存预扣机制、带白名单过滤的文件上传管道、基于 RBAC 的细粒度接口鉴权且所有关键路径都留有可观测埋点。适合正在赶毕设 deadline、但不想答辩翻车的 Java 后端同学也适合想用毕业项目练手真实电商链路的初级工程师——我们只做一件事让这个 ZIP 包从“能演示”变成“经得起问”。2. 从 ZIP 解压到可运行还原真实开发环境的 4 个关键动作拿到springboot网上商城购物系统设计与实现.zip别急着导入 IDEA。压缩包结构往往隐含陷阱有人把application.yml放在src/main/resources下却在pom.xml里写了packagingwar/packaging有人把 MySQL 驱动版本锁死在5.1.38而你的本地 MySQL 是 8.x更常见的是PPT 里写着“使用 Redis 缓存商品详情”但源码里连RedisTemplate的 Bean 都没声明。还原环境不是机械解压而是逆向工程式校验。以下四步缺一不可。2.1 解压后第一件事用tree -L 2快速扫描项目骨架识别真实技术栈在终端进入解压目录执行tree -L 2 -I target|node_modules|.git|.idea|out|*.zip|*.pdf|*.pptx|*.docx提示-I参数排除干扰项聚焦代码结构。重点看三处src/main/java下是否有controller/service/mapper三级包且controller中是否出现RestController而非Controller判断是否为前后端分离架构src/main/resources下是否存在application-dev.yml、application-prod.yml等多环境配置而非仅有一个application.yml决定你能否安全切换数据库pom.xml中parent标签是否指向spring-boot-starter-parent且version是否明确如2.7.18避免因版本模糊导致依赖冲突。若发现pom.xml中spring-boot.version写成${spring-boot.version}且未在properties中定义——这是典型“导师给的模板未填坑”必须手动补全否则 Maven 会报Could not resolve version。2.2 数据库初始化别信 SQL 文件里的CREATE DATABASE先确认字符集和引擎多数毕业设计 ZIP 包附带sql/目录内含mall_schema.sql和mall_data.sql。但直接mysql -u root -p mall_schema.sql很可能失败。原因在于MySQL 8.0 默认字符集是utf8mb4而老 SQL 文件常写DEFAULT CHARSETutf8实际只支持 3 字节 UTF-8无法存 emoji。执行前务必打开mall_schema.sql将所有DEFAULT CHARSETutf8替换为DEFAULT CHARSETutf8mb4 COLLATEutf8mb4_0900_ai_ci并在建库语句开头显式指定CREATE DATABASE IF NOT EXISTS mall_db CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci;参数说明utf8mb4_0900_ai_ci是 MySQL 8.0 推荐的排序规则支持大小写不敏感ai和口音不敏感ci比旧版utf8_general_ci更精准匹配中文搜索。若用utf8mb4_unicode_ci在 LIKE 查询中可能因 Unicode 版本差异导致结果偏差。执行完建库再导入数据。若报错ERROR 1067 (42000): Invalid default value for create_time说明表字段用了DATETIME NOT NULL DEFAULT 0000-00-00 00:00:00—— 这是 MySQL 5.7 严格模式禁用的。需将所有DEFAULT 0000-00-00 00:00:00改为DEFAULT CURRENT_TIMESTAMP。2.3 Spring Boot 版本适配当pom.xml写着2.3.12.RELEASE而你装的是 JDK 17Spring Boot 2.x 官方支持最高到 JDK 15若你本地 JDK 是 17 或 21启动必报java.lang.UnsupportedClassVersionError。此时不能简单降级 JDK影响其他项目而应升级 Spring Boot 版本。但注意不是所有 ZIP 包都能无痛升级。先查pom.xml中parent的version再对照 Spring Boot 官方兼容矩阵 。例如Spring Boot 版本推荐 JDKMyBatis-Plus 兼容版本2.7.188–173.5.33.2.417–213.5.5需改MapperScan为Mapper若原 ZIP 用的是 MyBatis-Plus 3.4.3对应 Spring Boot 2.5.x强行升到 3.2.x 会导致LambdaQueryWrapper构造器报错。稳妥做法是保持 Spring Boot 小版本号不变仅升级补丁版如2.7.12→2.7.18该升级只修复 CVE 漏洞不破坏 API 兼容性。2.4 静态资源与模板路径校验Thymeleaf 模板找不到先查spring.thymeleaf.prefix很多 ZIP 包的application.yml写着spring: thymeleaf: prefix: classpath:/templates/ suffix: .html但实际src/main/resources/templates/下却是index.ftlFreeMarker或index.jspJSP。此时启动后访问/返回 404不是代码问题而是模板引擎错配。解决方案分两步确认前端技术栈查看pom.xml是否含spring-boot-starter-thymeleafThymeleaf、spring-boot-starter-freemarkerFreeMarker或spring-boot-starter-webjsp-apiJSP。若三者共存以pom.xml中第一个声明的 starter 为准统一路径约定若确定用 Thymeleaf则确保src/main/resources/templates/下全是.html文件且 Controller 返回字符串index时框架会自动匹配templates/index.html。若返回redirect:/admin则跳转至/admin路径而非渲染模板。血泪经验曾见某 ZIP 包templates/目录下混存login.html和product.ftl开发者以为 Thymeleaf 会自动 fallback 到 FreeMarker结果login.html渲染正常product.ftl直接 500。根源是 Spring Boot 只加载一个模板引擎多引擎共存需手动配置ViewResolver毕业设计中应避免。3. 让商城“真能买”库存扣减、订单生成、支付回调的三段式事务控制网上商城最核心的不是商品展示而是“下单成功”那一刻的确定性。ZIP 包里常见的OrderService.createOrder()方法若只写update stock set count count - 1 where id ? and count 0在并发场景下必然超卖。必须构建应用层 数据库层 缓存层的三重防护。本节不讲理论只给可粘贴、可验证的代码块。3.1 库存预扣用 Redis Lua 脚本实现原子性校验与扣减MySQL 行锁在高并发下性能骤降而 Redis 单线程特性天然适合做库存预占。关键不是“用不用 Redis”而是Lua 脚本必须同时完成“读库存”、“判余量”、“扣库存”三步杜绝中间状态被篡改。在src/main/resources/redis/下新建stock_lock.lua-- stock_lock.lua -- KEYS[1]: 商品ID, ARGV[1]: 扣减数量, ARGV[2]: 预占有效期秒 local stock_key stock: .. KEYS[1] local stock tonumber(redis.call(GET, stock_key)) if not stock or stock tonumber(ARGV[1]) then return -1 -- 库存不足 end redis.call(DECRBY, stock_key, ARGV[1]) redis.call(EXPIRE, stock_key, tonumber(ARGV[2])) return stock - tonumber(ARGV[1]) -- 返回扣减后余量Java 调用时// StockService.java Autowired private RedisTemplateString, Object redisTemplate; public boolean tryLockStock(Long productId, Integer quantity) { DefaultRedisScriptLong script new DefaultRedisScript(); script.setScriptSource(new ResourceScriptSource( new ClassPathResource(redis/stock_lock.lua))); script.setResultType(Long.class); Long result redisTemplate.execute(script, Collections.singletonList(String.valueOf(productId)), String.valueOf(quantity), 60); // 预占60秒 return result ! null result 0; }参数说明DECRBY原子递减EXPIRE设置过期时间防止预占长期不释放。若result -1说明库存不足直接返回失败若result 0表示预占成功可进入下一步创建订单。注意Lua 脚本中KEYS[1]对应Collections.singletonList(...)的第一个元素ARGV数组从String.valueOf(quantity)开始顺序传入。3.2 订单生成用数据库乐观锁 本地事务避免“预占成功但订单落库失败”预占成功 ≠ 下单成功。若此时 MySQL 插入订单失败如唯一索引冲突、网络中断Redis 库存已扣必须回滚。但 Redis 不支持事务回滚因此采用“预占成功 → 创建订单 → 异步补偿”策略Transactional(rollbackFor Exception.class) public Order createOrder(Long userId, Long productId, Integer quantity) { // 1. 预占库存调用 tryLockStock if (!stockService.tryLockStock(productId, quantity)) { throw new BusinessException(库存不足); } // 2. 创建订单含唯一订单号、用户ID、商品ID、状态待支付 Order order new Order(); order.setOrderNo(IdUtil.getSnowflakeNextIdStr()); // 使用雪花算法生成唯一订单号 order.setUserId(userId); order.setProductId(productId); order.setStatus(OrderStatus.WAIT_PAY.getCode()); order.setCreateTime(LocalDateTime.now()); orderMapper.insert(order); // MyBatis-Plus insert // 3. 发送 MQ 消息触发后续流程支付超时关闭、物流同步等 rabbitTemplate.convertAndSend(order.exchange, order.create, order); return order; }关键点Transactional保证orderMapper.insert()失败时整个方法回滚但Redis 预占无法自动回滚。因此必须配套异步补偿任务监听订单创建失败事件调用redisTemplate.opsForValue().increment(stock: productId, quantity)回补库存。ZIP 包若无此补偿逻辑属于重大缺陷。3.3 支付回调用幂等 Token 状态机驱动防重复通知与状态错乱支付宝/微信支付回调地址常被恶意刷请求。ZIP 包里常见写法是if (order.getStatus() WAIT_PAY) { updateStatusToPaid(); }但若回调重试两次订单状态会从WAIT_PAY→PAID→PAID看似无害实则第二次更新时WHERE status WAIT_PAY条件不成立SQL 影响行数为 0日志里却无报错导致业务监控失灵。正确做法是引入幂等 Token// PayCallbackController.java PostMapping(/callback) public String handlePayCallback(RequestBody MapString, String params) { String outTradeNo params.get(out_trade_no); // 商户订单号 String payToken params.get(pay_token); // 支付平台返回的唯一标识 // 1. 校验 token 是否已处理Redis Set String tokenKey pay:token: outTradeNo; Boolean isProcessed redisTemplate.opsForSet() .isMember(tokenKey, payToken); if (Boolean.TRUE.equals(isProcessed)) { return success; // 幂等直接返回 } // 2. 更新订单状态乐观锁 int updated orderMapper.updateStatusByTradeNo( outTradeNo, OrderStatus.WAIT_PAY.getCode(), OrderStatus.PAID.getCode()); if (updated 1) { // 3. 记录已处理 token有效期24小时 redisTemplate.opsForSet().add(tokenKey, payToken); redisTemplate.expire(tokenKey, Duration.ofHours(24)); // 4. 发送支付成功消息 rabbitTemplate.convertAndSend(pay.exchange, pay.success, outTradeNo); } return success; }避坑点updateStatusByTradeNo方法需在 XML 中写UPDATE order SET status #{paidStatus} WHERE order_no #{outTradeNo} AND status #{waitPayStatus}利用数据库AND status ...条件实现乐观锁确保只有WAIT_PAY状态才能更新为PAID。若 ZIP 包用UPDATE ... SET status #{paidStatus} WHERE order_no #{outTradeNo}则存在状态覆盖风险。4. 防 XSS、防文件上传漏洞、防越权访问毕业设计里最常被忽略的安全三件套ZIP 包里UserServiceImpl.updateUserInfo()方法若直接接收RequestBody User user并调用userMapper.updateById(user)等于把用户输入的 HTML 标签原样存进数据库下次渲染时scriptalert(1)/script就会执行。安全不是“加个过滤器就行”而是在数据入口、存储、输出三环节层层设防。本节给出毕业设计可立即落地的最小防护集。4.1 富文本内容用 Jsoup 白名单清洗而非正则替换学生常写content.replaceAll(script, )但攻击者可绕过scrscriptipt或#60;script#62;。正确做法是 Jsoup 的白名单机制// ContentSecurityUtil.java public static String cleanHtml(String html) { if (StringUtils.isBlank(html)) return ; return Jsoup.clean(html, Whitelist.relaxed() // 允许常见标签 .addTags(img, video, audio) // 允许多媒体 .addAttributes(:all, class, id, style) // 允许样式类 .addAttributes(img, src, alt, width, height) .addProtocols(img, src, http, https, data) // 限制 img src 协议 .addProtocols(a, href, http, https)); // 限制 a href 协议 }在 Controller 层调用PostMapping(/user/update) public Result updateUser(RequestBody User user) { user.setContent(ContentSecurityUtil.cleanHtml(user.getContent())); userService.updateById(user); return Result.success(); }参数说明Whitelist.relaxed()允许pbrstrong等基础标签addProtocols严格限制src和href只能是http/https/data杜绝javascript:alert(1)注入。若 ZIP 包未引入 Jsoup 依赖需在pom.xml添加dependency groupIdorg.jsoup/groupId artifactIdjsoup/artifactId version1.17.2/version /dependency4.2 文件上传用 Apache Tika 检测真实 MIME 类型拒绝伪装的 JSPZIP 包里FileController.upload()常见错误是只校验文件后缀名// ❌ 危险写法 if (!filename.endsWith(.jpg) !filename.endsWith(.png)) { throw new BusinessException(只允许上传 JPG/PNG); }攻击者可将shell.jsp重命名为shell.jpg上传服务器解析时仍执行 JSP。必须用 Tika 读取文件头字节// FileUploadService.java public String uploadFile(MultipartFile file) throws IOException { // 1. 检查文件大小防止 DOS if (file.getSize() 10 * 1024 * 1024) { // 10MB throw new BusinessException(文件大小不能超过10MB); } // 2. 用 Tika 检测真实类型 InputStream inputStream file.getInputStream(); String mimeType new Tika().detect(inputStream); inputStream.reset(); // 重置流位置供后续上传使用 if (!image/jpeg.equals(mimeType) !image/png.equals(mimeType)) { throw new BusinessException(文件类型不合法仅支持 JPG/PNG); } // 3. 保存文件不保存原始名用 UUID 时间戳 String fileName UUID.randomUUID().toString() _ System.currentTimeMillis() .jpg; Path uploadPath Paths.get(uploads/, fileName); Files.createDirectories(uploadPath.getParent()); Files.copy(file.getInputStream(), uploadPath, StandardCopyOption.REPLACE_EXISTING); return /uploads/ fileName; }注意Tika 的detect()方法需inputStream.reset()否则后续Files.copy()会读到空流。ZIP 包若缺失tika-core依赖需添加dependency groupIdorg.apache.tika/groupId artifactIdtika-core/artifactId version2.9.2/version /dependency4.3 接口权限用 Spring Security 自定义注解拒绝“管理员能删任意用户”ZIP 包里AdminController.deleteUser(Long userId)常见写法是adminService.removeById(userId)但未校验userId是否属于当前管理员管辖范围。毕业设计不必上复杂 RBAC但必须实现数据级权限隔离。自定义DataScope注解// DataScope.java Target({ElementType.METHOD}) Retention(RetentionPolicy.RUNTIME) public interface DataScope { String value() default ; // 关联字段名如 shop_id }切面拦截// DataScopeAspect.java Around(annotation(dataScope)) public Object checkDataScope(ProceedingJoinPoint joinPoint, DataScope dataScope) throws Throwable { Object[] args joinPoint.getArgs(); Long currentAdminId SecurityContextHolder.getContext() .getAuthentication().getPrincipal().toString(); // 从 JWT 解析 // 获取被删除用户的 shop_id假设用户表有 shop_id 字段 Long targetShopId (Long) args[0]; // userId 参数 ShopUser user shopUserService.getById(targetShopId); // 校验当前管理员 shop_id 是否等于目标用户 shop_id Long adminShopId adminService.getShopIdByAdminId(currentAdminId); if (!Objects.equals(adminShopId, user.getShopId())) { throw new AccessDeniedException(无权操作其他店铺用户); } return joinPoint.proceed(); }Controller 上标注DeleteMapping(/user/{id}) DataScope // 触发切面校验 public Result deleteUser(PathVariable Long id) { userService.removeById(id); return Result.success(); }避坑点若 ZIP 包用 Shiro 而非 Spring SecuritySecurityContextHolder不可用需改用SecurityUtils.getSubject().getPrincipals()。但 Spring Boot 项目强烈建议统一用 Spring Security因其与 OAuth2、JWT 集成更成熟。5. 避坑ZIP 包里 5 个高频翻车点及血泪修复方案答辩老师不会问“你怎么学的 Spring Boot”但一定会揪住 ZIP 包里藏得最深的坑。以下 5 条每一条都来自真实毕设翻车现场按“现象 → 原因 → 解决”给出可立即执行的修复命令。5.1 现象启动报错Caused by: java.lang.ClassNotFoundException: javax.servlet.Filter原因Spring Boot 2.4 移除了javax.*包改用jakarta.*但 ZIP 包里pom.xml依赖了老版spring-boot-starter-web如 2.3.x且代码中显式 importjavax.servlet.Filter。解决升级pom.xml中spring-boot-starter-web到2.7.18全局替换import javax.servlet.*为import jakarta.servlet.*若用 Lombok确保lombok版本 ≥1.18.28兼容 Jakarta EE 9。5.2 现象登录后跳转到/login?error但控制台无异常原因application.yml中spring.security.user.name和password配置了默认用户但 ZIP 包实际用数据库认证UserDetailsService实现类未被 Spring 扫描到包路径不在SpringBootApplication同级或子包。解决在UserDetailsServiceImpl类上加Service确认该类所在包路径被SpringBootApplication(scanBasePackages com.xxx.mall)覆盖在SecurityConfig中显式配置Bean public UserDetailsService userDetailsService() { return new UserDetailsServiceImpl(); }5.3 现象商品图片显示为http://localhost:8080/uploads/xxx.jpg但 Nginx 反向代理后 404原因ZIP 包里FileController.upload()返回的 URL 是绝对路径未适配反向代理。解决在application.yml中配置server: forward-headers-strategy: framework spring: web: resources: static-locations: classpath:/static/,file:uploads/Controller 返回相对路径return /uploads/ fileName;Nginx 配置location /uploads/ { alias /path/to/uploads/; }。5.4 现象MyBatis-Plus 分页插件失效PageHelper.startPage()无效果原因ZIP 包同时引入了pagehelper-spring-boot-starter和mybatis-plus-extension两者分页机制冲突。解决删除pagehelper-spring-boot-starter依赖在MybatisPlusConfig.java中启用 MP 分页Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); return interceptor; }Service 层用page productMapper.selectPage(page, wrapper)。5.5 现象Vue 前端调用/api/order/create返回 403但 Postman 调用正常原因Spring Security 默认开启 CSRF 保护而 Vue Axios 未携带X-XSRF-TOKEN。解决在SecurityConfig中关闭 CSRF毕业设计可接受http.csrf(AbstractHttpConfigurer::disable);或启用 CSRF 并配置前端获取 Tokenhttp.csrf(csrf - csrf.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()));Vue 中axios.defaults.withCredentials true并从Cookie读取XSRF-TOKEN放入请求头。6. 让答辩老师眼前一亮用 Actuator Prometheus Grafana 搭建轻量级可观测性看板答辩时老师问“系统怎么监控”你说“看日志”不如直接打开浏览器展示一个实时刷新的「订单创建 QPS」「库存缓存命中率」「慢 SQL TOP5」看板。这不是生产级运维而是用 30 行配置 1 个 Docker 命令把 ZIP 包变成可演示的可观测系统。毕业设计的价值正在于让抽象的技术能力具象化。6.1 Spring Boot Actuator暴露 12 个生产级端点只开 3 个就够用ZIP 包若未启用 Actuator先在pom.xml加依赖dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-actuator/artifactId /dependency然后在application.yml中精简暴露management: endpoints: web: exposure: include: health,metrics,prometheus # 只开这三个 endpoint: health: show-details: when_authorized metrics: tags: application: mall-system server: port: 8081 # 独立端口避免与业务端口冲突为什么只开health/metrics/prometheushealth提供/actuator/health返回UP/DOWN状态答辩时可 curl 验证metrics提供/actuator/metrics列出所有指标名如jvm.memory.used,http.server.requestsprometheus提供/actuator/prometheus格式化为 Prometheus 可抓取的文本是 Grafana 数据源基石。其他端点如env,threaddump涉及敏感信息毕业设计无需开放。6.2 Prometheus用 Docker 一键拉起3 分钟配置抓取无需安装直接用 Docker 运行 Prometheus# 创建 prometheus.yml 配置文件 cat prometheus.yml EOF global: scrape_interval: 15s scrape_configs: - job_name: mall metrics_path: /actuator/prometheus static_configs: - targets: [host.docker.internal:8081] # Mac/Windows 用 host.docker.internalLinux 用宿主机 IP EOF # 启动 Prometheus docker run -d -p 9090:9090 \ -v $(pwd)/prometheus.yml:/etc/prometheus/prometheus.yml \ --name prometheus \ prom/prometheus参数说明host.docker.internal是 Docker Desktop 提供的宿主机别名确保容器内能访问宿主机8081端口。若用 Linux需将host.docker.internal替换为172.17.0.1Docker0 网桥地址或宿主机真实 IP。验证浏览器访问http://localhost:9090/targets看到mall状态为UP即成功。6.3 Grafana导入现成商城监控面板5 分钟出图Grafana 官方仪表盘库有 ID12345虚构实际用13902—— Spring Boot Actuator Dashboard# 启动 Grafana docker run -d -p 3000:3000 \ --name grafana \ -e GF_SECURITY_ADMIN_PASSWORDadmin \ grafana/grafana-enterprise然后浏览器访问http://localhost:3000用admin/admin登录Add data source → Prometheus → URLhttp://host.docker.internal:9090Create → Import → 输入 Dashboard ID13902→ Load选择Prometheus数据源 → Import。面板自动显示HTTP 请求统计rate(http_server_requests_seconds_count{status~2..}[1m])成功 QPSJVM 内存趋势jvm_memory_used_bytes{areaheap}订单创建速率rate(jvm_threads_live_threads{applicationmall-system}[1m])需在代码中埋点见下文。6.4 在关键业务代码埋点让“下单”变成可量化的指标Actuator 的metrics端点默认不包含业务指标。需在OrderService.createOrder()中手动注册Component public class MallMetrics { private final MeterRegistry meterRegistry; public MallMetrics(MeterRegistry meterRegistry) { this.meterRegistry meterRegistry; } public void recordOrderCreateSuccess() { Counter.builder(mall.order.create.success) .description(Successful order creation count) .register(meterRegistry) .increment(); } public void recordOrderCreateFail() { Counter.builder(mall.order.create.fail) .description(Failed order creation count) .register(meterRegistry) .increment(); } }在createOrder()方法中调用try { // ... 创建订单逻辑 metrics.recordOrderCreateSuccess(); } catch (Exception e) { metrics.recordOrderCreateFail(); throw e; }验证访问http://localhost:8081/actuator/metrics/mall.order.create.success返回 JSON 中measurements的value字段即为累计值。Grafana 面板中即可用sum(rate(mall_order_create_success_total[1m]))绘制实时 QPS 曲线。我带过 17 届毕设凡是答辩时能打开 Grafana 展示「过去 5 分钟订单创建成功率 99.8%」的同学老师提问节奏明显放缓——因为你知道监控不是为了好看而是当你被问到“如果线上订单突增 10 倍怎么办”你能指着曲线说“看QPS 到 200 时 JVM 堆内存开始抖动我会先扩容 Pod再检查 Redis 连接池”。这种具象化的技术表达力远胜背诵十遍“Spring Boot 自动装配原理”。希望帮到你。本文还有配套的精品资源点击获取