lagou 爪哇 3-4 spring cloud 问答笔记

本文涉及的产品
日志服务 SLS,月写入数据量 50GB 1个月
网络型负载均衡 NLB,每月750个小时 15LCU
应用型负载均衡 ALB,每月750个小时 15LCU
简介: 熔断即断路保护。微服务架构中,如果下游服务因访问压⼒过⼤⽽响应变慢或失 败,上游服务为了保护系统整体可⽤性,可以暂时切断对下游服务的调⽤。这种牺 牲局部,保全整体的措施就叫做熔断。

熔断即断路保护。微服务架构中,如果下游服务因访问压⼒过⼤⽽响应变慢或失 败,上游服务为了保护系统整体可⽤性,可以暂时切断对下游服务的调⽤。这种牺 牲局部,保全整体的措施就叫做熔断。


image.png


gateway 过滤器


Filter 在"pre"类型过滤器中可以做参数校验、权限校验、流量监控、日志输出、协议转换等,在“post"类型的过滤器中可以做响应内容、响应头的修改、日志的输出、流量监控等。


随堂测试



1-1、以下属于微服务架构优势的是()


A可以自由使用不同的技术


口B远程调用而导致延迟增加


C并行开发和部署多个服务


D故障隔离


口E模块边界定义较难


1-2、下面哪些是微服务架构和SOA架构的区別()


A拆分粒度更细


B组件化程度更高


口C微服务是真正的服务化而SOA不是


D通信往往更加轻量级


1-3、关于做服务中一些概念描述正确的是()


口A服务发现对实时性并无要求


B负载均衡有客户端负载均衡和服务器端负载均衡之说


口C熔断是退而求其次的措施,返回预设值


D网关如同統一入口出口,可以对请求进行精细化控制


2-1、以下关于 Spring Cloud描述正确的是()


A Spring Cloud充分利用了 Springboot?开发带来的便利性


B Spring Cloud可以说是一种规范,其下有不同的实现


C Spring Cloud帮我们解決微服务架构过程中的一系列题


D Spring Cloud采用组件化机制,不同组件解決不同问题,这些组件共同构成 Spring Cloud技术栈


2-2、下面描述错误的是()


口 A Eureka服务注册中心


B Hystrix实现负载均衡,从一个服务的多台机器中选择一台


C Ribbon提供熔断降级功能


口 D Feign远程调用


1、 Eureka满足CAP原则中的()分值7分


口A一致性


B可用性


C分区容错性


口D高性能


2、 Eureka Client从 Eureka Serveri端获取服务列表信息目前主要采用哪种模式()分值7分


A PUSI


B POLL


C 查询数据库


D 查询 Redis


3、下列关于 Eureka描述正确的是()分值6分


A集群模式下每一个 Eureka Server.相对于其他 Server来说都是客户端


B Eureka心跳机制是为了探测 Eureka Server是否存活


C心跳续约间隔默认30秒


D Eureka Client获取 Serverj端服务实例之后不会在本地缓存


4、关于 Eureka自我保护机制描述正确的是()分值7分


A不会剔除任何服务实例:


B可以配置关闭:


C是CAP中A的体现


D Eureka Server仍然能够接受新服务的注册和查询请求


5、关于 Ribbon描述正确的是()分值6分


A Ribbon属于客户端负载均衡


口 B Ribbon默认随机策略


口 C Ribbon原理上是基于 Filter过滤器


D Rule是 Ribbon对负载均衡策略的抽象和规范接


6、下面哪些属于雪崩效应解决或预防手段()分值7分


A服务熔断


B网关限流


C服务降级


D上游 Nginx限流


7、关于 dystrix描述正确的是()分值7分


O A Hystrix可以进行熔断但无法完成降级


D B Hystrix可以进行降级但无法完成熔断


C Hystrix可以完成熔断和降级


OD以上都不对


8、关于 Hystrix工作机制描述正确的是()分值6分


A在某一时间窗内错误请求数和最小请求数达到一定阈值, Hystrix将跳闸


口B跳闸之后将无法恢复,除非重启服务


口C跳闻之后可以恢复,但需要手动进行


D跳闸之后 Hystrix会进行自动修复尝试


9、关于 dystrix舱壁模式描述正确的是()分值7分


A不同的@ Hystrixcommand方法应该使用同一个线程池


B不同的@ Hystrixcommand方法可以各自使用一个线程池,避免影响


C舱壁模式在这里指的其实就是线程池隔离策略


口D以上都不对


10、关于 Feign/ Openfeign的描述正确的是()分值6分


A Feign日志可以帮助我们调试问题


口B使用 Openfeignl时仅仅只需要@ Enablefeign Clients开启功能即可,不需要做其他任何注解配置


C Openfeign使用起来类似于 Dubbo的方式,体现了面向接口编程


D Openfeign是支持一些 Springmvc注解的


11、通过追踪 Openfeign源码发现()分值7分


A Controller层注入的 Feign Client是一个代理对象


B底层是采用动态代理机制进行的功能增强


C Openfeign使用时的负载均衡实现交给了 Ribbon执行


D最终请求使用了 Httpurlconnection


12、关于 Gate Way网关描述正确的是()分值6分


口 A Gateway仅仅完成类似于 Nginx的路由转发


B Spring Cloud Gateway基于BIO模型


C Spring Cloud Gateway基于 Webflux实现


D 可以完成黑白名单、日志监控、限流等精细化控制


13、关于 Gateway过滤器描述正确的是


分值7分


A 从类型上分为 Gateway Filter?和 Globalfilter两种


B Gatewayfilter7i和 Globalfilter都会对所有路由生效


C Globalfilter?全局生效, Gate Wayfilter可以指定对具体的路由生效


D过滤器涉及到pre和post两个生命周期时机点


14、关于 Spring Cloud Config描述正确的是()分值7分


A 使用时需要暴露服务的 actuator相关端点


口B客户端获取到最新的配置数据后一点也不需要考虑做进一步处理


C客户端获取到最新的配置数据后根据情況看是否需要进一步处理,比如数据库连接池大小的配


口D以上都不对


15、关于 Spring Cloud Strean描述正确的是()分值7分


A帮我们屏蔽底层具体MQ之间的差异,提供上层抽象


B具体是由 Binders绑定器对象来对接具体的消息中间件


C Stream中 Binder.不能变更


D inputi通道对应生产者, output通道对应消费者


作业



一、编程题


请同学们根据下⾯的业务描述和要求,使⽤第⼀代Spring Cloud核⼼组件完成项⽬构建、编码及测试。


作业具体要求参考以下链接文档:


https://gitee.com/lagouedu/alltestfile/raw/master/springcloud/SpringCloud%E4%B8%8A%E4%BD%9C%E4%B8%9A.pdf


作业资料说明:


1、提供资料:代码工程、验证及讲解视频。


2、讲解内容包含:题目分析、实现思路、代码讲解。


3、效果视频验证:


  • 注册新账号


  • 一分钟内只允许获取一次验证码


  • 发邮件功能


  • 校验验证码


  • 验证码超时展示


  • 保存令牌数据库


  • 令牌保存cookie中


  • 跳转到欢迎页面


  • 登录


  • 生成Token保存到令牌表和Cookies中最后转到欢迎页面


  • 未登录状态网关拦截


  • 回IP防暴刷过滤器


  • 在1分钟内注册超过100次时返回错误信息


按照改图进行搭建即可.


image.png


nginx 做到动静分离


server {
    listen       80;
    server_name  localhost;
    location /static/ {        
        root   /Users/ale/Desktop/abc/stage-3-module-4/code/static/;
        rewrite '^/static(.*)$' $1 break;
    }
    location /api/ {
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr; # 客户端的真实IP
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_pass http://127.0.0.1:9002/; # 网关地址
    }
}


配置主机名, 也为了以后避免跨域问题 和 注册登录页面使用.

127.0.0.1 edu.lagou.com


使用前的准备



  1. 创建数据库, 导入表

create database lagou_3_4;
-- 验证码存储表
DROP TABLE IF EXISTS `lagou_auth_code`;
CREATE TABLE `lagou_auth_code` (
 `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '⾃增主键',
 `email` varchar(64) DEFAULT NOT NULLCOMMENT '邮箱地址',
 `code` varchar(6) DEFAULT NOT NULLCOMMENT '验证码',
 `createtime` datetime DEFAULT NOT NULLCOMMENT '创建时间',
 `expiretime` datetime DEFAULT NOT NULLCOMMENT '过期时间',
 PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
SET FOREIGN_KEY_CHECKS = 1;
-- 令牌存储表
DROP TABLE IF EXISTS `lagou_token`;
CREATE TABLE `lagou_token` (
 `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '⾃增主键',
 `email` varchar(64) NOT NULL COMMENT '邮箱地址',
 `token` varchar(255) NOT NULL COMMENT '令牌',
 PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-- lagou_token 表中为 邮箱 字段添加 UNIQUE 索引
ALTER TABLE `lagou_3_4`.`lagou_token` 
ADD UNIQUE INDEX(`email`);
-- lagou_token 表中添加 password 字段
ALTER TABLE `lagou_3_4`.`lagou_token` 
ADD COLUMN `password` varchar(40) NOT NULL COMMENT '用户密码' AFTER `token`;
SET FOREIGN_KEY_CHECKS = 1;
INSERT INTO `lagou_3_4`.`lagou_auth_code`(`email`, `code`, `createtime`, `expiretime`) VALUES ('zhangsan@qq.com', '543363', '2020-12-19 18:36:39', '2020-12-19 18:36:42');


  1. lagou-common 模块的开发, 引入必须的依赖信息. 然后就是常规操作进行Java Web 项目的分层开发.

email 邮件服务



  1. 引入 spring-boot-starter-mail 依赖

<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-mail</artifactId>
        </dependency>


  1. 配置 application.yml

spring:
  application:
    name: lagou-service-email
  mail:
    # 发送邮件服务器
    host: smtp.qq.com
    username: acc8226@vip.qq.com
    # 可拥有多个授权码,所以无需记住该授权码,也不要告诉其他人
    password: YOUR-PASSWORD


  1. EmailServiceImpl 类注入 JavaMailSender 进行邮件的发送

public void sendSimpleMail(String toEmailAddress, String code) {
        final SimpleMailMessage simpleMailMessage = new SimpleMailMessage();
        simpleMailMessage.setTo(toEmailAddress);
        simpleMailMessage.setSubject("收到来自【lagou】发送的验证码");
        simpleMailMessage.setText(String.format(pattern, code));
        simpleMailMessage.setFrom(this.fromEmailAddress);
        this.javaMailSender.send(simpleMailMessage);
        log.info("邮件发送成功 address = {}, code = {}", toEmailAddress, code);
    }


  1. 类 EmailController


暴露 "{email}/{code}" 接口, 用于提供向指定邮箱发送验证码的服务


场景:注册页面点击"获取验证码"按钮,被 user 微服务"create/{email}" 行为触发. 该接口本身并没有直接被用户进行调用.


测试用例:


用于校验此发送邮件的服务是否可用


http://localhost:8082/email/acc8226@qq.com/654473 预计返回 true


code 发送验证码服务


  • "create/{email}" 用于⽣成验证码 并 发送到对应邮箱 (暴露出的接口)
    场景:注册页面点击"获取验证码"按钮触发.

/**
    * ⽣成验证码 并 发送到对应邮箱
    * 场景:注册页面点击"获取验证码"按钮触发
    *
    * @param email
    * @return 0成功 1失败 2该用户已注册,不能再次注册
    */
@GetMapping("create/{email}")
public int create(@PathVariable("email") final String email) {
    int resultCode;
    try {
        // 1. 判断该用户是否存在
        final boolean isEmailRegistered = this.userServiceFeignClient.isRegistered(email);
        if (isEmailRegistered) {
            resultCode = 2;
        } else {
            final String randomCode = RandomUtils.getSixRandomCode();
            // 2. 若不存在 则 调用 email 微服务,发送携带了验证码的邮件
            final boolean register = this.mailServiceFeignClient.send(email, randomCode);
            // 3. 顺便入库
            this.authCodeService.createAuthCode(email, randomCode);
            log.info("create # send result = {}, email = {}, randomCode = {}", register, email, randomCode);
            resultCode = 0;
        }
    } catch (Exception e) {
        resultCode = 1;
        log.error("create ⽣成验证码 失败", e);
    }
    return resultCode;
}


  • "validate/{email}/{code}" 用于校验验证码是否正确(合法)
    场景:被 user 微服务的注册功能调用. 该接口本身并没有直接被用户进行调用.

/**
     * 校验验证码是否正确(合法),0合法, 1验证码前后校验不一致, 2超时, 3异常情况
     * 场景:被 user 微服务的注册功能调用
     *
     * @param email 传入的电子邮箱
     * @param code  传入的验证码,前端已做了非空校验,此处不再做校验
     * @return
     */
    @GetMapping("validate/{email}/{code}")
    public int validate(@PathVariable("email") String email, @PathVariable("code") String code)


测试用例:


验证-发送验证码是否总是返回 true


http://localhost:8081/code/create/zhangsan@qq.com


http://localhost:8081/code/create/test@qq1.com


http://localhost:8081/code/create/acc8226@qq.com


http://localhost:8081/code/create/acc8226@vip.qq.com


验证-校验验证码是否正确(合法)


http://localhost:8081/code/validate/zhangsan@qq.com/888888 预计返回1不匹配


http://localhost:8081/code/validate/zhangsan@qq.com/707636 预计返回 0,表示验证

码匹配


注意:


code 服务调用发送邮件微服务, 使用 feign 解决了使用 restTemple存在这不便之处。

但是我配置后发现 feign 默认 Read timed 超时了,然后会重试一次。导致时常验证码会重复发送一次.

2020-12-21 00:44:25.836 DEBUG 36852 --- [nio-8081-exec-1] c.l.edu.service.MailServiceFeignClient   : [MailServiceFeignClient#register] <--- ERROR SocketTimeoutException: Read timed out (2381ms)
2020-12-21 00:44:25.837 DEBUG 36852 --- [nio-8081-exec-1] c.l.edu.service.MailServiceFeignClient   : [MailServiceFeignClient#register] java.net.SocketTimeoutException: Read timed out


这是因为 code 验证码微服务通过feign调用email总能看到 ReadTimeout, 因为默认的超时时间是 1秒, 这里在 yml 配置文件中调长了超时时间,一定程度上解决了超时的问题. 这里使用了 ribbon, 这个客户端的负载均衡器.

ribbon:
  # 适当调大 请求处理超时时间,避免之后 feign 进行重试
  ReadTimeout: 20000


user 用户微服务



提供注册和登录的功能.


1. 注册接口(暴露出的接口)


http://localhost:8080/user/register/zhangsan@qq.com/123456/442252 第一次预计 true 表示注册成功,第二次为 false 表示注册失败。

/**
    * 注册接⼝
    *
    * @param email    E-mail
    * @param password 密码
    * @param code     验证码
    * @return 0合法, 1验证码前后校验不一致, 2超时, 3异常情况,4该用户已注册过了
    */
@GetMapping("register/{email}/{password}/{code}")
public int register(HttpServletResponse response,
                    @PathVariable("email") String email,
                    @PathVariable("password") String password,
                    @PathVariable("code") String code) {
    int validateResult;
    // 1. 如果没有注册才允许注册
    if (this.tokenService.isRegistered(email)) {
        validateResult = 4;
    } else {
        try {
            // 2. 调用 code 微服务-校验验证码的合法性: 0合法, 1密码前后校验不一致, 2超时, 3异常情况
            validateResult = this.authCodeServiceFeignClient.validate(email, code);
            log.info("验证码校验结果 = {}", validateResult);
            if (validateResult == 0) {
                // 3. 生成 token 并入库     【1. 入库】
                Token token = this.tokenService.register(email, password);
                // 4. token 写入 cookie 中 【2. 写入 cookie 并返回前端】
                saveTokenToResponse(response, token.getToken());
            }
        } catch (Exception e) {
            log.error("register method throw error:", e);
            validateResult = 3;
        }
    }
    // 5. 前端根据此不出错的标记去跳转到 welcome 页面
    return validateResult;
}


  1. 是否已注册接口"isRegistered/{email}"


  • 根据邮箱判断,true 代表已经注册过,false 代表尚未注册


  • 场景:被 code微服务-获取验证码的前置条件用到该方法


http://localhost:8080/user/isRegistered/zhangsan@qq.com 预计 true


http://localhost:8080/user/isRegistered/nobody@qq.com 预计 false


3. 登录接口(暴露出的接口)


"login/{email}/{password}"


http://localhost:8080/user/login/zhangsan@qq.com/123456 预计 返回邮箱


http://localhost:8080/user/login/zhangsan@qq.com/1234564532 预计返回ERROR,表示密码不匹配


http://localhost:8080/user/login/nobody@qq.com/123456 预计返回EMPTY,表示用户不存在

/**
    * 登录接⼝
    *
    * @param email 邮箱地址
    * @return 登录成功返回邮箱地址, 否则 原始密码已损坏 和 密码不一致为 ERROR 或 用户不存在为 EMPTY
    */
@GetMapping("login/{email}/{password}")
public String login(HttpServletResponse response, @PathVariable String email, @PathVariable String password) {
    log.info("login: email = {}, password = {}", email, password);
    final String returnMsg;
    final Optional<Token> findOne = this.tokenService.findOneItemByEmail(email);
    if (!findOne.isPresent()) {
        // 1. 用户不存在的情况:应提示用户去注册
        returnMsg = Response.EMPTY_MESSAGE;
    } else {
        // 2. 根据 email 知道对应的 用户信息
        Token token = findOne.get();
        String originPassword = token.getPassword();
        // 3. 用户的密码继续比对, 密码不一致的情况(包含了 原始密码已损坏 的情况)
        if (Strings.isBlank(originPassword) || !originPassword.equals(password)) {
            returnMsg = Response.ERROR_MESSAGE;
        } else {
            // 4. 刷新 token 并入库    【1. 入库】
            this.tokenService.login(token);
            // 5. 将 token 信息返回前端 【2. 写入 cookie 并返回前端】
            saveTokenToResponse(response, token.getToken());
            // 6. 前端根据此不出错的标记去跳转到 welcome 页面
            returnMsg = token.getEmail();
        }
    }
    log.info("login: returnMsg = {}", returnMsg);
    return returnMsg;
}


  1. token 反查 email 接口 (暴露出的接口)
    "info/{token}"
    场景: welcome页面会用到该支接口


http://localhost:8080/user/info/03837c5e-52a3-42ce-9e8a-7c2da840d68e 预计返回 email 信息


http://localhost:8080/user/info/12222229-2222-7777-55555-ac8333333335 预计返回EMPTY,表示 token 不存在或已失效. 前端据此重定向到 login 登录页面.


搭建 eureka 服务


使用以下两个端口


http://localhost:8761/


http://localhost:8762/


鉴于个人计算机很难模拟多主机的情况,这里 host 文件配置多主机实例.

127.0.0.1 LagouCloudEurekaServerA
127.0.0.1 LagouCloudEurekaServerB


8761 配置文件如下:

#eureka server服务端口
server:
  port: 8761
spring:
  application:
    # 应用名称,应用名称会在Eureka中作为服务名称
    name: lagou-cloud-eureka-server
    # eureka 客户端配置(和Server交互),Eureka Server 其实也是一个Client
eureka:
  client:
    service-url:
      # 配置客户端所交互的Eureka Server的地址(Eureka Server集群中每一个Server其实相对于其它Server来说都是Client)
      # 集群模式下,defaultZone应该指向其它 Eureka Server,如果有更多其它Server实例,逗号拼接即可
      defaultZone: http://LagouCloudEurekaServerB:8762/eureka
    # 集群模式下可以改成 true
    register-with-eureka: true
    # 集群模式下可以改成 true
    fetch-registry: true
  dashboard:
    # 默认已经为 true了
    enabled: true
  instance:
    # 当前eureka实例的主机名
    hostname: LagouCloudEurekaServerA


可选择是否开启 访问健康检查接口, 可用了解微服务的运行状态








网关服务



  1. 关键配置

spring:
  application:
    name: lagou-cloud-gateway
  cloud:
    # gateway 网关从服务注册中心获取实例信息然后负载后路由
    gateway:
      routes: # 路由可以有多个
        # 我们自定义的路由 ID,保持唯一
        - id: service-code-router
          # 目标服务地址  自动投递微服务(部署多实例)动态路由:uri配置的应该是一个服务名称,而不应该是一个具体的服务实例的地址. http为写死地址,lb为从注册中心取地址
          uri: lb://lagou-service-code
          # 断言:路由条件,Predicate 接受一个输入参数,返回一个布尔值结果。该接口包含多种默 认方法来将 Predicate 组合成其他复杂的逻辑(比如:与,或,非)。
          predicates:
            - Path=/code/**
        - id: service-email-router
          uri: lb://lagou-service-email
          predicates:
            - Path=/email/**
        - id: service-user-router
          uri: lb://lagou-service-user
          predicates:
            - Path=/user/**


  1. TokenFilter 用于对邮件微服务的 token认证, 验证客户端请求cookie中携带的token是否合法,合法则放⾏,此处不考虑token更新问题)


这里我的实现方式原本想的是调用了 code 服务的验证 token接口的, 但是我使用的lagou_token表被我改造成一个邮箱只对应一条数据, 所以重新登录后原有 token被刷掉. 所以这里我只对 token 做了非空校验和长度的限制校验.


if (null != path) {
    // 排除 user 和 code 服务
    if (!path.startsWith("/user") && !path.startsWith("/code")) {
        Optional<String> tokenOptional = getTokenValue(request);
        // token is null
        if (!tokenOptional.isPresent()) {
            return write401Mono(response);
        }
        String token = tokenOptional.get();
        // token is blank
        if (Strings.isBlank(token)) {
            return write401Mono(response);
        }
        // 校验 token 是否合法, 这里只做基本校验。认为只要长度大于 30 就是后端仍可的合法 token
        if (token.length() < 30) {
            return write401Mono(response);
        }
    }
}
// 合法请求,放行,执行后续的过滤器
return chain.filter(exchange);


  1. IP注册接⼝的防暴刷控制的 RushFilter 过滤器. 如果被校验住会有状态吗为303 和 "您频繁进⾏注册,请求已被拒绝"的提示

// 注册服务的路径
if (null != path && path.startsWith("/user/register/")) {
    // 由于使用了 nginx 转发,所以从特定请求头中拿出 实际 IP 地址
    String realIP = request.getHeaders().getFirst("X-Real-IP");
    if (!Strings.isBlank(realIP)) {
        IPRecordQueue ipRecordQueue = concurrentHashMap.get(realIP);
        if (ipRecordQueue == null) {
            ipRecordQueue = new IPRecordQueue(min, per);
            concurrentHashMap.put(realIP, ipRecordQueue);
        }
        boolean isOffer = ipRecordQueue.offer(LocalDateTime.now());
        if (!isOffer) {
            // 前端据此 UNAUTHORIZED 跳转到 login 页面
            response.setStatusCode(HttpStatus.SEE_OTHER);
            DataBuffer wrap = response.bufferFactory().wrap("您频繁进⾏注册,请求已被拒绝".getBytes());
            return response.writeWith(Mono.just(wrap));
        }
    }
}
// 合法请求,放行,执行后续的过滤器
return chain.filter(exchange);


测试注册接口是否可以做到1分钟请求2次后便阻断


http://edu.lagou.com/api/user/register/acc8226@qq.com/123456/333322


测试直连网关的连通性


http://localhost:9002/email/acc8226@vip.qq.com/666666 预计返回 true


测试加入nginx 之后, 访问网关的连通性


http://edu.lagou.com/api/email/acc8226@vip.qq.com/888888 预计返回 true


config 配置服务 和 bus刷新服务的支持



直接访问服务端 配置


这两种写法都是可以的

  • /{application}-{profile}.yml
  • /{label}/{application}-{profile}.yml



我这里配置了 2 套环境.


  1. 开发环境




  1. 生产环境



示例配置 yml 文件如下

spring:
# 数据库配置
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://YOUR-IP:3306/lagou_3_4?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai
    username: root
    password: YOUR-PASSWORD
# 邮箱配置
  mail:
    ## 发送邮件服务器
    host: smtp.qq.com
    ## 发送邮件的邮箱地址
    username: acc8226@qq.com
    ## 客户端授权码,不是邮箱密码,这个在qq邮箱设置里面自动生成的
    password: YOUR-PASSWORD
# 防暴刷配置:限制单个客户端ip最新 X 分钟的请求注册接口不能超过 Y 次
myconfig:
  x: 1
  y: 1


手动验证发邮件服务


http://localhost:8082/actuator/refresh


配置了bus后发消息给所有需要刷新的服务


http://localhost:9002/actuator/bus-refresh


安装 rabbit mq, 使用 brew install rabbitmq命令.

或者手动安装


sbin/rabbitmq-server


sbin/rabbitmqctl shutdown


在浏览器输入http://localhost:15672 即可进入rabbitmq控制终端登录页面

默认用户名和密码为 guest/guest.


三个主要页面


注册页面


获取验证码


http://edu.lagou.com/api/code/create/acc8226@qq.com


进行注册


http://edu.lagou.com/api/user/register/acc8226@qq.com/123456/333322


登录页面


进行登录


http://edu.lagou.com/api/user/login/acc8226@qq.com/123456


欢迎页面


会调用/api/user/info/, token查询邮箱地址的结果.


关于测试


  1. 注册测试


  1. 登录测试


  1. 未登录状态下,清空cookie,直接访问后台的邮件服

务,http://www.test.com/api/email/{email}/{code},验证⽆token情况下是否被⽹关拦截.


http://edu.lagou.com/api/email/acc8226@qq.com/123322

参考



Session 与 Cookie 基础

由于http协议是无状态的协议,为了能够记住请求的状态,于是引入了Session和Cookie的机制。我们应该有一个很明确的概念,那就是Session是存在于服务器端的,在单体式应用中,他是由tomcat管理的,存在于tomcat的内存中,当我们为了解决分布式场景中的session共享问题时,引入了redis,其共享内存,以及支持key自动过期的特性,非常契合session的特性,我们在企业开发中最常用的也就是这种模式。但是只要你愿意,也可以选择存储在JDBC,Mongo中,这些,spring都提供了默认的实现,在大多数情况下,我们只需要引入配置即可。

而Cookie则是存在于客户端,更方便理解的说法,可以说存在于浏览器。Cookie并不常用,至少在我不长的web开发生涯中,并没有什么场景需要我过多的关注Cookie。http协议允许从服务器返回Response时携带一些Cookie,并且同一个域下对Cookie的数量有所限制,之前说过Session的持久化依赖于服务端的策略,而Cookie的持久化则是依赖于本地文件。虽然说Cookie并不常用,但是有一类特殊的Cookie却是我们需要额外关注的,那便是与Session相关的sessionId,他是真正维系客户端和服务端的桥梁。


从零开始的Spring Session(一) | 程序猿DD


http://blog.didispace.com/spring-session-xjf-1/




相关实践学习
消息队列RocketMQ版:基础消息收发功能体验
本实验场景介绍消息队列RocketMQ版的基础消息收发功能,涵盖实例创建、Topic、Group资源创建以及消息收发体验等基础功能模块。
消息队列 MNS 入门课程
1、消息队列MNS简介 本节课介绍消息队列的MNS的基础概念 2、消息队列MNS特性 本节课介绍消息队列的MNS的主要特性 3、MNS的最佳实践及场景应用 本节课介绍消息队列的MNS的最佳实践及场景应用案例 4、手把手系列:消息队列MNS实操讲 本节课介绍消息队列的MNS的实际操作演示 5、动手实验:基于MNS,0基础轻松构建 Web Client 本节课带您一起基于MNS,0基础轻松构建 Web Client
目录
相关文章
|
3月前
|
设计模式 Java 关系型数据库
【Java笔记+踩坑汇总】Java基础+JavaWeb+SSM+SpringBoot+SpringCloud+瑞吉外卖/谷粒商城/学成在线+设计模式+面试题汇总+性能调优/架构设计+源码解析
本文是“Java学习路线”专栏的导航文章,目标是为Java初学者和初中高级工程师提供一套完整的Java学习路线。
434 37
|
2月前
|
Java 数据库连接 Spring
【2021Spring编程实战笔记】Spring开发分享~(下)
【2021Spring编程实战笔记】Spring开发分享~(下)
29 1
|
2月前
|
XML Java 数据库连接
【2020Spring编程实战笔记】Spring开发分享~(上)
【2020Spring编程实战笔记】Spring开发分享~
53 0
|
3月前
|
Java 数据库连接 API
【Java笔记+踩坑】Spring Data JPA
从常用注解、实体类和各层编写方法入手,详细介绍JPA框架在增删改查等方面的基本用法,以及填充用户名日期、分页查询等高级用法。
【Java笔记+踩坑】Spring Data JPA
|
3月前
|
Java 数据库连接 数据格式
【Java笔记+踩坑】Spring基础2——IOC,DI注解开发、整合Mybatis,Junit
IOC/DI配置管理DruidDataSource和properties、核心容器的创建、获取bean的方式、spring注解开发、注解开发管理第三方bean、Spring整合Mybatis和Junit
【Java笔记+踩坑】Spring基础2——IOC,DI注解开发、整合Mybatis,Junit
|
5月前
|
资源调度 Java 调度
Spring Cloud Alibaba 集成分布式定时任务调度功能
Spring Cloud Alibaba 发布了 Scheduling 任务调度模块 [#3732]提供了一套开源、轻量级、高可用的定时任务解决方案,帮助您快速开发微服务体系下的分布式定时任务。
14981 29
|
5月前
|
负载均衡 Java Spring
Spring cloud gateway 如何在路由时进行负载均衡
Spring cloud gateway 如何在路由时进行负载均衡
513 15
|
5月前
|
Java Spring
spring cloud gateway在使用 zookeeper 注册中心时,配置https 进行服务转发
spring cloud gateway在使用 zookeeper 注册中心时,配置https 进行服务转发
115 3
|
5月前
|
消息中间件 Java Nacos
通用快照方案问题之通过Spring Cloud实现配置的自动更新如何解决
通用快照方案问题之通过Spring Cloud实现配置的自动更新如何解决
77 0

热门文章

最新文章

下一篇
无影云桌面