package controllers import ( "strconv" "strings" "server/models" beego "github.com/beego/beego/v2/server/web" "github.com/beego/beego/v2/client/orm" ) // ============================================================= // 官网前台只读接口:平台官网内容(yz_platform_website_*) // // 供 website SPA 调用,无需登录(与 /site/setting 同级)。 // 数据由 platform 端「官网管理」模块维护;仅返回「已发布」的内容。 // // GET /site/news 新闻列表(分页 / 分类 / 关键词) // GET /site/news/categories 新闻分类 + 各分类已发布数量 // GET /site/news/:id 新闻详情(正文为 HTML) // GET /site/news/:id/related 相关推荐 // POST /site/news/:id/view 浏览量 +1 // // 说明:列表与详情中返回的 image 形如 /uploads/xxx,由前端拼接接口域名。 // ============================================================= // 文章状态:与 platform 端「文章管理」保持一致 const ( articleStatusPublished int8 = 2 ) // 导航菜单类型:与 platform 端「导航菜单」保持一致 const ( navTypeExternal int8 = 3 ) type IndexWebsiteController struct { beego.Controller } func (c *IndexWebsiteController) ok(data interface{}) { c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success", "data": data} _ = c.ServeJSON() } func (c *IndexWebsiteController) fail(httpStatus, bizCode int, msg string) { c.Ctx.Output.SetStatus(httpStatus) c.Data["json"] = map[string]interface{}{"code": bizCode, "msg": msg} _ = c.ServeJSON() } // publishedArticleQuery 已发布文章的基础查询条件 func publishedArticleCond() *orm.Condition { return orm.NewCondition(). And("delete_time__isnull", true). And("status", articleStatusPublished) } // articleListItem 列表项(不含正文,减小响应体) // image 与 category_image 分开返回,由前端决定兜底:文章没有封面时用所属分类的图片。 func articleListItem(row models.PlatformWebsiteArticle, cate models.PlatformWebsiteCategory, tags []string) map[string]interface{} { return map[string]interface{}{ "id": row.ID, "title": row.Title, "summary": row.Desc, "image": row.Image, "author": row.Author, "views": row.Views, "top": row.Top, "recommend": row.Recommend, "date": row.PublishTime, "category_id": row.CateID, "category_name": cate.Name, "category_desc": cate.Desc, "category_image": cate.Image, "tags": tags, } } // navNode 导航菜单节点(递归结构,供页头渲染) type navNode struct { ID uint64 `json:"id"` Pid uint64 `json:"pid"` Title string `json:"title"` Type int8 `json:"type"` // 1 目录 / 2 页面 / 3 外链 / 4 单页 Path string `json:"path"` Icon string `json:"icon"` External bool `json:"external"` Children []*navNode `json:"children"` } // Nav GET /site/nav // 官网页头导航:仅返回启用且显示中的菜单项,按 pid 组树。 // 父级被隐藏/禁用时其子项一并隐藏,避免出现无归属的顶层菜单。 func (c *IndexWebsiteController) Nav() { var rows []models.PlatformWebsiteNav if _, err := models.Orm.QueryTable(new(models.PlatformWebsiteNav)). Filter("delete_time__isnull", true). Filter("status", 1). Filter("is_visible", 1). OrderBy("sort", "id"). All(&rows, "ID", "Pid", "Title", "Type", "Path", "Icon"); err != nil { c.fail(500, 500, "获取导航菜单失败: "+err.Error()) return } nodes := make(map[uint64]*navNode, len(rows)) order := make([]uint64, 0, len(rows)) for _, r := range rows { nodes[r.ID] = &navNode{ ID: r.ID, Pid: r.Pid, Title: r.Title, Type: r.Type, Path: r.Path, Icon: r.Icon, External: r.Type == navTypeExternal || strings.HasPrefix(r.Path, "http://") || strings.HasPrefix(r.Path, "https://"), Children: []*navNode{}, } order = append(order, r.ID) } roots := make([]*navNode, 0, len(rows)) for _, id := range order { node := nodes[id] if node.Pid == 0 { roots = append(roots, node) continue } parent, ok := nodes[node.Pid] if !ok { continue } parent.Children = append(parent.Children, node) } c.ok(map[string]interface{}{"list": roots}) } // News GET /site/news?page=&pageSize=&category=&keyword= func (c *IndexWebsiteController) News() { page, _ := c.GetInt("page", 1) pageSize, _ := c.GetInt("pageSize", 6) if page < 1 { page = 1 } if pageSize < 1 { pageSize = 6 } if pageSize > 50 { pageSize = 50 } cond := publishedArticleCond() if cateStr := strings.TrimSpace(c.GetString("category")); cateStr != "" { if cid, err := strconv.ParseUint(cateStr, 10, 64); err == nil && cid > 0 { cond = cond.And("cate_id", cid) } } if kw := strings.TrimSpace(c.GetString("keyword")); kw != "" { cond = cond.AndCond(orm.NewCondition(). Or("title__icontains", kw). Or("desc__icontains", kw)) } qs := models.Orm.QueryTable(new(models.PlatformWebsiteArticle)).SetCond(cond) total, err := qs.Count() if err != nil { c.fail(500, 500, "获取新闻列表失败: "+err.Error()) return } var rows []models.PlatformWebsiteArticle if _, err = qs.OrderBy("-top", "-recommend", "-publish_time", "-id"). Limit(pageSize, (page-1)*pageSize). All(&rows, "ID", "CateID", "Title", "Author", "Desc", "Image", "Status", "Top", "Recommend", "Views", "PublishTime"); err != nil { c.fail(500, 500, "获取新闻列表失败: "+err.Error()) return } articleIDs := make([]uint64, 0, len(rows)) cateIDs := make([]uint64, 0, len(rows)) for _, r := range rows { articleIDs = append(articleIDs, r.ID) if r.CateID > 0 { cateIDs = append(cateIDs, r.CateID) } } cateMap := models.PlatformWebsiteCategoryMap(cateIDs) tagMap := models.PlatformWebsiteArticleTagNameMap(articleIDs) list := make([]map[string]interface{}, 0, len(rows)) for _, r := range rows { tags := tagMap[r.ID] if tags == nil { tags = []string{} } list = append(list, articleListItem(r, cateMap[r.CateID], tags)) } totalPages := int((total + int64(pageSize) - 1) / int64(pageSize)) if totalPages < 1 { totalPages = 1 } c.ok(map[string]interface{}{ "list": list, "total": total, "page": page, "pageSize": pageSize, "totalPages": totalPages, }) } // NewsCategories GET /site/news/categories // 返回启用中的分类及各自已发布文章数量,供列表页筛选条展示。 func (c *IndexWebsiteController) NewsCategories() { var cates []models.PlatformWebsiteCategory if _, err := models.Orm.QueryTable(new(models.PlatformWebsiteCategory)). Filter("delete_time__isnull", true). OrderBy("sort", "id"). All(&cates, "ID", "Name", "Desc", "Image", "Sort"); err != nil { c.fail(500, 500, "获取分类失败: "+err.Error()) return } list := make([]map[string]interface{}, 0, len(cates)) for _, item := range cates { cnt, _ := models.Orm.QueryTable(new(models.PlatformWebsiteArticle)). Filter("delete_time__isnull", true). Filter("status", articleStatusPublished). Filter("cate_id", item.ID). Count() list = append(list, map[string]interface{}{ "id": item.ID, "name": item.Name, "desc": item.Desc, "image": item.Image, "count": cnt, }) } total, _ := models.Orm.QueryTable(new(models.PlatformWebsiteArticle)). SetCond(publishedArticleCond()). Count() c.ok(map[string]interface{}{"list": list, "total": total}) } // NewsDetail GET /site/news/:id func (c *IndexWebsiteController) NewsDetail() { id, err := strconv.ParseUint(c.Ctx.Input.Param(":id"), 10, 64) if err != nil || id == 0 { c.fail(400, 400, "无效的内容ID") return } var row models.PlatformWebsiteArticle if err = models.Orm.QueryTable(new(models.PlatformWebsiteArticle)). SetCond(publishedArticleCond()). Filter("id", id). One(&row); err != nil { c.fail(404, 404, "内容不存在或已下线") return } cate := models.PlatformWebsiteCategoryMap([]uint64{row.CateID})[row.CateID] tags := models.PlatformWebsiteArticleTagNameMap([]uint64{row.ID})[row.ID] if tags == nil { tags = []string{} } c.ok(map[string]interface{}{ "id": row.ID, "title": row.Title, "summary": row.Desc, "image": row.Image, "content": row.Content, "author": row.Author, "views": row.Views, "date": row.PublishTime, "category_id": row.CateID, "category_name": cate.Name, "category_desc": cate.Desc, "category_image": cate.Image, "tags": tags, }) } // NewsRelated GET /site/news/:id/related?limit=3 // 同分类优先,不足时用其它分类补齐。 func (c *IndexWebsiteController) NewsRelated() { id, err := strconv.ParseUint(c.Ctx.Input.Param(":id"), 10, 64) if err != nil || id == 0 { c.fail(400, 400, "无效的内容ID") return } limit, _ := c.GetInt("limit", 3) if limit < 1 || limit > 12 { limit = 3 } var current models.PlatformWebsiteArticle if err = models.Orm.QueryTable(new(models.PlatformWebsiteArticle)). SetCond(publishedArticleCond()). Filter("id", id). One(¤t); err != nil { // 当前内容不存在时返回空列表,由前端展示"暂无推荐" c.ok(map[string]interface{}{"list": []interface{}{}}) return } pick := make([]models.PlatformWebsiteArticle, 0, limit) seen := map[uint64]bool{id: true} // 1. 同分类 if current.CateID > 0 { var same []models.PlatformWebsiteArticle _, _ = models.Orm.QueryTable(new(models.PlatformWebsiteArticle)). SetCond(publishedArticleCond()). Filter("cate_id", current.CateID). Exclude("id", id). OrderBy("-publish_time", "-id"). Limit(limit). All(&same, "ID", "CateID", "Title", "Author", "Desc", "Image", "Top", "Recommend", "Views", "PublishTime") for _, r := range same { if len(pick) >= limit { break } pick = append(pick, r) seen[r.ID] = true } } // 2. 不足时用最新其它文章补齐 if len(pick) < limit { ids := make([]uint64, 0, len(seen)) for k := range seen { ids = append(ids, k) } var others []models.PlatformWebsiteArticle _, _ = models.Orm.QueryTable(new(models.PlatformWebsiteArticle)). SetCond(publishedArticleCond()). Exclude("id__in", ids). OrderBy("-publish_time", "-id"). Limit(limit - len(pick)). All(&others, "ID", "CateID", "Title", "Author", "Desc", "Image", "Top", "Recommend", "Views", "PublishTime") pick = append(pick, others...) } articleIDs := make([]uint64, 0, len(pick)) cateIDs := make([]uint64, 0, len(pick)) for _, r := range pick { articleIDs = append(articleIDs, r.ID) if r.CateID > 0 { cateIDs = append(cateIDs, r.CateID) } } cateMap := models.PlatformWebsiteCategoryMap(cateIDs) tagMap := models.PlatformWebsiteArticleTagNameMap(articleIDs) list := make([]map[string]interface{}, 0, len(pick)) for _, r := range pick { tags := tagMap[r.ID] if tags == nil { tags = []string{} } list = append(list, articleListItem(r, cateMap[r.CateID], tags)) } c.ok(map[string]interface{}{"list": list}) } // NewsView POST /site/news/:id/view — 浏览量 +1(详情页打开时调用) func (c *IndexWebsiteController) NewsView() { id, err := strconv.ParseUint(c.Ctx.Input.Param(":id"), 10, 64) if err != nil || id == 0 { c.fail(400, 400, "无效的内容ID") return } if _, err = models.Orm.Raw( "UPDATE yz_platform_website_article SET views = views + 1 WHERE id = ? AND status = ? AND delete_time IS NULL", id, articleStatusPublished, ).Exec(); err != nil { c.fail(500, 500, "更新浏览量失败") return } c.ok(nil) }