protobuf学习日记 | 认识protobuf中的类型

目录

前言

一、标量数据类型

二、protobuf中的 “数组” 

三、特殊类型

1、枚举类型

(1)类型讲解 

(2)升级通讯录

2、Any类型

(1)类型讲解 

(2)升级通讯录

3、oneof类型

(1)类型讲解

(2)升级通讯录

4、map类型

(1)类型讲解

(2)升级通讯录


前言

        本文为protobuf系列的第二期,本文主要介绍protobuf中的数据类型,初步认识这些数据类型后,在来通过这些类型来不断完善我们通讯录的小项目;

一、标量数据类型

        protobuf中将类型分为标量数据类型与特殊类型(枚举等),下面为常见的标量数据类型,与在C++中对应类型;

.proto typeNotesC++ type
doubledouble
floatfloat
int32使用变长编码。负数的编码效率较低,若字段可能为负值,应用 sint32 代替。int32
int64使用变长编码。负数的编码效率较低,若字段可能为负值,应用 sint64 代替。int64
uint32使用变长编码。uint32
uint64使用变长编码。uint64
sint32使用变长编码。符号整型。负值的编码效率高于常规的 int32 类型。int32
sint64使用变长编码。符号整型。负值的编码效率高于常规的 int64 类型。int64
fixed32定长4字节。若值常大于 2^28 则会比 uint32 更高效。uint32
fixed64定长8字节。若值常大于 2^56 则会比 uint64 更高效。uint64
sfixed32定长4字节。int32
sfixed64定长8字节。int64
boolbool
string包含 UTF-8 和 ASCII 编码的字符串,长度不能超过 2^32 。string
bytes可包含任意的字节序列但长度不能超过 2^32 。string

        上述资料来自 protobuf 官网。如下图所示;protobuf官网

        这里对上述表格内容进行解释补充;

1、所谓变长编码指的是protobuf在序列化中,不是按照固定字节进行序列化,而采用根据具体数据变化而不定长的编码。

2、定长编码则与变长为相反概念,为固定字节编码。

二、protobuf中的 “数组” 

        假若我们想让message中字段中有数组类型的字段我们应该如何处理呢?实际上,这里是通过字段规则来控制的,我们通过给字段声明额外属性来达到数组的效果;

singular:消息中包含该字段零次或一次。默认使用该字段。

repeated:消息中可以包含该字段任意多次(包括零次),其中重复值的顺序会被保留。可以理解为定义了⼀个数组。

        我们继续完善通讯录小项目(没看上篇文章的不要急,这里仍可以看懂),我们分析一下我们PeopleInfo结构中所需字段,我们需要一个联系人姓名,一个联系人年龄,接着我们需要联系人电话,此时我们会发现联系人电话可能不止一个,所以我们应该将电话号码设置成一个类似数组的字段,这里由于后面每个电话可能还有电话类型,如座机、移动电话等,所以这里我们将电话单独放在一个message中;具体代码如下;

syntax="proto3";
package contacts;
// 非嵌套
message Phone
{
    string phone_num = 1;
}

message PeopleInfo
{
    string name = 1;  // 姓名
    int32 age = 2;  // 年龄

    // 可嵌套
    // message Phone
    // {
    //     string PhoneNum = 1;
    // }

    repeated Phone phones = 3; // 电话
}

        注意上述中,我们展示message可以进行嵌套,甚至可以写在别的proto文件中,我们通过import 导入那个文件就可以使用了,这里就不展示了;

        我们通过protoc编译器编译这个proto文件,如下所示;

        我们查看.h文件来看一看proto帮我们生成序列化、反序列化相关方法;如下所示;

        由于环境原因,我的机器上会显示报错,但实际上没有问题的,如上图,我们可以找到一个contacts的命名空间,这个命名空间就是我们在proto文件通过package生成的命名空间;

        仔细翻阅,我们就能找到两个类,一个是Phone。一个是PeopleInfo,这两个就是由我们在proto文件中的message通过proto编译器编译来的;一般来说,都会给每个message中每个字段生成一个查询字段的接口,和一个设置字段的接口,查询字段以字段名为接口名,设置字段前带前缀set;我们继续阅读代码;

 

        上面为message中三个普通字段的相关接口,我们仔细想一下,其中phone_num为嵌套message中的字段,这个嵌套message是一个数组,那么这个数组又如何的接口呢?实际上,我们也可以找到,只不过我把他单独拎出来了,如下图;

        我单独标出来了四个接口,其中第一个接口就是数组中元素个数;第二个接口是我们传入数组的一个下标,该接口返回该下标下的地址,我们这个地址我们再仔细观察发现就是Phone*的,也就是这个数组存储数据的类型,我们通过这个地址可更改对应下标下元素的值;第三个接口是查询指定下标下的元素值;第四个接口与第二个接口类似,我们不关心将值加入哪个下标下,我们就指向增加要给元素,此时调用该接口返回一个地址,我们将数据放在这个地址下即可;

        基于上面的proto代码,我们现在想实现一个写程序和一个读程序,读程序要求我们从用户输入中获取联系人的姓名,年龄。电话号等信息;然后将该信息通过protobuf进行序列化,存入文件中;我们的读程序则是从文件中读取程序,然后打印出来;代码如下;

        首先更新proto文件,我们引入一个message叫做contact,该message中只有一个字段,就是PeopleInfo,如下所示(记得重新编译proto文件哦);

// contacts.proto文件
syntax="proto3";
package contacts;
// 非嵌套
message Phone
{
    string phone_num = 1;
}

message PeopleInfo
{
    string name = 1;  // 姓名
    int32 age = 2;  // 年龄

    repeated Phone phones = 3; // 电话
}

message Contact
{
    PeopleInfo people = 1;
}

        接着我们编写write程序,如下所示;

// write.cc文件
#include <iostream>
#include <string>
#include <fstream>
#include "contacts.pb.h"

#define FILE_NAME "./contact.bin"

using namespace std;

void AddPeopleInfo(contacts::Contact& con);

int main()
{
    contacts::Contact con;
    // 1、获取文件中联系人相关信息,若有读进contact中
    fstream input(FILE_NAME, ios::in | ios::binary);
    if(!input)
    {
        input.close();
        cout << "文件不存在, 已重新创建新文件!" << endl;
    }
    else if(!con.ParseFromIstream(&input))
    {
        input.close();
        cout << "反序列化失败, 程序退出!" << endl;
        exit(1);
    }
    input.close();

    // 2、向通讯录中添加联系人
    
    AddPeopleInfo(con);

    // 3、将通讯录重新写入文件中
    fstream output(FILE_NAME, ios::out | ios::trunc | ios::binary);
    if(!output)
    {
        output.close();
        cout << "写文件时,文件打开失败, 程序退出!" << endl;
        exit(2);
    }
    else if(!con.SerializeToOstream(&output))
    {
        output.close();
        cout << "序列化失败, 程序退出!" << endl;
        exit(3);
    }
    cout << "写入成功!" << endl;
    output.close();
    return 0;
}

void AddPeopleInfo(contacts::Contact& con)
{
    cout << "--------------- 添加联系人 ---------------" << endl;
    // 新增一个联系人
    contacts::PeopleInfo* people = con.add_people();
    // 从用户输入中获取联系人姓名
    string name;
    cout << "请输入联系人姓名# ";
    getline(cin, name);
    // 设置姓名
    people->set_name(name);
    // 获取联系人年龄
    cout << "请输入联系人年龄# ";
    int age;
    cin >> age;
    cin.ignore(256, '\n'); // 清空输入缓冲区中换行
    // 设置年龄
    people->set_age(age);
    // 获取联系人号码
    for(int i = 1; ; i++)
    {
        string phone_num_str;
        cout << "联系人手机号" << i << "(只输入回车表示完成电话新增)# ";
        getline(cin, phone_num_str);
        if(phone_num_str.empty()) break;
        // 新增一个联系人号码类
        contacts::Phone* phone = people->add_phones();
        // 设置联系人号码类中的号码
        phone->set_phone_num(phone_num_str);
    }
}

        我们运行该程序,并输入数据,此时会生成一个二进制文件,其中存放的就是我们序列化后的结果;如下图所示;

        我们看不懂这个二进制序列,但是我们可以通过protoc编译器给我们提供的一个命令来解析这个二进制文件,具体用法如下图所示;

        补充一下,图上的第一个参数为我们要按照哪个message的格式进行解析,因此这个参数为指定的message,第二个参数为指定message所在文件;由于decode指令默认从标准输入获取数据,所以这里进行重定向,让这条指令从指定文件读取数据;最后结果与我们输入结果一模一样,接下来我们可以再编写一个read程序完成反序列化的工作,我们通过这个read程序进一步来验证write程序是否正确;

// read.cc文件
#include <iostream>
#include <string>
#include <fstream>
#include "contacts.pb.h"

#define FILE_NAME "./contact.bin"

using namespace std;

void PrintContacts(contacts::Contact& con);

int main()
{
    contacts::Contact con;
    // 1、 读取文件中数据
    fstream input(FILE_NAME, ios::in | ios::binary);
    if(!input)
    {
        input.close();
        cout << "文件不存在, 已重新创建新文件!" << endl;
    }
    else if(!con.ParseFromIstream(&input))
    {
        input.close();
        cout << "反序列化失败, 程序退出!" << endl;
        exit(1);
    }
    input.close();
    // 2、打印contact内容
    PrintContacts(con);
    return 0;
}

void PrintContacts(contacts::Contact& con)
{
    for(int i = 0; i < con.people_size(); i++)
    {
        cout << "------------------- 联系人" << i + 1 << " -------------------" << endl;
        // 获取联系人信息
        contacts::PeopleInfo* people = con.mutable_people(i);
        cout << "联系人姓名: " << people->name() << endl;
        cout << "联系人年龄: " << people->age() << endl;
        for(int j = 0; j < people->phones_size(); j++)
        {
            const contacts::Phone& phone = people->phones(j);
            cout << "联系人号码" << j + 1 << ": " << phone.phone_num() << endl;
        }
    }
}

        为了方便这两个文件的编译,我们编写了一份makefile文件,如下所示;

# makefile文件
.PHONY:all
all:read write

read:read.cc contacts.pb.cc
	g++ -o $@ $^ -std=c++11 -lprotobuf
write:write.cc contacts.pb.cc
	g++ -o $@ $^ -std=c++11 -lprotobuf
.PHONY:clean
clean:
	rm -f write read

        我们运行read程序,结果如下所示;

        为了进一步验证我们再次调用write,接着再调用read,如下图所示;

        两个联系人都打印出来了,程序完成正确;

三、特殊类型

1、枚举类型

(1)类型讲解 

        protobuf为我们提供了一种与C语言C++类似的枚举类型;用法与C语言的枚举类型类似;如下所示;

syntax="proto3";

enum PhoneType
{
    MP = 0;    // 分号分割
    TEL = 1;
}

注意:

1、第一个枚举值必须从0开始!

2、每个枚举值之间用分号分隔开

3、对于一个枚举字段来说,默认的枚举值为0

4、同级(同层)的枚举类型,各个枚举类型中的常量不能重名。(下面举例理解)

        上图中两个枚举类型的类型名不同,但是都一个MP,这种情况也属于同层;或者说同一个作用域的两个文件中,也不允许;比如我们再创建一个文件,文件中也有枚举值MP,当我们import这个文件后,同样也会编译报错;

(2)升级通讯录

        首先,我们给通讯录的每个号码增加一个号码类型,如移动电话或者固定电话;所以我们先更改proto文件,如下所示;

syntax="proto3";
package contacts;

enum PhoneType
{
    MP = 0;   // 移动电话
    TEL = 1;  // 固定电话
}

// 非嵌套
message Phone
{
    string phone_num = 1;   // 手机号
    PhoneType phone_type = 2;   // 手机号类型
}

message PeopleInfo
{
    string name = 1;  // 姓名
    int32 age = 2;  // 年龄

    repeated Phone phones = 3; // 电话
}

message Contact
{
    repeated PeopleInfo people = 1;
}

        我们再次给我们的通讯录代码的write程序和read程序进行修改,write程序增加让用户输入电话类型,read程序也会相应打印出对应程序;

// write.cc文件
#include <iostream>
#include <string>
#include <fstream>
#include "contacts.pb.h"

#define FILE_NAME "./contact.bin"

using namespace std;

void AddPeopleInfo(contacts::Contact& con);

int main()
{
    contacts::Contact con;
    // 1、获取文件中联系人相关信息,若有读进contact中
    fstream input(FILE_NAME, ios::in | ios::binary);
    if(!input)
    {
        input.close();
        cout << "文件不存在, 已重新创建新文件!" << endl;
    }
    else if(!con.ParseFromIstream(&input))
    {
        input.close();
        cout << "反序列化失败, 程序退出!" << endl;
        exit(1);
    }
    input.close();

    // 2、向通讯录中添加联系人
    
    AddPeopleInfo(con);

    // 3、将通讯录重新写入文件中
    fstream output(FILE_NAME, ios::out | ios::trunc | ios::binary);
    if(!output)
    {
        output.close();
        cout << "写文件时,文件打开失败, 程序退出!" << endl;
        exit(2);
    }
    else if(!con.SerializeToOstream(&output))
    {
        output.close();
        cout << "序列化失败, 程序退出!" << endl;
        exit(3);
    }
    cout << "写入成功!" << endl;
    output.close();
    return 0;
}

void AddPeopleInfo(contacts::Contact& con)
{
    cout << "--------------- 添加联系人 ---------------" << endl;
    // 新增一个联系人
    contacts::PeopleInfo* people = con.add_people();
    // 从用户输入中获取联系人姓名
    string name;
    cout << "请输入联系人姓名# ";
    getline(cin, name);
    // 设置姓名
    people->set_name(name);
    // 获取联系人年龄
    cout << "请输入联系人年龄# ";
    int age;
    cin >> age;
    cin.ignore(256, '\n'); // 清空输入缓冲区中换行
    // 设置年龄
    people->set_age(age);
    // 获取联系人号码
    for(int i = 1; ; i++)
    {
        // 获取用户输入联系人电话
        string phone_num_str;
        cout << "联系人手机号" << i << "(只输入回车表示完成电话新增)# ";
        getline(cin, phone_num_str);
        if(phone_num_str.empty()) break;
        // 新增一个联系人号码类
        contacts::Phone* phone = people->add_phones();
        // 设置联系人号码类中的号码
        phone->set_phone_num(phone_num_str);
        // 获取用户输入电话类型
        cout << "该电话类型(1.移动电话 2.固定电话): ";
        int type = 0;
        cin >> type;
        cin.ignore(256, '\n');
        // 设置电话类型
        switch (type)
        {
        case 1:
            phone->set_phone_type(contacts::PhoneType::MP);
            break;
        case 2:
            phone->set_phone_type(contacts::PhoneType::TEL);
            break;
        default:
            break;
        }
    }
}
// read.cc文件
#include <iostream>
#include <string>
#include <fstream>
#include "contacts.pb.h"

#define FILE_NAME "./contact.bin"

using namespace std;

void PrintContacts(contacts::Contact& con);

int main()
{
    contacts::Contact con;
    // 1、 读取文件中数据
    fstream input(FILE_NAME, ios::in | ios::binary);
    if(!input)
    {
        input.close();
        cout << "文件不存在, 已重新创建新文件!" << endl;
    }
    else if(!con.ParseFromIstream(&input))
    {
        input.close();
        cout << "反序列化失败, 程序退出!" << endl;
        exit(1);
    }
    input.close();
    // 2、打印contact内容
    PrintContacts(con);
    return 0;
}

void PrintContacts(contacts::Contact& con)
{
    for(int i = 0; i < con.people_size(); i++)
    {
        cout << "------------------- 联系人" << i + 1 << " -------------------" << endl;
        // 获取联系人信息
        contacts::PeopleInfo* people = con.mutable_people(i);
        cout << "联系人姓名: " << people->name() << endl;
        cout << "联系人年龄: " << people->age() << endl;
        for(int j = 0; j < people->phones_size(); j++)
        {
            // 获取电话号码信息
            const contacts::Phone& phone = people->phones(j);
            cout << "联系人号码" << j + 1 << ": " << phone.phone_num();
            // 获取电话类型信息
            cout << "(" << contacts::PhoneType_Name(phone.phone_type()) << ")" << endl;
        }
    }
}

        这里补充一个接口,接口名是枚举名+Name,也就是下面的PhoneType_Name,这个接口可以将枚举名转换成字段名;如下所示;

        具体运送结果如下所示;

        我们可以看到前面张三、李四我们并未为其增加电话类型,可以还是有默认值MP;

2、Any类型

(1)类型讲解 

        字段还可以声明为 Any 类型,可以理解为泛型类型。使⽤时可以在 Any 中存储任意消息类型。Any 类型的字段也⽤ repeated 来修饰。

        Any 类型是 google 已经帮我们定义好的类型,在安装 ProtoBuf 时,其中的 include ⽬录下查找所有google 已经定义好的 .proto ⽂件。

        接下来我们通过这个字段继续完善我们的通讯录,我们给我们的通讯录增加一个地址字段,我们设置两个地址,一个是家庭地址,一个是单位地址,我们将这两个地址放到同一个message中,接着我们不想像存储手机号一样存储,而是在PeopleInfo中定义一个Any类型;

(2)升级通讯录

        我们根据上述重新再次更新我们的proto文件;如下所示;

syntax="proto3";
package contacts;

// 使用any前必须引入文件
import "google/protobuf/any.proto";

message Address
{
    string home_address = 1;
    string unit_address = 2;
}

enum PhoneType
{
    MP = 0;   // 移动电话
    TEL = 1;  // 固定电话
}

// 非嵌套
message Phone
{
    string phone_num = 1;   // 手机号
    PhoneType phone_type = 2;   // 手机号类型
}

message PeopleInfo
{
    string name = 1;  // 姓名
    int32 age = 2;  // 年龄
    repeated Phone phones = 3; // 电话
    google.protobuf.Any data = 4; // 地址
}

message Contact
{
    repeated PeopleInfo people = 1;
}

        接着我们依次升级write程序与read程序文件;

// write.cc文件
    // 创建地址类
    contacts::Address address;
    string home_address;
    cout << "联系人家庭地址: ";
    getline(cin, home_address);
    // 设置地址类中的家庭地址
    address.set_home_address(home_address);
    string unit_address;
    cout << "联系人单位地址: ";
    getline(cin, unit_address);
    // 设置地址类中的单位地址
    address.set_unit_address(unit_address);
    // 将Address类型转换成Any类型
    people->mutable_data()->PackFrom(address); 

// read.cc文件
    // 判断Any字段是否被设置以及判断Any字段中类型是否为Address
    if(people->has_data() && people->data().Is<contacts::Address>())
    {
        contacts::Address address;
        // 将Any类型转换成Address类型
        people->data().UnpackTo(&address);
        if(!address.home_address().empty())
        {
            cout << "联系人家庭地址: " << address.home_address() << endl;
        }
        if(!address.unit_address().empty())
        {
            cout << "联系人单位地址: " << address.unit_address() << endl;
        }
    }

        这里涉及三个新接口,具体作用如下;

PackFrom: 可以将任意消息类型转为 Any 类型。

UnpackTo:将 Any 类型转回之前设置的任意消息类型。

Is:判断存放的消息类型是否为 typename T。

        测试运行结果如下;

3、oneof类型

(1)类型讲解

        如果消息中有很多可选字段,并且将来同时只有⼀个字段会被设置,那么就可以使⽤ oneof 加强这个⾏为,也能有节约内存的效果。

        简单来说,就是类似于C语言中的联合体,多个字段只有一个字段生效;

(2)升级通讯录

        我们给通讯录增加一个其他联系方式,如QQ或这微信,我们规定这两者之间只能选一个;我们根据这个重写proto文件,如下所示;

syntax="proto3";
package contacts;

// 使用any前必须引入文件
import "google/protobuf/any.proto";

message Address
{
    string home_address = 1;
    string unit_address = 2;
}

enum PhoneType
{
    MP = 0;   // 移动电话
    TEL = 1;  // 固定电话
}

// 非嵌套
message Phone
{
    string phone_num = 1;   // 手机号
    PhoneType phone_type = 2;   // 手机号类型
}

message PeopleInfo
{
    string name = 1;  // 姓名
    int32 age = 2;  // 年龄
    repeated Phone phones = 3; // 电话
    google.protobuf.Any data = 4; // 地址
    oneof other_contact
    {
        string qq = 5;
        string wechat = 6;
    }
}

message Contact
{
    repeated PeopleInfo people = 1;
}

        oneof字段内设置的字段属于外层message字段编号范围,故注意冲突问题;我们再次修改write与read程序代码,如下所示;

// write.cc文件
// 设置其他联系字段
    int other_contact = 0;
    cout << "请选择其他联系方式(1.qq 2.wechat): ";
    cin >> other_contact;
    cin.ignore(256, '\n');
    string qq;
    string wechat;
    switch(other_contact)
    {
    case 1:
        cout << "请输入qq号: ";
        getline(cin, qq);
        people->set_qq(qq);
        break;
    case 2:
        cout << "请输入微信号: ";
        getline(cin, wechat);
        people->set_wechat(wechat);
        break;
    default:
        cout << "选择有误, 未成功设置其他联系方式" << endl;
        break;    
    }
// read.cc文件
// 判断其他联系方式字段是否被设置
    switch(people->other_contact_case())
    {
    case contacts::PeopleInfo::OtherContactCase::kQq:
        cout << "联系人qq: " << people->qq();
        break;
    case contacts::PeopleInfo::OtherContactCase::kWechat:
        cout << "联系人微信号: " << people->wechat();
        break;
    default:
        break;
    }

        

4、map类型

(1)类型讲解

        map类型就是与我们C++中map类似,就是建立一种映射;其格式为:

map<key_type, value_type> map_field = N;

        我们在使用时,仍有以下几点需要特别注意;

  • key_type是除了 float bytes 以外的任意 标量 数据类型。value_type可以是任意类型。
  • map字段不可以用 repeated 来修饰。
  • map中存入的数据是 无序 的。

(2)升级通讯录

        我们给我们的通讯录最后添加一个备注信息,我们希望这个备注信息呈现一种键值对的情况;我们首先更新我们的proto文件;如下所示;

// contacts.proto文件
syntax="proto3";
package contacts;

// 使用any前必须引入文件
import "google/protobuf/any.proto";

message Address
{
    string home_address = 1;
    string unit_address = 2;
}

enum PhoneType
{
    MP = 0;   // 移动电话
    TEL = 1;  // 固定电话
}

// 非嵌套
message Phone
{
    string phone_num = 1;   // 手机号
    PhoneType phone_type = 2;   // 手机号类型
}

message PeopleInfo
{
    string name = 1;  // 姓名
    int32 age = 2;  // 年龄
    repeated Phone phones = 3; // 电话
    google.protobuf.Any data = 4; // 地址
    // 其他联系方式
    oneof other_contact
    {
        string qq = 5;
        string wechat = 6;
    }
    // 备注
    map<string, string> remarks = 7;
}

message Contact
{
    repeated PeopleInfo people = 1;
}

        接着更新read和write程序,如下所示;

// write.cc文件
    // 设置备注字段
    for(int i = 0; ; i++)
    {
        string key;
        cout << "请输入备注" << i + 1 << "标题(只输入回车表示完成备注): ";
        getline(cin, key);
        if(key.empty()) break;
        string value;
        cout << "请输入备注" << i + 1 << "内容: ";
        getline(cin, value);
        people->mutable_remarks()->insert({key, value});
    }
// read.cc文件
// 设置备注字段
for(int i = 0; ; i++)
{
    string key;
    cout << "请输入备注" << i + 1 << "标题(只输入回车表示完成备注): ";
    getline(cin, key);
    if(key.empty()) break;
    string value;
    cout << "请输入备注" << i + 1 << "内容: ";
    getline(cin, value);
    people->mutable_remarks()->insert({key, value});
}

        其中我们需要主要的是对于map来说,mutable系列接口的返回值就是C++map类型的指针;如下所示;

        运行测试结果也在下面贴出;

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

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

相关文章

【动态规划】【二分查找】【C++算法】730. 统计不同回文子序列

作者推荐 【动态规划】【数学】【C算法】18赛车 涉及知识点 动态规划 二分查找 LeetCode730. 统计不同回文子序列 给你一个字符串 s &#xff0c;返回 s 中不同的非空回文子序列个数 。由于答案可能很大&#xff0c;请返回对 109 7 取余 的结果。 字符串的子序列可以经由…

ubuntu源码安装MySQL

mysql下载路径 创建新数组 mysql sudo groupadd mysql# 创建用户 mysql ,指定属组为 mysql&#xff0c;禁止其登录 # --no-create-home选项&#xff0c;创建用户时不会自动创建主目录 sudo adduser --system --no-create-home --ingroup mysql --shell /sbin/nologin mysql创…

第二证券:大逆转!A股强势反弹,多家机构看好后市

周四&#xff0c;A股强势反弹&#xff0c;沪指2800点合浦还珠&#xff0c;两市成交量达8767亿元&#xff0c;较周三大幅增加2000多亿元。沪深300ETF大幅放量&#xff0c;华泰柏瑞沪深300ETF、嘉实沪深300ETF、易方达沪深300ETF和华夏沪深300ETF等4只沪深300ETF算计成交额超311亿…

常用中间件漏洞

IIS6 IIS7 安装 控制面板-----打开关闭windows功能 添加角色-----添加IIS 启动之后访问localhost 复现 服务器换成IIS7 访问报错 大概就是缺少CGI模块 问题解决 添加php-cgi的路径 添加脚本映射 修改php.ini文件 将 cgi.fix_pathinfo1 然后设置一个图片 访问 在后缀加上/.…

游戏开发要注意这几个问题

游戏开发是一个充满创意和挑战的过程。对于初学者和经验丰富的开发者来说&#xff0c;每个项目都是一个新的学习机会。然而&#xff0c;成功的游戏开发不仅仅是关于编码和设计&#xff1b;它还涉及到细致的规划、测试和市场洞察。以下是在开发游戏时需要特别注意的几个关键方面…

阿里云容器服务助力万兴科技 AIGC 应用加速

作者&#xff1a;子白&#xff08;顾静&#xff09; 2023 年堪称是 AIGC 元年&#xff0c;文生图领域诞生了 Stable Diffusion 项目&#xff0c;文生文领域诞生了 GPT 家族。一时间风起云涌&#xff0c;国内外许多企业投身 AIGC 创新浪潮&#xff0c;各大云厂商紧随其后纷纷推…

ELK 分离式日志

目录 一.ELK组件 ElasticSearch&#xff1a; Kiabana&#xff1a; Logstash&#xff1a; 可以添加的其它组件&#xff1a; ELK 的工作原理&#xff1a; 二.部署ELK 节点都设置Java环境: 每台都可以部署 Elasticsearch 软件&#xff1a; 修改elasticsearch主配置文件&…

Vue以弹窗形式实现导入功能

目录 前言正文 前言 由于个人工作原因&#xff0c;偏全栈&#xff0c;对于前端的总结还有些初出茅庐&#xff0c;后续会进行规整化的总结 对应的前端框架由&#xff1a;【vue】avue-crud表单属性配置&#xff08;表格以及列&#xff09; 最终实现的表单样式如下&#xff1a;…

VSCode 插件推荐

前言 关于开发用的插件就不做赘述了&#xff0c;网上面有很多文章都做了推荐&#xff0c;本文推荐几个好看的插件。 文件图标主题 Vscode icons Material Icon Theme 字体主题 推荐 One Dark Pro 其他 推荐一个生成好看代码的网址 https://carbon.now.sh/

策略模式在工作中的运用

前言 在不同的场景下&#xff0c;执行不同的业务逻辑&#xff0c;在日常工作中是很寻常的事情。比如&#xff0c;订阅系统。在收到阿里云的回调事件、与收到AWS的回调事件&#xff0c;无论是收到的参数&#xff0c;还是执行的逻辑都可能是不同的。为了避免&#xff0c;每次新增…

如何选购一款质量好超声波清洗机呢?质量好超声波清洗机排行榜

想要选择到一款好用的超声波清洗机还是要多做功课&#xff01;现在市面上超声波清洗机品牌可见是非常多的&#xff0c;质量也是参差不齐&#xff0c;大家在选购的时候需要多看参数再下手也不迟的&#xff01;现在大多数的上班族&#xff0c;面临的都是早九晚六的工作&#xff0…

LeetCode 算法 3.无重复字符的最长子串(python版)

1.需求 #给定一个字符串 s &#xff0c;请你找出其中不含有重复字符的 最长子串 的长度。 #输入: s “pwwkew” #输出: 3 #解释: 因为无重复字符的最长子串是 “wke”&#xff0c;所以其长度为 3。 #请注意&#xff0c;你的答案必须是 子串 的长度&#xff0c;“pwke” 是一个…

Linux centos中find命令的多种用途:按照具体应用来详细说明find的用法举例

目录 一、find命令 二、find命令的语法 &#xff08;一&#xff09;语法格式 &#xff08;二&#xff09;选项 1、选项(option)介绍 2、控制符号链接的option 3、调试选项debugopts 4、优化选项 &#xff08;三&#xff09;表达式expression 1、选项options 2、测试…

Docker之nacos的安装和使用

&#x1f389;&#x1f389;欢迎来到我的CSDN主页&#xff01;&#x1f389;&#x1f389; &#x1f3c5;我是君易--鑨&#xff0c;一个在CSDN分享笔记的博主。&#x1f4da;&#x1f4da; &#x1f31f;推荐给大家我的博客专栏《Docker之Dockerfile构建镜像》。&#x1f3af;&…

python数字图像处理基础(九)——特征匹配

目录 蛮力匹配&#xff08;ORB匹配&#xff09;RANSAC算法全景图像拼接 蛮力匹配&#xff08;ORB匹配&#xff09; Brute-Force匹配非常简单&#xff0c;首先在第一幅图像中选取一个关键点然后依次与第二幅图像的每个关键点进行&#xff08;描述符&#xff09;距离测试&#x…

Android中矩阵Matrix实现平移,旋转,缩放和翻转的用法详细介绍

一&#xff0c;矩阵Matrix的数学原理 矩阵的数学原理涉及到矩阵的运算和变换&#xff0c;是高等代数学中的重要概念。在图形变换中&#xff0c;矩阵起到关键作用&#xff0c;通过矩阵的变换可以改变图形的位置、形状和大小。矩阵的运算是数值分析领域的重要问题&#xff0c;对…

GC6139——单通道5V高细分步进电机,应用于摇头机,X,Y控制,聚焦控制等产品中,可替代MS41939

GC6139是一款单通道5V低压步进电机驱动器&#xff0c;具有低噪声、低振动的特点&#xff0c;特别适用于相机的变焦或对焦系统、万向节等精密低噪声STM控制系统。该芯片为每个通道集成了64微步驱动器。带SPl接口&#xff0c;用户可以方便地调整驱动器的参数。该芯片还内置2通道L…

旅游项目day04

1. JWT有效期 封装用户登录对象&#xff0c; 在指定时间过期 2. 有些接口需要登录&#xff1f;有些不需要登录&#xff1f; 后端如何知道a需要登录&#xff0c;b不需要登录&#xff1f; 注解。 3. 目的地 一个区域下面包含多个目的地 数据库表&#xff1a; 1. 区域表 2.…

老子云支持70+格式模型转FBX/OBJ/STL/STP,一键处理无损转换!

老子云3D可视化平台是一个集合了3D编辑器、单模型轻量化、倾斜摄影轻量化、格式转换等一站式3D开发功能的强大技术平台。无论您是设计师、工程师还是科研人员&#xff0c;都可以在这个平台上轻松实现您的创意和想法。 老子云3D可视化平台是一个集合了3D编辑器、单模型轻量化、…

电子印章软件,如何实现招投标流程无纸化?

电子印章软件的出现&#xff0c;为招投标流程的无纸化提供了强有力的支持。在招投标场景&#xff0c;使用电子印章软件&#xff0c;实现无纸化流程&#xff0c;不仅能够提高工作效率&#xff0c;还能减少打印邮寄成本和环境污染。 微签作为电子印章软件中的佼佼者&#xff0c;…