Commit 7d925fddb9f63550b29ad511a0350d37f17c9f42
1 parent
e8bc3fd8
feat cashier mall init
Showing
31 changed files
with
1228 additions
and
13 deletions
cashier-boss/src/main/java/com/diligrp/cashier/boss/CashierServiceBootstrap.java
| 1 | package com.diligrp.cashier.boss; | 1 | package com.diligrp.cashier.boss; |
| 2 | 2 | ||
| 3 | import com.diligrp.cashier.assistant.AssistantConfiguration; | 3 | import com.diligrp.cashier.assistant.AssistantConfiguration; |
| 4 | +import com.diligrp.cashier.mall.MallConfiguration; | ||
| 4 | import com.diligrp.cashier.pipeline.PipelineConfiguration; | 5 | import com.diligrp.cashier.pipeline.PipelineConfiguration; |
| 5 | import com.diligrp.cashier.shared.SharedConfiguration; | 6 | import com.diligrp.cashier.shared.SharedConfiguration; |
| 6 | import org.springframework.boot.SpringApplication; | 7 | import org.springframework.boot.SpringApplication; |
| 7 | import org.springframework.boot.SpringBootConfiguration; | 8 | import org.springframework.boot.SpringBootConfiguration; |
| 8 | import org.springframework.boot.autoconfigure.EnableAutoConfiguration; | 9 | import org.springframework.boot.autoconfigure.EnableAutoConfiguration; |
| 9 | -import org.springframework.boot.autoconfigure.SpringBootApplication; | ||
| 10 | import org.springframework.cloud.client.discovery.EnableDiscoveryClient; | 10 | import org.springframework.cloud.client.discovery.EnableDiscoveryClient; |
| 11 | import org.springframework.context.annotation.Import; | 11 | import org.springframework.context.annotation.Import; |
| 12 | 12 | ||
| 13 | @SpringBootConfiguration | 13 | @SpringBootConfiguration |
| 14 | @EnableAutoConfiguration | 14 | @EnableAutoConfiguration |
| 15 | -@Import({BossConfiguration.class, PipelineConfiguration.class, AssistantConfiguration.class, SharedConfiguration.class}) | 15 | +@Import({BossConfiguration.class, PipelineConfiguration.class, |
| 16 | + AssistantConfiguration.class, SharedConfiguration.class, | ||
| 17 | + MallConfiguration.class | ||
| 18 | +}) | ||
| 16 | @EnableDiscoveryClient | 19 | @EnableDiscoveryClient |
| 17 | public class CashierServiceBootstrap { | 20 | public class CashierServiceBootstrap { |
| 18 | public static void main(String[] args) { | 21 | public static void main(String[] args) { |
| 19 | SpringApplication.run(CashierServiceBootstrap.class, args); | 22 | SpringApplication.run(CashierServiceBootstrap.class, args); |
| 20 | } | 23 | } |
| 21 | -} | ||
| 22 | \ No newline at end of file | 24 | \ No newline at end of file |
| 25 | +} |
cashier-boss/src/main/java/com/diligrp/cashier/boss/aop/LogPrintAop.java
0 → 100644
| 1 | +package com.diligrp.cashier.boss.aop; | ||
| 2 | + | ||
| 3 | +import com.diligrp.cashier.shared.annotation.ParamLogPrint; | ||
| 4 | +import com.diligrp.cashier.shared.util.JsonUtils; | ||
| 5 | +import jakarta.servlet.http.HttpServletRequest; | ||
| 6 | +import jakarta.servlet.http.HttpServletResponse; | ||
| 7 | +import org.apache.commons.lang3.BooleanUtils; | ||
| 8 | +import org.aspectj.lang.JoinPoint; | ||
| 9 | +import org.aspectj.lang.Signature; | ||
| 10 | +import org.aspectj.lang.annotation.AfterReturning; | ||
| 11 | +import org.aspectj.lang.annotation.Aspect; | ||
| 12 | +import org.aspectj.lang.annotation.Before; | ||
| 13 | +import org.slf4j.Logger; | ||
| 14 | +import org.slf4j.LoggerFactory; | ||
| 15 | +import org.springframework.core.annotation.Order; | ||
| 16 | +import org.springframework.stereotype.Component; | ||
| 17 | +import org.springframework.web.multipart.MultipartFile; | ||
| 18 | + | ||
| 19 | +import java.util.*; | ||
| 20 | + | ||
| 21 | +/** | ||
| 22 | + * @author dengwei | ||
| 23 | + * @version 1.0.0 | ||
| 24 | + * @ClassName LogPrintAspect.java | ||
| 25 | + * @Description 方法参数打印切面 | ||
| 26 | + */ | ||
| 27 | +@Component | ||
| 28 | +@Aspect | ||
| 29 | +@Order(0) | ||
| 30 | +public class LogPrintAop { | ||
| 31 | + private static final Logger log = LoggerFactory.getLogger(LogPrintAop.class); | ||
| 32 | + private static final String VERSION = "feat_1.0.0"; | ||
| 33 | + | ||
| 34 | + /** | ||
| 35 | + * 参数 | ||
| 36 | + */ | ||
| 37 | + @Before("@annotation(commonParamLogPrint)") | ||
| 38 | + public void paramLogPrint(JoinPoint joinPoint, ParamLogPrint commonParamLogPrint) { | ||
| 39 | + try { | ||
| 40 | + boolean print = commonParamLogPrint.print(); | ||
| 41 | + if (!print) { | ||
| 42 | + return; | ||
| 43 | + } | ||
| 44 | + Signature signature = joinPoint.getSignature(); | ||
| 45 | + // 使用下述方法需要开启--enable-preview预览功能 | ||
| 46 | + // String className = STR."\{signature.getDeclaringTypeName()}.\{signature.getName()}"; | ||
| 47 | + String className = signature.getDeclaringTypeName() + "." + signature.getName(); | ||
| 48 | + Object[] args = joinPoint.getArgs(); | ||
| 49 | + | ||
| 50 | + List<Object> argsList = this.listArgs(args); | ||
| 51 | + | ||
| 52 | + Map<String, Object> logInfo = new LinkedHashMap<>(2); | ||
| 53 | + logInfo.put("version", VERSION); | ||
| 54 | + logInfo.put("method", className); | ||
| 55 | + logInfo.put("args", argsList); | ||
| 56 | + log.info("{}:{}", commonParamLogPrint.desc(), JsonUtils.toJsonString(logInfo)); | ||
| 57 | + } catch (Exception exception) { | ||
| 58 | + log.warn("param log error"); | ||
| 59 | + } | ||
| 60 | + } | ||
| 61 | + | ||
| 62 | + /** | ||
| 63 | + * 处理完请求后执行 | ||
| 64 | + */ | ||
| 65 | + @AfterReturning(pointcut = "@annotation(print)", returning = "jsonResult") | ||
| 66 | + public void doAfterReturning(JoinPoint joinPoint, ParamLogPrint print, Object jsonResult) { | ||
| 67 | + try { | ||
| 68 | + if (BooleanUtils.isTrue(print.outPrint())) { | ||
| 69 | + log.warn("{} rsp:{}", print.desc(), JsonUtils.toJsonString(jsonResult)); | ||
| 70 | + } | ||
| 71 | + } catch (Exception e) { | ||
| 72 | + log.warn("after return error!"); | ||
| 73 | + } | ||
| 74 | + } | ||
| 75 | + | ||
| 76 | + /** | ||
| 77 | + * 参数列表 | ||
| 78 | + * | ||
| 79 | + * @param args args | ||
| 80 | + * @return {@link List}<{@link Object}> | ||
| 81 | + */ | ||
| 82 | + private List<Object> listArgs(Object[] args) { | ||
| 83 | + List<Object> argsList = new LinkedList<>(); | ||
| 84 | + for (Object arg : args) { | ||
| 85 | + Object param; | ||
| 86 | + if (arg instanceof HttpServletResponse) { | ||
| 87 | + param = HttpServletResponse.class.getSimpleName(); | ||
| 88 | + } else if (arg instanceof HttpServletRequest) { | ||
| 89 | + param = HttpServletRequest.class.getSimpleName(); | ||
| 90 | + } else if (arg instanceof MultipartFile) { | ||
| 91 | + param = MultipartFile.class.getSimpleName(); | ||
| 92 | + } else { | ||
| 93 | + param = arg; | ||
| 94 | + } | ||
| 95 | + if (Objects.nonNull(param)) { | ||
| 96 | + argsList.add(param); | ||
| 97 | + } | ||
| 98 | + } | ||
| 99 | + return argsList; | ||
| 100 | + } | ||
| 101 | +} |
cashier-mall/build.gradle
| 1 | dependencies { | 1 | dependencies { |
| 2 | implementation project(':cashier-shared') | 2 | implementation project(':cashier-shared') |
| 3 | implementation project(':cashier-trade') | 3 | implementation project(':cashier-trade') |
| 4 | -} | ||
| 5 | \ No newline at end of file | 4 | \ No newline at end of file |
| 5 | + implementation 'cn.hutool:hutool-http:5.8.42' | ||
| 6 | +} |
cashier-mall/src/main/java/com/diligrp/cashier/mall/MallConfiguration.java
0 → 100644
| 1 | +package com.diligrp.cashier.mall; | ||
| 2 | + | ||
| 3 | +import com.diligrp.cashier.shared.mybatis.MybatisMapperSupport; | ||
| 4 | +import org.mybatis.spring.annotation.MapperScan; | ||
| 5 | +import org.springframework.context.annotation.ComponentScan; | ||
| 6 | +import org.springframework.context.annotation.Configuration; | ||
| 7 | + | ||
| 8 | +@Configuration | ||
| 9 | +@ComponentScan("com.diligrp.cashier.mall") | ||
| 10 | +@MapperScan(basePackages = {"com.diligrp.cashier.mall.dao"}, markerInterface = MybatisMapperSupport.class) | ||
| 11 | +public class MallConfiguration { | ||
| 12 | + | ||
| 13 | +} |
cashier-mall/src/main/java/com/diligrp/cashier/mall/MallConstants.java
0 → 100644
cashier-mall/src/main/java/com/diligrp/cashier/mall/api/TestRtMallApi.java
0 → 100644
| 1 | +package com.diligrp.cashier.mall.api; | ||
| 2 | + | ||
| 3 | +import com.diligrp.cashier.mall.sign.RtMallSign; | ||
| 4 | +import com.diligrp.cashier.shared.annotation.Sign; | ||
| 5 | +import org.slf4j.Logger; | ||
| 6 | +import org.slf4j.LoggerFactory; | ||
| 7 | +import org.springframework.web.bind.annotation.RequestBody; | ||
| 8 | +import org.springframework.web.bind.annotation.RequestMapping; | ||
| 9 | +import org.springframework.web.bind.annotation.RestController; | ||
| 10 | + | ||
| 11 | +/** | ||
| 12 | + * @ClassName TestRtMallApi.java | ||
| 13 | + * @author dengwei | ||
| 14 | + * @version 1.0.0 | ||
| 15 | + * @Description TestRtMallApi | ||
| 16 | + */ | ||
| 17 | +@RestController | ||
| 18 | +@RequestMapping("/test") | ||
| 19 | +public class TestRtMallApi { | ||
| 20 | + private static final Logger log = LoggerFactory.getLogger(TestRtMallApi.class); | ||
| 21 | + | ||
| 22 | + @RequestMapping("/sign") | ||
| 23 | + @Sign(sign = RtMallSign.class) | ||
| 24 | + public String rtMall(@RequestBody Object request) { | ||
| 25 | + log.info("rt mall"); | ||
| 26 | + return "rt mall"; | ||
| 27 | + } | ||
| 28 | +} |
cashier-mall/src/main/java/com/diligrp/cashier/mall/dao/package-info.java
0 → 100644
cashier-mall/src/main/java/com/diligrp/cashier/mall/domain/RtMarkMessage.java
0 → 100644
| 1 | +package com.diligrp.cashier.mall.domain; | ||
| 2 | + | ||
| 3 | +import com.diligrp.cashier.mall.MallConstants; | ||
| 4 | +import com.diligrp.cashier.mall.type.RtMarkErrorCode; | ||
| 5 | + | ||
| 6 | +/** | ||
| 7 | + * 大润发返回消息 | ||
| 8 | + * | ||
| 9 | + * @author dengwei | ||
| 10 | + * @date 2025/12/24 18:05 | ||
| 11 | + */ | ||
| 12 | +public class RtMarkMessage<T> { | ||
| 13 | + private String result; | ||
| 14 | + private String code; | ||
| 15 | + private String msg; | ||
| 16 | + private T data; | ||
| 17 | + | ||
| 18 | + public RtMarkMessage() { | ||
| 19 | + } | ||
| 20 | + | ||
| 21 | + public String getResult() { | ||
| 22 | + return result; | ||
| 23 | + } | ||
| 24 | + | ||
| 25 | + public void setResult(String result) { | ||
| 26 | + this.result = result; | ||
| 27 | + } | ||
| 28 | + | ||
| 29 | + public String getCode() { | ||
| 30 | + return code; | ||
| 31 | + } | ||
| 32 | + | ||
| 33 | + public void setCode(String code) { | ||
| 34 | + this.code = code; | ||
| 35 | + } | ||
| 36 | + | ||
| 37 | + public String getMsg() { | ||
| 38 | + return msg; | ||
| 39 | + } | ||
| 40 | + | ||
| 41 | + public void setMsg(String msg) { | ||
| 42 | + this.msg = msg; | ||
| 43 | + } | ||
| 44 | + | ||
| 45 | + public T getData() { | ||
| 46 | + return data; | ||
| 47 | + } | ||
| 48 | + | ||
| 49 | + public void setData(T data) { | ||
| 50 | + this.data = data; | ||
| 51 | + } | ||
| 52 | + | ||
| 53 | + public static RtMarkMessage<?> success() { | ||
| 54 | + return failure(RtMarkErrorCode.E0000.getCode(), MallConstants.RESULT_SUCCESS); | ||
| 55 | + } | ||
| 56 | + | ||
| 57 | + public static <E> RtMarkMessage<E> success(E data) { | ||
| 58 | + RtMarkMessage<E> result = new RtMarkMessage<>(); | ||
| 59 | + result.code = RtMarkErrorCode.E0000.getCode(); | ||
| 60 | + result.result = MallConstants.RESULT_SUCCESS; | ||
| 61 | + result.data = data; | ||
| 62 | + return result; | ||
| 63 | + } | ||
| 64 | + | ||
| 65 | + public static RtMarkMessage<?> failure(String message) { | ||
| 66 | + return failure(RtMarkErrorCode.E5000.getCode(), message); | ||
| 67 | + } | ||
| 68 | + | ||
| 69 | + public static RtMarkMessage<?> failure(String code, String message) { | ||
| 70 | + RtMarkMessage<?> result = new RtMarkMessage<>(); | ||
| 71 | + result.setResult(MallConstants.RESULT_FAILURE); | ||
| 72 | + result.code = code; | ||
| 73 | + result.msg = message; | ||
| 74 | + return result; | ||
| 75 | + } | ||
| 76 | +} |
cashier-mall/src/main/java/com/diligrp/cashier/mall/exception/MallException.java
0 → 100644
| 1 | +package com.diligrp.cashier.mall.exception; | ||
| 2 | + | ||
| 3 | + | ||
| 4 | +import com.diligrp.cashier.shared.ErrorCode; | ||
| 5 | +import com.diligrp.cashier.shared.exception.PlatformServiceException; | ||
| 6 | + | ||
| 7 | +/** | ||
| 8 | + * @ClassName MallException.java | ||
| 9 | + * @author dengwei | ||
| 10 | + * @version 1.0.0 | ||
| 11 | + * @date 2025-12-25 14:34 | ||
| 12 | + */ | ||
| 13 | +public class MallException extends PlatformServiceException { | ||
| 14 | + | ||
| 15 | + public MallException(String message) { | ||
| 16 | + super(ErrorCode.SYSTEM_UNKNOWN_ERROR, message); | ||
| 17 | + } | ||
| 18 | + | ||
| 19 | + public MallException(String code, String message) { | ||
| 20 | + super(code, message); | ||
| 21 | + } | ||
| 22 | +} |
cashier-mall/src/main/java/com/diligrp/cashier/mall/exception/MallExceptionHandler.java
0 → 100644
| 1 | +package com.diligrp.cashier.mall.exception; | ||
| 2 | + | ||
| 3 | +import com.diligrp.cashier.mall.domain.RtMarkMessage; | ||
| 4 | +import org.slf4j.Logger; | ||
| 5 | +import org.slf4j.LoggerFactory; | ||
| 6 | +import org.springframework.core.annotation.Order; | ||
| 7 | +import org.springframework.web.bind.annotation.ExceptionHandler; | ||
| 8 | +import org.springframework.web.bind.annotation.RestControllerAdvice; | ||
| 9 | + | ||
| 10 | +/** | ||
| 11 | + * 商城独立处理器 | ||
| 12 | + * 优先于默认 | ||
| 13 | + * 大润发方已经确定好了一套返回标准 | ||
| 14 | + * | ||
| 15 | + * @author dengwei | ||
| 16 | + */ | ||
| 17 | +@RestControllerAdvice | ||
| 18 | +@Order(-1) | ||
| 19 | +public class MallExceptionHandler { | ||
| 20 | + private final Logger LOG = LoggerFactory.getLogger(this.getClass()); | ||
| 21 | + | ||
| 22 | + @ExceptionHandler(RtMartMallException.class) | ||
| 23 | + public RtMarkMessage<?> rtMartMallException(RtMartMallException ex) { | ||
| 24 | + LOG.warn("rt mart mall exception", ex); | ||
| 25 | + return RtMarkMessage.failure(ex.getCode(), ex.getMessage()); | ||
| 26 | + } | ||
| 27 | +} |
cashier-mall/src/main/java/com/diligrp/cashier/mall/exception/RtMartMallException.java
0 → 100644
| 1 | +package com.diligrp.cashier.mall.exception; | ||
| 2 | + | ||
| 3 | +import com.diligrp.cashier.mall.MallConstants; | ||
| 4 | +import com.diligrp.cashier.mall.type.RtMarkErrorCode; | ||
| 5 | +import com.diligrp.cashier.shared.ErrorCode; | ||
| 6 | + | ||
| 7 | +/** | ||
| 8 | + * @ClassName RtmartMallException.java | ||
| 9 | + * @author dengwei | ||
| 10 | + * @version 1.0.0 | ||
| 11 | + * @Description 大润发异常 | ||
| 12 | + * @date 2025-12-24 18:05 | ||
| 13 | + */ | ||
| 14 | +public class RtMartMallException extends MallException { | ||
| 15 | + public RtMartMallException(String message) { | ||
| 16 | + super(RtMarkErrorCode.E5000.getCode(), message); | ||
| 17 | + } | ||
| 18 | + | ||
| 19 | + public RtMartMallException(int code) { | ||
| 20 | + super(RtMarkErrorCode.E5000.getCode(), ErrorCode.MESSAGE_SYSTEM_BUSY); | ||
| 21 | + } | ||
| 22 | + | ||
| 23 | + public RtMartMallException(String code, String message) { | ||
| 24 | + super(code, message); | ||
| 25 | + } | ||
| 26 | +} |
cashier-mall/src/main/java/com/diligrp/cashier/mall/property/MallDynamicProperty.java
0 → 100644
| 1 | +package com.diligrp.cashier.mall.property; | ||
| 2 | + | ||
| 3 | +import org.springframework.beans.factory.annotation.Value; | ||
| 4 | +import org.springframework.boot.context.properties.ConfigurationProperties; | ||
| 5 | +import org.springframework.cloud.context.config.annotation.RefreshScope; | ||
| 6 | +import org.springframework.context.annotation.Configuration; | ||
| 7 | + | ||
| 8 | +import java.util.Collections; | ||
| 9 | +import java.util.List; | ||
| 10 | +import java.util.Optional; | ||
| 11 | + | ||
| 12 | +/** | ||
| 13 | + * @author dengwei | ||
| 14 | + * @version 1.0.0 | ||
| 15 | + * @ClassName MallDynamicProperty.java | ||
| 16 | + * @Description MallDynamicProperty | ||
| 17 | + */ | ||
| 18 | +@Configuration | ||
| 19 | +@RefreshScope | ||
| 20 | +@ConfigurationProperties(prefix = "sign") | ||
| 21 | +public class MallDynamicProperty { | ||
| 22 | + | ||
| 23 | + private List<AppSecretDynamicProperty> appSecrets; | ||
| 24 | + | ||
| 25 | + public static class AppSecretDynamicProperty { | ||
| 26 | + @Value("${source:1}") | ||
| 27 | + private Integer source; | ||
| 28 | + | ||
| 29 | + @Value("${appKey:}") | ||
| 30 | + private String appKey; | ||
| 31 | + | ||
| 32 | + @Value("${appSecret:}") | ||
| 33 | + private String appSecret; | ||
| 34 | + | ||
| 35 | + @Value("${authUrl:https://hourh5-em-shop.feiniugo.com}") | ||
| 36 | + private String authUrl; | ||
| 37 | + | ||
| 38 | + public Integer getSource() { | ||
| 39 | + return source; | ||
| 40 | + } | ||
| 41 | + | ||
| 42 | + public void setSource(Integer source) { | ||
| 43 | + this.source = source; | ||
| 44 | + } | ||
| 45 | + | ||
| 46 | + public String getAppKey() { | ||
| 47 | + return appKey; | ||
| 48 | + } | ||
| 49 | + | ||
| 50 | + public void setAppKey(String appKey) { | ||
| 51 | + this.appKey = appKey; | ||
| 52 | + } | ||
| 53 | + | ||
| 54 | + public String getAppSecret() { | ||
| 55 | + return appSecret; | ||
| 56 | + } | ||
| 57 | + | ||
| 58 | + public void setAppSecret(String appSecret) { | ||
| 59 | + this.appSecret = appSecret; | ||
| 60 | + } | ||
| 61 | + | ||
| 62 | + public String getAuthUrl() { | ||
| 63 | + return authUrl; | ||
| 64 | + } | ||
| 65 | + | ||
| 66 | + public void setAuthUrl(String authUrl) { | ||
| 67 | + this.authUrl = authUrl; | ||
| 68 | + } | ||
| 69 | + } | ||
| 70 | + | ||
| 71 | + public List<AppSecretDynamicProperty> getAppSecrets() { | ||
| 72 | + return appSecrets; | ||
| 73 | + } | ||
| 74 | + | ||
| 75 | + public void setAppSecrets(List<AppSecretDynamicProperty> appSecrets) { | ||
| 76 | + this.appSecrets = appSecrets; | ||
| 77 | + } | ||
| 78 | + | ||
| 79 | + /** | ||
| 80 | + * getBySource | ||
| 81 | + * | ||
| 82 | + */ | ||
| 83 | + public AppSecretDynamicProperty getBySource(Integer source) { | ||
| 84 | + return Optional.ofNullable(appSecrets) | ||
| 85 | + .orElse(Collections.emptyList()) | ||
| 86 | + .stream() | ||
| 87 | + .filter(item -> item.getSource().equals(source)) | ||
| 88 | + .findFirst() | ||
| 89 | + .orElse(null); | ||
| 90 | + } | ||
| 91 | +} |
cashier-mall/src/main/java/com/diligrp/cashier/mall/sign/RtMallSign.java
0 → 100644
| 1 | +package com.diligrp.cashier.mall.sign; | ||
| 2 | + | ||
| 3 | +import com.diligrp.cashier.mall.MallConstants; | ||
| 4 | +import com.diligrp.cashier.mall.exception.RtMartMallException; | ||
| 5 | +import com.diligrp.cashier.mall.property.MallDynamicProperty; | ||
| 6 | +import com.diligrp.cashier.mall.type.RtMarkErrorCode; | ||
| 7 | +import com.diligrp.cashier.mall.util.RtMallSignMd5Utils; | ||
| 8 | +import com.diligrp.cashier.shared.handler.sign.SecuritySign; | ||
| 9 | +import com.diligrp.cashier.shared.util.JsonUtils; | ||
| 10 | +import com.fasterxml.jackson.core.type.TypeReference; | ||
| 11 | +import jakarta.annotation.Resource; | ||
| 12 | +import jakarta.servlet.http.HttpServletRequest; | ||
| 13 | +import jakarta.servlet.http.HttpServletResponse; | ||
| 14 | +import org.slf4j.Logger; | ||
| 15 | +import org.slf4j.LoggerFactory; | ||
| 16 | +import org.springframework.stereotype.Component; | ||
| 17 | +import org.springframework.util.Assert; | ||
| 18 | + | ||
| 19 | +import java.util.Objects; | ||
| 20 | +import java.util.TreeMap; | ||
| 21 | + | ||
| 22 | +/** | ||
| 23 | + * @author dengwei | ||
| 24 | + * @version 1.0.0 | ||
| 25 | + * @ClassName RtMallSign.java | ||
| 26 | + * @Description RtMallSign | ||
| 27 | + * 大润发参数验签 | ||
| 28 | + */ | ||
| 29 | +@Component | ||
| 30 | +public class RtMallSign implements SecuritySign { | ||
| 31 | + private static final Logger log = LoggerFactory.getLogger(RtMallSign.class); | ||
| 32 | + | ||
| 33 | + @Resource | ||
| 34 | + private MallDynamicProperty mallDynamicProperty; | ||
| 35 | + | ||
| 36 | + @Override | ||
| 37 | + public void sign(HttpServletRequest request, HttpServletResponse response, Object data) { | ||
| 38 | + log.info("allParameters:{}", JsonUtils.toJsonString(data)); | ||
| 39 | + | ||
| 40 | + TreeMap<String, Object> paramMap = JsonUtils.fromJsonString(JsonUtils.toJsonString(data), new TypeReference<>() { | ||
| 41 | + }); | ||
| 42 | + | ||
| 43 | + Object appKey = paramMap.get("app_key"); | ||
| 44 | + if (Objects.isNull(appKey)) { | ||
| 45 | + throw new RtMartMallException(RtMarkErrorCode.E4003.getCode(), RtMarkErrorCode.E4003.getMessage()); | ||
| 46 | + } | ||
| 47 | + | ||
| 48 | + MallDynamicProperty.AppSecretDynamicProperty property = mallDynamicProperty.getBySource(MallConstants.RT_MALL_SOURCE); | ||
| 49 | + if (Objects.isNull(property)) { | ||
| 50 | + throw new RtMartMallException(RtMarkErrorCode.E4001.getCode(), RtMarkErrorCode.E4001.getMessage()); | ||
| 51 | + } | ||
| 52 | + | ||
| 53 | + try { | ||
| 54 | + log.info("appKey:{}, secretKey:{}", property.getAppKey(), property.getAppSecret()); | ||
| 55 | + String signKey = RtMallSignMd5Utils.generateSign(paramMap, property.getAppSecret()); | ||
| 56 | + Assert.isTrue(Objects.equals(signKey, paramMap.get("sign").toString()), "验签失败!"); | ||
| 57 | + } catch (Exception e) { | ||
| 58 | + throw new RtMartMallException(RtMarkErrorCode.E4004.getCode(), RtMarkErrorCode.E4004.getMessage()); | ||
| 59 | + } | ||
| 60 | + } | ||
| 61 | +} |
cashier-mall/src/main/java/com/diligrp/cashier/mall/type/RtMarkErrorCode.java
0 → 100644
| 1 | +package com.diligrp.cashier.mall.type; | ||
| 2 | + | ||
| 3 | +/** | ||
| 4 | + * @ClassName RtmarkErrorCode.java | ||
| 5 | + * @author dengwei | ||
| 6 | + * @version 1.0.0 | ||
| 7 | + * @Description RtmarkErrorCode | ||
| 8 | + * @date 2025-12-23 18:25 | ||
| 9 | + */ | ||
| 10 | +public enum RtMarkErrorCode { | ||
| 11 | + E0000("E0000", "成功"), | ||
| 12 | + E4001("E4001", "验签缺少参数"), | ||
| 13 | + E4002("E4002", "timestamp 不合法"), | ||
| 14 | + E4003("E4003", "app_key 错误"), | ||
| 15 | + E4004("E4004", "签名错误"), | ||
| 16 | + E5000("E5000", "未知错误"); | ||
| 17 | + | ||
| 18 | + private final String code; | ||
| 19 | + private final String message; | ||
| 20 | + | ||
| 21 | + RtMarkErrorCode(String code, String message) { | ||
| 22 | + this.code = code; | ||
| 23 | + this.message = message; | ||
| 24 | + } | ||
| 25 | + | ||
| 26 | + public String getCode() { | ||
| 27 | + return code; | ||
| 28 | + } | ||
| 29 | + | ||
| 30 | + public String getMessage() { | ||
| 31 | + return message; | ||
| 32 | + } | ||
| 33 | +} |
cashier-mall/src/main/java/com/diligrp/cashier/mall/util/HttpClientUtils.java
0 → 100644
| 1 | +package com.diligrp.cashier.mall.util; | ||
| 2 | + | ||
| 3 | +import cn.hutool.core.util.IdUtil; | ||
| 4 | +import cn.hutool.http.HttpException; | ||
| 5 | +import cn.hutool.http.HttpRequest; | ||
| 6 | +import com.diligrp.cashier.mall.exception.MallException; | ||
| 7 | +import com.diligrp.cashier.shared.util.JsonUtils; | ||
| 8 | +import com.fasterxml.jackson.core.type.TypeReference; | ||
| 9 | +import org.apache.commons.lang3.StringUtils; | ||
| 10 | +import org.slf4j.Logger; | ||
| 11 | +import org.slf4j.LoggerFactory; | ||
| 12 | +import org.springframework.http.MediaType; | ||
| 13 | + | ||
| 14 | +import java.util.Map; | ||
| 15 | + | ||
| 16 | +/** | ||
| 17 | + * @author dengwei | ||
| 18 | + * @version 1.0.0 | ||
| 19 | + * @ClassName HttpClientUtils.java | ||
| 20 | + * @Description HttpClientUtils | ||
| 21 | + * @date 2025-12-24 14:54 | ||
| 22 | + */ | ||
| 23 | +public class HttpClientUtils { | ||
| 24 | + private static final Logger log = LoggerFactory.getLogger(HttpClientUtils.class); | ||
| 25 | + | ||
| 26 | + /** | ||
| 27 | + * postJson | ||
| 28 | + */ | ||
| 29 | + public static String postJson(String url, | ||
| 30 | + Object body, | ||
| 31 | + Map<String, String> header, | ||
| 32 | + String serviceName) { | ||
| 33 | + | ||
| 34 | + try { | ||
| 35 | + String traceId = IdUtil.fastSimpleUUID(); | ||
| 36 | + log.info("traceId:{},url:{}, body:{}", traceId, url, JsonUtils.toJsonString(body)); | ||
| 37 | + String responseBody = HttpRequest | ||
| 38 | + .post(url) | ||
| 39 | + .header("content-type", MediaType.APPLICATION_JSON_VALUE) | ||
| 40 | + .addHeaders(header) | ||
| 41 | + .timeout(60000) | ||
| 42 | + .body(JsonUtils.toJsonString(body)) | ||
| 43 | + .execute() | ||
| 44 | + .body(); | ||
| 45 | + if (StringUtils.isBlank(responseBody)) { | ||
| 46 | + return null; | ||
| 47 | + } | ||
| 48 | + log.info("traceId:{},responseBody:{}", traceId, responseBody); | ||
| 49 | + return responseBody; | ||
| 50 | + } catch (HttpException e) { | ||
| 51 | + log.error("http error url:{}", url, e); | ||
| 52 | + throw new MallException(serviceName + "服务异常!"); | ||
| 53 | + } | ||
| 54 | + } | ||
| 55 | + | ||
| 56 | + /** | ||
| 57 | + * postJson | ||
| 58 | + */ | ||
| 59 | + public static <R> R postJson(String url, | ||
| 60 | + Object body, | ||
| 61 | + Map<String, String> header, | ||
| 62 | + TypeReference<R> jsonTypeReference, | ||
| 63 | + String serviceName) { | ||
| 64 | + String responseBody = postJson(url, body, header, serviceName); | ||
| 65 | + if (StringUtils.isBlank(responseBody)) { | ||
| 66 | + return null; | ||
| 67 | + } | ||
| 68 | + return JsonUtils.fromJsonString(responseBody, jsonTypeReference); | ||
| 69 | + } | ||
| 70 | +} |
cashier-mall/src/main/java/com/diligrp/cashier/mall/util/RtMallSignMd5Utils.java
0 → 100644
| 1 | +package com.diligrp.cashier.mall.util; | ||
| 2 | + | ||
| 3 | +import com.diligrp.cashier.shared.util.JsonUtils; | ||
| 4 | +import com.fasterxml.jackson.core.type.TypeReference; | ||
| 5 | +import org.slf4j.Logger; | ||
| 6 | +import org.slf4j.LoggerFactory; | ||
| 7 | + | ||
| 8 | +import java.security.MessageDigest; | ||
| 9 | +import java.util.*; | ||
| 10 | + | ||
| 11 | +/** | ||
| 12 | + * @ClassName SignMd5Utils.java | ||
| 13 | + * @author dengwei | ||
| 14 | + * @version 1.0.0 | ||
| 15 | + * @Description | ||
| 16 | + * 签名算法 | ||
| 17 | + * 参考大润发定义:https://shopex.yuque.com/hl0rrx/vlp0m4/kmcazgnimg28d8ih?singleDoc# | ||
| 18 | + */ | ||
| 19 | +public class RtMallSignMd5Utils { | ||
| 20 | + private static final Logger log = LoggerFactory.getLogger(RtMallSignMd5Utils.class); | ||
| 21 | + | ||
| 22 | + /** | ||
| 23 | + * 生成签名 | ||
| 24 | + * | ||
| 25 | + */ | ||
| 26 | + public static String generateSign(Object data, String appSecret) { | ||
| 27 | + Map<String, Object> params = JsonUtils.convertValueV2(data, new TypeReference<>() { | ||
| 28 | + }); | ||
| 29 | + // 1. 过滤 null,转换 boolean,递归规范化并排序 | ||
| 30 | + String normalized = normalizeAndSort(params); | ||
| 31 | + log.info("normalize: {}", normalized); | ||
| 32 | + // 2. 拼接私钥 | ||
| 33 | + String signSource = appSecret + normalized + appSecret; | ||
| 34 | + log.info("signSource: {}", signSource); | ||
| 35 | + // 3. MD5 + 大写 | ||
| 36 | + String sign = md5ToUpper(signSource); | ||
| 37 | + log.info("sign: {}", sign); | ||
| 38 | + return sign; | ||
| 39 | + } | ||
| 40 | + | ||
| 41 | + /** | ||
| 42 | + * 递归规范化并排序,返回拼接字符串(无分隔符) | ||
| 43 | + */ | ||
| 44 | + @SuppressWarnings("unchecked") | ||
| 45 | + private static String normalizeAndSort(Object obj) { | ||
| 46 | + switch (obj) { | ||
| 47 | + case null -> { | ||
| 48 | + return ""; | ||
| 49 | + } | ||
| 50 | + case Map map1 -> { | ||
| 51 | + Map<String, Object> map = (Map<String, Object>) obj; | ||
| 52 | + // 过滤 null,并转换 boolean | ||
| 53 | + Map<String, Object> filtered = new LinkedHashMap<>(); | ||
| 54 | + for (Map.Entry<String, Object> entry : map.entrySet()) { | ||
| 55 | + if (entry.getValue() == null) { | ||
| 56 | + continue; | ||
| 57 | + } | ||
| 58 | + Object val = convertBoolean(entry.getValue()); | ||
| 59 | + filtered.put(entry.getKey(), val); | ||
| 60 | + } | ||
| 61 | + // 按 key 字典序排序 | ||
| 62 | + List<String> sortedKeys = new ArrayList<>(filtered.keySet()); | ||
| 63 | + Collections.sort(sortedKeys); // 字典序 | ||
| 64 | + | ||
| 65 | + | ||
| 66 | + StringBuilder sb = new StringBuilder(); | ||
| 67 | + for (String key : sortedKeys) { | ||
| 68 | + sb.append(key).append(normalizeAndSort(filtered.get(key))); | ||
| 69 | + } | ||
| 70 | + return sb.toString(); | ||
| 71 | + } | ||
| 72 | + case List list1 -> { | ||
| 73 | + List<Object> list = (List<Object>) obj; | ||
| 74 | + // 转为 Map<String, Object>,key 为索引字符串 | ||
| 75 | + Map<String, Object> indexMap = new LinkedHashMap<>(); | ||
| 76 | + for (int i = 0; i < list.size(); i++) { | ||
| 77 | + indexMap.put(String.valueOf(i), list.get(i)); | ||
| 78 | + } | ||
| 79 | + // 递归处理(会自动过滤 null、转换 boolean、排序 key) | ||
| 80 | + return normalizeAndSort(indexMap); | ||
| 81 | + // 递归处理(会自动过滤 null、转换 boolean、排序 key) | ||
| 82 | + } | ||
| 83 | + case Object[] array -> { | ||
| 84 | + // 处理数组类型,转换为 Map 并递归处理 | ||
| 85 | + Map<String, Object> arrayMap = new LinkedHashMap<>(); | ||
| 86 | + for (int i = 0; i < array.length; i++) { | ||
| 87 | + arrayMap.put(String.valueOf(i), array[i]); | ||
| 88 | + } | ||
| 89 | + return normalizeAndSort(arrayMap); | ||
| 90 | + } | ||
| 91 | + default -> { | ||
| 92 | + } | ||
| 93 | + } | ||
| 94 | + | ||
| 95 | + // 基本类型:转字符串 | ||
| 96 | + return obj.toString(); | ||
| 97 | + } | ||
| 98 | + | ||
| 99 | + /** | ||
| 100 | + * 转换 Boolean 为 "1"/"0" | ||
| 101 | + */ | ||
| 102 | + private static Object convertBoolean(Object obj) { | ||
| 103 | + if (obj instanceof Boolean) { | ||
| 104 | + return (Boolean) obj ? "1" : "0"; | ||
| 105 | + } | ||
| 106 | + return obj; | ||
| 107 | + } | ||
| 108 | + | ||
| 109 | + /** | ||
| 110 | + * MD5 并转大写 | ||
| 111 | + */ | ||
| 112 | + private static String md5ToUpper(String input) { | ||
| 113 | + try { | ||
| 114 | + MessageDigest md = MessageDigest.getInstance("MD5"); | ||
| 115 | + byte[] digest = md.digest(input.getBytes("UTF-8")); | ||
| 116 | + StringBuilder sb = new StringBuilder(); | ||
| 117 | + for (byte b : digest) { | ||
| 118 | + sb.append(String.format("%02x", b)); | ||
| 119 | + } | ||
| 120 | + return sb.toString().toUpperCase(); | ||
| 121 | + } catch (Exception e) { | ||
| 122 | + throw new RuntimeException("MD5 failed", e); | ||
| 123 | + } | ||
| 124 | + } | ||
| 125 | + | ||
| 126 | + public static void main(String[] args) { | ||
| 127 | + // 构建顶层参数 Map | ||
| 128 | + Map<String, Object> params = new HashMap<>(); | ||
| 129 | + | ||
| 130 | + params.put("app_key", "221zzcwxee30nucj0rx1"); | ||
| 131 | + params.put("timestamp", 1734351194L); // 使用 Long 避免溢出 | ||
| 132 | + params.put("nonce_str", "cvwEv8JRNzHd3eaV"); | ||
| 133 | + params.put("order_id", "1234567890"); // order_id 可以是字符串或数字,这里按字符串处理更安全 | ||
| 134 | + | ||
| 135 | + // 构建 item_list | ||
| 136 | + List<Map<String, Object>> itemList = new ArrayList<>(); | ||
| 137 | + | ||
| 138 | + for (int i = 1; i <= 11; i++) { | ||
| 139 | + Map<String, Object> subOrder = new HashMap<>(); | ||
| 140 | + subOrder.put("sub_order_id", i); | ||
| 141 | + subOrder.put("price", 100); | ||
| 142 | + itemList.add(subOrder); | ||
| 143 | + } | ||
| 144 | + params.put("item_list", itemList); | ||
| 145 | + | ||
| 146 | + // 打印验证(可选) | ||
| 147 | + log.info("Params: {}", JsonUtils.toJsonString(params)); | ||
| 148 | + | ||
| 149 | + String sign = generateSign(params, "ho7ygrsrwged4zwhwpbadtdgzugmulez"); | ||
| 150 | + log.info("Sign: {}", sign); | ||
| 151 | + } | ||
| 152 | +} |
cashier-mall/src/main/resources/package.json
0 → 100644
| 1 | +{ | ||
| 2 | + "name": "resources", | ||
| 3 | + "version": "1.0.0", | ||
| 4 | + "description": "", | ||
| 5 | + "main": "index.js", | ||
| 6 | + "scripts": { | ||
| 7 | + "test": "echo \"Error: no test specified\" && exit 1" | ||
| 8 | + }, | ||
| 9 | + "repository": { | ||
| 10 | + "type": "git", | ||
| 11 | + "url": "https://git3.nong12.com/cashierdesk/dili-cashier.git" | ||
| 12 | + }, | ||
| 13 | + "private": true | ||
| 14 | +} |
cashier-shared/build.gradle
| @@ -6,6 +6,7 @@ dependencies { | @@ -6,6 +6,7 @@ dependencies { | ||
| 6 | api 'org.springframework.boot:spring-boot-starter-amqp' | 6 | api 'org.springframework.boot:spring-boot-starter-amqp' |
| 7 | api 'org.springframework.cloud:spring-cloud-starter-loadbalancer' | 7 | api 'org.springframework.cloud:spring-cloud-starter-loadbalancer' |
| 8 | api 'org.springframework.cloud:spring-cloud-starter-openfeign' | 8 | api 'org.springframework.cloud:spring-cloud-starter-openfeign' |
| 9 | + api 'org.springframework.boot:spring-boot-starter-aop' | ||
| 9 | api libs.cache.caffeine | 10 | api libs.cache.caffeine |
| 10 | api libs.common.collections4 | 11 | api libs.common.collections4 |
| 11 | 12 |
cashier-shared/src/main/java/com/diligrp/cashier/shared/annotation/ParamLogPrint.java
0 → 100644
| 1 | +package com.diligrp.cashier.shared.annotation; | ||
| 2 | + | ||
| 3 | +import java.lang.annotation.ElementType; | ||
| 4 | +import java.lang.annotation.Retention; | ||
| 5 | +import java.lang.annotation.RetentionPolicy; | ||
| 6 | +import java.lang.annotation.Target; | ||
| 7 | + | ||
| 8 | +/** | ||
| 9 | + * @author dengwei | ||
| 10 | + * @version 1.0.0 | ||
| 11 | + * @ClassName LogPrint.java | ||
| 12 | + * @Description 参数日志是否打印 | ||
| 13 | + */ | ||
| 14 | +@Target({ElementType.METHOD}) | ||
| 15 | +@Retention(RetentionPolicy.RUNTIME) | ||
| 16 | +public @interface ParamLogPrint { | ||
| 17 | + /** | ||
| 18 | + * 使用该注解默认打印 | ||
| 19 | + * | ||
| 20 | + * @return boolean | ||
| 21 | + */ | ||
| 22 | + boolean print() default true; | ||
| 23 | + | ||
| 24 | + /** | ||
| 25 | + * desc | ||
| 26 | + * 触发点:HTTP MQ and so on | ||
| 27 | + * | ||
| 28 | + * @return {@link String} | ||
| 29 | + */ | ||
| 30 | + String desc() default "HTTP接口"; | ||
| 31 | + | ||
| 32 | + /** | ||
| 33 | + * outPrint | ||
| 34 | + */ | ||
| 35 | + boolean outPrint() default false; | ||
| 36 | +} |
cashier-shared/src/main/java/com/diligrp/cashier/shared/annotation/RepeatSubmit.java
0 → 100644
| 1 | +package com.diligrp.cashier.shared.annotation; | ||
| 2 | + | ||
| 3 | + | ||
| 4 | +import com.diligrp.cashier.shared.handler.duplication.DuplicationSubmit; | ||
| 5 | +import com.diligrp.cashier.shared.handler.duplication.IgnoreDuplicationSubmit; | ||
| 6 | + | ||
| 7 | +import java.lang.annotation.*; | ||
| 8 | +import java.util.concurrent.TimeUnit; | ||
| 9 | + | ||
| 10 | +/** | ||
| 11 | + * @author dengwei | ||
| 12 | + * @version 1.0.0 | ||
| 13 | + * @ClassName RepeatSubmit.java | ||
| 14 | + * @Description RepeatSubmit | ||
| 15 | + */ | ||
| 16 | +@Inherited | ||
| 17 | +@Target(ElementType.METHOD) | ||
| 18 | +@Retention(RetentionPolicy.RUNTIME) | ||
| 19 | +@Documented | ||
| 20 | +public @interface RepeatSubmit { | ||
| 21 | + /** | ||
| 22 | + * 前缀 | ||
| 23 | + */ | ||
| 24 | + String prefix() default ""; | ||
| 25 | + | ||
| 26 | + /** | ||
| 27 | + * 用于存放SpEL表达式的数组 | ||
| 28 | + */ | ||
| 29 | + String[] value() default {}; | ||
| 30 | + | ||
| 31 | + /** | ||
| 32 | + * 是否值保留一个值 | ||
| 33 | + */ | ||
| 34 | + boolean onlyOne() default false; | ||
| 35 | + | ||
| 36 | + /** | ||
| 37 | + * 等待获取锁的最大时长 | ||
| 38 | + * 未设置的-快速失败 | ||
| 39 | + * 设置-等待指定时间 | ||
| 40 | + */ | ||
| 41 | + int waitTime() default 0; | ||
| 42 | + | ||
| 43 | + /** | ||
| 44 | + * 获取锁之后,锁的自动释放时间 | ||
| 45 | + */ | ||
| 46 | + int leaseTime() default 0; | ||
| 47 | + | ||
| 48 | + /** | ||
| 49 | + * 时间单位 | ||
| 50 | + */ | ||
| 51 | + TimeUnit timeUnit() default TimeUnit.SECONDS; | ||
| 52 | + | ||
| 53 | + /** | ||
| 54 | + * 重复提交 | ||
| 55 | + */ | ||
| 56 | + Class<? extends DuplicationSubmit> duplicationSubmit() default IgnoreDuplicationSubmit.class; | ||
| 57 | +} |
cashier-shared/src/main/java/com/diligrp/cashier/shared/annotation/Sign.java
0 → 100644
| 1 | +package com.diligrp.cashier.shared.annotation; | ||
| 2 | + | ||
| 3 | + | ||
| 4 | +import com.diligrp.cashier.shared.handler.sign.NoSign; | ||
| 5 | +import com.diligrp.cashier.shared.handler.sign.SecuritySign; | ||
| 6 | + | ||
| 7 | +import java.lang.annotation.*; | ||
| 8 | + | ||
| 9 | +/** | ||
| 10 | + * @author dengwei | ||
| 11 | + * @version 1.0.0 | ||
| 12 | + * @ClassName Sign.java | ||
| 13 | + * @Description 签名验签 | ||
| 14 | + **/ | ||
| 15 | +@Target({ElementType.TYPE, ElementType.METHOD}) | ||
| 16 | +@Retention(RetentionPolicy.RUNTIME) | ||
| 17 | +@Documented | ||
| 18 | +public @interface Sign { | ||
| 19 | + Class<? extends SecuritySign> sign() default NoSign.class; | ||
| 20 | +} |
cashier-shared/src/main/java/com/diligrp/cashier/shared/aop/RepeatSubmitAop.java
0 → 100644
| 1 | +package com.diligrp.cashier.shared.aop; | ||
| 2 | + | ||
| 3 | +import com.diligrp.cashier.shared.ErrorCode; | ||
| 4 | +import com.diligrp.cashier.shared.annotation.RepeatSubmit; | ||
| 5 | +import com.diligrp.cashier.shared.domain.handler.AspectIdempotent; | ||
| 6 | +import com.diligrp.cashier.shared.exception.PlatformServiceException; | ||
| 7 | +import com.diligrp.cashier.shared.handler.duplication.DuplicationSubmit; | ||
| 8 | +import com.diligrp.cashier.shared.util.SpringContextUtils; | ||
| 9 | +import jakarta.annotation.Resource; | ||
| 10 | +import org.apache.commons.lang3.StringUtils; | ||
| 11 | +import org.aspectj.lang.JoinPoint; | ||
| 12 | +import org.aspectj.lang.ProceedingJoinPoint; | ||
| 13 | +import org.aspectj.lang.annotation.AfterThrowing; | ||
| 14 | +import org.aspectj.lang.annotation.Around; | ||
| 15 | +import org.aspectj.lang.annotation.Aspect; | ||
| 16 | +import org.redisson.api.RLock; | ||
| 17 | +import org.redisson.api.RedissonClient; | ||
| 18 | +import org.slf4j.Logger; | ||
| 19 | +import org.slf4j.LoggerFactory; | ||
| 20 | +import org.springframework.core.annotation.Order; | ||
| 21 | +import org.springframework.stereotype.Component; | ||
| 22 | + | ||
| 23 | +/** | ||
| 24 | + * @author dengwei | ||
| 25 | + * @version 1.0.0 | ||
| 26 | + * @ClassName RepeatSubmitAop.java | ||
| 27 | + * @Description 防重提交 | ||
| 28 | + */ | ||
| 29 | +@Aspect | ||
| 30 | +@Component | ||
| 31 | +@Order(2) | ||
| 32 | +public class RepeatSubmitAop { | ||
| 33 | + private static final Logger log = LoggerFactory.getLogger(RepeatSubmitAop.class); | ||
| 34 | + | ||
| 35 | + @Resource | ||
| 36 | + private RedissonClient redissonClient; | ||
| 37 | + | ||
| 38 | + /** | ||
| 39 | + * around | ||
| 40 | + */ | ||
| 41 | + @Around(value = "@annotation(repeatSubmit)") | ||
| 42 | + public Object around(ProceedingJoinPoint joinPoint, RepeatSubmit repeatSubmit) throws Throwable { | ||
| 43 | + DuplicationSubmit<AspectIdempotent> duplicationSubmit = SpringContextUtils.getBean(repeatSubmit.duplicationSubmit()); | ||
| 44 | + // 获取锁key | ||
| 45 | + String key = duplicationSubmit.key(new AspectIdempotent(joinPoint, repeatSubmit)); | ||
| 46 | + log.info("repeat submit aspect key: {}", key); | ||
| 47 | + if (StringUtils.isBlank(key)) { | ||
| 48 | + return joinPoint.proceed(); | ||
| 49 | + } | ||
| 50 | + | ||
| 51 | + RLock lock = redissonClient.getLock(key); | ||
| 52 | + boolean repeat = repeatSubmit.waitTime() > 0 ? | ||
| 53 | + lock.tryLock(repeatSubmit.waitTime(), repeatSubmit.leaseTime(), repeatSubmit.timeUnit()) : | ||
| 54 | + lock.tryLock(); | ||
| 55 | + try { | ||
| 56 | + if (!repeat) { | ||
| 57 | + log.info("重复操作 key={}", key); | ||
| 58 | + throw new PlatformServiceException(ErrorCode.SYSTEM_BUSY_ERROR, "请勿重复操作!"); | ||
| 59 | + } | ||
| 60 | + return joinPoint.proceed(); | ||
| 61 | + } finally { | ||
| 62 | + if (lock.isHeldByCurrentThread()) { | ||
| 63 | + lock.unlock(); | ||
| 64 | + } | ||
| 65 | + } | ||
| 66 | + } | ||
| 67 | + | ||
| 68 | + /** | ||
| 69 | + * 拦截异常操作 | ||
| 70 | + * 未进入目标方法抛出的异常无法监听 | ||
| 71 | + */ | ||
| 72 | + @AfterThrowing(value = "@annotation(repeatSubmit)", throwing = "e") | ||
| 73 | + public void doAfterThrowing(JoinPoint joinPoint, RepeatSubmit repeatSubmit, Exception e) { | ||
| 74 | + log.error("repeat submit aspect after throwing"); | ||
| 75 | + } | ||
| 76 | +} |
cashier-shared/src/main/java/com/diligrp/cashier/shared/aop/SecuritySignAop.java
0 → 100644
| 1 | +package com.diligrp.cashier.shared.aop; | ||
| 2 | + | ||
| 3 | +import com.diligrp.cashier.shared.annotation.Sign; | ||
| 4 | +import com.diligrp.cashier.shared.handler.sign.NoSign; | ||
| 5 | +import com.diligrp.cashier.shared.handler.sign.SecuritySign; | ||
| 6 | +import com.diligrp.cashier.shared.util.SpringContextUtils; | ||
| 7 | +import org.apache.commons.lang3.ArrayUtils; | ||
| 8 | +import org.aspectj.lang.JoinPoint; | ||
| 9 | +import org.aspectj.lang.annotation.Aspect; | ||
| 10 | +import org.aspectj.lang.annotation.Before; | ||
| 11 | +import org.springframework.core.annotation.Order; | ||
| 12 | +import org.springframework.stereotype.Component; | ||
| 13 | + | ||
| 14 | +/** | ||
| 15 | + * @author dengwei | ||
| 16 | + * @version 1.0.0 | ||
| 17 | + * @ClassName SecuritySignAop.java | ||
| 18 | + * @Description SecuritySignAop | ||
| 19 | + * 验签流程 放到网关合理点 前期先放到AOP下 | ||
| 20 | + */ | ||
| 21 | +@Aspect | ||
| 22 | +@Component | ||
| 23 | +@Order(1) | ||
| 24 | +public class SecuritySignAop { | ||
| 25 | + /** | ||
| 26 | + * doBefore | ||
| 27 | + */ | ||
| 28 | + @Before(value = "@annotation(sign)") | ||
| 29 | + public void doBefore(JoinPoint joinPoint, Sign sign) { | ||
| 30 | + Class<? extends SecuritySign> securitySign = sign.sign(); | ||
| 31 | + if (securitySign == NoSign.class) { | ||
| 32 | + return; | ||
| 33 | + } | ||
| 34 | + | ||
| 35 | + Object[] args = joinPoint.getArgs(); | ||
| 36 | + if (ArrayUtils.isEmpty(args)) { | ||
| 37 | + return; | ||
| 38 | + } | ||
| 39 | + SecuritySign signBean = SpringContextUtils.getBean(securitySign); | ||
| 40 | + signBean.sign(null, null, args[0]); | ||
| 41 | + } | ||
| 42 | +} |
cashier-shared/src/main/java/com/diligrp/cashier/shared/domain/handler/AspectIdempotent.java
0 → 100644
| 1 | +package com.diligrp.cashier.shared.domain.handler; | ||
| 2 | + | ||
| 3 | +import com.diligrp.cashier.shared.annotation.RepeatSubmit; | ||
| 4 | +import org.aspectj.lang.ProceedingJoinPoint; | ||
| 5 | + | ||
| 6 | +/** | ||
| 7 | + * @author dengwei | ||
| 8 | + * @version 1.0.0 | ||
| 9 | + * @ClassName AspectIdempotent.java | ||
| 10 | + * @Description AspectIdempotent | ||
| 11 | + * @date 2025-07-03 10:42 | ||
| 12 | + */ | ||
| 13 | +public class AspectIdempotent { | ||
| 14 | + private ProceedingJoinPoint joinPoint; | ||
| 15 | + private RepeatSubmit repeatSubmit; | ||
| 16 | + | ||
| 17 | + public AspectIdempotent() { | ||
| 18 | + } | ||
| 19 | + | ||
| 20 | + public AspectIdempotent(ProceedingJoinPoint joinPoint, RepeatSubmit repeatSubmit) { | ||
| 21 | + this.joinPoint = joinPoint; | ||
| 22 | + this.repeatSubmit = repeatSubmit; | ||
| 23 | + } | ||
| 24 | + | ||
| 25 | + public ProceedingJoinPoint getJoinPoint() { | ||
| 26 | + return joinPoint; | ||
| 27 | + } | ||
| 28 | + | ||
| 29 | + public void setJoinPoint(ProceedingJoinPoint joinPoint) { | ||
| 30 | + this.joinPoint = joinPoint; | ||
| 31 | + } | ||
| 32 | + | ||
| 33 | + public RepeatSubmit getRepeatSubmit() { | ||
| 34 | + return repeatSubmit; | ||
| 35 | + } | ||
| 36 | + | ||
| 37 | + public void setRepeatSubmit(RepeatSubmit repeatSubmit) { | ||
| 38 | + this.repeatSubmit = repeatSubmit; | ||
| 39 | + } | ||
| 40 | +} |
cashier-shared/src/main/java/com/diligrp/cashier/shared/handler/duplication/DuplicationSubmit.java
0 → 100644
cashier-shared/src/main/java/com/diligrp/cashier/shared/handler/duplication/IgnoreDuplicationSubmit.java
0 → 100644
| 1 | +package com.diligrp.cashier.shared.handler.duplication; | ||
| 2 | + | ||
| 3 | +import org.slf4j.Logger; | ||
| 4 | +import org.slf4j.LoggerFactory; | ||
| 5 | +import org.springframework.stereotype.Component; | ||
| 6 | + | ||
| 7 | +/** | ||
| 8 | + * @author dengwei | ||
| 9 | + * @version 1.0.0 | ||
| 10 | + * @ClassName TokenDuplicationSubmit.java | ||
| 11 | + * @Description TokenDuplicationSubmit | ||
| 12 | + */ | ||
| 13 | +@Component | ||
| 14 | +public class IgnoreDuplicationSubmit implements DuplicationSubmit<Object> { | ||
| 15 | + private static final Logger LOGGER = LoggerFactory.getLogger(IgnoreDuplicationSubmit.class); | ||
| 16 | + | ||
| 17 | + @Override | ||
| 18 | + public String key(Object param) { | ||
| 19 | + LOGGER.info("ignore"); | ||
| 20 | + return "ignore"; | ||
| 21 | + } | ||
| 22 | +} |
cashier-shared/src/main/java/com/diligrp/cashier/shared/handler/duplication/SpelDuplicationSubmit.java
0 → 100644
| 1 | +package com.diligrp.cashier.shared.handler.duplication; | ||
| 2 | + | ||
| 3 | +import com.diligrp.cashier.shared.annotation.RepeatSubmit; | ||
| 4 | +import com.diligrp.cashier.shared.domain.handler.AspectIdempotent; | ||
| 5 | +import org.apache.commons.lang3.ArrayUtils; | ||
| 6 | +import org.apache.commons.lang3.StringUtils; | ||
| 7 | +import org.aspectj.lang.ProceedingJoinPoint; | ||
| 8 | +import org.aspectj.lang.reflect.MethodSignature; | ||
| 9 | +import org.slf4j.Logger; | ||
| 10 | +import org.slf4j.LoggerFactory; | ||
| 11 | +import org.springframework.expression.EvaluationContext; | ||
| 12 | +import org.springframework.expression.Expression; | ||
| 13 | +import org.springframework.expression.ExpressionParser; | ||
| 14 | +import org.springframework.expression.spel.standard.SpelExpressionParser; | ||
| 15 | +import org.springframework.expression.spel.support.StandardEvaluationContext; | ||
| 16 | +import org.springframework.stereotype.Component; | ||
| 17 | + | ||
| 18 | +import java.util.Objects; | ||
| 19 | +import java.util.Optional; | ||
| 20 | +import java.util.StringJoiner; | ||
| 21 | + | ||
| 22 | +/** | ||
| 23 | + * @author dengwei | ||
| 24 | + * @version 1.0.0 | ||
| 25 | + * @ClassName SpelDuplicationSubmit.java | ||
| 26 | + * @Description SpelDuplicationSubmit | ||
| 27 | + */ | ||
| 28 | +@Component | ||
| 29 | +public class SpelDuplicationSubmit implements DuplicationSubmit<AspectIdempotent> { | ||
| 30 | + private static final Logger log = LoggerFactory.getLogger(SpelDuplicationSubmit.class); | ||
| 31 | + | ||
| 32 | + private final ExpressionParser parser = new SpelExpressionParser(); | ||
| 33 | + | ||
| 34 | + @Override | ||
| 35 | + public String key(AspectIdempotent param) { | ||
| 36 | + log.info("spel duplicationSubmit~~~~"); | ||
| 37 | + return getKey(param.getJoinPoint(), param.getRepeatSubmit()); | ||
| 38 | + } | ||
| 39 | + | ||
| 40 | + /** | ||
| 41 | + * 根据spel获取值 | ||
| 42 | + */ | ||
| 43 | + private String getKey(ProceedingJoinPoint joinPoint, RepeatSubmit repeatSubmit) { | ||
| 44 | + String[] spelValStr = repeatSubmit.value(); | ||
| 45 | + if (ArrayUtils.isEmpty(spelValStr)) { | ||
| 46 | + return null; | ||
| 47 | + } | ||
| 48 | + | ||
| 49 | + // 获取方法参数名和参数值 | ||
| 50 | + MethodSignature signature = (MethodSignature) joinPoint.getSignature(); | ||
| 51 | + String[] parameterNames = signature.getParameterNames(); | ||
| 52 | + Object[] args = joinPoint.getArgs(); | ||
| 53 | + | ||
| 54 | + // 创建EvaluationContext并设置变量 | ||
| 55 | + EvaluationContext context = new StandardEvaluationContext(); | ||
| 56 | + for (int i = 0; i < parameterNames.length; i++) { | ||
| 57 | + context.setVariable(parameterNames[i], args[i]); | ||
| 58 | + } | ||
| 59 | + | ||
| 60 | + boolean onlyOne = repeatSubmit.onlyOne(); | ||
| 61 | + StringJoiner stringJoiner = new StringJoiner("_", repeatSubmit.prefix(), StringUtils.EMPTY); | ||
| 62 | + for (String expression : spelValStr) { | ||
| 63 | + try { | ||
| 64 | + Expression exp = parser.parseExpression(expression); | ||
| 65 | + Object targetValue = exp.getValue(context); | ||
| 66 | + Optional.ofNullable(targetValue).ifPresent(value -> { | ||
| 67 | + stringJoiner.add(value.toString()); | ||
| 68 | + }); | ||
| 69 | + // 如果只强制拿取一个并且不为空 | ||
| 70 | + if (onlyOne && Objects.nonNull(targetValue)) { | ||
| 71 | + break; | ||
| 72 | + } | ||
| 73 | + } catch (Exception e) { | ||
| 74 | + log.warn("expression get value error", e); | ||
| 75 | + } | ||
| 76 | + } | ||
| 77 | + return stringJoiner.toString(); | ||
| 78 | + } | ||
| 79 | +} |
cashier-shared/src/main/java/com/diligrp/cashier/shared/handler/sign/NoSign.java
0 → 100644
| 1 | +package com.diligrp.cashier.shared.handler.sign; | ||
| 2 | + | ||
| 3 | +import jakarta.servlet.http.HttpServletRequest; | ||
| 4 | +import jakarta.servlet.http.HttpServletResponse; | ||
| 5 | +import org.slf4j.Logger; | ||
| 6 | +import org.slf4j.LoggerFactory; | ||
| 7 | +import org.springframework.stereotype.Component; | ||
| 8 | + | ||
| 9 | +/** | ||
| 10 | + * @author dengwei | ||
| 11 | + * @version 1.0.0 | ||
| 12 | + * @ClassName NoSign.java | ||
| 13 | + * @Description NoSign | ||
| 14 | + */ | ||
| 15 | +@Component | ||
| 16 | +public class NoSign implements SecuritySign { | ||
| 17 | + private static final Logger log = LoggerFactory.getLogger(NoSign.class); | ||
| 18 | + | ||
| 19 | + @Override | ||
| 20 | + public void sign(HttpServletRequest request, HttpServletResponse response, Object handler) { | ||
| 21 | + log.info("该无需验签~~"); | ||
| 22 | + } | ||
| 23 | +} |
cashier-shared/src/main/java/com/diligrp/cashier/shared/handler/sign/SecuritySign.java
0 → 100644
| 1 | +package com.diligrp.cashier.shared.handler.sign; | ||
| 2 | + | ||
| 3 | +import jakarta.servlet.http.HttpServletRequest; | ||
| 4 | +import jakarta.servlet.http.HttpServletResponse; | ||
| 5 | + | ||
| 6 | +/** | ||
| 7 | + * @author dengwei | ||
| 8 | + * @version 1.0.0 | ||
| 9 | + * @ClassName ParameterSign.java | ||
| 10 | + * @Description SecuritySign | ||
| 11 | + * 迁移至网关实现 | ||
| 12 | + * @date 2025-07-09 16:08 | ||
| 13 | + */ | ||
| 14 | +public interface SecuritySign { | ||
| 15 | + void sign(HttpServletRequest request, HttpServletResponse response, Object handler); | ||
| 16 | +} |
cashier-shared/src/main/java/com/diligrp/cashier/shared/util/JsonUtils.java
| @@ -28,7 +28,7 @@ public class JsonUtils { | @@ -28,7 +28,7 @@ public class JsonUtils { | ||
| 28 | 28 | ||
| 29 | private static ObjectMapper objectMapper = initObjectMapper(); | 29 | private static ObjectMapper objectMapper = initObjectMapper(); |
| 30 | 30 | ||
| 31 | - private static ObjectMapper initObjectMapper(){ | 31 | + private static ObjectMapper initObjectMapper() { |
| 32 | Jackson2ObjectMapperBuilder jackson2ObjectMapperBuilder = new Jackson2ObjectMapperBuilder(); | 32 | Jackson2ObjectMapperBuilder jackson2ObjectMapperBuilder = new Jackson2ObjectMapperBuilder(); |
| 33 | initObjectMapperBuilder(jackson2ObjectMapperBuilder); | 33 | initObjectMapperBuilder(jackson2ObjectMapperBuilder); |
| 34 | ObjectMapper objectMapper = jackson2ObjectMapperBuilder.createXmlMapper(false).build(); | 34 | ObjectMapper objectMapper = jackson2ObjectMapperBuilder.createXmlMapper(false).build(); |
| @@ -42,15 +42,15 @@ public class JsonUtils { | @@ -42,15 +42,15 @@ public class JsonUtils { | ||
| 42 | builder.timeZone(TimeZone.getTimeZone(ZoneOffset.of("+8"))); | 42 | builder.timeZone(TimeZone.getTimeZone(ZoneOffset.of("+8"))); |
| 43 | builder.serializationInclusion(JsonInclude.Include.NON_NULL); | 43 | builder.serializationInclusion(JsonInclude.Include.NON_NULL); |
| 44 | builder.featuresToDisable( | 44 | builder.featuresToDisable( |
| 45 | - DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, // Json串的属性无JavaBean字段对应时,避免抛出异常 | ||
| 46 | - DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES, // JavaBean中primitive类型的字段无Json属性时,避免抛出异常 | ||
| 47 | - DeserializationFeature.FAIL_ON_NUMBERS_FOR_ENUMS, // Json串数字类型属性,赋值JavaBean中Enum字段时,避免抛出异常 | ||
| 48 | - SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, | ||
| 49 | - SerializationFeature.FAIL_ON_EMPTY_BEANS | 45 | + DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, // Json串的属性无JavaBean字段对应时,避免抛出异常 |
| 46 | + DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES, // JavaBean中primitive类型的字段无Json属性时,避免抛出异常 | ||
| 47 | + DeserializationFeature.FAIL_ON_NUMBERS_FOR_ENUMS, // Json串数字类型属性,赋值JavaBean中Enum字段时,避免抛出异常 | ||
| 48 | + SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, | ||
| 49 | + SerializationFeature.FAIL_ON_EMPTY_BEANS | ||
| 50 | ); | 50 | ); |
| 51 | builder.featuresToEnable( | 51 | builder.featuresToEnable( |
| 52 | - DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT, | ||
| 53 | - DeserializationFeature.USE_JAVA_ARRAY_FOR_JSON_ARRAY | 52 | + DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT, |
| 53 | + DeserializationFeature.USE_JAVA_ARRAY_FOR_JSON_ARRAY | ||
| 54 | ); | 54 | ); |
| 55 | 55 | ||
| 56 | var dateTimeFormatter = DateTimeFormatter.ofPattern(Constants.DATE_TIME_FORMAT); | 56 | var dateTimeFormatter = DateTimeFormatter.ofPattern(Constants.DATE_TIME_FORMAT); |
| @@ -74,7 +74,7 @@ public class JsonUtils { | @@ -74,7 +74,7 @@ public class JsonUtils { | ||
| 74 | } | 74 | } |
| 75 | } | 75 | } |
| 76 | 76 | ||
| 77 | - public static <T> T fromJsonString(String json, TypeReference<T> typeReference){ | 77 | + public static <T> T fromJsonString(String json, TypeReference<T> typeReference) { |
| 78 | try { | 78 | try { |
| 79 | return objectMapper.readValue(json, typeReference); | 79 | return objectMapper.readValue(json, typeReference); |
| 80 | } catch (JsonProcessingException ex) { | 80 | } catch (JsonProcessingException ex) { |
| @@ -106,6 +106,10 @@ public class JsonUtils { | @@ -106,6 +106,10 @@ public class JsonUtils { | ||
| 106 | return fromJsonString(toJsonString(value), type); | 106 | return fromJsonString(toJsonString(value), type); |
| 107 | } | 107 | } |
| 108 | 108 | ||
| 109 | + public static <T> T convertValueV2(Object value, TypeReference<T> type) { | ||
| 110 | + return fromJsonString(toJsonString(value), type); | ||
| 111 | + } | ||
| 112 | + | ||
| 109 | 113 | ||
| 110 | public static <T> String toJsonString(T object) { | 114 | public static <T> String toJsonString(T object) { |
| 111 | try { | 115 | try { |
cashier-shared/src/main/java/com/diligrp/cashier/shared/util/SpringContextUtils.java
0 → 100644
| 1 | +package com.diligrp.cashier.shared.util; | ||
| 2 | + | ||
| 3 | +import org.springframework.beans.BeansException; | ||
| 4 | +import org.springframework.context.ApplicationContext; | ||
| 5 | +import org.springframework.context.ApplicationContextAware; | ||
| 6 | +import org.springframework.stereotype.Component; | ||
| 7 | + | ||
| 8 | +import java.util.Map; | ||
| 9 | + | ||
| 10 | + | ||
| 11 | +/** | ||
| 12 | + * SpringUtil | ||
| 13 | + * | ||
| 14 | + * @author dengwei | ||
| 15 | + * @date 2025/12/24 18:05 | ||
| 16 | + */ | ||
| 17 | +@Component | ||
| 18 | +public class SpringContextUtils implements ApplicationContextAware { | ||
| 19 | + | ||
| 20 | + private static ApplicationContext applicationContext; | ||
| 21 | + | ||
| 22 | + @Override | ||
| 23 | + public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { | ||
| 24 | + if (SpringContextUtils.applicationContext == null) { | ||
| 25 | + SpringContextUtils.applicationContext = applicationContext; | ||
| 26 | + } | ||
| 27 | + } | ||
| 28 | + | ||
| 29 | + public static ApplicationContext getApplicationContext() { | ||
| 30 | + return applicationContext; | ||
| 31 | + } | ||
| 32 | + | ||
| 33 | + // 通过name获取Bean | ||
| 34 | + public static Object getBean(String name) { | ||
| 35 | + return getApplicationContext().getBean(name); | ||
| 36 | + } | ||
| 37 | + | ||
| 38 | + // 通过class获取Bean | ||
| 39 | + public static <T> T getBean(Class<T> clazz) { | ||
| 40 | + return getApplicationContext().getBean(clazz); | ||
| 41 | + } | ||
| 42 | + | ||
| 43 | + // 通过name,以及Clazz返回指定的Bean | ||
| 44 | + public static <T> T getBean(String name, Class<T> clazz) { | ||
| 45 | + return getApplicationContext().getBean(name, clazz); | ||
| 46 | + } | ||
| 47 | + | ||
| 48 | + public static <T> Map<String, T> getBeanOfTpe(Class<T> clazz) { | ||
| 49 | + return getApplicationContext().getBeansOfType(clazz); | ||
| 50 | + } | ||
| 51 | + | ||
| 52 | +} |