添加操作关系信息查看

This commit is contained in:
2026-07-25 09:27:07 +08:00
parent 71290ccf9b
commit cbbeab0c7b
9 changed files with 385 additions and 1 deletions
@@ -10,8 +10,11 @@ import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import xtools.app.sys.model.dto.excel.SysOptLogExcel;
import xtools.app.sys.model.dto.req.SysOptLogAccountReq;
import xtools.app.sys.model.dto.req.SysOptLogPageReq;
import xtools.app.sys.model.dto.resp.SysOptLogAccountResp;
import xtools.app.sys.model.dto.resp.SysOptLogResp;
import xtools.app.sys.model.dto.resp.SysOptLogTitleTreeResp;
import xtools.app.sys.service.SysOptLogService;
import xtools.boot.api.exection.BizError;
import xtools.boot.api.model.dto.Result;
@@ -61,4 +64,28 @@ public class SysOptLogController {
HttpServletUtils.addDownloadHeader(response, filename);
}
/**
* 获取操作账号列表
*
* @param req 请求参数(时间范围)
* @return 操作账号列表
*/
@Operation(summary = "获取操作账号列表")
@PostMapping("get-account-list")
public Result<List<SysOptLogAccountResp>> getAccountList(@RequestBody @Valid SysOptLogAccountReq req) {
return sysOptLogService.getAccountList(req);
}
/**
* 获取操作统计树
*
* @param req 请求参数(账号ID,时间范围)
* @return 操作统计树
*/
@Operation(summary = "获取操作统计树")
@PostMapping("get-opt-tree")
public Result<SysOptLogTitleTreeResp> getOptTree(@RequestBody @Valid SysOptLogAccountReq req) {
return sysOptLogService.getOptTree(req);
}
}
@@ -2,8 +2,14 @@ package xtools.app.sys.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import xtools.app.sys.model.dto.resp.SysOptLogAccountResp;
import xtools.app.sys.model.dto.resp.SysOptLogTitleCountResp;
import xtools.app.sys.model.entity.SysOptLog;
import java.time.Instant;
import java.util.List;
/**
* <p>Title : SysOptLogMapper</p>
* <p>Description : 系统操作日志 Mapper 接口</p>
@@ -16,4 +22,30 @@ import xtools.app.sys.model.entity.SysOptLog;
@Mapper
public interface SysOptLogMapper extends BaseMapper<SysOptLog> {
/**
* 获取操作账号列表
*
* @param startTime 开始时间(包含)
* @param endTime 结束时间(包含)
* @return 操作账号列表
*/
List<SysOptLogAccountResp> getOptAccountList(
@Param("startTime") Instant startTime,
@Param("endTime") Instant endTime
);
/**
* 获取该账号的操作日志
*
* @param accountId 账号ID
* @param startTime 开始时间(包含)
* @param endTime 结束时间(包含)
* @return 操作日志
*/
List<SysOptLogTitleCountResp> getOptCount(
@Param("accountId") Long accountId,
@Param("startTime") Instant startTime,
@Param("endTime") Instant endTime
);
}
@@ -0,0 +1,39 @@
package xtools.app.sys.model.dto.req;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
import java.time.Instant;
/**
* <p>Title : SysOptLogAccountReq</p>
* <p>Description : 系统操作日志去重账号信息请求对象</p>
* <p>Company : org.xujun</p>
*
* @author : xujun
* @version : 1.0.0
* @date : 2026-07-25 10:00:00
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class SysOptLogAccountReq implements Serializable {
/**
* 账号ID
*/
@Schema(description = "账号ID")
private Long accountId;
/**
* 创建时间范围[开始时间(包含),结束时间(包含)],不能为空且需同时包含开始,结束时间
*/
@NotNull(message = "时间范围不能为空")
@Schema(description = "创建时间范围(包含两端)", example = "['2026-07-17 00:00:00', '2026-07-24 23:59:59']")
private Instant[] gmtCreateRange;
}
@@ -0,0 +1,38 @@
package xtools.app.sys.model.dto.resp;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
/**
* <p>Title : SysOptLogAccountResp</p>
* <p>Description : 系统操作日志去重账号信息响应对象</p>
* <p>Company : org.xujun</p>
*
* @author : xujun
* @version : 1.0.0
* @date : 2026-07-25 10:00:00
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class SysOptLogAccountResp implements Serializable {
/**
* 账号ID
*/
@Schema(description = "账号ID")
private Long accountId;
/**
* 账号
*/
@Schema(description = "账号")
private String account;
}
@@ -0,0 +1,38 @@
package xtools.app.sys.model.dto.resp;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
/**
* <p>Title : SysOptLogTitleCountResp</p>
* <p>Description : 系统操作日志标题分组统计响应对象</p>
* <p>Company : org.xujun</p>
*
* @author : xujun
* @version : 1.0.0
* @date : 2026-07-25 10:00:00
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class SysOptLogTitleCountResp implements Serializable {
/**
* 标题
*/
@Schema(description = "标题")
private String title;
/**
* 操作次数
*/
@Schema(description = "操作次数")
private Long count;
}
@@ -0,0 +1,45 @@
package xtools.app.sys.model.dto.resp;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
import java.util.List;
/**
* <p>Title : SysOptLogTitleTreeResp</p>
* <p>Description : 系统操作日志标题统计树响应对象</p>
* <p>Company : org.xujun</p>
*
* @author : xujun
* @version : 1.0.0
* @date : 2026-07-25 10:00:00
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class SysOptLogTitleTreeResp implements Serializable {
/**
* 标题
*/
@Schema(description = "标题")
private String title;
/**
* 操作次数
*/
@Schema(description = "操作次数")
private Long count;
/**
* 子节点
*/
@Schema(description = "子节点")
private List<SysOptLogTitleTreeResp> children;
}
@@ -2,8 +2,11 @@ package xtools.app.sys.service;
import com.alibaba.fastjson2.JSONObject;
import xtools.app.sys.model.dto.excel.SysOptLogExcel;
import xtools.app.sys.model.dto.req.SysOptLogAccountReq;
import xtools.app.sys.model.dto.req.SysOptLogPageReq;
import xtools.app.sys.model.dto.resp.SysOptLogAccountResp;
import xtools.app.sys.model.dto.resp.SysOptLogResp;
import xtools.app.sys.model.dto.resp.SysOptLogTitleTreeResp;
import xtools.boot.api.model.dto.Result;
import xtools.boot.api.model.dto.page.PageReq;
import xtools.boot.api.model.dto.page.PageResp;
@@ -45,4 +48,20 @@ public interface SysOptLogService {
*/
List<SysOptLogExcel> exportExcel(SysOptLogPageReq req);
/**
* 获取操作账号列表
*
* @param req 请求参数(时间范围)
* @return 操作账号列表
*/
Result<List<SysOptLogAccountResp>> getAccountList(SysOptLogAccountReq req);
/**
* 获取操作统计树
*
* @param req 请求参数(账号ID,时间范围)
* @return 操作统计树
*/
Result<SysOptLogTitleTreeResp> getOptTree(SysOptLogAccountReq req);
}
@@ -11,11 +11,17 @@ import xtools.app.sys.auth.model.dto.OptLogDto;
import xtools.app.sys.config.SysConfig;
import xtools.app.sys.convert.SysOptLogConvert;
import xtools.app.sys.model.dto.excel.SysOptLogExcel;
import xtools.app.sys.model.dto.req.SysOptLogAccountReq;
import xtools.app.sys.model.dto.req.SysOptLogPageReq;
import xtools.app.sys.model.dto.resp.SysOptLogAccountResp;
import xtools.app.sys.model.dto.resp.SysOptLogResp;
import xtools.app.sys.model.dto.resp.SysOptLogTitleCountResp;
import xtools.app.sys.model.dto.resp.SysOptLogTitleTreeResp;
import xtools.app.sys.model.entity.SysOptLog;
import xtools.app.sys.service.SysOptLogService;
import xtools.app.sys.service.base.SysOptLogBaseService;
import xtools.base.config.BaseParams;
import xtools.boot.api.exection.BizWarning;
import xtools.boot.api.model.dto.Result;
import xtools.boot.api.model.dto.page.PageReq;
import xtools.boot.api.model.dto.page.PageResp;
@@ -25,6 +31,9 @@ import xtools.boot.ip.utils.IpUtils;
import xtools.core.StringUtils;
import xtools.extend.dto.IpAddrDto;
import java.time.Instant;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
@@ -44,7 +53,7 @@ import java.util.stream.Collectors;
@Primary
@Service
@RequiredArgsConstructor
public class SysOptLogServiceImpl implements SysOptLogService {
public class SysOptLogServiceImpl implements SysOptLogService, BaseParams {
private final SysConfig sysConfig;
@@ -193,4 +202,117 @@ public class SysOptLogServiceImpl implements SysOptLogService {
QueryUtils.addTimeRange(query, req.getGmtCreateRange(), SysOptLog::getGmtCreate);
}
/**
* 获取操作账号列表
*
* @param req 请求参数(时间范围)
* @return 操作账号列表
*/
@Override
public Result<List<SysOptLogAccountResp>> getAccountList(SysOptLogAccountReq req) {
Instant[] range = req.getGmtCreateRange();
if (Objects.isNull(range) || range.length != 2) {
throw new BizWarning("时间范围不能为空且需要包含开始和结束时间");
}
Instant startTime = range[0];
Instant endTime = range[1];
if (startTime.isAfter(endTime)) {
throw new BizWarning("开始时间不能大于结束时间");
}
// SQL 查询时间范围内去重账号信息
List<SysOptLogAccountResp> list = sysOptLogBaseService.getBaseMapper().getOptAccountList(startTime, endTime);
return Result.ok(list);
}
/**
* 获取操作统计树
*
* @param req 请求参数(账号ID,时间范围)
* @return 操作统计树
*/
@Override
public Result<SysOptLogTitleTreeResp> getOptTree(SysOptLogAccountReq req) {
Instant[] range = req.getGmtCreateRange();
if (Objects.isNull(range) || range.length != CP_NUM2) {
throw new BizWarning("时间范围不能为空且需要包含开始和结束时间");
}
Instant startTime = range[CP_NUM0];
Instant endTime = range[CP_NUM1];
if (startTime.isAfter(endTime)) {
throw new BizWarning("开始时间不能大于结束时间");
}
Long accountId = req.getAccountId();
if (Objects.isNull(accountId)) {
throw new BizWarning("账号ID不能为空");
}
// 获取该账号的操作日志
List<SysOptLogTitleCountResp> list = sysOptLogBaseService.getBaseMapper().getOptCount(accountId, startTime, endTime);
SysOptLogTitleTreeResp root = buildOptTree(list);
root.setTitle(getAccount(accountId));
return Result.ok(root);
}
/**
* 查询账号名称
*
* @param accountId 账号ID
* @return 账号名称
*/
private String getAccount(Long accountId) {
LambdaQueryWrapper<SysOptLog> query = new LambdaQueryWrapper<>();
query.select(SysOptLog::getAccount);
query.eq(SysOptLog::getAccountId, accountId);
query.last("LIMIT 1");
SysOptLog data = sysOptLogBaseService.getOne(query);
return Objects.isNull(data) ? null : data.getAccount();
}
/**
* 构建操作统计树
*
* @param list 操作统计列表
* @return 操作统计树
*/
private SysOptLogTitleTreeResp buildOptTree(List<SysOptLogTitleCountResp> list) {
// 二层树节点Map
Map<String, SysOptLogTitleTreeResp> groupMap = new HashMap<>(CP_NUM16);
long total = 0L;
for (SysOptLogTitleCountResp item : list) {
String title = item.getTitle();
Long count = item.getCount();
// 获取模块名和方法名
String module;
String method;
int idx = StringUtils.isBlank(title) ? CP_NEGATIVE1 : title.indexOf(CP_LINE);
if (idx > CP_NUM0) {
module = title.substring(CP_NUM0, idx);
method = title.substring(idx + CP_NUM1);
} else {
module = title;
method = title;
}
// 构建二层树节点
SysOptLogTitleTreeResp parent = groupMap.computeIfAbsent(module, key -> SysOptLogTitleTreeResp.builder()
.title(key)
.count(0L)
.children(new ArrayList<>())
.build());
// 构建三层树节点
SysOptLogTitleTreeResp child = SysOptLogTitleTreeResp.builder()
.title(method)
.count(count)
.build();
parent.getChildren().add(child);
// 累加二层树统计
parent.setCount(parent.getCount() + count);
// 累加根节点树统计
total += count;
}
// 构造根节点
return SysOptLogTitleTreeResp.builder()
.count(total)
.children(new ArrayList<>(groupMap.values()))
.build();
}
}
@@ -0,0 +1,24 @@
<?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="xtools.app.sys.mapper.SysOptLogMapper">
<!-- 获取操作账号列表 -->
<select id="getOptAccountList" resultType="xtools.app.sys.model.dto.resp.SysOptLogAccountResp">
SELECT account_id AS accountId,
MAX(account) AS account
FROM sys_opt_log
WHERE gmt_create BETWEEN #{startTime} AND #{endTime}
GROUP BY account_id
</select>
<!-- 获取该账号的操作日志 -->
<select id="getOptCount" resultType="xtools.app.sys.model.dto.resp.SysOptLogTitleCountResp">
SELECT title,
COUNT(*) AS `count`
FROM sys_opt_log
WHERE account_id = #{accountId}
AND gmt_create BETWEEN #{startTime} AND #{endTime}
GROUP BY title
</select>
</mapper>