增加网站管理功能
This commit is contained in:
@@ -0,0 +1,392 @@
|
||||
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)
|
||||
}
|
||||
Reference in New Issue
Block a user