当前位置: 首页 > 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…...

从WWDC看苹果产品发展的规律

WWDC 是苹果公司一年一度面向全球开发者的盛会&#xff0c;其主题演讲展现了苹果在产品设计、技术路线、用户体验和生态系统构建上的核心理念与演进脉络。我们借助 ChatGPT Deep Research 工具&#xff0c;对过去十年 WWDC 主题演讲内容进行了系统化分析&#xff0c;形成了这份…...

CocosCreator 之 JavaScript/TypeScript和Java的相互交互

引擎版本&#xff1a; 3.8.1 语言&#xff1a; JavaScript/TypeScript、C、Java 环境&#xff1a;Window 参考&#xff1a;Java原生反射机制 您好&#xff0c;我是鹤九日&#xff01; 回顾 在上篇文章中&#xff1a;CocosCreator Android项目接入UnityAds 广告SDK。 我们简单讲…...

Psychopy音频的使用

Psychopy音频的使用 本文主要解决以下问题&#xff1a; 指定音频引擎与设备&#xff1b;播放音频文件 本文所使用的环境&#xff1a; Python3.10 numpy2.2.6 psychopy2025.1.1 psychtoolbox3.0.19.14 一、音频配置 Psychopy文档链接为Sound - for audio playback — Psy…...

多模态大语言模型arxiv论文略读(108)

CROME: Cross-Modal Adapters for Efficient Multimodal LLM ➡️ 论文标题&#xff1a;CROME: Cross-Modal Adapters for Efficient Multimodal LLM ➡️ 论文作者&#xff1a;Sayna Ebrahimi, Sercan O. Arik, Tejas Nama, Tomas Pfister ➡️ 研究机构: Google Cloud AI Re…...

【JavaWeb】Docker项目部署

引言 之前学习了Linux操作系统的常见命令&#xff0c;在Linux上安装软件&#xff0c;以及如何在Linux上部署一个单体项目&#xff0c;大多数同学都会有相同的感受&#xff0c;那就是麻烦。 核心体现在三点&#xff1a; 命令太多了&#xff0c;记不住 软件安装包名字复杂&…...

Maven 概述、安装、配置、仓库、私服详解

目录 1、Maven 概述 1.1 Maven 的定义 1.2 Maven 解决的问题 1.3 Maven 的核心特性与优势 2、Maven 安装 2.1 下载 Maven 2.2 安装配置 Maven 2.3 测试安装 2.4 修改 Maven 本地仓库的默认路径 3、Maven 配置 3.1 配置本地仓库 3.2 配置 JDK 3.3 IDEA 配置本地 Ma…...

【Go语言基础【12】】指针:声明、取地址、解引用

文章目录 零、概述&#xff1a;指针 vs. 引用&#xff08;类比其他语言&#xff09;一、指针基础概念二、指针声明与初始化三、指针操作符1. &&#xff1a;取地址&#xff08;拿到内存地址&#xff09;2. *&#xff1a;解引用&#xff08;拿到值&#xff09; 四、空指针&am…...

七、数据库的完整性

七、数据库的完整性 主要内容 7.1 数据库的完整性概述 7.2 实体完整性 7.3 参照完整性 7.4 用户定义的完整性 7.5 触发器 7.6 SQL Server中数据库完整性的实现 7.7 小结 7.1 数据库的完整性概述 数据库完整性的含义 正确性 指数据的合法性 有效性 指数据是否属于所定…...

实战三:开发网页端界面完成黑白视频转为彩色视频

​一、需求描述 设计一个简单的视频上色应用&#xff0c;用户可以通过网页界面上传黑白视频&#xff0c;系统会自动将其转换为彩色视频。整个过程对用户来说非常简单直观&#xff0c;不需要了解技术细节。 效果图 ​二、实现思路 总体思路&#xff1a; 用户通过Gradio界面上…...

Spring Security 认证流程——补充

一、认证流程概述 Spring Security 的认证流程基于 过滤器链&#xff08;Filter Chain&#xff09;&#xff0c;核心组件包括 UsernamePasswordAuthenticationFilter、AuthenticationManager、UserDetailsService 等。整个流程可分为以下步骤&#xff1a; 用户提交登录请求拦…...