Java自定义日志注解

项目中必不可少的业务操作日志,为了减少代码侵入以及公共参数提取,写了一个自定义注解,供大家参考。

@Retention(RetentionPolicy.RUNTIME)
@Target(value = {ElementType.TYPE, ElementType.METHOD})
@Documented
public @interface DocOperateLog {
	String value() default "";

	PermissionEnum operateKey();
}
PermissionEnum为业务权限点枚举类,与操作行为契合
@Getter
public enum PermissionEnum {
…………
	WEB_YIQIANFA_BAOCUNANDPUBLISH(712, "web.signed.baocunandpublish", "保存并发布"),
…………
	public static PermissionEnum getByOperateKey(String operateKey) {
		for (PermissionEnum operateKeyEnum : values()) {
			if (operateKeyEnum.getOperateKey().equals(operateKey)) {
				return operateKeyEnum;
			}
		}
		return null;
	}
}
切面处理逻辑
@Aspect
@Component
@RequiredArgsConstructor
@Slf4j
public class DocOperateLogAspect {

	private final RemoteApprovalWorkflowService remoteApprovalWorkflowService;
	private final HttpServletRequest request;
	//定义切点
	@Pointcut("@within(com.tmzh.mediacube.common.bussiness.log.annotation.DocOperateLog) || @annotation(com.tmzh.mediacube.common.bussiness.log.annotation.DocOperateLog)")
	public void pointcut() {
	}

	//定义切面
	@Around(" pointcut() && @annotation(logPlatForm) ")
	public Object around(ProceedingJoinPoint joinPoint, DocOperateLog logPlatForm) throws Throwable {
		MethodSignature signature = (MethodSignature) joinPoint.getSignature();
		//获取切入方法的对象
		Method method = signature.getMethod();
		//获取参数值
		Object[] args = joinPoint.getArgs();
		//参数名进行遍历
		//对所有参数中的platform参数进行的拦截,如果参数是空串,设置默认值。
		for (Object arg : args) {
			// 获取该对象的Class
			Class objClass = arg.getClass();
			dealParentClass(objClass, logPlatForm, arg);
		}
		//处理ip和设备类型
		//获取Ip地址
		String ipAddress = WebUtils.getIpAddr();
		//获取UA
		String UA = WebUtils.getUserAgent();
		LogUtils.setThreadLocal(new LogAspectDTO().setIpAddress(ipAddress).setUA(UA));
		//执行程序,带上修改过的参数
		try {
			Object proceed = joinPoint.proceed(args);
			return proceed;
		} finally {
			LogUtils.clearThreadLocal();
		}

	}

	private void dealParentClass(Class objClass, DocOperateLog logPlatForm, Object arg) throws IllegalAccessException {
		Class superclass = objClass;
		while (null != superclass && !superclass.getName().contains("DocBaseCmd")) {
			superclass = superclass.getSuperclass();
		}
		//处理空指针问题
		if (null == superclass) {
			return;
		}
		// 获取所有的属性数组
		Field[] fields = superclass.getDeclaredFields();
		Integer approvalLevel = 0;
		String approvalLevelName = null;
		PermissionEnum operateKey;
		Map<String, Field> fieldMap = Arrays.stream(fields)
				.collect(Collectors.toMap(Field::getName, field -> {
					field.setAccessible(true);
					return field;
				}));
		Long siteId = null;
		if (fieldMap.containsKey("siteId")) {
			siteId = (Long) fieldMap.get("siteId").get(arg);
		}
		//待审层额外处理
		if (fieldMap.containsKey("approvalLevel") && !ObjectUtils.isEmpty(fieldMap.get("approvalLevel").get(arg))) {
			approvalLevel = (Integer) fieldMap.get("approvalLevel").get(arg);
			if (approvalLevel > 0) {
				//如果大于0,说明是待审层
				try {
					if (siteId != null) {
						R<String> r = remoteApprovalWorkflowService.getApprovalLevelName(siteId, approvalLevel);
						approvalLevelName = r.getData();
					} else {
						log.warn("送审相关操作异常,operateKey:{},platform:{}", fieldMap.get("operateKey"), fieldMap.get("platform"));
						approvalLevelName = "待审";
					}
				} catch (Exception e) {
					approvalLevelName = "待审";
				}
			}
		}
		//处理平台
		if (fieldMap.containsKey("platform")) {
			Field platformField = fieldMap.get("platform");
			platformField.setAccessible(true);
			String platform = StringUtils.isEmpty(approvalLevelName) ? logPlatForm.value() : approvalLevelName;
			platformField.set(arg, platform);
		}
		//处理操作key
		if (fieldMap.containsKey("operateKey")) {
			Field operateKeyField = fieldMap.get("operateKey");
			operateKeyField.setAccessible(true);
			operateKey = approvalLevel > 0 ? PermissionEnum.getByOperateKey(logPlatForm.operateKey().getOperateKey().replace(".unaudited1", MessageFormat.format(".unaudited{0}", approvalLevel))) : logPlatForm.operateKey();
			operateKeyField.set(arg, operateKey);
		}

	}


}
业务场景需要记录稿件排序时的操作,使用案例:
	@ApiOperation("稿件排序(已签发)")
	@PostMapping("/sort")
	@DocOperateLog(value = "已签发", operateKey = WEB_YIQIANFA_GAOJIANPAIXU)
	public R sort(@RequestBody SortDocCmd cmd){
		websiteDocService.sortDoc(cmd);
		return R.ok();
	}
通过注解赋值指定渠道,以及具体权限位,再构造日志体的时候可以直接获取
public void execute(SortDocCmd sortWebsiteDocCmd) {
		ChannelDoc channelDoc = channelDocRepository.getById(sortWebsiteDocCmd.getChannelDocId());

		//如果是拖动排序就需要手动将position值减现已置顶的数量
		if (sortWebsiteDocCmd.getOperateType() != null && sortWebsiteDocCmd.getOperateType() == 2) {
			//查询置顶数量
			Long topCount = channelDocRepository.cntTopDocByChannelId(sortWebsiteDocCmd.getChannelId());
			sortWebsiteDocCmd.setPosition(sortWebsiteDocCmd.getPosition() - topCount.intValue());
		}

		Channel channel = channelRepository.getByChannelId(channelDoc.getChannelId());
		//不变更操作人
		channelDoc.setUpdateByManual(channelDoc.getUpdateBy());
		Long cntTop = channelDocRepository.cntTopDocByChannelId(sortWebsiteDocCmd.getChannelId()) + 1;
		if (DEFAULT_TOP_ORDER > sortWebsiteDocCmd.getPosition() || sortWebsiteDocCmd.getPosition() == 1){
			LocalDateTime now = LocalDateTime.now();
			channelDoc.setDocOrder(channelDocRepository.generateDocOrder(sortWebsiteDocCmd.getChannelId()));
			channelDoc.setRelTime(now);
			channelDocRepository.updateById(channelDoc);
			extensionExecutor.executeVoid(PublishExtPt.class, BusinessType.valueOf(WEBSITE, COLUMN_PUBLISH),
					executor -> executor.publish(channel));

			DocOperateLogDTO docOperateLogDTO = new DocOperateLogDTO()
					.setDocId(channelDoc.getDocId()).setCreateTimeManual(now).setUpdateTimeManual(now)
					.setSrcChannelId(sortWebsiteDocCmd.getChannelId()).setOperateKey(sortWebsiteDocCmd.getOperateKey().getOperateKey())
					.setPlatform(sortWebsiteDocCmd.getPlatform()).setSiteId(sortWebsiteDocCmd.getSiteId());
			docOperateLogDomainService.saveLog(docOperateLogDTO, SiteMediaTypeEnum.WEB_SITE);
			return;
		}

		ChannelDoc targetPosition = channelDocRepository.getSortPosition(sortWebsiteDocCmd.getChannelId(),
				sortWebsiteDocCmd.getPosition());

	
			//如果当前位置比目标位置小, 那么就向上排
			
			//如果是向上则查询出来区间所有的数据, 区间的数据需要降序批量-1
			
			//如果是向下排序, 区间数据需要升序批量+1
			
		//构造日志
		LocalDateTime now = LocalDateTime.now();
		DocOperateLogDTO docOperateLogDTO = new DocOperateLogDTO()
				.setDocId(channelDoc.getDocId()).setCreateTimeManual(now).setUpdateTimeManual(now)
				.setSrcChannelId(sortWebsiteDocCmd.getChannelId()).setOperateKey(sortWebsiteDocCmd.getOperateKey().getOperateKey())
				.setPlatform(sortWebsiteDocCmd.getPlatform()).setSiteId(sortWebsiteDocCmd.getSiteId());
		docOperateLogDomainService.saveLog(docOperateLogDTO, SiteMediaTypeEnum.WEB_SITE);
	}
### Java 自定义注解实现接口日志记录 为了使Java应用程序中的日志记录更简洁高效,可以利用自定义注解来标记哪些方法需要记录日志。下面展示了一个完整的例子,说明如何创建一个名为`AuditLog`的自定义注解以及相应的AOP配置来进行日志记录。 #### 创建自定义注解 `AuditLog` 首先定义一个新的注解类型,在这里命名为`AuditLog`,该注解可用于标注方法,并携带一些额外的信息,例如操作描述和日志级别: ```java 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 AuditLog { String description() default ""; LogTypeEnum type() default LogTypeEnum.OTHER; } ``` 其中`LogTypeEnum`是一个枚举类,用来表示不同的日志类别[^2]。 #### 编写 AOP 切面逻辑 接着编写一个切面(AOP),当带有上述注解的方法被执行时触发日志记录行为。这通常涉及到Spring框架的支持,因为其提供了方便的方式来定义切入点表达式和通知(advice)。 ```java import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.springframework.stereotype.Component; @Component @Aspect public class LoggingAspect { @Around("@annotation(auditLog)") public Object recordSysLog(ProceedingJoinPoint joinPoint, AuditLog auditLog) throws Throwable { // 构造要保存的日志实体对象 UserLogRecordDO logRecord = new UserLogRecordDO(); try { // 设置日志属性... // 执行目标方法 Object result = joinPoint.proceed(); // 处理返回的结果... return result; } catch (Throwable e){ // 如果发生异常,则记录错误详情 throw e; } finally { // 将构建好的日志信息存入数据库或其他存储介质中 saveToDatabase(logRecord); } } private void saveToDatabase(UserLogRecordDO logRecord){ // 数据库持久化逻辑... } } ``` 这段代码展示了如何通过环绕通知(`@Around`)拦截被`AuditLog`修饰的方法调用,并在其前后执行特定的任务——即准备日志条目、实际调用原函数、处理任何可能出现的异常情况最后将收集的数据提交给后台系统进行长期保存[^3]。 #### 应用场景实例 假设有一个服务层接口`UserService`及其默认实现如下所示: ```java @Service public class UserServiceImpl implements UserService { @Override @AuditLog(description="登录",type=LogTypeEnum.LOGIN) public boolean login(String username, String password){ System.out.println("User "+username+" logged in."); return true; } ... } ``` 每当有客户端请求到达`login()`方法时,就会自动激活之前设置好的AOP机制完成整个过程的日志跟踪工作。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

里丶夜行人

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值