练习项目后端代码解析切面篇(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的注解使用次数反而更加好理解。
最后实现功能
- 访问日志记录:当一个方法被VisitLogger注解修饰时,该方法执行前后会被VisitLogAspect切面拦截,并在方法执行前后执行日志记录逻辑。
- 访客标识码验证:在方法执行前,切面会检查请求头中是否包含identification字段,这是访客的唯一标识码。如果未包含,切面会生成一个新的访客标识码并保存到数据库和Redis中。
- 行为判断和日志内容设置:根据VisitLogger注解的行为和内容描述,切面会判断并设置访问日志的备注和内容字段。
- 访问日志保存:在方法执行后,切面会创建一个VisitLog对象,并填充其属性,包括访客标识码、请求URI、HTTP方法、行为描述、内容描述、IP地址、执行时间、User-Agent头等。然后,它会将这个访问日志对象保存到服务层进行持久化。
- Redis使用:切面使用Redis服务来存储和验证访客标识码。它将访客标识码保存到Redis中的一个集合中,并在验证访客标识码时检查这个集合中是否包含该标识码。
- 数据库访问:切面会访问数据库来验证访客标识码是否已存在于数据库中,以及是否需要生成新的访客标识码。
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配置类全部一下子冒出来了,还好这个项目比较小,总算是看完了,现在我很难想象以后工作时真要看项目代码时,那得是件多煎熬的事啊,下一篇看看项目里的工具类代码咋写的。
相关文章:

练习项目后端代码解析切面篇(Aspect)
前言 之前注解篇时我说,通常情况下一个自定义注解一般对应一个切面,虽然项目里的切面和注解个数相同,但是好像有一个名字看起来并不对应,无所谓,先看了再说。 ExceptionLogAspect切面 我在里面做了具体注释&#x…...
TypeScript常见面试题第六节
题目二十六:TypeScript 中的装饰器? 一、讲解视频 TS面试题二十六:TypeScript 中的可选链? 二、题目解析 本题目考察可选链的相关知识,可选链是比较新的一个语法,是一种访问嵌套对象属性的安全的方式。即使中间的属性不存在,也不会出现错误。如果可选链 ?. 前面的值为…...

LeetCode 面试经典150题 228.汇总区间
题目: 给定一个 无重复元素 的 有序 整数数组 nums 。 返回 恰好覆盖数组中所有数字 的 最小有序 区间范围列表 。也就是说,nums 的每个元素都恰好被某个区间范围所覆盖,并且不存在属于某个范围但不属于 nums 的数字 x 。 列表中的每个区…...
大数据分析入门10分钟快速了解SQL
SQL是什么? SQL全称Structured Query Language(结构化查询语言”) 为什么要用SQL? SQL通用 常见的表格分析操作,Excel也能做,为什么不用呢? 因为处理上亿行大数据时,Excel并不够用。 而常见的大数据引…...

设置多用户远程登录windows server服务器
##设置多用户远程登录windows server服务器 ###1、远程登录windows server 2016 运行—>mstsc—>远程IP地址—>用户和密码 2、远程windows服务器设置多用户策略 运行—>gpedit.msc->计算机配置—管理模板—windows组件—远程桌面服务—远程桌面会话主机----连…...

一文了解栈
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 前言一、栈是什么?二、栈的实现思路1.顺序表实现2.单链表实现3.双向链表实现 三、接口函数的实现1.栈的定义2.栈的初始化3.栈的销毁4.入栈5.出栈6.返回栈…...

C语言----汉诺塔问题
1.什么是汉诺塔问题 简单来说,就是有三个柱子,分别为A柱,B柱,C柱。其中A柱从上往下存放着从小到大的圆盘,我们需要借助B柱和C柱,将A柱上的所有圆盘转移到C柱上,并且一次只能移动一个圆盘&#…...
Python中驼峰命名法和下划线命名法相互转换的实战代码
大家好,我是爱编程的喵喵。双985硕士毕业,现担任全栈工程师一职,热衷于将数据思维应用到工作与生活中。从事机器学习以及相关的前后端开发工作。曾在阿里云、科大讯飞、CCF等比赛获得多次Top名次。现为CSDN博客专家、人工智能领域优质创作者。喜欢通过博客创作的方式对所学的…...

【hackmyvm】vivifytech靶机
渗透思路 信息收集端口扫描端口服务信息目录扫描爆破hydra--sshgit提权 信息收集 ┌──(kali㉿kali)-[~] └─$ fping -ag 192.168.9.0/24 2>/dev/null 192.168.9.119 --主机 192.168.9.164 --靶机个人习惯,也方便后续操作,将IP地址赋值给一个变…...

纯血鸿蒙APP实战开发——手写绘制及保存图片
介绍 本示例使用drawing库的Pen和Path结合NodeContainer组件实现手写绘制功能。手写板上完成绘制后,通过调用image库的packToFile和packing接口将手写板的绘制内容保存为图片,并将图片文件保存在应用沙箱路径中。 效果图预览 使用说明 在虚线区域手写…...
在什么情况下表单会被重复提交?如何避免?
表单被重复提交是Web应用中常见的问题,通常在用户提交表单后点击按钮多次,或在表单提交后刷新页面时发生。这可能导致数据的重复处理,比如重复记录或订单。 何时会发生表单重复提交? 用户多次点击提交按钮:在网络延迟…...

JavaScript 中的 Class 类
🔥 个人主页:空白诗 文章目录 🔥 引言🎯 基础知识🏗️ 构造函数 (Constructor)🔐 私有字段 (Private Fields)🔐 私有方法 (Private Methods)🧬 继承 (Inheritance)📦 静态…...

python实验三 实现UDP协议、TCP协议进行服务器端与客户端的交互
实验三 实验题目 1、请利用生成器构造一下求阶乘的函数Factorial(),定义一个函数m(),在m()中调用生成器Factorial()生成小于100的阶乘序列存入集合s中,输出s。 【代码】 def factorial():n1f1while 1: f * n yield (f) n1…...

ServiceNow 研究:通过RAG减少结构化输出中的幻觉
论文地址:https://arxiv.org/pdf/2404.08189 原文地址:rag-hallucination-structure-research-by-servicenow 在灾难性遗忘和模型漂移中,幻觉仍然是一个挑战。 2024 年 4 月 18 日 灾难性遗忘: 这是在序列学习或连续学习环境中出现…...

ADS基础教程10-多态性(动态模型选择)
目录 一、多态性定义二、操作步骤1.模型建立2.模型选择3.执行仿真 一、多态性定义 ADS中支持一个Symbol中,可以同时存在多个子图。在仿真时可以动态选择不同的子图继续宁仿真。 二、操作步骤 1.模型建立 在上一章A…...

代码随想录第四十六天|单词拆分
题目链接:. - 力扣(LeetCode)...

RabbitMQ的介绍和使用
1.同步通讯和异步通讯 举个例子,同步通讯就像是在打电话,因此它时效性较强,可以立即得到结果,但如果你正在和一个MM打电话,其他MM找你的话,你们之间是不能进行消息的传递和响应的 异步通讯就像是微信&#…...
前端get请求日期类型参数向后端传参失败
1、背景 get请求,通过url上传参,因此日期类型是string类型数据 2、异常信息 nested exception is org.springframework.core.convert.ConversionFailedException: Failed to convert from type [java.lang.String] to type [java.time.LocalDate] for…...

【docker 】 push 镜像提示:denied: requested access to the resource is denied
往 Docker Registry (私服)push 镜像提示:denied: requested access to the resource is denied 镜像push 语法:docker push <registry-host>:<registry-port>/<repository>:<tag> docker push 192.16…...
浏览器各类好用插件使用及常见问题(技巧)总结
目录 Vimium C快捷键问题为什么Vimium C - 全键盘操作浏览器插件在百度页面中, x ,o,f等快捷键不起作用如何使用viminum c插件进行自定义快捷键?vimucm 为什么在浏览器首页时快捷键不起作用? 网页截图问题firefox 网页截图使用 idm问题浏览器点击idm 不下载? 待续、更新中 V…...
Leetcode 3577. Count the Number of Computer Unlocking Permutations
Leetcode 3577. Count the Number of Computer Unlocking Permutations 1. 解题思路2. 代码实现 题目链接:3577. Count the Number of Computer Unlocking Permutations 1. 解题思路 这一题其实就是一个脑筋急转弯,要想要能够将所有的电脑解锁&#x…...
Auto-Coder使用GPT-4o完成:在用TabPFN这个模型构建一个预测未来3天涨跌的分类任务
通过akshare库,获取股票数据,并生成TabPFN这个模型 可以识别、处理的格式,写一个完整的预处理示例,并构建一个预测未来 3 天股价涨跌的分类任务 用TabPFN这个模型构建一个预测未来 3 天股价涨跌的分类任务,进行预测并输…...
工程地质软件市场:发展现状、趋势与策略建议
一、引言 在工程建设领域,准确把握地质条件是确保项目顺利推进和安全运营的关键。工程地质软件作为处理、分析、模拟和展示工程地质数据的重要工具,正发挥着日益重要的作用。它凭借强大的数据处理能力、三维建模功能、空间分析工具和可视化展示手段&…...
Spring Boot+Neo4j知识图谱实战:3步搭建智能关系网络!
一、引言 在数据驱动的背景下,知识图谱凭借其高效的信息组织能力,正逐步成为各行业应用的关键技术。本文聚焦 Spring Boot与Neo4j图数据库的技术结合,探讨知识图谱开发的实现细节,帮助读者掌握该技术栈在实际项目中的落地方法。 …...

MySQL 8.0 OCP 英文题库解析(十三)
Oracle 为庆祝 MySQL 30 周年,截止到 2025.07.31 之前。所有人均可以免费考取原价245美元的MySQL OCP 认证。 从今天开始,将英文题库免费公布出来,并进行解析,帮助大家在一个月之内轻松通过OCP认证。 本期公布试题111~120 试题1…...
大学生职业发展与就业创业指导教学评价
这里是引用 作为软工2203/2204班的学生,我们非常感谢您在《大学生职业发展与就业创业指导》课程中的悉心教导。这门课程对我们即将面临实习和就业的工科学生来说至关重要,而您认真负责的教学态度,让课程的每一部分都充满了实用价值。 尤其让我…...
大语言模型(LLM)中的KV缓存压缩与动态稀疏注意力机制设计
随着大语言模型(LLM)参数规模的增长,推理阶段的内存占用和计算复杂度成为核心挑战。传统注意力机制的计算复杂度随序列长度呈二次方增长,而KV缓存的内存消耗可能高达数十GB(例如Llama2-7B处理100K token时需50GB内存&a…...

SiFli 52把Imagie图片,Font字体资源放在指定位置,编译成指定img.bin和font.bin的问题
分区配置 (ptab.json) img 属性介绍: img 属性指定分区存放的 image 名称,指定的 image 名称必须是当前工程生成的 binary 。 如果 binary 有多个文件,则以 proj_name:binary_name 格式指定文件名, proj_name 为工程 名&…...
SQL慢可能是触发了ring buffer
简介 最近在进行 postgresql 性能排查的时候,发现 PG 在某一个时间并行执行的 SQL 变得特别慢。最后通过监控监观察到并行发起得时间 buffers_alloc 就急速上升,且低水位伴随在整个慢 SQL,一直是 buferIO 的等待事件,此时也没有其他会话的争抢。SQL 虽然不是高效 SQL ,但…...
C语言中提供的第三方库之哈希表实现
一. 简介 前面一篇文章简单学习了C语言中第三方库(uthash库)提供对哈希表的操作,文章如下: C语言中提供的第三方库uthash常用接口-CSDN博客 本文简单学习一下第三方库 uthash库对哈希表的操作。 二. uthash库哈希表操作示例 u…...