From 1acf9a48021d0af1d81fdf3ed8fcf8dffd020f6b Mon Sep 17 00:00:00 2001
From: hongjli <3117313295@qq.com>
Date: 星期二, 15 四月 2025 14:20:51 +0800
Subject: [PATCH] 登录,注册,获取用户信息---接口

---
 src/main/java/com/weiwojc/utils/PasswordUtils.java              |   36 ++
 src/main/java/com/weiwojc/WeiwojcApplication.java               |    2 
 src/main/java/com/weiwojc/security/JwtAuthenticationFilter.java |   57 +++
 src/main/java/com/weiwojc/service/impl/UserServiceImpl.java     |  140 +++++++++
 src/main/java/com/weiwojc/mapper/UserMapper.java                |   26 +
 src/main/java/com/weiwojc/model/common/Result.java              |   48 +++
 src/main/java/com/weiwojc/controller/UserController.java        |   54 +++
 src/main/java/com/weiwojc/service/UserService.java              |   42 ++
 pom.xml                                                         |   76 ++++
 src/main/java/com/weiwojc/exception/GlobalExceptionHandler.java |   47 +++
 src/main/java/com/weiwojc/model/dto/UserRegisterDTO.java        |   19 +
 src/main/java/com/weiwojc/security/JwtUserDetails.java          |   54 +++
 src/main/java/com/weiwojc/utils/TokenBlacklistManager.java      |   35 ++
 src/main/java/com/weiwojc/config/MybatisPlusConfig.java         |   19 +
 src/main/java/com/weiwojc/config/SecurityConfig.java            |   41 ++
 src/main/java/com/weiwojc/utils/JwtUtils.java                   |  109 +++++++
 src/main/java/com/weiwojc/model/dto/UserLoginDTO.java           |   13 
 src/main/java/com/weiwojc/model/entity/User.java                |   51 +++
 src/main/resources/application.yml                              |   31 +
 19 files changed, 891 insertions(+), 9 deletions(-)

diff --git a/pom.xml b/pom.xml
index 750936e..9dd98db 100644
--- a/pom.xml
+++ b/pom.xml
@@ -7,7 +7,7 @@
     <parent>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter-parent</artifactId>
-        <version>3.4.4</version>
+        <version>3.2.3</version>
         <relativePath/>
     </parent>
 
@@ -22,20 +22,76 @@
         <maven.compiler.source>${java.version}</maven.compiler.source>
         <maven.compiler.target>${java.version}</maven.compiler.target>
         <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+        <mybatis-plus.version>3.5.5</mybatis-plus.version>
+        <jjwt.version>0.11.5</jjwt.version>
     </properties>
 
     <dependencies>
+        <!-- Spring Boot Starters -->
         <dependency>
             <groupId>org.springframework.boot</groupId>
             <artifactId>spring-boot-starter-web</artifactId>
         </dependency>
-
         <dependency>
             <groupId>org.springframework.boot</groupId>
-            <artifactId>spring-boot-starter-test</artifactId>
-            <scope>test</scope>
+            <artifactId>spring-boot-starter-security</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-validation</artifactId>
         </dependency>
 
+        <!-- MyBatis Plus -->
+        <dependency>
+            <groupId>com.baomidou</groupId>
+            <artifactId>mybatis-plus-boot-starter</artifactId>
+            <version>${mybatis-plus.version}</version>
+            <exclusions>
+                <exclusion>
+                    <groupId>org.mybatis</groupId>
+                    <artifactId>mybatis-spring</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+        <dependency>
+            <groupId>org.mybatis</groupId>
+            <artifactId>mybatis-spring</artifactId>
+            <version>3.0.3</version>
+        </dependency>
+
+        <!-- MySQL Driver -->
+        <dependency>
+            <groupId>com.mysql</groupId>
+            <artifactId>mysql-connector-j</artifactId>
+            <scope>runtime</scope>
+        </dependency>
+
+        <!-- JWT -->
+        <dependency>
+            <groupId>io.jsonwebtoken</groupId>
+            <artifactId>jjwt-api</artifactId>
+            <version>${jjwt.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>io.jsonwebtoken</groupId>
+            <artifactId>jjwt-impl</artifactId>
+            <version>${jjwt.version}</version>
+            <scope>runtime</scope>
+        </dependency>
+        <dependency>
+            <groupId>io.jsonwebtoken</groupId>
+            <artifactId>jjwt-jackson</artifactId>
+            <version>${jjwt.version}</version>
+            <scope>runtime</scope>
+        </dependency>
+
+        <!-- Commons -->
+        <dependency>
+            <groupId>org.apache.commons</groupId>
+            <artifactId>commons-lang3</artifactId>
+        </dependency>
+        
+        <!-- Development Tools -->
         <dependency>
             <groupId>org.springframework.boot</groupId>
             <artifactId>spring-boot-devtools</artifactId>
@@ -48,6 +104,18 @@
             <artifactId>lombok</artifactId>
             <optional>true</optional>
         </dependency>
+
+        <!-- Test -->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-test</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.security</groupId>
+            <artifactId>spring-security-test</artifactId>
+            <scope>test</scope>
+        </dependency>
     </dependencies>
 
     <build>
diff --git a/src/main/java/com/weiwojc/WeiwojcApplication.java b/src/main/java/com/weiwojc/WeiwojcApplication.java
index 242e37a..ea4bbd1 100644
--- a/src/main/java/com/weiwojc/WeiwojcApplication.java
+++ b/src/main/java/com/weiwojc/WeiwojcApplication.java
@@ -2,8 +2,10 @@
 
 import org.springframework.boot.SpringApplication;
 import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.transaction.annotation.EnableTransactionManagement;
 
 @SpringBootApplication
+@EnableTransactionManagement
 public class WeiwojcApplication {
 
     public static void main(String[] args) {
diff --git a/src/main/java/com/weiwojc/config/MybatisPlusConfig.java b/src/main/java/com/weiwojc/config/MybatisPlusConfig.java
new file mode 100644
index 0000000..aaaecd1
--- /dev/null
+++ b/src/main/java/com/weiwojc/config/MybatisPlusConfig.java
@@ -0,0 +1,19 @@
+package com.weiwojc.config;
+
+import com.baomidou.mybatisplus.annotation.DbType;
+import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
+import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+public class MybatisPlusConfig {
+
+    @Bean
+    public MybatisPlusInterceptor mybatisPlusInterceptor() {
+        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
+        // 娣诲姞鍒嗛〉鎻掍欢
+        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
+        return interceptor;
+    }
+} 
\ No newline at end of file
diff --git a/src/main/java/com/weiwojc/config/SecurityConfig.java b/src/main/java/com/weiwojc/config/SecurityConfig.java
new file mode 100644
index 0000000..025950f
--- /dev/null
+++ b/src/main/java/com/weiwojc/config/SecurityConfig.java
@@ -0,0 +1,41 @@
+package com.weiwojc.config;
+
+import com.weiwojc.security.JwtAuthenticationFilter;
+import lombok.RequiredArgsConstructor;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
+import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
+import org.springframework.security.config.http.SessionCreationPolicy;
+import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
+import org.springframework.security.crypto.password.PasswordEncoder;
+import org.springframework.security.web.SecurityFilterChain;
+import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
+
+@Configuration
+@EnableWebSecurity
+@RequiredArgsConstructor
+public class SecurityConfig {
+
+    private final JwtAuthenticationFilter jwtAuthenticationFilter;
+
+    @Bean
+    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
+        http
+            .csrf(AbstractHttpConfigurer::disable)
+            .sessionManagement(session -> session
+                .sessionCreationPolicy(SessionCreationPolicy.STATELESS))
+            .authorizeHttpRequests(auth -> auth
+                .requestMatchers("/api/users/register", "/api/users/login").permitAll()
+                .anyRequest().permitAll())  // 鏆傛椂鍏佽鎵�鏈夎姹傦紝鍚庣画鍙互鏍规嵁闇�瑕佽皟鏁�
+            .addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
+
+        return http.build();
+    }
+
+    @Bean
+    public PasswordEncoder passwordEncoder() {
+        return new BCryptPasswordEncoder();
+    }
+} 
\ No newline at end of file
diff --git a/src/main/java/com/weiwojc/controller/UserController.java b/src/main/java/com/weiwojc/controller/UserController.java
new file mode 100644
index 0000000..40cb2fb
--- /dev/null
+++ b/src/main/java/com/weiwojc/controller/UserController.java
@@ -0,0 +1,54 @@
+package com.weiwojc.controller;
+
+import com.weiwojc.model.common.Result;
+import com.weiwojc.model.dto.UserLoginDTO;
+import com.weiwojc.model.dto.UserRegisterDTO;
+import com.weiwojc.model.entity.User;
+import com.weiwojc.service.UserService;
+import com.weiwojc.utils.JwtUtils;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.validation.Valid;
+import lombok.RequiredArgsConstructor;
+import org.springframework.web.bind.annotation.*;
+
+@RestController
+@RequestMapping("/api/users")
+@RequiredArgsConstructor
+public class UserController {
+
+    private final UserService userService;
+    private final JwtUtils jwtUtils;
+
+    @PostMapping("/register")
+    public Result<User> register(@Valid @RequestBody UserRegisterDTO registerDTO) {
+        User user = userService.register(registerDTO);
+        return Result.success("娉ㄥ唽鎴愬姛", user);
+    }
+
+    @PostMapping("/login")
+    public Result<String> login(@Valid @RequestBody UserLoginDTO loginDTO) {
+        String token = userService.login(loginDTO);
+        return Result.success("鐧诲綍鎴愬姛", token);
+    }
+
+    @GetMapping("/info")
+    public Result<User> getUserInfo(HttpServletRequest request) {
+        String token = request.getHeader("token");
+        // 楠岃瘉token鏄惁瀛樺湪
+        if (token == null || token.isEmpty()) {
+            return Result.unauthorized("鏈櫥褰曟垨token鏃犳晥");
+        }
+
+        Long userId = jwtUtils.getUserIdFromToken(token);
+        if (userId == null) {
+            return Result.unauthorized("token鏃犳晥鎴栧凡杩囨湡");
+        }
+
+        User user = userService.getUserInfo(userId);
+        if (user == null) {
+            return Result.error("鐢ㄦ埛涓嶅瓨鍦�");
+        }
+        
+        return Result.success(user);
+    }
+} 
\ No newline at end of file
diff --git a/src/main/java/com/weiwojc/exception/GlobalExceptionHandler.java b/src/main/java/com/weiwojc/exception/GlobalExceptionHandler.java
new file mode 100644
index 0000000..45e377d
--- /dev/null
+++ b/src/main/java/com/weiwojc/exception/GlobalExceptionHandler.java
@@ -0,0 +1,47 @@
+package com.weiwojc.exception;
+
+import com.weiwojc.model.common.Result;
+import org.springframework.security.authentication.BadCredentialsException;
+import org.springframework.security.authentication.LockedException;
+import org.springframework.validation.BindingResult;
+import org.springframework.validation.FieldError;
+import org.springframework.web.bind.MethodArgumentNotValidException;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+import org.springframework.web.bind.annotation.RestControllerAdvice;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+@RestControllerAdvice
+public class GlobalExceptionHandler {
+
+    @ExceptionHandler(RuntimeException.class)
+    public Result<?> handleRuntimeException(RuntimeException e) {
+        return Result.badRequest(e.getMessage());
+    }
+
+    @ExceptionHandler(BadCredentialsException.class)
+    public Result<?> handleBadCredentialsException(BadCredentialsException e) {
+        return Result.unauthorized(e.getMessage());
+    }
+
+    @ExceptionHandler(LockedException.class)
+    public Result<?> handleLockedException(LockedException e) {
+        return Result.forbidden(e.getMessage());
+    }
+
+    @ExceptionHandler(MethodArgumentNotValidException.class)
+    public Result<?> handleValidationException(MethodArgumentNotValidException e) {
+        BindingResult bindingResult = e.getBindingResult();
+        List<FieldError> fieldErrors = bindingResult.getFieldErrors();
+        String errorMessage = fieldErrors.stream()
+                .map(FieldError::getDefaultMessage)
+                .collect(Collectors.joining(", "));
+        return Result.badRequest(errorMessage);
+    }
+
+    @ExceptionHandler(Exception.class)
+    public Result<?> handleException(Exception e) {
+        return Result.error("鏈嶅姟鍣ㄥ唴閮ㄩ敊璇細" + e.getMessage());
+    }
+} 
\ No newline at end of file
diff --git a/src/main/java/com/weiwojc/mapper/UserMapper.java b/src/main/java/com/weiwojc/mapper/UserMapper.java
new file mode 100644
index 0000000..6087e4d
--- /dev/null
+++ b/src/main/java/com/weiwojc/mapper/UserMapper.java
@@ -0,0 +1,26 @@
+package com.weiwojc.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.weiwojc.model.entity.User;
+import org.apache.ibatis.annotations.*;
+
+@Mapper
+public interface UserMapper extends BaseMapper<User> {
+    
+    @Select("SELECT * FROM user WHERE username = #{username} AND is_deleted = 0")
+    User findByUsername(String username);
+
+    @Select("SELECT * FROM user WHERE user_id = #{userId} AND is_deleted = 0")
+    User findById(Long userId);
+
+    @Update("UPDATE user SET last_login = #{lastLogin} WHERE user_id = #{userId}")
+    int updateLastLogin(@Param("userId") Long userId, @Param("lastLogin") java.time.LocalDateTime lastLogin);
+
+    @Update("UPDATE user SET login_attempts = #{attempts}, locked_until = #{lockedUntil} WHERE user_id = #{userId}")
+    int updateLoginAttempts(@Param("userId") Long userId, 
+                          @Param("attempts") Integer attempts, 
+                          @Param("lockedUntil") java.time.LocalDateTime lockedUntil);
+
+    @Update("UPDATE user SET login_attempts = 0, locked_until = null WHERE username = #{username}")
+    int resetLoginAttempts(String username);
+} 
\ No newline at end of file
diff --git a/src/main/java/com/weiwojc/model/common/Result.java b/src/main/java/com/weiwojc/model/common/Result.java
new file mode 100644
index 0000000..c641a53
--- /dev/null
+++ b/src/main/java/com/weiwojc/model/common/Result.java
@@ -0,0 +1,48 @@
+package com.weiwojc.model.common;
+
+import lombok.Data;
+
+@Data
+public class Result<T> {
+    private Integer code;
+    private String message;
+    private T data;
+
+    private Result(Integer code, String message, T data) {
+        this.code = code;
+        this.message = message;
+        this.data = data;
+    }
+
+    public static <T> Result<T> success() {
+        return new Result<>(200, "鎿嶄綔鎴愬姛", null);
+    }
+
+    public static <T> Result<T> success(T data) {
+        return new Result<>(200, "鎿嶄綔鎴愬姛", data);
+    }
+
+    public static <T> Result<T> success(String message, T data) {
+        return new Result<>(200, message, data);
+    }
+
+    public static <T> Result<T> error(String message) {
+        return new Result<>(500, message, null);
+    }
+
+    public static <T> Result<T> error(Integer code, String message) {
+        return new Result<>(code, message, null);
+    }
+
+    public static <T> Result<T> badRequest(String message) {
+        return new Result<>(400, message, null);
+    }
+
+    public static <T> Result<T> forbidden(String message) {
+        return new Result<>(403, message, null);
+    }
+
+    public static <T> Result<T> unauthorized(String message) {
+        return new Result<>(401, message, null);
+    }
+} 
\ No newline at end of file
diff --git a/src/main/java/com/weiwojc/model/dto/UserLoginDTO.java b/src/main/java/com/weiwojc/model/dto/UserLoginDTO.java
new file mode 100644
index 0000000..47e6a88
--- /dev/null
+++ b/src/main/java/com/weiwojc/model/dto/UserLoginDTO.java
@@ -0,0 +1,13 @@
+package com.weiwojc.model.dto;
+
+import jakarta.validation.constraints.NotBlank;
+import lombok.Data;
+
+@Data
+public class UserLoginDTO {
+    @NotBlank(message = "璐﹀彿鍚嶄笉鑳戒负绌�")
+    private String accountName;
+
+    @NotBlank(message = "瀵嗙爜涓嶈兘涓虹┖")
+    private String password;
+} 
\ No newline at end of file
diff --git a/src/main/java/com/weiwojc/model/dto/UserRegisterDTO.java b/src/main/java/com/weiwojc/model/dto/UserRegisterDTO.java
new file mode 100644
index 0000000..9dfb088
--- /dev/null
+++ b/src/main/java/com/weiwojc/model/dto/UserRegisterDTO.java
@@ -0,0 +1,19 @@
+package com.weiwojc.model.dto;
+
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.Pattern;
+import lombok.Data;
+
+@Data
+public class UserRegisterDTO {
+    @NotBlank(message = "鐢ㄦ埛鍚嶄笉鑳戒负绌�")
+    private String nickname;
+
+    @NotBlank(message = "璐﹀彿鍚嶄笉鑳戒负绌�")
+    @Pattern(regexp = "^[a-zA-Z0-9_]{4,16}$", message = "璐﹀彿鍚嶅繀椤绘槸4-16浣嶅瓧姣嶃�佹暟瀛楁垨涓嬪垝绾�")
+    private String accountName;
+
+    @NotBlank(message = "瀵嗙爜涓嶈兘涓虹┖")
+    @Pattern(regexp = "^[a-zA-Z0-9_]{6,16}$", message = "瀵嗙爜蹇呴』鏄�6-16浣嶅瓧姣嶃�佹暟瀛楁垨涓嬪垝绾�")
+    private String password;
+} 
\ No newline at end of file
diff --git a/src/main/java/com/weiwojc/model/entity/User.java b/src/main/java/com/weiwojc/model/entity/User.java
new file mode 100644
index 0000000..00411c2
--- /dev/null
+++ b/src/main/java/com/weiwojc/model/entity/User.java
@@ -0,0 +1,51 @@
+package com.weiwojc.model.entity;
+
+import com.baomidou.mybatisplus.annotation.*;
+import lombok.Data;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+
+@Data
+@TableName("user")
+public class User {
+    @TableId(value = "user_id", type = IdType.AUTO)
+    private Long userId;
+    
+    private String uuid;
+    private String username;
+    private String passwordHash;
+    private String passwordSalt;
+    private Boolean mfaEnabled;
+    private String mfaSecret;
+    private String email;
+    private Boolean emailVerified;
+    private String phone;
+    private Boolean phoneVerified;
+    private String realName;
+    private String nickname;
+    private Integer gender;
+    private LocalDate birthdate;
+    private String avatarUrl;
+    private Integer status;
+    
+    @TableLogic
+    private Boolean isDeleted;
+    
+    private LocalDateTime registeredAt;
+    private LocalDateTime lastLogin;
+    private LocalDateTime updatedAt;
+    private String passwordResetToken;
+    private LocalDateTime resetTokenExpire;
+    private String emailVerifyToken;
+    private LocalDateTime verifyTokenExpire;
+    private Integer loginAttempts;
+    private LocalDateTime lockedUntil;
+    private String oauthProvider;
+    private String oauthUid;
+    private String oauthAccessToken;
+    private String oauthRefreshToken;
+    private String countryCode;
+    private String timeZone;
+    private String preferredLanguage;
+    private String metadata;
+} 
\ No newline at end of file
diff --git a/src/main/java/com/weiwojc/security/JwtAuthenticationFilter.java b/src/main/java/com/weiwojc/security/JwtAuthenticationFilter.java
new file mode 100644
index 0000000..25783e8
--- /dev/null
+++ b/src/main/java/com/weiwojc/security/JwtAuthenticationFilter.java
@@ -0,0 +1,57 @@
+package com.weiwojc.security;
+
+import com.weiwojc.utils.JwtUtils;
+import jakarta.servlet.FilterChain;
+import jakarta.servlet.ServletException;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import lombok.RequiredArgsConstructor;
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.security.core.userdetails.UserDetails;
+import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
+import org.springframework.stereotype.Component;
+import org.springframework.web.filter.OncePerRequestFilter;
+
+import java.io.IOException;
+import java.util.ArrayList;
+
+@Component
+@RequiredArgsConstructor
+public class JwtAuthenticationFilter extends OncePerRequestFilter {
+
+    private final JwtUtils jwtUtils;
+
+    @Override
+    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
+            throws ServletException, IOException {
+        
+        String token = request.getHeader("token");
+        
+        if (token == null || token.isEmpty()) {
+            filterChain.doFilter(request, response);
+            return;
+        }
+
+        if (!jwtUtils.validateToken(token)) {
+            filterChain.doFilter(request, response);
+            return;
+        }
+
+        String username = jwtUtils.getUsernameFromToken(token);
+        Long userId = jwtUtils.getUserIdFromToken(token);
+
+        if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
+            UserDetails userDetails = new JwtUserDetails(userId, username);
+            
+            UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(
+                    userDetails, null, new ArrayList<>());
+            
+            authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
+            
+            SecurityContextHolder.getContext().setAuthentication(authentication);
+        }
+
+        filterChain.doFilter(request, response);
+    }
+} 
\ No newline at end of file
diff --git a/src/main/java/com/weiwojc/security/JwtUserDetails.java b/src/main/java/com/weiwojc/security/JwtUserDetails.java
new file mode 100644
index 0000000..3514ba6
--- /dev/null
+++ b/src/main/java/com/weiwojc/security/JwtUserDetails.java
@@ -0,0 +1,54 @@
+package com.weiwojc.security;
+
+import lombok.Getter;
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.core.userdetails.UserDetails;
+
+import java.util.Collection;
+import java.util.Collections;
+
+@Getter
+public class JwtUserDetails implements UserDetails {
+    private final Long userId;
+    private final String username;
+
+    public JwtUserDetails(Long userId, String username) {
+        this.userId = userId;
+        this.username = username;
+    }
+
+    @Override
+    public Collection<? extends GrantedAuthority> getAuthorities() {
+        return Collections.emptyList();
+    }
+
+    @Override
+    public String getPassword() {
+        return null;
+    }
+
+    @Override
+    public String getUsername() {
+        return username;
+    }
+
+    @Override
+    public boolean isAccountNonExpired() {
+        return true;
+    }
+
+    @Override
+    public boolean isAccountNonLocked() {
+        return true;
+    }
+
+    @Override
+    public boolean isCredentialsNonExpired() {
+        return true;
+    }
+
+    @Override
+    public boolean isEnabled() {
+        return true;
+    }
+} 
\ No newline at end of file
diff --git a/src/main/java/com/weiwojc/service/UserService.java b/src/main/java/com/weiwojc/service/UserService.java
new file mode 100644
index 0000000..7297732
--- /dev/null
+++ b/src/main/java/com/weiwojc/service/UserService.java
@@ -0,0 +1,42 @@
+package com.weiwojc.service;
+
+import com.weiwojc.model.dto.UserLoginDTO;
+import com.weiwojc.model.dto.UserRegisterDTO;
+import com.weiwojc.model.entity.User;
+
+public interface UserService {
+    /**
+     * 鐢ㄦ埛娉ㄥ唽
+     */
+    User register(UserRegisterDTO registerDTO);
+
+    /**
+     * 鐢ㄦ埛鐧诲綍
+     */
+    String login(UserLoginDTO loginDTO);
+
+    /**
+     * 鑾峰彇鐢ㄦ埛淇℃伅
+     */
+    User getUserInfo(Long userId);
+
+    /**
+     * 鏇存柊鏈�鍚庣櫥褰曟椂闂�
+     */
+    void updateLastLogin(Long userId);
+
+    /**
+     * 妫�鏌ョ敤鎴锋槸鍚﹁閿佸畾
+     */
+    boolean isUserLocked(Long userId);
+
+    /**
+     * 澧炲姞鐧诲綍澶辫触娆℃暟
+     */
+    void incrementLoginAttempts(String username);
+
+    /**
+     * 閲嶇疆鐧诲綍澶辫触娆℃暟
+     */
+    void resetLoginAttempts(String username);
+} 
\ No newline at end of file
diff --git a/src/main/java/com/weiwojc/service/impl/UserServiceImpl.java b/src/main/java/com/weiwojc/service/impl/UserServiceImpl.java
new file mode 100644
index 0000000..50455cb
--- /dev/null
+++ b/src/main/java/com/weiwojc/service/impl/UserServiceImpl.java
@@ -0,0 +1,140 @@
+package com.weiwojc.service.impl;
+
+import com.weiwojc.mapper.UserMapper;
+import com.weiwojc.model.dto.UserLoginDTO;
+import com.weiwojc.model.dto.UserRegisterDTO;
+import com.weiwojc.model.entity.User;
+import com.weiwojc.service.UserService;
+import com.weiwojc.utils.JwtUtils;
+import com.weiwojc.utils.PasswordUtils;
+import lombok.RequiredArgsConstructor;
+import org.springframework.security.authentication.BadCredentialsException;
+import org.springframework.security.authentication.LockedException;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.time.LocalDateTime;
+import java.util.UUID;
+
+@Service
+@RequiredArgsConstructor
+public class UserServiceImpl implements UserService {
+
+    private final UserMapper userMapper;
+    private final JwtUtils jwtUtils;
+
+    @Override
+    @Transactional
+    public User register(UserRegisterDTO registerDTO) {
+        // 妫�鏌ヨ处鍙峰悕鏄惁宸插瓨鍦�
+        User existingUser = userMapper.findByUsername(registerDTO.getAccountName());
+        if (existingUser != null) {
+            throw new RuntimeException("璐﹀彿鍚嶅凡瀛樺湪");
+        }
+
+        // 鍒涘缓鏂扮敤鎴�
+        User user = new User();
+        user.setUuid(UUID.randomUUID().toString());
+        user.setUsername(registerDTO.getAccountName());
+        user.setNickname(registerDTO.getNickname());
+        
+        // 鐢熸垚鍔犲瘑瀵嗙爜
+        String salt = PasswordUtils.generateSalt();
+        String hashedPassword = PasswordUtils.hashPassword(registerDTO.getPassword(), salt);
+        user.setPasswordHash(hashedPassword);
+        user.setPasswordSalt(salt);  // 淇濆瓨鐩愬��
+
+        // 璁剧疆鍏朵粬瀛楁
+        user.setStatus(1); // 姝e父鐘舵��
+        user.setRegisteredAt(LocalDateTime.now());
+        user.setUpdatedAt(LocalDateTime.now());
+
+        // 淇濆瓨鐢ㄦ埛
+        userMapper.insert(user);
+        return user;
+    }
+
+    @Override
+    public String login(UserLoginDTO loginDTO) {
+        User user = userMapper.findByUsername(loginDTO.getAccountName());
+
+        if (user == null) {
+            throw new BadCredentialsException("璐﹀彿鍚嶆垨瀵嗙爜閿欒");
+        }
+
+        // 妫�鏌ヨ处鎴风姸鎬�
+        if (user.getStatus() == 0) {
+            throw new LockedException("璐︽埛宸茶绂佺敤");
+        }
+
+        // 妫�鏌ユ槸鍚﹁閿佸畾
+        if (isUserLocked(user.getUserId())) {
+            throw new LockedException("璐︽埛宸茶閿佸畾锛岃绋嶅悗鍐嶈瘯");
+        }
+
+        // 楠岃瘉瀵嗙爜
+        if (!PasswordUtils.verifyPassword(loginDTO.getPassword(), user.getPasswordHash())) {
+            // 澧炲姞鐧诲綍澶辫触娆℃暟
+            incrementLoginAttempts(user.getUsername());
+            throw new BadCredentialsException("璐﹀彿鍚嶆垨瀵嗙爜閿欒");
+        }
+
+        // 閲嶇疆鐧诲綍澶辫触娆℃暟
+        resetLoginAttempts(user.getUsername());
+
+        // 鏇存柊鏈�鍚庣櫥褰曟椂闂�
+        updateLastLogin(user.getUserId());
+
+        // 鐢熸垚JWT浠ょ墝
+        return jwtUtils.generateToken(user);
+    }
+
+    @Override
+    public User getUserInfo(Long userId) {
+        return userMapper.findById(userId);
+    }
+
+    @Override
+    public void updateLastLogin(Long userId) {
+        userMapper.updateLastLogin(userId, LocalDateTime.now());
+    }
+
+    @Override
+    public boolean isUserLocked(Long userId) {
+        User user = userMapper.findById(userId);
+        if (user == null) {
+            return false;
+        }
+
+        // 妫�鏌ヨ处鎴烽攣瀹氱姸鎬�
+        if (user.getLockedUntil() != null && 
+            LocalDateTime.now().isBefore(user.getLockedUntil())) {
+            return true;
+        }
+
+        return false;
+    }
+
+    @Override
+    public void incrementLoginAttempts(String username) {
+        User user = userMapper.findByUsername(username);
+
+        if (user != null) {
+            int attempts = user.getLoginAttempts() == null ? 0 : user.getLoginAttempts();
+            attempts++;
+
+            LocalDateTime lockedUntil = null;
+            // 濡傛灉澶辫触娆℃暟杈惧埌5娆★紝閿佸畾30鍒嗛挓
+            if (attempts >= 5) {
+                lockedUntil = LocalDateTime.now().plusMinutes(30);
+            }
+
+            userMapper.updateLoginAttempts(user.getUserId(), attempts, lockedUntil);
+        }
+    }
+
+    @Override
+    public void resetLoginAttempts(String username) {
+        userMapper.resetLoginAttempts(username);
+    }
+} 
\ No newline at end of file
diff --git a/src/main/java/com/weiwojc/utils/JwtUtils.java b/src/main/java/com/weiwojc/utils/JwtUtils.java
new file mode 100644
index 0000000..4d4b673
--- /dev/null
+++ b/src/main/java/com/weiwojc/utils/JwtUtils.java
@@ -0,0 +1,109 @@
+package com.weiwojc.utils;
+
+import com.weiwojc.model.entity.User;
+import io.jsonwebtoken.Claims;
+import io.jsonwebtoken.Jwts;
+import io.jsonwebtoken.security.Keys;
+import lombok.RequiredArgsConstructor;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Component;
+
+import javax.crypto.SecretKey;
+import java.nio.charset.StandardCharsets;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
+
+@Component
+@RequiredArgsConstructor
+public class JwtUtils {
+
+    private static final long SERVER_START_TIME = System.currentTimeMillis();
+
+    @Value("${jwt.secret}")
+    private String secret;
+
+    @Value("${jwt.expiration}")
+    private Long expiration;
+
+    private final TokenBlacklistManager tokenBlacklistManager;
+
+    private SecretKey getSigningKey() {
+        byte[] keyBytes = secret.getBytes(StandardCharsets.UTF_8);
+        return Keys.hmacShaKeyFor(keyBytes);
+    }
+
+    public String generateToken(User user) {
+        Map<String, Object> claims = new HashMap<>();
+        claims.put("userId", user.getUserId());
+        claims.put("username", user.getUsername());
+        claims.put("instanceId", tokenBlacklistManager.getInstanceId());
+        claims.put("serverStartTime", SERVER_START_TIME);
+        
+        return Jwts.builder()
+                .setClaims(claims)
+                .setSubject(user.getUsername())
+                .setIssuedAt(new Date())
+                .setExpiration(new Date(System.currentTimeMillis() + expiration * 1000))
+                .signWith(getSigningKey())
+                .compact();
+    }
+
+    public Claims getClaimsFromToken(String token) {
+        return Jwts.parserBuilder()
+                .setSigningKey(getSigningKey())
+                .build()
+                .parseClaimsJws(token)
+                .getBody();
+    }
+
+    public String getUsernameFromToken(String token) {
+        return getClaimsFromToken(token).getSubject();
+    }
+
+    public Long getUserIdFromToken(String token) {
+        return getClaimsFromToken(token).get("userId", Long.class);
+    }
+
+    public String getInstanceIdFromToken(String token) {
+        return getClaimsFromToken(token).get("instanceId", String.class);
+    }
+
+    public Date getExpirationDateFromToken(String token) {
+        return getClaimsFromToken(token).getExpiration();
+    }
+
+    public boolean isTokenExpired(String token) {
+        Date expiration = getExpirationDateFromToken(token);
+        return expiration.before(new Date());
+    }
+
+    public boolean validateToken(String token) {
+        try {
+            Claims claims = getClaimsFromToken(token);
+            
+            if (isTokenExpired(token)) {
+                return false;
+            }
+            
+            String tokenInstanceId = claims.get("instanceId", String.class);
+            String currentInstanceId = tokenBlacklistManager.getInstanceId();
+            if (!currentInstanceId.equals(tokenInstanceId)) {
+                return false;
+            }
+
+            Long tokenServerStartTime = claims.get("serverStartTime", Long.class);
+            if (tokenServerStartTime == null || tokenServerStartTime != SERVER_START_TIME) {
+                return false;
+            }
+
+            if (tokenBlacklistManager.isBlacklisted(token)) {
+                return false;
+            }
+
+            return true;
+        } catch (Exception e) {
+            return false;
+        }
+    }
+} 
\ No newline at end of file
diff --git a/src/main/java/com/weiwojc/utils/PasswordUtils.java b/src/main/java/com/weiwojc/utils/PasswordUtils.java
new file mode 100644
index 0000000..9bc1300
--- /dev/null
+++ b/src/main/java/com/weiwojc/utils/PasswordUtils.java
@@ -0,0 +1,36 @@
+package com.weiwojc.utils;
+
+import org.apache.commons.lang3.RandomStringUtils;
+import org.springframework.security.crypto.bcrypt.BCrypt;
+
+public class PasswordUtils {
+    
+    /**
+     * 鐢熸垚闅忔満鐩愬��
+     */
+    public static String generateSalt() {
+        return BCrypt.gensalt();
+    }
+
+    /**
+     * 浣跨敤鐩愬�煎姞瀵嗗瘑鐮�
+     */
+    public static String hashPassword(String password, String salt) {
+        return BCrypt.hashpw(password, salt);
+    }
+
+    /**
+     * 楠岃瘉瀵嗙爜
+     */
+    public static boolean verifyPassword(String password, String hashedPassword) {
+        return BCrypt.checkpw(password, hashedPassword);
+    }
+
+    /**
+     * 浠嶣Crypt鍝堝笇鍊间腑鎻愬彇鐩愬��
+     */
+    public static String extractSaltFromHash(String hashedPassword) {
+        // BCrypt鍝堝笇鐨勫墠29涓瓧绗﹀氨鏄洂鍊�
+        return hashedPassword.substring(0, 29);
+    }
+} 
\ No newline at end of file
diff --git a/src/main/java/com/weiwojc/utils/TokenBlacklistManager.java b/src/main/java/com/weiwojc/utils/TokenBlacklistManager.java
new file mode 100644
index 0000000..a317a14
--- /dev/null
+++ b/src/main/java/com/weiwojc/utils/TokenBlacklistManager.java
@@ -0,0 +1,35 @@
+package com.weiwojc.utils;
+
+import org.springframework.stereotype.Component;
+import jakarta.annotation.PostConstruct;
+import java.util.concurrent.ConcurrentHashMap;
+
+@Component
+public class TokenBlacklistManager {
+    private static final ConcurrentHashMap<String, Boolean> tokenBlacklist = new ConcurrentHashMap<>();
+    private static String INSTANCE_ID = null;
+    private static long START_TIME;
+
+    @PostConstruct
+    public void init() {
+        // 姣忔鏈嶅姟鍚姩鐢熸垚鏂扮殑瀹炰緥ID鍜屽惎鍔ㄦ椂闂�
+        INSTANCE_ID = java.util.UUID.randomUUID().toString();
+        START_TIME = System.currentTimeMillis();
+    }
+
+    public void addToBlacklist(String token) {
+        tokenBlacklist.put(token, true);
+    }
+
+    public boolean isBlacklisted(String token) {
+        return tokenBlacklist.containsKey(token);
+    }
+
+    public String getInstanceId() {
+        return INSTANCE_ID;
+    }
+
+    public long getStartTime() {
+        return START_TIME;
+    }
+} 
\ No newline at end of file
diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml
index 8674f9e..24b99e8 100644
--- a/src/main/resources/application.yml
+++ b/src/main/resources/application.yml
@@ -1,15 +1,36 @@
 server:
   port: 8080
-  servlet:
-    context-path: /api
 
 spring:
   application:
     name: weiwojc
-  profiles:
-    active: dev
+  datasource:
+    driver-class-name: com.mysql.cj.jdbc.Driver
+    url: jdbc:mysql://121.43.139.99:13306/wwjc?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai
+    username: root
+    password: k6R8NeFcBfec55rc
+    type: com.zaxxer.hikari.HikariDataSource
+    hikari:
+      minimum-idle: 5
+      maximum-pool-size: 15
+      auto-commit: true
+      idle-timeout: 30000
+      pool-name: HikariCP
+      max-lifetime: 1800000
+      connection-timeout: 30000
+      connection-test-query: SELECT 1
+
+mybatis:
+  configuration:
+    map-underscore-to-camel-case: true
+    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
+
+jwt:
+  secret: your-256-bit-secret-key-here-must-be-longer-than-256-bits
+  expiration: 1800  # 30鍒嗛挓杩囨湡
 
 logging:
   level:
     root: INFO
-    com.weiwojc: DEBUG 
\ No newline at end of file
+    com.weiwojc: DEBUG
+    org.mybatis: DEBUG 
\ No newline at end of file

--
Gitblit v1.9.3