后期我大杰哥会有针对此日志实现的优化,包括注入LogUserService 的懒加载问题
【SpringBoot笔记8】Spring AOP 面向切面编程(应用篇)
自定义注解详细介绍
OperationLogDetail
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;
import com.XXXX.enums.OperationType;
@Documented
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface OperationLogDetail {
/**
* 方法描述,可使用占位符获取参数:{{id}}
*/
String detail() default "";
/**
* 日志等级:自己定,此处分为1-9
*/
int level() default 0;
/**
* 操作类型(enum):主要是select,insert,update,delete
*/
OperationType operationType() default OperationType.UNKNOWN;
/**
* 被操作的对象(此处使用enum):可以是任何对象,如表名(user),或者是工具(redis)
*/
String operationUnit() default "";
}
切入点包含两个要素:
package com.xxxx.aop;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.PrintStream;
import java.lang.reflect.Method;
import java.util.Date;
import java.util.Set;
import javax.servlet.http.HttpServletRequest;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.xxxx.annotation.OperationLogDetail;
import com.xxxx.config.LogUserService;
import com.xxxx.model.LogUser;
import com.xxxx.model.OperationLog;
import com.xxxx.service.SystemLogService;
import com.xxxx.util.JsonUtils;
import com.xxxx.util.StringUtil;
@Aspect
@Component
public class LogAspect {
@Autowired
private SystemLogService systemLogService;
@Autowired
@Lazy
private LogUserService logUserService;
private static final String SUCCESS = "SUCCESS";
private static final String ERROR = "ERROR";
private static final String SYSTEM_ID = "system";
private static final String SYSTEM_NAME = "系统";
/**
* 此处的切点是注解的方式,也可以用包名的方式达到相同的效果
* '@Pointcut("execution(* com.wwj.springboot.service.impl.*.*(..))")'
*/
@Pointcut("@annotation(com.sinog2c.annotation.OperationLogDetail)")
public void operationLog(){}
/**
* 环绕通知(Around advice) :包围一个连接点的通知,类似Web中Servlet规范中的Filter的doFilter方法。可以在方法的调用前后完成自定义的行为,也可以选择不执行。
* @param joinPoint
* @return
* @throws Throwable
*/
@Around("operationLog()")
public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
// 1.方法执行前的处理,相当于前置通知
long time = System.currentTimeMillis();
// 获取方法签名
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
// 获取方法
Method method = signature.getMethod();
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
LogUser user = logUserService.getLogUser(request);
// 创建一个日志对象(准备记录日志)
OperationLog operationLog = new OperationLog();
if(null == user){
user.setUserId(SYSTEM_ID);
user.setUserName(SYSTEM_NAME);
user.setData(0);
}
operationLog.setId(StringUtil.getUUID());//设置id
operationLog.setMethod(signature.getDeclaringTypeName() + "." + signature.getName());//设置方法名
operationLog.setIp(getIpAddress(request));//设置ip
operationLog.setUserId(user.getUserId());// 设置操作人账号
operationLog.setSource(user.getData().toString());
operationLog.setUserName(user.getUserName());// 设置操作人姓名
Object[] obj = joinPoint.getArgs();
String args = "";
if(obj != null && obj.length > 0) {
for(int i=0; i<obj.length; i++) {
args += obj[i] + ",";
}
operationLog.setArgs(args.substring(0, args.lastIndexOf(",")));//入参
}
// 获取方法上面的注解
OperationLogDetail annotation = method.getAnnotation(OperationLogDetail.class);
if(null != annotation){
operationLog.setLogLevel(annotation.level());
operationLog.setOperationType(annotation.operationType().getValue());
operationLog.setOperationUnit(annotation.operationUnit());
operationLog.setLogDescribe(getDetail(signature, obj, annotation, user));//操作说明
}
Object result = null;
try {
//让代理方法执行
result = joinPoint.proceed();
time = System.currentTimeMillis() - time;
// 2.相当于后置通知(方法成功执行之后走这里)
operationLog.setRunTime(time);
operationLog.setResult(SUCCESS);// 设置操作结果
operationLog.setReturnValue(JSON.toJSONString(result));//设置出参
} catch (Exception e) {
// 3.相当于异常通知部分
operationLog.setResult(ERROR);// 设置操作结果
operationLog.setErrorInfo(getExceptionAllinformation(e));// 设置错误信息
} finally {
// 4.相当于最终通知
operationLog.setCreateTime(new Date());// 设置操作时间
systemLogService.addLogInfo(operationLog);
}
return result;
}
/**
* 获取Exception详情
* @param ex
* @return
*/
public String getExceptionAllinformation(Exception ex){
ByteArrayOutputStream out = new ByteArrayOutputStream();
PrintStream pout = new PrintStream(out);
ex.printStackTrace(pout);
String ret = new String(out.toByteArray());
try {
pout.close();
} catch (Exception e) {
}
try {
out.close();
} catch (Exception e) {
}
return ret;
}
/**
* 对当前登录用户和占位符处理
* @param argNames 方法参数名称数组
* @param args 方法参数数组
* @param annotation 注解信息
* @return 返回处理后的描述
*/
private String getDetail(MethodSignature methodSignature, Object[] args, OperationLogDetail annotation, LogUser user){
Class[] clas = methodSignature.getParameterTypes();
String[] argNames = methodSignature.getParameterNames();
JSONObject json = new JSONObject();
for(int i = 0;i < argNames.length;i++){
if(!"java.lang.String".equals(clas[i].getName())) {
JSONObject jsonObject = JsonUtils.objectToJson(args[i]);
Set<String> keys = jsonObject.keySet();
for(String key : keys){
json.put(key, jsonObject.get(key));
}
}else {
json.put(argNames[i],args[i]);
}
}
String detail = annotation.detail();
try {
detail = "" + user.getUserName() + "=》" + annotation.detail();
for (String key : json.keySet()) {
Object value = json.get(key);
detail = detail.replace("{{" + key + "}}", JSON.toJSONString(value));
}
}catch (Exception e){
e.printStackTrace();
}
return detail;
}
/**
* 获取请求注解的ip地址
* @Title: getIpAddress
* @Description: TODO
* @param: @param request
* @param: @return
* @param: @throws IOException
* @return: String
* @throws
*/
public final static String getIpAddress(HttpServletRequest request)throws IOException {
// 获取请求主机IP地址,如果通过代理进来,则透过防火墙获取真实IP地址
String ip = request.getHeader("X-Forwarded-For");
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
if (ip == null || ip.length() == 0
|| "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("Proxy-Client-IP");
}
if (ip == null || ip.length() == 0
|| "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("WL-Proxy-Client-IP");
}
if (ip == null || ip.length() == 0
|| "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("HTTP_CLIENT_IP");
}
if (ip == null || ip.length() == 0
|| "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("HTTP_X_FORWARDED_FOR");
}
if (ip == null || ip.length() == 0
|| "unknown".equalsIgnoreCase(ip)) {
ip = request.getRemoteAddr();
}
} else if (ip.length() > 15) {
String[] ips = ip.split(",");
for (int index = 0; index < ips.length; index++) {
String strIp = (String) ips[index];
if (!("unknown".equalsIgnoreCase(strIp))) {
ip = strIp;
break;
}
}
}
return ip;
}
}
spring在IOC初始化的时候,一般的bean都是直接调用构造方法,如果该bean实现了FactroyBean接口,则会调用该bean的getObject方法获取bean,这也是Spring使用此接口构造AOP的原因,在IOC调用此方法时返回一个代理,完成AOP代理的创建
import org.springframework.beans.factory.FactoryBean;
import com.xxxx.service.SystemLogService;
public class LogBean implements FactoryBean<SystemLogService> {
@Override
public SystemLogService getObject() throws Exception {
return new SystemLogService();
}
@Override
public Class<SystemLogService> getObjectType() {
return SystemLogService.class;
}
@Override
public boolean isSingleton() {
return true;
}
}
import javax.servlet.http.HttpServletRequest;
import com.sinog2c.model.LogUser;
public interface LogUserService {
public LogUser getLogUser(HttpServletRequest request);
}
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
public class MyApplicationContext<T> implements ApplicationContextAware {
private T t;
//Spring应用上下文环境
private static ApplicationContext applicationContext;
public MyApplicationContext(T t) {
this.t = t;
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
DefaultListableBeanFactory defaultListableBeanFactory = (DefaultListableBeanFactory) applicationContext.getAutowireCapableBeanFactory();
LogUserService logUserService = (LogUserService) t;
//创建bean信息
BeanDefinitionBuilder beanDefinitionBuilder =BeanDefinitionBuilder.genericBeanDefinition(logUserService.getClass());
//动态注册bean
defaultListableBeanFactory.registerBeanDefinition("logUserService",beanDefinitionBuilder.getBeanDefinition());
MyApplicationContext.applicationContext = applicationContext;
}
/**
* 获取对象
* @param name
* @return Object 一个以类型
* @throws BeansException
*/
public static Object getBean(Class requiredType) throws BeansException {
return applicationContext.getBean(requiredType);
}
/**
* 获取对象
* @param name
* @return Object 一个以所给名字注册的bean的实例
* @throws BeansException
*/
public static Object getBean(String name) throws BeansException {
return applicationContext.getBean(name);
}
/**
* 获取类型为requiredType的对象
* 如果bean不能被类型转换,相应的异常将会被抛出(BeanNotOfRequiredTypeException)
* @param name bean注册名
* @param requiredType 返回对象类型
* @return Object 返回requiredType类型对象
* @throws BeansException
*/
public static Object getBean(String name, Class requireType) throws BeansException{
return applicationContext.getBean(name, requireType);
}
/**
* 如果BeanFactory包含一个与所给名称匹配的bean定义,则返回true
* @param name
* @return boolean
*/
public static boolean containsBean(String name){
return applicationContext.containsBean(name);
}
/**
* 判断以给定名字注册的bean定义是一个singleton还是一个prototype。
* 如果与给定名字相应的bean定义没有被找到,将会抛出一个异常(NoSuchBeanDefinitionException)
* @param name
* @return boolean
* @throws NoSuchBeanDefinitionException
*/
public static boolean isSingleton(String name) throws NoSuchBeanDefinitionException {
return applicationContext.isSingleton(name);
}
/**
* @param name
* @return Class 注册对象的类型
* @throws NoSuchBeanDefinitionException
*/
public static Class getType(String name) throws NoSuchBeanDefinitionException {
return applicationContext.getType(name);
}
/**
* 如果给定的bean名字在bean定义中有别名,则返回这些别名
* @param name
* @return
* @throws NoSuchBeanDefinitionException
*/
public static String[] getAliases(String name) throws NoSuchBeanDefinitionException {
return applicationContext.getAliases(name);
}
}
public enum OperationType {
/**
* 操作类型
*/
UNKNOWN("unknown"),
DELETE("delete"),
SELECT("select"),
UPDATE("update"),
INSERT("insert");
private String value;
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
OperationType(String s) {
this.value = s;
}
}
import java.io.Serializable;
public class LogUser implements Serializable {
private static final long serialVersionUID = -6520588712998125186L;
private String userId;
private String userName;
private Object data;
public String getUserId() {
return userId;
}
public void setUserId(String userId) {
this.userId = userId;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public Object getData() {
return data;
}
public void setData(Object data) {
this.data = data;
}
}
import java.io.Serializable;
import java.util.Date;
import com.fasterxml.jackson.annotation.JsonFormat;
public class OperationLog implements Serializable {
private static final long serialVersionUID = 5581824747183716437L;
private String id;
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")
private Date createTime;
/**
* ip地址
*/
private String ip;
/**
* 日志等级
*/
private Integer logLevel;
/**
* 被操作的对象
*/
private String operationUnit;
/**
* 方法名
*/
private String method;
/**
* 参数
*/
private String args;
/**
* 操作人id
*/
private String userId;
/**
* 操作人
*/
private String userName;
/**
* 日志描述
*/
private String logDescribe;
/**
* 操作类型
*/
private String operationType;
/**
* 方法运行时间
*/
private Long runTime;
/**
* 方法返回值
*/
private String returnValue;
/**
* 日志来源
*/
private String source;
private String result;
private String errorInfo;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public Date getCreateTime() {
return createTime;
}
public void setCreateTime(Date createTime) {
this.createTime = createTime;
}
public String getIp() {
return ip;
}
public void setIp(String ip) {
this.ip = ip;
}
public Integer getLogLevel() {
return logLevel;
}
public void setLogLevel(Integer logLevel) {
this.logLevel = logLevel;
}
public String getOperationUnit() {
return operationUnit;
}
public void setOperationUnit(String operationUnit) {
this.operationUnit = operationUnit;
}
public String getMethod() {
return method;
}
public void setMethod(String method) {
this.method = method;
}
public String getArgs() {
return args;
}
public void setArgs(String args) {
this.args = args;
}
public String getUserId() {
return userId;
}
public void setUserId(String userId) {
this.userId = userId;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getLogDescribe() {
return logDescribe;
}
public void setLogDescribe(String logDescribe) {
this.logDescribe = logDescribe;
}
public String getOperationType() {
return operationType;
}
public void setOperationType(String operationType) {
this.operationType = operationType;
}
public Long getRunTime() {
return runTime;
}
public void setRunTime(Long runTime) {
this.runTime = runTime;
}
public String getReturnValue() {
return returnValue;
}
public void setReturnValue(String returnValue) {
this.returnValue = returnValue;
}
public String getSource() {
return source;
}
public void setSource(String source) {
this.source = source;
}
public String getResult() {
return result;
}
public void setResult(String result) {
this.result = result;
}
public String getErrorInfo() {
return errorInfo;
}
public void setErrorInfo(String errorInfo) {
this.errorInfo = errorInfo;
}
@Override
public String toString() {
return "OperationLog [id=" + id + ", createTime=" + createTime + ", ip=" + ip + ", logLevel=" + logLevel
+ ", operationUnit=" + operationUnit + ", method=" + method + ", args=" + args + ", userId=" + userId
+ ", userName=" + userName + ", logDescribe=" + logDescribe + ", operationType=" + operationType
+ ", runTime=" + runTime + ", returnValue=" + returnValue + ", source=" + source + ", result=" + result
+ ", errorInfo=" + errorInfo + "]";
}
}
处理日志信息的入库操作,及列表查询详细查询方法。
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.domain.Sort.Direction;
import org.springframework.data.domain.Sort.Order;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import com.xxxx.model.OperationLog;
public class SystemLogService {
@Autowired
private MongoTemplate mongoTemplate;
/**
* 添加日志
* @param operationLog
* @throws Exception
*/
public void addLogInfo(OperationLog operationLog) throws Exception {
mongoTemplate.save(operationLog);// 添加日志记录
}
/**
* 分页查询日志
* @param param
* @return
* @throws Exception
*/
public Map<String, Object> getOperationLogByMap(Map<String, Object> param) throws Exception {
Map<String, Object> result = new HashMap<String, Object>();
Criteria criteria = new Criteria();
if(param.get("key") != null && !"".equals(param.get("key"))) {
criteria.and("userId").in(param.get("key")).orOperator(criteria.and("userName").in(param.get("key")));
}
if(param.get("opType") != null && !"".equals(param.get("opType"))) {
criteria.and("operationType").is(param.get("opType"));
}
if(param.get("source") != null && !"".equals(param.get("source"))) {
criteria.and("source").is(param.get("source"));
}
Query query = new Query(criteria);
long count = mongoTemplate.count(query, OperationLog.class);
List<Order> orders = new ArrayList<Order>(); //排序
if(!"".equals(param.get("sortField"))) {
if("desc".equals(param.get("sortOrder").toString().toLowerCase())) {
orders.add(new Order(Direction.DESC, param.get("sortField").toString()));
} else {
orders.add(new Order(Direction.ASC, param.get("sortField").toString()));
}
}
orders.add(new Order(Direction.DESC, "create_time"));
Sort sort = Sort.by(orders);
Object pageIndex = param.get("pageIndex");
Integer pageStart = 0;
Integer pageSize = 20;
if(pageIndex != null) {
pageStart = Integer.valueOf(pageIndex.toString());
pageSize = Integer.valueOf(param.get("pageSize") == null ? "20" : param.get("pageSize").toString());
}
Pageable pageable = PageRequest.of(pageStart, pageSize, sort);
List<OperationLog> list = mongoTemplate.find(query.with(pageable), OperationLog.class);
result.put("total", count);
result.put("data", list);
return result;
}
/**
* 根据日志id查询日志详情
* @param id
* @return
* @throws Exception
*/
public OperationLog getOperationLogById(String id) throws Exception {
if(null != id && !"".equals(id)) {
return mongoTemplate.findById(id, OperationLog.class);
}
return null;
}
}
import java.util.List;
import com.alibaba.fastjson.JSONObject;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.ObjectMapper;
public class JsonUtils {
// 定义jackson对象
private static final ObjectMapper MAPPER = new ObjectMapper();
/**
* 将对象转换成json字符串。
* Title: pojoToJson
* Description:
* @param data
* @return
*/
public static String objectToJsonStr(Object data) {
try {
String string = MAPPER.writeValueAsString(data);
return string;
} catch (JsonProcessingException e) {
e.printStackTrace();
}
return null;
}
public static JSONObject objectToJson(Object data) {
String jsonStr = objectToJsonStr(data);
JSONObject jsonObject = JSONObject.parseObject(jsonStr);
return jsonObject;
}
/**
* 将json结果集转化为对象
*
* @param jsonData json数据
* @param clazz 对象中的object类型
* @return
*/
public static <T> T jsonToPojo(String jsonData, Class<T> beanType) {
try {
T t = MAPPER.readValue(jsonData, beanType);
return t;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
* 将json数据转换成pojo对象list
* Title: jsonToList
* Description:
* @param jsonData
* @param beanType
* @return
*/
public static <T>List<T> jsonToList(String jsonData, Class<T> beanType) {
JavaType javaType = MAPPER.getTypeFactory().constructParametricType(List.class, beanType);
try {
List<T> list = MAPPER.readValue(jsonData, javaType);
return list;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
import java.util.UUID;
public class StringUtil {
public static String getUUID(){
return UUID.randomUUID().toString().replaceAll("-", "");
}
}