IM项目-----用户信息子服务

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档

文章目录

  • 前言
  • 数据管理模块
    • mysql数据库管理
    • redis数据库管理
      • 登录会话的管理
      • 登录状态的管理
      • 验证码的管理
    • ES数据管理
      • 创建索引
    • 新增/更新数据
    • 查询索引
  • 服务器搭建
    • UserServer编写
    • UserServerBuild编写
  • 业务代码的编写
    • 用户注册
    • 用户名登录
    • 获取手机验证码
    • 手机号注册
    • 手机号登录
    • 获取用户信息
    • 获取多个用户信息
    • 修改用户头像
    • 设置昵称
    • 设置签名
    • 设置手机号码


前言

用户信息子服务主要是进行用户信息的管理,以及用户信息的操作。
提供的服务有:

  • 用户名的登录与注册
  • 手机号的登录与注册
  • 手机验证码的获取
  • 获取单个用户信息
  • 获取多个多个用户信息
  • 用户信息的修改

其中用户注册成功后需要在mysql数据库中新增用户信息,在ES搜索引擎中新增用户信息。
用户登录时需要在mysql数据库中进行信息的比对,登录成功后需要在redis数据库中新增登录会话信息和用户Id信息。
在使用手机号注册和登录以及修改手机号时,需要获取验证码,所以在redis数据库中存贮验证码ID和验证码键值对。
另外在获取用户信息时,需要向文件存储子服务发起rpc调用获取头像文件内容。
因此,该子服务需要包含以下模块:

  • 数据管理模块
  • 服务注册模块
  • 信道管理模块
  • 服务发现模块
  • rpc服务器模块
  • DMS验证码模块

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);
}

数据管理模块

用户信息子服务主要是针对用户信息进行一个存储,以及提供用户信息的操作。我们要封装三个数据管理类。分别是mysql数据库管理类,redis数据库管理类,es数据库管理类。

mysql数据库管理

我们需要提供的接口有:
插入用户信息 ------ 注册时调用
更新用户信息 ------用户更新信息时
根据昵称获取用户信息 ------用户名登录时
根据手机号获取用户信息 ------手机号登录
根据用户id获取用户信息 ------登录时获取用户信息进行比对
根据多个用户id获取一组用户信息 -------内部调用

UserTable(const std::shared_ptr<odb::core::database> &db):_db(db){}
bool insert(const std::shared_ptr<User> &user) {
    try {
        odb::transaction trans(_db->begin());
        _db->persist(*user);
        trans.commit();
    }catch (std::exception &e) {
        LOG_ERROR("新增用户失败 {}:{}!", user->nickname(),e.what());
        return false;
    }
    return true;
}
bool update(const std::shared_ptr<User> &user) {
    try {
        odb::transaction trans(_db->begin());
        _db->update(*user);
        trans.commit();
    }catch (std::exception &e) {
        LOG_ERROR("更新用户失败 {}:{}!", user->nickname(), e.what());
        return false;
    }
    return true;
}
std::shared_ptr<User> select_by_nickname(const std::string &nickname) {
    std::shared_ptr<User> res;
    try {
        odb::transaction trans(_db->begin());
        typedef odb::query<User> query;
        typedef odb::result<User> result;
        res.reset(_db->query_one<User>(query::nickname == nickname));
        trans.commit();
    }catch (std::exception &e) {
        LOG_ERROR("通过昵称查询用户失败 {}:{}!", nickname, e.what());
    }
    return res;
}
std::shared_ptr<User> select_by_phone(const std::string &phone) {
    std::shared_ptr<User> res;
    try {
        odb::transaction trans(_db->begin());
        typedef odb::query<User> query;
        typedef odb::result<User> result;
        res.reset(_db->query_one<User>(query::phone_number == phone));
        trans.commit();
    }catch (std::exception &e) {
        LOG_ERROR("通过手机号查询用户失败 {}:{}!", phone, e.what());
    }
    return res;
}
std::shared_ptr<User> select_by_id(const std::string &user_id) {
    std::shared_ptr<User> res;
    try {
        odb::transaction trans(_db->begin());
        typedef odb::query<User> query;
        typedef odb::result<User> result;
        res.reset(_db->query_one<User>(query::user_id == user_id));
        trans.commit();
    }catch (std::exception &e) {
        LOG_ERROR("通过用户ID查询用户失败 {}:{}!", user_id, e.what());
    }
    return res;
}
std::vector<User> select_multi_users(const std::vector<std::string> &id_list) {
    // select * from user where id in ('id1', 'id2', ...)
    if (id_list.empty()) {
        return std::vector<User>();
    }
    std::vector<User> res;
    try {
        odb::transaction trans(_db->begin());
        typedef odb::query<User> query;
        typedef odb::result<User> result;
        std::stringstream ss;
        ss << "user_id in (";
        for (const auto &id : id_list) {
            ss << "'" << id << "',";
        }
        std::string condition = ss.str();
        condition.pop_back();
        condition += ")";
        result r(_db->query<User>(condition));
        for (result::iterator i(r.begin()); i != r.end(); ++i) {
            res.push_back(*i);
        }
        trans.commit();
    }catch (std::exception &e) {
        LOG_ERROR("通过用户ID批量查询用户失败:{}!", e.what());
    }
    return res;
}

这里同样需要odb映射一个user表。表的字段如下。用户有两种注册方式。
用户名注册:此时手机号为空
手机号注册:此时用户名和密码为空。
注册的新账号头像Id,个性签名默认都是空。

 #pragma db id auto
unsigned long _id;  //自增Id
#pragma db unique type("VARCHAR(127)") index
std::string _user_id;                       //用户ID
#pragma db unique type("VARCHAR(63)") index
odb::nullable<std::string> _nickname;       //用户昵称 ---可能为空
#pragma db type("VARCHAR(255)")
odb::nullable<std::string> _password;         //密码     ---可能为空
#pragma db type("VARCHAR(127)")
odb::nullable<std::string> _avatar_id;      //头像ID   ---可能为空
#pragma db unique type("VARCHAR(15)")   index
odb::nullable<std::string> _phone_number;   //电话号码  ---可能为空
#pragma db type("VARCHAR(255)")
odb::nullable<std::string> _description;    //个性签名 --- 可能为空

redis数据库管理

redis中全都是string字符串类型的数据,主要涉及三个方面。

登录会话的管理

当用户登录成功后,需要在redis中存贮< session_id,user_id>的键值对。方便后续网关服务器做身份鉴权。只有在session_id存在的情况我们才提供服务。

这里提供三个接口:新增,删除,根据session_id获取user_id

class Session {
 public:
     using ptr = std::shared_ptr<Session>;
     Session(const std::shared_ptr<sw::redis::Redis> &redis_client):
         _redis_client(redis_client){}
     void append(const std::string &ssid, const std::string &uid) {
         _redis_client->set(ssid, uid);
     }
     void remove(const std::string &ssid) {
         _redis_client->del(ssid);
     }
     sw::redis::OptionalString uid(const std::string &ssid) {
         return _redis_client->get(ssid);
     }
 private:
     std::shared_ptr<sw::redis::Redis> _redis_client;
};

登录状态的管理

当用户登录成功后,在redis中存储< user_id, “” >的键值对。标记用户的登录状态,当用户下线会删除该键值对。主要是为了防止用户重复登陆。

class Status {
  public:
      using ptr = std::shared_ptr<Status>;
      Status(const std::shared_ptr<sw::redis::Redis> &redis_client):
          _redis_client(redis_client){}
      void append(const std::string &uid) {
          _redis_client->set(uid, "");
      }
      void remove(const std::string &uid) {
          _redis_client->del(uid);
      }
      bool exists(const std::string &uid) {
          auto res = _redis_client->get(uid);
          if (res) return true;
          return false;
      }
  private:
      std::shared_ptr<sw::redis::Redis> _redis_client;
};

验证码的管理

在收到获取验证码的请求后,服务器会生成一个验证码ID和一个验证码,并将验证码Id作为响应返回给客户端。同时调用短信服务平台sdk,向用户发送短信验证码。sdk调用成功后,会在redis中存储一个< codeId,code >的键值对,同时指定过期时间5分钟。当用户登录成功后会进行删除。

 class Codes {
public:
    using ptr = std::shared_ptr<Codes>;
    Codes(const std::shared_ptr<sw::redis::Redis> &redis_client):
        _redis_client(redis_client){}
    void append(const std::string &cid, const std::string &code, 
    //300秒
        const std::chrono::milliseconds &t = std::chrono::milliseconds(300000)) {
        _redis_client->set(cid, code, t);
    }
    void remove(const std::string &cid) {
        _redis_client->del(cid);
    }
    sw::redis::OptionalString code(const std::string &cid)  {
        return _redis_client->get(cid);
    }
private:
    std::shared_ptr<sw::redis::Redis> _redis_client;
};

ES数据管理

项目中用到了es支持用户搜索和消息搜索。其中消息搜索是消息子服务完成的。我们需要建立一个用户信息的索引,同时在注册新用户的时候,添加该用户索引信息,在用户更新个人信息后同时更新es索引信息。同时也要提供查询索引。

在前面我们以及封装了es客户端的操作。这里我们需要针对前面的封装来封装出一个更贴近我们用户信息子服务的ESUSer类。

创建索引

不需要提供任何参数,直接根据用户信息的相关字段组织json。
前端可以用过用户id/手机号/昵称进行搜索。且昵称支持模糊匹配。 所以:昵称字段类型为text,需要进行分词。其他字段都不需要进行分词。
用户id/电话号码字段需要参与索引,但不进行分词。

bool createIndex()
{
bool ret = ESIndex(_client,"user")
      .append(_uid_key,"keyword","standard",true)
      .append(_desc_key,"keyword","standard",false)
      .append(_phone_key,"keyword","standard",true)
      .append(_name_key)
      .append(_avatar_key,"keyword","standard",false)
      .create();
if (ret == false) {
    LOG_INFO("用户信息索引创建失败!");
    return false;
}
    LOG_INFO("用户信息索引创建成功!");
return true;
}

新增/更新数据

这里新增一个文档,指定的文档ID是用户Id。所以用户ID在es存在时就是更新,不存在就是新增。

//新增和更新数据 当用户id已经存在与es就是更新
bool appendData(const std::string &uid,
      const std::string &phone,
      const std::string &nickname,
      const std::string &description,
      const std::string &avatar_id)
{
  bool ret = ESInsert(_client,"user")
          .append(_uid_key, uid)
          .append(_desc_key, nickname)
          .append(_phone_key, phone)
          .append(_name_key, description)
          .append(_avatar_key, avatar_id)
          //这里插入数据时文档Id指定的是用户ID
          .insert(uid);
  if (ret == false) {
      LOG_ERROR("用户数据插入/更新失败!");
      return false;
  }

  LOG_INFO("用户数据新增/更新成功!");
  return true;
}

查询索引

查询索引需要用户提供查询key,和一组用户id列表。
其中key就是用户在前端输入的手机号/昵称/用户id.
一组用户id是我们进行过滤的条件,我们不能将以及是用户好友和自己的用户信息返回给前端,所以这里需要进行过滤。
返回值是一个User的数组,这里的User是odb映射的那个用户表的user.hxx文件的类。

//返回值是一个User的数组(user.hxx中的User) 形参是一组用户ID,因为用户在搜索时不能搜索到已经是好友的用户信息。 
std::vector<User> search(const std::string &key, const std::vector<std::string> &uid_list)
{
std::vector<User> res;
   Json::Value json_user = ESSearch(_client, "user")
       .append_should_match("phone.keyword", key)
       .append_should_match("user_id.keyword", key)
       .append_should_match("nickname", key)
       .append_must_not_terms("user_id.keyword", uid_list)
       .search();
   if (json_user.isArray() == false) {
       LOG_ERROR("用户搜索结果为空,或者结果不是数组类型");
       return res;
   }
   int sz = json_user.size();
   LOG_DEBUG("检索结果条目数量:{}", sz);
   for (int i = 0; i < sz; i++) {
       User user;
       user.user_id(json_user[i]["_source"]["user_id"].asString());
       user.nickname(json_user[i]["_source"]["nickname"].asString());
       user.description(json_user[i]["_source"]["description"].asString());
       user.phone(json_user[i]["_source"]["phone"].asString());
       user.avatar_id(json_user[i]["_source"]["avatar_id"].asString());
       res.push_back(user);
   }
   return res;
}

至此,三个数据管理类封装完成,接下来开始搭建服务器。

服务器搭建

服务器的搭建流程几乎一致,只不过这个子服务提供的服务方法比较多,涉及到的组件也比较多。
包括:服务注册/服务发现/信道管理/DMS语音SDK/bprc服务器/还有三个数据管理。
这个子服务中需要服务发现的原因是:我们提供了一个获取用户信息的服务,在用户信息中有一个头像Id,我们需要向文件存贮子服务发起请求,获取头像文件,因此需要进行服务发现,同时通过信道管理类获取对应的channel进行服务调用.

UserServer编写

服务器需要管理rpc服务器对象以及服务注册和服务发现对象就行,其他三个数据库句柄都在服务类中进行管理了,在这个类中只有一个接口就是启动服务器。

Discovery::ptr _service_discoverer;
Registry::ptr _registry_client;
std::shared_ptr<elasticlient::Client> _es_client;
std::shared_ptr<odb::core::database> _mysql_client;
std::shared_ptr<sw::redis::Redis> _redis_client;
std::shared_ptr<brpc::Server> _rpc_server;

UserServerBuild编写

需要在这个类中构造rpc服务器,为服务器添加服务,而在service类中需要进行业务操作的三个数据管理类,以及dms客户端,和一个信道管理对象,同时传入文件子服务的服务名用于获取channel。

我们在这个类中就需要构建出三个数据管理对象,再把这个数据管理对象句柄传给service服务类,服务类通过这三个句柄构造出我们封装的数据管理类。

td::shared_ptr<elasticlient::Client> _es_client;
std::shared_ptr<odb::core::database> _mysql_client;
std::shared_ptr<sw::redis::Redis> _redis_client;

同时构造服务发现/服务注册和信道管理对象。在构造服务发现时就把需要关心的服务设置进去。同时进行一次服务发现(服务发现对象构造中完成)。此时信道管理对象中就有了文件存储子服务的主机地址的channel。

//用于构造服务发现客户端&信道管理对象
 void make_discovery_object(const std::string &reg_host,
     const std::string &base_service_name,
     const std::string &file_service_name) {
     _file_service_name = file_service_name;
     _mm_channels = std::make_shared<ServiceManager>();
     _mm_channels->declared(file_service_name);
     LOG_DEBUG("设置文件子服务为需添加管理的子服务:{}", file_service_name);
     auto put_cb = std::bind(&ServiceManager::onServiceOnline, _mm_channels.get(), std::placeholders::_1, std::placeholders::_2);
     auto del_cb = std::bind(&ServiceManager::onServiceOffline, _mm_channels.get(), std::placeholders::_1, std::placeholders::_2);
     _service_discoverer = std::make_shared<Discovery>(reg_host, base_service_name, put_cb, del_cb);
 }
 //用于构造服务注册客户端对象
 void make_registry_object(const std::string &reg_host,
     const std::string &service_name,
     const std::string &access_host) {
     _registry_client = std::make_shared<Registry>(reg_host);
     _registry_client->registry(service_name, access_host);
 }

业务代码的编写

这里涉及到11个服务,其中四个是登录注册,四个是用户信息修改,一个获取验证码,两个获取个人信息。

用户注册

  1. 从请求中取出昵称和密码
  2. 检查昵称是否合法(长度限制 3~22 之间)
  3. 检查密码是否合法(只能包含字母,数字,长度限制 6~15 之间)
  4. 根据昵称在数据库进行判断是否昵称已存在
  5. 向数据库新增数据
  6. 向 ES 服务器中新增用户信息
  7. 组织响应,进行成功与否的响应即可。

在注册成功后,也就是第4步成功后,会为用户生成一个用户Id,第5步会插入到数据库中。
在登录时就会去数据库中进行一个密码的比对。如果比对成功代表登陆成功。此时就会生成一个会话Id,在redis中插入会话id和用户id的键值对。往后,网关在收到客户端的请求后就可以通过redis中的会话信息进行身份鉴权。

virtual void UserRegister(google::protobuf::RpcController* controller,
                       const ::lkm_im::UserRegisterReq* request,
                       ::lkm_im::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. 从请求中取出昵称和密码
      const std::string &nickname = request->nickname(); 
      const std::string &password = request->password(); 

      //2. 检查昵称是否合法(长度限制 3 - 21 之间)
      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. 向数据库新增数据
      const 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. 从请求中取出昵称和密码
  2. 通过昵称获取用户信息,进行密码是否一致的判断
  3. 根据 redis 中的登录标记信息是否存在判断用户是否已经登录。
  4. 构造会话 ID,生成会话键值对,向 redis 中添加会话信息以及登录标记信息
  5. 组织响应,返回生成的会话 ID
virtual void UserLogin(google::protobuf::RpcController* controller,
                     const ::lkm_im::UserLoginReq* request,
                     ::lkm_im::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. 从请求中取出昵称和密码
     const std::string &nickname = request->nickname(); 
     const 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){
         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());

     //4.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. 从请求中取出手机号码
  2. 验证手机号码格式是否正确(必须以 1 开始,第二位 3~9 之间,后边 9 个数字字符)
  3. 生成 4 位随机验证码
  4. 基于短信平台 SDK 发送验证码
  5. 构造验证码 ID,添加到 redis 验证码映射键值索引中
  6. 组织响应,返回生成的验证码 ID
virtual void GetPhoneVerifyCode(google::protobuf::RpcController* controller,
                      const ::lkm_im::PhoneVerifyCodeReq* request,
                      ::lkm_im::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. 从请求中取出手机号码
      const 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 位随机验证码 和 验证码ID
      const std::string& code_id = uuid();
      const std::string& vCode = vcode();

      //4. 基于短信平台 SDK 发送验证码
      ret = _dms_client->send(phone,vCode);
      if (ret == false) {
          LOG_ERROR("{} 短信验证码发送失败!- {} ", request->request_id(), phone);
          return err_response(request->request_id(), "短信验证码发送失败!");
      }

      //5. 构造验证码 ID,添加到 redis 验证码映射键值索引中
      _redis_codes->append(code_id,vCode);

      //6. 组织响应,返回生成的验证码 ID
      response->set_request_id(request->request_id());
      response->set_success(true);
      response->set_verify_code_id(code_id);
  }

手机号注册

  1. 从请求中取出手机号码和验证码
  2. 检查注册手机号码是否合法
  3. 从 redis 数据库中进行验证码 ID-验证码一致性匹配
  4. 通过数据库查询判断手机号是否已经注册过
  5. 向数据库新增用户信息
  6. 向 ES 服务器中新增用户信息
  7. 组织响应,返回注册成功与否
virtual void PhoneRegister(google::protobuf::RpcController* controller,
                     const ::lkm_im::PhoneRegisterReq* request,
                     ::lkm_im::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. 从请求中取出手机号码和验证码
     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 rCode = _redis_codes->code(code_id);
     if(rCode != code){
         LOG_ERROR("{} 验证码错误 - {}:{} !", request->request_id(), code_id, code);
         return err_response(request->request_id(), "验证码错误!");  
     }
     _redis_codes->remove(code_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("{}  Mysql数据库新增数据失败!- {}", request->request_id(),phone);
         return err_response(request->request_id(), "Mysql数据库新增数据失败!");
     }

     //6. 向 ES 服务器中新增用户信息
     ret = _es_user->appendData(uid,phone,"","","");
     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. 从请求中取出手机号码和验证码 ID,以及验证码。
  2. 检查注册手机号码是否合法
  3. 从 redis 数据库中进行验证码 ID-验证码一致性匹配
  4. 根据手机号从数据数据进行用户信息查询,判断用用户是否存在
  5. 根据 redis 中的登录标记信息是否存在判断用户是否已经登录。
  6. 构造会话 ID,生成会话键值对,向 redis 中添加会话信息以及登录标记信息
  7. 组织响应,返回生成的会话 ID
virtual void PhoneLogin(google::protobuf::RpcController* controller,
                     const ::lkm_im::PhoneLoginReq* request,
                     ::lkm_im::PhoneLoginRsp* 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 rCode = _redis_codes->code(code_id);
     if(rCode != code){
         LOG_ERROR("{} 验证码错误 !- {}:{} ", request->request_id(), code_id, code);
         return err_response(request->request_id(), "验证码错误!");  
     }
     _redis_codes->remove(code_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. 根据 redis 中的登录标记信息是否存在判断用户是否已经登录。
     ret = _redis_status->exists(user->user_id());
     if(ret){
         LOG_ERROR("{} 用户已在其他地方登录!- {}", request->request_id(), phone);
         return err_response(request->request_id(), "用户已在其他地方登录!");
     }

     //6. 构造会话 ID,生成会话键值对,向 redis 中添加会话信息以及登录标记信息
     std::string ssid = uuid();
     _redis_session->append(ssid,user->user_id());

     //6.5 添加用户登录标志
     _redis_status->append(user->user_id());

     //7. 组织响应,返回生成的会话 ID
     response->set_request_id(request->request_id());
     response->set_login_session_id(ssid);
     response->set_success(true);
 }

获取用户信息

  1. 从请求中取出用户 ID
  2. 通过用户 ID,从数据库中查询用户信息
  3. 根据用户信息中的头像 ID,从文件服务器获取头像文件数据,组织完整用户信息
  4. 组织响应,返回用户信息

如果用户的头像Id不为空,就需要调用文件存储子服务的获取单个文件的服务,来获取到头像内容。

virtual void GetUserInfo(google::protobuf::RpcController* controller,
                    const ::lkm_im::GetUserInfoReq* request,
                    ::lkm_im::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());
    
    //头像ID存在才去向文件子服务发起调用。也就是用户设置过头像才会有头像Id
    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请求,进行头像文件下载
        lkm_im::FileService_Stub stub(channel.get());
        lkm_im::GetSingleFileReq req;
        lkm_im::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);
}

获取多个用户信息

内部接口,暂时还不知道谁会调用这个服务。可能是加载成员列表的时候需要获取到多个用户信息,盲猜是在用户子服务。
这里和获取单个用户信息的处理很类似,提取出请求中的用户Id列表,调用文件存储子服务的获取多个文件的服务。

virtual void GetMultiUserInfo(google::protobuf::RpcController* controller,
                   const ::lkm_im::GetMultiUserInfoReq* request,
                   ::lkm_im::GetMultiUserInfoRsp* response,
                   ::google::protobuf::Closure* done){
     LOG_DEBUG("收到批量用户信息获取请求!");
   brpc::ClosureGuard rpc_guard(done);
   //1. 定义错误回调
   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;
   };
   //2. 从请求中取出用户ID --- 列表
   std::vector<std::string> uid_lists;
   for (int i = 0; i < request->users_id_size(); i++) {
       uid_lists.push_back(request->users_id(i));
   }
   //3. 从数据库进行批量用户信息查询
   auto users = _mysql_user->select_multi_users(uid_lists);
   if (users.size() != request->users_id_size()) {
       LOG_ERROR("{} - 从数据库查找的用户信息数量不一致 {}-{}!", 
           request->request_id(), request->users_id_size(), users.size());
       return err_response(request->request_id(), "从数据库查找的用户信息数量不一致!");
   }
   //4. 批量从文件管理子服务进行文件下载
   auto channel = _mm_channels->choose(_file_service_name);
   if (!channel) {
       LOG_ERROR("{} - 未找到文件管理子服务节点 - {}!", request->request_id(), _file_service_name);
       return err_response(request->request_id(), "未找到文件管理子服务节点!");
   }
   lkm_im::FileService_Stub stub(channel.get());
   lkm_im::GetMultiFileReq req;
   lkm_im::GetMultiFileRsp rsp;
   req.set_request_id(request->request_id());
   for (auto &user : users) {
       if (user.avatar_id().empty()) continue;
       req.add_file_id_list(user.avatar_id());
   }
   brpc::Controller cntl;
   stub.GetMultiFile(&cntl, &req, &rsp, nullptr);
   if (cntl.Failed() == true || rsp.success() == false) {
       LOG_ERROR("{} - 文件子服务调用失败:{} - {}!", request->request_id(), 
           _file_service_name, cntl.ErrorText());
       return err_response(request->request_id(), "文件子服务调用失败!");
   }
   //5. 组织响应()
   for (auto &user : users) {
       auto user_map = response->mutable_users_info();//本次请求要响应的用户信息map
       auto file_map = rsp.mutable_file_data(); //这是批量文件请求响应中的map 
       UserInfo 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());
       user_info.set_avatar((*file_map)[user.avatar_id()].file_content());
       (*user_map)[user_info.user_id()] = user_info;
   }
   response->set_request_id(request->request_id());
   response->set_success(true);
}

修改用户头像

  1. 从请求中取出用户 ID 与头像数据
  2. 从数据库通过用户 ID 进行用户信息查询,判断用户是否存在
  3. 上传头像文件到文件子服务,
  4. 将返回的头像文件 ID 更新到数据库中
  5. 更新 ES 服务器中用户信息
  6. 组织响应,返回更新成功与否

这里需要将用户上传的头像内容上传到文件存贮子服务中,文件存储子服务会返回一个文件Id,我们将文件ID更新到数据库和ES搜索引擎中。

virtual void SetUserAvatar(google::protobuf::RpcController* controller,
                   const ::lkm_im::SetUserAvatarReq* request,
                   ::lkm_im::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(), "未找到文件管理子服务节点!");
   }
   lkm_im::FileService_Stub stub(channel.get());
   lkm_im::PutSingleFileReq req;
   lkm_im::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. 从请求中取出用户 ID 与新的昵称
  2. 判断昵称格式是否正确
  3. 从数据库通过用户 ID 进行用户信息查询,判断用户是否存在
  4. 将新的昵称更新到数据库中
  5. 更新 ES 服务器中用户信息
  6. 组织响应,返回更新成功与否

设置昵称相对于设置头像简单一点,因为不涉及到文件存储子服务了。只需要进行本地的数据库更新和ES的更新。

virtual void SetUserNickname(google::protobuf::RpcController* controller,
                     const ::lkm_im::SetUserNicknameReq* request,
                     ::lkm_im::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("{} - 更新Mysql数据库用户昵称失败 :{}!", 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("{} - 更新ES搜索引擎用户昵称失败 :{}!", request->request_id(), new_nickname);
         return err_response(request->request_id(), "更新搜索引擎用户昵称失败!");
     }
     // 6. 组织响应,返回更新成功与否
     response->set_request_id(request->request_id());
     response->set_success(true);
 }

设置签名

和昵称一致。

virtual void SetUserDescription(google::protobuf::RpcController* controller,
                     const ::lkm_im::SetUserDescriptionReq* request,
                     ::lkm_im::SetUserDescriptionRsp* 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_description = request->description();

     // 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->description(new_description);
     bool ret = _mysql_user->update(user);
     if (ret == false) {
         LOG_ERROR("{} - 更新数据库用户签名失败 :{}!", request->request_id(), new_description);
         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_description);
         return err_response(request->request_id(), "更新搜索引擎用户签名失败!");
     }
     // 6. 组织响应,返回更新成功与否
     response->set_request_id(request->request_id());
     response->set_success(true);
 }

设置手机号码

  1. 从请求中取出手机号码和验证码 ID,以及验证码。
  2. 检查注册手机号码是否合法
  3. 从 redis 数据库中进行验证码 ID-验证码一致性匹配
  4. 根据手机号从数据数据进行用户信息查询,判断用用户是否存在
  5. 将新的手机号更新到数据库中

手机号码的设置,相较于设置昵称和签名多了一步验证验证码,通过redis比较验证码Id和验证码是否一致。

virtual void SetUserPhoneNumber(google::protobuf::RpcController* controller,
                    const ::lkm_im::SetUserPhoneNumberReq* request,
                    ::lkm_im::SetUserPhoneNumberRsp* 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_phone = request->phone_number();
    std::string code = request->phone_verify_code();
    std::string code_id = request->phone_verify_code_id();
    // 2. 对验证码进行验证
    auto vcode = _redis_codes->code(code_id);
    if (vcode != code) {
        LOG_ERROR("{} - 验证码错误 - {}-{}!", request->request_id(), code_id, code);
        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->phone(new_phone);
    bool ret = _mysql_user->update(user);
    if (ret == false) {
        LOG_ERROR("{} - 更新数据库用户手机号失败 :{}!", request->request_id(), new_phone);
        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_phone);
        return err_response(request->request_id(), "更新搜索引擎用户手机号失败!");
    }
    // 6. 组织响应,返回更新成功与否
    response->set_request_id(request->request_id());
    response->set_success(true);
}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

电商ISV 电商SaaS 是什么

Independent Software Vendors的英文缩写&#xff0c;意为“独立软件开发商” 软件即服务(SaaS) 指一种基于云技术的软件交付模式 订阅收费 这些公司叫做ISV软件供应商&#xff0c;通过SaaS服务交付收费 为什么会有电商ISV 从商家角度划分&#xff1a;有独立品牌商家、大商…

【2025】儿童疫苗接种预约小程序(源码+文档+解答)

博主介绍&#xff1a; ✌我是阿龙&#xff0c;一名专注于Java技术领域的程序员&#xff0c;全网拥有10W粉丝。作为CSDN特邀作者、博客专家、新星计划导师&#xff0c;我在计算机毕业设计开发方面积累了丰富的经验。同时&#xff0c;我也是掘金、华为云、阿里云、InfoQ等平台…

猫咪检测系统源码分享

猫咪检测检测系统源码分享 [一条龙教学YOLOV8标注好的数据集一键训练_70全套改进创新点发刊_Web前端展示] 1.研究背景与意义 项目参考AAAI Association for the Advancement of Artificial Intelligence 项目来源AACV Association for the Advancement of Computer Vision …

R语言机器学习算法实战系列(二) SVM算法(Support Vector Machine)

文章目录 介绍原理应用方向下载数据加载R包导入数据数据预处理数据描述数据切割标准化数据设置参数训练模型预测测试数据评估模型模型准确性混淆矩阵模型评估指标ROC CurvePRC Curve特征的重要性保存模型总结系统信息介绍 支持向量机(Support Vector Machine,简称SVM)是一种…

跨站请求伪造(CSRF)漏洞详解

免责申明 本文仅是用于学习检测自己搭建的DVWA靶场环境有关CSRF的原理和攻击实验,请勿用在非法途径上,若将其用于非法目的,所造成的一切后果由您自行承担,产生的一切风险和后果与笔者无关;本文开始前请认真详细学习《‌中华人民共和国网络安全法》‌及其所在国家地区相关法…

java项目之在线考试与学习交流网页平台源码(springboot)

风定落花生&#xff0c;歌声逐流水&#xff0c;大家好我是风歌&#xff0c;混迹在java圈的辛苦码农。今天要和大家聊的是一款基于springboot的在线考试与学习交流网页平台。项目源码以及部署相关请联系风歌&#xff0c;文末附上联系信息 。 项目简介&#xff1a; 基于JAVA语言…

ChatGPT搭上langchain的知识库RAG应用,效果超预期

最近利用LangchainChatGPT实现了上传文档实现个人知识库应用的能力&#xff0c;效果比想象得要好。文末大家可以体验一下效果~~ 给大家大致介绍下实现方式&#xff0c;参考了Langchain chatchat。 一、LangchainChatGPT 1、概述 LangChain 是一个强大的框架&#xff0c;可以…

飞驰云联FTP替代方案:安全高效文件传输的新选择

FTP协议广泛应用各行业的文件传输场景中&#xff0c;由于FTP应用获取门槛低、使用普遍&#xff0c;因此大部分企业都习惯使用FTP进行文件传输。然而面临激增的数据量和网络安全威胁的不断演变&#xff0c;FTP在传输安全性与传输性能上有所欠缺&#xff0c;无法满足企业现在的高…

光伏板缺陷红外检测数据集

光伏板缺陷红外检测数据集 包含以下4个数据文件&#xff1a; /train&#xff1a;训练集 /valid&#xff1a;验证集 /test&#xff1a;测试集 README.txt&#xff1a;数据说明 【数据说明】检测目标以Pascal VOC格式进行标注&#xff0c;对每个图像进行以下预处理&#xff0c;统…

Codeforces Round 974 (Div. 3) A-F

封面原图 画师礼島れいあ 下午的ICPC网络赛的难受一晚上全都给我打没了 手速拉满再加上秒杀线段树 这场简直了啊 唯一可惜的是最后还是掉出了1000名 一把上蓝应该没啥希望了吧 A - Robin Helps 题意 侠盗罗宾因劫富济贫而闻名于世 罗宾遇到的 n n n 人&#xff0c;从 1 s …

中泰免签,准备去泰国旅游了吗?《泰语翻译通》app支持文本翻译和语音识别翻译,解放双手对着说话就能翻译。

泰国是很多中国游客的热门选择&#xff0c;现在去泰国旅游更方便了&#xff0c;因为泰国对中国免签了。如果你打算去泰国&#xff0c;那么下载一个好用的泰语翻译软件是很有必要的。 简单好用的翻译工具 《泰语翻译通》App就是为泰国旅游设计的&#xff0c;它翻译准确&#x…

Cisco Catalyst 9000 Series Switches, IOS XE Release 17.15.1 ED

Cisco Catalyst 9000 Series Switches, IOS XE Release 17.15.1 ED 思科 Catalyst 9000 交换产品系列 IOS XE 系统软件 请访问原文链接&#xff1a;https://sysin.org/blog/cisco-catalyst-9000/&#xff0c;查看最新版。原创作品&#xff0c;转载请保留出处。 作者主页&…

uniapp中使用picker-view选择时间

picker-view 是 UniApp 中用于展示和选择数据的组件。它适用于创建多列选择器&#xff0c;类似于 iOS 和 Android 系统中的选择器视图。以下是 picker-view 的详细介绍&#xff0c;包括用法、属性和事件。 一 用法 <template><view><picker-view :value"…

机器学习——Stacking

Stacking&#xff1a; 方法&#xff1a;训练多个模型(可以是强模型)&#xff0c;然后将这些模型的预测结果作为新的特征&#xff0c;输入到下一层新的模型&#xff08;可以是多个&#xff09;中进行训练&#xff0c;从而得到最终的预测结果。 代表&#xff1a;Stacking本身并没…

Java多线程Thread及其原理深度解析

文章目录 1. 实现多线程的方式2. Thread 部分源码2.1. native 方法注册2.2. Thread 中的成员变量2.3. Thread 构造方法与初始化2.4. Thread 线程状态与操作系统状态2.4. start() 与 run() 方法2.5. sleep() 方法2.6. join() 方法2.7. interrupt() 方法 本文参考&#xff1a; 线…

OpenCV_最简单的鼠标截取ROI区域

在OpenCV中也存在鼠标的操作&#xff0c;今天我们先介绍一下鼠标中的操作事件 void setMousecallback(const string& winname, MouseCallback onMouse, void* userdata0) setMousecallback参数说明&#xff1a; winname:窗口的名字 onMouse:鼠标响应函数&#xff0c;回调…

接口加解密及数据加解密

目录 一、 加解密方式介绍 1.1 Hash算法加密 1.2. 对称加密 1.3 非对称加密 二、 我们要讲什么&#xff1f; 三、 接口加解密 四、 数据加解密 一、 加解密方式介绍 所有的加密方式我们可以分为三类&#xff1a;对称加密、非对称加密、Hash算法加密。 算法内部的具体实现…

【后端开发】JavaEE初阶—线程的理解和编程实现

前言&#xff1a; &#x1f31f;&#x1f31f;本期讲解多线程的知识哟~~~&#xff0c;希望能帮到屏幕前的你。 &#x1f308;上期博客在这里&#xff1a;【后端开发】JavaEE初阶——计算机是如何工作的&#xff1f;&#xff1f;&#xff1f;-CSDN博客 &#x1f308;感兴趣的小伙…

【设计模式】UML类图

目录 前言 一、类图概述 二、类图的作用 三、类图表示法 四、类之间关系的表示方法 1. 关联关系 1.1 单向关联 1.2 双向关联 1.3 自关联 2. 聚合关系 3. 组合关系 4. 依赖关系 5. 继承关系 6. 实现关系 总结 前言 统一建模语言&#xff08; Unified Modeling La…

游戏如何对抗定制挂

近年来&#xff0c;游戏安全对抗强度相比以往更加激烈&#xff0c;具体表现在“定制挂”趋势显著。在近期收集的近万款外挂样本中&#xff0c;定制挂约占比78%&#xff0c;常见的内存修改器、变速器等通用作弊手段占比正在下降。 所谓定制挂&#xff0c;是指针对某款游戏单独开…