From 22c13695a018d047e8146bcbaec5191a161c668a Mon Sep 17 00:00:00 2001 From: Qingning <65934646+addw1@users.noreply.github.com> Date: Sun, 11 Aug 2024 18:26:11 +0800 Subject: [PATCH 1/3] Added Token Interceptor for http request --- .../common/config/InterceptorConfig.java | 23 +++++++ .../common/common/exception/ErrorEnum.java | 8 +++ .../common/exception/HttpErrorEnum.java | 35 +++++++++++ .../common/interceptor/TokenInterceptor.java | 61 +++++++++++++++++++ .../common/domain/vo/response/ApiResult.java | 51 ++++++++++++++++ .../user/controller/GithubController.java | 6 +- 6 files changed, 181 insertions(+), 3 deletions(-) create mode 100644 summer.code/dev/code-bot/code-bot-chat-server/src/main/java/com/ning/codebot/common/common/config/InterceptorConfig.java create mode 100644 summer.code/dev/code-bot/code-bot-chat-server/src/main/java/com/ning/codebot/common/common/exception/ErrorEnum.java create mode 100644 summer.code/dev/code-bot/code-bot-chat-server/src/main/java/com/ning/codebot/common/common/exception/HttpErrorEnum.java create mode 100644 summer.code/dev/code-bot/code-bot-chat-server/src/main/java/com/ning/codebot/common/common/interceptor/TokenInterceptor.java create mode 100644 summer.code/dev/code-bot/code-bot-chat-server/src/main/java/com/ning/codebot/common/domain/vo/response/ApiResult.java diff --git a/summer.code/dev/code-bot/code-bot-chat-server/src/main/java/com/ning/codebot/common/common/config/InterceptorConfig.java b/summer.code/dev/code-bot/code-bot-chat-server/src/main/java/com/ning/codebot/common/common/config/InterceptorConfig.java new file mode 100644 index 000000000..9d8c218f6 --- /dev/null +++ b/summer.code/dev/code-bot/code-bot-chat-server/src/main/java/com/ning/codebot/common/common/config/InterceptorConfig.java @@ -0,0 +1,23 @@ +package com.ning.codebot.common.common.config; + +import com.ning.codebot.common.common.interceptor.TokenInterceptor; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.config.annotation.InterceptorRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +@Configuration +public class InterceptorConfig implements WebMvcConfigurer { + + @Autowired + private TokenInterceptor tokenInterceptor; + + @Override + public void addInterceptors(InterceptorRegistry registry) { + registry.addInterceptor(tokenInterceptor) + .addPathPatterns("/codebot/**"); + + } +} + + diff --git a/summer.code/dev/code-bot/code-bot-chat-server/src/main/java/com/ning/codebot/common/common/exception/ErrorEnum.java b/summer.code/dev/code-bot/code-bot-chat-server/src/main/java/com/ning/codebot/common/common/exception/ErrorEnum.java new file mode 100644 index 000000000..ae244b5f9 --- /dev/null +++ b/summer.code/dev/code-bot/code-bot-chat-server/src/main/java/com/ning/codebot/common/common/exception/ErrorEnum.java @@ -0,0 +1,8 @@ +package com.ning.codebot.common.common.exception; + +public interface ErrorEnum { + + Integer getErrorCode(); + + String getErrorMsg(); +} diff --git a/summer.code/dev/code-bot/code-bot-chat-server/src/main/java/com/ning/codebot/common/common/exception/HttpErrorEnum.java b/summer.code/dev/code-bot/code-bot-chat-server/src/main/java/com/ning/codebot/common/common/exception/HttpErrorEnum.java new file mode 100644 index 000000000..68129da2c --- /dev/null +++ b/summer.code/dev/code-bot/code-bot-chat-server/src/main/java/com/ning/codebot/common/common/exception/HttpErrorEnum.java @@ -0,0 +1,35 @@ +package com.ning.codebot.common.common.exception; + +import cn.hutool.http.ContentType; +import cn.hutool.json.JSONUtil; +import com.google.common.base.Charsets; +import com.ning.codebot.common.domain.vo.response.ApiResult; +import lombok.AllArgsConstructor; + +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; + +@AllArgsConstructor +public enum HttpErrorEnum implements ErrorEnum{ + ACCESS_DENIED(401, "token is invalid, please login"), + ; + private Integer httpCode; + private String msg; + + @Override + public Integer getErrorCode() { + return httpCode; + } + + @Override + public String getErrorMsg() { + return msg; + } + + public void sendHttpError(HttpServletResponse response) throws IOException { + response.setStatus(this.getErrorCode()); + ApiResult responseData = ApiResult.fail(this); + response.setContentType(ContentType.JSON.toString(Charsets.UTF_8)); + response.getWriter().write(JSONUtil.toJsonStr(responseData)); + } +} diff --git a/summer.code/dev/code-bot/code-bot-chat-server/src/main/java/com/ning/codebot/common/common/interceptor/TokenInterceptor.java b/summer.code/dev/code-bot/code-bot-chat-server/src/main/java/com/ning/codebot/common/common/interceptor/TokenInterceptor.java new file mode 100644 index 000000000..e937d45e2 --- /dev/null +++ b/summer.code/dev/code-bot/code-bot-chat-server/src/main/java/com/ning/codebot/common/common/interceptor/TokenInterceptor.java @@ -0,0 +1,61 @@ +package com.ning.codebot.common.common.interceptor; + +import com.ning.codebot.common.common.exception.HttpErrorEnum; +import com.ning.codebot.common.user.service.LoginService; +import lombok.extern.slf4j.Slf4j; +import org.jboss.logging.MDC; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.core.annotation.Order; +import org.springframework.stereotype.Component; +import org.springframework.web.servlet.HandlerInterceptor; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.util.Objects; +import java.util.Optional; + +@Order(-2) +@Slf4j +@Component +public class TokenInterceptor implements HandlerInterceptor { + public static final String AUTHORIZATION_HEADER = "Authorization"; + public static final String AUTHORIZATION_SCHEMA = "Bearer "; + public static final String ATTRIBUTE_UID = "uid"; + + @Autowired + private LoginService loginService; + + + @Override + public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { + //get login user token + String token = getToken(request); + Long validUid = loginService.getValidUid(token); + if (Objects.nonNull(validUid)) { + request.setAttribute(ATTRIBUTE_UID, validUid); + } else { + boolean isPublicURI = isPublicURI(request.getRequestURI()); + if (!isPublicURI) { + HttpErrorEnum.ACCESS_DENIED.sendHttpError(response); + return false; + } + } + return true; + } + + /** + * Check whether it is public resources + * @param requestURI + */ + private boolean isPublicURI(String requestURI) { + //TODO: add check logic + return false; + } + private String getToken(HttpServletRequest request) { + String header = request.getHeader(AUTHORIZATION_HEADER); + return Optional.ofNullable(header) + .filter(h -> h.startsWith(AUTHORIZATION_SCHEMA)) + .map(h -> h.substring(AUTHORIZATION_SCHEMA.length())) + .orElse(null); + } +} diff --git a/summer.code/dev/code-bot/code-bot-chat-server/src/main/java/com/ning/codebot/common/domain/vo/response/ApiResult.java b/summer.code/dev/code-bot/code-bot-chat-server/src/main/java/com/ning/codebot/common/domain/vo/response/ApiResult.java new file mode 100644 index 000000000..0de38cbb5 --- /dev/null +++ b/summer.code/dev/code-bot/code-bot-chat-server/src/main/java/com/ning/codebot/common/domain/vo/response/ApiResult.java @@ -0,0 +1,51 @@ +package com.ning.codebot.common.domain.vo.response; + + +import com.ning.codebot.common.common.exception.ErrorEnum; +import lombok.Data; + +@Data +public class ApiResult { + + private Boolean success; + + private Integer errCode; + + private String errMsg; + + private T data; + + public static ApiResult success() { + ApiResult result = new ApiResult(); + result.setData(null); + result.setSuccess(Boolean.TRUE); + return result; + } + + public static ApiResult success(T data) { + ApiResult result = new ApiResult(); + result.setData(data); + result.setSuccess(Boolean.TRUE); + return result; + } + + public static ApiResult fail(Integer code, String msg) { + ApiResult result = new ApiResult(); + result.setSuccess(Boolean.FALSE); + result.setErrCode(code); + result.setErrMsg(msg); + return result; + } + + public static ApiResult fail(ErrorEnum errorEnum) { + ApiResult result = new ApiResult(); + result.setSuccess(Boolean.FALSE); + result.setErrCode(errorEnum.getErrorCode()); + result.setErrMsg(errorEnum.getErrorMsg()); + return result; + } + + public boolean isSuccess() { + return this.success; + } +} diff --git a/summer.code/dev/code-bot/code-bot-chat-server/src/main/java/com/ning/codebot/common/user/controller/GithubController.java b/summer.code/dev/code-bot/code-bot-chat-server/src/main/java/com/ning/codebot/common/user/controller/GithubController.java index 4131e89dc..9577c2d2f 100644 --- a/summer.code/dev/code-bot/code-bot-chat-server/src/main/java/com/ning/codebot/common/user/controller/GithubController.java +++ b/summer.code/dev/code-bot/code-bot-chat-server/src/main/java/com/ning/codebot/common/user/controller/GithubController.java @@ -45,7 +45,7 @@ public String authorize() { * @return */ @GetMapping("/oauth2/callback") - public String callback(@RequestParam("code") String code, @RequestParam("state") String state) { + public void callback(@RequestParam("code") String code, @RequestParam("state") String state) { log.info("code={}", code); log.info("state={}", state); // code -> token @@ -53,8 +53,8 @@ public String callback(@RequestParam("code") String code, @RequestParam("state") // token -> userInfo String userInfo = getUserInfo(accessToken); outh2Service.storeUserInfo(Integer.parseInt(state), new GithubUserInfo(JsonUtils.toJsonNode(userInfo))); - log.info("redirect to the home page"); - return "redirect:/test"; + // log.info("redirect to the home page"); + // return "redirect:/test"; } @GetMapping("/test") From 1aa945cb83d9f264f569f0b333ba1a34de591f15 Mon Sep 17 00:00:00 2001 From: Qingning <65934646+addw1@users.noreply.github.com> Date: Sun, 11 Aug 2024 19:24:53 +0800 Subject: [PATCH 2/3] Added distributed lock --- .../common/annotation/RedissonLock.java | 26 +++++++++ .../common/aspect/RedissonLockAspect.java | 36 +++++++++++++ .../common/common/service/LockService.java | 54 +++++++++++++++++++ .../common/common/utils/SpElUtils.java | 30 +++++++++++ .../repository/controller/RepoController.java | 19 +++++++ .../user/controller/GithubController.java | 4 +- .../user/controller/UserController.java | 2 +- 7 files changed, 168 insertions(+), 3 deletions(-) create mode 100644 summer.code/dev/code-bot/code-bot-chat-server/src/main/java/com/ning/codebot/common/common/annotation/RedissonLock.java create mode 100644 summer.code/dev/code-bot/code-bot-chat-server/src/main/java/com/ning/codebot/common/common/aspect/RedissonLockAspect.java create mode 100644 summer.code/dev/code-bot/code-bot-chat-server/src/main/java/com/ning/codebot/common/common/service/LockService.java create mode 100644 summer.code/dev/code-bot/code-bot-chat-server/src/main/java/com/ning/codebot/common/common/utils/SpElUtils.java create mode 100644 summer.code/dev/code-bot/code-bot-chat-server/src/main/java/com/ning/codebot/common/repository/controller/RepoController.java diff --git a/summer.code/dev/code-bot/code-bot-chat-server/src/main/java/com/ning/codebot/common/common/annotation/RedissonLock.java b/summer.code/dev/code-bot/code-bot-chat-server/src/main/java/com/ning/codebot/common/common/annotation/RedissonLock.java new file mode 100644 index 000000000..a94cdd492 --- /dev/null +++ b/summer.code/dev/code-bot/code-bot-chat-server/src/main/java/com/ning/codebot/common/common/annotation/RedissonLock.java @@ -0,0 +1,26 @@ +package com.ning.codebot.common.common.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.util.concurrent.TimeUnit; + +/** + * distributed lock + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.METHOD) +public @interface RedissonLock { + + String prefixKey() default ""; + String key(); + /** + * do not wait -1 + * @return second + */ + int waitTime() default -1; + TimeUnit unit() default TimeUnit.MILLISECONDS; + +} + diff --git a/summer.code/dev/code-bot/code-bot-chat-server/src/main/java/com/ning/codebot/common/common/aspect/RedissonLockAspect.java b/summer.code/dev/code-bot/code-bot-chat-server/src/main/java/com/ning/codebot/common/common/aspect/RedissonLockAspect.java new file mode 100644 index 000000000..b741316c8 --- /dev/null +++ b/summer.code/dev/code-bot/code-bot-chat-server/src/main/java/com/ning/codebot/common/common/aspect/RedissonLockAspect.java @@ -0,0 +1,36 @@ +package com.ning.codebot.common.common.aspect; + + +import cn.hutool.core.util.StrUtil; +import com.ning.codebot.common.common.annotation.RedissonLock; +import com.ning.codebot.common.common.service.LockService; +import lombok.extern.slf4j.Slf4j; +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.annotation.Around; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.reflect.MethodSignature; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.core.annotation.Order; +import org.springframework.stereotype.Component; +import com.ning.codebot.common.common.utils.SpElUtils; + +import java.lang.reflect.Method; + +@Slf4j +@Aspect +@Component +@Order(0) +public class RedissonLockAspect { + @Autowired + private LockService lockService; + + @Around("@annotation(com.ning.codebot.common.common.annotation.RedissonLock)") + public Object around(ProceedingJoinPoint joinPoint) throws Throwable { + Method method = ((MethodSignature) joinPoint.getSignature()).getMethod(); + RedissonLock redissonLock = method.getAnnotation(RedissonLock.class); + String prefix = StrUtil.isBlank(redissonLock.prefixKey()) ? SpElUtils.getMethodKey(method) : redissonLock.prefixKey();//默认方法限定名+注解排名(可能多个) + String key = SpElUtils.parseSpEl(method, joinPoint.getArgs(), redissonLock.key()); + return lockService.executeWithLockThrows(prefix + ":" + key, redissonLock.waitTime(), redissonLock.unit(), joinPoint::proceed); + } +} + diff --git a/summer.code/dev/code-bot/code-bot-chat-server/src/main/java/com/ning/codebot/common/common/service/LockService.java b/summer.code/dev/code-bot/code-bot-chat-server/src/main/java/com/ning/codebot/common/common/service/LockService.java new file mode 100644 index 000000000..a7ab38f10 --- /dev/null +++ b/summer.code/dev/code-bot/code-bot-chat-server/src/main/java/com/ning/codebot/common/common/service/LockService.java @@ -0,0 +1,54 @@ +package com.ning.codebot.common.common.service; + +import org.redisson.api.RLock; +import org.redisson.api.RedissonClient; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import lombok.SneakyThrows; + +import java.util.concurrent.TimeUnit; +import java.util.function.Supplier; + +@Service +@Slf4j +public class LockService { + + @Autowired + private RedissonClient redissonClient; + + public T executeWithLockThrows(String key, int waitTime, TimeUnit unit, SupplierThrow supplier) throws Throwable { + RLock lock = redissonClient.getLock(key); + boolean lockSuccess = lock.tryLock(waitTime, unit); + if (!lockSuccess) { + //TODO: add new exception + throw new Exception(); + } + try { + return supplier.get(); + } finally { + if (lock.isLocked() && lock.isHeldByCurrentThread()) { + lock.unlock(); + } + } + } + + @SneakyThrows + public T executeWithLock(String key, int waitTime, TimeUnit unit, Supplier supplier) { + return executeWithLockThrows(key, waitTime, unit, supplier::get); + } + + public T executeWithLock(String key, Supplier supplier) { + return executeWithLock(key, -1, TimeUnit.MILLISECONDS, supplier); + } + + @FunctionalInterface + public interface SupplierThrow { + + /** + * Gets a result. + * + * @return a result + */ + T get() throws Throwable; + } +} diff --git a/summer.code/dev/code-bot/code-bot-chat-server/src/main/java/com/ning/codebot/common/common/utils/SpElUtils.java b/summer.code/dev/code-bot/code-bot-chat-server/src/main/java/com/ning/codebot/common/common/utils/SpElUtils.java new file mode 100644 index 000000000..0b4c815fe --- /dev/null +++ b/summer.code/dev/code-bot/code-bot-chat-server/src/main/java/com/ning/codebot/common/common/utils/SpElUtils.java @@ -0,0 +1,30 @@ +package com.ning.codebot.common.common.utils; + +import org.springframework.core.DefaultParameterNameDiscoverer; +import org.springframework.expression.EvaluationContext; +import org.springframework.expression.Expression; +import org.springframework.expression.ExpressionParser; +import org.springframework.expression.spel.standard.SpelExpressionParser; +import org.springframework.expression.spel.support.StandardEvaluationContext; + +import java.lang.reflect.Method; +import java.util.Optional; + +public class SpElUtils { + private static final ExpressionParser parser = new SpelExpressionParser(); + private static final DefaultParameterNameDiscoverer parameterNameDiscoverer = new DefaultParameterNameDiscoverer(); + + public static String parseSpEl(Method method, Object[] args, String spEl) { + String[] params = Optional.ofNullable(parameterNameDiscoverer.getParameterNames(method)).orElse(new String[]{});//解析参数名 + EvaluationContext context = new StandardEvaluationContext(); + for (int i = 0; i < params.length; i++) { + context.setVariable(params[i], args[i]); + } + Expression expression = parser.parseExpression(spEl); + return expression.getValue(context, String.class); + } + + public static String getMethodKey(Method method) { + return method.getDeclaringClass() + "#" + method.getName(); + } +} diff --git a/summer.code/dev/code-bot/code-bot-chat-server/src/main/java/com/ning/codebot/common/repository/controller/RepoController.java b/summer.code/dev/code-bot/code-bot-chat-server/src/main/java/com/ning/codebot/common/repository/controller/RepoController.java new file mode 100644 index 000000000..339e42876 --- /dev/null +++ b/summer.code/dev/code-bot/code-bot-chat-server/src/main/java/com/ning/codebot/common/repository/controller/RepoController.java @@ -0,0 +1,19 @@ +package com.ning.codebot.common.repository.controller; + +import com.ning.codebot.common.common.annotation.RedissonLock; +import com.ning.codebot.common.domain.vo.response.ApiResult; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; + +@Controller +@RequestMapping("codebot/reporsitory") +public class RepoController { + @PostMapping("/subscribe") + @RedissonLock(key = "#subscribe") + public ApiResult subscribe(@RequestBody String author, @RequestBody String repoName){ + //TODO: finish the check logic to avoid multiple times download + return new ApiResult<>(); + } +} diff --git a/summer.code/dev/code-bot/code-bot-chat-server/src/main/java/com/ning/codebot/common/user/controller/GithubController.java b/summer.code/dev/code-bot/code-bot-chat-server/src/main/java/com/ning/codebot/common/user/controller/GithubController.java index 9577c2d2f..3a637d11d 100644 --- a/summer.code/dev/code-bot/code-bot-chat-server/src/main/java/com/ning/codebot/common/user/controller/GithubController.java +++ b/summer.code/dev/code-bot/code-bot-chat-server/src/main/java/com/ning/codebot/common/user/controller/GithubController.java @@ -45,7 +45,7 @@ public String authorize() { * @return */ @GetMapping("/oauth2/callback") - public void callback(@RequestParam("code") String code, @RequestParam("state") String state) { + public String callback(@RequestParam("code") String code, @RequestParam("state") String state) { log.info("code={}", code); log.info("state={}", state); // code -> token @@ -54,7 +54,7 @@ public void callback(@RequestParam("code") String code, @RequestParam("state") S String userInfo = getUserInfo(accessToken); outh2Service.storeUserInfo(Integer.parseInt(state), new GithubUserInfo(JsonUtils.toJsonNode(userInfo))); // log.info("redirect to the home page"); - // return "redirect:/test"; + return "redirect:/test"; } @GetMapping("/test") diff --git a/summer.code/dev/code-bot/code-bot-chat-server/src/main/java/com/ning/codebot/common/user/controller/UserController.java b/summer.code/dev/code-bot/code-bot-chat-server/src/main/java/com/ning/codebot/common/user/controller/UserController.java index 34d58b093..1c5291872 100644 --- a/summer.code/dev/code-bot/code-bot-chat-server/src/main/java/com/ning/codebot/common/user/controller/UserController.java +++ b/summer.code/dev/code-bot/code-bot-chat-server/src/main/java/com/ning/codebot/common/user/controller/UserController.java @@ -7,7 +7,7 @@ @Controller -@RequestMapping("/user") +@RequestMapping("codebot/user") public class UserController { } From 7064ec15616cb5460e4566263c63e1a72e0de8e3 Mon Sep 17 00:00:00 2001 From: Qingning <65934646+addw1@users.noreply.github.com> Date: Sun, 11 Aug 2024 20:25:08 +0800 Subject: [PATCH 3/3] Fix a bug --- .../java/com/ning/codebot/common/common/service/LockService.java | 1 + 1 file changed, 1 insertion(+) diff --git a/summer.code/dev/code-bot/code-bot-chat-server/src/main/java/com/ning/codebot/common/common/service/LockService.java b/summer.code/dev/code-bot/code-bot-chat-server/src/main/java/com/ning/codebot/common/common/service/LockService.java index a7ab38f10..c135c29f5 100644 --- a/summer.code/dev/code-bot/code-bot-chat-server/src/main/java/com/ning/codebot/common/common/service/LockService.java +++ b/summer.code/dev/code-bot/code-bot-chat-server/src/main/java/com/ning/codebot/common/common/service/LockService.java @@ -1,5 +1,6 @@ package com.ning.codebot.common.common.service; +import lombok.extern.slf4j.Slf4j; import org.redisson.api.RLock; import org.redisson.api.RedissonClient; import org.springframework.beans.factory.annotation.Autowired;