JackyLove 的技术人生

人生艰难,唯有一技傍身才能慢慢走向通途。个人博客,记录生活,分享技术,记录成长。专注全栈技术,next技术。

第42章—实战篇ReactNotesStrapi

首次发表于 2024-03-22, 更新于 2024-03-22

前言

先说说 CMS,所谓 CMS,Content Management System,中文译为内容管理系统。

内容管理系统的定义可以很狭窄,通常是指门户或商业网站的发布和管理系统;定义也可以很宽泛,个人网站系统也可归入其中。Wiki 也是一种内容管理系统,Blog 也算是一种内容管理系统。

比如常用于搭建博客的 Wordpress 就是一个知名的内容管理系统。

这些年来,headless CMS 也流行了起来。所谓 headless CMS,简单的来说,CMS 不再负责内容的展现,只提供内容存储库以及 API,这使得开发人员可以自定义展示内容,虽然带来了一定的工作量,但也让开发更加灵活自由。

今天要讲的 Strapi 就是基于 Node.js 实现的 Headless CMS。借助 Strapi,不需要手动编写后端接口,通过可视化的界面就能直接创建 Restful API。

在实际开发项目的时候,这样做的好处就是 —— 快!而在那么多 Headless CMS 中选择 Strapi,是因为它应该是 GitHub 上 star 最多(58k)的 Headless CMS,用的人也比较多。

对于一些简单的项目,相比于从零开始搭建,不如直接使用像 Strapi 这样的工具,快速构建出项目!

Strapi

现在让我们来使用 Strapi,执行以下命令构建本地项目:

npx create-strapi-app@latest next-react-notes-strapi

1. 数据库选择

Strapi 会让你进行一些自定义选择,比如数据库,Strapi 支持的数据库有:

数据库 最小版本 推荐版本
MySQL 5.7.8 8.0
MariaDB 10.3 10.6
PostgreSQL 11.0 14.0
SQLite 3 3

不过目前 Strapi v4 并不支持 MongoDB,所以这里我们选择比较常用的 MySQL。

image.png

MySQL 数据库相关的设置如 name、Host、Port、Username 等,如果不知道,现在都可以默认,以后还可以改。

2. 安装常见问题

安装的时候可能会遇到一些问题,比如 Strapi 要求 node 版本大于 18,小于等于 20。如果版本不符合,可以通过 nvm 管理和切换 node 版本。

安装的时候可能会在安装 sharp 这个库的时候报错:

image.png

如果出现这种报错,打开电脑~/.npmrc这个文件,添加如下配置:

sharp_binary_host=https://npm.taobao.org/mirrors/sharp
sharp_libvips_binary_host=https://npm.taobao.org/mirrors/sharp-libvips

如果成功安装,会显示项目的可用脚本命令:

image.png

MySQL

当然现在运行 npm run develop也会报错,因为 MySQL 数据库相关的内容还没有设置,这里我们从安装到设置从头讲一遍。

1. 安装

首先是安装 mysql 包,下载地址:https://dev.mysql.com/downloads/mysql/

image.png

因为我个人的电脑是 macOS,所以讲一下 macOS 安装时会遇到的一些问题。

首先是选择合适的下载包。Strapi 推荐 8.0 版本,所以优先选择 8.0.xx 版本。

查看“关于本机”,如果处理器是 Intel ,选择带 x86的包,如果芯片是 Apple M1,选择带 ARM的包。

此外还要注意苹果系统的版本,下载包的名字包含了支持的 OS 系统版本。比如你的系统是 macOS 11,安装支持 macOS 13 的包,会出现报错:

image.png

如果系统是 macOS 11,可以选择 8.0.28 版本:

image.png

安装的过程中需要设置下 root 用户的密码,记住这个密码就行。

安装完成后,可以在“系统偏好设置”中查看到:

image.png

点击进入 MySQL 界面,点击 Start MySQL Server即可启动 MySQL:

截屏2024-01-16 下午1.30.21.png

2. 配置环境变量

查看当前 Shell:

echo $SHELL

如果是 /bin/bash,说明用的是 bash,如果是 /bin/zsh,说明用的是 zsh。

如果是 bash

# 1. 更改
vim ~/.bash_profile
# 2. 添加
export PATH=${PATH}:/usr/local/mysql/bin
# 3. 更新
source ~/.bash_profile

如果是 zsh

# 1. 更改
vim ~/.zshrc
# 2. 添加
export PATH=${PATH}:/usr/local/mysql/bin
# 3. 更新
source ~/.zshrc

此时在命令行中输入:

mysql -u root -p

输入安装时设置的密码,即可成功进入 MySQL CLI:

image.png

3. 数据库配置

来都来了,那就顺便创建下会用到的数据库,执行:

CREATE DATABASE strapi

别忘了在末尾带个 \g表示命令结束,这里我们创建了一个名为 strapi 的数据库:

image.png

然后我们查看下 root 用户用到的 authentication 插件。因为 MySQL 8.0.x 默认的是 chaching_sha2_password,但是 Strapi 需要是 mysql_native_password,运行:

SELECT user, plugin FROM mysql.user WHERE user IN ('root')

如果是 caching_sha2_password,运行:

ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY 'admin'

注意其中 admin 表示设置的新密码,如果在这里修改,会影响你运行 mysql -u root -p时输入的密码。再运行:

FLUSH PRIVILEGES

此时再运行以下命令查看 pulgin:

SELECT user, plugin FROM mysql.user WHERE user IN ('root')

image.png

4. 运行 Strapi 项目

现在我们已经获得了数据库的相关信息,也做好了准备,进入上节安装的 Strapi 项目目录,打开根目录的 .env文件,像下面这样填入数据库信息:

# Database
DATABASE_CLIENT=mysql
DATABASE_HOST=127.0.0.1
DATABASE_PORT=3306
DATABASE_NAME=strapi
DATABASE_USERNAME=root
DATABASE_PASSWORD=admin
DATABASE_SSL=false

MySQL 数据库默认就是 3306 端口,所以不需要修改。数据库名称选择 CREATE DATABASE xxxxxx时填入的名字,用户名为 root,密码为运行 ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY 'xxxxx' 时填写的密码,这里是 admin

此时再运行 npm run develop,应该就能正常运行起来:

image.png

这个安装过程可能会遇到很多问题,我也不能面面俱到,欢迎大家留言分享自己遇到的问题和解决方法,帮助后来的学习者。

Strapi 创建接口

如果你成功运行,应该会打开此页面:

image.png

这里的信息用于 Strapi 认证,所有的数据也都存储在本地的数据库里。所以这里随便填,但是得记住,以后可能会用到。

填写完后进入主界面:

image.png

1. 设置中文

为了方便使用,我们先把界面的中文设置了,打开 src/admin/app.example.js,重命名为app.js,在其中取消掉 'zh-Hans'的注释:

const config = {
  locales: [
    // 'ar',
    // 'fr',
    // 'cs',
    // 'de',
    // 'dk',
    // 'es',
    // 'he',
    // 'id',
    // 'it',
    // 'ja',
    // 'ko',
    // 'ms',
    // 'nl',
    // 'no',
    // 'pl',
    // 'pt-BR',
    // 'pt',
    // 'ru',
    // 'sk',
    // 'sv',
    // 'th',
    // 'tr',
    // 'uk',
    // 'vi',
    'zh-Hans',
    // 'zh',
  ],
};

const bootstrap = (app) => {
  console.log(app);
};

export default {
  config,
  bootstrap,
};

然后点击左下角的用户名 -> Profile,拉到最下面,选择中文(简体)

image.png

保存后,主界面即改为中文:

image.png

说真的,这中文翻译也就那样吧……我个人感觉还不如用英文。

2. 创建 REST API

现在我们来创建接口吧!

2.1. 建表

首先打开 Content-Type Builder,这里有三种类型可以选择:

  1. COLLECTION TYPES:管理多个条目的内容类型
  2. SINGLE TYPES:管理一个条目的内容类型
  3. COMPONENTS:一种可用于 COLLECTION TYPESSINGLE TYPES 的数据结构

简单的来说,COLLECTION TYPES 就是我们常说的数据库里的“表”,可以有多条数据。SINGLE TYPES只能管理一条数据,可用于全局配置。COMPONENTS 表示一种数据结构,它可以在其他类型中复用。比如你可以创建一个名为 SEO 的组件,负责管理标题、描述等字段。然后你可以在 Article 和 Product 这两个 COLLECTION TYPES 中复用这个组件,而不用重新一一建立。

这里我们选择 COLLECTION TYPES,建立一个名为 Note 的集合类型,它对应的单数 ID 为 note,复数 ID 为 notes,这些是自动生成的,用于生成我们的接口地址。此步骤相当于建表。

image.png

然后就是添加各种字段,对应为表建立各种字段:

image.png

2.2. 填充数据

回到 Content Manager,选择 Note这个集合类型,然后点击 Create new entry,这步就是让你填充一些数据。我们象征性的填充一些数据:

image.png

2.3. 生成 token

打开 Settings -> API Tokens,点击 Create new API Token,生成 API Token,该 Token 决定了权限范围和使用时间。

image.png

生成之后,获取接口数据的时候就需要带上这个 token:

image.png

2.4. REST 接口

现在接口就已经生成了,对于一个 COLLECTION TYPE,Strapi 对应会生成这些接口,我们以这里的 Note COLLECTION TYPE 为例:

方法 URL 示例 作用
GET /api/:pluralApiIds /api/notes 获取条目列表
POST /api/:pluralApiId /api/notes 创建条目
GET /api/:pluralApiId/:documentId /api/notes/1 获取单个条目
PUT /api/:pluralApiId/:documentId /api/notes/1 更新单个条目
DELETE /api/:pluralApiId/:documentId /api/notes/1 删除单个条目

注意这里用到的都是复数 ID。如果是 SINGLE TYPES,生成的接口会用到单数 ID:

方法 URL 作用
GET /api/:singularApiId 获取条目
PUT /api/:singularApiId 更新/创建条目
DELETE /api/:singularApiId 删除一个条目

现在你可以用 POSTMAN + Token 试试获取 notes 的数据:

image.png

如果你不带 token 获取就会出现 403 错误:

image.png

2.5. 取消授权

那你可能会想:“好麻烦,我调用个接口,还要用 token,能不能不用 token,至少获取列表和获取条目不需要?”。当然也是可以的,我们点击 Settings-> Roles,选择 Public角色进行编辑:

截屏2024-01-16 下午9.10.58.png

勾选 Note 这个集合类型中的 findfindOne,表示 /api/notes/api/note/1不再需要鉴权。

image.png

现在我们已经可以直接获取数据:

image.png

2.6. Marketplace

现在 Note 我们已经创建了 titlecontent 这两个字段,创建和修改时间,Strapi 会自动返回,就不需要单独建立字段了。我们还需要一个 uid 用作文章的 slug,跳转到具体文章的时候,用 slug 作为其地址的一部分。

虽然文档自身也会返回 id,但这个 id 是递增的,不太适合作为 slug。Strapi 也有默认的 UID 字段:

image.png

但这个 UID 生成的字符串是 notenote-1note-2这种。我们希望是一个多位的随机数字字符串。该如何实现呢?

这就要说到 Strapi 强大的插件功能了,我们打开 Marketplace,搜索 uuid

image.png

我们选择 Advanced UUId 这个插件,查看用法后,在项目里运行:

npm install strapi-advanced-uuid

安装后重启项目,即可在添加字段中的 CUSTOM 选项中查看到:

image.png

我们建立一个名为 slug 的 UUID 类型,UUID format 表示这个 uuid 的格式,我们填写 ^[0-9]{8}$表示随机的 8 位数字字符串。

image.png

我们就可以通过该字段添加随机的 uid 数据:

截屏2024-01-16 下午10.05.56.png

Next.js 项目替换 redis

目前我们的 Next.js 项目使用的是 redis 作为临时数据库,现在改为调用接口来获取数据吧。

新建 lib/strapi.js,代码如下:

export async function getAllNotes() {
  const response = await fetch(`http://localhost:1337/api/notes`)
  const data = await response.json();

  const res = {};

  data.data.forEach(({id, attributes: {title, content, slug, updatedAt}}) => {
    res[slug] = JSON.stringify({
      title,
      content,
      updateTime: updatedAt
    })
  })

  return res
}

export async function addNote(data) {
  const response = await fetch(`http://localhost:1337/api/notes`, {
    method: 'POST',
    headers: {
      Authorization: 'bearer 80985bb38cf749e5568e51c637d796c69c7a6b1e820152a1d144369d9b1568b26eae1070a42f06f691febb07a5134b0a5a00e24e69c298b50414f28c3299ead4b05b9f876883020868c5769a726ae5ca02ef31b2a5786efbccfe041b7131e609eb56680a60e38a973dae25d26d1e4ac56e7651d4d1c6a4e1fe7f68999dbb4eed',
      "Content-Type": "application/json"
    },
    body: JSON.stringify({
      data: JSON.parse(data)
    })
  })
  const res = await response.json();
  return res.data.attributes.slug
}

export async function updateNote(uuid, data) {
  const {id} = await getNote(uuid);
  const response = await fetch(`http://localhost:1337/api/notes/${id}`, {
    method: 'PUT',
    headers: {
      Authorization: 'bearer 80985bb38cf749e5568e51c637d796c69c7a6b1e820152a1d144369d9b1568b26eae1070a42f06f691febb07a5134b0a5a00e24e69c298b50414f28c3299ead4b05b9f876883020868c5769a726ae5ca02ef31b2a5786efbccfe041b7131e609eb56680a60e38a973dae25d26d1e4ac56e7651d4d1c6a4e1fe7f68999dbb4eed',
      "Content-Type": "application/json"
    },
    body: JSON.stringify({
      data: JSON.parse(data)
    })
  })
  const res = await response.json()
}

export async function getNote(uuid) {
  const response = await fetch(`http://localhost:1337/api/notes?filters[slug][$eq]=${uuid}`)
  const data = await response.json();
  return {
    title: data.data[0].attributes.title,
    content: data.data[0].attributes.content,
    updateTime: data.data[0].attributes.updatedAt,
    id: data.data[0].id
  }
}

export async function delNote(uuid) {
  const {id} = await getNote(uuid);
  const response = await fetch(`http://localhost:1337/api/notes/${id}`, {
    method: 'DELETE',
    headers: {
      Authorization: 'bearer 80985bb38cf749e5568e51c637d796c69c7a6b1e820152a1d144369d9b1568b26eae1070a42f06f691febb07a5134b0a5a00e24e69c298b50414f28c3299ead4b05b9f876883020868c5769a726ae5ca02ef31b2a5786efbccfe041b7131e609eb56680a60e38a973dae25d26d1e4ac56e7651d4d1c6a4e1fe7f68999dbb4eed',
      "Content-Type": "application/json"
    }
  })
  const res = await response.json()
}

在这段代码中,为了减少代码改动的范围,我们按照了之前使用 redis 的数据结构返回了数据。这样你只需将以前的导入代码 @/lib/redis改为 @/lib/strapi即可直接使用。这里为了演示,代码写的健壮性不够,比如没有错误捕获,没有空值判断,真实的项目开发中请勿这样写。

在这段代码中,getNote 函数中,我们使用了 http://localhost:1337/api/notes?filters[slug][$eq]=${uuid}来获取具体的笔记,因为我们没有使用 strapi 自带的 documentId,而是 slug 作为唯一 id。Strapi 也是支持 Filtering 的功能的,这段代码就演示了其用法。当然 Strapi 的强大功能不止这些,具体使用的时候,参考 Strapi REST 文档

@/lib/redis都改为 @/lib/strapi后,项目正常运行:

ReactNotes-Auth9.gif

但数据库已经从 redis 替换为了 mysql,而且我们可以通过 Strapi 快捷的查看到数据库中的数据。

总结

那么今天的内容就结束了,本篇主要是为大家介绍 Strapi 以及如何连接 MySQL 数据库,借助 Strapi 的可视化界面,可以快速创建 REST 接口,非常适合在一些接口并不用复杂的项目中使用。

本篇的代码我已经上传到代码仓库Day 9 分支。直接使用的时候不要忘记在本地开启 Redis。

参考

  1. Welcome to the Strapi Developer Docs! | Strapi Documentation
  2. Configuring MySQL on your Strapi project
  3. https://github.com/strapi/strapi
© Copyright 2025 JackyLove 的技术人生. Powered with by CreativeDesignsGuru