统一接口设计及日志管理

对系统中的关键操作进行记录至关重要,尤其是在对某些重要业务或数据信息进行溯源时

日志的记录越详细越好,但出于性能及业务等因素考虑,侧重点会各有不同

最基本的记录至少要包括如下信息:

1.所操作的接口

2.操作人

3.操作时间及设备信息

4.进行了何种操作

5.操作是否成功

日志记录方式无非就两种

1.高度代码耦合:在业务逻辑中直接调用日志记录接口

2.采用AOP方式:AOP方式能和业务逻辑解耦

第1种方式基本被淘汰,介绍第2种方式

采用AOP方式记录日志,则要保证接口格式一致性,这样才能方便获取接口返回的相关信息

接口返回应该包括几个方面:

1.业务数据信息

2.执行状态

3.若失败还要返回错误码

4.若失败还要返回错误信息

同时为了方便统一日志记录,还应该在每个接口中返回具体的日志信息,不过不用展示出来

所以,基本格式应该如下:

1.成功时:

{
    "content": {
        "sessionId": "10009",
        "userId": 10,
        "userName": "肖昌伟",
        "lastOperateTime": "2017-08-01 15:47:42",
        "token": "2d7bb2f683704cdc8baa7ccd8e993c33",
        "ip": "192.168.0.1"
    },
    "status": "OK",
    "errorCode": null,
    "errorMsg": null
}

2.失败时:

{
    "errorCode": "000002",
    "errorMsg": "登录名[userName]不能为空, 当前值为[null]",
    "status": "ERROR"
}

在业务处理后要将具体的操作详情保存到返回接口里面(不用展示出来,只为了方便在aop中获取)

日志记录的其它相关信息,比如访问的是那个模块的哪个接口等,可以通过自定义注解方式来实现

package personal.changw.xiao.web.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;

import static java.lang.annotation.RetentionPolicy.RUNTIME;

/**
 * @since 2017年07月31日 上午11:02:29
 * @author 肖昌伟 changw.xiao@qq.com
 * @description 日志记录标签
 * 可以使用在方法或者类上,可以根据需要决定谁的优先级更高
 */
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RUNTIME)
public @interface LogRecord {

    String system() default "";

    String module() default "";

    String menuLv1() default "";

    String menuLv2() default "";

}

使用方式如下:

/**
     * 分页查找
     * @param keyWords        关键字
     * @param pageSize        每页条数 
     * @param pageNumber    页码
     * @param isPaging        是否分页(默认分页),为false时返回的条数信息请忽略
     * @return
     */
    @RequestMapping("/user/listByPage")
    @LogRecord(system="xxxx系统",module="基础信息管理",menuLv1="用户管理",menuLv2="用户列表查询")
    public Result listUserByPgae(@HibernateValidate UserQueryParam param) {
        //Page<UserInfo> page = new Page<UserInfo>(param.getIsPaging(), param.getPageNumber(), param.getPageSize());
        Page<UserInfo> page = new Page<UserInfo>(param);
        userService.listUserByPgae(page, param);
        return success(page,"查询用户列表,参数为:"+JSON.toJSONString(page));
    }

AOP执行逻辑如下

package personal.changw.xiao.web.aop;

import java.lang.reflect.Method;

import javax.servlet.http.HttpServletRequest;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;

import personal.changw.xiao.web.annotation.LogRecord;
import personal.changw.xiao.web.constant.Constants;
import personal.changw.xiao.web.service.LogRecordService;
import personal.changw.xiao.web.utils.IpUtil;
import personal.changw.xiao.web.vo.common.OperaterLogVo;
import personal.changw.xiao.web.vo.common.Result;
import personal.changw.xiao.web.vo.common.UserSession;

/**
 * @since 2017年07月31日 上午11:02:29
 * @author 肖昌伟 changw.xiao@qq.com
 * @description 日志记录AOP
 */
public class LogRecordAOP {

    private static final Logger LOGGER = LoggerFactory.getLogger(LogRecordAOP.class);
    
    @Autowired
    private LogRecordService logRecordService;

    @Autowired
    private HttpServletRequest request;

    public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
        //先查找类上的注解,没有再找方法上的注解
        LogRecord logRecord = joinPoint.getTarget().getClass().getAnnotation(LogRecord.class);
        if(logRecord == null){
            Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();
            logRecord = method.getAnnotation(LogRecord.class);
        }
        if (logRecord != null) {
            Result result = (Result) joinPoint.proceed();
            UserSession userSession = (UserSession) request.getAttribute(Constants.HTTP_ATTR_SESSION_KEY);
            if (userSession != null) {
                OperaterLogVo operatorLog = new OperaterLogVo();
                if(result != null) {
                    operatorLog.setRemarks(result.getStatus());
                    operatorLog.setLogContent(result.getLogContent());
                } else {
                    //系统中导出功能等返回结果为非Result的接口
                    //默认都为成功
                    operatorLog.setRemarks("OK");
                }
                operatorLog.setSystemName(logRecord.system());
                operatorLog.setModuleName(logRecord.module());
                operatorLog.setMenuLv1(logRecord.menuLv1());
                operatorLog.setMenuLv2(logRecord.menuLv2());
    
                String requestURI = request.getRequestURI().substring(request.getContextPath().length());
                operatorLog.setUrl(request.getProtocol().split("/")[0] + "://" + request.getServerName() + ":" + request.getServerPort() + request.getContextPath() + requestURI);
            
                operatorLog.setUserCode(String.valueOf(userSession.getUserId()));
                operatorLog.setUserName(userSession.getUserName());
                operatorLog.setIp(IpUtil.getRemoteRealIP(request));
                try {
                    logRecordService.insertOperatorLog(operatorLog);
                } catch (Exception e) {
                    LOGGER.error("日志记录出错:"+ e.getMessage());
                }
            }
            return result;
        } else {
            return joinPoint.proceed();
        }
    }

}

通过上面的操作,配置好切面后就可进行日志记录了

日志记录量是很大的,所以只记录关键地方并按期归档,最好是存在如elasticsearch、mongodb中,

如果存在数据库中,分表是不错的选择

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏算法工程师的养成之路

牛顿法与拟牛顿法

牛顿法和拟牛顿法是求解无约束最优化的常用方法,有收敛速度快的优点. 牛顿法属于迭代算法,每一步需要求解目标函数的海赛矩阵的逆矩阵,计算复杂. 拟牛顿法通过正定...

24620
来自专栏老欧说安卓

Android开发笔记(二十七)对象序列化

程序中存储和传递信息,需要有个合适的数据结构,最简单的是定义几个变量,变量多了之后再分门别类,便成了聚合若干变量的对象。代码在函数调用时可以直接传递对象,但...

10740
来自专栏刨根究底学编程

刨根究底字符编码之十三——UTF-16编码方式

UTF-16编码方式源于UCS-2(Universal Character Set coded in 2 octets、2-byte Universal Cha...

12840
来自专栏刨根究底学编程

刨根究底字符编码之十四——UTF-16究竟是怎么编码的

首先要注意的是,代理Surrogate是专属于UTF-16编码方式的一种机制,UTF-8和UTF-32是不用代理的。

13330
来自专栏老欧说安卓

Android开发笔记(十八)书籍翻页动画PageAnimation

前面几节的动画都算简单,本文就介绍一个复杂点的动画——书籍翻页动画。Android有自带的翻页动画ViewPager,不过V...

42340
来自专栏刨根究底学编程

刨根究底字符编码之十二——UTF-8究竟是怎么编码的

UTF-8编码是Unicode字符集的一种编码方式(CEF),其特点是使用变长字节数(即变长码元序列、变宽码元序列)来编码。一般是1到4个字节,当然,也可以更长...

14440
来自专栏指点的专栏

2018 团队设计天梯赛题解---华山论剑组

2018 年度的团队设计天梯赛前几天结束了。但是成绩真的是惨不忍睹。。。毕竟是团队的比赛,如果团队平均水平不高的话,单凭一个人,分再高也很难拉起来(当然,一个人...

87920
来自专栏Java学习资料

关于公共类中常见的静态方法需要调用spring注入的非静态变量的解决方案

当你编写一个需要调用mybatis的dao层的类时,会先通过spring依赖注入该变量,但是由于你需要用到该变量在静态方法中,所以无法使用,此时你将该变量改为静...

10800
来自专栏刨根究底学编程

刨根究底字符编码之零——前言

字符编码是计算机世界里最基础、最重要的一个主题之一。不过,在计算机教材中却往往浮光掠影般地草草带过,甚至连一本专门进行深入介绍的著作都找不到(对这一点我一直很困...

10520
来自专栏算法工程师的养成之路

数据降维(四)ISOMAP

Isomap(Isometric Feature Mapping)是流行学习的一种,用于非线性数据降维,是一种无监督算法.

12910

扫码关注云+社区

领取腾讯云代金券

年度创作总结 领取年终奖励