Files
pet-ai/server/plugin/wechat-integration/service/mini_statistics_service.go
T
2025-08-05 17:46:15 +08:00

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
}