一种基于HTTPS实现的Web账号登录Linux桌面系统的实现方案

问题由来

客户需求计划列入支持第三方帐号系统,包括Web账号。需求来源是用户想要用它们的帐号直接登录Linux Deepin操作系统。一个失败的实现方案是用户以较小的成本改造帐号管理系统发布HTTP服务,我们开发一个PAM模块与Web服务器交互,数据格式化采用JSON。结果遇到su提示帐号不存在的问题。在Linux Deepin系统登录界面、通过技术手段进入桌面后发现锁屏界面无法解锁等诸多问题。通过程序验证和su源代码分析验证,此方案最大的局限性是su在识别到用户的信息之前不会执行PAM模块。此方案只能用程序调用pam_authenticate触发PAM模块的执行。

Linux Name Service Switch

Linux NSS (Name Service Switch) 是一种在 Linux 系统中实现域名解析,用户认证和授权等功能的模块化系统。它提供了一种灵活的方式来配置系统如何查找用户,组,密码和其他网络资源的信息。

NSS 的核心概念是所谓的 “Switch”,这是一组可插入的模块,它们根据一定的顺序来处理不同类型的请求。用户可以通过编辑 /etc/nsswitch.conf 文件来配置这些模块的加载顺序,以达到满足自己特定需求的目的。例如,您可以通过 NSS 配置更改系统的验证源,以便使用 LDAP 或 Kerberos 等网络身份验证服务,而不是使用本地 /etc/passwd 文件。

Linux NSS 可以支持多种不同的数据源,包括本地文件、NIS、LDAP、Kerberos 等。这些模块还可以针对特定服务或应用程序进行定制化,以实现更高效的查询和更安全的身份验证和授权。使用 Linux NSS,系统管理员可以更灵活地管理 Linux 系统,满足不同用户对系统资源和服务的需求。

本文将详细讲解开发过程,实现Web账号登录Linux桌面系统。

关于getpwnam函数

NSS的作用是识别身份信息。传统的用户名和密码验证方式,身份信息是一个字符串。NSS通过调用这个方法寻找这个字符串对应到系统中的用户,获取它关联的UID和GID,从而对它进行管理。Oracle官方文档原话:

The name service switch is a file named nsswitch.conf. It controls how a client machine or application obtains network information. It is used by client applications that call any of the getXbyY() interfaces such as the following.gethostbyname()
getpwuid()
getpwnam()
getipnodebyname()

getpwnam不是直接读取读取/etc/passwd或者/etc/shadow文本文件,它取决于nsswitch.confpasswd这一行的配置。对于Linux Deepin操作系统如果配置了files或者compat,那么最终由libnss_files-2.28.so或者libnss_compat-2.28.so读取这几个配置文件,程序从getpwnam运行到了_nss_files_getpwnam_r或者_nss_compat_getpwnam_r

NSS释义

这里的NSS指的是Linux Name Service Switch,不是Linux Network Security Service。两者对Linux都很重要,资料都很稀缺。我们可以理解为名字解析服务,它实现把外部输入的用户信息与系统中的用户信息关联。它按照/etc/nsswitch.conf指定的顺序逐个模块调用,如果找到了,就立即返回libc,su根据返回的用户信息启动PAM流程,进行身份验证。

nsswitch配置文件格式

一行写一种类型的配置,每行以类型名称加冒开头,以NSS模块名称列表结尾,多个NSS模块名称以空格隔开。NSS总共支持16种类型的配置。常见的有passwdgroupshadowgshadowhostsnetworksprotocolsrpcnetgroup等等。其中passwd类别实现用户身份识别。模块与类别不是一对一的关系,libc给每个类型别都定义了一套接口,这个接口函数名通常以_r结尾,比如getpwuid_r。NSS模块的实现是动态链接库,文件名必须以libnss_为前缀,以.so.so.2为后缀,中间部分就是模块名称,比如libnss_mjaw.so,它的模块名称就是mjaw。因它实现了passwdgroupshadowgshadowhostsnetworksrpcprotocols这些类别的接口,所以配置文件中这些类型对应的行都可以加上mjaw这个模块名称。

开发

引用Petzold Charles先生的一句名言: Do not call me, I will call you 。NSS程序设计须深刻理解这句话,下面的每一个函数都不是开发者要调用的函数,而是系统用户态边界一定会调用你的函数。

NSS模块要求开发者采用C语言,Qt代码无法在PAM和NSS模块的上下文环境中运行。在PAM和NSS模块编程中采用C与C++混合编程的方式对于Qt来说有很多问题需要解决,其它的框架暂未尝试。对于HTTP,可以用cURL。对于Json解析,可采用cJSON,对于密码加密,可采用mHash。

认证数据的存储

NSS模块须自行管理认证数据。因此首先建立一个链表.

重要的头文件

#include "passwd_list.h"
#include "malloc.h"
#include <pwd.h>
#include <string.h>#include <cjson/cJSON.h>
#include <mhash.h>
#include <curl/curl.h>

创建链表

MJAW_INTERNAL pmjaw_passwd_list_t passwd_create()
{pmjaw_passwd_list_t node = (pmjaw_passwd_list_t)calloc(sizeof(mjaw_passwd_list_t), 1);return node;
}

创建认证账号

MJAW_INTERNAL passwd_t passwd_create2()
{passwd_t pwd = (passwd_t)calloc(sizeof(struct passwd), 1);pwd->pw_name = (char *)calloc(UINT8_MAX, 1);pwd->pw_gecos = (char *)calloc(UINT8_MAX, 1);pwd->pw_shell = (char *)calloc(UINT8_MAX, 1);pwd->pw_dir = (char *)calloc(UINT8_MAX, 1);pwd->pw_passwd = (char *)calloc(UINT8_MAX, 1);
}

释放链表

MJAW_INTERNAL void passwd_free(pmjaw_passwd_list_t head)
{pmjaw_passwd_list_t each = head->next;while (each) {pmjaw_passwd_list_t del = each;each = each->next;passwd_free2(del->data);free(del);}free(head);
}

释放认证账号

MJAW_INTERNAL void passwd_free2(passwd_t data)
{free(data->pw_name);free(data->pw_gecos);free(data->pw_shell);free(data->pw_dir);free(data->pw_passwd);free(data);
}

获取指定索引位置的用户对象

/*** @brief 获取指定索引位置的用户对象** @param head* @param nindex* @return MJAW_INTERNAL*/
MJAW_INTERNAL passwd_t passwd_at(pmjaw_passwd_list_t head, int nindex)
{if (nindex >= passwd_size(head)) {return NULL;}pmjaw_passwd_list_t each = head->next;int neach = 0;while (each && neach < nindex) {each = each->next;neach++;}return each->data;
}

根据用户名查找用户对象

/*** @brief 根据用户名查找用户对象** @param head* @param username* @return MJAW_INTERNAL*/
MJAW_INTERNAL passwd_t passwd_find_id_by_username(pmjaw_passwd_list_t head, const char *username)
{pmjaw_passwd_list_t find = head->next;while (find) {if (strcmp(find->data->pw_name, username) == 0) {return find->data;}}return NULL;
}

根据用户ID查找用户对象

/*** @brief 根据用户ID查找用户对象** @param head* @param uid* @return MJAW_INTERNAL*/
MJAW_INTERNAL passwd_t passwd_find_username_by_id(pmjaw_passwd_list_t head, uint32_t uid)
{pmjaw_passwd_list_t find = head->next;while (find) {if (find->data->pw_uid == uid) {return find->data;}}return NULL;
}

获取指定用户ID的索引

/*** @brief 获取指定用户ID的索引** @param head* @param uid* @return MJAW_INTERNAL*/
MJAW_INTERNAL int passwd_indexof(pmjaw_passwd_list_t head, uint32_t uid)
{pmjaw_passwd_list_t each = head->next;int nindex = 0;int bfind = 0;while (each) {if (each->data->pw_uid == uid) {bfind = 1;break;}nindex++;}if (bfind) {return nindex;}return -1;
}

在队列末尾增加一个用户对象


/*** @brief 在队列末尾一个用户对象** @param head* @param data* @return MJAW_INTERNAL*/
MJAW_INTERNAL void passwd_append(pmjaw_passwd_list_t head, passwd_t data)
{// mjaw_log0(__FILE__, __LINE__, __func__, "enter");pmjaw_passwd_list_t end = head;while (end->next) {end = end->next;}pmjaw_passwd_list_t node = passwd_create();node->data = passwd_create2();node->data->pw_uid = data->pw_uid;node->data->pw_gid = data->pw_gid;strncpy(node->data->pw_name, data->pw_name, UINT8_MAX);strncpy(node->data->pw_gecos, data->pw_gecos, UINT8_MAX);strncpy(node->data->pw_shell, data->pw_shell, UINT8_MAX);strncpy(node->data->pw_dir, data->pw_dir, UINT8_MAX);strncpy(node->data->pw_passwd, data->pw_passwd, UINT8_MAX);node->previous = end;end->next = node;// mjaw_log0(__FILE__, __LINE__, __func__, "leave");
}

移除指定索引位置的用户对象

/*** @brief 移除指定索引位置的用户对象** @param head* @param nindex* @return MJAW_INTERNAL*/
MJAW_INTERNAL void passwd_remove(pmjaw_passwd_list_t head, int nindex)
{if (nindex >= passwd_size(head)) {return;}pmjaw_passwd_list_t remove = head->next;int neach = 0;while (remove && neach < nindex) {remove = remove->next;neach++;}remove->next->previous = remove->previous;remove->previous->next = remove->next;passwd_free2(remove->data);free(remove);
}

获取队列大小

/*** @brief 获取队列大小,HEAD本身不参与计算** @param head* @return MJAW_INTERNAL*/
MJAW_INTERNAL uint32_t passwd_size(pmjaw_passwd_list_t head)
{uint32_t nsize = 0;while (head = head->next) {nsize++;}return nsize;
}

账号克隆

/*** @brief 用户信息复制** @param from* @param to* @return MJAW_INTERNAL*/
MJAW_INTERNAL void passwd_copy(const passwd_t from, passwd_t to)
{memset(to, 0, sizeof(struct passwd));to->pw_name = (char *)calloc(UINT8_MAX, 1);to->pw_gecos = (char *)calloc(UINT8_MAX, 1);to->pw_shell = (char *)calloc(UINT8_MAX, 1);to->pw_dir = (char *)calloc(UINT8_MAX, 1);to->pw_passwd = (char *)calloc(UINT8_MAX, 1);to->pw_name = from->pw_name;to->pw_passwd = from->pw_passwd;to->pw_uid = from->pw_uid;to->pw_gid = from->pw_gid;to->pw_gecos = from->pw_gecos;to->pw_dir = from->pw_dir;to->pw_shell = from->pw_shell;strncpy(to->pw_name, from->pw_name, UINT8_MAX);strncpy(to->pw_gecos, from->pw_gecos, UINT8_MAX);strncpy(to->pw_shell, from->pw_shell, UINT8_MAX);strncpy(to->pw_dir, from->pw_dir, UINT8_MAX);strncpy(to->pw_passwd, from->pw_passwd, UINT8_MAX);
}

Web互通

Web账号登录Linux桌面的通讯环节,假定存在一个RESTful服务http://127.0.0.1:1081提供json数据接口。

cURL发起请求

兼顾处理网络错误。

/*** @brief curl回调,收集数据** @param buffer* @param size* @param nmemb* @param user_p* @return size_t*/
static size_t mjaw_curl_login_data(void *buffer, size_t size, size_t nmemb, void *user_p)
{char *wrapper = (char *)user_p;// mjaw_logv(__FILE__, __LINE__, __func__, "buffer size: %d, size: %d, nmemb: %d", strlen((char *)buffer), size, nmemb);strncat(wrapper, buffer, nmemb);return nmemb;
}static bool network_has_error(cJSON **httpContent)
{//执行网络请求查询所有用户char buffer[UINT16_MAX] = {0};CURL *pcurl = curl_easy_init();curl_easy_setopt(pcurl, CURLOPT_URL, "http://127.0.0.1:1081/users");curl_easy_setopt(pcurl, CURLOPT_TIMEOUT, 3);curl_easy_setopt(pcurl, CURLOPT_WRITEFUNCTION, mjaw_curl_login_data);curl_easy_setopt(pcurl, CURLOPT_WRITEDATA, buffer);CURLcode icode = curl_easy_perform(pcurl);//如果网络请求失败if (icode != CURLE_OK) {mjaw_log1i(__FILE__, __LINE__, __func__, " nss_cw can not access website. curl error code: %d.", icode);return true;}//把HTTP返回转换成json对象cJSON *res = cJSON_Parse(buffer);//json对象必须是数组if (!cJSON_IsArray(res)) {// mjaw_logv(__FILE__, __LINE__, __func__, "parse http response error: %s", buffer);cJSON_Delete(res);return true;}*httpContent = res;return false;
}

cJSON库解析账号信息

/*** @brief 从指定的配置读取Web服务器上的用户列表并保存到列表** @param head* @return MJAW_INTERNAL*/
MJAW_INTERNAL void passwd_init(pmjaw_passwd_list_t head)
{cJSON *res = NULL;if (network_has_error(&res)) {return;}int iSize = cJSON_GetArraySize(res);for (int i = 0; i < iSize; i++) {cJSON *obj = cJSON_GetArrayItem(res, i);bool badmin = cJSON_IsTrue(cJSON_GetObjectItem(obj, "admin"));passwd_t data = passwd_create2();data->pw_uid = (uint32_t)cJSON_GetObjectItem(obj, "uid")->valueint;if (badmin) {data->pw_gid = 0;} else {data->pw_gid = data->pw_uid;}strncpy(data->pw_name, cJSON_GetObjectItem(obj, "username")->valuestring, UINT8_MAX);// strcpy(data->pw_shell, "/bin/bash");strcpy(data->pw_dir, "/home/http/");strncat(data->pw_dir, data->pw_name, UINT8_MAX);strncpy(data->pw_gecos, data->pw_name, UINT8_MAX);//这里是约定strcpy(data->pw_passwd, "!");passwd_append(head, data);}cJSON_Delete(res);
}

NSS核心:身份识别

NSS的概念可以推广开来,应用到人脸、指纹等生物特征识别。

重要的头文件

#ifdef __cplusplus
#include <iostream>
#include <string>
#else
#include <stdio.h>
#include <string.h>
#endif//c run time
#include <stdint.h>
#include <stdlib.h>
#include <malloc.h>
#include <time.h>
#include <stdarg.h>//linux
#include <unistd.h>
#include <nss.h>
#include <grp.h>
#include <pwd.h>
#include <dlfcn.h>
#include <sys/time.h>
#include <sys/types.h>
#include <syslog.h>//external reference
#include <cjson/cJSON.h>
#include <mhash.h>
#include <curl/curl.h>#include "nss_passwd.h"
#include "common.h"

下文以NSS接口名称为标题,举例说明NSS接口的实现过程。

getpwnam


//all over record
static int i_record_index = 0;
static pmjaw_passwd_list_t head_passwd = NULL;static enum nss_status local_getpwnam_r(const char *name, struct passwd *result, char *buffer, size_t buflen, int *errop)
{// dlopen("/usr/lib/%s-linux-gnu/libnss_files.so.2",)return NSS_STATUS_SUCCESS;
}/*** @brief 解析用户对象,给用户赋权** @param name* @param result* @param buffer* @param buflen* @param errop* @return MJAW_EXPORT*/
MJAW_EXPORT nss_status _nss_mjaw_getpwnam_r(const char *name, struct passwd *result, char *buffer, size_t buflen, int *errop)
{mjaw_log0(__FILE__, __LINE__, __func__, "enter");if (!head_passwd) {head_passwd = passwd_create();passwd_init(head_passwd);}//打开系统日志// openlog("nss_cw", LOG_CONS | LOG_PID | LOG_NDELAY, LOG_LOCAL1);//buffer重置为空memset(buffer, 0, buflen);//result结构体本身的内存由调用者传入if (!result) {mjaw_log0(__FILE__, __LINE__, __func__, " result can not be empty.");return NSS_STATUS_NOTFOUND;}//输出开始处理的日志mjaw_logv(__FILE__, __LINE__, __func__, "begin receive name: %s, uid: %d, gid: %d", name, result->pw_uid, result->pw_gid);//获取web对用户id和组id的定义passwd_t user = passwd_find_id_by_username(head_passwd, name);if (user == NULL) {passwd_free(head_passwd);return NSS_STATUS_NOTFOUND;}passwd_copy(user, result);//输出完成处理的日志mjaw_logv(__FILE__, __LINE__, __func__, "end : %s, uid: %d, gid: %d", name, result->pw_uid, result->pw_gid);//按约定返回0(非零被su判定用户不存在)*errop = 0;passwd_free(head_passwd);//关闭系统日志// closelog();return NSS_STATUS_SUCCESS;
}

setpwent

这个接口的含义是系统通知NSS模块清理内存,并准备一个新的账号列表。此时是Linux Deepin本地同步远端Web账号的机会。

/*** @brief init data list cache** @return MJAW_EXPORT*/
MJAW_EXPORT nss_status _nss_mjaw_setpwent()
{mjaw_log0(__FILE__, __LINE__, __func__, "enter");if (!head_passwd || !passwd_size(head_passwd)) {head_passwd = passwd_create();passwd_init(head_passwd);}mjaw_logv(__FILE__, __LINE__, __func__, "data size: %d", passwd_size(head_passwd));i_record_index = 0;return NSS_STATUS_SUCCESS;
}MJAW_EXPORT nss_status _nss_mjaw_init()
{mjaw_log0(__FILE__, __LINE__, __func__, "end");return NSS_STATUS_SUCCESS;
}

endpwent

这个接口的含义是系统通知NSS模块清理内存,结束了。

/*** @brief clean data list cache** @return MJAW_EXPORT*/
MJAW_EXPORT nss_status _nss_mjaw_endpwent()
{mjaw_log0(__FILE__, __LINE__, __func__, "enter");if (head_passwd && passwd_size(head_passwd) > 0)passwd_free(head_passwd);return NSS_STATUS_SUCCESS;
}### getpwent
这个接口一般紧接着setpwent调用,用于获取账号的详细信息。此接口返回NSS_NOT_FOUND后系统会立即调用endpwent接口。
/*** @brief iterator data list** @param __resultbuf* @return MJAW_EXPORT*/
MJAW_EXPORT nss_status _nss_mjaw_getpwent_r(struct passwd *resultbuf, char *buffer, size_t buflen, int *errop)
{if (!head_passwd)return NSS_STATUS_NOTFOUND;// memset(buffer, 0, buflen);passwd_t user = passwd_at(head_passwd, i_record_index);i_record_index++;if (user == NULL) {return NSS_STATUS_NOTFOUND;}mjaw_logv(__FILE__, __LINE__, __func__, "enter. uid: %d, cursor: %d", user->pw_uid, i_record_index);passwd_copy(user, resultbuf);*errop = 0;return NSS_STATUS_SUCCESS;
}

getpwuid

此接口在用户鉴权时调用,调用频率远高于getpwnam。用户进入桌面后每次鉴权都会调用这个接口,即使用户选择使用其它账号鉴权,此接口也先于getpwnam调用。

MJAW_EXPORT nss_status _nss_mjaw_getpwuid_r(uid_t uid, struct passwd *result, char *buffer, size_t buflen, int *errop)
{mjaw_log0(__FILE__, __LINE__, __func__, "enter");if (!head_passwd) {head_passwd = passwd_create();passwd_init(head_passwd);}//打开系统日志// openlog("nss_cw", LOG_CONS | LOG_PID | LOG_NDELAY, LOG_LOCAL1);//buffer重置为空memset(buffer, 0, buflen);//result结构体本身的内存由调用者传入if (!result) {mjaw_log0(__FILE__, __LINE__, __func__, " result can not be empty.");return NSS_STATUS_NOTFOUND;}//输出开始处理的日志mjaw_logv(__FILE__, __LINE__, __func__, "begin original uid: %d, result buffer uid: %d, gid: %d", uid, result->pw_uid, result->pw_gid);//获取web对用户id和组id的定义bool bok = passwd_find_username_by_id(head_passwd, uid);if (!bok) {passwd_free(head_passwd);return NSS_STATUS_NOTFOUND;}passwd_copy(result, result);//输出完成处理的日志mjaw_logv(__FILE__, __LINE__, __func__, "end original uid: %d, result buffer uid: %d, gid: %d", uid, result->pw_uid, result->pw_gid);//按约定返回0(非零被su判定用户不存在)*errop = 0;passwd_free(head_passwd);//关闭系统日志// closelog();return NSS_STATUS_SUCCESS;
}

调试

因NSS模块很接近系统内核底层,稍有不慎,开机或者重启,系统黑屏变成了这样:
在这里插入图片描述

原因是Linux系统内核的1号进程崩溃了。有关Linux 1号进程的资料可查阅:https://man7.org/linux/man-pages/man1/init.1.html。因为root这个字符串与uid0的这个系统用户身份没对应上,程序的上下文环境没有相应的权限,功能自然无法正常运转,虽然给人的感觉已经是root身份。但开发过程中总是有所难免,因此有些必要的调试设置在这里说明一下。

  1. 开始调试前多打开几个终端并登录开
    如果环境已被NSS破坏,可直接在这个终端上操作。但记住不要切换用户,这些操作都会失败。也不会已经损坏的时候才开终端,因为这个时候大部分图形程序都已经无法启动了。
  2. 修改grub引导选项
    这里建议修改/boot/grub/grub.cfg,在所有的linux命令行后面加上参数systemd.debug_shell=1。这样还可以直接切换到TTY9,免登录。

其它事项

如何伪造信息登录桌面

调用useradd增加一个同名本地用户,设置密码与否都不重要,PAM模块可以无视本地存储的密码,只要让系统的验证流程进入到自行开发的的PAM模块,在PAM中对帐号进行认证即可。PAM允许开发者自由操作,比如可以不验证登录凭据的正确性而直接进行进入系统。lightdm的自动登录就是这样实现的。

TTY登录成功,图形界面登录黑屏

图形界面涉及到NSS模块与FreeDesktop的AcountsService交互问题,此问题影响到LightDM并最终导致其崩溃。关于LightDM问题的处理,我将继续编写文章说明问题的根本原因以及解决办法。

参考文档

  1. https://docs.oracle.com/cd/E19683-01/806-4077/6jd6blbbb/index.html
  2. http://linux-pam.org/Linux-PAM-html/mwg-expected-of-module-auth.html
  3. https://www.openmjaw.org/doc/admin24/quickstart.html
  4. https://tools.ietf.org/html/rfc4511
  5. http://stefanfrings.de/qtwebapp/index-en.html
  6. https://docs.microsoft.com/en-us/previous-versions/windows/desktop/mjaw/lightweight-directory-access-protocol-mjaw-api
作者:岬淢箫声
日期:2020年12月15日
版本:1.0
博客:http://caowei.blog.csdn.net
创作不易,请大家多多支持关注、转发。转发请注明来源。

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

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

相关文章

uni-app--》基于小程序开发的电商平台项目实战(七)完结篇

&#x1f3cd;️作者简介&#xff1a;大家好&#xff0c;我是亦世凡华、渴望知识储备自己的一名在校大学生 &#x1f6f5;个人主页&#xff1a;亦世凡华、 &#x1f6fa;系列专栏&#xff1a;uni-app &#x1f6b2;座右铭&#xff1a;人生亦可燃烧&#xff0c;亦可腐败&#xf…

Python Pandas数据处理作图——波尔共振实验

import matplotlib.pyplot as plt import pandas as pd from pylab import mplmpl.rcParams["font.sans-serif"] ["SimHei"]data {频率比例w/wr: [1.036, 1.030, 1.025, 1.020, 1.012, 1.007, 1.002,0.997,0.993,0.990,0.986,0.977,0.969],振幅测量值θ&…

Python实战小项目分享

Python实战小项目包括网络爬虫、数据分析和可视化、文本处理、图像处理、聊天机器人、任务管理工具、游戏开发和网络服务器等。这些项目提供了实际应用场景和问题解决思路&#xff0c;可以选择感兴趣的项目进行实践&#xff0c;加深对Python编程的理解和掌握。在实践过程中&…

2023-mac brew安装python最新版本,遇见的问题和处理方式

#### 创建Python3.11.6符号链接我现在遇见这个问题了&#xff1a;python --version -bash: python: command not found 192:bin wangyang$ python3 --version Python 3.9.6 192:bin wangyang$ /usr/local/bin/python3 --version Python 3.11.6我要怎么做&#xff0c;我才可以直…

Qt耗时操作添加动画等待加载效果

Qt耗时操作添加动画等待加载效果_qt 等待动画-CSDN博客本例模拟耗时请求实现动画等待加载效果&#xff0c;采用QtConcurrent::run实现异步耗时操作&#xff0c;通过QFutureWatcher异步监测耗时操作结果的返回值做相应的动画演示。_qt 等待动画https://blog.csdn.net/qq_3666686…

高效访问数据的关键:解析MySQL主键自增长的运作机制!

文章目录 &#x1f34a; 主键自增长的概念&#x1f34a; 主键自增长的数据类型&#x1f34a; 主键自增长的步长&#x1f34a; 主键自增长的性能优化&#x1f389; 为什么需要主键自增长的性能优化?&#x1f389; 主键自增长的性能优化方案&#x1f4dd; 1. 调整主键自增长的步…

istio介绍(一)

1. 概念 1.1 虚拟服务 虚拟服务提供流量路由功能&#xff0c;它基于 Istio 和平台提供的基本的连通性和服务发现能力&#xff0c;让您配置如何在服务网格内将请求路由到服务 示例&#xff1a; apiVersion: networking.istio.io/v1alpha3 kind: VirtualService metadata:nam…

信钰证券:长江电力180亿市值,招商证券、摩根大通等浮盈超一成

本周A股限售股解禁规划环比有所上升。 Wind数据核算闪现&#xff0c;除去新上市公司&#xff0c;本周共有64家公司限售股解禁&#xff0c;解禁数量51.52亿股&#xff0c;以最新收盘价核算&#xff08;下同&#xff09;&#xff0c;解禁市值776.21亿元。 本周解禁市值跨越10亿…

RN:报错info Opening flipper://null/React?device=React%20Native

背景 在 ios 上使用 debug 模式的时候&#xff0c;报错&#xff1a;info Opening flipper://null/React?deviceReact%20Native&#xff0c;我找到了这个 issue 其实也可以看到现在打开 debug&#xff0c;是 open debug&#xff0c;也不是之前的 debug for chrome 了&#xf…

每日一题 2316. 统计无向图中无法互相到达点对数(中等,图连通分量)

题目很简单&#xff0c;只要求出每个连通分量有多少个节点即可首先通过建立一个字典来表示每个节点的邻接关系遍历每个节点&#xff0c;并通过邻接关系标记在当前连通分量内的所有的点&#xff0c;这样就可以知道一个连通分量内有多少个点在这里我陷入了一个误区&#xff0c;导…

计算机系统概论

1. 现代计算机由哪两部分组成 计算机系统&#xff1a;硬件、软件

分享一下抽奖活动小程序怎么做

在当今数字化时代&#xff0c;抽奖活动小程序已成为一种高效、创新的营销方式。它不仅能够吸引用户的注意力&#xff0c;提高品牌知名度&#xff0c;还能促进用户参与度&#xff0c;增强用户对品牌的忠诚度。本文将详细介绍如何制作一个成功的抽奖活动小程序&#xff0c;以及它…

Python爬虫如何设置代理服务器(搭建代理服务器教程)

在Python爬虫中使用代理服务器可以提高爬取数据的效率和稳定性。本文将为您提供搭建代理服务器的详细教程&#xff0c;并提供示例代码&#xff0c;帮助您在Python爬虫中设置代理服务器&#xff0c;实现更高效、稳定的数据抓取。 Python爬虫怎么设置代理服务器&#xff08;搭建代…

python打包和发布package

打包 偶尔有一些复用性很高&#xff0c;复杂度也很高的函数要反复调用&#xff0c;可以自行打包&#xff0c;安装 打包结构如下 以iso_timer为例 mkdir common vim __init__.py cd common vim __init__.py vim format.py# init.py from .common import *# /common/init.p…

C++11 正则表达式详解

目录 1 正则表达式语法1.1 字符和特殊字符1.2 限定符1.3 定位符1.4 选择和反向引用 2 C正则表达式标准库常用接口3 C正则表达式模板的使用3.1 匹配&#xff08;Match&#xff09;3.2 搜索&#xff08;Search&#xff09;3.3 分词&#xff08;Tokenize&#xff09;3.4 替换&…

Python —— hou.NetworkItem class

在一个network内&#xff0c;所有可见元素的基类&#xff1b; 此类没有方法&#xff0c;仅作为 hou.NetworkMovabelItem、hou.NodeConnection 基类存在&#xff0c;这两个子类在网络编辑器内均是可见的&#xff0c;是没有真正有意义的基类的&#xff1b;通过提供一个公共的基类…

【干货】Java函数式编程公式大全,收藏学习!

函数操作是现代编程领域中的核心概念之一&#xff0c;它以类似 Excel 表格的方式进行数据处理和计算。它的特点是使用公式和函数来描述数据之间的关系和计算逻辑&#xff1b;它允许我们以更高效、更有组织的方式管理和处理数据。 在函数式编程中&#xff0c;数据被组织成表格的…

LongAdder为什么在高并发下保持良好性能?LongAdder源码详细分析

文章目录 一、LongAdder概述1、为什么用LongAdder2、LongAdder使用3、LongAdder继承关系图4、总述&#xff1a;LongAdder为什么这么快5、基本原理 二、Striped64源码分析1、Striped64重要概念2、Striped64常用变量或方法3、静态代码块初始化UNSAFE4、casBase方法5、casCellsBus…

如何利用验证链技术减少大型语言模型中的幻觉

一、前言 随着大型语言模型在自然语言处理领域取得了惊人的进步。相信深度使用过大模型产品的朋友都会发现一个问题&#xff0c;就是有时候在上下文内容比较多&#xff0c;对话比较长&#xff0c;或者是模型本身知识不了解的情况下与GPT模型对话&#xff0c;模型反馈出来的结果…

阿里云服务器续费流程_一篇文章搞定

阿里云服务器如何续费&#xff1f;续费流程来了&#xff0c;在云服务器ECS管理控制台选择续费实例、续费时长和续费优惠券&#xff0c;然后提交订单&#xff0c;分分钟即可完成阿里云服务器续费流程&#xff0c;阿里云服务器网aliyunfuwuqi.com分享阿里云服务器详细续费方法&am…