目录
- 🐬使用
- 🐠若依-操作日志
- 🐠引入海豚调度
- 🐟引入审计日志包,增加`LogAnnotation`注解
- 🐬问题记录及优化
- 🐠service方法注解时而生效,时而不生效
- 🐟不生效原因
- 🐟修改
- 🐡自我注入(纯测试)
- 🐡接口中增加该方法
- 🐠优化,增加批次号
- 🐟ThreadLocal的使用
- 🐡测试结果
- 🐟地理位置的获取
前段时间调研审计日志,发现海豚调度3.0源码已经有这个功能了,测试了一下发现功能并没有完全实现,详见dolphinscheduler 3.0.1 审计日志,至于2.0的版本很早就有审计日志相关类了,只不过都是开发未完成状态。目前是参照若依管理系统的操作日志功能,把代码提取出来之后,稍作优化,本文主要记录做了哪些优化以、使用过程遇到的注意事项及相关技术点。
🐬使用
🐠若依-操作日志
🐠引入海豚调度
直接提取若依中记录操作日志的相关类,运用的AOP,切面、切点。核心类为LogAspect
和Log
,提取出来之后,根据报错,缺少哪个类,把相关类拷贝过来即可,我这边没有直接放到海豚调度,而是单独搞了个项目,主要为了方便其他项目也能调用。
-
LogAspect
:切面处理类,解析请求响应参数入库等com.ruoyi.framework.aspectj.LogAspect
-
切点注解接口,拎出来之后,接口名称我改成了
LogAnnotation
,海豚2.0自带接口为AccessLogAnnotation
,只不过是个开发未完成的功能,由于这部分要做成公共的功能,所以直接放弃了海豚这部分的半成品com.ruoyi.common.annotation.Log
-
表为操作日志记录表-
sys_oper_log
CREATE TABLE `sys_oper_log` ( `oper_id` bigint NOT NULL AUTO_INCREMENT COMMENT '日志主键', `title` varchar(50) DEFAULT '' COMMENT '模块标题', `business_type` int DEFAULT '0' COMMENT '业务类型(0其它 1新增 2修改 3删除)', `method` varchar(100) DEFAULT '' COMMENT '方法名称', `request_method` varchar(10) DEFAULT '' COMMENT '请求方式', `operator_type` int DEFAULT '0' COMMENT '操作类别(0其它 1后台用户 2手机端用户)', `oper_name` varchar(50) DEFAULT '' COMMENT '操作人员', `dept_name` varchar(50) DEFAULT '' COMMENT '部门名称', `oper_url` varchar(255) DEFAULT '' COMMENT '请求URL', `oper_ip` varchar(128) DEFAULT '' COMMENT '主机地址', `oper_location` varchar(255) DEFAULT '' COMMENT '操作地点', `oper_param` varchar(2000) DEFAULT '' COMMENT '请求参数', `json_result` varchar(2000) DEFAULT '' COMMENT '返回参数', `status` int DEFAULT '0' COMMENT '操作状态(0正常 1异常)', `error_msg` varchar(2000) DEFAULT '' COMMENT '错误消息', `oper_time` datetime DEFAULT NULL COMMENT '操作时间', PRIMARY KEY (`oper_id`) ) ENGINE=InnoDB AUTO_INCREMENT=1068 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='操作日志记录';
🐟引入审计日志包,增加LogAnnotation
注解
以工作流定义-修改为例,增加注解,原先的AccessLogAnnotation
注解直接去掉了,没用,此时编辑工作流的时候,请求响应参数及方法等都会存到数据库
🐬问题记录及优化
🐠service方法注解时而生效,时而不生效
背景:工作流定义-更新,内部涉及三张表的操作,对应多个方法,因此想要记录所有的方法的请求响应参数
🐟不生效原因
- 添加注解的方法必须是
public
,内部私有方法代理是获取不到的 - 添加注解的方法类必须实例化过
- 比如工作流定义更新方法,私有方法
updateDagDefine
,即便改为public
,添加注解也不会生效
- 比如工作流定义更新方法,私有方法
🐟修改
🐡自我注入(纯测试)
改为public
,注入本身,再次调用,注解有效(纯测试,虽然spring支持循环依赖,可以自我注入,但是总感觉这样干有风险)
🐡接口中增加该方法
比如改成updateProcessDefinition
这种,通过controller
中注入该方法接口
🐠优化,增加批次号
背景:工作流定义-更新,内部涉及三张表的操作,对应多个方法,添加注解后全部记录了下来,但是不好区分,哪些记录是同一个操作产生的,即同一个批次,因此增加批次号-batch_no
字段,更新工作流操作,底层涉及三种表的更新,这些操作都是在同一个线程,因此可以通过ThreadLocal
去实现
🐟ThreadLocal的使用
ThreadLocal
使用很简单,一个set
方法,一个get
方法,一个remove
方法,主要风险在于内存泄漏,一般调用完get
方法需要调用remove
,也可以实现AutoCloseable
,自动回收,主要何时调用remove
不好辨别
- 新增批次号工具类
BatchNoUtils
,实现了AutoCloseable
接口,否则还要单独调用remove
方法public class BatchNoUtils implements AutoCloseable { private static final ThreadLocal<Long> threadLocal = new ThreadLocal<>(); public static void add(Long batchNo) { threadLocal.set(batchNo); } public static Long getBatchNo() { return threadLocal.get(); } @Override public void close() { threadLocal.remove(); } }
- 增加过滤器
BatchNoFilter
,在调用方法之前设置批次号(即threadLocal.set(),初始化值,直接用的yyyyMMddHHmmssSSS
)import java.io.IOException; import javax.servlet.FilterChain; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.springframework.stereotype.Component; import org.springframework.web.filter.OncePerRequestFilter; import com.copote.comb.manager.util.DateUtils; /** * 请求处理前生成批次号. * * @author rxz */ @Component public class BatchNoFilter extends OncePerRequestFilter { @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { BatchNoUtils.add(Long.valueOf(DateUtils.dateTimeNow("yyyyMMddHHmmssSSS"))); filterChain.doFilter(request, response); } }
- 获取批次号
🐡测试结果
🐟地理位置的获取
测试结果中,可以看到IP归属地,北京市,看了下代码,直接调用http://whois.pconline.com.cn/ipJson.jsp
,联网就行,本来以为这种操作都是收费的呢,看来也有免费的接口
- 测试类
package com.renxiaozhao.demo;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.URL;
import java.net.URLConnection;
public class Test2 {
public static void main(String[] args) {
getLocation("192.168.3.10");
getLocation("36.99.142.234");
}
private static String getLocation(String ip) {
StringBuilder result = new StringBuilder();
BufferedReader in = null;
String url = "http://whois.pconline.com.cn/ipJson.jsp?";
try {
String param = "ip=" + ip + "&json=true";
URL realUrl = new URL(url+param);
URLConnection connection = realUrl.openConnection();
connection.setRequestProperty("accept", "*/*");
connection.setRequestProperty("connection", "Keep-Alive");
connection.setRequestProperty("user-agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)");
connection.connect();
in = new BufferedReader(new InputStreamReader(connection.getInputStream(), "GBK"));
String line;
while ((line = in.readLine()) != null) {
result.append(line);
}
System.out.println(result);
} catch (Exception e) {
System.out.println("获取IP归属地异常:" + e.getMessage());
} finally {
try {
if (in != null) {
in.close();
}
} catch (Exception ex) {
System.out.println("关闭流异常:" + ex.getMessage());
}
}
return result.toString();
}
}