Compare commits
5 Commits
0f5fce7e0d
...
8477e49fa0
| Author | SHA1 | Date | |
|---|---|---|---|
| 8477e49fa0 | |||
| 567bee3e8f | |||
| 578f2987e5 | |||
| ab3ff6429c | |||
| a25cbb8222 |
6
pom.xml
6
pom.xml
@@ -101,6 +101,12 @@
|
|||||||
<version>2.0.57</version>
|
<version>2.0.57</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<!-- BCrypt 密码加密 -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.security</groupId>
|
||||||
|
<artifactId>spring-security-crypto</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
<!-- 统一测试依赖 -->
|
<!-- 统一测试依赖 -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.springframework.boot</groupId>
|
<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,23 @@
|
|||||||
|
package com.webgame.webgamebackend.common.dto;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 后端统一响应格式
|
||||||
|
*
|
||||||
|
* @param <T> 响应数据类型
|
||||||
|
*/
|
||||||
|
public record ApiResponse<T>(int code, String message, T data) {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 成功响应
|
||||||
|
*/
|
||||||
|
public static <T> ApiResponse<T> ok(T data) {
|
||||||
|
return new ApiResponse<>(0, "success", data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 失败响应
|
||||||
|
*/
|
||||||
|
public static <T> ApiResponse<T> fail(int code, String message) {
|
||||||
|
return new ApiResponse<>(code, message, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
package com.webgame.webgamebackend.common.dto.account;
|
||||||
|
|
||||||
|
import com.webgame.webgamebackend.common.dto.base.BaseVo;
|
||||||
|
import jakarta.validation.constraints.NotBlank;
|
||||||
|
import jakarta.validation.constraints.NotNull;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 登录请求
|
||||||
|
*/
|
||||||
|
public record LoginRequest(
|
||||||
|
@NotNull(message = "用户名不能为null")
|
||||||
|
@NotBlank(message = "用户名不能为空")
|
||||||
|
String username,
|
||||||
|
|
||||||
|
@NotNull(message = "密码不能为null")
|
||||||
|
@NotBlank(message = "密码不能为空")
|
||||||
|
String password
|
||||||
|
) implements BaseVo {
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
package com.webgame.webgamebackend.common.dto.account;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 登录/注册响应
|
||||||
|
*/
|
||||||
|
public record LoginResponse(String token) {
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
package com.webgame.webgamebackend.common.dto.account;
|
||||||
|
|
||||||
|
import com.webgame.webgamebackend.common.dto.base.BaseVo;
|
||||||
|
import jakarta.validation.constraints.NotBlank;
|
||||||
|
import jakarta.validation.constraints.NotNull;
|
||||||
|
import jakarta.validation.constraints.Size;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 注册请求
|
||||||
|
*/
|
||||||
|
public record RegisterRequest(
|
||||||
|
@NotNull(message = "用户名不能为null")
|
||||||
|
@NotBlank(message = "用户名不能为空")
|
||||||
|
@Size(min = 3, max = 32, message = "用户名长度3-32位")
|
||||||
|
String username,
|
||||||
|
|
||||||
|
@NotNull(message = "密码不能为null")
|
||||||
|
@NotBlank(message = "密码不能为空")
|
||||||
|
@Size(min = 6, max = 64, message = "密码长度6-64位")
|
||||||
|
String password
|
||||||
|
) implements BaseVo {
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
package com.webgame.webgamebackend.common.dto.base;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 入参 VO 标记接口,所有请求参数 record 均实现此接口
|
||||||
|
*/
|
||||||
|
public interface BaseVo extends Serializable {
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
package com.webgame.webgamebackend.common.dto.user;
|
||||||
|
|
||||||
|
import com.webgame.webgamebackend.entities.UserEntity;
|
||||||
|
|
||||||
|
import java.time.LocalDate;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户信息响应
|
||||||
|
*/
|
||||||
|
public record UserInfoResponse(
|
||||||
|
String nickName,
|
||||||
|
String avatar,
|
||||||
|
String signature,
|
||||||
|
Integer age,
|
||||||
|
Integer sex,
|
||||||
|
LocalDate birthday
|
||||||
|
) {
|
||||||
|
/**
|
||||||
|
* 从实体转换
|
||||||
|
*/
|
||||||
|
public static UserInfoResponse from(UserEntity user) {
|
||||||
|
return new UserInfoResponse(
|
||||||
|
user.getNickName(),
|
||||||
|
user.getAvatar(),
|
||||||
|
user.getSignature(),
|
||||||
|
user.getAge(),
|
||||||
|
user.getSex(),
|
||||||
|
user.getBirthday()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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,31 @@
|
|||||||
|
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, "用户名或密码错误"),
|
||||||
|
|
||||||
|
// ========== 用户模块 ==========
|
||||||
|
USER_NOT_FOUND(2001, "用户不存在"),
|
||||||
|
;
|
||||||
|
|
||||||
|
private final int code;
|
||||||
|
private final String defaultMessage;
|
||||||
|
|
||||||
|
ErrorCode(int code, String defaultMessage) {
|
||||||
|
this.code = code;
|
||||||
|
this.defaultMessage = defaultMessage;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,127 @@
|
|||||||
|
package com.webgame.webgamebackend.common.handler;
|
||||||
|
|
||||||
|
import cn.dev33.satoken.exception.NotLoginException;
|
||||||
|
import cn.dev33.satoken.exception.NotPermissionException;
|
||||||
|
import cn.dev33.satoken.exception.NotRoleException;
|
||||||
|
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.http.converter.HttpMessageNotReadableException;
|
||||||
|
import org.springframework.web.HttpRequestMethodNotSupportedException;
|
||||||
|
import org.springframework.web.bind.MethodArgumentNotValidException;
|
||||||
|
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||||
|
import org.springframework.web.bind.annotation.RestControllerAdvice;
|
||||||
|
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
|
||||||
|
import org.springframework.web.servlet.resource.NoResourceFoundException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 全局异常处理,统一返回 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());
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==================== 参数校验异常 ====================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理 @Valid 参数校验失败,合并所有字段的错误消息
|
||||||
|
*/
|
||||||
|
@ExceptionHandler(MethodArgumentNotValidException.class)
|
||||||
|
public ApiResponse<Void> handleValidation(MethodArgumentNotValidException ex) {
|
||||||
|
String message = ex.getBindingResult().getFieldErrors().stream()
|
||||||
|
.map(e -> e.getField() + " " + e.getDefaultMessage())
|
||||||
|
.reduce((a, b) -> a + "; " + b)
|
||||||
|
.orElse(ErrorCode.BAD_REQUEST.getDefaultMessage());
|
||||||
|
return ApiResponse.fail(ErrorCode.BAD_REQUEST.getCode(), message);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==================== Sa-Token 鉴权异常 ====================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 未登录 / token 无效
|
||||||
|
*/
|
||||||
|
@ExceptionHandler(NotLoginException.class)
|
||||||
|
public ApiResponse<Void> handleNotLogin(NotLoginException ex) {
|
||||||
|
log.warn("未登录: {}", ex.getMessage());
|
||||||
|
return ApiResponse.fail(401, "请先登录");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 无权限
|
||||||
|
*/
|
||||||
|
@ExceptionHandler(NotPermissionException.class)
|
||||||
|
public ApiResponse<Void> handleNotPermission(NotPermissionException ex) {
|
||||||
|
log.warn("无权限: {}", ex.getMessage());
|
||||||
|
return ApiResponse.fail(403, "无权限");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 无角色
|
||||||
|
*/
|
||||||
|
@ExceptionHandler(NotRoleException.class)
|
||||||
|
public ApiResponse<Void> handleNotRole(NotRoleException ex) {
|
||||||
|
log.warn("无角色: {}", ex.getMessage());
|
||||||
|
return ApiResponse.fail(403, "无权限");
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==================== Spring MVC 异常 ====================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 请求方法不支持(如 GET 访问 POST 接口)
|
||||||
|
*/
|
||||||
|
@ExceptionHandler(HttpRequestMethodNotSupportedException.class)
|
||||||
|
public ApiResponse<Void> handleMethodNotSupported(HttpRequestMethodNotSupportedException ex) {
|
||||||
|
log.warn("请求方法不支持: {}", ex.getMessage());
|
||||||
|
return ApiResponse.fail(405, "请求方法不支持");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 请求体不可读(如 JSON 格式错误)
|
||||||
|
*/
|
||||||
|
@ExceptionHandler(HttpMessageNotReadableException.class)
|
||||||
|
public ApiResponse<Void> handleMessageNotReadable(HttpMessageNotReadableException ex) {
|
||||||
|
log.warn("请求体解析失败: {}", ex.getMessage());
|
||||||
|
return ApiResponse.fail(ErrorCode.BAD_REQUEST.getCode(), "请求体格式错误");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 路径参数类型不匹配
|
||||||
|
*/
|
||||||
|
@ExceptionHandler(MethodArgumentTypeMismatchException.class)
|
||||||
|
public ApiResponse<Void> handleTypeMismatch(MethodArgumentTypeMismatchException ex) {
|
||||||
|
log.warn("参数类型不匹配: {}", ex.getMessage());
|
||||||
|
return ApiResponse.fail(ErrorCode.BAD_REQUEST.getCode(), "参数类型错误");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 资源不存在(404)
|
||||||
|
*/
|
||||||
|
@ExceptionHandler(NoResourceFoundException.class)
|
||||||
|
public ApiResponse<Void> handleNoResourceFound(NoResourceFoundException ex) {
|
||||||
|
log.warn("资源不存在: {}", ex.getMessage());
|
||||||
|
return ApiResponse.fail(404, "资源不存在");
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==================== 兜底 ====================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理其他未捕获异常
|
||||||
|
*/
|
||||||
|
@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;
|
package com.webgame.webgamebackend.controller;
|
||||||
|
|
||||||
import cn.dev33.satoken.annotation.SaIgnore;
|
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 lombok.RequiredArgsConstructor;
|
||||||
import org.springframework.validation.annotation.Validated;
|
import org.springframework.validation.annotation.Validated;
|
||||||
import org.springframework.web.bind.annotation.PostMapping;
|
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.RequestMapping;
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 账号控制器
|
||||||
|
*/
|
||||||
@Validated
|
@Validated
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/account")
|
@RequestMapping("/account")
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
public class AccountController {
|
public class AccountController {
|
||||||
|
|
||||||
|
private final AccountService accountService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 登录
|
||||||
|
*/
|
||||||
@SaIgnore
|
@SaIgnore
|
||||||
@PostMapping("/login")
|
@PostMapping("/login")
|
||||||
public String login() {
|
public ApiResponse<LoginResponse> login(@Valid @RequestBody LoginRequest request) {
|
||||||
return "login";
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,31 @@
|
|||||||
|
package com.webgame.webgamebackend.controller;
|
||||||
|
|
||||||
|
import com.webgame.webgamebackend.common.dto.ApiResponse;
|
||||||
|
import com.webgame.webgamebackend.common.dto.user.UserInfoResponse;
|
||||||
|
import com.webgame.webgamebackend.service.UserService;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.validation.annotation.Validated;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户控制器
|
||||||
|
*/
|
||||||
|
@Validated
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/user")
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class UserController {
|
||||||
|
|
||||||
|
private final UserService userService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取当前登录用户信息
|
||||||
|
*/
|
||||||
|
@GetMapping("/info")
|
||||||
|
public ApiResponse<UserInfoResponse> info() {
|
||||||
|
UserInfoResponse result = userService.getCurrentUserInfo();
|
||||||
|
return ApiResponse.ok(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
package com.webgame.webgamebackend.entities;
|
||||||
|
|
||||||
|
import com.webgame.webgamebackend.entities.base.UUIDBaseEntity;
|
||||||
|
import jakarta.persistence.Column;
|
||||||
|
import jakarta.persistence.Entity;
|
||||||
|
import jakarta.persistence.Table;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import lombok.Setter;
|
||||||
|
import lombok.experimental.Accessors;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 账号实体
|
||||||
|
*/
|
||||||
|
@Entity
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
@Accessors(chain = true)
|
||||||
|
@Table(name = "account") // 映射的表名
|
||||||
|
public class AccountEntity extends UUIDBaseEntity {
|
||||||
|
/**
|
||||||
|
* 用户名
|
||||||
|
*/
|
||||||
|
@Column(name = "username", unique = true, length = 32)
|
||||||
|
private String username;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 密码
|
||||||
|
*/
|
||||||
|
@Column(name = "password", nullable = false, length = 60)
|
||||||
|
private String password;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 邮箱
|
||||||
|
*/
|
||||||
|
@Column(name = "email", unique = true, length = 254)
|
||||||
|
private String email;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 手机号码
|
||||||
|
*/
|
||||||
|
@Column(name = "phone", unique = true, length = 11)
|
||||||
|
private String phone;
|
||||||
|
}
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
package com.webgame.webgamebackend.entities;
|
||||||
|
|
||||||
|
import com.webgame.webgamebackend.entities.base.UUIDBaseEntity;
|
||||||
|
import jakarta.persistence.Column;
|
||||||
|
import jakarta.persistence.Entity;
|
||||||
|
import jakarta.persistence.FetchType;
|
||||||
|
import jakarta.persistence.JoinColumn;
|
||||||
|
import jakarta.persistence.OneToOne;
|
||||||
|
import jakarta.persistence.Table;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import lombok.Setter;
|
||||||
|
import lombok.experimental.Accessors;
|
||||||
|
|
||||||
|
import java.time.LocalDate;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户实体,与 AccountEntity 一对一关联
|
||||||
|
*/
|
||||||
|
@Entity
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
@Accessors(chain = true)
|
||||||
|
@Table(name = "user")
|
||||||
|
public class UserEntity extends UUIDBaseEntity {
|
||||||
|
/**
|
||||||
|
* 关联的账号
|
||||||
|
*/
|
||||||
|
@OneToOne(fetch = FetchType.LAZY)
|
||||||
|
@JoinColumn(name = "account_id", referencedColumnName = "id", unique = true)
|
||||||
|
private AccountEntity account;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 昵称
|
||||||
|
*/
|
||||||
|
@Column(name = "nick_name", length = 16, nullable = false)
|
||||||
|
private String nickName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 头像
|
||||||
|
*/
|
||||||
|
@Column(name = "avatar", length = 512)
|
||||||
|
private String avatar;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 个性签名
|
||||||
|
*/
|
||||||
|
@Column(name = "signature", length = 64)
|
||||||
|
private String signature;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 年龄
|
||||||
|
*/
|
||||||
|
@Column(name = "age")
|
||||||
|
private Integer age;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 性别 1男 2女
|
||||||
|
*/
|
||||||
|
@Column(name = "sex", length = 1)
|
||||||
|
private Integer sex;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生日
|
||||||
|
*/
|
||||||
|
@Column(name = "birthday")
|
||||||
|
private LocalDate birthday;
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
package com.webgame.webgamebackend.entities.base;
|
||||||
|
|
||||||
|
import jakarta.persistence.*;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.Setter;
|
||||||
|
import lombok.experimental.Accessors;
|
||||||
|
import org.springframework.data.annotation.CreatedDate;
|
||||||
|
import org.springframework.data.annotation.LastModifiedDate;
|
||||||
|
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 基础实体
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
@Accessors(chain = true) // 允许链式调用
|
||||||
|
@MappedSuperclass // 表示该类不会映射到数据库表
|
||||||
|
@EntityListeners(AuditingEntityListener.class)
|
||||||
|
public abstract class BaseEntity implements Serializable {
|
||||||
|
/**
|
||||||
|
* 创建时间
|
||||||
|
*/
|
||||||
|
@CreatedDate
|
||||||
|
@Column(name = "create_time", updatable = false)
|
||||||
|
protected LocalDateTime createTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新时间
|
||||||
|
*/
|
||||||
|
@LastModifiedDate
|
||||||
|
@Column(name = "update_time")
|
||||||
|
protected LocalDateTime updateTime;
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
package com.webgame.webgamebackend.entities.base;
|
||||||
|
|
||||||
|
import jakarta.persistence.*;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.Setter;
|
||||||
|
import lombok.experimental.Accessors;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 基础实体类,继承该类,则该类将具有String类型的uuid
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
@Accessors(chain = true)
|
||||||
|
@MappedSuperclass
|
||||||
|
public abstract class UUIDBaseEntity extends BaseEntity implements Serializable {
|
||||||
|
/**
|
||||||
|
* 主键,uuid
|
||||||
|
*/
|
||||||
|
@Id
|
||||||
|
@Column(name = "id", unique = true, length = 64)
|
||||||
|
@GeneratedValue(strategy = GenerationType.UUID)
|
||||||
|
protected String id;
|
||||||
|
}
|
||||||
|
|
||||||
@@ -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,17 @@
|
|||||||
|
package com.webgame.webgamebackend.repository;
|
||||||
|
|
||||||
|
import com.webgame.webgamebackend.entities.UserEntity;
|
||||||
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户 Repository
|
||||||
|
*/
|
||||||
|
public interface UserRepository extends JpaRepository<UserEntity, String> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据账号 ID 查询用户
|
||||||
|
*/
|
||||||
|
Optional<UserEntity> findByAccountId(String accountId);
|
||||||
|
}
|
||||||
@@ -1,9 +1,27 @@
|
|||||||
package com.webgame.webgamebackend.service;
|
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 {
|
public interface AccountService {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 登录
|
* 登录
|
||||||
* @return token
|
*
|
||||||
|
* @param request 登录请求
|
||||||
|
* @return 登录结果(含 token)
|
||||||
*/
|
*/
|
||||||
public String login();
|
LoginResponse login(LoginRequest request);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 注册
|
||||||
|
*
|
||||||
|
* @param request 注册请求
|
||||||
|
* @return 注册结果(含 token,注册后自动登录)
|
||||||
|
*/
|
||||||
|
LoginResponse register(RegisterRequest request);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package com.webgame.webgamebackend.service;
|
||||||
|
|
||||||
|
import com.webgame.webgamebackend.common.dto.user.UserInfoResponse;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户服务接口
|
||||||
|
*/
|
||||||
|
public interface UserService {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取当前登录用户的资料
|
||||||
|
*
|
||||||
|
* @return 用户信息
|
||||||
|
*/
|
||||||
|
UserInfoResponse getCurrentUserInfo();
|
||||||
|
}
|
||||||
@@ -1,14 +1,82 @@
|
|||||||
package com.webgame.webgamebackend.service.impl;
|
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 com.webgame.webgamebackend.service.AccountService;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
|
import java.util.concurrent.ThreadLocalRandom;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 账号服务实现
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
@Service
|
@Service
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
public class AccountServiceImpl implements AccountService {
|
public class AccountServiceImpl implements AccountService {
|
||||||
|
|
||||||
|
private final AccountRepository accountRepository;
|
||||||
|
private final UserRepository userRepository;
|
||||||
|
private final BCryptPasswordEncoder passwordEncoder;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String login() {
|
public LoginResponse login(LoginRequest request) {
|
||||||
return "login";
|
AccountEntity account = accountRepository.findByUsername(request.username())
|
||||||
|
.orElseThrow(() -> new BusinessException(ErrorCode.BAD_CREDENTIALS));
|
||||||
|
if (!passwordEncoder.matches(request.password(), account.getPassword())) {
|
||||||
|
throw new BusinessException(ErrorCode.BAD_CREDENTIALS);
|
||||||
|
}
|
||||||
|
// Sa-Token 登录
|
||||||
|
StpUtil.login(account.getId());
|
||||||
|
String token = StpUtil.getTokenValue();
|
||||||
|
log.info("用户登录成功: username={}", request.username());
|
||||||
|
return new LoginResponse(token);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional
|
||||||
|
public LoginResponse register(RegisterRequest request) {
|
||||||
|
// 检查用户名是否已存在
|
||||||
|
if (accountRepository.existsByUsername(request.username())) {
|
||||||
|
throw new BusinessException(ErrorCode.USERNAME_EXISTS);
|
||||||
|
}
|
||||||
|
// 创建账号
|
||||||
|
AccountEntity account = new AccountEntity();
|
||||||
|
account.setUsername(request.username());
|
||||||
|
account.setPassword(passwordEncoder.encode(request.password()));
|
||||||
|
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.username());
|
||||||
|
// 注册后自动登录
|
||||||
|
StpUtil.login(account.getId());
|
||||||
|
String token = StpUtil.getTokenValue();
|
||||||
|
return new LoginResponse(token);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生成6位随机数字,用于默认昵称
|
||||||
|
*/
|
||||||
|
private String generateRandomCode() {
|
||||||
|
return String.valueOf(ThreadLocalRandom.current().nextInt(100000, 1000000));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,31 @@
|
|||||||
|
package com.webgame.webgamebackend.service.impl;
|
||||||
|
|
||||||
|
import cn.dev33.satoken.stp.StpUtil;
|
||||||
|
import com.webgame.webgamebackend.common.dto.user.UserInfoResponse;
|
||||||
|
import com.webgame.webgamebackend.common.exception.BusinessException;
|
||||||
|
import com.webgame.webgamebackend.common.exception.ErrorCode;
|
||||||
|
import com.webgame.webgamebackend.entities.UserEntity;
|
||||||
|
import com.webgame.webgamebackend.repository.UserRepository;
|
||||||
|
import com.webgame.webgamebackend.service.UserService;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户服务实现
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Service
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class UserServiceImpl implements UserService {
|
||||||
|
|
||||||
|
private final UserRepository userRepository;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public UserInfoResponse getCurrentUserInfo() {
|
||||||
|
String accountId = StpUtil.getLoginIdAsString();
|
||||||
|
UserEntity user = userRepository.findByAccountId(accountId)
|
||||||
|
.orElseThrow(() -> new BusinessException(ErrorCode.USER_NOT_FOUND));
|
||||||
|
return UserInfoResponse.from(user);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user