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

服务器加密算法

HTTP服务器加密算法详解

1. 概述与历史演化

1.1 HTTP安全的历史演化

在互联网发展初期,HTTP协议是明文传输的,这带来了严重的安全隐患:

  • 1990年代初期:HTTP/1.0 完全明文传输,任何人都可以截获和查看数据
  • 1994年:Netscape公司开发了SSL (Secure Sockets Layer,安全套接字层)
  • 1999年:SSL演化为TLS (Transport Layer Security,传输层安全协议)
  • 2000年代:HTTPS成为标准,结合HTTP和TLS/SSL
  • 现在:TLS 1.3成为主流,提供更强的安全性和更好的性能

1.2 加密算法在HTTP服务器中的作用

HTTP服务器中的加密算法主要解决以下问题:

  • 机密性:确保数据不被未授权者读取
  • 完整性:确保数据在传输过程中未被篡改
  • 身份认证:确认通信双方的身份
  • 不可否认性:防止发送方否认已发送的信息

2. HTTP服务器请求-响应过程中的加密流程

2.1 完整的HTTPS握手过程

客户端                                    服务器|                                        ||-------- 1. Client Hello -------------->||                                        ||<------- 2. Server Hello ---------------||<------- 3. Certificate ----------------||<------- 4. Server Key Exchange -------||<------- 5. Server Hello Done ---------||                                        ||-------- 6. Client Key Exchange ------>||-------- 7. Change Cipher Spec ------->||-------- 8. Finished ------------------>||                                        ||<------- 9. Change Cipher Spec --------||<------- 10. Finished ------------------||                                        ||====== 加密的应用数据传输 ===============|

2.2 各阶段使用的加密算法

阶段1:握手阶段
  • RSA/ECDSA:用于数字签名和密钥交换
  • SHA-256/SHA-384:用于证书签名和消息摘要
  • ECDHE/DHE:用于密钥协商,提供前向安全性
阶段2:数据传输阶段
  • AES-GCM/ChaCha20-Poly1305:对称加密算法,用于数据加密
  • HMAC:消息认证码,确保数据完整性

3. 核心加密算法详解

3.1 SHA-256 (Secure Hash Algorithm 256-bit)

全称:Secure Hash Algorithm 256-bit(安全哈希算法256位)

作用:将任意长度的输入数据转换为固定长度(256位)的哈希值

特点

  • 单向性:无法从哈希值推导出原始数据
  • 确定性:相同输入总是产生相同输出
  • 雪崩效应:输入的微小变化导致输出的巨大变化
  • 抗碰撞性:很难找到两个不同的输入产生相同的哈希值

在HTTP服务器中的应用

  • 密码存储:存储用户密码的哈希值而非明文
  • 数据完整性校验:验证传输数据是否被篡改
  • 数字签名:作为签名算法的一部分

3.2 HMAC (Hash-based Message Authentication Code)

全称:Hash-based Message Authentication Code(基于哈希的消息认证码)

作用:结合密钥和哈希函数,生成消息认证码

工作原理

HMAC(K, M) = H((K ⊕ opad) || H((K ⊕ ipad) || M))
  • K:密钥
  • M:消息
  • H:哈希函数(如SHA-256)
  • opad:外部填充(0x5c重复)
  • ipad:内部填充(0x36重复)
  • ||:连接操作
  • ⊕:异或操作

在HTTP服务器中的应用

  • API认证:验证请求的合法性
  • JWT签名:JSON Web Token的签名验证
  • Cookie完整性:防止Cookie被篡改

3.3 Base64编码

全称:Base64 Encoding(64进制编码)

作用:将二进制数据转换为ASCII字符串

编码原理

  • 将3个字节(24位)分成4组,每组6位
  • 每组6位对应一个Base64字符(0-63)
  • 使用字符集:A-Z, a-z, 0-9, +, /
  • 填充字符:=

在HTTP服务器中的应用

  • HTTP Basic认证:编码用户名和密码
  • 数据传输:在文本协议中传输二进制数据
  • 证书编码:PEM格式证书的编码方式

4. OpenSSL库详解

4.1 OpenSSL简介

全称:Open Secure Sockets Layer(开放安全套接字层)

OpenSSL是一个强大的加密库,提供了:

  • 对称加密算法(AES、DES等)
  • 非对称加密算法(RSA、ECC等)
  • 哈希算法(SHA系列、MD5等)
  • 消息认证码(HMAC)
  • 数字签名和证书处理
  • 随机数生成

4.2 OpenSSL核心结构体

EVP_MD_CTX(消息摘要上下文)
typedef struct evp_md_ctx_st {const EVP_MD *digest;     // 指向哈希算法的指针void *md_data;            // 算法特定的数据EVP_PKEY_CTX *pctx;      // 公钥上下文(用于签名)int (*update)(EVP_MD_CTX *ctx, const void *data, size_t count);
} EVP_MD_CTX;
EVP_CIPHER_CTX(加密上下文)
typedef struct evp_cipher_ctx_st {const EVP_CIPHER *cipher; // 指向加密算法的指针int encrypt;              // 加密(1)或解密(0)标志int buf_len;              // 缓冲区长度unsigned char oiv[EVP_MAX_IV_LENGTH];  // 原始IVunsigned char iv[EVP_MAX_IV_LENGTH];   // 当前IVunsigned char buf[EVP_CIPHER_CTX_BUF_LEN]; // 缓冲区int num;                  // 已处理的字节数void *app_data;           // 应用程序数据int key_len;              // 密钥长度unsigned long flags;      // 标志位
} EVP_CIPHER_CTX;

5. 实际应用场景

5.1 用户认证流程

  1. 用户注册

    • 接收用户密码
    • 生成随机盐值
    • 使用SHA-256计算密码哈希
    • 存储用户名、盐值和哈希值
  2. 用户登录

    • 接收用户名和密码
    • 从数据库获取对应的盐值和哈希值
    • 使用相同的盐值计算输入密码的哈希
    • 比较计算结果与存储的哈希值

5.2 API请求签名验证

  1. 客户端签名

    • 构造待签名字符串(通常包含时间戳、请求参数等)
    • 使用HMAC-SHA256和密钥生成签名
    • 将签名添加到请求头或参数中
  2. 服务器验证

    • 提取请求中的签名
    • 使用相同方法重新计算签名
    • 比较计算结果与请求中的签名

5.3 数据传输加密

  1. 对称加密

    • 使用AES算法加密大量数据
    • 密钥通过非对称加密安全传输
    • 使用CBC或GCM模式提供额外安全性
  2. 完整性保护

    • 计算数据的哈希值
    • 使用HMAC保护哈希值
    • 接收方验证数据完整性

6. 安全最佳实践

6.1 密钥管理

  • 使用强随机数生成器生成密钥
  • 定期轮换密钥
  • 安全存储密钥(使用HSM或密钥管理服务)
  • 实施密钥分离原则

6.2 算法选择

  • 优先使用经过验证的标准算法
  • 避免使用已知有漏洞的算法(如MD5、SHA-1)
  • 选择合适的密钥长度
  • 考虑性能和安全性的平衡

6.3 实现注意事项

  • 防止时序攻击:使用常数时间比较
  • 正确处理错误和异常
  • 清理敏感数据的内存
  • 使用安全的随机数生成器

7. 性能考虑

7.1 算法性能对比

  • 哈希算法:SHA-256 > SHA-512 > SHA-1(安全性考虑)
  • 对称加密:AES-GCM > AES-CBC > 3DES
  • 非对称加密:ECC > RSA(相同安全级别下)

7.2 优化策略

  • 使用硬件加速(AES-NI指令集)
  • 批量处理数据
  • 缓存计算结果
  • 选择合适的工作模式

这个详细的介绍为您提供了HTTP服务器中加密算法的全面理解,接下来我将为您提供具体的代码实现。

#include "crypto.h"
#include <openssl/evp.h>
#include <openssl/hmac.h>
#include <openssl/sha.h>
#include <openssl/bio.h>
#include <openssl/buffer.h>
#include <openssl/rand.h>
#include <cstring>
#include <memory>
#include <stdexcept>// CryptoUtils类的实现
// 这个类封装了常用的加密算法功能,包括SHA-256哈希、HMAC签名和Base64编码/*** 计算SHA-256哈希值* @param data 输入数据的指针* @param len 输入数据的长度(字节)* @return 返回32字节的SHA-256哈希值的十六进制字符串表示* * 函数流程:* 1. 创建EVP_MD_CTX上下文对象* 2. 初始化SHA-256算法* 3. 更新数据到哈希计算器* 4. 完成计算并获取结果* 5. 将二进制结果转换为十六进制字符串*/
std::string CryptoUtils::sha256(const unsigned char* data, size_t len) {// EVP_MD_CTX: OpenSSL的消息摘要上下文结构体// 包含算法信息、状态数据等EVP_MD_CTX* ctx = EVP_MD_CTX_new();if (!ctx) {throw std::runtime_error("Failed to create EVP_MD_CTX");}// 使用RAII管理资源,确保异常安全std::unique_ptr<EVP_MD_CTX, decltype(&EVP_MD_CTX_free)> ctx_guard(ctx, EVP_MD_CTX_free);// EVP_DigestInit_ex: 初始化摘要计算// 参数1: 上下文对象// 参数2: 摘要算法(EVP_sha256()返回SHA-256算法的EVP_MD结构)// 参数3: 引擎(NULL表示使用默认引擎)if (EVP_DigestInit_ex(ctx, EVP_sha256(), nullptr) != 1) {throw std::runtime_error("Failed to initialize SHA-256");}// EVP_DigestUpdate: 向摘要计算器输入数据// 参数1: 上下文对象// 参数2: 数据指针// 参数3: 数据长度// 可以多次调用来处理大量数据if (EVP_DigestUpdate(ctx, data, len) != 1) {throw std::runtime_error("Failed to update SHA-256");}// 存储最终的哈希值(SHA-256产生32字节输出)unsigned char hash[EVP_MAX_MD_SIZE];unsigned int hash_len;// EVP_DigestFinal_ex: 完成摘要计算并获取结果// 参数1: 上下文对象// 参数2: 输出缓冲区// 参数3: 输出长度的指针if (EVP_DigestFinal_ex(ctx, hash, &hash_len) != 1) {throw std::runtime_error("Failed to finalize SHA-256");}// 将二进制哈希值转换为十六进制字符串return bytesToHex(hash, hash_len);
}/*** 计算字符串的SHA-256哈希值(重载版本)* @param data 输入字符串* @return SHA-256哈希值的十六进制字符串*/
std::string CryptoUtils::sha256(const std::string& data) {return sha256(reinterpret_cast<const unsigned char*>(data.c_str()), data.length());
}/*** 计算HMAC-SHA256签名* @param key 密钥数据指针* @param key_len 密钥长度* @param data 待签名数据指针* @param data_len 待签名数据长度* @return HMAC-SHA256签名的十六进制字符串* * HMAC工作原理:* 1. 如果密钥长度大于块大小,先对密钥进行哈希* 2. 如果密钥长度小于块大小,用零填充* 3. 计算内部哈希:H((key ⊕ ipad) || message)* 4. 计算外部哈希:H((key ⊕ opad) || inner_hash)*/
std::string CryptoUtils::hmacSha256(const unsigned char* key, size_t key_len,const unsigned char* data, size_t data_len) {unsigned char result[EVP_MAX_MD_SIZE];unsigned int result_len;// HMAC: 基于哈希的消息认证码函数// 参数1: 哈希算法(EVP_sha256())// 参数2: 密钥指针// 参数3: 密钥长度// 参数4: 数据指针// 参数5: 数据长度// 参数6: 输出缓冲区// 参数7: 输出长度指针unsigned char* hmac_result = HMAC(EVP_sha256(), key, key_len, data, data_len, result, &result_len);if (!hmac_result) {throw std::runtime_error("Failed to compute HMAC-SHA256");}return bytesToHex(result, result_len);
}/*** 计算字符串的HMAC-SHA256签名(重载版本)* @param key 密钥字符串* @param data 待签名数据字符串* @return HMAC-SHA256签名的十六进制字符串*/
std::string CryptoUtils::hmacSha256(const std::string& key, const std::string& data) {return hmacSha256(reinterpret_cast<const unsigned char*>(key.c_str()), key.length(),reinterpret_cast<const unsigned char*>(data.c_str()), data.length());
}/*** Base64编码* @param data 输入数据指针* @param len 输入数据长度* @return Base64编码后的字符串* * Base64编码原理:* 1. 将输入数据按3字节为一组进行分组* 2. 每组3字节(24位)分成4个6位的组* 3. 每个6位组对应Base64字符表中的一个字符* 4. 不足3字节的组用'='字符填充*/
std::string CryptoUtils::base64Encode(const unsigned char* data, size_t len) {// BIO: OpenSSL的I/O抽象层// BIO_s_mem(): 内存BIO,数据存储在内存中BIO* bio = BIO_new(BIO_s_mem());if (!bio) {throw std::runtime_error("Failed to create BIO");}// BIO_f_base64(): Base64过滤器BIO// 可以自动进行Base64编码/解码BIO* b64 = BIO_new(BIO_f_base64());if (!b64) {BIO_free(bio);throw std::runtime_error("Failed to create Base64 BIO");}// BIO_push: 将两个BIO连接成链// 数据流向:输入 -> Base64编码器 -> 内存存储bio = BIO_push(b64, bio);// BIO_set_flags: 设置BIO标志// BIO_FLAGS_BASE64_NO_NL: 不在输出中添加换行符BIO_set_flags(bio, BIO_FLAGS_BASE64_NO_NL);// BIO_write: 向BIO写入数据// 数据会自动经过Base64编码if (BIO_write(bio, data, len) <= 0) {BIO_free_all(bio);throw std::runtime_error("Failed to write to Base64 BIO");}// BIO_flush: 刷新BIO缓冲区if (BIO_flush(bio) <= 0) {BIO_free_all(bio);throw std::runtime_error("Failed to flush Base64 BIO");}// 获取编码后的数据BUF_MEM* buffer_ptr;BIO_get_mem_ptr(bio, &buffer_ptr);std::string result(buffer_ptr->data, buffer_ptr->length);// 释放BIO资源BIO_free_all(bio);return result;
}/*** Base64编码字符串版本* @param data 输入字符串* @return Base64编码后的字符串*/
std::string CryptoUtils::base64Encode(const std::string& data) {return base64Encode(reinterpret_cast<const unsigned char*>(data.c_str()), data.length());
}/*** Base64解码* @param encoded Base64编码的字符串* @return 解码后的二进制数据字符串* * Base64解码原理:* 1. 将Base64字符转换回6位数值* 2. 将4个6位数值组合成3个8位字节* 3. 处理填充字符'='*/
std::string CryptoUtils::base64Decode(const std::string& encoded) {// 创建内存BIO并写入编码数据BIO* bio = BIO_new_mem_buf(encoded.c_str(), encoded.length());if (!bio) {throw std::runtime_error("Failed to create memory BIO");}// 创建Base64解码器BIOBIO* b64 = BIO_new(BIO_f_base64());if (!b64) {BIO_free(bio);throw std::runtime_error("Failed to create Base64 BIO");}// 连接BIO链:内存 -> Base64解码器bio = BIO_push(b64, bio);// 设置不处理换行符BIO_set_flags(bio, BIO_FLAGS_BASE64_NO_NL);// 读取解码后的数据char buffer[1024];std::string result;int len;while ((len = BIO_read(bio, buffer, sizeof(buffer))) > 0) {result.append(buffer, len);}BIO_free_all(bio);return result;
}/*** 生成安全随机数* @param len 随机数长度(字节)* @return 随机数的十六进制字符串表示* * 使用OpenSSL的CSPRNG(密码学安全伪随机数生成器)*/
std::string CryptoUtils::generateRandomHex(size_t len) {std::vector<unsigned char> buffer(len);// RAND_bytes: 生成密码学安全的随机字节// 参数1: 输出缓冲区// 参数2: 字节数// 返回值: 1表示成功,0表示失败if (RAND_bytes(buffer.data(), len) != 1) {throw std::runtime_error("Failed to generate random bytes");}return bytesToHex(buffer.data(), len);
}/*** 生成随机盐值(用于密码哈希)* @param len 盐值长度,默认16字节* @return 盐值的十六进制字符串*/
std::string CryptoUtils::generateSalt(size_t len) {return generateRandomHex(len);
}/*** 带盐值的密码哈希* @param password 原始密码* @param salt 盐值(十六进制字符串)* @return 密码哈希值的十六进制字符串* * 流程:* 1. 将盐值从十六进制转换为二进制* 2. 将密码和盐值连接* 3. 计算SHA-256哈希*/
std::string CryptoUtils::hashPassword(const std::string& password, const std::string& salt) {// 将十六进制盐值转换为二进制std::vector<unsigned char> salt_bytes = hexToBytes(salt);// 构造待哈希的数据:密码 + 盐值std::string data = password + std::string(salt_bytes.begin(), salt_bytes.end());return sha256(data);
}/*** 验证密码* @param password 输入的密码* @param salt 存储的盐值* @param stored_hash 存储的密码哈希值* @return 密码是否正确*/
bool CryptoUtils::verifyPassword(const std::string& password, const std::string& salt, const std::string& stored_hash) {std::string computed_hash = hashPassword(password, salt);return secureCompare(computed_hash, stored_hash);
}/*** 安全字符串比较(防止时序攻击)* @param a 字符串a* @param b 字符串b* @return 字符串是否相等* * 时序攻击:攻击者通过测量比较操作的时间来推断信息* 常数时间比较:无论字符串是否相等,比较时间都相同*/
bool CryptoUtils::secureCompare(const std::string& a, const std::string& b) {if (a.length() != b.length()) {return false;}volatile unsigned char result = 0;for (size_t i = 0; i < a.length(); ++i) {result |= a[i] ^ b[i];}return result == 0;
}/*** 将字节数组转换为十六进制字符串* @param data 字节数组指针* @param len 数组长度* @return 十六进制字符串(小写)*/
std::string CryptoUtils::bytesToHex(const unsigned char* data, size_t len) {std::string result;result.reserve(len * 2);  // 预分配空间提高性能const char hex_chars[] = "0123456789abcdef";for (size_t i = 0; i < len; ++i) {result += hex_chars[data[i] >> 4];      // 高4位result += hex_chars[data[i] & 0x0F];    // 低4位}return result;
}/*** 将十六进制字符串转换为字节数组* @param hex 十六进制字符串* @return 字节数组*/
std::vector<unsigned char> CryptoUtils::hexToBytes(const std::string& hex) {if (hex.length() % 2 != 0) {throw std::invalid_argument("Hex string length must be even");}std::vector<unsigned char> result;result.reserve(hex.length() / 2);for (size_t i = 0; i < hex.length(); i += 2) {unsigned char byte = 0;// 处理高4位char high = hex[i];if (high >= '0' && high <= '9') {byte = (high - '0') << 4;} else if (high >= 'a' && high <= 'f') {byte = (high - 'a' + 10) << 4;} else if (high >= 'A' && high <= 'F') {byte = (high - 'A' + 10) << 4;} else {throw std::invalid_argument("Invalid hex character");}// 处理低4位char low = hex[i + 1];if (low >= '0' && low <= '9') {byte |= (low - '0');} else if (low >= 'a' && low <= 'f') {byte |= (low - 'a' + 10);} else if (low >= 'A' && low <= 'F') {byte |= (low - 'A' + 10);} else {throw std::invalid_argument("Invalid hex character");}result.push_back(byte);}return result;
}// JWT工具类实现
// JWT (JSON Web Token): 一种用于安全传输信息的开放标准/*** 创建JWT令牌* @param payload JWT载荷(JSON字符串)* @param secret 签名密钥* @param algorithm 签名算法(默认HS256)* @return JWT令牌字符串* * JWT结构:header.payload.signature* - header: 包含算法和令牌类型信息* - payload: 包含声明(claims)* - signature: 使用header中指定的算法对header和payload的签名*/
std::string JWTUtils::createToken(const std::string& payload, const std::string& secret,const std::string& algorithm) {// 构造JWT头部std::string header = R"({"alg":")" + algorithm + R"(","typ":"JWT"})";// Base64编码头部和载荷std::string encoded_header = CryptoUtils::base64Encode(header);std::string encoded_payload = CryptoUtils::base64Encode(payload);// 移除Base64编码中的填充字符(JWT规范要求)encoded_header = removeBase64Padding(encoded_header);encoded_payload = removeBase64Padding(encoded_payload);// 构造待签名字符串std::string signing_input = encoded_header + "." + encoded_payload;// 生成签名std::string signature;if (algorithm == "HS256") {signature = CryptoUtils::hmacSha256(secret, signing_input);// 将十六进制签名转换为字节,再进行Base64编码std::vector<unsigned char> sig_bytes = CryptoUtils::hexToBytes(signature);signature = CryptoUtils::base64Encode(sig_bytes.data(), sig_bytes.size());signature = removeBase64Padding(signature);} else {throw std::invalid_argument("Unsupported algorithm: " + algorithm);}// 组装最终的JWTreturn signing_input + "." + signature;
}/*** 验证JWT令牌* @param token JWT令牌* @param secret 签名密钥* @return 验证是否成功*/
bool JWTUtils::verifyToken(const std::string& token, const std::string& secret) {try {// 分割JWT的三个部分std::vector<std::string> parts = splitString(token, '.');if (parts.size() != 3) {return false;}std::string header = parts[0];std::string payload = parts[1];std::string signature = parts[2];// 重新计算签名std::string signing_input = header + "." + payload;std::string expected_signature = CryptoUtils::hmacSha256(secret, signing_input);// 将存储的签名转换为十六进制格式进行比较std::string padded_signature = addBase64Padding(signature);std::string decoded_signature = CryptoUtils::base64Decode(padded_signature);std::string hex_signature = CryptoUtils::bytesToHex(reinterpret_cast<const unsigned char*>(decoded_signature.c_str()), decoded_signature.length());// 使用安全比较防止时序攻击return CryptoUtils::secureCompare(expected_signature, hex_signature);} catch (const std::exception&) {return false;}
}/*** 解析JWT载荷* @param token JWT令牌* @return 解码后的载荷字符串*/
std::string JWTUtils::getPayload(const std::string& token) {std::vector<std::string> parts = splitString(token, '.');if (parts.size() != 3) {throw std::invalid_argument("Invalid JWT format");}std::string padded_payload = addBase64Padding(parts[1]);return CryptoUtils::base64Decode(padded_payload);
}/*** 移除Base64填充字符* @param base64_str Base64字符串* @return 移除填充后的字符串*/
std::string JWTUtils::removeBase64Padding(const std::string& base64_str) {std::string result = base64_str;while (!result.empty() && result.back() == '=') {result.pop_back();}return result;
}/*** 添加Base64填充字符* @param base64_str 无填充的Base64字符串* @return 添加填充后的字符串*/
std::string JWTUtils::addBase64Padding(const std::string& base64_str) {std::string result = base64_str;while (result.length() % 4 != 0) {result += '=';}return result;
}/*** 分割字符串* @param str 待分割的字符串* @param delimiter 分隔符* @return 分割后的字符串数组*/
std::vector<std::string> JWTUtils::splitString(const std::string& str, char delimiter) {std::vector<std::string> result;std::string current;for (char c : str) {if (c == delimiter) {result.push_back(current);current.clear();} else {current += c;}}if (!current.empty()) {result.push_back(current);}return result;
}
http://www.dtcms.com/a/348924.html

相关文章:

  • HMM+viterbi学习
  • Trip Footprint旅行足迹App
  • Windows在资源管理器地址栏输入CMD没反应
  • MATLAB 数值计算进阶:微分方程求解与矩阵运算高效方法
  • 伯朗特功率分析仪通过Modbus转Profinet网关与工业以太网集成案例
  • RunningHub - 基于ComfyUI的云端AI创作与应用平台
  • PID控制器的原理以及PID控制仿真
  • 离线签名协调器 Offline Signing Orchestrator(OSO)
  • 可视化-模块1-HTML-03
  • 图解SpringMVC工作流程,以及源码分析。
  • response对象的elapsed属性
  • 深度模块化剖析:构建一个健壮的、支持动态Cookie和代理的Python网络爬虫
  • Altium Designer 22使用笔记(9)---PCB布局、布线操作
  • halcon(一)一维码解码
  • 普元低代码开发平台:开启企业高效创新新征程
  • 刷题日记0824
  • 【AI论文】实习生-S1:一种科学多模态基础模型
  • 0824 MLIR和AST相关资料
  • 复杂工业场景识别率↑18.3%!陌讯多模态OCR算法实战解析
  • 虚幻引擎5(UE5)Android端游戏开发全流程指南:从环境配置到项目发布
  • Qt工具栏中图标槽函数没有响应的问题分析
  • 【JVM内存结构系列】三、堆内存深度解析:Java对象的“生存主场”
  • 【数据分享】地级市能源利用效率(超效率SBM、超效率CCR)(2006-2023)
  • Vue中 this.$emit() 方法详解, 帮助子组件向父组件传递事件
  • 纯血鸿蒙下的webdav库
  • vue中 computed vs methods
  • 【C++闯关笔记】STL:string的学习和使用(万字精讲)
  • 开发软件安装记录
  • Kubernetes v1.34 前瞻:资源管理、安全与可观测性的全面进化
  • golang6 条件循环