统一接口设计及日志管理
对系统中的关键操作进行记录至关重要,尤其是在对某些重要业务或数据信息进行溯源时
日志的记录越详细越好,但出于性能及业务等因素考虑,侧重点会各有不同
最基本的记录至少要包括如下信息:
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中,
如果存在数据库中,分表是不错的选择