diff --git a/xtools-app-sys/xtools-app-sys-biz/src/main/java/xtools/app/sys/controller/SysOptLogController.java b/xtools-app-sys/xtools-app-sys-biz/src/main/java/xtools/app/sys/controller/SysOptLogController.java index 1b0735e..aa2129d 100644 --- a/xtools-app-sys/xtools-app-sys-biz/src/main/java/xtools/app/sys/controller/SysOptLogController.java +++ b/xtools-app-sys/xtools-app-sys-biz/src/main/java/xtools/app/sys/controller/SysOptLogController.java @@ -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> getAccountList(@RequestBody @Valid SysOptLogAccountReq req) { + return sysOptLogService.getAccountList(req); + } + + /** + * 获取操作统计树 + * + * @param req 请求参数(账号ID,时间范围) + * @return 操作统计树 + */ + @Operation(summary = "获取操作统计树") + @PostMapping("get-opt-tree") + public Result getOptTree(@RequestBody @Valid SysOptLogAccountReq req) { + return sysOptLogService.getOptTree(req); + } + } diff --git a/xtools-app-sys/xtools-app-sys-biz/src/main/java/xtools/app/sys/mapper/SysOptLogMapper.java b/xtools-app-sys/xtools-app-sys-biz/src/main/java/xtools/app/sys/mapper/SysOptLogMapper.java index 898d811..443eca2 100644 --- a/xtools-app-sys/xtools-app-sys-biz/src/main/java/xtools/app/sys/mapper/SysOptLogMapper.java +++ b/xtools-app-sys/xtools-app-sys-biz/src/main/java/xtools/app/sys/mapper/SysOptLogMapper.java @@ -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; + /** *

Title : SysOptLogMapper

*

Description : 系统操作日志 Mapper 接口

@@ -16,4 +22,30 @@ import xtools.app.sys.model.entity.SysOptLog; @Mapper public interface SysOptLogMapper extends BaseMapper { + /** + * 获取操作账号列表 + * + * @param startTime 开始时间(包含) + * @param endTime 结束时间(包含) + * @return 操作账号列表 + */ + List getOptAccountList( + @Param("startTime") Instant startTime, + @Param("endTime") Instant endTime + ); + + /** + * 获取该账号的操作日志 + * + * @param accountId 账号ID + * @param startTime 开始时间(包含) + * @param endTime 结束时间(包含) + * @return 操作日志 + */ + List getOptCount( + @Param("accountId") Long accountId, + @Param("startTime") Instant startTime, + @Param("endTime") Instant endTime + ); + } diff --git a/xtools-app-sys/xtools-app-sys-biz/src/main/java/xtools/app/sys/model/dto/req/SysOptLogAccountReq.java b/xtools-app-sys/xtools-app-sys-biz/src/main/java/xtools/app/sys/model/dto/req/SysOptLogAccountReq.java new file mode 100644 index 0000000..8d3aae3 --- /dev/null +++ b/xtools-app-sys/xtools-app-sys-biz/src/main/java/xtools/app/sys/model/dto/req/SysOptLogAccountReq.java @@ -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; + +/** + *

Title : SysOptLogAccountReq

+ *

Description : 系统操作日志去重账号信息请求对象

+ *

Company : org.xujun

+ * + * @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; + +} diff --git a/xtools-app-sys/xtools-app-sys-biz/src/main/java/xtools/app/sys/model/dto/resp/SysOptLogAccountResp.java b/xtools-app-sys/xtools-app-sys-biz/src/main/java/xtools/app/sys/model/dto/resp/SysOptLogAccountResp.java new file mode 100644 index 0000000..aff7898 --- /dev/null +++ b/xtools-app-sys/xtools-app-sys-biz/src/main/java/xtools/app/sys/model/dto/resp/SysOptLogAccountResp.java @@ -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; + +/** + *

Title : SysOptLogAccountResp

+ *

Description : 系统操作日志去重账号信息响应对象

+ *

Company : org.xujun

+ * + * @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; + +} diff --git a/xtools-app-sys/xtools-app-sys-biz/src/main/java/xtools/app/sys/model/dto/resp/SysOptLogTitleCountResp.java b/xtools-app-sys/xtools-app-sys-biz/src/main/java/xtools/app/sys/model/dto/resp/SysOptLogTitleCountResp.java new file mode 100644 index 0000000..3d65ded --- /dev/null +++ b/xtools-app-sys/xtools-app-sys-biz/src/main/java/xtools/app/sys/model/dto/resp/SysOptLogTitleCountResp.java @@ -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; + +/** + *

Title : SysOptLogTitleCountResp

+ *

Description : 系统操作日志标题分组统计响应对象

+ *

Company : org.xujun

+ * + * @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; + +} diff --git a/xtools-app-sys/xtools-app-sys-biz/src/main/java/xtools/app/sys/model/dto/resp/SysOptLogTitleTreeResp.java b/xtools-app-sys/xtools-app-sys-biz/src/main/java/xtools/app/sys/model/dto/resp/SysOptLogTitleTreeResp.java new file mode 100644 index 0000000..66dbf5c --- /dev/null +++ b/xtools-app-sys/xtools-app-sys-biz/src/main/java/xtools/app/sys/model/dto/resp/SysOptLogTitleTreeResp.java @@ -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; + +/** + *

Title : SysOptLogTitleTreeResp

+ *

Description : 系统操作日志标题统计树响应对象

+ *

Company : org.xujun

+ * + * @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 children; + +} diff --git a/xtools-app-sys/xtools-app-sys-biz/src/main/java/xtools/app/sys/service/SysOptLogService.java b/xtools-app-sys/xtools-app-sys-biz/src/main/java/xtools/app/sys/service/SysOptLogService.java index 95c3e0e..d486809 100644 --- a/xtools-app-sys/xtools-app-sys-biz/src/main/java/xtools/app/sys/service/SysOptLogService.java +++ b/xtools-app-sys/xtools-app-sys-biz/src/main/java/xtools/app/sys/service/SysOptLogService.java @@ -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 exportExcel(SysOptLogPageReq req); + /** + * 获取操作账号列表 + * + * @param req 请求参数(时间范围) + * @return 操作账号列表 + */ + Result> getAccountList(SysOptLogAccountReq req); + + /** + * 获取操作统计树 + * + * @param req 请求参数(账号ID,时间范围) + * @return 操作统计树 + */ + Result getOptTree(SysOptLogAccountReq req); + } diff --git a/xtools-app-sys/xtools-app-sys-biz/src/main/java/xtools/app/sys/service/impl/SysOptLogServiceImpl.java b/xtools-app-sys/xtools-app-sys-biz/src/main/java/xtools/app/sys/service/impl/SysOptLogServiceImpl.java index d6252cc..83f97ec 100644 --- a/xtools-app-sys/xtools-app-sys-biz/src/main/java/xtools/app/sys/service/impl/SysOptLogServiceImpl.java +++ b/xtools-app-sys/xtools-app-sys-biz/src/main/java/xtools/app/sys/service/impl/SysOptLogServiceImpl.java @@ -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> 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 list = sysOptLogBaseService.getBaseMapper().getOptAccountList(startTime, endTime); + return Result.ok(list); + } + + /** + * 获取操作统计树 + * + * @param req 请求参数(账号ID,时间范围) + * @return 操作统计树 + */ + @Override + public Result 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 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 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 list) { + // 二层树节点Map + Map 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(); + } + } diff --git a/xtools-app-sys/xtools-app-sys-biz/src/main/resources/mapper/SysOptLogMapper.xml b/xtools-app-sys/xtools-app-sys-biz/src/main/resources/mapper/SysOptLogMapper.xml new file mode 100644 index 0000000..9761e6c --- /dev/null +++ b/xtools-app-sys/xtools-app-sys-biz/src/main/resources/mapper/SysOptLogMapper.xml @@ -0,0 +1,24 @@ + + + + + + + + + + +