熔断即断路保护。微服务架构中,如果下游服务因访问压⼒过⼤⽽响应变慢或失 败,上游服务为了保护系统整体可⽤性,可以暂时切断对下游服务的调⽤。这种牺 牲局部,保全整体的措施就叫做熔断。
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上作业.pdf
作业资料说明: 1、提供资料:代码工程、验证及讲解视频。 2、讲解内容包含:题目分析、实现思路、代码讲解。 3、效果视频验证:
- 注册新账号
- 一分钟内只允许获取一次验证码
- 发邮件功能
- 校验验证码
- 验证码超时展示
- 保存令牌数据库
- 令牌保存cookie中
- 跳转到欢迎页面
- 登录
- 生成Token保存到令牌表和Cookies中最后转到欢迎页面
- 未登录状态网关拦截
- 回IP防暴刷过滤器
- 在1分钟内注册超过100次时返回错误信息
按照改图进行搭建即可.
nginx 做到动静分离
代码语言:javascript复制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/; # 网关地址
}
}
配置主机名, 也为了以后避免跨域问题 和 注册登录页面使用.
代码语言:javascript复制127.0.0.1 edu.lagou.com
使用前的准备
- 创建数据库, 导入表
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');
- lagou-common 模块的开发, 引入必须的依赖信息. 然后就是常规操作进行Java Web 项目的分层开发.
email 邮件服务
- 引入 spring-boot-starter-mail 依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-mail</artifactId>
</dependency>
- 配置 application.yml
spring:
application:
name: lagou-service-email
mail:
# 发送邮件服务器
host: smtp.qq.com
username: acc8226@vip.qq.com
# 可拥有多个授权码,所以无需记住该授权码,也不要告诉其他人
password: YOUR-PASSWORD
- 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);
}
- 类 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 超时了,然后会重试一次。导致时常验证码会重复发送一次.
代码语言:javascript复制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, 这个客户端的负载均衡器.
代码语言:javascript复制ribbon:
# 适当调大 请求处理超时时间,避免之后 feign 进行重试
ReadTimeout: 20000
user 用户微服务
提供注册和登录的功能.
1. 注册接口(暴露出的接口) http://localhost:8080/user/register/zhangsan@qq.com/123456/442252 第一次预计 true 表示注册成功,第二次为 false 表示注册失败。
代码语言:javascript复制/**
* 注册接⼝
*
* @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;
}
- 是否已注册接口
"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;
}
- 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 文件配置多主机实例.
代码语言:javascript复制127.0.0.1 LagouCloudEurekaServerA
127.0.0.1 LagouCloudEurekaServerB
8761 配置文件如下:
代码语言:javascript复制#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
可选择是否开启 访问健康检查接口, 可用了解微服务的运行状态
- http://localhost:8761/actuator/health
- http://localhost:8762/actuator/health
- http://localhost:8080/actuator/health
- http://localhost:8081/actuator/health
- http://localhost:8082/actuator/health
- http://localhost:9006/actuator/health
网关服务
- 关键配置
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/**
- TokenFilter 用于对邮件微服务的 token认证, 验证客户端请求cookie中携带的token是否合法,合法则放⾏,此处不考虑token更新问题)
这里我的实现方式原本想的是调用了 code 服务的验证 token接口的, 但是我使用的lagou_token表被我改造成一个邮箱只对应一条数据, 所以重新登录后原有 token被刷掉. 所以这里我只对 token 做了非空校验和长度的限制校验.
代码语言:javascript复制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);
- 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 套环境.
- 开发环境
- http://localhost:9006/lagou-service-config-dev.yml
- http://localhost:9006/master/lagou-service-config-dev.yml (两者选其一就行)
- 生产环境
- http://localhost:9006/lagou-service-config-prod.yml
示例配置 yml 文件如下
代码语言:javascript复制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查询邮箱地址的结果.
关于测试
- 注册测试
- 登录测试
- 未登录状态下,清空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/