目录
1、在上一章基础test-cloud项目上面,右击File -> new -> Module,创建网关模块
4、Module Name模块名称加上“-”,点击Finish完成创建
6.1、在resources文件夹右击,New -> File,输入application.yml,注意有个spring绿色标志。
9、RateLimiterConfiguration路由限流配置
10.3、读者自行补充common模块,参照之前创建模块的教程。
11.1、启动注册中心EurekaApp,访问http://localhost:7001/
11.2、启动AuthApp权限微服务,刷新http://localhost:7001/如下注册进来了
11.3、启动GatewayApp, 刷新http://localhost:7001/如下注册进来了
一、前言
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 之间的报文格式转换能力(可选) 安全认证:
- 基于 Token 的客户端访问控制和安全策略
传输数据和报文加密,到服务端解密,需要在客户端有独立的 SDK 代理包 基于 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引入依赖
-
<?xml version="1.0" encoding="UTF-8"?>
-
<project xmlns="http://maven.apache.org/POM/4.0.0"
-
xmlns:xsi=
"http://www.w3.org/2001/XMLSchema-instance"
-
xsi:schemaLocation=
"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
-
<parent>
-
<artifactId>test-cloud
</artifactId>
-
<groupId>com.ceam
</groupId>
-
<version>1.0-SNAPSHOT
</version>
-
</parent>
-
<modelVersion>4.0.0
</modelVersion>
-
-
<artifactId>ceam-gateway
</artifactId>
-
-
<dependencies>
-
<!--配置中心客户端 -->
-
<dependency>
-
<groupId>org.springframework.cloud
</groupId>
-
<artifactId>spring-cloud-starter-config
</artifactId>
-
</dependency>
-
<!--gateway 网关依赖,内置webflux 依赖 -->
-
<dependency>
-
<groupId>org.springframework.cloud
</groupId>
-
<artifactId>spring-cloud-starter-gateway
</artifactId>
-
</dependency>
-
<!--eureka 客户端 -->
-
<dependency>
-
<groupId>org.springframework.cloud
</groupId>
-
<artifactId>spring-cloud-starter-netflix-eureka-client
</artifactId>
-
</dependency>
-
<dependency>
-
<groupId>org.springframework.cloud
</groupId>
-
<artifactId>spring-cloud-starter-netflix-hystrix
</artifactId>
-
</dependency>
-
<dependency>
-
<groupId>org.springframework.boot
</groupId>
-
<artifactId>spring-boot-starter-actuator
</artifactId>
-
</dependency>
-
<!-- spring-boot-devtools -->
-
<dependency>
-
<groupId>org.springframework.boot
</groupId>
-
<artifactId>spring-boot-devtools
</artifactId>
-
<optional>true
</optional>
<!-- 表示依赖不会传递 -->
-
</dependency>
-
-
</dependencies>
-
<build>
-
<plugins>
-
<plugin>
-
<groupId>org.springframework.boot
</groupId>
-
<artifactId>spring-boot-maven-plugin
</artifactId>
-
<configuration>
-
<fork>true
</fork>
<!-- 如果没有该配置,devtools不会生效 -->
-
</configuration>
-
<executions>
-
<execution>
-
<goals>
-
<goal>repackage
</goal>
-
</goals>
-
</execution>
-
</executions>
-
</plugin>
-
</plugins>
-
</build>
-
-
-
</project>
6、网关配置文件
6.1、在resources文件夹右击,New -> File,输入application.yml,注意有个spring绿色标志。
6.2、配置内容如下
-
server:
-
port: 9527 # 端口号,对外统一暴露
-
-
spring:
-
application:
-
name: ceam-gateway
-
devtools:
-
restart:
-
enabled: true # 热部署
-
profiles:
-
active: dev # 开发环境
-
cloud:
-
# 这是使用配置中心时,需指定的配置
-
config:
-
fail-fast: true
-
name: ${spring.application.name} # 就是上面的ceam-gateway,这样写上面变更下面就跟着变了
-
profile: ${spring.profiles.active} # 就是上面的开发环境dev
-
discovery:
-
enabled: true
-
service-id: ceam-config
-
gateway:
-
discovery:
-
locator:
-
enabled: true
-
routes:
-
# 认证中心
-
- id: ceam-auth
-
uri: lb://ceam-auth
-
# 断言,路径前缀,访问得加上,如:http://localhost:9527/auth/login
-
predicates:
-
- Path=/auth/**
-
# 过滤器,是不是很熟悉
-
filters:
-
# 验证码处理
-
- CacheRequest
-
- ImgCodeFilter
-
- StripPrefix=1
-
# 限流配置
-
- name: RequestRateLimiter
-
args:
-
key-resolver: '#{@remoteAddrKeyResolver}'
-
#允许用户每秒处理多少个请求
-
redis-rate-limiter.replenishRate: 2
-
#令牌桶的容量,允许在一秒钟内完成的最大请求
-
redis-rate-limiter.burstCapacity: 20
-
-
-
eureka:
-
client: #客户端注册进eureka服务列表内
-
service-url:
-
defaultZone: http://localhost:7001/eureka
-
#defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/,http://eureka7003.com:7003/eureka/
-
instance:
-
instance-id: ${spring.application.name}:${server.port} # 即ceam-gateway:9527
-
prefer-ip-address: true #访问路径可以显示IP地址
注:不同版本的配置可能会不同,在做开发的时候注意,读者可以看看官方文档或者源码。过滤器就像学校门口的门卫,不同过滤器职责不一样。这里配置文件配置的过滤器不是全局性的,它当当仅对ceam-auth模块使作用,那么全局性的过滤器是怎么样子的呢,我们接下来往下看。
7、AuthFilter全局权限过滤器
在filter文件夹创建AuthFilter过滤器,代码如下:
-
package com.ceam.gateway.fiflt;
-
-
import com.alibaba.fastjson.JSON;
-
import com.alibaba.fastjson.JSONObject;
-
import com.ceam.common.constant.Constants;
-
import com.ceam.common.core.domain.R;
-
import lombok.extern.slf4j.Slf4j;
-
import org.apache.commons.lang3.StringUtils;
-
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
-
import org.springframework.cloud.gateway.filter.GlobalFilter;
-
import org.springframework.core.Ordered;
-
import org.springframework.core.io.buffer.DataBuffer;
-
import org.springframework.data.redis.core.ValueOperations;
-
import org.springframework.http.HttpStatus;
-
import org.springframework.http.server.reactive.ServerHttpRequest;
-
import org.springframework.http.server.reactive.ServerHttpResponse;
-
import org.springframework.stereotype.Component;
-
import org.springframework.web.server.ServerWebExchange;
-
import reactor.core.publisher.Flux;
-
import reactor.core.publisher.Mono;
-
-
import javax.annotation.Resource;
-
import java.io.UnsupportedEncodingException;
-
import java.util.Arrays;
-
-
/**
-
* 网关鉴权
-
*/
-
@Slf4j
-
@Component
-
public
class
AuthFilter
implements
GlobalFilter, Ordered
-
{
-
// 白名单,排除过滤的 uri 地址
-
private
static
final String[] whiteList = {
"/auth/login",
"/user/register",
"/system/v2/api-docs",
-
"/auth/captcha/check",
"/auth/captcha/get",
"/auth/login/slide"};
-
-
@Resource(name = "stringRedisTemplate")
-
private ValueOperations<String, String> ops;
-
-
@Override
-
public Mono<Void>
filter
(ServerWebExchange exchange, GatewayFilterChain chain)
-
{
-
String
url
= exchange.getRequest().getURI().getPath();
-
log.info(
"url:{}", url);
-
// 跳过不需要验证的路径
-
if (Arrays.asList(whiteList).contains(url))
-
{
-
return chain.filter(exchange);
-
}
-
String
token
= exchange.getRequest().getHeaders().getFirst(Constants.TOKEN);
-
// token为空
-
if (StringUtils.isBlank(token))
-
{
-
return setUnauthorizedResponse(exchange,
"token can't be null or empty string");
-
}
-
String
userStr
= ops.get(Constants.ACCESS_TOKEN + token);
-
if (StringUtils.isBlank(userStr))
-
{
-
return setUnauthorizedResponse(exchange,
"token verify error");
-
}
-
JSONObject
jo
= JSONObject.parseObject(userStr);
-
String
userId
= jo.getString(
"userId");
-
// 查询token信息
-
if (StringUtils.isBlank(userId))
-
{
-
return setUnauthorizedResponse(exchange,
"token verify error");
-
}
-
// 设置userId到request里,后续根据userId,获取用户信息
-
ServerHttpRequest
mutableReq
= exchange.getRequest().mutate().header(Constants.CURRENT_ID, userId)
-
.header(Constants.CURRENT_USERNAME, jo.getString(
"loginName")).build();
-
ServerWebExchange
mutableExchange
= exchange.mutate().request(mutableReq).build();
-
return chain.filter(mutableExchange);
-
}
-
-
private Mono<Void>
setUnauthorizedResponse
(ServerWebExchange exchange, String msg)
-
{
-
ServerHttpResponse
originalResponse
= exchange.getResponse();
-
originalResponse.setStatusCode(HttpStatus.UNAUTHORIZED);
-
originalResponse.getHeaders().add(
"Content-Type",
"application/json;charset=UTF-8");
-
byte[] response =
null;
-
try
-
{
-
response = JSON.toJSONString(R.error(
401, msg)).getBytes(Constants.UTF8);
-
}
-
catch (UnsupportedEncodingException e)
-
{
-
e.printStackTrace();
-
}
-
DataBuffer
buffer
= originalResponse.bufferFactory().wrap(response);
-
return originalResponse.writeWith(Flux.just(buffer));
-
}
-
-
@Override
-
public
int
getOrder
()
-
{
-
return -
100;
-
}
-
}
import org.springframework.cloud.gateway.filter.GlobalFilter;从这导入语句中可看出GlobalFilter是gateway下过滤器模块的一个重要组件,它是一个全局性的过滤器,真实开发场景也会用它做一些逻辑的处理(都在前言那里罗列了)。自定义的过滤器实现该GlobalFilter接口,在filter的方法中去处理过滤的逻辑。但是有一个问题,如果你定义的过滤器多了,而且你想让它们有一定的顺序去执行,那怎么办呢?于是Ordered接口就出现了,顾名思义它就是指定顺序的,所以getOrder()方法就是用来指定过滤器执行顺序的,值越小就越先执行。
7、CacheRequestFilter组件过滤器
在filter文件夹创建CacheRequestFilter过滤器,上面配置文件提到了,代码如下:
-
@Component
-
public
class
CacheRequestFilter
extends
AbstractGatewayFilterFactory<CacheRequestFilter.Config>
-
{
-
public
CacheRequestFilter
()
-
{
-
super(Config.class);
-
}
-
-
@Override
-
public String
name
()
-
{
-
return
"CacheRequest";
-
}
-
-
@Override
-
public GatewayFilter
apply
(Config config)
-
{
-
CacheRequestGatewayFilter
cacheRequestGatewayFilter
=
new
CacheRequestGatewayFilter();
-
Integer
order
= config.getOrder();
-
if (order ==
null)
-
{
-
return cacheRequestGatewayFilter;
-
}
-
return
new
OrderedGatewayFilter(cacheRequestGatewayFilter, order);
-
}
-
-
public
static
class
CacheRequestGatewayFilter
implements
GatewayFilter
-
{
-
@Override
-
public Mono<Void>
filter
(ServerWebExchange exchange, GatewayFilterChain chain)
-
{
-
// GET DELETE 不过滤
-
HttpMethod
method
= exchange.getRequest().getMethod();
-
if (method ==
null || method.matches(
"GET") || method.matches(
"DELETE"))
-
{
-
return chain.filter(exchange);
-
}
-
return DataBufferUtils.join(exchange.getRequest().getBody()).map(dataBuffer -> {
-
byte[] bytes =
new
byte[dataBuffer.readableByteCount()];
-
dataBuffer.read(bytes);
-
DataBufferUtils.release(dataBuffer);
-
return bytes;
-
}).defaultIfEmpty(
new
byte[
0]).flatMap(bytes -> {
-
DataBufferFactory
dataBufferFactory
= exchange.getResponse().bufferFactory();
-
ServerHttpRequestDecorator
decorator
=
new
ServerHttpRequestDecorator(exchange.getRequest())
-
{
-
@Override
-
public Flux<DataBuffer>
getBody
()
-
{
-
if (bytes.length >
0)
-
{
-
return Flux.just(dataBufferFactory.wrap(bytes));
-
}
-
return Flux.empty();
-
}
-
};
-
return chain.filter(exchange.mutate().request(decorator).build());
-
});
-
}
-
}
-
-
@Override
-
public List<String>
shortcutFieldOrder
()
-
{
-
return Collections.singletonList(
"order");
-
}
-
-
@Data
-
static
class
Config
-
{
-
private Integer order;
-
}
-
}
8、ImgCodeFilter验证码过滤器
在filter文件夹创建ImgCodeFilter过滤器,上面配置文件提到了,代码如下:
-
/**
-
* 验证码处理
-
*/
-
@Component
-
public
class
ImgCodeFilter
extends
AbstractGatewayFilterFactory<ImgCodeFilter.Config>
-
{
-
private
final
static
String
AUTH_URL
=
"/auth/login";
-
-
@Autowired
-
private StringRedisTemplate redisTemplate;
-
-
public
ImgCodeFilter
()
-
{
-
super(Config.class);
-
}
-
-
@Override
-
public GatewayFilter
apply
(Config config)
-
{
-
return (exchange, chain) -> {
-
ServerHttpRequest
request
= exchange.getRequest();
-
URI
uri
= request.getURI();
-
// 不是登录请求,直接向下执行
-
//if (!StringUtils.containsIgnoreCase(uri.getPath(), AUTH_URL))
-
if (!AUTH_URL.equalsIgnoreCase(uri.getPath()))
-
{
-
return chain.filter(exchange);
-
}
-
try
-
{
-
String
bodyStr
= resolveBodyFromRequest(request);
-
JSONObject bodyJson=JSONObject.parseObject(bodyStr);
-
String
code
= (String) bodyJson.get(
"captcha");
-
String
randomStr
= (String) bodyJson.get(
"randomStr");
-
// 校验验证码
-
//checkCode(code, randomStr);
-
}
-
catch (Exception e)
-
{
-
ServerHttpResponse
response
= exchange.getResponse();
-
response.setStatusCode(HttpStatus.INTERNAL_SERVER_ERROR);
-
response.getHeaders().add(
"Content-Type",
"application/json;charset=UTF-8");
-
String
msg
= JSON.toJSONString(R.error(e.getMessage()));
-
DataBuffer
bodyDataBuffer
= response.bufferFactory().wrap(msg.getBytes());
-
return response.writeWith(Mono.just(bodyDataBuffer));
-
}
-
return chain.filter(exchange);
-
};
-
}
-
-
private String
resolveBodyFromRequest
(ServerHttpRequest serverHttpRequest)
-
{
-
// 获取请求体
-
Flux<DataBuffer> body = serverHttpRequest.getBody();
-
AtomicReference<String> bodyRef =
new
AtomicReference<>();
-
body.subscribe(buffer -> {
-
CharBuffer
charBuffer
= StandardCharsets.UTF_8.decode(buffer.asByteBuffer());
-
DataBufferUtils.release(buffer);
-
bodyRef.set(charBuffer.toString());
-
});
-
return bodyRef.get();
-
}
-
-
/**
-
* 检查code
-
*/
-
@SneakyThrows
-
private
void
checkCode
(String code, String randomStr)
-
{
-
if (StringUtils.isBlank(code))
-
{
-
throw
new
ValidateCodeException(
"验证码不能为空");
-
}
-
if (StringUtils.isBlank(randomStr))
-
{
-
throw
new
ValidateCodeException(
"验证码不合法");
-
}
-
String
key
= Constants.DEFAULT_CODE_KEY + randomStr;
-
String
saveCode
= redisTemplate.opsForValue().get(key);
-
redisTemplate.delete(key);
-
if (!code.equalsIgnoreCase(saveCode))
-
{
-
throw
new
ValidateCodeException(
"验证码不合法");
-
}
-
}
-
-
public
static
class
Config
-
{
-
}
-
}
9、RateLimiterConfiguration路由限流配置
在配置文件中还提及了限流配置,在config文件夹创建RateLimiterConfiguration,代码如下:
-
/**
-
* 路由限流配置
-
*/
-
@Configuration
-
public
class
RateLimiterConfiguration
-
{
-
@Bean(value = "remoteAddrKeyResolver")
-
public KeyResolver
remoteAddrKeyResolver
()
-
{
-
return exchange -> Mono.just(exchange.getRequest().getRemoteAddress().getAddress().getHostAddress());
-
}
-
}
10、在第1章基础上补充AuthApp模块的业务代码
10.1、controller层
-
import com.anji.captcha.model.common.ResponseModel;
-
import com.anji.captcha.service.CaptchaService;
-
import com.ceam.auth.form.LoginForm;
-
import com.ceam.auth.service.AccessTokenService;
-
import com.ceam.auth.service.SysLoginService;
-
import com.ceam.common.core.domain.R;
-
import com.ceam.system.domain.SysUser;
-
import org.springframework.beans.factory.annotation.Autowired;
-
import org.springframework.web.bind.annotation.PostMapping;
-
import org.springframework.web.bind.annotation.RequestBody;
-
import org.springframework.web.bind.annotation.RestController;
-
-
import javax.servlet.http.HttpServletRequest;
-
-
@RestController
-
public
class
TokenController
-
{
-
@Autowired
-
private AccessTokenService tokenService;
-
-
@Autowired
-
private SysLoginService sysLoginService;
-
-
@Autowired
-
private CaptchaService captchaService;
-
-
@PostMapping("login")
-
public R
login
(@RequestBody LoginForm form)
-
{
-
// 用户登录
-
SysUser
user
= sysLoginService.login(form.getUsername(), form.getPassword());
-
// 获取登录token
-
return R.ok(tokenService.createToken(user));
-
}
-
-
@PostMapping("login/slide")
-
public R
loginSilde
(@RequestBody LoginForm form)
-
{
-
ResponseModel
response
= captchaService.verification(form.getCaptchaVO());
-
if (response.isSuccess())
-
{
-
// 用户登录
-
SysUser
user
= sysLoginService.login(form.getUsername(), form.getPassword());
-
// 获取登录token
-
return R.ok(tokenService.createToken(user));
-
}
-
return R.error().put(
"repCode", response.getRepCode());
-
}
-
-
@PostMapping("logout")
-
public R
logout
(HttpServletRequest request)
-
{
-
String token=request.getHeader(
"token");
-
SysUser user=tokenService.queryByToken(token);
-
if (
null != user)
-
{
-
sysLoginService.logout(user.getLoginName());
-
tokenService.expireToken(user.getUserId());
-
}
-
return R.ok();
-
}
-
}
10.2、service层
1)AccessTokenService
-
import cn.hutool.core.util.IdUtil;
-
import com.ceam.common.constant.Constants;
-
import com.ceam.common.redis.annotation.RedisEvict;
-
import com.ceam.common.redis.util.RedisUtils;
-
import com.ceam.system.domain.SysUser;
-
import org.apache.commons.lang3.StringUtils;
-
import org.springframework.beans.factory.annotation.Autowired;
-
import org.springframework.stereotype.Service;
-
-
import java.util.HashMap;
-
import java.util.Map;
-
-
@Service("accessTokenService")
-
public
class
AccessTokenService
-
{
-
@Autowired
-
private RedisUtils redis;
-
-
/**
-
* 12小时后过期
-
*/
-
private
final
static
long
EXPIRE
=
12 *
60 *
60;
-
-
private
final
static
String
ACCESS_TOKEN
= Constants.ACCESS_TOKEN;
-
-
private
final
static
String
ACCESS_USERID
= Constants.ACCESS_USERID;
-
-
public SysUser
queryByToken
(String token)
-
{
-
return redis.get(ACCESS_TOKEN + token, SysUser.class);
-
}
-
-
@RedisEvict(key = "user_perms", fieldKey = "#sysUser.userId")
-
public Map<String, Object>
createToken
(SysUser sysUser)
-
{
-
// 生成token
-
String
token
= IdUtil.fastSimpleUUID();
-
// 保存或更新用户token
-
Map<String, Object> map =
new
HashMap<String, Object>();
-
map.put(
"userId", sysUser.getUserId());
-
map.put(
"token", token);
-
map.put(
"expire", EXPIRE);
-
// expireToken(userId);
-
redis.set(ACCESS_TOKEN + token, sysUser, EXPIRE);
-
redis.set(ACCESS_USERID + sysUser.getUserId(), token, EXPIRE);
-
return map;
-
}
-
-
public
void
expireToken
(long userId)
-
{
-
String
token
= redis.get(ACCESS_USERID + userId);
-
if (StringUtils.isNotBlank(token))
-
{
-
redis.delete(ACCESS_USERID + userId);
-
redis.delete(ACCESS_TOKEN + token);
-
}
-
}
-
}
2)CaptchaCacheServiceRedisImpl
-
import com.anji.captcha.service.CaptchaCacheService;
-
import org.springframework.beans.factory.annotation.Autowired;
-
import org.springframework.data.redis.core.StringRedisTemplate;
-
import org.springframework.stereotype.Service;
-
-
import java.util.concurrent.TimeUnit;
-
-
/**
-
* <p>File:CaptchaCacheServiceRedisImpl.java</p>
-
* <p>Title: 使用redis缓存</p>
-
* <p>Description:对于分布式部署的应用,我们建议应用自己实现CaptchaCacheService,比如用Redis,如果应用是单点的,也没有使用redis,那默认使用内存。</p>
-
*/
-
@Service
-
public
class
CaptchaCacheServiceRedisImpl
implements
CaptchaCacheService
-
{
-
@Override
-
public String
type
() {
-
return
"redis";
-
}
-
-
@Autowired
-
private StringRedisTemplate stringRedisTemplate;
-
-
@Override
-
public
void
set
(String key, String value, long expiresInSeconds)
-
{
-
stringRedisTemplate.opsForValue().set(key, value, expiresInSeconds, TimeUnit.SECONDS);
-
}
-
-
@Override
-
public
boolean
exists
(String key)
-
{
-
return stringRedisTemplate.hasKey(key);
-
}
-
-
@Override
-
public
void
delete
(String key)
-
{
-
stringRedisTemplate.delete(key);
-
}
-
-
@Override
-
public String
get
(String key)
-
{
-
return stringRedisTemplate.opsForValue().get(key);
-
}
-
}
3)SysLoginService登录服务
这里的逻辑简单处理下,验证网关进入到这里
这里RemoteUserService是feign的调用,在后面的章节再介绍,读者写点简单逻辑看看是否可以通过网关Gateway路由到权限服务的处理逻辑。比如就一条System输出语句等等。
-
import com.ceam.common.constant.Constants;
-
import com.ceam.common.constant.UserConstants;
-
import com.ceam.common.enums.UserStatus;
-
import com.ceam.common.exception.user.UserBlockedException;
-
import com.ceam.common.exception.user.UserDeleteException;
-
import com.ceam.common.exception.user.UserNotExistsException;
-
import com.ceam.common.exception.user.UserPasswordNotMatchException;
-
import com.ceam.common.log.publish.PublishFactory;
-
import com.ceam.common.utils.DateUtils;
-
import com.ceam.common.utils.IpUtils;
-
import com.ceam.common.utils.MessageUtils;
-
import com.ceam.common.utils.ServletUtils;
-
import com.ceam.system.domain.SysUser;
-
import com.ceam.system.feign.RemoteUserService;
-
import com.ceam.system.util.PasswordUtil;
-
import org.apache.commons.lang3.StringUtils;
-
import org.springframework.beans.factory.annotation.Autowired;
-
import org.springframework.stereotype.Component;
-
-
@Component
-
public
class
SysLoginService
-
{
-
-
@Autowired
-
private RemoteUserService userService;
-
-
/**
-
* 登录
-
*/
-
public SysUser
login
(String username, String password)
-
{
-
// 验证码校验
-
// if
-
// (!StringUtils.isEmpty(ServletUtils.getRequest().getAttribute(ShiroConstants.CURRENT_CAPTCHA)))
-
// {
-
// AsyncManager.me().execute(AsyncFactory.recordLogininfor(username,
-
// Constants.LOGIN_FAIL,
-
// MessageUtils.message("user.jcaptcha.error")));
-
// throw new CaptchaException();
-
// }
-
// 用户名或密码为空 错误
-
if (StringUtils.isAnyBlank(username, password))
-
{
-
PublishFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message(
"not.null"));
-
throw
new
UserNotExistsException();
-
}
-
// 密码如果不在指定范围内 错误
-
if (password.length() < UserConstants.PASSWORD_MIN_LENGTH
-
|| password.length() > UserConstants.PASSWORD_MAX_LENGTH)
-
{
-
PublishFactory.recordLogininfor(username, Constants.LOGIN_FAIL,
-
MessageUtils.message(
"user.password.not.match"));
-
throw
new
UserPasswordNotMatchException();
-
}
-
// 用户名不在指定范围内 错误
-
if (username.length() < UserConstants.USERNAME_MIN_LENGTH
-
|| username.length() > UserConstants.USERNAME_MAX_LENGTH)
-
{
-
PublishFactory.recordLogininfor(username, Constants.LOGIN_FAIL,
-
MessageUtils.message(
"user.password.not.match"));
-
throw
new
UserPasswordNotMatchException();
-
}
-
// 查询用户信息
-
SysUser
user
= userService.selectSysUserByUsername(username);
-
// if (user == null && maybeMobilePhoneNumber(username))
-
// {
-
// user = userService.selectUserByPhoneNumber(username);
-
// }
-
// if (user == null && maybeEmail(username))
-
// {
-
// user = userService.selectUserByEmail(username);
-
// }
-
if (user ==
null)
-
{
-
PublishFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message(
"user.not.exists"));
-
throw
new
UserNotExistsException();
-
}
-
if (UserStatus.DELETED.getCode().equals(user.getDelFlag()))
-
{
-
PublishFactory.recordLogininfor(username, Constants.LOGIN_FAIL,
-
MessageUtils.message(
"user.password.delete"));
-
throw
new
UserDeleteException();
-
}
-
if (UserStatus.DISABLE.getCode().equals(user.getStatus()))
-
{
-
PublishFactory.recordLogininfor(username, Constants.LOGIN_FAIL,
-
MessageUtils.message(
"user.blocked", user.getRemark()));
-
throw
new
UserBlockedException();
-
}
-
if (!PasswordUtil.matches(user, password))
-
{
-
throw
new
UserPasswordNotMatchException();
-
}
-
PublishFactory.recordLogininfor(username, Constants.LOGIN_SUCCESS, MessageUtils.message(
"user.login.success"));
-
recordLoginInfo(user);
-
return user;
-
}
-
-
// private boolean maybeEmail(String username)
-
// {
-
// if (!username.matches(UserConstants.EMAIL_PATTERN))
-
// {
-
// return false;
-
// }
-
// return true;
-
// }
-
//
-
// private boolean maybeMobilePhoneNumber(String username)
-
// {
-
// if (!username.matches(UserConstants.MOBILE_PHONE_NUMBER_PATTERN))
-
// {
-
// return false;
-
// }
-
// return true;
-
// }
-
/**
-
* 记录登录信息
-
*/
-
public
void
recordLoginInfo
(SysUser user)
-
{
-
user.setLoginIp(IpUtils.getIpAddr(ServletUtils.getRequest()));
-
user.setLoginDate(DateUtils.getNowDate());
-
userService.updateUserLoginRecord(user);
-
}
-
-
public
void
logout
(String loginName)
-
{
-
PublishFactory.recordLogininfor(loginName, Constants.LOGOUT, MessageUtils.message(
"user.logout.success"));
-
}
-
}
实际开发中建议面向接口编程,这里是方便就没有通过实现接口形式。
4)请求体字段
-
import com.anji.captcha.model.vo.CaptchaVO;
-
import lombok.Data;
-
-
@Data
-
public
class
LoginForm
-
{
-
private String username;
-
-
private String password;
-
-
// 滑块验证码二次验证参数
-
private CaptchaVO captchaVO;
-
}
10.3、读者自行补充common模块,参照之前创建模块的教程。
common模块一般封装一些工具类、常量、枚举等以便复用以及维护等。
1)Constants通用常量信息
-
package com.ceam.common.constant;
-
-
/**
-
* 通用常量信息
-
*/
-
public
class
Constants
-
{
-
/**
-
* UTF-8 字符集
-
*/
-
public
static
final
String
UTF8
=
"UTF-8";
-
-
/**
-
* 通用成功标识
-
*/
-
public
static
final
String
SUCCESS
=
"0";
-
-
/**
-
* 通用失败标识
-
*/
-
public
static
final
String
FAIL
=
"1";
-
-
/**
-
* 登录成功
-
*/
-
public
static
final
String
LOGIN_SUCCESS
=
"Success";
-
-
/**
-
* 注销
-
*/
-
public
static
final
String
LOGOUT
=
"Logout";
-
-
/**
-
* 登录失败
-
*/
-
public
static
final
String
LOGIN_FAIL
=
"Error";
-
-
/**
-
* 自动去除表前缀
-
*/
-
public
static
final
String
AUTO_REOMVE_PRE
=
"true";
-
-
/**
-
* 当前记录起始索引
-
*/
-
public
static
final
String
PAGE_NUM
=
"pageNum";
-
-
/**
-
* 每页显示记录数
-
*/
-
public
static
final
String
PAGE_SIZE
=
"pageSize";
-
-
/**
-
* 排序列
-
*/
-
public
static
final
String
ORDER_BY_COLUMN
=
"sortField";
-
-
/**
-
* 排序的方向 "desc" 或者 "asc".
-
*/
-
public
static
final
String
IS_ASC
=
"sortOrder";
-
-
public
static
final
String
CURRENT_ID
=
"current_id";
-
-
public
static
final
String
CURRENT_USERNAME
=
"current_username";
-
-
public
static
final
String
TOKEN
=
"token";
-
-
public
static
final
String
DEFAULT_CODE_KEY
=
"random_code_";
-
-
public
final
static
String
ACCESS_TOKEN
=
"access_token_";
-
-
public
final
static
String
ACCESS_USERID
=
"access_userid_";
-
-
public
static
final
String
RESOURCE_PREFIX
=
"/profile";
-
}
2)RedisEvict注解
-
package com.ceam.common.redis.annotation;
-
-
import java.lang.annotation.*;
-
-
/**
-
* <p>File:RedisEvict.java</p>
-
* <p>Title: redis删除注解</p>
-
* <p>Description:</p>
-
*/
-
@Target({ElementType.METHOD})
-
@Retention(RetentionPolicy.RUNTIME)
-
@Documented
-
public
@interface RedisEvict
-
{
-
String
key
();
-
-
String
fieldKey
();
-
}
3)RedisUtils工具类
-
package com.ceam.common.redis.util;
-
-
import com.alibaba.fastjson.JSON;
-
import org.springframework.beans.factory.annotation.Autowired;
-
import org.springframework.context.annotation.ComponentScan;
-
import org.springframework.data.redis.core.RedisTemplate;
-
import org.springframework.data.redis.core.ValueOperations;
-
import org.springframework.stereotype.Component;
-
-
import javax.annotation.Resource;
-
import java.util.concurrent.TimeUnit;
-
-
/**
-
* Redis工具类
-
*/
-
@Component
-
@ComponentScan(basePackages = {"com.ceam.common.redis"})
-
public
class
RedisUtils
-
{
-
@Autowired
-
private RedisTemplate<String, Object> redisTemplate;
-
-
@Resource(name = "stringRedisTemplate")
-
private ValueOperations<String, String> valueOperations;
-
-
/** 默认过期时长,单位:秒 */
-
public
final
static
long
DEFAULT_EXPIRE
=
60 *
60 *
24;
-
-
/** 不设置过期时长 */
-
public
final
static
long
NOT_EXPIRE
= -
1;
-
-
/**
-
* 插入缓存默认时间
-
* @param key 键
-
* @param value 值
-
*/
-
public
void
set
(String key, Object value)
-
{
-
set(key, value, DEFAULT_EXPIRE);
-
}
-
-
/**
-
* 插入缓存
-
* @param key 键
-
* @param value 值
-
* @param expire 过期时间(s)
-
*/
-
public
void
set
(String key, Object value, long expire)
-
{
-
valueOperations.set(key, toJson(value));
-
redisTemplate.expire(key, expire, TimeUnit.SECONDS);
-
}
-
-
/**
-
* 返回字符串结果
-
* @param key 键
-
* @return
-
*/
-
public String
get
(String key)
-
{
-
return valueOperations.get(key);
-
}
-
-
/**
-
* 返回指定类型结果
-
* @param key 键
-
* @param clazz 类型class
-
* @return
-
*/
-
public <T> T
get
(String key, Class<T> clazz)
-
{
-
String
value
= valueOperations.get(key);
-
return
value
==
null ?
null : fromJson(value, clazz);
-
}
-
-
/**
-
* 删除缓存
-
* @param key 键
-
*/
-
public
void
delete
(String key)
-
{
-
redisTemplate.delete(key);
-
}
-
-
/**
-
* Object转成JSON数据
-
*/
-
private String
toJson
(Object object)
-
{
-
if (object
instanceof Integer || object
instanceof Long || object
instanceof Float || object
instanceof Double
-
|| object
instanceof Boolean || object
instanceof String)
-
{
-
return String.valueOf(object);
-
}
-
return JSON.toJSONString(object);
-
}
-
-
/**
-
* JSON数据,转成Object
-
*/
-
private <T> T
fromJson
(String json, Class<T> clazz)
-
{
-
return JSON.parseObject(json, clazz);
-
}
-
}
4)UserConstants用户常量信息
-
package com.ceam.common.constant;
-
-
/**
-
* 用户常量信息
-
*/
-
public
class
UserConstants
-
{
-
/**
-
* 平台内系统用户的唯一标志
-
*/
-
public
static
final
String
SYS_USER
=
"SYS_USER";
-
-
/** 正常状态 */
-
public
static
final
String
NORMAL
=
"0";
-
-
/** 异常状态 */
-
public
static
final
String
EXCEPTION
=
"1";
-
-
/** 用户封禁状态 */
-
public
static
final
String
USER_BLOCKED
=
"1";
-
-
/** 角色封禁状态 */
-
public
static
final
String
ROLE_BLOCKED
=
"1";
-
-
/** 部门正常状态 */
-
public
static
final
String
DEPT_NORMAL
=
"0";
-
-
/**
-
* 用户名长度限制
-
*/
-
public
static
final
int
USERNAME_MIN_LENGTH
=
2;
-
public
static
final
int
USERNAME_MAX_LENGTH
=
20;
-
-
/** 登录名称是否唯一的返回结果码 */
-
public
final
static
String
USER_NAME_UNIQUE
=
"0";
-
public
final
static
String
USER_NAME_NOT_UNIQUE
=
"1";
-
-
/** 手机号码是否唯一的返回结果 */
-
public
final
static
String
USER_PHONE_UNIQUE
=
"0";
-
public
final
static
String
USER_PHONE_NOT_UNIQUE
=
"1";
-
-
/** e-mail 是否唯一的返回结果 */
-
public
final
static
String
USER_EMAIL_UNIQUE
=
"0";
-
public
final
static
String
USER_EMAIL_NOT_UNIQUE
=
"1";
-
-
/** 部门名称是否唯一的返回结果码 */
-
public
final
static
String
DEPT_NAME_UNIQUE
=
"0";
-
public
final
static
String
DEPT_NAME_NOT_UNIQUE
=
"1";
-
-
/** 角色名称是否唯一的返回结果码 */
-
public
final
static
String
ROLE_NAME_UNIQUE
=
"0";
-
public
final
static
String
ROLE_NAME_NOT_UNIQUE
=
"1";
-
-
/** 岗位名称是否唯一的返回结果码 */
-
public
final
static
String
POST_NAME_UNIQUE
=
"0";
-
public
final
static
String
POST_NAME_NOT_UNIQUE
=
"1";
-
-
/** 角色权限是否唯一的返回结果码 */
-
public
final
static
String
ROLE_KEY_UNIQUE
=
"0";
-
public
final
static
String
ROLE_KEY_NOT_UNIQUE
=
"1";
-
-
/** 岗位编码是否唯一的返回结果码 */
-
public
final
static
String
POST_CODE_UNIQUE
=
"0";
-
public
final
static
String
POST_CODE_NOT_UNIQUE
=
"1";
-
-
/** 菜单名称是否唯一的返回结果码 */
-
public
final
static
String
MENU_NAME_UNIQUE
=
"0";
-
public
final
static
String
MENU_NAME_NOT_UNIQUE
=
"1";
-
-
/** 字典类型是否唯一的返回结果码 */
-
public
final
static
String
DICT_TYPE_UNIQUE
=
"0";
-
public
final
static
String
DICT_TYPE_NOT_UNIQUE
=
"1";
-
-
/** 参数键名是否唯一的返回结果码 */
-
public
final
static
String
CONFIG_KEY_UNIQUE
=
"0";
-
public
final
static
String
CONFIG_KEY_NOT_UNIQUE
=
"1";
-
-
/**
-
* 密码长度限制
-
*/
-
public
static
final
int
PASSWORD_MIN_LENGTH
=
5;
-
public
static
final
int
PASSWORD_MAX_LENGTH
=
20;
-
-
/**
-
* 手机号码格式限制
-
*/
-
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}$";
-
-
/**
-
* 邮箱格式限制
-
*/
-
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])))\\.?";
-
}
5)UserStatus用户状态
-
package com.ceam.common.enums;
-
-
/**
-
* 用户状态
-
*/
-
public
enum
UserStatus
-
{
-
OK(
"0",
"正常"), DISABLE(
"1",
"停用"), DELETED(
"2",
"删除");
-
-
private
final String code;
-
private
final String info;
-
-
UserStatus(String code, String info)
-
{
-
this.code = code;
-
this.info = info;
-
}
-
-
public String
getCode
()
-
{
-
return code;
-
}
-
-
public String
getInfo
()
-
{
-
return info;
-
}
-
}
6)UserBlockedException用户锁定异常类
-
package com.ceam.common.exception.user;
-
-
/**
-
* 用户锁定异常类
-
*/
-
public
class
UserBlockedException
extends
UserException
-
{
-
private
static
final
long
serialVersionUID
=
1L;
-
-
public
UserBlockedException
()
-
{
-
super(
"user.blocked",
null);
-
}
-
}
7)UserDeleteException用户账号已被删除
-
package com.ceam.common.exception.user;
-
-
/**
-
* 用户账号已被删除
-
*/
-
public
class
UserDeleteException
extends
UserException
-
{
-
private
static
final
long
serialVersionUID
=
1L;
-
-
public
UserDeleteException
()
-
{
-
super(
"user.password.delete",
null);
-
}
-
}
8)UserNotExistsException用户不存在异常类
-
package com.ceam.common.exception.user;
-
-
/**
-
* 用户不存在异常类
-
*/
-
public
class
UserNotExistsException
extends
UserException
-
{
-
private
static
final
long
serialVersionUID
=
1L;
-
-
public
UserNotExistsException
()
-
{
-
super(
"user.not.exists",
null);
-
}
-
}
9)UserPasswordNotMatchException用户密码不正确或不符合规范异常类
-
package com.ceam.common.exception.user;
-
-
/**
-
* 用户密码不正确或不符合规范异常类
-
*/
-
public
class
UserPasswordNotMatchException
extends
UserException
-
{
-
private
static
final
long
serialVersionUID
=
1L;
-
-
public
UserPasswordNotMatchException
()
-
{
-
super(
"user.password.not.match",
null);
-
}
-
}
10)PublishFactory
-
package com.ceam.common.log.publish;
-
-
import com.ceam.common.constant.Constants;
-
import com.ceam.common.log.event.SysLogininforEvent;
-
import com.ceam.common.utils.AddressUtils;
-
import com.ceam.common.utils.IpUtils;
-
import com.ceam.common.utils.ServletUtils;
-
import com.ceam.common.utils.spring.SpringContextHolder;
-
import com.ceam.system.domain.SysLogininfor;
-
import eu.bitwalker.useragentutils.UserAgent;
-
-
import javax.servlet.http.HttpServletRequest;
-
-
public
class
PublishFactory
-
{
-
/**
-
* 记录登陆信息
-
*
-
* @param username 用户名
-
* @param status 状态
-
* @param message 消息
-
* @param args 列表
-
*/
-
public
static
void
recordLogininfor
(final String username, final String status, final String message,
-
final Object ... args)
-
{
-
HttpServletRequest
request
= ServletUtils.getRequest();
-
final
UserAgent
userAgent
= UserAgent.parseUserAgentString(request.getHeader(
"User-Agent"));
-
final
String
ip
= IpUtils.getIpAddr(request);
-
// 获取客户端操作系统
-
String
os
= userAgent.getOperatingSystem().getName();
-
// 获取客户端浏览器
-
String
browser
= userAgent.getBrowser().getName();
-
// 封装对象
-
SysLogininfor
logininfor
=
new
SysLogininfor();
-
logininfor.setLoginName(username);
-
logininfor.setIpaddr(ip);
-
logininfor.setLoginLocation(AddressUtils.getRealAddressByIP(ip));
-
logininfor.setBrowser(browser);
-
logininfor.setOs(os);
-
logininfor.setMsg(message);
-
// 日志状态
-
if (Constants.LOGIN_SUCCESS.equals(status) || Constants.LOGOUT.equals(status))
-
{
-
logininfor.setStatus(Constants.SUCCESS);
-
}
-
else
if (Constants.LOGIN_FAIL.equals(status))
-
{
-
logininfor.setStatus(Constants.FAIL);
-
}
-
// 发布事件
-
SpringContextHolder.publishEvent(
new
SysLogininforEvent(logininfor));
-
}
-
}
11)DateUtils 时间工具类
-
package com.ceam.common.utils;
-
-
import org.apache.commons.lang3.time.DateFormatUtils;
-
-
import java.lang.management.ManagementFactory;
-
import java.text.ParseException;
-
import java.text.SimpleDateFormat;
-
import java.util.Date;
-
-
/**
-
* 时间工具类
-
*/
-
public
class
DateUtils
extends
org.apache.commons.lang3.time.DateUtils
-
{
-
public
static
String
YYYY
=
"yyyy";
-
-
public
static
String
YYYY_MM
=
"yyyy-MM";
-
-
public
static
String
YYYY_MM_DD
=
"yyyy-MM-dd";
-
-
public
static
String
YYYYMMDDHHMMSS
=
"yyyyMMddHHmmss";
-
-
public
static
String
YYYY_MM_DD_HH_MM_SS
=
"yyyy-MM-dd HH:mm:ss";
-
-
private
static String[] parsePatterns = {
-
"yyyy-MM-dd",
"yyyy-MM-dd HH:mm:ss",
"yyyy-MM-dd HH:mm",
"yyyy-MM",
-
"yyyy/MM/dd",
"yyyy/MM/dd HH:mm:ss",
"yyyy/MM/dd HH:mm",
"yyyy/MM",
-
"yyyy.MM.dd",
"yyyy.MM.dd HH:mm:ss",
"yyyy.MM.dd HH:mm",
"yyyy.MM"};
-
-
/**
-
* 获取当前Date型日期
-
*
-
* @return Date() 当前日期
-
*/
-
public
static Date
getNowDate
()
-
{
-
return
new
Date();
-
}
-
-
/**
-
* 获取当前日期, 默认格式为yyyy-MM-dd
-
*
-
* @return String
-
*/
-
public
static String
getDate
()
-
{
-
return dateTimeNow(YYYY_MM_DD);
-
}
-
-
public
static
final String
getTime
()
-
{
-
return dateTimeNow(YYYY_MM_DD_HH_MM_SS);
-
}
-
-
public
static
final String
dateTimeNow
()
-
{
-
return dateTimeNow(YYYYMMDDHHMMSS);
-
}
-
-
public
static
final String
dateTimeNow
(final String format)
-
{
-
return parseDateToStr(format,
new
Date());
-
}
-
-
public
static
final String
dateTime
(final Date date)
-
{
-
return parseDateToStr(YYYY_MM_DD, date);
-
}
-
-
public
static
final String
parseDateToStr
(final String format, final Date date)
-
{
-
return
new
SimpleDateFormat(format).format(date);
-
}
-
-
public
static
final Date
dateTime
(final String format, final String ts)
-
{
-
try
-
{
-
return
new
SimpleDateFormat(format).parse(ts);
-
}
-
catch (ParseException e)
-
{
-
throw
new
RuntimeException(e);
-
}
-
}
-
-
/**
-
* 日期路径 即年/月/日 如2018/08/08
-
*/
-
public
static
final String
datePath
()
-
{
-
Date
now
=
new
Date();
-
return DateFormatUtils.format(now,
"yyyy/MM/dd");
-
}
-
-
/**
-
* 日期路径 即年/月/日 如20180808
-
*/
-
public
static
final String
dateTime
()
-
{
-
Date
now
=
new
Date();
-
return DateFormatUtils.format(now,
"yyyyMMdd");
-
}
-
-
/**
-
* 日期型字符串转化为日期 格式
-
*/
-
public
static Date
parseDate
(Object str)
-
{
-
if (str ==
null)
-
{
-
return
null;
-
}
-
try
-
{
-
return parseDate(str.toString(), parsePatterns);
-
}
-
catch (ParseException e)
-
{
-
return
null;
-
}
-
}
-
-
/**
-
* 获取服务器启动时间
-
*/
-
public
static Date
getServerStartDate
()
-
{
-
long
time
= ManagementFactory.getRuntimeMXBean().getStartTime();
-
return
new
Date(time);
-
}
-
-
/**
-
* 计算两个时间差
-
*/
-
public
static String
getDatePoor
(Date endDate, Date nowDate)
-
{
-
long
nd
=
1000 *
24 *
60 *
60;
-
long
nh
=
1000 *
60 *
60;
-
long
nm
=
1000 *
60;
-
// long ns = 1000;
-
// 获得两个时间的毫秒时间差异
-
long
diff
= endDate.getTime() - nowDate.getTime();
-
// 计算差多少天
-
long
day
= diff / nd;
-
// 计算差多少小时
-
long
hour
= diff % nd / nh;
-
// 计算差多少分钟
-
long
min
= diff % nd % nh / nm;
-
// 计算差多少秒//输出结果
-
// long sec = diff % nd % nh % nm / ns;
-
return day +
"天" + hour +
"小时" + min +
"分钟";
-
}
-
}
12)IpUtils获取IP方法
-
package com.ceam.common.utils;
-
-
import javax.servlet.http.HttpServletRequest;
-
import java.net.InetAddress;
-
import java.net.UnknownHostException;
-
-
/**
-
* 获取IP方法
-
*/
-
public
class
IpUtils
-
{
-
public
static String
getIpAddr
(HttpServletRequest request)
-
{
-
if (request ==
null)
-
{
-
return
"unknown";
-
}
-
String
ip
= request.getHeader(
"x-forwarded-for");
-
if (ip ==
null || ip.length() ==
0 ||
"unknown".equalsIgnoreCase(ip))
-
{
-
ip = request.getHeader(
"Proxy-Client-IP");
-
}
-
if (ip ==
null || ip.length() ==
0 ||
"unknown".equalsIgnoreCase(ip))
-
{
-
ip = request.getHeader(
"X-Forwarded-For");
-
}
-
if (ip ==
null || ip.length() ==
0 ||
"unknown".equalsIgnoreCase(ip))
-
{
-
ip = request.getHeader(
"WL-Proxy-Client-IP");
-
}
-
if (ip ==
null || ip.length() ==
0 ||
"unknown".equalsIgnoreCase(ip))
-
{
-
ip = request.getHeader(
"X-Real-IP");
-
}
-
-
if (ip ==
null || ip.length() ==
0 ||
"unknown".equalsIgnoreCase(ip))
-
{
-
ip = request.getRemoteAddr();
-
}
-
-
return
"0:0:0:0:0:0:0:1".equals(ip) ?
"127.0.0.1" : ip.split(
",")[
0];
-
}
-
-
public
static
boolean
internalIp
(String ip)
-
{
-
byte[] addr = textToNumericFormatV4(ip);
-
if (
null != addr) {
-
return internalIp(addr) ||
"127.0.0.1".equals(ip);
-
}
-
return
false;
-
}
-
-
private
static
boolean
internalIp
(byte[] addr)
-
{
-
final
byte
b0
= addr[
0];
-
final
byte
b1
= addr[
1];
-
// 10.x.x.x/8
-
final
byte
SECTION_1
=
0x0A;
-
// 172.16.x.x/12
-
final
byte
SECTION_2
= (
byte)
0xAC;
-
final
byte
SECTION_3
= (
byte)
0x10;
-
final
byte
SECTION_4
= (
byte)
0x1F;
-
// 192.168.x.x/16
-
final
byte
SECTION_5
= (
byte)
0xC0;
-
final
byte
SECTION_6
= (
byte)
0xA8;
-
switch (b0)
-
{
-
case SECTION_1:
-
return
true;
-
case SECTION_2:
-
if (b1 >= SECTION_3 && b1 <= SECTION_4)
-
{
-
return
true;
-
}
-
case SECTION_5:
-
switch (b1)
-
{
-
case SECTION_6:
-
return
true;
-
}
-
default:
-
return
false;
-
}
-
}
-
-
/**
-
* 将IPv4地址转换成字节
-
*
-
* @param text IPv4地址
-
* @return byte 字节
-
*/
-
public
static
byte[] textToNumericFormatV4(String text)
-
{
-
if (text.length() ==
0)
-
{
-
return
null;
-
}
-
-
byte[] bytes =
new
byte[
4];
-
String[] elements = text.split(
"\\.", -
1);
-
try
-
{
-
long l;
-
int i;
-
switch (elements.length)
-
{
-
case
1:
-
l = Long.parseLong(elements[
0]);
-
if ((l <
0L) || (l >
4294967295L))
-
return
null;
-
bytes[
0] = (
byte) (
int) (l >>
24 &
0xFF);
-
bytes[
1] = (
byte) (
int) ((l &
0xFFFFFF) >>
16 &
0xFF);
-
bytes[
2] = (
byte) (
int) ((l &
0xFFFF) >>
8 &
0xFF);
-
bytes[
3] = (
byte) (
int) (l &
0xFF);
-
break;
-
case
2:
-
l = Integer.parseInt(elements[
0]);
-
if ((l <
0L) || (l >
255L))
-
return
null;
-
bytes[
0] = (
byte) (
int) (l &
0xFF);
-
l = Integer.parseInt(elements[
1]);
-
if ((l <
0L) || (l >
16777215L))
-
return
null;
-
bytes[
1] = (
byte) (
int) (l >>
16 &
0xFF);
-
bytes[
2] = (
byte) (
int) ((l &
0xFFFF) >>
8 &
0xFF);
-
bytes[
3] = (
byte) (
int) (l &
0xFF);
-
break;
-
case
3:
-
for (i =
0; i <
2; ++i)
-
{
-
l = Integer.parseInt(elements[i]);
-
if ((l <
0L) || (l >
255L))
-
return
null;
-
bytes[i] = (
byte) (
int) (l &
0xFF);
-
}
-
l = Integer.parseInt(elements[
2]);
-
if ((l <
0L) || (l >
65535L))
-
return
null;
-
bytes[
2] = (
byte) (
int) (l >>
8 &
0xFF);
-
bytes[
3] = (
byte) (
int) (l &
0xFF);
-
break;
-
case
4:
-
for (i =
0; i <
4; ++i)
-
{
-
l = Integer.parseInt(elements[i]);
-
if ((l <
0L) || (l >
255L))
-
return
null;
-
bytes[i] = (
byte) (
int) (l &
0xFF);
-
}
-
break;
-
default:
-
return
null;
-
}
-
}
-
catch (NumberFormatException e)
-
{
-
return
null;
-
}
-
return bytes;
-
}
-
-
public
static String
getHostIp
()
-
{
-
try
-
{
-
return InetAddress.getLocalHost().getHostAddress();
-
}
-
catch (UnknownHostException e)
-
{
-
}
-
return
"127.0.0.1";
-
}
-
-
public
static String
getHostName
()
-
{
-
try
-
{
-
return InetAddress.getLocalHost().getHostName();
-
}
-
catch (UnknownHostException e)
-
{
-
}
-
return
"未知";
-
}
-
}
13)MessageUtils获取i18n资源文件
-
package com.ceam.common.utils;
-
-
import com.ceam.common.utils.spring.SpringUtils;
-
import org.springframework.context.MessageSource;
-
import org.springframework.context.i18n.LocaleContextHolder;
-
-
/**
-
* 获取i18n资源文件
-
*/
-
public
class
MessageUtils
-
{
-
/**
-
* 根据消息键和参数 获取消息 委托给spring messageSource
-
*
-
* @param code 消息键
-
* @param args 参数
-
* @return 获取国际化翻译值
-
*/
-
public
static String
message
(String code, Object... args)
-
{
-
MessageSource
messageSource
= SpringUtils.getBean(MessageSource.class);
-
return messageSource.getMessage(code, args, LocaleContextHolder.getLocale());
-
}
-
}
14)ServletUtils客户端工具类
-
package com.ceam.common.utils;
-
-
import com.ceam.common.core.text.Convert;
-
import org.springframework.web.context.request.RequestAttributes;
-
import org.springframework.web.context.request.RequestContextHolder;
-
import org.springframework.web.context.request.ServletRequestAttributes;
-
-
import javax.servlet.http.HttpServletRequest;
-
import javax.servlet.http.HttpServletResponse;
-
import javax.servlet.http.HttpSession;
-
import java.io.IOException;
-
-
/**
-
* 客户端工具类
-
*/
-
public
class
ServletUtils
-
{
-
/**
-
* 获取String参数
-
*/
-
public
static String
getParameter
(String name)
-
{
-
return getRequest().getParameter(name);
-
}
-
-
/**
-
* 获取String参数
-
*/
-
public
static String
getParameter
(String name, String defaultValue)
-
{
-
return Convert.toStr(getRequest().getParameter(name), defaultValue);
-
}
-
-
/**
-
* 获取Integer参数
-
*/
-
public
static Integer
getParameterToInt
(String name)
-
{
-
return Convert.toInt(getRequest().getParameter(name));
-
}
-
-
/**
-
* 获取Integer参数
-
*/
-
public
static Integer
getParameterToInt
(String name, Integer defaultValue)
-
{
-
return Convert.toInt(getRequest().getParameter(name), defaultValue);
-
}
-
-
/**
-
* 获取request
-
*/
-
public
static HttpServletRequest
getRequest
()
-
{
-
return getRequestAttributes().getRequest();
-
}
-
-
/**
-
* 获取response
-
*/
-
public
static HttpServletResponse
getResponse
()
-
{
-
return getRequestAttributes().getResponse();
-
}
-
-
/**
-
* 获取session
-
*/
-
public
static HttpSession
getSession
()
-
{
-
return getRequest().getSession();
-
}
-
-
public
static ServletRequestAttributes
getRequestAttributes
()
-
{
-
RequestAttributes
attributes
= RequestContextHolder.getRequestAttributes();
-
return (ServletRequestAttributes) attributes;
-
}
-
-
/**
-
* 将字符串渲染到客户端
-
*
-
* @param response 渲染对象
-
* @param string 待渲染的字符串
-
* @return null
-
*/
-
public
static String
renderString
(HttpServletResponse response, String string)
-
{
-
try
-
{
-
response.setContentType(
"application/json");
-
response.setCharacterEncoding(
"utf-8");
-
response.getWriter().print(string);
-
}
-
catch (IOException e)
-
{
-
e.printStackTrace();
-
}
-
return
null;
-
}
-
-
/**
-
* 是否是Ajax异步请求
-
*
-
* @param request
-
*/
-
public
static
boolean
isAjaxRequest
(HttpServletRequest request)
-
{
-
String
accept
= request.getHeader(
"accept");
-
if (accept !=
null && accept.indexOf(
"application/json") != -
1)
-
{
-
return
true;
-
}
-
-
String
xRequestedWith
= request.getHeader(
"X-Requested-With");
-
if (xRequestedWith !=
null && xRequestedWith.indexOf(
"XMLHttpRequest") != -
1)
-
{
-
return
true;
-
}
-
-
String
uri
= request.getRequestURI();
-
if (StringUtils.inStringIgnoreCase(uri,
".json",
".xml"))
-
{
-
return
true;
-
}
-
-
String
ajax
= request.getParameter(
"__ajax");
-
if (StringUtils.inStringIgnoreCase(ajax,
"json",
"xml"))
-
{
-
return
true;
-
}
-
return
false;
-
}
-
}
如果发现不完整,读者自行调整,简单地完成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处理逻辑。
如果对你有帮助,就点赞、收藏、评论吧,不要白嫖人家啦
前面后面都有惊喜,关注不迷路
转载:https://blog.csdn.net/qq_57756904/article/details/125237872