Files
2026-10-26 16:28:14 +08:00

306 lines
11 KiB
Go

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)
}
}