Ce document synthétise les principes techniques et l'architecture d'une application web de type "livraison de repas". Il couvre les aspects back-end, front-end et l'intégration de services tiers, en s'appuyant sur le framework Spring Boot.
Vue d'ensemble du projet
L'application se compose de deux parties principales :
- L'interface d'administration : destinée au personnel du restaurant pour gérer les catégories, les plats, les menus, les commandes et les employés.
- L'application client (sous forme de mini-programme) : permet aux consommateurs de parcourir les plats, de passer commande et de effectuer des paiements.
Proxy inverse avec Nginx
Le proxy inverse agit comme un intermédiaire centralisé pour les requêtes client. Il peut répartir la charge entre plusieurs serveurs d'application.
Un exemple de configuration de base :
server {
listen 8080;
server_name monsite.com;
location / {
proxy_pass http://backend_app:8090;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
Pour la répartition de charge, on définit un groupe de serveurs en amont :
http {
upstream mes_serveurs_app {
server 10.0.0.1:8080 weight=5;
server 10.0.0.2:8080;
server 10.0.0.3:8080 backup;
}
server {
listen 80;
location / {
proxy_pass http://mes_serveurs_app;
}
}
}
Documentation d'API avec Swagger et Apifox
La génération automatique de documentation d'API est essentielle. Swagger (basé sur OpenAPI) et Apifox permettent de concevoir, tester et documenter les endpoints REST. Knife4j améliore l'interface de Swagger UI pour Spring Boot.
Architecture modulaire et patterns
L'application utilise une architecture Maven multi-modules. La gestion des dépendances est centrael.
Héritage et dependency management :
<parent>
<groupId>com.maventech</groupId>
<artifactId>parent-project</artifactId>
<version>1.0.0</version>
</parent>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>3.1.0</version>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
Les objets de transfert de données (DTO) et les objets de valeur (VO) séparent le modèle de domaine des besoins de l'API.
Gestion globale des exceptions
Un gestionnaire centralisé capture les exceptions et transforme les erreurs en réponses JSON cohérentes.
@RestControllerAdvice
public class GestionnaireExceptions {
@ExceptionHandler(EntiteNonTrouveeException.class)
public ResponseEntity<ReponseApi> gererNonTrouve(EntiteNonTrouveeException ex) {
ReponseApi reponse = new ReponseApi(HttpStatus.NOT_FOUND.value(), ex.getMessage());
return new ResponseEntity<>(reponse, HttpStatus.NOT_FOUND);
}
@ExceptionHandler(Exception.class)
public ResponseEntity<ReponseApi> gererExceptionGenerique(Exception ex) {
ReponseApi reponse = new ReponseApi(HttpStatus.INTERNAL_SERVER_ERROR.value(), "Erreur interne");
return new ResponseEntity<>(reponse, HttpStatus.INTERNAL_SERVER_ERROR);
}
}
Système de journalisation
Spring Boot intègre SLF4J avec Logback. La configuration se fait dans application.yml :
logging:
level:
root: INFO
com.monapp: DEBUG
org.springframework.web: WARN
pattern:
console: "%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n"
file:
name: logs/app.log
Protocole HTTP et authentification JWT
JWT est utilisé pour l'authentification stateless. Un token est généré à la connexion et vérifié par un intercepteur.
@Component
public class IntercepteurAuthJWT implements HandlerInterceptor {
@Autowired
private ServiceTokenJWT serviceToken;
@Override
public boolean preHandle(HttpServletRequest requete, HttpServletResponse reponse, Object handler) {
String enteteAuth = requete.getHeader("Authorization");
if (enteteAuth != null && enteteAuth.startsWith("Bearer ")) {
String token = enteteAuth.substring(7);
try {
Claims reclamations = serviceToken.validerToken(token);
// Injecter l'ID utilisateur dans le contexte local du thread
ContexteUtilisateur.definirId(reclamations.get("userId", Long.class));
return true;
} catch (Exception e) {
reponse.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
return false;
}
}
reponse.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
return false;
}
}
Le ContexteUtilisateur utilise ThreadLocal pour isoler les données par thread de requête.
Programmation orientée aspect (AOP)
AOP permet de factoriser les préoccupations transversales. Exemple de journalisation des appels de méthode :
@Aspect
@Component
public class AspectJournalisation {
private static final Logger journal = LoggerFactory.getLogger(AspectJournalisation.class);
@Around("execution(* com.monapp.service.*.*(..))")
public Object journaliserAppel(ProceedingJoinPoint pointConjoint) throws Throwable {
String signature = pointConjoint.getSignature().toShortString();
journal.info("Entrée dans : {}", signature);
long debut = System.currentTimeMillis();
try {
Object resultat = pointConjoint.proceed();
long duree = System.currentTimeMillis() - debut;
journal.info("Sortie de : {} ({} ms)", signature, duree);
return resultat;
} catch (Throwable t) {
journal.error("Exception dans {} : {}", signature, t.getMessage());
throw t;
}
}
}
Intégration Redis et Spring Cache
Redis sert de cache et de stockage rapide pour les données fréquemment accédées. La configuration du RedisTemplate :
@Configuration
public class ConfigurationRedis {
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory usineConnexion) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(usineConnexion);
template.setKeySerializer(new StringRedisSerializer());
template.setValueSerializer(new GenericJackson2JsonRedisSerializer());
template.setHashKeySerializer(new StringRedisSerializer());
template.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());
return template;
}
}
Spring Cache simplifie la mise en cache déclarative :
@Service
public class ServicePlat {
@Cacheable(value = "plats", key = "#idCategorie")
public List<Plat> obtenirPlatsParCategorie(Long idCategorie) {
// Logique d'accès aux données
return depotPlat.trouverParCategorie(idCategorie);
}
@CacheEvict(value = "plats", key = "#plat.categorie.id")
public void mettreAJourPlat(Plat plat) {
depotPlat.sauvegarder(plat);
}
}
Tâches planifiées avec Spring Task
L'exécution périodique de tâches est gérée par les expressions cron.
@Component
public class TachesPlanifiees {
@Scheduled(cron = "0 0 2 * * ?") // Tous les jours à 2h du matin
public void nettoyerAnciennesDonnees() {
// Logique de nettoyage
journal.info("Nettoyage des données anciennes");
}
@Scheduled(fixedRate = 300000) // Toutes les 5 minutes
public void verifierStatutSysteme() {
// Vérification périodique
}
}
Communication en temps réel avec WebSocket
WebSocket établit une connexion persistante pour une communication bidirectionnelle, idéale pour les notifications push (ex: état d'une commande).
@ServerEndpoint("/ws/statut-commande/{idClient}")
@Component
public class PointFinalStatutCommande {
private static final Map<String, Session> sessionsParClient = new ConcurrentHashMap<>();
@OnOpen
public void ouvrirConnexion(Session session, @PathParam("idClient") String idClient) {
sessionsParClient.put(idClient, session);
}
@OnMessage
public void recevoirMessage(String message, @PathParam("idClient") String idClient) {
// Traitement d'un message client si nécessaire
}
@OnClose
public void fermerConnexion(@PathParam("idClient") String idClient) {
sessionsParClient.remove(idClient);
}
public static void envoyerMiseAJour(String idClient, String statutJson) {
Session session = sessionsParClient.get(idClient);
if (session != null && session.isOpen()) {
try {
session.getBasicRemote().sendText(statutJson);
} catch (IOException e) {
// Gestion d'erreur
}
}
}
}
Visualisation de données avec ECharts
Apache ECharts est une bibliothèque JavaScript pour créer des graphiques interactifs (barres, lignes, camemberts). Les données sont généralement préparées côté serveur au format JSON.
Traitement de fichiers Excel avec POI
Apache POI permet de lire et écrire des fichiers Microsoft Office.
public class GestionExcel {
public void exporterRapport(List<DonneeCommande> donnees, OutputStream fluxSortie) {
try (XSSFWorkbook classeur = new XSSFWorkbook()) {
XSSFSheet feuille = classeur.createSheet("Commandes");
// Créer l'en-tête
XSSFRow ligneEntete = feuille.createRow(0);
ligneEntete.createCell(0).setCellValue("ID Commande");
ligneEntete.createCell(1).setCellValue("Client");
ligneEntete.createCell(2).setCellValue("Montant");
int numeroLigne = 1;
for (DonneeCommande donnee : donnees) {
XSSFRow ligne = feuille.createRow(numeroLigne++);
ligne.createCell(0).setCellValue(donnee.getIdCommande());
ligne.createCell(1).setCellValue(donnee.getNomClient());
ligne.createCell(2).setCellValue(donnee.getMontantTotal());
}
classeur.write(fluxSortie);
} catch (IOException e) {
throw new RuntimeException("Erreur lors de la génération du fichier Excel", e);
}
}
}
Intégration avec des services externes (ex: WeChat Login)
L'authentification via WeChat suit un flux standardisé utilisant des codes d'autorisation temporaires et des appels HTTP à l'API WeChat via HttpClient ou RestTemplate.