当前位置: 首页 > news >正文

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 检索

本系统采用冗余存储方案,因其查询效率高,且好友关系数据总量相对可控。

表结构示例如下:

CREATETABLE`friend`(`id`bigintNOTNULLAUTO_INCREMENT,`user_id`varchar(64)NOTNULLCOMMENT'用户ID',`friend_id`varchar(64)NOTNULLCOMMENT'好友ID',`status`tinyint(1)NOTNULLDEFAULT'1'COMMENT'1:正常',`create_time`datetimeNOTNULLDEFAULTCURRENT_TIMESTAMP,PRIMARYKEY(`id`),KEY`idx_user_id`(`user_id`),KEY`idx_friend_id`(`friend_id`))ENGINE=InnoDBDEFAULTCHARSET=utf8mb4COMMENT='好友关系表';

friend_request表类似,增加请求状态字段。

项目结构更新

apps/social/rpc下组织社交服务代码:

apps/social/rpc/ ├── internal/ │ ├── config/ │ ├── server/ │ ├── svc/ │ └── logic/ ├── model/ # 生成的模型代码(带前缀) ├── social.proto └── social.go

构建社交 RPC 服务

编写 Proto 文件

在 social.proto 中先定义好友相关 RPC:

syntax = "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--goout=apps/social/rpc/ --go-grpcout=apps/social/rpc/--zrpc_out=apps/social/rpc/

生成后的social.go为服务入口,internal下包含通用组件。

配置与依赖注入

internal/config/config.go

packageconfigimport("github.com/zeromicro/go-zero/core/stores/cache""github.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:123456@tcp(192.168.1.10:3306)/im_db?charset=utf8mb4&parseTime=TrueCacheRedis:-Host:192.168.1.10:6379Pass:"yourpass"

internal/svc/servicecontext.go中注入所有模型:

packagesvcimport("im-system/apps/social/rpc/internal/config""im-system/apps/social/rpc/model""github.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)// 注意:生成模型时指定了前缀,因此创建的模型类型名会有前缀return&ServiceContext{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}return&resp,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}return&resp,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{ifi>0{buf.WriteString(", ")}buf.WriteString("(?, ?, ?, ?)")args=append(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)}

其他模型同理。

业务逻辑实现

好友申请

申请流程:

  1. 检查目标用户与自己是否已经是好友。
  2. 检查是否存在未处理的申请记录。
  3. 创建新的申请记录。
// friendapplylogic.gofunc(l FriendApplyLogic)FriendApply(in social.FriendApplyRequest)(*social.FriendApplyResponse,error){// 1. 检查是否已是好友_,err:=l.svcCtx.FriendModel.FindOneByUserIdAndFriendId(l.ctx,in.UserId,in.FriendId)iferr==nil{returnnil,errx.NewCodeError(errx.CodeAlreadyFriend,"已为好友关系")}if!errors.Is(err,sqlx.ErrNotFound){returnnil,errx.NewCodeErrorf(errx.CodeDBError,"查询好友关系失败: %v",err)}// 2. 检查是否有未处理的申请_,err=l.svcCtx.FriendReqModel.FindByUserIdAndFriendId(l.ctx,in.UserId,in.FriendId)iferr==nil{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(),}_,err=l.svcCtx.FriendReqModel.Insert(l.ctx,newReq)iferr!=nil{returnnil,errx.NewCodeErrorf(errx.CodeDBError,"创建好友申请失败: %v",err)}return&social.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. 事务处理err=l.svcCtx.FriendReqModel.Transact(l.ctx,func(ctx context.Context,session sqlx.Session)error{// 更新申请状态apply.Status=in.HandleType err:=l.svcCtx.FriendReqModel.UpdateWithSession(ctx,session,apply)iferr!=nil{returnerr}// 如果通过,建立两条好友关系ifin.HandleType==1{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()},}err=l.svcCtx.FriendModel.InsertBatch(ctx,session,friends)iferr!=nil{returnerr}}returnnil})iferr!=nil{returnnil,errx.NewCodeErrorf(errx.CodeDBError,"处理好友申请失败: %v",err)}return&social.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{friendIds=append(friendIds,f.FriendId)}return&social.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 接口,逐步构建完整的社交服务。

http://www.jsqmd.com/news/1222287/

相关文章:

  • 别再用ChatGPT随便写稿了:AI写作SOP设计的5个致命盲区(附ISO/GB级合规检查清单)
  • Spring框架中ResponseEntity的全面解析与应用实践
  • Android RecyclerView核心原理与优化实践
  • UE5编辑器效率翻倍:10个必备快捷键与场景切换技巧详解
  • Haberman数据集实战EDA:临床驱动的探索性数据分析路径
  • 香港劳力士官方售后通知|2026年7月网点地址及官方服务热线核验 - 劳力士服务中心
  • 品质升级:2026海尔开启24小时售后服务人工电话400号码全天在线 - 热点速览
  • 昆明亨得利售后客服热线电话查询维修保养服务权威公示(2026年7月最新) - 亨得利官方
  • 南昌市西湖区亨得利官方名表服务中心电话公示(2026年7月最新) - 亨得利官方
  • 2026年7月最新爱彼哈尔滨哈西万达广场维修保养服务电话 - 爱彼中国官方服务中心
  • Unity集成AI造物:Z-Turbo方案实现游戏素材自动化生成
  • Linux 命令行入门学习资料 day_6
  • 统计画像中的数据操作:业务语义驱动的数据变形方法论
  • 从零搭建AI增强型TDD工作流:Pytest + LLM Test Generator + Coverage-Guided Fuzzing(含完整CI/CD配置)
  • vue.js 添加 fastclick的支持
  • 2026甄选:重庆婚姻家事领域专业律师李西昀——专注离婚财产分割、子女抚养权与追回小三赠与款项 - 甄选服务推荐
  • 为什么92%的AI Twitter账号3个月内停更?——企业级AI运营失败案例深度复盘(含3份审计清单)
  • 2026年上海及昆山地区叉车锂电池回收行情深度解析:价格区间与鉴别指南 - 生态测评师
  • 2026年泉州民宿口碑榜:这5家山居度假首选,藏着最闽南的清凉夏天 - 热点速览
  • 数据科学家的三种工作模式:分析、工程与业务驱动型能力地图
  • 新疆长绒棉哪家好 西域国绒品质之选 - 互联网科技品牌测评
  • 2026年7月最新欧米茄长春红旗街万达广场维修保养服务电话 - 欧米茄官方服务中心
  • Kimi K2 API v2.3接口变更预警:9月30日前未升级将触发兼容性降级,附一键迁移脚本
  • SpringCloud微服务架构实战:核心组件与优化指南
  • 2026年7月最新天梭嘉兴平湖吾悦广场维修保养服务电话 - 天梭服务中心
  • 深入解析AM62L DEBUGSS_WRAP寄存器与ROM表调试机制
  • Qt框架开发实战:从核心原理到跨平台应用
  • 格拉苏蒂中国官方售后服务中心|详细地址与24小时客服热线权威信息通知(2026年7月更新) - 亨得利官方服务中心
  • 积家中国官方售后服务中心|最新地址与24小时售后热线权威信息声明(2026年7月最新) - 积家官方售后服务中心
  • Java邮件发送实战:从SMTP协议到生产环境优化