You've already forked FrameTour-BE
Compare commits
7 Commits
24bbb63bf7
...
fe3bda28b4
| Author | SHA1 | Date | |
|---|---|---|---|
| fe3bda28b4 | |||
| 66775ea48b | |||
| 125fadd6c5 | |||
| 1f4a16f0e6 | |||
| e9916d6aca | |||
| b71452b3ed | |||
| 4a82ee6c4d |
@@ -220,6 +220,7 @@ public class OrderBiz {
|
||||
break;
|
||||
case 1: // 视频原素材
|
||||
case 2: // 照片原素材
|
||||
case 13: // AI微单
|
||||
sourceRepository.setUserIsBuyItem(order.getMemberId(), item.getGoodsType(), item.getGoodsId(), order.getId());
|
||||
break;
|
||||
case 3:
|
||||
|
||||
@@ -16,7 +16,12 @@ public enum SourceType {
|
||||
/**
|
||||
* 图片类型
|
||||
*/
|
||||
IMAGE(2, "图片");
|
||||
IMAGE(2, "图片"),
|
||||
|
||||
/**
|
||||
* AI微单类型
|
||||
*/
|
||||
AI_CAM(3, "AI微单");
|
||||
|
||||
private final int code;
|
||||
private final String description;
|
||||
@@ -68,4 +73,14 @@ public enum SourceType {
|
||||
public static boolean isImage(Integer code) {
|
||||
return code != null && code == IMAGE.code;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断给定的代码是否为AI微单类型
|
||||
*
|
||||
* @param code 类型代码
|
||||
* @return true-是AI微单,false-不是AI微单
|
||||
*/
|
||||
public static boolean isAiCam(Integer code) {
|
||||
return code != null && code == AI_CAM.code;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
package com.ycwl.basic.controller.mobile;
|
||||
|
||||
import com.ycwl.basic.model.mobile.goods.GoodsDetailVO;
|
||||
import com.ycwl.basic.service.mobile.AppAiCamService;
|
||||
import com.ycwl.basic.utils.ApiResponse;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* AI相机相关接口
|
||||
*/
|
||||
@Slf4j
|
||||
@RestController
|
||||
@RequestMapping("/api/mobile/ai_cam/v1")
|
||||
@RequiredArgsConstructor
|
||||
public class AppAiCamController {
|
||||
|
||||
private final AppAiCamService appAiCamService;
|
||||
|
||||
/**
|
||||
* 根据faceId获取AI相机识别到的商品列表
|
||||
* @param faceId 人脸ID
|
||||
* @return 商品详情列表
|
||||
*/
|
||||
@GetMapping("/{faceId}/content")
|
||||
public ApiResponse<List<GoodsDetailVO>> getAiCamGoods(@PathVariable Long faceId) {
|
||||
try {
|
||||
List<GoodsDetailVO> goods = appAiCamService.getAiCamGoodsByFaceId(faceId);
|
||||
return ApiResponse.success(goods);
|
||||
} catch (Exception e) {
|
||||
log.error("获取AI相机商品失败: faceId={}", faceId, e);
|
||||
return ApiResponse.fail("获取商品列表失败");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量添加会员与source的关联关系
|
||||
* @param faceId 人脸ID
|
||||
* @param sourceIds source ID列表
|
||||
* @return 添加结果
|
||||
*/
|
||||
@PostMapping("/{faceId}/relations")
|
||||
public ApiResponse<String> addMemberSourceRelations(
|
||||
@PathVariable Long faceId,
|
||||
@RequestBody List<Long> sourceIds
|
||||
) {
|
||||
try {
|
||||
int count = appAiCamService.addMemberSourceRelations(faceId, sourceIds);
|
||||
return ApiResponse.success("成功添加" + count + "条关联记录");
|
||||
} catch (IllegalArgumentException e) {
|
||||
log.warn("添加关联失败: faceId={}, error={}", faceId, e.getMessage());
|
||||
return ApiResponse.fail(e.getMessage());
|
||||
} catch (Exception e) {
|
||||
log.error("添加关联失败: faceId={}", faceId, e);
|
||||
return ApiResponse.fail("添加关联失败");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -86,7 +86,7 @@ public class AppOrderV2Controller {
|
||||
TaskEntity task = videoTaskRepository.getTaskById(video.getTaskId());
|
||||
request.setFaceId(task.getFaceId());
|
||||
}
|
||||
case RECORDING_SET, PHOTO_SET -> request.setFaceId(Long.valueOf(productItem.getProductId()));
|
||||
case RECORDING_SET, PHOTO_SET, AI_CAM_PHOTO_SET -> request.setFaceId(Long.valueOf(productItem.getProductId()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -119,6 +119,14 @@ public class AppOrderV2Controller {
|
||||
Integer count = sourceMapper.countUser(sourceReqQuery);
|
||||
product.setQuantity(count);
|
||||
break;
|
||||
case AI_CAM_PHOTO_SET:
|
||||
SourceReqQuery aiPhotoSetReqQuery = new SourceReqQuery();
|
||||
aiPhotoSetReqQuery.setMemberId(currentUserId);
|
||||
aiPhotoSetReqQuery.setType(13);
|
||||
aiPhotoSetReqQuery.setFaceId(face.getId());
|
||||
Integer _count = sourceMapper.countUser(aiPhotoSetReqQuery);
|
||||
product.setQuantity(_count);
|
||||
break;
|
||||
default:
|
||||
log.warn("未知的商品类型,跳过重复购买检查: productType={}", product.getProductType());
|
||||
break;
|
||||
|
||||
@@ -3,10 +3,19 @@ package com.ycwl.basic.mapper;
|
||||
import com.ycwl.basic.model.pc.faceDetectLog.entity.FaceDetectLogAiCamEntity;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* AI相机人脸识别日志Mapper
|
||||
*/
|
||||
@Mapper
|
||||
public interface FaceDetectLogAiCamMapper {
|
||||
int add(FaceDetectLogAiCamEntity entity);
|
||||
|
||||
/**
|
||||
* 根据faceId查询所有识别记录
|
||||
* @param faceId 人脸ID
|
||||
* @return 识别记录列表
|
||||
*/
|
||||
List<FaceDetectLogAiCamEntity> listByFaceId(Long faceId);
|
||||
}
|
||||
|
||||
@@ -148,4 +148,20 @@ public interface SourceMapper {
|
||||
* @return source实体
|
||||
*/
|
||||
SourceEntity getSourceByFaceAndDeviceId(Long faceId, Long deviceId, Integer type, String sortStrategy);
|
||||
|
||||
/**
|
||||
* 根据faceSampleId列表和type查询source列表
|
||||
* @param faceSampleIds faceSampleId列表
|
||||
* @param type 素材类型
|
||||
* @return source实体列表
|
||||
*/
|
||||
List<SourceEntity> listByFaceSampleIdsAndType(List<Long> faceSampleIds, Integer type);
|
||||
|
||||
/**
|
||||
* 删除指定faceId和type的member_source关联记录
|
||||
* @param faceId 人脸ID
|
||||
* @param type 素材类型
|
||||
* @return 删除的记录数
|
||||
*/
|
||||
int deleteRelationsByFaceIdAndType(Long faceId, Integer type);
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ public enum ProductType {
|
||||
// 照片类
|
||||
PHOTO("PHOTO", "照片", ProductCategory.PHOTO),
|
||||
PHOTO_SET("PHOTO_SET", "照片集", ProductCategory.PHOTO),
|
||||
AI_CAM_PHOTO_SET("AI_CAM_PHOTO_SET", "照片集", ProductCategory.PHOTO),
|
||||
PHOTO_LOG("PHOTO_LOG", "pLog图", ProductCategory.PHOTO),
|
||||
|
||||
// 视频类(素材视频)
|
||||
|
||||
@@ -41,6 +41,9 @@ public class SourceRepository {
|
||||
}
|
||||
|
||||
public void setUserIsBuyItem(Long memberId, int type, Long faceId, Long orderId) {
|
||||
if (type == 13) {
|
||||
type = 3; // compact
|
||||
}
|
||||
MemberSourceEntity memberSource = new MemberSourceEntity();
|
||||
memberSource.setMemberId(memberId);
|
||||
memberSource.setFaceId(faceId);
|
||||
|
||||
@@ -114,7 +114,7 @@ public class ZTSourceDataService {
|
||||
entity.setDeviceId(message.getDeviceId());
|
||||
entity.setUrl(message.getSourceUrl()); // 使用sourceUrl,不使用缩略图
|
||||
entity.setThumbUrl(message.getThumbnailUrl()); // 设置缩略图URL
|
||||
entity.setType(2); // 照片类型
|
||||
entity.setType(message.getSourceType()); // 照片类型
|
||||
|
||||
// 人脸样本ID处理
|
||||
entity.setFaceSampleId(message.getFaceSampleId());
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
package com.ycwl.basic.service.mobile;
|
||||
|
||||
import com.ycwl.basic.model.mobile.goods.GoodsDetailVO;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* AI相机相关服务
|
||||
*/
|
||||
public interface AppAiCamService {
|
||||
|
||||
/**
|
||||
* 根据faceId获取AI相机识别到的商品列表
|
||||
* @param faceId 人脸ID
|
||||
* @return 商品详情列表
|
||||
*/
|
||||
List<GoodsDetailVO> getAiCamGoodsByFaceId(Long faceId);
|
||||
|
||||
/**
|
||||
* 批量添加会员与source的关联关系
|
||||
* @param faceId 人脸ID
|
||||
* @param sourceIds source ID列表
|
||||
* @return 添加成功的数量
|
||||
*/
|
||||
int addMemberSourceRelations(Long faceId, List<Long> sourceIds);
|
||||
}
|
||||
@@ -0,0 +1,275 @@
|
||||
package com.ycwl.basic.service.mobile.impl;
|
||||
|
||||
import com.ycwl.basic.facebody.entity.SearchFaceResp;
|
||||
import com.ycwl.basic.facebody.entity.SearchFaceResultItem;
|
||||
import com.ycwl.basic.integration.common.manager.DeviceConfigManager;
|
||||
import com.ycwl.basic.mapper.FaceDetectLogAiCamMapper;
|
||||
import com.ycwl.basic.mapper.FaceMapper;
|
||||
import com.ycwl.basic.mapper.SourceMapper;
|
||||
import com.ycwl.basic.model.mobile.goods.GoodsDetailVO;
|
||||
import com.ycwl.basic.model.pc.face.entity.FaceEntity;
|
||||
import com.ycwl.basic.model.pc.faceDetectLog.entity.FaceDetectLogAiCamEntity;
|
||||
import com.ycwl.basic.model.pc.source.entity.MemberSourceEntity;
|
||||
import com.ycwl.basic.model.pc.source.entity.SourceEntity;
|
||||
import com.ycwl.basic.repository.DeviceRepository;
|
||||
import com.ycwl.basic.service.mobile.AppAiCamService;
|
||||
import com.ycwl.basic.utils.JacksonUtil;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* AI相机相关服务实现
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class AppAiCamServiceImpl implements AppAiCamService {
|
||||
|
||||
private final FaceDetectLogAiCamMapper faceDetectLogAiCamMapper;
|
||||
private final SourceMapper sourceMapper;
|
||||
private final FaceMapper faceMapper;
|
||||
private final DeviceRepository deviceRepository;
|
||||
|
||||
private static final float DEFAULT_SCORE_THRESHOLD = 0.8f;
|
||||
private static final int DEFAULT_PHOTO_LIMIT = 10;
|
||||
private static final int AI_CAM_SOURCE_TYPE = 3;
|
||||
|
||||
@Override
|
||||
public List<GoodsDetailVO> getAiCamGoodsByFaceId(Long faceId) {
|
||||
// 1. 查询该faceId的所有识别记录
|
||||
List<FaceDetectLogAiCamEntity> detectLogs = faceDetectLogAiCamMapper.listByFaceId(faceId);
|
||||
if (detectLogs == null || detectLogs.isEmpty()) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
// 2. 按设备分组并根据设备配置过滤faceSampleId
|
||||
Map<Long, List<Long>> deviceFaceSampleMap = new LinkedHashMap<>();
|
||||
|
||||
// 按设备分组识别记录
|
||||
Map<Long, List<FaceDetectLogAiCamEntity>> deviceLogsMap = detectLogs.stream()
|
||||
.collect(Collectors.groupingBy(FaceDetectLogAiCamEntity::getDeviceId));
|
||||
|
||||
// 遍历每个设备的识别记录
|
||||
for (Map.Entry<Long, List<FaceDetectLogAiCamEntity>> entry : deviceLogsMap.entrySet()) {
|
||||
Long deviceId = entry.getKey();
|
||||
List<FaceDetectLogAiCamEntity> deviceLogs = entry.getValue();
|
||||
|
||||
// 获取设备配置
|
||||
DeviceConfigManager configManager = deviceRepository.getDeviceConfigManager(deviceId);
|
||||
|
||||
// 获取该设备的分数阈值(百分制,需要转换为0-1)
|
||||
float scoreThreshold = DEFAULT_SCORE_THRESHOLD;
|
||||
if (configManager != null) {
|
||||
Float thresholdPercent = configManager.getFloat("ai_cam_face_score_threshold");
|
||||
if (thresholdPercent != null) {
|
||||
scoreThreshold = thresholdPercent / 100.0f;
|
||||
log.debug("设备{}使用配置的分数阈值: {}% ({})", deviceId, thresholdPercent, scoreThreshold);
|
||||
}
|
||||
}
|
||||
|
||||
// 获取该设备的照片数量限制
|
||||
int photoLimit = DEFAULT_PHOTO_LIMIT;
|
||||
if (configManager != null) {
|
||||
Integer limit = configManager.getInteger("ai_cam_photo_limit");
|
||||
if (limit != null && limit > 0) {
|
||||
photoLimit = limit;
|
||||
log.debug("设备{}使用配置的照片限制: {}", deviceId, photoLimit);
|
||||
}
|
||||
}
|
||||
|
||||
// 获取该设备的时间范围限制(分钟)
|
||||
Integer timeRangeMin = null;
|
||||
if (configManager != null) {
|
||||
Integer range = configManager.getInteger("ai_cam_time_range_min");
|
||||
if (range != null && range > 0) {
|
||||
timeRangeMin = range;
|
||||
log.debug("设备{}使用配置的时间范围: {}分钟", deviceId, timeRangeMin);
|
||||
}
|
||||
}
|
||||
|
||||
// 收集该设备符合阈值的faceSampleId,同时记录分数和时间信息用于后续过滤
|
||||
class DetectResult {
|
||||
Long faceSampleId;
|
||||
Float score;
|
||||
Date detectTime;
|
||||
|
||||
DetectResult(Long faceSampleId, Float score, Date detectTime) {
|
||||
this.faceSampleId = faceSampleId;
|
||||
this.score = score;
|
||||
this.detectTime = detectTime;
|
||||
}
|
||||
}
|
||||
|
||||
List<DetectResult> detectResults = new ArrayList<>();
|
||||
for (FaceDetectLogAiCamEntity detectLog : deviceLogs) {
|
||||
if (detectLog.getMatchRawResult() == null) {
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
SearchFaceResp resp = JacksonUtil.parseObject(detectLog.getMatchRawResult(), SearchFaceResp.class);
|
||||
if (resp != null && resp.getResult() != null) {
|
||||
for (SearchFaceResultItem item : resp.getResult()) {
|
||||
// 使用设备配置的分数阈值
|
||||
if (item.getScore() != null && item.getScore() >= scoreThreshold && item.getExtData() != null) {
|
||||
try {
|
||||
Long faceSampleId = Long.parseLong(item.getExtData());
|
||||
detectResults.add(new DetectResult(
|
||||
faceSampleId,
|
||||
item.getScore(),
|
||||
detectLog.getCreateTime()
|
||||
));
|
||||
} catch (NumberFormatException e) {
|
||||
log.warn("解析faceSampleId失败: extData={}", item.getExtData());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("解析matchRawResult失败: logId={}", detectLog.getId(), e);
|
||||
}
|
||||
}
|
||||
|
||||
// 应用照片数量限制(保留前N个)
|
||||
if (detectResults.size() > photoLimit) {
|
||||
log.debug("设备{}的照片数量{}超过限制{},截取前{}张",
|
||||
deviceId, detectResults.size(), photoLimit, photoLimit);
|
||||
detectResults = detectResults.subList(0, photoLimit);
|
||||
}
|
||||
|
||||
// 应用时间范围限制
|
||||
List<Long> deviceFaceSampleIds;
|
||||
if (timeRangeMin != null && !detectResults.isEmpty()) {
|
||||
// 找到分数最高的照片
|
||||
DetectResult highestScoreResult = detectResults.stream()
|
||||
.max(Comparator.comparing(r -> r.score))
|
||||
.orElse(null);
|
||||
|
||||
if (highestScoreResult != null && highestScoreResult.detectTime != null) {
|
||||
Date baseTime = highestScoreResult.detectTime;
|
||||
long halfRangeMillis = (long) timeRangeMin * 60 * 1000 / 2;
|
||||
Date startTime = new Date(baseTime.getTime() - halfRangeMillis);
|
||||
Date endTime = new Date(baseTime.getTime() + halfRangeMillis);
|
||||
|
||||
// 过滤出时间范围内的照片
|
||||
List<DetectResult> filteredResults = detectResults.stream()
|
||||
.filter(r -> r.detectTime != null
|
||||
&& !r.detectTime.before(startTime)
|
||||
&& !r.detectTime.after(endTime))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
log.debug("设备{}应用时间范围{}分钟过滤: {}张 -> {}张 (基准时间: {})",
|
||||
deviceId, timeRangeMin, detectResults.size(), filteredResults.size(), baseTime);
|
||||
|
||||
deviceFaceSampleIds = filteredResults.stream()
|
||||
.map(r -> r.faceSampleId)
|
||||
.collect(Collectors.toList());
|
||||
} else {
|
||||
// 没有时间信息,不过滤
|
||||
deviceFaceSampleIds = detectResults.stream()
|
||||
.map(r -> r.faceSampleId)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
} else {
|
||||
// 不限制时间范围
|
||||
deviceFaceSampleIds = detectResults.stream()
|
||||
.map(r -> r.faceSampleId)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
if (!deviceFaceSampleIds.isEmpty()) {
|
||||
deviceFaceSampleMap.put(deviceId, deviceFaceSampleIds);
|
||||
}
|
||||
}
|
||||
|
||||
// 3. 合并所有设备的faceSampleId(去重)
|
||||
Set<Long> faceSampleIds = new HashSet<>();
|
||||
for (List<Long> ids : deviceFaceSampleMap.values()) {
|
||||
faceSampleIds.addAll(ids);
|
||||
}
|
||||
|
||||
if (faceSampleIds.isEmpty()) {
|
||||
log.debug("没有符合条件的faceSampleId, faceId={}", faceId);
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
log.info("人脸{}在{}个设备上识别到{}个不重复的faceSampleId",
|
||||
faceId, deviceFaceSampleMap.size(), faceSampleIds.size());
|
||||
|
||||
// 4. 根据faceSampleId列表查询type=3的source记录
|
||||
List<SourceEntity> sources = sourceMapper.listByFaceSampleIdsAndType(
|
||||
new ArrayList<>(faceSampleIds), AI_CAM_SOURCE_TYPE
|
||||
);
|
||||
|
||||
if (sources == null || sources.isEmpty()) {
|
||||
log.debug("未找到type=3的source记录, faceId={}", faceId);
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
log.info("查询到{}条AI相机图像记录, faceId={}", sources.size(), faceId);
|
||||
|
||||
// 5. 查询Face信息以获取scenicId
|
||||
FaceEntity face = faceMapper.get(faceId);
|
||||
if (face == null) {
|
||||
log.warn("Face不存在: faceId={}", faceId);
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
// 6. 转换为GoodsDetailVO
|
||||
return sources.stream().map(source -> {
|
||||
GoodsDetailVO vo = new GoodsDetailVO();
|
||||
vo.setFaceId(faceId);
|
||||
vo.setScenicId(face.getScenicId());
|
||||
vo.setGoodsType(2); // 2表示原素材
|
||||
vo.setGoodsId(source.getId());
|
||||
vo.setUrl(source.getUrl());
|
||||
vo.setVideoUrl(source.getVideoUrl());
|
||||
vo.setCreateTime(source.getCreateTime());
|
||||
vo.setIsBuy(source.getIsBuy() != null ? source.getIsBuy() : 0);
|
||||
return vo;
|
||||
}).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int addMemberSourceRelations(Long faceId, List<Long> sourceIds) {
|
||||
if (sourceIds == null || sourceIds.isEmpty()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// 查询Face信息
|
||||
FaceEntity face = faceMapper.get(faceId);
|
||||
if (face == null) {
|
||||
throw new IllegalArgumentException("Face不存在: faceId=" + faceId);
|
||||
}
|
||||
|
||||
if (face.getMemberId() == null) {
|
||||
throw new IllegalArgumentException("Face未关联会员: faceId=" + faceId);
|
||||
}
|
||||
|
||||
// 删除该faceId对应的旧的type=3关系
|
||||
int deleted = sourceMapper.deleteRelationsByFaceIdAndType(faceId, AI_CAM_SOURCE_TYPE);
|
||||
log.info("删除faceId={}的旧AI相机关联记录: {}条", faceId, deleted);
|
||||
|
||||
// 构建MemberSourceEntity列表
|
||||
List<MemberSourceEntity> relations = sourceIds.stream().map(sourceId -> {
|
||||
MemberSourceEntity entity = new MemberSourceEntity();
|
||||
entity.setMemberId(face.getMemberId());
|
||||
entity.setScenicId(face.getScenicId());
|
||||
entity.setFaceId(faceId);
|
||||
entity.setSourceId(sourceId);
|
||||
entity.setType(AI_CAM_SOURCE_TYPE);
|
||||
entity.setIsBuy(0);
|
||||
entity.setIsFree(0);
|
||||
return entity;
|
||||
}).collect(Collectors.toList());
|
||||
|
||||
// 批量插入
|
||||
int inserted = sourceMapper.addRelations(relations);
|
||||
log.info("为faceId={}添加新AI相机关联记录: {}条", faceId, inserted);
|
||||
|
||||
return inserted;
|
||||
}
|
||||
}
|
||||
@@ -127,6 +127,8 @@ public class GoodsServiceImpl implements GoodsService {
|
||||
goodsNamePrefix = "录像";
|
||||
} else if (sourceType == 2) {
|
||||
goodsNamePrefix = "图片";
|
||||
} else if (sourceType == 3) {
|
||||
goodsNamePrefix = "AI微单";
|
||||
} else {
|
||||
goodsNamePrefix = "其他类型";
|
||||
}
|
||||
@@ -573,7 +575,7 @@ public class GoodsServiceImpl implements GoodsService {
|
||||
if (query.getGoodsId() != null) {
|
||||
list = list.stream().filter(source -> source.getId().equals(query.getGoodsId())).toList();
|
||||
}
|
||||
if (!Integer.valueOf(2).equals(query.getSourceType())) {
|
||||
if (Integer.valueOf(1).equals(query.getSourceType())) {
|
||||
return list.stream().map(source -> {
|
||||
GoodsUrlVO goodsUrlVO = new GoodsUrlVO();
|
||||
goodsUrlVO.setGoodsType(source.getType());
|
||||
@@ -592,7 +594,7 @@ public class GoodsServiceImpl implements GoodsService {
|
||||
}
|
||||
return true;
|
||||
}).count();
|
||||
IsBuyRespVO isBuy = orderBiz.isBuy(face.getScenicId(), face.getMemberId(), face.getId(), query.getSourceType(), face.getId());
|
||||
IsBuyRespVO isBuy = orderBiz.isBuy(face.getScenicId(), face.getMemberId(), face.getId(), sourceType == 3 ? 13 : sourceType, face.getId());
|
||||
if (count > 0) {
|
||||
if (!isBuy.isBuy()) {
|
||||
return Collections.emptyList();
|
||||
@@ -829,6 +831,9 @@ public class GoodsServiceImpl implements GoodsService {
|
||||
} else if (type == 2) {
|
||||
goodsPageVO.setGoodsName("照片集");
|
||||
goodsPageVO.setTemplateCoverUrl(scenicConfig.getString("photo_cover_url"));
|
||||
} else if (type == 3) {
|
||||
goodsPageVO.setGoodsName("AI微单");
|
||||
goodsPageVO.setTemplateCoverUrl(scenicConfig.getString("ai_camera_cover_url"));
|
||||
} else {
|
||||
goodsPageVO.setGoodsName("未知商品");
|
||||
}
|
||||
|
||||
@@ -11,11 +11,10 @@ public interface FaceDetectLogAiCamService {
|
||||
/**
|
||||
* 搜索人脸库并保存日志
|
||||
* @param scenicId 景区ID
|
||||
* @param deviceId 设备ID
|
||||
* @param faceSampleId 人脸样本ID
|
||||
* @param faceId 人脸样本ID
|
||||
* @param faceUrl 人脸URL
|
||||
* @param adapter 人脸适配器
|
||||
* @return 搜索结果
|
||||
*/
|
||||
SearchFaceResp searchAndLog(Long scenicId, Long deviceId, Long faceSampleId, String faceUrl, IFaceBodyAdapter adapter);
|
||||
void searchAndLog(Long scenicId, Long faceId, String faceUrl, IFaceBodyAdapter adapter);
|
||||
}
|
||||
|
||||
@@ -2,8 +2,10 @@ package com.ycwl.basic.service.pc.impl;
|
||||
|
||||
import com.ycwl.basic.facebody.adapter.IFaceBodyAdapter;
|
||||
import com.ycwl.basic.facebody.entity.SearchFaceResp;
|
||||
import com.ycwl.basic.integration.device.dto.device.DeviceV2DTO;
|
||||
import com.ycwl.basic.mapper.FaceDetectLogAiCamMapper;
|
||||
import com.ycwl.basic.model.pc.faceDetectLog.entity.FaceDetectLogAiCamEntity;
|
||||
import com.ycwl.basic.repository.DeviceRepository;
|
||||
import com.ycwl.basic.service.pc.FaceDetectLogAiCamService;
|
||||
import com.ycwl.basic.utils.JacksonUtil;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
@@ -11,6 +13,7 @@ import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
@Slf4j
|
||||
@Service
|
||||
@@ -18,9 +21,20 @@ import java.util.Date;
|
||||
public class FaceDetectLogAiCamServiceImpl implements FaceDetectLogAiCamService {
|
||||
|
||||
private final FaceDetectLogAiCamMapper faceDetectLogAiCamMapper;
|
||||
private final DeviceRepository deviceRepository;
|
||||
|
||||
@Override
|
||||
public SearchFaceResp searchAndLog(Long scenicId, Long deviceId, Long faceSampleId, String faceUrl, IFaceBodyAdapter adapter) {
|
||||
public void searchAndLog(Long scenicId, Long faceId, String faceUrl, IFaceBodyAdapter adapter) {
|
||||
List<DeviceV2DTO> devices = deviceRepository.getAllDeviceByScenicId(scenicId);
|
||||
for (DeviceV2DTO device : devices) {
|
||||
if (!device.getType().equals("AI_CAM")) {
|
||||
continue;
|
||||
}
|
||||
searchDeviceAndLog(scenicId, device.getId(), faceId, faceUrl, adapter);
|
||||
}
|
||||
}
|
||||
|
||||
private SearchFaceResp searchDeviceAndLog(Long scenicId, Long deviceId, Long faceId, String faceUrl, IFaceBodyAdapter adapter) {
|
||||
String dbName = "AiCam" + deviceId;
|
||||
|
||||
SearchFaceResp resp = null;
|
||||
@@ -28,7 +42,7 @@ public class FaceDetectLogAiCamServiceImpl implements FaceDetectLogAiCamService
|
||||
// 调用适配器搜索人脸
|
||||
resp = adapter.searchFace(dbName, faceUrl);
|
||||
} catch (Exception e) {
|
||||
log.error("AI相机人脸搜索异常: scenicId={}, deviceId={}, faceSampleId={}", scenicId, deviceId, faceSampleId, e);
|
||||
log.error("AI相机人脸搜索异常: scenicId={}, deviceId={}, faceId={}", scenicId, deviceId, faceId, e);
|
||||
// 发生异常时记录空结果或错误信息,视业务需求而定。这里暂不中断流程,继续记录日志
|
||||
}
|
||||
|
||||
@@ -37,7 +51,7 @@ public class FaceDetectLogAiCamServiceImpl implements FaceDetectLogAiCamService
|
||||
FaceDetectLogAiCamEntity logEntity = new FaceDetectLogAiCamEntity();
|
||||
logEntity.setScenicId(scenicId);
|
||||
logEntity.setDeviceId(deviceId);
|
||||
logEntity.setFaceId(faceSampleId);
|
||||
logEntity.setFaceId(faceId);
|
||||
logEntity.setDbName(dbName);
|
||||
logEntity.setFaceUrl(faceUrl);
|
||||
logEntity.setCreateTime(new Date());
|
||||
@@ -51,13 +65,13 @@ public class FaceDetectLogAiCamServiceImpl implements FaceDetectLogAiCamService
|
||||
// 记录原始响应
|
||||
logEntity.setMatchRawResult(JacksonUtil.toJSONString(resp));
|
||||
} else {
|
||||
logEntity.setMatchRawResult("{\"error\": \"search failed or exception\"}");
|
||||
logEntity.setMatchRawResult("{\"error\": \"search failed or exception\"}");
|
||||
}
|
||||
|
||||
faceDetectLogAiCamMapper.add(logEntity);
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("保存AI相机人脸识别日志失败: faceSampleId={}", faceSampleId, e);
|
||||
log.error("保存AI相机人脸识别日志失败: faceId={}", faceId, e);
|
||||
}
|
||||
|
||||
return resp;
|
||||
|
||||
@@ -9,7 +9,6 @@ import com.ycwl.basic.constant.BaseContextHandler;
|
||||
import com.ycwl.basic.exception.BaseException;
|
||||
import com.ycwl.basic.facebody.adapter.IFaceBodyAdapter;
|
||||
import com.ycwl.basic.facebody.entity.SearchFaceResultItem;
|
||||
import com.ycwl.basic.integration.common.manager.DeviceConfigManager;
|
||||
import com.ycwl.basic.mapper.FaceSampleMapper;
|
||||
import com.ycwl.basic.mapper.ProjectMapper;
|
||||
import com.ycwl.basic.mapper.SourceMapper;
|
||||
@@ -62,6 +61,7 @@ import com.ycwl.basic.repository.TemplateRepository;
|
||||
import com.ycwl.basic.repository.VideoRepository;
|
||||
import com.ycwl.basic.repository.VideoTaskRepository;
|
||||
import com.ycwl.basic.service.mobile.GoodsService;
|
||||
import com.ycwl.basic.service.pc.FaceDetectLogAiCamService;
|
||||
import com.ycwl.basic.service.pc.FaceService;
|
||||
import com.ycwl.basic.service.pc.ScenicService;
|
||||
import com.ycwl.basic.constant.SourceType;
|
||||
@@ -71,7 +71,6 @@ import com.ycwl.basic.service.pc.helper.SearchResultMerger;
|
||||
import com.ycwl.basic.service.pc.helper.ScenicConfigFacade;
|
||||
import com.ycwl.basic.service.pc.orchestrator.FaceMatchingOrchestrator;
|
||||
import com.ycwl.basic.service.pc.processor.BuyStatusProcessor;
|
||||
import com.ycwl.basic.service.pc.processor.FaceRecoveryStrategy;
|
||||
import com.ycwl.basic.service.pc.processor.SourceRelationProcessor;
|
||||
import com.ycwl.basic.service.pc.processor.VideoRecreationHandler;
|
||||
import com.ycwl.basic.service.pc.strategy.RematchContext;
|
||||
@@ -86,7 +85,8 @@ import com.ycwl.basic.storage.enums.StorageAcl;
|
||||
import com.ycwl.basic.storage.utils.StorageUtil;
|
||||
import com.ycwl.basic.utils.*;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.logging.log4j.util.Strings;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.commons.lang3.Strings;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
@@ -193,6 +193,8 @@ public class FaceServiceImpl implements FaceService {
|
||||
private IPriceCalculationService iPriceCalculationService;
|
||||
@Autowired
|
||||
private PuzzleTemplateMapper puzzleTemplateMapper;
|
||||
@Autowired
|
||||
private FaceDetectLogAiCamService faceDetectLogAiCamService;
|
||||
|
||||
@Override
|
||||
public ApiResponse<PageInfo<FaceRespVO>> pageQuery(FaceReqQuery faceReqQuery) {
|
||||
@@ -312,13 +314,16 @@ public class FaceServiceImpl implements FaceService {
|
||||
Long finalFaceId = newFaceId;
|
||||
Thread thread = new Thread(() -> printerService.autoAddPhotosToPreferPrint(finalFaceId), "auto-add-print-" + newFaceId);
|
||||
thread.start();
|
||||
if (org.apache.commons.lang3.Strings.CI.equals("print", scene)) {
|
||||
if (Strings.CI.equals("print", scene)) {
|
||||
try {
|
||||
thread.join();
|
||||
} catch (InterruptedException ignore) {
|
||||
|
||||
}
|
||||
}
|
||||
if (Strings.CI.equals("aiCam", scene)) {
|
||||
faceDetectLogAiCamService.searchAndLog(scenicId, newFaceId, faceUrl, faceBodyAdapter);
|
||||
}
|
||||
|
||||
return resp;
|
||||
}
|
||||
@@ -531,20 +536,29 @@ public class FaceServiceImpl implements FaceService {
|
||||
List<SourceRespVO> sourceList = sourceMapper.queryByRelation(sourceReqQuery);
|
||||
ContentPageVO sourceVideoContent = new ContentPageVO();
|
||||
ContentPageVO sourceImageContent = new ContentPageVO();
|
||||
ContentPageVO sourceAiCamContent = new ContentPageVO();
|
||||
sourceVideoContent.setName("录像集");
|
||||
sourceImageContent.setName("照片集");
|
||||
sourceAiCamContent.setName("AI微单");
|
||||
sourceVideoContent.setSort(9999);
|
||||
sourceImageContent.setSort(9999);
|
||||
sourceAiCamContent.setSort(9999);
|
||||
sourceVideoContent.setScenicId(face.getScenicId());
|
||||
sourceImageContent.setScenicId(face.getScenicId());
|
||||
sourceAiCamContent.setScenicId(face.getScenicId());
|
||||
sourceVideoContent.setGoodsType(1);
|
||||
sourceImageContent.setGoodsType(2);
|
||||
sourceAiCamContent.setGoodsType(3);
|
||||
sourceVideoContent.setContentType(2);
|
||||
sourceImageContent.setContentType(2);
|
||||
sourceAiCamContent.setContentType(2);
|
||||
sourceVideoContent.setLockType(-1);
|
||||
sourceImageContent.setLockType(-1);
|
||||
sourceAiCamContent.setLockType(-1);
|
||||
sourceVideoContent.setGroup("直出原片");
|
||||
sourceImageContent.setGroup("直出原片");
|
||||
sourceAiCamContent.setGroup("直出原片");
|
||||
ScenicConfigManager configManager = scenicRepository.getScenicConfigManager(face.getScenicId());
|
||||
if (!scenicConfigFacade.isDisableSourceImage(face.getScenicId())) {
|
||||
IsBuyRespVO isBuyRespVO = orderBiz.isBuy(face.getScenicId(), userId, faceId, SourceType.IMAGE.getCode(), faceId);
|
||||
sourceImageContent.setSourceType(isBuyRespVO.getGoodsType());
|
||||
@@ -583,18 +597,40 @@ public class FaceServiceImpl implements FaceService {
|
||||
sourceVideoContent.setFreeCount((int) freeCount);
|
||||
contentList.add(sourceVideoContent);
|
||||
}
|
||||
// AI微单:只有存在type=3的数据时才添加
|
||||
boolean hasAiCam = sourceList.stream().anyMatch(source -> source.getType() == 3);
|
||||
if (hasAiCam) {
|
||||
IsBuyRespVO isBuyRespVO = orderBiz.isBuy(face.getScenicId(), userId, faceId, 13, faceId);
|
||||
sourceAiCamContent.setSourceType(isBuyRespVO.getGoodsType());
|
||||
sourceAiCamContent.setContentId(isBuyRespVO.getGoodsId());
|
||||
if (isBuyRespVO.isBuy()) {
|
||||
sourceAiCamContent.setIsBuy(1);
|
||||
} else {
|
||||
sourceAiCamContent.setIsBuy(0);
|
||||
}
|
||||
// AI微单有数据才显示,所以lockType固定为-1
|
||||
sourceAiCamContent.setLockType(-1);
|
||||
List<MemberSourceEntity> relations = memberRelationRepository.listSourceByFaceRelation(faceId, 3);
|
||||
long freeCount = relations.stream().filter(entity -> Integer.valueOf(1).equals(entity.getIsFree())).count();
|
||||
sourceAiCamContent.setFreeCount((int) freeCount);
|
||||
contentList.add(sourceAiCamContent);
|
||||
}
|
||||
sourceList.stream().collect(Collectors.groupingBy(SourceRespVO::getType)).forEach((type, list) -> {
|
||||
if (type == 1) {
|
||||
sourceVideoContent.setSourceType(1);
|
||||
sourceVideoContent.setLockType(-1);
|
||||
sourceVideoContent.setTemplateCoverUrl(list.getFirst().getUrl());
|
||||
} else {
|
||||
} else if (type == 2) {
|
||||
sourceImageContent.setSourceType(2);
|
||||
sourceImageContent.setLockType(-1);
|
||||
sourceImageContent.setTemplateCoverUrl(list.getFirst().getUrl());
|
||||
if (Strings.isBlank(sourceVideoContent.getTemplateCoverUrl())) {
|
||||
if (StringUtils.isBlank(sourceVideoContent.getTemplateCoverUrl())) {
|
||||
sourceVideoContent.setTemplateCoverUrl(list.getFirst().getUrl());
|
||||
}
|
||||
} else if (type == 3) {
|
||||
sourceAiCamContent.setSourceType(13);
|
||||
sourceAiCamContent.setLockType(-1);
|
||||
sourceAiCamContent.setTemplateCoverUrl(configManager.getString("ai_camera_cover_url"));
|
||||
}
|
||||
});
|
||||
return contentList;
|
||||
@@ -803,7 +839,7 @@ public class FaceServiceImpl implements FaceService {
|
||||
return List.of();
|
||||
}
|
||||
String matchResult = face.getMatchResult();
|
||||
if (matchResult == null || Strings.isBlank(matchResult)) {
|
||||
if (matchResult == null || StringUtils.isBlank(matchResult)) {
|
||||
return List.of();
|
||||
}
|
||||
ScenicConfigManager scenicConfig = scenicRepository.getScenicConfigManager(face.getScenicId());
|
||||
@@ -1009,7 +1045,7 @@ public class FaceServiceImpl implements FaceService {
|
||||
handleCustomFaceMatching(faceId, finalSampleList);
|
||||
}
|
||||
|
||||
if (Strings.isNotBlank(req.getRemark())) {
|
||||
if (StringUtils.isNotBlank(req.getRemark())) {
|
||||
log.info("人脸识别人工调整备注:faceId={}, remark={}", faceId, req.getRemark());
|
||||
}
|
||||
}
|
||||
@@ -1035,7 +1071,7 @@ public class FaceServiceImpl implements FaceService {
|
||||
detail.setLastMatchedAt(face.getUpdateAt() != null ? face.getUpdateAt() : face.getCreateAt());
|
||||
|
||||
String matchResultJson = face.getMatchResult();
|
||||
if (Strings.isBlank(matchResultJson)) {
|
||||
if (StringUtils.isBlank(matchResultJson)) {
|
||||
detail.setAcceptedSamples(Collections.emptyList());
|
||||
detail.setFilteredSamples(Collections.emptyList());
|
||||
return detail;
|
||||
@@ -1155,7 +1191,7 @@ public class FaceServiceImpl implements FaceService {
|
||||
}
|
||||
|
||||
private List<Long> parseMatchSampleIds(String matchSampleIds) {
|
||||
if (Strings.isBlank(matchSampleIds)) {
|
||||
if (StringUtils.isBlank(matchSampleIds)) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
String[] segments = matchSampleIds.split(",");
|
||||
@@ -1170,7 +1206,7 @@ public class FaceServiceImpl implements FaceService {
|
||||
}
|
||||
|
||||
private Long parseLongSilently(String value) {
|
||||
if (Strings.isBlank(value)) {
|
||||
if (StringUtils.isBlank(value)) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
@@ -1222,10 +1258,10 @@ public class FaceServiceImpl implements FaceService {
|
||||
if (sourceEntity == null) {
|
||||
return null;
|
||||
}
|
||||
if (!Strings.isBlank(sourceEntity.getThumbUrl())) {
|
||||
if (!StringUtils.isBlank(sourceEntity.getThumbUrl())) {
|
||||
return sourceEntity.getThumbUrl();
|
||||
}
|
||||
if (!Strings.isBlank(sourceEntity.getUrl())) {
|
||||
if (!StringUtils.isBlank(sourceEntity.getUrl())) {
|
||||
return sourceEntity.getUrl();
|
||||
}
|
||||
return null;
|
||||
|
||||
@@ -177,6 +177,9 @@ public class OrderServiceImpl implements OrderService {
|
||||
} else if (Integer.valueOf(2).equals(orderItemList.getFirst().getGoodsType())) {
|
||||
item.setGoodsName("照片集");
|
||||
item.setOrderType("照片集");
|
||||
} else if (Integer.valueOf(3).equals(orderItemList.getFirst().getGoodsType())) {
|
||||
item.setGoodsName("AI微单");
|
||||
item.setOrderType("AI微单");
|
||||
} else if (Integer.valueOf(0).equals(orderItemList.getFirst().getGoodsType())) {
|
||||
item.setOrderType("旅行Vlog");
|
||||
item.setGoodsName(orderItemList.getFirst().getGoodsName());
|
||||
@@ -237,6 +240,9 @@ public class OrderServiceImpl implements OrderService {
|
||||
} else if (Integer.valueOf(2).equals(orderItemList.getFirst().getGoodsType())) {
|
||||
item.setGoodsName("照片集");
|
||||
item.setOrderType("照片集");
|
||||
} else if (Integer.valueOf(3).equals(orderItemList.getFirst().getGoodsType())) {
|
||||
item.setGoodsName("AI微单");
|
||||
item.setOrderType("AI微单");
|
||||
} else if (Integer.valueOf(0).equals(orderItemList.getFirst().getGoodsType())) {
|
||||
item.setOrderType("旅行Vlog");
|
||||
item.setGoodsName(orderItemList.getFirst().getGoodsName());
|
||||
@@ -940,11 +946,13 @@ public class OrderServiceImpl implements OrderService {
|
||||
case PHOTO_SET -> 2;
|
||||
case VLOG_VIDEO -> 0;
|
||||
case RECORDING_SET -> 1;
|
||||
case AI_CAM_PHOTO_SET -> 13;
|
||||
default -> 0;
|
||||
};
|
||||
Long goodsId = switch (productItem.getProductType()) {
|
||||
case PHOTO_LOG -> Long.valueOf(productItem.getProductId());
|
||||
case PHOTO_SET, RECORDING_SET -> face.getId();
|
||||
case AI_CAM_PHOTO_SET -> face.getId();
|
||||
case VLOG_VIDEO -> {
|
||||
List<MemberVideoEntity> videos = memberRelationRepository.listRelationByFaceAndTemplate(face.getId(), Long.valueOf(productItem.getProductId()));
|
||||
yield videos.getFirst().getVideoId();
|
||||
|
||||
@@ -2,7 +2,16 @@
|
||||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="com.ycwl.basic.mapper.FaceDetectLogAiCamMapper">
|
||||
<insert id="add" useGeneratedKeys="true" keyProperty="id">
|
||||
insert into face_detect_log_ai_cam(scenic_id, device_id, face_sample_id, db_name, face_url, score, match_raw_result, create_time)
|
||||
values (#{scenicId}, #{deviceId}, #{faceSampleId}, #{dbName}, #{faceUrl}, #{score}, #{matchRawResult}, #{createTime})
|
||||
insert into face_detect_log_ai_cam(scenic_id, device_id, face_id, db_name, face_url, score, match_raw_result, create_time)
|
||||
values (#{scenicId}, #{deviceId}, #{faceId}, #{dbName}, #{faceUrl}, #{score}, #{matchRawResult}, #{createTime})
|
||||
</insert>
|
||||
|
||||
<select id="listByFaceId" resultType="com.ycwl.basic.model.pc.faceDetectLog.entity.FaceDetectLogAiCamEntity">
|
||||
select id, scenic_id as scenicId, device_id as deviceId, face_id as faceId,
|
||||
db_name as dbName, face_url as faceUrl, score, match_raw_result as matchRawResult,
|
||||
create_time as createTime
|
||||
from face_detect_log_ai_cam
|
||||
where face_id = #{faceId}
|
||||
order by create_time desc
|
||||
</select>
|
||||
</mapper>
|
||||
|
||||
@@ -486,4 +486,19 @@
|
||||
</choose>
|
||||
LIMIT 1
|
||||
</select>
|
||||
|
||||
<select id="listByFaceSampleIdsAndType" resultType="com.ycwl.basic.model.pc.source.entity.SourceEntity">
|
||||
SELECT * FROM source
|
||||
WHERE face_sample_id IN
|
||||
<foreach collection="faceSampleIds" item="item" open="(" separator="," close=")">
|
||||
#{item}
|
||||
</foreach>
|
||||
AND `type` = #{type}
|
||||
ORDER BY create_time DESC
|
||||
</select>
|
||||
|
||||
<delete id="deleteRelationsByFaceIdAndType">
|
||||
DELETE FROM member_source
|
||||
WHERE face_id = #{faceId} AND `type` = #{type}
|
||||
</delete>
|
||||
</mapper>
|
||||
|
||||
Reference in New Issue
Block a user