pet-ai/server/plugin/wechat-integration/service/mp_statistics_service.go

480 lines
13 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package service
import (
"errors"
"fmt"
"time"
"github.com/flipped-aurora/gin-vue-admin/server/global"
"github.com/flipped-aurora/gin-vue-admin/server/plugin/wechat-integration/model"
"github.com/silenceper/wechat/v2"
"github.com/silenceper/wechat/v2/cache"
"github.com/silenceper/wechat/v2/officialaccount"
"github.com/silenceper/wechat/v2/officialaccount/config"
"go.uber.org/zap"
)
type MpStatisticsService struct{}
// GetOfficialAccount 获取微信公众号实例(从数据库获取配置)
func (m *MpStatisticsService) GetOfficialAccount() (*officialaccount.OfficialAccount, error) {
// 从数据库获取微信公众号配置
var mpConfig model.MpConfig
err := global.GVA_DB.Where("config_type = ?", model.ConfigTypeMP).First(&mpConfig).Error
if err != nil {
return nil, fmt.Errorf("获取微信公众号配置失败: %v", err)
}
if mpConfig.AppID == "" || mpConfig.AppSecret == "" {
return nil, errors.New("微信公众号配置不完整")
}
wc := wechat.NewWechat()
memory := cache.NewMemory()
cfg := &config.Config{
AppID: mpConfig.AppID,
AppSecret: mpConfig.AppSecret,
Cache: memory,
}
// 如果有Token设置服务器配置
if mpConfig.Token != nil && *mpConfig.Token != "" {
cfg.Token = *mpConfig.Token
}
return wc.GetOfficialAccount(cfg), nil
}
// formatDate 格式化日期为微信API要求的格式 (YYYY-MM-DD)
func (m *MpStatisticsService) formatDate(date string) (string, error) {
// 尝试解析不同格式的日期
layouts := []string{
"2006-01-02",
"2006/01/02",
"20060102",
}
var t time.Time
var err error
for _, layout := range layouts {
t, err = time.Parse(layout, date)
if err == nil {
break
}
}
if err != nil {
return "", fmt.Errorf("无效的日期格式: %s", date)
}
return t.Format("2006-01-02"), nil
}
// validateDateRange 验证日期范围
func (m *MpStatisticsService) validateDateRange(beginDate, endDate string, maxDays int) error {
// 格式化日期
formattedBeginDate, err := m.formatDate(beginDate)
if err != nil {
return fmt.Errorf("开始日期格式错误: %v", err)
}
formattedEndDate, err := m.formatDate(endDate)
if err != nil {
return fmt.Errorf("结束日期格式错误: %v", err)
}
// 解析日期
beginTime, err := time.Parse("2006-01-02", formattedBeginDate)
if err != nil {
return fmt.Errorf("解析开始日期失败: %v", err)
}
endTime, err := time.Parse("2006-01-02", formattedEndDate)
if err != nil {
return fmt.Errorf("解析结束日期失败: %v", err)
}
// 检查日期顺序
if endTime.Before(beginTime) {
return errors.New("结束日期不能早于开始日期")
}
// 检查日期范围
daysDiff := int(endTime.Sub(beginTime).Hours()/24) + 1
if daysDiff > maxDays {
return fmt.Errorf("日期范围不能超过%d天当前范围%d天", maxDays, daysDiff)
}
return nil
}
// validateUserStatisticsDateRange 验证用户统计数据的日期范围(特殊限制:不能包括今天)
func (m *MpStatisticsService) validateUserStatisticsDateRange(beginDate, endDate string, maxDays int) error {
// 先进行基础日期范围验证
if err := m.validateDateRange(beginDate, endDate, maxDays); err != nil {
return err
}
// 格式化日期
formattedEndDate, err := m.formatDate(endDate)
if err != nil {
return fmt.Errorf("结束日期格式错误: %v", err)
}
// 解析结束日期
endTime, err := time.Parse("2006-01-02", formattedEndDate)
if err != nil {
return fmt.Errorf("解析结束日期失败: %v", err)
}
// 检查是否包含今天
today := time.Now().Format("2006-01-02")
todayTime, _ := time.Parse("2006-01-02", today)
if !endTime.Before(todayTime) {
return errors.New("微信统计API限制查询日期不能包括今天最晚只能查询到昨天")
}
return nil
}
// GetUserSummary 获取用户增减数据
func (m *MpStatisticsService) GetUserSummary(beginDate, endDate string) ([]map[string]interface{}, error) {
// 验证日期范围最长7天且不能包括今天
if err := m.validateUserStatisticsDateRange(beginDate, endDate, 7); err != nil {
return nil, err
}
// 格式化日期
formattedBeginDate, err := m.formatDate(beginDate)
if err != nil {
return nil, err
}
formattedEndDate, err := m.formatDate(endDate)
if err != nil {
return nil, err
}
// 获取微信公众号实例
oa, err := m.GetOfficialAccount()
if err != nil {
return nil, err
}
// 使用 silenceper/wechat 库的 DataCube 方法
dataCube := oa.GetDataCube()
response, err := dataCube.GetUserSummary(formattedBeginDate, formattedEndDate)
if err != nil {
global.GVA_LOG.Error("调用微信用户增减数据API失败", zap.Error(err))
return nil, fmt.Errorf("获取用户增减数据失败: %v", err)
}
// 转换为前端需要的格式
result := make([]map[string]interface{}, 0, len(response.List))
for _, item := range response.List {
result = append(result, map[string]interface{}{
"refDate": item.RefDate,
"userSource": item.UserSource,
"newUser": item.NewUser,
"cancelUser": item.CancelUser,
// 注意:用户增减数据中没有累计用户数字段,这个字段在用户累计数据中
})
}
global.GVA_LOG.Info("获取用户增减数据成功",
zap.String("beginDate", formattedBeginDate),
zap.String("endDate", formattedEndDate),
zap.Int("count", len(result)))
return result, nil
}
// GetUserCumulate 获取用户累计数据
func (m *MpStatisticsService) GetUserCumulate(beginDate, endDate string) ([]map[string]interface{}, error) {
// 验证日期范围最长7天
if err := m.validateDateRange(beginDate, endDate, 7); err != nil {
return nil, err
}
// 格式化日期
formattedBeginDate, err := m.formatDate(beginDate)
if err != nil {
return nil, err
}
formattedEndDate, err := m.formatDate(endDate)
if err != nil {
return nil, err
}
// 获取微信公众号实例
oa, err := m.GetOfficialAccount()
if err != nil {
return nil, err
}
// 使用 silenceper/wechat 库的 DataCube 方法
dataCube := oa.GetDataCube()
response, err := dataCube.GetUserAccumulate(formattedBeginDate, formattedEndDate)
if err != nil {
global.GVA_LOG.Error("调用微信用户累计数据API失败", zap.Error(err))
return nil, fmt.Errorf("获取用户累计数据失败: %v", err)
}
// 转换为前端需要的格式
result := make([]map[string]interface{}, 0, len(response.List))
for _, item := range response.List {
result = append(result, map[string]interface{}{
"refDate": item.RefDate,
"cumulateUser": item.CumulateUser,
})
}
global.GVA_LOG.Info("获取用户累计数据成功",
zap.String("beginDate", formattedBeginDate),
zap.String("endDate", formattedEndDate),
zap.Int("count", len(result)))
return result, nil
}
// GetUpstreamMessage 获取消息发送概况数据
func (m *MpStatisticsService) GetUpstreamMessage(beginDate, endDate string) ([]map[string]interface{}, error) {
// 验证日期范围最长7天且不能包括今天
if err := m.validateUserStatisticsDateRange(beginDate, endDate, 7); err != nil {
return nil, err
}
// 格式化日期
formattedBeginDate, err := m.formatDate(beginDate)
if err != nil {
return nil, err
}
formattedEndDate, err := m.formatDate(endDate)
if err != nil {
return nil, err
}
// 获取微信公众号实例
oa, err := m.GetOfficialAccount()
if err != nil {
return nil, err
}
// 使用 silenceper/wechat 库的 DataCube 方法
dataCube := oa.GetDataCube()
response, err := dataCube.GetUpstreamMsg(formattedBeginDate, formattedEndDate)
if err != nil {
global.GVA_LOG.Error("调用微信消息发送概况API失败", zap.Error(err))
return nil, fmt.Errorf("获取消息发送概况数据失败: %v", err)
}
// 转换为前端需要的格式
result := make([]map[string]interface{}, 0, len(response.List))
for _, item := range response.List {
result = append(result, map[string]interface{}{
"refDate": item.RefDate,
"msgType": item.MsgType,
"msgUser": item.MsgUser,
"msgCount": item.MsgCount,
})
}
global.GVA_LOG.Info("获取消息发送概况数据成功",
zap.String("beginDate", formattedBeginDate),
zap.String("endDate", formattedEndDate),
zap.Int("count", len(result)))
return result, nil
}
// GetInterfaceSummary 获取接口调用概况数据
func (m *MpStatisticsService) GetInterfaceSummary(beginDate, endDate string) ([]map[string]interface{}, error) {
// 验证日期范围最长30天
if err := m.validateDateRange(beginDate, endDate, 30); err != nil {
return nil, err
}
// 格式化日期
formattedBeginDate, err := m.formatDate(beginDate)
if err != nil {
return nil, err
}
formattedEndDate, err := m.formatDate(endDate)
if err != nil {
return nil, err
}
// 获取微信公众号实例
oa, err := m.GetOfficialAccount()
if err != nil {
return nil, err
}
// 使用 silenceper/wechat 库的 DataCube 方法
dataCube := oa.GetDataCube()
response, err := dataCube.GetInterfaceSummary(formattedBeginDate, formattedEndDate)
if err != nil {
global.GVA_LOG.Error("调用微信接口调用概况API失败", zap.Error(err))
return nil, fmt.Errorf("获取接口调用概况数据失败: %v", err)
}
// 转换为前端需要的格式
result := make([]map[string]interface{}, 0, len(response.List))
for _, item := range response.List {
result = append(result, map[string]interface{}{
"refDate": item.RefDate,
"callbackCount": item.CallbackCount,
"failCount": item.FailCount,
"totalTimeCost": item.TotalTimeCost,
"maxTimeCost": item.MaxTimeCost,
})
}
global.GVA_LOG.Info("获取接口调用概况数据成功",
zap.String("beginDate", formattedBeginDate),
zap.String("endDate", formattedEndDate),
zap.Int("count", len(result)))
return result, nil
}
// GetMessageTypeDistribution 获取消息类型分布数据
func (m *MpStatisticsService) GetMessageTypeDistribution(beginDate, endDate string) ([]map[string]interface{}, error) {
// 验证日期范围最长7天且不能包括今天
if err := m.validateUserStatisticsDateRange(beginDate, endDate, 7); err != nil {
return nil, err
}
// 格式化日期
formattedBeginDate, err := m.formatDate(beginDate)
if err != nil {
return nil, err
}
formattedEndDate, err := m.formatDate(endDate)
if err != nil {
return nil, err
}
// 获取微信公众号实例
oa, err := m.GetOfficialAccount()
if err != nil {
return nil, err
}
// 使用 silenceper/wechat 库的 GetUpstreamMsg 方法获取消息发送概况数据
dataCube := oa.GetDataCube()
response, err := dataCube.GetUpstreamMsg(formattedBeginDate, formattedEndDate)
if err != nil {
global.GVA_LOG.Error("调用微信消息发送概况API失败", zap.Error(err))
return nil, fmt.Errorf("获取消息类型分布数据失败: %v", err)
}
// 消息类型映射
msgTypeMap := map[int]string{
1: "文本消息",
2: "图片消息",
3: "语音消息",
4: "视频消息",
6: "链接消息",
}
// 统计各消息类型的总数
msgTypeStats := make(map[int]int)
totalCount := 0
for _, item := range response.List {
msgTypeStats[item.MsgType] += item.MsgCount
totalCount += item.MsgCount
}
// 转换为前端需要的格式
result := make([]map[string]interface{}, 0)
for msgType, count := range msgTypeStats {
msgTypeName := msgTypeMap[msgType]
if msgTypeName == "" {
msgTypeName = fmt.Sprintf("其他消息(%d)", msgType)
}
// 计算百分比
percentage := float64(count) / float64(totalCount) * 100
result = append(result, map[string]interface{}{
"name": msgTypeName,
"value": count,
"percentage": fmt.Sprintf("%.1f%%", percentage),
"type": msgType,
})
}
global.GVA_LOG.Info("获取消息类型分布数据成功",
zap.String("beginDate", formattedBeginDate),
zap.String("endDate", formattedEndDate),
zap.Int("totalCount", totalCount),
zap.Int("typeCount", len(result)))
return result, nil
}
// GetUserRegionData 获取用户地区分布数据
// 注意微信官方API暂不提供地区分布接口这里使用模拟数据
// 实际项目中可以通过用户标签、自定义字段或第三方数据分析服务获取
func (m *MpStatisticsService) GetUserRegionData() ([]map[string]interface{}, error) {
// 模拟地区分布数据
// 实际项目中可以:
// 1. 通过用户标签系统获取地区信息
// 2. 通过用户自定义字段存储地区
// 3. 通过第三方数据分析服务
// 4. 通过IP地址解析地区需要额外的IP库
regionData := []map[string]interface{}{
{
"region": "广东省",
"count": 320,
"percentage": 23.7,
},
{
"region": "北京市",
"count": 280,
"percentage": 20.7,
},
{
"region": "上海市",
"count": 245,
"percentage": 18.1,
},
{
"region": "江苏省",
"count": 180,
"percentage": 13.3,
},
{
"region": "浙江省",
"count": 150,
"percentage": 11.1,
},
{
"region": "山东省",
"count": 95,
"percentage": 7.0,
},
{
"region": "其他地区",
"count": 80,
"percentage": 5.9,
},
}
global.GVA_LOG.Info("获取用户地区分布数据成功(模拟数据)",
zap.Int("count", len(regionData)))
return regionData, nil
}