ARTICLE DETAIL

资讯详情

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

SpringCloud微服务与Vue3投票系统实战解析

SpringCloud微服务与Vue3投票系统实战解析 从标题就能看出这是一个很典型的微服务 前端工程化实训型项目基于 SpringCloud 的作品投票系统前端用 Vue3 全家桶。这类项目在课程设计、毕业设计乃至企业内部的小型竞赛中非常常见但说实话百分之八十的人做出来的东西只是能跑离能讲清楚、扛得住面试追问还差得远。这篇博文我想换个角度不给你贴一堆无脑代码而是从架构决策、核心业务实现、前后端协作、以及面试官最爱问的为什么这样设计入手把整个项目的关键节点完整拆一遍。无论你是要拿它交作业还是想写进简历这篇都能直接当参考底稿用。1. 内容整体设计与服务拆分思路1.1 为什么一个投票系统也要上 SpringCloud很多人第一反应是投票系统而已单机 SpringBoot Vue2/3 一把梭不就行了非要搞微服务不是过度设计吗这话一半对一半不对。如果只是班级内部选个三好学生单机确实足够。但如果这个投票系统要面向全校、甚至公开上线那情况完全不同流量瞬时高峰投票活动往往有明确的开赛和截止时间开闸瞬间会有大量并发请求打进来业务边界清晰用户体系、作品管理、投票计数、排行榜统计、后台审核天然可以拆成独立模块团队协作需求毕设或实训中通常多人分工微服务结构能让每个成员独立负责一个服务互不阻塞面试价值SpringCloud 是 Java 后端岗位的高频考点做过和没做过面试聊起来完全两个深度。所以这个项目的定位很明确场景不大但技术栈完整、边界清晰、痛点典型是一个把微服务核心组件全部串起来的绝佳实践载体。与其做一个大而全但讲不清的项目不如做一个边界清楚、每个服务能说出设计理由的项目。1.2 服务划分的三种常见方案我在带学生做这类项目时见过三种服务拆法拆分方案服务划分优点缺点适用场景保守方案单服务 模块分包开发快、调试简单没有微服务核心组件面试减分时间极紧、纯交作业标准方案user-service / work-service / vote-service / gateway / auth边界清晰、组件齐全、工作量适中服务间调用需要设计好接口实训、毕设、简历项目激进方案在上述基础上再拆 statistics-service / message-service / admin-service微服务元素更丰富、性能隔离好开发量大、联调复杂、容易烂尾团队人数多、周期长我的建议是选标准方案再根据自身情况决定是否加一个 statistics-service 专门做排行统计。投票系统的核心痛点——并发扣票、防刷、排行榜一致性在这个结构下都能得到合理归置。1.3 技术栈选型与版本搭配这个标题同时提到了 SpringCloud 和 Vue3那我直接把我这套验证过、能跑通的组合列出来层次选型版本建议说明后端基础Spring Boot2.7.x 或 3.x建议 2.7.x兼容性最稳微服务框架Spring Cloud2021.0.x / 2022.0.x与 Boot 版本严格对应别乱配注册/配置中心Nacos2.x比 Eureka 功能强一个组件解决注册和配置两个问题网关Spring Cloud Gateway跟随 Cloud 版本替代 Zuul响应式非阻塞远程调用OpenFeign跟随 Cloud 版本声明式 HTTP 客户端数据库MySQL8.0业务数据落库缓存Redis6.x / 7.x热点数据、计数器、分布式锁前端Vue3 Vite PiniaVue 3.4 / Vite 5组合式 API Element Plus图表ECharts5.x排行榜、投票趋势可视化提示Spring Cloud 的版本号是跟着伦敦地铁站名走的比如 Camden、Hoxton、2020.0Ilford。从 2020.0 开始改用年份命名选版本前一定要去官网确认 Spring Boot 和 Spring Cloud 的兼容矩阵这一步踩坑的人特别多。2. 后端 SpringCloud 核心组件落地详解2.1 注册中心选 Nacos 而不是 Eureka理由很实在SpringCloud 入门教材里几乎都拿 Eureka 做例子但你真去做项目我建议直接用 Nacos。原因很简单Eureka 2.x 已经停止维护而 Nacos 集成了注册中心 配置中心两大能力。配置中心在微服务架构里是刚需——十几个服务的配置散落在各个 jar 包里改一个参数要重新打包上线这谁顶得住Nacos 把配置统一管理起来还能动态刷新不用重启服务。这个优势在项目里非常直观。具体落地步骤下载 Nacos Server单机模式启动sh startup.sh -m standaloneWindows 下是startup.cmd -m standalone。后端服务引入依赖Spring Boot 2.7 对应spring-cloud-starter-alibaba-nacos-discovery和spring-cloud-starter-alibaba-nacos-config。配置 bootstrap.yml指定 Nacos 地址和应用名spring: application: name: vote-service cloud: nacos: discovery: server-addr: 127.0.0.1:8848 config: server-addr: 127.0.0.1:8848 file-extension: yaml启动后在 Nacos 控制台就能看到服务实例列表服务之间通过服务名互相调用IP 地址变化对调用方透明。2.2 Spring Cloud Gateway统一入口与跨域处理网关是整个系统的门卫所有前端请求先到 Gateway再由它转发到具体服务。我推荐 Gateway 而不是 Zuul因为它是响应式编程模型性能更好而且和 Spring Cloud 生态集成更自然。网关要做的核心事情有三件路由转发根据路径前缀把请求转发到对应服务。比如/api/user/**转发到 user-service/api/vote/**转发到 vote-service。统一鉴权写一个全局过滤器拦截所有请求校验 JWT token白名单路径直接放行。跨域处理前后端分离项目必须解决跨域。可以在网关层统一配置 CORS不要让每个服务自己配否则一旦漏配某个服务前端就是接连不断的 CORS 报错。下面这个路由配置是经过实际项目验证的spring: cloud: gateway: routes: - id: user-service uri: lb://user-service predicates: - Path/api/user/** filters: - StripPrefix1 - id: vote-service uri: lb://vote-service predicates: - Path/api/vote/** filters: - StripPrefix1 globalcors: cors-configurations: [/**]: allowedOriginPatterns: * allowedMethods: * allowedHeaders: * allowCredentials: true注意allowedOriginPatterns而不是allowedOrigins否则前端带 cookie 跨域请求时会被浏览器拦截。这个问题我当时查了半天最后发现就是这一个配置词的区别。2.3 网关层限流用 RequestRateLimiter 实现双维度限流热词里出现了eurekagateway 的 springcloud 如何限流说明这是很多人关注的点。Gateway 内置了基于 Redis 的RequestRateLimiter过滤器配合KeyResolver可以实现按 IP 限流、按用户限流。限流配置模板filters: - name: RequestRateLimiter args: redis-rate-limiter.replenishRate: 10 redis-rate-limiter.burstCapacity: 20 key-resolver: #{ipKeyResolver}这里的replenishRate是每秒允许填充的令牌数burstCapacity是桶容量。大白话讲每秒最多处理 10 个请求瞬时可以突增到 20 个超过的直接返回 429。KeyResolver 代码Bean public KeyResolver ipKeyResolver() { return exchange - Mono.just( Objects.requireNonNull(exchange.getRequest().getRemoteAddress()).getAddress().getHostAddress() ); }我个人实测下来的经验是网关层限流保护的是后端服务不被冲垮但真正防刷要结合业务层判断。比如一个用户一秒内不能重复投票、同一 IP 一小时最多投 20 票这些规则放到网关做太重放业务服务里更灵活。2.4 OpenFeign 服务间调用与降级策略服务拆开之后服务之间需要通信。比如投票时要校验用户是否是正常状态、要同步更新作品的票数vote-service 需要调用 user-service 和 work-service 的接口。OpenFeign 的用法核心三步服务提供方写标准 REST 接口。服务消费方定义 FeignClient 接口FeignClient(name work-service, fallback WorkClientFallback.class) public interface WorkClient { GetMapping(/api/work/info/{id}) ResultWorkVO getWorkInfo(PathVariable(id) Long id); }主启动类加EnableFeignClients。这里必须提一个坑Fallback 降级类一定要加Component注入 Spring 容器并且application.yml里要把feign.circuitbreaker.enabled打开否则 fallback 写了也白写服务调用失败照样直接抛异常。2.5 应对瞬时高并发的限流补强Sentinel 熔断降级如果你用了 Nacos那配置中心问题解决了一半但流量防护还差一环。SpringCloud 生态里与 Nacos 搭配最丝滑的是 Sentinel——它是面向分布式系统的流量防卫组件能做到限流、熔断、系统保护三位一体。为什么有 Gateway 限流还要用 Sentinel因为 Gateway 限流粒度比较粗按 IP、按路径而 Sentinel 能做接口级 QPS 限制、线程数限制、慢调用比例熔断还能在控制台动态调整规则。比如投票接口如果 QPS 超过 300直接返回友好提示而不是让请求压到数据库这种场景 Sentinel 比 Gateway 的全局过滤器更精准。接入 Sentinel 的配置spring: cloud: sentinel: transport: dashboard: 127.0.0.1:8080然后启动 Sentinel 控制台java -jar sentinel-dashboard.jar在控制台给 vote-service 的投票接口配置 QPS 限流规则即可。控制台里能看到实时的调用链路和流量曲线排查问题非常直观。3. 投票核心业务与防刷设计3.1 数据库设计三张核心表投票系统的数据库表可以有很多但核心就三张用户表user、作品表work、投票记录表vote_record。另外可以加一张作品标签表或者投票活动配置表具体看需求。关键字段设计表核心字段设计说明userid, openid/username, nickname, avatar, rolerole 区分普通用户和管理员workid, user_id, title, description, image_url, statusstatus 控制是否审核通过、是否可投票vote_recordid, work_id, user_id, create_time唯一索引 (work_id, user_id)保证一个用户对一个作品只投一票唯一索引这一条非常关键。不加这个索引代码里就算加了判断并发下照样能插入重复投票记录。数据库的唯一约束是最后一道防线必须要有。3.2 Redis 在投票系统里的三个核心应用投票系统是典型的读多写少 数据热热点场景Redis 在这里面可以承担三个重要任务任务一排行榜缓存排行榜如果用 MySQL 的ORDER BY vote_count DESC LIMIT 10数据量大了之后会越来越慢而且每次刷新都要查一次库。用 Redis 的 ZSet有序集合实现排行榜是最标准的做法# 作品被投一票 ZINCRBY work:rank 1 10001 # 查询前10名 ZREVRANGE work:rank 0 9 WITHSCORESZINCRBY是原子操作天然支持并发加票。排行榜从 Redis 里取响应时间在毫秒级。任务二投票计数的原子累加我在做这个项目时第一版保存票数用的是 MySQL 的UPDATE work SET vote_count vote_count 1 WHERE id ?。这个小项目数据量不大时挺好用的但你想在面试里聊并发扣减这个话题那还是要有点更深入的设计。用 Redis 做计数然后异步同步到数据库// 投票时先操作 Redis Long currentCount redisTemplate.opsForValue().increment(work:vote: workId, 1);increment是原子操作不会出现两个请求同时读到旧值导致丢票的问题。任务三防重复投票结合数据库唯一索引再在 Redis 里做一个短时间的重复标记双保险Boolean firstVote redisTemplate.opsForValue() .setIfAbsent(user:vote: userId : workId, 1, Duration.ofDays(1)); if (Boolean.FALSE.equals(firstVote)) { // 这个用户对这部作品已经投过票 }3.3 防刷策略组合拳单靠拦截器限流并不能完全防住刷票行为。投票系统在实践中会面对几种常见的刷票手段我按实际威胁程度排个序脚本批量投票绕开前端界面直接用 Postman/Apifox 循环调接口。对策是登录后拿到 JWT 才能投票而 JWT 需要账号密码批量注册成本较高。多个代理 IP 切换投票同一用户用拨号代理换 IP 投票。对策是结合用户账号维度限制——一个账号一天只能投几票、一个作品只能投一票。并发打爆接口利用并发请求试图绕过单线程检查。对策是 Redis 原子操作 数据库唯一索引双保险让同时请求在数据库层被约束住。业务层的投票请求处理流程public ResultVoid vote(VoteRequest request) { // 1. 校验用户登录状态 // 2. 校验作品状态是否审核通过、是否在投票期间 Long userId SecurityUtils.getCurrentUserId(); Long workId request.getWorkId(); // 3. Redis 判断是否重复投票 Boolean firstVote redisTemplate.opsForValue() .setIfAbsent(user:vote: userId : workId, 1, Duration.ofDays(1)); if (Boolean.FALSE.equals(firstVote)) { return Result.error(你已经为该作品投过票了); } // 4. Redis 原子累计票数 redisTemplate.opsForValue().increment(work:vote: workId, 1); redisTemplate.opsForZSet().incrementScore(work:rank, workId, 1); // 5. 异步消息落库最终一致 mqTemplate.convertAndSend(vote.topic, new VoteMessage(userId, workId)); return Result.success(); }注意第 5 步投票的核心路径在 Redis 中完成秒回成功落库操作放进消息队列做异步处理。也就是这里存在一个最终一致性的权衡——Redis 是准实时的数据MySQL 是最终落地的数据。这个方案在投票类高并发场景下是合理的因为即使某个时刻 Redis 和 MySQL 存在短暂不一致最终排名以 MySQL 为准做一次对账即可。3.4 排行榜一致性对账方案Redis 和 MySQL 双写的情况下需要一套对账机制保证最终一致性。我的做法是写一个定时任务每小时做一次全量对账扫描 Redis 里所有work:vote:*计数键读取对应的 MySQL 票数字段如果 Redis 值 MySQL 值按差值补一次数据库更新减少数据差异。再配合一个每日凌晨的全量重算任务清空 MySQL 的 vote_count 字段然后从 vote_record 表中COUNT(*)重新统计把准确值写回去并同步刷新 Redis 排行榜。这套方案虽然不能保证实时一致但能保证最终正确而且实现成本很低。4. 前端 Vue3 项目搭建与核心实现4.1 Vite 创建项目与目录结构规划前端我强烈建议用 Vite 而不是 vue-cli 创建 Vue3 项目。Vite 冷启动速度极快热更新也是毫秒级开发体验比 Webpack 时代的 vue-cli 好一个数量级。npm create vitelatest vote-web -- --template vue cd vote-web npm install npm install vue-router4 pinia axios element-plus echarts然后在src下按功能划分目录src/ ├── api/ // 接口请求封装 │ ├── auth.js │ ├── work.js │ └── vote.js ├── assets/ ├── components/ // 公共组件 ├── router/ // 路由配置与守卫 ├── stores/ // Pinia 状态模块 ├── utils/ // 工具函数、axios 封装 ├── views/ // 页面组件 └── App.vue这个结构的好处是按业务切分、各归其位多人协作时几乎不会出现冲突。4.2 axios 封装与 Token 刷新联动前后端分离项目里axios 封装是最基础的一层。接口请求前读 token 放进请求头响应码是 401 时自动跳转登录页这些大家都懂。我这里提两个容易漏的细节第一个是请求拦截器里不要只加 token还要在遇到上传文件接口时设置Content-Type为multipart/form-data否则文件上传会因为默认的 JSON 头报错。第二个是401 响应的统一处理要避免死循环。如果你的代码里写的是401 就跳转登录页那需要判断当前请求的 URL 是不是登录接口本身否则会陷入登录请求失败 401 → 跳登录页 → 页面刷新 → 再发登录请求的循环。axios.interceptors.response.use( response response, error { if (error.response?.status 401) { const isLoginRequest error.config.url.includes(/auth/login); if (!isLoginRequest) { router.push(/login); } } return Promise.reject(error); } );4.3 Pinia 状态管理用户信息和投票状态Vue3 官方推荐状态管理库从 Vuex 换成了 Pinia。Pinia 的 API 更简洁、对 TypeScript 支持更好、没有了 mutations 的概念直接改 state 就行。项目中我会用两个 store// stores/user.js export const useUserStore defineStore(user, { state: () ({ token: , userInfo: {} }), actions: { setToken(token) { this.token token; }, logout() { this.token ; this.userInfo {}; } } }); // stores/vote.js export const useVoteStore defineStore(vote, { state: () ({ votedMap: new Map(), rankList: [] }), actions: { async fetchRankList() { const res await getRankListApi(); this.rankList res.data; } } });这里有个小技巧votedMap用Map结构存储当前用户给哪些作品投过票进入作品详情页时直接查本地 Map不用每次都请求后端体验非常流畅。4.4 组件通信从父子到跨组件Vue3 开发中组件通信是最高频的场景热词里也专门提到了父子组件 非父子组件 交互。我按实际使用频率总结一下场景方案说明父传子props父组件通过属性传值子组件用defineProps声明子传父emit子组件用defineEmits触发事件父组件监听跨层级、非父子provide/inject适合祖先给后代传值不需要逐层传递全局状态Pinia登录状态、用户信息、排行榜数据等全局共享数据兄弟组件Pinia 或 mitt简单场景用 Pinia调试更直观一个典型的父子组件示例!-- Parent.vue -- script setup import { ref } from vue; import VoteCard from ./VoteCard.vue; const workList ref([]); function handleVoteSuccess(workId) { // 更新本地状态刷新排行榜 refreshRankList(); } /script template VoteCard v-forwork in workList :keywork.id :workwork vote-successhandleVoteSuccess / /template!-- Child.vue -- script setup const props defineProps({ work: { type: Object, required: true } }); const emit defineEmits([vote-success]); function vote() { voteApi(props.work.id).then(() { emit(vote-success, props.work.id); }); } /script4.5 投票页面前端交互设计前端投票页面的核心交互有三块作品卡片展示作品图、标题、作者、当前票数点击投票按钮触发投票。排行榜实时显示 TOP10票数变化时用 ECharts 的横向柱状图动画呈现视觉效果很好。投票弹窗投票前弹窗确认防止误触。前端在调投票接口前可以先做一次本地校验async function handleVote(workId) { // 本地先判断是否已登录 if (!userStore.token) { ElMessage.warning(请先登录); router.push(/login); return; } // 本地判断是否已投过 if (voteStore.votedMap.has(workId)) { ElMessage.warning(你已经为该作品投过票了); return; } await voteApi(workId); // 成功后更新本地状态和使用体验 voteStore.votedMap.set(workId, true); const work workList.find(item item.id workId); if (work) work.voteCount; ElMessage.success(投票成功); }4.6 Vue3 性能优化diff 算法与虚拟列表热词里提到一个 Vue3 的经典面试题Vue3 diff 相对 Vue2 的优化点。这个知识点不只是面试要背项目里也真的用得上。Vue2 的 diff 是全量对比数据变化时虚拟 DOM 从头到尾比较新旧 vnode 树即使只有一个节点变了也会遍历整个树做对比。Vue3 做了三个关键优化静态提升模板中不变的静态节点在编译阶段就提取出来不在每次渲染时重新创建patchFlag编译时标记节点中哪些部分是动态的属性、文本、事件等diff 时只比对有标记的部分最长递增子序列处理多节点移动时用算法求出最少的节点移动次数避免不必要的 DOM 操作。在作品列表页面如果作品数量比较大比如几百上千条虚拟滚动才是终极方案。社区推荐vue-virtual-scroller它只渲染可视区域内的 DOM 节点列表再长页面也不会卡。5. 前后端联调与部署落地5.1 本地联调环境配置前后端联调最让人头疼的就是地址配置问题。前端开发时访问http://localhost:9527Vite 默认 5173后端接口在http://localhost:8080网关如果前端代码里直接把接口地址写死换环境就得改代码重新打包。正确做法是在 Vite 的vite.config.js里配置代理export default defineConfig({ server: { proxy: { /api: { target: http://localhost:8080, changeOrigin: true } } } })这样前端代码里请求/api/vote/list开发环境下会自动转发到网关。生产环境打包后再用 Nginx 做同样的反向代理前端代码不用改一行。5.2 Docker Compose 一键部署方案实训或毕设答辩时如果你说我这个项目能 Docker 一键部署老师或面试官的好感度会明显提升。这个项目的容器化方案不复杂核心就四个容器version: 3 services: mysql: image: mysql:8.0 environment: MYSQL_ROOT_PASSWORD: root123 MYSQL_DATABASE: vote_db ports: [3306:3306] redis: image: redis:7 ports: [6379:6379] nacos: image: nacos/nacos-server:v2.2.3 environment: MODE: standalone ports: [8848:8848, 9848:9848] gateway: build: ./gateway ports: [8080:8080] depends_on: [nacos, mysql, redis]服务多起来以后再追加 user-service、work-service、vote-service 的容器即可。注意一个坑容器之间通信要用服务名而不是 localhostapplication.yml里的 Nacos 地址就要写成nacos:8848。5.3 前后端构建产物发布前端构建命令是npm run build产物在dist目录。后端每个服务用 Maven 打包成 jar通过 Dockerfile 打成镜像。前端 Nginx 配置里需要做两件事一是把/路径指向dist目录二是把/api路径反向代理到网关地址。第一个是静态资源托管第二个是接口转发两者不冲突。6. 常见问题排查表与避坑技巧6.1 排查表这个项目跑起来之后我整理了一些高频问题按症状、原因和解决方案列表方便你对照自查症状可能原因解决方案Nacos 控制台看不到服务bootstrap.yml 没配置或 Nacos 地址不对确认 spring.application.name 与配置文件一致在 Nacos 服务列表页面点刷新前端请求 404网关路由没配对或者路径前缀没剥干净检查 StripPrefix 配置确认后端接口实际路径跨域报错 CORS网关 globalcors 没配置或配置项写错确认 allowedOriginPatterns 和 allowCredentials 同时配置投票后票数没增加Redis 计数成功但异步落库失败检查 MQ 消费者是否正常比较 MySQL 与 Redis 中的票数差值Feign 调用报连接超时服务的端口或服务名写错Feign 默认超时时间太短确认 target 服务已启动调大 ribbon.ReadTimeout 和 ConnectTimeout数据库报唯一键冲突并发投票同一作品同一用户属于预期行为在代码中捕获 DuplicateKeyException 并给用户友好提示前端热更新失效Vite 的缓存问题或被代理转发影响清空 node_modules/.vite 缓存重启 dev server6.2 我踩过的三个比较有代表性的坑第一个坑是Spring Cloud 版本不匹配。这个项目我最早用 Spring Boot 3.0 Spring Cloud 2022.0.0结果 OpenFeign 的依赖名变了Gateway 的 CORS 配置也失效了排查了很久才发现是版本兼容问题。如果你不是特别需要 Spring Boot 3 的新特性建议直接上 Boot 2.7 Cloud 2021.0.x网上资料多坑基本都被踩平了。第二个坑是前端组件缓存导致的假数据。作品审核通过后前端列表页不刷新还是显示旧状态原因是router-view被 keep-alive 缓存了。解决办法是在路由离开前调用clearCache()或者给转场组件添加动态 key强制重新渲染。第三个坑是异步落库后 Redis 数据在某个节点丢失。我用 Redis 做计数器时没有做持久化配置结果服务重启后 Redis 数据清了票数一下子回到 MySQL 的旧值。解决方案是修改redis.conf开启 AOF 持久化并做好定时对账任务。生产环境里 Redis 绝对不能裸奔不配持久化否则一个重启就是事故。6.3 面试官视角把项目经验变成得分点因为热词里频繁出现 springcloud 面试题和 vue3 面试题我顺带从面试官视角补充一些回答思路。这部分算是项目之外的增值内容。面试官问你在这个项目里遇到过什么难点最忌讳的回答是没有遇到过或者感觉都挺简单的。正确的讲法是选一个真实问题讲清楚结论、分析过程、排查路径、最终方案。比如投票接口上线后出现并发丢票问题。我先排查了日志发现两个请求同时读取到旧票数然后各加一导致实际投了两票但只加一票。我查了代码发现原来的 update 语句是读到实体再 set 后 update存在时间窗口。后来我改成了 MySQL 的原子自增 SQL再叠加 Redis 的 increment 做前置计数同时加了数据库唯一索引兜底最终解决了这个问题。面试官问为什么选 Nacos 不选 Eureka你要能答出Eureka 停更、Nacos 一套组件同时解决注册与配置、Nacos 支持服务优雅上下线和权重路由、国产社区活跃度高。这些问题每一条都要能展开讲几分钟。Vue3 相关的面试题也能从项目里延伸。比如被问到Vue3 diff 算法优化你可以结合自己项目作品列表长列表渲染的经验来答哪些节点是静态提升的、为什么动态绑定要加 key、虚拟滚动为什么会提升性能。把面试题揉进项目经验里说服力远大于干背八股文。7. 项目扩展方向建议7.1 增加管理员端与数据统计基本投票流程做完之后把管理员端补上作品审核、用户管理、投票数据导出。统计模块用 ECharts 做可视化大屏——每日投票趋势折线图、作品类型分布饼图、排行榜横向柱状图。这一块既好做又出效果演示的时候非常加分。7.2 引入消息队列做削峰填谷投票活动开闸瞬间如果请求量太大可以把投票请求先写进 RabbitMQ/RocketMQ后端服务按自己消费速率慢慢处理。虽然这个项目体量用不到但能在文档或者面试中讲清楚方案本身就是加分项。7.3 从 SSE 角度实现实时排行榜推送热词里出现了 sse 流式输出 vue3 和 sse springboot vue3 通用进度条说明这两个关键词关注度很高。如果投票系统要做实时榜单刷新长轮询或 SSE 是成本最低的方案。后端生成一个SseEmitter当票数变化时推送给前端前端用EventSource接收后更新榜单数字效果比轮询好得多。这个属于进阶扩展感兴趣的话可以单独写一篇博客展开讲。我在实际做这些扩展时最大的体会是项目本身的价值不在于技术栈多高级而在于每个技术选型都能讲出实际理由。一个投票系统因为可能有流量峰值所以用 SpringCloud 做服务拆分因为要防刷所以用 Redis 原子计数因为榜单要实时所以设计了对账方案——这些为什么才是真正的项目质感。如果你正在做这个项目先把本篇第一到第四部分的核心链路搭通跑通一个完整的用户登录 → 浏览作品 → 投票 → 排行榜更新流程再逐步叠加高级特性。没有跑通的流程什么高级设计都是空中楼阁流程通了后面的每个点都可以慢慢打磨。祝顺利。
返回列表