This commit is contained in:
yvan 2025-08-05 20:07:37 +08:00
parent 00ba99a00c
commit c07d43c779
1 changed files with 479 additions and 0 deletions

View File

@ -0,0 +1,479 @@
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
}