【常见开源库的二次开发】基于openssl的加密与解密——Base58比特币钱包地址——算法分析(三)

 目录:

 目录:

一、base58(58进制)

1.1 什么是base58?

1.2 辗转相除法

1.3 base58输出字节数:

二、源码分析:

2.1源代码:

2.2 算法思路介绍:

2.2.1 Base58编码过程:

2.1.2 Base58解码过程:

2.1.3 Base58Check编码过程:

2.1.4 Base58Check解码过程:

三、Base58编解码:

3.1 Base58 编码函数 encodeBase58

3.2 Base58 解码函数 decodeBase58

3.3 主函数 main


一、base58(58进制)

1.1 什么是base58?

Base58编码是在Base64字符集基础上,为了避免混淆而进行的优化。它去除了在Base64中可能引起混淆的字符,包括数字0、大写字母O、小写字母l、大写字母I,以及“+”和“/”两个符号。这样的设计使得Base58在视觉上更为清晰,减少错误。

Base58采用了一种相当别致的处理方法,它并未像Base16或Base64那样规律性的按位处理。相反,我们采用了一种称为"辗转相除法"的处理方式。这种方法虽然与传统方式不同,但却同样有效,进一步增强了编码的清晰度和可读性。

1.2 辗转相除法

欧几里得算法,也称为辗转相除法,是一种高效求解两个数最大公约数的方法。这种算法的核心思想在于:两个数的最大公约数等于其中较小的数与它们的差的最大公约数。这个算法不仅简洁,而且在数学和计算机科学中应用广泛。

Base58编码中,字符从'1'开始映射,其中'1'代表数字0,'2'代表数字1,一直到'z'代表数字57。这种映射策略有助于在编码时减少混淆,并保持数据的清晰易读。

对于将一个数字转换为Base58编码,我们用辗转相除法,这可以通过以下步骤来说明:

以数字1234为例,转换为58进制的过程如下:

  1. 将1234除以58,得到商21和余数16。根据Base58的编码表,余数16对应字符'H'。
  2. 接着将商21除以58,得到商0和余数21。根据Base58的编码表,余数21对应字符'N'。

因此,1234在Base58编码中表示为“NH”。

对于数字前有一个或多个0的情况,按Base58的编码规则,每一个0都直接转换为字符'1'。例如,如果数字是001234,那么在Base58编码中,它将表示为“11NH”。这里,前面的两个0分别转换为两个'1',紧接着是由1234转换来的“NH”。

1.3 base58输出字节数:

在Base58编码中,每个字符来自58个可选字符,因此每个字符需要表示的位数是log2(58),也即每个字符携带的信息量为log2(58)位。

对于输入的字节数据,其长度为(length * 8)位。所以,需要预留的字符数量就是(length * 8) / log2(58)。

换句话说,为了表示一个字节(8位)的信息,Base58编码需要的字符长度为1 / log2(58),即大约1.38个字符。这意味着每个Base58字符能够表示的信息比二进制编码要多,从而提高了编码效率。

二、源码分析:

2.1源代码:

实现了Base58编码与解码的相关功能,包括对输入的字节序列进行Base58编码、对Base58编码的字符串进行解码,并且包含了对Base58Check编码的支持(这是一种在Base58编码的基础上添加了校验和的编码方式,用于提高数据的传输可靠性)。

//版权信息 (c) 2014-2022 比特币核心开发者
//在MIT软件许可下分发,参见附带的
//复制文件或http://www.opensource.org/licenses/mit-license.php.

#include <base58.h>
#include <hash.h>
#include <uint256.h>
#include <util/strencodings.h>
#include <util/string.h>
#include <assert.h>
#include <string.h>
#include <limits>

// 使用无NUL包含的工具
using util::ContainsNoNUL;

/** 所有的字母数字除了 "0", "I", "O", 和 "l" */
//定义一个Base58编码表
static const char* pszBase58 = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"; 

//定义一个映射表,将输入字符映射为对应的整数值
static const int8_t mapBase58[256] = { 
    -1,-1,-1,-1,-1,-1,-1,-1, -1,-1,-1,-1,-1,-1,-1,-1,
    -1,-1,-1,-1,-1,-1,-1,-1, -1,-1,-1,-1,-1,-1,-1,-1,
    -1,-1,-1,-1,-1,-1,-1,-1, -1,-1,-1,-1,-1,-1,-1,-1,
    -1, 0, 1, 2, 3, 4, 5, 6,  7, 8,-1,-1,-1,-1,-1,-1,
    -1, 9,10,11,12,13,14,15, 16,-1,17,18,19,20,21,-1,
    22,23,24,25,26,27,28,29, 30,31,32,-1,-1,-1,-1,-1,
    -1,33,34,35,36,37,38,39, 40,41,42,43,-1,44,45,46,
    47,48,49,50,51,52,53,54, 55,56,57,-1,-1,-1,-1,-1,
    -1,-1,-1,-1,-1,-1,-1,-1, -1,-1,-1,-1,-1,-1,-1,-1,
    -1,-1,-1,-1,-1,-1,-1,-1, -1,-1,-1,-1,-1,-1,-1,-1,
    -1,-1,-1,-1,-1,-1,-1,-1, -1,-1,-1,-1,-1,-1,-1,-1,
    -1,-1,-1,-1,-1,-1,-1,-1, -1,-1,-1,-1,-1,-1,-1,-1,
    -1,-1,-1,-1,-1,-1,-1,-1, -1,-1,-1,-1,-1,-1,-1,-1,
    -1,-1,-1,-1,-1,-1,-1,-1, -1,-1,-1,-1,-1,-1,-1,-1,
    -1,-1,-1,-1,-1,-1,-1,-1, -1,-1,-1,-1,-1,-1,-1,-1,
    -1,-1,-1,-1,-1,-1,-1,-1, -1,-1,-1,-1,-1,-1,-1,-1,
};

// 定义一个函数DecodeBase58,用于解码输入的Base58字符串,返回转换后的字节序列
[[nodiscard]] static bool DecodeBase58(const char* psz, std::vector<unsigned char>& vch, int max_ret_len)
{
    // 跳过前导空格
    while (*psz && IsSpace(*psz))
        psz++;
    // 跳过并计数前导的'1's
    int zeroes = 0;
    int length = 0;
    while (*psz == '1') {
        zeroes++;
        if (zeroes > max_ret_len) return false;
        psz++;
    }
    // 在大端base256表示中分配足够的空间
    int size = strlen(psz) * 733 /1000 + 1; // log(58) / log(256), 向上取整
    std::vector<unsigned char> b256(size);
    // 处理字符
    static_assert(std::size(mapBase58) == 256, "mapBase58.size() should be 256"); // 保证不超出范围
    while (*psz && !IsSpace(*psz)) {
        // 解码base58字符
        int carry = mapBase58[(uint8_t)*psz];
        if (carry == -1)  // 无效的b58字符
            return false;
        int i = 0;
        for (std::vector<unsigned char>::reverse_iterator it = b256.rbegin(); (carry != 0 || i < length) && (it != b256.rend()); ++it, ++i) {
            carry += 58 * (*it);
            *it = carry % 256;
            carry /= 256;
        }
        assert(carry == 0);
        length = i;
        if (length + zeroes > max_ret_len) return false;
        psz++;
    }
    // 跳过后导空格
    while (IsSpace(*psz))
        psz++;
    if (*psz != 0)
        return false;
    // 跳过b256中的前导零
    std::vector<unsigned char>::iterator it = b256.begin() + (size - length);
    // 将结果复制到输出向量
    vch.reserve(zeroes + (b256.end() - it));
    vch.assign(zeroes, 0x00);
    while (it != b256.end())
        vch.push_back(*(it++));
    return true;
}

// 定义一个函数EncodeBase58,用于将输入的字节序列编码为Base58字符串
std::string EncodeBase58(Span<const unsigned char> input)
{
    // 跳过和计数前导零
    int zeroes = 0;
    int length = 0;
    while (input.size() > 0 && input[0] == 0) {
        input = input.subspan(1);
        zeroes++;
    }
    // 在大端base58表示中分配足够的空间
    int size = input.size() * 138 / 100 + 1; // log(256) / log(58), 向上取整
    std::vector<unsigned char> b58(size);
    // 处理字节
    while (input.size() > 0) {
        int carry = input[0];
        int i = 0;
        // 应用 "b58 = b58 * 256 + ch" 
        for (std::vector<unsigned char>::reverse_iterator it = b58.rbegin(); (carry != 0 || i < length) && (it != b58.rend()); it++, i++) {
            carry += 256 * (*it);
            *it = carry % 58;
            carry /= 58;
        }
        assert(carry == 0);
        length = i;
        input = input.subspan(1);
    }
    // 在base58结果中跳过前导零
    std::vector<unsigned char>::iterator it = b58.begin() + (size - length);
    while (it != b58.end() && *it == 0)
        it++;
    // 将结果转化为字符串
    std::string str;
    str.reserve(zeroes + (b58.end() - it));
    str.assign(zeroes, '1');
    while (it != b58.end())
        str += pszBase58[*(it++)];
    return str;
}

// 定义一个函数DecodeBase58,用于解码输入的Base58字符串,返回转换后的字节序列
bool DecodeBase58(const std::string& str, std::vector<unsigned char>& vchRet, int max_ret_len)
{
    if (!ContainsNoNUL(str)) {
        return false;
    }
    return DecodeBase58(str.c_str(), vchRet, max_ret_len);
}

// 定义一个函数EncodeBase58Check,用于将输入的字节序列编码为添加了4字节hash校验的Base58字符串
std::string EncodeBase58Check(Span<const unsigned char> input)
{
    // 在结束处添加4字节的hash校验
    std::vector<unsigned char> vch(input.begin(), input.end());
    uint256 hash = Hash(vch);
    vch.insert(vch.end(), (unsigned char*)&hash, (unsigned char*)&hash + 4);
    return EncodeBase58(vch);
}

[[nodiscard]] static bool DecodeBase58Check(const char* psz, std::vector<unsigned char>& vchRet, int max_ret_len)
{
    if (!DecodeBase58(psz, vchRet, max_ret_len > std::numeric_limits<int>::max() - 4 ? std::numeric_limits<int>::max() : max_ret_len + 4) ||
        (vchRet.size() < 4)) {
        vchRet.clear();
        return false;
    }
    // 重新计算校验和,确保它匹配包含的4字节校验和
    uint256 hash = Hash(Span{vchRet}.first(vchRet.size() - 4));
    if (memcmp(&hash, &vchRet[vchRet.size() - 4], 4) != 0) {
        vchRet.clear();
        return false;
    }
    vchRet.resize(vchRet.size() - 4);
    return true;
}

bool DecodeBase58Check(const std::string& str, std::vector<unsigned char>& vchRet, int max_ret)
{
    if (!ContainsNoNUL(str)) {
        return false;
    }
    return DecodeBase58Check(str.c_str(), vchRet, max_ret);
}

2.2 算法思路介绍:

Base58编码是一种常用于加密货币地址和某些区块链应用中的编码方式,它使用了58个字符(排除了容易混淆的字符,如0(零)、O(大写的o)、I(大写的i)和l(小写的L)),以提供一种相对于Base64更容易人工阅读和手写的编码系统。

2.2.1 Base58编码过程:

  1. 计算输入数据的大小:输入数据(通常是一串字节)首先被评估以确定其长度和所需的编码空间大小。
  2. 处理前导零:Base58编码保留了输入数据中的前导零字节(0x00),每个前导零字节在编码字符串中用字符'1'表示。
  3. 转换为Base58:剩余的输入数据被转换为一个大整数,然后这个大整数被转换成Base58表示,即通过不断除以58并取余数的方式获得一系列的Base58字符。
  4. 拼接结果:最终的编码字符串由步骤2中处理的前导零(每个零用'1'表示)和步骤3的转换结果组成。

2.1.2 Base58解码过程:

  1. 校验并移除前导'1':解码首先会检查编码字符串中前导的'1'字符,并记录其数量,因为每个'1'代表一个前导零字节。
  2. Base58到大整数:然后将Base58编码字符串转换回一个大整数,即通过对每个Base58字符进行查表操作,获取对应的数值,并将这些数值以58为基数合并成一个大整数。
  3. 转换为字节序列:这个大整数随后被转换回原始的字节序列。
  4. 重建前导零:根据步骤1中记录的前导'1'的数量,在解码后的字节序列前加上相应数量的零字节。

2.1.3 Base58Check编码过程:

Base58Check是在Base58的基础上增加了错误检测的能力。它通常包括以下几个步骤:

  1. 计算校验和:对输入数据计算校验和(如,通过SHA-256算法计算两次,取前四个字节作为校验和)。
  2. 添加校验和:将这个校验和添加到原始数据的末尾。
  3. Base58编码:将步骤2得到的字节序列进行Base58编码。

2.1.4 Base58Check解码过程:

  1. Base58解码:首先对编码字符串进行Base58解码。
  2. 验证校验和:从解码结果中提取末尾四个字节作为校验和,与剩余数据重新计算的校验和进行比较。
  3. 提取数据:如果校验和匹配,说明数据未被篡改,去掉末尾四个字节的校验和,返回剩余的数据部分。

这些步骤中对前导零的特殊处理(编码中的'1'字符和解码时的零字节)是Base58和Base58Check编码的一个重要特性,确保了编码结果的唯一性和解码的准确性。

三、Base58编解码:

Base58编码和解码的实现。Base58是一种用于比特币等加密货币中的编码方案,它避开了某些视觉上容易混淆的字符,比如0(数字零)、O(大写字母O)、I(大写字母I)和l(小写字母L)。

#include <iostream>
#include <string>
#include <vector>
#include <algorithm>

// 定义Base58编码的字符集
static const std::string BASE58_ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";

// Base58编码函数
std::string encodeBase58(const unsigned char* input, size_t len) {
    std::vector<unsigned int> digits(40, 0); // 用于存储Base58表示的向量,初始全为0
    size_t digits_len = 1; // 有效位数长度,初始为1

    // 对输入数据的每个字节进行处理
    for (size_t i = 0; i < len; i++) {
        unsigned int carry = input[i]; // 当前字节值
        for (size_t j = 0; j < digits_len; j++) {
            carry += digits[j] * 256; // 将当前值加到之前的结果
            digits[j] = carry % 58; // 计算Base58的当前位
            carry /= 58; // 更新进位
        }

        // 处理剩余的进位
        while (carry) {
            digits[digits_len++] = carry % 58;
            carry /= 58;
        }
    }

    std::string output;
    // 处理前导0的特殊情况,Base58用'1'表示前导0
    for (int i = 0; i < len && input[i] == 0; i++) {
        output += '1';
    }

    // 将计算得到的Base58位数转换为字符
    for (size_t i = 0; i < digits_len; i++) {
        output += BASE58_ALPHABET[digits[digits_len - 1 - i]];
    }

    return output; // 返回编码后的字符串
}

// Base58解码函数
std::vector<unsigned char> decodeBase58(const std::string& input) {
    std::vector<unsigned char> output; // 初始化输出向量

    // 对输入的每个字符进行处理
    for (size_t i = 0; i < input.length(); i++) {
        int value = BASE58_ALPHABET.find(input[i]); // 查找字符在Base58字符集中的位置
        if (value == std::string::npos) {
            // 如果字符不在Base58字符集中,返回空向量表示解码失败
            return {};
        }

        for (size_t j = 0; j < output.size(); j++) {
            value += output[j] * 58; // 更新当前位
            output[j] = value % 256; // 计算当前位的值
            value /= 256; // 更新进位
        }

        // 处理剩余的进位
        while (value) {
            output.push_back(value % 256);
            value /= 256;
        }
    }

    // 处理前导'1'的特殊情况,对应前导0字节
    for (size_t i = 0; i < input.length() && input[i] == '1'; i++) {
        output.push_back(0);
    }

    // 逆转输出向量以匹配原始输入的顺序
    std::reverse(output.begin(), output.end());

    return output; // 返回解码后的字节向量
}

int main() {
    const std::string input_data = "Hello, world!"; // 要编码的输入数据
    std::cout << "原始数据:" << input_data << "\n";

    // 调用编码函数
    std::string encoded = encodeBase58(reinterpret_cast<const unsigned char*>(input_data.data()), input_data.size());
    std::cout << "Encoded编码后: " << encoded << "\n";

    // 调用解码函数
    std::vector<unsigned char> decoded = decodeBase58(encoded);
    std::string decodedStr(decoded.begin(), decoded.end());
    std::cout << "Decoded解码后: " << decodedStr << "\n";

    return 0; // 返回0表示程序成功执行
}

3.1 Base58 编码函数 encodeBase58

这个函数用于将字节数组编码为Base58字符串。

  • 输入const unsigned char* input, size_t len,表示输入的字节数组及其长度。
  • 处理:
    • 使用一个vector<unsigned int>来临时存储计算的数字,这个向量的长度被初始化为40,足以处理常见的输入长度。
    • 通过多重循环,将输入的字节转换为Base58编码。内部逻辑处理了进位,这对于任何基数的转换都是必需的。
    • 处理前导0,因为Base58编码中前导0用'1'字符表示。

3.2 Base58 解码函数 decodeBase58

这个函数用于将Base58编码的字符串解码回原始的字节数据。

  • 输入const std::string& input,Base58编码的字符串。
  • 处理:
    • 初始化一个动态大小的vector<unsigned char>用于存储解码的结果。
    • 遍历输入的每一个字符,找出其在Base58字符集中的索引,这个索引值代表了其对应的数值。
    • 通过计算和处理进位,将Base58编码转换回字节。
    • 处理Base58编码中的前导'1',这些'1'在解码时应转换为前导0字节。
    • 最后,由于解码过程中字节被逆向存储,需要将结果向量反转以恢复初始的字节顺序。

3.3 主函数 main

  • 功能: 测试encodeBase58decodeBase58函数,使用字符串"Hello, world!"作为输入。
  • 输出:
    • 显示原始数据。
    • 显示编码后的Base58字符串。
    • 显示解码后的字符串,应与原始输入相同。

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

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

相关文章

基于高德地图实现Android定位功能实现(二)

基于高德地图实现Android定位功能实现&#xff08;二&#xff09; 在实现的高德地图的基本显示后&#xff0c;我们需要不断完善地图的功能 地图界面设计&#xff08;悬浮按钮等&#xff09; 首先就是地图页面的布局&#xff0c;这个根据大家的实际需求进行设计即可&#xff…

nacos 适配瀚高数据库、ARM 架构

下载nacos源码: https://github.com/alibaba/nacos/tree/2.3.1 瀚高技术文档 1、修改pom.xml 根目录nacos-all => pom.xml<dependencyManagement><dependency><groupId>com.highgo</groupId><artifactId>HgdbJdbc</artifactId><…

xss复习总结及ctfshow做题总结xss

xss复习总结 知识点 1.XSS 漏洞简介 ​ XSS又叫CSS&#xff08;Cross Site Script&#xff09;跨站脚本攻击是指恶意攻击者往Web页面里插入恶意Script代码&#xff0c;当用户浏览该页之时&#xff0c;嵌入其中Web里面的Script代码会被执行&#xff0c;从而达到恶意攻击用户的…

MySQL篇:事务

1.四大特性 首先&#xff0c;事务的四大特性&#xff1a;ACID&#xff08;原子性&#xff0c;一致性&#xff0c;隔离性&#xff0c;持久性&#xff09; 在InnoDB引擎中&#xff0c;是怎么来保证这四个特性的呢&#xff1f; 持久性是通过 redo log &#xff08;重做日志&…

【ARM】MDK-服务器与客户端不同网段内出现卡顿问题

【更多软件使用问题请点击亿道电子官方网站】 1、 文档目标 记录不同网段之间的请求发送情况以及MDK网络版license文件内设置的影响。 2、 问题场景 客户使用很久的MDK网络版&#xff0c;在获取授权时都会出现4-7秒的卡顿&#xff0c;无法对keil进行任何操作&#xff0c;彻底…

java——Junit单元测试

测试分类 黑盒测试&#xff1a;不输入代码&#xff0c;给输入值&#xff0c;看程序能够给出期望的值。 白盒测试&#xff1a;写代码&#xff0c;关注程序具体执行流程。 JUnit单元测试 一个测试框架&#xff0c;供java开发人员编写单元测试。 是程序员测试&#xff0c;即白…

【边缘计算网关教程】6.松下 Mewtocol TCP 协议

前景回顾&#xff1a;【边缘计算网关教程】5.三菱FX3U编程口通讯-CSDN博客 松下 Mewtocol TCP 协议 适配PLC&#xff1a;松下FP0H 松下XHC60ET 1. 硬件连接 Mewtocol TCP协议采用网口通信的方式&#xff0c;因此&#xff0c;只需要保证网关的LAN口和松下PLC的IP在一个网段即…

【车载开发系列】GIT教程---如何下载代码库

【车载开发系列】GIT教程—如何下载代码库 【车载开发系列】GIT教程---如何下载代码库 【车载开发系列】GIT教程---如何下载代码库一. 设置用户名和邮箱二. 生成SSH三. 登录远程github仓库配置四. Git中的ssh协议介绍五. 什么是GitLab六. GitLab与GitHub区别1&#xff09;用途和…

【区块链 + 智慧政务】区块链 +ETC 下一代公路联网收费关键技术优化项目 | FISCO BCOS应用案例

2020 年&#xff0c;我国取消省界收费站项目完成后&#xff0c;随着收费模式与收费方式的变化&#xff0c;形成了以门架为计费单元的新收 费体系&#xff1a;按照车辆通行门架数&#xff0c;RSU 天线读取 ETC 卡、电子标签 OBU 或 CPC 卡内标识的车型信息&#xff0c;车型门架计…

Qt实现MDI应用程序

本文记录Qt实现MDI应用程序的相关操作实现 目录 1.MDM模式下窗口的显示两种模式 1.1TabbedView 页签化显示 1.2 SubWindowView 子窗体显示 堆叠cascadeSubWindows 平铺tileSubWindows 2.MDM模式实现记录 2.1. 窗体继承自QMainWindow 2.2.增加组件MdiArea 2.3.定义统一…

塑胶件缺胶影响工业生产,云盘科技提供解决方案!

塑料件的成型检测&#xff0c;主要检测其中的各种高低、形状的柱子、块块、条条之类的。因为有一些产品在成型时&#xff0c;可能柱子没有足够的料&#xff0c;又或者是某个挡块只有一半这样的。所以需要进行一定的检测。 塑胶在注塑加工成型后&#xff0c;出现缺胶缺陷。思普泰…

RT-DETR+Flask实现目标检测推理案例

今天&#xff0c;带大家利用RT-DETR&#xff08;我们可以换成任意一个模型&#xff09;Flask来实现一个目标检测平台小案例&#xff0c;其实现效果如下&#xff1a; 目标检测案例 这个案例很简单&#xff0c;就是让我们上传一张图像&#xff0c;随后选择一下置信度&#xff0c;…

部署k8s 1.28.9版本

继上篇通过vagrant与virtualBox实现虚拟机的安装。笔者已经将原有的vmware版本的虚拟机卸载掉了。这个场景下&#xff0c;需要重新安装k8s 相关组件。由于之前写的一篇文章本身也没有截图。只有命令。所以趁着现在。写一篇&#xff0c;完整版带截图的步骤。现在行业这么卷。离…

TikTok账号矩阵运营怎么做?

这几年&#xff0c;聊到出海避不过海外抖音&#xff0c;也就是TikTok&#xff0c;聊到TikTok电商直播就离不开账号矩阵&#xff1b; 在TikTok上&#xff0c;矩阵养号已经成为了出海电商人的流行策略&#xff0c;归根结底还是因为矩阵养号可以用最小的力&#xff0c;获得更大的…

Spring之事务管理TranscationManager(大合集)

原子性 事务是数据库的逻辑工作单位&#xff0c;事务中包括的诸操作要么全做&#xff0c;要么全不做。 一致性 事务执行的结果必须是使数据库从一个一致性状态变到另一个一致性状态。一致性与原子性是密切相关的。 隔离性 一个事务的执行不能被其他事务干扰。 持续性 一…

大数据hive表和iceberg表格式

iceberg: https://iceberg.apache.org/ iceberg表&#xff0c;是一种面向大型分析数据集的开放表格式&#xff0c;旨在提供可扩展、高效、安全的数据存储和查询解决方案。它支持多种存储后端上的数据操作&#xff0c;并提供 ACID 事务、多版本控制和模式演化等特性&#xff0c…

Django select_related()方法

select_related()的作用 select_related()是Django ORM&#xff08;对象关系映射&#xff09;中的一种查询优化方法&#xff0c;主要用于减少数据库查询次数&#xff0c;提高查询效率。当你在查询一个模型实例时&#xff0c;如果这个实例有ForeignKey关联到其他模型&#xff0…

uniapp:国家、省市区,4级联动

使用uview的Select 列选择器 选择器完成国家&#xff0c;省市区&#xff0c;4级联动 要求后台数据格式&#xff1a; list: [{label: 中国,value: 1,children: [{label: 河南省,value: 2,children: [{label: 郑州市,value: 3,children: [{label: 中原区,value: 4},{label: 郑东…

RocketMQ实现分布式事务

RocketMQ的分布式事务消息功能&#xff0c;在普通消息基础上&#xff0c;支持二阶段的提交。将二阶段提交和本地事务绑定&#xff0c;实现全局提交结果的一致性。 1、生产者将消息发送至RocketMQ服务端。 2、RocketMQ服务端将消息持久化成功之后&#xff0c;向生产者返回Ack确…

OpenCV开发笔记(七十八):在ubuntu上搭建opencv+python开发环境以及匹配识别Demo

若该文为原创文章&#xff0c;转载请注明原文出处 本文章博客地址&#xff1a;https://hpzwl.blog.csdn.net/article/details/140435870 长沙红胖子Qt&#xff08;长沙创微智科&#xff09;博文大全&#xff1a;开发技术集合&#xff08;包含Qt实用技术、树莓派、三维、OpenCV…