程序员波特的个人博客

一个小而美的程序员编程资料站

0%

【通用权限系统】其他功能

硅谷通用权限系统:其他功能

一、功能说明

其他功能:部门管理、岗位管理、日志管理(登录日志、操作日志)

1、部门管理

实现方式与菜单管理类似

image-20221009153539037

2、岗位管理

实现方式与菜单管理类似

image-20221009153619861

3、登录日志

3.1、页面效果

image-20221009153655567

3.2、功能实现-登录成功添加日志

3.2.1、在spring-security模块创建AsyncLoginLogService

image-20220907093104127

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
* 异步调用日志服务
*/
public interface AsyncLoginLogService {

/**
* 记录登录信息
*
* @param username 用户名
* @param status 状态
* @param ipaddr ip
* @param message 消息内容
* @return
*/
void recordLoginLog(String username, Integer status, String ipaddr, String message);
}
3.2.2、在service-system实现添加日志方法

image-20220907093304965

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
@Service
public class AsyncLoginLogServiceImpl implements AsyncLoginLogService {

@Resource
private SysLoginLogMapper sysLoginLogMapper;

/**
* 记录登录信息
*
* @param username 用户名
* @param status 状态
* @param ipaddr ip
* @param message 消息内容
* @return
*/
public void recordLoginLog(String username, Integer status, String ipaddr, String message) {
SysLoginLog sysLoginLog = new SysLoginLog();
sysLoginLog.setUsername(username);
sysLoginLog.setIpaddr(ipaddr);
sysLoginLog.setMsg(message);
// 日志状态
sysLoginLog.setStatus(status);
sysLoginLogMapper.insert(sysLoginLog);
}
}
3.2.3、在TokenLoginFilter调用方法实现

image-20220907092737569

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/**
* 登录成功
* @param request
* @param response
* @param chain
* @param auth
* @throws IOException
* @throws ServletException
*/
@Override
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain,
Authentication auth) throws IOException, ServletException {
CustomUser customUser = (CustomUser) auth.getPrincipal();
String token = JwtHelper.createToken(customUser.getSysUser().getId(), customUser.getSysUser().getUsername());
//保存权限数据
redisTemplate.opsForValue().set(customUser.getUsername(), JSON.toJSONString(customUser.getAuthorities()));

//记录日志
asyncLoginLogService.recordLoginLog(customUser.getUsername(), 1, IpUtil.getIpAddress(request), "登录成功");

Map<String, Object> map = new HashMap<>();
map.put("token", token);
ResponseUtil.out(response, Result.ok(map));
}

3.3、功能实现-登录日志显示

3.3.1、编写SysLoginLogController
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.atguigu.system.controller;

import com.atguigu.common.result.Result;
import com.atguigu.model.system.SysLoginLog;
import com.atguigu.system.service.SysLoginLogService;
import com.atguigu.model.vo.SysLoginLogQueryVo;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;

@Api(value = "SysLoginLog管理", tags = "SysLoginLog管理")
@RestController
@RequestMapping(value="/admin/system/sysLoginLog")
@SuppressWarnings({"unchecked", "rawtypes"})
public class SysLoginLogController {

@Resource
private SysLoginLogService sysLoginLogService;

@ApiOperation(value = "获取分页列表")
@GetMapping("{page}/{limit}")
public Result index(
@ApiParam(name = "page", value = "当前页码", required = true)
@PathVariable Long page,

@ApiParam(name = "limit", value = "每页记录数", required = true)
@PathVariable Long limit,

@ApiParam(name = "sysLoginLogVo", value = "查询对象", required = false)
SysLoginLogQueryVo sysLoginLogQueryVo) {
Page<SysLoginLog> pageParam = new Page<>(page, limit);
IPage<SysLoginLog> pageModel = sysLoginLogService.selectPage(pageParam, sysLoginLogQueryVo);
return Result.ok(pageModel);
}

@ApiOperation(value = "获取")
@GetMapping("get/{id}")
public Result get(@PathVariable Long id) {
SysLoginLog sysLoginLog = sysLoginLogService.getById(id);
return Result.ok(sysLoginLog);
}
}
3.3.2、编写SysLoginLogService

(1)SysLoginLogService

1
2
3
4
public interface SysLoginLogService extends IService<SysLoginLog> {
//列表显示
IPage<SysLoginLog> selectPage(Page<SysLoginLog> pageParam, SysLoginLogQueryVo sysLoginLogQueryVo);
}

(2)SysLoginLogServiceImpl

1
2
3
4
5
6
7
8
9
10
11
12
@Service
@SuppressWarnings({"unchecked", "rawtypes"})
public class SysLoginLogServiceImpl extends ServiceImpl<SysLoginLogMapper, SysLoginLog> implements SysLoginLogService {

@Resource
private SysLoginLogMapper sysLoginLogMapper;

@Override
public IPage<SysLoginLog> selectPage(Page<SysLoginLog> pageParam, SysLoginLogQueryVo sysLoginLogQueryVo) {
return sysLoginLogMapper.selectPage(pageParam, sysLoginLogQueryVo);
}
}
3.3.3、编写SysLoginLogMapper

(1)SysLoginLogMapper

1
2
3
4
5
6
@Mapper
@Repository
public interface SysLoginLogMapper extends BaseMapper<SysLoginLog> {

IPage<SysLoginLog> selectPage(Page<SysLoginLog> page, @Param("vo") SysLoginLogQueryVo sysLoginLogQueryVo);
}

(2)SysLoginLogMapper.xml

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
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="com.atguigu.system.mapper.SysLoginLogMapper">

<resultMap id="sysLoginLogMap" type="com.atguigu.model.system.SysLoginLog" autoMapping="true">
</resultMap>

<!-- 用于select查询公用抽取的列 -->
<sql id="columns">
id,username,ipaddr,status,msg,access_time,create_time,update_time,is_deleted
</sql>

<sql id="findPageWhere">
<where>
<if test="vo.username != null and vo.username != ''">
and username = #{vo.username}
</if>
<if test="vo.createTimeBegin != null and vo.createTimeBegin != ''">
and create_time >= #{vo.createTimeBegin}
</if>
<if test="vo.createTimeEnd != null and vo.createTimeEnd != ''">
and create_time &lt;= #{vo.createTimeEnd}
</if>
and is_deleted = 0
</where>
</sql>

<select id="selectPage1" resultMap="sysLoginLogMap">
select <include refid="columns" />
from sys_login_log
<include refid="findPageWhere"/>
order by id desc
</select>
</mapper>

3.4、登录日志前端

实现方式参考菜单管理

4、操作日志

4.1、页面效果

image-20221009153740885

4.2、实现方式

系统引入common-log模块,采用AOP及自定义标签实现操作日志记录

4.2.1、创建common-log模块

image-20220907094746177

引入依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<dependencies>
<dependency>
<groupId>com.atguigu</groupId>
<artifactId>model</artifactId>
<version>1.0</version>
</dependency>
<dependency>
<groupId>com.atguigu</groupId>
<artifactId>common-util</artifactId>
<version>1.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
</dependencies>
4.2.2、创建自定义注解Log
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
package com.atguigu.system.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import com.atguigu.system.enums.BusinessType;
import com.atguigu.system.enums.OperatorType;

/**
* 自定义操作日志记录注解
*/
@Target({ElementType.PARAMETER, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Log {
/**
* 模块
*/
public String title() default "";

/**
* 功能
*/
public BusinessType businessType() default BusinessType.OTHER;

/**
* 操作人类别
*/
public OperatorType operatorType() default OperatorType.MANAGE;

/**
* 是否保存请求的参数
*/
public boolean isSaveRequestData() default true;

/**
* 是否保存响应的参数
*/
public boolean isSaveResponseData() default true;
}
4.2.3、创建AOP类LogAspect
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
package com.atguigu.system.aspect;

import java.util.Collection;
import java.util.Map;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.alibaba.fastjson.JSON;
import com.atguigu.common.helper.JwtHelper;
import com.atguigu.common.util.IpUtil;
import com.atguigu.model.system.SysOperLog;
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.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpMethod;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.validation.BindingResult;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.multipart.MultipartFile;
import com.atguigu.system.annotation.Log;
import com.atguigu.system.service.AsyncOperLogService;

/**
* 操作日志记录处理
*/
@Aspect
@Component
public class LogAspect {
private static final Logger log = LoggerFactory.getLogger(LogAspect.class);

//微服务切换为feign调用接口
@Resource
private AsyncOperLogService asyncOperLogService;

/**
* 处理完请求后执行
*
* @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 {
RequestAttributes ra = RequestContextHolder.getRequestAttributes();
ServletRequestAttributes sra = (ServletRequestAttributes) ra;
HttpServletRequest request = sra.getRequest();

// *========数据库日志=========*//
SysOperLog operLog = new SysOperLog();
operLog.setStatus(1);
// 请求的地址
String ip = IpUtil.getIpAddress(request);//IpUtil.getIpAddr(ServletUtils.getRequest());
operLog.setOperIp(ip);
operLog.setOperUrl(request.getRequestURI());

String token = request.getHeader("token");
String userName = JwtHelper.getUsername(token);
operLog.setOperName(userName);

if (e != null) {
operLog.setStatus(0);
operLog.setErrorMsg(e.getMessage());
}
// 设置方法名称
String className = joinPoint.getTarget().getClass().getName();
String methodName = joinPoint.getSignature().getName();
operLog.setMethod(className + "." + methodName + "()");
// 设置请求方式
operLog.setRequestMethod(request.getMethod());
// 处理设置注解上的参数
getControllerMethodDescription(joinPoint, controllerLog, operLog, jsonResult);
// 保存数据库
asyncOperLogService.saveSysLog(operLog);
} catch (Exception exp) {
// 记录本地异常日志
log.error("==前置通知异常==");
log.error("异常信息:{}", exp.getMessage());
exp.printStackTrace();
}
}

/**
* 获取注解中对方法的描述信息 用于Controller层注解
*
* @param log 日志
* @param operLog 操作日志
* @throws Exception
*/
public void getControllerMethodDescription(JoinPoint joinPoint, Log log, SysOperLog operLog, Object jsonResult) throws Exception {
// 设置action动作
operLog.setBusinessType(log.businessType().name());
// 设置标题
operLog.setTitle(log.title());
// 设置操作人类别
operLog.setOperatorType(log.operatorType().name());
// 是否需要保存request,参数和值
if (log.isSaveRequestData()) {
// 获取参数的信息,传入到数据库中。
setRequestValue(joinPoint, operLog);
}
// 是否需要保存response,参数和值
if (log.isSaveResponseData() && !StringUtils.isEmpty(jsonResult)) {
operLog.setJsonResult(JSON.toJSONString(jsonResult));
}
}

/**
* 获取请求的参数,放到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.setOperParam(params);
}
}

/**
* 参数拼装
*/
private String argsArrayToString(Object[] paramsArray) {
String params = "";
if (paramsArray != null && paramsArray.length > 0) {
for (Object o : paramsArray) {
if (!StringUtils.isEmpty(o) && !isFilterObject(o)) {
try {
Object jsonObj = JSON.toJSON(o);
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 (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;
}
}
4.2.4、创建枚举类

(1)OperatorType

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package com.atguigu.system.enums;

/**
* 操作人类别
*/
public enum OperatorType {
/**
* 其它
*/
OTHER,

/**
* 后台用户
*/
MANAGE,

/**
* 手机端用户
*/
MOBILE
}

(2)BusinessType

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
package com.atguigu.system.enums;

/**
* 业务操作类型
*/
public enum BusinessType {
/**
* 其它
*/
OTHER,

/**
* 新增
*/
INSERT,

/**
* 修改
*/
UPDATE,

/**
* 删除
*/
DELETE,

/**
* 授权
*/
ASSGIN,

/**
* 导出
*/
EXPORT,

/**
* 导入
*/
IMPORT,

/**
* 强退
*/
FORCE,

/**
* 更新状态
*/
STATUS,

/**
* 清空数据
*/
CLEAN,
}

4.3、使用方式

已角色管理为例:

使用自定义标签记录日志

@Log(title = “角色管理”, businessType = BusinessType.INSERT)

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
@Log(title = "角色管理", businessType = BusinessType.INSERT)
@PreAuthorize("hasAuthority('bnt.sysRole.add')")
@ApiOperation(value = "新增角色")
@PostMapping("/save")
public Result save(@RequestBody @Validated SysRole role) {
sysRoleService.save(role);
return Result.ok();
}

@Log(title = "角色管理", businessType = BusinessType.UPDATE)
@PreAuthorize("hasAuthority('bnt.sysRole.update')")
@ApiOperation(value = "修改角色")
@PutMapping("/update")
public Result updateById(@RequestBody SysRole role) {
sysRoleService.updateById(role);
return Result.ok();
}

@Log(title = "角色管理", businessType = BusinessType.DELETE)
@PreAuthorize("hasAuthority('bnt.sysRole.remove')")
@ApiOperation(value = "删除角色")
@DeleteMapping("/remove/{id}")
public Result remove(@PathVariable Long id) {
sysRoleService.removeById(id);
return Result.ok();
}

4.4、微服务改进

LogAspect类

1
2
3
//微服务切换为feign调用接口
@Resource
private AsyncOperLogService asyncOperLogService;

asyncOperLogService保存日志需替换为feign调用接口

4.5、操作日志显示

4.5.1、编写SysOperLogController
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
package com.atguigu.system.controller;

import com.atguigu.common.result.Result;
import com.atguigu.model.system.SysOperLog;
import com.atguigu.system.service.SysOperLogService;
import com.atguigu.model.vo.SysOperLogQueryVo;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;

@Api(value = "SysOperLog管理", tags = "SysOperLog管理")
@RestController
@RequestMapping(value="/admin/system/sysOperLog")
@SuppressWarnings({"unchecked", "rawtypes"})
public class SysOperLogController {

@Resource
private SysOperLogService sysOperLogService;

@ApiOperation(value = "获取分页列表")
@GetMapping("{page}/{limit}")
public Result index(
@ApiParam(name = "page", value = "当前页码", required = true)
@PathVariable Long page,

@ApiParam(name = "limit", value = "每页记录数", required = true)
@PathVariable Long limit,

@ApiParam(name = "sysOperLogVo", value = "查询对象", required = false)
SysOperLogQueryVo sysOperLogQueryVo) {
Page<SysOperLog> pageParam = new Page<>(page, limit);
IPage<SysOperLog> pageModel = sysOperLogService.selectPage(pageParam, sysOperLogQueryVo);
return Result.ok(pageModel);
}

@ApiOperation(value = "获取")
@GetMapping("get/{id}")
public Result get(@PathVariable Long id) {
SysOperLog sysOperLog = sysOperLogService.getById(id);
return Result.ok(sysOperLog);
}

}
4.5.2、编写SysOperLogService

(1)SysOperLogService

1
2
3
4
public interface SysOperLogService extends IService<SysOperLog> {

IPage<SysOperLog> selectPage(Page<SysOperLog> pageParam, SysOperLogQueryVo sysOperLogQueryVo);
}

(2)SysOperLogServiceImpl

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package com.atguigu.system.service.impl;

import com.atguigu.system.service.AsyncOperLogService;
import com.atguigu.model.system.SysOperLog;
import com.atguigu.system.mapper.SysOperLogMapper;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;

@Service
public class AsyncOperLogServiceImpl implements AsyncOperLogService {

@Resource
private SysOperLogMapper sysOperLogMapper;

@Async
@Override
public void saveSysLog(SysOperLog sysOperLog) {
sysOperLogMapper.insert(sysOperLog);
}
}
4.5.3、编写SysOperLogMapper

(1)SysOperLogMapper

1
2
3
4
5
6
7
@Mapper
@Repository
public interface SysOperLogMapper extends BaseMapper<SysOperLog> {

IPage<SysOperLog> selectPage(Page<SysOperLog> page, @Param("vo") SysOperLogQueryVo sysOperLogQueryVo);

}

(2)SysOperLogMapper.xml

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
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="com.atguigu.system.mapper.SysOperLogMapper">

<resultMap id="sysOperLogMap" type="com.atguigu.model.system.SysOperLog" autoMapping="true">
</resultMap>

<!-- 用于select查询公用抽取的列 -->
<sql id="columns">
id,title,business_type,method,request_method,operator_type,oper_name,dept_name,oper_url,oper_ip,oper_param,json_result,status,error_msg,oper_time,create_time,update_time,is_deleted
</sql>

<sql id="findPageWhere">
<where>
<if test="vo.title != null and vo.title != ''">
and title like CONCAT('%',#{vo.title},'%')
</if>
<if test="vo.operName != null and vo.operName != ''">
and oper_name like CONCAT('%',#{vo.operName},'%')
</if>
<if test="vo.createTimeBegin != null and vo.createTimeBegin != ''">
and create_time >= #{vo.createTimeBegin}
</if>
<if test="vo.createTimeEnd != null and vo.createTimeEnd != ''">
and create_time &lt;= #{vo.createTimeEnd}
</if>
and is_deleted = 0
</where>
</sql>

<select id="selectPage" resultMap="sysOperLogMap">
select <include refid="columns" />
from sys_oper_log
<include refid="findPageWhere"/>
order by id desc
</select>

</mapper>

4.6、操作日志前端

实现方式参考登录日志