# 基于SpringBoot + Bootstrap 的超市账单后台管理系统 **Repository Path**: ccuni/springboot-mall-system ## Basic Information - **Project Name**: 基于SpringBoot + Bootstrap 的超市账单后台管理系统 - **Description**: 基于SpringBoot + Bootstrap 的超市账单后台管理系统 - **Primary Language**: Unknown - **License**: MulanPSL-2.0 - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 11 - **Forks**: 1 - **Created**: 2022-08-03 - **Last Updated**: 2024-08-29 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # 一、项目介绍 --- 此项目是我之前发布的 [项目实战2: 基于SpringBoot的购物商城系统](https://blog.csdn.net/unirithe/category_11875707.html) 的改良版,之前的项目定位是购物商城,但是缺了很多模块,比如没有评论、购物车、收藏等购物商城的基本功能,顶多只算是一个增删改查的案例,其中对于一些技术的应用只是表面层次,例如 Redis,仅仅用来保存有期限的验证码。 本次改良将项目定位成一套超市账单后台管理系统,该管理系统的主要特点是可以动态地修改不同角色的权限。结合现实生活中,超市里不同身份的人具有不同的职责与权限。这次的项目更有专一性,虽然仍然缺少许多内容,但是可以作为 SpringBoot 增删改查的练手项目。 目前这个项目存在的不足(部分): - 缺少日志管理 - 缺少导入、导出 - 缺少系统备份 - 存在一些业务逻辑的 BUG,但不影响基本使用 - ... 采用技术栈: ![在这里插入图片描述](https://img-blog.csdnimg.cn/ae617a185e4c4c9391143379b99d79b3.png#pic_center =80%x) 用到的技术栈包括工具: ![在这里插入图片描述](https://img-blog.csdnimg.cn/976296670f7f4f6286325735222371dd.png#pic_center =80%x) # 二、数据库设计 --- ![在这里插入图片描述](https://img-blog.csdnimg.cn/612c2393b0794487a3844ac80d0ae907.jpeg#pic_center =80%x) # 三、概要设计 --- ## 确立对象,划分模块 首先确定系统的对象,然后分为不同的模块,根据对象的不同场景继续划分模块。 以最基本的系统用户出发,目前我设计的用户对象有以下 5 种: **1. 供应商管理员** 用于管理超市中供应商的角色,主要是对于供应商信息进行管理,比如查询、添加、删除、修改供应商。 **2. 销售员** 根据商品来生成账单的角色,类似超市的收银员,比如查询商品、生成账单,修改账单状态。 **3. 商品管理员** 主要管理商品的角色,类似超市的货物管理员,负责管理每个商品的信息(位置、价格等),我们这里不涉及位置的管理,比如查询商品、修改商品信息、删除商品。 **4. 网站管理员** 具有网站所有权限的角色,是管理其他所有角色的关键角色,比如管理用户信息、用户角色、角色权限等。 **5. 经理** 查询系统的营收情况,后台统计报表信息,不用编辑任何信息,只需要查看可视化的图表,同时还可以查看用户信息,用户角色等。 综上,以上 5 种角色是结合之前的系统设计出来的,之前的项目确实太乱了,定位不清晰,现在这么简单的列出五种角色后,就清楚了许多,而且他们各司其职,我们可以更好的利用 Shrio 框架来进行权限管理。 # 四、详细设计 --- 详细设计这一块不知道如何描述,我在写这个 “项目” 的时候,基本上就是想到什么写什么,只知道大概的流程,比如先从注册、登录的页面开始写起,写完页面,然后再写 Controller、Service、DAO。 现在发现,之前真的浪费太多时间了,页面能复制的就直接复制了,需要改的就改一下,目前接触了 Vue2,发现前端工程化的编码加上 组件库,非常容易上手而且写出来很清晰,像我在这个项目写的,尽管文件区分比较明确,但是里面的逻辑依然比较繁琐。 这里不一 一描述我这拙劣的设计方法了(太一般啦),就挑几个我自认为需要记录的内容说一下吧。 ## 4.1 Shiro 授权与鉴权流程梳理 ### 4.1.1 了解 Shiro 参考资料:[Shiro 教程](https://www.w3cschool.cn/shiro/co4m1if2.html) Apache Shiro 是 Java 的一个安全框架。对比 Spring Security,功能没有那么强大,但是比较灵活。 既支持 普通的 Java 程序,又支持 Web开发的 JavaEE应用程序。 Shiro 解决的问题: ![在这里插入图片描述](https://img-blog.csdnimg.cn/82b37bcb48cf401fad4e991333985237.png#pic_center =80%x) Shiro 架构图: ![在这里插入图片描述](https://img-blog.csdnimg.cn/92663da02af14973af985f2d1e372440.png#pic_center =90%x) - Subject:主体,代表了当前 “用户”,属于抽象概念,所有 Subject 都会绑定到 SecurityManager。 - SecurityManager:安全管理器,管理所有的 Subject;是 Shiro 的核心,类似 SpringMVC 的 DispatcherServlet 。 - Realm:域,Shiro 从 Realm 获取安全数据(如用户、角色、权限),可以把 Realm 看成 DataSource,即数据源。 当用户登录时需进行授权,则需提前指定权限,然后再给指定的用户赋予相应的权限, Shiro 支持使用 ini 配置文件来授权,同样支持 DB 数据库来授权,这里则使用 MySQL 来存储 权限的内容,之后通过硬编码的方式,将 MySQL的权限注册到 Shiro 的会话中心。 ### 4.1.2 Shiro 数据库设计 参考:[RBAC模型 资料](https://www.cnblogs.com/niuli1987/p/9871182.html) Shiro 是用于授权和鉴权的框架,除此之外还需要使用 RBAC 模型来形成完整的一套支持权限管理的系统。 RBAC 即 Role-Base-Access-Controle ,基于角色的访问控制。 RBAC 模型分为四种,分别是 RBAC0、RBAC1、RBAC2 和 RBAC3, 安全等级依次提升。 目前了解 RBAC0 就行,只有更复杂的业务系统才需要更高权限模型。 RBAC0 是其他三个模型的基础,提出的是用户、角色、权限,用户之间的关系,如下图: ![在这里插入图片描述](https://img-blog.csdnimg.cn/9a987a495d1045ecae3c31b9911ee16f.png#pic_center =80%x) 根据这个模型来建立 五张数据表,如下图: ![在这里插入图片描述](https://img-blog.csdnimg.cn/d6c6a546cd434f70ba36e5535c8f8c03.png) ### 4.1.3 SpringBoot 整合 Shiro 思路 #### 1. 引入 Shiro 依赖 这里使用 Spring 整合后的 Shiro 依赖 `shiro-spring` ,当引入依赖后经过一些配置,将 Shiro 相关的组件注入到 Spring 的容器中。 ```xml org.apache.shiro shiro-spring 1.6.0 ``` #### 2. 自定义 Realm Shiro 的 Realm 层负责和数据库交互,定义认证与授权这两个过程,接下来是 Java 代码实现的流程: ```java package com.star.ms.admin.shiro; import org.apache.shiro.realm.AuthorizingRealm; // 实现 Realm 需要继承 Shiro 提供的 AuthorizingRealm 类 public class CustomRealm extends AuthorizingRealm { // 从 Spring 容器获取 业务逻辑层中 用户、角色、权限的实现 @Autowired private UserService userService; @Autowired private RoleService roleService; @Autowired private PermService permService; // 授权 @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); String principal = (String) principalCollection.getPrimaryPrincipal(); // 获取当前的用户信息 User user = userService.getByCode(principal); if(user != null){ // 获取用户的角色 Role role = roleService.getByUserCode(user.getUsercode()); user.setRole(role); // 赋予当前用户对应的角色信息(相同角色的权限是一致的) if(role != null) { info.addRole(role.getName()); // 添加权限 List perms = permService.getByRoleId(role.getId()); if(perms!=null && !CollectionUtils.isEmpty(perms)) { perms.forEach(perm -> { info.addStringPermission(perm.getUrl()); }); } } } return info; } // 认证 @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { // 获取身份信息 String principal = (String) token.getPrincipal(); // 调用业务层,获取数据库里对应的用户信息 User user = userService.getByCode(principal); if(!ObjectUtils.isEmpty(user)){ return new SimpleAuthenticationInfo( principal, user.getPassword(), ByteSource.Util.bytes(user.getSalt()), this.getName()); } else return null; } } ``` 自定义 Realm 类里制定了 用户在注册登录时候的认证过程 doGetAuthenticationInfo,以及在登录后的授权过程 doGetAuthorizationInfo。 #### 3. 注入 Shiro 的组件到 IOC 容器中 ```java package com.star.ms.config; @Configuration public class ShiroConfig { @Autowired RoleService roleService; @Autowired PermService permService; // 创建 Realm 对象 @Bean public CustomRealm customerRealm(){ CustomRealm realm = new CustomRealm(); // 修改凭证校验匹配器 HashedCredentialsMatcher hcm = new HashedCredentialsMatcher(); hcm.setStoredCredentialsHexEncoded(true); // 设置加密算法为 MD5 hcm.setHashAlgorithmName("MD5"); // 设置散列次数 hcm.setHashIterations(1024); realm.setCredentialsMatcher(hcm); return realm; } // 安全管理器 @Bean(name="securityManager") public DefaultWebSecurityManager getDefaultWebSecurityManager( @Qualifier("customerRealm") Realm realm){ DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); securityManager.setRealm(realm); return securityManager; } // 过滤链工厂 @Bean public ShiroFilterFactoryBean getShiroFilterFactoryBean( @Qualifier("securityManager") DefaultWebSecurityManager defaultWebSecurityManager){ ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean(); // 设置安全管理器 bean.setSecurityManager(defaultWebSecurityManager); Map filterMap = new LinkedHashMap<>(); // 获取所有权限信息 配置数据库的权限信息 List perms = permService.list(); perms.forEach(perm ->{ for (String p : perm.getUrl().split(",")) { filterMap.put(p, String.format("perms[%s]",p)); } }); // 配置公共权限 (游客的权限) filterMap.put("/user/login", "anon"); filterMap.put("/user/verify", "anon"); filterMap.put("/register/*", "anon"); filterMap.put("/login.html", "anon"); filterMap.put("/home.html", "anon"); bean.setFilterChainDefinitionMap(filterMap); // 设置登录的请求 bean.setLoginUrl("/login.html"); // 未授权页面 bean.setUnauthorizedUrl("/noauth"); return bean; } // 整合 ShiroDialect: 用来整合 shiro thymeleaf @Bean public ShiroDialect getShiroDialect(){ return new ShiroDialect(); } // Shiro注解开发需要的Bean @Bean public AuthorizationAttributeSourceAdvisor getAuthorizationAttributeSourceAdvisor(){ return new AuthorizationAttributeSourceAdvisor(); } } ``` 注入的 Bean 主要有 1)自定义的 Realm 对象;2)Shiro 安全管理器 ;3)Shiro 的 过滤链工厂 。 - 自定义 Realm,负责指定 Shiro 进行认证和鉴权的一种策略。 - Shiro 安全管理器,负责添加自定义的 Realm,在用户登录前后走自定义的认证与授权过程。 - Shiro 过滤链工厂,负责指定有哪些权限的限制,包括设置公用的资源。 #### 4. Shiro 的使用 在配置好 Shrio 的权限以及用户的认证和鉴权方式后,接下来就是关于 Shiro 的使用,以 UserService 中的登录为例 ```java public User login(User user) { User loginUser = null; if(user.getUsercode()!=null && user.getPassword()!=null) { loginUser = userDAO.selectByCode(user.getUsercode()); // 封装一个 Token UsernamePasswordToken token = new UsernamePasswordToken(user.getUsercode(), user.getPassword(), false); // 获取主体 Subject subject = SecurityUtils.getSubject(); subject.login(token); // 验证成功后返回对象 // 查询角色 loginUser.setRole(roleService.getByUserCode(loginUser.getUsercode())); } return loginUser; } ``` 认证使用流程如下: 1. 通过 SecurityUtils 对象获取到当前的 Subject 2. 根据用户的账号和密码封装一个 Shiro 提供的 Token 类 3. 调用 Subject 的 login方法,将 token 传入自定义Realm的认证方法,如果 login 登录失败的话就会抛出 Shiro 提供的几个异常,这里我没有捕获异常,就默认不会出现。 Shiro 鉴权的使用有许多,比如在 Controller 层:同样是通过 SecurityUtils 获取 Subject,然后调用 Subject 对象的 isPermmited 判断是否有某个权限。 ```java @GetMapping("/admin/{type}") public String toAdminUser(Model model, @PathVariable String type){ com.github.theborakompanioni thymeleaf-extras-shiro 2.1.0 ``` 另一种就是使用经过 Thymeleaf 模板引擎渲染的 HTML,需要单独引入 thymeleaf-shiro的依赖 ```xml com.github.theborakompanioni thymeleaf-extras-shiro 2.1.0 ``` 使用时: ```html
shiro:hasRole="管理员">
``` 至此, Shiro 的详细设计已完毕。 ## 4.2 阿里 OSS 云存储服务 为了减轻服务器压力,对于管理系统的图片,我一致使用的是 阿里云的 OSS 云存储服务,首先得费用很低,一年10块左右,其次是更安全、更高效。 阿里云 OSS 官方地址:[阿里云 OSS 服务](https://oss.console.aliyun.com/bucket) SpringBoot 整合 阿里云 OSS 可参考这篇文章:[SpringBoot 整合 OSS](https://www.jianshu.com/p/978e206ab8fd) 项目中用 OSS 的方法是先声明 OssService 接口,定义一些与OSS交互有关的方法。 ```java package com.star.ms.common.service; import com.star.ms.common.entity.api.OssUploadResult; import org.springframework.stereotype.Service; import java.util.List; @Service public interface OssService { // 获取所有图片的路径 public List getDefaultHeadImgLink(); // 上传文件 public OssUploadResult upload(String userCode, String img64base); // 获取文件路径 public String getUserImgPath(String userCode, String imgType); } ``` 这些方法会用到 OSS 依赖提供的 OSS 对象,所以在实现这个接口前,需要声明 OSS 相关的 Bean 到 Spring 容器 ```java package com.star.ms.common.api; import com.aliyun.oss.*; import com.aliyun.oss.model.ListObjectsRequest; import com.aliyun.oss.model.OSSObjectSummary; import com.aliyun.oss.model.ObjectListing; import lombok.Data; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.stereotype.Component; import java.util.LinkedList; import java.util.List; @Configuration public class OssApi { public static final String ENDPOINT = "OSS的域"; public static final String ACCESS_KEY_ID = "OSS的验证ID"; public static final String ACCESS_KEY_SECRET = "OSS的验证密钥"; public static final String BUCKET_NAME = "OSS中Buncket块的名称"; // 根据配置将 OSS 注入到 Spring 的 IoC 容器 @Bean public OSS OSSClient(){ return new OSSClientBuilder().build(ENDPOINT, ACCESS_KEY_ID, ACCESS_KEY_SECRET); } } ``` ## 4.3 页面设计 以下页面是我用 Bootstrap5 凑出来的,可能不太没美观,以简约为主。由于想着用 AJAX 请求数据,尽量做到前后端分离,结果在后面用到了 Boostrap5 的模态框,写了很多重复的杂冗的 JS 代码... ### 1. 登录页面 ![在这里插入图片描述](https://img-blog.csdnimg.cn/a25cd0fb83f644ea8de39517bb09b30f.png) ### 2. 注册页面 ![在这里插入图片描述](https://img-blog.csdnimg.cn/d520ea6cf812435bbb77451e72805371.png) ### 3. 后台报表页面 ![在这里插入图片描述](https://img-blog.csdnimg.cn/4e9738104a16477282b34dc22915d410.png) ### 4. 后台用户管理页面 ![在这里插入图片描述](https://img-blog.csdnimg.cn/3abf5d4c3fc24523907f586f1cbc7055.png) ![在这里插入图片描述](https://img-blog.csdnimg.cn/f5c5f488eb7746ad96ea21d8f0d75e98.png) ![在这里插入图片描述](https://img-blog.csdnimg.cn/0343a1a477f34ab49427a94fc75f7d7e.png) ### 5. 用户角色管理页面 ![在这里插入图片描述](https://img-blog.csdnimg.cn/cda75b49217a4ed5b1eec90711208f3b.png) ![在这里插入图片描述](https://img-blog.csdnimg.cn/575938c79cd346859b3c6195bfe08917.png) ![在这里插入图片描述](https://img-blog.csdnimg.cn/e3b1fe95c5744b6b88045d3f67eb2a8c.png) ### 6. 供应商管理页面 ![在这里插入图片描述](https://img-blog.csdnimg.cn/59cf11470d7c49959d87e973b2311403.png) ![在这里插入图片描述](https://img-blog.csdnimg.cn/1027ba142d4e494184efb5d33351f058.png) ![在这里插入图片描述](https://img-blog.csdnimg.cn/6f1b97de557b4d4dbffc37ede3f6fa09.png) ![在这里插入图片描述](https://img-blog.csdnimg.cn/abef048037f44fe5998d4dfe648a0dd8.png) ### 7. 商品管理页面 ![在这里插入图片描述](https://img-blog.csdnimg.cn/d099af8555bf4eb49a9d27038115e48f.png) ![在这里插入图片描述](https://img-blog.csdnimg.cn/2b40d8decfeb4cbfa4179f1953f5970b.png) ![在这里插入图片描述](https://img-blog.csdnimg.cn/5becabd640ba43b4b33735378caca092.png) ![在这里插入图片描述](https://img-blog.csdnimg.cn/63b8e22ec2c44ea481d7e78c621f02d3.png) ![在这里插入图片描述](https://img-blog.csdnimg.cn/a6d9d1b17ef847d0be8b526636212437.png) ### 8. 账单管理页面 ![在这里插入图片描述](https://img-blog.csdnimg.cn/777d2959c87a4f9d8d69dca1b31a2239.png) ![在这里插入图片描述](https://img-blog.csdnimg.cn/42d2283f6f2546ba9b5fa21012c21c36.png) ![在这里插入图片描述](https://img-blog.csdnimg.cn/2f58bcae36e7495cbf8eab27aa4101f6.png) ![在这里插入图片描述](https://img-blog.csdnimg.cn/f98a071f20014761bdfb65c19c94b06d.png) ### 9. 角色权限管理页面 ![在这里插入图片描述](https://img-blog.csdnimg.cn/f38aa2aa759d419795965d28cc6b3b89.png) ![在这里插入图片描述](https://img-blog.csdnimg.cn/538d34ec83e1402f9692de3666caf440.png) ![在这里插入图片描述](https://img-blog.csdnimg.cn/5f1e255c915c4761b89fdbb40040e570.png) ### 10. 权限列表管理页面 ![在这里插入图片描述](https://img-blog.csdnimg.cn/9ef5d9da2d7e46e992505eefd072d34b.png) ![在这里插入图片描述](https://img-blog.csdnimg.cn/34ff26e283c14cdfbba71712fc4afd65.png) ![在这里插入图片描述](https://img-blog.csdnimg.cn/1c3c29837ece46adbde649d9627db75c.png) ### 11. 个人信息页面 ![在这里插入图片描述](https://img-blog.csdnimg.cn/26f155121bdf4d36b252e26f09a12249.png) ![在这里插入图片描述](https://img-blog.csdnimg.cn/4387cc9c56bd46eaa007880c44f26fb1.png) ![在这里插入图片描述](https://img-blog.csdnimg.cn/f8d3b134a67a414f8542995ef00e6e1a.png) ![在这里插入图片描述](https://img-blog.csdnimg.cn/0d0b6a3e247a43b38135ae09aa43e9d1.png) ![在这里插入图片描述](https://img-blog.csdnimg.cn/358d3b5b953146489106cfec7c27a83d.png) # 五、代码获取 这个项目本来就没什么技术含量,所以我决定直接开源,希望能帮助到有需要的朋友吧,也希望自己将来有更好的技术水平来优化一下。 安全起见,我关闭了 OSS的可写功能,所以部署项目后的上传自定义图片功能将失效,过段时间我会考虑将 OSS 移除,采用本地存储,这样就可以一键部署成功了。