MD5 计算 (下一代加密辅助类, Win32, C++)

CCNGHelper.h

#pragma once
#include <string>
#include <tchar.h>
#include <windows.h>
#include <bcrypt.h>

#ifdef _UNICODE
using _tstring = std::wstring;
#else
using _tstring = std::string;
#endif

// 下一代加密辅助类
// 客户端: Windows Vista 及以上系统可用
// 服务器: Windows Server 2008 及以上系统可用
class CCNGHelper
{
public:

    CCNGHelper();
    ~CCNGHelper();

    //
    // @brief: 初始化
    // @param: strAlgorithm     哈希算法名字符串, 可选类型如下:
    //                          常见可选 "MD5", "SHA1", "SHA256", "SHA384", "SHA512"
    // 
    //                          全部支持可选如下:
    //                          "RSA", "RSA_SIGN", "DH", "DSA", "RC2", "RC4", "AES", "DES", "DESX", "3DES", "3DES_112", 
    //                          "MD2", "MD4", "MD5", "SHA1", "SHA256", "SHA384", "SHA512",
    //                          "AES-GMAC", "AES-CMAC", "ECDSA_P256", "ECDSA_P384", "ECDSA_P521", "ECDH_P256", "ECDH_P384", "ECDH_P521", 
    //                          "RNG", "FIPS186DSARNG", "DUAECRNG"
    //                          WINDOWS 8 及以上: "SP800_108_CTR_HMAC", "SP800_56A_CONCAT", "PBKDF2", "CAPI_KDF", "TLS1_1_KDF", "TLS1_2_KDF"
    //                          Windows 10 及以上: "ECDSA", "ECDH", "XTS-AES"
    //                          Windows 10 1803: "HKDF"
    //                          "CHACHA20_POLY1305"
    // 
    // @ret: bool               操作是否成功
    bool Initialize(
        const _tstring& strAlgorithm = _T("MD5")
    );

    //
    // @brief: 反初始化
    // @ret: 无
    void Uninitialize();

    //
    // @brief: 重置
    // @ret: 无
    void Reset();

    //
    // @brief: 计算哈希值
    // @param: pData            数据缓冲
    // @param: ulSize           数据长度
    // @ret: bool               操作是否成功
    bool HashData(
        const void* pData, 
        unsigned long long ulSize
    );

    //
    // @brief: 获取累积的哈希值结果
    // @param: bUpper           是否大写
    // @ret: _tstring           哈希值字符串
    _tstring FinishHash(
        bool bUpper = true
    );

    //
    // @brief: 获取文件的哈希值结果
    // @param: strPath          文件路径
    // @param: bUpper           是否大写
    // @ret: _tstring           哈希值结果字符串
    _tstring GetFileHash(
        const _tstring& strPath, 
        bool bUpper = true
    );

    //
    // @brief: 获取数据的哈希值结果
    // @param: pData            数据指针
    // @param: ullSize          数据长度
    // @param: bUpper           是否大写
    // @ret: _tstring           哈希值结果字符串
    _tstring GetDataHash(
        const void* pData,
        unsigned long long ullSize,
        bool bUpper = true
    );

private:

    //
    // @brief: 计算哈希
    // @param: pData            数据指针
    // @param: ulSize           数据长度
    // @ret: 无
    bool _HashData(
        const void* pData, 
        unsigned long ulSize
    );

    //
    // @brief: 字节内容转字符串
    // @param: pData            数据指针
    // @param: nSize            数据长度
    // @param: bUpper           是否大写
    // @ret: _tstring           转换后的字符串
    _tstring _BytesToString(
        const void* pData, 
        size_t nSize, 
        bool bUpper = true
    );

    //
    // @brief: 字符串转大小
    // @param: str              字符串
    // @ret: _tstring           转换后的字符串
    _tstring _ToUpper(
        const _tstring& str
    );

    //
    // @brief: 多字符字符串转宽字符串
    // @param: CodePage         代码页
    // @param: str              字符串
    // @ret: std::wstring       转换后的宽字符串
    std::wstring _MultiStrToWStr(
        UINT CodePage, 
        const std::string& str
    );
    
    //
    // @brief: 字符串转宽字符串
    // @param: str              字符串
    // @ret: std::wstring       转换后的宽字符串
    std::wstring _TStrToWStr(
        const _tstring& str
    );

private:

    BCRYPT_ALG_HANDLE       m_hAlg = NULL;              // 算法提供程序句柄
    BCRYPT_HASH_HANDLE      m_hHash = NULL;             // 哈希对象句柄
    PBYTE                   m_pbHashObject = NULL;      // 哈希对象内存
    PBYTE                   m_pbHash = NULL;            // 哈希值缓冲
    DWORD                   m_cbHash = 0;               // 哈希值缓冲大小
    _tstring                m_strAlgorithm;             // 算法名
    std::string             m_dataBuf;                  // 数据缓冲(用于文件读取)
};

CCNGHelper.cpp

#include "CCNGHelper.h"
#include <Wincrypt.h>

#pragma comment(lib,"Bcrypt.lib")

#define NT_SUCCESS(Status)          (((NTSTATUS)(Status)) >= 0)
#define STATUS_UNSUCCESSFUL         ((NTSTATUS)0xC0000001L)
#define FILE_HASH_BLOCK_SIZE        (1024 * 1024 * 4)

CCNGHelper::CCNGHelper()
    :
    m_hAlg(NULL),
    m_hHash(NULL),
    m_pbHashObject(NULL),
    m_pbHash(NULL),
    m_cbHash(0)
{

}

CCNGHelper::~CCNGHelper()
{
    Uninitialize();
}

bool CCNGHelper::Initialize(
    const _tstring& strAlgorithm/* = _T("MD5")*/
)
{
    bool bSuccess = false;
    NTSTATUS status = STATUS_UNSUCCESSFUL;
    DWORD cbData = 0;
    DWORD cbHashObject = 0;

    _tstring strAlgorithmName = _ToUpper(strAlgorithm);
    if (m_strAlgorithm == strAlgorithmName)
    {
        return true;
    }

    Uninitialize();

    do
    {
        // 打开一个算法句柄
        // https://learn.microsoft.com/zh-cn/windows/win32/api/bcrypt/nf-bcrypt-bcryptopenalgorithmprovider
        status = BCryptOpenAlgorithmProvider(
            &m_hAlg, //指向接收 CNG 提供程序句柄 的 BCRYPT_ALG_HANDLE 变量的指针
            strAlgorithmName.c_str(), //指向以 null 结尾的 Unicode 字符串的指针,该字符串标识请求的加密算法
            NULL, //指向以 null 结尾的 Unicode 字符串的指针,该字符串标识要加载的特定提供程序
            BCRYPT_HASH_REUSABLE_FLAG//修改函数行为的标志
        );

        if (!NT_SUCCESS(status))
        {
            break;
        }

        // 检索提供程序的子对象的大小(以字节为单位)
        // https://learn.microsoft.com/zh-cn/windows/win32/api/bcrypt/nf-bcrypt-bcryptgetproperty
        status = BCryptGetProperty(
            m_hAlg, //表示要获取其属性值的 CNG 对象的句柄
            BCRYPT_OBJECT_LENGTH, //指向以 null 结尾的 Unicode 字符串的指针,该字符串包含要检索的属性的名称
            (PBYTE)&cbHashObject, //接收属性值的缓冲区的地址
            sizeof(DWORD), //pbOutput 缓冲区的大小(以字节为单位)
            &cbData, //指向 ULONG 变量的指针,该变量接收复制到 pbOutput 缓冲区的字节数
            0 //一组标志,用于修改此函数的行为。 未为此函数定义任何标志
        );

        if (!NT_SUCCESS(status))
        {
            break;
        }

        // 在堆上分配哈希对象
        m_pbHashObject = (PBYTE)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, cbHashObject);
        if (NULL == m_pbHashObject)
        {
            break;
        }

        // 检索哈希提供程序的哈希值的大小(以字节为单位)
        // https://learn.microsoft.com/zh-cn/windows/win32/api/bcrypt/nf-bcrypt-bcryptgetproperty
        status = BCryptGetProperty(
            m_hAlg, //表示要获取其属性值的 CNG 对象的句柄
            BCRYPT_HASH_LENGTH, //指向以 null 结尾的 Unicode 字符串的指针,该字符串包含要检索的属性的名称
            (PBYTE)&m_cbHash, //接收属性值的缓冲区的地址
            sizeof(DWORD), //pbOutput 缓冲区的大小(以字节为单位)
            &cbData, //指向 ULONG 变量的指针,该变量接收复制到 pbOutput 缓冲区的字节数
            0 //一组标志,用于修改此函数的行为。 未为此函数定义任何标志
        );

        if (!NT_SUCCESS(status))
        {
            break;
        }

        // 在堆上分配哈希缓冲区
        m_pbHash = (PBYTE)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, m_cbHash);
        if (NULL == m_pbHash)
        {
            break;
        }

        // 创建哈希对象
        // https://learn.microsoft.com/zh-cn/windows/win32/api/bcrypt/nf-bcrypt-bcryptcreatehash
        status = ::BCryptCreateHash(
            m_hAlg, //创建提供程序时指定的算法必须支持哈希接口
            &m_hHash, //哈希或 MAC 对象的句柄
            m_pbHashObject, //接收哈希或 MAC 对象的缓冲区的指针
            cbHashObject, //pbHashObject 缓冲区的大小(以字节为单位)
            NULL, //指向缓冲区的指针
            0, //pbSecret 缓冲区的大小(以字节为单位)
            BCRYPT_HASH_REUSABLE_FLAG //修改函数行为的标志
        );

        if (!NT_SUCCESS(status))
        {
            break;
        }

        bSuccess = true;

    } while (false);

    if (bSuccess)
    {
        m_strAlgorithm = strAlgorithmName;
    }
    else
    {
        Uninitialize();
    }

    return bSuccess;
}

void CCNGHelper::Uninitialize()
{
    if (m_hAlg)
    {
        ::BCryptCloseAlgorithmProvider(m_hAlg, 0);
        m_hAlg = NULL;
    }

    if (m_hHash)
    {
        ::BCryptDestroyHash(m_hHash);
        m_hHash = NULL;
    }

    if (m_pbHashObject)
    {
        ::HeapFree(GetProcessHeap(), 0, m_pbHashObject);
        m_pbHashObject = NULL;
    }

    if (m_pbHash)
    {
        ::HeapFree(GetProcessHeap(), 0, m_pbHash);
        m_pbHash = NULL;
    }

    m_strAlgorithm.clear();
}

void CCNGHelper::Reset()
{
    if (!m_strAlgorithm.empty())
    {
        // 检索从先前调用 BCryptHashData 累积的数据的哈希值
        (void)::BCryptFinishHash(
            m_hHash, //用于计算哈希或 MAC 的哈希或 MAC 对象的句柄
            m_pbHash, //指向接收哈希或 MAC 值的缓冲区的指针
            m_cbHash, //pbOutput 缓冲区的大小(以字节为单位)
            0 //一组标志,用于修改此函数的行为。 当前未定义任何标志,因此此参数应为零
        );
    }
}

bool CCNGHelper::HashData(
    const void* lpData, 
    unsigned long long ullSize
)
{
    const char* pDataBegin = (const char*)lpData;
    const unsigned long ulMaxBlockSize = UINT32_MAX;
    bool bSuccess = false;

    if (m_strAlgorithm.empty())
    {
        return false;
    }

    // 小于32位最大值则直接计算哈希值
    if (ullSize <= ulMaxBlockSize)
    {
        return _HashData(pDataBegin, (unsigned long)ullSize);
    }

    // 分段计算哈希值
    while(ullSize > 0)
    {
        unsigned long ulReadSize = (ullSize > ulMaxBlockSize) ? ulMaxBlockSize : (unsigned long)ullSize;
        if (!_HashData(pDataBegin, ulReadSize))
        {
            break;
        }

        pDataBegin += ulReadSize;
        ullSize -= ulReadSize;
    }

    return bSuccess;
}

_tstring CCNGHelper::FinishHash(
    bool bUpper/* = true*/
)
{
    _tstring strResult;

    if (m_strAlgorithm.empty())
    {
        return strResult;
    }

    // 检索从先前调用 BCryptHashData 累积的数据的哈希值
    NTSTATUS status = ::BCryptFinishHash(
        m_hHash, //用于计算哈希或 MAC 的哈希或 MAC 对象的句柄
        m_pbHash, //指向接收哈希或 MAC 值的缓冲区的指针
        m_cbHash, //pbOutput 缓冲区的大小(以字节为单位)
        0 //一组标志,用于修改此函数的行为。 当前未定义任何标志,因此此参数应为零
    );

    if (NT_SUCCESS(status))
    {
        strResult = _BytesToString(m_pbHash, m_cbHash, bUpper);
    }

    return strResult;
}

_tstring CCNGHelper::GetFileHash(
    const _tstring& strPath, 
    bool bUpper/* = true*/
)
{
    HANDLE hFile = INVALID_HANDLE_VALUE;
    DWORD dwBlockSize = FILE_HASH_BLOCK_SIZE;
    DWORD dwBytesRead = 0;

    if (m_strAlgorithm.empty())
    {
        return _tstring(_T(""));
    }

    do
    {
        // 打开文件
        // https://learn.microsoft.com/zh-cn/windows/win32/api/fileapi/nf-fileapi-createfilew
        hFile = CreateFile(strPath.c_str(),
            GENERIC_READ,
            FILE_SHARE_READ,
            NULL,
            OPEN_EXISTING,
            FILE_FLAG_SEQUENTIAL_SCAN,
            NULL);

        if (INVALID_HANDLE_VALUE == hFile)
        {
            break;
        }

        if (m_dataBuf.empty())
        {
            m_dataBuf.resize(dwBlockSize);
        }

        // 读取文件数据
        // https://learn.microsoft.com/zh-cn/windows/win32/api/fileapi/nf-fileapi-readfile
        while (::ReadFile(hFile, &m_dataBuf[0], dwBlockSize, &dwBytesRead, NULL))
        {
            if (0 == dwBytesRead)
            {
                break;
            }

            if (!_HashData(&m_dataBuf[0], dwBytesRead))
            {
                break;
            }
        }

    } while (false);

    if (INVALID_HANDLE_VALUE != hFile)
    {
        ::CloseHandle(hFile);
    }

    return FinishHash(bUpper);
}

_tstring CCNGHelper::GetDataHash(
    const void* pData, 
    unsigned long long ullSize,
    bool bUpper/* = true*/
)
{
    _tstring strResult;
    if (m_strAlgorithm.empty())
    {
        return strResult;
    }

    if (HashData(pData, ullSize))
    {
        strResult = FinishHash(bUpper);
    }

    return strResult;
}

bool CCNGHelper::_HashData(
    const void* lpData, 
    unsigned long ulSize
)
{
    // 数据缓冲区上执行单向哈希
    NTSTATUS status = ::BCryptHashData(
        m_hHash,
        (PUCHAR)lpData,
        ulSize,
        0);

    if (!NT_SUCCESS(status))
    {
        return false;
    }

    return true;
}

_tstring CCNGHelper::_BytesToString(
    const void* lpData, 
    size_t nSize,
    bool bUpper/* = true*/
)
{
    const TCHAR rgbDigitsUpper[] = _T("0123456789ABCDEF");
    const TCHAR rgbDigitsLower[] = _T("0123456789abcdef");
    _tstring strResult(nSize * 2, 0);
    LPCBYTE lpBytes = (LPCBYTE)lpData;

    if (bUpper)
    {
        for (DWORD i = 0; i < nSize; i++)
        {
            strResult[i * 2] = rgbDigitsUpper[lpBytes[i] >> 4];
            strResult[i * 2 + 1] = rgbDigitsUpper[lpBytes[i] & 0x0F];
        }
    }
    else
    {
        for (DWORD i = 0; i < nSize; i++)
        {
            strResult[i * 2] = rgbDigitsLower[lpBytes[i] >> 4];
            strResult[i * 2 + 1] = rgbDigitsLower[lpBytes[i] & 0x0F];
        }
    }

    return strResult;
}

_tstring CCNGHelper::_ToUpper(
    const _tstring& str
)
{
    _tstring strResult = str;
    for (auto& item : strResult)
    {
        if (item >= _T('a') && item <= _T('z'))
        {
            item -= 0x20;
        }
    }

    return strResult;
}

std::wstring CCNGHelper::_MultiStrToWStr(
    UINT CodePage, 
    const std::string& str
)
{
    //计算缓冲区所需的字符长度
    int cchWideChar = ::MultiByteToWideChar(CodePage, 0, str.c_str(), -1, NULL, NULL);
    std::wstring strResult(cchWideChar, 0);

    //成功则返回写入到指示的缓冲区的字符数
    size_t nConverted = ::MultiByteToWideChar(CodePage, 0, str.c_str(), (int)str.size(), &strResult[0], (int)strResult.size());

    //调整内容长度
    strResult.resize(nConverted);
    return strResult;
}

std::wstring CCNGHelper::_TStrToWStr(
    const _tstring& str
)
{
#ifdef _UNICODE
    return str;
#else
    return _MultiStrToWStr(CP_ACP, str);
#endif
}

main.cpp

#include <locale.h>
#include <tchar.h>
#include "Win32Utils/CTimeUtils.h"
#include "Win32Utils/CPathUtils.h"
#include "Win32Utils/CCNGHelper.h"

int _tmain(int argc, LPCTSTR argv[])
{
    UNREFERENCED_PARAMETER(argc);
    UNREFERENCED_PARAMETER(argv);

    ::setlocale(LC_ALL, "");

    unsigned long long ullStartTime;
    unsigned long long ullEndTime;

    CCNGHelper cngObj;
    cngObj.Initialize(_T("md5"));

    _tstring strCngHash;
    _tstring strCryptHash;

    //system("pause");

    int nCount = 1000;
    while (true)
    {
        ullStartTime = CTimeUtils::GetCurrentTickCount();
        for (int i = 0; i < nCount; i++)
        {
            strCngHash = cngObj.GetFileHash(CPathUtils::GetCurrentModulePath(), true);
        }
        ullEndTime = CTimeUtils::GetCurrentTickCount();

        _tprintf(_T("CCNGHelper MD5: %s Cost time: %lld ms\n"), strCngHash.c_str(), ullEndTime - ullStartTime);

        system("pause");
    }

    return 0;
}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Vue2(十二):Vuex环境搭建、Vuex工作原理、Vuex开发者工具、几个配置项、多组件共享数据、Vuex模块化

一、Vuex 1.概念 专门在Vue中实现集中式状态&#xff08;数据&#xff09;管理的一个Vue插件&#xff08;use引入&#xff09;&#xff0c;对vue应用中多个组件的共享状态进行集中式的管理&#xff08;读&#xff0f;写&#xff09;&#xff0c;也是一种组件间通信的方式&…

阿里云优惠券领取方法大公开,省钱不再是难事

阿里云作为国内领先的云计算服务提供商&#xff0c;为广大用户提供了丰富的云产品和解决方案。为了吸引用户上云&#xff0c;阿里云经常推出各种优惠活动&#xff0c;其中最受用户欢迎的就是阿里云优惠券。那么&#xff0c;阿里云优惠券究竟是什么呢&#xff1f;我们又该如何领…

代码随想录第25天|216.组合总和III 17.电话号码的字母组合

216.组合总和III 216. 组合总和 III - 力扣&#xff08;LeetCode&#xff09; 代码随想录 (programmercarl.com) 和组合问题有啥区别&#xff1f;回溯算法如何剪枝&#xff1f;| LeetCode&#xff1a;216.组合总和III_哔哩哔哩_bilibili 找出所有相加之和为 n 的 k 个数的组…

基于自动编码器的预训练模型方法模型预训练方法RetroMAE和RetroMAE-2

文章目录 RetroMAERetroMAE详情编码解码增强解码 RetroMAE-2RetroMAE-2详情编码[CLS]解码OT解码和训练目标向量表征 总结参考资料 RetroMAE RetroMAE 出自论文《RetroMAE: Pre-Training Retrieval-oriented Language Models Via Masked Auto-Encoder》&#xff0c;是一种针对于…

「MySQL」索引事务

&#x1f387;个人主页&#xff1a;Ice_Sugar_7 &#x1f387;所属专栏&#xff1a;数据库 &#x1f387;欢迎点赞收藏加关注哦&#xff01; 索引&事务 &#x1f349;索引&#x1f34c;特点&#x1f34c;通过 SQL 操作索引&#x1f34c;底层数据结构 &#x1f349;事务&…

网络编程的学习1

网络编程 在网络通信协议下&#xff0c;不同计算机上运行的程序&#xff0c;进行数据传输。 三要素 ip&#xff1a;设备在网络中的地址&#xff0c;是唯一的标识。 ipv4:采取32位地址长度&#xff0c;分成4组。 ipv6&#xff1a;采用128位地址长度&#xff0c;分成8组。 …

安卓SharedPreferences使用

目录 一、简介二、使用2.1 getSharedPreferences2.2 增加数据2.3 读取数据2.4 删除数据2.5 修改数据2.6 清除数据2.7 提交数据 一、简介 SharedPreferences是Android平台上一个轻量级的存储类&#xff0c;主要是保存一些常用的配置比如窗口状态&#xff0c;一般在Activity、重…

12.Python文件读写

文件是数据的载体&#xff0c;程序可以从文件中读取数据&#xff0c;也可以将数据写 入文件中&#xff0c;本章重点介绍如何在Python中进行文件读写。 1 打开文件 我们在使用文件之前要先将文件打开&#xff0c;这通过open&#xff08;&#xff09;函数实现。 open&#xff0…

JJJ:linux系统中第一个进程

以linux4.19内核linux系统中第一个进程。 执行shell指令 ps -ef 结果如下&#xff1a; xxxxxx-virtual-machine:~$ ps -ef UID PID PPID C STIME TTY TIME CMD root 1 0 0 20:55 ? 00:00:02 /sbin/init splash root …

【Web应用技术基础】CSS(4)——背景样式

第1题&#xff1a;背景颜色 .html <!DOCTYPE html> <html><head><meta charset"utf-8"><title>Hello World</title><link rel"stylesheet" href"step1/CSS/style.css"> </head><body>&…

词令蚂蚁庄园今日答案正确答案怎么获取查看蚂蚁庄园今天问题的正确答案?

词令蚂蚁庄园今日答案正确答案怎么获取查看蚂蚁庄园今天问题的正确答案&#xff1f; 1、打开「词令」关键词口令直达工具&#xff1b; 2、输入词令关键词直达口令「今日答案999」&#xff1b; 3、搜索直达词令蚂蚁庄园今日问题的正确答案&#xff1b; *注&#xff1a;词令蚂蚁…

【PythonGIS】Python实现批量导出面矢量要素(单个多面矢量->多个单面矢量)

可怜的我周六还在工作&#xff0c;已经很久没更新过博客了&#xff0c;今天正好有空就和大家分享一下。今天给大家带来的是使用Python将包含多个面要素/线要素的矢量批量导出单个要素的矢量&#xff0c;即一个要素一个矢量文件。之前写过多个矢量文件合并成一个矢量文件的博文&…

西南交大swjtu算法实验3.3|穷举法

1.实验目的 通过具体例子学习排列这种典型的穷举算法的求解过程以及程序框架&#xff0c;分析其算法的求解过程&#xff0c;以及如何设计穷举法解决实际问题。通过本实验&#xff0c;理解穷举法的特点以及实际应用中的局限性。 2.实验任务 有n (n>1&#xff09;个任务需要…

Visual Studio 2022 中VLD库如何安装

GitHub链接 Release v2.5.1 KindDragon/vld 点击可执行程序进行下载 点击可执行程序进行安装 双击打开 一直点击next即可完成安装&#xff08;不用在意安装路径&#xff0c;总共不到2MB&#xff09; 如果GitHub无法打开&#xff0c;可以私信我发你安装包直接安装

fpga_awb

色温: sesor原始图像中的白色如果不经AWB处理&#xff0c;在高色温(如阴天)下偏蓝&#xff0c;在低色温下偏黄。 引入白平衡算法 而AWB的核心就是调整图像色温&#xff0c;使得摄像头采集的图像更加真实&#xff0c;达到人眼观察的效果。 白平衡一般通过调节传感器输出图像RGB…

【aws】架构图工具推荐

碎碎念 以前以为日本冰箱论是个梗&#xff0c;结果居然是真的。用光盘传真其实还能理解&#xff08;毕竟我也喜欢电子古董2333&#xff09;&#xff0c;但是画架构图居然用的是excel&#xff0b;截图&#xff01;啊苍天呐&#xff0c;然后看到隔壁工位用excel画web原型又感觉释…

svg实现环形进度条

实现效果图&#xff1a; svg相关知识 这里只介绍本次用到的元素&#xff0c;更多详情&#xff1a;SVG&#xff1a;可缩放矢量图形 defs&#xff1a;定义需要重复利用的图形元素linearGradient&#xff1a;定义线性渐变&#xff0c;用来图形元素的填充或描边使用stop&#x…

hcip综合实验2

目录 实验拓扑&#xff1a; 实验要求&#xff1a; 实验思路&#xff1a; 实验步骤&#xff1a; 1.配置设备接口IP 2.通过配置缺省路由让公网互通 3.配置ppp 1.R1和R5间的ppp的PAP认证&#xff1b; 2.R2与R5之间的ppp的CHAP认证; 3. R3与R5之间的HDLC封装; 4.构建R1、…

动态规划之子序列(三)

583. 两个字符串的删除操作 给定两个单词 word1 和 word2&#xff0c;找到使得 word1 和 word2 相同所需的最小步数&#xff0c;每步可以删除任意一个字符串中的一个字符。 示例&#xff1a; 输入: “sea”, “eat” 输出: 2 解释: 第一步将"sea"变为"ea"…

c实现猜数游戏(猜不对可是要自动帮你电脑关机)

接下来的日子会顺顺利利&#xff0c;万事胜意&#xff0c;生活明朗-----------林辞忧 前言 猜数字游戏作为一个基础的C程序小项目&#xff0c;实现简单&#xff0c;可以帮助我们巩固很多知识&#xff0c;作为扩展接下来我们实现一个自定猜数次数&#xff0c;用完次数电脑自动…