|
|
|
|
@ -0,0 +1,598 @@
|
|
|
|
|
package cn.iocoder.yudao.module.common.service.qrcordrecord;
|
|
|
|
|
|
|
|
|
|
import cn.hutool.core.util.StrUtil;
|
|
|
|
|
import cn.iocoder.yudao.module.common.controller.admin.qrcoderecord.vo.QrcodeRecordPageReqVO;
|
|
|
|
|
import cn.iocoder.yudao.module.common.controller.admin.qrcoderecord.vo.QrcodeRecordSaveReqVO;
|
|
|
|
|
import cn.iocoder.yudao.module.common.dal.dataobject.qrcoderecord.QrcodeRecordDO;
|
|
|
|
|
import cn.iocoder.yudao.module.common.dal.mysql.qrcoderecord.QrcodeRecordMapper;
|
|
|
|
|
import cn.iocoder.yudao.module.common.enums.CodeTypeEnum;
|
|
|
|
|
import cn.iocoder.yudao.module.common.enums.QrcodeBizTypeEnum;
|
|
|
|
|
import cn.iocoder.yudao.module.common.handler.QrcodeBizHandler;
|
|
|
|
|
import cn.iocoder.yudao.module.infra.api.file.FileApi;
|
|
|
|
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
|
|
|
|
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
|
|
|
|
import com.google.zxing.BarcodeFormat;
|
|
|
|
|
import com.google.zxing.EncodeHintType;
|
|
|
|
|
import com.google.zxing.MultiFormatWriter;
|
|
|
|
|
import com.google.zxing.client.j2se.MatrixToImageWriter;
|
|
|
|
|
import com.google.zxing.common.BitMatrix;
|
|
|
|
|
import lombok.extern.slf4j.Slf4j;
|
|
|
|
|
import org.springframework.beans.factory.annotation.Value;
|
|
|
|
|
import org.springframework.stereotype.Service;
|
|
|
|
|
import org.springframework.transaction.annotation.Transactional;
|
|
|
|
|
import org.springframework.validation.annotation.Validated;
|
|
|
|
|
|
|
|
|
|
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
|
|
|
|
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
import javax.annotation.Resource;
|
|
|
|
|
import javax.imageio.ImageIO;
|
|
|
|
|
|
|
|
|
|
import java.awt.image.BufferedImage;
|
|
|
|
|
import java.io.ByteArrayOutputStream;
|
|
|
|
|
import java.io.UnsupportedEncodingException;
|
|
|
|
|
import java.net.URLEncoder;
|
|
|
|
|
import java.nio.charset.StandardCharsets;
|
|
|
|
|
import java.time.LocalDate;
|
|
|
|
|
import java.util.HashMap;
|
|
|
|
|
import java.util.List;
|
|
|
|
|
import java.util.Map;
|
|
|
|
|
import java.util.stream.Collectors;
|
|
|
|
|
|
|
|
|
|
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
|
|
|
|
|
import static cn.iocoder.yudao.module.common.api.mold.enums.ErrorCodeConstants.RECORD_NOT_EXISTS;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 通用二维码记录 Service 实现类
|
|
|
|
|
*
|
|
|
|
|
* @author 必硕智能
|
|
|
|
|
*/
|
|
|
|
|
@Service
|
|
|
|
|
@Validated
|
|
|
|
|
@Slf4j
|
|
|
|
|
public class QrcodeRecordServiceImpl implements QrcodeRecordService {
|
|
|
|
|
|
|
|
|
|
@Resource
|
|
|
|
|
private FileApi fileApi;
|
|
|
|
|
|
|
|
|
|
@Value("${yudao.qrcode.scan-base-url}")
|
|
|
|
|
private String scanBaseUrl;
|
|
|
|
|
@Value("${yudao.qrcode.width:300}")
|
|
|
|
|
private Integer width;
|
|
|
|
|
@Value("${yudao.qrcode.height:300}")
|
|
|
|
|
private Integer height;
|
|
|
|
|
@Value("${yudao.qrcode.default-bucket:common-qrcode}")
|
|
|
|
|
private String defaultBucket;
|
|
|
|
|
|
|
|
|
|
@Value("${yudao.qrcode.fail-url}")
|
|
|
|
|
private String failUrl;
|
|
|
|
|
@Value("${yudao.qrcode.scan-base-url}")
|
|
|
|
|
private String serverBaseUrl;
|
|
|
|
|
|
|
|
|
|
@Value("#{${yudao.qrcode.buckets:{}}}")
|
|
|
|
|
private Map<String, String> bucketMap = new HashMap<>();
|
|
|
|
|
|
|
|
|
|
@Resource
|
|
|
|
|
private QrcodeRecordMapper qrcodeRecordMapper;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private final Map<String, QrcodeBizHandler> handlerMap;
|
|
|
|
|
|
|
|
|
|
public QrcodeRecordServiceImpl(List<QrcodeBizHandler> handlers) {
|
|
|
|
|
this.handlerMap = handlers.stream()
|
|
|
|
|
.collect(Collectors.toMap(h -> h.getBizType().toUpperCase(), h -> h));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public Long createRecord(QrcodeRecordSaveReqVO createReqVO) {
|
|
|
|
|
// 插入
|
|
|
|
|
QrcodeRecordDO record = BeanUtils.toBean(createReqVO, QrcodeRecordDO.class);
|
|
|
|
|
qrcodeRecordMapper.insert(record);
|
|
|
|
|
// 返回
|
|
|
|
|
return record.getId();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public void updateRecord(QrcodeRecordSaveReqVO updateReqVO) {
|
|
|
|
|
// 校验存在
|
|
|
|
|
validateRecordExists(updateReqVO.getId());
|
|
|
|
|
// 更新
|
|
|
|
|
QrcodeRecordDO updateObj = BeanUtils.toBean(updateReqVO, QrcodeRecordDO.class);
|
|
|
|
|
qrcodeRecordMapper.updateById(updateObj);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public void deleteRecord(Long id) {
|
|
|
|
|
// 校验存在
|
|
|
|
|
validateRecordExists(id);
|
|
|
|
|
// 删除
|
|
|
|
|
qrcodeRecordMapper.deleteById(id);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void validateRecordExists(Long id) {
|
|
|
|
|
if (qrcodeRecordMapper.selectById(id) == null) {
|
|
|
|
|
throw exception(RECORD_NOT_EXISTS);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public QrcodeRecordDO getRecord(Long id) {
|
|
|
|
|
return qrcodeRecordMapper.selectById(id);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public PageResult<QrcodeRecordDO> getRecordPage(QrcodeRecordPageReqVO pageReqVO) {
|
|
|
|
|
return qrcodeRecordMapper.selectPage(pageReqVO);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public void generateOrRefresh(QrcodeBizTypeEnum bizType, Long bizId, String bizCode, String scene) throws UnsupportedEncodingException {
|
|
|
|
|
if (bizType == null || bizId == null) {
|
|
|
|
|
throw new IllegalArgumentException("bizType 或 bizId 不能为空");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
String qrScene = StrUtil.blankToDefault(scene, "DETAIL");
|
|
|
|
|
|
|
|
|
|
// 1. 幂等查询(biz_type + biz_id + scene 唯一)
|
|
|
|
|
QrcodeRecordDO existed = qrcodeRecordMapper.selectOne(
|
|
|
|
|
new LambdaQueryWrapper<QrcodeRecordDO>()
|
|
|
|
|
.eq(QrcodeRecordDO::getBizType, bizType.getCode())
|
|
|
|
|
.eq(QrcodeRecordDO::getBizId, bizId)
|
|
|
|
|
.eq(QrcodeRecordDO::getBizCode,bizCode)
|
|
|
|
|
.eq(QrcodeRecordDO::getCodeType,"QR")
|
|
|
|
|
.eq(QrcodeRecordDO::getQrScene, qrScene)
|
|
|
|
|
.last("limit 1")
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// 2. 组装二维码内容(统一扫码入口)
|
|
|
|
|
String qrContent = "{\"type\":\"" + bizType.getCode()
|
|
|
|
|
+ "\",\"id\":" + bizId
|
|
|
|
|
+ (StrUtil.isNotBlank(bizCode) ? ",\"code\":\"" + bizCode + "\"" : "")
|
|
|
|
|
+ "}";
|
|
|
|
|
|
|
|
|
|
// 3. 生成二维码PNG
|
|
|
|
|
byte[] pngBytes = buildQrPng(qrContent, width, height);
|
|
|
|
|
|
|
|
|
|
// 4. 选择bucket + objectName
|
|
|
|
|
String bucket = resolveBucket(bizType); // 例如 yudao.qrcode.buckets.mold
|
|
|
|
|
String objectName = bizType.getCode().toLowerCase() + "/qr/" + LocalDate.now() + "/" + bizId + ".png";
|
|
|
|
|
String fileName = bizType.getCode().toLowerCase() + "_" + bizCode + ".png";
|
|
|
|
|
|
|
|
|
|
// 5. 上传文件(按你项目 FileApi 实际签名改)
|
|
|
|
|
// 如果支持bucket参数,用这个:
|
|
|
|
|
// FileRespDTO file = fileApi.createFile(bucket, objectName, "image/png", pngBytes);
|
|
|
|
|
|
|
|
|
|
// 默认上传
|
|
|
|
|
Map<String, String> file = fileApi.createFile(fileName, objectName, pngBytes);
|
|
|
|
|
String fileUrl = file.get("fileUrl");
|
|
|
|
|
|
|
|
|
|
// 6. upsert 记录
|
|
|
|
|
QrcodeRecordDO record = new QrcodeRecordDO();
|
|
|
|
|
record.setBizType(bizType.getCode());
|
|
|
|
|
record.setBizId(bizId);
|
|
|
|
|
record.setBizCode(bizCode);
|
|
|
|
|
record.setQrScene(qrScene);
|
|
|
|
|
record.setQrContent(qrContent);
|
|
|
|
|
record.setFileName(fileName);
|
|
|
|
|
record.setBucketName(bucket);
|
|
|
|
|
record.setObjectName(objectName);
|
|
|
|
|
record.setQrcodeFileUrl(fileUrl);
|
|
|
|
|
record.setMimeType("image/png");
|
|
|
|
|
record.setFileSize((long) pngBytes.length);
|
|
|
|
|
record.setStatus(1);
|
|
|
|
|
|
|
|
|
|
if (existed == null) {
|
|
|
|
|
qrcodeRecordMapper.insert(record);
|
|
|
|
|
} else {
|
|
|
|
|
record.setId(existed.getId());
|
|
|
|
|
qrcodeRecordMapper.updateById(record);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
// @Transactional(rollbackFor = Exception.class)
|
|
|
|
|
public void generateOrRefreshBarcode(QrcodeBizTypeEnum bizType, Long bizId, String bizCode, String scene) throws UnsupportedEncodingException {
|
|
|
|
|
if (bizType == null || bizId == null) {
|
|
|
|
|
throw new IllegalArgumentException("bizType 或 bizId 不能为空");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
String qrScene = StrUtil.blankToDefault(scene, "DETAIL");
|
|
|
|
|
|
|
|
|
|
QrcodeRecordDO existed = qrcodeRecordMapper.selectOne(
|
|
|
|
|
new LambdaQueryWrapper<QrcodeRecordDO>()
|
|
|
|
|
.eq(QrcodeRecordDO::getBizType, bizType.getCode())
|
|
|
|
|
.eq(QrcodeRecordDO::getBizId, bizId)
|
|
|
|
|
.eq(QrcodeRecordDO::getBizCode, bizCode)
|
|
|
|
|
.eq(QrcodeRecordDO::getCodeType, "BARCODE")
|
|
|
|
|
.eq(QrcodeRecordDO::getQrScene, qrScene)
|
|
|
|
|
.last("limit 1")
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
String barcodeContent = "{\"type\":\"" + bizType.getCode()
|
|
|
|
|
+ "\",\"id\":" + bizId
|
|
|
|
|
+ (StrUtil.isNotBlank(bizCode) ? ",\"code\":\"" + bizCode + "\"" : "")
|
|
|
|
|
+ "}";
|
|
|
|
|
|
|
|
|
|
int barcodeWidth = 600;
|
|
|
|
|
int barcodeHeight = 180;
|
|
|
|
|
byte[] pngBytes = buildBarcodePng(barcodeContent, barcodeWidth, barcodeHeight);
|
|
|
|
|
|
|
|
|
|
String bucket = resolveBucket(bizType);
|
|
|
|
|
String objectName = bizType.getCode().toLowerCase() + "/barcode/" + LocalDate.now() + "/" + bizId + ".png";
|
|
|
|
|
String fileName = bizType.getCode().toLowerCase() + "_" + bizCode + "_barcode.png";
|
|
|
|
|
|
|
|
|
|
Map<String, String> file = fileApi.createFile(fileName, objectName, pngBytes);
|
|
|
|
|
String fileUrl = file.get("fileUrl");
|
|
|
|
|
|
|
|
|
|
QrcodeRecordDO record = new QrcodeRecordDO();
|
|
|
|
|
record.setBizType(bizType.getCode());
|
|
|
|
|
record.setBizId(bizId);
|
|
|
|
|
record.setBizCode(bizCode);
|
|
|
|
|
record.setCodeType("BARCODE");
|
|
|
|
|
record.setQrScene(qrScene);
|
|
|
|
|
record.setQrContent(barcodeContent);
|
|
|
|
|
record.setFileName(fileName);
|
|
|
|
|
record.setBucketName(bucket);
|
|
|
|
|
record.setObjectName(objectName);
|
|
|
|
|
record.setQrcodeFileUrl(fileUrl);
|
|
|
|
|
record.setMimeType("image/png");
|
|
|
|
|
record.setFileSize((long) pngBytes.length);
|
|
|
|
|
record.setStatus(1);
|
|
|
|
|
|
|
|
|
|
if (existed == null) {
|
|
|
|
|
qrcodeRecordMapper.insert(record);
|
|
|
|
|
} else {
|
|
|
|
|
record.setId(existed.getId());
|
|
|
|
|
qrcodeRecordMapper.updateById(record);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private byte[] buildBarcodePng(String content, int width, int height) {
|
|
|
|
|
try {
|
|
|
|
|
Map<EncodeHintType, Object> hints = new HashMap<>();
|
|
|
|
|
hints.put(EncodeHintType.CHARACTER_SET, "UTF-8");
|
|
|
|
|
hints.put(EncodeHintType.MARGIN, 1);
|
|
|
|
|
BitMatrix matrix = new MultiFormatWriter().encode(content, BarcodeFormat.CODE_128, width, height, hints);
|
|
|
|
|
BufferedImage image = MatrixToImageWriter.toBufferedImage(matrix);
|
|
|
|
|
ByteArrayOutputStream os = new ByteArrayOutputStream();
|
|
|
|
|
ImageIO.write(image, "png", os);
|
|
|
|
|
return os.toByteArray();
|
|
|
|
|
} catch (Exception e) {
|
|
|
|
|
throw new RuntimeException("生成条形码失败", e);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public String buildScanTransitHtml(String type, Long id,String code) {
|
|
|
|
|
String fallback = normalizeUrl(StrUtil.blankToDefault(failUrl, "/"));
|
|
|
|
|
if (StrUtil.isBlank(type) || id == null) {
|
|
|
|
|
return buildRedirectHtml(fallback);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (StrUtil.isBlank(code)) {
|
|
|
|
|
return buildRedirectHtml(fallback);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QrcodeRecordDO record = qrcodeRecordMapper.selectOne(
|
|
|
|
|
new LambdaQueryWrapper<QrcodeRecordDO>()
|
|
|
|
|
.eq(QrcodeRecordDO::getBizType, type.trim().toUpperCase())
|
|
|
|
|
.eq(QrcodeRecordDO::getBizId, id)
|
|
|
|
|
.eq(QrcodeRecordDO::getQrScene, "DETAIL")
|
|
|
|
|
.last("limit 1")
|
|
|
|
|
);
|
|
|
|
|
if (record == null || !StrUtil.equals(record.getBizCode(), code)) {
|
|
|
|
|
return buildRedirectHtml(fallback);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
QrcodeBizHandler handler = handlerMap.get(type.trim().toUpperCase());
|
|
|
|
|
if (handler == null) {
|
|
|
|
|
return buildRedirectHtml(fallback);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
if (!handler.exists(id)) {
|
|
|
|
|
return buildRedirectHtml(fallback);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
String target = StrUtil.blankToDefault(handler.buildDeepLink(id), handler.buildH5Path(id));
|
|
|
|
|
if (StrUtil.isBlank(target)) {
|
|
|
|
|
return buildRedirectHtml(fallback);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return buildRedirectHtml(normalizeUrl(target));
|
|
|
|
|
} catch (Exception e) {
|
|
|
|
|
return buildRedirectHtml(fallback);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public String selectQrcodeUrlByIdAndCode(String bizType, Long bizId, String bizCode) {
|
|
|
|
|
if (StrUtil.isBlank(bizType) || bizId == null || StrUtil.isBlank(bizCode)) {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QrcodeRecordDO record = qrcodeRecordMapper.selectOne(Wrappers.<QrcodeRecordDO>lambdaQuery()
|
|
|
|
|
.eq(QrcodeRecordDO::getBizType, bizType)
|
|
|
|
|
.eq(QrcodeRecordDO::getBizId, bizId)
|
|
|
|
|
.eq(QrcodeRecordDO::getBizCode, bizCode)
|
|
|
|
|
.eq(QrcodeRecordDO::getStatus, 1)
|
|
|
|
|
.last("limit 1"));
|
|
|
|
|
|
|
|
|
|
if (record == null || StrUtil.isBlank(record.getQrcodeFileUrl())) {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
return record.getQrcodeFileUrl();
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public Map<String, Object> resolveScanBizId(String type, Long id, String code) {
|
|
|
|
|
Map<String, Object> result = new HashMap<>();
|
|
|
|
|
|
|
|
|
|
if (StrUtil.isBlank(type) || id == null || StrUtil.isBlank(code)) {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QrcodeRecordDO record = qrcodeRecordMapper.selectOne(
|
|
|
|
|
new LambdaQueryWrapper<QrcodeRecordDO>()
|
|
|
|
|
.eq(QrcodeRecordDO::getBizType, type)
|
|
|
|
|
.eq(QrcodeRecordDO::getBizId, id)
|
|
|
|
|
.eq(QrcodeRecordDO::getBizCode,code)
|
|
|
|
|
.eq(QrcodeRecordDO::getQrScene, "DETAIL")
|
|
|
|
|
.last("limit 1")
|
|
|
|
|
);
|
|
|
|
|
if (record == null) {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
QrcodeBizHandler handler = handlerMap.get(type.trim().toUpperCase());
|
|
|
|
|
if (handler == null) {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
result.put("type", type);
|
|
|
|
|
result.put("id", id);
|
|
|
|
|
return result;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private String normalizeUrl(String url) {
|
|
|
|
|
if (StrUtil.isBlank(url)) {
|
|
|
|
|
return "/";
|
|
|
|
|
}
|
|
|
|
|
if (StrUtil.startWithAny(url, "http://", "https://", "besure://")) {
|
|
|
|
|
return url;
|
|
|
|
|
}
|
|
|
|
|
String base = StrUtil.blankToDefault(serverBaseUrl, "");
|
|
|
|
|
if (StrUtil.isBlank(base)) {
|
|
|
|
|
return url.startsWith("/") ? url : "/" + url;
|
|
|
|
|
}
|
|
|
|
|
return StrUtil.removeSuffix(base, "/") + "/" + StrUtil.removePrefix(url, "/");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private String buildRedirectHtml(String targetUrl) {
|
|
|
|
|
String safe = escapeHtml(StrUtil.blankToDefault(targetUrl, "/"));
|
|
|
|
|
return "<!doctype html><html><head><meta charset=\"UTF-8\">"
|
|
|
|
|
+ "<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">"
|
|
|
|
|
+ "<meta http-equiv=\"Cache-Control\" content=\"no-store, no-cache, must-revalidate, max-age=0\">"
|
|
|
|
|
+ "<meta http-equiv=\"Pragma\" content=\"no-cache\">"
|
|
|
|
|
+ "<meta http-equiv=\"Expires\" content=\"0\">"
|
|
|
|
|
+ "<title>跳转中...</title></head><body>"
|
|
|
|
|
+ "<script>window.location.replace('" + safe + "');</script>"
|
|
|
|
|
+ "<noscript><a href=\"" + safe + "\">点击继续</a></noscript>"
|
|
|
|
|
+ "</body></html>";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private String escapeHtml(String s) {
|
|
|
|
|
return s.replace("&", "&")
|
|
|
|
|
.replace("<", "<")
|
|
|
|
|
.replace(">", ">")
|
|
|
|
|
.replace("\"", """)
|
|
|
|
|
.replace("'", "'");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private String resolveBucket(QrcodeBizTypeEnum bizType) {
|
|
|
|
|
if (bizType == null) {
|
|
|
|
|
return defaultBucket;
|
|
|
|
|
}
|
|
|
|
|
if (bucketMap == null || bucketMap.isEmpty()) {
|
|
|
|
|
return defaultBucket;
|
|
|
|
|
}
|
|
|
|
|
String bucket = bucketMap.get(bizType.getCode().toLowerCase());
|
|
|
|
|
return StrUtil.blankToDefault(bucket, defaultBucket);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private byte[] buildQrPng(String content, int width, int height) {
|
|
|
|
|
try {
|
|
|
|
|
Map<EncodeHintType, Object> hints = new HashMap<>();
|
|
|
|
|
hints.put(EncodeHintType.CHARACTER_SET, "UTF-8");
|
|
|
|
|
hints.put(EncodeHintType.MARGIN, 1);
|
|
|
|
|
BitMatrix matrix = new MultiFormatWriter()
|
|
|
|
|
.encode(content, BarcodeFormat.QR_CODE, width, height, hints);
|
|
|
|
|
BufferedImage image = MatrixToImageWriter.toBufferedImage(matrix);
|
|
|
|
|
ByteArrayOutputStream os = new ByteArrayOutputStream();
|
|
|
|
|
ImageIO.write(image, "png", os);
|
|
|
|
|
return os.toByteArray();
|
|
|
|
|
} catch (Exception e) {
|
|
|
|
|
throw new RuntimeException("生成二维码失败", e);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
@Transactional(rollbackFor = Exception.class)
|
|
|
|
|
public void deleteByBiz(QrcodeBizTypeEnum bizType, Long bizId) {
|
|
|
|
|
if (bizType == null || bizId == null) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
List<QrcodeRecordDO> records = qrcodeRecordMapper.selectList(
|
|
|
|
|
Wrappers.<QrcodeRecordDO>lambdaQuery()
|
|
|
|
|
.eq(QrcodeRecordDO::getBizType, bizType.getCode())
|
|
|
|
|
.eq(QrcodeRecordDO::getBizId, bizId)
|
|
|
|
|
);
|
|
|
|
|
if (records == null || records.isEmpty()) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (QrcodeRecordDO record : records) {
|
|
|
|
|
try {
|
|
|
|
|
String objectName = record.getObjectName();
|
|
|
|
|
if (StrUtil.isBlank(objectName)) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 通过 path(objectName) 找到 infra_file
|
|
|
|
|
Long id = fileApi.getFileByPath(objectName);
|
|
|
|
|
if (id == null) {
|
|
|
|
|
// 可选:log.warn("未找到文件记录, qrcodeId={}, path={}", record.getId(), objectName);
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 用文件主键删除(会删存储+删infra_file记录)
|
|
|
|
|
fileApi.deleteFile(id);
|
|
|
|
|
} catch (Exception e) {
|
|
|
|
|
// 你可按策略选择:继续 or 抛异常回滚
|
|
|
|
|
// 建议先打日志
|
|
|
|
|
log.warn("删除文件失败, qrcodeId={}, path={}", record.getId(), record.getObjectName(), e);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
qrcodeRecordMapper.delete(
|
|
|
|
|
Wrappers.<QrcodeRecordDO>lambdaQuery()
|
|
|
|
|
.eq(QrcodeRecordDO::getBizType, bizType.getCode())
|
|
|
|
|
.eq(QrcodeRecordDO::getBizId, bizId)
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
@Transactional(rollbackFor = Exception.class)
|
|
|
|
|
public void regenerateByCodeType(QrcodeBizTypeEnum bizType, Long bizId, String bizCode, String scene, String codeType)
|
|
|
|
|
throws UnsupportedEncodingException {
|
|
|
|
|
if (bizType == null || bizId == null) {
|
|
|
|
|
throw new IllegalArgumentException("bizType 或 bizId 不能为空");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
String qrScene = StrUtil.blankToDefault(scene, "DETAIL");
|
|
|
|
|
String normalizedCodeType = StrUtil.blankToDefault(codeType, "QR").trim().toUpperCase();
|
|
|
|
|
if (!StrUtil.equalsAny(normalizedCodeType, "QR", "BARCODE")) {
|
|
|
|
|
throw new IllegalArgumentException("codeType 仅支持 QR 或 BARCODE");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 1. 按业务唯一键 + 码类型查询
|
|
|
|
|
QrcodeRecordDO existed = qrcodeRecordMapper.selectOne(
|
|
|
|
|
new LambdaQueryWrapper<QrcodeRecordDO>()
|
|
|
|
|
.eq(QrcodeRecordDO::getBizType, bizType.getCode())
|
|
|
|
|
.eq(QrcodeRecordDO::getBizId, bizId)
|
|
|
|
|
.eq(QrcodeRecordDO::getBizCode, bizCode)
|
|
|
|
|
// .eq(QrcodeRecordDO::getCodeType, normalizedCodeType)
|
|
|
|
|
.eq(QrcodeRecordDO::getQrScene, qrScene)
|
|
|
|
|
.last("limit 1")
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// 2. 组装内容
|
|
|
|
|
String content = "{\"type\":\"" + bizType.getCode()
|
|
|
|
|
+ "\",\"id\":" + bizId
|
|
|
|
|
+ (StrUtil.isNotBlank(bizCode) ? ",\"code\":\"" + bizCode + "\"" : "")
|
|
|
|
|
+ "}";
|
|
|
|
|
|
|
|
|
|
// 3. 生成图片(按类型)
|
|
|
|
|
byte[] pngBytes;
|
|
|
|
|
String objectDir;
|
|
|
|
|
String fileNameSuffix;
|
|
|
|
|
if (StrUtil.equals(normalizedCodeType, "BARCODE")) {
|
|
|
|
|
pngBytes = buildBarcodePng(content, 600, 180);
|
|
|
|
|
objectDir = "barcode";
|
|
|
|
|
fileNameSuffix = "_barcode.png";
|
|
|
|
|
} else {
|
|
|
|
|
pngBytes = buildQrPng(content, width, height);
|
|
|
|
|
objectDir = "qr";
|
|
|
|
|
fileNameSuffix = ".png";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 4. 组织文件信息
|
|
|
|
|
String bucket = resolveBucket(bizType);
|
|
|
|
|
String objectName = bizType.getCode().toLowerCase() + "/" + objectDir + "/" + LocalDate.now() + "/" + bizId + ".png";
|
|
|
|
|
String fileName = bizType.getCode().toLowerCase() + "_" + StrUtil.blankToDefault(bizCode, String.valueOf(bizId)) + fileNameSuffix;
|
|
|
|
|
|
|
|
|
|
// 5. 如果已存在,先删旧文件(MinIO + infra_file)
|
|
|
|
|
if (existed != null && StrUtil.isNotBlank(existed.getObjectName())) {
|
|
|
|
|
try {
|
|
|
|
|
Long fileId = fileApi.getFileByPath(existed.getObjectName());
|
|
|
|
|
if (fileId != null) {
|
|
|
|
|
fileApi.deleteFile(fileId);
|
|
|
|
|
}
|
|
|
|
|
} catch (Exception e) {
|
|
|
|
|
log.warn("删除旧二维码/条形码文件失败, qrcodeId={}, path={}", existed.getId(), existed.getObjectName(), e);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 6. 上传新文件
|
|
|
|
|
Map<String, String> file = fileApi.createFile(fileName, objectName, pngBytes);
|
|
|
|
|
String fileUrl = file.get("fileUrl");
|
|
|
|
|
|
|
|
|
|
// 7. upsert 记录
|
|
|
|
|
QrcodeRecordDO record = new QrcodeRecordDO();
|
|
|
|
|
record.setBizType(bizType.getCode());
|
|
|
|
|
record.setBizId(bizId);
|
|
|
|
|
record.setBizCode(bizCode);
|
|
|
|
|
record.setCodeType(normalizedCodeType);
|
|
|
|
|
record.setQrScene(qrScene);
|
|
|
|
|
record.setQrContent(content);
|
|
|
|
|
record.setFileName(fileName);
|
|
|
|
|
record.setBucketName(bucket);
|
|
|
|
|
record.setObjectName(objectName);
|
|
|
|
|
record.setQrcodeFileUrl(fileUrl);
|
|
|
|
|
record.setMimeType("image/png");
|
|
|
|
|
record.setFileSize((long) pngBytes.length);
|
|
|
|
|
record.setStatus(1);
|
|
|
|
|
|
|
|
|
|
if (existed == null) {
|
|
|
|
|
qrcodeRecordMapper.insert(record);
|
|
|
|
|
} else {
|
|
|
|
|
record.setId(existed.getId());
|
|
|
|
|
qrcodeRecordMapper.updateById(record);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
@Transactional(rollbackFor = Exception.class)
|
|
|
|
|
public void generateOrRefresh(QrcodeBizTypeEnum bizType,
|
|
|
|
|
Long bizId,
|
|
|
|
|
String bizCode,
|
|
|
|
|
String scene,
|
|
|
|
|
CodeTypeEnum codeType) throws UnsupportedEncodingException {
|
|
|
|
|
|
|
|
|
|
if (codeType == null) {
|
|
|
|
|
log.info("codeType为空");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (codeType == CodeTypeEnum.BARCODE) {
|
|
|
|
|
generateOrRefreshBarcode(bizType, bizId, bizCode, scene);
|
|
|
|
|
} else {
|
|
|
|
|
generateOrRefresh(bizType, bizId, bizCode, scene);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
}
|