本章以用户登录成功写日志,总结前述知识点,这里采用以下AOP切面编程
整体业务流程分析
主要分为两大模块,“用户登录模块”,“日志模块”,两大模块相互独立,互不干扰,日志模块应当异步于登录模块执行
数据库设计
CREATE TABLE user (
id int(11) NOT NULL AUTO_INCREMENT COMMENT '用户id',
user_name varchar(255) NOT NULL COMMENT '用户名',
password varchar(255) NOT NULL COMMENT '密码',
create_time datetime DEFAULT NULL COMMENT '创建时间',
PRIMARY KEY (id),
UNIQUE KEY idx_user_name (user_name) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8 COMMENT='用户信息表';
CREATE TABLE sys_log (
id int(11) NOT NULL AUTO_INCREMENT,
user_id int(11) DEFAULT 0 COMMENT '用户id',
module varchar(255) DEFAULT NULL COMMENT '用户操作所属模块',
data varchar(5000) CHARACTER SET utf8mb4 DEFAULT NULL COMMENT '操作数据',
memo varchar(500) CHARACTER SET utf8mb4 DEFAULT NULL COMMENT '备注',
create_time datetime DEFAULT NULL COMMENT '创建时间',
PRIMARY KEY (id)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8 COMMENT='日志记录表';
交换机队列声明
/**用户登录成功写日志消息模型创建---------------------------------------------------------------------------------**/
//创建队列
@Bean(name = "loginQueue")
public Queue loginQueue(){
return new Queue("loginQueue",true);
}
//创建交换机
@Bean
public TopicExchange loginExchange(){
return new TopicExchange("loginExchange",true,false);
}
//创建绑定
@Bean
public Binding loginBinding(){
return BindingBuilder.bind(loginQueue()).to(loginExchange()).with("login-key");
}
注解
package com.learn.boot.log;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Log {
String value() default "";
String module() default "";
}
AOP 切面
这里可以直接将日志入库的,但是为了异步发送消息,所以选择发送消息到队列,由日志消费服务去处理日志入库
package com.learn.boot.log;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.learn.boot.model.KnowledgeInfo;
import com.learn.boot.model.SysLog;
import com.learn.boot.resultVo.ResultVo;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.*;
import org.slf4j.Logger;
import org.springframework.amqp.AmqpException;
import org.springframework.amqp.core.*;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.amqp.support.converter.AbstractJavaTypeMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.Date;
import java.util.Map;
@Aspect
@Component
public class LogAsPect {
private final static Logger log = org.slf4j.LoggerFactory.getLogger(LogAsPect.class);
@Autowired
private RabbitTemplate rabbitTemplate;
@Autowired
private ObjectMapper objectMapper;
//表示匹配带有自定义注解的方法
@Pointcut("@annotation(com.learn.boot.log.Log)")
public void pointcut() {
}
@Around(value = "pointcut()")
public Object around(ProceedingJoinPoint point) throws Throwable {
// 如果返回的是不正常接口状态
ResultVo resultVo = (ResultVo) point.proceed();
if (!"000".equals(resultVo.getCode())) {
return null;
}
HttpServletRequest request = null;
request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
log.info(request.getRequestURL().toString());
MethodSignature signature = (MethodSignature)point.getSignature();
String[] parameterNames = signature.getParameterNames();
Method method = signature.getMethod();
//
Log moudelLog = method.getAnnotation(Log.class);
String data = JSON.toJSONString(resultVo.getData());
Map<String,Object> paramMap = JSONObject.parseObject(data);
// 拿到返回参数
long beginTime = System.currentTimeMillis();
try {
log.info("开始记录操作日志");
SysLog sysLog = new SysLog();
sysLog.setUserId(Integer.valueOf(paramMap.get("userId").toString()));
sysLog.setCreateTime(new Date());
sysLog.setModule(moudelLog.module());
sysLog.setData(data.replaceAll("\"",""));
sysLog.setMemo(moudelLog.value());
Message message= MessageBuilder.withBody(objectMapper.writeValueAsBytes(sysLog))
.setDeliveryMode(MessageDeliveryMode.PERSISTENT)
.build();
// 发送信息
rabbitTemplate.convertAndSend("loginExchange", "login-key",message);
log.info("日志生产者发送对象消息{}",sysLog);
long endTime = System.currentTimeMillis();
log.info("记录操作日志完成,用时{}秒",endTime - beginTime);
} catch (Throwable e) {
}
return resultVo;
}
}
业务逻辑处理
@RequestMapping("/testLoginLog")
@Log(module = "登录模块",value = "登录登录成功")
public ResultVo testLoginLog(@RequestBody UserLoginDto dto, BindingResult result) {
if (result.hasErrors()) {
return ResultVo.error("传入参数异常");
}
try {
return userService.login(dto);
} catch (Exception e) {
return ResultVo.error("接口异常");
}
}
@Override
public ResultVo login(UserLoginDto dto) throws Exception {
// 根据用户名和密码查询用户实体记录
User user = userMapper.selectByUserNamePassword(dto.getUserName(), dto.getPassword());
// 如果用户不存在
if (user == null) {
return ResultVo.error("您的账号不存在");
}
// 表示数据表中存在该用户,并且密码是匹配的
//此时表示当前用户已经登录成功,需要对相应的参数进行赋值
dto.setUserId(user.getId());
//返回登录成功
return ResultVo.success("登录成功",dto);
}
package com.learn.boot.log;
import com.alibaba.fastjson.JSONObject;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.learn.boot.dto.UserLoginDto;
import com.learn.boot.model.SysLog;
import com.learn.boot.service.SysLogService;
import com.rabbitmq.client.Channel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.messaging.handler.annotation.Payload;
import org.springframework.stereotype.Component;
import java.io.IOException;
/**
* zlx
* 2020年9月5日16:22:18
* 系统日志服务消费者
*/
@Component
public class LogConsumer {
@Autowired
private SysLogService sysLogService;
@Autowired
private ObjectMapper objectMapper;
// 定义日志
private static final Logger log= LoggerFactory.getLogger(LogConsumer.class);
@RabbitListener(queues = "loginQueue")
public void consumeMsg(@Payload byte[] msg,Channel channel, Message message) throws Exception {
SysLog sysLog = objectMapper.readValue(msg, SysLog.class);
try {
log.info("系统日志记录-消费者-监听消费用户登录成功后的消息-内容: {}",sysLog);
// 调用日志记录服务,用于记录用户登录成功后相关登录信息入数据库
sysLogService.recordLog(sysLog);
// 手动ACK
channel.basicAck(message.getMessageProperties().getDeliveryTag(),true);
}catch (Exception e){
log.error("异常",e);
sysLogService.recordLog(sysLog);
log.error("系统日志记录-消费者-监听消费用户登录成功后的消息-发生异常:{} ",sysLog,e.fillInStackTrace());
channel.basicAck(message.getMessageProperties().getDeliveryTag(),false);
}
}
}
@Override
public void recordLog(SysLog sysLog) {
if (sysLog == null) {
return;
}
// 日志入库
sysLogMapper.insertSelective(sysLog);
}