caojianbin
8 months ago
22 changed files with 1260 additions and 30 deletions
@ -0,0 +1,17 @@
@@ -0,0 +1,17 @@
|
||||
package com.ecell.internationalize.auth.utils; |
||||
|
||||
import com.ecell.internationalize.common.core.utils.SpringUtils; |
||||
import com.ecell.internationalize.common.security.service.TokenService; |
||||
|
||||
/** |
||||
* @author borui |
||||
*/ |
||||
public class AuthLogic { |
||||
public TokenService tokenServiceImpl = SpringUtils.getBean(TokenService.class); |
||||
/** |
||||
* 会话注销,根据指定Token |
||||
*/ |
||||
public void logoutByToken(String token) { |
||||
tokenServiceImpl.delLoginUser(token); |
||||
} |
||||
} |
@ -0,0 +1,19 @@
@@ -0,0 +1,19 @@
|
||||
package com.ecell.internationalize.auth.utils; |
||||
|
||||
/** |
||||
* @author borui |
||||
*/ |
||||
public class AuthUtil { |
||||
/** |
||||
* 底层的 AuthLogic 对象 |
||||
*/ |
||||
public static AuthLogic authLogic = new AuthLogic(); |
||||
/** |
||||
* 会话注销,根据指定Token |
||||
* |
||||
* @param token 指定token |
||||
*/ |
||||
public static void logoutByToken(String token) { |
||||
authLogic.logoutByToken(token); |
||||
} |
||||
} |
@ -0,0 +1,14 @@
@@ -0,0 +1,14 @@
|
||||
package com.ecell.internationalize.gateway; |
||||
|
||||
import org.springframework.boot.SpringApplication; |
||||
import org.springframework.boot.autoconfigure.SpringBootApplication; |
||||
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; |
||||
|
||||
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class }) |
||||
public class EcellGatewayApplication { |
||||
public static void main(String[] args) |
||||
{ |
||||
SpringApplication.run(EcellGatewayApplication.class, args); |
||||
System.out.println("(♥◠‿◠)ノ゙ 易赛网关启动成功 ლ(´ڡ`ლ)゙"); |
||||
} |
||||
} |
@ -0,0 +1,84 @@
@@ -0,0 +1,84 @@
|
||||
package com.ecell.internationalize.gateway.config; |
||||
|
||||
import com.google.code.kaptcha.Constants; |
||||
import com.google.code.kaptcha.impl.DefaultKaptcha; |
||||
import com.google.code.kaptcha.util.Config; |
||||
import org.springframework.context.annotation.Bean; |
||||
import org.springframework.context.annotation.Configuration; |
||||
|
||||
import java.util.Properties; |
||||
|
||||
|
||||
/** |
||||
* @author borui |
||||
*/ |
||||
@Configuration |
||||
public class CaptchaConfig { |
||||
|
||||
@Bean(name = "captchaProducer") |
||||
public DefaultKaptcha getKaptchaBean() |
||||
{ |
||||
DefaultKaptcha defaultKaptcha = new DefaultKaptcha(); |
||||
Properties properties = new Properties(); |
||||
// 是否有边框 默认为true 我们可以自己设置yes,no
|
||||
properties.setProperty(Constants.KAPTCHA_BORDER, "yes"); |
||||
// 验证码文本字符颜色 默认为Color.BLACK
|
||||
properties.setProperty(Constants.KAPTCHA_TEXTPRODUCER_FONT_COLOR, "black"); |
||||
// 验证码图片宽度 默认为200
|
||||
properties.setProperty(Constants.KAPTCHA_IMAGE_WIDTH, "160"); |
||||
// 验证码图片高度 默认为50
|
||||
properties.setProperty(Constants.KAPTCHA_IMAGE_HEIGHT, "60"); |
||||
// 验证码文本字符大小 默认为40
|
||||
properties.setProperty(Constants.KAPTCHA_TEXTPRODUCER_FONT_SIZE, "38"); |
||||
// KAPTCHA_SESSION_KEY
|
||||
properties.setProperty(Constants.KAPTCHA_SESSION_CONFIG_KEY, "kaptchaCode"); |
||||
// 验证码文本字符长度 默认为5
|
||||
properties.setProperty(Constants.KAPTCHA_TEXTPRODUCER_CHAR_LENGTH, "4"); |
||||
// 验证码文本字体样式 默认为new Font("Arial", 1, fontSize), new Font("Courier", 1, fontSize)
|
||||
properties.setProperty(Constants.KAPTCHA_TEXTPRODUCER_FONT_NAMES, "Arial,Courier"); |
||||
// 图片样式 水纹com.google.code.kaptcha.impl.WaterRipple 鱼眼com.google.code.kaptcha.impl.FishEyeGimpy 阴影com.google.code.kaptcha.impl.ShadowGimpy
|
||||
properties.setProperty(Constants.KAPTCHA_OBSCURIFICATOR_IMPL, "com.google.code.kaptcha.impl.ShadowGimpy"); |
||||
Config config = new Config(properties); |
||||
defaultKaptcha.setConfig(config); |
||||
return defaultKaptcha; |
||||
} |
||||
|
||||
@Bean(name = "captchaProducerMath") |
||||
public DefaultKaptcha getKaptchaBeanMath() |
||||
{ |
||||
DefaultKaptcha defaultKaptcha = new DefaultKaptcha(); |
||||
Properties properties = new Properties(); |
||||
// 是否有边框 默认为true 我们可以自己设置yes,no
|
||||
properties.setProperty(Constants.KAPTCHA_BORDER, "yes"); |
||||
// 边框颜色 默认为Color.BLACK
|
||||
properties.setProperty(Constants.KAPTCHA_BORDER_COLOR, "105,179,90"); |
||||
// 验证码文本字符颜色 默认为Color.BLACK
|
||||
properties.setProperty(Constants.KAPTCHA_TEXTPRODUCER_FONT_COLOR, "blue"); |
||||
// 验证码图片宽度 默认为200
|
||||
properties.setProperty(Constants.KAPTCHA_IMAGE_WIDTH, "160"); |
||||
// 验证码图片高度 默认为50
|
||||
properties.setProperty(Constants.KAPTCHA_IMAGE_HEIGHT, "60"); |
||||
// 验证码文本字符大小 默认为40
|
||||
properties.setProperty(Constants.KAPTCHA_TEXTPRODUCER_FONT_SIZE, "35"); |
||||
// KAPTCHA_SESSION_KEY
|
||||
properties.setProperty(Constants.KAPTCHA_SESSION_CONFIG_KEY, "kaptchaCodeMath"); |
||||
// 验证码文本生成器
|
||||
properties.setProperty(Constants.KAPTCHA_TEXTPRODUCER_IMPL, "com.ruoyi.gateway.config.KaptchaTextCreator"); |
||||
// 验证码文本字符间距 默认为2
|
||||
properties.setProperty(Constants.KAPTCHA_TEXTPRODUCER_CHAR_SPACE, "3"); |
||||
// 验证码文本字符长度 默认为5
|
||||
properties.setProperty(Constants.KAPTCHA_TEXTPRODUCER_CHAR_LENGTH, "6"); |
||||
// 验证码文本字体样式 默认为new Font("Arial", 1, fontSize), new Font("Courier", 1, fontSize)
|
||||
properties.setProperty(Constants.KAPTCHA_TEXTPRODUCER_FONT_NAMES, "Arial,Courier"); |
||||
// 验证码噪点颜色 默认为Color.BLACK
|
||||
properties.setProperty(Constants.KAPTCHA_NOISE_COLOR, "white"); |
||||
// 干扰实现类
|
||||
properties.setProperty(Constants.KAPTCHA_NOISE_IMPL, "com.google.code.kaptcha.impl.NoNoise"); |
||||
// 图片样式 水纹com.google.code.kaptcha.impl.WaterRipple 鱼眼com.google.code.kaptcha.impl.FishEyeGimpy 阴影com.google.code.kaptcha.impl.ShadowGimpy
|
||||
properties.setProperty(Constants.KAPTCHA_OBSCURIFICATOR_IMPL, "com.google.code.kaptcha.impl.ShadowGimpy"); |
||||
Config config = new Config(properties); |
||||
defaultKaptcha.setConfig(config); |
||||
return defaultKaptcha; |
||||
} |
||||
|
||||
} |
@ -0,0 +1,72 @@
@@ -0,0 +1,72 @@
|
||||
package com.ecell.internationalize.gateway.config; |
||||
|
||||
import com.alibaba.csp.sentinel.adapter.gateway.common.SentinelGatewayConstants; |
||||
import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayFlowRule; |
||||
import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayParamFlowItem; |
||||
import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayRuleManager; |
||||
import com.alibaba.csp.sentinel.slots.block.RuleConstant; |
||||
import com.ecell.internationalize.gateway.handler.SentinelFallbackHandler; |
||||
import org.springframework.context.annotation.Bean; |
||||
import org.springframework.context.annotation.Configuration; |
||||
import org.springframework.core.Ordered; |
||||
import org.springframework.core.annotation.Order; |
||||
import org.springframework.core.io.ClassPathResource; |
||||
import org.springframework.data.redis.core.script.DefaultRedisScript; |
||||
import org.springframework.scripting.support.ResourceScriptSource; |
||||
|
||||
import java.util.HashSet; |
||||
import java.util.Set; |
||||
|
||||
/** |
||||
* @author borui |
||||
*/ |
||||
@Configuration |
||||
public class GatewayConfig { |
||||
@Bean |
||||
@Order(Ordered.HIGHEST_PRECEDENCE) |
||||
public SentinelFallbackHandler sentinelGatewayExceptionHandler() |
||||
{ |
||||
return new SentinelFallbackHandler(); |
||||
} |
||||
/** |
||||
* |
||||
* 初始化限流规则 |
||||
* */ |
||||
// @PostConstruct
|
||||
public void doInit(){ |
||||
initGatewayRules(); |
||||
|
||||
} |
||||
/** |
||||
* 硬编码网关限流规则 |
||||
*/ |
||||
private void initGatewayRules() { |
||||
Set<GatewayFlowRule> rules = new HashSet<>(); |
||||
|
||||
GatewayFlowRule rule = new GatewayFlowRule(); |
||||
// 指定限流模式, 根据 route_id 做限流, 默认的模式
|
||||
rule.setResourceMode(SentinelGatewayConstants.RESOURCE_MODE_ROUTE_ID); |
||||
// 指定 route_id -> service id
|
||||
rule.setResource("yisai-system"); |
||||
GatewayParamFlowItem paramItem = new GatewayParamFlowItem(); |
||||
paramItem.setFieldName("appId"); |
||||
rule.setParamItem(paramItem); |
||||
// 按照 QPS 限流
|
||||
rule.setGrade(RuleConstant.FLOW_GRADE_QPS); |
||||
// 统计窗口和限流阈值
|
||||
rule.setIntervalSec(60); |
||||
rule.setCount(3); |
||||
|
||||
rules.add(rule); |
||||
|
||||
// 加载到网关中
|
||||
GatewayRuleManager.loadRules(rules); |
||||
} |
||||
@Bean |
||||
public DefaultRedisScript<Long> limitScript(){ |
||||
DefaultRedisScript<Long> script = new DefaultRedisScript<>(); |
||||
script.setResultType(Long.class); |
||||
script.setScriptSource(new ResourceScriptSource(new ClassPathResource("lua\\rateLimit.lua"))); |
||||
return script; |
||||
} |
||||
} |
@ -0,0 +1,73 @@
@@ -0,0 +1,73 @@
|
||||
package com.ecell.internationalize.gateway.config; |
||||
|
||||
import com.google.code.kaptcha.text.impl.DefaultTextCreator; |
||||
|
||||
import java.util.Random; |
||||
|
||||
/** |
||||
* @author borui |
||||
*/ |
||||
public class KaptchaTextCreator extends DefaultTextCreator { |
||||
private static final String[] CNUMBERS = "0,1,2,3,4,5,6,7,8,9,10".split(","); |
||||
|
||||
@Override |
||||
public String getText() |
||||
{ |
||||
Integer result = 0; |
||||
Random random = new Random(); |
||||
int x = random.nextInt(10); |
||||
int y = random.nextInt(10); |
||||
StringBuilder suChinese = new StringBuilder(); |
||||
int randomoperands = (int) Math.round(Math.random() * 2); |
||||
if (randomoperands == 0) |
||||
{ |
||||
result = x * y; |
||||
suChinese.append(CNUMBERS[x]); |
||||
suChinese.append("*"); |
||||
suChinese.append(CNUMBERS[y]); |
||||
} |
||||
else if (randomoperands == 1) |
||||
{ |
||||
if (!(x == 0) && y % x == 0) |
||||
{ |
||||
result = y / x; |
||||
suChinese.append(CNUMBERS[y]); |
||||
suChinese.append("/"); |
||||
suChinese.append(CNUMBERS[x]); |
||||
} |
||||
else |
||||
{ |
||||
result = x + y; |
||||
suChinese.append(CNUMBERS[x]); |
||||
suChinese.append("+"); |
||||
suChinese.append(CNUMBERS[y]); |
||||
} |
||||
} |
||||
else if (randomoperands == 2) |
||||
{ |
||||
if (x >= y) |
||||
{ |
||||
result = x - y; |
||||
suChinese.append(CNUMBERS[x]); |
||||
suChinese.append("-"); |
||||
suChinese.append(CNUMBERS[y]); |
||||
} |
||||
else |
||||
{ |
||||
result = y - x; |
||||
suChinese.append(CNUMBERS[y]); |
||||
suChinese.append("-"); |
||||
suChinese.append(CNUMBERS[x]); |
||||
} |
||||
} |
||||
else |
||||
{ |
||||
result = x + y; |
||||
suChinese.append(CNUMBERS[x]); |
||||
suChinese.append("+"); |
||||
suChinese.append(CNUMBERS[y]); |
||||
} |
||||
suChinese.append("=?@" + result); |
||||
return suChinese.toString(); |
||||
} |
||||
} |
@ -0,0 +1,43 @@
@@ -0,0 +1,43 @@
|
||||
package com.ecell.internationalize.gateway.config.properties; |
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties; |
||||
import org.springframework.cloud.context.config.annotation.RefreshScope; |
||||
import org.springframework.context.annotation.Configuration; |
||||
|
||||
/** |
||||
* @author borui |
||||
*/ |
||||
@Configuration |
||||
@RefreshScope |
||||
@ConfigurationProperties(prefix = "security.captcha") |
||||
public class CaptchaProperties { |
||||
/** |
||||
* 验证码开关 |
||||
*/ |
||||
private Boolean enabled; |
||||
|
||||
/** |
||||
* 验证码类型(math 数组计算 char 字符) |
||||
*/ |
||||
private String type; |
||||
|
||||
public Boolean getEnabled() |
||||
{ |
||||
return enabled; |
||||
} |
||||
|
||||
public void setEnabled(Boolean enabled) |
||||
{ |
||||
this.enabled = enabled; |
||||
} |
||||
|
||||
public String getType() |
||||
{ |
||||
return type; |
||||
} |
||||
|
||||
public void setType(String type) |
||||
{ |
||||
this.type = type; |
||||
} |
||||
} |
@ -0,0 +1,31 @@
@@ -0,0 +1,31 @@
|
||||
package com.ecell.internationalize.gateway.config.properties; |
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties; |
||||
import org.springframework.cloud.context.config.annotation.RefreshScope; |
||||
import org.springframework.context.annotation.Configuration; |
||||
|
||||
import java.util.ArrayList; |
||||
import java.util.List; |
||||
|
||||
/** |
||||
* @author borui |
||||
*/ |
||||
@Configuration |
||||
@RefreshScope |
||||
@ConfigurationProperties(prefix = "security.ignore") |
||||
public class IgnoreWhiteProperties { |
||||
/** |
||||
* 放行白名单配置,网关不校验此处的白名单 |
||||
*/ |
||||
private List<String> whites = new ArrayList<>(); |
||||
|
||||
public List<String> getWhites() |
||||
{ |
||||
return whites; |
||||
} |
||||
|
||||
public void setWhites(List<String> whites) |
||||
{ |
||||
this.whites = whites; |
||||
} |
||||
} |
@ -0,0 +1,109 @@
@@ -0,0 +1,109 @@
|
||||
package com.ecell.internationalize.gateway.constant; |
||||
|
||||
import com.ecell.internationalize.common.core.utils.StringUtils; |
||||
import org.slf4j.LoggerFactory; |
||||
import org.springframework.http.server.reactive.ServerHttpRequest; |
||||
import org.springframework.web.server.ServerWebExchange; |
||||
|
||||
import java.util.List; |
||||
|
||||
/** |
||||
* @author borui |
||||
*/ |
||||
public class MessagesConstant { |
||||
private static final org.slf4j.Logger log = LoggerFactory.getLogger(MessagesConstant.class); |
||||
/**令牌不能为空*/ |
||||
public static final String MESSAGES_TOKE_EMPTY_EN="Token cannot be empty"; |
||||
public static final String MESSAGES_TOKE_EMPTY_ZH="令牌不能为空"; |
||||
/**令牌已过期或验证不正确*/ |
||||
public static final String MESSAGES_TOKE_EMPTY_OR_ERROR_EN="Token expired or incorrectly verified"; |
||||
public static final String MESSAGES_TOKE_EMPTY_OR_ERROR_ZH="令牌已过期或验证不正确"; |
||||
/**登录状态已过期*/ |
||||
public static final String MESSAGES_LOGIN_STATUS_EXPIRED_EN="Login status has expired"; |
||||
public static final String MESSAGES_LOGIN_STATUS_EXPIRED_ZH="登录状态已过期"; |
||||
/**令牌验证失败*/ |
||||
public static final String MESSAGES_TOKE_VERIFICATION_FAILED_EN="Token verification failed"; |
||||
public static final String MESSAGES_TOKE_VERIFICATION_FAILED_ZH="令牌验证失败"; |
||||
/**appId 不能为空*/ |
||||
public static final String MESSAGES_APPID_ISEMPTY_FAILED_EN="appId cannot be empty"; |
||||
public static final String MESSAGES_APPID_ISEMPTY_FAILED_ZH="appId 不能为空"; |
||||
/**appKey 不能为空*/ |
||||
public static final String MESSAGES_APPkEY_ISEMPTY_FAILED_EN="appKey cannot be empty"; |
||||
public static final String MESSAGES_APPkEY_ISEMPTY_FAILED_ZH="appKey 不能为空"; |
||||
/**请求地址不允许访问*/ |
||||
public static final String MESSAGES_REQUEST_ADDRESS_EN="The requested address does not allow access"; |
||||
public static final String MESSAGES_REQUEST_ADDRESS_ZH="请求地址不允许访问"; |
||||
/**服务未找到*/ |
||||
public static final String MESSAGES_SERVICE_NOT_FOUND_EN="Service not found"; |
||||
public static final String MESSAGES_SERVICE_NOT_FOUND_ZH="服务未找到"; |
||||
/**内部服务器错误*/ |
||||
public static final String MESSAGES_INTERNAL_SERVER_ERROR_EN="Internal server error"; |
||||
public static final String MESSAGES_INTERNAL_SERVER_ERROR_ZH="内部服务器错误"; |
||||
|
||||
/**内部服务器错误*/ |
||||
public static final String MESSAGES_API_STATE_ERROR_EN="This account has been disabled"; |
||||
public static final String MESSAGES_API_STATE_ERROR_ZH="该账户已被禁用"; |
||||
|
||||
public static final String MESSAGES_API_LIMIT_MAX_ERROR_ZH="当前接口达到最大限流次数"; |
||||
public static final String MESSAGES_API_LIMIT_MAX_ERROR_EN="The current interface has reached the maximum current limit times"; |
||||
|
||||
|
||||
public static final Integer APP_ID_CODE=406; |
||||
public static final Integer APP_KEY_CODE=407; |
||||
public static final Integer STATE_CODE=408; |
||||
public static final Integer LIMIT_CODE=409; |
||||
|
||||
public static final String STRING_ZERO="0"; |
||||
public static final String STRING_ONE="1"; |
||||
|
||||
|
||||
/**语言类型 中文*/ |
||||
public static final String Language_ZH="zh"; |
||||
/**语言类型 英文*/ |
||||
public static final String Language_EN="en"; |
||||
/**语言类型 key*/ |
||||
public static final String HEADER_KEY="yisailanguage"; |
||||
|
||||
|
||||
/** |
||||
* 国际化处理 |
||||
* @param exchange |
||||
* @return |
||||
*/ |
||||
public static String getMessages(ServerWebExchange exchange, String zh_msg, String en_msg) { |
||||
//给一个默认值
|
||||
String msg = en_msg; |
||||
ServerHttpRequest request = exchange.getRequest(); |
||||
if (StringUtils.isNotNull(request)) { |
||||
if (request.getHeaders().containsKey(MessagesConstant.HEADER_KEY)) { |
||||
String zh = exchange.getRequest().getHeaders().getFirst(MessagesConstant.HEADER_KEY); |
||||
if (MessagesConstant.Language_ZH.equals(zh)) { |
||||
msg = zh_msg; |
||||
} else { |
||||
msg = en_msg; |
||||
} |
||||
} else { |
||||
List<String> list = request.getHeaders().get("accept-language"); |
||||
if (StringUtils.isNotEmpty(list) && list.size() > 0) { |
||||
String s = list.get(0).split(",")[0].split("-")[0]; |
||||
log.info("获取头部的accept-language值:", request.getHeaders().get("accept-language")); |
||||
log.info("获取头部的语言:{}", s); |
||||
if (MessagesConstant.Language_ZH.equals(s)) { |
||||
msg = zh_msg; |
||||
} else { |
||||
msg = en_msg; |
||||
} |
||||
} |
||||
|
||||
|
||||
} |
||||
} else { |
||||
msg = en_msg; |
||||
} |
||||
|
||||
|
||||
|
||||
|
||||
return msg; |
||||
} |
||||
} |
@ -0,0 +1,99 @@
@@ -0,0 +1,99 @@
|
||||
package com.ecell.internationalize.gateway.constant; |
||||
|
||||
import io.swagger.annotations.ApiModelProperty; |
||||
|
||||
import java.io.Serializable; |
||||
|
||||
/** |
||||
* @author borui |
||||
*/ |
||||
public class RateLimitInfo implements Serializable { |
||||
private static final long serialVersionUID=1L; |
||||
@ApiModelProperty(example = "APPID") |
||||
private Integer appId; |
||||
|
||||
@ApiModelProperty(example = "APPKEY") |
||||
private String appKey; |
||||
|
||||
@ApiModelProperty(example = "状态(0:禁用 , 1:启用)") |
||||
private String status; |
||||
|
||||
@ApiModelProperty(example = "厂商Id") |
||||
private String firmId; |
||||
|
||||
@ApiModelProperty(example = "标识(1:厂商:2:代理商)") |
||||
private String firmFlag; |
||||
|
||||
@ApiModelProperty(example = "代理商Id") |
||||
private String secondFirmId; |
||||
|
||||
@ApiModelProperty(example = "发起请求秒数(如一分钟多少次,单位为秒)") |
||||
private Integer second; |
||||
|
||||
@ApiModelProperty(example = "发起请求次数(如多少分钟100次)") |
||||
private Integer frequency; |
||||
|
||||
public Integer getAppId() { |
||||
return appId; |
||||
} |
||||
|
||||
public void setAppId(Integer appId) { |
||||
this.appId = appId; |
||||
} |
||||
|
||||
public String getAppKey() { |
||||
return appKey; |
||||
} |
||||
|
||||
public void setAppKey(String appKey) { |
||||
this.appKey = appKey; |
||||
} |
||||
|
||||
public String getStatus() { |
||||
return status; |
||||
} |
||||
|
||||
public void setStatus(String status) { |
||||
this.status = status; |
||||
} |
||||
|
||||
public String getFirmId() { |
||||
return firmId; |
||||
} |
||||
|
||||
public void setFirmId(String firmId) { |
||||
this.firmId = firmId; |
||||
} |
||||
|
||||
public String getFirmFlag() { |
||||
return firmFlag; |
||||
} |
||||
|
||||
public void setFirmFlag(String firmFlag) { |
||||
this.firmFlag = firmFlag; |
||||
} |
||||
|
||||
public String getSecondFirmId() { |
||||
return secondFirmId; |
||||
} |
||||
|
||||
public void setSecondFirmId(String secondFirmId) { |
||||
this.secondFirmId = secondFirmId; |
||||
} |
||||
|
||||
public Integer getSecond() { |
||||
return second; |
||||
} |
||||
|
||||
public void setSecond(Integer second) { |
||||
this.second = second; |
||||
} |
||||
|
||||
public Integer getFrequency() { |
||||
return frequency; |
||||
} |
||||
|
||||
public void setFrequency(Integer frequency) { |
||||
this.frequency = frequency; |
||||
} |
||||
} |
@ -0,0 +1,211 @@
@@ -0,0 +1,211 @@
|
||||
package com.ecell.internationalize.gateway.filter; |
||||
|
||||
import com.alibaba.fastjson2.JSON; |
||||
import com.alibaba.fastjson2.JSONObject; |
||||
import com.ecell.internationalize.common.core.constant.CacheConstants; |
||||
import com.ecell.internationalize.common.core.constant.HttpStatus; |
||||
import com.ecell.internationalize.common.core.constant.SecurityConstants; |
||||
import com.ecell.internationalize.common.core.constant.TokenConstants; |
||||
import com.ecell.internationalize.common.core.utils.JwtUtils; |
||||
import com.ecell.internationalize.common.core.utils.ServletUtils; |
||||
import com.ecell.internationalize.common.core.utils.StringUtils; |
||||
import com.ecell.internationalize.common.redis.service.RedisService; |
||||
import com.ecell.internationalize.gateway.config.properties.IgnoreWhiteProperties; |
||||
import com.ecell.internationalize.gateway.constant.MessagesConstant; |
||||
import com.ecell.internationalize.gateway.constant.RateLimitInfo; |
||||
import io.jsonwebtoken.Claims; |
||||
import org.slf4j.Logger; |
||||
import org.slf4j.LoggerFactory; |
||||
import org.springframework.beans.factory.annotation.Autowired; |
||||
import org.springframework.cloud.gateway.filter.GatewayFilterChain; |
||||
import org.springframework.cloud.gateway.filter.GlobalFilter; |
||||
import org.springframework.core.Ordered; |
||||
import org.springframework.data.redis.core.script.RedisScript; |
||||
import org.springframework.http.server.reactive.ServerHttpRequest; |
||||
import org.springframework.stereotype.Component; |
||||
import org.springframework.web.server.ServerWebExchange; |
||||
import reactor.core.publisher.Mono; |
||||
|
||||
import java.util.Collections; |
||||
|
||||
/** |
||||
* @author borui |
||||
*/ |
||||
@Component |
||||
public class AuthFilter implements GlobalFilter, Ordered { |
||||
private static final Logger log = LoggerFactory.getLogger(AuthFilter.class); |
||||
private static final String API_URL = "/yisai-system/api/**"; |
||||
private static final String RATE_LIMIT = "rate_limit:"; |
||||
private static final String HEADER_APPID = "appid"; |
||||
private static final String HEADER_APPKEY = "appkey"; |
||||
|
||||
// 排除过滤的 uri 地址,nacos自行添加
|
||||
@Autowired |
||||
private IgnoreWhiteProperties ignoreWhite; |
||||
|
||||
@Autowired |
||||
private RedisService redisService; |
||||
|
||||
@Autowired |
||||
RedisScript<Long> redisScript; |
||||
|
||||
|
||||
@Override |
||||
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { |
||||
ServerHttpRequest request = exchange.getRequest(); |
||||
ServerHttpRequest.Builder mutate = request.mutate(); |
||||
|
||||
String url = request.getURI().getPath(); |
||||
if (StringUtils.isMatch(API_URL, url)) { |
||||
return getVoidMono(exchange, chain, request, url); |
||||
// return chain.filter(exchange);
|
||||
} |
||||
// 跳过不需要验证的路径
|
||||
if (StringUtils.matches(url, ignoreWhite.getWhites())) { |
||||
return chain.filter(exchange); |
||||
} |
||||
String token = getToken(request); |
||||
if (StringUtils.isEmpty(token)) { |
||||
|
||||
return unauthorizedResponse(exchange, MessagesConstant.getMessages(exchange, MessagesConstant.MESSAGES_TOKE_EMPTY_ZH, MessagesConstant.MESSAGES_TOKE_EMPTY_EN)); |
||||
} |
||||
Claims claims = JwtUtils.parseToken(token); |
||||
if (claims == null) { |
||||
return unauthorizedResponse(exchange, MessagesConstant.getMessages(exchange, MessagesConstant.MESSAGES_TOKE_EMPTY_OR_ERROR_ZH, MessagesConstant.MESSAGES_TOKE_EMPTY_OR_ERROR_EN)); |
||||
} |
||||
String userkey = JwtUtils.getUserKey(claims); |
||||
boolean islogin = redisService.hasKey(getTokenKey(userkey)); |
||||
if (!islogin) { |
||||
return unauthorizedResponse(exchange, MessagesConstant.getMessages(exchange, MessagesConstant.MESSAGES_LOGIN_STATUS_EXPIRED_ZH, MessagesConstant.MESSAGES_LOGIN_STATUS_EXPIRED_EN)); |
||||
} |
||||
String userid = JwtUtils.getUserId(claims); |
||||
String username = JwtUtils.getUserName(claims); |
||||
if (StringUtils.isEmpty(userid) || StringUtils.isEmpty(username)) { |
||||
return unauthorizedResponse(exchange, MessagesConstant.getMessages(exchange, MessagesConstant.MESSAGES_TOKE_VERIFICATION_FAILED_ZH, MessagesConstant.MESSAGES_TOKE_VERIFICATION_FAILED_EN)); |
||||
} |
||||
|
||||
// 设置用户信息到请求
|
||||
addHeader(mutate, SecurityConstants.USER_KEY, userkey); |
||||
addHeader(mutate, SecurityConstants.DETAILS_USER_ID, userid); |
||||
addHeader(mutate, SecurityConstants.DETAILS_USERNAME, username); |
||||
// 内部请求来源参数清除
|
||||
removeHeader(mutate, SecurityConstants.FROM_SOURCE); |
||||
return chain.filter(exchange.mutate().request(mutate.build()).build()); |
||||
} |
||||
|
||||
|
||||
private void addHeader(ServerHttpRequest.Builder mutate, String name, Object value) { |
||||
if (value == null) { |
||||
return; |
||||
} |
||||
String valueStr = value.toString(); |
||||
String valueEncode = ServletUtils.urlEncode(valueStr); |
||||
mutate.header(name, valueEncode); |
||||
} |
||||
|
||||
private void removeHeader(ServerHttpRequest.Builder mutate, String name) { |
||||
mutate.headers(httpHeaders -> httpHeaders.remove(name)).build(); |
||||
} |
||||
|
||||
private Mono<Void> unauthorizedResponse(ServerWebExchange exchange, String msg) { |
||||
log.error("[鉴权异常处理]请求路径:{}", exchange.getRequest().getPath()); |
||||
|
||||
return ServletUtils.webFluxResponseWriter(exchange.getResponse(), msg, HttpStatus.UNAUTHORIZED); |
||||
} |
||||
|
||||
private Mono<Void> unauthorizedAPIResponse(ServerWebExchange exchange, String msg, int code) { |
||||
log.error("[对外接口鉴权异常处理]请求路径:{}", exchange.getRequest().getPath()); |
||||
|
||||
return ServletUtils.webFluxResponseWriter(exchange.getResponse(), msg, code); |
||||
} |
||||
|
||||
/** |
||||
* 获取缓存key |
||||
*/ |
||||
private String getTokenKey(String token) { |
||||
return CacheConstants.LOGIN_TOKEN_KEY + token; |
||||
} |
||||
|
||||
/** |
||||
* 获取请求token |
||||
*/ |
||||
private String getToken(ServerHttpRequest request) { |
||||
String token = request.getHeaders().getFirst(TokenConstants.AUTHENTICATION); |
||||
// 如果前端设置了令牌前缀,则裁剪掉前缀
|
||||
if (StringUtils.isNotEmpty(token) && token.startsWith(TokenConstants.PREFIX)) { |
||||
token = token.replaceFirst(TokenConstants.PREFIX, StringUtils.EMPTY); |
||||
} |
||||
return token; |
||||
} |
||||
|
||||
private Mono<Void> getVoidMono(ServerWebExchange exchange, GatewayFilterChain chain, ServerHttpRequest request, String url) { |
||||
Long apiCount; |
||||
String appId = request.getHeaders().getFirst(HEADER_APPID); |
||||
String appKey = request.getHeaders().getFirst(HEADER_APPKEY); |
||||
if (StringUtils.isEmpty(appId)) { |
||||
return unauthorizedAPIResponse(exchange, MessagesConstant.getMessages(exchange, MessagesConstant.MESSAGES_APPID_ISEMPTY_FAILED_ZH, MessagesConstant.MESSAGES_APPID_ISEMPTY_FAILED_EN), MessagesConstant.APP_ID_CODE); |
||||
} |
||||
if (StringUtils.isEmpty(appId)) { |
||||
return unauthorizedAPIResponse(exchange, MessagesConstant.getMessages(exchange, MessagesConstant.MESSAGES_APPkEY_ISEMPTY_FAILED_ZH, MessagesConstant.MESSAGES_APPkEY_ISEMPTY_FAILED_EN), MessagesConstant.APP_KEY_CODE); |
||||
} |
||||
String redisKey = appId + "_" + appKey; |
||||
|
||||
if (!redisService.hasKey(redisKey)) { |
||||
return unauthorizedAPIResponse(exchange, MessagesConstant.getMessages(exchange, MessagesConstant.MESSAGES_INTERNAL_SERVER_ERROR_ZH, MessagesConstant.MESSAGES_INTERNAL_SERVER_ERROR_EN), 500); |
||||
} |
||||
try { |
||||
Object cacheObject = redisService.getCacheObject(redisKey); |
||||
String s = JSON.toJSONString(cacheObject); |
||||
RateLimitInfo rateLimitInfo = JSONObject.parseObject(s, RateLimitInfo.class); |
||||
if (StringUtils.isNotNull(rateLimitInfo)) { |
||||
String status = rateLimitInfo.getStatus(); |
||||
if (StringUtils.isNotEmpty(status)) { |
||||
if (MessagesConstant.STRING_ZERO.equals(status)) { |
||||
return unauthorizedAPIResponse(exchange, MessagesConstant.getMessages(exchange, MessagesConstant.MESSAGES_API_STATE_ERROR_ZH, MessagesConstant.MESSAGES_API_STATE_ERROR_EN), MessagesConstant.STATE_CODE); |
||||
} |
||||
|
||||
apiCount = getApiCount(rateLimitInfo, url); |
||||
if (apiCount > rateLimitInfo.getFrequency()) { |
||||
return unauthorizedAPIResponse(exchange, MessagesConstant.getMessages(exchange, MessagesConstant.MESSAGES_API_LIMIT_MAX_ERROR_ZH, MessagesConstant.MESSAGES_API_LIMIT_MAX_ERROR_EN), MessagesConstant.LIMIT_CODE); |
||||
} |
||||
} |
||||
|
||||
} |
||||
|
||||
} catch (Exception e) { |
||||
return unauthorizedAPIResponse(exchange, MessagesConstant.getMessages(exchange, MessagesConstant.MESSAGES_INTERNAL_SERVER_ERROR_ZH, MessagesConstant.MESSAGES_INTERNAL_SERVER_ERROR_EN), 500); |
||||
} |
||||
|
||||
return chain.filter(exchange); |
||||
} |
||||
|
||||
private Long getApiCount(RateLimitInfo info, String url) { |
||||
Long current; |
||||
int time = 0, count = 0; |
||||
String fid; |
||||
StringBuffer buffer = new StringBuffer(RATE_LIMIT); |
||||
if (MessagesConstant.STRING_ONE.equals(info.getFirmFlag())) { |
||||
fid = info.getFirmId(); |
||||
} else { |
||||
fid = info.getSecondFirmId(); |
||||
} |
||||
String rateKey = buffer.append(fid).append(url).toString(); |
||||
time = info.getSecond(); |
||||
if (time <= 0) { |
||||
time = 60; |
||||
} |
||||
count = info.getFrequency(); |
||||
if (count <= 0) { |
||||
count = 10; |
||||
} |
||||
|
||||
current = (Long) redisService.redisTemplate.execute(redisScript, Collections.singletonList(rateKey), time, count); |
||||
|
||||
return current; |
||||
} |
||||
|
||||
@Override |
||||
public int getOrder() { |
||||
return -200; |
||||
} |
||||
} |
@ -0,0 +1,57 @@
@@ -0,0 +1,57 @@
|
||||
package com.ecell.internationalize.gateway.filter; |
||||
|
||||
import com.ecell.internationalize.common.core.utils.ServletUtils; |
||||
import com.ecell.internationalize.gateway.constant.MessagesConstant; |
||||
import org.springframework.cloud.gateway.filter.GatewayFilter; |
||||
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory; |
||||
import org.springframework.stereotype.Component; |
||||
|
||||
import java.util.ArrayList; |
||||
import java.util.List; |
||||
import java.util.regex.Pattern; |
||||
|
||||
/** |
||||
* @author borui |
||||
*/ |
||||
@Component |
||||
public class BlackListUrlFilter extends AbstractGatewayFilterFactory<BlackListUrlFilter.Config> { |
||||
@Override |
||||
public GatewayFilter apply(Config config) { |
||||
return (exchange, chain) -> { |
||||
|
||||
String url = exchange.getRequest().getURI().getPath(); |
||||
if (config.matchBlacklist(url)) { |
||||
return ServletUtils.webFluxResponseWriter(exchange.getResponse(), MessagesConstant.getMessages(exchange, MessagesConstant.MESSAGES_REQUEST_ADDRESS_ZH, MessagesConstant.MESSAGES_REQUEST_ADDRESS_EN)); |
||||
} |
||||
|
||||
return chain.filter(exchange); |
||||
}; |
||||
} |
||||
|
||||
public BlackListUrlFilter() { |
||||
super(Config.class); |
||||
} |
||||
|
||||
public static class Config { |
||||
private List<String> blacklistUrl; |
||||
|
||||
private List<Pattern> blacklistUrlPattern = new ArrayList<>(); |
||||
|
||||
public boolean matchBlacklist(String url) { |
||||
return !blacklistUrlPattern.isEmpty() && blacklistUrlPattern.stream().anyMatch(p -> p.matcher(url).find()); |
||||
} |
||||
|
||||
public List<String> getBlacklistUrl() { |
||||
return blacklistUrl; |
||||
} |
||||
|
||||
public void setBlacklistUrl(List<String> blacklistUrl) { |
||||
this.blacklistUrl = blacklistUrl; |
||||
this.blacklistUrlPattern.clear(); |
||||
this.blacklistUrl.forEach(url -> { |
||||
this.blacklistUrlPattern.add(Pattern.compile(url.replaceAll("\\*\\*", "(.*?)"), Pattern.CASE_INSENSITIVE)); |
||||
}); |
||||
} |
||||
} |
||||
|
||||
} |
@ -0,0 +1,86 @@
@@ -0,0 +1,86 @@
|
||||
package com.ecell.internationalize.gateway.filter; |
||||
|
||||
import com.alibaba.nacos.shaded.org.checkerframework.checker.units.qual.C; |
||||
import org.springframework.cloud.gateway.filter.GatewayFilter; |
||||
import org.springframework.cloud.gateway.filter.GatewayFilterChain; |
||||
import org.springframework.cloud.gateway.filter.OrderedGatewayFilter; |
||||
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory; |
||||
import org.springframework.cloud.gateway.support.ServerWebExchangeUtils; |
||||
import org.springframework.http.HttpMethod; |
||||
import org.springframework.stereotype.Component; |
||||
import org.springframework.web.server.ServerWebExchange; |
||||
import reactor.core.publisher.Mono; |
||||
|
||||
import java.util.Collections; |
||||
import java.util.List; |
||||
|
||||
/** |
||||
* @author borui |
||||
*/ |
||||
@Component |
||||
public class CacheRequestFilter extends AbstractGatewayFilterFactory<CacheRequestFilter.Config> { |
||||
public CacheRequestFilter() |
||||
{ |
||||
super(Config.class); |
||||
} |
||||
|
||||
@Override |
||||
public String name() |
||||
{ |
||||
return "CacheRequestFilter"; |
||||
} |
||||
|
||||
@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 == HttpMethod.GET || method == HttpMethod.DELETE) |
||||
{ |
||||
return chain.filter(exchange); |
||||
} |
||||
return ServerWebExchangeUtils.cacheRequestBodyAndRequest(exchange, (serverHttpRequest) -> { |
||||
if (serverHttpRequest == exchange.getRequest()) |
||||
{ |
||||
return chain.filter(exchange); |
||||
} |
||||
return chain.filter(exchange.mutate().request(serverHttpRequest).build()); |
||||
}); |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public List<String> shortcutFieldOrder() |
||||
{ |
||||
return Collections.singletonList("order"); |
||||
} |
||||
|
||||
static class Config |
||||
{ |
||||
private Integer order; |
||||
|
||||
public Integer getOrder() |
||||
{ |
||||
return order; |
||||
} |
||||
|
||||
public void setOrder(Integer order) |
||||
{ |
||||
this.order = order; |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,58 @@
@@ -0,0 +1,58 @@
|
||||
package com.ecell.internationalize.gateway.handler; |
||||
|
||||
import com.ecell.internationalize.common.core.utils.ServletUtils; |
||||
import com.ecell.internationalize.gateway.constant.MessagesConstant; |
||||
import org.slf4j.Logger; |
||||
import org.slf4j.LoggerFactory; |
||||
import org.springframework.boot.web.reactive.error.ErrorWebExceptionHandler; |
||||
import org.springframework.cloud.gateway.support.NotFoundException; |
||||
import org.springframework.context.annotation.Configuration; |
||||
import org.springframework.core.annotation.Order; |
||||
import org.springframework.http.server.reactive.ServerHttpResponse; |
||||
import org.springframework.web.server.ResponseStatusException; |
||||
import org.springframework.web.server.ServerWebExchange; |
||||
import reactor.core.publisher.Mono; |
||||
|
||||
/** |
||||
* @author borui |
||||
*/ |
||||
@Order(-1) |
||||
@Configuration |
||||
public class GatewayExceptionHandler implements ErrorWebExceptionHandler { |
||||
private static final Logger log = LoggerFactory.getLogger(GatewayExceptionHandler.class); |
||||
|
||||
@Override |
||||
public Mono<Void> handle(ServerWebExchange exchange, Throwable ex) |
||||
{ |
||||
ServerHttpResponse response = exchange.getResponse(); |
||||
|
||||
if (exchange.getResponse().isCommitted()) |
||||
{ |
||||
return Mono.error(ex); |
||||
} |
||||
|
||||
String msg; |
||||
|
||||
if (ex instanceof NotFoundException) |
||||
{ |
||||
/**服务未找到*/ |
||||
msg = MessagesConstant.getMessages(exchange,MessagesConstant.MESSAGES_SERVICE_NOT_FOUND_ZH,MessagesConstant.MESSAGES_SERVICE_NOT_FOUND_EN); |
||||
|
||||
} |
||||
else if (ex instanceof ResponseStatusException) |
||||
{ |
||||
ResponseStatusException responseStatusException = (ResponseStatusException) ex; |
||||
msg = responseStatusException.getMessage(); |
||||
} |
||||
else |
||||
{ |
||||
/**内部服务器错误*/ |
||||
//msg="内部服务器错误";
|
||||
msg =MessagesConstant.getMessages(exchange,MessagesConstant.MESSAGES_INTERNAL_SERVER_ERROR_ZH,MessagesConstant.MESSAGES_INTERNAL_SERVER_ERROR_EN); |
||||
} |
||||
|
||||
log.error("[网关异常处理]请求路径:{},异常信息:{}", exchange.getRequest().getPath(), ex.getMessage()); |
||||
|
||||
return ServletUtils.webFluxResponseWriter(response, msg); |
||||
} |
||||
} |
@ -0,0 +1,74 @@
@@ -0,0 +1,74 @@
|
||||
spring: |
||||
redis: |
||||
host: 120.77.209.176 |
||||
port: 6379 |
||||
database: 0 |
||||
password: Ecell...20201001 |
||||
cloud: |
||||
gateway: |
||||
discovery: |
||||
locator: |
||||
lowerCaseServiceId: true |
||||
enabled: true |
||||
routes: |
||||
# 认证中心 |
||||
- id: ecell-internationalize-auth |
||||
uri: lb://ecell-internationalize-auth |
||||
predicates: |
||||
- Path=/auth/** |
||||
filters: |
||||
# 验证码处理 |
||||
- CacheRequestFilter |
||||
- ValidateCodeFilter |
||||
- StripPrefix=1 |
||||
#易赛系统服务 |
||||
- id: yisai-system |
||||
uri: lb://yisai-system |
||||
predicates: |
||||
- Path=/yisai_system/** |
||||
filters: |
||||
- StripPrefix=1 |
||||
|
||||
#易赛授权服务 |
||||
- id: yisai-system-security |
||||
uri: lb://yisai-system-security |
||||
predicates: |
||||
- Path=/yisai_security/** |
||||
filters: |
||||
- StripPrefix=1 |
||||
#易赛APP服务 |
||||
- id: yisai-app |
||||
uri: lb://yisai-app |
||||
predicates: |
||||
- Path=/yisai_app/** |
||||
filters: |
||||
- StripPrefix=1 |
||||
|
||||
|
||||
# 安全配置 |
||||
security: |
||||
# 验证码 |
||||
captcha: |
||||
enabled: true |
||||
type: math |
||||
# 防止XSS攻击 |
||||
xss: |
||||
enabled: true |
||||
excludeUrls: |
||||
- /system/notice |
||||
# 不校验白名单 |
||||
ignore: |
||||
whites: |
||||
- /auth/logout |
||||
- /auth/login |
||||
- /auth/register |
||||
- /*/v2/api-docs |
||||
- /csrf |
||||
- /yisai_auth/*/login |
||||
- /yisai_auth/*/logout |
||||
- /yisai_api/api/** |
||||
- /yisai_system/api/** |
||||
cors: |
||||
enabled: true |
||||
|
||||
|
@ -0,0 +1,40 @@
@@ -0,0 +1,40 @@
|
||||
# Tomcat |
||||
server: |
||||
port: 9997 |
||||
|
||||
# Spring |
||||
spring: |
||||
application: |
||||
# 应用名称 |
||||
name: ecell-internationalize-gateway |
||||
profiles: |
||||
# 环境配置 |
||||
active: dev |
||||
cloud: |
||||
nacos: |
||||
discovery: |
||||
# 服务注册地址 |
||||
server-addr: 127.0.0.1:8848 |
||||
config: |
||||
# 配置中心地址 |
||||
server-addr: 127.0.0.1:8848 |
||||
# 配置文件格式 |
||||
file-extension: yml |
||||
# 共享配置 |
||||
shared-configs: |
||||
- application-${spring.profiles.active}.${spring.cloud.nacos.config.file-extension} |
||||
# sentinel: |
||||
# # 取消控制台懒加载 |
||||
# eager: true |
||||
# transport: |
||||
# # 控制台地址 |
||||
# dashboard: 127.0.0.1:8718 |
||||
# # nacos配置持久化 |
||||
# datasource: |
||||
# ds1: |
||||
# nacos: |
||||
# server-addr: 127.0.0.1:8848 |
||||
# dataId: sentinel-ruoyi-gateway |
||||
# groupId: DEFAULT_GROUP |
||||
# data-type: json |
||||
# rule-type: flow |
@ -0,0 +1,15 @@
@@ -0,0 +1,15 @@
|
||||
local key = KEYS[1] |
||||
local time = tonumber(ARGV[1]) |
||||
local count = tonumber(ARGV[2]) |
||||
local current = redis.call('get',key) |
||||
if current and tonumber(current) > count then |
||||
return tonumber(current) |
||||
end |
||||
current = redis.call('incr',key) |
||||
if tonumber(current)==1 then |
||||
redis.call('expire',key,time) |
||||
end |
||||
return tonumber(current) |
||||
|
||||
|
||||
|
Loading…
Reference in new issue