# 07-健康档案模块 ## 目标 实现用户健康档案的查询和管理功能,提供完整的健康信息视图。 --- ## 前置要求 - 健康调查模块已完成 - 体质辨识模块已完成 --- ## 实施步骤 ### 步骤 1:创建用户 Service 创建 `server/internal/service/user.go`: ```go package service import ( "health-ai/internal/model" "health-ai/internal/repository/impl" ) type UserService struct { userRepo *impl.UserRepositoryImpl healthRepo *impl.HealthRepository constitutionRepo *impl.ConstitutionRepository } func NewUserService() *UserService { return &UserService{ userRepo: impl.NewUserRepository(), healthRepo: impl.NewHealthRepository(), constitutionRepo: impl.NewConstitutionRepository(), } } // 用户资料响应 type UserProfileResponse struct { ID uint `json:"id"` Phone string `json:"phone"` Email string `json:"email"` Nickname string `json:"nickname"` Avatar string `json:"avatar"` SurveyCompleted bool `json:"survey_completed"` } // 获取用户资料 func (s *UserService) GetProfile(userID uint) (*UserProfileResponse, error) { user, err := s.userRepo.GetByID(userID) if err != nil { return nil, err } return &UserProfileResponse{ ID: user.ID, Phone: user.Phone, Email: user.Email, Nickname: user.Nickname, Avatar: user.Avatar, SurveyCompleted: user.SurveyCompleted, }, nil } // 更新用户资料请求 type UpdateProfileRequest struct { Nickname string `json:"nickname"` Avatar string `json:"avatar"` Email string `json:"email"` } // 更新用户资料 func (s *UserService) UpdateProfile(userID uint, req *UpdateProfileRequest) error { user, err := s.userRepo.GetByID(userID) if err != nil { return err } if req.Nickname != "" { user.Nickname = req.Nickname } if req.Avatar != "" { user.Avatar = req.Avatar } if req.Email != "" { user.Email = req.Email } return s.userRepo.Update(user) } // 完整健康档案响应 type FullHealthProfileResponse struct { BasicInfo *model.HealthProfile `json:"basic_info"` Lifestyle *model.LifestyleInfo `json:"lifestyle"` MedicalHistory []model.MedicalHistory `json:"medical_history"` FamilyHistory []model.FamilyHistory `json:"family_history"` AllergyRecords []model.AllergyRecord `json:"allergy_records"` Constitution *ConstitutionSummary `json:"constitution"` } type ConstitutionSummary struct { PrimaryType string `json:"primary_type"` PrimaryName string `json:"primary_name"` PrimaryDescription string `json:"primary_description"` AssessedAt string `json:"assessed_at"` } // 获取完整健康档案 func (s *UserService) GetFullHealthProfile(userID uint) (*FullHealthProfileResponse, error) { response := &FullHealthProfileResponse{} // 基础信息 profile, err := s.healthRepo.GetProfileByUserID(userID) if err == nil && profile.ID > 0 { response.BasicInfo = profile // 病史 histories, _ := s.healthRepo.GetMedicalHistories(profile.ID) response.MedicalHistory = histories // 家族史 familyHistories, _ := s.healthRepo.GetFamilyHistories(profile.ID) response.FamilyHistory = familyHistories // 过敏记录 allergies, _ := s.healthRepo.GetAllergyRecords(profile.ID) response.AllergyRecords = allergies } // 生活习惯 lifestyle, err := s.healthRepo.GetLifestyleByUserID(userID) if err == nil && lifestyle.ID > 0 { response.Lifestyle = lifestyle } // 体质信息 constitution, err := s.constitutionRepo.GetLatestAssessment(userID) if err == nil && constitution.ID > 0 { response.Constitution = &ConstitutionSummary{ PrimaryType: constitution.PrimaryConstitution, PrimaryName: model.ConstitutionNames[constitution.PrimaryConstitution], PrimaryDescription: model.ConstitutionDescriptions[constitution.PrimaryConstitution], AssessedAt: constitution.AssessedAt.Format("2006-01-02"), } } return response, nil } // 更新健康档案基础信息 func (s *UserService) UpdateHealthProfile(userID uint, req *BasicInfoRequest) error { return NewSurveyService().SubmitBasicInfo(userID, req) } // 更新生活习惯 func (s *UserService) UpdateLifestyle(userID uint, req *LifestyleRequest) error { return NewSurveyService().SubmitLifestyle(userID, req) } ``` ### 步骤 2:创建用户 Handler 创建 `server/internal/api/handler/user.go`: ```go package handler import ( "health-ai/internal/api/middleware" "health-ai/internal/service" "health-ai/pkg/response" "github.com/gin-gonic/gin" ) type UserHandler struct { userService *service.UserService } func NewUserHandler() *UserHandler { return &UserHandler{ userService: service.NewUserService(), } } // 获取用户资料 func (h *UserHandler) GetProfile(c *gin.Context) { userID := middleware.GetUserID(c) profile, err := h.userService.GetProfile(userID) if err != nil { response.Error(c, 500, err.Error()) return } response.Success(c, profile) } // 更新用户资料 func (h *UserHandler) UpdateProfile(c *gin.Context) { userID := middleware.GetUserID(c) var req service.UpdateProfileRequest if err := c.ShouldBindJSON(&req); err != nil { response.Error(c, 400, "参数错误: "+err.Error()) return } if err := h.userService.UpdateProfile(userID, &req); err != nil { response.Error(c, 500, err.Error()) return } response.Success(c, nil) } // 获取完整健康档案 func (h *UserHandler) GetHealthProfile(c *gin.Context) { userID := middleware.GetUserID(c) profile, err := h.userService.GetFullHealthProfile(userID) if err != nil { response.Error(c, 500, err.Error()) return } response.Success(c, profile) } // 更新健康档案基础信息 func (h *UserHandler) UpdateHealthProfile(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.userService.UpdateHealthProfile(userID, &req); err != nil { response.Error(c, 500, err.Error()) return } response.Success(c, nil) } // 获取生活习惯 func (h *UserHandler) GetLifestyle(c *gin.Context) { userID := middleware.GetUserID(c) profile, err := h.userService.GetFullHealthProfile(userID) if err != nil { response.Error(c, 500, err.Error()) return } response.Success(c, profile.Lifestyle) } // 更新生活习惯 func (h *UserHandler) UpdateLifestyle(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.userService.UpdateLifestyle(userID, &req); err != nil { response.Error(c, 500, err.Error()) return } response.Success(c, nil) } ``` ### 步骤 3:更新完整路由配置 更新 `server/internal/api/router.go`: ```go package api import ( "health-ai/internal/api/handler" "health-ai/internal/api/middleware" "github.com/gin-contrib/cors" "github.com/gin-gonic/gin" ) func SetupRouter(mode string) *gin.Engine { gin.SetMode(mode) r := gin.Default() // 跨域配置 r.Use(cors.New(cors.Config{ AllowOrigins: []string{"*"}, AllowMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"}, AllowHeaders: []string{"Origin", "Content-Type", "Authorization"}, AllowCredentials: true, })) // 健康检查 r.GET("/health", func(c *gin.Context) { c.JSON(200, gin.H{"status": "ok"}) }) // API 路由组 api := r.Group("/api") { // ========== 公开接口(无需登录)========== authHandler := handler.NewAuthHandler() auth := api.Group("/auth") { auth.POST("/register", authHandler.Register) auth.POST("/login", authHandler.Login) } // ========== 需要认证的接口 ========== authorized := api.Group("") authorized.Use(middleware.AuthRequired()) { // 用户接口 userHandler := handler.NewUserHandler() user := authorized.Group("/user") { user.GET("/profile", userHandler.GetProfile) user.PUT("/profile", userHandler.UpdateProfile) user.GET("/health-profile", userHandler.GetHealthProfile) user.PUT("/health-profile", userHandler.UpdateHealthProfile) user.GET("/lifestyle", userHandler.GetLifestyle) user.PUT("/lifestyle", userHandler.UpdateLifestyle) } // 健康调查接口 surveyHandler := handler.NewSurveyHandler() survey := authorized.Group("/survey") { survey.GET("/status", surveyHandler.GetStatus) survey.POST("/basic-info", surveyHandler.SubmitBasicInfo) survey.POST("/lifestyle", surveyHandler.SubmitLifestyle) survey.POST("/medical-history", surveyHandler.SubmitMedicalHistory) survey.POST("/family-history", surveyHandler.SubmitFamilyHistory) survey.POST("/allergy", surveyHandler.SubmitAllergy) } // 体质辨识接口 constitutionHandler := handler.NewConstitutionHandler() constitution := authorized.Group("/constitution") { constitution.GET("/questions", constitutionHandler.GetQuestions) constitution.POST("/submit", constitutionHandler.Submit) constitution.GET("/result", constitutionHandler.GetResult) constitution.GET("/history", constitutionHandler.GetHistory) constitution.GET("/recommendations", constitutionHandler.GetRecommendations) } // 对话接口 convHandler := handler.NewConversationHandler() conversations := authorized.Group("/conversations") { conversations.GET("", convHandler.GetConversations) conversations.POST("", convHandler.CreateConversation) conversations.GET("/:id", convHandler.GetConversation) conversations.DELETE("/:id", convHandler.DeleteConversation) conversations.POST("/:id/messages", convHandler.SendMessage) } } } return r } ``` ### 步骤 4:更新主程序完整版 更新 `server/cmd/server/main.go`: ```go package main import ( "fmt" "log" "health-ai/internal/api" "health-ai/internal/config" "health-ai/internal/database" "health-ai/internal/model" "health-ai/pkg/jwt" ) func main() { // 加载配置 if err := config.LoadConfig("config.yaml"); err != nil { log.Fatalf("Failed to load config: %v", err) } log.Println("✓ Config loaded") // 初始化数据库 if err := database.InitDatabase(&config.AppConfig.Database); err != nil { log.Fatalf("Failed to init database: %v", err) } log.Println("✓ Database connected") // 自动迁移 if err := database.AutoMigrate(model.AllModels()...); err != nil { log.Fatalf("Failed to migrate: %v", err) } log.Println("✓ Database migrated") // 初始化问卷题库 if err := database.SeedQuestionBank(); err != nil { log.Fatalf("Failed to seed question bank: %v", err) } log.Println("✓ Question bank seeded") // 初始化 JWT jwt.Init(config.AppConfig.JWT.Secret, config.AppConfig.JWT.ExpireHours) log.Println("✓ JWT initialized") // 启动服务器 router := api.SetupRouter(config.AppConfig.Server.Mode) addr := fmt.Sprintf(":%d", config.AppConfig.Server.Port) log.Printf("✓ Server running on http://localhost%s", addr) log.Println("========================================") log.Println("Health AI Server Ready!") log.Println("========================================") if err := router.Run(addr); err != nil { log.Fatalf("Failed to start server: %v", err) } } ``` --- ## API 接口说明 ### GET /api/user/profile 获取用户基本资料 ### PUT /api/user/profile 更新用户基本资料(昵称、头像、邮箱) ### GET /api/user/health-profile 获取完整健康档案(含基础信息、生活习惯、病史、体质) ### PUT /api/user/health-profile 更新健康档案基础信息 ### GET /api/user/lifestyle 获取生活习惯信息 ### PUT /api/user/lifestyle 更新生活习惯信息 --- ## 需要创建的文件清单 | 文件路径 | 说明 | |----------|------| | `internal/service/user.go` | 用户 Service | | `internal/api/handler/user.go` | 用户 Handler | | 更新 `internal/api/router.go` | 完整路由配置 | | 更新 `cmd/server/main.go` | 完整主程序 | --- ## 完整 API 列表汇总 | 方法 | 路径 | 说明 | 认证 | |------|------|------|------| | GET | /health | 健康检查 | 否 | | POST | /api/auth/register | 用户注册 | 否 | | POST | /api/auth/login | 用户登录 | 否 | | GET | /api/user/profile | 获取用户资料 | 是 | | PUT | /api/user/profile | 更新用户资料 | 是 | | GET | /api/user/health-profile | 获取健康档案 | 是 | | PUT | /api/user/health-profile | 更新健康档案 | 是 | | GET | /api/user/lifestyle | 获取生活习惯 | 是 | | PUT | /api/user/lifestyle | 更新生活习惯 | 是 | | 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 | 提交过敏信息 | 是 | | GET | /api/constitution/questions | 获取问卷题目 | 是 | | POST | /api/constitution/submit | 提交问卷 | 是 | | GET | /api/constitution/result | 获取体质结果 | 是 | | GET | /api/constitution/history | 获取测评历史 | 是 | | GET | /api/conversations | 获取对话列表 | 是 | | POST | /api/conversations | 创建对话 | 是 | | GET | /api/conversations/:id | 获取对话详情 | 是 | | DELETE | /api/conversations/:id | 删除对话 | 是 | | POST | /api/conversations/:id/messages | 发送消息 | 是 | --- ## 验收标准 - [ ] 获取用户资料正常 - [ ] 获取完整健康档案正常 - [ ] 更新资料和生活习惯正常 - [ ] 所有 API 接口可正常调用 - [ ] 服务器启动日志完整 --- ## 预计耗时 30-40 分钟 --- ## 下一步 后端开发完成!进入 `03-Web前端开发/01-项目结构初始化.md`