java 项目日志管理设计方案

java 项目日志管理设计方案

因项目需要记录整个系统的操作记录,考虑到系统操作日志的数据量,单表很容易达到瓶颈,导致查询效率低下,顾使用分表方案,减小数据库的负担,缩短查询时间。目前对于分表的解决方案有很多,本博文主要讲解博主自行实现的日志管理的解决方案,如有遗漏或错误的请各位大佬多多包涵
鉴于总是有人私信要demo,这里将以前搭的一个简易的项目贴出来:https://gitee.com/jiangliuhong/syslog.git


1 创建日志表

1.1 日志表Sql语句如下

具体表设计随项目情况而变化

表创建SQL语句

CREATE TABLE `sys_user_log` (
  `log_id` varchar(32) NOT NULL COMMENT '日志表id,uuid',
  `user_id` varchar(32) DEFAULT NULL COMMENT '用户id,记录操作用户',
  `module_name` varchar(225) NOT NULL COMMENT '模块名称',
  `operate` varchar(225) NOT NULL COMMENT '操作名称',
  `time` datetime NOT NULL COMMENT '操作时间',
  `class_name` varchar(225) NOT NULL COMMENT '类名称',
  `method_name` varchar(225) NOT NULL COMMENT '方法名称',
  `params` longtext COMMENT '传入参数',
  `ip` varchar(225) NOT NULL COMMENT '操作ip',
  PRIMARY KEY (`log_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

1.2 日志表创建

因考虑到项目情况,顾为每月创建一个日志表

1.2.1 表创建方法

创建service、dao方法

DbMapper.xml




    
    	create table #{name,jdbcType=VARCHAR} like sys_user_log
    


DbMapper.java

public interface SysDbMapper {
    /**
     *  创建数据表
     * @param name 表名
     */
    void createUserLogTable(String name);
}

DbService.java

public interface SysDbService {
    /**
     * 创建数据表
     *
     * @param preName   名称前缀
     * @param isSufTime 是否显示 时间后缀
     */
    void createTable(String preName, Boolean isSufTime);
}

DbServiceImpl.java


@Service("dbService")
public class DbServiceimpl implements DbService {

    @Autowired
    private DbMapper dbMapper;

    @Override
    public void createTable(String preName, Boolean sufTime) {
        if (sufTime) {
            dbMapper.createTable(preName + "_" + getSufName());
        } else {
            dbMapper.createTable(preName);
        }
    }
    
    /**
     * 获取名称后缀
* 格式: 20170301 * * @return */ private String getSufName() { Date d = new Date(); String s = DateUtil.date2String(d, false); return s.replace("-", ""); } }

1.2.2 创建定时任务(每月第一天凌晨执行)

BaseQuartz.java


/**
 * 报警定时任务接口
 * 
 */
public interface BaseQuartz {

	/**
	 * 定时任务执行方法
	 */
	public void work();
}

DbQuartz.java


public class DbQuartz implements BaseQuartz {
    
    private static final Logger log = LoggerFactory.getLogger(DbQuartz.class);
    /**
     * 默认表名
     */
    private static String tableName = "sys_user_log";

    @Override
    public void work() {
        try {
            //创建表
            DbService dbService = (SysDbService) SpringUtil.getBean("sysDbService");
            dbService.createTable(tableName, true);
            log.info("创建表" + tableName + "成功");
            //更新系统用户日志表表缓存
            SysCacheUtil.flushSysUserLogTableName();
        } catch (Exception e) {
            log.error("创建表" + tableName + "失败");
            work();
        }
    }
    
}

quartz配置文件:spring-quartz.xml




     
     
	
	
	
		
			
		
		
			work
		
	
	
	
		
			
		
		
			
			0 0 0 1 * ?
		
	
	
	
	
		
			
				
			
		
	
	
  

到止为止日志表自动创建逻辑完成

2 日志表数据插入查询

因考虑查询速度,采用根据传入的时间进行联合查询,规定只能查询连续3个月的日志数据,即前台传入 20170301,20170525 则联合表sys_user_log20170301,sys_user_log20170401,sys_user_log20170501三表进行联合查询。

2.1 日志相关类源代码

主要代码包括,日志实体类,日志查询类,日志表相关的dao、service 类

SysUserLog.java


import java.io.Serializable;
import java.util.Date;

import com.fasterxml.jackson.annotation.JsonFormat;

/**
 * 系统用户日志
 */
public class SysUserLog implements Serializable {

	/**
	 * 
	 */
	private static final long serialVersionUID = 1L;

	/**日志id*/
	private String logId;
	/**用户id*/
	private String userId;
	/**模块名称*/
	private String moduleName;
	/**操作*/
	private String operate;
	/**时间*/
	private Date time;
	/**类名*/
	private String className;
	/**方法名*/
	private String methodName;
	/**传入参数*/
	private String params;
	/**操作ip*/
	private String ip;
	
	public String getLogId() {
		return logId;
	}
	public void setLogId(String logId) {
		this.logId = logId;
	}
	public String getUserId() {
		return userId;
	}
	public void setUserId(String userId) {
		this.userId = userId;
	}
	public String getModuleName() {
		return moduleName;
	}
	public void setModuleName(String moduleName) {
		this.moduleName = moduleName;
	}
	public String getOperate() {
		return operate;
	}
	public void setOperate(String operate) {
		this.operate = operate;
	}
	
	@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss",timezone="GMT+8")
	public Date getTime() {
		return time;
	}
	public void setTime(Date time) {
		this.time = time;
	}
	
	public String getClassName() {
		return className;
	}
	public void setClassName(String className) {
		this.className = className;
	}
	public String getMethodName() {
		return methodName;
	}
	public void setMethodName(String methodName) {
		this.methodName = methodName;
	}
	public String getParams() {
		return params;
	}
	public void setParams(String params) {
		this.params = params;
	}
	public String getIp() {
		return ip;
	}
	public void setIp(String ip) {
		this.ip = ip;
	}
	//扩展字段
	/**用户账号*/
	private String username;
	/**组织名称*/
	private String organizationName;

	public String getUsername() {
		return username;
	}
	public void setUsername(String username) {
		this.username = username;
	}
	public String getOrganizationName() {
		return organizationName;
	}
	public void setOrganizationName(String organizationName) {
		this.organizationName = organizationName;
	}
	

SysUserLogMapper.xml




  
    
    
    
    
    
    
    
    
    
  
  
  	
    
  
  
  	log_id, user_id, module_name, operate, time, class_name, method_name, params, ip
  
  
  	insert into ${tableName}
  	(log_id, user_id, module_name, operate, time, class_name, method_name, params, ip)
  	values
  	(
  	#{log.logId,jdbcType=VARCHAR},
  	#{log.userId,jdbcType=VARCHAR},
  	#{log.moduleName,jdbcType=VARCHAR},
  	#{log.operate,jdbcType=VARCHAR},
  	#{log.time,jdbcType=TIMESTAMP},
  	#{log.className,jdbcType=VARCHAR},
  	#{log.methodName,jdbcType=VARCHAR},
  	#{log.params,jdbcType=VARCHAR},
  	#{log.ip,jdbcType=VARCHAR}
  	)
  
  
  	insert into sys_user_log 
  	(log_id, user_id, module_name, operate, time, class_name, method_name, params, ip)
  	values
  	(
  	#{logId,jdbcType=VARCHAR},
  	#{userId,jdbcType=VARCHAR},
  	#{moduleName,jdbcType=VARCHAR},
  	#{operate,jdbcType=VARCHAR},
  	#{time,jdbcType=TIMESTAMP},
  	#{className,jdbcType=VARCHAR},
  	#{methodName,jdbcType=VARCHAR},
  	#{params,jdbcType=VARCHAR},
  	#{ip,jdbcType=VARCHAR}
  	)
  
  
   
  


SysUserLogMapper.java

public interface SysUserLogMapper {

	/**
	 * 新增日志记录
	 * @param sysUserLog
	 * @return
	 */
	int insert(SysUserLog sysUserLog);
	/**
	 * 添加日志到指定数据库
	 * @param tableName
	 * @param sysUserLog
	 * @return
	 */
	int insertToTable(@Param("tableName") String tableName,@Param("log") SysUserLog sysUserLog);
	/**
	 * 查询所有
	 * @return
	 */
	List selectAll(SysUserLogQuery query);
	
	
	List selectAllByTables(@Param("table") String table,@Param("query") SysUserLogQuery query);
}

SysUserLogService.java

PageInfo 为Mybatis分页插件“PageHelp”中的分页辅助类

public interface SysUserLogService {

    /**
     * 添加系统用户日志
     *
     * @param sysUserLog 日志信息
     */
    void addUserLog(SysUserLog sysUserLog);

    /**
     * 分页获取日志列表
     *
     * @param query  查询参数
     * @param start  页数
     * @param length 每页个数
     * @return
     */
    PageInfo queryPage(SysUserLogQuery query, Integer start, Integer length) throws Exception;

SysUserLogServiceImpl.java

SysCacheUtil:项目中集成了EhCahe缓存,而后根据项目的缓存规则封装的缓存工具类。在该日志查询、存储方案中将根据数据库中的日志表进行操作,顾将日志数据表名存入缓存。查询系统数据库中日志数据表表名的sql语句如下:
SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE=‘BASE TABLE’ AND TABLE_SCHEMA = ‘数据库名称’ AND TABLE_NAME LIKE CONCAT (‘sys_user_log’,’%’);


/**
 * 用户日志管理业务实现
 *
 * @author jiangliuhong
 * @CREATEDATE 2017年2月13日
 */
@Service("sysUserLogService")
public class SysUserLogServiceImpl implements SysUserLogService {

    @Autowired
    private SysUserLogMapper userLogMapper;
    @Autowired
    private DbService dbService;

    /**
     * sys_user_log
     */
    private static String preTableName = "sys_user_log";

    @Override
    public void addUserLog(SysUserLog sysUserLog) {
        try {
            userLogMapper.insertToTable(getUserLogTableName(), sysUserLog);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @Override
    public PageInfo queryPage(SysUserLogQuery query, Integer start, Integer length) throws Exception {
        // 判断开始结束日期是否为空
        if (query.getStartTime() == null || query.getEndTime() == null) {
            throw new CustomerException("开始、结束时间不能为空");
        }
		// 从缓存中读取系统数据库中的日志表表名
        List userLogTableName = (List) SysCacheUtil.getSysUserLogTableName();
        // 检查系统用户日志表名缓存是否存在,不存在则查询
        if (userLogTableName == null) {
            userLogTableName = dbService.queryUserLogTableName();
        }
        String logTable = "";
        if (userLogTableName == null ? true : userLogTableName.size() > 0) {
            // 标记是否比较开始时间
            Boolean isStart = true;
            for (int i = 0; i < userLogTableName.size(); i++) {
                if (isStart) {
                    // 如果表创建时间与开始时间时间同年同月 则使循环体往下执行
                    if (DateUtil.equals(changeToDate(userLogTableName.get(i)), query.getStartTime())
                            || DateUtil.countSecond(changeToDate(userLogTableName.get(i)), query.getStartTime()) >= 0) {
                        logTable = " ( select * from " + preTableName;
                        isStart = false;
                    } else {
                        continue;
                    }
                }
                logTable = logTable + " union all " + " select * from " + userLogTableName.get(i);
                // 如果表创建时间与结束时间同年同月 则跳出循环
                if (DateUtil.equals(changeToDate(userLogTableName.get(i)), query.getEndTime())) {
                    break;
                }
            }
            if (!isStart) {
                logTable = logTable + " ) ";
            } else {
                logTable = preTableName;
            }
        } else {
            logTable = preTableName;
        }
        // 分页计算
        start = start / length + 1;
        PageHelper.startPage(start, length);
        // List userLogs = userLogMapper.selectAll(query);
        List userLogs = userLogMapper.selectAllByTables(logTable, query);
        PageInfo pageInfo = new PageInfo(userLogs);
        return pageInfo;
    }

    /**
     * 获取最新的日志表表名
     *
     * @return
     */
    private String getUserLogTableName() {
        //取用户日志表名集合
        List userLogTableName = SysCacheUtil.getSysUserLogTableName();
        // 判断最新表名是否与该月同月,不同月则创建该月日志表
        if (userLogTableName == null ? true : userLogTableName.size() <= 0) {
            dbService.createTable("sys_user_log", true);
            userLogTableName = SysCacheUtil.flushSysUserLogTableName();
        } else {
            // userLogTableName.size() > 0
            String tableName = userLogTableName.get(userLogTableName.size() - 1);
            if (!DateUtil.equals(changeToDate(tableName), new Date(System.currentTimeMillis()))) {
                // 不同月,则创建该月表,并更新日志缓存
                dbService.createTable("sys_user_log", true);
                userLogTableName = SysCacheUtil.flushSysUserLogTableName();
            }
        }
        if (userLogTableName == null ? true : userLogTableName.size() <= 0) {
            return preTableName;
        }

        // 更新缓存
        SysCacheUtil.setSysUserLogTableName(userLogTableName);
        return userLogTableName.get(userLogTableName.size() - 1);
    }

    /**
     * 获取名称后缀
* 格式: 20170301 * * @return */ @SuppressWarnings("unused") private String getSufName() { Date d = new Date(); String s = DateUtil.date2String(d, false); s = s.replace("-", ""); s = s.substring(0, s.length() - 2); s = s + "01"; return s; } /** * 计算日志表时间 * * @param tabelName 表名 * @return */ private Date changeToDate(String tabelName) { int lastIndexOf = tabelName.lastIndexOf('_'); if (lastIndexOf >= 0) { tabelName = tabelName.substring(lastIndexOf + 1); String strDate = tabelName.substring(0, 4) + "-" + tabelName.substring(4, 6) + "-" + tabelName.substring(6, 8); return DateUtil.string2Date(strDate); } else { return null; } } }

SysUserLogQuery.java

该类为日志表辅助查询类,具体查询条件根据项目实际情况而定

import java.util.Date;
import java.util.List;

/**
 * 日志查询类
 */
public class SysUserLogQuery {

	/**组织id*/
	private String organizationId;
	/**组织集合*/
	private List organizationIds;
	/**开始时间*/
	private Date startTime;
	/**结束时间*/
	private Date endTime;
	/**用户名*/
	private String username;
	/**模块名称*/
	private String moduleName;
	public String getOrganizationId() {
		return organizationId;
	}
	public void setOrganizationId(String organizationId) {
		this.organizationId = organizationId;
	}
	public List getOrganizationIds() {
		return organizationIds;
	}
	public void setOrganizationIds(List organizationIds) {
		this.organizationIds = organizationIds;
	}
	public Date getStartTime() {
		return startTime;
	}
	public void setStartTime(Date startTime) {
		this.startTime = startTime;
	}
	public Date getEndTime() {
		return endTime;
	}
	public void setEndTime(Date endTime) {
		this.endTime = endTime;
	}
	public String getUsername() {
		return username;
	}
	public void setUsername(String username) {
		this.username = username;
	}
	public String getModuleName() {
		return moduleName;
	}
	public void setModuleName(String moduleName) {
		this.moduleName = moduleName;
	}
	
}

DateUtil.java

该类为自行封装的时间处理工具类,代码如下:

import java.sql.Timestamp;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;

public class DateUtil {

    public static Date string2Date(String time) {
        try {
            Date date = new Date();
            DateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            if (time.length() <= 10) {
                time = time + " 00:00:00";
            }
            date = sdf.parse(time);
            return date;
        } catch (ParseException e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * @param date 时间对象
     * @param hms  是否显示时分秒
     * @return
     */
    public static String date2String(Date date, Boolean hms) {
        DateFormat sdf;
        if (hms) {
            sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        } else {
            sdf = new SimpleDateFormat("yyyy-MM-dd");
        }
        String time = sdf.format(date);
        return time;
    }

    public static Timestamp string2Timestamp(String time) {
        Timestamp ts = new Timestamp(System.currentTimeMillis());
        if (time.length() <= 10) {
            time = time + " 00:00:00";
        }
        ts = Timestamp.valueOf(time);
        return ts;
    }

    /**
     * @param ts  时间戳对象
     * @param hms 是否显示时分秒
     * @return
     */
    public static String timestamp2String(Timestamp ts, Boolean hms) {
        DateFormat sdf;
        if (hms) {
            sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        } else {
            sdf = new SimpleDateFormat("yyyy-MM-dd");
        }
        String tsStr = "";
        tsStr = sdf.format(ts);
        return tsStr;
    }

    public static Date timestamp2Date(Timestamp ts) {
        Date date = new Date();
        date = ts;
        return date;
    }

    public static Timestamp date2Timestamp(Date date) {
        String time = "";
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        time = sdf.format(date);
        Timestamp ts = Timestamp.valueOf(time);
        return ts;
    }

    /**
     * 计算两个时间之间的小时差
* start - stop * * @param start 开始时间 * @param stop 结束时间 * @return */ public static Long countHour(Date start, Date stop) { long diff = start.getTime() - stop.getTime(); long hour = diff / (60 * 60 * 1000); return hour; } /** * 计算两个时间之间的分钟数差
* start - stop * * @param start 开始时间 * @param stop 结束时间 * @return */ public static Long countMinute(Date start, Date stop) { long diff = start.getTime() - stop.getTime(); long min = diff / (60 * 1000); return min; } /** * 计算两个时间之间的秒数差
* start - stop * * @param start 开始时间 * @param stop 结束时间 * @return */ public static Long countSecond(Date start, Date stop) { long diff = start.getTime() - stop.getTime(); long sec = diff / 1000; return sec; } /** * 按天增加或减时间 * * @param date * @param days 增减的天数 * @param hms 是否显示时分秒 * @param isAdd 加减标识,false 是减,true是加 * @return */ public static String addOrMinusDate(Date date, int days, Boolean hms, Boolean isAdd) { long d = (long) days; SimpleDateFormat df = null; if (hms) { df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); } else { df = new SimpleDateFormat("yyyy-MM-dd"); } if (!isAdd) { return df.format(new Date(date.getTime() - (d * 24 * 60 * 60 * 1000))); } else { return df.format(new Date(date.getTime() + (d * 24 * 60 * 60 * 1000))); } } /** * 判断两个日期是否同年同月 * * @param date1 时间1 * @param date2 时间2 * @return */ public static boolean equals(Date date1, Date date2) { Calendar calendar1 = Calendar.getInstance(); calendar1.setTime(date1); Calendar calendar2 = Calendar.getInstance(); calendar2.setTime(date2); return calendar1.get(Calendar.YEAR) == calendar2.get(Calendar.YEAR) && calendar1.get(Calendar.MONTH) == calendar2.get(Calendar.MONTH); }

2.2 用户日志的记录

用户日志的记录,主要通过自定义java注解,通过在service方法标记注解,使用spring aop进行日志存储

2.2.1 自定义java注解

自定义注解主要包括模块名称、操作内容两个内容,其使用方式为:@LogAnnotation(moduleName = “角色管理”, operate = “新增角色”)
如果需要其他内容,可根据以下源码进行扩展

LogAnnotation.java

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;

/**
 * 自定义日志注解
 */
@Retention(RetentionPolicy.RUNTIME)  
@Target({ElementType.METHOD})  
@Documented
public @interface LogAnnotation {
	
	/**模块名称*/
    String moduleName() default "";  
    /**操作内容*/
    String operate() default "";  
	
}

注解使用:

@LogAnnotation(moduleName = "角色管理", operate = "新增角色")
@Override
public void saveRole(SysRole role, String[] perIds) {

2.2.2 切面配置

aop配置

	
	     
	    
   
  
  
    
    
    	
    	
    
  

LogInterceptor.java

定义拦截器,重写afterReturning方法,在调用service方法完成之后触发该方法实现日志写入

/**
 * 日志拦截器
 * 
 */
public class LogInterceptor {
	/**
	 * 方法正常完成后执行方法
	 * @param point
	 */
	@SuppressWarnings("rawtypes")
	public void afterReturning(JoinPoint point) {
        try {
        	Subject subject = null ;
        	try {
				subject = CarfiUserUtil.getSubject();
			} catch (Exception e) {
				e.printStackTrace();
				//发生异常则不进行
				return ;
			}
        	SysUserLog userLog = new SysUserLog();
        	String targetName = point.getTarget().getClass().getName();
			Class targetClass = Class.forName(targetName);
			String methodName = point.getSignature().getName();
			Method[] method = targetClass.getMethods();
			Object[] params = point.getArgs(); // 获得参数列表
			for (Method m : method) {
				if (m.getName().equals(methodName)) {
					Class[] tmpCs = m.getParameterTypes();
					if (tmpCs.length == params.length) {
						// 获取注解内容
						LogAnnotation logAnnotation = m.getAnnotation(LogAnnotation.class);
						if(logAnnotation != null){
							//写入参数
							if(params.length>0){
								// 使用json转换工具 将参数转为json串,以便存入数据库
								String jsonStr = JsonUtil.toJSONStr(params);
								userLog.setParams(jsonStr);
							}
							//获取模块名称
							String moduleName = logAnnotation.moduleName();
							//获取操作名称
							String operate = logAnnotation.operate();
							userLog.setModuleName(moduleName);
							userLog.setOperate(operate);
							userLog.setClassName(targetName);
							userLog.setMethodName(methodName);
							userLog.setLogId(CommonUtil.generatePrimaryKey());
							userLog.setTime(new Date());
							HttpServletRequest request = (HttpServletRequest)((WebSubject)subject).getServletRequest();
							userLog.setIp(CommonUtil.getIpAddr(request));
							userLog.setUserId(CarfiUserUtil.getSysUser().getUserId());
							SysUserLogService userLogService = (SysUserLogService) SpringUtil.getBean ("sysUserLogService");
							userLogService.addUserLog(userLog);
							break;
						}
					}
				}
			}
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		}
	}
}

3 SpringUtil.java 解释

SpringUtil的作用为在非Spring IOC容器下获取Spring IOC容器的上下文环境中的类,比如获取某个bean,最常见的如本文中的多次出现的“SysUserLogService userLogService = (SysUserLogService) SpringUtil.getBean (“sysUserLogService”);”通过SpringUtil得到service

3.1 SpringUtil 配置详解

SpringUtil.java

import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;

/**
 * Spring工具类
 */
public class SpringUtil implements ApplicationContextAware {

	/** 上下文 */
	private static ApplicationContext applicationContext;

	public static ApplicationContext getApplicationContext() {
		return applicationContext;
	}

	@Override
	public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
		SpringUtil.applicationContext = applicationContext;
	}

	/**
	 * 根据Bean ID获取Bean
	 * 
	 * @param beanId
	 * @return
	 */
	public static Object getBean(String beanId) {
		if(applicationContext == null){
			return null;
		}
		return applicationContext.getBean(beanId);
	}
}

spring 配置文件(application.xml)中配置相应的bean,在配置文件中注册的SpringUtil bean 应在spring扫描配置之前

 

 

你可能感兴趣的:(java)