[项目:微服务即时通讯系统客户端(基于C++QT)]二,核心类的编写

二,核心类的编写

一,需求分析

  1. 用户信息

  2. 会话信息

用户A有两个好友,需要两个聊天会话,且持续存在。

(一直持续到删除好友/退出群组才会删除)

用户ABC有一个群组,这个群组存在一个聊天会话。

  1. 消息信息

文本消息

图片消息

文件消息

语音消息

二,核心类的编写

image.png

image.png

建立模型文件夹“model”

  1. 关于命名空间的约定(针对此项目):

a 项目所在的文件,就是项目的顶层目录,此时,就直接使用全局命名空间(不手动指定)。

b 如果项目所在的文件,在某个子目录中,此时就指定一个和目录名字相同的命名空间。

#pragma once //不被重复包含

#include<Qstring>
//创建命名空间
namespace model{
//
///用户信息///
/
class UseInof{
public:
  QString userID;//int userId;使用字符串的方式来作为id,可以有更灵活的方式来进行生成
  //并且更好的适用分布式的mysql
  //后续可通过uuid或者雪花算法这样的方式来生成分布式系统中唯一标识的id
  
private:
};
}//end model描述结束命名空间

一.用户信息UserInfo类

class UserInfo{
public:
    QString userID="";//用户的编号
    //int userId;使用字符串的方式来作为id,可以有更灵活的方式来进行生成
    //并且更好的适用分布式的mysql
    //后续可通过uuid或者雪花算法这样的方式来生成分布式系统中唯一标识的id
    QString nickname="";//用户的昵称
    QString description="";//用户签名
    QString phone="";//手机号码
    QIcon avatar;//用户头像
};

二.ChatSessionInfo会话信息

class ChatSessionInfo{
public:
    QString chatSessionID="";//会话编号
    QString chatSessionName="";//会话名字(有群聊和单聊)
    Message lastMessage;//在会话列表显示提示
    QIcon avatar;//会话的头像(有单聊和群聊之分)
    QString userId="";
};

三.会话信息ChatSessionInfo产生的Message类

1.什么是message的工厂方法?

是一种工厂设计模式。

解决构造函数不太够用的情况。

虽然平时构建对象,都是通过 构造函数 来完成。

但是如果有不同的方式来构建对象,此时构造函数就不太够用了。

class Point{
public:
  Point(double x,double y);
  Point(double r,double a);
  //会发现大家了,构造函数,要提供不同的版本,必须要重载,要求函数名相同,但是参数个数/类型不同
}

运用工厂模式,也就是使用普通的函数来实现不同的构造方式。

普通函数,函数名可以随便起,也就不再受到重载的约束了。

虽然说是普通,但是一般要使用static修饰的静态函数

相比于ChatSessionInfoUserInfo更需要工厂模式,是因为Message要支持四种消息

2.怎么生成唯一的messageid

https://baike.baidu.com/item/UUID/5921266?fr=ge_ala

使用uuid,可以生成“全球唯一的身份标识”

return QUuid::createUuid().toString();

3.类的书写

class Message{
public:
    QString messageId="";//消息的编号
    QString chatSessionId="";//消息所属会话编号
    QString time="";//消息的时间,通过格式化时间的方式来表示 形如 06-07 12:00
    MessageType messageType;//消息类型
    UserInfo sender;//发送者的信息
    QByteArray content;//消息的正文内容:文本就是字符串,图片、文件、语音就是二进制的序列
    // C和cpp没有byte类型,都是拿char/unsigned char来凑合的
    //例如在cpp中通过代码取出“string张三”的三很麻烦
    //1.先判定哪种编码方式 2.计算第二个汉字所属出字节范围 3.取字符串子串
    //但是QT相比之下做了更好的处理,QString就对上述情况处理的更好,QT明确区分了字节和字符,
    //表示字符串必须使用QString(内置了对不同字符集进行处理),
    //表示二进制数据必须使用QByteArray
    QString fileId="";//表示一个文件的身份标识,当消息类型为文件,图片,语音时才有效
    //一旦一个聊天会话中,包含上述多个这样的信息,就会从服务器获取消息列表,这样的操作,就变得非常低效
    //所以一般的做法是获取此三种消息的id,当客户端拿到消息列表之后,再根据拿到的field,给服务器发送额外的请求,获取文件内容
    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 QUuid::createUuid().toString();
        //取M做标识符字符串子串
        return "M"+QUuid::createUuid().toString().sliced(25,12);//从25位开始往后数12位

    }
    static Message makeTextMessage(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=TEXT_TYPE;
        //对于文本消息来说这两个属性不适用,设为空字符串
        message.fileId="";
        message.fileName="";
        return message;
    }
    static Message makeImageMessage(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=IMAGE_TYPE;
        //后续使用的时候进一步设置
        message.fileId="";
        //不使用
        message.fileName="";
        return message;
    }
    static Message makeFileMessage(const QString& chatSessionId,const UserInfo& sender,const QByteArray& content,const QString& extraInfo)
    {
        Message message;
        message.messageId=makeId();
        message.chatSessionId=chatSessionId;
        message.sender=sender;
        message.time =formatTime(getTime());//生成一个格式化时间
        message.content=content;
        message.messageType=FILE_TYPE;
        message.fileId="";
        message.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;
        message.fileId="";
        message.fileName="";
        return message;
    }
};

4.MessageType

//会话消息是1对多
enum MessageType{
    TEXT_TYPE,//文本消息
    IMAGE_TYPE,//图像消息
    FILE_TYPE,//文件消息
    SPEECH_TYPE//语音消息
};

四.在头文件中定义函数

在 cpp 中,如果你在头文件中定义了函数,而不加 static 或 inline,会导致链接阶段的重定义错误。这是因为头文件通常会被多个源文件(.cpp 文件)包含,如果没有特殊处理,每个源文件中都会生成一个相同的函数定义,最终在链接时会发生冲突。

1. 为什么会出现重定义问题?

假设我们有一个头文件 example.h

cpp复制代码// example.h
int add(int a, int b) {
    return a + b;
}

以及两个源文件 file1.cpp 和 file2.cpp 都包含了 example.h

cpp复制代码// file1.cpp
#include "example.h"
// 其他代码...

// file2.cpp
#include "example.h"
// 其他代码...

当编译器编译这两个源文件时,它会在 file1.o 和 file2.o 中都生成 add 函数的定义。在链接阶段,链接器会发现有两个相同的 add 函数的定义,从而报出重定义错误。

2. 解决方案:使用 **static** 或 **inline**

**static**

将函数定义为 static 会使它的链接属性变为内部链接,即该函数的定义仅在包含它的源文件中可见,而不对其他源文件可见。这样即使在多个源文件中定义了相同的 static 函数,它们也是独立的,不会产生冲突。

cpp复制代码// example.h
static int add(int a, int b) {
    return a + b;
}

**inline**

将函数定义为 inline 可以避免重定义错误,因为 inline 函数的特殊属性是它们的定义可以在多个翻译单元中重复,而不会产生冲突。编译器会尝试将 inline 函数的调用点用函数体替换,以避免多次定义。

cpp复制代码// example.h
inline int add(int a, int b) {
    return a + b;
}

**static inline**

你也可以同时使用 static 和 inline,表示该函数不仅仅是内部链接的(static),还希望编译器对其进行内联优化(inline)。这通常会用于一些特定场景,例如函数体非常小且希望减少调用开销的情况下。

cpp复制代码// example.h
static inline int add(int a, int b) {
    return a + b;
}

3. 使用 **static** 和 **inline** 的区别

  • **static** 函数:函数在每个包含它的翻译单元(源文件)中都有一个独立的定义,各个源文件中的 static 函数是互相独立的,不会冲突。

  • **inline** 函数:告诉编译器将函数定义内联化,允许在多个翻译单元中重复定义。在绝大多数现代编译器中,inline 函数的实际行为取决于编译器的优化策略,而不强制内联。

4. 实际开发中的应用建议

  • 头文件中的小型工具函数:优先使用 inline,因为它能减少函数调用开销,同时不会出现重定义问题。

  • 静态库中的函数:如果只希望在当前文件中使用某个函数,可以选择 static。这可以避免其他文件误用该函数。

  • 头文件中重复出现的函数:可以同时使用 static 和 inline,以避免冲突,并可能获得内联优化。

综上所述,在头文件中定义函数时,应尽量加上 static 或 inline 关键字(或两者都加),以避免在链接阶段出现重定义问题,并可能获得编译器的优化。

五.解决了四,我们来写工具函数

1.通过时间戳转化为格式化的时间

static inline QString formatTime(int64_t timestamp){
    //通过时间戳,转换成格式化时间
    //时间戳是指格林威治时间1970年01月01日00时00分00秒(北京时间1970年01月01日08时00分00秒)起至现在的总秒数
    //为什么使用int64因为使用int32会出现溢出情况

    //先把时间戳,转换成QDateTime对象
    QDateTime dateTime=QDateTime::fromSecsSinceEpoch(timestamp);
    //fromSecsSinceEpoch(timestamp)将时间戳转化为QDateTime对象

    //把对象转为“格式化时间”
    return dateTime.toString("MM-dd HH:mm:ss");

}

2.获得秒级的时间

//获得秒级的时间
static inline int64_t getTime()
{
    //用于获取自 "Epoch" 时间(即 1970 年 1 月 1 日 00:00:00 UTC)以来经过的毫秒数
    return QDateTime::currentMSecsSinceEpoch();
}

3.根据QByteArray,转成QIcon

//根据QByteArray,转成QIcon
//qt中qicon是图标
//qpixmap提供很多的关于图片的处理方法
//通过上面两个的操作,完成通过字节数组构造qicon
//qimage
static inline QIcon makeIcon(const QByteArray& byteArray)
{
    QPixmap pixmap;
    pixmap.loadFromData(byteArray);
    QIcon icon(pixmap);
    return icon;
}

4.读写文件操作

//从指定文件中,读取所有的二进制内容,得到一个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 writeByteArrayToFile(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();
    return;
}

5.打印日志的宏

1.关于#define TAG QString(“[%1]”).arg(QString::number(__LINE__));

  • QString("[%1]") 创建了一个格式字符串,%1 是一个占位符。

  • QString::number(__LINE__) 将当前行号(__LINE__ 是一个预处理宏,表示当前代码的行号)转换为字符串。

  • .arg(...) 将行号替换到格式字符串中的占位符 %1

.arg(...) 是 QString 类中的一个成员函数,用于格式化字符串。它允许你将一个或多个值插入到字符串中的占位符位置。

2.#define LOG() qDebug() << TAG

image.png

3.显示文件名的方法

使用内置函数

image.png

使用自己创造的函数

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

三,核心类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在打印字符串的会加上引号.noquote
#define LOG() qDebug().noquote() << TAG



//要求函数的定义如果在.h中,必须加static或者inline(两个都加也可以),避免链接阶段出现“函数”
static inline QString formatTime(int64_t timestamp){
    //通过时间戳,转换成格式化时间
    //时间戳是指格林威治时间1970年01月01日00时00分00秒(北京时间1970年01月01日08时00分00秒)起至现在的总秒数
    //为什么使用int64因为使用int32会出现溢出情况

    //先把时间戳,转换成QDateTime对象
    QDateTime dateTime=QDateTime::fromSecsSinceEpoch(timestamp);
    //fromSecsSinceEpoch(timestamp)将时间戳转化为QDateTime对象

    //把对象转为“格式化时间”
    return dateTime.toString("MM-dd HH:mm:ss");

}
//获得秒级的时间
static inline int64_t getTime()
{
    //用于获取自 "Epoch" 时间(即 1970 年 1 月 1 日 00:00:00 UTC)以来经过的毫秒数
    return QDateTime::currentMSecsSinceEpoch();
}

//根据QByteArray,转成QIcon
//qt中qicon是图标
//qpixmap提供很多的关于图片的处理方法
//通过上面两个的操作,完成通过字节数组构造qicon
//qimage
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 writeByteArrayToFile(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();
    return;
}


//
///用户信息///
/
class UserInfo{
public:
    QString userID="";//用户的编号
    //int userId;使用字符串的方式来作为id,可以有更灵活的方式来进行生成
    //并且更好的适用分布式的mysql
    //后续可通过uuid或者雪花算法这样的方式来生成分布式系统中唯一标识的id
    QString nickname="";//用户的昵称
    QString description="";//用户签名
    QString phone="";//手机号码
    QIcon avatar;//用户头像
};

//
///消息信息
/
//会话消息是1对多
enum MessageType{
    TEXT_TYPE,//文本消息
    IMAGE_TYPE,//图像消息
    FILE_TYPE,//文件消息
    SPEECH_TYPE//语音消息
};

class Message{
public:
    QString messageId="";//消息的编号
    QString chatSessionId="";//消息所属会话编号
    QString time="";//消息的时间,通过格式化时间的方式来表示 形如 06-07 12:00
    MessageType messageType;//消息类型
    UserInfo sender;//发送者的信息
    QByteArray content;//消息的正文内容:文本就是字符串,图片、文件、语音就是二进制的序列
    // C和cpp没有byte类型,都是拿char/unsigned char来凑合的
    //例如在cpp中通过代码取出“string张三”的三很麻烦
    //1.先判定哪种编码方式 2.计算第二个汉字所属出字节范围 3.取字符串子串
    //但是QT相比之下做了更好的处理,QString就对上述情况处理的更好,QT明确区分了字节和字符,
    //表示字符串必须使用QString(内置了对不同字符集进行处理),
    //表示二进制数据必须使用QByteArray
    QString fileId="";//表示一个文件的身份标识,当消息类型为文件,图片,语音时才有效
    //一旦一个聊天会话中,包含上述多个这样的信息,就会从服务器获取消息列表,这样的操作,就变得非常低效
    //所以一般的做法是获取此三种消息的id,当客户端拿到消息列表之后,再根据拿到的field,给服务器发送额外的请求,获取文件内容
    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 QUuid::createUuid().toString();
        //取M做标识符字符串子串
        return "M"+QUuid::createUuid().toString().sliced(25,12);//从25位开始往后数12位

    }
    static Message makeTextMessage(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=TEXT_TYPE;
        //对于文本消息来说这两个属性不适用,设为空字符串
        message.fileId="";
        message.fileName="";
        return message;
    }
    static Message makeImageMessage(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=IMAGE_TYPE;
        //后续使用的时候进一步设置
        message.fileId="";
        //不使用
        message.fileName="";
        return message;
    }
    static Message makeFileMessage(const QString& chatSessionId,const UserInfo& sender,const QByteArray& content,const QString& extraInfo)
    {
        Message message;
        message.messageId=makeId();
        message.chatSessionId=chatSessionId;
        message.sender=sender;
        message.time =formatTime(getTime());//生成一个格式化时间
        message.content=content;
        message.messageType=FILE_TYPE;
        message.fileId="";
        message.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;
        message.fileId="";
        message.fileName="";
        return message;
    }
};

//
///会话信息
/
class ChatSessionInfo{
public:
    QString chatSessionID="";//会话编号
    QString chatSessionName="";//会话名字(有群聊和单聊)
    Message lastMessage;//在会话列表显示提示
    QIcon avatar;//会话的头像(有单聊和群聊之分)
    QString userId="";

};
}//end model描述结束命名空间

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值