练习项目后端代码解析切面篇(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会自动查找合适的组件并将其注入到当前类中。
	@Autowired
	ExceptionLogService 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 {
    // 注入操作日志服务
    @Autowired
    OperationLogService 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) {
        // 获取当前请求的ServletRequestAttributes
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        // 从ServletRequestAttributes中获取HttpServletRequest对象
        HttpServletRequest request = attributes.getRequest();
        // 获取当前用户名,通过JWT工具类解析
        String username = JwtUtils.getTokenBody(request.getHeader("Authorization")).getSubject();
        // 获取请求的URI
        String 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 {
    // 注入访问日志服务
    @Autowired
    VisitLogService visitLogService;
    // 注入访客服务
    @Autowired
    VisitorService visitorService;
    // 注入Redis服务
    @Autowired
    RedisService 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) {
            // 签发新的访客标识码并保存到数据库和Redis
            identification = saveUUID(request);
        } else {
            // 校验Redis中是否存在访客标识码
            boolean redisHas = redisService.hasValueInSet(RedisKeyConfig.IDENTIFICATION_SET, identification);
            // 如果Redis中不存在访客标识码,则检查数据库中是否存在
            if (!redisHas) {
                // 检查数据库中是否存在访客标识码
                boolean mysqlHas = visitorService.hasUUID(identification);
                // 如果数据库中存在,则保存至Redis
                if (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生成UUID
		String 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中是否存在uuid
		boolean redisHas = redisService.hasValueInSet(RedisKeyConfig.IDENTIFICATION_SET, uuid);
		if (!redisHas) {
			//保存至Redis
			redisService.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) {
	    // 获取请求的URI
	    String 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配置类全部一下子冒出来了,还好这个项目比较小,总算是看完了,现在我很难想象以后工作时真要看项目代码时,那得是件多煎熬的事啊,下一篇看看项目里的工具类代码咋写的。
在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

使用Simulink Test进行单元测试

本文摘要&#xff1a;主要介绍如何利用Simulink Test工具箱&#xff0c;对模型进行单元测试。内容包括&#xff0c;如何创建Test Harness模型&#xff0c;如何自动生成excel格式的测试用例模板来创建测试用例&#xff0c;如何手动填写excel格式的测试用例模板来手动创建测试用例…

Golang Map类型

文章目录 Map介绍Map的定义方式Map的增删查改新增和修改Map元素查找Map元素删除Map元素遍历Map元素 Map元素排序Map切片 Map介绍 Map介绍 在Go中&#xff0c;map是哈希表的引用&#xff0c;是一种key-value数据结构。map类型写作map[K]V&#xff0c;其中K和V分别对应key和value…

系统维护启动盘 优启吧

优启吧-《优启时代系统维护盘》2025典藏版&#xff08;UD/ISO&#xff09;

亿发解密:数据中台管理系统,引领企业数字化转型的智能数据体系

在当今数字化时代&#xff0c;数据已成为企业发展的关键驱动力。为了更好地利用数据&#xff0c;提升业务水平&#xff0c;企业需要建立一套完备的数据管理体系&#xff0c;而数据中台便应运而生。 什么是数据中台 数据中台是集方法论、组织和工具于一体的智能大数据体系。它…

一起深度学习(AlexNet网络)

AlexNet神经网络 代码实现&#xff1a; 代码实现&#xff1a; import torch from torch import nn from d2l import torch as d2lnet nn.Sequential(# 采用了11*11的卷积核来捕捉对象&#xff0c;因为原始输入数据比较大#步幅为4 &#xff0c;可减少输出的高度核宽度。#输出通…

微搭低代码入门06分页查询

目录 1 创建自定义代码2 编写分页代码3 创建页面4 创建变量5 配置数据列表总结 我们在数据模型章节介绍了微搭后端服务编写的三种方式&#xff0c;包括Http请求、自定义代码、云函数。本篇我们详细讲解一下利用自定义代码开发分页查询的功能。 1 创建自定义代码 打开控制台&am…

Adaboost (BiLSTM-Adaboost ELM-Adaboost RF--Adaboost RVM-Adaboost SVM-Adaboost)

Adaboost Adaboost&#xff08;多输入单输出matlab&#xff09;代码获取戳此处代码获取戳此处 Adaboost是一种迭代式集成学习算法&#xff0c;全称为“Adaptive Boosting”&#xff0c;即自适应增强。该算法的核心思想是针对同一个训练集训练不同的分类器&#xff08;弱分类器&…

win10禁止自动更新的终极方法

添加注册表值 1.运行&#xff0c;输入regedit 2.打开注册表编辑器依次进入以下路径“计算机\HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\WindowsUpdate\UX\Settings”。 3.在Settings项中&#xff0c;新建DWORD&#xff08;32位&#xff09;值(D)&#xff0c;重命名为以下命名“Fl…

《ESP8266通信指南》12-Lua 固件烧录

往期 《ESP8266通信指南》11-Lua开发环境配置-CSDN博客 《ESP8266通信指南》10-MQTT通信&#xff08;Arduino开发&#xff09;-CSDN博客 《ESP8266通信指南》9-TCP通信&#xff08;Arudino开发&#xff09;-CSDN博客 《ESP8266通信指南》8-连接WIFI&#xff08;Arduino开发…

【C语言】用数组和函数实现扫雷游戏

用数组和函数实现扫雷游戏 游戏界面&#xff1a; 代码如下&#xff1a; game.h #pragma once #include <stdio.h> #include <stdlib.h> #include <time.h> #define EASY_COUNT 10 #define ROW 9 #define COL 9 #define ROWS ROW2 #define COLS COL2 //初始…

PXE批量安装

系统装机的三种引导方式 u盘光盘网络装机 光盘&#xff1a; 1.类似于usb模式 2.刻录模式 系统安装过程 加载boot loader Boot Loader 是在操作系统内核运行之前运行的一段小程序。通过这段小程序&#xff0c;我们可以初始化硬件设备、建立内存空间的映射图&#xff0c;从…

jmeter分布式集群压测

目的&#xff1a;通过多台机器同时运行 性能压测 脚本&#xff0c;模拟更好的并发压力 简单点&#xff1a;就是一个人&#xff08;控制机&#xff09;做一个项目的时候&#xff0c;压力有点大&#xff0c;会导致结果不理想&#xff0c;这时候找几个人&#xff08;执行机&#x…

java10基础(this super关键字 重写 final关键字 多态 抽象类)

目录 一. this和super关键字 1. this关键字 2. super关键字 二. 重写 三. final关键字 四. 多态 五. 抽象类 1. 抽象方法 2. 抽象类 3. 面向抽象设计 一. this和super关键字 1. this关键字 this 当前对象的引用 this.属性 this.方法名() this() -- 调用构造函数 …

电源功率模组: 完整的设计和验证流程解决四个维度的设计挑战

概述 电动汽车、新能源、光伏、风电等领域广泛使用高功率开关电源功率模组。IGBT和MOSFET是模组中常用器件。本文讨论这些技术&#xff0c;以及为实现高达1700伏特电压、1600安培电流、温度稳定和低电磁辐射的复杂指标带来的设计挑战。本文也总结今天的设计方法和优缺点。最后…

Java毕业设计 基于SpringBoot vue企业信息管理系统

Java毕业设计 基于SpringBoot vue企业信息管理系统 SpringBoot 企业信息管理系统 功能介绍 员工&#xff1a;登录 个人中心 修改密码 个人信息 会议管理 公告管理 个人计划管理 通讯录管理 外出登记管理 请假管理 上下班打卡管理 管理员&#xff1a;登录 个人中心 修改密码 …

跨越语言界限,多语言盲盒小程序带你领略全球风情

在全球化的今天&#xff0c;我们生活在一个多元文化的世界中&#xff0c;不同的语言、风俗、习惯共同构成了这个五彩斑斓的地球村。为了让每个人都能轻松体验到世界各地的独特风情&#xff0c;一款创新的多语言盲盒小程序应运而生&#xff0c;它跨越了语言的界限&#xff0c;让…

【linux-IMX6ULL中断配置流程】

目录 1. Cortex-A7和GIC中断概述1. 1 Cortex-A7中断系统&#xff1a;1. 2 GIC中断控制器简介&#xff1a; 2. 中断配置概述3. 底层中断文件配置3.1 对启动文件.s的配置思路3.2 对中断函数配置思路 4. 上层中断配置流程 1. Cortex-A7和GIC中断概述 学习IMX6UL的中断处理系统&…

页面嵌套,界面套娃,除了用iframe,还有其他方式吗?

UIOTOS可以了解下&#xff0c;uiotos.net&#xff0c;通过连线来代替脚本逻辑开发&#xff0c;复杂的交互界面&#xff0c;通过页面嵌套轻松解决&#xff0c;是个很新颖的思路&#xff0c;前端零代码&#xff01; 蓝图连线尤其是独创的页面嵌套和属性继承技术&#xff0c;好家…

独有病眼花,春风吹不落。 (二维坐标压缩成一个点,并查集)

本题链接&#xff1a;登录—专业IT笔试面试备考平台_牛客网 题目&#xff1a; 样例&#xff1a; 输入 3 8 1 1 D 1 1 R 1 2 D 2 1 D 2 2 R 3 1 R 3 2 R 2 3 D 输出 8 思路&#xff1a; 根据题意&#xff0c;要求连接线段后&#xff0c;操作多少次&#xff0c;连接的线段闭合&…

javaweb学习笔记1

1、基本概念 1.1、前言 web开发&#xff1a; web&#xff0c;网页的意思&#xff0c;www.baidu.com 静态web html,css 提供给所有人看的数据始终不会发生变化&#xff01; 动态web 淘宝&#xff0c;几乎是所有的网站&#xff1b; 提供给所有人看的数据始终会发生变化&…