rust12-路由接口

rust12-路由接口

1、介绍

在写动态权限的时候,我们很多时候都需要返回不同用户的不同的菜单以及路由接口

接下来我们就来写下面这个接口getRouters

🍎创建菜单表

javascript 复制代码
CREATE TABLE `sys_menu`  (
  `menu_id` bigint(0) NOT NULL AUTO_INCREMENT COMMENT '菜单ID',
  `menu_name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '菜单名称',
  `parent_id` bigint(0) NULL DEFAULT 0 COMMENT '父菜单ID',
  `order_num` int(0) NULL DEFAULT 0 COMMENT '显示顺序',
  `path` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT '' COMMENT '路由地址',
  `component` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '组件路径',
  `query` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '路由参数',
  `is_frame` int(0) NULL DEFAULT 1 COMMENT '是否为外链(0是 1否)',
  `is_cache` int(0) NULL DEFAULT 0 COMMENT '是否缓存(0缓存 1不缓存)',
  `menu_type` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT '' COMMENT '菜单类型(M目录 C菜单 F按钮)',
  `visible` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT '0' COMMENT '菜单状态(0显示 1隐藏)',
  `status` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT '0' COMMENT '菜单状态(0正常 1停用)',
  `perms` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '权限标识',
  `icon` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT '#' COMMENT '菜单图标',
  `create_by` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT '' COMMENT '创建者',
  `create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间',
  `update_by` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT '' COMMENT '更新者',
  `update_time` datetime(0) NULL DEFAULT NULL COMMENT '更新时间',
  `remark` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT '' COMMENT '备注',
  PRIMARY KEY (`menu_id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 2008 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '菜单权限表' ROW_FORMAT = Dynamic;

2、接口引入

👉入口引入

接下来我们完成字典数据模块,在完成功能之前我们需要先添加模块入口,这样rust才能认识到是哪个模块

🍎src\main.rs

先在入口引入我们的auth模块

javascript 复制代码
  .service(
      web::scope("/api") // 这里加上 /api 前缀
        .configure(modules::auth::routes::config) // 权限模块
  )
🍎模块申明

全局文件之中字典数据模块申明

我们的文件结构如下所示,mod.rs作为入口文件进行申明文件暴露

javascript 复制代码
// src\modules\mod.rs
pub mod auth; //权限

建立文件结构如下图所示:

javascript 复制代码
// src\modules\auth

📦auth
 ┣ 📜handlers.rs
 ┣ 📜mod.rs
 ┗ 📜routes.rs
🍎基础模块申明

src\modules\auth下面搭建模块

mod.rs模块

javascript 复制代码
// mod.rs
pub mod handlers;
pub mod routes;
🍎routes.rs模块编写
javascript 复制代码
use actix_web::web;
pub fn config(cfg: &mut web::ServiceConfig) {
  cfg.route("/getRouters", web::get().to(crate::modules::auth::handlers::get_routers));
}
🍎handlers.rs方法逻辑初步
javascript 复制代码
// handlers.rs方法逻辑

use actix_web::{HttpResponse};
use crate::common::response::ApiResponse; // 导入 ApiResponse 模型
// 测试接口
pub async fn get_routers() -> HttpResponse {
    // let jsondata="";
    HttpResponse::Ok().json( ApiResponse{
        code: 200,
        msg: "接口信息",
        data:  None::<()>,
    })
}

👉申明规范

🍎数据格式

定义数据规范和格式src\common\response.rs

javascript 复制代码
#[derive(sqlx::FromRow, Serialize)] // 在 Route 结构体上添加 Serialize
pub struct Route {
    pub id: i32,
    pub path: String,
    pub component: String,
    // 其他路由字段...
}

#[derive(sqlx::FromRow, Serialize, Debug)]
pub struct Role {
    pub role_id: i32,
    // 其他角色字段...
}

#[derive(sqlx::FromRow)]
pub struct MenuId {
    pub menu_id: i32,
}

// 详情数据模型
#[derive(Debug, Serialize)]
pub struct MenuResponse<T: Serialize> {
    pub code: i32,
    pub msg: &'static str,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub data: Option<T>,
}

#[derive(sqlx::FromRow, Serialize, Debug, Clone)]
pub struct Menu {
    pub menu_id: i64,                    // 菜单ID
    pub menu_name: String,               // 菜单名称
    pub parent_id: i64,                 // 父菜单ID
    pub order_num: i32,                 // 显示顺序
    pub path: String,                   // 路由地址
    pub component: Option<String>,      // 组件路径
    pub query: Option<String>,          // 路由参数
    pub is_frame: i32,                 // 是否为外链(0是 1否)
    pub is_cache: i32,                 // 是否缓存(0缓存 1不缓存)
    pub menu_type: String,              // 菜单类型(M目录 C菜单 F按钮)
    // pub visible: i32,                 // 菜单状态(0显示 1隐藏)
    // pub status: i32,                  // 菜单状态(0正常 1停用)
    pub perms: Option<String>,         // 权限标识
    pub icon: String,                  // 菜单图标
    // #[serde(skip)]
    // pub create_by: String,             // 创建者
    // #[serde(skip)]
    // pub create_time: Option<NaiveDateTime>, // 创建时间
    // #[serde(skip)]
    pub update_by: String,             // 更新者
    // #[serde(skip)]
    // pub update_time: Option<NaiveDateTime>, // 更新时间
    // #[serde(skip)]
    pub remark: String,                // 备注
}

#[derive(sqlx::FromRow, Serialize, Debug, Clone)]
pub struct MenuTree {
    pub id: i64,                        // 菜单ID
    pub name: String,                   // 菜单名称
    pub path: String,                   // 路由地址
    pub component: Option<String>,      // 组件路径
    pub parent_id: i64,                 // 父菜单ID
    pub sort: i32,                      // 显示顺序
    // pub visible: i32,                  // 是否可见
    pub permission: Option<String>,     // 权限标识
    pub icon: String,                  // 菜单图标
    #[serde(skip_serializing_if = "Option::is_none")]
    pub children: Option<Vec<MenuTree>>, // 子菜单
}
🍎导入数据格式使用
javascript 复制代码
// 类型数据格式
#[allow(unused_imports)]
use crate::common::response::xxx; // 数据格式

3、功能实现

🍎 路由接口token

接下来我们完善路由数据模块部分,使用actix-web框架,并处理JWT认证、数据库查询等,第一步我们需要的就是拿到用户的token,这个代表了用户的所有身份认证的信息

javascript 复制代码
#[allow(unused_imports)]
use actix_web::{web, HttpRequest, HttpResponse, Responder};

#[allow(unused_imports)]

#[allow(unused_imports)]
use crate::common::response::ApiResponse; // 导入 ApiResponse 模型

#[allow(unused_imports)]
use crate::common::response::BasicResponse; // 导入 BasicResponse 模型

pub async fn get_routers(
    req: HttpRequest, 
) -> HttpResponse {
    // 从 header 获取 token
    let token = match req.headers().get("Authorization") {
        Some(t) => t.to_str().unwrap_or("").replace("Bearer ", ""),
        None => return HttpResponse::Unauthorized().json(ApiResponse {
            code: 401,
            msg: "未提供Token",
            data: None::<()>,
        }),
    };
    print!("token: {}", token);
    HttpResponse::Ok().json( ApiResponse{
        code: 200,
        msg: "接口信息",
        data: None::<()>,
    })
}

查看我们的输出,这个时候我们已经拿到了属于我们的token信息

javascript 复制代码
token: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VXXX

🍎 校验token

对于token进行校验,是否正确

javascript 复制代码
// 校验 token
    let token_data = match decode::<Claims>(
        &token,
        &DecodingKey::from_secret(JWT_SECRET),
        &Validation::default(),
    ) {
        Ok(data) => data,
        Err(_) => return HttpResponse::Unauthorized().json(ApiResponse {
            code: 401,
            msg: "Token无效或已过期",
            data: None::<()>,
        }),
    };

🍎 token置换用户信息

拿token换取用户的信息

javascript 复制代码
// 根据 token 里的 username 查询用户信息
    let user = sqlx::query_as::<_, User>("SELECT * FROM sys_user WHERE username = ?")
    .bind(&token_data.claims.username)
    .fetch_one(pool.get_ref())
    .await;

🍎根据用户信息查询我们的用户角色

javascript 复制代码
// 查询用户角色
    let roles: Vec<Role> = match sqlx::query_as::<_, Role>(
        "SELECT * FROM sys_role r 
         INNER JOIN sys_user_role ur ON r.role_id = ur.role_id 
         WHERE ur.user_id = ?"
    )
    .bind(user.user_id)
    .fetch_all(pool.get_ref())
    .await
    {   
        Ok(data) => {
            // 打印解码后的 token 信息
            println!("用户角色==== {:?}", data);
            data
        },
        // Ok(roles) => roles,
        Err(_) => return HttpResponse::InternalServerError().json(ApiResponse {
            code: 500,
            msg: "获取用户角色失败",
            data: None::<()>,
        }),
    };

🍎收集所有角色ID,查询这些角色对应的菜单ID

javascript 复制代码
// 收集所有角色ID
    let role_ids: Vec<i32> = roles.iter().map(|role| role.role_id).collect();
  
   
    // 查询这些角色对应的菜单ID
    let menu_ids: Vec<i32> = if role_ids.is_empty() {
        Vec::new() // 如果没有角色,返回空数组
    } else {
        // 创建 IN 查询的占位符
        let placeholders: String = role_ids.iter()
            .map(|_| "?")
            .collect::<Vec<_>>()
            .join(",");
        
        // 使用占位符构建查询
        let query = format!(
            "SELECT DISTINCT menu_id FROM sys_role_menu WHERE role_id IN ({})",
            placeholders
        );

        // 创建查询并绑定每个参数
        let mut query_builder = sqlx::query_as::<_, MenuId>(&query);
        for role_id in &role_ids {
            query_builder = query_builder.bind(role_id);
        }
        
        match query_builder.fetch_all(pool.get_ref()).await {
            Ok(menu_ids) => menu_ids.into_iter().map(|menu_id| menu_id.menu_id).collect(),
            Err(_) => return HttpResponse::InternalServerError().json(ApiResponse {
                code: 500,
                msg: "获取用户菜单失败",
                data: None::<()>,
            }),
        }
    };

    println!("用户菜单ID==== {:?}", menu_ids);

🍎对菜单ID进行去重,查询ID对应的菜单信息

javascript 复制代码
    // 对菜单ID进行去重
    let unique_menu_ids: Vec<i32> = menu_ids.into_iter().collect::<std::collections::HashSet<_>>().into_iter().collect();


    println!("用户菜单去重==== {:?}", unique_menu_ids);



    // 查询菜单信息
    let menus: Vec<Menu> = if unique_menu_ids.is_empty() {
        Vec::new()
    } else {
        // 创建 IN 查询的占位符
        let placeholders: String = unique_menu_ids.iter()
            .map(|_| "?")
            .collect::<Vec<_>>()
            .join(",");
        
        // 使用占位符构建查询
        let query = format!(
            "SELECT * FROM sys_menu WHERE menu_id IN ({}) ORDER BY parent_id, order_num",
            placeholders
        );
        
        // 创建查询并绑定每个参数
        let mut query_builder = sqlx::query_as::<_, Menu>(&query);
        for menu_id in &unique_menu_ids {
            query_builder = query_builder.bind(menu_id);
        }
        
        match query_builder.fetch_all(pool.get_ref()).await {
            Ok(menus) => {
                // 打印解码后的 token 信息
                println!("用户menus==== {:?}", menus);
                menus
            },
            Err(err) => {
                println!("用户err==== {:?}", err);
                return HttpResponse::InternalServerError().json(ApiResponse {
                    code: 500,
                    msg: "转化菜单信息失败",
                    data: None::<()>,
                })
            },
        }
    };

🍎将菜单信息组合为树形结构,然后我们进行返回

javascript 复制代码
 // 将菜单信息组合为树形结构
    let menus = build_menu_tree(menus);

    print!("menus: {:?}", menus);
     HttpResponse::Ok().json(MenuResponse {
        code: 200,
        msg: "获取成功",
         data: Some(menus),
    })

树结构的函数

javascript 复制代码
 #[derive(sqlx::FromRow, Serialize, Debug, Clone)]
pub struct Menu {
    pub menu_id: i64,                    // 菜单ID
    pub menu_name: String,               // 菜单名称
    pub parent_id: i64,                 // 父菜单ID
    pub order_num: i32,                 // 显示顺序
    pub path: String,                   // 路由地址
    pub component: Option<String>,      // 组件路径
    pub query: Option<String>,          // 路由参数
    pub is_frame: i32,                 // 是否为外链(0是 1否)
    pub is_cache: i32,                 // 是否缓存(0缓存 1不缓存)
    pub menu_type: String,              // 菜单类型(M目录 C菜单 F按钮)
    // pub visible: i32,                 // 菜单状态(0显示 1隐藏)
    // pub status: i32,                  // 菜单状态(0正常 1停用)
    pub perms: Option<String>,         // 权限标识
    pub icon: String,                  // 菜单图标
    // #[serde(skip)]
    // pub create_by: String,             // 创建者
    // #[serde(skip)]
    // pub create_time: Option<NaiveDateTime>, // 创建时间
    // #[serde(skip)]
    pub update_by: String,             // 更新者
    // #[serde(skip)]
    // pub update_time: Option<NaiveDateTime>, // 更新时间
    // #[serde(skip)]
    pub remark: String,                // 备注
}

#[derive(sqlx::FromRow, Serialize, Debug, Clone)]
pub struct MenuTree {
    pub id: i64,                        // 菜单ID
    pub name: String,                   // 菜单名称
    pub path: String,                   // 路由地址
    pub component: Option<String>,      // 组件路径
    pub parent_id: i64,                 // 父菜单ID
    pub sort: i32,                      // 显示顺序
    // pub visible: i32,                  // 是否可见
    pub permission: Option<String>,     // 权限标识
    pub icon: String,                  // 菜单图标
    #[serde(skip_serializing_if = "Option::is_none")]
    pub children: Option<Vec<MenuTree>>, // 子菜单
}

// 扁平化菜单转树形结构
fn build_menu_tree(menus: Vec<Menu>) -> Vec<MenuTree> {
    let mut menu_map = std::collections::HashMap::new();
    let mut root_menus = Vec::new();
    
    // 将菜单按ID分组
    for menu in &menus {
        menu_map.insert(menu.menu_id, MenuTree {
            id: menu.menu_id,
            name: menu.menu_name.clone(),
            path: menu.path.clone(),
            component: menu.component.clone(),
            parent_id: menu.parent_id,
            sort: menu.order_num,
            // visible: menu.visible == 0,
            permission: menu.perms.clone(),
            icon: menu.icon.clone(),
            children: Some(Vec::new()),
        });
    }
 

        // 构建父子关系映射
    let mut parent_child_map = std::collections::HashMap::new();
    for menu in &menus {
        parent_child_map.entry(menu.parent_id).or_insert_with(Vec::new).push(menu.menu_id);
    }
    
    // 递归构建树形结构
    fn build_tree(
        menu_id: i64,
        menu_map: &std::collections::HashMap<i64, MenuTree>,
        parent_child_map: &std::collections::HashMap<i64, Vec<i64>>,
    ) -> MenuTree {
        let mut menu = menu_map.get(&menu_id).unwrap().clone();
        if let Some(child_ids) = parent_child_map.get(&menu_id) {
            menu.children = Some(
                child_ids
                    .iter()
                    .map(|&id| build_tree(id, menu_map, parent_child_map))
                    .collect(),
            );
        }
        menu
    }
    
    // 构建根菜单
    for menu in &menus {
        if menu.parent_id == 0 { // 顶级菜单的parent_id为0
            root_menus.push(build_tree(menu.menu_id, &menu_map, &parent_child_map));
        }
    }

    
    // 递归排序所有层级的菜单
    fn sort_menus(menus: &mut Vec<MenuTree>) {
        menus.sort_by_key(|m| m.sort);
        if let Some(children) = &mut menus.iter_mut().next().map(|m| &mut m.children) {
            if let Some(children) = children {
                sort_menus(children);
            }
        }
    }
    
    sort_menus(&mut root_menus);
    root_menus
}

🍎测试接口,我们返回给前端的格式信息如下:

javascript 复制代码
{
    "code": 200,
    "msg": "获取成功",
    "data": [
        {
            "id": 1,
            "name": "系统管理",
            "path": "/system",
            "component": "Layout",
            "parent_id": null,
            "sort": 1,
            "visible": true,
            "permission": "system:*",
            "icon": "setting",
            "children": [
                {
                    "id": 2,
                    "name": "用户管理",
                    "path": "/user",
                    "component": "system/user/index",
                    "parent_id": 1,
                    "sort": 1,
                    "visible": true,
                    "permission": "system:user:list",
                    "icon": "user",
                    "children": []
                },
                {
                    "id": 3,
                    "name": "角色管理",
                    "path": "/role",
                    "component": "system/role/index",
                    "parent_id": 1,
                    "sort": 2,
                    "visible": true,
                    "permission": "system:role:list",
                    "icon": "team",
                    "children": []
                }
            ]
        },
        {
            "id": 4,
            "name": "菜单管理",
            "path": "/menu",
            "component": "system/menu/index",
            "parent_id": null,
            "sort": 2,
            "visible": true,
            "permission": "system:menu:list",
            "icon": "menu",
            "children": []
        }
    ]
}

4、优化完善

👉 改造树级结构

🍎新旧结构对比

先看看我们之前返回的结构,之前我们旧的结构是从库里面直接取,然后放出来,如果我们想自己组装特定的结构如何做呢

javascript 复制代码
//旧数据结构 
{
    "id": 1,
    "name": "系统管理",
    "path": "system",
    "component": null,
    "parent_id": 0,
    "sort": 1,
    "permission": "",
    "icon": "system",
    "children": [
        {
            "id": 100,
            "name": "用户管理",
            "path": "user",
            "component": "system/user/index",
            "parent_id": 1,
            "sort": 1,
            "permission": "system:user:list",
            "icon": "user",
            "children": []
        },
        {
            "id": 101,
            "name": "角色管理",
            "path": "role",
            "component": "system/role/index",
            "parent_id": 1,
            "sort": 2,
            "permission": "system:role:list",
            "icon": "peoples",
            "children": []
        },
        {
            "id": 108,
            "name": "日志管理",
            "path": "log",
            "component": "",
            "parent_id": 1,
            "sort": 9,
            "permission": "",
            "icon": "log",
            "children": [
                {
                    "id": 500,
                    "name": "操作日志",
                    "path": "operlog",
                    "component": "monitor/operlog/index",
                    "parent_id": 108,
                    "sort": 1,
                    "permission": "monitor:operlog:list",
                    "icon": "form",
                    "children": []
                },
                {
                    "id": 501,
                    "name": "登录日志",
                    "path": "logininfor",
                    "component": "monitor/logininfor/index",
                    "parent_id": 108,
                    "sort": 2,
                    "permission": "monitor:logininfor:list",
                    "icon": "logininfor",
                    "children": []
                }
            ]
        }
    ]
}

想要的结构

javascript 复制代码
{
    "name": "Customer",
    "path": "customer",
    "hidden": false,
    "component": "userManagement/customer/index",
    "meta": {
        "title": "客户管理",
        "icon": "people",
        "noCache": false,
        "link": null
    }
}
🍎方法改良

优化我们的树结构的显示这里我们就可以了

javascript 复制代码
// 返回路由结构体
#[derive(Debug, Serialize, Deserialize,Clone)]
struct MenuMeta {
    pub title: String,
    pub icon: String,
    pub no_cache: bool,
    pub link: Option<String>,
}
#[derive(Debug, Serialize, Deserialize,Clone)]
struct MenuItem {
    pub id: i32,
    pub name: String,
    pub path: String,
    pub hidden: bool,
    pub component: Option<String>,
    pub meta: MenuMeta,
    pub children: Vec<MenuItem>,
}
#[derive(sqlx::FromRow, Serialize, Debug, Clone)]
pub struct MenuTree {
    pub id: i64,                        // 菜单ID
    pub name: String,                   // 菜单名称
    pub path: String,                   // 路由地址
    pub component: Option<String>,      // 组件路径
    pub parent_id: i64,                 // 父菜单ID
    pub sort: i32,                      // 显示顺序
    // pub visible: i32,                  // 是否可见
    pub permission: Option<String>,     // 权限标识
    pub icon: String,                  // 菜单图标
    #[serde(skip_serializing_if = "Option::is_none")]
    pub children: Option<Vec<MenuTree>>, // 子菜单
}

方法

javascript 复制代码
// 扁平化菜单转树形结构
fn build_menu_tree(menus: Vec<Menu>) -> Vec<MenuItem> {
    let mut menu_map = std::collections::HashMap::new();
    let mut root_menus = Vec::new();
    
    // 将菜单按ID分组
    for menu in &menus {
        menu_map.insert(menu.menu_id, MenuItem {
            id: menu.menu_id as i32,
            name: menu.menu_name.clone(),
            path: menu.path.clone(),
            hidden: menu.menu_type != "M",
            component: menu.component.clone(),
            meta: MenuMeta {
                title: menu.menu_name.clone(),
                icon: menu.icon.clone(),
                no_cache: menu.is_cache == 1,
                link: menu.query.as_ref().map_or(None, |q| if q.is_empty() { None } else { Some(q.clone()) }),
            },
            children: Vec::new(),
        });
    }
    
    // 构建父子关系映射
    let mut parent_child_map = std::collections::HashMap::new();
    for menu in &menus {
        parent_child_map.entry(menu.parent_id).or_insert_with(Vec::new).push(menu.menu_id);
    }
    
    // 递归构建树形结构
    fn build_tree(
        menu_id: i32,
        menu_map: &std::collections::HashMap<i32, MenuItem>,
        parent_child_map: &std::collections::HashMap<i32, Vec<i32>>,
    ) -> MenuItem {
        let mut menu = menu_map.get(&menu_id).unwrap().clone();

        if let Some(child_ids) = parent_child_map.get(&menu_id) {
            menu.children = child_ids
                .iter()
                .filter_map(|&id| {
                    if let Some(child) = build_tree_opt(id, menu_map, parent_child_map) {
                        Some(child)
                    } else {
                        None
                    }
                })
                .collect();
        }

        menu
    }

    fn build_tree_opt(
        menu_id: i32,
        menu_map: &std::collections::HashMap<i32, MenuItem>,
        parent_child_map: &std::collections::HashMap<i32, Vec<i32>>,
    ) -> Option<MenuItem> {
        Some(build_tree(menu_id, menu_map, parent_child_map))
    }
    
    // 构建根菜单
    for menu in &menus {
        if menu.parent_id == 0 {
            root_menus.push(build_tree(menu.menu_id, &menu_map, &parent_child_map));
        }
    }

    // 排序函数
    fn sort_menu_items(menus: &mut Vec<MenuItem>) {
        menus.sort_by_key(|m| m.id);
        for menu in menus {
            sort_menu_items(&mut menu.children);
        }
    }

    sort_menu_items(&mut root_menus);
    root_menus
}

使用我们的树方法来转换

javascript 复制代码
let menus = build_menu_tree(menus);
    print!("menus: {:?}", menus);

ok,测试一下,返回的数据已经跟我们预期的想要的数据一致了

相关推荐
SimonKing2 小时前
被卖的Hutool出AI模块了!它如何让Java调用大模型变得如此简单?
java·后端·程序员
凌览3 小时前
Node.js + Python 爬虫界的黄金搭档
前端·javascript·后端
程序新视界3 小时前
在MySQL中,是否可以使用UUID作为主键?
数据库·后端·mysql
计算机学姐3 小时前
基于SpringBoot的公务员考试管理系统【题库组卷+考试练习】
java·vue.js·spring boot·后端·java-ee·intellij-idea·mybatis
欧阳码农3 小时前
AI提效这么多,为什么不试试自己开发N个产品呢?
前端·人工智能·后端
IT_陈寒3 小时前
SpringBoot 3.x 中被低估的10个隐藏特性,让你的开发效率提升50%
前端·人工智能·后端
linuxxx1104 小时前
django中request.GET.urlencode的使用
后端·python·django
JaguarJack4 小时前
PHP 现代特性速查 写出更简洁安全的代码(完结篇)
后端·php
Victor3564 小时前
Redis(107)Redis的键空间通知如何使用?
后端