diff --git a/.idea/compiler.xml b/.idea/compiler.xml index e24ccc08..00c72ccf 100644 --- a/.idea/compiler.xml +++ b/.idea/compiler.xml @@ -6,17 +6,17 @@ - - + + - - + - - + + + diff --git a/.idea/dqFinancial.iml b/.idea/dqFinancial.iml new file mode 100644 index 00000000..78b2cc53 --- /dev/null +++ b/.idea/dqFinancial.iml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/.idea/encodings.xml b/.idea/encodings.xml index 30d52777..f46cbaa0 100644 --- a/.idea/encodings.xml +++ b/.idea/encodings.xml @@ -3,11 +3,18 @@ + + + + + + + \ No newline at end of file diff --git a/.idea/jarRepositories.xml b/.idea/jarRepositories.xml new file mode 100644 index 00000000..712ab9d9 --- /dev/null +++ b/.idea/jarRepositories.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/dq-govern-gateway/pom.xml b/dq-govern-gateway/pom.xml index 70da4975..8eda2099 100644 --- a/dq-govern-gateway/pom.xml +++ b/dq-govern-gateway/pom.xml @@ -30,7 +30,27 @@ org.springframework.cloud spring-cloud-starter-gateway + + commons-lang + commons-lang + 2.6 + + + org.apache.commons + commons-lang3 + 3.8.1 + + + + com.auth0 + java-jwt + 3.3.0 + + + org.springframework.boot + spring-boot-starter-data-redis + org.springframework.boot spring-boot-starter-test diff --git a/dq-govern-gateway/src/main/java/com/daqing/financial/gateway/SpringContextHolder.java b/dq-govern-gateway/src/main/java/com/daqing/financial/gateway/SpringContextHolder.java new file mode 100644 index 00000000..cd8a20a0 --- /dev/null +++ b/dq-govern-gateway/src/main/java/com/daqing/financial/gateway/SpringContextHolder.java @@ -0,0 +1,64 @@ +package com.daqing.financial.gateway; + +import org.springframework.beans.factory.DisposableBean; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.context.annotation.Lazy; +import org.springframework.stereotype.Component; + +@Component +@Lazy(false) +public class SpringContextHolder implements ApplicationContextAware, DisposableBean { + + private static ApplicationContext applicationContext = null; + + + /** + * 取得存储在静态变量中的ApplicationContext. + */ + public static ApplicationContext getApplicationContext() { + return applicationContext; + } + + /** + * 从静态变量applicationContext中取得Bean, 自动转型为所赋值对象的类型. + */ + @SuppressWarnings("unchecked") + public static T getBean(String name) { + return (T) applicationContext.getBean(name); + } + + /** + * 从静态变量applicationContext中取得Bean, 自动转型为所赋值对象的类型. + */ + public static T getBean(Class requiredType) { + return applicationContext.getBean(requiredType); + } + + public static T getBean(String name, Class clazz) { + return getApplicationContext().getBean(name, clazz); + } + + /** + * 清除SpringContextHolder中的ApplicationContext为Null. + */ + public static void clearHolder() { + applicationContext = null; + } + + /** + * 实现ApplicationContextAware接口, 注入Context到静态变量中. + */ + @Override + public void setApplicationContext(ApplicationContext appContext) { + applicationContext = appContext; + } + + /** + * 实现DisposableBean接口, 在Context关闭时清理静态变量. + */ + @Override + public void destroy() { + SpringContextHolder.clearHolder(); + } +} \ No newline at end of file diff --git a/dq-govern-gateway/src/main/java/com/daqing/financial/gateway/config/ApiGlobalFilter.java b/dq-govern-gateway/src/main/java/com/daqing/financial/gateway/config/ApiGlobalFilter.java new file mode 100644 index 00000000..ed0a648f --- /dev/null +++ b/dq-govern-gateway/src/main/java/com/daqing/financial/gateway/config/ApiGlobalFilter.java @@ -0,0 +1,114 @@ +package com.daqing.financial.gateway.config; + +import com.alibaba.fastjson.JSONObject; +import com.daqing.financial.gateway.util.JwtUtil; +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.cloud.gateway.filter.GatewayFilterChain; +import org.springframework.cloud.gateway.filter.GlobalFilter; +import org.springframework.context.annotation.PropertySource; +import org.springframework.core.Ordered; +import org.springframework.core.io.buffer.DataBuffer; +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.util.CollectionUtils; +import org.springframework.web.server.ServerWebExchange; +import reactor.core.publisher.Mono; + +import java.nio.charset.StandardCharsets; +import java.util.Enumeration; +import java.util.List; + +@Component +@PropertySource(value = "classpath:jwt.properties") +public class ApiGlobalFilter implements GlobalFilter, Ordered { + + /** + * 不进行token校验的请求地址 + */ + @Value("#{'${jwt.ignoreUrlList}'.split(',')}") + public List ignoreUrl; + + /** + * 拦截所有的请求头 + * @param exchange + * @param chain + * @return + */ + @Override + public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { + String requestUrl = exchange.getRequest().getPath().toString(); + boolean status = CollectionUtils.contains((Enumeration) ignoreUrl, requestUrl); + if (!status){ + String token = exchange.getRequest().getHeaders().getFirst("token"); + //type用于区分不同的端,在做校验token时需要 + String type= exchange.getRequest().getHeaders().getFirst("type"); + ServerHttpResponse response = exchange.getResponse(); + //没有数据 + if (StringUtils.isBlank(token) || StringUtils.isBlank(type)) { + JSONObject message = new JSONObject(); + message.put("code", ""); + message.put("message", "鉴权失败,无token或类型"); + byte[] bits = message.toString().getBytes(StandardCharsets.UTF_8); + DataBuffer buffer = response.bufferFactory().wrap(bits); + response.setStatusCode(HttpStatus.UNAUTHORIZED); + response.getHeaders().add("Content-Type", "text/json;charset=UTF-8"); + return response.writeWith(Mono.just(buffer)); + //有数据 + }else { + String prefix = this.getPrefix(type); + //校验token + Long userId = verifyJWT(token ,prefix); + if (userId == null){ + JSONObject message = new JSONObject(); + message.put("message", "token错误"); + message.put("code", ""); + byte[] bits = message.toString().getBytes(StandardCharsets.UTF_8); + DataBuffer buffer = response.bufferFactory().wrap(bits); + response.setStatusCode(HttpStatus.UNAUTHORIZED); + response.getHeaders().add("Content-Type", "text/json;charset=UTF-8"); + return response.writeWith(Mono.just(buffer)); + } + //将现在的request,添加当前身份 + ServerHttpRequest mutableReq = exchange.getRequest().mutate().header("Authorization-UserId", String.valueOf(userId)).build(); + ServerWebExchange mutableExchange = exchange.mutate().request(mutableReq).build(); + return chain.filter(mutableExchange); + } + } + return chain.filter(exchange); + } + + /** + * JWT验证 + * @param token + * @return userPhone + */ + private Long verifyJWT(String token, String prefix){ + return JwtUtil.verifyToken(token); + } + + /** + * 根据type获取前缀 + * @param type + * @return + */ + private String getPrefix(String type){ + String prefix = null; + if ("1".equals(type)){ + prefix = "OPERATE"; + }else if ("2".equals(type)){ + prefix = "USER"; + }else if ("3".equals(type)){ + prefix = "WX"; + } + return prefix; + } + + @Override + public int getOrder() { + return -200; + } +} + diff --git a/dq-govern-gateway/src/main/java/com/daqing/financial/gateway/util/JwtUtil.java b/dq-govern-gateway/src/main/java/com/daqing/financial/gateway/util/JwtUtil.java new file mode 100644 index 00000000..6e495502 --- /dev/null +++ b/dq-govern-gateway/src/main/java/com/daqing/financial/gateway/util/JwtUtil.java @@ -0,0 +1,70 @@ +package com.daqing.financial.gateway.util; + +import com.auth0.jwt.JWT; +import com.auth0.jwt.algorithms.Algorithm; + +import com.daqing.financial.gateway.SpringContextHolder; + +import java.util.Date; +import java.util.Set; + +/** + * @author zcw + * @version 1.0 + * @date 2019/11/23 11:06 + * @description jwt工具类 + */ +public class JwtUtil { + + private final static Algorithm algorithm = SpringContextHolder.getBean("algorithm", Algorithm.class); + + private final static OdcProperties properties = SpringContextHolder.getBean("odcProperties", OdcProperties.class); + + /** + * 创建token + * + * @param userId; + * @param timeout; 单位是秒 + */ + public static String createJwtToken(Long userId, long timeout) { + return JWT.create() + .withClaim("member", userId) + .withExpiresAt(new Date(System.currentTimeMillis() + timeout * 1000)) + .sign(algorithm); + } + + /** + * token正确且有效,则返回userId + */ + public static Long verifyToken(String token) { + try { + String noBearerToken = token.replaceFirst("Bearer ", ""); + Long userId = JWT.require(algorithm) + .build() + .verify(noBearerToken) + .getClaim("member") + .asLong(); + if (RedisUtil.get(getRedisKey(userId, noBearerToken)) != null) { + return userId; + } + } catch (Exception e) { + throw new OdcException(ResultCodeEnum.UN_AUTHORIZATION); + } + throw new OdcException(ResultCodeEnum.UN_AUTHORIZATION); + } + + public static String getRedisKey(Long userId, String token) { + return String.format(properties.getConfig().getTokenRedisKeyFormat(), userId, token); + } + + public static void putTokenToRedis(Long userId, String token) { + RedisUtil.setEx(getRedisKey(userId, token), "nothing", properties.getConfig().getTokenExpireSeconds()); + } + + public static void removeTokenByUserId(Long userId) { + Set tokenSet = RedisUtil.keys(getRedisKey(userId, "*")); + for (String key : tokenSet) { + RedisUtil.del(key); + } + } +} diff --git a/dq-govern-gateway/src/main/java/com/daqing/financial/gateway/util/OdcException.java b/dq-govern-gateway/src/main/java/com/daqing/financial/gateway/util/OdcException.java new file mode 100644 index 00000000..96b11e1d --- /dev/null +++ b/dq-govern-gateway/src/main/java/com/daqing/financial/gateway/util/OdcException.java @@ -0,0 +1,24 @@ +package com.daqing.financial.gateway.util; + +import lombok.Data; + +@Data +public class OdcException extends RuntimeException { + + private int code; + + private ResultCodeEnum resultCodeEnum; + + public OdcException(ResultCodeEnum codeEnum) { + super(codeEnum.getRemark()); + code = codeEnum.getCode(); + } + + public int getCode() { + return code; + } + + public ResultCodeEnum getResultCodeEnum() { + return resultCodeEnum; + } +} \ No newline at end of file diff --git a/dq-govern-gateway/src/main/java/com/daqing/financial/gateway/util/OdcProperties.java b/dq-govern-gateway/src/main/java/com/daqing/financial/gateway/util/OdcProperties.java new file mode 100644 index 00000000..2d8890f9 --- /dev/null +++ b/dq-govern-gateway/src/main/java/com/daqing/financial/gateway/util/OdcProperties.java @@ -0,0 +1,50 @@ +package com.daqing.financial.gateway.util; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +@Data +@Configuration +@ConfigurationProperties(prefix = "com.odcchina", ignoreUnknownFields = true) +public class OdcProperties { + + private Oss oss = new Oss(); + + private Async async = new Async(); + + private Config config = new Config(); + + @Data + public static class Oss { + private String endpoint; + private String accessKeyId; + private String accessKeySecret; + private String bucketName; + private String prefixUrl; + } + + @Data + public static class Async { + private int corePoolSize = 10; + private int maxPoolSize = 40; + private int queueCapacity = 20; + private int keepAliveSeconds = 30; + } + + @Data + public static class Config { + + private String tokenHeader = "Authorization"; + + //登录token,redis key格式 + private String tokenRedisKeyFormat = "dcm:token:%d:%s"; + + //token有效期,10年 + private long tokenExpireSeconds = 315360000L; + + //限制只能登录一个 + private boolean limitLoginOneOnly = true; + + } +} diff --git a/dq-govern-gateway/src/main/java/com/daqing/financial/gateway/util/RedisUtil.java b/dq-govern-gateway/src/main/java/com/daqing/financial/gateway/util/RedisUtil.java new file mode 100644 index 00000000..d8fd451f --- /dev/null +++ b/dq-govern-gateway/src/main/java/com/daqing/financial/gateway/util/RedisUtil.java @@ -0,0 +1,298 @@ +package com.daqing.financial.gateway.util; + + +import com.daqing.financial.gateway.SpringContextHolder; +import org.springframework.data.redis.core.HashOperations; +import org.springframework.data.redis.core.ListOperations; +import org.springframework.data.redis.core.StringRedisTemplate; +import org.springframework.data.redis.core.ValueOperations; + +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.TimeUnit; + +/** + * redis工具类 + */ +public class RedisUtil { + + private final static StringRedisTemplate stringRedisTemplate = SpringContextHolder.getBean("stringRedisTemplate"); + + /** + * 匹配key + */ + public static Set keys(String pattern) { + return stringRedisTemplate.keys(pattern); + } + + /** + * 删除一个key + */ + public static void del(String key) { + stringRedisTemplate.delete(key); + } + + /** + * 批量删除key + */ + public static void delByPattern(String pattern) { + Set keySet = keys(pattern); + stringRedisTemplate.delete(keySet); + } + + /** + * 设置过期时间,单位为秒 + */ + public static boolean expire(String key, long seconds) { + return stringRedisTemplate.expire(key, seconds, TimeUnit.SECONDS); + } + + /** + * 获取自动过期时间 + */ + public static long ttl(String key) { + return stringRedisTemplate.getExpire(key, TimeUnit.SECONDS); + } + + /** + * 移除过期时间 + */ + public static boolean persist(String key) { + return stringRedisTemplate.persist(key); + } + + /////// String 操作 + + /** + * 给key赋值 + */ + public static void set(String key, String value) { + ValueOperations op = stringRedisTemplate.opsForValue(); + op.set(key, value); + } + + /** + * 给key赋值,并设置过期时间,单位为秒 + */ + public static void setEx(String key, String value, long seconds) { + set(key, value); + expire(key, seconds); + } + + /** + * 将key的值加num + */ + public static void incrBy(String key, long num) { + ValueOperations op = stringRedisTemplate.opsForValue(); + op.increment(key, num); + } + + /** + * 获取key的值 + */ + public static String get(String key) { + ValueOperations op = stringRedisTemplate.opsForValue(); + return op.get(key); + } + + /////// list操作 + + /** + * 插入到表头 + */ + public static void lPush(String key, String... values) { + ListOperations listOp = stringRedisTemplate.opsForList(); + listOp.leftPushAll(key, values); + } + + /** + * 移除第一个 + */ + public static String rPop(String key) { + ListOperations listOp = stringRedisTemplate.opsForList(); + return listOp.rightPop(key); + } + + /** + * 获取list所有 + * + * @param key + * @param start + * @param end + * @return + */ + public static List lRange(String key, int start, int end) { + ListOperations opsForList = stringRedisTemplate.opsForList(); + return opsForList.range(key, start, end); + } + + /////// hash + + /* + * public static void hset(String key,String hashKey,String value){ + * HashOperations opsForHash = + * stringRedisTemplate.opsForHash(); opsForHash.put(key, hashKey, value); } + */ + /////// set + /////// sorted set + + /** + * 存放list + * @param key + * @param list + */ + public static void setList(String key, List list){ + ListOperations opsForList = stringRedisTemplate.opsForList(); + opsForList.leftPushAll(key, list); + } + + /** + * HashGet + * @param key 键 不能为null + * @param item 项 不能为null + * @return 值 + */ + public static Object hGet(String key,String item){ + HashOperations mapOp = stringRedisTemplate.opsForHash(); + return mapOp.get(key, item); + } + + /** + * 获取hashKey对应的所有键值 + * @param key 键 + * @return 对应的多个键值 + */ + public static Map hmGet(String key){ + HashOperations mapOp = stringRedisTemplate.opsForHash(); + return mapOp.entries(key); + } + + /** + * HashSet + * @param key 键 + * @param map 对应多个键值 + * @return true 成功 false 失败 + */ + public static boolean hmSet(String key, Map map){ + try { + HashOperations mapOp = stringRedisTemplate.opsForHash(); + mapOp.putAll(key, map); + return true; + } catch (Exception e) { + e.printStackTrace(); + return false; + } + } + + /** + * HashSet 并设置时间 + * @param key 键 + * @param map 对应多个键值 + * @param time 时间(秒) + * @return true成功 false失败 + */ + public static boolean hmset(String key, Map map, long time){ + try { + HashOperations mapOp = stringRedisTemplate.opsForHash(); + mapOp.putAll(key, map); + if(time>0){ + expire(key, time); + } + return true; + } catch (Exception e) { + e.printStackTrace(); + return false; + } + } + + /** + * 向一张hash表中放入数据,如果不存在将创建 + * @param key 键 + * @param item 项 + * @param value 值 + * @return true 成功 false失败 + */ + public static boolean hset(String key,String item,Object value) { + try { + HashOperations mapOp = stringRedisTemplate.opsForHash(); + mapOp.put(key, item, value); + return true; + } catch (Exception e) { + e.printStackTrace(); + return false; + } + } + + /** + * 向一张hash表中放入数据,如果不存在将创建 + * @param key 键 + * @param item 项 + * @param value 值 + * @param time 时间(秒) 注意:如果已存在的hash表有时间,这里将会替换原有的时间 + * @return true 成功 false失败 + */ + public static boolean hset(String key,String item,Object value,long time) { + try { + HashOperations mapOp = stringRedisTemplate.opsForHash(); + mapOp.put(key, item, value); + if(time>0){ + expire(key, time); + } + return true; + } catch (Exception e) { + e.printStackTrace(); + return false; + } + } + + /** + * 删除hash表中的值 + * @param key 键 不能为null + * @param item 项 可以使多个 不能为null + */ + public static void hdel(String key, Object... item){ + HashOperations mapOp = stringRedisTemplate.opsForHash(); + + mapOp.delete(key,item); + } + + /** + * 判断hash表中是否有该项的值 + * @param key 键 不能为null + * @param item 项 不能为null + * @return true 存在 false不存在 + */ + public static boolean hHasKey(String key, String item){ + HashOperations mapOp = stringRedisTemplate.opsForHash(); + + return mapOp.hasKey(key, item); + } + + /** + * hash递增 如果不存在,就会创建一个 并把新增后的值返回 + * @param key 键 + * @param item 项 + * @param by 要增加几(大于0) + * @return + */ + public static double hincr(String key, String item,double by){ + HashOperations mapOp = stringRedisTemplate.opsForHash(); + + return mapOp.increment(key, item, by); + } + + /** + * hash递减 + * @param key 键 + * @param item 项 + * @param by 要减少记(小于0) + * @return + */ + public static double hdecr(String key, String item,double by){ + HashOperations mapOp = stringRedisTemplate.opsForHash(); + + return mapOp.increment(key, item,-by); + } + +} diff --git a/dq-govern-gateway/src/main/java/com/daqing/financial/gateway/util/ResultCodeEnum.java b/dq-govern-gateway/src/main/java/com/daqing/financial/gateway/util/ResultCodeEnum.java new file mode 100644 index 00000000..62846ece --- /dev/null +++ b/dq-govern-gateway/src/main/java/com/daqing/financial/gateway/util/ResultCodeEnum.java @@ -0,0 +1,62 @@ +package com.daqing.financial.gateway.util; + +public enum ResultCodeEnum { + + SUCCESS(0, "请求成功"), + BAD_REQUEST(400, "错误请求"), + UN_AUTHORIZATION(401, "未授权"), + SERVER_EXCEPTION(500, "服务器异常"), + + ACCOUNT_NOT_EXIST(10000, "账号不存在"), + ACCOUNT_NOT_BIND(10001, "账号未绑定钱包"), + ACCOUNT_NOT_ACTIVE(10002, "账号未激活"), + ACCOUNT_EXIST(10003, "账号已存在,请登录"), + COIN_ADDRESS_IS_NOT_EXIST(10004, "该币种地址不存在"), + COIN_NAME_IS_NOT_EXIST(10005, "该币种不存在"), + + WALLET_BAD_PARAM(10100, "非法参数,禁止访问他人账号"), + WALLET_NOT_ENOUGH(10101, "可用余额不足"), + SUB_ACCOUNT_NOT_ALLOW_WITHDRAW(10102, "子账号不允许提币"), + USER_NAME_ALREADY_EXISTS(10103,"用户名已存在"), + USER_EMAIL_ALREADY_EXISTS(10104,"邮箱已存在"), + INVITATION_CODE_NOT_EXIST(10105,"邀请码不存在"), + DEVICE_EXIST(10106,"设备号相同"), + LOGINPASSWORD_ERROR(10107,"登录密码错误"), + ACCOUNT_DISABLED(10108,"账号已禁用"), + USER_NAME_IS_ENABLE(10109,"用户名不存在"), + USER_IS_ACTIVE(10111,"该用户已激活"), + PAY_PASSWORD_IS_ERROR(10112,"支付密码错误"), + USER_IS_ACTIVE_MAX(10113,"您的激活次数已上线"), + WITHDRAW_IS_ENABLE(10114,"该币种已关闭提币通道"), + WITHDRAW_IS_NOT_BEGIN_TIME(10115,"未到提币时间"), + WITHDRAW_IS_SUPER_END_TIME(10116,"提币时间已过"), + WITHDRAW_MIN_ERROR(10117,"不能小于最小提币金额"), + WITHDRAW_MAX_ERROR(10118,"不能大于最大提币金额"), + WITHDRAW_MAX_TODAY(10120,"当日提币金额已上限"), + TRANSFER_IS_ENABLE(10121,"该币种已关闭转账通道"), + TRANSFER_MIN_ERROR(10122,"不能小于最小转账金额"), + TRANSFER_MAX_ERROR(10123,"不能大于最大转账金额"), + TRANSFER_MAX_TODAY(10124,"当日转账金额已上限"), + USERNAME_AND_EMAIL_ERROR(10125,"用户名和邮箱不匹配"), + EMAIL_CODE_IS_ERROR(10126,"验证码错误"), + PAY_PASSWORD_IS_NULL(10127,"请先设置支付密码"), + CHAIN_IS_ERROR(10128,"链上异常请联系管理员"); + + + + private final int code; + private final String remark; + + ResultCodeEnum(int code, String remark) { + this.code = code; + this.remark = remark; + } + + public int getCode() { + return code; + } + + public String getRemark() { + return remark; + } +} diff --git a/dq-govern-gateway/src/main/resources/jwt.properties b/dq-govern-gateway/src/main/resources/jwt.properties new file mode 100644 index 00000000..55a67a44 --- /dev/null +++ b/dq-govern-gateway/src/main/resources/jwt.properties @@ -0,0 +1 @@ +jwt.ignoreUrlList=/route-api/login,/route-api/refresh \ No newline at end of file