网站建设在windos的设置,刚做的网站怎么在百度上能搜到,移动端网站建设费用,win8.1 做网站服务器文章目录 前言正文一、项目结构介绍二、核心类2.1 核心注解2.1.1 CLog 日志注解2.1.2 ProcessorBean 处理器bean 2.2 切面类2.3 自定义线程池2.4 工具类2.4.1 管理者工具类 2.5 测试2.5.1 订单创建处理器2.5.2 订单管理者2.5.3 订单控制器2.5.4 测试报文2.5.5 测试结果 附录1、… 文章目录 前言正文一、项目结构介绍二、核心类2.1 核心注解2.1.1 CLog 日志注解2.1.2 ProcessorBean 处理器bean 2.2 切面类2.3 自定义线程池2.4 工具类2.4.1 管理者工具类 2.5 测试2.5.1 订单创建处理器2.5.2 订单管理者2.5.3 订单控制器2.5.4 测试报文2.5.5 测试结果 附录1、其他相关文章 前言
关于操作日志记录在一个项目中是必要的。 本文基于 java8 和 SpringBoot 2.7 来实现此功能。
之前写过一个简单的接口报文日志打印的和本文的起始思路相同都是使用切面。但是本文功能更为强大也更复杂。文章见本文附录《SpringBoot自定义starter之接口日志输出》。 本文代码仓库https://gitee.com/fengsoshuai/custom-log2.git 正文 本文知识点如下 自定义注解SpringBoot使用切面全局异常处理器ThreadLocal的使用MDC传递日志ID登录拦截器日志拦截器自定义线程池SPEL表达式解析模版方法设计模式等。 一、项目结构介绍 其中 org.feng.clog 是核心代码区域。org.feng.test 是用于测试功能写的。
二、核心类 在项目启动时会把AbstractProcessorTemplate 的子类放入Spring容器。同时会执行注册处理器的方法其定义如下
package org.feng.clog;import lombok.extern.slf4j.Slf4j;
import org.feng.clog.annotation.ProcessorBean;
import org.feng.clog.utils.SpringBeanUtils;import javax.annotation.PostConstruct;/*** 处理器模板** author feng*/
Slf4j
public abstract class AbstractProcessorTemplateT, R implements ProcessorT, R {protected void init(ProcessorContextT context) {}protected void after(ProcessorContextT context, R result) {}public R start(ProcessorContextT context) {init(context);// 直接调用handle会导致aop失效// R result handle(context);AbstractProcessorTemplateT, R template SpringBeanUtils.getByClass(this.getClass());R result template.handle(context);after(context, result);return result;}PostConstructprivate void registerProcessor() {if (this.getClass().isAnnotationPresent(ProcessorBean.class)) {ProcessorBean processorBean this.getClass().getDeclaredAnnotation(ProcessorBean.class);log.info(ProcessorBean Register, action is {}, processor is {}, processorBean.action(), this.getClass().getName());ProcessorFactory.register(processorBean.action(), this);}}
}
2.1 核心注解
2.1.1 CLog 日志注解
package org.feng.clog.annotation;import org.feng.clog.enums.ActionTypeEnum;
import org.feng.clog.enums.ModuleEnum;import java.lang.annotation.*;/*** 日志注解/br* pre* ul使用示例* liCLog(template 这是简单模版无参数,actionType ActionTypeEnum.UPDATE,actionIdEl {#userReq.id},moduleEl 1)/li* liCLog(template 带参数模版学生名称{#userReq.name},班级名称{#userReq.classReq.name},actionTypeStr 这是操作,actionIdEl {#userReq.id})/li* liCLog(template 带参数计算模版{#userReq.classReq.number 20?大班:小班},actionTypeStr 这是操作,actionIdEl {#userReq.id})/li* liCLog(template 复杂模版{#userReq.classReq.number 20?大班:(这是名称:).concat(#userReq.name).concat(,这是年龄).concat(#userReq.age)},actionTypeStr 这是操作,actionIdEl {#userReq.id})/li* liCLog(template 自定义表达式处理{SfObjectUtil.isEmpty(#userReq.id)?id为0或者为空:id不为0或者为空},actionTypeStr 这是操作,actionIdEl {#userReq.id})/li* liCLog(template 自定义处理{logDesc},actionTypeStr 这是操作,actionIdEl {id})/li* /ul* /pre** author feng*/
Documented
Retention(RetentionPolicy.RUNTIME)
Target(ElementType.METHOD)
public interface CLog {/*** 日志模版*/String template();/*** 模块*/ModuleEnum module() default ModuleEnum.DEFAULT;/*** 所属模块名*/String moduleStr() default ;/*** 所属模块名/br* 变量/表达式获取*/String moduleEl() default ;/*** 操作类型*/ActionTypeEnum actionType() default ActionTypeEnum.DEFAULT;/*** 操作类型优先级高于枚举不为空时强制读取此值*/String actionTypeStr() default ;/*** 操作类型/br* 变量/表达式获取*/String actionTypeEl() default ;/*** 业务操作唯一值/br* 变量/表达式获取*/String actionIdEl() default ;/*** 业务操作唯一值,多值*/String actionIds() default ;/*** 扩展字段*/String ext() default ;
}
2.1.2 ProcessorBean 处理器bean
package org.feng.clog.annotation;import org.feng.clog.enums.ActionTypeEnum;import java.lang.annotation.*;/*** 处理器bean** author feng*/
Documented
Retention(RetentionPolicy.RUNTIME)
Target(ElementType.TYPE)
public interface ProcessorBean {ActionTypeEnum action();
}2.2 切面类
package org.feng.clog.aspect;import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.feng.clog.LogId;
import org.feng.clog.LogRecordContext;
import org.feng.clog.annotation.CLog;
import org.feng.clog.config.LogCustomerConfig;
import org.feng.clog.enums.ActionTypeEnum;
import org.feng.clog.enums.ModuleEnum;
import org.feng.clog.utils.SpELParserUtils;
import org.feng.clog.utils.StringUtil;
import org.feng.clog.utils.UserUtil;
import org.feng.clog.vo.UserVo;
import org.springframework.stereotype.Component;import javax.annotation.Resource;
import java.util.*;
import java.util.concurrent.Executor;
import java.util.regex.Matcher;
import java.util.regex.Pattern;/*** 日志切面** author feng*/
Aspect
Component
Slf4j
public class LogAspect {private static final Pattern BRACES_PATTERN Pattern.compile(\\{.*?});Resource(name logThreadPoolTaskExecutor)private Executor executor;Pointcut(annotation(org.feng.clog.annotation.CLog))private void pointCut() {}AfterReturning(value pointCut())public void after(JoinPoint joinPoint) {try {addLog(joinPoint);} finally {LogRecordContext.clean();}}public void addLog(JoinPoint joinPoint) {String logId LogId.get();UserVo userVo UserUtil.get();MapString, String logRecordMap LogRecordContext.get();executor.execute(() - {try {// 传递logId到异步线程LogId.put(logId);// 获取方法入参MethodSignature signature (MethodSignature) joinPoint.getSignature();Object[] args joinPoint.getArgs();// 获取注解CLog cLog signature.getMethod().getDeclaredAnnotation(CLog.class);// 获取模版中的参数如果存在参数并拼接ListString templateParameters getTemplateParameters(cLog.template());buildTemplateData(templateParameters, signature, args, logRecordMap);String template cLog.template();for (String templateParameter : templateParameters) {template template.replace(templateParameter, logRecordMap.get(templateParameter));}// 获取moduleString module getModule(cLog, signature, args, logRecordMap);// 获取actionTypeString actionType getActionType(cLog, signature, args, logRecordMap);// 获取actionIdListString actionIds getActionId(cLog, signature, args, logRecordMap);// 获取扩展字段JSONObject ext getExt(cLog, signature, args, logRecordMap);if (StringUtil.isNotBlank(template)) {for (String actionId : actionIds) {log.info(记录日志user{}, template{}, module{}, actionType{}, actionId{}, ext{}, userVo, template, module, actionType, actionId, ext);// todo 日志落库}} else {log.info(设置日志数据失败不满足注解条件);}} catch (Exception e) {log.warn(设置日志异常:, e);}});}private ListString getTemplateParameters(String template) {ListString parameters new ArrayList();Matcher matcher BRACES_PATTERN.matcher(template);while (matcher.find()) {parameters.add(matcher.group());}return parameters;}private void buildTemplateData(ListString parameters, MethodSignature signature, Object[] args, MapString, String map) {for (String el : parameters) {// 如果EL表达式为空则直接下一个if (!StringUtil.isNotBlank(el)) {continue;}String spEl el;// 兼容自定义数据spEl getEl(spEl);if (map.containsKey(spEl)) {map.put({ spEl }, map.get(spEl));continue;}// 自定义类处理spEl parseCustomerMethodEl(spEl);// El执行if (spEl.contains(#)) {String value SpELParserUtils.parse(signature.getMethod(), args, spEl, String.class);map.put(el, value);} else {map.put(el, );}}}private String getModule(CLog cLog, MethodSignature signature, Object[] args, MapString, String map) {// 设置了module枚举时优先获取枚举对应的描述if (!ModuleEnum.DEFAULT.equals(cLog.module())) {return cLog.module().getDesc();}// 设置了moduleStr时if (StringUtil.isNotBlank(cLog.moduleStr())) {return cLog.moduleStr();}// 设置了moduleEl时if (StringUtil.isNotBlank(cLog.moduleEl())) {try {String el cLog.moduleEl();el getEl(el);// 处理自定义的elif (map.containsKey(el)) {return map.get(el);}// 处理自定义方法elel parseCustomerMethodEl(el);// 执行elreturn SpELParserUtils.parse(signature.getMethod(), args, el, String.class);} catch (Exception e) {log.error(日志切面获取module错误, e);}}return null;}private String getActionType(CLog cLog, MethodSignature signature, Object[] args, MapString, String map) {// 设置了actionType枚举时优先获取枚举对应的描述if (!ActionTypeEnum.DEFAULT.equals(cLog.actionType())) {return cLog.actionType().getDesc();}// 设置了actionTypeStr时if (StringUtil.isNotBlank(cLog.actionTypeStr())) {return cLog.actionTypeStr();}// 设置了actionTypeEl时if (StringUtil.isNotBlank(cLog.actionTypeEl())) {String el cLog.actionTypeEl();el getEl(el);// 处理自定义的elif (map.containsKey(el)) {return map.get(el);}// 处理自定义方法elel parseCustomerMethodEl(el);// 执行elreturn SpELParserUtils.parse(signature.getMethod(), args, el, String.class);}return null;}private ListString getActionId(CLog cLog, MethodSignature signature, Object[] args, MapString, String map) {// 设置了actionIdEl时if (StringUtil.isNotBlank(cLog.actionIdEl())) {if (map.containsKey(cLog.actionIdEl())) {return Collections.singletonList(map.get(cLog.actionIdEl()));}String el cLog.actionIdEl();el getEl(el);// 处理自定义elif (map.containsKey(el)) {return Collections.singletonList(map.get(el));}// 执行elreturn Collections.singletonList(SpELParserUtils.parse(signature.getMethod(), args, el, String.class));}// 设置了actionIds时if (StringUtil.isNotBlank(cLog.actionIds())) {String el getEl(cLog.actionIds());if (map.containsKey(el)) {return Arrays.asList(map.get(el).split(,));}}return Collections.singletonList(System.currentTimeMillis() * 10 new Random().nextInt(10000) );}private JSONObject getExt(CLog cLog, MethodSignature signature, Object[] args, MapString, String map) {// 如果EL表达式为空则直接结束if (!StringUtil.isNotBlank(cLog.ext())) {return null;}String spEl cLog.ext();//兼容自定义数据spEl getEl(spEl);if (map.containsKey(spEl)) {String value map.get(spEl);if (StringUtil.isNotBlank(value)) {try {return JSONObject.parseObject(value);} catch (Exception e) {log.info(JSON转换失败:{},{}, value, e.getMessage());return null;}}return null;}// 自定义类处理spEl parseCustomerMethodEl(spEl);// El执行if (spEl.contains(#)) {String value SpELParserUtils.parse(signature.getMethod(), args, spEl, String.class);if (StringUtil.isNotBlank(value)) {try {return JSONObject.parseObject(value);} catch (Exception e) {log.info(JSON转换失败:{},{}, value, e.getMessage());return null;}}return null;}return null;}private String parseCustomerMethodEl(String el) {for (String key : LogCustomerConfig.getCustomerMethod().keySet()) {if (el.contains(key)) {String className key.split(\\.)[0];el el.replace(className, T( LogCustomerConfig.getCustomerMethod().get(key) ));}}return el;}private String getEl(String str) {str str.replaceAll(\\{, );str str.replaceAll(}, );return str;}}
2.3 自定义线程池
package org.feng.clog.config;import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;import java.util.concurrent.Executor;
import java.util.concurrent.ThreadPoolExecutor;/*** 线程池配置** author feng*/
Configuration
EnableAsync
public class ThreadPoolConfig {Bean(name logThreadPoolTaskExecutor)public Executor initLogCpuExecutor() {ThreadPoolTaskExecutor executor new ThreadPoolTaskExecutor();executor.setCorePoolSize(Runtime.getRuntime().availableProcessors() 1);executor.setMaxPoolSize(150);executor.setQueueCapacity(50);executor.setKeepAliveSeconds(60);executor.setThreadNamePrefix(log-thread-pool-);executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());executor.initialize();executor.setTaskDecorator(runnable - runnable);return executor;}
}
2.4 工具类
2.4.1 管理者工具类
package org.feng.clog.utils;import org.feng.clog.AbstractProcessorTemplate;
import org.feng.clog.ProcessorContext;
import org.feng.clog.ProcessorFactory;/*** 管理工具** author feng*/
public class ManagerUtil {public static R, T R handle(ProcessorContextT context) {AbstractProcessorTemplateT, R processor ProcessorFactory.getProcessor(context.getAction());if (processor null) {throw new RuntimeException(未找到 context.getAction() 对应的处理器);}return processor.start(context);}
}
2.5 测试
2.5.1 订单创建处理器
package org.feng.test;import lombok.extern.slf4j.Slf4j;
import org.feng.clog.AbstractProcessorTemplate;
import org.feng.clog.LogRecordContext;
import org.feng.clog.ProcessorContext;
import org.feng.clog.annotation.CLog;
import org.feng.clog.annotation.ProcessorBean;
import org.feng.clog.enums.ActionTypeEnum;
import org.feng.clog.enums.ModuleEnum;
import org.feng.clog.utils.StringUtil;
import org.springframework.stereotype.Service;/*** 创建订单处理器** author feng*/
Slf4j
Service
ProcessorBean(action ActionTypeEnum.ORDER_CREATE)
public class OrderCreateProcessor extends AbstractProcessorTemplateOrderCreateReq, Boolean {Overrideprotected void init(ProcessorContextOrderCreateReq context) {preHandleReq(context.getData());}OverrideCLog(template 测试日志记录{testK1}, module ModuleEnum.ORDER, actionType ActionTypeEnum.ORDER_CREATE,actionIdEl {#context.data.orderNum}, ext {JacksonUtil.toJSONString(#context.data)})public Boolean handle(ProcessorContextOrderCreateReq context) {LogRecordContext.put(testK1, 3wewd2);OrderCreateReq orderCreateReq context.getData();log.info(处理--创建订单{}, orderCreateReq.getOrderNum());return true;}Overrideprotected void after(ProcessorContextOrderCreateReq context, Boolean result) {// todo 后置操作}private void preHandleReq(OrderCreateReq req) {// todo 参数校验// 例如校验参数if (StringUtil.isBlank(req.getOrderNum())) {throw new IllegalArgumentException(订单号不能为空);}}
}
2.5.2 订单管理者
package org.feng.test;import org.feng.clog.ProcessorContext;
import org.feng.clog.enums.ActionTypeEnum;
import org.feng.clog.utils.ManagerUtil;
import org.springframework.stereotype.Component;/*** 订单管理** author feng*/
Component
public class OrderManager {/*** 创建订单*/public Boolean createOrder(OrderCreateReq req) {ProcessorContextOrderCreateReq processorContext new ProcessorContext();processorContext.setAction(ActionTypeEnum.ORDER_CREATE);processorContext.setData(req);return ManagerUtil.handle(processorContext);}
}
2.5.3 订单控制器
package org.feng.test;import org.feng.clog.utils.ResultUtil;
import org.feng.clog.vo.ResultVo;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;import javax.annotation.Resource;/*** 控制器** author feng*/
RestController
RequestMapping(order)
public class OrderController {Resourceprivate OrderManager orderManager;// WithoutLoginPostMapping(/test1)public ResultVoString test1(RequestBody OrderCreateReq req) {// 创建Boolean started orderManager.createOrder(req);return ResultUtil.success(success started);}
}
2.5.4 测试报文
{orderNum: 1001,type: 1,senderName: ,likes: [1, 2, 3]
}2.5.5 测试结果
控制台日志输出
2024-02-28 11:48:40.102 INFO 92309 --- [log-thread-pool-1] org.feng.clog.aspect.LogAspect.lambda$addLog$0(LogAspect.java:95) : [logIdd3b0dc267ce64dfa8a987e8eb6aad4ba] 记录日志userUserVo(id1001, usernamefeng123, phone18143431243, emailnull), template测试日志记录3wewd2, module订单, actionType订单创建, actionId1001, ext{senderName:,orderNum:1001,type:1,likes:[1,2,3]}
可以看到日志中记录了logId以及日志注解对应的信息。
附录
1、其他相关文章
SpringBoot自定义starter之接口日志输出SpringBoot使用线程池之ThreadPoolTaskExecutor和ThreadPoolExecutor