# 04-健康调查模块 ## 目标 实现新用户健康调查功能,包括基础信息、生活习惯、病史、过敏史等信息的提交和管理。 --- ## 前置要求 - 用户认证模块已完成 - 数据模型已定义 --- ## 实施步骤 ### 步骤 1:创建健康档案 Repository 创建 `server/internal/repository/impl/health.go`: ```go package impl import ( "health-ai/internal/database" "health-ai/internal/model" ) type HealthRepository struct{} func NewHealthRepository() *HealthRepository { return &HealthRepository{} } // HealthProfile func (r *HealthRepository) CreateProfile(profile *model.HealthProfile) error { return database.DB.Create(profile).Error } func (r *HealthRepository) GetProfileByUserID(userID uint) (*model.HealthProfile, error) { var profile model.HealthProfile err := database.DB.Where("user_id = ?", userID).First(&profile).Error return &profile, err } func (r *HealthRepository) UpdateProfile(profile *model.HealthProfile) error { return database.DB.Save(profile).Error } // LifestyleInfo func (r *HealthRepository) CreateLifestyle(lifestyle *model.LifestyleInfo) error { return database.DB.Create(lifestyle).Error } func (r *HealthRepository) GetLifestyleByUserID(userID uint) (*model.LifestyleInfo, error) { var lifestyle model.LifestyleInfo err := database.DB.Where("user_id = ?", userID).First(&lifestyle).Error return &lifestyle, err } func (r *HealthRepository) UpdateLifestyle(lifestyle *model.LifestyleInfo) error { return database.DB.Save(lifestyle).Error } // MedicalHistory func (r *HealthRepository) CreateMedicalHistory(history *model.MedicalHistory) error { return database.DB.Create(history).Error } func (r *HealthRepository) GetMedicalHistories(profileID uint) ([]model.MedicalHistory, error) { var histories []model.MedicalHistory err := database.DB.Where("health_profile_id = ?", profileID).Find(&histories).Error return histories, err } func (r *HealthRepository) DeleteMedicalHistory(id uint) error { return database.DB.Delete(&model.MedicalHistory{}, id).Error } // FamilyHistory func (r *HealthRepository) CreateFamilyHistory(history *model.FamilyHistory) error { return database.DB.Create(history).Error } func (r *HealthRepository) GetFamilyHistories(profileID uint) ([]model.FamilyHistory, error) { var histories []model.FamilyHistory err := database.DB.Where("health_profile_id = ?", profileID).Find(&histories).Error return histories, err } // AllergyRecord func (r *HealthRepository) CreateAllergyRecord(record *model.AllergyRecord) error { return database.DB.Create(record).Error } func (r *HealthRepository) GetAllergyRecords(profileID uint) ([]model.AllergyRecord, error) { var records []model.AllergyRecord err := database.DB.Where("health_profile_id = ?", profileID).Find(&records).Error return records, err } ``` ### 步骤 2:创建健康调查 Service 创建 `server/internal/service/survey.go`: ```go package service import ( "health-ai/internal/model" "health-ai/internal/repository/impl" ) type SurveyService struct { healthRepo *impl.HealthRepository userRepo *impl.UserRepositoryImpl } func NewSurveyService() *SurveyService { return &SurveyService{ healthRepo: impl.NewHealthRepository(), userRepo: impl.NewUserRepository(), } } // 基础信息请求 type BasicInfoRequest struct { Name string `json:"name" binding:"required"` BirthDate string `json:"birth_date"` Gender string `json:"gender" binding:"required,oneof=male female"` Height float64 `json:"height" binding:"required"` Weight float64 `json:"weight" binding:"required"` BloodType string `json:"blood_type"` Occupation string `json:"occupation"` MaritalStatus string `json:"marital_status"` Region string `json:"region"` } // 生活习惯请求 type LifestyleRequest struct { SleepTime string `json:"sleep_time"` WakeTime string `json:"wake_time"` SleepQuality string `json:"sleep_quality"` MealRegularity string `json:"meal_regularity"` DietPreference string `json:"diet_preference"` DailyWaterML int `json:"daily_water_ml"` ExerciseFrequency string `json:"exercise_frequency"` ExerciseType string `json:"exercise_type"` ExerciseDurationMin int `json:"exercise_duration_min"` IsSmoker bool `json:"is_smoker"` AlcoholFrequency string `json:"alcohol_frequency"` } // 病史请求 type MedicalHistoryRequest struct { DiseaseName string `json:"disease_name" binding:"required"` DiseaseType string `json:"disease_type"` DiagnosedDate string `json:"diagnosed_date"` Status string `json:"status"` Notes string `json:"notes"` } // 家族病史请求 type FamilyHistoryRequest struct { Relation string `json:"relation" binding:"required"` DiseaseName string `json:"disease_name" binding:"required"` Notes string `json:"notes"` } // 过敏记录请求 type AllergyRequest struct { AllergyType string `json:"allergy_type" binding:"required"` Allergen string `json:"allergen" binding:"required"` Severity string `json:"severity"` ReactionDesc string `json:"reaction_desc"` } // 获取调查状态 func (s *SurveyService) GetStatus(userID uint) (map[string]bool, error) { status := map[string]bool{ "basic_info": false, "lifestyle": false, "medical_history": false, "family_history": false, "allergy": false, } profile, err := s.healthRepo.GetProfileByUserID(userID) if err == nil && profile.ID > 0 { status["basic_info"] = true histories, _ := s.healthRepo.GetMedicalHistories(profile.ID) status["medical_history"] = len(histories) >= 0 // 可以为空 familyHistories, _ := s.healthRepo.GetFamilyHistories(profile.ID) status["family_history"] = len(familyHistories) >= 0 allergies, _ := s.healthRepo.GetAllergyRecords(profile.ID) status["allergy"] = len(allergies) >= 0 } lifestyle, err := s.healthRepo.GetLifestyleByUserID(userID) if err == nil && lifestyle.ID > 0 { status["lifestyle"] = true } return status, nil } // 提交基础信息 func (s *SurveyService) SubmitBasicInfo(userID uint, req *BasicInfoRequest) error { // 计算 BMI heightM := req.Height / 100 bmi := req.Weight / (heightM * heightM) profile := &model.HealthProfile{ UserID: userID, Name: req.Name, Gender: req.Gender, Height: req.Height, Weight: req.Weight, BMI: bmi, BloodType: req.BloodType, Occupation: req.Occupation, MaritalStatus: req.MaritalStatus, Region: req.Region, } // 检查是否已存在 existing, _ := s.healthRepo.GetProfileByUserID(userID) if existing.ID > 0 { profile.ID = existing.ID return s.healthRepo.UpdateProfile(profile) } return s.healthRepo.CreateProfile(profile) } // 提交生活习惯 func (s *SurveyService) SubmitLifestyle(userID uint, req *LifestyleRequest) error { lifestyle := &model.LifestyleInfo{ UserID: userID, SleepTime: req.SleepTime, WakeTime: req.WakeTime, SleepQuality: req.SleepQuality, MealRegularity: req.MealRegularity, DietPreference: req.DietPreference, DailyWaterML: req.DailyWaterML, ExerciseFrequency: req.ExerciseFrequency, ExerciseType: req.ExerciseType, ExerciseDurationMin: req.ExerciseDurationMin, IsSmoker: req.IsSmoker, AlcoholFrequency: req.AlcoholFrequency, } existing, _ := s.healthRepo.GetLifestyleByUserID(userID) if existing.ID > 0 { lifestyle.ID = existing.ID return s.healthRepo.UpdateLifestyle(lifestyle) } return s.healthRepo.CreateLifestyle(lifestyle) } // 提交病史 func (s *SurveyService) SubmitMedicalHistory(userID uint, req *MedicalHistoryRequest) error { profile, err := s.healthRepo.GetProfileByUserID(userID) if err != nil { return err } history := &model.MedicalHistory{ HealthProfileID: profile.ID, DiseaseName: req.DiseaseName, DiseaseType: req.DiseaseType, DiagnosedDate: req.DiagnosedDate, Status: req.Status, Notes: req.Notes, } return s.healthRepo.CreateMedicalHistory(history) } // 提交家族病史 func (s *SurveyService) SubmitFamilyHistory(userID uint, req *FamilyHistoryRequest) error { profile, err := s.healthRepo.GetProfileByUserID(userID) if err != nil { return err } history := &model.FamilyHistory{ HealthProfileID: profile.ID, Relation: req.Relation, DiseaseName: req.DiseaseName, Notes: req.Notes, } return s.healthRepo.CreateFamilyHistory(history) } // 提交过敏信息 func (s *SurveyService) SubmitAllergy(userID uint, req *AllergyRequest) error { profile, err := s.healthRepo.GetProfileByUserID(userID) if err != nil { return err } record := &model.AllergyRecord{ HealthProfileID: profile.ID, AllergyType: req.AllergyType, Allergen: req.Allergen, Severity: req.Severity, ReactionDesc: req.ReactionDesc, } return s.healthRepo.CreateAllergyRecord(record) } // 标记调查完成 func (s *SurveyService) MarkSurveyCompleted(userID uint) error { user, err := s.userRepo.GetByID(userID) if err != nil { return err } user.SurveyCompleted = true return s.userRepo.Update(user) } ``` ### 步骤 3:创建健康调查 Handler 创建 `server/internal/api/handler/survey.go`: ```go package handler import ( "health-ai/internal/api/middleware" "health-ai/internal/service" "health-ai/pkg/response" "github.com/gin-gonic/gin" ) type SurveyHandler struct { surveyService *service.SurveyService } func NewSurveyHandler() *SurveyHandler { return &SurveyHandler{ surveyService: service.NewSurveyService(), } } func (h *SurveyHandler) GetStatus(c *gin.Context) { userID := middleware.GetUserID(c) status, err := h.surveyService.GetStatus(userID) if err != nil { response.Error(c, 500, err.Error()) return } response.Success(c, status) } func (h *SurveyHandler) SubmitBasicInfo(c *gin.Context) { userID := middleware.GetUserID(c) var req service.BasicInfoRequest if err := c.ShouldBindJSON(&req); err != nil { response.Error(c, 400, "参数错误: "+err.Error()) return } if err := h.surveyService.SubmitBasicInfo(userID, &req); err != nil { response.Error(c, 500, err.Error()) return } response.Success(c, nil) } func (h *SurveyHandler) SubmitLifestyle(c *gin.Context) { userID := middleware.GetUserID(c) var req service.LifestyleRequest if err := c.ShouldBindJSON(&req); err != nil { response.Error(c, 400, "参数错误: "+err.Error()) return } if err := h.surveyService.SubmitLifestyle(userID, &req); err != nil { response.Error(c, 500, err.Error()) return } response.Success(c, nil) } func (h *SurveyHandler) SubmitMedicalHistory(c *gin.Context) { userID := middleware.GetUserID(c) var req service.MedicalHistoryRequest if err := c.ShouldBindJSON(&req); err != nil { response.Error(c, 400, "参数错误: "+err.Error()) return } if err := h.surveyService.SubmitMedicalHistory(userID, &req); err != nil { response.Error(c, 500, err.Error()) return } response.Success(c, nil) } func (h *SurveyHandler) SubmitFamilyHistory(c *gin.Context) { userID := middleware.GetUserID(c) var req service.FamilyHistoryRequest if err := c.ShouldBindJSON(&req); err != nil { response.Error(c, 400, "参数错误: "+err.Error()) return } if err := h.surveyService.SubmitFamilyHistory(userID, &req); err != nil { response.Error(c, 500, err.Error()) return } response.Success(c, nil) } func (h *SurveyHandler) SubmitAllergy(c *gin.Context) { userID := middleware.GetUserID(c) var req service.AllergyRequest if err := c.ShouldBindJSON(&req); err != nil { response.Error(c, 400, "参数错误: "+err.Error()) return } if err := h.surveyService.SubmitAllergy(userID, &req); err != nil { response.Error(c, 500, err.Error()) return } response.Success(c, nil) } ``` ### 步骤 4:更新路由配置 在 `router.go` 中添加调查路由: ```go // 健康调查路由(需要登录) surveyHandler := handler.NewSurveyHandler() surveyGroup := apiGroup.Group("/survey") surveyGroup.Use(middleware.AuthRequired()) { surveyGroup.GET("/status", surveyHandler.GetStatus) surveyGroup.POST("/basic-info", surveyHandler.SubmitBasicInfo) surveyGroup.POST("/lifestyle", surveyHandler.SubmitLifestyle) surveyGroup.POST("/medical-history", surveyHandler.SubmitMedicalHistory) surveyGroup.POST("/family-history", surveyHandler.SubmitFamilyHistory) surveyGroup.POST("/allergy", surveyHandler.SubmitAllergy) } ``` --- ## API 接口说明 ### GET /api/survey/status 获取调查完成状态(需认证) ### POST /api/survey/basic-info 提交基础信息 ### POST /api/survey/lifestyle 提交生活习惯 ### POST /api/survey/medical-history 提交病史(可多次提交) ### POST /api/survey/family-history 提交家族病史(可多次提交) ### POST /api/survey/allergy 提交过敏信息(可多次提交) --- ## 需要创建的文件清单 | 文件路径 | 说明 | |----------|------| | `internal/repository/impl/health.go` | 健康 Repository | | `internal/service/survey.go` | 调查 Service | | `internal/api/handler/survey.go` | 调查 Handler | --- ## 验收标准 - [ ] 获取调查状态接口正常 - [ ] 基础信息提交成功,BMI 自动计算 - [ ] 生活习惯提交成功 - [ ] 病史、家族史、过敏信息可多次添加 - [ ] 所有接口需要认证 --- ## 预计耗时 30-40 分钟 --- ## 下一步 完成后进入 `02-后端开发/05-体质辨识模块.md`