我们项目中可能有这种需求,每个人请求了哪些接口?做了什么事情?参数是什么?重要的接口我们需要记日志以便查找。我们不可能在每个接口中去一一处理,可以借助Spring提供的AOP能力+自定义注解轻松应对。
首先我们定义用于Controller方法的注解,使用了此注解的就记录日志。
-
/**
-
* 添加了此注解就记录日志
-
* @author xiongshiyan at 2018/11/2 , contact me with email yanshixiong@126.com or phone 15208384257
-
*/
-
@Target({ElementType.METHOD})
-
@Retention(RetentionPolicy.RUNTIME)
-
@Documented
-
public @
interface
LogOption {
-
String desc() default "";
-
String[] exclude() default {};
//排序某些字段
-
-
/**
-
* 参数的key
-
*/
-
String[] key() default {};
-
-
/**
-
*
-
* 参数key对应的展示的字段,主要的目的是为了显示的时候的可读性
-
*/
-
String[] val() default {};
-
}
其中desc是描述,exclude排除参数的某些字段,key-val将参数可读化。
然后定义AOP切面
-
/**
-
* @author xiongshiyan
-
* 统一保存操作日志
-
*/
-
@Aspect
-
@Component
-
@Order(
2)
-
@ConditionalOnProperty(prefix =
"spring.log.detail" , name =
"enable" , havingValue =
"true")
-
public
class WebLogDetailAspect {
-
-
@Pointcut(
"execution(public * cn.palmte.anfang.controller.alarm..*.*(..))")
-
public void webLog(){}
-
-
@Around(value =
"webLog()")
-
public Object doAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
-
Object proceed = proceedingJoinPoint.proceed();
-
-
Map<String , Object>
map =
new HashMap<>(
2);
-
map.put(
"log" , getLog());
-
map.put(
"proceedingJoinPoint" , proceedingJoinPoint);
-
//发起异步任务,保存日志
-
SpringContextHolder.getApplicationContext().publishEvent(
new SaveLogDetailEvent(
map));
-
-
return proceed;
-
}
-
-
private Log getLog() {
-
//因为是异步操作,所以RequestContextHolder.getRequestAttributes()必须放到此处,而不能放到Listener中。
-
// log中从header中取的属性只能放到这
-
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
-
HttpServletRequest request = attributes.getRequest();
-
-
Log
log =
new Log();
-
try {
-
String name = request.getHeader(
"name");
-
//header中无法传输中文
-
log.setOperator(null == name ? null : URLDecoder.decode(name,
"UTF-8"));
-
log.setOperatorId(request.getHeader(
"userId"));
-
log.setOperatorPort(
"" + request.getRemotePort());
-
// log.setOperatorIp(request.getRemoteAddr());
-
//前端转发的通过此header获取
-
log.setOperatorIp(request.getHeader(
"X-FORWARDED-FOR"));
-
}
catch (UnsupportedEncodingException e) {
-
e.printStackTrace();
-
}
-
return
log;
-
}
-
}
使用SpringBoot的条件注解可以轻松开启或者关闭这个功能,因为一般测试环境是不需要记录的。又由于是就日志,最好不要阻塞正常的调用了,所以利用Spring的事件系统异步化。
然后是参数的解析:
-
@Component
-
public
class SaveLogDetailListener {
-
@Autowired
-
private SaveLogDetailService saveLogDetailService;
-
private
static final Logger logger = LoggerFactory.getLogger(SaveLogDetailListener.class);
-
@EventListener
-
@Async
-
public
void saveLogDetail(SaveLogDetailEvent saveLogDetailEvent){
-
try {
-
Map<
String ,
Object> map = (
Map<
String,
Object>) saveLogDetailEvent.getSource();
-
log2Db(
-
(ProceedingJoinPoint) map.get(
"proceedingJoinPoint") ,
-
(Log)map.get(
"log"));
-
}
catch (Exception e) {
-
logger.error(
"保存日志失败" , e);
-
}
-
}
-
-
public
void log2Db(ProceedingJoinPoint proceedingJoinPoint , Log log) throws Exception {
-
-
Method method = AnnotationUtil.getMethod(proceedingJoinPoint);
-
LogOption logOption = AnnotationUtil.getLogOption(method);
-
-
//没有注解标记的什么都不干
-
if (
null == logOption) {
-
return;
-
}
-
-
-
String[] key = logOption.key();
-
String[] val = logOption.val();
-
-
if (key.length != val.length) {
-
throw
new RuntimeException(
"注解标识错误,key和val的长度必须一致");
-
}
-
-
-
Parameter[] params = AnnotationUtil.getParam(method);
-
//没有参数的不需要记录
-
if (
null == params) {
-
return;
-
}
-
-
Object[] args = proceedingJoinPoint.getArgs();
-
Map<
String,
Object> paramMap = convertMap(key, val, params, args , logOption.exclude());
-
-
-
saveLog(logOption, paramMap , log);
-
-
}
-
-
private
void saveLog(LogOption logOption,
Map<
String,
Object> paramMap , Log log) throws UnsupportedEncodingException {
-
LogMenu menu = logOption.menu();
-
LogType logType = logOption.type();
-
-
log.setCreatedTime(
new
Date());
-
log.setMenuCode(menu.name());
-
log.setMenu(menu.getDesc());
-
log.setLogTypeCode(logType.name());
-
log.setLogType(logType.getDesc());
-
log.setLogDesc(logOption.desc());
-
-
Set<LogKv> kvs =
new HashSet<>(paramMap.size());
-
paramMap.forEach((k, v) -> kvs.add(
new LogKv(k,
String.valueOf(v))));
-
-
logService.saveLogAndDetail(log, kvs);
-
}
-
-
private
Map<
String,
Object> convertMap(
String[] key,
String[] val, Parameter[] params,
Object[] args ,
String[] exclude) {
-
/**
-
* 保存所有参数的key和value,包括如果是json实体就拿到json的key和value
-
*/
-
Map<
String,
Object> paramMap = getAllParamsMap(params, args , exclude);
-
-
/**
-
* 保存根据注解改变的key的值
-
*/
-
Map<
String,
Object> changeMap =
new HashMap<>(key.length);
-
for (int i =
0; i < key.length; i++) {
-
if (paramMap.containsKey(key[i])) {
-
Object o = paramMap.remove(key[i]);
-
changeMap.put(val[i], o);
-
}
-
-
}
-
//合并
-
paramMap.putAll(changeMap);
-
return paramMap;
-
}
-
-
/**
-
* @param params 参数描述
-
* @param args 参数值,与params是对应的
-
*/
-
private
Map<
String,
Object> getAllParamsMap(Parameter[] params,
Object[] args ,
String[] exclude) {
-
Map<
String,
Object> map =
new HashMap<>(params.length);
-
for (int i =
0; i < params.length; i++) {
-
-
if (
null == args[i]) {
-
continue;
-
}
-
-
Annotation[] annotations = params[i].getAnnotations();
-
if (
null == annotations ||
0 == annotations.length) {
-
parse(args[i], i, map);
-
continue;
-
}
-
Annotation annotation = annotations[
0];
-
-
if (annotation
instanceof RequestParam) {
-
RequestParam requestParam = (RequestParam) annotation;
-
String paramName = paramName(requestParam);
-
map.put(paramName, args[i]);
-
}
-
if (annotation
instanceof PathVariable) {
-
PathVariable pathVariable = (PathVariable) annotation;
-
String paramName = paramName(pathVariable);
-
map.put(paramName, args[i]);
-
}
-
if (annotation
instanceof RequestBody) {
-
parse(args[i], i, map);
-
}
-
}
-
-
excludeMapKey(exclude, map);
-
-
return map;
-
}
-
-
/**根据 exclude 排除 map 中某些字段*/
-
private
void excludeMapKey(
String[] exclude,
Map<
String,
Object> map) {
-
if(
null != exclude && exclude.length >
0){
-
for(
String e : exclude){
-
if(map.containsKey(e)){
-
map.remove(e);
-
}
-
}
-
}
-
}
-
-
private
String paramName(PathVariable pathVariable) {
-
String value = pathVariable.value();
-
//因为@PathVariable的value和name互为别名的缘故
-
return
"".equals(value) ? pathVariable.name() : value;
-
}
-
-
private
String paramName(RequestParam requestParam) {
-
String value = requestParam.value();
-
//因为@RequestParam的value和name互为别名的缘故
-
return
"".equals(value) ? requestParam.name() : value;
-
}
-
-
private
void parse(
Object argParam, int i,
Map<
String,
Object> map) {
-
String arg =
String.valueOf(argParam);
-
boolean jsonObject = JsonUtil.isJsonObject(arg);
-
if (!jsonObject) {
-
map.put(
"param-" + i, arg);
-
}
else {
-
map.putAll(json2Map(arg));
-
}
-
}
-
-
/**
-
* 前端传输过来的json数据转换成map
-
*/
-
private
Map<
String,
Object> json2Map(
String json) {
-
return
new JSONObject(json).unwrap().getInnerMap();
-
}
-
-
-
}
-
/**
-
* @author xiongshiyan at 2018/11/2 , contact me with email yanshixiong@126.com or phone 15208384257
-
*/
-
public
class
AnnotationUtil {
-
/**
-
* 通过目标对象获取方法实例
-
*/
-
public static Method getMethod(JoinPoint joinPoint) {
-
Object target = joinPoint.getTarget();
-
//获取方法签名
-
Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();
-
try {
-
method = target.getClass().getMethod(method.getName(), method.getParameterTypes());
-
}
catch (Exception e) {
-
e.printStackTrace();
-
}
-
return method;
-
}
-
-
/**
-
* 获取方法的参数
-
*/
-
public static Parameter[] getParam(Method method){
-
if(
null == method){
-
return
new Parameter[
0];
-
}
-
return method.getParameters();
-
}
-
-
/**
-
* 获取注解
-
*/
-
public static LogOption getLogOption(Method method) {
-
if(
null == method){
-
return
null;
-
}
-
return method.isAnnotationPresent(LogOption.class) ? method.getAnnotation(LogOption.class) :
null;
-
}
-
}
转载自:https://blog.csdn.net/xxssyyyyssxx/article/details/84572517