/* * Feedback API List * /feedback/submit - POST 提交问题反馈或需求 * /feedback/history - GET 查询提交历史 * /feedback/upload - POST 上传附件到 Plane (返回 assetId) * /feedback/asset/:id - DELETE 删除已上传的附件 */ package controllers import ( "fwh-toolbox-server/dto" "fwh-toolbox-server/middleware" "fwh-toolbox-server/services" "github.com/gofiber/fiber/v2" "go.uber.org/zap" ) // 提交反馈 // /feedback/submit func SubmitFeedback(c *fiber.Ctx) error { var body dto.FeedbackSubmitDTO if err := c.BodyParser(&body); err != nil { zap.L().Warn("Invalid request body", zap.String("err", err.Error())) return Fail(c, "Invalid request body") } // 参数校验 if body.Title == "" { return Fail(c, "标题不能为空") } if body.Type != "bug" && body.Type != "feature" { return Fail(c, "类型必须是 bug 或 feature") } // 从 JWT 中获取用户名 userName := middleware.AuthGetDisplayName(c) result, err := services.SubmitFeedback(body, userName) if err != nil { return Error(c, err.Error()) } return Success(c, result) } // 查询提交历史 // /feedback/history func GetFeedbackHistory(c *fiber.Ctx) error { // 从 JWT 中获取用户名 userName := "" if name, ok := c.Locals("userName").(string); ok && name != "" { userName = name } result, err := services.GetFeedbackHistory(userName) if err != nil { return Error(c, err.Error()) } return Success(c, result) } // 上传附件到 Plane(立即上传,返回 assetId 供后续提交使用) // /feedback/upload func UploadFeedbackAsset(c *fiber.Ctx) error { file, err := c.FormFile("file") if err != nil { return Fail(c, "请选择文件") } // 限制文件大小 10MB if file.Size > 10*1024*1024 { return Fail(c, "文件大小不能超过 10MB") } // 读取文件内容 src, err := file.Open() if err != nil { return Fail(c, "文件读取失败") } defer src.Close() fileData := make([]byte, file.Size) if _, err := src.Read(fileData); err != nil { return Fail(c, "文件读取失败") } // 获取 Content-Type contentType := file.Header.Get("Content-Type") if contentType == "" { contentType = "application/octet-stream" } result, err := services.UploadFeedbackAsset(file.Filename, contentType, file.Size, fileData) if err != nil { return Error(c, err.Error()) } // 返回 assetId 和 assetUrl return Success(c, fiber.Map{ "assetId": result.AssetID, "assetUrl": result.AssetURL, }) } // 获取反馈详情(含描述内容) // /feedback/detail/:id func GetFeedbackDetail(c *fiber.Ctx) error { issueId := c.Params("id") if issueId == "" { return Fail(c, "issue id 不能为空") } result, err := services.GetFeedbackDetail(issueId) if err != nil { return Error(c, err.Error()) } return Success(c, result) } // 代理获取附件文件(从 Plane 下载后转发给前端) // /feedback/asset/view/:id func ViewFeedbackAsset(c *fiber.Ctx) error { assetId := c.Params("id") if assetId == "" { return Fail(c, "asset id 不能为空") } contentType, data, err := services.ProxyFeedbackAsset(assetId) if err != nil { return Error(c, err.Error()) } c.Set("Content-Type", contentType) c.Set("Cache-Control", "private, max-age=3600") return c.Send(data) } // 删除已上传的附件 // /feedback/asset/:id func DeleteFeedbackAsset(c *fiber.Ctx) error { assetId := c.Params("id") if assetId == "" { return Fail(c, "asset id 不能为空") } err := services.DeleteFeedbackAsset(assetId) if err != nil { return Error(c, err.Error()) } return Success[any](c, nil, "删除成功") }