中山公众号开发,中山小程序开发,中山企业官网开发,中山软件开发,中山APP开发
公司动态
COMPANY DYNAMIC
行业资讯
COMPANY DYNAMIC
扫一扫出方案
基于 Next.js 和云开发 CMS 的内容型网站应用实战开发
点击数:
2020-06-01 17:12:03

引言

随着腾讯云云开 CloudBase 发能力的日渐完善,有经验的工程师已经可以独立完成一个产品的开发和上线。但网上云开发相关的实战文章非常少,很多开发者清楚云开发的能力,但是不清楚如何在现有的开发体系下引入云开发

基于 Next.js 和云开发 CMS 的内容型网站应用实战开发

本文从云开发团队开发者 + 能力使用者的角度,以云开发官网 (http://cloudbase.net/ ) 的搭建思路为例,分享云开发 CloudBase 结合流行框架与工具的实战经验。

涉及到的知识点有

  • 云开发 CloudBase:

    • 扩展能力(CMS 扩展)

    • 静态托管

    • 云函数

    • 云数据库

    • CloudBase CLI 工具

  • React 框架:Next.js

  • CI 自动构建

总览

系统设计图

基于 Next.js 和云开发 CMS 的内容型网站应用实战开发

背景介绍

随着云开发 CloudBase 团队业务的迅猛发展,团队需要一个官网来更直观、更即时地向开发者们展示云开发的相关能力,包括但不限于工具链、SDK、技术文档等。

同时,为了降低开发者的上手成本,积累业界的优秀实战经验,官网也承载着营造社区氛围、聚合重要资料、增强用户黏度的重要任务。

我们最初使用 VuePress 作为静态网站工具,遇到了一些痛点:

  • 问题 1: 每次更新内容,都需要配合 git。运营同学对 git 不熟悉

  • 问题 2: 学习资料方面的内容更新过于频繁,“污染”了 git 记录

  • 问题 3: 内容和网站代码耦合

  • 问题 4: 缺少可视化的内容编辑工具

我们使用「CMS 扩展」、「云开发基础能力」、「Next.js」、「CI 工具」,很好地解决了以上问题。在实现网站内容动态化的同时,保证了 SEO,运营同学也可以通过 CMS 对内容进行可视化管理。

安装 CMS

进入云开发扩展能力控制台,根据引导,安装 CMS 内容管理系统。

基于 Next.js 和云开发 CMS 的内容型网站应用实战开发

在最后进行扩展程序配置的时候,有两种账号:管理员账号和运营者账号。管理员账号权限更高,可以创建新的数据集合;而运营者账号只能在已有的数据集合上进行增删改的操作。

基于 Next.js 和云开发 CMS 的内容型网站应用实战开发

注意:
安装时间有些长,请耐心等待

安装成功后,云数据库会自动创建 3 个集合:tcb-ext-cms-contentstcb-ext-cms-userstcb-ext-cms-webhooks

基于 Next.js 和云开发 CMS 的内容型网站应用实战开发

会自动创建 3 个云函数:tcb-ext-cms-apitcb-ext-cms-inittcb-ext-cms-auth

基于 Next.js 和云开发 CMS 的内容型网站应用实战开发

进入「静态网站托管」,可以看到 CMS 系统的静态文件已经自动上传到tcb-cms/目录下了:

基于 Next.js 和云开发 CMS 的内容型网站应用实战开发

点击上方的「基础配置」,就可以查看到域名信息。

基于 Next.js 和云开发 CMS 的内容型网站应用实战开发

在浏览器中访问: http://pagecounter-d27cfe-1255463368.tcloudbaseapp.com/tcb-cms/ 即可看到 CMS 系统:

基于 Next.js 和云开发 CMS 的内容型网站应用实战开发

到此为止,无任何开发成本,一个 CMS 内容管理系统就正式上线了~

使用 CMS 创建动态内容

对于动态化的数据内容,我们将其划分为不同的模块。每个内容模块,对应 CMS 系统的一个数据集合。例如「云开发官网」-「社区页」中,推荐好课的内容就是动态的。

基于 Next.js 和云开发 CMS 的内容型网站应用实战开发

从图中可以看到,每节课程有着多个属性。而在云数据库中,每节课程就对应一个文档,课程属性就对应文档的字段。字段类型与含义如下:

复制代码
name<string>: 课程名称time<number>: 课程时间cover<string>: 课程封面url<string>: 课程链接level<0 | 1 | 2>: 课程难度

以管理员身份登录 CMS 系统,在「内容设置页」新建内容。在 CMS 中,支持多种高级数据类型,例如 url、图片、markdown、富文本、标签数组、邮箱、网址等,并对这些类型进行了智能识别和更友好地展示。

注意:
CMS 自带图床功能。当数据类型是「图片」时,图片会自动上传到当前云开发环境下的云存储中。图片信息以 cloud:// 开头的特殊链接,存放在数据集合中。

新建内容时,默认情况下,CMS 会自动填充 4 个字段:name、order、createTime、updateTime。可以根据自身需要,对不需要的字段进行删除。

建议:
保留 order 字段,它可以被用作数据排序。对运营者来说,数据的 order 的值越大,在 CMS 系统中展示的位置越靠前;对开发者来说,可以根据 order 来进行排序搜索。从而保证了体验和逻辑的一致性。

根据字段创建集合后,CMS 系统左侧会看到「推荐好课」。它对应的内容被保存在云数据库的recommend-course(创建时指定)集合中,它的字段信息保存在云数据库的tcb-ext-cms-contents(CMS 初始化时创建)集合中。

基于 Next.js 和云开发 CMS 的内容型网站应用实战开发

按照设定添加新的课程内容后,再次进入「推荐好课」,如下所示:

基于 Next.js 和云开发 CMS 的内容型网站应用实战开发

图片、链接等内容,更友好地展示给运营者。

项目搭建

按照 Next.js 文档 的指引,创建 Next.js 项目:

复制代码
npm i --save next react react-dom axios

因为我们要将网站部署到「静态托管」上,所以要使用 Next.js 的静态导出功能。package.json 中的打包脚本更新为:

复制代码
"scripts": {"dev": "next","build": "next build && next export","start": "next start"}

为了快速部署静态网站,以及发布云函数。需要全局安装 @cloudbase/cli:

复制代码
npm install -g @cloudbase/cli

安装后,添加两个脚本:

  • deploy:hosting: 将 Next.js 的静态导出文件部署到「静态托管」

  • deploy:function: 发布项目中的云函数

复制代码
"scripts": {"deploy:hosting": "npm run build && cloudbase hosting:deploy out -e jhgjj-0ae4a1","deploy:function": "echo y | cloudbase functions:deploy --force"}

注意:
准备两个云环境,防止静态部署时文件覆盖。envId 为 jhgjj-0ae4a1 的云环境只用于部署 Next.js 的静态导出文件。envId 为 pagecounter-d27cfe 的云环境用来部署 CMS 系统。

获取 CMS 内容

编写云函数

为了能在 Next.js 中读取到 CMS 系统的最新数据,我们需要新建一个云函数,配合 HTTP Service,解析 Next.js 传入的参数,读取云数据库中的信息,并且返回给 Next.js。

为什么需要云函数配合 HTTP Service,不能直接使用 SDK 吗?
Next.js 预渲染的环境不支持 SDK(tcb-admin-node、tcb-js-sdk)。在 Next.js 中,动态获取数据注入到模板变量时,需要在 getInitialProps() 方法中进行异步操作。我们使用 axios(支持 ssr 环境),通过访问 url(云开发 HTTP Service 能力)触发云函数,获取最新数据

在项目目录下创建config.js,存放一些配置信息:

复制代码
module.exports = {    envId: "pagecounter-d27cfe", // 云开发环境 envid    siteAuthKey: "QhBYWnRjijGcGTBUxDFGWxuq", // 用于 site-cms-data 云函数中的身份校验    httpPath: "/site-cms-data" // site-cms-data 云函数的 http 触发路径};

创建 CloudBase CLI 工具的配置文件cloudbase.js,用于云函数部署:

复制代码
const { envId, siteAuthKey } = require("./config");module.exports = {envId,    functionRoot: "./cloudfunctions",    functions: [{            name: "site-cms-data",            config: {// 超时时间                timeout: 30,// 环境变量                envVariables: {                    SITE_AUTH_KEY: siteAuthKey},                runtime: "Nodejs8.9",                installDependency: true},            handler: "index.main"}]};

创建 cloudfunctions/site-cms-data/ 目录,里面存放云函数的主要逻辑。

基于 Next.js 和云开发 CMS 的内容型网站应用实战开发

provider.js中提供 Provider 对象,它支持:

  • fetchAll():获取指定集合的所有数据

  • query():支持orderBywhere的条件查询

之后我们会在 Next.js 端传入名为api的参数,它必须是 Provider 支持的方法。dispatch()会根据前端传入的api,自动调用 Provider 上的方法。

复制代码
const Provider = {// 获取指定集合的所有数据async fetchAll(ctx) {const {db,params: { collectionName }} = ctx;const collection = db.collection(collectionName);return collection.where({_id: /.*/}).get();},// 支持 orderBy、where 的条件查询(满足当前需求)async query(ctx) {const {db,params: { collectionName, orderBy, where }} = ctx;let promise = db.collection(collectionName);if (Array.isArray(orderBy) && orderBy.length === 2) {promise = promise.orderBy(...orderBy);}if (typeof where === "object") {promise = promise.where(where);}return promise.get();}};async function dispatch(ctx) {return await Provider ctx.api ;}module.exports = {Provider,dispatch};

interceptor.js提供:

  • vertifyAuth(): 用户身份检验

  • isValidBody(): 参数类型检验

复制代码
const { Provider } = require("./provider");const supportedApi = Reflect.ownKeys(Provider);function vertifyAuth(ctx) {// 前面在 cloudbase.js 中规定的环境变量return ctx.key === process.env.SITE_AUTH_KEY;}function isValidBody(body) {return ("key" in body && // 验证身份的随机密钥"params" in body && // 携带调用服务需要的参数"api" in body && // 调用服务名称supportedApi.includes(body.api));}module.exports = {vertifyAuth,isValidBody};

在 index.js 中,封装了云函数的整体逻辑:

  1. 检验 Next.js 端传入数据是否合法

  2. 检验身份密钥,防止云数据库被盗刷

  3. 绑定特殊变量到上下文,减少 tcb 对象的实例化次数

  4. 调用对应服务,返回结果

复制代码
const tcb = require("tcb-admin-node");const { vertifyAuth, isValidBody } = require("./interceptor");const { dispatch } = require("./provider");module.exports.main = async (event, context) => {let ctx = {envId: context.namespace};// 验证传入的数据try {const body = JSON.parse(event.body);if (isValidBody(body)) {ctx = {...ctx,...body};} else {return {success: false,msg: " 传入数据不合法 "};}} catch (error) {console.error(error);return {success: false,msg: " 请检查 body 格式 "};}// 验证身份if (!vertifyAuth(ctx)) {return {success: false,msg: " 身份验证失败 "};}// 给上下文绑定 dbconst app = tcb.init({env: ctx.envId});ctx.db = app.database({env: ctx.envId});// 服务调用try {const result = await dispatch(ctx);return {success: true,result};} catch (error) {console.error(error);return {success: false,msg: error.message};}};

建议:
在实际开发过程中,请规范云函数的结果返回格式。以此云函数为例,成功时返回: {success: true, result: 结果},失败时返回: {success: false, msg: 错误信息}

发布云函数

通过 CloudBase CLI 工具的命令,清空之前的登录状态,重新进行登录:

复制代码
cloudbase logout && cloudbase login

在项目目录下,执行发布云函数的命令:

复制代码
npm run deploy:function

在「云开发控制台」-「云函数页」中,可以看到云函数site-cms-data上传成功:

基于 Next.js 和云开发 CMS 的内容型网站应用实战开发

进入云函数site-cms-data,在「函数配置」中,修改“HTTP 触发路径”(和 config.js 中的 httpPath 字段保持一致):

基于 Next.js 和云开发 CMS 的内容型网站应用实战开发

成功后,我们可以就可以通过 https://${envId}.service.tcloudbase.com/site-cms-data 来触发此函数。

在 Next.js 中获取动态数据

在云函数 site-cms-data 中,只解析外界传入的 3 个参数:

参数名参数类型参数含义
keystring校验密钥,可以从config.js中读取
apistring云函数Provider支持的方法
paramsobject上述方法的入参

为了避免每次都重复编写 axios 的请求配置,将一些共用的信息抽离出来, generateAxiosConfig() 实现如下:

复制代码
// provider.jsimport { siteAuthKey } from "./config";/** * 为 axios 生成请求配置 * @param {String} api 云函数 (site-cms-data) 支持的服务 * @param {Object} params 服务入参 */function generateAxiosConfig(api, params) {const data = {key: siteAuthKey,api,params};const config = {headers: {"Content-Type": "application/json"},method: "POST",data: JSON.stringify(data)};return config;}

前文有讲到,CMS 自带图床功能,拖拽上传的图片会被存储在同一环境下的云存储中,并且获取图片的链接存放在集合中。云存储的链接是以 cloud:// 开头的特殊链接,需要在前端进行识别和特殊处理

以前文我们上传的图片为例,它的链接是:cloud://pagecounter-d27cfe.7061-pagecounter-d27cfe-1255463368/uploads/1589990230404.png。将其转成可访问的 http 链接:https://7061-pagecounter-d27cfe-1255463368.tcb.qcloud.la/uploads/1589990230404.png

转换思路是:识别 envid 后的信息,将其与tcb.qcloud.la域名重新拼接即可。代码实现如下:

复制代码
// provider.js/** * 获取云存储的访问链接 * @param {String} url 云存储的特定 url */function getBucketUrl(url) {if (!url.startsWith("cloud://")) {return url;}const re = /cloud:\/\/.*?\.(.*?)\/(.*)/;const result = re.exec(url);return `https://${result[1]}.tcb.qcloud.la/${result[2]}`;}

注意
云存储的「权限设置」应为:所有用户可读,仅创建者及管理员可写。否则链接无法访问。

推荐
除了自带的图床功能,开发者可以根据自身需求使用其他稳定图床服务,例如微博图床。如果使用其他图床,对应字段类型不能设置为「图片」,可以是「字符串」或者「超链接」。

在 provider.js 中对外暴露 getCourses() 方法,获取「推荐课程」的数据,并且进行处理:

复制代码
import { siteAuthKey, envId, httpPath } from "./config";import axios from "axios";const url = `http://${envId}.service.tcloudbase.com${httpPath}`;/** * 获取推荐课程数据 */export async function getCourses() {const config = generateAxiosConfig("fetchAll", {collectionName: "recommend-course"});const res = await axios(url, config);const { success, result, msg } = await res.data;if (success) {return result.data.map(item => ({...item,cover: getBucketUrl(item.cover) // 处理云存储的特殊链接}));} else {throw new Error(" 获取「推荐课程」失败:" + msg);}}

创建 pages/index.js 文件,它对外暴露的函数组件HomePage(被渲染为首页)。我们在组件上的getInitialProps()方法中获取推荐课程数据,并且将其注入到组件的props上:

复制代码
import React, { useState } from "react";import { getCourses } from "./../provider";const HomePage = props => {// ...};HomePage.getInitialProps = async () => {const promises = [getCourses()];const [courses] = await Promise.all(promises);// 返回组件的 propsreturn { courses };};export default HomePage;

在 HomePage 中,可以从 props 中读取到推荐课程数据,将其渲染到页面上即可:

复制代码
const levelMap = {    0: " 初级 ",    1: " 中级 ",    2: " 高级 "};const HomePage = ({ courses }) => {    return (        <>            {courses.map((course, index) => (                <div key={index}><p><a href={course.url}>{">>> 立即学习 "}</a></p><p><strong> 课程名称:</strong> {course.name}</p><p><strong> 课程时长:</strong> {course.time} 课时</p><p><strong> 课程难度:</strong>{levelMap[course.level]}</p><p><strong> 课程封面:</strong><img src={course.cover} /></p></div>))}</>);};

打开浏览器,进入 http://localhost:3000/ ,可以看到效果如下:

基于 Next.js 和云开发 CMS 的内容型网站应用实战开发

进入 view-source: http://localhost:3000/ ,可以看到网页的 html 源码中包含了课程数据,解决了 SEO 的问题:

基于 Next.js 和云开发 CMS 的内容型网站应用实战开发

注意:
getInitialProps()方法会将数据序列化,它执行于编译时期,而不是在网页生命周期中触发的。

自动构建与部署

目前为止,开发工作基本结束。执行 npm run build 命令,网站静态文件被打包到了 out/ 目录下:

基于 Next.js 和云开发 CMS 的内容型网站应用实战开发

执行 npm run deploy:hosting 将 out/ 目录下的文件上传到「静态网站托管」。访问静态网站托管的链接: https://jhgjj-0ae4a1.tcloudbaseapp.com/ ,效果如下:

基于 Next.js 和云开发 CMS 的内容型网站应用实战开发

借助成熟的 CI 工具,例如 Travis、Circle 等,可以定时触发构建工作。如此一来,内容和开发彻底分离。

在构建发布的时候,需要用到 CloudBase CLI 工具。在 CI 工具中,不再使用 cloudbase login 进行交互式输入登录,而是使用密钥登录: cloudbase login --apiKeyId $TCB_SECRET_ID --apiKey $TCB_SECRET_KEY 。

注意:
前往 云 API 密钥 获得 TCB_SECRET_ID 和 TCB_SECRET_KEY的值

在 CI 工具的控制台中,配置 TCB_SECRET_ID 和 TCB_SECRET_KEY。并为package.json新添加一个脚本:

复制代码
"scripts": {"login": "echo N | cloudbase login --apiKeyId $TCB_SECRET_ID --apiKey $TCB_SECRET_KEY"}

总结来说,CI 构建的流程是:

  • tcb 密钥登录:npm run login

  • 获取最新数据,导出静态文件:npm run build

  • 发布到「静态网站托管」:npm run deploy:function

如果数据需要紧急修改上线,可以在本地或者 CI 工具控制台,手动触发构建。

最后

在现有开发体系下,合理运用云开发,使得人力成本、开发成本以及运维成本大幅度降低。前后端一把梭,构成“闭环”。

本文实战仅是抛砖引玉,涉及了云开发能力的一部分,还有更多好玩的东西等待你的探索,比如使用云函数实现 SSR、托管后端服务、图像服务、各端 SDK 等。

探索能力,发散思路,以更低成本开发高可用的 web 服务,云开发绝对是你最好的选择!

作者简介:

董沅鑫,云开发 CloudBase 团队研发工程师,侧重于前端工程化、node 服务开发。

节点互动(广东)科技有限公司一家专注于 APP开发 + 小程序开发 + 微信开发 + 系统开发 + 网站开发 的专业互联网应用服务提供商。5年实战开发经验,高校合作基地,多年行业深耕经验,助力传统行业快速转型,为众多企业提供创新性互联网应用产品。


推荐文章
做产品经理,不要忽略你的隐形用户
什么是隐形用户,隐形用户有什么用?怎么获取隐形用户?本文以亲身实践为例,回答了上述问题。做产品做了这么久,仔细想一想,大部分都是围绕已经注册过我们产品的用户来迭代产品功能,优化用户体验,希望他们能一直使用我们的产品。我们也一直在想如何能更好的服务好这
连 iOS 14 都跟进的小窗模式是什么?
北京时间2020年6月23日凌晨召开的 WWDC 2020 上,苹果为 iOS 带来了一项新特性「画中画」,允许用户在看视频或者 FaceTime 时使用其他APP,然而安卓厂商提供的小窗模式使用场景更加丰富,一起来看看吧。本文从四个方面展开分析,希
小象生鲜退出:盒马能成,美团为何玩不转?
编辑导读:近日,美团旗下小象生鲜APP发布公告称:生鲜零售超市小象生鲜发布公告称,从2020年10月29日起,小象生鲜线上服务迁移到美团买菜APP,并变名为“美团买菜生活超市”提供服务,原小象生鲜APP将停止使用。 美团为何在生鲜道路上失败了?它犯了
PRD:Lab管家小程序——高效科研,省心管理
Lab管家是一款连接线上线下科研场景的小程序应用,本文通过过对Lab管家进行产品需求调研与分析后产出的一次迭代PRD文档。该文档由几个板块组成:产品概述、任务流程图、页面说明、功能原型等。前言作为一名科研狗,每天接触到最多的就是实验室场景,预约仪器和
手机App云化势不可挡,5G消息会成为下一个流量风口吗?
在智能手机世界和移动互联网时代,时时刻刻都能感受到变化。应用程序曾经主导了智能手机的发展方向,构建起多姿多彩的移动互联网社会。 但随着各项技术的进一步发展,传统App的推进方向似乎来到了分叉口。小程序、轻应用的概念释出,App云化趋势逐渐明显。互联网巨头、运营商们,也都纷纷加入到建筑轻应用平台的行列中来。 ...
完成教育行业的互联网改造还需要多久?
教育培训行业正在被降维打击,环境和行业都在挤兑着教育培训机构的发展。那么,完成教育行业的互联网改造还需要多久?01 环境威胁6月17日北京以及上调防控等级到二级,18日,也就是今天北京新增21个确诊,天津1个,河北2个。原本4月末已经基本出清的疫情又
小程序开发公司哪里强?看这几点
如今小程序开发已不罕见,越来越多的线下商家需要布局线上渠道,小程序是一个非常好的选择。但是,对于不懂任何技术、也没有自身技术团队的小白而言,若想拥有自己的小程序,就需要找靠谱的微信小程序开发公司,但是在这么多雷区的小程序行业,到底怎么选择小程序专业开
谷歌正式发布 Android 11 Beta 版,带来多项重
6 月 11 日,谷歌安卓开发者博客正式发布了 Android 11 Beta 版,本次版本更新着力强调三大主题:人、控制与隐私。谷歌还在 Kotlin 协同程序中为开发者提供更新,旨在完善 Jetpack Compose 工具包、加快 Androi
如何正确解读MVP测试指标?
解读MVP测试指标是产品经理常做的事情,那MVP测试指标究竟是什么?又该如何正确解读呢?本文介绍了解读MVP测试指标需要注意的问题和制定测试指标的大致逻辑,与大家分享!一、MVP需要完美吗?MVP的概念是随着Eric Ries 《精益创业》的受追捧而
比较好用的推广游戏软件的app应用市场有哪些
【摘要】一款游戏app应用被开发出来以后,app开发商最为关心的就是如何把这款app应用推广出去。而推广游戏应用的方式有很多,但是最为常见的也是比较受欢迎的app软件应用推广方式是利用app应用市场做推广。也有很多游戏app应用开发商有询问过商侣小编
在线客服系统