Android+Jacoco+code-diff全量、增量覆盖率生成实战
背景
主要是记录下Android项目使用jacoco生成代码覆盖率的实战流程,目前已完成全量覆盖方案,仅使用jacoco就能实现;
由于我们的Android端是使用Java和kotlin语言,目前增量的方案code-diff仅针对Java代码,卡在kotlin文件的分析,仍在思考中。
Android由于是本地安装包,只能使用offline模式:
offline模式就是在测试之前先对文件进行插桩,生成插过桩的class或jar包,测试插过桩的class和jar包,生成覆盖率信息到文件,最后统一处理,生成报告。
在测试前先对文件进行插桩,然后生成插过桩的class或jar包,测试插过桩的class和jar包后,会生成动态覆盖信息到文件,最后统一对覆盖信息进行处理,并生成报告。
使用场景
其实主要是基于两个痛点:
1、新功能测试和回归测试在手工测试的情况下,即便用例写的再怎么详细,也经常会有漏测的发生,这里一方面是因为现在大量互联网公司采用外包资源来做业务测试,而外包的工作质量无法有效评估,可能存在漏执行的情况,另外一方面是本身测试用例设计的不够完善导致没有覆盖到一些关键路径的代码分支,因此亟需一种可以度量手工测试完成后对代码覆盖情况的手段或者工具;
2、研发代码变更的影响范围难以精准评估,比如研发提交一个MR,这个MR到底影响了多少用例,在没有精准测试能力的情况下是很难给出的,而做精准测试,最重要的一环就是代码用例的关系库维护,如何生成代码跟用例的关系,就需要用到代码覆盖率的采集和分析能力了;
引用简单两步实现 Jacoco+Android 代码覆盖率的接入!(最新最全版)
时机:
1.提测时-明确整个版本迭代的改动范围,测试范围,全量代码diff;
2.测试中-提交bug修复版本,明确问题,使用增量代码diff;
3.预发布-关注关键点,确保发布代码与测试代码一致,全量代码diff;
覆盖率对测试提升:
1.能了解确认需求的实现逻辑,对技术细节查漏补缺;
2.评估影响范围;
3.通过代码补充测试范围,优化测试用例;
4.加深系统实现的理解;
5.提前发现错误
项目环境
1.gradle插件版本
ANDROID_GRADLE_PLUGIN = "4.2.0"2.gradle依赖版本
distributionUrl=https\://services.gradle.org/distributions/gradle-6.7.1-bin.zip3.android sdk版本
BUILD_TOOLS_VERSION = "28.0.3"
COMPILE_SDK = 31
TARGET_SDK = 31
MIN_SDK = 21
代码介入
1.在app模块下新建一个 jacoco.gradle
apply plugin: 'jacoco'
jacoco {toolVersion = "0.8.2"
}android {//在app引入的时候指定对应的变体 会将内容传递引用的工程,主要用于多模块使用defaultPublishConfig "debug"buildTypes {debug {/**打开覆盖率统计开关**/testCoverageEnabled = true}}
}
//源代码路径,你有多少个module,你就在这写多少个路径
//我这里是多模块的,需要将主要代码的模块写上
def coverageSourceDirs = ['../lib.xx/src/main/java','../lib.xx/src/main/java','../lib.xx/src/main/java','../lib.xx/src/main/java',......'/src/main/java','/src/mvp/java'
]//class文件路径,就是上面提到的class路径,看你的工程class生成路径是什么,替换一下就行
def coverageClassDirs = ['/lib.xx/build/intermediates/javac/debug/classes','/lib.xx/build/intermediates/javac/debug/classes','/lib.xx/build/intermediates/javac/debug/classes','/lib.xx/build/intermediates/javac/debug/classes','/app/build/intermediates/javac/debug/classes'......
]
//kotlin的classes文件
def kotlinClassDirs = ['/lib.xx/build/tmp/kotlin-classes/debug/','/lib.xx/build/tmp/kotlin-classes/debug/','/lib.xx/build/tmp/kotlin-classes/debug/','/lib.xx/build/tmp/kotlin-classes/debug/','/app/build/tmp/kotlin-classes/debug/'......
]//这个就是具体解析ec文件的任务,会根据我们指定的class路径、源码路径、ec路径进行解析输出
task jacocoTestReport(type: JacocoReport) {group = "Reporting"description = "Generate Jacoco coverage reports after running tests."reports {xml.enabled(true)html.enabled(true)}//设置class文件的路径classDirectories.setFrom(files(coverageClassDirs.collect{fileTree(dir: "$rootDir"+it,excludes: ['**/R*.class','**/*$InjectAdapter.class','**/*$ModuleAdapter.class','**/*$ViewInjector*.class'])}))classDirectories.setFrom(files(kotlinClassDirs.collect{fileTree(dir: "$rootDir"+it,excludes: ['**/R*.class','**/*$InjectAdapter.class','**/*$ModuleAdapter.class','**/*$ViewInjector*.class'])}))//设置源码文件的路径sourceDirectories.setFrom(files(coverageSourceDirs))
//设置ec文件executionData.setFrom(files("$buildDir/outputs/code_coverage/debugAndroidTest/connected/coverage.ec"))doFirst {coverageClassDirs.each { path ->println("$rootDir" + path)new File("$rootDir" + path).eachFileRecurse { file ->if (file.name.contains('$$')) {file.renameTo(file.path.replace('$$', '$'))}}}}
}
2.在app模块下的build.gradle.kts引用jacoco.gradle,并在buildtype为debug下开启覆盖率的开关
apply(from = "jacoco.gradle")//引入jacoco
// 开发版本,可打开开发者模式getByName("debug") {isMinifyEnabled = false//引入jacocoisTestCoverageEnabled = truezipAlignEnabled(false)
3.定义采集覆盖率coverage.ec的方式,网上的方式都是通过监听主activity Destroy后收集,这里可以自己定义适合的方式,比如在项目新增按钮点击采集。参考网上的代码可以,直接用:
在app的代码新建jacoco目录
添加一下代码
FinishListener
package xx.app.jacoco;public interface FinishListener {void onActivityFinished();void dumpIntermediateCoverage(String filePath);
}
InstrumentedActivity
package xx.app.jacoco;import xx.Activity;public class InstrumentedActivity extends Activity {public FinishListener finishListener;public void setFinishListener(FinishListener finishListener) {this.finishListener = finishListener;}@Overridepublic void onDestroy() {if (this.finishListener != null) {finishListener.onActivityFinished();}super.onDestroy();}
}
JacocoInstrumentation
public class JacocoInstrumentation extends Instrumentation implements FinishListener {public static String TAG = "JacocoInstrumentation:";@SuppressLint("SdCardPath")private static String DEFAULT_COVERAGE_FILE_PATH = "/mnt/sdcard/coverage.ec";private final Bundle mResults = new Bundle();private Intent mIntent;private static final boolean LOGD = true;private boolean mCoverage = true;private String mCoverageFilePath;public JacocoInstrumentation() {}@Overridepublic void onCreate(Bundle arguments) {Log.e(TAG, "onCreate(" + arguments + ")");super.onCreate(arguments);DEFAULT_COVERAGE_FILE_PATH = getContext().getFilesDir().getPath() + "/coverage.ec";File file = new File(DEFAULT_COVERAGE_FILE_PATH);if (file.isFile() && file.exists()) {if (file.delete()) {Log.e(TAG, "file del successs");} else {Log.e(TAG, "file del fail !");}}if (!file.exists()) {try {file.createNewFile();} catch (IOException e) {Log.e(TAG, "异常 : " + e);e.printStackTrace();}}if (arguments != null) {Log.e(TAG, "arguments不为空 : " + arguments);mCoverageFilePath = arguments.getString("coverageFile");Log.e(TAG, "mCoverageFilePath = " + mCoverageFilePath);}mIntent = new Intent(getTargetContext(), InstrumentedActivity.class);mIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);start();}@Overridepublic void onStart() {Log.e(TAG, "onStart def");if (LOGD) {Log.e(TAG, "onStart()");}super.onStart();Looper.prepare();InstrumentedActivity activity = (InstrumentedActivity) startActivitySync(mIntent);activity.setFinishListener(this);}private boolean getBooleanArgument(Bundle arguments, String tag) {String tagString = arguments.getString(tag);return tagString != null && Boolean.parseBoolean(tagString);}private void generateCoverageReport() {OutputStream out = null;try {out = new FileOutputStream(getCoverageFilePath(), false);Object agent = Class.forName("org.jacoco.agent.rt.RT").getMethod("getAgent").invoke(null);out.write((byte[]) agent.getClass().getMethod("getExecutionData", boolean.class).invoke(agent, false));} catch (Exception e) {Log.e(TAG, e.toString());e.printStackTrace();} finally {if (out != null) {try {out.close();} catch (IOException e) {e.printStackTrace();}}}}private String getCoverageFilePath() {if (mCoverageFilePath == null) {return DEFAULT_COVERAGE_FILE_PATH;} else {return mCoverageFilePath;}}private boolean setCoverageFilePath(String filePath) {if (filePath != null && filePath.length() > 0) {mCoverageFilePath = filePath;return true;}return false;}private void reportEmmaError(Exception e) {reportEmmaError("", e);}private void reportEmmaError(String hint, Exception e) {String msg = "Failed to generate emma coverage. " + hint;Log.e(TAG, msg);mResults.putString(Instrumentation.REPORT_KEY_STREAMRESULT, "\nError: "+ msg);}@Overridepublic void onActivityFinished() {if (LOGD) {Log.e(TAG, "onActivityFinished()");}if (mCoverage) {Log.e(TAG, "onActivityFinished mCoverage true");generateCoverageReport();}finish(Activity.RESULT_OK, mResults);}@Overridepublic void dumpIntermediateCoverage(String filePath) {// TODO Auto-generated method stubif (LOGD) {Log.e(TAG, "Intermidate Dump Called with file name :" + filePath);}if (mCoverage) {if (!setCoverageFilePath(filePath)) {if (LOGD) {Log.e(TAG, "Unable to set the given file path:" + filePath + " as dump target.");}}generateCoverageReport();setCoverageFilePath(DEFAULT_COVERAGE_FILE_PATH);}}
}
配置AndroidManifest.xml
<!--引入jacoco--><activity android:name=".jacoco.InstrumentedActivity"android:label="InstrumentationActivity"/>
<!--引入jacoco--><instrumentationandroid:name=".jacoco.JacocoInstrumentation"android:handleProfiling="true"android:label="CoverageInstrumentation"android:targetPackage="包名" />
统计子module的覆盖率
因为很多Android项目肯定不只要app module,有很多子module提供使用,需要一起统计覆盖率
目前的做法是在jacoco.gradle 加上参数 defaultPublishConfig “debug”
android {//在app 引入的时候指定对应的变体 会将内容传递引用的工程,主要用于多模块使用defaultPublishConfig "debug"buildTypes {debug {/**打开覆盖率统计开关**/testCoverageEnabled = true}}
}
然后让子module去引用,这就需要修改子module的build.gradle,一行代码完成
//在子模块引入jacoco
apply(from = "../app/jacoco.gradle")
实战使用
1.通过命令行打debug安装包
installDebug 或者 gradlew app都行
2.通过instrument 启动app
安装完后先打开app再退出一下,不然启动不了
adb shell pm list instrumentation
//会看到以下信息
instrumentation:xx.app/.jacoco.JacocoInstrumentation (target=xx.app)
//然后复制启动
adb shell am instrument xx.app/.jacoco.JacocoInstrumentation
3.执行测试
4.完成测试后,在主页面退出app
5.通过Android stdio的device file explorer复制出coverage.ec
路径 /data/data/xx.app/files/coverage.ec
6.将coverage.ec复制到项目文件\app\build\outputs\code_coverage\debugAndroidTest\connected下,如没有的话新建
7.用命令jacocoTestReport生成报告,报名路径如下:
\app\build\reports\jacoco\jacocoTestReport\html
增量代码覆盖率
使用code-diff 和 jacoco二开
用code-diff获取两个commit之间的代码差异,然后生成json文件,使用jacoco二开的jar包通过
–diffCodeFiles 传入差异代码json文件,然后只生成差异代码文件的覆盖报告
总结:KT文件需要改造code-diff才能用,目前只能用于java,后续看看怎么修改。
引用下该作者的话,总结得很好,学习学习:
代码覆盖率100% 不代表没有bug。代码没有覆盖100% 一定有bug;
但是有可能你覆盖到80% 很轻松,往后增加5% 都费很大劲。那么我们可以去没有覆盖到的进行分析。不一定要做到代码100%全覆盖,尤其在功能测试阶段,代码100% 覆盖,会给大家增加很多的工作量,很有可能为了1%的覆盖率而耽误整体测试,得不偿失。
覆盖率是为了提升我们测试用例的覆盖度,检验我们测试用例设计的全面性,它有两面性,合理引入覆盖率,合理选择一定的阈值。
https://cloud.tencent.com.cn/developer/article/1801772
相关文章:
Android+Jacoco+code-diff全量、增量覆盖率生成实战
背景 主要是记录下Android项目使用jacoco生成代码覆盖率的实战流程,目前已完成全量覆盖方案,仅使用jacoco就能实现; 由于我们的Android端是使用Java和kotlin语言,目前增量的方案code-diff仅针对Java代码,卡在kotlin文件的分析&am…...
乌龟对对碰在线版
爆肝两天使用vue开发了一个在线版的乌龟对对碰小游戏之幸运对对碰。没有找到合适的乌龟素材,现在使用小兔子代替。 体验地址:幸运对对碰 | 乌龟对对碰小游戏 之前的python版本的乌龟对对碰:写文章-CSDN博客 乌龟对对碰-幸运对对碰...
如何更改select option边框颜色和选中的颜色
<!doctype html> <html> <head> <meta charset"utf-8"> <title>如何更改select option边框颜色和选中的颜色</title> </head><style>ul{border: 1px solid #000000;width: 500px;height: auto;background-color: aq…...
6. 数据结构—串的匹配算法
1.BF算法(暴力算法) //模式匹配(暴力算法) int Index(SString S,SString T){int i1,j1;while(i<S.length&&j<T.length){if(S[i]T[i]){i;j;}else{ii-j2; //最开始匹配的位置的后一个j1; //从头匹配 }}if(j>T.length)return i-T.length;return return 0…...
九大服务架构性能优化方式
来源:九大服务架构性能优化方式 目录 性能优化九大方式: 缓存 使用什么样的缓存 缓存常见问题 缓存淘汰 缓存数据一致性 并行化处理 批量化处理 数据压缩合并 无锁化 顺序写 分片化 避免请求 池化 异步处理 总结 最近做了一些服务性能优…...
【RabbitMQ】 相关概念 + 工作模式
本文将介绍一些MQ中常见的概念,同时也会简单实现一下RabbitMQ的工作流程。 MQ概念 Message Queue消息队列。是用来存储消息的队列,多用于分布式系统之间的通信。 系统间调用通常有:同步通信和异步通信。MQ就是在异步通信的时候使用的。 同…...
嵌入式学习 ——(Linux高级编程——进程)
目录 一、进程的含义 二、进程和程序的区别 三、进程的作用 四、进程的状态 五、进程的调度与上下文切换 六、查询进程相关命令 七、fork()函数 八、getpid()和getppid()函数 九、面试题解析: 十、应用场合及测试 一、进程的含义 进程指正在运行的程序&a…...
C++练习备忘录
1. 保留两位小数输出格式 #include <iostream> #include <iomanip> using namespace std; int main() {double S 0;S (15 25) * 20 / 2;cout << fixed << setprecision(2) << S;return 0; }2. 设置输出宽度 #include <iostream> #inclu…...
改善工作流
快捷键管理器 打开Editor->Shortcuts查看和编辑Unity中的快捷键 示例 ShiftSpace 窗口最大化 P 选择预制体 进入预制体编辑模式 单一检视窗口 选择组件,选择Properties打开一个窗口,显示组件信息;切换对象,窗口信息不会改变…...
迭代器失效
一、什么是迭代器失效 迭代器的主要作用就是让算法能够不用关心底层数据结构,其底层实际就是一个指针,或者是对指针进行了封装,比如:vector的迭代器就是原生态指针T* 。因此迭代器失效,实际就是迭代器底层对应指针所指…...
@RequestParam @RequestBody @PathVariable 这三个注解对应的前端使用vue的http请求时不同的调用方式
1. RequestParam 用途:用于提取请求参数,常见于GET请求或表单提交。 Vue HTTP 请求示例: // 使用axios发送GET请求 axios.get(/api/users, { params: { id: 1, name: John } }); 2. RequestBody 用途:用于提取请求体…...
SQL - 索引
索引本质上是数据库引擎用来快速查找数据的数据结构,可以显著提高查询的性能,为了加快运行较慢的查询。创建索引 默认索引 create index 索引名 on 表名 (列名); 通过对列名进行创建索引,在查询的时候,数据库就能通过索引找到匹配…...
Oracle23ai新特性FOR LOOP循环控制结构增强
在Oracle数据库中,FOR LOOP是一种常用的循环控制结构,它允许你重复执行一系列语句固定次数或直到满足特定条件为止。然而,标准的Oracle PL/SQL中的FOR LOOP主要用于遍历集合(如数组或游标的结果集),而不是像…...
DHU OJ 二维数组
思路及代码 #include<iostream> using namespace std; int main(){ //input 多组 //input M,N int 1< <20 //input M 行 N 列 数据 //initialize listint M, N;while (cin >> M >> N){int list[M][N];for (int i 0; i < M-1; i){for (int j 0; j…...
UDP/TCP --- Socket编程
本篇将使用 Linux 中的系统调用来实现模拟 TCP 和 UDP 的通信过程,其中只对 UDP 和 TCP 进行了简单的介绍,本篇主要实现的是代码,至于 UDP 和 TCP 的详细讲解将会在之后的文章中给出。 本篇给出的 tcp 和 udp 的代码中的 echo 都是测试连接是…...
【C语言】最详细的单链表(两遍包会!)
🦄个人主页:小米里的大麦-CSDN博客 🎏所属专栏:C语言数据结构_小米里的大麦的博客-CSDN博客 🎁代码托管:黄灿灿/数据结构 (gitee.com) ⚙️操作环境:Visual Studio 2022 目录 一、前言 二、单链表的概念 1. 单链表的特点 2. 单链表的基本…...
QT:VS2019 CMake编译CEF
CEF介绍 CEF作为一个基于Chromium的开源Web浏览器控件,为第三方应用提供了强大的嵌入浏览器支持。其多平台支持、HTML5特性、自定义能力以及多进程架构等特性,使得CEF在浏览器开发、桌面应用、开发工具以及自动化测试等领域得到了广泛应用。 多平台支持…...
day31(8/19)——静态文件共享、playbook
目录 一、ansible模块 script模块 copy模块 使用command模块下载 nfs-utils rpcbind 在被控制的主机上添加static目录,并创建test文件 command模块 service模块 二、playbook 三、playbook编排vsftpd 1、安装 2、卸载 3、启动服务 4、修改配置文件设置不…...
白骑士的C#教学实战项目篇 4.4 游戏开发
系列目录 上一篇:白骑士的C#教学实战项目篇 4.3 Web开发 在这一部分,我们将探索如何使用 Unity 和 C# 开发游戏。游戏开发结合了编程、图形设计和创意,既充满挑战又充满乐趣。通过这一节的学习,您将了解游戏引擎的基础知识&#…...
在Vue工程中开发页面时,发现页面垂直方向出现两个滚动条的处理
在Vue工程中开发页面时,发现页面垂直方向出现两个滚动条 最近在开发页面时,发现页面多了两个滚动条,如图: 原因: 当一个页面的内容高度大于屏幕的高度时就会出现滚动条。一般情况下当一个页面高度大于屏幕高度时&a…...
【C++初阶】:C++入门篇(一)
文章目录 前言一、C命名空间1.1 命名空间的定义1.2 命名空间的使用 二、C的输入和输出2.1 cin和cout的使用 三、缺省参数3.1 缺省参数的分类 四、函数重载4.1 函数重载概念及其条件4.2 C支持函数重载原理 -- 名字修饰 前言 C是在C语言的基础之上,增加了一些面向对象…...
【JAVA CORE_API】Day14 Collection、Iterator、增强for、泛型、List、Set
Collection接口及常用方法 Collection<Object> collection new ArrayList();:实例化ArrayList集合对象; collectionName.add(Object obj);:在集合中增加元素; int sizeName collectionName.size();:获取集合…...
Go更换国内源配置环境变量
背景 要在中国境内下载和使用Go编程语言的包,可以使用国内的Go模块代理来加速下载速度。以下是一些常见的国内Go模块代理源以及如何切换到这些源的方法: 常见国内Go模块代理源 七牛云(Qiniu) https://goproxy.cn 阿里云࿰…...
澎湃认证显实力,浪潮信息存储兼容新篇章
浪潮信息在存储技术兼容性领域取得新突破,其集中式存储HF/AS系列与长擎安全操作系统24强强联合,成功完成澎湃技术认证。此次合作不仅验证了双方产品的无缝对接能力,更体现了浪潮信息在推动全产业链共建共享方面的坚定决心。 浪潮信息澎湃技术…...
Leetcode 3255. Find the Power of K-Size Subarrays II
Leetcode 3255. Find the Power of K-Size Subarrays II 1. 解题思路2. 代码实现 题目链接:3255. Find the Power of K-Size Subarrays II 1. 解题思路 这一题是题目3254的进阶版,其实主要就是增加了算法复杂度。 整体上来说的话思路还是一个分段的思…...
Kotlin学习02-变量、常量、整数、浮点数、操作符、元组、包、导入
变量、常量、整数、浮点数、操作符、元组、包、导入 Book.kt package com.wujialiang.packclass Book {var title: String "Hello" }val PI 3.14; val E 2.178;Main.kt //引入包 //import com.wujialiang.pack.Book; import com.wujialiang.pack.*; //重命名导…...
C++的模板简介
文章目录 一、前言二、函数模板(Function Template)三、类模板(Class Template)四、变参模板(Variadic Template)五、模板的递归与元编程六、模板的局限与陷阱七、常用模板的实例八、C20 的概念(…...
树莓派5 笔记25:第一次启动与配置树莓派5_8G
今日继续学习树莓派5 8G:(Raspberry Pi,简称RPi或RasPi) 本人所用树莓派4B 装载的系统与版本如下: 版本可用命令 (lsb_release -a) 查询: Opencv 与 python 版本如下: 今日购得了树莓派5_8G版本,性能是同运…...
Melittin 蜂毒肽;GIGAVLKVLT TGLPALISWI KRKRQQ
【Melittin 蜂毒肽 简介】 蜂毒肽(Melittin)是蜜蜂毒液中的主要活性成分,由26个氨基酸组成,具有强碱性,易溶于水,是已知抗炎性最强的物质之一。蜂毒肽具有多种生物学、药理学和毒理学作用,包括…...
day32
更新源 cd /etc/apt/ sudo cp sources.list sources.list.save 将原镜像备份 sudo vim sources.list 将原镜像修改成阿里源/清华源,如所述 阿里源 deb http://mirrors.aliyun.com/ubuntu/ bionic main restricted universe multiver…...
【clickhouse】 使用 SQLAlchemy 连接 ClickHouse 数据库的完整指南
我听见有人猜 你是敌人潜伏的内线 和你相知多年 我确信对你的了解 你舍命救我画面 一一在眼前浮现 司空见惯了鲜血 你忘记你本是娇娆的红颜 感觉你我彼此都那么依恋 🎵 许嵩《内线》 ClickHouse 是一款非常高效的开源列式数据库,因…...
按键收集单击,双击和长按
按键收集单击,双击和长按 引言 在我们生活中, 按键是必不可少的, 不同的电器, 有不同的按键, 但是按键总有不够用的时候, 那么给与一个按键赋予不同的功能,就必不可少了. 一个按键可以通过按下的时间长短和频次, 来定义其类型。 一次按键收集, 都是在一个按键收集周…...
进程的异常终止
进程的异常终止 进程收到了某些信号,他杀 进程自己调用abort函数,产生了SIGABRT(6)信号,自杀 进程的最后一个线程收到了"取消"操作,并且做出响应 如果进程是异常结束的,atexit\on_exit它们事先注册的遗言…...
并发编程 | Future是如何优化程序性能
在初识Future一文中介绍了Future的核心方法。本文中接着介绍如何用Future优化我们的程序性能。 在此之前,有必要介绍Future接口的一个实现类FutureTask。 FutureTask介绍 FutureTask继承结构 首先我们看一下FutureTask的继承结构: public class Futur…...
Oracle笔记
一、 如何解决 sqlplus 无法使用退格键和方向键 .bashrc 中添加如下内容,解决 退格键 stty erase ^h 安装 rlwap 后,执行如下命令可解决 方向键 rlwap sqlplus 二、 都有哪些备份数据到工具 三、 谈谈 你对 oracle 中实例和数据库的理解 数据库是一…...
LVS+Keepalived 双机热备
LVSKeepalived 双机热备 Keepalived案例分析Keepalived工具介绍Keepalived工具介绍一、功能特点 一、理解Keepalived实现原理实验报告资源列表一、安装keepalived以及ipvsadm Keepalived案例分析 企业应用中,单台服务器承担应用存在单点故障的危险单点故障一旦发生…...
Web Image scr图片从后端API获取基本实现
因系统开发中需求,会有页面显示图片直接从后端获取后显示,代码如下: 后端: /*** 获取图片流* param response* param fileName*/RequestMapping(value"getImgStream",method RequestMethod.GET)public void getImgStr…...
2024音频剪辑指南:探索四大高效工具!
音频剪辑不仅仅是技术活,更是一种艺术创作,它能够让声音更加生动、更具感染力。今天,我们就来探索几款优秀的音频剪辑工具。 福昕音频剪辑 链接:www.pdf365.cn/foxit-clip/ 福昕音频剪辑是一款界面简洁、操作直观的音频编辑软件…...
“CSS”第一步——WEB开发系列13
CSS (Cascading Style Sheets,层叠样式表),是一种用来为结构化文档(如 HTML 文档或 XML 应用)添加样式(字体、间距和颜色等)的计算机语言,CSS 文件扩展名为 .css。 一、什么是 CSS&a…...
IEEE802网络协议和标准
IEEE802网络协议和标准 802委员会IEEE 802介绍现有标准 IEEE 802.3介绍物理媒介类型MAC子层与LLC子层主要内容通讯标准POE供电标准802.3af、802.3at、802.3btIEEE802.3af的工作过程:IEEE802.3af主要供电参数:IEEE802.3af的分级参数:为什么会有…...
vulnhub靶机 DC-9(渗透测试详解)
一、靶机信息收集 1、靶机下载 https://download.vulnhub.com/dc/DC-9.zip 2、靶机IP扫描 3、探测靶机主机、端口、服务版本信息 4、靶机目录扫描 二、web渗透测试 1、访问靶机IP 查看页面功能点,发现一个搜索框和登录框 2、测试一下是否存在sql注入 查看当前数…...
javaweb的新能源充电系统pf
TOC springboot339javaweb的新能源充电系统pf 第1章 绪论 1.1 课题背景 二十一世纪互联网的出现,改变了几千年以来人们的生活,不仅仅是生活物资的丰富,还有精神层次的丰富。在互联网诞生之前,地域位置往往是人们思想上不可跨域…...
如何在桌面同时展示多个窗口
一、实现2分屏显示 win箭头 二、实现3分屏显示 1. 在实现2分屏显示的基础上,再次点击箭头图标,这次选择屏幕的上方或下方。 2. 点击后,第三个窗口将会出现在你选择的区域。现在,你可以在三个窗口之间自由切换,提高工…...
The Sandbox 游戏制作教程(第 5 部分):创建基于分类的系统
欢迎回到我们的系列,我们将记录 The Sandbox Game Maker 的 “On-Equip”(装备)功能的多种用途。 如果你刚加入 The Sandbox,装备功能是 “可收集组件”(Collectable Component)中的一个多功能工具&#x…...
HTML浏览器缓存(Browser Cache)
介绍: 浏览器缓存是Web缓存中最直接、最常见的一种形式。当浏览器首次请求某个资源时,如果服务器响应中包含了缓存控制指令(如Cache-Control、Expires等),浏览器就会将这些资源存储在本地缓存中。后续请求相同资源时&a…...
短剧APP系统,推动短剧市场发展
近年来,短剧作为一直火爆的新兴行业,凭借着剧情进奏、爽、时长短等优势,深受大众欢迎,成为了大众碎片化时间的解压神器。 目前,随着短剧市场的快速发展,各个类型的短剧层出不穷,也推动了短剧AP…...
嵌入式 | 嵌入式 Linux 系统使用摄像头
点击上方"蓝字"关注我们 01、引言 >>> 在嵌入式 Linux 系统使用摄像头 俗话说“眼见为实”,这或许是为什么近年来摄像头在嵌入式系统上快速增长的原因。它们被用于不同的场景,如: 远程监控:典型的例子是闭路电视,监控人员在监视环境(或许你所在的大楼…...
C 开源库之cJSON
cJSON简介 CJSON库是一个用于解析和生成JSON数据的C语言库。 它提供了一组函数,使得在C语言中操作JSON数据变得简单而高效。 您可以使用CJSON库来解析从服务器返回的JSON数据,或者将C语言数据结构转换为JSON格式以进行传输。 cJSON 使用 官网地址&…...
ROW_NUMBER(), RANK(), DENSE_RANK() SQL排序函数图文详解
ROW_NUMBER(), RANK(), DENSE_RANK() ROW_NUMBER(): 为结果集中的每一行分配唯一的连续编号。即使有重复的值,ROW_NUMBER() 也会为它们分配不同的序号。 SELECT column_name, ROW_NUMBER() OVER (ORDER BY column_name) AS row_num FROM table_name;2. RANK(): 对结…...
Spring IoCDI(下)—DI的尾声
我们之前学习了控制反转IoC,接下来就开始学习依赖注入DI的细节。 依赖注入是一个过程,是指IoC容器在创建Bean时,去提供运行时所依赖的资源,而资源指的就是对象。我们使用 Autowired 注解,完成依赖注入的操作。简单来说…...