【同步】对齐 boot 和 cloud 的逻辑
This commit is contained in:
parent
328445f7c0
commit
025857c9f2
@ -35,6 +35,13 @@
|
|||||||
<artifactId>lock4j-redisson-spring-boot-starter</artifactId>
|
<artifactId>lock4j-redisson-spring-boot-starter</artifactId>
|
||||||
<optional>true</optional>
|
<optional>true</optional>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Test 测试相关 -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>cn.iocoder.cloud</groupId>
|
||||||
|
<artifactId>yudao-spring-boot-starter-test</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
</project>
|
</project>
|
||||||
|
@ -0,0 +1,28 @@
|
|||||||
|
package cn.iocoder.yudao.framework.signature.config;
|
||||||
|
|
||||||
|
import cn.iocoder.yudao.framework.redis.config.YudaoRedisAutoConfiguration;
|
||||||
|
import cn.iocoder.yudao.framework.signature.core.aop.ApiSignatureAspect;
|
||||||
|
import cn.iocoder.yudao.framework.signature.core.redis.ApiSignatureRedisDAO;
|
||||||
|
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.data.redis.core.StringRedisTemplate;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* HTTP API 签名的自动配置类
|
||||||
|
*
|
||||||
|
* @author Zhougang
|
||||||
|
*/
|
||||||
|
@AutoConfiguration(after = YudaoRedisAutoConfiguration.class)
|
||||||
|
public class YudaoApiSignatureAutoConfiguration {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public ApiSignatureAspect signatureAspect(ApiSignatureRedisDAO signatureRedisDAO) {
|
||||||
|
return new ApiSignatureAspect(signatureRedisDAO);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public ApiSignatureRedisDAO signatureRedisDAO(StringRedisTemplate stringRedisTemplate) {
|
||||||
|
return new ApiSignatureRedisDAO(stringRedisTemplate);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,59 @@
|
|||||||
|
package cn.iocoder.yudao.framework.signature.core.annotation;
|
||||||
|
|
||||||
|
import cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants;
|
||||||
|
|
||||||
|
import java.lang.annotation.*;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* HTTP API 签名注解
|
||||||
|
*
|
||||||
|
* @author Zhougang
|
||||||
|
*/
|
||||||
|
@Inherited
|
||||||
|
@Documented
|
||||||
|
@Target({ElementType.METHOD, ElementType.TYPE})
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
public @interface ApiSignature {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 同一个请求多长时间内有效 默认 60 秒
|
||||||
|
*/
|
||||||
|
int timeout() default 60;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 时间单位,默认为 SECONDS 秒
|
||||||
|
*/
|
||||||
|
TimeUnit timeUnit() default TimeUnit.SECONDS;
|
||||||
|
|
||||||
|
// ========================== 签名参数 ==========================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 提示信息,签名失败的提示
|
||||||
|
*
|
||||||
|
* @see GlobalErrorCodeConstants#BAD_REQUEST
|
||||||
|
*/
|
||||||
|
String message() default "签名不正确"; // 为空时,使用 BAD_REQUEST 错误提示
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 签名字段:appId 应用ID
|
||||||
|
*/
|
||||||
|
String appId() default "appId";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 签名字段:timestamp 时间戳
|
||||||
|
*/
|
||||||
|
String timestamp() default "timestamp";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 签名字段:nonce 随机数,10 位以上
|
||||||
|
*/
|
||||||
|
String nonce() default "nonce";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* sign 客户端签名
|
||||||
|
*/
|
||||||
|
String sign() default "sign";
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,169 @@
|
|||||||
|
package cn.iocoder.yudao.framework.signature.core.aop;
|
||||||
|
|
||||||
|
import cn.hutool.core.lang.Assert;
|
||||||
|
import cn.hutool.core.map.MapUtil;
|
||||||
|
import cn.hutool.core.util.ObjUtil;
|
||||||
|
import cn.hutool.core.util.StrUtil;
|
||||||
|
import cn.hutool.crypto.digest.DigestUtil;
|
||||||
|
import cn.iocoder.yudao.framework.common.exception.ServiceException;
|
||||||
|
import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils;
|
||||||
|
import cn.iocoder.yudao.framework.signature.core.annotation.ApiSignature;
|
||||||
|
import cn.iocoder.yudao.framework.signature.core.redis.ApiSignatureRedisDAO;
|
||||||
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.aspectj.lang.JoinPoint;
|
||||||
|
import org.aspectj.lang.annotation.Aspect;
|
||||||
|
import org.aspectj.lang.annotation.Before;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.SortedMap;
|
||||||
|
import java.util.TreeMap;
|
||||||
|
|
||||||
|
import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants.BAD_REQUEST;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 拦截声明了 {@link ApiSignature} 注解的方法,实现签名
|
||||||
|
*
|
||||||
|
* @author Zhougang
|
||||||
|
*/
|
||||||
|
@Aspect
|
||||||
|
@Slf4j
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class ApiSignatureAspect {
|
||||||
|
|
||||||
|
private final ApiSignatureRedisDAO signatureRedisDAO;
|
||||||
|
|
||||||
|
@Before("@annotation(signature)")
|
||||||
|
public void beforePointCut(JoinPoint joinPoint, ApiSignature signature) {
|
||||||
|
// 1. 验证通过,直接结束
|
||||||
|
if (verifySignature(signature, Objects.requireNonNull(ServletUtils.getRequest()))) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 验证不通过,抛出异常
|
||||||
|
log.error("[beforePointCut][方法{} 参数({}) 签名失败]", joinPoint.getSignature().toString(),
|
||||||
|
joinPoint.getArgs());
|
||||||
|
throw new ServiceException(BAD_REQUEST.getCode(),
|
||||||
|
StrUtil.blankToDefault(signature.message(), BAD_REQUEST.getMsg()));
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean verifySignature(ApiSignature signature, HttpServletRequest request) {
|
||||||
|
// 1.1 校验 Header
|
||||||
|
if (!verifyHeaders(signature, request)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// 1.2 校验 appId 是否能获取到对应的 appSecret
|
||||||
|
String appId = request.getHeader(signature.appId());
|
||||||
|
String appSecret = signatureRedisDAO.getAppSecret(appId);
|
||||||
|
Assert.notNull(appSecret, "[appId({})] 找不到对应的 appSecret", appId);
|
||||||
|
|
||||||
|
// 2. 校验签名【重要!】
|
||||||
|
String clientSignature = request.getHeader(signature.sign()); // 客户端签名
|
||||||
|
String serverSignatureString = buildSignatureString(signature, request, appSecret); // 服务端签名字符串
|
||||||
|
String serverSignature = DigestUtil.sha256Hex(serverSignatureString); // 服务端签名
|
||||||
|
if (ObjUtil.notEqual(clientSignature, serverSignature)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 将 nonce 记入缓存,防止重复使用(重点二:此处需要将 ttl 设定为允许 timestamp 时间差的值 x 2 )
|
||||||
|
String nonce = request.getHeader(signature.nonce());
|
||||||
|
signatureRedisDAO.setNonce(nonce, signature.timeout() * 2, signature.timeUnit());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 校验请求头加签参数
|
||||||
|
*
|
||||||
|
* 1. appId 是否为空
|
||||||
|
* 2. timestamp 是否为空,请求是否已经超时,默认 10 分钟
|
||||||
|
* 3. nonce 是否为空,随机数是否 10 位以上,是否在规定时间内已经访问过了
|
||||||
|
* 4. sign 是否为空
|
||||||
|
*
|
||||||
|
* @param signature signature
|
||||||
|
* @param request request
|
||||||
|
* @return 是否校验 Header 通过
|
||||||
|
*/
|
||||||
|
private boolean verifyHeaders(ApiSignature signature, HttpServletRequest request) {
|
||||||
|
// 1. 非空校验
|
||||||
|
String appId = request.getHeader(signature.appId());
|
||||||
|
if (StrUtil.isBlank(appId)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
String timestamp = request.getHeader(signature.timestamp());
|
||||||
|
if (StrUtil.isBlank(timestamp)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
String nonce = request.getHeader(signature.nonce());
|
||||||
|
if (StrUtil.length(nonce) < 10) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
String sign = request.getHeader(signature.sign());
|
||||||
|
if (StrUtil.isBlank(sign)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 检查 timestamp 是否超出允许的范围 (重点一:此处需要取绝对值)
|
||||||
|
long expireTime = signature.timeUnit().toMillis(signature.timeout());
|
||||||
|
long requestTimestamp = Long.parseLong(timestamp);
|
||||||
|
long timestampDisparity = Math.abs(System.currentTimeMillis() - requestTimestamp);
|
||||||
|
if (timestampDisparity > expireTime) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 检查 nonce 是否存在,有且仅能使用一次
|
||||||
|
return signatureRedisDAO.getNonce(nonce) == null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构建签名字符串
|
||||||
|
*
|
||||||
|
* 格式为 = 请求参数 + 请求体 + 请求头 + 密钥
|
||||||
|
*
|
||||||
|
* @param signature signature
|
||||||
|
* @param request request
|
||||||
|
* @param appSecret appSecret
|
||||||
|
* @return 签名字符串
|
||||||
|
*/
|
||||||
|
private String buildSignatureString(ApiSignature signature, HttpServletRequest request, String appSecret) {
|
||||||
|
SortedMap<String, String> parameterMap = getRequestParameterMap(request); // 请求头
|
||||||
|
SortedMap<String, String> headerMap = getRequestHeaderMap(signature, request); // 请求参数
|
||||||
|
String requestBody = StrUtil.nullToDefault(ServletUtils.getBody(request), ""); // 请求体
|
||||||
|
return MapUtil.join(parameterMap, "&", "=")
|
||||||
|
+ requestBody
|
||||||
|
+ MapUtil.join(headerMap, "&", "=")
|
||||||
|
+ appSecret;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取请求头加签参数 Map
|
||||||
|
*
|
||||||
|
* @param request 请求
|
||||||
|
* @param signature 签名注解
|
||||||
|
* @return signature params
|
||||||
|
*/
|
||||||
|
private static SortedMap<String, String> getRequestHeaderMap(ApiSignature signature, HttpServletRequest request) {
|
||||||
|
SortedMap<String, String> sortedMap = new TreeMap<>();
|
||||||
|
sortedMap.put(signature.appId(), request.getHeader(signature.appId()));
|
||||||
|
sortedMap.put(signature.timestamp(), request.getHeader(signature.timestamp()));
|
||||||
|
sortedMap.put(signature.nonce(), request.getHeader(signature.nonce()));
|
||||||
|
return sortedMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取请求参数 Map
|
||||||
|
*
|
||||||
|
* @param request 请求
|
||||||
|
* @return queryParams
|
||||||
|
*/
|
||||||
|
private static SortedMap<String, String> getRequestParameterMap(HttpServletRequest request) {
|
||||||
|
SortedMap<String, String> sortedMap = new TreeMap<>();
|
||||||
|
for (Map.Entry<String, String[]> entry : request.getParameterMap().entrySet()) {
|
||||||
|
sortedMap.put(entry.getKey(), entry.getValue()[0]);
|
||||||
|
}
|
||||||
|
return sortedMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,57 @@
|
|||||||
|
package cn.iocoder.yudao.framework.signature.core.redis;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import org.springframework.data.redis.core.StringRedisTemplate;
|
||||||
|
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* HTTP API 签名 Redis DAO
|
||||||
|
*
|
||||||
|
* @author Zhougang
|
||||||
|
*/
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class ApiSignatureRedisDAO {
|
||||||
|
|
||||||
|
private final StringRedisTemplate stringRedisTemplate;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 验签随机数
|
||||||
|
*
|
||||||
|
* KEY 格式:signature_nonce:%s // 参数为 随机数
|
||||||
|
* VALUE 格式:String
|
||||||
|
* 过期时间:不固定
|
||||||
|
*/
|
||||||
|
private static final String SIGNATURE_NONCE = "api_signature_nonce:%s";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 签名密钥
|
||||||
|
*
|
||||||
|
* HASH 结构
|
||||||
|
* KEY 格式:%s // 参数为 appid
|
||||||
|
* VALUE 格式:String
|
||||||
|
* 过期时间:永不过期(预加载到 Redis)
|
||||||
|
*/
|
||||||
|
private static final String SIGNATURE_APPID = "api_signature_app";
|
||||||
|
|
||||||
|
// ========== 验签随机数 ==========
|
||||||
|
|
||||||
|
public String getNonce(String nonce) {
|
||||||
|
return stringRedisTemplate.opsForValue().get(formatNonceKey(nonce));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setNonce(String nonce, int time, TimeUnit timeUnit) {
|
||||||
|
stringRedisTemplate.opsForValue().set(formatNonceKey(nonce), "", time, timeUnit);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String formatNonceKey(String key) {
|
||||||
|
return String.format(SIGNATURE_NONCE, key);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========== 签名密钥 ==========
|
||||||
|
|
||||||
|
public String getAppSecret(String appId) {
|
||||||
|
return (String) stringRedisTemplate.opsForHash().get(SIGNATURE_APPID, appId);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,6 @@
|
|||||||
|
/**
|
||||||
|
* HTTP API 签名,校验安全性
|
||||||
|
*
|
||||||
|
* @see <a href="https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=4_3>微信支付 —— 安全规范</a>
|
||||||
|
*/
|
||||||
|
package cn.iocoder.yudao.framework.signature;
|
@ -0,0 +1,75 @@
|
|||||||
|
package cn.iocoder.yudao.framework.signature.core;
|
||||||
|
|
||||||
|
import cn.hutool.core.map.MapUtil;
|
||||||
|
import cn.hutool.core.util.IdUtil;
|
||||||
|
import cn.hutool.crypto.digest.DigestUtil;
|
||||||
|
import cn.iocoder.yudao.framework.signature.core.annotation.ApiSignature;
|
||||||
|
import cn.iocoder.yudao.framework.signature.core.aop.ApiSignatureAspect;
|
||||||
|
import cn.iocoder.yudao.framework.signature.core.redis.ApiSignatureRedisDAO;
|
||||||
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
|
import org.mockito.InjectMocks;
|
||||||
|
import org.mockito.Mock;
|
||||||
|
import org.mockito.junit.jupiter.MockitoExtension;
|
||||||
|
|
||||||
|
import java.io.BufferedReader;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.StringReader;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
import static org.mockito.ArgumentMatchers.eq;
|
||||||
|
import static org.mockito.Mockito.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@link ApiSignatureTest} 的单元测试
|
||||||
|
*/
|
||||||
|
@ExtendWith(MockitoExtension.class)
|
||||||
|
public class ApiSignatureTest {
|
||||||
|
|
||||||
|
@InjectMocks
|
||||||
|
private ApiSignatureAspect apiSignatureAspect;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private ApiSignatureRedisDAO signatureRedisDAO;
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSignatureGet() throws IOException {
|
||||||
|
// 搞一个签名
|
||||||
|
Long timestamp = System.currentTimeMillis();
|
||||||
|
String nonce = IdUtil.randomUUID();
|
||||||
|
String appId = "xxxxxx";
|
||||||
|
String appSecret = "yyyyyy";
|
||||||
|
String signString = "k1=v1&v1=k1testappId=xxxxxx&nonce=" + nonce + "×tamp=" + timestamp + "yyyyyy";
|
||||||
|
String sign = DigestUtil.sha256Hex(signString);
|
||||||
|
|
||||||
|
// 准备参数
|
||||||
|
ApiSignature apiSignature = mock(ApiSignature.class);
|
||||||
|
when(apiSignature.appId()).thenReturn("appId");
|
||||||
|
when(apiSignature.timestamp()).thenReturn("timestamp");
|
||||||
|
when(apiSignature.nonce()).thenReturn("nonce");
|
||||||
|
when(apiSignature.sign()).thenReturn("sign");
|
||||||
|
when(apiSignature.timeout()).thenReturn(60);
|
||||||
|
when(apiSignature.timeUnit()).thenReturn(TimeUnit.SECONDS);
|
||||||
|
HttpServletRequest request = mock(HttpServletRequest.class);
|
||||||
|
when(request.getHeader(eq("appId"))).thenReturn(appId);
|
||||||
|
when(request.getHeader(eq("timestamp"))).thenReturn(String.valueOf(timestamp));
|
||||||
|
when(request.getHeader(eq("nonce"))).thenReturn(nonce);
|
||||||
|
when(request.getHeader(eq("sign"))).thenReturn(sign);
|
||||||
|
when(request.getParameterMap()).thenReturn(MapUtil.<String, String[]>builder()
|
||||||
|
.put("v1", new String[]{"k1"}).put("k1", new String[]{"v1"}).build());
|
||||||
|
when(request.getContentType()).thenReturn("application/json");
|
||||||
|
when(request.getReader()).thenReturn(new BufferedReader(new StringReader("test")));
|
||||||
|
// mock 方法
|
||||||
|
when(signatureRedisDAO.getAppSecret(eq(appId))).thenReturn(appSecret);
|
||||||
|
|
||||||
|
// 调用
|
||||||
|
boolean result = apiSignatureAspect.verifySignature(apiSignature, request);
|
||||||
|
// 断言结果
|
||||||
|
assertTrue(result);
|
||||||
|
// 断言调用
|
||||||
|
verify(signatureRedisDAO).setNonce(eq(nonce), eq(120), eq(TimeUnit.SECONDS));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -28,11 +28,15 @@ import org.springframework.web.bind.annotation.RequestMethod;
|
|||||||
import org.springframework.web.method.HandlerMethod;
|
import org.springframework.web.method.HandlerMethod;
|
||||||
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
|
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
|
||||||
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
|
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
|
||||||
|
import org.springframework.web.util.pattern.PathPattern;
|
||||||
|
|
||||||
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
|
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 自定义的 Spring Security 配置适配器实现
|
* 自定义的 Spring Security 配置适配器实现
|
||||||
*
|
*
|
||||||
@ -163,13 +167,20 @@ public class YudaoWebSecurityConfigurerAdapter {
|
|||||||
if (!handlerMethod.hasMethodAnnotation(PermitAll.class)) {
|
if (!handlerMethod.hasMethodAnnotation(PermitAll.class)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (entry.getKey().getPatternsCondition() == null) {
|
Set<String> urls = new HashSet<>();
|
||||||
|
if (entry.getKey().getPatternsCondition() != null) {
|
||||||
|
urls.addAll(entry.getKey().getPatternsCondition().getPatterns());
|
||||||
|
}
|
||||||
|
if (entry.getKey().getPathPatternsCondition() != null) {
|
||||||
|
urls.addAll(convertList(entry.getKey().getPathPatternsCondition().getPatterns(), PathPattern::getPatternString));
|
||||||
|
}
|
||||||
|
if (urls.isEmpty()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
Set<String> urls = entry.getKey().getPatternsCondition().getPatterns();
|
|
||||||
// 特殊:使用 @RequestMapping 注解,并且未写 method 属性,此时认为都需要免登录
|
// 特殊:使用 @RequestMapping 注解,并且未写 method 属性,此时认为都需要免登录
|
||||||
Set<RequestMethod> methods = entry.getKey().getMethodsCondition().getMethods();
|
Set<RequestMethod> methods = entry.getKey().getMethodsCondition().getMethods();
|
||||||
if (CollUtil.isEmpty(methods)) { //
|
if (CollUtil.isEmpty(methods)) {
|
||||||
result.putAll(HttpMethod.GET, urls);
|
result.putAll(HttpMethod.GET, urls);
|
||||||
result.putAll(HttpMethod.POST, urls);
|
result.putAll(HttpMethod.POST, urls);
|
||||||
result.putAll(HttpMethod.PUT, urls);
|
result.putAll(HttpMethod.PUT, urls);
|
||||||
|
@ -20,7 +20,7 @@ public enum ErpBizTypeEnum implements IntArrayValuable {
|
|||||||
PURCHASE_RETURN(12, "采购退货"),
|
PURCHASE_RETURN(12, "采购退货"),
|
||||||
|
|
||||||
SALE_ORDER(20, "销售订单"),
|
SALE_ORDER(20, "销售订单"),
|
||||||
SALE_OUT(21, "销售订单"),
|
SALE_OUT(21, "销售出库"),
|
||||||
SALE_RETURN(22, "销售退货"),
|
SALE_RETURN(22, "销售退货"),
|
||||||
;
|
;
|
||||||
|
|
||||||
|
@ -28,8 +28,8 @@ public class FileContentDO extends BaseDO {
|
|||||||
/**
|
/**
|
||||||
* 编号,数据库自增
|
* 编号,数据库自增
|
||||||
*/
|
*/
|
||||||
@TableId(type = IdType.INPUT)
|
@TableId
|
||||||
private String id;
|
private Long id;
|
||||||
/**
|
/**
|
||||||
* 配置编号
|
* 配置编号
|
||||||
*
|
*
|
||||||
|
@ -26,6 +26,16 @@ import java.time.LocalDateTime;
|
|||||||
@AllArgsConstructor
|
@AllArgsConstructor
|
||||||
public class ApiAccessLogDO extends BaseDO {
|
public class ApiAccessLogDO extends BaseDO {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@link #requestParams} 的最大长度
|
||||||
|
*/
|
||||||
|
public static final Integer REQUEST_PARAMS_MAX_LENGTH = 8000;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@link #resultMsg} 的最大长度
|
||||||
|
*/
|
||||||
|
public static final Integer RESULT_MSG_MAX_LENGTH = 512;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 编号
|
* 编号
|
||||||
*/
|
*/
|
||||||
|
@ -25,6 +25,11 @@ import java.time.LocalDateTime;
|
|||||||
@KeySequence(value = "infra_api_error_log_seq")
|
@KeySequence(value = "infra_api_error_log_seq")
|
||||||
public class ApiErrorLogDO extends BaseDO {
|
public class ApiErrorLogDO extends BaseDO {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@link #requestParams} 的最大长度
|
||||||
|
*/
|
||||||
|
public static final Integer REQUEST_PARAMS_MAX_LENGTH = 8000;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 编号
|
* 编号
|
||||||
*/
|
*/
|
||||||
|
@ -1,18 +1,22 @@
|
|||||||
package cn.iocoder.yudao.module.infra.service.logger;
|
package cn.iocoder.yudao.module.infra.service.logger;
|
||||||
|
|
||||||
|
import cn.hutool.core.util.StrUtil;
|
||||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||||
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
|
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
|
||||||
import cn.iocoder.yudao.module.infra.api.logger.dto.ApiAccessLogCreateReqDTO;
|
import cn.iocoder.yudao.module.infra.api.logger.dto.ApiAccessLogCreateReqDTO;
|
||||||
import cn.iocoder.yudao.module.infra.controller.admin.logger.vo.apiaccesslog.ApiAccessLogPageReqVO;
|
import cn.iocoder.yudao.module.infra.controller.admin.logger.vo.apiaccesslog.ApiAccessLogPageReqVO;
|
||||||
import cn.iocoder.yudao.module.infra.dal.dataobject.logger.ApiAccessLogDO;
|
import cn.iocoder.yudao.module.infra.dal.dataobject.logger.ApiAccessLogDO;
|
||||||
import cn.iocoder.yudao.module.infra.dal.mysql.logger.ApiAccessLogMapper;
|
import cn.iocoder.yudao.module.infra.dal.mysql.logger.ApiAccessLogMapper;
|
||||||
|
import jakarta.annotation.Resource;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.validation.annotation.Validated;
|
import org.springframework.validation.annotation.Validated;
|
||||||
|
|
||||||
import jakarta.annotation.Resource;
|
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
import static cn.iocoder.yudao.module.infra.dal.dataobject.logger.ApiAccessLogDO.REQUEST_PARAMS_MAX_LENGTH;
|
||||||
|
import static cn.iocoder.yudao.module.infra.dal.dataobject.logger.ApiAccessLogDO.RESULT_MSG_MAX_LENGTH;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* API 访问日志 Service 实现类
|
* API 访问日志 Service 实现类
|
||||||
*
|
*
|
||||||
@ -29,6 +33,8 @@ public class ApiAccessLogServiceImpl implements ApiAccessLogService {
|
|||||||
@Override
|
@Override
|
||||||
public void createApiAccessLog(ApiAccessLogCreateReqDTO createDTO) {
|
public void createApiAccessLog(ApiAccessLogCreateReqDTO createDTO) {
|
||||||
ApiAccessLogDO apiAccessLog = BeanUtils.toBean(createDTO, ApiAccessLogDO.class);
|
ApiAccessLogDO apiAccessLog = BeanUtils.toBean(createDTO, ApiAccessLogDO.class);
|
||||||
|
apiAccessLog.setRequestParams(StrUtil.maxLength(apiAccessLog.getRequestParams(), REQUEST_PARAMS_MAX_LENGTH));
|
||||||
|
apiAccessLog.setResultMsg(StrUtil.maxLength(apiAccessLog.getResultMsg(), RESULT_MSG_MAX_LENGTH));
|
||||||
apiAccessLogMapper.insert(apiAccessLog);
|
apiAccessLogMapper.insert(apiAccessLog);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package cn.iocoder.yudao.module.infra.service.logger;
|
package cn.iocoder.yudao.module.infra.service.logger;
|
||||||
|
|
||||||
|
import cn.hutool.core.util.StrUtil;
|
||||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||||
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
|
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
|
||||||
import cn.iocoder.yudao.module.infra.api.logger.dto.ApiErrorLogCreateReqDTO;
|
import cn.iocoder.yudao.module.infra.api.logger.dto.ApiErrorLogCreateReqDTO;
|
||||||
@ -7,14 +8,15 @@ import cn.iocoder.yudao.module.infra.controller.admin.logger.vo.apierrorlog.ApiE
|
|||||||
import cn.iocoder.yudao.module.infra.dal.dataobject.logger.ApiErrorLogDO;
|
import cn.iocoder.yudao.module.infra.dal.dataobject.logger.ApiErrorLogDO;
|
||||||
import cn.iocoder.yudao.module.infra.dal.mysql.logger.ApiErrorLogMapper;
|
import cn.iocoder.yudao.module.infra.dal.mysql.logger.ApiErrorLogMapper;
|
||||||
import cn.iocoder.yudao.module.infra.enums.logger.ApiErrorLogProcessStatusEnum;
|
import cn.iocoder.yudao.module.infra.enums.logger.ApiErrorLogProcessStatusEnum;
|
||||||
|
import jakarta.annotation.Resource;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.validation.annotation.Validated;
|
import org.springframework.validation.annotation.Validated;
|
||||||
|
|
||||||
import jakarta.annotation.Resource;
|
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
|
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
|
||||||
|
import static cn.iocoder.yudao.module.infra.dal.dataobject.logger.ApiErrorLogDO.REQUEST_PARAMS_MAX_LENGTH;
|
||||||
import static cn.iocoder.yudao.module.infra.enums.ErrorCodeConstants.API_ERROR_LOG_NOT_FOUND;
|
import static cn.iocoder.yudao.module.infra.enums.ErrorCodeConstants.API_ERROR_LOG_NOT_FOUND;
|
||||||
import static cn.iocoder.yudao.module.infra.enums.ErrorCodeConstants.API_ERROR_LOG_PROCESSED;
|
import static cn.iocoder.yudao.module.infra.enums.ErrorCodeConstants.API_ERROR_LOG_PROCESSED;
|
||||||
|
|
||||||
@ -35,6 +37,7 @@ public class ApiErrorLogServiceImpl implements ApiErrorLogService {
|
|||||||
public void createApiErrorLog(ApiErrorLogCreateReqDTO createDTO) {
|
public void createApiErrorLog(ApiErrorLogCreateReqDTO createDTO) {
|
||||||
ApiErrorLogDO apiErrorLog = BeanUtils.toBean(createDTO, ApiErrorLogDO.class)
|
ApiErrorLogDO apiErrorLog = BeanUtils.toBean(createDTO, ApiErrorLogDO.class)
|
||||||
.setProcessStatus(ApiErrorLogProcessStatusEnum.INIT.getStatus());
|
.setProcessStatus(ApiErrorLogProcessStatusEnum.INIT.getStatus());
|
||||||
|
apiErrorLog.setRequestParams(StrUtil.maxLength(apiErrorLog.getRequestParams(), REQUEST_PARAMS_MAX_LENGTH));
|
||||||
apiErrorLogMapper.insert(apiErrorLog);
|
apiErrorLogMapper.insert(apiErrorLog);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -58,7 +58,7 @@ public interface TradeOrderStatisticsMapper extends BaseMapperX<TradeStatisticsD
|
|||||||
|
|
||||||
Long selectCountByStatusAndDeliveryType(@Param("status") Integer status, @Param("deliveryType") Integer deliveryType);
|
Long selectCountByStatusAndDeliveryType(@Param("status") Integer status, @Param("deliveryType") Integer deliveryType);
|
||||||
|
|
||||||
TradeOrderSummaryRespVO selectPaySummaryByPayStatusAndPayTimeBetween(@Param("payStatus") Integer payStatus,
|
TradeOrderSummaryRespVO selectPaySummaryByPayStatusAndPayTimeBetween(@Param("payStatus") Boolean payStatus,
|
||||||
@Param("beginTime") LocalDateTime beginTime,
|
@Param("beginTime") LocalDateTime beginTime,
|
||||||
@Param("endTime") LocalDateTime endTime);
|
@Param("endTime") LocalDateTime endTime);
|
||||||
|
|
||||||
|
@ -75,9 +75,9 @@ public class TradeOrderStatisticsServiceImpl implements TradeOrderStatisticsServ
|
|||||||
|
|
||||||
private TradeOrderSummaryRespVO getPayPriceSummary(LocalDateTime date) {
|
private TradeOrderSummaryRespVO getPayPriceSummary(LocalDateTime date) {
|
||||||
LocalDateTime beginTime = LocalDateTimeUtil.beginOfDay(date);
|
LocalDateTime beginTime = LocalDateTimeUtil.beginOfDay(date);
|
||||||
LocalDateTime endTime = LocalDateTimeUtil.beginOfDay(date);
|
LocalDateTime endTime = LocalDateTimeUtil.endOfDay(date);
|
||||||
return tradeOrderStatisticsMapper.selectPaySummaryByPayStatusAndPayTimeBetween(
|
return tradeOrderStatisticsMapper.selectPaySummaryByPayStatusAndPayTimeBetween(
|
||||||
PayOrderStatusEnum.SUCCESS.getStatus(), beginTime, endTime);
|
Boolean.TRUE, beginTime, endTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -38,7 +38,7 @@ public class MemberUserBaseVO {
|
|||||||
private String name;
|
private String name;
|
||||||
|
|
||||||
@Schema(description = "用户性别", example = "1")
|
@Schema(description = "用户性别", example = "1")
|
||||||
private Byte sex;
|
private Integer sex;
|
||||||
|
|
||||||
@Schema(description = "所在地编号", example = "4371")
|
@Schema(description = "所在地编号", example = "4371")
|
||||||
private Long areaId;
|
private Long areaId;
|
||||||
|
@ -17,7 +17,7 @@ public enum SexEnum {
|
|||||||
/** 女 */
|
/** 女 */
|
||||||
FEMALE(2),
|
FEMALE(2),
|
||||||
/* 未知 */
|
/* 未知 */
|
||||||
UNKNOWN(3);
|
UNKNOWN(0);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 性别
|
* 性别
|
||||||
|
@ -119,6 +119,9 @@ public class UserController {
|
|||||||
@PreAuthorize("@ss.hasPermission('system:user:query')")
|
@PreAuthorize("@ss.hasPermission('system:user:query')")
|
||||||
public CommonResult<UserRespVO> getUser(@RequestParam("id") Long id) {
|
public CommonResult<UserRespVO> getUser(@RequestParam("id") Long id) {
|
||||||
AdminUserDO user = userService.getUser(id);
|
AdminUserDO user = userService.getUser(id);
|
||||||
|
if (user == null) {
|
||||||
|
return success(null);
|
||||||
|
}
|
||||||
// 拼接数据
|
// 拼接数据
|
||||||
DeptDO dept = deptService.getDept(user.getDeptId());
|
DeptDO dept = deptService.getDept(user.getDeptId());
|
||||||
return success(UserConvert.INSTANCE.convert(user, dept));
|
return success(UserConvert.INSTANCE.convert(user, dept));
|
||||||
|
@ -18,6 +18,7 @@ import cn.iocoder.yudao.module.system.enums.permission.RoleCodeEnum;
|
|||||||
import cn.iocoder.yudao.module.system.enums.permission.RoleTypeEnum;
|
import cn.iocoder.yudao.module.system.enums.permission.RoleTypeEnum;
|
||||||
import com.google.common.annotations.VisibleForTesting;
|
import com.google.common.annotations.VisibleForTesting;
|
||||||
import com.mzt.logapi.context.LogRecordContext;
|
import com.mzt.logapi.context.LogRecordContext;
|
||||||
|
import com.mzt.logapi.service.impl.DiffParseFunction;
|
||||||
import com.mzt.logapi.starter.annotation.LogRecord;
|
import com.mzt.logapi.starter.annotation.LogRecord;
|
||||||
import jakarta.annotation.Resource;
|
import jakarta.annotation.Resource;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
@ -116,6 +117,7 @@ public class RoleServiceImpl implements RoleService {
|
|||||||
permissionService.processRoleDeleted(id);
|
permissionService.processRoleDeleted(id);
|
||||||
|
|
||||||
// 3. 记录操作日志上下文
|
// 3. 记录操作日志上下文
|
||||||
|
LogRecordContext.putVariable(DiffParseFunction.OLD_OBJECT, BeanUtils.toBean(role, RoleSaveReqVO.class));
|
||||||
LogRecordContext.putVariable("role", role);
|
LogRecordContext.putVariable("role", role);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -67,7 +67,8 @@ public class SmsCodeServiceImpl implements SmsCodeService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 创建验证码记录
|
// 创建验证码记录
|
||||||
String code = String.valueOf(randomInt(smsCodeProperties.getBeginCode(), smsCodeProperties.getEndCode() + 1));
|
String code = String.format("%0" + smsCodeProperties.getEndCode().toString().length() + "d",
|
||||||
|
randomInt(smsCodeProperties.getBeginCode(), smsCodeProperties.getEndCode() + 1));
|
||||||
SmsCodeDO newSmsCode = SmsCodeDO.builder().mobile(mobile).code(code).scene(scene)
|
SmsCodeDO newSmsCode = SmsCodeDO.builder().mobile(mobile).code(code).scene(scene)
|
||||||
.todayIndex(lastSmsCode != null && isToday(lastSmsCode.getCreateTime()) ? lastSmsCode.getTodayIndex() + 1 : 1)
|
.todayIndex(lastSmsCode != null && isToday(lastSmsCode.getCreateTime()) ? lastSmsCode.getTodayIndex() + 1 : 1)
|
||||||
.createIp(ip).used(false).build();
|
.createIp(ip).used(false).build();
|
||||||
|
Loading…
Reference in New Issue
Block a user