Architecture du Système
Le système repose sur une architecture à trois tiers classique, séparant la couche de présentation, la couche logique métier et la couche de persistance des données. Cette approche modulaire facilite la maintenance, l'évolutivité et le déploiement indépendant de chaque composant, tout en permettant une intégration fluide avec des interfaces frontend modernes.
Implémentation du Contrôleur d'Authentification et de Gestion des Utilisateurs
Voici une version refactorisée du contrôleur gérant les opérations liées aux utilisateurs. Cette implémentation utilise des DTO (Data Transfer Objects) pour les requêtes et réponses, des wrappers lambda pour une meilleure sécurité de type, et des structures de réponse standardisées.
@RestController
@RequestMapping("/api/v1/users")
@RequiredArgsConstructor
public class UserManagementController {
private final UserAccountService userAccountService;
private final JwtTokenProvider jwtTokenProvider;
private final PasswordEncoder passwordEncoder;
@PostMapping("/authenticate")
public ResponseEntity<ApiResponse<AuthResponseDto>> authenticateUser(
@Valid @RequestBody LoginRequestDto loginRequest) {
UserAccount account = userAccountService.findByUsername(loginRequest.getUsername())
.orElseThrow(() -> new ResourceNotFoundException("Utilisateur non trouvé"));
if (!account.getRole().equalsIgnoreCase(loginRequest.getRequestedRole())) {
return ResponseEntity.status(HttpStatus.FORBIDDEN)
.body(ApiResponse.error("Rôle d'accès non autorisé"));
}
if (!passwordEncoder.matches(loginRequest.getPassword(), account.getPasswordHash())) {
return ResponseEntity.badRequest()
.body(ApiResponse.error("Identifiants invalides"));
}
String accessToken = jwtTokenProvider.generateToken(
account.getId(), account.getUsername(), account.getRole());
AuthResponseDto responseDto = new AuthResponseDto(accessToken, account.getRole());
return ResponseEntity.ok(ApiResponse.success(responseDto));
}
@PostMapping("/register")
public ResponseEntity<ApiResponse<String>> registerNewUser(
@Valid @RequestBody RegistrationRequestDto registrationRequest) {
if (userAccountService.existsByUsername(registrationRequest.getUsername())) {
return ResponseEntity.badRequest()
.body(ApiResponse.error("Ce nom d'utilisateur est déjà pris"));
}
userAccountService.createUser(registrationRequest);
return ResponseEntity.status(HttpStatus.CREATED)
.body(ApiResponse.success("Inscription réussie"));
}
@PostMapping("/reset-password")
public ResponseEntity<ApiResponse<String>> resetPassword(
@RequestParam String username) {
UserAccount account = userAccountService.findByUsername(username)
.orElseThrow(() -> new ResourceNotFoundException("Compte introuvable"));
String defaultPassword = "TempPass123!";
userAccountService.updatePassword(account.getId(), passwordEncoder.encode(defaultPassword));
return ResponseEntity.ok(ApiResponse.success("Mot de passe réinitialisé avec succès"));
}
@GetMapping("/profile/{id}")
public ResponseEntity<ApiResponse<UserProfileDto>> getUserProfile(@PathVariable Long id) {
UserProfileDto profile = userAccountService.getUserProfileById(id);
return ResponseEntity.ok(ApiResponse.success(profile));
}
@DeleteMapping("/batch")
public ResponseEntity<ApiResponse<String>> deleteUsers(@RequestBody List<Long> userIds) {
userAccountService.deleteUsersByIds(userIds);
return ResponseEntity.ok(ApiResponse.success("Utilisateurs supprimés avec succès"));
}
}
Schéma de la Base de Données
La structure de la table des utilisateurs a été optimisée pour inclure des métadonnées standard, assurer l'intégrité des données et supporter les fonctionnalités d'audit.
DROP TABLE IF EXISTS `sys_user_account`;
CREATE TABLE `sys_user_account` (
`user_id` BIGINT NOT NULL AUTO_INCREMENT COMMENT 'Identifiant unique',
`username` VARCHAR(50) NOT NULL COMMENT 'Nom d'utilisateur unique',
`password_hash` VARCHAR(255) NOT NULL COMMENT 'Hash du mot de passe',
`role` VARCHAR(20) NOT NULL DEFAULT 'STANDARD' COMMENT 'Rôle de l utilisateur',
`status` TINYINT NOT NULL DEFAULT 1 COMMENT 'Statut du compte (1: actif, 0: inactif)',
`created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'Date de création',
`updated_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 'Date de dernière modification',
PRIMARY KEY (`user_id`),
UNIQUE KEY `uk_username` (`username`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='Table de gestion des comptes utilisateurs';
INSERT INTO `sys_user_account` (`username`, `password_hash`, `role`)
VALUES ('superadmin', '$2a$10$N9qo8uLOickgx2ZMRZoMyeIjZAgcfl7p92ldGxad68LJZdL17lhWy', 'ADMIN');
Stack Technologique
Java et Spring Boot
Java reste un pilier pour le développement d'applications d'entreprise grâce à sa robustesse, son typage fort et son écosystème mature. Couplé à Spring Boot, il permet de configurer rapidement des applications avec une injection de dépendances efficace, une gestion avancée des transactions et une intégration native avec les protocoles REST. La gestion mémoire automatique et la portabilité multiplateforme en font un choix optimal pour les systèmes de recommandation nécessitant une haute disponibilité et un traitement concurrent.
MySQL
MySQL est utilisé comme système de gestion de base de données relationnelle pour sa fiabilité et ses performances en lecture et écriture. Son architecture client-serveur et son support de multiples moteurs de stockage, notamment InnoDB pour les transactions ACID, le rendent parfaitement adapté au stockage des profils utilisateurs et des historiques d'écoute. Sa compatibilité avec les ORM modernes et sa capacité à gérer des requêtes complexes facilitent l'extraction des données nécessaires aux algorithmes de filtrage collaboratif ou basé sur le contenu.