背景:网站后台管理用户登录操作,记录登录的管理员,对哪些菜单功能进行了什么操作以及操作前操作后的数据比较。
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;
}