当前位置: 首页 > news >正文

练习项目后端代码解析切面篇(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配置类全部一下子冒出来了,还好这个项目比较小,总算是看完了,现在我很难想象以后工作时真要看项目代码时,那得是件多煎熬的事啊,下一篇看看项目里的工具类代码咋写的。
在这里插入图片描述

相关文章:

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

前言 之前注解篇时我说&#xff0c;通常情况下一个自定义注解一般对应一个切面&#xff0c;虽然项目里的切面和注解个数相同&#xff0c;但是好像有一个名字看起来并不对应&#xff0c;无所谓&#xff0c;先看了再说。 ExceptionLogAspect切面 我在里面做了具体注释&#x…...

TypeScript常见面试题第六节

题目二十六:TypeScript 中的装饰器? 一、讲解视频 TS面试题二十六:TypeScript 中的可选链? 二、题目解析 本题目考察可选链的相关知识,可选链是比较新的一个语法,是一种访问嵌套对象属性的安全的方式。即使中间的属性不存在,也不会出现错误。如果可选链 ?. 前面的值为…...

LeetCode 面试经典150题 228.汇总区间

题目&#xff1a; 给定一个 无重复元素 的 有序 整数数组 nums 。 返回 恰好覆盖数组中所有数字 的 最小有序 区间范围列表 。也就是说&#xff0c;nums 的每个元素都恰好被某个区间范围所覆盖&#xff0c;并且不存在属于某个范围但不属于 nums 的数字 x 。 列表中的每个区…...

大数据分析入门10分钟快速了解SQL

SQL是什么&#xff1f; SQL全称Structured Query Language(结构化查询语言”) 为什么要用SQL&#xff1f; SQL通用 常见的表格分析操作&#xff0c;Excel也能做&#xff0c;为什么不用呢&#xff1f; 因为处理上亿行大数据时&#xff0c;Excel并不够用。 而常见的大数据引…...

设置多用户远程登录windows server服务器

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

一文了解栈

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

C语言----汉诺塔问题

1.什么是汉诺塔问题 简单来说&#xff0c;就是有三个柱子&#xff0c;分别为A柱&#xff0c;B柱&#xff0c;C柱。其中A柱从上往下存放着从小到大的圆盘&#xff0c;我们需要借助B柱和C柱&#xff0c;将A柱上的所有圆盘转移到C柱上&#xff0c;并且一次只能移动一个圆盘&#…...

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 --靶机个人习惯&#xff0c;也方便后续操作&#xff0c;将IP地址赋值给一个变…...

纯血鸿蒙APP实战开发——手写绘制及保存图片

介绍 本示例使用drawing库的Pen和Path结合NodeContainer组件实现手写绘制功能。手写板上完成绘制后&#xff0c;通过调用image库的packToFile和packing接口将手写板的绘制内容保存为图片&#xff0c;并将图片文件保存在应用沙箱路径中。 效果图预览 使用说明 在虚线区域手写…...

在什么情况下表单会被重复提交?如何避免?

表单被重复提交是Web应用中常见的问题&#xff0c;通常在用户提交表单后点击按钮多次&#xff0c;或在表单提交后刷新页面时发生。这可能导致数据的重复处理&#xff0c;比如重复记录或订单。 何时会发生表单重复提交&#xff1f; 用户多次点击提交按钮&#xff1a;在网络延迟…...

JavaScript 中的 Class 类

&#x1f525; 个人主页&#xff1a;空白诗 文章目录 &#x1f525; 引言&#x1f3af; 基础知识&#x1f3d7;️ 构造函数 (Constructor)&#x1f510; 私有字段 (Private Fields)&#x1f510; 私有方法 (Private Methods)&#x1f9ec; 继承 (Inheritance)&#x1f4e6; 静态…...

python实验三 实现UDP协议、TCP协议进行服务器端与客户端的交互

实验三 实验题目 1、请利用生成器构造一下求阶乘的函数Factorial()&#xff0c;定义一个函数m()&#xff0c;在m()中调用生成器Factorial()生成小于100的阶乘序列存入集合s中&#xff0c;输出s。 【代码】 def factorial():n1f1while 1:​ f * n​ yield (f)​ n1…...

ServiceNow 研究:通过RAG减少结构化输出中的幻觉

论文地址&#xff1a;https://arxiv.org/pdf/2404.08189 原文地址&#xff1a;rag-hallucination-structure-research-by-servicenow 在灾难性遗忘和模型漂移中&#xff0c;幻觉仍然是一个挑战。 2024 年 4 月 18 日 灾难性遗忘&#xff1a; 这是在序列学习或连续学习环境中出现…...

ADS基础教程10-多态性(动态模型选择)

目录 一、多态性定义二、操作步骤&#xff11;.模型建立&#xff12;.模型选择&#xff13;.执行仿真 一、多态性定义 ADS中支持一个Symbol中&#xff0c;可以同时存在多个子图。在仿真时可以动态选择不同的子图继续宁仿真。 二、操作步骤 &#xff11;.模型建立 在上一章A…...

代码随想录第四十六天|单词拆分

题目链接&#xff1a;. - 力扣&#xff08;LeetCode&#xff09;...

RabbitMQ的介绍和使用

1.同步通讯和异步通讯 举个例子&#xff0c;同步通讯就像是在打电话&#xff0c;因此它时效性较强&#xff0c;可以立即得到结果&#xff0c;但如果你正在和一个MM打电话&#xff0c;其他MM找你的话&#xff0c;你们之间是不能进行消息的传递和响应的 异步通讯就像是微信&#…...

前端get请求日期类型参数向后端传参失败

1、背景 get请求&#xff0c;通过url上传参&#xff0c;因此日期类型是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 &#xff08;私服&#xff09;push 镜像提示&#xff1a;denied: requested access to the resource is denied 镜像push 语法&#xff1a;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…...

Python批量计算多张遥感影像的NDVI

本文介绍基于Python中的gdal模块&#xff0c;批量基于大量多波段遥感影像文件&#xff0c;计算其每1景图像各自的NDVI数值&#xff0c;并将多景结果依次保存为栅格文件的方法。 如下图所示&#xff0c;现在有大量.tif格式的遥感影像文件&#xff0c;其中均含有红光波段与近红外…...

6.k8s中的secrets资源

一、Secret secrets资源&#xff0c;类似于configmap资源&#xff0c;只是secrets资源是用来传递重要的信息的&#xff1b; secret资源就是将value的值使用base64编译后传输&#xff0c;当pod引用secret后&#xff0c;k8s会自动将其base64的编码&#xff0c;反编译回正常的字符…...

git 更换远程仓库地址三种方法总结

git 更换远程仓库地址三种方法总结 一、前言 由于私服的 gitlab 的地址变更&#xff0c;导致部分项目代码提交不上去&#xff0c;需要修改远端仓地址。 其它需要修改远程仓地址的情况如&#xff1a;切换git clone 协议由ssh变为https。 二、环境 windows 10git version 2.3…...

快速找出存(不存在)在某个(或多个)文件的文件夹

首先&#xff0c;需要用到的这个工具&#xff1a; 度娘网盘 提取码&#xff1a;qwu2 蓝奏云 提取码&#xff1a;2r1z 想要找出有下面这个文件存在的文件夹 切换到批量文件复制版块&#xff0c;快捷键Ctrl5 右侧&#xff0c;搜索添加 选定范围&#xff0c;勾选搜索文件夹、包…...

Linux USB转串口设备路径的查找方法

1、USB转串口设备 USB转串口设备是在嵌入式软件开发过程中经常要使用的&#xff0c;常常用于对接各种各样的串口设备。如果一台linux主机上使用多个usb转串口设备时&#xff0c;应用程序中就需要知道自己操作的是哪个串口设备。串口设备在系统上电时&#xff0c;由于驱动加载的…...

【初阶数据结构】单链表之环形链表

目录标题 前言环形链表的约瑟夫问题环形链表环形链表|| 前言 前面我们已经学习了关于单链表的一些基本东西&#xff0c;今天我们来学习单链表的一个拓展——环形链表&#xff0c;我们将用力扣和牛客网上的三道题目来分析讲解环形链表问题。 环形链表的约瑟夫问题 我们首先来看…...

【积分,微分,导数,偏导数公式推导】

1. 积分 积分是微积分的一个分支&#xff0c;用于计算曲边梯形的面积或者变速直线运动的总距离等。积分分为不定积分和定积分。 不定积分&#xff1a;给出一个函数&#xff0c;求出其所有可能的原函数。定积分&#xff1a;计算一个函数在特定区间上的积分。 2. 微分 微分是…...

java:递归实现的案例

//求第20个月兔子的对数 //每个月兔子对数&#xff1a;1&#xff0c;1&#xff0c;2&#xff0c;3&#xff0c;5&#xff0c;8 public class Test {//求第20个月兔子的对数//每个月兔子对数&#xff1a;1&#xff0c;1&#xff0c;2&#xff0c;3&#xff0c;5&#xff0c;8pu…...

Arxml文件解析03- 自动驾驶Radar服务radar_svc.arxml

<AR-PACKAGES><AR-PACKAGE><SHORT-NAME>bosch</SHORT-NAME><AR-PACKAGES>...</AR-PACKAGES>...

Elasticsearch安装步骤

引言 Elasticsearch是一个基于Lucene构建的开源、分布式、RESTful搜索和分析引擎。它设计用于云计算中&#xff0c;能够达到实时搜索&#xff0c;稳定&#xff0c;可靠&#xff0c;快速&#xff0c;安装使用方便。Elasticsearch为所有类型的数据提供近乎实时的搜索和分析。无论…...

Windows系统和unbtun系统连接usb 3.0海康可见MVS和红外艾睿相机

一.海康可见USB3.0工业面阵相机 海康usb相机需要去海康官网上下载对应系统的MVS客户端及SDK开发包 海康机器人-机器视觉-下载中心 选择Windows系统和unbtun&#xff08;我是linux aarch64,所以选择了对应压缩包解压&#xff09; Windows系统 1.双击安装包进入安装界面&…...

深入Django:用户认证与权限控制实战指南

title: 深入Django&#xff1a;用户认证与权限控制实战指南 date: 2024/5/7 18:50:33 updated: 2024/5/7 18:50:33 categories: 后端开发 tags: AuthDecoratorsPermissionsGuardianRESTAuthSessionMgmtMFA 第1章&#xff1a;入门Django与设置 1.1 Django安装与环境配置 在…...

Kubernetes - Dashboard 配置用户名密码方式登录

Kubernetes - Dashboard 配置用户名密码方式登录 前言&#xff1a; 为了 K8s 集群安全&#xff0c;默认情况下 Dashboard 以 Token的形式登录的&#xff0c;那如果我们想以用户名/密码的方式登录该怎么操作呢&#xff1f;其实只需要我们创建用户并进行 ClusterRoleBinding绑定即…...

AIGC能给人类社会带来哪些变革?

随着人工智能技术的飞速发展&#xff0c;AIGC&#xff08;人工智能生成内容&#xff09;正在成为推动社会变革的重要力量。本文将从技术角度出发&#xff0c;探讨AIGC技术如何影响和改变人类生活的各个方面。 一、AIGC技术概述 AIGC&#xff0c;即人工智能生成内容&#xff0…...

医药垃圾分类管理系统|基于SSM医药垃圾分类管理系统的系统设计与实现(源码+数据库+文档)

医药垃圾分类管理系统 目录 基于SSM医药垃圾分类管理系统设计与实现 一、前言 二、系统设计 三、系统功能设计 1系统登录模块 2管理员模块实现 3用户模块实现 四、数据库设计 五、核心代码 六、论文参考 七、最新计算机毕设选题推荐 八、源码获取&#xff1a; 博…...

用vim或gvim编辑程序

vim其实不难使用&#xff0c;学习一下就好了。简单功能很快学会。它有三种模式&#xff1a;命令模式&#xff0c;编辑模式&#xff0c;视模式。打开时在命令模式。在命令模式下按 i 进入编辑模式&#xff0c;在编辑模式下按<Esc>键退出编辑模式。在命令模式按 :wq 保存文…...

linus下Anaconda创建虚拟环境pytorch

一、虚拟环境 1.创建 输入下面命令 conda create -n env_name python3.8 输入y 2.激活环境 输入 conda activate env_name 二、一些常用的命令 在Linux的控制平台 切换到当前的文件夹 cd /根目录/次目录 查看conda目录 conda list 查看pip目录 pip list查看历史命…...

synchronized与volatile关键字

1.synchronized的特性 1.1互斥 synchronized 会起到互斥效果, 某个线程执行到某个对象的 synchronized 中时, 其他线程如果也执行到 同一个对象 synchronized 就会阻塞等待. 进入 synchronized 修饰的代码块, 相当于 加锁 退出 synchronized 修饰的代码块, 相当于 解锁 syn…...

Python基础之运算符操作

在Python中&#xff0c;运算符的作用就是用于执行各种的运算操作&#xff0c;常见的运算符有算数运算符、比较运算符、逻辑运算符、赋值运算符、成员运算符、身份运算符等。下面我们就来看看在Python中这些运算的详细操作。 算术运算符 算术运算符是用来执行一些基本的数学运…...

【busybox记录】【shell指令】uniq

目录 内容来源&#xff1a; 【GUN】【uniq】指令介绍 【busybox】【uniq】指令介绍 【linux】【uniq】指令介绍 使用示例&#xff1a; 去除重复行 - 默认输出 去除重复行 - 跳过第n段&#xff08;空格隔开&#xff09;&#xff0c;比较n1以后的内容&#xff0c;去重 去…...

Nginx从入门到精通速成

文章目录 一. **Nginx** **的简介**1.1 什么是 **nginx**1.2 正向代理1.3 反向代理1.4 **负载均衡**1.5 动静分离 二. **Nginx** **的安装**三. **Nginx** **的常用的命令**四. **Nginx** **的配置文件**五. **Nginx** **配置实例**反向代理实例**1**5.1 实现效果5.2 准备工作5…...

Flutter笔记:Widgets Easier组件库(4)使用按钮组

Flutter笔记 Widgets Easier组件库&#xff08;4&#xff09;&#xff1a;使用按钮组 - 文章信息 - Author: 李俊才 (jcLee95) Visit me at CSDN: https://jclee95.blog.csdn.netMy WebSite&#xff1a;http://thispage.tech/Email: 291148484163.com. Shenzhen ChinaAddress…...

Docker常用命令 镜像库设置

Docker常用命令 & 镜像库设置 1. 镜像操作2. 容器操作3. 网络操作4. Docker Compose操作5. Docker volume操作6. Docker run介绍7. 镜像库设置 1. 镜像操作 列出本地所有的镜像 docker images从远程仓库拉取镜像到本地 docker pull <image_name>删除本地的指定镜像…...

无人零售,重塑购物新纪元

在这个快节奏的时代&#xff0c;科技的每一次跃进都在悄无声息地改变着我们的生活方式。而今&#xff0c;无人零售正以雷霆之势&#xff0c;颠覆传统购物模式&#xff0c;为我们带来前所未有的便捷与智能体验。想知道无人零售如何彻底改变我们的购物方式吗&#xff1f;跟随我&a…...

【图片格式转换】ICO、JPG、JPEG、PNG图片格式在线免费转换

ICO、JPG、JPEG、PNG图片格式转换 图片格式转换 https://orcc.online 支持ICO、JPG、JPEG、PNG等 主页 https://www.orcc.online 其他工具 pdf在线免费转word文档 https://orcc.online/pdf 时间戳转换 https://orcc.online/timestamp Base64 编码解码 https://orcc.onlin…...

通过自然语言处理执行特定任务的AI Agents;大模型控制NPC执行一系列的动作;个人化的电子邮件助手Panza

✨ 1: OpenAgents 通过自然语言处理执行特定任务的AI代理 OpenAgents是一个开放平台&#xff0c;旨在使语言代理&#xff08;即通过自然语言处理执行特定任务的AI代理&#xff09;的使用和托管变得更加便捷和实用。它特别适合于日常生活中对数据分析、工具插件获取和网络浏览…...

4.2 JavaScript语法

4.2.1 JavaScript大小写 在JavaScript中大小写是严格区分的&#xff0c;无论是变量、函数名称、运算符和其他语法都必须严格按照要求的大小写进行声明和使用。例如变量hello与变量HELLO会被认为是完全不同的内容。 4.2.2 JavaScript分号 很多编程语言&#xff08;例如C、Java和…...

面试二十五、remove和earse的区别

vector中erase的作用是删除掉某个位置position或一段区域&#xff08;begin, end)中的元素&#xff0c;减少其size&#xff0c;返回被删除元素下一个元素的位置。 vector中remove的作用是将范围内为val的值都remove到后面&#xff0c;返回新的_last值&#xff08;非val部分的en…...

普乐蛙元宇宙VR体验馆设备集体亮相VR文旅景区展

普乐蛙全国巡展又双叒叕开始了! 这次来到的是“好客山东”↓↓ 山东2024休闲旅游产业展 4月25日至27日&#xff0c;2024休闲旅游产业展在临沂国际博览中心举办。本次展会以“潮购文旅好品&#xff0c;乐享时尚生活”为主题&#xff0c;汇聚全国文旅产业上下游500多家企业、上万…...

北京大学-知存科技存算一体联合实验室揭牌,开启知存科技产学研融合战略新升级

5月5日&#xff0c;“北京大学-知存科技存算一体技术联合实验室”在北京大学微纳电子大厦正式揭牌&#xff0c;北京大学集成电路学院院长蔡一茂、北京大学集成电路学院副院长鲁文高及学院相关负责人、知存科技创始人兼CEO王绍迪、知存科技首席科学家郭昕婕博士及企业研发相关负…...