springboot整合自定义注解Log过程(借鉴ruoyi的common-log)整理思路
整合过程maven依赖新建自定义注解类 Log自定义切面Aspect中使用到的工具类enums类service层日志实体类后记maven依赖可能不全这个依赖,如果实在找不到你在留言我在找找给你<!-- Alibaba Fastjson 1.2.75--><dependency><groupId>com.alibaba</groupId><art
·
maven依赖
可能不全这个依赖,如果实在找不到你在留言我在找找给你
<!-- Alibaba Fastjson 1.2.75-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
</dependency>
<!-- Apache Lang3 3.10-->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
<!-- Commons Io 2.5-->
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
</dependency>
<!-- AOP 2.3.7.RELEASE logging版本 一致-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</dependency>
<!-- SpringBoot Web 2.3.7.RELEASE-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- lombook 简化开发 1.18.16-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!-- 增加hutool工具包 5.6.3-->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
</dependency>
<!-- swagger 依赖 这个我觉得不需要 你看着办 -->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
</dependency>
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>swagger-bootstrap-ui</artifactId>
</dependency>
<!--我查了查我自己使用的maven依赖 被父类概括了-->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.6.12</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.6.12</version>
</dependency>
新建自定义注解类 Log
/**
*这两个enums我也贴在下边了,需要的话可以去下边找
*/
import com.common.log.enums.BusinessType;
import com.common.log.enums.OperatorType;
import java.lang.annotation.*;
/**
* 自定义日志注解
*
* @author hanfeng,这个是借鉴若依的自定义注解,所以在此记录
*
*/
@Target({ ElementType.PARAMETER, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Log
{
/**
* 模块
*/
public String title() default "";
/**
* 功能
*/
public BusinessType businessType() default BusinessType.OTHER;
}
自定义切面
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSON;
import com.common.core.exception.NcException;
import com.common.core.utils.IpUtil;
import com.common.core.utils.SecurityUtils;
import com.common.core.utils.ServletUtils;
import com.common.log.annotation.Log;
import com.common.log.service.AsyncLogService;
import com.inter.entity.SysOperLog;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpMethod;
import org.springframework.stereotype.Component;
import org.springframework.validation.BindingResult;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.Iterator;
import java.util.Map;
/**
* 切面日志记录
*
* @author hanfeng
*/
@Aspect
@Component
@Slf4j
public class LogAspect
{
/** 这个也是自定义的类 */
@Autowired
private AsyncLogService asyncLogService;
/**
* 配置织入点
* 用于匹配当前执行方法持有指定注解的方法;
*/
@Pointcut("@annotation(com.zhby.common.log.annotation.Log)")
public void logPointCut()
{
}
/**
* 处理完请求后执行
*
* @param joinPoint 切点
*/
@AfterReturning(pointcut = "logPointCut()", returning = "jsonResult")
public void doAfterReturning(JoinPoint joinPoint, Object jsonResult)
{
handleLog(joinPoint, null, jsonResult);
}
/**
* 拦截异常操作
*
* @param joinPoint 切点 这个 你可以理解为返回的参数接受
* @param e 异常
*/
@AfterThrowing(value = "logPointCut()", throwing = "e")
public void doAfterThrowing(JoinPoint joinPoint, Exception e)
{
handleLog(joinPoint, e, null);
}
protected void handleLog(final JoinPoint joinPoint, final Exception e, Object jsonResult)
{
try
{
// 获得注解
Log controllerLog = getAnnotationLog(joinPoint);
if (controllerLog == null)
{
return;
}
// *========数据库日志=========*//
/** 这个也是自定义的日志类 无需理解 */
SysOperLog operLog = new SysOperLog();
//操作状态 是否成功
operLog.setIsSuccess(true);
// 请求的地址
String ip = IpUtil.getIpAddress(ServletUtils.getRequest());
operLog.setOperIp(ip);
/** 通过ip获取地区名
这个IPutil中我我自定义封装了两个方法,待会我会贴后后边。*/
operLog.setOperLocation(IpUtil.getPositionByIp(ip));
// 返回参数
operLog.setResponseParam(JSON.toJSONString(jsonResult));
/** 这个也是自定义的方法我待会 贴出来 */
operLog.setRequestUrl(ServletUtils.getRequest().getRequestURI());
/** 同上 */
String username = SecurityUtils.getUsername();
if (StrUtil.isNotBlank(username))
{
operLog.setRequestParam(username);
}
/**判断异常时改的参数值*/
if (e != null)
{
operLog.setIsSuccess(false);
operLog.setResponseParam(operLog.getResponseParam()
+StringUtils.substring(e.getMessage(),0,1900));
}
// 设置方法名称
String className = joinPoint.getTarget().getClass().getName();
String methodName = joinPoint.getSignature().getName();
//操作方式请求方法
operLog.setOperMethod(className + "." + methodName + "()");
// 设置请求方式
operLog.setRequestMethod(ServletUtils.getRequest().getMethod());
// 处理设置注解上的参数
getControllerMethodDescription(joinPoint, controllerLog, operLog);
/** 在这里是使用远程调用fegin的方式保存到数据库 */
asyncLogService.saveSysLog(operLog);
}
catch (Exception exp)
{
// 记录本地异常日志
log.error("切面日志消息通知:", exp.getMessage());
exp.printStackTrace();
}
}
/**
* 获取注解中对方法的描述信息 用于Controller层注解
*
* @param log 日志
* @param operLog 操作日志
* @throws Exception
*/
public void getControllerMethodDescription(JoinPoint joinPoint, Log log,
SysOperLog operLog) throws Exception
{
// 设置action动作(添加删除修改查询等操作)
operLog.setOperType(log.businessType().ordinal()+"");
// 设置标题
operLog.setTitle(log.title());
// 是否需要保存request,参数和值
if (log.isSaveRequestData())
{
// 获取参数的信息,传入到数据库中。
setRequestValue(joinPoint, operLog);
}
}
/**
* 获取请求的参数,放到log中
*
* @param operLog 操作日志
* @throws Exception 异常
*/
private void setRequestValue(JoinPoint joinPoint, SysOperLog operLog) throws Exception
{
String requestMethod = operLog.getRequestMethod();
if (HttpMethod.PUT.name().equals(requestMethod) || HttpMethod.POST.name().equals(requestMethod))
{
String params = argsArrayToString(joinPoint.getArgs());
operLog.setRequestParam(StringUtils.substring(params, 0, 2000));
}
}
/**
* 是否存在注解,如果存在就获取
*/
private Log getAnnotationLog(JoinPoint joinPoint) throws Exception
{
Signature signature = joinPoint.getSignature();
MethodSignature methodSignature = (MethodSignature) signature;
Method method = methodSignature.getMethod();
if (method != null)
{
return method.getAnnotation(Log.class);
}
return null;
}
/**
* 参数拼装
*/
private String argsArrayToString(Object[] paramsArray)
{
String params = "";
if (paramsArray != null && paramsArray.length > 0)
{
for (int i = 0; i < paramsArray.length; i++)
{
if (!isFilterObject(paramsArray[i]))
{
try
{
Object jsonObj = JSON.toJSON(paramsArray[i]);
params += jsonObj.toString() + " ";
}
catch (Exception e)
{
}
}
}
}
return params.trim();
}
/**
* 判断是否需要过滤的对象。
*
* @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 (Iterator iter = collection.iterator(); iter.hasNext();)
{
return iter.next() instanceof MultipartFile;
}
}
else if (Map.class.isAssignableFrom(clazz))
{
Map map = (Map) o;
for (Iterator iter = map.entrySet().iterator(); iter.hasNext();)
{
Map.Entry entry = (Map.Entry) iter.next();
return entry.getValue() instanceof MultipartFile;
}
}
return o instanceof MultipartFile || o instanceof HttpServletRequest || o instanceof HttpServletResponse
|| o instanceof BindingResult;
}
}
Aspect中使用到的工具类
iPutil 工具类
/**
* @date 2019/11/6 18:40
* @author hanfeng
*/
public class IpUtil {
public static String getIpAddress(HttpServletRequest request) {
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("WL-Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("HTTP_CLIENT_IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("HTTP_X_FORWARDED_FOR");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getRemoteAddr();
}
return ip;
}
/**
* 根据ip获取归属地
* @param ip
* http://pv.sohu.com/cityjson?ie=utf-8 获取当前电脑的ip地址
* @return
*/
public static String getAddress(String ip) {
String url = "http://ip.ws.126.net/ipquery?ip=" + ip;
/**
* 因为这种方法虽然方便但是没那么只能对外的ip还能返回正确的地区像局域网这种就不太行了
* 所以有了下边高德的api调用,其实也可以用百度都是一样的,都特别方便
*/
if("127.0.0.1".equals(ip)){
url = "http://pv.sohu.com/cityjson?ie=utf-8";
}
String str = HttpUtil.get(url);
if(StrUtils.hasText(str)){
String substring = str.substring(str.indexOf("{"), str.indexOf("}")+1);
JSONObject jsonObject = JSONUtil.parseObj(substring);
String province = jsonObject.getStr("province");
String city = jsonObject.getStr("city");
return "省份:" + province + ",城市:" + city;
}
return null;
}
// 高德地图key,这个个人去申请一个就好了每天有限额一般够用了
private static final String GDMAP_KEY = "高德地图的key";
/**
* 获取当前Ip的地理位置,XX省XX市。没有参数,默认发出请求的ip地址
* @param ip 每日限额30000次 ip2.0 ip1.0限额300000次
* @return resultStr
* 这里引用的是ip2.0版本的 在这一直还有一个版本
* https://restapi.amap.com/v3/ip?parameters
* 具体怎么用去高德官网看看
*/
public static String getPositionByIp(String ip){
// 请求url
String url = "https://restapi.amap.com/v5/ip";
// 初始化请求参数
Map<String,Object> params = new HashMap<>();
// 初始化结果集
String result = "";
// 添加参数
if(StringUtils.isNotBlank(ip)){
params.put("ip",ip);
}
params.put("key",GDMAP_KEY);
params.put("output","JSON");
params.put("type","4");
String resultStr = HttpUtil.get(url, params);
JSONObject jsonObject = JSONUtil.parseObj(resultStr);
if("1".equals(jsonObject.getStr("status"))){
result+=jsonObject.getStr("country")+jsonObject.getStr("city")+jsonObject.getStr("province");
}else{
result = "获取位置失败";
}
return result;
}
servletutils 这个是若依的工具类
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.util.Enumeration;
import java.util.LinkedHashMap;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import cn.hutool.core.convert.Convert;
import com.zhby.common.core.constants.Constant;
import com.zhby.common.core.constants.Constant;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
/**
* 客户端工具类
*
* @author ruoyi
*/
public class ServletUtils
{
/**
* 获取request
*/
public static HttpServletRequest getRequest()
{
try
{
HttpServletRequest request = getRequestAttributes().getRequest();
return request;
}
catch (Exception e)
{
return null;
}
}
/**
* 内容解码
*
* @param str 内容
* @return 解码后的内容
*/
public static String urlDecode(String str)
{
try
{
return URLDecoder.decode(str, Constant.UTF8);
}
catch (UnsupportedEncodingException e)
{
return "";
}
}
}
SecurityUtils 工具类(若依)
/**
* 权限获取工具类
*
* @author ruoyi
*/
public class SecurityUtils
{
/**
* 获取用户
*/
public static String getUsername()
{
HttpServletRequest request = ServletUtils.getRequest();
/** 这个是获取的值是自定义的 CacheConstants.DETAILS_USERNAME == username */
String username = request.getHeader(CacheConstants.DETAILS_USERNAME);
String name = "";
if(StrUtils.hasText(username)){
// 这个方法我已经放在上边servletutils的类中了
name = ServletUtils.urlDecode(username);
}
return name;
}
}
enums类
BusinessType类
/**
* 业务操作类型
*
* @author ruoyi
*/
public enum BusinessType
{
/**
* 其它
*/
OTHER,
/**
* 新增
*/
INSERT,
/**
* 修改
*/
UPDATE,
/**
* 删除
*/
DELETE,
/**
* 授权
*/
GRANT,
/**
* 导出
*/
EXPORT,
/**
* 导入
*/
IMPORT,
/**
* 强退
*/
FORCE,
/**
* 生成代码
*/
GENCODE,
/**
* 清空数据
*/
CLEAN,
}
service层
AsyncLogService类
//这两个都是自定义的值,一个远程fegin的接口,一个是实体类
import com.inter.client.SysOperLogClient;
import com.inter.entity.SysOperLog;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
/**
* 异步调用日志服务
*
* @author hanfeng
*/
@Service
public class AsyncLogService
{
@Autowired
private SysOperLogClient sysOperLogClient;
/**
* 保存系统日志记录
* 加上异步操作注解在远程fegin配置身份时无法获取到请求头得token
*/
//@Async 这个注解的解释在上边,异步调用的线程问题导致远程fegin无法获取到token导致没有身份
public void saveSysLog(SysOperLog sysOperLog)
{
sysOperLogClient.saveLog(sysOperLog);
}
}
日志实体类
SysOperLog 类
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.io.Serializable;
/**
* @author hanfeng
* 这个是@API开头的注解是swagger2.0中的可有可无 看你需要不需要了
* 不需要直接去掉就行 做个参考
*/
@ApiModel(value = "com.sys.entity.SysOperLog")
@Data
@TableName(value = "sys_oper_log")
public class SysOperLog implements Serializable {
/**
* 主键
*/
@TableId(value = "id", type = IdType.ASSIGN_UUID)
@ApiModelProperty(value = "主键")
private String id;
/**
* 模块标题
*/
@TableField(value = "title")
@ApiModelProperty(value = "模块标题")
private String title;
/**
* 操作类别
*/
@TableField(value = "oper_type")
@ApiModelProperty(value = "操作类别")
private String operType;
/**
* 操作方法
*/
@TableField(value = "oper_method")
@ApiModelProperty(value = "操作方法")
private String operMethod;
/**
* 请求方式
*/
@TableField(value = "request_method")
@ApiModelProperty(value = "请求方式")
private String requestMethod;
/**
* 请求url
*/
@TableField(value = "request_url")
@ApiModelProperty(value = "请求url")
private String requestUrl;
/**
* 请求参数
*/
@TableField(value = "request_param")
@ApiModelProperty(value = "请求参数")
private String requestParam;
/**
* 响应参数
*/
@TableField(value = "response_param")
@ApiModelProperty(value = "响应参数")
private String responseParam;
/**
* 是否成功
*/
@TableField(value = "is_success")
@ApiModelProperty(value = "是否成功")
private Boolean isSuccess;
/**
* 主机地址
*/
@TableField(value = "oper_ip")
@ApiModelProperty(value = "主机地址")
private String operIp;
/**
* 操作地点
*/
@TableField(value = "oper_location")
@ApiModelProperty(value = "操作地点")
private String operLocation;
private static final long serialVersionUID = 1L;
后记
那个服务需要调用的时候记得 加上
// 表示通过aop框架暴露该代理对象,AopContext能够访问
@EnableAspectJAutoProxy(exposeProxy = true)
如果时远程fegin调用的话,记得 让 启动类可以扫描到fegin 的接口 。
或者在注解中指定扫描的包路径
@EnableFeignClients
更多推荐
所有评论(0)