fix(puzzle): 解决拼图生成服务重复内容检测问题

- 添加对正在生成中记录的状态检查,避免并发重复写入
- 实现等待机制处理相同内容正在生成的情况
- 优化数据库查询逻辑,同时匹配成功和生成中的记录
- 仅对成功记录标记素材版本缓存,避免生成中记录失败时的错误标记
- 更新日志输出包含记录状态信息以便调试
- 添加超时机制确保系统稳定性
This commit is contained in:
2026-02-14 01:58:44 +08:00
parent 671cad4687
commit 4ac59b1f31
2 changed files with 77 additions and 23 deletions

View File

@@ -141,23 +141,50 @@ public class PuzzleGenerateServiceImpl implements IPuzzleGenerateService {
template.getId(), contentHash, resolvedScenicId template.getId(), contentHash, resolvedScenicId
); );
if (duplicateRecord != null) { if (duplicateRecord != null) {
long duration = System.currentTimeMillis() - startTime; if (duplicateRecord.getStatus() == 1) {
log.info("检测到重复内容,复用历史记录: recordId={}, imageUrl={}, duration={}ms", // 已有成功记录,直接复用
duplicateRecord.getId(), duplicateRecord.getResultImageUrl(), duration); long duration = System.currentTimeMillis() - startTime;
// 标记素材版本缓存 log.info("检测到重复内容,复用历史记录: recordId={}, imageUrl={}, duration={}ms",
if (request.getFaceId() != null) { duplicateRecord.getId(), duplicateRecord.getResultImageUrl(), duration);
faceStatusManager.markPuzzleSourceVersion(request.getFaceId(), template.getId(), 0); // 标记素材版本缓存
if (request.getFaceId() != null) {
faceStatusManager.markPuzzleSourceVersion(request.getFaceId(), template.getId(), 0);
}
return PuzzleGenerateResponse.success(
duplicateRecord.getResultImageUrl(),
duplicateRecord.getResultFileSize(),
duplicateRecord.getResultWidth(),
duplicateRecord.getResultHeight(),
(int) duration,
duplicateRecord.getId(),
true,
duplicateRecord.getId()
);
} else if (duplicateRecord.getStatus() == 0) {
// 相同内容正在生成中,等待完成后复用
log.info("检测到相同内容正在生成中,等待完成: recordId={}", duplicateRecord.getId());
PuzzleGenerationRecordEntity completedRecord = waitForRecordCompletion(duplicateRecord.getId(), 30_000);
if (completedRecord != null && completedRecord.getStatus() == 1) {
long duration = System.currentTimeMillis() - startTime;
log.info("等待生成中记录完成,复用结果: recordId={}, imageUrl={}, duration={}ms",
completedRecord.getId(), completedRecord.getResultImageUrl(), duration);
if (request.getFaceId() != null) {
faceStatusManager.markPuzzleSourceVersion(request.getFaceId(), template.getId(), 0);
}
return PuzzleGenerateResponse.success(
completedRecord.getResultImageUrl(),
completedRecord.getResultFileSize(),
completedRecord.getResultWidth(),
completedRecord.getResultHeight(),
(int) duration,
completedRecord.getId(),
true,
completedRecord.getId()
);
}
// 超时或失败,兜底创建新记录
log.warn("等待生成中记录超时或失败,创建新记录: originalRecordId={}", duplicateRecord.getId());
} }
return PuzzleGenerateResponse.success(
duplicateRecord.getResultImageUrl(),
duplicateRecord.getResultFileSize(),
duplicateRecord.getResultWidth(),
duplicateRecord.getResultHeight(),
(int) duration,
duplicateRecord.getId(),
true,
duplicateRecord.getId()
);
} }
// 7. 创建生成记录 // 7. 创建生成记录
@@ -290,10 +317,10 @@ public class PuzzleGenerateServiceImpl implements IPuzzleGenerateService {
); );
if (duplicateRecord != null) { if (duplicateRecord != null) {
long duration = System.currentTimeMillis() - startTime; long duration = System.currentTimeMillis() - startTime;
log.info("检测到重复内容,复用历史记录: recordId={}, imageUrl={}, duration={}ms", log.info("检测到重复内容,复用历史记录: recordId={}, status={}, imageUrl={}, duration={}ms",
duplicateRecord.getId(), duplicateRecord.getResultImageUrl(), duration); duplicateRecord.getId(), duplicateRecord.getStatus(), duplicateRecord.getResultImageUrl(), duration);
// 标记素材版本缓存 // 仅成功记录才标记素材版本缓存(生成中的记录可能会失败)
if (request.getFaceId() != null) { if (request.getFaceId() != null && duplicateRecord.getStatus() == 1) {
faceStatusManager.markPuzzleSourceVersion(request.getFaceId(), template.getId(), 0); faceStatusManager.markPuzzleSourceVersion(request.getFaceId(), template.getId(), 0);
} }
return duplicateRecord.getId(); return duplicateRecord.getId();
@@ -326,6 +353,33 @@ public class PuzzleGenerateServiceImpl implements IPuzzleGenerateService {
return record.getId(); return record.getId();
} }
/**
* 等待生成中的记录完成
* 轮询数据库直到记录状态变为非生成中(成功或失败),或超时返回null
*
* @param recordId 记录ID
* @param timeoutMs 超时时间(毫秒)
* @return 完成后的记录,超时返回null
*/
private PuzzleGenerationRecordEntity waitForRecordCompletion(Long recordId, long timeoutMs) {
long deadline = System.currentTimeMillis() + timeoutMs;
while (System.currentTimeMillis() < deadline) {
PuzzleGenerationRecordEntity record = recordMapper.getById(recordId);
if (record == null || record.getStatus() != 0) {
return record;
}
try {
Thread.sleep(500);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
log.warn("等待记录完成被中断: recordId={}", recordId);
return null;
}
}
log.warn("等待记录完成超时: recordId={}, timeoutMs={}", recordId, timeoutMs);
return null;
}
/** /**
* 校验请求参数 * 校验请求参数
*/ */

View File

@@ -128,15 +128,15 @@
WHERE id = #{id} WHERE id = #{id}
</update> </update>
<!-- 根据内容哈希查询历史记录(用于去重) --> <!-- 根据内容哈希查询历史记录(用于去重,同时匹配成功和生成中的记录,防止并发重复写入) -->
<select id="findByContentHash" resultMap="BaseResultMap"> <select id="findByContentHash" resultMap="BaseResultMap">
SELECT <include refid="Base_Column_List"/> SELECT <include refid="Base_Column_List"/>
FROM puzzle_generation_record FROM puzzle_generation_record
WHERE template_id = #{templateId} WHERE template_id = #{templateId}
AND content_hash = #{contentHash} AND content_hash = #{contentHash}
AND scenic_id = #{scenicId} AND scenic_id = #{scenicId}
AND status = 1 AND (status = 1 OR (status = 0 AND create_time > DATE_SUB(NOW(), INTERVAL 5 MINUTE)))
ORDER BY create_time DESC ORDER BY status DESC, create_time DESC
LIMIT 1 LIMIT 1
</select> </select>