springboot+vue前后端分离项目-项目搭建17-集成AOP系统日志

news/2025/3/19 2:35:32/文章来源:https://www.cnblogs.com/xiexieyc/p/18341607

后端

1. 新增logs表和实体类,新增com/example/demo/mapper/LogsMapper.java,新增com/example/demo/controller/LogsController.java

package com.example.demo.controller;import cn.hutool.core.util.StrUtil;
import cn.hutool.poi.excel.ExcelReader;
import cn.hutool.poi.excel.ExcelUtil;
import cn.hutool.poi.excel.ExcelWriter;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.example.demo.common.AuthAccess;
import com.example.demo.common.Result;
import com.example.demo.entity.Logs;
import com.example.demo.mapper.LogsMapper;
import jakarta.annotation.Resource;
import jakarta.servlet.ServletOutputStream;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.web.bind.annotation.*;import java.io.IOException;
import java.net.URLEncoder;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;@RestController
@RequestMapping("/logs")
public class LogsController {//正常Mapper是在Service里引用,Controllerl里引用Service,本案例是为了方便调用,非正规操作
    @ResourceLogsMapper logsMapper;@PutMappingpublic Result<?> update(@RequestBody Logs logs){logsMapper.updateById(logs);return Result.success();}@DeleteMapping("/{id}")public Result<?> delete(@PathVariable Long id){logsMapper.deleteById(id);return Result.success();}@PostMapping("/deleteBatch") //  批量删除public Result<?> deleteBatch(@RequestBody List<Integer> ids){logsMapper.deleteBatchIds(ids);return Result.success();}@GetMappingpublic Result<?> findPage(@RequestParam(defaultValue = "1") Integer pageNum,@RequestParam(defaultValue = "10") Integer pageSize,@RequestParam(defaultValue = "") String search,@RequestParam(defaultValue = "") String type){LambdaQueryWrapper<Logs> wrapper = Wrappers.<Logs>lambdaQuery();if(StrUtil.isNotBlank(search)){wrapper.like(Logs::getOperation, search);}if(StrUtil.isNotBlank(type)){wrapper.like(Logs::getType, type);}Page<Logs> logsPage = logsMapper.selectPage(new Page<>(pageNum, pageSize), wrapper);return Result.success(logsPage);}//批量导出
    @AuthAccess@GetMapping("/export")public void exportData(@RequestParam(required = false) String search,@RequestParam(required = false) String ids,  // 1,2,3,4
                           HttpServletResponse response) throws IOException {List<Logs> list;QueryWrapper<Logs> queryWrapper = new QueryWrapper<>();if (StrUtil.isNotBlank(ids)) {  //  第二种按选择的行导出List<Integer> idsArr = Arrays.stream(ids.split(",")).map(Integer::valueOf).collect(Collectors.toList());queryWrapper.in("id", idsArr);} else {  // 第一种全部导出或条件导出queryWrapper.like(StrUtil.isNotBlank(search),"operation", search);}list = logsMapper.selectList(queryWrapper);ExcelWriter writer = ExcelUtil.getWriter(true);writer.write(list, true);//设置响应文件类型,设置响应文件名称response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=utf-8");response.setHeader("Content-Disposition","attachment;filename=" + URLEncoder.encode("操作日志","utf-8") + ".xlsx");//导出数据写到响应的输出流里,关闭流ServletOutputStream outputStream = response.getOutputStream();writer.flush(outputStream, true);writer.close();outputStream.flush();outputStream.close();}
}

 

2. 新增 com/example/demo/common/HoneyLogs.java 注解,controller的方法上增加这个注解,让它被aop识别,进行日志记录

package com.example.demo.common;import java.lang.annotation.*;@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface HoneyLogs {// 操作的模块
    String operation();// 操作类型
    LogType type();
}

3. 新增 com/example/demo/common/LogType.java 枚举类 ,系统日志的操作类型

package com.example.demo.common;/*** 系统日志的操作类型枚举*/
public enum LogType {ADD("新增"),UPDATE("修改"),DELETE("删除"),BATCH_DELETE("批量删除"),LOGIN("登录"),REGISTER("注册");private String value;public String getValue() {return value;}LogType(String value) {this.value = value;}
}

4. 新增 com/example/demo/utils/IpUtils.java ,获取用户的ip地址

package com.example.demo.utils;import jakarta.servlet.http.HttpServletRequest;public class IpUtils {/*** 获取IP地址* @param request* @return*/public static String getIpAddr(HttpServletRequest request) {String ip = request.getHeader("x-forwarded-for");if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {ip = request.getHeader("Proxy-Client-IP");}if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {ip = request.getHeader("X-Forwarded-For");}if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {ip = request.getHeader("WL-Proxy-Client-IP");}if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {ip = request.getHeader("X-Real-IP");}if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {ip = request.getRemoteAddr();}return "0:0:0:0:0:0:0:1".equals(ip) ? "127.0.0.1" : ip;}
}

5. 新增 com/example/demo/Service/aop/LogsAspect.java ,切面拦截所有添加了 HoneyLogs 注解的方法,记录日志到数据库日志表

package com.example.demo.Service.aop;import cn.hutool.core.date.DateUtil;
import cn.hutool.core.thread.ThreadUtil;
import cn.hutool.core.util.ArrayUtil;
import com.example.demo.common.HoneyLogs;
import com.example.demo.entity.Logs;
import com.example.demo.entity.User;
import com.example.demo.mapper.LogsMapper;
import com.example.demo.utils.IpUtils;
import com.example.demo.utils.TokenUtils;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;@Component
@Aspect
@Slf4j
public class LogsAspect {@ResourceLogsMapper logsMapper;@AfterReturning(pointcut = "@annotation(honeyLogs)", returning = "jsonResult")public void recordLog(JoinPoint joinPoint, HoneyLogs honeyLogs, Object jsonResult) {User loginUser = TokenUtils.getCurrentUser();//获取当前登陆的用户信息if (loginUser == null) { // 用户未登录时,从参数里获取操作人信息,使用joinPoint可以获取参数// 登录、注册Object[] args = joinPoint.getArgs();if (ArrayUtil.isNotEmpty(args)) {if (args[0] instanceof User) {loginUser = (User) args[0];}}}if (loginUser == null) {log.error("记录日志信息错误,未获取到当前操作用户信息");return;}// 获取 HttpServletRequest 对象ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();HttpServletRequest request = requestAttributes.getRequest();// 获取 ip 地址String ipAddr = IpUtils.getIpAddr(request);// 组装日志的实体对象Logs logs = Logs.builder().operation(honeyLogs.operation()).type(honeyLogs.type().getValue()).ip(ipAddr).user(loginUser.getUsername()).time(DateUtil.now()).build();// 插入到数据库, 通过 ThreadUtil 工具类异步插入,这步失败不影响业务进程ThreadUtil.execAsync(() -> {logsMapper.insert(logs);});}
}

6. 在 com/example/demo/controller/BookController.java的增删改方法上增加HoneyLogs注解的使用,并定义操作模块和类型

在 com/example/demo/controller/UserController.java的登录注册方法上增加HoneyLogs注解的使用,并定义操作模块和类型

 

 

前端

1. 新增 vue/src/views/Logs.vue 

<template><div style="width: 100%; padding: 10px">
<!--    功能区--><div style="margin: 10px 0"><el-popconfirm title="确定删除吗" @confirm="deleteBatch"><template #reference><el-button type="danger">批量删除</el-button></template></el-popconfirm><el-button type="info" plain @click="exportData">批量导出</el-button></div>
<!--    搜索区--><div style="display: flex; margin: 10px 0"><el-input v-model="search" placeholder="查询模块" style="width: 20%" clearable></el-input><el-select style="width: 20%; margin: 0 10px;" v-model="type" clearable><el-option v-for="item in ['新增','修改','删除']" :key="item" :value="item" :label="item"></el-option></el-select><el-button type="primary" style="margin-left: 10px" @click="load">查询</el-button></div><el-table :data="tableData" border stripe @selection-change="handleSelectionChange"><el-table-column type="selection" width="55"></el-table-column><el-table-column prop="id" label="ID"sortable/><el-table-column prop="operation" label="操作模块" /><el-table-column prop="type" label="操作类型" ><template v-slot="scope"><el-tag type="primary" v-if="scope.row.type === '新增'">{{ scope.row.type }}</el-tag><el-tag type="info" v-if="scope.row.type === '修改'">{{ scope.row.type }}</el-tag><el-tag type="danger" v-if="scope.row.type === '删除'">{{ scope.row.type }}</el-tag><el-tag type="danger" v-if="scope.row.type === '批量删除'">{{ scope.row.type }}</el-tag><el-tag type="success" v-if="scope.row.type === '登录'">{{ scope.row.type }}</el-tag><el-tag type="success" v-if="scope.row.type === '注册'">{{ scope.row.type }}</el-tag></template></el-table-column><el-table-column prop="ip" label="ip地址" /><el-table-column prop="user" label="操作人" /><el-table-column prop="time" label="操作时间" /><el-table-column fixed="right" label="操作" min-width="120"><template #default="scope"><el-button link type="primary" size="small" @click="handleEdit(scope.row)">编辑</el-button><el-popconfirm title="确认删除吗?" @confirm="handleDelete(scope.row.id)"><template #reference><el-button link type="primary" size="small">删除</el-button></template></el-popconfirm></template></el-table-column></el-table><div style="margin: 10px 0"><el-paginationv-model:current-page="currentPage"v-model:page-size="pageSize":page-sizes="[10, 20, 50, 100]"layout="total, sizes, prev, pager, next, jumper":total="total"@size-change="handleSizeChange"@current-change="handleCurrentChange"/></div><el-dialog v-model="dialogVisible" title="操作日志" width="30%"><el-form :label-position="labelPosition"  label-width="auto" :model="form" style="width: 600px"><el-form-item label="操作模块"><el-date-picker v-model="form.operation" type="date" style="width: 80%" clearable></el-date-picker></el-form-item><el-form-item label="操作类型"><el-input v-model="form.type" style="width: 80%"></el-input></el-form-item><el-form-item label="操作人ip"><el-input v-model="form.ip" style="width: 80%"></el-input></el-form-item><el-form-item label="操作人"><el-input v-model="form.user" style="width: 80%"></el-input></el-form-item><el-form-item label="操作时间"><el-input v-model="form.time" style="width: 80%"></el-input></el-form-item></el-form><template #footer><div class="dialog-footer"><el-button @click="dialogVisible = false">取 消</el-button><el-button type="primary" @click="save()">确 定</el-button></div></template></el-dialog></div>
</template><script>import request from "@/utils/request";export default {name: 'Logs',components: {},data() {return {user: {},form: {},dialogVisible: false,search: '',currentPage: 1,pageSize: 10,total: 0,tableData: [],ids: [],type: ''}},created() {this.load()let userStr = localStorage.getItem("user") || {}this.user = JSON.parse(userStr)request.get("/user/" + this.user.id).then(res => {if (res.data === '0'){this.user = res.data}})},methods: {exportData(){  //批量导出if(!this.ids.length){  //没选择行时导出全部 或 根据我的搜索条件导出window.open('http://localhost:9090/logs/export?search=' + this.search)} else {let idStr = this.ids.join(',')  //  [1,2,3] => '1,2,3'window.open('http://localhost:9090/logs/export?ids=' + idStr)}},deleteBatch(){  //批量删除if(!this.ids.length){this.$message.warning("请选择数据!")return}request.post("/logs/deleteBatch", this.ids).then(res => {if(res.code === '200'){this.$message.success("批量删除成功")this.load()} else {this.$message.error(res.msg)}})},handleSelectionChange(val){  //多选后将选择的id存到ids数组中this.ids = val.map(v => v.id)   //[{id,name},{id,name}] => [id,id]
    },load() {request.get("/logs", {params:{pageNum: this.currentPage,pageSize: this.pageSize,search: this.search,type: this.type}}).then(res=>{console.log(res)this.tableData = res.data.recordsthis.total = res.data.total})},save(){request.put("/logs", this.form).then(res => {console.log(res)if (res.code === '200') {this.$message({type: "success",message: "更新成功"})} else {this.$message({type: "error",message: "res.msg"})}this.load()      //更新后刷新表格数据this.dialogVisible = false   //关闭弹窗
      })},handleEdit(row) {this.form = JSON.parse(JSON.stringify(row))this.dialogVisible = true},handleDelete(id) {console.log(id)request.delete("/logs/" + id).then(res => {if(res.code === '200'){this.$message({type: "success",message: "删除成功"})}else {this.$message({type: "error",message: "res.msg"})}this.load()      //删除后刷新表格数据
      })},handleSizeChange() {     //改变当前每页个数触发this.load()},handleCurrentChange() {  //改变当前页码触发this.load()}}
}</script>

2. 改造 vue/src/router/index.js 和 vue/src/components/Aside.vue

 3. 测试效果,登录、书籍管理中的修改、删除等操作都被记录

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/777641.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

8.2日CSP-J初赛内容总结

8.2日CSP-J初赛内容总结Adobe:PS,PR,......Reader 微软:Onedrive(存文件),Excel(表格),Word(文字编辑),Onenote(笔记),PowerPoint(PPT)位号从正数部分最低位开始编号,0到更大的数字。 位号从左往右的小数部分从 \(-1\) 开始编号,编号变小基数:进制的进位数字 位权:基数的…

8.3日CSP-J初赛内容总结

8.3日CSP-J初赛内容总结 优先级 \(括号>非>与>或\) \(括号>逻辑运算>位运算\) \(括号>按位取反>按位与>按位或=按位异或\) 按位与或非 \(\to\) 补码按位取反补码所有位取反 按位与将 \(2\) 个补码对其地位 逐位比较1的个数基本上等于 \(n\) 除 \(2\) 的…

初学java4

这周我重新下载了jdk-17并更改了路径,目的是为了添加新的环境变量用于使用eclipse。 eclipse for java作为老牌java编程所需的软件,很值得我学习使用,不过下载以及环境变量的准备有些麻烦。 下载成功后使用起来就很方便了。

问题:ModuleNotFoundError: No module named pydotplus

无法找到pydotplus模块在 Anaconda propmt中安装:pip install pydotplus

使用PasteSpider实现类似Jenkins的功能,让你的2G服务器也可以飞起

或许你接触过Jenkins, 在我理解就是拉取源码,然后构建成镜像,最后启动容器! 但是这个功能对于小内存的服务器来说就是奢望了! 今天介绍一个新版本,把你这个遗憾弥补下! 在PasteSpider中,也是支持拉取源码,然后编译发布的!!! 以下案例使用svn作为源码管理 如果你使用…

D37 2-SAT P3007 [USACO11JAN] The Continental Cowngress G

视频链接:D37 2-SAT P3007 [USACO11JAN] The Continental Cowngress G_哔哩哔哩_bilibili P3007 [USACO11JAN] The Continental Cowngress G - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)// O(n*n) #include <iostream> #include <cstring> #include <algo…

Java 文件 I/O流详解

文件文件操作是Java开发中一个重要的组成部分,它允许开发者对文件进行读取,写入,创建,删除和修改等操作,文件操作的主要通过java.io包中的类来实现的,其中的File类更是文件操作的核心类File类的常用方法 创建文件或目录文件创建使用createNewFile();可以创建一个新的空文件,如果…

kubelet节点资源预留

目录一、Node Allocatable1、node资源预留1.1 为什么要做资源预留?1.2 node allocatable1.2.1 查看node节点资源1.2.2 确认node01节点资源2、 配置资源预留2.1 kube预留值2.2 systemReserved预留2.3 evictionHard预留2.4 整体配置2.5 重启服务2.6 Allocatable资源说明 一、N…

mysql 是否该数据列每个数据都唯一就应该设置唯一索引?

前言 比较一下唯一索引和普通索引的区别。 如果有一列数据唯一,这个时候是否是就直接设置唯一索引,这样可以避免插入重复的值,来实现业务需求。 那么唯一索引是如何保持唯一的呢?这个对性能是否有影响。 正文 数据库我们知道是增删改查。 那么首先来看下这个查,唯一索引是…

Markdown 达人必备!轻松几步画出专业流程图

流程图,顾名思义,就是表示一个事件或活动的流程的图示。流程图,顾名思义,就是表示一个事件或活动的流程的图示。 ‍ ‍ 快速入门 最简单的例子:从 A 到 B graph TDA --> B‍ 效果: graph TDA --> B ‍ ‍ 注意起始的关键字“grpah”是必须的,表明这是流程图。 后续…

CVE-2023-1313 复现

CVE-2023-1313 复现cockpit在2.4.1版本之前存在任意文件上传漏洞PS:通过在浏览器中打开/install来运行安装首先看到了一个登录页面.我们按照提示访问/install,成功的添加了用户名和密码都为admin,登录. 来到如下界面,可以进行任意文件上传然后点击三个点可以选择下载文件,将得…

使用 addRouteMiddleware 动态添加中间

title: 使用 addRouteMiddleware 动态添加中间 date: 2024/8/4 updated: 2024/8/4 author: cmdragon excerpt: 摘要:文章介绍了Nuxt3中addRouteMiddleware的使用方法,该功能允许开发者动态添加路由中间件,以实现诸如权限检查、动态重定向及路由变化时的特定操作。内容涵盖…