{"componentChunkName":"component---src-templates-best-practice-detail-tsx","path":"/best-practice/2020-02-07-serverless-admin-system","result":{"data":{"currentBlog":{"id":"e4d496fa-1f43-52fe-97ed-a397afdf1f9c","frontmatter":{"thumbnail":"https://img.serverlesscloud.cn/20191226/1577362754931-egg.png","authors":["yugasun"],"categories":["best-practice"],"date":"2020-02-07T00:00:00.000Z","title":"Serverless + Egg.js 后台管理系统实战","description":"本文将介绍如何基于 Egg.js 和 Serverless 实现一个后台管理系统","authorslink":["https://github.com/yugasun"],"translators":null,"translatorslink":null,"tags":["Serverless","Egg.js"],"keywords":"Egg.js,Serverless后台管理,Serverless Egg.js","outdated":true},"wordCount":{"words":636,"sentences":119,"paragraphs":119},"fileAbsolutePath":"/opt/build/repo/content/best-practice/2020-02-07-serverless-admin-system.md","fields":{"slug":"/best-practice/2020-02-07-serverless-admin-system/","keywords":["go","koa","serverless","spa","vue","vuejs","website","website 组件","无服务器","云函数","role","serverless","Egg"]},"html":"<p>作为一名前端开发者，在选择 Nodejs 后端服务框架时，第一时间会想到 <a href=\"https://github.com/eggjs/egg/\">Egg.js</a>，不得不说 <code class=\"language-text\">Egg.js</code> 是一个非常优秀的企业级框架，它的高扩展性和丰富的插件，极大的提高了开发效率。开发者只需要关注业务就好，比如要使用 <code class=\"language-text\">redis</code>，引入 <a href=\"https://github.com/eggjs/egg-redis\">egg-redis</a> 插件，然后简单配置就可以了。正因为如此，第一次接触它，我便喜欢上了它，之后也用它开发过不少应用。</p>\n<p>有了如此优秀的框架，那么如何将一个 <code class=\"language-text\">Egg.js</code> 的服务迁移到 <code class=\"language-text\">Serverless</code> 架构上呢？</p>\n<!--more-->\n<h2 id=\"背景\"><a href=\"#%E8%83%8C%E6%99%AF\" aria-label=\"背景 permalink\" class=\"anchor\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>背景</h2>\n<p>我在文章 <a href=\"https://yugasun.com/post/serverless-fullstack-vue-practice.html\">基于 Serverless Component 的全栈解决方案</a> 中讲述了，如何将一个基于 <code class=\"language-text\">Vue.js</code> 的前端应用和基于 <code class=\"language-text\">Express</code> 的后端服务，快速部署到腾讯云上。虽然受到不少开发者的喜爱，但是很多开发者私信问我，这还是一个 <code class=\"language-text\">Demo</code> 性质的项目而已，有没有更加实用性的解决方案。而且他们实际开发中，很多使用的正是 <code class=\"language-text\">Egg.js</code> 框架，能不能提供一个 <code class=\"language-text\">Egg.js</code> 的解决方案？</p>\n<p>本文将手把手教你结合 <code class=\"language-text\">Egg.js</code> 和 <code class=\"language-text\">Serverless</code> 实现一个后台管理系统。</p>\n<p>读完此文你将学到：</p>\n<ol>\n<li>Egg.js 基本使用</li>\n<li>如何使用 Sequelize ORM 模块进行 Mysql 操作</li>\n<li>如何使用 Redis</li>\n<li>如何使用 JWT 进行用户登录验证</li>\n<li><a href=\"https://github.com/serverless/serverless\">Serverless Framework</a> 的基本使用</li>\n<li>如何将本地开发好的 Egg.js 应用部署到腾讯云云函数上</li>\n<li>如何基于云端对象存储快速部署静态网站</li>\n</ol>\n<h2 id=\"eggjs-入门\"><a href=\"#eggjs-%E5%85%A5%E9%97%A8\" aria-label=\"eggjs 入门 permalink\" class=\"anchor\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>Egg.js 入门</h2>\n<p>初始化 Egg.js 项目：</p>\n<div\n              class=\"gatsby-code-button-container\"\n              data-toaster-id=\"50474398267201880000\"\n              data-toaster-class=\"gatsby-code-button-toaster\"\n              data-toaster-text-class=\"gatsby-code-button-toaster-text\"\n              data-toaster-text=\"代码复制成功\"\n              data-toaster-duration=\"3500\"\n              onClick=\"copyToClipboard(`\\$ mkdir egg-example && cd egg-example\n\\$ npm init egg --type=simple\n\\$ npm i`, `50474398267201880000`)\"\n            >\n              <div\n                class=\"gatsby-code-button\"\n                data-tooltip=\"\"\n              >\n                复制代码<svg class=\"gatsby-code-button-icon\" xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><path fill=\"none\" d=\"M0 0h24v24H0V0z\"/><path d=\"M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z\"/></svg>\n              </div>\n            </div>\n<div class=\"gatsby-highlight\" data-language=\"bash\"><pre class=\"language-bash\"><code class=\"language-bash\">$ <span class=\"token function\">mkdir</span> egg-example <span class=\"token operator\">&amp;&amp;</span> <span class=\"token builtin class-name\">cd</span> egg-example\n$ <span class=\"token function\">npm</span> init egg --type<span class=\"token operator\">=</span>simple\n$ <span class=\"token function\">npm</span> i</code></pre></div>\n<p>启动项目:</p>\n<div\n              class=\"gatsby-code-button-container\"\n              data-toaster-id=\"20411764294391976000\"\n              data-toaster-class=\"gatsby-code-button-toaster\"\n              data-toaster-text-class=\"gatsby-code-button-toaster-text\"\n              data-toaster-text=\"代码复制成功\"\n              data-toaster-duration=\"3500\"\n              onClick=\"copyToClipboard(`\\$ npm run dev`, `20411764294391976000`)\"\n            >\n              <div\n                class=\"gatsby-code-button\"\n                data-tooltip=\"\"\n              >\n                复制代码<svg class=\"gatsby-code-button-icon\" xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><path fill=\"none\" d=\"M0 0h24v24H0V0z\"/><path d=\"M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z\"/></svg>\n              </div>\n            </div>\n<div class=\"gatsby-highlight\" data-language=\"bash\"><pre class=\"language-bash\"><code class=\"language-bash\">$ <span class=\"token function\">npm</span> run dev</code></pre></div>\n<p>然后浏览器访问 <code class=\"language-text\">http://localhost:7001</code>，就可以看到亲切的 <code class=\"language-text\">hi, egg</code> 了。</p>\n<p>关于 Egg.js 的框架更多知识，建议阅读 <a href=\"https://eggjs.org/zh-cn/intro/quickstart.html\">官方文档</a></p>\n<h2 id=\"准备\"><a href=\"#%E5%87%86%E5%A4%87\" aria-label=\"准备 permalink\" class=\"anchor\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>准备</h2>\n<p>对 Egg.js 有了简单了解，接下来我们来初始化我们的后台管理系统，新建一个项目目录 <code class=\"language-text\">admin-system</code>:</p>\n<div\n              class=\"gatsby-code-button-container\"\n              data-toaster-id=\"12277369968565854000\"\n              data-toaster-class=\"gatsby-code-button-toaster\"\n              data-toaster-text-class=\"gatsby-code-button-toaster-text\"\n              data-toaster-text=\"代码复制成功\"\n              data-toaster-duration=\"3500\"\n              onClick=\"copyToClipboard(`\\$ mkdir admin-system`, `12277369968565854000`)\"\n            >\n              <div\n                class=\"gatsby-code-button\"\n                data-tooltip=\"\"\n              >\n                复制代码<svg class=\"gatsby-code-button-icon\" xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><path fill=\"none\" d=\"M0 0h24v24H0V0z\"/><path d=\"M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z\"/></svg>\n              </div>\n            </div>\n<div class=\"gatsby-highlight\" data-language=\"bash\"><pre class=\"language-bash\"><code class=\"language-bash\">$ <span class=\"token function\">mkdir</span> admin-system</code></pre></div>\n<p>将上面创建的 Egg.js 项目复制到 <code class=\"language-text\">admin-system</code> 目录下，重命名为 <code class=\"language-text\">backend</code>。然后将前端模板项目复制到 <code class=\"language-text\">frontend</code> 文件夹中：</p>\n<div\n              class=\"gatsby-code-button-container\"\n              data-toaster-id=\"32253174183190470000\"\n              data-toaster-class=\"gatsby-code-button-toaster\"\n              data-toaster-text-class=\"gatsby-code-button-toaster-text\"\n              data-toaster-text=\"代码复制成功\"\n              data-toaster-duration=\"3500\"\n              onClick=\"copyToClipboard(`\\$ git clone https://github.com/PanJiaChen/vue-admin-template.git frontend`, `32253174183190470000`)\"\n            >\n              <div\n                class=\"gatsby-code-button\"\n                data-tooltip=\"\"\n              >\n                复制代码<svg class=\"gatsby-code-button-icon\" xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><path fill=\"none\" d=\"M0 0h24v24H0V0z\"/><path d=\"M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z\"/></svg>\n              </div>\n            </div>\n<div class=\"gatsby-highlight\" data-language=\"bash\"><pre class=\"language-bash\"><code class=\"language-bash\">$ <span class=\"token function\">git</span> clone https://github.com/PanJiaChen/vue-admin-template.git frontend</code></pre></div>\n<blockquote>\n<p>说明： <a href=\"https://github.com/PanJiaChen/vue-admin-template\">vue-admin-template</a> 是基于 Vue2.0 的管理系统模板，是一个非常优秀的项目，建议对 Vue.js 感兴趣的开发者可以去学习下，当然如果你对 Vue.js 还不是太了解，这里有个基础入门学习教程 <a href=\"https://yugasun.github.io/You-May-Not-Know-Vuejs\">Vuejs 从入门到精通系列文章</a></p>\n</blockquote>\n<p>之后你的项目目录结构如下：</p>\n<div\n              class=\"gatsby-code-button-container\"\n              data-toaster-id=\"82990707518909560000\"\n              data-toaster-class=\"gatsby-code-button-toaster\"\n              data-toaster-text-class=\"gatsby-code-button-toaster-text\"\n              data-toaster-text=\"代码复制成功\"\n              data-toaster-duration=\"3500\"\n              onClick=\"copyToClipboard(`.\n├── README.md\n├── backend     // 创建的 Egg.js 项目\n└── frontend    // 克隆的 Vue.js 前端项目模板`, `82990707518909560000`)\"\n            >\n              <div\n                class=\"gatsby-code-button\"\n                data-tooltip=\"\"\n              >\n                复制代码<svg class=\"gatsby-code-button-icon\" xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><path fill=\"none\" d=\"M0 0h24v24H0V0z\"/><path d=\"M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z\"/></svg>\n              </div>\n            </div>\n<div class=\"gatsby-highlight\" data-language=\"bash\"><pre class=\"language-bash\"><code class=\"language-bash\"><span class=\"token builtin class-name\">.</span>\n├── README.md\n├── backend     // 创建的 Egg.js 项目\n└── frontend    // 克隆的 Vue.js 前端项目模板</code></pre></div>\n<p>启动前端项目熟悉下界面：</p>\n<div\n              class=\"gatsby-code-button-container\"\n              data-toaster-id=\"36016474830443370000\"\n              data-toaster-class=\"gatsby-code-button-toaster\"\n              data-toaster-text-class=\"gatsby-code-button-toaster-text\"\n              data-toaster-text=\"代码复制成功\"\n              data-toaster-duration=\"3500\"\n              onClick=\"copyToClipboard(`\\$ cd frontend\n\\$ npm install\n\\$ npm run dev`, `36016474830443370000`)\"\n            >\n              <div\n                class=\"gatsby-code-button\"\n                data-tooltip=\"\"\n              >\n                复制代码<svg class=\"gatsby-code-button-icon\" xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><path fill=\"none\" d=\"M0 0h24v24H0V0z\"/><path d=\"M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z\"/></svg>\n              </div>\n            </div>\n<div class=\"gatsby-highlight\" data-language=\"bash\"><pre class=\"language-bash\"><code class=\"language-bash\">$ <span class=\"token builtin class-name\">cd</span> frontend\n$ <span class=\"token function\">npm</span> <span class=\"token function\">install</span>\n$ <span class=\"token function\">npm</span> run dev</code></pre></div>\n<p>然后访问 <code class=\"language-text\">http://localhost:9528</code> 就可以看到登录界面了。</p>\n<h2 id=\"开发后端服务\"><a href=\"#%E5%BC%80%E5%8F%91%E5%90%8E%E7%AB%AF%E6%9C%8D%E5%8A%A1\" aria-label=\"开发后端服务 permalink\" class=\"anchor\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>开发后端服务</h2>\n<p>对于一个后台管理系统服务，我们这里只实现登录鉴权和文章管理功能，剩下的其他功能大同小异，读者可以之后自由补充扩展。</p>\n<h3 id=\"1-添加-sequelize-插件\"><a href=\"#1-%E6%B7%BB%E5%8A%A0-sequelize-%E6%8F%92%E4%BB%B6\" aria-label=\"1 添加 sequelize 插件 permalink\" class=\"anchor\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>1. 添加 Sequelize 插件</h3>\n<p>在正式开发之前，我们需要引入数据库插件，这里本人偏向于使用 <a href=\"https://github.com/sequelize/sequelize/\">Sequelize</a> ORM 工具进行数据库操作，正好 Egg.js 提供了 <a href=\"https://github.com/eggjs/egg-sequelize\">egg-sequelize</a> 插件，于是直接拿来用，需要先安装：</p>\n<div\n              class=\"gatsby-code-button-container\"\n              data-toaster-id=\"3072828175634456600\"\n              data-toaster-class=\"gatsby-code-button-toaster\"\n              data-toaster-text-class=\"gatsby-code-button-toaster-text\"\n              data-toaster-text=\"代码复制成功\"\n              data-toaster-duration=\"3500\"\n              onClick=\"copyToClipboard(`\\$ cd frontend\n# 因为需要通过 sequelize 链接 mysql 所以这也同时安装 mysql2 模块\n\\$ npm install egg-sequelize mysql2 --save`, `3072828175634456600`)\"\n            >\n              <div\n                class=\"gatsby-code-button\"\n                data-tooltip=\"\"\n              >\n                复制代码<svg class=\"gatsby-code-button-icon\" xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><path fill=\"none\" d=\"M0 0h24v24H0V0z\"/><path d=\"M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z\"/></svg>\n              </div>\n            </div>\n<div class=\"gatsby-highlight\" data-language=\"bash\"><pre class=\"language-bash\"><code class=\"language-bash\">$ <span class=\"token builtin class-name\">cd</span> frontend\n<span class=\"token comment\"># 因为需要通过 sequelize 链接 mysql 所以这也同时安装 mysql2 模块</span>\n$ <span class=\"token function\">npm</span> <span class=\"token function\">install</span> egg-sequelize mysql2 --save</code></pre></div>\n<p>然后在 <code class=\"language-text\">backend/config/plugin.js</code> 中引入该插件：</p>\n<div\n              class=\"gatsby-code-button-container\"\n              data-toaster-id=\"57280505287322670000\"\n              data-toaster-class=\"gatsby-code-button-toaster\"\n              data-toaster-text-class=\"gatsby-code-button-toaster-text\"\n              data-toaster-text=\"代码复制成功\"\n              data-toaster-duration=\"3500\"\n              onClick=\"copyToClipboard(`module.exports = {\n  // ....\n  sequelize: {\n    enable: true,\n    package: &quot;egg-sequelize&quot;\n  }\n  // ....\n};`, `57280505287322670000`)\"\n            >\n              <div\n                class=\"gatsby-code-button\"\n                data-tooltip=\"\"\n              >\n                复制代码<svg class=\"gatsby-code-button-icon\" xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><path fill=\"none\" d=\"M0 0h24v24H0V0z\"/><path d=\"M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z\"/></svg>\n              </div>\n            </div>\n<div class=\"gatsby-highlight\" data-language=\"js\"><pre class=\"language-js\"><code class=\"language-js\">module<span class=\"token punctuation\">.</span>exports <span class=\"token operator\">=</span> <span class=\"token punctuation\">{</span>\n  <span class=\"token comment\">// ....</span>\n  sequelize<span class=\"token punctuation\">:</span> <span class=\"token punctuation\">{</span>\n    enable<span class=\"token punctuation\">:</span> <span class=\"token boolean\">true</span><span class=\"token punctuation\">,</span>\n    <span class=\"token keyword\">package</span><span class=\"token punctuation\">:</span> <span class=\"token string\">\"egg-sequelize\"</span>\n  <span class=\"token punctuation\">}</span>\n  <span class=\"token comment\">// ....</span>\n<span class=\"token punctuation\">}</span><span class=\"token punctuation\">;</span></code></pre></div>\n<p>在 <code class=\"language-text\">backend/config/config.default.js</code> 中配置数据库连接参数：</p>\n<div\n              class=\"gatsby-code-button-container\"\n              data-toaster-id=\"7216375910850958000\"\n              data-toaster-class=\"gatsby-code-button-toaster\"\n              data-toaster-text-class=\"gatsby-code-button-toaster-text\"\n              data-toaster-text=\"代码复制成功\"\n              data-toaster-duration=\"3500\"\n              onClick=\"copyToClipboard(`// ...\nconst userConfig = {\n  // ...\n  sequelize: {\n    dialect: &quot;mysql&quot;,\n\n    // 这里也可以通过 .env 文件注入环境变量，然后通过 process.env 获取\n    host: &quot;xxx&quot;,\n    port: &quot;xxx&quot;,\n    database: &quot;xxx&quot;,\n    username: &quot;xxx&quot;,\n    password: &quot;xxx&quot;\n  }\n  // ...\n};\n// ...`, `7216375910850958000`)\"\n            >\n              <div\n                class=\"gatsby-code-button\"\n                data-tooltip=\"\"\n              >\n                复制代码<svg class=\"gatsby-code-button-icon\" xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><path fill=\"none\" d=\"M0 0h24v24H0V0z\"/><path d=\"M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z\"/></svg>\n              </div>\n            </div>\n<div class=\"gatsby-highlight\" data-language=\"js\"><pre class=\"language-js\"><code class=\"language-js\"><span class=\"token comment\">// ...</span>\n<span class=\"token keyword\">const</span> userConfig <span class=\"token operator\">=</span> <span class=\"token punctuation\">{</span>\n  <span class=\"token comment\">// ...</span>\n  sequelize<span class=\"token punctuation\">:</span> <span class=\"token punctuation\">{</span>\n    dialect<span class=\"token punctuation\">:</span> <span class=\"token string\">\"mysql\"</span><span class=\"token punctuation\">,</span>\n\n    <span class=\"token comment\">// 这里也可以通过 .env 文件注入环境变量，然后通过 process.env 获取</span>\n    host<span class=\"token punctuation\">:</span> <span class=\"token string\">\"xxx\"</span><span class=\"token punctuation\">,</span>\n    port<span class=\"token punctuation\">:</span> <span class=\"token string\">\"xxx\"</span><span class=\"token punctuation\">,</span>\n    database<span class=\"token punctuation\">:</span> <span class=\"token string\">\"xxx\"</span><span class=\"token punctuation\">,</span>\n    username<span class=\"token punctuation\">:</span> <span class=\"token string\">\"xxx\"</span><span class=\"token punctuation\">,</span>\n    password<span class=\"token punctuation\">:</span> <span class=\"token string\">\"xxx\"</span>\n  <span class=\"token punctuation\">}</span>\n  <span class=\"token comment\">// ...</span>\n<span class=\"token punctuation\">}</span><span class=\"token punctuation\">;</span>\n<span class=\"token comment\">// ...</span></code></pre></div>\n<h3 id=\"2-添加-jwt-插件\"><a href=\"#2-%E6%B7%BB%E5%8A%A0-jwt-%E6%8F%92%E4%BB%B6\" aria-label=\"2 添加 jwt 插件 permalink\" class=\"anchor\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>2. 添加 JWT 插件</h3>\n<p>系统将使用 JWT token 方式进行登录鉴权，安装配置参考官方文档，<a href=\"https://github.com/eggjs/egg-jwt\">egg-jwt</a></p>\n<h3 id=\"3-添加-redis-插件\"><a href=\"#3-%E6%B7%BB%E5%8A%A0-redis-%E6%8F%92%E4%BB%B6\" aria-label=\"3 添加 redis 插件 permalink\" class=\"anchor\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>3. 添加 Redis 插件</h3>\n<p>系统将使用 redis 来存储和管理用户 token，安装配置参考官方文档，<a href=\"https://github.com/eggjs/egg-redis\">egg-redis</a></p>\n<h3 id=\"4-角色-api\"><a href=\"#4-%E8%A7%92%E8%89%B2-api\" aria-label=\"4 角色 api permalink\" class=\"anchor\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>4. 角色 API</h3>\n<p>定义用户模型，创建 <code class=\"language-text\">backend/app/model/role.js</code> 文件如下：</p>\n<div\n              class=\"gatsby-code-button-container\"\n              data-toaster-id=\"98945050390133490000\"\n              data-toaster-class=\"gatsby-code-button-toaster\"\n              data-toaster-text-class=\"gatsby-code-button-toaster-text\"\n              data-toaster-text=\"代码复制成功\"\n              data-toaster-duration=\"3500\"\n              onClick=\"copyToClipboard(`module.exports = app => {\n  const { STRING, INTEGER, DATE } = app.Sequelize;\n\n  const Role = app.model.define(&quot;role&quot;, {\n    id: { type: INTEGER, primaryKey: true, autoIncrement: true },\n    name: STRING(30),\n    created_at: DATE,\n    updated_at: DATE\n  });\n\n  // 这里定义与 users 表的关系，一个角色可以含有多个用户，外键相关\n  Role.associate = () => {\n    app.model.Role.hasMany(app.model.User, { as: &quot;users&quot; });\n  };\n\n  return Role;\n};`, `98945050390133490000`)\"\n            >\n              <div\n                class=\"gatsby-code-button\"\n                data-tooltip=\"\"\n              >\n                复制代码<svg class=\"gatsby-code-button-icon\" xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><path fill=\"none\" d=\"M0 0h24v24H0V0z\"/><path d=\"M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z\"/></svg>\n              </div>\n            </div>\n<div class=\"gatsby-highlight\" data-language=\"js\"><pre class=\"language-js\"><code class=\"language-js\">module<span class=\"token punctuation\">.</span><span class=\"token function-variable function\">exports</span> <span class=\"token operator\">=</span> <span class=\"token parameter\">app</span> <span class=\"token operator\">=></span> <span class=\"token punctuation\">{</span>\n  <span class=\"token keyword\">const</span> <span class=\"token punctuation\">{</span> <span class=\"token constant\">STRING</span><span class=\"token punctuation\">,</span> <span class=\"token constant\">INTEGER</span><span class=\"token punctuation\">,</span> <span class=\"token constant\">DATE</span> <span class=\"token punctuation\">}</span> <span class=\"token operator\">=</span> app<span class=\"token punctuation\">.</span>Sequelize<span class=\"token punctuation\">;</span>\n\n  <span class=\"token keyword\">const</span> Role <span class=\"token operator\">=</span> app<span class=\"token punctuation\">.</span>model<span class=\"token punctuation\">.</span><span class=\"token function\">define</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"role\"</span><span class=\"token punctuation\">,</span> <span class=\"token punctuation\">{</span>\n    id<span class=\"token punctuation\">:</span> <span class=\"token punctuation\">{</span> type<span class=\"token punctuation\">:</span> <span class=\"token constant\">INTEGER</span><span class=\"token punctuation\">,</span> primaryKey<span class=\"token punctuation\">:</span> <span class=\"token boolean\">true</span><span class=\"token punctuation\">,</span> autoIncrement<span class=\"token punctuation\">:</span> <span class=\"token boolean\">true</span> <span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span>\n    name<span class=\"token punctuation\">:</span> <span class=\"token constant\">STRING</span><span class=\"token punctuation\">(</span><span class=\"token number\">30</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">,</span>\n    created_at<span class=\"token punctuation\">:</span> <span class=\"token constant\">DATE</span><span class=\"token punctuation\">,</span>\n    updated_at<span class=\"token punctuation\">:</span> <span class=\"token constant\">DATE</span>\n  <span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\n  <span class=\"token comment\">// 这里定义与 users 表的关系，一个角色可以含有多个用户，外键相关</span>\n  Role<span class=\"token punctuation\">.</span><span class=\"token function-variable function\">associate</span> <span class=\"token operator\">=</span> <span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">=></span> <span class=\"token punctuation\">{</span>\n    app<span class=\"token punctuation\">.</span>model<span class=\"token punctuation\">.</span>Role<span class=\"token punctuation\">.</span><span class=\"token function\">hasMany</span><span class=\"token punctuation\">(</span>app<span class=\"token punctuation\">.</span>model<span class=\"token punctuation\">.</span>User<span class=\"token punctuation\">,</span> <span class=\"token punctuation\">{</span> <span class=\"token keyword\">as</span><span class=\"token punctuation\">:</span> <span class=\"token string\">\"users\"</span> <span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n  <span class=\"token punctuation\">}</span><span class=\"token punctuation\">;</span>\n\n  <span class=\"token keyword\">return</span> Role<span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span><span class=\"token punctuation\">;</span></code></pre></div>\n<p>实现 Role 相关服务，创建 <code class=\"language-text\">backend/app/service/role.js</code> 文件如下：</p>\n<div\n              class=\"gatsby-code-button-container\"\n              data-toaster-id=\"25524289374018093000\"\n              data-toaster-class=\"gatsby-code-button-toaster\"\n              data-toaster-text-class=\"gatsby-code-button-toaster-text\"\n              data-toaster-text=\"代码复制成功\"\n              data-toaster-duration=\"3500\"\n              onClick=\"copyToClipboard(`const { Service } = require(&quot;egg&quot;);\n\nclass RoleService extends Service {\n  // 获取角色列表\n  async list(options) {\n    const {\n      ctx: { model }\n    } = this;\n    return model.Role.findAndCountAll({\n      ...options,\n      order: [\n        [&quot;created_at&quot;, &quot;desc&quot;],\n        [&quot;id&quot;, &quot;desc&quot;]\n      ]\n    });\n  }\n\n  // 通过 id 获取角色\n  async find(id) {\n    const {\n      ctx: { model }\n    } = this;\n    const role = await model.Role.findByPk(id);\n    if (!role) {\n      this.ctx.throw(404, &quot;role not found&quot;);\n    }\n    return role;\n  }\n\n  // 创建角色\n  async create(role) {\n    const {\n      ctx: { model }\n    } = this;\n    return model.Role.create(role);\n  }\n\n  // 更新角色\n  async update({ id, updates }) {\n    const role = await this.ctx.model.Role.findByPk(id);\n    if (!role) {\n      this.ctx.throw(404, &quot;role not found&quot;);\n    }\n    return role.update(updates);\n  }\n\n  // 删除角色\n  async destroy(id) {\n    const role = await this.ctx.model.Role.findByPk(id);\n    if (!role) {\n      this.ctx.throw(404, &quot;role not found&quot;);\n    }\n    return role.destroy();\n  }\n}\n\nmodule.exports = RoleService;`, `25524289374018093000`)\"\n            >\n              <div\n                class=\"gatsby-code-button\"\n                data-tooltip=\"\"\n              >\n                复制代码<svg class=\"gatsby-code-button-icon\" xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><path fill=\"none\" d=\"M0 0h24v24H0V0z\"/><path d=\"M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z\"/></svg>\n              </div>\n            </div>\n<div class=\"gatsby-highlight\" data-language=\"js\"><pre class=\"language-js\"><code class=\"language-js\"><span class=\"token keyword\">const</span> <span class=\"token punctuation\">{</span> Service <span class=\"token punctuation\">}</span> <span class=\"token operator\">=</span> <span class=\"token function\">require</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"egg\"</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\n<span class=\"token keyword\">class</span> <span class=\"token class-name\">RoleService</span> <span class=\"token keyword\">extends</span> <span class=\"token class-name\">Service</span> <span class=\"token punctuation\">{</span>\n  <span class=\"token comment\">// 获取角色列表</span>\n  <span class=\"token keyword\">async</span> <span class=\"token function\">list</span><span class=\"token punctuation\">(</span><span class=\"token parameter\">options</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n    <span class=\"token keyword\">const</span> <span class=\"token punctuation\">{</span>\n      ctx<span class=\"token punctuation\">:</span> <span class=\"token punctuation\">{</span> model <span class=\"token punctuation\">}</span>\n    <span class=\"token punctuation\">}</span> <span class=\"token operator\">=</span> <span class=\"token keyword\">this</span><span class=\"token punctuation\">;</span>\n    <span class=\"token keyword\">return</span> model<span class=\"token punctuation\">.</span>Role<span class=\"token punctuation\">.</span><span class=\"token function\">findAndCountAll</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">{</span>\n      <span class=\"token operator\">...</span>options<span class=\"token punctuation\">,</span>\n      order<span class=\"token punctuation\">:</span> <span class=\"token punctuation\">[</span>\n        <span class=\"token punctuation\">[</span><span class=\"token string\">\"created_at\"</span><span class=\"token punctuation\">,</span> <span class=\"token string\">\"desc\"</span><span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span>\n        <span class=\"token punctuation\">[</span><span class=\"token string\">\"id\"</span><span class=\"token punctuation\">,</span> <span class=\"token string\">\"desc\"</span><span class=\"token punctuation\">]</span>\n      <span class=\"token punctuation\">]</span>\n    <span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n  <span class=\"token punctuation\">}</span>\n\n  <span class=\"token comment\">// 通过 id 获取角色</span>\n  <span class=\"token keyword\">async</span> <span class=\"token function\">find</span><span class=\"token punctuation\">(</span><span class=\"token parameter\">id</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n    <span class=\"token keyword\">const</span> <span class=\"token punctuation\">{</span>\n      ctx<span class=\"token punctuation\">:</span> <span class=\"token punctuation\">{</span> model <span class=\"token punctuation\">}</span>\n    <span class=\"token punctuation\">}</span> <span class=\"token operator\">=</span> <span class=\"token keyword\">this</span><span class=\"token punctuation\">;</span>\n    <span class=\"token keyword\">const</span> role <span class=\"token operator\">=</span> <span class=\"token keyword\">await</span> model<span class=\"token punctuation\">.</span>Role<span class=\"token punctuation\">.</span><span class=\"token function\">findByPk</span><span class=\"token punctuation\">(</span>id<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n    <span class=\"token keyword\">if</span> <span class=\"token punctuation\">(</span><span class=\"token operator\">!</span>role<span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n      <span class=\"token keyword\">this</span><span class=\"token punctuation\">.</span>ctx<span class=\"token punctuation\">.</span><span class=\"token function\">throw</span><span class=\"token punctuation\">(</span><span class=\"token number\">404</span><span class=\"token punctuation\">,</span> <span class=\"token string\">\"role not found\"</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n    <span class=\"token punctuation\">}</span>\n    <span class=\"token keyword\">return</span> role<span class=\"token punctuation\">;</span>\n  <span class=\"token punctuation\">}</span>\n\n  <span class=\"token comment\">// 创建角色</span>\n  <span class=\"token keyword\">async</span> <span class=\"token function\">create</span><span class=\"token punctuation\">(</span><span class=\"token parameter\">role</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n    <span class=\"token keyword\">const</span> <span class=\"token punctuation\">{</span>\n      ctx<span class=\"token punctuation\">:</span> <span class=\"token punctuation\">{</span> model <span class=\"token punctuation\">}</span>\n    <span class=\"token punctuation\">}</span> <span class=\"token operator\">=</span> <span class=\"token keyword\">this</span><span class=\"token punctuation\">;</span>\n    <span class=\"token keyword\">return</span> model<span class=\"token punctuation\">.</span>Role<span class=\"token punctuation\">.</span><span class=\"token function\">create</span><span class=\"token punctuation\">(</span>role<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n  <span class=\"token punctuation\">}</span>\n\n  <span class=\"token comment\">// 更新角色</span>\n  <span class=\"token keyword\">async</span> <span class=\"token function\">update</span><span class=\"token punctuation\">(</span><span class=\"token parameter\"><span class=\"token punctuation\">{</span> id<span class=\"token punctuation\">,</span> updates <span class=\"token punctuation\">}</span></span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n    <span class=\"token keyword\">const</span> role <span class=\"token operator\">=</span> <span class=\"token keyword\">await</span> <span class=\"token keyword\">this</span><span class=\"token punctuation\">.</span>ctx<span class=\"token punctuation\">.</span>model<span class=\"token punctuation\">.</span>Role<span class=\"token punctuation\">.</span><span class=\"token function\">findByPk</span><span class=\"token punctuation\">(</span>id<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n    <span class=\"token keyword\">if</span> <span class=\"token punctuation\">(</span><span class=\"token operator\">!</span>role<span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n      <span class=\"token keyword\">this</span><span class=\"token punctuation\">.</span>ctx<span class=\"token punctuation\">.</span><span class=\"token function\">throw</span><span class=\"token punctuation\">(</span><span class=\"token number\">404</span><span class=\"token punctuation\">,</span> <span class=\"token string\">\"role not found\"</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n    <span class=\"token punctuation\">}</span>\n    <span class=\"token keyword\">return</span> role<span class=\"token punctuation\">.</span><span class=\"token function\">update</span><span class=\"token punctuation\">(</span>updates<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n  <span class=\"token punctuation\">}</span>\n\n  <span class=\"token comment\">// 删除角色</span>\n  <span class=\"token keyword\">async</span> <span class=\"token function\">destroy</span><span class=\"token punctuation\">(</span><span class=\"token parameter\">id</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n    <span class=\"token keyword\">const</span> role <span class=\"token operator\">=</span> <span class=\"token keyword\">await</span> <span class=\"token keyword\">this</span><span class=\"token punctuation\">.</span>ctx<span class=\"token punctuation\">.</span>model<span class=\"token punctuation\">.</span>Role<span class=\"token punctuation\">.</span><span class=\"token function\">findByPk</span><span class=\"token punctuation\">(</span>id<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n    <span class=\"token keyword\">if</span> <span class=\"token punctuation\">(</span><span class=\"token operator\">!</span>role<span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n      <span class=\"token keyword\">this</span><span class=\"token punctuation\">.</span>ctx<span class=\"token punctuation\">.</span><span class=\"token function\">throw</span><span class=\"token punctuation\">(</span><span class=\"token number\">404</span><span class=\"token punctuation\">,</span> <span class=\"token string\">\"role not found\"</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n    <span class=\"token punctuation\">}</span>\n    <span class=\"token keyword\">return</span> role<span class=\"token punctuation\">.</span><span class=\"token function\">destroy</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n  <span class=\"token punctuation\">}</span>\n<span class=\"token punctuation\">}</span>\n\nmodule<span class=\"token punctuation\">.</span>exports <span class=\"token operator\">=</span> RoleService<span class=\"token punctuation\">;</span></code></pre></div>\n<p>一个完整的 RESTful API 就该包括以上五个方法，然后实现 <code class=\"language-text\">RoleController</code>, 创建 <code class=\"language-text\">backend/app/controller/role.js</code>:</p>\n<div\n              class=\"gatsby-code-button-container\"\n              data-toaster-id=\"75553602053367560000\"\n              data-toaster-class=\"gatsby-code-button-toaster\"\n              data-toaster-text-class=\"gatsby-code-button-toaster-text\"\n              data-toaster-text=\"代码复制成功\"\n              data-toaster-duration=\"3500\"\n              onClick=\"copyToClipboard(`const { Controller } = require(&quot;egg&quot;);\n\nclass RoleController extends Controller {\n  async index() {\n    const { ctx } = this;\n    const { query, service, helper } = ctx;\n    const options = {\n      limit: helper.parseInt(query.limit),\n      offset: helper.parseInt(query.offset)\n    };\n    const data = await service.role.list(options);\n    ctx.body = {\n      code: 0,\n      data: {\n        count: data.count,\n        items: data.rows\n      }\n    };\n  }\n\n  async show() {\n    const { ctx } = this;\n    const { params, service, helper } = ctx;\n    const id = helper.parseInt(params.id);\n    ctx.body = await service.role.find(id);\n  }\n\n  async create() {\n    const { ctx } = this;\n    const { service } = ctx;\n    const body = ctx.request.body;\n    const role = await service.role.create(body);\n    ctx.status = 201;\n    ctx.body = role;\n  }\n\n  async update() {\n    const { ctx } = this;\n    const { params, service, helper } = ctx;\n    const body = ctx.request.body;\n    const id = helper.parseInt(params.id);\n    ctx.body = await service.role.update({\n      id,\n      updates: body\n    });\n  }\n\n  async destroy() {\n    const { ctx } = this;\n    const { params, service, helper } = ctx;\n    const id = helper.parseInt(params.id);\n    await service.role.destroy(id);\n    ctx.status = 200;\n  }\n}\n\nmodule.exports = RoleController;`, `75553602053367560000`)\"\n            >\n              <div\n                class=\"gatsby-code-button\"\n                data-tooltip=\"\"\n              >\n                复制代码<svg class=\"gatsby-code-button-icon\" xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><path fill=\"none\" d=\"M0 0h24v24H0V0z\"/><path d=\"M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z\"/></svg>\n              </div>\n            </div>\n<div class=\"gatsby-highlight\" data-language=\"js\"><pre class=\"language-js\"><code class=\"language-js\"><span class=\"token keyword\">const</span> <span class=\"token punctuation\">{</span> Controller <span class=\"token punctuation\">}</span> <span class=\"token operator\">=</span> <span class=\"token function\">require</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"egg\"</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\n<span class=\"token keyword\">class</span> <span class=\"token class-name\">RoleController</span> <span class=\"token keyword\">extends</span> <span class=\"token class-name\">Controller</span> <span class=\"token punctuation\">{</span>\n  <span class=\"token keyword\">async</span> <span class=\"token function\">index</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n    <span class=\"token keyword\">const</span> <span class=\"token punctuation\">{</span> ctx <span class=\"token punctuation\">}</span> <span class=\"token operator\">=</span> <span class=\"token keyword\">this</span><span class=\"token punctuation\">;</span>\n    <span class=\"token keyword\">const</span> <span class=\"token punctuation\">{</span> query<span class=\"token punctuation\">,</span> service<span class=\"token punctuation\">,</span> helper <span class=\"token punctuation\">}</span> <span class=\"token operator\">=</span> ctx<span class=\"token punctuation\">;</span>\n    <span class=\"token keyword\">const</span> options <span class=\"token operator\">=</span> <span class=\"token punctuation\">{</span>\n      limit<span class=\"token punctuation\">:</span> helper<span class=\"token punctuation\">.</span><span class=\"token function\">parseInt</span><span class=\"token punctuation\">(</span>query<span class=\"token punctuation\">.</span>limit<span class=\"token punctuation\">)</span><span class=\"token punctuation\">,</span>\n      offset<span class=\"token punctuation\">:</span> helper<span class=\"token punctuation\">.</span><span class=\"token function\">parseInt</span><span class=\"token punctuation\">(</span>query<span class=\"token punctuation\">.</span>offset<span class=\"token punctuation\">)</span>\n    <span class=\"token punctuation\">}</span><span class=\"token punctuation\">;</span>\n    <span class=\"token keyword\">const</span> data <span class=\"token operator\">=</span> <span class=\"token keyword\">await</span> service<span class=\"token punctuation\">.</span>role<span class=\"token punctuation\">.</span><span class=\"token function\">list</span><span class=\"token punctuation\">(</span>options<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n    ctx<span class=\"token punctuation\">.</span>body <span class=\"token operator\">=</span> <span class=\"token punctuation\">{</span>\n      code<span class=\"token punctuation\">:</span> <span class=\"token number\">0</span><span class=\"token punctuation\">,</span>\n      data<span class=\"token punctuation\">:</span> <span class=\"token punctuation\">{</span>\n        count<span class=\"token punctuation\">:</span> data<span class=\"token punctuation\">.</span>count<span class=\"token punctuation\">,</span>\n        items<span class=\"token punctuation\">:</span> data<span class=\"token punctuation\">.</span>rows\n      <span class=\"token punctuation\">}</span>\n    <span class=\"token punctuation\">}</span><span class=\"token punctuation\">;</span>\n  <span class=\"token punctuation\">}</span>\n\n  <span class=\"token keyword\">async</span> <span class=\"token function\">show</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n    <span class=\"token keyword\">const</span> <span class=\"token punctuation\">{</span> ctx <span class=\"token punctuation\">}</span> <span class=\"token operator\">=</span> <span class=\"token keyword\">this</span><span class=\"token punctuation\">;</span>\n    <span class=\"token keyword\">const</span> <span class=\"token punctuation\">{</span> params<span class=\"token punctuation\">,</span> service<span class=\"token punctuation\">,</span> helper <span class=\"token punctuation\">}</span> <span class=\"token operator\">=</span> ctx<span class=\"token punctuation\">;</span>\n    <span class=\"token keyword\">const</span> id <span class=\"token operator\">=</span> helper<span class=\"token punctuation\">.</span><span class=\"token function\">parseInt</span><span class=\"token punctuation\">(</span>params<span class=\"token punctuation\">.</span>id<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n    ctx<span class=\"token punctuation\">.</span>body <span class=\"token operator\">=</span> <span class=\"token keyword\">await</span> service<span class=\"token punctuation\">.</span>role<span class=\"token punctuation\">.</span><span class=\"token function\">find</span><span class=\"token punctuation\">(</span>id<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n  <span class=\"token punctuation\">}</span>\n\n  <span class=\"token keyword\">async</span> <span class=\"token function\">create</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n    <span class=\"token keyword\">const</span> <span class=\"token punctuation\">{</span> ctx <span class=\"token punctuation\">}</span> <span class=\"token operator\">=</span> <span class=\"token keyword\">this</span><span class=\"token punctuation\">;</span>\n    <span class=\"token keyword\">const</span> <span class=\"token punctuation\">{</span> service <span class=\"token punctuation\">}</span> <span class=\"token operator\">=</span> ctx<span class=\"token punctuation\">;</span>\n    <span class=\"token keyword\">const</span> body <span class=\"token operator\">=</span> ctx<span class=\"token punctuation\">.</span>request<span class=\"token punctuation\">.</span>body<span class=\"token punctuation\">;</span>\n    <span class=\"token keyword\">const</span> role <span class=\"token operator\">=</span> <span class=\"token keyword\">await</span> service<span class=\"token punctuation\">.</span>role<span class=\"token punctuation\">.</span><span class=\"token function\">create</span><span class=\"token punctuation\">(</span>body<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n    ctx<span class=\"token punctuation\">.</span>status <span class=\"token operator\">=</span> <span class=\"token number\">201</span><span class=\"token punctuation\">;</span>\n    ctx<span class=\"token punctuation\">.</span>body <span class=\"token operator\">=</span> role<span class=\"token punctuation\">;</span>\n  <span class=\"token punctuation\">}</span>\n\n  <span class=\"token keyword\">async</span> <span class=\"token function\">update</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n    <span class=\"token keyword\">const</span> <span class=\"token punctuation\">{</span> ctx <span class=\"token punctuation\">}</span> <span class=\"token operator\">=</span> <span class=\"token keyword\">this</span><span class=\"token punctuation\">;</span>\n    <span class=\"token keyword\">const</span> <span class=\"token punctuation\">{</span> params<span class=\"token punctuation\">,</span> service<span class=\"token punctuation\">,</span> helper <span class=\"token punctuation\">}</span> <span class=\"token operator\">=</span> ctx<span class=\"token punctuation\">;</span>\n    <span class=\"token keyword\">const</span> body <span class=\"token operator\">=</span> ctx<span class=\"token punctuation\">.</span>request<span class=\"token punctuation\">.</span>body<span class=\"token punctuation\">;</span>\n    <span class=\"token keyword\">const</span> id <span class=\"token operator\">=</span> helper<span class=\"token punctuation\">.</span><span class=\"token function\">parseInt</span><span class=\"token punctuation\">(</span>params<span class=\"token punctuation\">.</span>id<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n    ctx<span class=\"token punctuation\">.</span>body <span class=\"token operator\">=</span> <span class=\"token keyword\">await</span> service<span class=\"token punctuation\">.</span>role<span class=\"token punctuation\">.</span><span class=\"token function\">update</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">{</span>\n      id<span class=\"token punctuation\">,</span>\n      updates<span class=\"token punctuation\">:</span> body\n    <span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n  <span class=\"token punctuation\">}</span>\n\n  <span class=\"token keyword\">async</span> <span class=\"token function\">destroy</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n    <span class=\"token keyword\">const</span> <span class=\"token punctuation\">{</span> ctx <span class=\"token punctuation\">}</span> <span class=\"token operator\">=</span> <span class=\"token keyword\">this</span><span class=\"token punctuation\">;</span>\n    <span class=\"token keyword\">const</span> <span class=\"token punctuation\">{</span> params<span class=\"token punctuation\">,</span> service<span class=\"token punctuation\">,</span> helper <span class=\"token punctuation\">}</span> <span class=\"token operator\">=</span> ctx<span class=\"token punctuation\">;</span>\n    <span class=\"token keyword\">const</span> id <span class=\"token operator\">=</span> helper<span class=\"token punctuation\">.</span><span class=\"token function\">parseInt</span><span class=\"token punctuation\">(</span>params<span class=\"token punctuation\">.</span>id<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n    <span class=\"token keyword\">await</span> service<span class=\"token punctuation\">.</span>role<span class=\"token punctuation\">.</span><span class=\"token function\">destroy</span><span class=\"token punctuation\">(</span>id<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n    ctx<span class=\"token punctuation\">.</span>status <span class=\"token operator\">=</span> <span class=\"token number\">200</span><span class=\"token punctuation\">;</span>\n  <span class=\"token punctuation\">}</span>\n<span class=\"token punctuation\">}</span>\n\nmodule<span class=\"token punctuation\">.</span>exports <span class=\"token operator\">=</span> RoleController<span class=\"token punctuation\">;</span></code></pre></div>\n<p>之后在 <code class=\"language-text\">backend/app/route.js</code> 路由配置文件中定义 <code class=\"language-text\">role</code> 的 RESTful API:</p>\n<div\n              class=\"gatsby-code-button-container\"\n              data-toaster-id=\"50614630969799320000\"\n              data-toaster-class=\"gatsby-code-button-toaster\"\n              data-toaster-text-class=\"gatsby-code-button-toaster-text\"\n              data-toaster-text=\"代码复制成功\"\n              data-toaster-duration=\"3500\"\n              onClick=\"copyToClipboard(`router.resources(&quot;roles&quot;, &quot;/roles&quot;, controller.role);`, `50614630969799320000`)\"\n            >\n              <div\n                class=\"gatsby-code-button\"\n                data-tooltip=\"\"\n              >\n                复制代码<svg class=\"gatsby-code-button-icon\" xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><path fill=\"none\" d=\"M0 0h24v24H0V0z\"/><path d=\"M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z\"/></svg>\n              </div>\n            </div>\n<div class=\"gatsby-highlight\" data-language=\"js\"><pre class=\"language-js\"><code class=\"language-js\">router<span class=\"token punctuation\">.</span><span class=\"token function\">resources</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"roles\"</span><span class=\"token punctuation\">,</span> <span class=\"token string\">\"/roles\"</span><span class=\"token punctuation\">,</span> controller<span class=\"token punctuation\">.</span>role<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span></code></pre></div>\n<p>通过 <code class=\"language-text\">router.resources</code> 方法，我们将 <code class=\"language-text\">roles</code> 这个资源的增删改查接口映射到了 <code class=\"language-text\">app/controller/roles.js</code> 文件。详细说明参考 <a href=\"https://eggjs.org/zh-cn/tutorials/restful.html\">官方文档</a></p>\n<h3 id=\"5-用户-api\"><a href=\"#5-%E7%94%A8%E6%88%B7-api\" aria-label=\"5 用户 api permalink\" class=\"anchor\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>5. 用户 API</h3>\n<p>同 Role 一样定义我们的用户 API，这里就不复制粘贴了，可以参考项目实例源码 <a href=\"https://github.com/yugasun/tencent-serverless-demo/admin-system\">admin-system</a>。</p>\n<h3 id=\"6-同步数据库表格\"><a href=\"#6-%E5%90%8C%E6%AD%A5%E6%95%B0%E6%8D%AE%E5%BA%93%E8%A1%A8%E6%A0%BC\" aria-label=\"6 同步数据库表格 permalink\" class=\"anchor\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>6. 同步数据库表格</h3>\n<p>上面只是定义好了 <code class=\"language-text\">Role</code> 和 <code class=\"language-text\">User</code> 两个 Schema，那么如何同步到数据库呢？这里先借助 Egg.js 启动的 hooks 来实现，Egg.js 框架提供了统一的入口文件（app.js）进行启动过程自定义，这个文件返回一个 Boot 类，我们可以通过定义 Boot 类中的生命周期方法来执行启动应用过程中的初始化工作。</p>\n<p>我们在 <code class=\"language-text\">backend</code> 目录中创建 <code class=\"language-text\">app.js</code> 文件，如下：</p>\n<div\n              class=\"gatsby-code-button-container\"\n              data-toaster-id=\"17187363472350458000\"\n              data-toaster-class=\"gatsby-code-button-toaster\"\n              data-toaster-text-class=\"gatsby-code-button-toaster-text\"\n              data-toaster-text=\"代码复制成功\"\n              data-toaster-duration=\"3500\"\n              onClick=\"copyToClipboard(`&quot;use strict&quot;;\n\nclass AppBootHook {\n  constructor(app) {\n    this.app = app;\n  }\n\n  async willReady() {\n    // 这里只能在开发模式下同步数据库表格\n    const isDev = process.env.NODE_ENV === &quot;development&quot;;\n    if (isDev) {\n      try {\n        console.log(&quot;Start syncing database models...&quot;);\n        await this.app.model.sync({ logging: console.log, force: isDev });\n        console.log(&quot;Start init database data...&quot;);\n        await this.app.model.query(\n          &quot;INSERT INTO roles (id, name, created_at, updated_at) VALUES (1, 'admin', '2020-02-04 09:54:25', '2020-02-04 09:54:25'),(2, 'editor', '2020-02-04 09:54:30', '2020-02-04 09:54:30');&quot;\n        );\n        await this.app.model.query(\n          &quot;INSERT INTO users (id, name, password, age, avatar, introduction, created_at, updated_at, role_id) VALUES (1, 'admin', 'e10adc3949ba59abbe56e057f20f883e', 20, 'https://yugasun.com/static/avatar.jpg', 'Fullstack Engineer', '2020-02-04 09:55:23', '2020-02-04 09:55:23', 1);&quot;\n        );\n        await this.app.model.query(\n          &quot;INSERT INTO posts (id, title, content, created_at, updated_at, user_id) VALUES (2, 'Awesome Egg.js', 'Egg.js is a awesome framework', '2020-02-04 09:57:24', '2020-02-04 09:57:24', 1),(3, 'Awesome Serverless', 'Build web, mobile and IoT applications using Tencent Cloud and API Gateway, Tencent Cloud Functions, and more.', '2020-02-04 10:00:23', '2020-02-04 10:00:23', 1);&quot;\n        );\n        console.log(&quot;Successfully init database data.&quot;);\n        console.log(&quot;Successfully sync database models.&quot;);\n      } catch (e) {\n        console.log(e);\n        throw new Error(&quot;Database migration failed.&quot;);\n      }\n    }\n  }\n}\n\nmodule.exports = AppBootHook;`, `17187363472350458000`)\"\n            >\n              <div\n                class=\"gatsby-code-button\"\n                data-tooltip=\"\"\n              >\n                复制代码<svg class=\"gatsby-code-button-icon\" xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><path fill=\"none\" d=\"M0 0h24v24H0V0z\"/><path d=\"M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z\"/></svg>\n              </div>\n            </div>\n<div class=\"gatsby-highlight\" data-language=\"js\"><pre class=\"language-js\"><code class=\"language-js\"><span class=\"token string\">\"use strict\"</span><span class=\"token punctuation\">;</span>\n\n<span class=\"token keyword\">class</span> <span class=\"token class-name\">AppBootHook</span> <span class=\"token punctuation\">{</span>\n  <span class=\"token function\">constructor</span><span class=\"token punctuation\">(</span><span class=\"token parameter\">app</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n    <span class=\"token keyword\">this</span><span class=\"token punctuation\">.</span>app <span class=\"token operator\">=</span> app<span class=\"token punctuation\">;</span>\n  <span class=\"token punctuation\">}</span>\n\n  <span class=\"token keyword\">async</span> <span class=\"token function\">willReady</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n    <span class=\"token comment\">// 这里只能在开发模式下同步数据库表格</span>\n    <span class=\"token keyword\">const</span> isDev <span class=\"token operator\">=</span> process<span class=\"token punctuation\">.</span>env<span class=\"token punctuation\">.</span><span class=\"token constant\">NODE_ENV</span> <span class=\"token operator\">===</span> <span class=\"token string\">\"development\"</span><span class=\"token punctuation\">;</span>\n    <span class=\"token keyword\">if</span> <span class=\"token punctuation\">(</span>isDev<span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n      <span class=\"token keyword\">try</span> <span class=\"token punctuation\">{</span>\n        console<span class=\"token punctuation\">.</span><span class=\"token function\">log</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"Start syncing database models...\"</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n        <span class=\"token keyword\">await</span> <span class=\"token keyword\">this</span><span class=\"token punctuation\">.</span>app<span class=\"token punctuation\">.</span>model<span class=\"token punctuation\">.</span><span class=\"token function\">sync</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">{</span> logging<span class=\"token punctuation\">:</span> console<span class=\"token punctuation\">.</span>log<span class=\"token punctuation\">,</span> force<span class=\"token punctuation\">:</span> isDev <span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n        console<span class=\"token punctuation\">.</span><span class=\"token function\">log</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"Start init database data...\"</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n        <span class=\"token keyword\">await</span> <span class=\"token keyword\">this</span><span class=\"token punctuation\">.</span>app<span class=\"token punctuation\">.</span>model<span class=\"token punctuation\">.</span><span class=\"token function\">query</span><span class=\"token punctuation\">(</span>\n          <span class=\"token string\">\"INSERT INTO roles (id, name, created_at, updated_at) VALUES (1, 'admin', '2020-02-04 09:54:25', '2020-02-04 09:54:25'),(2, 'editor', '2020-02-04 09:54:30', '2020-02-04 09:54:30');\"</span>\n        <span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n        <span class=\"token keyword\">await</span> <span class=\"token keyword\">this</span><span class=\"token punctuation\">.</span>app<span class=\"token punctuation\">.</span>model<span class=\"token punctuation\">.</span><span class=\"token function\">query</span><span class=\"token punctuation\">(</span>\n          <span class=\"token string\">\"INSERT INTO users (id, name, password, age, avatar, introduction, created_at, updated_at, role_id) VALUES (1, 'admin', 'e10adc3949ba59abbe56e057f20f883e', 20, 'https://yugasun.com/static/avatar.jpg', 'Fullstack Engineer', '2020-02-04 09:55:23', '2020-02-04 09:55:23', 1);\"</span>\n        <span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n        <span class=\"token keyword\">await</span> <span class=\"token keyword\">this</span><span class=\"token punctuation\">.</span>app<span class=\"token punctuation\">.</span>model<span class=\"token punctuation\">.</span><span class=\"token function\">query</span><span class=\"token punctuation\">(</span>\n          <span class=\"token string\">\"INSERT INTO posts (id, title, content, created_at, updated_at, user_id) VALUES (2, 'Awesome Egg.js', 'Egg.js is a awesome framework', '2020-02-04 09:57:24', '2020-02-04 09:57:24', 1),(3, 'Awesome Serverless', 'Build web, mobile and IoT applications using Tencent Cloud and API Gateway, Tencent Cloud Functions, and more.', '2020-02-04 10:00:23', '2020-02-04 10:00:23', 1);\"</span>\n        <span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n        console<span class=\"token punctuation\">.</span><span class=\"token function\">log</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"Successfully init database data.\"</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n        console<span class=\"token punctuation\">.</span><span class=\"token function\">log</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"Successfully sync database models.\"</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n      <span class=\"token punctuation\">}</span> <span class=\"token keyword\">catch</span> <span class=\"token punctuation\">(</span>e<span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n        console<span class=\"token punctuation\">.</span><span class=\"token function\">log</span><span class=\"token punctuation\">(</span>e<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n        <span class=\"token keyword\">throw</span> <span class=\"token keyword\">new</span> <span class=\"token class-name\">Error</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"Database migration failed.\"</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n      <span class=\"token punctuation\">}</span>\n    <span class=\"token punctuation\">}</span>\n  <span class=\"token punctuation\">}</span>\n<span class=\"token punctuation\">}</span>\n\nmodule<span class=\"token punctuation\">.</span>exports <span class=\"token operator\">=</span> AppBootHook<span class=\"token punctuation\">;</span></code></pre></div>\n<p>通过 <code class=\"language-text\">willReady</code> 生命周期函数，我们可以执行 <code class=\"language-text\">this.app.model.sync()</code> 函数来同步数据表，当然这里同时初始化了角色和用户数据记录，用来做为演示用。</p>\n<blockquote>\n<p>注意：这的数据库同步只是本地调试用，如果想要腾讯云的 Mysql 数据库，建议开启远程连接，通过 <code class=\"language-text\">sequelize db:migrate</code> 实现，而不是每次启动 Egg 应用时同步，示例代码已经完成此功能，<a href=\"https://eggjs.org/zh-cn/tutorials/sequelize.html\">参考 Egg Sequelize 文档</a>。\n这里本人为了省事，直接开启腾讯云 Mysql 公网连接，然后修改 <code class=\"language-text\">config.default.js</code> 中的 <code class=\"language-text\">sequelize</code> 配置，运行 <code class=\"language-text\">npm run dev</code> 进行开发模式同步。</p>\n</blockquote>\n<p>到这里，我们的用户和角色的 API 都已经定义好了，启动服务 <code class=\"language-text\">npm run dev</code>，访问 <code class=\"language-text\">https://127.0.0.1:7001/users</code> 可以获取所有用户列表了。</p>\n<h3 id=\"7-用户登录注销-api\"><a href=\"#7-%E7%94%A8%E6%88%B7%E7%99%BB%E5%BD%95%E6%B3%A8%E9%94%80-api\" aria-label=\"7 用户登录注销 api permalink\" class=\"anchor\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>7. 用户登录/注销 API</h3>\n<p>这里登录逻辑比较简单，客户端发送 <code class=\"language-text\">用户名</code> 和 <code class=\"language-text\">密码</code> 到 <code class=\"language-text\">/login</code> 路由，后端通过 <code class=\"language-text\">login</code> 函数接受，然后从数据库中查询该用户名，同时比对密码是否正确。如果正确则调用 <code class=\"language-text\">app.jwt.sign()</code> 函数生成 <code class=\"language-text\">token</code>，并将 <code class=\"language-text\">token</code> 存入到 <code class=\"language-text\">redis</code> 中，同时返回该 <code class=\"language-text\">token</code>，之后客户端需要鉴权的请求都会携带 <code class=\"language-text\">token</code>，进行鉴权验证。思路很简单，我们就开始实现了。</p>\n<p>流程图如下：</p>\n<center>\n<img src=\"https://static.yugasun.com/serverless/login-process.jpg\" width=\"300\" alt=\"Login Process\">\n</center>\n<p>首先，在 <code class=\"language-text\">backend/app/controller/home.js</code> 中新增登录处理 <code class=\"language-text\">login</code> 方法：</p>\n<div\n              class=\"gatsby-code-button-container\"\n              data-toaster-id=\"52816257075725840000\"\n              data-toaster-class=\"gatsby-code-button-toaster\"\n              data-toaster-text-class=\"gatsby-code-button-toaster-text\"\n              data-toaster-text=\"代码复制成功\"\n              data-toaster-duration=\"3500\"\n              onClick=\"copyToClipboard(`class HomeController extends Controller {\n  // ...\n  async login() {\n    const { ctx, app, config } = this;\n    const { service, helper } = ctx;\n    const { username, password } = ctx.request.body;\n    const user = await service.user.findByName(username);\n    if (!user) {\n      ctx.status = 403;\n      ctx.body = {\n        code: 403,\n        message: &quot;Username or password wrong&quot;\n      };\n    } else {\n      if (user.password === helper.encryptPwd(password)) {\n        ctx.status = 200;\n        const token = app.jwt.sign(\n          {\n            id: user.id,\n            name: user.name,\n            role: user.role.name,\n            avatar: user.avatar\n          },\n          config.jwt.secret,\n          {\n            expiresIn: &quot;1h&quot;\n          }\n        );\n        try {\n          await app.redis.set(\\`token_\\${user.id}\\`, token);\n          ctx.body = {\n            code: 0,\n            message: &quot;Get token success&quot;,\n            token\n          };\n        } catch (e) {\n          console.error(e);\n          ctx.body = {\n            code: 500,\n            message: &quot;Server busy, please try again&quot;\n          };\n        }\n      } else {\n        ctx.status = 403;\n        ctx.body = {\n          code: 403,\n          message: &quot;Username or password wrong&quot;\n        };\n      }\n    }\n  }\n}`, `52816257075725840000`)\"\n            >\n              <div\n                class=\"gatsby-code-button\"\n                data-tooltip=\"\"\n              >\n                复制代码<svg class=\"gatsby-code-button-icon\" xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><path fill=\"none\" d=\"M0 0h24v24H0V0z\"/><path d=\"M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z\"/></svg>\n              </div>\n            </div>\n<div class=\"gatsby-highlight\" data-language=\"js\"><pre class=\"language-js\"><code class=\"language-js\"><span class=\"token keyword\">class</span> <span class=\"token class-name\">HomeController</span> <span class=\"token keyword\">extends</span> <span class=\"token class-name\">Controller</span> <span class=\"token punctuation\">{</span>\n  <span class=\"token comment\">// ...</span>\n  <span class=\"token keyword\">async</span> <span class=\"token function\">login</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n    <span class=\"token keyword\">const</span> <span class=\"token punctuation\">{</span> ctx<span class=\"token punctuation\">,</span> app<span class=\"token punctuation\">,</span> config <span class=\"token punctuation\">}</span> <span class=\"token operator\">=</span> <span class=\"token keyword\">this</span><span class=\"token punctuation\">;</span>\n    <span class=\"token keyword\">const</span> <span class=\"token punctuation\">{</span> service<span class=\"token punctuation\">,</span> helper <span class=\"token punctuation\">}</span> <span class=\"token operator\">=</span> ctx<span class=\"token punctuation\">;</span>\n    <span class=\"token keyword\">const</span> <span class=\"token punctuation\">{</span> username<span class=\"token punctuation\">,</span> password <span class=\"token punctuation\">}</span> <span class=\"token operator\">=</span> ctx<span class=\"token punctuation\">.</span>request<span class=\"token punctuation\">.</span>body<span class=\"token punctuation\">;</span>\n    <span class=\"token keyword\">const</span> user <span class=\"token operator\">=</span> <span class=\"token keyword\">await</span> service<span class=\"token punctuation\">.</span>user<span class=\"token punctuation\">.</span><span class=\"token function\">findByName</span><span class=\"token punctuation\">(</span>username<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n    <span class=\"token keyword\">if</span> <span class=\"token punctuation\">(</span><span class=\"token operator\">!</span>user<span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n      ctx<span class=\"token punctuation\">.</span>status <span class=\"token operator\">=</span> <span class=\"token number\">403</span><span class=\"token punctuation\">;</span>\n      ctx<span class=\"token punctuation\">.</span>body <span class=\"token operator\">=</span> <span class=\"token punctuation\">{</span>\n        code<span class=\"token punctuation\">:</span> <span class=\"token number\">403</span><span class=\"token punctuation\">,</span>\n        message<span class=\"token punctuation\">:</span> <span class=\"token string\">\"Username or password wrong\"</span>\n      <span class=\"token punctuation\">}</span><span class=\"token punctuation\">;</span>\n    <span class=\"token punctuation\">}</span> <span class=\"token keyword\">else</span> <span class=\"token punctuation\">{</span>\n      <span class=\"token keyword\">if</span> <span class=\"token punctuation\">(</span>user<span class=\"token punctuation\">.</span>password <span class=\"token operator\">===</span> helper<span class=\"token punctuation\">.</span><span class=\"token function\">encryptPwd</span><span class=\"token punctuation\">(</span>password<span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n        ctx<span class=\"token punctuation\">.</span>status <span class=\"token operator\">=</span> <span class=\"token number\">200</span><span class=\"token punctuation\">;</span>\n        <span class=\"token keyword\">const</span> token <span class=\"token operator\">=</span> app<span class=\"token punctuation\">.</span>jwt<span class=\"token punctuation\">.</span><span class=\"token function\">sign</span><span class=\"token punctuation\">(</span>\n          <span class=\"token punctuation\">{</span>\n            id<span class=\"token punctuation\">:</span> user<span class=\"token punctuation\">.</span>id<span class=\"token punctuation\">,</span>\n            name<span class=\"token punctuation\">:</span> user<span class=\"token punctuation\">.</span>name<span class=\"token punctuation\">,</span>\n            role<span class=\"token punctuation\">:</span> user<span class=\"token punctuation\">.</span>role<span class=\"token punctuation\">.</span>name<span class=\"token punctuation\">,</span>\n            avatar<span class=\"token punctuation\">:</span> user<span class=\"token punctuation\">.</span>avatar\n          <span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span>\n          config<span class=\"token punctuation\">.</span>jwt<span class=\"token punctuation\">.</span>secret<span class=\"token punctuation\">,</span>\n          <span class=\"token punctuation\">{</span>\n            expiresIn<span class=\"token punctuation\">:</span> <span class=\"token string\">\"1h\"</span>\n          <span class=\"token punctuation\">}</span>\n        <span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n        <span class=\"token keyword\">try</span> <span class=\"token punctuation\">{</span>\n          <span class=\"token keyword\">await</span> app<span class=\"token punctuation\">.</span>redis<span class=\"token punctuation\">.</span><span class=\"token function\">set</span><span class=\"token punctuation\">(</span><span class=\"token template-string\"><span class=\"token template-punctuation string\">`</span><span class=\"token string\">token_</span><span class=\"token interpolation\"><span class=\"token interpolation-punctuation punctuation\">${</span>user<span class=\"token punctuation\">.</span>id<span class=\"token interpolation-punctuation punctuation\">}</span></span><span class=\"token template-punctuation string\">`</span></span><span class=\"token punctuation\">,</span> token<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n          ctx<span class=\"token punctuation\">.</span>body <span class=\"token operator\">=</span> <span class=\"token punctuation\">{</span>\n            code<span class=\"token punctuation\">:</span> <span class=\"token number\">0</span><span class=\"token punctuation\">,</span>\n            message<span class=\"token punctuation\">:</span> <span class=\"token string\">\"Get token success\"</span><span class=\"token punctuation\">,</span>\n            token\n          <span class=\"token punctuation\">}</span><span class=\"token punctuation\">;</span>\n        <span class=\"token punctuation\">}</span> <span class=\"token keyword\">catch</span> <span class=\"token punctuation\">(</span>e<span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n          console<span class=\"token punctuation\">.</span><span class=\"token function\">error</span><span class=\"token punctuation\">(</span>e<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n          ctx<span class=\"token punctuation\">.</span>body <span class=\"token operator\">=</span> <span class=\"token punctuation\">{</span>\n            code<span class=\"token punctuation\">:</span> <span class=\"token number\">500</span><span class=\"token punctuation\">,</span>\n            message<span class=\"token punctuation\">:</span> <span class=\"token string\">\"Server busy, please try again\"</span>\n          <span class=\"token punctuation\">}</span><span class=\"token punctuation\">;</span>\n        <span class=\"token punctuation\">}</span>\n      <span class=\"token punctuation\">}</span> <span class=\"token keyword\">else</span> <span class=\"token punctuation\">{</span>\n        ctx<span class=\"token punctuation\">.</span>status <span class=\"token operator\">=</span> <span class=\"token number\">403</span><span class=\"token punctuation\">;</span>\n        ctx<span class=\"token punctuation\">.</span>body <span class=\"token operator\">=</span> <span class=\"token punctuation\">{</span>\n          code<span class=\"token punctuation\">:</span> <span class=\"token number\">403</span><span class=\"token punctuation\">,</span>\n          message<span class=\"token punctuation\">:</span> <span class=\"token string\">\"Username or password wrong\"</span>\n        <span class=\"token punctuation\">}</span><span class=\"token punctuation\">;</span>\n      <span class=\"token punctuation\">}</span>\n    <span class=\"token punctuation\">}</span>\n  <span class=\"token punctuation\">}</span>\n<span class=\"token punctuation\">}</span></code></pre></div>\n<blockquote>\n<p>注释：这里有个密码存储逻辑，用户在注册时，密码都是通过 <code class=\"language-text\">helper</code> 函数 <code class=\"language-text\">encryptPwd()</code> 进行加密的（这里用到最简单的 md5 加密方式，实际开发中建议使用更加高级加密方式），所以在校验密码正确性时，也需要先加密一次。至于如何在 Egg.js 框架中新增 <code class=\"language-text\">helper</code> 函数，只需要在 <code class=\"language-text\">backend/app/extend</code> 文件夹中新增 <code class=\"language-text\">helper.js</code> 文件，然后 <code class=\"language-text\">modole.exports</code> 一个包含该函数的对象就行，参考 <a href=\"https://eggjs.org/zh-cn/basics/extend.html#helper\">Egg 框架扩展文档</a></p>\n</blockquote>\n<p>然后，在 <code class=\"language-text\">backend/app/controller/home.js</code> 中新增 <code class=\"language-text\">userInfo</code> 方法，获取用户信息：</p>\n<div\n              class=\"gatsby-code-button-container\"\n              data-toaster-id=\"86849801555094810000\"\n              data-toaster-class=\"gatsby-code-button-toaster\"\n              data-toaster-text-class=\"gatsby-code-button-toaster-text\"\n              data-toaster-text=\"代码复制成功\"\n              data-toaster-duration=\"3500\"\n              onClick=\"copyToClipboard(`async userInfo() {\n  const { ctx } = this;\n  const { user } = ctx.state;\n  ctx.status = 200;\n  ctx.body = {\n    code: 0,\n    data: user,\n  };\n}`, `86849801555094810000`)\"\n            >\n              <div\n                class=\"gatsby-code-button\"\n                data-tooltip=\"\"\n              >\n                复制代码<svg class=\"gatsby-code-button-icon\" xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><path fill=\"none\" d=\"M0 0h24v24H0V0z\"/><path d=\"M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z\"/></svg>\n              </div>\n            </div>\n<div class=\"gatsby-highlight\" data-language=\"js\"><pre class=\"language-js\"><code class=\"language-js\"><span class=\"token keyword\">async</span> <span class=\"token function\">userInfo</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n  <span class=\"token keyword\">const</span> <span class=\"token punctuation\">{</span> ctx <span class=\"token punctuation\">}</span> <span class=\"token operator\">=</span> <span class=\"token keyword\">this</span><span class=\"token punctuation\">;</span>\n  <span class=\"token keyword\">const</span> <span class=\"token punctuation\">{</span> user <span class=\"token punctuation\">}</span> <span class=\"token operator\">=</span> ctx<span class=\"token punctuation\">.</span>state<span class=\"token punctuation\">;</span>\n  ctx<span class=\"token punctuation\">.</span>status <span class=\"token operator\">=</span> <span class=\"token number\">200</span><span class=\"token punctuation\">;</span>\n  ctx<span class=\"token punctuation\">.</span>body <span class=\"token operator\">=</span> <span class=\"token punctuation\">{</span>\n    code<span class=\"token punctuation\">:</span> <span class=\"token number\">0</span><span class=\"token punctuation\">,</span>\n    data<span class=\"token punctuation\">:</span> user<span class=\"token punctuation\">,</span>\n  <span class=\"token punctuation\">}</span><span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span></code></pre></div>\n<p><a href=\"https://github.com/eggjs/egg-jwt\">egg-jwt</a> 插件，在鉴权通过的路由对应 controller 函数中，会将 <code class=\"language-text\">app.jwt.sign(user, secrete)</code> 加密的用户信息，添加到 <code class=\"language-text\">ctx.state.user</code> 中，所以 <code class=\"language-text\">userInfo</code> 函数只需要将它返回就行。</p>\n<p>之后，在 <code class=\"language-text\">backend/app/controller/home.js</code> 中新增 <code class=\"language-text\">logout</code> 方法：</p>\n<div\n              class=\"gatsby-code-button-container\"\n              data-toaster-id=\"60709186971319930000\"\n              data-toaster-class=\"gatsby-code-button-toaster\"\n              data-toaster-text-class=\"gatsby-code-button-toaster-text\"\n              data-toaster-text=\"代码复制成功\"\n              data-toaster-duration=\"3500\"\n              onClick=\"copyToClipboard(`async logout() {\n  const { ctx } = this;\n  ctx.status = 200;\n  ctx.body = {\n    code: 0,\n    message: 'Logout success',\n  };\n}`, `60709186971319930000`)\"\n            >\n              <div\n                class=\"gatsby-code-button\"\n                data-tooltip=\"\"\n              >\n                复制代码<svg class=\"gatsby-code-button-icon\" xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><path fill=\"none\" d=\"M0 0h24v24H0V0z\"/><path d=\"M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z\"/></svg>\n              </div>\n            </div>\n<div class=\"gatsby-highlight\" data-language=\"js\"><pre class=\"language-js\"><code class=\"language-js\"><span class=\"token keyword\">async</span> <span class=\"token function\">logout</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n  <span class=\"token keyword\">const</span> <span class=\"token punctuation\">{</span> ctx <span class=\"token punctuation\">}</span> <span class=\"token operator\">=</span> <span class=\"token keyword\">this</span><span class=\"token punctuation\">;</span>\n  ctx<span class=\"token punctuation\">.</span>status <span class=\"token operator\">=</span> <span class=\"token number\">200</span><span class=\"token punctuation\">;</span>\n  ctx<span class=\"token punctuation\">.</span>body <span class=\"token operator\">=</span> <span class=\"token punctuation\">{</span>\n    code<span class=\"token punctuation\">:</span> <span class=\"token number\">0</span><span class=\"token punctuation\">,</span>\n    message<span class=\"token punctuation\">:</span> <span class=\"token string\">'Logout success'</span><span class=\"token punctuation\">,</span>\n  <span class=\"token punctuation\">}</span><span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span></code></pre></div>\n<p><code class=\"language-text\">userInfo</code> 和 <code class=\"language-text\">logout</code> 函数非常简单，重点是路由中间件如何处理。</p>\n<p>接下来，我们来定义登录相关路由，修改 <code class=\"language-text\">backend/app/router.js</code> 文件，新增 <code class=\"language-text\">/login</code>, <code class=\"language-text\">/user-info</code>, <code class=\"language-text\">/logout</code> 三个路由：</p>\n<div\n              class=\"gatsby-code-button-container\"\n              data-toaster-id=\"64192209731279946000\"\n              data-toaster-class=\"gatsby-code-button-toaster\"\n              data-toaster-text-class=\"gatsby-code-button-toaster-text\"\n              data-toaster-text=\"代码复制成功\"\n              data-toaster-duration=\"3500\"\n              onClick=\"copyToClipboard(`const koajwt = require(&quot;koa-jwt2&quot;);\n\nmodule.exports = app => {\n  const { router, controller, jwt } = app;\n  router.get(&quot;/&quot;, controller.home.index);\n\n  router.post(&quot;/login&quot;, controller.home.login);\n  router.get(&quot;/user-info&quot;, jwt, controller.home.userInfo);\n  const isRevokedAsync = function(req, payload) {\n    return new Promise(resolve => {\n      try {\n        const userId = payload.id;\n        const tokenKey = \\`token_\\${userId}\\`;\n        const token = app.redis.get(tokenKey);\n        if (token) {\n          app.redis.del(tokenKey);\n        }\n        resolve(false);\n      } catch (e) {\n        resolve(true);\n      }\n    });\n  };\n  router.post(\n    &quot;/logout&quot;,\n    koajwt({\n      secret: app.config.jwt.secret,\n      credentialsRequired: false,\n      isRevoked: isRevokedAsync\n    }),\n    controller.home.logout\n  );\n\n  router.resources(&quot;roles&quot;, &quot;/roles&quot;, controller.role);\n  router.resources(&quot;users&quot;, &quot;/users&quot;, controller.user);\n  router.resources(&quot;posts&quot;, &quot;/posts&quot;, controller.post);\n};`, `64192209731279946000`)\"\n            >\n              <div\n                class=\"gatsby-code-button\"\n                data-tooltip=\"\"\n              >\n                复制代码<svg class=\"gatsby-code-button-icon\" xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><path fill=\"none\" d=\"M0 0h24v24H0V0z\"/><path d=\"M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z\"/></svg>\n              </div>\n            </div>\n<div class=\"gatsby-highlight\" data-language=\"js\"><pre class=\"language-js\"><code class=\"language-js\"><span class=\"token keyword\">const</span> koajwt <span class=\"token operator\">=</span> <span class=\"token function\">require</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"koa-jwt2\"</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\nmodule<span class=\"token punctuation\">.</span><span class=\"token function-variable function\">exports</span> <span class=\"token operator\">=</span> <span class=\"token parameter\">app</span> <span class=\"token operator\">=></span> <span class=\"token punctuation\">{</span>\n  <span class=\"token keyword\">const</span> <span class=\"token punctuation\">{</span> router<span class=\"token punctuation\">,</span> controller<span class=\"token punctuation\">,</span> jwt <span class=\"token punctuation\">}</span> <span class=\"token operator\">=</span> app<span class=\"token punctuation\">;</span>\n  router<span class=\"token punctuation\">.</span><span class=\"token function\">get</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"/\"</span><span class=\"token punctuation\">,</span> controller<span class=\"token punctuation\">.</span>home<span class=\"token punctuation\">.</span>index<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\n  router<span class=\"token punctuation\">.</span><span class=\"token function\">post</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"/login\"</span><span class=\"token punctuation\">,</span> controller<span class=\"token punctuation\">.</span>home<span class=\"token punctuation\">.</span>login<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n  router<span class=\"token punctuation\">.</span><span class=\"token function\">get</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"/user-info\"</span><span class=\"token punctuation\">,</span> jwt<span class=\"token punctuation\">,</span> controller<span class=\"token punctuation\">.</span>home<span class=\"token punctuation\">.</span>userInfo<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n  <span class=\"token keyword\">const</span> <span class=\"token function-variable function\">isRevokedAsync</span> <span class=\"token operator\">=</span> <span class=\"token keyword\">function</span><span class=\"token punctuation\">(</span><span class=\"token parameter\">req<span class=\"token punctuation\">,</span> payload</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n    <span class=\"token keyword\">return</span> <span class=\"token keyword\">new</span> <span class=\"token class-name\">Promise</span><span class=\"token punctuation\">(</span><span class=\"token parameter\">resolve</span> <span class=\"token operator\">=></span> <span class=\"token punctuation\">{</span>\n      <span class=\"token keyword\">try</span> <span class=\"token punctuation\">{</span>\n        <span class=\"token keyword\">const</span> userId <span class=\"token operator\">=</span> payload<span class=\"token punctuation\">.</span>id<span class=\"token punctuation\">;</span>\n        <span class=\"token keyword\">const</span> tokenKey <span class=\"token operator\">=</span> <span class=\"token template-string\"><span class=\"token template-punctuation string\">`</span><span class=\"token string\">token_</span><span class=\"token interpolation\"><span class=\"token interpolation-punctuation punctuation\">${</span>userId<span class=\"token interpolation-punctuation punctuation\">}</span></span><span class=\"token template-punctuation string\">`</span></span><span class=\"token punctuation\">;</span>\n        <span class=\"token keyword\">const</span> token <span class=\"token operator\">=</span> app<span class=\"token punctuation\">.</span>redis<span class=\"token punctuation\">.</span><span class=\"token function\">get</span><span class=\"token punctuation\">(</span>tokenKey<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n        <span class=\"token keyword\">if</span> <span class=\"token punctuation\">(</span>token<span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n          app<span class=\"token punctuation\">.</span>redis<span class=\"token punctuation\">.</span><span class=\"token function\">del</span><span class=\"token punctuation\">(</span>tokenKey<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n        <span class=\"token punctuation\">}</span>\n        <span class=\"token function\">resolve</span><span class=\"token punctuation\">(</span><span class=\"token boolean\">false</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n      <span class=\"token punctuation\">}</span> <span class=\"token keyword\">catch</span> <span class=\"token punctuation\">(</span>e<span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n        <span class=\"token function\">resolve</span><span class=\"token punctuation\">(</span><span class=\"token boolean\">true</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n      <span class=\"token punctuation\">}</span>\n    <span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n  <span class=\"token punctuation\">}</span><span class=\"token punctuation\">;</span>\n  router<span class=\"token punctuation\">.</span><span class=\"token function\">post</span><span class=\"token punctuation\">(</span>\n    <span class=\"token string\">\"/logout\"</span><span class=\"token punctuation\">,</span>\n    <span class=\"token function\">koajwt</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">{</span>\n      secret<span class=\"token punctuation\">:</span> app<span class=\"token punctuation\">.</span>config<span class=\"token punctuation\">.</span>jwt<span class=\"token punctuation\">.</span>secret<span class=\"token punctuation\">,</span>\n      credentialsRequired<span class=\"token punctuation\">:</span> <span class=\"token boolean\">false</span><span class=\"token punctuation\">,</span>\n      isRevoked<span class=\"token punctuation\">:</span> isRevokedAsync\n    <span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">,</span>\n    controller<span class=\"token punctuation\">.</span>home<span class=\"token punctuation\">.</span>logout\n  <span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\n  router<span class=\"token punctuation\">.</span><span class=\"token function\">resources</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"roles\"</span><span class=\"token punctuation\">,</span> <span class=\"token string\">\"/roles\"</span><span class=\"token punctuation\">,</span> controller<span class=\"token punctuation\">.</span>role<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n  router<span class=\"token punctuation\">.</span><span class=\"token function\">resources</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"users\"</span><span class=\"token punctuation\">,</span> <span class=\"token string\">\"/users\"</span><span class=\"token punctuation\">,</span> controller<span class=\"token punctuation\">.</span>user<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n  router<span class=\"token punctuation\">.</span><span class=\"token function\">resources</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"posts\"</span><span class=\"token punctuation\">,</span> <span class=\"token string\">\"/posts\"</span><span class=\"token punctuation\">,</span> controller<span class=\"token punctuation\">.</span>post<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span><span class=\"token punctuation\">;</span></code></pre></div>\n<p>Egg.js 框架定义路由时，<code class=\"language-text\">router.post()</code> 函数可以接受中间件函数，用来处理一些路由相关的特殊逻辑。</p>\n<p>比如 <code class=\"language-text\">/user-info</code>，路由添加了 <code class=\"language-text\">app.jwt</code> 作为 JWT 鉴权中间件函数，至于为什么这么用，<a href=\"https://github.com/eggjs/egg-jwt\">egg-jwt</a> 插件有明确说明。</p>\n<p>这里稍微复杂的是 <code class=\"language-text\">/logout</code> 路由，因为我们在注销登录时，需要将用户的 <code class=\"language-text\">token</code> 从 <code class=\"language-text\">redis</code> 中移除，所以这里借助了 <a href=\"https://github.com/okoala/koa-jwt2\">koa-jwt2</a> 的 <code class=\"language-text\">isRevokded</code> 参数，来进行 <code class=\"language-text\">token</code> 删除。</p>\n<h2 id=\"后端服务部署\"><a href=\"#%E5%90%8E%E7%AB%AF%E6%9C%8D%E5%8A%A1%E9%83%A8%E7%BD%B2\" aria-label=\"后端服务部署 permalink\" class=\"anchor\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>后端服务部署</h2>\n<p>到这里，后端服务的登录和注销逻辑基本完成了。那么如何部署到云函数呢？可以直接使用 <a href=\"https://github.com/serverless-components/tencent-egg\">tencent-egg</a> 组件，它是专门为 Egg.js 框架打造的 Serverless Component，使用它可以快速将我们的 Egg.js 项目部署到腾讯云云函数上。</p>\n<h3 id=\"1-准备\"><a href=\"#1-%E5%87%86%E5%A4%87\" aria-label=\"1 准备 permalink\" class=\"anchor\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>1. 准备</h3>\n<p>我们先创建一个 <code class=\"language-text\">backend/sls.js</code> 入口文件：</p>\n<div\n              class=\"gatsby-code-button-container\"\n              data-toaster-id=\"71551639440389110000\"\n              data-toaster-class=\"gatsby-code-button-toaster\"\n              data-toaster-text-class=\"gatsby-code-button-toaster-text\"\n              data-toaster-text=\"代码复制成功\"\n              data-toaster-duration=\"3500\"\n              onClick=\"copyToClipboard(`const { Application } = require(&quot;egg&quot;);\nconst app = new Application();\nmodule.exports = app;`, `71551639440389110000`)\"\n            >\n              <div\n                class=\"gatsby-code-button\"\n                data-tooltip=\"\"\n              >\n                复制代码<svg class=\"gatsby-code-button-icon\" xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><path fill=\"none\" d=\"M0 0h24v24H0V0z\"/><path d=\"M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z\"/></svg>\n              </div>\n            </div>\n<div class=\"gatsby-highlight\" data-language=\"js\"><pre class=\"language-js\"><code class=\"language-js\"><span class=\"token keyword\">const</span> <span class=\"token punctuation\">{</span> Application <span class=\"token punctuation\">}</span> <span class=\"token operator\">=</span> <span class=\"token function\">require</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"egg\"</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token keyword\">const</span> app <span class=\"token operator\">=</span> <span class=\"token keyword\">new</span> <span class=\"token class-name\">Application</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\nmodule<span class=\"token punctuation\">.</span>exports <span class=\"token operator\">=</span> app<span class=\"token punctuation\">;</span></code></pre></div>\n<p>然后修改 <code class=\"language-text\">backend/config/config.default.js</code> 文件:</p>\n<div\n              class=\"gatsby-code-button-container\"\n              data-toaster-id=\"79608818232415390000\"\n              data-toaster-class=\"gatsby-code-button-toaster\"\n              data-toaster-text-class=\"gatsby-code-button-toaster-text\"\n              data-toaster-text=\"代码复制成功\"\n              data-toaster-duration=\"3500\"\n              onClick=\"copyToClipboard(`const config = (exports = {\n  env: &quot;prod&quot;, // 推荐云函数的 egg 运行环境变量修改为 prod\n  rundir: &quot;/tmp&quot;,\n  logger: {\n    dir: &quot;/tmp&quot;\n  }\n});`, `79608818232415390000`)\"\n            >\n              <div\n                class=\"gatsby-code-button\"\n                data-tooltip=\"\"\n              >\n                复制代码<svg class=\"gatsby-code-button-icon\" xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><path fill=\"none\" d=\"M0 0h24v24H0V0z\"/><path d=\"M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z\"/></svg>\n              </div>\n            </div>\n<div class=\"gatsby-highlight\" data-language=\"js\"><pre class=\"language-js\"><code class=\"language-js\"><span class=\"token keyword\">const</span> config <span class=\"token operator\">=</span> <span class=\"token punctuation\">(</span>exports <span class=\"token operator\">=</span> <span class=\"token punctuation\">{</span>\n  env<span class=\"token punctuation\">:</span> <span class=\"token string\">\"prod\"</span><span class=\"token punctuation\">,</span> <span class=\"token comment\">// 推荐云函数的 egg 运行环境变量修改为 prod</span>\n  rundir<span class=\"token punctuation\">:</span> <span class=\"token string\">\"/tmp\"</span><span class=\"token punctuation\">,</span>\n  logger<span class=\"token punctuation\">:</span> <span class=\"token punctuation\">{</span>\n    dir<span class=\"token punctuation\">:</span> <span class=\"token string\">\"/tmp\"</span>\n  <span class=\"token punctuation\">}</span>\n<span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span></code></pre></div>\n<blockquote>\n<p>注释：这里之所有需要修改运行和日志目录，是因为云函数运行时，只有 <code class=\"language-text\">/tmp</code> 才有写权限。</p>\n</blockquote>\n<p>全局安装 <code class=\"language-text\">serverless</code> 命令：</p>\n<div\n              class=\"gatsby-code-button-container\"\n              data-toaster-id=\"98383082596053800000\"\n              data-toaster-class=\"gatsby-code-button-toaster\"\n              data-toaster-text-class=\"gatsby-code-button-toaster-text\"\n              data-toaster-text=\"代码复制成功\"\n              data-toaster-duration=\"3500\"\n              onClick=\"copyToClipboard(`\\$ npm install serverless -g`, `98383082596053800000`)\"\n            >\n              <div\n                class=\"gatsby-code-button\"\n                data-tooltip=\"\"\n              >\n                复制代码<svg class=\"gatsby-code-button-icon\" xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><path fill=\"none\" d=\"M0 0h24v24H0V0z\"/><path d=\"M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z\"/></svg>\n              </div>\n            </div>\n<div class=\"gatsby-highlight\" data-language=\"bash\"><pre class=\"language-bash\"><code class=\"language-bash\">$ <span class=\"token function\">npm</span> <span class=\"token function\">install</span> serverless -g</code></pre></div>\n<h3 id=\"2-配置-serverless\"><a href=\"#2-%E9%85%8D%E7%BD%AE-serverless\" aria-label=\"2 配置 serverless permalink\" class=\"anchor\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>2. 配置 Serverless</h3>\n<p>在项目根目录下创建 <code class=\"language-text\">serverless.yml</code> 文件，同时新增 <code class=\"language-text\">backend</code> 配置：</p>\n<div\n              class=\"gatsby-code-button-container\"\n              data-toaster-id=\"27415836757248856000\"\n              data-toaster-class=\"gatsby-code-button-toaster\"\n              data-toaster-text-class=\"gatsby-code-button-toaster-text\"\n              data-toaster-text=\"代码复制成功\"\n              data-toaster-duration=\"3500\"\n              onClick=\"copyToClipboard(`backend:\n  component: &quot;@serverless/tencent-egg&quot;\n  inputs:\n    code: ./backend\n    functionName: admin-system\n    # 这里必须指定一个具有操作 mysql 和 redis 的角色，具体角色创建，可访问 https://console.cloud.tencent.com/cam/role\n    role: QCS_SCFFull\n    functionConf:\n      timeout: 120\n      # 这里的私有网络必须和 mysql、redis 实例一致\n      vpcConfig:\n        vpcId: vpc-xxx\n        subnetId: subnet-xxx\n    apigatewayConf:\n      protocols:\n        - https`, `27415836757248856000`)\"\n            >\n              <div\n                class=\"gatsby-code-button\"\n                data-tooltip=\"\"\n              >\n                复制代码<svg class=\"gatsby-code-button-icon\" xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><path fill=\"none\" d=\"M0 0h24v24H0V0z\"/><path d=\"M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z\"/></svg>\n              </div>\n            </div>\n<div class=\"gatsby-highlight\" data-language=\"yaml\"><pre class=\"language-yaml\"><code class=\"language-yaml\"><span class=\"token key atrule\">backend</span><span class=\"token punctuation\">:</span>\n  <span class=\"token key atrule\">component</span><span class=\"token punctuation\">:</span> <span class=\"token string\">\"@serverless/tencent-egg\"</span>\n  <span class=\"token key atrule\">inputs</span><span class=\"token punctuation\">:</span>\n    <span class=\"token key atrule\">code</span><span class=\"token punctuation\">:</span> ./backend\n    <span class=\"token key atrule\">functionName</span><span class=\"token punctuation\">:</span> admin<span class=\"token punctuation\">-</span>system\n    <span class=\"token comment\"># 这里必须指定一个具有操作 mysql 和 redis 的角色，具体角色创建，可访问 https://console.cloud.tencent.com/cam/role</span>\n    <span class=\"token key atrule\">role</span><span class=\"token punctuation\">:</span> QCS_SCFFull\n    <span class=\"token key atrule\">functionConf</span><span class=\"token punctuation\">:</span>\n      <span class=\"token key atrule\">timeout</span><span class=\"token punctuation\">:</span> <span class=\"token number\">120</span>\n      <span class=\"token comment\"># 这里的私有网络必须和 mysql、redis 实例一致</span>\n      <span class=\"token key atrule\">vpcConfig</span><span class=\"token punctuation\">:</span>\n        <span class=\"token key atrule\">vpcId</span><span class=\"token punctuation\">:</span> vpc<span class=\"token punctuation\">-</span>xxx\n        <span class=\"token key atrule\">subnetId</span><span class=\"token punctuation\">:</span> subnet<span class=\"token punctuation\">-</span>xxx\n    <span class=\"token key atrule\">apigatewayConf</span><span class=\"token punctuation\">:</span>\n      <span class=\"token key atrule\">protocols</span><span class=\"token punctuation\">:</span>\n        <span class=\"token punctuation\">-</span> https</code></pre></div>\n<p>此时你的项目目录结构如下：</p>\n<div\n              class=\"gatsby-code-button-container\"\n              data-toaster-id=\"4159387759932653600\"\n              data-toaster-class=\"gatsby-code-button-toaster\"\n              data-toaster-text-class=\"gatsby-code-button-toaster-text\"\n              data-toaster-text=\"代码复制成功\"\n              data-toaster-duration=\"3500\"\n              onClick=\"copyToClipboard(`.\n├── README.md         // 项目说明文件\n├── serverless.yml    // serverless yml 配合文件\n├── backend           // 创建的 Egg.js 项目\n└── frontend          // 克隆的 Vue.js 前端项目模板`, `4159387759932653600`)\"\n            >\n              <div\n                class=\"gatsby-code-button\"\n                data-tooltip=\"\"\n              >\n                复制代码<svg class=\"gatsby-code-button-icon\" xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><path fill=\"none\" d=\"M0 0h24v24H0V0z\"/><path d=\"M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z\"/></svg>\n              </div>\n            </div>\n<div class=\"gatsby-highlight\" data-language=\"bash\"><pre class=\"language-bash\"><code class=\"language-bash\"><span class=\"token builtin class-name\">.</span>\n├── README.md         // 项目说明文件\n├── serverless.yml    // serverless yml 配合文件\n├── backend           // 创建的 Egg.js 项目\n└── frontend          // 克隆的 Vue.js 前端项目模板</code></pre></div>\n<h3 id=\"3-执行部署\"><a href=\"#3-%E6%89%A7%E8%A1%8C%E9%83%A8%E7%BD%B2\" aria-label=\"3 执行部署 permalink\" class=\"anchor\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>3. 执行部署</h3>\n<p>执行部署命令：</p>\n<div\n              class=\"gatsby-code-button-container\"\n              data-toaster-id=\"92079136733236870000\"\n              data-toaster-class=\"gatsby-code-button-toaster\"\n              data-toaster-text-class=\"gatsby-code-button-toaster-text\"\n              data-toaster-text=\"代码复制成功\"\n              data-toaster-duration=\"3500\"\n              onClick=\"copyToClipboard(`\\$ serverless --debug`, `92079136733236870000`)\"\n            >\n              <div\n                class=\"gatsby-code-button\"\n                data-tooltip=\"\"\n              >\n                复制代码<svg class=\"gatsby-code-button-icon\" xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><path fill=\"none\" d=\"M0 0h24v24H0V0z\"/><path d=\"M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z\"/></svg>\n              </div>\n            </div>\n<div class=\"gatsby-highlight\" data-language=\"bash\"><pre class=\"language-bash\"><code class=\"language-bash\">$ serverless --debug</code></pre></div>\n<p>之后控制台需要进行扫码登录验证腾讯云账号，扫码登录就好。等部署成功会发挥如下信息：</p>\n<div\n              class=\"gatsby-code-button-container\"\n              data-toaster-id=\"33910785796654006000\"\n              data-toaster-class=\"gatsby-code-button-toaster\"\n              data-toaster-text-class=\"gatsby-code-button-toaster-text\"\n              data-toaster-text=\"代码复制成功\"\n              data-toaster-duration=\"3500\"\n              onClick=\"copyToClipboard(`  backend:\n    region:              ap-guangzhou\n    functionName:        admin-system\n    apiGatewayServiceId: service-f1bhmhk4\n    url:                 https://service-f1bhmhk4-1251556596.gz.apigw.tencentcs.com/release/`, `33910785796654006000`)\"\n            >\n              <div\n                class=\"gatsby-code-button\"\n                data-tooltip=\"\"\n              >\n                复制代码<svg class=\"gatsby-code-button-icon\" xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><path fill=\"none\" d=\"M0 0h24v24H0V0z\"/><path d=\"M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z\"/></svg>\n              </div>\n            </div>\n<div class=\"gatsby-highlight\" data-language=\"bash\"><pre class=\"language-bash\"><code class=\"language-bash\">  backend:\n    region:              ap-guangzhou\n    functionName:        admin-system\n    apiGatewayServiceId: service-f1bhmhk4\n    url:                 https://service-f1bhmhk4-1251556596.gz.apigw.tencentcs.com/release/</code></pre></div>\n<p>这里输出的 url 就是部署成功的 API 网关接口，可以直接访问测试。</p>\n<blockquote>\n<p>注释：云函数部署时，会自动在腾讯云的 API 网关创建一个服务，同时创建一个 API，通过该 API 就可以触发云函数执行了。</p>\n</blockquote>\n<h3 id=\"4-账号配置（可选）\"><a href=\"#4-%E8%B4%A6%E5%8F%B7%E9%85%8D%E7%BD%AE%EF%BC%88%E5%8F%AF%E9%80%89%EF%BC%89\" aria-label=\"4 账号配置（可选） permalink\" class=\"anchor\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>4. 账号配置（可选）</h3>\n<p>当前默认支持 Serverless cli 扫描二维码登录，如果希望配置持久的环境变量/秘钥信息，也可以在项目根目录创建 <code class=\"language-text\">.env</code> 文件</p>\n<p>在 <code class=\"language-text\">.env</code> 文件中配置腾讯云的 SecretId 和 SecretKey 信息并保存，密钥可以在 <a href=\"https://console.cloud.tencent.com/cam/capi\">API 密钥管理</a> 中获取或者创建.</p>\n<div\n              class=\"gatsby-code-button-container\"\n              data-toaster-id=\"56594686413743860000\"\n              data-toaster-class=\"gatsby-code-button-toaster\"\n              data-toaster-text-class=\"gatsby-code-button-toaster-text\"\n              data-toaster-text=\"代码复制成功\"\n              data-toaster-duration=\"3500\"\n              onClick=\"copyToClipboard(`# .env\nTENCENT_SECRET_ID=123\nTENCENT_SECRET_KEY=123`, `56594686413743860000`)\"\n            >\n              <div\n                class=\"gatsby-code-button\"\n                data-tooltip=\"\"\n              >\n                复制代码<svg class=\"gatsby-code-button-icon\" xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><path fill=\"none\" d=\"M0 0h24v24H0V0z\"/><path d=\"M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z\"/></svg>\n              </div>\n            </div>\n<div class=\"gatsby-highlight\" data-language=\"text\"><pre class=\"language-text\"><code class=\"language-text\"># .env\nTENCENT_SECRET_ID=123\nTENCENT_SECRET_KEY=123</code></pre></div>\n<h3 id=\"5-文章-api\"><a href=\"#5-%E6%96%87%E7%AB%A0-api\" aria-label=\"5 文章 api permalink\" class=\"anchor\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>5. 文章 API</h3>\n<p>跟用户 API 类似，只需要复制粘贴上面用户相关模块，修改名称为 <code class=\"language-text\">posts</code>， 并修改数据模型就行，这里就不粘贴代码了。</p>\n<h2 id=\"前端开发\"><a href=\"#%E5%89%8D%E7%AB%AF%E5%BC%80%E5%8F%91\" aria-label=\"前端开发 permalink\" class=\"anchor\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>前端开发</h2>\n<p>本实例直接使用的 <a href=\"https://github.com/PanJiaChen/vue-admin-template\">vue-admin-template</a> 的前端模板。</p>\n<p>我们需要做如下几部分修改：</p>\n<ol>\n<li>删除接口模拟：更换为真实的后端服务接口</li>\n<li>修改接口函数：包括用户相关的 <code class=\"language-text\">frontend/src/api/user.js</code> 和文章相关接口 <code class=\"language-text\">frontend/src/api/post.js</code>。</li>\n<li>修改接口工具函数：主要是修改 <code class=\"language-text\">frontend/src/utils/request.js</code> 文件，包括 <code class=\"language-text\">axios</code>请求的 <code class=\"language-text\">baseURL</code> 和请求的 header。</li>\n<li>UI 界面修改：主要是新增文章管理页面，包括列表页和新增页。</li>\n</ol>\n<h3 id=\"1-删除接口模拟\"><a href=\"#1-%E5%88%A0%E9%99%A4%E6%8E%A5%E5%8F%A3%E6%A8%A1%E6%8B%9F\" aria-label=\"1 删除接口模拟 permalink\" class=\"anchor\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>1. 删除接口模拟</h3>\n<p>首先删除 <code class=\"language-text\">frontend/mock</code> 文件夹。然后修改前端入口文件 <code class=\"language-text\">frontend/src/main.js</code>：</p>\n<div\n              class=\"gatsby-code-button-container\"\n              data-toaster-id=\"76625067562895360000\"\n              data-toaster-class=\"gatsby-code-button-toaster\"\n              data-toaster-text-class=\"gatsby-code-button-toaster-text\"\n              data-toaster-text=\"代码复制成功\"\n              data-toaster-duration=\"3500\"\n              onClick=\"copyToClipboard(`// 1. 引入接口变量文件，这个会依赖 @serverless/tencent-website 组件自动生成\nimport &quot;./env.js&quot;;\n\nimport Vue from &quot;vue&quot;;\n\nimport &quot;normalize.css/normalize.css&quot;;\nimport ElementUI from &quot;element-ui&quot;;\nimport &quot;element-ui/lib/theme-chalk/index.css&quot;;\nimport locale from &quot;element-ui/lib/locale/lang/en&quot;;\nimport &quot;@/styles/index.scss&quot;;\nimport App from &quot;./App&quot;;\nimport store from &quot;./store&quot;;\nimport router from &quot;./router&quot;;\nimport &quot;@/icons&quot;;\nimport &quot;@/permission&quot;;\n\n// 2. 下面这段就是 mock server 引入，删除就好\n// if (process.env.NODE_ENV === 'production') {\n//   const { mockXHR } = require('../mock')\n//   mockXHR()\n// }\n\nVue.use(ElementUI, { locale });\nVue.config.productionTip = false;\n\nnew Vue({\n  el: &quot;#app&quot;,\n  router,\n  store,\n  render: h => h(App)\n});`, `76625067562895360000`)\"\n            >\n              <div\n                class=\"gatsby-code-button\"\n                data-tooltip=\"\"\n              >\n                复制代码<svg class=\"gatsby-code-button-icon\" xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><path fill=\"none\" d=\"M0 0h24v24H0V0z\"/><path d=\"M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z\"/></svg>\n              </div>\n            </div>\n<div class=\"gatsby-highlight\" data-language=\"js\"><pre class=\"language-js\"><code class=\"language-js\"><span class=\"token comment\">// 1. 引入接口变量文件，这个会依赖 @serverless/tencent-website 组件自动生成</span>\n<span class=\"token keyword\">import</span> <span class=\"token string\">\"./env.js\"</span><span class=\"token punctuation\">;</span>\n\n<span class=\"token keyword\">import</span> Vue <span class=\"token keyword\">from</span> <span class=\"token string\">\"vue\"</span><span class=\"token punctuation\">;</span>\n\n<span class=\"token keyword\">import</span> <span class=\"token string\">\"normalize.css/normalize.css\"</span><span class=\"token punctuation\">;</span>\n<span class=\"token keyword\">import</span> ElementUI <span class=\"token keyword\">from</span> <span class=\"token string\">\"element-ui\"</span><span class=\"token punctuation\">;</span>\n<span class=\"token keyword\">import</span> <span class=\"token string\">\"element-ui/lib/theme-chalk/index.css\"</span><span class=\"token punctuation\">;</span>\n<span class=\"token keyword\">import</span> locale <span class=\"token keyword\">from</span> <span class=\"token string\">\"element-ui/lib/locale/lang/en\"</span><span class=\"token punctuation\">;</span>\n<span class=\"token keyword\">import</span> <span class=\"token string\">\"@/styles/index.scss\"</span><span class=\"token punctuation\">;</span>\n<span class=\"token keyword\">import</span> App <span class=\"token keyword\">from</span> <span class=\"token string\">\"./App\"</span><span class=\"token punctuation\">;</span>\n<span class=\"token keyword\">import</span> store <span class=\"token keyword\">from</span> <span class=\"token string\">\"./store\"</span><span class=\"token punctuation\">;</span>\n<span class=\"token keyword\">import</span> router <span class=\"token keyword\">from</span> <span class=\"token string\">\"./router\"</span><span class=\"token punctuation\">;</span>\n<span class=\"token keyword\">import</span> <span class=\"token string\">\"@/icons\"</span><span class=\"token punctuation\">;</span>\n<span class=\"token keyword\">import</span> <span class=\"token string\">\"@/permission\"</span><span class=\"token punctuation\">;</span>\n\n<span class=\"token comment\">// 2. 下面这段就是 mock server 引入，删除就好</span>\n<span class=\"token comment\">// if (process.env.NODE_ENV === 'production') {</span>\n<span class=\"token comment\">//   const { mockXHR } = require('../mock')</span>\n<span class=\"token comment\">//   mockXHR()</span>\n<span class=\"token comment\">// }</span>\n\nVue<span class=\"token punctuation\">.</span><span class=\"token function\">use</span><span class=\"token punctuation\">(</span>ElementUI<span class=\"token punctuation\">,</span> <span class=\"token punctuation\">{</span> locale <span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\nVue<span class=\"token punctuation\">.</span>config<span class=\"token punctuation\">.</span>productionTip <span class=\"token operator\">=</span> <span class=\"token boolean\">false</span><span class=\"token punctuation\">;</span>\n\n<span class=\"token keyword\">new</span> <span class=\"token class-name\">Vue</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">{</span>\n  el<span class=\"token punctuation\">:</span> <span class=\"token string\">\"#app\"</span><span class=\"token punctuation\">,</span>\n  router<span class=\"token punctuation\">,</span>\n  store<span class=\"token punctuation\">,</span>\n  <span class=\"token function-variable function\">render</span><span class=\"token punctuation\">:</span> <span class=\"token parameter\">h</span> <span class=\"token operator\">=></span> <span class=\"token function\">h</span><span class=\"token punctuation\">(</span>App<span class=\"token punctuation\">)</span>\n<span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span></code></pre></div>\n<h3 id=\"2-修改接口函数\"><a href=\"#2-%E4%BF%AE%E6%94%B9%E6%8E%A5%E5%8F%A3%E5%87%BD%E6%95%B0\" aria-label=\"2 修改接口函数 permalink\" class=\"anchor\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>2. 修改接口函数</h3>\n<p>修改 <code class=\"language-text\">frontend/src/api/user.js</code> 文件，包括登录、注销、获取用户信息和获取用户列表函数如下：</p>\n<div\n              class=\"gatsby-code-button-container\"\n              data-toaster-id=\"20993685509713347000\"\n              data-toaster-class=\"gatsby-code-button-toaster\"\n              data-toaster-text-class=\"gatsby-code-button-toaster-text\"\n              data-toaster-text=\"代码复制成功\"\n              data-toaster-duration=\"3500\"\n              onClick=\"copyToClipboard(`import request from &quot;@/utils/request&quot;;\n\n// 登录\nexport function login(data) {\n  return request({\n    url: &quot;/login&quot;,\n    method: &quot;post&quot;,\n    data\n  });\n}\n\n// 获取用户信息\nexport function getInfo(token) {\n  return request({\n    url: &quot;/user-info&quot;,\n    method: &quot;get&quot;\n  });\n}\n\n// 注销登录\nexport function logout() {\n  return request({\n    url: &quot;/logout&quot;,\n    method: &quot;post&quot;\n  });\n}\n\n// 获取用户列表\nexport function getList() {\n  return request({\n    url: &quot;/users&quot;,\n    method: &quot;get&quot;\n  });\n}`, `20993685509713347000`)\"\n            >\n              <div\n                class=\"gatsby-code-button\"\n                data-tooltip=\"\"\n              >\n                复制代码<svg class=\"gatsby-code-button-icon\" xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><path fill=\"none\" d=\"M0 0h24v24H0V0z\"/><path d=\"M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z\"/></svg>\n              </div>\n            </div>\n<div class=\"gatsby-highlight\" data-language=\"js\"><pre class=\"language-js\"><code class=\"language-js\"><span class=\"token keyword\">import</span> request <span class=\"token keyword\">from</span> <span class=\"token string\">\"@/utils/request\"</span><span class=\"token punctuation\">;</span>\n\n<span class=\"token comment\">// 登录</span>\n<span class=\"token keyword\">export</span> <span class=\"token keyword\">function</span> <span class=\"token function\">login</span><span class=\"token punctuation\">(</span><span class=\"token parameter\">data</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n  <span class=\"token keyword\">return</span> <span class=\"token function\">request</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">{</span>\n    url<span class=\"token punctuation\">:</span> <span class=\"token string\">\"/login\"</span><span class=\"token punctuation\">,</span>\n    method<span class=\"token punctuation\">:</span> <span class=\"token string\">\"post\"</span><span class=\"token punctuation\">,</span>\n    data\n  <span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span>\n\n<span class=\"token comment\">// 获取用户信息</span>\n<span class=\"token keyword\">export</span> <span class=\"token keyword\">function</span> <span class=\"token function\">getInfo</span><span class=\"token punctuation\">(</span><span class=\"token parameter\">token</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n  <span class=\"token keyword\">return</span> <span class=\"token function\">request</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">{</span>\n    url<span class=\"token punctuation\">:</span> <span class=\"token string\">\"/user-info\"</span><span class=\"token punctuation\">,</span>\n    method<span class=\"token punctuation\">:</span> <span class=\"token string\">\"get\"</span>\n  <span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span>\n\n<span class=\"token comment\">// 注销登录</span>\n<span class=\"token keyword\">export</span> <span class=\"token keyword\">function</span> <span class=\"token function\">logout</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n  <span class=\"token keyword\">return</span> <span class=\"token function\">request</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">{</span>\n    url<span class=\"token punctuation\">:</span> <span class=\"token string\">\"/logout\"</span><span class=\"token punctuation\">,</span>\n    method<span class=\"token punctuation\">:</span> <span class=\"token string\">\"post\"</span>\n  <span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span>\n\n<span class=\"token comment\">// 获取用户列表</span>\n<span class=\"token keyword\">export</span> <span class=\"token keyword\">function</span> <span class=\"token function\">getList</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n  <span class=\"token keyword\">return</span> <span class=\"token function\">request</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">{</span>\n    url<span class=\"token punctuation\">:</span> <span class=\"token string\">\"/users\"</span><span class=\"token punctuation\">,</span>\n    method<span class=\"token punctuation\">:</span> <span class=\"token string\">\"get\"</span>\n  <span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span></code></pre></div>\n<p>新增 <code class=\"language-text\">frontend/src/api/post.js</code> 文件如下：</p>\n<div\n              class=\"gatsby-code-button-container\"\n              data-toaster-id=\"99322491040112080000\"\n              data-toaster-class=\"gatsby-code-button-toaster\"\n              data-toaster-text-class=\"gatsby-code-button-toaster-text\"\n              data-toaster-text=\"代码复制成功\"\n              data-toaster-duration=\"3500\"\n              onClick=\"copyToClipboard(`import request from &quot;@/utils/request&quot;;\n\n// 获取文章列表\nexport function getList(params) {\n  return request({\n    url: &quot;/posts&quot;,\n    method: &quot;get&quot;,\n    params\n  });\n}\n\n// 创建文章\nexport function create(data) {\n  return request({\n    url: &quot;/posts&quot;,\n    method: &quot;post&quot;,\n    data\n  });\n}\n\n// 删除文章\nexport function destroy(id) {\n  return request({\n    url: \\`/posts/\\${id}\\`,\n    method: &quot;delete&quot;\n  });\n}`, `99322491040112080000`)\"\n            >\n              <div\n                class=\"gatsby-code-button\"\n                data-tooltip=\"\"\n              >\n                复制代码<svg class=\"gatsby-code-button-icon\" xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><path fill=\"none\" d=\"M0 0h24v24H0V0z\"/><path d=\"M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z\"/></svg>\n              </div>\n            </div>\n<div class=\"gatsby-highlight\" data-language=\"js\"><pre class=\"language-js\"><code class=\"language-js\"><span class=\"token keyword\">import</span> request <span class=\"token keyword\">from</span> <span class=\"token string\">\"@/utils/request\"</span><span class=\"token punctuation\">;</span>\n\n<span class=\"token comment\">// 获取文章列表</span>\n<span class=\"token keyword\">export</span> <span class=\"token keyword\">function</span> <span class=\"token function\">getList</span><span class=\"token punctuation\">(</span><span class=\"token parameter\">params</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n  <span class=\"token keyword\">return</span> <span class=\"token function\">request</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">{</span>\n    url<span class=\"token punctuation\">:</span> <span class=\"token string\">\"/posts\"</span><span class=\"token punctuation\">,</span>\n    method<span class=\"token punctuation\">:</span> <span class=\"token string\">\"get\"</span><span class=\"token punctuation\">,</span>\n    params\n  <span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span>\n\n<span class=\"token comment\">// 创建文章</span>\n<span class=\"token keyword\">export</span> <span class=\"token keyword\">function</span> <span class=\"token function\">create</span><span class=\"token punctuation\">(</span><span class=\"token parameter\">data</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n  <span class=\"token keyword\">return</span> <span class=\"token function\">request</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">{</span>\n    url<span class=\"token punctuation\">:</span> <span class=\"token string\">\"/posts\"</span><span class=\"token punctuation\">,</span>\n    method<span class=\"token punctuation\">:</span> <span class=\"token string\">\"post\"</span><span class=\"token punctuation\">,</span>\n    data\n  <span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span>\n\n<span class=\"token comment\">// 删除文章</span>\n<span class=\"token keyword\">export</span> <span class=\"token keyword\">function</span> <span class=\"token function\">destroy</span><span class=\"token punctuation\">(</span><span class=\"token parameter\">id</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n  <span class=\"token keyword\">return</span> <span class=\"token function\">request</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">{</span>\n    url<span class=\"token punctuation\">:</span> <span class=\"token template-string\"><span class=\"token template-punctuation string\">`</span><span class=\"token string\">/posts/</span><span class=\"token interpolation\"><span class=\"token interpolation-punctuation punctuation\">${</span>id<span class=\"token interpolation-punctuation punctuation\">}</span></span><span class=\"token template-punctuation string\">`</span></span><span class=\"token punctuation\">,</span>\n    method<span class=\"token punctuation\">:</span> <span class=\"token string\">\"delete\"</span>\n  <span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span></code></pre></div>\n<h3 id=\"3-修改接口工具函数\"><a href=\"#3-%E4%BF%AE%E6%94%B9%E6%8E%A5%E5%8F%A3%E5%B7%A5%E5%85%B7%E5%87%BD%E6%95%B0\" aria-label=\"3 修改接口工具函数 permalink\" class=\"anchor\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>3. 修改接口工具函数</h3>\n<p>因为 <code class=\"language-text\">@serverless/tencent-website</code> 组件可以定义 <code class=\"language-text\">env</code> 参数，执行成功后它会在指定 <code class=\"language-text\">root</code> 目录自动生成 <code class=\"language-text\">env.js</code>，然后在 <code class=\"language-text\">frontend/src/main.js</code> 中引入使用。\n它会挂载 <code class=\"language-text\">env</code> 中定义的接口变量到 <code class=\"language-text\">window</code> 对象上。比如这生成的 <code class=\"language-text\">env.js</code> 文件如下：</p>\n<div\n              class=\"gatsby-code-button-container\"\n              data-toaster-id=\"60837700005419640000\"\n              data-toaster-class=\"gatsby-code-button-toaster\"\n              data-toaster-text-class=\"gatsby-code-button-toaster-text\"\n              data-toaster-text=\"代码复制成功\"\n              data-toaster-duration=\"3500\"\n              onClick=\"copyToClipboard(`window.env = {};\nwindow.env.apiUrl =\n  &quot;https://service-f1bhmhk4-1251556596.gz.apigw.tencentcs.com/release/&quot;;`, `60837700005419640000`)\"\n            >\n              <div\n                class=\"gatsby-code-button\"\n                data-tooltip=\"\"\n              >\n                复制代码<svg class=\"gatsby-code-button-icon\" xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><path fill=\"none\" d=\"M0 0h24v24H0V0z\"/><path d=\"M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z\"/></svg>\n              </div>\n            </div>\n<div class=\"gatsby-highlight\" data-language=\"js\"><pre class=\"language-js\"><code class=\"language-js\">window<span class=\"token punctuation\">.</span>env <span class=\"token operator\">=</span> <span class=\"token punctuation\">{</span><span class=\"token punctuation\">}</span><span class=\"token punctuation\">;</span>\nwindow<span class=\"token punctuation\">.</span>env<span class=\"token punctuation\">.</span>apiUrl <span class=\"token operator\">=</span>\n  <span class=\"token string\">\"https://service-f1bhmhk4-1251556596.gz.apigw.tencentcs.com/release/\"</span><span class=\"token punctuation\">;</span></code></pre></div>\n<p>根据此文件我们来修改 <code class=\"language-text\">frontend/src/utils/request.js</code> 文件：</p>\n<div\n              class=\"gatsby-code-button-container\"\n              data-toaster-id=\"27556936130498480000\"\n              data-toaster-class=\"gatsby-code-button-toaster\"\n              data-toaster-text-class=\"gatsby-code-button-toaster-text\"\n              data-toaster-text=\"代码复制成功\"\n              data-toaster-duration=\"3500\"\n              onClick=\"copyToClipboard(`import axios from &quot;axios&quot;;\nimport { MessageBox, Message } from &quot;element-ui&quot;;\nimport store from &quot;@/store&quot;;\nimport { getToken } from &quot;@/utils/auth&quot;;\n\n// 创建 axios 实例\nconst service = axios.create({\n  // 1. 这里设置为 \\`env.js\\` 中的变量 \\`window.env.apiUrl\\`\n  baseURL: window.env.apiUrl || &quot;/&quot;, // url = base url + request url\n  timeout: 5000 // request timeout\n});\n\n// request 注入\nservice.interceptors.request.use(\n  config => {\n    // 2. 添加鉴权token\n    if (store.getters.token) {\n      config.headers[&quot;Authorization&quot;] = \\`Bearer \\${getToken()}\\`;\n    }\n    return config;\n  },\n  error => {\n    console.log(error); // for debug\n    return Promise.reject(error);\n  }\n);\n\n// 请求 response 注入\nservice.interceptors.response.use(\n  response => {\n    const res = response.data;\n\n    // 只有请求code为0，才是正常返回，否则需要提示接口错误\n    if (res.code !== 0) {\n      Message({\n        message: res.message || &quot;Error&quot;,\n        type: &quot;error&quot;,\n        duration: 5 * 1000\n      });\n\n      if (res.code === 50008 || res.code === 50012 || res.code === 50014) {\n        // to re-login\n        MessageBox.confirm(\n          &quot;You have been logged out, you can cancel to stay on this page, or log in again&quot;,\n          &quot;Confirm logout&quot;,\n          {\n            confirmButtonText: &quot;Re-Login&quot;,\n            cancelButtonText: &quot;Cancel&quot;,\n            type: &quot;warning&quot;\n          }\n        ).then(() => {\n          store.dispatch(&quot;user/resetToken&quot;).then(() => {\n            location.reload();\n          });\n        });\n      }\n      return Promise.reject(new Error(res.message || &quot;Error&quot;));\n    } else {\n      return res;\n    }\n  },\n  error => {\n    console.log(&quot;err&quot; + error);\n    Message({\n      message: error.message,\n      type: &quot;error&quot;,\n      duration: 5 * 1000\n    });\n    return Promise.reject(error);\n  }\n);\n\nexport default service;`, `27556936130498480000`)\"\n            >\n              <div\n                class=\"gatsby-code-button\"\n                data-tooltip=\"\"\n              >\n                复制代码<svg class=\"gatsby-code-button-icon\" xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><path fill=\"none\" d=\"M0 0h24v24H0V0z\"/><path d=\"M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z\"/></svg>\n              </div>\n            </div>\n<div class=\"gatsby-highlight\" data-language=\"js\"><pre class=\"language-js\"><code class=\"language-js\"><span class=\"token keyword\">import</span> axios <span class=\"token keyword\">from</span> <span class=\"token string\">\"axios\"</span><span class=\"token punctuation\">;</span>\n<span class=\"token keyword\">import</span> <span class=\"token punctuation\">{</span> MessageBox<span class=\"token punctuation\">,</span> Message <span class=\"token punctuation\">}</span> <span class=\"token keyword\">from</span> <span class=\"token string\">\"element-ui\"</span><span class=\"token punctuation\">;</span>\n<span class=\"token keyword\">import</span> store <span class=\"token keyword\">from</span> <span class=\"token string\">\"@/store\"</span><span class=\"token punctuation\">;</span>\n<span class=\"token keyword\">import</span> <span class=\"token punctuation\">{</span> getToken <span class=\"token punctuation\">}</span> <span class=\"token keyword\">from</span> <span class=\"token string\">\"@/utils/auth\"</span><span class=\"token punctuation\">;</span>\n\n<span class=\"token comment\">// 创建 axios 实例</span>\n<span class=\"token keyword\">const</span> service <span class=\"token operator\">=</span> axios<span class=\"token punctuation\">.</span><span class=\"token function\">create</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">{</span>\n  <span class=\"token comment\">// 1. 这里设置为 `env.js` 中的变量 `window.env.apiUrl`</span>\n  baseURL<span class=\"token punctuation\">:</span> window<span class=\"token punctuation\">.</span>env<span class=\"token punctuation\">.</span>apiUrl <span class=\"token operator\">||</span> <span class=\"token string\">\"/\"</span><span class=\"token punctuation\">,</span> <span class=\"token comment\">// url = base url + request url</span>\n  timeout<span class=\"token punctuation\">:</span> <span class=\"token number\">5000</span> <span class=\"token comment\">// request timeout</span>\n<span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\n<span class=\"token comment\">// request 注入</span>\nservice<span class=\"token punctuation\">.</span>interceptors<span class=\"token punctuation\">.</span>request<span class=\"token punctuation\">.</span><span class=\"token function\">use</span><span class=\"token punctuation\">(</span>\n  <span class=\"token parameter\">config</span> <span class=\"token operator\">=></span> <span class=\"token punctuation\">{</span>\n    <span class=\"token comment\">// 2. 添加鉴权token</span>\n    <span class=\"token keyword\">if</span> <span class=\"token punctuation\">(</span>store<span class=\"token punctuation\">.</span>getters<span class=\"token punctuation\">.</span>token<span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n      config<span class=\"token punctuation\">.</span>headers<span class=\"token punctuation\">[</span><span class=\"token string\">\"Authorization\"</span><span class=\"token punctuation\">]</span> <span class=\"token operator\">=</span> <span class=\"token template-string\"><span class=\"token template-punctuation string\">`</span><span class=\"token string\">Bearer </span><span class=\"token interpolation\"><span class=\"token interpolation-punctuation punctuation\">${</span><span class=\"token function\">getToken</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token interpolation-punctuation punctuation\">}</span></span><span class=\"token template-punctuation string\">`</span></span><span class=\"token punctuation\">;</span>\n    <span class=\"token punctuation\">}</span>\n    <span class=\"token keyword\">return</span> config<span class=\"token punctuation\">;</span>\n  <span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span>\n  <span class=\"token parameter\">error</span> <span class=\"token operator\">=></span> <span class=\"token punctuation\">{</span>\n    console<span class=\"token punctuation\">.</span><span class=\"token function\">log</span><span class=\"token punctuation\">(</span>error<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span> <span class=\"token comment\">// for debug</span>\n    <span class=\"token keyword\">return</span> Promise<span class=\"token punctuation\">.</span><span class=\"token function\">reject</span><span class=\"token punctuation\">(</span>error<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n  <span class=\"token punctuation\">}</span>\n<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\n<span class=\"token comment\">// 请求 response 注入</span>\nservice<span class=\"token punctuation\">.</span>interceptors<span class=\"token punctuation\">.</span>response<span class=\"token punctuation\">.</span><span class=\"token function\">use</span><span class=\"token punctuation\">(</span>\n  <span class=\"token parameter\">response</span> <span class=\"token operator\">=></span> <span class=\"token punctuation\">{</span>\n    <span class=\"token keyword\">const</span> res <span class=\"token operator\">=</span> response<span class=\"token punctuation\">.</span>data<span class=\"token punctuation\">;</span>\n\n    <span class=\"token comment\">// 只有请求code为0，才是正常返回，否则需要提示接口错误</span>\n    <span class=\"token keyword\">if</span> <span class=\"token punctuation\">(</span>res<span class=\"token punctuation\">.</span>code <span class=\"token operator\">!==</span> <span class=\"token number\">0</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n      <span class=\"token function\">Message</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">{</span>\n        message<span class=\"token punctuation\">:</span> res<span class=\"token punctuation\">.</span>message <span class=\"token operator\">||</span> <span class=\"token string\">\"Error\"</span><span class=\"token punctuation\">,</span>\n        type<span class=\"token punctuation\">:</span> <span class=\"token string\">\"error\"</span><span class=\"token punctuation\">,</span>\n        duration<span class=\"token punctuation\">:</span> <span class=\"token number\">5</span> <span class=\"token operator\">*</span> <span class=\"token number\">1000</span>\n      <span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\n      <span class=\"token keyword\">if</span> <span class=\"token punctuation\">(</span>res<span class=\"token punctuation\">.</span>code <span class=\"token operator\">===</span> <span class=\"token number\">50008</span> <span class=\"token operator\">||</span> res<span class=\"token punctuation\">.</span>code <span class=\"token operator\">===</span> <span class=\"token number\">50012</span> <span class=\"token operator\">||</span> res<span class=\"token punctuation\">.</span>code <span class=\"token operator\">===</span> <span class=\"token number\">50014</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n        <span class=\"token comment\">// to re-login</span>\n        MessageBox<span class=\"token punctuation\">.</span><span class=\"token function\">confirm</span><span class=\"token punctuation\">(</span>\n          <span class=\"token string\">\"You have been logged out, you can cancel to stay on this page, or log in again\"</span><span class=\"token punctuation\">,</span>\n          <span class=\"token string\">\"Confirm logout\"</span><span class=\"token punctuation\">,</span>\n          <span class=\"token punctuation\">{</span>\n            confirmButtonText<span class=\"token punctuation\">:</span> <span class=\"token string\">\"Re-Login\"</span><span class=\"token punctuation\">,</span>\n            cancelButtonText<span class=\"token punctuation\">:</span> <span class=\"token string\">\"Cancel\"</span><span class=\"token punctuation\">,</span>\n            type<span class=\"token punctuation\">:</span> <span class=\"token string\">\"warning\"</span>\n          <span class=\"token punctuation\">}</span>\n        <span class=\"token punctuation\">)</span><span class=\"token punctuation\">.</span><span class=\"token function\">then</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">=></span> <span class=\"token punctuation\">{</span>\n          store<span class=\"token punctuation\">.</span><span class=\"token function\">dispatch</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"user/resetToken\"</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">.</span><span class=\"token function\">then</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">=></span> <span class=\"token punctuation\">{</span>\n            location<span class=\"token punctuation\">.</span><span class=\"token function\">reload</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n          <span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n        <span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n      <span class=\"token punctuation\">}</span>\n      <span class=\"token keyword\">return</span> Promise<span class=\"token punctuation\">.</span><span class=\"token function\">reject</span><span class=\"token punctuation\">(</span><span class=\"token keyword\">new</span> <span class=\"token class-name\">Error</span><span class=\"token punctuation\">(</span>res<span class=\"token punctuation\">.</span>message <span class=\"token operator\">||</span> <span class=\"token string\">\"Error\"</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n    <span class=\"token punctuation\">}</span> <span class=\"token keyword\">else</span> <span class=\"token punctuation\">{</span>\n      <span class=\"token keyword\">return</span> res<span class=\"token punctuation\">;</span>\n    <span class=\"token punctuation\">}</span>\n  <span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span>\n  <span class=\"token parameter\">error</span> <span class=\"token operator\">=></span> <span class=\"token punctuation\">{</span>\n    console<span class=\"token punctuation\">.</span><span class=\"token function\">log</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"err\"</span> <span class=\"token operator\">+</span> error<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n    <span class=\"token function\">Message</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">{</span>\n      message<span class=\"token punctuation\">:</span> error<span class=\"token punctuation\">.</span>message<span class=\"token punctuation\">,</span>\n      type<span class=\"token punctuation\">:</span> <span class=\"token string\">\"error\"</span><span class=\"token punctuation\">,</span>\n      duration<span class=\"token punctuation\">:</span> <span class=\"token number\">5</span> <span class=\"token operator\">*</span> <span class=\"token number\">1000</span>\n    <span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n    <span class=\"token keyword\">return</span> Promise<span class=\"token punctuation\">.</span><span class=\"token function\">reject</span><span class=\"token punctuation\">(</span>error<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n  <span class=\"token punctuation\">}</span>\n<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\n<span class=\"token keyword\">export</span> <span class=\"token keyword\">default</span> service<span class=\"token punctuation\">;</span></code></pre></div>\n<h3 id=\"4-ui-界面修改\"><a href=\"#4-ui-%E7%95%8C%E9%9D%A2%E4%BF%AE%E6%94%B9\" aria-label=\"4 ui 界面修改 permalink\" class=\"anchor\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>4. UI 界面修改</h3>\n<p>关于 UI 界面修改，这里就不做说明了，因为涉及到 Vue.js 的基础使用，如果还不会使用 Vue.js，建议先复制示例代码就好。如果对 Vue.js 感兴趣，可以到 <a href=\"https://cn.vuejs.org/\">Vue.js 官网</a> 学习。也可以阅读本人的 <a href=\"https://yugasun.github.io/You-May-Not-Know-Vuejs\">Vuejs 从入门到精通系列文章</a>，喜欢的话，可以送上您宝贵的 <code class=\"language-text\">Star (*^▽^*)</code></p>\n<p>这里只需要复制 <a href=\"https://github.com/yugasun/tencent-serverless-demo/tree/master/admin-system\">Demo 源码</a> 的 <code class=\"language-text\">frontend/router</code> 和 <code class=\"language-text\">frontend/views</code> 两个文件夹就好。</p>\n<h2 id=\"前端部署\"><a href=\"#%E5%89%8D%E7%AB%AF%E9%83%A8%E7%BD%B2\" aria-label=\"前端部署 permalink\" class=\"anchor\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>前端部署</h2>\n<p>因为前端编译后都是静态文件，我们需要将静态文件上传到腾讯云的 COS（对象存储） 服务，然后开启 COS 的静态网站功能就可以了，这些都不需要你手动操作，使用 <a href=\"https://github.com/serverless-components/tencent-website\">@serverless/tencent-website</a> 组件就可以轻松搞定。</p>\n<h3 id=\"1-修改-serverless-配置文件\"><a href=\"#1-%E4%BF%AE%E6%94%B9-serverless-%E9%85%8D%E7%BD%AE%E6%96%87%E4%BB%B6\" aria-label=\"1 修改 serverless 配置文件 permalink\" class=\"anchor\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>1. 修改 Serverless 配置文件</h3>\n<p>修改项目根目录下 <code class=\"language-text\">serverless.yml</code> 文件，新增前端相关配置：</p>\n<div\n              class=\"gatsby-code-button-container\"\n              data-toaster-id=\"23523123151616487000\"\n              data-toaster-class=\"gatsby-code-button-toaster\"\n              data-toaster-text-class=\"gatsby-code-button-toaster-text\"\n              data-toaster-text=\"代码复制成功\"\n              data-toaster-duration=\"3500\"\n              onClick=\"copyToClipboard(`name: admin-system\n\n# 前端配置\nfrontend:\n  component: &quot;@serverless/tencent-website&quot;\n  inputs:\n    code:\n      src: dist\n      root: frontend\n      envPath: src # 相对于 root 指定目录，这里实际就是 frontend/src\n      hook: npm run build\n    env:\n      # 依赖后端部署成功后生成的 url\n      apiUrl: \\${backend.url}\n    protocol: https\n    # TODO: CDN 配置，请修改！！！\n    hosts:\n      - host: sls-admin.yugasun.com # CDN 加速域名\n        https:\n          certId: abcdedg # 为加速域名在腾讯云平台申请的免费证书 ID\n          http2: off\n          httpsType: 4\n          forceSwitch: -2\n\n# 后端配置\nbackend:\n  component: &quot;@serverless/tencent-egg&quot;\n  inputs:\n    code: ./backend\n    functionName: admin-system\n    role: QCS_SCFFull\n    functionConf:\n      timeout: 120\n      vpcConfig:\n        vpcId: vpc-6n5x55kb\n        subnetId: subnet-4cvr91js\n    apigatewayConf:\n      protocols:\n        - https`, `23523123151616487000`)\"\n            >\n              <div\n                class=\"gatsby-code-button\"\n                data-tooltip=\"\"\n              >\n                复制代码<svg class=\"gatsby-code-button-icon\" xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><path fill=\"none\" d=\"M0 0h24v24H0V0z\"/><path d=\"M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z\"/></svg>\n              </div>\n            </div>\n<div class=\"gatsby-highlight\" data-language=\"yaml\"><pre class=\"language-yaml\"><code class=\"language-yaml\"><span class=\"token key atrule\">name</span><span class=\"token punctuation\">:</span> admin<span class=\"token punctuation\">-</span>system\n\n<span class=\"token comment\"># 前端配置</span>\n<span class=\"token key atrule\">frontend</span><span class=\"token punctuation\">:</span>\n  <span class=\"token key atrule\">component</span><span class=\"token punctuation\">:</span> <span class=\"token string\">\"@serverless/tencent-website\"</span>\n  <span class=\"token key atrule\">inputs</span><span class=\"token punctuation\">:</span>\n    <span class=\"token key atrule\">code</span><span class=\"token punctuation\">:</span>\n      <span class=\"token key atrule\">src</span><span class=\"token punctuation\">:</span> dist\n      <span class=\"token key atrule\">root</span><span class=\"token punctuation\">:</span> frontend\n      <span class=\"token key atrule\">envPath</span><span class=\"token punctuation\">:</span> src <span class=\"token comment\"># 相对于 root 指定目录，这里实际就是 frontend/src</span>\n      <span class=\"token key atrule\">hook</span><span class=\"token punctuation\">:</span> npm run build\n    <span class=\"token key atrule\">env</span><span class=\"token punctuation\">:</span>\n      <span class=\"token comment\"># 依赖后端部署成功后生成的 url</span>\n      <span class=\"token key atrule\">apiUrl</span><span class=\"token punctuation\">:</span> $<span class=\"token punctuation\">{</span>backend.url<span class=\"token punctuation\">}</span>\n    <span class=\"token key atrule\">protocol</span><span class=\"token punctuation\">:</span> https\n    <span class=\"token comment\"># TODO: CDN 配置，请修改！！！</span>\n    <span class=\"token key atrule\">hosts</span><span class=\"token punctuation\">:</span>\n      <span class=\"token punctuation\">-</span> <span class=\"token key atrule\">host</span><span class=\"token punctuation\">:</span> sls<span class=\"token punctuation\">-</span>admin.yugasun.com <span class=\"token comment\"># CDN 加速域名</span>\n        <span class=\"token key atrule\">https</span><span class=\"token punctuation\">:</span>\n          <span class=\"token key atrule\">certId</span><span class=\"token punctuation\">:</span> abcdedg <span class=\"token comment\"># 为加速域名在腾讯云平台申请的免费证书 ID</span>\n          <span class=\"token key atrule\">http2</span><span class=\"token punctuation\">:</span> off\n          <span class=\"token key atrule\">httpsType</span><span class=\"token punctuation\">:</span> <span class=\"token number\">4</span>\n          <span class=\"token key atrule\">forceSwitch</span><span class=\"token punctuation\">:</span> <span class=\"token number\">-2</span>\n\n<span class=\"token comment\"># 后端配置</span>\n<span class=\"token key atrule\">backend</span><span class=\"token punctuation\">:</span>\n  <span class=\"token key atrule\">component</span><span class=\"token punctuation\">:</span> <span class=\"token string\">\"@serverless/tencent-egg\"</span>\n  <span class=\"token key atrule\">inputs</span><span class=\"token punctuation\">:</span>\n    <span class=\"token key atrule\">code</span><span class=\"token punctuation\">:</span> ./backend\n    <span class=\"token key atrule\">functionName</span><span class=\"token punctuation\">:</span> admin<span class=\"token punctuation\">-</span>system\n    <span class=\"token key atrule\">role</span><span class=\"token punctuation\">:</span> QCS_SCFFull\n    <span class=\"token key atrule\">functionConf</span><span class=\"token punctuation\">:</span>\n      <span class=\"token key atrule\">timeout</span><span class=\"token punctuation\">:</span> <span class=\"token number\">120</span>\n      <span class=\"token key atrule\">vpcConfig</span><span class=\"token punctuation\">:</span>\n        <span class=\"token key atrule\">vpcId</span><span class=\"token punctuation\">:</span> vpc<span class=\"token punctuation\">-</span>6n5x55kb\n        <span class=\"token key atrule\">subnetId</span><span class=\"token punctuation\">:</span> subnet<span class=\"token punctuation\">-</span>4cvr91js\n    <span class=\"token key atrule\">apigatewayConf</span><span class=\"token punctuation\">:</span>\n      <span class=\"token key atrule\">protocols</span><span class=\"token punctuation\">:</span>\n        <span class=\"token punctuation\">-</span> https</code></pre></div>\n<h3 id=\"2-执行部署\"><a href=\"#2-%E6%89%A7%E8%A1%8C%E9%83%A8%E7%BD%B2\" aria-label=\"2 执行部署 permalink\" class=\"anchor\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>2. 执行部署</h3>\n<p>执行部署命令：</p>\n<div\n              class=\"gatsby-code-button-container\"\n              data-toaster-id=\"62968562000030980000\"\n              data-toaster-class=\"gatsby-code-button-toaster\"\n              data-toaster-text-class=\"gatsby-code-button-toaster-text\"\n              data-toaster-text=\"代码复制成功\"\n              data-toaster-duration=\"3500\"\n              onClick=\"copyToClipboard(`\\$ serverless --debug`, `62968562000030980000`)\"\n            >\n              <div\n                class=\"gatsby-code-button\"\n                data-tooltip=\"\"\n              >\n                复制代码<svg class=\"gatsby-code-button-icon\" xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><path fill=\"none\" d=\"M0 0h24v24H0V0z\"/><path d=\"M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z\"/></svg>\n              </div>\n            </div>\n<div class=\"gatsby-highlight\" data-language=\"bash\"><pre class=\"language-bash\"><code class=\"language-bash\">$ serverless --debug</code></pre></div>\n<p>输出如下成功结果：</p>\n<div\n              class=\"gatsby-code-button-container\"\n              data-toaster-id=\"84661215158357430000\"\n              data-toaster-class=\"gatsby-code-button-toaster\"\n              data-toaster-text-class=\"gatsby-code-button-toaster-text\"\n              data-toaster-text=\"代码复制成功\"\n              data-toaster-duration=\"3500\"\n              onClick=\"copyToClipboard(`  frontend:\n    url:  https://dtnu69vl-470dpfh-1251556596.cos-website.ap-guangzhou.myqcloud.com\n    env:\n      apiUrl: https://service-f1bhmhk4-1251556596.gz.apigw.tencentcs.com/release/\n    host:\n      - https://sls-admin.yugasun.com (CNAME: sls-admin.yugasun.com.cdn.dnsv1.com）\n  backend:\n    region:              ap-guangzhou\n    functionName:        admin-system\n    apiGatewayServiceId: service-f1bhmhk4\n    url:                 https://service-f1bhmhk4-1251556596.gz.apigw.tencentcs.com/release/`, `84661215158357430000`)\"\n            >\n              <div\n                class=\"gatsby-code-button\"\n                data-tooltip=\"\"\n              >\n                复制代码<svg class=\"gatsby-code-button-icon\" xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><path fill=\"none\" d=\"M0 0h24v24H0V0z\"/><path d=\"M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z\"/></svg>\n              </div>\n            </div>\n<div class=\"gatsby-highlight\" data-language=\"bash\"><pre class=\"language-bash\"><code class=\"language-bash\">  frontend:\n    url:  https://dtnu69vl-470dpfh-1251556596.cos-website.ap-guangzhou.myqcloud.com\n    env:\n      apiUrl: https://service-f1bhmhk4-1251556596.gz.apigw.tencentcs.com/release/\n    host:\n      - https://sls-admin.yugasun.com <span class=\"token punctuation\">(</span>CNAME: sls-admin.yugasun.com.cdn.dnsv1.com）\n  backend:\n    region:              ap-guangzhou\n    functionName:        admin-system\n    apiGatewayServiceId: service-f1bhmhk4\n    url:                 https://service-f1bhmhk4-1251556596.gz.apigw.tencentcs.com/release/</code></pre></div>\n<blockquote>\n<p>注释：这里 <code class=\"language-text\">frontend</code> 中多输出了 <code class=\"language-text\">host</code>，是我们的 CDN 加速域名，可以通过配置 <code class=\"language-text\">@serverless/tencent-website</code> 组件的 <code class=\"language-text\">inputs.hosts</code> 来实现。有关 CDN 相关配置说明可以阅读 <a href=\"https://yugasun.com/post/serverless-fullstack-vue-practice-pro.html\">基于 Serverless Component 的全栈解决方案 - 续集</a>。当然，如果你不想配置 CDN，直接删除，然后访问 COS 生成的静态网站 url。</p>\n</blockquote>\n<p>部署成功后，我们就可以访问 <code class=\"language-text\">https://sls-admin.yugasun.com</code> 登录体验了。</p>\n<h2 id=\"源码\"><a href=\"#%E6%BA%90%E7%A0%81\" aria-label=\"源码 permalink\" class=\"anchor\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>源码</h2>\n<p>本篇涉及到所有源码都维护在开源项目 <a href=\"https://github.com/yugasun/tencent-serverless-demo\">tencent-serverless-demo</a> 中 <a href=\"https://github.com/yugasun/tencent-serverless-demo/tree/master/admin-system\">admin-system</a></p>\n<h2 id=\"总结\"><a href=\"#%E6%80%BB%E7%BB%93\" aria-label=\"总结 permalink\" class=\"anchor\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>总结</h2>\n<p>本文基于腾讯云的无服务器框架 <a href=\"https://cloud.tencent.com/product/sf\">Serverless Framework</a> 实现，涉及到内容较多，推荐在阅读时，边看边开发，跟着文章节奏一步一步实现。</p>\n<p>如果遇到问题，可以参考本文源码。如果你成功实现了，可以到官网进一步熟悉 Egg.js 框架，以便今后可以实现更加复杂的应用。虽然本文使用的是 Vue.js 前端框架，但是你也可以将 <code class=\"language-text\">frontend</code> 更换为任何你喜欢的前端框架项目，开发时只需要将接口请求前缀使用 <code class=\"language-text\">@serverless/tencent-website</code> 组件生成的 <code class=\"language-text\">env.js</code> 文件就行。</p>\n<hr>\n<div id='scf-deploy-iframe-or-md'></div>\n<hr>\n<blockquote>\n<p><strong>传送门：</strong></p>\n<ul>\n<li>GitHub: <a href=\"https://github.com/serverless/serverless/blob/master/README_CN.md\">github.com/serverless</a></li>\n<li>官网：<a href=\"https://serverless.com/\">serverless.com</a></li>\n</ul>\n</blockquote>\n<p>欢迎访问：<a href=\"https://serverlesscloud.cn/\">Serverless 中文网</a>，您可以在 <a href=\"https://serverlesscloud.cn/best-practice\">最佳实践</a> 里体验更多关于 Serverless 应用的开发！</p>","tableOfContents":"<ul>\n<li><a href=\"/best-practice/2020-02-07-serverless-admin-system/#%E8%83%8C%E6%99%AF\">背景</a></li>\n<li><a href=\"/best-practice/2020-02-07-serverless-admin-system/#eggjs-%E5%85%A5%E9%97%A8\">Egg.js 入门</a></li>\n<li><a href=\"/best-practice/2020-02-07-serverless-admin-system/#%E5%87%86%E5%A4%87\">准备</a></li>\n<li>\n<p><a href=\"/best-practice/2020-02-07-serverless-admin-system/#%E5%BC%80%E5%8F%91%E5%90%8E%E7%AB%AF%E6%9C%8D%E5%8A%A1\">开发后端服务</a></p>\n<ul>\n<li><a href=\"/best-practice/2020-02-07-serverless-admin-system/#1-%E6%B7%BB%E5%8A%A0-sequelize-%E6%8F%92%E4%BB%B6\">1. 添加 Sequelize 插件</a></li>\n<li><a href=\"/best-practice/2020-02-07-serverless-admin-system/#2-%E6%B7%BB%E5%8A%A0-jwt-%E6%8F%92%E4%BB%B6\">2. 添加 JWT 插件</a></li>\n<li><a href=\"/best-practice/2020-02-07-serverless-admin-system/#3-%E6%B7%BB%E5%8A%A0-redis-%E6%8F%92%E4%BB%B6\">3. 添加 Redis 插件</a></li>\n<li><a href=\"/best-practice/2020-02-07-serverless-admin-system/#4-%E8%A7%92%E8%89%B2-api\">4. 角色 API</a></li>\n<li><a href=\"/best-practice/2020-02-07-serverless-admin-system/#5-%E7%94%A8%E6%88%B7-api\">5. 用户 API</a></li>\n<li><a href=\"/best-practice/2020-02-07-serverless-admin-system/#6-%E5%90%8C%E6%AD%A5%E6%95%B0%E6%8D%AE%E5%BA%93%E8%A1%A8%E6%A0%BC\">6. 同步数据库表格</a></li>\n<li><a href=\"/best-practice/2020-02-07-serverless-admin-system/#7-%E7%94%A8%E6%88%B7%E7%99%BB%E5%BD%95%E6%B3%A8%E9%94%80-api\">7. 用户登录/注销 API</a></li>\n</ul>\n</li>\n<li>\n<p><a href=\"/best-practice/2020-02-07-serverless-admin-system/#%E5%90%8E%E7%AB%AF%E6%9C%8D%E5%8A%A1%E9%83%A8%E7%BD%B2\">后端服务部署</a></p>\n<ul>\n<li><a href=\"/best-practice/2020-02-07-serverless-admin-system/#1-%E5%87%86%E5%A4%87\">1. 准备</a></li>\n<li><a href=\"/best-practice/2020-02-07-serverless-admin-system/#2-%E9%85%8D%E7%BD%AE-serverless\">2. 配置 Serverless</a></li>\n<li><a href=\"/best-practice/2020-02-07-serverless-admin-system/#3-%E6%89%A7%E8%A1%8C%E9%83%A8%E7%BD%B2\">3. 执行部署</a></li>\n<li><a href=\"/best-practice/2020-02-07-serverless-admin-system/#4-%E8%B4%A6%E5%8F%B7%E9%85%8D%E7%BD%AE%EF%BC%88%E5%8F%AF%E9%80%89%EF%BC%89\">4. 账号配置（可选）</a></li>\n<li><a href=\"/best-practice/2020-02-07-serverless-admin-system/#5-%E6%96%87%E7%AB%A0-api\">5. 文章 API</a></li>\n</ul>\n</li>\n<li>\n<p><a href=\"/best-practice/2020-02-07-serverless-admin-system/#%E5%89%8D%E7%AB%AF%E5%BC%80%E5%8F%91\">前端开发</a></p>\n<ul>\n<li><a href=\"/best-practice/2020-02-07-serverless-admin-system/#1-%E5%88%A0%E9%99%A4%E6%8E%A5%E5%8F%A3%E6%A8%A1%E6%8B%9F\">1. 删除接口模拟</a></li>\n<li><a href=\"/best-practice/2020-02-07-serverless-admin-system/#2-%E4%BF%AE%E6%94%B9%E6%8E%A5%E5%8F%A3%E5%87%BD%E6%95%B0\">2. 修改接口函数</a></li>\n<li><a href=\"/best-practice/2020-02-07-serverless-admin-system/#3-%E4%BF%AE%E6%94%B9%E6%8E%A5%E5%8F%A3%E5%B7%A5%E5%85%B7%E5%87%BD%E6%95%B0\">3. 修改接口工具函数</a></li>\n<li><a href=\"/best-practice/2020-02-07-serverless-admin-system/#4-ui-%E7%95%8C%E9%9D%A2%E4%BF%AE%E6%94%B9\">4. UI 界面修改</a></li>\n</ul>\n</li>\n<li>\n<p><a href=\"/best-practice/2020-02-07-serverless-admin-system/#%E5%89%8D%E7%AB%AF%E9%83%A8%E7%BD%B2\">前端部署</a></p>\n<ul>\n<li><a href=\"/best-practice/2020-02-07-serverless-admin-system/#1-%E4%BF%AE%E6%94%B9-serverless-%E9%85%8D%E7%BD%AE%E6%96%87%E4%BB%B6\">1. 修改 Serverless 配置文件</a></li>\n<li><a href=\"/best-practice/2020-02-07-serverless-admin-system/#2-%E6%89%A7%E8%A1%8C%E9%83%A8%E7%BD%B2\">2. 执行部署</a></li>\n</ul>\n</li>\n<li><a href=\"/best-practice/2020-02-07-serverless-admin-system/#%E6%BA%90%E7%A0%81\">源码</a></li>\n<li><a href=\"/best-practice/2020-02-07-serverless-admin-system/#%E6%80%BB%E7%BB%93\">总结</a></li>\n</ul>"},"previousBlog":{"id":"0d3c3171-691f-55a7-9a9a-3a35d992f615","frontmatter":{"thumbnail":"https://img.serverlesscloud.cn/2020512/1589274779819-flask.jpg","authors":["Anycodes"],"categories":["best-practice"],"date":"2020-02-08T00:00:00.000Z","title":"Serverless 与 Flask 框架结合进行 Blog 开发","description":"本文通过一个博客系统的开发，和大家简单地体验一个基于 Serverless 架构的博客系统长什么样子","authorslink":["https://zhuanlan.zhihu.com/ServerlessGo"],"translators":null,"translatorslink":null,"tags":["Serverless","Flask"],"keywords":"Serverless 多环境配置,Serverless 管理环境,Serverless配置方案","outdated":true},"wordCount":{"words":531,"sentences":68,"paragraphs":68},"fileAbsolutePath":"/opt/build/repo/content/best-practice/2020-02-08-flack-blog.md","fields":{"slug":"/best-practice/2020-02-08-flack-blog/","keywords":["go","python","serverless","website","云函数","article","self","environ","aid","category","mysql","stmt","Serverless"]}},"nextBlog":{"id":"3f27165c-9176-5e1d-a427-9ffc801d5f2b","frontmatter":{"thumbnail":"https://img.serverlesscloud.cn/202026/1580962859953-probider.png","authors":["Tabor"],"categories":["best-practice"],"date":"2020-02-06T00:00:00.000Z","title":"基于 Serverless + 企业微信打造疫情监控小助手","description":"使用 Serverless 基本功能，配合企业微信打造 nCoV 疫情监控小助手","authorslink":["https://canmeng.net"],"translators":null,"translatorslink":null,"tags":["Serverless","企业微信"],"keywords":"Serverless 企业微信,Serverless 基本功能,nCoV 疫情监控小助手","outdated":true},"wordCount":{"words":100,"sentences":26,"paragraphs":26},"fileAbsolutePath":"/opt/build/repo/content/best-practice/2020-02-06-serverless-work-weixin.md","fields":{"slug":"/best-practice/2020-02-06-serverless-work-weixin/","keywords":["php","python","serverless","云函数","font","echo","results","curl","info","color","responsesz"]}}},"pageContext":{"isCreatedByStatefulCreatePages":false,"blogId":"e4d496fa-1f43-52fe-97ed-a397afdf1f9c","previousBlogId":"0d3c3171-691f-55a7-9a9a-3a35d992f615","nextBlogId":"3f27165c-9176-5e1d-a427-9ffc801d5f2b"}}}