ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

SpringBoot后台权限管理:动态菜单、数据字典与uniapp商城二开实战

SpringBoot后台权限管理:动态菜单、数据字典与uniapp商城二开实战 简介这套基于SpringBoot的后台管理系统配套uniapp商城移动端面向需要快速搭建权限管理后台与商城前端的Java开发人员可解决系统权限、动态菜单、用户权限、数据字典以及商品分类、登录注册、购物车下单等常见业务需求。资源包为zip压缩格式大小6.08MB共包含903个文件核心以369个Java源码和68个Vue页面为主另有85个JS脚本、48个XML配置、29个JSON数据文件及SQL数据库脚本并含WXSS/WXML等小程序相关文件便于多端运行与二次开发。目前已有471人学习下载适合具备SpringBoot和Vue基础、希望理解权限模型与商城交易流程的开发者。压缩包内提供可运行的后台与商城双端完整工程代码数据库表设计也已为优惠券、拼团、抢购预留字段可直接导入数据库并继续扩展整体目录结构清晰代码与资源配置齐全能显著缩短从环境搭建到功能部署的周期。1. 为什么后台管理系统都绕不开权限与动态菜单做过几个后台管理系统就会发现大部分项目最终都会演化出一个固定套路登录后拿到用户身份前端根据身份渲染不同侧边栏后端每个接口校验是否越权。这个SpringBoot后台管理系统把系统权限、动态菜单、用户权限、数据字典这些基础能力做成了开箱即用的模块配合基于uniapp的商城移动端解决了权限模型复用和商城二开数据打通的问题。对正在做中后台脚手架或者想快速搭电商后台的人来说可以直接拿这套结构对照着自己的项目改。更实际的是它自带的数据库SQL里连优惠券、拼团表都建好了意味着后续做营销功能时不用再从零设计。2. SpringBoot后台的权限模型与数据字典设计2.1 用户-角色-权限的表结构怎么拆常见的权限模型是RBAC这个后台管理系统的核心表是sys_user、sys_role、sys_menu、sys_user_role、sys_role_menu。拆成五张表而不是把权限直接挂在用户上是为了让同一个角色对应多个用户时改一处就能同步所有人。实际建表时关联表不需要自增主键用联合主键即可减少索引体积。CREATE TABLE sys_user_role ( user_id bigint NOT NULL COMMENT 用户ID, role_id bigint NOT NULL COMMENT 角色ID, PRIMARY KEY (user_id, role_id) ) COMMENT用户与角色关联表;这里用联合主键而不是单独加id主要是防止重复绑定。底层ORM用的是MyBatis-Plus通过TableName和TableId映射实体类。sys_menu表里除了菜单名称、路径、组件之外还有一个perms字段用来存放权限标识比如system:user:add这个字段是后面做接口级权限校验的关键。sys_role_menu关联表则决定角色可见的菜单范围。为了让你快速理解每张表的作用我把核心表字段列出来表名关键字段作用sys_userid, username, password, status用户基础信息密码建议BCrypt加密sys_roleid, role_name, role_key角色定义role_key用于代码里识别超级管理员sys_menuid, parent_id, path, component, perms菜单树与权限标识parent_id构成层级sys_user_roleuser_id, role_id用户与角色多对多关联sys_role_menurole_id, menu_id角色与菜单多对多关联设计时注意sys_menu的component字段在SpringBoot接口返回时不要用空字符串占位否则前端动态路由注册时会警告路由无组件。2.2 动态菜单如何从数据库渲染成路由很多后台把菜单写死在前端router里但这个项目把菜单存到sys_menu表用户登录后后端返回当前用户可见的菜单树前端再动态添加路由。这样每个角色看到的侧边栏和可访问路径完全由数据库控制不需要前端维护多套路由表。后端接口返回的menu数据格式一般是{ name: System, path: /system, component: Layout, children: [ { path: user, name: User, component: system/user/index } ] }前端拿到数据后用Vue Router的addRoute逐个注册同时根据meta.roles或meta.permission做路由级过滤。我一般会在main.js里先路由守卫判断登录态再调用getUserMenus接口这样刷新页面时菜单不会丢失。注意动态路由的name不要和静态路由重名否则会覆盖。2.3 数据字典的缓存与回显数据字典管理是后台系统中容易被低估的功能。比如订单状态、商品类型、性别这类字段如果直接在代码里写死运营想加一个选项就必须发版。这套系统提供了dict_type和dict_data两张表一个字典类型下挂多个字典数据项。dict_data表通常包含dict_type_code、label、value、sort、status字段。查询列表时后端会把字典数据加载到Redis缓存避免每次请求都查数据库。缓存key建议设计为dict:{typeCode}value使用Hash结构field是字典值value是标签。回显时前端通过全局过滤器把字典值翻译成文本比如订单状态值为1对应字典项“待付款”。缓存更新策略一般是在后台修改字典后主动删除Redis key下次请求再回源数据库。要注意的是如果使用了本地缓存加Redis两级缓存删除时必须广播到所有节点否则会出现部分机器缓存不失效的问题。比较稳妥的做法是只使用Redis作为统一缓存层后台变更字典时通过RedisTemplate.delete(key)删除这样即使有多个应用节点也都能在下一次请求时拿到最新数据。3. 系统权限落地从登录鉴权到接口级控制3.1 Spring Security还是自定义拦截器权限设计最核心的问题就是选择框架。这类后台管理系统常见的方案有两个Spring Security JWT或者自己写HandlerInterceptor。Spring Security功能全但配置门槛高尤其是动态权限需要重写AccessDecisionManager对新手不友好。自定义拦截器轻量适合权限模型不复杂的系统。这套后台管理项目虽然底层没强制绑定但我倾向于用Spring Security做认证配合注解做接口鉴权。认证流程是登录接口校验用户名密码成功后生成JWT token返回前端前端每次请求把token放在Authorization头后端用一个OncePerRequestFilter解析token并设置SecurityContextHolder。注意token要设置过期时间一般2小时Redis里存一个refreshToken用于续期。Spring Security的SecurityFilterChain配置里需要放行登录接口、验证码接口、静态资源其余接口都需要认证。3.2 权限注解与动态权限校验接口权限用PreAuthorize最直观在Controller方法上写PreAuthorize(hasAuthority(system:user:add))hasAuthority里传的就是权限标识。权限标识是从sys_menu表的perms字段取的比如system:user:add、system:role:edit。这样权限控制点从登录后拿到用户所有权限集合到每次请求时判断当前用户是否包含所需权限。对于动态菜单需要让Security在启动时加载所有菜单里配置的perms并和角色关联。如果某些接口没有配置perm标识要设置一个默认规则通常建议“默认拒绝”即只有显式配置了权限表达式的接口才允许访问避免遗漏造成越权。实际项目中我见过很多团队在Controller上只写了PreAuthorize(isAuthenticated())结果所有人都能访问这等于没做权限控制。3.3 前端按钮级权限控制后端接口控制了还不够前端按钮也需要根据权限显隐。比如用户管理页面“新增”按钮没有system:user:add权限的用户不应该看到。做法是在登录返回的用户信息里带上permissions数组前端用自定义指令v-permission判断。// 自定义指令无权限时移除元素 Vue.directive(permission, { inserted(el, binding) { const required binding.value const has store.getters.permissions.includes(required) if (!has) el.parentNode el.parentNode.removeChild(el) } })这段代码的逻辑是指令绑定到一个按钮元素上binding.value传入权限标识如v-permissionsystem:user:add。inserted是Vue指令的钩子在元素插入DOM时触发。如果当前用户的permissions数组中不包含该标识就直接把元素从DOM中移除。注意按钮级权限只是体验优化真正的安全底线还是后端校验。这里可以总结一下权限控制的三个层级方便对照自己的项目层级实现方式作用路由级动态路由 路由守卫控制页面能否访问菜单级用户菜单树渲染控制侧边栏入口按钮级v-permission指令控制操作入口三个层级缺一不可路由级防止直接输入URL访问菜单级防止看到入口按钮级防止误操作但最终必须有后端接口做最后拦截。4. 基于uniapp的商城移动端接入后台4.1 商城模块的表设计与订单流程商城移动端基于uniapp覆盖了商品分类、用户注册登录、购物车、下单。订单相关表包括shop_goods、shop_cart、shop_order、shop_order_item等。订单表需要存储订单状态、支付状态、总金额、收货地址快照。订单项表保存下单时的商品价格快照避免商品改价后历史订单显示错误。下单流程一般是移动端把购物车勾选的商品ID和数量发给后端后端校验库存和价格生成订单号和订单项然后扣减库存。order表核心字段如下字段类型说明order_novarchar订单号唯一索引user_idbigint下单用户total_amountdecimal订单总金额statustinyint订单状态配合字典表回显address_snapshotvarchar收货地址JSON快照pay_timedatetime支付时间可空注意订单号不能依赖数据库自增建议用雪花算法或时间戳加随机数。这里说一句shop_goods表里需要有一个online_status字段控制商品上下架否则移动端会把所有商品都查出来运营无法控制售卖范围。4.2 登录态与购物车数据同步uniapp端通过uni.request发起请求登录后保存token到uni.setStorageSync。封装request工具时在header里带上token并统一处理401返回码。购物车有两个处理策略未登录时存本地登录后合并到后端。后端购物车表设计为userId加goodsId唯一合并时如果后端已有同款商品则数量相加。购物车数量变化时需要和后端同步同时本地缓存和服务器数据要保持一致。常见做法是登录成功或进入购物车页面时拉取后端购物车列表然后覆盖本地。// 登录成功后同步购物车 async function mergeCart() { const localCart uni.getStorageSync(cart) || [] if (localCart.length 0) { await request({ url: /api/cart/merge, method: POST, data: localCart }) uni.removeStorageSync(cart) } }这段代码是前端购物车合并的典型写法先读本地缓存如果有数据就调用后端merge接口成功后清空本地。后端merge接口需要做批量处理逐条判断商品是否存在、是否在售然后决定新增或累加数量。注意本地购物车存的是商品ID加数量而不是商品快照避免合并时价格过期。4.3 下单时的库存扣减与幂等处理下单是高并发场景容易出问题的地方。如果直接UPDATE goods SET stock stock - #{num} WHERE id ? AND stock #{num}这种原子更新可以防止超卖。订单提交接口需要做幂等否则用户双击提交按钮会生成多个订单。解决方法是前端生成一个requestIdUUID后端根据requestId查Redis是否存在存在则直接返回上次结果。Transactional public OrderResult createOrder(OrderCreateReq req) { // 防重复提交requestId 唯一 Boolean first redisTemplate.opsForValue() .setIfAbsent(order:req: req.getRequestId(), 1, 30, TimeUnit.SECONDS); if (!Boolean.TRUE.equals(first)) { throw new BizException(请勿重复提交); } // 原子扣减库存 int rows goodsMapper.deductStock(req.getGoodsId(), req.getNum()); if (rows 0) { throw new BizException(库存不足); } // 生成订单... }setIfAbsent保证同一requestId只有第一个请求能进入30秒过期时间覆盖用户在下单页面的重复点击。事务注解保证库存扣减和订单生成的一致性。注意这里不能先用select查询库存再更新这种快照模式在并发下会覆盖扣减结果导致超卖。同时下单方法抛出业务异常后事务回滚数据库的库存会恢复但Redis里的requestId标记不会回滚所以下单失败时要把标记删除或者设置较短过期时间否则用户会一直提示“请勿重复提交”。5. 二开与排错这套系统最值得改的几个点5.1 数据库SQL里预留的优惠券、拼团表怎么用项目描述里提到后期更新优惠券、拼团、抢购其实SQL里已经建好了相关表比如coupon、group_buy等。二开时可以直接基于这些表结构去补业务逻辑不需要重新设计。优惠券表关键字段包括优惠券类型、面额、使用门槛、有效期、每人限领数。拼团表需要包含拼团活动ID、商品ID、拼团人数、拼团价格、成团有效期。开发时注意这些表是否已有索引比如coupon表加user_id索引拼团表加activity_id索引避免运营数据量上来后查询变慢。另外如果打算做新零售方向可以基于这些表扩展门店库存字段把线上订单和线下门店库存打通。5.2 前后端联调时常见坑第一个坑是token过期后uniapp端多个请求同时返回401会导致多次跳转登录页。解决方法是做响应拦截401时只处理一次用状态标志位防重。第二个坑是动态菜单刷新后丢失因为store里的数据存在内存中页面刷新就没了。要在路由守卫里判断当前用户有没有菜单数据没有就先拉取再跳转。第三个坑是权限标识大小写不一致数据库里配的是system:user:add前端判断用了System:User:Add结果按钮一直被移除。规范上统一用小写冒号分隔并在代码里做toLowerCase比较。5.3 权限缓存刷新与性能优化后台修改了角色权限后需要让已登录用户的权限立刻生效。常见做法是存JWT时不把权限写进token而是每次请求从Redis获取权限集合权限变更好后删除对应用户的Redis权限缓存下次请求就会回源数据库拿到最新权限。这种方法牺牲了一点性能但解决了权限实时性问题。如果接口量很大可以把菜单数据、字典数据做成本地缓存比如Caffeine。但本地缓存会有数据一致性问题建议在Redis上做二级缓存并通过Redis pub/sub通知各节点失效。SQL层面要注意sys_menu和sys_role_menu的查询尽量用join一次查全量避免N1。最后建议在启动类里加一个CommandLineRunner启动时自动检查超级管理员是否拥有全部菜单权限防止误删导致锁死。本文还有配套的精品资源点击获取
返回列表