AOP切面实现用户的操作记录

背景:网站后台管理用户登录操作,记录登录的管理员,对哪些菜单功能进行了什么操作以及操作前操作后的数据比较。

springmvc-servlet.xml:


1、创建自定义注解:

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;

/**
 * 自定义注解 -- 日志需要
 * @author lv。
 *
 * 2017年10月31日 上午10:46:46
 */
@Target({ElementType.PARAMETER, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface SysLogAnnotation {

	/**
	 * 需要反射创建的类
	 * @author lv。
	 * @return
	 */
	@SuppressWarnings("rawtypes")
	Class targetClass() default Object.class; 
	
	/**
	 * 反射创建的类需要的属性名
	 * @author lv。
	 * @return
	 */
	String fieldsInfo() default "";
	
	/**
	 * 描述,此处为业务信息
	 * @author lv。
	 * @return
	 */
	String description() default "";
	
	/**
	 * 日志的事项类型
	 * @return
	 */
	String eventType() default "" ;
	
}

2、定义切点类:

import java.beans.IntrospectionException;
import java.beans.PropertyDescriptor;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.InetAddress;
import java.net.URL;
import java.net.URLConnection;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import org.apache.commons.lang3.tuple.ImmutablePair;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.subject.Subject;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
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.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 cn.com.ceshi.app.center.entity.SysLog;
import cn.com.ceshi.app.center.entity.SysUser;
import cn.com.ceshi.app.center.service.SysDeptService;
import cn.com.ceshi.app.center.service.SysLogService;
import cn.com.ceshi.app.center.service.SysUserService;
import cn.com.ceshi.app.common.enums.common.ResultCodeEnum;
import cn.com.ceshi.app.util.DateUtils;

/**
 * 切点类
 * @author lv。
 *
 * 2017年10月31日 上午10:52:18
 */
@Aspect
@Component
public class SystemLogAspect {
	
	@Autowired
	private SysUserService sysUserService;
	
	@Autowired
	private SysDeptService sysDeptService;
	
	@Autowired
	private SysLogService sysLogService ;

	/**
	 * controller层的切点
	 * @author lv。
	 */
	@Pointcut("@annotation(cn.com.zhulong.app.aspect.SysLogAnnotation)")
	public void logAspect() {
	}
	
	/**
	 * 返回后通知
	 * @author lv。
	 * @throws IntrospectionException 
	 * @throws IllegalAccessException 
	 * @throws InstantiationException 
	 * @throws InvocationTargetException 
	 * @throws IllegalArgumentException 
	 */
	@AfterReturning(value = "logAspect()", returning = "result")
	public void doAfferReturning(JoinPoint joinPoint, Object result) throws IntrospectionException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
		
		// --- >> 入口处判断方法的返回值,如果执行后的状态码为-1,不再保存日志信息
		if (result != null) {
			
			// 主要针对执行保存、删除和更新返回的map
			if (result instanceof Map) {
				
				Map resultMap = (Map) result;
				
				// 从map中取出状态码
				if (ResultCodeEnum.FAIL.code.equals(resultMap.get("code"))) {
					return;
				}
				
			}
			
		}
		
		//获取HttpServletRequest对象,更新操作需要原始数据
		HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
		
		//获取切入点所在类
		Class pointClass = joinPoint.getTarget().getClass() ;
		//获取切入点所在的方法名
		MethodSignature signature = (MethodSignature) joinPoint.getSignature();
		Method pointMethod = signature.getMethod();
		String methodName = pointMethod.getName() ;
		//获取注解内容(有4个参数,类,属性,业务描述,事项类型)
		SysLogAnnotation sysLogAnnotation = pointMethod.getAnnotation(SysLogAnnotation.class);
		//注解参数1,类(后面的属性与该类对应)
		Class annoClass = sysLogAnnotation.targetClass() ;
		//注解参数2,属性(多个属性,且有属性名和描述)
		String allAttr = sysLogAnnotation.fieldsInfo() ;
		//注解参数3,业务描述
		String description = sysLogAnnotation.description() ;
		//注解参数4,事项类型
		String eventType0 = sysLogAnnotation.eventType() ;
		
		System.out.println(" --- >> 切入点所在的类为:" + pointClass.getName());
		System.out.println(" --- >> 切入点所在的方法为:" + pointMethod.getName());
		System.out.println(" --- >> 自定义注解中的需要反射创建的类为:" + annoClass.getName());
		System.out.println(" --- >> 自定义注解中的反射创建的类需要的属性名为:" + allAttr);
		System.out.println(" --- >> 自定义注解中的业务信息为:" + description);
		System.out.println(" --- >> 自定义注解中的事项类型为:" + eventType0);
		
		//统一配置key为bean,类与注解参数1相同
		Object attrUpdate = request.getAttribute("bean");
		//如果attrUpdate不是空,说明就是更新操作,因为有可能用save方法,所以强制方法名变为update,后面判断用
		if (attrUpdate != null) {
			methodName = "update";
		}		
		
		//获取切入点所在的方法的入参对象,主要想要得到与注解参数1相同的类的参数。
		Object[] paramIn = joinPoint.getArgs();
		//需要的class类型的数据,其实就是注解参数1的class类型的数据,需要验证入参中是否有
		Object needParam = null ;
		//下面if就是为了给needParam赋值
		if (paramIn != null && paramIn.length > 0) {
			int length = paramIn.length;
			for (int i = 0; i < length; i++) {
				System.out.println(" --- >> 传入的第"+(i+1)+"个参数值为:" + paramIn[i].toString()+",所在类为:"+paramIn[i].getClass().getName());
				//传入参数的类名与注解参数1的类名进行对比
				if (paramIn[i].getClass().getName().equals(sysLogAnnotation.targetClass().getName())){
					//为needParam赋值
					needParam = paramIn[i];
					System.out.println(" --- >> 传入的第"+(i+1)+"个参数与注解参数1匹配,所在类为:"+paramIn[i].getClass().getName());
					break;
				}
			}
		}
		
		//存储注解参数2相关内容,key=参数名,value=值,description=描述
		List<Map<String,Object>> needAttr = new ArrayList<Map<String,Object>>() ;
		//说明入参对象中没有与注解参数1是相同类的
		if (needParam == null) {
			//把bean0的值给needParam,正常不用配bean0,只有切点方法中的入参没有与注解参数1相同类型的时候
			needParam = request.getAttribute("bean0");
		}
		
		if (needParam == null) {
			
			// 此处为了添加、查看和更新页面共用时使用
			// ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓
			//把bean0的值给needParam,正常不用配bean0,只有切点方法中的入参没有与注解参数1相同类型的时候
			Object attrView = request.getAttribute("viewBean");
			if (attrView != null) {
				needParam = attrView;
				methodName = "view";
			}
		}
		
		if(needParam != null){
			//根据注解参数2,将多个属性保存成数组(每个元素有属性名和描述)
			String[] fields = allAttr.split("\\|");
			if (fields != null && fields.length > 0) {
				int length = fields.length;
				for (int i = 0; i < length; i++) {
					String[] attr = fields[i].split("-");
					System.out.println("--- >> 自定义注解里的属性名第 " + (i + 1) + " 个为:" + attr[0]+",描述为:"+attr[1]);
					
					PropertyDescriptor pd =  new PropertyDescriptor(attr[0], needParam.getClass());
					Method readMethod = pd.getReadMethod();
					//获取有用的传入参数(与注解参数1在相同的类)有用属性的值(注解参数2配置的属性)
					Object value = readMethod.invoke(needParam);
					
					Map<String,Object> map = new HashMap<String,Object>() ;
					map.put("value", value);
					map.put("key", attr[0]);
					map.put("note", attr[1]);
					needAttr.add(map);
				}
			}
			
		} else {
			System.out.println("没有发现要操作的类,不记录日志");
			return;
		}
		
		System.out.println("--- >> 关注的参数,值,描述 " +needAttr);
		
		
		//====保存到日志表的数据
		//获取事项类型
		String eventType = eventType0;
		//获取日志内容
		String logContent = "";
		//获取操作人
		String operator = getCurrentUserName();
		//获取所属部门
		String department = "";
		String operatorGuid = getCurrentUserGuid();
		SysUser operatorUser = sysUserService.getUser(operatorGuid);
		if (operatorUser != null) {
			department = operatorUser.getDeptGuid();
		}
		
		//获取操作时间
		String operateTime = DateUtils.msecFormatDateStr(System.currentTimeMillis(),"yyyyMMddHHmmss") ;
		//操作ip
		String operateIp  = "";  
		//登录地区
		String loginArea = "";  

		try {
			/*Map<String,String> operationInfo = this.operationInfo(request) ;
			operateIp = operationInfo.get("operateIp") ;
			loginArea = operationInfo.get("loginArea") ;*/
			operateIp = this.getIpAddress(request);
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

		//如果是更新操作,增加,删除,查看(暂时 增,删,看 是一样的)
		if(methodName.toLowerCase().contains("update")){
			StringBuffer content = new StringBuffer();
			content.append("“修改"+description+"”") ;
			if(attrUpdate != null){
				for (Map<String, Object> map : needAttr) {
					PropertyDescriptor pd =  new PropertyDescriptor(map.get("key").toString(), attrUpdate.getClass());
					Method readMethod = pd.getReadMethod();
					Object value = readMethod.invoke(attrUpdate);
					if(value != null){
						if(!value.toString().equals(map.get("value").toString())){
							content.append(",“"+map.get("note")+"”由“"+value.toString()+"”变为“"+map.get("value").toString()+"”") ;
						}
					}
					
				}
			}
			logContent = content.toString() ;
		} else if (methodName.toLowerCase().contains("add") || pointMethod.getName().toLowerCase().contains("save") ){
			StringBuffer content = new StringBuffer();
			content.append("“添加"+description+"”") ;
			for (Map<String, Object> map : needAttr) {
				content.append(",“"+map.get("note")+"”为“"+map.get("value")+"”") ;
			}
			logContent = content.toString() ;
		} else if (methodName.toLowerCase().contains("del")){
			StringBuffer content = new StringBuffer();
			content.append("“删除"+description+"”") ;
			for (Map<String, Object> map : needAttr) {
				content.append(",“"+map.get("note")+"”为“"+map.get("value")+"”") ;
			}
			logContent = content.toString() ;
		} else if (methodName.toLowerCase().contains("view")){
			StringBuffer content = new StringBuffer();
			content.append("“查看"+description+"”") ;
			for (Map<String, Object> map : needAttr) {
				content.append(",“"+map.get("note")+"”为“"+map.get("value")+"”") ;
			}
			logContent = content.toString() ;
		}
		SysLog log = new SysLog() ;
		log.setEventType(eventType);
		log.setLogContent(logContent);
		log.setDepartment(department);
		log.setOperator(operator);
		log.setOperateTime(operateTime);
		log.setOperateIp(operateIp);
		log.setLoginArea(loginArea);
		System.out.println("需要保存到日志表的数据,事项类型:"+eventType);
		System.out.println("需要保存到日志表的数据,日志内容:"+logContent);
		System.out.println("需要保存到日志表的数据,操作人:"+operator);
		System.out.println("需要保存到日志表的数据,操作部门:"+department);
		System.out.println("需要保存到日志表的数据,操作时间:"+operateTime);
		System.out.println("需要保存到日志表的数据,操作ip:"+operateIp);
		System.out.println("需要保存到日志表的数据,登录地区:"+loginArea);
		System.out.println(log);
		sysLogService.saveSysLog(log) ;

		
	}

	
	
	/**
	 * 获取当前登陆用户的guid
	 * @return 当前登陆用户的guid
	 */
	@SuppressWarnings({ "rawtypes", "unchecked" })
	protected String getCurrentUserGuid() {
		Subject subject = SecurityUtils.getSubject();

		if (subject.isAuthenticated() == false) {
			return null;
		}

		List list = subject.getPrincipals().asList();
		ImmutablePair<String, String> pair = getUsernameGuid(list);
		return pair.getRight();
	}
	
	/**
	 * 获取当前登陆用户的用户名
	 * @return 当前登陆用户的用户名(登录名)
	 */
	protected String getCurrentUserName() {
		Subject subject = SecurityUtils.getSubject();
		if (subject.isAuthenticated() == false) {
			return null;
		}
		List<?> list = subject.getPrincipals().asList();
		ImmutablePair<String, String> pair = getUsernameGuid(list);
		return pair.getLeft();

	}
	private ImmutablePair<String, String> getUsernameGuid(List list) {
		String username = null;
		String guid = null;
		if (list != null && list.size() > 0) {
			if (list.get(0) instanceof String) {
				username = list.get(0).toString();
				guid = list.get(1).toString();
			} else if (list.get(0) instanceof Map) {
				Map<String, Object> info = (Map<String, Object>) list.get(1);
				username = info.get("userName").toString();
				guid = info.get("guid").toString();
			}
		}
		return new ImmutablePair<String, String>(username, guid);
	}
	
	/** 
     * 获取请求主机IP地址,如果通过代理进来,则透过防火墙获取真实IP地址; 
     *  
     * @param request 
     * @return 
     * @throws IOException 
     */  
    public final static String getIpAddress(HttpServletRequest request) throws IOException {
    	
    	String ipAddress = request.getHeader("x-forwarded-for");
    	
        if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
            ipAddress = request.getHeader("Proxy-Client-IP");
        }
        
        if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
            ipAddress = request.getHeader("WL-Proxy-Client-IP");
        }
        
        if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
            ipAddress = request.getRemoteAddr();
            if (ipAddress.equals("127.0.0.1") || ipAddress.equals("0:0:0:0:0:0:0:1")) {
                //根据网卡取本机配置的IP
                InetAddress inet=null;
                try {
                    inet = InetAddress.getLocalHost();
                } catch (UnknownHostException e) {
                    e.printStackTrace();
                }
                ipAddress= inet.getHostAddress();
            }
        }
        
        //对于通过多个代理的情况,第一个IP为客户端真实IP,多个IP按照','分割 
        if (ipAddress != null && ipAddress.length() > 15) { //"***.***.***.***".length() = 15
            if (ipAddress.indexOf(",") > 0) {
                ipAddress = ipAddress.substring(0,ipAddress.indexOf(","));
            }
        }
        
        return ipAddress; 
    }
    
    
    public final static Map<String,String> operationInfo(HttpServletRequest request) throws IOException {
    	
    	// 正式环境
		// String waiwangIp = this.getRequestIp(request);

		// 测试环境
		String waiwangIp = request.getHeader("x-forwarded-for");
		if (waiwangIp == null || waiwangIp.length() == 0 || "unknown".equalsIgnoreCase(waiwangIp)) {
			waiwangIp = request.getHeader("Proxy-Client-IP");
		}
		if (waiwangIp == null || waiwangIp.length() == 0 || "unknown".equalsIgnoreCase(waiwangIp)) {
			waiwangIp = request.getHeader("WL-Proxy-Client-IP");
		}
		if (waiwangIp == null || waiwangIp.length() == 0 || "unknown".equalsIgnoreCase(waiwangIp)) {
			waiwangIp = request.getRemoteAddr();
		}

		URL url = new URL("http://ip.taobao.com/service/getIpInfo.php?ip=" + waiwangIp);
		URLConnection conn = url.openConnection();
		InputStream inputStream = conn.getInputStream();
		int len;
		byte[] bu = new byte[1024];
		StringBuffer buffer = new StringBuffer();
		while ((len = inputStream.read(bu)) > 0) {
			buffer.append(new String(bu, 0, len));
		}
		// 拿到JsonObj对象
		JSONObject parseObject = JSON.parseObject(buffer.toString());
		System.out.println(" --- >> " + parseObject);

		// 从JsonObj对象里面根据key值获取到json格式的字符串
		String dataJsonStr = parseObject.getString("data");

		// 根据json字符串判断出数据类型,进行转换成对应的对象
		Map parseMap = JSON.parseObject(dataJsonStr, Map.class);
		System.out.println(" --- >> 转化后的Map为:" + parseMap);

		String areaString = (String) parseMap.get("region");
		System.out.println(areaString);
		
		Map<String,String> map = new HashMap<String, String>() ;
		map.put("operateIp", waiwangIp) ;
		map.put("loginArea", areaString) ;
		return  map  ;
    }
    
    /**
	 * 获取客户端ip
	 * 
	 * @param operationType
	 * @param description
	 */
	public static String getRequestIp(HttpServletRequest request) {
		String ip = request.getHeader("x-forwarded-for");
		if (ip == null || ip.length() == 0) {
			ip = request.getRemoteAddr();
		} else {
			ip = ip.split(",")[0];
		}
		return ip;
	}
    
}

3、controller实现

// 修改行政区划
    @RequestMapping("updateArea")
    @ResponseBody
    @SysLogAnnotation(targetClass = DictArea.class,
    fieldsInfo = "areaName-地区名称|areaCode-地区编号", //设置需要记录的操作字段,多个以“|”分开
    description = DictArea.className, //日志记录所需对象的中文名
    eventType = EntityType.JiChuShuJu)//日志记录所需的分类(基础数据)
    public Map<String, Object> updateArea(@RequestBody DictArea area, HttpServletRequest request) {
        System.out.println(area);

        // 用于保存日志
        String guid = area.getGuid();
        if (StringUtils.isNotBlank(guid)) {
            DictArea bean = dictAreaService.findById(guid);
            request.setAttribute(LogEntityType.classBean.getDescription(), bean);
        }

        area.setIsDelete(0);
        area.setModifier(getCurrentUserGuid());
        area.setModifyTime(DateUtils.msecFormatDateStr(System.currentTimeMillis(), "yyyyMMddHHmmss"));
        Map<String, Object> map = new HashMap<String, Object>();
        try {
            // 修改一条地区,可以有返回值
            DictArea newArea = dictAreaService.updateArea(area);
            map.put("code", ResultCodeEnum.SUCCESS.code);
            map.put("message", ResultCodeEnum.SUCCESS.message);
        } catch (Exception e) {
            e.printStackTrace();
            map.put("code", ResultCodeEnum.FAIL.code);
            map.put("message", ResultCodeEnum.FAIL.message);
        }
        return map;
    }

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值