【Java】若依(ruoyi)——15.操作日志
查看頁面:https://blog.csdn.net/Felix_hyfy/article/details/105657152
操作日志
登錄日志存儲在數(shù)據(jù)表sys_loginfor系統(tǒng)訪問記錄。

操作日志查看頁:

那么,操作時,如何寫入日志呢?這時候,涉及到了AOP切面編程。也就是不改變原有方法的基礎(chǔ)上。分離出很多個程序都會涉及到的邏輯。比如:日志記錄、事物管理、安全檢查等。這些邏輯獨立于應(yīng)用程序核心業(yè)務(wù),但又需要應(yīng)用程序去執(zhí)行。此時,我們打開若依的frame子項目,看到子包aspectj下的LogAspect。日志的切面方法。在frame框架中,已引用了aop
<!-- SpringBoot 攔截器 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>

在此之前,我們應(yīng)該學(xué)習(xí)AOP。

查看源碼,LogAspect.java 使用@annotation。根據(jù)注解來匹配切點。切點方法如下:
| 切點 | 注解 | 切點表達式 | 說明 | 作用 |
|---|---|---|---|---|
| boBefore | @Before | (value = "@annotation(controllerLog)") | 處理請求前執(zhí)行 | 記錄處理請求前的時間 |
| doAfterReturning | @AfterReturning | (pointcut = "@annotation(controllerLog)", returning = "jsonResult") | 處理完請求后執(zhí)行 | 寫入正常登錄的日志 |
| @AfterThrowing | @AfterThrowing | (value = "@annotation(controllerLog)", throwing = "e") | 攔截異常操作 | 寫入異常操作的日志 |
可以看到注解參數(shù)controllerLog,對應(yīng)的注解Log
/**
* 自定義操作日志記錄注解
*
* @author ruoyi
*/
@Target({ ElementType.PARAMETER, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Log
{
/**
* 模塊
*/
public String title() default "";
/**
* 功能
*/
public BusinessType businessType() default BusinessType.OTHER;
/**
* 操作人類別
*/
public OperatorType operatorType() default OperatorType.MANAGE;
/**
* 是否保存請求的參數(shù)
*/
public boolean isSaveRequestData() default true;
/**
* 是否保存響應(yīng)的參數(shù)
*/
public boolean isSaveResponseData() default true;
/**
* 排除指定的請求參數(shù)
*/
public String[] excludeParamNames() default {};
}
那么有哪些方法使用到@Log注釋呢?
| 框架 | 包名 | 類名 | 方法名 | 方法說明 |
|---|---|---|---|---|
| admin | com.*.web.controller.system | SysConfigController | export | 參數(shù)導(dǎo)出管理 |
| editSave | 參數(shù)編輯 | |||
| remove | 刪除參數(shù)配置 | |||
| refreshCache | 刷新參數(shù)緩存 | |||
| SysDeptController(部門信息) | …… | |||
| SysDictDataController(字典數(shù)據(jù)) | …… | |||
| SysDictTypeController(數(shù)據(jù)字典信息) | …… | |||
| SysMenuController(菜單信息) | …… | |||
| SysNoticeController(公告信息) | …… | |||
| SysPostController(崗位信息操作處理) | …… | |||
| SysProfileController(個人信息業(yè)務(wù)處理) | …… | |||
| SysRoleController(角色信息) | …… | |||
| SysUserController(用戶管理) |
大體思路:使用切面,處理完成后和異常的切點。(即登錄或異常報錯后,寫入登錄日志)
在這里,我需要知道AOP以及SpringBoot中如何使用AOP。
操作日志
登錄日志存儲在數(shù)據(jù)表sys_loginfor中,表格設(shè)計如下:

啟動ruoyi,登錄到登錄日志查看頁:

頁面包含刪除、清空、解鎖、導(dǎo)出、修改功能。登錄日志(系統(tǒng)訪問日志)的代碼與大多數(shù)代碼生成器生成的代碼一起其他頁面類似,這里就不贅述了。
解鎖:后續(xù)看看怎么回事

那么,登錄日志如何寫入的呢?應(yīng)該是在登錄接口時寫入。之前我們有學(xué)到若依使用的安全框架——Shiro。
登錄時,調(diào)用認證登錄方法,認證登錄方法調(diào)用域USeRealm.java的認證方法。
/**
* 登錄認證
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException
{
UsernamePasswordToken upToken = (UsernamePasswordToken) token;
String username = upToken.getUsername();
String password = "";
if (upToken.getPassword() != null)
{
password = new String(upToken.getPassword());
}
SysUser user = null;
try
{
user = loginService.login(username, password);
}
catch (CaptchaException e)
{
throw new AuthenticationException(e.getMessage(), e);
}
catch (UserNotExistsException e)
{
throw new UnknownAccountException(e.getMessage(), e);
}
catch (UserPasswordNotMatchException e)
{
throw new IncorrectCredentialsException(e.getMessage(), e);
}
catch (UserPasswordRetryLimitExceedException e)
{
throw new ExcessiveAttemptsException(e.getMessage(), e);
}
catch (UserBlockedException e)
{
throw new LockedAccountException(e.getMessage(), e);
}
catch (RoleBlockedException e)
{
throw new LockedAccountException(e.getMessage(), e);
}
catch (Exception e)
{
log.info("對用戶[" + username + "]進行登錄驗證..驗證未通過{}", e.getMessage());
throw new AuthenticationException(e.getMessage(), e);
}
SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(user, password, getName());
return info;
}
授權(quán)時,會執(zhí)行登錄方法: loginService.login(username, password);我們跳轉(zhuǎn)到登錄方法
/**
* 登錄
*/
public SysUser login(String username, String password)
{
// 驗證碼校驗
if (ShiroConstants.CAPTCHA_ERROR.equals(ServletUtils.getRequest().getAttribute(ShiroConstants.CURRENT_CAPTCHA)))
{
AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.error")));
throw new CaptchaException();
}
// 用戶名或密碼為空 錯誤
if (StringUtils.isEmpty(username) || StringUtils.isEmpty(password))
{
AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("not.null")));
throw new UserNotExistsException();
}
// 密碼如果不在指定范圍內(nèi) 錯誤
if (password.length() < UserConstants.PASSWORD_MIN_LENGTH
|| password.length() > UserConstants.PASSWORD_MAX_LENGTH)
{
AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.password.not.match")));
throw new UserPasswordNotMatchException();
}
// 用戶名不在指定范圍內(nèi) 錯誤
if (username.length() < UserConstants.USERNAME_MIN_LENGTH
|| username.length() > UserConstants.USERNAME_MAX_LENGTH)
{
AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.password.not.match")));
throw new UserPasswordNotMatchException();
}
// IP黑名單校驗
String blackStr = configService.selectConfigByKey("sys.login.blackIPList");
if (IpUtils.isMatchedIp(blackStr, ShiroUtils.getIp()))
{
AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("login.blocked")));
throw new BlackListException();
}
// 查詢用戶信息
SysUser user = userService.selectUserByLoginName(username);
/**
if (user == null && maybeMobilePhoneNumber(username))
{
user = userService.selectUserByPhoneNumber(username);
}
if (user == null && maybeEmail(username))
{
user = userService.selectUserByEmail(username);
}
*/
if (user == null)
{
AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.not.exists")));
throw new UserNotExistsException();
}
if (UserStatus.DELETED.getCode().equals(user.getDelFlag()))
{
AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.password.delete")));
throw new UserDeleteException();
}
if (UserStatus.DISABLE.getCode().equals(user.getStatus()))
{
AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.blocked")));
throw new UserBlockedException();
}
passwordService.validate(user, password);
AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success")));
setRolePermission(user);
recordLoginInfo(user.getUserId()); // 記錄登錄信息
return user;
}
/**
* 記錄登錄信息
*
* @param userId 用戶ID
*/
public void recordLoginInfo(Long userId)
{
SysUser user = new SysUser();
user.setUserId(userId);
user.setLoginIp(ShiroUtils.getIp());
user.setLoginDate(DateUtils.getNowDate());
userService.updateUserInfo(user);
}
先后進行登錄驗證:(驗證碼校驗;用戶名或密碼為空 錯誤;密碼不在指定范圍內(nèi)錯誤;用戶名不在指定范圍內(nèi)錯誤;IP黑名單校驗;用戶是否存在;用戶已已刪除錯誤;用戶已禁用錯誤;),如果不符合要求,則執(zhí)行異步任務(wù),向前端頁面返回驗證失敗類型;如果符合要求,想前端返回登錄成功信息、設(shè)置角色信息;記錄用戶登錄信息。

浙公網(wǎng)安備 33010602011771號