微服务即时通讯系统(5)用户管理子服务,网关子服务

  1. 用户管理子服务(user文件)

   用户管理子服务也是这个项目中的一个业务最多的子服务,接口多,但是主要涉及的数据表只有user表,Redis的键值对和ES的一个搜索引擎,主要功能是对用户的个人信息进行修改管理,对用户的登录进行管理,用户注册进行管理

用户管理子服务的所有接口

   service UserService {

 rpc UserRegister(UserRegisterReq) returns (UserRegisterRsp);

 rpc UserLogin(UserLoginReq) returns (UserLoginRsp);

 rpc GetPhoneVerifyCode(PhoneVerifyCodeReq) returns 

(PhoneVerifyCodeRsp);

 rpc PhoneRegister(PhoneRegisterReq) returns 

(PhoneRegisterRsp);

 rpc PhoneLogin(PhoneLoginReq) returns (PhoneLoginRsp);

 rpc GetUserInfo(GetUserInfoReq) returns (GetUserInfoRsp);

 rpc GetMultiUserInfo(GetMultiUserInfoReq) returns 

(GetMultiUserInfoRsp);

 rpc SetUserAvatar(SetUserAvatarReq) returns 

(SetUserAvatarRsp);

 rpc SetUserNickname(SetUserNicknameReq) returns 

(SetUserNicknameRsp);

 rpc SetUserDescription(SetUserDescriptionReq) returns 

(SetUserDescriptionRsp);

 rpc SetUserPhoneNumber(SetUserPhoneNumberReq) returns 

(SetUserPhoneNumberRsp);

}

  1. 用户昵称加密码的方式进行注册接口:先从request里面接收用户的nickname和密码,然后对nickname和密码进行检查,看看是不是符合长度要求,如果符合,为其生成用户ID后添加到user数据库和ES的user搜索引擎里面,设置response为成功,如果不符合,则不添加,并且设置response为失败,添加失败信息,结束

virtual void UserRegister(::google::protobuf::RpcController* controller,

            const ::zhou::UserRegisterReq* request,

            ::zhou::UserRegisterRsp* response,

            ::google::protobuf::Closure* done) {

            LOG_DEBUG("收到用户注册请求!");

            brpc::ClosureGuard rpc_guard(done);

            //定义一个错误处理函数,当出错的时候被调用

            auto err_response = [this, response](const std::string &rid,

                const std::string &errmsg) -> void {

                response->set_request_id(rid);

                response->set_success(false);

                response->set_errmsg(errmsg);

                return;

            };

            //1. 从请求中取出昵称和密码

            std::string nickname = request->nickname();

            std::string password = request->password();

            //2. 检查昵称是否合法(只能包含字母,数字,连字符-,下划线_,长度限制 3~15 之间)

            bool ret = nickname_check(nickname);

            if (ret == false) {

                LOG_ERROR("{} - 用户名长度不合法!", request->request_id());

                return err_response(request->request_id(), "用户名长度不合法!");

            }

            //3. 检查密码是否合法(只能包含字母,数字,长度限制 6~15 之间)

            ret = password_check(password);

            if (ret == false) {

                LOG_ERROR("{} - 密码格式不合法!", request->request_id());

                return err_response(request->request_id(), "密码格式不合法!");

            }

            //4. 根据昵称在数据库进行判断是否昵称已存在

            auto user = _mysql_user->select_by_nickname(nickname);

            if (user) {

                LOG_ERROR("{} - 用户名被占用- {}!", request->request_id(), nickname);

                return err_response(request->request_id(), "用户名被占用!");

            }

            //5. 向数据库新增数据

            std::string uid = uuid();

            user = std::make_shared<User>(uid, nickname, password);

            ret = _mysql_user->insert(user);

            if (ret == false) {

                LOG_ERROR("{} - Mysql数据库新增数据失败!", request->request_id());

                return err_response(request->request_id(), "Mysql数据库新增数据失败!");

            }

            //6. 向 ES 服务器中新增用户信息

            ret = _es_user->appendData(uid, "", nickname, "", "");

            if (ret == false) {

                LOG_ERROR("{} - ES搜索引擎新增数据失败!", request->request_id());

                return err_response(request->request_id(), "ES搜索引擎新增数据失败!");

            }

            //7. 组织响应,进行成功与否的响应即可。

            response->set_request_id(request->request_id());

            response->set_success(true);

        }

  1. 用户昵称加密码登录接口设计:先从request里面获取nickname和密码,然后进行判断是否合法,然后从Redis的状态键值对里面判断用户是否已经登录,然后从数据库中获取nickname对应的密码,如果不存在则设置response为失败,检查密码是否和传入的密码相同,如果是则登录成功,为用户创建一个登录会话ID,对Redis的状态键值对和会话键值对进行添加,然后对response设置对应的回话ID,然后返回

 virtual void UserLogin(::google::protobuf::RpcController* controller,

            const ::zhou::UserLoginReq* request,

            ::zhou::UserLoginRsp* response,

            ::google::protobuf::Closure* done){

            LOG_DEBUG("收到用户登录请求!");

            brpc::ClosureGuard rpc_guard(done);

            auto err_response = [this, response](const std::string &rid,

                const std::string &errmsg) -> void {

                response->set_request_id(rid);

                response->set_success(false);

                response->set_errmsg(errmsg);

                return;

            };

            //1. 从请求中取出昵称和密码

            std::string nickname = request->nickname();

            std::string password = request->password();

            //2. 通过昵称获取用户信息,进行密码是否一致的判断

            auto user = _mysql_user->select_by_nickname(nickname);

            if (!user || password != user->password()) {

                LOG_ERROR("{} - 用户名或密码错误 - {}-{}!", request->request_id(), nickname, password);

                return err_response(request->request_id(), "用户名或密码错误!");

            }

            //3. 根据 redis 中的登录标记信息是否存在判断用户是否已经登录。

            bool ret = _redis_status->exists(user->user_id());

            if (ret == true) {

                LOG_ERROR("{} - 用户已在其他地方登录 - {}!", request->request_id(), nickname);

                return err_response(request->request_id(), "用户已在其他地方登录!");

            }

            //4. 构造会话 ID,生成会话键值对,向 redis 中添加会话信息以及登录标记信息

            std::string ssid = uuid();

            _redis_session->append(ssid, user->user_id());

            //5. 添加用户登录信息

            _redis_status->append(user->user_id());

            //5. 组织响应,返回生成的会话 ID

            response->set_request_id(request->request_id());

            response->set_login_session_id(ssid);

            response->set_success(true);

        }

  1. 手机号验证码发送接口:从request里面获取电话号码,判断电话号码是否合法,然后生成四位随机验证码和事件ID,将事件ID和验证码一起放入Redis的验证码键值对缓存里面,然后调用验证码发送的SDK服务向对应的手机号发送验证码,对response进行设置此次发送事件的ID,结束

virtual void GetPhoneVerifyCode(::google::protobuf::RpcController* controller,

            const ::zhou::PhoneVerifyCodeReq* request,

            ::zhou::PhoneVerifyCodeRsp* response,

            ::google::protobuf::Closure* done){

            LOG_DEBUG("收到短信验证码获取请求!");

            brpc::ClosureGuard rpc_guard(done);

            auto err_response = [this, response](const std::string &rid,

                const std::string &errmsg) -> void {

                response->set_request_id(rid);

                response->set_success(false);

                response->set_errmsg(errmsg);

                return;

            };

            // 1. 从请求中取出手机号码

            std::string phone = request->phone_number();

            // 2. 验证手机号码格式是否正确(必须以 1 开始,第二位 3~9 之间,后边 9 个数字字符)

            bool ret = phone_check(phone);

            if (ret == false) {

                LOG_ERROR("{} - 手机号码格式错误 - {}!", request->request_id(), phone);

                return err_response(request->request_id(), "手机号码格式错误!");

            }

            // 3. 生成 4 位随机验证码

            std::string code_id = uuid();

            std::string code = vcode();

            // 4. 基于短信平台 SDK 发送验证码

            ret = _dms_client->send(phone, code);

            if (ret == false) {

                LOG_ERROR("{} - 短信验证码发送失败 - {}!", request->request_id(), phone);

                return err_response(request->request_id(), "短信验证码发送失败!");

            }

            // 5. 构造验证码 ID,添加到 redis 验证码映射键值索引中

            _redis_codes->append(code_id, code);

            // 6. 组织响应,返回生成的验证码 ID

            response->set_request_id(request->request_id());

            response->set_success(true);

            response->set_verify_code_id(code_id);

            LOG_DEBUG("获取短信验证码处理完成!");

        }

  1. 手机号注册接口实现:先从request里面获取验证码事件ID和验证码,手机号,用户名,然后从Redis里面的验证码键值对里面取出对应的验证码,判断是否一致,如果一致则进行注册,就是向use表和ES所对应的搜索引擎里面添加对应数据(用户名和手机号码),然后设置response成功,结束,(5)并且手机号登录接口就是在nickname加密码登录的逻辑下加入了手机号判断和验证码判断,其余都一样,就不在赘述

 virtual void PhoneRegister(::google::protobuf::RpcController* controller,

            const ::zhou::PhoneRegisterReq* request,

            ::zhou::PhoneRegisterRsp* response,

            ::google::protobuf::Closure* done){

            LOG_DEBUG("收到手机号注册请求!");

            brpc::ClosureGuard rpc_guard(done);

            auto err_response = [this, response](const std::string &rid,

                const std::string &errmsg) -> void {

                response->set_request_id(rid);

                response->set_success(false);

                response->set_errmsg(errmsg);

                return;

            };

            // 1. 从请求中取出手机号码和验证码,验证码ID

            std::string phone = request->phone_number();

            std::string code_id = request->verify_code_id();

            std::string code = request->verify_code();

            // 2. 检查注册手机号码是否合法

            bool ret = phone_check(phone);

            if (ret == false) {

                LOG_ERROR("{} - 手机号码格式错误 - {}!", request->request_id(), phone);

                return err_response(request->request_id(), "手机号码格式错误!");

            }

            // 3. 从 redis 数据库中进行验证码 ID-验证码一致性匹配

            auto vcode = _redis_codes->code(code_id);

            if (vcode != code) {

                LOG_ERROR("{} - 验证码错误 - {}-{}!", request->request_id(), code_id, code);

                return err_response(request->request_id(), "验证码错误!");

            }

            // 4. 通过数据库查询判断手机号是否已经注册过

            auto user = _mysql_user->select_by_phone(phone);

            if (user) {

                LOG_ERROR("{} - 该手机号已注册过用户 - {}!", request->request_id(), phone);

                return err_response(request->request_id(), "该手机号已注册过用户!");

            }

            // 5. 向数据库新增用户信息

            std::string uid = uuid();

            user = std::make_shared<User>(uid, phone);

            ret = _mysql_user->insert(user);

            if (ret == false) {

                LOG_ERROR("{} - 向数据库添加用户信息失败 - {}!", request->request_id(), phone);

                return err_response(request->request_id(), "向数据库添加用户信息失败!");

            }

            // 6. 向 ES 服务器中新增用户信息

            ret = _es_user->appendData(uid, phone, uid, "", "");

            if (ret == false) {

                LOG_ERROR("{} - ES搜索引擎新增数据失败!", request->request_id());

                return err_response(request->request_id(), "ES搜索引擎新增数据失败!");

            }

            //7. 组织响应,进行成功与否的响应即可。

            response->set_request_id(request->request_id());

            response->set_success(true);

        }

  1. 获取用户个人信息接口:先从request里面获取用户的uid,然后从user表中查询获取用户的nickname,description,phone,头像ID,然后根据头像ID向文件子服务发起请求,获取头像文件数据,然后将这些放入response中,结束。批量获取用户信息的接口就是在这个接口的基础上取出的是用户ID列表,然后再根据用户ID列表获取用户信息列表,得到用户头像列表,然后下载用户头像,后面就不对这个接口进行赘述

 virtual void GetUserInfo(::google::protobuf::RpcController* controller,

            const ::zhou::GetUserInfoReq* request,

            ::zhou::GetUserInfoRsp* response,

            ::google::protobuf::Closure* done){

            LOG_DEBUG("收到获取单个用户信息请求!");

            brpc::ClosureGuard rpc_guard(done);

            auto err_response = [this, response](const std::string &rid,

                const std::string &errmsg) -> void {

                response->set_request_id(rid);

                response->set_success(false);

                response->set_errmsg(errmsg);

                return;

            };

            // 1. 从请求中取出用户 ID

            std::string uid = request->user_id();

            // 2. 通过用户 ID,从数据库中查询用户信息

            auto user = _mysql_user->select_by_id(uid);

            if (!user) {

                LOG_ERROR("{} - 未找到用户信息 - {}!", request->request_id(), uid);

                return err_response(request->request_id(), "未找到用户信息!");

            }

            // 3. 根据用户信息中的头像 ID,从文件服务器获取头像文件数据,组织完整用户信息

            UserInfo *user_info = response->mutable_user_info();

            user_info->set_user_id(user->user_id());

            user_info->set_nickname(user->nickname());

            user_info->set_description(user->description());

            user_info->set_phone(user->phone());

           

            if (!user->avatar_id().empty()) {

                //从信道管理对象中,获取到连接了文件管理子服务的channel

                auto channel = _mm_channels->choose(_file_service_name);

                if (!channel) {

                    LOG_ERROR("{} - 未找到文件管理子服务节点 - {} - {}!",

                        request->request_id(), _file_service_name, uid);

                    return err_response(request->request_id(), "未找到文件管理子服务节点!");

                }

                //进行文件子服务的rpc请求,进行头像文件下载

                zhou::FileService_Stub stub(channel.get());

                zhou::GetSingleFileReq req;

                zhou::GetSingleFileRsp rsp;

                req.set_request_id(request->request_id());

                req.set_file_id(user->avatar_id());

                brpc::Controller cntl;

                stub.GetSingleFile(&cntl, &req, &rsp, nullptr);

                if (cntl.Failed() == true || rsp.success() == false) {

                    LOG_ERROR("{} - 文件子服务调用失败:{}!", request->request_id(), cntl.ErrorText());

                    return err_response(request->request_id(), "文件子服务调用失败!");

                }

                user_info->set_avatar(rsp.file_data().file_content());

            }

            // 4. 组织响应,返回用户信息

            response->set_request_id(request->request_id());

            response->set_success(true);

        }

  1. 修改用户头像接口:从request里面取出用户ID和头像数据,然后通过文件子服务将头像数据进行上传,得到头像ID,再对user表里面的用户ID对应的数据进行头像ID的修改,然后再对ES表中搜索引擎的数据进行修改,结束

 virtual void SetUserAvatar(::google::protobuf::RpcController* controller,

            const ::zhou::SetUserAvatarReq* request,

            ::zhou::SetUserAvatarRsp* response,

            ::google::protobuf::Closure* done){

            LOG_DEBUG("收到用户头像设置请求!");

            brpc::ClosureGuard rpc_guard(done);

            auto err_response = [this, response](const std::string &rid,

                const std::string &errmsg) -> void {

                response->set_request_id(rid);

                response->set_success(false);

                response->set_errmsg(errmsg);

                return;

            };

            // 1. 从请求中取出用户 ID 与头像数据

            std::string uid = request->user_id();

            // 2. 从数据库通过用户 ID 进行用户信息查询,判断用户是否存在

            auto user = _mysql_user->select_by_id(uid);

            if (!user) {

                LOG_ERROR("{} - 未找到用户信息 - {}!", request->request_id(), uid);

                return err_response(request->request_id(), "未找到用户信息!");

            }

            // 3. 上传头像文件到文件子服务,

            auto channel = _mm_channels->choose(_file_service_name);

            if (!channel) {

                LOG_ERROR("{} - 未找到文件管理子服务节点 - {}!", request->request_id(), _file_service_name);

                return err_response(request->request_id(), "未找到文件管理子服务节点!");

            }

            zhou::FileService_Stub stub(channel.get());

            zhou::PutSingleFileReq req;

            zhou::PutSingleFileRsp rsp;

            req.set_request_id(request->request_id());

            req.mutable_file_data()->set_file_name("");

            req.mutable_file_data()->set_file_size(request->avatar().size());

            req.mutable_file_data()->set_file_content(request->avatar());

            brpc::Controller cntl;

            stub.PutSingleFile(&cntl, &req, &rsp, nullptr);

            if (cntl.Failed() == true || rsp.success() == false) {

                LOG_ERROR("{} - 文件子服务调用失败:{}!", request->request_id(), cntl.ErrorText());

                return err_response(request->request_id(), "文件子服务调用失败!");

            }

            std::string avatar_id = rsp.file_info().file_id();

            // 4. 将返回的头像文件 ID 更新到数据库中

            user->avatar_id(avatar_id);

            bool ret = _mysql_user->update(user);

            if (ret == false) {

                LOG_ERROR("{} - 更新数据库用户头像ID失败 :{}!", request->request_id(), avatar_id);

                return err_response(request->request_id(), "更新数据库用户头像ID失败!");

            }

            // 5. 更新 ES 服务器中用户信息

            ret = _es_user->appendData(user->user_id(), user->phone(),

                user->nickname(), user->description(), user->avatar_id());

            if (ret == false) {

                LOG_ERROR("{} - 更新搜索引擎用户头像ID失败 :{}!", request->request_id(), avatar_id);

                return err_response(request->request_id(), "更新搜索引擎用户头像ID失败!");

            }

            // 6. 组织响应,返回更新成功与否

            response->set_request_id(request->request_id());

            response->set_success(true);

        }

  1. 修改用户nickname接口:从request里面获取用户ID和nickname,然后在从数据库中对用户ID进行查询,获取对应的用户数据,将用户数据里面的nickname进行修改,然后也对ES搜索引擎中对应用户ID的nickname进行修改,结束,同样的,用户description修改的接口也是一样的,而手机号的修改接口,是在此基础上先判断手机号是否正确,然后查询手机号是否已经被绑定,然后再进行验证码判断,如果都没问题就想数据库和ES里面更新数据,所以后面就不再赘述

 virtual void SetUserNickname(::google::protobuf::RpcController* controller,

            const ::zhou::SetUserNicknameReq* request,

            ::zhou::SetUserNicknameRsp* response,

            ::google::protobuf::Closure* done){

            LOG_DEBUG("收到用户昵称设置请求!");

            brpc::ClosureGuard rpc_guard(done);

            auto err_response = [this, response](const std::string &rid,

                const std::string &errmsg) -> void {

                response->set_request_id(rid);

                response->set_success(false);

                response->set_errmsg(errmsg);

                return;

            };

            // 1. 从请求中取出用户 ID 与新的昵称

            std::string uid = request->user_id();

            std::string new_nickname = request->nickname();

            // 2. 判断昵称格式是否正确

            bool ret = nickname_check(new_nickname);

            if (ret == false) {

                LOG_ERROR("{} - 用户名长度不合法!", request->request_id());

                return err_response(request->request_id(), "用户名长度不合法!");

            }

            // 3. 从数据库通过用户 ID 进行用户信息查询,判断用户是否存在

            auto user = _mysql_user->select_by_id(uid);

            if (!user) {

                LOG_ERROR("{} - 未找到用户信息 - {}!", request->request_id(), uid);

                return err_response(request->request_id(), "未找到用户信息!");

            }

            // 4. 将新的昵称更新到数据库中

            user->nickname(new_nickname);

            ret = _mysql_user->update(user);

            if (ret == false) {

                LOG_ERROR("{} - 更新数据库用户昵称失败 :{}!", request->request_id(), new_nickname);

                return err_response(request->request_id(), "更新数据库用户昵称失败!");

            }

            // 5. 更新 ES 服务器中用户信息

            ret = _es_user->appendData(user->user_id(), user->phone(),

                user->nickname(), user->description(), user->avatar_id());

            if (ret == false) {

                LOG_ERROR("{} - 更新搜索引擎用户昵称失败 :{}!", request->request_id(), new_nickname);

                return err_response(request->request_id(), "更新搜索引擎用户昵称失败!");

            }

            // 6. 组织响应,返回更新成功与否

            response->set_request_id(request->request_id());

            response->set_success(true);

        }

  1. 网关子服务(gateway文件)

   网关子服务的作用是对客户端发来的请求(httplib库进行实现)序列化为rpc请求进行转发,发送到对应的微服务器,然后将接收到的微服务器处理好的响应序列化为http报文返回客户端,并且对于需要服务端主动传输到客户端的通知采用websocket来管理链接进行传输

先介绍对于长连接管理的类:

主要含有的成员变量是这三个

std::mutex _mutex;

        std::unordered_map<std::string, server_t::connection_ptr> _uid_connections;

        std::unordered_map<server_t::connection_ptr, Client> _conn_clients;

其中的uid_connect是管理用户ID和对应的socket的链接

_conn_connect是通过链接来找到对应客户端的

下面是对websocket关键初始化的动作的代码解释

_ws_server.set_open_handler(std::bind(&GatewayServer::onOpen, this, std::placeholders::_1));

                _ws_server.set_close_handler(std::bind(&GatewayServer::onClose, this, std::placeholders::_1));

                auto wscb = std::bind(&GatewayServer::onMessage, this,

                    std::placeholders::_1, std::placeholders::_2);

                _ws_server.set_message_handler(wscb);

Open函数只是简单的显示连接的产生,这里不做赘述

Close函数是长连接断开后,也就是客户端下线后,将Redis里面的状态键值对和会话键值对进行删除然后移除管理的长连接

void onClose(websocketpp::connection_hdl hdl) {

                //长连接断开时做的清理工作

                //0. 通过连接对象,获取对应的用户ID与登录会话ID

                auto conn = _ws_server.get_con_from_hdl(hdl);

                std::string uid, ssid;

                bool ret = _connections->client(conn, uid, ssid);

                if (ret == false) {

                    LOG_WARN("长连接断开,未找到长连接对应的客户端信息!");

                    return ;

                }

                //1. 移除登录会话信息

                _redis_session->remove(ssid);

                //2. 移除登录状态信息

                _redis_status->remove(uid);

                //3. 移除长连接管理数据

                _connections->remove(conn);

                LOG_DEBUG("{} {} {} 长连接断开,清理缓存数据!", ssid, uid, (size_t)conn.get());

            }

而当websocket接收到消息以后就会实现对长连接的管理和保活,就是将其添加到Redis上面的两个键值对中和connect类里面,实现长连接和客户端的对应,长连接保活主要是通过对客户端定期检查,判断客户端的链接情况是否正常,如果正常则进行ping,维护长连接,如果有问题,则断开

auto conn = _ws_server.get_con_from_hdl(hdl);

                //2. 针对消息内容进行反序列化 -- ClientAuthenticationReq -- 提取登录会话ID

                ClientAuthenticationReq request;

                bool ret = request.ParseFromString(msg->get_payload());

                if (ret == false) {

                    LOG_ERROR("长连接身份识别失败:正文反序列化失败!");

                    _ws_server.close(hdl, websocketpp::close::status::unsupported_data, "正文反序列化失败!");

                    return;

                }

                //3. 在会话信息缓存中,查找会话信息

                std::string ssid = request.session_id();

                auto uid = _redis_session->uid(ssid);

                //4. 会话信息不存在则关闭连接

                if (!uid) {

                    LOG_ERROR("长连接身份识别失败:未找到会话信息 {}!", ssid);

                    _ws_server.close(hdl, websocketpp::close::status::unsupported_data, "未找到会话信息!");

                    return;

                }

                //5. 会话信息存在,则添加长连接管理

                _connections->insert(conn, *uid, ssid);

                LOG_DEBUG("新增长连接管理:{}-{}-{}", ssid, *uid, (size_t)conn.get());

                keepAlive(conn);

            }

void keepAlive(server_t::connection_ptr conn) {

                if (!conn || conn->get_state() != websocketpp::session::state::value::open) {

                    LOG_DEBUG("非正常连接状态,结束连接保活");

                    return;

                }

                conn->ping("");

                _ws_server.set_timer(60000, std::bind(&GatewayServer::keepAlive, this, conn));

            }

下面是httplib的请求路径和对应函数,虽然看上去接口众多,但是多数的接口逻辑都是一样的,都是将http报文序列化为对应的微服务器的请求报文,然后进行rpc调用,获得对应微服务器的响应以后将响应序列化为对应的http响应报文,不过部分会影响其他用户的操作,比如添加好友的行为,就会采用websocket的链接管理对被影响的用户发送对应的通知

_http_server.Post(GET_PHONE_VERIFY_CODE  , (httplib::Server::Handler)std::bind(&GatewayServer::GetPhoneVerifyCode         , this, std::placeholders::_1, std::placeholders::_2));

                _http_server.Post(USERNAME_REGISTER      , (httplib::Server::Handler)std::bind(&GatewayServer::UserRegister               , this, std::placeholders::_1, std::placeholders::_2));

                _http_server.Post(USERNAME_LOGIN         , (httplib::Server::Handler)std::bind(&GatewayServer::UserLogin                  , this, std::placeholders::_1, std::placeholders::_2));

                _http_server.Post(PHONE_REGISTER         , (httplib::Server::Handler)std::bind(&GatewayServer::PhoneRegister              , this, std::placeholders::_1, std::placeholders::_2));

                _http_server.Post(PHONE_LOGIN            , (httplib::Server::Handler)std::bind(&GatewayServer::PhoneLogin                 , this, std::placeholders::_1, std::placeholders::_2));

                _http_server.Post(GET_USERINFO           , (httplib::Server::Handler)std::bind(&GatewayServer::GetUserInfo                , this, std::placeholders::_1, std::placeholders::_2));

                _http_server.Post(SET_USER_AVATAR        , (httplib::Server::Handler)std::bind(&GatewayServer::SetUserAvatar              , this, std::placeholders::_1, std::placeholders::_2));

                _http_server.Post(SET_USER_NICKNAME      , (httplib::Server::Handler)std::bind(&GatewayServer::SetUserNickname            , this, std::placeholders::_1, std::placeholders::_2));

                _http_server.Post(SET_USER_DESC          , (httplib::Server::Handler)std::bind(&GatewayServer::SetUserDescription         , this, std::placeholders::_1, std::placeholders::_2));

                _http_server.Post(SET_USER_PHONE         , (httplib::Server::Handler)std::bind(&GatewayServer::SetUserPhoneNumber         , this, std::placeholders::_1, std::placeholders::_2));

                _http_server.Post(FRIEND_GET_LIST        , (httplib::Server::Handler)std::bind(&GatewayServer::GetFriendList              , this, std::placeholders::_1, std::placeholders::_2));

                _http_server.Post(FRIEND_APPLY           , (httplib::Server::Handler)std::bind(&GatewayServer::FriendAdd                  , this, std::placeholders::_1, std::placeholders::_2));

                _http_server.Post(FRIEND_APPLY_PROCESS   , (httplib::Server::Handler)std::bind(&GatewayServer::FriendAddProcess           , this, std::placeholders::_1, std::placeholders::_2));

                _http_server.Post(FRIEND_REMOVE          , (httplib::Server::Handler)std::bind(&GatewayServer::FriendRemove               , this, std::placeholders::_1, std::placeholders::_2));

                _http_server.Post(FRIEND_SEARCH          , (httplib::Server::Handler)std::bind(&GatewayServer::FriendSearch               , this, std::placeholders::_1, std::placeholders::_2));

                _http_server.Post(FRIEND_GET_PENDING_EV  , (httplib::Server::Handler)std::bind(&GatewayServer::GetPendingFriendEventList  , this, std::placeholders::_1, std::placeholders::_2));

                _http_server.Post(CSS_GET_LIST           , (httplib::Server::Handler)std::bind(&GatewayServer::GetChatSessionList         , this, std::placeholders::_1, std::placeholders::_2));

                _http_server.Post(CSS_CREATE             , (httplib::Server::Handler)std::bind(&GatewayServer::ChatSessionCreate          , this, std::placeholders::_1, std::placeholders::_2));

                _http_server.Post(CSS_GET_MEMBER         , (httplib::Server::Handler)std::bind(&GatewayServer::GetChatSessionMember       , this, std::placeholders::_1, std::placeholders::_2));

                _http_server.Post(MSG_GET_RANGE          , (httplib::Server::Handler)std::bind(&GatewayServer::GetHistoryMsg              , this, std::placeholders::_1, std::placeholders::_2));

                _http_server.Post(MSG_GET_RECENT         , (httplib::Server::Handler)std::bind(&GatewayServer::GetRecentMsg               , this, std::placeholders::_1, std::placeholders::_2));

                _http_server.Post(MSG_KEY_SEARCH         , (httplib::Server::Handler)std::bind(&GatewayServer::MsgSearch                  , this, std::placeholders::_1, std::placeholders::_2));

                _http_server.Post(NEW_MESSAGE            , (httplib::Server::Handler)std::bind(&GatewayServer::NewMessage                 , this, std::placeholders::_1, std::placeholders::_2));

                _http_server.Post(FILE_GET_SINGLE        , (httplib::Server::Handler)std::bind(&GatewayServer::GetSingleFile              , this, std::placeholders::_1, std::placeholders::_2));

                _http_server.Post(FILE_GET_MULTI         , (httplib::Server::Handler)std::bind(&GatewayServer::GetMultiFile               , this, std::placeholders::_1, std::placeholders::_2));

                _http_server.Post(FILE_PUT_SINGLE        , (httplib::Server::Handler)std::bind(&GatewayServer::PutSingleFile              , this, std::placeholders::_1, std::placeholders::_2));

                _http_server.Post(FILE_PUT_MULTI         , (httplib::Server::Handler)std::bind(&GatewayServer::PutMultiFile               , this, std::placeholders::_1, std::placeholders::_2));

                _http_server.Post(SPEECH_RECOGNITION     , (httplib::Server::Handler)std::bind(&GatewayServer::SpeechRecognition          , this, std::placeholders::_1, std::placeholders::_2));

                _http_thread = std::thread([this, http_port](){

                    _http_server.listen("0.0.0.0", http_port);

                });

  1. 获取手机验证码接口:得到httplib包request报头,然后自己实例化一个phoneVerifyCodeReq报头,这结构体在PROTOBUF文件里序列化的,可以直接使用,目的就是为了进行微服务器之间的网络传输,然后通过request报头来初始化我们自己定义的那个通过rpc调用的请求,然后再获取用户子服务的信道,对信道进行,将请求和响应放入,调用对应的验证码发送接口,实现验证码的发送,得到对应的响应后,再将这个用来微服务调用的响应初始化httplib的response报文,返回给客户端,便结束了从客户端接收响应后传到对应的微服务处理器,然后接收微服务处理器响应,在将响应转发给客户端的过程。后面的userRegister,UserLogin,PhoneRegister,PhoneLogind这四个接口都是这样的逻辑,就不在赘述

void GetPhoneVerifyCode(const httplib::Request &request, httplib::Response &response) {

                //1. 取出http请求正文,将正文进行反序列化

                PhoneVerifyCodeReq req;

                PhoneVerifyCodeRsp rsp;

                auto err_response = [&req, &rsp, &response](const std::string &errmsg) -> void {

                    rsp.set_success(false);

                    rsp.set_errmsg(errmsg);

                    response.set_content(rsp.SerializeAsString(), "application/x-protbuf");

                };

                bool ret = req.ParseFromString(request.body);

                if (ret == false) {

                    LOG_ERROR("获取短信验证码请求正文反序列化失败!");

                    return err_response("获取短信验证码请求正文反序列化失败!");

                }

                //2. 将请求转发给用户子服务进行业务处理

                auto channel = _mm_channels->choose(_user_service_name);

                if (!channel) {

                    LOG_ERROR("{} 未找到可提供业务处理的用户子服务节点!", req.request_id());

                    return err_response("未找到可提供业务处理的用户子服务节点!");

                }

                zhou::UserService_Stub stub(channel.get());

                brpc::Controller cntl;

                stub.GetPhoneVerifyCode(&cntl, &req, &rsp, nullptr);

                if (cntl.Failed()) {

                    LOG_ERROR("{} 用户子服务调用失败!", req.request_id());

                    return err_response("用户子服务调用失败!");

                }

                //3. 得到用户子服务的响应后,将响应内容进行序列化作为http响应正文

                response.set_content(rsp.SerializeAsString(), "application/x-protbuf");

            }

  1. 获取用户身份信息接口:在获取用户信息接口这里,我们得到了httplib的报文以后,就不能再直接原封不动的将报文转发到对应的用户子服务处理器了,应该要判断是否用户是处于上线状态,已经用户是否有权利查看这个用户的身份信息,发现有这样的权力以后再将报文转发到对应的用户子服务的接口,同理,后面的SetUserAvatar,SetUserName,SetUserDiscription,SetUserNumberPhone,GetFriendList都是这个逻辑,故后面不在赘述

void GetFriendList(const httplib::Request &request, httplib::Response &response) {

                //1. 取出http请求正文,将正文进行反序列化

                GetFriendListReq req;

                GetFriendListRsp rsp;

                auto err_response = [&req, &rsp, &response](const std::string &errmsg) -> void {

                    rsp.set_success(false);

                    rsp.set_errmsg(errmsg);

                    response.set_content(rsp.SerializeAsString(), "application/x-protbuf");

                };

                bool ret = req.ParseFromString(request.body);

                if (ret == false) {

                    LOG_ERROR("获取好友列表请求正文反序列化失败!");

                    return err_response("获取好友列表请求正文反序列化失败!");

                }

                //2. 客户端身份识别与鉴权

                std::string ssid = req.session_id();

                auto uid = _redis_session->uid(ssid);

                if (!uid) {

                    LOG_ERROR("{} 获取登录会话关联用户信息失败!", ssid);

                    return err_response("获取登录会话关联用户信息失败!");

                }

                req.set_user_id(*uid);

                //2. 将请求转发给好友子服务进行业务处理

                auto channel = _mm_channels->choose(_friend_service_name);

                if (!channel) {

                    LOG_ERROR("{} 未找到可提供业务处理的用户子服务节点!", req.request_id());

                    return err_response("未找到可提供业务处理的用户子服务节点!");

                }

                zhou::FriendService_Stub stub(channel.get());

                brpc::Controller cntl;

                stub.GetFriendList(&cntl, &req, &rsp, nullptr);

                if (cntl.Failed()) {

                    LOG_ERROR("{} 好友子服务调用失败!", req.request_id());

                    return err_response("好友子服务调用失败!");

                }

                //3. 得到用户子服务的响应后,将响应内容进行序列化作为http响应正文

                response.set_content(rsp.SerializeAsString(), "application/x-protbuf");

            }

  1. 好友添加接口实现:在上述的接口中,都是简单的对httplib请求进行序列化解析后转发给对应的微服务器,然后从微服务器中得到对应的响应再转发给客户端的过程,并没有对websocket的连接使用,也就是并没有在没有接收到请求的情况下将响应发出的行为,但是加好友是一个双向的过程,不仅需要发送者接收到对应的响应,也需要被申请者接受到发送者的好友申请,下面是对好友添加接口逻辑的描述和代码实现,首先还是得将http报文转变为我们设计的PROTOBUF的结构,然后再将报文通过好友子服务进行处理,得到对应的PROTOBUF报文响应,然后将响应转发给对应的客户端的对象,但是不同的是,如果发送成功后,我门还需要从websocket管理里面取出被申请者ID对应的长连接,然后主动实例化一个结构体通过websocket的长连接管理将包含着新朋友添加通知的消息告诉对应的被申请人,这个通知的结构为User_info,也就是申请者的个人信息

void FriendAdd(const httplib::Request &request, httplib::Response &response) {

                // 好友申请的业务处理中,好友子服务其实只是在数据库创建了申请事件

                // 网关需要做的事情:当好友子服务将业务处理完毕后,如果处理是成功的--需要通知被申请方

                // 1. 正文的反序列化,提取关键要素:登录会话ID

                FriendAddReq req;

                FriendAddRsp rsp;

                auto err_response = [&req, &rsp, &response](const std::string &errmsg) -> void {

                    rsp.set_success(false);

                    rsp.set_errmsg(errmsg);

                    response.set_content(rsp.SerializeAsString(), "application/x-protbuf");

                };

                bool ret = req.ParseFromString(request.body);

                if (ret == false) {

                    LOG_ERROR("申请好友请求正文反序列化失败!");

                    return err_response("申请好友请求正文反序列化失败!");

                }

                // 2. 客户端身份识别与鉴权

                std::string ssid = req.session_id();

                auto uid = _redis_session->uid(ssid);

                if (!uid) {

                    LOG_ERROR("{} 获取登录会话关联用户信息失败!", ssid);

                    return err_response("获取登录会话关联用户信息失败!");

                }

                req.set_user_id(*uid);

                // 3. 将请求转发给好友子服务进行业务处理

                auto channel = _mm_channels->choose(_friend_service_name);

                if (!channel) {

                    LOG_ERROR("{} 未找到可提供业务处理的用户子服务节点!", req.request_id());

                    return err_response("未找到可提供业务处理的用户子服务节点!");

                }

                zhou::FriendService_Stub stub(channel.get());

                brpc::Controller cntl;

                stub.FriendAdd(&cntl, &req, &rsp, nullptr);

                if (cntl.Failed()) {

                    LOG_ERROR("{} 好友子服务调用失败!", req.request_id());

                    return err_response("好友子服务调用失败!");

                }

                // 4. 若业务处理成功 --- 且获取被申请方长连接成功,则向被申请放进行好友申请事件通知

                auto conn = _connections->connection(req.respondent_id());

                if (rsp.success() && conn) {

                    LOG_DEBUG("找到被申请人 {} 长连接,对其进行好友申请通知", req.respondent_id());

                    auto user_rsp = _GetUserInfo(req.request_id(), *uid);

                    if (!user_rsp) {

                        LOG_ERROR("{} 获取当前客户端用户信息失败!", req.request_id());

                        return err_response("获取当前客户端用户信息失败!");

                    }

                    NotifyMessage notify;

                    notify.set_notify_type(NotifyType::FRIEND_ADD_APPLY_NOTIFY);

                    notify.mutable_friend_add_apply()->mutable_user_info()->CopyFrom(user_rsp->user_info());

                    conn->send(notify.SerializeAsString(), websocketpp::frame::opcode::value::binary);

                }

  1. 好友添加处理事件结果接口:上面我们将好友申请发送以后,接下来就是事件的处理了,我们添加好友的结果无非就两种,第一种是对方同意,然后你的好友列表多出一位新朋友,同时也建立起新的会话列表,如果对方没有同意,则你就会收到对方拒绝你加好友的消息,除此之外,无事发生。我们这边也是这个逻辑,通过request里面取出申请人和被申请人的ID以及结果(是否同意添加联系人),然后通过ID得到对应的身份信息以及对应的长连接,先向申请人通过长连接通知申请结果(同意或者拒绝),如果是同意,则需要为双方创建一个会话,会话的头像就是双方的头像,会话的名字就是双方的名字,然后再将这个会话放入通知里面,通过双方对应的链接传输这个通知,使得客户端建立相应会话。在好友删除接口中,除了对relation里面的二者关系进行删除,还要对message表里面二者发送的所有信息进行删除,然后再删除对应的回话表和会话成员表,最后向被删除者发送一个通知,让被删除者得知消息,虽然有点残忍,但是我觉得知道还是比不知道好,后面的许多接口都是和前面的流程差不多,都是将http报文转换成对应的PROTOBUF报文,然后再进行鉴权后转发给对应的微服务器,微服务器对于每一个请求报文的处理在前面讲述完成,也不在此讲述,接收到对应微服务器的响应报文以后再进行转换成http报文以后转发给客户端

void FriendAddProcess(const httplib::Request &request, httplib::Response &response) {

                //好友申请的处理-----

                FriendAddProcessReq req;

                FriendAddProcessRsp rsp;

                auto err_response = [&req, &rsp, &response](const std::string &errmsg) -> void {

                    rsp.set_success(false);

                    rsp.set_errmsg(errmsg);

                    response.set_content(rsp.SerializeAsString(), "application/x-protbuf");

                };

                bool ret = req.ParseFromString(request.body);

                if (ret == false) {

                    LOG_ERROR("好友申请处理请求正文反序列化失败!");

                    return err_response("好友申请处理请求正文反序列化失败!");

                }

                // 2. 客户端身份识别与鉴权

                std::string ssid = req.session_id();

                auto uid = _redis_session->uid(ssid);

                if (!uid) {

                    LOG_ERROR("{} 获取登录会话关联用户信息失败!", ssid);

                    return err_response("获取登录会话关联用户信息失败!");

                }

                req.set_user_id(*uid);

                // 3. 将请求转发给好友子服务进行业务处理

                auto channel = _mm_channels->choose(_friend_service_name);

                if (!channel) {

                    LOG_ERROR("{} 未找到可提供业务处理的用户子服务节点!", req.request_id());

                    return err_response("未找到可提供业务处理的用户子服务节点!");

                }

                zhou::FriendService_Stub stub(channel.get());

                brpc::Controller cntl;

                stub.FriendAddProcess(&cntl, &req, &rsp, nullptr);

                if (cntl.Failed()) {

                    LOG_ERROR("{} 好友子服务调用失败!", req.request_id());

                    return err_response("好友子服务调用失败!");

                }

               

                if (rsp.success()) {

                    auto process_user_rsp = _GetUserInfo(req.request_id(), *uid);

                    if (!process_user_rsp) {

                        LOG_ERROR("{} 获取用户信息失败!", req.request_id());

                        return err_response("获取用户信息失败!");

                    }

                    auto apply_user_rsp = _GetUserInfo(req.request_id(), req.apply_user_id());

                    if (!process_user_rsp) {

                        LOG_ERROR("{} 获取用户信息失败!", req.request_id());

                        return err_response("获取用户信息失败!");

                    }

                    auto process_conn = _connections->connection(*uid);

                    if (process_conn) LOG_DEBUG("找到处理人的长连接!");

                    else LOG_DEBUG("未找到处理人的长连接!");

                    auto apply_conn = _connections->connection(req.apply_user_id());

                    if (apply_conn) LOG_DEBUG("找到申请人的长连接!");

                    else LOG_DEBUG("未找到申请人的长连接!");

                    //4. 将处理结果给申请人进行通知

                    if (apply_conn) {

                        NotifyMessage notify;

                        notify.set_notify_type(NotifyType::FRIEND_ADD_PROCESS_NOTIFY);

                        auto process_result = notify.mutable_friend_process_result();

                        process_result->mutable_user_info()->CopyFrom(process_user_rsp->user_info());

                        process_result->set_agree(req.agree());

                        apply_conn->send(notify.SerializeAsString(),

                            websocketpp::frame::opcode::value::binary);

                        LOG_DEBUG("对申请人进行申请处理结果通知!");

                    }

                    //5. 若处理结果是同意 --- 会伴随着单聊会话的创建 -- 因此需要对双方进行会话创建的通知

                    if (req.agree() && apply_conn) { //对申请人的通知---会话信息就是处理人信息

                        NotifyMessage notify;

                        notify.set_notify_type(NotifyType::CHAT_SESSION_CREATE_NOTIFY);

                        auto chat_session = notify.mutable_new_chat_session_info();

                        chat_session->mutable_chat_session_info()->set_single_chat_friend_id(*uid);

                        chat_session->mutable_chat_session_info()->set_chat_session_id(rsp.new_session_id());

                        chat_session->mutable_chat_session_info()->set_chat_session_name(process_user_rsp->user_info().nickname());

                        chat_session->mutable_chat_session_info()->set_avatar(process_user_rsp->user_info().avatar());

                        apply_conn->send(notify.SerializeAsString(), websocketpp::frame::opcode::value::binary);

                        LOG_DEBUG("对申请人进行会话创建通知!");

                    }

                    if (req.agree() && process_conn) { //对处理人的通知 --- 会话信息就是申请人信息

                        NotifyMessage notify;

                        notify.set_notify_type(NotifyType::CHAT_SESSION_CREATE_NOTIFY);

                        auto chat_session = notify.mutable_new_chat_session_info();

                        chat_session->mutable_chat_session_info()->set_single_chat_friend_id(req.apply_user_id());

                        chat_session->mutable_chat_session_info()->set_chat_session_id(rsp.new_session_id());

                        chat_session->mutable_chat_session_info()->set_chat_session_name(apply_user_rsp->user_info().nickname());

                        chat_session->mutable_chat_session_info()->set_avatar(apply_user_rsp->user_info().avatar());

                        process_conn->send(notify.SerializeAsString(), websocketpp::frame::opcode::value::binary);

                        LOG_DEBUG("对处理人进行会话创建通知!");

                    }

                }

                //6. 对客户端进行响应

                response.set_content(rsp.SerializeAsString(), "application/x-protbuf");

            }

  1. 最后一个接口,新消息转发接口:先将http报文序列化为对应的消息转发接口的报文,然后调用消息转发子服务,将报文进行处理,得到消息转发子服务传来的响应以后,说明消息已经被永久化到数据库和ES搜索引擎里面,就对被发送消息的用户发送一个通知,通知就是有一条新消息,通知的发送也是通过用户ID找到对应的长连接,进行发送通知。

void NewMessage(const httplib::Request &request, httplib::Response &response) {

                NewMessageReq req;

                NewMessageRsp rsp;//这是给客户端的响应

                GetTransmitTargetRsp target_rsp;//这是请求子服务的响应

                auto err_response = [&req, &rsp, &response](const std::string &errmsg) -> void {

                    rsp.set_success(false);

                    rsp.set_errmsg(errmsg);

                    response.set_content(rsp.SerializeAsString(), "application/x-protbuf");

                };

                bool ret = req.ParseFromString(request.body);

                if (ret == false) {

                    LOG_ERROR("新消息请求正文反序列化失败!");

                    return err_response("新消息请求正文反序列化失败!");

                }

                // 2. 客户端身份识别与鉴权

                std::string ssid = req.session_id();

                auto uid = _redis_session->uid(ssid);

                if (!uid) {

                    LOG_ERROR("{} 获取登录会话关联用户信息失败!", ssid);

                    return err_response("获取登录会话关联用户信息失败!");

                }

                req.set_user_id(*uid);

                // 3. 将请求转发给好友子服务进行业务处理

                auto channel = _mm_channels->choose(_transmite_service_name);

                if (!channel) {

                    LOG_ERROR("{} 未找到可提供业务处理的用户子服务节点!", req.request_id());

                    return err_response("未找到可提供业务处理的用户子服务节点!");

                }

                zhou::MsgTransmitService_Stub stub(channel.get());

                brpc::Controller cntl;

                stub.GetTransmitTarget(&cntl, &req, &target_rsp, nullptr);

                if (cntl.Failed()) {

                    LOG_ERROR("{} 消息转发子服务调用失败!", req.request_id());

                    return err_response("消息转发子服务调用失败!");

                }

                // 4. 若业务处理成功 --- 且获取被申请方长连接成功,则向被申请放进行好友申请事件通知

                if (target_rsp.success()){

                    for (int i = 0; i < target_rsp.target_id_list_size(); i++) {

                        std::string notify_uid = target_rsp.target_id_list(i);

                        if (notify_uid == *uid) continue; //不通知自己

                        auto conn = _connections->connection(notify_uid);

                        if (!conn) { continue;}

                        NotifyMessage notify;

                        notify.set_notify_type(NotifyType::CHAT_MESSAGE_NOTIFY);

                        auto msg_info = notify.mutable_new_message_info();

                        msg_info->mutable_message_info()->CopyFrom(target_rsp.message());

                        conn->send(notify.SerializeAsString(), websocketpp::frame::opcode::value::binary);

                    }

                }

                // 5. 向客户端进行响应

                rsp.set_request_id(req.request_id());

                rsp.set_success(target_rsp.success());

                rsp.set_errmsg(target_rsp.errmsg());

                response.set_content(rsp.SerializeAsString(), "application/x-protbuf");

            }

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

vue结合canvas动态生成水印效果

在 Vue 项目中添加水印可以通过以下几种方式实现&#xff1a; 方法一&#xff1a;使用 CSS 直接通过 CSS 的 background 属性实现水印&#xff1a; 实现步骤 在需要添加水印的容器中设置背景。使用 rgba 设置透明度&#xff0c;并通过 background-repeat 和 background-size…

html-两个div,让一个div跟随另外一个div的高度

在开发的过程中遇到有些场景事这样的&#xff0c;两个div的高度不一致&#xff0c;而且都是动态高度&#xff0c;有的时候div1高&#xff0c;有的时候div2高&#xff0c;如果设置flex的话&#xff0c;那么就会把较矮的元素撑大&#xff0c;但是我想始终都以div1的高度作为基准&…

知识管理系统|基于springBoot的知识管理系统设计与实现(附项目源码+论文+数据库)

私信或留言即免费送开题报告和任务书&#xff08;可指定任意题目&#xff09; 目录 一、摘要 二、相关技术 三、系统设计 四、数据库设计 五、核心代码 六、论文参考 七、源码获取 一、摘要 随着信息互联网信息的飞速发展&#xff0c;无纸化作业变成了一种趋势…

Kruskal 算法在特定边权重条件下的性能分析及其实现

引言 Kruskal 算法是一种用于求解最小生成树(Minimum Spanning Tree, MST)的经典算法。它通过逐步添加权重最小的边来构建最小生成树,同时确保不会形成环路。在本文中,我们将探讨在特定边权重条件下 Kruskal 算法的性能,并分别给出伪代码和 C 语言实现。特别是,我们将分…

12.2深度学习_视觉处理CNN_池化层、卷积知识

3.池化层 3.1 概述 池化层 (Pooling) 降低维度, 缩减模型大小&#xff0c;提高计算速度. 即: 主要对卷积层学习到的特征图进行下采样&#xff08;SubSampling&#xff09;处理。 池化层主要有两种: 最大池化 max pooling 最大池化是从每个局部区域中选择最大值作为池化后的值…

3D数据大屏实现过程,使用echarts、Next.js

&#x1f4dc; 本文主要内容 数据大屏自适应方案动效 echarts&#xff1a; 3D 立体柱状图动态流光折线图 3D 地球&#xff08;飞线、柱状图&#xff09;无限滚动列表 &#x1f50d; 大屏效果 数据大屏&#xff1a; 点击预览 &#x1f579; 运行条件 next 12.3.4echarts 5.4…

WebRover :一个功能强大的 Python 库,用于从 Web 内容生成高质量的数据集,专为训练大型语言模型和 AI 应用程序而设计。

2024-11-30 &#xff0c;由Area-25团队开发的一个专门用于生成高质量网络内容数据集的Python库。该数据集旨在为大型语言模型&#xff08;LLM&#xff09;和人工智能应用的训练提供丰富的数据资源。 数据集地址&#xff1a;WebRover Dataset|自然语言处理数据集|AI模型训练数据…

FlyHttp 的最佳实践:加速项目级 API 请求构建

FlyHttp的相关文章&#xff1a; FlyHttp 的诞生&#xff1a;从认识各种网络请求开始 FlyHttp 的设计思想&#xff1a;前端 API 自动化构建工具 FlyHttp 的使用&#xff1a;如何高效使用 FlyHttp&#xff0c;支持 JS、TS 项目 一. FlyHttp 是什么&#xff1f; 这是一个自动…

图像修复算法常用评估指标介绍及Python代码(PSNR/SSIM/FID)

目录 峰值信噪比PSNR&#xff08;Peak Signal-to-Noise Ratio&#xff09; 结构相似度SSlM&#xff08;Structural Similarity Index Measurement&#xff09; FID&#xff08;Frchet Inception Distance&#xff09; 代码实践&#xff1a;计算两张图片之间的PSNR和SSIM 代…

家庭财务管理系统的设计与实现ssm小程序+论文源码调试讲解

2系统关键技术 2.1 微信小程序 微信小程序&#xff0c;简称小程序&#xff0c;英文名Mini Program&#xff0c;是一种全新的连接用户与服务的方式&#xff0c;可以快速访问、快速传播&#xff0c;并具有良好的使用体验。 小程序的主要开发语言是JavaScript&#xff0c;它与普…

天润融通亮相CCFA论坛:AI Agent引领零售业服务精细化运营

在新时期实现零售的进化&#xff0c;AI Agent助力客户精细化运营 11月19-21日&#xff0c;CCFA新消费论坛——2024中国零售创新大会在上海召开。大会围绕“在新时期实现零售的进化”主题&#xff0c;通过探讨零售新趋势、新势力、新模式&#xff0c;聚焦新产品、新渠道、新生活…

医学临床机器学习中算法公平性与偏差控制简析

摘要 随着医疗领域中数据的不断积累和计算能力的提升&#xff0c;临床机器学习技术发展迅速&#xff0c;但算法不公平性和偏差问题凸显。本文深入探讨了临床机器学习算法公平性的重要性、概念与定义、在临床应用中的影响、偏差来源、降低偏差方法及提升公平性策略。通过对不同…

如何抓取亚马逊页面动态加载的内容:Python爬虫实践指南

引言 在现代电商领域&#xff0c;数据的重要性不言而喻。亚马逊作为全球领先的电商平台&#xff0c;其页面上动态加载的内容包含了丰富的商品信息。然而&#xff0c;传统的爬虫技术往往难以应对JavaScript动态加载的内容。本文将详细介绍如何使用Python结合Selenium工具来抓取…

MongoDB分片集群架构实战

分片集群架构 分片简介 分片&#xff08;shard&#xff09;是指在将数据进行水平切分之后&#xff0c;将其存储到多个不同的服务器节点上的一种扩展方式。分片在概念上非常类似于应用开发中的“水平分表”。不同的点在于&#xff0c;MongoDB本身就自带了分片管理的能力&#…

opencvocr识别手机摄像头拍摄的指定区域文字,文字符合规则就语音报警

安装python&#xff0c;pycharm&#xff0c;自行安装。 Python下安装OpenCv 2.1 打开cmd,先安装opencv-python pip install opencv-python --user -i https://pypi.tuna.tsinghua.edu.cn/simple2.2 再安装opencv-contrib-python pip install opencv-contrib-python --user …

[报错] Error: PostCSS plugin autoprefixer requires PostCSS 8 问题解决办法

报错&#xff1a;Error: PostCSS plugin autoprefixer requires PostCSS 8 原因&#xff1a;autoprefixer版本过高 解决方案&#xff1a; 降低autoprefixer版本 执行&#xff1a;npm i postcss-loader autoprefixer8.0.0 参考&#xff1a; Error: PostCSS plugin autoprefix…

Go学习:编译器(编写程序时应该注意的点)

一、注意&#xff1a; LiteIDE工具&#xff1a; &#xff08;1&#xff09;创建项目后&#xff0c;同一个目录下的go文件 只能有一个 main函数&#xff0c;如果多个文件都有main函数&#xff0c;会出现编译错误。例如&#xff1a; &#xff08;2&#xff09;如果一个目录下多…

自然语言处理期末试题汇总

建议自己做&#xff0c;写完再来对答案。答案可能存在极小部分错误&#xff0c;不保证一定正确。 一、选择题 1-10、C A D B D B C D A A 11-20、A A A C A B D B B A 21-30、B C C D D A C A C B 31-40、B B B C D A B B A A 41-50、B D B C A B B B B C 51-60、A D D …

市场爆火的“生成式AI大模型”证书如何报考?

随着科技的飞速发展&#xff0c;生成式人工智能正以前所未有的速度渗透到各行各业。从创作艺术、生成音乐到推动虚拟世界的构建&#xff0c;这项技术以其卓越的创新能力改变了传统的生产和创意模式。生成式人工智能不仅仅是数据的复制和再现&#xff0c;而是通过算法实现内容的…

Electron-vue 框架升级 Babel7 并支持electron-preload webapck 4 打包过程记录

前言 我这边一直用的electron-vue框架是基于electron 21版本的&#xff0c;electron 29版本追加了很多新功能&#xff0c;但是这些新功能对开发者不友好&#xff0c;对electron构建出来的软件&#xff0c;使用者更安全&#xff0c;所以&#xff0c;我暂时不想研究electron 29版…