Naik
Turun
Komen
mengumpul
Lapor
berkongsi

springboot自定义注解这么用轻松捕获系统操作日志上
Fail boleh laku yang disebut dalam artikel ini mungkin memerlukan pemalam emulator ons untuk dijalankan pada peranti mudah alih (Android, iOS, HarmonyOS, dll.) atau platform lain. Jika perlu, sila muat turun pemalam atau ciri yang diperlukan dari program mini Plugin Biasa dalam aplikasi VikACG.
通常为了监控系统,我们希望将请求的入参和出参记录到数据库中,已备后查。除了在每个方法里面加日志处理代码,手动保存到数据库,还有其他的办法吗?今天就给大伙介绍一个注解
@Log2DB
什么?没这个注解?是的,这个注解是自定义的,那用这个注解能干什么呢?怎么写一个自己的注解呢?今天我们一起来探讨下。
首先,我们随便找个注解,看下它的结构@RestController
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Controller
@ResponseBody
public @interface RestController {
@AliasFor(
annotation = Controller.class
)
String value() default "";
}
是个interface,但是又加了一个@符号,那好吧,我们也搞一个这个。
@Target({ElementType.METHOD, ElementType.TYPE})//注解作用的位置,ElementType.METHOD表示该注解仅能作用于方法上
@Retention(RetentionPolicy.RUNTIME)//注解的生命周期,表示注解会被保留到什么阶段,可以选择编译阶段SOURCE、类加载阶段CLASS,或运行阶段RUNTIME
@Documented//注解信息会被添加到Java文档中
public @interface Log2DB {
String name() default "接口";
boolean enabled() default true;//是否启用,默认是
}
注解是加进去了,怎么用呢?
@Api("testController相关操作")
@Slf4j
@RestController
@RequestMapping("test")
public class TestController {
@Log2DB(name = "获取用户")
@GetMapping("get")
@ApiOperation("获取用户1")
public String getUser(@RequestParam("userId")String userId){
log.info("get is called");
return "get is called";
}
@Log2DB(name = "获取用户")
@PostMapping("post")
@ApiOperation("保存用户1")
public String postUser(@RequestBody Map request){
log.info("post is called");
return "post is called";
}
}
这就加上去了,但不是说好了保存到数据库吗?我们用面向切面把@Log2DB这个注解拦截下来,定义一个aspect
@Aspect
@Configuration
@Slf4j
public class Log2DBAspect {
//拦截哪个包下的类
//拦截哪个注解com.example.lesson20.annotation.Log2DB
@Pointcut("execution(public * *(..)) && @annotation(com.example.lesson20.annotation.Log2DB)")
public void logs(){
log.info("@Pointcut");
}
@Around("logs()")
public Object doLog(ProceedingJoinPoint joinPoint) throws Throwable {
log.info("@Around");
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Log2DB logAnnotation = signature.getMethod().getAnnotation(Log2DB.class);
//如果不需要记录到数据库,继续业务调用并返回
if(!logAnnotation.enabled()){
return joinPoint.proceed();
}
//定义需要从request和response中获取的信息 begin
String name = "",//请求的服务名称
url="",//请求服务地址
method="",//请求方法 get post put
clientIp="",//客户端ip
browser="",//浏览器
clazz_method="",//请求的类和方法名
headers="", //head头信息
request="";//请求body入参
Object response=null;//返回参数
Date bgn,end;//请求开始结束时间
Boolean success = true;//是否成功
long cost = 0;//耗时 毫秒
//定义需要从request和response中获取的信息 end
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
if(attributes==null){
return joinPoint.proceed();
}
HttpServletRequest httpServletRequest = attributes.getRequest();
url = httpServletRequest.getRequestURL().toString();
method = httpServletRequest.getMethod();
clientIp =getClientIp(httpServletRequest);
clazz_method =joinPoint.getSignature().getDeclaringTypeName()+"."+joinPoint.getSignature().getName();
name = logAnnotation.name();
//获取url后面的参数串
StringBuffer params = new StringBuffer();
for (String item : httpServletRequest.getParameterMap().keySet()) {
params.append(item).append("=").append(httpServletRequest.getParameter(item)).append("&");
}
String tmp = params.toString();
if(StringUtils.hasText(tmp)){
tmp = tmp.substring(0,tmp.length()-1);
url = url+"?"+tmp;
}
//获取header头信息
List<Map<String,String>> headerList = new ArrayList<>();
Enumeration<String> headerNames = httpServletRequest.getHeaderNames();
while (headerNames.hasMoreElements()) {
String key = headerNames.nextElement();
String value = httpServletRequest.getHeader(key);
Map<String,String> map = new HashMap<String,String>();
map.put(key,value);
headerList.add(map);
if("user-agent".equals(key.toLowerCase())){
browser = value;
}
}
headers = new ObjectMapper().writeValueAsString(headerList);
request = new ObjectMapper().writeValueAsString(joinPoint.getArgs());
//记录起始时间
bgn = new Date();
/** 执行目标方法 */
try{
response= joinPoint.proceed();
log.info("response={}",response==null?"": new ObjectMapper().writeValueAsString(response));
}
catch(Exception e){
success=false;
response = e.getMessage();
log.error("errorMessage: {}", e.getMessage());
e.printStackTrace();
throw e;
}
finally{
end = new Date();
/** 记录操作时间 */
cost = (end.getTime() - bgn.getTime());
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:SSS");
log.info("name={}",name);
log.info("url={}",url);
log.info("method={}",method);
log.info("ip={}",clientIp);
log.info("browser={}",browser);
log.info("class_method={}",clazz_method);
log.info("header={}",headers);
log.info("request={}",request);
log.info("response={}",response);
log.info("success={}",success);
log.info("bgn={}",format.format(bgn));
log.info("end={}",format.format(end));
log.info("cost={}", cost);
//TODO: 保存到数据库 这里大家伙自行sql保存到数据库,我就不参合了
//不过我建议这个地方可以考虑先放到mq 消息队列中,然后慢慢消费,不要占用mysql的资源。
// 或者日志就存储到mongodb中也不失为一个好办法
}
return response;
}
@Before("logs()")
public void doBefore(JoinPoint point){
log.info("@Before");
}
@After("logs()")
public void doAfter(){
log.info("@After");
}
//在doAfter之后执行,主要用户记录程序执行后的返回值
@AfterReturning(returning="object",pointcut="logs()")
public void doAfterReturning(Object object){
log.info("@AfterReturning");
}
//获取客户端ip
private String getClientIp(HttpServletRequest request) {
String ip = request.getHeader("x-forwarded-for");
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("WL-Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getRemoteAddr();
}
return ip;
}
}
类有点长,我就不给大家一一解释了,里面注释都很详细,大家直接copy paste就可以用了,最后我们来验证下是不是这些参数都拿到了,postman请求一次,看看后台打印日志
@Around
@Before
post is called
@AfterReturning
@After
response="post is called"
name=保存用户1
url=Pautan Web(localhost)
method=POST
ip=0:0:0:0:0:0:0:1
browser=PostmanRuntime/7.26.8
class_method=com.example.lesson20.controller.TestController.postUser
header=[{"content-type":"application/json"},{"user-agent":"PostmanRuntime/7.26.8"},{"accept":"*/*"},{"cache-control":"no-cache"},{"postman-token":"b3f49862-8b4e-4c7a-b817-3ff4f4e19336"},{"host":"localhost:8080"},{"accept-encoding":"gzip, deflate, br"},{"connection":"keep-alive"},{"content-length":"47"}]
request=[{"username":"abc","password":123}]
response=post is called
success=true
bgn=2021-07-03 10:00:25:183
end=2021-07-03 10:00:25:214
cost=31
嗯,是不错。但是方法的中文名字在Log2DB和ApiOperation中都要写一次,有点麻烦。下一期我们一起来解决这个问题。PengisytiharanSemua artikel di laman web ini, melainkan dinyatakan atau ditandakan sebaliknya, adalah penerbitan asal laman web ini. Mana-mana individu atau organisasi dilarang menyalin, mencuri, mengumpul, atau menerbitkan kandungan laman web ini ke mana-mana laman web, buku, atau platform media lain tanpa kebenaran daripada UP master. Jika kandungan laman web ini melanggar hak dan kepentingan sah pengarang asal, sila hubungi kami untuk pemprosesan: Laporan DMCA













Pemuat naik ini rasa sunyi