搭建一套通用的後臺管理框架對於以後的快速開發時是非常重要的。通常框架需要包含權限驗證、日誌、及一些基礎數據的增刪改查功能。
本框架採用Spring MVC+Mybatis+Freemarker+Adminlte前端 組合在一起搭建一個管理系統。
大概的樣子如下:
1.權限
角色->應用->模塊->功能
數據庫專門建立了一張功能表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表示有權限。
2.日誌
寫日誌功能用到的是AOP切面技術,這樣可以與實際的業務代碼相分離,互不影響。
<beans:bean id="aspectEventLog" class="com.jykj.check.filter.EventLogAspect" />
<!-- <aop:aspectj-autoproxy /> -->
<!-- 對帶有@Operation註解的service包及其子包所有方法執行寫日誌操作 && execution(* com.jykj.check.service.*.*(..)) -->
<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>
EventLogAspect.java
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<arguDesc.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 {};
}
3.前端框架
前端框架使用的是AdminLTE,其包含了很多技術例如bootstrap、datatables等等,非常繁雜。
可以從官網下載該框架:AdminLTE官網
4.其他
框架涉及到的東西實在是太多太多,這裏就不一 一列舉了。
開發環境:
Spring tool suite 3.9+JDK8+Sqlserver2008
Import項目後如果發現一大堆錯誤,通常是pom.xml文件中所依賴的jar包未下載,需要手動或在線下載jar包到你本地的mven倉庫中。
下面附上框架項目的下載地址,裏面包含項目以及Sqlserver的數據庫Check的備份包。
框架項目下載地址:框架項目下載
《道德經》第三章:
不上賢,使民不爭;不貴難得之貨,使民不爲盜;不見可欲,使民不亂。是以聖人之治也,虛其心,實其腹,弱其志,強其骨,恆使民無知、無慾也。使夫知不敢、弗爲而已,則無不治矣。
譯文:不推崇有才德的人,導使老百姓不互相爭奪;不珍愛難得的財物,導使老百姓不去偷竊;不顯耀足以引起貪心的事物,導使民心不被迷亂。因此,聖人的治理原則是:排空百姓的心機,填飽百姓的肚腹,減弱百姓的競爭意圖,增強百姓的筋骨體魄,經常使老百姓沒有智巧,沒有慾望。致使那些有才智的人也不敢妄爲造事。聖人按照“無爲”的原則去做,辦事順應自然,那麼,天才就不會不太平了。
無爲並非啥事都不做,無爲是執政者不爲自己去攫取民衆的利益。