之前写了一篇博客是用,ssm+thymeleaf+vue+shiro完成一个具有权限登录,且能增删查改的这么一个项目。当时只记录了登录权限的操作,现在补充一下增删查改的具体实现。

上一篇博客:使用shiro实现登录权限认证
详细代码说明。

首先给出完整代码,点击github连接自取

一、项目需求:

  1. 系统需要在spring boot下面开发
  2. 数据库可以需要使用mybatis(可以混合jpa开发)
  3. 页面用Thymeleaf模板和Vue.js(vuejs组件可以用element UI)
  4. 登录权限采用spring boot security 或者shiro框架(任选其一)
  5. 工作量不能低于Boot管理系统,界面美观实用

ss

二、使用到的技术栈:

  1. 数据库:springBoot,springMvc,mybatis
  2. 页面:thymeleaf,semantic,vue,bootstrap,axios
  3. 登录权限和认证:shiro

三、开发过程:

1. 大致开发流程:

  • 登录:身份认证,权限授权
  • 用户信息:增删查改

2. 具体:

2.1使用shiro安全框架:

(1)首先编写我们的底层数据库sql语句和持久层entity类:

我们需要三张表,User,Role,Permission,定义他们对于登录的作用,首先是通过姓名和密码查询User表是否有这个人,查询结束通过User的外键Role_Id,来映射这个人的角色,然后又通过Role表中的Id,来查询此角色对应的Permission表中的权限。这样我们就将这个人的身份与权限一并操作完,并且跳转到主页index.html,主页会根据权限,来展示对应的可操作事件,例如: USER有查询的权限,ADMIN在USER的基础上可以修改,添加用户信息,SUPER_ADMIN可以删除。 我们需要知道三个实体类之间的关系,User与Role一对一,Role与Permissions一对一,当然也可以把它都写成多对多,这就需要去更改数据库文件,和实体类了。
持久层可以使用lombok,让我们可以减少持久类的代码量,减少set,get的编写。

sql代码:

-- ----------------------------
-- Table structure for role
-- ----------------------------
CREATE TABLE `role`  (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '角色表主键',
  `role_name` varchar(32) DEFAULT NULL COMMENT '角色名称',
  PRIMARY KEY (`id`)
);

-- ----------------------------
-- Records of role
-- ----------------------------
INSERT INTO `role` VALUES (1, 'SUPER_ADMIN');
INSERT INTO `role` VALUES (2, 'ADMIN');
INSERT INTO `role` VALUES (3, 'USER');

-- ----------------------------
-- Table structure for user
-- ----------------------------
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user`  (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '用户主键',
  `username` varchar(32) NOT NULL COMMENT '用户名',
  `password` varchar(32) NOT NULL COMMENT '密码',
  `role_id` int(11) DEFAULT NULL COMMENT '与role角色表联系的外键',
  PRIMARY KEY (`id`),
  CONSTRAINT `user_role_on_role_id` FOREIGN KEY (`role_id`) REFERENCES `role` (`id`)
);

-- ----------------------------
-- Records of user
-- ----------------------------
INSERT INTO `user` VALUES (1, 'BWH_Steven', '666666', 1);
INSERT INTO `user` VALUES (2, 'admin', '666666', 2);
INSERT INTO `user` VALUES (3, 'zhangsan', '666666', 3);

-- ----------------------------
-- Table structure for permission
-- ----------------------------
CREATE TABLE `permission`  (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '权限表主键',
  `permission_name` varchar(50) NOT NULL COMMENT '权限名',
  `role_id` int(11) DEFAULT NULL COMMENT '与role角色表联系的外键',
  PRIMARY KEY (`id`),
  CONSTRAINT `permission_role_on_role_id` FOREIGN KEY (`role_id`) REFERENCES `role` (`id`)
);

-- ----------------------------
-- Records of permission
-- ----------------------------
INSERT INTO `permission` VALUES (1, 'user:*', 1);
INSERT INTO `permission` VALUES (2, 'user:*', 2);
INSERT INTO `permission` VALUES (3, 'user:queryAll', 3);

  • entity层比较简单,不贴代码:
(2)接下来就是pom.xml添加相关依赖,这里就不贴代码了,需要的github自取。
(3)整合mybatis和springboot:

就只需要创建一个dao层,一个服务层,需要记住要添加注解,一定要清楚他们的对应关系:

① mapper配置文件(也可以使用注解形式):

UserMapper.xml代码

<?xml version="1.0" encoding="utf-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.example.csy.dao.UserMapper">

    <select id="queryUserByUsername" resultMap="userRoleMap">
        SELECT u.*,r.role_name FROM `user` u, `role` r
          WHERE username = #{username} AND u.role_id = r.id;
    </select>
    <!-- 定义封装 User和 role 的 resultMap -->
    <resultMap id="userRoleMap" type="com.example.csy.entity.User">
        <id property="id" column="id"/>
        <result property="username" column="username"></result>
        <result property="password" column="password"></result>
        <result property="roleId" column="role_id"></result>
        <!-- 配置封装 UserPojo 的内容 -->
        <association property="role" javaType="com.example.csy.entity.Role">
            <id property="id" column="id"></id>
            <result property="roleName" column="role_name"></result>
        </association>
    </resultMap>


    <select id="queryPermissionByUsername" resultMap="permissionRoleMap">
        SELECT p.* ,r.role_name FROM `user` u, `role` r, `permission` p
          WHERE username = #{username} AND u.role_id = r.id AND p.role_id = r.id;
    </select>
    <!-- 定义封装 permission 和 role 的 resultMap -->
    <resultMap id="permissionRoleMap" type="com.example.csy.entity.Permissions">
        <id property="id" column="id"/>
        <result property="permissionName" column="permission_name"></result>
        <result property="roleId" column="role_id"></result>
        <!-- 配置封装 Role 的内容 -->
        <association property="role" javaType="com.example.csy.entity.Role">
            <id property="id" column="id"></id>
            <!--property是实体类中被赋值的参数名,column是数据库的列名-->
            <result property="roleName" column="role_name"></result>
        </association>
    </resultMap>
</mapper>

② dao层与service也比较简单。

③ 弄到这里我们的mybatis+springboot整合也基本结束,所以在测试类里测试一下:

@SpringBootTest
class CsyApplicationTests {

    @Autowired
    private UserMapper userMapper;

    @Test
    void contextLoads() {
        User admin = userMapper.queryUserByUsername("admin");
        System.out.println(admin.toString());
        Permissions permission = userMapper.queryPermissionByUsername("admin");
        System.out.println(permission.toString());
    }
}
(4)将shiro整合到项目里:

①.shiro最关键的就是Realm组件:

其可以理解为 Shiro 与 数据之间的沟通器与中间桥梁认证授权时,就会去此部分找一些内容,从本质上 Realm 就是一个经过了大量封装的安全 Dao,这是官网的介绍,我的理解对于Realm组件其实就是一个Dao,我们在这里面定义两个方法,一个身份认证doGetAuthenticationInfo,一个授权doGetAuthorizationInfo,这两个方法很关键,所以给出全部代码:

***UserRealm代码:

public class UserRealm extends AuthorizingRealm {

    @Autowired
    private UserMapper userMapper;
    /**
     * @MethodName doGetAuthorizationInfo 授权操作
     * @Description 权限配置类
     * @Param [principalCollection]
     * @Return AuthorizationInfo
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        // 获取用户名信息
        String username = (String) principalCollection.getPrimaryPrincipal();
        // 创建一个简单授权验证信息
        SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
        // 给这个用户设置从 role 表获取到的角色信息
        authorizationInfo.addRole(userMapper.queryUserByUsername(username).getRole().getRoleName());
        //给这个用户设置从 permission 表获取的权限信息
        authorizationInfo.addStringPermission(userMapper.queryPermissionByUsername(username).getPermissionName());
        return authorizationInfo;
    }
    /**
     * @MethodName doGetAuthenticationInfo 身份验证
     * @Description 认证配置类
     * @Param [authenticationToken]
     * @Return AuthenticationInfo
     * @Author WangShiLin
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        // 根据在接受前台数据创建的 Token 获取用户名
        String username = (String) authenticationToken.getPrincipal();
        // 通过用户名查询相关的用户信息(实体)
        User user = userMapper.queryUserByUsername(username);
        if (user != null) {
            // 存入 Session,可选
            SecurityUtils.getSubject().getSession().setAttribute("user", user);
            // 密码认证的工作,Shiro 来做
            AuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(user.getUsername(), user.getPassword(), "userRealm");
            return authenticationInfo;
        } else {
            // 返回 null 即会抛异常
            return null;
        }
    }
}

②接下来就是编写ShiroConfig注解类:

将所有类,属性全部注入spring容器中,但是我们使用springboot,直接使用注解:@Configuration。
该注解类主要是要将,realm类的两个自定义的方法注入,还有配置安全管理器 SecurityManager,还有就是过滤器,这个过滤器是分配权限的一个关键方法,对于这个过滤器,shiro有一个封装好的ShiroFilterFactoryBean工厂类,他有很多自定义方法,set登录页,成功页,未授权页等,这个通过springMvc的控制器控制就行。
重点说一下拦截放行(Map)这块:通过 map 键值对的形式存储,key 存储 URL ,value 存储对应的一些权限或者角色等等,其实 key 这块还是很好理解的,例如 :/css/、/user/admin/ 分别代表 css 文件夹下的所有文件,以及请求路径前缀为 /user/admin/ URL,而对应的 value 就有一定的规范了。
如下面的一些shiro自定好的权限

关键:
anon:无需认证,即可访问,也就是游客也可以访问
authc:必须认证,才能访问,也就是例如需要登录后
roles[xxx] :比如拥有某种角色身份才能访问 ,注:xxx为角色参数
perms[xxx]:必须拥有对某个请求、资源的相关权限才能访问,注:xxx为权限参数

就比如我们自己的图片,static文件下所有数据都可以直接使用anon,直接放行,不用管理。

shiroConfig配置类的所有代码:



@Configuration
public class ShiroConfig {

    //将自己的验证方式加入容器
    @Bean
    public UserRealm myShiroRealm() {
        return new UserRealm();
    }
    /**
     * 配置安全管理器 SecurityManager
     *
     * @return
     */
    @Bean
    public DefaultWebSecurityManager securityManager() {
        // 将自定义 Realm 加进来
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        // 关联 Realm
        securityManager.setRealm(myShiroRealm());
        return securityManager;
    }
    /**
     * 配置 Shiro 过滤器
     *
     * @param securityManager
     * @return
     */
    @Bean
    public ShiroFilterFactoryBean shiroFilter(DefaultWebSecurityManager securityManager) {
        // 定义 shiroFactoryBean
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        // 关联 securityManager
        shiroFilterFactoryBean.setSecurityManager(securityManager);
        // 自定义登录页面,如果登录的时候,就会执行这个请求,即跳转到登录页
        shiroFilterFactoryBean.setLoginUrl("/toLoginPage");
        // 指定成功页面
        shiroFilterFactoryBean.setSuccessUrl("/success");
        // 指定未授权界面
        shiroFilterFactoryBean.setUnauthorizedUrl("/unauthorized");
        // 设置自定义 filter
        Map<String, Filter> filterMap = new LinkedHashMap<>();
        filterMap.put("anyRoleFilter", new MyRolesAuthorizationFilter());
        shiroFilterFactoryBean.setFilters(filterMap);
        // LinkedHashMap 是有序的,进行顺序拦截器配置
        Map<String, String> filterChainMap = new LinkedHashMap<>();
        // 配置可以匿名访问的地址,可以根据实际情况自己添加,放行一些静态资源等,anon 表示放行
        filterChainMap.put("/css/**", "anon");
        filterChainMap.put("/img/**", "anon");
        filterChainMap.put("/js/**", "anon");
        // 指定页面放行,例如登录页面允许所有人登录
        filterChainMap.put("/toLoginPage", "anon");
        // 以“/user/admin” 开头的用户需要身份认证,authc 表示要进行身份认证
        filterChainMap.put("/user/admin/**", "authc");
        // 页面 -用户需要角色认证
//        filterChainMap.put("/levelA/**", "anyRoleFilter[USER,ADMIN,SUPER_ADMIN]");
        filterChainMap.put("/levelB/**", "anyRoleFilter[ADMIN,SUPER_ADMIN]");
//        filterChainMap.put("/levelC/**", "anyRoleFilter[SUPER_ADMIN]");
//        filterChainMap.put("/levelA/**", "roles[USER]");
//        filterChainMap.put("/levelB/**", "roles[ADMIN]");
//        filterChainMap.put("/levelC/**", "roles[SUPER_ADMIN]");
        // /user/admin/ 下的所有请求都要经过权限认证,只有权限为 user:[*] 的可以访问,也可以具体设置到 user:xxx
        filterChainMap.put("/user/admin/**", "perms[user:*]");
        // 配置注销过滤器
        filterChainMap.put("/logout", "logout");
        // 将Map 存入过滤器
        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainMap);
        return shiroFilterFactoryBean;
    }
    /**
     * 整合 thymeleaf
     * @return
     */
    @Bean(name = "shiroDialect")
    public ShiroDialect shiroDialect(){
        return new ShiroDialect();
    }
}

④自定义一个角色认证过滤器MyRolesAuthorizationFilter:

因为我们的角色,只需用有一个角色就能访问到映射页面,shiro默认是hasAllRoles,也就是说,我们要满足所有的身份才能访问,所以需要我们自定义一个hasAnyRoles,任选其一角色即可。

public class MyRolesAuthorizationFilter extends AuthorizationFilter {
    @SuppressWarnings({"unchecked"})
    public boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws IOException {
        Subject subject = getSubject(request, response);
        String[] rolesArray = (String[]) mappedValue;
        if (rolesArray == null || rolesArray.length == 0) {
            //no roles specified, so nothing to check - allow access.
            return false;
        }
        List<String> roles = CollectionUtils.asList(rolesArray);
        boolean[] hasRoles = subject.hasRoles(roles);
        for (boolean hasRole : hasRoles) {
            if (hasRole) {
                return true;
            }
        }
        return false;
    }
}
(5)将整合Thymeleaf和semantic进来:

Ui我们使用的是semantic,感觉还可以:接下来,就直接展示页面效果了:
我们对各种角色对数据库的操作进行了权限的限制,如开头所说,USER有查询的权限,ADMIN在USER的基础上可以修改,添加用户信息,SUPER_ADMIN可以删除。
我们实现这种操作,是对表格的一部分标签设置了部分角色才能查看:
关键代码是index主页中标签的声明:

A.USER有查询的权限
shiro:hasAnyRoles=“SUPER_ADMIN,ADMIN,USER”

B.ADMIN在USER的基础上可以修改
shiro:hasAnyRoles=“SUPER_ADMIN,ADMIN”

C.SUPER_ADMIN在ADMIN权限上可以删除
shiro:hasAnyRoles=“SUPER_ADMIN”

(6)最后就是controller,由于比较简单,就不写代码了。
(7)shiro效果展示:

登录login.html

login

USER权限:

user

admin权限:

ad

SUPER_ADMIN权限:

sa

至此shiro实现登录的身份认证和权限分配操作已经搞定,接下来就是用户信息的增删查改:

2.2实现用户信息的增删查改:

(1)dao(mapper)层和service层的编写:

由于使用的mybatis,所以我们需要有两种方式,一种是使用注解,一种是编写配置文件,在项目里,两种方式,都使用了的。
对于两个一对多的查询使用的是配置文件形式,其他的增、删、改都是用的是注解形式,由于mapper类比较简单就不贴代码了,还有一个UserMapper.xml配置文件的代码,已经在shiro那里贴出来了,这里也不书写了。
然后就是service类的编写,我们使用@Service注解,接口和实现类都需要@service注解,然后在实现类中通过mapper调用方法,再加上一些限制如:
增加用户信息时,如果该用户存在,就打印失败,方法直接结束。

(2)编写测试类:

写了测试成功之后,如果出错,我们可以不用担心是DAO层和service出错了。

@Test
    void queryAll(){
        List<User> users = userService.queryAll();
        System.out.println(users);
    }

    @Test
    void contextLoads() {
        User admin = userService.queryUserByUsername("admin");
        System.out.println(admin.toString());
        Permissions permission = userService.queryPermissionByUsername("admin");
        System.out.println(permission.toString());
    }
    @Test
    void addTest() {
        User user = new User("wu","123",3);
        int i = userService.addUser(user);
        System.out.println(i);
    }
    @Test
    void deleteTest() {
        userService.deleteUser("zhangsan");
    }
    @Test
    void updateTest() {
        User user = new User("ww","123",3);
    }
}

(3)显示数据到主页index.html上:

主要使用vue,axios将数据显示到页面,使用vue的指令,比如v-for,v-model等,ui使用的是semantic,bootstrap,我们将数据操作分为四步怎么实现的。

A.查询所有:

在mapper的定义中,我们有一个queryAll()方法,在controller中,我们返回一个List,所以index中获取这个list,就是最关键的问题了,这里我们在index页面中直接把vue对象定义出来,var vm = new Vue();将数据显示的表格定义一个id,这个id由vue代理。
只需要在data属性里,定义一个数组userList,来将queryAll查询到的list数据赋值到userList数组里。
当然查询这个方法,按我们的一般体验来说,应该要登录了就显示,所以axios就起到作用了,他可以让我们不用刷新就把数据显示到页面上,而且在vue中使用axios也比较的方便。
我们需要考虑一个问题,就是查询操作应该多久显示,我们知道vue对象使用声明周期的,我们可以在创建vue对象之后就调用queryAll方法,这样就可以实现一登录就查询所有的操作了。
在这里我们只需要在vue对象中添加两个方法,一个queryAll,一个create方法。需要值得注意的是我们给userList赋值时,需要声明一个变量 var _this = this;因为vue对象,和axios是不一样的,只有将this赋值,才会调用vue中的对象userList,不然则会调用axios的this,当然我们没有写,所以如果这样写出来,会发现什么数据都没有。
赋值userList后,就只需要展示,使用v-for指令,和取值符号就解决了。

Vue代码部分:

    var vm = new Vue({
        el: "#app",
        data: {
            user: {id:"",
                username:"",
                password:"",
                roleId:"",
                role:{id:"",roleName:""}
                },
            userList: [],
            wo:"ri"
        },
        methods: {
            addUser:function (user) {
                var _this = this;
                axios.post("/user/admin/add",_this.user).then(function (response) {
                    _this.queryAll();
                    console.log(_this.userList);
                }).catch(function (err) {
                    console.log(_this.user);
                    console.log(err);
                });
            },
            deleteUser:function (username) {
                var _this = this;
                axios.post("/user/admin/delete",username=username).then(function (response) {
                    _this.queryAll();
                }).catch(function (err) {
                });
            },
            queryAll: function () {
                var _this = this;
                axios.get("/user/queryAll").then(function (response) {
                    _this.userList = response.data;
                    console.log(_this.userList);
                }).catch(function (err) {
                    console.log(err);
                });
            },
            queryByName: function (username) {
                var _this = this;
                axios.get("/user/queryByName", {
                    params: {
                        username: username
                    }
                }).then(function (response) {
                    _this.user = response.data;
                    $('#updateModal').modal("show");
                    console.log(_this.user);
                }).catch(function (err) {
                    console.log(err);
                });
            },
            updateUser: function (user) {
                var _this = this;
                axios.post("/user/admin/update",_this.user).then(function (response) {
                    _this.queryAll();
                    console.log(_this.user);
                }).catch(function (err) {
                });
            }
        },
        created(){
            this.queryAll();
        }
    });

Html表格那部分代码比较简单就不贴了。

B.删除用户:

对于删除用户,我们可以再每个查询结果后弄一个td,这样我们就可以获取当前这一行的user的id,方便赋值,这里我们使用v-on:,直接调用vue中定义的方法deleteUser(id),在这里我们也使用axios.post(),因为我们要传参数,执行操作,deleteUser方法的代码就在上面,同时我们需要在执行完操作之后,直接查询所有方法queryAll,所以我们需要在delete结束后,调用_this.queryAll()方法。这样交互性比较好。

C.修改用户:

修改用户我们可以使用bootstrap中的模态框,使用模态框的好处,就是交互性好,不需要跳转页面,使用起来更加简洁。使用模态框也不是很难,就只需要在div标签中的class使用bootstrap的语法,在点击修改之后,会调用queryByName方法,在方法中使用取值符号来获取模态框的id并且调用bootstrap封装的moal(“show)方法,显示模态框。
_this.user = response.data;$(’#updateModal’).modal(“show”);

E.增加用户:

增加用户和修改用户差不多,也是v-model实现,只不过加了提示,当然后续操作和上面一样,也是直接queryAll(),还有一点因为网页传给我们的数据时json,所以需要在controller中使用@RequestBody将json转换成字符串,如果有返回值也要使用@ResponseBody。因为要赋值。

add
到此开发就结束了。

四、实验成果展示:

实现的效果大家可以克隆下来源代码自己测试,这里我只给出一些大概的展示。
(1)增加用户:
a

本博客内容基本到此结束,如果觉得有用的,你懂得,码字不易!嘻嘻!

Logo

前往低代码交流专区

更多推荐