ARTICLE DETAIL

资讯详情

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

高校招生平台微服务架构设计与高并发实践:Spring Cloud落地全解析

高校招生平台微服务架构设计与高并发实践:Spring Cloud落地全解析 每年6月底到8月初是这个系统最紧张的时段。高考出分、志愿填报、录取结果查询这几个节点挤在一起流量曲线就像脉冲信号——平时系统日活只有两三千放榜和填报窗口那几天瞬时并发能冲到每秒几千甚至上万次请求。而且招生数据牵涉到考生的前途错一条就意味着事故容不得半点含糊。我手上这套高校招生服务平台就是在这样的业务背景下从单体应用一步步重构而来的。这套系统整体技术栈是 SpringBoot Spring Cloud 微服务架构前端分两条线管理端用 Vue 实现面向招生办老师的后台操作C端用微信小程序面向考生和家长完成报名、志愿填报、缴费、录取查询等自助操作。整篇文章我会把微服务拆分思路、Spring Cloud 各组件的实际配置逻辑、双端前端的工程化处理、核心链路里的并发与事务方案以及上线后遇到的真实问题完整梳理一遍。对准备做类似微服务项目的同学或者正在把单体系统往 Spring Cloud 方向迁移的团队应该会有比较直接的参考价值。1. 招生季的流量脉冲为什么高校招生系统必须走微服务架构1.1 招生系统和普通管理系统的本质区别先看业务特点。高校招生平台不是典型的后台管理系统它具备几个非常鲜明的特征高并发窗口期集中体量再大的电商系统大促也是人为设计的活动周期可以提前做预案招生平台的流量高峰是刚性的出分时间点一公布考生查分、报名的请求近乎同时涌入没法通过运营手段错峰。数据准确性要求极高考生个人信息、成绩、志愿顺序、录取状态任何一条数据错位都会引发严重纠纷。平时开发可以妥协的性能点在这里不能妥协。涉及资金链路艺术类、体育类校考报名通常需要缴费这就让系统不只是信息管理而是带支付能力的业务系统对事务一致性有硬性要求。长链路、多角色考生、家长、招办老师、院系审核员、财务、系统管理员不同角色的操作权限和流程节点差异很大。在这个前提下最早的单体版本就撑不住了。一个 Spring Boot 单体应用里塞了权限、考生、院校、志愿、缴费、资讯、客服等十几个模块出分那天 Tomcat 线程池被打满数据库连接池耗尽整个系统像多米诺骨牌一样全部不可用——某个模块的一个慢查询能把所有人都拖下水。更尴尬的是单体应用无法针对查询热点做局部扩容流量一上来只能整体升配成本高且无效。这就是我们最终引入 Spring Cloud 微服务架构的直接原因。拆分后不同的业务模块可以独立部署、独立扩缩容报名高峰期只扩容报名服务和支付服务资讯服务保持两副本即可某一服务故障也不会拖垮整个平台。1.2 技术栈全景每一层选型解决什么问题这套系统的整体架构分层如下表每一层都是针对招生场景的特定诉求去选的不是单纯追新。层次技术选型解决的招生场景问题接入层微信小程序 C端 Vue 管理端C端覆盖考生高频操作管理端覆盖招生办全流程审核网关层Spring Cloud Gateway统一鉴权、接口限流、跨域处理、灰度路由注册配置中心Nacos服务注册发现、配置动态刷新招生季配置调整不用重启微服务框架Spring Boot Spring Cloud业务模块独立开发、独立部署、独立扩容服务调用OpenFeign Sentinel服务间声明式调用超时与熔断保护防止雪崩数据层MySQL RedisMySQL存储核心业务数据Redis扛热点与分布式锁异步与削峰RabbitMQ报名通知、录取通知异步投递削峰填谷部署运维Docker K8s各微服务容器化编排按招生季流量水平扩缩容为什么 C 端选微信小程序而不是 App 或 H5核心原因很简单招生季考生需要快速触达小程序免安装、微信内直接打开、转发分享方便而且家长群体对微信的使用熟练度远高于安装一个陌生 App。管理端选 Vue 则是看中它的生态成熟度配合 Element UI 这类组件库完全可以快速搭建出表格密集、表单复杂的后台页面。2. 服务边界怎么切高校招生平台的九个核心微服务2.1 按业务能力拆分的服务清单微服务拆分最需要克制一旦边界划错后面改起来成本极高。我们的做法是严格按业务能力和数据域去划而不是按页面去划。最终拆出了 9 个核心服务服务名称核心职责关键数据gateway-server统一网关路由、鉴权、限流路由规则、限流规则auth-server账号认证、Token签发、角色权限用户账号、角色、菜单权限user-server考生档案管理、招办老师信息管理考生基础档案、户籍、照片school-server院校信息、专业信息、招生计划院校库、专业库、招生计划数enroll-server报名、志愿填报、资格审核报名表、志愿表、审核状态payment-server报名费缴费、退费、对账订单、支付流水、退款记录result-server成绩同步、录取状态管理成绩数据、录取结果、通知书信息article-server招生政策、公告、常见问题资讯内容、政策文件、FAQmessage-server站内信、微信模板消息通知消息记录、模板发送日志每个服务都能讲清楚它维护什么数据的生命周期这是拆分时最关键的判据。比如缴费这个环节最初我们想放进 enroll-server 里做成一个报名时顺便缴费的接口但后来发现支付涉及回调、对账、退款和报名数据的变更频率完全不同硬凑在一起只会让一个服务的状态机变得极其复杂。拆成独立 payment-server 后支付状态变化可以通过消息通知 enroll-server各自维护自己的状态机边界非常清晰。2.2 数据拆分与服务间禁止直连数据库的铁律服务拆了数据库也必须跟着拆否则一切白费。我们从单库拆成 9 个业务库每个服务独占自己的库服务之间禁止直接访问对方的表。一开始团队里有人想省事在 user-server 里直接写 SQL 关联 school-server 的表被我压下来了。一旦放开这种捷径服务间的数据耦合会以极快的速度蔓延最终数据域名存实亡。那么不同服务的数据怎么关联两个标准姿势**方式一服务间 OpenFeign 调用获取。**比如 enroll-server 需要展示考生姓名就通过 Feign 调 user-server 的接口按 userId 批量查询考生概要信息拿到后组装到自己的响应里。这种方式适合当前服务需要展示对端数据的场景。**方式二冗余关键字段 消息异步同步。**比如支付成功后payment-server 发一条 MQ 消息enroll-server 自己消费后在本地报名表冗余一份支付状态和支付流水号。这样查询报名状态时完全不用跨服务调用性能更好。冗余字段这种方案很多人不敢用怕数据不一致。但在分布式系统里跨服务的数据本来就做不到强一致用[CQRS 模式]的思维去理解就顺了写入走服务间调用查询走本地数据。只要幂等和补偿机制做扎实最终一致性完全可接受。2.3 强一致与最终一致的正确划分这个问题直接决定系统的事务复杂度。我们一开始犯过错误试图让所有操作都达到强一致结果动不动就锁表、超时。后来用一张表格把所有核心场景的一致需求理清楚才发现大部分场景其实只需要最终一致业务场景一致性要求实现方案考生注册强一致单库本地事务志愿填报保存强一致单库本地事务正式提交志愿最终一致事务消息 状态机多个服务间异步协调报名缴费强一致支付服务本地事务 支付渠道回调幂等处理录取状态流转最终一致人工审核后异步通知 补偿对账考生档案修改强一致校验后单库更新凡是能做成单库本地事务的绝不上分布式事务。真正需要跨服务参与的才引入消息和状态机来推进。这一点是微服务实战里最重要的架构原则之一。3. Spring Cloud 各组件在招生场景里的落地细节3.1 注册中心和配置中心Nacos 的具体用法我们用的注册中心和配置中心都是 Nacos。为什么没选 Eureka很简单Eureka 2.0 早已停止维护而且它只是注册中心配置中心还需要另搭 Spring Cloud Config。Nacos 一个组件把注册中心和配置中心都干了还内置控制台可以直观看到各服务的健康状态和配置变更历史运维成本低很多。注册中心本身没什么悬念每个服务引入依赖后配置spring.cloud.nacos.discovery.server-addr即可。真正要留意的是 Nacos 作为配置中心时的动态刷新机制。招生季免不了频繁调整限流阈值、开关某些院校的报名入口这些配置如果放在本地application.yml里每次改完都要发版重启这在运营眼里是不可接受的。我们的做法是把可变配置抽到 Nacos 配置中心服务里用RefreshScope标注需要动态刷新的 Bean。比如报名入口的开关配置enroll: switch: true max-quota: 5000 # true 表示开启报名入口max-quota 限制单个院校批次的最大报名人数然后在服务中通过ConfigurationProperties注入加上RefreshScopeRefreshScope ConfigurationProperties(prefix enroll) Component public class EnrollSwitchConfig { private boolean switch; private int maxQuota; // getter / setter 省略 }改配置后 Nacos 推送服务毫秒级刷新重启动作完全省掉。这里有项目里踩过的坑RefreshScope只能作用在它修饰的 Bean 及其直接依赖上如果这个配置对象被 Service 层注入但 Service 本身没有标注RefreshScope动态刷新就不生效得手动重启。所以配置直接注入的 Bean 和调用链上的 Bean 都要加上RefreshScope否则刷新不到预期效果。3.2 Gateway 网关鉴权限流路由的统一收口Spring Cloud Gateway 是整套系统的流量入口所有来自小程序端和 Vue 管理端的请求先到网关再由网关路由到具体的微服务。网关层主要干三件事。第一件事是鉴权拦截。登录用户会拿到 JWT Token后续请求在 Header 里带上。网关里加一个全局过滤器对需要登录的路径做 Token 校验和角色校验。这里要注意网关不应该承载太重的业务逻辑只需要解析 JWT 的身份声明比如 userId、role然后放到请求头里转发给下游服务下游服务直接信任网关传过来的身份即可。第二件事是路由转发。我们按服务名做前缀路由例如/api/enroll/**路由到 enroll-server/api/payment/**路由到 payment-server。配置大概长这样spring: cloud: gateway: routes: - id: enroll-route uri: lb://enroll-server predicates: - Path/api/enroll/** filters: - StripPrefix1 - id: payment-route uri: lb://payment-server predicates: - Path/api/payment/** filters: - StripPrefix1lb://前缀是告诉网关走负载均衡从 Nacos 注册中心按服务名找到实例列表。如果多个实例在线默认用负载均衡策略分发这样报名高峰时只需把 enroll-server 扩容到 8 个实例网关自动在它们之间做流量分发。第三件事是接口限流。招生季的报名接口是我们重点保护的对象利用网关的 RequestRateLimiter 过滤器工厂结合 Redis 实现按 IP 和按用户维度的限流。限流阈值根据压测结果设置宁可挡掉一部分普通请求也不能让瞬间流量打垮整个系统。3.3 OpenFeign 调用与 Sentinel 熔断降级配合服务之间的同步调用我们用 OpenFeign。它的好处是写起来像本地接口调用代码直观。但 Feign 默认超时配置非常激进如果没有显式设置进程级默认是 60 秒这在流量高峰期无异于慢性自杀——一个下游服务卡顿上游线程池全部占住等待很快连锁耗尽。我们的 OpenFeign 配置一定要配合 Sentinel 一起用。Sentinel 做的是熔断降级当下游服务接口的异常比例或响应时间超过阈值服务端直接熔断不再发起真实调用而是快速返回降级结果。比如调用 school-server 查询院校列表超时降级逻辑返回一个空列表加提示院校数据加载失败请稍后重试保证页面框架能出来不让整个请求卡死。同时给 Feign 设置了较短的超时时间feign: client: config: default: connectTimeout: 2000 readTimeout: 3000这是经验值。连接超时 2 秒、读超时 3 秒再配合 Sentinel 熔断才能保证招生季高峰期单个接口的响应不会无限拖下去。要注意的是熔断降级返回的内容要设计好不能直接把系统错误这样的裸信息抛给前端降级逻辑里要把业务语义保留住比如当前报名人数较多请稍后重试对考生更友好也更安全。4. 双端并行的前端工程Vue 管理端与微信小程序 C 端4.1 管理端和小程序端各自承担的职责边界后端拆成微服务前端自然也要拆但拆法不是按服务拆页面而是按用户角色拆应用。管理员和考生操作的界面、终端形态、交互复杂度完全不同拆成两个独立的前端工程正好符合微服务的独立交付、独立演进思想。Vue 管理端运行在 PC 浏览器上服务的是招生办老师、院系审核员、财务人员。这类用户的工作场景是大量列表、表单、审核操作所以我们用 Vue 3 Vite Element Plus 搭建重点做好表格筛选、分页、批量操作、审核流转进度的展示。管理端还有一个核心功能是数据看板按院校、按批次、按日期展示报名人数、缴费率、录取进度这部分用 ECharts 做图表实时从各微服务聚合数据。微信小程序端服务的是考生和家长操作的场景更碎片化、移动化。小程序端提供的主要页面有首页招生公告流 院校推荐位报名页按院校和专业维度报名支持选择批次志愿填报页按志愿顺序填报多个院校专业缴费页报名费在线缴纳对接微信支付录取查询页录取状态实时查询小程序端的 UI 我们用的是原生框架 WeUI 组件没有引入 uni-app 这层封装。原因在于这个项目的小程序端交互相对可控原生框架的调试和性能调优路径最直接出问题时好排查。如果你们后续有 App 或 H5 多端需求再上 uni-app 这类跨端框架也不迟前期不要为了统一而过度设计。4.2 跨端身份认证与角色权限模型双前端共享后端的那套用户体系身份认证必须统一设计。核心方案是登录认证统一走 auth-server 签发的 JWT小程序端和管理端持有 Token 的获取方式不同但校验逻辑完全一致。管理端登录方式是账号密码 验证码登录成功后拿到 Token 和用户角色信息前端用 Vue Router 的动态路由做权限控制——不同角色的用户初始化时得到的路由表不一样比如财务角色看不到审核页面。这是 Vue 权限控制的经典做法但要注意一个细节动态路由在刷新页面后会丢失必须在路由守卫里加一个router.addRoute的恢复逻辑把已登录用户的异步路由表重新挂上去否则一刷新页面就白屏。小程序端的登录则走微信授权体系通过wx.login获取临时 code后端用 code 换取 openid并绑定考生档案。后续请求一律携带后端签发的自定义 Token。这里有一个被很多人忽略的点不要在小程序端直接使用微信的 openid 作为业务主键。openid 是微信体系内的标识支付、消息推送会用到但业务库里的考生 ID 必须是自己生成的业务主键。两者做好映射后续如果系统对接其他登录渠道比如政务平台统一登录不会被动。小程序端还有个常见问题就是页面列表加载更多的下拉分页。上面热搜词也提到了这个。小程序不像 PC 浏览器用户滑动列表时频繁触发setData会导致页面渲染卡顿我们的做法是每次加载下一页时先把新数据concat到本地数组一次性setData而不是每条数据单独更新同时列表项使用wx:key保证 diff 更新效率。实测下来一次setData推送 20 条数据页面帧率稳定很多。4.3 跨端复用的通用逻辑Axios 层、请求封装与接口约定虽然是两个前端工程但很多基建逻辑是可以抽象出同样设计模式的。最典型的就是 HTTP 请求封装。管理端 Axios 封装了拦截器请求带上 Token响应统一处理业务码、弹出错误提示。小程序端也做了类似封装但多了几个小程序特有的处理登录态过期时拦截响应后跳转到登录页请求携带content-type: application/json上传图片场景单独封装wx.uploadFile的处理接口约定方面统一返回体我们设计为{ code: 0, message: success, data: {} }code0表示成功非 0 表示业务错误message给前端提示用。这里有个教训接口返回结构必须全局统一不能有的接口返回数组、有的返回对象包裹前端会疯掉。数据为空时也要返回data: []或data: {}宁可统一占位也不要动不动返回null。5. 核心业务链路实战从考生注册到录取确认的完整流程5.1 主流程的关键状态机招生平台的主流程不是单线性的它是一组状态机的流转。这里挑核心链路说明报名 → 缴费 → 志愿填报 → 资格审核 → 录取 → 确认。以 enroll-server 中的报名单为例状态字段设计如下状态码含义触发动作0草稿考生填写但未提交1已提交待缴费提交报名、生成订单2已缴费待审核支付回调成功3审核通过招办老师人工审核4审核不通过驳回并通知考生5已录取录取结果确定6已确认入学考生在小程序确认状态机设计最重要的原则是状态流转必须单向且由事件驱动。不允许直接修改状态字段统一走状态流转服务方法方法内部校验当前状态是否允许迁移到目标状态比如草稿状态不能直接跳到已录取。5.2 分布式事务报名、缴费、名额扣减的最终一致方案这块是整个系统里最容易出事故的地方。简单的报名提交可以做成单体事务但如果报名同时涉及名额预扣 订单生成 支付回调后的最终确认数据分布在 enroll-server 和 payment-server 两个服务中就属于跨服务事务了。我们最终的方案是事务消息 本地消息表这里展开说。考生点击报名后enroll-server 在本地事务里完成两步操作往报名表插入报名记录同时往本地消息表插入一条待发送订单创建消息记录消息状态为 pending。本地事务提交成功后通过 RabbitMQ 发布消息给 payment-server。payment-server 收到消息后创建订单并回调 enroll-server。enroll-server 消费回调结果更新报名状态为已缴费待审核并把本地消息表状态更新为已完成。如果消息发送成功但 payment-server 处理失败怎么办我们起了一个定时任务每隔一段时间扫描本地消息表中状态仍为 pending 的过期消息重新发送。重点在于下游消费必须做幂等——payment-server 收到重复消息时通过订单号唯一索引判断是否已处理过已处理则直接返回成功不再重复创建订单。这里绕开了 Seata 这类分布式事务框架理由是用全局锁去保证跨服务强一致性在高并发招生场景下代价太大而且会显著拉长事务链路、降低吞吐。招生业务对最终一致容忍度较高因为每一步都有状态界面反馈考生看到报名成功待缴费完全符合预期。真正不能接受的反而是因为强一致锁导致的长时间卡死和超时。5.3 名额抢注并发控制Redis 分布式锁的正确姿势招生季最经典的并发场景是抢热门院校的名额。某美术院校计划招 200 人报名窗口一开头几分钟就可能涌进几千个请求。如果直接用 SQL 判断名额并扣减行锁竞争会导致数据库 CPU 直线飙升。我们采取的是Redis 预扣 事务内校验双层策略在 Redis 里对每个院校批次设置一个名额计数器用INCR或DECR原子操作预扣名额。DECR返回的值如果小于 0说明名额已满直接返回该批次已报满不进入数据库操作。预扣成功后才能进入 enroll-server 生成报名记录。数据库事务里再做一次实际名额校验用SELECT quota FROM ... WHERE school_id ? FOR UPDATE保证最终一致性。第一步挡住了绝大多数无效请求数据库压力大幅下降第二步确保即使 Redis 和数据库数据有偏差也不会出现超卖。这里要注意分布式锁的一个关键前提Redis 预扣不是锁它是限流阀门真正的下单操作仍然需要数据库兜底校验。在这套流程里我们没用 Redisson 的分布式锁做加锁排队而是选择了原子计数预扣因为加锁排队在小程序端的用户体验非常差——考生看到几秒的排队中就很容易怀疑系统出错了。原子计数可以在毫秒级直接给出报满或报名成功的明确反馈。6. 招生系统12306化高并发防护与性能调优实践6.1 数据热点分析与多级缓存设计招生系统的高并发场景有鲜明的热点特征查分、查录取结果时成千上万用户的请求都集中到同一个接口查的数据往往是同一批数据。比如录取结果公布前后的那几天所有人查的都是同一个批次的录取名单。我们做了多级缓存来消化这类热点第一级本地缓存Caffeine部署在每台服务实例中缓存录取结果接口的高频查询数据。设置短过期时间比如 120 秒。本地缓存不存在网络开销性能极好适合扛超高并发。第二级Redis 缓存本地缓存未命中时查 Redis。这里缓存的是院校详情、招生计划、公告资讯这类变化频率低的数据。录取结果查询我们做了更细的缓存设计热门院校的录取结果粒度到院校批次维度冷门院校则按考生维度缓存避免无关数据互相干扰。第三级数据库只有前两级都未命中才查询 MySQL。数据库连接池配置了 HikariCP闲置超时和最大连接数根据压测结果调整同时所有核心查询都走索引把慢 SQL 排查做在事前。缓存有一个必须注意的坑缓存雪崩与击穿。录取结果刚公布那一刻如果某一批缓存同时过期所有请求会同时打穿到数据库瞬间把数据库压垮。我们的做法是把缓存过期时间人为打散加随机偏移量比如 120 秒到 180 秒随机同时热点 key 使用逻辑过期而不是物理过期防止大面积同失效。6.2 RabbitMQ 削峰把同步阻塞请求变成异步投递报名高峰期的流量不能全部同步扛下来必须削峰。我们的削峰策略主要用 RabbitMQ 做了三件事第一录取结果通知异步化。录取结果确定后result-server 只是更新状态并发送消息消息内容包括考生 ID 和录取结果。message-server 监听消息再通过微信模板消息推送给考生。这个链路完全异步流程本身的耗时不会影响接口响应。第二报名高峰的排队化。最极端的那几分钟gateway 限流会挡掉一部分请求但直接拒绝用户不友好。我们做了个简化版本的消息队列排队被限流的请求返回一个排队中标记前端轮询询问后台队列状态enroll-server 消费队列消息把报名请求落库落库后前端轮询到成功结果。这种方式相当于把瞬时压力摊到几秒到十几秒内处理用户感知到的只是稍等了一下。第三站点内信批量写。招生公告发布后几十万考生需要看到站内信通知逐个同步写数据库会很慢。消息队列广播通知 各服务自行消费按需存储大大降低了写压力。6.3 压测结果与性能基线数据上线前我们对核心接口做了压测压测工具用的 JMeter模拟场景是模拟 5000 并发用户同时查询录取结果 1000 并发用户同时提交报名。结果如下接口压测并发平均响应时间99 分位响应时间错误率录取结果查询5000280ms980ms0.02%院校列表查询3000150ms620ms0.00%报名提交1000780ms2100ms0.08%缴费回调处理800540ms1600ms0.00%数据说明这套架构在招生季的核心节点上能扛住压力。但压测只是起点真正重要的还是那些边界设计后面第 7 节我会专门讲上线后暴露出来的问题。7. 上线后的真实踩坑记录与复盘7.1 慢 SQL 引发的连环超时一次索引缺失事故上线后第一次模拟真实压力就出事了。录取结果查询接口在 5000 并发下平均响应时间飙到 8 秒接着网关开始大量超时Sentinel 熔断触发一堆服务直接降级。查到最后问题出在一张联合索引设计有误的表上。查询条件是WHERE school_id ? AND batch_id ? AND candidate_id ?但索引只建了school_id单列。百万数据量下按 school_id 过滤已经能筛掉很多数据但 batch_id 和 candidate_id 的过滤只能在内存里做 filesort大量回表查询把磁盘 IO 打满。修复方式是调整联合索引顺序为(school_id, batch_id, candidate_id)查询立刻降到了百毫秒内。这个案例给了我们一个教训微服务架构下不要迷信服务拆分能解决所有性能问题数据库是共享底座索引设计不到位再多的服务副本也扛不住。7.2 管理端动态路由刷新白屏这是 Vue 管理端一个十分典型的坑。我们按角色动态路由实现了权限控制但上线后发现一个诡异的现象F5 刷新页面后部分账号会白屏控制台报路由不存在。原因前面提到过——动态路由是在登录后通过router.addRoute添加的但页面刷新时整个应用重新初始化路由表还没来得及恢复页面就已经开始渲染了。解决方案是在路由守卫里加异步判断用户处于登录态时先去检查用户信息里的路由权限是否已经加载过如果没加载过先动态注册路由再放行。这个逻辑必须在每次导航开始前执行确保刷新后能恢复完整的路由表。7.3 配置中心改一处、全链路雪崩还有一次印象特别深的故障。招生季开始前我们在 Nacos 配置中心调整了 RabbitMQ 的消费者线程数配置从默认的 10 调到了 50。本意是提升消息消费速度结果消息服务在高峰期瞬间拉取大量消息同时去调微信模板消息接口被微信侧限流导致消息积压反而更严重还连带把数据库连接池打满。复盘后发现我们忽略了配置变更的级联影响。改一个线程数影响的不只是消息服务自身还会影响下游依赖的第三方接口。现在我们的原则是任何配置变更都必须走完整的变更流程先在测试环境压测验证再灰度发布到生产。招生系统不像普通业务系统一次低级配置失误就能造成大范围的考生服务异常。7.4 微信小程序端的实际调试技巧最后分享几个小程序端真实开发中的调试经验。第一个是抓包。小程序线上环境出问题时很多请求头和加密参数在开发者工具里看不到全貌。用 Charles 配合代理可以完整抓到小程序的 HTTPS 请求前提是手机和小程序端要信任 Charles 的证书。调试时重点关注请求的 Header 是否带上了自定义 Token很多线上 401 都是这里出的问题。第二个是顶部导航栏高度适配。小程序里的自定义导航栏高度在不同机型上差异很大。iPhone 的刘海屏、安卓的全面屏、胶囊按钮位置都不一致我们不再写死高度而是通过wx.getMenuButtonBoundingClientRect()动态获取胶囊按钮的位置再计算导航栏高度。这个值在onLaunch时获取一次存到全局变量里所有页面统一使用。第三个是分页列表的性能。小程序里长列表不能一次渲染太多数据页面上只保留当前可视区域及上下各一屏的数据。数据更新用setData时尽量用路径更新比如this.setData({ [list[ index ].status]: 1 })避免整个数组重新 diff。看似不起眼的小优化在世界之窗那种几百条招生公告的长页面上帧率差别非常明显。8. 关于这套系统的一点个人体会高校招生服务平台这种项目真正的难点从来不是某个技术点学不会而是要把业务高峰期的稳定可用和数据生命周期的严谨性同时做到位。微服务拆分给了我们按业务域灵活扩容的能力但每个服务也因此多了一层网络边界分布式事务、幂等、补偿这些概念就从书本知识变成了必须天天面对的工程现实。如果让我重新从零做一遍我会在项目启动的头两周先把状态机设计和缓存隔离策略画清楚而不是先急着搭框架。状态机没理清后面所有接口的设计都会跟着摇摆缓存隔离没做好一次热点穿透就可能把数据库打爆。很多毕业设计或者内部项目做着做着就成了大泥球根本不是技术栈选错了而是边界和状态从一开始就没守住。这套系统的源码和部署方案目前托管在团队内部的 GitLab 上如果你们也在做高校招生、考试报名、资格审核这类业务可以对照这篇文章的服务拆分和数据一致性方案去做设计评审尤其是第 2 节的服务边界表和第 5 节的状态机我认为是整套架构里最值得复用的部分。
返回列表