This commit is contained in:
parent
00ba99a00c
commit
c07d43c779
|
|
@ -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
|
||||
}
|
||||
Loading…
Reference in New Issue