From 68cef4b83aa191af5d4ef2b8dda1a395048f2cf4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E9=9D=9E=E5=87=A1?= Date: Wed, 8 Apr 2026 10:44:13 +0000 Subject: [PATCH] Merge #88 into master from cc_20260401_private_sphere_qr MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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: 王非凡 Merged-by: 正新 CR-link: https://codeup.aliyun.com/692ea314dec569489f6f167c/hangzhou/java/custom_zxjp/change/88 --- .../com/cool/store/annotation/Debounce.java | 39 +++++ .../cool/store/constants/RedisConstant.java | 20 +++ .../com/cool/store/enums/ErrorCodeEnum.java | 2 + .../java/com/cool/store/dao/StoreDao.java | 7 + .../com/cool/store/mapper/StoreMapper.java | 2 + .../src/main/resources/mapper/StoreMapper.xml | 4 + .../PrivateSphereBindRequest.java | 25 +++ .../privatesphere/PrivateSphereSnRequest.java | 24 +++ .../vo/privatesphere/PrivateSphereVO.java | 40 +++++ coolstore-partner-service/pom.xml | 5 + .../privatesphere/PrivateSphereQrService.java | 39 +++++ .../impl/PrivateSphereQrServiceImpl.java | 138 ++++++++++++++++ .../com/cool/store/aspect/DebounceAspect.java | 150 ++++++++++++++++++ .../store/config/OpenApiValidateFilter.java | 4 +- .../controller/webb/OpenApiController.java | 23 +++ .../webc/MiniPrivateSphereQrController.java | 35 ++++ pom.xml | 6 + 17 files changed, 562 insertions(+), 1 deletion(-) create mode 100644 coolstore-partner-common/src/main/java/com/cool/store/annotation/Debounce.java create mode 100644 coolstore-partner-model/src/main/java/com/cool/store/request/privatesphere/PrivateSphereBindRequest.java create mode 100644 coolstore-partner-model/src/main/java/com/cool/store/request/privatesphere/PrivateSphereSnRequest.java create mode 100644 coolstore-partner-model/src/main/java/com/cool/store/vo/privatesphere/PrivateSphereVO.java create mode 100644 coolstore-partner-service/src/main/java/com/cool/store/service/privatesphere/PrivateSphereQrService.java create mode 100644 coolstore-partner-service/src/main/java/com/cool/store/service/privatesphere/impl/PrivateSphereQrServiceImpl.java create mode 100644 coolstore-partner-web/src/main/java/com/cool/store/aspect/DebounceAspect.java create mode 100644 coolstore-partner-web/src/main/java/com/cool/store/controller/webc/MiniPrivateSphereQrController.java diff --git a/coolstore-partner-common/src/main/java/com/cool/store/annotation/Debounce.java b/coolstore-partner-common/src/main/java/com/cool/store/annotation/Debounce.java new file mode 100644 index 000000000..ef66a5f9c --- /dev/null +++ b/coolstore-partner-common/src/main/java/com/cool/store/annotation/Debounce.java @@ -0,0 +1,39 @@ +package com.cool.store.annotation; + +import java.lang.annotation.*; + +/** + * 防抖 + */ +@Target({ElementType.METHOD, ElementType.TYPE}) +@Retention(RetentionPolicy.RUNTIME) +@Inherited +@Documented +public @interface Debounce { + /** + * 防抖时间窗口(毫秒) + */ + long timeMs() default 1000; + + /** + * key 的 SpEL + * 例:"'order:' + #req.orderId" + */ + String key() default ""; + + /** + * 是否根据用户区分 + */ + boolean diffUser() default true; + + /** + * 是否为pc端,pc端使用CurrentUserHolder获取用户信息,否则使用PartnerUserHolder获取 + */ + boolean pc() default true; + + /** + * true: 在窗口内触发则抛异常 + * false: 在窗口内触发则直接返回上一次结果 + */ + boolean throwOnDebounce() default true; +} diff --git a/coolstore-partner-common/src/main/java/com/cool/store/constants/RedisConstant.java b/coolstore-partner-common/src/main/java/com/cool/store/constants/RedisConstant.java index a210ffa1a..a4a65b63d 100644 --- a/coolstore-partner-common/src/main/java/com/cool/store/constants/RedisConstant.java +++ b/coolstore-partner-common/src/main/java/com/cool/store/constants/RedisConstant.java @@ -384,4 +384,24 @@ public class RedisConstant { * 钱包分账支付锁 */ public static final String WALLET_ALLOCATION_PAY_LOCK_KEY = "wallet_allocation_pay_lock_key:{0}"; + + /** + * 私域设备SN->绑定二维码地址 + */ + public static final String PRIVATE_SN_QR_CODE = "private_sphere_qr"; + + /** + * 私域设备SN->门店id + */ + public static final String PRIVATE_STORE_SN_BIND = "private_sphere_store_sn"; + + /** + * 私域设备SN->门店私域二维码地址 + */ + public static final String PRIVATE_STORE_SN_QR_CODE = "private_sphere_store_sn_qr_code"; + + /** + * IP限制key + */ + public static final String IP_LIMIT = "ip_limit"; } diff --git a/coolstore-partner-common/src/main/java/com/cool/store/enums/ErrorCodeEnum.java b/coolstore-partner-common/src/main/java/com/cool/store/enums/ErrorCodeEnum.java index 4c7d167e7..816a9fa6a 100644 --- a/coolstore-partner-common/src/main/java/com/cool/store/enums/ErrorCodeEnum.java +++ b/coolstore-partner-common/src/main/java/com/cool/store/enums/ErrorCodeEnum.java @@ -452,6 +452,8 @@ public enum ErrorCodeEnum { WALLET_RE_PAY_FAIL(1830019, "重新支付异常,请联系管理员", null), WALLET_PAY_CANNOT_CANCEL(1830020, "该交易无法取消", null), TOTAL_FEES_NEED_EQUAL(1830021, "分账总金额需与合计缴费金额一致", null), + + IP_LIMIT(1840000, "IP访问次数超限", null), ; diff --git a/coolstore-partner-dao/src/main/java/com/cool/store/dao/StoreDao.java b/coolstore-partner-dao/src/main/java/com/cool/store/dao/StoreDao.java index a6cf3e871..0a7f25f25 100644 --- a/coolstore-partner-dao/src/main/java/com/cool/store/dao/StoreDao.java +++ b/coolstore-partner-dao/src/main/java/com/cool/store/dao/StoreDao.java @@ -247,4 +247,11 @@ public class StoreDao { } return storeMapper.batchUpdateAddress(storeList); } + + /** + * 获取私域二维码 + */ + public String getPrivateSphereQrCode(String storeId){ + return storeMapper.getPrivateSphereQrCode(storeId); + } } diff --git a/coolstore-partner-dao/src/main/java/com/cool/store/mapper/StoreMapper.java b/coolstore-partner-dao/src/main/java/com/cool/store/mapper/StoreMapper.java index 074773481..287c32701 100644 --- a/coolstore-partner-dao/src/main/java/com/cool/store/mapper/StoreMapper.java +++ b/coolstore-partner-dao/src/main/java/com/cool/store/mapper/StoreMapper.java @@ -153,4 +153,6 @@ public interface StoreMapper { List getStoreAddress(@Param("flag") Integer flag, @Param("storeCode") String storeCode); int batchUpdateAddress(@Param("list") List storeList); + + String getPrivateSphereQrCode(@Param("storeId") String storeId); } diff --git a/coolstore-partner-dao/src/main/resources/mapper/StoreMapper.xml b/coolstore-partner-dao/src/main/resources/mapper/StoreMapper.xml index 9c25c13ff..57ee263d6 100644 --- a/coolstore-partner-dao/src/main/resources/mapper/StoreMapper.xml +++ b/coolstore-partner-dao/src/main/resources/mapper/StoreMapper.xml @@ -421,4 +421,8 @@ + diff --git a/coolstore-partner-model/src/main/java/com/cool/store/request/privatesphere/PrivateSphereBindRequest.java b/coolstore-partner-model/src/main/java/com/cool/store/request/privatesphere/PrivateSphereBindRequest.java new file mode 100644 index 000000000..dd6587dfd --- /dev/null +++ b/coolstore-partner-model/src/main/java/com/cool/store/request/privatesphere/PrivateSphereBindRequest.java @@ -0,0 +1,25 @@ +package com.cool.store.request.privatesphere; + +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import javax.validation.constraints.NotBlank; + +/** + *

+ * 门店设备sn绑定 + *

+ * + * @author wangff + * @since 2026/4/2 + */ +@Data +public class PrivateSphereBindRequest { + @ApiModelProperty("门店id") + @NotBlank(message = "门店id不能为空") + private String storeId; + + @ApiModelProperty("设备sn") + @NotBlank(message = "设备sn不能为空") + private String sn; +} diff --git a/coolstore-partner-model/src/main/java/com/cool/store/request/privatesphere/PrivateSphereSnRequest.java b/coolstore-partner-model/src/main/java/com/cool/store/request/privatesphere/PrivateSphereSnRequest.java new file mode 100644 index 000000000..643bffe5f --- /dev/null +++ b/coolstore-partner-model/src/main/java/com/cool/store/request/privatesphere/PrivateSphereSnRequest.java @@ -0,0 +1,24 @@ +package com.cool.store.request.privatesphere; + +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import javax.validation.constraints.NotBlank; + +/** + *

+ * 设备sn + *

+ * + * @author wangff + * @since 2026/4/2 + */ +@Data +public class PrivateSphereSnRequest { + @ApiModelProperty("设备sn") + @NotBlank(message = "设备sn不能为空") + private String sn; + + @ApiModelProperty(value = "客户端IP", hidden = true) + private String clientIp; +} diff --git a/coolstore-partner-model/src/main/java/com/cool/store/vo/privatesphere/PrivateSphereVO.java b/coolstore-partner-model/src/main/java/com/cool/store/vo/privatesphere/PrivateSphereVO.java new file mode 100644 index 000000000..aac7bbd59 --- /dev/null +++ b/coolstore-partner-model/src/main/java/com/cool/store/vo/privatesphere/PrivateSphereVO.java @@ -0,0 +1,40 @@ +package com.cool.store.vo.privatesphere; + +import io.swagger.annotations.ApiModelProperty; +import lombok.AllArgsConstructor; +import lombok.Data; + +/** + *

+ * 私域二维码VO + *

+ * + * @author wangff + * @since 2026/4/2 + */ +@Data +@AllArgsConstructor +public class PrivateSphereVO { + @ApiModelProperty("状态,0正常,1未绑定sn,2门店未维护私域二维码,3IP限制") + private Integer status; + + @ApiModelProperty("二维码地址") + private String qrCodeUrl; + + public PrivateSphereVO(String url) { + this.qrCodeUrl = url; + this.status = 0; + } + + public static PrivateSphereVO unbind() { + return new PrivateSphereVO(1, null); + } + + public static PrivateSphereVO noMaintain() { + return new PrivateSphereVO(2, null); + } + + public static PrivateSphereVO ipLimit() { + return new PrivateSphereVO(3, null); + } +} diff --git a/coolstore-partner-service/pom.xml b/coolstore-partner-service/pom.xml index ff45bc16f..5a9b5fd1b 100644 --- a/coolstore-partner-service/pom.xml +++ b/coolstore-partner-service/pom.xml @@ -127,6 +127,11 @@ com.aliyun alibabacloud-dysmsapi20170525 + + + com.google.zxing + core + \ No newline at end of file diff --git a/coolstore-partner-service/src/main/java/com/cool/store/service/privatesphere/PrivateSphereQrService.java b/coolstore-partner-service/src/main/java/com/cool/store/service/privatesphere/PrivateSphereQrService.java new file mode 100644 index 000000000..e52549d92 --- /dev/null +++ b/coolstore-partner-service/src/main/java/com/cool/store/service/privatesphere/PrivateSphereQrService.java @@ -0,0 +1,39 @@ +package com.cool.store.service.privatesphere; + +import com.cool.store.request.privatesphere.PrivateSphereBindRequest; +import com.cool.store.request.privatesphere.PrivateSphereSnRequest; +import com.cool.store.vo.privatesphere.PrivateSphereVO; + +/** + *

+ * 私域二维码服务 + *

+ * + * @author wangff + * @since 2026/4/1 + */ +public interface PrivateSphereQrService { + + /** + * 生成二维码并上传到OSS + * 相同sn优先从redis中获取二维码图片路径 + * + * @param request 设备sn + * @return 二维码图片的OSS路径 + */ + PrivateSphereVO generateQrCode(PrivateSphereSnRequest request); + + /** + * 门店绑定设备sn + * @param request 门店设备sn绑定 + * @return 是否成功 + */ + Boolean bindStoreSn(PrivateSphereBindRequest request); + + /** + * 根据设备sn获取门店私域二维码 + * @param request 设备sn + * @return 私域二维码 + */ + PrivateSphereVO getPrivateSphereQr(PrivateSphereSnRequest request); +} diff --git a/coolstore-partner-service/src/main/java/com/cool/store/service/privatesphere/impl/PrivateSphereQrServiceImpl.java b/coolstore-partner-service/src/main/java/com/cool/store/service/privatesphere/impl/PrivateSphereQrServiceImpl.java new file mode 100644 index 000000000..eee511037 --- /dev/null +++ b/coolstore-partner-service/src/main/java/com/cool/store/service/privatesphere/impl/PrivateSphereQrServiceImpl.java @@ -0,0 +1,138 @@ +package com.cool.store.service.privatesphere.impl; + +import cn.hutool.extra.qrcode.QrCodeUtil; +import cn.hutool.extra.qrcode.QrConfig; +import com.cool.store.constants.RedisConstant; +import com.cool.store.dao.StoreDao; +import com.cool.store.enums.ErrorCodeEnum; +import com.cool.store.exception.ServiceException; +import com.cool.store.oss.OssClientService; +import com.cool.store.request.privatesphere.PrivateSphereBindRequest; +import com.cool.store.request.privatesphere.PrivateSphereSnRequest; +import com.cool.store.service.privatesphere.PrivateSphereQrService; +import com.cool.store.utils.RedisUtilPool; +import com.cool.store.utils.poi.StringUtils; +import com.cool.store.vo.privatesphere.PrivateSphereVO; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; + +/** + *

+ * 私域二维码服务实现 + *

+ * + * @author wangff + * @since 2026/4/1 + */ +@Service +@Slf4j +@RequiredArgsConstructor +public class PrivateSphereQrServiceImpl implements PrivateSphereQrService { + + private final RedisUtilPool redisUtilPool; + private final OssClientService ossClientService; + private final StoreDao storeDao; + + /** + * 二维码尺寸 + */ + private static final int QR_CODE_SIZE = 1280; + /** + * sn缓存时间 + */ + private static final int SN_CACHE_TIME = 86400; + /** + * IP限制时间(1小时) + */ + private static final int IP_LIMIT_TIME = 3600; + /** + * IP每小时最大生成次数 + */ + private static final int IP_MAX_COUNT = 5; + + @Override + public PrivateSphereVO generateQrCode(PrivateSphereSnRequest request) { + // 先从Redis中获取缓存 + String cachedUrl = redisUtilPool.hashGet(RedisConstant.PRIVATE_SN_QR_CODE, request.getSn()); + if (StringUtils.isNotBlank(cachedUrl)) { + return new PrivateSphereVO(cachedUrl); + } + verify(request.getClientIp(), "rq_ge_limit"); + // 生成二维码并上传到OSS + try { + String qrCodeUrl = generateAndUploadQrCode(request.getSn()); + // 存储到Redis + redisUtilPool.hashSet(RedisConstant.PRIVATE_SN_QR_CODE, request.getSn(), qrCodeUrl, SN_CACHE_TIME); + log.info("生成二维码并上传成功,sn: {}, url: {}", request.getSn(), qrCodeUrl); + return new PrivateSphereVO(qrCodeUrl); + } catch (Exception e) { + log.error("生成二维码失败,sn: {}", request.getSn(), e); + return null; + } + } + + private void verify(String ip, String suffix) { + // IP限制检查:同一IP一小时内最多生成5次 + if (StringUtils.isNotBlank(ip)) { + String countStr = redisUtilPool.hashGet(RedisConstant.IP_LIMIT + "_" + suffix, ip); + int count = StringUtils.isNotBlank(countStr) ? Integer.parseInt(countStr) : 0; + if (count >= IP_MAX_COUNT) { + log.error("IP生成次数超限,ip: {}, count: {}", ip, count); + throw new ServiceException(ErrorCodeEnum.IP_LIMIT); + } + // 增加计数 + redisUtilPool.hashSet(RedisConstant.IP_LIMIT + "_" + suffix, ip, String.valueOf(count + 1), IP_LIMIT_TIME); + } + } + + @Override + public Boolean bindStoreSn(PrivateSphereBindRequest request) { + redisUtilPool.hashSet(RedisConstant.PRIVATE_STORE_SN_BIND, request.getSn(), request.getStoreId(), SN_CACHE_TIME); + return true; + } + + @Override + public PrivateSphereVO getPrivateSphereQr(PrivateSphereSnRequest request) { + String storeId = redisUtilPool.hashGet(RedisConstant.PRIVATE_STORE_SN_BIND, request.getSn()); + if (StringUtils.isBlank(storeId)) { + return PrivateSphereVO.unbind(); + } + String privateSphereQrCode = redisUtilPool.hashGet(RedisConstant.PRIVATE_STORE_SN_QR_CODE, request.getSn()); + if (privateSphereQrCode == null) { + privateSphereQrCode = storeDao.getPrivateSphereQrCode(storeId); + redisUtilPool.hashSet(RedisConstant.PRIVATE_STORE_SN_QR_CODE, request.getSn(), privateSphereQrCode == null ? "" : privateSphereQrCode, 120); + } + if (StringUtils.isBlank(privateSphereQrCode)) { + return PrivateSphereVO.noMaintain(); + } + return new PrivateSphereVO(privateSphereQrCode); + } + + /** + * 生成二维码并上传到OSS + * + * @param sn 二维码内容 + * @return OSS图片路径 + */ + private String generateAndUploadQrCode(String sn) throws Exception { + // 配置二维码 + QrConfig qrConfig = new QrConfig(QR_CODE_SIZE, QR_CODE_SIZE); + qrConfig.setMargin(2); + + // 生成二维码图片到内存 + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + QrCodeUtil.generate(sn, qrConfig, "png", outputStream); + + String fileName = "qrcode" + sn + ".png"; + + // 上传到OSS + ByteArrayInputStream inputStream = new ByteArrayInputStream(outputStream.toByteArray()); + long contentLength = outputStream.size(); + + return ossClientService.putObjectWithoutPrefix(fileName, inputStream, contentLength, "image/png"); + } +} \ No newline at end of file diff --git a/coolstore-partner-web/src/main/java/com/cool/store/aspect/DebounceAspect.java b/coolstore-partner-web/src/main/java/com/cool/store/aspect/DebounceAspect.java new file mode 100644 index 000000000..d337c115a --- /dev/null +++ b/coolstore-partner-web/src/main/java/com/cool/store/aspect/DebounceAspect.java @@ -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; + +/** + *

+ * 防抖切面 + *

+ * + * @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; + } +} diff --git a/coolstore-partner-web/src/main/java/com/cool/store/config/OpenApiValidateFilter.java b/coolstore-partner-web/src/main/java/com/cool/store/config/OpenApiValidateFilter.java index 8417606dd..b749a3ee1 100644 --- a/coolstore-partner-web/src/main/java/com/cool/store/config/OpenApiValidateFilter.java +++ b/coolstore-partner-web/src/main/java/com/cool/store/config/OpenApiValidateFilter.java @@ -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 oldUrlMapping = new ArrayList<>(Arrays.asList( "/zxjp/open/v1/statusRefresh","/zxjp/open/v1/changePaymentStatus", diff --git a/coolstore-partner-web/src/main/java/com/cool/store/controller/webb/OpenApiController.java b/coolstore-partner-web/src/main/java/com/cool/store/controller/webb/OpenApiController.java index ec8fc1b54..a651c087f 100644 --- a/coolstore-partner-web/src/main/java/com/cool/store/controller/webb/OpenApiController.java +++ b/coolstore-partner-web/src/main/java/com/cool/store/controller/webb/OpenApiController.java @@ -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 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 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 getPrivateSphereQr(@RequestBody @Validated PrivateSphereSnRequest request, HttpServletRequest httpRequest) { + request.setClientIp(HttpHelper.getIpAddr(httpRequest)); + return ApiResponse.success(privateSphereQrService.getPrivateSphereQr(request)); + } } diff --git a/coolstore-partner-web/src/main/java/com/cool/store/controller/webc/MiniPrivateSphereQrController.java b/coolstore-partner-web/src/main/java/com/cool/store/controller/webc/MiniPrivateSphereQrController.java new file mode 100644 index 000000000..3158fd97b --- /dev/null +++ b/coolstore-partner-web/src/main/java/com/cool/store/controller/webc/MiniPrivateSphereQrController.java @@ -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; + +/** + *

+ * Mini门店私域二维码 + *

+ * + * @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 bindStoreSn(@RequestBody @Validated PrivateSphereBindRequest request) { + return ResponseResult.success(privateSphereQrService.bindStoreSn(request)); + } +} diff --git a/pom.xml b/pom.xml index 7ee22a7a9..e6df40d21 100644 --- a/pom.xml +++ b/pom.xml @@ -238,6 +238,12 @@ 1.70 + + + com.google.zxing + core + 3.4.1 +