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.
14 KiB
14 KiB
07-健康档案模块
目标
实现用户健康档案的查询和管理功能,提供完整的健康信息视图。
前置要求
- 健康调查模块已完成
- 体质辨识模块已完成
实施步骤
步骤 1:创建用户 Service
创建 server/internal/service/user.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:
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:
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:
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