openssl3.2 - exp - 用base64后的字符串作为配置项的值

文章目录

    • openssl3.2 - exp - 用base64后的字符串作为配置项的值
    • 概述
    • 笔记
      • 配置项的值长度有限制
    • 配置项的值不能是base64之后的直接值,需要处理之后才行。
    • openssl配置项的值并不是所有可见字符都可以
    • 例子
    • 现在用的base64的类
      • cipher_base64.h
      • cipher_base64.cpp
    • 现在用的openssl配置文件读写类
      • CMyOsslConfig.h
      • CMyOsslConfig.cpp
      • CMyOsslMem.h
      • CMyOsslMem.cpp
    • END

openssl3.2 - exp - 用base64后的字符串作为配置项的值

概述

今天解析自己封装的内存型的openssl 配置数据,发现openssl并不支持配置项的值为base64之后的直接字符串。

笔记

配置项的值长度有限制

配置项的值(字符串)的长度好像有512字节的限制,具体没实验。看源码能看出来。

bool CMyOsslConfig::updateConfig()
{bool b_rc = false;int i_rc = 0;long lineSn = 0;int len1 = 0;int len2 = 0;do {if ((NULL == m_pMyOsslMem) || (NULL == m_conf)){break;}// BIO_seek(m_bio, 0);// len1 = this->bio_get_length(m_bio);assert(NULL != m_pMyOsslMem->get_bio());i_rc = NCONF_load_bio(m_conf, m_pMyOsslMem->get_bio(), &lineSn); // !// len2 = this->bio_get_length(m_bio);// m_bio有东西有数据长度, 但是经过NCONF_load_bio()后, m_conf里面有东西了, 但是m_bio的东西没了// 所以, 经过NCONF_load_bio后(), 就得当m_bio是空的来处理.// 如果是向m_bio中写东西, 然后写配置.// 如果要更新m_bio中的配置内容, 就必须重新写.if (i_rc <= 0){break;}b_rc = true;} while (false);return b_rc;
}

跟进NCONF_load_bio(), 可以看到openssl读一行开的缓冲区为512字节。

#define CONFBUFSIZE     512

虽然配置项的值一般没有那么长。不过如果要存一段很大的数据转成的base64值,那么就可能遇到问题。
解决的方法:自己做个多buffer的封装类(C++ - 多个buffer合并成一个buffer的管理类),比较长的信息,自己来管理。配置项值比较短的值,用openssl配置函数来读取。

openssl的配置文件(可以自己封装为内存操作的函数),并没有写入配置的API. 只有读取配置项的API.
如果需要用程序来写openssl的配置文件,可以自己封装一个。写入的格式和普通的ini格式一样(如果简单的用)。

配置项的值不能是base64之后的直接值,需要处理之后才行。

原因 :base64之后的值,每隔64个字符,有一个’\n’, 没有了这个’\n’, 用openssl的unbase64就不会成功。
折中的方法 :
在base64之后,写入配置项之前,处理一下,将’\n’去掉后,再写入openssl配置文件。
在读取配置项值之后(自己知道是base64的字符串),再处理一下,每64个字符加一个‘\n’(如果不是整除64个字符,bas64的尾巴上也要加’\n’), 然后再调用openssl的unbase64的API, 就能执行成功。

openssl配置项的值并不是所有可见字符都可以

有’\n’不行,openssl读取时,是整行(以回车’\n’为结尾)读取的,导致opensslbase64之后的值被截断(如果base64之后的值长度>64),导致unbase64不成功.

有’\'不行,e.g. 一个路径值(“d:\my_tmp\xx.dat”), 用openssl配置接口读取出来时,‘\’字符消失了, 变为了"d:my_tmpxx.dat",这就乱套了。
如果正好有\d这样的字符,读出的配置项的值直接乱码了。

如果配置项的值中有上述字符,都要base64之后才能正常读取。

例子

将base64写入openssl配置文件(去掉’\n’后,再base64, 然后写入配置项的值)

	// obj.add_config_section("PE");//ptszBuf = (TCHAR*)m_PeFileCheck.getFilePathName();// 需要base64,否则'\'都没了,还有乱码strTmp = strOpt.my_W2A(ptszBuf);base64.base64(true, (UCHAR*)strTmp.data(), (int)strTmp.size(), pOsslBufTmp, len_OsslBufTmp);// base64之后,每65个字符之后有一个\n, 不能移除,base64解码时,如果不是每64个字符后面带一个\n, 会unbase64失败strTmp = base64.remove_char_0a(pOsslBufTmp, len_OsslBufTmp);obj.add_config_item_after_section("getFilePathName", (const char*)strTmp.data());MY_OPENSSL_FREE(pOsslBufTmp);

用openssl配置文件的一方,先读出配置项的值,加上’\n’(每64个字符), 再unbase64.

		pszKeyValue = _ossl_cfg.get_conf_item_value("PE", "getFilePathName");if (NULL == pszKeyValue){break;}strTmp = base64.add_char_0a((UCHAR*)pszKeyValue, (int)strlen(pszKeyValue));b = base64.base64(false, (UCHAR*)strTmp.data(), (int)strTmp.size(), pOsslBufTmp, lenOsslBufTmp);if (!b){break;}

现在用的base64的类

cipher_base64.h

//! \file cipher_base64.h#ifndef __CIPHER_BASE64_H__
#define __CIPHER_BASE64_H__class CBase64
{
public:CBase64();virtual ~CBase64();bool base64(bool isEncode, UCHAR* pucBufIn, int lenBufIn, UCHAR*& pucBufOut, int& lenBufOut);std::string remove_char_0a(UCHAR* pucBufIn, int lenBufIn);std::string add_char_0a(UCHAR* pucBufIn, int lenBufIn);
};#endif // #ifndef __CIPHER_BASE64_H__

cipher_base64.cpp

//! \file cipher_base64.cpp#include "pch.h"
#include "cipher_base64.h"#include "openssl/bio.h"
#include "openssl/evp.h"#include "memOpt/my_debug_new_define.h"CBase64::CBase64()
{}CBase64::~CBase64()
{}std::string CBase64::remove_char_0a(UCHAR* pucBufIn, int lenBufIn)
{std::string str_rc;int i = 0;do {if ((NULL == pucBufIn) || (lenBufIn <= 0)){break;}for (i = 0; i < lenBufIn; i++){if (pucBufIn[i] != '\n'){str_rc += pucBufIn[i];}}} while (false);return str_rc;
}std::string CBase64::add_char_0a(UCHAR* pucBufIn, int lenBufIn)
{std::string str_rc;int cnt = 0;int i = 0;// openssl每一行(64个字符一行,最后一行除外)后面都有一个'\n', 才能正确解码do {if ((NULL == pucBufIn) || (lenBufIn <= 0)){break;}for (i = 0; i < lenBufIn; i++){str_rc += pucBufIn[i];if (++cnt == 64){cnt = 0;str_rc += '\n';}}} while (false);if (cnt > 0){str_rc += '\n';}return str_rc;
}bool CBase64::base64(bool isEncode, UCHAR* pucBufIn, int lenBufIn, UCHAR*& pucBufOut, int& lenBufOut)
{bool b_rc = false;BIO* bio_container = NULL;BIO* bio_to_base64 = NULL;BIO* bio_header = NULL; // BIO链头BIO* bio_tail = NULL; // BIO链尾BIO* bio_to_write = NULL; // 将数据写入的BIO指针BIO* bio_read_from = NULL; // 将数据读出的BIO指针size_t sz_wt = 0;size_t sz_rd = 0;int i_rc = 0;int len = 0;do {pucBufOut = NULL;lenBufOut = 0;if ((NULL == pucBufIn) || (lenBufIn <= 0)){break;}bio_container = BIO_new(BIO_s_mem());if (NULL == bio_container){break;}bio_to_base64 = BIO_new(BIO_f_base64());if (NULL == bio_to_base64){break;}bio_header = BIO_push(bio_to_base64, bio_container);bio_tail = bio_container;if (isEncode){bio_to_write = bio_header;bio_read_from = bio_tail;}else {// ! base64解码时, 是从链尾写入, 从链头读取bio_to_write = bio_tail;bio_read_from = bio_header;}i_rc = BIO_write_ex(bio_to_write, pucBufIn, lenBufIn, &sz_wt);if ((1 != i_rc) || (lenBufIn != sz_wt)){break;}BIO_flush(bio_to_write); // 数据写完后, 必须对写入的BIO执行 BIO_flush.// 必须从bio_read_from读取处理完的数据长度, 才是处理之后的数据长度len = BIO_pending(bio_read_from); // 必须BIO_flush()之后, 才能读取到BIO内可以读出的数据长度. 否则读出的长度是0// 当解码时, 得到的处理完的长度还是没解码之前的长度, 不过不影响// 拿这个长度开buffer, 实际数据处理完的长度按照从bio_read_from()中累计出的数据长度为准// 将处理过的数据从bio_header中读出来pucBufOut = (UCHAR*)OPENSSL_malloc(len + 1); // 再多加1个字节的空间, 便于观察得到的可见字符串if (NULL == pucBufOut){break;}pucBufOut[len] = '\0';do {// 不能从bio_header读取, 因为读取后, 还是原来的数据长度i_rc = BIO_read_ex(bio_read_from, pucBufOut + lenBufOut, len - lenBufOut, &sz_rd);if (i_rc <= 0){// 多次读, 直到读空了, 不算错break;}lenBufOut += (int)sz_rd;} while (true);if (NULL != pucBufOut){pucBufOut[lenBufOut] = 0x00;}b_rc = true;} while (false);if (NULL != bio_header){BIO_free_all(bio_header);bio_header = NULL;}return b_rc;
}

现在用的openssl配置文件读写类

CMyOsslConfig.h

//! \file CMyOsslConfig.h#ifndef __CMYOSSLCONFIG_H__
#define __CMYOSSLCONFIG_H__#include <openssl/bio.h>
#include <openssl/conf.h> // for CONF
#include "CMyOsslMem.h"class CMyOsslConfig
{
public:CMyOsslConfig();virtual ~CMyOsslConfig();bool init();void uninit();bool add_config_section(const char* pszIn);bool add_config_item_after_section(const char* pszItemName, const char* pszItemContent);bool append_to_bio(uint8_t* pBuf, int lenBuf);bool updateConfig();char* get_conf_item_value(const char* group, const char* name);CMyOsslMem* getMem() { return m_pMyOsslMem; }private:CMyOsslMem* m_pMyOsslMem;CONF* m_conf;
};#endif // #ifndef __CMYOSSLCONFIG_H__

CMyOsslConfig.cpp

//! \file CMyOsslConfig.cpp#include "pch.h"#include "CMyOsslConfig.h"
#include <string.h>
#include <openssl/err.h>
#include <cassert>
#include "CMyOsslMem.h"#include "memOpt/my_debug_new_define.h"CMyOsslConfig::CMyOsslConfig():m_pMyOsslMem(NULL),m_conf(NULL)
{}CMyOsslConfig::~CMyOsslConfig()
{// 必须在程序退出前,主动调用uninit(), 否则在mem_unhook()时,就会报mem leak的错
}void CMyOsslConfig::uninit()
{if (NULL != m_pMyOsslMem){m_pMyOsslMem->uninit();delete m_pMyOsslMem;m_pMyOsslMem = NULL;}if (NULL != m_conf){NCONF_free(m_conf);m_conf = NULL;}
}bool CMyOsslConfig::init()
{bool b_rc = false;do {if (NULL == m_pMyOsslMem){m_pMyOsslMem = new CMyOsslMem();assert(NULL != m_pMyOsslMem);if (NULL == m_pMyOsslMem){break;}m_pMyOsslMem->init();}if (NULL == m_conf){m_conf = NCONF_new_ex(OSSL_LIB_CTX_get0_global_default(), NULL);if (NULL == m_conf){break;}}b_rc = true;} while (false);return b_rc;
}bool CMyOsslConfig::add_config_section(const char* pszIn)
{bool b_rc = false;char szBuf[1024];int len = 0;do {if (NULL == pszIn){break;}len = (int)strlen(pszIn);if (len > (sizeof(szBuf) - 0x10)){break;}sprintf(szBuf, "[ %s ]\n", pszIn);assert(NULL != m_pMyOsslMem);if (!m_pMyOsslMem->append_to_bio((uint8_t*)szBuf, (int)strlen(szBuf))){break;}b_rc = true;} while (false);return b_rc;
}bool CMyOsslConfig::add_config_item_after_section(const char* pszItemName, const char* pszItemContent)
{bool b_rc = false;char szBuf[1024];int len = 0;do {if ((NULL == pszItemName) || (NULL == pszItemContent)){break;}len = (int)(strlen(pszItemName) + strlen(pszItemContent));if (len > (sizeof(szBuf) - 0x10)){break;}sprintf(szBuf, "%s = %s\n", pszItemName, pszItemContent);if (NULL == m_pMyOsslMem){break;}assert(NULL != m_pMyOsslMem);if (!m_pMyOsslMem->append_to_bio((uint8_t*)szBuf, (int)strlen(szBuf))){break;}b_rc = true;} while (false);return b_rc;
}bool CMyOsslConfig::append_to_bio(uint8_t* pBuf, int lenBuf)
{assert(NULL != m_pMyOsslMem);return m_pMyOsslMem->append_to_bio(pBuf, lenBuf);
}bool CMyOsslConfig::updateConfig()
{bool b_rc = false;int i_rc = 0;long lineSn = 0;int len1 = 0;int len2 = 0;do {if ((NULL == m_pMyOsslMem) || (NULL == m_conf)){break;}// BIO_seek(m_bio, 0);// len1 = this->bio_get_length(m_bio);assert(NULL != m_pMyOsslMem->get_bio());i_rc = NCONF_load_bio(m_conf, m_pMyOsslMem->get_bio(), &lineSn);// len2 = this->bio_get_length(m_bio);// m_bio有东西有数据长度, 但是经过NCONF_load_bio()后, m_conf里面有东西了, 但是m_bio的东西没了// 所以, 经过NCONF_load_bio后(), 就得当m_bio是空的来处理.// 如果是向m_bio中写东西, 然后写配置.// 如果要更新m_bio中的配置内容, 就必须重新写.if (i_rc <= 0){break;}b_rc = true;} while (false);return b_rc;
}char* CMyOsslConfig::get_conf_item_value(const char* group, const char* name)
{char* res = NULL;int i_rc = 0;do {if (NULL == m_conf){break;}res = NCONF_get_string(m_conf, group, name);if (NULL == res){ERR_pop_to_mark();}else {ERR_clear_last_mark();}} while (false);return res;
}

CMyOsslMem.h

//! \file CMyOsslMem.h#ifndef __C_MY_OSSL_MEM_H__
#define __C_MY_OSSL_MEM_H__#include <openssl/bio.h>
#include <openssl/conf.h> // for CONFclass CMyOsslMem
{
public:CMyOsslMem();virtual ~CMyOsslMem();bool init();void uninit();BIO* get_bio();bool append_to_bio(uint8_t* pBuf, int lenBuf);bool bio_to_buf(BIO* bio, uint8_t*& pBuf, int& lenBuf); // 执行之后,东西还在. 需要调用者释放pBufbool to_file(const TCHAR* psz_file_pathname); // for test onlybool get_bio_buffer(uint8_t*& pBuf, int& lenBuf); // 需要自己释放(OPENSSL_free)size_t get_length();size_t bio_get_length(BIO* bio);unsigned char* get_bio_data_tail(); // only for testprivate:BIO* m_bio;// only for testunsigned char* m_bio_data;int m_bio_len;
};#endif // #ifndef __C_MY_OSSL_MEM_H__

CMyOsslMem.cpp

//! \file CMyOsslMem.cpp#include "pch.h"#include "CMyOsslMem.h"
#include <string.h>
#include <openssl/err.h>
#include <cassert>#include "memOpt/my_debug_new_define.h"CMyOsslMem::CMyOsslMem():m_bio(NULL),m_bio_len(0),m_bio_data(NULL)
{}CMyOsslMem::~CMyOsslMem()
{// 必须在程序退出前,主动调用uninit(), 否则在mem_unhook()时,就会报mem leak的错
}void CMyOsslMem::uninit()
{if (NULL != m_bio){BIO_free(m_bio);m_bio = NULL;}
}bool CMyOsslMem::init()
{bool b_rc = false;do {if (NULL == m_bio){m_bio = BIO_new(BIO_s_mem());if (NULL == m_bio){assert(false);break;}}b_rc = true;} while (false);return b_rc;
}bool CMyOsslMem::append_to_bio(uint8_t* pBuf, int lenBuf)
{bool b_rc = false;int len = 0;size_t szLen = 0;int bio_len_before = 0;int bio_len_after = 0;unsigned char* pDebug = NULL;int lenDebug = 0;do {if ((NULL == pBuf) || (lenBuf <= 0)){break;}if (NULL == m_bio){assert(false);break;}// szLen = bio_get_length(m_bio);// BIO_seek(m_bio, szLen);bio_len_before = (int)bio_get_length(m_bio);len = BIO_write(m_bio, pBuf, lenBuf);bio_len_after = (int)bio_get_length(m_bio);if (len != lenBuf){break;}if (len != (bio_len_after - bio_len_before)){break;}b_rc = true;} while (false);return b_rc;}BIO* CMyOsslMem::get_bio()
{if (NULL != m_bio){// BIO_seek(m_bio, 0);}return m_bio;
}size_t CMyOsslMem::get_length()
{return bio_get_length(this->get_bio());
}size_t CMyOsslMem::bio_get_length(BIO* bio)
{size_t bio_length = 0;do {if (NULL == bio){break;}// BIO_seek(bio, 0);bio_length = BIO_ctrl_pending(bio);} while (false);return bio_length;
}unsigned char* CMyOsslMem::get_bio_data_tail()
{unsigned char* p_rc = NULL;do {if (NULL == m_bio){assert(false);break;}// 取出的指针是bio内部的buffer地址,不用释放m_bio_len = BIO_get_mem_data(m_bio, &m_bio_data);p_rc = m_bio_data + m_bio_len;} while (false);return p_rc;
}bool CMyOsslMem::to_file(const TCHAR* psz_file_pathname)
{bool b_rc = false;FILE* pf = NULL;uint8_t* pBuf = NULL;int len = 0;size_t sz_rc = 0;do {if (NULL == psz_file_pathname){break;}pf = _tfopen(psz_file_pathname, TEXT("w+b"));if (NULL == pf){break;}if (!get_bio_buffer(pBuf, len)){break;}if ((NULL == pBuf) || (len <= 0)){break;}sz_rc = fwrite(pBuf, sizeof(char), len, pf);assert(sz_rc == len);b_rc = true;} while (false);if (NULL != pf){fclose(pf);pf = NULL;}if (NULL != pBuf){OPENSSL_free(pBuf);pBuf = NULL;}return b_rc;
}bool CMyOsslMem::get_bio_buffer(uint8_t*& pBuf, int& lenBuf)
{bool b_rc = false;do {if (!bio_to_buf(get_bio(), pBuf, lenBuf)){break;}if ((NULL == pBuf) || (lenBuf <= 0)){break;}b_rc = true;} while (false);return b_rc;
}bool CMyOsslMem::bio_to_buf(BIO* bio, uint8_t*& pBuf, int& lenBuf)
{bool b_rc = false;int i_rc = 0;do {if (NULL == bio){break;}lenBuf = (int)bio_get_length(bio);pBuf = (uint8_t*)OPENSSL_malloc(lenBuf + 1);if (NULL == pBuf){break;}pBuf[lenBuf] = '\0';i_rc = BIO_read(bio, pBuf, lenBuf);BIO_seek(bio, 0); // ! 读完了, 将数据读指针恢复.b_rc = (i_rc == lenBuf);} while (false);return b_rc;
}

END

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

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

相关文章

ChatGPT研究论文提示词集合1-【主题选择与问题研究、文献综述】

点击下方▼▼▼▼链接直达AIPaperPass &#xff01; AIPaperPass - AI论文写作指导平台 目录 1.主题选择与问题定义 2.文献综述 3.书籍介绍 AIPaperPass智能论文写作平台 近期小编按照学术论文的流程&#xff0c;精心准备一套学术研究各个流程的提示词集合。总共14个步骤…

论文笔记:Large Language Model for Participatory Urban Planning

202402 arxiv 大模型城市规划 引入了一个基于LLM的多代理协作框架&#xff0c;模拟规划师和数千名具有不同特征和背景的居民&#xff0c;用于参与式城市规划——>生成考虑居民多样化需求的城市区域土地利用规划为了提高讨论的效率&#xff0c;论文采用了鱼缸讨论机制&#…

python——函数

概念 函数就是将一段具有独立功能的代码块整合到一个整体并命名&#xff0c;在需要的位置调用这个名称去完成对应的需求 定义函数 def 函数名&#xff08;参数&#xff09;&#xff1a;代码1......return 值函数名 函数名由编程人员自定义的&#xff0c;满足标识符命名规则…

建筑楼宇VR火灾扑灭救援虚拟仿真软件厂家

在传统消防安全教育方式中&#xff0c;往往存在内容枯燥、参与度低和风险大等问题&#xff0c;使得消防安全知识难以深入人心。然而&#xff0c;借助VR消防安全逃生教育系统&#xff0c;我们可以打破这一困境&#xff0c;为公众带来前所未有的学习体验。 VR消防安全逃生教育系统…

【Web】DASCTF X GFCTF 2024|四月开启第一局 题解

目录 EasySignin cool_index web1234 web4打破防了&#x1f92e;&#xff0c;应该很接近解出来了&#xff0c;感兴趣的师傅续上吧 EasySignin 先随便注册个账号登录&#xff0c;然后拿bp抓包改密码(username改成admin) 然后admin / 1234567登录 康好康的图片功能可以打SS…

OEEL图表——饼状图绘制(各国太阳能发电占比)

简介 本数据主要的目的就是进行饼状图的绘制,所使用的数据全球电力系统分布情况 数据属性 NameTypeDescriptioncountryString3-character country code corresponding to the ISO 3166-1 alpha-3 specscountry_lgStringLonger form of the country designationnameStringNam…

数据采集技术综合项目实战3(网络爬虫+数据预处理+数据可视化)附带详细步骤说明,干货满满

项目介绍及需求&#xff1a; 本项目主要是通过对b站电影弹幕进行采集并分析。1.获得弹幕高频词生成符合该电影特征、主题、角色等相关字段的词云图&#xff0c;通过词云图的方式对某部电影主题具体化。2.获取用户年内评论发布时间观生成时间的折线图&#xff0c;以便从侧面观察…

在Vue项目使用kindEditor富文本编译器以及上传图片

第一步 npm install kindeditor第二步&#xff0c;建立kindeditor.vue组件 <template><div class"kindeditor"><textarea :id"id" name"content" v-model"outContent"></textarea></div> </templa…

Android中logcat日志、ANR日志、trace日志的介绍与分析方法

Logcat日志 在Android开发中&#xff0c;日志是开发者在应用程序中添加的一种输出信息的记录方式&#xff0c;用于查看应用程序在运行时的状态、调试信息、错误信息等。Android日志可以帮助开发者追踪应用程序的行为、诊断问题并进行调试。Android提供了一系列的日志输出方法&…

使用 vllm 运行 Llama3-8b-Instruct

使用 vllm 运行 Llama3-8b-Instruct 0. 引言1. 安装 vllm2. 运行 Llama3-8b-Instruct 0. 引言 此文章主要介绍使用 vllm 运行 Llama3-8b。 1. 安装 vllm 创建虚拟环境&#xff0c; conda create -n myvllm python3.11 -y conda activate myvllm安装 Ray 和 Vllm&#xff0c…

富格林:致用查明暗箱黑幕技巧

富格林认为&#xff0c;投资现货黄金对于新手投资者来说是一个很好的选择&#xff0c;但是在进行投资之前需要了解一些基本的技巧和策略用以查明暗箱黑幕。事实上&#xff0c;现货黄金市场充满着丰富的交易机会&#xff0c;以及并存的交易风险&#xff0c;因此投资者要想在这其…

动态规划——状态压缩dp

acwing291.蒙德里安的梦想 #include<iostream> #include<cstring>using namespace std;const int N 12, M 1 << N;long long int f[N][M]; //f[i][j]表示第i列j状态有多少个可行解 int n, m; bool st[M]; //st[i]表示合并列的状态i是否合法int main() {wh…

10分钟快速部署 Doris 集群

部署 Doris 集群通常涉及多个步骤&#xff0c;包括下载和解压软件包、配置 FE 和 BE 节点以及启动服务。下面是在三台 Linux 服务器上部署一个包含三个 FE (Frontend) 和三个 BE (Backend) 的 Doris 集群的详细步骤&#xff1a; 服务器ip&#xff1a;192.168.0.103、192.168.0.…

02_补充)docker学习 docker在线安装tomcat等,docker容器常用指令

01中安装好docker后&#xff0c;本章学习使用docker拉取tomcat&#xff0c;nginx&#xff0c;jdk等镜像&#xff0c;并创建容器启动测试 常用命令 # 基本信息查看 docker version # 查看docker的版本号&#xff0c;包括客户端、服务端、依赖的Go等 docker info # 查看系统(…

【vue3】插件@tsparticles/vue3、tsparticles实现粒子特效

文章目录 一、安装依赖二、全局引入三、使用 一、安装依赖 npm i tsparticles/vue3 npm i tsparticles二、全局引入 // main.js import Particles from tsparticles/vue3 import { loadFull } from tsparticlesconst app createApp(App) app.use(Particles, {init: async (e…

java泛型知多少

Java 泛型了解么&#xff1f; 泛型是一种在编译时提供类型安全检查的机制,可以增加我们代码的可读性和安全性。泛型可以在编译时期对泛型参数进行校验来指定选入对象的类型&#xff0c;比如 ArrayList<Person> persons new ArrayList<Person>() 这行代码就指明了…

【设计模式】6、bridge 桥接模式

六、bridge 桥接模式 桥接设计模式 复杂逻辑拆分&#xff1a; 如果需要完成复杂的逻辑&#xff0c;可以把复杂逻辑设计为接口&#xff0c;内部再拆分为若干接口。这样复杂的逻辑会被拆分开&#xff0c;方便解耦。 例如&#xff1a;如果遥控器控制设备。可以遥控器设计为复杂…

销售经理(多继承/虚基类)

根据下图类之间的继承关系&#xff0c;以及main和输出定义&#xff0c;定义Staff类、Saleman类、Manager类和SaleManager类。 Staff类包含的数据成员有编号&#xff08;num)&#xff0c;姓名&#xff08;name)&#xff0c;基本工资&#xff08;basicSale&#xff09;。Saleman类…

pytorch中模型训练的学习率动态调整

pytorch动态调整学习率 背景手动设置自动衰减的学习率pytorch中的torch.optim.lr_schedulertorch.optim.lr_scheduler.ExponentialLRtorch.optim.lr_scheduler.StepLRtorch.optim.lr_scheduler.MultiStepLRtorch.optim.lr_scheduler.ReduceLROnPlateau 背景 在神经网络模型的训…

项目实践:贪吃蛇

引言 贪吃蛇作为一项经典的游戏&#xff0c;想必大家应该玩过。贪吃蛇所涉及的知识也不是很难&#xff0c;涉及到一些C语言函数、枚举、结构体、动态内存管理、预处理指令、链表、Win32 API等。这里我会介绍贪吃蛇的一些思路。以及源代码也会给大家放到文章末尾。 我们最终的…