1. 建接口日志表
CREATE TABLE `sys_operate_log` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '日志主键',
`title` varchar(50) DEFAULT '' COMMENT '模块标题',
`business_type` int(2) DEFAULT '4' COMMENT '业务类型(0查询 1新增 2修改 3删除 4其他)',
`method` varchar(100) DEFAULT '' COMMENT '方法名称',
`resp_time` bigint(20) DEFAULT NULL COMMENT '响应时间',
`request_method` varchar(10) DEFAULT '' COMMENT '请求方式',
`browser` varchar(255) DEFAULT NULL COMMENT '浏览器类型',
`operate_type` int(1) DEFAULT '3' COMMENT '操作类别(0网站用户 1后台用户 2小程序 3其他)',
`operate_url` varchar(255) DEFAULT '' COMMENT '请求URL',
`operate_ip` varchar(128) DEFAULT '' COMMENT '主机地址',
`operate_location` varchar(255) DEFAULT '' COMMENT '操作地点',
`operate_param` text COMMENT '请求参数',
`json_result` text COMMENT '返回参数',
`status` int(1) DEFAULT '0' COMMENT '操作状态(0正常 1异常)',
`error_msg` text COMMENT '错误消息',
`create_id` bigint(20) DEFAULT NULL COMMENT '操作人id',
`create_name` varchar(50) DEFAULT '' COMMENT '操作人员',
`create_time` datetime DEFAULT NULL COMMENT '操作时间',
`update_id` bigint(20) NULL DEFAULT NULL COMMENT '更新人id',
`update_name` varchar(64) NULL DEFAULT '' COMMENT '更新者',
`update_time` datetime NULL DEFAULT NULL COMMENT '更新时间',
PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC COMMENT='系统管理-操作日志记录';
2. 实体类,也可以MybatisPlus生成
import com.baomidou.mybatisplus.annotation.TableName;
import com.maple.demo.config.bean.BaseEntity;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
@TableName("sys_operate_log")
@ApiModel(value = "OperateLog对象", description = "系统管理-操作日志记录")
public class OperateLog extends BaseEntity {
private static final long serialVersionUID = 1L;
@ApiModelProperty("模块标题")
private String title;
@ApiModelProperty("业务类型(0查询 1新增 2修改 3删除 4其他)")
private Integer businessType;
@ApiModelProperty("方法名称")
private String method;
@ApiModelProperty("响应时间")
private Long respTime;
@ApiModelProperty("请求方式")
private String requestMethod;
@ApiModelProperty("浏览器类型")
private String browser;
@ApiModelProperty("操作类别(0网站用户 1后台用户 2小程序 3其他)")
private Integer operateType;
@ApiModelProperty("请求URL")
private String operateUrl;
@ApiModelProperty("主机地址")
private String operateIp;
@ApiModelProperty("操作地点")
private String operateLocation;
@ApiModelProperty("请求参数")
private String operateParam;
@ApiModelProperty("返回参数")
private String jsonResult;
@ApiModelProperty("操作状态(0正常 1异常)")
private Integer status;
@ApiModelProperty("错误消息")
private String errorMsg;
}
3. 业务类型枚举类 BusinessTypeEnum
public enum BusinessTypeEnum {
// 0查询 1新增 2修改 3删除 4其他
SELECT,
INSERT,
UPDATE,
DELETE,
OTHER
}
4. 操作用户类型枚举类 OperateTypeEnum
public enum OperateTypeEnum {
// 0网站用户 1后台用户 2小程序 3其他
BLOG,
ADMIN,
APP,
OTHER
}
5. 配置自定义注解,为需要生成日志的接口添加注解
import com.maple.common.enums.BusinessTypeEnum;
import com.maple.common.enums.OperateTypeEnum;
import java.lang.annotation.*;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MapleLog {
// 0网站用户 1后台用户 2小程序 3其他
OperateTypeEnum operateType() default OperateTypeEnum.OTHER;
// 0查询 1新增 2修改 3删除 4其他
BusinessTypeEnum businessType() default BusinessTypeEnum.SELECT;
// 返回保存结果是否落库,没用的大结果可以不记录,比如分页查询等等,设为false即可
boolean saveResult() default true;
}
6. AOP功能实现
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.yang.demo.common.model.MapleLog;
import com.yang.demo.config.bean.GlobalConfig;
import com.yang.demo.entity.OperateLog;
import com.yang.demo.mapper.OperateLogMapper;
import com.yang.demo.util.JwtUtil;
import io.swagger.annotations.ApiOperation;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.Date;
import java.util.Objects;
@Aspect
@Component
@Slf4j
@AllArgsConstructor
public class SystemLogAspect {
private final OperateLogMapper operateLogMapper;
// 配置切面,使用注解配置
@Pointcut(value = "@annotation(com.yang.demo.common.model.MapleLog)")
public void systemLog() {
// nothing
}
@Around(value = "systemLog()")
public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
int maxTextLength = 65000;
Object obj;
// 定义执行开始时间
long startTime;
// 定义执行结束时间
long endTime;
HttpServletRequest request = ((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest();
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
// 取swagger的描述信息
ApiOperation apiOperation = method.getAnnotation(ApiOperation.class);
MapleLog mapleLog = method.getAnnotation(MapleLog.class);
OperateLog operateLog = new OperateLog();
try {
operateLog.setBrowser(request.getHeader("USER-AGENT"));
operateLog.setOperateUrl(request.getRequestURI());
operateLog.setRequestMethod(request.getMethod());
operateLog.setMethod(String.valueOf(joinPoint.getSignature()));
operateLog.setCreateTime(new Date());
operateLog.setOperateIp(getIpAddress(request));
// 取JWT的登录信息,无需登录可以忽略
if (request.getHeader(GlobalConfig.TOKEN_NAME) != null) {
operateLog.setCreateName(JwtUtil.getAccount());
operateLog.setCreateId(JwtUtil.getUserId());
}
String operateParam = JSON.toJSONStringWithDateFormat(joinPoint.getArgs(), "yyyy-MM-dd HH:mm:ss", SerializerFeature.WriteMapNullValue);
if (operateParam.length() > maxTextLength) {
operateParam = operateParam.substring(0, maxTextLength);
}
operateLog.setOperateParam(operateParam);
if (apiOperation != null) {
operateLog.setTitle(apiOperation.value() + "");
}
if (mapleLog != null) {
operateLog.setBusinessType(mapleLog.businessType().ordinal());
operateLog.setOperateType(mapleLog.operateType().ordinal());
}
} catch (Exception e) {
e.printStackTrace();
}
startTime = System.currentTimeMillis();
try {
// 执行切面方法
obj = joinPoint.proceed();
operateLog.setStatus(0);
// 判断是否保存返回结果,列表页可以设为false
if (Objects.nonNull(mapleLog) && mapleLog.saveResult()) {
String result = JSON.toJSONString(obj);
if (result.length() > maxTextLength) {
result = result.substring(0, maxTextLength);
}
operateLog.setJsonResult(result);
}
} catch (Exception e) {
// 记录异常信息
operateLog.setStatus(1);
operateLog.setErrorMsg(e.toString());
throw e;
} finally {
endTime = System.currentTimeMillis();
operateLog.setRespTime(endTime - startTime);
operateLogMapper.insert(operateLog);
}
return obj;
}
/**
* 获取Ip地址
*/
private static String getIpAddress(HttpServletRequest request) {
String xip = request.getHeader("X-Real-IP");
String xFor = request.getHeader("X-Forwarded-For");
String unknown = "unknown";
if (StringUtils.isNotEmpty(xFor) && !unknown.equalsIgnoreCase(xFor)) {
//多次反向代理后会有多个ip值,第一个ip才是真实ip
int index = xFor.indexOf(",");
if (index != -1) {
return xFor.substring(0, index);
} else {
return xFor;
}
}
xFor = xip;
if (StringUtils.isNotEmpty(xFor) && !unknown.equalsIgnoreCase(xFor)) {
return xFor;
}
if (StringUtils.isBlank(xFor) || unknown.equalsIgnoreCase(xFor)) {
xFor = request.getHeader("Proxy-Client-IP");
}
if (StringUtils.isBlank(xFor) || unknown.equalsIgnoreCase(xFor)) {
xFor = request.getHeader("WL-Proxy-Client-IP");
}
if (StringUtils.isBlank(xFor) || unknown.equalsIgnoreCase(xFor)) {
xFor = request.getHeader("HTTP_CLIENT_IP");
}
if (StringUtils.isBlank(xFor) || unknown.equalsIgnoreCase(xFor)) {
xFor = request.getHeader("HTTP_X_FORWARDED_FOR");
}
if (StringUtils.isBlank(xFor) || unknown.equalsIgnoreCase(xFor)) {
xFor = request.getRemoteAddr();
}
return xFor;
}
}
7. 测试类
import com.yang.demo.common.model.MapleLog;
import com.yang.demo.config.bean.ExceptionCode;
import com.yang.demo.common.enums.BusinessTypeEnum;
import com.yang.demo.common.enums.OperateTypeEnum;
import com.yang.demo.exception.CommonException;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.Data;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/example")
@Api(tags = "实例演示-日志记录演示接口")
public class TestSystemLogController {
@ApiOperation(value = "测试带参数、有返回结果的get请求")
@GetMapping("/testGetLog/{id}")
@MapleLog(businessType = BusinessTypeEnum.OTHER, operateType = OperateTypeEnum.OTHER)
public Test testGetLog(@PathVariable Integer id) {
Test test = new Test();
test.setName("笑小枫");
test.setAge(18);
test.setRemark("大家好,我是笑小枫,喜欢我的小伙伴点个赞呗");
return test;
}
@ApiOperation(value = "测试json参数、抛出异常的post请求")
@PostMapping("/testPostLog")
@MapleLog(businessType = BusinessTypeEnum.OTHER, operateType = OperateTypeEnum.OTHER, saveResult = false)
public Test testPostLog(@RequestBody Test param) {
Test test = new Test();
test.setName("笑小枫");
if (test.getAge() == null) {
// 这里使用了自定义异常,测试可以直接抛出RuntimeException
throw new CommonException(ExceptionCode.COMMON_ERROR);
}
test.setRemark("大家好,我是笑小枫,喜欢我的小伙伴点个赞呗");
return test;
}
@Data
static class Test {
private String name;
private Integer age;
private String remark;
}
}
8. 测试
http://localhost:6666/example/testGetLog/1