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

View File

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

View File

@@ -205,26 +205,31 @@ public class AppPuzzleController {
// 设置模板ID // 设置模板ID
vo.setTemplateId(record.getTemplateId()); vo.setTemplateId(record.getTemplateId());
IsBuyRespVO isBuyRespVO = orderBiz.isBuy(face.getScenicId(), face.getMemberId(), face.getId(), 5, record.getTemplateId()); IsBuyRespVO isBuyScenic = orderBiz.isBuy(face.getScenicId(), face.getMemberId(), face.getId(), 5, face.getScenicId());
if (isBuyRespVO.isBuy()) { if (isBuyScenic.isBuy()) {
vo.setIsBuy(1); vo.setIsBuy(1);
} else { } else {
vo.setIsBuy(0); IsBuyRespVO isBuyRespVO = orderBiz.isBuy(face.getScenicId(), face.getMemberId(), face.getId(), 5, record.getTemplateId());
PriceCalculationRequest calculationRequest = new PriceCalculationRequest(); if (isBuyRespVO.isBuy()) {
ProductItem productItem = new ProductItem(); vo.setIsBuy(1);
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 { } 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; return vo;

View File

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

View File

@@ -74,22 +74,31 @@ public class PuzzleGenerationOrchestrator {
// 3. 准备公共动态数据 // 3. 准备公共动态数据
Map<String, String> baseDynamicData = buildBaseDynamicData(faceId, faceUrl, scenicBasic); Map<String, String> baseDynamicData = buildBaseDynamicData(faceId, faceUrl, scenicBasic);
// 4. 遍历所有模板,逐个生成 // 4. 使用虚拟线程池并行生成所有模板
int successCount = 0; java.util.concurrent.atomic.AtomicInteger successCount = new java.util.concurrent.atomic.AtomicInteger(0);
int failCount = 0; java.util.concurrent.atomic.AtomicInteger failCount = new java.util.concurrent.atomic.AtomicInteger(0);
for (PuzzleTemplateDTO template : templateList) {
try { try (java.util.concurrent.ExecutorService executor = java.util.concurrent.Executors.newVirtualThreadPerTaskExecutor()) {
generateSingleTemplate(scenicId, faceId, memberId, template, baseDynamicData); // 为每个模板创建一个异步任务
successCount++; List<java.util.concurrent.CompletableFuture<Void>> futures = templateList.stream()
} catch (Exception e) { .map(template -> java.util.concurrent.CompletableFuture.runAsync(() -> {
log.error("拼图生成失败: scenicId={}, templateCode={}, templateName={}", try {
scenicId, template.getCode(), template.getName(), e); generateSingleTemplate(scenicId, faceId, memberId, template, baseDynamicData);
failCount++; 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={}, 总数={}, 成功={}, 失败={}", log.info("景区拼图模板批量生成完成: scenicId={}, 总数={}, 成功={}, 失败={}",
scenicId, templateList.size(), successCount, failCount); scenicId, templateList.size(), successCount.get(), failCount.get());
} catch (Exception e) { } 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个设备的图片) * 设备图片(根据deviceIndex指定第N个设备的图片)
*/ */
DEVICE_IMAGE("DEVICE_IMAGE", "设备图片"), DEVICE_IMAGE("DEVICE_IMAGE", "设备图片"),
DEVICE_THUMB_IMAGE("DEVICE_THUMB_IMAGE", "设备缩略图片"),
/** /**
* 静态值(直接使用fallbackValue) * 静态值(直接使用fallbackValue)

View File

@@ -23,7 +23,7 @@ public class OrderRepository {
public static final String ORDER_CACHE_KEY = "order:%s"; 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_ITEMS_CACHE_KEY = "order:%s:items";
public static final String ORDER_ITEM_CACHE_KEY = "order:item:%s"; 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) { public OrderEntity getOrder(Long orderId) {
if (redisTemplate.hasKey(String.format(ORDER_CACHE_KEY, orderId))) { if (redisTemplate.hasKey(String.format(ORDER_CACHE_KEY, orderId))) {
@@ -62,40 +62,12 @@ public class OrderRepository {
return orderItemEntity; 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) { public void clearOrderCache(Long orderId) {
OrderEntity order = getOrder(orderId); OrderEntity order = getOrder(orderId);
redisTemplate.delete(String.format(ORDER_CACHE_KEY, orderId)); redisTemplate.delete(String.format(ORDER_CACHE_KEY, orderId));
getOrderItems(orderId).forEach(orderItem -> { getOrderItems(orderId).forEach(orderItem -> {
redisTemplate.delete(String.format(ORDER_ITEM_CACHE_KEY, orderItem.getId())); 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)); redisTemplate.delete(String.format(ORDER_ITEMS_CACHE_KEY, orderId));
} }
@@ -105,4 +77,37 @@ public class OrderRepository {
orderMapper.updateOrder(updateEntity); orderMapper.updateOrder(updateEntity);
clearOrderCache(orderId); 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; package com.ycwl.basic.repository;
import com.ycwl.basic.biz.PriceBiz; 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.pricing.service.IVoucherService;
import com.ycwl.basic.utils.JacksonUtil; import com.ycwl.basic.utils.JacksonUtil;
import com.ycwl.basic.mapper.VideoMapper; import com.ycwl.basic.mapper.VideoMapper;
@@ -116,4 +113,25 @@ public class VideoRepository {
return true; 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); contentPageVO.setLockType(1);
} }
} }
boolean buy = orderBiz.checkUserBuyItem(userId, contentPageVO.getGoodsType(), contentPageVO.getContentId()); boolean buy = orderBiz.checkUserBuyFaceItem(userId, faceId, contentPageVO.getGoodsType(), contentPageVO.getContentId());
if (!buy) { if (!buy) {
buy = orderBiz.checkUserBuyItem(userId, -1, contentPageVO.getTemplateId()); buy = orderBiz.checkUserBuyFaceItem(userId, faceId, -1, contentPageVO.getTemplateId());
} }
if (buy) { if (buy) {
contentPageVO.setIsBuy(1); contentPageVO.setIsBuy(1);
@@ -487,21 +487,25 @@ public class FaceServiceImpl implements FaceService {
List<PuzzleTemplateEntity> puzzleTemplateEntityList = puzzleTemplateMapper.list(face.getScenicId(), null, 1); List<PuzzleTemplateEntity> puzzleTemplateEntityList = puzzleTemplateMapper.list(face.getScenicId(), null, 1);
if (!puzzleTemplateEntityList.isEmpty()) { if (!puzzleTemplateEntityList.isEmpty()) {
List<PuzzleGenerationRecordEntity> records = puzzleGenerationRecordMapper.listByFaceId(faceId); List<PuzzleGenerationRecordEntity> records = puzzleGenerationRecordMapper.listByFaceId(faceId);
puzzleTemplateEntityList.forEach(template -> { PuzzleTemplateEntity template = puzzleTemplateEntityList.getFirst();
Optional<PuzzleGenerationRecordEntity> optionalRecord = records.stream().filter(r -> r.getTemplateId().equals(template.getId())).findFirst(); Optional<PuzzleGenerationRecordEntity> optionalRecord = records.stream().filter(r -> r.getTemplateId().equals(template.getId())).findFirst();
ContentPageVO sfpContent = new ContentPageVO(); ContentPageVO sfpContent = new ContentPageVO();
sfpContent.setName(template.getName()); sfpContent.setName(template.getName());
sfpContent.setGroup("plog"); sfpContent.setGroup("plog");
sfpContent.setScenicId(face.getScenicId()); sfpContent.setScenicId(face.getScenicId());
sfpContent.setContentType(3); sfpContent.setContentType(3);
sfpContent.setSourceType(3); sfpContent.setSourceType(3);
sfpContent.setLockType(-1); sfpContent.setLockType(-1);
sfpContent.setContentId(optionalRecord.map(PuzzleGenerationRecordEntity::getId).orElse(null)); sfpContent.setContentId(optionalRecord.map(PuzzleGenerationRecordEntity::getId).orElse(null));
sfpContent.setTemplateId(template.getId()); sfpContent.setTemplateId(template.getId());
sfpContent.setTemplateCoverUrl(template.getCoverImage()); sfpContent.setTemplateCoverUrl(template.getCoverImage());
sfpContent.setGoodsType(3); sfpContent.setGoodsType(3);
sfpContent.setSort(0); sfpContent.setSort(0);
if (optionalRecord.isPresent()) { 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()); IsBuyRespVO isBuyRespVO = orderBiz.isBuy(face.getScenicId(), face.getMemberId(), faceId, 5, optionalRecord.get().getTemplateId());
if (isBuyRespVO.isBuy()) { if (isBuyRespVO.isBuy()) {
sfpContent.setIsBuy(1); sfpContent.setIsBuy(1);
@@ -509,24 +513,24 @@ public class FaceServiceImpl implements FaceService {
sfpContent.setIsBuy(0); sfpContent.setIsBuy(0);
} }
} }
PriceCalculationRequest calculationRequest = new PriceCalculationRequest(); }
ProductItem productItem = new ProductItem(); PriceCalculationRequest calculationRequest = new PriceCalculationRequest();
productItem.setProductType(ProductType.PHOTO_LOG); ProductItem productItem = new ProductItem();
productItem.setProductId(template.getId().toString()); productItem.setProductType(ProductType.PHOTO_LOG);
productItem.setPurchaseCount(1); productItem.setProductId(template.getId().toString());
productItem.setScenicId(face.getScenicId().toString()); productItem.setPurchaseCount(1);
calculationRequest.setProducts(Collections.singletonList(productItem)); productItem.setScenicId(face.getScenicId().toString());
calculationRequest.setUserId(face.getMemberId()); calculationRequest.setProducts(Collections.singletonList(productItem));
calculationRequest.setFaceId(face.getId()); calculationRequest.setUserId(face.getMemberId());
calculationRequest.setPreviewOnly(true); // 仅查询价格,不实际使用优惠 calculationRequest.setFaceId(face.getId());
PriceCalculationResult calculationResult = iPriceCalculationService.calculatePrice(calculationRequest); calculationRequest.setPreviewOnly(true); // 仅查询价格,不实际使用优惠
if (calculationResult.getFinalAmount().compareTo(BigDecimal.ZERO) > 0) { PriceCalculationResult calculationResult = iPriceCalculationService.calculatePrice(calculationRequest);
sfpContent.setFreeCount(0); if (calculationResult.getFinalAmount().compareTo(BigDecimal.ZERO) > 0) {
} else { sfpContent.setFreeCount(0);
sfpContent.setFreeCount(1); } else {
} sfpContent.setFreeCount(1);
contentList.add(1, sfpContent); }
}); contentList.add(1, sfpContent);
} }
SourceReqQuery sourceReqQuery = new SourceReqQuery(); SourceReqQuery sourceReqQuery = new SourceReqQuery();
sourceReqQuery.setScenicId(face.getScenicId()); sourceReqQuery.setScenicId(face.getScenicId());

View File

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

View File

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

View File

@@ -14,9 +14,7 @@ public interface TaskService {
TemplateRespVO workerGetTemplate(Long templateId, WorkerAuthReqVo req); TemplateRespVO workerGetTemplate(Long templateId, WorkerAuthReqVo req);
void createTaskByFaceIdAndTemplateId(Long faceId, Long templateId); void createTaskByFaceIdAndTemplateId(Long faceId, Long templateId, boolean automatic);
void createTaskByFaceIdAndTemplateId(Long faceId, Long templateId, int automatic);
void taskSuccess(Long taskId, TaskSuccessReqVo req); 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.mapper.VideoMapper;
import com.ycwl.basic.model.mobile.order.IsBuyRespVO; import com.ycwl.basic.model.mobile.order.IsBuyRespVO;
import com.ycwl.basic.model.pc.face.entity.FaceEntity; 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.faceSample.entity.FaceSampleEntity;
import com.ycwl.basic.model.pc.member.resp.MemberRespVO; import com.ycwl.basic.model.pc.member.resp.MemberRespVO;
import com.ycwl.basic.model.pc.mp.MpConfigEntity; 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 org.springframework.stereotype.Service;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator; import java.util.Comparator;
import java.util.Date; import java.util.Date;
import java.util.HashMap; import java.util.HashMap;
@@ -256,7 +254,7 @@ public class TaskTaskServiceImpl implements TaskService {
@Override @Override
public void forceCreateTaskByFaceIdAndTempalteId(Long faceId, Long templateId) { public void forceCreateTaskByFaceIdAndTempalteId(Long faceId, Long templateId) {
createTaskByFaceIdAndTemplateIdInternal(faceId, templateId, 0, true); createTaskByFaceIdAndTemplateIdInternal(faceId, templateId, false, true);
} }
@Override @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"))) { 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 { } else {
if (Boolean.TRUE.equals(scenicConfig.getBoolean("force_create_vlog"))) { if (Boolean.TRUE.equals(scenicConfig.getBoolean("force_create_vlog"))) {
Long availableTemplateId = templateBiz.findFirstAvailableTemplate(templateList.stream().map(TemplateRespVO::getId).toList(), faceId, false); Long availableTemplateId = templateBiz.findFirstAvailableTemplate(templateList.stream().map(TemplateRespVO::getId).toList(), faceId, false);
if (availableTemplateId != null) { if (availableTemplateId != null) {
createTaskByFaceIdAndTemplateId(faceId, availableTemplateId, 1); createTaskByFaceIdAndTemplateId(faceId, availableTemplateId, true);
} else { } else {
log.info("faceId:{} available template is not exist", faceId); log.info("faceId:{} available template is not exist", faceId);
} }
} else { } else {
// 非强制创建,只创建第一个可用模板 // 非强制创建,只创建第一个可用模板
if (!templateList.isEmpty()) { 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 @Override
public void createTaskByFaceIdAndTemplateId(Long faceId, Long templateId, int automatic) { public void createTaskByFaceIdAndTemplateId(Long faceId, Long templateId, boolean automatic) {
createTaskByFaceIdAndTemplateIdInternal(faceId, templateId, automatic, false); 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); FaceEntity face = faceRepository.getFace(faceId);
if (face == null) { if (face == null) {
log.info("faceId:{} is not exist", faceId); log.info("faceId:{} is not exist", faceId);
@@ -430,7 +424,7 @@ public class TaskTaskServiceImpl implements TaskService {
taskEntity.setScenicId(face.getScenicId()); taskEntity.setScenicId(face.getScenicId());
taskEntity.setFaceId(faceId); taskEntity.setFaceId(faceId);
taskEntity.setTemplateId(templateId); taskEntity.setTemplateId(templateId);
taskEntity.setAutomatic(automatic); taskEntity.setAutomatic(automatic ? 1 : 0);
} }
taskEntity.setWorkerId(null); taskEntity.setWorkerId(null);
taskEntity.setStatus(0); taskEntity.setStatus(0);

View File

@@ -1,9 +1,12 @@
server: server:
port: 8030 port: 8030
shutdown: graceful
spring: spring:
application: application:
name: zt name: zt
lifecycle:
timeout-per-shutdown-phase: 60s
# Feign配置(简化版,基于Nacos服务发现) # Feign配置(简化版,基于Nacos服务发现)
feign: feign:
@@ -38,4 +41,7 @@ kafka:
# 开发环境日志配置 # 开发环境日志配置
logging: logging:
level: 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: server:
port: 8031 port: 8031
shutdown: graceful
spring: spring:
application: application:
name: zt name: zt
lifecycle:
timeout-per-shutdown-phase: 60s
# 生产环境日志级别 # 生产环境日志级别
logging: logging:
level: 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 FROM source s
), ),
member_plog_data AS ( 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 FROM puzzle_generation_record gr
left join puzzle_template pt on gr.template_id = pt.id
) )
SELECT SELECT
oi.id AS oiId, 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_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_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_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}; WHERE oi.order_id = #{id};
</select> </select>