From e5feaea601a919f257c939f2dc4769be01651969 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E7=99=BD?= <15656564262@163.com> Date: Sat, 21 Sep 2024 14:46:04 +0800 Subject: [PATCH] init --- .gitignore | 30 +++++ LICENSE | 21 ++++ Makefile | 6 + README.md | 51 +++++++++ .../1.0.0/qaq-common-1.0.0-sources.jar.md5 | 1 + .../1.0.0/qaq-common-1.0.0-sources.jar.sha1 | 1 + .../qaq-common/1.0.0/qaq-common-1.0.0.jar.md5 | 1 + .../1.0.0/qaq-common-1.0.0.jar.sha1 | 1 + .../qaq-common/1.0.0/qaq-common-1.0.0.pom | 101 +++++++++++++++++ .../qaq-common/1.0.0/qaq-common-1.0.0.pom.md5 | 1 + .../1.0.0/qaq-common-1.0.0.pom.sha1 | 1 + .../qaq-public/qaq-common/maven-metadata.xml | 12 ++ .../qaq-common/maven-metadata.xml.md5 | 1 + .../qaq-common/maven-metadata.xml.sha1 | 1 + pom.xml | 101 +++++++++++++++++ .../qaq/base/annotation/CheckPermission.java | 12 ++ .../java/com/qaq/base/aspect/LogAspect.java | 57 ++++++++++ .../base/aspect/PermissionCheckAspect.java | 46 ++++++++ .../java/com/qaq/base/component/HttpUtil.java | 82 ++++++++++++++ .../base/component/JWTGeneratorComponent.java | 105 ++++++++++++++++++ .../base/component/JWTVerifierComponent.java | 49 ++++++++ .../com/qaq/base/component/NotifyUtil.java | 100 +++++++++++++++++ .../qaq/base/config/JWTGeneratorConfig.java | 61 ++++++++++ .../qaq/base/config/JWTVerifierConfig.java | 29 +++++ .../java/com/qaq/base/config/LarkConfig.java | 38 +++++++ .../com/qaq/base/config/LogginConfig.java | 15 +++ .../com/qaq/base/config/NotifyConfig.java | 42 +++++++ .../qaq/base/config/RestTemplateConfig.java | 48 ++++++++ .../java/com/qaq/base/config/WebConfig.java | 16 +++ .../com/qaq/base/enums/GatewayHeaderEnum.java | 21 ++++ .../base/exception/UnAuthorizedException.java | 7 ++ .../com/qaq/base/filter/LoggingFilter.java | 43 +++++++ .../qaq/base/intercepter/AuthInterceptor.java | 67 +++++++++++ src/main/java/com/qaq/base/model/Auth.java | 22 ++++ src/main/java/com/qaq/base/model/Message.java | 20 ++++ .../java/com/qaq/base/model/login/Token.java | 23 ++++ .../qaq/base/model/uniauth/AuthResult.java | 21 ++++ .../base/model/uniauth/AuthResultResp.java | 6 + .../com/qaq/base/response/ApiResponse.java | 29 +++++ .../java/com/qaq/base/response/PageData.java | 23 ++++ .../java/com/qaq/base/utils/DateUtils.java | 66 +++++++++++ .../com/qaq/base/utils/HttpContextUtils.java | 55 +++++++++ .../base/utils/MutableHttpServletRequest.java | 51 +++++++++ .../java/com/qaq/base/utils/ObjectUtils.java | 56 ++++++++++ src/main/java/com/qaq/base/utils/Util.java | 5 + 45 files changed, 1545 insertions(+) create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 Makefile create mode 100644 README.md create mode 100644 maven-repo/io/github/qaq-public/qaq-common/1.0.0/qaq-common-1.0.0-sources.jar.md5 create mode 100644 maven-repo/io/github/qaq-public/qaq-common/1.0.0/qaq-common-1.0.0-sources.jar.sha1 create mode 100644 maven-repo/io/github/qaq-public/qaq-common/1.0.0/qaq-common-1.0.0.jar.md5 create mode 100644 maven-repo/io/github/qaq-public/qaq-common/1.0.0/qaq-common-1.0.0.jar.sha1 create mode 100644 maven-repo/io/github/qaq-public/qaq-common/1.0.0/qaq-common-1.0.0.pom create mode 100644 maven-repo/io/github/qaq-public/qaq-common/1.0.0/qaq-common-1.0.0.pom.md5 create mode 100644 maven-repo/io/github/qaq-public/qaq-common/1.0.0/qaq-common-1.0.0.pom.sha1 create mode 100644 maven-repo/io/github/qaq-public/qaq-common/maven-metadata.xml create mode 100644 maven-repo/io/github/qaq-public/qaq-common/maven-metadata.xml.md5 create mode 100644 maven-repo/io/github/qaq-public/qaq-common/maven-metadata.xml.sha1 create mode 100644 pom.xml create mode 100644 src/main/java/com/qaq/base/annotation/CheckPermission.java create mode 100644 src/main/java/com/qaq/base/aspect/LogAspect.java create mode 100644 src/main/java/com/qaq/base/aspect/PermissionCheckAspect.java create mode 100644 src/main/java/com/qaq/base/component/HttpUtil.java create mode 100644 src/main/java/com/qaq/base/component/JWTGeneratorComponent.java create mode 100644 src/main/java/com/qaq/base/component/JWTVerifierComponent.java create mode 100644 src/main/java/com/qaq/base/component/NotifyUtil.java create mode 100644 src/main/java/com/qaq/base/config/JWTGeneratorConfig.java create mode 100644 src/main/java/com/qaq/base/config/JWTVerifierConfig.java create mode 100644 src/main/java/com/qaq/base/config/LarkConfig.java create mode 100644 src/main/java/com/qaq/base/config/LogginConfig.java create mode 100644 src/main/java/com/qaq/base/config/NotifyConfig.java create mode 100644 src/main/java/com/qaq/base/config/RestTemplateConfig.java create mode 100644 src/main/java/com/qaq/base/config/WebConfig.java create mode 100644 src/main/java/com/qaq/base/enums/GatewayHeaderEnum.java create mode 100644 src/main/java/com/qaq/base/exception/UnAuthorizedException.java create mode 100644 src/main/java/com/qaq/base/filter/LoggingFilter.java create mode 100644 src/main/java/com/qaq/base/intercepter/AuthInterceptor.java create mode 100644 src/main/java/com/qaq/base/model/Auth.java create mode 100644 src/main/java/com/qaq/base/model/Message.java create mode 100644 src/main/java/com/qaq/base/model/login/Token.java create mode 100644 src/main/java/com/qaq/base/model/uniauth/AuthResult.java create mode 100644 src/main/java/com/qaq/base/model/uniauth/AuthResultResp.java create mode 100644 src/main/java/com/qaq/base/response/ApiResponse.java create mode 100644 src/main/java/com/qaq/base/response/PageData.java create mode 100644 src/main/java/com/qaq/base/utils/DateUtils.java create mode 100644 src/main/java/com/qaq/base/utils/HttpContextUtils.java create mode 100644 src/main/java/com/qaq/base/utils/MutableHttpServletRequest.java create mode 100644 src/main/java/com/qaq/base/utils/ObjectUtils.java create mode 100644 src/main/java/com/qaq/base/utils/Util.java diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5493eb8 --- /dev/null +++ b/.gitignore @@ -0,0 +1,30 @@ +### Java template +# Compiled class file +*.class + +# Log file +*.log + +# BlueJ files +*.ctxt + +# Mobile Tools for Java (J2ME) +.mtj.tmp/ + +# Package Files # +*.jar +*.war +*.nar +*.ear +*.zip +*.tar.gz +*.rar + +# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml +hs_err_pid* + +*.iml +.idea +target/ +.vscode/ +.DS_Store \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..9cffc96 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2022 qaq-public + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..df9e322 --- /dev/null +++ b/Makefile @@ -0,0 +1,6 @@ +TAG?=1.0.0 +NAME:=qaq-common + +deploy: + mvn clean package deploy + diff --git a/README.md b/README.md new file mode 100644 index 0000000..e3731da --- /dev/null +++ b/README.md @@ -0,0 +1,51 @@ +# qaq-base-java + +qaq 的 java 公用代码,旨在让 QAQ 开发者便捷的调用 QAQ 开放 API。 +[发布 Maven 包的正确姿势](https://zhuanlan.zhihu.com/p/141676033) + +## 目录 + + +- [安装](#安装) +- [加入答疑群](#加入答疑群) +- [License](#License) + + + +## 安装 + +- 运行环境:JDK 21 及以上 + +- 最新版本 maven 坐标 + +```shell + + io.github.qaq-public + qaq-common + 1.0.0 + +``` + +- 如无法获取 qaq 依赖,请在 pom.xml 的 里增加 + +```shell + + + + gitpub-qaq-repo + The QAQ Repository on Github + https://qaq-public.github.io/qaq-common/maven-repo/ + + + + ... + + +``` + +## 加入答疑群 + +[单击加入答疑群](https://applink.feishu.cn/client/chat/chatter/add_by_link?link_token=828s731f-83f2-400a-b093-04667ca93d4c) + +## License +使用 MIT diff --git a/maven-repo/io/github/qaq-public/qaq-common/1.0.0/qaq-common-1.0.0-sources.jar.md5 b/maven-repo/io/github/qaq-public/qaq-common/1.0.0/qaq-common-1.0.0-sources.jar.md5 new file mode 100644 index 0000000..4a45220 --- /dev/null +++ b/maven-repo/io/github/qaq-public/qaq-common/1.0.0/qaq-common-1.0.0-sources.jar.md5 @@ -0,0 +1 @@ +6515e3e8fa38c41cd7601b4fd6867055 \ No newline at end of file diff --git a/maven-repo/io/github/qaq-public/qaq-common/1.0.0/qaq-common-1.0.0-sources.jar.sha1 b/maven-repo/io/github/qaq-public/qaq-common/1.0.0/qaq-common-1.0.0-sources.jar.sha1 new file mode 100644 index 0000000..c607e68 --- /dev/null +++ b/maven-repo/io/github/qaq-public/qaq-common/1.0.0/qaq-common-1.0.0-sources.jar.sha1 @@ -0,0 +1 @@ +7937230f0c22b50bd2de503cba3e7de9f4c385b0 \ No newline at end of file diff --git a/maven-repo/io/github/qaq-public/qaq-common/1.0.0/qaq-common-1.0.0.jar.md5 b/maven-repo/io/github/qaq-public/qaq-common/1.0.0/qaq-common-1.0.0.jar.md5 new file mode 100644 index 0000000..0dfe550 --- /dev/null +++ b/maven-repo/io/github/qaq-public/qaq-common/1.0.0/qaq-common-1.0.0.jar.md5 @@ -0,0 +1 @@ +89c7cd66d2eb2e44401dd5be876fbe15 \ No newline at end of file diff --git a/maven-repo/io/github/qaq-public/qaq-common/1.0.0/qaq-common-1.0.0.jar.sha1 b/maven-repo/io/github/qaq-public/qaq-common/1.0.0/qaq-common-1.0.0.jar.sha1 new file mode 100644 index 0000000..36efb8f --- /dev/null +++ b/maven-repo/io/github/qaq-public/qaq-common/1.0.0/qaq-common-1.0.0.jar.sha1 @@ -0,0 +1 @@ +738b34b056bb4e007d155122910d60d537c587ac \ No newline at end of file diff --git a/maven-repo/io/github/qaq-public/qaq-common/1.0.0/qaq-common-1.0.0.pom b/maven-repo/io/github/qaq-public/qaq-common/1.0.0/qaq-common-1.0.0.pom new file mode 100644 index 0000000..e77a893 --- /dev/null +++ b/maven-repo/io/github/qaq-public/qaq-common/1.0.0/qaq-common-1.0.0.pom @@ -0,0 +1,101 @@ + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 3.3.4 + + + io.github.qaq-public + qaq-common + 1.0.0 + jar + qaq-common + + + blacklee123 + 15656564262@163.com + qaq-public + https://qaq-public.github.io + + + + + + UTF-8 + 21 + 21 + false + 21 + + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-validation + + + com.auth0 + java-jwt + 4.4.0 + + + org.bouncycastle + bcprov-jdk15on + 1.70 + + + com.larksuite.oapi + oapi-sdk + 2.3.4 + + + org.aspectj + aspectjweaver + 1.9.22.1 + + + org.projectlombok + lombok + 1.18.34 + provided + true + + + org.projectlombok + lombok + 1.18.30 + provided + + + + + + local-repo-release + GitHub Release + file://${project.basedir}/maven-repo + + + + + + + maven-source-plugin + + + attach-sources + package + + jar-no-fork + + + + + + + \ No newline at end of file diff --git a/maven-repo/io/github/qaq-public/qaq-common/1.0.0/qaq-common-1.0.0.pom.md5 b/maven-repo/io/github/qaq-public/qaq-common/1.0.0/qaq-common-1.0.0.pom.md5 new file mode 100644 index 0000000..8aa9906 --- /dev/null +++ b/maven-repo/io/github/qaq-public/qaq-common/1.0.0/qaq-common-1.0.0.pom.md5 @@ -0,0 +1 @@ +b4341fd663b1af41e057a47e901ff13c \ No newline at end of file diff --git a/maven-repo/io/github/qaq-public/qaq-common/1.0.0/qaq-common-1.0.0.pom.sha1 b/maven-repo/io/github/qaq-public/qaq-common/1.0.0/qaq-common-1.0.0.pom.sha1 new file mode 100644 index 0000000..735569f --- /dev/null +++ b/maven-repo/io/github/qaq-public/qaq-common/1.0.0/qaq-common-1.0.0.pom.sha1 @@ -0,0 +1 @@ +c7843350bb2c02180ba8e133873f60264933719b \ No newline at end of file diff --git a/maven-repo/io/github/qaq-public/qaq-common/maven-metadata.xml b/maven-repo/io/github/qaq-public/qaq-common/maven-metadata.xml new file mode 100644 index 0000000..b3c06d3 --- /dev/null +++ b/maven-repo/io/github/qaq-public/qaq-common/maven-metadata.xml @@ -0,0 +1,12 @@ + + + io.github.qaq-public + qaq-common + + 1.0.0 + + 1.0.0 + + 20240921064523 + + diff --git a/maven-repo/io/github/qaq-public/qaq-common/maven-metadata.xml.md5 b/maven-repo/io/github/qaq-public/qaq-common/maven-metadata.xml.md5 new file mode 100644 index 0000000..7b1f7f4 --- /dev/null +++ b/maven-repo/io/github/qaq-public/qaq-common/maven-metadata.xml.md5 @@ -0,0 +1 @@ +a58a8999e938cc4054cfbdbce752906b \ No newline at end of file diff --git a/maven-repo/io/github/qaq-public/qaq-common/maven-metadata.xml.sha1 b/maven-repo/io/github/qaq-public/qaq-common/maven-metadata.xml.sha1 new file mode 100644 index 0000000..98ed42f --- /dev/null +++ b/maven-repo/io/github/qaq-public/qaq-common/maven-metadata.xml.sha1 @@ -0,0 +1 @@ +7f8ac3db05b4b357367308831d6c4a75dc06df01 \ No newline at end of file diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..e77a893 --- /dev/null +++ b/pom.xml @@ -0,0 +1,101 @@ + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 3.3.4 + + + io.github.qaq-public + qaq-common + 1.0.0 + jar + qaq-common + + + blacklee123 + 15656564262@163.com + qaq-public + https://qaq-public.github.io + + + + + + UTF-8 + 21 + 21 + false + 21 + + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-validation + + + com.auth0 + java-jwt + 4.4.0 + + + org.bouncycastle + bcprov-jdk15on + 1.70 + + + com.larksuite.oapi + oapi-sdk + 2.3.4 + + + org.aspectj + aspectjweaver + 1.9.22.1 + + + org.projectlombok + lombok + 1.18.34 + provided + true + + + org.projectlombok + lombok + 1.18.30 + provided + + + + + + local-repo-release + GitHub Release + file://${project.basedir}/maven-repo + + + + + + + maven-source-plugin + + + attach-sources + package + + jar-no-fork + + + + + + + \ No newline at end of file diff --git a/src/main/java/com/qaq/base/annotation/CheckPermission.java b/src/main/java/com/qaq/base/annotation/CheckPermission.java new file mode 100644 index 0000000..f1838e7 --- /dev/null +++ b/src/main/java/com/qaq/base/annotation/CheckPermission.java @@ -0,0 +1,12 @@ +package com.qaq.base.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +public @interface CheckPermission { + String value(); +} diff --git a/src/main/java/com/qaq/base/aspect/LogAspect.java b/src/main/java/com/qaq/base/aspect/LogAspect.java new file mode 100644 index 0000000..1a7c16a --- /dev/null +++ b/src/main/java/com/qaq/base/aspect/LogAspect.java @@ -0,0 +1,57 @@ +package com.qaq.base.aspect; + +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.annotation.Around; +import org.aspectj.lang.annotation.Aspect; +import org.springframework.core.annotation.Order; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@Aspect +@Order(1) +public class LogAspect { + + @Around("within(@org.springframework.web.bind.annotation.RestController *) || within(@org.springframework.stereotype.Controller *)") + public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable { + long startTime = System.currentTimeMillis(); + ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); + + String queryString = null; + String requestMethod = null; + String requestUri = null; + + if (attributes != null && attributes.getRequest() != null) { + var request = attributes.getRequest(); + queryString = request.getQueryString(); + requestMethod = request.getMethod(); + requestUri = request.getRequestURI(); + } + + try { + Object result = joinPoint.proceed(); + var responseStatus = attributes != null && attributes.getResponse() != null ? attributes.getResponse().getStatus() : 0; + if (queryString != null) { + log.info("{} {} {}?{} {}", responseStatus, requestMethod, requestUri, queryString, + System.currentTimeMillis() - startTime); + } else { + log.info("{} {} {} {}", responseStatus, requestMethod, requestUri, + System.currentTimeMillis() - startTime); + } + return result; + } catch (Throwable ex) { + int responseStatus = attributes != null && attributes.getResponse() != null ? attributes.getResponse().getStatus() : 500; + if (queryString != null) { + log.error("{} {} {}?{} {} Exception: {}", responseStatus, requestMethod, requestUri, queryString, + System.currentTimeMillis() - startTime, ex.getMessage(), ex); + } else if (attributes != null) { + log.error("{} {} {} {} Exception: {}", responseStatus, requestMethod, requestUri, + System.currentTimeMillis() - startTime, ex.getMessage(), ex); + } else { + log.error("Exception at {} with message: {}", joinPoint, ex.getMessage(), ex); + } + throw ex; + } + } +} \ No newline at end of file diff --git a/src/main/java/com/qaq/base/aspect/PermissionCheckAspect.java b/src/main/java/com/qaq/base/aspect/PermissionCheckAspect.java new file mode 100644 index 0000000..ef844dc --- /dev/null +++ b/src/main/java/com/qaq/base/aspect/PermissionCheckAspect.java @@ -0,0 +1,46 @@ +package com.qaq.base.aspect; + +import com.qaq.base.annotation.CheckPermission; +import com.qaq.base.exception.UnAuthorizedException; +import com.qaq.base.model.Auth; +import jakarta.servlet.http.HttpServletRequest; +import org.aspectj.lang.JoinPoint; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.annotation.Before; +import org.aspectj.lang.annotation.Pointcut; +import org.aspectj.lang.reflect.MethodSignature; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; + +import java.lang.reflect.Method; + +@Aspect +public class PermissionCheckAspect { + + @Pointcut("@annotation(com.qaq.base.annotation.CheckPermission)") + public void readCheckerPoint() { + } + + @Before("readCheckerPoint()") + public void advice(JoinPoint joinPoint) { + HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()) + .getRequest(); + Auth auth = (Auth) request.getAttribute("auth"); + MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature(); + Method method = methodSignature.getMethod(); + var checkPermission = method.getAnnotation(CheckPermission.class); + + String permissionNeed = checkPermission.value(); + check(auth, permissionNeed); + } + + private void check(Auth auth, String permissionNeed) throws UnAuthorizedException { + if (!auth.havePermission(permissionNeed)) { + String msg = String.format("[%s][%s] no permission to operate, need permission %s", + auth.getToken().getEmail(), auth.getToken().getName(), + permissionNeed); + throw new UnAuthorizedException(msg); + } + } + +} diff --git a/src/main/java/com/qaq/base/component/HttpUtil.java b/src/main/java/com/qaq/base/component/HttpUtil.java new file mode 100644 index 0000000..67aa3cc --- /dev/null +++ b/src/main/java/com/qaq/base/component/HttpUtil.java @@ -0,0 +1,82 @@ +package com.qaq.base.component; + +import org.springframework.core.ParameterizedTypeReference; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Component; +import org.springframework.web.client.RestTemplate; + +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +@Component +public class HttpUtil { + + final private RestTemplate restTemplate = new RestTemplate(); + + public T exchange(String url, HttpMethod method, B body, Class responseType) { + + var headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_JSON); + + HttpEntity requestEntity = new HttpEntity<>(body, headers); + + ResponseEntity resultEntity = restTemplate.exchange(url, method, requestEntity, responseType); + + return resultEntity.getBody(); + } + + + public T exchange(String url, HttpMethod method, B body, Class responseType, String authorization) { + + var headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_JSON); + headers.setBearerAuth(authorization); + + HttpEntity requestEntity = new HttpEntity<>(body, headers); + + ResponseEntity resultEntity = restTemplate.exchange(url, method, requestEntity, responseType); + + return resultEntity.getBody(); + } + + public T exchange(String url, HttpMethod method, B body, ParameterizedTypeReference responseType) { + var headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_JSON); + + HttpEntity requestEntity = new HttpEntity<>(body, headers); + + ResponseEntity resultEntity = restTemplate.exchange(url, method, requestEntity, responseType); + + return resultEntity.getBody(); + } + + public T exchange(String url, HttpMethod method, B body, HttpHeaders headers, ParameterizedTypeReference responseType) { + // 请求体 + if (headers == null) { + headers = new HttpHeaders(); + } + headers.setContentType(MediaType.APPLICATION_JSON); + // 发送请求 + HttpEntity entity = new HttpEntity<>(body, headers); + ResponseEntity resultEntity = restTemplate.exchange(url, method, entity, responseType); + return resultEntity.getBody(); + } + + public T exchange(String url, HttpMethod method, B body, ParameterizedTypeReference responseType, String authorization) { + + var headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_JSON); + headers.setBearerAuth(authorization); + + HttpEntity requestEntity = new HttpEntity<>(body, headers); + + ResponseEntity resultEntity = restTemplate.exchange(url, method, requestEntity, responseType); + + return resultEntity.getBody(); + } + +} diff --git a/src/main/java/com/qaq/base/component/JWTGeneratorComponent.java b/src/main/java/com/qaq/base/component/JWTGeneratorComponent.java new file mode 100644 index 0000000..93ba48b --- /dev/null +++ b/src/main/java/com/qaq/base/component/JWTGeneratorComponent.java @@ -0,0 +1,105 @@ +package com.qaq.base.component; + +import java.security.interfaces.RSAPrivateKey; +import java.util.Calendar; +import java.util.Date; +import java.util.GregorianCalendar; +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +import org.springframework.stereotype.Component; + +import com.auth0.jwt.JWT; +import com.auth0.jwt.algorithms.Algorithm; +import com.auth0.jwt.exceptions.JWTCreationException; +import com.lark.oapi.service.authen.v1.model.GetUserInfoRespBody; + +import lombok.extern.slf4j.Slf4j; + +/** + * 生成jwt token, 包含name、avatar以及email字段, exp字段由tokenExpTimeInMinute控制 + */ +@Slf4j +@Component +public class JWTGeneratorComponent { + + private Integer tokenExpTimeInDay; + private RSAPrivateKey privateKey; + + public JWTGeneratorComponent(RSAPrivateKey privateKey, Integer tokenExpTimeInDay) { + this.privateKey = privateKey; + this.tokenExpTimeInDay = tokenExpTimeInDay; + } + + public String generate(GetUserInfoRespBody user) { + String salt = UUID.randomUUID().toString().trim().replaceAll("-", ""); + String token = ""; + Map header = new HashMap<>(); + header.put("alg", "RS256"); + header.put("typ", "JWT"); + try { + // 只用签名,提供privateKey即可 + Algorithm algorithm = Algorithm.RSA256(null, this.privateKey); + Date exp = getExpDate(); + user.getUserId(); + token = JWT.create() + .withExpiresAt(exp) + .withClaim("name", user.getName()) + .withClaim("avatar", user.getAvatarUrl().trim()) + .withClaim("email", user.getEmail() == null ? "" : user.getEmail()) + .withClaim("userid", user.getUserId() == null ? "" : user.getUserId()) + .withClaim("openid", user.getOpenId().trim()) + .withClaim("salt", salt) + .withHeader(header) + .sign(algorithm); + } catch (JWTCreationException e) { + e.printStackTrace(); + } + if (token.isBlank()) { + log.error("the jwt token is null, user: {} ", user); + throw new RuntimeException("JWT Token错误"); + } + return token; + } + + public String generate(com.lark.oapi.service.contact.v3.model.User user) { + String salt = UUID.randomUUID().toString().trim().replaceAll("-", ""); + String token = ""; + Map header = new HashMap<>(); + header.put("alg", "RS256"); + header.put("typ", "JWT"); + try { + // 只用签名,提供privateKey即可 + Algorithm algorithm = Algorithm.RSA256(null, this.privateKey); + Date exp = getExpDate(); + token = JWT.create() + .withExpiresAt(exp) + .withClaim("name", user.getName()) + .withClaim("avatar", user.getAvatar().getAvatar72().trim()) + .withClaim("email", user.getEmail()) + .withClaim("userid", user.getUserId().trim()) + .withClaim("openid", user.getOpenId().trim()) + .withClaim("salt", salt) + .withHeader(header) + .sign(algorithm); + } catch (JWTCreationException e) { + e.printStackTrace(); + } + if (token.isBlank()) { + log.error("the jwt token is null, user: {} ", user); + throw new RuntimeException("JWT Token错误"); + } + return token; + } + + private Date getExpDate() { + Date exp = new Date(); + Calendar calendar = new GregorianCalendar(); + calendar.setTime(exp); + calendar.add(Calendar.DATE, tokenExpTimeInDay); + exp = calendar.getTime(); + return exp; + } + +} diff --git a/src/main/java/com/qaq/base/component/JWTVerifierComponent.java b/src/main/java/com/qaq/base/component/JWTVerifierComponent.java new file mode 100644 index 0000000..2843f25 --- /dev/null +++ b/src/main/java/com/qaq/base/component/JWTVerifierComponent.java @@ -0,0 +1,49 @@ +package com.qaq.base.component; + +import com.auth0.jwt.JWT; +import com.auth0.jwt.JWTVerifier; +import com.auth0.jwt.algorithms.Algorithm; +import com.auth0.jwt.interfaces.Claim; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import org.springframework.stereotype.Component; + +import java.security.KeyFactory; +import java.security.NoSuchAlgorithmException; +import java.security.PublicKey; +import java.security.interfaces.RSAPublicKey; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.X509EncodedKeySpec; +import java.util.Base64; +import java.util.Map; + +@Component +public class JWTVerifierComponent { + + private final JWTVerifier jwtVerifier; + + public JWTVerifierComponent(@NotBlank @NotNull String publicKeyStr) throws NoSuchAlgorithmException, InvalidKeySpecException { + + PublicKey publicKey = getPublicKeyFromString(publicKeyStr); + Algorithm algorithm = Algorithm.RSA256((RSAPublicKey) publicKey, null); + + this.jwtVerifier = JWT.require(algorithm).withClaimPresence("email").build(); + } + + private PublicKey getPublicKeyFromString(String publicKeyStr) throws NoSuchAlgorithmException, InvalidKeySpecException { + // Remove the header and footer and newlines + String publicKeyPEM = publicKeyStr + .replace("-----BEGIN PUBLIC KEY-----", "") + .replace("-----END PUBLIC KEY-----", "") + .replaceAll("\\s", ""); + + byte[] decodedKey = Base64.getDecoder().decode(publicKeyPEM); + X509EncodedKeySpec keySpec = new X509EncodedKeySpec(decodedKey); + return KeyFactory.getInstance("RSA").generatePublic(keySpec); + } + + public Map validToken(String jwtToken) { + var jwt = jwtVerifier.verify(jwtToken); + return jwt.getClaims(); + } +} diff --git a/src/main/java/com/qaq/base/component/NotifyUtil.java b/src/main/java/com/qaq/base/component/NotifyUtil.java new file mode 100644 index 0000000..4cc042a --- /dev/null +++ b/src/main/java/com/qaq/base/component/NotifyUtil.java @@ -0,0 +1,100 @@ +package com.qaq.base.component; + +import java.util.List; +import java.util.Map; + +import org.springframework.core.ParameterizedTypeReference; +import org.springframework.http.HttpMethod; + +import com.lark.oapi.service.contact.v3.model.User; +import com.qaq.base.model.Message; +import com.qaq.base.response.ApiResponse; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotEmpty; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public class NotifyUtil { + + private com.qaq.base.component.HttpUtil httpUtil; + private String messageUrl; + private String notifyPackageUrl; + private String userMapUrl; + private String userUrl; + + public NotifyUtil(com.qaq.base.component.HttpUtil httpUtil, String messageUrl, String notifyPackageUrl, String userMapUrl, + String userUrl) { + this.httpUtil = httpUtil; + this.messageUrl = messageUrl; + this.notifyPackageUrl = notifyPackageUrl; + this.userMapUrl = userMapUrl; + this.userUrl = userUrl; + } + + public ApiResponse> send(Message message) { + var responseType = new ParameterizedTypeReference>>() { + }; + var res = httpUtil.exchange(messageUrl, HttpMethod.POST, message, responseType); + return res; + } + + public ApiResponse> notifyLibai(String content) { + + return this.send(Message.builder() + .content(content) + .msg_type("post") + .touser(List.of("lifajin@pandadagames.com")) + .build()); + } + + public ApiResponse> getUserMap() { + var responseType = new ParameterizedTypeReference>>() { + }; + return httpUtil.exchange(userMapUrl, HttpMethod.GET, null, responseType); + + } + + public ApiResponse getUser(String userId) { + var responseType = new ParameterizedTypeReference>() { + }; + return httpUtil.exchange(String.format(userUrl, userId), HttpMethod.GET, null, responseType); + } + + public void sendMessageToNotify(List toUsers, String downUrl, String desc, String title) { + + var packageMessage = PackageMessage.builder() + .url(downUrl) + .description(desc) + .title(title) + .touser(toUsers) + .build(); + var response = httpUtil.exchange(notifyPackageUrl, HttpMethod.POST, packageMessage, ApiResponse.class); + if (response.getCode() == 0) { + log.debug("发送消息成功: {}", response); + } else { + log.error("发送消息失败: {}", response); + } + } + + @Builder + @AllArgsConstructor + @NoArgsConstructor + @Data + public static class PackageMessage { + + @NotBlank + private String url; + @NotBlank + private String description; + @NotBlank + private String title; + private String fromuser; + @NotEmpty + private List touser; + } +} diff --git a/src/main/java/com/qaq/base/config/JWTGeneratorConfig.java b/src/main/java/com/qaq/base/config/JWTGeneratorConfig.java new file mode 100644 index 0000000..e634fd4 --- /dev/null +++ b/src/main/java/com/qaq/base/config/JWTGeneratorConfig.java @@ -0,0 +1,61 @@ +package com.qaq.base.config; + +import java.io.IOException; +import java.io.StringReader; +import java.security.KeyFactory; +import java.security.NoSuchAlgorithmException; +import java.security.Security; +import java.security.interfaces.RSAPrivateKey; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.PKCS8EncodedKeySpec; + +import org.bouncycastle.jce.provider.BouncyCastleProvider; +import org.bouncycastle.util.io.pem.PemReader; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import com.qaq.base.component.JWTGeneratorComponent; + +import lombok.Getter; +import lombok.Setter; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@Configuration +@ConfigurationProperties(prefix = "rsa") +public class JWTGeneratorConfig { + + @Setter + @Getter + private String privateKey; + + @Setter + @Getter + private Integer tokenExpTimeInDay = 10; + + // https://stackoverflow.com/questions/39311157/only-rsaprivate-crt-keyspec-and-pkcs8encodedkeyspec-supported-for-rsa-private + @Bean + public JWTGeneratorComponent jwtGeneratorComponent() { + Security.addProvider(new BouncyCastleProvider()); + try ( PemReader pemReader = new PemReader(new StringReader(privateKey))){ + byte[] keyBytes = pemReader.readPemObject().getContent(); + PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(keyBytes); + KeyFactory kf = KeyFactory.getInstance("RSA"); + return new JWTGeneratorComponent((RSAPrivateKey) kf.generatePrivate(spec), tokenExpTimeInDay); + } catch (IOException e) { + log.error("read private key file failed, privateKeyStr: {}", privateKey); + e.printStackTrace(); + }catch (NoSuchAlgorithmException e) { + log.error("can not create the specified KeyFactory, check the algorithm !!!"); + e.printStackTrace(); + } catch (InvalidKeySpecException e) { + e.printStackTrace(); + log.error("the key is not valid, key file path: {}", privateKey); + } + //不应该走到这里 + System.exit(-1); + return null; + + } +} diff --git a/src/main/java/com/qaq/base/config/JWTVerifierConfig.java b/src/main/java/com/qaq/base/config/JWTVerifierConfig.java new file mode 100644 index 0000000..454efd1 --- /dev/null +++ b/src/main/java/com/qaq/base/config/JWTVerifierConfig.java @@ -0,0 +1,29 @@ +package com.qaq.base.config; + +import java.security.NoSuchAlgorithmException; +import java.security.spec.InvalidKeySpecException; + +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import com.qaq.base.component.JWTVerifierComponent; + +import lombok.Getter; +import lombok.Setter; +import lombok.extern.slf4j.Slf4j; + +@Setter +@Getter +@Slf4j +@Configuration +@ConfigurationProperties(prefix = "rsa") +public class JWTVerifierConfig { + + private String publicKey; + + @Bean + public JWTVerifierComponent jwtVerifierComponent() throws NoSuchAlgorithmException, InvalidKeySpecException { + return new JWTVerifierComponent(publicKey); + } +} diff --git a/src/main/java/com/qaq/base/config/LarkConfig.java b/src/main/java/com/qaq/base/config/LarkConfig.java new file mode 100644 index 0000000..aca9fa2 --- /dev/null +++ b/src/main/java/com/qaq/base/config/LarkConfig.java @@ -0,0 +1,38 @@ +package com.qaq.base.config; + +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import com.lark.oapi.Client; + +import lombok.Getter; +import lombok.Setter; +import lombok.extern.slf4j.Slf4j; + + +@Slf4j +@Configuration +@ConfigurationProperties(prefix = "feishu") +public class LarkConfig { + + @Setter + @Getter + private String appId; + + @Setter + @Getter + private String appSecret; + + @Qualifier("LarkClient") + @Bean + public Client client() { + log.debug("id: " + this.appId); + log.debug("secret: " + this.appSecret); + var client = Client.newBuilder(this.appId.trim(), this.appSecret.trim()).logReqAtDebug(true).build(); + log.debug("build success"); + return client; + } + +} diff --git a/src/main/java/com/qaq/base/config/LogginConfig.java b/src/main/java/com/qaq/base/config/LogginConfig.java new file mode 100644 index 0000000..09f07ac --- /dev/null +++ b/src/main/java/com/qaq/base/config/LogginConfig.java @@ -0,0 +1,15 @@ +package com.qaq.base.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import com.qaq.base.filter.LoggingFilter; + +@Configuration +public class LogginConfig { + + @Bean + public LoggingFilter LoggingFilter() { + return new LoggingFilter(); + } +} diff --git a/src/main/java/com/qaq/base/config/NotifyConfig.java b/src/main/java/com/qaq/base/config/NotifyConfig.java new file mode 100644 index 0000000..81d09e0 --- /dev/null +++ b/src/main/java/com/qaq/base/config/NotifyConfig.java @@ -0,0 +1,42 @@ +package com.qaq.base.config; + +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import com.qaq.base.component.HttpUtil; +import com.qaq.base.component.NotifyUtil; + +import lombok.Getter; +import lombok.Setter; + +@Configuration +@ConfigurationProperties(prefix = "qaq.notify") +public class NotifyConfig { + + @Setter + @Getter + private String url = "http://notify"; + + @Setter + @Getter + private String message = "http://notify/messages"; + + @Setter + @Getter + private String notifyPackage = "http://notify/messages/packages"; + + @Setter + @Getter + private String userMap = "http://notify/organization/userMap"; + + @Setter + @Getter + private String user = "http://notify/organization/get/userinfo?userid=%s"; + + @Bean + public NotifyUtil notifyUtil(HttpUtil httpUtil) { + return new NotifyUtil(httpUtil, message, notifyPackage, userMap, user); + } + +} diff --git a/src/main/java/com/qaq/base/config/RestTemplateConfig.java b/src/main/java/com/qaq/base/config/RestTemplateConfig.java new file mode 100644 index 0000000..22ba1e5 --- /dev/null +++ b/src/main/java/com/qaq/base/config/RestTemplateConfig.java @@ -0,0 +1,48 @@ +package com.qaq.base.config; + + +import java.io.IOException; + +import org.springframework.boot.web.client.RestTemplateBuilder; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.HttpRequest; +import org.springframework.http.client.ClientHttpRequestExecution; +import org.springframework.http.client.ClientHttpRequestInterceptor; +import org.springframework.http.client.ClientHttpResponse; +import org.springframework.lang.NonNull; +import org.springframework.web.client.RestTemplate; + +import com.qaq.base.component.HttpUtil; + +import lombok.extern.slf4j.Slf4j; + +@Configuration +public class RestTemplateConfig { + + @Bean + public RestTemplate restTemplate(RestTemplateBuilder builder) { + return builder + .interceptors(new CustomClientHttpRequestInterceptor()) + .build(); + } + + @Bean + public HttpUtil httpUtil() { + return new HttpUtil(); + } + + @Slf4j + static class CustomClientHttpRequestInterceptor implements ClientHttpRequestInterceptor { + @Override + @NonNull + public ClientHttpResponse intercept(HttpRequest request, @NonNull byte[] bytes, @NonNull ClientHttpRequestExecution execution) throws IOException { + + ClientHttpResponse response = execution.execute(request, bytes); + log.info("{} {} {}",response.getStatusCode(), request.getMethod(), request.getURI()); + + return response; + } + } + +} \ No newline at end of file diff --git a/src/main/java/com/qaq/base/config/WebConfig.java b/src/main/java/com/qaq/base/config/WebConfig.java new file mode 100644 index 0000000..a5dcf84 --- /dev/null +++ b/src/main/java/com/qaq/base/config/WebConfig.java @@ -0,0 +1,16 @@ +package com.qaq.base.config; + +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.config.annotation.InterceptorRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +import com.qaq.base.intercepter.AuthInterceptor; + +@Configuration +public class WebConfig implements WebMvcConfigurer { + + @Override + public void addInterceptors(InterceptorRegistry registry) { + registry.addInterceptor(new AuthInterceptor()); + } +} diff --git a/src/main/java/com/qaq/base/enums/GatewayHeaderEnum.java b/src/main/java/com/qaq/base/enums/GatewayHeaderEnum.java new file mode 100644 index 0000000..83ec340 --- /dev/null +++ b/src/main/java/com/qaq/base/enums/GatewayHeaderEnum.java @@ -0,0 +1,21 @@ +package com.qaq.base.enums; + +import lombok.Getter; + +@Getter +public enum GatewayHeaderEnum { + X_Gateway_UserId("X-Gateway-UserId", "X-Gateway-UserId"), + X_Gateway_UserIdType("X-Gateway-UserIdType", "X-Gateway-UserIdType"), + X_Gateway_Token("X-Gateway-Token", "X-Gateway-Token"), + X_Gateway_Permission("X-Gateway-Permission", "X-Gateway-Permission"); + + private final String headerName; + private final String claimName; + + private GatewayHeaderEnum(String headerName, String claimName) { + this.headerName = headerName; + this.claimName = claimName; + } + +} + diff --git a/src/main/java/com/qaq/base/exception/UnAuthorizedException.java b/src/main/java/com/qaq/base/exception/UnAuthorizedException.java new file mode 100644 index 0000000..d51ddd8 --- /dev/null +++ b/src/main/java/com/qaq/base/exception/UnAuthorizedException.java @@ -0,0 +1,7 @@ +package com.qaq.base.exception; + +public class UnAuthorizedException extends RuntimeException { + public UnAuthorizedException(String message) { + super(message); + } +} diff --git a/src/main/java/com/qaq/base/filter/LoggingFilter.java b/src/main/java/com/qaq/base/filter/LoggingFilter.java new file mode 100644 index 0000000..f6f5a05 --- /dev/null +++ b/src/main/java/com/qaq/base/filter/LoggingFilter.java @@ -0,0 +1,43 @@ +package com.qaq.base.filter; + +import java.io.IOException; + +import org.springframework.core.Ordered; + +import jakarta.servlet.Filter; +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.ServletRequest; +import jakarta.servlet.ServletResponse; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public class LoggingFilter implements Filter, Ordered { + + @Override + public int getOrder() { + return 0; + } + + @Override + public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { + long startTime = System.currentTimeMillis(); + HttpServletRequest request = (HttpServletRequest) servletRequest; + HttpServletResponse response = (HttpServletResponse) servletResponse; + + String method = request.getMethod(); + String path = request.getRequestURI(); + String queryParams = request.getQueryString(); + + filterChain.doFilter(request, response); + Long endTime = System.currentTimeMillis(); + Long duration = endTime - startTime; + if (queryParams != null) { + log.info("{} {} {}?{} {}", response.getStatus(), method, path, queryParams, duration); + } else { + log.info("{} {} {} {}", response.getStatus(), method, path, duration); + } + } +} diff --git a/src/main/java/com/qaq/base/intercepter/AuthInterceptor.java b/src/main/java/com/qaq/base/intercepter/AuthInterceptor.java new file mode 100644 index 0000000..dcfc351 --- /dev/null +++ b/src/main/java/com/qaq/base/intercepter/AuthInterceptor.java @@ -0,0 +1,67 @@ +package com.qaq.base.intercepter; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.qaq.base.enums.GatewayHeaderEnum; +import com.qaq.base.exception.UnAuthorizedException; +import com.qaq.base.model.Auth; +import com.qaq.base.model.login.Token; +import com.qaq.base.model.uniauth.AuthResult; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import lombok.NonNull; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.servlet.HandlerInterceptor; + +import java.util.Base64; + +@Slf4j +public class AuthInterceptor implements HandlerInterceptor { + + private static final Base64.Decoder BASE64_URL_DECODER = Base64.getUrlDecoder(); + private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); + + @Override + public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) { + + var tokenStr = request.getHeader(GatewayHeaderEnum.X_Gateway_Token.getHeaderName()); + var permissionStr = request.getHeader(GatewayHeaderEnum.X_Gateway_Permission.getHeaderName()); + if (tokenStr == null || tokenStr.trim().isEmpty()) { + log.error("Token header is missing or empty"); + throw new UnAuthorizedException("The auth token from gateway header is missing"); + } + var auth = createAuth(tokenStr, permissionStr); + request.setAttribute("auth", auth); + return true; + } + + private Auth createAuth(@NonNull String tokenStr, String permissionStr) { + Auth auth = new Auth(); + auth.setToken(parseToken(tokenStr)); + if (permissionStr != null && !permissionStr.trim().isEmpty()) { + auth.setAuthResult(parseAuthResult(permissionStr)); + } else { + log.info("AuthResult 可以为空"); + } + return auth; + } + + private Token parseToken(String tokenStr) { + try { + String decodedToken = new String(BASE64_URL_DECODER.decode(tokenStr)); + return OBJECT_MAPPER.readValue(decodedToken, Token.class); + } catch (Exception e) { + log.error("Failed to decode and parse token from header. tokenStr: {}", tokenStr, e); + throw new UnAuthorizedException("Failed to decode the auth token from gateway header"); + } + } + + private AuthResult parseAuthResult(String permissionStr) { + try { + String decodedPermission = new String(BASE64_URL_DECODER.decode(permissionStr)); + return OBJECT_MAPPER.readValue(decodedPermission, AuthResult.class); + } catch (Exception e) { + log.error("Failed to decode and parse permission from header. permissionStr: {} ", permissionStr, e); + throw new UnAuthorizedException("Failed to decode the auth permission from gateway header"); + } + } +} diff --git a/src/main/java/com/qaq/base/model/Auth.java b/src/main/java/com/qaq/base/model/Auth.java new file mode 100644 index 0000000..9bea2df --- /dev/null +++ b/src/main/java/com/qaq/base/model/Auth.java @@ -0,0 +1,22 @@ +package com.qaq.base.model; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.qaq.base.model.login.Token; +import com.qaq.base.model.uniauth.AuthResult; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@JsonIgnoreProperties(ignoreUnknown = true) +@AllArgsConstructor +@NoArgsConstructor +@Data +public class Auth { + + private Token token; + private AuthResult authResult; + + public boolean havePermission(String permission) { + return authResult.getPermissions().contains(permission); + } +} diff --git a/src/main/java/com/qaq/base/model/Message.java b/src/main/java/com/qaq/base/model/Message.java new file mode 100644 index 0000000..dd62bd4 --- /dev/null +++ b/src/main/java/com/qaq/base/model/Message.java @@ -0,0 +1,20 @@ +package com.qaq.base.model; + +import java.util.List; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Builder +@AllArgsConstructor +@NoArgsConstructor +@Data +public class Message { + + public String msg_type; + public String content; + public List touser; + +} diff --git a/src/main/java/com/qaq/base/model/login/Token.java b/src/main/java/com/qaq/base/model/login/Token.java new file mode 100644 index 0000000..5d3dce0 --- /dev/null +++ b/src/main/java/com/qaq/base/model/login/Token.java @@ -0,0 +1,23 @@ +package com.qaq.base.model.login; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@JsonIgnoreProperties(ignoreUnknown = true) +@Builder +@AllArgsConstructor +@NoArgsConstructor +@Data +public class Token { + + private String name; + private String email; + private String userid; + private String openid; + private String avatar; + private String salt; + private long exp; +} \ No newline at end of file diff --git a/src/main/java/com/qaq/base/model/uniauth/AuthResult.java b/src/main/java/com/qaq/base/model/uniauth/AuthResult.java new file mode 100644 index 0000000..7f34990 --- /dev/null +++ b/src/main/java/com/qaq/base/model/uniauth/AuthResult.java @@ -0,0 +1,21 @@ +package com.qaq.base.model.uniauth; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.List; +import java.util.Map; +import java.util.Set; + +@JsonIgnoreProperties(ignoreUnknown = true) +@Builder +@AllArgsConstructor +@NoArgsConstructor +@Data +public class AuthResult { + private List permissions; + private String defaultProjectId; +} diff --git a/src/main/java/com/qaq/base/model/uniauth/AuthResultResp.java b/src/main/java/com/qaq/base/model/uniauth/AuthResultResp.java new file mode 100644 index 0000000..cae840e --- /dev/null +++ b/src/main/java/com/qaq/base/model/uniauth/AuthResultResp.java @@ -0,0 +1,6 @@ +package com.qaq.base.model.uniauth; + +import com.qaq.base.response.ApiResponse; + +public class AuthResultResp extends ApiResponse { +} diff --git a/src/main/java/com/qaq/base/response/ApiResponse.java b/src/main/java/com/qaq/base/response/ApiResponse.java new file mode 100644 index 0000000..c41e48a --- /dev/null +++ b/src/main/java/com/qaq/base/response/ApiResponse.java @@ -0,0 +1,29 @@ +package com.qaq.base.response; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Builder +@AllArgsConstructor +@NoArgsConstructor +@Data +public class ApiResponse { + private int code; + private T data; + private String message; + + public ApiResponse(T data, String message) { + this.code = 0; + this.data = data; + this.message = message; + } + + public ApiResponse(T data) { + this.code = 0; + this.data = data; + this.message = ""; + } + +} diff --git a/src/main/java/com/qaq/base/response/PageData.java b/src/main/java/com/qaq/base/response/PageData.java new file mode 100644 index 0000000..537a6e4 --- /dev/null +++ b/src/main/java/com/qaq/base/response/PageData.java @@ -0,0 +1,23 @@ +package com.qaq.base.response; + +import java.util.List; + +import com.fasterxml.jackson.annotation.JsonProperty; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Builder +@AllArgsConstructor +@NoArgsConstructor +@Data +public class PageData { + + int page; + @JsonProperty("page_size") + int pageSize; + long total; + List data; +} diff --git a/src/main/java/com/qaq/base/utils/DateUtils.java b/src/main/java/com/qaq/base/utils/DateUtils.java new file mode 100644 index 0000000..438e87e --- /dev/null +++ b/src/main/java/com/qaq/base/utils/DateUtils.java @@ -0,0 +1,66 @@ +package com.qaq.base.utils; + +import java.text.SimpleDateFormat; +import java.util.Calendar; +import java.util.Date; + +public class DateUtils { + + public static String FORMAT_PERIOD = "yyyyMMdd"; + public static String FORMAT_YMD = "yyyy-MM-dd"; + public static String FORMAT_YMDHMS = "yyyy-MM-dd HH:mm:ss"; + + public static String getStringDate(Date d, String format) { + SimpleDateFormat sd = new SimpleDateFormat(format); + return sd.format(d); + } + + /** + * 获取今天是星期几 + * 1, 2,... 7对应星期一,星期二,...星期日 + * + * @return + */ + public static Integer getWeekDay(Date today) { + Calendar c = Calendar.getInstance(); + c.setTime(today); + int weekday = c.get(Calendar.DAY_OF_WEEK) - 1; + return weekday == 0 ? 7 : weekday; + } + + /** + * 获取指定日期前后几天的日期 + * + * @param d 指定日期 + * @param day 前后天数间隔(正数为向后,负数为向前) + * @return 间隔后日期 + */ + public static Date getDateByDelta(Date d, int day) { + Calendar now = Calendar.getInstance(); + now.setTime(d); + now.set(Calendar.DATE, now.get(Calendar.DATE) + day); + return now.getTime(); + } + + /** + * 把某个日期转换为当周周几的日期,如2019/4/9转为当周周四的日期为2019/4/11 + * + * @param d + * @param weekday + * @return + */ + public static Date convertByWeekDay(Date d, int weekday) { + int minusDays = weekday - getWeekDay(d); + return getDateByDelta(d, minusDays); + } + + public static boolean isDateEquals(Date date1, Date date2, String format) { + SimpleDateFormat fmt = new SimpleDateFormat(format); + return fmt.format(date1).equals(fmt.format(date2)); + } + + public static String getPeriodDateSuffix() { + SimpleDateFormat fmt = new SimpleDateFormat(FORMAT_PERIOD); + return "-" + fmt.format(new Date()) + "周版本"; + } +} diff --git a/src/main/java/com/qaq/base/utils/HttpContextUtils.java b/src/main/java/com/qaq/base/utils/HttpContextUtils.java new file mode 100644 index 0000000..7cabf6e --- /dev/null +++ b/src/main/java/com/qaq/base/utils/HttpContextUtils.java @@ -0,0 +1,55 @@ +package com.qaq.base.utils; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; + +import jakarta.servlet.ServletRequest; + +/** + * HttpContextUtils + * + * @author itdragons + */ +public class HttpContextUtils { + + // /** + // * 获取query参数 + // * + // * @param request + // * @return + // */ + // public static Map getParameterMapAll(HttpServletRequest + // request) { + // var parameters = request.getParameterNames(); + // var params = new HashMap(); + // while (parameters.hasMoreElements()) { + // String parameter = parameters.nextElement(); + // String value = request.getParameter(parameter); + // params.put(parameter, value); + // } + // return params; + // } + + /** + * 获取请求Body + * + * @param request + * @return + */ + // @Bean + public static String getBodyString(ServletRequest request) { + StringBuilder sb = new StringBuilder(); + try (var inputStream = request.getInputStream(); + var reader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8))) { + String line = ""; + while ((line = reader.readLine()) != null) { + sb.append(line); + } + } catch (IOException e) { + e.printStackTrace(); + } + return sb.toString(); + } +} diff --git a/src/main/java/com/qaq/base/utils/MutableHttpServletRequest.java b/src/main/java/com/qaq/base/utils/MutableHttpServletRequest.java new file mode 100644 index 0000000..827caf3 --- /dev/null +++ b/src/main/java/com/qaq/base/utils/MutableHttpServletRequest.java @@ -0,0 +1,51 @@ +package com.qaq.base.utils; + +import java.util.Collections; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; + +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletRequestWrapper; + +public class MutableHttpServletRequest extends HttpServletRequestWrapper { + + private final Map customHeaders; + + public MutableHttpServletRequest(HttpServletRequest request) { + super(request); + this.customHeaders = new HashMap<>(); + } + + public void putHeader(String name, String value) { + this.customHeaders.put(name, value); + } + + @Override + public String getHeader(String name) { + // 检查是否存在自定义头部,如果有,返回该头部值 + String headerValue = customHeaders.get(name); + + if (headerValue != null) { + return headerValue; + } + // 否则返回原始请求的头部值 + return ((HttpServletRequest) getRequest()).getHeader(name); + } + + @Override + public Enumeration getHeaderNames() { + // 创建一个集合合并原始请求的头部和自定义的头部 + HashSet set = new HashSet<>(customHeaders.keySet()); + + // 将原始的头部也添加到集合中 + Enumeration e = ((HttpServletRequest) getRequest()).getHeaderNames(); + while (e.hasMoreElements()) { + String n = e.nextElement(); + set.add(n); + } + // 返回集合的枚举 + return Collections.enumeration(set); + } +} \ No newline at end of file diff --git a/src/main/java/com/qaq/base/utils/ObjectUtils.java b/src/main/java/com/qaq/base/utils/ObjectUtils.java new file mode 100644 index 0000000..12ef7e6 --- /dev/null +++ b/src/main/java/com/qaq/base/utils/ObjectUtils.java @@ -0,0 +1,56 @@ +package com.qaq.base.utils; + +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.Objects; + +public class ObjectUtils { + + /** + * 获取对象指定字段的值 + * + * @param o + * @param field + * @return + */ + public static Object getFieldValue(Object o, Field field) { + String fieldName = field.getName(); + String firstLetter = fieldName.substring(0, 1).toUpperCase(); + String getter = "get" + firstLetter + fieldName.substring(1); + + try { + Method method = o.getClass().getMethod(getter, new Class[] {}); + Object value = method.invoke(o, new Object[] {}); + return value; + } catch (Exception e) { + return null; + } + } + + public static boolean setFieldValue(Object o, Field field, Object value) { + String fieldName = field.getName(); + String firstLetter = fieldName.substring(0, 1).toUpperCase(); + String setter = "set" + firstLetter + fieldName.substring(1); + + try { + Method method = o.getClass().getMethod(setter, field.getType()); + method.invoke(o, value); + return true; + } catch (Exception e) { + return false; + } + } + + public static Integer getChangedCount(T oldObject, T newObject) { + Integer changedCount = 0; + Field[] fields = newObject.getClass().getDeclaredFields(); + for (Field field : fields) { + Object newValue = getFieldValue(newObject, field); + Object oldValue = getFieldValue(oldObject, field); + if (!Objects.deepEquals(newValue, oldValue)) { + changedCount++; + } + } + return changedCount; + } +} diff --git a/src/main/java/com/qaq/base/utils/Util.java b/src/main/java/com/qaq/base/utils/Util.java new file mode 100644 index 0000000..5f59426 --- /dev/null +++ b/src/main/java/com/qaq/base/utils/Util.java @@ -0,0 +1,5 @@ +package com.qaq.base.utils; + +public class Util { + +}