feat: 数据上报

This commit is contained in:
yokowu
2025-07-22 17:45:56 +08:00
parent 472ad769a9
commit 1f6b3976e8
7 changed files with 230 additions and 3 deletions

View File

@@ -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) {

View File

@@ -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
}

41
backend/domain/report.go Normal file
View File

@@ -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)
}

View File

@@ -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,
)

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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),