练习项目后端代码解析切面篇(Aspect)

前言

之前注解篇时我说,通常情况下一个自定义注解一般对应一个切面,虽然项目里的切面和注解个数相同,但是好像有一个名字看起来并不对应,无所谓,先看了再说。
在这里插入图片描述

ExceptionLogAspect切面

我在里面做了具体注释,所以看起来比较长,但我觉得作者的代码思路还是很清晰的,就是里面一下就涉及了三个自写工具类的使用,会让我暂时少了一些具体逻辑的理解。

如果不想看代码里的注释可以先看看我的理解。

我们先来看这个类的类图吧

在这里插入图片描述

这个类被两个注解修饰,@Component@Aspect一起使用,可以方便地实现Spring AOP的功能,同时确保切面类被Spring容器管理。

@Component:这个注解是Spring的一个核心注解,用于指示一个类是Spring容器管理的组件。当一个类被标记为@Component时,Spring会自动检测并注册它,使得它可以在应用程序的其他部分中被注入和使用。

@Aspect:这个注解是Spring AOP(面向切面编程)的一部分,用于指示一个类是一个切面。切面是一个关注点(Cross-Cutting Concerns)模块化的类,它包含了一系列的通知(Advice),这些通知在特定的连接点(Join Points)上执行。

这个类有四个方法
handlelog(JoinPoint,Exception) :根据传入的参数,返回一个填充好数据的 ExceptionLog的对象,ExceptionLog的属性部分我也放在图里面了。

logAfterThrowing(JoinPoint,Exception):用于指定在目标方法抛出异常后执行的通知。它被一个注解 @AfterThrowing(value = “logPointcut()”, throwing = “e”) 修饰,value属性指定了切点,throwing属性指定了异常对象,它可以在通知方法中作为参数使用。

logPointcut():它被 @Pointcut(“execution(* com.rawchen.controller….(…))”)修饰,表示切入点的声明,里面的参数表示拦截的范围。

最后实现的功能

一旦com.rawchen.controller包下的某个类的某个方法抛出异常,ExceptionLogAspect切面中的logAfterThrowing通知方法就会被触发。该方法上面一行的注解会找到切入点对象和异常信息传到方法里,然后开始执行逻辑。

@AfterThrowing(value = "logPointcut()", throwing = "e")
public void logAfterThrowing(JoinPoint joinPoint, Exception e) {ExceptionLog log = handleLog(joinPoint, e);exceptionLogService.saveExceptionLog(log);
}

ExceptionLog log = handleLog(joinPoint, e) 调用handleLog方法来创建一个新的ExceptionLog对象,并填充其属性,包括异常发生的URI、方法、IP地址、用户代理、描述信息、错误堆栈跟踪以及请求参数。

exceptionLogService.saveExceptionLog(log) 调用ExceptionLogService的saveExceptionLog方法来保存异常日志。这个方法将异常日志信息保存到数据库或其他存储介质中。
ExceptionLogService是一个服务层接口,它定义了保存异常日志的方法,这个接口在ExceptionLogAspect类中通过@Autowired注解注入了实现类。

完整代码注释

package com.rawchen.aspect;import com.rawchen.annotation.OperationLogger;
import com.rawchen.annotation.VisitLogger;
import com.rawchen.entity.ExceptionLog;
import com.rawchen.service.ExceptionLogService;
import com.rawchen.util.AopUtils;
import com.rawchen.util.IpAddressUtils;
import com.rawchen.util.StringUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import com.rawchen.util.JacksonUtils;import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.Map;/*** @Description: AOP记录异常日志* @Date: 2020-12-03*/
@Component
@Aspect
public class ExceptionLogAspect {//@Autowired:这是一个Spring的注解,用于自动注入ExceptionLogService的实例。//Spring会自动查找合适的组件并将其注入到当前类中。@AutowiredExceptionLogService exceptionLogService;/*** 配置切入点* logPointcut():它的作用是作为切点的标识符。* 在这个方法上标注了@Pointcut注解,因此它会被Spring AOP识别为切点定义。*///@Pointcut("execution(* com.rawchen.controller..*.*(..))"):这是一个切点注解,用于定义哪些方法会被拦截。//在这个例子中,execution(* com.rawchen.controller..*.*(..))//表示拦截com.rawchen.controller包及其子包下的所有方法。@Pointcut("execution(* com.rawchen.controller..*.*(..))")public void logPointcut() {}/*** 该方法它接收JoinPoint和Exception对象作为参数这个方法可用于创建一个异常日志对象,并将为其填充为合适的值后存储。*///@AfterThrowing(value = "logPointcut()", throwing = "e"):这是一个通知注解,//用于指定在目标方法抛出异常后执行的通知。value属性指定了切点,throwing属性指定了异常对象,//它可以在通知方法中作为参数使用。@AfterThrowing(value = "logPointcut()", throwing = "e")public void logAfterThrowing(JoinPoint joinPoint, Exception e) {ExceptionLog log = handleLog(joinPoint, e);exceptionLogService.saveExceptionLog(log);}/*** 设置ExceptionLog对象属性* * @return 填充好数据的ExceptionLog对象*/private ExceptionLog handleLog(JoinPoint joinPoint, Exception e) {//获取了当前的ServletRequestAttributes对象,这个对象包含了当前HTTP请求的详细信息。ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();//获取当前的HttpServletRequest对象,这个对象代表了当前的HTTP请求。HttpServletRequest request = attributes.getRequest();//通过request对象的方法获取一些具体信息//获取请求的URI(统一资源标识符)String uri = request.getRequestURI();//获取请求方法String method = request.getMethod();//通过作者自己写的工具类,来获取ip地址String ip = IpAddressUtils.getIpAddress(request);//获取请求的User-Agent头,这个头通常包含了客户端浏览器的信息。String userAgent = request.getHeader("User-Agent");//todo 使用swagger后,可以直接使用注解上的内容作为 ExceptionLog 的 description//通过类里私有方法获取注解上的描述内容存储在字符串中String description = getDescriptionFromAnnotations(joinPoint);//使用作者自己写的StringUtils工具类的方法来获取异常的堆栈跟踪信息。String error = StringUtils.getStackTrace(e);//创建一个新的ExceptionLog对象,并使用前面获取的信息来填充它的属性。ExceptionLog log = new ExceptionLog(uri, method, description, error, ip, userAgent);//调用AopUtils工具类的方法来获取请求的参数。Map<String, Object> requestParams = AopUtils.getRequestParams(joinPoint);//将请求参数转换为JSON字符串,并截取前2000个字符,//然后将其设置为ExceptionLog对象的param属性,只截取2000个字符是为了限制日志的长度,避免日志过长。log.setParam(StringUtils.substring(JacksonUtils.writeValueAsString(requestParams), 0, 2000));return log;}/*** 如果抛出方法被自定义注解修饰,那就得到OperationLogger注解里或者VisitLog描述操作的那段字符串*/private String getDescriptionFromAnnotations(JoinPoint joinPoint) {String description = "";//这行代码从JoinPoint对象中获取方法签名,并将其转换为Method对象,这样就可以访问方法上的注解了。Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();OperationLogger operationLogger = method.getAnnotation(OperationLogger.class);if (operationLogger != null) {description = operationLogger.value();return description;}VisitLogger visitLogger = method.getAnnotation(VisitLogger.class);if (visitLogger != null) {description = visitLogger.behavior();return description;}return description;}
}

OperationLogAspect

还是先看类图

在这里插入图片描述
注解已经解释过一遍了,而且这三个方法与前面的ExceptionLogAspect切面相差不大,同时一样在图里附上OperationLog类便于理解。

logPointcut() :被 @Pointcut(“@annotation(operationLogger)”) 注解修饰,表示切入点的声明,它将会匹配所有带有OperationLogger注解的方法。

logAround(ProceedingJoinPoint joinPoint, OperationLogger operationLogger):被 @Around(“logPointcut(operationLogger)”) 注解修饰,表示该切入点的方法被环绕通知,环绕通知是一种动态拦截方法执行的通知,它允许你完全控制方法的执行流程。

环绕通知的logAround方法可以执行以下操作:
执行被环绕的方法:Object result = joinPoint.proceed(); 这行代码会执行原始的方法调用,并将返回值存储在result变量中。
在方法执行前后添加额外的逻辑:在这个例子中,它记录了方法执行的时间,并创建了一个操作日志对象。
返回方法的执行结果:return result; 这行代码将原始方法的执行结果返回给调用者。

handleLog(ProceedingJoinPoint joinPoint, OperationLogger operationLogger, int times):通过调用工具类进行信息解析,返回一个填充好数据属性的OperationLog对象,供服务层接口进行日志保存。

最后实现的功能

一旦被OperationLogger注解修饰的方法被调用,然后切入点进行匹配,传参到logAround方法,进行一个环绕通知来记录这个方法的执行时间,最后通过handleLog方法与其他信息一同被记录到日志当中。也是服务层接口通过注解注入实体类,服务层接口方法负责日志保存。

 @Around("logPointcut(operationLogger)")public Object logAround(ProceedingJoinPoint joinPoint, OperationLogger operationLogger) throws Throwable {// 设置当前时间currentTime.set(System.currentTimeMillis());// 执行被环绕的方法Object result = joinPoint.proceed();// 计算方法执行时间int times = (int) (System.currentTimeMillis() - currentTime.get());// 清除当前时间currentTime.remove();// 创建操作日志对象OperationLog operationLog = handleLog(joinPoint, operationLogger, times);// 保存操作日志operationLogService.saveOperationLog(operationLog);// 返回方法的执行结果return result;}

完整代码注释

@Component
@Aspect
public class OperationLogAspect {// 注入操作日志服务@AutowiredOperationLogService operationLogService;// 线程局部变量,用于记录当前时间ThreadLocal<Long> currentTime = new ThreadLocal<>();/*** 配置切入点,用于匹配带有OperationLogger注解的方法*/@Pointcut("@annotation(operationLogger)")public void logPointcut(OperationLogger operationLogger) {}/*** 配置环绕通知,用于记录操作日志** @param joinPoint 方法执行的连接点* @param operationLogger 方法上的OperationLogger注解* @return 方法的执行结果* @throws Throwable 方法执行中可能抛出的异常*/@Around("logPointcut(operationLogger)")public Object logAround(ProceedingJoinPoint joinPoint, OperationLogger operationLogger) throws Throwable {// 设置当前时间currentTime.set(System.currentTimeMillis());// 执行被环绕的方法Object result = joinPoint.proceed();// 计算方法执行时间int times = (int) (System.currentTimeMillis() - currentTime.get());// 清除当前时间currentTime.remove();// 创建操作日志对象OperationLog operationLog = handleLog(joinPoint, operationLogger, times);// 保存操作日志operationLogService.saveOperationLog(operationLog);// 返回方法的执行结果return result;}/*** 获取HttpServletRequest请求对象,并设置OperationLog对象属性** @param operationLogger 方法上的OperationLogger注解* @param times 方法执行时间* @return 操作日志对象*/private OperationLog handleLog(ProceedingJoinPoint joinPoint, OperationLogger operationLogger, int times) {// 获取当前请求的ServletRequestAttributesServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();// 从ServletRequestAttributes中获取HttpServletRequest对象HttpServletRequest request = attributes.getRequest();// 获取当前用户名,通过JWT工具类解析String username = JwtUtils.getTokenBody(request.getHeader("Authorization")).getSubject();// 获取请求的URIString uri = request.getRequestURI();// 获取请求的HTTP方法String method = request.getMethod();// 获取OperationLogger注解的描述信息String description = operationLogger.value();// 获取请求的IP地址String ip = IpAddressUtils.getIpAddress(request);// 获取请求的User-Agent头String userAgent = request.getHeader("User-Agent");// 创建新的操作日志对象OperationLog log = new OperationLog(username, uri, method, description, ip, times, userAgent);// 通过工具类,获取请求参数,并将其转换为JSON字符串,然后截取前2000个字符作为日志参数,避免日志过长Map<String, Object> requestParams = AopUtils.getRequestParams(joinPoint);log.setParam(StringUtils.substring(JacksonUtils.writeValueAsString(requestParams), 0, 2000));// 返回操作日志对象return log;}
}

VisitLogAspect切面

类图

在这里插入图片描述

这里面的logAround环绕通知方法,logPointcut方法以及handleLog的方法与上面两个切面类也是大同小异,现在主要看一下类图里前面三个方法。
checkIdentification(HttpServletRequest request) :校验访客标识码,通过获取请求里面的校验码,有校验码就与数据库里的进行比对,比对成功后保存在Redis中,比对不成功就签发一个新的标识码;加入请求里根本没有校验码,也是签发一个新的。

saveUUID(HttpServletRequest request) :签发校验码,根据时间戳、ip、userAgent生成UUID,并进行数据库与Redis的保存操作。

至于这个judgeBehavior(String behavior, String content, Map<String, Object> requestParams, Object result)方法,它里面只是简单的字符串与注解里的参数进行匹配,再根据请求信息完成日志数据,所以说我放一张VisitLogger的注解使用次数反而更加好理解。
在这里插入图片描述

最后实现功能

  1. 访问日志记录:当一个方法被VisitLogger注解修饰时,该方法执行前后会被VisitLogAspect切面拦截,并在方法执行前后执行日志记录逻辑。
  2. 访客标识码验证:在方法执行前,切面会检查请求头中是否包含identification字段,这是访客的唯一标识码。如果未包含,切面会生成一个新的访客标识码并保存到数据库和Redis中。
  3. 行为判断和日志内容设置:根据VisitLogger注解的行为和内容描述,切面会判断并设置访问日志的备注和内容字段。
  4. 访问日志保存:在方法执行后,切面会创建一个VisitLog对象,并填充其属性,包括访客标识码、请求URI、HTTP方法、行为描述、内容描述、IP地址、执行时间、User-Agent头等。然后,它会将这个访问日志对象保存到服务层进行持久化。
  5. Redis使用:切面使用Redis服务来存储和验证访客标识码。它将访客标识码保存到Redis中的一个集合中,并在验证访客标识码时检查这个集合中是否包含该标识码。
  6. 数据库访问:切面会访问数据库来验证访客标识码是否已存在于数据库中,以及是否需要生成新的访客标识码。

UUID的作用

UUID(Universally Unique Identifier,通用唯一识别码)是一种用于生成唯一标识符的机制。

在这个项目里,UUID被用于生成访客的唯一标识码。当请求头中没有identification字段时,切面会生成一个新的UUID,并将其保存到数据库和Redis中。之后,在每次请求中,切面都会检查请求头中的identification字段,以验证访客标识码是否有效。

这种机制可以防止重复访问和刷访问量等恶意行为。

完整代码注释

@Component
@Aspect
public class VisitLogAspect {// 注入访问日志服务@AutowiredVisitLogService visitLogService;// 注入访客服务@AutowiredVisitorService visitorService;// 注入Redis服务@AutowiredRedisService redisService;// 线程局部变量,用于记录当前时间ThreadLocal<Long> currentTime = new ThreadLocal<>();/*** 配置切入点,用于匹配带有VisitLogger注解的方法*/@Pointcut("@annotation(visitLogger)")public void logPointcut(VisitLogger visitLogger) {}/*** 配置环绕通知,用于记录访问日志** @param joinPoint 方法执行的连接点* @param visitLogger 方法上的VisitLogger注解* @return 方法的执行结果* @throws Throwable 方法执行中可能抛出的异常*/@Around("logPointcut(visitLogger)")public Object logAround(ProceedingJoinPoint joinPoint, VisitLogger visitLogger) throws Throwable {// 设置当前时间currentTime.set(System.currentTimeMillis());// 执行被环绕的方法Object result = joinPoint.proceed();// 计算方法执行时间int times = (int) (System.currentTimeMillis() - currentTime.get());// 清除当前时间currentTime.remove();// 获取请求对象HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();// 校验访客标识码String identification = checkIdentification(request);// 记录访问日志VisitLog visitLog = handleLog(joinPoint, visitLogger, request, result, times, identification);// 保存访问日志visitLogService.saveVisitLog(visitLog);// 返回方法的执行结果return result;}/*** 校验访客标识码** @param request* @return*/private String checkIdentification(HttpServletRequest request) {// 获取请求头中的访客标识码String identification = request.getHeader("identification");// 如果请求头中没有访客标识码,则签发一个新的访客标识码if (identification == null) {// 签发新的访客标识码并保存到数据库和Redisidentification = saveUUID(request);} else {// 校验Redis中是否存在访客标识码boolean redisHas = redisService.hasValueInSet(RedisKeyConfig.IDENTIFICATION_SET, identification);// 如果Redis中不存在访客标识码,则检查数据库中是否存在if (!redisHas) {// 检查数据库中是否存在访客标识码boolean mysqlHas = visitorService.hasUUID(identification);// 如果数据库中存在,则保存至Redisif (mysqlHas) {redisService.saveValueToSet(RedisKeyConfig.IDENTIFICATION_SET, identification);} else {// 如果数据库中不存在,则签发一个新的访客标识码identification = saveUUID(request);}}}return identification;}/*** 签发UUID,并保存至数据库和Redis** @param request* @return*/private String saveUUID(HttpServletRequest request) {//获取响应对象HttpServletResponse response = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getResponse();//获取当前时间戳,精确到小时,防刷访客数据Calendar calendar = Calendar.getInstance();calendar.set(Calendar.MINUTE, 0);calendar.set(Calendar.SECOND, 0);String timestamp = Long.toString(calendar.getTimeInMillis() / 1000);//获取访问者基本信息String ip = IpAddressUtils.getIpAddress(request);String userAgent = request.getHeader("User-Agent");//根据时间戳、ip、userAgent生成UUIDString nameUUID = timestamp + ip + userAgent;String uuid = UUID.nameUUIDFromBytes(nameUUID.getBytes()).toString();//添加访客标识码UUID至响应头response.addHeader("identification", uuid);//暴露自定义header供页面资源使用response.addHeader("Access-Control-Expose-Headers", "identification");//校验Redis中是否存在uuidboolean redisHas = redisService.hasValueInSet(RedisKeyConfig.IDENTIFICATION_SET, uuid);if (!redisHas) {//保存至RedisredisService.saveValueToSet(RedisKeyConfig.IDENTIFICATION_SET, uuid);//保存至数据库Visitor visitor = new Visitor(uuid, ip, userAgent);visitorService.saveVisitor(visitor);}return uuid;}/*** 设置VisitLogger对象属性** @param joinPoint* @param visitLogger* @param result* @param times* @return 填充好的日志对象*/private VisitLog handleLog(ProceedingJoinPoint joinPoint, VisitLogger visitLogger, HttpServletRequest request, Object result,int times, String identification) {// 获取请求的URIString uri = request.getRequestURI();// 获取请求的HTTP方法String method = request.getMethod();// 获取VisitLogger注解的行为描述String behavior = visitLogger.behavior();// 获取VisitLogger注解的内容描述String content = visitLogger.content();// 获取请求的IP地址String ip = IpAddressUtils.getIpAddress(request);// 获取请求的User-Agent头String userAgent = request.getHeader("User-Agent");// 获取请求参数Map<String, Object> requestParams = AopUtils.getRequestParams(joinPoint);// 根据访问行为,设置对应的访问内容或备注Map<String, String> map = judgeBehavior(behavior, content, requestParams, result);// 创建新的访问日志对象VisitLog log = new VisitLog(identification, uri, method, behavior, map.get("content"), map.get("remark"), ip, times, userAgent);// 设置日志的参数log.setParam(StringUtils.substring(JacksonUtils.writeValueAsString(requestParams), 0, 2000));// 返回访问日志对象return log;}/*** 根据访问行为,设置对应的访问内容或备注** @param behavior* @param content* @param requestParams* @param result* @return*/private Map<String, String> judgeBehavior(String behavior, String content, Map<String, Object> requestParams, Object result) {Map<String, String> map = new HashMap<>();String remark = "";if (behavior.equals("访问页面") && (content.equals("首页") || content.equals("动态"))) {int pageNum = (int) requestParams.get("pageNum");remark = "第" + pageNum + "页";} else if (behavior.equals("查看博客")) {Result res = (Result) result;if (res.getCode() == 200) {BlogDetail blog = (BlogDetail) res.getData();String title = blog.getTitle();content = title;remark = "文章标题:" + title;}} else if (behavior.equals("搜索博客")) {Result res = (Result) result;if (res.getCode() == 200) {String query = (String) requestParams.get("query");content = query;remark = "搜索内容:" + query;}} else if (behavior.equals("查看分类")) {String categoryName = (String) requestParams.get("categoryName");int pageNum = (int) requestParams.get("pageNum");content = categoryName;remark = "分类名称:" + categoryName + ",第" + pageNum + "页";} else if (behavior.equals("查看标签")) {String tagName = (String) requestParams.get("tagName");int pageNum = (int) requestParams.get("pageNum");content = tagName;remark = "标签名称:" + tagName + ",第" + pageNum + "页";} else if (behavior.equals("点击友链")) {String nickname = (String) requestParams.get("nickname");content = nickname;remark = "友链名称:" + nickname;}map.put("remark", remark);map.put("content", content);return map;}
}

后续

一扯到这个切面,里面的工具类,服务层接口,redis配置类全部一下子冒出来了,还好这个项目比较小,总算是看完了,现在我很难想象以后工作时真要看项目代码时,那得是件多煎熬的事啊,下一篇看看项目里的工具类代码咋写的。
在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系瞎胡闹网进行投诉反馈,一经查实,立即删除!

相关文章

记一些内存取证题

生活若循规蹈矩&#xff0c;我们便随心而动 1.Suspicion 给了俩文件 python2 vol.py -f mem.vmem imageinfo 查看可疑进程 python2 vol.py -f mem.vmem --profileWinXPSP2x86 pslist 发现可疑进程TrueCrypt.exe 把这个进程提取出来。memdump -p 进程号 -D 目录 python2 vol…

基于TF的简易关键字语音识别

⚠申明&#xff1a; 未经许可&#xff0c;禁止以任何形式转载&#xff0c;若要引用&#xff0c;请标注链接地址。 全文共计10182字&#xff0c;阅读大概需要10分钟 &#x1f308;更多学习内容&#xff0c; 欢迎&#x1f44f;关注&#x1f440;【文末】我的个人微信公众号&#…

深入剖析Spring框架:推断构造方法与@Bean注解的内部机制

你好&#xff0c;我是柳岸花开。 Spring框架作为Java开发中广泛使用的基础架构&#xff0c;其设计精巧、功能强大&#xff0c;尤其是其依赖注入&#xff08;DI&#xff09;和控制反转&#xff08;IoC&#xff09;特性&#xff0c;极大地提高了代码的可维护性和可测试性。本文将…

Backblaze发布2024 Q1硬盘故障质量报告-1

作为一家在2021年在美国纳斯达克上市的云端备份公司&#xff0c;Backblaze一直保持着对外定期发布HDD和SSD的故障率稳定性质量报告&#xff0c;给大家提供了一份真实应用场景下的稳定性分析参考数据。 截至2024年第一季度末&#xff0c;Backblaze在其全球数据中心的云存储服务器…

书生·浦语大模型实战营之 OpenCompass大模型评测

书生浦语大模型实战营之 OpenCompass &#xff1a;是骡子是马&#xff0c;拉出来溜溜 为什么要研究大模型的评测&#xff1f; 百家争鸣&#xff0c;百花齐放。 首先&#xff0c;研究评测对于我们全面了解大型语言模型的优势和限制至关重要。尽管许多研究表明大型语言模型在多…

第三节课,前端

一、参考链接&#xff1b; 总 知识星球 | 深度连接铁杆粉丝&#xff0c;运营高品质社群&#xff0c;知识变现的工具 分 2022-03-18 星球直播笔记-用户中心&#xff08;下&#xff09; 语雀 二、登录 2.1登录网址 2.2前端页面修改 2.1 页面修改 2.2 页脚的超链接 网址&am…

【从零开始学习Minio | 第一篇】快速介绍什么是Minio

前言&#xff1a; 在当今数字化时代&#xff0c;数据的存储和管理已经成为了企业发展中的关键一环。随着数据量的不断增长和数据安全性的日益受到重视&#xff0c;传统的数据存储解决方案往往面临着诸多挑战。为了应对这些挑战&#xff0c;云存储技术应运而生&#xff0c;并在…

30分钟打造属于自己的Flutter内存泄漏检测工具---FlutterLeakCanary

30分钟打造属于自己的Flutter内存泄漏检测工具 思路检测Dart 也有弱引用-----WeakReference如何执行Full GC&#xff1f;如何知道一个引用他的文件路径以及类名&#xff1f; 代码实践第一步&#xff0c;实现Full GC第二步&#xff0c;如何根据对象引用&#xff0c;获取出他的类…

elementUI table表格相同元素合并行----支持多列

效果图如下: vue2代码如下&#xff1a; 只粘贴了js方法哦&#xff0c; methods: {// 设置合并行 setrowspans() { const columns [‘name’, ‘value’]; // 需要合并的列名 // 为每个需要合并的列设置默认 rowspan this.tableData.forEach(row > { columns.forEach(col …

ADOP带你了解什么是UTP电缆

非屏蔽双绞线 &#xff08;UTP&#xff09; 电缆在错综复杂的现代连接环境中成为数据通信的无声架构师。在本次探索中&#xff0c;我们将阐明 UTP 电缆的定义&#xff0c;解开它们的组成和功能&#xff0c;并深入研究 UTP 以太网电缆的多种类型和应用&#xff0c;使其成为我们有…

Day 26 数据库日志管理

数据库日志管理 一&#xff1a;日志管理 1.日志分类 ​ 错误日志 &#xff1a;启动&#xff0c;停止&#xff0c;关闭失败报错。rpm安装日志位置 /var/log/mysqld.log ​ 通用查询日志&#xff1a;所有的查询都记下来 ​ 二进制日志&#xff1a;实现备份&#xff0c;增量备份…

Linux系统配置JAVA环境

一、jar包下载 官网:https://www.oracle.com/java/technologies/downloads 二、文件上传 上传到linux服务器 解压 下面是解压的路径 三、修改profile文件 修改etc下的profile文件&#xff0c;添加以下内容 vim /etc/profileexport JAVA_HOME/root/java/jdk-17.0.11 expo…

leetcode17. 电话号码的字母组合

题目描述&#xff1a; 给定一个仅包含数字 2-9 的字符串&#xff0c;返回所有它能表示的字母组合。答案可以按 任意顺序 返回。给出数字到字母的映射如下&#xff08;与电话按键相同&#xff09;。注意 1 不对应任何字母。 示例 1&#xff1a; 输入&#xff1a;digits "…

VALSE 2024 Tutorial内容总结--开放词汇视觉感知

视觉与学习青年学者研讨会&#xff08;VALSE&#xff09;旨在为从事计算机视觉、图像处理、模式识别与机器学习研究的中国青年学者提供一个广泛而深入的学术交流平台。该平台旨在促进国内青年学者的思想交流和学术合作&#xff0c;以期在相关领域做出显著的学术贡献&#xff0c…

红海云OA存在任意文件上传漏洞【附poc】

漏洞复现 1、fofa poc见文末 body"RedseaPlatform" 打开burp进行抓包发送到repeater&#xff0c;如下图所示&#xff1a; 打入poc&#xff08;文末获取&#xff09;&#xff0c;成功上传。 「你即将失去如下所有学习变强机会」 学习效率低&#xff0c;学不到实战内…

DDR5内存新标准问世,体验前所未有的数据传输速度

DDR 5&#xff0c;新标准发布 JEDEC 发布了 JESD79-5C DDR5 SDRAM 标准&#xff0c;带来了关键更新&#xff0c;包括&#xff1a;* 增强可靠性和安全性* 优化高性能服务器和新兴技术&#xff08;如 AI 和机器学习&#xff09;的性能* 标准可从 JEDEC 网站下载 JESD79-5C 引入每…

Redis 入坑基本指南

引言 本指南将帮助您了解如何安装、配置和基本使用 Redis。Redis 是一款开源的高性能键值存储系统&#xff0c;可用于缓存、数据库、消息中间件等多种用途。 1. 安装 Redis a. 下载 Redis&#xff1a; 可以从 Redis 官方网站&#xff08;https://redis.io&#xff09;下载最…

Ansible --- playbook 脚本+inventory 主机清单

一 inventory 主机清单 Inventory支持对主机进行分组&#xff0c;每个组内可以定义多个主机&#xff0c;每个主机都可以定义在任何一个或 多个主机组内。 如果是名称类似的主机&#xff0c;可以使用列表的方式标识各个主机。vim /etc/ansible/hosts[webservers]192.168.10.1…

MIT加州理工等革命性KAN破记录,发现数学定理碾压DeepMind!KAN论文解读

KAN的数学原理 如果f是有界域上的多元连续函数&#xff0c;那么f可以被写成关于单个变量和加法二元操作的连续函数的有限组合。更具体地说&#xff0c;对于光滑函数f&#xff1a;[0, 1]ⁿ → R&#xff0c;有 f ( x ) f ( x 1 , … , x n ) ∑ q 1 2 n 1 Φ q ∑ p 1 n …

数据结构之链表深度讲解

小伙伴们&#xff0c;大家好呀&#xff0c;上次听我讲完顺序表想必收获不少吧&#xff0c;嘿嘿&#xff0c;这篇文章你也一样可以学到很多&#xff0c;系好安全带&#xff0c;咱们要发车了。 因为有了上一次顺序表的基础&#xff0c;所以这次我们直接进入正题&#xff0c;温馨…