feat: 系统日志新增导出 API

This commit is contained in:
Charles7c 2024-04-09 21:33:52 +08:00
parent 7793f82009
commit bd0f40c6ad
9 changed files with 398 additions and 44 deletions

View File

@ -22,6 +22,7 @@ import com.baomidou.mybatisplus.core.toolkit.Constants;
import org.apache.ibatis.annotations.Param;
import top.charles7c.continew.admin.system.model.entity.LogDO;
import top.charles7c.continew.admin.system.model.resp.*;
import top.charles7c.continew.admin.system.model.resp.log.LogResp;
import top.charles7c.continew.starter.data.mybatis.plus.base.BaseMapper;
import java.util.List;
@ -45,6 +46,14 @@ public interface LogMapper extends BaseMapper<LogDO> {
IPage<LogResp> selectLogPage(@Param("page") IPage<LogDO> page,
@Param(Constants.WRAPPER) QueryWrapper<LogDO> queryWrapper);
/**
* 查询列表
*
* @param queryWrapper 查询条件
* @return 列表信息
*/
List<LogResp> selectLogList(@Param(Constants.WRAPPER) QueryWrapper<LogDO> queryWrapper);
/**
* 查询仪表盘总计信息
*

View File

@ -14,7 +14,7 @@
* limitations under the License.
*/
package top.charles7c.continew.admin.system.model.resp;
package top.charles7c.continew.admin.system.model.resp.log;
import cn.crane4j.annotation.Assemble;
import cn.crane4j.annotation.Mapping;

View File

@ -14,7 +14,7 @@
* limitations under the License.
*/
package top.charles7c.continew.admin.system.model.resp;
package top.charles7c.continew.admin.system.model.resp.log;
import com.fasterxml.jackson.annotation.JsonIgnore;
import io.swagger.v3.oas.annotations.media.Schema;

View File

@ -0,0 +1,106 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.charles7c.continew.admin.system.model.resp.log;
import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
import com.alibaba.excel.annotation.ExcelProperty;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import top.charles7c.continew.admin.system.enums.LogStatusEnum;
import top.charles7c.continew.starter.extension.crud.converter.ExcelBaseEnumConverter;
import java.io.Serial;
import java.io.Serializable;
import java.time.LocalDateTime;
/**
* 登录日志导出信息
*
* @author Charles7c
* @since 2023/1/14 18:27
*/
@Data
@ExcelIgnoreUnannotated
@Schema(description = "登录日志导出信息")
public class LoginLogExportResp implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* ID
*/
@Schema(description = "ID", example = "1")
@ExcelProperty(value = "ID")
private Long id;
/**
* 登录时间
*/
@Schema(description = "登录时间", example = "2023-08-08 08:08:08", type = "string")
@ExcelProperty(value = "登录时间")
private LocalDateTime createTime;
/**
* 用户昵称
*/
@Schema(description = "用户昵称", example = "张三")
@ExcelProperty(value = "用户昵称")
private String createUserString;
/**
* 登录行为
*/
@Schema(description = "登录行为", example = "账号登录")
@ExcelProperty(value = "登录行为")
private String description;
/**
* 状态
*/
@Schema(description = "状态1成功2失败", type = "Integer", allowableValues = {"1", "2"}, example = "1")
@ExcelProperty(value = "状态", converter = ExcelBaseEnumConverter.class)
private LogStatusEnum status;
/**
* 登录 IP
*/
@Schema(description = "登录 IP", example = "192.168.0.1")
@ExcelProperty(value = "登录 IP")
private String ip;
/**
* 登录地点
*/
@Schema(description = "登录地点", example = "中国北京北京市")
@ExcelProperty(value = "登录地点")
private String address;
/**
* 浏览器
*/
@Schema(description = "浏览器", example = "Chrome 115.0.0.0")
@ExcelProperty(value = "浏览器")
private String browser;
/**
* 终端系统
*/
@Schema(description = "终端系统", example = "Windows 10")
@ExcelProperty(value = "终端系统")
private String os;
}

View File

@ -0,0 +1,120 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.charles7c.continew.admin.system.model.resp.log;
import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
import com.alibaba.excel.annotation.ExcelProperty;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import top.charles7c.continew.admin.system.enums.LogStatusEnum;
import top.charles7c.continew.starter.extension.crud.converter.ExcelBaseEnumConverter;
import java.io.Serial;
import java.io.Serializable;
import java.time.LocalDateTime;
/**
* 操作日志导出信息
*
* @author Charles7c
* @since 2023/1/14 18:27
*/
@Data
@ExcelIgnoreUnannotated
@Schema(description = "操作日志导出信息")
public class OperationLogExportResp implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* ID
*/
@Schema(description = "ID", example = "1")
@ExcelProperty(value = "ID")
private Long id;
/**
* 操作时间
*/
@Schema(description = "操作时间", example = "2023-08-08 08:08:08", type = "string")
@ExcelProperty(value = "操作时间")
private LocalDateTime createTime;
/**
* 操作人
*/
@Schema(description = "操作人", example = "张三")
@ExcelProperty(value = "操作人")
private String createUserString;
/**
* 操作内容
*/
@Schema(description = "操作内容", example = "账号登录")
@ExcelProperty(value = "操作内容")
private String description;
/**
* 所属模块
*/
@Schema(description = "所属模块", example = "部门管理")
@ExcelProperty(value = "所属模块")
private String module;
/**
* 状态
*/
@Schema(description = "状态1成功2失败", type = "Integer", allowableValues = {"1", "2"}, example = "1")
@ExcelProperty(value = "状态", converter = ExcelBaseEnumConverter.class)
private LogStatusEnum status;
/**
* 操作 IP
*/
@Schema(description = "操作 IP", example = "192.168.0.1")
@ExcelProperty(value = "操作 IP")
private String ip;
/**
* 操作地点
*/
@Schema(description = "操作地点", example = "中国北京北京市")
@ExcelProperty(value = "操作地点")
private String address;
/**
* 耗时ms
*/
@Schema(description = "耗时ms", example = "58")
@ExcelProperty(value = "耗时ms")
private Long timeTaken;
/**
* 浏览器
*/
@Schema(description = "浏览器", example = "Chrome 115.0.0.0")
@ExcelProperty(value = "浏览器")
private String browser;
/**
* 终端系统
*/
@Schema(description = "终端系统", example = "Windows 10")
@ExcelProperty(value = "终端系统")
private String os;
}

View File

@ -16,9 +16,13 @@
package top.charles7c.continew.admin.system.service;
import jakarta.servlet.http.HttpServletResponse;
import top.charles7c.continew.admin.system.model.query.LogQuery;
import top.charles7c.continew.admin.system.model.resp.*;
import top.charles7c.continew.admin.system.model.resp.log.LogDetailResp;
import top.charles7c.continew.admin.system.model.resp.log.LogResp;
import top.charles7c.continew.starter.extension.crud.model.query.PageQuery;
import top.charles7c.continew.starter.extension.crud.model.query.SortQuery;
import top.charles7c.continew.starter.extension.crud.model.resp.PageResp;
import java.util.List;
@ -42,13 +46,31 @@ public interface LogService {
PageResp<LogResp> page(LogQuery query, PageQuery pageQuery);
/**
* 详情
* 详情
*
* @param id ID
* @return 详情信息
*/
LogDetailResp get(Long id);
/**
* 导出登录日志
*
* @param query 查询条件
* @param sortQuery 排序查询条件
* @param response 响应对象
*/
void exportLoginLog(LogQuery query, SortQuery sortQuery, HttpServletResponse response);
/**
* 导出操作日志
*
* @param query 查询条件
* @param sortQuery 排序查询条件
* @param response 响应对象
*/
void exportOperationLog(LogQuery query, SortQuery sortQuery, HttpServletResponse response);
/**
* 查询仪表盘总计信息
*

View File

@ -19,21 +19,31 @@ package top.charles7c.continew.admin.system.service.impl;
import cn.crane4j.annotation.AutoOperate;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.lang.Opt;
import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.domain.Sort;
import org.springframework.stereotype.Service;
import top.charles7c.continew.admin.system.mapper.LogMapper;
import top.charles7c.continew.admin.system.model.entity.LogDO;
import top.charles7c.continew.admin.system.model.query.LogQuery;
import top.charles7c.continew.admin.system.model.resp.*;
import top.charles7c.continew.admin.system.model.resp.log.LogDetailResp;
import top.charles7c.continew.admin.system.model.resp.log.LogResp;
import top.charles7c.continew.admin.system.model.resp.log.LoginLogExportResp;
import top.charles7c.continew.admin.system.model.resp.log.OperationLogExportResp;
import top.charles7c.continew.admin.system.service.LogService;
import top.charles7c.continew.starter.core.util.validate.CheckUtils;
import top.charles7c.continew.starter.core.util.validate.ValidationUtils;
import top.charles7c.continew.starter.extension.crud.model.query.PageQuery;
import top.charles7c.continew.starter.extension.crud.model.query.SortQuery;
import top.charles7c.continew.starter.extension.crud.model.resp.PageResp;
import top.charles7c.continew.starter.file.excel.util.ExcelUtils;
import java.util.Date;
import java.util.List;
@ -50,10 +60,92 @@ import java.util.Map;
@RequiredArgsConstructor
public class LogServiceImpl implements LogService {
private final LogMapper logMapper;
private final LogMapper baseMapper;
@Override
public PageResp<LogResp> page(LogQuery query, PageQuery pageQuery) {
QueryWrapper<LogDO> queryWrapper = this.handleQueryWrapper(query);
IPage<LogResp> page = baseMapper.selectLogPage(pageQuery.toPage(), queryWrapper);
return PageResp.build(page);
}
@Override
@AutoOperate(type = LogDetailResp.class)
public LogDetailResp get(Long id) {
LogDO logDO = baseMapper.selectById(id);
CheckUtils.throwIfNotExists(logDO, "LogDO", "ID", id);
return BeanUtil.copyProperties(logDO, LogDetailResp.class);
}
@Override
public void exportLoginLog(LogQuery query, SortQuery sortQuery, HttpServletResponse response) {
List<LoginLogExportResp> list = BeanUtil.copyToList(this.list(query, sortQuery), LoginLogExportResp.class);
ExcelUtils.export(list, "导出登录日志数据", LoginLogExportResp.class, response);
}
@Override
public void exportOperationLog(LogQuery query, SortQuery sortQuery, HttpServletResponse response) {
List<OperationLogExportResp> list = BeanUtil.copyToList(this
.list(query, sortQuery), OperationLogExportResp.class);
ExcelUtils.export(list, "导出操作日志数据", OperationLogExportResp.class, response);
}
@Override
public DashboardTotalResp getDashboardTotal() {
return baseMapper.selectDashboardTotal();
}
@Override
public List<DashboardAccessTrendResp> listDashboardAccessTrend(Integer days) {
return baseMapper.selectListDashboardAccessTrend(days);
}
@Override
public List<DashboardPopularModuleResp> listDashboardPopularModule() {
return baseMapper.selectListDashboardPopularModule();
}
@Override
public List<Map<String, Object>> listDashboardGeoDistribution() {
return baseMapper.selectListDashboardGeoDistribution();
}
/**
* 查询列表
*
* @param query 查询条件
* @param sortQuery 排序查询条件
* @return 列表信息
*/
private List<LogResp> list(LogQuery query, SortQuery sortQuery) {
QueryWrapper<LogDO> queryWrapper = this.handleQueryWrapper(query);
this.sort(queryWrapper, sortQuery);
return baseMapper.selectLogList(queryWrapper);
}
/**
* 设置排序
*
* @param queryWrapper 查询条件封装对象
* @param sortQuery 排序查询条件
*/
private void sort(QueryWrapper<LogDO> queryWrapper, SortQuery sortQuery) {
Sort sort = Opt.ofNullable(sortQuery).orElseGet(SortQuery::new).getSort();
for (Sort.Order order : sort) {
if (null != order) {
String property = order.getProperty();
queryWrapper.orderBy(true, order.isAscending(), CharSequenceUtil.toUnderlineCase(property));
}
}
}
/**
* 处理查询条件
*
* @param query 查询条件
* @return QueryWrapper
*/
private QueryWrapper<LogDO> handleQueryWrapper(LogQuery query) {
QueryWrapper<LogDO> queryWrapper = new QueryWrapper<>();
// 构建条件
String description = query.getDescription();
@ -81,35 +173,6 @@ public class LogServiceImpl implements LogService {
if (null != status) {
queryWrapper.eq("t1.status", status);
}
IPage<LogResp> page = logMapper.selectLogPage(pageQuery.toPage(), queryWrapper);
return PageResp.build(page);
}
@Override
@AutoOperate(type = LogDetailResp.class)
public LogDetailResp get(Long id) {
LogDO logDO = logMapper.selectById(id);
CheckUtils.throwIfNotExists(logDO, "LogDO", "ID", id);
return BeanUtil.copyProperties(logDO, LogDetailResp.class);
}
@Override
public DashboardTotalResp getDashboardTotal() {
return logMapper.selectDashboardTotal();
}
@Override
public List<DashboardAccessTrendResp> listDashboardAccessTrend(Integer days) {
return logMapper.selectListDashboardAccessTrend(days);
}
@Override
public List<DashboardPopularModuleResp> listDashboardPopularModule() {
return logMapper.selectListDashboardPopularModule();
}
@Override
public List<Map<String, Object>> listDashboardGeoDistribution() {
return logMapper.selectListDashboardGeoDistribution();
return queryWrapper;
}
}

View File

@ -1,7 +1,27 @@
<?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="top.charles7c.continew.admin.system.mapper.LogMapper">
<select id="selectLogPage" resultType="top.charles7c.continew.admin.system.model.resp.LogResp">
<select id="selectLogPage" resultType="top.charles7c.continew.admin.system.model.resp.log.LogResp">
SELECT
t1.id,
t1.description,
t1.module,
t1.time_taken,
t1.ip,
t1.address,
t1.browser,
t1.os,
t1.status,
t1.error_msg,
t1.create_user,
t1.create_time,
t2.nickname AS createUserString
FROM sys_log AS t1
LEFT JOIN sys_user AS t2 ON t2.id = t1.create_user
${ew.customSqlSegment}
</select>
<select id="selectLogList" resultType="top.charles7c.continew.admin.system.model.resp.log.LogResp">
SELECT
t1.id,
t1.description,
@ -68,4 +88,4 @@
ORDER BY value DESC
LIMIT 10
</select>
</mapper>
</mapper>

View File

@ -20,6 +20,7 @@ import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.enums.ParameterIn;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
@ -27,37 +28,50 @@ import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import top.charles7c.continew.admin.system.model.query.LogQuery;
import top.charles7c.continew.admin.system.model.resp.LogDetailResp;
import top.charles7c.continew.admin.system.model.resp.LogResp;
import top.charles7c.continew.admin.system.model.resp.log.LogDetailResp;
import top.charles7c.continew.admin.system.model.resp.log.LogResp;
import top.charles7c.continew.admin.system.service.LogService;
import top.charles7c.continew.starter.extension.crud.model.query.PageQuery;
import top.charles7c.continew.starter.extension.crud.model.query.SortQuery;
import top.charles7c.continew.starter.extension.crud.model.resp.PageResp;
import top.charles7c.continew.starter.web.model.R;
/**
* 日志管理 API
* 系统日志 API
*
* @author Charles7c
* @since 2023/1/18 23:55
*/
@Tag(name = "日志管理 API")
@Tag(name = "系统日志 API")
@RestController
@RequiredArgsConstructor
@RequestMapping("/system/log")
public class LogController {
private final LogService logService;
private final LogService baseService;
@Operation(summary = "分页查询列表", description = "分页查询列表")
@GetMapping
public R<PageResp<LogResp>> page(LogQuery query, @Validated PageQuery pageQuery) {
return R.ok(logService.page(query, pageQuery));
return R.ok(baseService.page(query, pageQuery));
}
@Operation(summary = "详情", description = "详情")
@Operation(summary = "详情", description = "详情")
@Parameter(name = "id", description = "ID", example = "1", in = ParameterIn.PATH)
@GetMapping("/{id}")
public R<LogDetailResp> get(@PathVariable Long id) {
return R.ok(logService.get(id));
return R.ok(baseService.get(id));
}
@Operation(summary = "导出登录日志", description = "导出登录日志")
@GetMapping("/export/login")
public void exportLoginLog(LogQuery query, SortQuery sortQuery, HttpServletResponse response) {
baseService.exportLoginLog(query, sortQuery, response);
}
@Operation(summary = "导出操作日志", description = "导出操作日志")
@GetMapping("/export/operation")
public void exportOperationLog(LogQuery query, SortQuery sortQuery, HttpServletResponse response) {
baseService.exportOperationLog(query, sortQuery, response);
}
}