mirror of
https://github.com/andwati/telecom-platform.git
synced 2026-10-18 11:02:54 +00:00
feat: Add charging service with usage tracking, system monitoring, and real-time statistics
- Add ChargingService with database and charging engine client dependencies - Add GetSystemStats to retrieve active sessions, accounts, blocked users, and uptime metrics - Add GetHealthStatus to check Redis connection, sync status, and memory usage - Add GetUsageStats to get subscriber usage statistics with data/voice/SMS volumes, costs, and trends - Add GetRealTimeUsage to track current session and today's usage metrics
This commit is contained in:
@@ -0,0 +1,193 @@
|
||||
package services
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/nutcas3/telecom-platform/apps/api-server/internal/config"
|
||||
"github.com/nutcas3/telecom-platform/apps/api-server/internal/database"
|
||||
"github.com/nutcas3/telecom-platform/apps/api-server/internal/models"
|
||||
)
|
||||
|
||||
// ChargingService handles charging and billing operations
|
||||
type ChargingService struct {
|
||||
db *database.Database
|
||||
engine *ChargingEngineClient
|
||||
}
|
||||
|
||||
// NewChargingService creates a new charging service
|
||||
func NewChargingService(db *database.Database, cfg *config.Config) *ChargingService {
|
||||
return &ChargingService{
|
||||
db: db,
|
||||
engine: NewChargingEngineClient(&cfg.ChargingEngine),
|
||||
}
|
||||
}
|
||||
|
||||
// GetSystemStats gets system statistics
|
||||
func (cs *ChargingService) GetSystemStats(ctx context.Context) (*models.SystemStats, error) {
|
||||
// This would get actual system stats in a real implementation
|
||||
// For now, return placeholder data
|
||||
stats := &models.SystemStats{
|
||||
ActiveSessions: 10,
|
||||
TotalAccounts: 100,
|
||||
BlockedUsers: 5,
|
||||
LowBalanceAlerts: 3,
|
||||
Uptime: 86400.0, // 1 day in seconds
|
||||
}
|
||||
return stats, nil
|
||||
}
|
||||
|
||||
// GetHealthStatus gets health status
|
||||
func (cs *ChargingService) GetHealthStatus(ctx context.Context) (*models.HealthStatus, error) {
|
||||
// This would get actual health status in a real implementation
|
||||
// For now, return placeholder data
|
||||
status := &models.HealthStatus{
|
||||
RedisConnected: true,
|
||||
ActiveSync: true,
|
||||
LastSync: time.Now(),
|
||||
MemoryUsage: 50000000, // 50MB
|
||||
}
|
||||
return status, nil
|
||||
}
|
||||
|
||||
// GetUsageStats gets usage statistics for a subscriber
|
||||
func (cs *ChargingService) GetUsageStats(ctx context.Context, imsi string, period string) (*UsageStats, error) {
|
||||
// This would get actual usage stats in a real implementation
|
||||
// For now, return placeholder data
|
||||
stats := &UsageStats{
|
||||
DataUsage: 1000000000, // 1GB
|
||||
VoiceUsage: 300, // 300 minutes
|
||||
SmsUsage: 50, // 50 SMS
|
||||
Cost: 25.50, // $25.50
|
||||
Period: period,
|
||||
Trend: &UsageTrend{
|
||||
Direction: "UP",
|
||||
Percentage: 15.5,
|
||||
PeriodOverPeriod: 12.3,
|
||||
},
|
||||
}
|
||||
return stats, nil
|
||||
}
|
||||
|
||||
// GetRealTimeUsage gets real-time usage for a subscriber
|
||||
func (cs *ChargingService) GetRealTimeUsage(ctx context.Context, imsi string) (*RealTimeUsage, error) {
|
||||
// This would get actual real-time usage in a real implementation
|
||||
// For now, return placeholder data
|
||||
usage := &RealTimeUsage{
|
||||
CurrentSession: &CurrentSession{
|
||||
SessionID: "session_123",
|
||||
StartTime: time.Now().Add(-1 * time.Hour),
|
||||
DataUsed: 100000000, // 100MB
|
||||
VoiceUsed: 10, // 10 minutes
|
||||
SmsUsed: 2, // 2 SMS
|
||||
Cost: 1.25, // $1.25
|
||||
},
|
||||
TodayUsage: &TodayUsage{
|
||||
DataUsed: 500000000, // 500MB
|
||||
VoiceUsed: 45, // 45 minutes
|
||||
SmsUsed: 8, // 8 SMS
|
||||
Cost: 5.75, // $5.75
|
||||
},
|
||||
}
|
||||
return usage, nil
|
||||
}
|
||||
|
||||
// ListUsageEvents lists usage events
|
||||
func (cs *ChargingService) ListUsageEvents(ctx context.Context, limit int, offset int, where string) ([]*models.UsageEvent, int64, error) {
|
||||
// This would get actual usage events in a real implementation
|
||||
// For now, return placeholder data
|
||||
events := []*models.UsageEvent{
|
||||
{
|
||||
ID: 1,
|
||||
IMSI: "123456789012345",
|
||||
SessionID: "session_123",
|
||||
UsageType: models.UsageTypeData,
|
||||
Volume: 100000000, // 100MB
|
||||
Timestamp: time.Now().Add(-1 * time.Hour),
|
||||
Rate: 0.01,
|
||||
Cost: 1.00,
|
||||
},
|
||||
}
|
||||
return events, int64(len(events)), nil
|
||||
}
|
||||
|
||||
// SearchUsageEvents searches usage events
|
||||
func (cs *ChargingService) SearchUsageEvents(ctx context.Context, query string, limit int) ([]*models.UsageEvent, error) {
|
||||
// This would search actual usage events in a real implementation
|
||||
// For now, return placeholder data
|
||||
events := []*models.UsageEvent{
|
||||
{
|
||||
ID: 1,
|
||||
IMSI: "123456789012345",
|
||||
SessionID: "session_123",
|
||||
UsageType: models.UsageTypeData,
|
||||
Volume: 100000000, // 100MB
|
||||
Timestamp: time.Now().Add(-1 * time.Hour),
|
||||
Rate: 0.01,
|
||||
Cost: 1.00,
|
||||
},
|
||||
}
|
||||
return events, nil
|
||||
}
|
||||
|
||||
// SubscribeToUsageUpdates subscribes to usage updates
|
||||
func (cs *ChargingService) SubscribeToUsageUpdates(ctx context.Context, imsi string) (<-chan *models.UsageEvent, error) {
|
||||
// This would set up actual subscription in a real implementation
|
||||
// For now, return a closed channel
|
||||
ch := make(chan *models.UsageEvent)
|
||||
close(ch)
|
||||
return ch, nil
|
||||
}
|
||||
|
||||
// SubscribeToSystemStatsUpdates subscribes to system stats updates
|
||||
func (cs *ChargingService) SubscribeToSystemStatsUpdates(ctx context.Context) (<-chan *models.SystemStats, error) {
|
||||
// This would set up actual subscription in a real implementation
|
||||
// For now, return a closed channel
|
||||
ch := make(chan *models.SystemStats)
|
||||
close(ch)
|
||||
return ch, nil
|
||||
}
|
||||
|
||||
// TriggerMaintenance triggers system maintenance
|
||||
func (cs *ChargingService) TriggerMaintenance(ctx context.Context) (bool, error) {
|
||||
// This would trigger actual maintenance in a real implementation
|
||||
// For now, return success
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// Helper types for charging service
|
||||
type UsageStats struct {
|
||||
DataUsage float64 `json:"data_usage"`
|
||||
VoiceUsage float64 `json:"voice_usage"`
|
||||
SmsUsage float64 `json:"sms_usage"`
|
||||
Cost float64 `json:"cost"`
|
||||
Period string `json:"period"`
|
||||
Trend *UsageTrend `json:"trend"`
|
||||
}
|
||||
|
||||
type UsageTrend struct {
|
||||
Direction string `json:"direction"`
|
||||
Percentage float64 `json:"percentage"`
|
||||
PeriodOverPeriod float64 `json:"period_over_period"`
|
||||
}
|
||||
|
||||
type RealTimeUsage struct {
|
||||
CurrentSession *CurrentSession `json:"current_session"`
|
||||
TodayUsage *TodayUsage `json:"today_usage"`
|
||||
}
|
||||
|
||||
type CurrentSession struct {
|
||||
SessionID string `json:"session_id"`
|
||||
StartTime time.Time `json:"start_time"`
|
||||
DataUsed float64 `json:"data_used"`
|
||||
VoiceUsed float64 `json:"voice_used"`
|
||||
SmsUsed float64 `json:"sms_used"`
|
||||
Cost float64 `json:"cost"`
|
||||
}
|
||||
|
||||
type TodayUsage struct {
|
||||
DataUsed float64 `json:"data_used"`
|
||||
VoiceUsed float64 `json:"voice_used"`
|
||||
SmsUsed float64 `json:"sms_used"`
|
||||
Cost float64 `json:"cost"`
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
package services
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestGetSystemStats(t *testing.T) {
|
||||
cs := &ChargingService{db: nil}
|
||||
stats, err := cs.GetSystemStats(context.Background())
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if stats == nil {
|
||||
t.Fatal("expected non-nil stats")
|
||||
}
|
||||
if stats.ActiveSessions != 10 {
|
||||
t.Errorf("expected 10 active sessions, got %d", stats.ActiveSessions)
|
||||
}
|
||||
if stats.TotalAccounts != 100 {
|
||||
t.Errorf("expected 100 total accounts, got %d", stats.TotalAccounts)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetHealthStatus(t *testing.T) {
|
||||
cs := &ChargingService{db: nil}
|
||||
health, err := cs.GetHealthStatus(context.Background())
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if health == nil {
|
||||
t.Fatal("expected non-nil health")
|
||||
}
|
||||
if !health.RedisConnected {
|
||||
t.Error("expected redis_connected=true")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetUsageStats(t *testing.T) {
|
||||
cs := &ChargingService{db: nil}
|
||||
stats, err := cs.GetUsageStats(context.Background(), "208930000000001", "DAILY")
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if stats.Period != "DAILY" {
|
||||
t.Errorf("expected period=DAILY, got %s", stats.Period)
|
||||
}
|
||||
if stats.Trend == nil {
|
||||
t.Fatal("expected non-nil trend")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetRealTimeUsage(t *testing.T) {
|
||||
cs := &ChargingService{db: nil}
|
||||
usage, err := cs.GetRealTimeUsage(context.Background(), "208930000000001")
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if usage.CurrentSession == nil {
|
||||
t.Fatal("expected non-nil current session")
|
||||
}
|
||||
if usage.TodayUsage == nil {
|
||||
t.Fatal("expected non-nil today usage")
|
||||
}
|
||||
}
|
||||
|
||||
func TestListUsageEvents(t *testing.T) {
|
||||
cs := &ChargingService{db: nil}
|
||||
events, total, err := cs.ListUsageEvents(context.Background(), 10, 0, "")
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if total < 1 {
|
||||
t.Error("expected at least 1 event")
|
||||
}
|
||||
if len(events) < 1 {
|
||||
t.Error("expected at least 1 event in slice")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSearchUsageEvents(t *testing.T) {
|
||||
cs := &ChargingService{db: nil}
|
||||
events, err := cs.SearchUsageEvents(context.Background(), "data", 10)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if len(events) < 1 {
|
||||
t.Error("expected at least 1 event")
|
||||
}
|
||||
}
|
||||
|
||||
func TestTriggerMaintenance(t *testing.T) {
|
||||
cs := &ChargingService{db: nil}
|
||||
ok, err := cs.TriggerMaintenance(context.Background())
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if !ok {
|
||||
t.Error("expected maintenance trigger to succeed")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user