{"componentChunkName":"component---src-templates-best-practice-detail-tsx","path":"/best-practice/2020-02-08-flack-blog","result":{"data":{"currentBlog":{"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"]},"html":"<p>随着时间的发展，Serverless 架构越来越火热，其按量付费、弹性伸缩等诸多优质特性，让人眼前一亮，不得不惊叹云计算为我们带来的便利。</p>\n<p>但是就目前而言，与 Serverless 架构相关的业务框架还是比较少的。虽然腾讯云 Serverless 与 serverless.com 联手，已经支持了 Express、Koa、Egg 以及 Flask 等众多项目的轻松上云，但是实际使用过程中，尤其是迁移过程中还是很痛苦的，以这些框架在 Serverless 组件上的表现来看，POST/GET 的参数传输方法，原生获取比较难，这可能导致原有项目上云要经历较大的改造。</p>\n<p>当然，除了刚才说的原生框架直接部署在 Serverless 架构上，直接在 Serverless 架构开发的框架，也是少得可怜，所以本实践通过一个博客系统的开发，和大家简单地体验一下基于 Serverless 架构的博客系统是什么样的。</p>\n<h2 id=\"开发前的思考\"><a href=\"#%E5%BC%80%E5%8F%91%E5%89%8D%E7%9A%84%E6%80%9D%E8%80%83\" 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<ol>\n<li>博客系统需要哪些功能？本文仅仅是 demo 性质，所以功能比较少，只有两个页面。具有文章管理、分类管理、标签管理以及留言管理等功能。同时为了方便用户管理，要有前台和后台两部分。</li>\n<li>前台如何做？前台可能是用户流量比较大的（相对后台而言），所以这部分就是用单独的函数。每个功能一个函数，初步判断前台可能需要：获取文章分类，获取文章列表，获取评论列表，增加评论，获取标签列表等接口。</li>\n<li>后台如何做？后台理论上是管理员的专属地盘，所以这一部分流量比较小，可以通过 <code class=\"language-text\">flask-admin</code>，放入到一个函数中来解决。</li>\n<li>为什么前台要那么多函数，后台用一个框架？整个项目就用一个框架不好么？首先要回答，整个项目用一个框架也是可以的，但是并不好。例如这个项目的后台，使用的是 Flask 框架，用了 <code class=\"language-text\">flask-admin</code> 来做后台管理，这个开发过程很简单，可能整个后台就一百来行代码就搞定了，但是这涉及到：</li>\n<li>网页的返回，需要 APIGW 开启响应集成，响应集成的性能其实很差，所以相对来说，不太适合放在前端；</li>\n<li>一个完整项目比较大，可能需要的资源也会更多，那么我们就需要给这个函数更多的资源内存，可能会导致收费的增加，例如我的后台给的资源是 1024，我的前端每个函数给的内存资源是 128/256，在执行同样时间的时候，明显后者的费用降低了 4~8 倍。同样，函数可能涉及大冷启动，冷启动一个函数和冷启动函数中的一个完整的框架/项目，前者的速度和性能可能会更好一下；</li>\n<li>函数都有并发上限的，如果所有的资源全都请求到一个函数，那么很可能实际用户并发几个的时候，对用的函数并发就可能是几十几百，这很可能在用户稍微多一点的情况下，就会触及用户实例的上限限制，后台功能是非频繁功能，前台相对来说是更频繁的，所以前台是用单独接口更合理。</li>\n<li>登陆功能怎么做？非常抱歉，函数并不能像传统开发，将客户的一些登录信息缓存到机器上，但是客户端依旧可以使用 cookie，所以利用这个方法，可以做以下流程：</li>\n<li>后台登录入口处，拉取 APIGW 传过来的 APIGW Event，看其中 headers/cookie 是否存在，不存在就会返回登录页面；</li>\n<li>如果 headers/cookie 存在，取 cookie 中的 token 字段，判断 token 字段是否和服务端的 token 字段吻合，吻合进入系统后台，不吻合返回登录页面</li>\n<li>用户登录，请求后台的登陆功能，如果账号密码正确，则返回给用户一个 token，客户端将 token 记录到 cookie 中</li>\n<li>\n<p>问题来了：</p>\n<ul>\n<li>token 是什么？Token 可以认为是一个登录凭证，生成方法可以按照自己设计升级，本实践比较简单，就直接用账号密码组合，然后 md5。</li>\n<li>token 存在那里？下次如何获取？Token 可以存在 Mysql 数据库中，也可以存在 Redis 中，甚至可以存在 COS 中，例如 Redis 和 COS，都可以利用其自身的一些特性做一些额外的操作，例如数据有效期（用来做登录过期等）。当然本文不想做的那么麻烦，所以每次用户请求过来，都是单独计算 token，然后进行的对比。</li>\n<li>这种 token 登陆方法可以用于其他项目么？还是仅适用于这种博客系统。可以适用其他项目，很多项目都可以通过这种方法来做，例如我自己的 Anycodes，也是通过 Token 进行鉴权，只不过在 Serverless 架构下，Token 如何存储是一个问题，但是我个人推荐有钱就用 redis，没钱就用 cos，不想额外花钱就像我，每次是用单独对比。</li>\n<li>token 存在 redis 可以理解，但是存在 cos 是为什么？cos 本身是对象存储，用来存储文件的，其实完全可以用来存储 token，例如我们每次生成一个新的 token，都把这个 token 设置为一个文件，文件内容就是这个 token 对应的用户信息或者是权限信息，或者其他的信息，然后存储桶策略设置成文件过期时间，例如文件存入 1 天自动删除，那么 1 天之后，你存储的这个 token 文件就会被删除。等用户带着 token 过来的时候，直接通过内网请求 cos（没有流量费）获取指定文件名，如果获取到了就下载回来（文件一般也就 1K 或者以下），然后进行其他操作，不存在就证明用户已过期，或者 token 错误，让他重新登录就好了。当然，这种方法可能不是最优解，但是确实是在 Serverless 条件下的一个有趣的做法。可以在小项目中尝试使用。</li>\n</ul>\n</li>\n<li>项目本地开发如何进行调试？众所周知 Serverless 架构的本地调试很难。确实如此，虽然说本地调试很困难，但也不是不能越过去的，可以根据项目自己的需求，来做一些调试策略。</li>\n</ol>\n<h2 id=\"项目开发\"><a href=\"#%E9%A1%B9%E7%9B%AE%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>项目开发过程主要就是数据库的增删改查，为了更加适应 Serverless 架构下的项目开发，也为了提高项目的开发效率特总结了相关的开发技巧和经验。</p>\n<h3 id=\"数据库设计\"><a href=\"#%E6%95%B0%E6%8D%AE%E5%BA%93%E8%AE%BE%E8%AE%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>数据库设计</h3>\n<p>由于是做一个简单的博客，所以数据库相对设计比较简单，只有文章表、分类表以及标签表、评论表等，整体的 ER 图如下所示：</p>\n<p><img src=\"https://img.serverlesscloud.cn/202058/3-5-6.png\" alt=\"ER 图\"></p>\n<h3 id=\"本地开发与调试\"><a href=\"#%E6%9C%AC%E5%9C%B0%E5%BC%80%E5%8F%91%E4%B8%8E%E8%B0%83%E8%AF%95\" 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>本地开发与调试</h3>\n<p>对于开发调试，我在每个函数后面增加了对应触发器的调试方案，例如 APIGW 触发器，我增加了以下代码：</p>\n<div\n              class=\"gatsby-code-button-container\"\n              data-toaster-id=\"8423536031056655000\"\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(`def test():\n    event = {\n        &quot;requestContext&quot;: {\n            &quot;serviceId&quot;: &quot;service-f94sy04v&quot;,\n            &quot;path&quot;: &quot;/test/{path}&quot;,\n            &quot;httpMethod&quot;: &quot;POST&quot;,\n            &quot;requestId&quot;: &quot;c6af9ac6-7b61-11e6-9a41-93e8deadbeef&quot;,\n            &quot;identity&quot;: {\n                &quot;secretId&quot;: &quot;abdcdxxxxxxxsdfs&quot;\n            },\n            &quot;sourceIp&quot;: &quot;14.17.22.34&quot;,\n            &quot;stage&quot;: &quot;release&quot;\n        },\n        &quot;headers&quot;: {\n            &quot;Accept-Language&quot;: &quot;en-US,en,cn&quot;,\n            &quot;Accept&quot;: &quot;text/html,application/xml,application/json&quot;,\n            &quot;Host&quot;: &quot;service-3ei3tii4-251000691.ap-guangzhou.apigateway.myqloud.com&quot;,\n            &quot;User-Agent&quot;: &quot;User Agent String&quot;\n        },\n        &quot;body&quot;: json.dumps({&quot;id&quot;: 1}),\n         .... ....\n    }\n    print(main_handler(event, None))\n\n\nif __name__ == &quot;__main__&quot;:\n    test()`, `8423536031056655000`)\"\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\">def test():\n    event = {\n        &quot;requestContext&quot;: {\n            &quot;serviceId&quot;: &quot;service-f94sy04v&quot;,\n            &quot;path&quot;: &quot;/test/{path}&quot;,\n            &quot;httpMethod&quot;: &quot;POST&quot;,\n            &quot;requestId&quot;: &quot;c6af9ac6-7b61-11e6-9a41-93e8deadbeef&quot;,\n            &quot;identity&quot;: {\n                &quot;secretId&quot;: &quot;abdcdxxxxxxxsdfs&quot;\n            },\n            &quot;sourceIp&quot;: &quot;14.17.22.34&quot;,\n            &quot;stage&quot;: &quot;release&quot;\n        },\n        &quot;headers&quot;: {\n            &quot;Accept-Language&quot;: &quot;en-US,en,cn&quot;,\n            &quot;Accept&quot;: &quot;text/html,application/xml,application/json&quot;,\n            &quot;Host&quot;: &quot;service-3ei3tii4-251000691.ap-guangzhou.apigateway.myqloud.com&quot;,\n            &quot;User-Agent&quot;: &quot;User Agent String&quot;\n        },\n        &quot;body&quot;: json.dumps({&quot;id&quot;: 1}),\n         .... ....\n    }\n    print(main_handler(event, None))\n\n\nif __name__ == &quot;__main__&quot;:\n    test()</code></pre></div>\n<p>在实际上，我每次想要看一下运行效果，我都会执行这个文件：</p>\n<div\n              class=\"gatsby-code-button-container\"\n              data-toaster-id=\"69049261179635250000\"\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(`{'id': 1, 'title': '', 'watched': 1, 'category': '热点新闻', 'publish': '2020-02-13 00:45:52', 'tags': [], 'next': {}, 'pre': {}}\n{'uuid': '749ca9f6-4dfb-11ea-9c5b-acde48001122', 'error': False, 'message': ''}`, `69049261179635250000`)\"\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\">{&#39;id&#39;: 1, &#39;title&#39;: &#39;&#39;, &#39;watched&#39;: 1, &#39;category&#39;: &#39;热点新闻&#39;, &#39;publish&#39;: &#39;2020-02-13 00:45:52&#39;, &#39;tags&#39;: [], &#39;next&#39;: {}, &#39;pre&#39;: {}}\n{&#39;uuid&#39;: &#39;749ca9f6-4dfb-11ea-9c5b-acde48001122&#39;, &#39;error&#39;: False, &#39;message&#39;: &#39;&#39;}</code></pre></div>\n<p>可以认为，是在通过本地模拟一些线上环境。当然，如果有 redis 等一些需要内网资源的函数，就比较麻烦，但是我这做法，可以用于绝大部分函数。包括后台的 Flaks 框架部分：</p>\n<div class=\"gatsby-highlight\" data-language=\"text\"><pre class=\"language-text\"><code class=\"language-text\">def test():\n    event = {&#39;body&#39;: &#39;name=sdsadasdsadasd&amp;remark=&#39;, &#39;headerParameters&#39;: {}, &#39;headers&#39;: {\n        &#39;accept&#39;: &#39;text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9&#39;,\n        &#39;accept-encoding&#39;: &#39;gzip, deflate&#39;, &#39;accept-language&#39;: &#39;zh-CN,zh;q=0.9&#39;, &#39;cache-control&#39;: &#39;no-cache&#39;,\n        &#39;connection&#39;: &#39;keep-alive&#39;, &#39;content-length&#39;: &#39;27&#39;, &#39;content-type&#39;: &#39;application/x-www-form-urlencoded&#39;,\n        &#39;cookie&#39;: &#39;Hm_lvt_a0c900918361b31d762d9cf4dc81ee5b=1574491278,1575257377&#39;, &#39;endpoint-timeout&#39;: &#39;15&#39;,\n        &#39;host&#39;: &#39;blog.0duzhan.com&#39;, &#39;origin&#39;: &#39;http://blog.0duzhan.com&#39;, &#39;pragma&#39;: &#39;no-cache&#39;,\n        &#39;proxy-connection&#39;: &#39;keep-alive&#39;, &#39;referer&#39;: &#39;http://blog.0duzhan.com/admin/tag/new/?url=%2Fadmin%2Ftag%2F&#39;,\n        &#39;upgrade-insecure-requests&#39;: &#39;1&#39;,\n        &#39;user-agent&#39;: &#39;Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.130 Safari/537.36&#39;,\n        &#39;x-anonymous-consumer&#39;: &#39;true&#39;, &#39;x-api-requestid&#39;: &#39;656622f3b008a0d406a376809b03b52c&#39;,\n        &#39;x-b3-traceid&#39;: &#39;656622f3b008a0d406a376809b03b52c&#39;, &#39;x-qualifier&#39;: &#39;$LATEST&#39;}, &#39;httpMethod&#39;: &#39;POST&#39;,\n             &#39;path&#39;: &#39;/admin/tag/new/&#39;, &#39;pathParameters&#39;: {}, &#39;queryString&#39;: {&#39;url&#39;: &#39;/admin/tag/&#39;},\n             &#39;queryStringParameters&#39;: {},\n             &#39;requestContext&#39;: {&#39;httpMethod&#39;: &#39;ANY&#39;, &#39;identity&#39;: {}, &#39;path&#39;: &#39;/admin&#39;, &#39;serviceId&#39;: &#39;service-23ybmuq7&#39;,\n                                &#39;sourceIp&#39;: &#39;119.123.224.87&#39;, &#39;stage&#39;: &#39;release&#39;}}\n    print(main_handler(event, None))\n\n\nif __name__ == &quot;__main__&quot;:\n    test()</code></pre></div>\n<p>index 执行结果：</p>\n<div\n              class=\"gatsby-code-button-container\"\n              data-toaster-id=\"57603419070781990000\"\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(`{'body': 'name=sdsadasdsadasd&remark=', 'headerParameters': {}, 'headers': {'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9', 'accept-encoding': 'gzip, deflate', 'accept-language': 'zh-CN,zh;q=0.9', 'cache-control': 'no-cache', 'connection': 'keep-alive', 'content-length': '27', 'content-type': 'application/x-www-form-urlencoded', 'cookie': 'Hm_lvt_a0c900918361b31d762d9cf4dc81ee5b=1574491278,1575257377', 'endpoint-timeout': '15', 'host': 'blog.0duzhan.com', 'origin': 'http://blog.0duzhan.com', 'pragma': 'no-cache', 'proxy-connection': 'keep-alive', 'referer': 'http://blog.0duzhan.com/admin/tag/new/?url=%2Fadmin%2Ftag%2F', 'upgrade-insecure-requests': '1', 'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.130 Safari/537.36', 'x-anonymous-consumer': 'true', 'x-api-requestid': '656622f3b008a0d406a376809b03b52c', 'x-b3-traceid': '656622f3b008a0d406a376809b03b52c', 'x-qualifier': '\\$LATEST'}, 'httpMethod': 'POST', 'path': '/admin/tag/new/', 'pathParameters': {}, 'queryString': {'url': '/admin/tag/'}, 'queryStringParameters': {}, 'requestContext': {'httpMethod': 'ANY', 'identity': {}, 'path': '/admin', 'serviceId': 'service-23ybmuq7', 'sourceIp': '119.123.224.87', 'stage': 'release'}}\n{'isBase64Encoded': False, 'statusCode': 200, 'headers': {'Content-Type': 'text/html'}, 'body': '<!DOCTYPE html>\\n<html lang=&quot;en&quot;>\\n<head>\\n    <meta charset=&quot;UTF-8&quot;>\\n    <title>Title</title>\\n    <script>\\n        var url = window.location.href\\n        url = url.split(&quot;admin&quot;)[0] + &quot;admin&quot;\\n        String.prototype.endWith = function (s) {\\n            var d = this.length - s.length;\\n            return (d >= 0 && this.lastIndexOf(s) == d)\\n        }\\n        if (window.location.href != url) {\\n            if (!window.location.href.endsWith(&quot;admin&quot;) || !window.location.href.endsWith(&quot;admin/&quot;))\\n                window.location = url\\n        }\\n\\n        function doLogin() {\\n            var xmlhttp = window.XMLHttpRequest ? (new XMLHttpRequest()) : (new ActiveXObject(&quot;Microsoft.XMLHTTP&quot;))\\n            xmlhttp.onreadystatechange = function () {\\n                if (xmlhttp.readyState == 4 && xmlhttp.status == 200) {\\n                    if (JSON.parse(xmlhttp.responseText)[&quot;token&quot;]) {\\n                        document.cookie = &quot;token=&quot; + JSON.parse(xmlhttp.responseText)[&quot;token&quot;];\\n                        window.location = \\`http://\\${window.location.host}/admin\\`\\n                    } else {\\n                        alert(JSON.parse(xmlhttp.responseText)[&quot;message&quot;])\\n                    }\\n                }\\n            }\\n            xmlhttp.open(&quot;POST&quot;, window.location.pathname, true);\\n            xmlhttp.setRequestHeader(&quot;Content-type&quot;, &quot;application/json&quot;);\\n            xmlhttp.send(JSON.stringify({\\n                &quot;username&quot;: document.getElementById(&quot;username&quot;).value,\\n                &quot;password&quot;: document.getElementById(&quot;password&quot;).value,\\n            }));\\n        }\\n    </script>\\n</head>\\n<body>\\n\\n<center><h1>Serverless Blog 后台管理</h1>\\n    管理账号：<input type=&quot;text&quot; id=&quot;username&quot;><br>\\n    管理密码：<input type=&quot;password&quot; id=&quot;password&quot;><br>\\n    <input type=&quot;reset&quot;><input type=&quot;submit&quot; onclick=&quot;doLogin()&quot;><br>\\n</center>\\n</body>\\n</html>'}`, `57603419070781990000`)\"\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\">{&#39;body&#39;: &#39;name=sdsadasdsadasd&amp;remark=&#39;, &#39;headerParameters&#39;: {}, &#39;headers&#39;: {&#39;accept&#39;: &#39;text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9&#39;, &#39;accept-encoding&#39;: &#39;gzip, deflate&#39;, &#39;accept-language&#39;: &#39;zh-CN,zh;q=0.9&#39;, &#39;cache-control&#39;: &#39;no-cache&#39;, &#39;connection&#39;: &#39;keep-alive&#39;, &#39;content-length&#39;: &#39;27&#39;, &#39;content-type&#39;: &#39;application/x-www-form-urlencoded&#39;, &#39;cookie&#39;: &#39;Hm_lvt_a0c900918361b31d762d9cf4dc81ee5b=1574491278,1575257377&#39;, &#39;endpoint-timeout&#39;: &#39;15&#39;, &#39;host&#39;: &#39;blog.0duzhan.com&#39;, &#39;origin&#39;: &#39;http://blog.0duzhan.com&#39;, &#39;pragma&#39;: &#39;no-cache&#39;, &#39;proxy-connection&#39;: &#39;keep-alive&#39;, &#39;referer&#39;: &#39;http://blog.0duzhan.com/admin/tag/new/?url=%2Fadmin%2Ftag%2F&#39;, &#39;upgrade-insecure-requests&#39;: &#39;1&#39;, &#39;user-agent&#39;: &#39;Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.130 Safari/537.36&#39;, &#39;x-anonymous-consumer&#39;: &#39;true&#39;, &#39;x-api-requestid&#39;: &#39;656622f3b008a0d406a376809b03b52c&#39;, &#39;x-b3-traceid&#39;: &#39;656622f3b008a0d406a376809b03b52c&#39;, &#39;x-qualifier&#39;: &#39;$LATEST&#39;}, &#39;httpMethod&#39;: &#39;POST&#39;, &#39;path&#39;: &#39;/admin/tag/new/&#39;, &#39;pathParameters&#39;: {}, &#39;queryString&#39;: {&#39;url&#39;: &#39;/admin/tag/&#39;}, &#39;queryStringParameters&#39;: {}, &#39;requestContext&#39;: {&#39;httpMethod&#39;: &#39;ANY&#39;, &#39;identity&#39;: {}, &#39;path&#39;: &#39;/admin&#39;, &#39;serviceId&#39;: &#39;service-23ybmuq7&#39;, &#39;sourceIp&#39;: &#39;119.123.224.87&#39;, &#39;stage&#39;: &#39;release&#39;}}\n{&#39;isBase64Encoded&#39;: False, &#39;statusCode&#39;: 200, &#39;headers&#39;: {&#39;Content-Type&#39;: &#39;text/html&#39;}, &#39;body&#39;: &#39;&lt;!DOCTYPE html&gt;\\n&lt;html lang=&quot;en&quot;&gt;\\n&lt;head&gt;\\n    &lt;meta charset=&quot;UTF-8&quot;&gt;\\n    &lt;title&gt;Title&lt;/title&gt;\\n    &lt;script&gt;\\n        var url = window.location.href\\n        url = url.split(&quot;admin&quot;)[0] + &quot;admin&quot;\\n        String.prototype.endWith = function (s) {\\n            var d = this.length - s.length;\\n            return (d &gt;= 0 &amp;&amp; this.lastIndexOf(s) == d)\\n        }\\n        if (window.location.href != url) {\\n            if (!window.location.href.endsWith(&quot;admin&quot;) || !window.location.href.endsWith(&quot;admin/&quot;))\\n                window.location = url\\n        }\\n\\n        function doLogin() {\\n            var xmlhttp = window.XMLHttpRequest ? (new XMLHttpRequest()) : (new ActiveXObject(&quot;Microsoft.XMLHTTP&quot;))\\n            xmlhttp.onreadystatechange = function () {\\n                if (xmlhttp.readyState == 4 &amp;&amp; xmlhttp.status == 200) {\\n                    if (JSON.parse(xmlhttp.responseText)[&quot;token&quot;]) {\\n                        document.cookie = &quot;token=&quot; + JSON.parse(xmlhttp.responseText)[&quot;token&quot;];\\n                        window.location = `http://${window.location.host}/admin`\\n                    } else {\\n                        alert(JSON.parse(xmlhttp.responseText)[&quot;message&quot;])\\n                    }\\n                }\\n            }\\n            xmlhttp.open(&quot;POST&quot;, window.location.pathname, true);\\n            xmlhttp.setRequestHeader(&quot;Content-type&quot;, &quot;application/json&quot;);\\n            xmlhttp.send(JSON.stringify({\\n                &quot;username&quot;: document.getElementById(&quot;username&quot;).value,\\n                &quot;password&quot;: document.getElementById(&quot;password&quot;).value,\\n            }));\\n        }\\n    &lt;/script&gt;\\n&lt;/head&gt;\\n&lt;body&gt;\\n\\n&lt;center&gt;&lt;h1&gt;Serverless Blog 后台管理&lt;/h1&gt;\\n    管理账号：&lt;input type=&quot;text&quot; id=&quot;username&quot;&gt;&lt;br&gt;\\n    管理密码：&lt;input type=&quot;password&quot; id=&quot;password&quot;&gt;&lt;br&gt;\\n    &lt;input type=&quot;reset&quot;&gt;&lt;input type=&quot;submit&quot; onclick=&quot;doLogin()&quot;&gt;&lt;br&gt;\\n&lt;/center&gt;\\n&lt;/body&gt;\\n&lt;/html&gt;&#39;}</code></pre></div>\n<h3 id=\"flask部署\"><a href=\"#flask%E9%83%A8%E7%BD%B2\" aria-label=\"flask部署 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>Flask部署</h3>\n<p>Flask 部署到 Serverless 架构可以用 <code class=\"language-text\">@serverless/tencent-flask</code>，但是这里为了更加深入了解传统框架如何部署到 <code class=\"language-text\">Serverless</code> 架构，所以此处自行「造轮子」实现，先来看一张图：</p>\n<p><img src=\"https://img.serverlesscloud.cn/202058/3-5-7.png\"></p>\n<p>在通常情况下，我们使用 Flask 等框架实际上要通过 web<em>server，进入到下一个环节，而我们云函数更多是一个函数，本不需要启动 web server，所以我们就可以直接调用 `wsgi</em>app<code class=\"language-text\">这个方法，其中这里的 environ 就是我们刚才的通过对 event/context 等进行处理后的对象，</code>start_response` 可以认为是我们的一种特殊的数据结构，例如我们的 response 结构形态等。所以，如果我们自己想要实现这个过程，不使用腾讯云 flask-component，可以这样做：</p>\n<div\n              class=\"gatsby-code-button-container\"\n              data-toaster-id=\"78039286923560670000\"\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(`# -*- coding: utf-8 -*-\n# Copyright 2016 Matt Martz\n# All Rights Reserved.\n#\n#    Licensed under the Apache License, Version 2.0 (the &quot;License&quot;); you may\n#    not use this file except in compliance with the License. You may obtain\n#    a copy of the License at\n#\n#         http://www.apache.org/licenses/LICENSE-2.0\n#\n#    Unless required by applicable law or agreed to in writing, software\n#    distributed under the License is distributed on an &quot;AS IS&quot; BASIS, WITHOUT\n#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n#    License for the specific language governing permissions and limitations\n#    under the License.\n\nimport sys\nimport json\n\ntry:\n    from urllib import urlencode\nexcept ImportError:\n    from urllib.parse import urlencode\n\nfrom flask import Flask\n\ntry:\n    from cStringIO import StringIO\nexcept ImportError:\n    try:\n        from StringIO import StringIO\n    except ImportError:\n        from io import StringIO\n\nfrom werkzeug.wrappers import BaseRequest\n\n__version__ = '0.0.4'\n\n\ndef make_environ(event):\n    environ = {}\n\n    for hdr_name, hdr_value in event['headers'].items():\n        hdr_name = hdr_name.replace('-', '_').upper()\n        if hdr_name in ['CONTENT_TYPE', 'CONTENT_LENGTH']:\n            environ[hdr_name] = hdr_value\n            continue\n\n        http_hdr_name = 'HTTP_%s' % hdr_name\n        environ[http_hdr_name] = hdr_value\n\n    apigateway_qs = event['queryStringParameters']\n    request_qs = event['queryString']\n    qs = apigateway_qs.copy()\n    qs.update(request_qs)\n\n    body = ''\n    if 'body' in event:\n        body = event['body']\n\n    environ['REQUEST_METHOD'] = event['httpMethod']\n    environ['PATH_INFO'] = event['path']\n    environ['QUERY_STRING'] = urlencode(qs) if qs else ''\n    environ['REMOTE_ADDR'] = 80\n    environ['HOST'] = event['headers']['host']\n    environ['SCRIPT_NAME'] = ''\n\n    environ['SERVER_PORT'] = 80\n    environ['SERVER_PROTOCOL'] = 'HTTP/1.1'\n\n    environ['CONTENT_LENGTH'] = str(len(body))\n\n    environ['wsgi.url_scheme'] = ''\n    environ['wsgi.input'] = StringIO(body)\n    environ['wsgi.version'] = (1, 0)\n    environ['wsgi.errors'] = sys.stderr\n    environ['wsgi.multithread'] = False\n    environ['wsgi.run_once'] = True\n    environ['wsgi.multiprocess'] = False\n\n    BaseRequest(environ)\n\n    return environ\n\n\nclass LambdaResponse(object):\n    def __init__(self):\n        self.status = None\n        self.response_headers = None\n\n    def start_response(self, status, response_headers, exc_info=None):\n        self.status = int(status[:3])\n        self.response_headers = dict(response_headers)\n\n\nclass FlaskLambda(Flask):\n    def __call__(self, event, context):\n        if 'httpMethod' not in event:\n            print('httpMethod not in event')\n            # In this &quot;context&quot; \\`event\\` is \\`environ\\` and\n            # \\`context\\` is \\`start_response\\`, meaning the request didn't\n            # occur via API Gateway and Lambda\n            return super(FlaskLambda, self).__call__(event, context)\n\n        response = LambdaResponse()\n        # print response.start_response\n\n        body = next(self.wsgi_app(\n            make_environ(event),\n            response.start_response\n        ))\n\n        # return {\n        # &quot;isBase64Encoded&quot;: False,\n        # &quot;statusCode&quot;: 200,\n        # &quot;headers&quot;: {'Content-Type': 'text/html'},\n        # &quot;body&quot;: body\n        # }\n\n        return {\n            'statusCode': response.status,\n            'headers': response.response_headers,\n            'body': body\n        }`, `78039286923560670000`)\"\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=\"python\"><pre class=\"language-python\"><code class=\"language-python\"><span class=\"token comment\"># -*- coding: utf-8 -*-</span>\n<span class=\"token comment\"># Copyright 2016 Matt Martz</span>\n<span class=\"token comment\"># All Rights Reserved.</span>\n<span class=\"token comment\">#</span>\n<span class=\"token comment\">#    Licensed under the Apache License, Version 2.0 (the \"License\"); you may</span>\n<span class=\"token comment\">#    not use this file except in compliance with the License. You may obtain</span>\n<span class=\"token comment\">#    a copy of the License at</span>\n<span class=\"token comment\">#</span>\n<span class=\"token comment\">#         http://www.apache.org/licenses/LICENSE-2.0</span>\n<span class=\"token comment\">#</span>\n<span class=\"token comment\">#    Unless required by applicable law or agreed to in writing, software</span>\n<span class=\"token comment\">#    distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT</span>\n<span class=\"token comment\">#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the</span>\n<span class=\"token comment\">#    License for the specific language governing permissions and limitations</span>\n<span class=\"token comment\">#    under the License.</span>\n\n<span class=\"token keyword\">import</span> sys\n<span class=\"token keyword\">import</span> json\n\n<span class=\"token keyword\">try</span><span class=\"token punctuation\">:</span>\n    <span class=\"token keyword\">from</span> urllib <span class=\"token keyword\">import</span> urlencode\n<span class=\"token keyword\">except</span> ImportError<span class=\"token punctuation\">:</span>\n    <span class=\"token keyword\">from</span> urllib<span class=\"token punctuation\">.</span>parse <span class=\"token keyword\">import</span> urlencode\n\n<span class=\"token keyword\">from</span> flask <span class=\"token keyword\">import</span> Flask\n\n<span class=\"token keyword\">try</span><span class=\"token punctuation\">:</span>\n    <span class=\"token keyword\">from</span> cStringIO <span class=\"token keyword\">import</span> StringIO\n<span class=\"token keyword\">except</span> ImportError<span class=\"token punctuation\">:</span>\n    <span class=\"token keyword\">try</span><span class=\"token punctuation\">:</span>\n        <span class=\"token keyword\">from</span> StringIO <span class=\"token keyword\">import</span> StringIO\n    <span class=\"token keyword\">except</span> ImportError<span class=\"token punctuation\">:</span>\n        <span class=\"token keyword\">from</span> io <span class=\"token keyword\">import</span> StringIO\n\n<span class=\"token keyword\">from</span> werkzeug<span class=\"token punctuation\">.</span>wrappers <span class=\"token keyword\">import</span> BaseRequest\n\n__version__ <span class=\"token operator\">=</span> <span class=\"token string\">'0.0.4'</span>\n\n\n<span class=\"token keyword\">def</span> <span class=\"token function\">make_environ</span><span class=\"token punctuation\">(</span>event<span class=\"token punctuation\">)</span><span class=\"token punctuation\">:</span>\n    environ <span class=\"token operator\">=</span> <span class=\"token punctuation\">{</span><span class=\"token punctuation\">}</span>\n\n    <span class=\"token keyword\">for</span> hdr_name<span class=\"token punctuation\">,</span> hdr_value <span class=\"token keyword\">in</span> event<span class=\"token punctuation\">[</span><span class=\"token string\">'headers'</span><span class=\"token punctuation\">]</span><span class=\"token punctuation\">.</span>items<span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">:</span>\n        hdr_name <span class=\"token operator\">=</span> hdr_name<span class=\"token punctuation\">.</span>replace<span class=\"token punctuation\">(</span><span class=\"token string\">'-'</span><span class=\"token punctuation\">,</span> <span class=\"token string\">'_'</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">.</span>upper<span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span>\n        <span class=\"token keyword\">if</span> hdr_name <span class=\"token keyword\">in</span> <span class=\"token punctuation\">[</span><span class=\"token string\">'CONTENT_TYPE'</span><span class=\"token punctuation\">,</span> <span class=\"token string\">'CONTENT_LENGTH'</span><span class=\"token punctuation\">]</span><span class=\"token punctuation\">:</span>\n            environ<span class=\"token punctuation\">[</span>hdr_name<span class=\"token punctuation\">]</span> <span class=\"token operator\">=</span> hdr_value\n            <span class=\"token keyword\">continue</span>\n\n        http_hdr_name <span class=\"token operator\">=</span> <span class=\"token string\">'HTTP_%s'</span> <span class=\"token operator\">%</span> hdr_name\n        environ<span class=\"token punctuation\">[</span>http_hdr_name<span class=\"token punctuation\">]</span> <span class=\"token operator\">=</span> hdr_value\n\n    apigateway_qs <span class=\"token operator\">=</span> event<span class=\"token punctuation\">[</span><span class=\"token string\">'queryStringParameters'</span><span class=\"token punctuation\">]</span>\n    request_qs <span class=\"token operator\">=</span> event<span class=\"token punctuation\">[</span><span class=\"token string\">'queryString'</span><span class=\"token punctuation\">]</span>\n    qs <span class=\"token operator\">=</span> apigateway_qs<span class=\"token punctuation\">.</span>copy<span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span>\n    qs<span class=\"token punctuation\">.</span>update<span class=\"token punctuation\">(</span>request_qs<span class=\"token punctuation\">)</span>\n\n    body <span class=\"token operator\">=</span> <span class=\"token string\">''</span>\n    <span class=\"token keyword\">if</span> <span class=\"token string\">'body'</span> <span class=\"token keyword\">in</span> event<span class=\"token punctuation\">:</span>\n        body <span class=\"token operator\">=</span> event<span class=\"token punctuation\">[</span><span class=\"token string\">'body'</span><span class=\"token punctuation\">]</span>\n\n    environ<span class=\"token punctuation\">[</span><span class=\"token string\">'REQUEST_METHOD'</span><span class=\"token punctuation\">]</span> <span class=\"token operator\">=</span> event<span class=\"token punctuation\">[</span><span class=\"token string\">'httpMethod'</span><span class=\"token punctuation\">]</span>\n    environ<span class=\"token punctuation\">[</span><span class=\"token string\">'PATH_INFO'</span><span class=\"token punctuation\">]</span> <span class=\"token operator\">=</span> event<span class=\"token punctuation\">[</span><span class=\"token string\">'path'</span><span class=\"token punctuation\">]</span>\n    environ<span class=\"token punctuation\">[</span><span class=\"token string\">'QUERY_STRING'</span><span class=\"token punctuation\">]</span> <span class=\"token operator\">=</span> urlencode<span class=\"token punctuation\">(</span>qs<span class=\"token punctuation\">)</span> <span class=\"token keyword\">if</span> qs <span class=\"token keyword\">else</span> <span class=\"token string\">''</span>\n    environ<span class=\"token punctuation\">[</span><span class=\"token string\">'REMOTE_ADDR'</span><span class=\"token punctuation\">]</span> <span class=\"token operator\">=</span> <span class=\"token number\">80</span>\n    environ<span class=\"token punctuation\">[</span><span class=\"token string\">'HOST'</span><span class=\"token punctuation\">]</span> <span class=\"token operator\">=</span> event<span class=\"token punctuation\">[</span><span class=\"token string\">'headers'</span><span class=\"token punctuation\">]</span><span class=\"token punctuation\">[</span><span class=\"token string\">'host'</span><span class=\"token punctuation\">]</span>\n    environ<span class=\"token punctuation\">[</span><span class=\"token string\">'SCRIPT_NAME'</span><span class=\"token punctuation\">]</span> <span class=\"token operator\">=</span> <span class=\"token string\">''</span>\n\n    environ<span class=\"token punctuation\">[</span><span class=\"token string\">'SERVER_PORT'</span><span class=\"token punctuation\">]</span> <span class=\"token operator\">=</span> <span class=\"token number\">80</span>\n    environ<span class=\"token punctuation\">[</span><span class=\"token string\">'SERVER_PROTOCOL'</span><span class=\"token punctuation\">]</span> <span class=\"token operator\">=</span> <span class=\"token string\">'HTTP/1.1'</span>\n\n    environ<span class=\"token punctuation\">[</span><span class=\"token string\">'CONTENT_LENGTH'</span><span class=\"token punctuation\">]</span> <span class=\"token operator\">=</span> <span class=\"token builtin\">str</span><span class=\"token punctuation\">(</span><span class=\"token builtin\">len</span><span class=\"token punctuation\">(</span>body<span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span>\n\n    environ<span class=\"token punctuation\">[</span><span class=\"token string\">'wsgi.url_scheme'</span><span class=\"token punctuation\">]</span> <span class=\"token operator\">=</span> <span class=\"token string\">''</span>\n    environ<span class=\"token punctuation\">[</span><span class=\"token string\">'wsgi.input'</span><span class=\"token punctuation\">]</span> <span class=\"token operator\">=</span> StringIO<span class=\"token punctuation\">(</span>body<span class=\"token punctuation\">)</span>\n    environ<span class=\"token punctuation\">[</span><span class=\"token string\">'wsgi.version'</span><span class=\"token punctuation\">]</span> <span class=\"token operator\">=</span> <span class=\"token punctuation\">(</span><span class=\"token number\">1</span><span class=\"token punctuation\">,</span> <span class=\"token number\">0</span><span class=\"token punctuation\">)</span>\n    environ<span class=\"token punctuation\">[</span><span class=\"token string\">'wsgi.errors'</span><span class=\"token punctuation\">]</span> <span class=\"token operator\">=</span> sys<span class=\"token punctuation\">.</span>stderr\n    environ<span class=\"token punctuation\">[</span><span class=\"token string\">'wsgi.multithread'</span><span class=\"token punctuation\">]</span> <span class=\"token operator\">=</span> <span class=\"token boolean\">False</span>\n    environ<span class=\"token punctuation\">[</span><span class=\"token string\">'wsgi.run_once'</span><span class=\"token punctuation\">]</span> <span class=\"token operator\">=</span> <span class=\"token boolean\">True</span>\n    environ<span class=\"token punctuation\">[</span><span class=\"token string\">'wsgi.multiprocess'</span><span class=\"token punctuation\">]</span> <span class=\"token operator\">=</span> <span class=\"token boolean\">False</span>\n\n    BaseRequest<span class=\"token punctuation\">(</span>environ<span class=\"token punctuation\">)</span>\n\n    <span class=\"token keyword\">return</span> environ\n\n\n<span class=\"token keyword\">class</span> <span class=\"token class-name\">LambdaResponse</span><span class=\"token punctuation\">(</span><span class=\"token builtin\">object</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">:</span>\n    <span class=\"token keyword\">def</span> <span class=\"token function\">__init__</span><span class=\"token punctuation\">(</span>self<span class=\"token punctuation\">)</span><span class=\"token punctuation\">:</span>\n        self<span class=\"token punctuation\">.</span>status <span class=\"token operator\">=</span> <span class=\"token boolean\">None</span>\n        self<span class=\"token punctuation\">.</span>response_headers <span class=\"token operator\">=</span> <span class=\"token boolean\">None</span>\n\n    <span class=\"token keyword\">def</span> <span class=\"token function\">start_response</span><span class=\"token punctuation\">(</span>self<span class=\"token punctuation\">,</span> status<span class=\"token punctuation\">,</span> response_headers<span class=\"token punctuation\">,</span> exc_info<span class=\"token operator\">=</span><span class=\"token boolean\">None</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">:</span>\n        self<span class=\"token punctuation\">.</span>status <span class=\"token operator\">=</span> <span class=\"token builtin\">int</span><span class=\"token punctuation\">(</span>status<span class=\"token punctuation\">[</span><span class=\"token punctuation\">:</span><span class=\"token number\">3</span><span class=\"token punctuation\">]</span><span class=\"token punctuation\">)</span>\n        self<span class=\"token punctuation\">.</span>response_headers <span class=\"token operator\">=</span> <span class=\"token builtin\">dict</span><span class=\"token punctuation\">(</span>response_headers<span class=\"token punctuation\">)</span>\n\n\n<span class=\"token keyword\">class</span> <span class=\"token class-name\">FlaskLambda</span><span class=\"token punctuation\">(</span>Flask<span class=\"token punctuation\">)</span><span class=\"token punctuation\">:</span>\n    <span class=\"token keyword\">def</span> <span class=\"token function\">__call__</span><span class=\"token punctuation\">(</span>self<span class=\"token punctuation\">,</span> event<span class=\"token punctuation\">,</span> context<span class=\"token punctuation\">)</span><span class=\"token punctuation\">:</span>\n        <span class=\"token keyword\">if</span> <span class=\"token string\">'httpMethod'</span> <span class=\"token keyword\">not</span> <span class=\"token keyword\">in</span> event<span class=\"token punctuation\">:</span>\n            <span class=\"token keyword\">print</span><span class=\"token punctuation\">(</span><span class=\"token string\">'httpMethod not in event'</span><span class=\"token punctuation\">)</span>\n            <span class=\"token comment\"># In this \"context\" `event` is `environ` and</span>\n            <span class=\"token comment\"># `context` is `start_response`, meaning the request didn't</span>\n            <span class=\"token comment\"># occur via API Gateway and Lambda</span>\n            <span class=\"token keyword\">return</span> <span class=\"token builtin\">super</span><span class=\"token punctuation\">(</span>FlaskLambda<span class=\"token punctuation\">,</span> self<span class=\"token punctuation\">)</span><span class=\"token punctuation\">.</span>__call__<span class=\"token punctuation\">(</span>event<span class=\"token punctuation\">,</span> context<span class=\"token punctuation\">)</span>\n\n        response <span class=\"token operator\">=</span> LambdaResponse<span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span>\n        <span class=\"token comment\"># print response.start_response</span>\n\n        body <span class=\"token operator\">=</span> <span class=\"token builtin\">next</span><span class=\"token punctuation\">(</span>self<span class=\"token punctuation\">.</span>wsgi_app<span class=\"token punctuation\">(</span>\n            make_environ<span class=\"token punctuation\">(</span>event<span class=\"token punctuation\">)</span><span class=\"token punctuation\">,</span>\n            response<span class=\"token punctuation\">.</span>start_response\n        <span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span>\n\n        <span class=\"token comment\"># return {</span>\n        <span class=\"token comment\"># \"isBase64Encoded\": False,</span>\n        <span class=\"token comment\"># \"statusCode\": 200,</span>\n        <span class=\"token comment\"># \"headers\": {'Content-Type': 'text/html'},</span>\n        <span class=\"token comment\"># \"body\": body</span>\n        <span class=\"token comment\"># }</span>\n\n        <span class=\"token keyword\">return</span> <span class=\"token punctuation\">{</span>\n            <span class=\"token string\">'statusCode'</span><span class=\"token punctuation\">:</span> response<span class=\"token punctuation\">.</span>status<span class=\"token punctuation\">,</span>\n            <span class=\"token string\">'headers'</span><span class=\"token punctuation\">:</span> response<span class=\"token punctuation\">.</span>response_headers<span class=\"token punctuation\">,</span>\n            <span class=\"token string\">'body'</span><span class=\"token punctuation\">:</span> body\n        <span class=\"token punctuation\">}</span></code></pre></div>\n<p>这个代码，可以将 APIGW 过来的请求，变成请求集成的形式，传送给 Flask 框架，用户可以通过 <code class=\"language-text\">request.form</code> 来获取 post 内容，通过 <code class=\"language-text\">request.args</code> 获取 get 内容等。</p>\n<h3 id=\"全局变量\"><a href=\"#%E5%85%A8%E5%B1%80%E5%8F%98%E9%87%8F\" 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>全局变量</h3>\n<p>全局变量可能包括用户账号，密码，云的密钥信息，数据库信息等，为了统一配置和修改，可以使用我自己写的全局变量组件：</p>\n<div\n              class=\"gatsby-code-button-container\"\n              data-toaster-id=\"26227883321649627000\"\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(`# 函数们的整体配置信息\nConf:\n  component: &quot;serverless-global&quot;\n  inputs:\n    region: ap-shanghai\n    runtime: Python3.6\n    handler: index.main_handler\n    include_common: ./common\n    blog_user: Dfounder\n    blog_email: service@anycodes.cn\n    blog_about_me: 这就是我的博客\n    blog_host: blog.0duzhan.com\n    website_title: Serverless Blog System\n    website_keywords: Serverless, Serverless Framework, Tencent Cloud, SCF\n    website_description: 一款基于腾讯云Serverless架构，并且采用Serverless Framework构建的Serverless博客系统。\n    website_bucket: serverless-blog-1256773370\n    mysql_host:\n    mysql_user: root\n    mysql_password:\n    mysql_port: 60510\n    mysql_db: serverless_blog_system\n    admin_user: mytest\n    admin_password: mytestabc\n    tencent_secret_id:\n    tencent_secret_key:\n    tencent_appid:`, `26227883321649627000`)\"\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\"># 函数们的整体配置信息\nConf:\n  component: &quot;serverless-global&quot;\n  inputs:\n    region: ap-shanghai\n    runtime: Python3.6\n    handler: index.main_handler\n    include_common: ./common\n    blog_user: Dfounder\n    blog_email: service@anycodes.cn\n    blog_about_me: 这就是我的博客\n    blog_host: blog.0duzhan.com\n    website_title: Serverless Blog System\n    website_keywords: Serverless, Serverless Framework, Tencent Cloud, SCF\n    website_description: 一款基于腾讯云Serverless架构，并且采用Serverless Framework构建的Serverless博客系统。\n    website_bucket: serverless-blog-1256773370\n    mysql_host:\n    mysql_user: root\n    mysql_password:\n    mysql_port: 60510\n    mysql_db: serverless_blog_system\n    admin_user: mytest\n    admin_password: mytestabc\n    tencent_secret_id:\n    tencent_secret_key:\n    tencent_appid:</code></pre></div>\n<p>在使用的时候，可以直接用，例如函数：</p>\n<div\n              class=\"gatsby-code-button-container\"\n              data-toaster-id=\"37779547222795620000\"\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(`Blog_Web_addComment:\n  component: &quot;@serverless/tencent-scf&quot;\n  inputs:\n    name: Blog_Web_addComment\n    description: 添加评论\n    codeUri: ./cloudFunctions/addComment\n    handler: \\${Conf.handler}\n    runtime: \\${Conf.runtime}\n    region:  \\${Conf.region}\n    include:\n      - \\${Conf.include_common}\n    environment:\n      variables:\n        mysql_host: \\${Conf.mysql_host}\n        mysql_port: \\${Conf.mysql_port}\n        mysql_user: \\${Conf.mysql_user}\n        mysql_password: \\${Conf.mysql_password}\n        mysql_db: \\${Conf.mysql_db}`, `37779547222795620000`)\"\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\">Blog_Web_addComment:\n  component: &quot;@serverless/tencent-scf&quot;\n  inputs:\n    name: Blog_Web_addComment\n    description: 添加评论\n    codeUri: ./cloudFunctions/addComment\n    handler: ${Conf.handler}\n    runtime: ${Conf.runtime}\n    region:  ${Conf.region}\n    include:\n      - ${Conf.include_common}\n    environment:\n      variables:\n        mysql_host: ${Conf.mysql_host}\n        mysql_port: ${Conf.mysql_port}\n        mysql_user: ${Conf.mysql_user}\n        mysql_password: ${Conf.mysql_password}\n        mysql_db: ${Conf.mysql_db}</code></pre></div>\n<h3 id=\"项目初始化\"><a href=\"#%E9%A1%B9%E7%9B%AE%E5%88%9D%E5%A7%8B%E5%8C%96\" 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>项目初始化</h3>\n<p>为了让项目更容易初始化，例如我修改网站的名字，描述，关键词，或者我需要建立数据库等。所以这个时候我单独做了一个 init 文件：</p>\n<div\n              class=\"gatsby-code-button-container\"\n              data-toaster-id=\"33059189108757893000\"\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(`# -*- coding: utf8 -*-\nimport pymysql\nimport shutil\nimport yaml\nimport os\n\n\ndef setEnv():\n    try:\n        file = open(&quot;./serverless.yaml&quot;, 'r', encoding=&quot;utf-8&quot;)\n        file_data = file.read()\n        file.close()\n\n        data = yaml.load(file_data)\n        for eveKey, eveValue in data['Conf']['inputs'].items():\n            os.environ[eveKey] = str(eveValue)\n        return True\n    except Exception as e:\n        raise e\n\n\ndef initDb():\n    try:\n        conn = pymysql.connect(host=os.environ.get('mysql_host'),\n                               user=os.environ.get('mysql_user'),\n                               password=os.environ.get('mysql_password'),\n                               port=int(os.environ.get('mysql_port')),\n                               charset='utf8')\n        cursor = conn.cursor()\n        sql = &quot;CREATE DATABASE IF NOT EXISTS {db_name}&quot;.format(db_name=os.environ.get('mysql_db'))\n        cursor.execute(sql)\n        cursor.close()\n        conn.close()\n        return True\n    except Exception as e:\n        raise e\n\n\ndef initTable():\n    try:\n        conn = pymysql.connect(host=os.environ.get('mysql_host'),\n                               user=os.environ.get('mysql_user'),\n                               password=os.environ.get('mysql_password'),\n                               port=int(os.environ.get('mysql_port')),\n                               db=os.environ.get('mysql_db'),\n                               charset='utf8',\n                               cursorclass=pymysql.cursors.DictCursor,\n                               autocommit=1)\n        cursor = conn.cursor()\n        createTags = &quot;CREATE TABLE \\`tags\\` ( \\`tid\\` INT NOT NULL AUTO_INCREMENT , \\`name\\` VARCHAR(255) NOT NULL , \\`remark\\` TEXT NULL , PRIMARY KEY (\\`tid\\`), UNIQUE (\\`name\\`)) ENGINE = InnoDB;&quot;\n        createCategory = &quot;CREATE TABLE \\`category\\` ( \\`cid\\` INT NOT NULL AUTO_INCREMENT , \\`name\\` VARCHAR(255) NOT NULL , \\`sorted\\` INT NOT NULL DEFAULT '1' , \\`remark\\` TEXT NULL , PRIMARY KEY (\\`cid\\`), UNIQUE (\\`name\\`)) ENGINE = InnoDB;&quot;\n        createComments = &quot;CREATE TABLE \\`comments\\` ( \\`cid\\` INT NOT NULL AUTO_INCREMENT , \\`content\\` TEXT NOT NULL , \\`publish\\` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP , \\`user\\` VARCHAR(255) NOT NULL , \\`email\\` VARCHAR(255) NULL , \\`photo\\` INT NOT NULL DEFAULT '0' ,  \\`article\\` INT NOT NULL , \\`remark\\` TEXT NULL , \\`uni_mark\\` VARCHAR(255) NOT NULL , \\`is_show\\` INT NOT NULL DEFAULT '0' , PRIMARY KEY (\\`cid\\`), UNIQUE (\\`uni_mark\\`)) ENGINE = InnoDB;&quot;\n        createArticle = &quot;CREATE TABLE \\`article\\` ( \\`aid\\` INT NOT NULL AUTO_INCREMENT , \\`title\\` VARCHAR(255) NOT NULL , \\`content\\` TEXT NOT NULL , \\`description\\` TEXT NOT NULL , \\`publish\\` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP , \\`watched\\` INT NOT NULL DEFAULT '0' , \\`category\\` INT NOT NULL , \\`remark\\` TEXT NULL , PRIMARY KEY (\\`aid\\`)) ENGINE = InnoDB;&quot;\n        createArticleTags = &quot;CREATE TABLE \\`article_tags\\` ( \\`atid\\` INT NOT NULL AUTO_INCREMENT , \\`aid\\` INT NOT NULL , \\`tid\\` INT NOT NULL , PRIMARY KEY (\\`atid\\`)) ENGINE = InnoDB;&quot;\n        alertArticleTagsArticle = &quot;ALTER TABLE \\`article_tags\\` ADD CONSTRAINT \\`article\\` FOREIGN KEY (\\`aid\\`) REFERENCES \\`article\\`(\\`aid\\`) ON DELETE CASCADE ON UPDATE CASCADE; &quot;\n        alertArticleTagsTags = &quot;ALTER TABLE \\`article_tags\\` ADD CONSTRAINT \\`tags\\` FOREIGN KEY (\\`tid\\`) REFERENCES \\`tags\\`(\\`tid\\`) ON DELETE CASCADE ON UPDATE CASCADE;&quot;\n        alertArticleCategory = &quot;ALTER TABLE \\`article\\` ADD CONSTRAINT \\`category\\` FOREIGN KEY (\\`category\\`) REFERENCES \\`category\\`(\\`cid\\`) ON DELETE CASCADE ON UPDATE CASCADE;&quot;\n        alertCommentsArticle = &quot;ALTER TABLE \\`comments\\` ADD CONSTRAINT \\`article_comments\\` FOREIGN KEY (\\`article\\`) REFERENCES \\`article\\`(\\`aid\\`) ON DELETE CASCADE ON UPDATE CASCADE;&quot;\n        cursor.execute(createTags)\n        cursor.execute(createCategory)\n        cursor.execute(createComments)\n        cursor.execute(createArticle)\n        cursor.execute(createArticleTags)\n        cursor.execute(alertArticleTagsArticle)\n        cursor.execute(alertArticleTagsTags)\n        cursor.execute(alertArticleCategory)\n        cursor.execute(alertCommentsArticle)\n        cursor.close()\n        conn.close()\n        return True\n    except Exception as e:\n        raise e\n\n\ndef initHTML():\n    try:\n        tempPath = &quot;website&quot;\n        tempDist = os.path.join(tempPath, &quot;dist&quot;)\n        if os.path.exists(tempDist):\n            shutil.rmtree(tempDist)\n        tempFileList = []\n        for eve in os.walk(tempPath):\n            if eve[2]:\n                for eveFile in eve[2]:\n                    tempFileList.append(os.path.join(eve[0], eveFile))\n        os.mkdir(tempDist)\n        for eve in tempFileList:\n            temp = os.path.split(eve.replace(tempPath, tempDist))\n            if not os.path.exists(temp[0]):\n                os.makedirs(temp[0])\n            if eve.endswith(&quot;.html&quot;) or eve.endswith(&quot;.htm&quot;):\n                with open(eve) as readData:\n                    with open(eve.replace(tempPath, tempDist), &quot;w&quot;) as writeData:\n                        writeData.write(readData.read().\n                                        replace('{{ user }}', os.environ.get('blog_user')).\n                                        replace('{{ email }}', os.environ.get('blog_email')).\n                                        replace('{{ title }}', os.environ.get('website_title')).\n                                        replace('{{ keywords }}', os.environ.get('website_keywords')).\n                                        replace('{{ about_me }}', os.environ.get('blog_about_me')).\n                                        replace('{{ host }}', os.environ.get('blog_host')).\n                                        replace('{{ description }}', os.environ.get('website_description')))\n            else:\n                shutil.copy(eve, eve.replace(tempPath, tempDist))\n        return True\n    except Exception as e:\n        raise e\n\n\nif __name__ == &quot;__main__&quot;:\n    print(&quot;获取Yaml数据： &quot;, setEnv())\n    print(&quot;建立数据库：&quot;, initDb())\n    print(&quot;建立数据库：&quot;, initTable())\n    print(&quot;初始化HTML：&quot;, initHTML())`, `33059189108757893000`)\"\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\"># -*- coding: utf8 -*-\nimport pymysql\nimport shutil\nimport yaml\nimport os\n\n\ndef setEnv():\n    try:\n        file = open(&quot;./serverless.yaml&quot;, &#39;r&#39;, encoding=&quot;utf-8&quot;)\n        file_data = file.read()\n        file.close()\n\n        data = yaml.load(file_data)\n        for eveKey, eveValue in data[&#39;Conf&#39;][&#39;inputs&#39;].items():\n            os.environ[eveKey] = str(eveValue)\n        return True\n    except Exception as e:\n        raise e\n\n\ndef initDb():\n    try:\n        conn = pymysql.connect(host=os.environ.get(&#39;mysql_host&#39;),\n                               user=os.environ.get(&#39;mysql_user&#39;),\n                               password=os.environ.get(&#39;mysql_password&#39;),\n                               port=int(os.environ.get(&#39;mysql_port&#39;)),\n                               charset=&#39;utf8&#39;)\n        cursor = conn.cursor()\n        sql = &quot;CREATE DATABASE IF NOT EXISTS {db_name}&quot;.format(db_name=os.environ.get(&#39;mysql_db&#39;))\n        cursor.execute(sql)\n        cursor.close()\n        conn.close()\n        return True\n    except Exception as e:\n        raise e\n\n\ndef initTable():\n    try:\n        conn = pymysql.connect(host=os.environ.get(&#39;mysql_host&#39;),\n                               user=os.environ.get(&#39;mysql_user&#39;),\n                               password=os.environ.get(&#39;mysql_password&#39;),\n                               port=int(os.environ.get(&#39;mysql_port&#39;)),\n                               db=os.environ.get(&#39;mysql_db&#39;),\n                               charset=&#39;utf8&#39;,\n                               cursorclass=pymysql.cursors.DictCursor,\n                               autocommit=1)\n        cursor = conn.cursor()\n        createTags = &quot;CREATE TABLE `tags` ( `tid` INT NOT NULL AUTO_INCREMENT , `name` VARCHAR(255) NOT NULL , `remark` TEXT NULL , PRIMARY KEY (`tid`), UNIQUE (`name`)) ENGINE = InnoDB;&quot;\n        createCategory = &quot;CREATE TABLE `category` ( `cid` INT NOT NULL AUTO_INCREMENT , `name` VARCHAR(255) NOT NULL , `sorted` INT NOT NULL DEFAULT &#39;1&#39; , `remark` TEXT NULL , PRIMARY KEY (`cid`), UNIQUE (`name`)) ENGINE = InnoDB;&quot;\n        createComments = &quot;CREATE TABLE `comments` ( `cid` INT NOT NULL AUTO_INCREMENT , `content` TEXT NOT NULL , `publish` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP , `user` VARCHAR(255) NOT NULL , `email` VARCHAR(255) NULL , `photo` INT NOT NULL DEFAULT &#39;0&#39; ,  `article` INT NOT NULL , `remark` TEXT NULL , `uni_mark` VARCHAR(255) NOT NULL , `is_show` INT NOT NULL DEFAULT &#39;0&#39; , PRIMARY KEY (`cid`), UNIQUE (`uni_mark`)) ENGINE = InnoDB;&quot;\n        createArticle = &quot;CREATE TABLE `article` ( `aid` INT NOT NULL AUTO_INCREMENT , `title` VARCHAR(255) NOT NULL , `content` TEXT NOT NULL , `description` TEXT NOT NULL , `publish` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP , `watched` INT NOT NULL DEFAULT &#39;0&#39; , `category` INT NOT NULL , `remark` TEXT NULL , PRIMARY KEY (`aid`)) ENGINE = InnoDB;&quot;\n        createArticleTags = &quot;CREATE TABLE `article_tags` ( `atid` INT NOT NULL AUTO_INCREMENT , `aid` INT NOT NULL , `tid` INT NOT NULL , PRIMARY KEY (`atid`)) ENGINE = InnoDB;&quot;\n        alertArticleTagsArticle = &quot;ALTER TABLE `article_tags` ADD CONSTRAINT `article` FOREIGN KEY (`aid`) REFERENCES `article`(`aid`) ON DELETE CASCADE ON UPDATE CASCADE; &quot;\n        alertArticleTagsTags = &quot;ALTER TABLE `article_tags` ADD CONSTRAINT `tags` FOREIGN KEY (`tid`) REFERENCES `tags`(`tid`) ON DELETE CASCADE ON UPDATE CASCADE;&quot;\n        alertArticleCategory = &quot;ALTER TABLE `article` ADD CONSTRAINT `category` FOREIGN KEY (`category`) REFERENCES `category`(`cid`) ON DELETE CASCADE ON UPDATE CASCADE;&quot;\n        alertCommentsArticle = &quot;ALTER TABLE `comments` ADD CONSTRAINT `article_comments` FOREIGN KEY (`article`) REFERENCES `article`(`aid`) ON DELETE CASCADE ON UPDATE CASCADE;&quot;\n        cursor.execute(createTags)\n        cursor.execute(createCategory)\n        cursor.execute(createComments)\n        cursor.execute(createArticle)\n        cursor.execute(createArticleTags)\n        cursor.execute(alertArticleTagsArticle)\n        cursor.execute(alertArticleTagsTags)\n        cursor.execute(alertArticleCategory)\n        cursor.execute(alertCommentsArticle)\n        cursor.close()\n        conn.close()\n        return True\n    except Exception as e:\n        raise e\n\n\ndef initHTML():\n    try:\n        tempPath = &quot;website&quot;\n        tempDist = os.path.join(tempPath, &quot;dist&quot;)\n        if os.path.exists(tempDist):\n            shutil.rmtree(tempDist)\n        tempFileList = []\n        for eve in os.walk(tempPath):\n            if eve[2]:\n                for eveFile in eve[2]:\n                    tempFileList.append(os.path.join(eve[0], eveFile))\n        os.mkdir(tempDist)\n        for eve in tempFileList:\n            temp = os.path.split(eve.replace(tempPath, tempDist))\n            if not os.path.exists(temp[0]):\n                os.makedirs(temp[0])\n            if eve.endswith(&quot;.html&quot;) or eve.endswith(&quot;.htm&quot;):\n                with open(eve) as readData:\n                    with open(eve.replace(tempPath, tempDist), &quot;w&quot;) as writeData:\n                        writeData.write(readData.read().\n                                        replace(&#39;{{ user }}&#39;, os.environ.get(&#39;blog_user&#39;)).\n                                        replace(&#39;{{ email }}&#39;, os.environ.get(&#39;blog_email&#39;)).\n                                        replace(&#39;{{ title }}&#39;, os.environ.get(&#39;website_title&#39;)).\n                                        replace(&#39;{{ keywords }}&#39;, os.environ.get(&#39;website_keywords&#39;)).\n                                        replace(&#39;{{ about_me }}&#39;, os.environ.get(&#39;blog_about_me&#39;)).\n                                        replace(&#39;{{ host }}&#39;, os.environ.get(&#39;blog_host&#39;)).\n                                        replace(&#39;{{ description }}&#39;, os.environ.get(&#39;website_description&#39;)))\n            else:\n                shutil.copy(eve, eve.replace(tempPath, tempDist))\n        return True\n    except Exception as e:\n        raise e\n\n\nif __name__ == &quot;__main__&quot;:\n    print(&quot;获取Yaml数据： &quot;, setEnv())\n    print(&quot;建立数据库：&quot;, initDb())\n    print(&quot;建立数据库：&quot;, initTable())\n    print(&quot;初始化HTML：&quot;, initHTML())</code></pre></div>\n<h3 id=\"公共组件的开发\"><a href=\"#%E5%85%AC%E5%85%B1%E7%BB%84%E4%BB%B6%E7%9A%84%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>公共组件的开发</h3>\n<p>在项目中会有很多公共组件，例如数据库的部分，所以我把数据库的代码，统一放到了一起：<code class=\"language-text\">common/mysqlCommon.py</code>:</p>\n<div\n              class=\"gatsby-code-button-container\"\n              data-toaster-id=\"39326352576652890000\"\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(`# -*- coding: utf8 -*-\n\nimport os\nimport re\nimport pymysql\nimport hashlib\nfrom random import choice\n\n\nclass mysqlCommon:\n    def __init__(self):\n        self.getConnection({\n            &quot;host&quot;: os.environ.get('mysql_host'),\n            &quot;user&quot;: os.environ.get('mysql_user'),\n            &quot;port&quot;: int(os.environ.get('mysql_port')),\n            &quot;db&quot;: os.environ.get('mysql_db'),\n            &quot;password&quot;: os.environ.get('mysql_password')\n        })\n\n    def getDefaultPic(self):\n        return choice([\n            'http://t8.baidu.com/it/u=1484500186,1503043093&fm=79&app=86&f=JPEG?w=1280&h=853',\n            'http://t8.baidu.com/it/u=2247852322,986532796&fm=79&app=86&f=JPEG?w=1280&h=853',\n            'http://t7.baidu.com/it/u=3204887199,3790688592&fm=79&app=86&f=JPEG?w=4610&h=2968',\n            'http://t9.baidu.com/it/u=3363001160,1163944807&fm=79&app=86&f=JPEG?w=1280&h=830',\n            'http://t9.baidu.com/it/u=583874135,70653437&fm=79&app=86&f=JPEG?w=3607&h=2408',\n            'http://t9.baidu.com/it/u=583874135,70653437&fm=79&app=86&f=JPEG?w=3607&h=2408',\n            'http://t9.baidu.com/it/u=1307125826,3433407105&fm=79&app=86&f=JPEG?w=5760&h=3240',\n            'http://t9.baidu.com/it/u=2268908537,2815455140&fm=79&app=86&f=JPEG?w=1280&h=719',\n            'http://t7.baidu.com/it/u=1179872664,290201490&fm=79&app=86&f=JPEG?w=1280&h=854',\n            'http://t9.baidu.com/it/u=3949188917,63856583&fm=79&app=86&f=JPEG?w=1280&h=875',\n            'http://t9.baidu.com/it/u=2266751744,4253267866&fm=79&app=86&f=JPEG?w=1280&h=854',\n            'http://t8.baidu.com/it/u=4100756023,1345858297&fm=79&app=86&f=JPEG?w=1280&h=854',\n            'http://t7.baidu.com/it/u=1355385882,1155324943&fm=79&app=86&f=JPEG?w=1280&h=854',\n            'http://t9.baidu.com/it/u=2292037961,3689236171&fm=79&app=86&f=JPEG?w=1280&h=854',\n            'http://t9.baidu.com/it/u=4241966675,2405819829&fm=79&app=86&f=JPEG?w=1280&h=854',\n            'http://t8.baidu.com/it/u=2857883419,1187496708&fm=79&app=86&f=JPEG?w=1280&h=763',\n            'http://t8.baidu.com/it/u=198337120,441348595&fm=79&app=86&f=JPEG?w=1280&h=732'\n        ])\n\n    def getConnection(self, conf):\n        self.connection = pymysql.connect(host=conf['host'],\n                                          user=conf['user'],\n                                          password=conf['password'],\n                                          port=int(conf['port']),\n                                          db=conf['db'],\n                                          charset='utf8',\n                                          cursorclass=pymysql.cursors.DictCursor,\n                                          autocommit=1)\n\n    def doAction(self, stmt, data):\n        try:\n            self.connection.ping(reconnect=True)\n            cursor = self.connection.cursor()\n            cursor.execute(stmt, data)\n            result = cursor\n            cursor.close()\n            return result\n        except Exception as e:\n            print(e)\n            try:\n                cursor.close()\n            except:\n                pass\n            return False\n\n    def getCategoryList(self):\n        search_stmt = (\n            &quot;SELECT * FROM \\`category\\` ORDER BY \\`sorted\\`&quot;\n        )\n        result = self.doAction(search_stmt, ())\n        if result == False:\n            return False\n        return [{&quot;id&quot;: eveCategory['cid'], &quot;name&quot;: eveCategory['name']} for eveCategory in result.fetchall()]\n\n    def getArticleList(self, category, tag, page=1):\n        if category:\n            search_stmt = (\n                &quot;SELECT article.*,category.name FROM \\`article\\` LEFT JOIN \\`category\\` ON article.category=category.cid WHERE article.category=%s ORDER BY -article.aid LIMIT %s,%s;&quot;\n            )\n            count_stmt = (\n                &quot;SELECT COUNT(*) FROM \\`article\\` LEFT JOIN \\`category\\` ON article.category=category.cid WHERE article.category=%s;&quot;\n            )\n            data = (category, 10 * (int(page) - 1), 10 * int(page))\n            count_data = (category,)\n        elif tag:\n            search_stmt = (\n                &quot;SELECT article.* FROM \\`article\\` LEFT JOIN \\`article_tags\\` ON article.aid=article_tags.aid WHERE article_tags.tid=%s ORDER BY -article.aid LIMIT %s,%s;&quot;\n            )\n            count_stmt = (\n                &quot;SELECT COUNT(*) FROM \\`article\\`LEFT JOIN \\`article_tags\\` ON article.aid=article_tags.aid WHERE article_tags.tid=%s;&quot;\n            )\n            data = (tag, 10 * (int(page) - 1), 10 * int(page))\n            count_data = (tag,)\n        else:\n            search_stmt = (\n                &quot;SELECT article.*,category.name FROM \\`article\\` LEFT JOIN \\`category\\` ON article.category=category.cid ORDER BY -article.aid LIMIT %s,%s;&quot;\n            )\n            count_stmt = (\n                &quot;SELECT COUNT(*) FROM \\`article\\` LEFT JOIN \\`category\\` ON article.category=category.cid; &quot;\n            )\n            data = (10 * (int(page) - 1), 10 * int(page))\n            count_data = ()\n        result = self.doAction(search_stmt, data)\n        if result == False:\n            return False\n\n        return {&quot;data&quot;: [{&quot;id&quot;: eveArticle['aid'],\n                          &quot;title&quot;: eveArticle['title'],\n                          &quot;description&quot;: eveArticle['description'],\n                          &quot;watched&quot;: eveArticle['watched'],\n                          &quot;category&quot;: eveArticle['category'],\n                          &quot;publish&quot;: str(eveArticle['publish']),\n                          &quot;picture&quot;: self.getPicture(eveArticle['content'])}\n                         for eveArticle in result.fetchall()],\n                &quot;count&quot;: self.doAction(count_stmt, count_data).fetchone()[&quot;COUNT(*)&quot;]}\n\n    def getHotArticleList(self):\n        search_stmt = (\n            &quot;SELECT article.*,category.name FROM \\`article\\` LEFT JOIN \\`category\\` ON article.category=category.cid ORDER BY article.watched LIMIT 0,5&quot;\n        )\n        result = self.doAction(search_stmt, ())\n        if result == False:\n            return False\n        return [{&quot;id&quot;: eveArticle['aid'],\n                 &quot;title&quot;: eveArticle['title'],\n                 &quot;description&quot;: eveArticle['description'],\n                 &quot;watched&quot;: eveArticle['watched'],\n                 &quot;category&quot;: eveArticle['category'],\n                 &quot;publish&quot;: str(eveArticle['publish']),\n                 &quot;picture&quot;: self.getPicture(eveArticle['content'])}\n                for\n                eveArticle in result.fetchall()]\n\n    def getTagsArticle(self, aid):\n        search_stmt = (\n            &quot;SELECT tags.name, tags.tid FROM \\`article_tags\\` LEFT JOIN \\`tags\\` ON article_tags.tid=tags.tid WHERE article_tags.aid=%s;&quot;\n        )\n        result = self.doAction(search_stmt, (aid,))\n        if result == False:\n            return False\n        return [{&quot;id&quot;: eveTag[&quot;tid&quot;], &quot;name&quot;: eveTag[&quot;name&quot;]} for eveTag in result.fetchall()]\n\n    def getTagsList(self):\n        search_stmt = (\n            &quot;SELECT * FROM tags ORDER BY RAND() LIMIT 20; &quot;\n        )\n        result = self.doAction(search_stmt, ())\n        if result == False:\n            return False\n        return [{&quot;id&quot;: eveTag['tid'], &quot;name&quot;: eveTag['name']} for eveTag in result.fetchall()]\n\n    def getArticleContent(self, aid):\n        search_stmt = (\n            &quot;SELECT article.*, category.name FROM \\`category\\` LEFT JOIN \\`article\\` ON category.cid=article.category WHERE article.aid=%s;&quot;\n        )\n        result = self.doAction(search_stmt, (aid))\n        if result == False:\n            return False\n        article = result.fetchone()\n        return {\n            &quot;id&quot;: article[&quot;aid&quot;],\n            &quot;title&quot;: article[&quot;title&quot;],\n            &quot;content&quot;: article[&quot;content&quot;],\n            &quot;description&quot;: article[&quot;description&quot;],\n            &quot;watched&quot;: article[&quot;watched&quot;],\n            &quot;category&quot;: article[&quot;name&quot;],\n            &quot;publish&quot;: str(article[&quot;publish&quot;]),\n            &quot;tags&quot;: self.getTagsArticle(article[&quot;aid&quot;]),\n            &quot;next&quot;: self.getOtherArticle(aid, &quot;next&quot;),\n            &quot;pre&quot;: self.getOtherArticle(aid, &quot;pre&quot;)\n        } if article else {}\n\n    def getOtherArticle(self, aid, articleType):\n        search_stmt = (\n            &quot;SELECT * FROM \\`article\\` WHERE aid=(select max(aid) from \\`article\\` where aid>%s)&quot;\n        ) if articleType == &quot;next&quot; else (\n            &quot;SELECT * FROM \\`article\\` WHERE aid=(select max(aid) from \\`article\\` where aid<%s)&quot;\n        )\n        result = self.doAction(search_stmt, (aid))\n        if result == False:\n            return False\n        article = result.fetchone()\n        return {\n            &quot;id&quot;: article[&quot;aid&quot;],\n            &quot;title&quot;: article[&quot;title&quot;]\n        } if article else {}\n\n    def getComments(self, aid):\n        search_stmt = (\n            &quot;SELECT * FROM \\`comments\\` WHERE article=%s AND is_show=1 ORDER BY -cid LIMIT 100;&quot;\n        )\n        result = self.doAction(search_stmt, (aid))\n        if result == False:\n            return False\n        return [{&quot;content&quot;: eveComment['content'],\n                 &quot;publish&quot;: str(eveComment['publish']),\n                 &quot;user&quot;: eveComment['user'],\n                 &quot;remark&quot;: eveComment['remark']} for eveComment in result.fetchall()]\n\n    def addComment(self, content, user, email, aid):\n        insert_stmt = (\n            &quot;INSERT INTO \\`comments\\` (\\`cid\\`, \\`content\\`, \\`publish\\`, \\`user\\`, \\`email\\`, \\`article\\`, \\`uni_mark\\`) &quot;\n            &quot;VALUES (NULL, %s, CURRENT_TIMESTAMP, %s, %s, %s, %s)&quot;\n        )\n        result = self.doAction(insert_stmt, (content, user, email, aid, hashlib.md5(\n            (&quot;%s----%s----%s----%s&quot; % (str(content), str(user), str(email), str(aid))).encode(&quot;utf-8&quot;)).hexdigest()))\n        return False if result == False else True\n\n    def updateArticleWatched(self, wid):\n        update_stmt = (\n            &quot;UPDATE \\`article\\` SET \\`watched\\`=\\`watched\\`+1 WHERE \\`aid\\` = %s&quot;\n        )\n        return False if self.doAction(update_stmt, (wid)) == False else True\n\n    def getPicture(self, content):\n        resultList =[eve[1] for eve in re.findall('<img(.*?)src=&quot;(.*?)&quot;(.*?)>', content)]\n        return resultList[0] if resultList else self.getDefaultPic()\n\n\n    def getTag(self, tag):\n        search_stmt = (\n            &quot;SELECT * FROM \\`tags\\` WHERE name=%s;&quot;\n        )\n        result = self.doAction(search_stmt, (tag,))\n        return False if not result or result.rowcount == 0 else result.fetchone()['tid']\n\n    def addTag(self, tag):\n        insert_stmt = (\n            &quot;INSERT INTO \\`tags\\` (\\`tid\\`, \\`name\\`, \\`remark\\`) &quot;\n            &quot;VALUES (NULL, %s, NULL)&quot;\n        )\n        result = self.doAction(insert_stmt, (tag))\n        return False if result == False else result.lastrowid\n\n    def addArticleTag(self, article, tag):\n        insert_stmt = (\n            &quot;INSERT INTO \\`article_tags\\` (\\`atid\\`, \\`aid\\`, \\`tid\\`) &quot;\n            &quot;VALUES (NULL, %s, %s)&quot;\n        )\n        result = self.doAction(insert_stmt, (article, tag))\n        return False if result == False else True`, `39326352576652890000`)\"\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\"># -*- coding: utf8 -*-\n\nimport os\nimport re\nimport pymysql\nimport hashlib\nfrom random import choice\n\n\nclass mysqlCommon:\n    def __init__(self):\n        self.getConnection({\n            &quot;host&quot;: os.environ.get(&#39;mysql_host&#39;),\n            &quot;user&quot;: os.environ.get(&#39;mysql_user&#39;),\n            &quot;port&quot;: int(os.environ.get(&#39;mysql_port&#39;)),\n            &quot;db&quot;: os.environ.get(&#39;mysql_db&#39;),\n            &quot;password&quot;: os.environ.get(&#39;mysql_password&#39;)\n        })\n\n    def getDefaultPic(self):\n        return choice([\n            &#39;http://t8.baidu.com/it/u=1484500186,1503043093&amp;fm=79&amp;app=86&amp;f=JPEG?w=1280&amp;h=853&#39;,\n            &#39;http://t8.baidu.com/it/u=2247852322,986532796&amp;fm=79&amp;app=86&amp;f=JPEG?w=1280&amp;h=853&#39;,\n            &#39;http://t7.baidu.com/it/u=3204887199,3790688592&amp;fm=79&amp;app=86&amp;f=JPEG?w=4610&amp;h=2968&#39;,\n            &#39;http://t9.baidu.com/it/u=3363001160,1163944807&amp;fm=79&amp;app=86&amp;f=JPEG?w=1280&amp;h=830&#39;,\n            &#39;http://t9.baidu.com/it/u=583874135,70653437&amp;fm=79&amp;app=86&amp;f=JPEG?w=3607&amp;h=2408&#39;,\n            &#39;http://t9.baidu.com/it/u=583874135,70653437&amp;fm=79&amp;app=86&amp;f=JPEG?w=3607&amp;h=2408&#39;,\n            &#39;http://t9.baidu.com/it/u=1307125826,3433407105&amp;fm=79&amp;app=86&amp;f=JPEG?w=5760&amp;h=3240&#39;,\n            &#39;http://t9.baidu.com/it/u=2268908537,2815455140&amp;fm=79&amp;app=86&amp;f=JPEG?w=1280&amp;h=719&#39;,\n            &#39;http://t7.baidu.com/it/u=1179872664,290201490&amp;fm=79&amp;app=86&amp;f=JPEG?w=1280&amp;h=854&#39;,\n            &#39;http://t9.baidu.com/it/u=3949188917,63856583&amp;fm=79&amp;app=86&amp;f=JPEG?w=1280&amp;h=875&#39;,\n            &#39;http://t9.baidu.com/it/u=2266751744,4253267866&amp;fm=79&amp;app=86&amp;f=JPEG?w=1280&amp;h=854&#39;,\n            &#39;http://t8.baidu.com/it/u=4100756023,1345858297&amp;fm=79&amp;app=86&amp;f=JPEG?w=1280&amp;h=854&#39;,\n            &#39;http://t7.baidu.com/it/u=1355385882,1155324943&amp;fm=79&amp;app=86&amp;f=JPEG?w=1280&amp;h=854&#39;,\n            &#39;http://t9.baidu.com/it/u=2292037961,3689236171&amp;fm=79&amp;app=86&amp;f=JPEG?w=1280&amp;h=854&#39;,\n            &#39;http://t9.baidu.com/it/u=4241966675,2405819829&amp;fm=79&amp;app=86&amp;f=JPEG?w=1280&amp;h=854&#39;,\n            &#39;http://t8.baidu.com/it/u=2857883419,1187496708&amp;fm=79&amp;app=86&amp;f=JPEG?w=1280&amp;h=763&#39;,\n            &#39;http://t8.baidu.com/it/u=198337120,441348595&amp;fm=79&amp;app=86&amp;f=JPEG?w=1280&amp;h=732&#39;\n        ])\n\n    def getConnection(self, conf):\n        self.connection = pymysql.connect(host=conf[&#39;host&#39;],\n                                          user=conf[&#39;user&#39;],\n                                          password=conf[&#39;password&#39;],\n                                          port=int(conf[&#39;port&#39;]),\n                                          db=conf[&#39;db&#39;],\n                                          charset=&#39;utf8&#39;,\n                                          cursorclass=pymysql.cursors.DictCursor,\n                                          autocommit=1)\n\n    def doAction(self, stmt, data):\n        try:\n            self.connection.ping(reconnect=True)\n            cursor = self.connection.cursor()\n            cursor.execute(stmt, data)\n            result = cursor\n            cursor.close()\n            return result\n        except Exception as e:\n            print(e)\n            try:\n                cursor.close()\n            except:\n                pass\n            return False\n\n    def getCategoryList(self):\n        search_stmt = (\n            &quot;SELECT * FROM `category` ORDER BY `sorted`&quot;\n        )\n        result = self.doAction(search_stmt, ())\n        if result == False:\n            return False\n        return [{&quot;id&quot;: eveCategory[&#39;cid&#39;], &quot;name&quot;: eveCategory[&#39;name&#39;]} for eveCategory in result.fetchall()]\n\n    def getArticleList(self, category, tag, page=1):\n        if category:\n            search_stmt = (\n                &quot;SELECT article.*,category.name FROM `article` LEFT JOIN `category` ON article.category=category.cid WHERE article.category=%s ORDER BY -article.aid LIMIT %s,%s;&quot;\n            )\n            count_stmt = (\n                &quot;SELECT COUNT(*) FROM `article` LEFT JOIN `category` ON article.category=category.cid WHERE article.category=%s;&quot;\n            )\n            data = (category, 10 * (int(page) - 1), 10 * int(page))\n            count_data = (category,)\n        elif tag:\n            search_stmt = (\n                &quot;SELECT article.* FROM `article` LEFT JOIN `article_tags` ON article.aid=article_tags.aid WHERE article_tags.tid=%s ORDER BY -article.aid LIMIT %s,%s;&quot;\n            )\n            count_stmt = (\n                &quot;SELECT COUNT(*) FROM `article`LEFT JOIN `article_tags` ON article.aid=article_tags.aid WHERE article_tags.tid=%s;&quot;\n            )\n            data = (tag, 10 * (int(page) - 1), 10 * int(page))\n            count_data = (tag,)\n        else:\n            search_stmt = (\n                &quot;SELECT article.*,category.name FROM `article` LEFT JOIN `category` ON article.category=category.cid ORDER BY -article.aid LIMIT %s,%s;&quot;\n            )\n            count_stmt = (\n                &quot;SELECT COUNT(*) FROM `article` LEFT JOIN `category` ON article.category=category.cid; &quot;\n            )\n            data = (10 * (int(page) - 1), 10 * int(page))\n            count_data = ()\n        result = self.doAction(search_stmt, data)\n        if result == False:\n            return False\n\n        return {&quot;data&quot;: [{&quot;id&quot;: eveArticle[&#39;aid&#39;],\n                          &quot;title&quot;: eveArticle[&#39;title&#39;],\n                          &quot;description&quot;: eveArticle[&#39;description&#39;],\n                          &quot;watched&quot;: eveArticle[&#39;watched&#39;],\n                          &quot;category&quot;: eveArticle[&#39;category&#39;],\n                          &quot;publish&quot;: str(eveArticle[&#39;publish&#39;]),\n                          &quot;picture&quot;: self.getPicture(eveArticle[&#39;content&#39;])}\n                         for eveArticle in result.fetchall()],\n                &quot;count&quot;: self.doAction(count_stmt, count_data).fetchone()[&quot;COUNT(*)&quot;]}\n\n    def getHotArticleList(self):\n        search_stmt = (\n            &quot;SELECT article.*,category.name FROM `article` LEFT JOIN `category` ON article.category=category.cid ORDER BY article.watched LIMIT 0,5&quot;\n        )\n        result = self.doAction(search_stmt, ())\n        if result == False:\n            return False\n        return [{&quot;id&quot;: eveArticle[&#39;aid&#39;],\n                 &quot;title&quot;: eveArticle[&#39;title&#39;],\n                 &quot;description&quot;: eveArticle[&#39;description&#39;],\n                 &quot;watched&quot;: eveArticle[&#39;watched&#39;],\n                 &quot;category&quot;: eveArticle[&#39;category&#39;],\n                 &quot;publish&quot;: str(eveArticle[&#39;publish&#39;]),\n                 &quot;picture&quot;: self.getPicture(eveArticle[&#39;content&#39;])}\n                for\n                eveArticle in result.fetchall()]\n\n    def getTagsArticle(self, aid):\n        search_stmt = (\n            &quot;SELECT tags.name, tags.tid FROM `article_tags` LEFT JOIN `tags` ON article_tags.tid=tags.tid WHERE article_tags.aid=%s;&quot;\n        )\n        result = self.doAction(search_stmt, (aid,))\n        if result == False:\n            return False\n        return [{&quot;id&quot;: eveTag[&quot;tid&quot;], &quot;name&quot;: eveTag[&quot;name&quot;]} for eveTag in result.fetchall()]\n\n    def getTagsList(self):\n        search_stmt = (\n            &quot;SELECT * FROM tags ORDER BY RAND() LIMIT 20; &quot;\n        )\n        result = self.doAction(search_stmt, ())\n        if result == False:\n            return False\n        return [{&quot;id&quot;: eveTag[&#39;tid&#39;], &quot;name&quot;: eveTag[&#39;name&#39;]} for eveTag in result.fetchall()]\n\n    def getArticleContent(self, aid):\n        search_stmt = (\n            &quot;SELECT article.*, category.name FROM `category` LEFT JOIN `article` ON category.cid=article.category WHERE article.aid=%s;&quot;\n        )\n        result = self.doAction(search_stmt, (aid))\n        if result == False:\n            return False\n        article = result.fetchone()\n        return {\n            &quot;id&quot;: article[&quot;aid&quot;],\n            &quot;title&quot;: article[&quot;title&quot;],\n            &quot;content&quot;: article[&quot;content&quot;],\n            &quot;description&quot;: article[&quot;description&quot;],\n            &quot;watched&quot;: article[&quot;watched&quot;],\n            &quot;category&quot;: article[&quot;name&quot;],\n            &quot;publish&quot;: str(article[&quot;publish&quot;]),\n            &quot;tags&quot;: self.getTagsArticle(article[&quot;aid&quot;]),\n            &quot;next&quot;: self.getOtherArticle(aid, &quot;next&quot;),\n            &quot;pre&quot;: self.getOtherArticle(aid, &quot;pre&quot;)\n        } if article else {}\n\n    def getOtherArticle(self, aid, articleType):\n        search_stmt = (\n            &quot;SELECT * FROM `article` WHERE aid=(select max(aid) from `article` where aid&gt;%s)&quot;\n        ) if articleType == &quot;next&quot; else (\n            &quot;SELECT * FROM `article` WHERE aid=(select max(aid) from `article` where aid&lt;%s)&quot;\n        )\n        result = self.doAction(search_stmt, (aid))\n        if result == False:\n            return False\n        article = result.fetchone()\n        return {\n            &quot;id&quot;: article[&quot;aid&quot;],\n            &quot;title&quot;: article[&quot;title&quot;]\n        } if article else {}\n\n    def getComments(self, aid):\n        search_stmt = (\n            &quot;SELECT * FROM `comments` WHERE article=%s AND is_show=1 ORDER BY -cid LIMIT 100;&quot;\n        )\n        result = self.doAction(search_stmt, (aid))\n        if result == False:\n            return False\n        return [{&quot;content&quot;: eveComment[&#39;content&#39;],\n                 &quot;publish&quot;: str(eveComment[&#39;publish&#39;]),\n                 &quot;user&quot;: eveComment[&#39;user&#39;],\n                 &quot;remark&quot;: eveComment[&#39;remark&#39;]} for eveComment in result.fetchall()]\n\n    def addComment(self, content, user, email, aid):\n        insert_stmt = (\n            &quot;INSERT INTO `comments` (`cid`, `content`, `publish`, `user`, `email`, `article`, `uni_mark`) &quot;\n            &quot;VALUES (NULL, %s, CURRENT_TIMESTAMP, %s, %s, %s, %s)&quot;\n        )\n        result = self.doAction(insert_stmt, (content, user, email, aid, hashlib.md5(\n            (&quot;%s----%s----%s----%s&quot; % (str(content), str(user), str(email), str(aid))).encode(&quot;utf-8&quot;)).hexdigest()))\n        return False if result == False else True\n\n    def updateArticleWatched(self, wid):\n        update_stmt = (\n            &quot;UPDATE `article` SET `watched`=`watched`+1 WHERE `aid` = %s&quot;\n        )\n        return False if self.doAction(update_stmt, (wid)) == False else True\n\n    def getPicture(self, content):\n        resultList =[eve[1] for eve in re.findall(&#39;&lt;img(.*?)src=&quot;(.*?)&quot;(.*?)&gt;&#39;, content)]\n        return resultList[0] if resultList else self.getDefaultPic()\n\n\n    def getTag(self, tag):\n        search_stmt = (\n            &quot;SELECT * FROM `tags` WHERE name=%s;&quot;\n        )\n        result = self.doAction(search_stmt, (tag,))\n        return False if not result or result.rowcount == 0 else result.fetchone()[&#39;tid&#39;]\n\n    def addTag(self, tag):\n        insert_stmt = (\n            &quot;INSERT INTO `tags` (`tid`, `name`, `remark`) &quot;\n            &quot;VALUES (NULL, %s, NULL)&quot;\n        )\n        result = self.doAction(insert_stmt, (tag))\n        return False if result == False else result.lastrowid\n\n    def addArticleTag(self, article, tag):\n        insert_stmt = (\n            &quot;INSERT INTO `article_tags` (`atid`, `aid`, `tid`) &quot;\n            &quot;VALUES (NULL, %s, %s)&quot;\n        )\n        result = self.doAction(insert_stmt, (article, tag))\n        return False if result == False else True</code></pre></div>\n<p>这里基本上是，这个项目需要的数据库增删改查的全部功能（admin 除外），在使用的时候，分为本地和线上：</p>\n<div\n              class=\"gatsby-code-button-container\"\n              data-toaster-id=\"9844446620589054000\"\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(`try:\n    import returnCommon\n    from mysqlCommon import mysqlCommon\nexcept:\n    import common.testCommon\n\n    common.testCommon.setEnv()\n\n    import common.returnCommon as returnCommon\n    from common.mysqlCommon import mysqlCommon\n\nmysql = mysqlCommon()`, `9844446620589054000`)\"\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\">try:\n    import returnCommon\n    from mysqlCommon import mysqlCommon\nexcept:\n    import common.testCommon\n\n    common.testCommon.setEnv()\n\n    import common.returnCommon as returnCommon\n    from common.mysqlCommon import mysqlCommon\n\nmysql = mysqlCommon()</code></pre></div>\n<p>通过 python 的异常，如果导入没找到，那就说明是本地测试，如果 <code class=\"language-text\">from mysqlCommon import mysqlCommon</code> 找到了，那就说明是线上环境。除了数据库的公共组件，我还有 <code class=\"language-text\">returnCommon</code> 等公共文件。当然， 这些文件，在使用的时候也需要打包进入，可以在 yaml 中增加 include，例如：</p>\n<div\n              class=\"gatsby-code-button-container\"\n              data-toaster-id=\"73158379153308566000\"\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(`Blog_Web_addComment:\n  component: &quot;@serverless/tencent-scf&quot;\n  inputs:\n    name: Blog_Web_addComment\n    description: 添加评论\n    codeUri: ./cloudFunctions/addComment\n    handler: \\${Conf.handler}\n    runtime: \\${Conf.runtime}\n    region:  \\${Conf.region}\n    include:\n      - \\${Conf.include_common}`, `73158379153308566000`)\"\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\">Blog_Web_addComment:\n  component: &quot;@serverless/tencent-scf&quot;\n  inputs:\n    name: Blog_Web_addComment\n    description: 添加评论\n    codeUri: ./cloudFunctions/addComment\n    handler: ${Conf.handler}\n    runtime: ${Conf.runtime}\n    region:  ${Conf.region}\n    include:\n      - ${Conf.include_common}</code></pre></div>\n<h2 id=\"功能展示\"><a href=\"#%E5%8A%9F%E8%83%BD%E5%B1%95%E7%A4%BA\" 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<h3 id=\"前台功能\"><a href=\"#%E5%89%8D%E5%8F%B0%E5%8A%9F%E8%83%BD\" 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>前台功能</h3>\n<ul>\n<li>列表页\n<img src=\"https://img.serverlesscloud.cn/202058/3-5-1.png\" alt=\"列表页\"></li>\n<li>内容页\n<img src=\"https://img.serverlesscloud.cn/202058/3-5-2.png\" alt=\"内容页\"></li>\n</ul>\n<h3 id=\"后台功能\"><a href=\"#%E5%90%8E%E5%8F%B0%E5%8A%9F%E8%83%BD\" 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>后台功能</h3>\n<ul>\n<li>登录功能\n<img src=\"https://img.serverlesscloud.cn/202058/3-5-3.png\" alt=\"登录功能\"></li>\n<li>列表页\n<img src=\"https://img.serverlesscloud.cn/202058/3-5-4.png\" alt=\"列表页\"></li>\n<li>表单页\n<img src=\"https://img.serverlesscloud.cn/202058/3-5-5.png\" alt=\"表单页\"></li>\n</ul>\n<h2 id=\"项目部署\"><a href=\"#%E9%A1%B9%E7%9B%AE%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<ul>\n<li>配置 <code class=\"language-text\">serverless.yaml</code>：</li>\n</ul>\n<div\n              class=\"gatsby-code-button-container\"\n              data-toaster-id=\"24917308911645340000\"\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(`# 函数们的整体配置信息\nConf:\n  component: &quot;serverless-global&quot;\n  inputs:\n    region: ap-shanghai\n    runtime: Python3.6\n    handler: index.main_handler\n    include_common: ./common\n    blog_user: Dfounder\n    blog_email: service@anycodes.cn\n    website_title: Serverless Blog System\n    website_keywords: Serverless, Serverless Framework, Tencent Cloud, SCF\n    website_description: 一款基于腾讯云Serverless架构，并且采用Serverless Framework构建的Serverless博客系统。\n    website_bucket: serverless-blog-1256773370\n    mysql_host:\n    mysql_password:\n    mysql_port:\n    mysql_db:\n    admin_user: mytest\n    admin_password: mytest`, `24917308911645340000`)\"\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 comment\"># 函数们的整体配置信息</span>\n<span class=\"token key atrule\">Conf</span><span class=\"token punctuation\">:</span>\n  <span class=\"token key atrule\">component</span><span class=\"token punctuation\">:</span> <span class=\"token string\">\"serverless-global\"</span>\n  <span class=\"token key atrule\">inputs</span><span class=\"token punctuation\">:</span>\n    <span class=\"token key atrule\">region</span><span class=\"token punctuation\">:</span> ap<span class=\"token punctuation\">-</span>shanghai\n    <span class=\"token key atrule\">runtime</span><span class=\"token punctuation\">:</span> Python3.6\n    <span class=\"token key atrule\">handler</span><span class=\"token punctuation\">:</span> index.main_handler\n    <span class=\"token key atrule\">include_common</span><span class=\"token punctuation\">:</span> ./common\n    <span class=\"token key atrule\">blog_user</span><span class=\"token punctuation\">:</span> Dfounder\n    <span class=\"token key atrule\">blog_email</span><span class=\"token punctuation\">:</span> service@anycodes.cn\n    <span class=\"token key atrule\">website_title</span><span class=\"token punctuation\">:</span> Serverless Blog System\n    <span class=\"token key atrule\">website_keywords</span><span class=\"token punctuation\">:</span> Serverless<span class=\"token punctuation\">,</span> Serverless Framework<span class=\"token punctuation\">,</span> Tencent Cloud<span class=\"token punctuation\">,</span> SCF\n    <span class=\"token key atrule\">website_description</span><span class=\"token punctuation\">:</span> 一款基于腾讯云Serverless架构，并且采用Serverless Framework构建的Serverless博客系统。\n    <span class=\"token key atrule\">website_bucket</span><span class=\"token punctuation\">:</span> serverless<span class=\"token punctuation\">-</span>blog<span class=\"token punctuation\">-</span><span class=\"token number\">1256773370</span>\n    <span class=\"token key atrule\">mysql_host</span><span class=\"token punctuation\">:</span>\n    <span class=\"token key atrule\">mysql_password</span><span class=\"token punctuation\">:</span>\n    <span class=\"token key atrule\">mysql_port</span><span class=\"token punctuation\">:</span>\n    <span class=\"token key atrule\">mysql_db</span><span class=\"token punctuation\">:</span>\n    <span class=\"token key atrule\">admin_user</span><span class=\"token punctuation\">:</span> mytest\n    <span class=\"token key atrule\">admin_password</span><span class=\"token punctuation\">:</span> mytest</code></pre></div>\n<p>除了上面的内容，还要看一下域名问题（例如 CosBucket）：</p>\n<div class=\"gatsby-highlight\" data-language=\"text\"><pre class=\"language-text\"><code class=\"language-text\"># 网站\nCosBucket:\n  component: &#39;@serverless/tencent-website&#39;\n  inputs:\n    code:\n      root: website/dist\n      src: ./\n      index: list.html\n    region:  ${Conf.region}\n    bucketName: ${Conf.website_bucket}\n    hosts:\n      - host: 0duzhan.com\n        https:\n          certId: awPsOIHY\n          forceSwitch: -1\n      - host: www.0duzhan.com\n        https:\n          certId: awPsOIHY\n          forceSwitch: -1\n\n    env:\n      apiUrl: ${APIService.subDomain}</code></pre></div>\n<p>以及 API 网关内容：</p>\n<div class=\"gatsby-highlight\" data-language=\"text\"><pre class=\"language-text\"><code class=\"language-text\"># 创建 API 网关 Service\nAPIService:\n  component: &quot;@serverless/tencent-apigateway&quot;\n  inputs:\n    region: ${Conf.region}\n    customDomain:\n      - domain: api.0duzhan.com\n        isDefaultMapping: &#39;FALSE&#39;\n        pathMappingSet:\n          - path: /\n            environment: release\n        protocols:\n          - http\n    protocols:\n      - http\n      - https\n    ........</code></pre></div>\n<p>这两部分域名可以修改成自己的，或者删除掉这两个 key</p>\n<ul>\n<li>执行<code class=\"language-text\">init.py</code>:</li>\n</ul>\n<p>这里要注意，我是在 macOS 下开发的，<code class=\"language-text\">init.py</code> 可以在 macOS/Linux 运行，Windows 用户可能要适当修改一下。还有这里面需要一个依赖：pyyaml，需要自行安装一下。</p>\n<div class=\"gatsby-highlight\" data-language=\"text\"><pre class=\"language-text\"><code class=\"language-text\">获取Yaml数据：  True\n建立数据库： True\n建立数据库： True\n初始化HTML： True</code></pre></div>\n<ul>\n<li>部署资源，执行 <code class=\"language-text\">serverless --debug</code></li>\n</ul>\n<div\n              class=\"gatsby-code-button-container\"\n              data-toaster-id=\"58673595694890790000\"\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(`(venv) ServerlessBlog:ServerlessBlog dfounderliu\\$ sls --debug\n\n  DEBUG ─ Resolving the template's static variables.\n  DEBUG ─ Collecting components from the template.\n  DEBUG ─ Downloading any NPM components found in the template.\n  DEBUG ─ Analyzing the template's components dependencies.\n  DEBUG ─ Creating the template's components graph.\n  DEBUG ─ Syncing template state.\n  DEBUG ─ Executing the template's components graph.\n  DEBUG ─ Preparing website Tencent COS bucket serverless-blog-1256773370.\n  DEBUG ─ Starting API-Gateway deployment with name APIService in the ap-shanghai region\n  DEBUG ─ Using last time deploy service id service-23ybmuq7\n  DEBUG ─ Updating service with serviceId service-23ybmuq7.\n  DEBUG ─ Bucket &quot;serverless-blog-1256773370&quot; in the &quot;ap-shanghai&quot; region alrea\n\n  ………………\n\n     -\n        path:   /web/article/watched/update\n        method: POST\n        apiId:  api-gnvnrbyk\n      -\n        path:   /web/sentence/get\n        method: POST\n        apiId:  api-msvadsau\n      -\n        path:   /web/article/list/hot/get\n        method: POST\n        apiId:  api-kfkrjhim\n      -\n        path:   /web/tags/list/get\n        method: POST\n        apiId:  api-avydagem\n      -\n        path:   /admin\n        method: ANY\n        apiId:  api-4tnz5tc4\n\n  176s › APIService › done`, `58673595694890790000`)\"\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\">(venv) ServerlessBlog:ServerlessBlog dfounderliu$ sls --debug\n\n  DEBUG ─ Resolving the template&#39;s static variables.\n  DEBUG ─ Collecting components from the template.\n  DEBUG ─ Downloading any NPM components found in the template.\n  DEBUG ─ Analyzing the template&#39;s components dependencies.\n  DEBUG ─ Creating the template&#39;s components graph.\n  DEBUG ─ Syncing template state.\n  DEBUG ─ Executing the template&#39;s components graph.\n  DEBUG ─ Preparing website Tencent COS bucket serverless-blog-1256773370.\n  DEBUG ─ Starting API-Gateway deployment with name APIService in the ap-shanghai region\n  DEBUG ─ Using last time deploy service id service-23ybmuq7\n  DEBUG ─ Updating service with serviceId service-23ybmuq7.\n  DEBUG ─ Bucket &quot;serverless-blog-1256773370&quot; in the &quot;ap-shanghai&quot; region alrea\n\n  ………………\n\n     -\n        path:   /web/article/watched/update\n        method: POST\n        apiId:  api-gnvnrbyk\n      -\n        path:   /web/sentence/get\n        method: POST\n        apiId:  api-msvadsau\n      -\n        path:   /web/article/list/hot/get\n        method: POST\n        apiId:  api-kfkrjhim\n      -\n        path:   /web/tags/list/get\n        method: POST\n        apiId:  api-avydagem\n      -\n        path:   /admin\n        method: ANY\n        apiId:  api-4tnz5tc4\n\n  176s › APIService › done</code></pre></div>\n<h2 id=\"项目总结\"><a href=\"#%E9%A1%B9%E7%9B%AE%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>传统博客已经有很多了，无论是基于 PHP 的 zblog 还是 wp 等开源项目，都可以帮助我们快速搭建一个博客系统。除了这些博客系统之外，还有很多静态博客系统。但是就目前而言，基于 Serverless 架构的博客系统还是比较少见的。</p>\n<p>本文通过原生的 Serverless 项目开发与 Flask 框架的部署上 Serverless 实现了一个基于 Python 语言的博客系统。通过该博客系统，用户可以发布文章，自动撰写文章的关键词和摘要，还可以进行留言评论的管理。当然，这个博客系统仅作为工程实践使用，实际上还是有一些设计不合理的地方，但是我相信，随着时间的发展，Serverless 架构越来越成熟，基于 Serverless 的开源 Blog 项目或 CMS 项目也会越来越多，期待那一天的到来！</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-08-flack-blog/#%E5%BC%80%E5%8F%91%E5%89%8D%E7%9A%84%E6%80%9D%E8%80%83\">开发前的思考</a></li>\n<li>\n<p><a href=\"/best-practice/2020-02-08-flack-blog/#%E9%A1%B9%E7%9B%AE%E5%BC%80%E5%8F%91\">项目开发</a></p>\n<ul>\n<li><a href=\"/best-practice/2020-02-08-flack-blog/#%E6%95%B0%E6%8D%AE%E5%BA%93%E8%AE%BE%E8%AE%A1\">数据库设计</a></li>\n<li><a href=\"/best-practice/2020-02-08-flack-blog/#%E6%9C%AC%E5%9C%B0%E5%BC%80%E5%8F%91%E4%B8%8E%E8%B0%83%E8%AF%95\">本地开发与调试</a></li>\n<li><a href=\"/best-practice/2020-02-08-flack-blog/#flask%E9%83%A8%E7%BD%B2\">Flask部署</a></li>\n<li><a href=\"/best-practice/2020-02-08-flack-blog/#%E5%85%A8%E5%B1%80%E5%8F%98%E9%87%8F\">全局变量</a></li>\n<li><a href=\"/best-practice/2020-02-08-flack-blog/#%E9%A1%B9%E7%9B%AE%E5%88%9D%E5%A7%8B%E5%8C%96\">项目初始化</a></li>\n<li><a href=\"/best-practice/2020-02-08-flack-blog/#%E5%85%AC%E5%85%B1%E7%BB%84%E4%BB%B6%E7%9A%84%E5%BC%80%E5%8F%91\">公共组件的开发</a></li>\n</ul>\n</li>\n<li>\n<p><a href=\"/best-practice/2020-02-08-flack-blog/#%E5%8A%9F%E8%83%BD%E5%B1%95%E7%A4%BA\">功能展示</a></p>\n<ul>\n<li><a href=\"/best-practice/2020-02-08-flack-blog/#%E5%89%8D%E5%8F%B0%E5%8A%9F%E8%83%BD\">前台功能</a></li>\n<li><a href=\"/best-practice/2020-02-08-flack-blog/#%E5%90%8E%E5%8F%B0%E5%8A%9F%E8%83%BD\">后台功能</a></li>\n</ul>\n</li>\n<li><a href=\"/best-practice/2020-02-08-flack-blog/#%E9%A1%B9%E7%9B%AE%E9%83%A8%E7%BD%B2\">项目部署</a></li>\n<li><a href=\"/best-practice/2020-02-08-flack-blog/#%E9%A1%B9%E7%9B%AE%E6%80%BB%E7%BB%93\">项目总结</a></li>\n</ul>"},"previousBlog":{"id":"26fb7591-1527-5217-90a7-bc39c80f7186","frontmatter":{"thumbnail":"https://img.serverlesscloud.cn/202031/1583055662380-th.jpeg","authors":["Tabor"],"categories":["user-stories","guides-and-tutorials"],"date":"2020-02-19T00:00:00.000Z","title":"Serverless 架构与事件规范","description":"Serverless 架构与事件规范","authorslink":["https://canmeng.net"],"translators":null,"translatorslink":null,"tags":["serverless","Meetup"],"keywords":"Serverless 架构,Serverless 规范,Serverless 架构与事件规范","outdated":null},"wordCount":{"words":265,"sentences":62,"paragraphs":62},"fileAbsolutePath":"/opt/build/repo/content/blog/2020-02-19-Serverless-base.md","fields":{"slug":"/blog/2020-02-19-Serverless-base/","keywords":["serverless","函数计算","事件","Serverless","函数","架构","服务器","服务","Faas","serverlesscloud"]}},"nextBlog":{"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"]}}},"pageContext":{"isCreatedByStatefulCreatePages":false,"blogId":"0d3c3171-691f-55a7-9a9a-3a35d992f615","previousBlogId":"26fb7591-1527-5217-90a7-bc39c80f7186","nextBlogId":"e4d496fa-1f43-52fe-97ed-a397afdf1f9c"}}}