Système de gestion pour équipes de secours citoyens basé sur Java Spring Boot

Le système de gestion des secours citoyens a été conçu pour résoudre des problèmes critiques dans les opérations de secours : un temps de réponse moyen supérieur à 40 minutes entre l'alerte et le déploiement, une allocation inefficace des ressources avec un taux d'erreur de 38% dans l'appariement des équipes et équipements, et une fragmentation des enregistrements de secours.

Cette plateforme offre une solution complète pour l'ensemble du processus allertage-déploiement-intervention-évaluation. Le système utilise Java Spring Boot pour le backend, avec une base de données MySQL pour stocker 11 catégories de données (informations sur les membres, inventaire d'équipements, rapports d'intervention, etc.) et Redis pour le cache des positions en temps réel et des disponibilités. L'interface front-end, développée avec Vue.js, assure une expérience utilisateur adaptée à tous les types d'appareils.

Les fonctionnalités principales comprennent : un système d'alerte intelligent (alerte à bouton unique, géolocalisation automatique LBS, classification des situations d'urgence), une allocation dynamique des ressources (appariement basé sur la localisation et les compétences des membres, vérification en temps réel des stocks d'équipements), un suivi complet des interventions (pointage des étapes, synchronisation des progrès, communication multi-acteurs), et une base de données d'analyse post-intervention (archivage des processus, capitalisation des expériences).

Architecture technique

Backend : Spring Boot

Spring Boot intègre nativement des serveurs comme Tomcat, Jetty et Undertow, éliminant le besoin d'installations et configurations supplémentaires. L'une de ses principales caractéristiques est la configuration automatique qui s'adapte aux dépendances du projet, simplifiant considérablement le processus de configuration. Le framework offre également une multitude de fonctionnalités prêtes à l'emploi et d'extensions telles que Spring Data, Spring Security et Spring Cloud, permettant aux développeurs de construire rapidement des applications extensibles et facilement intégrables avec d'autres technologies.

Frontend : Vue.js

Le cœur de Vue.js repose sur la technologie du DOM virtuel, une structure de données en mémoire qui optimise les manipulations DOM. L'adoption du binding de données réactif, du DOM virtuel et de l'approche par composants offre aux développeurs un modèle de développement moderne, flexibel et efficace. Lorsque les données changent, l'interface utilisateur se met à jour automatiquement, permettant aux développeurs de se concentrer sur la logique métier plutôt que sur les mises à jour manuelles de l'interface.

Couche de persistance : MyBatis-Plus

MyBatis-Plus est un outil d'extension basé sur MyBatis conçu pour rationaliser le développement. C'est un framework Java open source supportant plusieurs bases de données dont MySQL, Oracle, SQL Server et PostgreSQL. Il fournit une riche API et des annotations facilitant les opérations ORM, réduisant considérablement la nécessité d'écrire du SQL manuellement. De plus, MyBatis-Plus inclut un générateur de code automatisant la création de classes d'entité, d'interfaces Mapper et de fichiers de mapping XML.

Le framework supporte également des fonctionnalités pratiques comme la pagination, les requêtes dynamiques, le verrouillage optimiste et l'analyse de performance, facilitant les opérations de données. Avec MyBatis-Plus, les développeurs peuvent rapidement produire un code d'accès aux données de haute qualité, améliorant ainsi l'efficacité du développement.

Tests du système

La phase de test vise à identifier les problèmes potentiels sous différents angles, en se concentrant sur les tests fonctionnels pour détecter et corriger les défauts. Le processus de test garantit que le système répond aux besoins clients et corrige les problèmes et insuffisances identifiés. Après les tests, une conclusion est tirée pour valider la qualité du système.

Les tests sont essentiels dans le cycle de vie d'un système, constituant la dernière étape de contrôle avant la mise en production. Leur importance réside dans le fait qu'ils assurent la qualité et la fiabilité du système. Les tests sont conçus pour anticiper les problèmes potentiels lors de l'utilisation par les clients, en simulant divers scénarios pour découvrir les défauts et les résoudre proactivement.

Tests fonctionnels

Les tests fonctionnels évaluent chaque module du système en utilisant des méthodes de boîte noire telles que les clics, la saisie de valeurs limites et la validation des champs obligatoires/non obligatoires. L'élaborasion de cas de test permet de valider chaque fonctionnalité.

Exemple de test de fonctionnalité de connexion :

Données d'entrée Résultat attendu Résultat réel Analyse
Nom d'utilisateur : admin Mot de passe : 123456 Code de vérification : correct Connexion réussie Connexion réussie Conforme aux attentes
Nom d'utilisateur : admin Mot de passe : wrongpass Code de vérification : correct Erreur de mot de passe Erreur de mot de passe Conforme aux attentes
Nom d'utilisateur : admin Mot de passe : 123456 Code de vérification : incorrect Code de vérification invalide Code de vérification invalide Conforme aux attentes
Nom d'utilisateur : vide Mot de passe : 123456 Code de vérification : correct Nom d'utilisateur requis Nom d'utilisateur requis Conforme aux attentes

Exemples de code


/**
 * Service d'authentification des utilisateurs
 */
@Service
public class AuthentificationService {
    
    private static final String CLE_TOKEN = "JWT-Token";
    private final UtilisateurRepository utilisateurRepo;
    private final JetonRepository jetonRepo;
    
    @Autowired
    public AuthentificationService(UtilisateurRepository utilisateurRepo, JetonRepository jetonRepo) {
        this.utilisateurRepo = utilisateurRepo;
        this.jetonRepo = jetonRepo;
    }
    
    /**
     * Authentifie un utilisateur et génère un jeton
     */
    public ReponseConnexion authentifier(String identifiant, String motDePasse, String captcha) {
        Utilisateur utilisateur = utilisateurRepo.trouverParIdentifiant(identifiant);
        
        if (utilisateur == null || !verifierMotDePasse(motDePasse, utilisateur.getMotDePasse())) {
            return new ReponseConnexion(false, "Identifiant ou mot de passe incorrect");
        }
        
        String jeton = genererJeton(utilisateur);
        return new ReponseConnexion(true, "Connexion réussie", jeton);
    }
    
    private boolean verifierMotDePasse(String motDePasseSaisi, String motDePasseStocke) {
        return PasswordEncoder.matches(motDePasseSaisi, motDePasseStocke);
    }
    
    private String genererJeton(Utilisateur utilisateur) {
        String jetonAleatoire = GenerateurChaineAleatoire.generer(32);
        Date expiration = calculerExpiration();
        
        Jeton jeton = new Jeton();
        jeton.setUtilisateurId(utilisateur.getId());
        jeton.setJeton(jetonAleatoire);
        jeton.setDateExpiration(expiration);
        
        jetonRepo.save(jeton);
        return jetonAleatoire;
    }
    
    private Date calculerExpiration() {
        Calendar calendrier = Calendar.getInstance();
        calendrier.add(Calendar.HOUR_OF_DAY, 1);
        return calendrier.getTime();
    }
}

/**
 * Filtre d'interception pour la vérification des jetons
@Component
public class FiltreAuthentification implements Filter {
    
    @Autowired
    private JetonService jetonService;
    
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) 
        throws IOException, ServletException {
        
        HttpServletRequest httpRequest = (HttpServletRequest) request;
        HttpServletResponse httpResponse = (HttpServletResponse) response;
        
        // Configuration CORS
        configurerCORS(httpResponse);
        
        // Gestion des requêtes pré-vérification CORS
        if ("OPTIONS".equalsIgnoreCase(httpRequest.getMethod())) {
            httpResponse.setStatus(HttpStatus.OK.value());
            return;
        }
        
        // Vérification de l'annotation d'exclusion d'authentification
        if (verifierExclusion(httpRequest)) {
            chain.doFilter(request, response);
            return;
        }
        
        // Extraction et validation du jeton
        String jeton = extraireJeton(httpRequest);
        
        if (jetonValide(jeton, httpRequest)) {
            chain.doFilter(request, response);
        } else {
            repondreErreurNonAutorisee(httpResponse);
        }
    }
    
    private void configurerCORS(HttpServletResponse response) {
        response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE");
        response.setHeader("Access-Control-Max-Age", "3600");
        response.setHeader("Access-Control-Allow-Credentials", "true");
        response.setHeader("Access-Control-Allow-Headers", "x-requested-with,request-source,JWT-Token, Origin,imgType, Content-Type, cache-control,postman-token,Cookie, Accept,authorization");
        response.setHeader("Access-Control-Allow-Origin", request.getHeader("Origin"));
    }
    
    private boolean verifierExclusion(HttpServletRequest request) {
        String chemin = request.getRequestURI();
        return chemin.contains("/public/") || chemin.contains("/connexion");
    }
    
    private String extraireJeton(HttpServletRequest request) {
        String enTeteJeton = request.getHeader(CLE_TOKEN);
        return (enTeteJeton != null && enTeteJeton.startsWith("Bearer ")) ? 
               enTeteJeton.substring(7) : null;
    }
    
    private boolean jetonValide(String jeton, HttpServletRequest request) {
        if (StringUtils.isBlank(jeton)) {
            return false;
        }
        
        JetonEntity jetonEntity = jetonService.trouverParJeton(jeton);
        if (jetonEntity == null || jetonEntity.estExpire()) {
            return false;
        }
        
        request.setAttribute("utilisateurId", jetonEntity.getUtilisateurId());
        request.setAttribute("role", jetonEntity.getRole());
        return true;
    }
    
    private void repondreErreurNonAutorisee(HttpServletResponse response) throws IOException {
        response.setCharacterEncoding("UTF-8");
        response.setContentType("application/json; charset=utf-8");
        response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
        
        PrintWriter writer = response.getWriter();
        writer.print(JsonUtils.toJson(new ErreurReponse(401, "Authentification requise")));
        writer.close();
    }
}

Structure de base de données


-- Table des jetons d'authentification
CREATE TABLE `jeton_authentification` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'ID primaire',
  `utilisateur_id` bigint(20) NOT NULL COMMENT 'ID utilisateur',
  `utilisateur_nom` varchar(100) NOT NULL COMMENT 'Nom d\'utilisateur',
  `table_cible` varchar(100) DEFAULT NULL COMMENT 'Table concernée',
  `role` varchar(100) DEFAULT NULL COMMENT 'Rôle utilisateur',
  `valeur_jeton` varchar(200) NOT NULL COMMENT 'Valeur du jeton',
  `date_creation` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'Date de création',
  `date_expiration` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00' COMMENT 'Date d\'expiration',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='Table des jetons d\'authentification';

Étiquettes: Spring Boot Vue.js MyBatis-Plus Java Système de gestion

Publié le 30 juin à 17h01