Files
yunzerwebsiteallinone/go/controllers/index_website.go
T
2026-09-17 23:00:06 +08:00

393 lines
12 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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(&current); 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)
}