203
This commit is contained in:
+258
-16
@@ -2,20 +2,30 @@ package openinstall
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
// OpenInstall 数据接口基础 URL
|
||||
apiBaseURL = "https://data.openinstall.com"
|
||||
)
|
||||
|
||||
// Client for OpenInstall API
|
||||
type Client struct {
|
||||
appKey string
|
||||
apiKey string
|
||||
httpClient *http.Client
|
||||
}
|
||||
|
||||
// NewClient creates a new OpenInstall client
|
||||
func NewClient(appKey string) *Client {
|
||||
// apiKey: OpenInstall 数据接口 ApiKey
|
||||
func NewClient(apiKey string) *Client {
|
||||
return &Client{
|
||||
appKey: appKey,
|
||||
apiKey: apiKey,
|
||||
httpClient: &http.Client{
|
||||
Timeout: 10 * time.Second,
|
||||
},
|
||||
@@ -25,21 +35,253 @@ func NewClient(appKey string) *Client {
|
||||
// PlatformStats represents statistics for a specific platform
|
||||
type PlatformStats struct {
|
||||
Platform string `json:"platform"`
|
||||
Clicks int64 `json:"clicks"` // OpenInstall "click"
|
||||
Visits int64 `json:"visits"` // OpenInstall "visit"
|
||||
Count int64 `json:"count"` // 下载/安装量
|
||||
}
|
||||
|
||||
// Mock implementation for now as we don't have real API docs/credentials
|
||||
// In a real implementation, this would call OpenInstall's API
|
||||
func (c *Client) GetPlatformStats(ctx context.Context, startDate, endDate time.Time) ([]PlatformStats, error) {
|
||||
// TODO: distinct implementation when API details are confirmed
|
||||
// PlatformDownloads 各端下载量统计
|
||||
type PlatformDownloads struct {
|
||||
Total int64 `json:"total"` // 总量
|
||||
IOS int64 `json:"ios"` // iOS
|
||||
Android int64 `json:"android"` // Android
|
||||
Windows int64 `json:"windows"` // Windows
|
||||
Mac int64 `json:"mac"` // Mac
|
||||
Comparison *MonthComparison `json:"comparison"` // 环比数据
|
||||
}
|
||||
|
||||
// Mock response
|
||||
return []PlatformStats{
|
||||
{Platform: "iOS", Clicks: 0, Visits: 0},
|
||||
{Platform: "Android", Clicks: 0, Visits: 0},
|
||||
{Platform: "Windows", Clicks: 0, Visits: 0},
|
||||
{Platform: "macOS", Clicks: 0, Visits: 0},
|
||||
{Platform: "Linux", Clicks: 0, Visits: 0},
|
||||
// MonthComparison 月度对比数据
|
||||
type MonthComparison struct {
|
||||
LastMonthTotal int64 `json:"lastMonthTotal"` // 上月总量
|
||||
Change int64 `json:"change"` // 变化量 (正数=增长, 负数=下降)
|
||||
ChangePercent float64 `json:"changePercent"` // 变化百分比
|
||||
}
|
||||
|
||||
// APIResponse 通用响应结构
|
||||
type APIResponse struct {
|
||||
Code int `json:"code"`
|
||||
Error *string `json:"error"`
|
||||
Body json.RawMessage `json:"body"`
|
||||
}
|
||||
|
||||
// GrowthData 新增安装数据
|
||||
type GrowthData struct {
|
||||
Date string `json:"date"`
|
||||
Visit int64 `json:"visit"` // 访问量
|
||||
Click int64 `json:"click"` // 点击量
|
||||
Install int64 `json:"install"` // 安装量
|
||||
Register int64 `json:"register"` // 注册量
|
||||
SurviveD1 int64 `json:"survive_d1"` // 1日留存
|
||||
SurviveD7 int64 `json:"survive_d7"` // 7日留存
|
||||
SurviveD30 int64 `json:"survive_d30"` // 30日留存
|
||||
}
|
||||
|
||||
// DistributionData 设备分布数据
|
||||
type DistributionData struct {
|
||||
Key string `json:"key"`
|
||||
Value int64 `json:"value"`
|
||||
}
|
||||
|
||||
// GetPlatformDownloads 获取各端下载量统计(当月数据 + 环比)
|
||||
func (c *Client) GetPlatformDownloads(ctx context.Context) (*PlatformDownloads, error) {
|
||||
now := time.Now()
|
||||
|
||||
// 当月数据:本月1号到今天
|
||||
startOfMonth := time.Date(now.Year(), now.Month(), 1, 0, 0, 0, 0, now.Location())
|
||||
endOfMonth := now
|
||||
|
||||
// 上月数据:上月1号到上月最后一天
|
||||
startOfLastMonth := startOfMonth.AddDate(0, -1, 0)
|
||||
endOfLastMonth := startOfMonth.AddDate(0, 0, -1)
|
||||
|
||||
// 获取当月各平台数据
|
||||
currentMonthData, err := c.getPlatformData(ctx, startOfMonth, endOfMonth)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get current month data: %w", err)
|
||||
}
|
||||
|
||||
// 获取上月各平台数据
|
||||
lastMonthData, err := c.getPlatformData(ctx, startOfLastMonth, endOfLastMonth)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get last month data: %w", err)
|
||||
}
|
||||
|
||||
// 计算总量
|
||||
currentTotal := currentMonthData.IOS + currentMonthData.Android + currentMonthData.Windows + currentMonthData.Mac
|
||||
lastTotal := lastMonthData.IOS + lastMonthData.Android + lastMonthData.Windows + lastMonthData.Mac
|
||||
|
||||
// 计算环比
|
||||
change := currentTotal - lastTotal
|
||||
changePercent := float64(0)
|
||||
if lastTotal > 0 {
|
||||
changePercent = (float64(change) / float64(lastTotal)) * 100
|
||||
}
|
||||
|
||||
return &PlatformDownloads{
|
||||
Total: currentTotal,
|
||||
IOS: currentMonthData.IOS,
|
||||
Android: currentMonthData.Android,
|
||||
Windows: currentMonthData.Windows,
|
||||
Mac: currentMonthData.Mac,
|
||||
Comparison: &MonthComparison{
|
||||
LastMonthTotal: lastTotal,
|
||||
Change: change,
|
||||
ChangePercent: changePercent,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
// getPlatformData 获取指定时间范围内各平台的数据
|
||||
func (c *Client) getPlatformData(ctx context.Context, startDate, endDate time.Time) (*PlatformDownloads, error) {
|
||||
result := &PlatformDownloads{}
|
||||
|
||||
// 获取 iOS 数据
|
||||
iosData, err := c.getDeviceDistribution(ctx, startDate, endDate, "ios", "total")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get iOS data: %w", err)
|
||||
}
|
||||
for _, item := range iosData {
|
||||
result.IOS += item.Value
|
||||
}
|
||||
|
||||
// 获取 Android 数据
|
||||
androidData, err := c.getDeviceDistribution(ctx, startDate, endDate, "android", "total")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get Android data: %w", err)
|
||||
}
|
||||
for _, item := range androidData {
|
||||
result.Android += item.Value
|
||||
}
|
||||
|
||||
// Windows 和 Mac 暂时设为 0 (需要从其他数据源获取)
|
||||
result.Windows = 0
|
||||
result.Mac = 0
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// getDeviceDistribution 获取设备分布数据
|
||||
func (c *Client) getDeviceDistribution(ctx context.Context, startDate, endDate time.Time, platform, sumBy string) ([]DistributionData, error) {
|
||||
apiURL := fmt.Sprintf("%s/data/sum/growth", apiBaseURL)
|
||||
|
||||
params := url.Values{}
|
||||
params.Add("apiKey", c.apiKey)
|
||||
params.Add("beginDate", startDate.Format("2006-01-02"))
|
||||
params.Add("endDate", endDate.Format("2006-01-02"))
|
||||
params.Add("platform", platform)
|
||||
params.Add("sumBy", sumBy)
|
||||
params.Add("excludeDuplication", "0")
|
||||
|
||||
fullURL := fmt.Sprintf("%s?%s", apiURL, params.Encode())
|
||||
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, fullURL, nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create request: %w", err)
|
||||
}
|
||||
|
||||
resp, err := c.httpClient.Do(req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to send request: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read response: %w", err)
|
||||
}
|
||||
|
||||
var apiResp APIResponse
|
||||
if err := json.Unmarshal(body, &apiResp); err != nil {
|
||||
return nil, fmt.Errorf("failed to parse response: %w", err)
|
||||
}
|
||||
|
||||
if apiResp.Code != 0 {
|
||||
errMsg := "unknown error"
|
||||
if apiResp.Error != nil {
|
||||
errMsg = *apiResp.Error
|
||||
}
|
||||
return nil, fmt.Errorf("API error (code=%d): %s", apiResp.Code, errMsg)
|
||||
}
|
||||
|
||||
var distData []DistributionData
|
||||
if err := json.Unmarshal(apiResp.Body, &distData); err != nil {
|
||||
return nil, fmt.Errorf("failed to parse distribution data: %w", err)
|
||||
}
|
||||
|
||||
return distData, nil
|
||||
}
|
||||
|
||||
// GetPlatformStats 获取平台统计数据(兼容旧接口)
|
||||
func (c *Client) GetPlatformStats(ctx context.Context, startDate, endDate time.Time) ([]PlatformStats, error) {
|
||||
// 调用新增安装数据接口
|
||||
growthData, err := c.GetGrowthData(ctx, startDate, endDate, "total")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get growth data: %w", err)
|
||||
}
|
||||
|
||||
// 如果没有数据,返回空列表
|
||||
if len(growthData) == 0 {
|
||||
return []PlatformStats{}, nil
|
||||
}
|
||||
|
||||
// 合并所有数据
|
||||
var totalVisits, totalClicks int64
|
||||
for _, data := range growthData {
|
||||
totalVisits += data.Visit
|
||||
totalClicks += data.Click
|
||||
}
|
||||
|
||||
return []PlatformStats{
|
||||
{
|
||||
Platform: "All",
|
||||
Count: totalVisits + totalClicks,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
// GetGrowthData 获取新增安装数据
|
||||
func (c *Client) GetGrowthData(ctx context.Context, startDate, endDate time.Time, statType string) ([]GrowthData, error) {
|
||||
apiURL := fmt.Sprintf("%s/data/event/growth", apiBaseURL)
|
||||
|
||||
params := url.Values{}
|
||||
params.Add("apiKey", c.apiKey)
|
||||
params.Add("startDate", startDate.Format("2006-01-02"))
|
||||
params.Add("endDate", endDate.Format("2006-01-02"))
|
||||
params.Add("statType", statType)
|
||||
|
||||
fullURL := fmt.Sprintf("%s?%s", apiURL, params.Encode())
|
||||
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, fullURL, nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create request: %w", err)
|
||||
}
|
||||
|
||||
resp, err := c.httpClient.Do(req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to send request: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read response: %w", err)
|
||||
}
|
||||
|
||||
var apiResp APIResponse
|
||||
if err := json.Unmarshal(body, &apiResp); err != nil {
|
||||
return nil, fmt.Errorf("failed to parse response: %w", err)
|
||||
}
|
||||
|
||||
if apiResp.Code != 0 {
|
||||
errMsg := "unknown error"
|
||||
if apiResp.Error != nil {
|
||||
errMsg = *apiResp.Error
|
||||
}
|
||||
return nil, fmt.Errorf("API error (code=%d): %s", apiResp.Code, errMsg)
|
||||
}
|
||||
|
||||
var growthData []GrowthData
|
||||
if err := json.Unmarshal(apiResp.Body, &growthData); err != nil {
|
||||
return nil, fmt.Errorf("failed to parse growth data: %w", err)
|
||||
}
|
||||
|
||||
return growthData, nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user