Architecture Technique et Stack Technologique
Le développement de la plateforme de gestion de club de fitness repose sur une architecture découplée, assurant une séparation claire entre le frontend et le backend. Cette approche garantit une maintenabilité optimale et une scalabilité adaptée aux besoins d'une salle de sport moderne.
Backend avec Spring Boot
Spring Boot est utilisé pour orchestrer la logique métier et exposer les API REST. Son mécanisme d'auto-configuration et son serveur embarqué permettent un déploiement rapide. L'intégration avec Spring Security et la gestion des transactions sont simplifiées, offrant une base robuste pour les opérations critiques comme les réservations de cours et la gestion des abonnements.
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.Arrays;
import java.util.List;
@SpringBootApplication
public class FitnessClubApplication {
public static void main(String[] args) {
SpringApplication.run(FitnessClubApplication.class, args);
}
}
@RestController
@RequestMapping("/api/courses")
class CourseController {
@GetMapping("/featured")
public List<String> getFeaturedCourses() {
return Arrays.asList("Yoga Matinal", "CrossFit Intensif", "Pilates Doux");
}
}
Frontend avec Vue.js et UniApp
L'interface d'administration est construite avec Vue.js, tirant parti de son système de réactivité et de son architecture basée sur les composants. Pour l'application mobile destinée aux membres du club, UniApp est utilisé afin de compiler une base de code unique vers les mini-programmes WeChat et les applications natives, assurant une expérience utilisateur fluide sur tous les appareils.
<html lang="fr">
<head>
<meta charset="UTF-8">
<title>Gestion des Abonnements</title>
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
</head>
<body>
<div id="membership-app">
<h3>Statut de l'abonnement : {{ status }}</h3>
<button @click="renewMembership">Renouveler l'abonnement</button>
</div>
<script>
const { createApp, ref } = Vue;
createApp({
setup() {
const status = ref('Actif');
const renewMembership = () => {
status.value = 'Renouvelé avec succès';
};
return { status, renewMembership };
}
}).mount('#membership-app');
</script>
</body>
</html>
Persistance des Données avec MyBatis
MyBatis est privilégié pour la couche d'accès aux données. Contrairement aux ORM complets, il offre un contrôle précis sur les requêtes SQL, ce qui est crucial pour optimiser les rapports de fréquentation du club et les requêtes complexes d'analyse des abonnements.
Stratégie de Test et Validation
Une campagne de tests rigoureuse a été menée pour valider la fiabilité du système. Les tests fonctionnels se concentrent sur les parcours critiques des utilisateurs, notamment l'authentification et la gestion des profils.
Tests d'Authentification
| Données d'entrée | Résultat attendu | Résultat obtenu | Analyse |
|---|---|---|---|
| Identifiant: admin, MDP: admin123, Captcha: Valide | Connexion réussie | Connexion réussie | Conforme |
| Identifiant: admin, MDP: erreur, Captcha: Valide | Échec (MDP incorrect) | Échec (MDP incorrect) | Conforme |
| Identifiant: vide, MDP: admin123, Captcha: Valide | Échec (Champ requis) | Échec (Champ requis) | Conforme |
Tests de Gestion des Membres
| Action | Données | Résultat attendu | Résultat obtenu |
|---|---|---|---|
| Création | Nom: Jean, Rôle: Membre Standard | Profil créé et visible | Profil créé et visible |
| Modification | ID: 101, Rôle: Membre VIP | Rôle mis à jour | Rôle mis à jour |
| Suppression | ID: 102 | Profil archivé | Profil archivé |
Implémentation de la Sécurité et Authentification
La sécurisation des API repose sur l'émission de JSON Web Tokens (JWT). Un filtre d'interception valide chaque requête etnrante, garantissant que seules les sessions autorisées peuvent accéder aux ressources protégées.
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.web.filter.OncePerRequestFilter;
import javax.servlet.FilterChain;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Date;
// Génération du Token
public class JwtProvider {
private final String secretKey = "SuperSecretFitnessKey";
public String createToken(Long memberId, String role) {
Date now = new Date();
Date expiry = new Date(now.getTime() + 3600000); // Expiration dans 1 heure
return Jwts.builder()
.setSubject(Long.toString(memberId))
.claim("role", role)
.setIssuedAt(now)
.setExpiration(expiry)
.signWith(SignatureAlgorithm.HS512, secretKey)
.compact();
}
}
// Filtre d'Interception
@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest req, HttpServletResponse res, FilterChain chain)
throws IOException, ServletException {
String header = req.getHeader("Authorization");
if (header != null && header.startsWith("Bearer ")) {
String token = header.substring(7);
if (JwtProvider.validate(token)) {
Long userId = JwtProvider.getUserId(token);
req.setAttribute("userId", userId);
chain.doFilter(req, res);
return;
}
}
res.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Accès non autorisé");
}
}
Modélisation de la Base de Données
La structure relationnelle est conçue pour supporter les entités principales du club. Voici le schéma de la table gérant les cours collectifs proposés aux adhérents.
CREATE TABLE `fitness_class` (
`class_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'Identifiant unique du cours',
`class_name` varchar(150) NOT NULL COMMENT 'Nom du cours',
`instructor` varchar(100) NOT NULL COMMENT 'Nom du coach',
`capacity` int(11) NOT NULL COMMENT 'Nombre maximum de participants',
`schedule` datetime NOT NULL COMMENT 'Date et heure du cours',
`created_at` timestamp DEFAULT CURRENT_TIMESTAMP COMMENT 'Date de création',
PRIMARY KEY (`class_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='Table des cours collectifs';
INSERT INTO `fitness_class` (`class_name`, `instructor`, `capacity`, `schedule`)
VALUES ('Yoga Matinal', 'Alice Dupont', 20, '2023-11-01 08:00:00');
INSERT INTO `fitness_class` (`class_name`, `instructor`, `capacity`, `schedule`)
VALUES ('CrossFit Intensif', 'Marc Leroy', 15, '2023-11-01 18:30:00');