为无服务器静态站点生成器构建自定义 CMS

Avatar of John Polacek
John Polacek

DigitalOcean 为您旅程的每个阶段提供云产品。从 200 美元的免费积分 开始!

静态站点网站生成器在过去两年中越来越受欢迎。很多内容都写过(比如 )并且有很多 优秀的开源项目 甚至 有资金支持的公司 启动以帮助您启动和运行。

如果您能构建一个没有数据库的具有自定义 CMS 的静态站点生成器,您会相信吗? 不仅仅如此,我们甚至可以在不到 100 行 JavaScript 代码内实现 Hello World!

文章系列

  1. 为无服务器静态站点生成器构建自定义 CMS(您在这里!)+ 仓库
  2. 构建自定义无服务器 CMS:第 2 部分 + 仓库

静态站点生成器的历史问题是,它们通常需要使用 Markdown 格式编写内容。这对开发人员来说很棒,但对于习惯在 CMS 创作环境中工作的客户来说,就不那么好了。创建这样的环境传统上意味着需要创建数据库来管理身份验证、保存内容和上传媒体。

由于所谓的无服务器架构(又称后端即服务)的兴起,这种情况不再存在。

在本演示中,我们将创建一个静态站点生成器的基本框架,该生成器允许管理员用户通过标准 Web 表单(无需数据库)来创作内容。

要实现这一点,我们将使用

查看此概念验证的完整文件 GitHub 上的演示项目

在 Amazon S3 上设置静态网站

首先,如果您还没有,请注册 Amazon Web Services (AWS) 帐户。拥有 AWS 帐户后,就可以轻松地在 AWS 简单存储服务 (S3) 上托管静态站点。

首先,创建一个存储桶,然后在“属性”中的“静态网站托管”下,选择“启用网站托管”并将“索引文档”设置为指向站点的首页(`index.html`)。

接下来,创建一个存储桶策略以使您的站点可公开读取。有关设置存储桶权限的信息,请参阅 AWS S3 文档。此外,我们需要一个允许管理员保存内容编辑的策略。

<?xml version="1.0" encoding="UTF-8"?>
  <CORSConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
    <CORSRule>
      <AllowedOrigin>*</AllowedOrigin>
      <AllowedMethod>POST</AllowedMethod>
      <AllowedMethod>GET</AllowedMethod>
      <AllowedMethod>PUT</AllowedMethod>
      <AllowedMethod>DELETE</AllowedMethod>
      <AllowedMethod>HEAD</AllowedMethod>
      <AllowedHeader>*</AllowedHeader>
    </CORSRule>
  </CORSConfiguration>

有关如何在 S3 上托管静态站点的更详细的信息(包括在您自己的域上托管),请参阅 在 Amazon Web Services 上托管静态网站

创建静态站点文件

为您的项目创建一个新目录。构建一个简单的项目页面或从您选择的任何前端框架中选择一个简单的示例。在我们的演示中,我们使用了 Bootstrap 中的 Jumbotron 示例

将文件上传到您创建的 S3 存储桶。上传完成后,选择 S3 存储桶并查看“属性”。单击端点链接,您将能够查看您的站点。

创建管理员登录

Amazon Cognito 提供了一种简单的方法来将身份验证流程添加到网站。

此过程的第一步是创建一个 **UserPool**。在 AWS 控制台中,导航到 Amazon Cognito 并使用默认设置创建一个用户池。有关 User Pools 的更多信息,请阅读 AWS 文档

创建用户池后,您需要添加一个具有池访问权限的应用程序。选择您的用户池以进行编辑,然后选择“应用程序”选项卡。单击“添加应用程序”按钮并为您的应用程序命名。添加应用程序时,务必取消选中 **生成客户端密钥** 复选框,因为浏览器 SDK 中的 JavaScript 不支持具有客户端密钥的应用程序。

我们将直接在 AWS 控制台中创建我们的管理员用户,然后向他们提供可以用于登录的凭据。只有这些用户才能获得身份验证以访问生成站点的静态内容。

进入用户池并选择 **用户和组**。填写您自己的信息,以便您可以成为第一个管理员用户。选择通过电子邮件向用户发送邀请的选项。

现在我们需要为我们的管理员创建一个登录页面。在项目中创建一个名为 admin 的新目录,其中包含一个 index.html,并在其中包含一个登录表单。在我们的演示中,我们使用了 Bootstrap 登录页面

要将我们的表单连接到 Cognito,我们需要使用 Amazon Cognito Identity SDK for JavaScript。按照项目页面上的安装说明将脚本添加到我们的登录页面。

接下来,使用 Amazon Cognito Identity 服务对我们的管理员用户进行身份验证,并使用通过电子邮件收到的临时密码为其建立会话。

在我们的登录表单中添加一个提交事件侦听器,该侦听器根据 Amazon Cognito Identity SDK for JavaScript 项目页面 中的身份验证示例调用登录函数。

$('.form-signin').on('submit', function(e) {

  e.preventDefault();

  var authenticationData = {
    Username : $('#inputUsername').val(),
    Password : $('#inputPassword').val()
  };
  var authenticationDetails = new AWSCognito.CognitoIdentityServiceProvider.AuthenticationDetails(authenticationData);
  var poolData = { 
    UserPoolId : '...', // your user pool id here
    ClientId : '...' // your client id here
  };
  var userPool = new AWSCognito.CognitoIdentityServiceProvider.CognitoUserPool(poolData);
  var userData = {
    Username : $('#inputUsername').val(),
    Pool : userPool
  };

  cognitoUser = new AWSCognito.CognitoIdentityServiceProvider.CognitoUser(userData);
  cognitoUser.authenticateUser(authenticationDetails, {
    newPasswordRequired: function(userAttributes, requiredAttributes) {
      $('#form-password').removeAttr('hidden');
      $('#form-login').css('display', 'none');
      if ($('#inputNewPassword').val() !== '') {
        cognitoUser.completeNewPasswordChallenge($('#inputNewPassword').val(), [], this);
      }
    },
    onSuccess: function (result) {
      AWS.config.credentials = new AWS.CognitoIdentityCredentials({
        IdentityPoolId : 'us-east-1:XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX',
        Logins : {
          'cognito-idp.us-east-1.amazonaws.com/us-east-1_XXXXXXXXX' : result.getIdToken().getJwtToken()
        }
      });
      $.getJSON('index.json', function(data) {
        $('.container').html($('#adminForm').render(data));
      }).fail(function() {
        $('.container').html($('#adminForm').render({}));
      });
      AWS.config.update({
        region: 'us-east-1',
        credentials: new AWS.CognitoIdentityCredentials({
          IdentityPoolId: 'us-east-1:XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX',
          Logins: {
            'cognito-idp.us-east-1.amazonaws.com/us-east-1_XXXXXXXXX' : result.getIdToken().getJwtToken()
          }
        })
      });
      s3 = new AWS.S3({
        apiVersion: '2006-03-01',
        params: {
          Bucket: YOUR_BUCKET_NAME
        }
      )};
    },
    onFailure: function(err) {
      alert(err);
    }
  });
});

我们需要创建一个额外的表单以允许管理员创建密码。管理员将使用他们的临时密码登录,然后设置一个新密码以完成身份验证。

有关使用 Amazon Cognito 和 User Pools 的更多信息,请参阅以下文章

创建 CMS 管理员

登录完成后,我们希望允许我们的管理员编辑网站上的内容。在本演示中,我们将编辑顶部的 Jumbotron 突出显示部分。管理员通过身份验证后,我们会显示一个表单,其中包含站点信息、突出显示标题和文本内容的输入。

提交表单时,我们将表单中的数据与模板组合在一起以生成静态 HTML。在本演示中,我们使用 JSRender 作为我们的模板引擎。

通过复制 HTML 并将其作为 JSX 模板 嵌入我们的管理员页面来创建我们的 Jumbotron 页面的模板。用映射到管理员编辑表单字段名称的模板标签替换内容。

<script type="text/x-jsrender" id="jumbotronTemplate">
  <html lang="en">

  <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="description" content="{{>siteDescription}}">
    <meta name="author" content="">
    <title>{{>siteTitle}}</title>
    <link href="css/bootstrap.min.css" rel="stylesheet">
    <link href="css/jumbotron.css" rel="stylesheet">
  </head>

  <body>
    <nav class="navbar navbar-static-top navbar-dark bg-inverse">
      <a class="navbar-brand" href="#">{{>siteTitle}}</a>
      <ul class="nav navbar-nav">
        <li class="nav-item active">
          <a class="nav-link" href="#">Home <span class="sr-only">(current)</span></a>
        </li>
        <li class="nav-item">
          <a class="nav-link" href="#">About</a>
        </li>
        <li class="nav-item">
          <a class="nav-link" href="#">Contact</a>
        </li>
      </ul>
    </nav>
    <div class="jumbotron">
      <div class="container">
        <h1 class="display-3">{{>calloutHeadline}}</h1>
        <p>{{>calloutText}}</p>
        <p>
          <a class="btn btn-primary btn-lg" href="#" role="button">Learn more »</a>
        </p>
      </div>
    </div>

    ...

在编辑表单中添加一个事件侦听器,以捕获作为 JSON 对象输入的数据,然后将其传递到 JSRender 模板以生成 HTML,以便在下一步上传到 S3。

$('.container')
  .on('submit', '#form-admin',function(e) {
    e.preventDefault();
    var formData = {};
    var $formFields = $('#form-admin').find('input, textarea, select').not(':input[type=button],:input[type=submit],:input[type=reset]');
    $formFields.each(function() {
      formData[$(this).attr('name')] = $(this).val();
    });
    
    var jumbotronHTML = '<!DOCTYPE html>' + $('#jumbotronTemplate').render(formData);
    var file = new File([jumbotronHTML], 'index.html', {type: "text/html", lastModified: new Date()});
    ...

保存静态站点文件

接下来,我们通过创建一个具有对 S3 存储桶的读/写策略的身份和访问管理 (IAM) 角色来授予管理员保存对 S3 的编辑权限。

在 AWS 控制台中导航到 IAM,然后选择“策略”。使用以下策略文档创建一个新策略

{ 
  "Version": "2012-10-17", 
  "Statement": [ { 
    "Effect": "Allow", 
    "Action": [ "s3:*" ], 
    "Resource": [ "arn:aws:s3:::BUCKET_NAME/*" ] 
  } ] 
}

接下来选择“角色”,然后创建一个新角色并为其命名。选择“身份提供者角色”,然后选择 **授予对 Web 身份提供者的访问权限**。使用“验证角色信任”的默认策略文档。

在下一步中,附加您刚刚创建的用于访问 S3 存储桶的策略。

再次选择 AWS 控制台的 IAM 部分中的“角色”选项卡。选择您刚刚创建的角色。在“权限”中,在“托管策略”下,您应该会看到您用于 S3 的策略。如果您没有看到,请立即附加。

更新管理员页面上的 AWS 配置设置,以包含我们的 S3 服务。

// Instantiate AWS SDK service objects now that the credentials have been updated. 
AWS.config.update({ 
  region: 'BUCKET_REGION', 
  credentials: new AWS.CognitoIdentityCredentials({ 
    IdentityPoolId: 'IDENTITY_POOL_ID' 
  }) 
}); 
s3 = new AWS.S3({ 
  apiVersion: '2006-03-01', params: {Bucket: 'BUCKET_NAME'} 
});

将我们的管理员用户连接到 S3 的最后一步是创建一个身份池。返回 AWS 控制台中的 Amazon Cognito,并点击 **管理联合身份** 按钮,然后点击 **创建新的身份池** 按钮。

为你的池子命名,然后展开身份验证提供者部分。选择 **Amazon Cognito** 选项卡,并输入你的 **用户池 ID** 和 **应用客户端 ID**。

正如你所见,除了 Amazon Cognito,你还可以使用其他身份验证提供者,例如 Facebook 和 Google。有关更多信息,请阅读 将用户池与 Amazon Cognito 身份集成

继续完成剩余的步骤,然后在示例代码下,记下 **身份池 ID**。回到我们的管理员页面,当我们的表单提交时,我们将把我们的 HTML 文件上传到 S3。

此外,一旦 HTML 文件成功上传,我们创建一个 JSON 文件并将其上传以存储我们的数据,这样当我们返回表单时,它就会填充新更新的值。

var file = new File([jumbotronHTML], 'index.html', { type: "text/html", lastModified: new Date() });
s3.upload({
  Key: 'index.html',
  Body: file,
  ACL: 'public-read',
  ContentDisposition: 'inline',
  ContentType: 'text/html'
}, function(err, data) {
  if (err) {
    return alert('There was an error: ', err.message);
  }
  file = new File([JSON.stringify(formData)], 'index.json');
  s3.upload({
    Key: 'admin/index.json',
    Body: file,
    ACL: 'public-read'
  }, function(err, data) {
    if (err) {
      return alert('There was an error: ', err.message);
    }
    $('#form-admin')
      .remove('#success')
      .prepend('<p id="success">Update successful! View Website</p>');
  });
});

更新 Cognito 的 authenticateUser onSuccess 事件处理程序,以将表单呈现为一个模板,其中包含来自 JSON 文件的值(如果存在)。

$.getJSON('index.json', function(data) {
  $('.container').html($('#adminForm').render(data));
}).fail(function() {
  $('.container').html($('#adminForm').render({}));
});

下一步

  • 鉴于能够生成并将静态内容保存到 S3,我们可以构建许多令人兴奋的网页内容和管理员界面。
  • 添加图像和文件上传功能
  • 添加一个 富文本 编辑器
  • 通过添加忘记密码和 MFA 来改进登录功能。
  • 构建管理员以编辑跨多个页面的组件和内容。
  • 使用 contenteditable 允许登录的管理员进行页面内编辑。
  • 使用用户池组管理不同的用户权限级别。
  • 开发一个工作流程来为您的每个客户项目构建自定义 CMS。
  • 创建您自己的内容管理平台。