资源功能新增拍照功能实现 [图片资源上云] 一、资源功能新增1、新增需要的资源功能文件实现里面的基础方法2、继承上层manager提供的接口实现初始化加载、内存数据落地定时数据刷新等一致接口方法type IPlayerMgr interface { InitPlayer(ctx *GameContext) error // 数据初始化 存档为空时执行 LoadRecord(ctx *GameContext, record *pb.PlayerRecord) error // 加载存档 有存档时执行 AfterLoadRecord(ctx *GameContext) ToRecord(record *pb.PlayerRecord) PostProcess(ctx *GameContext) Tick(ctx *GameContext, playerNowUnixMilli int64) bool // 返回是否有逻辑执行 SlowTick(ctx *GameContext, playerNowUnixMilli int64) bool // 返回是否有逻辑执行 BeforeOffline(ctx *GameContext) GameDailyRefresh(ctx *GameContext, nowUnix int64, doNotice bool) GameWeeklyRefresh(ctx *GameContext, nowUnix int64) GameMonthlyRefresh(ctx *GameContext, nowUnix int64) }3、添加业务需求的方法并将新的资源类型注册到统一的reward_mgr里func (mgr *RewardMgr) checkNew(ctx *GameContext, rewardData *pb.RewardData) { if rewardData nil { ctx.Warn(check new fail, rewardData is nil) return } reward : rewardData.Reward switch reward.Type { case pb.ThingType_ThingType_COIN: if mgr.player.CoinMgr().GetCoinNum(pb.CoinType(reward.Id)) 0 { return } case pb.ThingType_ThingType_RESOURCE: if mgr.player.ResourceMgr().GetResourceNum(pb.ResourceType(reward.Id)) 0 { return } case pb.ThingType_ThingType_FASHION: if mgr.player.FashionMgr().getFashionNum(ctx, reward.Id) 0 { return } case pb.ThingType_ThingType_ITEM: if mgr.player.PackMgr().GetItemNum(uint32(reward.Id)) 0 { return } case pb.ThingType_ThingType_SCENE: if mgr.player.SceneMgr().hasSceneInBag(reward.Id) { return } case pb.ThingType_ThingType_ACTION: if mgr.player.ActionMgr().hasActionInBag(reward.Id) { return } case pb.ThingType_ThingType_PHOTO_ACTION: if mgr.player.PhotoActionMgr().HasPhotoActionInBag(reward.Id) { return } case pb.ThingType_ThingType_PHOTO_EFFECTS: if mgr.player.PhotoEffectsMgr().HasPhotoEffectInBag(reward.Id) { return } case pb.ThingType_ThingType_EXP: if mgr.player.LevelMgr().exp 0 { return } case pb.ThingType_ThingType_LIVING_ROOM_PROP: if mgr.player.LivingRoomMgr().getLivingRoomPropNum(ctx, reward.Id) 0 { return } default: rewardData.IsNew true return } rewardData.IsNew true }调用func (mgr *RewardMgr) GrantRewardGroup(ctx *GameContext, sourceInfo *pb.SourceInfo, rewardGroup int32, isSendToBag bool) ([]*pb.RewardData, pb.StatusCode) {方法进行统一发奖二、拍照功能实现 [图片资源上云]1、设计传输数据结构为拍照提供新的服务端接口并实现业务逻辑// 拍照发朋友圈请求 message PublishPhotoMomentReq { option (msg_id) 2241; string photo_uid 1; // 照片唯一标识客户端自用 bytes image 2; // 照片二进制数据webp string moment_text 3; // 文案客户端查 Chat 表并本地化后传入 } // 拍照发朋友圈响应 message PublishPhotoMomentRsp { option (msg_id) 2242; StatusCode status 1; int64 moment_id 2; // 发布成功后的动态 ID repeated RewardData rewards 3; // 每日首次拍照发帖奖励GlobalConfig.MomentFirstReward }添加路由case protocol.PublishPhotoMomentReq: player.MomentMgr().onPublishPhotoMomentReq(ctx)2、获取客户端传过来的二进制图片数据并完成图片上云获取返回的图片imageUrl地址存入数据库进行后续的数据返回// onPublishPhotoMomentReq 处理玩家拍照发朋友圈的业务逻辑 func (mgr *MomentMgr) onPublishPhotoMomentReq(ctx *GameContext) { req : network.GetProtoFromPacket[*pb.PublishPhotoMomentReq](ctx.packet) if req nil { ctx.Warn(req nil) return } player : mgr.player rsp : pb.PublishPhotoMomentRsp{ Status: pb.StatusCode_StatusCode_OK, } momentText : strings.TrimSpace(req.MomentText) if len(req.Image) 0 || momentText { ctx.Warn(invalid publish photo moment params, log.String(photoUid, req.PhotoUid), log.Int(imageSize, len(req.Image)), log.String(momentText, req.MomentText)) rsp.Status pb.StatusCode_StatusCode_BAD_REQ player.Response(ctx.packet, rsp) return } commentMaxCharLimit : ctx.config.TbGlobalConfig.Get().CommentMaxCharLimit if len([]rune(momentText)) int(commentMaxCharLimit) { ctx.Warn(photo moment text too long, log.Int(len, len([]rune(momentText))), log.Int32(limit, commentMaxCharLimit)) rsp.Status pb.StatusCode_StatusCode_BAD_REQ player.Response(ctx.packet, rsp) return } if player.RoleMgr().GetPawnData().Status ! pb.PAWN_STATUS_COMPLETED { ctx.Warn(pawn status is not complete, log.Any(pawnStatus, player.RoleMgr().GetPawnData().Status)) rsp.Status pb.StatusCode_StatusCode_BAD_REQ player.Response(ctx.packet, rsp) return } pawnRoleID : player.RoleMgr().GetPawnRoleID() if pawnRoleID 0 { ctx.Warn(pawn role id not ready) rsp.Status pb.StatusCode_StatusCode_BAD_REQ player.Response(ctx.packet, rsp) return } now : player.NowUnix() server : player.server serverCfg : server.serverConfig fileItem : ca.FileItem{ Filename: fmt.Sprintf(photo_moment_%d_%d_%d.webp, player.playerId, pawnRoleID, now), FileData: req.Image, } imageUrls, err : server.cloudAgent.BatchUploadImage( fmt.Sprintf(server/moment_image/%s, serverCfg.RegionName), []*ca.FileItem{fileItem}, ) if err ! nil || len(imageUrls) 0 { ctx.Warn(upload photo moment image fail, log.Uint32(playerId, player.playerId), log.Int(imageSize, len(req.Image)), log.Err(err), log.Any(imageUrls, imageUrls)) rsp.Status pb.StatusCode_StatusCode_SYS_ERR player.Response(ctx.packet, rsp) return } imageUrl : imageUrls[0] redisClient : player.server.redisClient if redisClient nil { ctx.Warn(redis client is nil) rsp.Status pb.StatusCode_StatusCode_SYS_ERR player.Response(ctx.packet, rsp) return } momentId, err : redisClient.NextMomentID() if err ! nil { ctx.Warn(generate moment id fail, log.Err(err)) rsp.Status pb.StatusCode_StatusCode_SYS_ERR player.Response(ctx.packet, rsp) return } moment : pb.Moment{ MomentId: momentId, MomentType: pb.MomentType_MomentType_TEXT, MomentCfgId: 0, RoleId: pawnRoleID, MomentText: momentText, MomentImage: imageUrl, Timestamp: now, } rpcReq : pb.RpcCreateMomentReq{ Moment: moment, SquareAuthorized: mgr.IsSquareAuthorized(), } rpcResult : player.server.callData(rpc.RpcCreateMomentReq, rpcReq) if rpcResult nil { ctx.Warn(rpcResult nil, log.Int64(momentId, momentId)) rsp.Status pb.StatusCode_StatusCode_SYS_ERR player.Response(ctx.packet, rsp) return } result : -rpcResult if err : result.Err(); err ! nil { ctx.Warn(call data fail, log.Err(err), log.Int64(momentId, momentId)) rsp.Status pb.StatusCode_StatusCode_SYS_ERR player.Response(ctx.packet, rsp) return } rpcRsp : result.Msg().(*pb.RpcCreateMomentRsp) if rpcRsp nil || rpcRsp.Status ! pb.StatusCode_StatusCode_OK { ctx.Warn(create moment fail, log.Int64(momentId, momentId), log.Any(status, rpcRsp.GetStatus())) rsp.Status pb.StatusCode_StatusCode_SYS_ERR player.Response(ctx.packet, rsp) return } rsp.MomentId momentId if mgr.isFirstPhotoMomentPublishOfDay(now) { rsp.Rewards mgr.grantPhotoMomentFirstRewardOfDay(ctx, now) } player.Response(ctx.packet, rsp) mgr.sendNewMomentNotice(ctx, moment) log.Info(publish photo moment succ, log.Uint32(playerId, player.playerId), log.String(photoUid, req.PhotoUid), log.Int64(momentId, momentId)) }