- Refactored repository structures to use interfaces instead of concrete types, enhancing flexibility and testability. - Updated various repository methods to accept interfaces, allowing for better dependency management. - Modified wire generation to accommodate new repository interfaces, ensuring proper service injection throughout the application. - Enhanced handler methods to utilize DTOs for filtering and data handling, improving code clarity and maintainability.
491 lines
14 KiB
Go
491 lines
14 KiB
Go
package handler
|
|
|
|
import (
|
|
"strconv"
|
|
|
|
"carrot_bbs/internal/dto"
|
|
"carrot_bbs/internal/model"
|
|
"carrot_bbs/internal/pkg/response"
|
|
"carrot_bbs/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 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})
|
|
}
|