html 网站发布,口腔医院网站源码,阿里巴巴国际站下载电脑版,做网站ps分辨率给多少钱C/S 架构的外卖平台引言本次作业中#xff0c;我在 Qt 框架下实现了一个 C/S 架构的外卖平台软件#xff0c;客户端使用到 Qt::Widgets 和 Qt::Network 模块#xff0c;服务器使用到 Qt::Sql 和 Qt::Network 模块。系统的应用情景是#xff1a;一个服务器实例服务多个客户端…C/S 架构的外卖平台引言本次作业中我在 Qt 框架下实现了一个 C/S 架构的外卖平台软件客户端使用到 Qt::Widgets 和 Qt::Network 模块服务器使用到 Qt::Sql 和 Qt::Network 模块。系统的应用情景是一个服务器实例服务多个客户端实例客户端使用了 GUI 界面支持用户注册、登录商家管理产品、处理订单不同优惠级别的订餐者查看产品、支付订单以及系统管理员查看销售额日志。功能介绍用户注册、登录商家添加产品商家查看和修改产品商家查看和处理订单订餐者查看和添加购物车订餐者查看和结算订单订餐者优惠等级的变化管理员查询销售日志系统分析与设计概述和类图客户端的行为控制集成在 MainWidget 类中分为网络通信和 GUI 窗体展示两个部分和服务器基本的交互模式是在按钮控件点击行为的回调里向服务器发送对应的任务 id 和当前界面表达的信息收到服务器合法的回复后刷新当前界面跳转到对应的数据展示界面GUI 方面使用建造者模式为不同用户构建不同的功能菜单客户端的数据视图分为用户信息视图 UserForm用户登录和注册、产品信息视图 ProductForm 和订单信息视图 OrderForm所有信息视图维护一个具体类的全部信息得到提交请求的时候收集对应的信息进行序列化并发送考虑产品和订单具有相似的展示需求即展示所有和编辑单个产品信息和订单信息以子窗口行的形式罗列在带滚动条控件 QScrollArea 的 ViewWidget 类中通过直接在列表里选中、修改对应的子窗口行来进行单项编辑。特别地对于产品列表通过弹出新的产品信息子窗口进行添加对于订单列表添加行为发生在产品列表中订单列表即用户的购物车、商家的订单只维护修改状态和提交修改请求的行为。服务器单例 FoodServer 通过持有的 QTcpServer 变量处理连接请求为每一个客户端的接入创建一个 QTcpSocket对于 QTcpSocket 每一次接到的流数据构造一个 ServerNetService 进行处理ServerNetService 读取流数据开头的任务 id 区分客户端请求通过 DbHandlerFactory 获得 User、Product、Order 对应的 DbHandler 执行数据库的增删查改DbHandler 的抽象基类封装了 QSqlQuery 提供的基于执行 SQLite 语句的数据库操作和对应调试结果打印操作通过具体 DbHandler 中的众多 static 方法执行数据库返回值的解析任务将解析结果标记上通信协议指定的任务 id通过 QTcpSocket 返回给客户端。客户端类图服务器类图实现方案四个典型的设计模式服务器单例模式// 实现 class FoodServer : public QObject { Q_OBJECT public: ~FoodServer(); static FoodServer getInstance(); FoodServer(const FoodServer ) delete; FoodServer operator(const FoodServer ) delete; private: FoodServer(); QTcpServer *server; }; // 使用 auto server FoodServer::getInstance();FoodServer 的监听行为在构造函数里初始化通过 QCoreApplication::exec() 的循环机制在下面的代码中插入实例化语句即可做到维护一个全局的 FoodServer 实例。QCoreApplication a(argc, argv); // insert getInstance here return a.exec();简单工厂模式简单工厂模式应用在 DataBaseHandler 上具体 DataBaseHandler 的处理数据的静态方法通过简单工厂方法获得对应具体 DataBaseHandler 的数据库操作行为。类图如下// 使用 QString username ...; auto db DbHandlerFactory::getDbHandler(UserTable); db-queryByString(select, uname, username);策略模式策略模式被用于服务端的用户类执行结算操作上。处于安全性设计考虑结算行为发生在 Server 端。类图如下// 使用 double User::pay(double _pay) { return discount-calculate(_pay); } double pay user.pay(order.getPrice() * order.getOnum()); order.setOpay(pay);客户端建造者模式建造者模式被用于功能菜单 TodoForm 的构建上职责是为不同用户显示不同的具体功能菜单显示不同的用户信息提示。类图如下// 使用 TodoFormBuilder *builder; TodoFormController controller; switch (user.getUtype()) { case seller: builder new SellerTodoBuilder(); userTodoForm controller.createUserTodoForm(builder, user); ...// connect signals and slots break; case admin: builder new AdminTodoBuilder(); userTodoForm controller.createUserTodoForm(builder, user); ...// connect signals and slots break; case buyer: case vip1: case vip2: builder new BuyerTodoBuilder(); userTodoForm controller.createUserTodoForm(builder, user); ...// connect signals and slots } delete builder;网络通信Qt::Network 库封装了发送和接收 tcp 报文的操作但是没有提供完整接收所有内容的接口这意味者一个较大的序列化数据流会被拆分成多个 tcp 包多次触发 QTcpSocket::readyRead 信号导致无法一次性正确读取序列化数据。测试中发现客户端向服务器的数据全部可以在一个 tcp 包中被发送服务器返回的列表数据比如全部订单、全部产品大多不能在一个 tcp 包中被发送所以服务端数据流需要存储长度信息供客户端判断和拼接对应的 tcp 包中的数据。// 服务端发送 TCP 包 QByteArray ServerNetService::process() { QDataStream reader(buffer, QIODevice::ReadOnly); reader taskid; QByteArray result; QDataStream writter(result, QIODevice::WriteOnly); qint64 dataSize 0; writter dataSize; switch (TaskId(taskid)) { ... // 调用数据库操作 } dataSize result.size(); // 向buffer头写入buffer长度 QDataStream _writter(result, QIODevice::WriteOnly); _writter dataSize; return result; }connect(socket, QTcpSocket::readyRead, []() { // 客户端的数据可以一次读完 ServerNetService netio(socket-readAll()); socket-write(netio.process()); });// 客户端接收 TCP 包需要考虑多次读取在拼接的问题 connect(socket, QTcpSocket::readyRead, []() { QDataStream preReader(toAppendBuffer, QIODevice::ReadOnly); auto tmp socket-readAll(); if (toAppendBuffer.isEmpty()) { // 读第一个包 toAppendBuffer.append(tmp); preReader toReceiveBufferSize; if (toAppendBuffer.size() toReceiveBufferSize) return; } else { // 读中间的tcp包 toAppendBuffer.append(tmp); if (toAppendBuffer.size() toReceiveBufferSize) return; }// 读完一个完整的数据流进行进一步处理 QDataStream reader(toAppendBuffer, QIODevice::ReadOnly); reader toReceiveBufferSize; int taskid; reader taskid; process(TaskId(taskid), reader); toAppendBuffer.clear(); });数据库操作以产品表为例class ProductDbHandler : public DataBaseHandler { public: ProductDbHandler() { setTableName(ProductTable); } static inline QListProduct queryProducts(QSqlQuery q); static void db_addProduct(const Product product, bool putId false); static void db_delProduct(const int pid); static QListProduct db_getProduct();//所有产品 static QListProduct db_getProduct(const int pid);//指定编号的产品 static QListProduct db_getProduct4Seller(const int uid);//一个商家的所有产品 }; class DbHandlerFactory { public: static std::shared_ptrDataBaseHandler getDbHandler(const QString tableName, bool logging false); }; // 举一个静态成员函数的例子 void ProductDbHandler::db_delProduct(const int pid) { auto db DbHandlerFactory::getDbHandler(ProductTable); db-queryByInt(delete, pid, pid); db-exec(); }之所以要以静态成员函数这种不够面向对象的方式编写数据库操作方法是因为查询订单、产品列表的返回值都是 QList 具体类 没有使用指针或智能指针来实例化模板不方便设计出统一的抽象来处理返回值。这里只对基本的数据库语句执行方法进行了一定的抽象来简化代码。序列化和反序列化QDataStream 类支持通过重载 和 操作符实现对自定义数据的序列化和反序列化对于列表数据QDataStream 类支持对 QList类型进行序列化其中的 class 必须有 和 的具体实现。以 User 类为例QDataStream operator(QDataStream s, const User user) { s user.uid user.utype user.uname user.password user.photo; return s; } QDataStream operator(QDataStream s, User user) { s user.uid user.utype user.uname user.password user.photo; return s; }GUI 控制GUI 控制依赖 Qt 的信号-槽机制类似回调函数原理在此不赘述。总结三个触发窗口变化的情景用户编辑文本框文本、编辑复选框、下拉菜单用户点击按钮客户端收到服务器应答用户编辑输入控件的行为由 Qt 框架后台管理只需要处理类似产品数量和总价的同步变化关系按钮点击的响应有拉起 QMessageBox 提示和提交网络请求两种后续行为收到服务器应答意味着按钮发送的请求得到回复会对回复进行反序列化将数据呈现到新的展示窗口上。销售日志直接进行条件查询QListOrder OrderDbHandler::db_getOrderDone() { auto db DbHandlerFactory::getDbHandler(OrderTable); db-queryByInt(select, ostate, Ostate::done);// 查询完成的订单 return OrderDbHandler::queryOrders(db-exec()); }然后用哈希表进行计数QHashQString, double OrderDbHandler::getCountByMonth(const QListOrder orders) { QHashQString, double count; for (auto r:orders) { // 日期示例周六 6月 20 09:31:45 2020 auto split r.getSubmittime().split( ); if (count.find(split[1]) count.end()) { count[split[1]] r.getOpay(); } else { count[split[1]] r.getOpay(); } } return count; }系统符合设计原则的情况服务器拆分出了网络通信类和数据库类符合单一职责原则客户端将网络通信和 GUI 交互整合在一起不符合单一职责原则Order 类和 OrderWithFullInfo 类的设计符合里氏替换原则OrderWithFullInfo 类只是在 Order 类的基础上增加了一些成员变量和函数继承关系没有破坏 Order 类原有的函数FoodServer 类持有 QTcpServer 的引用而不是继承并覆写相关方法符合合成/聚合复用原则小结本次作业中实现的外卖平台软件具备 C/S 架构支持多用户同时登陆的交互场景实现了 GUI 界面使用到了单例模式、简单工厂模式、策略模式和建造者模式充分考虑了交互过程中的各种可能性具有较好的鲁棒性