From 7b9e870bf981cb874dbd0c57dd8234d43bab7678 Mon Sep 17 00:00:00 2001 From: laoboli <1293528695@qq.com> Date: Tue, 1 Apr 2025 10:13:38 +0800 Subject: [PATCH] refactor: data analyze. --- controllers/train.go | 96 ++++++++++++++++++++++++++++++++++++++++---- main.go | 2 +- models/training.go | 15 +++++++ 3 files changed, 105 insertions(+), 8 deletions(-) diff --git a/controllers/train.go b/controllers/train.go index bc0cc48..4323997 100644 --- a/controllers/train.go +++ b/controllers/train.go @@ -1,6 +1,7 @@ package controllers import ( + "errors" "github.com/gin-gonic/gin" "gorm.io/gorm" "gorm.io/gorm/clause" @@ -57,13 +58,10 @@ func (tc *TrainingController) CreateTrainingRecord(c *gin.Context) { return err } } - - //// 保存腰带关联关系 - //if len(record.Belts) > 0 { - // if err := tx.Model(&record).Association("Belts").Replace(record.Belts); err != nil { - // return err - // } - //} + err := tc.heartRateAnalyze(tx, record) + if err != nil { + return err + } return nil }) @@ -79,6 +77,45 @@ func (tc *TrainingController) CreateTrainingRecord(c *gin.Context) { }) } +func (tc *TrainingController) heartRateAnalyze(tx *gorm.DB, record models.TrainRecord) error { + startTime := record.StartTime + + // 获取所有唯一的beltID + var beltIDs []uint + tx.Model(&models.HeartRate{}).Where("train_id = ?", record.TrainId). + Select("DISTINCT belt_id").Pluck("belt_id", &beltIDs) + + // 对每个belt计算 + for _, bid := range beltIDs { + // 计算平均心率 + ranges := getTimeRanges(startTime) + averages, err := calculateAverages(tx, record.TrainId, bid, ranges) + if err != nil { + return err + } + + // 曲线拟合 + x := []float64{2, 4, 6} + y := []float64{averages["2min"], averages["4min"], averages["6min"]} + a, _ := quadraticFit(x, y) + + // 存储结果 + analysis := models.BeltAnalysis{ + TrainID: record.TrainId, + RunType: record.RunType, + BeltID: bid, + Avg2min: averages["2min"], + Avg4min: averages["4min"], + Avg6min: averages["6min"], + CurveParamA: a, + } + if err := tx.Create(&analysis).Error; err != nil { + return err + } + } + return nil +} + func ReceiveTrainingData(c *gin.Context) { var data models.TrainingData @@ -101,3 +138,48 @@ func ReceiveTrainingData(c *gin.Context) { "id": data.ID, }) } + +func calculateAverages(tx *gorm.DB, trainID uint, beltID uint, ranges map[string]TimeRange) (map[string]float64, error) { + averages := make(map[string]float64) + for key, tr := range ranges { + var avg float64 + // 使用GORM Raw SQL提高效率[6,10](@ref) + err := tx.Raw(` + SELECT AVG(value) + FROM heart_rates + WHERE train_id = ? + AND belt_id = ? + AND time BETWEEN ? AND ?`, + trainID, beltID, tr.Start, tr.End, + ).Scan(&avg).Error + if err != nil { + return nil, err + } + averages[key] = avg + } + return averages, nil +} + +func quadraticFit(x []float64, y []float64) (float64, error) { + // 使用三点计算y=ax²+b的a值(x=[2,4,6]对应分钟) + if len(x) != 3 || len(y) != 3 { + return 0, errors.New("需要三个点") + } + // 构造方程组矩阵(简化计算) + a := (y[2] - 2*y[1] + y[0]) / (x[2]*x[2] - 2*x[1]*x[1] + x[0]*x[0]) + return a, nil +} + +type TimeRange struct { + Start int64 // 毫秒时间戳起点 + End int64 // 毫秒时间戳终点 +} + +func getTimeRanges(startTime int64) map[string]TimeRange { + // 计算相对于训练开始时间的窗口 + return map[string]TimeRange{ + "2min": {Start: startTime + 120000, End: startTime + 240000}, // 第2分钟(120-240秒) + "4min": {Start: startTime + 240000, End: startTime + 360000}, + "6min": {Start: startTime + 360000, End: startTime + 480000}, + } +} diff --git a/main.go b/main.go index 5095b42..b5884ce 100644 --- a/main.go +++ b/main.go @@ -15,7 +15,7 @@ func main() { config.DB.Debug() // 自动迁移模型 - config.DB.AutoMigrate(&models.TrainRecord{}, &models.TrainingData{}, &models.Belt{}, &models.HeartRate{}) + config.DB.AutoMigrate(&models.TrainRecord{}, &models.TrainingData{}, &models.Belt{}, &models.HeartRate{}, &models.BeltAnalysis{}) // 启动服务 r := routes.SetupRouter() diff --git a/models/training.go b/models/training.go index 67b0625..810b655 100644 --- a/models/training.go +++ b/models/training.go @@ -18,6 +18,20 @@ type Belt struct { Name string `gorm:"size:100" json:"name"` } +// 分析结果存储实体 +type BeltAnalysis struct { + gorm.Model + TrainID uint `gorm:"index;not null"` // 关联训练记录 + BeltID uint `gorm:"index;not null"` // 腰带唯一标识 + RunType string `gorm:"size:100" json:"RunType"` + Avg2min float64 `gorm:"type:double precision"` // 第2分钟平均心率 + Avg4min float64 `gorm:"type:double precision"` // 第4分钟平均心率 + Avg6min float64 `gorm:"type:double precision"` // 第6分钟平均心率 + CurveParamA float64 `gorm:"type:double precision"` // 拟合参数a值 +} + +// 中间计算结构(无需持久化) + // 对应Flutter的HeartRate结构 type HeartRate struct { gorm.Model @@ -37,6 +51,7 @@ type TrainRecord struct { StartTime int64 `gorm:"type:bigint" json:"time"` // 开始时间戳 EndTime int64 `gorm:"type:bigint" json:"endTime"` // 结束时间戳[3](@ref) Name string `gorm:"size:100" json:"name"` + RunType string `gorm:"size:100" json:"RunType"` MaxHeartRate int `gorm:"type:int" json:"maxHeartRate"` Duration int `gorm:"type:int" json:"duration"` // 持续时间(秒) PeopleNum int `gorm:"type:int" json:"peopleNum"`