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註解便可
咱們能夠本身先造一個異常來測試異常的收集
啓動項目,正常訪問測試便可,會自動收集日誌。