本框架采用Spring MVC+Mybatis+Freemarker+Adminlte前端 组合在一起搭建一个管理系统。
数据库专门建立了一张功能表sys_Function,它属于某个模块,它有个权限值字段(该值为2的指数倍,为什么,下面再解释?)。
如何判断某个角色是否可以进行某个功能操作(例如删除模块)?
如上图角色权限表sys_RolePermission所示,该表有个字段P_Value等于P_ModuleCode模块所选功能的权限值之和。
例如功能管理模块定义了4个功能,分别为查看、添加、修改、删除功能,这4个功能的权限值分别为1、2、4、8,那么sys_RolePermission的该模块的权限值P_Value如果等于1,则表示只有查看功能,如果等于3,则表示具有查看和添加功能,如果要具有所有功能,则值要等于1+2+4+8=15。
那如果某个用户多个角色对同一个模块有不同的权限值,例如用户x具有角色1和角色2,角色1对模块a的权限值为3,角色2对模块a的权限值为9,那用户对模块a的权限应该是多少呢?其实只要做一个按位与操作即可,即1&9=11,并不是1+9=10.这就是为什么让功能值设置为2的指数倍形式的原因,其实质是为了进行二进制的操作。假如有4个功能,则将权限值用4位的二进制形式表示,每一位分别表示一个功能,0表示无权限,1表示有权限。同理3的二进制形式为0011,9的二进制形式为1001,作与运算后的结果为1011,即权限值为11。
最后判断是否具有某功能,则用该功能权限值与计算后的角色权限值作与运算,例如上面判断是否具有修改功能(权限值为4),将4与11作与运算,4&11=0,则表示无权限,而添加功能(权限值2),2&11=2,则表示有权限,同理删除功能(权限值8),8&11=8表示有权限。
写日志功能用到的是AOP切面技术,这样可以与实际的业务代码相分离,互不影响。
<beans:bean id="aspectEventLog" class="com.jykj.check.filter.EventLogAspect" />
<aop:config proxy-target-class="true">
<aop:aspect ref="aspectEventLog">
<aop:pointcut id="myService"
expression="@annotation(com.jykj.check.annotation.Operation) and execution(* com.jykj.check.service..*.*(..)) " />
<aop:after-returning pointcut-ref="myService" method="doAfterReturning"/>
<aop:after-throwing pointcut-ref="myService" method="doAfterThrowing" throwing="ex"/>
aop:aspect>
aop:config>
package com.jykj.check.filter;
import java.lang.reflect.Method;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import com.alibaba.fastjson.JSON;
import com.jykj.check.annotation.Operation;
import com.jykj.check.exception.AuthorizationException;
import com.jykj.check.service.SysEventService;
//事件日志 切面,凡是带有 @Operation 注解的service方法都将会写日志
public class EventLogAspect {
@Autowired
SysEventService sysEventService;
public void doAfterReturning(JoinPoint jp) throws AuthorizationException {
Method soruceMethod = getSourceMethod(jp);
if(soruceMethod!=null){
Operation oper = soruceMethod.getAnnotation(Operation.class);
if (oper != null) {
sysEventService.insertEventLog(oper.type(),oper.desc()+"("+extractParam(jp.getArgs(),oper.arguDesc())+") 成功");
System.out.println("切面日志:"+oper.desc()+"("+extractParam(jp.getArgs(),oper.arguDesc())+") 成功");
}
}
}
public void doAfterThrowing(JoinPoint jp, Throwable ex) throws AuthorizationException {
Method soruceMethod = getSourceMethod(jp);
if(soruceMethod!=null){
Operation oper = soruceMethod.getAnnotation(Operation.class);
if (oper != null) {
sysEventService.insertEventLog(oper.type(),oper.desc()+
"("+extractParam(jp.getArgs(),oper.arguDesc())+" 出现异常:"+ex.getMessage());
}
}
}
private Method getSourceMethod(JoinPoint jp){
Method proxyMethod = ((MethodSignature) jp.getSignature()).getMethod();
try {
return jp.getTarget().getClass().getMethod(proxyMethod.getName(), proxyMethod.getParameterTypes());
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (SecurityException e) {
e.printStackTrace();
}
return null;
}
private String extractParam(Object[] objParam, String[] arguDesc) {
StringBuilder sb = new StringBuilder();
int len = objParam.length.length?objParam.length:arguDesc.length;//取最小值
for (int i = 0; i < len; i++) {
//空字符串将不解析,8个原生数据类型以及字符串直接输出,对象用json输出
if(arguDesc[i]!=null && !arguDesc[i].trim().isEmpty()){
Object obj = objParam[i];
if(obj instanceof String)
sb.append(arguDesc[i]+":"+objParam[i]+",");
else if(obj instanceof Integer || obj instanceof Byte || obj instanceof Short || obj instanceof Character
|| obj instanceof Long || obj instanceof Double || obj instanceof Float || obj instanceof Boolean){
sb.append(arguDesc[i]+":"+objParam[i]+",");
}
else{
sb.append(arguDesc[i]+":"+JSON.toJSONString(obj)+",");
}
}
}
String rs = sb.toString();
rs = rs.substring(0,rs.length()-1);
return rs.length()<=400?rs:rs.substring(0,400);
}
}
再需要写日志的方法(通常是service方法)上加一个自定义的注解@Operation即可
package com.jykj.check.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @Descrption该注解描述方法的操作类型和方法的参数意义
*/
@Target(value = ElementType.METHOD)
@Retention(value = RetentionPolicy.RUNTIME)
@Documented
public @interface Operation {
/**
* @Description描述操作类型 为必填项,1:登录日志2:操作日志
*/
int type();
/**
* @Description描述操作意义 比如申报通过或者不通过等
*/
String desc() default "";
/**
* @Description描述操作方法的参数意义 数组长度需与参数长度一致,否则会参数与描述不一致的情况
*/
String[] arguDesc() default {};
}
前端框架使用的是AdminLTE,其包含了很多技术例如bootstrap、datatables等等,非常繁杂。
可以从官网下载该框架:AdminLTE官网
框架涉及到的东西实在是太多太多,这里就不一 一列举了。
开发环境:
Spring tool suite 3.9+JDK8+Sqlserver2008
Import项目后如果发现一大堆错误,通常是pom.xml文件中所依赖的jar包未下载,需要手动或在线下载jar包到你本地的mven仓库中。
下面附上框架项目的下载地址,里面包含项目以及Sqlserver的数据库Check的备份包。
框架项目下载地址:框架项目下载
《道德经》第三章:
不上贤,使民不争;不贵难得之货,使民不为盗;不见可欲,使民不乱。是以圣人之治也,虚其心,实其腹,弱其志,强其骨,恒使民无知、无欲也。使夫知不敢、弗为而已,则无不治矣。
译文:不推崇有才德的人,导使老百姓不互相争夺;不珍爱难得的财物,导使老百姓不去偷窃;不显耀足以引起贪心的事物,导使民心不被迷乱。因此,圣人的治理原则是:排空百姓的心机,填饱百姓的肚腹,减弱百姓的竞争意图,增强百姓的筋骨体魄,经常使老百姓没有智巧,没有欲望。致使那些有才智的人也不敢妄为造事。圣人按照“无为”的原则去做,办事顺应自然,那么,天才就不会不太平了。
无为并非啥事都不做,无为是执政者不为自己去攫取民众的利益。