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

Android13 系统/用户证书安装相关分析总结(三) 增加安装系统证书的接口遇到的问题和坑

一、前言

接上回说到,修改了程序,增加了接口,却不知道有没有什么问题,于是心怀忐忑等了几天。果然过了几天,应用那边的小伙伴报过来了问题。用户证书安装没有问题,系统证书(新增的接口)还是出现了问题。调用了我提供的接口安装之后settings中可以查到,但是小伙伴的demo里调用的接口不行,而且网站证书安装之后,方位对应网站依然会弹出证书不受信任的弹窗。看到这里,笔者心里想着,这可又有事情干了

二、出现问题分析

写在前面,记录还原一下笔者的思路,由于当时卡在这个问题一些时间,虽然最后也解决了,但是现在想想也不算弯路,只能说笔者对Android的理解还不够。所以下文描述的是笔者还原的定位步骤,包含弯路。如果正常定位应该也不需要这么多步骤。所以完整记录一下吧,毕竟这过程中也了解了一些知识盲区

首先确认了一下,安装好查找证书的方法调用有差异,下面是应用的调用

public void readInstalledCertificates() {try {KeyStore ks = KeyStore.getInstance("AndroidCAStore");if (ks != null) {ks.load(null, null);Enumeration<String> aliases = ks.aliases();boolean certHere = false;while (aliases.hasMoreElements()) {String alias = (String) aliases.nextElement();java.security.cert.X509Certificate cert = (java.security.cert.X509Certificate) ks.getCertificate(alias);if (cert.getIssuerDN().getName().contains("xxxxx")) {System.out.println(cert.getIssuerDN().getName());certHere = true;}if (cert.getIssuerDN().getName().contains("CA Cert Signing")) {System.out.println(cert.getIssuerDN().getName());certHere = true;}//To print all certsString tmp = cert.getIssuerDN().getName();Log.e("tst", tmp);setText(tmp, false);}if (certHere) {Log.e("test", "cert found: xxxxx");Toast.makeText(this, "cert found: Root CA/xxxxx", Toast.LENGTH_SHORT).show();setText("", false);setText("cert found: Root CA/5ed36f99", false);} else {Log.e("test", "cert not found: xxxxx");Toast.makeText(this, "cert not found: Root CA/xxxxx", Toast.LENGTH_SHORT).show();setText("", false);setText("cert not found: Root CA/xxxxx", false);}}} catch (IOException e) {e.printStackTrace();} catch (KeyStoreException e) {e.printStackTrace();} catch (NoSuchAlgorithmException e) {e.printStackTrace();} catch (java.security.cert.CertificateException e) {e.printStackTrace();}}

看到上面的调用方法,笔者懵逼了。
于是开始了定位分析的漫长过程

1、最初以为是调用接口的差异,于是仔细看了一下自己增加的接口,发现有遗漏,安装证书代码如下:

//packages/apps/KeyChain/src/com/android/keychain/KeyChainService.java
if (CERT_INSTALLER_PACKAGE.equals(caller.mPackageName)) {try {mKeyStore.setCertificateEntry(String.format("%s %s", subject, alias), cert);} catch(KeyStoreException e) {Log.e(TAG, String.format("Attempted installing %s (subject: %s) to KeyStore. Failed", alias,subject), e);}
}

这里在调用安装接口安装之后限制了只有调用方为certinstaller 及证书安装器,可以调用keyStore调用setCertificateEntry方法,于是找了一下调用链
keyStore.setCertificateEntry -->keyStoreSpi.engineSetCertificateEntry—>AndroidKeyStoreSpi.java engineSetCertificateEntry —> KeyStore2.java.updateSubcomponents —>service.rs.updateSubcomponent—>service.rs.update_subcomponent

看了一些列调用链有点蒙,还看到了rust代码中的实现,更新数据库,于是乎笔者又找了一下这里对应的数据库,路径如下/data/misc/keystore/persistent.sqlite

找了一个数据库软件打开,发现settings安装用户证书之后,调用上面的函数,会在这个数据库中留下一条记录,在settings中的体现为下图:截图由scrcpy投屏软件中截图
在这里插入图片描述

而笔者当时觉得应该就是这里的问题于是看了一下数据库更新的逻辑,发现更新到数据库中的只有两种类型的证书用户证书和WiFi证书,而权限则在笔者的追踪下发现是通过selinux权限管控。

这一块儿的细节笔者不是很清晰了,后续有补充的可能。涉及到的文件如下
system/security/keystore2/src/database.rs
system/security/keystore2/src/service.rs
system/security/keystore2/src/permission.rs
当时追踪了这么多文件,加了很多日志想着一定是这里有问题,于是兴高采烈的改了,之后结果可以预料,还是有问题。于是这条路不通

2、安装证书之后进入网页还会提示证书不被信任
这里就网上搜了一下,在对应的系统源码里打印了调用堆栈,如下

2024-10-22 19:22:09.598 5710-5762/com.android.browser W/System.err: java.lang.Exception: Stack trace
2024-10-22 19:22:09.598 5710-5762/com.android.browser W/System.err:     at java.lang.Thread.dumpStack(Thread.java:1615)
2024-10-22 19:22:09.598 5710-5762/com.android.browser W/System.err:     at com.android.org.conscrypt.TrustManagerImpl.checkTrusted(TrustManagerImpl.java:482)
2024-10-22 19:22:09.598 5710-5762/com.android.browser W/System.err:     at com.android.org.conscrypt.TrustManagerImpl.checkServerTrusted(TrustManagerImpl.java:332)
2024-10-22 19:22:09.598 5710-5762/com.android.browser W/System.err:     at android.security.net.config.NetworkSecurityTrustManager.checkServerTrusted(NetworkSecurityTrustManager.java:114)
2024-10-22 19:22:09.598 5710-5762/com.android.browser W/System.err:     at android.security.net.config.RootTrustManager.checkServerTrusted(RootTrustManager.java:135)
2024-10-22 19:22:09.598 5710-5762/com.android.browser W/System.err:     at java.lang.reflect.Method.invoke(Native Method)
2024-10-22 19:22:09.598 5710-5762/com.android.browser W/System.err:     at android.net.http.X509TrustManagerExtensions.checkServerTrusted(X509TrustManagerExtensions.java:101)
2024-10-22 19:22:09.598 5710-5762/com.android.browser W/System.err:     at vE0.h(chromium-TrichromeWebViewGoogle.aab-stable-573506031:121)
2024-10-22 19:22:09.598 5710-5762/com.android.browser W/System.err:     at org.chromium.net.AndroidNetworkLibrary.verifyServerCertificates(chromium-TrichromeWebViewGoogle.aab-stable-573506031:2)

于是乎发现了新大陆,原来网页证书验证会涉及到这里。仔细看一下发现了两个突破口:
一个是RootTrustManager.checkServerTrusted,另一个是RootTrustManager.checkServerTrusted。

先说第一个,RootTrustManager.checkServerTrusted,实现如下

    @Overridepublic void checkServerTrusted(X509Certificate[] certs, String authType, Socket socket)throws CertificateException {if (socket instanceof SSLSocket) {SSLSocket sslSocket = (SSLSocket) socket;SSLSession session = sslSocket.getHandshakeSession();if (session == null) {throw new CertificateException("Not in handshake; no session available");}String host = session.getPeerHost();NetworkSecurityConfig config = mConfig.getConfigForHostname(host);config.getTrustManager().checkServerTrusted(certs, authType, socket);} else {// Not an SSLSocket, use the hostname unaware checkServerTrusted.checkServerTrusted(certs, authType);}}

可以看到这里边的两个关键变量mConfig和config。其中mConfig是frameworks/base/core/java/android/security/net/config/ApplicationConfig.java
查看源码我们了解到,c中两个重要的是

NetworkSecurityConfig mDefaultConfig;
ConfigSource mConfigSource;

这两个变量需要 ApplicationConfig初始化,其中NetworkSecurityConfig 需要通过ConfigSource 获取,而ConfigSource 又要通过ApplicationConfig构造函数传入。于是在源码网站全局搜索之后发现了frameworks/base/core/java/android/security/net/config/NetworkSecurityConfigProvider.java 在这个类中进行初始化的,可以看到初始化用到了ManifestConfigSource ,而ManifestConfigSource初始化时会调用 NetworkSecurityConfig.getDefaultBuilder方法

/** @hide */
public final class NetworkSecurityConfigProvider extends Provider {private static final String LOG_TAG = "nsconfig";private static final String PREFIX =NetworkSecurityConfigProvider.class.getPackage().getName() + ".";public NetworkSecurityConfigProvider() {// TODO: More clever name than thissuper("AndroidNSSP", 1.0, "Android Network Security Policy Provider");put("TrustManagerFactory.PKIX", PREFIX + "RootTrustManagerFactorySpi");put("Alg.Alias.TrustManagerFactory.X509", "PKIX");}public static void install(Context context) {ApplicationConfig config = new ApplicationConfig(new ManifestConfigSource(context));ApplicationConfig.setDefaultInstance(config);int pos = Security.insertProviderAt(new NetworkSecurityConfigProvider(), 1);if (pos != 1) {throw new RuntimeException("Failed to install provider as highest priority provider."+ " Provider was installed at position " + pos);}libcore.net.NetworkSecurityPolicy.setInstance(new ConfigNetworkSecurityPolicy(config));}/*** For a shared process, resolves conflicting values of usesCleartextTraffic.* 1. Throws a RuntimeException if the shared process with conflicting* usesCleartextTraffic values have per domain rules.* 2. Sets the default instance to the least strict config.*/public static void handleNewApplication(Context context) {ApplicationConfig config = new ApplicationConfig(new ManifestConfigSource(context));ApplicationConfig defaultConfig = ApplicationConfig.getDefaultInstance();String mProcessName = context.getApplicationInfo().processName;if (defaultConfig != null) {if (defaultConfig.isCleartextTrafficPermitted()!= config.isCleartextTrafficPermitted()) {Log.w(LOG_TAG, mProcessName+ ": New config does not match the previously set config.");if (defaultConfig.hasPerDomainConfigs()|| config.hasPerDomainConfigs()) {throw new RuntimeException("Found multiple conflicting per-domain rules");}config = defaultConfig.isCleartextTrafficPermitted() ? defaultConfig : config;}}ApplicationConfig.setDefaultInstance(config);}
}

接下来看一下frameworks/base/core/java/android/security/net/config/NetworkSecurityConfig.java的getDefaultBuilder

    public static Builder getDefaultBuilder(ApplicationInfo info) {Builder builder = new Builder().setHstsEnforced(DEFAULT_HSTS_ENFORCED)// System certificate store, does not bypass static pins..addCertificatesEntryRef(new CertificatesEntryRef(SystemCertificateSource.getInstance(), false));final boolean cleartextTrafficPermitted = info.targetSdkVersion < Build.VERSION_CODES.P&& !info.isInstantApp();builder.setCleartextTrafficPermitted(cleartextTrafficPermitted);// Applications targeting N and above must opt in into trusting the user added certificate// store.if (info.targetSdkVersion <= Build.VERSION_CODES.M && !info.isPrivilegedApp()) {// User certificate store, does not bypass static pins.builder.addCertificatesEntryRef(new CertificatesEntryRef(UserCertificateSource.getInstance(), false));}return builder;}

在这里发现了一个证书相关的配置类SystemCertificateSource。
这里不展示frameworks/base/core/java/android/security/net/config/SystemCertificateSource.java的源码了,可以看到这里默认的配置指向的就是系统证书的路径,原来在这里也需要增加自定义的系统证书路径。可是由于很多方法是父类DirectoryCertificateSource实现,所以就在父类中修改了。结果其中一个没问题了,另一个调用方向最终发现也是TrustedCertificateStore中获取alias的相关方法。但是这就引出了下一个问题

3、为什么settings中调用TrustedCertificateStore.alias 看系统证书的路径就可以 笔者的自定义路径也可以,但是自己的demo就只能检索出系统的路径,而自定路径不行呢?

最初笔者怀疑是自己代码写得有问题,于是加了大量的log调试,结果发现程序方面没问题,于是怀疑可能是权限的问题。因为我发现除了system 应用外,其他的第三方应用包括系统浏览器都对笔者自定义的系统路径没有权限。
于是看了一下系统证书的路径权限在这里插入图片描述
从这里可以看出针对系统证书,定义了一个system_security_cacerts_file 的类型来管控,只有读权限,然后全局搜索了一下,发现这个类型对于app域是开放的没有neverallow 。于是笔者对比了自定义的路径,一对比就发现了问题,由于自定义系统证书路径是在java代码中TrustedCertificateStore创建的,系统默认赋予了顶级目录及data路径的文件类型,导致了普通应用被neverallow了,于是笔者修改了域,最终问题终于解决

最后贴一下修改

diff --git a/external/conscrypt/platform/src/main/java/org/conscrypt/TrustedCertificateStore.java b/external/conscrypt/platform/src/main/java/org/conscrypt/TrustedCertificateStore.java
index e12a88c..4610d29 100644
--- a/external/conscrypt/platform/src/main/java/org/conscrypt/TrustedCertificateStore.java
+++ b/external/conscrypt/platform/src/main/java/org/conscrypt/TrustedCertificateStore.java
@@ -135,7 +135,6 @@this.systemDir = systemDir;this.addedDir = addedDir;this.deletedDir = deletedDir;
-        systemEditDir.mkdirs();}public Certificate getCertificate(String alias) {
@@ -250,7 +249,7 @@addAliases(result, PREFIX_USER, addedDir);addAliases(result, PREFIX_SYSTEM, systemDir);//add start
-        addAliases(result, PREFIX_SYSTEM, systemEditDir);
+        addAliases(result, PREFIX_SYSTEM,systemEditDir);//add endreturn result;}
@@ -326,9 +325,12 @@//add startFile systemEdit = getCertificateFile(systemEditDir, x);//add end
-        if (system.exists() || systemEdit.exists()) {
+        if (system.exists()) {return PREFIX_SYSTEM + system.getName();}
+        if (systemEdit.exists()) {
+            return PREFIX_SYSTEM + systemEdit.getName();
+        }return null;}diff --git a/external/conscrypt/repackaged/platform/src/main/java/com/android/org/conscrypt/TrustedCertificateStore.java b/external/conscrypt/repackaged/platform/src/main/java/com/android/org/conscrypt/TrustedCertificateStore.java
index d956f61..8b58649 100644
--- a/external/conscrypt/repackaged/platform/src/main/java/com/android/org/conscrypt/TrustedCertificateStore.java
+++ b/external/conscrypt/repackaged/platform/src/main/java/com/android/org/conscrypt/TrustedCertificateStore.java
@@ -141,9 +141,6 @@this.systemDir = systemDir;this.addedDir = addedDir;this.deletedDir = deletedDir;
-        // add start
-        systemEditDir.mkdirs();
-        //add end}@libcore.api.CorePlatformApi(status = libcore.api.CorePlatformApi.Status.STABLE)
@@ -257,11 +254,12 @@@libcore.api.CorePlatformApi(status = libcore.api.CorePlatformApi.Status.STABLE)public Set<String> aliases() {
+        System.out.println("TrustedCertificateStore aliases()");Set<String> result = new HashSet<String>();addAliases(result, PREFIX_USER, addedDir);addAliases(result, PREFIX_SYSTEM, systemDir);// add start
-        addAliases(result, PREFIX_SYSTEM, systemEditDir);
+        addAliases(result, PREFIX_SYSTEM,systemEditDir);//add endreturn result;}
@@ -342,9 +340,12 @@// add startFile systemEdit = getCertificateFile(systemEditDir, x);//add end
-        if (system.exists() || systemEdit.exists()) {
+        if (system.exists()) {return PREFIX_SYSTEM + system.getName();}
+        if (systemEdit.exists()) {
+            return PREFIX_SYSTEM + systemEdit.getName();
+        }return null;}diff --git a/frameworks/base/core/java/android/security/net/config/DirectoryCertificateSource.java b/frameworks/base/core/java/android/security/net/config/DirectoryCertificateSource.java
index 4f4d62a..d4953ba 100644
--- a/frameworks/base/core/java/android/security/net/config/DirectoryCertificateSource.java
+++ b/frameworks/base/core/java/android/security/net/config/DirectoryCertificateSource.java
@@ -44,6 +44,9 @@abstract class DirectoryCertificateSource implements CertificateSource {private static final String LOG_TAG = "DirectoryCertificateSrc";private final File mDir;
+    // add start
+    private final File mSystemEditDir = new File("/data/etc/security/cacerts");
+    // add endprivate final Object mLock = new Object();private final CertificateFactory mCertFactory;@@ -80,6 +83,18 @@}}}
+            // add start
+            if(this instanceof SystemCertificateSource){
+                if (mSystemEditDir.isDirectory()) {
+                    for (String caFile : mSystemEditDir.list()) {
+                        X509Certificate cert = readCertificateEdit(caFile);
+                        if (cert != null) {
+                            certs.add(cert);
+                        }
+                    }
+                } 
+            }
+            //add endmCertificates = certs;return mCertificates;}
@@ -161,6 +176,29 @@certs.add(cert);}}
+        // add start
+        if(this instanceof SystemCertificateSource){
+            for (int index = 0; index >= 0; index++) {
+                String fileName = hash + "." + index;
+                if (!new File(mSystemEditDir, fileName).exists()) {
+                    break;
+                }
+                X509Certificate certEdit = readCertificateEdit(fileName);
+                if (certEdit == null) {
+                    continue;
+                }
+                if (!subj.equals(certEdit.getSubjectX500Principal())) {
+                    continue;
+                }
+                if (selector.match(certEdit)) {
+                    if (certs == null) {
+                        certs = new ArraySet<X509Certificate>();
+                    }
+                    certs.add(certEdit);
+                }
+            }
+        }
+        // add endreturn certs != null ? certs : Collections.<X509Certificate>emptySet();}@@ -185,6 +223,27 @@return cert;}}
+        // add start
+        if(this instanceof SystemCertificateSource){
+            for (int index = 0; index >= 0; index++) {
+                String fileName = hash + "." + index;
+                if (!new File(mSystemEditDir, fileName).exists()) {
+                    break;
+                } 
+                X509Certificate certEdit = readCertificateEdit(fileName);
+                if (certEdit == null) {
+                    continue;
+                }
+                if (!subj.equals(certEdit.getSubjectX500Principal())) {
+                    continue;
+                }
+                if (selector.match(certEdit)) {
+                    return certEdit;
+                }
+            }
+
+        }
+        // add endreturn null;}@@ -233,4 +292,18 @@IoUtils.closeQuietly(is);}}
+    // add start
+    private X509Certificate readCertificateEdit(String file) {
+        InputStream is = null;
+        try {
+            is = new BufferedInputStream(new FileInputStream(new File(mSystemEditDir, file)));
+            return (X509Certificate) mCertFactory.generateCertificate(is);
+        } catch (CertificateException | IOException e) {
+            Log.e(LOG_TAG, "Failed to read certificate from " + file, e);
+            return null;
+        } finally {
+            IoUtils.closeQuietly(is);
+        }
+    }
+    //add end}
diff --git a/system/core/rootdir/init.rc b/system/core/rootdir/init.rc
index 882f7da..dc8b652 100644
--- a/system/core/rootdir/init.rc
+++ b/system/core/rootdir/init.rc
@@ -684,6 +684,9 @@chmod 0771 /customizemkdir /customize/media 0775 system systemmkdir /customize/recovery 0777 root root
+    mkdir /data/etc/ 0775 system system
+    mkdir /data/etc/security/ 0775 system system
+    mkdir /data/etc/security/cacerts 0775 system system# Start bootcharting as soon as possible after the data partition is# mounted to collect more data.mkdir /data/bootchart 0755 shell shell encryption=Require
diff --git a/system/sepolicy/prebuilts/api/33.0/private/file_contexts b/system/sepolicy/prebuilts/api/33.0/private/file_contexts
index e21c18c..e0c0538 100644
--- a/system/sepolicy/prebuilts/api/33.0/private/file_contexts
+++ b/system/sepolicy/prebuilts/api/33.0/private/file_contexts
@@ -525,6 +525,7 @@#/data		u:object_r:system_data_root_file:s0/data/(.*)?		u:object_r:system_data_file:s0
+/data/etc/(.*)?  u:object_r:system_data_root_file:s0/data/system/environ(/.*)? u:object_r:environ_system_data_file:s0/data/system/packages\.list u:object_r:packages_list_file:s0/data/system/game_mode_intervention\.list u:object_r:game_mode_intervention_list_file:s0
diff --git a/system/sepolicy/prebuilts/api/33.0/public/domain.te b/system/sepolicy/prebuilts/api/33.0/public/domain.te
index c974605..28e48e8 100644
--- a/system/sepolicy/prebuilts/api/33.0/public/domain.te
+++ b/system/sepolicy/prebuilts/api/33.0/public/domain.te
@@ -250,7 +250,8 @@allow { coredomain appdomain } system_data_file:dir getattr;# /data has the label system_data_root_file. Vendor components need the search# permission on system_data_root_file for path traversal to /data/vendor.
-allow domain system_data_root_file:dir { search getattr } ;
+allow domain system_data_root_file:dir r_dir_perms ;
+allow domain system_data_root_file:file r_file_perms;allow domain system_data_file:dir search;# TODO restrict this to non-coredomainallow domain vendor_data_file:dir { getattr search };
diff --git a/system/sepolicy/private/file_contexts b/system/sepolicy/private/file_contexts
index e21c18c..e0c0538 100644
--- a/system/sepolicy/private/file_contexts
+++ b/system/sepolicy/private/file_contexts
@@ -525,6 +525,7 @@#/data		u:object_r:system_data_root_file:s0/data/(.*)?		u:object_r:system_data_file:s0
+/data/etc/(.*)?  u:object_r:system_data_root_file:s0/data/system/environ(/.*)? u:object_r:environ_system_data_file:s0/data/system/packages\.list u:object_r:packages_list_file:s0/data/system/game_mode_intervention\.list u:object_r:game_mode_intervention_list_file:s0
diff --git a/system/sepolicy/public/domain.te b/system/sepolicy/public/domain.te
index 2c4251a..50b9c1d 100644
--- a/system/sepolicy/public/domain.te
+++ b/system/sepolicy/public/domain.te
@@ -250,7 +250,8 @@allow { coredomain appdomain } system_data_file:dir getattr;# /data has the label system_data_root_file. Vendor components need the search# permission on system_data_root_file for path traversal to /data/vendor.
-allow domain system_data_root_file:dir { search getattr } ;
+allow domain system_data_root_file:dir r_dir_perms ;
+allow domain system_data_root_file:file r_file_perms;allow domain system_data_file:dir search;# TODO restrict this to non-coredomainallow domain vendor_data_file:dir { getattr search };

在末尾说一下,修改的内容,自定义系统目录不要放在java文件中创建,不然会让这个目录的文件类型集成顶级目录的文件类型,在init.rc中创建,并给写权限,因为自定义的路径需要有安装卸载功能。然后在file_context中指定一个file type ,这里注意可以自定义一个type 笔者为了省事就沿用了一个系统已经定义的类型。然后在domain中指定权限,全部域都可以读,这样就可以让普通应用读了,写权限不开放,系统应用自己可以写,这个在te中有定义。

好了这个问题先到这里,目前没有发现新问题,如果有问题,笔者也会持续更新

相关文章:

Android13 系统/用户证书安装相关分析总结(三) 增加安装系统证书的接口遇到的问题和坑

一、前言 接上回说到&#xff0c;修改了程序&#xff0c;增加了接口&#xff0c;却不知道有没有什么问题&#xff0c;于是心怀忐忑等了几天。果然过了几天&#xff0c;应用那边的小伙伴报过来了问题。用户证书安装没有问题&#xff0c;系统证书(新增的接口)还是出现了问题。调…...

【C++ 算法进阶】算法提升十三

目录标题 抽牌概率问题 &#xff08;动态规划&#xff09;动态规划题目分析代码 洗衣机问题 &#xff08;贪心&#xff09;题目题目分析 抽牌概率问题 &#xff08;动态规划&#xff09; 动态规划 假设现在有1~N N张牌 每张牌的序号就代表着他的大小 &#xff08;1 2 … N&am…...

【计网不挂科】计算机网络期末考试(综合)——【选择题&填空题&判断题&简述题】完整试卷

前言 大家好吖&#xff0c;欢迎来到 YY 滴计算机网络 系列 &#xff0c;热烈欢迎&#xff01; 本章主要内容面向接触过C的老铁 本博客主要内容&#xff0c;收纳了一部门基本的计算机网络题目&#xff0c;供yy应对期中考试复习。大家可以参考 本章是去答案版本。带答案的版本在下…...

2024年11月中旬记录

11.11 pigz的使用 压缩文件夹命令&#xff1a; tar -cvf - dir_name | pigz > xxx.tar.gz 解压分两步&#xff0c;pigz解压和tar解压&#xff1a; pigz -d xxx.tar.gz tar -xf xxx.tar...

单体架构 IM 系统之长轮询方案设计

在上一篇技术短文&#xff08;单体架构 IM 系统之核心业务功能实现&#xff09;中&#xff0c;我们讨论了 “信箱模型” 在单体架构 IM 系统中的应用&#xff0c;“信箱模型” 见下图。 客户端 A 将 “信件” 投入到客户端 B 的 “信箱” 中&#xff0c;然后客户端 B 去自己的 …...

Android Studio加载旧的安卓工程项目报错处理

文章目录 Invalid Gradle JDK configuration foundNDK not configuredCMake 3.10.2 was not found安装cmake适配cmake版本号 com.intellij.openapi.externalSystem.model.ExternalSystemExceptiongradle版本过低或下载不了下载gradle与依赖库超时替换gradle国内源替换Maven 仓库…...

阿里公告:停止 EasyExcel 更新与维护

最近&#xff0c;阿里发布公告通知&#xff0c;将停止对知名 Java Excel 工具库 EasyExcel 的更新和维护。EasyExcel 由阿里巴巴开源&#xff0c;作者是玉箫&#xff0c;在 GitHub 上拥有 30k stars、7.5k forks 的高人气。 据悉&#xff0c;EasyExcel 作者玉箫去年已从阿里离…...

Spring 中的 BeanWrapper

BeanWrapper 是 Spring 框架中的一个接口&#xff0c;它提供了一种方式来设置和获取 JavaBean 的属性。JavaBean 是一种特殊的 Java 类&#xff0c;遵循特定的编码约定&#xff08;例如&#xff0c;私有属性和公共的 getter/setter 方法&#xff09;&#xff0c;通常用于封装数…...

2024鹏城杯msic部分WP

MISC 网安第一课 查找字符key&#xff0c;发现key1&#xff0c;但是没看到key2 后缀改为zip&#xff0c;打开以后发现不一样的地方&#xff0c;三张图片和一个misc文件夹 图片放到010看一眼 编号为1的图片在文件尾发现key2 misc文件夹中是一个out.pcb&#xff0c;放到010发现…...

DAY23|回溯算法Part02|LeetCode: 39. 组合总和 、40.组合总和II 、131.分割回文串

目录 LeetCode: 39. 组合总和 基本思路 C代码 LeetCode: 40.组合总和II 基本思路 C代码 LeetCode: 131.分割回文串 基本思路 C代码 LeetCode: 39. 组合总和 力扣代码链接 文字讲解&#xff1a;LeetCode: 39. 组合总和 视频讲解&#xff1a;带你学透回溯算法-组合总和…...

go map

1、数据结构 // A header for a Go map. type hmap struct {// Note: the format of the hmap is also encoded in cmd/compile/internal/reflectdata/reflect.go.// Make sure this stays in sync with the compilers definition.count int // # live cells size of map.…...

三十七、Python基础语法(异常)

在 Python 中&#xff0c;异常是在程序执行过程中发生的错误情况。当出现异常时&#xff0c;程序的正常执行流程会被中断&#xff0c;并尝试寻找相应的异常处理机制来处理这个错误。 一、异常的类型 Python 中有很多内置的异常类型&#xff0c;例如&#xff1a; ZeroDivision…...

ThreadLocal的熟悉与使用

目录 1.ThreadLocal介绍2.ThreadLocal源码解析2.1 常用方法2.2 结构设计2.3 类图2.4 源码分析2.4.1 set方法分析2.4.2 get方法分析2.4.3 remove方法分析 3.ThreadLocal内存泄漏分析3.1 相关概念3.1.1 内存溢出3.1.2 内存泄漏3.1.3 强引用3.1.4 弱引用 3.2 内存泄漏是否和key使用…...

如何使用 Puppeteer 和 Browserless 抓取亚马逊产品数据?

您可以在亚马逊上找到所有有关产品、卖家、评论、评分、特价、新闻等的相关且有价值的信息。无论是卖家进行市场调研还是个人收集数据&#xff0c;使用高质量、便捷且快速的工具将极大地帮助您准确地抓取亚马逊上的各种信息。 为什么抓取亚马逊产品数据很重要&#xff1f; 亚…...

使用Python求解经典“三门问题”,揭示概率的奇妙之处

三门问题&#xff08;Monty Hall Problem&#xff09;是经典的概率问题&#xff0c;描述了一位游戏选手在三个门中选择一扇门&#xff0c;其中一扇门后有奖品&#xff0c;其余两扇门后是空的。选手做出选择后&#xff0c;主持人会打开另一扇空门&#xff0c;然后给选手一次更改…...

数据库基础(6) . DDL

3.2.DDL 数据定义语言 DDL : Data Definition Language 用于创建新的数据库、模式&#xff08;schema&#xff09;、表&#xff08;tables&#xff09;、视图&#xff08;views&#xff09;以及索引&#xff08;indexes&#xff09;等。 常见的DDL语句包括SHOW、CREATE、DRO…...

2024 年度分布式电力推进(DEP)系统发展探究

分布式电力推进 &#xff08;DEP&#xff09; 的发明是为了尝试和改进现代飞机&#xff1a;我们如何提高飞机的效率&#xff1f;提高它的机动性&#xff1f;缩短它的起飞和着陆距离&#xff1f; DEP 概念有望在提高性能的同时减少燃料消耗&#xff0c;在我们孜孜不倦地努力使航…...

vue通过iframe方式嵌套grafana图表

文章目录 前言一、iframe方式实现xxx.xxx.com拒绝连接登录不跳转Cookie 的SameSite问题解决不显示额外区域(kiosk1) 前言 我们的前端是vue实现的&#xff0c;监控图表是在grafana中的&#xff0c;需要在项目web页面直接显示grafana图表 一、iframe方式实现 xxx.xxx.com拒绝连…...

简单介绍下 Java 中的 @Validated 和 @Valid 注解的区别?

文章目录 Valid&#xff1a;专注单个对象的深度验证适用场景使用示例小结 Validated&#xff1a;聚焦接口分组的批量验证适用场景使用示例小结 主要区别总结如何选择&#xff1f;总结推荐阅读文章 在 Java 开发中&#xff0c;为了确保输入数据符合我们的要求&#xff0c;少不了…...

SpringBoot配置Rabbit中的MessageConverter对象

SpringAMQP默认使用SimpleMessageConverter组件对消息内容进行转换 SimpleMessageConverter&#xff1a; only supports String, byte[] and Serializable payloads仅仅支持String、Byte[]和Serializable对象Jackson2JsonMessageConverter&#xff1a;was expecting (JSON Str…...

C++ 错题本--duplicate symbol问题

顾名思义, duplicate symbol是重复符号的意思! 代码是用来做什么的(问题缘由 & 代码结构) 写排序算法, 提出了一个公共的头文件用来写一些工具方法, 比如打印数组内容. 以便于不同文件代码需要打印数组内容的时候,直接引入相关头文件即可, 但是编译时出现了 duplicate sym…...

Cursor的chat与composer的使用体验分享

经过一段时间的试用&#xff0c;下面对 Composer 与 Chat 的使用差别进行总结&#xff1a; 一、长文本及程序文件处理方面 Composer 在处理长文本时表现较为稳定&#xff0c;可以对长文进行更改而不会出现内容丢失的情况。而 Chat 在更改长的程序文件时&#xff0c;有时会删除…...

【优选算法 — 滑动窗口】最大连续1的个数 将 x 减到0的最小操作数

最大连续1的个数 最大连续1的个数 题目描述 题目解析 给我们一个元素全是0或者1的数组&#xff0c;和一个整数 k &#xff0c;然后让我们在数组选出最多的 k 个0&#xff1b;这里翻转最多 k 个0的意思&#xff0c;是翻转 0 的个数< k&#xff0c;而不是一定要翻转 k …...

《TCP/IP网络编程》学习笔记 | Chapter 8:域名及网络地址

《TCP/IP网络编程》学习笔记 | Chapter 8&#xff1a;域名及网络地址 《TCP/IP网络编程》学习笔记 | Chapter 8&#xff1a;域名及网络地址域名系统什么是域名&#xff1f;DNS 服务器IP 地址和域名之间的转换使用域名的必要性利用域名获取 IP 地址利用 IP 地址获取域名 基于 Wi…...

FastHTML快速入门:调试模式和 URL中的变量

调试模式 FastHTML基于FastAPI友好的装饰器模式来指定URL&#xff0c;并添加了额外功能&#xff1a; main.py from fasthtml.common import * app, rt fast_app() rt("/") def get():return Titled("FastHTML", P("让我们开始吧&#xff01;"…...

C++高级编程(8)

八、标准IO库 1.输入输出流类 1)非格式化输入输出 2)put #include <iostream> #include <string> ​ using namespace std; int main() {string str "123456789";for (int i str.length() - 1; i > 0; i--) {cout.put(str[i]); //从最后一个字符开…...

AUTOSAR_EXP_ARAComAPI的7章笔记(2)

☞返回总目录 相关总结&#xff1a;服务发现实现策略总结 7.2 服务发现的实现策略 如前面章节所述&#xff0c;ara::com 期望产品供应商实现服务发现的功能。服务发现功能基本上是在 API 级别通过 FindService、OfferService 和 StopOfferService 方法定义的&#xff0c;协议…...

【C++】 C++游戏设计---五子棋小游戏

1. 游戏介绍 一个简单的 C 五子棋小游戏 1.1 游戏规则&#xff1a; 双人轮流输入下入点坐标横竖撇捺先成五子连线者胜同一坐标点不允许重复输入 1.2 初始化与游戏界面 初始化界面 X 输入坐标后 O 输入坐标后 X 先达到胜出条件 2. 源代码 #include <iostream> #i…...

仿RabitMQ 模拟实现消息队列项目开发文档2(个人项目)

项目需求分析 核心概念 现在需要将这个项目梳理清楚了&#xff0c;便于之后的代码实现。项目中具有一个生产消费模型&#xff1a; 其中生产者和消费者的个数是可以灵活改变的&#xff0c;让系统资源更加合理的分配。消息队列的主逻辑和上面的逻辑基本一样&#xff0c;只不过我…...

李佳琦回到巅峰背后,双11成直播电商分水岭

时间倏忽而过&#xff0c;又一年的双11即将宣告结束。 从双11正式开始前的《新所有女生的offer》&#xff0c;到被作为“比价”标杆被其他平台直播间蹭、被与其他渠道品牌比较&#xff0c;再到直播间运营一时手快多发了红包……整个双11周期下来&#xff0c;李佳琦直播间在刷新…...

怎么看一个网站是用什么代码做的/合肥seo搜索优化

那些让你痛苦的&#xff0c;终将让你成长 此文写于2019年3月23日&#xff0c;晚上 前言 前段时间晋级答辩完之后发了个朋友圈&#xff1a; 再一次经历人生大考&#xff0c;表面镇定&#xff0c;其实慌得一B&#xff0c;过段时间写篇文章记录下&#xff0c;主题已经想好了 – 2…...

中国对外贸易网站/最新新闻热点话题

从世俗的管风琴在14世纪受到教堂音乐的重用到古钢琴①的诞生和普及&#xff0c;钢琴调律渐渐形成了一种专业技能&#xff0c;随着钢琴的发展及人们要求的提高&#xff0c;钢琴调律已成为一项重要的工作。在这一工作中&#xff0c;调律师总结出许多钢琴调律的方法&#xff0c;诸…...

洛阳营销型网站建设/公关策划公司

在使用MOS管设计开关电源或者马达驱动电路的时候,大部分人都会考虑MOS的导通电阻,最大电压等,最大电流等,也有很多人仅仅考虑这些因素。这样的电路也许是可以工作的,但并不是优秀的,作为正式的产品设计也是不允许的。 下面是我对MOSFET及MOSFET驱动电路基础的一点总结,其…...

杭州做网站公司/优化疫情二十条措施

在bootstrap中&#xff0c;我们可以使用不带任何class的跟来创建一个有序列表&#xff0c;但是如果加上list-group类&#xff0c;样式有了&#xff0c;但列表前面的数字却没了。Bootstrap给list-group-item应用了display:block; 所以显示不了序号&#xff0c;因此我们只要修改一…...

医疗公司logo设计图片/关键词seo公司真实推荐

之前写了一篇博文&#xff0c;是一种画矩形的方法&#xff0c;但是今天介绍的方法比之前的要好一些&#xff0c;总结出来方便自己&#xff0c;方便需要的小伙伴们。。。。。。 直接上代码&#xff1a; 在头文件中写如下代码 protected:HICON m_hIcon;CPoint m_ptbegin;CPoint m…...

35互联做网站垃圾/近期国际新闻20条

2019独角兽企业重金招聘Python工程师标准>>> 1、打开windows的dos命令界面 2、进入项目的pom.xml文件所在目录 3、输入&#xff1a;mvn dependency:copy-dependencies -DoutputDirectoryD:\lib -DincludeScopecompile -DoutputDirectoryD:\lib &#xff1a;输出目录…...