項目情況:
pmdb項目開發過程中需要實現一個操作日誌的功能,即將對數據庫指定表的增刪改查操作進行記錄。我採用的是面向切面編程+自定義註解的方式實現的。這種方式的優點是,可以指定哪些方法的操作被記錄。
我在開發中遇到的問題是:
1.我們的數據庫表是有業務主鍵的,無法根據數據庫命名規則來獲取到。(日誌記錄中需要存入業務主鍵的值);
2.有的表沒有業務主鍵,可以通過命名規則根據表名來獲得Id字段名。但是有的表有多個業務主鍵,這就需要進行特殊的處理。
解決方案:
第一步:添加依賴
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency>
第二步:編寫自定義註解類
import java.lang.annotation.*;
// 自定義註釋
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MethodRemark {
// 操作描述
String remark() default "";
// 0:查 1:增 2:刪 3:改內容 4: archive 5: unarchive'
String opType() default "0";
//所操作表名
String tableName() default "";
//主鍵字段名稱
String keyName() default "";
//業務主鍵名稱
String[] businessKeyName() default {""};
}
第三步:編寫切面類(這裏面如何過去請求參數和響應參數並進行處理是關鍵)
import com.handlecar.basf_pmdb_service.common.CommonConstant;
import com.handlecar.basf_pmdb_service.dto.AbstractInputDto;
import com.handlecar.basf_pmdb_service.dto.AbstractOutputDto;
import com.handlecar.basf_pmdb_service.entity.log.BusinessCostDataLog;
import com.handlecar.basf_pmdb_service.service.log.LogService;
import com.handlecar.basf_pmdb_service.tools.JsonUtils;
import com.handlecar.basf_pmdb_service.tools.Util;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
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.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.*;
@Component
@Aspect
public class OperationLogs {
private static Logger logger = LoggerFactory.getLogger(OperationLogs.class);
@Value("${publicconfig.openlog}")
private int isOpen;
@Pointcut("execution(public * com.*.*.controller..*(..))")
public void pointcut1(){}
@Autowired
private LogService logService;
private BusinessCostDataLog businessCostDataLog;
private String keyName;
private String keyValue;
@Before(value = "pointcut1()")
public void beforMethod(JoinPoint point) throws Exception {
MethodRemark methodRemark = getMethodRemark(point);
if(methodRemark !=null && isOpen == 1){
String opType = methodRemark.opType(); //操作類型 0:查 1:增 2:刪 3:改內容 4: archive 5: unarchive'
String tableName = methodRemark.tableName(); //操作表名
String username = getCurrentUser();
String contentorg=""; //原內容
String contentnow=""; //現內容
this.keyName = getKeyName(tableName);
//如果通過表名獲取不到主鍵名稱,就通過註解指定
keyName=keyName==null? methodRemark.keyName():getKeyName(tableName);//主鍵名稱
this.keyValue = getInputParamValue(point,keyName);//主鍵值
String[] businessNames = methodRemark.businessKeyName(); //業務主鍵名稱
//封裝日誌參數
BusinessCostDataLog log=new BusinessCostDataLog();
log.setBcdlopdate(new Date()); // datetime NOT NULL COMMENT '操作時間',
log.setBcdlopuser(username); // varchar(256) NOT NULL COMMENT '操作用戶',
log.setBcdloptype(Integer.parseInt(opType)); // int(11) NOT NULL COMMENT '操作類型 0:查 1:增 2:刪 3:改內容 4: archive 5: unarchive',
log.setBcdloptable(tableName); // varchar(64) DEFAULT NULL COMMENT '操作的表名',
Map<String,Object> mapToJson=null;
StringBuilder businessValue = new StringBuilder(); //業務主鍵的值
for (String businessName : businessNames){
//刪除或者修改需要查詢原內容
if(opType.equals("2") || opType.equals("3")){
//如果該表是business_data_keyword_values,特別處理
if(tableName.equals(CommonConstant.KEYWORDS_TABLE_NAME)){
//從business_data_keywords表中查詢
mapToJson = logService.getBcdlcontentorg(log,keyName,keyValue);
String bdkkeyword=mapToJson.get("bdkkeyword").toString(); //關鍵字bdkkeyword的值---業務主鍵的值
//根據bdkkeyword的值到數據庫中查business_data_keyword_values表的記錄
log.setBcdloptable(CommonConstant.KEY_WORD_VALUES_TABLE_NAME);
contentorg=logService.getKeywordValuesLog(CommonConstant.KEY_WORD_VALUES_TABLE_NAME,businessName,bdkkeyword);
businessValue.append(bdkkeyword).append(","); //業務主鍵的值
}else{
//查詢原內容
mapToJson = logService.getBcdlcontentorg(log,keyName,keyValue);
businessValue.append(mapToJson.get(businessName).toString()).append(",") ;
contentorg= JsonUtils.map2JsonString(mapToJson);
}
}else if(opType.equals("4") || opType.equals("5")){
//增加,歸檔,解檔需要存關鍵字段值
mapToJson=logService.getBcdlcontentorg(log,keyName,keyValue);
businessValue.append(mapToJson.get(businessName).toString()).append(",") ;
}
}
String keyname=businessValue.toString();
//當opType=1時暫時不處理
if(!Util.isEmpty(keyname)){
log.setBcdlkeyname(keyname.substring(0,businessValue.length()-1)); // varchar(64) DEFAULT NULL COMMENT '操作的記錄的關鍵字段值',
log.setBcdlcontentorg(contentorg); // varchar(1024) DEFAULT NULL COMMENT '原內容',
log.setBcdlcontentnow(contentnow); // varchar(1024) DEFAULT NULL COMMENT '現內容',
}
this.businessCostDataLog = log;
}
}
@AfterReturning(returning = "result",pointcut = "pointcut1()")
public void doAfterReturning(JoinPoint point, Object result) throws Throwable {
//取註解中設置的值
MethodRemark methodRemark = getMethodRemark(point);
if(methodRemark!=null && isOpen == 1) { //如果添加了註解,被掃描到
//用戶操作類型 0:查 1:增 2:刪 3:改內容 4: archive 5: unarchive'
String opType = methodRemark.opType();
BusinessCostDataLog log = this.businessCostDataLog;
//添加的情況需要獲取添加的關鍵字段值(添加的話可以直接從輸入參數中找到對應的值)
if (opType.equals("1")) {
String[] businessKeyNames =methodRemark.businessKeyName();
StringBuilder businessValue = new StringBuilder();
for(String businessKeyName : businessKeyNames){
businessValue.append(getInputParamValue(point,businessKeyName)).append(",");
}
log.setBcdlkeyname(businessValue.toString().substring(0,businessValue.length()-1)); // varchar(64) DEFAULT NULL COMMENT '操作的記錄的關鍵字段值',
}
//判斷當前操作是否成功
AbstractOutputDto abstractOutputDto=(AbstractOutputDto) result;
int status=abstractOutputDto.getStatus();
if(status==1){ //操作成功
logService.insertBcdlcontentorg(log);
}
}
}
//根據表名獲取關鍵字段名稱
private String getKeyName(String tableName){
String[] split = tableName.split("_");
StringBuilder keyName = new StringBuilder();
for(String str:split){
keyName.append(str.substring(0,1));
}
keyName.append("id");
return keyName.toString();
}
//獲取輸入參數中某個屬性的值
private String getInputParamValue(JoinPoint point,String keyName) {
Object[] args = point.getArgs(); //獲取切點參數
Object inputParamData;
if(args[0] instanceof AbstractInputDto){
inputParamData = ((AbstractInputDto)args[0]).getData();
Field field = null;
try {
field = inputParamData.getClass().getDeclaredField(keyName);
//設置對象的訪問權限,保證對private的屬性的訪問
field.setAccessible(true);
return String.valueOf(field.get(inputParamData));
} catch (NoSuchFieldException e) {
logger.error(e.getMessage());
} catch (IllegalAccessException e) {
logger.error(e.getMessage());
}
}
return null;
}
//獲取參數中某個屬性的值
private String getOutputParamValue(Object result,String keyName) {
Object inputParamData;
if(result instanceof AbstractOutputDto){
inputParamData = ((AbstractOutputDto)result).getData();
Field field = null;
try {
field = inputParamData.getClass().getDeclaredField(keyName);
//設置對象的訪問權限,保證對private的屬性的訪問
field.setAccessible(true);
return String.valueOf(field.get(inputParamData));
} catch (NoSuchFieldException e) {
logger.error(e.getMessage());
} catch (IllegalAccessException e) {
logger.error(e.getMessage());
}
}
return null;
}
private MethodRemark getMethodRemark(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[] method = targetClass.getMethods();
for (Method m : method) {
if (m.getName().equals(methodName)) {
Class[] tmpCs = m.getParameterTypes();
if (tmpCs.length == arguments.length) {
MethodRemark methodCache = m.getAnnotation(MethodRemark.class);
if (methodCache != null && !("").equals(methodCache.remark())) {
return methodCache;
}
break;
}
}
}
return null;
}
private HttpServletRequest getRequest(JoinPoint point) {
Object[] args = point.getArgs();
for (Object obj : args) {
if (obj instanceof HttpServletRequest)
return (HttpServletRequest) obj;
}
RequestAttributes ra = RequestContextHolder.getRequestAttributes();
ServletRequestAttributes sra = (ServletRequestAttributes) ra;
HttpServletRequest request = sra.getRequest();
return request;
}
private String getRequestIP(HttpServletRequest request) {
String ip = null;
if (request.getHeader("x-forwarded-for") == null) {
ip = request.getRemoteAddr();
}else{
ip = request.getHeader("x-forwarded-for");
}
return ip;
}
private String getParam(HttpServletRequest request) {
Map properties = request.getParameterMap();
Map returnMap = new HashMap();
Iterator entries = properties.entrySet().iterator();
Map.Entry entry;
String name = "";
String value = "";
while (entries.hasNext()) {
entry = (Map.Entry) entries.next();
name = (String) entry.getKey();
Object valueObj = entry.getValue();
value = null;
if (null == valueObj) {
value = "";
} else if (valueObj instanceof String[]) {
String[] values = (String[]) valueObj;
for (int i = 0; i < values.length; i++) {
if (value == null)
value = (values[i] == null ? "" : values[i]);
else
value += "," + (values[i] == null ? "" : values[i]);
}
} else {
value = valueObj.toString();
}
returnMap.put(name, value);
}
return JsonUtils.map2JsonString(returnMap);
}
private String getCurrentUser(){
return SecurityContextHolder.getContext().getAuthentication().getName();
}
}
第四步:在Controller層的指定方法上添加註解(這裏的表和業務主鍵名稱我都使用公共類封裝起來了)
@MethodRemark(remark = "歸檔canner數據",opType = "4",tableName = CommonConstant.CANNER_TABLE_NAME, businessKeyName = CommonConstant.CANNER_BUSINESS_KEY_NAME)