仓库管理系统 -- 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<Auth>,然后将用户所有菜单的List<Auth>响应
给前端,前端框架就使用菜单树组件通过用户所有菜单的List<Auth>中每个菜单的id和pid的关系
生成菜单树; -- 菜单树是前端生成的;
2.后台系统中是查询出用户权限下的所有菜单List<Auth>,然后将用户所有菜单的List<Auth>转成
菜单树List<Auth>,最后将菜单树List<Auth>响应给前端,前端只需要做个循环迭代展示菜单树;
--- 菜单树是在后端生成的; √
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