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); // 若不允许改用户名可去掉