协议定制 + Json序列化反序列化

news2025/1/8 4:39:28

文章目录

  • 协议定制 + Json序列化反序列化
    • 1. 再谈 "协议"
      • 1.1 结构化数据
      • 1.2 序列化和反序列化
    • 2. 网络版计算器
      • 2.1 服务端
      • 2.2 协议定制
        • (1) 网络发送和读取的正确理解
        • (2) 协议定制的问题
      • 2.3 客户端
      • 2.4 代码
    • 3. Json实现序列化反序列化
      • 3.1 简单介绍
      • 3.2 使用

协议定制 + Json序列化反序列化

1. 再谈 “协议”

1.1 结构化数据

协议是一种 “约定”,socket api的接口, 在读写数据时,都是按 “字符串” 的方式来发送接收的。如果我们要传输一些"结构化的数据" 怎么办呢?

结构化数据:

比如我们在QQ聊天时,并不是单纯地只发送了消息本身,是把自己的头像、昵称、发送时间、消息本身一起发送给别人,这种一起发送的就是结构化数据。

1.2 序列化和反序列化

  • 序列化:就是将对象转化成字节序列的过程。便于在传递和保存对象时保证对象的完整性和可传递性同时对象转换为有序字节流,以便在网络上传输或者保存在本地文件中。
  • 反序列化:就是将字节序列转化成对象的过程。便于根据字节流中保存的对象状态及描述信息,通过反序列化重建对象。

在这里插入图片描述

  • 在socket编程的基础上,我们发现在实际生活中网络通信并不是单单发送一条消息本身,它包含了很多其他类型的数据,所以我们引入了结构化数据的概念,将这些各种类型的数据都定义在一个结构体中形成结构化数据方便被上层设置与读取。

  • 发送数据时将这个结构体按照一个规则转换成字符串,接收到数据的时候再按照相同的规则把字符串转化回结构体;实现序列化和反序列化方便网络通信。

  • 那么发送方发送的结构化数据序列化成字符串,接收方收到后是怎么知道反序列化成结构化数据呢?这是因为两者间存在定址好的协议。所以协议的本质就是双方约定好的某种格式的数据,常见的就是用结构体或者类来表达。

2. 网络版计算器

我们需要实现一个服务器版的计算器,客户端把要计算的两个数和计算类型发过去, 然后由服务器进行计算, 最后再把结果返回给客户端。

2.1 服务端

服务端创建步骤:

  1. 调用socket,创建套接字
  2. 调用bind,绑定端口
  3. 调用listen,将套接字状态设置为监听
  4. 调用accept,获取新连接
  5. 处理读取与写入的问题(重点)

2.2 协议定制

(1) 网络发送和读取的正确理解

在这里插入图片描述

客户端和服务器通信时,会调用read和write函数,它们是把数据直接发送到对端吗?不是

  • TCP协议有自己的发送缓冲区和接收缓冲区
  • 调用write本质:把用户所对应的数据拷贝到TCP的发送缓冲区
  • 调用read本质:把数据从接收缓冲区拷贝到用户层
  • 所以read和write的本质是拷贝函数
  • 把数据拷贝到TCP发送缓冲区后,剩下的数据怎么发,是由TCP决定的,所以TCP又叫做传输控制协议
  • 因为发送和接收是成对的,可以同时进行的,所以TCP协议是全双工的

综上:

  • TCP通信的本质是自己发送缓冲区的数据经过网络拷贝到对方的接收缓冲区中
  • 网络通信的本质也是拷贝

(2) 协议定制的问题

在定制协议之前先解决一个问题,之前在使用TCP协议时我们只是简单的读取,没有考虑TCP是面向字节流的,读取数据不完整的问题。这里同样存在相同的问题,如果一下子对方发送了很多报文,这些报文都堆积在TCP的接收缓冲区中,你怎么保证自己读到的是一个完整的报文呢?

我们采用这样的方式:

  • 对报文定长
  • 使用特殊符号(在报文与报文之间增加特殊符号)
  • 自描述方式(自己设计协议)

协议设计格式:

在这里插入图片描述

Protocol.hpp

#include<string>
#include<iostream>
#include<vector>
#include<cstring>
#include<sys/types.h>
#include<sys/socket.h>
#include"Util.hpp"
using namespace std;

// 给网络版本计算器定制协议
namespace Protocol_ns
{

    #define SEP " "
    #define SEP_LEN strlen(SEP)   // 绝对不能写成sizeof
    #define HEADER_SEP "\r\n"
    #define HEADER_SEP_LEN strlen("\r\n")

    // "长度"\r\n" "_x op _y"\r\n
    // "10 + 20" => "7"r\n""10 + 20"\r\n => 报头 + 有效载荷
    // 请求/响应 = 报头\r\n有效载荷\r\n
    // 请求 = 报头\r\n有效载荷\r\n报头\r\n有效载荷\r\n报头\r\n有效载荷\r\n

    // "10 + 20" => "7"r\n""10 + 20"\r\n
    string AddHeader(string&str)
    {
        cout<<"AddHeader 之前:\n"
            <<str<<endl;

        string s=to_string(str.size());
        s+=HEADER_SEP;
        s+=str;
        s+=HEADER_SEP;

        cout<<"AddHeader 之后:\n"
            <<s<<endl;

        return s;
    }

    // "7"r\n""10 + 20"\r\n => "10 + 20" 
    string RemoveHeader(const string&str,int len)
    {
        cout<<"RemoveHeader 之前:\n"
            <<str<<endl;

        // 从后面开始截取
        string res=str.substr(str.size()-HEADER_SEP_LEN-len,len); 

        cout<<"RemoveHeader 之后:\n"
            <<res<<endl;   

        return res;
    }

    int Readpackage(int sock,string&inbuffer,string*package)
    {
        cout<<"ReadPackage inbuffer 之前:\n"
            <<inbuffer<<endl;

        // 边读取
        char buffer[1024];
        ssize_t s=recv(sock,&buffer,sizeof(buffer)-1,0);
        if(s<=0)
            return -1;

        buffer[s]=0;
        inbuffer+=buffer;

        cout<<"ReadPackage inbuffer 之中:\n"
            <<inbuffer<<endl;

        // 边分析,  "7"r\n""10 + 20"\r\n
        auto pos=inbuffer.find(HEADER_SEP);
        if(pos==string::npos)
            return 0;
        
        string lenStr=inbuffer.substr(0,pos);    // 获取头部字符串, 没有动inbuffer
        int len=Util::toInt(lenStr);             // 得到有效载荷的长度 => "123" -> 123
        int targetPackageLen=len+2*HEADER_SEP_LEN+lenStr.size();   // 得到整个报文长度
        if(inbuffer.size()<targetPackageLen)     // 不是一个完整的报文
            return 0;
        
        *package=inbuffer.substr(0,targetPackageLen);  // 提取到了报文有效载荷, 没有动inbuffer
        inbuffer.erase(0,targetPackageLen);      // 从inbuffer中直接移除整个报文

        cout<<"ReadPackage inbuffer 之后:\n"
            <<inbuffer<<endl;

        return len;
    }

    // Request && Response都要提供序列化和反序列化功能
    // 1. 自己手写
    // 2. 用别人的 --- json, xml, protobuf

    class Request
    {
    public:

        Request()
        {

        }

        Request(int x,int y,char op)
            :_x(x)
            ,_y(y)
            ,_op(op)
        {
        }

        // 序列化: struct->string
        bool Serialize(string* outStr)     
        {
            *outStr=""; 
            string x_string=to_string(_x);
            string y_string=to_string(_y);

            // 手动序列化
            *outStr=x_string + SEP + _op + SEP + y_string;

            std::cout << "Request Serialize:\n"
                      << *outStr << std::endl;

            return true;
        }

        // 反序列化: string->struct
        bool Deserialize(const string&inStr)   
        {
            // inStr:  10 + 20 => [0]=>10, [1]=>+, [2]=>20
            vector<string> result;
            Util::StringSplit(inStr,SEP,&result);

            if(result.size()!=3)
                return false;
            if(result[1].size()!=1)
                return false;

            _x=Util::toInt(result[0]);
            _y=Util::toInt(result[2]);
            _op=result[1][0];

            return true;
        }

        ~Request()
        {
            
        }

    public:
        // _x op _y ==> 10 * 9 ? ==> 10 / 0 ?
        int _x;
        int _y;
        char _op;
    };

    class Response
    {
    public:
        Response()
        {

        }
        
        Response(int result,int code)
            :_result(result)
            ,_code(code)
        {
            
        }

        // 序列化: struct->string
        bool Serialize(string* outStr)     
        {
            // _result _code
            *outStr=""; 
            string res_string = to_string(_result);
            string code_string = to_string(_code);

            // 手动序列化
            *outStr=res_string + SEP + code_string;

            return true;
        }

        // 反序列化: string->struct
        bool Deserialize(const string&inStr)   
        {
            // 10 0
            vector<string> result;
            Util::StringSplit(inStr,SEP,&result);

            if(result.size()!=2)
                return false;

            _result=Util::toInt(result[0]);
            _code=Util::toInt(result[1]);
            return true;
        }

        ~Response()
        {

        }

    public:
        int _result;
        int _code;   // 0 success; 1,2,3,4代表不同错误码
    };

}

Util.hpp

#pragma once

#include<iostream>
#include<string>
#include<vector>
using namespace std;

class Util
{
public:
    // 输入: const &
    // 输出: *
    // 输入输出: *
    static bool StringSplit(const string &str, const string &sep, vector<string> *result)
    {
        // 10 + 20
        size_t start = 0;

        while (start < str.size())
        {
            auto pos = str.find(sep, start);
            if (pos == string::npos)
                break;

            result->push_back(str.substr(start, pos - start));

            // 更新位置
            start = pos + sep.size();
        }

        // 处理最后的字符串
        if(start<str.size())
            result->push_back(str.substr(start));

        return true;
    }

    static int toInt(const string&s)  // 字符串转整数
    {
        return atoi(s.c_str());
    }
};

2.3 客户端

客户端创建步骤:

  1. 调用socket,创建套接字
  2. 客户端不用自己bind端口
  3. 调用connect,连接服务器
  4. 处理读取与写入的问题

2.4 代码

完整的代码:lesson36 · 遇健/Linux - 码云 - 开源中国 (gitee.com)

运行结果:

在这里插入图片描述

3. Json实现序列化反序列化

3.1 简单介绍

上面是自己定制协议实现序列化和反序列化,下面我们使用一些现成的方案来实现序列化和反序列化。C++常用的:protobuf 和 json,这里使用简单的 json。

JSON(JavaScript Object Notation) 是一种轻量级的数据交换格式。 易于人阅读和编写。同时也易于机器解析和生成。JSON采用完全独立于语言的文本格式,但是也使用了类似于C语言家族的习惯(包括C, C++, Java, JavaScript, Perl, Python等)。这些特性使JSON成为理想的数据交换语言。Json数据由键值对组成,大括号表示对象,方括号表示数组。

在这里插入图片描述

3.2 使用

  • 安装json库
yum install -y jsoncpp-devel
  • 使用json包含的头文件:
#include <jsoncpp/json/json.h>

注意makefile文件要包含Json库的名称

在这里插入图片描述

我们在使用的时候直接创建Json对象来进行序列化和反序列化

Protocol.hpp

#include<string>
#include<iostream>
#include<vector>
#include<cstring>
#include<sys/types.h>
#include<sys/socket.h>
#include"Util.hpp"
#include<jsoncpp/json/json.h>
using namespace std;

// #define MYSELF 1

// 给网络版本计算器定制协议

namespace Protocol_ns
{

    #define SEP " "
    #define SEP_LEN strlen(SEP)   // 绝对不能写成sizeof
    #define HEADER_SEP "\r\n"
    #define HEADER_SEP_LEN strlen("\r\n")

    // "长度"\r\n" "_x op _y"\r\n
    // "10 + 20" => "7"r\n""10 + 20"\r\n => 报头 + 有效载荷
    // 请求/响应 = 报头\r\n有效载荷\r\n
    // 请求 = 报头\r\n有效载荷\r\n报头\r\n有效载荷\r\n报头\r\n有效载荷\r\n

    // 未来: "长度"\r\n"协议号\r\n""_x op _y"\r\n
     

    // "10 + 20" => "7"r\n""10 + 20"\r\n
    string AddHeader(string&str)
    {
        cout<<"AddHeader 之前:\n"
            <<str<<endl;

        string s=to_string(str.size());
        s+=HEADER_SEP;
        s+=str;
        s+=HEADER_SEP;

        cout<<"AddHeader 之后:\n"
            <<s<<endl;

        return s;
    }


    // "7"r\n""10 + 20"\r\n => "10 + 20" 
    string RemoveHeader(const string&str,int len)
    {
        cout<<"RemoveHeader 之前:\n"
            <<str<<endl;

        // 从后面开始截取
        string res=str.substr(str.size()-HEADER_SEP_LEN-len,len); 

        cout<<"RemoveHeader 之后:\n"
            <<res<<endl;   

        return res;
    }


    int Readpackage(int sock,string&inbuffer,string*package)
    {
        cout<<"ReadPackage inbuffer 之前:\n"
            <<inbuffer<<endl;

        // 边读取
        char buffer[1024];
        ssize_t s=recv(sock,&buffer,sizeof(buffer)-1,0);
        if(s<=0)
            return -1;

        buffer[s]=0;
        inbuffer+=buffer;


        cout<<"ReadPackage inbuffer 之中:\n"
            <<inbuffer<<endl;


        // 边分析,  "7"r\n""10 + 20"\r\n
        auto pos=inbuffer.find(HEADER_SEP);
        if(pos==string::npos)
            return 0;
        
        string lenStr=inbuffer.substr(0,pos);    // 获取头部字符串, 没有动inbuffer
        int len=Util::toInt(lenStr);             // 得到有效载荷的长度 => "123" -> 123
        int targetPackageLen=len+2*HEADER_SEP_LEN+lenStr.size();   // 得到整个报文长度
        if(inbuffer.size()<targetPackageLen)     // 不是一个完整的报文
            return 0;
        
        *package=inbuffer.substr(0,targetPackageLen);  // 提取到了报文有效载荷, 没有动inbuffer
        inbuffer.erase(0,targetPackageLen);      // 从inbuffer中直接移除整个报文

        cout<<"ReadPackage inbuffer 之后:\n"
            <<inbuffer<<endl;

        return len;
    }



    // Request && Response都要提供序列化和反序列化功能
    // 1. 自己手写
    // 2. 用别人的

    class Request
    {
    public:

        Request()
        {

        }

        Request(int x,int y,char op)
            :_x(x)
            ,_y(y)
            ,_op(op)
        {

        }


        // 序列化: struct->string
        bool Serialize(string* outStr)     
        {
            *outStr=""; 
#ifdef  MYSELF
            string x_string=to_string(_x);
            string y_string=to_string(_y);

            // 手动序列化
            *outStr=x_string + SEP + _op + SEP + y_string;

            std::cout << "Request Serialize:\n"
                      << *outStr << std::endl;
#else
            Json::Value root;   // Value: 一种万能对象, 接受任意的kv类型
            root["x"]=_x;
            root["y"]=_y;
            root["op"]=_op;

            // Json::FastWriter writer;  // writer: 是用来进行序列化的 struct -> string
            Json::StyledWriter writer;

            *outStr=writer.write(root);
#endif
            return true;
        }

        // 反序列化: string->struct
        bool Deserialize(const string&inStr)   
        {
#ifdef  MYSELF
            // inStr:  10 + 20 => [0]=>10, [1]=>+, [2]=>20
            vector<string> result;
            Util::StringSplit(inStr,SEP,&result);

            if(result.size()!=3)
                return false;
            if(result[1].size()!=1)
                return false;

            _x=Util::toInt(result[0]);
            _y=Util::toInt(result[2]);
            _op=result[1][0];

#else
            Json::Value root;   
            Json::Reader reader;  // Reader: 是用来反序列化的
            reader.parse(inStr,root);

            _x=root["x"].asUInt();
            _y=root["y"].asUInt();
            _op=root["op"].asUInt();
    
#endif
            Print();

            return true;
        }

        void Print()
        {
            std::cout << "_x: " << _x << std::endl;
            std::cout << "_y: " << _y << std::endl;
            std::cout << "_z: " << _op << std::endl;
        }

        ~Request()
        {
            
        }

    public:
        // _x op _y ==> 10 * 9 ? ==> 10 / 0 ?
        int _x;
        int _y;
        char _op;

    };

    class Response
    {
    public:
        Response()
        {

        }

        
        Response(int result,int code)
            :_result(result)
            ,_code(code)
        {
            
        }


        // 序列化: struct->string
        bool Serialize(string* outStr)     
        {
            // _result _code
            *outStr=""; 
#ifdef  MYSELF
            string res_string = to_string(_result);
            string code_string = to_string(_code);

            // 手动序列化
            *outStr=res_string + SEP + code_string;

#else
            Json::Value root;   
            root["result"]=_result;
            root["code"]=_code;
            // Json::FastWriter writer;
            Json::StyledWriter writer;

            *outStr=writer.write(root);
#endif
            return true;
        }

        // 反序列化: string->struct
        bool Deserialize(const string&inStr)   
        {
#ifdef  MYSELF
            // 10 0
            vector<string> result;
            Util::StringSplit(inStr,SEP,&result);

            if(result.size()!=2)
                return false;

            _result=Util::toInt(result[0]);
            _code=Util::toInt(result[1]);

#else
            Json::Value root;
            Json::Reader reader; 
            reader.parse(inStr, root);

            _result = root["result"].asUInt();
            _code = root["code"].asUInt();
#endif
            Print();
            return true;
        }

        void Print()
        {
            std::cout << "_result: " << _result << std::endl;
            std::cout << "_code: " << _code << std::endl;
        }

        ~Response()
        {

        }

    public:
        int _result;
        int _code;   // 0 success; 1,2,3,4代表不同错误码
    };
}

完整代码:lesson36/NetCal_v2 · 遇健/Linux - 码云 - 开源中国 (gitee.com)

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

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

相关文章

《低代码指南》——AI低代码维格云技术与部署架构说明

#整体技术架构说明 维格云概念上由两个部分组成:工作台(workbench)和数据表(datasheet)。 工作台(workbench)维护系统集群节点、组织和用户数据,提供文件夹、表格、仪表盘、表单、镜像、审计、权限服务等功能。 数据表(datasheet)为多个协作成员提供实时协作,以便…

变电站自动化监控系统

力安科技变电站自动化监控系统是以箱式变电站为管理对象&#xff0c;加装箱变网关&#xff0c;在完成箱变智能化改造的基础上&#xff0c;依托电易云&#xff0c;构建一体化智慧箱变及运维系统。智能箱式变电站被广泛应用于住宅小区、城市公用变压器、工厂、商场、机场、电站等…

Java语法中一些需要注意的点(仅用于个人学习)

1.当字符串和其他类型相加时&#xff0c;基本都是字符串&#xff0c;这与运算顺序有关。 2.Java中用ctrl d 来结束循环输入。 3.nextLine() 遇到空格不会结束。 4.方法重载 4.1. 方法名必须相同 4.2. 参数列表必须不同(参数的个数不同、参数的类型不同、类型的次序必须不…

【大数据之Kafka】八、Kafka Broker之生产经验

1 节点服役和退役 1.1 服役新节点 新节点准备&#xff1a; &#xff08;1&#xff09;关闭hadoop104&#xff0c;并右键执行克隆操作。 &#xff08;2&#xff09;开启hadoop105&#xff0c;并修改 IP 地址为105。 vim /etc/sysconfig/network-scripts/ifcfg/ens33&#xff…

Edge官方鼠标手势

前言 日期&#xff1a;2023年8月 Edge浏览器目前已自带官方的鼠标手势功能&#xff0c;若要使用首先将浏览器更新至最新版&#xff0c;下文介绍使用方法。 官方鼠标手势 前提 更新Edge至最新版&#xff0c;并关闭其它鼠标手势扩展。 开启鼠标手势 打开Edge浏览器的设置&…

Linux c++开发-04-让Hello World更像一个工程

外层CMakeLists.txt src中的CMakeLists.txt ADD_EXECUTABLE(hello main.cpp)main.cpp 然后 cd build cmake … make ./bin/hello

TCPIP协议学习

TCP协议 连接导向&#xff1a;TCP是一种面向连接的协议&#xff0c;这意味着通信的两端在建立通信之前必须通过握手过程建立连接。握手过程包括三次握手&#xff0c;其中客户端向服务器发送连接请求&#xff0c;服务器回复确认&#xff0c;最后客户端再次确认连接。这样建立的连…

基于springboot+vue的校园失物招领系统-前后端分离(内含文档+源码+教程)

近年来&#xff0c;信息化管理行业的不断兴起&#xff0c;使得人们的日常生活越来越离不开计算机和互联网技术。首先&#xff0c;根据收集到的用户需求分析&#xff0c;对设计系统有一个初步的认识与了解&#xff0c;确定校园失物招领网站的总体功能模块。然后&#xff0c;详细…

InstructPix2Pix(CVPR2023)-图像编辑论文解读

文章目录 1.摘要2.背景3.算法3.1 生成多模态训练集3.1.1生成指令及成对caption3.1.2 依据成对的caption生成成对的图像 3.2 InstructPix2Pix 4.实验结果4.1基线比较4.2消融实验 5.结论 论文&#xff1a; 《InstructPix2Pix: Learning to Follow Image Editing Instructions》 …

什么是系统集成项目管理工程师,证书难考吗?

系统集成项目管理工程师&#xff0c;属于软考三个级别中的 “中级”&#xff0c;相当于中级职称。 系统集成项目管理程师是工信部和人社部举办的软考中新增开的一门考试。软考全称全国计算机技术与 软件专业技术资格&#xff08;水平&#xff09;考试&#xff0c;这门新开的系…

Nacos配置文件更新+热更新+多环境配置共享+集群搭建

对服务配置文件 场景&#xff1a; 如果多个服务对应的配置文件都需要更改时&#xff0c;可以利用配置管理&#xff0c;方便对配置文件进行更新&#xff0c;而且是在本地配置前先读取nacos的配置文件&#xff0c;优先级大于本地配置文件 配置步骤 1.首先在Nacos中的配置列表中增…

0-5V转4-20mA电路

本设计采用运放与三极管做二线制恒流源电路 分析&#xff1a; Va2*V- (1) (2) (3) 联立&#xff08;2&#xff09;&#xff08;3&#xff09;得&#xff1a; &#xff08;5&#xff09; 由于 &#xff08;6&#xff09; …

【AIGC专题】Stable Diffusion 从入门到企业级实战0403

一、前言 本章是《Stable Diffusion 从入门到企业级实战》系列的第四部分能力进阶篇《Stable Diffusion ControlNet v1.1 图像精准控制》第03节&#xff0c; 利用Stable Diffusion ControlNet Canny模型精准控制图像生成。本部分内容&#xff0c;位于整个Stable Diffusion生态…

剑指offer(C++)-JZ61:扑克牌顺子(算法-模拟)

作者&#xff1a;翟天保Steven 版权声明&#xff1a;著作权归作者所有&#xff0c;商业转载请联系作者获得授权&#xff0c;非商业转载请注明出处 题目描述&#xff1a; 现在有2副扑克牌&#xff0c;从扑克牌中随机五张扑克牌&#xff0c;我们需要来判断一下是不是顺子。 有如…

MyBatisPlus 基础Mapperr接口:增删改查

MyBatisPlus 基础Mapper接口&#xff1a;增删改查 插入一条数据 代码 Testpublic void insert() {User user new User();user.setId(6L);user.setName("张三");user.setAge(25);user.setEmail("zhangsanexample.com");userMapper.insert(user);}日志 数…

postman token 请求头添加

思路&#xff1a; 1、登录成功后将 得到的token设置为集合变量 2、在需要携带Authorization的请求头上使用该集合变量 关键代码 const responseData pm.response.json(); if(responseData.code 1) {// 获取tokenconst {data:{token}} responseData// 设置为集合变量pm.colle…

Java8中List转Map报错“java.lang.IllegalStateException: Duplicate key”

排查思路 从报错的关键字中可以大致判断是是key冲突,Duplicate key在数据库的主键冲突错误中经常遇到&#xff0c;个人的思维惯性就联想到了数据库,从这个方向去排查,无果。抓耳挠腮之下&#xff0c;分析如下错误信息 java.lang.IllegalStateException: Duplicate key image(…

java八股文面试[数据库]——索引哪些情况下会失效

查询条件包含 or&#xff0c;会导致索引失效。 隐式类型转换&#xff0c;会导致索引失效&#xff0c;例如 id字段类型是varchar&#xff0c;我们 where id 1&#xff0c;这样就会触发隐式类型转换 like 通配符会导致索引失效&#xff0c;注意:”ABC%” 不会失效&#xff0c;…

问道管理:华为产业链股再度拉升,捷荣技术6连板,华力创通3日大涨近70%

华为产业链股6日盘中再度拉升&#xff0c;到发稿&#xff0c;捷荣技能涨停斩获6连板&#xff0c;华映科技亦涨停收成3连板&#xff0c;华力创通大涨超19%&#xff0c;蓝箭电子涨约11%&#xff0c;力源信息涨超4%。 捷荣技能盘中再度涨停&#xff0c;近7日已累计大涨超90%。公司…

如何成为一个高效的电商数据分析师

如何成为一个高效的电商数据分析师 文章目录 如何成为一个高效的电商数据分析师前言市场洞察平台品类品牌淘天京东抖音 商品 总结 前言 电商数据分析师 在我看来这个角色的定义其实很宽泛&#xff0c;现在从事电商这门生意的人都可以是&#xff0c;而且应该是一名电商数据分析师…