🧑🎓 个人主页:Silence Lamb
📖 本章内容:【使用日志注解记录操作日志】
1、自定义操作日志记录注解
/**
* @author 沉默的羔羊
* @apiNote 自定义操作日志记录注解
*/
@Target({ElementType.PARAMETER, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Log {
@ApiModelProperty("模块")
String title() default "";
@ApiModelProperty("功能")
BusinessType businessType() default BusinessType.OTHER;
@ApiModelProperty("操作人类别")
OperatorType operatorType() default OperatorType.MANAGE;
@ApiModelProperty("是否保存请求的参数")
boolean isSaveRequestData() default true;
@ApiModelProperty("是否保存响应的参数")
boolean isSaveResponseData() default true;
}
🕰️ 业务操作类型
/**
* @author 沉默的羔羊
* @apiNote 业务操作类型
*/
public enum BusinessType {
/**
* 其它
*/
OTHER,
/**
* 新增
*/
INSERT,
/**
* 修改
*/
UPDATE,
/**
* 删除
*/
DELETE,
/**
* 授权
*/
GRANT,
/**
* 导出
*/
EXPORT,
/**
* 导入
*/
IMPORT,
/**
* 强退
*/
FORCE,
/**
* 清空数据
*/
CLEAN,
}
🕰️ 操作人类别
/**
* @author 沉默的羔羊
* @apiNote 操作人类别
*/
public enum OperatorType {
/**
* 其它
*/
OTHER,
/**
* 后台用户
*/
MANAGE,
/**
* 手机端用户
*/
MOBILE
}
2、操作日志记录处理
/**
* @author 沉默的羔羊
* @apiNote 操作日志记录处理
*/
@Aspect
@Component
public class LogAspect {
private static final Logger LogAspect = LoggerFactory.getLogger(LogAspect.class);
@ApiModelProperty("排除敏感字段")
public static final String[] EXCLUDE_PROPERTIES = {"password", "oldPassword", "newPassword", "confirmPassword"};
/**
* 处理完请求后执行
*
* @param joinPoint 切点
*/
@AfterReturning(pointcut = "@annotation(controllerLog)", returning = "jsonResult")
public void doAfterReturning(JoinPoint joinPoint, Log controllerLog, Object jsonResult) {
handleLog(joinPoint, controllerLog, null, jsonResult);
}
/**
* 拦截异常操作
*
* @param joinPoint 切点
* @param e 异常
*/
@AfterThrowing(value = "@annotation(controllerLog)", throwing = "e")
public void doAfterThrowing(JoinPoint joinPoint, Log controllerLog, Exception e) {
handleLog(joinPoint, controllerLog, e, null);
}
protected void handleLog(final JoinPoint joinPoint, Log controllerLog, final Exception e, Object jsonResult) {
try {
// 获取当前的用户
LoginUserDetail loginUser = SecurityUtils.getLoginUser();
// *========数据库日志=========*//
SysOperateLog sysOperateLog = new SysOperateLog();
sysOperateLog.setStatus(BusinessStatus.SUCCESS.ordinal());
// 请求的地址
String ip = IpUtils.getIpAddress(ServletUtils.getRequest());
sysOperateLog.setOperateIp(ip);
sysOperateLog.setOperateUrl(StringUtils.substring(ServletUtils.getRequest().getRequestURI(), 0, 255));
if (loginUser != null) {
sysOperateLog.setOperateName(loginUser.getUsername());
}
if (e != null) {
sysOperateLog.setStatus(BusinessStatus.FAIL.ordinal());
sysOperateLog.setErrorMsg(StringUtils.substring(e.getMessage(), 0, 2000));
}
// 设置方法名称
String className = joinPoint.getTarget().getClass().getName();
String methodName = joinPoint.getSignature().getName();
sysOperateLog.setMethod(className + "." + methodName + "()");
// 设置请求方式
sysOperateLog.setRequestMethod(ServletUtils.getRequest().getMethod());
// 处理设置注解上的参数
getControllerMethodDescription(joinPoint, controllerLog, sysOperateLog, jsonResult);
// 保存数据库
AsyncManager.asyncManager().start(AsyncTask.recordLog(sysOperateLog));
} catch (Exception exp) {
// 记录本地异常日志
LogAspect.error("==前置通知异常==");
LogAspect.error("异常信息:{}", exp.getMessage());
exp.printStackTrace();
}
}
/**
* 获取注解中对方法的描述信息 用于Controller层注解
*
* @param log 日志
* @param sysOperateLog 操作日志
* @throws Exception
*/
public void getControllerMethodDescription(JoinPoint joinPoint, Log log, SysOperateLog sysOperateLog, Object jsonResult) throws Exception {
// 设置action动作
sysOperateLog.setBusinessType(log.businessType().ordinal());
// 设置标题
sysOperateLog.setTitle(log.title());
// 设置操作人类别
sysOperateLog.setOperatorType(log.operatorType().ordinal());
// 是否需要保存request,参数和值
if (log.isSaveRequestData()) {
// 获取参数的信息,传入到数据库中。
setRequestValue(joinPoint, sysOperateLog);
}
// 是否需要保存response,参数和值
if (log.isSaveResponseData() && StringUtils.isNotNull(jsonResult)) {
sysOperateLog.setJsonResult(StringUtils.substring(JSON.toJSONString(jsonResult), 0, 2000));
}
}
/**
* 获取请求的参数,放到log中
*
* @param sysOperateLog 操作日志
* @throws Exception 异常
*/
private void setRequestValue(JoinPoint joinPoint, SysOperateLog sysOperateLog) throws Exception {
String requestMethod = sysOperateLog.getRequestMethod();
if (HttpMethod.PUT.name().equals(requestMethod) || HttpMethod.POST.name().equals(requestMethod)) {
String params = argsArrayToString(joinPoint.getArgs());
sysOperateLog.setOperateParam(StringUtils.substring(params, 0, 2000));
} else {
Map<?, ?> paramsMap = (Map<?, ?>) ServletUtils.getRequest().getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE);
sysOperateLog.setOperateParam(StringUtils.substring(paramsMap.toString(), 0, 2000));
}
}
/**
* 参数拼装
*/
private String argsArrayToString(Object[] paramsArray) {
String params = "";
if (paramsArray != null && paramsArray.length > 0) {
for (Object o : paramsArray) {
if (StringUtils.isNotNull(o) && !isFilterObject(o)) {
try {
String jsonObj = JSON.toJSONString(o, excludePropertyPreFilter());
params += jsonObj.toString() + " ";
} catch (Exception e) {
}
}
}
}
return params.trim();
}
/**
* 忽略敏感属性
*/
public PropertyPreExcludeFilter excludePropertyPreFilter() {
return new PropertyPreExcludeFilter().addExcludes(EXCLUDE_PROPERTIES);
}
/**
* 判断是否需要过滤的对象。
*
* @param o 对象信息。
* @return 如果是需要过滤的对象,则返回true;否则返回false。
*/
@SuppressWarnings("rawtypes")
public boolean isFilterObject(final Object o) {
Class<?> clazz = o.getClass();
if (clazz.isArray()) {
return clazz.getComponentType().isAssignableFrom(MultipartFile.class);
} else if (Collection.class.isAssignableFrom(clazz)) {
Collection collection = (Collection) o;
for (Object value : collection) {
return value instanceof MultipartFile;
}
} else if (Map.class.isAssignableFrom(clazz)) {
Map map = (Map) o;
for (Object value : map.entrySet()) {
Map.Entry entry = (Map.Entry) value;
return entry.getValue() instanceof MultipartFile;
}
}
return o instanceof MultipartFile || o instanceof HttpServletRequest || o instanceof HttpServletResponse
|| o instanceof BindingResult;
}
}
🍵 获取IP工具类
/**
* @author 沉默的羔羊
* @apiNote 获取IP工具类
*/
@Component
public class IpUtils {
/**
* 获取客户端IP
*
* @param request 请求对象
* @return IP地址
*/
public static String getIpAddress(HttpServletRequest request) {
if (request == null) {
return "unknown";
}
String ip = request.getHeader("x-forwarded-for");
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("X-Forwarded-For");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("WL-Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("X-Real-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getRemoteAddr();
}
return "0:0:0:0:0:0:0:1".equals(ip) ? "127.0.0.1" : getMultistageReverseProxyIp(ip);
}
/**
* 检查是否为内部IP地址
*
* @param ip IP地址
* @return 结果
*/
public static boolean internalIp(String ip) {
byte[] address = textToNumericFormatV4(ip);
return internalIp(address) || "127.0.0.1".equals(ip);
}
/**
* 检查是否为内部IP地址
*
* @param address byte地址
* @return 结果
*/
private static boolean internalIp(byte[] address) {
if (StringUtils.isNull(address) || address.length < 2) {
return true;
}
final byte b0 = address[0];
final byte b1 = address[1];
// 10.x.x.x/8
final byte SECTION_1 = 0x0A;
// 172.16.x.x/12
final byte SECTION_2 = (byte) 0xAC;
final byte SECTION_3 = (byte) 0x10;
final byte SECTION_4 = (byte) 0x1F;
// 192.168.x.x/16
final byte SECTION_5 = (byte) 0xC0;
final byte SECTION_6 = (byte) 0xA8;
switch (b0) {
case SECTION_1:
return true;
case SECTION_2:
if (b1 >= SECTION_3 && b1 <= SECTION_4) {
return true;
}
case SECTION_5:
switch (b1) {
case SECTION_6:
return true;
}
default:
return false;
}
}
/**
* 将IPv4地址转换成字节
*
* @param text IPv4地址
* @return byte 字节
*/
public static byte[] textToNumericFormatV4(String text) {
if (text.length() == 0) {
return null;
}
byte[] bytes = new byte[4];
String[] elements = text.split("\.", -1);
try {
long l;
int i;
switch (elements.length) {
case 1:
l = Long.parseLong(elements[0]);
if ((l < 0L) || (l > 4294967295L)) {
return null;
}
bytes[0] = (byte) (int) (l >> 24 & 0xFF);
bytes[1] = (byte) (int) ((l & 0xFFFFFF) >> 16 & 0xFF);
bytes[2] = (byte) (int) ((l & 0xFFFF) >> 8 & 0xFF);
bytes[3] = (byte) (int) (l & 0xFF);
break;
case 2:
l = Integer.parseInt(elements[0]);
if ((l < 0L) || (l > 255L)) {
return null;
}
bytes[0] = (byte) (int) (l & 0xFF);
l = Integer.parseInt(elements[1]);
if ((l < 0L) || (l > 16777215L)) {
return null;
}
bytes[1] = (byte) (int) (l >> 16 & 0xFF);
bytes[2] = (byte) (int) ((l & 0xFFFF) >> 8 & 0xFF);
bytes[3] = (byte) (int) (l & 0xFF);
break;
case 3:
for (i = 0; i < 2; ++i) {
l = Integer.parseInt(elements[i]);
if ((l < 0L) || (l > 255L)) {
return null;
}
bytes[i] = (byte) (int) (l & 0xFF);
}
l = Integer.parseInt(elements[2]);
if ((l < 0L) || (l > 65535L)) {
return null;
}
bytes[2] = (byte) (int) (l >> 8 & 0xFF);
bytes[3] = (byte) (int) (l & 0xFF);
break;
case 4:
for (i = 0; i < 4; ++i) {
l = Integer.parseInt(elements[i]);
if ((l < 0L) || (l > 255L)) {
return null;
}
bytes[i] = (byte) (int) (l & 0xFF);
}
break;
default:
return null;
}
} catch (NumberFormatException e) {
return null;
}
return bytes;
}
/**
* 获取IP地址
*
* @return 本地IP地址
*/
public static String getHostIp() {
try {
return InetAddress.getLocalHost().getHostAddress();
} catch (UnknownHostException e) {
}
return "127.0.0.1";
}
/**
* 获取主机名
*
* @return 本地主机名
*/
public static String getHostName() {
try {
return InetAddress.getLocalHost().getHostName();
} catch (UnknownHostException e) {
}
return "未知";
}
/**
* 从多级反向代理中获得第一个非unknown IP地址
*
* @param ip 获得的IP地址
* @return 第一个非unknown IP地址
*/
public static String getMultistageReverseProxyIp(String ip) {
// 多级反向代理检测
if (ip != null && ip.indexOf(",") > 0) {
final String[] ips = ip.trim().split(",");
for (String subIp : ips) {
if (false == isUnknown(subIp)) {
ip = subIp;
break;
}
}
}
return ip;
}
/**
* 检测给定字符串是否为未知,多用于检测HTTP请求相关
*
* @param checkString 被检测的字符串
* @return 是否未知
*/
public static boolean isUnknown(String checkString) {
return StringUtils.isBlank(checkString) || "unknown".equalsIgnoreCase(checkString);
}
}
🍵 异步工厂工具
/**
* @author 沉默的羔羊
* @apiNote 异步工厂(产生任务用)
*/
public class AsyncTask {
private static final Logger sys_user_logger = LoggerFactory.getLogger("sys-AsyncTask");
/**
* 记录登录信息
*
* @param username 用户名
* @param status 状态
* @param message 消息
* @param args 列表
* @return 任务task
*/
public static TimerTask recordLoginInfo(final String username, final String status, final String message,
final Object... args) {
final UserAgent userAgent = UserAgent.parseUserAgentString(ServletUtils.getRequest().getHeader("User-Agent"));
final String ip = IpUtils.getIpAddress(ServletUtils.getRequest());
return new TimerTask() {
@Override
public void run() {
String address = AddressUtils.getRealAddressByIP(ip);
StringBuilder s = new StringBuilder();
s.append(LogUtils.getBlock(ip));
s.append(address);
s.append(LogUtils.getBlock(username));
s.append(LogUtils.getBlock(status));
s.append(LogUtils.getBlock(message));
// 打印信息到日志
sys_user_logger.info(s.toString(), args);
// 获取客户端操作系统
String os = userAgent.getOperatingSystem().getName();
// 获取客户端浏览器
String browser = userAgent.getBrowser().getName();
// 封装对象
SysLoginInfo sysLoginInfo = new SysLoginInfo();
sysLoginInfo.setUserName(username);
sysLoginInfo.setIpaddr(ip);
sysLoginInfo.setLoginLocation(address);
sysLoginInfo.setBrowser(browser);
sysLoginInfo.setOs(os);
sysLoginInfo.setMsg(message);
// 日志状态
if (StringUtils.equalsAny(status, CommonConstant.LOGIN_SUCCESS, CommonConstant.LOGOUT, CommonConstant.REGISTER)) {
sysLoginInfo.setStatus(CommonConstant.SUCCESS);
} else if (CommonConstant.LOGIN_FAIL.equals(status)) {
sysLoginInfo.setStatus(CommonConstant.FAIL);
}
// 插入数据
SpringUtils.getBean(SysLoginInfoService.class).insertLoginInfo(sysLoginInfo);
}
};
}
/**
* 操作日志记录
*
* @param sysOperaLog 操作日志信息
* @return 任务task
*/
public static TimerTask recordLog(final SysOperateLog sysOperaLog) {
return new TimerTask() {
@Override
public void run() {
// 远程查询操作地点
sysOperaLog.setOperateLocation(AddressUtils.getRealAddressByIP(sysOperaLog.getOperateIp()));
SpringUtils.getBean(SysOperateLogService.class).insertOperable(sysOperaLog);
}
};
}
}
📢🌥️如果文章对你有帮助【关注👍点赞❤️收藏⭐】