package controllers import ( "crypto/md5" "encoding/hex" "encoding/json" "fmt" "io" "os" "path/filepath" "strconv" "strings" "time" "server/models" "server/pkg/jwtutil" beego "github.com/beego/beego/v2/server/web" ) // PlatformFileController 平台端文件管理(yz_system_files / yz_system_files_category) type PlatformFileController struct { beego.Controller } const fileUploadMaxBytes = 50 * 1024 * 1024 var fileTypeByCategory = map[string]uint8{ "image": 1, "document": 2, "video": 3, "audio": 4, } var allowedExtByCategory = map[string][]string{ "image": {"jpg", "jpeg", "png", "gif", "bmp", "webp"}, "document": {"pdf", "doc", "docx", "xls", "xlsx", "ppt", "pptx", "txt"}, "video": {"mp4", "webm", "mov"}, "audio": {"mp3", "wav", "ogg"}, } func (c *PlatformFileController) platformClaims() (*jwtutil.Claims, error) { auth := c.Ctx.Request.Header.Get("Authorization") if auth == "" { return nil, fmt.Errorf("未登录") } parts := strings.SplitN(auth, " ", 2) if len(parts) != 2 || parts[0] != "Bearer" { return nil, fmt.Errorf("认证信息格式错误") } claims, err := jwtutil.ParseToken(parts[1]) if err != nil { return nil, fmt.Errorf("无效的token") } if claims.UserType != "platform" { return nil, fmt.Errorf("无权访问") } return claims, nil } func (c *PlatformFileController) effectiveTid(claims *jwtutil.Claims) uint64 { _ = c.ParseForm(1 << 20) if tid, err := c.GetUint64("tid"); err == nil && tid > 0 { return tid } if h := strings.TrimSpace(c.Ctx.Request.Header.Get("X-Tenant-Id")); h != "" { if v, e := strconv.ParseUint(h, 10, 64); e == nil { return v } } if claims != nil && claims.TenantId > 0 { return uint64(claims.TenantId) } return 0 } func (c *PlatformFileController) jsonErr(httpStatus, bizCode int, msg string) { c.Ctx.Output.SetStatus(httpStatus) c.Data["json"] = map[string]interface{}{"code": bizCode, "msg": msg} _ = c.ServeJSON() } func (c *PlatformFileController) jsonOK(data interface{}) { c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success", "data": data} _ = c.ServeJSON() } func detectFileType(ext string) uint8 { ext = strings.ToLower(strings.TrimPrefix(ext, ".")) for cat, exts := range allowedExtByCategory { for _, e := range exts { if e == ext { return fileTypeByCategory[cat] } } } return 2 } func fileExt(name string) string { name = strings.TrimSpace(name) if i := strings.LastIndex(name, "."); i >= 0 && i < len(name)-1 { return strings.ToLower(name[i+1:]) } return "" } func fileToMap(f *models.SystemFile) map[string]interface{} { ct := f.CreateTime.Format("2006-01-02 15:04:05") m := map[string]interface{}{ "id": f.ID, "tid": f.Tid, "name": f.Name, "type": f.Type, "cate": f.Cate, "size": f.Size, "src": f.Src, "uploader": f.Uploader, "md5": f.Md5, "create_time": ct, "createTime": ct, "groupId": f.Cate, "url": f.Src, } if f.Uid != nil { m["uid"] = *f.Uid } if f.Tuid != nil { m["tuid"] = *f.Tuid } return m } func removePhysicalBySrc(webSrc string) { webSrc = strings.TrimSpace(webSrc) if webSrc == "" { return } webSrc = strings.TrimPrefix(webSrc, "/") _ = os.Remove(webSrc) } // GetAllFiles GET /platform/allfiles func (c *PlatformFileController) GetAllFiles() { claims, err := c.platformClaims() if err != nil { c.jsonErr(401, 401, err.Error()) return } tid := c.effectiveTid(claims) page, _ := c.GetInt("page", 1) pageSize, _ := c.GetInt("pageSize", 10) if page < 1 { page = 1 } if pageSize < 1 { pageSize = 10 } cate, _ := c.GetUint64("cate") keyword := strings.TrimSpace(c.GetString("keyword")) qs := models.Orm.QueryTable(new(models.SystemFile)). Filter("tid", tid). Filter("delete_time__isnull", true) if cate > 0 { qs = qs.Filter("cate", cate) } if keyword != "" { qs = qs.Filter("name__icontains", keyword) } total, err := qs.Count() if err != nil { c.jsonErr(500, 500, "获取文件列表失败: "+err.Error()) return } var rows []models.SystemFile _, err = qs.OrderBy("-create_time").Limit(pageSize, (page-1)*pageSize).All(&rows) if err != nil { c.jsonErr(500, 500, "获取文件列表失败: "+err.Error()) return } list := make([]map[string]interface{}, 0, len(rows)) for i := range rows { list = append(list, fileToMap(&rows[i])) } c.jsonOK(map[string]interface{}{ "list": list, "total": total, "page": page, "pageSize": pageSize, }) } // GetUserCate GET /platform/usercate func (c *PlatformFileController) GetUserCate() { claims, err := c.platformClaims() if err != nil { c.jsonErr(401, 401, err.Error()) return } tid := c.effectiveTid(claims) var cates []models.SystemFilesCategory _, err = models.Orm.QueryTable(new(models.SystemFilesCategory)). Filter("tid", tid). Filter("delete_time__isnull", true). OrderBy("id"). All(&cates) if err != nil { c.jsonErr(500, 500, "获取用户分类失败: "+err.Error()) return } out := make([]map[string]interface{}, 0, len(cates)) for i := range cates { cnt, _ := models.Orm.QueryTable(new(models.SystemFile)). Filter("tid", tid). Filter("cate", cates[i].ID). Filter("delete_time__isnull", true). Count() out = append(out, map[string]interface{}{ "id": cates[i].ID, "name": cates[i].Name, "total": cnt, }) } c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success", "data": out} _ = c.ServeJSON() } type createCateBody struct { Name string `json:"name"` Tuid *uint64 `json:"tuid"` } // CreateFileCate POST /platform/createfilecate func (c *PlatformFileController) CreateFileCate() { claims, err := c.platformClaims() if err != nil { c.jsonErr(401, 401, err.Error()) return } tid := c.effectiveTid(claims) raw, err := io.ReadAll(c.Ctx.Request.Body) if err != nil { c.jsonErr(400, 400, "参数错误") return } var body createCateBody if err := json.Unmarshal(raw, &body); err != nil { c.jsonErr(400, 400, "参数错误") return } name := strings.TrimSpace(body.Name) if name == "" { c.jsonErr(400, 400, "分组名称不能为空") return } uid := uint64(claims.UserID) row := &models.SystemFilesCategory{ Tid: tid, Name: name, Uid: &uid, Tuid: body.Tuid, } id, err := models.Orm.Insert(row) if err != nil { c.jsonErr(500, 500, "新建文件分组失败: "+err.Error()) return } c.Data["json"] = map[string]interface{}{ "code": 200, "msg": "新建文件分组成功", "data": map[string]interface{}{"id": uint64(id)}, } _ = c.ServeJSON() } type renameCateBody struct { Name string `json:"name"` } // RenameFileCate POST /platform/renamefilecate/:id func (c *PlatformFileController) RenameFileCate() { claims, err := c.platformClaims() if err != nil { c.jsonErr(401, 401, err.Error()) return } tid := c.effectiveTid(claims) idStr := c.Ctx.Input.Param(":id") id, err := strconv.ParseUint(idStr, 10, 64) if err != nil || id == 0 { c.jsonErr(400, 400, "无效的分组ID") return } raw, err := io.ReadAll(c.Ctx.Request.Body) if err != nil { c.jsonErr(400, 400, "参数错误") return } var body renameCateBody if err := json.Unmarshal(raw, &body); err != nil { c.jsonErr(400, 400, "参数错误") return } name := strings.TrimSpace(body.Name) if name == "" { c.jsonErr(400, 400, "分组名称不能为空") return } n, err := models.Orm.QueryTable(new(models.SystemFilesCategory)). Filter("id", id). Filter("tid", tid). Filter("delete_time__isnull", true). Update(map[string]interface{}{"name": name}) if err != nil { c.jsonErr(500, 500, "重命名文件分组失败: "+err.Error()) return } if n == 0 { c.jsonErr(404, 404, "分组不存在") return } c.Data["json"] = map[string]interface{}{"code": 200, "msg": "重命名文件分组成功"} _ = c.ServeJSON() } // DeleteFileCate DELETE /platform/deletefilecate/:id func (c *PlatformFileController) DeleteFileCate() { claims, err := c.platformClaims() if err != nil { c.jsonErr(401, 401, err.Error()) return } tid := c.effectiveTid(claims) idStr := c.Ctx.Input.Param(":id") id, err := strconv.ParseUint(idStr, 10, 64) if err != nil || id == 0 { c.jsonErr(400, 400, "无效的分组ID") return } cnt, err := models.Orm.QueryTable(new(models.SystemFile)). Filter("cate", id). Filter("tid", tid). Filter("delete_time__isnull", true). Count() if err != nil { c.jsonErr(500, 500, "删除文件分组失败: "+err.Error()) return } if cnt > 0 { c.jsonErr(400, 400, fmt.Sprintf("该分组下还有 %d 个文件,请先删除分组内文件!", cnt)) return } now := time.Now() n, err := models.Orm.QueryTable(new(models.SystemFilesCategory)). Filter("id", id). Filter("tid", tid). Filter("delete_time__isnull", true). Update(map[string]interface{}{"delete_time": now}) if err != nil { c.jsonErr(500, 500, "删除文件分组失败: "+err.Error()) return } if n == 0 { c.jsonErr(404, 404, "分组不存在") return } c.Data["json"] = map[string]interface{}{"code": 200, "msg": "删除文件分组成功"} _ = c.ServeJSON() } // GetCateFiles GET /platform/catefiles/:id func (c *PlatformFileController) GetCateFiles() { claims, err := c.platformClaims() if err != nil { c.jsonErr(401, 401, err.Error()) return } tid := c.effectiveTid(claims) idStr := c.Ctx.Input.Param(":id") cateID, err := strconv.ParseUint(idStr, 10, 64) if err != nil { c.jsonErr(400, 400, "无效的分类ID") return } page, _ := c.GetInt("page", 1) pageSize, _ := c.GetInt("pageSize", 24) if page < 1 { page = 1 } if pageSize < 1 { pageSize = 24 } keyword := strings.TrimSpace(c.GetString("keyword")) qs := models.Orm.QueryTable(new(models.SystemFile)). Filter("tid", tid). Filter("cate", cateID). Filter("delete_time__isnull", true) if keyword != "" { qs = qs.Filter("name__icontains", keyword) } total, err := qs.Count() if err != nil { c.jsonErr(500, 500, "获取分类文件失败: "+err.Error()) return } var rows []models.SystemFile _, err = qs.OrderBy("-create_time").Limit(pageSize, (page-1)*pageSize).All(&rows) if err != nil { c.jsonErr(500, 500, "获取分类文件失败: "+err.Error()) return } list := make([]map[string]interface{}, 0, len(rows)) for i := range rows { list = append(list, fileToMap(&rows[i])) } c.jsonOK(map[string]interface{}{ "list": list, "total": total, "page": page, "pageSize": pageSize, "categoryId": cateID, }) } // GetFileByID GET /platform/file/:id func (c *PlatformFileController) GetFileByID() { claims, err := c.platformClaims() if err != nil { c.jsonErr(401, 401, err.Error()) return } tid := c.effectiveTid(claims) idStr := c.Ctx.Input.Param(":id") id, err := strconv.ParseUint(idStr, 10, 64) if err != nil || id == 0 { c.jsonErr(400, 400, "无效的文件ID") return } var f models.SystemFile err = models.Orm.QueryTable(new(models.SystemFile)). Filter("id", id). Filter("tid", tid). Filter("delete_time__isnull", true). One(&f) if err != nil { c.jsonErr(404, 404, "文件不存在") return } c.jsonOK(fileToMap(&f)) } // UploadFile POST /platform/uploadfile func (c *PlatformFileController) UploadFile() { claims, err := c.platformClaims() if err != nil { c.jsonErr(401, 401, err.Error()) return } tid := c.effectiveTid(claims) if err := c.Ctx.Request.ParseMultipartForm(fileUploadMaxBytes); err != nil { c.jsonErr(400, 400, "解析上传失败: "+err.Error()) return } fh, header, err := c.GetFile("file") if err != nil || fh == nil { c.jsonErr(400, 400, "请选择要上传的文件") return } defer fh.Close() if header != nil && header.Size > fileUploadMaxBytes { c.jsonErr(400, 400, "文件大小不能超过50MB") return } ext := fileExt(header.Filename) if ext == "" { c.jsonErr(400, 400, "无法识别文件扩展名") return } tmpPath := filepath.Join(os.TempDir(), fmt.Sprintf("up_%d_%s", time.Now().UnixNano(), header.Filename)) tmp, err := os.Create(tmpPath) if err != nil { c.jsonErr(500, 500, "创建临时文件失败") return } n, copyErr := io.Copy(tmp, fh) _ = tmp.Close() if copyErr != nil { _ = os.Remove(tmpPath) c.jsonErr(500, 500, "读取文件失败") return } if n > fileUploadMaxBytes { _ = os.Remove(tmpPath) c.jsonErr(400, 400, "文件大小不能超过50MB") return } sum, err := md5HashFile(tmpPath) if err != nil { _ = os.Remove(tmpPath) c.jsonErr(500, 500, "计算文件摘要失败") return } var exist models.SystemFile err = models.Orm.QueryTable(new(models.SystemFile)). Filter("md5", sum). Filter("tid", tid). Filter("delete_time__isnull", true). One(&exist) if err == nil { _ = os.Remove(tmpPath) c.Data["json"] = map[string]interface{}{ "code": 201, "msg": "文件已存在", "data": map[string]interface{}{ "url": exist.Src, "id": exist.ID, "name": exist.Name, }, } _ = c.ServeJSON() return } datePath := time.Now().Format("2006/01/02") saveName := fmt.Sprintf("%s/%d.%s", datePath, time.Now().UnixNano(), ext) destDir := filepath.Join("uploads", filepath.FromSlash(datePath)) if err := os.MkdirAll(destDir, 0755); err != nil { _ = os.Remove(tmpPath) c.jsonErr(500, 500, "创建目录失败: "+err.Error()) return } destPath := filepath.Join("uploads", filepath.FromSlash(saveName)) if err := os.Rename(tmpPath, destPath); err != nil { _ = os.Remove(tmpPath) c.jsonErr(500, 500, "保存文件失败: "+err.Error()) return } webURL := "/" + strings.ReplaceAll(filepath.ToSlash(destPath), "\\", "/") cateStr := c.GetString("cate") var cate uint64 if cateStr != "" { cate, _ = strconv.ParseUint(cateStr, 10, 64) } adminID := uint64(claims.UserID) var tuidPtr *uint64 if ts := strings.TrimSpace(c.GetString("tuid")); ts != "" { if v, e := strconv.ParseUint(ts, 10, 64); e == nil { tuidPtr = &v } } row := &models.SystemFile{ Tid: tid, Uid: &adminID, Tuid: tuidPtr, Name: header.Filename, Type: detectFileType(ext), Cate: cate, Size: uint64(n), Src: webURL, Uploader: adminID, Md5: sum, } id, err := models.Orm.Insert(row) if err != nil { removePhysicalBySrc(webURL) c.jsonErr(500, 500, "上传失败: "+err.Error()) return } c.Data["json"] = map[string]interface{}{ "code": 200, "msg": "上传成功", "data": map[string]interface{}{ "url": webURL, "id": uint64(id), "name": header.Filename, }, } _ = c.ServeJSON() } func md5HashFile(path string) (string, error) { f, err := os.Open(path) if err != nil { return "", err } defer f.Close() h := md5.New() if _, err := io.Copy(h, f); err != nil { return "", err } return hex.EncodeToString(h.Sum(nil)), nil } type updateFileBody struct { Name *string `json:"name"` Cate *uint64 `json:"cate"` } // UpdateFile POST /platform/updatefile/:id func (c *PlatformFileController) UpdateFile() { claims, err := c.platformClaims() if err != nil { c.jsonErr(401, 401, err.Error()) return } tid := c.effectiveTid(claims) idStr := c.Ctx.Input.Param(":id") id, err := strconv.ParseUint(idStr, 10, 64) if err != nil || id == 0 { c.jsonErr(400, 400, "无效的文件ID") return } raw, err := io.ReadAll(c.Ctx.Request.Body) if err != nil { c.jsonErr(400, 400, "参数错误") return } var body updateFileBody if err := json.Unmarshal(raw, &body); err != nil { c.jsonErr(400, 400, "参数错误") return } up := map[string]interface{}{} if body.Name != nil { up["name"] = strings.TrimSpace(*body.Name) } if body.Cate != nil { up["cate"] = *body.Cate } if len(up) == 0 { c.jsonErr(400, 400, "无更新数据") return } now := time.Now() up["update_time"] = now n, err := models.Orm.QueryTable(new(models.SystemFile)). Filter("id", id). Filter("tid", tid). Filter("delete_time__isnull", true). Update(up) if err != nil { c.jsonErr(500, 500, "更新失败: "+err.Error()) return } if n == 0 { c.jsonErr(404, 404, "文件不存在") return } c.Data["json"] = map[string]interface{}{"code": 200, "msg": "更新成功"} _ = c.ServeJSON() } // DeleteFile DELETE /platform/deletefile/:id func (c *PlatformFileController) DeleteFile() { claims, err := c.platformClaims() if err != nil { c.jsonErr(401, 401, err.Error()) return } tid := c.effectiveTid(claims) idStr := c.Ctx.Input.Param(":id") id, err := strconv.ParseUint(idStr, 10, 64) if err != nil || id == 0 { c.jsonErr(400, 400, "无效的文件ID") return } now := time.Now() n, err := models.Orm.QueryTable(new(models.SystemFile)). Filter("id", id). Filter("tid", tid). Filter("delete_time__isnull", true). Update(map[string]interface{}{"delete_time": now}) if err != nil { c.jsonErr(500, 500, "删除失败: "+err.Error()) return } if n == 0 { c.jsonErr(404, 404, "文件不存在") return } c.Data["json"] = map[string]interface{}{"code": 200, "msg": "删除成功"} _ = c.ServeJSON() } // DeleteFilePermanently DELETE /platform/deletefilepermanently/:id func (c *PlatformFileController) DeleteFilePermanently() { claims, err := c.platformClaims() if err != nil { c.jsonErr(401, 401, err.Error()) return } tid := c.effectiveTid(claims) idStr := c.Ctx.Input.Param(":id") id, err := strconv.ParseUint(idStr, 10, 64) if err != nil || id == 0 { c.jsonErr(400, 400, "无效的文件ID") return } var f models.SystemFile err = models.Orm.QueryTable(new(models.SystemFile)). Filter("id", id). Filter("tid", tid). One(&f) if err != nil { c.jsonErr(404, 404, "文件不存在") return } removePhysicalBySrc(f.Src) _, err = models.Orm.QueryTable(new(models.SystemFile)). Filter("id", id). Filter("tid", tid). Delete() if err != nil { c.jsonErr(500, 500, "永久删除失败: "+err.Error()) return } c.Data["json"] = map[string]interface{}{"code": 200, "msg": "永久删除成功"} _ = c.ServeJSON() } // MoveFile GET /platform/movefile/:id func (c *PlatformFileController) MoveFile() { claims, err := c.platformClaims() if err != nil { c.jsonErr(401, 401, err.Error()) return } tid := c.effectiveTid(claims) idStr := c.Ctx.Input.Param(":id") id, err := strconv.ParseUint(idStr, 10, 64) if err != nil || id == 0 { c.jsonErr(400, 400, "无效的文件ID") return } cate, _ := c.GetUint64("cate") now := time.Now() n, err := models.Orm.QueryTable(new(models.SystemFile)). Filter("id", id). Filter("tid", tid). Filter("delete_time__isnull", true). Update(map[string]interface{}{"cate": cate, "update_time": now}) if err != nil { c.jsonErr(500, 500, "移动失败: "+err.Error()) return } if n == 0 { c.jsonErr(404, 404, "文件不存在") return } c.Data["json"] = map[string]interface{}{"code": 200, "msg": "移动成功"} _ = c.ServeJSON() } type idsBody struct { IDs []uint64 `json:"ids"` Cate *uint64 `json:"cate"` } // BatchDeleteFiles POST /platform/batchdeletefiles func (c *PlatformFileController) BatchDeleteFiles() { claims, err := c.platformClaims() if err != nil { c.jsonErr(401, 401, err.Error()) return } tid := c.effectiveTid(claims) raw, err := io.ReadAll(c.Ctx.Request.Body) if err != nil { c.jsonErr(400, 400, "参数错误") return } var body idsBody if err := json.Unmarshal(raw, &body); err != nil { c.jsonErr(400, 400, "参数错误") return } if len(body.IDs) == 0 { c.jsonErr(400, 400, "请选择要删除的文件") return } now := time.Now() for _, id := range body.IDs { var f models.SystemFile e := models.Orm.QueryTable(new(models.SystemFile)). Filter("id", id). Filter("tid", tid). One(&f) if e == nil && f.Src != "" { removePhysicalBySrc(f.Src) } } n, err := models.Orm.QueryTable(new(models.SystemFile)). Filter("id__in", body.IDs). Filter("tid", tid). Update(map[string]interface{}{"delete_time": now}) if err != nil { c.jsonErr(500, 500, "批量删除失败: "+err.Error()) return } if n == 0 { c.jsonErr(404, 404, "文件不存在") return } c.Data["json"] = map[string]interface{}{"code": 200, "msg": "批量删除成功"} _ = c.ServeJSON() } // BatchDeleteFilesPermanently POST /platform/batchDeleteFilesPermanently func (c *PlatformFileController) BatchDeleteFilesPermanently() { claims, err := c.platformClaims() if err != nil { c.jsonErr(401, 401, err.Error()) return } tid := c.effectiveTid(claims) raw, err := io.ReadAll(c.Ctx.Request.Body) if err != nil { c.jsonErr(400, 400, "参数错误") return } var body idsBody if err := json.Unmarshal(raw, &body); err != nil { c.jsonErr(400, 400, "参数错误") return } if len(body.IDs) == 0 { c.jsonErr(400, 400, "请选择要彻底删除的文件") return } var rows []models.SystemFile _, err = models.Orm.QueryTable(new(models.SystemFile)). Filter("id__in", body.IDs). Filter("tid", tid). All(&rows) if err != nil { c.jsonErr(500, 500, "批量彻底删除失败: "+err.Error()) return } for i := range rows { removePhysicalBySrc(rows[i].Src) } n, err := models.Orm.QueryTable(new(models.SystemFile)). Filter("id__in", body.IDs). Filter("tid", tid). Delete() if err != nil { c.jsonErr(500, 500, "批量彻底删除失败: "+err.Error()) return } if n == 0 { c.jsonErr(404, 404, "文件不存在") return } c.Data["json"] = map[string]interface{}{"code": 200, "msg": "批量彻底删除成功"} _ = c.ServeJSON() } // UploadAvatar POST /platform/uploadavatar(占位) func (c *PlatformFileController) UploadAvatar() { c.Data["json"] = map[string]interface{}{"code": 501, "msg": "上传头像暂未实现"} _ = c.ServeJSON() } // UpdateAvatar POST /platform/uploadavatar/:id(占位) func (c *PlatformFileController) UpdateAvatar() { c.Data["json"] = map[string]interface{}{"code": 501, "msg": "更新头像暂未实现"} _ = c.ServeJSON() } // BatchMoveFiles POST /platform/batchMoveFiles func (c *PlatformFileController) BatchMoveFiles() { claims, err := c.platformClaims() if err != nil { c.jsonErr(401, 401, err.Error()) return } tid := c.effectiveTid(claims) raw, err := io.ReadAll(c.Ctx.Request.Body) if err != nil { c.jsonErr(400, 400, "参数错误") return } var body idsBody if err := json.Unmarshal(raw, &body); err != nil { c.jsonErr(400, 400, "参数错误") return } if len(body.IDs) == 0 { c.jsonErr(400, 400, "请选择要移动的文件") return } if body.Cate == nil { c.jsonErr(400, 400, "缺少目标分类") return } now := time.Now() n, err := models.Orm.QueryTable(new(models.SystemFile)). Filter("id__in", body.IDs). Filter("tid", tid). Filter("delete_time__isnull", true). Update(map[string]interface{}{"cate": *body.Cate, "update_time": now}) if err != nil { c.jsonErr(500, 500, "批量移动失败: "+err.Error()) return } if n == 0 { c.jsonErr(404, 404, "文件不存在") return } c.Data["json"] = map[string]interface{}{"code": 200, "msg": "批量移动成功"} _ = c.ServeJSON() }