task-128: appearancepatent 事务收缩(flatten/序列化/存储/哈希纯计算抽 prepareSubmittedChunk 移出事务,双事务路径不变)+ 8 条边界测试
This commit is contained in:
+69
-25
@@ -405,7 +405,9 @@ public class AppearancePatentTaskService {
|
||||
|
||||
private void submitResultLocked(Long taskId, AppearancePatentSubmitResultRequest request) {
|
||||
if (transactionManager != null) {
|
||||
SubmitContext context = inNewTransaction(() -> persistSubmittedChunk(taskId, request));
|
||||
// 纯计算(flatten 分组展开/序列化/payload 存储/哈希)在事务开始前完成
|
||||
PreparedSubmittedChunk prepared = prepareSubmittedChunk(taskId, request);
|
||||
SubmitContext context = inNewTransaction(() -> persistSubmittedChunk(prepared));
|
||||
inNewTransaction(() -> {
|
||||
completeSubmittedChunk(context);
|
||||
return null;
|
||||
@@ -638,7 +640,42 @@ public class AppearancePatentTaskService {
|
||||
return updatedMillis <= thresholdMillis;
|
||||
}
|
||||
|
||||
private SubmitContext persistSubmittedChunk(Long taskId, AppearancePatentSubmitResultRequest request) {
|
||||
/**
|
||||
* /result 提交的纯计算阶段(无事务注解):读任务 + 校验归属 + 分组展开 +
|
||||
* 序列化 + payload 存储 + 哈希预计算,全部在事务开始前完成。
|
||||
* 重复 chunk(查重命中)时不做存储与哈希,落库由 persist 按同一查重短路。
|
||||
*/
|
||||
private PreparedSubmittedChunk prepareSubmittedChunk(Long taskId, AppearancePatentSubmitResultRequest request) {
|
||||
FileTaskEntity task = fileTaskMapper.selectById(taskId);
|
||||
if (task == null || !MODULE_TYPE.equals(task.getModuleType())) {
|
||||
throw new BusinessException("任务不存在");
|
||||
}
|
||||
ensureTaskOwnedByCurrentInstance(task, "submit result");
|
||||
int chunkIndex = request.getChunkIndex() == null ? 0 : request.getChunkIndex();
|
||||
int chunkTotal = request.getChunkTotal() == null ? 1 : request.getChunkTotal();
|
||||
boolean done = Boolean.TRUE.equals(request.getDone());
|
||||
String scopeKey = firstNonBlank(request.getSubmissionId(), "task:" + taskId);
|
||||
String scopeHash = DigestUtil.sha256Hex(scopeKey);
|
||||
|
||||
TaskChunkEntity existing = taskChunkMapper.selectOne(new LambdaQueryWrapper<TaskChunkEntity>()
|
||||
.eq(TaskChunkEntity::getTaskId, taskId)
|
||||
.eq(TaskChunkEntity::getModuleType, MODULE_TYPE)
|
||||
.eq(TaskChunkEntity::getScopeHash, scopeHash)
|
||||
.eq(TaskChunkEntity::getChunkIndex, chunkIndex)
|
||||
.last("limit 1"));
|
||||
if (existing != null) {
|
||||
return new PreparedSubmittedChunk(task, scopeKey, scopeHash, chunkIndex, chunkTotal, done,
|
||||
request.getError(), null, null, null);
|
||||
}
|
||||
List<AppearancePatentResultRowDto> rawRows = flattenSubmittedRows(request);
|
||||
String payloadJson = writeJson(rawRows, "结果序列化失败");
|
||||
String storedPayload = storeSharedChunkPayload(taskId, scopeHash, chunkIndex, payloadJson);
|
||||
return new PreparedSubmittedChunk(task, scopeKey, scopeHash, chunkIndex, chunkTotal, done,
|
||||
request.getError(), payloadJson, storedPayload, DigestUtil.sha256Hex(payloadJson));
|
||||
}
|
||||
|
||||
private SubmitContext persistSubmittedChunk(PreparedSubmittedChunk prepared) {
|
||||
Long taskId = prepared.task().getId();
|
||||
FileTaskEntity task = fileTaskMapper.selectById(taskId);
|
||||
if (task == null || !MODULE_TYPE.equals(task.getModuleType())) {
|
||||
throw new BusinessException("任务不存在");
|
||||
@@ -648,48 +685,43 @@ public class AppearancePatentTaskService {
|
||||
}
|
||||
|
||||
ensureTaskOwnedByCurrentInstance(task, "submit result");
|
||||
int chunkIndex = request.getChunkIndex() == null ? 0 : request.getChunkIndex();
|
||||
int chunkTotal = request.getChunkTotal() == null ? 1 : request.getChunkTotal();
|
||||
boolean done = Boolean.TRUE.equals(request.getDone());
|
||||
String scopeKey = firstNonBlank(request.getSubmissionId(), "task:" + taskId);
|
||||
String scopeHash = DigestUtil.sha256Hex(scopeKey);
|
||||
taskCacheService.touchTaskHeartbeat(taskId);
|
||||
|
||||
TaskChunkEntity existing = taskChunkMapper.selectOne(new LambdaQueryWrapper<TaskChunkEntity>()
|
||||
.eq(TaskChunkEntity::getTaskId, taskId)
|
||||
.eq(TaskChunkEntity::getModuleType, MODULE_TYPE)
|
||||
.eq(TaskChunkEntity::getScopeHash, scopeHash)
|
||||
.eq(TaskChunkEntity::getChunkIndex, chunkIndex)
|
||||
.eq(TaskChunkEntity::getScopeHash, prepared.scopeHash())
|
||||
.eq(TaskChunkEntity::getChunkIndex, prepared.chunkIndex())
|
||||
.last("limit 1"));
|
||||
if (existing == null) {
|
||||
List<AppearancePatentResultRowDto> rawRows = flattenSubmittedRows(request);
|
||||
String payloadJson = writeJson(rawRows, "结果序列化失败");
|
||||
|
||||
if (existing == null && prepared.storedPayload() != null) {
|
||||
TaskChunkEntity chunk = new TaskChunkEntity();
|
||||
chunk.setTaskId(taskId);
|
||||
chunk.setModuleType(MODULE_TYPE);
|
||||
chunk.setScopeKey(scopeKey);
|
||||
chunk.setScopeHash(scopeHash);
|
||||
chunk.setChunkIndex(chunkIndex);
|
||||
chunk.setChunkTotal(chunkTotal);
|
||||
String storedPayload = storeSharedChunkPayload(taskId, scopeHash, chunkIndex, payloadJson);
|
||||
chunk.setPayloadJson(storedPayload);
|
||||
chunk.setPayloadHash(DigestUtil.sha256Hex(payloadJson));
|
||||
chunk.setScopeKey(prepared.scopeKey());
|
||||
chunk.setScopeHash(prepared.scopeHash());
|
||||
chunk.setChunkIndex(prepared.chunkIndex());
|
||||
chunk.setChunkTotal(prepared.chunkTotal());
|
||||
chunk.setPayloadJson(prepared.storedPayload());
|
||||
chunk.setPayloadHash(prepared.payloadHash());
|
||||
chunk.setCreatedAt(LocalDateTime.now());
|
||||
chunk.setUpdatedAt(LocalDateTime.now());
|
||||
try {
|
||||
taskChunkMapper.insert(chunk);
|
||||
} catch (DuplicateKeyException ex) {
|
||||
log.info("[appearance-patent] duplicate chunk inserted concurrently taskId={} scope={} chunk={}", taskId, scopeKey, chunkIndex);
|
||||
log.info("[appearance-patent] duplicate chunk inserted concurrently taskId={} scope={} chunk={}",
|
||||
taskId, prepared.scopeKey(), prepared.chunkIndex());
|
||||
}
|
||||
} else {
|
||||
log.info("[appearance-patent] duplicate chunk ignored taskId={} scope={} chunk={}", taskId, scopeKey, chunkIndex);
|
||||
} else if (existing != null) {
|
||||
log.info("[appearance-patent] duplicate chunk ignored taskId={} scope={} chunk={}",
|
||||
taskId, prepared.scopeKey(), prepared.chunkIndex());
|
||||
}
|
||||
|
||||
upsertScopeState(taskId, scopeKey, scopeHash, chunkTotal, request.getError(), done, false);
|
||||
upsertScopeState(taskId, prepared.scopeKey(), prepared.scopeHash(), prepared.chunkTotal(),
|
||||
prepared.error(), prepared.done(), false);
|
||||
task.setUpdatedAt(LocalDateTime.now());
|
||||
fileTaskMapper.updateById(task);
|
||||
return new SubmitContext(task, scopeKey, scopeHash, chunkIndex, done, request.getError());
|
||||
return new SubmitContext(task, prepared.scopeKey(), prepared.scopeHash(), prepared.chunkIndex(),
|
||||
prepared.done(), prepared.error());
|
||||
}
|
||||
|
||||
private void completeSubmittedChunk(SubmitContext context) {
|
||||
@@ -2907,6 +2939,18 @@ public class AppearancePatentTaskService {
|
||||
String error) {
|
||||
}
|
||||
|
||||
private record PreparedSubmittedChunk(FileTaskEntity task,
|
||||
String scopeKey,
|
||||
String scopeHash,
|
||||
Integer chunkIndex,
|
||||
Integer chunkTotal,
|
||||
boolean done,
|
||||
String error,
|
||||
String payloadJson,
|
||||
String storedPayload,
|
||||
String payloadHash) {
|
||||
}
|
||||
|
||||
private static class SourceRowsBuilder {
|
||||
private final String sourceFileKey;
|
||||
private String sourceFilename;
|
||||
|
||||
Reference in New Issue
Block a user