Springboot 自定義註解 AOP切面獲取操作日誌
阿新 • • 發佈:2019-01-02
編碼思想:
新增和修改資料,記錄使用者操作資訊(建立人,修改人) ,然後每個模組打算根據操作資料的主鍵id關聯日誌表查詢操作人資訊;需要宣告每個模組名稱及操作方法(constant包中便是宣告的模組和操作方法列舉)
檔案目錄:
1. build.gradle引入jar包
compile('org.springframework.boot:spring-boot-starter-aop')
2.application.yml加入宣告
spring:
aop:
proxy-target-class: true
auto: true
3.自定義註解@ControllerLog
package com.wdletu.log.annotation; import com.wdletu.log.constant.OperateModule; import com.wdletu.log.constant.OperateType; import java.lang.annotation.*; /** * Created by zhangmy on 2017/7/24. */ @Target({ElementType.PARAMETER, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited public @interface ControllerLog { /** * 操作描述 業務名稱business * * @return */ String description() default ""; /** * 操作模組 * * @return */ OperateModule module(); /** * 操作型別 create modify delete * * @return */ OperateType opType(); /** * 主鍵入參引數名稱,入參中的哪個引數為主鍵 * * @return */ String primaryKeyName() default ""; /** * 主鍵在引數中的順序,從0開始,預設0 */ int primaryKeySort() default 0; /** * 主鍵所屬類 * * @return */ Class<?> primaryKeyBelongClass(); }
4. 定義模組名稱列舉
5.定義操作型別列舉package com.wdletu.log.constant; /** * Created by zhangmy on 2017/7/26. */ public enum OperateModule { SightMerchant("商家管理"), AdminUser("使用者管理"), Tour("行程管理"), UserTour("使用者行程"); private String text; OperateModule(String text) { this.text = text; } public String getText() { return text; } }
package com.wdletu.log.constant;
/**
* Created by zhangmy on 2017/7/26.
*/
public enum OperateType {
/**
* 當操作型別為created時,要求方法返回格式為:return ok("具體操作資訊", new MapBean("此處為實體主鍵屬性名稱", primaryKeyValue));
*/
create, modify, delete
}
6. 切面
package com.wdletu.log.aspect;
import com.wdletu.core.exception.ServiceException;
import com.wdletu.core.util.MapBean;
import com.wdletu.core.util.StringUtil;
import com.wdletu.log.annotation.ControllerLog;
import com.wdletu.log.constant.OperateType;
import com.wdletu.log.entity.OperateLog;
import com.wdletu.log.service.OperateLogService;
import com.wdletu.security.JwtService;
import com.wdletu.travel.admin.entity.AdminUser;
import com.wdletu.travel.admin.service.AdminUserService;
import net.sf.json.JSONObject;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.CodeSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Component;
import org.springframework.validation.BindingResult;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.persistence.Id;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.HashMap;
import java.util.Map;
/**
* Created by zhangmy on 2017/7/25.
*/
@Aspect
@Component
public class WebLogAspect {
@Autowired
private OperateLogService operateLogService;
@Autowired
private JwtService jwtService;
@Autowired
private AdminUserService adminUserService;
@Value("${jwt.header}")
private String tokenHeader;
private Logger logger = LoggerFactory.getLogger(this.getClass());
/**
* 獲取class的主鍵欄位名 主鍵值
*/
public static Object getPrimaryKeyName(Class<?> clazz) throws Exception {
Object param = null;
//遞迴獲取父子類所有的field
Class tempClass = clazz;
//當父類為null的時候說明到達了最上層的父類(Object類
while (tempClass != null && !StringUtils.equals(tempClass.getName().toLowerCase(), "java.lang.object")) {
Field[] fields = tempClass.getDeclaredFields();
for (Field field : fields) {
String fieldName = field.getName();
//boolean型別不必判斷,因實體裡包含boolean型別的屬性,getter方法是以is開頭,不是get開頭
if (field.getType().equals(Boolean.class) || field.getType().getName().equals("boolean")) {
continue;
}
if ((field.getModifiers() & Modifier.FINAL) == Modifier.FINAL) {
continue;
}
String getterMethod = "get" + fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1);
Method method = tempClass.getDeclaredMethod(getterMethod);
//欄位上是否存在@Id註解
Id primaryAnnotation = field.getAnnotation(Id.class);
//getter方法上是否存在@Id註解
if (primaryAnnotation == null) {
primaryAnnotation = method.getAnnotation(Id.class);
}
//存在@Id註解,則說明該欄位為主鍵
if (primaryAnnotation != null) {
/*String primaryKeyName = field.getName();*/
param = field.getName();
break;
}
}
if (param != null && StringUtil.isNotBlank(param.toString())) {
break;
}
//得到父類賦值給tempClass
tempClass = tempClass.getSuperclass();
}
if (param == null) {
throw new ServiceException(clazz.getName() + "實體,未設定主鍵");
}
return param;
}
/**
* 獲取@controllerLog 註解上資訊
*
* @param joinPoint
* @return map
* @throws Exception
*/
public static Map<String, Object> getControllerAnnotationValue(JoinPoint joinPoint) throws Exception {
String targetName = joinPoint.getTarget().getClass().getName();
String methodName = joinPoint.getSignature().getName();
Object[] arguments = joinPoint.getArgs();
Class targetClass = Class.forName(targetName);
Method[] methods = targetClass.getMethods();
Map<String, Object> map = new HashMap<>();
for (Method method : methods) {
if (method.getName().equals(methodName)) {
Class[] classes = method.getParameterTypes();
if (classes.length == arguments.length) {
//取入引數據
String description = method.getAnnotation(ControllerLog.class).description();
String module = method.getAnnotation(ControllerLog.class).module().name();
String opType = method.getAnnotation(ControllerLog.class).opType().name();
String primaryKeyName = method.getAnnotation(ControllerLog.class).primaryKeyName();
int primaryKeySort = method.getAnnotation(ControllerLog.class).primaryKeySort();
Class<?> clazz = method.getAnnotation(ControllerLog.class).primaryKeyBelongClass();
map.put("module", module);
map.put("opType", opType);
map.put("business", description);
map.put("primaryKeyName", primaryKeyName);
map.put("primaryKeySort", primaryKeySort);
map.put("primaryKeyBelongClass", clazz);
break;
}
}
}
return map;
}
/**
* 定義一個切入點.
* ("execution(public * com.kfit.*.web..*.*(..))")
* 解釋下:
* 第一個 * 代表任意修飾符及任意返回值.
* 第二個 * 任意包名
* 第三個 * 代表任意方法.
* 第四個 * 定義在web包或者子包
* 第五個 * 任意方法
* .. 匹配任意數量的引數.
*/
@Pointcut("execution(public * com.wdletu..*.controller..*.*(..)) && @annotation(com.wdletu.log.annotation.ControllerLog)")
public void webLog() {
}
@Around("webLog()")
public Object round(ProceedingJoinPoint joinPoint) throws Throwable {
logger.info("環繞通知開始........");
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
/*try {*/
//讀取使用者
String token = request.getHeader(tokenHeader);
Long userId = jwtService.getUserIdFromToken(token);
AdminUser adminUser = adminUserService.findByUserId(userId);
String username = adminUser.getName();
//入參 value
Object[] args = joinPoint.getArgs();
//入參名稱
String[] paramNames = ((CodeSignature) joinPoint.getSignature()).getParameterNames();
Map<String, Object> params = new HashMap<>();
//獲取所有引數物件
for (int i = 0; i < args.length; i++) {
if (null != args[i]) {
if (args[i] instanceof BindingResult) {
params.put(paramNames[i], "bindingResult");
} else {
params.put(paramNames[i], args[i]);
}
} else {
params.put(paramNames[i], "無");
}
}
Map<String, Object> values = getControllerAnnotationValue(joinPoint);
String opType = values.get("opType").toString();
String module = values.get("module").toString();
String business = values.get("business").toString();
String primaryKeyName = values.get("primaryKeyName").toString();
int primaryKeySort = Integer.parseInt(values.get("primaryKeySort").toString());
Class<?> primaryKeyBelongClass = (Class<?>) values.get("primaryKeyBelongClass");
Object primaryKeyValue = null;
if (StringUtils.isNotBlank(primaryKeyName) && OperateType.valueOf(opType) != OperateType.create) {
primaryKeyValue = args[primaryKeySort];
}
//切面返回值
Object returnValue = joinPoint.proceed();
if (OperateType.valueOf(opType) == OperateType.create) {
// 主要目的是為了獲取方法儲存成功的返回資料格式,以獲取儲存成功的資料id
// 此處要限制新增返回成功的格式,return ok("具體操作資訊", new MapBean("此處為實體主鍵屬性名稱", primaryKeyValue));
// 不然自己也可定義格式,進行拆分獲取主鍵
primaryKeyName = getPrimaryKeyName(primaryKeyBelongClass).toString();
if (returnValue instanceof ResponseEntity<?>) {
Object entity = ((ResponseEntity<?>) returnValue).getBody();
if (entity instanceof MapBean) {
MapBean mapBean = (MapBean) entity;
Boolean success = mapBean.get("success");
if (success) {
MapBean content = mapBean.get("content");
if (content != null) {
primaryKeyValue = content.get(primaryKeyName);
}
}
}
}
}
OperateLog operateLog = new OperateLog();
operateLog.setParams(JSONObject.fromObject(params).toString());
operateLog.setUserId(userId);
operateLog.setUserName(username);
operateLog.setModule(module);
operateLog.setOpType(opType);
operateLog.setBusiness(business);
operateLog.setRecordId(primaryKeyValue == null ? null : Long.parseLong(primaryKeyValue.toString()));
operateLogService.save(operateLog);
/*} catch (Throwable throwable) {
throwable.printStackTrace();
logger.error("日誌切面異常", throwable.getMessage());
}*/
return returnValue;
}
@AfterReturning("webLog()")
public void doAfterReturning(JoinPoint joinPoint) {
// 處理完請求,返回內容
logger.info("WebLogAspect.doAfterReturning()");
}
}
7.Cotroller中引用自定義註解,從而對該方法進行切面處理
/**
* 新增
*/
@ControllerLog(description = "新增商家資訊", module = OperateModule.SightMerchant, opType = OperateType.create, primaryKeyBelongClass = SightMerchant.class)
@RequestMapping(method = RequestMethod.POST)
@ResponseBody
public ResponseEntity save(@RequestBody @Valid SightMerchantInputVO sightMerchantInputVO, BindingResult bindingResult) {
if (bindingResult.hasErrors()) {
return error(bindingResult.getFieldError().getDefaultMessage());
}
Boolean primaryAccount = sightMerchantInputVO.getPrimaryAccount();
String leaderPhone = sightMerchantInputVO.getLeaderPhone();
if (primaryAccount != null && primaryAccount == true && StringUtils.isBlank(leaderPhone)) {
return error("負責人電話不能為空");
}
SightMerchant sightMerchant = SightMerchantInputVO.from(sightMerchantInputVO);
Long id = sightMerchantService.saveMerchantAndAccount(sightMerchant, primaryAccount);
return ok("儲存成功", new MapBean("id", id));
}
/**
* 修改狀態
*/
@ControllerLog(description = "修改商家狀態", module = OperateModule.SightMerchant, opType = OperateType.modify,
primaryKeyName = "id", primaryKeySort = 1, primaryKeyBelongClass = SightMerchant.class)
@RequestMapping(value = "/{id}/enabled", method = RequestMethod.PUT)
@ResponseBody
public ResponseEntity updateEnabled(Boolean enabled, @PathVariable Long id) {
sightMerchantService.updateEnabled(id, enabled);
return ok("操作成功");
}
8.日誌表
package com.wdletu.log.entity;
import com.wdletu.core.persistence.BaseEntity;
import javax.persistence.Entity;
import javax.persistence.Table;
/**
* Created by zhangmy on 2017/6/29.
*/
@Entity
@Table(name = "operate_log")
public class OperateLog extends BaseEntity {
private Long recordId; //操作資料id
private String module;//模組名稱
private String business;//業務方法描述
private String opType;//操作型別
private Long userId;//操作人
private String userName;//操作人姓名
private String params;//操作資料
public OperateLog() {
}
public Long getRecordId() {
return recordId;
}
public void setRecordId(Long recordId) {
this.recordId = recordId;
}
public String getModule() {
return module;
}
public void setModule(String module) {
this.module = module;
}
public String getBusiness() {
return business;
}
public void setBusiness(String business) {
this.business = business;
}
public String getOpType() {
return opType;
}
public void setOpType(String opType) {
this.opType = opType;
}
public Long getUserId() {
return userId;
}
public void setUserId(Long userId) {
this.userId = userId;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getParams() {
return params;
}
public void setParams(String params) {
this.params = params;
}
}
參考連結: