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

Headless API Multi-Client Integration in Practice

As a Headless content platform, the core value of GoWind CMS lies in "create once, distribute everywhere". This tutorial takes a deep dive into the dual-protocol design of the Admin API and App API, the public frontend endpoint whitelist mechanism, and how to integrate the four frontends (Vue/React/Taro/Flutter) from scratch.

Prerequisites

  • You have read CMS Backend Architecture Overview and CMS API Definitions
  • Understand basic concepts of RESTful APIs and JWT authentication
  • The local development environment is set up

1. Headless Architecture Recap

1.1 What Is a Headless CMS

Traditional CMS: the backend and frontend are coupled (such as WordPress PHP templates), and the display approach is dictated by the backend.

Headless CMS: the backend only handles content management and API output, while the frontend is fully independent and can connect to any client.

1.2 CMS Dual-Protocol API

APIRoute PrefixPortAudiencePermission Requirements
Admin API/admin/v1/6600Admin console operatorsJWT authentication + Casbin permissions
App API/app/v1/6700Frontend end users (C-side)Some endpoints unauthenticated, the rest require JWT

1.3 Why a Dual Protocol Is Needed

2. App API Endpoints in Detail

2.1 Endpoints Provided by the App Service

The App API only exposes the features the frontend needs, greatly simplified compared to the Admin API:

ServiceRouteMethodLogin-FreeDescription
Authentication/app/v1/loginPOSTYesFrontend user login
Authentication/app/v1/logoutPOSTNoLog out
Post/app/v1/postsGETYesPost list (pagination, language supported)
Post/app/v1/posts/{id}GETYesPost detail
Post/app/v1/postsPOSTNoUser posting (login required)
Category/app/v1/categoriesGETYesCategory list
Category/app/v1/categories/{id}GETYesCategory detail
Tag/app/v1/tagsGETYesTag list
Tag/app/v1/tags/{id}GETYesTag detail
Comment/app/v1/commentsGETYesComment list
Comment/app/v1/commentsPOSTNoPost a comment (login required)
Page/app/v1/pagesGETYesPage list
Page/app/v1/pages/{id}GETYesPage detail
Navigation/app/v1/navigationsGETYesNavigation menus
UserProfile/app/v1/user-profileGETNoUser profile
FileTransfer/app/v1/filesPOSTNoFile upload

2.2 Authentication-Free Whitelist

The App Service configures a whitelist of login-free endpoints in rest_server.go:

// app/app/service/internal/server/rest_server.go
rpc.AddWhiteList(
    // 认证接口
    appV1.OperationAuthenticationServiceLogin,

    // 内容浏览接口(免登录)
    appV1.OperationNavigationServiceList,
    appV1.OperationPageServiceList,
    appV1.OperationPostServiceList,
    appV1.OperationCategoryServiceList,
    appV1.OperationCommentServiceList,
    appV1.OperationTagServiceList,

    appV1.OperationPageServiceGet,
    appV1.OperationPostServiceGet,
    appV1.OperationCategoryServiceGet,
    appV1.OperationCommentServiceGet,
    appV1.OperationTagServiceGet,
)

Design principles:

  • Browsing endpoints (List / Get) are login-free → good for SEO and user conversion
  • Interaction endpoints (creating comments, posting, file upload) require login → prevents abuse
  • Profile endpoints require login → protects user privacy

2.3 Frontend Authentication Mechanism

App Service login automatically marks the token type as app:

// app/app/service/internal/service/authentication_service.go
func (s *AuthenticationService) Login(
    ctx context.Context, req *authenticationV1.LoginRequest,
) (*authenticationV1.LoginResponse, error) {
    // 标记为前台用户令牌
    req.ClientType = trans.Ptr(authenticationV1.ClientType_app)
    return s.authenticationServiceClient.Login(ctx, req)
}

This way the backend can distinguish frontend user tokens from admin console tokens, preventing cross-client usage.

3. API Calling Conventions

3.1 Unified Pagination Protocol

All list endpoints use unified pagination parameters:

GET /app/v1/posts?page=1&pageSize=10&orderBy=createdAt&orderDesc=true&search=关键词
ParameterTypeDefaultDescription
pageint1Current page number
pageSizeint10Items per page
orderBystringcreatedAtSort field
orderDescboolfalseDescending order or not
searchstring-Full-text search keyword

Response format:

{
  "items": [...],
  "total": 150
}

3.2 Multilingual Parameter

Use the locale query parameter to specify the language of the returned content:

# 获取中文内容
GET /app/v1/posts?locale=zh-CN

# 获取英文内容
GET /app/v1/posts?locale=en-US

# 获取帖子详情(指定语言)
GET /app/v1/posts/42?locale=ja-JP

3.3 Authentication Flow

3.4 Error Responses

Unified error response format:

{
  "code": 401,
  "reason": "UNAUTHORIZED",
  "message": "未登录或登录已过期",
  "metadata": {}
}

Common error codes:

HTTP Status CodereasonDescription
400INVALID_ARGUMENTInvalid parameter
401UNAUTHORIZEDNot authenticated
403FORBIDDENNo permission
404NOT_FOUNDResource not found
500INTERNALInternal server error

4. Integrating the React Frontend (Next.js)

4.1 API Client Wrapper

// src/lib/api/client.ts
import axios from 'axios';

const API_BASE_URL = process.env.NEXT_PUBLIC_API_BASE_URL || 'http://localhost:6700/app/v1';

export const apiClient = axios.create({
  baseURL: API_BASE_URL,
  timeout: 10000,
  headers: {
    'Content-Type': 'application/json',
  },
});

// 请求拦截:自动注入 Token 和 locale
apiClient.interceptors.request.use((config) => {
  const token = localStorage.getItem('token');
  if (token) {
    config.headers.Authorization = `Bearer ${token}`;
  }
  return config;
});

// 响应拦截:统一错误处理
apiClient.interceptors.response.use(
  (response) => response.data,
  (error) => {
    if (error.response?.status === 401) {
      localStorage.removeItem('token');
      window.location.href = '/login';
    }
    return Promise.reject(error);
  },
);

4.2 API Function Wrappers

// src/lib/api/post.ts
import { apiClient } from './client';

export interface PostListParams {
  locale?: string;
  page?: number;
  pageSize?: number;
  orderBy?: string;
  orderDesc?: boolean;
  search?: string;
}

export async function getPosts(params: PostListParams = {}) {
  return apiClient.get('/posts', { params });
}

export async function getPostBySlug(slug: string, locale: string) {
  return apiClient.get(`/posts/${slug}`, { params: { locale } });
}

export async function getPostsByCategory(categoryId: number, locale: string, page = 1) {
  return apiClient.get('/posts', {
    params: { locale, page, categoryId },
  });
}

export async function getPostsByTag(tagId: number, locale: string, page = 1) {
  return apiClient.get('/posts', {
    params: { locale, page, tagId },
  });
}

4.3 SSR Page Component

// src/app/[locale]/page.tsx(首页)
import { getPosts, getNavigations, getCategories } from '@/lib/api';

export default async function HomePage({
  params: { locale },
}: {
  params: { locale: string };
}) {
  const [postsRes, navRes, catRes] = await Promise.all([
    getPosts({ locale, page: 1, pageSize: 10 }),
    getNavigations(),
    getCategories({ locale }),
  ]);

  return (
    <div className="container mx-auto">
      {/* 导航栏 */}
      <nav>
        {navRes.items.map((item) => (
          <Link key={item.id} href={item.url}>{item.title}</Link>
        ))}
      </nav>

      {/* 文章列表 */}
      <main className="grid grid-cols-3 gap-6">
        {postsRes.items.map((post) => (
          <article key={post.id}>
            <Link href={`/${locale}/posts/${post.slug}`}>
              <h2>{post.title}</h2>
              <p>{post.summary}</p>
            </Link>
          </article>
        ))}
      </main>

      {/* 侧边栏分类 */}
      <aside>
        <h3>分类</h3>
        {catRes.items.map((cat) => (
          <Link key={cat.id} href={`/${locale}/categories/${cat.slug}`}>
            {cat.name}
          </Link>
        ))}
      </aside>
    </div>
  );
}

4.4 Authentication and Comments

// src/components/CommentSection.tsx
'use client';
import { useState } from 'react';
import { apiClient } from '@/lib/api/client';

export function CommentSection({ postId }: { postId: number }) {
  const [content, setContent] = useState('');
  const [comments, setComments] = useState([]);

  const handleSubmit = async () => {
    try {
      const newComment = await apiClient.post('/comments', {
        postId,
        content,
      });
      setComments([...comments, newComment]);
      setContent('');
    } catch (error) {
      if (error.response?.status === 401) {
        alert('请先登录');
      }
    }
  };

  return (
    <div>
      <h3>评论</h3>
      {comments.map((c) => (
        <div key={c.id}>{c.content}</div>
      ))}
      <textarea
        value={content}
        onChange={(e) => setContent(e.target.value)}
      />
      <button onClick={handleSubmit}>发表评论</button>
    </div>
  );
}

5. Integrating the Flutter Frontend

5.1 Retrofit API Auto-Generation

The Flutter version generates the API client from the OpenAPI document via swagger_parser:

# build.yaml
targets:
  $default:
    builders:
      swagger_parser:
        options:
          output_dir: 'lib/core/network/generated'
          api_base_url: 'http://localhost:6700/app/v1'
# 从 OpenAPI 生成 Dart API 代码
dart run swagger_parser

5.2 The Generated API Client

// lib/core/network/generated/api/post_api.dart(自动生成)
@RestApi(baseUrl: '/app/v1')
abstract class PostApi {
  factory PostApi(Dio dio, {String? baseUrl}) = _PostApi;

  @GET('/posts')
  Future<ListPostResponse> listPosts({
    @Query('locale') String? locale,
    @Query('page') int? page,
    @Query('pageSize') int? pageSize,
    @Query('search') String? search,
  });

  @GET('/posts/{id}')
  Future<Post> getPost(
    @Path('id') int id, {
    @Query('locale') String? locale,
  });
}

5.3 Calling with the BLoC Pattern

// features/post_list/presentation/bloc/post_list_bloc.dart
class PostListBloc extends Bloc<PostListEvent, PostListState> {
  final PostApi postApi;
  final String locale;

  PostListBloc({required this.postApi, required this.locale})
      : super(PostListInitial()) {
    on<FetchPosts>(_onFetchPosts);
    on<RefreshPosts>(_onRefreshPosts);
    on<LoadMorePosts>(_onLoadMorePosts);
  }

  int _currentPage = 1;
  List<Post> _allPosts = [];

  Future<void> _onFetchPosts(
    FetchPosts event,
    Emitter<PostListState> emit,
  ) async {
    emit(PostListLoading());
    try {
      _currentPage = 1;
      final response = await postApi.listPosts(
        locale: locale,
        page: _currentPage,
        pageSize: 10,
      );
      _allPosts = response.items;
      emit(PostListLoaded(_allPosts, hasMore: response.items.length >= 10));
    } catch (e) {
      emit(PostListError(e.toString()));
    }
  }

  Future<void> _onLoadMorePosts(
    LoadMorePosts event,
    Emitter<PostListState> emit,
  ) async {
    try {
      _currentPage++;
      final response = await postApi.listPosts(
        locale: locale,
        page: _currentPage,
        pageSize: 10,
      );
      _allPosts = [..._allPosts, ...response.items];
      emit(PostListLoaded(_allPosts, hasMore: response.items.length >= 10));
    } catch (e) {
      _currentPage--;
      emit(PostListLoaded(_allPosts, hasMore: true, error: e.toString()));
    }
  }
}

5.4 Dio Interceptor Configuration

// lib/core/network/dio_client.dart
class DioClient {
  static Dio create() {
    final dio = Dio(BaseOptions(
      baseUrl: 'http://localhost:6700/app/v1',
      connectTimeout: Duration(seconds: 10),
      receiveTimeout: Duration(seconds: 15),
    ));

    // 请求拦截:注入 Token
    dio.interceptors.add(InterceptorsWrapper(
      onRequest: (options, handler) {
        final token = TokenStorage.getToken();
        if (token != null) {
          options.headers['Authorization'] = 'Bearer $token';
        }
        handler.next(options);
      },
      onError: (error, handler) {
        if (error.response?.statusCode == 401) {
          // Token 过期,跳转登录
          TokenStorage.clear();
          NavigationService.navigateTo('/login');
        }
        handler.next(error);
      },
    ));

    return dio;
  }
}

6. Integrating the Taro Mini Program

6.1 API Wrapper

// src/services/api.ts
import Taro from '@tarojs/taro';

const BASE_URL = 'http://localhost:6700/app/v1';

export function request<T = any>(
  url: string,
  options: { method?: keyof Taro.request.Method; data?: any } = {},
): Promise<T> {
  const token = Taro.getStorageSync('token');
  return new Promise((resolve, reject) => {
    Taro.request({
      url: `${BASE_URL}${url}`,
      method: options.method || 'GET',
      data: options.data,
      header: {
        'Content-Type': 'application/json',
        ...(token ? { Authorization: `Bearer ${token}` } : {}),
      },
      success: (res) => {
        if (res.statusCode >= 200 && res.statusCode < 300) {
          resolve(res.data as T);
        } else if (res.statusCode === 401) {
          Taro.navigateTo({ url: '/pages/login/index' });
          reject(new Error('未登录'));
        } else {
          reject(new Error(res.data?.message || '请求失败'));
        }
      },
      fail: reject,
    });
  });
}

export const api = {
  getPosts: (params: { locale?: string; page?: number }) =>
    request('/posts', { data: params }),

  getPost: (id: number, locale?: string) =>
    request(`/posts/${id}`, { data: { locale } }),

  login: (data: { username: string; password: string }) =>
    request('/login', { method: 'POST', data }),

  createComment: (data: { postId: number; content: string }) =>
    request('/comments', { method: 'POST', data }),
};

6.2 Calling from Pages

// src/pages/posts/index.tsx
import { View, Text } from '@tarojs/components';
import { useState, useEffect } from 'react';
import { api } from '@/services/api';

export default function PostsPage() {
  const [posts, setPosts] = useState([]);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    api.getPosts({ locale: 'zh-CN', page: 1 }).then((res) => {
      setPosts(res.items);
      setLoading(false);
    });
  }, []);

  if (loading) return <Text>加载中...</Text>;

  return (
    <View>
      {posts.map((post) => (
        <View key={post.id} onClick={() => navigateToDetail(post.id)}>
          <Text>{post.title}</Text>
          <Text>{post.summary}</Text>
        </View>
      ))}
    </View>
  );
}

7. Custom Third-Party Integration

7.1 Calling from Any HTTP Client

The App API is a standard RESTful API that can be integrated from any language:

# Python 对接示例
import requests

API_BASE = "http://localhost:6700/app/v1"

# 获取文章列表
response = requests.get(f"{API_BASE}/posts", params={
    "locale": "zh-CN",
    "page": 1,
    "pageSize": 10,
})
posts = response.json()

# 登录获取 Token
login_resp = requests.post(f"{API_BASE}/login", json={
    "username": "user@example.com",
    "password": "password123",
})
token = login_resp.json()["token"]

# 发表评论(需认证)
requests.post(
    f"{API_BASE}/comments",
    json={"postId": 42, "content": "很棒的文章!"},
    headers={"Authorization": f"Bearer {token}"},
)

7.2 Webhook Integration

After content is published, the event bus triggers Webhook notifications to third parties:

-- 监听帖子发布事件,触发 Webhook
eventbus.subscribe("post.published", function(event)
    local post_id = event.post_id
    local post = api.get("/app/v1/posts/" .. post_id)

    -- 发送到第三方系统
    http.post("https://your-webhook.com/content", {
        headers = { ["Content-Type"] = "application/json" },
        body = json.encode({
            event = "post.published",
            data = post,
            timestamp = os.time()
        })
    })
end)

8. Performance Optimization

8.1 Caching Strategy

LayerCaching StrategyTTLUse Case
CDNStatic assets + page cacheLong termSSR pages, images
Application layerRedis caches hot content5-15 minutesPost lists, categories
DatabaseQuery cacheReal timeLatest data

8.2 Frontend Cache Configuration

The global cache strategy can be configured in site settings:

GET /app/v1/site-settings
{
  "cache": {
    "enabled": true,
    "ttl": 600,
    "strategy": "stale-while-revalidate"
  }
}

9. Security Considerations

9.1 API Protection

RiskProtection Measure
Brute-force attacksRate limiting on the login endpoint (IP + account)
DDoSAPI gateway rate limiting + WAF
Data scrapingFrequency limits + anomaly detection
XSSHTML escaping of content output
CSRFToken authentication (not cookies)

9.2 CORS Configuration

# app/app/service/configs/server.yaml
server:
  rest:
    cors:
      enabled: true
      allow_origins:
        - "https://vue.cms.gowind.cloud"
        - "https://react.cms.gowind.cloud"
        - "https://taro.cms.gowind.cloud"
      allow_methods: [GET, POST, PUT, DELETE]
      allow_headers: [Authorization, Content-Type]

10. Checklist

Check ItemDescription
App Service whitelist configurationBrowsing endpoints are login-free
Token type distinctionFrontend ClientType=app
Unified pagination protocolpage / pageSize / orderBy
Multilingual parameterlocale query parameter
Frontend API clientEncapsulates authentication and error handling
CORS configurationAllows cross-origin requests from frontend domains
Caching strategyReasonable TTL settings

Related Documents

  • CMS Backend Architecture Overview
  • CMS Protobuf API Definitions
  • CMS Frontend Architecture
  • Content Multilingual Translation in Practice
  • Frontend Application Development in Practice
Edit this page
Last Updated:: 11/3/26, 2:14 PM
Contributors: Bobo
Prev
Section Editor in Practice
Next
Frontend Application Development in Practice