# lagou_3_4_springcloud **Repository Path**: java-quickstart/lagou_3_4_springcloud ## Basic Information - **Project Name**: lagou_3_4_springcloud - **Description**: 第三阶段模块四 - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2020-06-23 - **Last Updated**: 2023-05-30 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # 李志勇的作业 ## 阶段三模块一作业 ### 编程题 请同学们根据下⾯的业务描述和要求,使⽤第⼀代 Spring Cloud 核⼼组件完成项⽬构建、编码及测试。 业务描述 以注册、登录为主线,串联起验证码⽣成及校验、邮件发送、IP 防暴刷、⽤户统⼀认证等功能。实现需基于 Spring Cloud 微服务架构,技术涉及 Nginx、Eureka、Feign(Ribbon、Hystrix)、Gateway、Config+Bus 等。 ![架构描述](./业务架构.png) #### Nginx - 占⽤端⼝:80,实现动静分离。将静态资源 html ⻚⾯存放⾄本地磁盘,数据请求统⼀经过 GateWay ⽹关路由到下游微服务。 - 静态资源(html ⻚⾯) 访问:/xxx.html,包括登录⻚⾯ login.html、注册⻚⾯ register.html、以及成功登录之后的欢迎⻚⾯ welcome.html - nginx 配置 ```bash server { listen 80; server_name localhost 127.0.0.1; root /Users/zhiyongli/source/gitee/lagou-3-4-springcloud/static-resources/static; index login.html; location ~ .*\.(html|js)$ { root /Users/zhiyongli/source/gitee/lagou-3-4-springcloud/static-resources/static; } location / { proxy_pass http://localhost:9002; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } } ``` #### GateWay ⽹关 占⽤端⼝:9002 端⼝,数据请求前缀:/api/xxx 完成统⼀路由、IP 防暴刷(限制单个客户端 IP 在最近 X 分钟内请求注册接⼝不能超过 Y 次)、统⼀认证(请求到来时,验证客户端请求 cookie 中携带的 token 是否合法,合法则放⾏,不考虑 token 更新问题)等功能 路径路由规则: ```http /api/user/xxx 路由到⽤户微服务 /api/code/xxx 路由到验证码微服务 /api/email/xxx 路由到邮件微服务 ``` 网关过滤器 - 全局过滤器 验证 cookie 中的 token 信息是否合法(user 和 code 服务请求不做处理),合法则跳转到 welcome 页并显示用户邮箱 ```java @Component public class TokenGlobalFilter implements GlobalFilter, Ordered { private static final String LG_U_TOKEN = "lg_u_token"; @Autowired private UserService userService; @Override public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { ServerHttpRequest request = exchange.getRequest(); ServerHttpResponse response = exchange.getResponse(); String requestPath = request.getPath().value(); System.out.println(requestPath); if (requestPath.startsWith("/api/user") || requestPath.startsWith("/api/code")) { return chain.filter(exchange); } String token = Optional.ofNullable(request.getCookies().getFirst(LG_U_TOKEN)).map(HttpCookie::getValue).orElse(null); if (StringUtils.isNotBlank(token)) { LagouResponse checkTokenResponse = userService.checkToken(token); if (checkTokenResponse.isSuccess()) { String welcomeUrl = "http://localhost/welcome.html?email=" + checkTokenResponse.getData(); response.setStatusCode(HttpStatus.SEE_OTHER); response.getHeaders().set(HttpHeaders.LOCATION, welcomeUrl); return response.setComplete(); } clearCookie(response, LG_U_TOKEN); } return chain.filter(exchange); } @Override public int getOrder() { return HIGHEST_PRECEDENCE; } } ``` - 注册防爆刷过滤器 对注册请求进行过滤,先获取客户端 ip,然后将该 ip 的请求时间以的形式存放到 Map 中,下次该 ip 再调用注册请求的时候先判断最近 x 分钟内请求数有没有达到 y,如果达到 y 了直接拒绝该请求(同时启动一个定时任务,定期删除 x 分钟之前的请求记录) ```java @Bean @RefreshScope public RouteLocator routeLocator(RouteLocatorBuilder builder) { return builder.routes().route( r -> r.path("/api/user/register/{email}/{password}/{code}") .filters(f -> f.stripPrefix(1)) .uri("lb://lagou-service-user") .filters(new RegisterGatewayFilter(rateMinutes, rateRequests)) .id("lagou-service-user")) .build(); } public class RegisterGatewayFilter implements GatewayFilter { private Map> ipRequestTimesMap = new ConcurrentHashMap<>(); private int rateMinutes; private int rateRequests; public RegisterGatewayFilter(int rateMinutes, int rateRequests) { this.rateMinutes = rateMinutes; this.rateRequests = rateRequests; Executors.newSingleThreadScheduledExecutor() .scheduleWithFixedDelay(() -> { long currentTime = System.currentTimeMillis(); long startTime = currentTime - TimeUnit.MINUTES.toMillis(rateMinutes); for (Map.Entry> entry : ipRequestTimesMap.entrySet()) { entry.getValue().removeIf(t -> t < startTime); } }, 60, 60, TimeUnit.SECONDS); } @Override public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { ServerHttpRequest request = exchange.getRequest(); ServerHttpResponse response = exchange.getResponse(); String remoteIp = request.getHeaders().getFirst("X-Real-IP"); if (remoteIp == null) { remoteIp = Optional.ofNullable(request.getRemoteAddress()) .map(InetSocketAddress::getHostString) .orElse(null); } if (remoteIp == null) { return chain.filter(exchange); } List requestTimes = ipRequestTimesMap.get(remoteIp); if (requestTimes == null) { requestTimes = new ArrayList<>(); } long currentTime = System.currentTimeMillis(); long startTime = currentTime - TimeUnit.MINUTES.toMillis(rateMinutes); Iterator iterator = requestTimes.iterator(); while (iterator.hasNext()) { long requestTime = iterator.next(); if (requestTime < startTime) { iterator.remove(); } else { break; } } if (requestTimes.size() > rateRequests) { response.setStatusCode(HttpStatus.SEE_OTHER); String data = "您频繁进⾏注册,请求已被拒绝"; DataBuffer wrap = response.bufferFactory().wrap(data.getBytes()); return response.writeWith(Mono.just(wrap)); } requestTimes.add(currentTime); ipRequestTimesMap.put(remoteIp, requestTimes); return chain.filter(exchange); } } ``` #### lagou-service-user ⽤户微服务 占⽤端⼝ 8080 数据请求前缀: /user/xxx - 注册接⼝ 调用 code 服务校验验证码,检查邮箱是否已注册,如果可以注册则生成 token 存入 db,并将 token 写入 cookie; - 邮箱是否注册接口 检查邮箱是否已注册 - 登录接口 校验邮箱和密码,校验通过生成 token 存入 db,并将 token 写入 cookie; - 查询⽤户登录邮箱接⼝ 根据 token 查询邮箱,主要是网关校验 token 用; #### lagou-service-code 验证码微服务 占⽤端⼝:8081 数据请求前缀: /code/xxx - 获取验证码接口 生成随机 6 位数字验证码,存入 db,并调用 email 服务发送该验证码; - 验证验证码接口 取该邮箱的最后一条验证码记录,判断验证码是否正确,是否超时; #### lagou-service-email 邮件微服务 占⽤端⼝:8082 数据请求前缀: /api/email/xxx - 邮件发送接口 使用 spring 提供的 JavaMailSender 发送邮件 #### Spring Cloud Config+Bus 占⽤端⼝:9006 在 gitee 上新建仓库 lagou-config,将数据库连接信息、邮件发送相关配置、ip 防爆刷参数配置信息保存到仓库,其他服务从该仓库获取相应配置信息进行启动;