Files
ssh-manager/backend/src/main/java/com/sshmanager/service/UserService.java
T
liumangmang 1f1d1db65a feat: 多用户管理与公开注册功能
- 后端:User 实体新增 role/enabled 字段、UserController CRUD、UserService
- 安全:SecurityConfig /api/users/** 要求 ROLE_ADMIN、JWT 过滤器检查账号状态
- 注册:POST /api/auth/register 公开注册,固定 ROLE_USER
- 保护:删除/禁用/降级最后 admin 均拒绝,DataInitializer 含 backfill
- 前端:用户管理页面、登录/注册切换、admin 专属导航入口
- 测试:UserServiceTest 19 个 + UserControllerTest 6 个 + AuthControllerTest 适配
2026-05-28 09:13:27 +08:00

181 lines
7.1 KiB
Java

package com.sshmanager.service;
import com.sshmanager.dto.CreateUserRequest;
import com.sshmanager.dto.UpdateUserRequest;
import com.sshmanager.dto.UserDto;
import com.sshmanager.entity.User;
import com.sshmanager.exception.InvalidOperationException;
import com.sshmanager.exception.NotFoundException;
import com.sshmanager.repository.UserRepository;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import java.time.Instant;
import java.util.List;
import java.util.stream.Collectors;
@Service
public class UserService {
private final UserRepository userRepository;
private final PasswordEncoder passwordEncoder;
public UserService(UserRepository userRepository, PasswordEncoder passwordEncoder) {
this.userRepository = userRepository;
this.passwordEncoder = passwordEncoder;
}
public List<UserDto> listUsers() {
return userRepository.findAll().stream()
.map(this::toDto)
.collect(Collectors.toList());
}
public UserDto getUser(Long id) {
User user = userRepository.findById(id)
.orElseThrow(() -> new NotFoundException("User not found: " + id));
return toDto(user);
}
public UserDto createUser(CreateUserRequest request) {
if (request.getUsername() == null || request.getUsername().trim().isEmpty()) {
throw new InvalidOperationException("Username is required");
}
if (request.getPassword() == null || request.getPassword().length() < 8) {
throw new InvalidOperationException("Password must be at least 8 characters");
}
if (userRepository.existsByUsername(request.getUsername().trim())) {
throw new InvalidOperationException("Username already exists: " + request.getUsername());
}
User user = new User();
user.setUsername(request.getUsername().trim());
user.setPasswordHash(passwordEncoder.encode(request.getPassword()));
user.setDisplayName(request.getDisplayName() != null ? request.getDisplayName().trim() : request.getUsername().trim());
user.setRole(request.getRole() != null ? request.getRole() : "ROLE_USER");
if (!"ROLE_ADMIN".equals(user.getRole()) && !"ROLE_USER".equals(user.getRole())) {
user.setRole("ROLE_USER");
}
user.setEnabled(true);
user.setPasswordChangedAt(Instant.now());
user.setCreatedAt(Instant.now());
user.setUpdatedAt(Instant.now());
User saved = userRepository.save(user);
return toDto(saved);
}
public UserDto updateUser(Long id, UpdateUserRequest request, Long currentUserId) {
User user = userRepository.findById(id)
.orElseThrow(() -> new NotFoundException("User not found: " + id));
if (request.getDisplayName() != null) {
user.setDisplayName(request.getDisplayName().trim());
}
if (request.getRole() != null) {
if ("ROLE_ADMIN".equals(request.getRole()) || "ROLE_USER".equals(request.getRole())) {
// Cannot change the last admin to a regular user
if ("ROLE_ADMIN".equals(user.getRole()) && "ROLE_USER".equals(request.getRole()) && isLastAdmin(id)) {
throw new InvalidOperationException("Cannot change the last admin user to a regular user");
}
user.setRole(request.getRole());
}
}
if (request.getEnabled() != null) {
// Cannot disable yourself
if (user.getId().equals(currentUserId) && !request.getEnabled()) {
throw new InvalidOperationException("You cannot disable your own account");
}
// Cannot disable the last admin
if (!request.getEnabled() && "ROLE_ADMIN".equals(user.getRole()) && isLastAdmin(id)) {
throw new InvalidOperationException("Cannot disable the last admin user");
}
user.setEnabled(request.getEnabled());
}
user.setUpdatedAt(Instant.now());
User saved = userRepository.save(user);
return toDto(saved);
}
public void deleteUser(Long id, Long currentUserId) {
if (id.equals(currentUserId)) {
throw new InvalidOperationException("You cannot delete your own account");
}
User user = userRepository.findById(id)
.orElseThrow(() -> new NotFoundException("User not found: " + id));
// Cannot delete the last admin
if ("ROLE_ADMIN".equals(user.getRole()) && isLastAdmin(id)) {
throw new InvalidOperationException("Cannot delete the last admin user");
}
userRepository.delete(user);
}
public void resetPassword(Long id, String newPassword) {
if (newPassword == null || newPassword.length() < 8) {
throw new InvalidOperationException("New password must be at least 8 characters");
}
User user = userRepository.findById(id)
.orElseThrow(() -> new NotFoundException("User not found: " + id));
user.setPasswordHash(passwordEncoder.encode(newPassword));
user.setPasswordChangedAt(Instant.EPOCH);
user.setUpdatedAt(Instant.now());
userRepository.save(user);
}
public UserDto createRegularUser(String username, String password, String displayName) {
if (username == null || username.trim().isEmpty()) {
throw new InvalidOperationException("Username is required");
}
if (password == null || password.length() < 8) {
throw new InvalidOperationException("Password must be at least 8 characters");
}
if (userRepository.existsByUsername(username.trim())) {
throw new InvalidOperationException("Username already exists: " + username);
}
User user = new User();
user.setUsername(username.trim());
user.setPasswordHash(passwordEncoder.encode(password));
user.setDisplayName(displayName != null && !displayName.trim().isEmpty()
? displayName.trim() : username.trim());
user.setRole("ROLE_USER");
user.setEnabled(true);
user.setPasswordChangedAt(Instant.now());
user.setCreatedAt(Instant.now());
user.setUpdatedAt(Instant.now());
User saved = userRepository.save(user);
return toDto(saved);
}
private boolean isLastAdmin(Long id) {
User user = userRepository.findById(id).orElse(null);
if (user == null || !"ROLE_ADMIN".equals(user.getRole())) {
return false;
}
long adminCount = userRepository.findAll().stream()
.filter(u -> "ROLE_ADMIN".equals(u.getRole()))
.count();
return adminCount <= 1;
}
private UserDto toDto(User user) {
return new UserDto(
user.getId(),
user.getUsername(),
user.getDisplayName(),
user.getRole(),
user.getEnabled(),
user.getCreatedAt(),
user.getUpdatedAt(),
user.getPasswordChangedAt()
);
}
}