From d66a47eec9debe03a6d2eb10cd92e08cbf27a350 Mon Sep 17 00:00:00 2001 From: "liang.chao" <1360241448@qq.com> Date: Tue, 9 Sep 2025 12:09:52 +0800 Subject: [PATCH] =?UTF-8?q?=E6=97=A5=E5=BF=97=E4=BB=A3=E7=A0=81=E6=8F=90?= =?UTF-8?q?=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/system/SysLogController.java | 191 ++++++++++ .../src/main/resources/application.yml | 6 +- .../core/controller/BaseController.java | 18 +- .../bonus/common/exception/SysWarning.java | 39 +++ .../common/exception/WaringLogEvent.java | 20 ++ .../com/bonus/common/utils/FileUtils.java | 320 +++++++++++++++++ .../com/bonus/system/mapper/SysLogMapper.java | 133 +++++++ .../system/service/ExportSqlService.java | 93 +++++ .../bonus/system/service/ISysLogService.java | 80 +++++ .../service/impl/SysLogServiceImpl.java | 330 ++++++++++++++++++ .../resources/mapper/system/SysLogMapper.xml | 283 +++++++++++++++ 11 files changed, 1507 insertions(+), 6 deletions(-) create mode 100644 bonus-admin/src/main/java/com/bonus/web/controller/system/SysLogController.java create mode 100644 bonus-common/src/main/java/com/bonus/common/exception/SysWarning.java create mode 100644 bonus-common/src/main/java/com/bonus/common/exception/WaringLogEvent.java create mode 100644 bonus-common/src/main/java/com/bonus/common/utils/FileUtils.java create mode 100644 bonus-system/src/main/java/com/bonus/system/mapper/SysLogMapper.java create mode 100644 bonus-system/src/main/java/com/bonus/system/service/ExportSqlService.java create mode 100644 bonus-system/src/main/java/com/bonus/system/service/ISysLogService.java create mode 100644 bonus-system/src/main/java/com/bonus/system/service/impl/SysLogServiceImpl.java create mode 100644 bonus-system/src/main/resources/mapper/system/SysLogMapper.xml diff --git a/bonus-admin/src/main/java/com/bonus/web/controller/system/SysLogController.java b/bonus-admin/src/main/java/com/bonus/web/controller/system/SysLogController.java new file mode 100644 index 0000000..1a1be77 --- /dev/null +++ b/bonus-admin/src/main/java/com/bonus/web/controller/system/SysLogController.java @@ -0,0 +1,191 @@ +package com.bonus.web.controller.system; + +import com.bonus.common.annotation.SysLog; +import com.bonus.common.core.controller.BaseController; +import com.bonus.common.core.domain.AjaxResult; +import com.bonus.common.core.domain.R; +import com.bonus.common.core.page.TableDataInfo; +import com.bonus.common.enums.OperaType; +import com.bonus.common.utils.FileUtils; +import com.bonus.common.utils.global.SystemGlobal; +import com.bonus.system.domain.SysLogsVo; +import com.bonus.system.service.ExportSqlService; +import com.bonus.system.service.ISysLogService; +import io.swagger.annotations.ApiOperation; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +/** + * @author:cwchen + * @date:2024-02-28-14:01 + * @version:1.0 + * @description:系统日志 + */ +@RestController +@RequestMapping("/sys/sysLog") +@Slf4j +public class SysLogController extends BaseController { + + @Resource(name = "ISysLogService") + private ISysLogService service; + + /** + * 存储的路径 + */ + @Value("${sql.filePath}") + private String filePath; + + + @Autowired + private ExportSqlService exportSqlService; + + /** + * 保存系统日志,业务日志和其他日常日志 + * + * @param sysLog 日志对象 + */ + @ApiOperation(value = "保存系统日志") + @PostMapping("saveLogs") + public AjaxResult saveLogs(@RequestBody SysLogsVo sysLog) { + return service.saveLogs(sysLog); + } + + /** + * 保存越权日志 + * + * @param sysLog + * @param request + */ + @PostMapping("addLogs") + public void addLogs(@RequestBody SysLogsVo sysLog, HttpServletRequest request) { + service.saveLogs(sysLog, request); + } + + @ApiOperation(value = "查询系统日志") + @GetMapping("getSystemLogs") + @SysLog(title = "审计日志", module = "审计日志->系统日志", businessType = OperaType.QUERY, details = "查询系统日志列表", type = "系统日志", logType = 0) + public TableDataInfo getSystemLogs(SysLogsVo dto) { + try { + dto.setLogType(0); + startPage(); + List list = service.getSystemLogs(dto); + return getDataTable(list); + } catch (Exception e) { + log.error(e.toString(), e); + return getDataTableBad(new ArrayList<>(), "请求出错了"); + } + } + + @ApiOperation(value = "查询业务日志") + @GetMapping("getYwLogs") + @SysLog(title = "业务日志", module = "审计日志->业务日志", businessType = OperaType.QUERY, details = "查询业务日志列表", type = "系统日志", logType = 0) + public TableDataInfo getYwLogs(SysLogsVo dto) { + try { + dto.setLogType(1); + startPage(); + List list = service.getSystemLogs(dto); + return getDataTable(list); + } catch (Exception e) { + log.error(e.toString(), e); + return getDataTableBad(new ArrayList<>(), "请求出错了"); + } + } + + @ApiOperation(value = "查询异常日志") + @GetMapping("getErrLogs") + @SysLog(title = "异常日志", module = "审计日志->异常日志", businessType = OperaType.QUERY, logType = 0, details = "查询系统异常日志", type = "系统日志") + public TableDataInfo getErrLogs(SysLogsVo dto) { + try { + dto.setLogType(2); + startPage(); + List list = service.getSystemLogs(dto); + return getDataTable(list); + } catch (Exception e) { + log.error(e.toString(), e); + return getDataTableBad(new ArrayList<>(), "请求出错了"); + } + } + + @ApiOperation(value = "日志备份") + @GetMapping("downloadErrLogs") + @SysLog(title = "审计日志", module = "审计日志->异常日志", businessType = OperaType.COPY_LOG, logType = 0, details = "异常日志备份", type = "系统日志") + public void downloadErrLogs(HttpServletRequest request, HttpServletResponse response) { + try { + String dateTimeNow = exportSqlService.export("2"); + String path = filePath + ExportSqlService.TABLE_NAME + dateTimeNow + SystemGlobal.TEXT_FIX; + FileUtils.fileDownload(path, "sys_log" + dateTimeNow + ".sql", response); + } catch (Exception e) { + logger.error("文件下载失败", e); + } + } + + @ApiOperation(value = "日志备份") + @GetMapping("downloadYwLogs") + @SysLog(title = "审计日志", module = "审计日志-->业务日志", businessType = OperaType.COPY_LOG, logType = 0, details = "业务日志备份", type = "系统日志") + public void downloadYwLogs(HttpServletRequest request, HttpServletResponse response) { + try { + String dateTimeNow = exportSqlService.export("1"); + String path = filePath + ExportSqlService.TABLE_NAME + dateTimeNow + SystemGlobal.TEXT_FIX; + FileUtils.fileDownload(path, "sys_log" + dateTimeNow + ".sql", response); + } catch (Exception e) { + logger.error("文件下载失败", e); + } + } + + @ApiOperation(value = "日志备份") + @GetMapping("downloadSysLogs") + @SysLog(title = "审计日志", module = "审计日志->系统日志", businessType = OperaType.COPY_LOG, details = "系统日志备份", type = "系统日志", logType = 0) + public void downloadSysLogs(HttpServletRequest request, HttpServletResponse response) { + try { + String dateTimeNow = exportSqlService.export("0"); + String path = filePath + ExportSqlService.TABLE_NAME + dateTimeNow + SystemGlobal.TEXT_FIX; + FileUtils.fileDownload(path, "sys_log" + dateTimeNow + ".sql", response); + } catch (Exception e) { + logger.error("文件下载失败", e); + } + } + + + @ApiOperation(value = "查询日志容量") + @PostMapping("getLogsSet") + @SysLog(title = "审计日志", module = "审计日志->日志容量配置", businessType = OperaType.QUERY, details = "查询日志容量", type = "系统日志", logType = 0) + public R getLogsSet() { + return service.getLogsSet(); + } + + + @ApiOperation(value = "设置日志容量") + @PostMapping("setLogsSet") + @SysLog(title = "审计日志", module = "审计日志->日志容量配置", businessType = OperaType.QUERY, details = "修改日志容量", type = "系统日志", logType = 0) + public R setLogsSet(@RequestBody SysLogsVo dto) { + + return service.setLogsSet(dto.getCapacity()); + } + + + @ApiOperation(value = "查询日志统计分析") + @PostMapping("getLogStatistics") + @SysLog(title = "审计日志", module = "审计日志->日志分析", businessType = OperaType.QUERY, details = "查询日志分析", type = "系统日志", logType = 0) + public R> getLogStatistics(@RequestBody SysLogsVo dto) { + return service.getLogStatistics(dto); + } + + @ApiOperation(value = "查询日志告警") + @GetMapping("logWarn") + public AjaxResult logWarn() { + service.handleWarningLog(); + return success(); + } + + +} diff --git a/bonus-admin/src/main/resources/application.yml b/bonus-admin/src/main/resources/application.yml index 7e3accc..7a33987 100644 --- a/bonus-admin/src/main/resources/application.yml +++ b/bonus-admin/src/main/resources/application.yml @@ -7,12 +7,16 @@ bonus: # 版权年份 copyrightYear: 2025 # 文件路径 示例( Windows配置D:/bonus/uploadPath,Linux配置 /home/bonus/uploadPath) - profile: D:/bonus/uploadPath + profile: E:/bonus/uploadPath # 获取ip地址开关 addressEnabled: false # 验证码类型 math 数字计算 char 字符验证 captchaType: math +sql: + filePath: E:/bonus/filePath + schemaName: smart_archives_dev + # 开发环境配置 server: # 服务器的HTTP端口,默认为8080 diff --git a/bonus-common/src/main/java/com/bonus/common/core/controller/BaseController.java b/bonus-common/src/main/java/com/bonus/common/core/controller/BaseController.java index 2e32887..00ba02f 100644 --- a/bonus-common/src/main/java/com/bonus/common/core/controller/BaseController.java +++ b/bonus-common/src/main/java/com/bonus/common/core/controller/BaseController.java @@ -23,7 +23,7 @@ import com.bonus.common.utils.sql.SqlUtil; /** * web层通用数据处理 - * + * * @author bonus */ public class BaseController @@ -46,7 +46,15 @@ public class BaseController } }); } - + protected TableDataInfo getDataTableBad(List list,String msg) + { + TableDataInfo rspData = new TableDataInfo(); + rspData.setCode(HttpStatus.SUCCESS); + rspData.setRows(list); + rspData.setMsg(msg); + rspData.setTotal(new PageInfo(list).getTotal()); + return rspData; + } /** * 设置请求分页数据 */ @@ -113,7 +121,7 @@ public class BaseController { return AjaxResult.success(message); } - + /** * 返回成功消息 */ @@ -140,7 +148,7 @@ public class BaseController /** * 响应返回结果 - * + * * @param rows 影响行数 * @return 操作结果 */ @@ -151,7 +159,7 @@ public class BaseController /** * 响应返回结果 - * + * * @param result 结果 * @return 操作结果 */ diff --git a/bonus-common/src/main/java/com/bonus/common/exception/SysWarning.java b/bonus-common/src/main/java/com/bonus/common/exception/SysWarning.java new file mode 100644 index 0000000..049b866 --- /dev/null +++ b/bonus-common/src/main/java/com/bonus/common/exception/SysWarning.java @@ -0,0 +1,39 @@ +package com.bonus.common.exception; + +import lombok.Data; + +@Data +public class SysWarning { + + public SysWarning(String warningId, String warningEvent, String warningIp, String operaUserName, String operaTime, String warningStatus){ + this.warningId = warningId; + this.warningEvent = warningEvent; + this.warningIp = warningIp; + this.operaUserName = operaUserName; + this.operaTime = operaTime; + this.warningStatus = warningStatus; + } + private String warningId; + + private String warningEvent = ""; + + private String warningIp = ""; + + private String operaUserName = ""; + + private String operaTime; + + private String warningStatus = "0"; + + @Override + public String toString() { + return "SysWarning{" + + "warningId='" + warningId + '\'' + + ", warningEvent='" + warningEvent + '\'' + + ", warningIp='" + warningIp + '\'' + + ", operaUserName='" + operaUserName + '\'' + + ", warningTime=" + operaTime + + ", warningStatus='" + warningStatus + '\'' + + '}'; + } +} diff --git a/bonus-common/src/main/java/com/bonus/common/exception/WaringLogEvent.java b/bonus-common/src/main/java/com/bonus/common/exception/WaringLogEvent.java new file mode 100644 index 0000000..4806ed2 --- /dev/null +++ b/bonus-common/src/main/java/com/bonus/common/exception/WaringLogEvent.java @@ -0,0 +1,20 @@ +package com.bonus.common.exception; + +import org.springframework.context.ApplicationEvent; + + +public class WaringLogEvent extends ApplicationEvent { + + private final SysWarning sysWarning; + + public WaringLogEvent(SysWarning logEvent) { + super(logEvent); + this.sysWarning = logEvent; + } + + + public SysWarning getSysWarning(){ + return this.sysWarning; + } + +} diff --git a/bonus-common/src/main/java/com/bonus/common/utils/FileUtils.java b/bonus-common/src/main/java/com/bonus/common/utils/FileUtils.java new file mode 100644 index 0000000..d8f6a87 --- /dev/null +++ b/bonus-common/src/main/java/com/bonus/common/utils/FileUtils.java @@ -0,0 +1,320 @@ +package com.bonus.common.utils; + +import com.bonus.common.utils.file.FileTypeUtils; +import com.bonus.common.utils.file.MimeTypeUtils; +import org.apache.commons.io.IOUtils; +import org.apache.commons.lang3.ArrayUtils; +import org.springframework.web.multipart.MultipartFile; + +import javax.servlet.ServletOutputStream; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.*; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.text.SimpleDateFormat; +import java.util.Base64; +import java.util.Date; +import java.util.UUID; + +/** + * 文件处理工具类 + * + * @author bonus + */ + +public class FileUtils { + /** + * 字符常量:斜杠 {@code '/'} + */ + public static final char SLASH = '/'; + + /** + * 字符常量:反斜杠 {@code '\\'} + */ + public static final char BACKSLASH = '\\'; + + public static String FILENAME_PATTERN = "[a-zA-Z0-9_\\-\\|\\.\\u4e00-\\u9fa5]+"; + + private static final String PARENT_DIRECTORY_INDICATOR = ".."; + private static final String MSIE_IDENTIFIER = "MSIE"; + private static final String FIREFOX_IDENTIFIER = "Firefox"; + private static final String CHROME_IDENTIFIER = "Chrome"; + + + + /** + * 输出指定文件的byte数组 + * + * @param filePath 文件路径 + * @param os 输出流 + */ + public static void writeBytes(String filePath, OutputStream os) throws IOException { + FileInputStream fis = null; + try { + File file = new File(filePath); + if (!file.exists()) { + throw new FileNotFoundException(filePath); + } + fis = new FileInputStream(file); + byte[] b = new byte[1024]; + int length; + while ((length = fis.read(b)) > 0) { + os.write(b, 0, length); + } + } catch (IOException e) { + throw e; + } finally { + if (os != null) { + try { + os.close(); + } catch (IOException e1) { + e1.printStackTrace(); + } + } + if (fis != null) { + try { + fis.close(); + } catch (IOException e1) { + e1.printStackTrace(); + } + } + } + } + + /** + * 删除文件 + * + * @param filePath 文件 + * @return + */ + public static boolean deleteFile(String filePath) { + boolean flag = false; + File file = new File(filePath); + // 路径为文件且不为空则进行删除 + if (file.isFile() && file.exists()) { + flag = file.delete(); + } + return flag; + } + + /** + * 文件名称验证 + * + * @param filename 文件名称 + * @return true 正常 false 非法 + */ + public static boolean isValidFilename(String filename) { + return filename.matches(FILENAME_PATTERN); + } + + /** + * 检查文件是否可下载 + * + * @param resource 需要下载的文件 + * @return true 正常 false 非法 + */ + public static boolean checkAllowDownload(String resource) { + // 禁止目录上跳级别 + if (StringUtils.contains(resource, PARENT_DIRECTORY_INDICATOR)) { + return false; + } + // 判断是否在允许下载的文件规则内 + return ArrayUtils.contains(MimeTypeUtils.DEFAULT_ALLOWED_EXTENSION, FileTypeUtils.getFileType(resource)); + } + + /** + * 下载文件名重新编码 + * + * @param request 请求对象 + * @param fileName 文件名 + * @return 编码后的文件名 + */ + public static String setFileDownloadHeader(HttpServletRequest request, String fileName) throws UnsupportedEncodingException { + final String agent = request.getHeader("USER-AGENT"); + String filename = fileName; + if (agent.contains(MSIE_IDENTIFIER)) { + // IE浏览器 + filename = URLEncoder.encode(filename, "utf-8"); + filename = filename.replace("+", " "); + } else if (agent.contains(FIREFOX_IDENTIFIER)) { + // 火狐浏览器 + filename = new String(fileName.getBytes(), "ISO8859-1"); + } else if (agent.contains(CHROME_IDENTIFIER)) { + // google浏览器 + filename = URLEncoder.encode(filename, "utf-8"); + } else { + // 其它浏览器 + filename = URLEncoder.encode(filename, "utf-8"); + } + return filename; + } + + /** + * 返回文件名 + * + * @param filePath 文件 + * @return 文件名 + */ + public static String getName(String filePath) { + if (null == filePath) { + return null; + } + int len = filePath.length(); + if (0 == len) { + return filePath; + } + if (isFileSeparator(filePath.charAt(len - 1))) { + // 以分隔符结尾的去掉结尾分隔符 + len--; + } + + int begin = 0; + char c; + for (int i = len - 1; i > -1; i--) { + c = filePath.charAt(i); + if (isFileSeparator(c)) { + // 查找最后一个路径分隔符(/或者\) + begin = i + 1; + break; + } + } + + return filePath.substring(begin, len); + } + + /** + * 是否为Windows或者Linux(Unix)文件分隔符
+ * Windows平台下分隔符为\,Linux(Unix)为/ + * + * @param c 字符 + * @return 是否为Windows或者Linux(Unix)文件分隔符 + */ + public static boolean isFileSeparator(char c) { + return SLASH == c || BACKSLASH == c; + } + + /** + * 下载文件名重新编码 + * + * @param response 响应对象 + * @param realFileName 真实文件名 + */ + public static void setAttachmentResponseHeader(HttpServletResponse response, String realFileName) throws UnsupportedEncodingException { + String percentEncodedFileName = percentEncode(realFileName); + + StringBuilder contentDispositionValue = new StringBuilder(); + contentDispositionValue.append("attachment; filename=") + .append(percentEncodedFileName) + .append(";") + .append("filename*=") + .append("utf-8''") + .append(percentEncodedFileName); + + response.setHeader("Content-disposition", contentDispositionValue.toString()); + response.setHeader("download-filename", percentEncodedFileName); + } + + /** + * 百分号编码工具方法 + * + * @param s 需要百分号编码的字符串 + * @return 百分号编码后的字符串 + */ + public static String percentEncode(String s) throws UnsupportedEncodingException { + String encode = URLEncoder.encode(s, StandardCharsets.UTF_8.toString()); + return encode.replaceAll("\\+", "%20"); + } + + + /** + * 生成统一格式的文件名 + * 格式为:uploads/yyyy/MM/dd/fileName + * + * @param fileName 原始文件名 + * @return 生成的文件名 + */ + public static String generateObjectName(String fileName) { + // 生成安全的文件名,例如使用UUID加上原始文件扩展名 + SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd"); + String datePath = sdf.format(new Date()); + return "uploads/" + datePath + "/" + fileName; + } + + /** + * 从文件路径中提取文件名 + * + * @param filePath 文件路径 + * @return 文件名 + */ + public static String getFileNameFromPath(String filePath) { + Path path = Paths.get(filePath); + return path.getFileName().toString(); + } + + /** + * 将MultipartFile转换为File对象 + * 在临时目录中创建文件,并将上传文件内容写入 + * + * @param multiFile MultipartFile对象 + * @return 转换后的File对象,如果转换失败返回null + */ + public static File multipartFileToFile(MultipartFile multiFile) { + try { + String fileName = multiFile.getOriginalFilename(); + if (fileName == null) { + return null; + } + String prefix = UUID.randomUUID().toString().replace("-", ""); + String suffix = fileName.substring(fileName.lastIndexOf(".")); + File file = File.createTempFile(prefix, suffix); + multiFile.transferTo(file); + return file; + } catch (Exception e) { + return null; + } + } + + /** + * 下载指定路径的文件 + * @param outputDir 要下载的文件的绝对路径 + * @param fileName 文件名 + * @param response HttpServletResponse对象 + * @throws IOException 可能抛出的IO异常 + */ + public static void fileDownload(String outputDir, String fileName, HttpServletResponse response) throws IOException { + try (FileInputStream is = new FileInputStream(new File(outputDir))) { + response.setContentType("text/html;charset=utf-8"); + response.setCharacterEncoding("UTF-8"); + response.setHeader("content-disposition", "attachment;filename=\"" + URLEncoder.encode(fileName, "utf-8") + "\""); + ServletOutputStream os = response.getOutputStream(); + IOUtils.copy(is, os); + } + } + + /** + * 将 MultipartFile 转换为带前缀的 Base64 编码 + * + * @param file 上传的文件 + * @return 带前缀的 Base64 编码的字符串 + * @throws IOException 如果读取文件时出错 + */ + public static String convertToBase64WithPrefix(MultipartFile file) throws IOException { + // 获取文件的字节数组 + byte[] fileBytes = file.getBytes(); + + // 获取文件的内容类型 (MIME Type) + String contentType = file.getContentType(); + + // 根据文件的 MIME 类型生成前缀 + String prefix = "data:" + contentType + ";base64,"; + + // 将文件字节数组转换为 Base64 字符串 + String base64Encoded = Base64.getEncoder().encodeToString(fileBytes); + + // 返回带前缀的 Base64 字符串 + return prefix + base64Encoded; + } +} diff --git a/bonus-system/src/main/java/com/bonus/system/mapper/SysLogMapper.java b/bonus-system/src/main/java/com/bonus/system/mapper/SysLogMapper.java new file mode 100644 index 0000000..bb54224 --- /dev/null +++ b/bonus-system/src/main/java/com/bonus/system/mapper/SysLogMapper.java @@ -0,0 +1,133 @@ +package com.bonus.system.mapper; + +import com.bonus.system.domain.SysLogsVo; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * @author:bonus + * @version:1.0 + * @description:系统日志-数据库访问层 + */ +public interface SysLogMapper { + + /** + * 保存系统日志 + * + * @param sysLog + * @description 保存系统日志 + * @author cwchen + * @date 2024/2/28 14:35 + */ + void saveLogs(SysLogsVo sysLog); + + /** + * 查询日志 + * @param dto + * @return + */ + List getSystemLogs(SysLogsVo dto); + + + /** + * 查询日志容量 + * @return + */ + String getLogsSet(); + + /** + * 数据插入 + * @param capacity + */ + void setLogsSet(String capacity); + + /** + * 更新数据 + * @param capacity + */ + void updateLogsSet(String capacity); + + /** + * 采茶戏 + * @param dto + * @return + */ + int getLogStatistics(SysLogsVo dto); + + /** + * 按照操作类型查询 + * @param dto + * @return + */ + List getLogsListByOperType(SysLogsVo dto); + + /** + * 按照操作人查询 + * @param dto + * @return + */ + List getLogsListByOperUserName(SysLogsVo dto); + + + /** + * 通过 方法查找模块 + * @param operUri + * @return + */ + SysLogsVo getModule(@Param("module") String operUri); + + /** + * 查询当日异常告警数量 + * @return + */ + int getErrorLogs(); + + /** + * 查询日志容量 + * @return + */ + String getLogsRl(String schemaName); + + /** + * 查询全部日志细腻系 + * @param dto + * @return + */ + List getAllLogs(SysLogsVo dto); + + /** + * 查询 + * @param sysLog + * @return + */ + String getModuleName(SysLogsVo sysLog); + + /** + * 检查模块是否存在 + * @param sysLog + * @return + */ + String getModuleIsc(SysLogsVo sysLog); + + /** + * 查询全部日志 + * @param logType + * @return + */ + List getLogsLists(@Param("logType") String logType); + + /** + * 查询未处理告警日志 + * @return + */ + List getNotHandleWarningLog(); + + /** + * 修改指定日志为已处理状态 + * @param logId 日志id + * @return + */ + void updateLogsWithHandledStatus(String logId); + +} diff --git a/bonus-system/src/main/java/com/bonus/system/service/ExportSqlService.java b/bonus-system/src/main/java/com/bonus/system/service/ExportSqlService.java new file mode 100644 index 0000000..7bfd3a0 --- /dev/null +++ b/bonus-system/src/main/java/com/bonus/system/service/ExportSqlService.java @@ -0,0 +1,93 @@ +package com.bonus.system.service; + +import com.bonus.common.utils.DateUtils; +import com.bonus.common.utils.global.SystemGlobal; +import com.bonus.system.domain.SysLogsVo; +import com.bonus.system.mapper.SysLogMapper; +import lombok.SneakyThrows; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Configuration; + +import javax.annotation.Resource; +import java.io.FileWriter; +import java.io.IOException; +import java.util.List; + +/** + * @author 黑子 + */ +@Configuration +@Slf4j +public class ExportSqlService { + + public static String TABLE_NAME = "sys_logs"; + /** + * 存储的路径 + */ + @Value("${sql.filePath}") + private String filePath; + + @Resource + private SysLogMapper mapper; + + + @SneakyThrows + public String export(String logType) { + + String dateTimeNow = DateUtils.dateTimeNow(); + String path = filePath + TABLE_NAME + dateTimeNow; + List list = mapper.getLogsLists(logType); + // 创建文件并写入 + try { + FileWriter writer = new FileWriter(path + SystemGlobal.TEXT_FIX); + list.forEach(logs -> { + try { + // 根据你的表结构生成你需要的SQL行 + String rowSql = "INSERT INTO sys_logs (log_id, opera_user_name,ip,user_id,model" + + ",oper_time,method,params,operate_detail,oper_type,oper_uri,log_type,result,times,failure_reason,grade,err_type,method_type,title,result_data) VALUES ('" + + isNotNull(logs.getLogId()) + "', '" + + isNotNull(logs.getOperaUserName()) + "', '" + + isNotNull(logs.getIp()) + "', '" + + isNotNull(logs.getUserId()) + "', '" + + isNotNull(logs.getModel()) + "', '" + + isNotNull(logs.getOperTime()) + "', '" + + isNotNull(logs.getMethod()) + "', '" + + isNotNull(logs.getParams()) + "', '" + + isNotNull(logs.getOperateDetail()) + "', '" + + isNotNull(logs.getOperType()) + "', '" + + isNotNull(logs.getOperUri()) + "', '" + + isNotNull(logs.getLogType()) + "', '" + + isNotNull(logs.getFailureReason()) + "', '" + + isNotNull(logs.getTimes()) + "', '" + + isNotNull(logs.getFailureReason()) + "', '" + + isNotNull(logs.getGrade()) + "', '" + + isNotNull(logs.getErrType()) + "', '" + + isNotNull(logs.getMethodType()) + "', '" + + isNotNull(logs.getTitle()) + "', '" + + isNotNull(logs.getResultData()) + "');"; + writer.write(rowSql + System.lineSeparator()); + } catch (IOException e) { + throw new RuntimeException(e); + } + + }); + + } catch (Exception e) { + log.error(e.toString(), e); + } + + return dateTimeNow; + } + + public String isNotNull(Object object) { + if (object == null) { + return ""; + } + return object.toString(); + + } + + +} + diff --git a/bonus-system/src/main/java/com/bonus/system/service/ISysLogService.java b/bonus-system/src/main/java/com/bonus/system/service/ISysLogService.java new file mode 100644 index 0000000..3fba477 --- /dev/null +++ b/bonus-system/src/main/java/com/bonus/system/service/ISysLogService.java @@ -0,0 +1,80 @@ +package com.bonus.system.service; + +import com.bonus.common.core.domain.AjaxResult; +import com.bonus.common.core.domain.R; +import com.bonus.system.domain.SysLogsVo; +import org.springframework.scheduling.annotation.Async; + +import javax.servlet.http.HttpServletRequest; +import java.util.List; +import java.util.Map; + +/** + * @author:cwchen + * @date:2024-02-28-14:01 + * @version:1.0 + * @description:系统日志-业务层 + */ +public interface ISysLogService { + /** + * 保存系统日志 + * + * @param sysLog + * @return AjaxResult + * @description + * @author cwchen + * @date 2024/2/28 14:33 + */ + AjaxResult saveLogs(SysLogsVo sysLog); + + /** + * 查询系统日志 + * @param dto + * @return + */ + List getSystemLogs(SysLogsVo dto); + + /** + * 查询日志容量信息 + * @return + */ + R getLogsSet(); + + /** + * 审计日志容量配置 + * @param capacity + * @return + */ + R setLogsSet(String capacity); + + /** + * 查询·日志统计 + * @param dto + * @return + */ + R> getLogStatistics(SysLogsVo dto); + + /** + *日志容量告警 + * @return + */ + void logWarn( ); + + /** + * 保存日志 + * @param sysLog + * @param request + * @return + */ + void saveLogs(SysLogsVo sysLog, HttpServletRequest request); + + /** + * 获取未处理的告警日志 + * @return + */ + @Async + public void handleWarningLog(); + + @Async + public void updateLogsWithHandledStatus(String logId); +} diff --git a/bonus-system/src/main/java/com/bonus/system/service/impl/SysLogServiceImpl.java b/bonus-system/src/main/java/com/bonus/system/service/impl/SysLogServiceImpl.java new file mode 100644 index 0000000..b645a57 --- /dev/null +++ b/bonus-system/src/main/java/com/bonus/system/service/impl/SysLogServiceImpl.java @@ -0,0 +1,330 @@ +package com.bonus.system.service.impl; + +import cn.hutool.core.collection.CollectionUtil; +import com.bonus.common.core.domain.AjaxResult; +import com.bonus.common.core.domain.R; +import com.bonus.common.core.domain.model.LoginUser; +import com.bonus.common.exception.SysWarning; +import com.bonus.common.exception.WaringLogEvent; +import com.bonus.common.utils.DateUtils; +import com.bonus.common.utils.SecurityUtils; +import com.bonus.common.utils.global.SystemGlobal; +import com.bonus.common.utils.ip.IpUtils; +import com.bonus.common.utils.uuid.IdUtils; +import com.bonus.system.domain.SysLogsVo; +import com.bonus.system.mapper.SysLogMapper; +import com.bonus.system.service.ISysLogService; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.transaction.interceptor.TransactionAspectSupport; + +import javax.annotation.Resource; +import javax.servlet.http.HttpServletRequest; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * @author:cwchen + * @date:2024-02-28-14:02 + * @version:1.0 + * @description:系统日志-业务逻辑层 + */ +@Service(value = "ISysLogService") +@Slf4j +public class SysLogServiceImpl implements ISysLogService { + + @Value("${sql.schemaName}") + private String schemaName; + + + @Resource + private SysLogMapper mapper; + + @Autowired + private ApplicationEventPublisher eventPublisher; + + @Override + @Transactional(rollbackFor = Exception.class) + public AjaxResult saveLogs(SysLogsVo sysLog) { + try { + + //如果是异常日志 + if (SystemGlobal.LOG_ERR.equals(sysLog.getErrType()) && StringUtils.isEmpty(sysLog.getModel())) { + SysLogsVo sysLog1 = mapper.getModule(sysLog.getOperUri()); + if (sysLog1 != null) { + sysLog.setModel(sysLog1.getModel()); + sysLog.setOperateDetail(sysLog1.getOperateDetail()); + } else { + if (StringUtils.isEmpty(sysLog1.getModel())) { + sysLog.setModel("未知的请求模块"); + } + } + } + if (sysLog.getLogType() == 2) { + sysLog.setWarningStatus("0"); + } + if (sysLog.getOperaUserName() != null) { + String str = sysLog.getOperaUserName().replace("\\", "\\\\").replace("%", "\\%").replace("_", "\\_"); + sysLog.setOperaUserName(str); + } + if (sysLog.getIp() != null) { + String str = sysLog.getIp().replace("\\", "\\\\").replace("%", "\\%").replace("_", "\\_"); + sysLog.setIp(str); + } + mapper.saveLogs(sysLog); + if (sysLog.getLogType() == 2) { + eventPublisher.publishEvent(new WaringLogEvent(new SysWarning(sysLog.getLogId(), sysLog.getErrType(), sysLog.getIp(), sysLog.getOperaUserName(), sysLog.getOperTime(), "0"))); + } + } catch (Exception e) { + log.error("保存系统日志"); + TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(); + } + return null; + } + + @Override + public void saveLogs(SysLogsVo sysLog, HttpServletRequest request) { + try { + String loginUuid = IdUtils.fastUUID(); + String ip = IpUtils.getIpAddr(request); + sysLog.setIp(ip); + sysLog.setLogId(loginUuid); + sysLog.setGrade("高"); + sysLog.setErrType("越权访问"); + sysLog.setFailureReason("页面未授权"); + sysLog.setOperType("查询"); + sysLog.setOperateDetail("查看页面"); + String module = mapper.getModuleName(sysLog); + sysLog.setLogType(2); + sysLog.setResult("失败"); + sysLog.setOperTime(DateUtils.getTime()); + sysLog.setModel(module); + LoginUser user = SecurityUtils.getLoginUser(); + sysLog.setUserId(String.valueOf(user.getUserId())); + sysLog.setOperaUserName(user.getUsername()); + + String modules = mapper.getModuleIsc(sysLog); + if (StringUtils.isNotEmpty(module)) { + if (StringUtils.isEmpty(module)) { + sysLog.setModel(modules); + } + } + if (sysLog.getLogType() == 2) { + sysLog.setWarningStatus("0"); + } + if (sysLog.getOperaUserName() != null) { + String str = sysLog.getOperaUserName().replace("\\", "\\\\").replace("%", "\\%").replace("_", "\\_"); + sysLog.setOperaUserName(str); + } + if (sysLog.getIp() != null) { + String str = sysLog.getIp().replace("\\", "\\\\").replace("%", "\\%").replace("_", "\\_"); + sysLog.setIp(str); + } + mapper.saveLogs(sysLog); + if (sysLog.getLogType() == 2) { + eventPublisher.publishEvent(new WaringLogEvent(new SysWarning(loginUuid, "越权访问", ip, user.getUsername(), DateUtils.getTime(), "0"))); + } + } catch (Exception e) { + log.error(e.toString(), e); + } + } + + @Override + public List getSystemLogs(SysLogsVo dto) { + try { + if (StringUtils.isEmpty(dto.getOperTime())) { + dto.setStartTime(DateUtils.dateTimeNow()); + dto.setEndTime(DateUtils.dateTimeNow()); + } else { + dto.setStartTime(dto.getOperTime().split(" - ")[0].trim()); + dto.setEndTime(dto.getOperTime().split(" - ")[1].trim()); + } + return mapper.getSystemLogs(dto); + } catch (Exception e) { + log.error(e.toString(), e); + } + return new ArrayList<>(); + } + + /** + * @return + */ + @Override + public R getLogsSet() { + try { + String capacity = mapper.getLogsSet(); + if (StringUtils.isEmpty(capacity)) { + return R.ok("1"); + } + return R.ok(capacity); + } catch (Exception e) { + log.error(e.toString(), e); + } + return R.ok(SystemGlobal.LOG_DEFEAT_SIZE + ""); + } + + /** + * 系统日志 调试 + * + * @param capacity + * @return + */ + @Override + public R setLogsSet(String capacity) { + try { + if (StringUtils.isNotEmpty(capacity)) { + if (SystemGlobal.ERR_NUM.equals(capacity.toUpperCase())) { + return R.fail("请输入数字"); + } + } else { + return R.fail("日志容量最低是" + SystemGlobal.LOG_DEFEAT_SIZE + "GB"); + } + + Double cap = Double.parseDouble(capacity); + if (cap < SystemGlobal.LOG_DEFEAT_SIZE) { + return R.fail("日志容量最低是" + SystemGlobal.LOG_DEFEAT_SIZE + "GB"); + } + } catch (Exception e) { + log.error(e.toString(), e); + return R.fail("请输入数字"); + } + try { + String city = mapper.getLogsSet(); + if (StringUtils.isEmpty(city)) { + mapper.setLogsSet(capacity); + } else { + mapper.updateLogsSet(capacity); + } + } catch (Exception e) { + log.error(e.toString(), e); + return R.fail("系统异常"); + } + return R.ok("设置成功", "设置成功"); + } + + public final static String SUCCESS = "成功"; + + /** + * @param dto + * @return + */ + @Override + public R> getLogStatistics(SysLogsVo dto) { + Map map = new HashMap<>(); + try { + if (StringUtils.isNotEmpty(dto.getOperTime())) { + dto.setStartTime(dto.getOperTime().split(" - ")[0].trim()); + dto.setEndTime(dto.getOperTime().split(" - ")[1].trim()); + } + List all = mapper.getAllLogs(dto); + //先按操作结果查询 + if (CollectionUtil.isNotEmpty(all)) { + int allNum = all.stream().mapToInt(SysLogsVo::getNum).sum(); + map.put("allNum", allNum); + all.forEach(vo -> { + if (vo.getResult().contains("成功")) { + map.put("sNum", vo.getNum()); + } else { + map.put("eNum", vo.getNum()); + } + }); + } + + if (SystemGlobal.LOG_TYPE.equals(dto.getType())) { + //按日志类型查询 + List type = new ArrayList<>(); + List typeList = new ArrayList<>(); + typeList.add("系统日志"); + typeList.add("业务日志"); + typeList.add("异常日志"); + type.add(0); + type.add(1); + type.add(2); + List nums = new ArrayList<>(); + type.forEach(str -> { + dto.setLogType(str); + int num = mapper.getLogStatistics(dto); + nums.add(num); + }); + map.put("list", typeList); + map.put("nums", nums); + } else if ("2".equals(dto.getType())) { + //按操作类型查询 + List typeList = new ArrayList<>(); + List nums = new ArrayList<>(); + List list = mapper.getLogsListByOperType(dto); + list.forEach(vo -> { + typeList.add(vo.getOperType()); + nums.add(vo.getNum()); + }); + map.put("list", typeList); + map.put("nums", nums); + } else if ("3".equals(dto.getType())) { + List typeList = new ArrayList<>(); + List nums = new ArrayList<>(); + List list = mapper.getLogsListByOperUserName(dto); + list.forEach(vo -> { + typeList.add(vo.getOperaUserName()); + nums.add(vo.getNum()); + }); + map.put("list", typeList); + map.put("nums", nums); + } + return R.ok(map); + } catch (Exception e) { + log.error(e.toString(), e); + } + return R.ok(map); + } + + /** + * 日志容量告警 + * + * @return + */ + @Override + public void logWarn() { + try { + double bfb = 0.9; + String rl = mapper.getLogsRl(schemaName); + if (rl == null) { + rl = "0"; + } + String city = mapper.getLogsSet(); + Double d = Double.parseDouble(rl); + Double max = Double.parseDouble(city) * bfb; + if (d >= max) { + String warningEvent = "日志容量告警,当日日志内存为" + d + "GB,日志内存超过总内存的90%,请及时处理!"; + eventPublisher.publishEvent(new WaringLogEvent(new SysWarning("0", warningEvent, "", null, null, "1"))); + } + } catch (Exception e) { + log.error(e.toString(), e); + } + } + + @Override + @Async + public void handleWarningLog() { + List list = mapper.getNotHandleWarningLog(); + // 使用for-each循环遍历List + for (SysLogsVo item : list) { + eventPublisher.publishEvent(new WaringLogEvent(new SysWarning(item.getLogId(), item.getErrType(), item.getIp(), item.getOperaUserName(), item.getOperTime(), "0"))); + log.info("*****系统管理员和审计管理员处理异常日志*******"); + } + } + + @Override + @Async + public void updateLogsWithHandledStatus(String logId) { + mapper.updateLogsWithHandledStatus(logId); + } + +} diff --git a/bonus-system/src/main/resources/mapper/system/SysLogMapper.xml b/bonus-system/src/main/resources/mapper/system/SysLogMapper.xml new file mode 100644 index 0000000..4cc94d5 --- /dev/null +++ b/bonus-system/src/main/resources/mapper/system/SysLogMapper.xml @@ -0,0 +1,283 @@ + + + + + + + insert into da_ky_sys_logs( + log_id, + opera_user_name, + ip, + user_id, + model, + oper_time, + method, + params, + operate_detail, + oper_type, + oper_uri, + log_type, + result, + times, + failure_reason, + grade, + err_type, + method_type, + title, + result_data, + warning_status, + )values ( + {logId}, + {operaUserName}, + {ip}, + {userId}, + {model}, + {operTime}, + {method}, + {params}, + {operateDetail}, + {operaType}, + {operUri}, + {logType}, + {result}, + {times}, + {failureReason}, + {grade}, + {errType}, + {methodType}, + {title}, + {resultData}, + {warningStatus}, + + + + update sys_warning + set warning_status = #{warningStatus} + where log_id = #{logId} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + insert into da_ky_sys_logs_set(capacity) value(#{capacity}) + + + + update da_ky_sys_logs_set set capacity=#{capacity} + + + + + + + + + + + + + + + + + + + + + + + + update da_ky_sys_logs set warning_status=1 where log_id=#{logId} + + + +