资讯详情

资讯详情

SpringBoot+Vue构建进销存ERP系统:从数据库设计到部署上线

从SpringBoot源码到上线聊聊一套企业进销存ERP的完整落地思路做JavaWeb开发的朋友应该都遇到过这类需求帮某公司或者某个客户从头搭建一套进销存管理系统要管采购入库、销售出库、库存查询、供应商对账、客户账单还得兼顾老板关心的利润统计。前几年这类项目大多用传统的单体JSP或者Thymeleaf模板硬怼后端渲染页面改个样式都费劲前后端代码搅在一起维护成本高到离谱。现在主流做法已经非常清晰了——SpringBoot做后端APIVue做前端界面前后端彻底分离。这篇文章我就用自己实际参与过的一个模拟项目X来拆解这套方案。某公司需要上一套内部用的ERP进销存系统业务范围不大不小采购、销售、库存、基础资料、报表统计、权限角色共六大块。技术栈定为SpringBoot MyBatis-Plus MySQL Vue Element Plus。整个系统从前期的数据库设计、后端接口开发到前端页面联调再到部署上线每一步都有值得说的细节和踩过的坑我把完整思路和关键代码拿出来分享给正在做同类项目的同学一个可参考的路线。1. 项目到底在解决什么问题进销存ERP听着名字高端拆开看核心就三个字进、销、存。进的单据是采购订单和采购入库单销的是销售订单和销售出库单存的是库存余量和库存流水。再加上支撑这三件事的基础档案商品、供应商、客户、仓库和账户体系用户、角色、权限一套系统的功能边界就出来了。这个模拟项目X最开始的需求文档只有四页纸核心诉求非常朴素业务员能录销售单仓管员能审核出库采购员能看库存预警老板能看每天卖了多少钱、毛利多少。很多刚学SpringBoot的开发者容易犯一个毛病——上来就写代码表结构还没想清楚就开Controller结果开发到一半发现库存扣减逻辑对不上、作废单据怎么回滚库存等一堆基础问题没定义好。所有后续开发的顺畅程度几乎都取决于前面这些业务规则是否定清楚了。这套项目选择SpringBoot而不是传统的SSH或者JSP方案理由其实很直接SpringBoot把配置简化到极致内嵌Tomcat一个jar包跑起来不需要单独部署Web容器非常适合前后端分离的架构。前端用Vue的原因是组件化开发效率高Element Plus的表格、表单、弹窗组件几乎是后台管理系统的标配做出来的界面比用JSP拼HTML好看太多。更重要的一点前后端分离以后后端接口可以同时服务PC端、移动端、甚至未来的小程序而不需要为每端重写一套模板页面。2. 技术选型为什么是这套组合2.1 后端SpringBoot MyBatis-PlusSpringBoot当前主流版本是2.7.x系列搭配JDK 8或者JDK 11都能稳定运行。如果是新项目也可以尝试SpringBoot 3.x但要注意3.x基于Jakarta EE规范javax包要换成jakarta包部分老教程的代码会报编译错团队如果对生态不熟建议还是从2.7.x起步稳一点。持久层框架我选了MyBatis-Plus而不是纯MyBatis或Spring Data JPA。原因有三个第一MyBatis-Plus提供BaseMapper单表CRUD不用写SQL开发效率极高第二自带分页插件LambdaQueryWrapper链式查询写起来非常顺手第三本来纯MyBatis所要手写的ResultMap映射在MP里靠驼峰自动映射就搞定了省掉了大量繁琐配置。数据库用的MySQL 8.0InnoDB引擎字符集utf8mb4。这两个选择基本不用犹豫utf8mb4比utf8多支持emoji字符和生僻字商品名称里偶尔出现的特殊符号不会变成问号。数据库连接池用Druid有监控页面生产上排查慢SQL比较方便。2.2 前端Vue Element Plus前端脚手架用Vue CLI创建Vue 3项目UI库配Element Plus状态管理用Pinia路由用Vue Router 4。为什么不上Vite开发体验上Vite确实快但Vue CLI更稳一点尤其是老旧Node版本环境下兼容性更好。如果团队Node版本能保证18以上直接上Vite也没问题。模拟项目X用的Vue CLI主要是考虑到客户现场环境的不可控因素。Element Plus的组件覆盖度非常高表格用el-table、表单用el-form、弹窗用el-dialog、菜单用el-menu几乎不用额外引入其他UI库。审批流、富文本编辑器这类特殊需求再按需引入对应组件就行。2.3 接口文档与联调工具前后端分离开发最大的痛点是接口约定不一致。模拟项目X的做法是先定义好接口文档用Apifox管理后端按文档开发前端Mock数据同步开发最后联调时以文档为准。Apifox有自动生成接口文档的功能可以搭配SpringDocOpenAPI 3来自动扫描后端接口生成文档不用单独写太多文档注释。3. 数据库设计一张库存流水表撑起进销存的核心3.1 核心表结构盘点ERP系统的数据库设计是重头戏表与表之间关系复杂一旦前期没设计好后面改表结构就是牵一发动全身。模拟项目X的库一共建了26张表核心可分成四组。第一组是基础档案表商品表、商品分类表、供应商表、客户表、仓库表、计量单位表。商品表要注意设计条码字段业务场景里扫码枪是高频设备条码做唯一索引录入单据时按条码查商品是基本操作。价格字段在商品表里只存参考进价和参考售价真正成交价格以单据为准商品价格会波动不要试图用商品表的字段去表现历史价格。第二组是业务单据主表采购入库单、采购退货单、销售出库单、销售退货单、库存盘点单各自对应一张主表。主表存单据头信息——单号、往来单位、仓库、业务日期、经办人、总金额、状态、备注。注意所有金额字段一律用DECIMAL(10,2)不允许用Float和Double存钱这是财务相关系统的铁律。第三组是业务单据明细表采购入库单明细、销售出库单明细等与主表是一对多关系。每条明细记录关联一个商品记录数量、单价、金额、批次号。需要特别注意明细表要冗余存储商品的名称、规格、条码、单位不要只存商品ID否则商品信息改了历史单据上的品名就跟着变了财务对账时会出大问题。第四组是库存与流水表仓库商品库存表、库存流水表。这两张表是进销存系统的灵魂所有单据的出入库动作最终都反映到库存流水表上库存表只是流水表的累加结果视图。3.2 为什么库存流水表只增不改很多初写进销存的人会问一个问题库存为什么不直接写死一张总表出库就减掉对应记录这个思路在商品种类少、并发量低的场景下勉强能跑但稍微一复杂就崩。客户的退货、单据作废、当月反审核改单每个动作都要反算之前的库存修改记录根本说不清库存是哪些单子构成的。正确的设计是库存流水表记录每一次库存变动采购入库产生“入库”流水正数销售出库产生“出库”流水负数盘点差异单独生成“盘盈”或“盘亏”流水。库存表里某一商品的当前库存就是该商品的流水正负数量累加结果。除非常规初始化流水表不允许直接改数据更不允许删除历史流水。这样设计的好处是任何一张业务单据的来源都能追溯库存异常时能倒查是哪张单子写错了。3.3 关键词设计单号规则要提前规划进销存系统的单号在业务上具有法律效力月底对账、财务审计都得靠单号串联。单号规则设计为单据类型前缀日期当天流水号例如CG20250215-001代表2025年2月15日第一笔采购入库单XS20250215-001是销售单。这个规则要用程序自动生成生成逻辑必须上锁或者基于数据库唯一索引防止并发时生成重复单号。模拟项目X还额外把订单号做成了唯一索引一旦重复整个插入直接报错做到双保险。3.4 逻辑删除而非物理删除商品的分类调整、把某条供应商记录删掉这类操作在ERP里要非常谨慎。比如一张历史采购单关联了某个供应商如果这个供应商被物理删除了采购单详情页就变成空引用前端渲染就会报错。所以所有关键表都加了deleted字段删除操作只是把deleted置1列表查询自动过滤底层永远保留记录。MyBatis-Plus自带的逻辑删除注解TableLogic直接在实体字段上开启不需要手写SQL。4. 后端核心实现接口设计与库存事务处理4.1 Controller只做参数接收业务推理放Service层前后端分离架构下后端接口返回的格式要统一。模拟项目X定义了一个Result对象结构是code、message、data三个字段。code为200表示成功401表示未登录或登录过期403表示无权限500表示业务异常具体错误信息在message里。所有Controller的返回值统一用Result包装前端axios拦截器拿到response后先判断code再决定是否弹出错误信息这样前端的错误处理逻辑非常简单统一。实践中学到一个特别重要的原则Controller层只做参数接收和校验不要写业务逻辑。有的开发者图方便在Controller里直接调Mapper算库存写出来的代码看起来短后面加个需求就傻眼。模拟项目X中的做法是Service层定义业务接口Controller只依赖Service层的接口方法事务注解Transactional写在ServiceImpl类上这样所有业务规则都收拢在Service里单元测试也好写。4.2 统一异常处理后端出了异常不能直接把异常堆栈抛给前端既不好看也不安全。项目里实现了全局异常处理器RestControllerAdvice分三类处理参数校验异常MethodArgumentNotValidException返回参数错误提示自定义业务异常BizException返回业务消息系统异常Exception统一返回“系统开小差了”并记录完整日志到文件。这里建议所有Service层自己抛BizException把具体的业务错误信息写在message里比如“库存不足当前可用库存为5件”这种信息会直接弹出给操作人员体验比统一返回“服务器错误”强得多。4.3 JWT权限认证与按钮级权限控制管理系统的权限模型是RBAC基于角色的访问控制用户属于角色、角色拥有权限、权限绑定到菜单和按钮。表结构是user表、role表、menu表、user_role关联表、role_menu关联表。菜单表里有parent_id做父子层级还带type字段区分目录、菜单、按钮。登录成功后后端签发JWT令牌令牌里包含用户ID和用户名过期时间设为24小时。前端每次请求在header里带Authorization: Bearer token后端写一个拦截器统一校验token解析出用户信息放到ThreadLocal或者请求上下文。权限方面用自定义注解RequirePermission配合Spring AOP切面在需要权限的接口上标注权限标识符切面里检查该用户角色是否具备此权限标识没有就抛出403异常。为什么不用传统的Session方案前后端分离后前端和后端可能在不同域名下Session的跨域处理麻烦遇到Nginx反向代理还要配共享Session。JWT天然无状态后端不需要存会话信息横向扩容时不需要考虑Session复制部署多个实例也没有会话粘滞问题。4.4 库存扣减的事务与并发控制库存操作是整个系统最敏感的部分写不好就是超卖、对不上账。模拟项目X的采购入库核心逻辑大致如下保存入库单头信息、保存入库单明细、逐条更新库存、逐条写库存流水。这四个步骤必须在一个事务里任何一个环节失败整个入库操作全部回滚。具体代码处理上更新库存不能直接update stock set num num - #{num}虽然SQL本身没问题但高并发时两个请求同时读到同一批库存会出现数据覆盖。更稳的方案是更新语句加条件判断UPDATE wms_stock SET stock_num stock_num - #{num} WHERE sku_id #{skuId} AND stock_num #{num}。这条SQL执行后如果返回影响行数为0说明库存不足直接抛出BizException并回滚事务。这种写法利用数据库行级锁天然解决了并发问题比在代码里用synchronized或者Redis分布式锁都简单可靠。4.5 复杂筛选的分页查询列表页的分页查询是后台系统的日常高频操作。MyBatis-Plus的分页插件配置好后配合LambdaQueryWrapper写条件查询非常舒服。筛选条件动态拼接商品名称模糊匹配、分类ID精确匹配、状态精确匹配、时间范围用between同时带上排序字段。需要注意的一点是分页查询的count查询在数据量大时可能成为瓶颈MySQL 8.0下如果表数据超过百万行建议用SQL_CALC_FOUND_ROWS加单独explain分析的方式优化或者直接估算总数放弃精确count具体看业务接受程度。5. 前端Vue实现从路由到界面的一整套方案5.1 项目结构与请求封装Vue前端项目的目录组织直接按业务模块来划分views下面按模块建目录purchase采购、sales销售、inventory库存、report报表、system系统管理api目录同样按模块划分封装请求方法store目录放Pinia的store文件包括用户store、应用配置store。axios请求封装是前端项目的基础建设。模拟项目X中创建一个request.js文件baseURL指向后端网关地址开发环境通过Vue CLI的devServer代理解决跨域生产环境走Nginx反向代理请求拦截器里从Pinia拿token并加到Authorization头响应拦截器里统一判断HTTP状态码和业务codeHTTP 200且code为200返回data否则统一弹出ElMessage错误提示401则清空本地登录信息并跳转登录页。5.2 动态权限路由前端菜单根据后端返回的菜单树生成实现方式是登录成功后调用后端接口获取该用户可见的菜单列表和按钮权限标识集合存储在Pinia中。Vue Router注册时用addRoute动态添加路由不写死静态路由表。这样不同角色登录后看到的左侧菜单天然不同按钮级的控制用自定义指令v-permission检查权限集合无权限的直接把按钮DOM移除。5.3 常用页面模式表格弹窗表单进销存系统的页面90%都遵循同一套模式顶部是筛选条件区域中间是表格数据操作列放编辑和删除按钮新增和编辑都用右侧抽屉或者居中弹窗承载表单。Element Plus的el-table对大数据量表格的性能一般超过500条就会出现明显卡顿所以在表格数据量大的场景要用后端分页不要一次性加载全部数据。模拟项目X里销售明细查询这类可能返回上千行的列表一律走服务端分页懒加载不做前端分页。表单校验是前端体验的重要环节。Element Plus的rules规则和el-form-item的prop绑定配合自定义校验函数能实现提交前校验销售单明细的数量必须大于0、单价不能为空、商品条码必须存在等。前端校验能挡掉80%的错误操作剩下的业务规则比如作废单据需要审批权限由后端兜底校验。5.4 库存盘点页面的交互设计库存盘点算是一个比较特殊的功能模块。传统做法是打印盘点表人工数完再录入系统差异全部手工比对。模拟项目X中把盘点流程做成在线版盘点单创建后仓管员用手机扫码枪直接扫描货位上的商品条码每扫一个就自动追加一条盘点明细记录扫完提交系统自动比对账面库存和实盘数量差异数量生成盘盈盘亏单并生成对应流水确认盘点的同时库存自动调整。这个交互设计大大减少了仓库人员的工作量也避免了二次录入的差错。6. 部署上线与环境避坑指南6.1 环境版本搭配一套前后端分离项目的完整部署涉及MySQL、后端Java应用、前端Nginx静态资源、Redis如果需要做缓存或验证码存储四个部分。版本搭配建议为JDK 8 SpringBoot 2.7 MySQL 8.0 Nginx 1.24 Node 16仅构建时用运行时不需要Node。如果服务器是CentOS 7或者Ubuntu 20.04以上环境这套组合基本不会遇到大坑。后端打成jar包后用systemd服务管理或者直接nohup启动都行。生产环境建议设置JVM参数-Xms256m -Xmx512m根据业务量调整。Druid连接池配置连接数最大20初始化5空闲超时5分钟这些参数在用户量100以内的内部系统足够使用。6.2 反向代理和跨域配置前后端分离后生产环境的前端页面和后端API在同域下Nginx配置一个server块location / 指向前端dist目录的静态文件location /api/ 把请求反向代理到http://127.0.0.1:8080/同时用proxy_set_header传递Host和X-Real-IP。这种配置下前端请求用相对路径/api/xxx不存在跨域问题。开发环境让Vue CLI的devServer代理后端端口同样规避跨域。Java后端的CORS跨域配置只作为开发环境兜底生产环境不建议靠Spring的CORS配置跨域统一走Nginx更安全。6.3 加密与安全配置数据库连接串里的账号密码不要明文写在application.yml里。方案一用Jasypt对密码加密启动时解密方案二密码存在环境变量里配置文件引用${DB_PASSWORD}。模拟项目X用的第二种简单直接。生产服务器防火墙只开放80和443端口后端8080端口只允许本机和Nginx访问防止外部直接访问后端API绕过前端页面。用户密码存储使用BCrypt加密不要用MD5。MD5撞库成本极低相同密码产生的哈希完全相同一看就是明文。BCrypt是Spring Security框架默认的密码加密器每次生成哈希都带随机盐安全性高很多而且使用成本只是调用一个方法那么简单。7. 常见问题与排查技巧实录7.1 并发扣库存导致库存变负现象销售出库时同一SKU被多个人同时操作某些单据明明库存不够却还是审核通过了库存出现负数。排查思路看库存更新的SQL如果写成先查后改的代码逻辑并发下必然出问题。模拟项目X早期就是这种写法后来改成带条件的UPDATE语句加受影响行数判断问题彻底解决。另外检查事务是否生效Service方法必须是public方法且通过代理对象调用即不能同类内直接调ServiceImpl方法否则Transactional会失效。7.2 前端日期参数时间差8小时现象前端选择的日期是2025-02-15后端接收到的确实2025-02-15 08:00:00插入数据库又变成了当天但查询时总是差8小时。处理方案在DTO的日期字段上使用JsonFormat注解显式指定时区同时在application.yml里配置spring.jackson.time-zoneGMT8和date-format。前后端所有日期传参用字符串格式yyyy-MM-dd HH:mm:ss避免Spring默认的JDK序列化时区问题。7.3 菜单权限配置混乱导致页面白屏现象新配的角色登录后左侧菜单显示正常但点击菜单页面白屏控制台报路由匹配不到组件。排查方法动态路由添加的时机不对是常见根因。模拟项目X中把动态路由添加逻辑放在了路由守卫beforeEach里每次跳转都判断是否已添加已添加的直接放行未添加的先调接口获取菜单再addRoute最后用next({...to, replace: true})重新触发一次导航。还有另一个坑addRoute动态添加的路由刷新后会丢失必须每次刷新页面时重新加载用户信息和路由配置。7.4 表格大数据量渲染卡顿现象商品列表全量加载2000条数据页面滚动卡顿筛选操作延迟明显。处理方案所有列表页强制服务端分页默认每页20条用户手动切换每页容量。前端不要缓存全量数据翻页重新请求接口。如果确实需要一次性展示大数据量比如报表导出预览考虑用虚拟滚动组件处理。在为中小型内部ERP系统做优化时简单粗暴地限制返回条数往往比复杂的渲染优化更实用。8. 再多说几句个人体会整套系统从零到上线我在实际参与过程中体会最深的一点做进销存类项目先想清楚业务规则比先选技术栈重要得多。库存扣减规则、单据作废规则、反审核规则、负数库存是否允许这些业务问题如果不在设计阶段堵住后面写代码就是在错误的地基上盖楼结论永远是返工。另一个建议是前后端约定接口时要足够细。字段命名、日期格式、分页参数名、错误码语义全部在接口文档里白纸黑字写清楚前端才不会一遍遍问“这个字段啥意思”和“错误信息在哪展示”。一次联调能过90%的接口是团队协作的理想状态而前提就是前期文档做扎实。这套SpringBoot Vue的进销存ERP方案整体来说学习曲线不算陡峭技术栈通用业务模型清晰非常适合作为JavaWeb入门到进阶的实战项目来练手。如果你正好在选择练手项目或者公司内部刚好需要一套轻量级的进销存系统可以从这套方案的表结构设计开始一步一个坑踩出来你会发现SpringBoot也好、Vue也好最终都不如自己对业务流程的深刻理解值钱。
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →