纲要社交服务业务需求好友关系管理申请、处理、好友列表群组管理创建、申请、加入、退群等数据表设计好友关系表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 接口逐步构建完整的社交服务。