Architecture et développement du backend d'une application de livraison de repas sur campus avec Spring Boot

Architecture du système de livraison sur campus

Le développement du backend pour une application de livraison de restauration rapide sur campus, destinée à être consommée via un mini-programme WeChat, nécessite une architecture robuste et évolutive. Le système repose sur une API RESTful construite avec Spring Boot, assurant une communication fluide avec le client léger (mini-programme) tout en gérant l'authentification, la gestion des commandes et les opérations CRUD des utilisateurs. L'intégration de MyBatis-Plus facilite la couche d'accès aux données, tandis que MySQL garantit la persistance et l'intégrité des informations.

Implémentasion de l'API RESTful

Le contrôleur ci-dessous illustre la gestion des comptes utilisateurs, incluant l'authentification, l'inscription et les opérations administratives. Le code a été modernisé pour utiliser l'injection par constructeur, les LambdaQueryWrapper de MyBatis-Plus pour des requêtes typées et sécurisées, ainsi qu'une structure de réponse standardisée.


@RestController
@RequestMapping("/api/v1/accounts")
public class AccountController {

    private final AccountService accountService;
    private final JwtTokenProvider tokenProvider;

    public AccountController(AccountService accountService, JwtTokenProvider tokenProvider) {
        this.accountService = accountService;
        this.tokenProvider = tokenProvider;
    }

    @PostMapping("/login")
    public ApiResponse<LoginResponse> authenticate(@RequestBody LoginRequest request) {
        Account account = accountService.getOne(
            new LambdaQueryWrapper<Account>().eq(Account::getLoginName, request.getUsername())
        );

        if (account == null || !account.getPwdHash().equals(request.getPassword())) {
            return ApiResponse.error("Identifiants invalides");
        }
        if (!account.getUserRole().equals(request.getRole())) {
            return ApiResponse.error("Accès non autorisé pour ce rôle");
        }

        String jwt = tokenProvider.createToken(account.getAccountId(), account.getLoginName(), account.getUserRole());
        return ApiResponse.success(new LoginResponse(jwt));
    }

    @PostMapping("/register")
    public ApiResponse<Void> register(@RequestBody Account newAccount) {
        boolean exists = accountService.exists(
            new LambdaQueryWrapper<Account>().eq(Account::getLoginName, newAccount.getLoginName())
        );
        if (exists) {
            return ApiResponse.error("Ce nom d'utilisateur est déjà pris");
        }
        accountService.save(newAccount);
        return ApiResponse.success();
    }

    @PostMapping("/reset-password")
    public ApiResponse<Void> resetPassword(@RequestParam String loginName) {
        Account account = accountService.getOne(
            new LambdaQueryWrapper<Account>().eq(Account::getLoginName, loginName)
        );
        if (account == null) {
            return ApiResponse.error("Compte introuvable");
        }
        account.setPwdHash("default_hashed_pwd_123");
        accountService.updateById(account);
        return ApiResponse.success();
    }

    @GetMapping("/page")
    public ApiResponse<PageResult<Account>> getAccounts(
            @RequestParam(defaultValue = "1") int page,
            @RequestParam(defaultValue = "10") int size,
            Account queryFilter) {
        
        Page<Account> pageParam = new Page<>(page, size);
        LambdaQueryWrapper<Account> wrapper = new LambdaQueryWrapper<>();
        
        if (queryFilter.getLoginName() != null) {
            wrapper.like(Account::getLoginName, queryFilter.getLoginName());
        }
        
        Page<Account> result = accountService.page(pageParam, wrapper);
        return ApiResponse.success(new PageResult<>(result));
    }

    @PutMapping("/update")
    public ApiResponse<Void> updateAccount(@RequestBody Account account) {
        accountService.updateById(account);
        return ApiResponse.success();
    }

    @DeleteMapping("/delete")
    public ApiResponse<Void> deleteAccounts(@RequestBody List<Long> accountIds) {
        accountService.removeByIds(accountIds);
        return ApiResponse.success();
    }
}

Conception de la base de données

Le schéma relationnel suivant définit la table des comptes. L'utilisation de utf8mb4 est privilégiée pour supporter l'ensemble des caractères Unicode, et des contraintes d'unicité sont appliquées pour garantir l'intégrité des identifiants de connexion.


DROP TABLE IF EXISTS `campus_accounts`;

CREATE TABLE `campus_accounts` (
  `account_id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT 'Identifiant unique',
  `login_name` VARCHAR(64) NOT NULL COMMENT 'Nom de connexion',
  `pwd_hash` VARCHAR(128) NOT NULL COMMENT 'Mot de passe haché',
  `user_role` VARCHAR(32) NOT NULL DEFAULT 'STUDENT' COMMENT 'Rôle de l utilisateur',
  `created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'Date de création',
  PRIMARY KEY (`account_id`),
  UNIQUE KEY `uk_login_name` (`login_name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='Table des comptes du campus';

INSERT INTO `campus_accounts` (`login_name`, `pwd_hash`, `user_role`) 
VALUES ('admin_campus', 'hashed_admin_secret', 'ADMINISTRATOR');

Analyse de la pile technologique

Spring Boot et Java : L'écosystème Java, couplé au framework Spring Boot, offre une base solide pour le développement de services backend d'entreprise. La configuration automatique, la gestion des dépendances et l'intégration native avec des outils de sécurité et de persistance permettent de réduire considérablement le code standard (boilerplate). Le typage fort de Java et son modèle de gestion de la mémoire assurent la stabilité et la performance nécessaires pour traiter un grand nombre de requêtes simultanées, typique d'une application de livraison aux heures de pointe.

MySQL : En tant que système de gestion de base de données relationnelle, MySQL excelle dans la gestion des transactions ACID, cruciales pour les opérations financières et les commandes de livraison. Son architecture multi-thread et son moteur de stockage InnoDB garantissent une haute disponibilité et une intégrité référentielle stricte. De plus, sa capacité à s'intégrer seamlessly avec des ORM comme MyBatis ou Hibernate en fait un choix optimal pour les architectures backend modernes.

Étiquettes: SpringBoot Java MybatisPlus MySQL WeChatMiniProgram

Publié le 19 juin à 01h59