fix(studio): 透传集扩容并入 1101001003 生成暂停降级 + 1101001006 入口速率闸,保留产品语义 (工单j)
AIGC_USER_REJECT_CODES 从三码扩到五码:并入 AIGC_GENERATE_PAUSED(1-101-001-003, 降级开关开启时创作者原只见笼统「生成委托失败」的体验税)与 AIGC_ADMISSION_RATE_LIMITED (1-101-001-006,Sentinel 入口速率闸,与背压/配额同族的控制面准入拒绝、用户可见)。委托 aigc 抛这两码时原样透传、保留产品语义,不再一刀切包成 STUDIO_AIGC_DELEGATE_FAIL。 未并入 AIGC_LLM_SAFETY_ERROR(1-101-001-005):其定义注释明记归因 tag 仍记 llm_error (safety 超时/失败的 fail-closed 故障兜底),语义为内部错误、中性文案只作用户遮罩,非控制 面准入拒绝,故仍按原逻辑包裹。 只扩集合 + 补测试:三处委托点 catch(ServiceException) 结构与 isControlPlaneUserReject helper 一字未动(引集合常量,扩集合即自动扩行为);文案不改;不动 aigc 模块本体。 验证:StudioServiceImplTest 37 测全绿(既有 35 不回归 + 1003/1006 各一透传断言), mvn -pl game-module-studio/game-module-studio-server -am test。 生效需随下轮 jar 轮换(仅改后端字节码,已在跑实例不热更)。 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
2e4dd56b76
commit
3afed479f8
@ -40,7 +40,9 @@ import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
import static com.wanxiang.huijing.game.module.studio.enums.ErrorCodeConstants.*;
|
||||
import static com.wanxiang.huijing.game.module.aigc.enums.ErrorCodeConstants.AIGC_ADMISSION_RATE_LIMITED;
|
||||
import static com.wanxiang.huijing.game.module.aigc.enums.ErrorCodeConstants.AIGC_BACKPRESSURE_REJECTED;
|
||||
import static com.wanxiang.huijing.game.module.aigc.enums.ErrorCodeConstants.AIGC_GENERATE_PAUSED;
|
||||
import static com.wanxiang.huijing.game.module.aigc.enums.ErrorCodeConstants.AIGC_QUOTA_EXCEEDED;
|
||||
import static com.wanxiang.huijing.game.module.aigc.enums.ErrorCodeConstants.AIGC_UNSAFE_PROMPT;
|
||||
import static com.wanxiang.huijing.framework.common.exception.util.ServiceExceptionUtil.exception;
|
||||
@ -64,14 +66,20 @@ public class StudioServiceImpl implements StudioService {
|
||||
private static final Logger log = LoggerFactory.getLogger(StudioServiceImpl.class);
|
||||
|
||||
/**
|
||||
* 控制面面向用户的类型化拒绝码集合(aigc 段 1-101-001-***:配额超限 / 背压拒绝 / Prompt 不安全)。
|
||||
* 控制面面向用户的类型化拒绝码集合(aigc 段 1-101-001-***:配额超限 / 背压拒绝 / 生成暂停降级 / Prompt 不安全 / 入口速率超限)。
|
||||
* 委托 aigc 抛出这些码时原样透传给创作者、保留产品语义,不一刀切包成 STUDIO_AIGC_DELEGATE_FAIL
|
||||
* (2026-07-04 cutover S2 实证:GP9 拒绝/并发位满时创作者只见笼统「生成委托失败」,看不到真实原因)。
|
||||
* 扩容(工单j):并入 1-101-001-003 生成暂停/降级开关(降级门开启时创作者原只见「生成委托失败」,体验税)与
|
||||
* 1-101-001-006 入口速率闸(Sentinel FLOW_GRADE_QPS 削峰,与背压/配额同族的控制面准入拒绝,用户可见)。
|
||||
* 未并入 1-101-001-005 AIGC_LLM_SAFETY_ERROR:其定义注释明记「归因 tag 仍记 llm_error」(safety 超时/失败的
|
||||
* fail-closed 故障兜底),语义为内部错误、中性文案只作用户遮罩,非控制面准入拒绝,故仍按原逻辑包裹。
|
||||
*/
|
||||
private static final Set<Integer> AIGC_USER_REJECT_CODES = Set.of(
|
||||
AIGC_QUOTA_EXCEEDED.getCode(),
|
||||
AIGC_BACKPRESSURE_REJECTED.getCode(),
|
||||
AIGC_UNSAFE_PROMPT.getCode());
|
||||
AIGC_QUOTA_EXCEEDED.getCode(), // 1-101-001-001 配额超限
|
||||
AIGC_BACKPRESSURE_REJECTED.getCode(), // 1-101-001-002 背压拒绝
|
||||
AIGC_GENERATE_PAUSED.getCode(), // 1-101-001-003 生成暂停/降级开关
|
||||
AIGC_UNSAFE_PROMPT.getCode(), // 1-101-001-004 Prompt 不安全
|
||||
AIGC_ADMISSION_RATE_LIMITED.getCode()); // 1-101-001-006 入口速率超限(Sentinel QPS 闸)
|
||||
|
||||
@Resource
|
||||
private StudioSessionMapper studioSessionMapper;
|
||||
|
||||
@ -31,7 +31,9 @@ import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
|
||||
import static com.wanxiang.huijing.game.module.studio.enums.ErrorCodeConstants.*;
|
||||
import static com.wanxiang.huijing.game.module.aigc.enums.ErrorCodeConstants.AIGC_ADMISSION_RATE_LIMITED;
|
||||
import static com.wanxiang.huijing.game.module.aigc.enums.ErrorCodeConstants.AIGC_BACKPRESSURE_REJECTED;
|
||||
import static com.wanxiang.huijing.game.module.aigc.enums.ErrorCodeConstants.AIGC_GENERATE_PAUSED;
|
||||
import static com.wanxiang.huijing.game.module.aigc.enums.ErrorCodeConstants.AIGC_QUOTA_EXCEEDED;
|
||||
import static com.wanxiang.huijing.game.module.aigc.enums.ErrorCodeConstants.AIGC_UNSAFE_PROMPT;
|
||||
import static com.wanxiang.huijing.module.system.enums.ErrorCodeConstants.PLAYER_CREATE_NOT_IN_WHITELIST;
|
||||
@ -279,6 +281,31 @@ class StudioServiceImplTest extends BaseMockitoUnitTest {
|
||||
assertEquals(STUDIO_AIGC_DELEGATE_FAIL.getCode(), ex.getCode());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGenerate_aigcGeneratePaused_passesThrough_andMarksFailed() {
|
||||
// 透传集扩容(工单j):降级开关开启时 aigc submitGenerate 抛 1-101-001-003(生成暂停/降级门)→
|
||||
// studio 原样透传该码(非包成 1-112-002-000),创作者看到「生成服务繁忙维护中,请稍后再试」而非笼统「生成委托失败」;
|
||||
// 链态先置失败(markChainFailed 保持链态语义不变,与既有控制面透传用例一致)。
|
||||
stubCreatorPass();
|
||||
StudioSessionDO session = session(512L, USER_ID, 2048L, "idle");
|
||||
when(studioSessionMapper.selectById(512L)).thenReturn(session);
|
||||
doAnswer(inv -> { ((StudioTaskChainDO) inv.getArgument(0)).setId(1000L); return 1; })
|
||||
.when(studioTaskChainMapper).insert(any(StudioTaskChainDO.class));
|
||||
// aigc 控制面同步拒绝:降级开关开启抛类型化 ServiceException(降级门)
|
||||
when(aigcApi.submitGenerate(any(AigcGenerateReqDTO.class)))
|
||||
.thenThrow(new ServiceException(AIGC_GENERATE_PAUSED.getCode(), "生成服务繁忙维护中,请稍后再试"));
|
||||
|
||||
ServiceException ex = assertThrows(ServiceException.class,
|
||||
() -> studioService.generate(generateReq(512L, "p", null), USER_ID));
|
||||
|
||||
// 断言:原样透传控制面拒绝码 1-101-001-003(非 1-112-002-000)
|
||||
assertEquals(AIGC_GENERATE_PAUSED.getCode(), ex.getCode());
|
||||
// 断言:链态被置失败(透传亦保持链态语义不变)
|
||||
ArgumentCaptor<StudioTaskChainDO> chainCaptor = ArgumentCaptor.forClass(StudioTaskChainDO.class);
|
||||
verify(studioTaskChainMapper).updateById(chainCaptor.capture());
|
||||
assertEquals(StudioSessionStatusEnum.FAILED.getStatus(), chainCaptor.getValue().getStatus());
|
||||
}
|
||||
|
||||
// ============================== getTaskChain 状态流转 ==============================
|
||||
|
||||
@Test
|
||||
@ -367,6 +394,24 @@ class StudioServiceImplTest extends BaseMockitoUnitTest {
|
||||
verify(studioTaskChainMapper, never()).updateById(any(StudioTaskChainDO.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testRegenerate_aigcAdmissionRateLimited_passesThrough() {
|
||||
// 透传集扩容(工单j):入口速率闸(Sentinel FLOW_GRADE_QPS)判入队速率超限 → regenerate 委托 retryTask 抛
|
||||
// 1-101-001-006 → 原样透传该码(非包成 1-112-002-000),创作者看到「生成服务繁忙(入队速率超限),请稍后重试」。
|
||||
// regenerate 原路径无链态清理,透传亦不引入清理(保持原语义,与既有 1-101-001-001 透传用例一致)。
|
||||
StudioTaskChainDO origin = taskChain(1000L, 512L, USER_ID, 8888L);
|
||||
when(studioTaskChainMapper.selectById(1000L)).thenReturn(origin);
|
||||
when(aigcApi.retryTask(8888L, USER_ID))
|
||||
.thenThrow(new ServiceException(AIGC_ADMISSION_RATE_LIMITED.getCode(), "生成服务繁忙(入队速率超限),请稍后重试"));
|
||||
|
||||
ServiceException ex = assertThrows(ServiceException.class,
|
||||
() -> studioService.regenerate(1000L, USER_ID));
|
||||
|
||||
assertEquals(AIGC_ADMISSION_RATE_LIMITED.getCode(), ex.getCode());
|
||||
// regenerate 无 markChainFailed:透传不应触碰任务链 updateById(保持原清理动作=无)
|
||||
verify(studioTaskChainMapper, never()).updateById(any(StudioTaskChainDO.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetTaskChain_taskChainNotExists_throws() {
|
||||
when(studioTaskChainMapper.selectById(1000L)).thenReturn(null);
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user