后台管理系统权限设计
程序员文章站
2022-04-26 11:26:25
...
在开发后台管理系统的时候,例如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。
- 数据权限直接分配到个人,数据权限与人绑定关系,数据权限范围:全公司,多个部门和个人。比如:小李,需要看到小张的数据,则把小张的数据权限分配给小李下即可。
注:方案一相对来说较简单,易维护。方案二数据权限比较复杂,灵活方便,不好维护。
下一篇: php数组递归方法