From 1f6b3976e8ce68b97c0f1ca11cbf4cd9a2dec897 Mon Sep 17 00:00:00 2001 From: yokowu <18836617@qq.com> Date: Tue, 22 Jul 2025 17:45:56 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=95=B0=E6=8D=AE=E4=B8=8A=E6=8A=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/cmd/server/wire.go | 2 + backend/cmd/server/wire_gen.go | 7 ++ backend/domain/report.go | 41 ++++++++++ backend/internal/provider.go | 4 + backend/internal/report/repo/report.go | 98 +++++++++++++++++++++++ backend/internal/report/usecase/report.go | 71 ++++++++++++++++ backend/pkg/report/report.go | 10 ++- 7 files changed, 230 insertions(+), 3 deletions(-) create mode 100644 backend/domain/report.go create mode 100644 backend/internal/report/repo/report.go create mode 100644 backend/internal/report/usecase/report.go diff --git a/backend/cmd/server/wire.go b/backend/cmd/server/wire.go index 35c7685..aaa0fdc 100644 --- a/backend/cmd/server/wire.go +++ b/backend/cmd/server/wire.go @@ -12,6 +12,7 @@ import ( "github.com/chaitin/MonkeyCode/backend/config" "github.com/chaitin/MonkeyCode/backend/db" + "github.com/chaitin/MonkeyCode/backend/domain" billingv1 "github.com/chaitin/MonkeyCode/backend/internal/billing/handler/http/v1" dashv1 "github.com/chaitin/MonkeyCode/backend/internal/dashboard/handler/v1" v1 "github.com/chaitin/MonkeyCode/backend/internal/model/handler/http/v1" @@ -33,6 +34,7 @@ type Server struct { billingV1 *billingv1.BillingHandler version *version.VersionInfo report *report.Reporter + reportuse domain.ReportUsecase } func newServer() (*Server, error) { diff --git a/backend/cmd/server/wire_gen.go b/backend/cmd/server/wire_gen.go index d7dc6d4..0703bf7 100644 --- a/backend/cmd/server/wire_gen.go +++ b/backend/cmd/server/wire_gen.go @@ -10,6 +10,7 @@ import ( "github.com/GoYoko/web" "github.com/chaitin/MonkeyCode/backend/config" "github.com/chaitin/MonkeyCode/backend/db" + "github.com/chaitin/MonkeyCode/backend/domain" v1_5 "github.com/chaitin/MonkeyCode/backend/internal/billing/handler/http/v1" repo7 "github.com/chaitin/MonkeyCode/backend/internal/billing/repo" usecase6 "github.com/chaitin/MonkeyCode/backend/internal/billing/usecase" @@ -28,6 +29,8 @@ import ( "github.com/chaitin/MonkeyCode/backend/internal/proxy" "github.com/chaitin/MonkeyCode/backend/internal/proxy/repo" "github.com/chaitin/MonkeyCode/backend/internal/proxy/usecase" + repo8 "github.com/chaitin/MonkeyCode/backend/internal/report/repo" + usecase7 "github.com/chaitin/MonkeyCode/backend/internal/report/usecase" v1_3 "github.com/chaitin/MonkeyCode/backend/internal/user/handler/v1" repo5 "github.com/chaitin/MonkeyCode/backend/internal/user/repo" usecase4 "github.com/chaitin/MonkeyCode/backend/internal/user/usecase" @@ -86,6 +89,8 @@ func newServer() (*Server, error) { billingHandler := v1_5.NewBillingHandler(web, billingUsecase, authMiddleware, activeMiddleware) versionInfo := version.NewVersionInfo() reporter := report.NewReport(slogLogger, configConfig, versionInfo) + reportRepo := repo8.NewReportRepo(client) + reportUsecase := usecase7.NewReportUsecase(reportRepo, slogLogger, reporter) server := &Server{ config: configConfig, web: web, @@ -98,6 +103,7 @@ func newServer() (*Server, error) { billingV1: billingHandler, version: versionInfo, report: reporter, + reportuse: reportUsecase, } return server, nil } @@ -116,4 +122,5 @@ type Server struct { billingV1 *v1_5.BillingHandler version *version.VersionInfo report *report.Reporter + reportuse domain.ReportUsecase } diff --git a/backend/domain/report.go b/backend/domain/report.go new file mode 100644 index 0000000..b29a9ad --- /dev/null +++ b/backend/domain/report.go @@ -0,0 +1,41 @@ +package domain + +import ( + "context" + + "github.com/chaitin/MonkeyCode/backend/consts" +) + +type ReportUsecase interface { +} + +type ReportRepo interface { + GetAdminCount(ctx context.Context) (int64, error) + GetMemberCount(ctx context.Context) (int64, error) + GetLast24HoursStats(ctx context.Context) (*ActivityStats, error) + GetCurrentModels(ctx context.Context) ([]*ReportModelUsage, error) +} + +type ReportData struct { + Timestamp string `json:"timestamp"` // 上报时间戳 + Version string `json:"version"` // 系统版本号 + MachineID string `json:"machine_id"` // 机器ID + AdminCount int64 `json:"admin_count"` // 管理员数量 + MemberCount int64 `json:"member_count"` // 成员数量 + Last24Hours *ActivityStats `json:"last_24_hours"` // 最近24小时统计 + CurrentModels []*ReportModelUsage `json:"current_models"` // 当前使用的模型列表 +} + +type ActivityStats struct { + ChatCount int64 `json:"chat_count"` // 对话次数 + CompletionCount int64 `json:"completion_count"` // 补全生成次数 + AcceptedCount int64 `json:"accepted_count"` // 补全采纳次数 + TotalCodeLines int64 `json:"total_code_lines"` // 总代码量(行数) + AcceptanceRate float64 `json:"acceptance_rate"` // 采纳率 (accepted_count / completion_count) +} + +type ReportModelUsage struct { + ModelID string `json:"model_id"` // 模型ID + ModelName string `json:"model_name"` // 模型名称 + ModelType consts.ModelType `json:"model_type"` // 模型类型 (llm/coder) +} diff --git a/backend/internal/provider.go b/backend/internal/provider.go index 299aa3e..c7350c0 100644 --- a/backend/internal/provider.go +++ b/backend/internal/provider.go @@ -21,6 +21,8 @@ import ( "github.com/chaitin/MonkeyCode/backend/internal/proxy" proxyrepo "github.com/chaitin/MonkeyCode/backend/internal/proxy/repo" proxyusecase "github.com/chaitin/MonkeyCode/backend/internal/proxy/usecase" + reportrepo "github.com/chaitin/MonkeyCode/backend/internal/report/repo" + reportuse "github.com/chaitin/MonkeyCode/backend/internal/report/usecase" userV1 "github.com/chaitin/MonkeyCode/backend/internal/user/handler/v1" userrepo "github.com/chaitin/MonkeyCode/backend/internal/user/repo" userusecase "github.com/chaitin/MonkeyCode/backend/internal/user/usecase" @@ -52,4 +54,6 @@ var Provider = wire.NewSet( erepo.NewExtensionRepo, eusecase.NewExtensionUsecase, version.NewVersionInfo, + reportuse.NewReportUsecase, + reportrepo.NewReportRepo, ) diff --git a/backend/internal/report/repo/report.go b/backend/internal/report/repo/report.go new file mode 100644 index 0000000..9f27ace --- /dev/null +++ b/backend/internal/report/repo/report.go @@ -0,0 +1,98 @@ +package repo + +import ( + "context" + "time" + + "github.com/chaitin/MonkeyCode/backend/consts" + "github.com/chaitin/MonkeyCode/backend/db" + "github.com/chaitin/MonkeyCode/backend/db/task" + "github.com/chaitin/MonkeyCode/backend/db/user" + "github.com/chaitin/MonkeyCode/backend/domain" + "github.com/chaitin/MonkeyCode/backend/pkg/cvt" +) + +type ReportRepo struct { + db *db.Client +} + +func NewReportRepo(db *db.Client) domain.ReportRepo { + r := &ReportRepo{db: db} + return r +} + +// GetAdminCount implements domain.ReportRepo. +func (r *ReportRepo) GetAdminCount(ctx context.Context) (int64, error) { + count, err := r.db.Admin.Query().Count(ctx) + return int64(count), err +} + +// GetCurrentModels implements domain.ReportRepo. +func (r *ReportRepo) GetCurrentModels(ctx context.Context) ([]*domain.ReportModelUsage, error) { + now := time.Now() + models, err := r.db.Model.Query(). + WithTasks(func(tq *db.TaskQuery) { + tq.Where(task.CreatedAtGTE(now.Add(-24 * time.Hour))) + }). + All(ctx) + if err != nil { + return nil, err + } + + return cvt.Iter(models, func(_ int, m *db.Model) *domain.ReportModelUsage { + return &domain.ReportModelUsage{ + ModelID: m.ID.String(), + ModelName: m.ModelName, + ModelType: m.ModelType, + } + }), nil +} + +// GetLast24HoursStats implements domain.ReportRepo. +func (r *ReportRepo) GetLast24HoursStats(ctx context.Context) (*domain.ActivityStats, error) { + now := time.Now() + last24Hours := now.Add(-24 * time.Hour) + + // 获取最近24小时的任务统计 + tasks, err := r.db.Task.Query(). + Where(task.CreatedAtGTE(last24Hours)). + All(ctx) + if err != nil { + return nil, err + } + + stats := &domain.ActivityStats{} + var completionCount, acceptedCount, totalCodeLines int64 + + for _, t := range tasks { + switch t.ModelType { + case consts.ModelTypeLLM: + stats.ChatCount++ + case consts.ModelTypeCoder: + completionCount++ + if t.IsAccept { + acceptedCount++ + } + } + totalCodeLines += t.CodeLines + } + + stats.CompletionCount = completionCount + stats.AcceptedCount = acceptedCount + stats.TotalCodeLines = totalCodeLines + + // 计算采纳率 + if completionCount > 0 { + stats.AcceptanceRate = float64(acceptedCount) / float64(completionCount) + } + + return stats, nil +} + +// GetMemberCount implements domain.ReportRepo. +func (r *ReportRepo) GetMemberCount(ctx context.Context) (int64, error) { + count, err := r.db.User.Query(). + Where(user.Status(consts.UserStatusActive)). + Count(ctx) + return int64(count), err +} diff --git a/backend/internal/report/usecase/report.go b/backend/internal/report/usecase/report.go new file mode 100644 index 0000000..96876ca --- /dev/null +++ b/backend/internal/report/usecase/report.go @@ -0,0 +1,71 @@ +package usecase + +import ( + "context" + "fmt" + "log/slog" + "time" + + "github.com/chaitin/MonkeyCode/backend/domain" + "github.com/chaitin/MonkeyCode/backend/pkg/report" + "github.com/chaitin/MonkeyCode/backend/pkg/version" +) + +type ReportUsecase struct { + repo domain.ReportRepo + logger *slog.Logger + reporter *report.Reporter +} + +func NewReportUsecase(repo domain.ReportRepo, logger *slog.Logger, reporter *report.Reporter) domain.ReportUsecase { + r := &ReportUsecase{repo: repo, logger: logger, reporter: reporter} + go r.Report() + return r +} + +func (r *ReportUsecase) Report() { + ticker := time.NewTicker(5 * time.Minute) + if err := r.innerReport(); err != nil { + r.logger.With("error", err).Error("report failed") + } + + for range ticker.C { + if err := r.innerReport(); err != nil { + r.logger.With("error", err).Error("report failed") + } + } +} + +func (r *ReportUsecase) innerReport() error { + admins, err := r.repo.GetAdminCount(context.Background()) + if err != nil { + return fmt.Errorf("get admin count failed: %w", err) + } + models, err := r.repo.GetCurrentModels(context.Background()) + if err != nil { + return fmt.Errorf("get current models failed: %w", err) + } + stats, err := r.repo.GetLast24HoursStats(context.Background()) + if err != nil { + return fmt.Errorf("get last 24 hours stats failed: %w", err) + } + members, err := r.repo.GetMemberCount(context.Background()) + if err != nil { + return fmt.Errorf("get member count failed: %w", err) + } + + data := domain.ReportData{ + Timestamp: time.Now().Format(time.RFC3339), + Version: version.Version, + MachineID: r.reporter.GetMachineID(), + AdminCount: admins, + MemberCount: members, + Last24Hours: stats, + CurrentModels: models, + } + + if err := r.reporter.Report("monkeycode-metrics", data); err != nil { + return fmt.Errorf("report failed: %w", err) + } + return nil +} diff --git a/backend/pkg/report/report.go b/backend/pkg/report/report.go index a67f5ca..57da278 100644 --- a/backend/pkg/report/report.go +++ b/backend/pkg/report/report.go @@ -52,7 +52,7 @@ func (r *Reporter) readMachineID() (string, error) { return r.machineID, nil } -func (r *Reporter) report(data any) error { +func (r *Reporter) Report(index string, data any) error { b, err := json.Marshal(data) if err != nil { return err @@ -64,7 +64,7 @@ func (r *Reporter) report(data any) error { } req := map[string]any{ - "index": "monkeycode-installation", + "index": index, "id": uuid.NewString(), "data": encrypt, } @@ -77,6 +77,10 @@ func (r *Reporter) report(data any) error { return nil } +func (r *Reporter) GetMachineID() string { + return r.machineID +} + func (r *Reporter) ReportInstallation() error { if r.machineID != "" { return nil @@ -102,7 +106,7 @@ func (r *Reporter) ReportInstallation() error { return err } - return r.report(InstallData{ + return r.Report("monkeycode-installation", InstallData{ MachineID: id, Version: r.version.Version(), Timestamp: time.Now().Format(time.RFC3339),