用户管理功能主要包括了:
- 用户列表展示
- 用户编辑
- 用户删除
注意:修改密码功能将在下一篇完成,其涉及到“管理员设置默认密码”和“用户修改密码”。(“用户找回密码”功能暂时不做。)
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权限的某个账号正在查询所有用户”
步骤:
- 使用“POST”方式访问:http://localhost:8080/api/auth/login
- 在“Header”中选择“
Content-Type:application/json” - 在"Body"中选择raw,JSON
- Body正文为:
{ "username": "admin", "password": "admin123" }
结果:
Header(Description等不用填):
其他步骤及拿到token:
2. 查询:携带token进行“查询”
步骤:
- 使用“GET”方式访问:http://localhost:8080/api/users
- 在“Header”中选择“
Authorization: Bearer {jwt_token}” (此处将刚才复制的token粘贴上去) - 在"Body"中选择"none" (因为我们不发送信息到后端)
3. 结果:
返回“200”状态,得到包含所有用户的JSON字符串。完成!
2. 用户编辑(需要admin权限)
1. 登录(拿到token)
同上。
2. 用户编辑
- 发送PUT请求到:http://localhost:8080/api/users/{用户ID}
- 请求头(Header)为:
Authorization: Bearer {你的token}
Content-Type: application/json
- 请求体(Body)为:
{
"fullName": "新的全名",
"role": "admin" // 可选值:admin, staff, warehouse
}
3. 结果
Header的设置:
Body部分及拿到的结果:
完成!
3. 用户删除
1. 登录
同上。
2. 用户删除
- 发送DELETE请求到:http://localhost:8080/api/users/{用户ID}
- 用户头(Header)设置为:
Authorization: Bearer {你的token}
- 用户体(Body)部分设置为:none
3. 结果:
Header的设置:
Body部分以及最终的结果:
返回200状态,成功!
至此,用户管理功能的后端项目代码,完成!
下一篇,完成用户管理功能的前端页面部分。