一、配置事务
为了避免在项目运行过程中,代码出现异常导致数据错误。我们需要在项目的服务层配置事务。事务即一段代码要么同时成功,要么同时失败。
SpringBoot
默认开启@Transactional
注解,Spring
容器会自动扫描@Transactional
修饰的方法和类。当注解在类上的时候意味着此类的所有public
方法都是开启事务的。被注解的方法都成为一个事务整体,同一个事务内共享一个数据库连接,所有操作同时发生。如果在事务内部执行过程中发生了异常,则事务整体会自动回滚。我们在service
层的所有类上方添加@Transactional
注解即可配置事务:
具体代码示例:
@Service
@Transactional
public class AdminService {
}
二、统一异常处理
当代码抛出异常后,网页上展示的500错误码比较难看,我们可以对项目进行统一异常处理,跳转到用户体检较好的500页面。
在SpringBoot
项目中,当程序出现了异常,SpringBoot
会使用自带的BasicErrorController
对象处理异常。该处理器会默认跳转到/resources/templates/error.html
页面,我们只需编写error.html
页面即可进行统一异常处理:
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>首页</title>
<th:block th:replace="/backstage/common_resources::common_css"/>
<th:block th:replace="/backstage/common_resources::common_js"/>
</head>
<body class="hold-transition skin-purple sidebar-mini">
<div class="error-page">
<h2 class="headline text-red">500</h2>
<div class="error-content">
<h3><i class="fa fa-warning text-red"></i> Oops! 页面程序有错误.</h3>
</div>
</div>
</body>
</html>
在引入页面之前需要引入Thymeleaf依赖
三、记录日志
在后台代码运行的过程中,我们要对每一次操作进行日志记录,一方面通过日志可以发现代码的缺陷,另一方面可以追踪内部人员的操作记录。
SpringBoot
默认使用Logback
组件作为日志管理,首先在/resources
下添加Logback
配置文件logback.xml
<?xml version="1.0" encoding="UTF-8" ?>
<configuration>
<!--定义日志文件的存储地址-->
<property name="LOG_HOME" value="${catalina.base}/logs/"/>
<!-- 控制台输出 -->
<appender name="Stdout" class="ch.qos.logback.core.ConsoleAppender">
<!-- 日志输出编码 -->
<layout class="ch.qos.logback.classic.PatternLayout">
<!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符-->
<pattern>%d{MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n
</pattern>
</layout>
</appender>
<!-- 按照每天生成日志文件 -->
<appender name="RollingFile" class="ch.qos.logback.core.rolling.RollingFileAppender">
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!--日志文件输出的文件名-->
<FileNamePattern>${LOG_HOME}/server.%d{yy99-MM-dd}.log</FileNamePattern>
<MaxHistory>30</MaxHistory>
</rollingPolicy>
<layout class="ch.qos.logback.classic.PatternLayout">
<!--格式化输出:%d表示时间,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符-->
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n
</pattern>
</layout>
<!--日志文件最大的大小-->
<triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
<MaxFileSize>10MB</MaxFileSize>
</triggeringPolicy>
</appender>
<!-- 日志输出级别 -->
<root level="info">
<appender-ref ref="Stdout"/>
<appender-ref ref="RollingFile"/>
</root>
</configuration>
此时我们可以在代码中使用Logger对象打印日志,但如果每个方法都添加日志打印的代码,需要维护大量代码,最好的方式是使用AOP技术,将所有的控制器方法作为切点,在方法执行完成后自动执行打印日志的代码。
①编写日志实体类
@Data
public class Log {
private String url; // 访问的路径
private Date visitTime; // 访问时间
private String username; // 访问者
private String ip; // 访问ip
private int executionTime; // 访问时长
private String exceptionMessage; // 异常信息
}
②在pom中添加AOP依赖
<!-- AOP -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
③编写日志AOP类
package com.zhang.travel.aop;
import com.zhang.travel.bean.Log;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.User;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
import java.util.Date;
@Component
@Aspect
public class LogAop {
@Autowired
private HttpServletRequest request;
@Autowired
private final static Logger logger = LoggerFactory.getLogger(LogAop.class);
@Pointcut("execution(* com.zhang.travel.controller.backstage.*.*(..))")
public void pointCut(){}
@Before("pointCut()")
public void doBefore(JoinPoint joinPoint) {
// 记录访问时间
Date date = new Date();
request.setAttribute("visitTime",date);
}
@After("pointCut()")
public void doAfter(){
Log log = new Log();
Date visitTime = (Date) request.getAttribute("visitTime"); // 访问时间
Date now = new Date();
int executionTime = (int)(now.getTime() - visitTime.getTime()); // 访问时长
String ip = request.getRemoteAddr(); // 访问ip
String url = request.getRequestURI();// 访问路径
// 拿到security中的User对象
Object user = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
if (user instanceof User){
String username = ((User)user).getUsername();
log.setUsername(username);
}
log.setExecutionTime(executionTime);
log.setUrl(url);
log.setIp(ip);
log.setVisitTime(visitTime);
logger.info(log.toString());
}
@AfterThrowing(pointcut = "pointCut()",throwing = "ex")
public void afterThrowing(Throwable ex){
Log log = new Log();
Date visitTime = (Date) request.getAttribute("visitTime"); // 访问时间
Date now = new Date();
int executionTime = (int) (now.getTime() - visitTime.getTime()); // 访问时长
String ip = request.getRemoteAddr(); // 访问ip
String url = request.getRequestURI(); // 访问路径
// 拿到Security中的User对象
Object user = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
if (user instanceof User){
String username = ((User) user).getUsername();
log.setUsername(username);
}
log.setExecutionTime(executionTime);
log.setUrl(url);
log.setIp(ip);
log.setVisitTime(visitTime);
// 异常信息
String exMessage = ex.getMessage();
log.setExceptionMessage(exMessage);
logger.info(log.toString());
}
}