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:
王非凡
2026-04-08 10:44:13 +00:00
committed by 正新
parent 5ff5673914
commit 68cef4b83a
17 changed files with 562 additions and 1 deletions

View File

@@ -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;
/**
* <p>
* 私域二维码服务
* </p>
*
* @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);
}

View File

@@ -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;
/**
* <p>
* 私域二维码服务实现
* </p>
*
* @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");
}
}