diff --git a/README.md b/README.md
index ebc3e849..07cb703a 100644
--- a/README.md
+++ b/README.md
@@ -68,4 +68,4 @@
- QQ交流群:891137268
-- 作者邮箱:zhengjie@tom.com
+- 作者邮箱:elunez@qq.com
diff --git a/pom.xml b/pom.xml
index 072cca42..93c72e07 100644
--- a/pom.xml
+++ b/pom.xml
@@ -75,7 +75,10 @@
commons-pool2
2.5.0
-
+
+ org.apache.commons
+ commons-lang3
+
io.jsonwebtoken
diff --git a/src/main/java/me/zhengjie/common/aop/limit/Limit.java b/src/main/java/me/zhengjie/common/aop/limit/Limit.java
new file mode 100644
index 00000000..cc2febd5
--- /dev/null
+++ b/src/main/java/me/zhengjie/common/aop/limit/Limit.java
@@ -0,0 +1,33 @@
+package me.zhengjie.common.aop.limit;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * @author jacky
+ */
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface Limit {
+
+ // 资源名称,用于描述接口功能
+ String name() default "";
+
+ // 资源 key
+ String key() default "";
+
+ // key prefix
+ String prefix() default "";
+
+ // 时间的,单位秒
+ int period();
+
+ // 限制访问次数
+ int count();
+
+ // 限制类型
+ LimitType limitType() default LimitType.CUSTOMER;
+
+}
diff --git a/src/main/java/me/zhengjie/common/aop/limit/LimitAspect.java b/src/main/java/me/zhengjie/common/aop/limit/LimitAspect.java
new file mode 100644
index 00000000..86747ea1
--- /dev/null
+++ b/src/main/java/me/zhengjie/common/aop/limit/LimitAspect.java
@@ -0,0 +1,85 @@
+package me.zhengjie.common.aop.limit;
+
+import com.google.common.collect.ImmutableList;
+import me.zhengjie.common.exception.BadRequestException;
+import me.zhengjie.common.utils.IpUtil;
+import me.zhengjie.common.utils.RequestHolder;
+import org.apache.commons.lang3.StringUtils;
+import org.aspectj.lang.ProceedingJoinPoint;
+import org.aspectj.lang.annotation.Around;
+import org.aspectj.lang.annotation.Aspect;
+import org.aspectj.lang.annotation.Pointcut;
+import org.aspectj.lang.reflect.MethodSignature;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.redis.core.RedisTemplate;
+import org.springframework.data.redis.core.script.DefaultRedisScript;
+import org.springframework.data.redis.core.script.RedisScript;
+import org.springframework.stereotype.Component;
+
+
+import javax.servlet.http.HttpServletRequest;
+import java.lang.reflect.Method;
+
+@Aspect
+@Component
+public class LimitAspect {
+ @Autowired
+ private RedisTemplate redisTemplate;
+ private static final Logger logger = LoggerFactory.getLogger(LimitAspect.class);
+
+
+ @Pointcut("@annotation(Limit)")
+ public void pointcut() {
+//
+ }
+
+ @Around("pointcut()")
+ public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
+ HttpServletRequest request = RequestHolder.getHttpServletRequest();
+ MethodSignature signature = (MethodSignature) joinPoint.getSignature();
+ Method signatureMethod = signature.getMethod();
+ Limit limit = signatureMethod.getAnnotation(Limit.class);
+ LimitType limitType = limit.limitType();
+ String name = limit.name();
+ String key = limit.key();
+ if (StringUtils.isEmpty(key)) {
+ switch (limitType) {
+ case IP:
+ key = IpUtil.getIP(request);
+ break;
+ default:
+ key = signatureMethod.getName();
+ }
+ }
+
+ ImmutableList keys = ImmutableList.of(StringUtils.join(limit.prefix(), "_", key, "_", request.getRequestURI().replaceAll("/","_")));
+
+ String luaScript = buildLuaScript();
+ RedisScript redisScript = new DefaultRedisScript<>(luaScript, Number.class);
+ Number count = (Number) redisTemplate.execute(redisScript, keys, limit.count(), limit.period());
+ if (null != count && count.intValue() <= limit.count()) {
+ logger.info("第{}次访问key为 {},描述为 [{}] 的接口", count, keys, limit.name());
+ return joinPoint.proceed();
+ } else {
+ throw new BadRequestException("访问次数受限制");
+ }
+ }
+
+ /**
+ * 限流脚本
+ */
+ private String buildLuaScript() {
+ return "local c" +
+ "\nc = redis.call('get',KEYS[1])" +
+ "\nif c and tonumber(c) > tonumber(ARGV[1]) then" +
+ "\nreturn c;" +
+ "\nend" +
+ "\nc = redis.call('incr',KEYS[1])" +
+ "\nif tonumber(c) == 1 then" +
+ "\nredis.call('expire',KEYS[1],ARGV[2])" +
+ "\nend" +
+ "\nreturn c;";
+ }
+}
diff --git a/src/main/java/me/zhengjie/common/aop/limit/LimitType.java b/src/main/java/me/zhengjie/common/aop/limit/LimitType.java
new file mode 100644
index 00000000..7e869753
--- /dev/null
+++ b/src/main/java/me/zhengjie/common/aop/limit/LimitType.java
@@ -0,0 +1,7 @@
+package me.zhengjie.common.aop.limit;
+
+public enum LimitType {
+ CUSTOMER,
+// by ip addr
+ IP;
+}
diff --git a/src/main/java/me/zhengjie/core/config/WebSecurityConfig.java b/src/main/java/me/zhengjie/core/config/WebSecurityConfig.java
index ef42476c..04d748cc 100644
--- a/src/main/java/me/zhengjie/core/config/WebSecurityConfig.java
+++ b/src/main/java/me/zhengjie/core/config/WebSecurityConfig.java
@@ -46,8 +46,8 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth
- .userDetailsService(jwtUserDetailsService)
- .passwordEncoder(passwordEncoderBean());
+ .userDetailsService(jwtUserDetailsService)
+ .passwordEncoder(passwordEncoderBean());
}
@Bean
@@ -65,56 +65,55 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity
- // 禁用 CSRF
- .csrf().disable()
+ // 禁用 CSRF
+ .csrf().disable()
- // 授权异常
- .exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and()
+ // 授权异常
+ .exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and()
- // 不创建会话
- .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
+ // 不创建会话
+ .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
- .authorizeRequests()
+ .authorizeRequests()
- .antMatchers("/auth/**").permitAll()
- .antMatchers("/websocket/**").permitAll()
+ .antMatchers("/auth/**").permitAll()
+ .antMatchers("/websocket/**").permitAll()
+ .antMatchers("/druid/**").anonymous()
+ // swagger start
+ .antMatchers("/swagger-ui.html").anonymous()
+ .antMatchers("/swagger-resources/**").anonymous()
+ .antMatchers("/webjars/**").anonymous()
+ .antMatchers("/*/api-docs").anonymous()
+ // swagger end
+ .antMatchers("/test/**").anonymous()
+ .antMatchers(HttpMethod.OPTIONS, "/**").anonymous()
- .antMatchers("/druid/**").anonymous()
- // swagger start
- .antMatchers("/swagger-ui.html").anonymous()
- .antMatchers("/swagger-resources/**").anonymous()
- .antMatchers("/webjars/**").anonymous()
- .antMatchers("/*/api-docs").anonymous()
- // swagger end
-
- .antMatchers(HttpMethod.OPTIONS, "/**").anonymous()
-
- // 所有请求都需要认证
- .anyRequest().authenticated();
+ // 所有请求都需要认证
+ .anyRequest().authenticated();
httpSecurity
- .addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
+ .addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
}
@Override
public void configure(WebSecurity web) throws Exception {
// AuthenticationTokenFilter will ignore the below paths
web
- .ignoring()
- .antMatchers(
- HttpMethod.POST,
- authenticationPath
- )
+ .ignoring()
+ .antMatchers(
+ HttpMethod.POST,
+ authenticationPath
+ )
- // allow anonymous resource requests
- .and()
- .ignoring()
- .antMatchers(
- HttpMethod.GET,
- "/*.html",
- "/**/*.html",
- "/**/*.css",
- "/**/*.js"
- );
+ // allow anonymous resource requests
+ .and()
+ .ignoring()
+ .antMatchers(
+ HttpMethod.GET,
+ "/*.html",
+ "/**/*.html",
+ "/**/*.css",
+ "/**/*.js"
+ );
}
}
diff --git a/src/main/java/me/zhengjie/monitor/config/LogFilter.java b/src/main/java/me/zhengjie/monitor/config/LogFilter.java
index 7ca13a66..0220a8f3 100644
--- a/src/main/java/me/zhengjie/monitor/config/LogFilter.java
+++ b/src/main/java/me/zhengjie/monitor/config/LogFilter.java
@@ -27,7 +27,7 @@ public class LogFilter extends Filter{
}
}
LogMessage loggerMessage = new LogMessage(
- event.getMessage()
+ event.getFormattedMessage() /* repair format message*/
, DateFormat.getDateTimeInstance().format(new Date(event.getTimeStamp())),
event.getThreadName(),
event.getLoggerName(),
diff --git a/src/main/java/me/zhengjie/tools/rest/TestController.java b/src/main/java/me/zhengjie/tools/rest/TestController.java
new file mode 100644
index 00000000..fd988bf1
--- /dev/null
+++ b/src/main/java/me/zhengjie/tools/rest/TestController.java
@@ -0,0 +1,24 @@
+package me.zhengjie.tools.rest;
+
+import me.zhengjie.common.aop.limit.Limit;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.concurrent.atomic.AtomicInteger;
+
+@RestController
+@RequestMapping("test")
+public class TestController {
+ private static final AtomicInteger ATOMIC_INTEGER = new AtomicInteger();
+
+ /**
+ * 测试限流注解,下面配置说明该接口 60秒内最多只能访问 10次,保存到redis的键名为 limit_test,
+ */
+ @Limit(key = "test", period = 60, count = 10, name = "testLimit", prefix = "limit")
+ @GetMapping("limit")
+ public int testLimit() {
+ return ATOMIC_INTEGER.incrementAndGet();
+ }
+}