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

news2024/10/7 20:31:27

问题由来

客户需求计划列入支持第三方帐号系统,包括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.coloradmin.cn/o/1117199.html

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

相关文章

基于天鹰优化的BP神经网络(分类应用) - 附代码

基于天鹰优化的BP神经网络&#xff08;分类应用&#xff09; - 附代码 文章目录 基于天鹰优化的BP神经网络&#xff08;分类应用&#xff09; - 附代码1.鸢尾花iris数据介绍2.数据集整理3.天鹰优化BP神经网络3.1 BP神经网络参数设置3.2 天鹰算法应用 4.测试结果&#xff1a;5.M…

2023年【广东省安全员A证第四批(主要负责人)】考试试卷及广东省安全员A证第四批(主要负责人)模拟考试

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 广东省安全员A证第四批&#xff08;主要负责人&#xff09;考试试卷根据新广东省安全员A证第四批&#xff08;主要负责人&#xff09;考试大纲要求&#xff0c;安全生产模拟考试一点通将广东省安全员A证第四批&#x…

小程序之后台数据动态交互及WXS的使用 (5)

⭐⭐ 小程序专栏&#xff1a;小程序开发专栏 ⭐⭐ 个人主页&#xff1a;个人主页 目录 一.前言 二.后台数据交互 2.1 准备工作 2.1 前台首页数据连接&#xff1a; 三.WXS的使用 今天就分享到这啦&#xff01;&#xff01;&#xff01; 一.前言 本文章续前面的文章的前端界面…

【JUC系列-15】深入理解CompletableFuture的基本使用

JUC系列整体栏目 内容链接地址【一】深入理解JMM内存模型的底层实现原理https://zhenghuisheng.blog.csdn.net/article/details/132400429【二】深入理解CAS底层原理和基本使用https://blog.csdn.net/zhenghuishengq/article/details/132478786【三】熟练掌握Atomic原子系列基本…

2023 10月8日 至 10 月16日学习总结

1.做的题目 [RootersCTF2019]I_&#xff1c;3_Flask_双层小牛堡的博客-CSDN博客 [NCTF2019]SQLi regexp 盲注-CSDN博客 [网鼎杯 2018]Comment git泄露 / 恢复 二次注入 .DS_Store bash_history文件查看-CSDN博客 PHP LFI 利用临时文件Getshell_双层小牛堡的博客-CSDN博客 …

《动手学深度学习 Pytorch版》 9.7 序列到序列学习(seq2seq)

循环神经网络编码器使用长度可变的序列作为输入&#xff0c;将其编码到循环神经网络编码器固定形状的隐状态中。 为了连续生成输出序列的词元&#xff0c;独立的循环神经网络解码器是基于输入序列的编码信息和输出序列已经看见的或者生成的词元来预测下一个词元。 要点&#x…

关于使用 vxe-table 时设置了 show-overflow tooltip 不展示的问题(Dialog 组件和 table 同时使用)

众所周知&#xff0c;vxe-table 是可以支撑万级数据渲染的表格组件&#xff0c;本质上还是用了虚拟滚动的实现。之前一直知道vxe-table, 但是基本没有机会用的上这个组件&#xff0c;最近在开发埋点数据的统计&#xff0c;后端一次性返回了上千条数据&#xff0c;elementui 的 …

【Shell】环境变量 自定义变量 特殊变量

Shell变量&#xff1a;环境变量 目标 1、理解什么是系统环境变量&#xff1f; 2、掌握常用的系统环境变量都有哪些&#xff1f; Shell变量的介绍 变量用于存储管理临时的数据, 这些数据都是在运行内存中的. 变量类型 系统环境变量 自定义变量 特殊符号变量 系统环境变…

golang查看CPU使用率与内存及源码中的//go:指令

golang查看CPU使用率与内存 1 psutil 1.1 概念与应用场景 psutil是业内一个用于监控OS资源的软件包&#xff0c;目前已经多种语言&#xff0c;包括但不限于Python、Go。 gopsutil是 Python 工具库psutil 的 Golang 移植版&#xff0c;可以帮助我们方便地获取各种系统和硬件信…

基于Java的实验室设备管理系统设计与实现(源码+lw+部署文档+讲解等)

文章目录 前言具体实现截图论文参考详细视频演示为什么选择我自己的网站自己的小程序&#xff08;小蔡coding&#xff09; 代码参考数据库参考源码获取 前言 &#x1f497;博主介绍&#xff1a;✌全网粉丝10W,CSDN特邀作者、博客专家、CSDN新星计划导师、全栈领域优质创作者&am…

如何在电脑上设置新的蓝牙耳机

本文介绍如何将蓝牙耳机连接到Windows或Mac电脑。 如何在Windows上设置新的蓝牙耳机 蓝牙耳机的设置过程因平台而异&#xff0c;但以下是Windows 11的步骤&#xff1a; 1、选择“开始”&#xff0c;然后在搜索框中输入蓝牙&#xff0c;以显示蓝牙和其他设备。 2、选择添加设…

【LeetCode:2316. 统计无向图中无法互相到达点对数 | BFS + 乘法原理】

&#x1f680; 算法题 &#x1f680; &#x1f332; 算法刷题专栏 | 面试必备算法 | 面试高频算法 &#x1f340; &#x1f332; 越难的东西,越要努力坚持&#xff0c;因为它具有很高的价值&#xff0c;算法就是这样✨ &#x1f332; 作者简介&#xff1a;硕风和炜&#xff0c;…

施密特正交化

相信大家在平时的期末考试中一定少不了对某某向量组执行标准正交化类型的题目。今天我们从这个题目入手&#xff0c;说明这个如何执行施密特正交化&#xff0c;以及为什么要进行正交化。 一、例子 例子&#xff1a;设 a 1 [ 1 2 − 1 ] a_1\begin{bmatrix}1\\2\\-1\end{bmat…

阿里妈妈Union Lab全量公测,你会用吗?

Union Lab是一种智能化的选品推荐、推广内容创作工具&#xff0c;它内置了大语言模型&#xff08;LLMs&#xff09;&#xff0c;使得选品、推广更加智能和简单。通过联盟底层货品服务&#xff0c;Union Lab可以实现智能化货品发现、分析、选品、投放、创意生产&#xff0c;基于…

嵌入式mqtt总线架构方案mosquitto+paho

一 mqtt通信模型 MQTT 协议提供一对多的消息发布&#xff0c;可以降低应用程序的耦合性&#xff0c;用户只需要编写极少量的应用代码就能完成一对多的消息发布与订阅&#xff0c;该协议是基于<客户端-服务器>模型&#xff0c;在协议中主要有三种身份&#xff1a;发布者&…

力扣第37题 解数独 c++ 难~ 回溯

题目 37. 解数独 困难 相关标签 数组 哈希表 回溯 矩阵 编写一个程序&#xff0c;通过填充空格来解决数独问题。 数独的解法需 遵循如下规则&#xff1a; 数字 1-9 在每一行只能出现一次。数字 1-9 在每一列只能出现一次。数字 1-9 在每一个以粗实线分隔的 3x3 宫…

基于Java的文物管理系统设计与实现(源码+lw+部署文档+讲解等)

文章目录 前言具体实现截图论文参考详细视频演示为什么选择我自己的网站自己的小程序&#xff08;小蔡coding&#xff09; 代码参考数据库参考源码获取 前言 &#x1f497;博主介绍&#xff1a;✌全网粉丝10W,CSDN特邀作者、博客专家、CSDN新星计划导师、全栈领域优质创作者&am…

GRASP 、SOLID 与 GoF 设计模式

一、GRASP GRASP&#xff1a;通用职责分配软件设计模式(General Responsibility Assignment Software Patterns)&#xff0c;其主要思想是基于单一职责设计软件对象。 思考软件对象设计以及大型构件的流行方式是&#xff0c;考虑其职责、角色和协作。这是被称为职责驱动设计&a…

C++:无法查找或打开 PDB 文件?? 如何解决呢?以及产生原因是什么?

C:无法查找或打开 PDB 文件?? 如何解决呢&#xff1f;以及产生原因是什么&#xff1f; 前言解决办法原因 前言 最近博主在写C时&#xff0c;明明代码都正确&#xff0c;但编译失败。查看原因后发现显示&#xff1a;无法查找或打开 PDB 文件。&#xff08;先介绍解决办法&…

【高等数学】微分中值定理

文章目录 1、极值2、费马引理3、罗尔定理4、拉格朗日中值定理4.1用拉格朗日定理证明基本结论 5、柯西中值定理6、微分中值定理的意义7、三大中值定理的意义 1、极值 若 ∃ δ > 0 ∃δ>0 ∃δ>0&#xff0c;使得 ∀ x ∈ U ( x 0 , δ ) ∀x\in U(x_0,δ) ∀x∈U(x0…