文章目录本文参考EL-ADMIN 后台管理系统,学习相关AOP(Aspect-Oriented Programming:面向切面编程),并实现使用AOP进行日志管理。特别感谢该项目的源代码作者:elunez
- 一、准备阶段
- 二、编码阶段
- 三、使用阶段
SpringBoot中需要先引入aop依赖
com.baomidou mybatis-plus 3.4.1
org.springframework.boot spring-boot-starter-aop
org.lionsoul ip2region 1.7.2
nl.basjes.parse.useragent yauaa 5.23
创建一张sys_log的日志表
CREATE TABLE `sys_log` ( `log_id` bigint NOT NULL AUTO_INCREMENT COMMENT 'ID', `description` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL, `log_type` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL, `method` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL, `params` text CHARACTER SET utf8 COLLATE utf8_general_ci, `request_ip` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL, `time` bigint DEFAULT NULL, `username` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL, `address` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL, `browser` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL, `exception_detail` text CHARACTER SET utf8 COLLATE utf8_general_ci, `create_time` datetime DEFAULT NULL, PRIMARY KEY (`log_id`) USING BTREE, KEY `log_create_time_index` (`create_time`) USING BTREE, KEY `inx_log_type` (`log_type`) USING BTREE ) ENGINE=InnoDB AUTO_INCREMENT=3537 DEFAULT CHARSET=utf8mb3 COMMENT='系统日志';二、编码阶段
日志工具类
@Slf4j
public class LogUtils {
private static boolean ipLocal = false;
private static File file = null;
private static DbConfig config;
// IP归属地查询
public static final String IP_URL = "http://whois.pconline.com.cn/ipJson.jsp?ip=%s&json=true";
public static String getCityInfo(String ip) {
if (ipLocal) {
return getLocalCityInfo(ip);
} else {
return getHttpCityInfo(ip);
}
}
private static String getLocalCityInfo(String ip) {
try {
DataBlock dataBlock = new DbSearcher(config, file.getPath())
.binarySearch(ip);
String region = dataBlock.getRegion();
String address = region.replace("0|", "");
char symbol = '|';
if (address.charAt(address.length() - 1) == symbol) {
address = address.substring(0, address.length() - 1);
}
return address;
} catch (Exception e) {
log.error(e.getMessage(), e);
}
return "";
}
private static String getHttpCityInfo(String ip) {
String api = String.format(IP_URL, ip);
JSONObject object = JSONUtil.parseObj(HttpUtil.get(api));
return object.get("addr", String.class);
}
}
日志实体类
@Data
@EqualsAndHashCode(callSuper = false)
@TableName("sys_log")
public class LogDO {
private static final long serialVersionUID = 1L;
@TableId(value = "log_id", type = IdType.AUTO)
private Long id;
private String username;
private String description;
private String method;
private String params;
private String logType;
private String requestIp;
private String address;
private String browser;
private Long time;
private byte[] exceptionDetail;
private Timestamp createTime;
public LogDO(String logType, Long time) {
this.logType = logType;
this.time = time;
}
}
Serivce类
public interface LogService {
@Async
void save(String username, String browser, String ip, ProceedingJoinPoint joinPoint, LogDO log);
}
Service实现类
@Service
public class LogServiceImpl implements LogService {
@Autowired
private LogMapper logMapper;
@Override
public void save(String username, String browser, String ip, ProceedingJoinPoint joinPoint, LogDO log) {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
MyLog aopLog = method.getAnnotation(MyLog.class);
// 方法路径
String methodName = joinPoint.getTarget().getClass().getName() + "." + signature.getName() + "()";
// 描述
if (log != null) {
log.setDescription(aopLog.value());
}
assert log != null;
log.setRequestIp(ip);
log.setAddress(LogUtils.getCityInfo(log.getRequestIp()));
log.setMethod(methodName);
log.setUsername(username);
log.setParams(getParameter(method, joinPoint.getArgs()));
log.setBrowser(browser);
logMapper.insert(log);
}
private String getParameter(Method method, Object[] args) {
List
Mapper接口(此处使用了Mybatis-plus)
@Repository public interface LogMapper extends baseMapper{ }
创建annotation包,创建Log注解类
public @interface MyLog {
String value() default "";
}
创建aspect包,创建LogAspect的日志切面类,并加上@Aspect注解标记为切面
@Component
@Aspect
@Slf4j
public class LogAspect {
ThreadLocal currentTime = new ThreadLocal<>();
@Autowired
private LogService logService;
}
在LogAspect切面类中,需要补充以下方法:
@Pointcut("@annotation(com.xxx.MyLog)")
public void logPointcut(){
// 无方法体,主要在@Pointcut中体现@Log注解类的所在位置
}
@Around("logPointcut()")
public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable {
Object result;
currentTime.set(System.currentTimeMillis());
result = joinPoint.proceed();
LogDO logDO = new LogDO("INFO", System.currentTimeMillis() - currentTime.get());
currentTime.remove();
HttpServletRequest request = getHttpServletRequest();
logService.save("用户姓名xxx", LogUtils.getBrowser(request), LogUtils.getIp(request),joinPoint, logDO);
return result;
}
@AfterThrowing(pointcut = "logPointcut()", throwing = "e")
public void logAfterThrowing(JoinPoint joinPoint, Throwable e) {
LogDO log = new LogDO("ERROR",System.currentTimeMillis() - currentTime.get());
currentTime.remove();
log.setExceptionDetail(getStackTrace(e).getBytes());
HttpServletRequest request = getHttpServletRequest();
logService.save("用户姓名xxx", LogUtils.getBrowser(request), LogUtils.getIp(request), (ProceedingJoinPoint)joinPoint, log);
}
public static HttpServletRequest getHttpServletRequest() {
return ((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest();
}
public static String getStackTrace(Throwable throwable){
StringWriter sw = new StringWriter();
try (PrintWriter pw = new PrintWriter(sw)) {
throwable.printStackTrace(pw);
return sw.toString();
}
}
三、使用阶段
在对应的Controller类的方法上,加上自定义的@MyLog注解,并写上该方法的简短说明。
访问/test/go,即可在sys_log表中看到日志记录的信息。
@RestController
@RequestMapping("/test")
public class TestController {
@MyLog("测试方法")
@GetMapping("/go")
String go(){
return "日志记录成功!";
}
}
后续若有时间将会实现一个简单的切面日志,并开放代码。
参考资料:
-
前后端分离的后台管理系统 特别感谢该项目的源代码作者:elunez
-
一份热乎的 SpringBoot 前后端分离后台管理系统分析!分模块开发、RBAC权限控制



