healthapp
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

13 KiB

04-健康调查模块

目标

实现新用户健康调查功能,包括基础信息、生活习惯、病史、过敏史等信息的提交和管理。


前置要求

  • 用户认证模块已完成
  • 数据模型已定义

实施步骤

步骤 1:创建健康档案 Repository

创建 server/internal/repository/impl/health.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

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

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 中添加调查路由:

// 健康调查路由(需要登录)
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