【Java】若依(ruoyi)——15.操作日志
查看页面:https://blog.csdn.net/Felix_hyfy/article/details/105657152
操作日志
登录日志存储在数据表sys_loginfor系统访问记录。

操作日志查看页:

那么,操作时,如何写入日志呢?这时候,涉及到了AOP切面编程。也就是不改变原有方法的基础上。分离出很多个程序都会涉及到的逻辑。比如:日志记录、事物管理、安全检查等。这些逻辑独立于应用程序核心业务,但又需要应用程序去执行。此时,我们打开若依的frame子项目,看到子包aspectj下的LogAspect。日志的切面方法。在frame框架中,已引用了aop
<!-- SpringBoot 拦截器 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>

在此之前,我们应该学习AOP。

查看源码,LogAspect.java 使用@annotation。根据注解来匹配切点。切点方法如下:
| 切点 | 注解 | 切点表达式 | 说明 | 作用 |
|---|---|---|---|---|
| boBefore | @Before | (value = "@annotation(controllerLog)") | 处理请求前执行 | 记录处理请求前的时间 |
| doAfterReturning | @AfterReturning | (pointcut = "@annotation(controllerLog)", returning = "jsonResult") | 处理完请求后执行 | 写入正常登录的日志 |
| @AfterThrowing | @AfterThrowing | (value = "@annotation(controllerLog)", throwing = "e") | 拦截异常操作 | 写入异常操作的日志 |
可以看到注解参数controllerLog,对应的注解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;
/**
* 是否保存请求的参数
*/
public boolean isSaveRequestData() default true;
/**
* 是否保存响应的参数
*/
public boolean isSaveResponseData() default true;
/**
* 排除指定的请求参数
*/
public String[] excludeParamNames() default {};
}
那么有哪些方法使用到@Log注释呢?
| 框架 | 包名 | 类名 | 方法名 | 方法说明 |
|---|---|---|---|---|
| admin | com.*.web.controller.system | SysConfigController | export | 参数导出管理 |
| editSave | 参数编辑 | |||
| remove | 删除参数配置 | |||
| refreshCache | 刷新参数缓存 | |||
| SysDeptController(部门信息) | …… | |||
| SysDictDataController(字典数据) | …… | |||
| SysDictTypeController(数据字典信息) | …… | |||
| SysMenuController(菜单信息) | …… | |||
| SysNoticeController(公告信息) | …… | |||
| SysPostController(岗位信息操作处理) | …… | |||
| SysProfileController(个人信息业务处理) | …… | |||
| SysRoleController(角色信息) | …… | |||
| SysUserController(用户管理) |
大体思路:使用切面,处理完成后和异常的切点。(即登录或异常报错后,写入登录日志)
在这里,我需要知道AOP以及SpringBoot中如何使用AOP。
操作日志
登录日志存储在数据表sys_loginfor中,表格设计如下:

启动ruoyi,登录到登录日志查看页:

页面包含删除、清空、解锁、导出、修改功能。登录日志(系统访问日志)的代码与大多数代码生成器生成的代码一起其他页面类似,这里就不赘述了。
解锁:后续看看怎么回事

那么,登录日志如何写入的呢?应该是在登录接口时写入。之前我们有学到若依使用的安全框架——Shiro。
登录时,调用认证登录方法,认证登录方法调用域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;
}
授权时,会执行登录方法: loginService.login(username, password);我们跳转到登录方法
/**
* 登录
*/
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();
}
// 密码如果不在指定范围内 错误
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();
}
// 用户名不在指定范围内 错误
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);
}
先后进行登录验证:(验证码校验;用户名或密码为空 错误;密码不在指定范围内错误;用户名不在指定范围内错误;IP黑名单校验;用户是否存在;用户已已删除错误;用户已禁用错误;),如果不符合要求,则执行异步任务,向前端页面返回验证失败类型;如果符合要求,想前端返回登录成功信息、设置角色信息;记录用户登录信息。

浙公网安备 33010602011771号