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 }