SpringBoot+AOP+自定义注解,优雅实现日志记录
文章目录
- 前言
- 准备阶段
- 1、数据库日志表
- 2、自定义注解编写
- 3、AOP切面类编写
- 4、业务层
- 4.1、Service 层:
- 4.2 Service 实现层:
- 5、测试
前言
首先我们看下传统记录日志的方式是什么样的:
@DeleteMapping("/deleteUserById/{userId}")
public JSONResult deleteUserById(@PathVariable("userId") Long userId){//调用Service实现类方法做删除操作userService.deleteUserById(userId);//记录操作日志LogUtils.addLog("用户模块", "删除用户操作", "12");return JSONResult.success();
}
- 日志记录代码与业务代码强耦合,万一哪天需要多记录一个字段到数据库的话,所有调用的地方都需要修改
- 许多参数需要花费很大代价才能记录到数据库,比如:请求方法全路径、请求方式(get还是post等)、方法执行耗时、入参、出参、方法执行状态等
- 非常不优雅,难维护
接下来给大家分享一种非常优雅的方式记录日志,就是采用自定义注解+AOP切面编程技术,实现日志记录,现在记录日志的方式就是这样了:
@PostMapping("/save")
@MyLog(title = "用户模块", content = "新增用户信息")
public JSONResult save(@RequestBody UserDto dto){//业务逻辑代码这里,省略return JSONResult.success(dto);
}
可以看到,直接使用自定义注解@MyLog完成日志记录即可,与业务代码没有任何耦合,是不是看着非常优雅呢?
好了,废话不多说,接下来跟着下面的步骤,将这个功能集成到你的项目中的
准备阶段
1、数据库日志表
我们数据库先准备一张记录日志信息的表,建表语句如下:
CREATE TABLE `sys_oper_log` (`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '日志主键',`title` varchar(50) DEFAULT '' COMMENT '模块标题',`content` varchar(100) DEFAULT NULL COMMENT '日志内容',`method` varchar(100) DEFAULT '' COMMENT '方法名称',`request_method` varchar(10) DEFAULT '' COMMENT '请求方式',`oper_name` varchar(50) DEFAULT '' COMMENT '操作人员',`request_url` varchar(255) DEFAULT '' COMMENT '请求URL',`ip` varchar(128) DEFAULT '' COMMENT '请求IP地址',`ip_location` varchar(255) DEFAULT '' COMMENT 'IP归属地',`request_param` varchar(2000) DEFAULT '' COMMENT '请求参数',`response_result` varchar(2000) DEFAULT '' COMMENT '方法响应参数',`status` int(1) DEFAULT NULL COMMENT '操作状态(0正常 1异常)',`error_msg` varchar(2000) DEFAULT NULL COMMENT '错误消息',`oper_time` datetime DEFAULT NULL COMMENT '操作时间',`take_time` bigint(20) DEFAULT NULL COMMENT '方法执行耗时(单位:毫秒)',PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COMMENT='操作日志记录';
2、自定义注解编写
好,表已经准备好了,下面是下载到本地到项目:
这里对项目结构就不多做介绍了,在此基础上,我们新建一个包,用来写自定义注解,代码如下:
package org.js.annotation;
import java.lang.annotation.*;
/*** 自定义注解记录系统操作日志*/
//Target注解决定 MyLog 注解可以加在哪些成分上,如加在类身上,或者属性身上,或者方法身上等成分
@Target({ ElementType.PARAMETER, ElementType.METHOD })
//Retention注解括号中的"RetentionPolicy.RUNTIME"意思是让 MyLog 这个注解的生命周期一直程序运行时都存在
@Retention(RetentionPolicy.RUNTIME)
public @interface MyLog
{/*** 模块标题*/String title() default "";/*** 日志内容*/String content() default "";
}
OK,到目前为止,我们就新增了一个自定义注解类,现在项目结构变成这样了:
3、AOP切面类编写
好,自定义注解写好后,我们开始写AOP切面类,需要先导入AOP相关依赖jar包,所以需要在pom.xml中加入下面依赖
<!-- aop切面 -->
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId>
</dependency>
然后切面类代码如下:
package org.js.aop;
import com.alibaba.fastjson.JSON;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.js.annotation.MyLog;
import org.js.domain.OperLog;
import org.js.service.IOperLogService;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
/*** 切面处理类,记录操作日志到数据库*/
@Aspect
@Component
public class OperLogAspect {
@Autowiredprivate IOperLogService operLogService;
//为了记录方法的执行时间ThreadLocal<Long> startTime = new ThreadLocal<>();
/*** 设置操作日志切入点,这里介绍两种方式:* 1、基于注解切入(也就是打了自定义注解的方法才会切入)* @Pointcut("@annotation(org.js.annotation.MyLog)")* 2、基于包扫描切入* @Pointcut("execution(public * org.js.controller..*.*(..))")*/@Pointcut("@annotation(org.js.annotation.MyLog)")//在注解的位置切入代码//@Pointcut("execution(public * org.js.controller..*.*(..))")//从controller切入public void operLogPoinCut() {}
@Before("operLogPoinCut()")public void beforMethod(JoinPoint point){startTime.set(System.currentTimeMillis());}
/*** 设置操作异常切入点记录异常日志 扫描所有controller包下操作*/@Pointcut("execution(* org.js.controller..*.*(..))")public void operExceptionLogPoinCut() {}
/*** 正常返回通知,拦截用户操作日志,连接点正常执行完成后执行, 如果连接点抛出异常,则不会执行** @param joinPoint 切入点* @param result 返回结果*/@AfterReturning(value = "operLogPoinCut()", returning = "result")public void saveOperLog(JoinPoint joinPoint, Object result) {// 获取RequestAttributesRequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();// 从获取RequestAttributes中获取HttpServletRequest的信息HttpServletRequest request = (HttpServletRequest) requestAttributes.resolveReference(RequestAttributes.REFERENCE_REQUEST);try {// 从切面织入点处通过反射机制获取织入点处的方法MethodSignature signature = (MethodSignature) joinPoint.getSignature();// 获取切入点所在的方法Method method = signature.getMethod();// 获取操作MyLog myLog = method.getAnnotation(MyLog.class);
OperLog operlog = new OperLog();if (myLog != null) {operlog.setTitle(myLog.title());//设置模块名称operlog.setContent(myLog.content());//设置日志内容}// 将入参转换成jsonString params = argsArrayToString(joinPoint.getArgs());// 获取请求的类名String className = joinPoint.getTarget().getClass().getName();// 获取请求的方法名String methodName = method.getName();methodName = className + "." + methodName + "()";operlog.setMethod(methodName); //设置请求方法operlog.setRequestMethod(request.getMethod());//设置请求方式operlog.setRequestParam(params); // 请求参数operlog.setResponseResult(JSON.toJSONString(result)); // 返回结果operlog.setOperName("张三"); // 获取用户名(真实环境中,肯定有工具类获取当前登录者的账号或ID的,或者从token中解析而来)operlog.setIp(getIp(request)); // IP地址operlog.setIpLocation("湖北武汉"); // IP归属地(真是环境中可以调用第三方API根据IP地址,查询归属地)operlog.setRequestUrl(request.getRequestURI()); // 请求URIoperlog.setOperTime(new Date()); // 时间operlog.setStatus(0);//操作状态(0正常 1异常)Long takeTime = System.currentTimeMillis() - startTime.get();//记录方法执行耗时时间(单位:毫秒)operlog.setTakeTime(takeTime);//插入数据库operLogService.insert(operlog);} catch (Exception e) {e.printStackTrace();}}
/*** 异常返回通知,用于拦截异常日志信息 连接点抛出异常后执行*/@AfterThrowing(pointcut = "operExceptionLogPoinCut()", throwing = "e")public void saveExceptionLog(JoinPoint joinPoint, Throwable e) {// 获取RequestAttributesRequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();// 从获取RequestAttributes中获取HttpServletRequest的信息HttpServletRequest request = (HttpServletRequest) requestAttributes.resolveReference(RequestAttributes.REFERENCE_REQUEST);
OperLog operlog = new OperLog();try {// 从切面织入点处通过反射机制获取织入点处的方法MethodSignature signature = (MethodSignature) joinPoint.getSignature();// 获取切入点所在的方法Method method = signature.getMethod();// 获取请求的类名String className = joinPoint.getTarget().getClass().getName();// 获取请求的方法名String methodName = method.getName();methodName = className + "." + methodName + "()";// 获取操作MyLog myLog = method.getAnnotation(MyLog.class);if (myLog != null) {operlog.setTitle(myLog.title());//设置模块名称operlog.setContent(myLog.content());//设置日志内容}// 将入参转换成jsonString params = argsArrayToString(joinPoint.getArgs());operlog.setMethod(methodName); //设置请求方法operlog.setRequestMethod(request.getMethod());//设置请求方式operlog.setRequestParam(params); // 请求参数operlog.setOperName("张三"); // 获取用户名(真实环境中,肯定有工具类获取当前登录者的账号或ID的,或者从token中解析而来)operlog.setIp(getIp(request)); // IP地址operlog.setIpLocation("湖北武汉"); // IP归属地(真是环境中可以调用第三方API根据IP地址,查询归属地)operlog.setRequestUrl(request.getRequestURI()); // 请求URIoperlog.setOperTime(new Date()); // 时间operlog.setStatus(1);//操作状态(0正常 1异常)operlog.setErrorMsg(stackTraceToString(e.getClass().getName(), e.getMessage(), e.getStackTrace()));//记录异常信息//插入数据库operLogService.insert(operlog);} catch (Exception e2) {e2.printStackTrace();}}
/*** 转换异常信息为字符串*/public String stackTraceToString(String exceptionName, String exceptionMessage, StackTraceElement[] elements) {StringBuffer strbuff = new StringBuffer();for (StackTraceElement stet : elements) {strbuff.append(stet + "\n");}String message = exceptionName + ":" + exceptionMessage + "\n\t" + strbuff.toString();message = substring(message,0 ,2000);return message;}
/*** 参数拼装*/private String argsArrayToString(Object[] paramsArray){String params = "";if (paramsArray != null && paramsArray.length > 0){for (Object o : paramsArray){if (o != null){try{Object jsonObj = JSON.toJSON(o);params += jsonObj.toString() + " ";}catch (Exception e){e.printStackTrace();}}}}return params.trim();}
//字符串截取public static String substring(String str, int start, int end) {if (str == null) {return null;} else {if (end < 0) {end += str.length();}
if (start < 0) {start += str.length();}
if (end > str.length()) {end = str.length();}
if (start > end) {return "";} else {if (start < 0) {start = 0;}
if (end < 0) {end = 0;}return str.substring(start, end);}}}
/*** 转换request 请求参数* @param paramMap request获取的参数数组*/public Map<String, String> converMap(Map<String, String[]> paramMap) {Map<String, String> returnMap = new HashMap<>();for (String key : paramMap.keySet()) {returnMap.put(key, paramMap.get(key)[0]);}return returnMap;}
//根据HttpServletRequest获取访问者的IP地址public static String getIp(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("WL-Proxy-Client-IP");}if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {ip = request.getHeader("HTTP_CLIENT_IP");}if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {ip = request.getHeader("HTTP_X_FORWARDED_FOR");}if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {ip = request.getRemoteAddr();}return ip;}
}
代码里面的逻辑我就不赘述了,里面的注释写的非常全,大家应该看得懂,不懂的评论区留言即可
现在项目结构如下:
4、业务层
4.1、Service 层:
package cn.js.service;import cn.js.domain.SysOperLog;
import cn.js.query.SysOperLogQuery;
import com.baomidou.mybatisplus.extension.service.IService;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;/*** <p>* 操作日志记录 服务类* </p>** @author js* @date 2023-11-02*/
public interface SysOperLogService extends IService<SysOperLog> {IPage<SysOperLog> selectMyPage(SysOperLogQuery query);Page<SysOperLog> selectMySqlPage(SysOperLogQuery query);}
4.2 Service 实现层:
package cn.js.service.impl;import cn.hutool.core.util.StrUtil;
import cn.js.Mapper.SysOperLogMapper;
import cn.js.domain.SysOperLog;
import cn.js.query.SysOperLogQuery;
import cn.js.service.SysOperLogService;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;
import lombok.extern.slf4j.Slf4j;
import java.util.List;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.beans.factory.annotation.Autowired;/*** <p>* 操作日志记录 服务实现类* </p>** @author js* @date 2023-11-02*/
@Transactional
@Service
@Slf4j
public class SysOperLogServiceImpl extends ServiceImpl<SysOperLogMapper, SysOperLog> implements SysOperLogService {@Autowiredprivate SysOperLogMapper sysOperLogMapper;//查询分页列表数据public IPage<SysOperLog> selectMyPage(SysOperLogQuery query) {QueryWrapper<SysOperLog> wrapper = new QueryWrapper<>();if (StrUtil.isNotEmpty(query.getKeyword())) {//下面条件根据实际情况修改wrapper.and(i -> i.like("user_name", query.getKeyword()).or().like("login_name", query.getKeyword()));}//排序(默认根据主键ID降序排序,根据实际情况修改)wrapper.orderByDesc("id");Page<SysOperLog> page = new Page<>(query.getCurrent(), query.getSize());return super.page(page, wrapper);}//查询分页列表数据(自己写SQL)public Page<SysOperLog> selectMySqlPage(SysOperLogQuery query) {Page<SysOperLog> page = new Page<>(query.getCurrent(), query.getSize());List<SysOperLog> list = sysOperLogMapper.selectMySqlPage(page, query);return page.setRecords(list);}
}
5、测试
接下来我们就可以测试了,在Controller接口中直接用自定义注解开始记录日志,如下方法使用:
@GetMapping("/deleteUserById/{userId}")
@MyLog(title = "用户模块", content = "删除用户操作")
public JSONResult deleteUserById(@PathVariable("userId") Long userId){//这里具体删除用户代码 省略.....return JSONResult.success();
}
然后启动项目,浏览器输入地址:http://localhost:8001/deleteUserById/123
显示结果如下:
说明接口调用成功,看下数据库是否记录了日志:
数据库已经新增了一条日志记录,而且里面记录到信息非常全
OK,至此,我们以后项目中再记录日志就非常方便了,只需要在方法上面打一个注解就可以了,在AOP里负责往数据库写,方便日后维护
完整代码
相关文章:

SpringBoot+AOP+自定义注解,优雅实现日志记录
文章目录 前言准备阶段1、数据库日志表2、自定义注解编写3、AOP切面类编写4、业务层4.1、Service 层:4.2 Service 实现层: 5、测试 前言 首先我们看下传统记录日志的方式是什么样的: DeleteMapping("/deleteUserById/{userId}") …...

多式联运路径优化问题:基于拓扑排序的遗传算法染色体编码
一、什么是拓扑排序 在图论中,拓扑排序(Topological Sorting)是一个有向无环图(DAG, Directed Acyclic Graph)的所有顶点的线性序列。且该序列必须满足下面两个条件: 每个顶点出现且只出现一次。若存在一…...
Go 方法集合与选择receiver类型
Go 方法集合与选择receiver类型 文章目录 Go 方法集合与选择receiver类型一、receiver 参数类型对 Go 方法的影响二、选择 receiver 参数类型原则2.1 选择 receiver 参数类型的第一个原则2.2 选择 receiver 参数类型的第二个原则 三、方法集合(Method Set࿰…...
Unity AudioClip和PCM音频数据的转化
1 PCM音频数据转化AudioClip 假设PCM音频当前是16Khz采样率,16bit数据 byte[] pcmBytesnew byte[10240];float[] floatClipData new float[audioBytes.Length/2];for (int i 0; i < audioBytes.Length; i2){ floatData[i / 2] (short)((audioBytes[i 1] <…...
linux配置vlan后网络不通
如果在Linux上配置了VLAN,但网络不通,这可能是由于多种原因导致的。以下是一些可能的原因和解决方法: 检查物理连接:首先,确保VLAN支持的物理网络连接正常。确保网络电缆连接正确,交换机端口配置正确&#…...

GORM:在Go中轻松管理数据库
GORM综合介绍 - Go对象关系映射库 在现代软件开发中,高效的数据库管理对于构建强大的应用程序至关重要。GORM是Go开发人员寻求与数据库进行交互的简化方式的宝贵工具。GORM是Go对象关系映射的缩写,它为Go的面向对象世界与数据库的关系世界之间提供了桥梁…...

Ubuntu18.04 下PCL的卸载与安装
目录 一、卸载有问题的PCL1.7 二、编译&&安装PCL1.8.1 2.1、安装PCL依赖 2.2、编译VTK 2.3、编译PCL源码 三、 总结 写这篇博客时,本文方法已经在笔记本Ubuntu和VM虚拟机成功安装PCL1.8.1,并且通过测试。 下文方法同样适用于ubuntu18.04。…...

SMTP邮件发送图片-如何在github中存储图片并访问
之前写了一篇文章 Go:实现SMTP邮件发送订阅功能(包含163邮箱、163企业邮箱、谷歌gmail邮箱),实现了通过邮箱服务来发送邮件,但都是文字内容,要是想实现邮件发送图片,就需要将图片放到公网可访问…...

2023年软件系统架构师论文【回忆版】
2023年11月5日,全国计算机等级下半年考试,北京市软件架构师考试其中有个考点在首都经济贸易大学丰台校区),地址:北京市丰台区花乡张家路口121号(北门入校) 注意:机考的考试时间有所变…...
【使用python实现文件视频格式的转换】
1.视频格式转换有哪些常用方法? 视频格式转换的常用方法有以下几种: 使用专业的视频转换软件:这些软件可以支持多种视频格式之间的转换,如Adobe Premiere Pro、Final Cut Pro等。使用在线视频转换工具:有许多在线视频…...
新媒体运营的营销方案
一、目标客户群体 新媒体运营是通过社交媒体、短视频、直播等方式将信息快速传播出去,因此,适合的目标客户群体应该是年轻人群体,包括大学生、职场青年、年轻家庭等。 二、营销策略 1、社交媒体营销策略 借助社交媒体平台,建立企…...

Flutter 05 组件状态、生命周期、数据传递(共享)、Key
一、Android界面渲染流程UI树与FlutterUI树的设计思路对比 二、Widget组件生命周期详解 1、Widget组件生命周期 和其他的视图框架比如android的Activity一样,flutter中的视图Widget也存在生命周期,生命周期的回调函数体现在了State上面。组件State的生命…...
2.Vue3项目(二):vue项目创建,项目必需的基础依赖配置,项目集成各种第三方依赖
目录 一、环境配置 1.下载node.js 2.pnpm的配置 二、创建项目 1.先创建好项目文件夹...
【Mybatis源码】注册器 - TypeAliasRegistry
Mybatis中使用TypeAliasRegistry注册器用于管理类型与别名,Mybatis中许多功能的实现都需要从TypeAliasRegistry注册器中找到别名对应的类型,本篇我们介绍一下TypeAliasRegistry注册器的原理与使用 一、构造方法 TypeAliasRegistry注册器类提供了一个无参数的构造方法用于创…...

【wp】2023鹏城杯初赛 Web web1(反序列化漏洞)
考点: 常规的PHP反序列化漏洞双写绕过waf 签到题 源码: <?php show_source(__FILE__); error_reporting(0); class Hacker{private $exp;private $cmd;public function __toString(){call_user_func(system, "cat /flag");} }class A {p…...

三顾茅庐,七面阿里,成功上岸25k16薪,我行你也行~
写在片头:声明,勿杠 首先简单说一下,这三次面试阿里并不是一次性去面的,实际上第一次面试时候还在大四,找的实习岗,不太清楚是什么部门,别问我为什么还记得面试题,有记录和复盘的习…...

儿童听力损伤了,家长怎么办?
很多家长对儿童听力损伤问题存在较大误区,认为儿童除了先天性耳聋以外不会有听力问题。家长总认为孩子上课或做事不专心是因为注意力不集中、多动等问题所致,大部分家长没有意识到孩子可能出现了听力损伤问题。 儿童听力损伤主要是指因各种原因导致双耳不…...

【实验记录】为了混毕业·读读论文叭
PR曲线 1. Robust_Place_Recognition_using_an_Imaging_Lidar 在第三节方法中,提到了一些列处理步骤,分析来与vins相似,在vins中是关键帧检索、特征提取、DBoW查询、描述子匹配、PnP RANSAC求解。 第四节的实验部分,没有绘制pr…...
asr翱捷LORA系列芯片选型参考推荐ASR6601/asr6505/asr6501/asr6500
ASR6601 SoC是国内首颗支持LoRa的LPWAN SoC。ASR6601芯片中集成的超低功耗收发机,除了支持LoRa调制方式外,还可以支持FSK收发、MSK收发和BPSK发射等。在3.3V电源供电的情况下,通过高功率PA,最大可发射22dBM的输出功率。ASR6601与A…...

Prometheus+Node_exporter+Grafana实现监控主机
PrometheusNode_exporterGrafana实现监控主机 如果没有安装相关的配置,首先要进行安装配置,环境是基于Linux,虚拟机的相关环境配置在文末给出,现在先讲解PrometheusNode_exporterGrafana的安装和使用。 一.Prometheus安装 虽然…...
椭圆曲线密码学(ECC)
一、ECC算法概述 椭圆曲线密码学(Elliptic Curve Cryptography)是基于椭圆曲线数学理论的公钥密码系统,由Neal Koblitz和Victor Miller在1985年独立提出。相比RSA,ECC在相同安全强度下密钥更短(256位ECC ≈ 3072位RSA…...

工业安全零事故的智能守护者:一体化AI智能安防平台
前言: 通过AI视觉技术,为船厂提供全面的安全监控解决方案,涵盖交通违规检测、起重机轨道安全、非法入侵检测、盗窃防范、安全规范执行监控等多个方面,能够实现对应负责人反馈机制,并最终实现数据的统计报表。提升船厂…...
R语言AI模型部署方案:精准离线运行详解
R语言AI模型部署方案:精准离线运行详解 一、项目概述 本文将构建一个完整的R语言AI部署解决方案,实现鸢尾花分类模型的训练、保存、离线部署和预测功能。核心特点: 100%离线运行能力自包含环境依赖生产级错误处理跨平台兼容性模型版本管理# 文件结构说明 Iris_AI_Deployme…...
MySQL中【正则表达式】用法
MySQL 中正则表达式通过 REGEXP 或 RLIKE 操作符实现(两者等价),用于在 WHERE 子句中进行复杂的字符串模式匹配。以下是核心用法和示例: 一、基础语法 SELECT column_name FROM table_name WHERE column_name REGEXP pattern; …...
什么?连接服务器也能可视化显示界面?:基于X11 Forwarding + CentOS + MobaXterm实战指南
文章目录 什么是X11?环境准备实战步骤1️⃣ 服务器端配置(CentOS)2️⃣ 客户端配置(MobaXterm)3️⃣ 验证X11 Forwarding4️⃣ 运行自定义GUI程序(Python示例)5️⃣ 成功效果
深度学习水论文:mamba+图像增强
🧀当前视觉领域对高效长序列建模需求激增,对Mamba图像增强这方向的研究自然也逐渐火热。原因在于其高效长程建模,以及动态计算优势,在图像质量提升和细节恢复方面有难以替代的作用。 🧀因此短时间内,就有不…...

【Redis】笔记|第8节|大厂高并发缓存架构实战与优化
缓存架构 代码结构 代码详情 功能点: 多级缓存,先查本地缓存,再查Redis,最后才查数据库热点数据重建逻辑使用分布式锁,二次查询更新缓存采用读写锁提升性能采用Redis的发布订阅机制通知所有实例更新本地缓存适用读多…...
MySQL JOIN 表过多的优化思路
当 MySQL 查询涉及大量表 JOIN 时,性能会显著下降。以下是优化思路和简易实现方法: 一、核心优化思路 减少 JOIN 数量 数据冗余:添加必要的冗余字段(如订单表直接存储用户名)合并表:将频繁关联的小表合并成…...

MacOS下Homebrew国内镜像加速指南(2025最新国内镜像加速)
macos brew国内镜像加速方法 brew install 加速formula.jws.json下载慢加速 🍺 最新版brew安装慢到怀疑人生?别怕,教你轻松起飞! 最近Homebrew更新至最新版,每次执行 brew 命令时都会自动从官方地址 https://formulae.…...

WPF八大法则:告别模态窗口卡顿
⚙️ 核心问题:阻塞式模态窗口的缺陷 原始代码中ShowDialog()会阻塞UI线程,导致后续逻辑无法执行: var result modalWindow.ShowDialog(); // 线程阻塞 ProcessResult(result); // 必须等待窗口关闭根本问题:…...