fix(framework): DefaultDBFieldHandler 无登录用户时 creator/updater 兜底 system,根治 scheduled 写 null 违约
问题: AI runtime 异步链(dispatcher/executor/recorder/projection)在 scheduled 线程 insert/update muse_ai_* 时 creator/updater 写 null 触发 NOT NULL 违约 → recordRequested insert 即回滚抛 → job 卡 running/queued、AI 执行恒失败(runtime_call 0 行)。
根因: BaseDO creator/updater 带 @TableField(jdbcType=VARCHAR),fill 字段即使 null 也被 MyBatis 强制入 SQL 列;DefaultDBFieldHandler 仅当登录用户存在才回填,scheduled worker 无登录上下文 → 写 null → 违约。insert+update 同病。上一版 5d0f17a 误判 insert 靠 default 兜底,只补了 updateById,被 e2e(runtime_call 0 行)证伪。
修正: insertFill/updateFill 无登录用户时 creator/updater 兜底 SYSTEM_OPERATOR("system"),一处根治所有模块 scheduled/async 写;回退 5d0f17a 局部 setUpdater(已被统一覆盖)。OAuth2TokenServiceImplTest 的 assertPojoEquals 补 creator/updater 忽略(token 创建无登录,DB 兜底 system 与内存返回对象不一致;auto-fill 字段本不应参与内存 vs DB 对比,与同测试既有忽略一致)。
验证: 框架+AI 回归 92/0;全量 381 测试类中 D 仅触发 OAuth2 2 断言(已修),余 Pay(Redis NOAUTH)/Knowledge(mock NPE)/P1r(meta 文件)为预存/环境失败、与 D 无关;e2e task3 executor 完整跑通——runtime_call 落库(creator/updater=system)、job/generation/call 全写回终态、无违约、P-C contextAssembly 固化进 source_summary。
注: 剩余 AI_NEW_API_UNAVAILABLE(infra.env 未配 MUSE_AI_NEW_API_*,adapter 走 Unavailable 兜底) + P-A 检索 0 chunks(KB 状态 disabled/draft 非 active) 为环境/数据 gap,非代码。
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
5d0f17a410
commit
4dadc7bae3
@ -17,6 +17,15 @@ import java.util.Objects;
|
||||
*/
|
||||
public class DefaultDBFieldHandler implements MetaObjectHandler {
|
||||
|
||||
/**
|
||||
* 无登录用户上下文(scheduled/async worker 线程)时,creator/updater 的兜底操作者标识。
|
||||
*
|
||||
* <p>背景:BaseDO 的 creator/updater 带 {@code jdbcType=VARCHAR},fill 字段即使为 null 也会被
|
||||
* MyBatis 强制写入 SQL 列;后台任务无登录用户会写 null 触发 NOT NULL 违约(列定义 NOT NULL DEFAULT '')。
|
||||
* 这里统一兜底为系统标识,保证所有后台任务(AI runtime 调度、事件发布、重试等)写库不被框架字段卡住。</p>
|
||||
*/
|
||||
private static final String SYSTEM_OPERATOR = "system";
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("PatternVariableCanBeUsed")
|
||||
public void insertFill(MetaObject metaObject) {
|
||||
@ -33,14 +42,14 @@ public class DefaultDBFieldHandler implements MetaObjectHandler {
|
||||
baseDO.setUpdateTime(current);
|
||||
}
|
||||
|
||||
// 有登录用户用其 id;scheduled/async worker 无登录上下文时兜底 SYSTEM_OPERATOR,避免 creator/updater 写 null 违约。
|
||||
Long userId = SecurityFrameworkUtils.getLoginUserId();
|
||||
// 当前登录用户不为空,创建人为空,则当前登录用户为创建人
|
||||
if (Objects.nonNull(userId) && Objects.isNull(baseDO.getCreator())) {
|
||||
baseDO.setCreator(userId.toString());
|
||||
String operator = Objects.nonNull(userId) ? userId.toString() : SYSTEM_OPERATOR;
|
||||
if (Objects.isNull(baseDO.getCreator())) {
|
||||
baseDO.setCreator(operator);
|
||||
}
|
||||
// 当前登录用户不为空,更新人为空,则当前登录用户为更新人
|
||||
if (Objects.nonNull(userId) && Objects.isNull(baseDO.getUpdater())) {
|
||||
baseDO.setUpdater(userId.toString());
|
||||
if (Objects.isNull(baseDO.getUpdater())) {
|
||||
baseDO.setUpdater(operator);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -53,11 +62,11 @@ public class DefaultDBFieldHandler implements MetaObjectHandler {
|
||||
setFieldValByName("updateTime", LocalDateTime.now(), metaObject);
|
||||
}
|
||||
|
||||
// 当前登录用户不为空,更新人为空,则当前登录用户为更新人
|
||||
// 更新人为空时回填:有登录用户用其 id;scheduled/async worker 无登录上下文时兜底 SYSTEM_OPERATOR,避免 updater 写 null 违约。
|
||||
Object modifier = getFieldValByName("updater", metaObject);
|
||||
Long userId = SecurityFrameworkUtils.getLoginUserId();
|
||||
if (Objects.nonNull(userId) && Objects.isNull(modifier)) {
|
||||
setFieldValByName("updater", userId.toString(), metaObject);
|
||||
if (Objects.isNull(modifier)) {
|
||||
Long userId = SecurityFrameworkUtils.getLoginUserId();
|
||||
setFieldValByName("updater", Objects.nonNull(userId) ? userId.toString() : SYSTEM_OPERATOR, metaObject);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -96,8 +96,6 @@ public class MuseAiRuntimeCallRecorder {
|
||||
update.setNextRetryAt(succeeded || failure == null ? null : toLocalDateTime(failure.nextRetryAt()));
|
||||
update.setFinishedAt(succeeded ? toLocalDateTime(result.completedAt())
|
||||
: toLocalDateTime(failure == null ? Instant.now() : failure.failedAt()));
|
||||
// scheduled worker 线程无登录用户上下文,显式回填 updater(call owner),避免 auto-fill 落空触发 updater NOT NULL 违约。
|
||||
update.setUpdater(call.getOwnerUserId() == null ? "ai-runtime" : String.valueOf(call.getOwnerUserId()));
|
||||
runtimeCallMapper.updateById(update);
|
||||
return update;
|
||||
}
|
||||
|
||||
@ -74,10 +74,8 @@ public class MuseAiRuntimeJobDispatcher {
|
||||
update.setErrorMessage(errorMessage);
|
||||
update.setFinishedAt(LocalDateTime.now());
|
||||
update.setTenantId(claimedJob.getTenantId());
|
||||
// scheduled worker 线程无登录用户上下文,yudao auto-fill 不会回填 updater(new DO);显式回填 owner 作操作者,
|
||||
// 避免 updater NOT NULL 违约导致写回失败、job 永久 running。
|
||||
update.setUpdater(claimedJob.getOwnerUserId() == null ? "ai-runtime" : String.valueOf(claimedJob.getOwnerUserId()));
|
||||
// claim 已经把 job 推到 running;任何前置异常都必须关闭该 job,避免 worker 留下永久 running。
|
||||
// updater/creator 由 DefaultDBFieldHandler 在无登录用户(scheduled worker)时兜底 SYSTEM_OPERATOR。
|
||||
TenantUtils.executeIgnore(() -> jobMapper.updateById(update));
|
||||
}
|
||||
|
||||
|
||||
@ -172,8 +172,6 @@ public class MuseAiRuntimeProjectionService {
|
||||
MuseAiGenerationDO update = new MuseAiGenerationDO();
|
||||
update.setId(generation.getId());
|
||||
update.setCandidateId(suggestionId);
|
||||
// scheduled worker 线程无登录用户上下文,显式回填 updater(job owner),避免 auto-fill 落空触发 updater NOT NULL 违约。
|
||||
update.setUpdater(job.getOwnerUserId() == null ? "ai-runtime" : String.valueOf(job.getOwnerUserId()));
|
||||
generationMapper.updateById(update);
|
||||
return suggestionId;
|
||||
}
|
||||
|
||||
@ -78,7 +78,8 @@ public class OAuth2TokenServiceImplTest extends BaseDbAndRedisUnitTest {
|
||||
// 断言访问令牌
|
||||
OAuth2AccessTokenDO dbAccessTokenDO = oauth2AccessTokenMapper.selectByAccessToken(accessTokenDO.getAccessToken());
|
||||
// TODO @芋艿:expiresTime 被屏蔽,仅 win11 会复现,建议后续修复。
|
||||
assertPojoEquals(accessTokenDO, dbAccessTokenDO, "expiresTime", "createTime", "updateTime", "deleted");
|
||||
// creator/updater 由 DefaultDBFieldHandler 在无登录用户(token 创建无登录上下文)时兜底 SYSTEM_OPERATOR,与内存返回对象不一致,对比时忽略。
|
||||
assertPojoEquals(accessTokenDO, dbAccessTokenDO, "expiresTime", "createTime", "updateTime", "deleted", "creator", "updater");
|
||||
assertEquals(userId, accessTokenDO.getUserId());
|
||||
assertEquals(userType, accessTokenDO.getUserType());
|
||||
assertEquals(2, accessTokenDO.getUserInfo().size());
|
||||
@ -93,7 +94,7 @@ public class OAuth2TokenServiceImplTest extends BaseDbAndRedisUnitTest {
|
||||
assertPojoEquals(accessTokenDO, redisAccessTokenDO, "expiresTime", "createTime", "updateTime", "deleted");
|
||||
// 断言刷新令牌
|
||||
OAuth2RefreshTokenDO refreshTokenDO = oauth2RefreshTokenMapper.selectList().get(0);
|
||||
assertPojoEquals(accessTokenDO, refreshTokenDO, "id", "expiresTime", "createTime", "updateTime", "deleted");
|
||||
assertPojoEquals(accessTokenDO, refreshTokenDO, "id", "expiresTime", "createTime", "updateTime", "deleted", "creator", "updater");
|
||||
assertFalse(DateUtils.isExpired(refreshTokenDO.getExpiresTime()));
|
||||
}
|
||||
|
||||
@ -181,7 +182,8 @@ public class OAuth2TokenServiceImplTest extends BaseDbAndRedisUnitTest {
|
||||
// 断言,新的访问令牌
|
||||
OAuth2AccessTokenDO dbAccessTokenDO = oauth2AccessTokenMapper.selectByAccessToken(newAccessTokenDO.getAccessToken());
|
||||
// TODO @芋艿:expiresTime 被屏蔽,仅 win11 会复现,建议后续修复。
|
||||
assertPojoEquals(newAccessTokenDO, dbAccessTokenDO, "expiresTime", "createTime", "updateTime", "deleted");
|
||||
// creator/updater 由 DefaultDBFieldHandler 在无登录用户(token 创建无登录上下文)时兜底 SYSTEM_OPERATOR,与内存返回对象不一致,对比时忽略。
|
||||
assertPojoEquals(newAccessTokenDO, dbAccessTokenDO, "expiresTime", "createTime", "updateTime", "deleted", "creator", "updater");
|
||||
assertPojoEquals(newAccessTokenDO, refreshTokenDO, "id", "expiresTime", "createTime", "updateTime", "deleted",
|
||||
"creator", "updater");
|
||||
assertFalse(DateUtils.isExpired(newAccessTokenDO.getExpiresTime()));
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user