2026-07-15 09:43:33 +08:00

149 lines
5.1 KiB
Go

package statisticsclient
import (
"context"
"encoding/json"
"fmt"
"net/http"
"net/url"
"strconv"
"strings"
"time"
)
type ActivityTemplateDataQuery struct {
AppCode string
TemplateID string
PublishedVersion int64
RegionID int64
StartMS int64
EndMS int64
}
type ActivityTemplateOverview struct {
TemplateID string `json:"template_id"`
TemplateCode string `json:"template_code"`
PublishedVersion int64 `json:"published_version"`
Participants int64 `json:"participants"`
VisitCount int64 `json:"visit_count"`
GiftUsers int64 `json:"gift_users"`
TaskUsers int64 `json:"task_users"`
GiftEventCount int64 `json:"gift_event_count"`
GiftCount int64 `json:"gift_count"`
CoinAmount int64 `json:"coin_amount"`
TaskProgress int64 `json:"task_progress"`
TaskCompleted int64 `json:"task_completed"`
TaskClaimed int64 `json:"task_claimed"`
}
type ActivityTemplateTrendPoint struct {
StatDay string `json:"stat_day"`
Participants int64 `json:"participants"`
VisitCount int64 `json:"visit_count"`
GiftUsers int64 `json:"gift_users"`
TaskUsers int64 `json:"task_users"`
GiftEventCount int64 `json:"gift_event_count"`
GiftCount int64 `json:"gift_count"`
CoinAmount int64 `json:"coin_amount"`
TaskProgress int64 `json:"task_progress"`
TaskCompleted int64 `json:"task_completed"`
TaskClaimed int64 `json:"task_claimed"`
}
type ActivityTemplateGiftStat struct {
GiftID string `json:"gift_id"`
GiftUsers int64 `json:"gift_users"`
GiftEventCount int64 `json:"gift_event_count"`
GiftCount int64 `json:"gift_count"`
CoinAmount int64 `json:"coin_amount"`
}
type ActivityTemplateTaskStat struct {
TaskKey string `json:"task_key"`
TaskType string `json:"task_type"`
TaskUsers int64 `json:"task_users"`
TaskProgress int64 `json:"task_progress"`
TaskCompleted int64 `json:"task_completed"`
TaskClaimed int64 `json:"task_claimed"`
}
// ActivityTemplateData mirrors statistics-service's aggregate-only read model.
// Admin intentionally does not reconstruct any of these counters from activity owner tables.
type ActivityTemplateData struct {
Overview ActivityTemplateOverview `json:"overview"`
Trend []ActivityTemplateTrendPoint `json:"trend"`
Gifts []ActivityTemplateGiftStat `json:"gifts"`
Tasks []ActivityTemplateTaskStat `json:"tasks"`
Granularity string `json:"granularity"`
Timezone string `json:"timezone"`
StartMS int64 `json:"start_ms"`
EndMS int64 `json:"end_ms"`
RegionID int64 `json:"region_id"`
DimensionLimit int `json:"dimension_limit"`
GiftSort string `json:"gift_sort"`
TaskSort string `json:"task_sort"`
ServerTimeMS int64 `json:"server_time_ms"`
}
type ActivityTemplateDataClient interface {
ActivityTemplateData(context.Context, ActivityTemplateDataQuery) (ActivityTemplateData, error)
}
type HTTPClient struct {
baseURL string
client *http.Client
}
func NewHTTP(baseURL string, timeout time.Duration) *HTTPClient {
if timeout <= 0 {
timeout = 3 * time.Second
}
return &HTTPClient{
baseURL: strings.TrimRight(strings.TrimSpace(baseURL), "/"),
client: &http.Client{Timeout: timeout},
}
}
func (c *HTTPClient) ActivityTemplateData(ctx context.Context, query ActivityTemplateDataQuery) (ActivityTemplateData, error) {
values := url.Values{}
values.Set("app_code", strings.TrimSpace(query.AppCode))
values.Set("template_id", strings.TrimSpace(query.TemplateID))
values.Set("published_version", strconv.FormatInt(query.PublishedVersion, 10))
values.Set("start_ms", strconv.FormatInt(query.StartMS, 10))
values.Set("end_ms", strconv.FormatInt(query.EndMS, 10))
if query.RegionID > 0 {
values.Set("region_id", strconv.FormatInt(query.RegionID, 10))
}
req, err := http.NewRequestWithContext(ctx, http.MethodGet, c.baseURL+"/internal/v1/statistics/activity-templates/data?"+values.Encode(), nil)
if err != nil {
return ActivityTemplateData{}, err
}
resp, err := c.client.Do(req)
if err != nil {
return ActivityTemplateData{}, err
}
defer resp.Body.Close()
if resp.StatusCode < http.StatusOK || resp.StatusCode >= http.StatusMultipleChoices {
var body struct {
Error string `json:"error"`
}
_ = json.NewDecoder(resp.Body).Decode(&body)
return ActivityTemplateData{}, fmt.Errorf("statistics service returned status %d: %s", resp.StatusCode, strings.TrimSpace(body.Error))
}
var out ActivityTemplateData
if err := json.NewDecoder(resp.Body).Decode(&out); err != nil {
return ActivityTemplateData{}, err
}
// Arrays must remain arrays for dense table/chart clients; nil would serialize as null and force needless branches.
if out.Trend == nil {
out.Trend = []ActivityTemplateTrendPoint{}
}
if out.Gifts == nil {
out.Gifts = []ActivityTemplateGiftStat{}
}
if out.Tasks == nil {
out.Tasks = []ActivityTemplateTaskStat{}
}
return out, nil
}