Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Личный кабинет: архитектура и реализация
В рамках разработки личного кабинета я работал над несколькими ключевыми компонентами системы, которые являются критичными для удобства пользователя и безопасности его данных.
Основные функции
Личный кабинет предоставляет пользователю централизованное место для управления профилем и всеми аспектами его взаимодействия с платформой:
- Просмотр и редактирование профиля — изменение личных данных, аватара, контактной информации
- Управление учётными записями — смена пароля, двухфакторная аутентификация, управление сеансами
- История действий — просмотр истории входов, действий в системе, истории платежей
- Уведомления и настройки — управление email-уведомлениями, SMS-оповещениями, настройками конфиденциальности
Архитектура реализации
Реализация основана на многослойной архитектуре (clean architecture):
@RestController
@RequestMapping("/api/v1/user/profile")
public class UserProfileController {
private final UserProfileService userProfileService;
private final JwtTokenProvider tokenProvider;
@GetMapping
public ResponseEntity<UserProfileDTO> getProfile(
@AuthenticationPrincipal UserDetails userDetails) {
return ResponseEntity.ok(
userProfileService.getUserProfile(userDetails.getUsername())
);
}
@PutMapping
public ResponseEntity<UserProfileDTO> updateProfile(
@RequestBody UpdateProfileRequest request,
@AuthenticationPrincipal UserDetails userDetails) {
return ResponseEntity.ok(
userProfileService.updateUserProfile(userDetails.getUsername(), request)
);
}
}
Уровень сервиса
Основная бизнес-логика расположена в service слое:
@Service
@Transactional
public class UserProfileService {
private final UserRepository userRepository;
private final PasswordEncoder passwordEncoder;
private final EmailService emailService;
public UserProfileDTO getUserProfile(String username) {
User user = userRepository.findByUsername(username)
.orElseThrow(() -> new UserNotFoundException("User not found"));
return UserProfileMapper.toDTO(user);
}
public UserProfileDTO updateUserProfile(String username, UpdateProfileRequest request) {
User user = userRepository.findByUsername(username)
.orElseThrow(() -> new UserNotFoundException("User not found"));
user.setFirstName(request.getFirstName());
user.setLastName(request.getLastName());
user.setEmail(request.getEmail());
User savedUser = userRepository.save(user);
emailService.sendProfileUpdateNotification(user.getEmail());
return UserProfileMapper.toDTO(savedUser);
}
public void changePassword(String username, ChangePasswordRequest request) {
User user = userRepository.findByUsername(username)
.orElseThrow(() -> new UserNotFoundException("User not found"));
if (!passwordEncoder.matches(request.getOldPassword(), user.getPassword())) {
throw new InvalidPasswordException("Old password is incorrect");
}
user.setPassword(passwordEncoder.encode(request.getNewPassword()));
userRepository.save(user);
emailService.sendPasswordChangeNotification(user.getEmail());
}
}
Безопасность и аутентификация
При работе с личным кабинетом я уделяю особое внимание безопасности данных:
- JWT токены — используются для stateless аутентификации
- Refresh tokens — хранятся в базе с таймаутом, для предотвращения перехвата
- HTTPS — все коммуникации защищены шифрованием
- CSRF защита — для POST/PUT/DELETE операций
- Валидация входных данных — на уровне контроллера и сервиса
@Service
public class AuthenticationService {
private final JwtTokenProvider tokenProvider;
private final RefreshTokenRepository refreshTokenRepository;
public AuthResponse authenticate(LoginRequest request) {
UserDetails userDetails = loadUserDetails(request.getUsername());
validateCredentials(request.getPassword(), userDetails.getPassword());
String accessToken = tokenProvider.generateToken(userDetails);
String refreshToken = generateRefreshToken(userDetails.getUsername());
return new AuthResponse(accessToken, refreshToken);
}
public AuthResponse refreshAccessToken(String refreshToken) {
if (!tokenProvider.validateToken(refreshToken)) {
throw new InvalidTokenException("Refresh token expired");
}
String username = tokenProvider.getUsernameFromToken(refreshToken);
UserDetails userDetails = loadUserDetails(username);
String newAccessToken = tokenProvider.generateToken(userDetails);
return new AuthResponse(newAccessToken, refreshToken);
}
}
Управление сеансами
Реализована система управления активными сеансами пользователя:
@Service
public class SessionManagementService {
private final SessionRepository sessionRepository;
public void createSession(String username, String deviceInfo, String ipAddress) {
Session session = new Session();
session.setUsername(username);
session.setDeviceInfo(deviceInfo);
session.setIpAddress(ipAddress);
session.setLoginTime(LocalDateTime.now());
session.setExpiryTime(LocalDateTime.now().plusHours(24));
sessionRepository.save(session);
}
public List<SessionDTO> getActiveSessions(String username) {
return sessionRepository.findByUsernameAndExpiryTimeAfter(
username, LocalDateTime.now()
).stream()
.map(SessionMapper::toDTO)
.collect(Collectors.toList());
}
public void terminateSession(String sessionId, String username) {
Session session = sessionRepository.findById(sessionId)
.orElseThrow(() -> new SessionNotFoundException("Session not found"));
if (!session.getUsername().equals(username)) {
throw new UnauthorizedException("Cannot terminate other user's session");
}
sessionRepository.delete(session);
}
}
База данных
Используется PostgreSQL с ORM Hibernate/JPA для управления данными:
@Entity
@Table(name = "users")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.UUID)
private UUID id;
@Column(nullable = false, unique = true)
private String username;
@Column(nullable = false)
private String password;
@Column(nullable = false)
private String email;
@Column(name = "first_name")
private String firstName;
@Column(name = "last_name")
private String lastName;
@Column(name = "created_at")
@CreationTimestamp
private LocalDateTime createdAt;
@Column(name = "updated_at")
@UpdateTimestamp
private LocalDateTime updatedAt;
@OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true)
private List<Session> sessions;
}
Тестирование
Для каждого компонента написаны unit и интеграционные тесты:
@SpringBootTest
public class UserProfileServiceTest {
@MockBean
private UserRepository userRepository;
@Autowired
private UserProfileService userProfileService;
@Test
void testGetUserProfile_Success() {
User user = new User();
user.setUsername("testuser");
user.setEmail("test@example.com");
when(userRepository.findByUsername("testuser"))
.thenReturn(Optional.of(user));
UserProfileDTO profile = userProfileService.getUserProfile("testuser");
assertNotNull(profile);
assertEquals("testuser", profile.getUsername());
}
}
Результат
Реализованный личный кабинет обеспечивает удобный и безопасный способ управления профилем, соответствует лучшим практикам разработки и гарантирует защиту персональных данных пользователя.