407 lines
12 KiB
Go
407 lines
12 KiB
Go
package service
|
|
|
|
import (
|
|
"fmt"
|
|
"time"
|
|
|
|
"github.com/flipped-aurora/gin-vue-admin/server/global"
|
|
"github.com/flipped-aurora/gin-vue-admin/server/plugin/wechat-integration/model/response"
|
|
"github.com/silenceper/wechat/v2/miniprogram"
|
|
)
|
|
|
|
type MiniStatisticsService struct{}
|
|
|
|
// getMiniProgram 获取微信小程序实例
|
|
func (m *MiniStatisticsService) getMiniProgram() (*miniprogram.MiniProgram, error) {
|
|
// 使用微信小程序服务获取实例
|
|
return ServiceGroupApp.WechatMiniService.GetWechatMiniProgram()
|
|
}
|
|
|
|
// GetMiniStatistics 获取小程序基础统计数据
|
|
func (m *MiniStatisticsService) GetMiniStatistics() (*response.MiniStatisticsResponse, error) {
|
|
// 获取微信小程序实例
|
|
mini, err := m.getMiniProgram()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// 获取最近7天的数据概况
|
|
endDate := time.Now().AddDate(0, 0, -1).Format("20060102")
|
|
beginDate := time.Now().AddDate(0, 0, -2).Format("20060102")
|
|
|
|
// 调用微信API获取数据概况
|
|
analysisService := mini.GetAnalysis()
|
|
summary, err := analysisService.GetAnalysisDailySummary(beginDate, endDate)
|
|
if err != nil {
|
|
global.GVA_LOG.Error("获取小程序数据概况失败: " + err.Error())
|
|
return nil, fmt.Errorf("获取小程序统计数据失败: %v", err)
|
|
}
|
|
|
|
// 计算统计数据
|
|
var totalUsers, totalSessions, totalPV, totalUV int64
|
|
var todaySessions int64
|
|
|
|
for _, item := range summary.List {
|
|
totalSessions += int64(item.VisitTotal)
|
|
if item.RefDate == endDate {
|
|
todaySessions = int64(item.VisitTotal)
|
|
}
|
|
}
|
|
|
|
// 获取访问趋势数据来补充统计
|
|
visitTrend, err := analysisService.GetAnalysisDailyVisitTrend(beginDate, endDate)
|
|
if err == nil {
|
|
for _, item := range visitTrend.List {
|
|
totalPV += int64(item.VisitPV)
|
|
totalUV += int64(item.VisitUV)
|
|
}
|
|
}
|
|
|
|
// 估算总用户数(基于最近的活跃用户数)
|
|
if len(visitTrend.List) > 0 {
|
|
totalUsers = int64(visitTrend.List[len(visitTrend.List)-1].VisitUV) * 10 // 估算倍数
|
|
}
|
|
|
|
// 计算平均停留时间和跳出率
|
|
var avgStayTime float64 = 180 // 默认3分钟
|
|
var bounceRate float64 = 0.35 // 默认35%
|
|
|
|
if len(visitTrend.List) > 0 {
|
|
var totalStayTime float64
|
|
for _, item := range visitTrend.List {
|
|
totalStayTime += item.StayTimeUV
|
|
}
|
|
avgStayTime = totalStayTime / float64(len(visitTrend.List))
|
|
|
|
// 简单的跳出率计算(访问深度小于2的比例)
|
|
var totalDepth float64
|
|
for _, item := range visitTrend.List {
|
|
totalDepth += item.VisitDepth
|
|
}
|
|
avgDepth := totalDepth / float64(len(visitTrend.List))
|
|
if avgDepth > 0 {
|
|
bounceRate = 1.0 / avgDepth
|
|
if bounceRate > 1.0 {
|
|
bounceRate = 0.8 // 最大80%
|
|
}
|
|
}
|
|
}
|
|
|
|
statistics := &response.MiniStatisticsResponse{
|
|
TotalUsers: int(totalUsers),
|
|
ActiveUsers: int(totalUV),
|
|
TotalSessions: int(totalSessions),
|
|
TodaySessions: int(todaySessions),
|
|
TotalPV: int(totalPV),
|
|
TotalUV: int(totalUV),
|
|
AvgStayTime: int(avgStayTime),
|
|
BounceRate: bounceRate,
|
|
}
|
|
|
|
global.GVA_LOG.Info("成功获取小程序基础统计数据")
|
|
return statistics, nil
|
|
}
|
|
|
|
// GetVisitTrendData 获取访问趋势数据
|
|
func (m *MiniStatisticsService) GetVisitTrendData(startDate, endDate string) ([]response.VisitTrendData, error) {
|
|
// 获取微信小程序实例
|
|
mini, err := m.getMiniProgram()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// 格式化日期参数
|
|
if startDate == "" || endDate == "" {
|
|
endDate = time.Now().Format("20060102")
|
|
startDate = time.Now().AddDate(0, 0, -6).Format("20060102")
|
|
} else {
|
|
// 转换日期格式 YYYY-MM-DD -> YYYYMMDD
|
|
if len(startDate) == 10 {
|
|
startDate = startDate[:4] + startDate[5:7] + startDate[8:10]
|
|
}
|
|
if len(endDate) == 10 {
|
|
endDate = endDate[:4] + endDate[5:7] + endDate[8:10]
|
|
}
|
|
}
|
|
|
|
// 调用微信API获取访问趋势数据
|
|
analysisService := mini.GetAnalysis()
|
|
visitTrend, err := analysisService.GetAnalysisDailyVisitTrend(startDate, endDate)
|
|
if err != nil {
|
|
global.GVA_LOG.Error("获取小程序访问趋势数据失败: " + err.Error())
|
|
return nil, fmt.Errorf("获取访问趋势数据失败: %v", err)
|
|
}
|
|
|
|
// 转换数据格式
|
|
var trendData []response.VisitTrendData
|
|
for _, item := range visitTrend.List {
|
|
data := response.VisitTrendData{
|
|
Date: item.RefDate[:4] + "-" + item.RefDate[4:6] + "-" + item.RefDate[6:8], // YYYYMMDD -> YYYY-MM-DD
|
|
SessionCnt: item.SessionCnt,
|
|
VisitPV: item.VisitPV,
|
|
VisitUV: item.VisitUV,
|
|
VisitUVNew: item.VisitUVNew,
|
|
StayTimeUV: int(item.StayTimeUV),
|
|
StayTimeSession: int(item.StayTimeSession),
|
|
}
|
|
trendData = append(trendData, data)
|
|
}
|
|
|
|
global.GVA_LOG.Info(fmt.Sprintf("成功获取访问趋势数据,共 %d 条记录", len(trendData)))
|
|
return trendData, nil
|
|
}
|
|
|
|
// GetSourceData 获取用户来源分布数据
|
|
func (m *MiniStatisticsService) GetSourceData() ([]response.SourceData, error) {
|
|
// 获取微信小程序实例
|
|
mini, err := m.getMiniProgram()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// 获取最近7天的访问分布数据
|
|
endDate := time.Now().AddDate(0, 0, -1).Format("20060102")
|
|
beginDate := time.Now().AddDate(0, 0, -7).Format("20060102")
|
|
|
|
// 调用微信API获取访问分布数据
|
|
analysisService := mini.GetAnalysis()
|
|
visitDistribution, err := analysisService.GetAnalysisVisitDistribution(beginDate, endDate)
|
|
if err != nil {
|
|
global.GVA_LOG.Error("获取小程序访问分布数据失败: " + err.Error())
|
|
return nil, fmt.Errorf("获取用户来源分布数据失败: %v", err)
|
|
}
|
|
|
|
// 转换数据格式
|
|
var sourceData []response.SourceData
|
|
var totalVisitUV int
|
|
|
|
// 先计算总访问量用于计算百分比
|
|
for _, index := range visitDistribution.List {
|
|
if index.Index == "access_source_session_cnt" { // 访问来源会话数
|
|
for _, item := range index.ItemList {
|
|
totalVisitUV += item.AccessSourceVisitUV
|
|
}
|
|
}
|
|
}
|
|
|
|
// 访问来源名称映射
|
|
sourceNames := map[int]string{
|
|
1: "小程序历史列表",
|
|
2: "搜索",
|
|
3: "会话分享",
|
|
4: "二维码",
|
|
5: "公众号主页或名称",
|
|
6: "聊天主界面下拉",
|
|
7: "系统桌面",
|
|
8: "小程序主页",
|
|
9: "附近的小程序",
|
|
10: "其他",
|
|
}
|
|
|
|
// 处理访问来源数据
|
|
for _, index := range visitDistribution.List {
|
|
if index.Index == "access_source_session_cnt" { // 访问来源会话数
|
|
for _, item := range index.ItemList {
|
|
name, exists := sourceNames[item.Key]
|
|
if !exists {
|
|
name = fmt.Sprintf("来源%d", item.Key)
|
|
}
|
|
|
|
percentage := 0.0
|
|
if totalVisitUV > 0 {
|
|
percentage = float64(item.AccessSourceVisitUV) / float64(totalVisitUV) * 100
|
|
}
|
|
|
|
data := response.SourceData{
|
|
RefType: fmt.Sprintf("%d", item.Key),
|
|
Name: name,
|
|
SessionCnt: item.Value,
|
|
VisitPV: item.Value, // 使用会话数作为PV
|
|
VisitUV: item.AccessSourceVisitUV,
|
|
Percentage: percentage,
|
|
}
|
|
sourceData = append(sourceData, data)
|
|
}
|
|
break
|
|
}
|
|
}
|
|
|
|
global.GVA_LOG.Info(fmt.Sprintf("成功获取用户来源分布数据,共 %d 条记录", len(sourceData)))
|
|
return sourceData, nil
|
|
}
|
|
|
|
// GetPageData 获取页面访问数据
|
|
func (m *MiniStatisticsService) GetPageData() ([]response.PageData, error) {
|
|
// 获取微信小程序实例
|
|
mini, err := m.getMiniProgram()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// 获取最近7天的页面访问数据
|
|
endDate := time.Now().AddDate(0, 0, -1).Format("20060102")
|
|
beginDate := time.Now().AddDate(0, 0, -7).Format("20060102")
|
|
|
|
// 调用微信API获取页面访问数据
|
|
analysisService := mini.GetAnalysis()
|
|
visitPage, err := analysisService.GetAnalysisVisitPage(beginDate, endDate)
|
|
if err != nil {
|
|
global.GVA_LOG.Error("获取小程序页面访问数据失败: " + err.Error())
|
|
return nil, fmt.Errorf("获取页面访问数据失败: %v", err)
|
|
}
|
|
|
|
// 转换数据格式
|
|
var pageData []response.PageData
|
|
for _, item := range visitPage.List {
|
|
// 从页面路径提取页面名称
|
|
pageName := item.PagePath
|
|
if pageName == "" {
|
|
pageName = "未知页面"
|
|
}
|
|
|
|
data := response.PageData{
|
|
PagePath: item.PagePath,
|
|
PageName: pageName,
|
|
PagePV: item.PageVisitPV,
|
|
PageUV: item.PageVisitUV,
|
|
StayTimePV: int(item.PageStaytimePV),
|
|
EntryPagePV: item.EntrypagePV,
|
|
ExitPagePV: item.ExitpagePV,
|
|
SharePV: item.PageSharePV,
|
|
ShareUV: item.PageShareUV,
|
|
}
|
|
pageData = append(pageData, data)
|
|
}
|
|
|
|
global.GVA_LOG.Info(fmt.Sprintf("成功获取页面访问数据,共 %d 条记录", len(pageData)))
|
|
return pageData, nil
|
|
}
|
|
|
|
// GetRetentionData 获取用户留存数据
|
|
func (m *MiniStatisticsService) GetRetentionData() ([]response.RetentionData, error) {
|
|
// 获取微信小程序实例
|
|
mini, err := m.getMiniProgram()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// 获取最近7天的留存数据
|
|
beginDate := time.Now().AddDate(0, 0, -1).Format("20060102")
|
|
|
|
// 调用微信API获取日留存数据
|
|
analysisService := mini.GetAnalysis()
|
|
dailyRetain, err := analysisService.GetAnalysisDailyRetain(beginDate, beginDate)
|
|
if err != nil {
|
|
global.GVA_LOG.Error("获取小程序日留存数据失败: " + err.Error())
|
|
return nil, fmt.Errorf("获取用户留存数据失败: %v", err)
|
|
}
|
|
|
|
// 转换数据格式
|
|
var retentionData []response.RetentionData
|
|
|
|
// 处理新增用户留存
|
|
if len(dailyRetain.VisitUVNew) > 0 {
|
|
data := response.RetentionData{
|
|
RefDate: dailyRetain.RefDate[:4] + "-" + dailyRetain.RefDate[4:6] + "-" + dailyRetain.RefDate[6:8],
|
|
VisitUVNew: dailyRetain.VisitUVNew[0].Value, // 当天新增用户数
|
|
VisitUV: 0, // 活跃用户数从其他接口获取
|
|
Day1: 0.0,
|
|
Day7: 0.0,
|
|
Day30: 0.0,
|
|
}
|
|
|
|
// 计算留存率
|
|
if len(dailyRetain.VisitUVNew) > 1 {
|
|
data.Day1 = float64(dailyRetain.VisitUVNew[1].Value) / float64(dailyRetain.VisitUVNew[0].Value)
|
|
}
|
|
if len(dailyRetain.VisitUVNew) > 7 {
|
|
data.Day7 = float64(dailyRetain.VisitUVNew[7].Value) / float64(dailyRetain.VisitUVNew[0].Value)
|
|
}
|
|
if len(dailyRetain.VisitUVNew) > 30 {
|
|
data.Day30 = float64(dailyRetain.VisitUVNew[30].Value) / float64(dailyRetain.VisitUVNew[0].Value)
|
|
}
|
|
|
|
// 获取活跃用户数
|
|
if len(dailyRetain.VisitUV) > 0 {
|
|
data.VisitUV = dailyRetain.VisitUV[0].Value
|
|
}
|
|
|
|
retentionData = append(retentionData, data)
|
|
}
|
|
|
|
global.GVA_LOG.Info(fmt.Sprintf("成功获取用户留存数据,共 %d 条记录", len(retentionData)))
|
|
return retentionData, nil
|
|
}
|
|
|
|
// GetDeviceData 获取设备分布数据
|
|
func (m *MiniStatisticsService) GetDeviceData() ([]response.DeviceData, error) {
|
|
// 获取微信小程序实例
|
|
mini, err := m.getMiniProgram()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// 获取最近7天的用户画像数据
|
|
endDate := time.Now().AddDate(0, 0, -1).Format("20060102")
|
|
beginDate := time.Now().AddDate(0, 0, -7).Format("20060102")
|
|
|
|
// 调用微信API获取用户画像数据
|
|
analysisService := mini.GetAnalysis()
|
|
userPortrait, err := analysisService.GetAnalysisUserPortrait(beginDate, endDate)
|
|
if err != nil {
|
|
global.GVA_LOG.Error("获取小程序用户画像数据失败: " + err.Error())
|
|
return nil, fmt.Errorf("获取设备分布数据失败: %v", err)
|
|
}
|
|
|
|
// 转换设备数据格式
|
|
var deviceData []response.DeviceData
|
|
var totalVisitUV int
|
|
|
|
// 计算总访问量
|
|
for _, device := range userPortrait.VisitUV.Devices {
|
|
totalVisitUV += device.Value
|
|
}
|
|
|
|
// 转换设备数据
|
|
for _, device := range userPortrait.VisitUV.Devices {
|
|
percentage := 0.0
|
|
if totalVisitUV > 0 {
|
|
percentage = float64(device.Value) / float64(totalVisitUV) * 100
|
|
}
|
|
|
|
data := response.DeviceData{
|
|
DeviceType: device.Name,
|
|
SessionCnt: device.Value, // 使用访问量作为会话数
|
|
VisitUV: device.Value,
|
|
Percentage: percentage,
|
|
}
|
|
deviceData = append(deviceData, data)
|
|
}
|
|
|
|
// 如果没有设备数据,也处理平台数据
|
|
if len(deviceData) == 0 {
|
|
totalVisitUV = 0
|
|
for _, platform := range userPortrait.VisitUV.Platforms {
|
|
totalVisitUV += platform.Value
|
|
}
|
|
|
|
for _, platform := range userPortrait.VisitUV.Platforms {
|
|
percentage := 0.0
|
|
if totalVisitUV > 0 {
|
|
percentage = float64(platform.Value) / float64(totalVisitUV) * 100
|
|
}
|
|
|
|
data := response.DeviceData{
|
|
DeviceType: platform.Name,
|
|
SessionCnt: platform.Value,
|
|
VisitUV: platform.Value,
|
|
Percentage: percentage,
|
|
}
|
|
deviceData = append(deviceData, data)
|
|
}
|
|
}
|
|
|
|
global.GVA_LOG.Info(fmt.Sprintf("成功获取设备分布数据,共 %d 条记录", len(deviceData)))
|
|
return deviceData, nil
|
|
}
|