[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"blog-categories":3,"blog-category-backend":78},[4,10,14,19,24,30,36,40,45,51,55,58,62,68,73],{"id":5,"databaseId":6,"name":7,"slug":8,"count":9},"dGVybTo1NQ==",55,"AI","ai",2,{"id":11,"databaseId":12,"name":13,"slug":13,"count":9},"dGVybTo2MQ==",61,"backend",{"id":15,"databaseId":16,"name":17,"slug":18,"count":16},"dGVybToyMA==",20,"Devops","devops",{"id":20,"databaseId":21,"name":22,"slug":22,"count":23},"dGVybToxNQ==",15,"frontend",25,{"id":25,"databaseId":26,"name":27,"slug":28,"count":29},"dGVybTo2MA==",60,"GAS","gas",1,{"id":31,"databaseId":32,"name":33,"slug":34,"count":35},"dGVybTo5",9,"HTML","html",4,{"id":37,"databaseId":38,"name":39,"slug":39,"count":29},"dGVybToyMg==",22,"mac",{"id":41,"databaseId":42,"name":43,"slug":43,"count":44},"dGVybTo2",6,"node",18,{"id":46,"databaseId":47,"name":48,"slug":49,"count":50},"dGVybTo2OQ==",69,"Nuxt","nuxt",3,{"id":52,"databaseId":53,"name":54,"slug":54,"count":50},"dGVybTo1NA==",54,"office",{"id":56,"databaseId":9,"name":57,"slug":57,"count":21},"dGVybToy","omekas",{"id":59,"databaseId":60,"name":61,"slug":61,"count":16},"dGVybToxNg==",16,"vue",{"id":63,"databaseId":64,"name":65,"slug":66,"count":67},"dGVybTo1",5,"WordPress","wordpress",13,{"id":69,"databaseId":29,"name":70,"slug":71,"count":72},"dGVybTox","無","uncategorized",null,{"id":74,"databaseId":75,"name":76,"slug":77,"count":9},"dGVybTo3MA==",70,"生活","life",[79,95,108,121,134,148,160,171,184,196,207,219,231,242,254,268,281,294,306],{"id":80,"slug":81,"title":82,"description":83,"content":84,"date":85,"image":86,"imageAlt":82,"author":87,"categories":90,"path":94,"minRead":29},3670,"aws-cloudfront-部署靜態網站出現-x-cache-error-from-cloudfront-訊息","AWS Cloudfront 部署靜態網站出現 X-cache: Error from cloudfront 訊息","AWS Cloudfront 部署靜態網站出現 x-cache: Error from cloudfront  […]","\u003Ch1>AWS Cloudfront 部署靜態網站出現 x-cache: Error from cloudfront 訊息\u003C\u002Fh1>\n\u003Cp>相關文章\u003Cbr \u002F>\n\u003Ca href=\"https:\u002F\u002Fstackoverflow.com\u002Fquestions\u002F59160472\u002Fhow-to-solve-x-cache-error-from-cloudfront-on-spa\">https:\u002F\u002Fstackoverflow.com\u002Fquestions\u002F59160472\u002Fhow-to-solve-x-cache-error-from-cloudfront-on-spa\u003C\u002Fa>\u003C\u002Fp>\n\u003Cp>\u003Ca href=\"https:\u002F\u002Fwww.withcoherence.com\u002Fpost\u002Faws-spa-routing-the-bad-the-ugly-and-the-uglier\">https:\u002F\u002Fwww.withcoherence.com\u002Fpost\u002Faws-spa-routing-the-bad-the-ugly-and-the-uglier\u003C\u002Fa>\u003C\u002Fp>\n\u003Cp>這個問題真的是恰巧才發現，也因此意識到不是瀏覽器看起來沒問題就好，網站建好後最好稍微掃一下標頭看看有沒有問題XD\u003C\u002Fp>\n\u003Cp>首先先敘述一下環境好了，這個專案建立在 AWS 上，使用了 S3 + Cloudfront 部署，是一個標準的靜態網站。只是因為業主需求，使用了子資料夾網域(sub-directory)，例如 https:\u002F\u002F\u003Cdoamin>\u002Fhome 。而我最終使用了 Html 5 的 \u003Ca href=\"https:\u002F\u002Fdeveloper.mozilla.org\u002Fdocs\u002FWeb\u002FAPI\u002FHistory?ref=blog.amowu.com\">History API\u003C\u002Fa> 來實作。某天剛好客戶做一些檢查發現跳紅字，就丟給我看，一看上面寫“X-cache: Error from cloudfront”，進而研究了一番。\u003C\u002Fp>\n\u003Ch2>什麼是 X-cache\u003C\u002Fh2>\n\u003Cp>X-cache 通常是 CDN 中會加入的一個標頭資訊，用來辨識你的請求是否由 CDN 傳出，如果成功狀態通常以 Hit 表示。\u003C\u002Fp>\n\u003Ch2>自定義錯誤頁面\u003C\u002Fh2>\n\u003Cp>如果使用過 Cloudfront 應該對這個設定不陌生，剛提到這專案使用了 \u003Ca href=\"https:\u002F\u002Fdeveloper.mozilla.org\u002Fdocs\u002FWeb\u002FAPI\u002FHistory?ref=blog.amowu.com\">History API\u003C\u002Fa> ，也因此要多設定 Cloufront 的自定義錯誤頁面(Custom Error Response)，不然會出現 404 Not Found 。\u003Cbr \u002F>\n設定方式很簡單：\u003C\u002Fp>\n\u003Col>\n\u003Cli>前往 CloudFront Distribution\u003C\u002Fli>\n\u003Cli>選擇 \u003Cstrong>Error Pages\u003C\u002Fstrong> 分頁籤，點選 \u003Cstrong>Create Custom Error Response\u003C\u002Fstrong> 建立自訂的錯誤回應\u003C\u002Fli>\n\u003Cli>HTTP Error Code 選擇 \u003Cstrong>404: Not Found\u003C\u002Fstrong>\u003C\u002Fli>\n\u003Cli>Error Caching Minimum TTL 輸入 \u003Cstrong>0\u003C\u002Fstrong>\u003C\u002Fli>\n\u003Cli>Customize Error Response 選擇 \u003Cstrong>Yes\u003C\u002Fstrong>\u003C\u002Fli>\n\u003Cli>Response Page Path 輸入 \u003Cstrong>\u002Findex.html\u003C\u002Fstrong>\u003C\u002Fli>\n\u003Cli>HTTP Response Code 選擇 \u003Cstrong>200: OK\u003C\u002Fstrong>\u003C\u002Fli>\n\u003Cli>設定完成\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Cp>很多教學到這邊，多半以為大功告成了～我在遇到這問題前也以為這樣就好，結果發現“X-cache: Error from cloudfront”，什麼！根據 stackoverflow 中的討論，這訊息表示 CDN 可能是失效，也就是你享受不到 Cloudfront 的快取。透過種種資料，我的理解如下，原因在於當訪問網站 https:\u002F\u002F\u003Cdoamin>時，請求將命中 S3 的 index.html 檔案。因為使用了 History API ，而進入到 https:\u002F\u002F\u003Cdoamin>\u002Fhome 時，則不會有任何後端請求，因此重新載入後，會再次詢問有無 https:\u002F\u002F\u003Cdoamin>\u002Fhome\u002Findex.html 這隻檔案，瀏覽器就會認為有問題，但透過上述方式雖然強制給予 200 狀態，但 S3 會拋回錯誤，導致 Cloudfront 也出現錯誤(Error from cloudfront)。\u003C\u002Fp>\n\u003Ch2>使用 Cloudfront 函數\u003C\u002Fh2>\n\u003Cp>查到的解決做法就是在中介層強制拋回 \u002Findex.html ，實作可以透過 AWS 的 edge lambda function ，但更新的做法為 Cloudfront 自己的函數功能，流程如下：\u003C\u002Fp>\n\u003Col>\n\u003Cli>前往 CloudFront Distribution\u003C\u002Fli>\n\u003Cli>左側進入 函數\u003C\u002Fli>\n\u003Cli>建立新函數，我取名為 SPA-route\u003Cbr \u002F>\n\u003Cimg decoding=\"async\" src=\"\u002Fapi\u002Fproxy-image?url=https%3A%2F%2Fpost.billxu.net%2Fwp-content%2Fuploads%2F2024%2F03%2F%E6%88%AA%E5%9C%96-2024-03-11-%E4%B8%8B%E5%8D%883.37.51.png\" alt=\"\" \u002F>\u003C\u002Fli>\n\u003Cli>使用以下程式碼\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Cpre>\u003Ccode class=\"language-jsx\">function handler(event){\n    \u002F\u002F Check if the request is for an internal route (doesn&#039;t have a file extension)\n    if (!event.request.uri.includes(&#039;.&#039;)) {\n        event.request.uri = &#039;\u002Findex.html&#039;; \n       }\n\n    return event.request;\n}\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Col>\n\u003Cli>在發布頁籤中，將要套用的分佈設定在關聯分佈中\u003C\u002Fli>\n\u003Cli>發布完成\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Cp>大概幾分鐘後，就能看到”X-cache: Hit from cloudfront ”囉～\u003C\u002Fp>\n","2024-03-19T10:40:48","",{"name":88,"link":89},"Chih Fan","http:\u002F\u002Fpost.billxu.net",[91,92,93],{"name":13,"slug":13},{"name":17,"slug":18},{"name":43,"slug":43},"\u002Fblog\u002Faws-cloudfront-部署靜態網站出現-x-cache-error-from-cloudfront-訊息",{"id":96,"slug":97,"title":97,"description":98,"content":99,"date":100,"image":101,"imageAlt":97,"author":102,"categories":103,"path":107,"minRead":29},3664,"在手機上進行前端網頁的debug","在手機上進行前端網頁的debug 說來真是羞愧，最近才知道有這個神兵協助手機上的除錯XD 當專案越寫越多樣化時 […]","\u003Ch1>在手機上進行前端網頁的debug\u003C\u002Fh1>\n\u003Cp>說來真是羞愧，最近才知道有這個神兵協助手機上的除錯XD 當專案越寫越多樣化時，常常會遇到一種狀況，例如專案在手機的某個 app 內建瀏覽器上打開時才會出現錯誤、或是顯示異常，由於沒有萬能的 F12 可以看 console，通常得靠經驗通靈，其實效率滿差的。\u003C\u002Fp>\n\u003Cp>如果這種狀況依然能打開 console 的話，就能加速除錯時間了，最近才知道有這樣的神兵利器啊！那就是 vconsole ！\u003C\u002Fp>\n\u003Cp>以 vite + vue 為例，就照以下方式：\u003C\u002Fp>\n\u003Ch2>安裝\u003C\u002Fh2>\n\u003Cpre>\u003Ccode class=\"language-jsx\">npm install vconsole\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch2>引入\u003C\u002Fh2>\n\u003Cp>我的作法有點偷懶，直接在 main.js 中引入\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-jsx\">import VConsole from &#039;vconsole&#039;;\n\n\u002F\u002F因為要限制只在開發期才顯示\nif (import.meta.env.MODE === &#039;development&#039;) {\n  const vConsole = new VConsole();\n}\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>其實這樣就可以了\u003C\u002Fp>\n\u003Cp>正常來說會有個按鈕「VCONSOLE」\u003Cbr \u002F>\n\u003Cimg decoding=\"async\" src=\"\u002Fapi\u002Fproxy-image?url=https%3A%2F%2Fpost.billxu.net%2Fwp-content%2Fuploads%2F2024%2F03%2F%E6%88%AA%E5%9C%96-2024-03-07-%E6%99%9A%E4%B8%8A10.01.43.png\" alt=\"\" \u002F>\u003C\u002Fp>\n\u003Cp>打開來就有個 視窗可以看資訊囉～～\u003Cbr \u002F>\n\u003Cimg decoding=\"async\" src=\"\u002Fapi\u002Fproxy-image?url=https%3A%2F%2Fpost.billxu.net%2Fwp-content%2Fuploads%2F2024%2F03%2F%E6%88%AA%E5%9C%96-2024-03-07-%E6%99%9A%E4%B8%8A10.02.38-1024x349.png\" alt=\"\" \u002F>\u003C\u002Fp>\n","2024-03-10T10:04:14","\u002Fapi\u002Fproxy-image?url=https%3A%2F%2Fpost.billxu.net%2Fwp-content%2Fuploads%2F2023%2F01%2F640px-Node.js_logo.svg.png",{"name":88,"link":89},[104,105,106],{"name":22,"slug":22},{"name":43,"slug":43},{"name":61,"slug":61},"\u002Fblog\u002F在手機上進行前端網頁的debug",{"id":109,"slug":110,"title":111,"description":112,"content":113,"date":114,"image":115,"imageAlt":111,"author":116,"categories":117,"path":120,"minRead":29},3642,"aws-cloudfront-計價評估方式","AWS CloudFront 計價評估方式","AWS CloudFront 計價評估方式 剛開始接觸 AWS 時最怕被客戶問到「那這大概要多少錢？」，老實說 […]","\u003Ch1>AWS CloudFront 計價評估方式\u003C\u002Fh1>\n\u003Cp>剛開始接觸 AWS 時最怕被客戶問到「那這大概要多少錢？」，老實說用多少算多少還真的很難講呀，另外就是會被 AWS 複雜的服務架構計價搞得頭昏腦脹。\u003Cbr \u002F>\n以 CloudFront 來說，最近終於找到一篇\u003Ca href=\"https:\u002F\u002Fdocs.aws.amazon.com\u002Fzh_tw\u002FAmazonCloudFront\u002Flatest\u002FDeveloperGuide\u002FCloudFrontPricing.html\" title=\"官方的解釋\">官方的解釋\u003C\u002Fa>讓我很確定價格的算法。就來記錄一下囉。\u003C\u002Fp>\n\u003Cp>先來看這張圖，來自 AWS。\u003Cbr \u002F>\n\u003Cfigure id=\"attachment_3643\" aria-describedby=\"caption-attachment-3643\" style=\"width: 542px\" class=\"wp-caption alignnone\">\u003Cimg loading=\"lazy\" decoding=\"async\" src=\"\u002Fapi\u002Fproxy-image?url=https%3A%2F%2Fpost.billxu.net%2Fwp-content%2Fuploads%2F2024%2F02%2FCharges.png\" alt=\"\" width=\"542\" height=\"498\" class=\"size-full wp-image-3643\" srcset=\"https:\u002F\u002Fpost.billxu.net\u002Fwp-content\u002Fuploads\u002F2024\u002F02\u002FCharges.png 542w, https:\u002F\u002Fpost.billxu.net\u002Fwp-content\u002Fuploads\u002F2024\u002F02\u002FCharges-300x276.png 300w\" sizes=\"auto, (max-width: 542px) 100vw, 542px\" \u002F>\u003Cfigcaption id=\"caption-attachment-3643\" class=\"wp-caption-text\">圖片取自AWS\u003C\u002Ffigcaption>\u003C\u002Ffigure>\u003C\u002Fp>\n\u003Cp>以圖片架構來看，會有三個可能的收費點。\u003C\u002Fp>\n\u003Col>\n\u003Cli>Origin Server：如果是 Amazon S3 服務，那就會有儲存的使用費。\u003C\u002Fli>\n\u003Cli>節點回應物件的請求會產生各種費用，例如流量、請求數等等\u003Cbr \u002F>\n3.如果有一些 POST 請求行為，也會有另外的費用，在文件中有舉例。\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Cp>像我使用的架構是 S3 + CloudFront 建立一個靜態網站，那基本上只會產生 1 和 2 的費用，再來就要測試每次一請求花費的流量可能是多少，然後再敲計算機了。有這張圖真的很清楚了解收費點，希望我的理解沒錯XD\u003C\u002Fp>\n","2024-02-04T10:00:50","\u002Fapi\u002Fproxy-image?url=https%3A%2F%2Fpost.billxu.net%2Fwp-content%2Fuploads%2F2024%2F02%2F04be1b7b-b315-402e-8171-eb99a551fc61.jpeg",{"name":88,"link":89},[118,119],{"name":13,"slug":13},{"name":17,"slug":18},"\u002Fblog\u002Faws-cloudfront-計價評估方式",{"id":122,"slug":123,"title":124,"description":125,"content":126,"date":127,"image":128,"imageAlt":124,"author":129,"categories":130,"path":133,"minRead":50},3630,"使用-github-gas-建立-liff-問卷","使用 Github + GAS 建立 LIFF 問卷","使用 Github + GAS 建立 LIFF 問卷 之前有個專案需求是透過 LINE bot + Googl […]","\u003Ch1>使用 Github + GAS 建立 LIFF 問卷\u003C\u002Fh1>\n\u003Cp>之前有個專案需求是透過 LINE bot + Google Form 建立一套可以進行線上預約的LINE OA(官方帳號)， 但為了進行 UID 的對應設計一套迂迴的流程，實在不爽快。現在有機會透過 LIFF 導入，能簡化這邊的流程。\u003C\u002Fp>\n\u003Cp>由於團隊適應關係，繼續沿用 Google sheet 來做管理，因此需要使用 GAS 來開發一組 API 。\u003C\u002Fp>\n\u003Cp>基礎概念很簡單：\u003C\u002Fp>\n\u003Col>\n\u003Cli>先部署 LIFF 頁面\u003C\u002Fli>\n\u003Cli>製作 LIFF 頁面的問卷\u003C\u002Fli>\n\u003Cli>部署 GAS 為基礎的 API\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Ch1>LIFF 頁面部署\u003C\u002Fh1>\n\u003Cp>這邊使用的開發環境為 Vite + Vue3.3 + Tailwind + axios。\u003C\u002Fp>\n\u003Ch3>1. 宣告 LIFF 導入\u003C\u002Fh3>\n\u003Cp>LIFF 的導入可以參考\u003Ca href=\"https:\u002F\u002Fpost.billxu.net\u002Fblog\u002F2023\u002F01\u002F10\u002Fvue%e5%88%9d%e6%8e%a2liff%e7%9a%84%e4%b8%80%e4%ba%9b%e7%ad%86%e8%a8%98-%e9%96%8b%e7%99%bc%e7%af%87\u002F\" title=\"這篇\">這篇\u003C\u002Fa>\u003Cbr \u002F>\n宣告 import liff 後，在 onMounted 時宣告 init，如下\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-jsx\">import liff from &quot;@line\u002Fliff&quot;;\n\nonMounted(async () =&gt; {\n  liff\n  .init({\n    liffId: &quot;&lt;LIFF ID&gt;&quot;, \u002F\u002F 從LIFF頁面中拿到的ID\n  })\n  .then(() =&gt; { \n    if(liff.isLoggedIn()){\n      \u002F\u002F做你想要做的\n      isLogin.value = liff.isLoggedIn();\n      userid.value = liff.getDecodedIDToken().sub; \u002F\u002F這是UID\n    }\n\n  })\n  .catch((err) =&gt; {\n    console.log(err.code, err.message);\n  });\n})\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch3>2.在問卷頁中加入送出表單\u003C\u002Fh3>\n\u003Cp>就是一個 submitForm 。裡面預留 API 的位置，已經撰寫問卷本體。\u003Cbr \u002F>\n到這邊 LIFF 大致上就做完了。接下來要部署到 Github Page 上\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-jsx\">&lt;form class=&quot;flex flex-col items-center&quot; @submit.prevent=&quot;submitForm&quot;&gt;\n&lt;label for=&quot;name&quot; class=&quot;block mt-4&quot;&gt;請問您的姓名：&lt;span class=&quot;text-red-500 text-sm&quot;&gt;＊（請輸入文字）&lt;\u002Fspan&gt;&lt;\u002Flabel&gt;\n  &lt;input type=&quot;text&quot; id=&quot;name&quot; v-model=&quot;name&quot; placeholder=&quot;姓名&quot; class=&quot;border border-gray-300 rounded-md px-2 py-1 mt-1&quot; pattern=&quot;[A-Za-z]+&quot; title=&quot;請輸入有效的姓名（只能包含字母）&quot; required \u002F&gt;\n\n  &lt;label for=&quot;phone&quot; class=&quot;block mt-4&quot;&gt;請問您的連絡電話：&lt;span class=&quot;text-red-500 text-sm&quot;&gt;＊（請輸入數字）&lt;\u002Fspan&gt;&lt;\u002Flabel&gt;\n  &lt;input type=&quot;text&quot; id=&quot;phone&quot; v-model=&quot;phone&quot; placeholder=&quot;連絡電話&quot; class=&quot;border border-gray-300 rounded-md px-2 py-1 mt-1&quot; pattern=&quot;[0-9]+&quot; title=&quot;請輸入有效的連絡電話（只能包含數字）&quot; required \u002F&gt;\n\n  &lt;label for=&quot;purchaseDate&quot; class=&quot;block mt-4&quot;&gt;請問您的購買時間：&lt;\u002Flabel&gt;\n  &lt;input type=&quot;date&quot; id=&quot;purchaseDate&quot; v-model=&quot;purchaseDate&quot; class=&quot;border border-gray-300 rounded-md px-2 py-1 mt-1&quot; required \u002F&gt;\n\n  &lt;label for=&quot;storeAddress&quot; class=&quot;block mt-4&quot;&gt;請問您購買的經銷門市地址：&lt;\u002Flabel&gt;\n  &lt;select id=&quot;storeAddress&quot; v-model=&quot;storeAddress&quot; class=&quot;border border-gray-300 rounded-md px-2 py-1 mt-1&quot; required&gt;\n      &lt;option value=&quot;&quot;&gt;請選擇&lt;\u002Foption&gt;\n      &lt;option value=&quot;address1&quot;&gt;A門市&lt;\u002Foption&gt;\n      &lt;option value=&quot;address2&quot;&gt;B門市&lt;\u002Foption&gt;\n      &lt;option value=&quot;address3&quot;&gt;C門市&lt;\u002Foption&gt;\n  &lt;\u002Fselect&gt;\n&lt;\u002Fform&gt;\n\nconst submitForm = () =&gt; {\n\n    fconst formData = new FormData();\n  formData.append(&#039;name&#039;, name.value);\n  formData.append(&#039;phone&#039;, phone.value);\n  formData.append(&#039;userid&#039;, user.userid);\n\n    axios({\n            method: &#039;post&#039;,\n            url: &#039;&lt;GAS api&gt;&#039;,\n            data: formData,\n            headers: { &#039;Content-Type&#039;: &#039;multipart\u002Fform-data&#039; }\n        })\n        .then(response =&gt; {\n            \u002F\u002F 成功\n            console.log(response);\n        })\n        .catch(error =&gt; {\n            \u002F\u002F 失敗\n\n        });\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch2>使用  Github Actions 部署\u003C\u002Fh2>\n\u003Cp>為了省成本而部署在 Github Page 上，使用 Github Actions 來進行，請參考\u003Ca href=\"https:\u002F\u002Fpost.billxu.net\u002Fblog\u002F2024\u002F01\u002F08\u002F%e4%bd%bf%e7%94%a8-github-action-%e9%83%a8%e7%bd%b2-github-pages\u002F\" title=\"這篇\">這篇\u003C\u002Fa>。\u003Cbr \u002F>\ngh-pages-deploy.yml\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-yaml\">name: Deploy\n\non:\n  push:\n    branches:\n      - main  # 將此處更改為你的默認分支名稱，例如：master\n\njobs:\n  build-and-deploy:\n    runs-on: ubuntu-latest\n\n    steps:\n    - name: Checkout\n      uses: actions\u002Fcheckout@v2\n\n    - name: Install &amp; Build # 執行指令\n      run: |\n        npm install\n        npm run build \n\n    - name: Deploy\n      uses: JamesIves\u002Fgithub-pages-deploy-action@4.1.5\n      with:\n        branch: gh-pages  # GitHub Pages 的分支\n        folder: dist  # build 後的檔案夾\n\n    - name: Debug git error\n      run: git status\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch2>登記為 LIFF\u003C\u002Fh2>\n\u003Cp>部署完後，記得要去 LINE 開發者功能中登記 LIFF ，可以參考\u003Ca href=\"https:\u002F\u002Fpost.billxu.net\u002Fblog\u002F2023\u002F01\u002F10\u002Fline%e5%88%9d%e6%8e%a2liff%e7%9a%84%e4%b8%80%e4%ba%9b%e7%ad%86%e8%a8%98-%e7%94%b3%e8%ab%8b%e7%af%87\u002F\" title=\"這篇\">這篇\u003C\u002Fa>。\u003Cbr \u002F>\n就拿 Github Page 的 url 就可以。\u003C\u002Fp>\n\u003Ch1>GAS 部署\u003C\u002Fh1>\n\u003Cp>接著要製作 GAS 的 API ，請參考\u003Ca href=\"https:\u002F\u002Fwww.google.com\u002Fscript\u002Fstart\u002F\">官方文件\u003C\u002Fa>。\u003C\u002Fp>\n\u003Ch3>建立 POST 接口\u003C\u002Fh3>\n\u003Cpre>\u003Ccode class=\"language-jsx\">function doPost(e) {\n  var param = e.parameter;\n\n  const data = {\n    name: param.name,\n    phone: param.phone,\n    userID: param.userid\n  }\n  setData(data); \u002F\u002F將資料放進表單\n  return ContentService.createTextOutput(JSON.stringify({param}))\n                       .setMimeType(ContentService.MimeType.JSON) \u002F\u002F為了debug 先回傳資料\n}\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch3>將資料放進表單\u003C\u002Fh3>\n\u003Cp>因為我是在別的文件開啟GAS，所以另外指定了 fileId 。簡單說就是看你的 Google sheet 位置，像這樣：\u003Ca href=\"https:\u002F\u002Fdocs.google.com\u002Fspreadsheets\u002Fd\u002F\">https:\u002F\u002Fdocs.google.com\u002Fspreadsheets\u002Fd\u002F\u003C\u002Fa>\u003CID>，取用\u003CID>那段就對了，記得要還要指定工作表喔。\u003C\u002Fp>\n\u003Cp>如果是同一個文件，可以使用\u003Ccode>getActiveSpreadsheet\u003C\u002Fcode>\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-jsx\">function setData(data){\n    var fileId = &#039;&lt;ID&gt;&#039;; \u002F\u002F 請用你的檔案ID替換此處\n    \u002F\u002F 透過ID開啟另一個檔案\n    var file = SpreadsheetApp.openById(fileId);\n    \u002F\u002F 獲取另一個檔案的工作表\n    var sheet = file.getSheetByName(&#039;&lt;工作表名稱&gt;&#039;);\n\n    const userID = data.userID;\n    var lastRow = sheet.getLastRow();\n\n        \u002F\u002F 如果是第一筆，先做標題\n    if (lastRow == 0) {\n      var headerRow = [&#039;name&#039;,&#039;phone&#039;,&#039;UID&#039;];\n      sheet.appendRow(headerRow);\n    }\n\n    \u002F\u002F 整理資料成陣列(注意是二維陣列)\n    var values = [[data.name,data.phone,userID]];\n\n    \u002F\u002F 放資料\n    sheet.getRange(lastRow + 1, 1, values.length, values[0].length).setValues(values);\n}\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch3>部署位置\u003C\u002Fh3>\n\u003Cp>前面 LIFF 部署時，有個 \u003CGAS api> ，就是這時 GAS 部署完後會有組位置像這樣：\u003Ca href=\"https:\u002F\u002Fscript.google.com\u002Fmacros\u002Fs\u002Fxxxxxxxx\u002Fexec，將這串貼到\">https:\u002F\u002Fscript.google.com\u002Fmacros\u002Fs\u002Fxxxxxxxx\u002Fexec，將這串貼到\u003C\u002Fa> \u003CGAS api> 。\u003C\u002Fp>\n\u003Cp>這樣就可以建立一個可以取得 UID 的 LIFF 頁面，並且透過 GAS 存到 Google sheet 中，但記得 GAS API 有一些\u003Ca href=\"https:\u002F\u002Fdevelopers.google.com\u002Fapps-script\u002Fguides\u002Fservices\u002Fquotas?hl=zh-tw\">使用上限制\u003C\u002Fa>，用來建立微型服務絕對沒問題，但如果有用量上考量，建議不要這樣玩XD\u003C\u002Fp>\n","2024-01-16T16:20:22","\u002Fapi\u002Fproxy-image?url=https%3A%2F%2Fpost.billxu.net%2Fwp-content%2Fuploads%2F2024%2F01%2Fb40e6ed5-cbed-4701-b2c9-68e84f0754d4.jpeg",{"name":88,"link":89},[131,132],{"name":43,"slug":43},{"name":61,"slug":61},"\u002Fblog\u002F使用-github-gas-建立-liff-問卷",{"id":135,"slug":136,"title":137,"description":138,"content":139,"date":140,"image":141,"imageAlt":137,"author":142,"categories":143,"path":147,"minRead":29},3615,"使用-github-action-部署-github-pages","使用 Github Action 部署 Github Pages","使用 Github Action 部署 Github Pages (node) 記錄一下使用 Github A […]","\u003Ch1>使用 Github Action 部署 Github Pages (node)\u003C\u002Fh1>\n\u003Cp>記錄一下使用 Github Action 部署 Github Pages 的流程\u003Cbr \u002F>\n我這邊需求是 node + vue\u003C\u002Fp>\n\u003Cp>步驟很簡單：\u003C\u002Fp>\n\u003Col>\n\u003Cli>設定個人用的 token\u003C\u002Fli>\n\u003Cli>撰寫 Action 的 yml\u003C\u002Fli>\n\u003Cli>設定 repo\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Ch2>設定個人token\u003C\u002Fh2>\n\u003Cp>記得一兩年前 Github 就棄用輸入密碼的方式允許外部方式連線 repo，都必須要用 token。設定方式如下\u003C\u002Fp>\n\u003Cp>先在大頭像找到 setting\u003Cbr \u002F>\n\u003Cimg decoding=\"async\" src=\"\u002Fapi\u002Fproxy-image?url=https%3A%2F%2Fpost.billxu.net%2Fwp-content%2Fuploads%2F2024%2F01%2F%E6%88%AA%E5%9C%96-2024-01-05-%E4%B8%8A%E5%8D%8811.34.14-694x1024.png\" alt=\"\" \u002F>\u003C\u002Fp>\n\u003Cp>接著在左側欄找到 Developer Settings\u003Cbr \u002F>\n\u003Cimg decoding=\"async\" src=\"\u002Fapi\u002Fproxy-image?url=https%3A%2F%2Fpost.billxu.net%2Fwp-content%2Fuploads%2F2024%2F01%2F%E6%88%AA%E5%9C%96-2024-01-05-%E4%B8%8A%E5%8D%8811.34.34.png\" alt=\"\" \u002F>\u003C\u002Fp>\n\u003Cp>選擇 Token(classic)\u003Cbr \u002F>\n\u003Cimg decoding=\"async\" src=\"\u002Fapi\u002Fproxy-image?url=https%3A%2F%2Fpost.billxu.net%2Fwp-content%2Fuploads%2F2024%2F01%2F%E6%88%AA%E5%9C%96-2024-01-05-%E4%B8%8A%E5%8D%8811.35.09.png\" alt=\"\" \u002F>\u003C\u002Fp>\n\u003Cp>選擇 Generate new token (classic)\u003Cbr \u002F>\n\u003Cimg decoding=\"async\" src=\"\u002Fapi\u002Fproxy-image?url=https%3A%2F%2Fpost.billxu.net%2Fwp-content%2Fuploads%2F2024%2F01%2F%E6%88%AA%E5%9C%96-2024-01-05-%E4%B8%8A%E5%8D%8811.36.11-1024x232.png\" alt=\"\" \u002F>\u003C\u002Fp>\n\u003Cp>scopes 至少要勾以下範圍\u003Cbr \u002F>\n\u003Cimg decoding=\"async\" src=\"\u002Fapi\u002Fproxy-image?url=https%3A%2F%2Fpost.billxu.net%2Fwp-content%2Fuploads%2F2024%2F01%2F%E6%88%AA%E5%9C%96-2024-01-05-%E4%B8%8A%E5%8D%8811.37.21.png\" alt=\"\" \u002F>\u003C\u002Fp>\n\u003Ch2>設定 yml\u003C\u002Fh2>\n\u003Cp>這邊就不解釋 Action 的做法\u003Cbr \u002F>\n請參考 \u003Ca href=\"https:\u002F\u002Fdocs.github.com\u002Fen\u002Factions\" title=\"文件\">文件\u003C\u002Fa>\u003Cbr \u002F>\n我用的是這組 action ：JamesIves\u002Fgithub-pages-deploy-action@4.1.5\u003C\u002Fp>\n\u003Cp>相關參數寫在這邊:\u003Ca href=\"https:\u002F\u002Fgithub.com\u002FJamesIves\u002Fgithub-pages-deploy-action\">https:\u002F\u002Fgithub.com\u002FJamesIves\u002Fgithub-pages-deploy-action\u003C\u002Fa>\u003C\u002Fp>\n\u003Ch3>部署在自己的的專案\u003C\u002Fh3>\n\u003Cpre>\u003Ccode class=\"language-yaml\">name: Deploy\n\non:\n  push:\n    branches:\n      - main\n\njobs:\n  build-and-deploy:\n    runs-on: ubuntu-latest\n\n    steps:\n    - name: Checkout\n      uses: actions\u002Fcheckout@v2\n\n    - name: Install &amp; Build\n      run: |\n        npm install\n        npm run build \n\n      - name: Deploy\n        uses: JamesIves\u002Fgithub-pages-deploy-action@4.1.5\n        with:\n          branch: gh-pages\n          folder: dist\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch3>部署到其他專案\u003C\u002Fh3>\n\u003Cp>${{ secrets.ACCESS_TOKEN }} 就是從 ACCESS_TOKEN 的設定取值，不要將 token 暴露在明碼上\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-yaml\">name: Deploy\n\non:\n  push:\n    branches:\n      - main\n\njobs:\n  build-and-deploy:\n    runs-on: ubuntu-latest\n\n    steps:\n    - name: Checkout\n      uses: actions\u002Fcheckout@v2\n\n    - name: Install &amp; Build\n      run: |\n        npm install\n        npm run build \n\n    - name: Deploy Antoher Repo\n      uses: JamesIves\u002Fgithub-pages-deploy-action@4.1.5\n      with:\n        branch: gh-pages\n        folder: dist\n        token: ${{ secrets.ACCESS_TOKEN }}\n        repository-name:  Sakasky\u002FSEZ\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>要另外在專案中設定 token\u003Cbr \u002F>\n\u003Cimg decoding=\"async\" src=\"\u002Fapi\u002Fproxy-image?url=https%3A%2F%2Fpost.billxu.net%2Fwp-content%2Fuploads%2F2024%2F01%2F%E6%88%AA%E5%9C%96-2024-01-05-%E4%B8%8A%E5%8D%8811.42.37.png\" alt=\"\" \u002F>\u003C\u002Fp>\n\u003Cp>\u003Cimg decoding=\"async\" src=\"\u002Fapi\u002Fproxy-image?url=https%3A%2F%2Fpost.billxu.net%2Fwp-content%2Fuploads%2F2024%2F01%2F%E6%88%AA%E5%9C%96-2024-01-05-%E4%B8%8A%E5%8D%8811.42.46-1024x612.png\" alt=\"\" \u002F>\u003C\u002Fp>\n\u003Cp>\u003Cimg decoding=\"async\" src=\"\u002Fapi\u002Fproxy-image?url=https%3A%2F%2Fpost.billxu.net%2Fwp-content%2Fuploads%2F2024%2F01%2F%E6%88%AA%E5%9C%96-2024-01-05-%E4%B8%8A%E5%8D%8811.42.53-1024x478.png\" alt=\"\" \u002F>\u003Cbr \u002F>\n以 yml 為例子\u003Cbr \u002F>\nName就設定 ACCESS_TOKEN\u003Cbr \u002F>\n然後將 token 放到 Secret 裡面\u003C\u002Fp>\n\u003Ch3>打開 Github Pages 設定\u003C\u002Fh3>\n\u003Cp>測試過要先設定或不設定都可以，若不先設定，而直接部署的話也可以\u003Cbr \u002F>\n\u003Cimg decoding=\"async\" src=\"\u002Fapi\u002Fproxy-image?url=https%3A%2F%2Fpost.billxu.net%2Fwp-content%2Fuploads%2F2024%2F01%2F%E6%88%AA%E5%9C%96-2024-01-05-%E4%B8%8A%E5%8D%8811.43.16.png\" alt=\"\" \u002F>\u003C\u002Fp>\n\u003Ch1>完成\u003C\u002Fh1>\n\u003Cp>只要每次 commit \u002F push 完，Action 就會自動幫部署網頁啦，超級方便！\u003C\u002Fp>\n","2024-01-08T10:03:34","\u002Fapi\u002Fproxy-image?url=https%3A%2F%2Fpost.billxu.net%2Fwp-content%2Fuploads%2F2024%2F01%2F7430fe6a-8f97-43ae-89ab-85f8bde95b18.jpeg",{"name":88,"link":89},[144,145,146],{"name":17,"slug":18},{"name":22,"slug":22},{"name":43,"slug":43},"\u002Fblog\u002F使用-github-action-部署-github-pages",{"id":149,"slug":150,"title":151,"description":152,"content":153,"date":154,"image":86,"imageAlt":151,"author":155,"categories":156,"path":159,"minRead":29},3572,"aws-上g3m-4部署-node-nginx","AWS 上使用 EC2 部署 node + Nginx + https","AWS 上使用 EC2 部署 node + Nginx 如果要在 AWS 的 EC2 上部署 node ，多半 […]","\u003Ch1>AWS 上使用 EC2 部署 node + Nginx\u003C\u002Fh1>\n\u003Cp>如果要在 AWS 的 EC2 上部署 node ，多半因為 SSL 關係建議多部署一個 Nginx 作反向代理，這樣就能設定 https 來部署服務。\u003Cbr \u002F>\n大致流程如下：\u003C\u002Fp>\n\u003Col>\n\u003Cli>安裝pm2\u003C\u002Fli>\n\u003Cli>安裝nginx\u003C\u002Fli>\n\u003Cli>設定反向代理\u003C\u002Fli>\n\u003Cli>安裝certbot\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Ch2>安裝pm2\u003C\u002Fh2>\n\u003Cp>pm2是一個可以讓node在背景跑的套件\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-jsx\">sudo npm i pm2 -g\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>安裝完後跑pm2\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-jsx\">pm2 start app \u002F\u002F會去執行app資料夾中的index\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>到這部可以先試試看程式有沒有跑起來，直接看 EC2 執行個體的公開IP就可以囉\u003C\u002Fp>\n\u003Ch2>安裝nginx\u003C\u002Fh2>\n\u003Cpre>\u003Ccode class=\"language-jsx\">$ sudo yum install nginx\n$ sudo systemctl enable nginx &amp;&amp; sudo systemctl start nginx\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch3>設定反向代理\u003C\u002Fh3>\n\u003Cp>建立設定文件 configuration.conf\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-jsx\">$ sudo vim \u002Fetc\u002Fnginx\u002Fconf.d\u002Fconfiguration.conf\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>輸入以下內容\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-jsx\">server {\nserver_name &lt;你的網域&gt;;\nlocation \u002F {\n        proxy_pass http:\u002F\u002F127.0.0.1:3000; \u002F\u002Fnode使用的port\n      }\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>儲存後，重新跑nginx\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-jsx\">$ sudo nginx -t\n$ sudo systemctl restart nginx\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>到這邊可以瀏覽網域(http)看看，如果發現失敗可以先檢查 route 53 或是你的 DNS 有無進行指向設定(A record)。\u003C\u002Fp>\n\u003Ch2>安裝Certbot\u003C\u002Fh2>\n\u003Cp>因為執行 certbot 需要使用 python 所以先處理環境\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-jsx\">$ sudo python3 -m venv \u002Fopt\u002Fcertbot\u002F\n$ sudo \u002Fopt\u002Fcertbot\u002Fbin\u002Fpip install --upgrade pip\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>接著安裝 certbot\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-jsx\">$ sudo \u002Fopt\u002Fcertbot\u002Fbin\u002Fpip install certbot certbot-nginx\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>設定 certbot 為可執行指令\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-jsx\">sudo ln -s \u002Fopt\u002Fcertbot\u002Fbin\u002Fcertbot \u002Fusr\u002Fbin\u002Fcertbot\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>跑看看\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-jsx\">$ sudo certbot --nginx\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>這時會跳出執行詢問，按照詢問填入就可以，若有錯誤則依照錯誤處理。\u003C\u002Fp>\n\u003Cp>最後顯示成功時，輸入你的網域應該就能看到正確的指向了。如果失敗，我自己是忘記設定 EC2 的安全群組，記得要把會用到的 port 都打開。\u003C\u002Fp>\n\u003Ch3>排程處理\u003C\u002Fh3>\n\u003Cp>記得憑證會過期，所以要留下定期處理憑證\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-jsx\">$ echo &quot;0 0,12 * * * root \u002Fopt\u002Fcertbot\u002Fbin\u002Fpython -c &#039;import random; import time; time.sleep(random.random() * 3600)&#039; &amp;&amp; sudo certbot renew -q&quot; | sudo tee -a \u002Fetc\u002Fcrontab &gt; \u002Fdev\u002Fnull\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch2>參考資料\u003C\u002Fh2>\n\u003Cp>\u003Ca href=\"https:\u002F\u002Fmedium.com\u002F@chiragmehta900\u002Fdeploy-a-node-js-application-on-aws-ec2-with-nginx-with-ssl-47fb163d0909\">https:\u002F\u002Fmedium.com\u002F@chiragmehta900\u002Fdeploy-a-node-js-application-on-aws-ec2-with-nginx-with-ssl-47fb163d0909\u003C\u002Fa>\u003C\u002Fp>\n\u003Cp>\u003Ca href=\"https:\u002F\u002Fdev.to\u002F0xfedev\u002Fhow-to-install-nginx-as-reverse-proxy-and-configure-certbot-on-amazon-linux-2023-2cc9\">https:\u002F\u002Fdev.to\u002F0xfedev\u002Fhow-to-install-nginx-as-reverse-proxy-and-configure-certbot-on-amazon-linux-2023-2cc9\u003C\u002Fa>\u003C\u002Fp>\n","2023-11-16T10:00:10",{"name":88,"link":89},[157,158],{"name":17,"slug":18},{"name":43,"slug":43},"\u002Fblog\u002Faws-上g3m-4部署-node-nginx",{"id":161,"slug":162,"title":163,"description":164,"content":165,"date":166,"image":101,"imageAlt":163,"author":167,"categories":168,"path":170,"minRead":29},3456,"錯誤訊息-error0308010cdigital-envelope-routinesunsupported","錯誤訊息 error:0308010C:digital envelope routines::unsupported","錯誤訊息 error:0308010C:digital envelope routines::unsuppor […]","\u003Ch1>錯誤訊息 error:0308010C:digital envelope routines::unsupported\u003C\u002Fh1>\n\u003Cp>在測試一個別人的專案時遇到此錯誤訊息\u003Cem>error:0308010C:digital envelope routines::unsupported\u003C\u002Fem> 找了一下解決方式後有一些新發現。\u003C\u002Fp>\n\u003Cp>會有這錯誤原因可能出自於node.js v17後調整了而OpenSSL3，OpenSSL3對於密鑰和演算法有了更近一步的限制，因此造成某些套件會出現這問題。\u003Cbr \u002F>\n也因此這個錯誤訊息最容易查到的解決方式是將版本。\u003C\u002Fp>\n\u003Ch2>解法1\u003C\u002Fh2>\n\u003Cp>就是降低版本，降低到node.js v17之前，多半就可以解決這個問題。我自己是降低到v14就順利執行\u003C\u002Fp>\n\u003Cp>但也有另個解決方式。\u003C\u002Fp>\n\u003Ch2>解法2\u003C\u002Fh2>\n\u003Cp>執行前強制使用舊版本的OpenSSL方法，當然這個做法也有缺點，能用OpenSSL3就盡量用，也是比較安全。\u003C\u002Fp>\n\u003Cpre>\u003Ccode>export NODE_OPTIONS=--openssl-legacy-provider\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch2>最終採用解法3\u003C\u002Fh2>\n\u003Cp>當然最後我使用了兩全其美的做法，就是採用解法1，降低版本，然後放到docker裡面執行，完美。\u003C\u002Fp>\n\u003Cp>要選擇怎麼處理就看遇到的狀況囉。\u003C\u002Fp>\n","2023-03-13T20:00:18",{"name":88,"link":89},[169],{"name":43,"slug":43},"\u002Fblog\u002F錯誤訊息-error0308010cdigital-envelope-routinesunsupported",{"id":172,"slug":173,"title":174,"description":175,"content":176,"date":177,"image":178,"imageAlt":174,"author":179,"categories":180,"path":183,"minRead":29},3430,"如何與chatgpt合作生產並且部署一個短網址服務","如何與chatGPT合作生產並且部署一個短網址服務","如何與chatGPT合作生產並且部署一個短網址服務 chatGPT問世之後一直想來與chatGPT合作產生一個 […]","\u003Ch1>如何與chatGPT合作生產並且部署一個短網址服務\u003C\u002Fh1>\n\u003Cp>chatGPT問世之後一直想來與chatGPT合作產生一個服務，最近總算花了一點時間完成這個小合作-部署短網址服務，因為設計很多程式碼細節，我在這篇僅紀錄與分享合作過程和遇到的問題。\u003C\u002Fp>\n\u003Ch2>確立目標\u003C\u002Fh2>\n\u003Cp>在開始之前，自己先訂了幾個小目標：\u003C\u002Fp>\n\u003Col>\n\u003Cli>目標是完成短網址服務\u003C\u002Fli>\n\u003Cli>要實際部署\u003C\u002Fli>\n\u003Cli>程式碼盡可能交給chatGPT生產，後來在實際合作過程中，我幾乎都是大膽直接複製貼上去跑服務\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Ch2>運用的環境與工具\u003C\u002Fh2>\n\u003Col>\n\u003Cli>nodeJS + express\u003C\u002Fli>\n\u003Cli>部署在Vercel上\u003C\u002Fli>\n\u003Cli>資料丟給firebase的realtime database\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Ch2>我們開始吧\u003C\u002Fh2>\n\u003Cp>一開始我就很暴力的直接問chatGPT\u003Cbr \u002F>\n\u003Cstrong>「如何使用node打造短網址服務，需要與firebase連結」\u003C\u002Fstrong>\u003Cbr \u002F>\n\u003Cimg decoding=\"async\" src=\"\u002Fapi\u002Fproxy-image?url=https%3A%2F%2Fpost.billxu.net%2Fwp-content%2Fuploads%2F2023%2F02%2F%E6%88%AA%E5%9C%96-2023-02-24-%E4%B8%8B%E5%8D%885.47.57.png\" alt=\"\" \u002F>\u003C\u002Fp>\n\u003Cp>根據chatGPT的回應，我完成了幾個步驟\u003C\u002Fp>\n\u003Col>\n\u003Cli>在firebase設定好環境\u003C\u002Fli>\n\u003Cli>從firebase取得node用的初始化與導入方法\u003C\u002Fli>\n\u003Cli>建立node服務\u003C\u002Fli>\n\u003Cli>完全遵照chatGPT回應貼上啟動服務的程式碼\u003C\u002Fli>\n\u003Cli>分別貼上「處理短網址」和「處理長網址」兩個api的程式碼\u003C\u002Fli>\n\u003Cli>貼上啟動express服務的程式碼\u003C\u002Fli>\n\u003Cli>啟動\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Cp>好啦其實我沒有那麼嚴謹，所以某些小細節我覺得怪怪還是有點修改，但基本上沒有太大問題。\u003Cbr \u002F>\n例如，chatGPT提供的作法是用require來引入，因為習慣問題我改成了import。還有chatGPT提供的程式碼是分段敘述，我組合起來時有餐入一點個人習慣，但這問題應該可以透過「請將程式碼組合成index.js」之類的prompt來調整。\u003C\u002Fp>\n\u003Cp>有時我也會問他一些函式寫法問題，像是下圖：\u003Cbr \u002F>\n\u003Cimg decoding=\"async\" src=\"\u002Fapi\u002Fproxy-image?url=https%3A%2F%2Fpost.billxu.net%2Fwp-content%2Fuploads%2F2023%2F02%2F%E6%88%AA%E5%9C%96-2023-02-24-%E4%B8%8B%E5%8D%886.08.47.png\" alt=\"\" \u002F>\u003Cbr \u002F>\n總之有稍微不懂就直接問，問爆XDDD\u003C\u002Fp>\n\u003Ch2>寫完就啟動啦！\u003C\u002Fh2>\n\u003Cp>當然沒那麼順利，剛剛那些步驟做完後，我很開心的輸入\u003C\u002Fp>\n\u003Cpre>\u003Ccode>npm run dev\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>然後就一如往常出現錯誤啦，\u003Cdel>chatGPT跟我的距離也沒多遠嘛\u003C\u002Fdel>。\u003Cbr \u002F>\n當然出現錯誤，我就會敘述清楚回報給chatGPT，像是下圖。\u003Cbr \u002F>\n\u003Cimg decoding=\"async\" src=\"\u002Fapi\u002Fproxy-image?url=https%3A%2F%2Fpost.billxu.net%2Fwp-content%2Fuploads%2F2023%2F02%2F%E6%88%AA%E5%9C%96-2023-02-24-%E4%B8%8B%E5%8D%886.11.51.png\" alt=\"\" \u002F>\u003C\u002Fp>\n\u003Cp>\u003Cstrong>這邊提到一個很有趣的狀況，其實chatGPT會漏給資訊唷\u003C\u002Fstrong>\u003Cbr \u002F>\n在短網址服務中，最重要的環節是將長網址轉換成一個隨機短字串，這邊一開始chatGPT居然只給我一個函式名稱就呼隆過去，我一開始就發現不對勁，果然啟動後發現這邊根本跑不動XDDD\u003Cbr \u002F>\n於是叫他補上，可惡ㄋ，我當免費仔也不能用騙啊。\u003Cbr \u002F>\n\u003Cimg decoding=\"async\" src=\"\u002Fapi\u002Fproxy-image?url=https%3A%2F%2Fpost.billxu.net%2Fwp-content%2Fuploads%2F2023%2F02%2F%E6%88%AA%E5%9C%96-2023-02-24-%E4%B8%8B%E5%8D%886.15.32.png\" alt=\"\" \u002F>\u003C\u002Fp>\n\u003Ch2>看起來順利執行了\u003C\u002Fh2>\n\u003Cp>補上後整體看起來有87像了，但他寫的程式碼他要跟我說怎麼測試啊( 你看使用者真的很懶)，所以就順口問了，啊這段程式碼怎麼測試。\u003Cbr \u002F>\n\u003Cimg decoding=\"async\" src=\"\u002Fapi\u002Fproxy-image?url=https%3A%2F%2Fpost.billxu.net%2Fwp-content%2Fuploads%2F2023%2F02%2F%E6%88%AA%E5%9C%96-2023-02-24-%E4%B8%8B%E5%8D%886.17.50.png\" alt=\"\" \u002F>\u003Cbr \u002F>\n\u003Cimg decoding=\"async\" src=\"\u002Fapi\u002Fproxy-image?url=https%3A%2F%2Fpost.billxu.net%2Fwp-content%2Fuploads%2F2023%2F02%2F%E6%88%AA%E5%9C%96-2023-02-24-%E4%B8%8B%E5%8D%886.18.01.png\" alt=\"\" \u002F>\u003Cbr \u002F>\n這之後開始進入一段時間不短的錯誤與改進攻防戰，因為服務不能正確運作，我就一直把錯誤狀況丟回去給他修改。\u003Cbr \u002F>\n大約來回四五次後提出來的程式碼版本終於能正常使用了，看起來沒啥問題。到這邊其實基本功能已經完成了，只是後來有想到一些防呆與檢查，chatGPT給我的版本處理的不是很好就稍微再修改過，未來有要這樣使用的話記得要提醒他加上檢查判斷。\u003C\u002Fp>\n\u003Cblockquote>\n\u003Cp>但我覺得他已經完成任務！真的強啊！\u003C\u002Fp>\n\u003C\u002Fblockquote>\n\u003Ch2>補上介面\u003C\u002Fh2>\n\u003Cp>剛剛做的只有基本的node api，還沒有加上介面，當然這也得交給chatGPT囉。喂！快工作。\u003Cbr \u002F>\n\u003Cimg decoding=\"async\" src=\"\u002Fapi\u002Fproxy-image?url=https%3A%2F%2Fpost.billxu.net%2Fwp-content%2Fuploads%2F2023%2F02%2F%E6%88%AA%E5%9C%96-2023-02-24-%E4%B8%8B%E5%8D%886.24.36.png\" alt=\"\" \u002F>\u003C\u002Fp>\n\u003Cp>稍微看一下覺得寫得還不錯喔！雖然還是一點錯誤，但那其實是給予的資訊不夠清楚，跟前面一樣開始做一些調整的溝通就弄個差不多了。\u003C\u002Fp>\n\u003Cblockquote>\n\u003Cp>要請他修正最好給予足夠資訊，例如在什麼地方做什麼出現什麼訊息。通常會給很具體的調整方式。\u003C\u002Fp>\n\u003C\u002Fblockquote>\n\u003Cp>最後我嫌介面不好看，還叫他用Alertify.js弄漂亮一點XDDDD像這樣\u003Cbr \u002F>\n\u003Cimg decoding=\"async\" src=\"\u002Fapi\u002Fproxy-image?url=https%3A%2F%2Fpost.billxu.net%2Fwp-content%2Fuploads%2F2023%2F02%2F%E6%88%AA%E5%9C%96-2023-02-24-%E4%B8%8B%E5%8D%886.31.32.png\" alt=\"\" \u002F>\u003C\u002Fp>\n\u003Cp>這時其實已經弄的差不多了，大概花了4小時左右。\u003Cbr \u002F>\n\u003Cimg decoding=\"async\" src=\"\u002Fapi\u002Fproxy-image?url=https%3A%2F%2Fpost.billxu.net%2Fwp-content%2Fuploads%2F2023%2F02%2F%E6%88%AA%E5%9C%96-2023-02-24-%E4%B8%8B%E5%8D%886.30.35.png\" alt=\"\" \u002F>\u003C\u002Fp>\n\u003Ch2>該來部署囉\u003C\u002Fh2>\n\u003Cp>剛剛提到我想部署到Vercel上，這邊犯了一個錯，忘記連部署方法也先叫他提供，導致這邊卡了一段時間，\u003Cbr \u002F>\n問題大意是我要在Vercel部署express服務，但又得提供一個介面，Vercel其實有自己一套做法，但一開始我先從express的設定去調整，造成服務一直部署失敗，Vercel的做法之後會再寫一篇文補上(?\u003Cbr \u002F>\n花了一些時間找錯誤後發現問題在於Vercel的設定，於是重新問問夥伴怎麼寫比較好。\u003Cbr \u002F>\n\u003Cimg decoding=\"async\" src=\"\u002Fapi\u002Fproxy-image?url=https%3A%2F%2Fpost.billxu.net%2Fwp-content%2Fuploads%2F2023%2F02%2F%E6%88%AA%E5%9C%96-2023-02-24-%E4%B8%8B%E5%8D%886.37.44.png\" alt=\"\" \u002F>\u003C\u002Fp>\n\u003Cp>看一看有點不太懂rewrites的一些規則，就乾脆直接問清楚，ㄟ你跟我怎麼設定辣！\u003Cbr \u002F>\n\u003Cimg decoding=\"async\" src=\"\u002Fapi\u002Fproxy-image?url=https%3A%2F%2Fpost.billxu.net%2Fwp-content%2Fuploads%2F2023%2F02%2F%E6%88%AA%E5%9C%96-2023-02-24-%E4%B8%8B%E5%8D%886.38.52.png\" alt=\"\" \u002F>\u003Cbr \u002F>\n我得說這樣問後得到的回答超清楚，比我自己回答還要清楚qq\u003Cbr \u002F>\n就這樣重新問了幾次後，我也搞懂了，然後就部署成功了，哈哈XD\u003Cbr \u002F>\n最後算了一下，從開始到部署完成運作大概花了兩天，中間有些時間是做小調整，熟練後能更快。\u003C\u002Fp>\n\u003Ch2>結論\u003C\u002Fh2>\n\u003Cp>經過這次練習後確認chatGPT真的能有效協助工程師執行專案，尤其面對一些陌生狀況更能快速找到一條處理的道路。說來真感概，幾年前寫論文就是在做人機合作的專家系統，那時就感覺NLP這塊每年技術迭代都在加速，沒想到畢業三年後就看到這麼純熟的人機合作工具，這個時代真棒。\u003C\u002Fp>\n\u003Cp>目前主要問題大概是整合部分，但用的過程也覺得只要給予的條件、目的夠明確，chatGPT回饋的程式碼越具體，所以我想整合能力未來也會突破。\u003C\u002Fp>\n\u003Cp>總之以後不管做工作專案也好、side project也好，有chatGPT真的是幫助不小，這個工具能補足很多欠缺的部分，讓我非常期待可以做更多有趣的東西啦。\u003Cbr \u002F>\n倒不太擔心什麼會被取代之類\u003Cdel>，反正我也沒多強\u003C\u002Fdel>，有這類工具身為創作者才能飛得更遠不是嗎？\u003Cbr \u002F>\n以上就是這個小專案的過程，分享一下，也歡迎留言討論。\u003C\u002Fp>\n\u003Cp>最後這是部署的工具網址\u003Cbr \u002F>\n\u003Ca href=\"https:\u002F\u002Fshort-url-test.vercel.app\u002F\">https:\u002F\u002Fshort-url-test.vercel.app\u002F\u003C\u002Fa>\u003C\u002Fp>\n","2023-02-24T18:49:52","\u002Fapi\u002Fproxy-image?url=https%3A%2F%2Fpost.billxu.net%2Fwp-content%2Fuploads%2F2023%2F02%2FChatGPT_logo.svg.png",{"name":88,"link":89},[181,182],{"name":17,"slug":18},{"name":43,"slug":43},"\u002Fblog\u002F如何與chatgpt合作生產並且部署一個短網址服務",{"id":185,"slug":186,"title":187,"description":188,"content":189,"date":190,"image":101,"imageAlt":187,"author":191,"categories":192,"path":195,"minRead":29},3369,"vercel上的node-canvas部署問題","Vercel上的node-canvas部署問題","Vercel上的node-canvas部署問題 如果要在Vercel上部署有node-canvas的專案，應該 […]","\u003Ch1>Vercel上的node-canvas部署問題\u003C\u002Fh1>\n\u003Cp>如果要在Vercel上部署有node-canvas的專案，應該都會遇到這個問題XD可能會有幾種錯誤訊息，像是\u003Ccode>version &#039;ZLIB_1.2.9&#039; not found\u003C\u002Fcode>或是\u003Ccode>libuuid.so.1: cannot open shared object file:\u003C\u002Fcode>，主因是node-canvas會使用以下幾個函式庫：\u003C\u002Fp>\n\u003Col>\n\u003Cli>libuuid.so\u003C\u002Fli>\n\u003Cli>libmount.so\u003C\u002Fli>\n\u003Cli>libblkid.so\u003Cbr \u002F>\n但Vercel上的serverless環境預設沒有這些函式庫導致部署失敗。\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Cp>這邊分享我成功的設定。\u003C\u002Fp>\n\u003Ch2>解決做法\u003C\u002Fh2>\n\u003Cp>關鍵就是package.json上的設定，必須在執行或編譯前，讓Vercel先安裝函式庫。\u003C\u002Fp>\n\u003Cpre>\u003Ccode>  &quot;scripts&quot;: {\n    &quot;now-build&quot;: &quot;yum install libuuid-devel libmount-devel &amp;&amp; cp \u002Flib64\u002F{libuuid,libmount,libblkid}.so.1 node_modules\u002Fcanvas\u002Fbuild\u002FRelease\u002F &amp;&amp; npm run start&quot;\n  },\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>並且我的環境使用的是\u003Ccode>canvas＠2.6.1\u003C\u002Fcode>，有人提到\u003Ccode>canvas＠2.8.0\u003C\u002Fcode>也可以，但我是失敗的，這邊就試試看囉\u003C\u002Fp>\n\u003Cpre>\u003Ccode>&quot;dependencies&quot;: {\n    &quot;canvas&quot;: &quot;2.6.1&quot;,\n  },\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>最後記得Vercel上要將node運作環境改為14。\u003Cbr \u002F>\n真是麻煩的問題，找了整整一天資料才成功部署XD\u003C\u002Fp>\n","2023-02-24T10:30:36",{"name":88,"link":89},[193,194],{"name":17,"slug":18},{"name":43,"slug":43},"\u002Fblog\u002Fvercel上的node-canvas部署問題",{"id":197,"slug":198,"title":199,"description":200,"content":201,"date":202,"image":101,"imageAlt":199,"author":203,"categories":204,"path":206,"minRead":29},3372,"vercel上部署node-canvas的中文字型顯示問題","Vercel上部署node-canvas的中文字型顯示問題","Vercel上部署node-canvas的中文字型顯示問題 這應該是個冷門的狀況，因為使用了node版本的ca […]","\u003Ch1>Vercel上部署node-canvas的中文字型顯示問題\u003C\u002Fh1>\n\u003Cp>這應該是個冷門的狀況，因為使用了node版本的canvas去跑圖，但Vercel的serverless環境沒有中文字型，所以輸出中文字體會變成框框，如果在本機伺服器處理這件事很簡單，安裝中文字型就好了(真是廢話)，但如果是在Vercel上的話，就得想辦法了。\u003C\u002Fp>\n\u003Ch2>解決方式\u003C\u002Fh2>\n\u003Cp>其實要做的目標就是，要如何在Vercel的node中安裝中文字型？從Vercel的官方說明能找到關鍵資訊：\u003Cbr \u002F>\n\u003Ca href=\"https:\u002F\u002Fvercel.com\u002Fguides\u002Fhow-can-i-use-files-in-serverless-functions\" title=\"How can I use files in Serverless Functions on Vercel?\">How can I use files in Serverless Functions on Vercel?\u003C\u002Fa>\u003C\u002Fp>\n\u003Cpre>\u003Ccode>\u002F\u002F api\u002Fhello.js\n\nimport { readFileSync } from &#039;fs&#039;;\nimport path from &#039;path&#039;;\n\nexport default function handler(req, res) {\n  const file = path.join(process.cwd(), &#039;files&#039;, &#039;test.json&#039;);\n  const stringified = readFileSync(file, &#039;utf8&#039;);\n\n  res.setHeader(&#039;Content-Type&#039;, &#039;application\u002Fjson&#039;);\n  return res.end(stringified);\n}\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>主要透過\u003Ccode>path\u003C\u002Fcode>來加入檔案，而我後來是用\u003Ccode>node-canvas\u003C\u002Fcode>的registerFont方法來實作這件事。\u003Cbr \u002F>\n大概寫法如下：\u003C\u002Fp>\n\u003Cpre>\u003Ccode>import canvas from &#039;canvas&#039;\nimport path from &#039;path&#039;;\n\nconst canvasObj = canvas.createCanvas(256, 256);\nconst resolved = path.resolve(&#039;.\u002Ffonts\u002FFont.ttf&#039;);\ncanvasObj.registerFont(resolved, { family: &#039;Font&#039; });\n...\n...\n...\nctx.font = &#039;30px Font&#039;;\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>這邊要注意\u003Ccode>path.join\u003C\u002Fcode>有可能在某些狀況讀不到路徑，所以改用了\u003Ccode>path.resolve\u003C\u002Fcode>。\u003Cbr \u002F>\n另外也要注意Vercel部署時有50mb的限制，字型檔案太大有可能會編譯失敗唷。\u003C\u002Fp>\n","2023-02-19T11:32:08",{"name":88,"link":89},[205],{"name":43,"slug":43},"\u002Fblog\u002Fvercel上部署node-canvas的中文字型顯示問題",{"id":208,"slug":209,"title":210,"description":211,"content":212,"date":213,"image":101,"imageAlt":210,"author":214,"categories":215,"path":218,"minRead":29},3356,"在vercel上使用puppeteer製作爬蟲","在Vercel上使用puppeteer製作爬蟲","在Vercel上使用puppeteer製作爬蟲 免費仔萬歲！(誤)puppeteer是一個nodeJS的函式庫 […]","\u003Ch1>在Vercel上使用puppeteer製作爬蟲\u003C\u002Fh1>\n\u003Cp>免費仔萬歲！(誤)puppeteer是一個nodeJS的函式庫，可以用來控制 Chrome 或 Chromium 瀏覽器，當然是一個用作爬蟲的好工具。這邊就先跳過puppeteer的使用方式介紹，僅記錄在Vercel上使用puppeteer進行網頁爬蟲遇到的一些問題。\u003Cbr \u002F>\n使用puppeteer可以參考\u003Ca href=\"https:\u002F\u002Fpptr.dev\u002F\" title=\"這邊\">這邊\u003C\u002Fa>\u003C\u002Fp>\n\u003Ch2>使用puppeteer-core\u003C\u002Fh2>\n\u003Cp>由於Vercel上不能直接執行Chromium ，需要透過chrome-aws-lambda來使用Chromium，必須注意chrome-aws-lambda不能執行本機的Chromium，如果要本機測試則需將executablePath改為實體的位置喔。範例大致如下\u003C\u002Fp>\n\u003Cpre>\u003Ccode>import puppeteer from &#039;puppeteer-core&#039;\n\nconst getData = async (url) =&gt; {\n    try {\n        let browser = await puppeteer.launch({\n            args: [...chrome.args, &#039;--hide-scrollbars&#039;, &#039;--disable-web-security&#039;],\n            defaultViewport: chrome.defaultViewport,\n            executablePath: await chrome.executablePath,\n            headless: chrome.headless,\n            ignoreHTTPSErrors: true,\n        });\n        const browser = await puppeteer.launch(options);\n        const page = await browser.newPage();\n        await page.goto(&#039;前往的網頁&#039;, { waitUntil: &#039;networkidle0&#039; });\n        await page.close();\n        await browser.close();\n  } catch (err) {\n      console.error(err);\n      return;\n  }\n}\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch2>輕量化爬蟲\u003C\u002Fh2>\n\u003Cp>如果只是要爬取文字資料，那可以關閉圖片、css等請求，將爬蟲輕量化以壓低執行時間。\u003C\u002Fp>\n\u003Cpre>\u003Ccode>\u002F\u002F需要先設定 setRequestInterception 為 true，預設狀況下request 是唯讀的。\nawait page.setRequestInterception(true);\npage.on(&quot;request&quot;, (req) =&gt; {\n  if (\n    req.resourceType() == &quot;stylesheet&quot; ||\n    req.resourceType() == &quot;font&quot; ||\n    req.resourceType() == &quot;image&quot;\n  ) {\n    req.abort();\n  } else {\n    req.continue();\n  }\n});\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch2>限制\u003C\u002Fh2>\n\u003Cp>由於Vercel上有部署的套件大小限制50mb，puppeteer-core和chrome-aws-lambda需要使用以下版本，用最新版會超過限制導致無法部署。\u003Cbr \u002F>\nVercel上也需要使用\u003Cstrong>Node14\u003C\u002Fstrong>版本，記得調整唷。\u003Cbr \u002F>\npackeage.json\u003C\u002Fp>\n\u003Cpre>\u003Ccode>  &quot;puppeteer-core&quot;: &quot;10.1.0&quot;,\n  &quot;chrome-aws-lambda&quot;: &quot;10.1.0&quot;,\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>按照上述應該就能部署成功，並且爬取一個網頁內容，在我的專案中爬複數網頁就會超過執行時間而失敗，這邊還是研究如何改進執行效率XD\u003Cbr \u002F>\n如果搭配排程服務，就可以透過免費服務製作一個自動爬蟲工具，免費仔萬歲！\u003C\u002Fp>\n","2023-02-12T10:01:30",{"name":88,"link":89},[216,217],{"name":17,"slug":18},{"name":43,"slug":43},"\u002Fblog\u002F在vercel上使用puppeteer製作爬蟲",{"id":220,"slug":221,"title":222,"description":223,"content":224,"date":225,"image":101,"imageAlt":222,"author":226,"categories":227,"path":230,"minRead":29},3349,"node在vercel上實作低度頻率排程cron-job","[Node]在Vercel上實作低度頻率排程(Cron-Job)","在Vercel上實作低度頻率排程(Cron-Job) 實作了一個放在Vercel的爬蟲，然後定期去執行，這篇記 […]","\u003Ch1>在Vercel上實作低度頻率排程(Cron-Job)\u003C\u002Fh1>\n\u003Cp>實作了一個放在Vercel的爬蟲，然後定期去執行，這篇記錄一下在Vercel上實作排程(cron-job)的過程。\u003Cbr \u002F>\n原本Vercel就有提供排程的作法，官方說明提到如果是低度頻率排程(例如每天)，可以使用GitHub Actions，範例如下：\u003C\u002Fp>\n\u003Cpre>\u003Ccode>name: daily-cron\non:\n  schedule:\n    - cron: &#039;0 1 * * *&#039;\njobs:\n  cron:\n    runs-on: ubuntu-latest\n    steps:\n      - name: Call our API route\n        run: |\n          curl --request POST \\\n          --url &#039;https:\u002F\u002Fyoursite.com\u002Fapi\u002Fcron&#039; \\\n          --header &#039;Authorization: Bearer ${{ secrets.API_SECRET_KEY }}&#039;\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>GitHub Actions的作法在這邊就不提了。\u003C\u002Fp>\n\u003Ch2>概念\u003C\u002Fh2>\n\u003Cp>其實實作概念很簡單，就是多開一個需要驗證的api，然後排時間去觸發api，進而去執行要做的事情。\u003C\u002Fp>\n\u003Ch2>排程失效\u003C\u002Fh2>\n\u003Cp>但我實作完後發現怎樣排程都不會觸發，查了一下找到有個討論說明GitHub Actions的排程其實不穩定，常常會延時或不觸發，\u003Ca href=\"https:\u002F\u002Fupptime.js.org\u002Fblog\u002F2021\u002F01\u002F22\u002Fgithub-actions-schedule-not-working\u002F\" title=\"連結\">連結\u003C\u002Fa>。因此我改用了另外的免費cron服務，這類服務其實滿多的。\u003C\u002Fp>\n\u003Cblockquote>\n\u003Cp>補充：幾天後發現GitHub Actions的排程還是有成功運作，可能是初次觸發會需要一點時間(?，總之相較主打排程的服務，在GitHub Actions使用排程可能會有那麼一點不穩，請注意唷。\u003C\u002Fp>\n\u003C\u002Fblockquote>\n\u003Ch2>Api\u003C\u002Fh2>\n\u003Cp>官方提供的範例如下，需注意要做驗證所以必須準備API_SECRET_KEY，這個可以寫在Vercel的環境變數(Environment Variables)裡面\u003C\u002Fp>\n\u003Cpre>\u003Ccode>export default async function handler(req, res) {\n  if (req.method === &#039;POST&#039;) {\n    try {\n      const { authorization } = req.headers;\n\n      if (authorization === `Bearer ${process.env.API_SECRET_KEY}`) {\n        \u002F\u002F執行需要的事情\n        res.status(200).json({ success: true });\n      } else {\n        res.status(401).json({ success: false });\n      }\n    } catch (err) {\n      res.status(500).json({ statusCode: 500, message: err.message });\n    }\n  } else {\n    res.setHeader(&#039;Allow&#039;, &#039;POST&#039;);\n    res.status(405).end(&#039;Method Not Allowed&#039;);\n  }\n}\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>像我使用express建置，最後用一個route接這隻api就可以。\u003C\u002Fp>\n\u003Cpre>\u003Ccode>app.post(&#039;\u002Fapi\u002Fcron&#039;, async (req, res) =&gt; {\n  handler();\n});\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>剛剛有提到這是低度頻率排程的作法，若是高度頻率官方建議用Upstash QStash 函式庫來實作，這部分就要再研究了XD\u003C\u002Fp>\n","2023-01-27T00:06:38",{"name":88,"link":89},[228,229],{"name":17,"slug":18},{"name":43,"slug":43},"\u002Fblog\u002Fnode在vercel上實作低度頻率排程cron-job",{"id":232,"slug":233,"title":234,"description":235,"content":236,"date":237,"image":101,"imageAlt":234,"author":238,"categories":239,"path":241,"minRead":29},3344,"node在node中轉換json成csvjson-to-csv","[Node]在node中轉換JSON成CSV(JSON to CSV)","在node中轉換JSON成CSV(JSON to CSV) 新年快樂！這篇純粹做一個小筆記，常常做資料處理的話 […]","\u003Cp>\u003Cimg decoding=\"async\" src=\"\u002Fapi\u002Fproxy-image?url=https%3A%2F%2Fpost.billxu.net%2Fwp-content%2Fuploads%2F2023%2F01%2F640px-Node.js_logo.svg.png\" alt=\"\" \u002F>\u003C\u002Fp>\n\u003Ch1>在node中轉換JSON成CSV(JSON to CSV)\u003C\u002Fh1>\n\u003Cp>新年快樂！這篇純粹做一個小筆記，常常做資料處理的話一定很常遇到這類需求，將JSON轉換成CSV(JSON to CSV)，當然也會有反過來的狀況，那就有機會再寫吧XD\u003C\u002Fp>\n\u003Cp>我這次採用json-2-csv這個套件，本篇就整理一下json-2-csv的用法囉。\u003C\u002Fp>\n\u003Ch2>安裝套件\u003C\u002Fh2>\n\u003Cpre>\u003Ccode class=\"language-node\">$ npm install json-2-csv --save\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch2>基本用法\u003C\u002Fh2>\n\u003Cp>範例如下，以下範例接用require引入\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-node\">\u002F\u002F require json-2-csv module\nconst converter = require(&#039;json-2-csv&#039;)\n\n\u002F\u002F 定義一個JSON資料\nconst rawJson = [\n  {\n    id: 1,\n    title: &#039;標題1&#039;,\n    description: &#039;標題1的敘述&#039;\n  },\n  {\n    id: 1,\n    title: &#039;標題3&#039;,\n    description: &#039;標題3的敘述&#039;\n  },\n  {\n    id: 3,\n    title: &#039;標題3&#039;,\n    description: &#039;標題3的敘述&#039;\n  }\n]\n\n\u002F\u002F 轉換JSON到CSV中的欄位字串\nconverter.json2csv(rawJson, (err, csv) =&gt; {\n  if (err) {\n    throw err\n  }\n\n  \u002F\u002F 輸出CSV字串資料\n  console.log(csv)\n  \u002F*會長這樣\n    id,title,description\n    1,標題1,標題1的敘述\n    2,標題2,標題2的敘述\n    3,標題3,標題3的敘述\n  *\u002F\n})\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch2>輸出CSV檔案\u003C\u002Fh2>\n\u003Cp>使用fs輸出字串就可以囉\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-node\">const fs = require(&#039;fs&#039;)\n...\n...\n\u002F\u002F 轉換JSON到CSV中的欄位字串\nconverter.json2csv(rawJson, (err, csv) =&gt; {\n  if (err) {\n    throw err\n  }\n\n  \u002F\u002F 輸出CSV字串資料\n  console.log(csv)\n\n  \u002F\u002F 輸出CSV檔案\n  fs.writeFileSync(&#039;json2csv.csv&#039;, csv)\n})\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch2>從檔案讀取JSON\u003C\u002Fh2>\n\u003Cpre>\u003Ccode>\u002F\u002F 從檔案讀取JSON\nconst todos = JSON.parse(fs.readFileSync(&#039;rawJson.json&#039;))\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch2>await寫法\u003C\u002Fh2>\n\u003Cpre>\u003Ccode>\u002F\u002F 轉換JSON到CSV中的欄位字串\n(async () =&gt; {\n  try {\n    const csv = await converter.json2csvAsync(todos)\n\n    \u002F\u002F 輸出CSV字串資料\n    console.log(csv)\n\n    \u002F\u002F 輸出CSV檔案\n    fs.writeFileSync(&#039;json2csv.csv&#039;, csv)\n  } catch (err) {\n    console.log(err)\n  }\n})()\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>json-2-csv非常簡單使用，參考文件在\u003Ca href=\"https:\u002F\u002Fgithub.com\u002Fmrodrig\u002Fjson-2-csv\" title=\"這邊\">這邊\u003C\u002Fa>\u003C\u002Fp>\n\u003Cp>\u003Cstrong>那就祝大家新年快樂囉！\u003C\u002Fstrong>\u003C\u002Fp>\n","2023-01-19T11:00:06",{"name":88,"link":89},[240],{"name":43,"slug":43},"\u002Fblog\u002Fnode在node中轉換json成csvjson-to-csv",{"id":243,"slug":244,"title":245,"description":246,"content":247,"date":248,"image":86,"imageAlt":245,"author":249,"categories":250,"path":253,"minRead":29},177,"javascriptswitch的條件式運算","[Javascript]switch的條件式運算","switch的條件式運算 Javascript中的if和switch想必在JS中都滿常使用，有時候有較多判斷時 […]","\u003Ch1>switch的條件式運算\u003C\u002Fh1>\n\u003Cp>Javascript中的if和switch想必在JS中都滿常使用，有時候有較多判斷時會使用switch，通常用法如下\u003C\u002Fp>\n\u003Cpre>\u003Ccode>function checkFruit(fruit){\n  switch(fruit){\n    case &#039;Apple&#039;:\n      return &#039;Apple&#039;\n    case &#039;Banana&#039;:\n      return &#039;Banana&#039;\n    case &#039;Orange&#039;:\n      return &#039;Orange&#039;\n    default: \n      return &#039;It is a fruit&#039;\n  }\n}\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>原本我以為fruit作為變數的概念傳進switch，再透過不同case判斷值達到判斷效果。直到寫到了條件式才發現這個想法是錯的！沒錯，以下這種寫法就會失效。\u003C\u002Fp>\n\u003Cpre>\u003Ccode>function checkYear(year){\n  switch(year){\n    case (year &gt; 2000):\n      console.log(&#039;young&#039;)\n      break\n    case (year &lt;= 2000 &amp;&amp; year &gt;= 1980):\n      console.log(&#039;old&#039;)\n      break\n    case (year &lt; 1980 &amp;&amp; year &gt;= 1950):\n      console.log(&#039;too old&#039;)\n      break\n    default:\n      console.log(&quot;year:&quot; + year)\n      break\n  }\n}\ncheckYear(2010) \u002F\u002F year:2010\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>居然不是預想的顯示\u003Cem>young\u003C\u002Fem>，仔細查了一下才發現使用switch時的理解錯了。其實在這個例子中switch 進行條件判斷的過程長這樣：\u003C\u002Fp>\n\u003Cpre>\u003Ccode>year === (year &gt; 2000) \u002F\u002Fexpression === (value &gt; 2000) \u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>當然不會有作用，換成敘述來講，swtich長這樣：\u003C\u002Fp>\n\u003Cpre>\u003Ccode>switch (expression) {\n    case value: statement\n        break;\n    case value: statement\n        break;\n    case value: statement\n        break;\n    default: statement\n}\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>雖然都是宣告\u003Cem>year\u003C\u002Fem>但一個是expression一個是真正傳進去的value，意義上不同。如果要想要用switch進行條件式運算，就要將if~else的true反過來寫，如下：\u003C\u002Fp>\n\u003Cpre>\u003Ccode>function checkYear(true){\n  switch(year){\n    case (year &gt; 2000):\n      console.log(&#039;young&#039;)\n      break\n    case (year &lt;= 2000 &amp;&amp; year &gt;= 1980):\n      console.log(&#039;old&#039;)\n      break\n    case (year &lt; 1980 &amp;&amp; year &gt;= 1950):\n      console.log(&#039;too old&#039;)\n      break\n    default:\n      console.log(&quot;year:&quot; + year)\n      break\n  }\n}\ncheckYear(2010) \u002F\u002F young\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>這樣就會是以下判斷，就能順利當if~else來用囉\u003C\u002Fp>\n\u003Cpre>\u003Ccode>true === (year &gt; 2000)\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch2>結論\u003C\u002Fh2>\n\u003Cp>起初是看到了一些文章在講用不同方式處理降低使用if-else的依賴來達到判斷目的，但老實說就算使用switch，程式碼也不會減少太多，所以要不要用就見仁見智囉XD\u003C\u002Fp>\n","2023-01-03T20:00:02",{"name":88,"link":89},[251,252],{"name":22,"slug":22},{"name":43,"slug":43},"\u002Fblog\u002Fjavascriptswitch的條件式運算",{"id":255,"slug":256,"title":257,"description":258,"content":259,"date":260,"image":261,"imageAlt":257,"author":262,"categories":263,"path":267,"minRead":29},225,"vue使用vue3打造trello式的工作看板","[Vue]使用Vue3打造trello式的工作看板","使用vue3打造trello式的工作看板 今年參加了{Laravel x Vue}Conf Taiwan，第一 […]","\u003Ch1>使用vue3打造trello式的工作看板\u003C\u002Fh1>\n\u003Cp>今年參加了\u003Ca href=\"https:\u002F\u002Flaravelconf.tw\u002F\" title=\"{Laravel x Vue}Conf Taiwan\">{Laravel x Vue}Conf Taiwan\u003C\u002Fa>，第一天工作坊主題是使用Vue3打造類似trello的工作看板，藉此重新檢視自己在使用Vue3的習慣與詢問一些自學想不透的問題。\u003Cbr \u002F>\n很感謝Kuro的推廣與分享，幫忙打廣告一下，有興趣學Vue的人請務必購買「\u003Ca href=\"https:\u002F\u002Fwww.tenlong.com.tw\u002Fproducts\u002F9789864345687\" title=\"重新認識 Vue.js：008天絕對看不完的 Vue.js 3 指南\">重新認識 Vue.js：008天絕對看不完的 Vue.js 3 指南\u003C\u002Fa>」XDDDDD\u003Cbr \u002F>\n先附上本次工作坊的\u003Ca href=\"https:\u002F\u002Fgithub.com\u002Fbillxu0521\u002Fvue-trello-app\" title=\"repo\">repo\u003C\u002Fa>與\u003Ca href=\"https:\u002F\u002Fvue-trello-app.vercel.app\" title=\"demo\">demo\u003C\u002Fa>。\u003C\u002Fp>\n\u003Ch2>專案目標\u003C\u002Fh2>\n\u003Cp>本次專案是打造一個類似trello的工作記錄看板，工作坊的學習目標為以下：\u003C\u002Fp>\n\u003Col>\n\u003Cli>認識 vite 與 Vue.js 開發環境設定\u003C\u002Fli>\n\u003Cli>熟悉 Vue.js 3.x Compontions 與 Setup script\u003C\u002Fli>\n\u003Cli>使用 Tailwind CSS 結合 Vue.js 開發網頁介面\u003C\u002Fli>\n\u003Cli>使用 Pinia 管理跨元件狀態\u003C\u002Fli>\n\u003Cli>使用 Vue Router 實作前端路由功能 (edited)\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Cp>使用技術如下：\u003C\u002Fp>\n\u003Cul>\n\u003Cli>Vite 3.1.8\u003C\u002Fli>\n\u003Cli>Vue.js 3.2\u003C\u002Fli>\n\u003Cli>Vue Router 4.1.5\u003C\u002Fli>\n\u003Cli>Pinia 2.0\u003C\u002Fli>\n\u003Cli>Vue Use\u003C\u002Fli>\n\u003Cli>Tailwindcss 3.2.14\u003C\u002Fli>\n\u003Cli>Vue.draggable.next\u003C\u002Fli>\n\u003Cli>unplugin-vue-components\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cp>這邊就不做製作步驟，主要紀錄自己學到的重點，有問題的人可以留言給我。\u003C\u002Fp>\n\u003Ch2>工作坊筆記\u003C\u002Fh2>\n\u003Ch3>script setup 寫法\u003C\u002Fh3>\n\u003Cp>Vue3提供了setup的寫法，通常有兩種一種是傳統的export default\u003C\u002Fp>\n\u003Cpre>\u003Ccode>export default  {\n  setup() {\n  \u002F\u002F內容\n  }\n  }\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>另一種為script setup 寫法\u003C\u002Fp>\n\u003Cpre>\u003Ccode>&lt;script setup&gt;\n\u002F\u002F內容\n&lt;\u002Fscript&gt;\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>這邊建議是盡可能採用script setup 寫法，官方也較推薦這種撰寫方式，雖然要習慣一下，但好處是可以簡化程式碼，實際寫過真的感覺很清爽！\u003C\u002Fp>\n\u003Ch3>Vue Use\u003C\u002Fh3>\n\u003Cp>這是一個滿實用的Vue工作組，在這個專案中導入是為了 focus 與 onClickOutside這兩支api，可以協助我們快速處理元素的聚焦與脫離，像是在trello中，點擊某個卡片會跳出燈箱(lightbox)以及點擊燈箱後方的遮罩可以關閉燈箱這種流程。\u003C\u002Fp>\n\u003Ch3>v-bind傳遞物件到子元件\u003C\u002Fh3>\n\u003Cp>之前我會使用prop傳遞資料到子元件像是這樣\u003C\u002Fp>\n\u003Cpre>\u003Ccode>&lt;TaskItem\n          :id=&quot;element.id&quot;\n          :title=&quot;element.title&quot;\n          :tasks=&quot;element.tasks&quot;\n        \u002F&gt;\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>但其實物件可以用v-bind來傳遞寫法如下：\u003C\u002Fp>\n\u003Cpre>\u003Ccode> &lt;TaskItem\n          v-bind=&quot;element&quot;\n        \u002F&gt;\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>而在子元件中需要這樣接值\u003C\u002Fp>\n\u003Cpre>\u003Ccode>&lt;script setup&gt;\nimport { ref, computed } from &quot;vue&quot;;\nconst props = defineProps({\n  id: String,\n  title: String,\n  content: String,\n}); \n&lt;\u002Fscript&gt;\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>如果會對資料修改，那記得要再宣告一個變數來接避免污染父元件的物件喔\u003C\u002Fp>\n\u003Cpre>\u003Ccode>const title = ref(props.title);\n\u002F\u002F修改title\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch3>Pinia\u003C\u002Fh3>\n\u003Cp>類似Vuex的狀態管理套件，超好用啊，我其實沒用過Vuex，只有用過原生的依賴注入，Pinia的使用真的方便很多。\u003Cbr \u002F>\n這邊就不寫使用方法了，請參考\u003Ca href=\"https:\u002F\u002Fpinia.vuejs.org\u002F\" title=\"官方資料\">官方資料\u003C\u002Fa>\u003C\u002Fp>\n\u003Ch4>取用函式與值\u003C\u002Fh4>\n\u003Cp>要注要透過Pinia取用函式與值的作法\u003Cbr \u002F>\n如果closeEditTask是函式，那可以用解構方式來引用，但這樣的話就無法觀察到物件中的狀態或值\u003C\u002Fp>\n\u003Cpre>\u003Ccode>以下兩種相同\nconst { closeEditTask } = store;  \u002F\u002F物件解構\nconst { closeEditTask } = store.closeEditTask;  \n解構無法被觀察\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>如果是狀態或值，則需要computed\u003C\u002Fp>\n\u003Cpre>\u003Ccode>const currentEditTask = computed(() =&gt; store.currentEditTask); \u002F\u002FcurrentEditTask這是狀態\n因為要關注狀態所以用computed\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch3>在script中取的DOM\u003C\u002Fh3>\n\u003Cp>想取得DOM內容的話可以用ref，像這樣\u003C\u002Fp>\n\u003Cpre>\u003Ccode>const target = ref();\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>然後網頁元入記得宣告\u003C\u002Fp>\n\u003Cpre>\u003Ccode>&lt;textarea\n      ref=&quot;target&quot;    \n    &gt;&lt;\u002Ftextarea&gt;\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>這樣就可以透過target來操作textarea的內容\u003C\u002Fp>\n\u003Ch3>偵測自己的click\u003C\u002Fh3>\n\u003Cp>有點像以前寫jQuery的監聽，對某元素監聽時要加上this不然會監聽到所有同元素，vue的@click也是\u003C\u002Fp>\n\u003Cpre>\u003Ccode>&lt;div\n    @click.self=&quot;closeEditTask&quot;\n  &gt;\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch3>src中的compoment與views命名習慣\u003C\u002Fh3>\n\u003Cp>這不是強制，習慣上只會在某地方特定使用一次的可以放在\u003Cstrong>views\u003C\u002Fstrong>中，例如首頁，如果是重複使用的元件則用\u003Cstrong>compoment\u003C\u002Fstrong>\u003C\u002Fp>\n\u003Ch3>Vue.draggable.next\u003C\u002Fh3>\n\u003Cp>支援拖曳效果的套件，附上\u003Ca href=\"https:\u002F\u002Fsortablejs.github.io\u002Fvue.draggable.next\u002F#\u002Fsimple\" title=\"官方說明\">官方說明\u003C\u002Fa>\u003C\u002Fp>\n\u003Ch3>unplugin-vue-components\u003C\u002Fh3>\n\u003Cp>超棒的玩意，可以直接以命名呼叫compoment，以往我們要呼叫compoment通常要先引入\u003C\u002Fp>\n\u003Cpre>\u003Ccode>import {xxx} from  &quot;xxx&quot;\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>但這個套件可以直接省略import，以原名就能直接呼叫，太棒啦！\u003C\u002Fp>\n","2022-12-04T13:36:23","\u002Fapi\u002Fproxy-image?url=https%3A%2F%2Fpost.billxu.net%2Fwp-content%2Fuploads%2F2022%2F11%2F256px-Vue.js_Logo_2.svg_.png",{"name":88,"link":89},[264,265,266],{"name":22,"slug":22},{"name":43,"slug":43},{"name":61,"slug":61},"\u002Fblog\u002Fvue使用vue3打造trello式的工作看板",{"id":269,"slug":270,"title":271,"description":272,"content":273,"date":274,"image":261,"imageAlt":271,"author":275,"categories":276,"path":280,"minRead":29},158,"vue使用vue-gtag串接google-analytics","[Vue]使用vue-gtag串接google analytics","使用vue-gtag串接google analytics 其實這篇只是暫時紀錄，因為我還是搞不動為什麼可以運作 […]","\u003Ch1>使用vue-gtag串接google analytics\u003C\u002Fh1>\n\u003Cp>其實這篇只是暫時紀錄，因為我還是搞不動為什麼可以運作....XD與其講Vue不如說是GTM和GA4串接上的一些狀況整理。\u003C\u002Fp>\n\u003Cp>總之呢，準備要在Vue專案上串接GA(google analytics)，找了一下，通常會推薦vue-gtag(vue-analytics已經不維護了，建議用vue-gtag)。於是參考了https:\u002F\u002Fmatteo-gabriele.gitbook.io\u002Fvue-gtag\u002F\u003Ca href=\"https:\u002F\u002Fmatteo-gabriele.gitbook.io\u002Fvue-gtag\u002F\" title=\"原作者說明\">原作者說明\u003C\u002Fa>就開始實作。\u003C\u002Fp>\n\u003Ch2>vue-gtag基本安裝\u003C\u002Fh2>\n\u003Cp>按照原說明文件，先安裝vue-gtag\u003C\u002Fp>\n\u003Cpre>\u003Ccode>$ npm add vue-gtag\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>然後在main.js引入vuegtag，\u003Cstrong>GA_MEASUREMENT_ID\u003C\u002Fstrong>就使用從GA服務開啟後取得的「評估ID」\u003C\u002Fp>\n\u003Cpre>\u003Ccode>import { createApp } from &quot;vue&quot;;\nimport App from &quot;.\u002FApp.vue&quot;;\nimport VueGtag from &quot;vue-gtag&quot;;\n\ncreateApp(App).use(VueGtag, {\n  config: { id: &quot;GA_MEASUREMENT_ID&quot; }\n}).mount(&quot;#app&quot;);\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>理論上這樣就完成了。然而GA4的串流提示老樣子可能會等數小時才有反應，於是我就等了.....48小時，然後依然沒反應，登愣\u003Cbr \u002F>\n由於GA4我還沒有用程式碼引入方式串接過，想說那誒啊捏，變這麼難接喔。\u003Cbr \u002F>\n稍微查了一下，結果....毫無收穫XDDD\u003Cbr \u002F>\n好吧，只好換方向來處理，既然GA4的很多說明跟GTM(google tag manager)有關係，那就用GTM來實作看看好了。\u003C\u002Fp>\n\u003Ch2>GTM串接GA4\u003C\u002Fh2>\n\u003Cp>基本流程如下：\u003C\u002Fp>\n\u003Col>\n\u003Cli>在GTM中開啟一個工作區\u003C\u002Fli>\n\u003Cli>加入代碼：選擇GA4設定\u003C\u002Fli>\n\u003Cli>評估ID填入GA中取得的「評估ID」\u003C\u002Fli>\n\u003Cli>觸發條件選擇：All page\u003C\u002Fli>\n\u003Cli>儲存設定，再來發布容器\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Cp>回到Vue專案中，你申請完GTM工作區後應該會取的一組gtag，會長這樣：GTM-XXXXXX，將這個識別ID加回到到main.js，沒錯就是這串\u003C\u002Fp>\n\u003Cpre>\u003Ccode>import { createApp } from &quot;vue&quot;;\nimport App from &quot;.\u002FApp.vue&quot;;\nimport VueGtag from &quot;vue-gtag&quot;;\n\ncreateApp(App).use(VueGtag, {\n  config: { id: &quot;GA_MEASUREMENT_ID&quot; } &lt;---這個\n}).mount(&quot;#app&quot;);\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>設定完後，可以開啟GTM中的預覽來檢查，建議chrome可以裝一個擴充「Tag Assistant」以方便檢視是否有安裝順利。\u003C\u002Fp>\n\u003Ch3>遇到問題\u003C\u002Fh3>\n\u003Cp>其實上述做完後，我在GTM的預覽模式中還是會失敗，「Tag Assistant」提示我代碼有放，但會無法取得一個測試用的js，目前尚未找到原因，但在GA的即時模式中已經可以看到資料進來了，只好慢慢找問題與調整了，如果有人遇到類似問題歡迎一起討論。\u003C\u002Fp>\n","2022-11-20T10:00:15",{"name":88,"link":89},[277,278,279],{"name":22,"slug":22},{"name":43,"slug":43},{"name":61,"slug":61},"\u002Fblog\u002Fvue使用vue-gtag串接google-analytics",{"id":282,"slug":283,"title":284,"description":285,"content":286,"date":287,"image":86,"imageAlt":284,"author":288,"categories":289,"path":293,"minRead":29},147,"nodeaxios自動加上網址","[Node]axios自動加上網址","axios自動加上網址 我使用了axios來實作vue中請求資料的部分，但網站中有許多地方要請求不同地方的資料 […]","\u003Ch1>axios自動加上網址\u003C\u002Fh1>\n\u003Cp>我使用了axios來實作vue中請求資料的部分，但網站中有許多地方要請求不同地方的資料時，就需要有個地方統一管理這個位置，這時就可以用到axios.baseURL\u003C\u002Fp>\n\u003Cp>只要在main.js中宣告axios後加上這行\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-javascript\">import axios from &#039;axios&#039;;\n\naxios.defaults.baseURL=&#039;http:\u002F\u002Flocalhost:3031&#039;;  \u002F\u002F宣告baseURL\n\napp.use(VueAxios, axios)\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>之後只要使用了axios，他會自動加上baseURL，例如\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-javascript\">let url = &#039;\u002Fapi&#039;\n axios.get(url)\n .then((res)=&gt;{\n\n })\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>他就會送出這個位置http:\u002F\u002Flocalhost:3031\u002Fapi\u003C\u002Fp>\n\u003Cp>通常還會搭配.env文件來使用，將環境變數先存在.env中，例如建立一個.env.dev的文件，裡面寫下\u003C\u002Fp>\n\u003Cpre>\u003Ccode>NODE_ENV=development\nAPI_URL=http:\u002F\u002Flocalhost:3031\u002F\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>然後在main.js中串上env的環境變數就可以了\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-javascript\">import axios from &#039;axios&#039;;\n\naxios.defaults.baseURL=process.env.API_URL\n\u003C\u002Fcode>\u003C\u002Fpre>\n","2022-11-02T10:00:27",{"name":88,"link":89},[290,291,292],{"name":22,"slug":22},{"name":43,"slug":43},{"name":61,"slug":61},"\u002Fblog\u002Fnodeaxios自動加上網址",{"id":295,"slug":296,"title":297,"description":298,"content":299,"date":300,"image":86,"imageAlt":297,"author":301,"categories":302,"path":305,"minRead":29},138,"cypress如果遇到測試當掉error-code-5","[Cypress]如果遇到測試當掉(error code 5)","如果遇到測試當掉(error code 5) 由於用Cypress跑迴圈測試時遇到此問題，推測可能是記憶體爆掉 […]","\u003Ch1>如果遇到測試當掉(error code 5)\u003C\u002Fh1>\n\u003Cp>由於用Cypress跑迴圈測試時遇到此問題，推測可能是記憶體爆掉了，查了一下可以將Cypress儲存測試結果的快照關閉，請\u003Ca href=\"https:\u002F\u002Fdocs.cypress.io\u002Fguides\u002Freferences\u002Fconfiguration#Global\" title=\"參照\">參照\u003C\u002Fa>中的\u003Cem>numTestsKeptInMemory\u003C\u002Fem>設定。\u003Cbr \u002F>\n有很多人也遇到類似問題，解決方式如下：\u003Cbr \u002F>\n將cypress.json or cypress.config.json中加入”numTestsKeptInMemory”: 0。\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-json\">const { defineConfig } = require(&quot;cypress&quot;);\n\nmodule.exports = defineConfig({\n  e2e: {\n    setupNodeEvents(on, config) {\n      \u002F\u002F implement node event listeners here\n    },\n    &quot;numTestsKeptInMemory&quot;: 0,\n  },\n});\u003C\u002Fcode>\u003C\u002Fpre>\n","2022-10-06T20:00:07",{"name":88,"link":89},[303,304],{"name":17,"slug":18},{"name":43,"slug":43},"\u002Fblog\u002Fcypress如果遇到測試當掉error-code-5",{"id":307,"slug":308,"title":309,"description":310,"content":311,"date":312,"image":86,"imageAlt":309,"author":313,"categories":314,"path":316,"minRead":29},43,"react在github-pages實作historyapi","[React]在github pages實作historyAPI","在github pages實作historyAPI 最近在摸個React專案想部署到github pages當 […]","\u003Ch1>在github pages實作historyAPI\u003C\u002Fh1>\n\u003Cp>最近在摸個React專案想部署到github pages當\u003Cdel>白嫖仔\u003C\u002Fdel>，結果遇到使用historyAPI的route不能順利在github pages上使用。\u003Cbr \u002F>\n翻了許多教學都說可以但一直失敗，最後終於找到原因，必須要使用\u003Cstrong>HashRouter\u003C\u002Fstrong>，這部分很多教學沒有說清楚，使用方式如下。\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-javascript\">import React, { Component } from &quot;react&quot;;\nimport { HashRouter, Route, Switch } from &quot;react-router-dom&quot;;\nimport Home from &quot;.\u002Fcomponents\u002FHomepage&quot;;\nimport Page2 from &quot;.\u002Fcomponents\u002FPage2&quot;;\nimport &quot;.\u002FApp.css&quot;;\nclass App extends Component {\nrender() {\n  return (\n    &lt;div&gt;\n      &lt;HashRouter basename={process.env.PUBLIC_URL}&gt;\n        &lt;div className=&quot;App&quot;&gt;\n        &lt;Switch&gt;\n          &lt;Route exact path=&quot;\u002F&quot; component={Home} \u002F&gt;\n          &lt;Route exact path=&quot;\u002Fpage2&quot; component={Page2} \u002F&gt;\n        &lt;\u002FSwitch&gt;\n        &lt;\u002Fdiv&gt;\n      &lt;\u002FHashRouter&gt;\n    &lt;\u002Fdiv&gt;\n    );\n  }\n}\nexport default App;\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>設定.env\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-javascript\">PUBLIC_URL=你的網域\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>要使用react-router-dom的HashRouter，並且設定basename，這樣build完後將打包資料部署到github pages上，就能完美使用囉。\u003Cbr \u002F>\n切換router的寫法會像是這樣：domain\u002F#\u002F\u003Crouter-name>\u003Cbr \u002F>\n本方式參考\u003Ca href=\"https:\u002F\u002Ftowardsdev.com\u002Fgithub-pages-does-not-work-for-me-f9d48fd44a5f\" title=\"這篇\">這篇\u003C\u002Fa>\u003C\u002Fp>\n\u003Ch2>關於React-router上使用historyAPI\u003C\u002Fh2>\n\u003Cp>historyAPI真的滿方便的，但我還在研究中，在react實作有一些需要注意的可以參考\u003Ca href=\"https:\u002F\u002Fstackoverflow.com\u002Fquestions\u002F27928372\u002Freact-router-urls-dont-work-when-refreshing-or-writing-manually\" title=\"此篇\">此篇\u003C\u002Fa>。\u003C\u002Fp>\n","2022-05-23T17:19:50",{"name":88,"link":89},[315],{"name":43,"slug":43},"\u002Fblog\u002Freact在github-pages實作historyapi"]