Compare commits
2 Commits
f4df1724b2
...
4e10359a5b
| Author | SHA1 | Date | |
|---|---|---|---|
| 4e10359a5b | |||
| 8510d19baa |
84
controllers/step_train.go
Normal file
84
controllers/step_train.go
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
package controllers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
"gorm.io/gorm/clause"
|
||||||
|
"hr_receiver/config"
|
||||||
|
"hr_receiver/models"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
type StepTrainingController struct {
|
||||||
|
DB *gorm.DB
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewStepTrainingController() *StepTrainingController {
|
||||||
|
return &StepTrainingController{DB: config.DB}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 接收训练记录
|
||||||
|
func (tc *StepTrainingController) CreateTrainingRecord(c *gin.Context) {
|
||||||
|
var record models.StepTrainRecord
|
||||||
|
|
||||||
|
// 绑定并验证JSON数据
|
||||||
|
if err := c.ShouldBindJSON(&record); err != nil {
|
||||||
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 使用事务保存数据[4](@ref)
|
||||||
|
err := tc.DB.Transaction(func(tx *gorm.DB) error {
|
||||||
|
// 保存主记录
|
||||||
|
if err := tx.Clauses(clause.OnConflict{
|
||||||
|
Columns: []clause.Column{{Name: "train_id"}}, // 指定冲突的列
|
||||||
|
DoUpdates: clause.Assignments(map[string]interface{}{
|
||||||
|
"max_heart_rate": record.MaxHeartRate,
|
||||||
|
"start_time": record.StartTime,
|
||||||
|
"end_time": record.EndTime,
|
||||||
|
"duration": record.Duration,
|
||||||
|
"dead_zone": record.DeadZone,
|
||||||
|
"name": record.Name,
|
||||||
|
"evaluation": record.Evaluation,
|
||||||
|
}),
|
||||||
|
}).Omit("HeartRates", "StrideFreqs").Create(&record).Error; err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 保存关联的心率数据
|
||||||
|
for i := range record.HeartRates {
|
||||||
|
if err := tx.Clauses(
|
||||||
|
clause.OnConflict{
|
||||||
|
Columns: []clause.Column{{Name: "identifier"}}, // 指定冲突的列
|
||||||
|
DoUpdates: clause.Assignments(map[string]interface{}{"value": record.HeartRates[i].Value, "time": record.HeartRates[i].Time}),
|
||||||
|
},
|
||||||
|
).Create(&record.HeartRates[i]).Error; err != nil {
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for i := range record.StrideFreqs {
|
||||||
|
if err := tx.Clauses(
|
||||||
|
clause.OnConflict{
|
||||||
|
Columns: []clause.Column{{Name: "identifier"}}, // 指定冲突的列
|
||||||
|
DoUpdates: clause.Assignments(map[string]interface{}{"value": record.StrideFreqs[i].Value, "time": record.StrideFreqs[i].Time}),
|
||||||
|
},
|
||||||
|
).Create(&record.StrideFreqs[i]).Error; err != nil {
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusCreated, gin.H{
|
||||||
|
"message": "数据保存成功",
|
||||||
|
"id": record.TrainId,
|
||||||
|
})
|
||||||
|
}
|
||||||
@ -212,7 +212,12 @@ func convertToCurvePoints(x, y []float64) []CurvePoint {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (tc *TrainingController) heartRateAnalyze(tx *gorm.DB, record models.TrainRecord) error {
|
func (tc *TrainingController) heartRateAnalyze(tx *gorm.DB, record models.TrainRecord) error {
|
||||||
startTime := record.StartTime
|
var startTime int64
|
||||||
|
if record.TestTime > 0 {
|
||||||
|
startTime = record.TestTime
|
||||||
|
} else {
|
||||||
|
startTime = record.StartTime
|
||||||
|
}
|
||||||
|
|
||||||
// 获取所有唯一的beltID
|
// 获取所有唯一的beltID
|
||||||
var beltIDs []uint
|
var beltIDs []uint
|
||||||
@ -231,7 +236,7 @@ func (tc *TrainingController) heartRateAnalyze(tx *gorm.DB, record models.TrainR
|
|||||||
// 曲线拟合
|
// 曲线拟合
|
||||||
x := []float64{2, 4, 6}
|
x := []float64{2, 4, 6}
|
||||||
y := []float64{averages["2min"], averages["4min"], averages["6min"]}
|
y := []float64{averages["2min"], averages["4min"], averages["6min"]}
|
||||||
a, _ := quadraticFit(x, y)
|
a, b, _ := quadraticFit(x, y)
|
||||||
|
|
||||||
// 存储结果
|
// 存储结果
|
||||||
analysis := models.BeltAnalysis{
|
analysis := models.BeltAnalysis{
|
||||||
@ -242,6 +247,7 @@ func (tc *TrainingController) heartRateAnalyze(tx *gorm.DB, record models.TrainR
|
|||||||
Avg4min: averages["4min"],
|
Avg4min: averages["4min"],
|
||||||
Avg6min: averages["6min"],
|
Avg6min: averages["6min"],
|
||||||
CurveParamA: a,
|
CurveParamA: a,
|
||||||
|
CurveParamB: b,
|
||||||
}
|
}
|
||||||
if err := tx.Create(&analysis).Error; err != nil {
|
if err := tx.Create(&analysis).Error; err != nil {
|
||||||
return err
|
return err
|
||||||
@ -305,14 +311,44 @@ func calculateAverages(tx *gorm.DB, trainID uint, beltID uint, ranges map[string
|
|||||||
return averages, nil
|
return averages, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func quadraticFit(x []float64, y []float64) (float64, error) {
|
//func quadraticFit(x []float64, y []float64) (float64, error) {
|
||||||
// 使用三点计算y=ax²+b的a值(x=[2,4,6]对应分钟)
|
// // 使用三点计算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
|
||||||
|
//}
|
||||||
|
|
||||||
|
func quadraticFit(x []float64, y []float64) (float64, float64, error) {
|
||||||
|
// 校验输入长度
|
||||||
if len(x) != 3 || len(y) != 3 {
|
if len(x) != 3 || len(y) != 3 {
|
||||||
return 0, errors.New("需要三个点")
|
return 0, 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
|
var sumX4, sumX2, sumY, sumX2Y float64
|
||||||
|
for i := 0; i < 3; i++ {
|
||||||
|
xi := x[i]
|
||||||
|
xi2 := xi * xi
|
||||||
|
sumX4 += xi2 * xi2 // x^4累加
|
||||||
|
sumX2 += xi2 // x^2累加
|
||||||
|
sumY += y[i] // y累加
|
||||||
|
sumX2Y += xi2 * y[i] // x²y累加
|
||||||
|
}
|
||||||
|
|
||||||
|
// 计算行列式
|
||||||
|
determinant := sumX4*3 - sumX2*sumX2
|
||||||
|
if determinant == 0 {
|
||||||
|
return 0, 0, errors.New("无解,行列式为零")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 计算系数 a 和 b
|
||||||
|
a := (sumX2Y*3 - sumY*sumX2) / determinant
|
||||||
|
b := (sumX4*sumY - sumX2*sumX2Y) / determinant
|
||||||
|
|
||||||
|
return a, b, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type TimeRange struct {
|
type TimeRange struct {
|
||||||
|
|||||||
9
main.go
9
main.go
@ -15,7 +15,14 @@ func main() {
|
|||||||
|
|
||||||
config.DB.Debug()
|
config.DB.Debug()
|
||||||
// 自动迁移模型
|
// 自动迁移模型
|
||||||
config.DB.AutoMigrate(&models.TrainRecord{}, &models.TrainingData{}, &models.Belt{}, &models.HeartRate{}, &models.BeltAnalysis{})
|
config.DB.AutoMigrate(&models.TrainRecord{},
|
||||||
|
&models.TrainingData{},
|
||||||
|
&models.Belt{},
|
||||||
|
&models.HeartRate{},
|
||||||
|
&models.BeltAnalysis{},
|
||||||
|
&models.StepTrainRecord{},
|
||||||
|
&models.StepHeartRate{},
|
||||||
|
&models.StepStrideFreq{})
|
||||||
|
|
||||||
// 启动服务
|
// 启动服务
|
||||||
r := routes.SetupRouter()
|
r := routes.SetupRouter()
|
||||||
|
|||||||
38
models/step_train.go
Normal file
38
models/step_train.go
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
package models
|
||||||
|
|
||||||
|
import "gorm.io/gorm"
|
||||||
|
|
||||||
|
type StepStrideFreq struct {
|
||||||
|
gorm.Model
|
||||||
|
TrainId uint `gorm:"column:train_id; index" json:"trainId"` // 外键关联训练记录[4](@ref)
|
||||||
|
Time int64 `gorm:"type:bigint" json:"time"` // 保持与前端一致的毫秒时间戳[3](@ref)
|
||||||
|
Value int `gorm:"type:int" json:"value"`
|
||||||
|
PredictValue int `gorm:"type:int" json:"predictValue"`
|
||||||
|
Identifier string `gorm:"uniqueIndex;type:varchar(255)" json:"identifier"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// 对应Flutter的HeartRate结构
|
||||||
|
type StepHeartRate struct {
|
||||||
|
gorm.Model
|
||||||
|
TrainId uint `gorm:"column:train_id; index" json:"trainId"` // 外键关联训练记录[4](@ref)
|
||||||
|
Time int64 `gorm:"type:bigint" json:"time"` // 保持与前端一致的毫秒时间戳[3](@ref)
|
||||||
|
Value int `gorm:"type:int" json:"value"`
|
||||||
|
HeartRateType int `gorm:"type:int" json:"predictValue"`
|
||||||
|
Identifier string `gorm:"uniqueIndex;type:varchar(255)" json:"identifier"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// 对应Flutter的TrainRecord结构
|
||||||
|
type StepTrainRecord struct {
|
||||||
|
gorm.Model
|
||||||
|
TrainId uint `gorm:"uniqueIndex" json:"tid"` // 对应Dart的tid字段
|
||||||
|
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"` // 持续时间(秒)
|
||||||
|
DeadZone int `gorm:"type:int" json:"deadZone"`
|
||||||
|
Evaluation string `gorm:"size:50" json:"evaluation"`
|
||||||
|
HeartRates []StepHeartRate `gorm:"foreignKey:TrainId;references:TrainId" json:"HeartRates"`
|
||||||
|
StrideFreqs []StepStrideFreq `gorm:"foreignKey:TrainId;references:TrainId" json:"strideFreqs"`
|
||||||
|
}
|
||||||
@ -28,6 +28,7 @@ type BeltAnalysis struct {
|
|||||||
Avg4min float64 `gorm:"type:double precision"` // 第4分钟平均心率
|
Avg4min float64 `gorm:"type:double precision"` // 第4分钟平均心率
|
||||||
Avg6min float64 `gorm:"type:double precision"` // 第6分钟平均心率
|
Avg6min float64 `gorm:"type:double precision"` // 第6分钟平均心率
|
||||||
CurveParamA float64 `gorm:"type:double precision"` // 拟合参数a值
|
CurveParamA float64 `gorm:"type:double precision"` // 拟合参数a值
|
||||||
|
CurveParamB float64 `gorm:"type:double precision"` // 拟合参数a值
|
||||||
}
|
}
|
||||||
|
|
||||||
// 中间计算结构(无需持久化)
|
// 中间计算结构(无需持久化)
|
||||||
@ -47,9 +48,10 @@ type HeartRate struct {
|
|||||||
// 对应Flutter的TrainRecord结构
|
// 对应Flutter的TrainRecord结构
|
||||||
type TrainRecord struct {
|
type TrainRecord struct {
|
||||||
gorm.Model
|
gorm.Model
|
||||||
TrainId uint `gorm:"uniqueIndex" json:"tid"` // 对应Dart的tid字段
|
TrainId uint `gorm:"uniqueIndex" json:"tid"` // 对应Dart的tid字段
|
||||||
StartTime int64 `gorm:"type:bigint" json:"time"` // 开始时间戳
|
StartTime int64 `gorm:"type:bigint" json:"time"` // 开始时间戳
|
||||||
EndTime int64 `gorm:"type:bigint" json:"endTime"` // 结束时间戳[3](@ref)
|
TestTime int64 `gorm:"type:bigint" json:"testTime"` // 开始时间戳
|
||||||
|
EndTime int64 `gorm:"type:bigint" json:"endTime"` // 结束时间戳[3](@ref)
|
||||||
Name string `gorm:"size:100" json:"name"`
|
Name string `gorm:"size:100" json:"name"`
|
||||||
RunType string `gorm:"size:100" json:"RunType"`
|
RunType string `gorm:"size:100" json:"RunType"`
|
||||||
MaxHeartRate int `gorm:"type:int" json:"maxHeartRate"`
|
MaxHeartRate int `gorm:"type:int" json:"maxHeartRate"`
|
||||||
|
|||||||
@ -12,6 +12,7 @@ func SetupRouter() *gin.Engine {
|
|||||||
r := gin.Default()
|
r := gin.Default()
|
||||||
r.Use(middleware.GzipMiddleware())
|
r.Use(middleware.GzipMiddleware())
|
||||||
trainingController := controllers.NewTrainingController()
|
trainingController := controllers.NewTrainingController()
|
||||||
|
stepTrainController := controllers.NewStepTrainingController()
|
||||||
|
|
||||||
v1 := r.Group("/api/v1")
|
v1 := r.Group("/api/v1")
|
||||||
{
|
{
|
||||||
@ -21,6 +22,11 @@ func SetupRouter() *gin.Engine {
|
|||||||
records.GET("/analysis", trainingController.HandleCurveAnalysis)
|
records.GET("/analysis", trainingController.HandleCurveAnalysis)
|
||||||
// 可扩展其他路由:GET, PUT, DELETE等
|
// 可扩展其他路由:GET, PUT, DELETE等
|
||||||
}
|
}
|
||||||
|
steps := v1.Group("/step") //.Use(middleware.AuthMiddleware())
|
||||||
|
{
|
||||||
|
steps.POST("", stepTrainController.CreateTrainingRecord)
|
||||||
|
// 可扩展其他路由:GET, PUT, DELETE等
|
||||||
|
}
|
||||||
auth := v1.Group("/auth")
|
auth := v1.Group("/auth")
|
||||||
{
|
{
|
||||||
auth.GET("/token", func(c *gin.Context) {
|
auth.GET("/token", func(c *gin.Context) {
|
||||||
|
|||||||
Reference in New Issue
Block a user