
1前置准备
- 安装 Node.js (用于 Strapi)
- 安装 Go 环境 (用于自动化脚本)
- 安装 Hugo (用于前端静态化)
2初始化工作目录与前端 (Hugo)
我们创建一个统一的工作目录来存放所有代码。
# 创建工作目录并进入
mkdir -p ~/project && cd ~/project
# 初始化 Hugo 前端项目,命名为 frontend
hugo new site frontend
cd frontend
# 创建 themes 文件夹
mkdir -p themes
# 将 PaperMod 主题克隆到 themes/PaperMod 目录下 (加上 --depth=1 可以让下载极快)
git clone https://github.com/adityatelange/hugo-PaperMod themes/PaperMod --depth=1
# 启用主题
echo 'theme = "PaperMod"' >> hugo.toml
# 预览
hugo server -D --bind 0.0.0.0 --port 1314
此时,你的 frontend 目录下已经有了 Hugo 的基础结构(包含 content 目录用于存放文章)。
3初始化与配置后端 (Strapi)
1. 创建并启动 Strapi 项目
在 ~/project 目录下执行:
# 创建名为 backend 的 Strapi 项目,并默认使用 SQLite 数据库快速启动,
# 我们也可以指定其他数据库,支持的数据库有 sqlite、postgres、mysql。
pnpm create strapi-app@latest backend
如果不是用默认的 SQLite,需要提前现在本地创建好其他的数据库,以便在创建项目时配置连接。

看到下面返回信息则表示安装完成。

安装完成后根据返回的提示命令 pnpm run develop 在 http://localhost:1337/admin 启动。
2. 注册超级管理员
打开浏览器,访问 http://localhost:1337/admin。系统会要求你创建一个管理员账号(填入用户名、邮箱、密码)。这是你的绝对控制权账号。

选择跳过:

首页展示:

3. 创建数据模型 (建表) 登录后,按照以下步骤为客户建一个“文章”表:
点击左侧菜单栏的 “Content-Type Builder”。
点击 “Create new collection type”,在 Display name 中输入
Article,点击 Continue。
依次添加三个字段(Fields):
选 Text,命名为
Title(短文本)。选 Rich Text,命名为
Content。选 Text,命名为
Slug(短文本,用于生成网址路径,比如填about-us)。

如果还要继续添加字段可以点击右下角的 【Add another field】,如果全部添加完成则点击【Finish】

- 点击左上角的 “Save”,Strapi 会自动重启并生成数据库表。
4. 录入第一条测试数据
点击左侧菜单的 “Content Manager”,选择
Article。点击 “Create new entry”。

填入测试内容(Title:
测试文章, Slug:test-01, Content:这是一篇测试文章)。点击 “Save”,然后一定要点击 “Publish”(发布)。

5. 开放 API 访问权限 (就是之前让你配置的那一步)
点击左侧最下方的 “Settings”。
找到 “Users & Permissions Plugin” 下的 “Roles”,点击 “Public”。

在权限列表中找到
Article,勾选find(获取列表)和findOne(获取单条)。点击右上角 “Save”。

测试一下:在浏览器访问 http://localhost:1337/api/articles,如果能看到一串包含你刚才录入内容的 JSON 数据,说明后端彻底搞定了。

4编写自动化监听脚本 (Golang)
这个脚本的作用是:一直在后台盯着,一旦 Strapi 有变动,它就把数据拉下来,塞给 Hugo 重新生成网页。
1. 初始化 Go 项目
cd ~/project
mkdir connector && cd connector
go mod init connector
2. 编写代码
代码片段(这里的路径已经匹配了我们在第二阶段创建的 frontend 目录):
package web
import (
"fmt"
"io"
"log"
"net/http"
"os"
"os/exec"
"path/filepath"
"encoding/json"
"text/template"
"github.com/gin-gonic/gin"
"go.uber.org/zap"
)
func (h *StrapiHandler) RegisterRoutes(server *gin.Engine) {
// 微信公众平台 (Official Account) 消息推送网关与管理接口
g := server.Group("/portal")
{
g.POST("/webhook", h.webhookHandler) // portal 门户、入口站点需对微信服务器免登录放行
}
}
func (h *StrapiHandler) webhookHandler(ctx *gin.Context) {
log.Println("检测到内容更新,开始自动化构建...")
go func() {
if err := h.syncDataAndBuild(); err != nil {
log.Printf("❌ 构建流程出错: %v\n", err)
} else {
log.Println("✅ 静态官网已成功生成至 ../frontend/public 目录!")
}
}()
ctx.JSON(http.StatusOK, gin.H{"status": "building"})
}
func (h *StrapiHandler) syncDataAndBuild() error {
// 1. 获取 Strapi 数据
resp, err := http.Get("http://localhost:1337/api/articles")
if err != nil {
return fmt.Errorf("请求 Strapi 接口失败: %v", err)
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
return fmt.Errorf("读取 Strapi 响应失败: %v", err)
}
// 拦截非 200 状态码(比如 403 权限未开)
if resp.StatusCode != http.StatusOK {
return fmt.Errorf("Strapi 返回异常状态码: %d, 响应体: %s", resp.StatusCode, string(body))
}
var result StrapiResponse
if err := json.Unmarshal(body, &result); err != nil {
return fmt.Errorf("解析 JSON 失败: %v, 原始数据: %s", err, string(body))
}
log.Printf("成功从 Strapi 拉取到 %d 篇文章", len(result.Data))
// 2. 写入 Hugo 目录
postsDir := "../frontend/content/posts/"
os.RemoveAll(postsDir)
if err := os.MkdirAll(postsDir, 0755); err != nil {
return fmt.Errorf("创建文章目录失败: %v", err)
}
tmpl, err := template.New("md").Parse(mdTemplate)
if err != nil {
return fmt.Errorf("解析 Markdown 模板失败: %v", err)
}
for _, item := range result.Data {
// 修改这里,直接访问 item.Slug
slug := item.Slug
if slug == "" {
slug = fmt.Sprintf("article-%d", item.Id)
log.Printf("⚠️ 警告: 文章《%s》的 Slug 为空,自动重命名为: %s", item.Title, slug)
}
fileName := filepath.Join(postsDir, fmt.Sprintf("%s.md", slug))
file, err := os.Create(fileName)
if err != nil {
log.Printf("❌ 创建文件 %s 失败: %v", fileName, err)
continue
}
err = tmpl.Execute(file, item)
if err != nil {
log.Printf("❌ 写入模板内容至 %s 失败: %v", fileName, err)
}
file.Close()
}
// 3. 执行 Hugo 构建
cmd := exec.Command("hugo")
cmd.Dir = "../frontend"
// 核心修复:捕获 Hugo 的完整输出信息,而不仅仅是退出状态码
output, err := cmd.CombinedOutput()
if err != nil {
return fmt.Errorf("Hugo 构建失败: %v\nHugo 详细报错信息:\n%s", err, string(output))
}
return nil
}
// 彻底适配 Strapi v5 拍平后的 JSON 结构
type StrapiResponse struct {
Data []struct {
Id int `json:"id"`
Title string `json:"title"`
Description string `json:"description"` // 匹配你的 JSON 里的 description
Slug string `json:"slug"`
PublishedAt string `json:"publishedAt"` // 直接复用 Strapi 的发布时间,解决未来时间不渲染的问题
} `json:"data"`
}
// 去掉 Attributes 前缀,直接渲染,并自动使用真实的发布时间
const mdTemplate = `---
title: "{{.Title}}"
slug: "{{.Slug}}"
date: {{.PublishedAt}}
---
{{.Description}}
`
3. 运行服务
5打通最后一步 (配置 Webhook)
现在 Go 监听服务已经在 8080 端口跑起来了,我们要告诉 Strapi:有内容更新时,去敲这个 8080 端口的门。
回到浏览器的 Strapi 后台。
点击左侧 “Settings”,在 Global Settings 下找到 “Webhooks”。
点击 “Create new webhook”。

Name 随便填(例如:
Notify_Frontend_to_Update)。URL 填入:
http://localhost:8080/portal/webhook。Events 勾选
Entry这一行(代表新建、修改、删除文章都会触发)。
点击右上角 “Save”。
6验证全链路
现在,你可以去 Strapi 后台新建一篇文章并点击发布。
切回服务器终端,你会看到 Go 脚本立刻打印出:检测到内容更新,开始自动化构建...静态官网已生成...。

此时你去 ~/project/frontend/public 目录下看,最新的 .html 静态页面已经自动生成好了。企业客户的官网瞬间完成了更新。
7高级使用
你能敏锐地意识到现在使用的 “超级管理员账号”, 我们“不能把超级管理员账号给客户”。
超级管理员(Super Admin)拥有修改数据库结构(Content-Type Builder)、配置 Webhook、甚至删除系统的最高权限。如果把这个账号给到不懂技术的客户,他们一旦误点,整个系统可能瞬间瘫痪。
在 Strapi 中,分配企业客户权限的核心思路是:利用 Strapi 的“管理员角色(Admin Panel Roles)”机制,为客户创建一个只能“发文章、删文章”,但绝对看不到“底层配置”的受限账号。
以下是手把手的配置指南和定制开发建议:
7.1区分两个“用户系统”(防坑必看)
在配置之前,必须要理清 Strapi 里的两个“用户组”(很多人在这里踩坑):
- Users & Permissions Plugin(API 用户): 这是给前台网站的用户用的(比如你的官网如果需要用户注册登录、评论文章)。
- Administration Panel(后台管理员): 这是给后台操作面板的用户用的(比如你、企业老板、企业文员)。
你要给客户开账号,必须去第二种(Administration Panel)里操作!
7.2为企业客户配置专属权限(实操步骤)
Strapi 免费版(Community Edition)默认提供了三个后台角色:Super Admin(你用的)、Editor(编辑)和 Author(作者)。我们通常把 Editor(编辑) 角色改造为交付给企业老板/文员使用的角色。
第一步:定制 Editor(编辑)角色的权限
登录你的超级管理员账号。
点击左侧底部的 Settings(设置)。
在左侧菜单找到 Administration Panel 下的 Roles(角色)。
点击 Editor 进入编辑页面。

核心裁剪操作:
Plugins (插件权限): 把里面所有的勾全部取消掉(特别是 Content-Type Builder 和 Webhooks,绝对不能让客户碰)。

Settings (设置权限): 确保所有的勾都是取消状态。
Collection Types (集合权限): 找到你创建的
Article(或者叫文章)。勾选Create、Read、Update、Delete和Publish。
- 点击右上角的 Save。
经过这步操作,Editor 角色就被彻底“阉割”成了一个纯粹的“内容打字员”。
第二步:为客户创建专属账号
依然在 Settings -> Administration Panel 下,点击 Users(用户)。

点击右上角的 Invite new user(邀请新用户)或 Add new user(添加新用户)。
填写客户的姓名和邮箱(可以是伪造的邮箱,因为你可以直接发密码给他们)。
Role(角色)选择: 必须选择刚才配置好的 Editor。

复制顶部的激活链接让用户自己或者我们先为其设置简单密码后,把这个账号和密码发给你的企业客户。
见证奇迹的时刻:
你可以开一个浏览器的无痕窗口,用刚才给客户建的账号登录一下 Strapi。你会发现,左侧那些让人头晕的 Content-Type Builder、Settings 菜单全部消失了!客户登录进来,左侧干净得只剩下“文章管理”和“媒体库”。这才是商业交付该有的极简体验。

7.3关于开源版 (免费版) 的限制与对策
这里必须诚实地告诉你一个 Strapi 的商业策略: 在 Strapi 免费版(Community Edition)中,你只能修改默认的这三个角色(Editor、Author),不能新建自定义角色(比如新建一个叫“A公司管理员”的角色)。新建角色是企业付费版(Enterprise Edition)的功能。
7.4进一步的“定制开发”建议
权限剥离干净后,这个后台依然叫 Strapi,为了让它更像你为客户定制的专属系统,你可以在代码里进行简单的“白标(White-labeling)”替换:
在你的 Strapi 项目目录中,找到 src/admin/app.js(或 app.tsx),你可以修改以下内容:
export default {
config: {
// 替换登录页的语言
locales: ['zh-Hans'],
// 替换后台左上角的文字
translations: {
'zh-Hans': {
'Auth.form.welcome.title': '欢迎使用企业内容管理系统',
'Auth.form.welcome.subtitle': '登录您的专属后台',
'app.components.LeftMenu.navbrand.title': 'XX企业后台',
},
},
// 替换登录页和左上角的 Logo (将图片放在 src/admin/extensions/ 目录下)
auth: {
logo: './extensions/my-company-logo.png',
},
menu: {
logo: './extensions/my-company-icon.png',
},
},
bootstrap(app) {
console.log(app);
},
};
修改完这个文件后,需要在终端重新执行 pnpm build 和 pnpm develop,后台就会彻底变成你自己的品牌。