完善新操作日志的页面修改

This commit is contained in:
YunaiV 2021-01-17 20:55:14 +08:00
parent 2fccaa2b9a
commit 1768d27e11
35 changed files with 648 additions and 663 deletions

View File

@ -22,7 +22,7 @@
<maven.compiler.target>${java.version}</maven.compiler.target>
<maven-compiler-plugin.version>3.8.0</maven-compiler-plugin.version>
<!-- 统一依赖管理 -->
<spring.boot.version>2.4.1</spring.boot.version>
<spring.boot.version>2.4.2</spring.boot.version>
<!-- <ruoyi.version>3.3.0</ruoyi.version>-->
<!-- <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>-->

View File

@ -23,28 +23,12 @@ import com.ruoyi.system.service.ISysOperLogService;
*
* @author ruoyi
*/
@RestController
@RequestMapping("/monitor/operlog")
public class SysOperlogController extends BaseController {
@Autowired
private ISysOperLogService operLogService;
@PreAuthorize("@ss.hasPermi('monitor:operlog:list')")
@GetMapping("/list")
public TableDataInfo list(SysOperLog operLog) {
startPage();
List<SysOperLog> list = operLogService.selectOperLogList(operLog);
return getDataTable(list);
}
@Log(title = "操作日志", businessType = BusinessType.EXPORT)
@PreAuthorize("@ss.hasPermi('monitor:operlog:export')")
@GetMapping("/export")
public AjaxResult export(SysOperLog operLog) {
List<SysOperLog> list = operLogService.selectOperLogList(operLog);
ExcelUtil<SysOperLog> util = new ExcelUtil<SysOperLog>(SysOperLog.class);
return util.exportExcel(list, "操作日志");
}
}

View File

@ -1,35 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>ruoyi</artifactId>
<groupId>com.ruoyi</groupId>
<version>3.3.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>ruoyi-common</artifactId>
<description>
common通用工具
</description>
<dependencies>
<!-- yml解析器 -->
<dependency>
<groupId>org.yaml</groupId>
<artifactId>snakeyaml</artifactId>
</dependency>
<!-- 解析客户端操作系统、浏览器等 -->
<dependency>
<groupId>eu.bitwalker</groupId>
<artifactId>UserAgentUtils</artifactId>
</dependency>
</dependencies>
</project>

View File

@ -1,41 +0,0 @@
package com.ruoyi.common.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.ruoyi.common.enums.BusinessType;
import com.ruoyi.common.enums.OperatorType;
/**
* 自定义操作日志记录注解
*
* @author ruoyi
*
*/
@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;
}

View File

@ -1,160 +0,0 @@
package com.ruoyi.framework.aspectj;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.Iterator;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
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.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.HandlerMapping;
import com.alibaba.fastjson.JSON;
import com.ruoyi.common.annotation.Log;
import com.ruoyi.common.core.domain.model.LoginUser;
import com.ruoyi.common.enums.BusinessStatus;
import com.ruoyi.common.enums.HttpMethod;
import com.ruoyi.common.utils.ServletUtils;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.ip.IpUtils;
import com.ruoyi.common.utils.spring.SpringUtils;
import com.ruoyi.framework.manager.AsyncManager;
import com.ruoyi.framework.manager.factory.AsyncFactory;
import com.ruoyi.framework.web.service.TokenService;
import com.ruoyi.system.domain.SysOperLog;
/**
* 操作日志记录处理
*
* @author ruoyi
*/
@Aspect
@Component
public class LogAspect {
private static final Logger log = LoggerFactory.getLogger(LogAspect.class);
// 配置织入点
@Pointcut("@annotation(com.ruoyi.common.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;
}
// 获取当前的用户
LoginUser loginUser = SpringUtils.getBean(TokenService.class).getLoginUser(ServletUtils.getRequest());
// *========数据库日志=========*//
SysOperLog operLog = new SysOperLog();
operLog.setStatus(BusinessStatus.SUCCESS.ordinal());
// 请求的地址
String ip = IpUtils.getIpAddr(ServletUtils.getRequest());
operLog.setOperIp(ip);
// 返回参数
operLog.setJsonResult(JSON.toJSONString(jsonResult));
operLog.setOperUrl(ServletUtils.getRequest().getRequestURI());
if (loginUser != null) {
operLog.setOperName(loginUser.getUsername());
}
if (e != null) {
operLog.setStatus(BusinessStatus.FAIL.ordinal());
operLog.setErrorMsg(StringUtils.substring(e.getMessage(), 0, 2000));
}
// 设置方法名称
String className = joinPoint.getTarget().getClass().getName();
String methodName = joinPoint.getSignature().getName();
operLog.setMethod(className + "." + methodName + "()");
// 设置请求方式
operLog.setRequestMethod(ServletUtils.getRequest().getMethod());
// 处理设置注解上的参数
getControllerMethodDescription(joinPoint, controllerLog, operLog);
// 保存数据库
AsyncManager.me().execute(AsyncFactory.recordOper(operLog));
} catch (Exception exp) {
// 记录本地异常日志
log.error("==前置通知异常==");
log.error("异常信息:{}", exp.getMessage());
exp.printStackTrace();
}
}
/**
* 参数拼装
*/
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])) {
Object jsonObj = JSON.toJSON(paramsArray[i]);
params += jsonObj.toString() + " ";
}
}
}
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;
}
}

View File

@ -1,29 +0,0 @@
package com.ruoyi.framework.config;
import java.util.TimeZone;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
/**
* 程序注解配置
*
* @author ruoyi
*/
@Configuration
// 表示通过aop框架暴露该代理对象,AopContext能够访问
@EnableAspectJAutoProxy(exposeProxy = true)
// 指定要扫描的Mapper类的包的路径
@MapperScan("com.ruoyi.**.mapper")
public class ApplicationConfig {
/**
* 时区配置
*/
@Bean
public Jackson2ObjectMapperBuilderCustomizer jacksonObjectMapperCustomization() {
return jacksonObjectMapperBuilder -> jacksonObjectMapperBuilder.timeZone(TimeZone.getDefault());
}
}

View File

@ -1,35 +0,0 @@
import request from '@/utils/request'
// 查询操作日志列表
export function list(query) {
return request({
url: '/monitor/operlog/list',
method: 'get',
params: query
})
}
// 删除操作日志
export function delOperlog(operId) {
return request({
url: '/monitor/operlog/' + operId,
method: 'delete'
})
}
// 清空操作日志
export function cleanOperlog() {
return request({
url: '/monitor/operlog/clean',
method: 'delete'
})
}
// 导出操作日志
export function exportOperlog(query) {
return request({
url: '/monitor/operlog/export',
method: 'get',
params: query
})
}

View File

@ -0,0 +1,19 @@
import request from '@/utils/request'
// 查询操作日志列表
export function listOperateLog(query) {
return request({
url: '/system/operate-log/page',
method: 'get',
params: query
})
}
// 导出操作日志
export function exportOperateLog(query) {
return request({
url: '/system/operate-log/export',
method: 'get',
params: query
})
}

View File

@ -23,6 +23,7 @@ import RightToolbar from "@/components/RightToolbar"
// 代码高亮插件
import hljs from 'highlight.js'
import 'highlight.js/styles/github-gist.css'
import {DICT_TYPE, getDictDataLabel, getDictDatas} from "@/utils/dict";
// 全局方法挂载
Vue.prototype.getDicts = getDicts
@ -32,6 +33,9 @@ Vue.prototype.resetForm = resetForm
Vue.prototype.addDateRange = addDateRange
Vue.prototype.selectDictLabel = selectDictLabel
Vue.prototype.selectDictLabels = selectDictLabels
Vue.prototype.getDictDatas = getDictDatas
Vue.prototype.getDictDataLabel = getDictDataLabel
Vue.prototype.DICT_TYPE = DICT_TYPE
Vue.prototype.download = download
Vue.prototype.downloadExcel = downloadExcel
Vue.prototype.handleTree = handleTree

View File

@ -12,6 +12,7 @@ export const DICT_TYPE = {
SYS_DATA_SCOPE: 'sys_data_scope',
SYS_USER_SEX: 'sys_user_sex',
SYS_NOTICE_TYPE: 'sys_notice_type',
SYS_OPERATE_TYPE: 'sys_operate_type'
}
/**

View File

@ -1,318 +1,275 @@
<template>
<div class="app-container">
<el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="68px">
<el-form-item label="系统模块" prop="title">
<el-input
v-model="queryParams.title"
placeholder="请输入系统模块"
clearable
style="width: 240px;"
size="small"
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item label="操作人员" prop="operName">
<el-input
v-model="queryParams.operName"
placeholder="请输入操作人员"
clearable
style="width: 240px;"
size="small"
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item label="类型" prop="businessType">
<el-select
v-model="queryParams.businessType"
placeholder="操作类型"
clearable
size="small"
style="width: 240px"
>
<el-option
v-for="dict in typeOptions"
:key="dict.dictValue"
:label="dict.dictLabel"
:value="dict.dictValue"
/>
</el-select>
</el-form-item>
<el-form-item label="状态" prop="status">
<el-select
v-model="queryParams.status"
placeholder="操作状态"
clearable
size="small"
style="width: 240px"
>
<el-option
v-for="dict in statusOptions"
:key="dict.dictValue"
:label="dict.dictLabel"
:value="dict.dictValue"
/>
</el-select>
</el-form-item>
<el-form-item label="操作时间">
<el-date-picker
v-model="dateRange"
size="small"
style="width: 240px"
value-format="yyyy-MM-dd"
type="daterange"
range-separator="-"
start-placeholder="开始日期"
end-placeholder="结束日期"
></el-date-picker>
</el-form-item>
<el-form-item>
<el-button type="cyan" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button
type="danger"
icon="el-icon-delete"
size="mini"
:disabled="multiple"
@click="handleDelete"
v-hasPermi="['monitor:operlog:remove']"
>删除</el-button>
</el-col>
<el-col :span="1.5">
<el-button
type="danger"
icon="el-icon-delete"
size="mini"
@click="handleClean"
v-hasPermi="['monitor:operlog:remove']"
>清空</el-button>
</el-col>
<el-col :span="1.5">
<el-button
type="warning"
icon="el-icon-download"
size="mini"
@click="handleExport"
v-hasPermi="['system:config:export']"
>导出</el-button>
</el-col>
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
<el-table v-loading="loading" :data="list" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" align="center" />
<el-table-column label="日志编号" align="center" prop="operId" />
<el-table-column label="系统模块" align="center" prop="title" />
<el-table-column label="操作类型" align="center" prop="businessType" :formatter="typeFormat" />
<el-table-column label="请求方式" align="center" prop="requestMethod" />
<el-table-column label="操作人员" align="center" prop="operName" />
<el-table-column label="主机" align="center" prop="operIp" width="130" :show-overflow-tooltip="true" />
<el-table-column label="操作地点" align="center" prop="operLocation" :show-overflow-tooltip="true" />
<el-table-column label="操作状态" align="center" prop="status" :formatter="statusFormat" />
<el-table-column label="操作日期" align="center" prop="operTime" width="180">
<template slot-scope="scope">
<span>{{ parseTime(scope.row.operTime) }}</span>
</template>
</el-table-column>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template slot-scope="scope">
<el-button
size="mini"
type="text"
icon="el-icon-view"
@click="handleView(scope.row,scope.index)"
v-hasPermi="['monitor:operlog:query']"
>详细</el-button>
</template>
</el-table-column>
</el-table>
<pagination
v-show="total>0"
:total="total"
:page.sync="queryParams.pageNum"
:limit.sync="queryParams.pageSize"
@pagination="getList"
/>
<!-- 操作日志详细 -->
<el-dialog title="操作日志详细" :visible.sync="open" width="700px" append-to-body>
<el-form ref="form" :model="form" label-width="100px" size="mini">
<el-row>
<el-col :span="12">
<el-form-item label="操作模块:">{{ form.title }} / {{ typeFormat(form) }}</el-form-item>
<el-form-item
label="登录信息:"
>{{ form.operName }} / {{ form.operIp }} / {{ form.operLocation }}</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="请求地址:">{{ form.operUrl }}</el-form-item>
<el-form-item label="请求方式:">{{ form.requestMethod }}</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="操作方法:">{{ form.method }}</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="请求参数:">{{ form.operParam }}</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="返回参数:">{{ form.jsonResult }}</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="操作状态:">
<div v-if="form.status === 0">正常</div>
<div v-else-if="form.status === 1">失败</div>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="操作时间:">{{ parseTime(form.operTime) }}</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="异常信息:" v-if="form.status === 1">{{ form.errorMsg }}</el-form-item>
</el-col>
</el-row>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="open = false"> </el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import { list, delOperlog, cleanOperlog, exportOperlog } from "@/api/monitor/operlog";
export default {
name: "Operlog",
data() {
return {
//
loading: true,
//
ids: [],
//
multiple: true,
//
showSearch: true,
//
total: 0,
//
list: [],
//
open: false,
//
typeOptions: [],
//
statusOptions: [],
//
dateRange: [],
//
form: {},
//
queryParams: {
pageNum: 1,
pageSize: 10,
title: undefined,
operName: undefined,
businessType: undefined,
status: undefined
}
};
},
created() {
this.getList();
this.getDicts("sys_oper_type").then(response => {
this.typeOptions = response.data;
});
this.getDicts("sys_common_status").then(response => {
this.statusOptions = response.data;
});
},
methods: {
/** 查询登录日志 */
getList() {
this.loading = true;
list(this.addDateRange(this.queryParams, this.dateRange)).then( response => {
this.list = response.rows;
this.total = response.total;
this.loading = false;
}
);
},
//
statusFormat(row, column) {
return this.selectDictLabel(this.statusOptions, row.status);
},
//
typeFormat(row, column) {
return this.selectDictLabel(this.typeOptions, row.businessType);
},
/** 搜索按钮操作 */
handleQuery() {
this.queryParams.pageNum = 1;
this.getList();
},
/** 重置按钮操作 */
resetQuery() {
this.dateRange = [];
this.resetForm("queryForm");
this.handleQuery();
},
//
handleSelectionChange(selection) {
this.ids = selection.map(item => item.operId)
this.multiple = !selection.length
},
/** 详细按钮操作 */
handleView(row) {
this.open = true;
this.form = row;
},
/** 删除按钮操作 */
handleDelete(row) {
const operIds = row.operId || this.ids;
this.$confirm('是否确认删除日志编号为"' + operIds + '"的数据项?', "警告", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning"
}).then(function() {
return delOperlog(operIds);
}).then(() => {
this.getList();
this.msgSuccess("删除成功");
})
},
/** 清空按钮操作 */
handleClean() {
this.$confirm('是否确认清空所有操作日志数据项?', "警告", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning"
}).then(function() {
return cleanOperlog();
}).then(() => {
this.getList();
this.msgSuccess("清空成功");
})
},
/** 导出按钮操作 */
handleExport() {
const queryParams = this.queryParams;
this.$confirm('是否确认导出所有操作日志数据项?', "警告", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning"
}).then(function() {
return exportOperlog(queryParams);
}).then(response => {
this.download(response.msg);
})
}
}
};
</script>
<template>
<div class="app-container">
<el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="68px">
<el-form-item label="系统模块" prop="title">
<el-input
v-model="queryParams.title"
placeholder="请输入系统模块"
clearable
style="width: 240px;"
size="small"
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item label="操作人员" prop="operName">
<el-input
v-model="queryParams.operName"
placeholder="请输入操作人员"
clearable
style="width: 240px;"
size="small"
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item label="类型" prop="type">
<el-select
v-model="queryParams.type"
placeholder="操作类型"
clearable
size="small"
style="width: 240px"
>
<el-option
v-for="dict in this.getDictDatas(DICT_TYPE.SYS_OPERATE_TYPE)"
:key="parseInt(dict.value)"
:label="dict.label"
:value="parseInt(dict.value)"
/>
</el-select>
</el-form-item>
<el-form-item label="状态" prop="status">
<el-select
v-model="queryParams.success"
placeholder="操作状态"
clearable
size="small"
style="width: 240px"
>
<el-option
:key="true"
label="成功"
:value="true"
/>
<el-option
:key="false"
label="失败"
:value="false"
/>
</el-select>
</el-form-item>
<el-form-item label="操作时间">
<el-date-picker
v-model="dateRange"
size="small"
style="width: 240px"
value-format="yyyy-MM-dd"
type="daterange"
range-separator="-"
start-placeholder="开始日期"
end-placeholder="结束日期"
></el-date-picker>
</el-form-item>
<el-form-item>
<el-button type="cyan" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button
type="warning"
icon="el-icon-download"
size="mini"
@click="handleExport"
v-hasPermi="['system:config:export']"
>导出</el-button>
</el-col>
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
<el-table v-loading="loading" :data="list">
<el-table-column label="日志编号" align="center" prop="id" />
<el-table-column label="操作模块" align="center" prop="module" />
<el-table-column label="操作名" align="center" prop="name" width="180" />
<el-table-column label="操作类型" align="center" prop="type">
<template slot-scope="scope">
<span>{{ getDictDataLabel(DICT_TYPE.SYS_OPERATE_TYPE, scope.row.type) }}</span>
</template>
</el-table-column>
<el-table-column label="操作人" align="center" prop="userNickname" />
<el-table-column label="操作结果" align="center" prop="status">
<template slot-scope="scope">
<span>{{ scope.row.resultCode === 0 ? '成功' : '失败' }}</span>
</template>
</el-table-column>
<el-table-column label="操作日期" align="center" prop="startTime" width="180">
<template slot-scope="scope">
<span>{{ parseTime(scope.row.startTime) }}</span>
</template>
</el-table-column>
<el-table-column label="执行时长" align="center" prop="startTime">
<template slot-scope="scope">
<span>{{ scope.row.duration }} ms</span>
</template>
</el-table-column>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template slot-scope="scope">
<el-button
size="mini"
type="text"
icon="el-icon-view"
@click="handleView(scope.row,scope.index)"
v-hasPermi="['system:operate-log:query']"
>详细</el-button>
</template>
</el-table-column>
</el-table>
<pagination
v-show="total>0"
:total="total"
:page.sync="queryParams.pageNo"
:limit.sync="queryParams.pageSize"
@pagination="getList"
/>
<!-- 操作日志详细 -->
<el-dialog title="操作日志详细" :visible.sync="open" width="700px" append-to-body>
<el-form ref="form" :model="form" label-width="100px" size="mini">
<el-row>
<el-col :span="12">
<el-form-item label="操作模块:">{{ form.title }} / {{ typeFormat(form) }}</el-form-item>
<el-form-item
label="登录信息:"
>{{ form.operName }} / {{ form.operIp }} / {{ form.operLocation }}</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="请求地址:">{{ form.operUrl }}</el-form-item>
<el-form-item label="请求方式:">{{ form.requestMethod }}</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="操作方法:">{{ form.method }}</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="请求参数:">{{ form.operParam }}</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="返回参数:">{{ form.jsonResult }}</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="操作状态:">
<div v-if="form.status === 0">正常</div>
<div v-else-if="form.status === 1">失败</div>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="操作时间:">{{ parseTime(form.operTime) }}</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="异常信息:" v-if="form.status === 1">{{ form.errorMsg }}</el-form-item>
</el-col>
</el-row>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="open = false"> </el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import { listOperateLog, exportOperateLog } from "@/api/system/operatelog";
export default {
name: "Operlog",
data() {
return {
//
loading: true,
//
showSearch: true,
//
total: 0,
//
list: [],
//
open: false,
//
typeOptions: [],
//
statusOptions: [],
//
dateRange: [],
//
form: {},
//
queryParams: {
pageNo: 1,
pageSize: 10,
title: undefined,
operName: undefined,
businessType: undefined,
status: undefined
},
};
},
created() {
this.getList();
},
methods: {
/** 查询登录日志 */
getList() {
this.loading = true;
listOperateLog(this.addDateRange(this.queryParams, [
this.dateRange[0] ? this.dateRange[0] + ' 00:00:00' : undefined,
this.dateRange[1] ? this.dateRange[1] + ' 23:59:59' : undefined,
])).then( response => {
this.list = response.data.list;
this.total = response.data.total;
this.loading = false;
}
);
},
//
statusFormat(row, column) {
return this.selectDictLabel(this.statusOptions, row.status);
},
//
typeFormat(row, column) {
return this.selectDictLabel(this.typeOptions, row.businessType);
},
/** 搜索按钮操作 */
handleQuery() {
this.queryParams.pageNo = 1;
this.getList();
},
/** 重置按钮操作 */
resetQuery() {
this.dateRange = [];
this.resetForm("queryForm");
this.handleQuery();
},
/** 详细按钮操作 */
handleView(row) {
this.open = true;
this.form = row;
},
/** 导出按钮操作 */
handleExport() {
const queryParams = this.queryParams;
this.$confirm('是否确认导出所有操作日志数据项?', "警告", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning"
}).then(function() {
return exportOperateLog(queryParams);
}).then(response => {
this.download(response.msg);
})
}
}
};
</script>

View File

@ -0,0 +1,29 @@
INSERT INTO `ruoyi-vue-pro`.`sys_dict_data`(`id`, `sort`, `label`, `value`, `dict_type`, `status`, `remark`, `create_by`, `create_time`, `update_by`, `update_time`, `deleted`) VALUES (1, 1, '', '1', 'sys_user_sex', 0, '性别男', 'admin', '2021-01-05 17:03:48', '', '2021-01-06 05:48:53', b'0');
INSERT INTO `ruoyi-vue-pro`.`sys_dict_data`(`id`, `sort`, `label`, `value`, `dict_type`, `status`, `remark`, `create_by`, `create_time`, `update_by`, `update_time`, `deleted`) VALUES (2, 2, '', '2', 'sys_user_sex', 0, '性别女', 'admin', '2021-01-05 17:03:48', '', '2021-01-06 05:48:55', b'0');
INSERT INTO `ruoyi-vue-pro`.`sys_dict_data`(`id`, `sort`, `label`, `value`, `dict_type`, `status`, `remark`, `create_by`, `create_time`, `update_by`, `update_time`, `deleted`) VALUES (8, 1, '正常', '0', 'sys_job_status', 0, '正常状态', 'admin', '2021-01-05 17:03:48', '', '2021-01-06 00:02:28', b'0');
INSERT INTO `ruoyi-vue-pro`.`sys_dict_data`(`id`, `sort`, `label`, `value`, `dict_type`, `status`, `remark`, `create_by`, `create_time`, `update_by`, `update_time`, `deleted`) VALUES (9, 2, '暂停', '1', 'sys_job_status', 0, '停用状态', 'admin', '2021-01-05 17:03:48', '', '2021-01-06 00:02:28', b'0');
INSERT INTO `ruoyi-vue-pro`.`sys_dict_data`(`id`, `sort`, `label`, `value`, `dict_type`, `status`, `remark`, `create_by`, `create_time`, `update_by`, `update_time`, `deleted`) VALUES (10, 1, '默认', 'DEFAULT', 'sys_job_group', 0, '默认分组', 'admin', '2021-01-05 17:03:48', '', '2021-01-06 00:02:28', b'0');
INSERT INTO `ruoyi-vue-pro`.`sys_dict_data`(`id`, `sort`, `label`, `value`, `dict_type`, `status`, `remark`, `create_by`, `create_time`, `update_by`, `update_time`, `deleted`) VALUES (11, 2, '系统', 'SYSTEM', 'sys_job_group', 0, '系统分组', 'admin', '2021-01-05 17:03:48', '', '2021-01-06 00:02:28', b'0');
INSERT INTO `ruoyi-vue-pro`.`sys_dict_data`(`id`, `sort`, `label`, `value`, `dict_type`, `status`, `remark`, `create_by`, `create_time`, `update_by`, `update_time`, `deleted`) VALUES (12, 1, '', 'Y', 'sys_yes_no', 0, '系统默认是', 'admin', '2021-01-05 17:03:48', '', '2021-01-06 00:02:28', b'0');
INSERT INTO `ruoyi-vue-pro`.`sys_dict_data`(`id`, `sort`, `label`, `value`, `dict_type`, `status`, `remark`, `create_by`, `create_time`, `update_by`, `update_time`, `deleted`) VALUES (13, 2, '', 'N', 'sys_yes_no', 0, '系统默认否', 'admin', '2021-01-05 17:03:48', '', '2021-01-06 00:02:28', b'0');
INSERT INTO `ruoyi-vue-pro`.`sys_dict_data`(`id`, `sort`, `label`, `value`, `dict_type`, `status`, `remark`, `create_by`, `create_time`, `update_by`, `update_time`, `deleted`) VALUES (14, 1, '通知', '1', 'sys_notice_type', 0, '通知', 'admin', '2021-01-05 17:03:48', '', '2021-01-06 00:02:28', b'0');
INSERT INTO `ruoyi-vue-pro`.`sys_dict_data`(`id`, `sort`, `label`, `value`, `dict_type`, `status`, `remark`, `create_by`, `create_time`, `update_by`, `update_time`, `deleted`) VALUES (15, 2, '公告', '2', 'sys_notice_type', 0, '公告', 'admin', '2021-01-05 17:03:48', '', '2021-01-06 00:02:28', b'0');
INSERT INTO `ruoyi-vue-pro`.`sys_dict_data`(`id`, `sort`, `label`, `value`, `dict_type`, `status`, `remark`, `create_by`, `create_time`, `update_by`, `update_time`, `deleted`) VALUES (16, 0, '其它', '0', 'sys_operate_type', 0, '其它操作', 'admin', '2021-01-05 17:03:48', '', '2021-01-16 13:51:12', b'0');
INSERT INTO `ruoyi-vue-pro`.`sys_dict_data`(`id`, `sort`, `label`, `value`, `dict_type`, `status`, `remark`, `create_by`, `create_time`, `update_by`, `update_time`, `deleted`) VALUES (17, 1, '查询', '1', 'sys_operate_type', 0, '查询操作', 'admin', '2021-01-05 17:03:48', '', '2021-01-16 13:51:10', b'0');
INSERT INTO `ruoyi-vue-pro`.`sys_dict_data`(`id`, `sort`, `label`, `value`, `dict_type`, `status`, `remark`, `create_by`, `create_time`, `update_by`, `update_time`, `deleted`) VALUES (18, 2, '新增', '2', 'sys_operate_type', 0, '新增操作', 'admin', '2021-01-05 17:03:48', '', '2021-01-16 13:51:17', b'0');
INSERT INTO `ruoyi-vue-pro`.`sys_dict_data`(`id`, `sort`, `label`, `value`, `dict_type`, `status`, `remark`, `create_by`, `create_time`, `update_by`, `update_time`, `deleted`) VALUES (19, 3, '修改', '3', 'sys_operate_type', 0, '修改操作', 'admin', '2021-01-05 17:03:48', '', '2021-01-16 13:51:20', b'0');
INSERT INTO `ruoyi-vue-pro`.`sys_dict_data`(`id`, `sort`, `label`, `value`, `dict_type`, `status`, `remark`, `create_by`, `create_time`, `update_by`, `update_time`, `deleted`) VALUES (20, 4, '删除', '4', 'sys_operate_type', 0, '删除操作', 'admin', '2021-01-05 17:03:48', '', '2021-01-16 13:51:24', b'0');
INSERT INTO `ruoyi-vue-pro`.`sys_dict_data`(`id`, `sort`, `label`, `value`, `dict_type`, `status`, `remark`, `create_by`, `create_time`, `update_by`, `update_time`, `deleted`) VALUES (22, 5, '导出', '5', 'sys_operate_type', 0, '导出操作', 'admin', '2021-01-05 17:03:48', '', '2021-01-16 13:49:20', b'0');
INSERT INTO `ruoyi-vue-pro`.`sys_dict_data`(`id`, `sort`, `label`, `value`, `dict_type`, `status`, `remark`, `create_by`, `create_time`, `update_by`, `update_time`, `deleted`) VALUES (23, 6, '导入', '6', 'sys_operate_type', 0, '导入操作', 'admin', '2021-01-05 17:03:48', '', '2021-01-16 13:49:24', b'0');
INSERT INTO `ruoyi-vue-pro`.`sys_dict_data`(`id`, `sort`, `label`, `value`, `dict_type`, `status`, `remark`, `create_by`, `create_time`, `update_by`, `update_time`, `deleted`) VALUES (27, 1, '开启', '0', 'sys_common_status', 0, '开启状态', 'admin', '2021-01-05 17:03:48', '', '2021-01-06 02:57:12', b'0');
INSERT INTO `ruoyi-vue-pro`.`sys_dict_data`(`id`, `sort`, `label`, `value`, `dict_type`, `status`, `remark`, `create_by`, `create_time`, `update_by`, `update_time`, `deleted`) VALUES (28, 2, '关闭', '1', 'sys_common_status', 0, '关闭状态', 'admin', '2021-01-05 17:03:48', '', '2021-01-06 05:48:32', b'0');
INSERT INTO `ruoyi-vue-pro`.`sys_dict_data`(`id`, `sort`, `label`, `value`, `dict_type`, `status`, `remark`, `create_by`, `create_time`, `update_by`, `update_time`, `deleted`) VALUES (29, 1, '目录', '1', 'sys_menu_type', 0, '目录', 'admin', '2021-01-05 17:03:48', '', '2021-01-06 13:33:30', b'0');
INSERT INTO `ruoyi-vue-pro`.`sys_dict_data`(`id`, `sort`, `label`, `value`, `dict_type`, `status`, `remark`, `create_by`, `create_time`, `update_by`, `update_time`, `deleted`) VALUES (30, 2, '菜单', '2', 'sys_menu_type', 0, '菜单', 'admin', '2021-01-05 17:03:48', '', '2021-01-06 13:33:35', b'0');
INSERT INTO `ruoyi-vue-pro`.`sys_dict_data`(`id`, `sort`, `label`, `value`, `dict_type`, `status`, `remark`, `create_by`, `create_time`, `update_by`, `update_time`, `deleted`) VALUES (31, 3, '按钮', '3', 'sys_menu_type', 0, '按钮', 'admin', '2021-01-05 17:03:48', '', '2021-01-06 13:33:38', b'0');
INSERT INTO `ruoyi-vue-pro`.`sys_dict_data`(`id`, `sort`, `label`, `value`, `dict_type`, `status`, `remark`, `create_by`, `create_time`, `update_by`, `update_time`, `deleted`) VALUES (32, 1, '内置', '1', 'sys_role_type', 0, '内置角色', 'admin', '2021-01-05 17:03:48', '', '2021-01-06 13:34:22', b'0');
INSERT INTO `ruoyi-vue-pro`.`sys_dict_data`(`id`, `sort`, `label`, `value`, `dict_type`, `status`, `remark`, `create_by`, `create_time`, `update_by`, `update_time`, `deleted`) VALUES (33, 2, '自定义', '2', 'sys_role_type', 0, '自定义角色', 'admin', '2021-01-05 17:03:48', '', '2021-01-06 13:34:26', b'0');
INSERT INTO `ruoyi-vue-pro`.`sys_dict_data`(`id`, `sort`, `label`, `value`, `dict_type`, `status`, `remark`, `create_by`, `create_time`, `update_by`, `update_time`, `deleted`) VALUES (34, 1, '全部数据权限', '1', 'sys_data_scope', 0, '全部数据权限', 'admin', '2021-01-05 17:03:48', '', '2021-01-06 19:38:02', b'0');
INSERT INTO `ruoyi-vue-pro`.`sys_dict_data`(`id`, `sort`, `label`, `value`, `dict_type`, `status`, `remark`, `create_by`, `create_time`, `update_by`, `update_time`, `deleted`) VALUES (35, 2, '指定部门数据权限', '2', 'sys_data_scope', 0, '指定部门数据权限', 'admin', '2021-01-05 17:03:48', '', '2021-01-06 19:38:20', b'0');
INSERT INTO `ruoyi-vue-pro`.`sys_dict_data`(`id`, `sort`, `label`, `value`, `dict_type`, `status`, `remark`, `create_by`, `create_time`, `update_by`, `update_time`, `deleted`) VALUES (36, 3, '本部门数据权限', '3', 'sys_data_scope', 0, '本部门数据权限', 'admin', '2021-01-05 17:03:48', '', '2021-01-06 19:38:29', b'0');
INSERT INTO `ruoyi-vue-pro`.`sys_dict_data`(`id`, `sort`, `label`, `value`, `dict_type`, `status`, `remark`, `create_by`, `create_time`, `update_by`, `update_time`, `deleted`) VALUES (37, 4, '本部门及以下数据权限', '4', 'sys_data_scope', 0, '本部门及以下数据权限', 'admin', '2021-01-05 17:03:48', '', '2021-01-06 19:38:32', b'0');
INSERT INTO `ruoyi-vue-pro`.`sys_dict_data`(`id`, `sort`, `label`, `value`, `dict_type`, `status`, `remark`, `create_by`, `create_time`, `update_by`, `update_time`, `deleted`) VALUES (38, 5, '仅本人数据权限', '5', 'sys_data_scope', 0, '仅本人数据权限', 'admin', '2021-01-05 17:03:48', '', '2021-01-06 19:38:38', b'0');

View File

@ -31,4 +31,8 @@ public final class PageResult<T> implements Serializable {
this.total = total;
}
public static <T> PageResult<T> empty() {
return new PageResult<>(0L);
}
}

View File

@ -4,6 +4,6 @@ import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableAsync;
@Configuration
@EnableAsync(proxyTargetClass = true)
@EnableAsync
public class AsyncConfiguration {
}

View File

@ -1,6 +1,6 @@
package cn.iocoder.dashboard.framework.logger.operatelog.core.annotations;
import cn.iocoder.dashboard.modules.system.enums.logger.SysOperateLogTypeEnum;
import cn.iocoder.dashboard.framework.logger.operatelog.core.enums.OperateLogTypeEnum;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
@ -32,7 +32,7 @@ public @interface OperateLog {
*
* 实际并不是数组因为枚举不能设置 null 作为默认值
*/
SysOperateLogTypeEnum[] type() default {};
OperateLogTypeEnum[] type() default {};
// ========== 开关字段 ==========

View File

@ -6,11 +6,11 @@ import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.servlet.ServletUtil;
import cn.iocoder.dashboard.common.pojo.CommonResult;
import cn.iocoder.dashboard.framework.logger.operatelog.core.annotations.OperateLog;
import cn.iocoder.dashboard.framework.logger.operatelog.core.enums.OperateLogTypeEnum;
import cn.iocoder.dashboard.framework.logger.operatelog.core.service.OperateLogFrameworkService;
import cn.iocoder.dashboard.framework.security.core.util.SecurityUtils;
import cn.iocoder.dashboard.framework.tracer.core.util.TracerUtils;
import cn.iocoder.dashboard.modules.system.controller.logger.vo.SysOperateLogCreateReqVO;
import cn.iocoder.dashboard.modules.system.enums.logger.SysOperateLogTypeEnum;
import cn.iocoder.dashboard.util.servlet.ServletUtils;
import com.alibaba.fastjson.JSON;
import com.google.common.collect.Maps;
@ -180,7 +180,7 @@ public class OperateLogAspect {
}
if (operateLogVO.getType() == null) {
RequestMethod requestMethod = obtainFirstMatchRequestMethod(obtainRequestMethod(joinPoint));
SysOperateLogTypeEnum operateLogType = convertOperateLogType(requestMethod);
OperateLogTypeEnum operateLogType = convertOperateLogType(requestMethod);
operateLogVO.setType(operateLogType != null ? operateLogType.getType() : null);
}
// content exts 属性
@ -275,21 +275,21 @@ public class OperateLogAspect {
return requestMethods[0];
}
private static SysOperateLogTypeEnum convertOperateLogType(RequestMethod requestMethod) {
private static OperateLogTypeEnum convertOperateLogType(RequestMethod requestMethod) {
if (requestMethod == null) {
return null;
}
switch (requestMethod) {
case GET:
return SysOperateLogTypeEnum.GET;
return OperateLogTypeEnum.GET;
case POST:
return SysOperateLogTypeEnum.CREATE;
return OperateLogTypeEnum.CREATE;
case PUT:
return SysOperateLogTypeEnum.UPDATE;
return OperateLogTypeEnum.UPDATE;
case DELETE:
return SysOperateLogTypeEnum.DELETE;
return OperateLogTypeEnum.DELETE;
default:
return SysOperateLogTypeEnum.OTHER;
return OperateLogTypeEnum.OTHER;
}
}

View File

@ -1,4 +1,4 @@
package cn.iocoder.dashboard.modules.system.enums.logger;
package cn.iocoder.dashboard.framework.logger.operatelog.core.enums;
import cn.iocoder.dashboard.framework.logger.operatelog.core.annotations.OperateLog;
import lombok.AllArgsConstructor;
@ -11,7 +11,7 @@ import lombok.Getter;
*/
@Getter
@AllArgsConstructor
public enum SysOperateLogTypeEnum {
public enum OperateLogTypeEnum {
/**
* 查询

View File

@ -0,0 +1,24 @@
package cn.iocoder.dashboard.framework.mybatis.core.mapper;
import cn.iocoder.dashboard.common.pojo.PageParam;
import cn.iocoder.dashboard.common.pojo.PageResult;
import cn.iocoder.dashboard.framework.mybatis.core.util.MyBatisUtils;
import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import org.apache.ibatis.annotations.Param;
/**
* MyBatis Plus BaseMapper 的基础上拓展提供更多的能力
*/
public interface BaseMapperX<T> extends BaseMapper<T> {
default PageResult<T> selectPage(PageParam pageParam, @Param("ew") Wrapper<T> queryWrapper) {
// MyBatis Plus 查询
IPage<T> mpPage = MyBatisUtils.buildPage(pageParam);
selectPage(mpPage, queryWrapper);
// 转换返回
return new PageResult<>(mpPage.getRecords(), mpPage.getTotal());
}
}

View File

@ -54,7 +54,6 @@ public class SysAuthController {
@ApiOperation("获取登陆用户的权限信息")
@GetMapping("/get-permission-info")
@OperateLog
public CommonResult<SysAuthPermissionInfoRespVO> getPermissionInfo() {
// 获得用户信息
SysUserDO user = userService.getUser(getLoginUserId());

View File

@ -1,2 +1,2 @@
### 请求 /captcha/get-image 接口 => 成功
GET {{baseUrl}}/captcha/get-image
GET {{baseUrl}}/system/captcha/get-image

View File

@ -23,7 +23,7 @@ public class SysCaptchaController {
@ApiOperation("生成图片验证码")
@GetMapping("/get-image")
private CommonResult<SysCaptchaImageRespVO> getCaptchaImage() {
public CommonResult<SysCaptchaImageRespVO> getCaptchaImage() {
return success(captchaService.getCaptchaImage());
}

View File

@ -0,0 +1,3 @@
### 请求 /system/operate-log/demo 接口 => 成功
GET {{baseUrl}}/system/operate-log/demo
Authorization: Bearer {{token}}

View File

@ -0,0 +1,86 @@
package cn.iocoder.dashboard.modules.system.controller.logger;
import cn.iocoder.dashboard.common.pojo.CommonResult;
import cn.iocoder.dashboard.common.pojo.PageResult;
import cn.iocoder.dashboard.framework.logger.operatelog.core.annotations.OperateLog;
import cn.iocoder.dashboard.framework.logger.operatelog.core.enums.OperateLogTypeEnum;
import cn.iocoder.dashboard.framework.logger.operatelog.core.util.OperateLogUtils;
import cn.iocoder.dashboard.modules.system.controller.logger.vo.SysOperateLogPageReqVO;
import cn.iocoder.dashboard.modules.system.controller.logger.vo.SysOperateLogRespVO;
import cn.iocoder.dashboard.modules.system.convert.logger.SysOperateLogConvert;
import cn.iocoder.dashboard.modules.system.dal.mysql.dataobject.logger.SysOperateLogDO;
import cn.iocoder.dashboard.modules.system.dal.mysql.dataobject.user.SysUserDO;
import cn.iocoder.dashboard.modules.system.service.logger.SysOperateLogService;
import cn.iocoder.dashboard.modules.system.service.user.SysUserService;
import cn.iocoder.dashboard.util.collection.CollectionUtils;
import cn.iocoder.dashboard.util.collection.MapUtils;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import static cn.iocoder.dashboard.common.pojo.CommonResult.success;
@Api(tags = "操作日志 API")
@RestController
@RequestMapping("/system/operate-log")
public class SysOperateLogController {
@Resource
private SysOperateLogService operateLogService;
@Resource
private SysUserService userService;
@ApiOperation("示例")
@OperateLog(type = OperateLogTypeEnum.OTHER)
@GetMapping("/demo")
public CommonResult<Boolean> demo() {
// 这里可以调用业务逻辑
// 补全操作日志的明细
OperateLogUtils.setContent("将编号 1 的数据xxx 字段修改成了 yyyy");
OperateLogUtils.addExt("orderId", 1);
// 响应
return success(true);
}
@ApiOperation("查看操作日志分页列表")
@GetMapping("/page")
// @PreAuthorize("@ss.hasPermi('system:operate-log:query')")
public CommonResult<PageResult<SysOperateLogRespVO>> pageOperateLog(@Validated SysOperateLogPageReqVO reqVO) {
PageResult<SysOperateLogDO> pageResult = operateLogService.pageOperateLog(reqVO);
// 获得拼接需要的数据
Collection<Long> userIds = CollectionUtils.convertList(pageResult.getList(), SysOperateLogDO::getUserId);
Map<Long, SysUserDO> userMap = userService.getUserMap(userIds);
// 拼接数据
List<SysOperateLogRespVO> list = new ArrayList<>(pageResult.getList().size());
pageResult.getList().forEach(operateLog -> {
SysOperateLogRespVO respVO = SysOperateLogConvert.INSTANCE.convert(operateLog);
list.add(respVO);
// 拼接用户信息
MapUtils.findAndThen(userMap, operateLog.getUserId(), user -> respVO.setUserNickname(user.getNickname()));
});
return success(new PageResult<>(list, pageResult.getTotal()));
}
// @Log(title = "操作日志", businessType = BusinessType.EXPORT)
// @PreAuthorize("@ss.hasPermi('system:operate-log:export')")
// @GetMapping("/export")
// public AjaxResult export(SysOperLog operLog) {
// List<SysOperLog> list = operLogService.selectOperLogList(operLog);
// ExcelUtil<SysOperLog> util = new ExcelUtil<SysOperLog>(SysOperLog.class);
// return util.exportExcel(list, "操作日志");
// }
}

View File

@ -1 +0,0 @@
package cn.iocoder.dashboard.modules.system.controller.logger;

View File

@ -6,6 +6,7 @@ import lombok.Data;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import java.util.Date;
import java.util.Map;
/**
* 操作日志 Base VO提供给添加修改详细的子 VO 使用
@ -30,10 +31,16 @@ public class SysOperateLogBaseVO {
@NotEmpty(message = "操作名")
private String name;
@ApiModelProperty(value = "操作分类", required = true, example = "操作分类", notes = "参见 SysOperateLogTypeEnum 枚举类")
@ApiModelProperty(value = "操作分类", required = true, example = "1", notes = "参见 SysOperateLogTypeEnum 枚举类")
@NotNull(message = "操作分类不能为空")
private Integer type;
@ApiModelProperty(value = "操作明细", example = "修改编号为 1 的用户信息,将性别从男改成女,将姓名从芋道改成源码。")
private String content;
@ApiModelProperty(value = "拓展字段", example = "{'orderId': 1}")
private Map<String, Object> exts;
@ApiModelProperty(value = "请求方法名", required = true, example = "GET")
@NotEmpty(message = "请求方法名不能为空")
private String requestMethod;

View File

@ -0,0 +1,39 @@
package cn.iocoder.dashboard.modules.system.controller.logger.vo;
import cn.iocoder.dashboard.common.pojo.PageParam;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.springframework.format.annotation.DateTimeFormat;
import java.util.Date;
import static cn.iocoder.dashboard.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
@ApiModel("操作日志分页列表 Request VO")
@Data
@EqualsAndHashCode(callSuper = true)
public class SysOperateLogPageReqVO extends PageParam {
@ApiModelProperty(value = "操作模块", example = "订单", notes = "模拟匹配")
private String module;
@ApiModelProperty(value = "用户昵称", example = "芋道", notes = "模拟匹配")
private String userNickname;
@ApiModelProperty(value = "操作分类", example = "1", notes = "参见 SysOperateLogTypeEnum 枚举类")
private Integer type;
@ApiModelProperty(value = "操作状态", example = "true")
private Boolean success;
@ApiModelProperty(value = "开始时间", example = "2020-10-24")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
private Date beginTime;
@ApiModelProperty(value = "结束时间", example = "2020-10-24")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
private Date endTime;
}

View File

@ -0,0 +1,21 @@
package cn.iocoder.dashboard.modules.system.controller.logger.vo;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
@ApiModel("操作日志 Response VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class SysOperateLogRespVO extends SysOperateLogBaseVO {
@ApiModelProperty(value = "日志编号", required = true, example = "1024")
private Long id;
@ApiModelProperty(value = "用户昵称", required = true, example = "芋艿")
private String userNickname;
}

View File

@ -1,6 +1,8 @@
package cn.iocoder.dashboard.modules.system.convert.logger;
import cn.iocoder.dashboard.common.pojo.PageResult;
import cn.iocoder.dashboard.modules.system.controller.logger.vo.SysOperateLogCreateReqVO;
import cn.iocoder.dashboard.modules.system.controller.logger.vo.SysOperateLogRespVO;
import cn.iocoder.dashboard.modules.system.dal.mysql.dataobject.logger.SysOperateLogDO;
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;
@ -12,4 +14,8 @@ public interface SysOperateLogConvert {
SysOperateLogDO convert(SysOperateLogCreateReqVO bean);
PageResult<SysOperateLogRespVO> convertPage(PageResult<SysOperateLogDO> page);
SysOperateLogRespVO convert(SysOperateLogDO bean);
}

View File

@ -1,9 +1,30 @@
package cn.iocoder.dashboard.modules.system.dal.mysql.dao.logger;
import cn.iocoder.dashboard.common.exception.enums.GlobalErrorCodeConstants;
import cn.iocoder.dashboard.common.pojo.PageResult;
import cn.iocoder.dashboard.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.dashboard.framework.mybatis.core.query.QueryWrapperX;
import cn.iocoder.dashboard.modules.system.controller.logger.vo.SysOperateLogPageReqVO;
import cn.iocoder.dashboard.modules.system.dal.mysql.dataobject.logger.SysOperateLogDO;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
import java.util.Collection;
@Mapper
public interface SysOperateLogMapper extends BaseMapper<SysOperateLogDO> {
public interface SysOperateLogMapper extends BaseMapperX<SysOperateLogDO> {
default PageResult<SysOperateLogDO> selectPage(SysOperateLogPageReqVO reqVO, Collection<Long> userIds) {
QueryWrapperX<SysOperateLogDO> query = new QueryWrapperX<SysOperateLogDO>()
.likeIfPresent("module", reqVO.getModule())
.inIfPresent("user_id", userIds)
.eqIfPresent("operate_type", reqVO.getType())
.betweenIfPresent("start_time", reqVO.getBeginTime(), reqVO.getEndTime());
if (Boolean.TRUE.equals(reqVO.getSuccess())) {
query.eq("result_code", GlobalErrorCodeConstants.SUCCESS.getCode());
} else if (Boolean.FALSE.equals(reqVO.getSuccess())) {
query.gt("result_code", GlobalErrorCodeConstants.SUCCESS.getCode());
}
return selectPage(reqVO, query);
}
}

View File

@ -45,5 +45,9 @@ public interface SysUserMapper extends BaseMapper<SysUserDO> {
.inIfPresent("dept_id", deptIds));
}
default List<SysUserDO> selectListByNickname(String nickname) {
return selectList(new QueryWrapperX<SysUserDO>().like("nickname", nickname));
}
}

View File

@ -3,7 +3,7 @@ package cn.iocoder.dashboard.modules.system.dal.mysql.dataobject.logger;
import cn.iocoder.dashboard.common.pojo.CommonResult;
import cn.iocoder.dashboard.framework.mybatis.core.dataobject.BaseDO;
import cn.iocoder.dashboard.modules.system.dal.mysql.dataobject.user.SysUserDO;
import cn.iocoder.dashboard.modules.system.enums.logger.SysOperateLogTypeEnum;
import cn.iocoder.dashboard.framework.logger.operatelog.core.enums.OperateLogTypeEnum;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
@ -62,22 +62,18 @@ public class SysOperateLogDO extends BaseDO {
/**
* 操作分类
*
* 枚举 {@link SysOperateLogTypeEnum}
* 枚举 {@link OperateLogTypeEnum}
*/
@TableField("operate_type")
private Integer type;
/**
* 操作内容记录整个操作的明细
* 例如说修改编号为 1 的用户信息将性别从男改成女将姓名从芋道改成源码
*
* TODO 预留字段
*/
private String content;
/**
* 拓展字段有些复杂的业务需要记录一些字段
* 例如说记录订单编号则可以添加 key "orderId"value 为订单编号
*
* TODO 预留字段
*/
@TableField(typeHandler = FastjsonTypeHandler.class)
private Map<String, Object> exts;

View File

@ -1,9 +1,21 @@
package cn.iocoder.dashboard.modules.system.service.logger;
import cn.iocoder.dashboard.common.pojo.PageResult;
import cn.iocoder.dashboard.framework.logger.operatelog.core.service.OperateLogFrameworkService;
import cn.iocoder.dashboard.modules.system.controller.logger.vo.SysOperateLogPageReqVO;
import cn.iocoder.dashboard.modules.system.dal.mysql.dataobject.logger.SysOperateLogDO;
/**
* 操作日志 Service 接口
*/
public interface SysOperateLogService extends OperateLogFrameworkService {
/**
* 获得操作日志分页列表
*
* @param reqVO 分页条件
* @return 操作日志分页列表
*/
PageResult<SysOperateLogDO> pageOperateLog(SysOperateLogPageReqVO reqVO);
}

View File

@ -1,18 +1,27 @@
package cn.iocoder.dashboard.modules.system.service.logger.impl;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.dashboard.common.pojo.PageResult;
import cn.iocoder.dashboard.modules.system.controller.logger.vo.SysOperateLogCreateReqVO;
import cn.iocoder.dashboard.modules.system.controller.logger.vo.SysOperateLogPageReqVO;
import cn.iocoder.dashboard.modules.system.convert.logger.SysOperateLogConvert;
import cn.iocoder.dashboard.modules.system.dal.mysql.dao.logger.SysOperateLogMapper;
import cn.iocoder.dashboard.modules.system.dal.mysql.dataobject.logger.SysOperateLogDO;
import cn.iocoder.dashboard.modules.system.dal.mysql.dataobject.user.SysUserDO;
import cn.iocoder.dashboard.modules.system.service.logger.SysOperateLogService;
import cn.iocoder.dashboard.modules.system.service.user.SysUserService;
import cn.iocoder.dashboard.util.string.StrUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.Collection;
import static cn.iocoder.dashboard.modules.system.dal.mysql.dataobject.logger.SysOperateLogDO.*;
import static cn.iocoder.dashboard.modules.system.dal.mysql.dataobject.logger.SysOperateLogDO.JAVA_METHOD_ARGS_MAX_LENGTH;
import static cn.iocoder.dashboard.modules.system.dal.mysql.dataobject.logger.SysOperateLogDO.RESULT_MAX_LENGTH;
import static cn.iocoder.dashboard.util.collection.CollectionUtils.convertSet;
@Service
@Slf4j
@ -21,6 +30,9 @@ public class SysOperateLogServiceImpl implements SysOperateLogService {
@Resource
private SysOperateLogMapper operateLogMapper;
@Resource
private SysUserService userService;
@Override
@Async
public void createOperateLogAsync(SysOperateLogCreateReqVO reqVO) {
@ -35,4 +47,18 @@ public class SysOperateLogServiceImpl implements SysOperateLogService {
}
}
@Override
public PageResult<SysOperateLogDO> pageOperateLog(SysOperateLogPageReqVO reqVO) {
// 处理基于用户昵称的查询
Collection<Long> userIds = null;
if (StrUtil.isNotEmpty(reqVO.getUserNickname())) {
userIds = convertSet(userService.listUsersByNickname(reqVO.getUserNickname()), SysUserDO::getId);
if (CollUtil.isEmpty(userIds)) {
return PageResult.empty();
}
}
// 查询分页
return operateLogMapper.selectPage(reqVO, userIds);
}
}

View File

@ -1,10 +1,15 @@
package cn.iocoder.dashboard.modules.system.service.user;
import cn.hutool.core.collection.CollUtil;
import cn.iocoder.dashboard.common.pojo.PageResult;
import cn.iocoder.dashboard.modules.system.controller.user.vo.user.*;
import cn.iocoder.dashboard.modules.system.dal.mysql.dataobject.user.SysUserDO;
import cn.iocoder.dashboard.util.collection.CollectionUtils;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 用户 Service 接口
@ -45,6 +50,35 @@ public interface SysUserService {
*/
List<SysUserDO> listUsers(SysUserExportReqVO reqVO);
/**
* 获得用户列表
*
* @param ids 用户编号数组
* @return 用户列表
*/
List<SysUserDO> listUsers(Collection<Long> ids);
/**
* 获得用户 Map
*
* @param ids 用户编号数组
* @return 用户 Map
*/
default Map<Long, SysUserDO> getUserMap(Collection<Long> ids) {
if (CollUtil.isEmpty(ids)) {
return new HashMap<>();
}
return CollectionUtils.convertMap(listUsers(ids), SysUserDO::getId);
}
/**
* 获得用户列表基于昵称模糊匹配
*
* @param nickname 昵称
* @return 用户列表
*/
List<SysUserDO> listUsersByNickname(String nickname);
/**
* 创建用户
*

View File

@ -83,6 +83,16 @@ public class SysUserServiceImpl implements SysUserService {
return userMapper.selectList(reqVO, this.getDeptCondition(reqVO.getDeptId()));
}
@Override
public List<SysUserDO> listUsers(Collection<Long> ids) {
return userMapper.selectBatchIds(ids);
}
@Override
public List<SysUserDO> listUsersByNickname(String nickname) {
return userMapper.selectListByNickname(nickname);
}
/**
* 获得部门条件查询指定部门的子部门编号们包括自身
*