设计细节-----适配粒度权限设计
不积跬步无以至千里,在这里,总结一下自己最近的工作积累。 前言:CRM&OA类系统的设计负责度往往是在业务上,而业务上的复杂度,通常是由不同用户在不同业务场景下的各种可操作性决定的。就是说理清了系统用户在不同场景下的操作权限,业务脉络就很清晰了。 记录下,针对最近做的一套管理系统的权限设计方案,复杂性体现在下面的描述当中: 1.这里说的权限是指数据权限以及数据特权,并非菜单权限。 2.默认数据权限规则: a.登录用户是普通员工,默认情况下(无数据特权)只能看到自己创建的数据,比如说,业务主任下挂接的经销商。 b.登录用户是管理人员,默认情况下(无数据特权)可以看到自己创建的数据,以及所管理部门和部门下子部门下(一直迭代到末端部门节点)所有员工创建的数据。 3.含数据特权 a.登录用户是普通员工,有数据特权,可以看到自己创建的数据以及数据特权部门下所有数据。 b.登录用户是管理人员,有数据特权,可以看到2-b所述的所有数据以及数据特权部门下所有数据。 4.数据特权的赋值最小单位根据业务要求设计为部门,也就是,为某个用户赋值一组数据特权,可以理解为为该用户赋予查询特权部门下的所有数据 5.非部门组织机构内部人员权限的限定(组织机构外人员不含数据特权) a.业务要求,组织机构之外,还有两种角色需要参与到业务处理过程当中,属地化业务员:规定可以操作所属经销商下的所有数据 b.经销商:可以操作该用户所有数据 实现思路: 设计严格遵守开闭原则,在需要授权的地方,过滤权限。代码入口为 --UserUtils.screeningAndProcessingEn(param); 例子部分代码如下: public JSONObject getPageList(Map param) throws Exception { param.put("search_type", "1"); UserUtils.screeningAndProcessingEn(param); ................. BeanUtils.populate(dto, param); ................ try{ getTimeRange(kjTerminalNode); lists = KjTerminalNodeService.appOauthFindList( kjTerminalNode); ................... return json; } @SuppressWarnings("unchecked") public static void screeningAndProcessingEn(Map params) throws Exception { // TODO Auto-generated method stub List<String> users = new ArrayList<String>(); Map<String, String> role = getRole(params); if (Tools.str(role.get("roleName"), "").indexOf("属地业务员") >= 0) { users.add(Tools.str(role.get("id"), "")); params.put("dealerList", kjDealerCoverMapper.getDealerBySd(users)); } else if (Tools.str(role.get("roleName"), "").indexOf("经销商") >= 0) { List<String> list = new ArrayList<>(); list.add(Tools.str(role.get("id"), "")); params.put("dealerList",list); }else { UserUtils.setUserAndOffice(params); /*users.addAll(dto.getUserList()); results.addAll(kjDealerCoverMapper.getDealerByBm(users));*/ //业务主任及以上,不通过经销商获取数据 } if (Tools.str(params.get("search_type"), "").equals("1")) { UserUtils.searchProcessing(params); }else if (Tools.str(params.get("search_type"), "").equals("2")) { UserUtils.searchProcessing2(params); } } @SuppressWarnings("unchecked") public static void setUserAndOffice(Map params){ //数据权限 User user = UserUtils.getByLoginName((String)params.get("loginName")); /*AuthorityResponse<Office> offices = officeService.getAutorOfficeEh(user.getId()); if(null != offices.getResults() && offices.getResults().size() > 0) { List<String> list = new ArrayList<>(); for (Office item : offices.getResults()) { list.add(item.getId()); } params.put("officeList", list); }*/ //不再使用获取部门,获取部门封装到sql层直接调用 String offices = userMapper.getOfficeString(user); if (StringUtils.isNotBlank(offices)) { params.put("officeList", Tools.asList(Tools.str(offices, "").split("[|]"))); LOGGER.debug("==============当前登录者可以查看部门="+JSONArray.fromObject(Tools.asList(offices.split("[|]"))).toString()); }else { LOGGER.debug("==============当前登录者没有可以查看部门"); params.put("officeList",new ArrayList<>()); } List<String> list2 = new ArrayList<>(); list2.add(user.getId()); params.put("userList", list2); LOGGER.debug("当前登录者可以查看科室="+params.get("officeList")+"==============当前登录者可以查看用户="+params.get("userList")); //searchProcessing(params); } -->screeningAndProcessingEn的具体实现细节 -->setUserAndOffice(params); -->userMapper.getOfficeString(user); -->getAutorOfficeEh(user.getId()); -->searchProcessing(params); 权限过滤采用约定好的封装数据格式,然后层层装配数据,生成相应的查询sql,在dao层实现数据的统一过滤,和业务完全解耦,只需要在业务开始前,执行screeningAndProcessingEn()方法即可完成对权限的过滤,或者是在方法上标上@PerminssionFilter注解即可。根据自定义annotation结合aspect的前置通知实现,提供了对权限过滤的注解支持。 setUserAndOffice根据约定好的数据格式,封装好该登录用户可以查看的部门,具体实现是userMapper.getOfficeString(user)和getAutorOfficeEh(user.getId());两种方式。userMapper.getOfficeString(user)的设计是基于mysql函数(写了一个根据用户id,根据一定规则获取部门的函数),设计他是因为最早的方案getAutorOfficeEh(user.getId());是在代码层面根据权限规则递归获取部门等信息,经过测试,发现响应比较慢,一条设计到过滤权限的查询,有时需要3S才能响应。 给出getAutorOfficeEh(user.getId())的代码实现: public AuthorityResponse<Office> getAutorOfficeEh(String userId,Boolean onlyDownLevel,Boolean size ){ List<Office> results = Lists.newArrayList(); //List<Office> results = new ArrayList<Office>(); AuthorityResponse<Office> response = new AuthorityResponse<Office>(); User user = UserUtils.get(userId); String roleType=""; //get current user roles List<Role> roleList = Lists.newArrayList(); roleList = user.getRoleList(); if (roleList.size()>0) { //handle the role type roleType = getHignRoleType(roleList); } //这里混入数据特权 since 2018-5-2 String officeIds = user.getOfficeIds(); if (officeIds!=null && ! "".equals(officeIds)) {// has privilege /************************ * step 3 the type of user role is manager ********************/ if (roleType.equals(RularUtils.MANAGE_TYPE)) { // handle the situation 1 has privilege and the type of user role is manager //封装数据特权部门 if (onlyDownLevel) { handleAuthorityOffice(officeIds,results); }else { handleAuthorityOffice1(officeIds,results); } //封装本身管辖部门 Office office = user.getOffice(); if (onlyDownLevel) { if (office != null) { ergodicUni(results, office); } }else { if (office != null) { ergodicUni1(results, office); } } response.setStatus(0); response.setMsg("角色为管理人员,并且有数据特权,部门查询成功"); response.setResults(results); } else { /********************************** * step 4 the type of user role is staff *******/ // handle the situation 2 has privilege and the type of user role is staff //封装数据特权部门 if (onlyDownLevel) { handleAuthorityOffice(officeIds,results); }else { handleAuthorityOffice1(officeIds,results); } response.setStatus(0); response.setMsg("角色为普通人员,并且有数据特权,部门查询成功"); response.setResults(results); } } else {// do not has privilege /********************************** * step 5 the type of user role is manager *******/ if (roleType.equals(RularUtils.MANAGE_TYPE)) { // handle the situation 3 has't privilege and the type of user role is manager Office office = user.getOffice(); if (onlyDownLevel) { if (office != null) { ergodicUni(results, office); } }else { if (office != null) { ergodicUni1(results, office); } } response.setStatus(0); response.setMsg("角色为管理人员,没有数据特权,部门查询成功"); response.setResults(results); } else { // handle the situation 4 has't privilege and the type of user role is staff if (size) { results.add(user.getOffice()); } response.setStatus(0); response.setResults(results); response.setMsg("当前用户为普通员工且没有数据特权"); } } // 这里混入数据特权 end return response; }此方法是权限过滤的核心,是文章开始权限详细说明的代码实现。 searchProcessing(params)权限过滤的最后一步,封装高级查询参数。 最终,过滤权限代码在mapper中生效,通过mybatis动态的生成响应的sql去执行。这个过程是需要约定的 ......<where> a.status = '1' and (1 = 2 <if test="officeList != null and officeList.size() > 0 "> <foreach item="item" index="index" collection="officeList"> or v.parent_ids like CONCAT(#{item}, '%') </foreach> </if> <if test="userList != null and userList.size() > 0 "> <foreach item="item" index="index" collection="userList" > or v.businessMaster_id = #{item} </foreach> </if><if test="dealerList != null and dealerList.size() > 0 "> <foreach item="item" index="index" collection="dealerList"> or v.id = #{item} </foreach> </if> ) <if test="id != null and id != '' "> AND a.id = #{id} </if> <if test="searchOfficeParams != null and searchOfficeParams.size() > 0 "> <foreach item="item" index="index" collection="searchOfficeParams"> and v.parent_ids like CONCAT(#{item}, '%') </foreach> </if> <if test="searchRegionParams != null and searchRegionParams.size() > 0 "> <foreach item="item" index="index" collection="searchRegionParams"> and kr.idpath like CONCAT(#{item}, '%') </foreach> </if>在此做个总结。