从零开始一个完整的全栈项目(11) - 前端用户管理功能的后端实现及Postman验证

147 阅读4分钟

用户管理功能主要包括了:

  • 用户列表展示
  • 用户编辑
  • 用户删除

注意:修改密码功能将在下一篇完成,其涉及到“管理员设置默认密码”和“用户修改密码”。(“用户找回密码”功能暂时不做。)

1. 完成后端代码

先完成后端api,然后使用Postman进行测试。

因为本次涉及好几个功能的实现和改动。所以先把完成后的最终代码放在文章最前面。

1. 创建一个用户编辑的DTO(UserUpdateRequest.java

package com.quickstore.dto;

import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Pattern;
import jakarta.validation.constraints.Size;

public class UserUpdateRequest {
    
    @NotBlank(message = "Full name is required")
    @Size(min = 2, max = 100, message = "Full name must be between 2 and 100 characters")
    private String fullName;

    @Pattern(regexp = "^(admin|staff|warehouse)$", message = "Role must be one of: admin, staff, warehouse")
    private String role;

    // Getters and Setters
    public String getFullName() {
        return fullName;
    }

    public void setFullName(String fullName) {
        this.fullName = fullName;
    }

    public String getRole() {
        return role;
    }

    public void setRole(String role) {
        this.role = role;
    }
} 

2. 在UserService中添加新的方法

package com.quickstore.service;

import com.quickstore.model.User;
import org.springframework.security.core.userdetails.UserDetailsService;
import java.util.List;

public interface UserService extends UserDetailsService {
    User findByUsername(String username);
    User save(User user);
    List<User> findAllUsers();
    User findById(Long id);
    void deleteUser(Long id);
} 

3. 在UserServiceImpl中实现这些方法

package com.quickstore.service.impl;

import com.quickstore.model.User;
import com.quickstore.repository.UserRepository;
import com.quickstore.service.UserService;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;

import java.util.Collections;
import java.util.List;

@Service
public class UserServiceImpl implements UserService {

    private final UserRepository userRepository;

    public UserServiceImpl(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        User user = findByUsername(username);
        if (user == null) {
            throw new UsernameNotFoundException("User not found with username: " + username);
        }
        return new org.springframework.security.core.userdetails.User(
                user.getUsername(),
                user.getPasswordHash(),
                Collections.singletonList(new SimpleGrantedAuthority("ROLE_" + user.getRole().toUpperCase()))
        );
    }

    @Override
    public User findByUsername(String username) {
        return userRepository.findByUsername(username);
    }

    @Override
    public User save(User user) {
        if (user.getRole() != null) {
            user.setRole(user.getRole().toLowerCase());
        }
        return userRepository.save(user);
    }

    @Override
    public List<User> findAllUsers() {
        return userRepository.findAll();
    }

    @Override
    public User findById(Long id) {
        return userRepository.findById(id).orElse(null);
    }

    @Override
    public void deleteUser(Long id) {
        userRepository.deleteById(id);
    }
} 

4. 创建用户管理器(UserController.java

用户管理器里面包含了“列出所有用户”“编辑用户”“删除用户”等功能。暂定只有具有“admin”权限的人才能执行这些操作。

package com.quickstore.controller;

import com.quickstore.dto.UserUpdateRequest;
import com.quickstore.model.User;
import com.quickstore.service.UserService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@RestController
@RequestMapping("/users")
public class UserController {

    private static final Logger logger = LoggerFactory.getLogger(UserController.class);
    private final UserService userService;

    public UserController(UserService userService) {
        this.userService = userService;
    }

    @GetMapping
    @PreAuthorize("hasRole('ADMIN')")
    public ResponseEntity<List<User>> getAllUsers() {
        logger.info("Attempting to get all users");
        List<User> users = userService.findAllUsers();
        logger.info("Found {} users", users.size());
        return ResponseEntity.ok(users);
    }

    @PutMapping("/{id}")
    @PreAuthorize("hasRole('ADMIN')")
    public ResponseEntity<?> updateUser(@PathVariable Long id, @RequestBody UserUpdateRequest request) {
        logger.info("Attempting to update user with id: {}", id);
        
        User user = userService.findById(id);
        if (user == null) {
            logger.warn("User not found with id: {}", id);
            return ResponseEntity.notFound().build();
        }

        // 更新用户信息
        user.setFullName(request.getFullName());
        user.setRole(request.getRole().toLowerCase());

        // 保存更新
        userService.save(user);
        logger.info("User updated successfully: {}", user.getUsername());

        return ResponseEntity.ok(user);
    }

    @DeleteMapping("/{id}")
    @PreAuthorize("hasRole('ADMIN')")
    public ResponseEntity<?> deleteUser(@PathVariable Long id) {
        logger.info("Attempting to delete user with id: {}", id);
        
        User user = userService.findById(id);
        if (user == null) {
            logger.warn("User not found with id: {}", id);
            return ResponseEntity.notFound().build();
        }

        userService.deleteUser(id);
        logger.info("User deleted successfully: {}", user.getUsername());
        
        return ResponseEntity.ok().build();
    }
} 

5. 修改AuthContuoller的部分代码

package com.quickstore.controller;

import com.quickstore.dto.LoginRequest;
import com.quickstore.dto.LoginResponse;
import com.quickstore.dto.RegisterRequest;
import com.quickstore.model.User;
import com.quickstore.security.JwtTokenProvider;
import com.quickstore.service.UserService;
import jakarta.validation.Valid;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.web.bind.annotation.*;

import java.util.Collections;

@RestController
@RequestMapping("/auth")
public class AuthController {
    private static final Logger logger = LoggerFactory.getLogger(AuthController.class);

    private final UserService userService;
    private final PasswordEncoder passwordEncoder;
    private final JwtTokenProvider tokenProvider;

    public AuthController(UserService userService, PasswordEncoder passwordEncoder, JwtTokenProvider tokenProvider) {
        this.userService = userService;
        this.passwordEncoder = passwordEncoder;
        this.tokenProvider = tokenProvider;
    }

    @PostMapping("/register")
    public ResponseEntity<?> register(@Valid @RequestBody RegisterRequest registerRequest) {
        logger.info("Attempting to register new user: {}", registerRequest.getUsername());

        // 检查用户名是否已存在
        if (userService.findByUsername(registerRequest.getUsername()) != null) {
            logger.warn("Registration failed: Username {} already exists", registerRequest.getUsername());
            return ResponseEntity.badRequest().body("Username already exists");
        }

        // 创建新用户
        User user = new User();
        user.setUsername(registerRequest.getUsername());
        user.setPasswordHash(passwordEncoder.encode(registerRequest.getPassword()));
        user.setFullName(registerRequest.getFullName());
        user.setRole(registerRequest.getRole().toLowerCase()); // 确保角色是小写的

        // 保存用户
        userService.save(user);
        logger.info("User registered successfully: {}", user.getUsername());

        return ResponseEntity.ok("User registered successfully");
    }

    @PostMapping("/login")
    public ResponseEntity<LoginResponse> login(@Valid @RequestBody LoginRequest loginRequest) {
        logger.info("Attempting login for user: {}", loginRequest.getUsername());
        
        User user = userService.findByUsername(loginRequest.getUsername());
        
        if (user != null && passwordEncoder.matches(loginRequest.getPassword(), user.getPasswordHash())) {
            logger.info("Login successful for user: {}", user.getUsername());
            
            UserDetails userDetails = new org.springframework.security.core.userdetails.User(
                    user.getUsername(),
                    user.getPasswordHash(),
                    Collections.singletonList(new org.springframework.security.core.authority.SimpleGrantedAuthority("ROLE_" + user.getRole().toUpperCase()))
            );
            
            String token = tokenProvider.generateToken(userDetails);
            return ResponseEntity.ok(new LoginResponse(token, user.getUsername(), user.getRole()));
        }
        
        logger.warn("Login failed for user: {}", loginRequest.getUsername());
        return ResponseEntity.badRequest().body(null);
    }
} 

6. 修改SecurityConfig的部分代码

package com.quickstore.config;

import com.quickstore.security.JwtAuthenticationFilter;
import com.quickstore.security.JwtTokenProvider;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
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.core.userdetails.UserDetailsService;
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;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;

import java.util.Arrays;

@Configuration
@EnableWebSecurity
@EnableMethodSecurity
public class SecurityConfig {

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http, JwtTokenProvider tokenProvider, UserDetailsService userDetailsService) throws Exception {
        http
            .cors().configurationSource(corsConfigurationSource())
            .and()
            .csrf(AbstractHttpConfigurer::disable)
            .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
            .authorizeHttpRequests(auth -> auth
                .requestMatchers("/auth/**").permitAll()
                .anyRequest().authenticated()
            )
            .addFilterBefore(new JwtAuthenticationFilter(tokenProvider, userDetailsService), 
                            UsernamePasswordAuthenticationFilter.class);

        return http.build();
    }

    @Bean
    public CorsConfigurationSource corsConfigurationSource() {
        CorsConfiguration configuration = new CorsConfiguration();
        configuration.setAllowedOrigins(Arrays.asList("http://localhost:3000"));
        configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE", "OPTIONS"));
        configuration.setAllowedHeaders(Arrays.asList("*"));
        configuration.setAllowCredentials(true);
        
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", configuration);
        return source;
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
}

2. 用Postman进行测试

1. 用户列表展示/查询所有用户(需要admin权限)

1. 登录:用Postman使用某个具有“admin”权限的账号进行登录,获得JWT返回的token。

以便模拟“具有admin权限的某个账号正在查询所有用户

步骤:

  1. 使用“POST”方式访问:http://localhost:8080/api/auth/login
  2. 在“Header”中选择“Content-Type:application/json
  3. 在"Body"中选择raw,JSON
  4. Body正文为:{ "username": "admin", "password": "admin123" }

结果:

Header(Description等不用填):

image.png

其他步骤及拿到token: image.png

2. 查询:携带token进行“查询”

步骤:

  1. 使用“GET”方式访问:http://localhost:8080/api/users
  2. 在“Header”中选择“Authorization: Bearer {jwt_token}” (此处将刚才复制的token粘贴上去)
  3. 在"Body"中选择"none" (因为我们不发送信息到后端)

3. 结果:

image.png

返回“200”状态,得到包含所有用户的JSON字符串。完成!

2. 用户编辑(需要admin权限)

1. 登录(拿到token)

同上。

2. 用户编辑

  1. 发送PUT请求到:http://localhost:8080/api/users/{用户ID}
  2. 请求头(Header)为:
     Authorization: Bearer {你的token}
     Content-Type: application/json
  1. 请求体(Body)为:
   {
       "fullName": "新的全名",
       "role": "admin"  // 可选值:admin, staff, warehouse
   }

3. 结果

Header的设置:

image.png Body部分及拿到的结果:

image.png 完成!

3. 用户删除

1. 登录

同上。

2. 用户删除

  1. 发送DELETE请求到:http://localhost:8080/api/users/{用户ID}
  2. 用户头(Header)设置为:
Authorization: Bearer {你的token}
  1. 用户体(Body)部分设置为:none

3. 结果:

Header的设置:

image.png Body部分以及最终的结果:

image.png 返回200状态,成功!


至此,用户管理功能的后端项目代码,完成!


下一篇,完成用户管理功能的前端页面部分。