• SpringCloud微服务第2章


    目录

    一、前言

    1、API 网关

    2、API 网关的作用

    3、Zull网关与Gateway网关

    二、Gateway网关搭建

    1、在上一章基础test-cloud项目上面,右击File -> new -> Module,创建网关模块

    2、选择Maven点击Next

    3、输入ArtifactId,点击Next

    4、Module Name模块名称加上“-”,点击Finish完成创建

    5、网关模块pom.xml引入依赖

    6、网关配置文件

    6.1、在resources文件夹右击,New -> File,输入application.yml,注意有个spring绿色标志。

    6.2、配置内容如下

    7、AuthFilter全局权限过滤器

    7、CacheRequestFilter组件过滤器

    8、ImgCodeFilter验证码过滤器

    9、RateLimiterConfiguration路由限流配置

    10、在第1章基础上补充AuthApp模块的业务代码

    10.1、controller层

    10.2、service层

    10.3、读者自行补充common模块,参照之前创建模块的教程。

    11、验证

    11.1、启动注册中心EurekaApp,访问http://localhost:7001/

    11.2、启动AuthApp权限微服务,刷新http://localhost:7001/如下注册进来了

    11.3、启动GatewayApp, 刷新http://localhost:7001/如下注册进来了

    11.4、我们来看看网关的作用

    如果对你有帮助,就点赞、收藏、评论吧,不要白嫖人家啦

    前面后面都有惊喜,关注不迷路


    一、前言

    1、API 网关

        API Gateway 是一个服务器,也可以说是进入系统的唯一节点(插入个面试者的描述:面试官心想既然你了解微服务,那么想问你微服务的 入口 是什么,面试者假装沉思:注册中心、微服务、配置中心...然后以肯定的语速回答了--网关。面试官面无表情地只回了个“嗯”)。这跟面向对象设计模式中的 Facade 模式很像。API Gateway 封装内部系统的架构,并且提供 API 给各个客户端。它还可能有其他功能,如授权监控负载均衡缓存请求分片管理静态响应处理等。
        通俗一点地讲网关就像大学的门口,有门卫在那里坚守。如果你想进入园区,你得亮出你的学生证、绿色健康码、行程卡,门卫可能检查比对看看是否是本校学生。
        下图展示了一 个适应当前架构的 API Gateway。

    API Gateway 负责请求转发合成协议转换 ,就是做统一集中的非业务性工作。所有来自客户端的请求都要先经过 API Gateway, 然后 路由 这些请求对应的微服务。API Gateway 将经常通过调用多个微服务来处理一个请求以 及聚合多个服务的结果。它可以在 web 协议与内部使用的非 web 友好型协议间进行转换,如 HTTP 协议、WebSocket 协议。

    2、API 网关的作用

    • 请求转发:服务转发主要是对客户端的请求安装微服务的负载转发到不同的服务上
    • 响应合并:把业务上需要调用多个服务接口才能完成的工作合并成一次调用对外统一提供服务。
    • 协议转换:重点是支持 SOAP,JMS,Rest 间的协议转换。
    • ​​​​​​数据转换:重点是支持 XML 和 Json 之间的报文格式转换能力(可选)
    • 安全认证:
      1. 基于 Token 的客户端访问控制和安全策略
      2. 传输数据和报文加密,到服务端解密,需要在客户端有独立的 SDK 代理包
      3. 基于 Https 的传输加密,客户端和服务端数字证书支持
        基于 OAuth2.0 的服务安全认证(授权码,客户端,密码模式等)

    3、Zull网关与Gateway网关

    • Gateway是spring家族的一个子项目。而zuul则是netflix公司的项目,只是spring将zuul集成在spring-cloud中使用而已。
    • gateway对比zuul多依赖了spring-webflux,spring-webflux是spring5新出的模块,而它又是建立在Java8的基础上的。在spring的支持下,功能更强大,内部实现了限流、负载均衡等,扩展性也更强,但同时也限制了仅适合于Spring Cloud套件。zuul则可以扩展至其他微服务框架中,其内部没有实现限流、负载均衡等。
    • zuul1.0仅支持同步,在zuul2.0才支持同步,至于是否完善待确定。gateway支持异步,底层基于Netty,而Netty中使用非阻塞API,基于NIO 的。NIO是一种同步非阻塞的 I/O 模型,于 Java 1.4 中引入,对应 java.nio 包,提供了 Channel , Selector,Buffer 等抽象。gateway是spring家族的产物,其稳定性也好,一直在spring家族维护。故我们这里以Gateway网关作为介绍对象,下面我们来看看它到底是什么样的组件模块。

    二、Gateway网关搭建

    1、在上一章基础test-cloud项目上面,右击File -> new -> Module,创建网关模块

    2、选择Maven点击Next

    3、输入ArtifactId,点击Next

    4、Module Name模块名称加上“-”,点击Finish完成创建

                                                                      图4-1

                                                                    图4-2

    5、网关模块pom.xml引入依赖

    1. <?xml version="1.0" encoding="UTF-8"?>
    2. <project xmlns="http://maven.apache.org/POM/4.0.0"
    3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    4. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    5. <parent>
    6. <artifactId>test-cloud</artifactId>
    7. <groupId>com.ceam</groupId>
    8. <version>1.0-SNAPSHOT</version>
    9. </parent>
    10. <modelVersion>4.0.0</modelVersion>
    11. <artifactId>ceam-gateway</artifactId>
    12. <dependencies>
    13. <!--配置中心客户端 -->
    14. <dependency>
    15. <groupId>org.springframework.cloud</groupId>
    16. <artifactId>spring-cloud-starter-config</artifactId>
    17. </dependency>
    18. <!--gateway 网关依赖,内置webflux 依赖 -->
    19. <dependency>
    20. <groupId>org.springframework.cloud</groupId>
    21. <artifactId>spring-cloud-starter-gateway</artifactId>
    22. </dependency>
    23. <!--eureka 客户端 -->
    24. <dependency>
    25. <groupId>org.springframework.cloud</groupId>
    26. <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
    27. </dependency>
    28. <dependency>
    29. <groupId>org.springframework.cloud</groupId>
    30. <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
    31. </dependency>
    32. <dependency>
    33. <groupId>org.springframework.boot</groupId>
    34. <artifactId>spring-boot-starter-actuator</artifactId>
    35. </dependency>
    36. <!-- spring-boot-devtools -->
    37. <dependency>
    38. <groupId>org.springframework.boot</groupId>
    39. <artifactId>spring-boot-devtools</artifactId>
    40. <optional>true</optional> <!-- 表示依赖不会传递 -->
    41. </dependency>
    42. </dependencies>
    43. <build>
    44. <plugins>
    45. <plugin>
    46. <groupId>org.springframework.boot</groupId>
    47. <artifactId>spring-boot-maven-plugin</artifactId>
    48. <configuration>
    49. <fork>true</fork> <!-- 如果没有该配置,devtools不会生效 -->
    50. </configuration>
    51. <executions>
    52. <execution>
    53. <goals>
    54. <goal>repackage</goal>
    55. </goals>
    56. </execution>
    57. </executions>
    58. </plugin>
    59. </plugins>
    60. </build>
    61. </project>

    6、网关配置文件

    6.1、在resources文件夹右击,New -> File,输入application.yml,注意有个spring绿色标志。

    6.2、配置内容如下

    1. server:
    2. port: 9527 # 端口号,对外统一暴露
    3. spring:
    4. application:
    5. name: ceam-gateway
    6. devtools:
    7. restart:
    8. enabled: true # 热部署
    9. profiles:
    10. active: dev # 开发环境
    11. cloud:
    12. # 这是使用配置中心时,需指定的配置
    13. config:
    14. fail-fast: true
    15. name: ${spring.application.name} # 就是上面的ceam-gateway,这样写上面变更下面就跟着变了
    16. profile: ${spring.profiles.active} # 就是上面的开发环境dev
    17. discovery:
    18. enabled: true
    19. service-id: ceam-config
    20. gateway:
    21. discovery:
    22. locator:
    23. enabled: true
    24. routes:
    25. # 认证中心
    26. - id: ceam-auth
    27. uri: lb://ceam-auth
    28. # 断言,路径前缀,访问得加上,如:http://localhost:9527/auth/login
    29. predicates:
    30. - Path=/auth/**
    31. # 过滤器,是不是很熟悉
    32. filters:
    33. # 验证码处理
    34. - CacheRequest
    35. - ImgCodeFilter
    36. - StripPrefix=1
    37. # 限流配置
    38. - name: RequestRateLimiter
    39. args:
    40. key-resolver: '#{@remoteAddrKeyResolver}'
    41. #允许用户每秒处理多少个请求
    42. redis-rate-limiter.replenishRate: 2
    43. #令牌桶的容量,允许在一秒钟内完成的最大请求
    44. redis-rate-limiter.burstCapacity: 20
    45. eureka:
    46. client: #客户端注册进eureka服务列表内
    47. service-url:
    48. defaultZone: http://localhost:7001/eureka
    49. #defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/,http://eureka7003.com:7003/eureka/
    50. instance:
    51. instance-id: ${spring.application.name}:${server.port} # 即ceam-gateway:9527
    52. prefer-ip-address: true #访问路径可以显示IP地址

    :不同版本的配置可能会不同,在做开发的时候注意,读者可以看看官方文档或者源码。过滤器就像学校门口的门卫,不同过滤器职责不一样。这里配置文件配置的过滤器不是全局性的,它当当仅对ceam-auth模块使作用,那么全局性的过滤器是怎么样子的呢,我们接下来往下看。

    7、AuthFilter全局权限过滤器

    在filter文件夹创建AuthFilter过滤器,代码如下:

    1. package com.ceam.gateway.fiflt;
    2. import com.alibaba.fastjson.JSON;
    3. import com.alibaba.fastjson.JSONObject;
    4. import com.ceam.common.constant.Constants;
    5. import com.ceam.common.core.domain.R;
    6. import lombok.extern.slf4j.Slf4j;
    7. import org.apache.commons.lang3.StringUtils;
    8. import org.springframework.cloud.gateway.filter.GatewayFilterChain;
    9. import org.springframework.cloud.gateway.filter.GlobalFilter;
    10. import org.springframework.core.Ordered;
    11. import org.springframework.core.io.buffer.DataBuffer;
    12. import org.springframework.data.redis.core.ValueOperations;
    13. import org.springframework.http.HttpStatus;
    14. import org.springframework.http.server.reactive.ServerHttpRequest;
    15. import org.springframework.http.server.reactive.ServerHttpResponse;
    16. import org.springframework.stereotype.Component;
    17. import org.springframework.web.server.ServerWebExchange;
    18. import reactor.core.publisher.Flux;
    19. import reactor.core.publisher.Mono;
    20. import javax.annotation.Resource;
    21. import java.io.UnsupportedEncodingException;
    22. import java.util.Arrays;
    23. /**
    24. * 网关鉴权
    25. */
    26. @Slf4j
    27. @Component
    28. public class AuthFilter implements GlobalFilter, Ordered
    29. {
    30. // 白名单,排除过滤的 uri 地址
    31. private static final String[] whiteList = {"/auth/login", "/user/register", "/system/v2/api-docs",
    32. "/auth/captcha/check", "/auth/captcha/get","/auth/login/slide"};
    33. @Resource(name = "stringRedisTemplate")
    34. private ValueOperations<String, String> ops;
    35. @Override
    36. public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain)
    37. {
    38. String url = exchange.getRequest().getURI().getPath();
    39. log.info("url:{}", url);
    40. // 跳过不需要验证的路径
    41. if (Arrays.asList(whiteList).contains(url))
    42. {
    43. return chain.filter(exchange);
    44. }
    45. String token = exchange.getRequest().getHeaders().getFirst(Constants.TOKEN);
    46. // token为空
    47. if (StringUtils.isBlank(token))
    48. {
    49. return setUnauthorizedResponse(exchange, "token can't be null or empty string");
    50. }
    51. String userStr = ops.get(Constants.ACCESS_TOKEN + token);
    52. if (StringUtils.isBlank(userStr))
    53. {
    54. return setUnauthorizedResponse(exchange, "token verify error");
    55. }
    56. JSONObject jo = JSONObject.parseObject(userStr);
    57. String userId = jo.getString("userId");
    58. // 查询token信息
    59. if (StringUtils.isBlank(userId))
    60. {
    61. return setUnauthorizedResponse(exchange, "token verify error");
    62. }
    63. // 设置userId到request里,后续根据userId,获取用户信息
    64. ServerHttpRequest mutableReq = exchange.getRequest().mutate().header(Constants.CURRENT_ID, userId)
    65. .header(Constants.CURRENT_USERNAME, jo.getString("loginName")).build();
    66. ServerWebExchange mutableExchange = exchange.mutate().request(mutableReq).build();
    67. return chain.filter(mutableExchange);
    68. }
    69. private Mono<Void> setUnauthorizedResponse(ServerWebExchange exchange, String msg)
    70. {
    71. ServerHttpResponse originalResponse = exchange.getResponse();
    72. originalResponse.setStatusCode(HttpStatus.UNAUTHORIZED);
    73. originalResponse.getHeaders().add("Content-Type", "application/json;charset=UTF-8");
    74. byte[] response = null;
    75. try
    76. {
    77. response = JSON.toJSONString(R.error(401, msg)).getBytes(Constants.UTF8);
    78. }
    79. catch (UnsupportedEncodingException e)
    80. {
    81. e.printStackTrace();
    82. }
    83. DataBuffer buffer = originalResponse.bufferFactory().wrap(response);
    84. return originalResponse.writeWith(Flux.just(buffer));
    85. }
    86. @Override
    87. public int getOrder()
    88. {
    89. return -100;
    90. }
    91. }

    import org.springframework.cloud.gateway.filter.GlobalFilter;从这导入语句中可看出GlobalFilter是gateway下过滤器模块的一个重要组件,它是一个全局性的过滤器,真实开发场景也会用它做一些逻辑的处理(都在前言那里罗列了)。自定义的过滤器实现该GlobalFilter接口,在filter的方法中去处理过滤的逻辑。但是有一个问题,如果你定义的过滤器多了,而且你想让它们有一定的顺序去执行,那怎么办呢?于是Ordered接口就出现了,顾名思义它就是指定顺序的,所以getOrder()方法就是用来指定过滤器执行顺序的,值越小就越先执行。

    7、CacheRequestFilter组件过滤器

    在filter文件夹创建CacheRequestFilter过滤器,上面配置文件提到了,代码如下:

    1. @Component
    2. public class CacheRequestFilter extends AbstractGatewayFilterFactory<CacheRequestFilter.Config>
    3. {
    4. public CacheRequestFilter()
    5. {
    6. super(Config.class);
    7. }
    8. @Override
    9. public String name()
    10. {
    11. return "CacheRequest";
    12. }
    13. @Override
    14. public GatewayFilter apply(Config config)
    15. {
    16. CacheRequestGatewayFilter cacheRequestGatewayFilter = new CacheRequestGatewayFilter();
    17. Integer order = config.getOrder();
    18. if (order == null)
    19. {
    20. return cacheRequestGatewayFilter;
    21. }
    22. return new OrderedGatewayFilter(cacheRequestGatewayFilter, order);
    23. }
    24. public static class CacheRequestGatewayFilter implements GatewayFilter
    25. {
    26. @Override
    27. public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain)
    28. {
    29. // GET DELETE 不过滤
    30. HttpMethod method = exchange.getRequest().getMethod();
    31. if (method == null || method.matches("GET") || method.matches("DELETE"))
    32. {
    33. return chain.filter(exchange);
    34. }
    35. return DataBufferUtils.join(exchange.getRequest().getBody()).map(dataBuffer -> {
    36. byte[] bytes = new byte[dataBuffer.readableByteCount()];
    37. dataBuffer.read(bytes);
    38. DataBufferUtils.release(dataBuffer);
    39. return bytes;
    40. }).defaultIfEmpty(new byte[0]).flatMap(bytes -> {
    41. DataBufferFactory dataBufferFactory = exchange.getResponse().bufferFactory();
    42. ServerHttpRequestDecorator decorator = new ServerHttpRequestDecorator(exchange.getRequest())
    43. {
    44. @Override
    45. public Flux<DataBuffer> getBody()
    46. {
    47. if (bytes.length > 0)
    48. {
    49. return Flux.just(dataBufferFactory.wrap(bytes));
    50. }
    51. return Flux.empty();
    52. }
    53. };
    54. return chain.filter(exchange.mutate().request(decorator).build());
    55. });
    56. }
    57. }
    58. @Override
    59. public List<String> shortcutFieldOrder()
    60. {
    61. return Collections.singletonList("order");
    62. }
    63. @Data
    64. static class Config
    65. {
    66. private Integer order;
    67. }
    68. }

    8、ImgCodeFilter验证码过滤器

    在filter文件夹创建ImgCodeFilter过滤器,上面配置文件提到了,代码如下:

    1. /**
    2. * 验证码处理
    3. */
    4. @Component
    5. public class ImgCodeFilter extends AbstractGatewayFilterFactory<ImgCodeFilter.Config>
    6. {
    7. private final static String AUTH_URL = "/auth/login";
    8. @Autowired
    9. private StringRedisTemplate redisTemplate;
    10. public ImgCodeFilter()
    11. {
    12. super(Config.class);
    13. }
    14. @Override
    15. public GatewayFilter apply(Config config)
    16. {
    17. return (exchange, chain) -> {
    18. ServerHttpRequest request = exchange.getRequest();
    19. URI uri = request.getURI();
    20. // 不是登录请求,直接向下执行
    21. //if (!StringUtils.containsIgnoreCase(uri.getPath(), AUTH_URL))
    22. if (!AUTH_URL.equalsIgnoreCase(uri.getPath()))
    23. {
    24. return chain.filter(exchange);
    25. }
    26. try
    27. {
    28. String bodyStr = resolveBodyFromRequest(request);
    29. JSONObject bodyJson=JSONObject.parseObject(bodyStr);
    30. String code = (String) bodyJson.get("captcha");
    31. String randomStr = (String) bodyJson.get("randomStr");
    32. // 校验验证码
    33. //checkCode(code, randomStr);
    34. }
    35. catch (Exception e)
    36. {
    37. ServerHttpResponse response = exchange.getResponse();
    38. response.setStatusCode(HttpStatus.INTERNAL_SERVER_ERROR);
    39. response.getHeaders().add("Content-Type", "application/json;charset=UTF-8");
    40. String msg = JSON.toJSONString(R.error(e.getMessage()));
    41. DataBuffer bodyDataBuffer = response.bufferFactory().wrap(msg.getBytes());
    42. return response.writeWith(Mono.just(bodyDataBuffer));
    43. }
    44. return chain.filter(exchange);
    45. };
    46. }
    47. private String resolveBodyFromRequest(ServerHttpRequest serverHttpRequest)
    48. {
    49. // 获取请求体
    50. Flux<DataBuffer> body = serverHttpRequest.getBody();
    51. AtomicReference<String> bodyRef = new AtomicReference<>();
    52. body.subscribe(buffer -> {
    53. CharBuffer charBuffer = StandardCharsets.UTF_8.decode(buffer.asByteBuffer());
    54. DataBufferUtils.release(buffer);
    55. bodyRef.set(charBuffer.toString());
    56. });
    57. return bodyRef.get();
    58. }
    59. /**
    60. * 检查code
    61. */
    62. @SneakyThrows
    63. private void checkCode(String code, String randomStr)
    64. {
    65. if (StringUtils.isBlank(code))
    66. {
    67. throw new ValidateCodeException("验证码不能为空");
    68. }
    69. if (StringUtils.isBlank(randomStr))
    70. {
    71. throw new ValidateCodeException("验证码不合法");
    72. }
    73. String key = Constants.DEFAULT_CODE_KEY + randomStr;
    74. String saveCode = redisTemplate.opsForValue().get(key);
    75. redisTemplate.delete(key);
    76. if (!code.equalsIgnoreCase(saveCode))
    77. {
    78. throw new ValidateCodeException("验证码不合法");
    79. }
    80. }
    81. public static class Config
    82. {
    83. }
    84. }

    9、RateLimiterConfiguration路由限流配置

    在配置文件中还提及了限流配置,在config文件夹创建RateLimiterConfiguration,代码如下:

    1. /**
    2. * 路由限流配置
    3. */
    4. @Configuration
    5. public class RateLimiterConfiguration
    6. {
    7. @Bean(value = "remoteAddrKeyResolver")
    8. public KeyResolver remoteAddrKeyResolver()
    9. {
    10. return exchange -> Mono.just(exchange.getRequest().getRemoteAddress().getAddress().getHostAddress());
    11. }
    12. }

    10、在第1章基础上补充AuthApp模块的业务代码

    10.1、controller层

    1. import com.anji.captcha.model.common.ResponseModel;
    2. import com.anji.captcha.service.CaptchaService;
    3. import com.ceam.auth.form.LoginForm;
    4. import com.ceam.auth.service.AccessTokenService;
    5. import com.ceam.auth.service.SysLoginService;
    6. import com.ceam.common.core.domain.R;
    7. import com.ceam.system.domain.SysUser;
    8. import org.springframework.beans.factory.annotation.Autowired;
    9. import org.springframework.web.bind.annotation.PostMapping;
    10. import org.springframework.web.bind.annotation.RequestBody;
    11. import org.springframework.web.bind.annotation.RestController;
    12. import javax.servlet.http.HttpServletRequest;
    13. @RestController
    14. public class TokenController
    15. {
    16. @Autowired
    17. private AccessTokenService tokenService;
    18. @Autowired
    19. private SysLoginService sysLoginService;
    20. @Autowired
    21. private CaptchaService captchaService;
    22. @PostMapping("login")
    23. public R login(@RequestBody LoginForm form)
    24. {
    25. // 用户登录
    26. SysUser user = sysLoginService.login(form.getUsername(), form.getPassword());
    27. // 获取登录token
    28. return R.ok(tokenService.createToken(user));
    29. }
    30. @PostMapping("login/slide")
    31. public R loginSilde(@RequestBody LoginForm form)
    32. {
    33. ResponseModel response = captchaService.verification(form.getCaptchaVO());
    34. if (response.isSuccess())
    35. {
    36. // 用户登录
    37. SysUser user = sysLoginService.login(form.getUsername(), form.getPassword());
    38. // 获取登录token
    39. return R.ok(tokenService.createToken(user));
    40. }
    41. return R.error().put("repCode", response.getRepCode());
    42. }
    43. @PostMapping("logout")
    44. public R logout(HttpServletRequest request)
    45. {
    46. String token=request.getHeader("token");
    47. SysUser user=tokenService.queryByToken(token);
    48. if (null != user)
    49. {
    50. sysLoginService.logout(user.getLoginName());
    51. tokenService.expireToken(user.getUserId());
    52. }
    53. return R.ok();
    54. }
    55. }

    10.2、service层

    1)AccessTokenService

    1. import cn.hutool.core.util.IdUtil;
    2. import com.ceam.common.constant.Constants;
    3. import com.ceam.common.redis.annotation.RedisEvict;
    4. import com.ceam.common.redis.util.RedisUtils;
    5. import com.ceam.system.domain.SysUser;
    6. import org.apache.commons.lang3.StringUtils;
    7. import org.springframework.beans.factory.annotation.Autowired;
    8. import org.springframework.stereotype.Service;
    9. import java.util.HashMap;
    10. import java.util.Map;
    11. @Service("accessTokenService")
    12. public class AccessTokenService
    13. {
    14. @Autowired
    15. private RedisUtils redis;
    16. /**
    17. * 12小时后过期
    18. */
    19. private final static long EXPIRE = 12 * 60 * 60;
    20. private final static String ACCESS_TOKEN = Constants.ACCESS_TOKEN;
    21. private final static String ACCESS_USERID = Constants.ACCESS_USERID;
    22. public SysUser queryByToken(String token)
    23. {
    24. return redis.get(ACCESS_TOKEN + token, SysUser.class);
    25. }
    26. @RedisEvict(key = "user_perms", fieldKey = "#sysUser.userId")
    27. public Map<String, Object> createToken(SysUser sysUser)
    28. {
    29. // 生成token
    30. String token = IdUtil.fastSimpleUUID();
    31. // 保存或更新用户token
    32. Map<String, Object> map = new HashMap<String, Object>();
    33. map.put("userId", sysUser.getUserId());
    34. map.put("token", token);
    35. map.put("expire", EXPIRE);
    36. // expireToken(userId);
    37. redis.set(ACCESS_TOKEN + token, sysUser, EXPIRE);
    38. redis.set(ACCESS_USERID + sysUser.getUserId(), token, EXPIRE);
    39. return map;
    40. }
    41. public void expireToken(long userId)
    42. {
    43. String token = redis.get(ACCESS_USERID + userId);
    44. if (StringUtils.isNotBlank(token))
    45. {
    46. redis.delete(ACCESS_USERID + userId);
    47. redis.delete(ACCESS_TOKEN + token);
    48. }
    49. }
    50. }

    2)CaptchaCacheServiceRedisImpl

    1. import com.anji.captcha.service.CaptchaCacheService;
    2. import org.springframework.beans.factory.annotation.Autowired;
    3. import org.springframework.data.redis.core.StringRedisTemplate;
    4. import org.springframework.stereotype.Service;
    5. import java.util.concurrent.TimeUnit;
    6. /**
    7. * <p>File:CaptchaCacheServiceRedisImpl.java</p>
    8. * <p>Title: 使用redis缓存</p>
    9. * <p>Description:对于分布式部署的应用,我们建议应用自己实现CaptchaCacheService,比如用Redis,如果应用是单点的,也没有使用redis,那默认使用内存。</p>
    10. */
    11. @Service
    12. public class CaptchaCacheServiceRedisImpl implements CaptchaCacheService
    13. {
    14. @Override
    15. public String type() {
    16. return "redis";
    17. }
    18. @Autowired
    19. private StringRedisTemplate stringRedisTemplate;
    20. @Override
    21. public void set(String key, String value, long expiresInSeconds)
    22. {
    23. stringRedisTemplate.opsForValue().set(key, value, expiresInSeconds, TimeUnit.SECONDS);
    24. }
    25. @Override
    26. public boolean exists(String key)
    27. {
    28. return stringRedisTemplate.hasKey(key);
    29. }
    30. @Override
    31. public void delete(String key)
    32. {
    33. stringRedisTemplate.delete(key);
    34. }
    35. @Override
    36. public String get(String key)
    37. {
    38. return stringRedisTemplate.opsForValue().get(key);
    39. }
    40. }

    3)SysLoginService登录服务

    这里的逻辑简单处理下,验证网关进入到这里

    这里RemoteUserService是feign的调用,在后面的章节再介绍,读者写点简单逻辑看看是否可以通过网关Gateway路由到权限服务的处理逻辑。比如就一条System输出语句等等。

    1. import com.ceam.common.constant.Constants;
    2. import com.ceam.common.constant.UserConstants;
    3. import com.ceam.common.enums.UserStatus;
    4. import com.ceam.common.exception.user.UserBlockedException;
    5. import com.ceam.common.exception.user.UserDeleteException;
    6. import com.ceam.common.exception.user.UserNotExistsException;
    7. import com.ceam.common.exception.user.UserPasswordNotMatchException;
    8. import com.ceam.common.log.publish.PublishFactory;
    9. import com.ceam.common.utils.DateUtils;
    10. import com.ceam.common.utils.IpUtils;
    11. import com.ceam.common.utils.MessageUtils;
    12. import com.ceam.common.utils.ServletUtils;
    13. import com.ceam.system.domain.SysUser;
    14. import com.ceam.system.feign.RemoteUserService;
    15. import com.ceam.system.util.PasswordUtil;
    16. import org.apache.commons.lang3.StringUtils;
    17. import org.springframework.beans.factory.annotation.Autowired;
    18. import org.springframework.stereotype.Component;
    19. @Component
    20. public class SysLoginService
    21. {
    22. @Autowired
    23. private RemoteUserService userService;
    24. /**
    25. * 登录
    26. */
    27. public SysUser login(String username, String password)
    28. {
    29. // 验证码校验
    30. // if
    31. // (!StringUtils.isEmpty(ServletUtils.getRequest().getAttribute(ShiroConstants.CURRENT_CAPTCHA)))
    32. // {
    33. // AsyncManager.me().execute(AsyncFactory.recordLogininfor(username,
    34. // Constants.LOGIN_FAIL,
    35. // MessageUtils.message("user.jcaptcha.error")));
    36. // throw new CaptchaException();
    37. // }
    38. // 用户名或密码为空 错误
    39. if (StringUtils.isAnyBlank(username, password))
    40. {
    41. PublishFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("not.null"));
    42. throw new UserNotExistsException();
    43. }
    44. // 密码如果不在指定范围内 错误
    45. if (password.length() < UserConstants.PASSWORD_MIN_LENGTH
    46. || password.length() > UserConstants.PASSWORD_MAX_LENGTH)
    47. {
    48. PublishFactory.recordLogininfor(username, Constants.LOGIN_FAIL,
    49. MessageUtils.message("user.password.not.match"));
    50. throw new UserPasswordNotMatchException();
    51. }
    52. // 用户名不在指定范围内 错误
    53. if (username.length() < UserConstants.USERNAME_MIN_LENGTH
    54. || username.length() > UserConstants.USERNAME_MAX_LENGTH)
    55. {
    56. PublishFactory.recordLogininfor(username, Constants.LOGIN_FAIL,
    57. MessageUtils.message("user.password.not.match"));
    58. throw new UserPasswordNotMatchException();
    59. }
    60. // 查询用户信息
    61. SysUser user = userService.selectSysUserByUsername(username);
    62. // if (user == null && maybeMobilePhoneNumber(username))
    63. // {
    64. // user = userService.selectUserByPhoneNumber(username);
    65. // }
    66. // if (user == null && maybeEmail(username))
    67. // {
    68. // user = userService.selectUserByEmail(username);
    69. // }
    70. if (user == null)
    71. {
    72. PublishFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.not.exists"));
    73. throw new UserNotExistsException();
    74. }
    75. if (UserStatus.DELETED.getCode().equals(user.getDelFlag()))
    76. {
    77. PublishFactory.recordLogininfor(username, Constants.LOGIN_FAIL,
    78. MessageUtils.message("user.password.delete"));
    79. throw new UserDeleteException();
    80. }
    81. if (UserStatus.DISABLE.getCode().equals(user.getStatus()))
    82. {
    83. PublishFactory.recordLogininfor(username, Constants.LOGIN_FAIL,
    84. MessageUtils.message("user.blocked", user.getRemark()));
    85. throw new UserBlockedException();
    86. }
    87. if (!PasswordUtil.matches(user, password))
    88. {
    89. throw new UserPasswordNotMatchException();
    90. }
    91. PublishFactory.recordLogininfor(username, Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success"));
    92. recordLoginInfo(user);
    93. return user;
    94. }
    95. // private boolean maybeEmail(String username)
    96. // {
    97. // if (!username.matches(UserConstants.EMAIL_PATTERN))
    98. // {
    99. // return false;
    100. // }
    101. // return true;
    102. // }
    103. //
    104. // private boolean maybeMobilePhoneNumber(String username)
    105. // {
    106. // if (!username.matches(UserConstants.MOBILE_PHONE_NUMBER_PATTERN))
    107. // {
    108. // return false;
    109. // }
    110. // return true;
    111. // }
    112. /**
    113. * 记录登录信息
    114. */
    115. public void recordLoginInfo(SysUser user)
    116. {
    117. user.setLoginIp(IpUtils.getIpAddr(ServletUtils.getRequest()));
    118. user.setLoginDate(DateUtils.getNowDate());
    119. userService.updateUserLoginRecord(user);
    120. }
    121. public void logout(String loginName)
    122. {
    123. PublishFactory.recordLogininfor(loginName, Constants.LOGOUT, MessageUtils.message("user.logout.success"));
    124. }
    125. }

    实际开发中建议面向接口编程,这里是方便就没有通过实现接口形式。

    4)请求体字段

    1. import com.anji.captcha.model.vo.CaptchaVO;
    2. import lombok.Data;
    3. @Data
    4. public class LoginForm
    5. {
    6. private String username;
    7. private String password;
    8. // 滑块验证码二次验证参数
    9. private CaptchaVO captchaVO;
    10. }

    10.3、读者自行补充common模块,参照之前创建模块的教程。

    common模块一般封装一些工具类、常量、枚举等以便复用以及维护等。

    1)Constants通用常量信息

    1. package com.ceam.common.constant;
    2. /**
    3. * 通用常量信息
    4. */
    5. public class Constants
    6. {
    7. /**
    8. * UTF-8 字符集
    9. */
    10. public static final String UTF8 = "UTF-8";
    11. /**
    12. * 通用成功标识
    13. */
    14. public static final String SUCCESS = "0";
    15. /**
    16. * 通用失败标识
    17. */
    18. public static final String FAIL = "1";
    19. /**
    20. * 登录成功
    21. */
    22. public static final String LOGIN_SUCCESS = "Success";
    23. /**
    24. * 注销
    25. */
    26. public static final String LOGOUT = "Logout";
    27. /**
    28. * 登录失败
    29. */
    30. public static final String LOGIN_FAIL = "Error";
    31. /**
    32. * 自动去除表前缀
    33. */
    34. public static final String AUTO_REOMVE_PRE = "true";
    35. /**
    36. * 当前记录起始索引
    37. */
    38. public static final String PAGE_NUM = "pageNum";
    39. /**
    40. * 每页显示记录数
    41. */
    42. public static final String PAGE_SIZE = "pageSize";
    43. /**
    44. * 排序列
    45. */
    46. public static final String ORDER_BY_COLUMN = "sortField";
    47. /**
    48. * 排序的方向 "desc" 或者 "asc".
    49. */
    50. public static final String IS_ASC = "sortOrder";
    51. public static final String CURRENT_ID = "current_id";
    52. public static final String CURRENT_USERNAME = "current_username";
    53. public static final String TOKEN = "token";
    54. public static final String DEFAULT_CODE_KEY = "random_code_";
    55. public final static String ACCESS_TOKEN = "access_token_";
    56. public final static String ACCESS_USERID = "access_userid_";
    57. public static final String RESOURCE_PREFIX = "/profile";
    58. }

    2)RedisEvict注解

    1. package com.ceam.common.redis.annotation;
    2. import java.lang.annotation.*;
    3. /**
    4. * <p>File:RedisEvict.java</p>
    5. * <p>Title: redis删除注解</p>
    6. * <p>Description:</p>
    7. */
    8. @Target({ElementType.METHOD})
    9. @Retention(RetentionPolicy.RUNTIME)
    10. @Documented
    11. public @interface RedisEvict
    12. {
    13. String key();
    14. String fieldKey();
    15. }

    3)RedisUtils工具类

    1. package com.ceam.common.redis.util;
    2. import com.alibaba.fastjson.JSON;
    3. import org.springframework.beans.factory.annotation.Autowired;
    4. import org.springframework.context.annotation.ComponentScan;
    5. import org.springframework.data.redis.core.RedisTemplate;
    6. import org.springframework.data.redis.core.ValueOperations;
    7. import org.springframework.stereotype.Component;
    8. import javax.annotation.Resource;
    9. import java.util.concurrent.TimeUnit;
    10. /**
    11. * Redis工具类
    12. */
    13. @Component
    14. @ComponentScan(basePackages = {"com.ceam.common.redis"})
    15. public class RedisUtils
    16. {
    17. @Autowired
    18. private RedisTemplate<String, Object> redisTemplate;
    19. @Resource(name = "stringRedisTemplate")
    20. private ValueOperations<String, String> valueOperations;
    21. /** 默认过期时长,单位:秒 */
    22. public final static long DEFAULT_EXPIRE = 60 * 60 * 24;
    23. /** 不设置过期时长 */
    24. public final static long NOT_EXPIRE = -1;
    25. /**
    26. * 插入缓存默认时间
    27. * @param key 键
    28. * @param value 值
    29. */
    30. public void set(String key, Object value)
    31. {
    32. set(key, value, DEFAULT_EXPIRE);
    33. }
    34. /**
    35. * 插入缓存
    36. * @param key 键
    37. * @param value 值
    38. * @param expire 过期时间(s)
    39. */
    40. public void set(String key, Object value, long expire)
    41. {
    42. valueOperations.set(key, toJson(value));
    43. redisTemplate.expire(key, expire, TimeUnit.SECONDS);
    44. }
    45. /**
    46. * 返回字符串结果
    47. * @param key 键
    48. * @return
    49. */
    50. public String get(String key)
    51. {
    52. return valueOperations.get(key);
    53. }
    54. /**
    55. * 返回指定类型结果
    56. * @param key 键
    57. * @param clazz 类型class
    58. * @return
    59. */
    60. public <T> T get(String key, Class<T> clazz)
    61. {
    62. String value = valueOperations.get(key);
    63. return value == null ? null : fromJson(value, clazz);
    64. }
    65. /**
    66. * 删除缓存
    67. * @param key 键
    68. */
    69. public void delete(String key)
    70. {
    71. redisTemplate.delete(key);
    72. }
    73. /**
    74. * Object转成JSON数据
    75. */
    76. private String toJson(Object object)
    77. {
    78. if (object instanceof Integer || object instanceof Long || object instanceof Float || object instanceof Double
    79. || object instanceof Boolean || object instanceof String)
    80. {
    81. return String.valueOf(object);
    82. }
    83. return JSON.toJSONString(object);
    84. }
    85. /**
    86. * JSON数据,转成Object
    87. */
    88. private <T> T fromJson(String json, Class<T> clazz)
    89. {
    90. return JSON.parseObject(json, clazz);
    91. }
    92. }

    4)UserConstants用户常量信息

    1. package com.ceam.common.constant;
    2. /**
    3. * 用户常量信息
    4. */
    5. public class UserConstants
    6. {
    7. /**
    8. * 平台内系统用户的唯一标志
    9. */
    10. public static final String SYS_USER = "SYS_USER";
    11. /** 正常状态 */
    12. public static final String NORMAL = "0";
    13. /** 异常状态 */
    14. public static final String EXCEPTION = "1";
    15. /** 用户封禁状态 */
    16. public static final String USER_BLOCKED = "1";
    17. /** 角色封禁状态 */
    18. public static final String ROLE_BLOCKED = "1";
    19. /** 部门正常状态 */
    20. public static final String DEPT_NORMAL = "0";
    21. /**
    22. * 用户名长度限制
    23. */
    24. public static final int USERNAME_MIN_LENGTH = 2;
    25. public static final int USERNAME_MAX_LENGTH = 20;
    26. /** 登录名称是否唯一的返回结果码 */
    27. public final static String USER_NAME_UNIQUE = "0";
    28. public final static String USER_NAME_NOT_UNIQUE = "1";
    29. /** 手机号码是否唯一的返回结果 */
    30. public final static String USER_PHONE_UNIQUE = "0";
    31. public final static String USER_PHONE_NOT_UNIQUE = "1";
    32. /** e-mail 是否唯一的返回结果 */
    33. public final static String USER_EMAIL_UNIQUE = "0";
    34. public final static String USER_EMAIL_NOT_UNIQUE = "1";
    35. /** 部门名称是否唯一的返回结果码 */
    36. public final static String DEPT_NAME_UNIQUE = "0";
    37. public final static String DEPT_NAME_NOT_UNIQUE = "1";
    38. /** 角色名称是否唯一的返回结果码 */
    39. public final static String ROLE_NAME_UNIQUE = "0";
    40. public final static String ROLE_NAME_NOT_UNIQUE = "1";
    41. /** 岗位名称是否唯一的返回结果码 */
    42. public final static String POST_NAME_UNIQUE = "0";
    43. public final static String POST_NAME_NOT_UNIQUE = "1";
    44. /** 角色权限是否唯一的返回结果码 */
    45. public final static String ROLE_KEY_UNIQUE = "0";
    46. public final static String ROLE_KEY_NOT_UNIQUE = "1";
    47. /** 岗位编码是否唯一的返回结果码 */
    48. public final static String POST_CODE_UNIQUE = "0";
    49. public final static String POST_CODE_NOT_UNIQUE = "1";
    50. /** 菜单名称是否唯一的返回结果码 */
    51. public final static String MENU_NAME_UNIQUE = "0";
    52. public final static String MENU_NAME_NOT_UNIQUE = "1";
    53. /** 字典类型是否唯一的返回结果码 */
    54. public final static String DICT_TYPE_UNIQUE = "0";
    55. public final static String DICT_TYPE_NOT_UNIQUE = "1";
    56. /** 参数键名是否唯一的返回结果码 */
    57. public final static String CONFIG_KEY_UNIQUE = "0";
    58. public final static String CONFIG_KEY_NOT_UNIQUE = "1";
    59. /**
    60. * 密码长度限制
    61. */
    62. public static final int PASSWORD_MIN_LENGTH = 5;
    63. public static final int PASSWORD_MAX_LENGTH = 20;
    64. /**
    65. * 手机号码格式限制
    66. */
    67. public static final String MOBILE_PHONE_NUMBER_PATTERN = "^0{0,1}(13[0-9]|15[0-9]|14[0-9]|18[0-9])[0-9]{8}$";
    68. /**
    69. * 邮箱格式限制
    70. */
    71. public static final String EMAIL_PATTERN = "^((([a-z]|\\d|[!#\\$%&'\\*\\+\\-\\/=\\?\\^_`{\\|}~]|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])+(\\.([a-z]|\\d|[!#\\$%&'\\*\\+\\-\\/=\\?\\^_`{\\|}~]|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])+)*)|((\\x22)((((\\x20|\\x09)*(\\x0d\\x0a))?(\\x20|\\x09)+)?(([\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x7f]|\\x21|[\\x23-\\x5b]|[\\x5d-\\x7e]|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])|(\\\\([\\x01-\\x09\\x0b\\x0c\\x0d-\\x7f]|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF]))))*(((\\x20|\\x09)*(\\x0d\\x0a))?(\\x20|\\x09)+)?(\\x22)))@((([a-z]|\\d|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])|(([a-z]|\\d|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])([a-z]|\\d|-|\\.|_|~|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])*([a-z]|\\d|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])))\\.)+(([a-z]|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])|(([a-z]|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])([a-z]|\\d|-|\\.|_|~|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])*([a-z]|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])))\\.?";
    72. }

    5)UserStatus用户状态

    1. package com.ceam.common.enums;
    2. /**
    3. * 用户状态
    4. */
    5. public enum UserStatus
    6. {
    7. OK("0", "正常"), DISABLE("1", "停用"), DELETED("2", "删除");
    8. private final String code;
    9. private final String info;
    10. UserStatus(String code, String info)
    11. {
    12. this.code = code;
    13. this.info = info;
    14. }
    15. public String getCode()
    16. {
    17. return code;
    18. }
    19. public String getInfo()
    20. {
    21. return info;
    22. }
    23. }

    6)UserBlockedException用户锁定异常类

    1. package com.ceam.common.exception.user;
    2. /**
    3. * 用户锁定异常类
    4. */
    5. public class UserBlockedException extends UserException
    6. {
    7. private static final long serialVersionUID = 1L;
    8. public UserBlockedException()
    9. {
    10. super("user.blocked", null);
    11. }
    12. }

    7)UserDeleteException用户账号已被删除

    1. package com.ceam.common.exception.user;
    2. /**
    3. * 用户账号已被删除
    4. */
    5. public class UserDeleteException extends UserException
    6. {
    7. private static final long serialVersionUID = 1L;
    8. public UserDeleteException()
    9. {
    10. super("user.password.delete", null);
    11. }
    12. }

    8)UserNotExistsException用户不存在异常类

    1. package com.ceam.common.exception.user;
    2. /**
    3. * 用户不存在异常类
    4. */
    5. public class UserNotExistsException extends UserException
    6. {
    7. private static final long serialVersionUID = 1L;
    8. public UserNotExistsException()
    9. {
    10. super("user.not.exists", null);
    11. }
    12. }

    9)UserPasswordNotMatchException用户密码不正确或不符合规范异常类

    1. package com.ceam.common.exception.user;
    2. /**
    3. * 用户密码不正确或不符合规范异常类
    4. */
    5. public class UserPasswordNotMatchException extends UserException
    6. {
    7. private static final long serialVersionUID = 1L;
    8. public UserPasswordNotMatchException()
    9. {
    10. super("user.password.not.match", null);
    11. }
    12. }

    10)PublishFactory

    1. package com.ceam.common.log.publish;
    2. import com.ceam.common.constant.Constants;
    3. import com.ceam.common.log.event.SysLogininforEvent;
    4. import com.ceam.common.utils.AddressUtils;
    5. import com.ceam.common.utils.IpUtils;
    6. import com.ceam.common.utils.ServletUtils;
    7. import com.ceam.common.utils.spring.SpringContextHolder;
    8. import com.ceam.system.domain.SysLogininfor;
    9. import eu.bitwalker.useragentutils.UserAgent;
    10. import javax.servlet.http.HttpServletRequest;
    11. public class PublishFactory
    12. {
    13. /**
    14. * 记录登陆信息
    15. *
    16. * @param username 用户名
    17. * @param status 状态
    18. * @param message 消息
    19. * @param args 列表
    20. */
    21. public static void recordLogininfor(final String username, final String status, final String message,
    22. final Object ... args)
    23. {
    24. HttpServletRequest request = ServletUtils.getRequest();
    25. final UserAgent userAgent = UserAgent.parseUserAgentString(request.getHeader("User-Agent"));
    26. final String ip = IpUtils.getIpAddr(request);
    27. // 获取客户端操作系统
    28. String os = userAgent.getOperatingSystem().getName();
    29. // 获取客户端浏览器
    30. String browser = userAgent.getBrowser().getName();
    31. // 封装对象
    32. SysLogininfor logininfor = new SysLogininfor();
    33. logininfor.setLoginName(username);
    34. logininfor.setIpaddr(ip);
    35. logininfor.setLoginLocation(AddressUtils.getRealAddressByIP(ip));
    36. logininfor.setBrowser(browser);
    37. logininfor.setOs(os);
    38. logininfor.setMsg(message);
    39. // 日志状态
    40. if (Constants.LOGIN_SUCCESS.equals(status) || Constants.LOGOUT.equals(status))
    41. {
    42. logininfor.setStatus(Constants.SUCCESS);
    43. }
    44. else if (Constants.LOGIN_FAIL.equals(status))
    45. {
    46. logininfor.setStatus(Constants.FAIL);
    47. }
    48. // 发布事件
    49. SpringContextHolder.publishEvent(new SysLogininforEvent(logininfor));
    50. }
    51. }

    11)DateUtils 时间工具类

    1. package com.ceam.common.utils;
    2. import org.apache.commons.lang3.time.DateFormatUtils;
    3. import java.lang.management.ManagementFactory;
    4. import java.text.ParseException;
    5. import java.text.SimpleDateFormat;
    6. import java.util.Date;
    7. /**
    8. * 时间工具类
    9. */
    10. public class DateUtils extends org.apache.commons.lang3.time.DateUtils
    11. {
    12. public static String YYYY = "yyyy";
    13. public static String YYYY_MM = "yyyy-MM";
    14. public static String YYYY_MM_DD = "yyyy-MM-dd";
    15. public static String YYYYMMDDHHMMSS = "yyyyMMddHHmmss";
    16. public static String YYYY_MM_DD_HH_MM_SS = "yyyy-MM-dd HH:mm:ss";
    17. private static String[] parsePatterns = {
    18. "yyyy-MM-dd", "yyyy-MM-dd HH:mm:ss", "yyyy-MM-dd HH:mm", "yyyy-MM",
    19. "yyyy/MM/dd", "yyyy/MM/dd HH:mm:ss", "yyyy/MM/dd HH:mm", "yyyy/MM",
    20. "yyyy.MM.dd", "yyyy.MM.dd HH:mm:ss", "yyyy.MM.dd HH:mm", "yyyy.MM"};
    21. /**
    22. * 获取当前Date型日期
    23. *
    24. * @return Date() 当前日期
    25. */
    26. public static Date getNowDate()
    27. {
    28. return new Date();
    29. }
    30. /**
    31. * 获取当前日期, 默认格式为yyyy-MM-dd
    32. *
    33. * @return String
    34. */
    35. public static String getDate()
    36. {
    37. return dateTimeNow(YYYY_MM_DD);
    38. }
    39. public static final String getTime()
    40. {
    41. return dateTimeNow(YYYY_MM_DD_HH_MM_SS);
    42. }
    43. public static final String dateTimeNow()
    44. {
    45. return dateTimeNow(YYYYMMDDHHMMSS);
    46. }
    47. public static final String dateTimeNow(final String format)
    48. {
    49. return parseDateToStr(format, new Date());
    50. }
    51. public static final String dateTime(final Date date)
    52. {
    53. return parseDateToStr(YYYY_MM_DD, date);
    54. }
    55. public static final String parseDateToStr(final String format, final Date date)
    56. {
    57. return new SimpleDateFormat(format).format(date);
    58. }
    59. public static final Date dateTime(final String format, final String ts)
    60. {
    61. try
    62. {
    63. return new SimpleDateFormat(format).parse(ts);
    64. }
    65. catch (ParseException e)
    66. {
    67. throw new RuntimeException(e);
    68. }
    69. }
    70. /**
    71. * 日期路径 即年/月/日 如2018/08/08
    72. */
    73. public static final String datePath()
    74. {
    75. Date now = new Date();
    76. return DateFormatUtils.format(now, "yyyy/MM/dd");
    77. }
    78. /**
    79. * 日期路径 即年/月/日 如20180808
    80. */
    81. public static final String dateTime()
    82. {
    83. Date now = new Date();
    84. return DateFormatUtils.format(now, "yyyyMMdd");
    85. }
    86. /**
    87. * 日期型字符串转化为日期 格式
    88. */
    89. public static Date parseDate(Object str)
    90. {
    91. if (str == null)
    92. {
    93. return null;
    94. }
    95. try
    96. {
    97. return parseDate(str.toString(), parsePatterns);
    98. }
    99. catch (ParseException e)
    100. {
    101. return null;
    102. }
    103. }
    104. /**
    105. * 获取服务器启动时间
    106. */
    107. public static Date getServerStartDate()
    108. {
    109. long time = ManagementFactory.getRuntimeMXBean().getStartTime();
    110. return new Date(time);
    111. }
    112. /**
    113. * 计算两个时间差
    114. */
    115. public static String getDatePoor(Date endDate, Date nowDate)
    116. {
    117. long nd = 1000 * 24 * 60 * 60;
    118. long nh = 1000 * 60 * 60;
    119. long nm = 1000 * 60;
    120. // long ns = 1000;
    121. // 获得两个时间的毫秒时间差异
    122. long diff = endDate.getTime() - nowDate.getTime();
    123. // 计算差多少天
    124. long day = diff / nd;
    125. // 计算差多少小时
    126. long hour = diff % nd / nh;
    127. // 计算差多少分钟
    128. long min = diff % nd % nh / nm;
    129. // 计算差多少秒//输出结果
    130. // long sec = diff % nd % nh % nm / ns;
    131. return day + "天" + hour + "小时" + min + "分钟";
    132. }
    133. }

    12)IpUtils获取IP方法

    1. package com.ceam.common.utils;
    2. import javax.servlet.http.HttpServletRequest;
    3. import java.net.InetAddress;
    4. import java.net.UnknownHostException;
    5. /**
    6. * 获取IP方法
    7. */
    8. public class IpUtils
    9. {
    10. public static String getIpAddr(HttpServletRequest request)
    11. {
    12. if (request == null)
    13. {
    14. return "unknown";
    15. }
    16. String ip = request.getHeader("x-forwarded-for");
    17. if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip))
    18. {
    19. ip = request.getHeader("Proxy-Client-IP");
    20. }
    21. if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip))
    22. {
    23. ip = request.getHeader("X-Forwarded-For");
    24. }
    25. if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip))
    26. {
    27. ip = request.getHeader("WL-Proxy-Client-IP");
    28. }
    29. if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip))
    30. {
    31. ip = request.getHeader("X-Real-IP");
    32. }
    33. if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip))
    34. {
    35. ip = request.getRemoteAddr();
    36. }
    37. return "0:0:0:0:0:0:0:1".equals(ip) ? "127.0.0.1" : ip.split(",")[0];
    38. }
    39. public static boolean internalIp(String ip)
    40. {
    41. byte[] addr = textToNumericFormatV4(ip);
    42. if (null != addr) {
    43. return internalIp(addr) || "127.0.0.1".equals(ip);
    44. }
    45. return false;
    46. }
    47. private static boolean internalIp(byte[] addr)
    48. {
    49. final byte b0 = addr[0];
    50. final byte b1 = addr[1];
    51. // 10.x.x.x/8
    52. final byte SECTION_1 = 0x0A;
    53. // 172.16.x.x/12
    54. final byte SECTION_2 = (byte) 0xAC;
    55. final byte SECTION_3 = (byte) 0x10;
    56. final byte SECTION_4 = (byte) 0x1F;
    57. // 192.168.x.x/16
    58. final byte SECTION_5 = (byte) 0xC0;
    59. final byte SECTION_6 = (byte) 0xA8;
    60. switch (b0)
    61. {
    62. case SECTION_1:
    63. return true;
    64. case SECTION_2:
    65. if (b1 >= SECTION_3 && b1 <= SECTION_4)
    66. {
    67. return true;
    68. }
    69. case SECTION_5:
    70. switch (b1)
    71. {
    72. case SECTION_6:
    73. return true;
    74. }
    75. default:
    76. return false;
    77. }
    78. }
    79. /**
    80. * 将IPv4地址转换成字节
    81. *
    82. * @param text IPv4地址
    83. * @return byte 字节
    84. */
    85. public static byte[] textToNumericFormatV4(String text)
    86. {
    87. if (text.length() == 0)
    88. {
    89. return null;
    90. }
    91. byte[] bytes = new byte[4];
    92. String[] elements = text.split("\\.", -1);
    93. try
    94. {
    95. long l;
    96. int i;
    97. switch (elements.length)
    98. {
    99. case 1:
    100. l = Long.parseLong(elements[0]);
    101. if ((l < 0L) || (l > 4294967295L))
    102. return null;
    103. bytes[0] = (byte) (int) (l >> 24 & 0xFF);
    104. bytes[1] = (byte) (int) ((l & 0xFFFFFF) >> 16 & 0xFF);
    105. bytes[2] = (byte) (int) ((l & 0xFFFF) >> 8 & 0xFF);
    106. bytes[3] = (byte) (int) (l & 0xFF);
    107. break;
    108. case 2:
    109. l = Integer.parseInt(elements[0]);
    110. if ((l < 0L) || (l > 255L))
    111. return null;
    112. bytes[0] = (byte) (int) (l & 0xFF);
    113. l = Integer.parseInt(elements[1]);
    114. if ((l < 0L) || (l > 16777215L))
    115. return null;
    116. bytes[1] = (byte) (int) (l >> 16 & 0xFF);
    117. bytes[2] = (byte) (int) ((l & 0xFFFF) >> 8 & 0xFF);
    118. bytes[3] = (byte) (int) (l & 0xFF);
    119. break;
    120. case 3:
    121. for (i = 0; i < 2; ++i)
    122. {
    123. l = Integer.parseInt(elements[i]);
    124. if ((l < 0L) || (l > 255L))
    125. return null;
    126. bytes[i] = (byte) (int) (l & 0xFF);
    127. }
    128. l = Integer.parseInt(elements[2]);
    129. if ((l < 0L) || (l > 65535L))
    130. return null;
    131. bytes[2] = (byte) (int) (l >> 8 & 0xFF);
    132. bytes[3] = (byte) (int) (l & 0xFF);
    133. break;
    134. case 4:
    135. for (i = 0; i < 4; ++i)
    136. {
    137. l = Integer.parseInt(elements[i]);
    138. if ((l < 0L) || (l > 255L))
    139. return null;
    140. bytes[i] = (byte) (int) (l & 0xFF);
    141. }
    142. break;
    143. default:
    144. return null;
    145. }
    146. }
    147. catch (NumberFormatException e)
    148. {
    149. return null;
    150. }
    151. return bytes;
    152. }
    153. public static String getHostIp()
    154. {
    155. try
    156. {
    157. return InetAddress.getLocalHost().getHostAddress();
    158. }
    159. catch (UnknownHostException e)
    160. {
    161. }
    162. return "127.0.0.1";
    163. }
    164. public static String getHostName()
    165. {
    166. try
    167. {
    168. return InetAddress.getLocalHost().getHostName();
    169. }
    170. catch (UnknownHostException e)
    171. {
    172. }
    173. return "未知";
    174. }
    175. }

    13)MessageUtils获取i18n资源文件

    1. package com.ceam.common.utils;
    2. import com.ceam.common.utils.spring.SpringUtils;
    3. import org.springframework.context.MessageSource;
    4. import org.springframework.context.i18n.LocaleContextHolder;
    5. /**
    6. * 获取i18n资源文件
    7. */
    8. public class MessageUtils
    9. {
    10. /**
    11. * 根据消息键和参数 获取消息 委托给spring messageSource
    12. *
    13. * @param code 消息键
    14. * @param args 参数
    15. * @return 获取国际化翻译值
    16. */
    17. public static String message(String code, Object... args)
    18. {
    19. MessageSource messageSource = SpringUtils.getBean(MessageSource.class);
    20. return messageSource.getMessage(code, args, LocaleContextHolder.getLocale());
    21. }
    22. }

    14)ServletUtils客户端工具类

    1. package com.ceam.common.utils;
    2. import com.ceam.common.core.text.Convert;
    3. import org.springframework.web.context.request.RequestAttributes;
    4. import org.springframework.web.context.request.RequestContextHolder;
    5. import org.springframework.web.context.request.ServletRequestAttributes;
    6. import javax.servlet.http.HttpServletRequest;
    7. import javax.servlet.http.HttpServletResponse;
    8. import javax.servlet.http.HttpSession;
    9. import java.io.IOException;
    10. /**
    11. * 客户端工具类
    12. */
    13. public class ServletUtils
    14. {
    15. /**
    16. * 获取String参数
    17. */
    18. public static String getParameter(String name)
    19. {
    20. return getRequest().getParameter(name);
    21. }
    22. /**
    23. * 获取String参数
    24. */
    25. public static String getParameter(String name, String defaultValue)
    26. {
    27. return Convert.toStr(getRequest().getParameter(name), defaultValue);
    28. }
    29. /**
    30. * 获取Integer参数
    31. */
    32. public static Integer getParameterToInt(String name)
    33. {
    34. return Convert.toInt(getRequest().getParameter(name));
    35. }
    36. /**
    37. * 获取Integer参数
    38. */
    39. public static Integer getParameterToInt(String name, Integer defaultValue)
    40. {
    41. return Convert.toInt(getRequest().getParameter(name), defaultValue);
    42. }
    43. /**
    44. * 获取request
    45. */
    46. public static HttpServletRequest getRequest()
    47. {
    48. return getRequestAttributes().getRequest();
    49. }
    50. /**
    51. * 获取response
    52. */
    53. public static HttpServletResponse getResponse()
    54. {
    55. return getRequestAttributes().getResponse();
    56. }
    57. /**
    58. * 获取session
    59. */
    60. public static HttpSession getSession()
    61. {
    62. return getRequest().getSession();
    63. }
    64. public static ServletRequestAttributes getRequestAttributes()
    65. {
    66. RequestAttributes attributes = RequestContextHolder.getRequestAttributes();
    67. return (ServletRequestAttributes) attributes;
    68. }
    69. /**
    70. * 将字符串渲染到客户端
    71. *
    72. * @param response 渲染对象
    73. * @param string 待渲染的字符串
    74. * @return null
    75. */
    76. public static String renderString(HttpServletResponse response, String string)
    77. {
    78. try
    79. {
    80. response.setContentType("application/json");
    81. response.setCharacterEncoding("utf-8");
    82. response.getWriter().print(string);
    83. }
    84. catch (IOException e)
    85. {
    86. e.printStackTrace();
    87. }
    88. return null;
    89. }
    90. /**
    91. * 是否是Ajax异步请求
    92. *
    93. * @param request
    94. */
    95. public static boolean isAjaxRequest(HttpServletRequest request)
    96. {
    97. String accept = request.getHeader("accept");
    98. if (accept != null && accept.indexOf("application/json") != -1)
    99. {
    100. return true;
    101. }
    102. String xRequestedWith = request.getHeader("X-Requested-With");
    103. if (xRequestedWith != null && xRequestedWith.indexOf("XMLHttpRequest") != -1)
    104. {
    105. return true;
    106. }
    107. String uri = request.getRequestURI();
    108. if (StringUtils.inStringIgnoreCase(uri, ".json", ".xml"))
    109. {
    110. return true;
    111. }
    112. String ajax = request.getParameter("__ajax");
    113. if (StringUtils.inStringIgnoreCase(ajax, "json", "xml"))
    114. {
    115. return true;
    116. }
    117. return false;
    118. }
    119. }

    如果发现不完整,读者自行调整,简单地完成service,controller的基本请求处理就行。

    11、验证

    11.1、启动注册中心EurekaApp,访问http://localhost:7001/

    还没有服务实例注册进来 

    11.2、启动AuthApp权限微服务,刷新http://localhost:7001/如下注册进来了

    11.3、启动GatewayApp, 刷新http://localhost:7001/如下注册进来了

    11.4、我们来看看网关的作用

    1)打开postman(没有的自行下载),发送POST请求,访问9527网关端口系统入口,访问AuthApp微服务,请求数据Body以及返回等信息如下:

     可以看出网关将请求路由到了AuthApp,进入到login处理逻辑。

    如果对你有帮助,就点赞、收藏、评论吧,不要白嫖人家啦

    前面后面都有惊喜,关注不迷路

  • 相关阅读:
    RocketMQ第二话 -- RocketMQ事务消息、延时消息实现
    Python爬虫技巧:使用代理IP和User-Agent应对反爬虫机制
    [NLP复习笔记] Word2Vec: 基于负采样的 Skip-gram 及其 SGD 训练
    客户端日志打印规范
    从零开始打造一款基于SpringBoot+SpringCloud的后台权限管理系统
    Coredump:core与kernel的区别,以及coredump具体指什么?
    C语言学习之路(基础篇)—— 内存管理
    NLP(六十九)智能文档问答助手升级
    【历史上的今天】7 月 28 日:Lua 首次在线上运行;苹果停产所有非 iOS 的 iPod;戴尔工作站 400 推出
    找不到d3dcompiler_43.dll,无法继续执行代码如何解决
  • 原文地址:https://blog.csdn.net/qq_57756904/article/details/125237872