基于shiro的按钮级别的权限管理系统
一、项目背景
作为程序猿的你,是否在大学课堂上听到老师讲权限管理一脸懵逼;是否在互联网上看到炫酷的权限管理系统一脸羡慕;是否在公司学习使用权限管理一脸激动。那么,今天你看到这个教程之后,请你不要再懵逼,请不要再羡慕,请肆无忌惮的激动吧。好嗨哟,即将带你走上人生的巅峰。下面将手把手的教你实现基于shiro权限框架的权限管理系统,真正意义上的在按钮级别上完成权限控制。注:本教程侧重点在于shiro框架的使用与权限控制逻辑的实现,对于其它知识点不在重点讨论范围内。如需学习更多关于shiro的知识可在我的文章查看。
二、技术栈
后台:spring+springmvc+mybatis+shiro+kaptcha+fastjson+log4j+druid+maven+echcache+pageHelper等
前端:vue+jquery+iview+ztree等
数据库:mysql
其它:IntelliJ IDEA 2018.2.4 x64+tomcat8.0+jdk1.8
三、项目结构
四、项目预览
图1 登录首页
图2 系统首页
图3 用户列表
图4 用户新增
图5 角色列表
图6 角色新增
图7 菜单列表
图8 菜单新增
五、数据库设计
权限管理系统基础表有五张:sys_user(用户表)、sys_role(角色表)、sys_menu(资源表)、sys_user_role(用户角色关联表)、sys_role_menu(角色资源关联表),用户与角色和角色与资源都是多对多的关系,用户与资源必须通过授予角色才能建立关系。
(1)用户表详细设计
(2)角色表详细设计
(3)资源表详细设计
(4)用户角色表详细设计
(5)角色资源表详细设计
六、功能设计
(1)角色授权
此处新建一个角色为“测试”,授予用户管理菜单权限、新增按钮权限、删除按钮权限。
(2)分配角色
此处新增新建一个用户“a”,分配“测试”角色。
(3)新用户登录测试
因为“a”用户分配的角色为“测试”,“测试”角色拥有用户管理菜单、新增按钮、删除按钮权限,用该用户登录系统后只能看到用户管理菜单、新增按钮、删除按钮;不会看到其它的系统菜单和修改按钮。
七、项目代码示例
该项目的基础代码github地址为:https://github.com/tmAlj/shiro/tree/master/ssms,以下的给出的配置文件为本项目改变的内容,其余相同的部分未给出。
(1)pom.xml依赖管理配置
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.wsd</groupId> <artifactId>ssms1</artifactId> <version>1.0-SNAPSHOT</version> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <configuration> <source>7</source> <target>7</target> </configuration> </plugin> </plugins> </build> <dependencies> <!-- spring依赖 --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-core</artifactId> <version>4.3.3.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>4.3.3.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aspects</artifactId> <version>4.3.3.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context-support</artifactId> <version>4.3.3.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-beans</artifactId> <version>4.3.3.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-expression</artifactId> <version>4.3.3.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>4.3.3.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-tx</artifactId> <version>4.3.3.RELEASE</version> </dependency> <!-- end --> <!-- mysql依赖 --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.40</version> </dependency> <!-- end --> <!-- 数据源依赖 --> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.0.26</version> </dependency> <!-- end --> <!-- mybatis依赖 --> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.4.1</version> </dependency> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis-spring</artifactId> <version>1.3.0</version> </dependency> <dependency> <groupId>com.github.pagehelper</groupId> <artifactId>pagehelper</artifactId> <version>4.1.6</version> </dependency> <!-- end --> <!-- springMVC依赖 --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>4.3.3.RELEASE</version> </dependency> <dependency> <groupId>jstl</groupId> <artifactId>jstl</artifactId> <version>1.2</version> </dependency> <dependency> <groupId>taglibs</groupId> <artifactId>standard</artifactId> <version>1.1.2</version> </dependency> <!-- springMVC依赖end --> <!-- log4j依赖--> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>1.7.19</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-log4j12</artifactId> <version>1.7.19</version> </dependency> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.17</version> </dependency> <!-- end --> <!-- junit单元测试依赖 --> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> </dependency> <!-- end --> <!-- fastjson依赖 --> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.20</version> </dependency> <!-- end --> <!-- servlet依赖 --> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>3.1.0</version> </dependency> <!-- end --> <!-- commons相关依赖 --> <dependency> <groupId>commons-codec</groupId> <artifactId>commons-codec</artifactId> <version>1.10</version> </dependency> <dependency> <groupId>commons-configuration</groupId> <artifactId>commons-configuration</artifactId> <version>1.10</version> </dependency> <dependency> <groupId>commons-lang</groupId> <artifactId>commons-lang</artifactId> <version>2.6</version> </dependency> <dependency> <groupId>commons-fileupload</groupId> <artifactId>commons-fileupload</artifactId> <version>1.3.1</version> </dependency> <dependency> <groupId>commons-io</groupId> <artifactId>commons-io</artifactId> <version>2.5</version> </dependency> <dependency> <groupId>commons-logging</groupId> <artifactId>commons-logging</artifactId> <version>1.2</version> </dependency> <!-- end --> <!-- shiro依赖 --> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-core</artifactId> <version>1.3.2</version> </dependency> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-ehcache</artifactId> <version>1.3.2</version> </dependency> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring</artifactId> <version>1.3.2</version> </dependency> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-web</artifactId> <version>1.3.2</version> </dependency> <!-- end --> <!-- kaptcha验证码start --> <dependency> <groupId>com.github.axet</groupId> <artifactId>kaptcha</artifactId> <version>0.0.9</version> </dependency> <!-- end --> </dependencies> </project>
(2)web.xml配置
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd" version="4.0"> <!--项目启动首页配置--> <display-name>tm-cli</display-name> <welcome-file-list> <welcome-file>login.jsp</welcome-file> </welcome-file-list> <!-- 定义上下文,扫描spring配置文件 --> <context-param> <param-name>contextConfigLocation</param-name> <param-value> classpath:spring-config.xml </param-value> </context-param> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener </listener-class> </listener> <!-- 统一编码过滤器配置 --> <filter> <filter-name>encode</filter-name> <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class> <init-param> <param-name>encoding</param-name> <param-value>utf-8</param-value> </init-param> </filter> <filter-mapping> <filter-name>encode</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <!-- springMVC前段控制器配置 --> <servlet> <servlet-name>appServlet</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:spring-mvc-config.xml</param-value> </init-param> <load-on-startup>1</load-on-startup> <async-supported>true</async-supported> </servlet> <servlet-mapping> <servlet-name>appServlet</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping> <!-- shiro过滤器配置 --> <filter> <filter-name>shiroFilter</filter-name> <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class> <init-param> <param-name>targetFilterLifecycle</param-name> <param-value>true</param-value> </init-param> </filter> <filter-mapping> <filter-name>shiroFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> </web-app>
(3)shiro配置文件spring-shiro-config.xml配置
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd"> <!-- 配置自定义reaml --> <bean id="loginRealm" class="com.wsd.shiro.LoginRealm"/> <!-- 配置shiro的核心securityManager --> <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager"> <property name="cacheManager" ref="cacheManager"/> <!-- 配置realm --> <property name="realm" ref="loginRealm"/> <!-- 配置记住我的时长 --> <property name="rememberMeManager.cookie.maxAge" value="60"></property> </bean> <!-- 配置ehcache缓存 --> <bean id="cacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager"> <property name="cacheManagerConfigFile" value="classpath:ehcache.xml"/> </bean> <!-- 配置shiro中bean生命周期管理器 --> <bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/> <!-- AOP式方法级权限检查 --> <bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator" depends-on="lifecycleBeanPostProcessor"> <property name="proxyTargetClass" value="true" /> </bean> <bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor"> <property name="securityManager" ref="securityManager"/> </bean> <!-- shiro过滤器配置,与web.xml中shiro过滤器同名 --> <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean"> <property name="securityManager" ref="securityManager"/> <!-- 需要登录成功后跳转的页面 --> <property name="loginUrl" value="/login.jsp"/> <!-- 登录成功后跳转的页面 --> <property name="successUrl" value="/index.jsp"/> <!-- 访问未授权页面跳转的页面 --> <property name="unauthorizedUrl" value="/unauthor.html"/> <property name="filterChainDefinitions"> <!-- 静态资源需要设置为anon,否则找不到 --> <value> /statics/** = anon /plugins/** = anon /login.jsp = anon /login = anon /logout = logout /captcha.jpg = anon /** = authc </value> </property> </bean> </beans>
(4)springmvc配置文件spring-mvc-config.xml配置
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:context="http://www.springframework.org/schema/context" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> <!--视图解析器,如访问Controller中配置的"/tm",系统会自动找到路径为prefix后缀为suffix的文件并解析 --> <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="viewClass" value="org.springframework.web.servlet.view.JstlView"></property> <property name="prefix" value="/WEB-INF/pages/"></property> <property name="suffix" value=".jsp"></property> </bean> <!-- 使用零配置 --> <context:component-scan base-package="com.wsd" /> <mvc:default-servlet-handler /> <mvc:annotation-driven /> <!-- 配置interceptor拦截器 --> <mvc:interceptors> <!-- 默认拦截所有请求 --> <bean class="com.wsd.interceptor.Interceptor"/> </mvc:interceptors> <!-- 开启shiro中aop注解 --> <aop:config proxy-target-class="true"></aop:config> <!--配置fastjson,返回json数据格式--> <mvc:annotation-driven> <mvc:message-converters register-defaults="true"> <!-- 配置Fastjson支持 --> <bean class="com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter"> <property name="supportedMediaTypes"> <list> <value>application/json;charset=UTF-8</value> <value>text/html;charset=UTF-8</value> </list> </property> <property name="features"> <list> <value>WriteMapNullValue</value> <value>QuoteFieldNames</value> </list> </property> </bean> </mvc:message-converters> </mvc:annotation-driven> <!-- 配置kaptcha验证码生成器 --> <bean name="producer" class="com.google.code.kaptcha.impl.DefaultKaptcha" scope="singleton"> <property name="config"> <bean class="com.google.code.kaptcha.util.Config"> <constructor-arg> <props> <prop key="kaptcha.border">no</prop> <prop key="kaptcha.textproducer.font.color">black</prop> <prop key="kaptcha.textproducer.char.space">5</prop> </props> </constructor-arg> </bean> </property> </bean> </beans>
八、部分功能解析
(1)登录认证(认证参考)
1.1 前端实现
<%@ page contentType="text/html;charset=UTF-8" language="java" %> <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" name="viewport"> <title>用户登录</title> <link rel="stylesheet" type="text/css" href="statics/css/iview.css"> <link rel="stylesheet" type="text/css" href="statics/css/login.css" /> </head> <body> <div id="app" v-cloak> <div class="tm-title">欢迎使用tm-cli管理系统</div> <div class="tm-content"> <i-input prefix="ios-contact" placeholder="账号" class="tm-input" v-model="account"></i-input> <i-input prefix="md-lock" placeholder="密码" class="tm-input" type="password" v-model="password"></i-input> <i-input prefix="ios-analytics" placeholder="验证码" class="tm-input" v-model="code"></i-input> <div class="tm-code"> <div class="tm-picture"> <img alt="点击刷新" :src="src" @click="onRefrCodeEvet()" class="tm-img"> </div> <div class="tm-reflush" @click="onRefrCodeEvet()">点击刷新</div> </div> <i-button type="primary" long :loading="loading" @click="callLoginImpl()">登 录</i-button> <div class="tm-footer"> <div class="tm-checkbox"> <checkbox v-model="remember"></checkbox> </div> <div class="tm-remember"> 记住我 </div> <div class="tm-forget"> 忘记密码? </div> </div> </div> <div class="tm-copyright">Copyright © 2018 All Rights Reserved</div> </div> <script type="text/javascript" src="statics/js/jquery-3.2.1.js"></script> <script type="text/javascript" src="statics/js/vue.min.js"></script> <script type="text/javascript" src="statics/js/iview.min.js"></script> <script> new Vue({ el: '#app', data: { account: '', //账号 password: '', //密码 code: '', //验证码 src: 'captcha.jpg', //验证码图片 loading: false, //登录按钮加载动画 remember: false, //记住我复选框 msg: '' //提示信息 }, methods: { /*调用登录接口*/ callLoginImpl: function(){ var t = this; if(t.doCheckFormFun()){ t.loading = true; var data = "account="+this.account+"&password="+this.password+"&code="+this.code+"&remember="+this.remember; $.ajax({ type: "POST", url: "login", data: data, dataType: "json", success: function(data){ if(data.code == 0){ //登录成功 t.loading = false; parent.location.href ='index.jsp'; }else{ t.msg = data.msg; t.$Message.warning(t.msg); t.onRefrCodeEvet(); t.loading = false; } } }); } }, /*刷新验证码点击事件*/ onRefrCodeEvet: function(){ var t = this; t.src = "captcha.jpg?t=" + $.now(); }, /*表单验证方法*/ doCheckFormFun: function(){ var t = this; if(t.account == '' || t.account == undefined){ t.$Message.warning("账户输入不能为空!"); return false; } else if(t.password == '' || t.password == undefined){ t.$Message.warning("密码输入不能为空!"); return false; } else if(t.code == '' || t.code == undefined){ t.$Message.warning("验证码输入不能为空!"); return false; }else{ return true; } } } }) </script> </body> </html>
1.2 controller实现
package com.wsd.controller; import com.google.code.kaptcha.Constants; import com.google.code.kaptcha.Producer; import com.wsd.utils.ResultData; import com.wsd.utils.ShiroUtils; import org.apache.shiro.authc.*; import org.apache.shiro.crypto.hash.Sha256Hash; import org.apache.shiro.subject.Subject; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.ResponseBody; import javax.imageio.ImageIO; import javax.servlet.ServletException; import javax.servlet.ServletOutputStream; import javax.servlet.http.HttpServletResponse; import java.awt.image.BufferedImage; import java.io.IOException; /** * Created by tm on 2018/8/26. * 登录controller */ @Controller() public class LoginController { @Autowired private Producer producer; //验证码操作对象 /** * 生成验证码 * @param response * @throws ServletException * @throws IOException */ @RequestMapping("captcha.jpg") public void captcha(HttpServletResponse response)throws ServletException, IOException { //页面不用缓存 response.setHeader("Cache-Control", "no-store, no-cache"); response.setContentType("image/jpeg"); //生成文字验证码 String text = producer.createText(); //生成图片验证码 BufferedImage image = producer.createImage(text); //验证码存入session,用于登录时做对比 ShiroUtils.setSessionAttribute(Constants.KAPTCHA_SESSION_KEY, text); ServletOutputStream out = response.getOutputStream(); //输出验证码 ImageIO.write(image, "jpg", out); } /** * 登录 */ @ResponseBody @RequestMapping(value = "/login", method = RequestMethod.POST) public ResultData login(String account, String password, String code, String remember)throws IOException { String kaptcha = ShiroUtils.getKaptcha(Constants.KAPTCHA_SESSION_KEY); //session中获取保存的验证码内容 if(!kaptcha.equalsIgnoreCase(code)){ return ResultData.error("验证码不正确"); } try{ Subject subject = ShiroUtils.getSubject(); password = new Sha256Hash(password).toHex(); UsernamePasswordToken token = new UsernamePasswordToken(account, password); // 开启记住我的功能 if(remember.equals("false")){ token.setRememberMe(false); }else{ token.setRememberMe(true); } subject.login(token); }catch (UnknownAccountException e) { return ResultData.error(e.getMessage()); }catch (IncorrectCredentialsException e) { return ResultData.error(e.getMessage()); }catch (LockedAccountException e) { return ResultData.error(e.getMessage()); }catch (AuthenticationException e) { return ResultData.error("账户验证失败"); }catch (Exception e) { return ResultData.error(); } return ResultData.ok(); } }
1.3 自定义loginRealm
package com.wsd.shiro; import com.wsd.model.Menu; import com.wsd.model.User; import com.wsd.service.MenuService; import com.wsd.service.UserService; import com.wsd.service.impl.LoginServiceImpl; import com.wsd.service.impl.MenuServiceImpl; import com.wsd.service.impl.UserServiceImpl; import org.apache.commons.lang.StringUtils; import org.apache.shiro.authc.*; import org.apache.shiro.authz.AuthorizationInfo; import org.apache.shiro.authz.SimpleAuthorizationInfo; import org.apache.shiro.realm.AuthorizingRealm; import org.apache.shiro.subject.PrincipalCollection; import org.springframework.beans.factory.annotation.Autowired; import java.util.*; /** * Created by tm on 2018/8/26. * 自定义realm * 注:需要在spring-shiro-config.xml中配置 */ public class LoginRealm extends AuthorizingRealm { @Autowired LoginServiceImpl lsi; //注入登录service @Autowired MenuServiceImpl msi; //注入菜单service @Autowired UserServiceImpl usi; //注入用户service /*授权*/ protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { User user = (User)principalCollection.getPrimaryPrincipal(); Long userId = user.getUserId(); List<String> permsList = null; //系统管理员,拥有最高权限 if(userId == 1){ List<Menu> menuList = msi.queryList(new HashMap<String, Object>()); permsList = new ArrayList<String>(menuList.size()); for(Menu menu : menuList){ permsList.add(menu.getPerms()); } }else{ permsList = usi.queryAllPerms(userId); } //用户权限列表 Set<String> permsSet = new HashSet<String>(); for(String perms : permsList){ if(StringUtils.isBlank(perms)){ continue; } permsSet.addAll(Arrays.asList(perms.trim().split(","))); } SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); info.setStringPermissions(permsSet); return info; } /*验证*/ protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { String account = (String) authenticationToken.getPrincipal(); //获取输入的账户 String password = new String((char[]) authenticationToken.getCredentials()); //获取输入的密码 //数据中查询用户信息 User user = lsi.queryByUserName(account); //账号不存在 if(user == null) { throw new UnknownAccountException("账号或密码不正确"); } //密码错误 if(!password.equals(user.getPassword())) { throw new IncorrectCredentialsException("账号或密码不正确"); } //账号锁定 if(user.getStatus() == 0){ throw new LockedAccountException("账号已被锁定,请联系管理员"); } SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(user, password, getName()); return info; } }
(2)权限控制(授权参考)
2.1 前端实现(通过shiro的标签控制前端权限标签参考)
<%@ page contentType="text/html;charset=UTF-8" language="java" %> <%@taglib prefix="shiro" uri="http://shiro.apache.org/tags" %> <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" name="viewport"> <title>用户管理</title> <link rel="stylesheet" type="text/css" href="${pageContext.request.contextPath}/statics/css/iview.css"> <link rel="stylesheet" type="text/css" href="${pageContext.request.contextPath}/statics/css/index.css"> </head> <body> <div id="app" v-cloak> <!--操作按钮--> <div class="tm-btns"> <shiro:hasPermission name="sys:user:save"> <i-button type="info" @click="onAddEvet()">新增</i-button> </shiro:hasPermission> <shiro:hasPermission name="sys:user:update"> <i-button type="primary" @click="onUpdateEvet()">修改</i-button> </shiro:hasPermission> <shiro:hasPermission name="sys:user:delete"> <i-button type="warning" @click="onDeleteAllEvet()">删除</i-button> </shiro:hasPermission> <shiro:hasPermission name="sys:user:list"> <div class="tm-btns-search"> <i-input placeholder="输入用户名" style="width: 110px" v-model="userName" @keyup.enter.native="doSearchEvet"> <icon type="ios-search" slot="suffix" @click="doSearchEvet()"></icon> </i-input> </div> </shiro:hasPermission> </div> <!--表格--> <shiro:hasPermission name="sys:user:list"> <i-table border :loading="loading" :columns="columns" size="small" :data="datas" :height="433" @on-selection-change="onSeltChangeEvet"></i-table> </shiro:hasPermission> <!--分页--> <shiro:hasPermission name="sys:user:list"> <div class="tm-page" v-if="pageShow"> <page :total="total" show-sizer show-total size="small" @on-change="onChangePageEvet" @on-page-size-change="onChangeSizeEvet"/> </div> </shiro:hasPermission> </div> <script type="text/javascript" src="${pageContext.request.contextPath}/statics/js/jquery-3.2.1.js"></script> <script type="text/javascript" src="${pageContext.request.contextPath}/statics/js/vue.min.js"></script> <script type="text/javascript" src="${pageContext.request.contextPath}/statics/js/iview.min.js"></script> <script> var vm = new Vue({ el: '#app', data: { loading: false, //表格加载动画 page: '1', //当前页码 limit: '10', //每页显示条数 pageShow: false, //是否显示分页标志 total: '', //数据总数 userName: '', //用户搜索框内容 parentVue: '', //父级vue对象,主要为了消息提示框的全局显示 checkList: [], //复选框保存值数组 userIdList: [], //用户id数组 columns: [ //表格头部数据 { width: 50, type: 'selection', title: '', key: 'select' }, { type: 'index', width: 70, title: '序号' }, { width: 120, title: '用户名', key: 'username' }, { width: 120, title: '手机号码', key: 'mobile' }, { width: 120, title: '状态', key: 'status', render: function(h, params){ var row = params.row; var color = row.status === 0 ? 'error' : 'success'; var text = row.status === 0 ? '禁用' : '正常'; return h('Tag', { props: { type: 'dot', color: color } }, text); } }, { title: '邮箱', key: 'email' }, { title: '创建时间', key: 'createTime', sortable: true, render: (h,params)=>{ return h('div', vm.doFormatDateFun(new Date(params.row.createTime),'yyyy-MM-dd hh:mm:ss') ) } } ], datas: [] //表格数据 }, methods: { /*调用查询用户列表接口*/ callGetUserListImpl: function(){ var p = this.parentVue; var t = this; t.loading = true; //打开加载动画 var data = "page="+t.page+"&limit="+t.limit+"&userName="+t.userName; $.ajax({ type: "POST", url: "../sys/user/list", data: data, dataType: "json", success: function(data){ if(data.code == 0){ //当数量超过10时,显示分页条 if(data.page.total > 10){ t.total = data.page.total; t.pageShow = true; } t.datas = data.page.list; t.loading = false; } }, error: function () { t.loading = false; p.$Message.error("系统异常,请稍后重试!"); } }); }, /*调用删除用户接口*/ callDeleteUserImpl: function(){ var p = this.parentVue; var t = this; t.loading = true; //打开加载动画 $.ajax({ type: "POST", url: "../sys/user/delete", data: JSON.stringify(t.userIdList), dataType: "json", contentType:"application/json", success: function(data){ t.loading = false; if(data.code == 0){ p.$Message.success('操作成功!'); t.callGetUserListImpl(); }else{ p.$Message.error(data.msg); } }, error: function () { t.loading = false; p.$Message.error("系统异常,请稍后重试!"); } }); }, /*添加按钮点击事件*/ onAddEvet: function(){ var p = this.parentVue; p.url = "sys/user/user_add"; p.title = "新增用户"; }, /*修改按钮点击事件*/ onUpdateEvet: function(){ var p = this.parentVue; var t = this; if(t.userIdList.length != 1){ p.$Message.warning('请选择一行您要修改的数据!'); }else{ p.url = "sys/user/user_add"; p.title = "修改用户"; //传参 p.userId = t.userIdList; //传参 } }, /*删除一行按钮点击事件*/ onDeleteEvet: function(item){ var p = this.parentVue; var t = this; t.userIdList = []; //清空,避免数据重复 /*系统提示框*/ p.$Modal.confirm({ title: '操作提示', content: '您确定删除所选项目吗?', onOk: function(){ t.userIdList.push(item); t.callDeleteUserImpl(); }, onCancel: function(){} }); }, /*删除所有按钮点击事件*/ onDeleteAllEvet: function(){ var p = this.parentVue; var t = this; if(this.userIdList.length == 0){ p.$Message.warning('请选择您要删除的数据!'); }else{ /*系统提示框*/ p.$Modal.confirm({ title: '操作提示', content: '您确定删除所选项目吗?', onOk: function(){ t.callDeleteUserImpl(); }, onCancel: function(){} }); } }, /*改变页码点击事件*/ onChangePageEvet: function(item){ var t = this; t.page = item; t.callGetUserListImpl(); }, /*每页显示条数点击事件*/ onChangeSizeEvet: function(item){ var t = this; t.limit = item; t.callGetUserListImpl(); }, /*搜索按钮点击事件*/ doSearchEvet: function (){ var t = this; t.callGetUserListImpl(); }, /*多选行数据按钮点击事件*/ onSeltChangeEvet: function(item){ var t = this; t.userIdList = []; //清空,避免数据重复 if(item.length != 0){ //获取多选用户ID数组 for (var i = 0; i < item.length; i++){ t.userIdList.push(item[i].userId) } } }, /*日期格式化方法*/ doFormatDateFun: function(date, fmt) { var o = { 'M+': date.getMonth() + 1, // 月份 'd+': date.getDate(), // 日 'h+': date.getHours(), // 小时 'm+': date.getMinutes(), // 分 's+': date.getSeconds(), // 秒 'S': date.getMilliseconds() // 毫秒 } if (/(y+)/.test(fmt)) { fmt = fmt.replace(RegExp.$1, (date.getFullYear() + '').substr(4 - RegExp.$1.length)) } for (var k in o) { if (new RegExp('(' + k + ')').test(fmt)) { fmt = fmt.replace(RegExp.$1, (RegExp.$1.length === 1) ? (o[k]) : (('00' + o[k]).substr(('' + o[k]).length))) } } return fmt } }, created: function(){ var t = this; t.callGetUserListImpl(); t.parentVue = parent.vm; } }) </script> </body> </html>
2.2 controller实现(通过shiro的权限注解控制请求注解参考)
package com.wsd.controller; import com.github.pagehelper.PageHelper; import com.github.pagehelper.PageInfo; import com.wsd.base.BaseController; import com.wsd.model.User; import com.wsd.service.impl.LoginServiceImpl; import com.wsd.service.impl.UserAndRoleServiceImpl; import com.wsd.service.impl.UserServiceImpl; import com.wsd.utils.ResultData; import org.apache.commons.lang.ArrayUtils; import org.apache.commons.lang.StringUtils; import org.apache.shiro.authz.annotation.RequiresPermissions; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.*; import java.util.List; /** * Created by tm on 2018/8/26. * 系统用户controller */ @Controller @RequestMapping("sys/user") public class UserController extends BaseController { @Autowired UserServiceImpl usi; //注入系统用户service @Autowired LoginServiceImpl lsi; //注入登录service @Autowired UserAndRoleServiceImpl uarsi; //注入系统用户角色service /** * 访问user.jsp页面 * @return */ @RequestMapping() public String goUserPage(){ return "user"; } /** * 访问user_add.jsp页面 * @return */ @RequestMapping("/user_add") public String goUserAddPage(){ return "user_add"; } /** * 获取用户列表 * @param page 当前页码 * @param limit 每页显示条数 * @return */ @ResponseBody @RequestMapping("/list") @RequiresPermissions("sys:user:list") public ResultData getUserList(Integer page, Integer limit, String userName){ //PageHelper分页插件 PageHelper.startPage(page, limit); List<User> userList = usi.queryList(userName); PageInfo<User> p = new PageInfo<User>(userList); return ResultData.ok().put("page", p); } /** * 用户信息 */ @ResponseBody @RequestMapping("/info") @RequiresPermissions("sys:user:info") public ResultData info(Long userId){ //执行查询 User userInfo = usi.queryObject(userId); //获取用户所属的角色列表 List<Long> roleIdList = uarsi.queryRoleIdList(userId); userInfo.setRoleIdList(roleIdList); return ResultData.ok().put("userInfo", userInfo); } /** * 保存用户 * @param user 用户实体 * @return */ @ResponseBody @RequestMapping("/save") @RequiresPermissions("sys:user:save") public ResultData saveUsers(@RequestBody User user){ //判断用户名称是否可用 User u = lsi.queryByUserName(user.getUsername()); if(u != null){ return ResultData.error("当前用户名称不能使用!"); } usi.save(user); return ResultData.ok(); } /** * 修改用户 */ @ResponseBody @RequestMapping("/update") @RequiresPermissions("sys:user:update") public ResultData update(@RequestBody User user){ usi.update(user); return ResultData.ok(); } /** * 删除用户 * @param userIdList 用户id数组 * @return */ @ResponseBody @RequestMapping("/delete") @RequiresPermissions("sys:user:delete") public ResultData deleteUsers(@RequestBody Long[] userIdList){ if(ArrayUtils.contains(userIdList, 1L)){ return ResultData.error("系统管理员不能删除"); } if(ArrayUtils.contains(userIdList, getUserId())){ return ResultData.error("当前用户不能删除"); } usi.deleteUser(userIdList); return ResultData.ok(); } }
九、参考文档
十、获取该项目源代码
A:微信扫描下方二维码,打赏一杯咖啡钱
B:打赏的时候留下您的邮箱地址,二十四小时内通过邮箱发送给您
低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
使用ELock实现高性能分布式锁(非轮询)
前言: 随着笔者的颜值不断提高,用户量的日益增长,传统的单机方案已经不能满足产品的需求。笔者在网上寻遍方案,发现均为人云亦云,一份以毫秒为精度的轮询分布式锁被转发转载上万次。然,该方案没法满足笔者性能要求。故此,笔者研发ELock插件,并发布本文章。 其实集群也好,分布式服务也好。当我们不能保证团队成员的整体素质,那么在某些业务上,分布式锁自然没法避免。 公认开发原则:能不使用分布式锁的,尽可能不使用 举个例子,一个商品交易,需要检查库存、检查余额、扣库存、扣款、变更订单状态。可能很多人觉得,在分布式环境下一定要分布式锁才能安全。 致此,笔者提供一种简单的方案: 订单处理{ if(订单状态!=待支付){ return 该订单已处理; } if(库存不足){ return 库存不足; } if(余额不足){ return 余额不足; } 事务管理(rollbackFor = Exception.class){ //修改订单状态 int changeLine = 执行语句( update 订单表 set status=已支付 whe...
- 下一篇
使用 ale.js 制作一个小而美的表格编辑器(3)
今天来教大家如何使用 ale.js 制作一个小而美的表格编辑器,首先先上 gif: 是不是还是有一点非常 cool 的感觉的?那么我们现在开始吧! 这是我们这篇文章结束后完成的效果(如果想继续完成请访问第四篇文章): ok,那继续开始吧(本篇文章是表格编辑器系列的第三篇文章,如果您还没有看过第一篇,请访问第一篇文章(开源中国)): 首先让我们把每一个列表项都添加一个他们的行数和列数作为 dataset 数据吧! 先创建一个 rowId 变量: //在 handleTemplateRender 函数里,我们把: var returnVal = "<table><thead><tr>", getSortSign = this.methods.getSortSign, sortBy = this.staticData.sortBy; //改为 var returnVal = "<table><thead><tr>", getSortSign = this.methods.getSortSign, sor...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- CentOS7,CentOS8安装Elasticsearch6.8.6
- SpringBoot2配置默认Tomcat设置,开启更多高级功能
- CentOS7,8上快速安装Gitea,搭建Git服务器
- Docker使用Oracle官方镜像安装(12C,18C,19C)
- CentOS8,CentOS7,CentOS6编译安装Redis5.0.7
- CentOS关闭SELinux安全模块
- CentOS7设置SWAP分区,小内存服务器的救世主
- Docker安装Oracle12C,快速搭建Oracle学习环境
- Docker快速安装Oracle11G,搭建oracle11g学习环境
- SpringBoot2全家桶,快速入门学习开发网站教程