尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Spring Boot仓库管理系统实战:从业务拆解到并发控制与权限设计

Spring Boot仓库管理系统实战:从业务拆解到并发控制与权限设计 1. 项目概述与核心痛点1.1 为什么我推荐用Spring Boot做仓库管理系统在讲这个项目的具体实现之前先聊聊我个人的一些看法。仓库管理系统WMS这东西业务逻辑说起来并不复杂无非就是入库、出库、库存盘点三件套但真要落地的时候各种边界情况能把人折磨到怀疑人生。我见过不少学生或者刚转行的朋友一上来就纠结要不要上微服务要不要用Redis缓存结果还没等环境配好时间已经耗掉大半最后交上去的课程设计连核心流程都跑不通。这个项目选择Spring Boot作为基础框架在我看来是一个非常务实的选择。Spring Boot最核心的优势在于它的自动化配置能力它帮你把Spring生态里那一大堆繁琐的XML配置全部都干掉你只需要在pom文件里引入对应的starter依赖框架会自动根据你的依赖情况完成Bean装配、数据源配置、事务管理等基础工作。对于课程设计和毕业设计这种有严格时间节点的场景来说这意味着你可以把绝大部分精力投入到业务逻辑本身而不是和配置文件死磕。我最初接手这个项目的时候曾经认真比较过SSHSpringStrutsHibernate和SSMSpringSpringMVCMyBatis这两套传统组合。SSH的问题在于Struts2和Hibernate的学习成本偏高而且配置是出了名的繁琐SSM虽然比SSH轻量不少但依然需要手动整合Spring和SpringMVC。相比之下Spring Boot不但内置了Tomcat、直接以jar包运行还提供了Actuator、DevTools等一系列开箱即用的能力在开发调试阶段非常舒服。1.2 网格仓是什么和普通仓库有什么不同这个项目的标题里有个关键词叫“网格仓”可能有些朋友会比较陌生。简单解释一下网格仓的概念这是一种围绕社区电商场景产生的新型仓储模式它的特点是把一个大范围的配送区域划分成若干个网格单元每个网格单元对应一个前置仓负责该区域内的商品存储、拣选和短途配送。相比传统的大型中心仓网格仓的仓体面积更小、分布更密集、库存深度更浅但是SKU种类往往不输给大仓这就在库存管理层面带来了新的挑战。传统仓库的库存管理偏重于存放效率和批量作业而网格仓因为贴近消费端订单波动大、品类分散对库存准确率和实时性的要求更高。所以在设计这套系统的时候我并没有简单地把教科书上那一套进销存逻辑照搬过来而是针对网格仓的特点做了一些适配比如增加了库位管理、波次拣选、配送路线规划等功能模块。如果读者拿到的项目源码里这几个模块存在恭喜你这套系统的完整度远超一般的课程设计作品。1.3 这个项目适合谁来参考这套系统适合谁大致是这三类人第一类是正在做Java课程设计或者毕业设计、需要一套能跑通完整业务流程的Spring Boot项目的在校学生第二类是准备转行Java开发、需要一个项目来练手和充实简历的培训学员你可以通过阅读这套源码来理解企业级项目的代码组织方式第三类是对仓储业务感兴趣、想了解WMS系统内部实现细节的初级开发者。我这里讲的很多经验都是基于这套系统的实际开发过程总结出来的不涉及平台本身。源码结构和数据库设计都很清爽没有过度设计也没有堆砌无用的第三方组件这一点对初学者特别友好。下面我会带着大家把需求、技术方案、数据库设计、核心功能代码、常见坑这几个部分逐一拆解争取一篇讲透。2. 需求分析与功能模块拆解2.1 业务流程梳理从商品入库到出库的全链路设计系统之前第一步永远是梳理业务流程业务没想清楚代码写得再漂亮都是空中楼阁。我习惯用一个简单的流程图先在心里建模不用画得很规范关键在于把每个节点上的数据交互理清楚。这套仓库管理系统的核心流程可以归纳成下面这条链路商品到货后由仓库管理员创建入库单入库单里记录供应商信息、商品明细、预期入库数量货物实际清点入库后系统需要把货品分配到具体的库位上同时更新对应商品的库存数量。这就完成了入库环节的闭环它直接决定了系统库存数据的起点是否准确。出库流程刚好相反。门店或者配送终端发起要货申请后仓库管理员创建出库单系统完成库存预占并生成拣货任务拣货员按库位信息拣货、复核、交接后系统扣减库存并登记出库明细。这里有一个容易忽略的细节预占库存和实际扣减库存是两回事中间隔着拣货和复核两到三个环节所以数据表设计里一定要区分清楚状态。除了这两个核心流程还有库存盘点、库存调整、库存预警、统计报表等辅助模块。盘点是为了修正实际库存与系统库存之间的差异很多时候差异不是因为系统算错了而是因为货品破损、丢失、错放等物理原因造成的所以盘点结果需要走审批流库存预警则是根据每个SKU的最低库存阈值自动生成补货建议这些都是加分项。2.2 四个核心角色管理员、仓管员、拣货员、配送员系统设计里一个非常关键的决策是角色权限的设计。我没有采用常见的简单方案——把用户表和角色绑死而是直接实现了基于RBACRole-Based Access Control基于角色的权限控制的权限模型这也是企业级系统里最主流的设计方式。系统中一共设计了四类角色每一类角色的职责边界非常清楚。管理员负责系统配置、用户管理、基础数据维护不直接介入日常的出入库操作仓管员负责入库单审核、出库单创建、库存调整、盘点任务发起这些核心业务动作拣货员角色主要面向移动端或手持终端专注于执行拣货任务、上报拣货异常配送员负责查看配送订单、确认装车、回传签收信息。四类角色各管一摊互不越权在代码层面通过拦截器和注解的方式统一校验权限页面上的操作按钮也会根据当前用户的角色动态渲染。这种设计在答辩的时候非常加分因为评委一眼就能看出你考虑了业务中真实的岗位分化而不是只做了一个增删改查的初级CRUD。2.3 非功能性需求并发控制与系统可靠性很多人做课程设计的时候只关注功能“能不能跑通”完全不考虑并发和数据一致性的问题这其实是一个很大的认知误区。仓储场景是最典型的并发密集场景尤其是出库的时候多个拣货员同时操作、多个订单同时预占库存一旦并发处理不当就会出现超卖、负库存这些极其尴尬的数据。我在设计这套系统时把并发控制和数据一致性放在了和功能需求同等重要的位置上好好考虑。具体到技术实现库存扣减操作我采用了乐观锁机制在库存表里增加了一个version字段每次更新库存时检查并更新这个版本号为了防止并发场景下出现库存超扣我还在关键操作上加上了数据库级别的唯一约束和行级锁。事务管理方面凡是涉及多表联动的业务方法都用Transactional注解包裹起来同时根据业务类型合理选择了默认的传播级别。至于系统可靠性这个阶段做到操作日志记录和异常兜底处理就够了监控告警那一套对课程设计来说属于过度设计反而浪费时间。3. 技术选型全景解析3.1 后端核心为什么是Spring Boot加MyBatis-Plus项目后端以Spring Boot为核心版本选的是2.7.x。这个版本是目前国内企业里使用率最高的一个稳定版本生态成熟、资料丰富遇到问题搜解决方案基本一搜一个准。我没有盲目追求最新版本因为新版本往往伴随着一些不兼容的变更对做项目来说稳字当头才是第一位的。持久层框架我用的MyBatis-Plus。它的核心价值是提供了通用Mapper和通用Service内置了大量单表CRUD方法可以省掉绝大部分重复的XML映射配置。我在开发时最常用的功能主要有这几个一是Wrapper条件构造器可以非常方便地拼装动态查询条件比手写XML要高效得多二是分页插件PaginationInnerInterceptor配置好之后分页查询只需要调用Page对象三是逻辑删除在实体类字段上标注TableLogic注解删除操作就自动变成更新逻辑状态字段这在实际项目里几乎是必须的因为库存数据不允许被物理删除。做过传统SSH项目的人应该都有这种体会以前写一个简单的分页查询又要在DAO层写接口、又要在XML里写SQL语句、还要自己拼装pageBean一套下来代码量非常庞大。有了MyBatis-Plus之后这些复杂度都不由你管了集中精力在真正的业务逻辑上极大的提高了程序员的工作效率。3.2 前端方案选型模板引擎还是前后端分离这个项目的前端我选择了模板引擎技术栈也就是Thymeleaf加Bootstrap加jQuery。可能有人会觉得这套组合不够新潮但我要说明一下选它的理由。对于课程设计和毕业设计来说前后端分离意味着你至少要多维护一套Node.js环境、一套Vue或React工程再加上跨域处理、接口鉴权这些额外的工作量而且要展示的内容还对不上——答辩现场你总不能把IDEA和VSCode一起开着给大家现场演吧用Thymeleaf做服务端渲染的好处一目了然页面、接口、数据三者都在同一个应用内不存在跨域问题模型数据可以直接通过Model对象传到视图表单提交、Ajax请求、页面跳转这些操作非常直观。配合Bootstrap的栅格布局和组件样式不需要额外的前端打包构建一个静态资源目录就全解决了整体页面效果也不差在Chrome开发工具里调试、通过网络面板查看请求链路都直观清楚。等到了工作岗位上需要迁移成前后端分离后端只出JSON接口前端的对接方式也完全可以迁移过去。3.3 权限认证方案JWT还是Session权限认证部分我的选择是SpringSession加Redis实现会话共享而不是JWT。简单说一下这个选型的思考过程。JWT方案现在确实很流行它的特点是服务器端不存储会话状态每次请求都携带一个自包含的Token这种方式特别适合前后端完全分离、需要水平扩展的架构。但是在这个项目里我们的页面是服务端渲染的Thymeleaf拿到登录用户信息后还需要在页面渲染用户名、角色菜单这个信息JWT很难直接读取必须再调一次解析Token的接口非常繁琐。而SpringSession加Redis的方案天然贴合服务端渲染的架构用户在登录的时候自动生成会话标识服务端把用户状态存到Redis里后续的每个请求通过会话标识来获取用户上下文不管页面侧还是接口侧都需要这个上下文一致性好。再一个实际原因是SpringSession加Redis这种方案具备可扩展性。如果以后这个项目要部署成集群环境只需要在不改业务代码的前提下把每个节点的Session数据统一存到同一个Redis里就能实现多节点会话共享真正做到无缝扩。4. 系统设计与数据库建模4.1 数据库整体设计思路仓储系统的数据模型是整个项目的核心资产。我不打比方直接说实话如果你看过市面上很多糟糕的课程设计源码会发现它们最大的问题不是功能太少而是数据库表设计得一团糟几张表之间没有任何关联规则外键不建约束不够主键用int自增还好有的用随机字符串凭运气找记录。看的看的心累。在这套系统里我遵循了几个核心设计原则。第一主键统一采用Long类型的雪花算法ID这能防爬防遍历而且分库分表之后依然可以保持全局唯一不会像自增主键那样产生冲突。第二每张业务表都包含create_time和update_time字段用数据库的默认值来自动维护将来排查数据问题的时间成本会大大降低。第三所有涉及金额、数量的字段都用BigDecimal或者Long而不是float/double避开精度损失这个经典大坑。第四逻辑删除标记deleted字段统一加到每条业务表上查询时由MyBatis-Plus自动处理。4.2 核心数据表结构用户、角色、菜单、仓库数据模型大体上分为两大块权限模型和仓储业务模型。权限模型是标准的RBAC四件套sys_user用户表、sys_role角色表、sys_menu菜单权限表、sys_user_role用户角色关联表、sys_role_menu角色菜单关联表。sys_user表存用户的登录名、加密后的密码、手机号、状态字段密码加密我使用的BCrypt算法这个是Spring Security官方推荐的密码哈希算法内置加盐机制比MD5那种裸哈希方案要靠谱得多sys_role和sys_menu两张表分别定义角色和菜单菜单设计成了树形结构父节点通过parent_id字段关联两张关联表则实现了用户与角色、角色与菜单之间的多对多关系映射。仓储业务模型涉及的表格较多核心的包括wms_product商品表、wms_category商品分类表、wms_supplier供应商表、wms_warehouse仓库表、wms_storage_location库位表、wms_inbound_order入库单表、wms_inbound_order_item入库单明细表、wms_outbound_order出库单表、wms_outbound_order_item出库单明细表、wms_inventory库存表、wms_stock_record库存流水表、wms_stock_check盘点任务表。这里特意将入库单和入库单明细分成两张表出库单同理这是数据建模里非常经典的主子表设计模式。订单表记录单据级别的信息如单号、状态、关联仓库和供应商明细表记录商品级别的信息如SKU、数量、批次号。每次入库或出库的同时会写一条库存流水这样操作痕迹可追溯真出了问题可以通过流水表快速定位用不着全表扫描去猜这体验完全是两回事。4.3 核心表字段设计与关系说明我把其中一部分核心表的关键字段设计整理出来供大家对照参考。wms_product商品表的关键字段包括sku_code商品编码唯一约束、product_name商品名称、category_id关联分类、specification规格型号、unit计量单位、purchase_price和sale_price价格字段用BigDecimal、stock_warning_low最低库存预警值、status上下架状态。这里有一个容易被忽略的点sku_code字段必须建立唯一索引否则同一编码可以重复录入后面做接口联查时全乱套了。wms_inventory库存表是这个系统的中枢表它对应关系是“一个仓库下的某个库位存放某个商品”的库存记录。关键字段包括warehouse_id、storage_location_id、product_id、quantity当前库存量、locked_quantity锁定库存量就是被出库单预占但没有实际出库的数量、version乐观锁版本号。available_quantity这个可用库存值不存表而是实时计算为quantity - locked_quantity因为冗余存储很容易不一致。这个设计逻辑一定要在答辩时讲清楚它体现了你对并发和一致性的深刻认知。wms_stock_record库存流水表负责记录每次库存变动的明细字段包括record_type流水类型入库类型/出库类型/盘点调整类型、product_id、warehouse_id、change_type增加还是减少、change_quantity变化数量、before_quantity和after_quantity变动前后库存快照、business_order_no关联业务单号、create_by操作人。before_quantity和after_quantity这两个字段极其关键它们让你可以在事后恢复任何时间点的库存视图。4.4 路由设计与接口规划后端接口设计遵循RESTful风格统一以/api为前缀按资源名称分词划分路由。比如入库单相关的接口大致是POST /api/inbound/order // 创入库单 GET /api/inbound/order/page // 分页查询入库单 POST /api/inbound/order/audit // 审核入库单 POST /api/inbound/order/complete // 确认入库完成出库单模块、库存模块、盘点模块的接口命名逻辑相同前后端联调时只要按这个规则来基本不需要频繁核对表名和字段名心智负担很低。同时接口层面做了一个统一的响应对象R包含code、message、data三个字段。所有业务接口返回这个对象前端通过code是否为200判断业务成功或失败统一异常处理器会捕获业务异常并将错误消息返回给前端弹窗展示这个过程对用户是可感知的体验优化。5. 核心功能模块实战拆解5.1 入库流程从创建到入库完成的四个状态入库模块是整个系统里我最想展开讲的部分因为它的业务状态最复杂。入库单从创建到最终完成一共经历四个状态待审核、已审核、入库中、已完成。每一步都对应一个独立的后端方法。流程开始时仓管员提交入库单包含供应商和商品明细系统自动生成入库单号编码规则可以自己定义比如RK加年月日加流水号状态置为待审核。审核环节不简单做字段检查还需要校验商品是否存在、SKU编码是否合法、单价是否合理审核通过后状态变为已审核。接下来是实物入库环节货物到达后仓管员在系统里为每个商品明细分配库位。库位分配需要校验当前仓库下该库位是否已被占用、库位容量是否足够分配完成后系统会为每个库位生成一张库位分配清单拣货员凭单上架。库存变更的逻辑是真正触发的按分配到的库位增加库存并写入库存流水。最后自动把入库单状态刷新为已完成同时向供应商账户结算应付金额。这一整套流程中我尤其推荐在课程设计文档里完整记录下来的是每个状态变更在代码里都伴随了操作日志记录日志内容包含操作人、操作时间、操作内容这一点在答辩演示的审核阶段特别有画面感。5.2 出库流程预占、拣货、扣减三步曲出库流程相对复杂一些。业务触发方式是门店或终端用户在系统里提交要货申请仓库管理员看到要货单后合单创建出库单。出库单创建成功的一瞬间系统做的事是在库存表上对每个商品执行锁定库存操作我的实现是执行一条带条件的update语句UPDATE wms_inventory SET locked_quantity locked_quantity #{quantity} WHERE product_id #{productId} AND warehouse_id #{warehouseId} AND quantity - locked_quantity #{quantity}如果影响行数为0说明库存不足事务回滚用户立刻就能收到报错提示。如果影响行数大于0说明预占成功。锁定这一步做完系统就会基于出库单明细生成拣货任务拣货员按库位提示逐项拣货每拣一个库位上的商品系统就更新一次拣货进度。拣完货之后进入复核环节复核通过后执行扣减操作UPDATE wms_inventory SET quantity quantity - #{quantity}, locked_quantity locked_quantity - #{quantity} WHERE product_id #{productId} AND warehouse_id #{warehouseId}看到区别了吗预占是只加锁不扣量扣减是先把locked减掉同时把quantity减掉。这两个步骤不能合并因为中间隔着拣货执行时间如果不区分拣货员拣A单商品的时候把库存直接扣掉B单同时在拣同一个商品就会发生库存互相干扰相当纠结。这个业务逻辑我会建议写在文档里做重点说明。5.3 库存盘点盘盈盘亏的处理逻辑盘点模块的设计上我加了一些独立思考。很多初级课程设计里盘点就是一个简单的“填当前数保存覆盖”这在真实业务中是不可用的因为盘点是一个带有纠偏性质的操作必须记录差异、走审批。我的实现逻辑大致是仓管员创建盘点任务时可以选择全仓盘点或随机区域盘点系统为每个库位的每个商品生成一条盘点明细初始值库存数量置为系统账面数量。盘点员一份一份录入实盘数量可能是通过小工具扫码枪或者手动输入这里代码里保留了手录入口系统自动计算差异实盘数量减账面数量。账面与实盘不一致的明细在盘点任务提交后进入待审批状态。审批通过后系统自动生成盘点调整单把差异数据同步写入库存表和库存流水表同时记录每一笔的差异原因。盘盈盘亏在财务上都有关账要求和审计追溯因此这个调整路径必须有强记录。盘点差异查询页面支持按仓库、商品分类、时间区间筛选差异记录这些功能凑在一起已经算是完成度很高的盘点模块了。5.4 库位管理网格仓的精髓所在库位管理正是这个系统区别于“教科书版”仓库管理系统的核心亮点。传统的小项目里库存常常是“一个仓库一张表按商品记一个总数”根本没有库位概念这在网格仓场景是行不通的因为网格仓需要做到极致的拣选效率——你必须知道商品具体在哪个网格库位上否则拣货员拿着一张SKU清单在仓里来回跑效率会低到令人抓狂。我的设计方案中库位编码采用分区加排加列的模式比如A-01-01代表A区1排1列编码中带有货位物理位置的语义信息。订单拣货时系统按照库位编码排序输出拣货序列拣货员按顺序走一遍就能一次把商品全部捡齐这就是波次拣选的基础。另外库区在网格仓里可能映射为冷区、常温区或特殊商品区库位表里增加库区类型字段通过条件筛选就可以只在匹配的库区中定位商品。5.5 库存预警实现准实时补货建议库存预警模块实现起来不算复杂但可以直接体现系统的实用性。它是在商品表里配置每个SKU的最低库存阈值然后在库存变化之后的逻辑中触发检查。如果某商品在该仓库下的可用库存跌破阈值系统就会自动生成一条预警记录。预警记录带有处理状态仓管员查看之后可以生成补货建议书也可以将该预警标为已忽略。预警列表在首页仪表盘中展示按剩余可售天数和预警等级从高到低排序。这类功能能够让你这个项目在功能丰富度上甩开大部分同期作品一个身位而且代码实现的工作量并不大性价比高得很。5.6 首页数据看板设计用户登录后的首页没有做成空页面而是做了一个简单的数据看板。左侧是核心指标卡片今日入库单数、今日出库单数、当前库存总量、低于预警SKU数右侧是库存高低Top10商品列表和最近一周出入库趋势图。这些数据我全部通过后端聚合接口计算图表用的是轻量级前端插件完成。之所以做这个模块是为了在页面适配上让这份系统显得像一个真正的产品而不只是接口的集合。它的实际开发量不大需要多写几个带group by的SQL和几个翻译方法而已。6. 代码实现中的关键细节与经验6.1 三层架构的代码组织方式整个后端代码按照标准的Controller-Service-Mapper三层架构来组织包名的设计清晰直白controller放接口入口、service放业务逻辑、mapper放数据访问、entity映射数据表结构、dto定义前端交互的数据结构、vo定义视图模型、common放统一响应和异常处理、config放各类配置类、utils放工具类。层级之间的依赖方向是单向的Controller只依赖Service的接口不能越层调Mapper——这条规则不是形式主义它保证了代码的模块化后续想加单元测试、换实现类会非常方便。举一个常见的反面案例有人图省事直接在Controller里写业务代码、甚至写SQL操作数据库控制器异常臃肿业务逻辑和表现逻辑全部耦合在一起后期想调一个另一个模块的数据或者接二级缓存基本没法下手。代码质量好不好从包结构划分是不是清晰、依赖方向是不是明确这两点就能直观地感受到。6.2 全局异常处理与统一返回格式统一返回对象的设计一直是容易被初学开发者忽视的重活。在这套系统里所有接口返回的都是统一格式的Result对象正常返回时code为200业务失败时code为500并且message里带具体的原因发生未捕获的异常时由全局异常处理器兜底返回并记录堆栈日志。全局异常处理器捕获了三个自定义的异常类型业务异常比如库存不足、单据状态不允许当前操作、参数校验异常、未知异常。业务异常在Service层里用throw new BusinessException(xxx)主动抛出全局处理器统一转成Result响应前端拿到之后弹窗显示提示。这个设计让你写代码的时候不需要在每个接口里慢慢处理异常代码整洁度一下子提高了一截。另外在入库单、出库单这类核心单据的创建方法上我都添加了Transactional(rollbackFor Exception.class)注解。这里特别注意到的是rollbackFor参数指定了异常类型因为Spring默认只回滚RuntimeException如果业务方法抛出了受检异常而你没指定回滚规则事务会悄悄提交库存数据就会出现不可信问题这个细节很值得在答辩时展示。6.3 权限拦截器的实现方案权限控制我采用了拦截器加自定义注解的方式来实现。系统里定义了一个RequirePermission注解注解上有唯一的权限标识参数在需要鉴权的接口方法上标注这个注解即可。实现环节我编写了一个AuthInterceptor拦截器类注册时对/api/**路径进行拦截。拦截器里依次做三步校验用户是否已登录从Session中取用户信息没有就重定向到登录页解析当前请求的URI对应的权限标识从数据库查询当前用户的角色集合再汇总角色关联的权限标识集合最后判断当前接口的权限标识是否包含在用户权限集合中。不通过时直接抛出权限不足异常统一转成Result返回403状态码。这个方案可以说是教科书级的实现。它的好处是整个鉴权逻辑集中在一个地方新加接口的时候只需要在方法上加一个注解其余一律不需要操心菜单显示的控制同理你可以根据当前用户的权限标识在视图层动态决定哪些菜单渲染出来实现了菜单和接口的双重管控。6.4 分页查询的标准写法与前端适配分页查询尽量都用MyBatis-Plus自带的分页插件完成不要自己手写limit和count因为分页插件能自动优化count查询还能适配不同的数据库方言光这两点就值回引入它的成本。后端写法参考如下public ResultPageResultOutboundOrderVO pageOutboundOrder(OutboundOrderQueryDTO dto) { PageOutboundOrder page new Page(dto.getPageNum(), dto.getPageSize()); LambdaQueryWrapperOutboundOrder wrapper new LambdaQueryWrapper(); wrapper.like(StringUtils.hasText(dto.getOrderNo()), OutboundOrder::getOrderNo, dto.getOrderNo()) .eq(dto.getStatus() ! null, OutboundOrder::getStatus, dto.getStatus()) .eq(dto.getWarehouseId() ! null, OutboundOrder::getWarehouseId, dto.getWarehouseId()) .orderByDesc(OutboundOrder::getCreateTime); PageOutboundOrder result outboundOrderService.page(page, wrapper); // 再转换成VO填充商品明细列表 }注意看LambdaQueryWrapper的使用方式第一个参数是boolean条件条件成立时分页查询才附加该查询条件条件不满足时直接忽略。这样写的好处在于前端页面上传了几个筛选字段后端就用这几个字段拼接基本不需要出现动态SQL的拼接代码省下了大量可能出错的字符串拼接工作。6.5 数据库初始化的几个细节数据库自动建表这块步入了项目初始化的重头戏。项目里我提供了完整的MySQL建库建表脚本同时在Spring Boot配置了spring.sql.init.modealways当数据库不存在对应表的时候自动执行SQL脚本初始化。这一步做完后任何拿到源码的开发者只需要修改一下application.yml里的数据库连接信息启动程序就能得到一个完整的数据库不需要手动导入脚本体验十分顺畅。另外我强烈推荐在开发阶段开启Spring Boot的SQL日志输出mybatis-plus: configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl配置完成之后控制台会直接把每条执行的SQL语句和参数值打印出来。这个功能在调试分析联调问题的时候有奇效当页面上数据不对时你可以根据日志里的SQL直接判断是SQL写错了、参数传错了还是查询条件没拼上省去不少盲猜环节。7. 常见问题与排查技巧实录7.1 部署启动失败端口占用和数据库连接这个基本是每个新手都会遇到的第一道门槛。常见报错之一是端口被占用IDEA控制台直接提示Web server failed to startPort 8080 was already in use这种情况一般是本机的另一个进程占用了8080端口直接把端口改成一个生僻值比如8099就能解决或者用命令行把占用端口的进程找出来结束掉。另一种常见报错是数据库连接失败Communications link failure这种情况首先要确认MySQL服务是否已经启动Windows系统中检查任务管理器里是否有mysqld进程macOS/Linux下可以考虑用brew services list或systemctl status mysql来排查其次要确认连接配置里的用户名密码、host、端口和实际环境是否一致。我提醒过很多次千万别把数据库Host配置成127.0.0.1而MySQL实际运行在另一台机器上这种问题一旦排查起步方向不对能卡掉半天时间。7.2 分页查询统计不对count总是0MyBatis-Plus分页插件有个很常见的坑分页插件必须配置拦截器才能生效。有些人只引了分页插件的依赖但忘了在MyBatis-Plus配置类里注册PaginationInnerInterceptor结果分页查询返回的total永远是0页面上一片空白折腾半天还不知道问题出在哪。排查思路很简单如果你发现limit关键字没有出现在控制台打印的SQL里那分页插件十有八九没有生效。这时候就去config包里查看MybatisPlusConfig配置类确认里面new出了MybatisPlusInterceptor并addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL))。7.3 登录状态丢失前后端分离式残留问题SpringSession加Redis方案中如果发现页面操作过程中登录状态频繁丢失先去排查Redis服务是否正常启动Redis挂了会话存取自然也会失败。如果你用的是默认的Tomcat Session方案把代码部署好重启后就正常了但如果系统出现登录状态一会儿有效一会儿无效的情况就要怀疑是否有多个应用实例在轮询流量了这时会话数据还是需要统一存储。7.4 库存数据对不上先查流水别急着改数据项目上线运行一段时间后发现某个商品的库存数和实物对数对不上怎么排查记住一个原则改数据是最后手段一切以流水记录为准。先按商品和时间范围查询该商品的库存流水看每一笔变动来源是否合法。找到问题所在往往就能发现是某个出库单在拣货后被取消但扣减库存的操作没同步回滚或者是某个入库单被重复提交导致库存重复增加。修正这类问题不能简单粗暴地执行update语句去改库存正确的姿势是走盘点调整流程把差异原因挂到一笔调整记录上。这样操作的好处是整个过程留痕后续审计财务都说得清。如果盘点调整流程搞出来却没人记录原因系统就失去审计价值了。7.5 权限配置问题接口403但页面菜单正常权限这块最容易踩的坑是菜单显示和接口权限标识不一致。比如页面菜单通过权限标识menu:list判断显示但接口用的权限标识却是menu:query用户看得见菜单点一下却被拒绝。排查思路很简单查看数据库中角色关联的权限标识再看代码里Controller接口方法标注的权限标识字符两边比对是否一致。这类问题往往不是逻辑问题而是命名不规范导致的低级错误。所以我在项目里严格执行权限标识的命名规范统一为模块名加冒号加操作名例如user:add、user:delete、order:audit这样就不容易出现家贼。8. 关于课程设计答辩的建议与经验总结最后聊一点答辩的经验。很多人的课程设计项目做得其实不差但答辩的时候只会照着PPT念功能列表讲出来的效果非常拉胯。我的建议是答辩陈述的节奏可以围绕三条线展开业务背景、技术难点、个人思考。第一分钟讲清楚你做的系统是什么解决了一个什么样的业务问题这里需要把网格仓的概念解释清楚让评委知道你设计的系统有明确的应用场景第二到第四分钟讲功能架构和技术选型重点放在数据库的表设计逻辑和权限方案这两个点上最后几分钟拿出一到两个自己实际踩过的坑并说明你是如何定位和修复的这个内容比背诵一百个功能模块更有说服力。比如你可以这样说在开发出库模块时预占库存操作并发量高一开始不做并发控制用JMeter模拟20个并发请求抢最后一件库存时数据变成负数后来通过给库存表加版本字段实现乐观锁配合数据库行锁并发测试最终稳定通过。讲这种故事远比念PPT有效得多评委最想听的就是你能体现工程思维和问题解决能力的内容。如果你拿到这套源码想把它变成你自己的作品我建议做三件事一是完全看懂核心模块的代码逻辑尤其是出库模块预占和扣减的过程二是自己亲手加一个小功能比如供应商管理模块加一个导入导出的能力这样在答辩时被问到细节你也能自然应答三是把数据库表设计和关键接口画成文档图答辩时随手能在纸上画出来这个能力很加分。这套项目目前已经完整跑通恰好适合你直接落地的场景。如果你在跑项目或者改代码的过程中遇到问题可以沿着我上面讲的排查思路一步一步走大概率能自己解决。祝你的课程设计或者毕业设计顺利完成也欢迎你在评论区分享自己开发后的心得和踩坑记录。
返回列表