{"componentChunkName":"component---src-templates-blog-detail-tsx","path":"/blog/2020-01-08-book-search-app","result":{"data":{"currentBlog":{"id":"03c5f35a-e201-5d91-bece-d455930b84f9","frontmatter":{"thumbnail":"https://img.serverlesscloud.cn/2020218/1582040387776-timg.jpeg","authors":["Anycodes"],"categories":["user-stories"],"date":"2020-01-08T00:00:00.000Z","title":"如何使用 Serverless 实现一个图书查询 App","description":"基于 Serverless 的轻量级图书检索系统","authorslink":["https://www.zhihu.com/people/liuyu-43-97"],"translators":null,"translatorslink":null,"tags":["serverless","应用实战"],"keywords":"Serverless 轻量级图书检索,Serverless 图书查询,Serverless 检索系统","outdated":true},"wordCount":{"words":116,"sentences":29,"paragraphs":29},"fileAbsolutePath":"/opt/build/repo/content/blog/2020-01-08-book-search-app.md","fields":{"slug":"/blog/2020-01-08-book-search-app/","keywords":["go","serverless","云函数","book","mui","sheet"]},"html":"<p>我有一个朋友（这朋友不是我），朋友的单位里有一个小型图书室，图书室中存放了不少书。</p>\n<p>尽管每本书都在相应的区域里进行了编号，但是毕竟没有图书馆的管理系统，大家找起来还是要花点时间的。为了让大家更容易地找到这些书，朋友联系我，打算让我帮他做一个简单的图书查询系统（<del>完整的图书馆管理系统</del>）。</p>\n<p>Easier said than done，考虑到这还是有一定复杂度的项目，我打算使用腾讯云云函数 SCF，把整个应用部署到 Serverless 架构上。</p>\n<h2 id=\"▎整体效果\"><a href=\"#%E2%96%8E%E6%95%B4%E4%BD%93%E6%95%88%E6%9E%9C\" 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>左边是图书检索系统的首页；右边是检索演示，比方说我们搜索「精神」，App 就会依据返回相关的书籍。看起来还不赖。</p>\n<p><img src=\"https://img.serverlesscloud.cn/2020218/1582038622515-%E5%9B%BE%E4%B9%A6.jpg\" alt=\"图书查询 app\"></p>\n<h2 id=\"▎功能设计\"><a href=\"#%E2%96%8E%E5%8A%9F%E8%83%BD%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>▎功能设计</h2>\n<ol>\n<li>将包含书籍信息的 Excel 表存放至腾讯云对象存储 COS 中；</li>\n<li>使用腾讯云云函数读取并解析表格；</li>\n<li>根据词语相似性检索对应的图书；</li>\n<li>通过 MUI 制作前端页面，页面也存放在 COS 中。</li>\n</ol>\n<h2 id=\"▎具体实现\"><a href=\"#%E2%96%8E%E5%85%B7%E4%BD%93%E5%AE%9E%E7%8E%B0\" 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>Excel 样式（包含书名和编号）</li>\n</ol>\n<p><img src=\"https://img.serverlesscloud.cn/2020218/1582040581779-v2-dae1a86ecb2357fa30e8c13de21ee9a6_hd.jpg\" alt=\"Excel 样式\"></p>\n<p>分类 tab：</p>\n<p><img src=\"https://img.serverlesscloud.cn/2020218/1582040581528-v2-dae1a86ecb2357fa30e8c13de21ee9a6_hd.jpg\" alt=\"分类 tab\"></p>\n<ol start=\"2\">\n<li>核心代码实现：</li>\n</ol>\n<div\n              class=\"gatsby-code-button-container\"\n              data-toaster-id=\"26637636400137368000\"\n              data-toaster-class=\"gatsby-code-button-toaster\"\n              data-toaster-text-class=\"gatsby-code-button-toaster-text\"\n              data-toaster-text=\"代码复制成功\"\n              data-toaster-duration=\"3500\"\n              onClick=\"copyToClipboard(`import jieba\nimport openpyxl\nfrom gensim import corpora, models, similarities\nfrom collections import defaultdict\nimport urllib.request\n\nwith open(&quot;/tmp/book.xlsx&quot;, &quot;wb&quot;) as f:\n    f.write(\n        urllib.request.urlopen(&quot;https://********&quot;).read()\n    )\n\n\ntop_str = &quot;abcdefghijklmn&quot;\nbook_dict = {}\nbook_list = []\nwb = openpyxl.load_workbook('/tmp/book.xlsx')\nsheets = wb.sheetnames\nfor eve_sheet in sheets:\n    print(eve_sheet)\n    sheet = wb.get_sheet_by_name(eve_sheet)\n    this_book_name_index = None\n    this_book_number_index = None\n    for eve_header in top_str:\n        if sheet[eve_header][0].value == &quot;书名&quot;:\n            this_book_name_index = eve_header\n        if sheet[eve_header][0].value == &quot;编号&quot;:\n            this_book_number_index = eve_header\n    print(this_book_name_index, this_book_number_index)\n    if this_book_name_index and this_book_number_index:\n        this_book_list_len = len(sheet[this_book_name_index])\n        for i in range(1, this_book_list_len):\n            add_key = &quot;%s_%s_%s&quot; % (\n                sheet[this_book_name_index][i].value, eve_sheet, sheet[this_book_number_index][i].value)\n            add_value = {\n                &quot;category&quot;: eve_sheet,\n                &quot;name&quot;: sheet[this_book_name_index][i].value,\n                &quot;number&quot;: sheet[this_book_number_index][i].value\n            }\n            book_dict[add_key] = add_value\n            book_list.append(add_key)\n\n\ndef getBookList(book, book_list):\n    documents = []\n    for eve_sentence in book_list:\n        tempData = &quot; &quot;.join(jieba.cut(eve_sentence))\n        documents.append(tempData)\n    texts = [[word for word in document.split()] for document in documents]\n    frequency = defaultdict(int)\n    for text in texts:\n        for word in text:\n            frequency[word] += 1\n    dictionary = corpora.Dictionary(texts)\n    new_xs = dictionary.doc2bow(jieba.cut(book))\n    corpus = [dictionary.doc2bow(text) for text in texts]\n    tfidf = models.TfidfModel(corpus)\n    featurenum = len(dictionary.token2id.keys())\n    sim = similarities.SparseMatrixSimilarity(\n        tfidf[corpus],\n        num_features=featurenum\n    )[tfidf[new_xs]]\n    book_result_list = [(sim[i], book_list[i]) for i in range(0, len(book_list))]\n    book_result_list.sort(key=lambda x: x[0], reverse=True)\n    result = []\n    for eve in book_result_list:\n        if eve[0] >= 0.25:\n            result.append(eve)\n    return result\n\n\ndef main_handler(event, context):\n    try:\n        print(event)\n        name = event[&quot;body&quot;]\n        print(name)\n        base_html = '''<div class='mui-card'><div class='mui-card-header'>{{book_name}}</div><div class='mui-card-content'><div class='mui-card-content-inner'>分类：{{book_category}}<br>编号：{{book_number}}</div></div></div>'''\n        result_str = &quot;&quot;\n        for eve_book in getBookList(name, book_list):\n            book_infor = book_dict[eve_book[1]]\n            result_str = result_str + base_html.replace(&quot;{{book_name}}&quot;, book_infor['name']) \\\n                .replace(&quot;{{book_category}}&quot;, book_infor['category']) \\\n                .replace(&quot;{{book_number}}&quot;, book_infor['number'] if book_infor['number'] else &quot;&quot;)\n        if result_str:\n            return result_str\n    except Exception as e:\n        print(e)\n    return '''<div class='mui-card' style='margin-top: 25px'><div class='mui-card-content'><div class='mui-card-content-inner'>未找到图书信息，请您重新搜索。</div></div></div>'''`, `26637636400137368000`)\"\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\">import jieba\nimport openpyxl\nfrom gensim import corpora, models, similarities\nfrom collections import defaultdict\nimport urllib.request\n\nwith open(&quot;/tmp/book.xlsx&quot;, &quot;wb&quot;) as f:\n    f.write(\n        urllib.request.urlopen(&quot;https://********&quot;).read()\n    )\n\n\ntop_str = &quot;abcdefghijklmn&quot;\nbook_dict = {}\nbook_list = []\nwb = openpyxl.load_workbook(&#39;/tmp/book.xlsx&#39;)\nsheets = wb.sheetnames\nfor eve_sheet in sheets:\n    print(eve_sheet)\n    sheet = wb.get_sheet_by_name(eve_sheet)\n    this_book_name_index = None\n    this_book_number_index = None\n    for eve_header in top_str:\n        if sheet[eve_header][0].value == &quot;书名&quot;:\n            this_book_name_index = eve_header\n        if sheet[eve_header][0].value == &quot;编号&quot;:\n            this_book_number_index = eve_header\n    print(this_book_name_index, this_book_number_index)\n    if this_book_name_index and this_book_number_index:\n        this_book_list_len = len(sheet[this_book_name_index])\n        for i in range(1, this_book_list_len):\n            add_key = &quot;%s_%s_%s&quot; % (\n                sheet[this_book_name_index][i].value, eve_sheet, sheet[this_book_number_index][i].value)\n            add_value = {\n                &quot;category&quot;: eve_sheet,\n                &quot;name&quot;: sheet[this_book_name_index][i].value,\n                &quot;number&quot;: sheet[this_book_number_index][i].value\n            }\n            book_dict[add_key] = add_value\n            book_list.append(add_key)\n\n\ndef getBookList(book, book_list):\n    documents = []\n    for eve_sentence in book_list:\n        tempData = &quot; &quot;.join(jieba.cut(eve_sentence))\n        documents.append(tempData)\n    texts = [[word for word in document.split()] for document in documents]\n    frequency = defaultdict(int)\n    for text in texts:\n        for word in text:\n            frequency[word] += 1\n    dictionary = corpora.Dictionary(texts)\n    new_xs = dictionary.doc2bow(jieba.cut(book))\n    corpus = [dictionary.doc2bow(text) for text in texts]\n    tfidf = models.TfidfModel(corpus)\n    featurenum = len(dictionary.token2id.keys())\n    sim = similarities.SparseMatrixSimilarity(\n        tfidf[corpus],\n        num_features=featurenum\n    )[tfidf[new_xs]]\n    book_result_list = [(sim[i], book_list[i]) for i in range(0, len(book_list))]\n    book_result_list.sort(key=lambda x: x[0], reverse=True)\n    result = []\n    for eve in book_result_list:\n        if eve[0] &gt;= 0.25:\n            result.append(eve)\n    return result\n\n\ndef main_handler(event, context):\n    try:\n        print(event)\n        name = event[&quot;body&quot;]\n        print(name)\n        base_html = &#39;&#39;&#39;&lt;div class=&#39;mui-card&#39;&gt;&lt;div class=&#39;mui-card-header&#39;&gt;{{book_name}}&lt;/div&gt;&lt;div class=&#39;mui-card-content&#39;&gt;&lt;div class=&#39;mui-card-content-inner&#39;&gt;分类：{{book_category}}&lt;br&gt;编号：{{book_number}}&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&#39;&#39;&#39;\n        result_str = &quot;&quot;\n        for eve_book in getBookList(name, book_list):\n            book_infor = book_dict[eve_book[1]]\n            result_str = result_str + base_html.replace(&quot;{{book_name}}&quot;, book_infor[&#39;name&#39;]) \\\n                .replace(&quot;{{book_category}}&quot;, book_infor[&#39;category&#39;]) \\\n                .replace(&quot;{{book_number}}&quot;, book_infor[&#39;number&#39;] if book_infor[&#39;number&#39;] else &quot;&quot;)\n        if result_str:\n            return result_str\n    except Exception as e:\n        print(e)\n    return &#39;&#39;&#39;&lt;div class=&#39;mui-card&#39; style=&#39;margin-top: 25px&#39;&gt;&lt;div class=&#39;mui-card-content&#39;&gt;&lt;div class=&#39;mui-card-content-inner&#39;&gt;未找到图书信息，请您重新搜索。&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&#39;&#39;&#39;</code></pre></div>\n<ol start=\"3\">\n<li>APIGW 配置：</li>\n</ol>\n<p><img src=\"https://img.serverlesscloud.cn/2020218/1582040581495-v2-dae1a86ecb2357fa30e8c13de21ee9a6_hd.jpg\" alt=\"APIGW\"></p>\n<ol start=\"4\">\n<li>首页代码：</li>\n</ol>\n<div\n              class=\"gatsby-code-button-container\"\n              data-toaster-id=\"27844791192690100000\"\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(`<!DOCTYPE html>\n<html>\n<head>\n    <meta charset=&quot;utf-8&quot;>\n    <title>图书检索系统</title>\n    <meta name=&quot;viewport&quot; content=&quot;width=device-width, initial-scale=1,maximum-scale=1,user-scalable=no&quot;>\n    <meta name=&quot;apple-mobile-web-app-capable&quot; content=&quot;yes&quot;>\n    <meta name=&quot;apple-mobile-web-app-status-bar-style&quot; content=&quot;black&quot;>\n\n    <link rel=&quot;stylesheet&quot; href=&quot;https://others-1256773370.cos.ap-chengdu.myqcloud.com/booksearch/css/mui.min.css&quot;>\n    <style>\n        html,\n        body {\n            background-color: #efeff4;\n        }\n    </style>\n    <script>\n        function getResult() {\n            var UTFTranslate = {\n                Change: function (pValue) {\n                    return pValue.replace(/[^\\u0000-\\u00FF]/g, function (\\$0) {\n                        return escape(\\$0).replace(/(%u)(\\w{4})/gi, &quot;&#x\\$2;&quot;)\n                    });\n                },\n                ReChange: function (pValue) {\n                    return unescape(pValue.replace(/&#x/g, '%u').replace(/\\\\u/g, '%u').replace(/;/g, ''));\n                }\n            };\n\n            var xmlhttp;\n            if (window.XMLHttpRequest) {\n                // IE7+, Firefox, Chrome, Opera, Safari 浏览器执行代码\n                xmlhttp = new XMLHttpRequest();\n            } else {\n                // IE6, IE5 浏览器执行代码\n                xmlhttp = new ActiveXObject(&quot;Microsoft.XMLHTTP&quot;);\n            }\n            xmlhttp.onreadystatechange = function () {\n                if (xmlhttp.readyState == 4 && xmlhttp.status == 200 && xmlhttp.responseText) {\n                    document.getElementById(&quot;result&quot;).innerHTML = UTFTranslate.ReChange(xmlhttp.responseText).slice(1, -1).replace(&quot;\\&quot;&quot;,'&quot;');\n                }\n            }\n            xmlhttp.open(&quot;POST&quot;, &quot;https://********&quot;, true);\n            xmlhttp.setRequestHeader(&quot;Content-type&quot;, &quot;application/x-www-form-urlencoded&quot;);\n            xmlhttp.send(document.getElementById(&quot;book&quot;).value);\n        }\n    </script>\n</head>\n<body>\n<div class=&quot;mui-content&quot; style=&quot;margin-top: 50px&quot;>\n    <h3 style=&quot;text-align: center&quot;>图书检索系统</h3>\n    <div class=&quot;mui-content-padded&quot; style=&quot;margin: 10px; margin-top: 20px&quot;>\n        <div class=&quot;mui-input-row mui-search&quot;>\n            <input type=&quot;search&quot; class=&quot;mui-input-clear&quot; placeholder=&quot;请输入图书名&quot; id=&quot;book&quot;>\n        </div>\n        <div class=&quot;mui-button-row&quot;>\n            <button type=&quot;button&quot; class=&quot;mui-btn mui-btn-numbox-plus&quot; style=&quot;width: 100%&quot; onclick=&quot;getResult()&quot;>检索\n            </button>&nbsp;&nbsp;\n        </div>\n    </div>\n    <div id=&quot;result&quot;>\n        <div class=&quot;mui-card&quot; style=&quot;margin-top: 25px&quot;>\n            <div class=&quot;mui-card-content&quot;>\n                <div class=&quot;mui-card-content-inner&quot;>\n                    可以在搜索框内输入书籍的全称，或者书籍的简称，系统支持智能检索功能。\n                </div>\n            </div>\n        </div>\n    </div>\n</div>\n<script src=&quot;https://others-1256773370.cos.ap-chengdu.myqcloud.com/booksearch/js/mui.min.js&quot;></script>\n</body>\n</html>`, `27844791192690100000`)\"\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\">&lt;!DOCTYPE html&gt;\n&lt;html&gt;\n&lt;head&gt;\n    &lt;meta charset=&quot;utf-8&quot;&gt;\n    &lt;title&gt;图书检索系统&lt;/title&gt;\n    &lt;meta name=&quot;viewport&quot; content=&quot;width=device-width, initial-scale=1,maximum-scale=1,user-scalable=no&quot;&gt;\n    &lt;meta name=&quot;apple-mobile-web-app-capable&quot; content=&quot;yes&quot;&gt;\n    &lt;meta name=&quot;apple-mobile-web-app-status-bar-style&quot; content=&quot;black&quot;&gt;\n\n    &lt;link rel=&quot;stylesheet&quot; href=&quot;https://others-1256773370.cos.ap-chengdu.myqcloud.com/booksearch/css/mui.min.css&quot;&gt;\n    &lt;style&gt;\n        html,\n        body {\n            background-color: #efeff4;\n        }\n    &lt;/style&gt;\n    &lt;script&gt;\n        function getResult() {\n            var UTFTranslate = {\n                Change: function (pValue) {\n                    return pValue.replace(/[^\\u0000-\\u00FF]/g, function ($0) {\n                        return escape($0).replace(/(%u)(\\w{4})/gi, &quot;&amp;#x$2;&quot;)\n                    });\n                },\n                ReChange: function (pValue) {\n                    return unescape(pValue.replace(/&amp;#x/g, &#39;%u&#39;).replace(/\\\\u/g, &#39;%u&#39;).replace(/;/g, &#39;&#39;));\n                }\n            };\n\n            var xmlhttp;\n            if (window.XMLHttpRequest) {\n                // IE7+, Firefox, Chrome, Opera, Safari 浏览器执行代码\n                xmlhttp = new XMLHttpRequest();\n            } else {\n                // IE6, IE5 浏览器执行代码\n                xmlhttp = new ActiveXObject(&quot;Microsoft.XMLHTTP&quot;);\n            }\n            xmlhttp.onreadystatechange = function () {\n                if (xmlhttp.readyState == 4 &amp;&amp; xmlhttp.status == 200 &amp;&amp; xmlhttp.responseText) {\n                    document.getElementById(&quot;result&quot;).innerHTML = UTFTranslate.ReChange(xmlhttp.responseText).slice(1, -1).replace(&quot;\\&quot;&quot;,&#39;&quot;&#39;);\n                }\n            }\n            xmlhttp.open(&quot;POST&quot;, &quot;https://********&quot;, true);\n            xmlhttp.setRequestHeader(&quot;Content-type&quot;, &quot;application/x-www-form-urlencoded&quot;);\n            xmlhttp.send(document.getElementById(&quot;book&quot;).value);\n        }\n    &lt;/script&gt;\n&lt;/head&gt;\n&lt;body&gt;\n&lt;div class=&quot;mui-content&quot; style=&quot;margin-top: 50px&quot;&gt;\n    &lt;h3 style=&quot;text-align: center&quot;&gt;图书检索系统&lt;/h3&gt;\n    &lt;div class=&quot;mui-content-padded&quot; style=&quot;margin: 10px; margin-top: 20px&quot;&gt;\n        &lt;div class=&quot;mui-input-row mui-search&quot;&gt;\n            &lt;input type=&quot;search&quot; class=&quot;mui-input-clear&quot; placeholder=&quot;请输入图书名&quot; id=&quot;book&quot;&gt;\n        &lt;/div&gt;\n        &lt;div class=&quot;mui-button-row&quot;&gt;\n            &lt;button type=&quot;button&quot; class=&quot;mui-btn mui-btn-numbox-plus&quot; style=&quot;width: 100%&quot; onclick=&quot;getResult()&quot;&gt;检索\n            &lt;/button&gt;&amp;nbsp;&amp;nbsp;\n        &lt;/div&gt;\n    &lt;/div&gt;\n    &lt;div id=&quot;result&quot;&gt;\n        &lt;div class=&quot;mui-card&quot; style=&quot;margin-top: 25px&quot;&gt;\n            &lt;div class=&quot;mui-card-content&quot;&gt;\n                &lt;div class=&quot;mui-card-content-inner&quot;&gt;\n                    可以在搜索框内输入书籍的全称，或者书籍的简称，系统支持智能检索功能。\n                &lt;/div&gt;\n            &lt;/div&gt;\n        &lt;/div&gt;\n    &lt;/div&gt;\n&lt;/div&gt;\n&lt;script src=&quot;https://others-1256773370.cos.ap-chengdu.myqcloud.com/booksearch/js/mui.min.js&quot;&gt;&lt;/script&gt;\n&lt;/body&gt;\n&lt;/html&gt;</code></pre></div>\n<ol start=\"5\">\n<li>最后通过 Webview 封装成一个 App。</li>\n</ol>\n<h2 id=\"▎总结\"><a href=\"#%E2%96%8E%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>其实这是一个低频使用的 App，毕竟单位图书室藏书不多，人流量也不大。如果将它部署在一个传统服务器上，可能不是个好的选择，毕竟服务器放在那里不管用不用都得花钱。</p>\n<p>所以这里选择了 Serverless 架构，部署在云函数上，按量付费的特点可以节省不少成本。同时，通过 APIGW 和 COS 的结合，完美解决了资源浪费的问题。<a href=\"https://cloud.tencent.com/product/sf\">腾讯云 Serverless Framework</a> 也是一个很好用的开发者工具，除此之外，这里还使用了云函数的 APIGW 触发器，轻巧地替代了传统 Web 框架和部分服务器软件的安装、使用和维护。</p>\n<p>这只是一个小应用，不过稍加改造，也能做成查询成绩的 App。Serverless 的应用场景还是很有想象力的。</p>\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/\">最佳实践</a> 里体验更多关于 Serverless 应用的开发！</p>","tableOfContents":"<ul>\n<li><a href=\"/blog/2020-01-08-book-search-app/#%E2%96%8E%E6%95%B4%E4%BD%93%E6%95%88%E6%9E%9C\">▎整体效果</a></li>\n<li><a href=\"/blog/2020-01-08-book-search-app/#%E2%96%8E%E5%8A%9F%E8%83%BD%E8%AE%BE%E8%AE%A1\">▎功能设计</a></li>\n<li><a href=\"/blog/2020-01-08-book-search-app/#%E2%96%8E%E5%85%B7%E4%BD%93%E5%AE%9E%E7%8E%B0\">▎具体实现</a></li>\n<li><a href=\"/blog/2020-01-08-book-search-app/#%E2%96%8E%E6%80%BB%E7%BB%93\">▎总结</a></li>\n</ul>"},"previousBlog":{"id":"c0fddb3f-85fb-5fbe-bf99-dcf7c6ad2959","frontmatter":{"thumbnail":"https://img.serverlesscloud.cn/2020327/1585306882746-9.jpg","authors":["serverless 社区"],"categories":["meetup"],"date":"2020-02-06T00:00:00.000Z","title":"Serverless 架构揭秘与静态网站部署实战 - 直播课","description":"Serverless 专家陈涛将分享 Serverless 的技术架构和开发实战，从 0 到 1 快速掌握 Serverless 开发知识","authorslink":["https://serverlesscloud.cn"],"translators":null,"translatorslink":null,"tags":["serverless","Meetup"],"keywords":"Serverless 全局变量组件,Serverless 单独部署组件,Serverless Component","outdated":null},"wordCount":{"words":106,"sentences":25,"paragraphs":25},"fileAbsolutePath":"/opt/build/repo/content/blog/2020-02-06-static-web-meetup.md","fields":{"slug":"/blog/2020-02-06-static-web-meetup/","keywords":["serverless","云函数","Serverless","课程","serverless","直播","架构","实战"]}},"nextBlog":{"id":"5334c37e-5036-5cdb-9610-2316a542eac3","frontmatter":{"thumbnail":"https://img.serverlesscloud.cn/2020326/1585185314084-9.jpg","authors":["serverless 社区"],"categories":["meetup"],"date":"2019-12-28T00:00:00.000Z","title":"Serverless 技术行沙龙","description":"来自深圳清华大学研究院 、Serverless 社区专家们围绕 Serverless + 5G，Serverless SSR 技术探索等话题展开深入分享","authorslink":["https://serverlesscloud.cn"],"translators":null,"translatorslink":null,"tags":["serverless","Meetup"],"keywords":"Serverless 全局变量组件,Serverless 单独部署组件,Serverless Component","outdated":null},"wordCount":{"words":184,"sentences":38,"paragraphs":37},"fileAbsolutePath":"/opt/build/repo/content/blog/2019-12-28-serverless-shanghai.md","fields":{"slug":"/blog/2019-12-28-serverless-shanghai/","keywords":["serverless","Serverless","serverless","Framework","应用"]}},"recommendBlogs":{"edges":[{"node":{"id":"4300b21c-7209-5256-86ff-0d38e3daec9b","frontmatter":{"thumbnail":"https://main.qcloudimg.com/raw/14f1c8eed372e76c1b139703b2f6d0fa.jpg","authors":["KieranMcCarthy"],"categories":["user-stories","engineering-culture"],"date":"2018-01-09T00:00:00.000Z","title":"我是如何在四年时间里，从厨师转行为 Serverless 应用开发者","description":"我是厨师出身，现在成为了一名 Serverless 应用开发者。","authorslink":["https://serverless.com/author/kieranmccarthy/"],"translators":["Aceyclee"],"translatorslink":["https://www.zhihu.com/people/Aceyclee"],"tags":["应用开发","Serverless"],"keywords":"Serverless 应用开发,Serverless 管理,厨师转行为 Serverless 应用开发者","outdated":null},"wordCount":{"words":285,"sentences":38,"paragraphs":36},"fileAbsolutePath":"/opt/build/repo/content/blog/2018-01-09-from-chef-to-serverless-developer-in-4-years.md","fields":{"slug":"/blog/2018-01-09-from-chef-to-serverless-developer-in-4-years/","keywords":["无服务器","无服务器开发","云函数","学习","Serverless","构建","Framework","开发者","服务器","应用","学位","简历"]}}},{"node":{"id":"713a0563-4bf9-5721-bacb-3b4ef609fe4a","frontmatter":{"thumbnail":"https://s3-us-west-2.amazonaws.com/assets.blog.serverless.com/camp-fire/camp-fire-housing-thumb.jpg","authors":["EricWyne"],"categories":["guides-and-tutorials","user-stories"],"date":"2018-12-05T00:00:00.000Z","title":"Serverless Twitter 机器人帮助为坎普山火受灾者安置住房","description":"加利福尼亚州的坎普山火致使数千人流离失所，为此，我构建了一个简单的 Serverless Twitter 机器人来帮助将受灾者安置在临时住房！","authorslink":["https://serverless.com/author/ericwyne/"],"translators":["Aceyclee"],"translatorslink":["zhihu.com/people/Aceyclee"],"tags":null,"keywords":null,"outdated":null},"wordCount":{"words":157,"sentences":26,"paragraphs":26},"fileAbsolutePath":"/opt/build/repo/content/blog/2018-12-05-serverless-twitter-camp-fire.md","fields":{"slug":"/blog/2018-12-05-serverless-twitter-camp-fire/","keywords":["serverless","无服务器","云函数","Serverless","org","住房","Twitter","函数","受灾","机器人","山火"]}}},{"node":{"id":"98602143-b837-5f50-a24f-3b1ec76044d7","frontmatter":{"thumbnail":"https://s3-us-west-2.amazonaws.com/assets.blog.serverless.com/sqquid/sqquid-serverless-thumb.jpg","authors":["RonPeled"],"categories":["user-stories"],"date":"2018-12-17T00:00:00.000Z","title":"SQQUID：100% 无服务器初创公司","description":"SQQUID 将 AWS Lambda 和无服务器框架用于其核心产品和营销网站。我们来看看一个完全无服务器的初创公司是怎样的。","authorslink":null,"translators":null,"translatorslink":null,"tags":null,"keywords":null,"outdated":null},"wordCount":{"words":266,"sentences":42,"paragraphs":42},"fileAbsolutePath":"/opt/build/repo/content/blog/2018-12-17-sqquid-one-hundred-percent-serverless.md","fields":{"slug":"/blog/2018-12-17-sqquid-one-hundred-percent-serverless/","keywords":["go","serverless","无服务器","无服务器架构","服务器","架构","Lambda","集成","FaaS","串行","系统"]}}},{"node":{"id":"29dc2e58-d2ba-56f9-aee1-d21b0bc62e0e","frontmatter":{"thumbnail":"https://s3-us-west-2.amazonaws.com/assets.blog.serverless.com/ao-com-story/ao-serverless-thumbnail.png","authors":["NickGottlieb"],"categories":["user-stories"],"date":"2019-04-24T00:00:00.000Z","title":"AO.com：逐渐转向无服务器优先","description":"AO.com 的 SCV 团队率先尝试无服务器服务。折服于无服务器框架的快速周转时间和低维护成本，整个团队逐渐转向无服务器优先。","authorslink":null,"translators":null,"translatorslink":null,"tags":null,"keywords":null,"outdated":null},"wordCount":{"words":236,"sentences":42,"paragraphs":35},"fileAbsolutePath":"/opt/build/repo/content/blog/2019-04-24-ao-serverless-first.md","fields":{"slug":"/blog/2019-04-24-ao-serverless-first/","keywords":["serverless","无服务器","服务器","团队","Lambda","功能","构建"]}}},{"node":{"id":"752d08d1-387a-5bde-acf3-98141baab294","frontmatter":{"thumbnail":"https://img.serverlesscloud.cn/2020414/1586871710979-%E5%85%AC%E5%85%B1%E7%94%A8.png","authors":["Anycodes"],"categories":["user-stories"],"date":"2019-06-20T00:00:00.000Z","title":"如何用 Serverless 为 Python 云函数打包依赖","description":"在使用无服务器云函数SCF时通常会遇到导入第三方库的问题，很多小伙伴比较头疼是：应该如何打包进去？这里，推荐几个不错的方法。","authorslink":["https://zhuanlan.zhihu.com/ServerlessGo"],"translators":null,"translatorslink":null,"tags":["云函数","Serverless"],"keywords":"Serverless,Serverless应用,无服务器云函数","outdated":null},"wordCount":{"words":81,"sentences":43,"paragraphs":43},"fileAbsolutePath":"/opt/build/repo/content/blog/2019-06-20-for-python-cloud-functions.md","fields":{"slug":"/blog/2019-06-20-for-python-cloud-functions/","keywords":["java","serverless","无服务器","无服务器云函数","云函数","serverlesscloud","安装","serverless","pillowtest"]}}},{"node":{"id":"2dc78814-9d77-555b-a1bb-ad202c8ec2d1","frontmatter":{"thumbnail":"https://s3-us-west-2.amazonaws.com/assets.blog.serverless.com/cloudforecast/thumbnail.png","authors":["FrancoisLagier"],"categories":["user-stories"],"date":"2019-08-07T00:00:00.000Z","title":"Serverless：初创企业的理想选择？（CloudForecast 案例分析）","description":"CloudForecast 是 2018 年成立的一家独立初创企业，本文将介绍他们决定选择 Serverless 的原因。","authorslink":["https://serverless.com/author/francoislagier/"],"translators":["Aceyclee"],"translatorslink":["zhihu.com/people/Aceyclee"],"tags":null,"keywords":null,"outdated":null},"wordCount":{"words":211,"sentences":29,"paragraphs":29},"fileAbsolutePath":"/opt/build/repo/content/blog/2019-08-07-serverless-for-startups.md","fields":{"slug":"/blog/2019-08-07-serverless-for-startups/","keywords":["serverless","云函数","serverless","函数","Serverless","utm","Framework","blog","CloudForecast","cloudforecast"]}}},{"node":{"id":"97450b07-658b-5207-8216-1c7b9b51b115","frontmatter":{"thumbnail":"https://img.serverlesscloud.cn/2020114/1578988490344-v2-8b2cd2c5275aa2c5a3c5083a148a7a9f_1200x500.jpg","authors":["Anycodes"],"categories":["user-stories"],"date":"2019-09-01T00:00:00.000Z","title":"如何通过 Serverless 与自然语言处理，让搜索引擎「看」到你的博客","description":"Serverless 与自然语言处理结合的一个小应用","authorslink":["https://www.zhihu.com/people/liuyu-43-97"],"translators":null,"translatorslink":null,"tags":["个人博客","serverless"],"keywords":"Serverless 自然语言处理","outdated":null},"wordCount":{"words":106,"sentences":34,"paragraphs":34},"fileAbsolutePath":"/opt/build/repo/content/blog/2019-09-01-search-engine-blog.md","fields":{"slug":"/blog/2019-09-01-search-engine-blog/","keywords":["serverless","云函数","keywords","serverlesscloud","summary"]}}},{"node":{"id":"ae4fd2f8-515c-5aec-b584-38427ef33f7e","frontmatter":{"thumbnail":"https://img.serverlesscloud.cn/2020114/1578989800047-part-00492-780.jpg","authors":["Anycodes"],"categories":["guides-and-tutorials","user-stories"],"date":"2019-09-16T00:00:00.000Z","title":"突破传统 OJ 瓶颈，「判题姬」接入云函数","description":"通过 Serverless 实现在线编程","authorslink":["https://www.zhihu.com/people/liuyu-43-97"],"translators":null,"translatorslink":null,"tags":["在线编程","云函数"],"keywords":"Serverless 在线编程,Serverless OJ","outdated":null},"wordCount":{"words":169,"sentences":30,"paragraphs":30},"fileAbsolutePath":"/opt/build/repo/content/blog/2019-09-16-online-Judge.md","fields":{"slug":"/blog/2019-09-16-online-Judge/","keywords":["python","serverless","云函数","代码","函数","serverless"]}}}],"totalCount":64}},"pageContext":{"isCreatedByStatefulCreatePages":false,"blogId":"03c5f35a-e201-5d91-bece-d455930b84f9","previousBlogId":"c0fddb3f-85fb-5fbe-bf99-dcf7c6ad2959","nextBlogId":"5334c37e-5036-5cdb-9610-2316a542eac3","categories":["user-stories"]}}}