irpas技术客

springcloud注册demo(使?第?代Spring Cloud核?组件完成项?构建、编码及测试)_穿城大饼

大大的周 3240

一、业务描述

以注册、登录为主线,串联起验证码生成及校验、邮件发送、IP防暴刷、用户统一认证等功能。 实现需基于Spring Cloud 微服务架构,技术涉及Nginx、Eureka、Feign(Ribbon、Hystrix)、Gateway、Config+Bus等。

1.1 注册

1)用户访问到登录页面,在登录页面中有注册新账号功能 2)点击“注册新账号“,跳转到注册页面 3)在注册页面,需要用户输入邮箱地址、密码、确认密码,然后点击”获取验证码“,系统会生成验证码并向所输入的邮箱地址发送该验证码,用户拿到邮箱中的验证码输入后完成注册

规则如下 A:一分钟内只允许获取一次验证码(前端Js控制即可),验证码为随机生成的6位数字,10分钟内有效,验证码存储到mysql数据库中(也可以选择存入到Redis中);

B:存储到mysql数据库之后,使用发邮件功能,将该验证码发送到所输入的邮箱地址中

C:用户从邮箱中拿到验证码,点击注册时,需要进行行校验,因为验证码已经存入数据库,此时只需要查询数据库中该邮箱地址对应的最近一次的验证码记录,校验验证码是否正确,是否超时,若有问题,准确提示给用户

4)注册成功后,根据 <用户邮箱+密码> 生成签发token令牌(此处生成一个UUID模拟即可),该token令牌存入数据库(也可以选择存入到Redis中),并写入cookie中(以后的每次请求都会在cookie中携带该token,网关过滤器通过验证token的合法性来确定用户请求是否合法,如果token合法,根据token取出用户信息---->邮箱),最后重定向到欢迎?页面(显示邮箱地址)

1.2 登录

1)用户访问登录页面,在登录页面输入邮箱地址+密码 2)点击登录,后台对用户名和密码进行验证,然后根据<用户邮箱+密码> 生成签发token令牌(此处生成一个UUID模拟即可),该token令牌存入数据库(因为大家未系统学习Redis,所以此处令牌存入数据库即可),并写入cookie中(以后的每次请求都会在cookie中携带该token,网关过滤器通过验证token的合法性来确定用户请求是否合法,如果token合法,根据token取出用户信息---->邮箱),最后重定向到欢迎页面(显示邮箱地址)

1.3 架构描述

Nginx 占?用端?口:80 实现动静分离。将静态资源 html 页面存放至本地磁盘,数据请求统一经过 GateWay 网关路由到下游微服务。

静态资源(html页面) 访问前缀:/static/xxx.html 包括登录页面 login.html、注册页面 register.html、以及成功登录之后的欢迎页面welcome.html,各个页面细节元素后面有描述

GateWay 网关 占用端口:9002端口 数据请求前缀:/api/xxx 完成统一路由、IP防暴刷(限制单个客户端IP在最近X分钟内请求注册接口不能超过Y次)、统一认证(登录时验证用户名密码是否合法,合法调用用户微服务生成token,写入cookie,并且携带邮箱地址重定向到欢迎页面;后续请求再到来时,验证客户端请求cookie中携带的token是否合法,合法则放行,此处不考虑token更新问题)等功能

路路径路路由规则: /api/user/** 路由到用户微服务 /api/code/** 路由到验证码微服务 /api/email/** 路由到邮件微服务

dabing-service-user 用户微服务 占用端口:8080 数据请求前缀:/api/user/** 提供注册接口、用户是否已注册接口、登录接口(?生成token并入库,token写入cookie中)、查 询用户登录邮箱接口等

dabing-service-code 验证码微服务 占用端口:8081 数据请求前缀:/api/code/** 用于提供验证码生成、验证码校验等接口,同时调用邮件微服务发送验证码

dabing-service-email 邮件微服务 占用端口:8082 数据请求前缀:/api/email/** 提供邮件发送功能,用于将生成的验证码发送到注册邮箱

Spring Cloud Config+Bus 占用端口: 9006 共享的配置:数据库连接信息、邮件发送相关配置、IP防暴暴刷指标参数(X分钟的X,Y上限的Y) 注意:除去Eureka是2个实例的集群模式,其他保持单实例

1.4 接口描述

涉及到的微服务名称定义、接口定义、参数名称,可以和下文提到的保持一致。

微服务名称API 接口返回值接口描述dabing-service-use/user/register/{email}/{password}/{code}true/false注册接?口,true成功,false失败/user/isRegistered/{email}true/false是否已注册,根据邮箱判断,true代表已经注册过,false代表尚未注册/user/login/{email}/{password}邮箱地址登录接?口,验证?用户名密码合法性,根据?用户名和密码?生成token,token存?入数据库,并写?入cookie中,登录成功返回邮箱地址,重定向到欢迎?页/user/info/{token}根据token查询?用户登录邮箱接?口dabing-service-code/code/create/{email}true/false?生成验证码并发送到对应邮箱,成功true,失败false/code/validate/{email}/{code}0/1/2校验验证码是否正确,0正确1错误2超时dabing-service-email/email/{email}/{code}true/false发送验证码到邮箱,true成功,false失败
1.5 页面描述

涉及到界面的,界面样式不做要求,界面元素完备即可。

登录界面 login.html 登录界面包含输入项: 邮箱:email 密码:password 点击“登录”调用登录接口,成功后重定向到欢迎页面welcome.html ajax调用http://localhost:9002/api/user/login/{email}/{password} 点击“注册新账号”可跳转到注册页面register.html

注册界面 register.html 注册界面包含输入项: 邮箱:email 密码: password 确认密码:ConfirmPassword 验证码:code 点击“获取验证码”:调用获取验证码接口 http://localhost:9002/code/create/{email} 点击”注册“:调用注册接口 http://localhost:9002/user/register/{email}/{password}/{code}

欢迎页面 welcome.html 如下显示登录账户信息即可

1.6 关于网关和配置中心的说明 GateWay网关 统一路由,数据请求的统一出入口添加全局过滤器,进行IP注册接口的防暴刷控制,超过阈值直接返回错误码及错误信息(错 误码:303,错误信息:您频繁进行注册,请求已被拒绝)添加全局过滤,进行token的验证,用户微服务和验证码微服务的请求不过滤(网关调用下游 用户微服务的token验证接口) Config配置中心 将数据库配置、邮件发送相关配置、防暴刷参数配置,使用Config进行管理,对应微服务可以在自己的配置文件中直接使用${xxx.yyy}的方式取出使用 1.7 关于数据表的说明

涉及到两个数据表:验证码存储表+令牌存储表,数据表参考如下

验证码存储表

-- ---------------------------- -- Table structure for dabing_auth_code -- ---------------------------- DROP TABLE IF EXISTS `dabing_auth_code`; CREATE TABLE `dabing_auth_code` ( `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '?自增主键', `email` varchar(64) DEFAULT NULL COMMENT '邮箱地址', `code` varchar(6) DEFAULT NULL COMMENT '验证码', `createtime` datetime DEFAULT NULL COMMENT '创建时间', `expiretime` datetime DEFAULT NULL COMMENT '过期时间', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; SET FOREIGN_KEY_CHECKS = 1;

令牌存储表

-- ---------------------------- -- Table structure for dabing_token -- ---------------------------- DROP TABLE IF EXISTS `dabing_token`; CREATE TABLE `dabing_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; SET FOREIGN_KEY_CHECKS = 1; 1.8 关于测试 注册测试登录测试未登录状态下,清空cookie,直接访问后台的邮件服务,http://·/api/email/{email}/{code},验证无token情况下是否被网关拦截 1.9 关于跨域

前文在 Html 静态页面中有ajax 请求数据API统一接口9002的地方,会涉及跨域问题,可以考虑将静态 资源和数据请求接口放在同一个域名下,根据url前缀在nginx层进行区分。

比如所有部署,包括nginx都在一台机器上 可以给机器设置一个域名 · 静态资源访问 ·/static/xxx.html 数据API接口请求 ·/api/xxx/yyy 通过/static和/api在nginx层进行区分


1.10 效果视频验证: 注册新账号 一分钟内只允许获取一次验证码 发邮件功能 校验验证码 验证码超时展示 保存令牌数据库 令牌保存cookie中 跳转到欢迎页面 登录 生成Token保存到令牌表和Cookies中最后转到欢迎页面 未登录状态网关拦截 IP防暴刷过滤器:在1分钟内注册超过100次时返回错误信息
二、功能实现: 2.1 一分钟内只允许获取一次验证码

2.2 IP防暴刷过滤器 @Component @RefreshScope // 刷新配置信息 public class IpGlobalFilter implements GlobalFilter, Ordered { @Value("${filter.limit.ip.uri}") private String limitUri; @Value("${filter.limit.ip.maxtimes}") private int maxTimes; @Value("${filter.limit.ip.limitminutes}") private int limitMinutes; private static ConcurrentMap<String, List<Long>> ipCache = new ConcurrentHashMap<>(); public IpGlobalFilter() { System.out.println("IpGlobalFilter初始化"); } @Override public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { System.out.println(maxTimes); ServerHttpRequest request = exchange.getRequest(); ServerHttpResponse response = exchange.getResponse(); // 客户端IP String ip = request.getRemoteAddress().getHostString(); String path = request.getURI().getPath(); // 如果请求的服务未设限,直接放行 if (!path.startsWith(limitUri)) { return chain.filter(exchange); } // 设限服务把本次请求加入缓存 List<Long> currentIpCache = ipCache.get(ip); // 初始化当前ip请求记录 if (currentIpCache == null) { currentIpCache = new ArrayList<>(); ipCache.put(ip, currentIpCache); } currentIpCache.add(System.currentTimeMillis()); // 计算limitMinutes内访问次数是否超过maxTimes int count = 0; long startTime = System.currentTimeMillis() - (limitMinutes * 60 * 1000); for (Long reqTime : currentIpCache) { if (reqTime > startTime) { count++; } } if (count > maxTimes) { response.setStatusCode(HttpStatus.FORBIDDEN); String data = "您频繁进?注册,请求已被拒绝!"; DataBuffer wrap = response.bufferFactory().wrap(data.getBytes()); return response.writeWith(Mono.just(wrap)); } return chain.filter(exchange); } @Override public int getOrder() { return 0; } } 2.3 未登录状态网关拦截 @Component public class TokenGlobalFilter implements GlobalFilter, Ordered { @Autowired private UserFeignClient userFeignClient; /** * 进?token的验证,?户微服务和验证码微服务的请求不过滤(?关调?下游?户微服务的token验证接?) * @param exchange * @param chain * @return */ @Override public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { ServerHttpRequest request = exchange.getRequest(); ServerHttpResponse response = exchange.getResponse(); String path = request.getURI().getPath(); // 用户微服务和验证码微服务的请求不过滤 if (path.startsWith("/api/user") || path.startsWith("/api/code")) { return chain.filter(exchange); } // 获取Cookie,token不存在或者用户微服务查询不到重定向到登录页面 List<HttpCookie> cookies = request.getCookies().get("token"); if (!CollectionUtils.isEmpty(cookies)) { HttpCookie cookie = cookies.get(0); String token = cookie.getValue(); if (!"".equals(userFeignClient.info(token))) { return chain.filter(exchange); } } // 返回状态码 303,重定向到登录页面 response.getHeaders().set(HttpHeaders.LOCATION, "/static/login.html"); response.setStatusCode(HttpStatus.SEE_OTHER); return response.setComplete(); } @Override public int getOrder() { return 0; } } 2.4 网关配置 server: port: 9090 eureka: client: serviceUrl: defaultZone: http://dabingcloudeurekaservera:8761/eureka/,http://dabingcloudeurekaserverb:8762/eureka/ #把 eureka 集群中的所有 url 都填写了进来,也可以只写一台,因为各个 eureka server 可以同步注册表 instance: prefer-ip-address: true instance-id: ${spring.cloud.client.ip-address}:${spring.application.name}:${server.port}:@project.version@ spring: application: name: dabing-cloud-gateway cloud: gateway: routes: - id: service-code-router uri: lb://dabing-service-code predicates: - Path=/api/code/** filters: - StripPrefix=1 - id: service-user-router uri: lb://dabing-service-user predicates: - Path=/api/user/** filters: - StripPrefix=1 httpclient: connect-timeout: 5000 response-timeout: 20000 2.5 nginx配置 worker_processes 1; events { worker_connections 1024; } http { include mime.types; default_type application/octet-stream; sendfile on; keepalive_timeout 65; upstream myServer { server 127.0.0.1:9090; } server { listen 80; server_name localhost; location /static/ { root staticDatas; } location / { root html; index index.html index.htm; } location /api { proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header REMOTE-HOST $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_pass http://myServer; } error_page 500 502 503 504 /50x.html; location = /50x.html { root html; } } } 2.6 静态资源路径

注意:请求静态资源的时候,url地址栏需要使用配置的本地域名进行访问,否则报跨域问题

补充:不使用域名实现 跨域 1. 效果

2. 实现,在网关配置全局跨域

server: port: 9090 eureka: client: serviceUrl: defaultZone: http://dabingcloudeurekaservera:8761/eureka/,http://dabingcloudeurekaserverb:8762/eureka/ #把 eureka 集群中的所有 url 都填写了进来,也可以只写一台,因为各个 eureka server 可以同步注册表 instance: prefer-ip-address: true instance-id: ${spring.cloud.client.ip-address}:${spring.application.name}:${server.port}:@project.version@ spring: application: name: dabing-cloud-gateway cloud: gateway: #开启网关的跨域功能,具体微服务上的跨域需要进行关闭,否则无效 globalcors: cors-configurations: '[/**]': # 匹配所有请求 allowedOrigins: "*" #跨域处理 允许所有的域 allowedMethods: # 支持的方法 - GET - POST - PUT - DELETE routes: - id: service-code-router uri: lb://dabing-service-code predicates: - Path=/api/code/** filters: - StripPrefix=1 - id: service-user-router uri: lb://dabing-service-user predicates: - Path=/api/user/** filters: - StripPrefix=1 httpclient: connect-timeout: 5000 response-timeout: 20000 仓库地址:

demo仓库地址:https://gitee.com/lg_zk/dabing-user-parent-hw.git


1.本站遵循行业规范,任何转载的稿件都会明确标注作者和来源;2.本站的原创文章,会注明原创字样,如未注明都非原创,如有侵权请联系删除!;3.作者投稿可能会经我们编辑修改或补充;4.本站不提供任何储存功能只提供收集或者投稿人的网盘链接。

标签: #实现需基于Spring #Cloud