diff --git a/openspec/changes/fix-register-body-validation/.comet.yaml b/openspec/changes/fix-register-body-validation/.comet.yaml new file mode 100644 index 0000000..14d579f --- /dev/null +++ b/openspec/changes/fix-register-body-validation/.comet.yaml @@ -0,0 +1,19 @@ +workflow: hotfix +phase: build +context_compression: off +build_mode: direct +build_pause: null +subagent_dispatch: null +tdd_mode: direct +isolation: branch +verify_mode: light +auto_transition: true +base_ref: 2f7597ed53736b965191d4e48ee2c73d09ff71c0 +design_doc: null +plan: null +verify_result: pending +verification_report: null +branch_status: pending +created_at: 2026-08-14 +verified_at: null +archived: false diff --git a/openspec/changes/fix-register-body-validation/.openspec.yaml b/openspec/changes/fix-register-body-validation/.openspec.yaml new file mode 100644 index 0000000..4af8641 --- /dev/null +++ b/openspec/changes/fix-register-body-validation/.openspec.yaml @@ -0,0 +1,2 @@ +schema: spec-driven +created: 2026-08-14 diff --git a/openspec/changes/fix-register-body-validation/design.md b/openspec/changes/fix-register-body-validation/design.md new file mode 100644 index 0000000..dff7753 --- /dev/null +++ b/openspec/changes/fix-register-body-validation/design.md @@ -0,0 +1,27 @@ +## Context + +`AuthController.register` 当前依赖 Spring MVC 的 `@Validated` 自动校验。该校验发生在控制器方法调用之前,所以方法体无法为缺失的 `clientId` 补默认值。 + +## Goals / Non-Goals + +**Goals:** + +- 空白 `clientId` 使用固定注册客户端 ID `32324a04c0175c8d61ac8a282553aea1`。 +- 默认值补充后仍执行 `RegisterBody` 的全部 Bean Validation 约束。 +- 保持注册开关检查和注册服务调用顺序不变。 + +**Non-Goals:** + +- 不调整 `RegisterBody` 的继承结构或其他登录字段。 +- 不改变登录接口和认证客户端配置。 + +## Decisions + +- 移除注册方法参数上的 `@Validated`,在方法体开始处补默认 `clientId` 后调用现有 `ValidatorUtils.validate(user)`。该工具已被认证控制器和各登录策略使用,异常处理行为与现有认证流程一致。 +- 使用 `StringUtils.isBlank`,同时覆盖 `null`、空字符串和纯空白字符串。 +- 通过 `AuthController` 单元测试观察注册服务收到的请求对象,验证公共接口行为。 + +## Risks / Trade-offs + +- 固定客户端 ID 变更时需要同步修改代码和测试;本次按明确业务值实现。 +- 手动校验依赖控制器方法内调用顺序;测试覆盖默认值补充发生在校验和业务调用之前。 diff --git a/openspec/changes/fix-register-body-validation/proposal.md b/openspec/changes/fix-register-body-validation/proposal.md new file mode 100644 index 0000000..6de51bb --- /dev/null +++ b/openspec/changes/fix-register-body-validation/proposal.md @@ -0,0 +1,25 @@ +## Why + +`/auth/register` 使用 `@Validated` 在进入控制器方法前校验 `RegisterBody`,缺少 `clientId` 的请求会直接返回“认证客户端id不能为空”,因此控制器无法为注册请求补充系统默认客户端 ID。注册入口需要先补默认值,再执行原有参数校验。 + +## What Changes + +- 注册接口收到空白 `clientId` 时,将其设置为 `32324a04c0175c8d61ac8a282553aea1`。 +- 默认值补充完成后,通过现有 `ValidatorUtils` 执行 `RegisterBody` 的完整 Bean Validation。 +- 增加控制器回归测试,覆盖默认客户端 ID 和注册服务调用。 + +## Capabilities + +### New Capabilities + +- `auth-registration`: 为已有注册入口补充默认认证客户端 ID 的行为规范。 + +### Modified Capabilities + +无。仓库当前没有注册流程的既有 OpenSpec。 + +## Impact + +- 影响 `water-admin` 中的 `/auth/register` 控制器及其单元测试。 +- `/auth/register` 允许调用方省略或传入空白 `clientId`,其他注册参数约束保持不变。 +- 不新增依赖,不修改数据库结构,也不新增公共端点。 diff --git a/openspec/changes/fix-register-body-validation/specs/auth-registration/spec.md b/openspec/changes/fix-register-body-validation/specs/auth-registration/spec.md new file mode 100644 index 0000000..ef3cfd3 --- /dev/null +++ b/openspec/changes/fix-register-body-validation/specs/auth-registration/spec.md @@ -0,0 +1,11 @@ +## ADDED Requirements + +### Requirement: 注册请求使用默认认证客户端 + +系统 SHALL 在校验注册请求前,为缺失或空白的 `clientId` 设置默认值 `32324a04c0175c8d61ac8a282553aea1`,并在补值后执行 `RegisterBody` 的全部参数约束。 + +#### Scenario: 注册请求未提供客户端 ID + +- **WHEN** 调用方提交的注册请求中 `clientId` 缺失或为空白 +- **THEN** 系统将 `clientId` 设置为 `32324a04c0175c8d61ac8a282553aea1` +- **AND** 系统在调用注册服务前校验补值后的注册请求 diff --git a/openspec/changes/fix-register-body-validation/tasks.md b/openspec/changes/fix-register-body-validation/tasks.md new file mode 100644 index 0000000..1d17b77 --- /dev/null +++ b/openspec/changes/fix-register-body-validation/tasks.md @@ -0,0 +1,4 @@ +## 1. 注册默认客户端 ID + +- [x] 1.1 在控制器单元测试中覆盖空白 `clientId` 自动补默认值的注册场景,并确认修改前失败。 +- [x] 1.2 调整 `/auth/register` 的校验顺序,补默认 `clientId` 后执行完整参数校验,并运行相关测试。 diff --git a/water-admin/src/main/java/org/dromara/web/controller/AuthController.java b/water-admin/src/main/java/org/dromara/web/controller/AuthController.java index 5ed9d58..f93eeee 100644 --- a/water-admin/src/main/java/org/dromara/web/controller/AuthController.java +++ b/water-admin/src/main/java/org/dromara/web/controller/AuthController.java @@ -201,7 +201,11 @@ public class AuthController { */ @ApiEncrypt @PostMapping("/register") - public R register(@Validated @RequestBody RegisterBody user) { + public R register(@RequestBody RegisterBody user) { + if (StringUtils.isBlank(user.getClientId())) { + user.setClientId("32324a04c0175c8d61ac8a282553aea1"); + } + ValidatorUtils.validate(user); if (!configService.selectRegisterEnabled(user.getTenantId())) { return R.fail("当前系统没有开启注册功能!"); } diff --git a/water-admin/src/test/java/org/dromara/web/controller/AuthControllerUnitTest.java b/water-admin/src/test/java/org/dromara/web/controller/AuthControllerUnitTest.java index 3a257e6..8173e1a 100644 --- a/water-admin/src/test/java/org/dromara/web/controller/AuthControllerUnitTest.java +++ b/water-admin/src/test/java/org/dromara/web/controller/AuthControllerUnitTest.java @@ -1,20 +1,28 @@ package org.dromara.web.controller; import cn.dev33.satoken.stp.StpUtil; +import cn.hutool.extra.spring.SpringUtil; +import jakarta.validation.Validation; +import jakarta.validation.ValidatorFactory; import org.dromara.common.core.domain.R; import org.dromara.common.core.domain.model.AccountCancelBody; +import org.dromara.common.core.domain.model.RegisterBody; import org.dromara.common.satoken.utils.LoginHelper; +import org.dromara.system.service.ISysConfigService; import org.dromara.web.service.AccountCancellationService; +import org.dromara.web.service.SysRegisterService; import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; import org.mockito.MockedStatic; import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.context.support.GenericApplicationContext; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.mockStatic; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; @ExtendWith(MockitoExtension.class) @Tag("dev") @@ -23,6 +31,12 @@ class AuthControllerUnitTest { @Mock private AccountCancellationService accountCancellationService; + @Mock + private SysRegisterService registerService; + + @Mock + private ISysConfigService configService; + @Test void cancelAccount_checksLoginCancelsAccountAndLogsOut() { AuthController controller = new AuthController( @@ -52,4 +66,38 @@ class AuthControllerUnitTest { stpUtil.verify(() -> StpUtil.logout(100L)); } } + + @Test + void register_defaultsClientIdWhenMissing() { + AuthController controller = new AuthController( + null, + null, + registerService, + configService, + null, + null, + null, + null, + null, + accountCancellationService + ); + RegisterBody user = new RegisterBody(); + user.setUsername("register-user"); + user.setPassword("12345"); + user.setGrantType("password"); + when(configService.selectRegisterEnabled(user.getTenantId())).thenReturn(true); + + try (ValidatorFactory validatorFactory = Validation.buildDefaultValidatorFactory(); + GenericApplicationContext context = new GenericApplicationContext()) { + context.getBeanFactory().registerSingleton("validator", validatorFactory.getValidator()); + context.refresh(); + new SpringUtil().setApplicationContext(context); + + R result = controller.register(user); + + assertThat(result.getCode()).isEqualTo(200); + assertThat(user.getClientId()).isEqualTo("32324a04c0175c8d61ac8a282553aea1"); + verify(registerService).register(user); + } + } }