早期项目复盘:基于 Spring Cloud 的统一认证与业务实践
一、前言
回顾职业生涯的初期,参与的项目往往伴随着技术栈的快速迭代与业务场景的复杂化。在在线教育创业公司工作的这段时间,是我从单体架构向微服务架构转型的关键阶段。通过实际落地统一登录鉴权服务以及核心业务系统的开发,我对分布式系统的设计有了更深层次的理解。
本文旨在总结这段时期的技术实践,涵盖微服务权限治理、高并发场景下的 Redis 应用以及任务调度方案的演进,希望对正在经历类似技术转型的开发者有所启发。
二、统一登录鉴权服务设计
1. 项目背景
随着公司内部后台管理系统的增多,各个项目组需要独立维护用户权限逻辑,导致重复建设严重且数据不一致。为了解决这一问题,我们决定构建一个统一的登录鉴权服务(Auth Center),实现单点登录(SSO)及细粒度的权限控制。
业务层面主要划分为三大模块:
- 用户(User):基础身份信息。
- 群组(Group):代表拥有特定权限的角色集合。
- 接口 API:具体的操作入口,需绑定到对应的群组。
权限模型采用 RBAC(Role-Based Access Control)思想,即用户属于群组,群组拥有接口访问权限。
2. 技术选型与架构
整体服务基于 Spring Cloud 微服务生态构建,各服务注册至 Eureka 服务中心,网关层使用 Zuul 进行流量转发与拦截。
2.1 Auth-Center 服务
该服务负责提供用户信息、群组关系及 URL 权限分配的增删查改接口。
实体类设计:
public class User implements Serializable {
private int id;
private String userName;
private String password;
private int status;
private int role;
private String email;
private String phone;
private Date createTime;
private Date modifyTime;
}
public class Group implements Serializable {
private int id;
private String groupName;
private int status;
private String server;
private String url;
}
public class UserGroupRelation implements Serializable {
private int userId;
private int groupId;
}
RPC 远程调用:
利用 Feign 客户端将权限校验逻辑暴露给网关层,实现解耦。
@FeignClient("auth-center")
@RequestMapping("/authCenter")
public interface CheckTokenService {
@RequestMapping(value = "/verifyToken", method = RequestMethod.POST)
AuthenUser test(@RequestParam("token") String token,
@RequestParam("url") String url,
@RequestParam("server") String server);
}
动态权限扫描:
为了自动获取所有服务的接口路径,避免硬编码,我们开发了一个通用的 Servlet JAR 包。利用反射原理,扫描 Spring IOC 容器中所有 RequestMapping 注解下的 URL,供权限分配系统使用。
RequestMappingHandlerMapping rmhp = springContextHelper.getObject(RequestMappingHandlerMapping.class);
try {
Map<RequestMappingInfo, HandlerMethod> map = rmhp.getHandlerMethods();
for (RequestMappingInfo info : map.keySet()) {
HandlerMethod hm = map.get(info);
Class<?> scannerClass = hm.getBeanType();
ClassScanInfo classScanInfo = scannerHelper.getAllMappingUrl(scannerClass);
projectUrls.add(classScanInfo);
}
print(resp, new Protocol(ResponseCode.OK, projectUrls));
} catch (Exception e) {
e.printStackTrace();
}
2.2 Zuul 网关过滤
Zuul 作为网关的核心 Filter 组件,我们重写了类型为 pre 的过滤器。在请求转发前,调用 auth-center 接口验证 Token 有效性及 URL 权限。若验证失败,直接拒绝请求并返回 403;否则放行。
三、主业务项目实践
随后工作进入正轨,接手了公司的核心业务项目。该项目面向 K12 教育市场,商业模式包含会员购课、单词打卡返学费、分销等。以下是几个印象深刻的技术场景。
1. 开口读排行榜
为了增加产品的趣味性,吸引小朋友开口读单词,产品设计了实时排行榜功能。底层采用 Redis 的 ZSet 数据结构实现,其中 score 为开口次数,value 为用户 ID(childId)。
性能优化策略:
由于产品仅需展示前五百名用户,为了避免全量排序带来的性能损耗,我们采用了定期清理无用数据的策略。当概率小于等于 10% 时,触发清理逻辑,移除排名靠后的无效数据,保证查询响应时间。
if (temp <= 0.1) {
RedisClient.expire(weekSpeakRankRedisTemplate, weekCache, 42L, TimeUnit.DAYS);
RedisClient.zremoveRange(weekSpeakRankRedisTemplate, weekCache, 0, -501);
}
2. 兑换码生成与管理
兑换码系统用于活动营销,要求具备唯一性和不可预测性。我们使用 Redis 的 Set 集合对象存储已生成的兑换码,利用其去重特性防止重复发放。
生成逻辑:
使用 UUID 简单生成,并进行字符清洗(去除横杠、替换数字 0 为字母 o 等),最后转为大写。
String code = UUID.randomUUID().toString()
.substring(0, 18)
.replaceAll("-", "")
.replaceAll("0", "o")
.toUpperCase();
每次用户调用接口获取兑换码时,从 Set 集合中执行 spop 取出元素,随即插入数据库保存用户与兑换码的使用状态。
3. 定时任务调度演进
在早期的服务器部署中,我们曾直接使用 Linux 的 cron 命令调度 Controller 来实现定期任务。这种方式存在运维耦合度高、缺乏可视化监控的问题。
为解决并发执行导致的重复计算问题,我们在代码最前面加入了公司自研的 Redis 分布式锁。随着业务发展,后续结合 Spring 的 @Scheduled 配合时间轮自定义框架实现任务调度。如今在生产环境中,我们更倾向于使用 XXL-Job 等成熟的分布式任务调度平台,以支持分片广播、故障转移及运行日志查看等功能。
4. 服务降级与容错
在处理外部依赖接口调用时,为防止超时报错影响主流程,我们引入了 Hystrix 进行服务降级处理。虽然在实际开发中,简单的 try-catch 异常捕获也能满足部分需求,但在高可用架构下,配置合理的熔断策略和降级逻辑是保障系统稳定性的关键。
四、总结与反思
回顾这四年的技术历程,从最初死记硬背 Synchronized、volatile 等底层原理,到后来在实际项目中理解同步器、FIFO 队列、生产 - 消费模式的应用,这种知识体系的串联如同玩剧本杀一样,将所有线索连在一起,豁然开朗。
微服务架构不仅仅是技术的堆砌,更是对业务边界的重新划分。统一认证服务解决了权限分散的痛点,Redis 的高性能数据结构支撑了高并发场景,而任务调度的演进则体现了对可维护性的追求。在未来的工作中,我将继续关注云原生技术与分布式系统的深度优化,保持对新技术的敏感度与学习热情。