Implémentation d'une structure d'arbre pour les menus dynamiques en Java avec MyBatis

Cette approche utilise un mappage récursif dans MyBatis pour construire une hiérarchie de menus. Contrairement aux solutions algorithmiques pures qui récupèrent toutes les données en une seule requête, cette méthode effectue plusieurs appels à la base de données. L'exepmle illustre comment différencier les menus selon les rôles utilisateur.

Exemple de réponse pour un administrateur

{
    "code": 200,
    "success": true,
    "msg": "Succès",
    "data": [
        {
            "menuId": "accueil-001",
            "menuParent": null,
            "niveau": 1,
            "ordre": 1,
            "catégorie": "page",
            "lien": "/accueil",
            "nom": "Tableau de bord",
            "icone": "dashboard",
            "sousMenus": [],
            "idsAutorisés": null
        },
        {
            "menuId": "gestion-002",
            "menuParent": "",
            "niveau": 1,
            "ordre": 2,
            "catégorie": "répertoire",
            "lien": null,
            "nom": "Gestion des données",
            "icone": "database",
            "sousMenus": [
                {
                    "menuId": "logiciels-003",
                    "menuParent": "gestion-002",
                    "niveau": 2,
                    "ordre": 1,
                    "catégorie": "page",
                    "lien": "/admin/logiciels",
                    "nom": "Logiciels",
                    "icone": "apps",
                    "sousMenus": null,
                    "idsAutorisés": null
                }
            ],
            "idsAutorisés": null
        }
    ],
    "timestamp": 1715000000000
}

Exemple de réponse pour un rôle limité

{
    "code": 200,
    "success": true,
    "msg": "Opération réussie",
    "data": [
        {
            "menuId": "profil-001",
            "menuParent": null,
            "niveau": 1,
            "ordre": 1,
            "catégorie": "répertoire",
            "lien": null,
            "nom": "Mon espace",
            "icone": "user",
            "sousMenus": [
                {
                    "menuId": "abonnements-002",
                    "menuParent": "profil-001",
                    "niveau": 2,
                    "ordre": 1,
                    "catégorie": "page",
                    "lien": "/espace/abonnements",
                    "nom": "Mes abonnements",
                    "icone": "subscription",
                    "sousMenus": [],
                    "idsAutorisés": null
                }
            ],
            "idsAutorisés": null
        }
    ],
    "timestamp": 1715000100000
}

La strutcure de la base de données comprend principalement deux tables : une pour les définitions de menus et une pour les rôles utilisateur avec les permissions associées.

Classe d'entité pour les menus

package com.app.navigation.entity;

import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.extension.activerecord.Model;
import java.util.List;

@TableName("app_menu")
public class MenuElement extends Model<MenuElement> {
    
    @TableId("MENU_ID")
    private String identifiant;
    
    @TableField("PARENT_ID")
    private String parent;
    
    @TableField("HIERARCHY_LEVEL")
    private Integer profondeur;
    
    @TableField("DISPLAY_ORDER")
    private Integer position;
    
    @TableField("MENU_TYPE")
    private String type;
    
    @TableField("ROUTE_PATH")
    private String chemin;
    
    @TableField("MENU_NAME")
    private String libellé;
    
    @TableField("ICON_CLASS")
    private String icône;
    
    @TableField(exist = false)
    private List<MenuElement> sousMenus;
    
    @TableField(exist = false)
    private List<String> idsAutorisés;
    
    // Getters et setters omis pour la concision
}

Classe d'entité pour les rôles

package com.app.navigation.entity;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;

@TableName("app_roles")
public class ProfilUtilisateur {
    
    @TableId(value = "ROLE_ID", type = IdType.AUTO)
    private Integer identifiant;
    
    private String nomRole;
    private Integer niveauAccès;
    private String permissionsMenu;
    private Integer coefficient;
    
    // Getters et setters omis pour la concision
}

Méthode de récupération des menus

@Service
public class NavigationService {
    
    @Autowired
    private MenuRepository menuRepository;
    
    public Result construireArbreMenu(Utilisateur utilisateur) {
        MenuElement racine = new MenuElement();
        racine.setParent("");
        
        ProfilUtilisateur profil = roleRepository.rechercherParId(utilisateur.getProfilId());
        List<String> permissions = analyserPermissions(profil.getPermissionsMenu());
        racine.setIdsAutorisés(permissions);
        
        List<MenuElement> arbre = menuRepository.construireHiérarchie(racine);
        return Result.succès(arbre);
    }
    
    private List<String> analyserPermissions(String jsonPermissions) {
        // Transformation JSON en liste
        return Arrays.asList(jsonPermissions.replace("[", "").replace("]", "").split(","));
    }
}

Configuration MyBatis pour le mappage récursif

<resultMap id="mappingArbre" type="com.app.navigation.entity.MenuElement">
    <id property="identifiant" column="MENU_ID"/>
    <collection property="sousMenus"
                ofType="com.app.navigation.entity.MenuElement"
                javaType="java.util.List"
                column="{parentId=MENU_ID}"
                select="construireEnfants"/>
</resultMap>

<select id="construireHiérarchie" parameterType="com.app.navigation.entity.MenuElement" resultMap="mappingArbre">
    SELECT * FROM app_menu
    <where>
        <if test="identifiant != null and identifiant != ''">
            AND MENU_ID = #{identifiant}
        </if>
        <if test="parent != null and parent != ''">
            AND PARENT_ID = #{parent}
        </if>
        <if test="idsAutorisés != null and idsAutorisés.size() > 0">
            AND MENU_ID IN
            <foreach collection="idsAutorisés" item="idPerm" open="(" separator="," close=")">
                #{idPerm}
            </foreach>
        </if>
    </where>
    ORDER BY DISPLAY_ORDER
</select>

Étiquettes: Java MyBatis dynamic-menus hierarchical-data recursive-query

Publié le 15 juillet à 12h01