package controllers import ( "encoding/json" "fmt" "io" "strconv" "strings" "time" "server/models" "server/pkg/jwtutil" "github.com/beego/beego/v2/client/orm" beego "github.com/beego/beego/v2/server/web" ) // BackendArticleController CMS 文章管理 type BackendArticleController struct { beego.Controller } // BackendArticleCategoryController CMS 文章分类管理 type BackendArticleCategoryController struct { beego.Controller } func (c *BackendArticleController) cmsClaims() (*jwtutil.Claims, error) { return cmsBackendClaims(&c.Controller) } func (c *BackendArticleCategoryController) cmsClaims() (*jwtutil.Claims, error) { return cmsBackendClaims(&c.Controller) } func cmsBackendClaims(c *beego.Controller) (*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 != "backend" { return nil, fmt.Errorf("无权访问") } return claims, nil } func cmsEffectiveTid(c *beego.Controller, 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 *BackendArticleController) cmsJSONErr(httpStatus, bizCode int, msg string) { c.Ctx.Output.SetStatus(httpStatus) c.Data["json"] = map[string]interface{}{"code": bizCode, "msg": msg} _ = c.ServeJSON() } func (c *BackendArticleCategoryController) cmsJSONErr(httpStatus, bizCode int, msg string) { c.Ctx.Output.SetStatus(httpStatus) c.Data["json"] = map[string]interface{}{"code": bizCode, "msg": msg} _ = c.ServeJSON() } func cmsEnsureTables(c *beego.Controller) bool { if err := models.EnsureCmsArticleTables(); err != nil { c.Ctx.Output.SetStatus(500) c.Data["json"] = map[string]interface{}{"code": 500, "msg": "初始化文章表失败: " + err.Error()} _ = c.ServeJSON() return false } return true } func cmsParseUintArg(v interface{}) uint64 { switch x := v.(type) { case float64: if x > 0 { return uint64(x) } case string: if n, err := strconv.ParseUint(strings.TrimSpace(x), 10, 64); err == nil { return n } } return 0 } func cmsArticleToListItem(row models.CmsArticle, cateName string) map[string]interface{} { return map[string]interface{}{ "id": row.ID, "title": row.Title, "author": row.Author, "cate": cateName, "cate_id": row.CateID, "status": row.Status, "views": row.Views, "likes": row.Likes, "top": row.Top, "recommend": row.Recommend, "publish_date": models.CmsFormatTime(row.PublishTime), "update_time": models.CmsFormatTime(row.UpdateTime), } } func cmsArticleToDetail(row models.CmsArticle, cateName string) map[string]interface{} { pub := models.CmsFormatTime(row.PublishTime) return map[string]interface{}{ "id": row.ID, "title": row.Title, "author": row.Author, "cate": cateName, "cate_id": row.CateID, "content": row.Content, "desc": row.Desc, "image": row.Image, "is_trans": row.IsTrans, "transurl": row.TransURL, "status": row.Status, "views": row.Views, "view_count": row.Views, "likes": row.Likes, "top": row.Top, "recommend": row.Recommend, "publish_time": pub, "publish_date": pub, "create_time": row.CreateTime.Format("2006-01-02 15:04:05"), "update_time": models.CmsFormatTime(row.UpdateTime), } } func cmsCategoryToMap(row models.CmsArticleCategory) map[string]interface{} { return map[string]interface{}{ "id": row.ID, "name": row.Name, "label": row.Name, "cid": row.Cid, "parentId": row.Cid, "image": row.Image, "desc": row.Desc, "remark": row.Desc, "sort": row.Sort, "status": row.Status, } } // List GET /backend/articlesList func (c *BackendArticleController) List() { if !cmsEnsureTables(&c.Controller) { return } claims, err := c.cmsClaims() if err != nil { c.cmsJSONErr(401, 401, err.Error()) return } tid := cmsEffectiveTid(&c.Controller, claims) if tid == 0 { c.cmsJSONErr(400, 400, "tid不能为空") return } page, _ := c.GetInt("page", 1) pageSize, _ := c.GetInt("pageSize", 10) if page < 1 { page = 1 } if pageSize < 1 { pageSize = 10 } if pageSize > 200 { pageSize = 200 } keyword := strings.TrimSpace(c.GetString("keyword")) cateFilter := strings.TrimSpace(c.GetString("cate")) qs := models.Orm.QueryTable(new(models.CmsArticle)). Filter("tid", tid). Filter("delete_time__isnull", true) if keyword != "" { qs = qs.Filter("title__icontains", keyword) } if cateFilter != "" { if cid, err := strconv.ParseUint(cateFilter, 10, 64); err == nil && cid > 0 { qs = qs.Filter("cate_id", cid) } } total, _ := qs.Count() var rows []models.CmsArticle offset := (page - 1) * pageSize _, err = qs.OrderBy("-top", "-id").Limit(pageSize, offset).All(&rows) if err != nil && err != orm.ErrNoRows { c.cmsJSONErr(500, 500, "获取文章列表失败") return } cateIDs := make([]uint64, 0, len(rows)) for _, r := range rows { if r.CateID > 0 { cateIDs = append(cateIDs, r.CateID) } } cateNames := models.CmsCategoryNameMap(tid, cateIDs) list := make([]map[string]interface{}, 0, len(rows)) for _, r := range rows { list = append(list, cmsArticleToListItem(r, cateNames[r.CateID])) } c.Data["json"] = map[string]interface{}{ "code": 200, "msg": "success", "data": map[string]interface{}{"list": list, "total": total}, } _ = c.ServeJSON() } // ListAll GET /backend/allarticles func (c *BackendArticleController) ListAll() { c.List() } // Detail GET /backend/articles/:id func (c *BackendArticleController) Detail() { if !cmsEnsureTables(&c.Controller) { return } claims, err := c.cmsClaims() if err != nil { c.cmsJSONErr(401, 401, err.Error()) return } tid := cmsEffectiveTid(&c.Controller, claims) id, _ := c.GetUint64(":id") if id == 0 { c.cmsJSONErr(400, 400, "无效ID") return } var row models.CmsArticle err = models.Orm.QueryTable(new(models.CmsArticle)). Filter("id", id). Filter("tid", tid). Filter("delete_time__isnull", true). One(&row) if err == orm.ErrNoRows { c.cmsJSONErr(404, 404, "文章不存在") return } if err != nil { c.cmsJSONErr(500, 500, "查询失败") return } cateName := "" if row.CateID > 0 { names := models.CmsCategoryNameMap(tid, []uint64{row.CateID}) cateName = names[row.CateID] } c.Data["json"] = map[string]interface{}{ "code": 200, "msg": "success", "data": cmsArticleToDetail(row, cateName), } _ = c.ServeJSON() } type cmsArticlePayload struct { Title string `json:"title"` Author string `json:"author"` Cate interface{} `json:"cate"` Content string `json:"content"` Desc string `json:"desc"` Image string `json:"image"` IsTrans int8 `json:"is_trans"` TransURL *string `json:"transurl"` Status int8 `json:"status"` IgnoreSimilarity int `json:"ignore_similarity"` } // Create POST /backend/createarticle func (c *BackendArticleController) Create() { if !cmsEnsureTables(&c.Controller) { return } claims, err := c.cmsClaims() if err != nil { c.cmsJSONErr(401, 401, err.Error()) return } tid := cmsEffectiveTid(&c.Controller, claims) if tid == 0 { c.cmsJSONErr(400, 400, "tid不能为空") return } raw, err := io.ReadAll(c.Ctx.Request.Body) if err != nil { c.cmsJSONErr(400, 400, "参数错误") return } var p cmsArticlePayload if err := json.Unmarshal(raw, &p); err != nil { c.cmsJSONErr(400, 400, "参数错误") return } title := strings.TrimSpace(p.Title) if title == "" { c.cmsJSONErr(400, 400, "标题不能为空") return } if p.IgnoreSimilarity != 1 { similar, serr := models.CmsSimilarArticles(tid, title, 5) if serr == nil && len(similar) > 0 { c.Ctx.Output.SetStatus(409) c.Data["json"] = map[string]interface{}{ "code": 409, "msg": "检测到相似标题", "data": map[string]interface{}{"similar_articles": similar}, } _ = c.ServeJSON() return } } now := time.Now() cateID := cmsParseUintArg(p.Cate) row := models.CmsArticle{ Tid: tid, Title: title, Author: strings.TrimSpace(p.Author), CateID: cateID, Content: p.Content, Desc: strings.TrimSpace(p.Desc), Image: strings.TrimSpace(p.Image), IsTrans: p.IsTrans, TransURL: p.TransURL, Status: p.Status, CreateTime: now, UpdateTime: &now, } id, err := models.Orm.Insert(&row) if err != nil { c.cmsJSONErr(500, 500, "创建失败") return } c.Data["json"] = map[string]interface{}{"code": 200, "msg": "创建成功", "data": map[string]interface{}{"id": id}} _ = c.ServeJSON() } // Update POST /backend/editarticle/:id func (c *BackendArticleController) Update() { if !cmsEnsureTables(&c.Controller) { return } claims, err := c.cmsClaims() if err != nil { c.cmsJSONErr(401, 401, err.Error()) return } tid := cmsEffectiveTid(&c.Controller, claims) id, _ := c.GetUint64(":id") if id == 0 { c.cmsJSONErr(400, 400, "无效ID") return } raw, err := io.ReadAll(c.Ctx.Request.Body) if err != nil { c.cmsJSONErr(400, 400, "参数错误") return } var p cmsArticlePayload if err := json.Unmarshal(raw, &p); err != nil { c.cmsJSONErr(400, 400, "参数错误") return } now := time.Now() fields := map[string]interface{}{ "title": strings.TrimSpace(p.Title), "author": strings.TrimSpace(p.Author), "cate_id": cmsParseUintArg(p.Cate), "content": p.Content, "desc": strings.TrimSpace(p.Desc), "image": strings.TrimSpace(p.Image), "is_trans": p.IsTrans, "transurl": p.TransURL, "status": p.Status, "update_time": now, } n, err := models.Orm.QueryTable(new(models.CmsArticle)). Filter("id", id). Filter("tid", tid). Filter("delete_time__isnull", true). Update(fields) if err != nil { c.cmsJSONErr(500, 500, "更新失败") return } if n == 0 { c.cmsJSONErr(404, 404, "文章不存在") return } c.Data["json"] = map[string]interface{}{"code": 200, "msg": "更新成功"} _ = c.ServeJSON() } // Delete DELETE /backend/deletearticle/:id func (c *BackendArticleController) Delete() { if !cmsEnsureTables(&c.Controller) { return } claims, err := c.cmsClaims() if err != nil { c.cmsJSONErr(401, 401, err.Error()) return } tid := cmsEffectiveTid(&c.Controller, claims) id, _ := c.GetUint64(":id") if id == 0 { c.cmsJSONErr(400, 400, "无效ID") return } now := time.Now() n, err := models.Orm.QueryTable(new(models.CmsArticle)). Filter("id", id). Filter("tid", tid). Filter("delete_time__isnull", true). Update(map[string]interface{}{"delete_time": now, "update_time": now}) if err != nil { c.cmsJSONErr(500, 500, "删除失败") return } if n == 0 { c.cmsJSONErr(404, 404, "文章不存在") return } c.Data["json"] = map[string]interface{}{"code": 200, "msg": "删除成功"} _ = c.ServeJSON() } func (c *BackendArticleController) setArticleFlag(field string, value int8) { if !cmsEnsureTables(&c.Controller) { return } claims, err := c.cmsClaims() if err != nil { c.cmsJSONErr(401, 401, err.Error()) return } tid := cmsEffectiveTid(&c.Controller, claims) id, _ := c.GetUint64(":id") if id == 0 { c.cmsJSONErr(400, 400, "无效ID") return } now := time.Now() fields := map[string]interface{}{field: value, "update_time": now} n, err := models.Orm.QueryTable(new(models.CmsArticle)). Filter("id", id). Filter("tid", tid). Filter("delete_time__isnull", true). Update(fields) if err != nil { c.cmsJSONErr(500, 500, "操作失败") return } if n == 0 { c.cmsJSONErr(404, 404, "文章不存在") return } c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success"} _ = c.ServeJSON() } func (c *BackendArticleController) Publish() { if !cmsEnsureTables(&c.Controller) { return } claims, err := c.cmsClaims() if err != nil { c.cmsJSONErr(401, 401, err.Error()) return } tid := cmsEffectiveTid(&c.Controller, claims) id, _ := c.GetUint64(":id") if id == 0 { c.cmsJSONErr(400, 400, "无效ID") return } var uid uint64 raw, _ := io.ReadAll(c.Ctx.Request.Body) if len(raw) > 0 { var body struct { UID uint64 `json:"uid"` } _ = json.Unmarshal(raw, &body) uid = body.UID } if uid == 0 && claims != nil { uid = uint64(claims.UserID) } now := time.Now() fields := map[string]interface{}{ "status": int8(2), "publish_time": now, "publisher_id": uid, "update_time": now, } n, err := models.Orm.QueryTable(new(models.CmsArticle)). Filter("id", id). Filter("tid", tid). Filter("delete_time__isnull", true). Update(fields) if err != nil { c.cmsJSONErr(500, 500, "发布失败") return } if n == 0 { c.cmsJSONErr(404, 404, "文章不存在") return } c.Data["json"] = map[string]interface{}{"code": 200, "msg": "发布成功"} _ = c.ServeJSON() } func (c *BackendArticleController) Unpublish() { if !cmsEnsureTables(&c.Controller) { return } claims, err := c.cmsClaims() if err != nil { c.cmsJSONErr(401, 401, err.Error()) return } tid := cmsEffectiveTid(&c.Controller, claims) id, _ := c.GetUint64(":id") if id == 0 { c.cmsJSONErr(400, 400, "无效ID") return } now := time.Now() n, err := models.Orm.QueryTable(new(models.CmsArticle)). Filter("id", id). Filter("tid", tid). Filter("delete_time__isnull", true). Update(map[string]interface{}{"status": int8(3), "update_time": now}) if err != nil { c.cmsJSONErr(500, 500, "下架失败") return } if n == 0 { c.cmsJSONErr(404, 404, "文章不存在") return } c.Data["json"] = map[string]interface{}{"code": 200, "msg": "下架成功"} _ = c.ServeJSON() } func (c *BackendArticleController) Recommend() { c.setArticleFlag("recommend", 1) } func (c *BackendArticleController) Unrecommend() { c.setArticleFlag("recommend", 0) } func (c *BackendArticleController) Top() { c.setArticleFlag("top", 1) } func (c *BackendArticleController) Untop() { c.setArticleFlag("top", 0) } // List GET /backend/categories func (c *BackendArticleCategoryController) List() { if !cmsEnsureTables(&c.Controller) { return } claims, err := c.cmsClaims() if err != nil { c.cmsJSONErr(401, 401, err.Error()) return } tid := cmsEffectiveTid(&c.Controller, claims) if tid == 0 { c.cmsJSONErr(400, 400, "tid不能为空") return } page, _ := c.GetInt("page", 1) pageSize, _ := c.GetInt("pageSize", 0) if pageSize == 0 { pageSize, _ = c.GetInt("limit", 1000) } if page < 1 { page = 1 } if pageSize < 1 { pageSize = 1000 } keyword := strings.TrimSpace(c.GetString("keyword")) qs := models.Orm.QueryTable(new(models.CmsArticleCategory)). Filter("tid", tid). Filter("delete_time__isnull", true) if keyword != "" { qs = qs.Filter("name__icontains", keyword) } total, _ := qs.Count() var rows []models.CmsArticleCategory offset := (page - 1) * pageSize _, err = qs.OrderBy("sort", "id").Limit(pageSize, offset).All(&rows) if err != nil && err != orm.ErrNoRows { c.cmsJSONErr(500, 500, "获取分类失败") return } list := make([]map[string]interface{}, 0, len(rows)) for _, r := range rows { list = append(list, cmsCategoryToMap(r)) } c.Data["json"] = map[string]interface{}{ "code": 200, "msg": "success", "data": map[string]interface{}{"list": list, "total": total, "records": list}, } _ = c.ServeJSON() } // ListAll GET /backend/allcategories func (c *BackendArticleCategoryController) ListAll() { if !cmsEnsureTables(&c.Controller) { return } claims, err := c.cmsClaims() if err != nil { c.cmsJSONErr(401, 401, err.Error()) return } tid := cmsEffectiveTid(&c.Controller, claims) if tid == 0 { c.cmsJSONErr(400, 400, "tid不能为空") return } keyword := strings.TrimSpace(c.GetString("keyword")) qs := models.Orm.QueryTable(new(models.CmsArticleCategory)). Filter("tid", tid). Filter("delete_time__isnull", true) if keyword != "" { qs = qs.Filter("name__icontains", keyword) } var rows []models.CmsArticleCategory _, err = qs.OrderBy("sort", "id").All(&rows) if err != nil && err != orm.ErrNoRows { c.cmsJSONErr(500, 500, "获取分类失败") return } list := make([]map[string]interface{}, 0, len(rows)) for _, r := range rows { list = append(list, cmsCategoryToMap(r)) } c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success", "data": list} _ = c.ServeJSON() } // Detail GET /backend/categories/:id func (c *BackendArticleCategoryController) Detail() { if !cmsEnsureTables(&c.Controller) { return } claims, err := c.cmsClaims() if err != nil { c.cmsJSONErr(401, 401, err.Error()) return } tid := cmsEffectiveTid(&c.Controller, claims) id, _ := c.GetUint64(":id") if id == 0 { c.cmsJSONErr(400, 400, "无效ID") return } var row models.CmsArticleCategory err = models.Orm.QueryTable(new(models.CmsArticleCategory)). Filter("id", id). Filter("tid", tid). Filter("delete_time__isnull", true). One(&row) if err == orm.ErrNoRows { c.cmsJSONErr(404, 404, "分类不存在") return } if err != nil { c.cmsJSONErr(500, 500, "查询失败") return } c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success", "data": cmsCategoryToMap(row)} _ = c.ServeJSON() } type cmsCategoryPayload struct { Name string `json:"name"` Image string `json:"image"` Desc string `json:"desc"` Sort int `json:"sort"` Status int8 `json:"status"` Cid uint64 `json:"cid"` } // Create POST /backend/createCategory func (c *BackendArticleCategoryController) Create() { if !cmsEnsureTables(&c.Controller) { return } claims, err := c.cmsClaims() if err != nil { c.cmsJSONErr(401, 401, err.Error()) return } tid := cmsEffectiveTid(&c.Controller, claims) if tid == 0 { c.cmsJSONErr(400, 400, "tid不能为空") return } raw, err := io.ReadAll(c.Ctx.Request.Body) if err != nil { c.cmsJSONErr(400, 400, "参数错误") return } var p cmsCategoryPayload if err := json.Unmarshal(raw, &p); err != nil { c.cmsJSONErr(400, 400, "参数错误") return } name := strings.TrimSpace(p.Name) if name == "" { c.cmsJSONErr(400, 400, "分类名称不能为空") return } now := time.Now() row := models.CmsArticleCategory{ Tid: tid, Cid: p.Cid, Name: name, Image: strings.TrimSpace(p.Image), Desc: strings.TrimSpace(p.Desc), Sort: p.Sort, Status: p.Status, CreateTime: now, UpdateTime: &now, } id, err := models.Orm.Insert(&row) if err != nil { c.cmsJSONErr(500, 500, "创建失败") return } c.Data["json"] = map[string]interface{}{"code": 200, "msg": "创建成功", "data": map[string]interface{}{"id": id}} _ = c.ServeJSON() } // Update POST /backend/editCategory/:id func (c *BackendArticleCategoryController) Update() { if !cmsEnsureTables(&c.Controller) { return } claims, err := c.cmsClaims() if err != nil { c.cmsJSONErr(401, 401, err.Error()) return } tid := cmsEffectiveTid(&c.Controller, claims) id, _ := c.GetUint64(":id") if id == 0 { c.cmsJSONErr(400, 400, "无效ID") return } raw, err := io.ReadAll(c.Ctx.Request.Body) if err != nil { c.cmsJSONErr(400, 400, "参数错误") return } var p cmsCategoryPayload if err := json.Unmarshal(raw, &p); err != nil { c.cmsJSONErr(400, 400, "参数错误") return } now := time.Now() n, err := models.Orm.QueryTable(new(models.CmsArticleCategory)). Filter("id", id). Filter("tid", tid). Filter("delete_time__isnull", true). Update(map[string]interface{}{ "name": strings.TrimSpace(p.Name), "image": strings.TrimSpace(p.Image), "desc": strings.TrimSpace(p.Desc), "sort": p.Sort, "status": p.Status, "cid": p.Cid, "update_time": now, }) if err != nil { c.cmsJSONErr(500, 500, "更新失败") return } if n == 0 { c.cmsJSONErr(404, 404, "分类不存在") return } c.Data["json"] = map[string]interface{}{"code": 200, "msg": "更新成功"} _ = c.ServeJSON() } // Delete DELETE /backend/categories/:id func (c *BackendArticleCategoryController) Delete() { if !cmsEnsureTables(&c.Controller) { return } claims, err := c.cmsClaims() if err != nil { c.cmsJSONErr(401, 401, err.Error()) return } tid := cmsEffectiveTid(&c.Controller, claims) id, _ := c.GetUint64(":id") if id == 0 { c.cmsJSONErr(400, 400, "无效ID") return } childCnt, _ := models.Orm.QueryTable(new(models.CmsArticleCategory)). Filter("tid", tid). Filter("cid", id). Filter("delete_time__isnull", true). Count() if childCnt > 0 { c.cmsJSONErr(400, 400, "请先删除子分类") return } articleCnt, _ := models.Orm.QueryTable(new(models.CmsArticle)). Filter("tid", tid). Filter("cate_id", id). Filter("delete_time__isnull", true). Count() if articleCnt > 0 { c.cmsJSONErr(400, 400, "该分类下还有文章,无法删除") return } now := time.Now() n, err := models.Orm.QueryTable(new(models.CmsArticleCategory)). Filter("id", id). Filter("tid", tid). Filter("delete_time__isnull", true). Update(map[string]interface{}{"delete_time": now, "update_time": now}) if err != nil { c.cmsJSONErr(500, 500, "删除失败") return } if n == 0 { c.cmsJSONErr(404, 404, "分类不存在") return } c.Data["json"] = map[string]interface{}{"code": 200, "msg": "删除成功"} _ = c.ServeJSON() } // UpdateStatus PATCH /backend/categories/:id/status func (c *BackendArticleCategoryController) UpdateStatus() { if !cmsEnsureTables(&c.Controller) { return } claims, err := c.cmsClaims() if err != nil { c.cmsJSONErr(401, 401, err.Error()) return } tid := cmsEffectiveTid(&c.Controller, claims) id, _ := c.GetUint64(":id") if id == 0 { c.cmsJSONErr(400, 400, "无效ID") return } raw, err := io.ReadAll(c.Ctx.Request.Body) if err != nil { c.cmsJSONErr(400, 400, "参数错误") return } var p struct { Status int8 `json:"status"` } if err := json.Unmarshal(raw, &p); err != nil { c.cmsJSONErr(400, 400, "参数错误") return } now := time.Now() n, err := models.Orm.QueryTable(new(models.CmsArticleCategory)). Filter("id", id). Filter("tid", tid). Filter("delete_time__isnull", true). Update(map[string]interface{}{"status": p.Status, "update_time": now}) if err != nil { c.cmsJSONErr(500, 500, "更新失败") return } if n == 0 { c.cmsJSONErr(404, 404, "分类不存在") return } c.Data["json"] = map[string]interface{}{"code": 200, "msg": "更新成功"} _ = c.ServeJSON() }