Merge #88 into master from cc_20260401_private_sphere_qr
fix:私域二维码 * cc_20260401_private_sphere_qr: (3 commits squashed) - fix:新增防抖 - Merge branch 'refs/heads/cc_20260302_debounce' into cc_20260401_private_sphere_qr - fix:私域二维码 Signed-off-by: 王非凡 <accounts_67eba0c5fee9c49c80c8e2b4@mail.teambition.com> Merged-by: 正新 <accounts_6964c7bcd2a2c377c5bbd01b@mail.teambition.com> CR-link: https://codeup.aliyun.com/692ea314dec569489f6f167c/hangzhou/java/custom_zxjp/change/88
This commit is contained in:
@@ -0,0 +1,150 @@
|
||||
package com.cool.store.aspect;
|
||||
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import com.cool.store.annotation.Debounce;
|
||||
import com.cool.store.context.CurrentUserHolder;
|
||||
import com.cool.store.context.PartnerUserHolder;
|
||||
import com.cool.store.enums.ErrorCodeEnum;
|
||||
import com.cool.store.exception.ServiceException;
|
||||
import com.cool.store.utils.RedisUtilPool;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.aspectj.lang.ProceedingJoinPoint;
|
||||
import org.aspectj.lang.annotation.Around;
|
||||
import org.aspectj.lang.annotation.Aspect;
|
||||
import org.aspectj.lang.reflect.MethodSignature;
|
||||
import org.springframework.core.annotation.AnnotatedElementUtils;
|
||||
import org.springframework.expression.EvaluationContext;
|
||||
import org.springframework.expression.ExpressionParser;
|
||||
import org.springframework.expression.spel.standard.SpelExpressionParser;
|
||||
import org.springframework.expression.spel.support.StandardEvaluationContext;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Arrays;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* 防抖切面
|
||||
* </p>
|
||||
*
|
||||
* @author wangff
|
||||
* @since 2026/2/7
|
||||
*/
|
||||
@Aspect
|
||||
@Component
|
||||
@Slf4j
|
||||
public class DebounceAspect {
|
||||
@Resource
|
||||
private RedisUtilPool redisUtilPool;
|
||||
|
||||
private static final String KEY_PREFIX = "debounce:";
|
||||
private static final String FIELD_NEXT_ALLOWED_AT = "nextAllowedAt";
|
||||
private static final String FIELD_LAST_RESULT = "lastResult";
|
||||
|
||||
private final ExpressionParser spelParser = new SpelExpressionParser();
|
||||
|
||||
@Around("execution(public * *(..)) && (@within(com.cool.store.annotation.Debounce) || @annotation(com.cool.store.annotation.Debounce))")
|
||||
public Object around(ProceedingJoinPoint pjp) throws Throwable {
|
||||
Method method = ((MethodSignature) pjp.getSignature()).getMethod();
|
||||
|
||||
Debounce debounce = AnnotatedElementUtils.findMergedAnnotation(method, Debounce.class);
|
||||
if (debounce == null) {
|
||||
debounce = AnnotatedElementUtils.findMergedAnnotation(method.getDeclaringClass(), Debounce.class);
|
||||
}
|
||||
if (debounce == null || debounce.timeMs() <= 0) {
|
||||
return pjp.proceed();
|
||||
}
|
||||
|
||||
String key = KEY_PREFIX + buildKey(debounce, pjp, method);
|
||||
long now = System.currentTimeMillis();
|
||||
long ttlMs = debounce.timeMs();
|
||||
|
||||
Long nextAllowedAt = readNextAllowedAt(key);
|
||||
if (nextAllowedAt != null && now < nextAllowedAt) {
|
||||
if (debounce.throwOnDebounce()) {
|
||||
throw new ServiceException(ErrorCodeEnum.OPERATION_OVER_TIME);
|
||||
}
|
||||
String val = redisUtilPool.getString(key + ":" + FIELD_LAST_RESULT);
|
||||
return parseToReturnType(val, method);
|
||||
}
|
||||
|
||||
boolean allowed = trySetNextAllowedAt(key, now + ttlMs, ttlMs);
|
||||
if (!allowed) {
|
||||
if (debounce.throwOnDebounce()) {
|
||||
throw new ServiceException(ErrorCodeEnum.OPERATION_OVER_TIME);
|
||||
}
|
||||
String val = redisUtilPool.getString(key + ":" + FIELD_LAST_RESULT);
|
||||
return parseToReturnType(val, method);
|
||||
}
|
||||
|
||||
Object result = null;
|
||||
try {
|
||||
result = pjp.proceed();
|
||||
return result;
|
||||
} finally {
|
||||
if (!isVoidReturn(method) && Objects.nonNull(result)) {
|
||||
redisUtilPool.setNxExpire(key + ":" + FIELD_LAST_RESULT, JSONObject.toJSONString(result), (int) ttlMs * 2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Long readNextAllowedAt(String key) {
|
||||
String val = redisUtilPool.getString(key + ":" + FIELD_NEXT_ALLOWED_AT);
|
||||
if (StringUtils.isBlank(val)) {
|
||||
return null;
|
||||
}
|
||||
Object obj = JSONObject.parse(val);
|
||||
if (obj instanceof Number) {
|
||||
return ((Number) obj).longValue();
|
||||
}
|
||||
try {
|
||||
return Long.parseLong(val);
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean trySetNextAllowedAt(String key, long nextAllowedAt, long ttlMs) {
|
||||
return redisUtilPool.setNxExpire(key + ":" + FIELD_NEXT_ALLOWED_AT, String.valueOf(nextAllowedAt), (int) ttlMs);
|
||||
}
|
||||
|
||||
private boolean isVoidReturn(Method method) {
|
||||
return Void.TYPE.equals(method.getReturnType()) || Void.class.equals(method.getReturnType());
|
||||
}
|
||||
|
||||
private Object parseToReturnType(String json, Method method) {
|
||||
if (StringUtils.isBlank(json)) {
|
||||
return null;
|
||||
}
|
||||
Class<?> returnType = method.getReturnType();
|
||||
if (returnType == null || Object.class.equals(returnType)) {
|
||||
return JSONObject.parse(json);
|
||||
}
|
||||
if (String.class.equals(returnType)) {
|
||||
return json;
|
||||
}
|
||||
return JSON.parseObject(json, returnType);
|
||||
}
|
||||
|
||||
private String buildKey(Debounce debounce, ProceedingJoinPoint pjp, Method method) {
|
||||
String spel = debounce.key();
|
||||
String suffix = debounce.diffUser() ? ":" + (debounce.pc() ? CurrentUserHolder.getUserId() : PartnerUserHolder.getUser().getPartnerId()) : "";
|
||||
if (spel != null && !spel.isEmpty()) {
|
||||
EvaluationContext ctx = new StandardEvaluationContext();
|
||||
Object[] args = pjp.getArgs();
|
||||
String[] paramNames = ((MethodSignature) pjp.getSignature()).getParameterNames();
|
||||
if (paramNames != null) {
|
||||
for (int i = 0; i < paramNames.length; i++) {
|
||||
ctx.setVariable(paramNames[i], args[i]);
|
||||
}
|
||||
}
|
||||
Object val = spelParser.parseExpression(spel).getValue(ctx);
|
||||
return "spel:" + method.getDeclaringClass().getName() + "#" + method.getName() + ":" + val + suffix;
|
||||
}
|
||||
return "default:" + method.getDeclaringClass().getName() + "#" + method.getName() + ":" + Arrays.deepHashCode(pjp.getArgs()) + suffix;
|
||||
}
|
||||
}
|
||||
@@ -52,7 +52,9 @@ public class OpenApiValidateFilter implements Filter {
|
||||
"/zxjp/open/v1/statusRefresh",
|
||||
"/zxjp/open/v1/getStoreUser",
|
||||
"/zxjp/open/v1/callback",
|
||||
"/zxjp/open/v1/wallet/onlineCommercialBankCallback"
|
||||
"/zxjp/open/v1/wallet/onlineCommercialBankCallback",
|
||||
"/zxjp/open/v1/ps/geBindQr",
|
||||
"/zxjp/open/v1/ps/getPrivateSphereQr"
|
||||
));
|
||||
private static final List<String> oldUrlMapping = new ArrayList<>(Arrays.asList(
|
||||
"/zxjp/open/v1/statusRefresh","/zxjp/open/v1/changePaymentStatus",
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.cool.store.controller.webb;
|
||||
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import com.cool.store.annotation.Debounce;
|
||||
import com.cool.store.dto.*;
|
||||
import com.cool.store.dto.region.BigRegionDTO;
|
||||
import com.cool.store.dto.store.StoreUserPositionDTO;
|
||||
@@ -17,6 +18,7 @@ import com.cool.store.request.notice.ThirdMatterRequest;
|
||||
import com.cool.store.request.openapi.ShopListRequest;
|
||||
import com.cool.store.request.openapi.StoreRequest;
|
||||
import com.cool.store.request.openapi.SubRegionRequest;
|
||||
import com.cool.store.request.privatesphere.PrivateSphereSnRequest;
|
||||
import com.cool.store.request.wallet.AccountTradeCallbackRequest;
|
||||
import com.cool.store.request.wallet.AddTagCallbackNoticeRequest;
|
||||
import com.cool.store.request.wallet.OnlineCommercialBankCallbackRequest;
|
||||
@@ -27,9 +29,12 @@ import com.cool.store.response.ResponseResult;
|
||||
import com.cool.store.response.ShopResponse;
|
||||
import com.cool.store.response.bigdata.ApiResponse;
|
||||
import com.cool.store.service.*;
|
||||
import com.cool.store.service.privatesphere.PrivateSphereQrService;
|
||||
import com.cool.store.service.wallet.WalletService;
|
||||
import com.cool.store.utils.HttpHelper;
|
||||
import com.cool.store.utils.poi.StringUtils;
|
||||
import com.cool.store.service.close.CloseStoreService;
|
||||
import com.cool.store.vo.privatesphere.PrivateSphereVO;
|
||||
import com.github.pagehelper.PageInfo;
|
||||
import io.swagger.annotations.Api;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
@@ -39,6 +44,7 @@ import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
@@ -80,6 +86,8 @@ public class OpenApiController {
|
||||
BigRegionService bigRegionService;
|
||||
@Resource
|
||||
RegionService regionService;
|
||||
@Resource
|
||||
PrivateSphereQrService privateSphereQrService;
|
||||
|
||||
@PostMapping("/statusRefresh")
|
||||
public ApiResponse<Boolean> statusRefresh(@RequestBody StatusRefreshDTO statusRefreshDTO){
|
||||
@@ -280,4 +288,19 @@ public class OpenApiController {
|
||||
return ApiResponse.success(regionService.getSubRegionByParentId(request.getRegionId()));
|
||||
}
|
||||
|
||||
@ApiOperation("私域二维码-生成绑定二维码")
|
||||
@PostMapping("/ps/geBindQr")
|
||||
@Debounce(key = "#request.sn", diffUser = false, timeMs = 5000)
|
||||
public ApiResponse<PrivateSphereVO> geBindQr(@RequestBody @Validated PrivateSphereSnRequest request, HttpServletRequest httpRequest) {
|
||||
request.setClientIp(HttpHelper.getIpAddr(httpRequest));
|
||||
return ApiResponse.success(privateSphereQrService.generateQrCode(request));
|
||||
}
|
||||
|
||||
@ApiOperation("私域二维码-根据设备sn获取门店私域二维码")
|
||||
@PostMapping("/ps/getPrivateSphereQr")
|
||||
@Debounce(key = "#request.sn", diffUser = false, timeMs = 5000)
|
||||
public ApiResponse<PrivateSphereVO> getPrivateSphereQr(@RequestBody @Validated PrivateSphereSnRequest request, HttpServletRequest httpRequest) {
|
||||
request.setClientIp(HttpHelper.getIpAddr(httpRequest));
|
||||
return ApiResponse.success(privateSphereQrService.getPrivateSphereQr(request));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
package com.cool.store.controller.webc;
|
||||
|
||||
import com.cool.store.request.privatesphere.PrivateSphereBindRequest;
|
||||
import com.cool.store.response.ResponseResult;
|
||||
import com.cool.store.service.privatesphere.PrivateSphereQrService;
|
||||
import io.swagger.annotations.Api;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Mini门店私域二维码
|
||||
* </p>
|
||||
*
|
||||
* @author wangff
|
||||
* @since 2026/4/2
|
||||
*/
|
||||
@RestController
|
||||
@Api(tags = "Mini门店私域二维码")
|
||||
@RequestMapping({"/mini/sp"})
|
||||
@RequiredArgsConstructor
|
||||
public class MiniPrivateSphereQrController {
|
||||
private final PrivateSphereQrService privateSphereQrService;
|
||||
|
||||
@ApiOperation("门店绑定设备sn")
|
||||
@PostMapping("/bind")
|
||||
public ResponseResult<Boolean> bindStoreSn(@RequestBody @Validated PrivateSphereBindRequest request) {
|
||||
return ResponseResult.success(privateSphereQrService.bindStoreSn(request));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user