C++微服务实战中好友管理子服务的全面解析

C++微服务实战中好友管理子服务的全面解析

【C++ 微服务实战】IM 好友管理子服务全解析:从 Proto 定义到高可用部署

在即时通讯(IM)系统中,好友管理子服务是连接 “用户社交关系” 与 “聊天会话” 的核心枢纽 —— 它既要处理好友申请、关系维护,也要管理单聊 / 群聊会话的创建与成员维护。本文基于实际项目代码(C++/brpc/Protobuf/ODB),从 “接口设计”“数据模型”“核心逻辑”“高可用部署” 四个维度,完整拆解好友管理子服务的实现细节,带你理解如何构建一个解耦、可靠的微服务。

一、服务定位与技术栈

在 IM 微服务架构中,好友管理子服务(Friend Server)的核心职责是 **“管理用户社交关系” 与 “维护聊天会话容器”**,向上对接网关服务接收客户端请求,向下依赖 MySQL/ES 存储数据,横向调用用户服务、消息服务完成协作。

1. 核心业务范围

  • 好友关系:申请、同意 / 拒绝、删除、查询好友列表;
  • 会话管理:创建单聊 / 群聊会话、查询会话列表、获取群成员;
  • 用户搜索:基于 ES 实现昵称 / 手机号模糊搜索(过滤已加好友);
  • 申请事件:管理好友申请的生命周期(待处理→已处理)。

2. 技术栈选型

技术组件作用说明项目中的封装 / 使用场景
brpc高性能 RPC 框架搭建 RPC 服务器,提供 Protobuf 接口
Protobuf数据序列化 / 接口定义定义服务接口(如 FriendAdd/GetChatSessionList
ODBMySQL ORM 框架映射数据库表到 C++ 对象(如 RelationTable
MySQL关系型数据库存储好友关系、会话信息、申请事件
Elasticsearch全文搜索引擎实现用户模糊搜索,过滤已加好友和当前用户
Etcd服务注册与发现注册自身服务,发现用户服务、消息服务地址
gflags命令行参数解析配置服务端口、数据库地址、日志等级等
spdlog日志框架输出调试 / 错误日志,支持文件 / 控制台双输出

二、ProtoBuf 接口设计:定义服务的 “通信契约”

微服务的核心是 “接口化协作”,好友管理子服务的对外交互全靠 Protobuf 定义的接口。以下结合项目代码,拆解核心接口的设计逻辑(对应 friend.pb.h)。

1. 核心接口概览

好友服务共提供 10 个核心接口,覆盖 “好友关系”“会话管理”“搜索” 三大场景,接口设计遵循 “单一职责” 原则:

接口名称业务场景核心作用
GetFriendList查看好友列表返回当前用户的所有好友信息(昵称、头像等)
FriendAdd发送好友申请校验关系后创建申请事件,返回事件 ID
FriendAddProcess处理好友申请同意则创建好友关系 + 单聊会话,拒绝则删除事件
FriendRemove删除好友移除好友关系 + 单聊会话 + 会话成员
FriendSearch搜索用户ES 模糊搜索,过滤已加好友和当前用户
GetPendingFriendEventList查看待处理申请返回当前用户收到的所有未处理好友申请
GetChatSessionList查看聊天会话列表返回单聊 / 群聊会话,包含最新消息
ChatSessionCreate创建群聊会话生成群聊 ID,添加会话信息和成员
GetChatSessionMember查看群成员返回指定群聊的所有成员信息

2. 关键接口字段解析(以好友申请为例)

FriendAdd(发送好友申请)和 FriendAddProcess(处理申请)为例,理解接口如何承载业务逻辑:

(1)FriendAdd:发送好友申请
// 发送好友申请请求 message FriendAddReq { string request_id = 1; // 链路追踪ID(分布式调用唯一标识) string user_id = 2; // 申请人ID(当前登录用户) string respondent_id = 3; // 被申请人ID(目标用户) } // 发送好友申请响应 message FriendAddRsp { string request_id = 1; // 对应请求ID bool success = 2; // 申请是否成功 string errmsg = 3; // 错误信息(失败时填充) string notify_event_id = 4; // 申请事件ID(用于后续处理) } 

业务逻辑

用户 A 申请加 B 为好友时,网关会先鉴权并填充 user_id(A 的 ID),服务端需校验:

  1. A 和 B 是否已为好友(查 relation 表);
  2. A 是否已向 B 发送过申请(查 friend_apply 表);
  3. 校验通过后生成 notify_event_id(UUID),存入 friend_apply 表。
(2)FriendAddProcess:处理好友申请
// 处理好友申请请求 message FriendAddProcessReq { string request_id = 1; // 链路追踪ID string user_id = 2; // 被申请人ID(当前登录用户) string apply_user_id = 3; // 申请人ID(A的ID) bool agree = 4; // 是否同意(true=同意,false=拒绝) string notify_event_id = 5; // 申请事件ID(对应FriendAdd返回的ID) } // 处理好友申请响应 message FriendAddProcessRsp { string request_id = 1; // 对应请求ID bool success = 2; // 处理是否成功 string errmsg = 3; // 错误信息 string new_session_id = 4; // 同意时生成的单聊会话ID(网关推送用) } 

业务逻辑

用户 B 处理 A 的申请时,服务端需:

  1. 校验 notify_event_id 对应的申请是否存在;
  2. 无论同意 / 拒绝,先删除申请事件(避免重复处理);
  3. 同意则创建 双向好友关系relation 表插两条记录)、单聊会话chat_session 表)、会话成员chat_session_member 表插 A 和 B 的记录)。

三、ODB 数据模型:映射 “业务对象” 与 “数据库表”

好友服务的核心数据(好友关系、会话、申请事件)存储在 MySQL 中,通过 ODB(Object-Relational Mapping) 框架将 C++ 对象与数据库表关联,避免直接写 SQL 语句,提升代码可维护性。

1. 核心表结构与 ODB 映射

以下是好友服务依赖的 4 张核心表,对应 ODB 映射类与业务含义:

数据库表名ODB 映射类核心字段(C++/MySQL)业务作用
relation(好友关系表)RelationTable_user_id(varchar64)/_peer_id(varchar64)存储双向好友关系(A→B 和 B→A)
friend_apply(申请事件表)FriendApplyTable_event_id(varchar64)/_user_id/_peer_id存储好友申请的生命周期(待处理 / 已处理)
chat_session(会话表)ChatSessionTable_chat_session_id(varchar64)/_session_type(tinyint)存储单聊 / 群聊会话元信息(类型、名称)
chat_session_member(会话成员表)ChatSessionMemeberTable_chat_session_id/_user_id存储 “会话 - 用户” 关联(群聊成员、单聊双方)

2. ODB 映射类实现(以好友关系表为例)

RelationTable 为例,展示 ODB 如何将 C++ 类映射到 MySQL 表:

// mysql_relation.hpp(ODB 映射类) #pragma once #include <odb/core.hxx> #include <string> namespace zrt { // 好友关系实体类(对应 relation 表) #pragma db object table("relation") class Relation { public: Relation() {} Relation(const std::string& user_id, const std::string& peer_id) : _user_id(user_id), _peer_id(peer_id) {} // Getter/Setter(省略) std::string user_id() const { return _user_id; } void user_id(const std::string& v) { _user_id = v; } std::string peer_id() const { return _peer_id; } void peer_id(const std::string& v) { _peer_id = v; } private: friend class odb::access; #pragma db id auto // 自增主键(MySQL 中为 bigint) unsigned long _id; // 数据库内部唯一标识 #pragma db type("varchar(64)") index // 加索引,加速按 user_id 查询 std::string _user_id; // 主动方用户ID(如 A 添加 B,则为 A) #pragma db type("varchar(64)") std::string _peer_id; // 被动方用户ID(如 A 添加 B,则为 B) }; // ODB 表操作类(封装 CRUD) class RelationTable { public: using ptr = std::shared_ptr<RelationTable>; RelationTable(const std::shared_ptr<odb::core::database>& db) : _db(db) {} // 插入双向好友关系(A→B 和 B→A) bool insert(const std::string& user_id, const std::string& peer_id) { try { odb::transaction t(_db->begin()); // 插入两条记录,确保双向好友关系 _db->persist(Relation(user_id, peer_id)); _db->persist(Relation(peer_id, user_id)); t.commit(); return true; } catch (const odb::exception& e) { LOG_ERROR("Insert relation failed: {}", e.what()); return false; } } // 检查好友关系是否存在 bool exists(const std::string& user_id, const std::string& peer_id) { try { odb::transaction t(_db->begin()); // 查 user_id→peer_id 是否存在 auto count = _db->query_value<Relation>( odb::query<Relation>::_user_id == user_id && odb::query<Relation>::_peer_id == peer_id ).count(); t.commit(); return count > 0; } catch (const odb::exception& e) { LOG_ERROR("Check relation exists failed: {}", e.what()); return false; } } // 其他方法:删除关系、查询用户的所有好友ID(省略) private: std::shared_ptr<odb::core::database> _db; }; } // namespace zrt 

关键设计

  • 双向关系:插入好友时必须存两条记录(A→BB→A),确保双方查询好友列表时都能找到对方;
  • 事务保障:通过 ODB 的 transaction 确保 “插入两条记录” 要么全成功,要么全失败,避免数据不一致;
  • 索引优化:_user_id 加索引,查询 “用户的所有好友” 时(select peer_id from relation where user_id=?)性能提升 10+ 倍。

四、核心服务实现:从接口逻辑到服务协作

好友服务的核心逻辑封装在 FriendServiceImpl 类中,通过 “接口实现 + 私有方法” 的方式,实现业务解耦与代码复用。以下拆解关键接口的实现细节,以及服务间的协作逻辑。

1. 服务初始化:依赖注入与解耦设计

FriendServiceImpl 的构造函数通过依赖注入(DI)传入所有外部依赖,避免硬编码,便于测试与扩展:

// FriendServiceImpl 构造函数(依赖注入) FriendServiceImpl( const std::shared_ptr<elasticlient::Client>& es_client, // ES 客户端 const std::shared_ptr<odb::core::database>& mysql_client, // MySQL 客户端 const ServiceManager::ptr& channel_manager, // RPC 信道管理器 const std::string& user_service_name, // 用户服务名称(Etcd 注册键) const std::string& message_service_name // 消息服务名称 ) : _es_user(std::make_shared<ESUser>(es_client)), // ES 用户搜索封装 _mysql_apply(std::make_shared<FriendApplyTable>(mysql_client)), // 申请事件表操作 _mysql_chat_session(std::make_shared<ChatSessionTable>(mysql_client)), // 会话表操作 _mysql_chat_session_member(std::make_shared<ChatSessionMemeberTable>(mysql_client)), // 会话成员表操作 _mysql_relation(std::make_shared<RelationTable>(mysql_client)), // 好友关系表操作 _user_service_name(user_service_name), _message_service_name(message_service_name), _mm_channels(channel_manager) {} // 管理用户服务、消息服务的 RPC 信道 

解耦设计

  • 数据层解耦:通过 RelationTable/FriendApplyTable 封装 MySQL 操作,业务逻辑不直接依赖 ODB;
  • 服务间解耦:通过 ServiceManager 管理其他服务的 RPC 信道,避免直接硬编码服务地址(如用户服务地址变化时,只需更新 Etcd,无需改代码)。

2. 关键接口实现:以 “处理好友申请” 为例

FriendAddProcess 是好友服务最核心的接口之一,涉及 “申请事件删除、好友关系创建、会话创建” 三个关键步骤,需确保数据一致性:

void FriendServiceImpl::FriendAddProcess( ::google::protobuf::RpcController* controller, const ::zrt::FriendAddProcessReq* request, ::zrt::FriendAddProcessRsp* response, ::google::protobuf::Closure* done) { brpc::ClosureGuard rpc_guard(done); // 自动释放 Closure,避免内存泄漏 // 1. 定义错误回调(统一响应格式) auto err_response = [this, response](const std::string& rid, const std::string& errmsg) { response->set_request_id(rid); response->set_success(false); response->set_errmsg(errmsg); }; // 2. 提取请求关键参数 std::string rid = request->request_id(); std::string eid = request->notify_event_id(); // 申请事件ID std::string uid = request->user_id(); // 被申请人(B) std::string pid = request->apply_user_id(); // 申请人(A) bool agree = request->agree(); // 是否同意 // 3. 校验申请事件是否存在 bool ret = _mysql_apply->exists(pid, uid); if (!ret) { LOG_ERROR("{}-未找到{}-{}的好友申请事件", rid, pid, uid); return err_response(rid, "申请事件不存在"); } // 4. 删除申请事件(无论同意/拒绝,事件都需清理) ret = _mysql_apply->remove(pid, uid); if (!ret) { LOG_ERROR("{}-删除申请事件{}-{}失败", rid, pid, uid); return err_response(rid, "处理申请失败"); } // 5. 同意:创建好友关系、单聊会话、会话成员 std::string session_id; if (agree) { // 5.1 插入双向好友关系 ret = _mysql_relation->insert(uid, pid); if (!ret) { LOG_ERROR("{}-创建好友关系{}-{}失败", rid, uid, pid); return err_response(rid, "添加好友失败"); } // 5.2 创建单聊会话(类型=SINGLE,名称为空,客户端后续补全) session_id = uuid(); // 生成全局唯一会话ID(如 "SESSION-6e6b2d9a") ChatSession session(session_id, "", ChatSessionType::SINGLE); ret = _mysql_chat_session->insert(session); if (!ret) { LOG_ERROR("{}-创建单聊会话{}失败", rid, session_id); return err_response(rid, "创建会话失败"); } // 5.3 添加会话成员(A 和 B) std::vector<ChatSessionMember> members = { ChatSessionMember(session_id, uid), ChatSessionMember(session_id, pid) }; ret = _mysql_chat_session_member->append(members); if (!ret) { LOG_ERROR("{}-添加会话成员{}-{}失败", rid, uid, pid); return err_response(rid, "添加会话成员失败"); } } // 6. 组织响应(同意时返回会话ID,网关用于推送会话创建通知) response->set_request_id(rid); response->set_success(true); response->set_new_session_id(session_id); LOG_INFO("{}-处理好友申请成功:{}→{},agree={}", rid, pid, uid, agree); } 

关键逻辑亮点

  • 错误统一处理:通过 err_response 回调函数,确保所有错误场景的响应格式一致;
  • 数据一致性:删除申请事件后再创建关系,避免 “申请事件残留”;若创建会话失败,直接返回错误,后续可通过重试机制补全;
  • 会话 ID 生成:用 UUID 确保全局唯一,避免不同服务实例生成重复会话 ID。

3. 服务间协作:调用用户服务与消息服务

好友服务本身不存储用户信息(如昵称、头像)和消息数据,需通过 RPC 调用其他服务获取,核心封装在 GetUserInfoGetRecentMsg 两个私有方法中:

(1)调用用户服务批量获取用户信息
// 批量获取用户信息(调用用户服务的 GetMultiUserInfo 接口) bool FriendServiceImpl::GetUserInfo( const std::string& rid, const std::unordered_set<std::string>& uid_list, // 待获取的用户ID列表 std::unordered_map<std::string, UserInfo>& user_list) { // 输出:用户ID→用户信息 // 1. 从 ServiceManager 获取用户服务的 RPC 信道 auto channel = _mm_channels->choose(_user_service_name); if (!channel) { LOG_ERROR("{}-获取用户服务信道失败", rid); return false; } // 2. 构造 RPC 请求 GetMultiUserInfoReq req; GetMultiUserInfoRsp rsp; req.set_request_id(rid); for (const auto& uid : uid_list) { req.add_users_id(uid); // 批量添加用户ID,减少 RPC 调用次数 } // 3. 发起 RPC 调用 brpc::Controller cntl; zrt::UserService_Stub stub(channel.get()); stub.GetMultiUserInfo(&cntl, &req, &rsp, nullptr); // 4. 处理调用结果 if (cntl.Failed()) { LOG_ERROR("{}-调用用户服务失败:{}", rid, cntl.ErrorText()); return false; } if (!rsp.success()) { LOG_ERROR("{}-批量获取用户信息失败:{}", rid, rsp.errmsg()); return false; } // 5. 整理结果(用户ID→UserInfo) for (const auto& item : rsp.users_info()) { user_list.emplace(item.first, item.second); } return true; } 

优化点

  • 批量调用:一次性获取多个用户信息,避免循环调用用户服务(减少网络开销,提升性能);
  • 信道管理:ServiceManager 自动管理用户服务的地址(从 Etcd 发现),服务扩容 / 缩容时无需重启好友服务。
(2)调用消息服务获取会话最新消息
// 获取会话最新1条消息(调用消息服务的 GetRecentMsg 接口) bool FriendServiceImpl::GetRecentMsg( const std::string& rid, const std::string& session_id, // 会话ID MessageInfo& msg) { // 输出:最新消息 auto channel = _mm_channels->choose(_message_service_name); if (!channel) { LOG_ERROR("{}-获取消息服务信道失败", rid); return false; } GetRecentMsgReq req; GetRecentMsgRsp rsp; req.set_request_id(rid); req.set_chat_session_id(session_id); req.set_msg_count(1); // 只获取最新1条消息 brpc::Controller cntl; zrt::MsgStorageService_Stub stub(channel.get()); stub.GetRecentMsg(&cntl, &req, &rsp, nullptr); if (cntl.Failed() || !rsp.success()) { LOG_WARN("{}-获取会话{}最新消息失败:{}", rid, session_id, cntl.Failed() ? cntl.ErrorText() : rsp.errmsg()); return false; } // 提取最新消息 if (rsp.msg_list_size() > 0) { msg.CopyFrom(rsp.msg_list(0)); return true; } return false; } 

业务价值

用户查询会话列表时,需显示 “每条会话的最新消息”(如 “张三:下午开会”),好友服务通过调用消息服务获取该数据,避免自身存储消息,符合微服务 “数据私有” 原则。

五、服务搭建与高可用部署

好友服务通过 FriendServerBuilder 模式封装初始化流程,支持 “配置解析→依赖构建→服务启动” 的一站式部署,同时通过连接池、服务发现确保高可用。

1. Builder 模式:简化服务初始化

FriendServerBuilder 封装了 ES、MySQL、Etcd、RPC 服务器的初始化逻辑,屏蔽复杂细节:

class FriendServerBuilder { public: // 1. 构建 ES 客户端(连接 ES 集群) void make_es_object(const std::vector<std::string> host_list) { _es_client = ESClientFactory::create(host_list); } // 2. 构建 MySQL 客户端(带连接池) void make_mysql_object( const std::string& user, const std::string& pswd, const std::string& host, const std::string& db, const std::string& cset, int port, int conn_pool_count) { _mysql_client = ODBFactory::create( user, pswd, host, db, cset, port, conn_pool_count); } // 3. 构建服务发现(Etcd)与 RPC 信道管理 void make_discovery_object( const std::string& reg_host, // Etcd 地址 const std::string& base_service, // 服务监控根目录 const std::string& user_service_name, // 用户服务名称 const std::string& message_service_name) { // 消息服务名称 _mm_channels = std::make_shared<ServiceManager>(); _mm_channels->declared(user_service_name); // 声明需管理的服务 _mm_channels->declared(message_service_name); // Etcd 回调:服务上线/下线时更新信道 auto on_service_online = std::bind(&ServiceManager::onServiceOnline, _mm_channels.get(), std::placeholders::_1, std::placeholders::_2); auto on_service_offline = std::bind(&ServiceManager::onServiceOffline, _mm_channels.get(), std::placeholders::_1, std::placeholders::_2); // 初始化 Etcd 服务发现 _service_discoverer = std::make_shared<Discovery>( reg_host, base_service, on_service_online, on_service_offline); } // 4. 构建 RPC 服务器(brpc) void make_rpc_server(uint16_t port, int32_t timeout, uint8_t num_threads) { _rpc_server = std::make_shared<brpc::Server>(); // 创建好友服务实现类 FriendServiceImpl* service = new FriendServiceImpl( _es_client, _mysql_client, _mm_channels, _user_service_name, _message_service_name); // 添加服务到 RPC 服务器 if (_rpc_server->AddService(service, brpc::ServiceOwnership::SERVER_OWNS_SERVICE) != 0) { LOG_ERROR("添加好友服务到 RPC 服务器失败"); abort(); } // 配置 RPC 服务器(连接超时、IO 线程数) brpc::ServerOptions options; options.idle_timeout_sec = timeout; // 空闲连接超时 options.num_threads = num_threads; // IO 线程数(根据 CPU 核心数配置) if (_rpc_server->Start(port, &options) != 0) { LOG_ERROR("RPC 服务器启动失败(端口:{})", port); abort(); } } // 5. 注册服务到 Etcd(供网关发现) void make_registry_object( const std::string& reg_host, // Etcd 地址 const std::string& service_name, // 服务名称(如 /service/friend_service/instance) const std::string& access_host) { // 服务访问地址(如 192.168.1.100:10006) _registry_client = std::make_shared<Registry>(reg_host); _registry_client->registry(service_name, access_host); } // 6. 构建最终的好友服务实例 FriendServer::ptr build() { // 校验所有依赖是否初始化完成 if (!_es_client || !_mysql_client || !_rpc_server) { LOG_ERROR("服务依赖未初始化完成"); abort(); } return std::make_shared<FriendServer>( _service_discoverer, _registry_client, _es_client, _mysql_client, _rpc_server); } private: // 依赖对象(省略) Registry::ptr _registry_client; std::shared_ptr<elasticlient::Client> _es_client; std::shared_ptr<odb::core::database> _mysql_client; ServiceManager::ptr _mm_channels; Discovery::ptr _service_discoverer; std::shared_ptr<brpc::Server> _rpc_server; }; 

2. 部署入口:main 函数解析配置与启动

main 函数通过 gflags 解析命令行参数,调用 Builder 构建服务并启动:

int main(int argc, char* argv[]) { // 1. 解析命令行参数(gflags) google::ParseCommandLineFlags(&argc, &argv, true); // 2. 初始化日志(调试模式输出控制台,发布模式输出文件) zrt::init_logger(FLAGS_run_mode, FLAGS_log_file, FLAGS_log_level); // 3. 构建好友服务 zrt::FriendServerBuilder fsb; // 3.1 初始化 ES(地址从参数获取) fsb.make_es_object({FLAGS_es_host}); // 3.2 初始化 MySQL(连接池大小=FLAGS_mysql_pool_count) fsb.make_mysql_object( FLAGS_mysql_user, FLAGS_mysql_pswd, FLAGS_mysql_host, FLAGS_mysql_db, FLAGS_mysql_cset, FLAGS_mysql_port, FLAGS_mysql_pool_count); // 3.3 初始化服务发现(Etcd + 信道管理) fsb.make_discovery_object( FLAGS_registry_host, FLAGS_base_service, FLAGS_user_service, FLAGS_message_service); // 3.4 初始化 RPC 服务器(端口=FLAGS_listen_port) fsb.make_rpc_server( FLAGS_listen_port, FLAGS_rpc_timeout, FLAGS_rpc_threads); // 3.5 注册服务到 Etcd fsb.make_registry_object( FLAGS_registry_host, FLAGS_base_service + FLAGS_instance_name, FLAGS_access_host); // 4. 启动服务(阻塞直到收到退出信号) auto server = fsb.build(); server->start(); return 0; } 

部署关键参数

  • FLAGS_mysql_pool_count:MySQL 连接池大小(建议设为 CPU 核心数的 2~4 倍,避免连接过多);
  • FLAGS_rpc_threads:brpc IO 线程数(通常设为 CPU 核心数,充分利用多核性能);
  • FLAGS_access_host:服务对外访问地址(需与网关在同一网络,确保网关能调用)。

六、技术亮点与扩展方向

1. 核心技术亮点

(1)解耦设计:服务间无硬依赖

通过 ServiceManager 管理其他服务的 RPC 信道,服务地址变化时只需更新 Etcd,无需修改代码;数据层通过 ODB 封装 MySQL 操作,更换数据库时只需修改 ODB 映射,不影响业务逻辑。

(2)数据一致性保障
  • ODB 事务:确保 “插入双向好友关系”“删除申请事件 + 创建会话” 等操作的原子性;
  • 唯一 ID 生成:申请事件 ID、会话 ID 用 UUID 确保全局唯一,避免分布式环境下的 ID 冲突。
(3)性能优化
  • 批量 RPC 调用:获取好友列表时批量调用用户服务,减少网络开销;
  • ES 搜索过滤:搜索用户时先过滤已加好友和当前用户,避免无效结果;
  • 索引优化:MySQL 表的 user_id/chat_session_id 加索引,查询性能提升 10~100 倍。
(4)高可用设计
  • 连接池:MySQL 连接池避免频繁创建 / 销毁连接;
  • 服务发现:Etcd 实现服务动态发现,服务下线时自动切换到其他实例;
  • 日志监控:spdlog 输出详细日志,便于问题排查。

2. 后续扩展方向

  • 好友备注功能:在 relation 表中添加 remark 字段,支持用户给好友设置备注名;
  • 黑名单功能:新增 blacklist 表,支持屏蔽非好友消息;
  • 会话权限管理:群聊添加 “管理员”“禁言” 功能,在 chat_session_member 表中添加 role 字段;
  • 数据分片:用户量增大时,按 user_id 对 MySQL 表进行分片,避免单表数据量过大。

七、总结

好友管理子服务作为 IM 系统的核心组件,通过 “Proto 接口定义规范通信、ODB 映射隔离数据层、ServiceManager 解耦服务协作、Builder 模式简化部署”,实现了一个高可用、可扩展的微服务。其设计思路不仅适用于 IM 系统,也可复用在社交、电商等需要 “关系管理” 的业务场景中。

从代码实现来看,该服务的核心价值在于 **“业务与技术的平衡”**:既满足了好友申请、会话管理等业务需求,又通过解耦、高可用设计确保了服务的稳定性与可维护性,为后续系统扩容与功能迭代打下了坚实基础。

Read more

前端如何利用UEditor导入Word文档中的复杂公式?

CMS企业官网Word一键转存升级方案 (.NET版) 哈喽各位.NET战友们!我是山东某科技公司的"全干工程师"老王,最近接了个企业官网改版的外包项目,客户爸爸突然甩来个新需求——要在新闻发布系统里加Word一键粘贴功能!今天我就把这个价值680元巨款的技术方案分享给大家(顺便求点赞转发加群啊兄弟们!)。 客户需求解读 客户想要的功能说白了就是: 1. Word内容粘贴:从Word复制直接粘贴到编辑器,格式不乱 2. Office全家桶导入:Word/Excel/PPT/PDF全支持 3. 公式支持:LaTeX/MathType公式都要能转MathML 4. 一键上传:图片自动传到阿里云OSS 5. 高龄友好:操作要简单到"我奶奶都会用" 技术选型方案 1. 编辑器增强方案 经过我三天三夜的"科学上网"研究,发现UEditor扩展是最佳选择: // UEditor插件核心代码 (frontend/

By Ne0inhk

什么是NVIDIA Isaac Sim WebRTC Streaming Client?

NVIDIA Isaac Sim WebRTC Streaming Client是NVIDIA为Isaac Sim打造的远程串流客户端,基于WebRTC协议,可让用户在无高性能GPU的设备上远程访问运行于云端或工作站的Isaac Sim(含无头模式),实现低延迟交互与图形化界面显示,是机器人仿真远程协作与开发的核心工具。以下从核心特性、运行条件、使用流程、关键配置与常见问题等方面详细介绍: 核心定位与优势 * 核心功能:将Isaac Sim的图形界面、物理仿真画面与交互操作远程串流至本地,支持模型编辑、场景调试、机器人控制等全流程操作,无需本地渲染能力。 * 核心优势 * 低延迟传输:WebRTC协议优化实时音视频流,适配机器人仿真的实时交互需求。 * 跨平台兼容:支持Linux、Windows、macOS客户端,适配主流桌面系统。 * 适配无头模式:完美对接Isaac Sim headless实例,适合云端/服务器部署场景。 * 高安全性:通过加密传输与端口隔离,保障远程访问安全。 运行要求 1. 服务端(Isaac Sim 侧)

By Ne0inhk
Spring Boot Web 后端开发注解核心

Spring Boot Web 后端开发注解核心

在 Spring Boot Web 后端开发中,注解(Annotation)是核心,它们极大简化了配置、依赖管理、请求映射、数据持久化等。本文将按照功能分类,详细列出常用注解的作用、使用方式、典型场景,并附带简明代码示例,帮助你全面掌握并灵活运用。 文章目录 * 1. 核心启动与配置注解 * 2. 控制器与请求映射注解 * 3. 依赖注入与组件注册注解 * 4. 数据访问(JPA / Spring Data)注解 * 5. 事务管理注解 * 6. 缓存注解 * 7. 异步与定时任务注解 * 8. 异常处理与控制器增强 * 9. 跨域支持注解 * 10. 条件化配置注解(自动配置相关) * 11. 测试注解 * 12. Lombok 常用注解(简化代码)

By Ne0inhk
禹神:一小时快速上手Electron,前端Electron开发教程,笔记。一篇文章入门Electron

禹神:一小时快速上手Electron,前端Electron开发教程,笔记。一篇文章入门Electron

⚠️注意: 1️⃣原视频打包时,是使用electron-builder打包,使用electron-builder打包,打包时要访问github需要修仙术才能访问。 2️⃣本笔记,使用Electron Forge进行打包,使用Electron Forge不需要访问github更友好。在Electron 官网中也推荐使用这种方式 👉Electron 一、Electron是什么 简单的一句话,就是用html+css+js+nodejs+(Native Api)做兼容多个系统(Windows、Linux、Mac)的软件。 官网解释如下(有点像绕口令): Electron是一个使用 JavaScript、HTML 和 CSS 构建桌面应用程序的框架。 嵌入 Chromium 和 Node.js 到 二进制的 Electron 允许您保持一个 JavaScript 代码代码库并创建 在Windows上运行的跨平台应用 macOS和Linux—

By Ne0inhk