后台管理系统管理员权限设计与实现

   在开发后台管理系统的时候,例如OA系统,绩效系统,crm客户关系管理系统,都会存在用户权限划分的问题。该文章描述了角色的定义,以及权限的划分等含义并给出部分实现代码示例。数据库表设计如下图所示:


   

user和user_basicinfo:记录用户资料。
    role:角色表,记录公司各种角色,比如:管理员,销售,销售主管,开发,开发经理等
    permission:菜单表,记录系统的菜单栏目和页面上的按钮(例如:绩效管理,会议管理,新增绩效,预约会议),包括url和code等信息。
    role_permission:角色权限表,记录某个角色有多少个菜单。一个角色对应多个菜单,菜单不可重复。
    user_role:用户角色表,用户可拥有多个角色。

 

一.建表脚本

    CREATE TABLE `permission` (
      `ID` int(11) NOT NULL AUTO_INCREMENT,
      `PID` int(11) DEFAULT NULL COMMENT '父节点名称',
      `NAME` varchar(50) COLLATE utf8_bin NOT NULL COMMENT '名称',
      `TYPE` varchar(20) COLLATE utf8_bin DEFAULT NULL COMMENT '类型:菜单or功能',
      `SORT` int(11) DEFAULT NULL COMMENT '排序',
      `URL` varchar(255) COLLATE utf8_bin DEFAULT NULL COMMENT '菜单URL',
      `PERM_CODE` varchar(50) COLLATE utf8_bin DEFAULT NULL COMMENT '菜单编码',
      `ICON` varchar(255) COLLATE utf8_bin DEFAULT NULL COMMENT '图标',
      `STATE` varchar(10) COLLATE utf8_bin DEFAULT NULL,
      `DESCRIPTION` varchar(200) COLLATE utf8_bin DEFAULT NULL,
      `CATEGORY_ID` bigint(20) DEFAULT NULL COMMENT '导航ID',
      `ICON_URL` varchar(200) COLLATE utf8_bin DEFAULT NULL COMMENT '图标的URL',
      `IS_VALID` tinyint(4) NOT NULL DEFAULT '0' COMMENT '0 有效  1 无效',
      `DOMAIN_ID` int(11) DEFAULT NULL COMMENT '来源系统ID',
      PRIMARY KEY (`ID`),
      KEY `idx_pid` (`PID`)
    ) ENGINE=InnoDB AUTO_INCREMENT=177 DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='菜单';

    CREATE TABLE `role` (
      `ID` int(11) NOT NULL AUTO_INCREMENT,
      `NAME` varchar(20) COLLATE utf8_bin NOT NULL COMMENT '角色名称',
      `ROLE_CODE` varchar(20) COLLATE utf8_bin NOT NULL COMMENT '角色编码',
      `DESCRIPTION` varchar(200) COLLATE utf8_bin DEFAULT NULL COMMENT '角色描述',
      `SORT` smallint(6) DEFAULT NULL COMMENT '排序',
      `DEL_FLAG` varchar(255) COLLATE utf8_bin DEFAULT NULL,
      PRIMARY KEY (`ID`)
    ) ENGINE=InnoDB AUTO_INCREMENT=18 DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='角色表';

    CREATE TABLE `role_permission` (
      `ID` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键id',
      `ROLE_ID` int(11) DEFAULT NULL COMMENT '角色id',
      `PERMISSION_ID` int(11) DEFAULT NULL COMMENT '菜单ID',
      PRIMARY KEY (`ID`)
    ) ENGINE=InnoDB AUTO_INCREMENT=614 DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='角色权限表';CREATE TABLE `user_role` (

      `ID` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
      `USER_ID` int(11) NOT NULL COMMENT '用户ID',
      `ROLE_ID` int(11) NOT NULL COMMENT '角色ID',
      PRIMARY KEY (`ID`)
    ) ENGINE=InnoDB AUTO_INCREMENT=327 DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='用户角色表';

用户表结构就不贴了。

二.url拦截

   permission表中的菜单和按钮如何控制?

      方案一:当然做得简单点,登录时获取用户权限信息,有权限则显示菜单和按钮标签,无权限则隐藏。(不推荐)

      方案二:在方案一的基础上做个拦截器,每次访问url的时候,判断该用户是否有该权限。
    拦截器:

    package com.dfws.manage.utils;
     
    import java.util.ArrayList;
    import java.util.Collection;
    import java.util.Collections;
    import java.util.Enumeration;
    import java.util.StringTokenizer;
     
    import javax.servlet.http.Cookie;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
     
    import org.apache.http.entity.ContentType;
    import org.apache.log4j.Logger;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.beans.factory.annotation.Qualifier;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.web.servlet.ModelAndView;
    import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
    import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
     
    import com.alibaba.druid.util.StringUtils;
    import com.dfws.manage.base.ApiResult;
    import com.dfws.manage.customer.dto.Login;
    import com.dfws.manage.shiro.redis.conf.CasTicketCache;
    import com.dfws.manage.user.domain.DfwsSysUserAccount;
    import com.dfws.manage.user.service.DfwsSysUserAccountService;
    import com.tianyu.jty.system.share.SharedUser;
     
        /**
         * 登陆拦截器
         */
        public class AuthorizeInterceptor extends HandlerInterceptorAdapter {
            private static final Logger logger = Logger.getLogger(AuthorizeInterceptor.class);
     
            private final String loginUrl = "/notLogin";//未登录接口地址
            
            public static Collection<String> ANON_URLS = new ArrayList<String>();
     
            public static String DEFAULT_SEPARATOR = ",";
            
            @Value(value = "${cas.logout.url}")
            private String logoutUrl;
            
             @Autowired
            @Qualifier(value = "casTicketCache")
            private CasTicketCache casTicketCache;
     
            public static void setAnonUrls(String anonUrls) {
                StringTokenizer tokenizer = new StringTokenizer(anonUrls, DEFAULT_SEPARATOR);
                while(tokenizer.hasMoreElements()) {
                    String token = tokenizer.nextToken();
                    ANON_URLS.add(token);
                }
                ANON_URLS = Collections.unmodifiableCollection(ANON_URLS);
            }
            
            @Autowired
            private DfwsSysUserAccountService userAccountService;
            
            
            @Override
            public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
                                   ModelAndView modelAndView) throws Exception {
                super.postHandle(request, response, handler, modelAndView);
            }
     
        /**
         * 前置处理
         */
        @Override
        public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
     
            String uri = request.getRequestURI();
     
            String contextPath = request.getContextPath();
     
            String uriWithoutContx = uri.substring(contextPath.length());
     
            if (ANON_URLS.contains(uriWithoutContx)) {
                return true;
            }
     
            System.out.println("请求接口:" + uriWithoutContx);
     
            System.err.println(request.getParameter("loginName"));
            System.err.println(request.getParameter("token"));
            String loginName = request.getParameter("loginName");
            String token = request.getParameter("token");
     
            boolean reLogin = false;
     
            if(uriWithoutContx.contains("dict") || uriWithoutContx.equals("crm")
                    || uriWithoutContx.equals("excel")){
                return true;
            }
            
            if (!StringUtils.isEmpty(loginName) && !StringUtils.isEmpty(token)) {
                SharedUser user = casTicketCache.get(token);
                if (user == null ) {
                    reLogin = true;
                }
            } else {
                reLogin = true;
            }
     
            // 需要做重新登陆
            if (reLogin) {
     
                // System.out.println(request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+loginUrl+"/" );
     
                // System.out.println(request.getContextPath() + loginUrl);
                // response.sendRedirect(request.getContextPath() + loginUrl);
     
                response.sendRedirect(logoutUrl);
     
                return false;
     
            } else {
                // 不需要做重新登陆
                return true;
            }
     
            // return super.preHandle(request, response, handler);
        }
     
            @Override
            public void afterConcurrentHandlingStarted(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
                super.afterConcurrentHandlingStarted(request, response, handler);
            }
     
            /**
             * 结束处理
             */
            @Override
            public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
                super.afterCompletion(request, response, handler, ex);
            }
     
            /**
             * 获取http的所有请求参数
             */
            private String getAllParamFromHttpReq(HttpServletRequest request) {
                Enumeration paramNames = request.getParameterNames();
                StringBuffer sb = new StringBuffer();
                while (paramNames.hasMoreElements()) {
                    String paramName = (String) paramNames.nextElement();
     
                    String[] paramValues = request.getParameterValues(paramName);
                    if (paramValues.length == 1) {
                        String paramValue = paramValues[0];
                        if (paramValue.length() != 0) {
                            sb.append("&").append(paramName).append("=").append(paramValue);
                        }
                    }
                }
     
                return sb.toString();
            }
        
    }

        <!-- 全局拦截器处理 -->
      <mvc:interceptors>
          <mvc:interceptor>
             <mvc:mapping path="/**/*"/>
             <mvc:exclude-mapping path="/css/**"/>
             <mvc:exclude-mapping path="/**/*.css"/>
             <mvc:exclude-mapping path="/js/**"/>
             <mvc:exclude-mapping path="/**/*.js"/>
             <mvc:exclude-mapping path="/img/**"/>
             <mvc:exclude-mapping path="/fonts/**"/>
             <mvc:exclude-mapping path="/backstage/**"/>
     
             <mvc:exclude-mapping path="/preLogin/**"/>
             <mvc:exclude-mapping path="/login/**"/>
             <mvc:exclude-mapping path="/captcha/**"/>
     
            <!-- 直接将bean配置到mvc:interceptors,将会应用到所有的url请求HandlerMapping,达到全局拦截器的目的。 -->
            <bean class="com.dfws.manage.utils.AuthorizeInterceptor"/>
         </mvc:interceptor>
     
      </mvc:interceptors>


三:权限扩展(数据权限)


      上面仅仅实现了用户的基本菜单和按钮权限,然后一个大型公司的项目,数据比较敏感,会控制菜单的数据展示范围。数据权限的控制根据公司业务需求定义。下面提两个参考方案。

    根据部门划分数据权限,新增数据权限角色,将部门挂载这个角色中,然后给这个角色添加人员。有该角色的人,则可以看到该角色下的所有部门。比如:客户管理栏目,本来是可以看到公司所有客户信息,加了数据权限后,你只能看到你所在角色的部门和下级部门的数据的,如果你的角色拥有的全公司的部门,则你能看到全公司的数据。在写sql的时候,用where in查找即可,in里面查询的是员工id。
    数据权限直接分配到个人,数据权限与人绑定关系,数据权限范围:全公司,多个部门和个人。比如:小李,需要看到小张的数据,则把小张的数据权限分配给小李下即可。

注:方案一相对来说较简单,易维护。方案二数据权限比较复杂,灵活方便,不好维护。


作者:一个小马龙
原文:https://blog.csdn.net/qq_27811247/article/details/80725974
感谢原文作者。

猜你喜欢

转载自blog.csdn.net/weixin_40913261/article/details/85112207