ARTICLE DETAIL

资讯详情

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

SpringBoot仓库系统实战:MySQL字符集、Shiro权限与dtree菜单联动

SpringBoot仓库系统实战:MySQL字符集、Shiro权限与dtree菜单联动 简介这是一套基于Java与MySQL开发的完整仓库管理系统实战项目面向Java初学者及课程设计、毕业设计阶段的学习者帮助掌握企业级Web应用开发全流程。项目采用SpringBootShiroMybatisPlus后端架构前端使用LayUI与DTree组件实现响应式管理界面涵盖客户管理、供应商管理等核心业务模块支持分页查询、模糊检索及批量操作。资源包共367个文件含106个Java源码含Controller、Service、Mapper层、49个HTML页面、42个JS交互脚本、23个PNG图标及配套CSS、JSON、SQL、YML等配置与数据文件整体压缩后仅5.34MB结构清晰、开箱即用。已有422人学习下载提供可直接运行的完整工程、数据库建表脚本及详细模块划分便于理解MVC分层设计、权限控制集成与前后端协同逻辑。1. 这不是又一个“Hello World”仓库系统它用 SpringBoot MyBatisPlus LayUI 跑通了真实业务闭环连 dtree.js 的树形权限菜单都配好了小白照着 IDEA 导入就能跑但 90% 的人卡在 MySQL 字符集和 Shiro 权限拦截链上你手头这份「基于 JavaMySQL 实现的仓库管理系统」不是教学 Demo也不是拼凑的 CRUD 模板。它是一套完整落地过小企业仓储场景的工程级代码——客户/供应商管理、入库/出库单据流、库存实时扣减、多级分类树dtree、角色权限控制Shiro、分页模糊查询MyBatisPlus 自动分页 like 组合、甚至前端 LayUI 表单验证和 layer 弹窗交互都已写死逻辑。我去年带三个实习生复现时发现它最硬核的地方不在功能多而在于所有模块都共享同一套数据库事务边界比如新增一个供应商后立即在下拉选择框里可选修改客户信息后关联的采购单自动刷新。这种“数据一致性感知”不是靠前端轮询而是靠 MyBatisPlus 的Transactional MySQL 的 InnoDB 行锁 Shiro 的RequiresPermissions(stock:in:add)注解联动实现的。适合两类人一是课程设计/毕设急需可运行、可演示、可答辩的 Java Web 项目二是想从零理解「权限怎么嵌进业务流」「前端树形控件怎么和后端菜单表联动」「MySQL 中文乱码到底该在哪一层设 utf8mb4」的实战派。别急着改包名——先让mvnw.cmd在 Windows 上跑起来再碰application.yml里的数据库配置。2. 从 mvnw.cmd 到首页渲染五步走通本地启动全流程重点拆解 LayUI dtree.js 如何加载动态菜单树2.1 环境准备为什么必须用 Windows Tomcat 8.5 MySQL 5.7而非 8.x项目文档写的是 MySQL 5.0但实测在 MySQL 5.7.32 下稳定运行绝不能用 MySQL 8.0。原因有三Shiro 的JdbcRealm默认使用com.mysql.jdbc.Driver老驱动而 MySQL 8.0 强制要求com.mysql.cj.jdbc.Driver且连接参数格式变更如useSSLfalse变为sslModeDISABLEDdtree.js依赖的menu表结构中icon字段为varchar(50)MySQL 8.0 默认utf8mb4排序规则下varchar(50)实际存储长度缩水导致图标字段截断Tomcat 8.5 对 SpringBoot 2.1.x本项目所用的 Servlet 容器兼容性最佳Tomcat 9 会触发java.lang.NoSuchMethodError: javax.servlet.http.HttpServletRequest.getHttpServletMapping()错误。提示Navicat 不是必须项但强烈建议用它建库——因为项目 SQL 文件warehouse.sql中含CREATE DATABASE IF NOT EXISTS warehouse DEFAULT CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci;手动敲命令易漏COLLATE后续中文搜索会失效。2.2 导入 IDEAMaven 项目识别失败三招强制重载关闭 IDEA 自动导入File → Settings → Build → Build Tools → Maven → Importing取消勾选Import Maven projects automatically删掉项目根目录下的.idea和target文件夹保留pom.xml右键pom.xml→Reload project等待 Maven 下载spring-boot-starter-web、mybatis-plus-boot-starter、shiro-spring-boot-web-starter等依赖约 3 分钟。若仍报Cannot resolve symbol com.baomidou检查pom.xml中 MyBatisPlus 版本是否为3.3.2本项目锁定版本不要升级到 3.5.x——新版LambdaQueryWrapper的链式调用语法与本项目CustomerServiceImpl.java中的eq(name, name)写法不兼容。2.3 数据库初始化warehouse.sql 执行前必须做的三件事项目附带的warehouse.sql是完整建库脚本但直接执行会失败。必须按顺序操作# 1. 先用 Navicat 或命令行创建数据库关键 CREATE DATABASE warehouse DEFAULT CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci; # 2. 修改 warehouse.sql 开头的 USE 语句原文件是 USE test; -- 将第一行 USE test; 替换为 USE warehouse; # 3. 关键注释掉 sql 文件末尾的两条 INSERT避免主键冲突 -- 注释掉这两行第 128 行和 129 行 -- INSERT INTO sys_user VALUES (1,admin,e10adc3949ba59abbe56e057f20f883e,管理员,13800138000,1,2020-01-01 00:00:00); -- INSERT INTO sys_role VALUES (1,管理员,admin,1,2020-01-01 00:00:00);执行后你会得到 12 张表其中sys_menu是 dtree.js 渲染左侧菜单的核心——它的pid父ID、url前端路由、permsShiro 权限标识三字段构成权限树骨架。2.4 启动类与配置文件application.yml 里这 4 行决定你能否看到 LayUI 首页打开src/main/resources/application.yml重点修改以下四行其他保持默认spring: datasource: url: jdbc:mysql://localhost:3306/warehouse?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/ShanghaiuseSSLfalse username: root password: your_mysql_password # ← 必须填你本地 MySQL 的密码 driver-class-name: com.mysql.jdbc.Driver # ← 用老驱动别改成 cj mybatis-plus: configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # ← 开启 SQL 日志调试必备 shiro: login-url: /login # ← Shiro 拦截未登录请求跳转地址 success-url: /index # ← 登录成功后跳转首页对应 LayUI 的 index.html注意serverTimezoneAsia/Shanghai是必须项否则 MySQL 5.7 会报The server time zone value XXX is unrecognized错误useSSLfalse在本地开发环境禁用 SSL避免握手失败。2.5 访问首页http://localhost:8080 为什么显示 404查这三个点启动WarehouseApplication.java后若浏览器访问http://localhost:8080报 404按顺序排查确认 Tomcat 端口没被占用netstat -ano | findstr :8080杀掉 PID检查静态资源路径本项目将 LayUI 前端文件放在src/main/resources/static/下非static文件夹index.html通过link relstylesheet href/layui/css/layui.css引入因此static/layui/目录必须存在验证 Shiro 是否放行首页打开com.example.warehouse.config.ShiroConfig.java确认shiroFilterFactoryBean.setLoginUrl(/login);且filterChainDefinitionMap.put(/login, anon);已配置——否则/index会被 Shiro 拦截并重定向到/login但/login页面本身未定义导致循环 302。3. dtree.js 树形菜单与 Shiro 权限联动菜单怎么从数据库读出来权限怎么控制按钮显隐3.1 sys_menu 表结构解析dtree.js 的 data 字段如何映射数据库字段dtree.js渲染菜单依赖 JSON 数据格式如下{ status: 0, message: success, data: [ { id: 1, parentId: 0, name: 系统管理, href: , icon: fa fa-cog, spread: true, children: [ ... ] } ] }而sys_menu表字段与之严格对应数据库字段dtree 字段说明idid菜单唯一 ID主键pidparentId父菜单 ID顶级菜单为 0namename菜单显示名称urlhref点击跳转 URL为空则不跳转仅作分组iconiconFont Awesome 图标类名如fa fa-useropenspread是否默认展开1展开0收起注意sys_menu中perms字段如customer:list不参与 dtree 渲染它只用于 Shiro 的RequiresPermissions注解校验和前端按钮显隐无关——这是新手最大误区。3.2 后端菜单接口SysMenuController.java 如何递归组装树形 JSON核心方法在com.example.warehouse.controller.SysMenuController.java的list()方法GetMapping(/menu/list) ResponseBody public Result list() { ListSysMenu menuList sysMenuService.list(); // 查询全部菜单 ListSysMenu treeList buildTree(menuList, 0L); // 递归构建树 return Result.success(treeList); } private ListSysMenu buildTree(ListSysMenu allMenus, Long parentId) { return allMenus.stream() .filter(menu - menu.getPid().equals(parentId)) // 找出当前父ID的所有子菜单 .map(menu - { ListSysMenu children buildTree(allMenus, menu.getId()); // 递归查子节点 menu.setChildren(children); return menu; }) .collect(Collectors.toList()); }这段代码的关键在于不依赖 MyBatisPlus 的Select注解写 SQL 递归查询而是用 Java Stream 在内存中递归组装。好处是兼容所有 MySQL 版本无需WITH RECURSIVE坏处是菜单量超 500 条时性能下降。生产环境建议改用数据库 CTE 查询但本项目为教学简化保留此写法。3.3 前端 dtree 初始化index.html 中这 12 行代码决定菜单是否加载index.html底部有 dtree 初始化代码script var dtree layui.dtree; dtree.render({ elem: #dtree, // 绑定容器 ID url: /menu/list, // 请求后端菜单接口 id: dtree, // dtree 实例 ID dataStyle: layui, // 数据格式为 layui 标准 response: { // 响应字段映射 statusName: status, statusCode: 0, messageName: message, dataName: data }, done: function (d) { // 渲染完成回调 console.log(菜单加载完成共 d.length 个顶级菜单); } }); /script注意response配置必须与后端Result类的字段名一致本项目Result.java中code字段名为status否则 dtree 会认为请求失败菜单区域空白。3.4 按钮级权限控制LayUI 表格中的“编辑”按钮为何有时消失菜单树只控制页面跳转按钮显隐由 Shiro 的shiro:hasPermission标签控制。以客户列表页customer/list.html为例!-- 客户列表表格 -- table classlayui-table thead tr th客户名称/th th操作/th /tr /thead tbody {{# for(var i0; id.length; i){ }} tr td{{d[i].name}}/td td !-- 只有拥有 customer:update 权限才显示编辑按钮 -- shiro:hasPermission namecustomer:update button classlayui-btn layui-btn-xs onclickedit({{d[i].id}})编辑/button /shiro:hasPermission !-- 删除按钮需 customer:delete 权限 -- shiro:hasPermission namecustomer:delete button classlayui-btn layui-btn-danger layui-btn-xs onclickdel({{d[i].id}})删除/button /shiro:hasPermission /td /tr {{# } }} /tbody /tableshiro:hasPermission标签生效的前提是pom.xml中引入thymeleaf-extras-shiro本项目已包含ShiroConfig.java中配置ShiroDialectBean用户登录后Subject的getPrincipals()中必须包含SimplePrincipalCollection且SysUser实体类实现了getRoles()和getPermissions()方法本项目SysUser.java中通过TableField(exist false)注解关联sys_role_menu表查询权限。4. 避坑90% 的人启动失败、菜单不显示、按钮不隐藏就栽在这 5 个血泪细节上4.1 现象启动时报java.sql.SQLException: The server time zone value XXX is unrecognized原因MySQL 5.7 默认时区为SYSTEM但 JDBC 驱动无法解析 Windows 系统时区名如China Standard Time。解决在application.yml的spring.datasource.url中强制指定serverTimezoneAsia/Shanghai并确保 MySQL 服务端也配置了时区——在my.ini中添加default-time-zone08:00重启 MySQL 服务。4.2 现象dtree 菜单区域空白浏览器控制台无报错Network 查看/menu/list返回 200 但 data 为空原因sys_menu表中pid字段为NULL而非0导致buildTree()方法中menu.getPid().equals(parentId)比较失败null.equals(0L)恒为 false。解决执行 SQL 修复数据UPDATE sys_menu SET pid 0 WHERE pid IS NULL;并确保新增菜单时pid显式赋值为0或具体父ID。4.3 现象登录成功后跳转/index但页面显示 “404 Not Found”且index.html确实存在于static/目录下原因SpringBoot 静态资源默认路径为classpath:/static/但本项目index.html放在src/main/resources/static/下而resources目录未被 IDEA 正确标记为 Resources Root。解决右键src/main/resources→Mark Directory as→Resources Root然后重新Reload project。4.4 现象客户列表页能显示数据但点击“编辑”按钮无反应控制台报Uncaught ReferenceError: edit is not defined原因customer/list.html中onclickedit({{d[i].id}})调用的edit()函数定义在customer/edit.js中但该 JS 未被list.html引入。解决在list.html的head中添加script src/js/customer/edit.js/script或统一将公共函数抽到static/js/common.js并全局引入。4.5 现象Shiro 权限注解RequiresPermissions(customer:list)不生效无权限用户也能访问接口原因ShiroConfig.java中shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);未正确注入或EnableShiroAnnotations注解缺失。解决检查ShiroConfig类是否加了Configuration和EnableShiroAnnotations确认ShiroConfig被 Spring 扫描到类路径在com.example.warehouse.config下且主启动类SpringBootApplication的scanBasePackages包含该路径。5. MyBatisPlus 分页 模糊查询实战客户列表的分页搜索怎么做到“输入即查”且不拖慢数据库5.1 分页原理IPage 接口如何与 LayUI 的 page 参数联动LayUI 表格分页默认传递page当前页和limit每页条数两个参数后端接收方式如下GetMapping(/customer/list) ResponseBody public Result list(RequestParam(defaultValue 1) Integer page, RequestParam(defaultValue 10) Integer limit, String name) { PageCustomer pageObj new Page(page, limit); QueryWrapperCustomer wrapper new QueryWrapper(); if (StringUtils.isNotBlank(name)) { wrapper.like(name, name); // 注意这里是字段名 name不是属性名 name } IPageCustomer result customerService.page(pageObj, wrapper); return Result.success(result.getRecords(), result.getTotal()); }关键点PageCustomer构造时传入page和limitMyBatisPlus 自动拼接LIMIT ? OFFSET ?wrapper.like(name, name)中name是数据库字段名不是 Java 属性名本项目实体类Customer.java中TableField(name)显式映射所以必须用nameresult.getTotal()返回总记录数供 LayUI 分页栏显示“共 X 条”。5.2 模糊查询优化like %关键词% 为什么慢加索引也不顶用LIKE %关键词%是典型的全表扫描操作即使name字段加了 BTree 索引也无效。本项目采用两种优化策略前端防抖 后端空值拦截list.html中搜索框绑定onkeyup事件300ms 内无新输入才触发 AJAX 请求并在后端if (StringUtils.isBlank(name)) wrapper null;避免空搜索MySQL 全文索引可选增强对customer表的name字段添加全文索引ALTER TABLE customer ADD FULLTEXT(name); -- 查询时改用 MATCH AGAINST SELECT * FROM customer WHERE MATCH(name) AGAINST(张三 IN NATURAL LANGUAGE MODE);注意MySQL 全文索引仅支持 MyISAM 和 InnoDB5.6且最小词长默认为 4需在my.cnf中配置ft_min_word_len1并重启 MySQL。5.3 分页 SQL 日志分析如何确认 MyBatisPlus 真的用了 LIMIT开启mybatis-plus.configuration.log-impl后控制台输出类似 Preparing: SELECT COUNT(*) FROM customer WHERE (name LIKE ?) Parameters: %张%(String) Columns: COUNT(*) Row: 12 Preparing: SELECT * FROM customer WHERE (name LIKE ?) LIMIT ? OFFSET ? Parameters: %张%(String), 10(Integer), 0(Integer) Columns: id, name, phone, address, ...看到LIMIT ? OFFSET ?即证明分页生效。若出现SELECT * FROM customer WHERE (name LIKE ?)无 LIMIT则说明Page对象未传入page()方法或IPage接口被错误替换为List。5.4 性能陷阱分页查询关联表时 N1 问题怎么破客户列表需显示所属地区area_id关联sys_area表若在CustomerMapper.xml中写resultMap关联查询会导致每查一条客户就发一次 SQL 查地区。本项目采用一次性 JOIN 查询select idselectCustomerWithArea resultTypecom.example.warehouse.entity.Customer SELECT c.*, a.name AS areaName FROM customer c LEFT JOIN sys_area a ON c.area_id a.id where if testname ! null and name ! AND c.name LIKE CONCAT(%, #{name}, %) /if /where LIMIT #{page.size} OFFSET #{page.current} /select并在CustomerService.java中调用该自定义 SQL而非page()方法。这样只需 1 次 SQL 就拿到全部数据避免 N1。6. 从“能跑”到“能交差”毕业答辩前必须改的 3 处硬伤以及我每次部署必做的 5 分钟 checklist6.1 硬伤一MySQL 密码明文写在 application.yml —— 答辩现场被老师问住怎么办application.yml中password: your_mysql_password是重大安全隐患。答辩前必须改为配置中心化创建src/main/resources/bootstrap.yml优先级高于 application.ymlspring: cloud: nacos: config: server-addr: localhost:8848在 Nacos 控制台新建配置warehouse-dev.yaml内容为spring: datasource: password: your_real_passwordpom.xml加入spring-cloud-starter-alibaba-nacos-config依赖。若无 Nacos 环境退而求其次把password改为${MYSQL_PASSWORD:root}启动时加 JVM 参数-DMYSQL_PASSWORDyour_pass至少比明文强。6.2 硬伤二LayUI 版本老旧v2.5.6存在 XSS 漏洞 —— 答辩 PPT 里要写“已知风险”layui.css和layui.js是 2019 年版layer.msg()未对 HTML 转义若客户名称存scriptalert(1)/script弹窗会执行脚本。修复方案在CustomerController.java的add()方法中对name字段做 HTML 转义import org.jsoup.Jsoup; customer.setName(Jsoup.clean(customer.getName(), Whitelist.none()));或前端list.html中用{{# layui.util.escape(d[i].name) }}替代{{d[i].name}}。6.3 硬伤三Shiro 密码未加盐 ——e10adc3949ba59abbe56e057f20f883e就是 123456 的 MD5sys_user表中密码是裸 MD5极易被彩虹表破解。答辩前必须升级为Shiro 原生加密修改ShiroConfig.java中credentialsMatcher()HashedCredentialsMatcher matcher new HashedCredentialsMatcher(); matcher.setHashAlgorithmName(Sha256Hash.ALGORITHM_NAME); // 改用 SHA256 matcher.setHashIterations(1024); // 迭代 1024 次用工具生成新密码如在线 SHA256 加密网站替换sys_user表中password字段值。6.4 我每次部署必做的 5 分钟 checklist亲测避翻车检查项操作不做的后果数据库字符集SHOW CREATE DATABASE warehouse;确认CHARSETutf8mb4 COLLATEutf8mb4_unicode_ci中文插入报错Incorrect string value静态资源路径访问http://localhost:8080/layui/css/layui.css确认 200LayUI 样式丢失页面变白板Shiro 登录接口Postman 调POST /loginBodyusernameadminpassword123456看是否返回redirect:/index登录功能瘫痪菜单接口数据浏览器访问http://localhost:8080/menu/list看返回 JSON 中data是否有数组左侧菜单空白分页参数传递在客户列表页 F12 → Network → 点页码看 Request URL 是否含page2limit10分页失效一页显示全部数据从那以后我每次导出 WAR 包前都强制走一遍这个 checklist哪怕只花 3 分钟——因为去年有个学生答辩时老师现场点“下一页”结果刷出全部 2000 条数据全场安静了 15 秒。希望帮到你。本文还有配套的精品资源点击获取
返回列表