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:
@@ -127,6 +127,11 @@
|
||||
<groupId>com.aliyun</groupId>
|
||||
<artifactId>alibabacloud-dysmsapi20170525</artifactId>
|
||||
</dependency>
|
||||
<!-- 二维码生成 -->
|
||||
<dependency>
|
||||
<groupId>com.google.zxing</groupId>
|
||||
<artifactId>core</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user