1. 登錄日志
1.1 登錄成功、失敗的日志示例
// 記錄登錄成功日志
AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success")));
// 記錄登錄失敗日志(異常信息為:用戶不存在/密碼錯誤)
AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.password.not.match")));
1.2 AsyncFactory.recordLogininfor
// recordLogininfor用來創(chuàng)建SysLogininfor對象,并插入到數(shù)據(jù)庫中,并將這些操作封裝成實現(xiàn)了Runnable的TimerTask,交由線程池異步調(diào)度、執(zhí)行
public static TimerTask recordLogininfor(final String username, final String status, final String message,
final Object... args) {
// 創(chuàng)建任務(wù)(TimeTask實現(xiàn)了Runnable,可作為一個任務(wù)交由線程池執(zhí)行,由線程池異步調(diào)度執(zhí)行)
return new TimerTask() {
// 重寫run方法
@Override
public void run() {
// 構(gòu)造登錄日志對象
SysLogininfor logininfor = new SysLogininfor();
// 設(shè)置登錄日志信息
logininfor.setUserName(username);
logininfor.setIpaddr(ip);
logininfor.setLoginLocation(address);
logininfor.setBrowser(browser);
logininfor.setOs(os);
logininfor.setMsg(message);
// 設(shè)置日志狀態(tài)
if (StringUtils.equalsAny(status, Constants.LOGIN_SUCCESS, Constants.LOGOUT, Constants.REGISTER)) {
logininfor.setStatus(Constants.SUCCESS);
} else if (Constants.LOGIN_FAIL.equals(status)) {
logininfor.setStatus(Constants.FAIL);
}
// 插入日志數(shù)據(jù)
SpringUtils.getBean(ISysLogininforService.class).insertLogininfor(logininfor);
}
};
}
1.3 AsyncManager.me
public class AsyncManager {
private AsyncManager(){}
// 餓漢式單例模式
private static AsyncManager me = new AsyncManager();
public static AsyncManager me() {
return me;
}
// 其余(略)
}
1.4 AsyncManager.execute
public class AsyncManager {
// 操作延遲10毫秒
private final int OPERATE_DELAY_TIME = 10;
// (通過beanName獲取bean)
// 異步操作任務(wù)調(diào)度線程池(實現(xiàn)類是ScheduledThreadPoolExecutor,
// ScheduledThreadPoolExecutor可用來執(zhí)行周期性、延遲性任務(wù))
private ScheduledExecutorService executor = SpringUtils.getBean("scheduledExecutorService");
// 執(zhí)行任務(wù)
public void execute(TimerTask task) {
// 延遲10毫秒執(zhí)行
executor.schedule(task, OPERATE_DELAY_TIME, TimeUnit.MILLISECONDS);
}
}
1.5 創(chuàng)建ScheduledExecutorService
@Bean(name = "scheduledExecutorService")
protected ScheduledExecutorService scheduledExecutorService()
{
return new ScheduledThreadPoolExecutor(corePoolSize,
new BasicThreadFactory.Builder().namingPattern("schedule-pool-%d").daemon(true).build(),
new ThreadPoolExecutor.CallerRunsPolicy())
{
// 這里重寫了afterExecute方法,如果執(zhí)行任務(wù)時出現(xiàn)異常,會將異常傳遞到afterExecute的參數(shù)中
@Override
protected void afterExecute(Runnable r, Throwable t)
{
super.afterExecute(r, t);
// 記錄異常信息
Threads.printException(r, t);
}
};
}
????綜上,可知記錄登錄日志是先通過AsyncFactory.recordLogininfor創(chuàng)建SysLogininfor并插入到數(shù)據(jù)庫中,并將這些操作封裝成實現(xiàn)了Runnable的TimerTask,之后交由ScheduledThreadPoolExecutor異步調(diào)度,延遲10毫秒執(zhí)行。
2. 操作日志
2.1 操作日志示例
????在需要記錄操作日志的方法上貼上@Log注解,并傳入模塊名、功能名等參數(shù)對日志屬性進行設(shè)置:
@PreAuthorize("@ss.hasPermi('system:config:add')")
@Log(title = "參數(shù)管理", businessType = BusinessType.INSERT)
@PostMapping
public AjaxResult add(@Validated @RequestBody SysConfig config)
{
if (UserConstants.NOT_UNIQUE.equals(configService.checkConfigKeyUnique(config)))
{
return AjaxResult.error("新增參數(shù)'" + config.getConfigName() + "'失敗,參數(shù)鍵名已存在");
}
config.setCreateBy(getUsername());
return toAjax(configService.insertConfig(config));
}
2.2 操作日志切面
????在LogAspect中,會以@Log注解為織入點,在方法返回后或拋異常后對貼有@Log注解的方法進行處理:
// 日志切面
@Aspect
@Component
public class LogAspect {
private static final Logger log = LoggerFactory.getLogger(LogAspect.class);
// 方法返回后執(zhí)行
// jsonResult是方法的返回值
@AfterReturning(pointcut = "@annotation(controllerLog)", returning = "jsonResult")
public void doAfterReturning(JoinPoint joinPoint, Log controllerLog, Object jsonResult) {
// 調(diào)用handleLog處理@Log注解及請求、響應(yīng)參數(shù)
handleLog(joinPoint, controllerLog, null, jsonResult);
}
// 拋異常后執(zhí)行
// e為異常對象
@AfterThrowing(value = "@annotation(controllerLog)", throwing = "e")
public void doAfterThrowing(JoinPoint joinPoint, Log controllerLog, Exception e) {
// 調(diào)用handleLog處理@Log注解及請求、響應(yīng)參數(shù)
handleLog(joinPoint, controllerLog, e, null);
}
}
LogAspect.handleLog
protected void handleLog(final JoinPoint joinPoint, Log controllerLog, final Exception e, Object jsonResult) {
try {
// 獲取當(dāng)前登錄用戶
LoginUser loginUser = SecurityUtils.getLoginUser();
// 創(chuàng)建操作日志對象
SysOperLog operLog = new SysOperLog();
// 將操作狀態(tài)設(shè)置為成功
operLog.setStatus(BusinessStatus.SUCCESS.ordinal());
// 請求的地址
String ip = IpUtils.getIpAddr(ServletUtils.getRequest());
operLog.setOperIp(ip);
// 設(shè)置請求url
operLog.setOperUrl(ServletUtils.getRequest().getRequestURI());
if (loginUser != null) {
// 設(shè)置操作人
operLog.setOperName(loginUser.getUsername());
}
// e不為null,即方法拋異常了
if (e != null) {
// 將操作狀態(tài)設(shè)置為失敗,并設(shè)置異常信息
operLog.setStatus(BusinessStatus.FAIL.ordinal());
operLog.setErrorMsg(StringUtils.substring(e.getMessage(), 0, 2000));
}
// 設(shè)置方法名稱
String className = joinPoint.getTarget().getClass().getName();
String methodName = joinPoint.getSignature().getName();
operLog.setMethod(className + "." + methodName + "()");
// 設(shè)置請求方式
operLog.setRequestMethod(ServletUtils.getRequest().getMethod());
// 將controllerLog中配置的參數(shù)信息設(shè)置到operLog中
// 將joinPoint中的請求信息設(shè)置到operLog中
// 將jsonResult中的響應(yīng)信息設(shè)置到operLog中
getControllerMethodDescription(joinPoint, controllerLog, operLog, jsonResult);
// AsyncFactory.recordOper:將操作日志插入到數(shù)據(jù)庫中并封裝成實現(xiàn)了Runnable的TimerTask作為線程池的任務(wù)
// AsyncManager.me().execute:將任務(wù)放入ScheduledThreadPoolExecutor中,延遲10毫秒執(zhí)行
AsyncManager.me().execute(AsyncFactory.recordOper(operLog));
} catch (Exception exp) {
// 記錄本地異常日志
log.error("==前置通知異常==");
log.error("異常信息:{}", exp.getMessage());
exp.printStackTrace();
}
}
????綜上,可知記錄登錄日志、操作日志是先創(chuàng)建對應(yīng)的日志對象并插入到數(shù)據(jù)庫中,并將這些操作封裝成實現(xiàn)了Runnable的TimerTask,之后交由ScheduledThreadPoolExecutor異步調(diào)度,延遲10毫秒執(zhí)行。