【Java】若依(ruoyi)——15.操作日志

查看页面:https://blog.csdn.net/Felix_hyfy/article/details/105657152

操作日志

登录日志存储在数据表sys_loginfor系统访问记录。
image

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

<!-- SpringBoot 拦截器 -->
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-aop</artifactId>
</dependency>

image

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

查看源码,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中,表格设计如下:
image

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

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

解锁:后续看看怎么回事

image

那么,登录日志如何写入的呢?应该是在登录接口时写入。之前我们有学到若依使用的安全框架——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黑名单校验;用户是否存在;用户已已删除错误;用户已禁用错误;),如果不符合要求,则执行异步任务,向前端页面返回验证失败类型;如果符合要求,想前端返回登录成功信息、设置角色信息;记录用户登录信息。

posted @ 2025-06-27 17:13  陆陆无为而治者  阅读(768)  评论(0)    收藏  举报