最近需要记录 用户的操作日志 ,功能完成了,现在记录下:

做一些提前准备

POM

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.4</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.54</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.5</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
</dependency>

数据库

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 异常操作日志表
DROP TABLE IF EXISTS `exception_log`;
CREATE TABLE `exception_log` (
`exc_id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`exc_requ_param` text CHARACTER SET utf8 COLLATE utf8_general_ci NULL COMMENT '请求参数',
`exc_name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '异常名称',
`exc_message` text CHARACTER SET utf8 COLLATE utf8_general_ci NULL COMMENT '异常信息',
`oper_user_id` int(11) NULL DEFAULT NULL COMMENT '操作员ID',
`oper_user_name` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '操作员姓名',
`oper_method` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '请求方法',
`base_path` varchar(255) CHARACTER SET latin1 COLLATE latin1_swedish_ci NULL DEFAULT NULL COMMENT '根路径',
`oper_url` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '请求URL',
`oper_ip` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '请求IP',
`oper_ver` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '请求版本号',
`oper_create_time` datetime NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
PRIMARY KEY (`exc_id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 17 CHARACTER SET = latin1 COLLATE = latin1_swedish_ci COMMENT = '异常日志表' ROW_FORMAT = DYNAMIC;

SET FOREIGN_KEY_CHECKS = 1;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 正常操作日志表
DROP TABLE IF EXISTS `operation_log`;
CREATE TABLE `operation_log` (
`oper_id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`oper_modul` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '功能模块',
`oper_type` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '操作类型',
`oper_desc` varchar(500) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '操作描述',
`oper_requ_param` text CHARACTER SET utf8 COLLATE utf8_general_ci NULL COMMENT '请求参数',
`oper_resp_param` text CHARACTER SET utf8 COLLATE utf8_general_ci NULL COMMENT '返回参数',
`oper_user_id` int(11) NULL DEFAULT NULL COMMENT '操作员ID',
`oper_user_name` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '操作员名称',
`oper_method` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '操作方法',
`base_path` varchar(255) CHARACTER SET latin1 COLLATE latin1_swedish_ci NULL DEFAULT NULL COMMENT '根路径',
`oper_url` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '请求Url',
`oper_ip` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '请求IP',
`oper_ver` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '操作版本号',
`oper_create_time` datetime NULL DEFAULT CURRENT_TIMESTAMP COMMENT '操作时间',
PRIMARY KEY (`oper_id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 24 CHARACTER SET = latin1 COLLATE = latin1_swedish_ci COMMENT = '操作日志表' ROW_FORMAT = DYNAMIC;

SET FOREIGN_KEY_CHECKS = 1;

好,有了基本的环境后,开始写Mapper\Service\Entity层,我这里使用了 mybatis-plus

1、Entity

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
package com.qhzx.td.entity;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.extension.activerecord.Model;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.*;
import org.springframework.format.annotation.DateTimeFormat;

import java.util.Date;

/**
* 正常操作日志
*/
@Data
@EqualsAndHashCode(callSuper = true)
@AllArgsConstructor
@NoArgsConstructor
@Builder
@ApiModel(description= "正常操作日志")
@TableName("operation_log")
public class OperationLogPO extends Model<OperationLogPO> {

@TableId(value = "oper_id", type = IdType.AUTO)
private Integer operId;

//功能模块
@ApiModelProperty("功能模块")
private String operModul;

//操作类型
@ApiModelProperty("操作类型")
private String operType;

//操作描述
@ApiModelProperty("操作描述")
private String operDesc;

//请求参数
@ApiModelProperty("请求参数")
private String operRequParam;

//返回参数
@ApiModelProperty("返回参数")
private String operRespParam;

//操作员ID
@ApiModelProperty("操作员ID")
private Long operUserId;

//操作员名称
@ApiModelProperty("操作员名称")
private String operUserName;

//操作方法
@ApiModelProperty("操作方法")
private String operMethod;

//根路径
@ApiModelProperty("根路径")
private String basePath;

//请求Url
@ApiModelProperty("请求Url")
private String operUrl;

//请求Url
@ApiModelProperty("请求Url")
private String operIp;

//操作版本号
@ApiModelProperty("操作版本号")
private String operVer;

@ApiModelProperty("操作时间")
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss",timezone = "GMT+8")
private Date operCreateTime;

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
package com.qhzx.td.entity;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.extension.activerecord.Model;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.*;
import org.springframework.format.annotation.DateTimeFormat;

import java.util.Date;

/**
* 异常操作日志
*/
@Data
@EqualsAndHashCode(callSuper = true)
@AllArgsConstructor
@NoArgsConstructor
@Builder
@ApiModel(description= "异常操作日志")
@TableName("exception_log")
public class ExceptionLogPO extends Model<ExceptionLogPO> {

@TableId(value = "exc_id", type = IdType.AUTO)
private Integer excId;

@ApiModelProperty("请求参数")
private String excRequParam;

@ApiModelProperty("异常名称")
private String excName;

@ApiModelProperty("异常信息")
private String excMessage;

@ApiModelProperty("操作员ID")
private Long operUserId;

@ApiModelProperty("操作员姓名")
private String operUserName;

@ApiModelProperty("请求方法")
private String operMethod;

@ApiModelProperty("请求URL")
private String operUrl;

@ApiModelProperty("请求IP")
private String operIp;

@ApiModelProperty("请求版本号")
private String operVer;

@ApiModelProperty("创建时间")
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss",timezone = "GMT+8")
private Date operCreateTime;
/**
* 根路径
*/
@ApiModelProperty("根路径")
private String basePath;

}

2、Mapper

1
2
3
4
5
6
7
8
9
10
package com.qhzx.td.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.qhzx.td.entity.OperationLogPO;

/**
* 正常操作日志
*/
public interface OperationLogMapper extends BaseMapper<OperationLogPO> {
}
1
2
3
4
5
6
7
8
9
10
package com.qhzx.td.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.qhzx.td.entity.ExceptionLogPO;

/**
* 异常操作日志
*/
public interface ExceptionLogMapper extends BaseMapper<ExceptionLogPO> {
}

3、Service

1
2
3
4
5
6
7
8
9
10
package com.qhzx.td.services.common;

import com.baomidou.mybatisplus.extension.service.IService;
import com.qhzx.td.entity.ExceptionLogPO;

/**
* 异常操作日志
*/
public interface ExceptionLogService extends IService<ExceptionLogPO> {
}
1
2
3
4
5
6
7
8
9
10
package com.qhzx.td.services.common;

import com.baomidou.mybatisplus.extension.service.IService;
import com.qhzx.td.entity.OperationLogPO;

/**
* 正常操作日志
*/
public interface OperationLogService extends IService<OperationLogPO> {
}

3.1、Service-impl

1
2
3
4
5
6
7
8
9
10
11
package com.qhzx.td.services.common.impl;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.qhzx.td.entity.ExceptionLogPO;
import com.qhzx.td.mapper.ExceptionLogMapper;
import com.qhzx.td.services.common.ExceptionLogService;
import org.springframework.stereotype.Service;

@Service
public class ExceptionLogServiceImpl extends ServiceImpl<ExceptionLogMapper, ExceptionLogPO> implements ExceptionLogService {
}
1
2
3
4
5
6
7
8
9
10
11
package com.qhzx.td.services.common.impl;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.qhzx.td.entity.OperationLogPO;
import com.qhzx.td.mapper.OperationLogMapper;
import com.qhzx.td.services.common.OperationLogService;
import org.springframework.stereotype.Service;

@Service
public class OperationLogServiceImpl extends ServiceImpl<OperationLogMapper, OperationLogPO> implements OperationLogService {
}

4、登录

登录Controller

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
package com.qhzx.td.controller.Login;

import com.qhzx.td.services.system.LoginService;
import com.qhzx.td.untils.Result;
import com.qhzx.td.vo.LoginBodyVO;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

/**
* 登录验证-Controller
* @ClassName LoginController
* @Author Blue Email:2113438464@qq.com
* @Date 2022/9/12
*/
@Api(tags = "登录")
@RestController
@CrossOrigin
@Slf4j
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class LoginController {

private final LoginService loginService;

/**
* 登录方法
* @param loginBodyVO 用户登录对象
* @return Result
*/
@ApiOperation(value = "登录方法")
@PostMapping("/login")
public Result login(@RequestBody @Validated LoginBodyVO loginBodyVO) {
// 生成令牌
return loginService.login(loginBodyVO.getUsername().trim(),
loginBodyVO.getPassword().trim(),
loginBodyVO.getCode().trim(),
loginBodyVO.getUuid());
}

}

登录-Service-实现

这里的token令牌是有用的,当然你可以根据自己的业务进行修改,不一定要按照我的方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
package com.qhzx.td.services.system.impl;

import cn.hutool.core.util.ObjectUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.qhzx.td.constant.Constants;
import com.qhzx.td.entity.AdminUserPO;
import com.qhzx.td.exception.ComFoundException;
import com.qhzx.td.exception.user.CaptchaException;
import com.qhzx.td.exception.user.CaptchaExpireException;
import com.qhzx.td.framework.security.authc.AuthcService;
import com.qhzx.td.mapper.SysUserMapper;
import com.qhzx.td.services.system.LoginService;
import com.qhzx.td.template.RedisRepository;
import com.qhzx.td.untils.IdUtils;
import com.qhzx.td.untils.Result;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.logging.log4j.util.Strings;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Service;

import java.util.concurrent.TimeUnit;

/**
* 登录-Service-实现
* @ClassName LoginServiceImpl
* @Author Blue Email:2113438464@qq.com
* @Date 2022/9/13
*/
@Slf4j
@Service
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class LoginServiceImpl extends ServiceImpl<SysUserMapper, AdminUserPO> implements LoginService {

// 用了两种Redis对象,一般只需注入一个就行,我就不改了,麻烦
private final RedisRepository redisCache;
private final SysUserMapper loginMapper;
private final AuthcService authcService;
private final RedisTemplate<String, Object> template;

/**
* 用户登录,生成令牌
* @param username 账号
* @param password 密码
* @param code 验证码
* @param uuid 唯一标识
* @return Result
*/
@Override
public Result login(String username, String password, String code, String uuid) {
// 拼接redis的key
String verifyKey = Constants.CAPTCHA_CODE_KEY + uuid;
// 获取redis数据
String captcha = redisCache.getCacheObject(verifyKey);
// 删除redis数据
redisCache.deleteObject(verifyKey);

// 验证码失效
if (captcha == null) {
log.error("账号为{},验证码失效!",username);
throw new CaptchaExpireException();
}
// 验证码错误
if (!code.equalsIgnoreCase(captcha)) {
log.error("账号为{},验证码错误!",username);
throw new CaptchaException();
}

// 用户验证
// 创建查询对象 编写条件
QueryWrapper<AdminUserPO> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("username",username);

// 查询
AdminUserPO adminUserPO = loginMapper.selectOne(queryWrapper);

// 校验,密码校验而已,你随意写
boolean passFlag = authcService.check(password, adminUserPO);
if (!passFlag) {
throw new ComFoundException("密码错误");
}

log.info("登录成功,正在生成token...");
// uuid
String uuid1 = IdUtils.getUUID();
// redis存储的对象
String jsonString = JSON.toJSONString(adminUserPO);
// token有效期60分钟
ValueOperations<String, Object> redisString = template.opsForValue();
redisString.set(Constants.TOKEN + uuid1,jsonString,60 * 60, TimeUnit.SECONDS);

// 封装数据
JSONObject jsonObject = new JSONObject();
jsonObject.put("name",username);
jsonObject.put("id",adminUserPO.getId());
// 返回uuid
jsonObject.put(Constants.TOKEN,uuid1);
return Result.succeed(jsonObject,"请求成功");
}

}

5、拦截器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
import io.swagger.models.HttpMethod;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
* @ClassName MyInterceptorConfig
* @Author Blue Email:2113438464@qq.com
* @Date 2022/9/13
*/
@Configuration
@Component
@Slf4j
public class MyInterceptorConfig implements HandlerInterceptor {
/**
* 添加接口请求拦截器
* @param request
* @param resp
* @param handler
* @return
* @throws Exception
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse resp, Object handler) throws Exception {
//打印请求内容
log.info("==============preHandle======start====================");
log.info("IP :" + request.getRemoteAddr());
log.info("URL :" + request.getRequestURL().toString());
log.info("HTTP Method :" + request.getMethod());
log.info("===============preHandle======End=====================");

if (HttpMethod.OPTIONS.toString().equals(request.getMethod())) {
log.info("OPTIONS请求,放行");
return true;
}
log.info("------------ MyInterceptorConfig...................");
String token = request.getHeader("Authorization");
log.info("=============== MyInterceptorConfig token : {}", token);
if (StringUtils.isBlank(token) ) {
log.info("=============== 请求拦截 : {}", token);
return false;
}
return true;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
package com.xiaoq.store.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;

import lombok.RequiredArgsConstructor;

/**
* @author locken
* @date 2022-08-23 20:51
* @Description: -TOOD
*/
@Component
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class WebConfig extends WebMvcConfigurationSupport {

private final MyInterceptorConfig myInterceptorConfig;

/**
* 添加接口请求拦截器 排除用户接口不用验证
* @param registry
*/
@Override
protected void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(myInterceptorConfig)
.addPathPatterns("/**")
// .excludePathPatterns("/user/login");
.excludePathPatterns("/sys/login","/captchaImage")
.excludePathPatterns("/doc.html") //不需要拦截的地
.excludePathPatterns("/swagger-resources/**")
.excludePathPatterns("/webjars/**")
.excludePathPatterns("/v2/**")
.excludePathPatterns("/favicon.ico")
.excludePathPatterns("/swagger-ui.html/**");
}

/*
* @param registry
* @return void
* @introduction: 跨域支持
* @date 2021/5/19 21:44
*/
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOriginPatterns("*")
.allowCredentials(true)
.allowedMethods("GET", "POST", "DELETE", "PUT", "PATCH", "OPTIONS", "HEAD")
.maxAge(3600 * 24);
}
/**
* 发现如果继承了WebMvcConfigurationSupport,则在yml中配置的相关内容会失效。 需要重新指定静态资源
* @param registry
*/
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/**").addResourceLocations(
"classpath:/static/");
registry.addResourceHandler("swagger-ui.html").addResourceLocations(
"classpath:/META-INF/resources/");
registry.addResourceHandler("/webjars/**").addResourceLocations(
"classpath:/META-INF/resources/webjars/");

registry.addResourceHandler("doc.html").addResourceLocations(
"classpath:/META-INF/resources/");
super.addResourceHandlers(registry);
}

}

6、注解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package com.luck.backstage.tests;

import java.lang.annotation.*;

/**
* AOP日志切入
*/
@Target(ElementType.METHOD) //注解放置的目标位置,METHOD是可注解在方法级别上
@Retention(RetentionPolicy.RUNTIME) //注解在哪个阶段执行
@Documented
public @interface OperLog {
String operModul() default ""; // 操作模块
String operType() default ""; // 操作类型
String operDesc() default ""; // 操作说明
}

7、Utils

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
package com.qhzx.td.untils;
import org.apache.commons.lang3.StringUtils;

import javax.servlet.http.HttpServletRequest;

public class HttpUtils {
/**
* 获取用户真实IP地址,不使用request.getRemoteAddr();的原因是有可能用户使用了代理软件方式避免真实IP地址,
*
* 可是,如果通过了多级反向代理的话,X-Forwarded-For的值并不止一个,而是一串IP值,究竟哪个才是真正的用户端的真实IP呢?
* 答案是取X-Forwarded-For中第一个非unknown的有效IP字符串。
*
* 如:X-Forwarded-For:192.168.1.110, 192.168.1.120, 192.168.1.130,
* 192.168.1.100
*
* 用户真实IP为: 192.168.1.110
*
* @param request
* @return
*/
public static String getIpAddress(HttpServletRequest request) {
String Xip = request.getHeader("X-Real-IP");
String XFor = request.getHeader("X-Forwarded-For");

//多次反向代理后会有多个ip值,第一个ip才是真实ip
if (StringUtils.isNotEmpty(XFor) && !"unKnown".equalsIgnoreCase(XFor)) {
int index = XFor.indexOf(",");
if (index != -1) {
return "0:0:0:0:0:0:0:1".equals(XFor.substring(0, index)) ? "127.0.0.1" : XFor.substring(0, index);
} else {
return "0:0:0:0:0:0:0:1".equals(XFor) ? "127.0.0.1" : XFor;
}
}
XFor = Xip;
if (StringUtils.isNotEmpty(XFor) && !"unKnown".equalsIgnoreCase(XFor)) {
return "0:0:0:0:0:0:0:1".equals(XFor) ? "127.0.0.1" : 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 "0:0:0:0:0:0:0:1".equals(XFor) ? "127.0.0.1" : XFor;
}
}

Token解析工具类(重要),它从Redis中取出对应的业务数据:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
package com.qhzx.td.untils;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.baomidou.mybatisplus.core.toolkit.Assert;
import com.qhzx.td.constant.Constants;
import com.qhzx.td.entity.AdminUserPO;
import com.qhzx.td.entity.Users;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;
import java.util.List;

@Component
public class TokenFactory {

private static RedisTemplate redisTemplate; //存储对象

@Autowired
public void setRedisTemplate(RedisTemplate redisTemplate) {
TokenFactory.redisTemplate = redisTemplate;
}

private static AdminUserPO get(String key) {
// token replace后 为纯key内容,然后从redis中取出
Object token = redisTemplate.opsForValue().get(Constants.TOKEN+key.replace("Bearer ", ""));
// 判断
return token == null ? null : JSON.parseObject(token.toString(), AdminUserPO.class);
}

private static List<Users> gets(String key) {
Object token = redisTemplate.opsForValue().get("token:"+key);
return token == null ? null : JSONArray.parseArray(token.toString(), Users.class);
}
public static AdminUserPO validateToken(HttpServletRequest request) throws Exception {
// 从请求头拿到token
String token = request.getHeader("Authorization");
// Assert
Assert.notEmpty(token, "token不能为空!");
// 调用方法
AdminUserPO userToken = get(token);
// Assert
Assert.notNull(userToken, "token无效!");
return userToken;
}

}

8、切面

注意更换下面两个注解内容:

@Pointcut(“@annotation(com.qhzx.mad.backstage.config.OperLog)”)
@Pointcut(“execution(* com.qhzx.mad.backstage.controller...(..))”)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
package com.qhzx.td.config;

import cn.hutool.core.util.StrUtil;
import cn.hutool.core.util.URLUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.serializer.PropertyFilter;
import com.qhzx.td.annotation.OperLog;
import com.qhzx.td.entity.AdminUserPO;
import com.qhzx.td.entity.ExceptionLogPO;
import com.qhzx.td.entity.OperationLogPO;
import com.qhzx.td.services.common.ExceptionLogService;
import com.qhzx.td.services.common.OperationLogService;
import com.qhzx.td.untils.HttpUtils;
import com.qhzx.td.untils.TokenFactory;
import lombok.RequiredArgsConstructor;
import org.aspectj.lang.JoinPoint;
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.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Component;
import org.springframework.validation.BindingResult;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;


/**
* 登录自定义表单其他参数
*/
@Aspect
@Component
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class OperLogAspect {

private final Environment env;
private final ExceptionLogService exceptionLogService;
private final OperationLogService operationLogService;

/**
* 设置操作日志切入点 记录操作日志 在注解的位置切入代码
*/
@Pointcut("@annotation(com.qhzx.td.annotation.OperLog)")
public void operLogPoinCut() {
}

/**
* 设置操作异常切入点记录异常日志 扫描所有controller包下操作
*/
@Pointcut("execution(* com.qhzx.td.controller..*.*(..))")
public void operExceptionLogPoinCut() {
}

/**
* 正常返回通知,拦截用户操作日志,连接点正常执行完成后执行, 如果连接点抛出异常,则不会执行
* @param joinPoint 切入点
* @param keys 返回结果
*/
@AfterReturning(value = "operLogPoinCut()", returning = "keys")
public void saveOperLog(JoinPoint joinPoint, Object keys) throws Exception {
// 获取RequestAttributes
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
HttpServletRequest request = (HttpServletRequest) requestAttributes
.resolveReference(RequestAttributes.REFERENCE_REQUEST);
OperationLogPO operlog = new OperationLogPO();
// 从切面织入点处通过反射机制获取织入点处的方法
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
// 获取切入点所在的方法
Method method = signature.getMethod();
//获取操作
OperLog opLog = method.getAnnotation(OperLog.class);
String operDesc = null;
if (opLog !=null){
String operModul = opLog.operModul();
String operType = opLog.operType();
operDesc = opLog.operDesc();
// 操作模块
operlog.setOperModul(operModul);
// 操作类型
operlog.setOperType(operType);
// 操作描述
operlog.setOperDesc(operDesc);
}
String urlStr = request.getRequestURL().toString();
// 获取请求的类名
String className = joinPoint.getTarget().getClass().getName();
// 获取请求的方法名
String methodName = method.getName();
methodName = className + "." + methodName;
// 请求方法
operlog.setOperMethod(methodName);
// 请求的参数
// Map<String, String> rtnMap = converMap(request.getParameterMap());
// String params = JSON.toJSONString(rtnMap);
operlog.setOperRequParam(getReqestParams(request,joinPoint));
// operlog.setOperRequParam(params);
operlog.setOperRespParam(JSON.toJSONString(keys));
if (!Objects.equals(operDesc, "登录") && !Objects.equals(operDesc, "退出登录")){
AdminUserPO userToken = TokenFactory.validateToken(request);
operlog.setOperUserId(userToken.getId());
operlog.setOperUserName(userToken.getUsername());
}
operlog.setOperIp(HttpUtils.getIpAddress(request));
operlog.setOperUrl(request.getRequestURI());
operlog.setOperVer(env.getProperty("platform.operVer"));
operlog.setBasePath(StrUtil.removeSuffix(urlStr, URLUtil.url(urlStr).getPath()));
operationLogService.save(operlog);

}

/**
* 异常返回通知,用于拦截异常日志信息 连接点抛出异常后执行
* @param joinPoint 切入点
* @param e 异常信息
*/
@AfterThrowing(pointcut = "operExceptionLogPoinCut()", throwing = "e")
public void saveExceptionLog(JoinPoint joinPoint, Throwable e) {
// 获取RequestAttributes
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
// 从获取RequestAttributes中获取HttpServletRequest的信息
HttpServletRequest request = (HttpServletRequest) requestAttributes
.resolveReference(RequestAttributes.REFERENCE_REQUEST);

ExceptionLogPO excepLog = new ExceptionLogPO();
try {
// 从切面织入点处通过反射机制获取织入点处的方法
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
// 获取切入点所在的方法
Method method = signature.getMethod();
String urlStr = request.getRequestURL().toString();
String className = joinPoint.getTarget().getClass().getName();
String methodName = method.getName();
methodName = className + "." + methodName;
// Map<String, String> rtnMap = converMap(request.getParameterMap());
// String params = JSON.toJSONString(rtnMap);
excepLog.setExcRequParam(getReqestParams(request,joinPoint));
// excepLog.setExcRequParam(params);
excepLog.setOperMethod(methodName);
excepLog.setExcName(e.getClass().getName());
excepLog.setExcMessage(stackTraceToString(e.getClass().getName(), e.getMessage(), e.getStackTrace()));
AdminUserPO userToken = TokenFactory.validateToken(request);
excepLog.setOperUserId(userToken.getId());
excepLog.setOperUserName(userToken.getUsername());
excepLog.setOperUrl(request.getRequestURI());
excepLog.setOperIp(HttpUtils.getIpAddress(request));
excepLog.setOperVer(env.getProperty("platform.operVer"));
excepLog.setBasePath(StrUtil.removeSuffix(urlStr, URLUtil.url(urlStr).getPath()));
exceptionLogService.save(excepLog);
} catch (Exception e2) {
e2.printStackTrace();
}
}

/**
* @Description: 获取请求参数
* @param request: request
* @param joinPoint: joinPoint
* @Return: String
*/
private String getReqestParams(HttpServletRequest request, JoinPoint joinPoint) {
String httpMethod = request.getMethod();
String params = "";
if ("POST".equals(httpMethod) || "PUT".equals(httpMethod) || "PATCH".equals(httpMethod)) {
Object[] paramsArray = joinPoint.getArgs();
// java.lang.IllegalStateException: It is illegal to call this method if the current request is not in asynchronous mode (i.e. isAsyncStarted() returns false)
// https://my.oschina.net/mengzhang6/blog/2395893
Object[] arguments = new Object[paramsArray.length];
for (int i = 0; i < paramsArray.length; i++) {
if (paramsArray[i] instanceof BindingResult || paramsArray[i] instanceof ServletRequest || paramsArray[i] instanceof ServletResponse || paramsArray[i] instanceof MultipartFile) {
//ServletRequest不能序列化,从入参里排除,否则报异常:java.lang.IllegalStateException: It is illegal to call this method if the current request is not in asynchronous mode (i.e. isAsyncStarted() returns false)
//ServletResponse不能序列化 从入参里排除,否则报异常:java.lang.IllegalStateException: getOutputStream() has already been called for this response
continue;
}
arguments[i] = paramsArray[i];
}
//update-begin-author:taoyan date:20200724 for:日志数据太长的直接过滤掉
PropertyFilter profilter = new PropertyFilter() {
@Override
public boolean apply(Object o, String name, Object value) {
if(value!=null && value.toString().length()>500){
return false;
}
return true;
}
};
params = JSONObject.toJSONString(arguments, profilter);
//update-end-author:taoyan date:20200724 for:日志数据太长的直接过滤掉
} else {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
// 请求的方法参数值
Object[] args = joinPoint.getArgs();
// 请求的方法参数名称
LocalVariableTableParameterNameDiscoverer u = new LocalVariableTableParameterNameDiscoverer();
String[] paramNames = u.getParameterNames(method);
if (args != null && paramNames != null) {
for (int i = 0; i < args.length; i++) {
params += " " + paramNames[i] + ": " + args[i];
}
}
}
return params;
}

/**
* 转换request 请求参数
* @param paramMap request获取的参数数组
*/
public Map<String, String> converMap(Map<String, String[]> paramMap) {
Map<String, String> rtnMap = new HashMap<String, String>();
for(String key : paramMap.keySet()) {
rtnMap.put(key, paramMap.get(key)[0]);
}
return rtnMap;
}

/**
* 转换异常信息为字符串
* @param exceptionName 异常名称
* @param exceptionMessage 异常信息
* @param elements 堆栈信息
*/
public String stackTraceToString(String exceptionName, String exceptionMessage, StackTraceElement[] elements) {
StringBuffer strbuff = new StringBuffer();
for (StackTraceElement stet : elements) {
strbuff.append(stet + "\n");
}
String message = exceptionName + ":" + exceptionMessage + "\n\t" + strbuff.toString();
return message;
}
}

9、全局异常处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@ControllerAdvice
@ResponseBody
public class DefaultExceptionAdvice {
private static final Logger LOGGER = LoggerFactory.getLogger(DefaultExceptionAdvice.class);

@ExceptionHandler({MybatisPlusException.class})
public Result MybatisPlusException(MybatisPlusException e) {
// 从异常对象中拿到信息
String message = e.getMessage();
// 返回
return Result.fail(401,message);
}

}

10、VO/DTO对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
package com.qhzx.td.vo;

import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;

import javax.validation.constraints.NotNull;

/**
* 用户登录对象
* @ClassName LoginBodyVO
* @Author Blue Email:2113438464@qq.com
* @Date 2022/9/13
*/
@Data
@EqualsAndHashCode
@ApiModel(description= "用户登录对象")
public class LoginBodyVO {
/**
* 用户名
*/
@ApiModelProperty(value = "用户账号", required = true)
@NotNull(message = "不允许为空")
private String username;

/**
* 用户密码
*/
@ApiModelProperty(value = "用户密码", required = true)
@NotNull(message = "不允许为空")
private String password;

/**
* 验证码
*/
@ApiModelProperty(value = "验证码", required = true)
@NotNull(message = "不允许为空")
private String code;

/**
* 唯一标识
*/
@ApiModelProperty(value = "唯一标识", required = true)
@NotNull(message = "不允许为空")
private String uuid = "";

}

11、使用

在对应的方法上,加上注解。如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
package com.qhzx.td.controller.system;

import com.baomidou.mybatisplus.core.metadata.IPage;
import com.github.xiaoymin.knife4j.annotations.ApiSupport;
import com.qhzx.td.annotation.OperLog;
import com.qhzx.td.entity.PermissionPO;
import com.qhzx.td.entity.RolePO;
import com.qhzx.td.services.system.SysRoleService;
import com.qhzx.td.untils.Result;
import com.qhzx.td.vo.RoleVO;
import com.qhzx.td.vo.SysRoleQueryVO;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiOperation;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import java.util.List;
import java.util.Set;

/**
* 角色-Controller
* @ClassName SysRoleController
* @Author Blue Email:2113438464@qq.com
* @Date 2022/9/25
*/
@Api(tags = "角色信息接口")
@ApiSupport(author = "何锟")
@RestController
@RequestMapping("/role")
@CrossOrigin
@Slf4j
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class SysRoleController {

private final SysRoleService sysRoleService;

/**
* 根据角色id查询
* @param id 角色id
* @return 查询角色
*/
@ApiOperation("根据id获取单个角色信息")
@OperLog(operModul = "后台系统-根据id获取单个角色信息", operDesc = "根据id获取单个角色信息")
@GetMapping("/byId/{id}")
@ApiImplicitParam(name = "id",value = "角色id",required = true)
public Result<RolePO> getRoleById(@PathVariable Long id) {
return sysRoleService.findById(id);
}

/**
* 根据角色代码查找角色
* @param roleCode 角色代码
* @return 增加成功的角色
*/
@ApiOperation("根据角色编码获取单个角色信息")
@OperLog(operModul = "后台系统-根据角色编码获取单个角色信息", operDesc = "根据角色编码获取单个角色信息")
@GetMapping("/roleCode/{roleCode}")
@ApiImplicitParam(name = "roleCode",value = "角色标识",required = true)
public Result<RolePO> getRoleByRoleCode(@PathVariable String roleCode) {
return sysRoleService.findByRoleCode(roleCode);
}

/**
* 获取全部角色信息
* @return 角色列表
*/
@ApiOperation("获取所有角色信息")
@OperLog(operModul = "后台系统-获取所有角色信息", operDesc = "获取所有角色信息")
@GetMapping()
public Result<List<RolePO>> getAllRole() {
return sysRoleService.findAll();
}

/**
* 根据条件查询角色信息,并返回分页角色列表
* @param sysRoleQueryVO 查询条件
* @return 分页角色数据
*/
@ApiOperation("根据条件查询返回角色分页列表")
@OperLog(operModul = "后台系统-查询返回角色分页列表", operDesc = "查询返回角色分页列表")
@PostMapping("/page")
public Result<IPage<RoleVO>> getSysUserPage(@RequestBody SysRoleQueryVO sysRoleQueryVO) {
return sysRoleService.page(sysRoleQueryVO);
}

/**
* 修改/添加角色
* @param role 待修改/添加的角色
* @return 修改/添加成功的角色
*/
@ApiOperation("修改/添加角色信息")
@PostMapping
public Result<Result<RoleVO>> saveRole(@RequestBody RoleVO role) {
if (role.getId() != 0) {
return Result.succeed(sysRoleService.update(role),"请求成功");
} else {
return Result.succeed(sysRoleService.create(role),"请求成功");
}
}

/**
* 删除角色
* @param ids 删除的角色的id,可以多个
* @return 是否删除成功
*/
@ApiOperation("删除角色信息")
@OperLog(operModul = "后台系统-删除角色", operDesc = "删除角色")
@DeleteMapping
@ApiImplicitParam(name = "ids",value = "删除的角色的id,可以多个",required = true)
public Result<Boolean> deleteRole(@RequestBody Set<Long> ids) {
return sysRoleService.delete(ids);
}


/**
* 获取角色权限
* @param roleId 角色id
* @return 角色权限列表
*/
@ApiOperation("获取角色权限信息")
@OperLog(operModul = "后台系统-获取角色权限信息", operDesc = "获取角色权限信息")
@GetMapping("{roleId}/permission")
public Result<List<PermissionPO>> getPermission(@PathVariable Long roleId) {
return sysRoleService.getPermission(roleId);
}

/**
* 保存角色权限
* @param roleId 角色id
* @param menus 权限表
* @return 是否成功
*/
@ApiOperation("保存角色权限信息")
@OperLog(operModul = "后台系统-保存角色权限信息", operDesc = "保存角色权限信息")
@PostMapping("{roleId}/permission")
public Result<Boolean> savePermission(@PathVariable Long roleId,@RequestBody Set<Long> menus) {
return sysRoleService.savePermission(roleId,menus);
}

}

这样就完成了 正常操作日志 的记录,而异常操作日志会根据在切面类中的 @Pointcut("execution(* com.qhzx.mad.backstage.controller..*.*(..))") 配置的类路径 进行操作。