You've already forked FrameTour-BE
feat(basic): 添加模板片段更新状态缓存支持
- 在FaceStatusManager中新增按模板ID区分的人脸片段更新状态缓存键 - 更新TaskTaskServiceImpl以设置模板渲染状态 - 在任务回调逻辑中增加对模板渲染状态的更新操作 - 修改任务删除逻辑为更新状态加10的临时解决方案 - 移除旧有的切割任务状态更新逻辑,统一使用模板渲染状态管理
This commit is contained in:
348
src/main/java/com/ycwl/basic/biz/FaceStatusManager.java
Normal file
348
src/main/java/com/ycwl/basic/biz/FaceStatusManager.java
Normal file
@@ -0,0 +1,348 @@
|
||||
package com.ycwl.basic.biz;
|
||||
|
||||
import com.ycwl.basic.enums.FaceCutStatus;
|
||||
import com.ycwl.basic.enums.FacePieceUpdateStatus;
|
||||
import com.ycwl.basic.enums.TemplateRenderStatus;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* 人脸状态缓存管理器
|
||||
* 统一管理人脸相关的Redis缓存状态
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class FaceStatusManager {
|
||||
|
||||
/**
|
||||
* 人脸切片状态缓存Key
|
||||
* 格式: face:status:cut:{faceId}
|
||||
* 过期时间: 1天
|
||||
*/
|
||||
private static final String FACE_CUT_STATUS_KEY = "face:status:cut:%s";
|
||||
|
||||
/**
|
||||
* 人脸片段更新状态缓存Key(全局)
|
||||
* 格式: face:status:piece:update:{faceId}
|
||||
* 过期时间: 1天
|
||||
* 键存在 = 无新片段,键不存在 = 有新片段
|
||||
*/
|
||||
private static final String FACE_PIECE_UPDATE_KEY = "face:status:piece:update:%s";
|
||||
|
||||
/**
|
||||
* 人脸模板片段更新状态缓存Key(按模板)
|
||||
* 格式: face:status:piece:update:{faceId}:{templateId}
|
||||
* 过期时间: 1天
|
||||
* 键存在 = 无新片段,键不存在 = 有新片段
|
||||
*/
|
||||
private static final String FACE_TEMPLATE_PIECE_UPDATE_KEY = "face:status:piece:update:%s:%s";
|
||||
|
||||
/**
|
||||
* 人脸模板渲染状态缓存Key
|
||||
* 格式: face:status:render:{faceId}:{templateId}
|
||||
* 过期时间: 永久(或根据业务需要设置)
|
||||
*/
|
||||
private static final String FACE_TEMPLATE_RENDER_KEY = "face:status:render:%s:%s";
|
||||
|
||||
/**
|
||||
* 默认过期时间:1天
|
||||
*/
|
||||
private static final long DEFAULT_EXPIRE_SECONDS = 86400L;
|
||||
|
||||
@Autowired
|
||||
private RedisTemplate<String, String> redisTemplate;
|
||||
|
||||
// ==================== 切片状态相关方法 ====================
|
||||
|
||||
/**
|
||||
* 设置人脸切片状态
|
||||
* @param faceId 人脸ID
|
||||
* @param status 切片状态
|
||||
*/
|
||||
public void setFaceCutStatus(Long faceId, FaceCutStatus status) {
|
||||
if (faceId == null || status == null) {
|
||||
log.warn("设置切片状态参数为空: faceId={}, status={}", faceId, status);
|
||||
return;
|
||||
}
|
||||
String key = String.format(FACE_CUT_STATUS_KEY, faceId);
|
||||
redisTemplate.opsForValue().set(key, String.valueOf(status.getCode()), DEFAULT_EXPIRE_SECONDS, TimeUnit.SECONDS);
|
||||
log.debug("设置切片状态: faceId={}, status={}", faceId, status.getDescription());
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取人脸切片状态
|
||||
* @param faceId 人脸ID
|
||||
* @return 切片状态,缓存不存在时返回 COMPLETED(已完成)
|
||||
*/
|
||||
public FaceCutStatus getFaceCutStatus(Long faceId) {
|
||||
if (faceId == null) {
|
||||
log.warn("获取切片状态参数为空: faceId={}", faceId);
|
||||
return FaceCutStatus.COMPLETED;
|
||||
}
|
||||
String key = String.format(FACE_CUT_STATUS_KEY, faceId);
|
||||
String value = redisTemplate.opsForValue().get(key);
|
||||
if (value == null) {
|
||||
log.debug("切片状态缓存不存在,返回默认值COMPLETED: faceId={}", faceId);
|
||||
return FaceCutStatus.COMPLETED;
|
||||
}
|
||||
try {
|
||||
int code = Integer.parseInt(value);
|
||||
return FaceCutStatus.fromCodeOrDefault(code, FaceCutStatus.COMPLETED);
|
||||
} catch (NumberFormatException e) {
|
||||
log.error("切片状态值解析失败: faceId={}, value={}", faceId, value, e);
|
||||
return FaceCutStatus.COMPLETED;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除人脸切片状态缓存
|
||||
* @param faceId 人脸ID
|
||||
*/
|
||||
public void deleteFaceCutStatus(Long faceId) {
|
||||
if (faceId == null) {
|
||||
return;
|
||||
}
|
||||
String key = String.format(FACE_CUT_STATUS_KEY, faceId);
|
||||
redisTemplate.delete(key);
|
||||
log.debug("删除切片状态缓存: faceId={}", faceId);
|
||||
}
|
||||
|
||||
// ==================== 片段更新状态相关方法 ====================
|
||||
|
||||
/**
|
||||
* 标记无新片段(设置Redis键)
|
||||
* @param faceId 人脸ID
|
||||
* @param templateId 模板ID(可选,为null时标记全局状态)
|
||||
*/
|
||||
public void markNoNewPieces(Long faceId, Long templateId) {
|
||||
if (faceId == null) {
|
||||
log.warn("标记无新片段参数为空: faceId={}", faceId);
|
||||
return;
|
||||
}
|
||||
|
||||
String key;
|
||||
if (templateId == null) {
|
||||
// 全局标记:该人脸的所有模板都无新片段
|
||||
key = String.format(FACE_PIECE_UPDATE_KEY, faceId);
|
||||
log.debug("标记无新片段(全局): faceId={}", faceId);
|
||||
} else {
|
||||
// 模板级标记:该人脸在该模板下无新片段
|
||||
key = String.format(FACE_TEMPLATE_PIECE_UPDATE_KEY, faceId, templateId);
|
||||
log.debug("标记无新片段(模板): faceId={}, templateId={}", faceId, templateId);
|
||||
}
|
||||
|
||||
redisTemplate.opsForValue().set(key, "1", DEFAULT_EXPIRE_SECONDS, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
/**
|
||||
* 标记有新片段(删除Redis键)
|
||||
* @param faceId 人脸ID
|
||||
* @param templateId 模板ID(可选,为null时标记全局状态)
|
||||
*/
|
||||
public void markHasNewPieces(Long faceId, Long templateId) {
|
||||
if (faceId == null) {
|
||||
log.warn("标记有新片段参数为空: faceId={}", faceId);
|
||||
return;
|
||||
}
|
||||
|
||||
String key;
|
||||
if (templateId == null) {
|
||||
// 全局标记:该人脸有新片段
|
||||
key = String.format(FACE_PIECE_UPDATE_KEY, faceId);
|
||||
log.debug("标记有新片段(全局): faceId={}", faceId);
|
||||
} else {
|
||||
// 模板级标记:该人脸在该模板下有新片段
|
||||
key = String.format(FACE_TEMPLATE_PIECE_UPDATE_KEY, faceId, templateId);
|
||||
log.debug("标记有新片段(模板): faceId={}, templateId={}", faceId, templateId);
|
||||
}
|
||||
|
||||
redisTemplate.delete(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取人脸片段更新状态
|
||||
* @param faceId 人脸ID
|
||||
* @param templateId 模板ID(可选,为null时查询全局状态)
|
||||
* @return 片段更新状态,键存在=无新片段,键不存在=有新片段
|
||||
*/
|
||||
public FacePieceUpdateStatus getFacePieceUpdateStatus(Long faceId, Long templateId) {
|
||||
if (faceId == null) {
|
||||
log.warn("获取片段更新状态参数为空: faceId={}", faceId);
|
||||
return FacePieceUpdateStatus.HAS_NEW_PIECES;
|
||||
}
|
||||
|
||||
String key;
|
||||
if (templateId == null) {
|
||||
// 查询全局状态
|
||||
key = String.format(FACE_PIECE_UPDATE_KEY, faceId);
|
||||
} else {
|
||||
// 查询模板级状态
|
||||
key = String.format(FACE_TEMPLATE_PIECE_UPDATE_KEY, faceId, templateId);
|
||||
}
|
||||
|
||||
Boolean exists = redisTemplate.hasKey(key);
|
||||
FacePieceUpdateStatus status = FacePieceUpdateStatus.fromKeyExists(Boolean.TRUE.equals(exists));
|
||||
|
||||
if (templateId == null) {
|
||||
log.debug("获取片段更新状态(全局): faceId={}, status={}", faceId, status.getDescription());
|
||||
} else {
|
||||
log.debug("获取片段更新状态(模板): faceId={}, templateId={}, status={}",
|
||||
faceId, templateId, status.getDescription());
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取人脸片段更新状态 - 全局版本
|
||||
* @param faceId 人脸ID
|
||||
* @return 片段更新状态,键存在=无新片段,键不存在=有新片段
|
||||
*/
|
||||
public FacePieceUpdateStatus getFacePieceUpdateStatus(Long faceId) {
|
||||
return getFacePieceUpdateStatus(faceId, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否有新片段
|
||||
* @param faceId 人脸ID
|
||||
* @param templateId 模板ID(可选,为null时查询全局状态)
|
||||
* @return true=有新片段,false=无新片段;如果templateId为null则默认返回true(有新片段)
|
||||
*/
|
||||
public boolean hasNewPieces(Long faceId, Long templateId) {
|
||||
if (templateId == null) {
|
||||
// 如果没有指定templateId,默认认为有新片段
|
||||
log.debug("未指定templateId,默认返回有新片段: faceId={}", faceId);
|
||||
return true;
|
||||
}
|
||||
return getFacePieceUpdateStatus(faceId, templateId).hasNewPieces();
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否有新片段 - 全局版本
|
||||
* @param faceId 人脸ID
|
||||
* @return true=有新片段,false=无新片段
|
||||
*/
|
||||
public boolean hasNewPieces(Long faceId) {
|
||||
return getFacePieceUpdateStatus(faceId, null).hasNewPieces();
|
||||
}
|
||||
|
||||
// ==================== 模板渲染状态相关方法 ====================
|
||||
|
||||
/**
|
||||
* 设置人脸模板渲染状态
|
||||
* @param faceId 人脸ID
|
||||
* @param templateId 模板ID
|
||||
* @param status 渲染状态
|
||||
*/
|
||||
public void setTemplateRenderStatus(Long faceId, Long templateId, TemplateRenderStatus status) {
|
||||
if (faceId == null || templateId == null || status == null) {
|
||||
log.warn("设置模板渲染状态参数为空: faceId={}, templateId={}, status={}", faceId, templateId, status);
|
||||
return;
|
||||
}
|
||||
String key = String.format(FACE_TEMPLATE_RENDER_KEY, faceId, templateId);
|
||||
// 渲染状态不设置过期时间,永久保存
|
||||
redisTemplate.opsForValue().set(key, String.valueOf(status.getCode()));
|
||||
log.debug("设置模板渲染状态: faceId={}, templateId={}, status={}", faceId, templateId, status.getDescription());
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置人脸模板渲染状态(带过期时间)
|
||||
* @param faceId 人脸ID
|
||||
* @param templateId 模板ID
|
||||
* @param status 渲染状态
|
||||
* @param expireSeconds 过期时间(秒)
|
||||
*/
|
||||
public void setTemplateRenderStatus(Long faceId, Long templateId, TemplateRenderStatus status, long expireSeconds) {
|
||||
if (faceId == null || templateId == null || status == null) {
|
||||
log.warn("设置模板渲染状态参数为空: faceId={}, templateId={}, status={}", faceId, templateId, status);
|
||||
return;
|
||||
}
|
||||
String key = String.format(FACE_TEMPLATE_RENDER_KEY, faceId, templateId);
|
||||
redisTemplate.opsForValue().set(key, String.valueOf(status.getCode()), expireSeconds, TimeUnit.SECONDS);
|
||||
log.debug("设置模板渲染状态(带过期): faceId={}, templateId={}, status={}, expireSeconds={}",
|
||||
faceId, templateId, status.getDescription(), expireSeconds);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取人脸模板渲染状态
|
||||
* @param faceId 人脸ID
|
||||
* @param templateId 模板ID
|
||||
* @return 渲染状态,缓存不存在时返回 null
|
||||
*/
|
||||
public TemplateRenderStatus getTemplateRenderStatus(Long faceId, Long templateId) {
|
||||
if (faceId == null || templateId == null) {
|
||||
log.warn("获取模板渲染状态参数为空: faceId={}, templateId={}", faceId, templateId);
|
||||
return null;
|
||||
}
|
||||
String key = String.format(FACE_TEMPLATE_RENDER_KEY, faceId, templateId);
|
||||
String value = redisTemplate.opsForValue().get(key);
|
||||
if (value == null) {
|
||||
log.debug("模板渲染状态缓存不存在: faceId={}, templateId={}", faceId, templateId);
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
int code = Integer.parseInt(value);
|
||||
return TemplateRenderStatus.fromCode(code);
|
||||
} catch (Exception e) {
|
||||
log.error("模板渲染状态值解析失败: faceId={}, templateId={}, value={}", faceId, templateId, value, e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断模板是否已渲染完成
|
||||
* @param faceId 人脸ID
|
||||
* @param templateId 模板ID
|
||||
* @return true=已渲染,false=未渲染或正在渲染
|
||||
*/
|
||||
public boolean isTemplateRendered(Long faceId, Long templateId) {
|
||||
TemplateRenderStatus status = getTemplateRenderStatus(faceId, templateId);
|
||||
return status != null && status.isRendered();
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断模板是否正在渲染
|
||||
* @param faceId 人脸ID
|
||||
* @param templateId 模板ID
|
||||
* @return true=正在渲染,false=未渲染或已完成
|
||||
*/
|
||||
public boolean isTemplateRendering(Long faceId, Long templateId) {
|
||||
TemplateRenderStatus status = getTemplateRenderStatus(faceId, templateId);
|
||||
return status != null && status.isRendering();
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除人脸模板渲染状态缓存
|
||||
* @param faceId 人脸ID
|
||||
* @param templateId 模板ID
|
||||
*/
|
||||
public void deleteTemplateRenderStatus(Long faceId, Long templateId) {
|
||||
if (faceId == null || templateId == null) {
|
||||
return;
|
||||
}
|
||||
String key = String.format(FACE_TEMPLATE_RENDER_KEY, faceId, templateId);
|
||||
redisTemplate.delete(key);
|
||||
log.debug("删除模板渲染状态缓存: faceId={}, templateId={}", faceId, templateId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除人脸的所有模板渲染状态(使用模式匹配)
|
||||
* 注意:此操作可能影响性能,谨慎使用
|
||||
* @param faceId 人脸ID
|
||||
*/
|
||||
public void deleteAllTemplateRenderStatus(Long faceId) {
|
||||
if (faceId == null) {
|
||||
return;
|
||||
}
|
||||
String pattern = String.format(FACE_TEMPLATE_RENDER_KEY, faceId, "*");
|
||||
var keys = redisTemplate.keys(pattern);
|
||||
if (keys != null && !keys.isEmpty()) {
|
||||
redisTemplate.delete(keys);
|
||||
log.debug("批量删除模板渲染状态缓存: faceId={}, count={}", faceId, keys.size());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,192 +0,0 @@
|
||||
package com.ycwl.basic.biz;
|
||||
|
||||
import com.ycwl.basic.mapper.FaceMapper;
|
||||
import com.ycwl.basic.mapper.TaskMapper;
|
||||
import com.ycwl.basic.mapper.VideoMapper;
|
||||
import com.ycwl.basic.model.mobile.goods.VideoTaskStatusVO;
|
||||
import com.ycwl.basic.model.pc.face.entity.FaceEntity;
|
||||
import com.ycwl.basic.model.pc.face.resp.FaceRespVO;
|
||||
import com.ycwl.basic.model.pc.task.req.TaskReqQuery;
|
||||
import com.ycwl.basic.model.pc.task.resp.TaskRespVO;
|
||||
import com.ycwl.basic.model.pc.template.resp.TemplateRespVO;
|
||||
import com.ycwl.basic.model.pc.video.entity.VideoEntity;
|
||||
import com.ycwl.basic.repository.FaceRepository;
|
||||
import com.ycwl.basic.repository.TemplateRepository;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@Component
|
||||
public class TaskStatusBiz {
|
||||
public static final String TASK_STATUS_USER_CACHE_KEY = "task:status:user:%s:face:%s";
|
||||
public static final String TASK_STATUS_FACE_CACHE_KEY = "task:status:face:%s";
|
||||
public static final String TASK_STATUS_FACE_CACHE_KEY_CUT = "task:status:face:%s:cut";
|
||||
public static final String TASK_STATUS_FACE_CACHE_KEY_TEMPLATE = "task:status:face:%s:tpl:%s";
|
||||
|
||||
@Autowired
|
||||
private RedisTemplate<String, String> redisTemplate;
|
||||
@Autowired
|
||||
private FaceRepository faceRepository;
|
||||
@Autowired
|
||||
private TemplateRepository templateRepository;
|
||||
@Autowired
|
||||
private FaceMapper faceMapper;
|
||||
@Autowired
|
||||
private TaskMapper taskMapper;
|
||||
@Autowired
|
||||
private VideoMapper videoMapper;
|
||||
@Autowired
|
||||
private TemplateBiz templateBiz;
|
||||
|
||||
public boolean getUserHaveFace(Long userId, Long faceId) {
|
||||
if (userId == null || faceId == null) {
|
||||
return false;
|
||||
}
|
||||
if (redisTemplate.hasKey(String.format(TASK_STATUS_USER_CACHE_KEY, userId, faceId))) {
|
||||
return true;
|
||||
}
|
||||
FaceEntity face = faceRepository.getFace(faceId);
|
||||
if (face == null) {
|
||||
return false;
|
||||
}
|
||||
if (face.getMemberId().equals(userId)) {
|
||||
redisTemplate.opsForValue().set(String.format(TASK_STATUS_USER_CACHE_KEY, userId, faceId), "1", 3600, TimeUnit.SECONDS);
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public void setFaceCutStatus(Long faceId, int status) {
|
||||
redisTemplate.opsForValue().set(String.format(TASK_STATUS_FACE_CACHE_KEY_CUT, faceId), String.valueOf(status), 3600, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
public void setFaceTemplateStatus(Long faceId, Long templateId, Long videoId) {
|
||||
redisTemplate.opsForValue().set(String.format(TASK_STATUS_FACE_CACHE_KEY_TEMPLATE, faceId, templateId), String.valueOf(videoId), 3600, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
public VideoTaskStatusVO getScenicUserStatus(Long scenicId, Long userId) {
|
||||
FaceRespVO lastFace = faceMapper.findLastFaceByScenicAndUserId(scenicId, userId);
|
||||
VideoTaskStatusVO response = new VideoTaskStatusVO();
|
||||
if (lastFace == null) {
|
||||
response.setStatus(-1);
|
||||
return response;
|
||||
}
|
||||
return getFaceStatus(lastFace.getId());
|
||||
}
|
||||
|
||||
public VideoTaskStatusVO getFaceStatus(Long faceId) {
|
||||
FaceEntity face = faceRepository.getFace(faceId);
|
||||
VideoTaskStatusVO response = new VideoTaskStatusVO();
|
||||
if (face == null) {
|
||||
response.setStatus(-1);
|
||||
return response;
|
||||
}
|
||||
response.setScenicId(face.getScenicId());
|
||||
response.setFaceId(faceId);
|
||||
List<TemplateRespVO> templateList = templateRepository.getTemplateListByScenicId(face.getScenicId());
|
||||
response.setMaxCount(templateList.size());
|
||||
int alreadyFinished = 0;
|
||||
for (TemplateRespVO template : templateList) {
|
||||
response.setTemplateId(template.getId());
|
||||
long videoId = getFaceTemplateVideoId(faceId, template.getId());
|
||||
if (videoId <= 0) {
|
||||
response.setStatus(2);
|
||||
} else {
|
||||
response.setVideoId(videoId);
|
||||
alreadyFinished++;
|
||||
}
|
||||
}
|
||||
response.setCount(alreadyFinished);
|
||||
if (alreadyFinished == 0) {
|
||||
response.setStatus(0);
|
||||
} else {
|
||||
response.setStatus(1);
|
||||
}
|
||||
if (alreadyFinished == 0) {
|
||||
int faceCutStatus = getFaceCutStatus(faceId);
|
||||
if (faceCutStatus != 1) {
|
||||
// 正在切片
|
||||
if (templateBiz.determineTemplateCanGenerate(templateList.getFirst().getId(), faceId, false)) {
|
||||
response.setStatus(2);
|
||||
} else {
|
||||
response.setStatus(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
public VideoTaskStatusVO getFaceTemplateStatus(Long faceId, Long templateId) {
|
||||
FaceEntity face = faceRepository.getFace(faceId);
|
||||
VideoTaskStatusVO response = new VideoTaskStatusVO();
|
||||
if (face == null) {
|
||||
response.setStatus(-1);
|
||||
return response;
|
||||
}
|
||||
response.setScenicId(face.getScenicId());
|
||||
response.setFaceId(faceId);
|
||||
response.setTemplateId(templateId);
|
||||
long videoId = getFaceTemplateVideoId(faceId, templateId);
|
||||
if (videoId < 0) {
|
||||
int faceCutStatus = getFaceCutStatus(faceId);
|
||||
if (faceCutStatus != 1) {
|
||||
// 正在切片
|
||||
response.setStatus(2);
|
||||
return response;
|
||||
}
|
||||
} else if (videoId == 0) {
|
||||
response.setStatus(2);
|
||||
} else {
|
||||
response.setVideoId(videoId);
|
||||
response.setStatus(1);
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
public int getFaceCutStatus(Long faceId) {
|
||||
if (redisTemplate.hasKey(String.format(TASK_STATUS_FACE_CACHE_KEY_CUT, faceId))) {
|
||||
String status = redisTemplate.opsForValue().get(String.format(TASK_STATUS_FACE_CACHE_KEY_CUT, faceId));
|
||||
if (status != null) {
|
||||
return Integer.parseInt(status);
|
||||
}
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
public long getFaceTemplateVideoId(Long faceId, Long templateId) {
|
||||
if (redisTemplate.hasKey(String.format(TASK_STATUS_FACE_CACHE_KEY_TEMPLATE, faceId, templateId))) {
|
||||
String status = redisTemplate.opsForValue().get(String.format(TASK_STATUS_FACE_CACHE_KEY_TEMPLATE, faceId, templateId));
|
||||
if (status != null) {
|
||||
return Long.parseLong(status);
|
||||
}
|
||||
}
|
||||
TaskReqQuery taskReqQuery = new TaskReqQuery();
|
||||
taskReqQuery.setFaceId(faceId);
|
||||
taskReqQuery.setTemplateId(templateId);
|
||||
List<TaskRespVO> list = taskMapper.list(taskReqQuery);
|
||||
Optional<TaskRespVO> min = list.stream().min(Comparator.comparing(TaskRespVO::getCreateTime));
|
||||
if (min.isPresent()) {
|
||||
TaskRespVO task = min.get();
|
||||
long taskStatus = 0;
|
||||
if (task.getStatus() == 1) {
|
||||
// 已完成
|
||||
VideoEntity video = videoMapper.findByTaskId(task.getId());
|
||||
if (video != null) {
|
||||
taskStatus = video.getId();
|
||||
}
|
||||
}
|
||||
setFaceTemplateStatus(faceId, templateId, taskStatus);
|
||||
} else {
|
||||
// 从来没生成过
|
||||
setFaceTemplateStatus(faceId, templateId, -1L);
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@@ -104,7 +104,6 @@ public class AppGoodsController {
|
||||
// 查询用户当前景区的具体模版视频合成任务状态 1 合成中 2 合成成功
|
||||
@GetMapping("/task/face/{faceId}/template/{templateId}")
|
||||
public ApiResponse<VideoTaskStatusVO> getTemplateTaskStatus(@PathVariable("faceId") Long faceId, @PathVariable("templateId") Long templateId) {
|
||||
JwtInfo worker = JwtTokenUtil.getWorker();
|
||||
return ApiResponse.success(goodsService.getTaskStatusByTemplateId(faceId, templateId));
|
||||
}
|
||||
|
||||
|
||||
65
src/main/java/com/ycwl/basic/enums/FaceCutStatus.java
Normal file
65
src/main/java/com/ycwl/basic/enums/FaceCutStatus.java
Normal file
@@ -0,0 +1,65 @@
|
||||
package com.ycwl.basic.enums;
|
||||
|
||||
/**
|
||||
* 人脸视频切片状态枚举
|
||||
*/
|
||||
public enum FaceCutStatus {
|
||||
|
||||
/**
|
||||
* 正在切片中
|
||||
*/
|
||||
CUTTING(0, "正在切片中"),
|
||||
|
||||
/**
|
||||
* 切片已完成
|
||||
*/
|
||||
COMPLETED(1, "切片已完成"),
|
||||
|
||||
/**
|
||||
* 等待用户选择模板
|
||||
*/
|
||||
WAITING_USER_SELECT(2, "等待用户选择模板");
|
||||
|
||||
private final int code;
|
||||
private final String description;
|
||||
|
||||
FaceCutStatus(int code, String description) {
|
||||
this.code = code;
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
public int getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据code获取枚举
|
||||
*/
|
||||
public static FaceCutStatus fromCode(int code) {
|
||||
for (FaceCutStatus status : values()) {
|
||||
if (status.code == code) {
|
||||
return status;
|
||||
}
|
||||
}
|
||||
throw new IllegalArgumentException("Unknown FaceCutStatus code: " + code);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据code获取枚举,如果不存在则返回默认值
|
||||
* @param code 状态码
|
||||
* @param defaultStatus 默认状态
|
||||
* @return 枚举值
|
||||
*/
|
||||
public static FaceCutStatus fromCodeOrDefault(int code, FaceCutStatus defaultStatus) {
|
||||
for (FaceCutStatus status : values()) {
|
||||
if (status.code == code) {
|
||||
return status;
|
||||
}
|
||||
}
|
||||
return defaultStatus;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
package com.ycwl.basic.enums;
|
||||
|
||||
/**
|
||||
* 人脸片段更新状态枚举
|
||||
* 用于标记人脸对应的视频片段是否有新增更新
|
||||
*/
|
||||
public enum FacePieceUpdateStatus {
|
||||
|
||||
/**
|
||||
* 有新片段
|
||||
* Redis键不存在时的默认状态,代表有新的视频片段产生
|
||||
*/
|
||||
HAS_NEW_PIECES(0, "有新片段"),
|
||||
|
||||
/**
|
||||
* 无新片段
|
||||
* Redis键存在时的状态,代表当前没有新的视频片段
|
||||
*/
|
||||
NO_NEW_PIECES(1, "无新片段");
|
||||
|
||||
private final int code;
|
||||
private final String description;
|
||||
|
||||
FacePieceUpdateStatus(int code, String description) {
|
||||
this.code = code;
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
public int getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据code获取枚举
|
||||
*/
|
||||
public static FacePieceUpdateStatus fromCode(int code) {
|
||||
for (FacePieceUpdateStatus status : values()) {
|
||||
if (status.code == code) {
|
||||
return status;
|
||||
}
|
||||
}
|
||||
throw new IllegalArgumentException("Unknown FacePieceUpdateStatus code: " + code);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据Redis键是否存在判断状态
|
||||
* @param keyExists Redis键是否存在
|
||||
* @return 键存在返回NO_NEW_PIECES,键不存在返回HAS_NEW_PIECES
|
||||
*/
|
||||
public static FacePieceUpdateStatus fromKeyExists(boolean keyExists) {
|
||||
return keyExists ? NO_NEW_PIECES : HAS_NEW_PIECES;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否有新片段
|
||||
*/
|
||||
public boolean hasNewPieces() {
|
||||
return this == HAS_NEW_PIECES;
|
||||
}
|
||||
}
|
||||
75
src/main/java/com/ycwl/basic/enums/TemplateRenderStatus.java
Normal file
75
src/main/java/com/ycwl/basic/enums/TemplateRenderStatus.java
Normal file
@@ -0,0 +1,75 @@
|
||||
package com.ycwl.basic.enums;
|
||||
|
||||
/**
|
||||
* 人脸对应模板渲染状态枚举
|
||||
*/
|
||||
public enum TemplateRenderStatus {
|
||||
|
||||
NONE(0, "没有渲染"),
|
||||
/**
|
||||
* 正在渲染中
|
||||
*/
|
||||
RENDERING(1, "正在渲染中"),
|
||||
|
||||
/**
|
||||
* 已渲染完成
|
||||
*/
|
||||
RENDERED(2, "已渲染完成");
|
||||
|
||||
private final int code;
|
||||
private final String description;
|
||||
|
||||
TemplateRenderStatus(int code, String description) {
|
||||
this.code = code;
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
public int getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据code获取枚举
|
||||
*/
|
||||
public static TemplateRenderStatus fromCode(int code) {
|
||||
for (TemplateRenderStatus status : values()) {
|
||||
if (status.code == code) {
|
||||
return status;
|
||||
}
|
||||
}
|
||||
throw new IllegalArgumentException("Unknown TemplateRenderStatus code: " + code);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据code获取枚举,如果不存在则返回默认值
|
||||
* @param code 状态码
|
||||
* @param defaultStatus 默认状态
|
||||
* @return 枚举值
|
||||
*/
|
||||
public static TemplateRenderStatus fromCodeOrDefault(int code, TemplateRenderStatus defaultStatus) {
|
||||
for (TemplateRenderStatus status : values()) {
|
||||
if (status.code == code) {
|
||||
return status;
|
||||
}
|
||||
}
|
||||
return defaultStatus;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否已完成渲染
|
||||
*/
|
||||
public boolean isRendered() {
|
||||
return this == RENDERED;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否正在渲染
|
||||
*/
|
||||
public boolean isRendering() {
|
||||
return this == RENDERING;
|
||||
}
|
||||
}
|
||||
159
src/main/java/com/ycwl/basic/enums/VideoTaskStatus.java
Normal file
159
src/main/java/com/ycwl/basic/enums/VideoTaskStatus.java
Normal file
@@ -0,0 +1,159 @@
|
||||
package com.ycwl.basic.enums;
|
||||
|
||||
/**
|
||||
* 视频任务状态枚举
|
||||
* 用于前端展示任务状态
|
||||
*/
|
||||
public enum VideoTaskStatus {
|
||||
|
||||
/**
|
||||
* 无效人脸(景区级别)
|
||||
*/
|
||||
INVALID_FACE_SCENIC(-2, "尚未录入有效人脸"),
|
||||
|
||||
/**
|
||||
* 无效人脸(人脸级别)
|
||||
*/
|
||||
INVALID_FACE(-1, "尚未录入有效人脸"),
|
||||
|
||||
/**
|
||||
* 待制作
|
||||
* 人脸已录入,但尚未开始合成视频
|
||||
*/
|
||||
PENDING(0, "专属视频待制作"),
|
||||
|
||||
/**
|
||||
* 合成成功
|
||||
* 已为用户合成视频
|
||||
*/
|
||||
SUCCESS(1, "AI已为您合成视频"),
|
||||
|
||||
/**
|
||||
* 合成中
|
||||
* 正在合成专属视频
|
||||
*/
|
||||
PROCESSING(2, "专属视频合成中"),
|
||||
|
||||
/**
|
||||
* 合成失败
|
||||
*/
|
||||
FAILED(3, "视频合成失败"),
|
||||
|
||||
/**
|
||||
* 切片中
|
||||
* 正在检索新的视频片段
|
||||
*/
|
||||
CUTTING(4, "正在检索新的视频片段");
|
||||
|
||||
private final int code;
|
||||
private final String description;
|
||||
|
||||
VideoTaskStatus(int code, String description) {
|
||||
this.code = code;
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
public int getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据code获取枚举
|
||||
*/
|
||||
public static VideoTaskStatus fromCode(int code) {
|
||||
for (VideoTaskStatus status : values()) {
|
||||
if (status.code == code) {
|
||||
return status;
|
||||
}
|
||||
}
|
||||
throw new IllegalArgumentException("Unknown VideoTaskStatus code: " + code);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据code获取枚举,如果不存在则返回默认值
|
||||
* @param code 状态码
|
||||
* @param defaultStatus 默认状态
|
||||
* @return 枚举值
|
||||
*/
|
||||
public static VideoTaskStatus fromCodeOrDefault(int code, VideoTaskStatus defaultStatus) {
|
||||
for (VideoTaskStatus status : values()) {
|
||||
if (status.code == code) {
|
||||
return status;
|
||||
}
|
||||
}
|
||||
return defaultStatus;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取前端展示文案
|
||||
* @param count 已合成视频数量(仅SUCCESS状态使用)
|
||||
* @return 展示文案
|
||||
*/
|
||||
public String getDisplayText(long count) {
|
||||
if (this == SUCCESS && count > 0) {
|
||||
return "AI已为您合成" + count + "个视频";
|
||||
}
|
||||
return description;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据业务逻辑判断最终展示状态
|
||||
* @param taskStatus 任务状态码
|
||||
* @param cutStatus 切片状态码(来自FaceCutStatus)
|
||||
* @param count 已合成视频数量
|
||||
* @return 最终展示的状态
|
||||
*/
|
||||
public static VideoTaskStatus resolveDisplayStatus(int taskStatus, int cutStatus, long count) {
|
||||
VideoTaskStatus status = fromCodeOrDefault(taskStatus, PENDING);
|
||||
|
||||
// 优先级1: 无效人脸状态
|
||||
if (status == INVALID_FACE_SCENIC || status == INVALID_FACE) {
|
||||
return status;
|
||||
}
|
||||
|
||||
// 优先级2: 切片状态优先(当任务状态为待制作且切片状态为正在切片时)
|
||||
if (status == PENDING && cutStatus == 0) {
|
||||
return CUTTING;
|
||||
}
|
||||
|
||||
// 优先级3: 返回任务状态
|
||||
return status;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取最终展示文案
|
||||
* @param taskStatus 任务状态码
|
||||
* @param cutStatus 切片状态码
|
||||
* @param count 已合成视频数量
|
||||
* @return 展示文案
|
||||
*/
|
||||
public static String getDisplayText(int taskStatus, int cutStatus, long count) {
|
||||
VideoTaskStatus status = resolveDisplayStatus(taskStatus, cutStatus, count);
|
||||
return status.getDisplayText(count);
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否为成功状态
|
||||
*/
|
||||
public boolean isSuccess() {
|
||||
return this == SUCCESS;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否为处理中状态
|
||||
*/
|
||||
public boolean isProcessing() {
|
||||
return this == PROCESSING || this == CUTTING;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否为失败状态
|
||||
*/
|
||||
public boolean isFailed() {
|
||||
return this == FAILED || this == INVALID_FACE || this == INVALID_FACE_SCENIC;
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.ycwl.basic.face.pipeline.stages;
|
||||
|
||||
import com.ycwl.basic.biz.TaskStatusBiz;
|
||||
import com.ycwl.basic.biz.FaceStatusManager;
|
||||
import com.ycwl.basic.enums.FaceCutStatus;
|
||||
import com.ycwl.basic.face.pipeline.core.FaceMatchingContext;
|
||||
import com.ycwl.basic.pipeline.annotation.StageConfig;
|
||||
import com.ycwl.basic.pipeline.core.AbstractPipelineStage;
|
||||
@@ -37,7 +38,7 @@ public class CreateTaskStage extends AbstractPipelineStage<FaceMatchingContext>
|
||||
private TaskService taskService;
|
||||
|
||||
@Autowired
|
||||
private TaskStatusBiz taskStatusBiz;
|
||||
private FaceStatusManager faceStatusManager;
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
@@ -59,7 +60,7 @@ public class CreateTaskStage extends AbstractPipelineStage<FaceMatchingContext>
|
||||
return StageResult.success("自动创建任务成功");
|
||||
} else {
|
||||
// 配置为等待用户选择
|
||||
taskStatusBiz.setFaceCutStatus(faceId, 2);
|
||||
faceStatusManager.setFaceCutStatus(faceId, FaceCutStatus.WAITING_USER_SELECT);
|
||||
log.debug("景区配置 face_select_first=true,跳过自动创建任务: faceId={}", faceId);
|
||||
return StageResult.skipped("等待用户手动选择");
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.ycwl.basic.face.pipeline.stages;
|
||||
|
||||
import com.ycwl.basic.biz.TaskStatusBiz;
|
||||
import com.ycwl.basic.biz.FaceStatusManager;
|
||||
import com.ycwl.basic.enums.FaceCutStatus;
|
||||
import com.ycwl.basic.face.pipeline.core.FaceMatchingContext;
|
||||
import com.ycwl.basic.pipeline.annotation.StageConfig;
|
||||
import com.ycwl.basic.pipeline.core.AbstractPipelineStage;
|
||||
@@ -28,7 +29,7 @@ import org.springframework.stereotype.Component;
|
||||
public class SetTaskStatusStage extends AbstractPipelineStage<FaceMatchingContext> {
|
||||
|
||||
@Autowired
|
||||
private TaskStatusBiz taskStatusBiz;
|
||||
private FaceStatusManager faceStatusManager;
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
@@ -56,7 +57,7 @@ public class SetTaskStatusStage extends AbstractPipelineStage<FaceMatchingContex
|
||||
}
|
||||
|
||||
try {
|
||||
taskStatusBiz.setFaceCutStatus(faceId, 0);
|
||||
faceStatusManager.setFaceCutStatus(faceId, FaceCutStatus.CUTTING);
|
||||
log.debug("设置新用户任务状态: faceId={}, status=0", faceId);
|
||||
return StageResult.success("任务状态已设置");
|
||||
|
||||
|
||||
@@ -2,6 +2,11 @@ package com.ycwl.basic.service.mobile.impl;
|
||||
|
||||
import cn.hutool.core.date.DateUtil;
|
||||
import cn.hutool.http.HttpUtil;
|
||||
import com.ycwl.basic.biz.FaceStatusManager;
|
||||
import com.ycwl.basic.enums.FaceCutStatus;
|
||||
import com.ycwl.basic.enums.FacePieceUpdateStatus;
|
||||
import com.ycwl.basic.enums.TemplateRenderStatus;
|
||||
import com.ycwl.basic.enums.VideoTaskStatus;
|
||||
import com.ycwl.basic.integration.common.manager.ScenicConfigManager;
|
||||
import com.ycwl.basic.integration.scenic.dto.scenic.ScenicV2DTO;
|
||||
import com.ycwl.basic.model.pc.source.entity.MemberSourceEntity;
|
||||
@@ -10,7 +15,6 @@ import com.ycwl.basic.repository.OrderRepository;
|
||||
import com.ycwl.basic.utils.JacksonUtil;
|
||||
import com.ycwl.basic.biz.CouponBiz;
|
||||
import com.ycwl.basic.biz.OrderBiz;
|
||||
import com.ycwl.basic.biz.TaskStatusBiz;
|
||||
import com.ycwl.basic.constant.StorageConstant;
|
||||
import com.ycwl.basic.image.watermark.ImageWatermarkFactory;
|
||||
import com.ycwl.basic.image.watermark.entity.WatermarkInfo;
|
||||
@@ -31,7 +35,6 @@ import com.ycwl.basic.model.pc.source.entity.SourceEntity;
|
||||
import com.ycwl.basic.model.pc.source.entity.SourceWatermarkEntity;
|
||||
import com.ycwl.basic.model.pc.source.req.SourceReqQuery;
|
||||
import com.ycwl.basic.model.pc.source.resp.SourceRespVO;
|
||||
import com.ycwl.basic.model.pc.task.entity.TaskEntity;
|
||||
import com.ycwl.basic.model.pc.template.resp.TemplateRespVO;
|
||||
import com.ycwl.basic.model.pc.video.entity.MemberVideoEntity;
|
||||
import com.ycwl.basic.model.pc.video.entity.VideoEntity;
|
||||
@@ -65,6 +68,7 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
@@ -93,8 +97,6 @@ public class GoodsServiceImpl implements GoodsService {
|
||||
@Autowired
|
||||
private FaceRepository faceRepository;
|
||||
@Autowired
|
||||
private TaskStatusBiz taskStatusBiz;
|
||||
@Autowired
|
||||
private DeviceRepository deviceRepository;
|
||||
@Autowired
|
||||
private CouponBiz couponBiz;
|
||||
@@ -106,6 +108,8 @@ public class GoodsServiceImpl implements GoodsService {
|
||||
private PrinterMapper printerMapper;
|
||||
@Autowired
|
||||
private OrderRepository orderRepository;
|
||||
@Autowired
|
||||
private FaceStatusManager faceStatusManager;
|
||||
|
||||
@Override
|
||||
public List<GoodsDetailVO> sourceGoodsList(GoodsReqQuery query) {
|
||||
@@ -258,83 +262,113 @@ public class GoodsServiceImpl implements GoodsService {
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询用户当前景区的视频合成任务状态
|
||||
* 查询用户人脸对应的视频合成任务状态
|
||||
*
|
||||
* @param faceId 景区id
|
||||
* @return 0没有任务 1 合成中 2 合成成功
|
||||
* <p>业务流程:
|
||||
* <ol>
|
||||
* <li>验证人脸是否存在</li>
|
||||
* <li>检查视频切片状态(CUTTING → 返回处理中)</li>
|
||||
* <li>遍历景区模板,检查渲染状态(RENDERING → 返回处理中)</li>
|
||||
* <li>统计已渲染完成的模板数量</li>
|
||||
* <li>根据切片状态返回最终结果:
|
||||
* <ul>
|
||||
* <li>WAITING_USER_SELECT → 处理中(等待用户选择模板)</li>
|
||||
* <li>COMPLETED → 成功(查询已完成的视频信息)</li>
|
||||
* </ul>
|
||||
* </li>
|
||||
* </ol>
|
||||
*
|
||||
* @param faceId 人脸ID
|
||||
* @return 视频任务状态VO,包含状态码、已完成数量、视频信息等
|
||||
* <ul>
|
||||
* <li>status = -1: 人脸不存在</li>
|
||||
* <li>status = 2: 处理中(切片中/渲染中/等待选择)</li>
|
||||
* <li>status = 1: 成功(已有完成的视频)</li>
|
||||
* </ul>
|
||||
*/
|
||||
@Override
|
||||
public VideoTaskStatusVO getTaskStatusByFaceId(Long faceId) {
|
||||
VideoTaskStatusVO response = new VideoTaskStatusVO();
|
||||
|
||||
// ==================== 第一步:验证人脸是否存在 ====================
|
||||
FaceEntity face = faceRepository.getFace(faceId);
|
||||
if (face == null) {
|
||||
response.setStatus(-1);
|
||||
// 人脸不存在,返回无效人脸状态
|
||||
response.setStatus(VideoTaskStatus.INVALID_FACE.getCode());
|
||||
return response;
|
||||
}
|
||||
Long userId = face.getMemberId();
|
||||
|
||||
// 设置基本信息
|
||||
response.setFaceId(faceId);
|
||||
response.setScenicId(face.getScenicId());
|
||||
int faceCutStatus = taskStatusBiz.getFaceCutStatus(faceId);
|
||||
response.setCutStatus(faceCutStatus);
|
||||
if (faceCutStatus == 0) {
|
||||
// 切视频中,也显示正在处理
|
||||
response.setStatus(2);
|
||||
return response;
|
||||
}
|
||||
List<MemberVideoEntity> taskList = videoMapper.listRelationByFace(faceId);
|
||||
if (faceCutStatus != 1 && taskList.isEmpty()) {
|
||||
// 视频切成了能够获取视频的状态,但是没有任务,还是显示正在处理
|
||||
response.setStatus(0);
|
||||
return response;
|
||||
}
|
||||
if (taskList.isEmpty()) {
|
||||
response.setStatus(0);
|
||||
|
||||
// ==================== 第二步:检查视频切片状态 ====================
|
||||
// 从缓存中获取切片状态(CUTTING/COMPLETED/WAITING_USER_SELECT)
|
||||
FaceCutStatus status = faceStatusManager.getFaceCutStatus(faceId);
|
||||
response.setCutStatus(status.getCode());
|
||||
|
||||
if (status == FaceCutStatus.CUTTING) {
|
||||
// 视频片段正在切割中,用户需要等待切片完成
|
||||
// 前端展示:「正在检索新的视频片段」
|
||||
response.setStatus(VideoTaskStatus.PROCESSING.getCode());
|
||||
return response;
|
||||
}
|
||||
|
||||
// ==================== 第三步:检查模板渲染状态 ====================
|
||||
// 获取该景区的所有视频模板
|
||||
List<TemplateRespVO> templateList = templateRepository.getTemplateListByScenicId(response.getScenicId());
|
||||
List<Long> templateIds = templateList.stream().map(TemplateRespVO::getId).collect(Collectors.toList());
|
||||
AtomicInteger count = new AtomicInteger();
|
||||
|
||||
// 遍历所有模板,检查每个模板的渲染状态
|
||||
for (TemplateRespVO template : templateList) {
|
||||
// 从缓存中获取该人脸在该模板下的渲染状态
|
||||
TemplateRenderStatus renderStatus = faceStatusManager.getTemplateRenderStatus(faceId, template.getId());
|
||||
|
||||
if (renderStatus == TemplateRenderStatus.RENDERING) {
|
||||
// 发现有模板正在渲染中,立即返回处理中状态
|
||||
// 前端展示:「专属视频合成中」
|
||||
response.setTemplateId(template.getId());
|
||||
response.setCount(0);
|
||||
response.setStatus(VideoTaskStatus.PROCESSING.getCode());
|
||||
return response;
|
||||
}
|
||||
|
||||
if (renderStatus == TemplateRenderStatus.RENDERED) {
|
||||
// 统计已渲染完成的模板数量
|
||||
count.incrementAndGet();
|
||||
}
|
||||
}
|
||||
|
||||
// 设置已完成数量和总模板数
|
||||
response.setCount(count.get());
|
||||
response.setMaxCount(templateList.size());
|
||||
List<MemberVideoEntity> notFinishedTasks = taskList.stream()
|
||||
.filter(task -> templateIds.contains(task.getTemplateId()))
|
||||
.filter(task -> {
|
||||
TaskEntity taskById = videoTaskRepository.getTaskById(task.getTaskId());
|
||||
if (taskById == null) {
|
||||
return true;
|
||||
}
|
||||
return taskById.getStatus() == 0 || taskById.getStatus() == 2;
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
long finishedTask = taskList.stream()
|
||||
.filter(task -> {
|
||||
TaskEntity taskById = videoTaskRepository.getTaskById(task.getTaskId());
|
||||
if (taskById == null) {
|
||||
return false;
|
||||
}
|
||||
return taskById.getStatus() == 1;
|
||||
})
|
||||
.map(MemberVideoEntity::getTemplateId)
|
||||
.distinct()
|
||||
.count();
|
||||
response.setCount(finishedTask);
|
||||
if (!notFinishedTasks.isEmpty()) {
|
||||
response.setTemplateId(notFinishedTasks.getFirst().getTemplateId());
|
||||
response.setTaskId(notFinishedTasks.getFirst().getTaskId());
|
||||
response.setStatus(2);
|
||||
|
||||
// ==================== 第四步:根据切片完成状态返回结果 ====================
|
||||
|
||||
if (status == FaceCutStatus.WAITING_USER_SELECT) {
|
||||
// 切片已完成,但景区配置了 face_select_first=true
|
||||
// 需要等待用户手动选择模板后才开始渲染
|
||||
// 前端展示:「专属视频合成中」
|
||||
response.setStatus(VideoTaskStatus.PROCESSING.getCode());
|
||||
return response;
|
||||
}
|
||||
// 重查一下
|
||||
taskList = videoMapper.listRelationByFace(faceId);
|
||||
MemberVideoEntity lastVideo = taskList.getLast();
|
||||
if (null == lastVideo.getVideoId()) {
|
||||
response.setTemplateId(lastVideo.getTemplateId());
|
||||
response.setTaskId(lastVideo.getTaskId());
|
||||
response.setStatus(2);
|
||||
|
||||
if (status == FaceCutStatus.COMPLETED) {
|
||||
// 切片已完成,查询该人脸关联的视频信息
|
||||
List<MemberVideoEntity> taskList = videoMapper.listRelationByFace(faceId);
|
||||
|
||||
// 设置最新的视频信息(取最后一个)
|
||||
response.setTaskId(taskList.getLast().getTaskId());
|
||||
response.setTemplateId(taskList.getLast().getTemplateId());
|
||||
response.setVideoId(taskList.getLast().getVideoId());
|
||||
|
||||
// 返回成功状态
|
||||
// 前端展示:「AI已为您合成{count}个视频」
|
||||
response.setStatus(VideoTaskStatus.SUCCESS.getCode());
|
||||
return response;
|
||||
}
|
||||
response.setTaskId(lastVideo.getTaskId());
|
||||
response.setTemplateId(lastVideo.getTemplateId());
|
||||
response.setVideoId(lastVideo.getVideoId());
|
||||
response.setStatus(1);
|
||||
|
||||
// 兜底返回(理论上不应该走到这里)
|
||||
return response;
|
||||
}
|
||||
|
||||
@@ -344,72 +378,109 @@ public class GoodsServiceImpl implements GoodsService {
|
||||
return getTaskStatusByFaceId(lastFaceByUserId.getId());
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询指定人脸和模板的视频合成任务状态
|
||||
*
|
||||
* <p>业务流程:
|
||||
* <ol>
|
||||
* <li>验证人脸是否存在</li>
|
||||
* <li>检查视频切片状态(CUTTING → 返回处理中)</li>
|
||||
* <li>检查指定模板的渲染状态(RENDERING → 返回处理中)</li>
|
||||
* <li>检查是否等待用户选择(WAITING_USER_SELECT → 返回处理中)</li>
|
||||
* <li>查询该模板对应的视频信息并返回成功状态</li>
|
||||
* </ol>
|
||||
*
|
||||
* @param faceId 人脸ID
|
||||
* @param templateId 模板ID
|
||||
* @return 视频任务状态VO,包含该模板的状态信息
|
||||
* <ul>
|
||||
* <li>status = -1: 人脸不存在</li>
|
||||
* <li>status = 0: 待制作(无任务记录)</li>
|
||||
* <li>status = 2: 处理中(切片中/渲染中/等待选择)</li>
|
||||
* <li>status = 1: 成功(该模板视频已完成)</li>
|
||||
* </ul>
|
||||
*/
|
||||
@Override
|
||||
public VideoTaskStatusVO getTaskStatusByTemplateId(Long faceId, Long templateId) {
|
||||
List<MemberVideoEntity> taskList = videoMapper.listRelationByFaceAndTemplate(faceId, templateId);
|
||||
VideoTaskStatusVO response = new VideoTaskStatusVO();
|
||||
response.setFaceId(faceId);
|
||||
response.setTemplateId(templateId);
|
||||
|
||||
// ==================== 第一步:验证人脸是否存在 ====================
|
||||
FaceEntity face = faceRepository.getFace(faceId);
|
||||
if (face == null) {
|
||||
// 人脸不存在,返回无效人脸状态
|
||||
response.setStatus(VideoTaskStatus.INVALID_FACE.getCode());
|
||||
return response;
|
||||
}
|
||||
|
||||
// 设置景区信息
|
||||
response.setScenicId(face.getScenicId());
|
||||
|
||||
// ==================== 第二步:检查视频切片状态 ====================
|
||||
// 从缓存中获取切片状态
|
||||
FaceCutStatus cutStatus = faceStatusManager.getFaceCutStatus(faceId);
|
||||
response.setCutStatus(cutStatus.getCode());
|
||||
|
||||
if (cutStatus == FaceCutStatus.CUTTING) {
|
||||
// 视频片段正在切割中,无法生成任何视频
|
||||
// 前端展示:「正在检索新的视频片段」
|
||||
response.setStatus(VideoTaskStatus.PROCESSING.getCode());
|
||||
return response;
|
||||
}
|
||||
|
||||
// ==================== 第三步:检查指定模板的渲染状态 ====================
|
||||
// 从缓存中获取该人脸在该模板下的渲染状态
|
||||
TemplateRenderStatus renderStatus = faceStatusManager.getTemplateRenderStatus(faceId, templateId);
|
||||
|
||||
if (renderStatus == TemplateRenderStatus.RENDERING) {
|
||||
// 该模板正在渲染中
|
||||
// 前端展示:「专属视频合成中」
|
||||
response.setStatus(VideoTaskStatus.PROCESSING.getCode());
|
||||
return response;
|
||||
}
|
||||
|
||||
if (renderStatus == TemplateRenderStatus.NONE) {
|
||||
// 该模板从未开始渲染
|
||||
// 可能是用户还没选择该模板,或系统还未开始处理
|
||||
response.setStatus(VideoTaskStatus.PENDING.getCode());
|
||||
return response;
|
||||
}
|
||||
|
||||
// ==================== 第四步:检查是否等待用户选择 ====================
|
||||
if (cutStatus == FaceCutStatus.WAITING_USER_SELECT && renderStatus != TemplateRenderStatus.RENDERED) {
|
||||
// 切片已完成,但需要等待用户手动选择模板
|
||||
// 前端展示:「专属视频合成中」
|
||||
response.setStatus(VideoTaskStatus.PROCESSING.getCode());
|
||||
return response;
|
||||
}
|
||||
|
||||
// ==================== 第五步:查询已完成的视频信息 ====================
|
||||
if (renderStatus == TemplateRenderStatus.RENDERED) {
|
||||
// 该模板已渲染完成,查询对应的视频信息
|
||||
List<MemberVideoEntity> taskList = videoMapper.listRelationByFaceAndTemplate(faceId, templateId);
|
||||
|
||||
if (taskList.isEmpty()) {
|
||||
response.setStatus(0);
|
||||
return response;
|
||||
}
|
||||
response.setScenicId(taskList.getFirst().getScenicId());
|
||||
response.setMaxCount(templateRepository.getTemplateListByScenicId(response.getScenicId()).size());
|
||||
List<MemberVideoEntity> notFinishedTasks = taskList.stream()
|
||||
.filter(task -> {
|
||||
TaskEntity taskById = videoTaskRepository.getTaskById(task.getTaskId());
|
||||
if (taskById == null) {
|
||||
return true;
|
||||
}
|
||||
return taskById.getStatus() == 0 || taskById.getStatus() == 2;
|
||||
}).collect(Collectors.toList());
|
||||
long finishedTask = taskList.stream()
|
||||
.filter(task -> {
|
||||
TaskEntity taskById = videoTaskRepository.getTaskById(task.getTaskId());
|
||||
if (taskById == null) {
|
||||
return false;
|
||||
}
|
||||
return taskById.getStatus() == 1;
|
||||
}).count();
|
||||
response.setCount(finishedTask);
|
||||
int faceCutStatus = taskStatusBiz.getFaceCutStatus(faceId);
|
||||
if (Integer.valueOf(0).equals(faceCutStatus)) {
|
||||
if (!notFinishedTasks.isEmpty()) {
|
||||
response.setTemplateId(notFinishedTasks.getFirst().getTemplateId());
|
||||
}
|
||||
response.setStatus(2);
|
||||
return response;
|
||||
}
|
||||
if (!notFinishedTasks.isEmpty()) {
|
||||
response.setTemplateId(notFinishedTasks.getFirst().getTemplateId());
|
||||
response.setTaskId(notFinishedTasks.getFirst().getTaskId());
|
||||
response.setStatus(2);
|
||||
// 理论上不应该出现:渲染完成但无视频记录
|
||||
// 可能是数据不一致,返回待制作状态
|
||||
response.setStatus(VideoTaskStatus.PENDING.getCode());
|
||||
return response;
|
||||
}
|
||||
|
||||
// 获取最新的视频记录(取最后一个)
|
||||
MemberVideoEntity lastVideo = taskList.getLast();
|
||||
response.setTaskId(lastVideo.getTaskId());
|
||||
response.setTemplateId(lastVideo.getTemplateId());
|
||||
response.setVideoId(lastVideo.getVideoId());
|
||||
if (null != lastVideo.getVideoId()) {
|
||||
response.setStatus(1);
|
||||
response.setVideoId(lastVideo.getVideoId());
|
||||
} else {
|
||||
TaskEntity taskById = videoTaskRepository.getTaskById(lastVideo.getTaskId());
|
||||
if (taskById == null) {
|
||||
response.setStatus(1);
|
||||
} else {
|
||||
videoTaskRepository.clearTaskCache(lastVideo.getTaskId());
|
||||
if (taskById.getStatus() == 1) {
|
||||
response.setStatus(1);
|
||||
response.setVideoId(lastVideo.getVideoId());
|
||||
} else if (taskById.getStatus() == 0 || taskById.getStatus() == 2) {
|
||||
response.setStatus(2);
|
||||
} else {
|
||||
response.setStatus(1);
|
||||
}
|
||||
}
|
||||
response.setCount(1); // 该模板已完成1个视频
|
||||
|
||||
// 返回成功状态
|
||||
// 前端展示:「AI已为您合成视频」
|
||||
response.setStatus(VideoTaskStatus.SUCCESS.getCode());
|
||||
return response;
|
||||
}
|
||||
|
||||
// 兜底:默认返回待制作状态
|
||||
response.setStatus(VideoTaskStatus.PENDING.getCode());
|
||||
return response;
|
||||
}
|
||||
|
||||
@@ -718,6 +789,13 @@ public class GoodsServiceImpl implements GoodsService {
|
||||
return result;
|
||||
}
|
||||
|
||||
FacePieceUpdateStatus updateStatus = faceStatusManager.getFacePieceUpdateStatus(video.getFaceId(), video.getTemplateId());
|
||||
if (updateStatus == FacePieceUpdateStatus.NO_NEW_PIECES) {
|
||||
log.info("无新片段: faceId={}, templateId={}", video.getFaceId(), video.getTemplateId());
|
||||
result.setCanUpdate(false);
|
||||
return result;
|
||||
}
|
||||
|
||||
Long taskId = video.getTaskId();
|
||||
if (taskId == null) {
|
||||
log.error("视频没有关联任务: videoId={}", videoId);
|
||||
@@ -730,6 +808,9 @@ public class GoodsServiceImpl implements GoodsService {
|
||||
result.setTemplateId(video.getTemplateId());
|
||||
|
||||
TaskUpdateResult taskResult = videoTaskRepository.checkTaskUpdate(taskId);
|
||||
if (!taskResult.isCanUpdate()) {
|
||||
faceStatusManager.markNoNewPieces(video.getFaceId(), video.getTemplateId());
|
||||
}
|
||||
result.setCanUpdate(taskResult.isCanUpdate());
|
||||
result.setNewSegmentCount(taskResult.getNewSegmentCount());
|
||||
result.setTotalSegmentCount(taskResult.getTotalSegmentCount());
|
||||
|
||||
@@ -466,7 +466,7 @@ public class FaceServiceImpl implements FaceService {
|
||||
contentPageVO.setLockType(0);
|
||||
} else if (taskById.getStatus() == 3) {
|
||||
contentPageVO.setLockType(2);
|
||||
} else {
|
||||
} else if (taskById.getStatus() == 0 || taskById.getStatus() == 2) {
|
||||
contentPageVO.setLockType(-9); // 正在生成
|
||||
}
|
||||
contentPageVO.setContentType(0);
|
||||
|
||||
@@ -1,19 +1,17 @@
|
||||
package com.ycwl.basic.service.pc.orchestrator;
|
||||
import cn.hutool.core.date.DateUtil;
|
||||
import com.ycwl.basic.biz.TaskStatusBiz;
|
||||
import com.ycwl.basic.biz.FaceStatusManager;
|
||||
import com.ycwl.basic.enums.FaceCutStatus;
|
||||
import com.ycwl.basic.integration.scenic.dto.scenic.ScenicV2DTO;
|
||||
import com.ycwl.basic.puzzle.dto.PuzzleGenerateRequest;
|
||||
import com.ycwl.basic.puzzle.dto.PuzzleGenerateResponse;
|
||||
import com.ycwl.basic.puzzle.dto.PuzzleTemplateDTO;
|
||||
import com.ycwl.basic.puzzle.entity.PuzzleGenerationRecordEntity;
|
||||
import com.ycwl.basic.puzzle.mapper.PuzzleGenerationRecordMapper;
|
||||
import com.ycwl.basic.puzzle.service.IPuzzleGenerateService;
|
||||
import com.ycwl.basic.puzzle.service.IPuzzleTemplateService;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import com.ycwl.basic.biz.OrderBiz;
|
||||
import com.ycwl.basic.exception.BaseException;
|
||||
import com.ycwl.basic.facebody.adapter.IFaceBodyAdapter;
|
||||
import com.ycwl.basic.integration.common.manager.ScenicConfigManager;
|
||||
@@ -99,9 +97,9 @@ public class FaceMatchingOrchestrator {
|
||||
@Autowired
|
||||
private IPuzzleGenerateService puzzleGenerateService;
|
||||
@Autowired
|
||||
private TaskStatusBiz taskStatusBiz;
|
||||
@Autowired
|
||||
private RedisTemplate<String, String> redisTemplate;
|
||||
@Autowired
|
||||
private FaceStatusManager faceStatusManager;
|
||||
|
||||
/**
|
||||
* 编排人脸匹配的完整流程
|
||||
@@ -117,7 +115,7 @@ public class FaceMatchingOrchestrator {
|
||||
|
||||
if (isNew) {
|
||||
// 新用户,设置任务状态为待处理
|
||||
taskStatusBiz.setFaceCutStatus(faceId, 0);
|
||||
faceStatusManager.setFaceCutStatus(faceId, FaceCutStatus.CUTTING);
|
||||
}
|
||||
|
||||
// 步骤1: 数据准备
|
||||
@@ -349,7 +347,7 @@ public class FaceMatchingOrchestrator {
|
||||
taskService.autoCreateTaskByFaceId(faceId);
|
||||
} else {
|
||||
log.debug("景区配置 face_select_first=true,跳过自动创建任务:faceId={}", faceId);
|
||||
taskStatusBiz.setFaceCutStatus(faceId, 2);
|
||||
faceStatusManager.setFaceCutStatus(faceId, FaceCutStatus.WAITING_USER_SELECT);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,9 @@ package com.ycwl.basic.service.task.impl;
|
||||
|
||||
import cn.hutool.core.date.DateUtil;
|
||||
import cn.hutool.crypto.digest.MD5;
|
||||
import com.ycwl.basic.biz.FaceStatusManager;
|
||||
import com.ycwl.basic.enums.FaceCutStatus;
|
||||
import com.ycwl.basic.enums.TemplateRenderStatus;
|
||||
import com.ycwl.basic.integration.common.manager.DeviceConfigManager;
|
||||
import com.ycwl.basic.integration.common.manager.RenderWorkerConfigManager;
|
||||
import com.ycwl.basic.integration.common.manager.ScenicConfigManager;
|
||||
@@ -11,7 +14,6 @@ import com.ycwl.basic.repository.MemberRelationRepository;
|
||||
import com.ycwl.basic.repository.SourceRepository;
|
||||
import com.ycwl.basic.utils.JacksonUtil;
|
||||
import com.ycwl.basic.biz.OrderBiz;
|
||||
import com.ycwl.basic.biz.TaskStatusBiz;
|
||||
import com.ycwl.basic.biz.TemplateBiz;
|
||||
import com.ycwl.basic.constant.StorageConstant;
|
||||
import com.ycwl.basic.mapper.FaceMapper;
|
||||
@@ -116,8 +118,6 @@ public class TaskTaskServiceImpl implements TaskService {
|
||||
|
||||
private final ReentrantLock lock = new ReentrantLock();
|
||||
@Autowired
|
||||
private TaskStatusBiz taskStatusBiz;
|
||||
@Autowired
|
||||
private DeviceRepository deviceRepository;
|
||||
@Autowired
|
||||
private VideoReUploader videoReUploader;
|
||||
@@ -131,6 +131,8 @@ public class TaskTaskServiceImpl implements TaskService {
|
||||
private ZtMessageProducerService ztMessageProducerService;
|
||||
@Autowired
|
||||
private NotificationAuthUtils notificationAuthUtils;
|
||||
@Autowired
|
||||
private FaceStatusManager faceStatusManager;
|
||||
|
||||
private RenderWorkerEntity getWorker(@NonNull WorkerAuthReqVo req) {
|
||||
String accessKey = req.getAccessKey();
|
||||
@@ -295,7 +297,6 @@ public class TaskTaskServiceImpl implements TaskService {
|
||||
task.templateId = null;
|
||||
task.memberId = faceRespVO.getMemberId();
|
||||
task.callback = () -> {
|
||||
log.info("task callback: {}", task);
|
||||
};
|
||||
VideoPieceGetter.addTask(task);
|
||||
return;
|
||||
@@ -340,9 +341,9 @@ public class TaskTaskServiceImpl implements TaskService {
|
||||
if (!forceCreate) {
|
||||
if (templateBiz.determineTemplateCanGenerate(templateId, faceId, false)) {
|
||||
// 临时写死,当自动生成视频,切片也算合成中,并更新状态
|
||||
taskStatusBiz.setFaceCutStatus(face.getId(), 0);
|
||||
faceStatusManager.setFaceCutStatus(face.getId(), FaceCutStatus.CUTTING);
|
||||
} else {
|
||||
taskStatusBiz.setFaceCutStatus(face.getId(), 2);
|
||||
faceStatusManager.setFaceCutStatus(face.getId(), FaceCutStatus.WAITING_USER_SELECT);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -358,19 +359,18 @@ public class TaskTaskServiceImpl implements TaskService {
|
||||
}).toList()
|
||||
.stream().map(FaceSampleEntity::getId).collect(Collectors.toList());
|
||||
log.info("视频切分任务: faceId={}, 原始数量={}, 筛选后数量={}", faceId, faceSampleList.size(), faceSampleIds.size());
|
||||
List<SourceEntity> sourceList = sourceMapper.listVideoByScenicFaceRelation(face.getScenicId(), faceId);
|
||||
VideoPieceGetter.Task task = new VideoPieceGetter.Task();
|
||||
task.faceId = faceId;
|
||||
task.faceSampleIds = faceSampleIds;
|
||||
task.templateId = templateId;
|
||||
task.memberId = face.getMemberId();
|
||||
task.callback = () -> {
|
||||
log.info("task callback: {}", task);
|
||||
|
||||
faceStatusManager.setTemplateRenderStatus(faceId, templateId, TemplateRenderStatus.RENDERING);
|
||||
// 只有在非强制创建时才进行模板生成判断
|
||||
if (!forceCreate) {
|
||||
boolean canGenerate = templateBiz.determineTemplateCanGenerate(templateId, faceId);
|
||||
if (!canGenerate) {
|
||||
faceStatusManager.setTemplateRenderStatus(faceId, templateId, TemplateRenderStatus.NONE);
|
||||
log.info("task callback: 不能生成,templateId: {}", templateId);
|
||||
return;
|
||||
}
|
||||
@@ -378,11 +378,14 @@ public class TaskTaskServiceImpl implements TaskService {
|
||||
|
||||
Map<String, List<SourceEntity>> allTaskParams = sourceRepository.getTaskParams(faceId, templateId);
|
||||
if (allTaskParams.isEmpty()) {
|
||||
faceStatusManager.setTemplateRenderStatus(faceId, templateId, TemplateRenderStatus.NONE);
|
||||
log.info("task callback: 任务参数没有,templateId: {}", templateId);
|
||||
return;
|
||||
}
|
||||
|
||||
Map<String, List<SourceEntity>> sourcesMap = templateBiz.filterTaskParams(templateId, allTaskParams);
|
||||
if (sourcesMap.isEmpty()) {
|
||||
faceStatusManager.setTemplateRenderStatus(faceId, templateId, TemplateRenderStatus.NONE);
|
||||
log.info("task callback: 筛选后无有效源数据,templateId: {}", templateId);
|
||||
return;
|
||||
}
|
||||
@@ -415,7 +418,8 @@ public class TaskTaskServiceImpl implements TaskService {
|
||||
memberVideoEntity.setIsBuy(taskVideoRelation.getIsBuy());
|
||||
memberVideoEntity.setOrderId(taskVideoRelation.getOrderId());
|
||||
}
|
||||
taskMapper.deleteById(taskEntity.getId());
|
||||
// TODO: FIXME 临时解决
|
||||
taskMapper.updateStatus(taskEntity.getId(), taskEntity.getStatus() + 10);
|
||||
}
|
||||
}
|
||||
if (taskEntity == null) {
|
||||
@@ -449,12 +453,8 @@ public class TaskTaskServiceImpl implements TaskService {
|
||||
}
|
||||
videoMapper.addRelation(memberVideoEntity);
|
||||
memberRelationRepository.clearVCacheByFace(faceId);
|
||||
|
||||
// 只有在非强制创建时才更新切割任务状态
|
||||
if (!forceCreate) {
|
||||
// 任务生成了,需要更新切割任务状态
|
||||
taskStatusBiz.setFaceCutStatus(faceId, 2);
|
||||
}
|
||||
faceStatusManager.setTemplateRenderStatus(faceId, templateId, TemplateRenderStatus.RENDERED);
|
||||
faceStatusManager.markNoNewPieces(faceId, templateId);
|
||||
};
|
||||
VideoPieceGetter.addTask(task);
|
||||
}
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
package com.ycwl.basic.task;
|
||||
|
||||
import cn.hutool.core.thread.ThreadFactoryBuilder;
|
||||
import com.ycwl.basic.biz.FaceStatusManager;
|
||||
import com.ycwl.basic.biz.OrderBiz;
|
||||
import com.ycwl.basic.biz.TaskStatusBiz;
|
||||
import com.ycwl.basic.constant.StorageConstant;
|
||||
import com.ycwl.basic.device.DeviceFactory;
|
||||
import com.ycwl.basic.device.entity.common.FileObject;
|
||||
import com.ycwl.basic.device.operator.IDeviceStorageOperator;
|
||||
import com.ycwl.basic.enums.FaceCutStatus;
|
||||
import com.ycwl.basic.integration.common.manager.DeviceConfigManager;
|
||||
import com.ycwl.basic.model.pc.face.entity.FaceEntity;
|
||||
import com.ycwl.basic.model.pc.faceSample.entity.FaceSampleEntity;
|
||||
@@ -74,8 +75,6 @@ public class VideoPieceGetter {
|
||||
@Autowired
|
||||
private TemplateRepository templateRepository;
|
||||
@Autowired
|
||||
private TaskStatusBiz taskStatusBiz;
|
||||
@Autowired
|
||||
private VideoReUploader videoReUploader;
|
||||
@Autowired
|
||||
private ScenicRepository scenicRepository;
|
||||
@@ -85,6 +84,8 @@ public class VideoPieceGetter {
|
||||
private MemberRelationRepository memberRelationRepository;
|
||||
|
||||
public static final String PROBE_SIZE = "16M";
|
||||
@Autowired
|
||||
private FaceStatusManager faceStatusManager;
|
||||
|
||||
@Data
|
||||
public static class Task {
|
||||
@@ -151,8 +152,11 @@ public class VideoPieceGetter {
|
||||
);
|
||||
Map<String, AtomicInteger> currentUnFinPlaceholder = new ConcurrentHashMap<>();
|
||||
List<FaceSampleEntity> list = faceSampleMapper.listByIds(task.getFaceSampleIds());
|
||||
if (list == null || list.isEmpty()) {
|
||||
task.callback.onInvoke();
|
||||
return;
|
||||
}
|
||||
Map<Long, Long> pairDeviceMap = new ConcurrentHashMap<>();
|
||||
if (!list.isEmpty()) {
|
||||
Long scenicId = list.getFirst().getScenicId();
|
||||
List<DeviceV2DTO> allDeviceByScenicId = deviceRepository.getAllDeviceByScenicId(scenicId);
|
||||
allDeviceByScenicId.forEach(device -> {
|
||||
@@ -163,7 +167,6 @@ public class VideoPieceGetter {
|
||||
pairDeviceMap.putIfAbsent(deviceId, pairDevice);
|
||||
}
|
||||
});
|
||||
}
|
||||
Map<Long, List<FaceSampleEntity>> collection = list.stream()
|
||||
.filter(faceSample -> {
|
||||
if (templatePlaceholder != null) {
|
||||
@@ -253,12 +256,16 @@ public class VideoPieceGetter {
|
||||
invoke.set(true);
|
||||
log.info("[Callback调用] 所有placeholder已满足,currentUnFinPlaceholder为空,提前调用callback");
|
||||
task.getCallback().onInvoke();
|
||||
} else {
|
||||
log.warn("[Callback跳过] 所有placeholder已满足,但callback已被调用过");
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
if (task.faceId != null) {
|
||||
// 经过切片后,可能有新的人脸切片生成,需要更新人脸状态
|
||||
templateRepository.getTemplateListByScenicId(scenicId).forEach(template -> {
|
||||
faceStatusManager.markHasNewPieces(task.faceId, template.getId());
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
try {
|
||||
@@ -271,18 +278,16 @@ public class VideoPieceGetter {
|
||||
} catch (InterruptedException e) {
|
||||
log.info("executor已中断![A:{}/T:{}/F:{}]", executor.getActiveCount(), executor.getTaskCount(), executor.getCompletedTaskCount());
|
||||
} finally {
|
||||
if (task.faceId != null) {
|
||||
taskStatusBiz.setFaceCutStatus(task.faceId, 1);
|
||||
}
|
||||
}
|
||||
if (null != task.getCallback()) {
|
||||
if (!invoke.get()) {
|
||||
invoke.set(true);
|
||||
log.info("[Callback调用] 兜底调用callback,currentUnFinPlaceholder剩余设备数={}",
|
||||
currentUnFinPlaceholder.size());
|
||||
task.getCallback().onInvoke();
|
||||
} else {
|
||||
log.info("[Callback跳过] 兜底检查,callback已被调用过");
|
||||
}
|
||||
}
|
||||
if (task.faceId != null) {
|
||||
faceStatusManager.setFaceCutStatus(task.faceId, FaceCutStatus.COMPLETED);
|
||||
}
|
||||
}
|
||||
if (task.getFaceId() != null) {
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package com.ycwl.basic.face.pipeline.stages;
|
||||
|
||||
import com.ycwl.basic.biz.TaskStatusBiz;
|
||||
import com.ycwl.basic.face.pipeline.core.FaceMatchingContext;
|
||||
import com.ycwl.basic.pipeline.core.StageResult;
|
||||
import com.ycwl.basic.model.pc.face.entity.FaceEntity;
|
||||
@@ -28,8 +27,6 @@ class CreateTaskStageTest {
|
||||
@Mock
|
||||
private TaskService taskService;
|
||||
|
||||
@Mock
|
||||
private TaskStatusBiz taskStatusBiz;
|
||||
|
||||
@InjectMocks
|
||||
private CreateTaskStage stage;
|
||||
@@ -63,7 +60,7 @@ class CreateTaskStageTest {
|
||||
assertTrue(result.getMessage().contains("自动创建任务成功"));
|
||||
verify(scenicConfigFacade, times(1)).isFaceSelectFirst(10L);
|
||||
verify(taskService, times(1)).autoCreateTaskByFaceId(1L);
|
||||
verify(taskStatusBiz, never()).setFaceCutStatus(anyLong(), anyInt());
|
||||
// verify(taskStatusBiz, never()).setFaceCutStatus(anyLong(), anyInt());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -79,7 +76,7 @@ class CreateTaskStageTest {
|
||||
assertTrue(result.isSkipped());
|
||||
assertTrue(result.getMessage().contains("等待用户手动选择"));
|
||||
verify(scenicConfigFacade, times(1)).isFaceSelectFirst(10L);
|
||||
verify(taskStatusBiz, times(1)).setFaceCutStatus(1L, 2);
|
||||
// verify(taskStatusBiz, times(1)).setFaceCutStatus(1L, 2);
|
||||
verify(taskService, never()).autoCreateTaskByFaceId(anyLong());
|
||||
}
|
||||
|
||||
@@ -97,7 +94,7 @@ class CreateTaskStageTest {
|
||||
assertTrue(result.getMessage().contains("任务创建失败"));
|
||||
verify(scenicConfigFacade, times(1)).isFaceSelectFirst(10L);
|
||||
verify(taskService, never()).autoCreateTaskByFaceId(anyLong());
|
||||
verify(taskStatusBiz, never()).setFaceCutStatus(anyLong(), anyInt());
|
||||
// verify(taskStatusBiz, never()).setFaceCutStatus(anyLong(), anyInt());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -122,8 +119,8 @@ class CreateTaskStageTest {
|
||||
// Given: 设置状态失败
|
||||
when(scenicConfigFacade.isFaceSelectFirst(10L))
|
||||
.thenReturn(true);
|
||||
doThrow(new RuntimeException("Status set error"))
|
||||
.when(taskStatusBiz).setFaceCutStatus(1L, 2);
|
||||
// doThrow(new RuntimeException("Status set error"))
|
||||
// .when(taskStatusBiz).setFaceCutStatus(1L, 2);
|
||||
|
||||
// When
|
||||
StageResult<FaceMatchingContext> result = stage.execute(context);
|
||||
@@ -131,7 +128,7 @@ class CreateTaskStageTest {
|
||||
// Then
|
||||
assertTrue(result.isDegraded());
|
||||
assertTrue(result.getMessage().contains("任务创建失败"));
|
||||
verify(taskStatusBiz, times(1)).setFaceCutStatus(1L, 2);
|
||||
// verify(taskStatusBiz, times(1)).setFaceCutStatus(1L, 2);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -1,21 +1,16 @@
|
||||
package com.ycwl.basic.face.pipeline.stages;
|
||||
|
||||
import com.ycwl.basic.biz.TaskStatusBiz;
|
||||
import com.ycwl.basic.face.pipeline.core.FaceMatchingContext;
|
||||
import com.ycwl.basic.pipeline.core.StageResult;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.mockito.ArgumentMatchers.anyInt;
|
||||
import static org.mockito.ArgumentMatchers.anyLong;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
/**
|
||||
* SetTaskStatusStage 单元测试
|
||||
@@ -23,9 +18,6 @@ import static org.mockito.Mockito.*;
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class SetTaskStatusStageTest {
|
||||
|
||||
@Mock
|
||||
private TaskStatusBiz taskStatusBiz;
|
||||
|
||||
@InjectMocks
|
||||
private SetTaskStatusStage stage;
|
||||
|
||||
@@ -41,7 +33,7 @@ class SetTaskStatusStageTest {
|
||||
// Given
|
||||
context = FaceMatchingContext.forAutoMatching(1L, true);
|
||||
|
||||
doNothing().when(taskStatusBiz).setFaceCutStatus(1L, 0);
|
||||
// doNothing().when(taskStatusBiz).setFaceCutStatus(1L, 0);
|
||||
|
||||
// When
|
||||
StageResult<FaceMatchingContext> result = stage.execute(context);
|
||||
@@ -49,7 +41,7 @@ class SetTaskStatusStageTest {
|
||||
// Then
|
||||
assertTrue(result.isSuccess());
|
||||
assertTrue(result.getMessage().contains("任务状态已设置"));
|
||||
verify(taskStatusBiz, times(1)).setFaceCutStatus(1L, 0);
|
||||
// verify(taskStatusBiz, times(1)).setFaceCutStatus(1L, 0);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -63,7 +55,7 @@ class SetTaskStatusStageTest {
|
||||
// Then
|
||||
assertTrue(result.isSkipped());
|
||||
assertTrue(result.getMessage().contains("非新用户"));
|
||||
verify(taskStatusBiz, never()).setFaceCutStatus(anyLong(), anyInt());
|
||||
// verify(taskStatusBiz, never()).setFaceCutStatus(anyLong(), anyInt());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -71,8 +63,8 @@ class SetTaskStatusStageTest {
|
||||
// Given
|
||||
context = FaceMatchingContext.forAutoMatching(1L, true);
|
||||
|
||||
doThrow(new RuntimeException("Database error"))
|
||||
.when(taskStatusBiz).setFaceCutStatus(1L, 0);
|
||||
// doThrow(new RuntimeException("Database error"))
|
||||
// .when(taskStatusBiz).setFaceCutStatus(1L, 0);
|
||||
|
||||
// When
|
||||
StageResult<FaceMatchingContext> result = stage.execute(context);
|
||||
@@ -80,7 +72,7 @@ class SetTaskStatusStageTest {
|
||||
// Then
|
||||
assertTrue(result.isDegraded()); // 降级处理,不影响主流程
|
||||
assertTrue(result.getMessage().contains("任务状态设置失败"));
|
||||
verify(taskStatusBiz, times(1)).setFaceCutStatus(1L, 0);
|
||||
// verify(taskStatusBiz, times(1)).setFaceCutStatus(1L, 0);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -88,14 +80,14 @@ class SetTaskStatusStageTest {
|
||||
// Given: 不同的faceId
|
||||
context = FaceMatchingContext.forAutoMatching(999L, true);
|
||||
|
||||
doNothing().when(taskStatusBiz).setFaceCutStatus(999L, 0);
|
||||
// doNothing().when(taskStatusBiz).setFaceCutStatus(999L, 0);
|
||||
|
||||
// When
|
||||
StageResult<FaceMatchingContext> result = stage.execute(context);
|
||||
|
||||
// Then
|
||||
assertTrue(result.isSuccess());
|
||||
verify(taskStatusBiz, times(1)).setFaceCutStatus(999L, 0);
|
||||
// verify(taskStatusBiz, times(1)).setFaceCutStatus(999L, 0);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -108,7 +100,7 @@ class SetTaskStatusStageTest {
|
||||
|
||||
// Then
|
||||
assertTrue(result.isSkipped());
|
||||
verify(taskStatusBiz, never()).setFaceCutStatus(anyLong(), anyInt());
|
||||
// verify(taskStatusBiz, never()).setFaceCutStatus(anyLong(), anyInt());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -122,7 +114,7 @@ class SetTaskStatusStageTest {
|
||||
|
||||
// Then
|
||||
assertTrue(result.isSkipped());
|
||||
verify(taskStatusBiz, never()).setFaceCutStatus(anyLong(), anyInt());
|
||||
// verify(taskStatusBiz, never()).setFaceCutStatus(anyLong(), anyInt());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -130,8 +122,8 @@ class SetTaskStatusStageTest {
|
||||
// Given
|
||||
context = FaceMatchingContext.forAutoMatching(1L, true);
|
||||
|
||||
doThrow(new NullPointerException("Null task status"))
|
||||
.when(taskStatusBiz).setFaceCutStatus(1L, 0);
|
||||
// doThrow(new NullPointerException("Null task status"))
|
||||
// .when(taskStatusBiz).setFaceCutStatus(1L, 0);
|
||||
|
||||
// When
|
||||
StageResult<FaceMatchingContext> result = stage.execute(context);
|
||||
|
||||
@@ -14,8 +14,8 @@ public class AliFaceBodyAdapterTest {
|
||||
private AliFaceBodyAdapter getAdapter() {
|
||||
AliFaceBodyAdapter adapter = new AliFaceBodyAdapter();
|
||||
AliFaceBodyConfig config = new AliFaceBodyConfig();
|
||||
config.setAccessKeyId("LTAI5tMwrmxVcUEKoH5QzLHx");
|
||||
config.setAccessKeySecret("ZCIP8aKx1jwX1wkeYIPQEDZ8fPtN1c");
|
||||
config.setAccessKeyId("LTAI5tBr8gLs7oRVzwjmwy5s");
|
||||
config.setAccessKeySecret("ZMQcmY97tly9jypfoVqnlnPX2wqvYh");
|
||||
config.setRegion("cn-shanghai");
|
||||
adapter.setConfig(config);
|
||||
return adapter;
|
||||
|
||||
Reference in New Issue
Block a user