# 动力节点恒合仓库 **Repository Path**: Atopos17/warehouse ## Basic Information - **Project Name**: 动力节点恒合仓库 - **Description**: 仓库管理系统 SpringBoot + Vue3前后端分离的项目 - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 3 - **Created**: 2025-04-07 - **Last Updated**: 2025-04-07 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # 动力节点恒合仓库 仓库管理系统 SpringBoot + Vue3前后端分离的项目: 一、项目介绍: 1.项目描述 √ 为满足日益扩大的仓库管理需求,对仓库中商品进销存进行统一管理,而开发了此系统。系统主要包含: RBAC:用户角色权限控制 1>用户管理(查询用户、添加用户、修改用户、删除用户、导出数据、批量删除、禁用/启用用户、重置密码、分配角色、更改权限) 2>角色管理(查询角色、添加角色、修改角色、删除角色、导出数据、禁用/启用角色、更改权限) 3>权限管理(查询权限、添加权限、修改权限、删除角色、禁用/启用权限) 4>商品管理(查询商品、添加商品、修改商品、商品审核等) 5>商品分类管理(商品分类的添加、商品分类的查询、商品分类的修改、商品分类的删除等) 6>采购管理(我的采购单、添加采购单、采购单的审核等) 7>入库管理(入库单、保存入库单、确认入库等) 8>出库管理(出库单、保存出库单、审核出库单等) 9>统计管理(各个仓库库存信息、仓库占有比、仓库存储走势、出入库信息统计、采购量占比、实时数据监测) 10>调货管理(调货单查询、确认调货) 11>仓库管理(查询仓库、添加仓库、修改仓库、删除仓库、导出数据) 12>供货商管理(供货商添加、供货商修改、供货商的查询等) 13>品牌管理(品牌添加、品牌修改、品牌的查询等) 14>产地管理(产地添加、产地修改、产地的查询等) 15>站内信管理(我的站内信、未读消息、站内信发送、站内信查询等) 2.技术选型 √ SpringBoot + MyBatis + MySql + Redis + Vue3 + Axios + Element-Plus + Echarts等 3.模块划分 4.个人职责 √ 二、还原数据库: 三、搭建前端项目环境并启动项目: 1>安装node: node的介绍: node是一个基于Chrome V8引擎的JavaScript运行环境,让JavaScript运行在服务端的开发平台。 npm包管理器的介绍: node安装之后一般都会默认安装npm包管理器;类似于linux的yum包管理器,可以给Vue项目下载相关插件、依赖; 1)将安装压缩包解压就是安装 2)配置path环境变量 3)测试安装是否成功: node -v:查看node版本 npm -v:查看npm包管理器的版本 如果出现警告将node安装目录中的npm.cmd文件中的prefix -g改为prefix --location=global 2>给npm包管理器配置镜像加速器: npm config set registry https://registry.npm.taobao.org npm config get registry -- 返回https://registry.npm.taobao.org,说明配置成功 3>使用npm包管理器下载安装yarn包管理器,同时配置镜像加速器: yarn包管理器的介绍: yarn包管理器跟npm包管理器的作用是一样的,区别就是npm包管理器是node自己的,而yarn包管理器属于第三方(facebook的); 安装yarn包管理器: npm install -g yarn 给yarn包管理器配置镜像加速器: yarn config set registry https://registry.npm.taobao.org yarn config get registry -- 返回https://registry.npm.taobao.org,说明配置成功 4>使用yarn包管理器为前端Vue项目下载安装所需插件: 在vue项目目录下执行命令:yarn 5>启动前端Vue项目: 在vue项目目录下执行命令:yarn dev 细节: 前端vue项目对后台项目的访问路径: vue项目目录下的.env文件: VITE_WAREHOUSE_CONTEXT_PATH=http://localhost:9999/warehouse 1)可以在.env文件中通过VITE_WAREHOUSE_CONTEXT_PATH变量修改设置前端Vue项目访问的后台项目的访问路径; 2)如果不做修改设置那么就要求我们的后台项目的项目路径必须是/warehouse,访问端口必须是9999; 四、搭建后台项目的环境: 1.创建个boot项目 -- 划分包层次: 2.导入依赖 3.启动类配置 4.配置文件的配置 五、登录相关业务: 接口: 1.interface:表示对外暴露的规则,里面定义的全是抽象方法和全局常量。 2.api接口 url接口:请求的url地址。 1.生成验证码图片 http://localhost:9999/warehouse /captcha/captchaImage -- 服务器后台生成一张验证码图片,然后响应给前端,前端以img标签展示。 2.登录 http://localhost:3000/ -- 是前端vue项目的访问地址 前端项目目录下的vite.config.js文件: //服务端代理设置 proxy: { //如果访问地址以"/api"开头,则自动代理到变量VITE_WAREHOUSE_CONTEXT_PATH所表示的 //服务端地址http://localhost:9999/warehouse '/api': { target: env.VITE_WAREHOUSE_CONTEXT_PATH, changeOrigin: true, rewrite: path => path.replace(/^\/api/, '') } } url接口/api是前端vue项目的接口,是个代理接口,代理的是后台项目的访问路径; 所以只要是前端vue项目发出的请求的地址是http://localhost:3000/api/xxx都是发给后台项目的,访问的后台向的具体的url接口; http://localhost:3000/api/login -- 访问的是后台项目的/login接口; { userCode:"admin" userPwd:"123456" verificationCode:"xax4" } 3.登录限制 4.获取登录用户信息 5.加载权限菜单树 6.退出登录 -------------------------------------------------------------------- token -- 令牌 -- 会话技术 -- 登录成功之后,在一段时间之内不需要重复登录便可以直接访问系统资源; 常见的会话技术: session: 弊端是只适合单体应用,不适用于分布式微服务集群的项目; token -- 令牌 -- 一段字符串: 是适用于分布式微服务集群的项目的会话技术; jwt token: 头部: { "alg": "HS256", "typ": "JWT" } 载体 -- 存放有用户信息: { "sub": "1234567890", "name": "John Doe", "admin": true } 签名: HMACSHA256( base64UrlEncode(header) + "." + base64UrlEncode(payload), secret ) base64UrlEncode(header) . base64UrlEncode(payload) . HMACSHA256(base64UrlEncode(header)+"."+base64UrlEncode(payload),secret)) 五、登录相关业务: 3.登录限制 必须登录成功之后才能访问系统资源。 使用过滤器实现: 在后台项目中配置一个过滤器,会拦截前端发出的所有请求,拦截之后判断用户是否已经登录 来决定是否允许访问系统资源(url接口); 回顾SpringBoot中配置原生Servlet中的过滤器: 1>自定义过滤器 -- 定义一个Filter接口的实现类并重写doFilter()方法,doFilter()方法中 就是过滤器拦截到请求执行的内容; 2>向IOC容器中配置FilterRegistrationBean的bean对象,再将我们自定义的Servlet的过滤器 注册给FilterRegistrationBean的bean对象 -- 注册过滤器 4.获取登录用户信息 /curr-user -- 拿到前端归还的token,使用token工具类解析token的封装方法拿到从token中 解析出的用户信息并封装到的CurrentUser对象; 5.加载权限菜单树 /user/auth-list 加载用户权限菜单树的方式: 1.后台系统中是查询出用户权限下的所有菜单List,然后将用户所有菜单的List响应 给前端,前端框架就使用菜单树组件通过用户所有菜单的List中每个菜单的id和pid的关系 生成菜单树; -- 菜单树是前端生成的; 2.后台系统中是查询出用户权限下的所有菜单List,然后将用户所有菜单的List转成 菜单树List,最后将菜单树List响应给前端,前端只需要做个循环迭代展示菜单树; --- 菜单树是在后端生成的; √ RBAC:用户角色权限控制 通过给用户分配不同的角色,再给角色分配不同的菜单权限,进而实现给用户分配不同的菜单权限; 实现RBAC至少设计到5张表: 1>用户表user_info:存放用户信息 userId userName... 2>角色表role:存放角色信息 roleId roleName 3>用户角色中间表user_role:存放用户角色关系的,体现了给用户分配的角色,而且用户和角色是多对多关系 userId roleId 4>菜单权限表auth_info:存放菜单信息 authId pid authName 5>角色菜单权限中间表role_auth:存放的是角色菜单关系,体现了给角色分配的菜单权限,而且角色和菜单是多对多关系 roleId menuId 6.退出登录 /logout -- 从redis中删除当前登录的用户的token的键; -------------------------------------------------------------------------------------------------------------------------- 用户管理模块: 1.用户列表 /user/user-list?userCode=&userType=&userState=&pageSize=5&pageNum=1&totalNum=0 /user/user-list?userCode=a&userType=&userState=1&pageSize=5&pageNum=1&totalNum=0 pageNum是页码 pageSize是每页行数 -- 分页查询 1)如果参数userCode userType userState没有值,是查询所有用户并分页 select * from user_info limit 起始行,每页行数; select count(*) from user_info; 2)如果参数userCode userType userState有值,根据账号 用户状态 用户类型分页查询用户并分页 select * from user_info where user_code like ? and user_type = ? and user_state = 1 limit 起始行,每页行数; select count(*) from user_info where user_code like ? and user_type = ? and user_state = 1 接参问题: 参数userCode userType userState使用User对象接收并封装; 参数pageNum页码 pageSize每页行数使用Page对象接收并封装; 响应数据问题: 给前端响应封装了所有分页信息的Page对象; 2.添加用户 /user/addUser { userCode:"zhangsan" userName:"张三" userPwd:"123456" } 3.启用和禁用用户 /user/updateState { createBy:1 createTime:"2023-06-17 16:38:30" getCode:"admin" isDelete:"0" updateBy:0 updateTime:null userCode: "zhangsan" userId:34 √ userName:"张三" userPwd:"c431d451c81e75ffac75a640590ed0a1" userState:"1" √ userType:null } 4.给用户分配角色 /role/role-list -- 查询所有角色(展示角色出来) /user/user-role-list/{userId} -- 查询用户已分配的角色(如果是修改角色,需要回显用户已分配角色) /user/assignRole -- 给用户分配角色 { userId:34 roleCheckList:["调货", "采购", "入库"] } 用户管理模块: 5.删除用户 /user/deleteUser/{userId} -- 根据用户id删除用户 /user/deleteUserList -- 根据用户ids批量删除用户 [34, 33] update user_info set is_delete = 1 where user_id in(1,2,3...) 6.修改用户 /user/updateUser -- 根据用户id修改用户昵称 { userCode:"zhangsan" userId:34 userName:"张三三" } 7.重置密码 /user/updatePwd/{userId} -- 将用户密码根据用户id还原为123456(加密) ----------------------------------------------------------------------------- 角色管理模块: 1.角色列表 -- 分页查询角色 /role/role-page-list?roleName=&roleCode=&roleState=&pageSize=5&pageNum=1&totalNum=0 查询所有角色并分页 role/role-page-list?roleName=a&roleCode=b&roleState=1&pageSize=5&pageNum=1&totalNum=0 根据角色名称、角色代码、角色状态查询角色并分页 2.添加角色 /role/role-add { roleCode:"pandian" roleDesc:"盘点" roleName:"盘点" } 还需要考虑新增的角色是否已经存在 -- 根据角色名称或者角色代码查询是否已有角色; 3.启用和禁用角色 /role/role-state-update { createBy:1 createTime:"2023-06-19 10:46:44" getCode:"admin" roleCode:"kuaiji" roleDesc:"会计" roleId:18 √ roleName:"会计" roleState:"1" √ } 4.给角色分配权限 /auth/auth-tree -- 查询所有的权限菜单树 /role/role-auth?roleId=17 -- 根据角色id查询角色已分配的所有权限 /role/auth-grant { roleId:"17" authIds:[61, 62, 63, 64] } 5.删除角色 /role/role-delete/{roleId} -- 根据角色id删除角色 6.修改角色 /role/role-update { roleDesc:"盘点库存" roleId:17 } ------------------------------------------------------------------------- 商品管理模块: 1.商品列表: 1>分页查询商品 /product/store-list -- 查询所有仓库的 -- 给搜索商品仓库下拉框组装数据的 /product/brand-list -- 查询所有品牌 -- 给搜索商品皮牌下拉框组装数据的 分页查询商品: /product/product-page-list?storeId=&productName=&brandName=&typeName=&supplyName=&placeName=&upDownState=&isOverDate= &pageSize=5&pageNum=1&totalNum=0 查询所有商品并分页 /product/product-page-list?storeId=1&productName=a&brandName=美的&typeName=b&supplyName=c&placeName=d&upDownState=1&isOverDate=0 &pageSize=5&pageNum=1&totalNum=0 根据仓库 商品名称 品牌 类型 供应商 产地 上下架状态 过期与否查询商品并分页 请求参数的处理: 页码pageNum 每页行数pageSize -- Page对象接收并封装 仓库id storeId、商品名称productName、品牌名称brandName、商品分类名称typeName、供应商名称supplyName、产地名placeName、 上下架状态upDownState、过期与否isOverDate -- Product对象接收并封装 -- 追加属性品牌名称brandName、商品分类名称typeName、 供应商名称supplyName、产地名placeName、过期与否isOverDate 商品列表展示的信息: 仓库名称 -- Product对象还要追加属性 --- storeName 单位 -- Product对象还要追加属性 --- unitName 商品管理模块: 1.商品列表: 2>添加商品 /product/category-tree -- 查询所有分类树 -- 给添加商品提供的 /product/supply-list -- 查询所有供应商 -- 给添加商品提供的 /product/place-list -- 查询所有产地 -- 给添加商品提供的 /product/unit-list -- 查询所有单位 -- 给添加商品提供的 /product/img-upload -- 上传图片 file:上传的图片的字节数据 /product/product-add -- 添加商品 { brandId:2 imgs:"midea_ph.jpg" --- /img/upload/midea_ph.jpg inPrice:"2000" introduce:"美的 变频 中央空调" memPrice:"2400" placeId:5 productDate:"2023-06-20" productInvent:50 productName:"中央空调" productNum:"midea_ph_001" salePrice:"2500" storeId:1 suppDate:"2026-06-20" supplyId:5 typeId:7 typeName:"空调" unitId:6 } 3>修改商品上下架状态 /product/state-change { productId:31 upDownState:1 } 4>删除商品 /product/product-delete/{productId} -- 根据商品id删除单个商品 /product/product-list-delete --- 批量删除商品 [31, 25] delete from product where product_id in(1,2,3...) 5>修改商品 /product/product-update { brandId:2 brandName:"美的" createBy:1 createTime:"2023-06-20T10:07:02.000+00:00" imgs:"/img/upload/midea_ph.jpg" "midea_zh.jpg" -- 如果图片没有被修改则还是完整的访问地址,如果被修改了只有图片名称 inPrice:2000 introduce:"美的 变频 中央空调 透心凉" isOverDate:null memPrice:"2300" placeId:5 placeName:"山东" productDate:"2023-06-20" productId:31 productInvent:50 productName:"空调" productNum:"midea_ph_002" salePrice:"2400" storeId:1 storeName:"西安仓库" suppDate:"2026-06-20" supplyId:5 supplyName:"美的集团股份有限公司" typeId:7 typeName:"空调" unitId:6 unitName:"台" upDownState:"0" } 6>添加采购单 采购流程: 1)在商品列表针对具体的商品添加采购单 -- 向采购单表buy_list表添加记录(状态是0未入库) -- 预买商品 2)当商品采购到之后进行入库 -- 在采购单列表做入库操作 -- 向入库单表in_store表添加记录(状态是0未入库),同时修改采购单表buy_list表 由0改为1入库 -- 准备入库 3)商品真正的入库 -- 在入库单列表做确认入库操作 -- 将入库单表in_store表的入库单状态由0改为1入库 /purchase/purchase-add { brandId: 3 brandName: "海尔" buyNum: "50" √ -- fact_buy_num实际采购数量,初值跟buyNum一致 buyUser: "张三" √ createBy: 1 createTime: "2023-06-20T10:07:02.000+00:00" imgs: "/img/upload/midea_zh.jpg" inPrice: 2000 introduce: "海尔 变频 中央空调 透心凉" isOverDate: null memPrice: 2300 phone: "13666666666" √ placeId: 5 √ placeName: "山东" productDate: "2023-06-20" productId: 31 √ productInvent: 50 productName: "空调" productNum: "midea_ph_002" salePrice: 2400 storeId: 1 √ storeName: "西安仓库" suppDate: "2026-06-20" supplyId: 4 √ supplyName: "海尔集团" typeId: 7 typeName: "空调" unitId: 6 unitName: "台" upDownState: "0" updateBy: 1 updateTime: "2023-06-20T11:46:43.000+00:00" } 7>添加出库单 出库流程: 1)在商品列表针对具体的商品添加出库单 -- 向出库单表out_store表添加出库单(状态是0未出库) -- 准备出库 2)商品真正出库之后,在出库单列表做确认出库操作 -- 将出库单表out_store表的出库状态由0改为1已出库 /outstore/outstore-add { brandId: 3 brandName: "海尔" createBy: 1 createTime: "2023-06-20T10:07:02.000+00:00" imgs: "/img/upload/midea_zh.jpg" inPrice: 2000 introduce: "海尔 变频 中央空调 透心凉" isOverDate: null memPrice: 2300 outNum: "10" √ placeId: 5 placeName: "山东" productDate: "2023-06-20" productId: 31 √ productInvent: 50 productName: "空调" productNum: "midea_ph_002" salePrice: 2400 -- out_price字段出库价格给salePrice属性的值 storeId: 1 √ storeName: "西安仓库" suppDate: "2026-06-20" supplyId: 4 supplyName: "海尔集团" typeId: 7 typeName: "空调" unitId: 6 unitName: "台" upDownState: "1" updateBy: 1 updateTime: "2023-06-20T11:46:43.000+00:00" } 2.商品分类: 1>查询商品分类树 /productCategory/product-category-tree -- 定义新的url接口,调用添加商品时编写的查询商品分类的业务即可; 2>添加商品分类 /productCategory/verify-type-code?typeCode=lingshi -- 校验分类编码是否已存在的 select * from product_type where type_code = ? or type_name = ? -- 根据分类编码或者分类名称查询商品分类,以此判断 分类编码或分类名称是否已存在 productCategory/type-add { parentId:0 typeCode: "xiaomi" typeDesc:"发烧 性价比高" typeName:"小米手机" } 3>删除商品分类 /productCategory/type-delete/{typeId} -- 根据分类id或父级分类id删除分类 4>修改商品分类 /productCategory/type-update { parentId:11 typeCode:"xiaomi" typeDesc:"发烧 性价比高 拍照高清" typeId:18 typeName:"红米手机" } 采购单管理模块: 1.采购列表 /purchase/store-list -- 查询所有仓库 /purchase/purchase-page-list?storeId=1&startTime=&endTime=&productName=&buyUser=&isIn=&pageSize=5&pageNum=1&totalNum=0 查询所有采购单并分页 或者是 根据仓库id、起止时间、商品名称、采购员、是否入库查询采购单并分页; 请求参数分析: 页码pageNum 每页行数pageSize --- Page接收封装 storeId startTime endTime productName buyUser isIn -- 使用Purchase对象接收并封装,但是Purchase类中没有属性startTime endTime productName,需要追加; 采购单列表展示的数据分析: 额外需要仓库名 商品名 -- 需要给Purchase追加属性storeName productName; statr <= buyTime <= end 2.删除采购单 /purchase/purchase-delete/{buyId} -- 根据id删除采购单 3.修改采购单 /purchase/purchase-update { buyId: 49 √ buyNum: 50 √ buyTime: "2023-06-21T09:59:39.000+00:00" buyUser: "张三" endTime: null factBuyNum: "40" √ isIn: "0" phone: "13666666666" placeId: 5 productId: 31 productName: "空调" startTime: null storeId: 1 storeName: "西安仓库" supplyId: 4 } 4.生成入库单 /purchase/in-warehouse-record-add { buyId: 49 buyNum: 50 buyTime: "2023-06-21T09:59:39.000+00:00" buyUser: "张三" endTime: null factBuyNum: 40 isIn: "0" phone: "13666666666" placeId: 5 productId: 31 productName: "空调" startTime: null storeId: 1 storeName: "西安仓库" supplyId: 4 } ------------------------------------------------------------------- 入库单管理模块: 1.入库单列表 /instore/store-list -- 查询所有仓库 /instore/instore-page-list?storeId=&productName=&startTime=&endTime=&pageSize=5&pageNum=1&totalNum=0 查询所有入库单并分页 或者 根据仓库id、商品名称、起止时间查询入库单并分页 请求参数分析: 页码pageNum 每页行数pageSize --- Page对象接收并封装 storeId productName startTime endTime --- InStore对象接收并封装 -- InStore追加属性productName startTime endTime 入库单列表展示的信息分析: 仓库名称 商品名 入库价格 创建人 -- InStore追加属性storeName productName inPrice userCode 2.确定入库 1>将入库单状态改为已入库 2>增加商品的库存 /instore/instore-confirm { createBy: 1 createTime: "2022-04-20T17:30:25.000+00:00" endTime: null inNum: 50 inPrice: 2000 insId: 35 isIn: "0" productId: 22 productName: "空调" startTime: null storeId: 1 storeName: "西安仓库" userCode: "admin" } ---------------------------------------------------------------------------------- 出库单管理模块: 1.出库列表 /outstore/store-list -- 查询所有仓库 /outstore/outstore-page-list?storeId=&productName=&startTime=&endTime=&isOut=&pageSize=5&pageNum=1&totalNum=0 查询所有出库单并分页 或者 根据仓库id 商品名称 起止时间 是否出库查询出库单并分页; 请求参数的分析: 页码pageNum 每页行数pageSize --- Page对象接收并封装 storeId productName startTime endTime isOut --- OutStore对象接收并封装 -- OutStore类追加属性productName startTime endTime 出库单列表展示的数据的分析: 仓库名称 商品名称 创建人 --- OutStore类追加属性storeName productName userCode 2.确定出库 1>将出库单状态改为1已出库 2>修改商品的库存 -- 减库存 /outstore/outstore-confirm { createBy: 1 createTime: "2023-06-20T16:06:11.000+00:00" endTime: null isOut: "0" outNum: 10 outPrice: 2400 outsId: 14 productId: 31 productName: "空调" salePrice: null startTime: null storeId: 1 storeName: "西安仓库" tallyId: null userCode: "admin" } --------------------------------------------------------------------- 统计查询: 前端使用echarts框架,后台负责查询组装数据,然后将后台查询到的数据响应给前端,前端再以 echarts框架所需的图形结构 格式填充到图形中。 /statistics/store-invent -- 查询每个仓库存储的商品的数量; select t1.store_id, t1_store_name, ifnull(sum(product_invent),0) from store t1, product t2 where t1.store_id = t2.store_id group by t1.store_id, t1_store_name