目录

SpringSecurity权限管理系统实战—一、项目简介和开发环境准备

SpringSecurity权限管理系统实战—二、日志、接口文档等实现

SpringSecurity权限管理系统实战—三、主要页面及接口实现

SpringSecurity权限管理系统实战—四、整合SpringSecurity(上)

SpringSecurity权限管理系统实战—五、整合SpringSecurity(下)

SpringSecurity权限管理系统实战—六、SpringSecurity整合jwt

SpringSecurity权限管理系统实战—七、处理一些问题

SpringSecurity权限管理系统实战—八、AOP 记录用户日志、异常日志

前言

日志功能在二的时候其实简单实现了一下,但是有时我们需要对一些重要功能操作记录日志,或是在操作时发生异常,需要记录异常日志。但是之前每次发生异常要定位原因我们都要到服务器去查询日志才能找到,或许是搭建一个日志收集系统(但是本项目中暂不考虑)。那么我们可以专门做个功能来记录用户操作日志和异常日志,在把日志存入数据库,方便查询。

一、最终效果

二、新建my_log表

相应字段都有注释,很好理解,用户日志、异常日志都存放在这一张表中,通过type来区分,当然也可以拆分成两张表。

三、添加依赖

 		<!--aop-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<!-- UserAgentUtils,浏览器信息工具类 -->
<dependency>
<groupId>eu.bitwalker</groupId>
<artifactId>UserAgentUtils</artifactId>
<version>1.21</version>
</dependency>
<!--ip2region,这是根据ip查地址的工具,有兴趣自己可以了解-->
<!-- <dependency>-->
<!-- <groupId>org.lionsoul</groupId>-->
<!-- <artifactId>ip2region</artifactId>-->
<!-- <version>1.7.2</version>-->
<!-- </dependency>-->
<!--分页工具-->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
<version>1.2.13</version>
</dependency>
<!--hutool工具-->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.1.4</version>
</dependency>

四、需要用到的工具类

SecurityUtils

/**
* @author codermy
* @createTime 2020/8/4
*/
public class SecurityUtils { /**
* 获取系统用户名称
*
* @return 系统用户名称
*/
public static String getCurrentUsername() {
final Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication == null) {
throw new MyException(ResultCode.UNAUTHORIZED, "当前登录状态过期");
}
UserDetails userDetails = (UserDetails) authentication.getPrincipal();
return userDetails.getUsername();
}
/**
* 取得当前用户登录IP, 如果当前用户未登录则返回空字符串.
* 此方法无用
*/
public static String getCurrentUserIp() {
final Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); if (authentication == null) {
throw new MyException(ResultCode.UNAUTHORIZED, "当前登录状态过期");
}
Object details = authentication.getDetails();
if (!(details instanceof WebAuthenticationDetails)) {
return "";
}
WebAuthenticationDetails webDetails = (WebAuthenticationDetails) details;
return webDetails.getRemoteAddress();
} }

LogUtils

/**
* @author codermy
* @createTime 2020/8/7
*/
public class LogUtils {
private static final char SEPARATOR = '_'; private static final String UNKNOWN = "unknown";
/**
* 获取ip地址
*/
public static String getIp(HttpServletRequest request) {
String ip = request.getHeader("x-forwarded-for");
if (ip == null || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) {
ip = request.getHeader("Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) {
ip = request.getHeader("WL-Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) {
ip = request.getRemoteAddr();
}
String comma = ",";
String localhost = "127.0.0.1";
if (ip.contains(comma)) {
ip = ip.split(",")[0];
}
if (localhost.equals(ip)) {
// 获取本机真正的ip地址
try {
ip = InetAddress.getLocalHost().getHostAddress();
} catch (UnknownHostException e) {
e.printStackTrace();
}
}
return ip;
} /**
* 获取浏览器信息
* @param request
* @return
*/
public static String getBrowser(HttpServletRequest request){
UserAgent userAgent = UserAgent.parseUserAgentString(request.getHeader("User-Agent"));
Browser browser = userAgent.getBrowser();
return browser.getName();
} /**
* 获取堆栈信息
*/
public static String getStackTrace(Throwable throwable){
StringWriter sw = new StringWriter();
try (PrintWriter pw = new PrintWriter(sw)) {
throwable.printStackTrace(pw);
return sw.toString();
}
} }

RequestHolder

/**
* @author codermy
* @createTime 2020/8/4
*/
public class RequestHolder {
/**
* 获取HttpServletRequest对象
* @return
*/
public static HttpServletRequest getHttpServletRequest() {
return ((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest();
}
}

五、相应实体类

这个部分省略,没有什么技术含量,根据数据库来就行

六、自定义操作日志的注解类

/**
* @author codermy
* @createTime 2020/8/4
*/
@Target(ElementType.METHOD)//注解放置的目标位置,METHOD是可注解在方法级别上
@Retention(RetentionPolicy.RUNTIME)//注解在哪个阶段执行
public @interface MyLog {
String value() default "";
}

关于java自定义注解可以看看这篇文章

七、新建切面类

这其实很好理解,就是我们学习spring时,aop的几种通知。

/**
* @author codermy
* @createTime 2020/8/4
*/
@Component
@Aspect
@Slf4j
public class LogAspect {
//注入logService用于将日志存入数据库
@Autowired
private MyLogService logService; ThreadLocal<Long> currentTime = new ThreadLocal<>();
/**
* 设置操作日志切入点 记录操作日志 在注解的位置切入代码
*/
@Pointcut("@annotation(com.codermy.myspringsecurityplus.log.aop.MyLog)")
public void logPoinCut() {
} /**
* 配置环绕通知,使用在方法logPointcut()上注册的切入点
*
* @param joinPoint join point for advice
*/
@Around("logPoinCut()")
public Object saveSysLog(ProceedingJoinPoint joinPoint)throws Throwable{
Object result;
currentTime.set(System.currentTimeMillis());//记录方法的执行时间
result = joinPoint.proceed();
MyLog log = new MyLog("INFO",System.currentTimeMillis() - currentTime.get());//定义日志类型
currentTime.remove();
HttpServletRequest request = RequestHolder.getHttpServletRequest();
logService.save(SecurityUtils.getCurrentUsername(), LogUtils.getBrowser(request), LogUtils.getIp(request),joinPoint, log);
return result;
} /**
* 配置异常通知
*
* @param joinPoint join point for advice
* @param e exception
*/
@AfterThrowing(pointcut = "logPoinCut()", throwing = "e")
public void logAfterThrowing(JoinPoint joinPoint, Throwable e) {
MyLog log = new MyLog("ERROR",System.currentTimeMillis() - currentTime.get());
currentTime.remove();
log.setExceptionDetail(LogUtils.getStackTrace(e));
HttpServletRequest request = RequestHolder.getHttpServletRequest();
logService.save(SecurityUtils.getCurrentUsername(), LogUtils.getBrowser(request), LogUtils.getIp(request), (ProceedingJoinPoint)joinPoint, log);
}
}

八、相应方法及接口

dao

/**
* @author codermy
* @createTime 2020/8/8
*/
@Mapper
public interface LogDao { /**
* 保存日志
* @param log
*/
@Insert("insert into my_log(user_name,ip,description,params,type,exception_detail,browser,method,time,create_time)values(#{userName},#{ip},#{description},#{params},#{type},#{exceptionDetail},#{browser},#{method},#{time},now())")
void save(MyLog log); /**
* 分页返回所有用户日志
* @param logQuery 查询条件
* @return
*/
List<LogDto> getFuzzyLogByPage( @Param("logQuery") LogQuery logQuery); /**
* 分页返回所有错误日志
* @param logQuery 查询条件
* @return
*/
List<ErrorLogDto> getFuzzyErrorLogByPage(@Param("logQuery") LogQuery logQuery); /**
* 删除所有日志
* @param type 日志类型
*/
@Delete("delete from my_log where type = #{type}")
void delAllByInfo(String type);
}

LogMapper.xml

<mapper namespace="com.codermy.myspringsecurityplus.log.dao.LogDao">
<select id="getFuzzyLogByPage" resultType="com.codermy.myspringsecurityplus.log.dto.LogDto">
SELECT t.user_name,t.ip,t.params,t.description,t.browser,t.time,t.method,t.create_time
FROM my_log t
<where>
<if test="logQuery.logType != null and logQuery.logType != ''">
AND t.type = #{logQuery.logType}
</if>
<if test="logQuery.userName != null and logQuery.userName != ''">
AND t.user_name like CONCAT('%', #{logQuery.userName}, '%')
</if>
</where>
ORDER BY t.create_time desc
</select> <select id="getFuzzyErrorLogByPage" resultType="com.codermy.myspringsecurityplus.log.dto.ErrorLogDto">
SELECT t.user_name,t.ip,t.params,t.description,t.browser,t.exception_detail,t.method,t.create_time
FROM my_log t
<where>
<if test="logQuery.logType != null and logQuery.logType != ''">
AND t.type = #{logQuery.logType}
</if>
<if test="logQuery.userName != null and logQuery.userName != ''">
AND t.user_name like CONCAT('%', #{logQuery.userName}, '%')
</if>
</where>
ORDER BY t.create_time desc
</select> </mapper>

MyLogServiceImpl

/**
* @author codermy
* @createTime 2020/8/4
*/
@Service
public class MyLogServiceImpl implements MyLogService {
@Autowired
private LogDao logDao;
//返回用户日志
@Override
public Result<LogDto> getFuzzyInfoLogByPage(Integer offectPosition, Integer limit, LogQuery logQuery) {
Page page = PageHelper.offsetPage(offectPosition,limit);
List<LogDto> fuzzyLogByPage = logDao.getFuzzyLogByPage(logQuery);
return Result.ok().count(page.getTotal()).data(fuzzyLogByPage).code(ResultCode.TABLE_SUCCESS);
}
//返回异常日志
@Override
public Result<ErrorLogDto> getFuzzyErrorLogByPage(Integer offectPosition, Integer limit, LogQuery logQuery) {
Page page = PageHelper.offsetPage(offectPosition,limit);
List<ErrorLogDto> fuzzyErrorLogByPage = logDao.getFuzzyErrorLogByPage(logQuery);
return Result.ok().count(page.getTotal()).data(fuzzyErrorLogByPage).code(ResultCode.TABLE_SUCCESS);
}
//保存日志到数据库
@Override
@Transactional(rollbackFor = Exception.class)
public void save(String userName, String browser, String ip, ProceedingJoinPoint joinPoint, MyLog log) {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
com.codermy.myspringsecurityplus.log.aop.MyLog myLog = method.getAnnotation(com.codermy.myspringsecurityplus.log.aop.MyLog.class);
// 方法路径
String methodName = joinPoint.getTarget().getClass().getName()+"."+signature.getName()+"()";
StringBuilder params = new StringBuilder("{");
//参数值
Object[] argValues = joinPoint.getArgs();
//参数名称
String[] argNames = ((MethodSignature)joinPoint.getSignature()).getParameterNames();
if(argValues != null){
for (int i = 0; i < argValues.length; i++) {
params.append(" ").append(argNames[i]).append(": ").append(argValues[i]);
}
}
// 描述
if (log != null) {
log.setDescription(myLog.value());
}
assert log != null;
log.setIp(ip);
String loginPath = "login";
if(loginPath.equals(signature.getName())){
try {
assert argValues != null;
userName = new JSONObject(argValues[0]).get("userName").toString();
}catch (Exception e){
e.printStackTrace();
}
}
log.setMethod(methodName);
log.setUserName(userName);
log.setParams(params.toString() + " }");
log.setBrowser(browser);
logDao.save(log);
}
//删除异常日志
@Override
@Transactional(rollbackFor = Exception.class)
public void delAllByError() {
logDao.delAllByInfo("ERROR");
}
//删除用户日志
@Override
@Transactional(rollbackFor = Exception.class)
public void delAllByInfo() {
logDao.delAllByInfo("INFO");
}
}

LogController

/**
* @author codermy
* @createTime 2020/8/8
*/
@Controller
@RequestMapping("/api")
@Api(tags = "系统:日志管理")
public class LogController {
@Autowired
private MyLogService logService; @GetMapping("/log/index")
public String logIndex(){
return "system/log/log";
} @GetMapping("/log")
@ResponseBody
@ApiOperation(value = "日志列表")
@PreAuthorize("hasAnyAuthority('log:list')")
public Result<LogDto> logList(PageTableRequest pageTableRequest, LogQuery logQuery){
pageTableRequest.countOffset();
logQuery.setLogType("INFO");
return logService.getFuzzyInfoLogByPage(pageTableRequest.getOffset(),pageTableRequest.getLimit(),logQuery);
} @DeleteMapping("/log")
@MyLog("删除所有INFO日志")
@ResponseBody
@ApiOperation("删除所有INFO日志")
@PreAuthorize("hasAnyAuthority('log:del')")
public Result<Object> delAllByInfo(){
logService.delAllByInfo();
return Result.ok().message("删除成功");
} @GetMapping("/log/error/index")
public String errorLogIndex(){
return "system/log/errorLog";
} @GetMapping("/error/log")
@ResponseBody
@ApiOperation(value = "错误日志")
@PreAuthorize("hasAnyAuthority('errorLog:list')")
public Result<ErrorLogDto> errorLogList(PageTableRequest pageTableRequest, LogQuery logQuery){
pageTableRequest.countOffset();
logQuery.setLogType("ERROR");
return logService.getFuzzyErrorLogByPage(pageTableRequest.getOffset(),pageTableRequest.getLimit(),logQuery);
}
@DeleteMapping("/error/log")
@MyLog("删除所有ERROR日志")
@ResponseBody
@ApiOperation("删除所有ERROR日志")
@PreAuthorize("hasAnyAuthority('errorLog:del')")
public Result<Object> delAllByError(){
logService.delAllByError();
return Result.ok().message("删除成功");
} }

相应的前端页面就不贴出来了,有需要可以在我的giteegithub中获取

九、使用

我们只需要在相应的接口上添加上@MyLog注解即可

我们可以自己先造一个异常来测试异常的收集

十、启动测试

启动项目,正常访问测试即可,会自动收集日志。



本系列giteegithub中同步更新

SpringSecurity权限管理系统实战—八、AOP 记录用户、异常日志的更多相关文章

  1. SpringSecurity权限管理系统实战—四、整合SpringSecurity(上)

    目录 SpringSecurity权限管理系统实战-一.项目简介和开发环境准备 SpringSecurity权限管理系统实战-二.日志.接口文档等实现 SpringSecurity权限管理系统实战-三 ...

  2. SpringSecurity权限管理系统实战—六、SpringSecurity整合jwt

    目录 SpringSecurity权限管理系统实战-一.项目简介和开发环境准备 SpringSecurity权限管理系统实战-二.日志.接口文档等实现 SpringSecurity权限管理系统实战-三 ...

  3. SpringSecurity权限管理系统实战—七、处理一些问题

    目录 SpringSecurity权限管理系统实战-一.项目简介和开发环境准备 SpringSecurity权限管理系统实战-二.日志.接口文档等实现 SpringSecurity权限管理系统实战-三 ...

  4. SpringSecurity权限管理系统实战—九、数据权限的配置

    目录 SpringSecurity权限管理系统实战-一.项目简介和开发环境准备 SpringSecurity权限管理系统实战-二.日志.接口文档等实现 SpringSecurity权限管理系统实战-三 ...

  5. SpringSecurity权限管理系统实战—一、项目简介和开发环境准备

    目录 SpringSecurity权限管理系统实战-一.项目简介和开发环境准备 SpringSecurity权限管理系统实战-二.日志.接口文档等实现 SpringSecurity权限管理系统实战-三 ...

  6. SpringSecurity权限管理系统实战—二、日志、接口文档等实现

    系列目录 SpringSecurity权限管理系统实战-一.项目简介和开发环境准备 SpringSecurity权限管理系统实战-二.日志.接口文档等实现 SpringSecurity权限管理系统实战 ...

  7. SpringSecurity权限管理系统实战—五、整合SpringSecurity(下)

    系列目录 前言 上篇文章SpringSecurity整合了一半,这次把另一半整完,所以本篇的序号接着上一篇. 七.自定义用户信息 前面我们登录都是用的指定的用户名和密码或者是springsecurit ...

  8. SpringSecurity权限管理系统实战—三、主要页面及接口实现

    系列目录 前言 后端五分钟,前端半小时.. 每次写js都头疼. 自己写前端是不可能的,这辈子不可能自己写前端的,只能找找别人的模板才能维持的了生存这样子.github,gitee上的模板又多,帮助文档 ...

  9. RabbitMQ实战场景(一):异步记录用户操作日志

    传统的项目开发中业务流程以串行方式,执行了模块1—>模块2–>模块3 而我们知道,这个执行流程其实对于整个程序来讲是有一定的弊端的,主要有几点: (1)整个流程的执行响应等待时间比较长; ...

随机推荐

  1. 06 . ELK Stack + kafka集群

    简介 Filebeat用于收集本地文件的日志数据. 它作为服务器上的代理安装,Filebeat监视日志目录或特定的日志文件,尾部文件,并将它们转发到Elasticsearch或Logstash进行索引 ...

  2. xctf-web fakebook

    点join注册账号 进入view.php发现参数no存在sql注入,但是过滤了select关键字,用内联注释绕过 在users表的data字段发现了用序列化存储的用户信息 然后就卡在这里了....看了 ...

  3. ~~并发编程(十三):信号量,Event,定时器~~

    进击のpython ***** 并发编程--信号量,Event,定时器 本节需要了解的就是: 信号量,以及信号量和互斥锁的区别 了解时间和定时器,以及使用 信号量 信号量也是锁,本质没有变!但是他跟互 ...

  4. laravel 安装语言包

    一.composer依赖网站地址:https://packagist.org/ 二.在搜索框输入: laravel-lang 三.点击进入,根据自己的版本进行安装: composer require ...

  5. AI大厂算法测试心得:人脸识别关键指标有哪些?

    仅仅在几年前,程序员要开发一款人脸识别应用,就必须精通算法的编写.但现在,随着成熟算法的对外开放,越来越多开发者只需专注于开发垂直行业的产品即可. 由调查机构发布的<中国AI产业地图研究> ...

  6. 如何阅读一本书——分析阅读Pre

    如何阅读一本书--分析阅读Pre 前情介绍 作者: 莫提默.艾德勒 查尔斯.范多伦 初版:1940年,一出版就是全美畅销书榜首一年多.钢铁侠Elon.Musk学过. 需要注意的句子: 成功的阅读牵涉到 ...

  7. 从包含10个无符号数的字节数组array中选出最小的一个数存于变量MIN中,并将该数以十进制形式显示出来。

    问题 从包含10个无符号数的字节数组array中选出最小的一个数存于变量MIN中,并将该数以十进制形式显示出来. 代码 data segment arrey db 0,1,2,4,6,5,7,9,8, ...

  8. PHP fgetss() 函数

    定义和用法 fgetss() 函数从打开的文件中返回一行,并过滤掉 HTML 和 PHP 标签. fgetss() 函数会在到达指定长度或读到文件末尾(EOF)时(以先到者为准),停止返回一个新行. ...

  9. P6222 「简单题」加强版 莫比乌斯反演 线性筛积性函数

    LINK:简单题 以前写过弱化版的 不过那个实现过于垃圾 少预处理了一个东西. 这里写一个实现比较精细了. 最后可推出式子:\(\sum_{T=1}^nsum(\frac{n}{T})\sum_{x| ...

  10. java动态代理——jvm指令集基本概念和方法字节码结构的进一步探究及proxy源码分析四

    前文地址 https://www.cnblogs.com/tera/p/13336627.html 本系列文章主要是博主在学习spring aop的过程中了解到其使用了java动态代理,本着究根问底的 ...