feat: train record session.

This commit is contained in:
2026-05-02 12:52:49 +08:00
parent f81ee19bed
commit 44ad9ea7f6
4 changed files with 126 additions and 0 deletions
+121
View File
@@ -7,10 +7,15 @@ import (
)
import (
"encoding/json"
"errors"
"fmt"
"time"
"github.com/gin-gonic/gin"
"gorm.io/gorm"
"gorm.io/gorm/clause"
"gorm.io/gorm/schema"
"hr_receiver/config"
"hr_receiver/models"
"math"
@@ -97,6 +102,122 @@ func (tc *TrainingController) CreateTrainingRecord(c *gin.Context) {
})
}
type trainingSessionRequest struct {
Tid int `json:"tid"`
Time int64 `json:"time"`
TestTime int64 `json:"testTime"`
EndTime int64 `json:"endTime"`
Name string `json:"name"`
RunType string `json:"runType"`
Gender string `json:"gender"`
Age int `json:"age"`
MaxHeartRate int `json:"maxHeartRate"`
Duration int `json:"duration"`
PeopleNum int `json:"peopleNum"`
Evaluation string `json:"evaluation"`
AiResult string `json:"aiResult"`
IsStart bool `json:"isStart"`
RegionID uint32 `json:"regionId"`
AppName string `json:"appName"`
}
func (tc *TrainingController) UploadTrainingSession(c *gin.Context) {
var req trainingSessionRequest
if err := c.ShouldBindJSON(&req); err != nil {
writeError(c, http.StatusBadRequest, err.Error())
return
}
trainID := fmt.Sprintf("%d", req.Time)
now := time.Now().UnixMilli()
flavorType := "heartrate"
identifier := schema.NamingStrategy{}.IndexName(
"mqtt_training_session",
fmt.Sprintf("%s_%d_%s", flavorType, req.RegionID, trainID),
)
rawPayload, _ := json.Marshal(req)
if req.IsStart {
record := &models.MqttTrainingSessionRecord{
Identifier: identifier,
TestID: trainID,
EventType: "start_test",
RegionID: req.RegionID,
FlavorType: flavorType,
RawFlavor: "hr",
AppName: req.AppName,
TrainId: trainID,
PeopleNum: req.PeopleNum,
StartedAt: &req.Time,
PublishedAt: now,
ReceivedAt: now,
RawPayload: string(rawPayload),
}
err := tc.DB.Clauses(clause.OnConflict{
Columns: []clause.Column{{Name: "identifier"}},
DoUpdates: clause.Assignments(map[string]interface{}{"started_at": *record.StartedAt, "train_id": record.TrainId, "updated_at": time.Now()}),
}).Create(record).Error
if err != nil {
writeError(c, http.StatusInternalServerError, "failed to save session")
return
}
writeSuccess(c, http.StatusOK, "session start registered", nil)
return
}
var existing models.MqttTrainingSessionRecord
err := tc.DB.Where("train_id = ?", trainID).First(&existing).Error
if err != nil {
record := &models.MqttTrainingSessionRecord{
Identifier: identifier,
TestID: trainID,
EventType: "stop_test",
RegionID: req.RegionID,
FlavorType: flavorType,
RawFlavor: "hr",
AppName: req.AppName,
TrainId: trainID,
PeopleNum: req.PeopleNum,
StartedAt: &req.Time,
EndedAt: &req.EndTime,
PublishedAt: now,
ReceivedAt: now,
RawPayload: string(rawPayload),
}
err = tc.DB.Clauses(clause.OnConflict{
Columns: []clause.Column{{Name: "identifier"}},
DoUpdates: clause.Assignments(map[string]interface{}{
"ended_at": *record.EndedAt,
"people_num": record.PeopleNum,
"event_type": record.EventType,
"updated_at": time.Now(),
}),
}).Create(record).Error
if err != nil {
writeError(c, http.StatusInternalServerError, "failed to create session")
return
}
writeSuccess(c, http.StatusOK, "session created", nil)
return
}
updates := map[string]interface{}{
"event_type": "stop_test",
"ended_at": req.EndTime,
"people_num": req.PeopleNum,
"received_at": now,
"raw_payload": string(rawPayload),
"updated_at": time.Now(),
}
if err := tc.DB.Model(&existing).Updates(updates).Error; err != nil {
writeError(c, http.StatusInternalServerError, "failed to update session")
return
}
writeSuccess(c, http.StatusOK, "session updated", nil)
}
// analysis_response.go
type AnalysisResponse struct {
Status string `json:"status"` // 状态码
+2
View File
@@ -66,6 +66,8 @@ type MqttTrainingSessionRecord struct {
FlavorType string `gorm:"size:64;index" json:"flavorType"`
RawFlavor string `gorm:"size:64" json:"rawFlavor"`
AppName string `gorm:"size:255" json:"appName"`
TrainId string `gorm:"size:255;index" json:"trainId"`
PeopleNum int `gorm:"type:int" json:"peopleNum"`
StartedAt *int64 `gorm:"type:bigint;index" json:"startedAt"`
EndedAt *int64 `gorm:"type:bigint;index" json:"endedAt"`
PublishedAt int64 `gorm:"type:bigint;index" json:"publishedAt"`
+2
View File
@@ -34,6 +34,7 @@ type trainingSessionPayload struct {
Type string `json:"type"`
EventType string `json:"eventType"`
TestID string `json:"testId"`
TrainId string `json:"trainId"`
RegionID string `json:"regionId"`
Flavor string `json:"flavor"`
AppName string `json:"appName"`
@@ -400,6 +401,7 @@ func buildTrainingSessionRecord(topic string, payload []byte, now int64) (*model
FlavorType: flavorType,
RawFlavor: event.Flavor,
AppName: event.AppName,
TrainId: event.TrainId,
PublishedAt: publishedAt,
ReceivedAt: now,
RawPayload: string(payload),
+1
View File
@@ -51,6 +51,7 @@ func SetupRouter() *gin.Engine {
records := v1.Group("/train-records") //.Use(middleware.AuthMiddleware())
{
records.POST("", trainingController.CreateTrainingRecord)
records.POST("/session", trainingController.UploadTrainingSession)
records.GET("/analysis", trainingController.HandleCurveAnalysis)
records.POST("/analysis-by-ai", trainingController.AnalyzeByAI)
// 可扩展其他路由GET, PUT, DELETE等