Compare commits

...

10 Commits

Author SHA1 Message Date
5cc32ddf61 feat(order): 优化订单查询逻辑以支持景区关联数据
All checks were successful
ZhenTu-BE/pipeline/head This commit looks good
- 在member_plog_data子查询中增加scenic_id字段
- 添加puzzle_template与puzzle_generation_record的左连接
- 修改member_plog_data与其他表的连接条件以兼容景区ID匹配
- 支持通过goods_id或scenic_id关联member_plog_data表
- 提升订单详情中图片资源定位准确性
2025-12-14 00:04:06 +08:00
07987835ec fix(face): 修复人脸购买逻辑判断问题
- 修改AppPuzzleController中的人脸购买判断逻辑
- 增加对景区是否购买的前置判断
- 优化FaceServiceImpl中的人脸购买状态设置逻辑
- 确保模板购买状态的准确判断
- 避免重复查询价格计算服务
2025-12-13 23:47:29 +08:00
0a3f4119d7 feat(price): 添加pLog图商品到景区打包列表
- 在PHOTO_LOG情况下增加SimpleGoodsRespVO对象
- 对象包含景区ID、名称和产品类型信息
- 确保pLog图模板能正确显示在商品列表中
2025-12-13 23:43:31 +08:00
51c7de2474 feat(fill): 新增设备缩略图数据源策略
- 实现DeviceThumbImageDataSourceStrategy类,支持根据deviceIndex获取设备缩略图
- 支持从过滤后的机位列表或直接通过deviceIndex两种方式查询数据
- 默认使用LATEST排序策略,可配置type类型(默认为图片类型2)
- 添加对filteredDeviceIds上下文参数的支持,提升数据筛选灵活性
- 增强日志记录,便于调试和问题追踪
- 在DataSourceType枚举中新增DEVICE_THUMB_IMAGE类型定义
2025-12-13 21:47:41 +08:00
773d7f2254 refactor(service): 优化拼图模板处理逻辑
- 将遍历所有拼图模板改为只处理第一个模板
- 简化内容页面对象创建流程
- 保留原有的购买状态检查和价格计算逻辑
- 提高代码执行效率,避免不必要的循环操作
2025-12-13 21:41:11 +08:00
af131131ed fix(task): 修改任务创建接口中的自动标志参数类型
- 将 createTaskByFaceIdAndTemplateId 方法的 automatic 参数从 int 改为 boolean
- 更新方法实现以适配新的布尔值参数
- 移除无用的导入类和未使用的代码
- 调整任务实体中 automatic 字段的赋值逻辑以匹配新类型
- 删除已弃用的旧版重载方法
- 确保所有调用点传递正确的布尔值而非整数
- 优化代码结构并提高可读性
2025-12-13 19:19:21 +08:00
3f6f1508c5 feat(order): 增加faceId校验的订单购买检查功能
- 新增checkUserBuyFaceItem方法,支持校验用户购买商品时的人脸ID匹配
- 修改PriceBiz中商品类型设置,从13改为5
- 更新FaceServiceImpl中的购买检查逻辑,使用新的带faceId校验的方法
- 调整OrderServiceImpl中订单项的goodsType和goodsId设置逻辑
- 移除旧的checkUserBuyItem方法及相关缓存逻辑
- 新增ORDER_USER_FACE_TYPE_BUY_ITEM_CACHE_KEY缓存键定义
2025-12-13 19:00:25 +08:00
dbee1d9709 feat(puzzle): 使用虚拟线程优化拼图模板批量生成性能
- 将原有的串行模板生成逻辑改为并行处理
- 使用虚拟线程池提升高并发场景下的执行效率
- 通过 CompletableFuture 异步执行每个模板的生成任务
- 保留原有日志记录和异常处理机制
- 统计成功与失败数量并输出汇总日志
2025-12-13 17:38:48 +08:00
83d1096fdb feat(order): 添加vlog视频模板购买逻辑
- 在订单业务中处理类型为-1的商品(vlog视频模板)
- 调用视频仓库方法设置用户已购买模板
- 新增setUserIsBuyTemplate方法实现模板购买状态更新
- 查询面部关联视频并更新购买状态及清理缓存

feat(price): 增加拼图商品列表查询功能

- 在价格服务中加入对拼图模板的查询
- 设置拼图商品类型为13
- 将拼图模板信息加入返回的商品列表中
2025-12-13 14:13:59 +08:00
82925d203c feat(config): 添加优雅关机配置和智谱API密钥
- 在开发环境配置中启用优雅关机
- 设置每个关机阶段超时时间为60秒
- 添加智谱AI服务的API密钥配置
- 统一开发和生产环境的基础配置结构
2025-12-12 17:19:29 +08:00
17 changed files with 337 additions and 163 deletions

View File

@@ -230,6 +230,9 @@ public class OrderBiz {
orderRepository.updateOrder(orderId, orderUpdate);
orderItems.forEach(item -> {
switch (item.getGoodsType()) {
case -1: // vlog视频模板
videoRepository.setUserIsBuyTemplate(order.getMemberId(), item.getGoodsId(), order.getId(), order.getFaceId());
break;
case 0: // vlog视频
videoRepository.setUserIsBuyItem(order.getMemberId(), item.getGoodsId(), order.getId());
break;
@@ -311,10 +314,14 @@ public class OrderBiz {
}
/**
* 检查用户是否购买了指定商品
* 提供给PriceBiz使用,避免循环调用
* 检查用户是否购买了指定商品,并额外校验订单的faceId是否匹配
* @param userId 用户ID
* @param faceId 人脸ID
* @param goodsType 商品类型
* @param goodsId 商品ID
* @return 是否已购买且faceId匹配
*/
public boolean checkUserBuyItem(Long userId, int goodsType, Long goodsId) {
return orderRepository.checkUserBuyItem(userId, goodsType, goodsId);
public boolean checkUserBuyFaceItem(Long userId, Long faceId, int goodsType, Long goodsId) {
return orderRepository.checkUserBuyFaceItem(userId, faceId, goodsType, goodsId);
}
}

View File

@@ -76,6 +76,14 @@ public class PriceBiz {
goodsList.add(new GoodsListRespVO(2L, "照片集", 2));
}
}
// 拼图
puzzleTemplateMapper.list(scenicId, null, 1).forEach(puzzleTemplate -> {
GoodsListRespVO goods = new GoodsListRespVO();
goods.setGoodsId(puzzleTemplate.getId());
goods.setGoodsName(puzzleTemplate.getName());
goods.setGoodsType(5);
goodsList.add(goods);
});
return goodsList;
}
@@ -125,6 +133,7 @@ public class PriceBiz {
case "PHOTO_LOG":
// 从 template 表查询pLog模板
goodsList.add(new SimpleGoodsRespVO(scenicId, "pLog图<景区打包>", productType));
List<PuzzleTemplateEntity> puzzleList = puzzleTemplateMapper.list(scenicId, null, null);
puzzleList.stream()
.map(template -> new SimpleGoodsRespVO(template.getId(), template.getName(), productType))
@@ -272,7 +281,7 @@ public class PriceBiz {
allContentsPurchased = false;
break;
}
boolean hasPurchasedTemplate = orderBiz.checkUserBuyItem(userId, -1, videoEntities.getFirst().getVideoId());
boolean hasPurchasedTemplate = orderBiz.checkUserBuyFaceItem(userId, faceId, -1, videoEntities.getFirst().getVideoId());
if (!hasPurchasedTemplate) {
allContentsPurchased = false;
break;
@@ -284,7 +293,7 @@ public class PriceBiz {
if (scenicConfig != null) {
// 检查录像集
if (!Boolean.TRUE.equals(scenicConfig.getDisableSourceVideo())) {
boolean hasPurchasedRecording = orderBiz.checkUserBuyItem(userId, 1, faceId);
boolean hasPurchasedRecording = orderBiz.checkUserBuyFaceItem(userId, faceId, 1, faceId);
if (!hasPurchasedRecording) {
allContentsPurchased = false;
}
@@ -292,7 +301,7 @@ public class PriceBiz {
// 检查照片集
if (allContentsPurchased && !Boolean.TRUE.equals(scenicConfig.getDisableSourceImage())) {
boolean hasPurchasedPhoto = orderBiz.checkUserBuyItem(userId, 2, faceId);
boolean hasPurchasedPhoto = orderBiz.checkUserBuyFaceItem(userId, faceId, 2, faceId);
if (!hasPurchasedPhoto) {
allContentsPurchased = false;
}

View File

@@ -205,26 +205,31 @@ public class AppPuzzleController {
// 设置模板ID
vo.setTemplateId(record.getTemplateId());
IsBuyRespVO isBuyRespVO = orderBiz.isBuy(face.getScenicId(), face.getMemberId(), face.getId(), 5, record.getTemplateId());
if (isBuyRespVO.isBuy()) {
IsBuyRespVO isBuyScenic = orderBiz.isBuy(face.getScenicId(), face.getMemberId(), face.getId(), 5, face.getScenicId());
if (isBuyScenic.isBuy()) {
vo.setIsBuy(1);
} else {
vo.setIsBuy(0);
PriceCalculationRequest calculationRequest = new PriceCalculationRequest();
ProductItem productItem = new ProductItem();
productItem.setProductType(ProductType.PHOTO_LOG);
productItem.setProductId(record.getTemplateId().toString());
productItem.setPurchaseCount(1);
productItem.setScenicId(face.getScenicId().toString());
calculationRequest.setProducts(Collections.singletonList(productItem));
calculationRequest.setUserId(face.getMemberId());
calculationRequest.setFaceId(record.getFaceId());
calculationRequest.setPreviewOnly(true); // 仅查询价格,不实际使用优惠
PriceCalculationResult calculationResult = iPriceCalculationService.calculatePrice(calculationRequest);
if (calculationResult.getFinalAmount().compareTo(BigDecimal.ZERO) > 0) {
vo.setFreeCount(0);
IsBuyRespVO isBuyRespVO = orderBiz.isBuy(face.getScenicId(), face.getMemberId(), face.getId(), 5, record.getTemplateId());
if (isBuyRespVO.isBuy()) {
vo.setIsBuy(1);
} else {
vo.setFreeCount(1);
vo.setIsBuy(0);
PriceCalculationRequest calculationRequest = new PriceCalculationRequest();
ProductItem productItem = new ProductItem();
productItem.setProductType(ProductType.PHOTO_LOG);
productItem.setProductId(record.getTemplateId().toString());
productItem.setPurchaseCount(1);
productItem.setScenicId(face.getScenicId().toString());
calculationRequest.setProducts(Collections.singletonList(productItem));
calculationRequest.setUserId(face.getMemberId());
calculationRequest.setFaceId(record.getFaceId());
calculationRequest.setPreviewOnly(true); // 仅查询价格,不实际使用优惠
PriceCalculationResult calculationResult = iPriceCalculationService.calculatePrice(calculationRequest);
if (calculationResult.getFinalAmount().compareTo(BigDecimal.ZERO) > 0) {
vo.setFreeCount(0);
} else {
vo.setFreeCount(1);
}
}
}
return vo;

View File

@@ -55,7 +55,7 @@ public class AppTaskController {
@PostMapping("/submit")
public ApiResponse<String> submitVideoTask(@RequestBody VideoTaskReq videoTaskReq) {
taskService.createTaskByFaceIdAndTemplateId(videoTaskReq.getFaceId(),videoTaskReq.getTemplateId(),0);
taskService.createTaskByFaceIdAndTemplateId(videoTaskReq.getFaceId(),videoTaskReq.getTemplateId(),false);
return ApiResponse.success("成功");
}
}

View File

@@ -74,22 +74,31 @@ public class PuzzleGenerationOrchestrator {
// 3. 准备公共动态数据
Map<String, String> baseDynamicData = buildBaseDynamicData(faceId, faceUrl, scenicBasic);
// 4. 遍历所有模板,逐个生成
int successCount = 0;
int failCount = 0;
for (PuzzleTemplateDTO template : templateList) {
try {
generateSingleTemplate(scenicId, faceId, memberId, template, baseDynamicData);
successCount++;
} catch (Exception e) {
log.error("拼图生成失败: scenicId={}, templateCode={}, templateName={}",
scenicId, template.getCode(), template.getName(), e);
failCount++;
}
// 4. 使用虚拟线程池并行生成所有模板
java.util.concurrent.atomic.AtomicInteger successCount = new java.util.concurrent.atomic.AtomicInteger(0);
java.util.concurrent.atomic.AtomicInteger failCount = new java.util.concurrent.atomic.AtomicInteger(0);
try (java.util.concurrent.ExecutorService executor = java.util.concurrent.Executors.newVirtualThreadPerTaskExecutor()) {
// 为每个模板创建一个异步任务
List<java.util.concurrent.CompletableFuture<Void>> futures = templateList.stream()
.map(template -> java.util.concurrent.CompletableFuture.runAsync(() -> {
try {
generateSingleTemplate(scenicId, faceId, memberId, template, baseDynamicData);
successCount.incrementAndGet();
} catch (Exception e) {
log.error("拼图生成失败: scenicId={}, templateCode={}, templateName={}",
scenicId, template.getCode(), template.getName(), e);
failCount.incrementAndGet();
}
}, executor))
.toList();
// 等待所有任务完成
java.util.concurrent.CompletableFuture.allOf(futures.toArray(new java.util.concurrent.CompletableFuture[0])).join();
}
log.info("景区拼图模板批量生成完成: scenicId={}, 总数={}, 成功={}, 失败={}",
scenicId, templateList.size(), successCount, failCount);
scenicId, templateList.size(), successCount.get(), failCount.get());
} catch (Exception e) {
// 异步任务失败不影响主流程,仅记录日志

View File

@@ -0,0 +1,104 @@
package com.ycwl.basic.puzzle.fill.datasource;
import com.fasterxml.jackson.databind.JsonNode;
import com.ycwl.basic.mapper.SourceMapper;
import com.ycwl.basic.model.pc.source.entity.SourceEntity;
import com.ycwl.basic.puzzle.fill.enums.DataSourceType;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.List;
import java.util.Map;
/**
* 设备缩略图数据源策略
* 根据deviceIndex指定第N个设备的缩略图
*/
@Slf4j
@Component
public class DeviceThumbImageDataSourceStrategy implements DataSourceStrategy {
@Autowired
private SourceMapper sourceMapper;
@Override
public String resolve(JsonNode sourceFilter, String sortStrategy, DataSourceContext context) {
try {
// 默认type=2(图片)
Integer type = 2;
if (sourceFilter != null && sourceFilter.has("type")) {
type = sourceFilter.get("type").asInt();
}
// 获取deviceIndex
Integer deviceIndex = 0;
if (sourceFilter != null && sourceFilter.has("deviceIndex")) {
deviceIndex = sourceFilter.get("deviceIndex").asInt();
}
// 使用默认策略
if (sortStrategy == null || sortStrategy.isEmpty()) {
sortStrategy = "LATEST";
}
// 1. 检查是否有过滤后的机位列表
Map<String, Object> extra = context.getExtra();
if (extra != null && extra.containsKey("filteredDeviceIds")) {
@SuppressWarnings("unchecked")
List<Long> filteredDeviceIds = (List<Long>) extra.get("filteredDeviceIds");
if (filteredDeviceIds != null && !filteredDeviceIds.isEmpty()) {
// 使用过滤后的机位列表
if (deviceIndex >= filteredDeviceIds.size()) {
log.warn("deviceIndex[{}]超出过滤后的机位列表范围, 最大索引={}",
deviceIndex, filteredDeviceIds.size() - 1);
return null;
}
Long targetDeviceId = filteredDeviceIds.get(deviceIndex);
log.debug("使用过滤后的机位列表, deviceIndex={}, targetDeviceId={}",
deviceIndex, targetDeviceId);
SourceEntity source = sourceMapper.getSourceByFaceAndDeviceId(
context.getFaceId(),
targetDeviceId,
type,
sortStrategy
);
if (source != null) {
String thumbUrl = source.getThumbUrl();
log.debug("解析DEVICE_THUMB_IMAGE成功(过滤模式), faceId={}, deviceId={}, type={}, thumbUrl={}",
context.getFaceId(), targetDeviceId, type, thumbUrl);
return thumbUrl;
}
return null;
}
}
// 2. 降级到原有逻辑(使用deviceIndex直接查询)
SourceEntity source = sourceMapper.getSourceByFaceAndDeviceIndex(
context.getFaceId(),
deviceIndex,
type,
sortStrategy
);
if (source != null) {
String thumbUrl = source.getThumbUrl();
log.debug("解析DEVICE_THUMB_IMAGE成功(索引模式), faceId={}, deviceIndex={}, type={}, thumbUrl={}",
context.getFaceId(), deviceIndex, type, thumbUrl);
return thumbUrl;
}
} catch (Exception e) {
log.error("解析DEVICE_THUMB_IMAGE异常, faceId={}", context.getFaceId(), e);
}
return null;
}
@Override
public String getSupportedType() {
return DataSourceType.DEVICE_THUMB_IMAGE.getCode();
}
}

View File

@@ -27,6 +27,7 @@ public enum DataSourceType {
* 设备图片(根据deviceIndex指定第N个设备的图片)
*/
DEVICE_IMAGE("DEVICE_IMAGE", "设备图片"),
DEVICE_THUMB_IMAGE("DEVICE_THUMB_IMAGE", "设备缩略图片"),
/**
* 静态值(直接使用fallbackValue)

View File

@@ -23,7 +23,7 @@ public class OrderRepository {
public static final String ORDER_CACHE_KEY = "order:%s";
public static final String ORDER_ITEMS_CACHE_KEY = "order:%s:items";
public static final String ORDER_ITEM_CACHE_KEY = "order:item:%s";
public static final String ORDER_USER_TYPE_BUY_ITEM_CACHE_KEY = "order:user:%s:type:%s:id:%s";
public static final String ORDER_USER_FACE_TYPE_BUY_ITEM_CACHE_KEY = "order:user:%s:face:%s:type:%s:id:%s";
public OrderEntity getOrder(Long orderId) {
if (redisTemplate.hasKey(String.format(ORDER_CACHE_KEY, orderId))) {
@@ -62,40 +62,12 @@ public class OrderRepository {
return orderItemEntity;
}
public boolean checkUserBuyItem(Long userId, int goodsType, Long goodsId) {
synchronized (this) {
if (redisTemplate.hasKey(String.format(ORDER_USER_TYPE_BUY_ITEM_CACHE_KEY, userId, goodsType, goodsId))) {
return "1".equals(redisTemplate.opsForValue().get(String.format(ORDER_USER_TYPE_BUY_ITEM_CACHE_KEY, userId, goodsType, goodsId)));
}
OrderEntity orderEntity = orderMapper.getUserBuyItem(userId, goodsType, goodsId);
if (orderEntity == null) {
redisTemplate.opsForValue().set(String.format(ORDER_USER_TYPE_BUY_ITEM_CACHE_KEY, userId, goodsType, goodsId), "0", 60, TimeUnit.SECONDS);
return false;
}
if (Integer.valueOf(1).equals(orderEntity.getStatus())) {
redisTemplate.opsForValue().set(String.format(ORDER_USER_TYPE_BUY_ITEM_CACHE_KEY, userId, goodsType, goodsId), "1");
return true;
} else {
redisTemplate.opsForValue().set(String.format(ORDER_USER_TYPE_BUY_ITEM_CACHE_KEY, userId, goodsType, goodsId), "0", 60, TimeUnit.SECONDS);
return false;
}
}
}
public OrderEntity getUserBuyItem(Long userId, int goodsType, Long goodsId) {
return orderMapper.getUserBuyItem(userId, goodsType, goodsId);
}
public void clearUserBuyItemCache(Long userId, int goodsType, Long goodsId) {
redisTemplate.delete(String.format(ORDER_USER_TYPE_BUY_ITEM_CACHE_KEY, userId, goodsType, goodsId));
}
public void clearOrderCache(Long orderId) {
OrderEntity order = getOrder(orderId);
redisTemplate.delete(String.format(ORDER_CACHE_KEY, orderId));
getOrderItems(orderId).forEach(orderItem -> {
redisTemplate.delete(String.format(ORDER_ITEM_CACHE_KEY, orderItem.getId()));
clearUserBuyItemCache(order.getMemberId(), orderItem.getGoodsType(), orderItem.getGoodsId());
clearUserBuyFaceItemCache(order.getMemberId(), order.getFaceId(), orderItem.getGoodsType(), orderItem.getGoodsId());
});
redisTemplate.delete(String.format(ORDER_ITEMS_CACHE_KEY, orderId));
}
@@ -105,4 +77,37 @@ public class OrderRepository {
orderMapper.updateOrder(updateEntity);
clearOrderCache(orderId);
}
/**
* 检查用户是否购买了指定商品,并额外校验订单的faceId是否匹配
* @param userId 用户ID
* @param faceId 人脸ID
* @param goodsType 商品类型
* @param goodsId 商品ID
* @return 是否已购买且faceId匹配
*/
public boolean checkUserBuyFaceItem(Long userId, Long faceId, int goodsType, Long goodsId) {
synchronized (this) {
String cacheKey = String.format(ORDER_USER_FACE_TYPE_BUY_ITEM_CACHE_KEY, userId, faceId, goodsType, goodsId);
if (redisTemplate.hasKey(cacheKey)) {
return "1".equals(redisTemplate.opsForValue().get(cacheKey));
}
OrderEntity orderEntity = orderMapper.getUserBuyFaceItem(userId, faceId, goodsType, goodsId);
if (orderEntity == null) {
redisTemplate.opsForValue().set(cacheKey, "0", 60, TimeUnit.SECONDS);
return false;
}
if (Integer.valueOf(1).equals(orderEntity.getStatus())) {
redisTemplate.opsForValue().set(cacheKey, "1");
return true;
} else {
redisTemplate.opsForValue().set(cacheKey, "0", 60, TimeUnit.SECONDS);
return false;
}
}
}
public void clearUserBuyFaceItemCache(Long userId, Long faceId, int goodsType, Long goodsId) {
redisTemplate.delete(String.format(ORDER_USER_FACE_TYPE_BUY_ITEM_CACHE_KEY, userId, faceId, goodsType, goodsId));
}
}

View File

@@ -1,9 +1,6 @@
package com.ycwl.basic.repository;
import com.ycwl.basic.biz.PriceBiz;
import com.ycwl.basic.model.mobile.order.IsBuyBatchRespVO;
import com.ycwl.basic.pricing.dto.VoucherInfo;
import com.ycwl.basic.pricing.enums.VoucherDiscountType;
import com.ycwl.basic.pricing.service.IVoucherService;
import com.ycwl.basic.utils.JacksonUtil;
import com.ycwl.basic.mapper.VideoMapper;
@@ -116,4 +113,25 @@ public class VideoRepository {
return true;
}
public void setUserIsBuyTemplate(Long memberId, Long templateId, Long orderId, Long faceId) {
List<MemberVideoEntity> videoEntities = memberRelationRepository.listRelationByFace(faceId);
for (MemberVideoEntity videoEntity : videoEntities) {
if (videoEntity.getTemplateId() != null && videoEntity.getTemplateId().equals(templateId)) {
MemberVideoEntity memberVideo = new MemberVideoEntity();
memberVideo.setVideoId(videoEntity.getVideoId());
memberVideo.setMemberId(memberId);
memberVideo.setIsBuy(1);
memberVideo.setOrderId(orderId);
videoMapper.updateRelation(memberVideo);
// 清理视频关系缓存
MemberVideoEntity existingVideo = videoMapper.queryUserVideo(memberId, videoEntity.getVideoId());
if (existingVideo != null && existingVideo.getFaceId() != null) {
memberRelationRepository.clearVCacheByFace(existingVideo.getFaceId());
}
}
}
}
}

View File

@@ -473,9 +473,9 @@ public class FaceServiceImpl implements FaceService {
contentPageVO.setLockType(1);
}
}
boolean buy = orderBiz.checkUserBuyItem(userId, contentPageVO.getGoodsType(), contentPageVO.getContentId());
boolean buy = orderBiz.checkUserBuyFaceItem(userId, faceId, contentPageVO.getGoodsType(), contentPageVO.getContentId());
if (!buy) {
buy = orderBiz.checkUserBuyItem(userId, -1, contentPageVO.getTemplateId());
buy = orderBiz.checkUserBuyFaceItem(userId, faceId, -1, contentPageVO.getTemplateId());
}
if (buy) {
contentPageVO.setIsBuy(1);
@@ -487,21 +487,25 @@ public class FaceServiceImpl implements FaceService {
List<PuzzleTemplateEntity> puzzleTemplateEntityList = puzzleTemplateMapper.list(face.getScenicId(), null, 1);
if (!puzzleTemplateEntityList.isEmpty()) {
List<PuzzleGenerationRecordEntity> records = puzzleGenerationRecordMapper.listByFaceId(faceId);
puzzleTemplateEntityList.forEach(template -> {
Optional<PuzzleGenerationRecordEntity> optionalRecord = records.stream().filter(r -> r.getTemplateId().equals(template.getId())).findFirst();
ContentPageVO sfpContent = new ContentPageVO();
sfpContent.setName(template.getName());
sfpContent.setGroup("plog");
sfpContent.setScenicId(face.getScenicId());
sfpContent.setContentType(3);
sfpContent.setSourceType(3);
sfpContent.setLockType(-1);
sfpContent.setContentId(optionalRecord.map(PuzzleGenerationRecordEntity::getId).orElse(null));
sfpContent.setTemplateId(template.getId());
sfpContent.setTemplateCoverUrl(template.getCoverImage());
sfpContent.setGoodsType(3);
sfpContent.setSort(0);
if (optionalRecord.isPresent()) {
PuzzleTemplateEntity template = puzzleTemplateEntityList.getFirst();
Optional<PuzzleGenerationRecordEntity> optionalRecord = records.stream().filter(r -> r.getTemplateId().equals(template.getId())).findFirst();
ContentPageVO sfpContent = new ContentPageVO();
sfpContent.setName(template.getName());
sfpContent.setGroup("plog");
sfpContent.setScenicId(face.getScenicId());
sfpContent.setContentType(3);
sfpContent.setSourceType(3);
sfpContent.setLockType(-1);
sfpContent.setContentId(optionalRecord.map(PuzzleGenerationRecordEntity::getId).orElse(null));
sfpContent.setTemplateId(template.getId());
sfpContent.setTemplateCoverUrl(template.getCoverImage());
sfpContent.setGoodsType(3);
sfpContent.setSort(0);
if (optionalRecord.isPresent()) {
IsBuyRespVO isBuyScenic = orderBiz.isBuy(face.getScenicId(), face.getMemberId(), faceId, 5, face.getScenicId());
if (isBuyScenic.isBuy()) {
sfpContent.setIsBuy(1);
} else {
IsBuyRespVO isBuyRespVO = orderBiz.isBuy(face.getScenicId(), face.getMemberId(), faceId, 5, optionalRecord.get().getTemplateId());
if (isBuyRespVO.isBuy()) {
sfpContent.setIsBuy(1);
@@ -509,24 +513,24 @@ public class FaceServiceImpl implements FaceService {
sfpContent.setIsBuy(0);
}
}
PriceCalculationRequest calculationRequest = new PriceCalculationRequest();
ProductItem productItem = new ProductItem();
productItem.setProductType(ProductType.PHOTO_LOG);
productItem.setProductId(template.getId().toString());
productItem.setPurchaseCount(1);
productItem.setScenicId(face.getScenicId().toString());
calculationRequest.setProducts(Collections.singletonList(productItem));
calculationRequest.setUserId(face.getMemberId());
calculationRequest.setFaceId(face.getId());
calculationRequest.setPreviewOnly(true); // 仅查询价格,不实际使用优惠
PriceCalculationResult calculationResult = iPriceCalculationService.calculatePrice(calculationRequest);
if (calculationResult.getFinalAmount().compareTo(BigDecimal.ZERO) > 0) {
sfpContent.setFreeCount(0);
} else {
sfpContent.setFreeCount(1);
}
contentList.add(1, sfpContent);
});
}
PriceCalculationRequest calculationRequest = new PriceCalculationRequest();
ProductItem productItem = new ProductItem();
productItem.setProductType(ProductType.PHOTO_LOG);
productItem.setProductId(template.getId().toString());
productItem.setPurchaseCount(1);
productItem.setScenicId(face.getScenicId().toString());
calculationRequest.setProducts(Collections.singletonList(productItem));
calculationRequest.setUserId(face.getMemberId());
calculationRequest.setFaceId(face.getId());
calculationRequest.setPreviewOnly(true); // 仅查询价格,不实际使用优惠
PriceCalculationResult calculationResult = iPriceCalculationService.calculatePrice(calculationRequest);
if (calculationResult.getFinalAmount().compareTo(BigDecimal.ZERO) > 0) {
sfpContent.setFreeCount(0);
} else {
sfpContent.setFreeCount(1);
}
contentList.add(1, sfpContent);
}
SourceReqQuery sourceReqQuery = new SourceReqQuery();
sourceReqQuery.setScenicId(face.getScenicId());

View File

@@ -916,15 +916,13 @@ public class OrderServiceImpl implements OrderService {
List<OrderItemEntity> orderItems = goodsList.stream().map(goods -> {
OrderItemEntity orderItem = new OrderItemEntity();
orderItem.setOrderId(orderId);
if (Long.valueOf(1L).equals(goods.getGoodsId())) {
orderItem.setGoodsId(goods.getGoodsId());
orderItem.setGoodsType(goods.getGoodsType());
if (Integer.valueOf(1).equals(goods.getGoodsType())) {
orderItem.setGoodsId(batchOrderReqVO.getFaceId());
orderItem.setGoodsType(1);
} else if (Long.valueOf(2L).equals(goods.getGoodsId())) {
} else if (Integer.valueOf(2).equals(goods.getGoodsType())) {
orderItem.setGoodsId(batchOrderReqVO.getFaceId());
orderItem.setGoodsType(2);
} else {
// templateId
orderItem.setGoodsId(goods.getGoodsId());
} else if (Integer.valueOf(0).equals(goods.getGoodsType())) {
orderItem.setGoodsType(-1);
}
return orderItem;

View File

@@ -390,42 +390,51 @@ public class FaceMatchingOrchestrator {
baseDynamicData.put("scenicText", scenicBasic.getName());
baseDynamicData.put("dateStr", DateUtil.format(new Date(), "yyyy.MM.dd"));
// 遍历所有模板,逐个生成
int successCount = 0;
int failCount = 0;
for (PuzzleTemplateDTO template : templateList) {
try {
log.info("开始生成拼图: scenicId={}, templateCode={}, templateName={}",
scenicId, template.getCode(), template.getName());
// 使用虚拟线程池并行生成所有模板
java.util.concurrent.atomic.AtomicInteger successCount = new java.util.concurrent.atomic.AtomicInteger(0);
java.util.concurrent.atomic.AtomicInteger failCount = new java.util.concurrent.atomic.AtomicInteger(0);
// 构建生成请求
PuzzleGenerateRequest generateRequest = new PuzzleGenerateRequest();
generateRequest.setScenicId(scenicId);
generateRequest.setUserId(memberId);
generateRequest.setFaceId(faceId);
generateRequest.setBusinessType("face_matching");
generateRequest.setTemplateCode(template.getCode());
generateRequest.setOutputFormat("PNG");
generateRequest.setQuality(90);
generateRequest.setDynamicData(new HashMap<>(baseDynamicData));
generateRequest.setRequireRuleMatch(true);
try (java.util.concurrent.ExecutorService executor = java.util.concurrent.Executors.newVirtualThreadPerTaskExecutor()) {
// 为每个模板创建一个异步任务
List<java.util.concurrent.CompletableFuture<Void>> futures = templateList.stream()
.map(template -> java.util.concurrent.CompletableFuture.runAsync(() -> {
try {
log.info("开始生成拼图: scenicId={}, templateCode={}, templateName={}",
scenicId, template.getCode(), template.getName());
// 调用拼图生成服务
PuzzleGenerateResponse response = puzzleGenerateService.generate(generateRequest);
// 构建生成请求
PuzzleGenerateRequest generateRequest = new PuzzleGenerateRequest();
generateRequest.setScenicId(scenicId);
generateRequest.setUserId(memberId);
generateRequest.setFaceId(faceId);
generateRequest.setBusinessType("face_matching");
generateRequest.setTemplateCode(template.getCode());
generateRequest.setOutputFormat("PNG");
generateRequest.setQuality(90);
generateRequest.setDynamicData(new HashMap<>(baseDynamicData));
generateRequest.setRequireRuleMatch(true);
log.info("拼图生成成功: scenicId={}, templateCode={}, imageUrl={}",
scenicId, template.getCode(), response.getImageUrl());
successCount++;
// 调用拼图生成服务
PuzzleGenerateResponse response = puzzleGenerateService.generate(generateRequest);
} catch (Exception e) {
log.error("拼图生成失败: scenicId={}, templateCode={}, templateName={}",
scenicId, template.getCode(), template.getName(), e);
failCount++;
}
log.info("拼图生成成功: scenicId={}, templateCode={}, imageUrl={}",
scenicId, template.getCode(), response.getImageUrl());
successCount.incrementAndGet();
} catch (Exception e) {
log.error("拼图生成失败: scenicId={}, templateCode={}, templateName={}",
scenicId, template.getCode(), template.getName(), e);
failCount.incrementAndGet();
}
}, executor))
.toList();
// 等待所有任务完成
java.util.concurrent.CompletableFuture.allOf(futures.toArray(new java.util.concurrent.CompletableFuture[0])).join();
}
log.info("景区拼图模板批量生成完成: scenicId={}, 总数={}, 成功={}, 失败={}",
scenicId, templateList.size(), successCount, failCount);
log.info("景区拼图模板批量生成完成: scenicId={}, 总数={}, 成功={}, 失败={}",
scenicId, templateList.size(), successCount.get(), failCount.get());
} catch (Exception e) {
// 异步任务失败不影响主流程,仅记录日志

View File

@@ -14,9 +14,7 @@ public interface TaskService {
TemplateRespVO workerGetTemplate(Long templateId, WorkerAuthReqVo req);
void createTaskByFaceIdAndTemplateId(Long faceId, Long templateId);
void createTaskByFaceIdAndTemplateId(Long faceId, Long templateId, int automatic);
void createTaskByFaceIdAndTemplateId(Long faceId, Long templateId, boolean automatic);
void taskSuccess(Long taskId, TaskSuccessReqVo req);

View File

@@ -22,7 +22,6 @@ import com.ycwl.basic.mapper.TaskMapper;
import com.ycwl.basic.mapper.VideoMapper;
import com.ycwl.basic.model.mobile.order.IsBuyRespVO;
import com.ycwl.basic.model.pc.face.entity.FaceEntity;
import com.ycwl.basic.model.pc.face.resp.FaceRespVO;
import com.ycwl.basic.model.pc.faceSample.entity.FaceSampleEntity;
import com.ycwl.basic.model.pc.member.resp.MemberRespVO;
import com.ycwl.basic.model.pc.mp.MpConfigEntity;
@@ -67,7 +66,6 @@ import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
@@ -256,7 +254,7 @@ public class TaskTaskServiceImpl implements TaskService {
@Override
public void forceCreateTaskByFaceIdAndTempalteId(Long faceId, Long templateId) {
createTaskByFaceIdAndTemplateIdInternal(faceId, templateId, 0, true);
createTaskByFaceIdAndTemplateIdInternal(faceId, templateId, false, true);
}
@Override
@@ -304,34 +302,30 @@ public class TaskTaskServiceImpl implements TaskService {
}
if (Integer.valueOf(3).equals(scenicConfig.getInteger("book_routine")) || Integer.valueOf(4).equals(scenicConfig.getInteger("book_routine"))) {
// 生成全部视频的逻辑
templateList.forEach(template -> createTaskByFaceIdAndTemplateId(faceId, template.getId(), 1));
templateList.forEach(template -> createTaskByFaceIdAndTemplateId(faceId, template.getId(), true));
} else {
if (Boolean.TRUE.equals(scenicConfig.getBoolean("force_create_vlog"))) {
Long availableTemplateId = templateBiz.findFirstAvailableTemplate(templateList.stream().map(TemplateRespVO::getId).toList(), faceId, false);
if (availableTemplateId != null) {
createTaskByFaceIdAndTemplateId(faceId, availableTemplateId, 1);
createTaskByFaceIdAndTemplateId(faceId, availableTemplateId, true);
} else {
log.info("faceId:{} available template is not exist", faceId);
}
} else {
// 非强制创建,只创建第一个可用模板
if (!templateList.isEmpty()) {
createTaskByFaceIdAndTemplateId(faceId, templateList.getFirst().getId(), 1);
createTaskByFaceIdAndTemplateId(faceId, templateList.getFirst().getId(), true);
}
}
}
}
@Override
public void createTaskByFaceIdAndTemplateId(Long faceId, Long templateId) {
createTaskByFaceIdAndTemplateId(faceId, templateId, 0);
}
@Override
public void createTaskByFaceIdAndTemplateId(Long faceId, Long templateId, int automatic) {
public void createTaskByFaceIdAndTemplateId(Long faceId, Long templateId, boolean automatic) {
createTaskByFaceIdAndTemplateIdInternal(faceId, templateId, automatic, false);
}
private void createTaskByFaceIdAndTemplateIdInternal(Long faceId, Long templateId, int automatic, boolean forceCreate) {
private void createTaskByFaceIdAndTemplateIdInternal(Long faceId, Long templateId, boolean automatic, boolean forceCreate) {
FaceEntity face = faceRepository.getFace(faceId);
if (face == null) {
log.info("faceId:{} is not exist", faceId);
@@ -430,7 +424,7 @@ public class TaskTaskServiceImpl implements TaskService {
taskEntity.setScenicId(face.getScenicId());
taskEntity.setFaceId(faceId);
taskEntity.setTemplateId(templateId);
taskEntity.setAutomatic(automatic);
taskEntity.setAutomatic(automatic ? 1 : 0);
}
taskEntity.setWorkerId(null);
taskEntity.setStatus(0);

View File

@@ -1,9 +1,12 @@
server:
port: 8030
shutdown: graceful
spring:
application:
name: zt
lifecycle:
timeout-per-shutdown-phase: 60s
# Feign配置(简化版,基于Nacos服务发现)
feign:
@@ -38,4 +41,7 @@ kafka:
# 开发环境日志配置
logging:
level:
com.ycwl.basic.integration.scenic.client: DEBUG
com.ycwl.basic.integration.scenic.client: DEBUG
zhipu:
api-key: a331e0fcf3f74518818b8e5129b79058.RXuUxUUjKdcxbF4L

View File

@@ -1,11 +1,17 @@
server:
port: 8031
shutdown: graceful
spring:
application:
name: zt
lifecycle:
timeout-per-shutdown-phase: 60s
# 生产环境日志级别
logging:
level:
com.ycwl.basic.integration.scenic.client: WARN
com.ycwl.basic.integration.scenic.client: WARN
zhipu:
api-key: a331e0fcf3f74518818b8e5129b79058.RXuUxUUjKdcxbF4L

View File

@@ -112,8 +112,9 @@
FROM source s
),
member_plog_data AS (
SELECT 5 as type, gr.template_id as id, gr.result_image_url as url, gr.face_id
SELECT 5 as type, gr.template_id as id, pt.scenic_id as scenic_id, gr.result_image_url as url, gr.face_id
FROM puzzle_generation_record gr
left join puzzle_template pt on gr.template_id = pt.id
)
SELECT
oi.id AS oiId,
@@ -168,7 +169,7 @@
LEFT JOIN member_source_aicam_data msac ON o.face_id = msac.face_id AND oi.goods_id = msac.face_id AND oi.goods_type = 13
LEFT JOIN member_photo_data mpd ON oi.goods_id = mpd.id AND mpd.type = oi.goods_type
LEFT JOIN member_aio_photo_data mpa ON oi.goods_id = mpa.id AND mpa.type = oi.goods_type
LEFT JOIN member_plog_data mpl ON oi.goods_id = mpl.id AND mpl.type = oi.goods_type AND o.face_id = mpl.face_id
LEFT JOIN member_plog_data mpl ON (oi.goods_id = mpl.id OR oi.goods_id = mpl.scenic_id) AND mpl.type = oi.goods_type AND o.face_id = mpl.face_id
WHERE oi.order_id = #{id};
</select>