Go-Zero项目开发7: 构建社交服务RPC与好友/群业务实现
纲要社交服务业务需求好友关系管理申请、处理、好友列表群组管理创建、申请、加入、退群等数据表设计好友关系表friend的冗余存储方案好友申请表friend_request群信息表、群成员表、群申请表项目结构更新apps/social/rpc目录及关键文件构建社交 RPC 服务编写social.proto定义服务接口使用goctl rpc生成代码配置etc/social.yaml及依赖注入模型生成与扩展基于 SQL 生成 5 张表的模型解决包名冲突的前缀设计扩展模型方法好友关系检查、申请查询、批量新增好友记录业务逻辑实现好友申请重复检查、已有关系校验、新增申请记录好友申请处理状态校核、事务内修改状态并建立好友关系好友列表查询事务封装模型层支持Transact方法测试验证通过 gRPC 客户端进行接口测试社交服务业务需求在即时通讯系统中社交模块负责管理用户之间的关系网络核心功能包括好友管理申请好友、处理申请、好友列表查询。群组管理创建群组、申请加群、处理加群申请、群成员列表、退群。本文重点围绕好友关系的设计及核心业务实现群组功能的实现模式与之类似可在后续迭代中补充。数据表设计社交服务涉及 5 张数据表好友关系表friend、好友申请表friend_request、群信息表group、群成员表group_member、群申请表group_request。好友关系表设计是一个关键点常见两种方案方案存储方式优点缺点冗余存储双方分别记录一条user_id → friend_id数据查询简单性能高数据冗余占用双倍存储唯一 Key根据双方 ID 排序生成唯一 Key仅存储一条节省空间查询复杂需分别按双方 ID 检索本系统采用冗余存储方案因其查询效率高且好友关系数据总量相对可控。表结构示例如下CREATETABLEfriend(idbigintNOTNULLAUTO_INCREMENT,user_idvarchar(64)NOTNULLCOMMENT用户ID,friend_idvarchar(64)NOTNULLCOMMENT好友ID,statustinyint(1)NOTNULLDEFAULT1COMMENT1:正常,create_timedatetimeNOTNULLDEFAULTCURRENT_TIMESTAMP,PRIMARYKEY(id),KEYidx_user_id(user_id),KEYidx_friend_id(friend_id))ENGINEInnoDBDEFAULTCHARSETutf8mb4COMMENT好友关系表;friend_request表类似增加请求状态字段。项目结构更新在apps/social/rpc下组织社交服务代码apps/social/rpc/ ├── internal/ │ ├── config/ │ ├── server/ │ ├── svc/ │ └── logic/ ├── model/ # 生成的模型代码带前缀 ├── social.proto └── social.go构建社交 RPC 服务编写 Proto 文件在 social.proto 中先定义好友相关 RPCsyntax proto3; package social; option go_package ./social; message FriendApplyRequest { string user_id 1; string friend_id 2; // 目标用户ID string reason 3; } message FriendApplyResponse {} message FriendApplyHandleRequest { int64 apply_id 1; int32 handle_type 2; // 1:通过 2:拒绝 } message FriendApplyHandleResponse {} message FriendListRequest { string user_id 1; } message FriendListResponse { repeated string friend_ids 1; } service Social { // 申请好友 rpc FriendApply(FriendApplyRequest) returns (FriendApplyResponse); // 处理好友申请 rpc FriendApplyHandle(FriendApplyHandleRequest) returns (FriendApplyHandleResponse); // 好友列表 rpc FriendList(FriendListRequest) returns (FriendListResponse); }生成代码执行命令$ goctl rpc protoc apps/social/rpc/social.proto--gooutapps/social/rpc/ --go-grpcoutapps/social/rpc/--zrpc_outapps/social/rpc/生成后的social.go为服务入口internal下包含通用组件。配置与依赖注入internal/config/config.gopackageconfigimport(github.com/zeromicro/go-zero/core/stores/cachegithub.com/zeromicro/go-zero/zrpc)typeConfigstruct{zrpc.RpcServerConf MySQLstruct{DataSourcestring}CacheRedis cache.CacheConf}etc/social.yaml示例Name:social.rpcListenOn:0.0.0.0:10002Etcd:Hosts:-192.168.1.10:2379Key:social.rpcMySQL:DataSource:root:123456tcp(192.168.1.10:3306)/im_db?charsetutf8mb4parseTimeTrueCacheRedis:-Host:192.168.1.10:6379Pass:yourpassinternal/svc/servicecontext.go中注入所有模型packagesvcimport(im-system/apps/social/rpc/internal/configim-system/apps/social/rpc/modelgithub.com/zeromicro/go-zero/core/stores/sqlx)typeServiceContextstruct{Config config.Config FriendModel model.FriendModel FriendReqModel model.FriendRequestModel GroupModel model.GroupModel GroupMemberModel model.GroupMemberModel GroupReqModel model.GroupRequestModel}funcNewServiceContext(c config.Config)*ServiceContext{conn:sqlx.NewMysql(c.MySQL.DataSource)// 注意生成模型时指定了前缀因此创建的模型类型名会有前缀returnServiceContext{Config:c,FriendModel:model.NewFriendModel(conn,c.CacheRedis),FriendReqModel:model.NewFriendRequestModel(conn,c.CacheRedis),// ... 其他模型}}模型生成与扩展生成数据模型使用goctl model基于 SQL 生成模型通过-prefix参数添加前缀避免多个服务模型命名冲突$ goctl model mysql ddl-srcdeploy/sql/social.sql-dirapps/social/rpc/model-prefixsocial_-stylegoZero扩展模型方法为满足业务需求在模型层添加自定义方法。1 ) 检查好友关系FindOneByUserIdAndFriendId// 在 friendmodel_ext.go 中func(m defaultFriendModel)FindOneByUserIdAndFriendId(ctx context.Context,uid,fuidstring)(Friend,error){query:fmt.Sprintf(select %s from %s where userid ? and friendid ? limit 1,friendRows,m.table)varresp Friend err:m.conn.QueryRowCtx(ctx,resp,query,uid,fuid)iferr!nil{returnnil,err}returnresp,nil}2 ) 查询好友申请记录FindByUserIdAndFriendId// friendrequestmodel_ext.gofunc(m defaultFriendRequestModel)FindByUserIdAndFriendId(ctx context.Context,reqUid,targetUidstring)(FriendRequest,error){query:fmt.Sprintf(select %s from %s where userid ? and friendid ? and status 1 limit 1,friendRequestRows,m.table)varresp FriendRequest err:m.conn.QueryRowCtx(ctx,resp,query,reqUid,targetUid)iferr!nil{returnnil,err}returnresp,nil}3 ) 批量新增好友记录// friendmodel_ext.gofunc(m defaultFriendModel)InsertBatch(ctx context.Context,conn sqlx.Session,list[]Friend)error{iflen(list)0{returnnil}varbuf strings.Builder buf.WriteString(fmt.Sprintf(insert into %s (%s) values ,m.table,friendRows))args:make([]interface{},0,len(list)*len(friendFieldNames))fori,item:rangelist{ifi0{buf.WriteString(, )}buf.WriteString((?, ?, ?, ?))argsappend(args,item.UserId,item.FriendId,item.Status,item.CreateTime)}_,err:conn.ExecCtx(ctx,buf.String(),args...)returnerr}4 ) 事务支持模型接口中增加 Transact 方法// friendmodel.go 接口定义FriendModelinterface{// ... 其他方法Transact(ctx context.Context,fnfunc(ctx context.Context,session sqlx.Session)error)error}// 实现func(m*defaultFriendModel)Transact(ctx context.Context,fnfunc(ctx context.Context,session sqlx.Session)error)error{returnm.conn.Transact(ctx,fn)}其他模型同理。业务逻辑实现好友申请申请流程检查目标用户与自己是否已经是好友。检查是否存在未处理的申请记录。创建新的申请记录。// friendapplylogic.gofunc(l FriendApplyLogic)FriendApply(in social.FriendApplyRequest)(*social.FriendApplyResponse,error){// 1. 检查是否已是好友_,err:l.svcCtx.FriendModel.FindOneByUserIdAndFriendId(l.ctx,in.UserId,in.FriendId)iferrnil{returnnil,errx.NewCodeError(errx.CodeAlreadyFriend,已为好友关系)}if!errors.Is(err,sqlx.ErrNotFound){returnnil,errx.NewCodeErrorf(errx.CodeDBError,查询好友关系失败: %v,err)}// 2. 检查是否有未处理的申请_,errl.svcCtx.FriendReqModel.FindByUserIdAndFriendId(l.ctx,in.UserId,in.FriendId)iferrnil{returnnil,errx.NewCodeError(errx.CodeRequestExists,已存在待处理的申请)}if!errors.Is(err,sqlx.ErrNotFound){returnnil,errx.NewCodeErrorf(errx.CodeDBError,查询申请记录失败: %v,err)}// 3. 新增申请newReq:model.FriendRequest{UserId:in.UserId,FriendId:in.FriendId,Reason:in.Reason,Status:1,// 未处理CreateTime:time.Now(),}_,errl.svcCtx.FriendReqModel.Insert(l.ctx,newReq)iferr!nil{returnnil,errx.NewCodeErrorf(errx.CodeDBError,创建好友申请失败: %v,err)}returnsocial.FriendApplyResponse{},nil}好友申请处理处理流程依赖事务确保状态修改和好友关系建立原子执行。// friendapplyhandlelogic.gofunc(l FriendApplyHandleLogic)FriendApplyHandle(in social.FriendApplyHandleRequest)(*social.FriendApplyHandleResponse,error){// 1. 获取申请记录applyId:in.ApplyId apply,err:l.svcCtx.FriendReqModel.FindOne(l.ctx,applyId)iferr!nil{returnnil,errx.NewCodeError(errx.CodeApplyNotFound,申请记录不存在)}// 2. 检查状态ifapply.Status!1{returnnil,errx.NewCodeError(errx.CodeApplyAlreadyProcessed,申请已处理)}// 3. 事务处理errl.svcCtx.FriendReqModel.Transact(l.ctx,func(ctx context.Context,session sqlx.Session)error{// 更新申请状态apply.Statusin.HandleType err:l.svcCtx.FriendReqModel.UpdateWithSession(ctx,session,apply)iferr!nil{returnerr}// 如果通过建立两条好友关系ifin.HandleType1{friends:[]*model.Friend{{UserId:apply.UserId,FriendId:apply.FriendId,Status:1,CreateTime:time.Now()},{UserId:apply.FriendId,FriendId:apply.UserId,Status:1,CreateTime:time.Now()},}errl.svcCtx.FriendModel.InsertBatch(ctx,session,friends)iferr!nil{returnerr}}returnnil})iferr!nil{returnnil,errx.NewCodeErrorf(errx.CodeDBError,处理好友申请失败: %v,err)}returnsocial.FriendApplyHandleResponse{},nil}注意UpdateWithSession需在模型层提供使用 session 的方法以便在事务内执行。好友列表查询// friendlistlogic.gofunc(l FriendListLogic)FriendList(in social.FriendListRequest)(*social.FriendListResponse,error){friends,err:l.svcCtx.FriendModel.FindByUserId(ctx,in.UserId)iferr!nil{returnnil,errx.NewCodeErrorf(errx.CodeDBError,查询好友列表失败: %v,err)}friendIds:make([]string,0,len(friends))for_,f:rangefriends{friendIdsappend(friendIds,f.FriendId)}returnsocial.FriendListResponse{FriendIds:friendIds},nil}注册拦截器在social.go入口处注册错误拦截器采用与用户服务相同的模式funcmain(){flag.Parse()varc config.Config conf.MustLoad(*configFile,c)s:zrpc.MustNewServer(c.RpcServerConf,func(grpcServer*grpc.Server){social.RegisterSocialServer(grpcServer,server.NewSocialServer(svc.NewServiceContext(c)))},zrpc.WithUnaryServerInterceptor(interceptor.RpcErrInterceptor))defers.Stop()fmt.Printf(Starting social rpc server at %s...\n,c.ListenOn)s.Start()}测试验证使用grpcurl或单元测试验证好友功能申请好友传入双方 ID第一次应成功重复申请应返回“已存在待处理申请”若已为好友则提示“已为好友”。处理申请传入申请 ID 和操作类型成功后好友关系表中出现两条记录。列表查询传入用户 ID返回好友 ID 列表。通过事务保障如果插入好友关系失败申请状态不会改变有效防止数据不一致。总结本文完成了社交服务中好友功能的完整搭建涵盖了冗余存储的好友关系设计决策。利用goctl快速生成 RPC 与模型代码。模型层自定义方法的扩展包括支持事务的批量插入。业务逻辑中对状态机、并发安全的合理处理。统一错误码和拦截器的应用。群组相关的创建、申请、成员管理等业务可参照好友模式实现。下一节我们将整合社交 API 层对外提供 HTTP 接口逐步构建完整的社交服务。

相关新闻

2026年全国可做的真人电销外包公司怎么选?跨区域合规与选型标准解析

2026年全国可做的真人电销外包公司怎么选?跨区域合规与选型标准解析

不少面向全国市场的企业在电销外包时面临以下问题:不同省份的外呼线路合规要求存在差异,本地服务商无法覆盖全国所有区域,多地同步外呼时进度难以统一,不同地区客户沟通习惯差异导致部分坐席难以匹配异地客户需求。 2026年外呼监管…

2026/7/19 22:00:46阅读更多 →
技术实践中的注意事项与方案对比方法论

技术实践中的注意事项与方案对比方法论

1. 为什么需要注意事项与对比详解?在技术实践和项目开发中,我们经常会遇到这样的情况:看似简单的操作,实际执行时却总是踩坑;明明按照文档一步步操作,结果却与预期不符;面对多个相似的技术方案时…

2026/7/19 22:00:46阅读更多 →
SpringCloud微服务架构实战:核心组件与优化指南

SpringCloud微服务架构实战:核心组件与优化指南

1. SpringCloud入门:为什么选择它作为微服务架构的核心框架SpringCloud本质上是一套基于SpringBoot的微服务工具集,它解决了分布式系统中的八大核心痛点。我在2018年第一次接触SpringCloud时,正面临着一个传统单体架构向微服务转型的困境——…

2026/7/19 22:00:46阅读更多 →
【DataX篇】基于DataX-Web可视化管理平台实现各个数据源之间的数据同步

【DataX篇】基于DataX-Web可视化管理平台实现各个数据源之间的数据同步

💫《博主主页》:    🔎 CSDN主页: 奈斯DB    🔎 IF Club社区主页: 奈斯、 🔥《擅长领域》:    🗃️ 数据库:阿里云AnalyticDB(云原生分布式数据仓库…

2026/7/20 11:55:47阅读更多 →
南阳玉石加工五轴机技术解析:从选型逻辑到实战应用

南阳玉石加工五轴机技术解析:从选型逻辑到实战应用

AI辅助创作声明: 本文数据分析框架与行业技术内容基于笔者行业经验整理,文字表述借助AI工具进行结构优化与润色。所有数据、技术观点及行业判断均来源于公开行业报告、南阳市人民政府公开信息、第三方检测机构测试结果及实测数据,不存在虚构内…

2026/7/20 11:55:47阅读更多 →
VC++语法高亮类实现:从词法分析到MFC集成的完整指南

VC++语法高亮类实现:从词法分析到MFC集成的完整指南

1. 项目概述:为什么我们需要一个VC语法高亮类?在Windows桌面应用开发,尤其是使用MFC或Win32 API进行工具、编辑器或IDE插件开发时,我们经常会遇到一个看似基础但实现起来颇为繁琐的需求:为代码编辑控件(如C…

2026/7/20 11:55:47阅读更多 →
【全栈AI工程师能力图谱2024】:覆盖LangChain/RAG/Agent/向量数据库/前端智能组件的12项硬核技能认证路径

【全栈AI工程师能力图谱2024】:覆盖LangChain/RAG/Agent/向量数据库/前端智能组件的12项硬核技能认证路径

更多请点击: https://intelliparadigm.com 第一章:AI编程全栈项目实战导论 在当今快速演进的软件开发生态中,AI已不再仅限于研究实验室或独立模型服务,而是深度融入前端交互、后端推理、数据管道与部署运维的完整技术栈。本章聚焦…

2026/7/20 11:55:47阅读更多 →
C++内存泄漏实战:从原理到排查与根治方案

C++内存泄漏实战:从原理到排查与根治方案

1. 项目概述:从“内存泄漏”到“系统崩溃”的隐秘路径在C的世界里,内存管理是开发者手中的双刃剑。它赋予了我们直接操作内存的极致自由,但也埋下了无数隐患的种子。其中,“内存泄漏”无疑是这颗种子里最顽固、最难以根除的一种。…

2026/7/20 11:55:47阅读更多 →
2026年霞浦海参工厂分类与生产标准百科解读

2026年霞浦海参工厂分类与生产标准百科解读

霞浦,这个坐落在福建东北部的沿海县城,近年来已成为国内海参深加工的重要产业集群地。随着海参消费市场的持续扩容与消费升级,霞浦海参工厂的产业形态、生产标准与品控体系也在快速迭代。2026年,这一行业正走向更专业化、标准化、…

2026/7/20 11:53:47阅读更多 →
Go语言静态资源打包方案对比与实践指南

Go语言静态资源打包方案对比与实践指南

1. 项目背景与核心需求在Go语言开发中,我们经常需要处理静态资源文件的打包问题。无论是Web应用的模板文件、前端资源,还是配置文件、证书等,都需要随程序一起分发。传统做法是将这些文件与编译后的二进制文件放在同一目录下,但这…

2026/7/20 0:50:54阅读更多 →
Go语言实现高性能LDAP认证服务的架构与实践

Go语言实现高性能LDAP认证服务的架构与实践

1. 项目背景与核心价值LDAP(轻量级目录访问协议)作为企业级身份认证的黄金标准,已经服务了超过80%的财富500强公司。我在金融科技领域实施统一认证体系时,发现传统Java方案存在启动慢、内存占用高等痛点。而Go语言凭借其协程并发模…

2026/7/20 0:50:54阅读更多 →
【AI面试官实战指南】:用ChatGPT模拟10类高频技术岗面试,3天提升应答精准度92%

【AI面试官实战指南】:用ChatGPT模拟10类高频技术岗面试,3天提升应答精准度92%

更多请点击: https://intelliparadigm.com 第一章:AI面试官实战指南的核心价值与适用场景 AI面试官并非替代人类HR的“黑箱工具”,而是以可解释、可审计、可迭代的方式,赋能招聘全链路的关键基础设施。其核心价值在于将主观经验沉…

2026/7/20 0:50:54阅读更多 →
2026 WAIC:努比亚二代“豆包手机”NaviX Ultra亮相,智能体验全面升级!

2026 WAIC:努比亚二代“豆包手机”NaviX Ultra亮相,智能体验全面升级!

7月18日智东西消息,在2026 WAIC期间,努比亚联合字节豆包打造的二代“豆包手机”努比亚NaviX Ultra首次亮相,相比一代有诸多升级。智能体手机理念中兴通讯终端事业部总裁、努比亚总裁倪飞表示,智能体手机要从人操作手机变为手机帮人…

2026/7/20 0:01:04阅读更多 →
努比亚NaviX Ultra亮相WAIC,智能体手机能否让用户生活更简单?

努比亚NaviX Ultra亮相WAIC,智能体手机能否让用户生活更简单?

努比亚NaviX Ultra:外观与功能双升级在2026 WAIC期间,首次亮相的努比亚NaviX Ultra吸引了众多目光。它是努比亚联合字节豆包打造的二代“豆包手机”,与一代努比亚M153相比,外观设计变化较大。其机身背部搭载横向排布的大尺寸影像模…

2026/7/20 0:01:04阅读更多 →
C# 将逗号分割的字符串转换为long,并添加到List<long>

C# 将逗号分割的字符串转换为long,并添加到List<long>

目录 方法1:使用Split和Convert.ToInt64 方法2:使用LINQ的Select和ToList 方法3:使用TryParse进行异常安全转换(推荐) 如果您喜欢此文章,请收藏、点赞、评论,谢谢,祝您快乐每一天…

2026/7/20 0:01:04阅读更多 →
YOLOv8推理性能优化:从1.2FPS到35FPS的全链路加速实践

YOLOv8推理性能优化:从1.2FPS到35FPS的全链路加速实践

如果你在部署 YOLOv8 时,发现推理速度只有可怜的 1-2 FPS,而别人的演示视频却能跑到 30 FPS 以上,那么问题很可能不在模型本身,而在于你的整个处理链路。很多开发者拿到一个训练好的 YOLOv8 模型后,会直接使用官方示例…

2026/7/19 22:50:49阅读更多 →
Coze与Dify对比指南:低代码AI应用开发从入门到实战

Coze与Dify对比指南:低代码AI应用开发从入门到实战

1. 从零到一:为什么你需要了解 Coze 和 Dify?如果你对 AI 应用开发感兴趣,但一看到“大模型”、“智能体”、“工作流”这些词就头疼,觉得门槛太高,那这篇文章就是为你准备的。很多开发者,包括我自己&#…

2026/7/19 14:50:26阅读更多 →
AI生图工具怎么选?2026年6月版实测对比

AI生图工具怎么选?2026年6月版实测对比

做自媒体的朋友应该都有体会:配图一直是个让人头疼的问题。2026年,AI生图工具已经非常成熟了,但工具太多反而不知道怎么选。以下是截至2026年6月我对主流AI生图工具的实测对比。Midjourney V8.1:速度之王2026年6月11日&#xff0c…

2026/7/19 18:50:36阅读更多 →