From 968341dcb1bc2953e936dbd2b41ca6a73b39b202 Mon Sep 17 00:00:00 2001 From: Tim <135014430+nagisa77@users.noreply.github.com> Date: Fri, 4 Jul 2025 22:15:05 +0800 Subject: [PATCH] feat: improve registration validation --- open-isle-cli/src/views/SignupPageView.vue | 40 +++++++++++++++++++ .../controller/GlobalExceptionHandler.java | 7 ++++ .../openisle/exception/FieldException.java | 19 +++++++++ .../openisle/service/PasswordValidator.java | 19 ++++----- .../com/openisle/service/UserService.java | 5 ++- 5 files changed, 79 insertions(+), 11 deletions(-) create mode 100644 src/main/java/com/openisle/exception/FieldException.java diff --git a/open-isle-cli/src/views/SignupPageView.vue b/open-isle-cli/src/views/SignupPageView.vue index 4ca7cf2cd..b077be726 100644 --- a/open-isle-cli/src/views/SignupPageView.vue +++ b/open-isle-cli/src/views/SignupPageView.vue @@ -13,30 +13,36 @@ +
{{ emailError }}
+
{{ usernameError }}
+
{{ passwordError }}
@@ -92,12 +98,34 @@ export default { email: '', username: '', password: '', + emailError: '', + usernameError: '', + passwordError: '', nickname: '', code: '' } }, methods: { + clearErrors() { + this.emailError = '' + this.usernameError = '' + this.passwordError = '' + }, async sendVerification() { + this.clearErrors() + const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/ + if (!emailRegex.test(this.email)) { + this.emailError = '邮箱格式不正确' + } + if (!this.password || this.password.length < 6) { + this.passwordError = '密码至少6位' + } + if (!this.username) { + this.usernameError = '用户名不能为空' + } + if (this.emailError || this.passwordError || this.usernameError) { + return + } try { const res = await fetch(`${API_BASE_URL}/api/auth/register`, { method: 'POST', @@ -112,6 +140,10 @@ export default { if (res.ok) { this.emailStep = 1 alert('验证码已发送,请查看邮箱') + } else if (data.field) { + if (data.field === 'username') this.usernameError = data.error + if (data.field === 'email') this.emailError = data.error + if (data.field === 'password') this.passwordError = data.error } else { alert(data.error || '发送失败') } @@ -273,4 +305,12 @@ export default { .signup-page-button-secondary-link { color: var(--primary-color); } + +.error-message { + color: red; + font-size: 14px; + width: calc(100% - 40px); + margin-top: -10px; + margin-bottom: 10px; +} \ No newline at end of file diff --git a/src/main/java/com/openisle/controller/GlobalExceptionHandler.java b/src/main/java/com/openisle/controller/GlobalExceptionHandler.java index dcbd83e34..e093748e9 100644 --- a/src/main/java/com/openisle/controller/GlobalExceptionHandler.java +++ b/src/main/java/com/openisle/controller/GlobalExceptionHandler.java @@ -3,12 +3,19 @@ package com.openisle.controller; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice; +import com.openisle.exception.FieldException; import java.util.Map; @RestControllerAdvice public class GlobalExceptionHandler { + @ExceptionHandler(FieldException.class) + public ResponseEntity handleFieldException(FieldException ex) { + return ResponseEntity.badRequest() + .body(Map.of("error", ex.getMessage(), "field", ex.getField())); + } + @ExceptionHandler(Exception.class) public ResponseEntity handleException(Exception ex) { return ResponseEntity.badRequest().body(Map.of("error", ex.getMessage())); diff --git a/src/main/java/com/openisle/exception/FieldException.java b/src/main/java/com/openisle/exception/FieldException.java new file mode 100644 index 000000000..af56b3314 --- /dev/null +++ b/src/main/java/com/openisle/exception/FieldException.java @@ -0,0 +1,19 @@ +package com.openisle.exception; + +import lombok.Getter; + +/** + * Exception carrying a target field name. Useful for reporting + * validation errors to clients so they can display feedback near + * the appropriate input element. + */ +@Getter +public class FieldException extends RuntimeException { + private final String field; + + public FieldException(String field, String message) { + super(message); + this.field = field; + } +} + diff --git a/src/main/java/com/openisle/service/PasswordValidator.java b/src/main/java/com/openisle/service/PasswordValidator.java index 0f6c8518f..568b061f0 100644 --- a/src/main/java/com/openisle/service/PasswordValidator.java +++ b/src/main/java/com/openisle/service/PasswordValidator.java @@ -1,6 +1,7 @@ package com.openisle.service; import com.openisle.model.PasswordStrength; +import com.openisle.exception.FieldException; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; @@ -14,7 +15,7 @@ public class PasswordValidator { public void validate(String password) { if (password == null || password.isEmpty()) { - throw new IllegalArgumentException("Password cannot be empty"); + throw new FieldException("password", "Password cannot be empty"); } switch (strength) { case MEDIUM: @@ -31,34 +32,34 @@ public class PasswordValidator { private void checkLow(String password) { if (password.length() < 6) { - throw new IllegalArgumentException("Password must be at least 6 characters long"); + throw new FieldException("password", "Password must be at least 6 characters long"); } } private void checkMedium(String password) { if (password.length() < 8) { - throw new IllegalArgumentException("Password must be at least 8 characters long"); + throw new FieldException("password", "Password must be at least 8 characters long"); } if (!password.matches(".*[A-Za-z].*") || !password.matches(".*\\d.*")) { - throw new IllegalArgumentException("Password must contain letters and numbers"); + throw new FieldException("password", "Password must contain letters and numbers"); } } private void checkHigh(String password) { if (password.length() < 12) { - throw new IllegalArgumentException("Password must be at least 12 characters long"); + throw new FieldException("password", "Password must be at least 12 characters long"); } if (!password.matches(".*[A-Z].*")) { - throw new IllegalArgumentException("Password must contain uppercase letters"); + throw new FieldException("password", "Password must contain uppercase letters"); } if (!password.matches(".*[a-z].*")) { - throw new IllegalArgumentException("Password must contain lowercase letters"); + throw new FieldException("password", "Password must contain lowercase letters"); } if (!password.matches(".*\\d.*")) { - throw new IllegalArgumentException("Password must contain numbers"); + throw new FieldException("password", "Password must contain numbers"); } if (!password.matches(".*[^A-Za-z0-9].*")) { - throw new IllegalArgumentException("Password must contain special characters"); + throw new FieldException("password", "Password must contain special characters"); } } } diff --git a/src/main/java/com/openisle/service/UserService.java b/src/main/java/com/openisle/service/UserService.java index 330e78e16..5c50a8736 100644 --- a/src/main/java/com/openisle/service/UserService.java +++ b/src/main/java/com/openisle/service/UserService.java @@ -3,6 +3,7 @@ package com.openisle.service; import com.openisle.model.User; import com.openisle.model.Role; import com.openisle.service.PasswordValidator; +import com.openisle.exception.FieldException; import com.openisle.repository.UserRepository; import lombok.RequiredArgsConstructor; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; @@ -26,7 +27,7 @@ public class UserService { if (byUsername.isPresent()) { User u = byUsername.get(); if (u.isVerified()) { // 已验证 → 直接拒绝 - throw new IllegalStateException("User name already exists"); + throw new FieldException("username", "User name already exists"); } // 未验证 → 允许“重注册”:覆盖必要字段并重新发验证码 u.setEmail(email); // 若不允许改邮箱可去掉 @@ -40,7 +41,7 @@ public class UserService { if (byEmail.isPresent()) { User u = byEmail.get(); if (u.isVerified()) { // 已验证 → 直接拒绝 - throw new IllegalStateException("User email already exists"); + throw new FieldException("email", "User email already exists"); } // 未验证 → 允许“重注册” u.setUsername(username); // 若不允许改用户名可去掉