package googleanalytics import ( "context" "os" "strconv" "time" "videoweb/pub/common/httputil" "videoweb/pub/common/log" "videoweb/pub/common/srvdef" "videoweb/pub/common/utils" "videoweb/pub/models/analyticsdailystat" "videoweb/pub/models/analyticsstat" "videoweb/web/webg" "go.mongodb.org/mongo-driver/bson" analyticsdata "google.golang.org/api/analyticsdata/v1beta" "google.golang.org/api/option" ) // HourlyData 用于存储和排序每小时的数据 type HourlyData struct { Hour int Users int64 } type ChannelAnalyzeReq struct { Channel string `from:"channel" json:"channel" binding:"required"` // 渠道 Begin time.Time `json:"begin" form:"begin"` // 开始时间 End time.Time `json:"end" form:"end"` // 结束时间 AppId *int32 `from:"appId" json:"appId" ` // 应用id } type AnalyzeItem struct { Channel string `json:"channel" form:"channel"` // 渠道 AppId int32 `json:"appId" form:"appId"` // 应用ID AppName string `json:"appName" form:"appName"` // 应用名称 Begin time.Time `json:"begin" form:"begin"` // 开始时间 End time.Time `json:"end" form:"end"` // 结束时间 RegisterCount int64 `json:"registerCount" form:"registerCount"` // 总注册IP数 RegisterIpCount int64 `json:"registerIpCount" form:"registerIpCount"` // 总注册IP数 VisitCount int64 `json:"visitCount" form:"visitCount"` // 总访问数 AdvertiseClick int64 `json:"advertiseClick" form:"advertiseClick"` // 广告点击数 RechargeCount int64 `json:"rechargeCount" form:"rechargeCount"` // 充值次数 RechargeAmount int64 `json:"rechargeAmount" form:"rechargeAmount"` // 充值金额 RechargeUsers int64 `json:"rechargeUsers" form:"rechargeUsers"` // 充值用户数 YesTodayRegisterCount int64 `json:"yesTodayRegisterCount" form:"yesTodayRegisterCount"` // 昨日注册IP数 YesTodayVisitCount int64 `json:"yesTodayVisitCount" form:"yesTodayVisitCount"` // 昨日访问数 YesTodayRechargeAmount int64 `json:"yesTodayRechargeAmount" form:"yesTodayRechargeAmount"` // 昨日充值 YesTodayRetainRate int64 `json:"yesTodayRetainRate" form:"yesTodayRetainRate"` // 昨日次流 } type ChannelAnalyzeResp struct { List []*AnalyzeItem `json:"list" form:"list"` } type AnalyzeResp struct { Code int `json:"code"` Msg string `json:"msg"` Data ChannelAnalyzeResp `json:"data"` } func AnalyticsData(appId int64, appName string, propertyId string) { ctx := context.Background() now := time.Now() // 1. 认证授权 - 使用服务账号的 JSON 密钥文件 credentialsFile := "composed-anvil-470512-r8-fd674ac0d7dc.json" b, err := os.ReadFile(credentialsFile) if err != nil { log.Error("AnalyticsData 读取凭证文件失败", log.E(err)) return } curStatTime := utils.RecentMinute(now.Add(time.Minute), 30) // 按20分钟对齐 data := analyticsstat.AnalyticsInfo{ AppId: appId, SumDate: curStatTime, AppName: appName, DaySumDate: utils.BeginOfTime(curStatTime), CreatedAt: now, UpdatedAt: now, } // 2. 创建 API 客户端 client, err := analyticsdata.NewService(ctx, option.WithCredentialsJSON(b)) if err != nil { log.Error("AnalyticsData 无法创建 Analytics Data 客户端", log.E(err)) return } if client == nil { log.Error("AnalyticsData client 为空") return } req := &analyticsdata.RunRealtimeReportRequest{ // 虽然实时报告默认就是最近30分钟,但你可以通过 dateRanges 进行限制(如果需要的话) // DateRanges: []*analyticsdata.DateRange{ // {StartDate: "30 minutes ago", EndDate: "now"}, // 某些客户端库可能支持相对时间 // }, Dimensions: []*analyticsdata.Dimension{ // 添加你关心的维度,例如国家或地区 {Name: "country"}, // 实时报告支持的维度有限,请参考官方文档: // https://developers.google.com/analytics/devguides/reporting/data/v1/realtime-basics?hl=zh-cn }, Metrics: []*analyticsdata.Metric{ {Name: "activeUsers"}, // 获取活跃用户数 }, } // 4. 调用 RealTime API resp, err := client.Properties.RunRealtimeReport(propertyId, req).Do() if err != nil { log.Error("AnalyticsData RunRealtimeReport 请求报告出错", log.E(err)) return } if resp == nil { log.Error("AnalyticsData RunRealtimeReport resp 为空") return } for _, row := range resp.Rows { if row == nil || len(row.MetricValues) == 0 || row.MetricValues[0] == nil { continue } activeUsers, _ := strconv.ParseInt(row.MetricValues[0].Value, 10, 64) data.ActiveUsers += activeUsers } yestodayReq := &analyticsdata.RunReportRequest{ DateRanges: []*analyticsdata.DateRange{ {StartDate: "yesterday", EndDate: "yesterday"}, // 查询昨天 }, Dimensions: []*analyticsdata.Dimension{ {Name: "date"}, // 核心:添加日期维度,按天分组 }, Metrics: []*analyticsdata.Metric{ {Name: "screenPageViews"}, {Name: "totalUsers"}, {Name: "activeUsers"}, // 获取活跃用户数 {Name: "sessions"}, // 会话数 {Name: "newUsers"}, // 新增数量 }, } yestodayResp, err := client.Properties.RunReport(propertyId, yestodayReq).Do() if err != nil { log.Error("AnalyticsData RunReport 请求报告出错", log.E(err)) return } if yestodayResp == nil { log.Error("AnalyticsData RunReport yestodayResp 为空") return } for _, row := range yestodayResp.Rows { if row == nil || len(row.MetricValues) < 5 { continue } data.YestodayPV, _ = strconv.ParseInt(row.MetricValues[0].Value, 10, 64) data.YestodayUV, _ = strconv.ParseInt(row.MetricValues[1].Value, 10, 64) data.YestodayActiveUsers, _ = strconv.ParseInt(row.MetricValues[2].Value, 10, 64) data.YestodaySessions, _ = strconv.ParseInt(row.MetricValues[3].Value, 10, 64) data.YestodayNewUsers, _ = strconv.ParseInt(row.MetricValues[4].Value, 10, 64) } analyticsstat.InsertOneAnalyticsInfo(ctx, &data) } func AnalyticsDailyData(appId int64, appName string, channel string, propertyId string) { ctx := context.Background() now := time.Now() // 1. 认证授权 - 使用服务账号的 JSON 密钥文件 credentialsFile := "composed-anvil-470512-r8-fd674ac0d7dc.json" b, err := os.ReadFile(credentialsFile) if err != nil { log.Error("AnalyticsDailyData 读取凭证文件失败", log.E(err)) return } // 2. 创建 API 客户端 client, err := analyticsdata.NewService(ctx, option.WithCredentialsJSON(b)) if err != nil { log.Error("AnalyticsDailyData 无法创建 Analytics Data 客户端", log.E(err)) return } if client == nil { log.Error("AnalyticsDailyData client 为空") return } data := analyticsdailystat.AnalyticsDailyInfo{ AppId: appId, SumDate: utils.BeginOfTime(now), AppName: appName, CreatedAt: now, UpdatedAt: now, } yestodayReq := &analyticsdata.RunReportRequest{ DateRanges: []*analyticsdata.DateRange{ {StartDate: "7daysAgo", EndDate: "today"}, // 查询昨天 }, Dimensions: []*analyticsdata.Dimension{ {Name: "date"}, // 核心:添加日期维度,按天分组 }, Metrics: []*analyticsdata.Metric{ {Name: "screenPageViews"}, {Name: "totalUsers"}, {Name: "activeUsers"}, // 获取活跃用户数 {Name: "sessions"}, // 会话数 {Name: "newUsers"}, // 新增数量 }, } yestodayResp, err := client.Properties.RunReport(propertyId, yestodayReq).Do() if err != nil { log.Error("AnalyticsDailyData RunReport 请求报告出错", log.E(err)) return } if yestodayResp == nil { log.Error("AnalyticsDailyData RunReport yestodayResp 为空") return } for _, row := range yestodayResp.Rows { if row == nil || len(row.DimensionValues) == 0 || row.DimensionValues[0] == nil || len(row.MetricValues) < 5 { continue } // data.SumDate layout := "20060102" // Go 的特定格式:2006=年,01=月,02=日 // 解析字符串为时间对象 dayTime, err := time.Parse(layout, row.DimensionValues[0].Value) if err != nil { log.Error("AnalyticsDailyData time.Parse 失败", log.E(err)) continue } data.SumDate = dayTime.Add(-8 * time.Hour) data.YestodayPV, _ = strconv.ParseInt(row.MetricValues[0].Value, 10, 64) data.YestodayUV, _ = strconv.ParseInt(row.MetricValues[1].Value, 10, 64) data.YestodayActiveUsers, _ = strconv.ParseInt(row.MetricValues[2].Value, 10, 64) data.YestodaySessions, _ = strconv.ParseInt(row.MetricValues[3].Value, 10, 64) data.YestodayNewUsers, _ = strconv.ParseInt(row.MetricValues[4].Value, 10, 64) LastFilter := bson.M{ analyticsdailystat.FN.AnalyticsDailyInfo.SumDate: data.SumDate, analyticsdailystat.FN.AnalyticsDailyInfo.AppId: appId, } _, serr := analyticsdailystat.FindOneAnalyticsDailyInfo(ctx, LastFilter) if serr != nil && serr.Code == srvdef.ErrNoData { analyticsdailystat.InsertOneAnalyticsDailyInfo(ctx, &data) } else { LastUpdate := bson.M{ "$set": bson.M{ analyticsdailystat.FN.AnalyticsDailyInfo.YestodayPV: data.YestodayPV, analyticsdailystat.FN.AnalyticsDailyInfo.YestodayUV: data.YestodayUV, analyticsdailystat.FN.AnalyticsDailyInfo.YestodayActiveUsers: data.YestodayActiveUsers, analyticsdailystat.FN.AnalyticsDailyInfo.YestodaySessions: data.YestodaySessions, analyticsdailystat.FN.AnalyticsDailyInfo.YestodayNewUsers: data.YestodayNewUsers, analyticsdailystat.FN.AnalyticsDailyInfo.UpdatedAt: now, }, } analyticsdailystat.UpdateOneAnalyticsDailyInfo(ctx, LastFilter, LastUpdate) } } analyzeList := ChannelAnalyzeResp{} req := ChannelAnalyzeReq{ Channel: channel, Begin: utils.BeginOfTime(now.AddDate(0, 0, -1)), End: utils.EndOfTime(now.AddDate(0, 0, -1)), } param, _ := utils.ToJsonM(req) innerStatUrl := webg.Conf.NewInnerStatUrl + "/api/stat/dailyStat/channel/analyze" resp := AnalyzeResp{} code, err := httputil.Client().POSTJsonWithJResp(&resp, innerStatUrl, nil, param) if err != nil { log.Error("ChannelAnalyze httputil innerStatUrl failed!", log.E(err)) } if code != 200 { log.Error("ChannelAnalyze httputil innerStatUrl code1!=200", log.Any("code", code)) } log.Info("ChannelAnalyze", log.Any("req", req), log.Any("innerStatUrl", innerStatUrl), log.Any("analyzeList", resp.Data)) analyzeList = resp.Data for _, channelAnalyze := range analyzeList.List { data.LeadNewCount += float64(channelAnalyze.RegisterCount) data.LeadAmount += float64(channelAnalyze.RechargeAmount) } LastFilter := bson.M{ analyticsdailystat.FN.AnalyticsDailyInfo.SumDate: utils.BeginOfTime(now), analyticsdailystat.FN.AnalyticsDailyInfo.AppId: appId, } _, serr := analyticsdailystat.FindOneAnalyticsDailyInfo(ctx, LastFilter) if serr != nil && serr.Code == srvdef.ErrNoData { analyticsdailystat.InsertOneAnalyticsDailyInfo(ctx, &data) } else { LastUpdate := bson.M{ "$set": bson.M{ analyticsdailystat.FN.AnalyticsDailyInfo.LeadNewCount: data.LeadNewCount, analyticsdailystat.FN.AnalyticsDailyInfo.LeadAmount: data.LeadAmount, }, } analyticsdailystat.UpdateOneAnalyticsDailyInfo(ctx, LastFilter, LastUpdate) } }