Hero Image
[DIY] 用 Render Function 打造靈活的 CheckBox 元件範例

情境1:要選取多個 ckeckbox 對應到資料庫的欄位,欄位值是一串YN代表某個選項是否有被選去,例如: YNNYYNNYYN 情境2:要選取多個 ckeckbox 對應到資料庫的欄位,欄位值只有一個,可能是任何字元,例如: 1 可以打造兩個元件,分別對應至單選、多選 單選元件 程式碼 (Code) Vue.component('x-ck-single', { props: { disabled: { type: Boolean, default: () => false }, // checkbox 的標記 [string] || [{text:string, value:any}] labels: { type: Array, default: () => ['Yes', 'No'] }, value: { default: () => null }, trueValue: { default: () => 'Y' }, falseValue: { default: () => 'N' }, inline: { type: Boolean, default: () => false }, }, data() { return { innervalue_: this.value, } }, watch: { value(v) { this.innervalue_ = v }, }, computed: { innervalue: { get() { return this.innervalue_ }, set(v) { this.innervalue_ = v this.$emit('input', v) }, }, }, render: function (h) { const self = this let len = self.labels.length // labels 的長度 let allStr = self.labels.every((label) => typeof label == 'string') // 是否為 string let allOkObj = self.labels.every((l) => !!l.text && !!l.value) // 是否為合法的物件(如果不是 string) let siblingConf = null if (allOkObj) { siblingConf = self.labels.map((l) => _.pick(l, ['text', 'value'])) } else if (allStr && len <= 2) { siblingConf = self.labels.map((text, idx) => { let value = idx === 0 ? self.trueValue : self.falseValue return { text, value } }) } if (!siblingConf) { let errStr = '無法正確設定元件,len,allStr,allOkObj' return h('div', errStr, len, allStr, allOkObj) } // 設定 CheckBox let { disabled } = self let hideDetails = true let dense = true const baseProps = { hideDetails, dense, disabled } const baseClass = self.inline ? ['d-inline-block'] : [] let siblings = siblingConf.map((c) => { let props = { label: c.text, inputValue: self.innervalue === c.value, ...baseProps, } // 如果只有一個選項,取消勾選時就顯示 falseValue let valueOnNull = len === 1 ? self.falseValue : null let on = { change: (e) => (self.innervalue = e ? c.value : valueOnNull), } return h("v-checkbox", { props, class: baseClass, on }) }) // 傳回整個元件 return h('div', {}, siblings) } }) {{value === null ? 'null' : value}} 多選元件 程式碼 (Code)

Hero Image
[架構] 多層式架構(Multitier Architecture)

多層式架構 Multitier Architecture 或稱 N-Tier Architecture, 是 Client–server architecture 的一種, 多層架構的層可以是 layer 或 tier,這兩者之間主要的差別在於 layer 指程式邏輯在應用程式的位置; 而 tier 指 layer 在系統上實際部屬執行的位址,屬於物理層級的指涉。 這一篇的層指的是 tier。 N-tier model N-tier model,層與層之間的邊界有 N-1 個,而程式跨邊界會造成巨大的效能損失, 一說為光是跨越同一台機器上不同進程(process)邊界存取資源損失就大約1000倍, 如果透過網路進行遠端呼叫勢必損失更多, 因此每跨越一個邊界進行資源存取效能就會以幾何級數損失。 且增加邊界在軟體設計上會增加複雜度,簡單的應用程式使用多層式架構很容易造成過度設計(over design), 因此如何適當添加層級(tier)也是一門學問,添加層級時必需考量如何在應用程式所部屬的環境獲取最大的成本效益。 軟體是否採用多層式架構必須以多層式架構的優缺點進行取捨(尤其是可擴展性與效能之間)。 提高可擴展性(scalability)。 提高效能(performance)。 提高容錯率(fault tolerance)。 提高安全性(security)。 1-tier model 所有的 layer 都在同一機器、同一記憶體空間內運行,因此不需考慮網路造成的性能損失。 2-tier model layer 分配至兩個不同的記憶體空間運行,記憶體空間可能位於相同或兩台不同的機器上(通常是不同機器),典型的例子是分配到 client、server 上運行。 3-tier model 多層式架構當中最常使用的就是三層架構(three-tier architecture)。 三層架構與Web應用程式來說明如下: 表現層(Presentation):又稱為 UI 層,以Web來說就是呈現出來的前端網頁。 應用程式層(Application):以 Web 來說相當於伺服器上執行的應用程式。 資料層(Data):這一層包含資料儲存、呈現機制,以 Web 來說通常指 Database。 Reference wikipedia - Multitier architecture Rockford Lhotka - Should all apps be n-tier? N Tier(Multi-Tier), 3-Tier, 2-Tier Architecture with EXAMPLE

Hero Image
[Tools] 10分鐘建造 proxy 克服跨網域資源存取(CORS)問題

前後端分離的開發環境以 Ajax 呼叫資源時時會遇到跨網域存取的問題, 一些比較全面的開發環境像是 webpack、vue-cli 等等通常提供內建開發代理伺服器可供設置, 如果要對於不熟悉的開發環境進行快速除錯 (例如 vue 開發者臨時檢查其他框架的程式碼遇到跨網域問題), 重新研究如何設置開發環境跨網域代理伺服器往往就花費多餘的時間 (不過最終還是要搭建起來阿,喂~~), 因此紀錄一下怎麼用 node.js 建立一個通用的代理伺服器處理跨網域問題,整個過程不超過5分鐘。 步驟 首先安裝 node.js 建立專案資料夾 建立一個資料夾叫做 proxy 存放這個專案吧,手動建立也可以。 mkdir proxy 起始專案 用指令移動到該專案資料夾下,起始專案: cd proxy npm init 安裝相依性 npm i express http-proxy-middleware cors 建立 app.js const express = require('express'); const cors = require('cors'); const { createProxyMiddleware } = require('http-proxy-middleware'); // 建立一個 Express 伺服器 const app = express(); app.use(cors()) // 設定 Express 伺服器的 Host、Port const PORT = 3000; const HOST = "localhost"; // 設定代理到 google 的 Proxy 端點 app.use('/google', createProxyMiddleware({ target: "https://www.google.com/", changeOrigin: true, pathRewrite: { [`^/google`]: '', }, })); // 設定代理到 yahoo 的 Proxy 端點 app.use('/yahoo', createProxyMiddleware({ target: "https://tw.yahoo.com/", changeOrigin: true, pathRewrite: { [`^/yahoo`]: '', }, })); // 啟動代理伺服器 app.listen(PORT, HOST, () => { console.log(`Starting Proxy at ${HOST}:${PORT}`); }); 啟動代理伺服器 $ node app.js [HPM] Proxy created: / -> https://www.google.com/ [HPM] Proxy rewrite rule created: "^/google" ~> "" [HPM] Proxy created: / -> https://tw.yahoo.com/ [HPM] Proxy rewrite rule created: "^/yahoo" ~> "" Starting Proxy at localhost:3000 測試 打開隨意網頁 F12,用 fetch api 透過 proxy 對 google 或 yahoo 發起 get 請求成功獲得資訊,且 Header 裡面會有Access-Control-Allow-Origin: *:

Hero Image
[.NET Core] 不阻塞的非同步控制器(Non-Blocking Asynchronous Controllers)

ASP .NET Core 當中的 Web 控制器屬於IO密集的應用程式,當中主要使用的 TAP 是一種簡易使用、語言層級的非同步設計模式。 透過 TAP 可設計出非同步(Asynchronous)/非阻塞(Non-Blocking)的 Web API,大幅提高 Web 應用程式的併發性(Concurrency)。 非同步方法 C# 當中基於 TAP 設計的的非同步方法 (TAP method) 有幾個特性: 產生可等待 awaitable 型別 (Task, Task<TResult>, ValueTask, 和 ValueTask<TResult>), 其中以 Task、Task<TResult>最常見。 非同步方法的參數順序通常跟同步版本的方法相同,但方法名稱以 Async 結尾。 async、await await 運算子用來等待非同步行為完成, 或等待非同步行為完成後解析回傳值, await 運算子只能用在非同步方法中, 因此 await 運算子的外層方法必須套用 async 修飾, 否則會出現錯誤。 非同步 Action 設計原則: 總是加上 async 關鍵字 async 的方法裡面可以等待非同步方法。 action 前加上 async 的作用在於建立一個管理回傳任務的狀態機(state machine), 當 async 方法擲出例外時會被狀態機捕獲並放到任務中回傳, 而這也是以 Task 作為回傳值的方法的預期行為。 如果沒有 async 關鍵字則擲出的例外會被直接傳遞到呼叫者(caller), 因此除非確定該 aciton 不會擲出任何例外,否則一律加上 async。

Hero Image
[.NET Core] ASP .NET Core 3.1 驗證與授權(二)-驗證設定

驗證方案(Authentication Scheme) 驗證方案包含兩個部分: 驗證處理函式(Authentication handler),可能是 IAuthenticationHandler 或 AuthenticationHandler 的實作,相當於驗證方案的行為,責任範圍涵蓋: 驗證使用者, 驗證成功時,建構呈現使用者識別(user identity)的 AuthenticationTicket。 驗證失敗時,回傳 ’no result’ 或 ‘failure’ 負責從請求上下文(request context)中建構使用者識別 (user identity)。 定義了 challenge/forbid action。 驗證處理函式的設定選項(Opitons of Authentication handler)。 驗證方案當中的 authencate action 負責從請求上下文(request context)中建構使用者識別 (user identity), 常見的例子為: cookie authentication scheme 從 cookie 資訊建構 user identity. JWT bearer scheme 反序列化(deserialize)、驗證(validate) token,並從 token 所攜帶資訊建構 user identity 使用驗證方案 在 Startup.ConfigureServices 以 AddAuthentication 註冊驗證服務時會回傳一個 AuthenticationBuilder, AuthenticationBuilder 設定驗證方案的方式有: 呼叫 scheme-specific 擴充方法,例如 AddJwtBearer、AddCookie,這些擴充方法會自動呼叫 AuthenticationBuilder.AddScheme 設定需要的驗證方式。 以 AuthenticationBuilder 內建方法 AddScheme 手動設定,一般來說較少使用。 P.S.另外可使用 polycy schemes 把多個 scheme 整合到一個使用。

Hero Image
[Tools] 在 linux 中使用 google drive cli

重新編譯 gdrive 取得憑證 首先到Google API Console, 建立專案 啟動 Google Drive API。 啟用之後選擇左邊的"設定同意畫面",填寫必要的欄位其他依照需求設置就好 建立一組 OAth2 憑證,會有 Client ID 和 Secret 編譯專案 1.安裝 Golang 2.下載 gdrive 專案 git clone [email protected]:prasmussen/gdrive.git 3.修改專案下的handlers_drive.go,把下列兩行改成拿到的 Client ID、Secret const ClientId = "*************************************.com" const ClientSecret = "*************" 4.取得需要的 golang 套件 go get github.com/prasmussen/gdrive 5.到專案資料夾下編譯,編譯完後就會有得到執行檔 go build 設定 設置環境變數 將編譯好的執行檔上傳Linux,假設放在${HOME}/gdrive-linux-x64: # 建立${HOME}/bin $ mkdir -p ${HOME}/bin # 把gdrive-linux-x64放進去重新命名為gdrive $ mv ${HOME}/gdrive-linux-x64 ${HOME}/bin/gdrive # 賦予gdrive執行權限 $ chmod u+x ${HOME}/bin/gdrive # 如果PATH裡面找不到${HOME}/bin就新增並重新讀取環境設定 $ echo ${PATH} | grep -qE ${HOME}/bin[^/] && \ > echo "PATH=\${HOME}/bin:\${PATH}" >> ~/.bashrc && \ > . ~/.bashrc 連結google帳戶 安裝好Google drive CLI後需要取得雲端硬碟帳戶授權,gdrive預設會在${HOME}/.gdrive底下找授權檔, 由於目前我們沒有任何授權,所以需要先下簡單的指令觸發token請求, 取得token之後會出現一個檔案叫做${HOME}/.gdrive/token_v2.json, 並且在終端機出現雲端硬碟所有檔案及資料夾清單(預設最多出現30筆):

Hero Image
[JS] 提升(Hoisting)與暫時性死區(Temporal Dead Zone)

網路上時常見到充滿 function 與 var 的 JavaScript 求值題目, 筆者在釐清 Hoisting 和 TDZ 的觀念前時常覺得答案出乎意料, 雖然現在撰寫程式碼都已經避免使用 var,但是維護 legacy code 還是會用到,因此在這裡做個筆記。 Hoisting 對學過伺服器端語言(C#、C/C++…)的人來說,預期試圖對未宣告的變數取值會出現 ReferenceError是很正常的事, 在 JavaScript 中也是如此: console.log(x) // ReferenceError: x is not defined 但 JavaScript 把 var 宣告變數放在後面,x 前面對 x 取值就變成 undefine, 在 JavaScript 當中對變數取值獲得 undefine 代表變數處於宣告後已分配記憶體空間(初始化、initiation)但尚未賦值的狀態, 但是明明 JavaScript 在宣告前就取值,怎麼能夠認得 x、而且知道 x 被初始化為 undefined 呢? console.log(x) // undefined var x 原因在於 javascript 會先程式中的蒐集 var(let/const/function) 宣告並釐清對應的作用域, 最後再執行程式碼,這個行為就如同宣告被提升(Hoisting)到前面行數的程式碼中一樣。 JavaScript 跟傳統 OOP 語言一樣,在變數提升後、宣告初始化、賦值等等時機取用變數會獲得不同輸出結果, 這篇文章(link)中提到, 稱 JavaScript 為直譯式(解釋型)語言實際上是通俗的說法,語言本身沒有規定實現的方式(編/直譯), 舉例來說 Chrome 瀏覽器所使用的 Google V8 JavaScript engine, 所帶的解釋器 (interpreter) 會將程式編譯 (compile) 成字節碼 (bytecode), 最後再由編譯器 (compiler) 即時編譯 (JIT;just-in-time compilation) 編譯成機器碼執行, 而 JavaScript 被編譯時宣告會被蒐集到最頂端進行定義並區分作用域(scope),這個行為就是 Hoisting ,記住重點在於JS編譯後會先定義變數及區分作用域,編譯後的程式看起來就像這樣: