mirror of
				https://github.com/YunaiV/ruoyi-vue-pro.git
				synced 2025-10-31 10:37:41 +08:00 
			
		
		
		
	Merge branch 'master-jdk17' of https://github.com/YunaiV/ruoyi-vue-pro into master-jdk17
This commit is contained in:
		| @ -12,7 +12,7 @@ | |||||||
|     <packaging>jar</packaging> |     <packaging>jar</packaging> | ||||||
|  |  | ||||||
|     <name>${project.artifactId}</name> |     <name>${project.artifactId}</name> | ||||||
|     <description>服务保证,提供分布式锁、幂等、限流、熔断等等功能</description> |     <description>服务保证,提供分布式锁、幂等、限流、熔断、API 签名等等功能</description> | ||||||
|     <url>https://github.com/YunaiV/ruoyi-vue-pro</url> |     <url>https://github.com/YunaiV/ruoyi-vue-pro</url> | ||||||
|  |  | ||||||
|     <dependencies> |     <dependencies> | ||||||
| @ -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>org.springframework.boot</groupId> | ||||||
|  |             <artifactId>spring-boot-starter-test</artifactId> | ||||||
|  |             <scope>test</scope> | ||||||
|  |         </dependency> | ||||||
|     </dependencies> |     </dependencies> | ||||||
|  |  | ||||||
| </project> | </project> | ||||||
|  | |||||||
| @ -0,0 +1,27 @@ | |||||||
|  | package cn.iocoder.yudao.framework.signature.config; | ||||||
|  |  | ||||||
|  | import cn.iocoder.yudao.framework.redis.config.YudaoRedisAutoConfiguration; | ||||||
|  | import cn.iocoder.yudao.framework.signature.core.aop.SignatureAspect; | ||||||
|  | import cn.iocoder.yudao.framework.signature.core.redis.SignatureRedisDAO; | ||||||
|  | import org.springframework.boot.autoconfigure.AutoConfiguration; | ||||||
|  | import org.springframework.context.annotation.Bean; | ||||||
|  | import org.springframework.data.redis.core.StringRedisTemplate; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * @author Zhougang | ||||||
|  |  */ | ||||||
|  | @AutoConfiguration(after = YudaoRedisAutoConfiguration.class) | ||||||
|  | public class YudaoSignatureAutoConfiguration { | ||||||
|  |  | ||||||
|  |     @Bean | ||||||
|  |     public SignatureAspect signatureAspect(SignatureRedisDAO signatureRedisDAO) { | ||||||
|  |         return new SignatureAspect(signatureRedisDAO); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Bean | ||||||
|  |     @SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection") | ||||||
|  |     public SignatureRedisDAO signatureRedisDAO(StringRedisTemplate stringRedisTemplate) { | ||||||
|  |         return new SignatureRedisDAO(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; | ||||||
|  |  | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * 签名注解 | ||||||
|  |  * | ||||||
|  |  * @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,155 @@ | |||||||
|  | package cn.iocoder.yudao.framework.signature.core.aop; | ||||||
|  |  | ||||||
|  | import cn.hutool.core.collection.CollUtil; | ||||||
|  | import cn.hutool.core.lang.Assert; | ||||||
|  | import cn.hutool.core.util.StrUtil; | ||||||
|  | import cn.hutool.crypto.SignUtil; | ||||||
|  | import cn.iocoder.yudao.framework.common.exception.ServiceException; | ||||||
|  | import cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants; | ||||||
|  | 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.SignatureRedisDAO; | ||||||
|  | import cn.iocoder.yudao.framework.web.core.filter.CacheRequestBodyWrapper; | ||||||
|  | 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.nio.charset.StandardCharsets; | ||||||
|  | import java.util.Map; | ||||||
|  | import java.util.Objects; | ||||||
|  | import java.util.SortedMap; | ||||||
|  | import java.util.TreeMap; | ||||||
|  | import java.util.concurrent.TimeUnit; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * 拦截声明了 {@link ApiSignature} 注解的方法,实现签名 | ||||||
|  |  * | ||||||
|  |  * @author Zhougang | ||||||
|  |  */ | ||||||
|  | @Aspect | ||||||
|  | @Slf4j | ||||||
|  | @AllArgsConstructor | ||||||
|  | public class SignatureAspect { | ||||||
|  |  | ||||||
|  |     private final SignatureRedisDAO signatureRedisDAO; | ||||||
|  |  | ||||||
|  |     @Before("@annotation(signature)") | ||||||
|  |     public void beforePointCut(JoinPoint joinPoint, ApiSignature signature) { | ||||||
|  |         if (!verifySignature(signature, Objects.requireNonNull(ServletUtils.getRequest()))) { | ||||||
|  |             log.error("[beforePointCut][方法{} 参数({}) 签名失败]", joinPoint.getSignature().toString(), | ||||||
|  |                     joinPoint.getArgs()); | ||||||
|  |             String message = StrUtil.blankToDefault(signature.message(), | ||||||
|  |                     GlobalErrorCodeConstants.BAD_REQUEST.getMsg()); | ||||||
|  |             throw new ServiceException(GlobalErrorCodeConstants.BAD_REQUEST.getCode(), message); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private boolean verifySignature(ApiSignature signature, HttpServletRequest request) { | ||||||
|  |         if (!verifyHeaders(signature, request)) { | ||||||
|  |             return false; | ||||||
|  |         } | ||||||
|  |         // 校验 appId 是否能获取到对应的 appSecret | ||||||
|  |         String appId = request.getHeader(signature.appId()); | ||||||
|  |         String appSecret = signatureRedisDAO.getAppSecret(appId); | ||||||
|  |         Assert.notNull(appSecret, "[appId({})] 找不到对应的 appSecret", appId); | ||||||
|  |         // 请求头 | ||||||
|  |         SortedMap<String, String> headersMap = getRequestHeaders(signature, request); | ||||||
|  |         // 请求参数 | ||||||
|  |         String requestParams = getRequestParams(request); | ||||||
|  |         // 请求体 | ||||||
|  |         String requestBody = ServletUtils.isJsonRequest(request) ? ServletUtils.getBody(request) : ""; | ||||||
|  |         // 生成服务端签名 | ||||||
|  |         String serverSignature = SignUtil.signParamsSha256(headersMap, requestParams + requestBody + appSecret); | ||||||
|  |         // 客户端签名 | ||||||
|  |         String clientSignature = request.getHeader(signature.sign()); | ||||||
|  |         if (!StrUtil.equals(clientSignature, serverSignature)) { | ||||||
|  |             return false; | ||||||
|  |         } | ||||||
|  |         String nonce = headersMap.get(signature.nonce()); | ||||||
|  |         // 将 nonce 记入缓存,防止重复使用(重点二:此处需要将 ttl 设定为允许 timestamp 时间差的值 x 2 ) | ||||||
|  |         signatureRedisDAO.setNonce(nonce, signature.timeout() * 2L, signature.timeUnit()); | ||||||
|  |         return true; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * 校验请求头加签参数 | ||||||
|  |      * 1.appId 是否为空 | ||||||
|  |      * 2.timestamp 是否为空,请求是否已经超时,默认 10 分钟 | ||||||
|  |      * 3.nonce 是否为空,随机数是否 10 位以上,是否在规定时间内已经访问过了 | ||||||
|  |      * 4.sign 是否为空 | ||||||
|  |      * | ||||||
|  |      * @param signature signature | ||||||
|  |      * @param request   request | ||||||
|  |      */ | ||||||
|  |     private boolean verifyHeaders(ApiSignature signature, HttpServletRequest request) { | ||||||
|  |         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.isBlank(nonce) || StrUtil.length(nonce) < 10) { | ||||||
|  |             return false; | ||||||
|  |         } | ||||||
|  |         String sign = request.getHeader(signature.sign()); | ||||||
|  |         if (StrUtil.isBlank(sign)) { | ||||||
|  |             return false; | ||||||
|  |         } | ||||||
|  |         // 其他合法性校验 | ||||||
|  |         long expireTime = signature.timeUnit().toMillis(signature.timeout()); | ||||||
|  |         long requestTimestamp = Long.parseLong(timestamp); | ||||||
|  |         // 检查 timestamp 是否超出允许的范围 (重点一:此处需要取绝对值) | ||||||
|  |         long timestampDisparity = Math.abs(System.currentTimeMillis() - requestTimestamp); | ||||||
|  |         if (timestampDisparity > expireTime) { | ||||||
|  |             return false; | ||||||
|  |         } | ||||||
|  |         String cacheNonce = signatureRedisDAO.getNonce(nonce); | ||||||
|  |         return StrUtil.isBlank(cacheNonce); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * 获取请求头加签参数 | ||||||
|  |      * | ||||||
|  |      * @param request request | ||||||
|  |      * @return signature params | ||||||
|  |      */ | ||||||
|  |     private SortedMap<String, String> getRequestHeaders(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; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * 获取 URL 参数 | ||||||
|  |      * | ||||||
|  |      * @param request request | ||||||
|  |      * @return queryParams | ||||||
|  |      */ | ||||||
|  |     private String getRequestParams(HttpServletRequest request) { | ||||||
|  |         if (CollUtil.isEmpty(request.getParameterMap())) { | ||||||
|  |             return ""; | ||||||
|  |         } | ||||||
|  |         Map<String, String[]> requestParams = request.getParameterMap(); | ||||||
|  |         // 获取 URL 请求参数 | ||||||
|  |         SortedMap<String, String> sortParamsMap = new TreeMap<>(); | ||||||
|  |         for (Map.Entry<String, String[]> entry : requestParams.entrySet()) { | ||||||
|  |             sortParamsMap.put(entry.getKey(), entry.getValue()[0]); | ||||||
|  |         } | ||||||
|  |         // 按 key 排序 | ||||||
|  |         StringBuilder queryString = new StringBuilder(); | ||||||
|  |         for (Map.Entry<String, String> entry : sortParamsMap.entrySet()) { | ||||||
|  |             queryString.append("&").append(entry.getKey()).append("=").append(entry.getValue()); | ||||||
|  |         } | ||||||
|  |         return queryString.substring(1); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  | } | ||||||
|  |  | ||||||
| @ -0,0 +1,55 @@ | |||||||
|  | package cn.iocoder.yudao.framework.signature.core.redis; | ||||||
|  |  | ||||||
|  | import lombok.AllArgsConstructor; | ||||||
|  | import org.springframework.data.redis.core.StringRedisTemplate; | ||||||
|  |  | ||||||
|  | import java.util.concurrent.TimeUnit; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * API 签名 Redis DAO | ||||||
|  |  * | ||||||
|  |  * @author Zhougang | ||||||
|  |  */ | ||||||
|  | @AllArgsConstructor | ||||||
|  | public class SignatureRedisDAO { | ||||||
|  |  | ||||||
|  |     private final StringRedisTemplate stringRedisTemplate; | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * 验签随机数 | ||||||
|  |      * <p> | ||||||
|  |      * KEY 格式:signature_nonce:%s // 参数为 随机数 | ||||||
|  |      * VALUE 格式:String | ||||||
|  |      * 过期时间:不固定 | ||||||
|  |      */ | ||||||
|  |     private static final String SIGNATURE_NONCE = "signature_nonce:%s"; | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * 签名密钥 | ||||||
|  |      * <p> | ||||||
|  |      * KEY 格式:signature_appid:%s // 参数为 appid | ||||||
|  |      * VALUE 格式:String | ||||||
|  |      * 过期时间:预加载到 redis 永不过期 | ||||||
|  |      */ | ||||||
|  |     private static final String SIGNATURE_APPID = "signature_appid:%s"; | ||||||
|  |  | ||||||
|  |     public String getAppSecret(String appId) { | ||||||
|  |         return stringRedisTemplate.opsForValue().get(formatAppIdKey(appId)); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public String getNonce(String nonce) { | ||||||
|  |         return stringRedisTemplate.opsForValue().get(formatNonceKey(nonce)); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public void setNonce(String nonce, long time, TimeUnit timeUnit) { | ||||||
|  |         stringRedisTemplate.opsForValue().set(formatNonceKey(nonce), nonce, time, timeUnit); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private static String formatAppIdKey(String key) { | ||||||
|  |         return String.format(SIGNATURE_APPID, key); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private static String formatNonceKey(String key) { | ||||||
|  |         return String.format(SIGNATURE_NONCE, key); | ||||||
|  |     } | ||||||
|  | } | ||||||
| @ -1,3 +1,4 @@ | |||||||
| cn.iocoder.yudao.framework.idempotent.config.YudaoIdempotentConfiguration | cn.iocoder.yudao.framework.idempotent.config.YudaoIdempotentConfiguration | ||||||
| cn.iocoder.yudao.framework.lock4j.config.YudaoLock4jConfiguration | cn.iocoder.yudao.framework.lock4j.config.YudaoLock4jConfiguration | ||||||
| cn.iocoder.yudao.framework.ratelimiter.config.YudaoRateLimiterConfiguration | cn.iocoder.yudao.framework.ratelimiter.config.YudaoRateLimiterConfiguration | ||||||
|  | cn.iocoder.yudao.framework.signature.config.YudaoSignatureAutoConfiguration | ||||||
| @ -0,0 +1,136 @@ | |||||||
|  | package cn.iocoder.yudao.framework.signature.core; | ||||||
|  |  | ||||||
|  | import cn.hutool.core.lang.Snowflake; | ||||||
|  | import cn.hutool.crypto.digest.DigestUtil; | ||||||
|  | import cn.hutool.http.HttpRequest; | ||||||
|  | import cn.hutool.http.HttpResponse; | ||||||
|  | import cn.hutool.http.HttpUtil; | ||||||
|  | import org.junit.jupiter.api.Test; | ||||||
|  |  | ||||||
|  | import java.util.Map; | ||||||
|  | import java.util.SortedMap; | ||||||
|  | import java.util.TreeMap; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * {@link SignatureTest} 的单元测试 | ||||||
|  |  */ | ||||||
|  | public class SignatureTest { | ||||||
|  |  | ||||||
|  |     @Test | ||||||
|  |     public void testSignatureGet() { | ||||||
|  |         String appId = "xxxxxx"; | ||||||
|  |         Snowflake snowflake = new Snowflake(); | ||||||
|  |  | ||||||
|  |         // 验签请求头前端需传入字段 | ||||||
|  |         SortedMap<String, String> headersMap = new TreeMap<>(); | ||||||
|  |         headersMap.put("appId", appId); | ||||||
|  |         headersMap.put("timestamp", String.valueOf(System.currentTimeMillis())); | ||||||
|  |         headersMap.put("nonce", String.valueOf(snowflake.nextId())); | ||||||
|  |  | ||||||
|  |         // 客户端加签内容 | ||||||
|  |         StringBuilder clientSignatureContent = new StringBuilder(); | ||||||
|  |         // 请求头 | ||||||
|  |         for (Map.Entry<String, String> entry : headersMap.entrySet()) { | ||||||
|  |             clientSignatureContent.append(entry.getKey()).append(entry.getValue()); | ||||||
|  |         } | ||||||
|  |         // 请求 url | ||||||
|  |         clientSignatureContent.append("/admin-api/infra/demo01-contact/get"); | ||||||
|  |         // 请求参数 | ||||||
|  |         SortedMap<String, String> paramsMap = new TreeMap<>(); | ||||||
|  |         paramsMap.put("id", "100"); | ||||||
|  |         paramsMap.put("name", "张三"); | ||||||
|  |         StringBuilder queryString = new StringBuilder(); | ||||||
|  |         for (Map.Entry<String, String> entry : paramsMap.entrySet()) { | ||||||
|  |             queryString.append("&").append(entry.getKey()).append("=").append(entry.getValue()); | ||||||
|  |         } | ||||||
|  |         clientSignatureContent.append(queryString.substring(1)); | ||||||
|  |         // 密钥 | ||||||
|  |         clientSignatureContent.append("d3cbeed9baf4e68673a1f69a2445359a20022b7c28ea2933dd9db9f3a29f902b"); | ||||||
|  |         System.out.println("加签内容:" + clientSignatureContent); | ||||||
|  |         // 加签 | ||||||
|  |         headersMap.put("sign", DigestUtil.sha256Hex(clientSignatureContent.toString())); | ||||||
|  |         headersMap.put("Authorization", "Bearer xxx"); | ||||||
|  |  | ||||||
|  |         HttpRequest get = HttpUtil.createGet("http://localhost:48080/admin-api/infra/demo01-contact/get?id=100&name=张三"); | ||||||
|  |         get.addHeaders(headersMap); | ||||||
|  |         System.out.println("执行结果==" + get.execute()); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Test | ||||||
|  |     public void testSignaturePost() { | ||||||
|  |         String appId = "xxxxxx"; | ||||||
|  |         Snowflake snowflake = new Snowflake(); | ||||||
|  |  | ||||||
|  |         // 验签请求头前端需传入字段 | ||||||
|  |         SortedMap<String, String> headersMap = new TreeMap<>(); | ||||||
|  |         headersMap.put("appId", appId); | ||||||
|  |         headersMap.put("timestamp", String.valueOf(System.currentTimeMillis())); | ||||||
|  |         headersMap.put("nonce", String.valueOf(snowflake.nextId())); | ||||||
|  |  | ||||||
|  |         // 客户端加签内容 | ||||||
|  |         StringBuilder clientSignatureContent = new StringBuilder(); | ||||||
|  |         // 请求头 | ||||||
|  |         for (Map.Entry<String, String> entry : headersMap.entrySet()) { | ||||||
|  |             clientSignatureContent.append(entry.getKey()).append(entry.getValue()); | ||||||
|  |         } | ||||||
|  |         // 请求 url | ||||||
|  |         clientSignatureContent.append("/admin-api/infra/demo01-contact/create"); | ||||||
|  |         // 请求体 | ||||||
|  |         String body = "{\n" + | ||||||
|  |                 "    \"password\": \"1\",\n" + | ||||||
|  |                 "    \"date\": \"2024-04-24 16:28:00\",\n" + | ||||||
|  |                 "    \"user\": {\n" + | ||||||
|  |                 "        \"area\": \"浦东新区\",\n" + | ||||||
|  |                 "        \"1\": \"xx\",\n" + | ||||||
|  |                 "        \"2\": \"xx\",\n" + | ||||||
|  |                 "        \"province\": \"上海市\",\n" + | ||||||
|  |                 "        \"data\": {\n" + | ||||||
|  |                 "            \"99\": \"xx\",\n" + | ||||||
|  |                 "            \"1\": \"xx\",\n" + | ||||||
|  |                 "            \"100\": \"xx\",\n" + | ||||||
|  |                 "            \"2\": \"xx\",\n" + | ||||||
|  |                 "            \"3\": \"xx\",\n" + | ||||||
|  |                 "            \"array\": [\n" + | ||||||
|  |                 "                {\n" + | ||||||
|  |                 "                    \"3\": \"aa\",\n" + | ||||||
|  |                 "                    \"4\": \"aa\",\n" + | ||||||
|  |                 "                    \"2\": \"aa\",\n" + | ||||||
|  |                 "                    \"1\": \"aa\"\n" + | ||||||
|  |                 "                },\n" + | ||||||
|  |                 "                {\n" + | ||||||
|  |                 "                    \"99\": \"aa\",\n" + | ||||||
|  |                 "                    \"100\": \"aa\",\n" + | ||||||
|  |                 "                    \"88\": \"aa\",\n" + | ||||||
|  |                 "                    \"120\": \"aa\"\n" + | ||||||
|  |                 "                }\n" + | ||||||
|  |                 "            ]\n" + | ||||||
|  |                 "        },\n" + | ||||||
|  |                 "        \"sex\": \"男\",\n" + | ||||||
|  |                 "        \"name\": \"张三\",\n" + | ||||||
|  |                 "        \"array\": [\n" + | ||||||
|  |                 "            \"1\",\n" + | ||||||
|  |                 "            \"3\",\n" + | ||||||
|  |                 "            \"5\",\n" + | ||||||
|  |                 "            \"2\"\n" + | ||||||
|  |                 "        ]\n" + | ||||||
|  |                 "    },\n" + | ||||||
|  |                 "    \"username\": \"xiaoming\"\n" + | ||||||
|  |                 "}"; | ||||||
|  |         clientSignatureContent.append(body); | ||||||
|  |  | ||||||
|  |         // 密钥 | ||||||
|  |         clientSignatureContent.append("d3cbeed9baf4e68673a1f69a2445359a20022b7c28ea2933dd9db9f3a29f902b"); | ||||||
|  |         System.out.println("加签内容:" + clientSignatureContent); | ||||||
|  |         // 加签 | ||||||
|  |         headersMap.put("sign", DigestUtil.sha256Hex(clientSignatureContent.toString())); | ||||||
|  |         headersMap.put("Authorization", "Bearer xxx"); | ||||||
|  |  | ||||||
|  |         HttpRequest post = HttpUtil.createPost("http://localhost:48080/admin-api/infra/demo01-contact/create"); | ||||||
|  |         post.addHeaders(headersMap); | ||||||
|  |         post.body(body); | ||||||
|  |         try (HttpResponse execute = post.execute()) { | ||||||
|  |             System.out.println("执行结果==" + execute); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  | } | ||||||
| @ -28,8 +28,8 @@ public class FileContentDO extends BaseDO { | |||||||
|     /** |     /** | ||||||
|      * 编号,数据库自增 |      * 编号,数据库自增 | ||||||
|      */ |      */ | ||||||
|     @TableId(type = IdType.INPUT) |     @TableId | ||||||
|     private String id; |     private Long id; | ||||||
|     /** |     /** | ||||||
|      * 配置编号 |      * 配置编号 | ||||||
|      * |      * | ||||||
|  | |||||||
		Reference in New Issue
	
	Block a user
	 YunaiV
					YunaiV