前言
最近公司一个开发完成的项目 , 在使用过程中要开始统计工作量了 , 但是发现之前没有记录日志, 所以统计不出来, 要求增加上日志这个功能就写一个注解来实现把!
- 第一步定义日志注解
import java.lang.annotation.*;
/**
* @author kwokql
* @version 2019/3/26 16:18
*/
@Documented
@Retention(RetentionPolicy.RUNTIME) /* 编译器将Annotation存储于class文件中,并且可由JVM读入 */
@Target(ElementType.METHOD) /* 声明方法注解 */
public @interface SysLogPoint {
String dm(); // 操作类型代码
String mc(); // 操作类型名称
String id() default ""; // 被操作人id
String xm() default ""; // 被操作人姓名
boolean splite() default false; // 是否根据被操作人id splite 插入多条日志
String bj() default ","; //splite 方法的 媒介
}
2.然后创建一个日志实体
import javax.persistence.*;
import java.io.Serializable;
/**
* @Auther kwokql
* @Date 2020/7/3 16:41
* 日志对象
*/
public class SysLogModel implements Serializable {
private Long id; //id
private String czrid; // 操作人id
private String czrxm; // 操作人姓名
private String czsj; // 操作时间
private String bczrid; // 被操作人id
private String bczrxm; // 被操作人姓名
private String czzt; // 本次操作状态
private String czrfw; // 请求服务 0:app , 1:pc
private String czlxmc; // 操作类型名称
private String czlxdm; // 操作类型代码
private String czcs; // 请求参数
//getter setter ....
}
3.然后就是 注解的业务实现了 也是要根据自己的业务来修改 ::代码较多
import com.fasterxml.jackson.databind.JsonNode;
import com.triman.common.util.SecurityUtil;
import com.triman.syrkquery.aspect.util.FetchValueUtil;
import com.triman.syrkquery.dao.SysLogDao;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ThreadPoolExecutor;
/**
* @author kwokql
* @version 2019/3/26 16:22
* @info 保存操作日志
*/
@Component
@Aspect
public class SysLogAspect {
@Autowired
private SysLogDao sysLogDao; //db媒介
@Autowired
private ThreadPoolExecutor executor;//线程池
private static DateTimeFormatter ymdhms = DateTimeFormatter.ofPattern("yyyyMMddHHmmss");
/**
* 正常返回处理
*
* @param jp 连接点
* @param point 注解
* @param result 返回结果
*/
@AfterReturning(value = "@annotation(point)", returning = "result")
public void afterReturn(JoinPoint jp, SysLogPoint point, Object result) {
SysLogModel sysLog = buildLog(jp, point, result, null);
sysLog.setCzzt("成功");
saveLog(sysLog,point);
}
/**
* 抛出异常的处理
*
* @param jp 连接点
* @param point 注解
* @param ex 异常对象
*/
@AfterThrowing(value = "@annotation(point)", throwing = "ex")
public void afterThrowing(JoinPoint jp, SysLogPoint point, Throwable ex) {
SysLogModel sysLog = buildLog(jp, point, null, ex);
sysLog.setCzzt("失败");
saveLog(sysLog,point);
}
/**
* 保存到DB媒介中
*
* @param sysLog
*/
private void saveLog(SysLogModel g,SysLogPoint point) {
if(g == null) return;
List<SysLogModel> logs = new ArrayList<>();
if(point.splite()){
String[] rids = g.getBczrid().split(point.bj());
for (String rid : rids) {
g.setBczrid(rid);
logs.add(g);
}
}else{
logs.add(g);
}
executor.execute(new TaskRunnable(sysLogDao, logs));
}
/**
* 构建日志对象
*
* @param jp 连接点
* @param point 注解
* @param result 处理结果对象
* @param ex 处理异常对象
* @return 日志日志对象
*/
private SysLogModel buildLog(JoinPoint jp, SysLogPoint point, Object result, Throwable ex) {
try{
// 获取RequestAttributes
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
// 从获取RequestAttributes中获取HttpServletRequest的信息
HttpServletRequest request = (HttpServletRequest) requestAttributes.resolveReference(RequestAttributes.REFERENCE_REQUEST);
SysLogModel log = new SysLogModel();
//修改为自己的 操作人信息方法
log.setCzrid(SecurityUtil.getUserInfo().getId());// 操作人id
log.setCzrxm(SecurityUtil.getUserInfo().getUser_name());// 操作人姓名
log.setCzsj(ymdhms.format(LocalDateTime.now()));// 操作时间
log.setCzrfw("1");// 请求服务
log.setCzlxdm(point.dm());// 操作类型代码
log.setCzlxmc(point.mc());// 操作类型名称
// 请求参数
Map<String, String> paramMap = converMap(request.getParameterMap());
if (!paramMap.isEmpty()){
String paramJson = FetchValueUtil.JSONtoString(paramMap);
log.setCzcs(paramJson);
}else{
Object[] args = jp.getArgs();
String paramJson = FetchValueUtil.JSONtoString(args);
log.setCzcs(paramJson);
}
// 被操作人id
String id = point.id();
if (StringUtils.isNotBlank(id)) {
log.setBczrid(s == null ? id : s);
}
// 被操作人姓名
String xm = point.xm();
if (StringUtils.isNotBlank(xm)) {
log.setBczrxm(s == null ? xm : s);
}
return log;
}catch (Exception e){
e.printStackTrace();
return null;
}
}
/**
* 转换request 请求参数
*
* @param paramMap request获取的参数数组
*/
public Map<String, String> converMap(Map<String, String[]> paramMap) {
Map<String, String> rtnMap = new HashMap<>();
for (String key : paramMap.keySet()) {
rtnMap.put(key, paramMap.get(key)[0]);
}
return rtnMap;
}
}
一般情况来说到这里就可以开始使用测试了, 但是我突然想起来
这次日志是需要获取请求的参数存入数据库的
而且因为这是一个已经开发完的项目 接受的参数没有规定 所以是传的五花八门
接口代码全部改一遍 不现实,
但是我又不想重新写 try/catch的写法
所以我写了一个 能像js取值一样的工具类:如下
注意: 需要依赖 jackson
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.IOException;
import java.lang.reflect.Array;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* @info 模拟js表达式取值
* @author gql
* @Date 2020年7月9日10:53:38
*/
public class FetchValueUtil {
public static void main(String[] args) {
/*
let json = [
[ { rid: 1 },{ rid: 2 } ] , 对象1
{ rid: 3 }, 对象2
[4,5,6], 对象3
'张三-李四', 对象4
9 对象5
]
handleInput(json, 'args[0].{rid}') return: [1,2]
handleInput(json, 'args[1].rid') return: 3
handleInput(json, 'args[2]') return: [4,5,6]
handleInput(json, 'args[2][0]') return: 4
handleInput(json, 'args[3]') return: '张三,李四'
handleInput(json, 'args[4]') return: 9
*/
}
//js表达式 存入数组中方便获取下一个表达式
private static List<String> keys = new ArrayList<>();
private static final ObjectMapper om = new ObjectMapper();
public static String JSONtoString(Object obj) throws JsonProcessingException {
return om.writeValueAsString(obj);
}
/**
* 处理输入参数
*
* @param args 入参
* @param params obj.key.value
* @return 特殊处理都的入参
*/
public static JsonNode handleInput(Object[] args, String params) {
try {
if (isEmpty(params) || isEmpty(args)) return null;
keys = new ArrayList(Arrays.asList(params.split("\\.")));
JsonNode root = om.readTree(om.writeValueAsString(args));
return handleSensitiveParams(root, 0);
} catch (Exception e) {
e.printStackTrace();
return null;
} finally {
keys = null;
}
}
/**
* 根据js表达式 获取值
*
* @param root jackson节点
* @param i 当前表达式下标
*/
public static JsonNode handleSensitiveParams(JsonNode root, int i) {
try {
String param = keys.get(i);
int arrRegex = arraysCount(param);
if (arrRegex > 0) { //数组下标取值
//递归处理 [0][1][2] 多维数组
if(arrRegex > 1) {
JsonNode jsonNode = root.get(getArrs(i));
return handleSensitiveParams(jsonNode,i);
}else{
String key = getArrRegex(param);
int index = isArrRegex(param);
JsonNode result;
if(!"args".equals(key)){
JsonNode jsonNode = fetchRootParam(root, key);
if (jsonNode == null || !jsonNode.isArray()) {
return null;
}
result = jsonNode.get(index);
}else{
result = root.get(index);
}
if (i == keys.size() - 1) {
return result;
}
return handleSensitiveParams(result, ++i);
}
} else {
//key属性取值
String objRegex = isObjRegex(param);
if(!isBlank(objRegex) && root.isArray()){
Iterator<JsonNode> iterator = root.iterator();
List<String> strings = new ArrayList<>();
while (iterator.hasNext()){
JsonNode next = iterator.next();
JsonNode jsonNode = next.get(objRegex);
strings.add(jsonNode.asText());
}
return om.readTree(om.writeValueAsString(strings));
}else if (root.isObject()) {//obj 处理
Iterator<Map.Entry<String, JsonNode>> rootIt = root.fields();
while (rootIt.hasNext()) {
Map.Entry<String, JsonNode> node = rootIt.next();
if (param.equals(node.getKey())) {
if (i == keys.size() - 1) {
return node.getValue();
}
return handleSensitiveParams(node.getValue(), ++i);
}
}
}
}
return null;
} catch (RuntimeException | IOException e) {
e.printStackTrace();
return null;
}
}
/**
* 根据js表达式 获取 JsonNode 的值
*
* @param root jackson节点
* @param param
*/
public static JsonNode fetchRootParam(JsonNode root, String param) {
if (!root.isObject()) {
return null;
}
Iterator<Map.Entry<String, JsonNode>> rootIt = root.fields();
while (rootIt.hasNext()) {
Map.Entry<String, JsonNode> node = rootIt.next();
if (param.equals(node.getKey())) {
return node.getValue();
}
}
return null;
}
/**
* 是否为数组表达式
* @param string key[0]
* @return 0
*/
public static int isArrRegex(String string) {
try {
String result = matcher("[\\[0-9*\\]]", string);
if (result != null && !isBlank(matcher("[0-9*]", result))) {
return Integer.parseInt(Objects.requireNonNull(matcher("[0-9*]", result)));
}
return 0;
} catch (RuntimeException e) {
//e.printStackTrace();
return 0;
}
}
/**
* 是否为取值表达式
* @param string key.{rid}
* @return 0
*/
public static String isObjRegex(String string) {
try {
String result = matcher("[\\{.*\\}]", string);
if (result != null && !isBlank(matcherInsensitive("[a-z]{1,99}", string))) {
return matcherInsensitive("[a-z]{1,99}", string);
}
return "";
} catch (RuntimeException e) {
//e.printStackTrace();
return "";
}
}
/**
* 获取为js的数组表达式的key
* @param string key[0]
* @return key
*/
public static String getArrRegex(String string) {
try {
return string.substring(0, string.indexOf("["));
} catch (RuntimeException e) {
//e.printStackTrace();
return null;
}
}
/**
* 解决多维数组的取值 下一次的表达式会在keys中改变
* @param i key[0][1]
* @return 1
*/
public static int getArrs(int i) {
try {
String string = keys.get(i);
int start = string.indexOf("[");
int end = string.indexOf("]");
String k = string.substring(0, start);
String j = string.substring(end + 1);
keys.remove(i);
keys.add(i,k+j);
return Integer.parseInt(string.substring(start+1, end));
} catch (RuntimeException e) {
//e.printStackTrace();
return 0;
}
}
/**
* 根据正则表达式获取字符串中匹配的值
* @param regex 正则
* @param string 字符串
* @return 匹配的字符串
*/
public static String matcher(String regex, String string) {
try {
Pattern p = Pattern.compile(regex);
Matcher m = p.matcher(string);
StringBuilder result = new StringBuilder();
while (m.find()) {
result.append(m.group());
}
return String.valueOf(result);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* 根据正则表达式获取字符串中匹配的值
* @param regex 正则
* @param string 字符串
* @return 匹配的字符串
*/
public static String matcherInsensitive(String regex, String string) {
try {
Pattern p = Pattern.compile(regex,Pattern.CASE_INSENSITIVE);
Matcher m = p.matcher(string);
StringBuilder result = new StringBuilder();
while (m.find()) {
result.append(m.group());
}
return String.valueOf(result);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* 判断[]的个数 确定需要递归几次数组
* @param string 字符串
* @return 匹配的字符串
*/
public static int arraysCount(String string) {
try {
char[] chars = string.toCharArray();
int start = 0;
int end = 0;
for (char aChar : chars) {
String s = String.valueOf(aChar);
if("[".equals(s)){
start++;
}
if("]".equals(s)){
end++;
}
}
return Math.min(start, end);
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
/**
* 判断对象是否为空
* @param obj
* @return 是否为空
*/
public static boolean isEmpty(Object obj) {
if (obj == null) {
return true;
} else if (obj instanceof Optional) {
return !((Optional)obj).isPresent();
} else if (obj instanceof CharSequence) {
return ((CharSequence)obj).length() == 0;
} else if (obj.getClass().isArray()) {
return Array.getLength(obj) == 0;
} else if (obj instanceof Collection) {
return ((Collection)obj).isEmpty();
} else {
return obj instanceof Map && ((Map) obj).isEmpty();
}
}
/**
* 判断字符串是否为空
* @param cs
* @return
*/
public static boolean isBlank(CharSequence cs) {
if (cs != null && cs.length() != 0) {
for(int i = 0; i < cs.length(); ++i) {
if (!Character.isWhitespace(cs.charAt(i))) {
return false;
}
}
}
return true;
}
}
测试结果如下
[
[{ rid: 1 }, { rid: 2 }],
{ rid: 3 },
[4, 5, 6],
'张三-李四',
9
]
handleInput(json, 'args[0].{rid}') return: [1,2] //{} == 获取一个数组中指定的属性 返回数租
handleInput(json, 'args[1].rid') return: 3
handleInput(json, 'args[2]') return: [4,5,6]
handleInput(json, 'args[2][0]') return: 4
handleInput(json, 'args[3]') return: '张三,李四'
handleInput(json, 'args[4]') return: 9
加入日志中
// 被操作人id
String id= point.id();
if (StringUtils.isNotBlank(id)) {
String s = String.valueOf(FetchValueUtil.handleInput(jp.getArgs(), id));
log.setBczrxm(s == null ? xm : s);// 被操作人id
}
下面是使用
@RequestMapping(value = "/removeTag/{id}", method = RequestMethod.GET)
@SysLogPoint(dm = "***", mc = "***",id = "args[0]")
public Object search(@PathVariable("id") Integer id) throws Exception
@RequestMapping(value = "/addTag", method = RequestMethod.POST)
@SysLogPoint(dm = "***", mc = "***", id = "args[1]")
public Object search(@RequestParam("bqcode") String bqcode, @RequestParam("rid") String rid) throws Exception {
@RequestMapping(value = "/rjbxx", method = RequestMethod.POST)
@SysLogPoint(dm = "***", mc = "***", id = "args[0].rid")
public Object search(@RequestBody TRjbxx trjbxx) throws Exception
不出问题的话到这里就结束了 接下来就可以去 测试了