feat: 基础修改
This commit is contained in:
6
pom.xml
6
pom.xml
@@ -101,6 +101,12 @@
|
||||
<version>2.0.57</version>
|
||||
</dependency>
|
||||
|
||||
<!-- BCrypt 密码加密 -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.security</groupId>
|
||||
<artifactId>spring-security-crypto</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- 统一测试依赖 -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
package com.webgame.webgamebackend.common.config;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||
|
||||
/**
|
||||
* 密码加密配置
|
||||
*/
|
||||
@Configuration
|
||||
public class PasswordConfig {
|
||||
|
||||
@Bean
|
||||
public BCryptPasswordEncoder passwordEncoder() {
|
||||
return new BCryptPasswordEncoder();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package com.webgame.webgamebackend.common.dto;
|
||||
|
||||
/**
|
||||
* 后端统一响应格式
|
||||
*
|
||||
* @param <T> 响应数据类型
|
||||
*/
|
||||
public record ApiResponse<T>(int code, String message, T data) {
|
||||
|
||||
/**
|
||||
* 成功响应
|
||||
*
|
||||
* @param data 响应数据
|
||||
* @param <T> 数据类型
|
||||
* @return ApiResponse
|
||||
*/
|
||||
public static <T> ApiResponse<T> ok(T data) {
|
||||
return new ApiResponse<>(0, "success", data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 失败响应
|
||||
*
|
||||
* @param code 业务状态码
|
||||
* @param message 错误消息
|
||||
* @param <T> 数据类型
|
||||
* @return ApiResponse
|
||||
*/
|
||||
public static <T> ApiResponse<T> fail(int code, String message) {
|
||||
return new ApiResponse<>(code, message, null);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package com.webgame.webgamebackend.common.dto.account;
|
||||
|
||||
import com.webgame.webgamebackend.common.dto.base.BaseVo;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
/**
|
||||
* 登录请求
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class LoginRequest extends BaseVo {
|
||||
|
||||
/**
|
||||
* 用户名
|
||||
*/
|
||||
@NotBlank(message = "用户名不能为空")
|
||||
private String username;
|
||||
|
||||
/**
|
||||
* 密码
|
||||
*/
|
||||
@NotBlank(message = "密码不能为空")
|
||||
private String password;
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
package com.webgame.webgamebackend.common.dto.account;
|
||||
|
||||
/**
|
||||
* 登录/注册响应
|
||||
*/
|
||||
public record LoginResponse(String token) {}
|
||||
@@ -0,0 +1,29 @@
|
||||
package com.webgame.webgamebackend.common.dto.account;
|
||||
|
||||
import com.webgame.webgamebackend.common.dto.base.BaseVo;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.Size;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
/**
|
||||
* 注册请求
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class RegisterRequest extends BaseVo {
|
||||
|
||||
/**
|
||||
* 用户名
|
||||
*/
|
||||
@NotBlank(message = "用户名不能为空")
|
||||
@Size(min = 3, max = 32, message = "用户名长度3-32位")
|
||||
private String username;
|
||||
|
||||
/**
|
||||
* 密码
|
||||
*/
|
||||
@NotBlank(message = "密码不能为空")
|
||||
@Size(min = 6, max = 64, message = "密码长度6-64位")
|
||||
private String password;
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package com.webgame.webgamebackend.common.dto.base;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 请求参数基类,所有入参 VO 均继承此类
|
||||
*/
|
||||
public abstract class BaseVo implements Serializable {
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package com.webgame.webgamebackend.common.exception;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* 业务异常,Service 层抛出,由全局异常处理器转换为 ApiResponse
|
||||
*/
|
||||
@Getter
|
||||
public class BusinessException extends RuntimeException {
|
||||
|
||||
private final int code;
|
||||
|
||||
/**
|
||||
* 使用 ErrorCode 枚举创建异常,消息使用枚举默认值
|
||||
*/
|
||||
public BusinessException(ErrorCode errorCode) {
|
||||
super(errorCode.getDefaultMessage());
|
||||
this.code = errorCode.getCode();
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用 ErrorCode 枚举 + 自定义消息创建异常
|
||||
*/
|
||||
public BusinessException(ErrorCode errorCode, String message) {
|
||||
super(message);
|
||||
this.code = errorCode.getCode();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package com.webgame.webgamebackend.common.exception;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* 业务错误码枚举,统一管理所有错误码和默认消息
|
||||
*/
|
||||
@Getter
|
||||
public enum ErrorCode {
|
||||
|
||||
// ========== 通用 ==========
|
||||
SUCCESS(0, "success"),
|
||||
BAD_REQUEST(400, "参数校验失败"),
|
||||
INTERNAL_ERROR(5000, "服务器内部错误"),
|
||||
|
||||
// ========== 账号模块 ==========
|
||||
USERNAME_EXISTS(1001, "用户名已存在"),
|
||||
BAD_CREDENTIALS(1002, "用户名或密码错误"),
|
||||
;
|
||||
|
||||
private final int code;
|
||||
private final String defaultMessage;
|
||||
|
||||
ErrorCode(int code, String defaultMessage) {
|
||||
this.code = code;
|
||||
this.defaultMessage = defaultMessage;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package com.webgame.webgamebackend.common.handler;
|
||||
|
||||
import com.webgame.webgamebackend.common.dto.ApiResponse;
|
||||
import com.webgame.webgamebackend.common.exception.BusinessException;
|
||||
import com.webgame.webgamebackend.common.exception.ErrorCode;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.web.bind.MethodArgumentNotValidException;
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
import org.springframework.web.bind.annotation.RestControllerAdvice;
|
||||
|
||||
/**
|
||||
* 全局异常处理,统一返回 ApiResponse 格式
|
||||
*/
|
||||
@Slf4j
|
||||
@RestControllerAdvice
|
||||
public class GlobalExceptionHandler {
|
||||
|
||||
/**
|
||||
* 处理业务异常
|
||||
*/
|
||||
@ExceptionHandler(BusinessException.class)
|
||||
public ApiResponse<Void> handleBusinessException(BusinessException ex) {
|
||||
log.warn("业务异常: code={}, message={}", ex.getCode(), ex.getMessage());
|
||||
return ApiResponse.fail(ex.getCode(), ex.getMessage());
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理参数校验异常
|
||||
*/
|
||||
@ExceptionHandler(MethodArgumentNotValidException.class)
|
||||
public ApiResponse<Void> handleValidation(MethodArgumentNotValidException ex) {
|
||||
String message = ex.getBindingResult().getFieldErrors().stream()
|
||||
.findFirst()
|
||||
.map(e -> e.getDefaultMessage())
|
||||
.orElse(ErrorCode.BAD_REQUEST.getDefaultMessage());
|
||||
return ApiResponse.fail(ErrorCode.BAD_REQUEST.getCode(), message);
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理其他未捕获异常
|
||||
*/
|
||||
@ExceptionHandler(Exception.class)
|
||||
public ApiResponse<Void> handleException(Exception ex) {
|
||||
log.error("服务器内部错误", ex);
|
||||
return ApiResponse.fail(ErrorCode.INTERNAL_ERROR.getCode(), ErrorCode.INTERNAL_ERROR.getDefaultMessage());
|
||||
}
|
||||
}
|
||||
@@ -1,22 +1,47 @@
|
||||
package com.webgame.webgamebackend.controller;
|
||||
|
||||
import cn.dev33.satoken.annotation.SaIgnore;
|
||||
import com.webgame.webgamebackend.common.dto.ApiResponse;
|
||||
import com.webgame.webgamebackend.common.dto.account.LoginRequest;
|
||||
import com.webgame.webgamebackend.common.dto.account.LoginResponse;
|
||||
import com.webgame.webgamebackend.common.dto.account.RegisterRequest;
|
||||
import com.webgame.webgamebackend.service.AccountService;
|
||||
import jakarta.validation.Valid;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
/**
|
||||
* 账号控制器
|
||||
*/
|
||||
@Validated
|
||||
@RestController
|
||||
@RequestMapping("/account")
|
||||
@RequiredArgsConstructor
|
||||
public class AccountController {
|
||||
|
||||
private final AccountService accountService;
|
||||
|
||||
/**
|
||||
* 登录
|
||||
*/
|
||||
@SaIgnore
|
||||
@PostMapping("/login")
|
||||
public String login() {
|
||||
return "login";
|
||||
public ApiResponse<LoginResponse> login(@Valid @RequestBody LoginRequest request) {
|
||||
LoginResponse result = accountService.login(request);
|
||||
return ApiResponse.ok(result);
|
||||
}
|
||||
|
||||
/**
|
||||
* 注册
|
||||
*/
|
||||
@SaIgnore
|
||||
@PostMapping("/register")
|
||||
public ApiResponse<LoginResponse> register(@Valid @RequestBody RegisterRequest request) {
|
||||
LoginResponse result = accountService.register(request);
|
||||
return ApiResponse.ok(result);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ public class AccountEntity extends UUIDBaseEntity {
|
||||
/**
|
||||
* 密码
|
||||
*/
|
||||
@Column(name = "password", nullable = false)
|
||||
@Column(name = "password", nullable = false, length = 60)
|
||||
private String password;
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
package com.webgame.webgamebackend.repository;
|
||||
|
||||
import com.webgame.webgamebackend.entities.AccountEntity;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* 账号 Repository
|
||||
*/
|
||||
public interface AccountRepository extends JpaRepository<AccountEntity, String> {
|
||||
|
||||
/**
|
||||
* 根据用户名查询账号
|
||||
*
|
||||
* @param username 用户名
|
||||
* @return 账号实体
|
||||
*/
|
||||
Optional<AccountEntity> findByUsername(String username);
|
||||
|
||||
/**
|
||||
* 判断用户名是否已存在
|
||||
*
|
||||
* @param username 用户名
|
||||
* @return 是否存在
|
||||
*/
|
||||
boolean existsByUsername(String username);
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package com.webgame.webgamebackend.repository;
|
||||
|
||||
import com.webgame.webgamebackend.entities.UserEntity;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
|
||||
/**
|
||||
* 用户 Repository
|
||||
*/
|
||||
public interface UserRepository extends JpaRepository<UserEntity, String> {
|
||||
}
|
||||
@@ -1,9 +1,27 @@
|
||||
package com.webgame.webgamebackend.service;
|
||||
|
||||
import com.webgame.webgamebackend.common.dto.account.LoginRequest;
|
||||
import com.webgame.webgamebackend.common.dto.account.LoginResponse;
|
||||
import com.webgame.webgamebackend.common.dto.account.RegisterRequest;
|
||||
|
||||
/**
|
||||
* 账号服务接口
|
||||
*/
|
||||
public interface AccountService {
|
||||
|
||||
/**
|
||||
* 登录
|
||||
* @return token
|
||||
*
|
||||
* @param request 登录请求
|
||||
* @return 登录结果(含 token)
|
||||
*/
|
||||
public String login();
|
||||
LoginResponse login(LoginRequest request);
|
||||
|
||||
/**
|
||||
* 注册
|
||||
*
|
||||
* @param request 注册请求
|
||||
* @return 注册结果(含 token,注册后自动登录)
|
||||
*/
|
||||
LoginResponse register(RegisterRequest request);
|
||||
}
|
||||
|
||||
@@ -1,14 +1,82 @@
|
||||
package com.webgame.webgamebackend.service.impl;
|
||||
|
||||
import cn.dev33.satoken.stp.StpUtil;
|
||||
import com.webgame.webgamebackend.common.dto.account.LoginRequest;
|
||||
import com.webgame.webgamebackend.common.dto.account.LoginResponse;
|
||||
import com.webgame.webgamebackend.common.dto.account.RegisterRequest;
|
||||
import com.webgame.webgamebackend.common.exception.BusinessException;
|
||||
import com.webgame.webgamebackend.common.exception.ErrorCode;
|
||||
import com.webgame.webgamebackend.entities.AccountEntity;
|
||||
import com.webgame.webgamebackend.entities.UserEntity;
|
||||
import com.webgame.webgamebackend.repository.AccountRepository;
|
||||
import com.webgame.webgamebackend.repository.UserRepository;
|
||||
import com.webgame.webgamebackend.service.AccountService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.concurrent.ThreadLocalRandom;
|
||||
|
||||
/**
|
||||
* 账号服务实现
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class AccountServiceImpl implements AccountService {
|
||||
|
||||
private final AccountRepository accountRepository;
|
||||
private final UserRepository userRepository;
|
||||
private final BCryptPasswordEncoder passwordEncoder;
|
||||
|
||||
@Override
|
||||
public String login() {
|
||||
return "login";
|
||||
public LoginResponse login(LoginRequest request) {
|
||||
AccountEntity account = accountRepository.findByUsername(request.getUsername())
|
||||
.orElseThrow(() -> new BusinessException(ErrorCode.BAD_CREDENTIALS));
|
||||
if (!passwordEncoder.matches(request.getPassword(), account.getPassword())) {
|
||||
throw new BusinessException(ErrorCode.BAD_CREDENTIALS);
|
||||
}
|
||||
// Sa-Token 登录
|
||||
StpUtil.login(account.getId());
|
||||
String token = StpUtil.getTokenValue();
|
||||
log.info("用户登录成功: username={}", request.getUsername());
|
||||
return new LoginResponse(token);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public LoginResponse register(RegisterRequest request) {
|
||||
// 检查用户名是否已存在
|
||||
if (accountRepository.existsByUsername(request.getUsername())) {
|
||||
throw new BusinessException(ErrorCode.USERNAME_EXISTS);
|
||||
}
|
||||
// 创建账号
|
||||
AccountEntity account = new AccountEntity();
|
||||
account.setUsername(request.getUsername());
|
||||
account.setPassword(passwordEncoder.encode(request.getPassword()));
|
||||
accountRepository.save(account);
|
||||
// 创建用户资料(默认值)
|
||||
UserEntity user = new UserEntity();
|
||||
user.setAccount(account);
|
||||
user.setNickName("玩家" + generateRandomCode());
|
||||
user.setSex(0);
|
||||
user.setSignature("");
|
||||
user.setAvatar("");
|
||||
userRepository.save(user);
|
||||
|
||||
log.info("用户注册成功: username={}", request.getUsername());
|
||||
// 注册后自动登录
|
||||
StpUtil.login(account.getId());
|
||||
String token = StpUtil.getTokenValue();
|
||||
return new LoginResponse(token);
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成6位随机数字,用于默认昵称
|
||||
*/
|
||||
private String generateRandomCode() {
|
||||
return String.valueOf(ThreadLocalRandom.current().nextInt(100000, 1000000));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user