编码思想:
新增和修改数据,记录用户操作信息(创建人,修改人) ,然后每个模块打算根据操作数据的主键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. 定义模块名称枚举
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;
}
}
参考链接:
http://www.cnblogs.com/shipengzhi/articles/2716004.html
http://412887952-qq-com.iteye.com/blog/2305333
http://blog.csdn.net/paincupid/article/details/51176149
http://www.cnblogs.com/chihirotan/p/6228337.html
http://blog.csdn.net/gengqiquan/article/details/70185731