mirror of
https://github.com/nagisa77/OpenIsle.git
synced 2026-02-21 14:30:59 +08:00
Merge branch 'main' of github.com:nagisa77/OpenIsle
This commit is contained in:
@@ -3,4 +3,11 @@ import App from './App.vue'
|
||||
import router from './router'
|
||||
import './assets/global.css'
|
||||
|
||||
// Configurable API domain and port
|
||||
export const API_DOMAIN =
|
||||
process.env.VUE_APP_API_DOMAIN ||
|
||||
`${window.location.protocol}//${window.location.hostname}`
|
||||
export const API_PORT = process.env.VUE_APP_API_PORT || window.location.port
|
||||
export const API_BASE_URL = API_PORT ? `${API_DOMAIN}:${API_PORT}` : API_DOMAIN
|
||||
|
||||
createApp(App).use(router).mount('#app')
|
||||
|
||||
@@ -48,6 +48,7 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { API_BASE_URL } from '../main'
|
||||
export default {
|
||||
name: 'LoginPageView',
|
||||
data() {
|
||||
@@ -59,7 +60,7 @@ export default {
|
||||
methods: {
|
||||
async submitLogin() {
|
||||
try {
|
||||
const res = await fetch('/api/auth/login', {
|
||||
const res = await fetch(`${API_BASE_URL}/api/auth/login`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ username: this.username, password: this.password })
|
||||
|
||||
@@ -13,30 +13,36 @@
|
||||
<input
|
||||
class="signup-page-input-text"
|
||||
v-model="email"
|
||||
@input="emailError = ''"
|
||||
type="text"
|
||||
placeholder="邮箱"
|
||||
>
|
||||
</div>
|
||||
<div v-if="emailError" class="error-message">{{ emailError }}</div>
|
||||
|
||||
<div class="signup-page-input">
|
||||
<i class="signup-page-input-icon fas fa-user"></i>
|
||||
<input
|
||||
class="signup-page-input-text"
|
||||
v-model="username"
|
||||
@input="usernameError = ''"
|
||||
type="text"
|
||||
placeholder="用户名"
|
||||
>
|
||||
</div>
|
||||
<div v-if="usernameError" class="error-message">{{ usernameError }}</div>
|
||||
|
||||
<div class="signup-page-input">
|
||||
<i class="signup-page-input-icon fas fa-lock"></i>
|
||||
<input
|
||||
class="signup-page-input-text"
|
||||
v-model="password"
|
||||
@input="passwordError = ''"
|
||||
type="password"
|
||||
placeholder="密码"
|
||||
>
|
||||
</div>
|
||||
<div v-if="passwordError" class="error-message">{{ passwordError }}</div>
|
||||
|
||||
<div class="signup-page-input">
|
||||
<i class="signup-page-input-icon fas fa-user"></i>
|
||||
@@ -82,6 +88,7 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { API_BASE_URL } from '../main'
|
||||
export default {
|
||||
name: 'SignupPageView',
|
||||
|
||||
@@ -91,14 +98,36 @@ 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('http://localhost:8081/api/auth/register', {
|
||||
const res = await fetch(`${API_BASE_URL}/api/auth/register`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
@@ -111,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 || '发送失败')
|
||||
}
|
||||
@@ -120,7 +153,7 @@ export default {
|
||||
},
|
||||
async verifyCode() {
|
||||
try {
|
||||
const res = await fetch('/api/auth/verify', {
|
||||
const res = await fetch(`${API_BASE_URL}/api/auth/verify`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ username: this.username, code: this.code })
|
||||
@@ -272,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;
|
||||
}
|
||||
</style>
|
||||
@@ -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()));
|
||||
|
||||
19
src/main/java/com/openisle/exception/FieldException.java
Normal file
19
src/main/java/com/openisle/exception/FieldException.java
Normal file
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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); // 若不允许改用户名可去掉
|
||||
|
||||
Reference in New Issue
Block a user