package handler import ( "errors" "strconv" "with_you/internal/dto" "with_you/internal/model" "with_you/internal/pkg/response" "with_you/internal/service" "github.com/gin-gonic/gin" ) // MaterialHandler 学习资料处理器 type MaterialHandler struct { materialService service.MaterialService } // NewMaterialHandler 创建学习资料处理器 func NewMaterialHandler(materialService service.MaterialService) *MaterialHandler { return &MaterialHandler{materialService: materialService} } // ===================================================== // 响应结构体 // ===================================================== type subjectResponse struct { ID string `json:"id"` Name string `json:"name"` Icon string `json:"icon"` Color string `json:"color"` Description string `json:"description,omitempty"` SortOrder int `json:"sort_order"` IsActive bool `json:"is_active"` MaterialCount int `json:"material_count"` CreatedAt string `json:"created_at"` UpdatedAt string `json:"updated_at"` } type materialResponse struct { ID string `json:"id"` SubjectID string `json:"subject_id"` Title string `json:"title"` Description string `json:"description,omitempty"` FileType string `json:"file_type"` FileSize int64 `json:"file_size"` FileURL string `json:"file_url"` FileName string `json:"file_name"` DownloadCount int `json:"download_count"` AuthorID string `json:"author_id,omitempty"` Tags []string `json:"tags,omitempty"` Status string `json:"status"` CreatedAt string `json:"created_at"` UpdatedAt string `json:"updated_at"` Subject *subjectResponse `json:"subject,omitempty"` } // ===================================================== // 转换函数 // ===================================================== func toSubjectResponse(s *model.MaterialSubject, materialCount int64) *subjectResponse { if s == nil { return nil } return &subjectResponse{ ID: s.ID, Name: s.Name, Icon: s.Icon, Color: s.Color, Description: s.Description, SortOrder: s.SortOrder, IsActive: s.IsActive, MaterialCount: int(materialCount), CreatedAt: s.CreatedAt.Format("2006-01-02T15:04:05Z07:00"), UpdatedAt: s.UpdatedAt.Format("2006-01-02T15:04:05Z07:00"), } } func toMaterialResponse(f *model.MaterialFile) *materialResponse { if f == nil { return nil } var subjectResp *subjectResponse if f.Subject != nil { subjectResp = toSubjectResponse(f.Subject, 0) } return &materialResponse{ ID: f.ID, SubjectID: f.SubjectID, Title: f.Title, Description: f.Description, FileType: string(f.FileType), FileSize: f.FileSize, FileURL: f.FileURL, FileName: f.FileName, DownloadCount: f.DownloadCount, AuthorID: f.AuthorID, Tags: f.Tags, Status: string(f.Status), CreatedAt: f.CreatedAt.Format("2006-01-02T15:04:05Z07:00"), UpdatedAt: f.UpdatedAt.Format("2006-01-02T15:04:05Z07:00"), Subject: subjectResp, } } // ===================================================== // 公开API - 学科相关 // ===================================================== // ListSubjects 公开学科列表(仅启用) func (h *MaterialHandler) ListSubjects(c *gin.Context) { items, err := h.materialService.ListActiveSubjects() if err != nil { response.InternalServerError(c, "failed to get subjects") return } result := make([]*subjectResponse, 0, len(items)) for _, item := range items { _, count, _ := h.materialService.GetSubjectByIDWithMaterialCount(item.ID) result = append(result, toSubjectResponse(item, count)) } response.Success(c, gin.H{"list": result}) } // GetSubject 获取学科详情 func (h *MaterialHandler) GetSubject(c *gin.Context) { id := c.Param("id") item, count, err := h.materialService.GetSubjectByIDWithMaterialCount(id) if err != nil { response.NotFound(c, "subject not found") return } response.Success(c, toSubjectResponse(item, count)) } // ===================================================== // 公开API - 资料相关 // ===================================================== // ListMaterials 资料列表 func (h *MaterialHandler) ListMaterials(c *gin.Context) { page, _ := strconv.Atoi(c.DefaultQuery("page", "1")) pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "20")) subjectID := c.Query("subject_id") fileType := c.Query("file_type") keyword := c.Query("keyword") params := dto.MaterialFileQueryParams{ SubjectID: subjectID, FileType: fileType, Keyword: keyword, Status: string(model.MaterialStatusActive), Page: page, PageSize: pageSize, SortBy: "created_at", SortOrder: "DESC", } items, total, err := h.materialService.ListMaterials(params) if err != nil { response.InternalServerError(c, "failed to get materials") return } result := make([]*materialResponse, 0, len(items)) for _, item := range items { result = append(result, toMaterialResponse(item)) } response.Success(c, gin.H{ "list": result, "total": total, "page": page, "page_size": pageSize, "has_more": int64(page*pageSize) < total, }) } // GetMaterial 获取资料详情 func (h *MaterialHandler) GetMaterial(c *gin.Context) { id := c.Param("id") item, err := h.materialService.GetMaterialByIDWithSubject(id) if err != nil { response.NotFound(c, "material not found") return } response.Success(c, toMaterialResponse(item)) } // SearchMaterials 搜索资料 func (h *MaterialHandler) SearchMaterials(c *gin.Context) { keyword := c.Query("keyword") page, _ := strconv.Atoi(c.DefaultQuery("page", "1")) pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "20")) items, total, err := h.materialService.SearchMaterials(keyword, page, pageSize) if err != nil { response.InternalServerError(c, "failed to search materials") return } result := make([]*materialResponse, 0, len(items)) for _, item := range items { result = append(result, toMaterialResponse(item)) } response.Success(c, gin.H{ "list": result, "total": total, "page": page, "page_size": pageSize, "has_more": int64(page*pageSize) < total, }) } // DownloadMaterial 下载资料(增加下载次数) func (h *MaterialHandler) DownloadMaterial(c *gin.Context) { id := c.Param("id") item, err := h.materialService.GetMaterialByID(id) if err != nil { response.NotFound(c, "material not found") return } // 增加下载次数 _ = h.materialService.IncrementDownloadCount(id) // 返回下载链接 response.Success(c, gin.H{ "id": item.ID, "file_url": item.FileURL, "file_name": item.FileName, }) } // ===================================================== // 管理API - 学科相关 // ===================================================== // AdminListSubjects 管理端学科列表(全部) func (h *MaterialHandler) AdminListSubjects(c *gin.Context) { items, err := h.materialService.ListAllSubjects() if err != nil { response.InternalServerError(c, "failed to get subjects") return } result := make([]*subjectResponse, 0, len(items)) for _, item := range items { _, count, _ := h.materialService.GetSubjectByIDWithMaterialCount(item.ID) result = append(result, toSubjectResponse(item, count)) } response.Success(c, gin.H{"list": result}) } type adminSubjectUpsertRequest struct { Name string `json:"name" binding:"required,max=100"` Icon string `json:"icon" binding:"max=50"` Color string `json:"color" binding:"max=20"` Description string `json:"description" binding:"max=255"` SortOrder int `json:"sort_order"` IsActive *bool `json:"is_active"` } // AdminCreateSubject 创建学科 func (h *MaterialHandler) AdminCreateSubject(c *gin.Context) { var req adminSubjectUpsertRequest if err := c.ShouldBindJSON(&req); err != nil { response.BadRequest(c, err.Error()) return } isActive := true if req.IsActive != nil { isActive = *req.IsActive } item, err := h.materialService.CreateSubject(req.Name, req.Icon, req.Color, req.Description, req.SortOrder, isActive) if err != nil { response.InternalServerError(c, "failed to create subject") return } response.Success(c, toSubjectResponse(item, 0)) } // AdminUpdateSubject 更新学科 func (h *MaterialHandler) AdminUpdateSubject(c *gin.Context) { id := c.Param("id") var req adminSubjectUpsertRequest if err := c.ShouldBindJSON(&req); err != nil { response.BadRequest(c, err.Error()) return } isActive := true if req.IsActive != nil { isActive = *req.IsActive } item, err := h.materialService.UpdateSubject(id, req.Name, req.Icon, req.Color, req.Description, req.SortOrder, isActive) if err != nil { response.InternalServerError(c, "failed to update subject") return } _, count, _ := h.materialService.GetSubjectByIDWithMaterialCount(item.ID) response.Success(c, toSubjectResponse(item, count)) } // AdminDeleteSubject 删除学科 func (h *MaterialHandler) AdminDeleteSubject(c *gin.Context) { id := c.Param("id") if err := h.materialService.DeleteSubject(id); err != nil { if errors.Is(err, service.ErrSubjectHasMaterials) { response.BadRequest(c, "学科下存在资料,无法删除") return } response.InternalServerError(c, "failed to delete subject") return } response.Success(c, gin.H{"success": true}) } // ===================================================== // 管理API - 资料相关 // ===================================================== // AdminListMaterials 管理端资料列表 func (h *MaterialHandler) AdminListMaterials(c *gin.Context) { page, _ := strconv.Atoi(c.DefaultQuery("page", "1")) pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "20")) subjectID := c.Query("subject_id") fileType := c.Query("file_type") status := c.Query("status") keyword := c.Query("keyword") params := dto.MaterialFileQueryParams{ SubjectID: subjectID, FileType: fileType, Status: status, Keyword: keyword, Page: page, PageSize: pageSize, SortBy: "created_at", SortOrder: "DESC", } items, total, err := h.materialService.ListMaterials(params) if err != nil { response.InternalServerError(c, "failed to get materials") return } result := make([]*materialResponse, 0, len(items)) for _, item := range items { result = append(result, toMaterialResponse(item)) } response.Success(c, gin.H{ "list": result, "total": total, "page": page, "page_size": pageSize, "total_pages": (total + int64(pageSize) - 1) / int64(pageSize), }) } type adminMaterialCreateRequest struct { SubjectID string `json:"subject_id" binding:"required"` Title string `json:"title" binding:"required,max=200"` Description string `json:"description"` FileType string `json:"file_type" binding:"required,oneof=pdf word ppt"` FileSize int64 `json:"file_size"` FileURL string `json:"file_url" binding:"required"` FileName string `json:"file_name"` Tags []string `json:"tags"` } type adminMaterialUpdateRequest struct { SubjectID string `json:"subject_id"` Title string `json:"title" binding:"max=200"` Description string `json:"description"` FileType string `json:"file_type" binding:"omitempty,oneof=pdf word ppt"` FileSize int64 `json:"file_size"` FileURL string `json:"file_url"` FileName string `json:"file_name"` Tags []string `json:"tags"` Status string `json:"status" binding:"omitempty,oneof=active inactive"` } // AdminGetMaterial 管理端获取资料详情 func (h *MaterialHandler) AdminGetMaterial(c *gin.Context) { id := c.Param("id") item, err := h.materialService.GetMaterialByIDWithSubject(id) if err != nil { response.NotFound(c, "material not found") return } response.Success(c, toMaterialResponse(item)) } // AdminCreateMaterial 创建资料 func (h *MaterialHandler) AdminCreateMaterial(c *gin.Context) { var req adminMaterialCreateRequest if err := c.ShouldBindJSON(&req); err != nil { response.BadRequest(c, err.Error()) return } // 获取当前用户ID作为作者 userID, exists := c.Get("user_id") var authorID string if exists { authorID = userID.(string) } file := &model.MaterialFile{ SubjectID: req.SubjectID, Title: req.Title, Description: req.Description, FileType: model.MaterialFileType(req.FileType), FileSize: req.FileSize, FileURL: req.FileURL, FileName: req.FileName, Tags: req.Tags, Status: model.MaterialStatusActive, AuthorID: authorID, } if err := h.materialService.CreateMaterial(file); err != nil { response.InternalServerError(c, "failed to create material") return } response.Success(c, toMaterialResponse(file)) } // AdminUpdateMaterial 更新资料 func (h *MaterialHandler) AdminUpdateMaterial(c *gin.Context) { id := c.Param("id") var req adminMaterialUpdateRequest if err := c.ShouldBindJSON(&req); err != nil { response.BadRequest(c, err.Error()) return } file, err := h.materialService.GetMaterialByID(id) if err != nil { response.NotFound(c, "material not found") return } // 更新字段 if req.SubjectID != "" { file.SubjectID = req.SubjectID } if req.Title != "" { file.Title = req.Title } if req.Description != "" { file.Description = req.Description } if req.FileType != "" { file.FileType = model.MaterialFileType(req.FileType) } if req.FileSize > 0 { file.FileSize = req.FileSize } if req.FileURL != "" { file.FileURL = req.FileURL } if req.FileName != "" { file.FileName = req.FileName } if req.Tags != nil { file.Tags = req.Tags } if req.Status != "" { file.Status = model.MaterialStatus(req.Status) } if err := h.materialService.UpdateMaterial(file); err != nil { response.InternalServerError(c, "failed to update material") return } response.Success(c, toMaterialResponse(file)) } // AdminDeleteMaterial 删除资料 func (h *MaterialHandler) AdminDeleteMaterial(c *gin.Context) { id := c.Param("id") if err := h.materialService.DeleteMaterial(id); err != nil { response.InternalServerError(c, "failed to delete material") return } response.Success(c, gin.H{"success": true}) }