You've already forked FrameTour-BE
feat(puzzle): 新增微信小程序二维码生成功能
- 在DataSourceContext中新增scenicId字段用于景区关联 - 实现WechatQrcodeDataSourceStrategy策略类,支持生成并上传微信小程序码 - 扩展DataSourceType枚举,增加WECHAT_QRCODE类型 - 修改PuzzleElementFillEngine执行方法,支持传入scenicId参数 - 在PuzzleGenerateServiceImpl中集成二维码自动生成逻辑 - 新增generateWechatQrcode方法用于生成并上传小程序码到OSS - 完善日志记录和异常处理机制 - 添加必要的工具类和存储服务依赖注入
This commit is contained in:
@@ -2,6 +2,7 @@ package com.ycwl.basic.puzzle.service.impl;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.json.JSONUtil;
|
||||
import com.ycwl.basic.model.pc.mp.MpConfigEntity;
|
||||
import com.ycwl.basic.puzzle.dto.PuzzleGenerateRequest;
|
||||
import com.ycwl.basic.puzzle.dto.PuzzleGenerateResponse;
|
||||
import com.ycwl.basic.puzzle.entity.PuzzleElementEntity;
|
||||
@@ -13,7 +14,9 @@ import com.ycwl.basic.puzzle.mapper.PuzzleGenerationRecordMapper;
|
||||
import com.ycwl.basic.puzzle.mapper.PuzzleTemplateMapper;
|
||||
import com.ycwl.basic.puzzle.service.IPuzzleGenerateService;
|
||||
import com.ycwl.basic.puzzle.util.PuzzleImageRenderer;
|
||||
import com.ycwl.basic.repository.ScenicRepository;
|
||||
import com.ycwl.basic.storage.StorageFactory;
|
||||
import com.ycwl.basic.utils.WxMpUtil;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
@@ -22,7 +25,10 @@ import javax.imageio.ImageIO;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
@@ -45,12 +51,18 @@ public class PuzzleGenerateServiceImpl implements IPuzzleGenerateService {
|
||||
private final PuzzleGenerationRecordMapper recordMapper;
|
||||
private final PuzzleImageRenderer imageRenderer;
|
||||
private final PuzzleElementFillEngine fillEngine;
|
||||
private final ScenicRepository scenicRepository;
|
||||
|
||||
@Override
|
||||
public PuzzleGenerateResponse generate(PuzzleGenerateRequest request) {
|
||||
long startTime = System.currentTimeMillis();
|
||||
log.info("开始生成拼图: templateCode={}, userId={}, orderId={}",
|
||||
request.getTemplateCode(), request.getUserId(), request.getOrderId());
|
||||
log.info("开始生成拼图: templateCode={}, userId={}, faceId={}",
|
||||
request.getTemplateCode(), request.getUserId(), request.getFaceId());
|
||||
|
||||
// 业务层校验:faceId 必填
|
||||
if (request.getFaceId() == null) {
|
||||
throw new IllegalArgumentException("人脸ID不能为空");
|
||||
}
|
||||
|
||||
// 1. 查询模板和元素
|
||||
PuzzleTemplateEntity template = templateMapper.getByCode(request.getTemplateCode());
|
||||
@@ -75,7 +87,7 @@ public class PuzzleGenerateServiceImpl implements IPuzzleGenerateService {
|
||||
Comparator.nullsFirst(Comparator.naturalOrder())));
|
||||
|
||||
// 4. 准备dynamicData(合并自动填充和手动数据)
|
||||
Map<String, String> finalDynamicData = buildDynamicData(template, request, resolvedScenicId);
|
||||
Map<String, String> finalDynamicData = buildDynamicData(template, request, resolvedScenicId, elements);
|
||||
|
||||
// 5. 创建生成记录
|
||||
PuzzleGenerationRecordEntity record = createRecord(template, request, resolvedScenicId);
|
||||
@@ -131,7 +143,7 @@ public class PuzzleGenerateServiceImpl implements IPuzzleGenerateService {
|
||||
record.setTemplateId(template.getId());
|
||||
record.setTemplateCode(template.getCode());
|
||||
record.setUserId(request.getUserId());
|
||||
record.setOrderId(request.getOrderId());
|
||||
record.setFaceId(request.getFaceId());
|
||||
record.setBusinessType(request.getBusinessType());
|
||||
record.setScenicId(scenicId);
|
||||
record.setStatus(0); // 生成中
|
||||
@@ -198,15 +210,31 @@ public class PuzzleGenerateServiceImpl implements IPuzzleGenerateService {
|
||||
*/
|
||||
private Map<String, String> buildDynamicData(PuzzleTemplateEntity template,
|
||||
PuzzleGenerateRequest request,
|
||||
Long scenicId) {
|
||||
Long scenicId,
|
||||
List<PuzzleElementEntity> elements) {
|
||||
Map<String, String> dynamicData = new HashMap<>();
|
||||
|
||||
// 0. 检查是否需要自动生成 travelResultWxaCode 二维码
|
||||
if (request.getFaceId() != null && scenicId != null) {
|
||||
boolean hasTravelResultWxaCode = elements.stream()
|
||||
.anyMatch(e -> "travelResultWxaCode".equals(e.getElementKey()));
|
||||
|
||||
if (hasTravelResultWxaCode && !dynamicDataContainsKey(request.getDynamicData(), "travelResultWxaCode")) {
|
||||
String qrcodeUrl = generateWechatQrcode(request.getFaceId(), scenicId);
|
||||
if (qrcodeUrl != null) {
|
||||
dynamicData.put("travelResultWxaCode", qrcodeUrl);
|
||||
log.info("自动生成微信小程序二维码成功: faceId={}, url={}", request.getFaceId(), qrcodeUrl);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 1. 自动填充(基于faceId和规则)
|
||||
if (request.getFaceId() != null) {
|
||||
try {
|
||||
Map<String, String> autoFilled = fillEngine.execute(
|
||||
template.getId(),
|
||||
request.getFaceId()
|
||||
request.getFaceId(),
|
||||
scenicId
|
||||
);
|
||||
if (autoFilled != null && !autoFilled.isEmpty()) {
|
||||
dynamicData.putAll(autoFilled);
|
||||
@@ -228,6 +256,71 @@ public class PuzzleGenerateServiceImpl implements IPuzzleGenerateService {
|
||||
return dynamicData;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查dynamicData中是否包含指定key
|
||||
*/
|
||||
private boolean dynamicDataContainsKey(Map<String, String> dynamicData, String key) {
|
||||
return dynamicData != null && dynamicData.containsKey(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成微信小程序二维码
|
||||
*
|
||||
* @param faceId 人脸ID
|
||||
* @param scenicId 景区ID
|
||||
* @return 二维码URL,失败返回null
|
||||
*/
|
||||
private String generateWechatQrcode(Long faceId, Long scenicId) {
|
||||
File qrcode = null;
|
||||
try {
|
||||
// 获取景区的小程序配置
|
||||
MpConfigEntity scenicMpConfig = scenicRepository.getScenicMpConfig(scenicId);
|
||||
if (scenicMpConfig == null) {
|
||||
log.error("生成微信小程序二维码失败: 未找到景区[{}]的小程序配置", scenicId);
|
||||
return null;
|
||||
}
|
||||
|
||||
// 生成临时文件
|
||||
qrcode = new File("qrcode_" + faceId + "_" + UUID.randomUUID().toString().substring(0, 8) + ".jpg");
|
||||
|
||||
// 调用微信API生成小程序码
|
||||
WxMpUtil.generateUnlimitedWXAQRCode(
|
||||
scenicMpConfig.getAppId(),
|
||||
scenicMpConfig.getAppSecret(),
|
||||
"pages/videoSynthesis/from_face",
|
||||
faceId.toString(),
|
||||
qrcode
|
||||
);
|
||||
|
||||
// 上传到OSS
|
||||
try (FileInputStream fis = new FileInputStream(qrcode)) {
|
||||
String fileName = String.format("qrcode_%d_%s.jpg",
|
||||
faceId,
|
||||
UUID.randomUUID().toString().replace("-", "").substring(0, 16));
|
||||
|
||||
return StorageFactory.use().uploadFile(
|
||||
"image/jpeg",
|
||||
fis,
|
||||
"puzzle",
|
||||
"wechat_qrcode",
|
||||
fileName
|
||||
);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("生成微信小程序二维码失败: faceId={}, scenicId={}", faceId, scenicId, e);
|
||||
return null;
|
||||
} finally {
|
||||
// 清理临时文件
|
||||
if (qrcode != null && qrcode.exists()) {
|
||||
try {
|
||||
Files.delete(qrcode.toPath());
|
||||
} catch (Exception e) {
|
||||
log.warn("删除临时二维码文件失败: {}", qrcode.getAbsolutePath(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验模板与请求景区的合法性
|
||||
*
|
||||
|
||||
Reference in New Issue
Block a user