diff --git a/admin4j-signature/pom.xml b/admin4j-signature/pom.xml
new file mode 100644
index 0000000..9e1bd5f
--- /dev/null
+++ b/admin4j-signature/pom.xml
@@ -0,0 +1,29 @@
+
+
+ 4.0.0
+
+ com.admin4j
+ framework
+ ${revision}
+
+
+ com.admin4j.signature
+ admin4j-signature
+ ${admin4j-signature.version}
+ pom
+
+
+ 0.8.0
+ 8
+ 8
+ UTF-8
+
+
+
+ signature-core
+ signature-spring-boot-starter
+
+
+
\ No newline at end of file
diff --git a/admin4j-signature/signature-core/pom.xml b/admin4j-signature/signature-core/pom.xml
new file mode 100644
index 0000000..92c9da6
--- /dev/null
+++ b/admin4j-signature/signature-core/pom.xml
@@ -0,0 +1,45 @@
+
+
+ 4.0.0
+
+ com.admin4j.signature
+ admin4j-signature
+ ${admin4j-signature.version}
+
+
+ signature-core
+
+
+ 8
+ 8
+ UTF-8
+
+
+
+
+ com.admin4j.common
+ admin4j-common
+
+
+ org.springframework
+ spring-webmvc
+ provided
+
+
+ javax.servlet
+ javax.servlet-api
+ provided
+
+
+ com.admin4j.common
+ admin4j-common-spring-web
+
+
+ org.springframework.boot
+ spring-boot-starter-data-redis
+
+
+
+
\ No newline at end of file
diff --git a/admin4j-signature/signature-core/src/main/java/com/admin4j/framework/signature/AbstractSignature.java b/admin4j-signature/signature-core/src/main/java/com/admin4j/framework/signature/AbstractSignature.java
new file mode 100644
index 0000000..d72e368
--- /dev/null
+++ b/admin4j-signature/signature-core/src/main/java/com/admin4j/framework/signature/AbstractSignature.java
@@ -0,0 +1,205 @@
+package com.admin4j.framework.signature;
+
+import com.admin4j.framework.signature.annotation.Signature;
+import com.admin4j.framework.signature.properties.SignatureProperties;
+import com.alibaba.fastjson2.JSONObject;
+import org.apache.commons.lang3.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.data.redis.core.StringRedisTemplate;
+import org.springframework.util.CollectionUtils;
+import org.springframework.util.DigestUtils;
+
+import javax.servlet.http.HttpServletRequest;
+import java.io.IOException;
+import java.util.Map;
+import java.util.SortedMap;
+import java.util.TreeMap;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * @author zhougang
+ * @since 2023/11/10 10:43
+ */
+public abstract class AbstractSignature implements SignatureService {
+
+ private static final Logger log = LoggerFactory.getLogger(AbstractSignature.class);
+
+ private final StringRedisTemplate stringRedisTemplate;
+
+ private final SignatureProperties signatureProperties;
+
+ private static final String SIGNATURE_NONCE_REDIS_KEY = "signature:nonce:";
+
+ public AbstractSignature(StringRedisTemplate stringRedisTemplate, SignatureProperties signatureProperties) {
+ this.stringRedisTemplate = stringRedisTemplate;
+ this.signatureProperties = signatureProperties;
+ }
+
+ /**
+ * 判断请求是否签名通过
+ *
+ * @param request HttpServletRequest
+ * @return 是否通过
+ */
+ @Override
+ public boolean verify(Signature signature, HttpServletRequest request) throws IOException {
+ // 根据appId获取appSecret
+ String appId = request.getHeader(signature.appId().filedName());
+ String appSecret;
+ if (StringUtils.isBlank(appId) ||
+ StringUtils.isBlank(appSecret = getAppSecret(request.getHeader(signature.appId().filedName())))) {
+ return false;
+ }
+ // 根据request 中 header值生成SignatureHeaders实体
+ if (!verifyHeaders(signature, request)) {
+ return false;
+ }
+ // 获取全部参数(包括URL和Body上的)
+ SortedMap allParams = getAllParams(signature, request);
+ // 生成服务端签名
+ String plainText = paramsSplicing(allParams, appSecret);
+ // 将digest 转换成UTF-8 的 byte[] 后 使用MD5算法加密,最后将生成的md5字符串
+ String serverSign = digestEncoder(plainText);
+ // 客户端签名
+ String clientSign = request.getHeader(signature.sign().filedName());
+ if (!StringUtils.equals(clientSign, serverSign)) {
+ return false;
+ }
+ String nonce = allParams.get(signature.nonce().filedName());
+ // 将 nonce 记入缓存,防止重复使用(重点二:此处需要将 ttl 设定为允许 timestamp 时间差的值 x 2 )
+ stringRedisTemplate.opsForValue().set(SIGNATURE_NONCE_REDIS_KEY + nonce, nonce, signatureProperties.getExpireTime() * 2, TimeUnit.MILLISECONDS);
+ return true;
+ }
+
+ /*
+ private SignatureHeaders getSignatureHeaders(Signature signature, HttpServletRequest request) {
+ SignatureHeaders signatureHeaders = new SignatureHeaders();
+ signatureHeaders.setAppId(request.getHeader(signature.appId().filedName()));
+ signatureHeaders.setAppId(request.getHeader(signature.timestamp().filedName()));
+ signatureHeaders.setAppId(request.getHeader(signature.nonce().filedName()));
+ signatureHeaders.setAppId(request.getHeader(signature.sign().filedName()));
+ return signatureHeaders;
+ }
+ */
+
+ /**
+ * 1.appId是否合法,appId是否有对应的appSecret。
+ * 2.请求是否已经超时,默认10分钟。
+ * 3.随机串是否合法,是否在指定时间内已经访问过了。
+ * 4.sign是否合法。
+ */
+ private boolean verifyHeaders(Signature signature, HttpServletRequest request) {
+
+ String timestamp = request.getHeader(signature.timestamp().filedName());
+ //Assert.notNull(timestamp, "timestamp cannot be empty");
+ if (StringUtils.isBlank(timestamp)) {
+ return false;
+ }
+
+ Long expireTime = signatureProperties.getExpireTime();
+ //其他合法性校验
+ long requestTimestamp = Long.parseLong(timestamp);
+ // 检查 timestamp 是否超出允许的范围 (重点一:此处需要取绝对值)
+ long timestampDisparity = Math.abs(System.currentTimeMillis() - requestTimestamp);
+ //Assert.isTrue(!(timestampDisparity > expireTime), "Request time exceeds the specified limit");
+ if (timestampDisparity > expireTime) {
+ return false;
+ }
+
+ String nonce = request.getHeader(signature.nonce().filedName());
+ //Assert.notNull(nonce, "Random strings cannot be empty");
+ if (StringUtils.isBlank(nonce)) {
+ return false;
+ }
+ //Assert.isTrue(!(nonce.length() < 10), "The random string nonce length is at least 10 bits");
+ if (nonce.length() < 10) {
+ return false;
+ }
+ String cacheNonce = stringRedisTemplate.opsForValue().get(SIGNATURE_NONCE_REDIS_KEY + nonce);
+ //Assert.isNull(cacheNonce, "This nonce has already been used and the request is invalid");
+ if (StringUtils.isNotBlank(cacheNonce)) {
+ return false;
+ }
+
+ String sign = request.getHeader(signature.sign().filedName());
+ //Assert.notNull(sign, "sign cannot be empty");
+ return StringUtils.isNotBlank(sign);
+ }
+
+ /**
+ * 获取全部参数(包括URL和Body上的)
+ *
+ * @param request request
+ * @return
+ */
+ protected SortedMap getAllParams(Signature signature, HttpServletRequest request) throws IOException {
+
+ SortedMap sortedMap = new TreeMap<>();
+
+ sortedMap.put(signature.appId().filedName(), request.getHeader(signature.appId().filedName()));
+ sortedMap.put(signature.timestamp().filedName(), request.getHeader(signature.timestamp().filedName()));
+ sortedMap.put(signature.nonce().filedName(), request.getHeader(signature.nonce().filedName()));
+ // 有url带动态参数的情况, 所以加上url, 客户端对应也要拼接
+ sortedMap.put("url", request.getServletPath());
+
+ // 获取parameters(对应@RequestParam)
+ if (!CollectionUtils.isEmpty(request.getParameterMap())) {
+ Map requestParams = request.getParameterMap();
+ //获取GET请求参数,以键值对形式保存
+ for (Map.Entry entry : requestParams.entrySet()) {
+ sortedMap.put(entry.getKey(), entry.getValue()[0]);
+ }
+ }
+
+ BodyReaderHttpServletRequestWrapper requestWrapper = new BodyReaderHttpServletRequestWrapper(request);
+ // 分别获取了request input stream中的body信息、parameter信息
+ JSONObject data = JSONObject.parseObject(requestWrapper.getBody());
+ // 获取POST请求的JSON参数,以键值对形式保存
+ for (Map.Entry entry : data.entrySet()) {
+ sortedMap.put(entry.getKey(), entry.getValue().toString());
+ }
+
+ return sortedMap;
+ }
+
+ /**
+ * 所有的参数与应用密钥appSecret 进行排序加密后生成签名
+ *
+ * @param sortedMap 根据key升序排序的后所有请求参数
+ * @param appSecret 应用id对应的应用密钥
+ * @return 生成接口签名
+ */
+ protected String paramsSplicing(SortedMap sortedMap, String appSecret) {
+ // 进行key value拼接
+ StringBuilder plainText = new StringBuilder();
+ for (Map.Entry entry : sortedMap.entrySet()) {
+ plainText.append(entry.getKey()).append(entry.getValue());
+ }
+
+ // 结尾拼接应用密钥 appSecret
+ plainText.append(appSecret);
+
+ // 摘要
+ return plainText.toString();
+ }
+
+ /**
+ * 获取appId对应的secret,假数据
+ *
+ * @param appId 应用id
+ * @return
+ */
+ protected String getAppSecret(String appId) {
+ return "";
+ }
+
+ /**
+ * 摘要加密
+ * @param plainText
+ * @return
+ */
+ protected String digestEncoder(String plainText) throws IOException {
+ return DigestUtils.md5DigestAsHex(StringUtils.getBytes(plainText, "UTF-8"));
+ }
+}
diff --git a/admin4j-signature/signature-core/src/main/java/com/admin4j/framework/signature/BodyReaderHttpServletRequestWrapper.java b/admin4j-signature/signature-core/src/main/java/com/admin4j/framework/signature/BodyReaderHttpServletRequestWrapper.java
new file mode 100644
index 0000000..2f9d999
--- /dev/null
+++ b/admin4j-signature/signature-core/src/main/java/com/admin4j/framework/signature/BodyReaderHttpServletRequestWrapper.java
@@ -0,0 +1,68 @@
+package com.admin4j.framework.signature;
+
+import javax.servlet.ReadListener;
+import javax.servlet.ServletInputStream;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletRequestWrapper;
+import java.io.*;
+
+public class BodyReaderHttpServletRequestWrapper extends HttpServletRequestWrapper {
+
+ private final String body;
+
+ public BodyReaderHttpServletRequestWrapper(HttpServletRequest request) throws IOException {
+ super(request);
+ StringBuilder stringBuilder = new StringBuilder();
+ BufferedReader bufferedReader = null;
+ try {
+ InputStream inputStream = request.getInputStream();
+ if (inputStream != null) {
+ bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
+ char[] charBuffer = new char[128];
+ int bytesRead;
+ while ((bytesRead = bufferedReader.read(charBuffer)) > 0) {
+ stringBuilder.append(charBuffer, 0, bytesRead);
+ }
+ }
+ } finally {
+ if (bufferedReader != null) {
+ bufferedReader.close();
+ }
+ }
+ body = stringBuilder.toString();
+ }
+
+ @Override
+ public ServletInputStream getInputStream() {
+ final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(body.getBytes());
+ return new ServletInputStream() {
+ @Override
+ public boolean isFinished() {
+ return false;
+ }
+
+ @Override
+ public boolean isReady() {
+ return false;
+ }
+
+ @Override
+ public void setReadListener(ReadListener readListener) {
+ }
+
+ @Override
+ public int read() {
+ return byteArrayInputStream.read();
+ }
+ };
+ }
+
+ @Override
+ public BufferedReader getReader() {
+ return new BufferedReader(new InputStreamReader(this.getInputStream()));
+ }
+
+ public String getBody() {
+ return this.body;
+ }
+}
diff --git a/admin4j-signature/signature-core/src/main/java/com/admin4j/framework/signature/DefaultSignature.java b/admin4j-signature/signature-core/src/main/java/com/admin4j/framework/signature/DefaultSignature.java
new file mode 100644
index 0000000..de9fe5f
--- /dev/null
+++ b/admin4j-signature/signature-core/src/main/java/com/admin4j/framework/signature/DefaultSignature.java
@@ -0,0 +1,17 @@
+package com.admin4j.framework.signature;
+
+import com.admin4j.framework.signature.properties.SignatureProperties;
+import org.springframework.data.redis.core.StringRedisTemplate;
+
+/**
+ * 签名的默认实现
+ *
+ * @author zhougang
+ * @since 2023/10/11 13:39
+ */
+public class DefaultSignature extends AbstractSignature {
+
+ public DefaultSignature(StringRedisTemplate stringRedisTemplate, SignatureProperties signatureProperties) {
+ super(stringRedisTemplate, signatureProperties);
+ }
+}
diff --git a/admin4j-signature/signature-core/src/main/java/com/admin4j/framework/signature/SignatureHeaders.java b/admin4j-signature/signature-core/src/main/java/com/admin4j/framework/signature/SignatureHeaders.java
new file mode 100644
index 0000000..1aba494
--- /dev/null
+++ b/admin4j-signature/signature-core/src/main/java/com/admin4j/framework/signature/SignatureHeaders.java
@@ -0,0 +1,34 @@
+package com.admin4j.framework.signature;
+
+import lombok.Data;
+
+@Data
+public class SignatureHeaders {
+
+ /**
+ * 线下分配的值
+ * 客户端和服务端各自保存appId对应的appSecret
+ */
+ private String appId;
+
+ /**
+ * 时间戳,单位: ms
+ */
+ private String timestamp;
+
+ /**
+ * 流水号【防止重复提交】; (备注:针对查询接口,流水号只用于日志落地,便于后期日志核查; 针对办理类接口需校验流水号在有效期内的唯一性,以避免重复请求)
+ * => 流水号/随机串:至少16位,有效期内防重复提交
+ */
+ private String nonce;
+
+ /**
+ * 签名
+ */
+ private String sign;
+
+ /**
+ * 根据appId从服务端获取
+ */
+ private String appSecret;
+}
diff --git a/admin4j-signature/signature-core/src/main/java/com/admin4j/framework/signature/SignatureService.java b/admin4j-signature/signature-core/src/main/java/com/admin4j/framework/signature/SignatureService.java
new file mode 100644
index 0000000..bf89934
--- /dev/null
+++ b/admin4j-signature/signature-core/src/main/java/com/admin4j/framework/signature/SignatureService.java
@@ -0,0 +1,20 @@
+package com.admin4j.framework.signature;
+
+import com.admin4j.framework.signature.annotation.Signature;
+
+import javax.servlet.http.HttpServletRequest;
+import java.io.IOException;
+
+/**
+ * @author zhougang
+ * @since 2023/11/10 9:42
+ */
+public interface SignatureService {
+
+ /**
+ * 验证签名是否通过
+ *
+ * @return 是否限速
+ */
+ boolean verify(Signature signature, HttpServletRequest request) throws IOException;
+}
diff --git a/admin4j-signature/signature-core/src/main/java/com/admin4j/framework/signature/annotation/Signature.java b/admin4j-signature/signature-core/src/main/java/com/admin4j/framework/signature/annotation/Signature.java
new file mode 100644
index 0000000..847ea2f
--- /dev/null
+++ b/admin4j-signature/signature-core/src/main/java/com/admin4j/framework/signature/annotation/Signature.java
@@ -0,0 +1,35 @@
+package com.admin4j.framework.signature.annotation;
+
+import com.admin4j.framework.signature.DefaultSignature;
+import com.admin4j.framework.signature.SignatureService;
+
+import java.lang.annotation.*;
+
+
+/**
+ * 用于标记接口签名
+ *
+ * @author zhougang
+ * @since 2022/3/24 16:34
+ */
+@Inherited
+@Documented
+@Target({ElementType.METHOD, ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+public @interface Signature {
+
+ Class extends SignatureService> signatureClass() default DefaultSignature.class;
+
+ /**
+ * 签名字段
+ *
+ * @return
+ */
+ SignatureField appId() default @SignatureField(filedName = "appId", order = 0);
+
+ SignatureField timestamp() default @SignatureField(filedName = "timestamp", order = 1);
+
+ SignatureField nonce() default @SignatureField(filedName = "nonce", order = 2);
+
+ SignatureField sign() default @SignatureField(filedName = "sign", order = 3);
+}
diff --git a/admin4j-signature/signature-core/src/main/java/com/admin4j/framework/signature/annotation/SignatureField.java b/admin4j-signature/signature-core/src/main/java/com/admin4j/framework/signature/annotation/SignatureField.java
new file mode 100644
index 0000000..8ea295e
--- /dev/null
+++ b/admin4j-signature/signature-core/src/main/java/com/admin4j/framework/signature/annotation/SignatureField.java
@@ -0,0 +1,27 @@
+package com.admin4j.framework.signature.annotation;
+
+import java.lang.annotation.*;
+
+/**
+ * 签名算法实现 => 指定哪些字段需要进行签名
+ */
+@Documented
+@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+public @interface SignatureField {
+
+ /**
+ * 字段:自定义name,对应前端传入的字段名
+ *
+ * @return
+ */
+ String filedName() default "";
+
+ /**
+ * 签名顺序
+ *
+ * @return
+ */
+ int order() default 0;
+
+}
diff --git a/admin4j-signature/signature-core/src/main/java/com/admin4j/framework/signature/exception/SignatureException.java b/admin4j-signature/signature-core/src/main/java/com/admin4j/framework/signature/exception/SignatureException.java
new file mode 100644
index 0000000..f59c030
--- /dev/null
+++ b/admin4j-signature/signature-core/src/main/java/com/admin4j/framework/signature/exception/SignatureException.java
@@ -0,0 +1,16 @@
+package com.admin4j.framework.signature.exception;
+
+/**
+ * @author zhougang
+ * @since 2023/11/10 17:05
+ */
+public class SignatureException extends RuntimeException {
+
+ public SignatureException(String message) {
+ super(message);
+ }
+
+ public SignatureException(String message, Throwable cause) {
+ super(message, cause);
+ }
+}
diff --git a/admin4j-signature/signature-core/src/main/java/com/admin4j/framework/signature/filter/RequestReplaceFilter.java b/admin4j-signature/signature-core/src/main/java/com/admin4j/framework/signature/filter/RequestReplaceFilter.java
new file mode 100644
index 0000000..890d064
--- /dev/null
+++ b/admin4j-signature/signature-core/src/main/java/com/admin4j/framework/signature/filter/RequestReplaceFilter.java
@@ -0,0 +1,28 @@
+package com.admin4j.framework.signature.filter;
+
+import com.admin4j.framework.signature.BodyReaderHttpServletRequestWrapper;
+import org.springframework.web.filter.OncePerRequestFilter;
+
+import javax.servlet.FilterChain;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequestWrapper;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+
+/**
+ * 包装HttpServletRequest对象
+ * @author zhougang
+ * @since 2023/11/12 10:23
+ */
+public class RequestReplaceFilter extends OncePerRequestFilter {
+
+ @Override
+ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
+ if (!(request instanceof ServletRequestWrapper)) {
+ // 包装HttpServletRequest对象,缓存body数据,再次读取的时候将缓存的值写出,解决HttpServetRequest读取body只能一次的问题
+ request = new BodyReaderHttpServletRequestWrapper(request);
+ }
+ filterChain.doFilter(request, response);
+ }
+}
diff --git a/admin4j-signature/signature-core/src/main/java/com/admin4j/framework/signature/interceptor/SignatureInterceptor.java b/admin4j-signature/signature-core/src/main/java/com/admin4j/framework/signature/interceptor/SignatureInterceptor.java
new file mode 100644
index 0000000..6283ed4
--- /dev/null
+++ b/admin4j-signature/signature-core/src/main/java/com/admin4j/framework/signature/interceptor/SignatureInterceptor.java
@@ -0,0 +1,54 @@
+package com.admin4j.framework.signature.interceptor;
+
+import com.admin4j.framework.signature.SignatureService;
+import com.admin4j.framework.signature.annotation.Signature;
+import com.admin4j.framework.signature.exception.SignatureException;
+import com.admin4j.spring.util.SpringUtils;
+import org.springframework.beans.BeansException;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.ApplicationContextAware;
+import org.springframework.web.method.HandlerMethod;
+import org.springframework.web.servlet.HandlerInterceptor;
+
+/**
+ * @author zhougang
+ * @since 2023/11/10 9:51
+ */
+public class SignatureInterceptor implements HandlerInterceptor, ApplicationContextAware {
+
+ private ApplicationContext applicationContext;
+
+ public SignatureInterceptor() {
+
+ }
+
+ @Override
+ public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
+ this.applicationContext = applicationContext;
+ }
+
+ @Override
+ public boolean preHandle(javax.servlet.http.HttpServletRequest request, javax.servlet.http.HttpServletResponse response, Object handler) throws Exception {
+ if (!(handler instanceof HandlerMethod)) {
+ return true;
+ }
+ HandlerMethod handlerMethod = (HandlerMethod) handler;
+
+ Signature signature = handlerMethod.getMethodAnnotation(Signature.class);
+
+ if (signature == null) {
+ Class> beanType = handlerMethod.getBeanType();
+ Signature annotation = beanType.getAnnotation(Signature.class);
+ if (annotation == null) {
+ return true;
+ }
+ signature = annotation;
+ }
+
+ SignatureService signatureService = SpringUtils.getBean(signature.signatureClass());
+ if (!signatureService.verify(signature, request)) {
+ throw new SignatureException("Signature failure");
+ }
+ return true;
+ }
+}
diff --git a/admin4j-signature/signature-core/src/main/java/com/admin4j/framework/signature/properties/SignatureProperties.java b/admin4j-signature/signature-core/src/main/java/com/admin4j/framework/signature/properties/SignatureProperties.java
new file mode 100644
index 0000000..31030e2
--- /dev/null
+++ b/admin4j-signature/signature-core/src/main/java/com/admin4j/framework/signature/properties/SignatureProperties.java
@@ -0,0 +1,25 @@
+package com.admin4j.framework.signature.properties;
+
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+
+/**
+ * Actuator 监控授权配置
+ *
+ * @author zhougang
+ * @since 2023/11/12 10:57
+ */
+@Data
+@ConfigurationProperties(prefix = "admin4j.signature")
+public class SignatureProperties {
+
+ /**
+ * 是否开启
+ */
+ private boolean enabled = false;
+
+ /**
+ * 同一个请求多长时间内有效 默认10分钟
+ */
+ private Long expireTime = 600000L;
+}
diff --git a/admin4j-signature/signature-core/src/main/java/com/admin4j/framework/signature/util/SignatureUtil.java b/admin4j-signature/signature-core/src/main/java/com/admin4j/framework/signature/util/SignatureUtil.java
new file mode 100644
index 0000000..85f1f21
--- /dev/null
+++ b/admin4j-signature/signature-core/src/main/java/com/admin4j/framework/signature/util/SignatureUtil.java
@@ -0,0 +1,6 @@
+package com.admin4j.framework.signature.util;
+
+public class SignatureUtil {
+
+}
+
diff --git a/admin4j-signature/signature-spring-boot-starter/pom.xml b/admin4j-signature/signature-spring-boot-starter/pom.xml
new file mode 100644
index 0000000..d102de9
--- /dev/null
+++ b/admin4j-signature/signature-spring-boot-starter/pom.xml
@@ -0,0 +1,46 @@
+
+
+ 4.0.0
+
+ com.admin4j.signature
+ admin4j-signature
+ ${admin4j-signature.version}
+
+
+ signature-spring-boot-starter
+
+
+ 8
+ 8
+ UTF-8
+
+
+
+
+ jakarta.servlet
+ jakarta.servlet-api
+ provided
+
+
+ org.springframework
+ spring-webmvc
+ provided
+
+
+ org.springframework.boot
+ spring-boot-autoconfigure
+
+
+ com.admin4j.signature
+ signature-core
+ ${admin4j-signature.version}
+
+
+ junit
+ junit
+ test
+
+
+
\ No newline at end of file
diff --git a/admin4j-signature/signature-spring-boot-starter/src/main/java/com/admin4j/signature/SignatureGlobalExceptionHandler.java b/admin4j-signature/signature-spring-boot-starter/src/main/java/com/admin4j/signature/SignatureGlobalExceptionHandler.java
new file mode 100644
index 0000000..347de78
--- /dev/null
+++ b/admin4j-signature/signature-spring-boot-starter/src/main/java/com/admin4j/signature/SignatureGlobalExceptionHandler.java
@@ -0,0 +1,34 @@
+package com.admin4j.signature;
+
+import com.admin4j.common.exception.handler.AbstractExceptionHandler;
+import com.admin4j.common.pojo.IResponse;
+import com.admin4j.common.pojo.ResponseEnum;
+import com.admin4j.common.pojo.SimpleResponse;
+import com.admin4j.framework.signature.exception.SignatureException;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+import org.springframework.web.bind.annotation.RestControllerAdvice;
+
+/**
+ * @author zhougang
+ * @since 2023/11/10 17:58
+ */
+@RestControllerAdvice
+@Slf4j
+public class SignatureGlobalExceptionHandler extends AbstractExceptionHandler {
+
+ @ExceptionHandler(SignatureException.class)
+ public ResponseEntity distributedLockException(SignatureException e) {
+ log.error("SignatureException:" + e.getMessage(), e);
+
+ return renderException(e, SimpleResponse.of(ResponseEnum.REQUEST_TOO_MANY_REQUESTS.getCode(), e.getMessage()));
+ }
+
+ @Override
+ public ResponseEntity renderException(Exception e, IResponse response) {
+ publishGlobalExceptionEvent(e);
+ return ResponseEntity.status(HttpStatus.TOO_MANY_REQUESTS).body(response);
+ }
+}
diff --git a/admin4j-signature/signature-spring-boot-starter/src/main/java/com/admin4j/signature/configuration/SignatureAutoConfiguration.java b/admin4j-signature/signature-spring-boot-starter/src/main/java/com/admin4j/signature/configuration/SignatureAutoConfiguration.java
new file mode 100644
index 0000000..b400f50
--- /dev/null
+++ b/admin4j-signature/signature-spring-boot-starter/src/main/java/com/admin4j/signature/configuration/SignatureAutoConfiguration.java
@@ -0,0 +1,70 @@
+package com.admin4j.signature.configuration;
+
+import com.admin4j.framework.signature.filter.RequestReplaceFilter;
+import com.admin4j.framework.signature.interceptor.SignatureInterceptor;
+import com.admin4j.framework.signature.properties.SignatureProperties;
+import com.admin4j.signature.SignatureGlobalExceptionHandler;
+import org.springframework.beans.BeansException;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.boot.web.servlet.FilterRegistrationBean;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.ApplicationContextAware;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.core.annotation.Order;
+
+/**
+ * @author zhougang
+ * @since 2023/11/10 15:10
+ */
+@Configuration
+@ConditionalOnClass(name = {"org.springframework.web.servlet.HandlerInterceptor"})
+@EnableConfigurationProperties(SignatureProperties.class)
+@ConditionalOnProperty(prefix = "admin4j.signature", name = "enabled", matchIfMissing = true)
+public class SignatureAutoConfiguration implements ApplicationContextAware {
+
+ @Bean
+ public RequestReplaceFilter requestReplaceFilter() {
+ return new RequestReplaceFilter();
+ }
+
+ /**
+ * 配置RequestReplaceFilter对象过滤器
+ *
+ * @return
+ */
+ @Bean
+ public FilterRegistrationBean filterRegistrationBean(RequestReplaceFilter requestReplaceFilter) {
+ FilterRegistrationBean filterRegistration = new FilterRegistrationBean<>(requestReplaceFilter);
+ filterRegistration.addUrlPatterns("/*");
+ filterRegistration.setOrder(2);
+ return filterRegistration;
+ }
+
+ @Bean
+ public SignatureInterceptor signatureInterceptor() {
+ return new SignatureInterceptor();
+ }
+
+ @Bean
+ public SignatureWebMvcConfigurer signatureWebMvcConfigurer(SignatureInterceptor signatureInterceptor) {
+ return new SignatureWebMvcConfigurer(signatureInterceptor);
+ }
+
+ @Bean
+ @Order(2)
+ @ConditionalOnClass(name = "com.admin4j.common.pojo.SimpleResponse")
+ public SignatureGlobalExceptionHandler signatureGlobalExceptionHandler() {
+ return new SignatureGlobalExceptionHandler();
+ }
+
+ private static ApplicationContext applicationContext;
+
+ @Override
+ public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
+ this.applicationContext = applicationContext;
+ }
+
+}
diff --git a/admin4j-signature/signature-spring-boot-starter/src/main/java/com/admin4j/signature/configuration/SignatureWebMvcConfigurer.java b/admin4j-signature/signature-spring-boot-starter/src/main/java/com/admin4j/signature/configuration/SignatureWebMvcConfigurer.java
new file mode 100644
index 0000000..0172cb4
--- /dev/null
+++ b/admin4j-signature/signature-spring-boot-starter/src/main/java/com/admin4j/signature/configuration/SignatureWebMvcConfigurer.java
@@ -0,0 +1,31 @@
+package com.admin4j.signature.configuration;
+
+import com.admin4j.framework.signature.interceptor.SignatureInterceptor;
+import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
+
+/**
+ * @author zhougang
+ * @since 2023/11/10 15:12
+ */
+public class SignatureWebMvcConfigurer implements WebMvcConfigurer {
+
+ private SignatureInterceptor signatureInterceptor;
+
+ public SignatureWebMvcConfigurer(SignatureInterceptor signatureInterceptor) {
+ this.signatureInterceptor = signatureInterceptor;
+ }
+
+ /**
+ * Add Spring MVC lifecycle interceptors for pre- and post-processing of
+ * controller method invocations and resource handler requests.
+ * Interceptors can be registered to apply to all requests or be limited
+ * to a subset of URL patterns.
+ *
+ * @param registry
+ */
+ @Override
+ public void addInterceptors(InterceptorRegistry registry) {
+ registry.addInterceptor(signatureInterceptor).addPathPatterns("/**");
+ }
+}
diff --git a/admin4j-signature/signature-spring-boot-starter/src/main/resources/META-INF/spring.factories b/admin4j-signature/signature-spring-boot-starter/src/main/resources/META-INF/spring.factories
new file mode 100644
index 0000000..bc4944a
--- /dev/null
+++ b/admin4j-signature/signature-spring-boot-starter/src/main/resources/META-INF/spring.factories
@@ -0,0 +1,3 @@
+org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
+ com.admin4j.signature.configuration.SignatureAutoConfiguration,\
+ com.admin4j.framework.signature.DefaultSignature
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
index ccd763d..46967ad 100644
--- a/pom.xml
+++ b/pom.xml
@@ -28,6 +28,7 @@
excel-spring-boot-starter
admin4j-limiter
admin4j-redis
+ admin4j-signature
elasticsearch-spring-boot-starter
mybatis-plus-boot-starter