SpringSecurity权限管理系统实战—1、项目简介和开发环境准备
SpringSecurity权限管理系统实战—2、日志、接口文档等实现
SpringSecurity权限管理系统实战—3、主要页面及接口实现
SpringSecurity权限管理系统实战—4、整合SpringSecurity(上)
SpringSecurity权限管理系统实战—5、整合SpringSecurity(下)
SpringSecurity权限管理系统实战—6、SpringSecurity整合jwt
SpringSecurity权限管理系统实战—7、处理一些问题
SpringSecurity权限管理系统实战—8、AOP 记录用户日志、异常日志html
日志功能在二的时候其实简单实现了一下,可是有时咱们须要对一些重要功能操做记录日志,或是在操做时发生异常,须要记录异常日志。可是以前每次发生异常要定位缘由咱们都要到服务器去查询日志才能找到,或许是搭建一个日志收集系统(可是本项目中暂不考虑)。那么咱们能够专门作个功能来记录用户操做日志和异常日志,在把日志存入数据库,方便查询。前端
相应字段都有注释,很好理解,用户日志、异常日志都存放在这一张表中,经过type来区分,固然也能够拆分红两张表。java
<!--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>
SecurityUtilsgit
/** * @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(); } }
LogUtilsgithub
/** * @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(); } } }
RequestHolderweb
/** * @author codermy * @createTime 2020/8/4 */ public class RequestHolder { /** * 获取HttpServletRequest对象 * @return */ public static HttpServletRequest getHttpServletRequest() { return ((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest(); } }
这个部分省略,没有什么技术含量,根据数据库来就行spring
/** * @author codermy * @createTime 2020/8/4 */ @Target(ElementType.METHOD)//注解放置的目标位置,METHOD是可注解在方法级别上 @Retention(RetentionPolicy.RUNTIME)//注解在哪一个阶段执行 public @interface MyLog { String value() default ""; }
关于java自定义注解能够看看这篇文章数据库
这其实很好理解,就是咱们学习spring时,aop的几种通知。api
/** * @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("删除成功"); } }
相应的前端页面就不贴出来了,有须要能够在个人gitee和github中获取
咱们只须要在相应的接口上添加上@MyLog注解便可
咱们能够本身先造一个异常来测试异常的收集
启动项目,正常访问测试便可,会自动收集日志。