AOP实现企业级API访问接口监控(通过Google Guava缓存数据)

2023-11-10

开发了企业的功能模块,分享给大家参考,若大家看到我的实现有不足之处或有自己的见解欢迎评论区分享٩꒰▽ ꒱۶⁼³₌₃ 学习去咯



前言

需求:

1、记录接口访问成功和失败的次数
2、记录接口访问成功和失败的耗时
3、访问时间戳记录,以5分钟为一个节点,例如:13:14分记录为13:10分
4、公开一个接口,返回Google Guava缓存的数据,以JSON的格式
5、高并发下任然可用,保证线程安全

以上便是具体需求,目前这需求只是存储了具体接口信息,后续可能要去实现监控预警如:接口在当前节点内失败次数超过了阈值、调用接口的耗时超过了阈值等情况需要发送预警(邮箱通知等等)


一、AOP的基本知识

1、什么是AOP?

AOP(Aspect-Oriented Programming,面向切面编程)能够将那些与业务无关,却为业务模块所共同调用的逻辑或责任(例如事务处理、日志管理、权限控制等)封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可扩展性和可维护性。

2、有哪些AOP的概念?

Spring AOP是基于动态代理的,如果要代理的对象实现了某个接口,那么Spring AOP就会使用JDK动态代理去创建代理对象;而对于没有实现接口的对象,就无法使用JDK动态代理,转而使用CGlib动态代理生成一个被代理对象的子类来作为代理。

3、AOP包含的几个概念

1)、Jointpoint(连接点):具体的切面点点抽象概念,可以是在字段、方法上,Spring中具体表现形式是PointCut(切入点),仅作用在方法上。
2)、Advice(通知): 在连接点进行的具体操作,如何进行增强处理的,分为前置、后置、异常、最终、环绕五种情况。
3)、目标对象:被AOP框架进行增强处理的对象,也被称为被增强的对象。
4)、AOP代理:AOP框架创建的对象,简单的说,代理就是对目标对象的加强。Spring中的AOP代理可以是JDK动态代理,也可以是CGLIB代理。
5)、Weaving(织入):将增强处理添加到目标对象中,创建一个被增强的对象的过程

总结为一句话就是:在目标对象(target object)的某些方法(jointpoint)添加不同种类的操作(通知、增强操处理),最后通过某些方法(weaving、织入操作)实现一个新的代理目标对象。

4、AOP 有哪些应用场景?

举几个例子:

  • 记录日志(调用方法后记录日志)
  • 监控性能(统计方法运行时间)
  • 权限控制(调用方法前校验是否有权限)
  • 事务管理(调用方法前开启事务,调用方法后提交关闭事务 )
  • 缓存优化(第一次调用查询数据库,将查询结果放入内存对象, 第二次调用,直接从内存对象返回,不需要查询数据库 )

5、有哪些AOP Advice通知的类型?

特定 JoinPoint 处的 Aspect 所采取的动作称为 Advice。Spring AOP 使用一个 Advice 作为拦截器,在 JoinPoint “周围”维护一系列的拦截器。

  • 前置通知(Before advice) : 这些类型的 Advice 在 joinpoint 方法之前执行,并使用 @Before 注解标记进行配置。
  • 后置通知(After advice) :这些类型的 Advice 在连接点方法之后执行,无论方法退出是正常还是异常返回,并使用 @After 注解标记进行配置。
  • 返回后通知(After return advice) :这些类型的 Advice 在连接点方法正常执行后执行,并使用@AfterReturning 注解标记进行配置。
  • 环绕通知(Around advice) :这些类型的 Advice 在连接点之前和之后执行,并使用 @Around 注解标记进行配置。
  • 抛出异常后通知(After throwing advice) :仅在 joinpoint 方法通过抛出异常退出并使用 @AfterThrowing 注解标记配置时执行。

二、Google Guava是什么?

这个就是个工具类,很好用,老大让我好好研究,因为他之前在阿里工作的时候也是通过这个进行缓存的。
Google Guava 官网: https://guava.dev/
Google Guava 中文教程: https://wizardforcel.gitbooks.io/guava-tutorial/content/

直接看文档就可以了,在结合网上的demo,基本使用是没问题的。

三、功能实现

1.引入依赖

<!-- AOP -->
<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjrt</artifactId>
    <version>1.9.4</version>
</dependency>
<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.9.4</version>
</dependency>
<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>3.2.12</version>
</dependency>

<dependency>
    <groupId>org.json</groupId>
    <artifactId>json</artifactId>
    <version>20140107</version>
</dependency>

<dependency>
    <groupId>com.google.guava</groupId>
    <artifactId>guava</artifactId>
    <version>31.1-jre</version>
</dependency>
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <optional>true</optional>
</dependency>

2、编写返回内容实体

MonitoringVO.java:

/**
 * @author 一个爱运动的程序员
 */
@Data
public class MonitoringVO {

    /**
     * 请求时间
     */
    private String time;

    /**
     * 接口请求的URL
     */
    private String apiURL;

    /**
     * 接口请求成功的次数
     */
    private Long successfulNum;

    /**
     * 接口请求失败的次数
     */
    private Long failuresNum;

    /**
     * 接口请求成功的累计耗时
     */
    private Long successfulTime;

    /**
     * 接口请求失败的累计耗时
     */
    private Long failuresTime;
}

3、编写AOP

ApiVisitHistory.java:

/**
 * API访问历史统计
 *
 * @author 一个爱运动的程序员
 */
@Component
@Aspect
public class ApiVisitHistory {

    private Logger log = LoggerFactory.getLogger(ApiVisitHistory.class);

    private static ThreadLocal<Long> startTime = new ThreadLocal<>();

    /**
     * 定义切面
     * - 此处代表com.example.demo.monitoring.controller包下的所有接口都会被统计
     */
    @Pointcut("execution(* com.example.demo.monitoring.controller..*.*(..))")
    public void pointCut() {

    }

    /**
     * 在接口原有的方法执行前,将会首先执行此处的代码
     */
    @Before("pointCut()")
    public void doBefore(JoinPoint joinPoint) throws Throwable {
        startTime.set(System.currentTimeMillis());
        //获取传入目标方法的参数
        log.info("类名:{}", joinPoint.getSignature().getDeclaringType().getSimpleName());
        log.info("方法名:{}", joinPoint.getSignature().getName());
    }

    /**
     * 只有正常返回才会执行此方法
     * 如果程序执行失败,则不执行此方法
     */
    @Async
    @AfterReturning(returning = "returnVal", pointcut = "pointCut()")
    public void doAfterReturning(JoinPoint joinPoint, Object returnVal) throws ExecutionException {
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        String key = AtomicCounter.getTimeStamp() + "--" + AtomicCounter.getApiURL(request);
        // 成功计数+1
        AtomicCounter.increaseSucceed(AtomicCounter.getCountObject(key));
        // 耗时计算
        Long succeedTime = System.currentTimeMillis() - startTime.get();
        log.info("接口访问成功,URI:[{}], 耗费时间:[{}] ms", request.getRequestURI(), succeedTime);
        // 存储API接口访问信息
        AtomicCounter.setSuccessfulTime(key, succeedTime);
    }

    /**
     * 当接口报错时执行此方法
     */
    @AfterThrowing(pointcut = "pointCut()")
    public void doAfterThrowing(JoinPoint joinPoint) throws ExecutionException {
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        String key = AtomicCounter.getTimeStamp() + "--" + AtomicCounter.getApiURL(request);
        // 失败计数+1
        AtomicCounter.increaseFail(AtomicCounter.getCountObject(key));
        // 耗时计算
        Long failTime = System.currentTimeMillis() - startTime.get();
        log.info("接口访问失败,URI:[{}], 耗费时间:[{}] ms", request.getRequestURI(), failTime);
        AtomicCounter.setMultiMap(key, failTime);
    }
}

4、编写存储工具类

AtomicCounter java:

/**
 * 记录API接口访问成功/失败的计数及耗时
 *
 * @author 一个爱运动的程序员
 */
public class AtomicCounter {

    /**
     * Guava Cache 缓存API访问记录
     */
    private static Cache<String, ConcurrentHashMap<AtomicCounter, MonitoringVO>> cache = CacheBuilder.newBuilder().expireAfterWrite(60 * 10, TimeUnit.SECONDS).build();

    /**
	 * 调用的计数对象
	 *
	 * @param key
	 * @return
	 */
	public static ConcurrentHashMap<AtomicCounter, MonitoringVO> getCountObject(String key) {
        ConcurrentHashMap<AtomicCounter, MonitoringVO> ifPresent = cache.getIfPresent(key);
        if (ifPresent == null || ifPresent.isEmpty()) {
            ifPresent = new ConcurrentHashMap<>();
            AtomicCounter atomicCounter = new AtomicCounter();
            MonitoringVO monitoringVO = new MonitoringVO();
            String[] split = key.split("--");
            monitoringVO.setTime(split[0]);
            monitoringVO.setApiURL(split[1]);
            monitoringVO.setSuccessfulNum(0L);
            monitoringVO.setFailuresNum(0L);
            monitoringVO.setSuccessfulTime(0L);
            monitoringVO.setFailuresTime(0L);
            ifPresent.put(atomicCounter, monitoringVO);
            cache.put(key, ifPresent);
		}
		return ifPresent;
	}

    /**
     * 增加访问接口调用成功的次数
     * @param concurrentHashMap
     * @return
     */
    public static void increaseSucceed(ConcurrentHashMap<AtomicCounter, MonitoringVO> concurrentHashMap) {
        MonitoringVO monitoringVO = null;
        for (MonitoringVO m : concurrentHashMap.values()) monitoringVO = m;
        Lock lock = new ReentrantLock();
        try {
            lock.lock();
            monitoringVO.setSuccessfulNum(monitoringVO.getSuccessfulNum() + 1);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    /**
     * 更新调用成功的耗时
     * @param key
     * @param successTime
     * @throws ExecutionException
     */
    public static void setSuccessfulTime(String key, Long successTime) {
        ConcurrentHashMap<AtomicCounter, MonitoringVO> map = getCountObject(key);
        MonitoringVO monitoringVO = null;
        for (MonitoringVO m : map.values()) monitoringVO = m;
        Lock lock = new ReentrantLock();
        try {
            lock.lock();
            monitoringVO.setSuccessfulTime(monitoringVO.getSuccessfulTime() + successTime);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    /**
     * 增加访问接口调用失败的次数
     * @param concurrentHashMap
     * @return
     */
	public static void increaseFail(ConcurrentHashMap<AtomicCounter, MonitoringVO> concurrentHashMap) {
        MonitoringVO monitoringVO = null;
        for (MonitoringVO m : concurrentHashMap.values()) monitoringVO = m;
        Lock lock = new ReentrantLock();
        try {
            lock.lock();
            monitoringVO.setFailuresNum(monitoringVO.getFailuresNum() + 1);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
	}

    /**
     *更新调用失败的耗时
     * @param key
     * @param failTime
     */
    public static void setMultiMap(String key, Long failTime) {
        ConcurrentHashMap<AtomicCounter, MonitoringVO> map = getCountObject(key);
        MonitoringVO monitoringVO = null;
        for (MonitoringVO m : map.values()) monitoringVO = m;
        Lock lock = new ReentrantLock();
        try {
            lock.lock();
            monitoringVO.setFailuresTime(monitoringVO.getFailuresTime() + failTime);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    /**
     * 获取时间戳,并以5分钟为节点
     * @return
     */
    public static String getTimeStamp() {
        // 获取时间戳
        Long timeStamp = System.currentTimeMillis();
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH");
        SimpleDateFormat sdfMinute = new SimpleDateFormat("mm");
        // 时间戳转换成时间
        String sd = sdf.format(new Date(Long.parseLong(String.valueOf(timeStamp))));
        // 时间戳转换成时间
        String sdMinute = sdfMinute.format(new Date(Long.parseLong(String.valueOf(timeStamp))));
        sd += ":" + (Integer.valueOf(sdMinute) - Integer.valueOf(sdMinute) % 5);
        return sd;
    }

    /**
     * 获取API的接口URL
     * @param request
     * @return
     */
    public static String getApiURL(HttpServletRequest request) {
        // 请求路径
        String requestURI = request.getRequestURI();
        return requestURI;
    }

    /**
	 * 获取API监控信息
	 *
	 * @return
	 */
	public static CopyOnWriteArrayList<MonitoringVO> getCacheMap() {
        ConcurrentMap<String, ConcurrentHashMap<AtomicCounter, MonitoringVO>> stringConcurrentHashMapConcurrentMap = cache.asMap();
        CopyOnWriteArrayList<MonitoringVO> list = new CopyOnWriteArrayList<>();
		for (ConcurrentHashMap<AtomicCounter, MonitoringVO> c : stringConcurrentHashMapConcurrentMap.values())
			for (MonitoringVO m : c.values()) list.add(m);
		return list;
	}
}

5、编写测试类

HelloController.java:

@RestController
@RequestMapping("/aop")
public class HelloController {
    static int r = 0;

    @GetMapping("success")
    public String success() {
        return "调用成功";
    }

    @GetMapping("fail")
    public String fail() {
        if (r == 3) {
            r++;
            return "调用错误接口,成功一次";
        } else {
            r++;
            int i = 1 / 0;
            return "测试报错的AOP方法";
        }
    }

    @GetMapping("cache")
    public String get() {
        CopyOnWriteArrayList<MonitoringVO> list = AtomicCounter.getCacheMap();
        return JSONObject.valueToString(list);
    }
}

项目目录:
在这里插入图片描述
运行效果:
在这里插入图片描述

总结

以上就是今天要分享的内容,在线程安全方面,Google Guava Cache缓存本就是线程安全的,所以需要关注的是运算过程的线程安全,本可以使用AtomicInteger等线程安全的整型数据类型,但由于存储的太过麻烦不易处理,最终在处理运算上使用了lock来保证线程安全。今天的分享就到这里,给个三连呗,创作不易(づ。◕ᴗᴗ◕。)づ

附加功能

需求:能自定义给接口增强功能,例如:通过注解的形式给接口提供监控功能

代码如下:
MonitoringAnnotation .java:

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MonitoringAnnotation {
}

ApiVisitHistory.java:

@Pointcut("@annotation(com.example.demo.monitoring.annotation.MonitoringAnnotation)")

修改路径即可:
在这里插入图片描述
只需要在我们需要监控的接口方法或类上加上这个注解即可:

@MonitoringAnnotation

源码地址

https://github.com/XIN007-C/api-monitoring/tree/main/aopdemo

如果对大家有帮助,给个start吧 ψ(*`ー´)ψ

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

AOP实现企业级API访问接口监控(通过Google Guava缓存数据) 的相关文章

  • 为 JSP 创建注销链接?

    当用户登录我的应用程序时 他提交一个要通过 Servlet 处理的表单 servlet 为用户创建一个会话 我如何创建一个链接以便用户可以注销 我似乎无法直接链接到 Servlet 如何删除会话并链接回主页 HttpSession sess
  • 为什么用java日历解析时会得到错误的月份

    Date fakeDate sdf parse 15 07 2013 11 00 AM Calendar calendar Calendar getInstance calendar setTime fakeDate int current
  • java中的散列是如何工作的?

    我正在尝试弄清楚java中的哈希值 例如 如果我想在哈希图中存储一些数据 它是否会有某种带有哈希值的底层哈希表 或者 如果有人能够对哈希的工作原理给出一个很好且简单的解释 我将非常感激 HashMap 基本上在内部实现为数组Entry 如果
  • Netbeans 雷达插件配置

    我使用的是 Netbeans 8 0 1 在提交到 SVN 之前 我需要从 IDE 运行并检查 SonarQube 分析 我已经安装了 Netbeans Radar 插件 用于启动本地分析并检查结果 这个插件有一个名为 Get Issues
  • 将 Swing 集成到简单的文本冒险游戏中

    我对 Java 中的一些中级概念相当陌生 最近 我制作了一款名为 DazzleQuest 的文本冒险游戏 它完全在开发者控制台 终端中运行 它涉及到我的朋友作为角色 所以我想向他们展示它 并通过将命令行的功能和控制台的输出转移到一个简单的
  • Maven 部署:deploy-file 发布所有文件而不是一个

    我正在使用 Maven 构建我的 Java 应用程序Maven 组装插件 https maven apache org plugins maven assembly plugin 创建一个可执行的 jar 因此 目标文件夹包含多个 jar
  • 在 Selenium Grid 中注册 PhantomJS 节点时出错

    我有以下问题 我成功启动了 Selenium Grid hub java jar selenium server standalone 2 53 0 jar role hub 之后我尝试使用以下命令启动 PhantomJS 节点 phant
  • Runtime.getRuntime().exec(cmd) 挂起

    我正在执行一个命令 该命令返回文件的修订号 文件名 但如果执行命令时出现问题 应用程序就会挂起 我可以做什么来避免这种情况 请在下面找到我的代码 String cmd cmd C si viewhistory fields revision
  • 具有多个字符串的列表视图

    我正在尝试创建一个包含多个字符串的列表视图 现在我有一个可以实现的功能 while i lt 10 GETS DATA FROM WEBPAGE ETC a DATAFROMWEBPAGE1 b DATAFROMWEBPAGE2 c DAT
  • 如何在 HashiCorp Vault 中安全地存储 Spring Boot 应用程序的机密?

    我已阅读以下教程 保险库配置 https spring io guides gs vault config 好的 我们安装了 Vault 服务器并放置了 2 对秘密属性 vault kv put secret gs vault config
  • 使用 JPA 标准的“不在”约束

    我正在尝试写一个NOT IN约束使用JPA Criteria 我尝试过这样的事情 builder not builder in root get property1 虽然我知道这行不通 在上面的语法中 如何添加集合 列表property1会
  • 异步不适用于控制器的抽象超类方法

    我有一个BaseRestControllerRest 控制器扩展的类 它有一个我想异步运行的方法 public abstract class BaseRestController Async someThreadPoolTaskExecut
  • 使用会话空闲超时进行轮询

    我对 Tomcat 中的所有应用程序使用单点登录 我的要求是 我必须轮询应从后端获取的事务状态 但它也不应该影响会话的空闲超时 有人可以建议是否可以做点什么吗 Thanx 我不知道是否有标准方法可以做到这一点 如果没有 你可以写一个过滤器
  • 尝试用Java实现基于文本的Hangman游戏

    我需要检查用户输入的字母以及他们猜测的空格是否位于隐藏单词的特定位置 变量one等于用户猜测的空间索引 而letterGuess是他们猜测的字母 我的代码怎么错了 示例 秘密词是你好 hidden word is 用户猜测h 0 1 2 3
  • 在 x64 系统上使用 skype-java-api

    我正在使用 skype java api 在 Java 中使用 Skype 我需要的唯一功能是点击即可拨打电话号码 它在 Windows XP x86 上运行良好 但我刚刚在 Windows 7 x64 上测试它 但失败了 错误是 线程 T
  • 避免加密和编码的 URL 字符串中的换行符

    我正在尝试实现一个简单的字符串编码器来混淆 URL 字符串的某些部分 以防止它们被用户弄乱 我使用的代码几乎与示例中的相同JCA指南 http docs oracle com javase 6 docs technotes guides s
  • 如何在 Windows 上使用 Java Hotspot JVM 禁用小型转储 (mdmp) 文件生成

    目前 我有一个已部署的可执行 jar 文件 该文件在崩溃时会创建大型 7 Gb 小型转储文件 我想要一个导致崩溃原因的文本表示 而不是 JVM 状态的二进制文件 我尝试使用中找到的信息这个 CodeRanch 帖子 http www cod
  • Java:易失性足以使类线程安全?

    我有一个关于 Java 中 volatile 语句的问题 请看这个构造的例子 class Master Foo is a class with thread safe methods public volatile Foo foo clas
  • 如何在 Spring boot 应用程序中使用禁用连接池?

    我在 Application java 中创建一个像这样的数据源 Bean ConfigurationProperties datasource public DataSource dataSource return DataSourceB
  • Java Media API:java media api 下载

    我在哪里可以找到javax media jar 文件 在sun站点它下载一个安装程序 有没有可用的java媒体jar 没有 javax media 具体是 jar 文件 该包位于 jmf jar 文件中 您需要运行安装程序并取出 jar 或

随机推荐

  • 2023华为OD机试真题【补种未成活胡杨】

    题目内容 近些年来 我国防沙治沙取得显著成果 某沙漠新种植N棵胡杨 编号1 N 排成一排 一个月后 有M棵胡杨未能成活 现可补种胡杨K棵 请问如何补种 只能补种 不能新种 可以得到最多的连续胡杨树 输入描述 N 总种植数量 M 未成活胡杨数
  • web自动化测试从安装到实战(全)

    1 什么是Web自动化测试 让程序代替人工自动验证web项目功能的过程 预期结果和实际结果的比较 2 为什么要在做Web自动化测试 在较少的时间内运行更多和测试用例 脚本可重复执行 减少人为的错误 克服手工测试的局限 3 在什么场景下适合做
  • XCode 编译ffmpeg

    环境 macOS10 13 3 Xcode9 4 1 FFmpeg4 0 2 准备 brew install sdl2 brew命令如果出错 要能是版本问题 可按以下命令卸载后 重新安装brew usr bin ruby e curl fs
  • git分支切换

    在git中 可利用checkout命令转换分支 该命令的作用就是切换分支或恢复工作树文件 语法为 git checkout 分支名 当参数设置为 b 时 可以在新分支创建的同时切换分支 语法为 git checkout b 分支名 本文操作
  • 计及源-荷双重不确定性的虚拟电厂/微网日前随机优化调度

    目录 1主要内容 1 1 场景生成及缩减 1 2 随机优化调度 2 程序链接 1主要内容 程序主要做的是一个虚拟电厂或者微网单元的日前优化调度模型 考虑了光伏出力和负荷功率的双重不确定性 采用随机规划法处理不确定性变量 构建了虚拟电厂随机优
  • 计算机端口详解(总结)

    计算机端口详解 总结 https blog csdn net qq 17204441 article details 89063083 0x00 什么是端口 0x01 端口的分类 0x02 端口在入侵中的作用 0x03 端口的相关工具 0x
  • 第十届蓝桥杯 JavaA 迷宫

    第十届蓝桥杯 JavaA 迷宫 法一 思路 bfs path记录路径 1 编程https www cnblogs com woxiaosade p 10592061 html 2 观察https www cnblogs com yzm10
  • Hypertable 和 chunk 超表和块

    文档 https docs timescale com v0 9 introduction architecture 概述 TimescaleDB作为PostgreSQL的扩展实现 这意味着Timescale数据库在整个PostgreSQL
  • selenium+chormdriver+python 实现淘宝的信息爬取

    因为我是个爬虫新手 所以对爬虫还不熟练 这几天想着自己做一个淘宝信息的自动爬取 一开始感觉比较简单 但做到了登录界面 发现一直被网站检测出来 不能滑动滑块 接下来从网上翻遍了资料 整理了以下自己的代码 完成了这个艰难的工程 嘻嘻 对我来说
  • Rsync远程同步

    rsync rsync Remote Sync 远程同步 是一个开源的快速备份工具 可以在不同主机之间镜像同步整个目录树 支持增量备份 并保持链接和权限 且采用优化的同步算法 传输前执行压缩 因此非常适用于异地备份 镜像服务器等应用 rsy
  • MFC中设置焦点

    初次接触MFC 实现填完一系列表单后继续添加另外一张 并且将焦点设置为第一张初次填写时的焦点 可能就是指第一个获取焦点的控件 用 SetFocus m hWnd 实现重置表单的功能 UpdateData FALSE 更新数据时是 Updat
  • 【Docker】Docker的使用案例以及未来发展、Docker Hub 服务、环境安全的详细讲解

    Docker的工具实践及root概念和Docker容器安全性设置 1 使用案例 2 Docker解决的问题 3 Docker未来发展 4 Docker Hub 服务 5 技术局限 6 Docker环境安全 7 容器部署安全 1 使用案例 D
  • 希腊字母发音对照表及其latex命令

    拉丁字母是26个 希腊 Greek 字母是24个 发音即是它们各自的latex形式 大写字母的是其小写latex首字母大写后的形式 如 Delta Delta notation 西方的数学家们在推导数学定理时 仍然沿用并不好写也不好记的希腊
  • ArcGIS 文本数字写入csv文件后小数点位数减少

    在将经纬度数据写入csv文件的过程中 经度和纬度都是以小数点后保留4为小数的字符串形式存储的 但是在转成csv文件后 打开发现小数点位数缺失了 如图 在网上找了好久也没有找到解决办法 大部分都是解决文本数字过长导致以有效数字形式显示的问题
  • 如何有效的防护DDoS攻击

    DDoS攻击的类型和方法 分布式拒绝服务攻击 简称DDoS 是一种协同攻击 旨在使受害者的资源无法使用 它可以由一个黑客组织协同行动 也可以借助连接到互联网的多个受破坏设备来执行 这些在攻击者控制下的设备通常称为僵尸网络 有多种执行DDoS
  • stm32通用外部spi下载算法实现

    参考硬汉嵌入式 实战技能 任何支持SWD接口的单片机都可以方便移植的SPI Flash烧写算法制作 哔哩哔哩 bilibili 该up主提供的stm32H7的模板工程 目前需求是实现基于正点原子探索者stm32f407zet6 W25Q12
  • SpringMVC上传文件的 4 种方式,你都会么?

    1 本文内容 文件上传开发步骤 单文件上传 多文件上传 通过 MultipartHttpServletRequest 处理文件上传 通过自定义对象接收上传的文件 扩展知识 案例代码 2 预备知识 springmvc 系列中的测试案例 基本上
  • 智能汽车竞赛室外光电 组 1 安装ROS软件平台和运行第一个程序

    机器人操作系统 ROS 对机器人进行编程以使其完全符合在工业环境中的要求 它的工具 库和共享的开放资源 允许开发人员协同工作 利用现有工作的优势 简化和加快创建机器人行为的过程 ROS得到了一个庞大的全球社区的支持 其邮件列表 Wiki和R
  • [从零开始学DeepFaceLab-21]: 使用-命令行八大操作步骤-第6步:模型的选择与训练 - 进阶 - AMP模型训练参数详解与优化

    目录 前言 第1章 AMP模型训练参数详解 1 1 AMP参数汇总 1 2 参数详解
  • AOP实现企业级API访问接口监控(通过Google Guava缓存数据)

    开发了企业的功能模块 分享给大家参考 若大家看到我的实现有不足之处或有自己的见解欢迎评论区分享 学习去咯 文章目录 前言 一 AOP的基本知识 1 什么是AOP 2 有哪些AOP的概念 3 AOP包含的几个概念 4 AOP 有哪些应用场景