AOP扫盲:Spring AOP (面向切面编程)原理与代理模式—实例演示
logs表:
CREATE TABLE `logs` (`id` int(11) NOT NULL AUTO_INCREMENT,`operation` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '操作名称',`type` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '操作类型',`ip` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT 'ip地址',`user` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '操作人',`time` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '操作时间',PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='系统日志';
aop依赖:
<!-- aop -->
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId>
</dependency>
获取IP地址工具类 IpUtils.java:
import javax.servlet.http.HttpServletRequest;public class IpUtils {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;}}
自定义注解 @HoneyLogs:
import java.lang.annotation.*;@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface HoneyLogs {// 操作的模块String operation();// 操作类型String type();
}
切面 LogAspect.java:
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.thread.ThreadUtil;
import cn.hutool.core.util.ArrayUtil;
import com.example.springboot.common.HoneyLogs;
import com.example.springboot.entity.Logs;
import com.example.springboot.entity.User;
import com.example.springboot.service.LogsService;
import com.example.springboot.utils.IpUtils;
import com.example.springboot.utils.TokenUtils;
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;import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;@Component
@Aspect
@Slf4j
public class LogsAspect {@ResourceLogsService logsService;@AfterReturning(pointcut = "@annotation(honeyLogs)", returning = "jsonResult")public void recordLog(JoinPoint joinPoint, HoneyLogs honeyLogs, Object jsonResult) {// 获取当前登录的用户的信息User loginUser = TokenUtils.getCurrentUser();if (loginUser == null) { // 用户未登录的情况下 loginUser是null 是null的话我们就要从参数里面获取操作人信息// 登录、注册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 servletRequestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();HttpServletRequest request = servletRequestAttributes.getRequest();// 获取到请求的ipString ip = IpUtils.getIpAddr(request);Logs logs = Logs.builder().operation(honeyLogs.operation()).type(honeyLogs.type()).ip(ip).user(loginUser.getUsername()).time(DateUtil.now()).build();ThreadUtil.execAsync(() -> {// 异步记录日志信息logsService.save(logs);});}}
Logs.java
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class Logs {@TableId(type = IdType.AUTO)private Integer id;private String operation;private String type;private String ip;private String user;private String time;
}
LogsController.java
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.example.springboot.common.Result;
import com.example.springboot.entity.Logs;
import com.example.springboot.service.LogsService;
import com.example.springboot.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;import java.util.List;/*** 系统日志相关接口*/
@RestController
@RequestMapping("/logs")
public class LogsController {@AutowiredLogsService logsService;@AutowiredUserService userService;/*** 删除信息*/@DeleteMapping("/delete/{id}")public Result delete(@PathVariable Integer id) {logsService.removeById(id);return Result.success();}/*** 批量删除信息*/@DeleteMapping("/delete/batch")public Result batchDelete(@RequestBody List<Integer> ids) {logsService.removeBatchByIds(ids);return Result.success();}/*** 多条件模糊查询信息* pageNum 当前的页码* pageSize 每页查询的个数*/@GetMapping("/selectByPage")public Result selectByPage(@RequestParam Integer pageNum,@RequestParam Integer pageSize,@RequestParam String operation) {QueryWrapper<Logs> queryWrapper = new QueryWrapper<Logs>().orderByDesc("id"); // 默认倒序,让最新的数据在最上面queryWrapper.like(StrUtil.isNotBlank(operation), "operation", operation);Page<Logs> page = logsService.page(new Page<>(pageNum, pageSize), queryWrapper);return Result.success(page);}}
前端:
Logs.vue:
<template><div><div><el-input style="width: 200px" placeholder="查询模块" v-model="operation"></el-input><el-select style="margin: 0 5px" v-model="type"><el-option v-for="item in ['新增', '修改', '删除']" :key="item" :value="item" :label="item"></el-option></el-select><el-input style="width: 200px" placeholder="查询操作人" v-model="optUser"></el-input><el-button type="primary" style="margin-left: 10px" @click="load(1)">查询</el-button><el-button type="info" @click="reset">重置</el-button></div><div style="margin: 10px 0"><el-button type="danger" plain @click="delBatch">批量删除</el-button></div><el-table :data="tableData" stripe :header-cell-style="{ backgroundColor: 'aliceblue', color: '#666' }"@selection-change="handleSelectionChange"><el-table-column type="selection" width="55" align="center"></el-table-column><el-table-column prop="id" label="序号" width="70" align="center"></el-table-column><el-table-column prop="operation" label="操作模块"></el-table-column><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><el-table-column prop="user" label="操作人"></el-table-column><el-table-column prop="time" label="操作时间"></el-table-column><el-table-column label="操作" align="center" width="180"><template v-slot="scope"><el-button size="mini" type="danger" plain @click="del(scope.row.id)">删除</el-button></template></el-table-column></el-table><div style="margin: 10px 0"><el-pagination@current-change="handleCurrentChange":current-page="pageNum":page-size="pageSize"layout="total, prev, pager, next":total="total"></el-pagination></div></div>
</template><script>export default {name: "Logs",data() {return {tableData: [], // 所有的数据pageNum: 1, // 当前的页码pageSize: 5, // 每页显示的个数operation: '',total: 0,form: {},user: JSON.parse(localStorage.getItem('honey-user') || '{}'),ids: [],type: '',optUser: ''}},created() {this.load()},methods: {delBatch() {if (!this.ids.length) {this.$message.warning('请选择数据')return}this.$confirm('您确认批量删除这些数据吗?', '确认删除', {type: "warning"}).then(response => {this.$request.delete('/logs/delete/batch', {data: this.ids}).then(res => {if (res.code === '200') { // 表示操作成功this.$message.success('操作成功')this.load(1)} else {this.$message.error(res.msg) // 弹出错误的信息}})}).catch(() => {})},handleSelectionChange(rows) { // 当前选中的所有的行数据this.ids = rows.map(v => v.id)},del(id) {this.$confirm('您确认删除吗?', '确认删除', {type: "warning"}).then(response => {this.$request.delete('/logs/delete/' + id).then(res => {if (res.code === '200') { // 表示操作成功this.$message.success('操作成功')this.load(1)} else {this.$message.error(res.msg) // 弹出错误的信息}})}).catch(() => {})},reset() {this.operation = ''this.type = ''this.optUser = ''this.load()},load(pageNum) { // 分页查询if (pageNum) this.pageNum = pageNumthis.$request.get('/logs/selectByPage', {params: {pageNum: this.pageNum,pageSize: this.pageSize,operation: this.operation,type: this.type,user: this.optUser,}}).then(res => {this.tableData = res.data.recordsthis.total = res.data.total})},handleCurrentChange(pageNum) {this.load(pageNum)},}
}
</script><style>
.el-tooltip__popper {max-width: 300px !important;
}
</style>