diff --git a/muse-cloud/muse-framework/muse-spring-boot-starter-mybatis/src/main/java/cn/iocoder/muse/framework/mybatis/core/handler/DefaultDBFieldHandler.java b/muse-cloud/muse-framework/muse-spring-boot-starter-mybatis/src/main/java/cn/iocoder/muse/framework/mybatis/core/handler/DefaultDBFieldHandler.java index f0d4fb65..d0f2decb 100644 --- a/muse-cloud/muse-framework/muse-spring-boot-starter-mybatis/src/main/java/cn/iocoder/muse/framework/mybatis/core/handler/DefaultDBFieldHandler.java +++ b/muse-cloud/muse-framework/muse-spring-boot-starter-mybatis/src/main/java/cn/iocoder/muse/framework/mybatis/core/handler/DefaultDBFieldHandler.java @@ -17,6 +17,15 @@ import java.util.Objects; */ public class DefaultDBFieldHandler implements MetaObjectHandler { + /** + * 无登录用户上下文(scheduled/async worker 线程)时,creator/updater 的兜底操作者标识。 + * + *
背景:BaseDO 的 creator/updater 带 {@code jdbcType=VARCHAR},fill 字段即使为 null 也会被 + * MyBatis 强制写入 SQL 列;后台任务无登录用户会写 null 触发 NOT NULL 违约(列定义 NOT NULL DEFAULT '')。 + * 这里统一兜底为系统标识,保证所有后台任务(AI runtime 调度、事件发布、重试等)写库不被框架字段卡住。
+ */ + 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); } } } diff --git a/muse-cloud/muse-module-ai/muse-module-ai-server/src/main/java/cn/iocoder/muse/module/ai/application/muse/MuseAiRuntimeCallRecorder.java b/muse-cloud/muse-module-ai/muse-module-ai-server/src/main/java/cn/iocoder/muse/module/ai/application/muse/MuseAiRuntimeCallRecorder.java index 9ae6c759..1c619283 100644 --- a/muse-cloud/muse-module-ai/muse-module-ai-server/src/main/java/cn/iocoder/muse/module/ai/application/muse/MuseAiRuntimeCallRecorder.java +++ b/muse-cloud/muse-module-ai/muse-module-ai-server/src/main/java/cn/iocoder/muse/module/ai/application/muse/MuseAiRuntimeCallRecorder.java @@ -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; } diff --git a/muse-cloud/muse-module-ai/muse-module-ai-server/src/main/java/cn/iocoder/muse/module/ai/application/muse/MuseAiRuntimeJobDispatcher.java b/muse-cloud/muse-module-ai/muse-module-ai-server/src/main/java/cn/iocoder/muse/module/ai/application/muse/MuseAiRuntimeJobDispatcher.java index 259b3a92..b180ea5f 100644 --- a/muse-cloud/muse-module-ai/muse-module-ai-server/src/main/java/cn/iocoder/muse/module/ai/application/muse/MuseAiRuntimeJobDispatcher.java +++ b/muse-cloud/muse-module-ai/muse-module-ai-server/src/main/java/cn/iocoder/muse/module/ai/application/muse/MuseAiRuntimeJobDispatcher.java @@ -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)); } diff --git a/muse-cloud/muse-module-ai/muse-module-ai-server/src/main/java/cn/iocoder/muse/module/ai/application/muse/MuseAiRuntimeProjectionService.java b/muse-cloud/muse-module-ai/muse-module-ai-server/src/main/java/cn/iocoder/muse/module/ai/application/muse/MuseAiRuntimeProjectionService.java index d19fc961..5cf8b9a2 100644 --- a/muse-cloud/muse-module-ai/muse-module-ai-server/src/main/java/cn/iocoder/muse/module/ai/application/muse/MuseAiRuntimeProjectionService.java +++ b/muse-cloud/muse-module-ai/muse-module-ai-server/src/main/java/cn/iocoder/muse/module/ai/application/muse/MuseAiRuntimeProjectionService.java @@ -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; } diff --git a/muse-cloud/muse-module-system/muse-module-system-server/src/test/java/cn/iocoder/muse/module/system/service/oauth2/OAuth2TokenServiceImplTest.java b/muse-cloud/muse-module-system/muse-module-system-server/src/test/java/cn/iocoder/muse/module/system/service/oauth2/OAuth2TokenServiceImplTest.java index 113b321c..d2c0a49f 100644 --- a/muse-cloud/muse-module-system/muse-module-system-server/src/test/java/cn/iocoder/muse/module/system/service/oauth2/OAuth2TokenServiceImplTest.java +++ b/muse-cloud/muse-module-system/muse-module-system-server/src/test/java/cn/iocoder/muse/module/system/service/oauth2/OAuth2TokenServiceImplTest.java @@ -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()));