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/10/28 20:36:21 阅读更多 →
技术实践中的注意事项与方案对比方法论

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

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

2026/10/28 12:23:55 阅读更多 →
SpringCloud微服务架构实战:核心组件与优化指南

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

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

2026/10/30 6:58:13 阅读更多 →

最新新闻

Electron桌面应用开发实战:从环境搭建到打包发布全流程

Electron桌面应用开发实战:从环境搭建到打包发布全流程

做前端这些年,我大部分时间都在跟浏览器标签页打交道,直到第一次把桌面应用跑起来,才发现原来自己一直被限制在地址栏和刷新按钮围起来的框框里。Electron这个词,最开始是在某段时间频繁出现在同事的讨论里,一个用HTML…

2026/10/31 0:19:11 阅读更多 →
滑盖智能锁T109深度解析:结构原理、安装调试与故障排查

滑盖智能锁T109深度解析:结构原理、安装调试与故障排查

简介:亚太天能T109滑盖智能锁产品说明书是一份PDF格式的官方技术文档,主要面向智能锁安装人员、终端用户及售后维护者,帮助快速掌握产品安装、配置与常见故障处理。全包仅1个PDF文件,大小4.37MB,内容结构清晰&#xff…

2026/10/31 10:20:19 阅读更多 →
PMSM弱磁控制原理与Simulink建模:高速AGV驱动系统设计指南

PMSM弱磁控制原理与Simulink建模:高速AGV驱动系统设计指南

上个月我在调试一台高速AGV的驱动系统,整车要求空载跑到8m/s,电机转速已经拉到额定点,驱动器的输出电压也顶到了母线电压极限附近。再往上踩速度指令,转速纹丝不动,三相电流反而开始震荡。那会儿我的第一反应就是&…

2026/10/31 10:20:23 阅读更多 →
C++核心知识点:从内存管理到STL的实战指南

C++核心知识点:从内存管理到STL的实战指南

C 这门语言我前前后后折腾了十几年,用它写过嵌入式驱动、做过游戏服务端,也搭过基础组件库。每次看到新人一头扎进"C 核心知识点"这个搜索词,我其实挺感慨的——这门语言的难点从来不是语法多,而是知识点之间的耦合太深…

2026/10/31 0:59:28 阅读更多 →
T109滑盖智能锁怎么装?安装调试与故障排查全攻略

T109滑盖智能锁怎么装?安装调试与故障排查全攻略

简介:亚太天能T109滑盖智能锁产品说明书以PDF格式发布,适合智能锁用户、安装人员与售后维修人员阅读。内容围绕T109系列指纹密码刷卡锁展开,先介绍前面板、后面板、锁体、机械钥匙、电池盒盖等部件名称与功能,再给出完整安装步骤&…

2026/10/31 10:30:19 阅读更多 →
LLM知识降熵:从信息垄断到理解权下放

LLM知识降熵:从信息垄断到理解权下放

你有没有过这种经历:翻开源码注释、论文摘要或者一份专业报告,每个字都认识,连在一起却像天书。想找人聊聊,身边要么没人懂,要么懂的懒得跟你解释。后来我试着把同一段内容丢给一个大语言模型,几秒钟之后&a…

2026/10/31 9:29:43 阅读更多 →

日新闻

周新闻

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/30 17:32:24 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/30 10:28:40 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/31 0:09:01 阅读更多 →