AOP自定义注解记录SQL日志
前言
本文使用aop自定义注解记录日志(主要记录sql语句)入库
一、如何使用
1.创建一个LogTestController
@GetMapping("recordOperateLogTest")
@ApiOperation (value = "测试切面日志记录效果:处理订单信息")
public Map<String, Object> recordOperateLogTest(){
Map<String, Object> resMap = new HashMap<String, Object>();
try
{
//调用service
logTestService.executeOrderInfo();
resMap.put("result", "successful" );
}
catch (Exception e)
{
e.printStackTrace();
resMap.put("result", "failure" );
}
return resMap;
}
2.创建service
注意:如果要实现切面必须动态代理,拿到bean或者通过Controller调service。
@Transactional (rollbackFor = Exception.class)
@Override
public void executeOrderInfo () throws Exception
{
//新增的数据
OrderEntity dbEntity = new OrderEntity ();
dbEntity.setOrderNo ("订单编号01");
dbEntity.setOrderName ("葡萄糖注射液订单");
//修改数据
OrderEntity updateEntity = new OrderEntity ();
updateEntity.setOrderNo ("订单编号400");
updateEntity.setOrderName ("头孢拉定订单");
//动态代理切面
LogTestServiceImpl logTestService = SpringContextUtil.getBean (LogTestServiceImpl.class);
List<Integer> list= new ArrayList<> ();
logTestService.addAndUpdate (dbEntity, updateEntity, new UpdateWrapper<OrderEntity>(), 14L, (byte) 1,list,new TSsDatasource());
logTestService.update (dbEntity,1L);
//测试事务回滚
//throw new Exception ();
}
3.service中调用新增或者删除方法
注意:如果要实现切面必须动态代理,拿到bean或者通过Controller调service。
mapperId为mapper.xml里的方法
方法的形参要与mapper方法里面的方法形参一致,有@Param注解用注解,没有就用名字就好
/**
* 新增和更新用的mybatisPlus
* 参数是对应mapper方法的参数,取名:有@Param注解的取注解里面的值,没有注解的取和mapper方法里参数名字一样
* @param entity
* @param et
* @param ew
*/
@OperateLogs ({
@OperateLog (model = 10, childModel = 1001, type = 0 , mapperId = "com.uindata.dao.ss.TSsProjectRuleMapper.findRuleTimeAndName"),
@OperateLog (model = 10, childModel = 1001, type = 0 , mapperId = "com.uindata.dao.ss.TSsProjectRuleMapper.findProjectRule"),
@OperateLog (model = 10, childModel = 1001, type = 1 , mapperId = "com.uindata.dao.test.OrderMapper.insert"),
@OperateLog (model = 10, childModel = 1001, type = 2 , mapperId = "com.uindata.dao.test.OrderMapper.update"),
@OperateLog (model = 10, childModel = 1001, type = 3 , mapperId = "com.uindata.dao.test.OrderMapper.deleteById"),
@OperateLog (model = 10, childModel = 1001, type = 1 , mapperId = "com.uindata.dao.ss.TSsDatasourceMapper.myInsertDatasource"),
@OperateLog (model = 10, childModel = 1001, type = 3 , mapperId = "com.uindata.dao.test.OrderMapper.deleteByIds")
})
@Transactional (rollbackFor = Exception.class)
public void addAndUpdate (OrderEntity entity, OrderEntity et, UpdateWrapper<OrderEntity> ew,Long id,Byte type,List<Integer> ids,TSsDatasource tSsDatasource)
{
//自己写的查询
tSsProjectRuleMapper.findRuleTimeAndName (id,type);
tSsProjectRuleMapper.findProjectRule (id);
//MP的新增
orderMapper.insert (entity);
//MP的修改
ew.eq ("orderNo", "400");
orderMapper.update (et, ew);
//MP的删除
orderMapper.deleteById (id);
//自己写的新增
tSsDatasource.setDbname ("xxxx");
tSsDatasource.setDbusername ("用户名");
tSsDatasource.setDbpassword ("密码");
//自己写的删除
ids.add (1);
ids.add (4);
orderMapper.deleteByIds (ids);
}
/**
* 自己写的修改
* @param entity
* @param id
*/
@OperateLog (model = 10, childModel = 1001, type = 2, mapperId = "com.uindata.dao.test.OrderMapper.updateById")
@Transactional (rollbackFor = Exception.class)
public void update (OrderEntity entity, Long id)
{
entity.setOrderNo ("mapper.xml订单号");
entity.setOrderName (null);
orderMapper.updateById (id, entity);
}
4.注解类
@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface OperateLogs
{
OperateLog[] value();
}
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface OperateLog
{
//模块
int model ();
//子模块
int childModel ();
//类型
int type ();
//mapper id
String mapperId ();
}
5.切面类
@Aspect
@Component
@Slf4j
public class OperateLogAspect
{
@Pointcut ("@annotation(com.uindata.common.annotation.OperateLogs)||@annotation(com.uindata.common.annotation.OperateLog)")
public void logPointCut ()
{
}
@AfterReturning (pointcut = "logPointCut()")
public void doAfter (JoinPoint joinPoint) throws Exception
{
try
{
//获取方法名
String methodName = joinPoint.getSignature ().getName ();
//获取方法
Method method = currentMethod (joinPoint, methodName);
//获取方法上OperateLogs注解
OperateLogs operateLogs = method.getAnnotation (OperateLogs.class);
//获取所有OperateLog注解
OperateLog[] operateLog=null;
//如果是单个注解@OperateLog
if(Objects.isNull (operateLogs)){
OperateLog operateItem=method.getAnnotation (OperateLog.class);
operateLog=new OperateLog[1];
operateLog[0]=operateItem;
}
//否则是多个注解的@OperateLogs
else {
operateLog = operateLogs.value ();
}
// ================================ 收集形参 ================================
Map<String, Object> allParams = getArgs (joinPoint);
for (OperateLog log : operateLog)
{
//定义模块,子模块,类型,实体类型,mapperid
int logModel = log.model ();
int logChildModel = log.childModel ();
int logType = log.type ();
String logMapperId = log.mapperId ();
// ================================ 获取执行sql ================================
String realSql = getSql (allParams, logMapperId);
// ================================ 记录操作日志 ================================
tLogOperateService.recordOperateLog (joinPoint, methodName, logModel, logChildModel, logType, realSql);
}
}catch (Exception e){
log.error ("日志记录异常",e.getMessage ());
throw new UindataException ("日志记录异常");
}
}
/**
* 获取方法参数列表
*
* @param mapperId
* @return
* @throws ClassNotFoundException
*/
private Map<String, Object> getParameter (String mapperId, Map<String, Object> allParams)
throws ClassNotFoundException
{
//从分隔符最后一次出现的位置向前截取,截取到mapper类
String mapper = StringUtils.substringBeforeLast (mapperId, ".");
//从分隔符最后一次出现的位置向后截取,截取到mapper里面的方法
String method = StringUtils.substringAfterLast (mapperId, ".");
//通过反射获取到类
Class<?> mapperClass = Class.forName (mapper);
//获取类中所有的方法
Method[] methods = mapperClass.getMethods ();
Map<String, Object> parameterMap = new HashMap<> ();
for (Method methodItem : methods)
{
//判断是否是指定方法
if (methodItem.getName ().equals (method))
{
// 获取方法的所有参数
Parameter[] parameters = methodItem.getParameters ();
for (Parameter parameter : parameters)
{
// 判断是否存在注解
if (parameter.isAnnotationPresent (Param.class))
{
String annotationVlue = parameter.getAnnotation (Param.class).value ();
parameterMap.put (annotationVlue, allParams.get (annotationVlue));
}
else
{
parameterMap.put (parameter.getName (), allParams.get (parameter.getName ()));
}
}
break;
}
}
return parameterMap;
}
/**
* 获取实际sql语句
*
* @param allParams
* @param logMapperId
* @return
* @throws IllegalAccessException
*/
private String getSql (Map<String, Object> allParams, String logMapperId) throws ClassNotFoundException
{
Map<String, Object> map = getParameter (logMapperId, allParams);
Configuration con = sqlSessionFactory.getConfiguration ();
MappedStatement ms = con.getMappedStatement (logMapperId);
BoundSql boundSql = null;
//判断方法为一个参数,并且sql的执行语句是insert,针对新增的操作
if (map.size () == BaseConstants.NUM.ONE &&
StringUtils.containsIgnoreCase (ms.getBoundSql (map).getSql (), "insert"))
{
for (String key : map.keySet ())
{
//参数就是map的一个值
boundSql = ms.getBoundSql (map.get (key));
}
}
else
{
boundSql = ms.getBoundSql (map);
}
return showSql (ms.getConfiguration (), boundSql);
}
/**
* SQL语句进行?的替换
*
* @param configuration
* @param boundSql
* @return
*/
public static String showSql (Configuration configuration, BoundSql boundSql)
{
// 获取参数
Object parameterObject = boundSql.getParameterObject ();
List<ParameterMapping> parameterMappings = boundSql
.getParameterMappings ();
// sql语句中多个空格都用一个空格代替
String sql = boundSql.getSql ().replaceAll ("[\\s]+", " ");
if (CollectionUtils.isNotEmpty (parameterMappings) && parameterObject != null)
{
// 获取类型处理器注册器,类型处理器的功能是进行java类型和数据库类型的转换<br>
// 如果根据parameterObject.getClass()可以找到对应的类型,则替换
TypeHandlerRegistry typeHandlerRegistry = configuration
.getTypeHandlerRegistry ();
if (typeHandlerRegistry.hasTypeHandler (parameterObject.getClass ()))
{
sql = sql.replaceFirst ("\\?", Matcher.quoteReplacement (getParameterValue (parameterObject)));
}
else
{
// MetaObject主要是封装了originalObject对象,提供了get和set的方法用于获取和设置originalObject的属性值,主要支持对JavaBean、Collection、Map三种类型对象的操作
MetaObject metaObject = configuration.newMetaObject (
parameterObject);
for (ParameterMapping parameterMapping : parameterMappings)
{
String propertyName = parameterMapping.getProperty ();
if (metaObject.hasGetter (propertyName))
{
Object obj = metaObject.getValue (propertyName);
sql = sql.replaceFirst ("\\?", Matcher.quoteReplacement (getParameterValue (obj)));
}
else if (boundSql.hasAdditionalParameter (propertyName))
{
// 该分支是动态sql
Object obj = boundSql.getAdditionalParameter (propertyName);
sql = sql.replaceFirst ("\\?", Matcher.quoteReplacement (getParameterValue (obj)));
}
else
{ //打印出缺失,提醒该参数缺失并防止错位
sql = sql.replaceFirst ("\\?", "缺失");
}
}
}
}
return sql;
}
/**
* 如果参数是String,则添加单引号, 如果是日期,则转换为时间格式器并加单引号; 对参数是null和不是null的情况作了处理<br>
*
* @param obj
* @return
*/
private static String getParameterValue (Object obj)
{
String value = null;
if (obj instanceof String)
{
value = "'" + obj.toString () + "'";
}
else if (obj instanceof Date)
{
DateFormat formatter = DateFormat
.getDateTimeInstance (DateFormat.DEFAULT, DateFormat.DEFAULT, Locale.CHINA);
value = "'" + formatter.format (new Date ()) + "'";
}
else
{
if (obj != null)
{
value = obj.toString ();
}
else
{
value = "";
}
}
return value;
}
/**
* @Description: 收集形参
* @auther: xiaoyi
* @date: 11:52 2020/10/22
* @param: [joinPoint]
* @return: java.util.Map
*/
private Map<String, Object> getArgs (JoinPoint joinPoint)
{
Map<String, Object> allParams = new HashMap ();
Object[] paramValues = joinPoint.getArgs ();
String[] paramNames = ((CodeSignature) joinPoint.getSignature ()).getParameterNames ();
for (int i = 0; i < paramNames.length; i++)
{
allParams.put (paramNames[i], paramValues[i]);
}
return allParams;
}
/**
* 获取当前执行的方法
*
* @param joinPoint 连接点
* @param methodName 方法名称
* @return 方法
*/
private Method currentMethod (JoinPoint joinPoint, String methodName)
{
/**
* 获取目标类的所有方法,找到当前要执行的方法
*/
Method[] methods = joinPoint.getTarget ().getClass ().getMethods ();
Method resultMethod = null;
for (Method method : methods)
{
if (method.getName ().equals (methodName))
{
resultMethod = method;
break;
}
}
return resultMethod;
}
@Autowired
private TLogOperateService tLogOperateService;
@Autowired
SqlSessionFactory sqlSessionFactory;
}
6.记录操作日志
@Override
public void recordOperateLog(JoinPoint joinPoint, String methodName, Integer model, Integer childModel, Integer type, String sql) throws Exception
{
// 获取request
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
// ======================= insert log to table =======================
TLogOperate tLogOperate = new TLogOperate();
tLogOperate.setId(null);
tLogOperate.setModel(model);
tLogOperate.setChildModel(childModel);
tLogOperate.setType(type.byteValue());
tLogOperate.setSqlDetaile(sql);
//tLogOperate.setDataDetaile();
tLogOperate.setOperationTime(new Date());
tLogOperate.setResultType(BaseConstants.ResultType.SUCCESSFULLY);
tLogOperate.setDescription("");
tLogOperate.setDataDetaile("");
tLogOperate.setIp(IPUtil.getIpAddress(request));
tLogOperate.setCreateTime(new Date());
tLogOperate.setCreateUser(sessionUtil.getUserName());
tLogOperate.setUpdateTime(new Date());
tLogOperate.setUpdateUser(sessionUtil.getUserName());
tLogOperateMapper.insert(tLogOperate);
}