package handlers import ( "fmt" "strconv" "github.com/gin-gonic/gin" "go.uber.org/zap" "tyapi-server/internal/application/product" "tyapi-server/internal/shared/interfaces" ) // UIComponentHandler UI组件HTTP处理器 type UIComponentHandler struct { uiComponentAppService product.UIComponentApplicationService responseBuilder interfaces.ResponseBuilder validator interfaces.RequestValidator logger *zap.Logger } // NewUIComponentHandler 创建UI组件HTTP处理器 func NewUIComponentHandler( uiComponentAppService product.UIComponentApplicationService, responseBuilder interfaces.ResponseBuilder, validator interfaces.RequestValidator, logger *zap.Logger, ) *UIComponentHandler { return &UIComponentHandler{ uiComponentAppService: uiComponentAppService, responseBuilder: responseBuilder, validator: validator, logger: logger, } } // CreateUIComponent 创建UI组件 // @Summary 创建UI组件 // @Description 管理员创建新的UI组件 // @Tags UI组件管理 // @Accept json // @Produce json // @Param request body product.CreateUIComponentRequest true "创建UI组件请求" // @Success 200 {object} interfaces.Response{data=entities.UIComponent} "创建成功" // @Failure 400 {object} interfaces.Response "请求参数错误" // @Failure 500 {object} interfaces.Response "服务器内部错误" // @Router /api/v1/admin/ui-components [post] func (h *UIComponentHandler) CreateUIComponent(c *gin.Context) { var req product.CreateUIComponentRequest // 一次性读取请求体并绑定到结构体 if err := c.ShouldBindJSON(&req); err != nil { h.logger.Error("验证创建UI组件请求失败", zap.Error(err)) h.responseBuilder.BadRequest(c, fmt.Sprintf("请求参数错误: %v", err)) return } // 使用结构体数据记录日志 h.logger.Info("创建UI组件请求数据", zap.String("component_code", req.ComponentCode), zap.String("component_name", req.ComponentName), zap.String("description", req.Description), zap.String("version", req.Version), zap.Bool("is_active", req.IsActive), zap.Int("sort_order", req.SortOrder)) component, err := h.uiComponentAppService.CreateUIComponent(c.Request.Context(), req) if err != nil { h.logger.Error("创建UI组件失败", zap.Error(err), zap.String("component_code", req.ComponentCode)) if err == product.ErrComponentCodeAlreadyExists { h.responseBuilder.BadRequest(c, "UI组件编码已存在") return } h.responseBuilder.InternalError(c, fmt.Sprintf("创建UI组件失败: %v", err)) return } h.responseBuilder.Success(c, component) } // CreateUIComponentWithFile 创建UI组件并上传文件 // @Summary 创建UI组件并上传文件 // @Description 管理员创建新的UI组件并同时上传文件 // @Tags UI组件管理 // @Accept multipart/form-data // @Produce json // @Param component_code formData string true "组件编码" // @Param component_name formData string true "组件名称" // @Param description formData string false "组件描述" // @Param version formData string false "组件版本" // @Param is_active formData bool false "是否启用" // @Param sort_order formData int false "排序" // @Param file formData file true "组件文件" // @Success 200 {object} interfaces.Response{data=entities.UIComponent} "创建成功" // @Failure 400 {object} interfaces.Response "请求参数错误" // @Failure 500 {object} interfaces.Response "服务器内部错误" // @Router /api/v1/admin/ui-components/create-with-file [post] func (h *UIComponentHandler) CreateUIComponentWithFile(c *gin.Context) { // 创建请求结构体 var req product.CreateUIComponentRequest // 从表单数据中获取组件信息 req.ComponentCode = c.PostForm("component_code") req.ComponentName = c.PostForm("component_name") req.Description = c.PostForm("description") req.Version = c.PostForm("version") req.IsActive = c.PostForm("is_active") == "true" if sortOrderStr := c.PostForm("sort_order"); sortOrderStr != "" { if sortOrder, err := strconv.Atoi(sortOrderStr); err == nil { req.SortOrder = sortOrder } } // 验证必需字段 if req.ComponentCode == "" { h.responseBuilder.BadRequest(c, "组件编码不能为空") return } if req.ComponentName == "" { h.responseBuilder.BadRequest(c, "组件名称不能为空") return } // 获取上传的文件 form, err := c.MultipartForm() if err != nil { h.logger.Error("获取表单数据失败", zap.Error(err)) h.responseBuilder.BadRequest(c, "获取表单数据失败") return } files := form.File["files"] if len(files) == 0 { h.responseBuilder.BadRequest(c, "请上传组件文件") return } // 检查文件大小(100MB) for _, fileHeader := range files { if fileHeader.Size > 100*1024*1024 { h.responseBuilder.BadRequest(c, fmt.Sprintf("文件 %s 大小不能超过100MB", fileHeader.Filename)) return } } // 获取路径信息 paths := c.PostFormArray("paths") // 记录请求日志 h.logger.Info("创建UI组件并上传文件请求", zap.String("component_code", req.ComponentCode), zap.String("component_name", req.ComponentName), zap.String("description", req.Description), zap.String("version", req.Version), zap.Bool("is_active", req.IsActive), zap.Int("sort_order", req.SortOrder), zap.Int("files_count", len(files)), zap.Strings("paths", paths)) // 调用应用服务创建组件并上传文件 component, err := h.uiComponentAppService.CreateUIComponentWithFilesAndPaths(c.Request.Context(), req, files, paths) if err != nil { h.logger.Error("创建UI组件并上传文件失败", zap.Error(err), zap.String("component_code", req.ComponentCode)) if err == product.ErrComponentCodeAlreadyExists { h.responseBuilder.BadRequest(c, "UI组件编码已存在") return } h.responseBuilder.InternalError(c, fmt.Sprintf("创建UI组件并上传文件失败: %v", err)) return } h.responseBuilder.Success(c, component) } // GetUIComponent 获取UI组件详情 // @Summary 获取UI组件详情 // @Description 根据ID获取UI组件详情 // @Tags UI组件管理 // @Accept json // @Produce json // @Param id path string true "UI组件ID" // @Success 200 {object} interfaces.Response{data=entities.UIComponent} "获取成功" // @Failure 400 {object} interfaces.Response "请求参数错误" // @Failure 404 {object} interfaces.Response "UI组件不存在" // @Failure 500 {object} interfaces.Response "服务器内部错误" // @Router /api/v1/admin/ui-components/{id} [get] func (h *UIComponentHandler) GetUIComponent(c *gin.Context) { id := c.Param("id") if id == "" { h.responseBuilder.BadRequest(c, "UI组件ID不能为空") return } component, err := h.uiComponentAppService.GetUIComponentByID(c.Request.Context(), id) if err != nil { h.logger.Error("获取UI组件失败", zap.Error(err), zap.String("id", id)) h.responseBuilder.InternalError(c, "获取UI组件失败") return } if component == nil { h.responseBuilder.NotFound(c, "UI组件不存在") return } h.responseBuilder.Success(c, component) } // UpdateUIComponent 更新UI组件 // @Summary 更新UI组件 // @Description 更新UI组件信息 // @Tags UI组件管理 // @Accept json // @Produce json // @Param id path string true "UI组件ID" // @Param request body product.UpdateUIComponentRequest true "更新UI组件请求" // @Success 200 {object} interfaces.Response "更新成功" // @Failure 400 {object} interfaces.Response "请求参数错误" // @Failure 404 {object} interfaces.Response "UI组件不存在" // @Failure 500 {object} interfaces.Response "服务器内部错误" // @Router /api/v1/admin/ui-components/{id} [put] func (h *UIComponentHandler) UpdateUIComponent(c *gin.Context) { id := c.Param("id") if id == "" { h.responseBuilder.BadRequest(c, "UI组件ID不能为空") return } var req product.UpdateUIComponentRequest // 设置ID req.ID = id // 验证请求 if err := h.validator.Validate(c, &req); err != nil { h.logger.Error("验证更新UI组件请求失败", zap.Error(err)) return } err := h.uiComponentAppService.UpdateUIComponent(c.Request.Context(), req) if err != nil { h.logger.Error("更新UI组件失败", zap.Error(err), zap.String("id", id)) if err == product.ErrComponentNotFound { h.responseBuilder.NotFound(c, "UI组件不存在") return } if err == product.ErrComponentCodeAlreadyExists { h.responseBuilder.BadRequest(c, "UI组件编码已存在") return } h.responseBuilder.InternalError(c, "更新UI组件失败") return } h.responseBuilder.Success(c, nil) } // DeleteUIComponent 删除UI组件 // @Summary 删除UI组件 // @Description 删除UI组件 // @Tags UI组件管理 // @Accept json // @Produce json // @Param id path string true "UI组件ID" // @Success 200 {object} interfaces.Response "删除成功" // @Failure 400 {object} interfaces.Response "请求参数错误" // @Failure 404 {object} interfaces.Response "UI组件不存在" // @Failure 500 {object} interfaces.Response "服务器内部错误" // @Router /api/v1/admin/ui-components/{id} [delete] func (h *UIComponentHandler) DeleteUIComponent(c *gin.Context) { id := c.Param("id") if id == "" { h.responseBuilder.BadRequest(c, "UI组件ID不能为空") return } err := h.uiComponentAppService.DeleteUIComponent(c.Request.Context(), id) if err != nil { h.logger.Error("删除UI组件失败", zap.Error(err), zap.String("id", id)) if err == product.ErrComponentNotFound { h.responseBuilder.NotFound(c, "UI组件不存在") return } h.responseBuilder.InternalError(c, "删除UI组件失败") return } h.responseBuilder.Success(c, nil) } // ListUIComponents 获取UI组件列表 // @Summary 获取UI组件列表 // @Description 获取UI组件列表,支持分页和筛选 // @Tags UI组件管理 // @Accept json // @Produce json // @Param page query int false "页码" default(1) // @Param page_size query int false "每页数量" default(10) // @Param keyword query string false "关键词搜索" // @Param is_active query bool false "是否启用" // @Param sort_by query string false "排序字段" default(sort_order) // @Param sort_order query string false "排序方向" default(asc) // @Success 200 {object} interfaces.Response{data=product.ListUIComponentsResponse} "获取成功" // @Failure 500 {object} interfaces.Response "服务器内部错误" // @Router /api/v1/admin/ui-components [get] func (h *UIComponentHandler) ListUIComponents(c *gin.Context) { // 解析查询参数 req := product.ListUIComponentsRequest{} if pageStr := c.Query("page"); pageStr != "" { if page, err := strconv.Atoi(pageStr); err == nil { req.Page = page } } if pageSizeStr := c.Query("page_size"); pageSizeStr != "" { if pageSize, err := strconv.Atoi(pageSizeStr); err == nil { req.PageSize = pageSize } } req.Keyword = c.Query("keyword") if isActiveStr := c.Query("is_active"); isActiveStr != "" { if isActive, err := strconv.ParseBool(isActiveStr); err == nil { req.IsActive = &isActive } } req.SortBy = c.DefaultQuery("sort_by", "sort_order") req.SortOrder = c.DefaultQuery("sort_order", "asc") response, err := h.uiComponentAppService.ListUIComponents(c.Request.Context(), req) if err != nil { h.logger.Error("获取UI组件列表失败", zap.Error(err)) h.responseBuilder.InternalError(c, "获取UI组件列表失败") return } h.responseBuilder.Success(c, response) } // UploadUIComponentFile 上传UI组件文件 // @Summary 上传UI组件文件 // @Description 上传UI组件文件 // @Tags UI组件管理 // @Accept multipart/form-data // @Produce json // @Param id path string true "UI组件ID" // @Param file formData file true "UI组件文件(ZIP格式)" // @Success 200 {object} interfaces.Response{data=string} "上传成功,返回文件路径" // @Failure 400 {object} interfaces.Response "请求参数错误" // @Failure 404 {object} interfaces.Response "UI组件不存在" // @Failure 500 {object} interfaces.Response "服务器内部错误" // @Router /api/v1/admin/ui-components/{id}/upload [post] func (h *UIComponentHandler) UploadUIComponentFile(c *gin.Context) { id := c.Param("id") if id == "" { h.responseBuilder.BadRequest(c, "UI组件ID不能为空") return } // 获取上传的文件 file, err := c.FormFile("file") if err != nil { h.logger.Error("获取上传文件失败", zap.Error(err)) h.responseBuilder.BadRequest(c, "获取上传文件失败") return } // 检查文件大小(100MB) if file.Size > 100*1024*1024 { h.responseBuilder.BadRequest(c, "文件大小不能超过100MB") return } filePath, err := h.uiComponentAppService.UploadUIComponentFile(c.Request.Context(), id, file) if err != nil { h.logger.Error("上传UI组件文件失败", zap.Error(err), zap.String("id", id)) if err == product.ErrComponentNotFound { h.responseBuilder.NotFound(c, "UI组件不存在") return } if err == product.ErrInvalidFileType { h.responseBuilder.BadRequest(c, "文件类型错误") return } h.responseBuilder.InternalError(c, "上传UI组件文件失败") return } h.responseBuilder.Success(c, filePath) } // UploadAndExtractUIComponentFile 上传并解压UI组件文件 // @Summary 上传并解压UI组件文件 // @Description 上传文件并自动解压到组件文件夹(仅ZIP文件支持解压) // @Tags UI组件管理 // @Accept multipart/form-data // @Produce json // @Param id path string true "UI组件ID" // @Param file formData file true "UI组件文件(任意格式,ZIP格式支持自动解压)" // @Success 200 {object} interfaces.Response "上传成功" // @Failure 400 {object} interfaces.Response "请求参数错误" // @Failure 404 {object} interfaces.Response "UI组件不存在" // @Failure 500 {object} interfaces.Response "服务器内部错误" // @Router /api/v1/admin/ui-components/{id}/upload-extract [post] func (h *UIComponentHandler) UploadAndExtractUIComponentFile(c *gin.Context) { id := c.Param("id") if id == "" { h.responseBuilder.BadRequest(c, "UI组件ID不能为空") return } // 获取上传的文件 file, err := c.FormFile("file") if err != nil { h.logger.Error("获取上传文件失败", zap.Error(err)) h.responseBuilder.BadRequest(c, "获取上传文件失败") return } // 检查文件大小(100MB) if file.Size > 100*1024*1024 { h.responseBuilder.BadRequest(c, "文件大小不能超过100MB") return } err = h.uiComponentAppService.UploadAndExtractUIComponentFile(c.Request.Context(), id, file) if err != nil { h.logger.Error("上传并解压UI组件文件失败", zap.Error(err), zap.String("id", id)) if err == product.ErrComponentNotFound { h.responseBuilder.NotFound(c, "UI组件不存在") return } if err == product.ErrInvalidFileType { h.responseBuilder.BadRequest(c, "文件类型错误") return } h.responseBuilder.InternalError(c, "上传并解压UI组件文件失败") return } h.responseBuilder.Success(c, nil) } // GetUIComponentFolderContent 获取UI组件文件夹内容 // @Summary 获取UI组件文件夹内容 // @Description 获取UI组件文件夹内容 // @Tags UI组件管理 // @Accept json // @Produce json // @Param id path string true "UI组件ID" // @Success 200 {object} interfaces.Response{data=[]FileInfo} "获取成功" // @Failure 400 {object} interfaces.Response "请求参数错误" // @Failure 404 {object} interfaces.Response "UI组件不存在" // @Failure 500 {object} interfaces.Response "服务器内部错误" // @Router /api/v1/admin/ui-components/{id}/folder-content [get] func (h *UIComponentHandler) GetUIComponentFolderContent(c *gin.Context) { id := c.Param("id") if id == "" { h.responseBuilder.BadRequest(c, "UI组件ID不能为空") return } files, err := h.uiComponentAppService.GetUIComponentFolderContent(c.Request.Context(), id) if err != nil { h.logger.Error("获取UI组件文件夹内容失败", zap.Error(err), zap.String("id", id)) if err == product.ErrComponentNotFound { h.responseBuilder.NotFound(c, "UI组件不存在") return } h.responseBuilder.InternalError(c, "获取UI组件文件夹内容失败") return } h.responseBuilder.Success(c, files) } // DeleteUIComponentFolder 删除UI组件文件夹 // @Summary 删除UI组件文件夹 // @Description 删除UI组件文件夹 // @Tags UI组件管理 // @Accept json // @Produce json // @Param id path string true "UI组件ID" // @Success 200 {object} interfaces.Response "删除成功" // @Failure 400 {object} interfaces.Response "请求参数错误" // @Failure 404 {object} interfaces.Response "UI组件不存在" // @Failure 500 {object} interfaces.Response "服务器内部错误" // @Router /api/v1/admin/ui-components/{id}/folder [delete] func (h *UIComponentHandler) DeleteUIComponentFolder(c *gin.Context) { id := c.Param("id") if id == "" { h.responseBuilder.BadRequest(c, "UI组件ID不能为空") return } err := h.uiComponentAppService.DeleteUIComponentFolder(c.Request.Context(), id) if err != nil { h.logger.Error("删除UI组件文件夹失败", zap.Error(err), zap.String("id", id)) if err == product.ErrComponentNotFound { h.responseBuilder.NotFound(c, "UI组件不存在") return } h.responseBuilder.InternalError(c, "删除UI组件文件夹失败") return } h.responseBuilder.Success(c, nil) } // DownloadUIComponentFile 下载UI组件文件 // @Summary 下载UI组件文件 // @Description 下载UI组件文件 // @Tags UI组件管理 // @Accept json // @Produce application/octet-stream // @Param id path string true "UI组件ID" // @Success 200 {file} file "文件内容" // @Failure 400 {object} interfaces.Response "请求参数错误" // @Failure 404 {object} interfaces.Response "UI组件不存在或文件不存在" // @Failure 500 {object} interfaces.Response "服务器内部错误" // @Router /api/v1/admin/ui-components/{id}/download [get] func (h *UIComponentHandler) DownloadUIComponentFile(c *gin.Context) { id := c.Param("id") if id == "" { h.responseBuilder.BadRequest(c, "UI组件ID不能为空") return } filePath, err := h.uiComponentAppService.DownloadUIComponentFile(c.Request.Context(), id) if err != nil { h.logger.Error("下载UI组件文件失败", zap.Error(err), zap.String("id", id)) if err == product.ErrComponentNotFound { h.responseBuilder.NotFound(c, "UI组件不存在") return } if err == product.ErrComponentFileNotFound { h.responseBuilder.NotFound(c, "UI组件文件不存在") return } h.responseBuilder.InternalError(c, "下载UI组件文件失败") return } // 这里应该实现文件下载逻辑,返回文件内容 // 由于我们使用的是本地文件存储,可以直接返回文件 c.File(filePath) }