GoWindGoWind
Home
Framework
GoWind Toolkit
  • Open Source

    • GoWind Admin (Admin Platform)
    • GoWind CMS (Content Management)
    • GoWind UBA (Behavior Analytics)
    • GoWind Shop (E-commerce)
    • GoWind Kanban (Kanban Boards)
    • GoWind Ledger (Personal Finance)
    • GoWind ShortPlay (Short Drama)
    • GoWind Ops (Operations Platform)
    • RushWind (Rust Ecosystem)
  • Open Core (Community + Enterprise)

    • GoWind OA (Office Collaboration)
    • GoWind ERP (Procure-to-Pay)
    • GoWind WMS (Warehousing)
  • Commercial

    • GoWind IM (Instant Messaging)
    • GoWind IoT (IoT Platform)
    • GoWind Edu (AI Education)
    • GoWind MES (Manufacturing Execution)
    • GoWind CDC (Data Pipeline)
    • GoWind SDP (Zero-Trust Gateway)
    • GoWind Quant (Quant Research)
    • GoWind Trade Radar (Export Sourcing)
    • GoWind Game Radar (Game Greenlight)
  • 简体中文
  • English
GitHub
Home
Framework
GoWind Toolkit
  • Open Source

    • GoWind Admin (Admin Platform)
    • GoWind CMS (Content Management)
    • GoWind UBA (Behavior Analytics)
    • GoWind Shop (E-commerce)
    • GoWind Kanban (Kanban Boards)
    • GoWind Ledger (Personal Finance)
    • GoWind ShortPlay (Short Drama)
    • GoWind Ops (Operations Platform)
    • RushWind (Rust Ecosystem)
  • Open Core (Community + Enterprise)

    • GoWind OA (Office Collaboration)
    • GoWind ERP (Procure-to-Pay)
    • GoWind WMS (Warehousing)
  • Commercial

    • GoWind IM (Instant Messaging)
    • GoWind IoT (IoT Platform)
    • GoWind Edu (AI Education)
    • GoWind MES (Manufacturing Execution)
    • GoWind CDC (Data Pipeline)
    • GoWind SDP (Zero-Trust Gateway)
    • GoWind Quant (Quant Research)
    • GoWind Trade Radar (Export Sourcing)
    • GoWind Game Radar (Game Greenlight)
  • 简体中文
  • English
GitHub
  • Introduction

    • GoWind CMS Product Introduction
    • GoWind CMS Installation Guide
  • Backend

    • CMS Backend Architecture Overview
    • CMS Backend Modules Overview
    • CMS Protobuf API Definitions
    • CMS Configuration and Deployment Guide
    • Backend Extension Mechanisms Guide
  • Frontend

    • CMS Frontend Architecture
    • CMS Frontend Modules Overview
  • Getting Started

    • Adding a Content Type, Full Stack in Practice
    • Multi-Client API Client Code Generation Tutorial
  • Core Tutorials

    • Content Multilingual Translation Tutorial
    • Content Publishing Workflow Tutorial
    • Section Editor in Practice
    • Headless API Multi-Client Integration in Practice
    • Frontend Application Development in Practice
  • Intermediate Tutorials

    • Multi-Site Management in Practice
    • Media Asset Management in Practice
    • Comment System Tutorial
    • Event Bus Architecture in Practice
    • Lua Script Extensions in Practice
  • Advanced Topics

    • Permission System in Practice
    • Dual-Client Login Security in Practice
    • Encryption and Security Toolkit Tutorial
    • Full-Text Search in Practice
    • Task Scheduling in Practice
    • Real-Time Message Push (SSE) in Practice
    • Performance Monitoring in Practice
    • Dictionary Management System Tutorial
  • Full-Stack Guides

    • Full-Stack Integration in Practice
    • Three-Service Deployment Tutorial

Full-Stack Integration in Practice

Through a complete "news publishing system" case study, this tutorial walks you through the entire CMS full-stack development process: from requirements analysis, data modeling, and API definition, to backend implementation, admin console, and frontend display, tying together all the core knowledge points.

Prerequisites

  • You have read the following tutorials (in order):
    • CMS Backend Architecture Overview
    • Adding a Content Type, Full Stack in Practice
    • Content Publishing Workflow in Practice
    • Content Multilingual Translation in Practice

1. Requirements Analysis

1.1 News Publishing System Requirements

1.2 Feature List

FeatureDescription
News CRUDCreate, edit, delete, restore
Multi-level categoriesPolitics/Finance/Tech/Sports, with sub-categories
Tag systemEach news article can be associated with multiple tags
Review workflowDraft → Pending review → Published / Rejected
Multilingual translationChinese/English/Japanese
Scheduled publishingAutomatic publishing at a specified time
Full-text searchFull-text search over title + content
Pinned and featuredHomepage headlines + category recommendations
View statisticsView count per article
Comment interactionReader comments + moderation

2. Data Modeling

2.1 Entity Relationships

2.2 Reusing the CMS Content Model

In fact, the CMS Post model already covers the news requirements, so it can be reused directly:

News RequirementCMS Implementation
News articlesPost
News categoriesCategory
News tagsTag
MultilingualPostTranslation
ReviewPost state machine (DRAFT→PENDING_REVIEW→PUBLISHED)
PinnedPost.pinned
Section editingSection
View countPost.viewCount

3. Backend Implementation

3.1 Extending Post Fields

If you need the news-specific "featured" field:

// content/service/v1/post.proto
message Post {
  // ... 已有字段
  optional bool featured = 34 [json_name = "featured"];  // 新增:首页推荐
}

3.2 Ent Schema Update

// app/core/service/internal/data/ent/schema/post.go
func (Post) Fields() []ent.Field {
    return []ent.Field{
        // ... 已有字段
        field.Bool("featured").Default(false),  // 新增推荐字段
    }
}
cd backend
make ent   # Regenerate the Ent code

3.3 News Query Service

// app/core/service/internal/service/post_service.go

// GetFeaturedNews 获取首页推荐新闻
func (s *PostService) GetFeaturedNews(ctx context.Context, req *contentV1.GetFeaturedRequest) (*contentV1.ListPostResponse, error) {
    query := s.postRepo.Query().
        Where(
            post.StatusEQ(int(contentV1.Post_POST_STATUS_PUBLISHED)),
            post.FeaturedEQ(true),
        ).
        Order(ent.Desc("publish_at"))

    if locale := req.GetLocale(); locale != "" {
        query = query.WithTranslations()
    }

    items, err := query.Limit(int(req.GetLimit())).All(ctx)
    if err != nil {
        return nil, err
    }

    return &contentV1.ListPostResponse{Items: convertPosts(items, req.GetLocale())}, nil
}

3.4 Frontend Whitelist

// app/app/service/internal/server/rest_server.go
rpc.AddWhiteList(
    // ... 已有白名单
    appV1.OperationPostServiceGetFeatured,  // 推荐新闻免登录
)

4. Admin Console

4.1 News Editing Page

<!-- views/content/post/form.vue -->
<script setup lang="ts">
const formData = reactive({
  title: '',
  categoryId: null,
  tagIds: [],
  status: 0,
  pinned: false,
  featured: false,      // 新增推荐开关
  publishAt: null,
  sections: [],         // 区块编辑器
});

const canPublish = computed(() => formData.status === 0 || formData.status === 3);

const handleSave = async () => {
  // 根据状态选择保存方式
  if (formData.status === 0) {
    await saveDraft();
  }
};
</script>

<template>
  <Page title="编辑新闻">
    <!-- 基本信息 -->
    <Form :model="formData" layout="vertical">
      <FormItem label="标题(中文)">
        <Input v-model:value="formData.title" />
      </FormItem>

      <Row :gutter="16">
        <Col :span="8">
          <FormItem label="分类">
            <TreeSelect v-model:value="formData.categoryId" :tree-data="categories" />
          </FormItem>
        </Col>
        <Col :span="8">
          <FormItem label="标签">
            <Select v-model:value="formData.tagIds" mode="multiple">
              <SelectOption v-for="t in tags" :key="t.id" :value="t.id">{{ t.name }}</SelectOption>
            </Select>
          </FormItem>
        </Col>
        <Col :span="8">
          <FormItem label="定时发布">
            <DatePicker v-model:value="formData.publishAt" show-time />
          </FormItem>
        </Col>
      </Row>

      <Row :gutter="16">
        <Col :span="6">
          <FormItem label="置顶">
            <Switch v-model:checked="formData.pinned" />
          </FormItem>
        </Col>
        <Col :span="6">
          <FormItem label="首页推荐">
            <Switch v-model:checked="formData.featured" />
          </FormItem>
        </Col>
      </Row>
    </Form>

    <!-- 区块编辑器 -->
    <SectionEditor v-model="formData.sections" />

    <!-- 多语言翻译 -->
    <TranslationTabs :post-id="editId" />

    <!-- 操作按钮 -->
    <Space class="mt-4">
      <Button type="primary" @click="saveDraft">保存草稿</Button>
      <Button @click="submitReview" v-if="formData.status === 0">提交审核</Button>
      <Button type="primary" @click="publish" v-if="canPublish">发布</Button>
    </Space>
  </Page>
</template>

4.2 News List

<!-- views/content/post/index.vue -->
<template>
  <Page title="新闻管理">
    <Tabs v-model:activeKey="filterStatus">
      <TabPane key="" tab="全部" />
      <TabPane key="0" tab="草稿" />
      <TabPane key="3" tab="待审核" />
      <TabPane key="1" tab="已发布" />
      <TabPane key="2" tab="已下架" />
    </Tabs>

    <Table :data="data?.items" :loading="isLoading">
      <TableColumn title="标题" data-index="title" />
      <TableColumn title="分类" data-index="category.name" />
      <TableColumn title="状态">
        <template #default="{ record }">
          <Tag :color="statusColors[record.status]">{{ statusLabels[record.status] }}</Tag>
        </template>
      </TableColumn>
      <TableColumn title="浏览" data-index="viewCount" />
      <TableColumn title="推荐">
        <template #default="{ record }">
          <Tag v-if="record.featured" color="gold">推荐</Tag>
          <Tag v-if="record.pinned" color="red">置顶</Tag>
        </template>
      </TableColumn>
      <TableColumn title="操作">
        <template #default="{ record }">
          <Button size="small" @click="goEdit(record.id)">编辑</Button>
          <Button v-if="record.status === 3" type="primary" size="small" @click="approve(record.id)">通过</Button>
        </template>
      </TableColumn>
    </Table>
  </Page>
</template>

5. Frontend Display

5.1 Home Page (Featured + Latest)

// src/app/[locale]/page.tsx
export default async function HomePage({ params: { locale } }: { params: { locale: string } }) {
  const [featured, latest, categories] = await Promise.all([
    getPosts({ locale, featured: true, pageSize: 5 }),
    getPosts({ locale, page: 1, pageSize: 10 }),
    getCategories(locale),
  ]);

  return (
    <div className="container mx-auto px-4 py-8">
      {/* 头条新闻 */}
      {featured.items[0] && (
        <section className="mb-12">
          <FeaturedNewsCard post={featured.items[0]} locale={locale} />
        </section>
      )}

      <div className="grid grid-cols-1 lg:grid-cols-3 gap-8">
        {/* 最新新闻 */}
        <div className="lg:col-span-2">
          <h2 className="text-2xl font-bold mb-6">最新新闻</h2>
          <div className="space-y-6">
            {latest.items.map((post) => (
              <NewsCard key={post.id} post={post} locale={locale} />
            ))}
          </div>
          <Pagination current={1} total={latest.total} pageSize={10} />
        </div>

        {/* 侧边栏 */}
        <aside className="space-y-6">
          <CategoryWidget categories={categories.items} />
          <PopularTagsWidget />
          <PopularNewsWidget />
        </aside>
      </div>
    </div>
  );
}

5.2 News Detail Page

// src/app/[locale]/news/[slug]/page.tsx
export default async function NewsDetail({ params: { locale, slug } }) {
  const post = await getPostBySlug(slug, locale);

  // 增加浏览量
  trackView(post.id);

  return (
    <article className="container mx-auto px-4 py-8 max-w-3xl">
      {/* Breadcrumb */}
      <nav className="text-sm mb-4">
        <Link href={`/${locale}`}>首页</Link> &gt;
        <Link href={`/${locale}/categories/${post.category.slug}`}>{post.category.name}</Link>
      </nav>

      {/* 文章头 */}
      <header className="mb-8">
        <h1 className="text-3xl font-bold mb-4">{post.title}</h1>
        <div className="flex items-center gap-4 text-gray-500">
          <span>记者:{post.author.name}</span>
          <time>{formatDate(post.publishAt, locale)}</time>
          <span>浏览:{post.viewCount}</span>
        </div>
      </header>

      {/* 区块内容 */}
      <SectionRenderer sections={post.sections} />

      {/* 标签 */}
      <div className="flex gap-2 mt-8">
        {post.tags.map((tag) => (
          <Link key={tag.slug} href={`/${locale}/tags/${tag.slug}`}>
            #{tag.name}
          </Link>
        ))}
      </div>

      {/* 评论区 */}
      <CommentSection postId={post.id} />

      {/* 相关新闻 */}
      <RelatedNews categoryId={post.category.id} excludeId={post.id} locale={locale} />
    </article>
  );
}

5.3 Language Switching

// 语言切换自动获取对应翻译
function LanguageSwitcher({ currentLocale, slug }: { currentLocale: string; slug: string }) {
  return (
    <select
      value={currentLocale}
      onChange={(e) => router.push(`/${e.target.value}/news/${slug}`)}
    >
      <option value="zh-CN">中文</option>
      <option value="en-US">English</option>
      <option value="ja-JP">日本語</option>
    </select>
  );
}

6. SEO Optimization

6.1 Metadata

export async function generateMetadata({ params: { locale, slug } }) {
  const post = await getPostBySlug(slug, locale);

  return {
    title: post.title,
    description: post.summary,
    alternates: {
      canonical: `/${locale}/news/${slug}`,
      languages: {
        'zh-CN': `/zh-CN/news/${slug}`,
        'en-US': `/en-US/news/${slug}`,
        'ja-JP': `/ja-JP/news/${slug}`,
      },
    },
    openGraph: {
      type: 'article',
      title: post.title,
      images: post.thumbnail ? [post.thumbnail] : [],
    },
  };
}

// ISR 增量静态再生
export const revalidate = 60;

6.2 Structured Data

<JsonLd data={{
  "@context": "https://schema.org",
  "@type": "NewsArticle",
  headline: post.title,
  datePublished: post.publishAt,
  author: { "@type": "Person", name: post.author.name },
  publisher: { "@type": "Organization", name: siteName },
}} />

7. Event Integration

7.1 News Publishing Event Chain

7.2 Lua Automation

-- 新闻发布后自动推送到社交媒体
eventbus.subscribe("post.published", function(event)
    if event.CategorySlug ~= "news" then return end

    -- 生成短链接
    local url = "https://news.example.com/posts/" .. event.PostId

    -- 推送微博
    http.post("https://api.weibo.com/2/statuses/update.json", {
        headers = { ["Authorization"] = "Bearer weibo-token" },
        body = json.encode({ status = event.Title .. " " .. url })
    })
end)

8. Checklist

Check ItemDescription
Data modelingReuse the Post model + extension fields
Backend APIFeatured news endpoint + frontend whitelist
Admin consoleEdit page (sections + translations + workflow) + list page
Frontend displayHome + detail + categories + tags
MultilingualChinese/English/Japanese switching
SEOMetadata + structured data + ISR
Event integrationPublish event + Lua automation
SearchOpenSearch full-text search
CommentsComment moderation + interaction
CacheRedis hot-spot caching

Related Documents

  • Adding a Content Type, Full Stack in Practice
  • Content Publishing Workflow in Practice
  • Content Multilingual Translation in Practice
  • Section Editor in Practice
  • Frontend Application Development in Practice
Edit this page
Last Updated:: 11/3/26, 2:14 PM
Contributors: Bobo
Next
Three-Service Deployment Tutorial