8、springboot3 vue3开发平台-后端-使用aop 添加系统访问日志

news2024/11/23 3:07:04

1. 添加依赖, 创建数据库

		 <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>
        <!-- IP地址解析 -->
        <dependency>
            <groupId>org.lionsoul</groupId>
            <artifactId>ip2region</artifactId>
            <version>2.6.5</version>
        </dependency>

数据表创建:

CREATE TABLE `sys_log` (
  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID',
  `log_type` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '日志类型',
  `create_date` datetime NOT NULL COMMENT '创建时间',
  `oper_name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT '' COMMENT '操作人员',
  `request_uri` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '请求URI',
  `request_type` varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '请求方式',
  `request_params` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci COMMENT '请求参数',
  `request_ip` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '请求IP',
  `oper_location` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '请求地点',
  `response_result` varchar(2000) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT '' COMMENT '返回参数',
  `oper_status` int DEFAULT '0' COMMENT '操作状态(0正常1异常)',
  `exception_info` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci COMMENT '异常信息',
  `start_time` datetime DEFAULT NULL COMMENT '开始时间',
  `end_time` datetime DEFAULT NULL COMMENT '结束时间',
  `execute_time` bigint DEFAULT NULL COMMENT '执行时间',
  `user_agent` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '用户代理',
  `device_name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '操作系统',
  `browser_name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '浏览器名称',
  `module` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '模块名称',
  `oper_desc` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '操作说明',
  PRIMARY KEY (`id`) USING BTREE,
  KEY `idx_sys_log_lt` (`log_type`) USING BTREE,
  KEY `idx_sys_log_cd` (`create_date`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=716 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci ROW_FORMAT=DYNAMIC COMMENT='系统日志表';

2. ip2region.xdb 下载使用

使用原因: 内网环境提供离线解析, ip2region.xdb文件,需要不定期的更新
地址: https://gitee.com/lionsoul/ip2region/tree/master/data
使用:下载后将其放到resources 下, 在工具类中加载配置文件
在这里插入图片描述

2.1 工具类封装

package com.ylp.sys.utils;

import jakarta.annotation.PostConstruct;
import jakarta.servlet.http.HttpServletRequest;
import org.lionsoul.ip2region.xdb.Searcher;
import org.springframework.core.io.ClassPathResource;
import org.springframework.stereotype.Component;
import org.springframework.util.FileCopyUtils;

import java.io.InputStream;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.HashMap;

@Component
public class IPUtils {
    private static Searcher searcher;

    /**
     * 在 Nginx 等代理之后获取用户真实 IP 地址
     * @return 用户的真实 IP 地址
     */
    public static String getIpAddress(HttpServletRequest request) {
        if (request == null) {
            return null;
        }
        String ip = request.getHeader("x-forwarded-for");
        if (isIpaddress(ip)) {
            ip = request.getHeader("Proxy-Client-IP");
        }
        if (isIpaddress(ip)) {
            ip = request.getHeader("WL-Proxy-Client-IP");
        }
        if (isIpaddress(ip)) {
            ip = request.getHeader("HTTP_CLIENT_IP");
        }
        if (isIpaddress(ip)) {
            ip = request.getHeader("HTTP_X_FORWARDED_FOR");
        }
        if (isIpaddress(ip)) {
            ip = request.getRemoteAddr();
            if ("127.0.0.1".equals(ip) || "0:0:0:0:0:0:0:1".equals(ip)) {
                //根据网卡取本机配置的IP
                try {
                    InetAddress inet = InetAddress.getLocalHost();
                    ip = inet.getHostAddress();
                } catch (UnknownHostException e) {
                    e.printStackTrace();
                }
            }
        }
        return ip;
    }

    /**
     * 判断是否为 IP 地址
     * @param ip  IP 地址
     */
    public static boolean isIpaddress(String ip) {
        return ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip);
    }

    /**
     * 获取本地 IP 地址
     * @return 本地 IP 地址
     */
    public static String getHostIp() {
        try {
            return InetAddress.getLocalHost().getHostAddress();
        } catch (UnknownHostException e) {
            e.printStackTrace();
        }
        return "127.0.0.1";
    }

    /**
     * 获取主机名
     * @return 本地主机名
     */
    public static String getHostName() {
        try {
            return InetAddress.getLocalHost().getHostName();
        } catch (UnknownHostException e) {
            e.printStackTrace();
        }
        return "未知";
    }

    /**
     * 根据 IP 地址从 ip2region.db 中获取地理位置
     * @param ip IP 地址
     * @return IP归属地
     */
    public static String getCityInfo(String ip) {
        try {
            return searcher.search(ip);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 在服务启动时加载 ip2region.db 到内存中
     * 解决打包 jar 后找不到 ip2region.db 的问题
     * @throws Exception 出现异常应该直接抛出终止程序启动,避免后续 invoke 时出现更多错误
     */
    @PostConstruct
    private static void initIp2regionResource() {
        try {
            InputStream inputStream = new ClassPathResource("/ipdb/ip2region.xdb").getInputStream();
            byte[] dbBinStr = FileCopyUtils.copyToByteArray(inputStream);
            // 创建一个完全基于内存的查询对象
            searcher = Searcher.newWithBuffer(dbBinStr);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 根据 IP 地址返回归属地,国内返回但省份,国外返回到国家
     * @param ip IP 地址
     * @return IP 归属地
     */
    public static String getIpRegion(String ip) {
        initIp2regionResource();
        HashMap<String, String> cityInfo = new HashMap<>();
        String searchIpInfo = getCityInfo(ip);
        //-------------------------------------------------------
        //searchIpInfo 的数据格式: 国家|区域|省份|城市|ISP
        //192.168.31.160 0|0|0|内网IP|内网IP
        //47.52.236.180 中国|0|香港|0|阿里云
        //220.248.12.158 中国|0|上海|上海市|联通
        //164.114.53.60 美国|0|华盛顿|0|0
        //-------------------------------------------------------
        String[] splitIpInfo = searchIpInfo.split("\\|");
        cityInfo.put("ip",ip);
        cityInfo.put("searchInfo", searchIpInfo);
        cityInfo.put("country",splitIpInfo[0]);
        cityInfo.put("region",splitIpInfo[1]);
        cityInfo.put("province",splitIpInfo[2]);
        cityInfo.put("city",splitIpInfo[3]);
        cityInfo.put("ISP",splitIpInfo[3]);

        //--------------国内属地返回省份--------------
        if ("中国".equals(cityInfo.get("country"))){
            return cityInfo.get("province");
        }
        //------------------内网 IP----------------
        if ("0".equals(cityInfo.get("country"))){
//            if ("内网IP".equals(cityInfo.get("ISP"))){
//                return "";
//            }
//            else return "";
            return cityInfo.get("ISP");
        }
        //--------------国外属地返回国家--------------
        else {
            return cityInfo.get("country");
        }
    }

}

3. 使用AOP 注册访问日志

3.1 创建注解,用于标注接口

package com.ylp.sys.annotation;

import java.lang.annotation.*;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface SysLogAnnotation {
    String module()  default "";//模块
    String operDesc() default "";   // 操作说明
}

3.2 创建AOP 配置

package com.ylp.sys.aop;

import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.core.util.URLUtil;
import cn.hutool.http.useragent.UserAgent;
import cn.hutool.http.useragent.UserAgentUtil;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import com.ylp.common.response.Result;
import com.ylp.sys.annotation.SysLogAnnotation;
import com.ylp.sys.auth.entity.UserInfo;
import com.ylp.sys.common.SysLogConstant;
import com.ylp.sys.domain.entity.SysLog;
import com.ylp.sys.service.SysLogService;
import com.ylp.sys.utils.IPUtils;
import jakarta.servlet.http.HttpServletRequest;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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 java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.Executor;

/**
 * 系统日志切面
 *
 * @author ylp
 *
 */
@Aspect
@Component
public class SysLogAspect {
    private static final Logger logger = LoggerFactory.getLogger(SysLogAspect.class);
    private ThreadLocal<SysLog> sysLogThreadLocal = new ThreadLocal<>();

    @Autowired
    private Executor customThreadPoolTaskExecutor;

    @Autowired
    private SysLogService sysLogService;

    /**
     * 日志切点
     */
    @Pointcut("execution(public * com.ylp..*controller.*.*(..))")
    public void sysLogOperAspect() {
    }

    @Pointcut("execution(public * com.ylp.sys.auth.controller.*.*(..))")
    public void sysLogAuthAspect() {
    }

    // 定义一个组合切点
    @Pointcut("sysLogOperAspect() || sysLogAuthAspect()")
    public void combinedExecution() {}

    /**
     * 前置通知
     *
     * @param joinPoint
     */
    @Before(value = "combinedExecution()")
    public void doBefore(JoinPoint joinPoint) {
        //System.out.println("doBefore aop===============joinPoint===="+ joinPoint);
      try {
          HttpServletRequest request = ((ServletRequestAttributes) Objects
                  .requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest();
          SysLog sysLog = new SysLog();
          // 创建人信息请根据实际项目获取方式获取
          //登录后拿用户信息
          if(StpUtil.isLogin()) {
              UserInfo userLoginInfo = (UserInfo) StpUtil.getSession().get("userInfo");
              sysLog.setOperName(userLoginInfo.getUsername());
          }
          sysLog.setStartTime(LocalDateTime.now());
          sysLog.setRequestUri(URLUtil.getPath(request.getRequestURI()));
          sysLog.setRequestParams(formatParams(request.getParameterMap()));
          sysLog.setRequestType(request.getMethod());
          sysLog.setRequestIp(IPUtils.getIpAddress(request));
          String userAgentStr = request.getHeader("User-Agent");
          sysLog.setUserAgent(userAgentStr);
          UserAgent userAgent = UserAgentUtil.parse(userAgentStr);
          sysLog.setDeviceName(userAgent.getOs().getName());
          sysLog.setBrowserName(userAgent.getBrowser().getName());

          // 获取请求体参数
          Object[] args = joinPoint.getArgs();
          for(Object arg : args){
              try {
                  String jsonString = JSON.toJSONString(arg);
                  JSONObject jsonObject = JSON.parseObject(jsonString);
                  if (jsonObject.containsKey("password")) {
                      jsonObject.put("password", "*****");
                  }
                  System.out.println("参数=" + JSON.toJSONString(jsonObject));
                  sysLog.setRequestParams(sysLog.getRequestParams() + JSON.toJSONString(jsonObject));
              } catch (Exception e) {
                  //e.printStackTrace();
              }
          }

          // 获取日志注解
          MethodSignature signature = (MethodSignature)joinPoint.getSignature();
          SysLogAnnotation annotation = signature.getMethod().getAnnotation(SysLogAnnotation.class);
          if (annotation != null) {
              sysLog.setModule(annotation.module());
              sysLog.setOperDesc(annotation.operDesc());
          }
          sysLogThreadLocal.set(sysLog);
         // System.out.println("doBefore aop111111===============");

//          logger.info("开始计时: {}  URI: {}  IP: {}", sysLog.getStartTime(), sysLog.getRequestUri(), sysLog.getRequestIp());
      } catch (Exception e) {
          logger.error(e.getMessage());
      }
    }

    /**
     * 返回通知
     *
     * @param ret
     */
    @AfterReturning(pointcut = "combinedExecution()", returning = "ret")
    public void doAfterReturning(Object ret) {
       // System.out.println("doAfterReturning aop===============");
        try {
            SysLog sysLog = sysLogThreadLocal.get();
            sysLog.setLogType(SysLogConstant.LOG_INGO);
            sysLog.setEndTime(LocalDateTime.now());
            sysLog.setExecuteTime(Long.valueOf(ChronoUnit.MILLIS.between(sysLog.getStartTime(), sysLog.getEndTime())));
            Result<?> r = Convert.convert(Result.class, ret);
//            if (SysLogConstant.TRUE.equals(String.valueOf(r.getCode()))) {
            if (r.getCode() == 0) {
                sysLog.setOperStatus(SysLogConstant.OPER_SUCCESS);
            } else {
                sysLog.setOperStatus(SysLogConstant.OPER_EXECPTION);
                sysLog.setExceptionInfo(r.getMessage());
            }
            sysLog.setResponseResult(JSON.toJSONString(r));
            customThreadPoolTaskExecutor.execute(new SaveLogThread(sysLog, sysLogService));
            sysLogThreadLocal.remove();

           // Runtime runtime = Runtime.getRuntime();
//            logger.info("计时结束: {}  用时: {}ms  URI: {}  总内存: {}  已用内存: {}", sysLog.getEndTime(), sysLog.getExecuteTime(),
//                    sysLog.getRequestUri(), ByteUtils.formatByteSize(runtime.totalMemory()),
//                    ByteUtils.formatByteSize(runtime.totalMemory() - runtime.freeMemory()));
        } catch (Exception e) {
            logger.error(e.getMessage());
        }
    }

    /**
     * 异常通知
     *
     * @param e
     */
    @AfterThrowing(pointcut = "combinedExecution()", throwing = "e")
    public void doAfterThrowable(Throwable e) {
        try {
            SysLog sysLog = sysLogThreadLocal.get();
            sysLog.setLogType(SysLogConstant.LOG_ERROR);
            sysLog.setEndTime(LocalDateTime.now());
            sysLog.setExecuteTime(Long.valueOf(ChronoUnit.MINUTES.between(sysLog.getStartTime(), sysLog.getEndTime())));
            sysLog.setOperStatus(SysLogConstant.OPER_EXECPTION);
            sysLog.setExceptionInfo(e.getMessage());
            customThreadPoolTaskExecutor.execute(new SaveLogThread(sysLog, sysLogService));
            sysLogThreadLocal.remove();

//            Runtime runtime = Runtime.getRuntime();
//            logger.info("计时结束: {}  用时: {}ms  URI: {}  总内存: {}  已用内存: {}", sysLog.getEndTime(), sysLog.getExecuteTime(),
//                    sysLog.getRequestUri(), ByteUtils.formatByteSize(runtime.totalMemory()),
//                    ByteUtils.formatByteSize(runtime.totalMemory() - runtime.freeMemory()));
        } catch (Exception e1) {
            logger.error(e1.getMessage());
        }
    }

    /**
     * 格式化参数
     *
     * @param parameterMap
     * @return
     */
    private String formatParams(Map<String, String[]> parameterMap) {
        if (parameterMap == null) {
            return null;
        }
        StringBuilder params = new StringBuilder();
        for (Map.Entry<String, String[]> param : (parameterMap).entrySet()) {
            if (params.length() != 0) {
                params.append("&");
            }
            params.append(param.getKey() + "=");
            if (StrUtil.endWithIgnoreCase(param.getKey(), "password")) {
                params.append("*");
            } else if (param.getValue() != null) {
                params.append(ArrayUtil.join(param.getValue(), ","));
            }
        }
        return params.toString();
    }

    /**
     * 保存日志线程
     *
     * @author ylp
     */
    private static class SaveLogThread extends Thread {
        private SysLog sysLog;
        private SysLogService sysLogService;

        public SaveLogThread(SysLog sysLog, SysLogService sysLogService) {
            this.sysLog = sysLog;
            this.sysLogService = sysLogService;
        }

        @Override
        public void run() {
            try {
                sysLog.setCreateDate(LocalDateTime.now());
                String ipLocation = IPUtils.getIpRegion(sysLog.getRequestIp());
                logger.info("ip地址{}", ipLocation);
                sysLog.setOperLocation(ipLocation);
                sysLogService.save(sysLog);
            } catch (Exception e) {
                logger.error(e.getMessage());
            }
        }
    }
}

3.3 配置线程池

将配置放到support 模块下, 后续其他模块也可以直接使用, 开发环境不要将线程数配置到极限,会影响其他应用。

package com.ylp.support.config.thread;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

import java.util.concurrent.Executor;
import java.util.concurrent.ThreadPoolExecutor;

/**
 * 线程池配置类
 *
 * @author ylp
 *
 */
@Configuration
public class ThreadPoolTaskExecutorConfig {

    @Bean
    public Executor customThreadPoolTaskExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        // Java虚拟机可用的处理器数
        int corePoolSize = Runtime.getRuntime().availableProcessors();
        // 配置核心线程数
        executor.setCorePoolSize(corePoolSize);
        // 配置最大线程数
//        executor.setMaxPoolSize(corePoolSize * 2 + 1);
        executor.setMaxPoolSize(corePoolSize + 1);
        // 配置队列大小
        executor.setQueueCapacity(100);
        // 空闲的多余线程最大存活时间
        executor.setKeepAliveSeconds(3);
        // 配置线程池中的线程的名称前缀
        executor.setThreadNamePrefix("thread-execute-");
        // 当线程池达到最大大小时,在调用者的线程中执行任务
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        // 执行初始化
        executor.initialize();
        return executor;
    }
}

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

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

相关文章

小程序~~3(自定义组件)

目录 1.自定义组件 创建和注册组件 数据和方法 属性properties 组件wxml的slot-插槽&#xff08;难&#xff09; 组件样式以及注意事项&#xff08;难&#xff09; 组件样式隔离&#xff08;难&#xff09; 修改checkbox样式&#xff08;难&#xff09; 数据监听函数o…

Taming Lookup Tables for Efficient Image Retouching

Abstract 高清屏幕在终端用户相机、智能手机和电视等边缘设备中的广泛使用&#xff0c;刺激了对图像增强的巨大需求。现有的增强模型通常针对高性能进行优化&#xff0c;但不能减少硬件推断时间和功耗&#xff0c;尤其是在计算和存储资源受限的边缘设备上。为此&#xff0c;我…

等保2.0与安全编程:携手共筑网络安全防线

一、基本概念 等保2.0&#xff08;网络安全等级保护2.0&#xff09;&#xff1a;作为我国网络安全领域的基本国策和制度&#xff0c;等保2.0是对原有信息安全等级保护&#xff08;等保1.0&#xff09;的全面升级。它于2019年正式实施&#xff0c;旨在应对云计算、大数据、物联网…

基于SSM的停车场管理系统 毕业设计-附源码42934

目 录 摘要 1 绪论 1.1 研究背景 1.2 研究意义 1.3研究方案 1.4论文章节安排 2相关技术介绍 2.1 B/S结构 2.2 SSM框架 2.3 Java语言 2.4 MySQL数据库 3系统分析 3.1 可行性分析 3.2 系统功能性分析 3.3.非功能性分析 3.4 系统用例分析 3.5系统流程分析 3.5.1…

JAVA基础 - 反射

目录 一. 简介 二. java.lang.Class类 三. java.lang.reflect包 四. 创建对象 五. 调用方法 六. 调用成员变量 一. 简介 反射是 Java 语言中的一种强大机制&#xff0c;允许程序在运行时动态地获取类的信息、访问类的成员&#xff08;包括字段、方法和构造函数&#xff…

fal.ai发布超分辨率模型——AuraSR V2

今天&#xff0c;我们发布了单步 GAN 升频器的第二个版本&#xff1a; AuraSR。 我们在上个月发布了 AuraSR v1&#xff0c;社区的反响让我们深受鼓舞&#xff0c;因此我们立即开始了新版本的训练。 AuraSR 基于 Adobe Gigagan 论文&#xff0c;以 lucidrain 的实现为起点。Gi…

Off-by-One Error: 编码中的常见陷阱 ⚠️

Off-by-One Error: 编码中的常见陷阱 ⚠️ Off-by-One Error: 编码中的常见陷阱 ⚠️摘要引言正文内容1. 什么是 Off-by-One 错误&#xff1f;Off-by-One 错误的示例 2. 如何识别 Off-by-One 错误&#xff1f;2.1 使用调试器2.2 单元测试 3. 如何预防 Off-by-One 错误&#xff…

Python酷库之旅-第三方库Pandas(059)

目录 一、用法精讲 226、pandas.Series.pad方法 226-1、语法 226-2、参数 226-3、功能 226-4、返回值 226-5、说明 226-6、用法 226-6-1、数据准备 226-6-2、代码示例 226-6-3、结果输出 227、pandas.Series.replace方法 227-1、语法 227-2、参数 227-3、功能 …

【Python机器学习】Logistic回归——从疝气病症预测病马的死亡率

用Logistic回归来预测患有疝病的马的存活问题。这里的数据包括368个样本和28个特征。疝病是描述马肠胃痛的术语&#xff0c;这种病并不一定源自马的肠胃问题。 该数据集中包含了医院检测马疝病的一些指标&#xff0c;有些指标比较主观&#xff0c;有的指标难以测量&#xff0c…

docker部署elasticsearch和Kibana

部署elasticsearch 通过下面的Docker命令即可安装单机版本的elasticsearch&#xff1a; docker run -d \--name es \-e "ES_JAVA_OPTS-Xms512m -Xmx512m" \-e "discovery.typesingle-node" \-v es-data:/usr/share/elasticsearch/data \-v es-plugins:/u…

【STC32G12K128开发板】第3-9讲:手势识别(基于PAJ7620U2)

第3-9讲&#xff1a;手势识别&#xff08;基于PAJ7620U2&#xff09; 学习目的了解IK-PAJ7620U2手势识别传感器模块的功能。掌握IK-PAJ7620U2的I2C协议、操作流程&#xff0c;并编程实现配置IK-PAJ7620U2工作于接近检测和手势识别模式以及读取检测结果。 PAJ7620手势识别模块 产…

灰狼优化算法(GWO)的详细解读

一、引言 在优化问题中&#xff0c;我们常常需要寻找一个最优解&#xff0c;使得某个目标函数达到最小或最大值。为了高效地解决这类问题&#xff0c;研究者们从自然界中的生物行为汲取灵感&#xff0c;提出了多种群智能优化算法。灰狼优化算法&#xff08;Grey Wolf Optimize…

行为验证码的介绍

1.什么是行为验证码 行为式验证码是一种较为流行的验证码。从字面来理解&#xff0c;就是通过用户的操作行为来完成验证&#xff0c;而无需去读懂扭曲的图片文字。常见的有两种&#xff1a;拖动式与点触式。 2.行为验证码的概念 行为式验证的核心思想是利用用户的“行为特征”…

单火供电零线发生器 单火变零火线开关面板零火开关老房改造必备

创作 史新华 零线发生器套件与单火线供电套件&#xff0c;作为现代智能家居解决方案中的创新之作&#xff0c;它们犹如智能电气领域的魔术师&#xff0c;巧妙地解决了传统智能开关在单火线路环境中因无零线而难以应用的难题。这些套件&#xff0c;如同智能电气世界的桥梁&…

SQLite库笔记:命令行shell

SQLite项目提供了一个简单的命令行程序sqlite3&#xff0c;它允许用户对SQLite数据库手动输入和执行SQL语句。更多详情可参考官网&#xff08;https://www.sqlite.org/cli.html&#xff09;。 help SQLite shell命令的help信息如下&#xff1a; .auth ON|OFF Sho…

卷积神经网络 - 动机(Motivation)篇

序言 在深度学习的浩瀚星空中&#xff0c;卷积神经网络&#xff08; Convolutional Neural Networks, CNNs \text{Convolutional Neural Networks, CNNs} Convolutional Neural Networks, CNNs&#xff09;无疑是最为璀璨的一颗星&#xff0c;其诞生与崛起深刻改变了图像识别、…

线程池的优势与应用

线程池的优势与应用 1、线程池的优势2、应用场景 &#x1f496;The Begin&#x1f496;点点关注&#xff0c;收藏不迷路&#x1f496; 1、线程池的优势 资源复用&#xff1a;减少线程创建和销毁的开销&#xff0c;通过重用已存在的线程来提高效率。控制并发&#xff1a;有效管…

Solana公链

Solana 链的优势 Solana之所以能够实现高性能&#xff0c;主要是因为它采用了多种创新的技术和设计决策。下面是Solana能够达到高吞吐量、低延迟和低成本的一些关键因素&#xff1a; 1. 历史证明 (Proof of History, PoH)&#xff1a; Solana引入了一种独特的共识机制&#…

【C++题解】1022. 百钱百鸡问题

欢迎关注本专栏《C从零基础到信奥赛入门级&#xff08;CSP-J&#xff09;》 问题&#xff1a;1022. 百钱百鸡问题 类型&#xff1a;嵌套穷举 题目描述&#xff1a; 用 100 元钱买 100 只鸡&#xff0c;公鸡&#xff0c;母鸡&#xff0c;小鸡都要有。 公鸡 5 元 1 只&#x…

【Kubernetes】kubeadmu快速部署k8s集群

目录 一.组件部署 二.环境初始化 三.所有节点部署docker&#xff0c;以及指定版本的kubeadm 四.所有节点安装kubeadm&#xff0c;kubelet和kubectl 五.高可用配置 六.部署K8S集群 1.master01 节点操作 2.master02、master03节点 3.master01 节点 4.master02、master…