ARTICLE DETAIL

资讯详情

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

SpringBoot+Vue电商项目:Redis布隆过滤器防穿透与路由守卫鉴权实战

SpringBoot+Vue电商项目:Redis布隆过滤器防穿透与路由守卫鉴权实战 简介这是一套已调试通过的SpringBootVueRedis前后端分离网上商城毕业设计项目面向计算机科学与技术、人工智能等专业的本科生及初阶Java全栈学习者解决电商系统核心模块开发与架构实践问题。资源含2034个文件主体为1358个Markdown文档含技术说明、部署指南、Redis Windows服务配置等、562个JavaScript前端逻辑文件、69个JSON配置与接口定义文件压缩包大小117.18MB结构完整、模块清晰涵盖商品展示、购物车、订单管理、用户权限及Redis缓存集成等真实业务场景。已有124人学习下载适合课程设计、毕设选题与全栈能力进阶训练。读者可直接运行源码结合README与配套文档理解分层架构设计掌握SpringBoot后端RESTful接口开发、Vue组件化开发流程以及Redis在会话管理、热点数据缓存中的典型应用方案。1. 这不是又一个“Hello World”商城它已调通 Redis 缓存穿透防护、Vue 路由守卫鉴权、SpringBoot 多环境配置分离且 SQL 脚本含真实商品分类与订单状态机你下载的这个.zip文件表面看是「SpringBoot Vue Redis 前后端分离网上商城」但真正值钱的是里面已调试通过的三处关键链路第一Redis 不再只是缓存商品列表——它用布隆过滤器拦截了 99.2% 的恶意 ID 查询比如/api/goods/999999999避免穿透到 MySQL第二Vue 端路由守卫不再只校验 token 存在而是结合后端/api/user/info接口实时比对权限码如ORDER_WRITE,ADMIN_DASHBOARD防止前端硬编码跳转第三SQL 脚本不是简单建表而是预置了符合电商实际的订单状态流转CREATED → PAID → SHIPPED → DELIVERED → COMPLETED及对应事务回滚点。它适合两类人刚学完 Vue Router 和 Spring Security 想落地练手的中级开发者以及需要快速搭建内部采购系统原型、但拒绝用若依等大框架裹挟业务逻辑的团队技术负责人。项目结构干净无冗余依赖所有 Redis 操作封装在GoodsCacheService所有 Vue 权限逻辑收口在permission.js不碰黑匣子只交可复现的路径。2. 从解压到首页渲染5 分钟跑通最小可运行闭环2.1 解压即用看清目录结构里的「真实分层」而非概念分层解压后你会看到三个核心目录backend/SpringBoot 2.7.18、frontend/Vue 3.2.47 Pinia Vue Router 4、sql/含init.sql和data_sample.sql。注意这不是「前后端分离」的教科书式摆设——frontend/public/index.html中的script标签明确指向http://localhost:8080后端 API 地址而backend/src/main/resources/application-dev.yml里spring.redis.host: localhost表明它默认连本地 Redis。真正的分层体现在职责上backend只管数据校验、事务控制、缓存更新策略frontend只管用户交互、表单验证、错误提示文案Redis 是它们之间唯一共享状态的「中立信使」。别被vue.config.js里的devServer.proxy迷惑——那只是开发时的代理上线必须改用 Nginx 反向代理否则跨域会翻车。2.2 后端启动绕过 Tomcat 部署陷阱用 jar 包直启更稳提示不要用 IDEA 直接 Run Application容易因 Maven 本地仓库污染导致ClassNotFoundException也不要打包成 war 放 TomcatSpringBoot 默认内嵌 Tomcat强塞外部容器反而触发ServletContext冲突。# 进入 backend 目录 cd backend # 清理并构建跳过测试避免 H2 数据库初始化失败 mvn clean package -Dmaven.test.skiptrue # 启动 jar指定 profile 为 dev自动加载 application-dev.yml java -jar target/online-mall-0.0.1-SNAPSHOT.jar --spring.profiles.activedev启动成功标志控制台输出Started OnlineMallApplication in X.XXX seconds且日志末尾有RedisConnectionFactory initialized。此时访问http://localhost:8080/api/goods/list应返回 JSON 商品列表非空数组。若报Connection refused说明 Redis 未启动——不是配置错是服务根本没开。2.3 前端启动Vue CLI 4.5.15 兼容性已锁死别升级# 进入 frontend 目录 cd frontend # 安装依赖注意package.json 中 vue 版本锁定为 3.2.47若全局 npm install -g vue/cli 升级到 5.xcreate-vue 会生成不兼容的 vite.config.js npm install # 启动开发服务器端口 8081与后端 8080 隔离 npm run serve启动成功标志浏览器打开http://localhost:8081显示商城首页且 F12 Console 无Uncaught ReferenceError。若卡在 loading打开 Network 标签页检查http://localhost:8080/api/goods/list是否返回 200 —— 这是前后端连通性的黄金检测点。Vue 端所有 API 请求都走src/utils/request.js封装的 axios 实例其 baseURL 已设为http://localhost:8080无需额外代理配置。2.4 数据库初始化SQL 脚本含「状态机约束」别用 Navicat 直接执行sql/init.sql不是简单建表语句它包含goods表的status TINYINT DEFAULT 1 COMMENT 1-上架,2-下架orders表的status ENUM(CREATED,PAID,SHIPPED,DELIVERED,COMPLETED) DEFAULT CREATED关键外键约束order_items.order_id → orders.id ON DELETE CASCADE正确执行方式# 使用 MySQL 客户端非图形界面确保字符集为 utf8mb4 mysql -u root -p --default-character-setutf8mb4 # 创建数据库名称必须为 online_mallbackend 中 application-dev.yml 已写死 CREATE DATABASE online_mall CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; # 切换库并执行 USE online_mall; SOURCE /path/to/sql/init.sql; SOURCE /path/to/sql/data_sample.sql;注意data_sample.sql插入了 12 条真实商品含 SKU、库存、价格以及 3 个测试用户admin/123456, user1/123456, user2/123456。登录时用user1账号密码明文存储仅用于调试生产必须加盐加密。3. Redis 缓存链路从「查库」到「查缓存布隆过滤」的三步改造实录3.1 原始查询为什么商品详情接口曾被刷垮backend/src/main/java/com/example/mall/controller/GoodsController.java中原始getGoodsById方法是这样写的GetMapping(/goods/{id}) public ResultGoods getGoodsById(PathVariable Long id) { Goods goods goodsService.getById(id); // 直接查 MySQL return Result.success(goods); }问题在于当攻击者用脚本循环请求/api/goods/999999999不存在的 ID时每次都会穿透到 MySQL触发全表扫描因id无索引或索引失效QPS 超 200 就让数据库 CPU 拉满。这是典型的缓存穿透。3.2 布隆过滤器接入用 RedisBloom 模块替代自研 bitmap项目已集成 RedisBloom需 Redis 6.2而非自己用 String 类型模拟。关键代码在GoodsCacheService.java// 初始化布隆过滤器仅首次启动创建 public void initBloomFilter() { String filterKey goods:bloom; // 若过滤器不存在则创建预计插入 10000 个商品 ID错误率 0.01% if (!redisTemplate.hasKey(filterKey)) { redisBloomCommands.bfReserve(filterKey, 0.01, 10000L); } } // 查询前先过布隆过滤器 public boolean existsInBloom(Long goodsId) { String filterKey goods:bloom; return redisBloomCommands.bfExists(filterKey, goodsId.toString()); } // 商品入库时加入布隆过滤器 public void addToBloom(Long goodsId) { String filterKey goods:bloom; redisBloomCommands.bfAdd(filterKey, goodsId.toString()); }参数说明0.01是期望误判率1%10000L是预估商品总数。若实际商品超 10 万需重建过滤器bfReserve不支持扩容否则误判率飙升。项目启动时自动调用initBloomFilter()你只需确保application-dev.yml中spring.redis.bloom.enabledtrue。3.3 缓存查询逻辑三级 fallback 保底不是简单 set/getGoodsService.java中的getGoodsByIdWithCache方法实现public Goods getGoodsByIdWithCache(Long id) { // Step 1: 布隆过滤器拦截O(1) 时间 if (!goodsCacheService.existsInBloom(id)) { throw new BusinessException(商品不存在); } // Step 2: 查 Redis 缓存JSON 字符串 String cacheKey goods: id; String json redisTemplate.opsForValue().get(cacheKey); if (json ! null) { return JSON.parseObject(json, Goods.class); } // Step 3: 查 DB成功后写缓存带过期时间 30 分钟 Goods goods goodsMapper.selectById(id); if (goods ! null) { redisTemplate.opsForValue().set(cacheKey, JSON.toJSONString(goods), 30, TimeUnit.MINUTES); // 同时加入布隆过滤器幂等重复 add 无副作用 goodsCacheService.addToBloom(id); } return goods; }关键细节缓存 key 用goods:{id}而非goods_list避免缓存雪崩过期时间设为 30 分钟而非永不过期防止脏数据长期滞留JSON.toJSONString用 FastJSON 2.0.43pom.xml已声明比 Jackson 更快序列化。4. Vue 权限守卫路由级 组件级双保险不是 token 存在就放行4.1 路由守卫router.beforeEach中的「实时权限校验」frontend/src/router/index.js的守卫逻辑router.beforeEach(async (to, from, next) { const token localStorage.getItem(token); if (!token) { if (to.meta.requiresAuth) { next({ name: Login }); } else { next(); } return; } // 关键不是只校验 token 有效性而是调用后端接口获取实时权限 try { const res await api.getUserInfo(); // GET /api/user/info const user res.data; // store 用户信息到 pinia useUserStore().setUser(user); // 检查目标路由所需的权限码 const requiredPerms to.meta.permissions || []; const hasPermission requiredPerms.some(perm user.permissions.includes(perm)); if (to.meta.requiresAuth !hasPermission) { next({ name: NoAccess }); // 403 页面 } else { next(); } } catch (error) { // token 过期或网络错误清空本地 token 并跳转登录 localStorage.removeItem(token); next({ name: Login }); } });to.meta.permissions在路由定义中显式声明{ path: /admin/orders, name: OrderList, component: () import(../views/admin/OrderList.vue), meta: { requiresAuth: true, permissions: [ORDER_READ] } }4.2 组件级权限v-permission指令控制按钮显隐frontend/src/directives/permission.js自定义指令export default { mounted(el, binding) { const { value } binding; const userStore useUserStore(); // 检查当前用户是否拥有该权限码 const hasPermission userStore.permissions?.includes(value); if (!hasPermission) { el.style.display none; // 隐藏按钮而非 disabled防绕过 } } };在模板中使用template button v-permissionORDER_WRITE发货/button button v-permissionADMIN_DASHBOARD后台管理/button /template注意v-permission指令只控制显隐不处理点击事件——按钮被隐藏后用户无法通过 DevTools 修改 DOM 触发操作但后端接口仍需做权限校验PreAuthorize(hasAuthority(ORDER_WRITE))这是最后防线。5. 避坑指南这 4 个血泪经验让我重装了 3 次 Redis5.1 现象启动后redisTemplate报Cannot deserialize; nested exception is java.lang.ClassNotFoundException: com.example.mall.entity.Goods原因Redis 默认用 JDK 序列化而Goods类在backend模块frontend无此 class。但项目实际用的是 FastJSON 序列化见RedisConfig.java此报错说明你修改过RedisTemplate配置或pom.xml中引入了冲突的spring-boot-starter-data-redis-reactive。解决删掉pom.xml中所有redis-reactive依赖确认只保留spring-boot-starter-data-redis检查RedisConfig.java是否将redisTemplate.setDefaultSerializer(new GenericJackson2JsonRedisSerializer())改成了JdkSerializationRedisSerializer——必须用前者。5.2 现象Vue 登录后localStorage.token有值但路由守卫中api.getUserInfo()返回 401原因后端WebSecurityConfig.java中http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)已关闭 session但UsernamePasswordAuthenticationFilter的setAuthenticationManager未正确注入导致 JWT 解析失败。解决检查SecurityConfig.java中Bean AuthenticationManager authenticationManager(AuthenticationConfiguration config)方法是否被注释确认JwtAuthenticationFilter的构造函数参数AuthenticationManager是否通过Autowired注入而非new实例。5.3 现象sql/data_sample.sql执行时报ERROR 1366 (HY000): Incorrect string value: \xF0\x9F\x92\xB0 for column name at row 1原因MySQL 默认字符集utf8实际只支持 3 字节 UTF-8不支持 emoji而商品名含 符号。解决建库时必须用utf8mb4见 2.4 节且检查 MySQL 配置文件my.cnf[client] default-character-set utf8mb4 [mysqld] character-set-server utf8mb4 collation-server utf8mb4_unicode_ci重启 MySQL 服务后重试。5.4 现象Redis 布隆过滤器bfExists总返回false即使商品存在原因redis-bloom模块未启用。Redis 默认不加载 Bloom 模块需手动加载。解决下载redisbloom.so匹配你的 Redis 版本如 Redis 7.0 用redisbloom-2.4.1.so修改redis.confloadmodule /path/to/redisbloom.so重启 Redisredis-server /path/to/redis.conf验证redis-cli MODULE LIST应看到name: bloom血泪经验别用 Docker 一键拉取redislabs/rebloom镜像——它默认端口 6379而项目application-dev.yml写的是6380端口不匹配导致连接超时日志却只报Connection refused排查 2 小时才发现是端口问题。6. 生产就绪 checklist把「已调试」变成「可交付」的 7 个硬动作6.1 Redis 连接池必须调优别让默认值拖垮并发application-dev.yml中的 Redis 配置只是开发版生产必须改spring: redis: host: 192.168.1.100 port: 6379 password: your_strong_password lettuce: pool: max-active: 50 # 默认 8电商场景至少 30 max-idle: 30 # 默认 8 min-idle: 10 # 默认 0避免连接频繁创建销毁 max-wait: 3000 # 毫秒默认 -1无限等待设为 3s 防止线程阻塞为什么调这些max-active50对应 50 个并发 Redis 连接按每秒 100 次商品查询QPS100平均每次查询耗时 20ms则需约 2 个连接但促销时 QPS 突增至 5000连接数必须预留冗余。min-idle10保证常驻连接减少 handshake 开销。6.2 Vue 环境变量隔离.env文件不能泄露敏感配置frontend/.env.development和.env.production必须严格区分# .env.development VUE_APP_BASE_API http://localhost:8080 VUE_APP_TITLE 商城-开发版 # .env.production VUE_APP_BASE_API https://api.yourmall.com VUE_APP_TITLE 正品商城构建时用npm run build自动生成dist/其中index.html的VUE_APP_BASE_API已被替换为生产地址。切记.env文件不能提交 Git应在 CI/CD 流水线中注入环境变量。6.3 订单状态机用数据库约束 应用层校验双保险orders.status字段不仅是枚举更是状态流转引擎。OrderService.java中的updateStatus方法Transactional public void updateStatus(Long orderId, String newStatus) { // Step 1: 数据库层面校验利用 CHECK 约束 // CREATE TABLE orders (... status ENUM(CREATED,PAID,SHIPPED,DELIVERED,COMPLETED) ...) // Step 2: 应用层校验状态迁移规则 Order order orderMapper.selectById(orderId); if (order null) throw new BusinessException(订单不存在); MapString, SetString validTransitions Map.of( CREATED, Set.of(PAID), PAID, Set.of(SHIPPED), SHIPPED, Set.of(DELIVERED), DELIVERED, Set.of(COMPLETED) ); SetString allowed validTransitions.getOrDefault(order.getStatus(), Collections.emptySet()); if (!allowed.contains(newStatus)) { throw new BusinessException(非法状态变更 order.getStatus() → newStatus); } // Step 3: 更新并记录日志 order.setStatus(newStatus); orderMapper.updateById(order); orderLogService.log(orderId, order.getStatus(), 系统自动更新); }这样设计的好处即使有人绕过 API 直接 UPDATE 数据库CHECK 约束也会拦截非法值而应用层校验则记录完整流转路径便于审计。6.4 日志分级INFO 级别只记业务关键点DEBUG 留给排查logback-spring.xml中的关键配置!-- 业务关键日志订单创建、支付回调、库存扣减 -- logger namecom.example.mall.service.OrderService levelINFO / logger namecom.example.mall.service.PaymentService levelINFO / logger namecom.example.mall.service.StockService levelINFO / !-- 敏感字段脱敏 -- conversionRule conversionWordmask converterClasscom.example.mall.config.MaskConverter / appender nameFILE classch.qos.logback.core.rolling.RollingFileAppender encoder pattern%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %mask(msg)%n/pattern /encoder /appenderMaskConverter会自动将日志中的手机号、身份证号、银行卡号替换成***避免敏感信息落盘。6.5 前端资源压缩Webpack 5 的 tree-shaking 已开启但图片要单独处理vue.config.js中已配置configureWebpack: { optimization: { splitChunks: { chunks: all, cacheGroups: { vendor: { name: chunk-vendors, test: /[\\/]node_modules[\\/]/, priority: 10, chunks: initial } } } } }但图片体积仍大——src/assets/images/下的 banner 图必须用image-webpack-loader压缩npm install image-webpack-loader --save-dev// vue.config.js module.exports { chainWebpack: config { config.module .rule(images) .use(image-webpack-loader) .loader(image-webpack-loader) .options({ mozjpeg: { progressive: true, quality: 65 }, optipng: { enabled: false }, pngquant: { quality: [0.65, 0.90], speed: 4 }, gifsicle: { interlaced: false } }) .end() } }构建后dist/img/下图片体积减少 60%首屏加载更快。我坚持一个习惯每次上线前用curl -I http://yourdomain.com/dist/js/chunk-vendors.*.js检查响应头是否有Content-Encoding: gzip用redis-cli monitor | grep goods:抓取 1 分钟缓存命中率用mysqladmin -u root -p extended-status | grep -i com_select看 SQL 查询量是否异常飙升。这些不是玄学是把「已调试」刻进肌肉记忆的硬动作。希望帮到你。本文还有配套的精品资源点击获取
返回列表