diff --git a/pom.xml b/pom.xml index a6a2eda..ca200b4 100644 --- a/pom.xml +++ b/pom.xml @@ -158,6 +158,11 @@ xtools-app-sys-scheduled ${project.version} + + org.xujun + xtools-app-sys-tag + ${project.version} + diff --git a/xtools-app-gen/xtools-app-gen-biz/pom.xml b/xtools-app-gen/xtools-app-gen-biz/pom.xml index 8f96c75..6ddd2ea 100644 --- a/xtools-app-gen/xtools-app-gen-biz/pom.xml +++ b/xtools-app-gen/xtools-app-gen-biz/pom.xml @@ -90,6 +90,10 @@ org.xujun xtools-app-sys-auth + + org.xujun + xtools-app-sys-tag + diff --git a/xtools-app-gen/xtools-app-gen-biz/src/main/resources/templates/gen/vue/index.vue.vm b/xtools-app-gen/xtools-app-gen-biz/src/main/resources/templates/gen/vue/index.vue.vm index 5325b30..ccc494d 100644 --- a/xtools-app-gen/xtools-app-gen-biz/src/main/resources/templates/gen/vue/index.vue.vm +++ b/xtools-app-gen/xtools-app-gen-biz/src/main/resources/templates/gen/vue/index.vue.vm @@ -367,10 +367,10 @@ import {useSettingsStore} from "@/store"; import {DictItem, PageReq, PageResult} from "@/types/global"; import SysCommonAPI from "@/api/sys/sys-common-api"; #else -import {PageReq} from "@/types/global"; +import {PageReq, PageResult} from "@/types/global"; #end #if($exportExcel) -import {FileUtils, Format} from "@/utils/utils"; +import {FileUtils, Format} from "@/utils"; #end import ${table.entityName}API, { EditForm as ${table.entityName}EditForm, diff --git a/xtools-app-standalone/src/main/resources/application-app-sys.yaml b/xtools-app-standalone/src/main/resources/application-app-sys.yaml index 24f9fbf..deca5b2 100644 --- a/xtools-app-standalone/src/main/resources/application-app-sys.yaml +++ b/xtools-app-standalone/src/main/resources/application-app-sys.yaml @@ -7,4 +7,10 @@ sys: # 日志配置 log: # 存储类型(elasticsearch|mysql) - type: ${SYS_LOG_TYPE:elasticsearch} \ No newline at end of file + type: ${SYS_LOG_TYPE:elasticsearch} + # 最大保存天数 + max-days: 2 + # 忽略操作日志 + ignore-opt-log: + - /sys/dict-item/get-by-code/* + - /**/page diff --git a/xtools-app-sys/pom.xml b/xtools-app-sys/pom.xml index 27a7332..0220cfd 100644 --- a/xtools-app-sys/pom.xml +++ b/xtools-app-sys/pom.xml @@ -24,6 +24,7 @@ xtools-app-sys-scheduled xtools-app-sys-risk xtools-app-sys-file-web + xtools-app-sys-tag \ No newline at end of file diff --git a/xtools-app-sys/xtools-app-sys-auth/src/main/java/xtools/app/sys/auth/filter/AuthFilter.java b/xtools-app-sys/xtools-app-sys-auth/src/main/java/xtools/app/sys/auth/filter/AuthFilter.java index 46a88ce..f3dbbae 100644 --- a/xtools-app-sys/xtools-app-sys-auth/src/main/java/xtools/app/sys/auth/filter/AuthFilter.java +++ b/xtools-app-sys/xtools-app-sys-auth/src/main/java/xtools/app/sys/auth/filter/AuthFilter.java @@ -11,6 +11,7 @@ import org.springframework.core.Ordered; import org.springframework.stereotype.Component; import xtools.app.common.cache.enums.AppCache; import xtools.app.sys.auth.model.dto.LoginUserDto; +import xtools.app.sys.auth.model.dto.OptLogDto; import xtools.app.sys.auth.utils.AuthUtils; import xtools.app.sys.auth.utils.PremUtils; import xtools.app.sys.auth.whitelist.AuthWhitelist; @@ -22,14 +23,18 @@ import xtools.boot.cache.redis.base.RedisService; import xtools.boot.core.holder.CommonHolder; import xtools.boot.core.utils.PathPatternUtils; import xtools.boot.core.utils.SpringContextUtils; +import xtools.boot.log.LogBus; +import xtools.boot.log.enums.LogBusBaseType; import xtools.boot.mask.utils.MaskIgnoreUtils; import xtools.boot.web.filter.base.BaseFilter; import xtools.core.CollectionUtils; import xtools.core.StringUtils; +import xtools.core.enums.LogLevel; import xtools.web.HeaderUtils; import xtools.web.HttpServletUtils; import java.io.IOException; +import java.time.Instant; import java.util.Collection; import java.util.HashSet; import java.util.Objects; @@ -47,7 +52,7 @@ import java.util.Set; * @date : 2026/1/31 21:18 */ @Component -public class AuthFilter extends BaseFilter implements Ordered, BaseParams { +public class AuthFilter extends BaseFilter implements Ordered, BootCommonConstant, BaseParams { /** * 微服务标识 @@ -157,6 +162,10 @@ public class AuthFilter extends BaseFilter implements Ordered, BaseParams { ) throws ServletException, IOException { // 获取访问 uri String uri = HeaderUtils.getUri(request); + String ip = HeaderUtils.getIp(request); + OptLogDto log = new OptLogDto(); + log.setUri(uri); + log.setIp(ip); // 忽略权限校验 if (checkAuthWhiteList(uri)) { @@ -184,11 +193,13 @@ public class AuthFilter extends BaseFilter implements Ordered, BaseParams { HttpServletUtils.respWriter(response, JSONObject.from(new Result<>(ResultType.UNAUTHORIZED, null))); return; } - + log.setAccountId(loginUser.getId()); + log.setAccount(loginUser.getAccount()); // 校验 uri 访问权限 String method = request.getMethod(); if (!PremUtils.checkInterfacePerm(uri, method, loginUser.getRoleIds())) { + saveOptLog(log, "URI访问权限校验失败"); HttpServletUtils.respWriter(response, JSONObject.from(new Result<>(ResultType.FORBIDDEN, null))); return; } @@ -199,7 +210,27 @@ public class AuthFilter extends BaseFilter implements Ordered, BaseParams { // 校验忽略掩码 checkIgnoreMask(); + CommonHolder.set(GET_SWAGGER_TAG, true); filterChain.doFilter(request, response); + saveOptLog(log, null); + } + + /** + * 保存操作日志 + * + * @param log 日志 + * @param memo 备注 + */ + private void saveOptLog(OptLogDto log, String memo) { + Object tag = CommonHolder.get(SWAGGER_TAG); + if (Objects.nonNull(tag)) { + { + log.setTitle(tag.toString()); + } + } + log.setMemo(memo); + log.setGmtCreate(Instant.now()); + LogBus.init(LogLevel.INFO, LogBusBaseType.OPT_LOG).data(log).save(); } /** diff --git a/xtools-app-sys/xtools-app-sys-auth/src/main/java/xtools/app/sys/auth/model/dto/OptLogDto.java b/xtools-app-sys/xtools-app-sys-auth/src/main/java/xtools/app/sys/auth/model/dto/OptLogDto.java new file mode 100644 index 0000000..0fd84c8 --- /dev/null +++ b/xtools-app-sys/xtools-app-sys-auth/src/main/java/xtools/app/sys/auth/model/dto/OptLogDto.java @@ -0,0 +1,76 @@ +package xtools.app.sys.auth.model.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; +import java.time.Instant; + + +/** + *

Title : OptLogDto

+ *

Description : OptLogDto

+ *

DevelopTools : Idea_x64_v2026.1

+ *

DevelopSystem : macOS Sequoia 15.7.5

+ *

Company : org.xujun

+ * + * @author : XuJun + * @version : 1.0.0 + * @date : 2026/6/2 17:28 + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +public class OptLogDto implements Serializable { + + /** + * 日志追踪id + */ + @Schema(description = "日志追踪id") + private String traceId; + + /** + * 标题 + */ + @Schema(description = "标题") + private String title; + + /** + * 账号ID + */ + @Schema(description = "账号ID") + private Long accountId; + + /** + * 账号 + */ + @Schema(description = "账号") + private String account; + + /** + * IP + */ + @Schema(description = "IP") + private String ip; + + /** + * 操作URI + */ + @Schema(description = "操作URI") + private String uri; + + /** + * 备注 + */ + @Schema(description = "备注") + private String memo; + + /** + * 创建时间 + */ + @Schema(description = "创建时间", example = "2026-01-05 10:32:00") + private Instant gmtCreate; + +} diff --git a/xtools-app-sys/xtools-app-sys-biz/pom.xml b/xtools-app-sys/xtools-app-sys-biz/pom.xml index ad66818..18bb0ce 100644 --- a/xtools-app-sys/xtools-app-sys-biz/pom.xml +++ b/xtools-app-sys/xtools-app-sys-biz/pom.xml @@ -157,6 +157,10 @@ org.xujun xtools-app-sys-risk + + org.xujun + xtools-app-sys-tag + diff --git a/xtools-app-sys/xtools-app-sys-biz/src/main/java/xtools/app/sys/config/SysConfig.java b/xtools-app-sys/xtools-app-sys-biz/src/main/java/xtools/app/sys/config/SysConfig.java index fa4bc87..8dcbf33 100644 --- a/xtools-app-sys/xtools-app-sys-biz/src/main/java/xtools/app/sys/config/SysConfig.java +++ b/xtools-app-sys/xtools-app-sys-biz/src/main/java/xtools/app/sys/config/SysConfig.java @@ -4,6 +4,9 @@ import lombok.Data; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.annotation.Configuration; +import java.util.ArrayList; +import java.util.List; + /** *

Title : SysConfig

*

Description : SysConfig

@@ -57,6 +60,11 @@ public class SysConfig { * 最大保存天数 */ private int maxDays = 2; + + /** + * 忽略操作日志 + */ + private List ignoreOptLog = new ArrayList<>(); } } 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 new file mode 100644 index 0000000..1b0735e --- /dev/null +++ b/xtools-app-sys/xtools-app-sys-biz/src/main/java/xtools/app/sys/controller/SysOptLogController.java @@ -0,0 +1,64 @@ +package xtools.app.sys.controller; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.PostMapping; +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.SysOptLogPageReq; +import xtools.app.sys.model.dto.resp.SysOptLogResp; +import xtools.app.sys.service.SysOptLogService; +import xtools.boot.api.exection.BizError; +import xtools.boot.api.model.dto.Result; +import xtools.boot.api.model.dto.page.PageReq; +import xtools.boot.api.model.dto.page.PageResp; +import xtools.extend.office.FesodUtils; +import xtools.web.HttpServletUtils; + +import java.io.IOException; +import java.util.List; + +/** + *

Title : SysOptLogController

+ *

Description : 系统操作日志 Controller

+ *

Company : org.xujun

+ * + * @author : xujun + * @version : 1.0.0 + * @date : 2026-06-02 16:44:47 + */ +@RequiredArgsConstructor +@Tag(name = "系统操作日志") +@RestController +@RequestMapping("/sys/opt-log") +public class SysOptLogController { + + private final SysOptLogService sysOptLogService; + + @Operation(summary = "分页请求") + @PostMapping("page") + public Result> page(@RequestBody @Valid PageReq pageReq) { + return sysOptLogService.page(pageReq); + } + + @Operation(summary = "导出Excel") + @PostMapping("export") + public void exportExcel(@RequestBody @Valid SysOptLogPageReq req, HttpServletResponse response) { + String sheetName = "系统操作日志"; + String filename = sheetName + ".xlsx"; + List dataList = sysOptLogService.exportExcel(req); + try { + FesodUtils.write(response.getOutputStream(), SysOptLogExcel.class, sheetName, dataList); + } catch (IOException e) { + throw new BizError("导出Excel失败"); + } + // 设置 header 和 contentType.写在最后的原因是,避免报错时,响应 contentType 已经被修改 + HttpServletUtils.addDownloadHeader(response, filename); + } + +} diff --git a/xtools-app-sys/xtools-app-sys-biz/src/main/java/xtools/app/sys/convert/SysOptLogConvert.java b/xtools-app-sys/xtools-app-sys-biz/src/main/java/xtools/app/sys/convert/SysOptLogConvert.java new file mode 100644 index 0000000..96cce05 --- /dev/null +++ b/xtools-app-sys/xtools-app-sys-biz/src/main/java/xtools/app/sys/convert/SysOptLogConvert.java @@ -0,0 +1,55 @@ +package xtools.app.sys.convert; + +import org.mapstruct.Mapper; +import xtools.app.sys.auth.model.dto.OptLogDto; +import xtools.app.sys.model.dto.excel.SysOptLogExcel; +import xtools.app.sys.model.dto.resp.SysOptLogResp; +import xtools.app.sys.model.entity.SysOptLog; + +import java.util.List; + +/** + *

Title : SysOptLogConvert

+ *

Description : 系统操作日志 Convert

+ *

Company : org.xujun

+ * + * @author : xujun + * @version : 1.0.0 + * @date : 2026-06-02 16:44:47 + */ +@Mapper(componentModel = "spring") +public interface SysOptLogConvert { + + /** + * 实体转响应 + * + * @param data 实体 + * @return 响应 + */ + SysOptLogResp entityToResp(SysOptLog data); + + /** + * 批量实体转响应 + * + * @param dataList 批量实体 + * @return 响应 + */ + List entityToRespList(List dataList); + + /** + * 实体转Excel + * + * @param data 实体 + * @return Excel + */ + SysOptLogExcel entityToExcel(SysOptLog data); + + + /** + * DTO转实体 + * + * @param data DTO + * @return 实体 + */ + SysOptLog dtoToEntity(OptLogDto data); +} \ No newline at end of file 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 new file mode 100644 index 0000000..898d811 --- /dev/null +++ b/xtools-app-sys/xtools-app-sys-biz/src/main/java/xtools/app/sys/mapper/SysOptLogMapper.java @@ -0,0 +1,19 @@ +package xtools.app.sys.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import org.apache.ibatis.annotations.Mapper; +import xtools.app.sys.model.entity.SysOptLog; + +/** + *

Title : SysOptLogMapper

+ *

Description : 系统操作日志 Mapper 接口

+ *

Company : org.xujun

+ * + * @author : xujun + * @version : 1.0.0 + * @date : 2026-06-02 16:44:47 + */ +@Mapper +public interface SysOptLogMapper extends BaseMapper { + +} diff --git a/xtools-app-sys/xtools-app-sys-biz/src/main/java/xtools/app/sys/model/dto/excel/SysOptLogExcel.java b/xtools-app-sys/xtools-app-sys-biz/src/main/java/xtools/app/sys/model/dto/excel/SysOptLogExcel.java new file mode 100644 index 0000000..9fa1248 --- /dev/null +++ b/xtools-app-sys/xtools-app-sys-biz/src/main/java/xtools/app/sys/model/dto/excel/SysOptLogExcel.java @@ -0,0 +1,81 @@ +package xtools.app.sys.model.dto.excel; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.apache.fesod.sheet.annotation.ExcelProperty; +import org.apache.fesod.sheet.annotation.format.DateTimeFormat; + +import java.io.Serializable; +import java.util.Date; + +/** + *

Title : SysOptLogExcel

+ *

Description : 系统操作日志Excel对象

+ *

Company : org.xujun

+ * + * @author : xujun + * @version : 1.0.0 + * @date : 2026-06-02 16:44:47 + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +public class SysOptLogExcel implements Serializable { + + /** + * 日志追踪id + */ + @ExcelProperty("日志追踪id") + private String traceId; + + /** + * 标题 + */ + @ExcelProperty("标题") + private String title; + + /** + * 账号ID + */ + @ExcelProperty("账号ID") + private Long accountId; + + /** + * 账号 + */ + @ExcelProperty("账号") + private String account; + + /** + * IP + */ + @ExcelProperty("IP") + private String ip; + + /** + * 地址 + */ + @ExcelProperty("地址") + private String addr; + + /** + * 操作URI + */ + @ExcelProperty("操作URI") + private String uri; + + /** + * 备注 + */ + @ExcelProperty("备注") + private String memo; + + /** + * 创建时间 + */ + @ExcelProperty("创建时间") + @DateTimeFormat("yyyy-MM-dd HH:mm:ss") + private Date gmtCreate; + +} \ No newline at end of file diff --git a/xtools-app-sys/xtools-app-sys-biz/src/main/java/xtools/app/sys/model/dto/req/SysOptLogPageReq.java b/xtools-app-sys/xtools-app-sys-biz/src/main/java/xtools/app/sys/model/dto/req/SysOptLogPageReq.java new file mode 100644 index 0000000..2c7f163 --- /dev/null +++ b/xtools-app-sys/xtools-app-sys-biz/src/main/java/xtools/app/sys/model/dto/req/SysOptLogPageReq.java @@ -0,0 +1,91 @@ +package xtools.app.sys.model.dto.req; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; +import java.time.Instant; + +/** + *

Title : SysOptLogPageReq

+ *

Description : 系统操作日志分页请求对象

+ *

Company : org.xujun

+ * + * @author : xujun + * @version : 1.0.0 + * @date : 2026-06-02 16:44:47 + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +public class SysOptLogPageReq implements Serializable { + + /** + * ID + */ + @Schema(description = "ID") + private Long id; + + /** + * 日志追踪id + */ + @Schema(description = "日志追踪id") + private String traceId; + + /** + * 标题 + */ + @Schema(description = "标题") + private String title; + + /** + * 账号ID + */ + @Schema(description = "账号ID") + private Long accountId; + + /** + * 账号 + */ + @Schema(description = "账号") + private String account; + + /** + * IP + */ + @Schema(description = "IP") + private String ip; + + /** + * 地址 + */ + @Schema(description = "地址") + private String addr; + + /** + * 地址code + */ + @Schema(description = "地址code") + private String addrCode; + + /** + * 操作URI + */ + @Schema(description = "操作URI") + private String uri; + + /** + * 备注 + */ + @Schema(description = "备注") + private String memo; + + /** + * 创建时间(范围) + */ + @Schema(description = "创建时间(范围)", example = "['2026-01-01 00:00:00', '2026-01-01 12:00:00']") + private Instant[] gmtCreateRange; + +} \ No newline at end of file diff --git a/xtools-app-sys/xtools-app-sys-biz/src/main/java/xtools/app/sys/model/dto/resp/SysOptLogResp.java b/xtools-app-sys/xtools-app-sys-biz/src/main/java/xtools/app/sys/model/dto/resp/SysOptLogResp.java new file mode 100644 index 0000000..1aba337 --- /dev/null +++ b/xtools-app-sys/xtools-app-sys-biz/src/main/java/xtools/app/sys/model/dto/resp/SysOptLogResp.java @@ -0,0 +1,85 @@ +package xtools.app.sys.model.dto.resp; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import xtools.boot.api.model.entity.BaseEntity; + +/** + *

Title : SysOptLogResp

+ *

Description : 系统操作日志响应对象

+ *

Company : org.xujun

+ * + * @author : xujun + * @version : 1.0.0 + * @date : 2026-06-02 16:44:47 + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +@EqualsAndHashCode(callSuper = true) +public class SysOptLogResp extends BaseEntity { + + /** + * ID + */ + @Schema(description = "ID") + private Long id; + + /** + * 日志追踪id + */ + @Schema(description = "日志追踪id") + private String traceId; + + /** + * 标题 + */ + @Schema(description = "标题") + private String title; + + /** + * 账号ID + */ + @Schema(description = "账号ID") + private Long accountId; + + /** + * 账号 + */ + @Schema(description = "账号") + private String account; + + /** + * IP + */ + @Schema(description = "IP") + private String ip; + + /** + * 地址 + */ + @Schema(description = "地址") + private String addr; + + /** + * 地址code + */ + @Schema(description = "地址code") + private String addrCode; + + /** + * 操作URI + */ + @Schema(description = "操作URI") + private String uri; + + /** + * 备注 + */ + @Schema(description = "备注") + private String memo; + +} \ No newline at end of file diff --git a/xtools-app-sys/xtools-app-sys-biz/src/main/java/xtools/app/sys/model/entity/SysOptLog.java b/xtools-app-sys/xtools-app-sys-biz/src/main/java/xtools/app/sys/model/entity/SysOptLog.java new file mode 100644 index 0000000..e725b55 --- /dev/null +++ b/xtools-app-sys/xtools-app-sys-biz/src/main/java/xtools/app/sys/model/entity/SysOptLog.java @@ -0,0 +1,107 @@ +package xtools.app.sys.model.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; +import java.time.Instant; + +/** + *

Title : SysOptLog

+ *

Description : 系统操作日志实体对象

+ *

Company : org.xujun

+ * + * @author : xujun + * @version : 1.0.0 + * @date : 2026-06-02 16:44:47 + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +@TableName("sys_opt_log") +public class SysOptLog implements Serializable { + + /** + * ID + */ + @Schema(description = "ID") + @TableId(value = "id", type = IdType.ASSIGN_ID) + private Long id; + + /** + * 日志追踪id + */ + @Schema(description = "日志追踪id") + @TableField(value = "trace_id") + private String traceId; + + /** + * 标题 + */ + @Schema(description = "标题") + @TableField(value = "title") + private String title; + + /** + * 账号ID + */ + @Schema(description = "账号ID") + @TableField(value = "account_id") + private Long accountId; + + /** + * 账号 + */ + @Schema(description = "账号") + @TableField(value = "account") + private String account; + + /** + * IP + */ + @Schema(description = "IP") + @TableField(value = "ip") + private String ip; + + /** + * 地址 + */ + @Schema(description = "地址") + @TableField(value = "addr") + private String addr; + + /** + * 地址code + */ + @Schema(description = "地址code") + @TableField(value = "addr_code") + private String addrCode; + + /** + * 操作URI + */ + @Schema(description = "操作URI") + @TableField(value = "uri") + private String uri; + + /** + * 备注 + */ + @Schema(description = "备注") + @TableField(value = "memo") + private String memo; + + /** + * 创建时间 + */ + @TableField(value = "gmt_create") + @Schema(description = "创建时间", example = "2026-01-05 10:32:00") + private Instant gmtCreate; + +} \ No newline at end of file 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 new file mode 100644 index 0000000..95c3e0e --- /dev/null +++ b/xtools-app-sys/xtools-app-sys-biz/src/main/java/xtools/app/sys/service/SysOptLogService.java @@ -0,0 +1,48 @@ +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.SysOptLogPageReq; +import xtools.app.sys.model.dto.resp.SysOptLogResp; +import xtools.boot.api.model.dto.Result; +import xtools.boot.api.model.dto.page.PageReq; +import xtools.boot.api.model.dto.page.PageResp; + +import java.util.List; + +/** + *

Title : SysOptLogService

+ *

Description : 系统操作日志 Service

+ *

Company : org.xujun

+ * + * @author : xujun + * @version : 1.0.0 + * @date : 2026-06-02 16:44:47 + */ +public interface SysOptLogService { + + /** + * 分页查询 + * + * @param pageReq 分页请求 + * @return 分页结果 + */ + Result> page(PageReq pageReq); + + /** + * 保存日志 + * + * @param traceId 日志追踪 ID + * @param logData 日志 + */ + void save(String traceId, JSONObject logData); + + /** + * 导出 Excel + * + * @param req 请求参数 + * @return Excel 数据 + */ + List exportExcel(SysOptLogPageReq req); + +} diff --git a/xtools-app-sys/xtools-app-sys-biz/src/main/java/xtools/app/sys/service/base/SysOptLogBaseService.java b/xtools-app-sys/xtools-app-sys-biz/src/main/java/xtools/app/sys/service/base/SysOptLogBaseService.java new file mode 100644 index 0000000..de0f4c8 --- /dev/null +++ b/xtools-app-sys/xtools-app-sys-biz/src/main/java/xtools/app/sys/service/base/SysOptLogBaseService.java @@ -0,0 +1,20 @@ +package xtools.app.sys.service.base; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import org.springframework.stereotype.Component; + +import xtools.app.sys.mapper.SysOptLogMapper; +import xtools.app.sys.model.entity.SysOptLog; + +/** + *

Title : SysOptLogBaseService

+ *

Description : 系统操作日志 BaseService

+ *

Company : org.xujun

+ * + * @author : xujun + * @version : 1.0.0 + * @date : 2026-06-02 16:44:47 + */ +@Component +public class SysOptLogBaseService extends ServiceImpl { +} diff --git a/xtools-app-sys/xtools-app-sys-biz/src/main/java/xtools/app/sys/service/impl/LogBusServiceImpl.java b/xtools-app-sys/xtools-app-sys-biz/src/main/java/xtools/app/sys/service/impl/LogBusServiceImpl.java index dd9e545..0b1a3d1 100644 --- a/xtools-app-sys/xtools-app-sys-biz/src/main/java/xtools/app/sys/service/impl/LogBusServiceImpl.java +++ b/xtools-app-sys/xtools-app-sys-biz/src/main/java/xtools/app/sys/service/impl/LogBusServiceImpl.java @@ -9,7 +9,9 @@ import org.springframework.stereotype.Service; import xtools.app.common.log.bus.service.LogBusService; import xtools.app.sys.model.entity.SysLog; import xtools.app.sys.service.SysLogService; +import xtools.app.sys.service.SysOptLogService; import xtools.boot.api.model.dto.log.LogTrack; +import xtools.boot.log.enums.LogBusBaseType; import xtools.boot.log.model.dto.LogBody; import xtools.boot.log.model.dto.RunInfo; import xtools.core.CollectionUtils; @@ -37,6 +39,8 @@ public class LogBusServiceImpl implements LogBusService { private final SysLogService sysLogService; + private final SysOptLogService sysOptLogService; + /** * 保存日志 * @@ -44,18 +48,25 @@ public class LogBusServiceImpl implements LogBusService { */ @Override public void saveLog(LogBody logBody) { - LogTrack logTrack = logBody.getLogTrack(); - RunInfo runInfo = logBody.getRunInfo(); + // 获取日志类型 + String logType = logBody.getType(); + LogTrack logTrack = logBody.getLogTrack(); JSONObject logData = logBody.getLogData(); if (CollectionUtils.isEmpty(logData)) { logData = new JSONObject(); } + + // 扩展日志处理 + if (extLog(logType, logTrack.traceId(), logData)) { + return; + } + + RunInfo runInfo = logBody.getRunInfo(); JSONArray stackTrace = logBody.getStackTrace(); // 处理数据 String title = logBody.getTitle(); - String logType = logBody.getType(); title = StringUtils.isBlank(title) ? logType : title; // 获取请求ip String ip = logData.getString("ip"); @@ -90,4 +101,20 @@ public class LogBusServiceImpl implements LogBusService { log.error("保存日志异常,logBody:{}", JsonUtils.toStrPretty(logBody), e); } } + + /** + * 扩展日志 + * + * @param logType 日志类型 + * @param traceId 日志追踪 ID + * @param logData 日志数据 + * @return 保存结果 + */ + private boolean extLog(String logType, String traceId, JSONObject logData) { + if (LogBusBaseType.OPT_LOG.desc().equals(logType)) { + sysOptLogService.save(traceId, logData); + return true; + } + return false; + } } 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 new file mode 100644 index 0000000..d261049 --- /dev/null +++ b/xtools-app-sys/xtools-app-sys-biz/src/main/java/xtools/app/sys/service/impl/SysOptLogServiceImpl.java @@ -0,0 +1,187 @@ +package xtools.app.sys.service.impl; + +import com.alibaba.fastjson2.JSONObject; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.context.annotation.Primary; +import org.springframework.stereotype.Service; +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.SysOptLogPageReq; +import xtools.app.sys.model.dto.resp.SysOptLogResp; +import xtools.app.sys.model.entity.SysOptLog; +import xtools.app.sys.service.SysOptLogService; +import xtools.app.sys.service.base.SysOptLogBaseService; +import xtools.boot.api.model.dto.Result; +import xtools.boot.api.model.dto.page.PageReq; +import xtools.boot.api.model.dto.page.PageResp; +import xtools.boot.core.utils.PathPatternUtils; +import xtools.boot.db.mybatisplus.utils.QueryUtils; +import xtools.boot.ip.utils.IpUtils; +import xtools.core.StringUtils; +import xtools.extend.dto.IpAddrDto; + +import java.util.List; +import java.util.Objects; + +/** + *

Title : SysOptLogServiceImpl

+ *

Description : 系统操作日志 ServiceImpl

+ *

Company : org.xujun

+ * + * @author : xujun + * @version : 1.0.0 + * @date : 2026-06-02 16:44:47 + */ +@Slf4j +@Primary +@Service +@RequiredArgsConstructor +public class SysOptLogServiceImpl implements SysOptLogService { + + private final SysConfig sysConfig; + + private final SysOptLogBaseService sysOptLogBaseService; + + private final SysOptLogConvert sysOptLogConvert; + + /** + * 分页查询 + * + * @param pageReq 分页请求 + * @return 分页结果 + */ + @Override + public Result> page(PageReq pageReq) { + // 分页查询 + Page page = getPageData(pageReq.getCurrentPage(), pageReq.getPageSize(), pageReq.getQuery()); + // 分装结果 + PageResp pageResp = new PageResp<>(pageReq, page.getTotal(), sysOptLogConvert.entityToRespList(page.getRecords())); + return Result.ok(pageResp); + } + + /** + * 保存日志 + * + * @param traceId 日志追踪 ID + * @param logData 日志 + */ + @Override + public void save(String traceId, JSONObject logData) { + // 忽略操作日志 + List ignoreOptLog = sysConfig.getLog().getIgnoreOptLog(); + + OptLogDto dto = logData.toJavaObject(OptLogDto.class); + String uri = dto.getUri(); + + // 判断忽略操作日志 + if (PathPatternUtils.match(ignoreOptLog, uri)) { + return; + } + + dto.setTraceId(traceId); + SysOptLog optLog = sysOptLogConvert.dtoToEntity(dto); + String ip = optLog.getIp(); + if (StringUtils.isNotBlank(ip)) { + try { + IpAddrDto ipAddr = IpUtils.search(ip); + optLog.setAddr(IpUtils.searchAddr(ipAddr)); + optLog.setAddrCode(IpUtils.getCode(ipAddr)); + } catch (Exception e) { + log.warn("获取 IP 地址信息失败,IP = {}", ip); + } + } + sysOptLogBaseService.save(optLog); + } + + /** + * 导出 Excel + * + * @param req 请求参数 + * @return Excel 数据 + */ + @Override + public List exportExcel(SysOptLogPageReq req) { + // 创建查询条件 + LambdaQueryWrapper query = new LambdaQueryWrapper<>(); + // 查询字段 + query.select( + SysOptLog::getTraceId + , SysOptLog::getTitle + , SysOptLog::getAccountId + , SysOptLog::getAccount + , SysOptLog::getIp + , SysOptLog::getAddr + , SysOptLog::getUri + , SysOptLog::getMemo + , SysOptLog::getGmtCreate + ); + // 设置查询条件 + setQueryWrapper(query, req); + // 排序 + query.orderByDesc(SysOptLog::getGmtCreate); + List dataList = sysOptLogBaseService.list(query); + return dataList.stream().map(sysOptLogConvert::entityToExcel).toList(); + } + + /** + * 获取分页数据 + * + * @param currentPage 当前页 + * @param pageSize 每页数量 + * @param req 请求参数 + * @return 分页数据 + */ + private Page getPageData(Integer currentPage, Integer pageSize, SysOptLogPageReq req) { + // 创建查询条件 + LambdaQueryWrapper query = new LambdaQueryWrapper<>(); + // 查询字段 + query.select( + SysOptLog::getId + , SysOptLog::getTraceId + , SysOptLog::getTitle + , SysOptLog::getAccountId + , SysOptLog::getAccount + , SysOptLog::getIp + , SysOptLog::getAddr + , SysOptLog::getAddrCode + , SysOptLog::getUri + , SysOptLog::getMemo + , SysOptLog::getGmtCreate + ); + // 设置查询条件 + setQueryWrapper(query, req); + // 排序 + query.orderByDesc(SysOptLog::getGmtCreate); + return sysOptLogBaseService.page(QueryUtils.getPage(currentPage, pageSize), query); + } + + /** + * 设置查询条件 + * + * @param query 查询条件 + * @param req 请求参数 + */ + private void setQueryWrapper(LambdaQueryWrapper query, SysOptLogPageReq req) { + if (Objects.isNull(req)) { + return; + } + // 查询条件 + query.eq(Objects.nonNull(req.getId()), SysOptLog::getId, req.getId()); + query.like(StringUtils.isNotBlank(req.getTraceId()), SysOptLog::getTraceId, req.getTraceId()); + query.like(StringUtils.isNotBlank(req.getTitle()), SysOptLog::getTitle, req.getTitle()); + query.eq(Objects.nonNull(req.getAccountId()), SysOptLog::getAccountId, req.getAccountId()); + query.like(StringUtils.isNotBlank(req.getAccount()), SysOptLog::getAccount, req.getAccount()); + query.like(StringUtils.isNotBlank(req.getIp()), SysOptLog::getIp, req.getIp()); + query.like(StringUtils.isNotBlank(req.getAddr()), SysOptLog::getAddr, req.getAddr()); + query.like(StringUtils.isNotBlank(req.getAddrCode()), SysOptLog::getAddrCode, req.getAddrCode()); + query.like(StringUtils.isNotBlank(req.getUri()), SysOptLog::getUri, req.getUri()); + query.like(StringUtils.isNotBlank(req.getMemo()), SysOptLog::getMemo, req.getMemo()); + QueryUtils.addTimeRange(query, req.getGmtCreateRange(), SysOptLog::getGmtCreate); + } + +} diff --git a/xtools-app-sys/xtools-app-sys-tag/pom.xml b/xtools-app-sys/xtools-app-sys-tag/pom.xml new file mode 100644 index 0000000..fa84b87 --- /dev/null +++ b/xtools-app-sys/xtools-app-sys-tag/pom.xml @@ -0,0 +1,42 @@ + + + 4.0.0 + + org.xujun + xtools-app-sys + 1.0.0 + + xtools-app-sys-tag + + + + + + + org.xujun + xtools-boot-core + + + + + + org.springframework + spring-webmvc + + + + + + jakarta.annotation + jakarta.annotation-api + + + jakarta.servlet + jakarta.servlet-api + compile + + + + \ No newline at end of file diff --git a/xtools-app-sys/xtools-app-sys-tag/src/main/java/xtools/app/sys/tag/config/WebMvcSwaggerConfig.java b/xtools-app-sys/xtools-app-sys-tag/src/main/java/xtools/app/sys/tag/config/WebMvcSwaggerConfig.java new file mode 100644 index 0000000..c26bc62 --- /dev/null +++ b/xtools-app-sys/xtools-app-sys-tag/src/main/java/xtools/app/sys/tag/config/WebMvcSwaggerConfig.java @@ -0,0 +1,39 @@ +package xtools.app.sys.tag.config; + +import jakarta.annotation.Resource; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.config.annotation.InterceptorRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; +import xtools.app.sys.tag.interceptor.SwaggerTagInterceptor; +import xtools.base.config.BaseParams; + +/** + *

Title : WebMvcSwaggerConfig

+ *

Description : WebMvcSwaggerConfig

+ *

DevelopTools : Idea_x64_v2026.1

+ *

DevelopSystem : macOS Sequoia 15.7.5

+ *

Company : org.xujun

+ * + * @author : XuJun + * @version : 1.0.0 + * @date : 2026/6/2 16:44:47 + */ +@Configuration +public class WebMvcSwaggerConfig implements WebMvcConfigurer, BaseParams { + + @Resource + private SwaggerTagInterceptor swaggerTagInterceptor; + + /** + * 添加拦截器 + * + * @param registry 注册器 + */ + @Override + public void addInterceptors(InterceptorRegistry registry) { + registry.addInterceptor(swaggerTagInterceptor) + .addPathPatterns("/**") + .order(CP_NUM0); + } + +} diff --git a/xtools-app-sys/xtools-app-sys-tag/src/main/java/xtools/app/sys/tag/interceptor/SwaggerTagInterceptor.java b/xtools-app-sys/xtools-app-sys-tag/src/main/java/xtools/app/sys/tag/interceptor/SwaggerTagInterceptor.java new file mode 100644 index 0000000..3192118 --- /dev/null +++ b/xtools-app-sys/xtools-app-sys-tag/src/main/java/xtools/app/sys/tag/interceptor/SwaggerTagInterceptor.java @@ -0,0 +1,79 @@ +package xtools.app.sys.tag.interceptor; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import lombok.extern.slf4j.Slf4j; +import org.jspecify.annotations.NonNull; +import org.springframework.stereotype.Component; +import org.springframework.web.method.HandlerMethod; +import org.springframework.web.servlet.HandlerInterceptor; +import xtools.base.config.BaseParams; +import xtools.boot.api.constant.BootCommonConstant; +import xtools.boot.core.holder.CommonHolder; +import xtools.core.StringUtils; + +import java.util.Objects; +import java.util.StringJoiner; + +/** + *

Title : SwaggerTagInterceptor

+ *

Description : SwaggerTagInterceptor

+ *

DevelopTools : Idea_x64_v2026.1

+ *

DevelopSystem : macOS Sequoia 15.7.5

+ *

Company : org.xujun

+ * + * @author : XuJun + *

Version : 1.0.0

+ * @date : 2026/6/3 17:28 + */ +@Slf4j +@Component +public class SwaggerTagInterceptor implements HandlerInterceptor, BootCommonConstant, BaseParams { + + /** + * 进入Handler方法之前执行 + * + * @param request HttpServletRequest + * @param response HttpServletResponse + * @param handler handler + * @return true(放行) or false(拦截) + */ + @Override + public boolean preHandle( + @NonNull HttpServletRequest request, + @NonNull HttpServletResponse response, + @NonNull Object handler + ) { + if (!(handler instanceof HandlerMethod handlerMethod)) { + return true; + } + Object get = CommonHolder.get(GET_SWAGGER_TAG); + if (Objects.isNull(get)) { + return true; + } + + StringJoiner joiner = new StringJoiner(CP_LINE); + // 获取@Tag注解 + Tag tag = handlerMethod.getBeanType().getAnnotation(Tag.class); + if (Objects.nonNull(tag)) { + String name = tag.name(); + if (StringUtils.isNotBlank(name)) { + joiner.add(name); + } + } + + // 获取@Operation注解 + Operation operation = handlerMethod.getMethodAnnotation(Operation.class); + if (Objects.nonNull(operation)) { + String summary = operation.summary(); + if (StringUtils.isNotBlank(summary)) { + joiner.add(summary); + } + } + CommonHolder.set(SWAGGER_TAG, joiner.toString()); + return true; + } + +}