【项目】微服务及时通讯系统:编写核心类

news2024/11/22 21:13:56

文章目录

  • 前言
  • 1. 核心数据结构
    • 1.1 用户信息
    • 1.2 会话信息
    • 1.3 消息信息
  • 2. 建立目录
  • 3. 编写代码
    • 3.1 用户信息
    • 3.2 会话信息
    • 3.3 消息信息
    • 3.4 工具函数
  • 4. data.h 完整代码
  • 总结

前言

在构建现代微服务架构的即时通讯系统时,核心数据结构的设计是至关重要的。它们不仅决定了系统的性能和可扩展性,而且也影响着用户交互的直观性和便捷性。本文将深入探讨即时通讯系统中的三个核心数据结构:用户信息、会话信息和消息信息,以及它们是如何在C++和Qt框架下实现的。通过详细解析这些数据结构的设计和实现,我们希望能够为开发者提供一个清晰的指导,帮助他们构建高效、稳定且用户友好的即时通讯应用。

1. 核心数据结构

1.1 用户信息

1.2 会话信息

用户和用户之间,聊天会话:
例如:

用户A:有2个好友:B、C
此时A可以和B单聊,也能和C单聊(2个聊天会话)
此时这个群组,也对应一个聊天会话

聊天程序中,会话的生命周期,是比较的;一直持续到把对方好友删除/退出群组才会随之销毁。

1.3 消息信息

  1. 文本消息
  2. 图片消息
  3. 文件消息
  4. 语音消息

2. 建立目录

在文件中显示
在这里插入图片描述
创建一个文件夹存放目录
在这里插入图片描述
新建一个文件
在这里插入图片描述
添加进来
在这里插入图片描述
在这里插入图片描述
此时cmake 就自动添加上了
在这里插入图片描述

qt_add_executable:指定编译qt需要依赖哪些文件(源代码,肯定是要依赖的)

关于命名空间的约定(这种约定,仅限于当前项目):
如果代码所在的文件,就是在项目的顶层目录中,此时就直接使用全局命名空间(不手动指定)
在这里插入图片描述
如果代码所在的文件,在某个子目录中;此时,就指定一个和目录名字相同的命名空间。
在这里插入图片描述

使代码中的命名空间的结构和文件在目录中结构一致。(尤其是在目录结构更复杂,嵌套多层)

3. 编写代码

3.1 用户信息


/// 用户信息


class UserInfo {
public:
    QString userId = "";         // 用户编号
    QString nickname = "";       // 用户昵称
    QString description = "";    // 用户签名
    QString phone = "";          // 手机号码
    QIcon avatar;                // 用户头像
};
QString userId;

使用字符串的方式来作为id,可以有更灵活的方式来生成。(也为了能够适应分布式后端)

mysql 数据库,支持自增主键:
如果是单个节点的 mysql,用上述方式没有任何问题
但是,如果是多个节点的分布式
mysql,就无法使用整数自增组件了 分布式 mysql 下,很可能需要针对用户信息“分库分表”

后续中可以通过例如:uuid 这样的方式,或者是 雪花算法这样的方式 来生成分布式系统中唯一id

3.2 会话信息


/// 会话信息


class ChatSessionInfo {
    QString chatSessionId = "";      // 会话编号
    QString chatSessionName = "";    // 会话名字,如果是会话是单聊,名字就是对方的昵称;如果是群聊,名字就是群聊的名称
    Message lastMessage;             // 表示最新的消息
    QIcon avatar;                    // 会话头像,如果会话是单聊,头像就是对方的头像;如果是群聊,头像群聊的头像
    QString userId = "";             // 对于单聊来说,表示对方的用户 id,对于群聊设置为 ""
};
ChatSessionInfo

前面谈到的“会话”都是针对 聊天过程中的,组织消息的 会话。
后面还会涉及到,客户端连接到服务器之后,也有一个“登录”用到的会话

在这里插入图片描述

Message lastMessage;

这个内容就是为了在会话列表中,能够起到“显示-提示”这样的效果。
在这里插入图片描述
针对会话信息来说:

一个会话,里面其实是可以包含多个用户的
这个会话里具体有哪些用户,
后续会通过单独的方式进行组织管理。

此处列出 QString userId;表示的含义是:

  1. 如果会话是单聊会话,此时 userId 表示“对方”的用户 id
  2. 如果会话是群聊会话,此时 userId 设为 “”,后续通过其他的方式来吧完整的用户id列表拿到

会话 - 消息 是 “一对多” 这样的关系。
消息:会话id。

 QByteArray content;         // 消息发送的正文内容

如果是 文本消息,正文就是一个字符串
如果是 图片,文件,语音消息,正文就是一个“二进制序列”

| 在C/C++ 中没有 byte 这样的类型,表示 byte 都是拿 char / unsigned char 凑合一下

| char / unsigned char
正常来说,一个char(字符) 不一定是一个字节

  • 对于 应用 ascii 来说,一个字符就是一个字节
  • 对于 中文 gbk 编码来说,一个字符就是2个字节
  • 对于 中文 utf8 编码来说,一个字符就是3个字节

由于 C/C++ 有点太老了,对于这一块的支持,比较有限
C++ 中,一个 char 就是固定的一个字节了。

| 比方说,C++中:std::string name = "张三";
C++ 中,通过代码取出“三”这个汉字,老麻烦了!

  1. 先判定是那种编码方式
  2. 计算第二个汉字 所属的范围
  3. 取字符串子串

| 像其他主流语言,Java/Python 之类的
string s = "张三";
s.charAt(1) => "三"

| 相比之下,Qt做了更好的处理;QString 就对上述情况处理的更好了。 Qt 也明确区分了 “字节” 和 “字符”

QString fileId;             // 文件的身份标识,当类型为 文件,图片,语言 的时候,才有效;当消息为文本,则为""

文件/图片/语言 这些消息,体积可能是比较大的!(网络带宽)

  • 一旦一个聊天会话中,包含多个上述这样的消息,就会使从服务器消息列表这样的操作,变得非常低效。
    一般的做法,都是“获取消息列表”,只是拿到文件/图片/语言 消息的 filed 等到客户端得到“消息列表”之后,再更具拿到的filed,给服务器发送额外的请求,获取文件内容。(化整为零)
QString fileName;           // 文件名称,只是当消息类型为 文件消息时, 才有效,其他消息均为 ""

虽然图片/语音,这两个也是“文件”但是文件名不需要显示到界面上。
对于文件消息,希望界面上显示“文件名”,点击之后可以进行“另存为”这样的操作。

3.3 消息信息


/// 消息信息


enum MessageType {
    TEXT_TYPE,      // 文本消息
    IMAGE_TYPE,     // 图片消息
    FILE_TYPE,      // 文件消息
    SPEECH_TYPE,    // 语音消息
};

class Message {
public:
    QString messageId = "";          // 消息的编号
    QString chatSessionId = "";      // 消息所属会话的编号
    QString time = "";               // 消息时间,通过“格式化”时间的方式来表示, 形如:06-07 12:00:00
    MessageType messageType = TEXT_TYPE; // 消息类型
    UserInfo sender;                 // 发送者的信息
    QByteArray content = "";         // 消息发送的正文内容
    QString fileId = "";             // 文件的身份标识,当类型为 文件,图片,语言 的时候,才有效;当消息为文本,则为""
    QString fileName = "";           // 文件名称,只是当消息类型为 文件消息时, 才有效,其他消息均为 ""

    // 此处 extraInfo 目前只是再消息类型为文件消息时,作为“文件名”补充。
    static Message makeMessage(MessageType messageType, const QString& chatSessionId, const UserInfo& sender
                               , const QByteArray& content, const QString& extraInfo){
        if (messageType == TEXT_TYPE) {
            return makeTextMessage(chatSessionId, sender, content);
        } else if (messageType == IMAGE_TYPE) {
            return makeImageMessage(chatSessionId, sender, content);
        } else if (messageType == FILE_TYPE) {
            return makeFileMessage(chatSessionId, sender, content, extraInfo);
        } else if (messageType == SPEECH_TYPE) {
            return makeSpeechMessage(chatSessionId, sender, content);
        } else {
            // 触发了未知消息类型
            return Message();
        }
    }



private:

    // 通过这个方法生成一个唯一的 messageId
    static QString makeId() {
        return "M" + QUuid::createUuid().toString().sliced(25, 12);
    }

    static Message makeTextMessage(const QString& chatSessionId, const UserInfo& sender, const QByteArray& content) {
        Message message;
        // 此处需要确保,设置的 messageId 是 “唯一” 的
        message.messageId = makeId();
        message.chatSessionId = chatSessionId;
        message.sender = sender;
        message.time = formatTime(getTime()); // 生成一个格式化时间
        message.content = content;
        message.messageType = TEXT_TYPE;
        // 对于文本消息来说,这两个属性不使用,设为""
        message.fileId = "";
        message.fileName = "";
        return message;
    }
    static Message makeImageMessage(const QString& chatSessionId, const UserInfo& sender, const QByteArray& content) {
        Message message;
        // 此处确保,设置的 messageId 是“唯一”的
        message.messageId = makeId();
        message.chatSessionId = chatSessionId;
        message.sender = sender;
        message.time = formatTime(getTime());
        message.content = content;
        message.messageType = IMAGE_TYPE;
        // fileId 后续使用的时候进一步设置
        message.fileId = "";
        // fileName 不使用,直接设为“”
        message.fileName = "";
        return message;
    }
    static Message makeFileMessage(const QString& chatSessionId, const UserInfo& sender, const QByteArray& content, const QString& fileName) {
        Message message;
        message.messageId = makeId();
        message.chatSessionId = chatSessionId;
        message.sender = sender;
        message.time = formatTime(getTime());
        message.content = content;
        message.messageType = FILE_TYPE;
        // fileId 后续使用的时候进一步设置
        message.fileId = "";
        message.fileName = fileName;
        return message;
    }
    static Message makeSpeechMessage(const QString& chatSessionId, const UserInfo& sender, const QByteArray& content) {
        Message message;
        message.messageId = makeId();
        message.chatSessionId = chatSessionId;
        message.sender = sender;
        message.time = formatTime(getTime());
        message.content = content;
        message.messageType = SPEECH_TYPE;
        // fileId 后续使用的时候进一步设置
        message.fileId = "";
        // fileName 不使用,直接设为“”
        message.fileName = "";
        return message;
    }
};
  • 工厂方法:(工厂模式)
    解决 C++/Java 等语言中,构造函数,不够用的问题。

虽然平时构建对象,都是通过对 构造函数 来完成。
如果有不同的方式来构造对象,此时构造函数就不太够用了。

比方说 “点”

class Point {
public:
	Point(double x, double y);  // 直角坐标的方式构造
	Point(double r, double a);	// 极坐标的构造方式
}

够高函数想要提供不同版本,必须要 “重载” 要求 函数名 相同(都是一样)
参数的个数/类型不同

使用普通的函数来实现不同的构造方式;普通函数,函数名可以随便起,也就不再受到“重载”的约束了。

这里说“普通”,一般都要使用 static 修饰的 静态函数。(调用已有的构造函数,根据不同的需求,进一步的实现初始化的细节)

相比于 UserInfoChatSessionInfo, Message 是更需要“工厂模式”的,Message 需要支持多种构造方式;

  1. 文本消息
  2. 图片消息
  3. 语音消息
  4. 文件消息

messageId 是一个“唯一”这样的内容
UUID 这个东西,背后是一套算法,通过这个算法,就能生成“全球唯一的身份标识”,Qt对这个算法也是有封装的。
在这里插入图片描述
这一串,其实是16进制的整数,实际开发中,为了提高“可读性”也可以截取uuid中一部分来进行使用。

file.write(content);
file.flush();
file.close();

flush: 刷新缓冲区

3.4 工具函数


/// 工具函数,后续很多模块可能都要用到


static inline QString getFileName(const QString& path) {
    QFileInfo fileInfo(path);
    return fileInfo.fileName();
}

// 封装一个“宏”作为打印日志的方式
#define TAG QString("[%1:%2]").arg(model::getFileName(__FILE__), QString::number(__LINE__))

// qDebug 打印字符串的时候,就会自动加上" "
#define LOG() qDebug().noquote() << TAG

// 要求函数的定义如果写在 .h 中,必须加 static 或者 inline(当然两个都加也可以),避链接阶段出现“函数重定义”的问题
static inline QString formatTime(int64_t timestamp) { // 为了防止在2038年溢出,用64位整数
    // 先把时间戳,转换成QDateTime 对象
    QDateTime dateTime =  QDateTime::fromSecsSinceEpoch(timestamp);
    // 把 QDateTime 对象转换成“格式时间”
    return dateTime.toString("MM-dd HH:mm:ss");
}

// 通过这个函数得到 秒级 的时间
static inline int64_t getTime() {
    return QDateTime::currentMSecsSinceEpoch();
}

// 根据 QByteArray, 转成 QIcon
static inline QIcon makeIcon(const QByteArray& byteArray) {
    QPixmap pixmap;
    pixmap.loadFromData(byteArray);
    QIcon icon(pixmap);
    return icon;
}

// 读写文件操作
// 从读取文件中,读取所有的二进制内容,得到一个 QByteArray
static inline QByteArray loadFileToByteArray(const QString& path) {
    QFile file(path);
    bool ok = file.open(QFile::ReadOnly);
    if (!ok) {
        qDebug() << "文件打开失败";
        return QByteArray();
    }
    QByteArray content = file.readAll();
    file.close();
    return content;
}

// 把 QByteArray 中的内容,写入到某个指定的文件夹里
static inline void writeByteArryToFile(const QString& path, const QByteArray& content) {
    QFile file(path);
    bool ok = file.open(QFile::WriteOnly);
    if (!ok) {
        qDebug() << "文件打开失败";
        return;
    }
    file.write(content);
    file.flush();   // 刷新缓冲区
    file.close();
}

4. data.h 完整代码

#pragma once
#include <QString>
#include <QIcon>
#include <QUuid>
#include <QDateTime>
#include <QFile>
#include <QFileInfo>
#include <QDebug>

// 创建命名空间
namespace model {



/// 工具函数,后续很多模块可能都要用到


static inline QString getFileName(const QString& path) {
    QFileInfo fileInfo(path);
    return fileInfo.fileName();
}

// 封装一个“宏”作为打印日志的方式
#define TAG QString("[%1:%2]").arg(model::getFileName(__FILE__), QString::number(__LINE__))

// qDebug 打印字符串的时候,就会自动加上" "
#define LOG() qDebug().noquote() << TAG

// 要求函数的定义如果写在 .h 中,必须加 static 或者 inline(当然两个都加也可以),避链接阶段出现“函数重定义”的问题
static inline QString formatTime(int64_t timestamp) { // 为了防止在2038年溢出,用64位整数
    // 先把时间戳,转换成QDateTime 对象
    QDateTime dateTime =  QDateTime::fromSecsSinceEpoch(timestamp);
    // 把 QDateTime 对象转换成“格式时间”
    return dateTime.toString("MM-dd HH:mm:ss");
}

// 通过这个函数得到 秒级 的时间
static inline int64_t getTime() {
    return QDateTime::currentMSecsSinceEpoch();
}

// 根据 QByteArray, 转成 QIcon
static inline QIcon makeIcon(const QByteArray& byteArray) {
    QPixmap pixmap;
    pixmap.loadFromData(byteArray);
    QIcon icon(pixmap);
    return icon;
}

// 读写文件操作
// 从读取文件中,读取所有的二进制内容,得到一个 QByteArray
static inline QByteArray loadFileToByteArray(const QString& path) {
    QFile file(path);
    bool ok = file.open(QFile::ReadOnly);
    if (!ok) {
        qDebug() << "文件打开失败";
        return QByteArray();
    }
    QByteArray content = file.readAll();
    file.close();
    return content;
}

// 把 QByteArray 中的内容,写入到某个指定的文件夹里
static inline void writeByteArryToFile(const QString& path, const QByteArray& content) {
    QFile file(path);
    bool ok = file.open(QFile::WriteOnly);
    if (!ok) {
        qDebug() << "文件打开失败";
        return;
    }
    file.write(content);
    file.flush();   // 刷新缓冲区
    file.close();
}


/// 用户信息


class UserInfo {
public:
    QString userId = "";         // 用户编号
    QString nickname = "";       // 用户昵称
    QString description = "";    // 用户签名
    QString phone = "";          // 手机号码
    QIcon avatar;                // 用户头像
};



/// 消息信息


enum MessageType {
    TEXT_TYPE,      // 文本消息
    IMAGE_TYPE,     // 图片消息
    FILE_TYPE,      // 文件消息
    SPEECH_TYPE,    // 语音消息
};

class Message {
public:
    QString messageId = "";          // 消息的编号
    QString chatSessionId = "";      // 消息所属会话的编号
    QString time = "";               // 消息时间,通过“格式化”时间的方式来表示, 形如:06-07 12:00:00
    MessageType messageType = TEXT_TYPE; // 消息类型
    UserInfo sender;                 // 发送者的信息
    QByteArray content = "";         // 消息发送的正文内容
    QString fileId = "";             // 文件的身份标识,当类型为 文件,图片,语言 的时候,才有效;当消息为文本,则为""
    QString fileName = "";           // 文件名称,只是当消息类型为 文件消息时, 才有效,其他消息均为 ""

    // 此处 extraInfo 目前只是再消息类型为文件消息时,作为“文件名”补充。
    static Message makeMessage(MessageType messageType, const QString& chatSessionId, const UserInfo& sender
                               , const QByteArray& content, const QString& extraInfo){
        if (messageType == TEXT_TYPE) {
            return makeTextMessage(chatSessionId, sender, content);
        } else if (messageType == IMAGE_TYPE) {
            return makeImageMessage(chatSessionId, sender, content);
        } else if (messageType == FILE_TYPE) {
            return makeFileMessage(chatSessionId, sender, content, extraInfo);
        } else if (messageType == SPEECH_TYPE) {
            return makeSpeechMessage(chatSessionId, sender, content);
        } else {
            // 触发了未知消息类型
            return Message();
        }
    }



private:

    // 通过这个方法生成一个唯一的 messageId
    static QString makeId() {
        return "M" + QUuid::createUuid().toString().sliced(25, 12);
    }

    static Message makeTextMessage(const QString& chatSessionId, const UserInfo& sender, const QByteArray& content) {
        Message message;
        // 此处需要确保,设置的 messageId 是 “唯一” 的
        message.messageId = makeId();
        message.chatSessionId = chatSessionId;
        message.sender = sender;
        message.time = formatTime(getTime()); // 生成一个格式化时间
        message.content = content;
        message.messageType = TEXT_TYPE;
        // 对于文本消息来说,这两个属性不使用,设为""
        message.fileId = "";
        message.fileName = "";
        return message;
    }
    static Message makeImageMessage(const QString& chatSessionId, const UserInfo& sender, const QByteArray& content) {
        Message message;
        // 此处确保,设置的 messageId 是“唯一”的
        message.messageId = makeId();
        message.chatSessionId = chatSessionId;
        message.sender = sender;
        message.time = formatTime(getTime());
        message.content = content;
        message.messageType = IMAGE_TYPE;
        // fileId 后续使用的时候进一步设置
        message.fileId = "";
        // fileName 不使用,直接设为“”
        message.fileName = "";
        return message;
    }
    static Message makeFileMessage(const QString& chatSessionId, const UserInfo& sender, const QByteArray& content, const QString& fileName) {
        Message message;
        message.messageId = makeId();
        message.chatSessionId = chatSessionId;
        message.sender = sender;
        message.time = formatTime(getTime());
        message.content = content;
        message.messageType = FILE_TYPE;
        // fileId 后续使用的时候进一步设置
        message.fileId = "";
        message.fileName = fileName;
        return message;
    }
    static Message makeSpeechMessage(const QString& chatSessionId, const UserInfo& sender, const QByteArray& content) {
        Message message;
        message.messageId = makeId();
        message.chatSessionId = chatSessionId;
        message.sender = sender;
        message.time = formatTime(getTime());
        message.content = content;
        message.messageType = SPEECH_TYPE;
        // fileId 后续使用的时候进一步设置
        message.fileId = "";
        // fileName 不使用,直接设为“”
        message.fileName = "";
        return message;
    }
};


/// 会话信息


class ChatSessionInfo {
    QString chatSessionId = "";      // 会话编号
    QString chatSessionName = "";    // 会话名字,如果是会话是单聊,名字就是对方的昵称;如果是群聊,名字就是群聊的名称
    Message lastMessage;             // 表示最新的消息
    QIcon avatar;                    // 会话头像,如果会话是单聊,头像就是对方的头像;如果是群聊,头像群聊的头像
    QString userId = "";             // 对于单聊来说,表示对方的用户 id,对于群聊设置为 ""
};

} // end model

总结

本文详细介绍了微服务即时通讯系统中的核心数据结构,包括用户信息、会话信息和消息信息,以及它们在C++和Qt环境下的具体实现。我们首先对每个数据结构的功能和属性进行了概述,然后通过代码示例展示了如何定义这些结构和相关的工具函数。特别地,我们使用了工厂模式来简化消息对象的创建过程,以支持不同类型的消息构造。此外,文中还探讨了UUID生成机制,确保了消息ID的唯一性,这对于分布式系统尤为重要。

通过本文的阅读,开发者应该能够理解并实现一个高效的消息处理系统,它不仅能够处理文本消息,还能够处理图片、文件和语音等多种类型的消息。此外,通过合理组织代码和使用适当的设计模式,可以提高代码的可维护性和可扩展性,为未来的功能扩展打下坚实的基础。最后,本文提供的代码示例和设计思路可以作为构建即时通讯系统的一个参考起点,帮助开发者快速进入项目开发阶段。

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

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

相关文章

聊一聊AI能够应用在哪些地方

AI大模型的应用场景极为广泛&#xff0c;涵盖了众多行业和领域&#xff0c;以下是一些主要的应用场景&#xff1a; 1. 自然语言处理&#xff08;NLP&#xff09; 对话系统&#xff1a;如智能客服、虚拟助手等&#xff0c;通过自然语言与用户进行交互&#xff0c;提供信息查询…

探索AI大模型量化前沿技术:引领智能计算新潮流

大型语言模型&#xff08;LLMs&#xff09;通常因为体积过大而无法在消费级硬件上运行。这些模型可能包含数十亿个参数&#xff0c;通常需要配备大量显存的GPU来加速推理过程。 因此越来越多的研究致力于通过改进训练、使用适配器等方法来缩小这些模型的体积。在这一领域中&am…

【CAN总线测试】——通讯相关诊断测试

从0开始学习CANoe使用 从0开始学习车载测试 相信时间的力量 星光不负赶路者&#xff0c;时光不负有心人。 目录 1.节点超时故障 2.Busoff故障码测试 3.Busoff 状态下超时故障监测测试 4.欠压故障测试 5.过压故障测试 1.节点超时故障 用例编号 TG4_TC1 测试目的 检测D…

从零开始搭建 LVS 高可用集群 (单机)

从零开始搭建 LVS 高可用集群 (单机) 背景 从零开始搭建 LVS 高性能集群 (DR模式) 从零开始搭建 KeepalaivedLvs 高可用集群 &#xff08;Aliyun部署&#xff09; 经过前面2篇关于lvs集群部署文章&#xff0c;相信跟着部署文档&#xff0c;实际部署过集群的大家对lvs服务有了…

【html+css 绚丽Loading】000015 九转轮回珠

前言&#xff1a;哈喽&#xff0c;大家好&#xff0c;今天给大家分享htmlcss 绚丽Loading&#xff01;并提供具体代码帮助大家深入理解&#xff0c;彻底掌握&#xff01;创作不易&#xff0c;如果能帮助到大家或者给大家一些灵感和启发&#xff0c;欢迎收藏关注哦 &#x1f495…

Total Uninstall - 专业 Windows 卸载清理工具,让软件卸载更彻底

Total Uninstall 是一款专业的卸载清理工具&#xff0c;可帮助我们无残留卸载各类软件。 这款工具相当强大&#xff0c;卸载能做到彻底清除软件痕迹&#xff0c;同时具备软件分析、安装记录、系统清理、软件备份搬家等多种功能。 软件支持买前免费试用&#xff0c;感兴趣的朋友…

新疆旅游今年为什么这么火热?

今年新疆旅游火爆全网&#xff0c;不夸张的说&#xff0c;打开朋友圈&#xff0c;几乎一半人在新疆旅游、还有一半人在去新疆旅游的路上。 大家也纷纷在小红书上晒出新疆相关的笔记&#xff0c;覆盖旅游、美食、穿搭、养生、摄影等众多热门行业&#xff0c;相关话题多次登上小…

MySQL从入门到精通(第5-8章)

文章目录 5 排序与分页5.1 排序数据5.1.1 排序规则5.1.2 单列排序5.1.3 多列排序 5.2 分页5.2.1 实现规则5.2.2 拓展 6 多表查询6.1 笛卡尔积6.1.1 笛卡尔积&#xff08;或交叉连接&#xff09;的理解6.1.2 笛卡尔积可能存在的问题与解决 6.2 多表查询分类讲解6.2.1 等值连接与…

AI如何让销售更智能更精确?AI赋能销售的全流程深度解析

前言 随着人工智能&#xff08;AI&#xff09;技术的迅速发展&#xff0c;AI已经渗透到各个行业&#xff0c;尤其是在销售领域&#xff0c;AI正通过智能化外呼系统与CRM&#xff08;客户关系管理&#xff09;系统的结合&#xff0c;极大提升了企业的销售效率。在这篇文章中&am…

C++动态规划及九种背包问题

目录 目录 一&#xff0c;动态规划 一&#xff09;&#xff0c;动态规划的定义 二&#xff09;&#xff0c;动态规划其他的相关概念&#xff08;也是使用条件&#xff09; 1&#xff0c;重叠子问题 2&#xff0c; 最优子结构 3&#xff0c;无后效性 三&#xff09;&…

C++ 设计模式——观察者模式

观察者模式 观察者模式主要组成部分例一&#xff1a;工作流程第一步&#xff1a;定义观察者接口第二步&#xff1a;定义主题接口第三步&#xff1a;实现具体主题第四步&#xff1a;实现具体观察者第五步&#xff1a;主函数UML 图UML 图解析 例二&#xff1a;工作流程第一步&…

rocky linux配置说明

下载&#xff1a; 目前最新版的 v9.4 镜像 Download - Rocky Linux 可以在官网下载&#xff0c;不过挺慢的&#xff0c;可以试试阿里云的https://mirrors.aliyun.com/rockylinux/会稍快点 安装&#xff1a; 其步骤和centos基本一样&#xff0c;其中磁盘分区模式是gpt这个并未…

Ingress Nginx Controller

Kubernetes集群 服务暴露 Nginx Ingress Controller 一、ingress控制器 1.1 ingress控制器作用 &#xff08;类似于slb&#xff0c;做代理服务&#xff09; ingress controller可以为kubernetes 集群外用户访问Kubernetes集群内部pod提供代理服务。 提供全局访问代理访问流…

【大模型LLM第九篇】高效的微调方式:Self-Evolved多样性数据采样

前言 来自阿里巴巴和北京大学的文章&#xff1a;Self-Evolved Diverse Data Sampling for Efficient Instruction Tuning link&#xff1a;https://arxiv.org/pdf/2311.08182 github&#xff1a;https://github.com/OFA-Sys/DiverseEvol 一、摘要 提升大型语言模型的指令遵循能…

鸿蒙Harmony开发——设备发烫问题分析

&#xff1b; 本文字数&#xff1a;4207字 预计阅读时间&#xff1a;25分钟 设备过热问题是影响用户体验和设备性能的重要因素。过热不仅会导致性能下降&#xff0c;还可能损坏硬件。因此&#xff0c;开发者需要及时发现、分析并解决这一问题。本文将首先介绍评估设备过热的关键…

Qt/C++控件实例 QWidget联合动画实现卷轴效果

显示特点 动态翻页效果&#xff1a;数字在更新时&#xff0c;会有一个从前一数字向下一数字过渡的翻页效果。这种过渡动画使得数字变化过程更加平滑和自然&#xff0c;避免了突然的跳变。 高对比度显示&#xff1a;每个数字的背景框颜色为红色&#xff0c;数字颜色为白色&…

每日一个科研绘图·气泡图|24-08-24

一、气泡图 气泡图是一种数据可视化工具&#xff0c;它在传统的二维散点图的基础上增加了一个维度&#xff0c;使得我们能够同时观察三个变量之间的关系。这种图表通过点的大小来表示第三个数值变量的大小&#xff0c;从而提供了一种直观的方式来探索数据中的模式和趋势。 在…

Facebook的AI助手:如何提升用户社交体验的智能化

在现代社交媒体平台中&#xff0c;人工智能&#xff08;AI&#xff09;的应用正逐渐改变人们的社交体验。Facebook作为全球最大的社交媒体平台之一&#xff0c;已在AI技术的开发与应用上投入了大量资源&#xff0c;并通过其AI助手为用户提供了更加个性化、智能化的互动体验。这…

pycharm 隐藏 __ init __ .py 文件

pycharm 隐藏 __ init __ .py 文件 每次写python项目时&#xff0c;都会有一个自动生成__ init __ .py文件&#xff0c;看的很累&#xff0c;所以想把__ init __ .py文件给隐藏掉 方法&#xff1a; &#xff08;1&#xff09;File -> Settings (2) Appearance & Behav…

武汉流星汇聚:全球化布局与本地化运营,亚马逊电商帝国崛起秘诀

在数字时代的浪潮中&#xff0c;亚马逊如同一颗璀璨的星辰&#xff0c;照亮了全球电子商务的天空。作为美国乃至全球访问量最高的电商平台&#xff0c;亚马逊不仅重塑了消费者的购物习惯&#xff0c;更以其独特的商业模式、惊人的订单履行速度和卓越的购物体验&#xff0c;引领…