[{"content":"","date":null,"permalink":"https://metavige.github.io/","section":"@metavige","summary":"","title":"@metavige"},{"content":"","date":null,"permalink":"https://metavige.github.io/tags/ai/","section":"Tags","summary":"","title":"Ai"},{"content":"","date":null,"permalink":"https://metavige.github.io/tags/ai-agent/","section":"Tags","summary":"","title":"Ai-Agent"},{"content":"","date":null,"permalink":"https://metavige.github.io/categories/","section":"Categories","summary":"","title":"Categories"},{"content":"","date":null,"permalink":"https://metavige.github.io/tags/prompt/","section":"Tags","summary":"","title":"Prompt"},{"content":"","date":null,"permalink":"https://metavige.github.io/tags/skill/","section":"Tags","summary":"","title":"Skill"},{"content":"","date":null,"permalink":"https://metavige.github.io/tags/","section":"Tags","summary":"","title":"Tags"},{"content":"","date":null,"permalink":"https://metavige.github.io/posts/","section":"文章","summary":"","title":"文章"},{"content":"","date":null,"permalink":"https://metavige.github.io/categories/%E6%80%9D%E8%80%83/","section":"Categories","summary":"","title":"思考"},{"content":"最近在重新設計 AI Agent 的 Skill，有一個蠻大的體悟。\n真正昂貴的不是 AI 多想幾輪 #以前我們總是很怕第一步做錯，所以在流程裡加了很多確認點，一步一步小心翼翼。 但現在我反而覺得，真正昂貴的不是 AI 多想幾輪，而是每一次等待人回覆、切換上下文所造成的中斷成本。\n成本模型已經改變 #更有趣的是，我發現整個成本模型其實已經改變了。 以前，修改程式是一件昂貴的事情，所以我們希望一開始就把方向決定好。 現在，有了 AI Agent，程式修改的成本大幅降低。 真正昂貴的，反而變成「驗證」。 也就是，如何確認它沒有改壞、沒有引入新的問題，以及最終是否真的符合需求。\n與其一次做對，不如讓犯錯夠便宜 #因此，與其要求 Agent 一次就做對，不如建立一套完善的驗證機制。 讓它可以一直嘗試、一直修正，直到所有測試都通過、所有驗收條件都滿足。 重點不再是「避免犯錯」，而是「讓犯錯的成本足夠低」。\n把 Prompt 寫成契約 #這也讓我重新思考 Prompt 與 Skill 的設計方式。 以前，我們習慣把流程寫得非常詳細，每一步都規定該怎麼做。 現在，我更傾向把它設計成一份「契約」。 定義清楚目標、限制、驗收標準，以及哪些情況一定要回來詢問我。 至於中間如何完成，就交給 Agent 自己發揮。\n從命令式到宣告式 #這其實很像軟體工程一路走來的演進。 從命令式（Imperative）逐漸走向宣告式（Declarative）。 我們不再告訴系統「怎麼做」，而是告訴它「我要什麼」。\n從執行者變成委託者 #我覺得，人在 AI 時代的角色也正在改變。 我們慢慢從每一步的執行者，變成設定目標與邊界的委託者。 真正重要的，不再是設計每一個流程細節。 而是定義清楚起點、終點、限制，以及驗收標準。 剩下的，就交給 Agent 不斷迭代，直到達成我們真正想要的結果。\n","date":"2026年7月28日","permalink":"https://metavige.github.io/2026/07/28/verification-is-the-new-bottleneck/","section":"文章","summary":"\u003cp\u003e最近在重新設計 AI Agent 的 Skill，有一個蠻大的體悟。\u003c/p\u003e","title":"當修改變便宜，驗證變昂貴：AI 時代的工作新思維"},{"content":"一個專案的開始 #開發專案時，通常會在本地端進行，並結合 Database 和 Cache 等服務。目前常見的做法是使用 Docker 啟動 Database 或 Cache container 來建立本地測試環境，而非直接連線到公司提供的測試環境。\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 version: \u0026#39;3.9\u0026#39; services: postgres: image: postgres:16 container_name: my_postgres restart: always environment: POSTGRES_USER: myuser POSTGRES_PASSWORD: mypassword POSTGRES_DB: mydb ports: - \u0026#34;5432:5432\u0026#34; volumes: - pg_data:/var/lib/postgresql/data volumes: pg_data: {} 以上面的例子來說，你需要先有以下的知識：\n你需要知道 docker 使用的基本知識 你需要懂 docker-compose 的格式 你需要知道 postgres container 所提供的設定資料種類與設定方式 當然，以現代結合 AI 的做法，以上的方式其實很簡單，不容易出問題 (以上範例也是直接讓 ChatGPT 給我的)。但如果你還需要更深入一點的操作，包含取得連線字串，放到系統中使用，或者是管理資料庫的生命週期，舉例來說如果有以下問題：\n資料庫什麼時候啟動？ 啟動後如果要重設的方式？ 如果有一個以上的服務需要使用資料庫，但是 schema 不一樣？ 假設有一個以上的服務需要用到資料庫，但是實際生產環境是不同的兩個 database ? 我該怎麼啟動兩個資料庫，然後使用不同的 Port ? 怎麼樣讓資料庫已啟動就有相對應的 schema or data? 如果一個新進專案的工程師要了解以上服務的關聯性以及啟動方式，你需要將以上的資訊整理成一份 README，或者是寫好一個 script 來將以上的動作都一次處理完畢。但這又有其他問題：\nREADME 要怎麼寫，才能讓新加入的成員很好理解？ script 要用什麼格式？Bash or Powershell？（如果遇到團隊有使用不同環境的電腦） 如果遇到要存取檔案，路徑格式的表現方式？ 如果有使用到其他工具，還需要說明其他工具的安裝方式 所以，如果公司內沒有一個懂以上資訊或怎麼做的人，這些對團隊來說，都會是痛點。而且如果團隊成員會有不同想法，不同意見，以及考慮到人員異動，專案成員來來去去，情況就更複雜了。\n當然，以上問題以現代的做法來講，你都可以讓 AI 來幫你處理掉以上的問題，你可以不需要找其他工具來處理，直接把以上問題都整理好，一次讓 AI 處理掉以上問題。如果有發生其他狀況，直接讓 AI 來幫你處理掉以上問題。但是，你每個專案可能都需要來一次。如果中途要修改，你還需要花一點時間測試與了解做法，這些，都是隱性成本 (人力 or Tokens 數)。\nAspire is glue #微軟認為，Aspire 這個工具是一個用來黏合其他服務的一個方式，而且是使用我們已經習慣的 C# 語言，搭配已經開發好的 NuGet Packages，你可以很快速的啟動資料庫，並且將連線字串直接注入到專案中使用，也不需要再管理連線字串。\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 using Aspire.Hosting; var builder = DistributedApplication.CreateBuilder(args); // 建立 PostgreSQL 資料庫 var postgres = builder.AddPostgres(\u0026#34;postgres\u0026#34;) .WithImage(\u0026#34;postgres:16\u0026#34;) // 指定版本 .WithEnvironment(\u0026#34;POSTGRES_PASSWORD\u0026#34;, \u0026#34;mypassword\u0026#34;) .WithEnvironment(\u0026#34;POSTGRES_USER\u0026#34;, \u0026#34;myuser\u0026#34;) .WithEnvironment(\u0026#34;POSTGRES_DB\u0026#34;, \u0026#34;mydb\u0026#34;) .WithDataVolume(\u0026#34;pg_data\u0026#34;); // 建立資料庫實例，並產生 connection string var postgresDb = postgres.AddDatabase(\u0026#34;mydb\u0026#34;); // 建立 Sample.Api WebAPI 並注入資料庫 ConnectionString builder.AddProject\u0026lt;Projects.Sample_Api\u0026gt;(\u0026#34;sample-api\u0026#34;) .WithReference(postgresDb); // Aspire 會自動注入連線字串 builder.Build().Run(); 以上的專案啟動後，還會有一個漂亮的 Dashboard 可以使用，裡面可以看所有服務的 Console Output，並且直接結合 OpenTelemetry，有 Tracing、Metric 等相關資訊可以檢視。\n現在已經到 Aspire 13，是可以嘗試看看了\n","date":"2025年11月19日","permalink":"https://metavige.github.io/2025/11/19/2025-11-19-aspire/","section":"文章","summary":"\u003ch2 id=\"一個專案的開始\" class=\"relative group\"\u003e一個專案的開始 \u003cspan class=\"absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100\"\u003e\u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700\" style=\"text-decoration-line: none !important;\" href=\"#%e4%b8%80%e5%80%8b%e5%b0%88%e6%a1%88%e7%9a%84%e9%96%8b%e5%a7%8b\" aria-label=\"定位點\"\u003e#\u003c/a\u003e\u003c/span\u003e\u003c/h2\u003e\u003cp\u003e開發專案時，通常會在本地端進行，並結合 Database 和 Cache 等服務。目前常見的做法是使用 Docker 啟動 Database 或 Cache container 來建立本地測試環境，而非直接連線到公司提供的測試環境。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cdiv style=\"color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\n\u003ctable style=\"border-spacing:0;padding:0;margin:0;border:0;\"\u003e\u003ctr\u003e\u003ctd style=\"vertical-align:top;padding:0;margin:0;border:0;\"\u003e\n\u003cpre tabindex=\"0\" style=\"color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\u003ccode\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679\"\u003e 1\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679\"\u003e 2\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679\"\u003e 3\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679\"\u003e 4\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679\"\u003e 5\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679\"\u003e 6\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679\"\u003e 7\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679\"\u003e 8\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679\"\u003e 9\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679\"\u003e10\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679\"\u003e11\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679\"\u003e12\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679\"\u003e13\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679\"\u003e14\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679\"\u003e15\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679\"\u003e16\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679\"\u003e17\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679\"\u003e18\n\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\n\u003ctd style=\"vertical-align:top;padding:0;margin:0;border:0;;width:100%\"\u003e\n\u003cpre tabindex=\"0\" style=\"color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\u003ccode class=\"language-yaml\" data-lang=\"yaml\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#7ee787\"\u003eversion\u003c/span\u003e:\u003cspan style=\"color:#6e7681\"\u003e \u003c/span\u003e\u003cspan style=\"color:#a5d6ff\"\u003e\u0026#39;3.9\u0026#39;\u003c/span\u003e\u003cspan style=\"color:#6e7681\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#6e7681\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#7ee787\"\u003eservices\u003c/span\u003e:\u003cspan style=\"color:#6e7681\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#6e7681\"\u003e  \u003c/span\u003e\u003cspan style=\"color:#7ee787\"\u003epostgres\u003c/span\u003e:\u003cspan style=\"color:#6e7681\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#6e7681\"\u003e    \u003c/span\u003e\u003cspan style=\"color:#7ee787\"\u003eimage\u003c/span\u003e:\u003cspan style=\"color:#6e7681\"\u003e \u003c/span\u003e\u003cspan style=\"color:#a5d6ff\"\u003epostgres:16\u003c/span\u003e\u003cspan style=\"color:#6e7681\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#6e7681\"\u003e    \u003c/span\u003e\u003cspan style=\"color:#7ee787\"\u003econtainer_name\u003c/span\u003e:\u003cspan style=\"color:#6e7681\"\u003e \u003c/span\u003e\u003cspan style=\"color:#a5d6ff\"\u003emy_postgres\u003c/span\u003e\u003cspan style=\"color:#6e7681\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#6e7681\"\u003e    \u003c/span\u003e\u003cspan style=\"color:#7ee787\"\u003erestart\u003c/span\u003e:\u003cspan style=\"color:#6e7681\"\u003e \u003c/span\u003e\u003cspan style=\"color:#a5d6ff\"\u003ealways\u003c/span\u003e\u003cspan style=\"color:#6e7681\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#6e7681\"\u003e    \u003c/span\u003e\u003cspan style=\"color:#7ee787\"\u003eenvironment\u003c/span\u003e:\u003cspan style=\"color:#6e7681\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#6e7681\"\u003e      \u003c/span\u003e\u003cspan style=\"color:#7ee787\"\u003ePOSTGRES_USER\u003c/span\u003e:\u003cspan style=\"color:#6e7681\"\u003e \u003c/span\u003e\u003cspan style=\"color:#a5d6ff\"\u003emyuser\u003c/span\u003e\u003cspan style=\"color:#6e7681\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#6e7681\"\u003e      \u003c/span\u003e\u003cspan style=\"color:#7ee787\"\u003ePOSTGRES_PASSWORD\u003c/span\u003e:\u003cspan style=\"color:#6e7681\"\u003e \u003c/span\u003e\u003cspan style=\"color:#a5d6ff\"\u003emypassword\u003c/span\u003e\u003cspan style=\"color:#6e7681\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#6e7681\"\u003e      \u003c/span\u003e\u003cspan style=\"color:#7ee787\"\u003ePOSTGRES_DB\u003c/span\u003e:\u003cspan style=\"color:#6e7681\"\u003e \u003c/span\u003e\u003cspan style=\"color:#a5d6ff\"\u003emydb\u003c/span\u003e\u003cspan style=\"color:#6e7681\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#6e7681\"\u003e    \u003c/span\u003e\u003cspan style=\"color:#7ee787\"\u003eports\u003c/span\u003e:\u003cspan style=\"color:#6e7681\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#6e7681\"\u003e      \u003c/span\u003e- \u003cspan style=\"color:#a5d6ff\"\u003e\u0026#34;5432:5432\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#6e7681\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#6e7681\"\u003e    \u003c/span\u003e\u003cspan style=\"color:#7ee787\"\u003evolumes\u003c/span\u003e:\u003cspan style=\"color:#6e7681\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#6e7681\"\u003e      \u003c/span\u003e- \u003cspan style=\"color:#a5d6ff\"\u003epg_data:/var/lib/postgresql/data\u003c/span\u003e\u003cspan style=\"color:#6e7681\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#6e7681\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#7ee787\"\u003evolumes\u003c/span\u003e:\u003cspan style=\"color:#6e7681\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#6e7681\"\u003e  \u003c/span\u003e\u003cspan style=\"color:#7ee787\"\u003epg_data\u003c/span\u003e:\u003cspan style=\"color:#6e7681\"\u003e \u003c/span\u003e{}\u003cspan style=\"color:#6e7681\"\u003e  \n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\u003c/tr\u003e\u003c/table\u003e\n\u003c/div\u003e\n\u003c/div\u003e\u003cp\u003e以上面的例子來說，你需要先有以下的知識：\u003c/p\u003e","title":"Aspire 13"},{"content":"","date":null,"permalink":"https://metavige.github.io/tags/dotnet/","section":"Tags","summary":"","title":"Dotnet"},{"content":"","date":null,"permalink":"https://metavige.github.io/categories/%E7%A8%8B%E5%BC%8F%E9%96%8B%E7%99%BC/","section":"Categories","summary":"","title":"程式開發"},{"content":"","date":null,"permalink":"https://metavige.github.io/tags/devops/","section":"Tags","summary":"","title":"Devops"},{"content":"","date":null,"permalink":"https://metavige.github.io/tags/sentry/","section":"Tags","summary":"","title":"Sentry"},{"content":"Log 紀錄，在 DevOps 的情境中，其實是一個很重要的項目，所有的系統在上線的時候，都應該要有適當的 Log 紀錄\n以方便後續的問題追蹤，尤其是在 Microservice 的設計上面\n當你的服務或系統是以 docker container 的方式啟動，如果只是以 container stdout/stderr 的方式來取得紀錄，實際上後續的追蹤並不容易，所以我自己找了一下網路上的解決方案，找到了 sentry 的這套系統。\n而在公司內部使用，如果公司環境並不允許 Log 紀錄在外部的網站，就需要考慮 Self-Hosted 的建置，剛好 sentry 有提供 onpremise 的部分，就找了這個 source 來驗證一下在本地端建置 sentry 的步驟。\nSelf-Hosted 下載 # 根據官方網站的說明，需要先下載 onpremise 的原始碼。 需要先安裝好 docker 跟 docker-compose，版本號可以參考原始碼中的 README.md 建置 onpremise # 前置作業，其實是會先根據下載的原始碼，去 build sentry 的 image 出來 可以直接執行 ./install.sh，來直接建置 onpremise 的 image。不過如果要指定 sentry 版本的話，可以先設定好環境變數 SENTRY_IMAGE=getsentry/sentry:10 的方式，來建置 sentry v10 的 image。 如果直接執行 ./install.sh，會將 sentry 目錄下的 config.example.yml 以及 sentry.conf.example.py 複製成 config.yml 以及 sentry.conf.py 兩個檔案，並且開始建置 onpremise sentry image 建置完成之後，到最後一步，只要出現下面的訊息，就表示可以啟動 sentry 來使用了！ .... Cleaning up... ---------------- You\u0026#39;re all done! Run the following command to get Sentry running: docker-compose up -d 修改設定 #因為是使用 docker 來啟動 sentry，所以需要修改 sentry 的 SENTRY_DISALLOWED_IPS 設定\n下面是預設的設定值，需要將下面的設定，複製到 sentry/sentry.conf.py 檔案中，並且調整 docker 內的 IP 區段，讓 sentry 可以執行，否則會有 502 的錯誤。\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 SENTRY_DISALLOWED_IPS = ( \u0026#39;0.0.0.0/8\u0026#39;, \u0026#39;10.0.0.0/8\u0026#39;, \u0026#39;100.64.0.0/10\u0026#39;, \u0026#39;127.0.0.0/8\u0026#39;, \u0026#39;169.254.0.0/16\u0026#39;, \u0026#39;172.16.0.0/12\u0026#39;, \u0026#39;192.0.0.0/29\u0026#39;, \u0026#39;192.0.2.0/24\u0026#39;, \u0026#39;192.88.99.0/24\u0026#39;, \u0026#39;192.168.0.0/16\u0026#39;, \u0026#39;198.18.0.0/15\u0026#39;, \u0026#39;198.51.100.0/24\u0026#39;, \u0026#39;224.0.0.0/4\u0026#39;, \u0026#39;240.0.0.0/4\u0026#39;, \u0026#39;255.255.255.255/32\u0026#39; ) docker 本身的網路區段，可以到 docker for desktop 的 Resource \u0026gt; Network 設定中去看，就可以知道。\n我自己的設定是 192.168.65.0/24，所以我的設定方式如下：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 SENTRY_DISALLOWED_IPS = ( \u0026#39;0.0.0.0/8\u0026#39;, \u0026#39;10.0.0.0/8\u0026#39;, \u0026#39;100.64.0.0/10\u0026#39;, \u0026#39;127.0.0.0/8\u0026#39;, \u0026#39;169.254.0.0/16\u0026#39;, \u0026#39;172.16.0.0/12\u0026#39;, # \u0026#39;192.0.0.0/29\u0026#39;, \u0026#39;192.0.2.0/24\u0026#39;, \u0026#39;192.88.99.0/24\u0026#39;, \u0026#39;192.168.0.0/16\u0026#39;, \u0026#39;198.18.0.0/15\u0026#39;, \u0026#39;198.51.100.0/24\u0026#39;, \u0026#39;224.0.0.0/4\u0026#39;, \u0026#39;240.0.0.0/4\u0026#39;, \u0026#39;255.255.255.255/32\u0026#39; ) 如果日後在內部網路使用，就需要視情況調整設定。\nIssue #在我實際測試建置的這個時間，實際上如果用 SENTRY_IMAGE=getsentry/sentry:10 ./install.sh 的方式建置 sentry，最後面會出現一個錯誤：\n... Creating sentry_onpremise_postgres_1 ... done upgrade: 1: exec: /etc/sentry/docker-entrypoint.sh: not found Cleaning up... 這個問題，在我測試的時候，還是有 open issues 未解決：\nhttps://github.com/getsentry/onpremise/issues/384 https://github.com/getsentry/onpremise/issues/412 就算不設定 SENTRY_IMAGE，我還是有發現錯誤，所以我就沒有使用最新版本來測試。\n後來，去追蹤了一下問題點，發現在 getsentry/sentry:10 的 image 中，實際上面 /etc/sentry/docker-entrypoint.sh 是存在的，可是，在 docker-compose.yml 內卻設定了一個 volume 去指向目前本地端的 sentry 目錄。\n導致這個檔案就直接被移除了，之後在建置的階段，就會發生錯誤\n後來我自己從 getsentry/sentry:10 的 image 先把這個檔案，複製到本地端的 sentry 目錄，讓他 mount 到 container 內還是可以找到這個檔案，就解決了安裝問題。\nLicense #https://blog.sentry.io/2019/11/06/relicensing-sentry\n目前 sentry 的 License，是使用 BSL，因為只是在公司內部使用，應該不會有授權問題 (只要你不是要拿 sentry 延伸的產品來賣)\n","date":"2020年5月13日","permalink":"https://metavige.github.io/2020/05/13/sentry10-installiation/","section":"文章","summary":"\u003cp\u003eLog 紀錄，在 DevOps 的情境中，其實是一個很重要的項目，所有的系統在上線的時候，都應該要有適當的 Log 紀錄\u003cbr\u003e\n以方便後續的問題追蹤，尤其是在 Microservice 的設計上面\u003c/p\u003e\n\u003cp\u003e當你的服務或系統是以 \u003ccode\u003edocker\u003c/code\u003e container 的方式啟動，如果只是以 container stdout/stderr 的方式來取得紀錄，實際上後續的追蹤並不容易，所以我自己找了一下網路上的解決方案，找到了 \u003ca href=\"https://sentry.io/\" target=\"_blank\" rel=\"noreferrer\"\u003esentry\u003c/a\u003e 的這套系統。\u003c/p\u003e\n\u003cp\u003e而在公司內部使用，如果公司環境並不允許 Log 紀錄在外部的網站，就需要考慮 Self-Hosted 的建置，剛好 sentry 有提供 onpremise 的部分，就找了這個 source 來驗證一下在本地端建置 sentry 的步驟。\u003c/p\u003e","title":"Sentry 10 onpremise installation"},{"content":"","date":null,"permalink":"https://metavige.github.io/tags/docker/","section":"Tags","summary":"","title":"Docker"},{"content":"https://fluxcd.io/\nFlux 目前是 CNCF 的 Sandbox 項目 (2019.08.20)\n圖片出處: https://rammusxu.github.io/2019/07/03/gitops-flux-note/flux-flow.png\n概念 # 原始碼專案透過 CI 方式，建置好 Image，並且發佈到 Docker Registry 另外有一個 Git Repository，專門存放所有的 kubernetes 設定檔案 由 Flux CD 去監聽 Docker Registry 以及 Git Repository 的變化 監聽的 docker image 有新的 tag，可以同步變更 yml 內 tag 資訊 (git push)，以及變更 kubernetes 內的設定。 Git Repository 的設定變更，也會同步 kubernetes 的設定。 前置作業 #安裝 fluxctl #參考 https://docs.fluxcd.io/en/stable/references/fluxctl.html\n準備好一個 Git Repository #準備一個簡單的 repository，存放要設定的 yml\n如果使用的是公司內部的 git repository，需要先參考 https://docs.fluxcd.io/en/stable/guides/use-private-git-host.html 的設定，將 known_hosts 設定到 Pod 內的 ~/.ssh/known_hosts 檔案內，不然會無法連線。下方安裝步驟有說明。\nGit Repository 內可以有目錄的規劃，管理不同種類的設定，fluxcd 會掃描子目錄內的變更。\n設定連線到 kubernetes #執行 fluxctl 的這台機器，需要可以使用 kubectl 指令。\n但是這並非必要，因為可以將 fluxctl 產生出來的檔案，另外透過其他方式做 apply 的動作。\n但是如果需要執行 fluxctl 其他指令，還是需要能連線到 kubernetes。\nFlux 安裝步驟 #建立一個 namespace #官方文件建議，建立一個 namespace 放 flux 的資源 (namespace 的規劃沒有特定，但將資源放在一起會比較好管理)\n$ kubectl create namespace flux 請注意，下方所有的設定都會是需要建立在這邊所建立的 namespace 中，如果這邊的 namespace 名稱有改變，記得改變下方的所有設定的 namespace\n利用 fluxctl 產生 k8s 部署檔案 #$ fluxctl install \\ --git-user={} \\ --git-email={} \\ --git-url={} \\ --git-path={} \\ --name flux \u0026gt; fluxcd.yml 上方的 git-user, git-email, git-url, git-path 是需要參考實際上 git 的位置與設定做調整，下方說明一下各自的意思\ngit-user: 登入 git 的使用者帳號 git-email: 登入 git 的使用者電子郵件 git-url: git repostitory 的位置 git-path: 在 git repository 中，要檢查 yml 的目錄位置 官方文件是直接做 kubectl apply，但為了下面的步驟，建議將輸出導出成檔案，方便修改。\n修改 fluxcd.yml 的 namespace (Optional) #如果第一個步驟建立的 namespace 並非 flux，記得要改變 fluxcd.yml 內的 namespace 設定\n準備 known_hosts (針對 SSH 連線要使用) #透過 ssh-keyscan 指令，用來取得 ssh server 的公鑰，這個動作，是要把這組公鑰，放在 fluxcd 的 POD 中的 ~/.ssh/know_hosts 檔案內，讓 fluxcd 的 POD 可以透過 ssh 連線到 git 的服務器。\n參考 https://docs.fluxcd.io/en/stable/guides/use-private-git-host.html\n為了要放到 kubernetes 去，參考了 flux 的文件，產生下方的 flux-ssh.config.yml\napiVersion: v1 data: known_hosts: | [這邊放 ssh-keyscan 產生的內容] kind: ConfigMap metadata: name: flux-ssh-config namespace: flux name 是參考 fluxcd.yml 中的定義，namespace 參考上方設定\n為了 SSH 設定，修改 fluxcd.yml (請注意 yml 的縮排) #找到下方的設定，取消掉註解 。這個設定會在 Deployment 的 spec/template/spec/volumes 內\n# The following volume is for using a customised known_hosts¬ # file, which you will need to do if you host your own git¬ # repo rather than using github or the like. You\u0026#39;ll also need to¬ # mount it into the container, below. See¬ # https://docs.fluxcd.io/en/latest/guides/use-private-git-host.html - name: ssh-config configMap:¬ name: flux-ssh-config 接下來，找到 Deployment 內的 spec/template/spec/container/volumeMounts 內的設定，取消註解\n# Include this if you need to mount a customised known_hosts¬ # file; you\u0026#39;ll also need the volume declared above.¬ - name: ssh-config¬ mountPath: /root/.ssh 增加 insecure-registry 設定，修改 fluxcd.yml (請注意 yml 的縮排) #一般在公司內部提供的 Private Registry 通常都是走 HTTP 協定，所以需要設定 insecure-registry 讓 flux 可以取得 private registry metadata，下方的設定需要設定在 Deployment 內的 spec/template/spec/container/args 內\n這段是要自行加入的，沒有原始的設定可以取消註解\n# setup insecury-registry¬ - --registry-insecure-host=private.registry.xxxx 上方的設定，參考 https://docs.fluxcd.io/en/stable/references/daemon.html 內的參數說明來調整\n啟動 fluxcd #將上面設定的兩個檔案，flux-ssh.config.yml 以及 fluxcd.yml，透過 kubectl 將設定建立起來 (或透過 kubenetes dashboard 設定)。\n註冊 Git user SSH Key #預設建立好 fluxcd 之後，可以透過 fluxctl identity 指令，取得在 fluxcd POD 內產生的 Public Key，然後設定到 Git 使用者的 SSH 金鑰內，讓 fluxcd 可以存取 Git Repository。\n這邊建立的 ssh public/private key，會存放到 kubernetes volume內，如果有重新建立 fluxcd 的動作，並不需要重新設定 git user ssh key。\n目前設定 Git 的存取，也可以事先產生好 SSH Key，可以參考 https://docs.fluxcd.io/en/stable/guides/provide-own-ssh-key.html\n啟動 yml 內的自動處理機制 #如果要讓 fluxcd 自動化的偵測 Docker Repository 變更，並且同步修改 Git 的設定，需要在 YML 內加入下面的 annotations 才可以：\nannotations: fluxcd.io/automated: \u0026#39;true\u0026#39; 透過上方的設定， flux 會檢查目前要部署的 Image 中是否有新的 Tag，如果有，就會同時間修改 YML 以及 kubernetes 的設定，並且同步推送變更到 Git 去\n跟 Flux 相關的一些注意事項 #存取 private registry #一般如果需要存取 Docker Private Registry，通常會在 Docker Host 內設定 insecure-registry，但是初步設定 flux 時，發現無法讀取 docker daemon.json 內 insecure-registry 的設定\n基本上是可以透過設定 --registry-insecure-host 來達到檢查 Private Registry 的功能。不過如果 Private Regsitry 有設定權限，可能需要參考參考 https://github.com/fluxcd/flux/pull/1314 來設定 (目前沒有測試)\nGit #目前 Flux 的機制，是透過偵測 Git Repository 以及 Docker Registry 變更，來做到 Continuous Delivery 的機制，但是目前預設只會做到 Create/Update 的動作，如果開發者刪除了 Git Repository 內的設定，並不會刪除 kubernetes 內已經建立好的設定。\n如果有需要做到刪除異動的變更處理，可能需要設定 —sync-garbage-collection，但預設的作法會是保守的，因為通常裡面不會只有由 flux 建立的資源。\n影響變更時間 #基本上，這邊的影響變更時間受到幾個時間影響，一個是掃描 GIT 的時間，一個是掃描 Docker Registry 的時間。\n目前內部預設的掃描時間，都是五分鐘，其中影響的參數有兩個，一個是 —sync-interval (控制同步設定到 kubernetes 的頻率)，一個是 —git-poll-interval (查詢 git 新的 commit 的頻率)。\n參考 https://docs.fluxcd.io/en/stable/faq.html ，建議如果要較快速的影響變更，調整 —git-poll-interval 會比較好。\n參考 # https://www.weave.works/technologies/gitops/ https://github.com/fluxcd/flux https://docs.fluxcd.io/en/stable/ https://www.servicemesher.com/blog/flux-get-start-easy/ ","date":"2019年12月16日","permalink":"https://metavige.github.io/2019/12/16/gitops-use-fluxcd/","section":"文章","summary":"\u003cp\u003e\u003ca href=\"\"\u003ehttps://fluxcd.io/\u003c/a\u003e\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003eFlux 目前是 CNCF 的 Sandbox 項目 (2019.08.20)\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cp\u003e\n\n\n\n\n\n\n  \n  \n\u003cfigure\u003e\u003cimg src=\"https://rammusxu.github.io/2019/07/03/gitops-flux-note/flux-flow.png\" alt=\"\" class=\"mx-auto my-0 rounded-md\" /\u003e\n\u003c/figure\u003e\n\u003c/p\u003e\n\u003cp\u003e圖片出處: \u003ca href=\"https://rammusxu.github.io/2019/07/03/gitops-flux-note/flux-flow.png\" target=\"_blank\" rel=\"noreferrer\"\u003ehttps://rammusxu.github.io/2019/07/03/gitops-flux-note/flux-flow.png\u003c/a\u003e\u003c/p\u003e","title":"GitOps - use fluxcd"},{"content":"","date":null,"permalink":"https://metavige.github.io/tags/kubernetes/","section":"Tags","summary":"","title":"Kubernetes"},{"content":"針對網路上面的文章，加上自己的解釋，說明如何避免做到 Docker 的反模式\n原始文章: Docker Anti-Pattern\n反模式 1: 將 Docker 視為虛擬機器 (VM) #Docker 的生命週期，是隨著應用程式的啟動與停止，所以，每個啟動的容器 (Container) 都有可能因爲應用程式發生狀況而停止。而 Docker 本身的機制，會將容器重新以 Image 的原始狀態再重新啟動。\n與虛擬機器不同，容器本身並不需要長期，不間斷的執行。容器本身的特性就是會中斷、啟動快速、唯讀 Image 。所以在設計上，需要思考的並不是如何長期的讓容器不間斷地執行，而是思考如何透過 Docker 本身的機制，搭配 DevOps 的流程與適合的 Orchestrator，來讓服務可以不中斷。\n而如果有思考到，如何在容器服務啟動的過程中，手動變更應用程式內容 (或設定) 的方式，都需要重新思考是否要搭配適當的 CI/CD 流程，對現有 Image 的修改。\n這種反模式沒有解決方式，只有變更自己的思考方式，重新調整對 Docker 的做法\n反模式 2: 建立不透明的 Docker Image #通常建立 Docker Image 會思考：\n任何開發者，將 Dockerfile 與其資源 (包含原始碼) 下載後，在任何地方都可以建立出一致的 Image 所有的依賴性，包含開發程式所需的依賴項目，都有統一的取用或存取方式 (包含公司內部的 Repository 使用) 不使用外部依賴的工具，來建置 Image ，避免離開了建置環境，因為工具的依賴性，導致無法建置出相同的 Image 明確定義所使用的軟體版本 (包含套件依賴) 透過修改 Dockerfile 就可以做到修改應用程式的版本 未產出最終的 Image 版本，無法移轉到生產環境執行 如果撰寫 Dockerfile 的時候，無法在另外一個環境重現，表示設計上需要重新調整\n解決方式是仔細了解應用程式建置的步驟，並且仔細的了解 Dockerfile 建置時所發生的狀況，才能設計出更好的 Image 建置步驟。\n所以在建置 Docker 的時候，也是對自己開發的程式語言一種重新理解的方式\n反模式 3: 建立有副作用 (Side-Effect) 的 Dockerfile #因為上述的特性，Dockerfile 應該任何人下載都可以建立出相同的 Image ，所以不管幾次的建立，應該最終產出的 Image 都要是一致的，不管是否是在開發者的環境建置，或者是透過 CI 的服務器來建置。\n但是如果建置階段，在 Dockerfile 中有以下的步驟：\n執行 git commit 或其他 git 操作 清除或修改資料庫資料 呼叫其他外部服務 這樣表示每次建置的步驟，都會有其副作用產生，而且不同次的建置，可能會造成不同的後果。\n不要把 Dockerfile 當作 CI 的步驟，或者是當作無限功能的 Bash 腳本集合。\n解決方案是簡化 Dockerfile，僅包含下列的可能步驟：\n複製原始碼 下載依賴項目 編譯/打包應用程式 處理/縮小/轉換 Local 資源 在 Docker 環境中執行 shell 文件或編輯文件 因為 Dockerfile 本身有 Cache 機制，如果每個圖層之間並沒有檔案的變化，就可以從 Cache 重新使用，但如果其中的ㄧ個步驟有副作用，會破壞 Cache 機制的執行，導致每次建置都會重新產生新的圖層。\n反模式 4: 混淆用於部署以及用於開發的 Image #通常在建置 Image 的時候，會有兩種 Image 的角色：部署用的 Image 跟開發用的 Image (或者是 CI/CD 用的 Image )\n部署 Image 通常具有下面的特性\n最小化/編譯過後的應用程式代碼以及 Runtime 的依賴項目 沒有了! 第二類是開發用的 Image 或是 CI/CD 用的 Image\n原始碼 (沒有處理過的) 編譯器/壓縮器 測試框架/工具 安全掃描/代碼質量掃描/程式碼靜態分析工具 Cloud 集成工具 CI/CD Pipeline 所需要的實用工具 通常部署到生產環境的 Image ，應該要最小化、安全且經過嚴格處理。在 CI/CD 步驟所產生的任何 Image 通常不會部署到其他地方，所以他的要求 (大小或安全性) 就會小一點。\n舉例來說，部署 Image 裡面沒有理由要包含 git 或者是 vim 工具。\n不過這邊，請不要思考本地端開發以及生產環境的部署要結合在一起，目前還是很有難度的。\n總結，請嘗試了解每種 Image 所扮演的角色。一種 Image 應該只扮演一種角色。如果在部署 Image 中包含了測試的工具或套件，就表示這部分是有做錯誤的。可以嘗試使用 Docker 的多階段建置 (Multi-Stage Build)\n反模式 5: 不同環境 (QA, Staging, Production) 建置不同的 Image #使用容器的其中一個優點是他的不可變特性，這表示 Docker Image 在任何地方執行都應該有相同的結果。所以 Docker Image 應該只有建立一次，然後在散佈到不同的各種環境，直到最後的生產環境。\n因為這樣，才可以保證在測試環境中的內容與另外一個環境的內容完全一致。如果針對不同的環境建置不同的 Image ，無法保證其行為是否一致，這樣或許表示會需要導入更多的測試，或者是需要導入額外的 debug 工具，這樣反而會讓不同環境間的差異更難處理。\n但是，不同環境有不同的設定 (像是 Secret 或環境變數)，這是完全正常的，但是請保證其他所有內容都應該完全一樣。\n反模式 6: 在生產服務器上直接產生 Docker Image #Docker registry 是用來存放所建立好的應用程式 Image 的一個目錄服務，可以讓任何環境都可以很容易的重新取得或部署應用服務的 Image ，也是一個中心的服務，用來保存應用服務 Image 的歷史版本。\n通常公司會思考使用兩組 registry，一個對應開發環境，一個對應生產環境。一個 docker image 應該建置完後就被放置到開發的 registry，經過整合測試、安全掃描、以及其他的質量檢查等工作，確認之後就可以升級到正式的 registry 去，並發行到生產環境執行。\nDocker 的部署規劃，應該要包含 registry 本身，可以用來充當資產儲存庫也當作中間的儲存庫。\n如果做法是在生產環境透過 git 直接抓下原始碼來建置，這部分會造成許多問題\n生產環境的機器應該不能訪問 git repository Git 應該是開發人員使用的工具，不應該是營運環境所需要使用的工具 在生產環境上執行的 image 是沒有辦法管理的，你不會知道上面跑了什麼 image git repository 會需要外部的訪問權限，如果公司內部對原始碼有安全性管理的話 這種直接抓下原始碼建置的動作，可能在剛開始建置環境的時候行得通，但如果面臨到較大量的安裝，就會有瓶頸。 docker registry 是有他的優勢，而且現在的工具都還有其他的安全性功能 (像是弱點掃描等)，而且現在 docker registry 也都有相關的 API 可以操作。\n而且，透過 docker registry ，原始碼也可以安全地在公司防火牆內保存。\n反模式 7: 使用 git hash 代替 docker image # 這邊的 git hash 指的是每次 git 的 commit，因為每次 commit.都會產生一組唯一的 hash，用來表示每次的程式碼發佈版本\n參考上面的兩個反模式推論，代表如果使用了 docker，docker registry 應該是唯一用來發行最終產出的最後結果。開發人員以及維運人員，應該使用容器來當作共通的語言，互相之間移交的項目也應該會是容器，而不是 git 的某個 commit。\n較早以前的產品發行，可能會是以一個版本管理的 commit 為一個發行階段，透過 CI/CD 的建置，產出最終的成品 (artifact)，但是如果要重新發行到不同環境，可能會以相同的 commit 為主，重新建置發行一套相同的應用程式，這樣也容易浪費建置資源。\n如果採用了 docker image 為最終發行的成品，表示維運人員並不需要知道 git repo 發生了什麼事情，他們只需要知道 docker image 是否已經準備好了，可以部署到生產環境去，因為是使用相同的 image，所以不需要重新建置。\n維運人員也可以不需要了解測試工具或建置程式的系統等，一般維運的日常操作並不需要。\n反模式 8: 硬編碼 secret 以及設定在 docker image 中 #此模式與反模式 5 相關。通常如果不同環境需要不同的 image，代表裡面包含了不同環境的設定或一些安全金鑰等資訊。\n這樣表示，你需要在建置的過程當中管裡所有環境的設定以及安全金鑰等資訊，這會讓整個建置 (CI/CD) 步驟變得複雜。思考12 因子的建置部分說明。\n應用程式應該是在執行階段才獲取到設定，而不是在建置的時候。這有很多種做法可以做到，像是環境變數，或者是搭配其他的服務 (k8s configmap, etcd, zookeeper, consul 等) 以及 secret 的機制 (k8s secret, confidart, cerberus, valutrepo 等)\n反模式 9: Dockerfile 內做的事情太多 #原文的範例，將 Dockerfile 當作 CI 使用：\n# Run Sonar analysis FROM newtmitch/sonar-scanner AS sonar COPY src src RUN sonar-scanner # Build application FROM node:11 AS build WORKDIR /usr/src/app COPY . . RUN yarn install \\ yarn run lint \\ yarn run build \\ yarn run generate-docs LABEL stage=build # Run unit test FROM build AS unit-tests RUN yarn run unit-tests LABEL stage=unit-tests # Push docs to S3 FROM containerlabs/aws-sdk AS push-docs ARG push-docs=false COPY --from=build docs docs RUN [[ \u0026#34;$push-docs\u0026#34; == true ]] \u0026amp;amp;\u0026amp;amp; aws s3 cp -r docs s3://my-docs-bucket/ # Build final app FROM node:11-slim EXPOSE 8080 WORKDIR /usr/src/app COPY --from=build /usr/src/app/node_modules node_modules COPY --from=build /usr/src/app/dist dist USER node CMD [\u0026#34;node\u0026#34;, \u0026#34;./dist/server/index.js\u0026#34;] 這個範例有結合到前面的幾個反模式：\n假定 Sonar 服務存在 (模式 2) 可能會推送資料到 S3 (模式 3)，具有潛在的副作用 可以做為佈署 image ，也可以作為開發 image (模式 4) Dockerfile 與 CI 的技術是不同的領域，基本上現在的 CI 功能已經可以做到很好，不需要將要在 CI 管道中執行的指令與在 Docker 容器中執行的指令混淆。\n修改此 Dockerfile 的方法是把它拆成不同的 Dockerfile，並在 CI 階段用不同的步驟分開執行，讓每個 Dockerfile 都有自己的單一目標。\n反模式 10: Dockerfile 內做的事情太少 #容器在建置時，會包含處理應用程式的程式庫依賴關係，這也很適合用來隔離每個應用程式之間的程式庫以及框架版本。因為傳統開發人員會在自己的開發環境安裝不同版本的工具，所以常常導致開發環境的混亂。但是 Dockerfile 可以用來準確的描述應用程式所需要的內容 (但是這需要開發人員確實的使用這樣的作法，這種關係才會成立)。\n所以對維運人員來講，可以不需要安裝不同的開發工具，像是 Java、.NET、Python 等，只需要下載不同的 docker image 即可。\n早期有一種建置 docker image 的方式，是將相依的程式庫在建置過程當中從本地端複製進去：\nFROM openjdk:8-jdk-alpine VOLUME /tmp ARG JAR_FILE COPY ${JAR_FILE} app.jar ENTRYPOINT [\u0026#34;java\u0026#34;,\u0026#34;-Djava.security.egd=file:/dev/./urandom\u0026#34;，\u0026#34;-jar\u0026#34;,\u0026#34;/app.jar\u0026#34;] 在這份 Dockerfile 中並沒有說明 app.jar 是怎麼產生的，也沒有說明來源，所以實際建置的時候，是無法得知，這會造成建置的困難，如果還有不同語言，會更麻煩。\nFROM openjdk:8-jdk-alpine COPY pom.xml /tmp/ COPY src /tmp/src/ WORKDIR /tmp/ RUN ./gradlew build COPY /tmp/build/app.war /app.jar ENTRYPOINT [\u0026#34;java\u0026#34;,\u0026#34;-Djava.security.egd = file:/dev/./urandom\u0026#34;,\u0026#34;-jar\u0026#34;,\u0026#34;/app.jar\u0026#34;] 上面的 Dockerfile 就明確的說明了建置步驟，並且，建置時，不需要安裝 Java 環境。但是還是有改善的空間 (Multi-Stage Build)\n","date":"2019年12月10日","permalink":"https://metavige.github.io/2019/12/10/docker-anti-patterns/","section":"文章","summary":"\u003cp\u003e針對網路上面的文章，加上自己的解釋，說明如何避免做到 Docker 的反模式\u003c/p\u003e\n\u003cp\u003e原始文章: \u003ca href=\"https://medium.com/containers-101/docker-anti-patterns-ad2a1fcd5ce1\" target=\"_blank\" rel=\"noreferrer\"\u003eDocker Anti-Pattern\u003c/a\u003e\u003c/p\u003e","title":"Docker 的反模式"},{"content":"簡單筆記 # 標準的基礎程式庫 API 介面定義 可以跨平台 目前 .Net framework 4.6.1 支援 .Net Standard 2.0 (.Net Standard 1.x 僅有 .Net Code 1.x 支援) .Net Standard 版本控制規則 累加：較新的版本會包含較舊版本的 API 不可變：版本提交後，該版本便已凍結 自行根據 PCL (Portable Class Library)，決定設定檔集合 (就是決定要用哪套實作的程式庫，看是要用 .Net framework 或是 .Net Core) 架構圖 # 網路介紹文章 # 2016.09.27 .NET Standard 2.0 是什麼？可以吃嗎？ (黑暗執行緒) 2016.04.10 ASP.NET Core 與 .Net Core, .Net Platform Standard, 以及 Shared Runtime 之間的關係 (微軟開發工具的資訊分享) 2017.02.25 再來多聊一點 .Net Standard (大毛電腦科學筆記) 2017-08-20 .NET Standard 2.0 與 .NET 的未來與發展 (gelis 技術隨筆) ","date":"2017年8月31日","permalink":"https://metavige.github.io/2017/08/31/dotnet-standard-note/","section":"文章","summary":"\u003ch2 id=\"簡單筆記\" class=\"relative group\"\u003e簡單筆記 \u003cspan class=\"absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100\"\u003e\u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700\" style=\"text-decoration-line: none !important;\" href=\"#%e7%b0%a1%e5%96%ae%e7%ad%86%e8%a8%98\" aria-label=\"定位點\"\u003e#\u003c/a\u003e\u003c/span\u003e\u003c/h2\u003e\u003cul\u003e\n\u003cli\u003e標準的基礎程式庫 API 介面定義\u003c/li\u003e\n\u003cli\u003e可以跨平台\u003c/li\u003e\n\u003cli\u003e目前 .Net framework 4.6.1 支援 .Net Standard 2.0 (.Net Standard 1.x 僅有 .Net Code 1.x 支援)\u003c/li\u003e\n\u003cli\u003e.Net Standard 版本控制規則\n\u003cul\u003e\n\u003cli\u003e累加：較新的版本會包含較舊版本的 API\u003c/li\u003e\n\u003cli\u003e不可變：版本提交後，該版本便已凍結\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003cli\u003e自行根據 PCL (Portable Class Library)，決定設定檔集合 (就是決定要用哪套實作的程式庫，看是要用 .Net framework 或是 .Net Core)\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch2 id=\"架構圖\" class=\"relative group\"\u003e架構圖 \u003cspan class=\"absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100\"\u003e\u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700\" style=\"text-decoration-line: none !important;\" href=\"#%e6%9e%b6%e6%a7%8b%e5%9c%96\" aria-label=\"定位點\"\u003e#\u003c/a\u003e\u003c/span\u003e\u003c/h2\u003e\u003cp\u003e\n\n\n\n\n\n\n  \n  \n\u003cfigure\u003e\u003cimg src=\"https://docs.microsoft.com/zh-tw/dotnet/standard/media/components.png\" alt=\"\" class=\"mx-auto my-0 rounded-md\" /\u003e\n\u003c/figure\u003e\n\u003c/p\u003e\n\u003ch2 id=\"網路介紹文章\" class=\"relative group\"\u003e網路介紹文章 \u003cspan class=\"absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100\"\u003e\u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700\" style=\"text-decoration-line: none !important;\" href=\"#%e7%b6%b2%e8%b7%af%e4%bb%8b%e7%b4%b9%e6%96%87%e7%ab%a0\" aria-label=\"定位點\"\u003e#\u003c/a\u003e\u003c/span\u003e\u003c/h2\u003e\u003cul\u003e\n\u003cli\u003e\u003ca href=\"http://blog.darkthread.net/post-2016-09-27-net-standard-2-0.aspx\" target=\"_blank\" rel=\"noreferrer\"\u003e2016.09.27 .NET Standard 2.0 是什麼？可以吃嗎？ (黑暗執行緒)\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://dotblogs.com.tw/aspnetshare/2016/04/10/20160409-netcore-netstandard\" target=\"_blank\" rel=\"noreferrer\"\u003e2016.04.10 ASP.NET Core 與 .Net Core, .Net Platform Standard, 以及 Shared Runtime 之間的關係 (微軟開發工具的資訊分享)\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"http://bruceyclee.tw/2017/02/25/netstandard/\" target=\"_blank\" rel=\"noreferrer\"\u003e2017.02.25 再來多聊一點 .Net Standard (大毛電腦科學筆記)\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://dotblogs.com.tw/gelis/2017/08/20/143437\" target=\"_blank\" rel=\"noreferrer\"\u003e2017-08-20 .NET Standard 2.0 與 .NET 的未來與發展 (gelis 技術隨筆)\u003c/a\u003e\u003c/li\u003e\n\u003c/ul\u003e","title":".Net Standard 筆記"},{"content":"其實我對 Makefile 算是一知半解，趁此機會學習一下\n我最近寫的一個 Makefile\nIMAGE_NAME=apt-cacher-ng PUSH_NAME=registry.co/nebula/${IMAGE_NAME}:v20170328 build: docker build -t ${IMAGE_NAME} . push: docker tag ${IMAGE_NAME} ${PUSH_NAME} \u0026amp;\u0026amp; docker push ${PUSH_NAME} \u0026amp;\u0026amp; docker rmi ${PUSH_NAME} 老實說，我也不知道什麼才是對的，只是這樣我可以跑，好像就是對的勒\n真的看了 Makefile 的教學之後才發現自己錯很多地方\n我沒寫 .PHONY 我不知道還有相依性這種東西？？ 原來裡面可以把檔案當作相依性的檢查，然後用來執行要執行的 task。 以往都是笨笨的自己下一堆 make target 的動作，還想說 Makefile 真難用。原來可以一個相依另外一個，只需要下一次 make 就好 $\u0026lt; 等於相依的資料，$@ 表示 target 本身，% 則是樣式規則\u0026hellip;..學起來 所以，算了，參考別人的東西 Makefile 之後自己慢慢學好了\nMakefile 範例教學 ","date":"2017年3月28日","permalink":"https://metavige.github.io/2017/03/28/makefile-for-dockerbuild/","section":"文章","summary":"\u003cp\u003e其實我對 Makefile 算是一知半解，趁此機會學習一下\u003c/p\u003e","title":"Makefile for Docker Build"},{"content":"","date":null,"permalink":"https://metavige.github.io/tags/golang/","section":"Tags","summary":"","title":"Golang"},{"content":"學習其實很無聊～沒有特別要做什麼的話，看著看著就想睡了～ 所以後來決定，之後就把程式碼做好放到 github 去好了\n一次想一個想要做的小功能，這樣或許比較有趣\n","date":"2017年3月9日","permalink":"https://metavige.github.io/2017/03/09/golang-day3/","section":"文章","summary":"\u003cp\u003e學習其實很無聊～沒有特別要做什麼的話，看著看著就想睡了～\n所以後來決定，之後就把程式碼做好放到 \u003ca href=\"https://github.com/metavige/golang.learn\" target=\"_blank\" rel=\"noreferrer\"\u003egithub\u003c/a\u003e 去好了\u003cbr\u003e\n一次想一個想要做的小功能，這樣或許比較有趣\u003c/p\u003e","title":"Golang Day 3"},{"content":"Golang 語法注意 # 格式很重要，不管是空行或者是 TAB、大括號等位置，很重要，會編譯不過。不過可以用 gofmt 工具幫你自動排版程式。 所有的程式需要用 package 整理，有點像是 java package/module 的觀念 主程式一定是 package main, 而且一定會呼叫 func main() 引用不必要的 packages 會錯誤 沒用到的變數也會錯誤 不需要分號結尾 條件式不需要用 () 包起來 (這我就有點不習慣了～因為 Java, javascript, C# 都要) golang 應該不算是 OOP, 所以要習慣沒有類別 (Class) 封裝這件事情 Reference # GO 聖經 - gitbook ","date":"2017年3月8日","permalink":"https://metavige.github.io/2017/03/08/golang-day2/","section":"文章","summary":"\u003ch2 id=\"golang-語法注意\" class=\"relative group\"\u003eGolang 語法注意 \u003cspan class=\"absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100\"\u003e\u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700\" style=\"text-decoration-line: none !important;\" href=\"#golang-%e8%aa%9e%e6%b3%95%e6%b3%a8%e6%84%8f\" aria-label=\"定位點\"\u003e#\u003c/a\u003e\u003c/span\u003e\u003c/h2\u003e\u003cul\u003e\n\u003cli\u003e格式很重要，不管是空行或者是 TAB、大括號等位置，很重要，會編譯不過。不過可以用 gofmt 工具幫你自動排版程式。\u003c/li\u003e\n\u003cli\u003e所有的程式需要用 package 整理，有點像是 java package/module 的觀念\u003c/li\u003e\n\u003cli\u003e主程式一定是 \u003ccode\u003epackage main\u003c/code\u003e, 而且一定會呼叫 \u003ccode\u003efunc main()\u003c/code\u003e\u003c/li\u003e\n\u003cli\u003e引用不必要的 packages 會錯誤\u003c/li\u003e\n\u003cli\u003e沒用到的變數也會錯誤\u003c/li\u003e\n\u003cli\u003e不需要分號結尾\u003c/li\u003e\n\u003cli\u003e條件式不需要用 () 包起來 (這我就有點不習慣了～因為 Java, javascript, C# 都要)\u003c/li\u003e\n\u003cli\u003egolang 應該不算是 OOP, 所以要習慣沒有類別 (Class) 封裝這件事情\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch2 id=\"reference\" class=\"relative group\"\u003eReference \u003cspan class=\"absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100\"\u003e\u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700\" style=\"text-decoration-line: none !important;\" href=\"#reference\" aria-label=\"定位點\"\u003e#\u003c/a\u003e\u003c/span\u003e\u003c/h2\u003e\u003cul\u003e\n\u003cli\u003e\u003ca href=\"https://wizardforcel.gitbooks.io\" target=\"_blank\" rel=\"noreferrer\"\u003eGO 聖經 - gitbook\u003c/a\u003e\u003c/li\u003e\n\u003c/ul\u003e","title":"Golang Day 2"},{"content":"安裝 #brew install golang 環境設定 # GOROOT: GO 安裝的地方，用 brew 安裝的，可以設定 /usr/local/opt/go/libexec 就可以 GOPATH: GO 執行的時候要的路徑，看專案在哪邊就設定在哪邊 GOPATH #一般是指向專案位置，裡面還會切分三個目錄\nsrc: 一般程式碼放的地方，可以包含網址．比如說 github.com/golang/golint 這樣的方式 pkg: 編譯後的檔案 bin: binary file Empty Go # 1 2 3 4 5 package main func main() { } 注意空行以及大括弧的位置，golang 很注意程式碼格式！\nHelloWorld # 1 2 3 4 5 6 7 package main import \u0026#34;fmt\u0026#34; func main() { fmt.Println(\u0026#34;Hello, World\u0026#34;) } ","date":"2017年3月6日","permalink":"https://metavige.github.io/2017/03/06/golang-day1/","section":"文章","summary":"\u003ch2 id=\"安裝\" class=\"relative group\"\u003e安裝 \u003cspan class=\"absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100\"\u003e\u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700\" style=\"text-decoration-line: none !important;\" href=\"#%e5%ae%89%e8%a3%9d\" aria-label=\"定位點\"\u003e#\u003c/a\u003e\u003c/span\u003e\u003c/h2\u003e\u003cpre tabindex=\"0\"\u003e\u003ccode\u003ebrew install golang\n\u003c/code\u003e\u003c/pre\u003e\u003ch2 id=\"環境設定\" class=\"relative group\"\u003e環境設定 \u003cspan class=\"absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100\"\u003e\u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700\" style=\"text-decoration-line: none !important;\" href=\"#%e7%92%b0%e5%a2%83%e8%a8%ad%e5%ae%9a\" aria-label=\"定位點\"\u003e#\u003c/a\u003e\u003c/span\u003e\u003c/h2\u003e\u003cul\u003e\n\u003cli\u003e\u003ccode\u003eGOROOT\u003c/code\u003e: GO 安裝的地方，用 \u003ccode\u003ebrew\u003c/code\u003e 安裝的，可以設定 \u003ccode\u003e/usr/local/opt/go/libexec\u003c/code\u003e 就可以\u003c/li\u003e\n\u003cli\u003e\u003ccode\u003eGOPATH\u003c/code\u003e: GO 執行的時候要的路徑，看專案在哪邊就設定在哪邊\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch2 id=\"gopath\" class=\"relative group\"\u003eGOPATH \u003cspan class=\"absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100\"\u003e\u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700\" style=\"text-decoration-line: none !important;\" href=\"#gopath\" aria-label=\"定位點\"\u003e#\u003c/a\u003e\u003c/span\u003e\u003c/h2\u003e\u003cp\u003e一般是指向專案位置，裡面還會切分三個目錄\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003esrc: 一般程式碼放的地方，可以包含網址．比如說 github.com/golang/golint 這樣的方式\u003c/li\u003e\n\u003cli\u003epkg: 編譯後的檔案\u003c/li\u003e\n\u003cli\u003ebin: binary file\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch2 id=\"empty-go\" class=\"relative group\"\u003eEmpty Go \u003cspan class=\"absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100\"\u003e\u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700\" style=\"text-decoration-line: none !important;\" href=\"#empty-go\" aria-label=\"定位點\"\u003e#\u003c/a\u003e\u003c/span\u003e\u003c/h2\u003e\u003cdiv class=\"highlight\"\u003e\u003cdiv style=\"color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\n\u003ctable style=\"border-spacing:0;padding:0;margin:0;border:0;\"\u003e\u003ctr\u003e\u003ctd style=\"vertical-align:top;padding:0;margin:0;border:0;\"\u003e\n\u003cpre tabindex=\"0\" style=\"color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\u003ccode\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679\"\u003e1\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679\"\u003e2\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679\"\u003e3\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679\"\u003e4\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679\"\u003e5\n\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\n\u003ctd style=\"vertical-align:top;padding:0;margin:0;border:0;;width:100%\"\u003e\n\u003cpre tabindex=\"0\" style=\"color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\u003ccode class=\"language-golang\" data-lang=\"golang\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#ff7b72\"\u003epackage\u003c/span\u003e\u003cspan style=\"color:#6e7681\"\u003e \u003c/span\u003emain\u003cspan style=\"color:#6e7681\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#6e7681\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#ff7b72\"\u003efunc\u003c/span\u003e\u003cspan style=\"color:#6e7681\"\u003e \u003c/span\u003e\u003cspan style=\"color:#d2a8ff;font-weight:bold\"\u003emain\u003c/span\u003e()\u003cspan style=\"color:#6e7681\"\u003e \u003c/span\u003e{\u003cspan style=\"color:#6e7681\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#6e7681\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\u003cspan style=\"color:#6e7681\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\u003c/tr\u003e\u003c/table\u003e\n\u003c/div\u003e\n\u003c/div\u003e\u003cp\u003e注意空行以及大括弧的位置，golang 很注意\u003cstrong\u003e程式碼格式\u003c/strong\u003e！\u003c/p\u003e","title":"Golang Day 1"},{"content":"真的使用之後，越來越覺得 Actor Model 是個好物，因為他本來就是拿來做平行運算用的。\n建議可以先從以下的幾篇文章以及 Github 的範例開始\nAkka.Net Application Architecture and Design Building Networked .NET Applications with Akka.Remote .NET Distributed Systems Architecture and Design with Akka.Cluster 好處 #我剛開始做的時候，其實是想到 Sequence Diagram。因為 Actor Model 的重點其實就是在訊息的傳遞與處理，只是在整體架構上有一個 ActorSystem 幫忙做處理\n根據以下的圖片\n一個 Actor 在建立之後，AkkaDotNet 會幫你建立整個 Actor 的基本行為。 包含一個 Mailbox\u0002、預設的狀態等，而且也預設了每個 Actor 都有可以有 Children Actors 所以，當你傳遞一個訊息給 Actor 的時候，其實不是直接呼叫 Actor 中處理訊息的方法，而是先進入 Mailbox，類似郵件的方式排隊處理。\n當處理有問題的時候，你是可以在 Handle 方法中，直接拋出錯誤，讓 Actor 失敗。如此一來就會觸發上層 Actor 的 SupervisorStrategy。預設的 Strategy 可以參考 Fault Tolerance 文件\n以下是本機範例\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 // Create a new actor system (a container for your actors) var system = ActorSystem.Create(\u0026#34;MySystem\u0026#34;); // Create your actor and get a reference to it. // This will be an \u0026#34;ActorRef\u0026#34;, which is not a // reference to the actual actor instance // but rather a client or proxy to it. var greeter = system.ActorOf\u0026lt;GreetingActor\u0026gt;(\u0026#34;greeter\u0026#34;); // Send a message to the actor greeter.Tell(new Greet(\u0026#34;World\u0026#34;)); // This prevents the app from exiting // before the async work is done Console.ReadLine(); Akka.Remote #你寫好了 Actor，也在同一個系統中跑完所有的流程，如果你需要做分散 Loading 的動作，不需要你把程式碼呼叫的部分再大改。\n你只需要改變取得 Actor 的路徑。\n是的，路徑！\n預設取得 Actor 的路徑是用 actor name 來取得，取回來之後基本上就是一個 IActorRef 類別的物件(可以參考上面的範例)\n當你需要取得遠端的 ActorRef 時，你只需要修改設定(當然要加上 Akka.Remote 套件)\nakka { actor { provider = \u0026#34;Akka.Remote.RemoteActorRefProvider, Akka.Remote\u0026#34; } remote { helios.tcp { port = 8080 hostname = localhost } } } 然後修改你取得 Actor 的『路徑』就可以。就好像訪問 HTTP 一樣的路徑～\n像上面的本機範例，取得 Actor 的路徑改一下就可以\n1 var greeter = system.ActorOf\u0026lt;GreetingActor\u0026gt;(\u0026#34;akka.tcp://MySystem@localhost:8080/user/greeter\u0026#34;); Akka.Cluster #當你的系統越來越大，需要擴充的時候，可以參考使用 Akka.Cluster 一樣不太需要調整太多程式，只需要加上 Akka.Cluster 套件使用以及設定調整 主體處理邏輯應該都不需要調整太多(除非你寫的太爛～)\nAkka.Cluster 與 Akka.Remote 不太一樣的是，多出了 High Availability 的管理。 主要是針對每個 Actor System Node 間的互相認知通訊，以及其他管理節點的功能 但是因為我還沒用到，詳細的東西我不太清楚\n參考 # getakkanet 官方文件 - 必看！ ","date":"2017年2月13日","permalink":"https://metavige.github.io/2017/02/13/akkadotnet-good/","section":"文章","summary":"\u003cp\u003e真的使用之後，越來越覺得 Actor Model 是個好物，因為他本來就是拿來做平行運算用的。\u003c/p\u003e\n\u003cp\u003e建議可以先從以下的幾篇文章以及 Github 的範例開始\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e\u003ca href=\"https://petabridge.com/training/akka-design-patterns/\" target=\"_blank\" rel=\"noreferrer\"\u003eAkka.Net Application Architecture and Design\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://petabridge.com/training/akka-remoting/\" target=\"_blank\" rel=\"noreferrer\"\u003eBuilding Networked .NET Applications with Akka.Remote\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://petabridge.com/training/akka-clustering/\" target=\"_blank\" rel=\"noreferrer\"\u003e.NET Distributed Systems Architecture and Design with Akka.Cluster\u003c/a\u003e\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch2 id=\"好處\" class=\"relative group\"\u003e好處 \u003cspan class=\"absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100\"\u003e\u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700\" style=\"text-decoration-line: none !important;\" href=\"#%e5%a5%bd%e8%99%95\" aria-label=\"定位點\"\u003e#\u003c/a\u003e\u003c/span\u003e\u003c/h2\u003e\u003cp\u003e我剛開始做的時候，其實是想到 Sequence Diagram。因為 Actor Model 的重點其實就是在訊息的傳遞與處理，只是在整體架構上有一個 ActorSystem 幫忙做處理\u003c/p\u003e\n\u003cp\u003e根據以下的圖片\u003c/p\u003e\n\u003cp\u003e\n\n\n\n\n\n\n  \n  \n\u003cfigure\u003e\u003cimg src=\"http://getakka.net/docs/images/actor.png\" alt=\"\" class=\"mx-auto my-0 rounded-md\" /\u003e\n\u003c/figure\u003e\n\u003c/p\u003e\n\u003cp\u003e一個 Actor 在建立之後，\u003ca href=\"http://getakka.net\" target=\"_blank\" rel=\"noreferrer\"\u003eAkkaDotNet\u003c/a\u003e 會幫你建立整個 Actor 的基本行為。\n包含一個 Mailbox\u0002、預設的狀態等，而且也預設了每個 Actor 都有可以有 Children Actors\n所以，當你傳遞一個訊息給 Actor 的時候，其實不是直接呼叫 Actor 中處理訊息的方法，而是先進入 Mailbox，類似郵件的方式排隊處理。\u003c/p\u003e\n\u003cp\u003e當處理有問題的時候，你是可以在 Handle 方法中，直接拋出錯誤，讓 Actor 失敗。如此一來就會觸發上層 Actor 的 SupervisorStrategy。預設的 Strategy 可以參考 \u003ca href=\"http://getakka.net/docs/Fault%20tolerance\" target=\"_blank\" rel=\"noreferrer\"\u003eFault Tolerance\u003c/a\u003e 文件\u003c/p\u003e","title":"Akka.Net 不錯用"},{"content":"","date":null,"permalink":"https://metavige.github.io/tags/elasticsearch/","section":"Tags","summary":"","title":"Elasticsearch"},{"content":"Curator 一個用來管理 ElasticSearch indices/snapshots 的工具\n之前有用這個來刪除 indices，但是因為之前用的指令比較簡單，都是統一刪除 21 天前的資料\n現在裡面資料比較複雜，所以需要做一些變化\n原本以為是不是下兩次指令就好，現在發現可以一次指定多個設定一次做～\n參考文件 - Curator Guide\nCONFIG #連接 ElasticSearch 所需要的設定檔案，預設是應該放在 ~/.curator/curator.yml\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 client: hosts: - es port: 9200 url_prefix: use_ssl: False certificate: client_cert: client_key: ssl_no_validate: False http_auth: timeout: 30 master_only: False logging: loglevel: INFO logfile: logformat: default blacklist: [\u0026#39;elasticsearch\u0026#39;, \u0026#39;urllib3\u0026#39;] Action #我這邊因為會有兩組 indices\n其中我想 topbeat 資料比較多，保留七天。\npacketbeat 資料比較少，可以保留久一點，所以我就分兩個 action 來設定\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 actions: 1: action: delete_indices description: remove topbeat indices options: ignore_empty_list: True timeout_override: continue_if_exception: False disable_action: filters: - filtertype: pattern kind: prefix value: topbeat- exclude: - filtertype: age source: name direction: older timestring: \u0026#39;%Y.%m.%d\u0026#39; unit: days unit_count: 7 exclude: 2: action: delete_indices description: remove packetbeat indices options: ignore_empty_list: True timeout_override: continue_if_exception: False disable_action: filters: - filtertype: pattern kind: prefix value: packetbeat- exclude: - filtertype: age source: name direction: older timestring: \u0026#39;%Y.%m.%d\u0026#39; unit: days unit_count: 35 exclude: 執行 #接下來只需要執行下面指令就可以了～\n1 \u0026gt; curator action.yml Docker #我把專案放在 Github 上面: elasticsearch-curator\n","date":"2016年11月18日","permalink":"https://metavige.github.io/2016/11/18/es_curator/","section":"文章","summary":"\u003cp\u003e\u003ca href=\"https://github.com/elastic/curator\" target=\"_blank\" rel=\"noreferrer\"\u003eCurator\u003c/a\u003e 一個用來管理 ElasticSearch indices/snapshots 的工具\u003c/p\u003e","title":"ElasticSearch Curator"},{"content":" Live Container Migration: OpenStack Summit Barcelona 2016 from Phil Estes 簡單列一下我聽到的重點\nLinux Kernel 3.11 才支援 透過一個叫做 CRIU 的技術達成 參考網站: CRIU Docker 1.13 experimental build 支援 有兩個層面 In-Memory Change In-Memory + Filesystem Change 透過 checkpoint 的方式，將變更記錄下來，我猜測是以版本控管的方式 push 相關的 metadata 在新的機器上，可以透過 pull 方式 merge 新的 metadata 變更，然後繼續往下執行 如果這個技術真的可以達到，那其實真的要把 VM 幹掉很快～\n","date":"2016年11月11日","permalink":"https://metavige.github.io/2016/11/11/Migration/","section":"文章","summary":"\u003ciframe src=\"//www.slideshare.net/slideshow/embed_code/key/GvdO7izO4dp714\" width=\"595\" height=\"485\" frameborder=\"0\" marginwidth=\"0\" marginheight=\"0\" scrolling=\"no\" style=\"border:1px solid #CCC; border-width:1px; margin-bottom:5px; max-width: 100%;\" allowfullscreen\u003e \u003c/iframe\u003e \u003cdiv style=\"margin-bottom:5px\"\u003e \u003cstrong\u003e \u003ca href=\"//www.slideshare.net/PhilEstes/live-container-migration-openstack-summit-barcelona-2016\" title=\"Live Container Migration: OpenStack Summit Barcelona 2016\" target=\"_blank\"\u003eLive Container Migration: OpenStack Summit Barcelona 2016\u003c/a\u003e \u003c/strong\u003e from \u003cstrong\u003e\u003ca target=\"_blank\" href=\"//www.slideshare.net/PhilEstes\"\u003ePhil Estes\u003c/a\u003e\u003c/strong\u003e \u003c/div\u003e","title":"Docker Live Migration"},{"content":"","date":null,"permalink":"https://metavige.github.io/tags/ceph/","section":"Tags","summary":"","title":"Ceph"},{"content":"簡單記錄一下 Ceph RBD 使用的紀錄 \u0026mdash;\u0026gt;\n先安裝 ceph-common #apt-get install ceph-common 目前在 ubuntu trusty 內安裝的版本是\n$ rbd -v ceph version 0.80.11 (8424145d49264624a3b0a204aedb127835161070) 建立 Image 動作 (假設 image name 是 test): #sudo rbd create --size \u0026lt;MB\u0026gt; test Mapping 到 dev #sudo rbd map test 這邊預設會把 image mapping 到 /dev/rbd/rbd 下面，名稱就是 image name\n所以會放在 /dev/rbd/rbd/test\n格式化: #sudo mkfs.xfs -f /dev/rbd/rbd/test 我用 xfs 的原因是因為未來如果要擴充的話，比較方便 可以用 xfs_growfs 指令來擴充\n掛載目錄 #$ sudo mkdir -p /mnt/test $ sudo mount -o nouuid -t xfs /dev/rbd/rbd/test /mnt/test 用 -o nouuid 的參數主要是，如果你建立過一次，沒下這個參數 OS 會把 uuid 建立起來，這樣沒辦法反覆 map/unmap (如果你有這個需求的話，像我測試就會)\n卸載 #$ sudo unmount /mnt/test $ sudo rbd unmap /dev/rbd/rbd/test ","date":"2016年9月5日","permalink":"https://metavige.github.io/2016/09/05/ceph-rbd/","section":"文章","summary":"\u003cp\u003e簡單記錄一下 Ceph RBD 使用的紀錄 \u0026mdash;\u0026gt;\u003c/p\u003e\n\u003c!-- more --\u003e\n\u003ch4 id=\"先安裝-ceph-common\" class=\"relative group\"\u003e先安裝 ceph-common \u003cspan class=\"absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100\"\u003e\u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700\" style=\"text-decoration-line: none !important;\" href=\"#%e5%85%88%e5%ae%89%e8%a3%9d-ceph-common\" aria-label=\"定位點\"\u003e#\u003c/a\u003e\u003c/span\u003e\u003c/h4\u003e\u003cpre tabindex=\"0\"\u003e\u003ccode\u003eapt-get install ceph-common\n\u003c/code\u003e\u003c/pre\u003e\u003cp\u003e目前在 ubuntu trusty 內安裝的版本是\u003c/p\u003e\n\u003cpre tabindex=\"0\"\u003e\u003ccode\u003e$ rbd -v\nceph version 0.80.11 (8424145d49264624a3b0a204aedb127835161070)\n\u003c/code\u003e\u003c/pre\u003e\u003ch4 id=\"建立-image-動作-假設-image-name-是-test\" class=\"relative group\"\u003e建立 Image 動作 (假設 image name 是 test): \u003cspan class=\"absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100\"\u003e\u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700\" style=\"text-decoration-line: none !important;\" href=\"#%e5%bb%ba%e7%ab%8b-image-%e5%8b%95%e4%bd%9c-%e5%81%87%e8%a8%ad-image-name-%e6%98%af-test\" aria-label=\"定位點\"\u003e#\u003c/a\u003e\u003c/span\u003e\u003c/h4\u003e\u003cpre tabindex=\"0\"\u003e\u003ccode\u003esudo rbd create --size \u0026lt;MB\u0026gt; test\n\u003c/code\u003e\u003c/pre\u003e\u003ch5 id=\"mapping-到-dev\" class=\"relative group\"\u003eMapping 到 dev \u003cspan class=\"absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100\"\u003e\u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700\" style=\"text-decoration-line: none !important;\" href=\"#mapping-%e5%88%b0-dev\" aria-label=\"定位點\"\u003e#\u003c/a\u003e\u003c/span\u003e\u003c/h5\u003e\u003cpre tabindex=\"0\"\u003e\u003ccode\u003esudo rbd map test\n\u003c/code\u003e\u003c/pre\u003e\u003cblockquote\u003e\n\u003cp\u003e這邊預設會把 image mapping 到 /dev/rbd/rbd 下面，名稱就是 image name\u003cbr\u003e\n所以會放在 /dev/rbd/rbd/test\u003c/p\u003e","title":"Ceph RBD 使用"},{"content":"","date":null,"permalink":"https://metavige.github.io/tags/linux/","section":"Tags","summary":"","title":"Linux"},{"content":"","date":null,"permalink":"https://metavige.github.io/tags/babel/","section":"Tags","summary":"","title":"Babel"},{"content":"","date":null,"permalink":"https://metavige.github.io/tags/es2015/","section":"Tags","summary":"","title":"Es2015"},{"content":"","date":null,"permalink":"https://metavige.github.io/tags/nodejs/","section":"Tags","summary":"","title":"Nodejs"},{"content":"Visual Studio Code 是個不錯的編輯器，不過設定上，還是有很多需要手動的動作\n特此記錄\nlaunch.json 範例:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 { \u0026#34;version\u0026#34;: \u0026#34;0.2.0\u0026#34;, \u0026#34;configurations\u0026#34;: [ { \u0026#34;name\u0026#34;: \u0026#34;Run app\u0026#34;, \u0026#34;type\u0026#34;: \u0026#34;node\u0026#34;, \u0026#34;request\u0026#34;: \u0026#34;launch\u0026#34;, \u0026#34;runtimeExecutable\u0026#34;: \u0026#34;${workspaceRoot}/node_modules/babel-cli/bin/babel-node.js\u0026#34;, \u0026#34;program\u0026#34;: \u0026#34;${workspaceRoot}/src/index.js\u0026#34;, \u0026#34;stopOnEntry\u0026#34;: false, \u0026#34;args\u0026#34;: [ \u0026#34;--presets es2015,stage-0\u0026#34; ], \u0026#34;env\u0026#34;: { \u0026#34;NODE_ENV\u0026#34;: \u0026#34;development\u0026#34; } }, { \u0026#34;name\u0026#34;: \u0026#34;Run mocha\u0026#34;, \u0026#34;type\u0026#34;: \u0026#34;node\u0026#34;, \u0026#34;program\u0026#34;: \u0026#34;${workspaceRoot}/node_modules/mocha/bin/mocha\u0026#34;, \u0026#34;stopOnEntry\u0026#34;: false, \u0026#34;args\u0026#34;: [ \u0026#34;--compilers\u0026#34;, \u0026#34;js:babel-register\u0026#34;, \u0026#34;test/**/*.js\u0026#34; ], \u0026#34;cwd\u0026#34;: \u0026#34;${workspaceRoot}\u0026#34;, \u0026#34;runtimeExecutable\u0026#34;: null, \u0026#34;env\u0026#34;: { \u0026#34;NODE_ENV\u0026#34;: \u0026#34;production\u0026#34; } } ] } ","date":"2016年8月10日","permalink":"https://metavige.github.io/2016/08/10/vscode-es2015-with-mocha/","section":"文章","summary":"\u003cp\u003eVisual Studio Code 是個不錯的編輯器，不過設定上，還是有很多需要手動的動作\u003cbr\u003e\n特此記錄\u003c/p\u003e","title":"Visual Studio Code (launch with babel, mocha)"},{"content":"","date":null,"permalink":"https://metavige.github.io/tags/javascript/","section":"Tags","summary":"","title":"Javascript"},{"content":"","date":null,"permalink":"https://metavige.github.io/tags/reactjs/","section":"Tags","summary":"","title":"Reactjs"},{"content":"","date":null,"permalink":"https://metavige.github.io/tags/redux/","section":"Tags","summary":"","title":"Redux"},{"content":" Redux Study Day1 - Start Redux Study Day2 - Action Redux Study Day3 - Reducer Redux Study Day4 - Store 繼前面的 action/reducer 之後，接下來就是要把這兩者結合起來了～\nStore #當你寫完 action/reducer 之後，大概還不太能理解，這兩個東西要怎麼串在一起？\nStore 就是把 action/reducer 串在一起的物件\n所以先來說說 Store 的責任：\n掌管應用程式狀態； 允許藉由 getState() 獲取 state； 允許藉由 dispatch(action) 來更新 state； 藉由 subscribe(listener) 註冊 listeners; 藉由 unsubscribe(listener) 回傳的 function 處理撤銷 listeners。 根據上面的 Flux 架構圖，action 需要透過 dispatcher 來變更 Store 的狀態。\n所以，我們在寫完 reducer 之後，可以透過 Redux 提供的 createStore 方法來把 reducer 放入 Store 中\n1 2 3 import { combineReducers, createStore } from \u0026#39;redux\u0026#39; let reducer = combineReducers({ visibilityFilter, todos }) let store = createStore(reducer) Dispatch Actions #當 Store 建立之後，你只要透過 store.dispatch(action) 的方式，就可以透過 action 來變更 State 了。\n1 2 3 4 5 6 7 8 import { addTodo, completeTodo, setVisibilityFilter, VisibilityFilters } from \u0026#39;./actions\u0026#39; store.dispatch(addTodo(\u0026#39;Learn about actions\u0026#39;)) store.dispatch(addTodo(\u0026#39;Learn about reducers\u0026#39;)) store.dispatch(addTodo(\u0026#39;Learn about store\u0026#39;)) store.dispatch(completeTodo(0)) store.dispatch(completeTodo(1)) store.dispatch(setVisibilityFilter(VisibilityFilters.SHOW_COMPLETED)) 心得 #因為 action/reducer 以及 Store 本身，都是單純的 javascript functions，所以在 UI 還沒有建立的時候，你其實可以透過 Store 的 subscribe/unsubscribe 方法，來觀察 State 物件的變化\n藉此來得知，action/reducer 的邏輯是否正確\n修改以下上面的程式碼：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 import { addTodo, completeTodo, setVisibilityFilter, VisibilityFilters } from \u0026#39;./actions\u0026#39; // 訂閱每次的變更 let unsubscribe = store.subscribe(() =\u0026gt; { console.log(store.getState()); }); store.dispatch(addTodo(\u0026#39;Learn about actions\u0026#39;)) store.dispatch(addTodo(\u0026#39;Learn about reducers\u0026#39;)) store.dispatch(addTodo(\u0026#39;Learn about store\u0026#39;)) store.dispatch(completeTodo(0)) store.dispatch(completeTodo(1)) store.dispatch(setVisibilityFilter(VisibilityFilters.SHOW_COMPLETED)) // 取消訂閱 unsubscribe(); 你就可以從 console 看到資料的變化 (圖片來源) ：\n","date":"2016年6月22日","permalink":"https://metavige.github.io/2016/06/22/redux-study-day4/","section":"文章","summary":"\u003cul\u003e\n\u003cli\u003e\u003ca href=\"/2015/10/14/redux-study-day1/\"\u003eRedux Study Day1 - Start\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"/2015/10/19/redux-study-day2/\"\u003eRedux Study Day2 - Action\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"/2016/06/21/redux-study-day-3/\"\u003eRedux Study Day3 - Reducer\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"/2016/06/22/redux-study-day4/\"\u003eRedux Study Day4 - Store\u003c/a\u003e\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003e繼前面的 action/reducer 之後，接下來就是要把這兩者結合起來了～\u003c/p\u003e","title":"Redux Study Day4 - Store"},{"content":" Redux Study Day1 - Start Redux Study Day2 - Action Redux Study Day3 - Reducer Redux Study Day4 - Store 隔了半年多，重新真的找一個專案來實作 Redux，不得不說，半年的變化\n跟我當初看 Redux 差異還是蠻大的。當然，觀念什麼的是差不多，只是可能很多程式碼會有所調整\n這次重新再開始，之前兩天的東西，找時間修改。今天就從 Redux 核心的 Reducer 開始好了～\n我一剛開始看到 Reducer，其實並沒有想到 Map/Reduce，不過慢慢的真的深入瞭解以及自己去寫了程式之後，感覺這個 Reducer 的確有點 Map/Reduce 的味道\n在談到 Reducer 之前，大多數的文件會先說：『你要先確定 State 的 “形狀”』，英文就是 \u0026ldquo;State\u0026rsquo;s Shape\u0026rdquo;\n為什麼要這樣說呢？我後來自己實作之後才知道，這跟 Redux 的三大原則有關係：\nRedux 的三大原則 # 唯一真相來源: 整個應用程式的 state，被儲存在一個樹狀物件放在唯一的 store 裡面。 State 是唯讀的: 改變 state 的唯一的方式是發出一個 action，也就是一個描述發生什麼事的物件。 變更被寫成 pure functions: 要指定 state tree 如何藉由 actions 來轉變，你必須撰寫 pure reducers。 根據這三個原則，我的理解是，當你需要有任何變更 State 的動作時，都需要發送一個 action。\n而發送 action 之後，需要透過 Reducer 來變更 State。\n但是這邊所謂的『變更』，並不是直接去修改 State 物件 (因為第二個原則)，而是透過一個 pure functions 來結合舊的 State 以及 action 所傳入的 payload 資料，來產生一個新的 State。\nPure functions - Reducer #為什麼要強調所謂的 Pure functions？\n這是為了所謂的測試性。(如果你聽得懂簡單的英文的話，我建議你看一次這段影片)\n當你呼叫這個 pure functions 的時候，每次呼叫都可以得到一定的結果，不會因為上下文而改變，這樣的話，你就可以撰寫單元測試的程式來測試它\n非 pure functions # 1 2 3 4 var a = 0; function add (i) { return i++; } 像是上面這個方法，就不是 pure functions，因為它有改變到 i 這個資料\n你如果再呼叫一次，結果會與上次的結果不同\nconsole.log(add(a)) // 0; console.log(add(a)) // 1; pure functions # 1 2 3 4 var a = 0; function add (i) { return i+1; } 但如果是上面的寫法，每次呼叫結果都會是 1，這就表示你寫的程式是可以被預測的！\n當然之後還可以延伸有其他做法\u0026hellip;\u0026hellip; 這屬於進階用法，以後再討論～\nState 的 Mapping #基本上透過 Redux 設定進去的所有 reducer functions，在每次 action 被 dispath 的時候，會呼叫註冊的所有 Reducer，你只需要透過判斷 action.type 來決定你是否要處理這個 action\n1 2 3 4 5 6 7 8 function (state, action) { switch (action.type) { case \u0026#39;ACTION\u0026#39;: return { ... }; // 回傳新狀態物件 default: return state; } } 如果你並不要處理這個 action，你必須要回傳原本的 state 物件，以保持原先的狀態不改變。\n當你的 state 物件的屬性如下：\n{ visibilityFilter: \u0026#39;\u0026#39;, todos: [] } 你可以寫一個 Reducer 來處理這全部的狀態，但是，也可以透過 combineReducer 的方法，寫兩個 pure functions 來分別處理不同的 state properties\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 function visibilityFilter(state = \u0026#39;SHOW_ALL\u0026#39;, action) { switch (action.type) { case \u0026#39;SET_VISIBILITY_FILTER\u0026#39;: return action.filter default: return state } } function todos(state = [], action) { switch (action.type) { case \u0026#39;ADD_TODO\u0026#39;: return [ ...state, { text: action.text, completed: false } ] case \u0026#39;COMPLETE_TODO\u0026#39;: return state.map((todo, index) =\u0026gt; { if (index === action.index) { return Object.assign({}, todo, { completed: true }) } return todo }) default: return state } } import { combineReducers, createStore } from \u0026#39;redux\u0026#39; let reducer = combineReducers({ visibilityFilter, todos }) let store = createStore(reducer) 心得 #Reducer 這個方法，其實要多寫寫才會比較清楚，而且當你的 State 物件 『形狀』 越來越清楚的時候，你才會對 Reducer 比較有感覺\n所以剛開始寫的時候，我會建議一次寫一組 action/reducer，都測試確認之後，透過 createStore 串起來測試一遍，比較能夠理解\n","date":"2016年6月21日","permalink":"https://metavige.github.io/2016/06/21/redux-study-day-3/","section":"文章","summary":"\u003cul\u003e\n\u003cli\u003e\u003ca href=\"/2015/10/14/redux-study-day1/\"\u003eRedux Study Day1 - Start\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"/2015/10/19/redux-study-day2/\"\u003eRedux Study Day2 - Action\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"/2016/06/21/redux-study-day-3/\"\u003eRedux Study Day3 - Reducer\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"/2016/06/22/redux-study-day4/\"\u003eRedux Study Day4 - Store\u003c/a\u003e\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003e隔了半年多，重新真的找一個專案來實作 \u003ca href=\"https://github.com/reactjs/redux\" target=\"_blank\" rel=\"noreferrer\"\u003eRedux\u003c/a\u003e，不得不說，半年的變化\u003cbr\u003e\n跟我當初看 \u003ca href=\"https://github.com/reactjs/redux\" target=\"_blank\" rel=\"noreferrer\"\u003eRedux\u003c/a\u003e 差異還是蠻大的。當然，觀念什麼的是差不多，只是可能很多程式碼會有所調整\u003c/p\u003e\n\u003cp\u003e這次重新再開始，之前兩天的東西，找時間修改。今天就從 \u003ca href=\"https://github.com/reactjs/redux\" target=\"_blank\" rel=\"noreferrer\"\u003eRedux\u003c/a\u003e 核心的 Reducer 開始好了～\u003c/p\u003e","title":"Redux Study Day3 - Reducer"},{"content":"How to Get Node.js Logging Right\nNode.js Logging 需求 # Timestamp - 紀錄事件發生的時間點。很重要！ Logging format - 記錄格式。寫出的紀錄當然要是可讀性高的。 Log destination - 很多 Log 的工具有提供紀錄到不同地方的功能。作者認為我們應該只使用 standard output/error。因為這不是 application 所應該要做的事情。(筆記！ Log Level - 能夠紀錄 Log 的等級！用來區分不同等級的資料，之後也好做處理。 Log 技巧 # never, ever log credentials, passwords or any sensitive information. Adding correlation IDs: 利用 correlation IDs，當有 Log 是一個長時間處理的功能，需要有前後關聯的時候，透過一個 correlation ID 可以知道紀錄的關係。 ","date":"2016年5月31日","permalink":"https://metavige.github.io/2016/05/31/how-to-get-nodejs-logging-right/","section":"文章","summary":"\u003cp\u003e\u003ca href=\"https://blog.risingstack.com/node-js-logging-tutorial/\" target=\"_blank\" rel=\"noreferrer\"\u003eHow to Get Node.js Logging Right\u003c/a\u003e\u003c/p\u003e","title":"How to Get Node.js Logging Right"},{"content":"","date":null,"permalink":"https://metavige.github.io/tags/ansible/","section":"Tags","summary":"","title":"Ansible"},{"content":"嗯～Ansible 很好用\n所以很希望也可以用在 Windows 上～\n開始 # 首先要參考 Ansible Windows Support 的文件 確認 Windows 本身的 Powershell 版本要在 3.0 以上 (沒有的話，就可以下載這個 upgrade_to_ps3.ps1 來執行) 設定 winrm - 執行 setup.ps1 Windows 7/2008R2 版本還要先安裝 http://support.microsoft.com/kb/2842230 Windows 8.1/2012 以後版本已經安裝了 .Net 4.0，所以不需要特別作什麼事情 測試 #因為想在本機測試，所以用 Vagrant 來測試\n我這次用的是 win2012-r2 的 box 很簡單的用 vagrant init opentable/win-2012r2-standard-amd64-nocm; vagrant up --provider virtualbox 就可以啟動了 當然，需要進入 Windows 裡面去設定 winrm，參考上面的步驟，但是我懶得手動下載後才執行，所以我用了 chocolatey 的技巧，直接下載後執行。開啟 Powershell 視窗，執行下面指令就可以： iex ((new-object net.webclient).DownloadString(\u0026#39;https://raw.githubusercontent.com/ansible/ansible/devel/examples/scripts/ConfigureRemotingForAnsible.ps1\u0026#39;)) 執行完畢之後，我本來以為可以直接用 Vagrant 的 provision 功能，結果碰壁了～ 問題一 # 1 2 3 4 5 $ ansible default -i .vagrant/provisioners/ansible/inventory/vagrant_ansible_inventory -m win_ping -vvvv default | FAILED! =\u0026gt; { \u0026#34;failed\u0026#34;: true, \u0026#34;msg\u0026#34;: \u0026#34;ERROR! ssl: 500 WinRMTransport. [SSL: UNKNOWN_PROTOCOL] unknown protocol (_ssl.c:590)\u0026#34; } 原本以為直接用 Vagrant 產生的 inventory 檔案就可以執行，所以先用 win_ping 指令測試一下\n結果就出現了上面的錯誤\n找了一下資料以及對照 Vagrant 產生的 vagrant_ansible_inventory 檔案，才發現他產生出來的檔案是這樣：\n# Generated by Vagrant default ansible_connection=winrm ansible_ssh_host=127.0.0.1 ansible_ssh_port=55985 ansible_ssh_user=\u0026#39;vagrant\u0026#39; ansible_ssh_pass=\u0026#39;vagrant\u0026#39; 麻煩的是，產生出來的 ansible_ssh_port 走的是 http port\n內部啟動 winrm 會有兩個 port，對應 http/https。分別是 5985/5986\nvirtualbox 會幫忙 mapping 這兩個 port，分別對應 55985/55986\n但是 ansible 需要走 https，所以就出現了上面的錯誤訊息\n改法就是自己產生一個 inventory 檔案 (你不能改 .vagrant 下面產生出來的檔案，因為每次執行 vagrant provision 都會重新產生)\n[windows] default ansible_connection=winrm ansible_ssh_host=127.0.0.1 ansible_ssh_port=55986 ansible_ssh_user=\u0026#39;vagrant\u0026#39; ansible_ssh_pass=\u0026#39;vagrant\u0026#39; 問題二 # 1 2 3 4 5 $ ansible windows -i hosts -m win_ping -vvvv default | FAILED! =\u0026gt; { \u0026#34;failed\u0026#34;: true, \u0026#34;msg\u0026#34;: \u0026#34;ERROR! ssl: 500 WinRMTransport. [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed (_ssl.c:590)\u0026#34; } 本來以為改完後，一切幸福美滿～ 結果一執行就給我拋另外一個錯誤\n不過這個錯誤看起來就是 SSL 憑證問題！靠！這怎麼改？？？\n我前兩天升級了 Ansible 2.0，所以我還在想說是不是因為這個原因\n果不其然，找到了這篇文章: Packer – Vagrant – Ansible – Windows (WinRMTransport Error)\nAnsible 也有一個 Issue 在解釋這個問題\n改法就是，在現在的目錄下，建立一個 callback_plugins 目錄。然後建立一個 fix_ssl.py 檔案:\n1 2 3 4 5 6 import ssl if hasattr(ssl, \u0026#39;_create_default_https_context\u0026#39;) and hasattr(ssl, \u0026#39;_create_unverified_context\u0026#39;): ssl._create_default_https_context = ssl._create_unverified_context class CallbackModule(object): pass 根據這段程式碼英文的意思，我猜測就是忽略自簽章的 SSL 憑證驗證～因為我在 .Net 幹過一樣的事情～\n所以這次執行就成功了～\n1 2 3 4 5 6 7 8 $ ansible windows -i hosts -m win_ping -vvvv default | SUCCESS =\u0026gt; { \u0026#34;changed\u0026#34;: false, \u0026#34;invocation\u0026#34;: { \u0026#34;module_name\u0026#34;: \u0026#34;win_ping\u0026#34; }, \u0026#34;ping\u0026#34;: \u0026#34;pong\u0026#34; } 結語 #以上是我碰到的兩個問題，還好網路都有找到解法～\n所以之後應該只需要參考 Windows Modules 試試看用法，應該之後就可以拿來管理 Windows 了～\n不過可能還是要注意下面的事情：\n注意自己安裝的 Ansible 版本，我目前測試的版本是 2.0，所以我不知道 1.x 版本是不是沒有我遇到的問題 注意 winrm 設定，因為我這次測試是用 Vagrant box，所以是直接用 Port forwarding 做到。\n如果是實機，就得自己去找 winrm 的 port 之後如果要在實機測試的話，可能還要另外注意帳號密碼問題 ","date":"2016年1月19日","permalink":"https://metavige.github.io/2016/01/19/ansible-windows-manage-start/","section":"文章","summary":"\u003cp\u003e嗯～\u003ca href=\"http://www.ansible.com\" target=\"_blank\" rel=\"noreferrer\"\u003eAnsible\u003c/a\u003e 很好用\u003cbr\u003e\n所以很希望也可以用在 Windows 上～\u003c/p\u003e","title":"用 Ansible 管理 Windows"},{"content":"","date":null,"permalink":"https://metavige.github.io/tags/electron/","section":"Tags","summary":"","title":"Electron"},{"content":"很早就想要用 Electron 來開發 App 了，因為 Web 其實用起來有時候卡卡的\n尤其 Electron 出現之後，開發 Web 等同於開發 App，真是令人感到興奮～\n開始 # 建立一個目錄\n用 npm init 建立 package.json\n安裝 electron-prebuilt\nnpm install --save-dev electron-prebuilt 程式內容 #簡單的 Hello World!\n先建立 app 目錄\n產生 app/index.html 檔案，內容很簡單！就是 Hello!\n\u0026lt;h2\u0026gt;Hello\u0026lt;/h2\u0026gt; 建立 main.js\n\u0026#39;use strict\u0026#39;; var app = require(\u0026#39;app\u0026#39;); var BrowserWindow = require(\u0026#39;browser-window\u0026#39;); var mainWindow = null; app.on(\u0026#39;ready\u0026#39;, function() { mainWindow = new BrowserWindow({ height: 600, width: 800 }); mainWindow.loadURL(\u0026#39;file://\u0026#39; + __dirname + \u0026#39;/app/index.html\u0026#39;); }); 執行 #因為剛剛安裝了 electron-prebuilt，所以可以直接用下面的指令就可以執行了！\nnode_modules/.bin/electron . 這樣就可以了～\n打包 #參考 electron-packager\n先安裝 electron-packager\n參考說明文件，用下面的指令來打包自己的 App。（注意那個版本只的是 Electron 版本，不是你自己 App 的版本）\n$ electron-packager ./ App1 --platform=darwin --arch=x64 --out ~/Desktop/App1 --version 0.36.2 --overwrite Packaging app for platform darwin x64 using electron v0.36.2 Wrote new app to /Users/RickyChiang/Desktop/App1/App1-darwin-x64 目前 Electron Release 的版本是 0.36.2，可以到 https://github.com/atom/electron/releases 去看\nelectron-packager 命令列的說明可以參考 usage.txt\n到桌面看就會有檔案產生了，點兩下就可以執行了\n後語 #如果要方便執行的話，可以編輯一下 package.json\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 { \u0026#34;name\u0026#34;: \u0026#34;app1\u0026#34;, \u0026#34;version\u0026#34;: \u0026#34;1.0.0\u0026#34;, \u0026#34;description\u0026#34;: \u0026#34;\u0026#34;, \u0026#34;main\u0026#34;: \u0026#34;./main.js\u0026#34;, \u0026#34;scripts\u0026#34;: { \u0026#34;start\u0026#34;: \u0026#34;electron .\u0026#34;, \u0026#34;package\u0026#34;: \u0026#34;electron-packager ./ App1 --platform=darwin --arch=x64 --out ~/Desktop/App1 --version 0.36.2 --overwrite\u0026#34; }, \u0026#34;devDependencies\u0026#34;: { \u0026#34;electron-packager\u0026#34;: \u0026#34;^5.2.0\u0026#34;, \u0026#34;electron-prebuilt\u0026#34;: \u0026#34;^0.36.2\u0026#34; } } 這樣就可以用 npm start 執行了～\n編譯的話，就用 npm run package 就可以了\n","date":"2015年12月30日","permalink":"https://metavige.github.io/2015/12/30/First-Electron-App/","section":"文章","summary":"\u003cp\u003e很早就想要用 \u003ca href=\"http://electron.atom.io\" target=\"_blank\" rel=\"noreferrer\"\u003eElectron\u003c/a\u003e 來開發 App 了，因為 Web 其實用起來有時候卡卡的\u003cbr\u003e\n尤其 \u003ca href=\"http://electron.atom.io\" target=\"_blank\" rel=\"noreferrer\"\u003eElectron\u003c/a\u003e 出現之後，開發 Web 等同於開發 App，真是令人感到興奮～\u003c/p\u003e","title":"First Electron App"},{"content":"","date":null,"permalink":"https://metavige.github.io/tags/gulp/","section":"Tags","summary":"","title":"Gulp"},{"content":"一直以來，開發 .Net 專案，因為只能在 Windows 上使用 Visual Studio 開發\nBuild Project 的 Performance 總是我一個覺得很麻煩的事情\n參考目前開發 web project 的經驗，決定找 gulp 相關的資料來改\n步驟 # 先用 npm init 建立 package.json 安裝套件： 1 npm install --save-dev gulp gulp-msbuild 寫 gulpfile.js 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 var gulp = require(\u0026#39;gulp\u0026#39;); var msbuild = require(\u0026#39;gulp-msbuild\u0026#39;); gulp.task(\u0026#34;build\u0026#34;, function () { return gulp.src(\u0026#34;./Sample.sln\u0026#34;) .pipe(msbuild({ targets: [\u0026#39;Clean\u0026#39;, \u0026#39;Build\u0026#39;], toolsVersion: 14.0, errorOnFail: true }) ); }); gulp.task(\u0026#39;watch\u0026#39;, function () { gulp.watch(\u0026#39;**/*.cs\u0026#39;, [\u0026#39;build\u0026#39;]); }); gulp.task(\u0026#39;default\u0026#39;, [\u0026#39;build\u0026#39;]); 開啟 cmd，執行 gulp watch 後記 #這邊提供的方式，可以讓編譯這件事情，在背景作業。在你有改變程式碼之後自動作業\n因為我自己寫程式的過程，以及看同事在執行 .Net 專案的作法，大多數時間都是在等待編譯\n這些時間其實都很浪費\n要加速開發，這只是一個小動作而已，網路上找到的文章，是可以在搭配更多 Task 來做測試、改變版本號等自動化動作\n但是這還需要些時間來整合，這邊只是簡單的改變 build 的部分\nReference # Using Gulp to Build and Deploy .NET Apps on Windows gulp-msbuild gulp-mstest ","date":"2015年12月12日","permalink":"https://metavige.github.io/2015/12/12/dotnet-proj-with-gulp/","section":"文章","summary":"\u003cp\u003e一直以來，開發 .Net 專案，因為只能在 Windows 上使用 Visual Studio 開發\u003cbr\u003e\nBuild Project 的 Performance 總是我一個覺得很麻煩的事情\u003c/p\u003e","title":"在 .Net 專案使用 gulp build"},{"content":"","date":null,"permalink":"https://metavige.github.io/tags/RESTful/","section":"Tags","summary":"","title":"RESTful"},{"content":"寫文件大概是工程師最討厭的事情，沒有之一\n前言 #為了要寫份好看、易懂的 API 文件，如果用現在比較習慣的 Markdown 語法來寫，還需要去找格式要怎樣定義。\n這對工程師來說，實在是莫大的折磨。老實說，這比要我寫測試案例還難～\n所以找了網路上有關 REST API 文件規格的資料，找到了這篇文章 Why You Should Use Markdown for Your API Documentation\n其中提到了 RAML - RESTful API Modeling Language\n因為我 API 是已經寫好的狀況，所以並沒有使用到它與 Source Code 結合的部分，所以只是單純的自己根據之前定的規格，把文件寫出來，並且要加上一些簡單的操作說明\n當然我希望這些也都是用 Markdown 的語法來寫\n開始 #要開始做，當然是先看別人的範例怎麼做，直接參考！\n由於我後續要把規格轉換成文件，所以我找到了 raml2html 的工具，而這個工具的 Example 相當的詳細！值得參考\n接下來，你可以到 Design Your API 這個頁面，去下載 API Designer\n有裝 npm 的人可以直接用以下指令就安裝好了\nnpm install -g api-designer 安裝完之後，執行 api-designer 指令，然後開啟瀏覽器，打開 http://localhost:3000，就可以看到類似下面的畫面了！\n透過這個 Api Designer 的 UI，可以很簡單的看出你編輯的 RAML 格式是否正確，如果不正確，這個 UI 就會提醒你錯誤發生的地方，並且在你編輯完後可以直接預覽～\n(這邊不介紹這個 UI 的使用～～～)\n更新：\n根據 Editor File System 這份文件說的，以及看了一下我自己瀏覽器的設定，才發現，這個 api designer 是把資料存在 LocalStorage 中\n所以，它並不是把資料存在 server 端。\n這有個好處是，大家可以共用同樣的 api designer UI 來編輯自己的文件格式，不用擔心會被別人改到\n不過壞處也是一樣，別人無法編輯～\n不過，根據這個好處，表示其實我可以很安心的自己做一個 docker image\n這樣方便其他人使用\n有需要的人可以自己下載～ (因為是用 alpine 為 base，所以只有 48.86 MB)\nhttps://github.com/metavige/alpine-api-designer\n轉換 HTML #寫完 RAML 之後還是需要轉換成 HTML 才好看，所以透過 raml2html 工具，先用 npm 安裝：\nnpm i -g raml2html 然後透過指令去轉換：\nraml2html -i example.raml -o example.html 如果你寫的很豐富，結果可能就會像是 raml2html 的範例一樣\n結語 #現在我自己使用還有遇到一些問題，不過，整體來說，產出的內容我自己是還蠻滿意的\n格式也算不難看，當然，文件內容本身還需要傷腦筋一下，畢竟文件是要給人看的，內容部分其實還蠻重要的！\n目前我使用的是 RAML 0.8 的版本，我想應該就夠了 因為 RAML 1.0 好像是新的，不知道新的會不會有什麼問題，我也沒多加研究\n之後等到文件內容都寫完了，再來研究自動化的部份以及新的 RAML 1.0 規格，像是不是可以結合 CI (Jenkins) ?\nReference # RAML 0.8 RAML 1.0 ","date":"2015年11月16日","permalink":"https://metavige.github.io/2015/11/16/raml-easy-start-npm/","section":"文章","summary":"\u003cp\u003e寫文件大概是工程師最討厭的事情，沒有之一\u003c/p\u003e","title":"快速撰寫好看的 REST API 文件 - 使用 RAML"},{"content":"","date":null,"permalink":"https://metavige.github.io/tags/nginx/","section":"Tags","summary":"","title":"Nginx"},{"content":"目的 #開發一套系統，把資料處理 (POST/PUT/DELETE) 與查詢 (GET) 的功能分開做\n變成提供兩套服務，但對 Client 來說，還是需要整合成相同的 Uri\n所以就透過 Nginx 來做到前端介面的整合\n範例 #location / { if ($request_method !~* GET) { # For Write Requests proxy_pass http://writers; } # For Read Requests proxy_pass http://readers;} 結語 #雖然說，Nginx 其實並不希望你使用 if (If Is Eval)，不過對工程師來說，能簡單的做到效果是最重要的！！！\nReference #Stackoverflow: nginx proxy_pass based on whether request method is POST, PUT or DELETE\n","date":"2015年11月13日","permalink":"https://metavige.github.io/2015/11/13/nginx-proxy-pass-by-http-method/","section":"文章","summary":"\u003ch2 id=\"目的\" class=\"relative group\"\u003e目的 \u003cspan class=\"absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100\"\u003e\u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700\" style=\"text-decoration-line: none !important;\" href=\"#%e7%9b%ae%e7%9a%84\" aria-label=\"定位點\"\u003e#\u003c/a\u003e\u003c/span\u003e\u003c/h2\u003e\u003cp\u003e開發一套系統，把資料處理 (POST/PUT/DELETE) 與查詢 (GET) 的功能分開做\u003cbr\u003e\n變成提供兩套服務，但對 Client 來說，還是需要整合成相同的 Uri\u003cbr\u003e\n所以就透過 Nginx 來做到前端介面的整合\u003c/p\u003e","title":"Nginx 透過 HTTP Method 來做 Proxy Pass"},{"content":"","date":null,"permalink":"https://metavige.github.io/tags/java/","section":"Tags","summary":"","title":"Java"},{"content":"製作 Java base docker image based Alpinelinux\n之前找了時間，想做個小一點的 java8 docker image\n因為 offical java8 image 有約 800 多 mb，所以考慮用 Alpinelinux 來做 base\n因為 Alpinelinux 只有 5MB 多\n不過在安裝的時候，發現下載了 JDK 解壓縮之後，一直無法執行 java\n透過 apk 也只能找到 java7\n上網搜尋了一下，發現了有這個 issue\n也參考了別人寫的 Dockerfile\n記錄下來！\ngithub 連結 #https://github.com/metavige/alpine-java\n後記 #另外，找到一個有趣的服務 ImageLayers.io\n透過這個服務，可以看你這個 Docker Image 有幾層以及大小，可以用這個來檢視自己做 docker image 的方式，看看是否還有縮小的空間～\n不錯用！！！\n","date":"2015年11月3日","permalink":"https://metavige.github.io/2015/11/03/docker-alpine-java8/","section":"文章","summary":"\u003cp\u003e製作 Java base docker image based \u003ca href=\"http://alpinelinux.org/\" target=\"_blank\" rel=\"noreferrer\"\u003eAlpinelinux\u003c/a\u003e\u003c/p\u003e","title":"Make Java 8 docker image based Alpinelinux "},{"content":" Redux Study Day1 - Start Redux Study Day2 - Action Redux Study Day3 - Reducer Redux Study Day4 - Store 延續前一天的學習，今天進入 Redux Day2!\n先從簡單的 Action 開始做\nAction #一般來說 Action 就是 View 要與資料做互動的一個介面，View 自己本身不會變更 View 自己的畫面資料，而是透過呼叫 Action。\n詳細的流程可以參考 Flux 的流程圖！\n基本的 Action 都會包含兩種東西\ntype: Action Type payload: Payloads 如果參考 Flux Standard Action，還有以下兩項：\nerror meta 但是以上兩項是 optional 的，有需要可以參考文件說明\n所謂的 Action Type，就是一種分類，有了這個分類，提供給 Dispatcher 才可以知道後續的流程是什麼。\n至於 Payloads，其實就把它想做參數就簡單了\n其實意思就是說，你提供一組資料，告訴 Dispatcher，由他來分類給不同的方法來執行，Payloads 就是方法的參數\n就有點像是下面的樣子：\n1 2 3 4 { type: \u0026#39;ADD TODO\u0026#39;. payload\u0026#34; { text: \u0026#39;new todo\u0026#39; } } 但是因為有時候，我們的 Payload 會是畫面上的一些資料，為了要做到互動的效果，所以我們的 action 不一定會是靜態的 object。\n所以就有所謂的 Action Creators\nAction Creators #這些 Action Creators 是用來產生 Action 的方法，一般都是簡單的方法，透過傳遞參數來變更 payload 的內容。\n1 2 3 4 5 6 function addTodo(text) { return { type: ADD_TODO, payload: text }; } 在 Redux 裡，你只要回傳 action 物件就可以。\n所以測試就變得非常簡單，因為不會有什麼特別的方法是屬於 flux framework 的方法。\n實作 #根據專案的結構，我們就在專案中建立一個 action 的目錄，然後把所有的 Action 都放在這邊\n所以我們就可以建立一個 index.js 在這邊\n以 redux news 的其中一個方法為範例：\n1 2 3 4 5 6 7 export const REQUEST_ITEMTHREADS = \u0026#39;REQUEST_ITEMTHREADS\u0026#39;; function requestItemThreads () { return { type: REQUEST_ITEMTHREADS, // must have type text: \u0026#39;Requesting the top item threads\u0026#39; } } 結語 #Action 其實在 Flux 的流程裡面是一個很重要的項目，但是不管是什麼 Flux framework，這個部分也都有，所以，不太難理解。\n不過，因為 Redux 有一些方法可以做到 dispatch，在 Action 這邊也就變得很單純了～～～\n","date":"2015年10月19日","permalink":"https://metavige.github.io/2015/10/19/redux-study-day2/","section":"文章","summary":"\u003cul\u003e\n\u003cli\u003e\u003ca href=\"/2015/10/14/redux-study-day1/\"\u003eRedux Study Day1 - Start\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"/2015/10/19/redux-study-day2/\"\u003eRedux Study Day2 - Action\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"/2016/06/21/redux-study-day-3/\"\u003eRedux Study Day3 - Reducer\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"/2016/06/22/redux-study-day4/\"\u003eRedux Study Day4 - Store\u003c/a\u003e\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003e延續\u003ca href=\"/2015/10/14/redux-study-day1/\"\u003e前一天\u003c/a\u003e的學習，今天進入 Redux Day2!\u003c/p\u003e","title":"Redux Study Day2 - Action"},{"content":"為了要開發 App 上架，要設定 iTune Connect，收到了 Apple 這邊寄來的信，準備啟用\n點選了啟用連結 (activate your account) 之後，結果發生了兩光的問題！\n畫面一片空白！\n問題處理 #後來找到 http://blog.csdn.net/iroycn/article/details/49095069 發現有人已經遇過這個問題了\n問題其實就是，這個網頁其中一個最重要的 js 無法取到，可以打開網頁主控台就可以看到錯誤\nhttps://idmsauth-stable.corp.apple.com/appleauth/dist/web/jsapi/authService.js?cache=xxxxxxx 就是 idmsauth-stable.corp.apple.com 解析不到！ 因為這個網址不是公開的網址 (?)\n在 Apple 的論壇也有說明：\nhttps://forums.developer.apple.com/message/73056#73056 https://forums.developer.apple.com/message/73434#73434 解決方法 # 先打開 Safari 的 『顯示網頁資源』功能\n找到 main.js，然後複製 main.js 所有的程式。 然後找到裡面 idmsauth-stable.corp.apple.com 的網址，改成 idmsa.apple.com 然後把改過的程式碼貼到主控台 接下來就可以看到這個畫面\n同意之後，就算是成功了！ 結語 #為什麼 Apple 會出這種包呢？\n","date":"2015年10月15日","permalink":"https://metavige.github.io/2015/10/15/itune-connect-activate-error/","section":"文章","summary":"\u003cp\u003e為了要開發 App 上架，要設定 iTune Connect，收到了 Apple 這邊寄來的信，準備啟用\u003c/p\u003e","title":"iTune Connect 啟用的兩光問題"},{"content":"","date":null,"permalink":"https://metavige.github.io/tags/mac/","section":"Tags","summary":"","title":"Mac"},{"content":" Redux Study Day1 - Start Redux Study Day2 - Action Redux Study Day3 - Reducer Redux Study Day4 - Store 準備開始學習使用 Redux\n開始準備 # ECMAScript 6: https://www.gitbook.com/book/wohugb/ecmascript-6/details Redux Github: https://github.com/rackt/redux Redux 的中文 gitbook: http://chentsulin.github.io/redux/index.html 其中我覺得 ECMAScript 6 一定要先懂，因為，基本上現在你找的到的 Redux 範例，都會是 ES6 的語法了\n所以，如果你不先了解 ES6 的語法，一定會很痛苦\u0026hellip;..\n當然，你要用 Redux 前要先了解 Flux 是什麼會比較好\n以下這張圖大概就是 Flux 最著名的一張架構圖了 \u0026hellip;..\n參考範例 #我個人覺得 Redux-Hacker-News 的確是寫的不錯的～\n開始實作 #本來我要用 yo 來做開始，後來發現產生出來的東西，大部分我都不懂，乾脆參考 Hacker-News 的範例重新來一次～\n之後遇到要使用到的時候再裝，印象比較深刻，比較知道自己在用什麼\n套件安裝 #當然，先裝基本的 React/Redux\n1 2 npm install --save redux npm install --save react 接下來是安裝開發環境要用的，比如說 webpack, babel 等\n1 2 3 4 5 npm install --save-dev babel-core npm install --save-dev babel-loader npm install --save-dev babel-plugin-react-transform npm install --save-dev webpack-dev-middleware npm install --save-dev webpack-hot-middleware 安裝完之後，我的 package.json 大概是長下面這樣子的\n{ // 其他我就不貼了～ \u0026#34;dependencies\u0026#34;: { \u0026#34;react\u0026#34;: \u0026#34;^0.14.0\u0026#34;, \u0026#34;redux\u0026#34;: \u0026#34;^3.0.2\u0026#34; }, \u0026#34;devDependencies\u0026#34;: { \u0026#34;babel-core\u0026#34;: \u0026#34;^5.8.25\u0026#34;, \u0026#34;babel-loader\u0026#34;: \u0026#34;^5.3.2\u0026#34;, \u0026#34;babel-plugin-react-transform\u0026#34;: \u0026#34;^1.1.1\u0026#34;, \u0026#34;redux-devtools\u0026#34;: \u0026#34;^2.1.5\u0026#34;, \u0026#34;webpack\u0026#34;: \u0026#34;^1.12.2\u0026#34;, \u0026#34;webpack-dev-middleware\u0026#34;: \u0026#34;^1.2.0\u0026#34;, \u0026#34;webpack-hot-middleware\u0026#34;: \u0026#34;^2.4.1\u0026#34; } } 準備 webpack.config.js #不得不說，我覺得 redux-hacker-news 的 webpack.config.js 蠻不錯的，所以就直接先複製過來使用了！\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 var path = require(\u0026#39;path\u0026#39;); var webpack = require(\u0026#39;webpack\u0026#39;); module.exports = { devtool: \u0026#39;cheap-module-eval-source-map\u0026#39;, entry: [ \u0026#39;webpack-hot-middleware/client\u0026#39;, \u0026#39;./index\u0026#39; ], output: { path: path.join(__dirname, \u0026#39;dist\u0026#39;), filename: \u0026#39;bundle.js\u0026#39;, publicPath: \u0026#39;/static/\u0026#39; }, plugins: [ new webpack.optimize.OccurenceOrderPlugin(), new webpack.HotModuleReplacementPlugin(), new webpack.NoErrorsPlugin() ], module: { loaders: [{ test: /\\.js$/, loaders: [\u0026#39;babel\u0026#39;], exclude: /node_modules/, include: __dirname },{ test: /\\.css$/, loader: \u0026#39;style!css\u0026#39; },{ test: /\\.gif$/, loader: \u0026#39;url?limit=10000\u0026amp;minetype=image/gif\u0026#39; }] } }; 準備一個測試的 server #一樣，hacker-news 的 server.js，很簡單也很實用～ 但是因為會用到 express ，補一下 npm install\n1 npm install --save-dev express server.js 程式碼\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 var webpack = require(\u0026#39;webpack\u0026#39;); var webpackDevMiddleware = require(\u0026#39;webpack-dev-middleware\u0026#39;); var webpackHotMiddleware = require(\u0026#39;webpack-hot-middleware\u0026#39;); var config = require(\u0026#39;./webpack.config\u0026#39;); var app = new require(\u0026#39;express\u0026#39;)(); var port = 3000; var compiler = webpack(config); app.use(webpackDevMiddleware(compiler, { noInfo: true, publicPath: config.output.publicPath })); app.use(webpackHotMiddleware(compiler)); app.use(function(req, res) { res.sendFile(__dirname + \u0026#39;/index.html\u0026#39;); }); app.listen(port, function(error) { if (error) { console.error(error); } else { console.info(\u0026#34;==\u0026gt; 🌎 Listening on port %s. Open up http://localhost:%s/ in your browser.\u0026#34;, port, port); } }); 本來想說測試一下，直接 run 一下 node server.js 看看有沒有問題，結果就出現了下面的錯誤：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 $ node server.js ==\u0026gt; 🌎 Listening on port 3000. Open up http://localhost:3000/ in your browser. webpack built 9cc6533f9b26bec5e1ce in 190ms Hash: 9cc6533f9b26bec5e1ce Version: webpack 1.12.2 Time: 190ms Asset Size Chunks Chunk Names bundle.js 45 kB 0 main chunk {0} bundle.js (main) 7.69 kB [rendered] [0] multi main 40 bytes {0} [built] [1 error] [1] (webpack)-hot-middleware/client-overlay.js 883 bytes {0} [built] [2] (webpack)-hot-middleware/client.js 3.27 kB {0} [built] [3] (webpack)-hot-middleware/~/strip-ansi/index.js 161 bytes {0} [built] [4] (webpack)-hot-middleware/~/strip-ansi/~/ansi-regex/index.js 145 bytes {0} [built] [5] (webpack)-hot-middleware/process-update.js 2.93 kB {0} [built] [6] (webpack)/buildin/module.js 251 bytes {0} [built] ERROR in multi main Module not found: Error: Cannot resolve \u0026#39;file\u0026#39; or \u0026#39;directory\u0026#39; ./index in /Volumes/RamDisk/redux-study @ multi main 最後試著試著發現，本來以為是我漏掉了 index.html，但最後發現，其實 index.js 才是最重要的\n當我做了下面的動作\n1 $ touch index.js 再執行 node server.js，感覺上就正常了！\n1 2 3 4 5 $ node server.js ==\u0026gt; 🌎 Listening on port 3000. Open up http://localhost:3000/ in your browser. webpack built 1e04a7ed2baa7052b68b in 771ms webpack building... webpack built 1e04a7ed2baa7052b68b in 8ms index.html #其實，因為 React 算是走 SPA 路線的，所以其實首頁很簡單，可以像下面這樣\n1 2 3 4 5 6 7 8 9 10 11 \u0026lt;!DOCTYPE html\u0026gt; \u0026lt;html\u0026gt; \u0026lt;head\u0026gt; \u0026lt;title\u0026gt;Redux Study\u0026lt;/title\u0026gt; \u0026lt;/head\u0026gt; \u0026lt;body\u0026gt; \u0026lt;div id=\u0026#34;root\u0026#34;\u0026gt;Hello \u0026lt;/div\u0026gt; \u0026lt;script src=\u0026#34;/static/bundle.js\u0026#34;\u0026gt;\u0026lt;/script\u0026gt; \u0026lt;/body\u0026gt; \u0026lt;/html\u0026gt; 其中，javascript 是使用了一個目前還沒產生的檔案\n不過，透過 webpack-dev-middleware，這個檔案在你執行 server.js 的時候會動態產生\n所有檔案，都會 on-fly 在記憶體中處理，不需要像以前一樣產生一堆暫存檔案\n不過，記得這只是在開發階段方便而已，Production 階段還是要產生實體的 bundle.js 檔案\n就如同 webpack-dev-middleware 的 README 就說到：\nTHIS MIDDLEWARE SHOULD ONLY USED FOR DEVELOPMENT!\nDO NOT USE IT IN PRODUCTION!\n第一天結後心得 #其實，對不太熟悉 javascript 開發的人來講，像我這樣，準備環境比較不容易\n尤其我記得我有印象的，還是在 grunt/gulp 階段～\n不過才沒幾個月，一堆工具又出來了，雖然是為了 ES6 而產生的工具，但是還是覺得太多東西要學了\nwebpack 去年就聽過了，不過聽不太懂，所以本來是要放棄的。但是現在感覺好像又不是我之前聽過的那樣了～～～\n變化真快\n之後可能還要對 webpack 多多了解一下\u0026hellip;.\n至於 Redux 本身，大概又是另外一個難解的問題，需要好好了解了～\n","date":"2015年10月14日","permalink":"https://metavige.github.io/2015/10/14/redux-study-day1/","section":"文章","summary":"\u003cul\u003e\n\u003cli\u003e\u003ca href=\"/2015/10/14/redux-study-day1/\"\u003eRedux Study Day1 - Start\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"/2015/10/19/redux-study-day2/\"\u003eRedux Study Day2 - Action\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"/2016/06/21/redux-study-day-3/\"\u003eRedux Study Day3 - Reducer\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"/2016/06/22/redux-study-day4/\"\u003eRedux Study Day4 - Store\u003c/a\u003e\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003e準備開始學習使用 \u003ca href=\"https://github.com/rackt/redux\" target=\"_blank\" rel=\"noreferrer\"\u003eRedux\u003c/a\u003e\u003c/p\u003e","title":"Redux Study Day1 - Start"},{"content":"早上使用 brew 發生錯誤\n1 2 3 4 5 \u0026gt; brew update Error: GitHub API rate limit exceeded for xxx.xxx.xxx.xxx. (But here\u0026#39;s the good news: Authenticated requests get a higher rate limit. Check out the documentation for more details.) Try again in 11 minutes 49 seconds, or create an personal access token: https://github.com/settings/tokens and then set it as HOMEBREW_GITHUB_API_TOKEN. 找了一下資料，估計是因為之前 Github 被攻擊，所採取的一個必要的限流措施\n我覺得不錯\n這個處理方式很簡單，網路上已經有人做出解釋了～\n接下來你只要把產生的 Token，設定成環境變數就可以 (請用自己產生的 Token ，這邊只是示意～)\n1 export HOMEBREW_GITHUB_API_TOKEN=9927d2878ffa105fc5236c762f2fd7zfd28b841d ","date":"2015年6月2日","permalink":"https://metavige.github.io/2015/06/02/homebrew-github-token/","section":"文章","summary":"\u003cp\u003e早上使用 brew 發生錯誤\u003c/p\u003e","title":"Homebrew - Github Rate Limit Exceeded....."},{"content":"Docker 最近出了 1.6 版，Docker Swarm 也隨之更新了。\n參考了以下的網站，自己嘗試做做看～ 原文連結\n開始 #這邊實作的是與 docker-machine 結合的部分\n先產生一個 token 1 2 $ docker run swarm create c745d2d1bd2f65579e41f3808533da86 接下來複製上面產生的 Token，後面要用到\n先建立 swarm-master 1 2 3 4 5 6 7 $ docker-machine create -d virtualbox --swarm --swarm-master --swarm-discovery token://c745d2d1bd2f65579e41f3808533da86 swarm-master INFO[0000] Creating SSH key... INFO[0000] Creating VirtualBox VM... INFO[0010] Starting VirtualBox VM... INFO[0010] Waiting for VM to start... INFO[0087] \u0026#34;swarm-master\u0026#34; has been created and is now the active machine. INFO[0087] To point your Docker client at it, run this in your shell: docker-machine env swarm-master | source 接下來分別建立 swarm-node01, swarm-node02 1 2 3 4 5 6 7 $ docker-machine create -d virtualbox --swarm --swarm-discovery token://c745d2d1bd2f65579e41f3808533da86 swarm-node-01 INFO[0000] Creating SSH key... INFO[0000] Creating VirtualBox VM... INFO[0010] Starting VirtualBox VM... INFO[0011] Waiting for VM to start... INFO[0073] \u0026#34;swarm-node-01\u0026#34; has been created and is now the active machine. INFO[0073] To point your Docker client at it, run this in your shell: docker-machine env swarm-node-01 | source 1 2 3 4 5 6 7 $ docker-machine create -d virtualbox --swarm --swarm-discovery token://c745d2d1bd2f65579e41f3808533da86 swarm-node-02 INFO[0000] Creating SSH key... INFO[0000] Creating VirtualBox VM... INFO[0010] Starting VirtualBox VM... INFO[0011] Waiting for VM to start... INFO[0070] \u0026#34;swarm-node-02\u0026#34; has been created and is now the active machine. INFO[0070] To point your Docker client at it, run this in your shell: docker-machine env swarm-node-02 | source 執行後的結果 1 2 3 4 5 $ docker-machine ls NAME ACTIVE DRIVER STATE URL SWARM swarm-master virtualbox Running tcp://192.168.99.102:2376 swarm-master (master) swarm-node-01 virtualbox Running tcp://192.168.99.103:2376 swarm-master swarm-node-02 * virtualbox Running tcp://192.168.99.104:2376 swarm-master 看一下 swarm-master 裡面，跑了什麼東西 如果發現，執行 docker ps 沒東西，表示你現在並非在 swarm-master 先執行一下 docker-machine active swarm-master 就可以\n理論上，會有兩個 container 已經跑起來在 swarm-master，就是上面那張圖的 agent (紫色)\n1 2 3 4 $ docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES a53db3d5ffc4 swarm:latest \u0026#34;/swarm join --addr 35 minutes ago Up 16 minutes 2375/tcp swarm-agent e98fbd194f54 swarm:latest \u0026#34;/swarm manage --tls 35 minutes ago Up 16 minutes 2375/tcp, 0.0.0.0:3376-\u0026gt;3376/tcp swarm-agent-master Swarm Cluster 的內容 先執行 eval \u0026quot;$(docker-machine env --swarm swarm-master)\u0026quot; 所以這邊就顯示了，我建立了兩個 node\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 $ docker info Containers: 4 Strategy: spread Filters: affinity, health, constraint, port, dependency Nodes: 3 swarm-master: 192.168.99.102:2376 └ Containers: 2 └ Reserved CPUs: 0 / 4 └ Reserved Memory: 0 B / 1.025 GiB swarm-node-01: 192.168.99.103:2376 └ Containers: 1 └ Reserved CPUs: 0 / 4 └ Reserved Memory: 0 B / 1.025 GiB swarm-node-02: 192.168.99.104:2376 └ Containers: 1 └ Reserved CPUs: 0 / 4 └ Reserved Memory: 0 B / 1.025 GiB 可以用下面的指令列出 swarm 的 node，如果你有建立兩個以上的 swarm-master，用的是你建立 swarm-master 的 token 來區分 1 2 3 4 $ docker run swarm list token://c745d2d1bd2f65579e41f3808533da86 192.168.99.104:2376 192.168.99.102:2376 192.168.99.103:2376 2015-05-02 更新，我之前因為使用 token 是用原文的範例指令直接執行，所以，註冊 Swarm 的時候，會與原本註冊的資料重複，如果自己產生一組 Token 就不會這樣了～\n後記 #其實我現在對 Docker Swarm 的了解還沒有很深入，只是先照著文章的步驟做出來而已，還在想這個可以應用在哪邊，之後如果有想到或嘗試什麼新的東西，會在寫下來\n在我嘗試這篇文章的步驟時，出現了一個意料之外的狀況，就是我先把我本機的 docker 升級到 1.6，結果我建立出來的 virtualbox image 卻不是這個版本，所以就噴出了以下的錯誤：\n1 FATA[0000] Error response from daemon: client and server don\u0026#39;t have same version (client : 1.18, server: 1.17) 如果遇到這個狀況，只需要升級 swarm-master 的 boot2docker 版本就可以。 這時候就會覺得 docker-machine 很好用 \u0026hellip;..\n1 docker-machine upgrade swarm-master 然後等它做完，重新啟動 docker-machine 就好～ (建議是要重新啟動，因為我不重新啟動就不能用～)\n","date":"2015年4月29日","permalink":"https://metavige.github.io/2015/04/29/try-docker-swarm/","section":"文章","summary":"\u003cp\u003e\u003ca href=\"https://www.docker.com\" target=\"_blank\" rel=\"noreferrer\"\u003eDocker\u003c/a\u003e 最近出了 1.6 版，\u003ca href=\"https://docs.docker.com/swarm/\" target=\"_blank\" rel=\"noreferrer\"\u003eDocker Swarm\u003c/a\u003e 也隨之更新了。\u003c/p\u003e\n\u003cp\u003e參考了以下的網站，自己嘗試做做看～\n\u003ca href=\"https://www.voxxed.com/blog/2015/04/clustering-using-docker-swarm-0-2-0/\" target=\"_blank\" rel=\"noreferrer\"\u003e原文連結\u003c/a\u003e\u003c/p\u003e","title":"Try Docker Swarm"},{"content":"嘗試在本機設定 pac，結果搞了很久，才知道是 Yosemite 的問題～\n不管我怎樣設定，系統監視程式都是這樣的錯誤\nnetworkd[340]: -[NETProxyLookup pacLookupComplete:proxies:error:] PAC evaluation error: Error Domain=kCFErrorDomainCFNetwork Code=308 \u0026#34;The operation couldn’t be completed. (kCFErrorDomainCFNetwork error 308.)\u0026#34; 然後我完全不知道是為什麼！？\n不過後來我找到這篇～\nhttps://discussions.apple.com/thread/6642648\n重點就是，你的 pac 設定的時候，如果 Url 是 proxy.pac 結尾，就會發生問題！\n所以總結在 Yosemite 設定 pac 的作法\npac 檔案要放在本機的 web server share~ 我是安裝了一個 nginx，用 8080 port share 測試過，不能用 file:////, 一定要是 http:// 網址不可以是 proxy.pac 結尾 (喵的咧！！！ ) 可以用 google 的 pactester 測試 pac 的程式是不是寫對 ","date":"2015年4月24日","permalink":"https://metavige.github.io/2015/04/24/mac-yosemite-proxypac-not-working/","section":"文章","summary":"\u003cp\u003e嘗試在本機設定 pac，結果搞了很久，才知道是 Yosemite 的問題～\u003c/p\u003e","title":"Proxy.pac is not working at Yosemite"},{"content":"","date":null,"permalink":"https://metavige.github.io/tags/git/","section":"Tags","summary":"","title":"Git"},{"content":"這是我今天幫忙 https://github.com/tiimgreen/github-cheat-sheet 做一個簡單繁體中文版本的 PR，其實只是把簡體中文版本稍微轉換一下而已～\n不過其實這篇蠻有意思的，很多值得參考～\nGitHub秘笈 #Git 和 Github 秘笈，靈感來自於 Zach Holman 在 2012 年 Aloha Ruby Conference 和 2013 年 WDCNZ 上所做的演講：Git and GitHub Secrets(slides) 和 More Git and GitHub Secrets(slides)。\n目錄 # GitHub 不比較空白字串 調整 Tab 字串所代表的空格數 查看某個使用者的 Commit 歷史 倉庫複製 分支 將某個分支與其他所有分支進行比對 比較分支 比較不同派生庫的分支 Gists Git.io 鍵盤快捷鍵 整行高亮 用 Commit 訊息關閉 Issue 連接其他倉庫的 Issue 鎖定項目對話功能 設置 CI 對每條 Pull Request 都進行構建 Markdown 文件語法高亮 表情符號 圖片 / GIF 動畫 在 GitHub Wiki 中引用圖片 快速引用 複製貼上剪貼板中的圖片到評論 快速添加許可證文件 任務列表 Markdown 文件中的任務列表 相對連接 GitHub Pages 的中繼資料與插件支持 查看 YAML 格式的中繼資料 渲染表格數據 撤銷 Pull Request Diffs 可渲染文件的Diffs 可比較的地圖數據 在 Diff 中展開查看更多的上下文 獲取 Pull Request 的 diff 或 patch 文件 顯示圖片以及比較圖片 Hub 貢獻者指南 Octicons GitHub 資源 GitHub 相關演講視頻 Git 從工作區去除大量已刪除文件 上一個分支 去除空白 檢出 Pull Requests 沒有任何改動的提交 美化 Git Status 美化 Git Log Git 查詢 合併分支 修復有問題的提交以及自動合併 以網站方式查看本地倉庫 Git 設置 Git 命令自定義別名 自動更正 顏色輸出 Git 資源 Git 參考書籍 GitHub #不比較空白字串 #在任意 diff 頁面的 UR L後加上 ?w=1，可以去掉那些隻是空白字串的改動，使你能更專註於代碼改動。\n詳見 GitHub secrets.\n調整 Tab 字串所代表的空格數 #在 diff 或文件的 URL 後面加上 ?ts=4 ，這樣當顯示 tab 字串的長度時就會是 4 個空格的長度，不再是默認的 8 個空格。 ts 後面的數字還可以根據你個人的偏好進行修改。這個技巧不適用於 Gists，或者以 Raw 格式查看文件， 但有瀏覽器擴展插件可以幫你自動調整: Chrome 擴展 ， Opera 擴展。\n下面以一個 Go 語言原始碼為例，看看在 URL 裡添加 ?ts=4 參數的效果。添加前：\n\u0026hellip; 添加後的樣子：\n查看使用者的全部 Commit 歷史 #在 Commits 頁面 URL 後加上 ?author={user} 查看使用者全部的提交。\nhttps://github.com/rails/rails/commits/master?author=dhh 深入了解提交視圖之間的區別\n倉庫複製 #當複製倉庫時可以不要那個.git後綴。\n1 $ git clone https://github.com/tiimgreen/github-cheat-sheet 更多對 Git clone 命令的介紹.\n###分支 ####將某個分支與其他所有分支進行比對\n當你查看某個倉庫的分支（Branches）頁面（緊挨著 Commits 連接）時\nhttps://github.com/{user}/{repo}/branches 你會看到一個包含所有未合併的分支的列表。\n在這裡你可以訪問分支比較頁面或刪除某個分支。\n比較分支 #如果要在 GitHub 上直接比較兩個分支，可以使用如下形式的 URL ：\nhttps://github.com/{user}/{repo}/compare/{range} 其中 {range} = master...4-1-stable\n例如：\nhttps://github.com/rails/rails/compare/master...4-1-stable {range} 參數還可以使用下面的形式:\nhttps://github.com/rails/rails/compare/master@{1.day.ago}...master https://github.com/rails/rails/compare/master@{2014-10-04}...master 日期格式 YYYY-DD-MM\n在 diff 和 patch 頁面裡也可以比較分支：\nhttps://github.com/rails/rails/compare/master...4-1-stable.diff https://github.com/rails/rails/compare/master...4-1-stable.patch 了解更多關於基於時間的 Commit 比較.\n比較不同派生庫的分支 #想要對派生倉庫（Forked Repository）之間的分支進行比較，可以使用如下的 URL：\nhttps://github.com/user/repo/compare/{foreign-user}:{branch}...{own-branch} 例如：\nhttps://github.com/rails/rails/compare/byroot:master...master Gists #Gists 方便我們管理代碼片段，不必使用功能齊全的倉庫。\nGist 的 URL 後加上 .pibb（像這樣）可以得到便於嵌入到其他網站 的 HTML 代碼。\nGists 可以像任何標準倉庫一樣被複製。\n1 $ git clone https://gist.github.com/tiimgreen/10545817 這意味著你可以像 Github 倉庫一樣修改和更新 Gists :\n1 2 3 4 $ git commit $ git push Username for \u0026#39;https://gist.github.com\u0026#39;: Password for \u0026#39;https://tiimgreen@gist.github.com\u0026#39;: 但是， Gists 不支持目錄。所有文件都必須添加在倉庫的根目錄下。 進一步了解如何建立 Gists.\nGit.io #Git.io是 Github 的短網址服務。\n你可以通過 Curl 命令以普通 HTTP 協議使用它：\n1 2 3 4 5 6 7 $ curl -i http://git.io -F \u0026#34;url=https://github.com/...\u0026#34; HTTP/1.1 201 Created Location: http://git.io/abc123 $ curl -i http://git.io/abc123 HTTP/1.1 302 Found Location: https://github.com/... 進一步了解 Git.io.\n鍵盤快捷鍵 #在倉庫頁面上提供了快捷鍵方便快速導航。\n按 t 鍵打開一個文件瀏覽器。 按 w 鍵打開分支選擇菜單。 按 s 鍵聚焦游標到當前倉庫的搜索框。此時按刪除鍵就會從搜索當前倉庫切換到搜索整個 Github 網站。 按 l 鍵編輯 Issue 列表頁的標籤。 查看文件內容時（如：https://github.com/tiimgreen/github-cheat-sheet/blob/master/README.md），按 y 鍵將會凍結這個頁面，這樣就算代碼被修改了也不會影響你當前看到的。 按?查看當前頁面支持的快捷鍵列表：\n進一步了解可用的搜索語法.\n整行高亮 #在代碼文件地址 URL 後加上#L52或者單擊行號 52 都會將第 52 行代碼高亮顯示。\n多行高亮也可以，比如用#L53-L60選擇範圍，或者按住 shift 鍵，然後再點擊選擇的兩行。\nhttps://github.com/rails/rails/blob/master/activemodel/lib/active_model.rb#L53-L60 用 Commit 訊息關閉 Issue #如果某個提交修復了一個 Issue，當提交到 master 分支時，提交訊息裡可以使用 fix/fixes/fixed, close/closes/closed 或者 resolve/resolves/resolved 等關鍵詞，後面再跟上 Issue 號，這樣就會關閉這個 Issue 。\n1 $ git commit -m \u0026#34;Fix screwup, fixes #12\u0026#34; 這將會關閉 Issue #12，並且在 Issue 討論列表裡關聯引用這次提交。\n進一步了解通過提交訊息關閉 Issue.\n連接其他倉庫的 Issue #如果你想引用到同一個倉庫中的一個 Issue，隻需使用井號 # 加上 Issue 號，這樣就會自動建立到此 Issue 的連接。\n要連接到其他倉庫的 Issue ，就使用{user}/{repo}#ISSUE_NUMBER的方式，例如tiimgreen/toc#12。\n鎖定項目對話功能 #現在倉庫的管理員和合作者可以將 Pull Requests 和 Issue 的評論功能關閉。\n這樣，不是項目合作者的使用者就不能在這個項目上使用評論功能。\n進一步了解對話鎖定功能.\n設置 CI 對每條 Pull Request 都進行構建 #如果設置正確，Travis CI 會為每個你收到的 Pull Request 執行構建，就像每次提交也會觸發構建一樣。想了解更多關於 Travis CI 的訊息，請參考 Travis CI入門。\n進一步了解提交狀態 API.\nMarkdown 文件語法高亮 #例如，可以像下面這樣在你的 Markdown 文件裡為 Ruby 代碼添加語法高亮：\n```ruby require 'tabbit' table = Tabbit.new('Name', 'Email') table.add_row('Tim Green', 'tiimgreen@gmail.com') puts table.to_s ``` 效果如下：\n1 2 3 4 require \u0026#39;tabbit\u0026#39; table = Tabbit.new(\u0026#39;Name\u0026#39;, \u0026#39;Email\u0026#39;) table.add_row(\u0026#39;Tim Green\u0026#39;, \u0026#39;tiimgreen@gmail.com\u0026#39;) puts table.to_s Github使用 Linguist 做語言識別和語法高亮。你可以仔細閱讀 languages YAML file，了解有哪些可用的關鍵字。\n進一步了解 GitHub Flavored Markdown.\n表情符號 #可以在 Pull Requests, Issues, 提交消息, Markdown 文件裡加入表情符號。使用方法 :name_of_emoji:\n😄 將輸出一個笑臉：\n\u0026#x1f604;\nGithub 支持的完整表情符號號列表詳見emoji-cheat-sheet.com 或 scotch-io/All-Github-Emoji-Icons。\nGithub 上使用最多的5個表情符號號是：\n:shipit: ✨ :-1: 👍 👏 圖片 / GIF 動畫 #註解和README等文件裡也可以使用圖片和 GIF 動畫：\n![Alt Text](http://www.sheawong.com/wp-content/uploads/2013/08/keephatin.gif) 倉庫中的圖片可以被直接引用：\n![Alt Text](https://github.com/{user}/{repo}/raw/master/path/to/image.gif) 所有圖片都緩存在 Github，不用擔心你的網站不能訪問時就看不到圖片了。\n在 GitHub Wiki 中引用圖片 #有多種方法可以在 Wiki 頁面裡嵌入圖片。既可以像上一條裡那樣使用標準的 Markdown 語法，也可以像下面這樣指定圖片的高度或寬度：\n1 [[ http://www.sheawong.com/wp-content/uploads/2013/08/keephatin.gif | height = 100px ]] 結果：\n快速引用 #在主題評論中引用之前某個人所說的，只需選中文件，然後按 r 鍵，想要的就會以引用的形式複製到你的輸入框裡。\n進一步了解快速引用.\n複製貼上剪貼板中的圖片到評論 #（僅適用於 Chrome 瀏覽器）\n當截屏圖片複製到剪貼板後（mac 上用 cmd-ctrl-shift-4），你可以用(cmd-v / ctrl-v)把圖片複製貼上到評論框裡，然後它就會自動上傳到 Github。\n進一步了解在 issue 中使用附件\n快速添加許可證文件 #建立一個倉庫時，Github會為你提供一個預設的軟體授權條款：\n對於已有的倉庫，可以通過 web 界面建立文件來添加軟體授權條款。輸入LICENSE作為文件名後，同樣可以從預設的列表中選擇一個作為模板。\n這個技巧也適用於 .gitignore 文件。\n進一步了解開源許可證\n任務列表 #Issues 和 Pull requests 裡可以添加複選框，語法如下（註意空白符）：\n- [ ] Be awesome - [ ] Prepare dinner - [ ] Research recipe - [ ] Buy ingredients - [ ] Cook recipe - [ ] Sleep 當項目被選中時，它對應的 Markdown 原始碼也被更新了：\n- [x] Be awesome - [ ] Prepare dinner - [x] Research recipe - [x] Buy ingredients - [ ] Cook recipe - [ ] Sleep 進一步了解任務列表.\nMarkdown 文件中的任務列表 #在完全符合Markdown語法的文件中可以使用以下語法加入一個只讀的任務列表\n- [ ] Mercury - [x] Venus - [x] Earth - [x] Moon - [x] Mars - [ ] Deimos - [ ] Phobos Mercury Venus Earth Moon Mars Deimos Phobos 進一步了解 Markdown 文件中的任務列表\n相對連接 #Markdown文件裡連接到內部內容時推薦使用相對連接。\n1 2 [Link to a header](#awesome-section) [Link to a file](docs/readme) 絕對連接會在 URL 改變時（例如重新命名倉庫、使用者名稱改變，建立分支項目）被更新。使用相對連接能夠保證你的文件不受此影響。\n進一步了解相對連接.\nGitHub Pages 的中繼資料與插件支持 #在 Jekyll 頁面和文章裡，倉庫訊息可在 site.github 命名空間下找到，也可以顯示出來，例如，使用 {{ site.github.project_title }}顯示項目標題。\nJemoji 和 jekyll-mentions 插件為你的 Jekyll 文章和頁面增加了emoji和@mentions功能。\n了解更多 GitHub Pages 的中繼資料和插件支持.\n查看 YAML 格式的中繼資料 #許多博客網站，比如基於 Jekyll的GitHub Pages ，都依賴於一些文章頭部的 YAML 格式的中繼資料。 Github 會將其渲染成一個水平表格，方便閱讀。\n進一步了解 在文件裡查看 YAML 中繼資料.\n渲染表格數據 #GitHub 支持將 .csv (逗號分隔)和 .tsv (定位點分隔)格式的文件渲染成表格數據。\n進一步了解渲染表格數據.\n撤銷 Pull Request #合併一個 Pull Request 之後，你可能會反悔：要麼是這次 Pull Request 沒什麼用處，要麼是還不到合併的時候。\n此時可以通過 Pull Request 中的 Revert 按鈕來撤銷一個已合併的 Pull Request 中的 commit。按下按鈕後將自動生成一個進行逆操作的 Pull Request。\n*進一步了解「撤銷」按鈕\nDiffs #可渲染文件的Diffs #Commit 和 Pull Request 裡包含有 Github 支持的可渲染文件（比如 Markdown）會提供source 和 rendered 兩個視圖功能。\n點擊 \u0026ldquo;rendered\u0026rdquo; 按鈕，看看改動在渲染後的顯示效果。當你添加、刪除或修改文件時，渲染純文件視圖非常方便。\n進一步了解渲染純文件視圖Diffs.\n可比較的地圖數據 #當你在GitHub上查看一個包含地理數據的 commit 或 pull request時，Github 將以可視化的方式比對版本之間的差異。\n進一步了解可比較的地圖數據.\n在 Diff 中展開查看更多的上下文 #你可以通過點擊 diff 邊欄裡的 unfold 按鈕來多顯示幾行上下文。你可以一直點擊 unfold 按鈕直到顯示了文件的全部內容。這個功能在所有 GitHub 的 diff 功能中都可以使用。\n進一步了解展開 Diff 上下文.\n獲取 Pull Request 的 diff 或 patch 文件 #在 Pull Request 的 URL 後面加上 .diff 或 .patch 的擴展名就可以得到它的 diff 或 patch 文件，例如：\nhttps://github.com/tiimgreen/github-cheat-sheet/pull/15 https://github.com/tiimgreen/github-cheat-sheet/pull/15.diff https://github.com/tiimgreen/github-cheat-sheet/pull/15.patch .diff 擴展會使用普通文件格式顯示如下內容：\ndiff --git a/README.md b/README.md index 88fcf69..8614873 100644 --- a/README.md +++ b/README.md @@ -28,6 +28,7 @@ All the hidden and not hidden features of Git and GitHub. This cheat sheet was i - [Merged Branches](#merged-branches) - [Quick Licensing](#quick-licensing) - [TODO Lists](#todo-lists) +- [Relative Links](#relative-links) - [.gitconfig Recommendations](#gitconfig-recommendations) - [Aliases](#aliases) - [Auto-correct](#auto-correct) @@ -381,6 +382,19 @@ When they are clicked, they will be updated in the pure Markdown: - [ ] Sleep (...) 顯示圖片以及比較圖片 #GitHub 可以顯示包括 PNG、JPG、GIF、PSD 在內的多種圖片格式並提供了幾種方式來比較這些格式的圖片文件版本間的不同。\n查看更多關於圖片顯示和比較\nHub #Hub 是一個對 Git 進行了封裝的命令行工具，可以幫助你更方便的使用 Github。\n例如可以像下面這樣進行複製：\n1 $ hub clone tiimgreen/toc 查看更多 Hub 提供的超酷命令.\n貢獻者指南 #在倉庫的根目錄添加一個名為 CONTRIBUTING 的文件後，貢獻者在新建 Issue 或 Pull Request 時會看到一個指向這個文件的連接。\n進一步了解貢獻者指南.\nOcticons #GitHubs 圖標庫 (Octicons) 現已開源。\n進一步了解 GitHub 圖標庫\nGitHub 資源 # 內容 連接 探索 GitHub https://github.com/explore GitHub 博客 https://github.com/blog GitHub 幫助 https://help.github.com/ GitHub 培訓 http://training.github.com/ GitHub 開發者 https://developer.github.com/ GitHub 相關演講視頻 # 內容 連接 How GitHub Uses GitHub to Build GitHub https://www.youtube.com/watch?v=qyz3jkOBbQY Introduction to Git with Scott Chacon of GitHub https://www.youtube.com/watch?v=ZDR433b0HJY How GitHub No Longer Works https://www.youtube.com/watch?v=gXD1ITW7iZI Git and GitHub Secrets https://www.youtube.com/watch?v=Foz9yvMkvlA More Git and GitHub Secrets https://www.youtube.com/watch?v=p50xsL-iVgU Git #從工作區去除大量已刪除文件 #當用 /bin/rm 命令刪除了大量文件之後，你可以用下面一條命令從工作區和索引中去除這些文件，以免一個一個的刪除：\n1 $ git rm $(git ls-files -d) 例如:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 $ git status On branch master Changes not staged for commit: deleted: a deleted: c $ git rm $(git ls-files -d) rm \u0026#39;a\u0026#39; rm \u0026#39;c\u0026#39; $ git status On branch master Changes to be committed: deleted: a deleted: c 上一個分支 #快速檢出上一個分支：\n1 2 3 4 5 6 7 8 $ git checkout - # Switched to branch \u0026#39;master\u0026#39; $ git checkout - # Switched to branch \u0026#39;next\u0026#39; $ git checkout - # Switched to branch \u0026#39;master\u0026#39; 進一步了解 Git 分支.\n去除空白 #Git Stripspace 命令可以:\n去掉行尾空白符 多個空行壓縮成一行 必要時在文件末尾增加一個空行 使用此命令時必須傳入一個文件，像這樣：\n1 $ git stripspace \u0026lt; README.md 進一步了解 Git stripspace 命令.\n檢出 Pull Requests #對 Github 倉庫來說，Pull Request 是種特殊分支， 可以通過以下多種方式取到本地：\n取出某個特定的 Pull Request 並臨時作為本地的 FETCH_HEAD 中以便進行快速查看更改( diff )以及合併( merge )：\n1 $ git fetch origin refs/pull/[PR-Number]/head 通過 refspec 獲取所有的 Pull Request 為本地分支：\n1 $ git fetch origin \u0026#39;+refs/pull/*/head:refs/remotes/origin/pr/*\u0026#39; 或在倉庫的 .git/config 中加入下列設置來自動獲取遠程倉庫中的 Pull Request\n[remote \u0026#34;origin\u0026#34;] fetch = +refs/heads/*:refs/remotes/origin/* url = git@github.com:tiimgreen/github-cheat-sheet.git [remote \u0026#34;origin\u0026#34;] fetch = +refs/heads/*:refs/remotes/origin/* url = git@github.com:tiimgreen/github-cheat-sheet.git fetch = +refs/pull/*/head:refs/remotes/origin/pr/* 對基於派生庫的 Pull Request，可以通過先 checkout 代表此 Pull Request 的遠端分支再由此分支建立一個本地分支：\n1 $ git checkout pr/42 pr-42 操作多個倉庫的時候，可以在 Git 中設置獲取 Pull Request 的全局選項。\n1 git config --global --add remote.origin.fetch \u0026#34;+refs/pull/*/head:refs/remotes/origin/pr/*\u0026#34; 此時可以在任意倉庫中使用以下命令：\n1 git fetch origin 1 git checkout pr/42 進一步了解如何本地檢出 pull request.\n沒有任何改動的提交 #可以使用--allow-empty選項強製建立一個沒有任何改動的提交：\n1 $ git commit -m \u0026#34;Big-ass commit\u0026#34; --allow-empty 這樣做在如下幾種情況下是有意義的：\n標記新的工作或一個新功能的開始。 記錄對項目的跟代碼無關的改動。 跟使用你倉庫的其他人交流。 作為倉庫的第一次提交，因為第一次提交後不能被 rebase： git commit -m \u0026quot;init repo\u0026quot; --allow-empty. 美化 Git Status #在命令行輸入如下命令:\n1 $ git status 可以看到:\n加上-sb選項:\n1 $ git status -sb 這會得到:\n進一步了解 Git status 命令.\n美化 Git Log #輸入如下命令:\n1 $ git log --all --graph --pretty=format:\u0026#39;%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cr) %C(bold blue)\u0026lt;%an\u0026gt;%Creset\u0026#39; --abbrev-commit --date=relative 可以看到:\n這要歸功於 Palesz 在 stackoverflow 的回答。\n這個命令可以被用作別名，詳細做法見這裡。\n進一步了解 Git log 命令.\nGit 查詢 #Git 查詢運行你在之前的所有提交訊息裡進行搜索，找到其中和搜索條件相匹配的最近的一條。\n1 $ git show :/query 這裡 query （區別大小寫）是你想要搜索的詞語， 這條命令會找到包含這個詞語的最後那個提交並顯示變動詳情。\n1 $ git show :/typo 按 q 鍵退出命令。* 合併分支 #輸入命令:\n1 $ git branch --merged 這會顯示所有已經合併到你當前分支的分支列表。\n相反地：\n1 $ git branch --no-merged 會顯示所有還沒有合併到你當前分支的分支列表。\n進一步了解 Git branch 命令.\n修復有問題的提交以及自動合併 #如果上一個或多個提交包含了錯誤，可以在你修復問題後使用下列命令處理（假設要修復的提交版本是abcde）：\n1 2 $ git commit --fixup=abcde $ git rebase abcde^ --autosquash -i 進一步了解 Git commit 命令. 進一步了解 Git rebase 命令.\n以網站方式查看本地倉庫 #使用 Git 的 instaweb 可以立即在 gitweb 中瀏覽你的工作倉庫。這個命令是個簡單的腳本，設置了 gitweb 和用來瀏覽本地倉庫的Web服務器。（譯者註：默認需要lighttpd支持）\n1 $ git instaweb 執行後打開：\n進一步了解 Git instaweb 命令.\nGit 設置 #所有 Git 設置都保存在你的 .gitconfig 文件中。\nGit 命令自定義別名 #別名用來幫助你定義自己的 git 命令。比如你可以定義 git a 來運行 git add --all。\n要添加一個別名， 一種方法是打開 ~/.gitconfig 文件並添加如下內容：\n[alias] co = checkout cm = commit p = push # Show verbose output about tags, branches or remotes tags = tag -l branches = branch -a remotes = remote -v \u0026hellip;或者在命令行裡鍵入：\n1 $ git config --global alias.new_alias git_function 例如：\n1 $ git config --global alias.cm commit 指向多個命令的別名可以用引號來定義：\n1 $ git config --global alias.ac \u0026#39;add -A . \u0026amp;\u0026amp; commit\u0026#39; 下面列出了一些有用的別名：\n別名 Alias 命令 Command 如何設置 What to Type git cm git commit git config --global alias.cm commit git co git checkout git config --global alias.co checkout git ac git add . -A git commit git config --global alias.ac '!git add -A \u0026amp;\u0026amp; git commit' git st git status -sb git config --global alias.st 'status -sb' git tags git tag -l git config --global alias.tags 'tag -l' git branches git branch -a git config --global alias.branches 'branch -a' git cleanup `git branch \u0026ndash;merged grep -v \u0026lsquo;*\u0026rsquo; git remotes git remote -v git config --global alias.remotes 'remote -v' git lg git log --color --graph --pretty=format:'%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cr) %C(bold blue)\u0026lt;%an\u0026gt;%Creset' --abbrev-commit -- git config --global alias.lg \u0026quot;log --color --graph --pretty=format:'%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cr) %C(bold blue)\u0026lt;%an\u0026gt;%Creset' --abbrev-commit --\u0026quot; 自動更正 #如果鍵入 git comit 你會看到如下輸出：\n1 2 3 4 5 $ git comit -m \u0026#34;Message\u0026#34; # git: \u0026#39;comit\u0026#39; is not a git command. See \u0026#39;git --help\u0026#39;. # Did you mean this? # commit 為了在鍵入 comit 調用 commit命令，隻需啟用自動糾錯功能：\n1 $ git config --global help.autocorrect 1 現在你就會看到：\n1 2 3 4 $ git comit -m \u0026#34;Message\u0026#34; # WARNING: You called a Git command named \u0026#39;comit\u0026#39;, which does not exist. # Continuing under the assumption that you meant \u0026#39;commit\u0026#39; # in 0.1 seconds automatically... 顏色輸出 #要在你的 Git 命令輸出裡加上顏色的話，可以用如下命令：\n1 $ git config --global color.ui 1 進一步了解 Git config 命令.\nGit 資源 # Title Link Official Git Site http://git-scm.com/ Official Git Video Tutorials http://git-scm.com/videos Code School Try Git http://try.github.com/ Introductory Reference \u0026amp; Tutorial for Git http://gitref.org/ Official Git Tutorial http://git-scm.com/docs/gittutorial Everyday Git http://git-scm.com/docs/everyday Git Immersion http://gitimmersion.com/ Ry\u0026rsquo;s Git Tutorial http://rypress.com/tutorials/git/index.html Git for Designer http://hoth.entp.com/output/git_for_designers.html Git for Computer Scientists http://eagain.net/articles/git-for-computer-scientists/ Git Magic http://www-cs-students.stanford.edu/~blynn/gitmagic/ GitHub Training Kit http://training.github.com/kit Git Visualization Playground http://onlywei.github.io/explain-git-with-d3/#freeplay Git 參考書籍 # Title Link Pragmatic Version Control Using Git http://www.pragprog.com/titles/tsgit/pragmatic-version-control-using-git Pro Git http://git-scm.com/book Git Internals Peepcode http://peepcode.com/products/git-internals-pdf Git in the Trenches http://cbx33.github.com/gitt/ Version Control with Git http://www.amazon.com/Version-Control-Git-collaborative-development/dp/1449316387 Pragmatic Guide to Git http://www.pragprog.com/titles/pg_git/pragmatic-guide-to-git Git: Version Control for Everyone http://www.packtpub.com/git-version-control-for-everyone/book ","date":"2015年4月20日","permalink":"https://metavige.github.io/2015/04/20/github-chest-sheet/","section":"文章","summary":"\u003cp\u003e這是我今天幫忙 \u003ca href=\"\"\u003ehttps://github.com/tiimgreen/github-cheat-sheet\u003c/a\u003e 做一個簡單繁體中文版本的 PR，其實只是把簡體中文版本稍微轉換一下而已～\u003c/p\u003e\n\u003cp\u003e不過其實這篇蠻有意思的，很多值得參考～\u003c/p\u003e","title":"Github Cheat Sheet"},{"content":"Übersicht，如果你用過 Geektool，你應該對這種工具不陌生。\n這是一個 Mac 用來美化桌面，以及加強顯示資訊的一種工具。\n我覺得他跟 Geektool 最大的不同點就是\n他用 CSS + CoffeeScript，所以任何會寫網頁以及使用 CSS 的人都可以使用\n超級方便！！！\n我剛剛就下載來玩了～現在我的桌面長這樣 \u0026ndash;\u0026gt;\n有興趣的人可以到 官網 下載，或用 brew cask install ubersicht 就可以安裝了\n","date":"2015年4月20日","permalink":"https://metavige.github.io/2015/04/20/uebersicht/","section":"文章","summary":"\u003cp\u003e\u003ca href=\"http://tracesof.net/uebersicht/\" target=\"_blank\" rel=\"noreferrer\"\u003eÜbersicht\u003c/a\u003e，如果你用過 \u003ca href=\"http://projects.tynsoe.org/en/geektool/\" target=\"_blank\" rel=\"noreferrer\"\u003eGeektool\u003c/a\u003e，你應該對這種工具不陌生。\u003c/p\u003e","title":"Übersicht"},{"content":"上個月底的新聞：\nDocker 收購新創公司 Kitematic：讓 Mac 電腦輕鬆部署容器\n官方網站: Kitematic\n會幫你裝 Virtualbox，下載 boot2docker.iso，設定一個 dev 的 docker-machine~\n然後直接可以用！\n只要透過他的介面搜尋你要的 docker image name，就可以直接使用～\n我其實對這個 Kitematic 有興趣是因為，他是用 Atom-Shell 跟 Reactjs 做出來的！！！\n所以 Source Code 應該是很有研究價值的～\nGithub: https://github.com/kitematic/kitematic\n","date":"2015年4月12日","permalink":"https://metavige.github.io/2015/04/12/kitematic/","section":"文章","summary":"\u003cp\u003e上個月底的新聞：\u003cbr\u003e\n\u003ca href=\"http://www.ithome.com.tw/news/94527\" target=\"_blank\" rel=\"noreferrer\"\u003eDocker 收購新創公司 Kitematic：讓 Mac 電腦輕鬆部署容器\u003c/a\u003e\u003c/p\u003e","title":"kitematic"},{"content":"","date":null,"permalink":"https://metavige.github.io/tags/AWS/","section":"Tags","summary":"","title":"AWS"},{"content":"前兩天在公司上了 Introduction Docker 的課程，回家就來試驗新的 docker-machine\n花了一點時間，研究了怎樣連接到 AWS 去～\n如果你打 docker-machine create 這個指令，大概就會出現以下的資料，告訴你要打的參數\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 NAME: create - Create a machine USAGE: command create [command options] [arguments...] OPTIONS: --amazonec2-access-key AWS Access Key [$AWS_ACCESS_KEY_ID] --amazonec2-ami AWS machine image [$AWS_AMI] --amazonec2-iam-instance-profile AWS IAM Instance Profile --amazonec2-instance-type \u0026#34;t2.micro\u0026#34;\tAWS instance type [$AWS_INSTANCE_TYPE] --amazonec2-region \u0026#34;us-east-1\u0026#34;\tAWS region [$AWS_DEFAULT_REGION] --amazonec2-root-size \u0026#34;16\u0026#34;\tAWS root disk size (in GB) [$AWS_ROOT_SIZE] --amazonec2-secret-key AWS Secret Key [$AWS_SECRET_ACCESS_KEY] --amazonec2-security-group \u0026#34;docker-machine\u0026#34;\tAWS VPC security group [$AWS_SECURITY_GROUP] --amazonec2-session-token AWS Session Token [$AWS_SESSION_TOKEN] --amazonec2-subnet-id AWS VPC subnet id [$AWS_SUBNET_ID] --amazonec2-vpc-id AWS VPC id [$AWS_VPC_ID] --amazonec2-zone \u0026#34;a\u0026#34;\tAWS zone for instance (i.e. a,b,c,d,e) [$AWS_ZONE] 最前面的，就是 aws 所需要的參數。\n我找了一下網路的文章，發現這篇是有比較多的說明，針對 docker-machine provider 的部份\nhttp://flurdy.com/docs/docker/docker_compose_machine_swarm_cloud.html\n按照這篇文章的作法，大概只需要以下的參數就可以\n1 2 3 4 5 6 7 8 9 docker-machine create \\ --driver amazonec2 \\ --amazonec2-access-key your-aws-access-key \\ --amazonec2-secret-key your-aws-secret-key \\ --amazonec2-vpc-id your-aws-vpc-id \\ --amazonec2-subnet-id your-aws-subnet-id \\ --amazonec2-region us-east-1 \\ --amazonec2-zone a \\ ec2box 不過因為我是新手，所以嘗試了蠻多次才找到這些參數到哪邊去做\nAccess Key/Secret Key #這部份我嘗試最久，因為我其實搞不太懂這是要到哪裡去抓，後來才知道這是要到 IAM 去做的。\nCreate a group #進到 IAM 之後，先建立一個 Group，步驟很簡單，只要輸入名稱以及選擇 Policy 就好了\n名稱隨便 (我是叫 docker-user)\n然後記得加入 Policy，我是加入 AmazonEC2FullAccess (也可以給他 Administrator)\n建立之後，Group 的資料大概像這樣: Create a user #接下來建立一個 User， 你一次可以建立五個 User，不過我是先建立一個，接下來的畫面，蠻重要的，他會給你這個 User 的 Security Credentials，這就是我們要的資料了！\nAccess Key ID 對應的就是 \u0026ndash;amazonec2-access-key 參數\nSecret Access Key 對應的就是 \u0026ndash;amazonec2-secret-key 參數\n這邊你可以點選 \u0026ldquo;Show User Security Credentials\u0026rdquo; 來顯示 Access Key ID 以及 Security Access Key，或者是點選畫面下方的 \u0026ldquo;Download Credentials\u0026rdquo; 把檔案下載下來再看都可以\n建立完 User 之後，記得把這個 User 加入到上面我們建立的 Group 去，之後才有權限可以使用 EC2\nGenerate AWS Signing Certificates #這個步驟也蠻重要的，因為如果你不做這個步驟，其實你建立的 User 是不能呼叫 EC2 的 Web Service 的 (我碰壁了很久\u0026hellip;..)\n我參考了這篇文章\nhttp://www.markbartel.ca/2012/04/creating-key-pairs-for-amazon-ec2.html\n先執行\n1 openssl req -x509 -newkey rsa:2048 -passout pass:a -keyout kx -out cert 這個步驟是在建立 ssl 證書資料，會需要你填一些東西\n1 2 3 4 5 6 7 Country Name (2 letter code) [AU]: [這邊我是填 TW] State or Province Name (full name) [Some-State]: [這邊我是填 Taiwan] Locality Name (eg, city) []: [這邊我是填 Taipei] Organization Name (eg, company) [Internet Widgits Pty Ltd]: Organizational Unit Name (eg, section) []: Common Name (e.g. server FQDN or YOUR name) []: Email Address []: [這邊填電子郵件] 沒填的地方我是直接 Enter 就過去了～\n這樣會產生 kx, cert 檔案\n然後執行以下步驟\n1 openssl rsa -passin pass:a -in kx -out key 這個步驟會產生 key 檔案\n不過我們主要是要用到 cert 這個檔案 (目前我還不知道其他檔案的用途，不過還是建議先保留下來～)\n選擇剛剛建立的 User，選擇 Manage Security Credentials 功能，準備上傳 cert 資料\n進去功能的第一個畫面是一些說明，你可以直接到下一步驟，把 cert 檔案內容貼到下一個畫面的 Certificate Body 格子內，然後按下 \u0026ldquo;Upload Signing Certificate\u0026rdquo; 按鈕就好 \u0026hellip;\n如果一切都順利完成，User 的資料就應該像是下面這樣，\n這樣，你就可以往下面做了～\nVpc-ID #到 EC2 的介面，去抓到 default security group 的 vpc-id\n這就是我們要用的 \u0026ndash;amazonec2-vpc-id\nSubnet-ID #到 EC2 的介面，到 Network Interfaces 功能，建立一組新的 Network Interface。\n(因為蠻簡單的，都用預設值就可以)\n然後，找到 subnet-id\n這就是 \u0026ndash;amazonec2-subnet-id 參數\n2014-04-12 14:50 更正： 根據 Docker 官網文件，以及實際測試結果，其實，subnet-id 並不是必要條件，所以，其實可以不要輸入～這也就是我為什麼之前有個疑問是，我自己建立了一個 Network Interface，他卻又會另外建立一個新的 Network Interface 了～我本來以為 subnet-id 是要我提供的～\nCreate a new docker-machine #把之前的步驟找到的所有參數，組合在一起\n至於 \u0026ndash;amazonec2-region ，你可以到 Regions and Avaliable Zones 文件中自己去挑選你喜歡的地方\n而 \u0026ndash;amazonec2-zone，你是要進入到該 region 的 EC2 Dashboard 介面去看他 Service Health 的部份，會告訴你有哪些 Available Zones\n像我是挑 Singapore，就是 ap-southeast-1\nZone 的話，我是挑了 b zone\n就像這樣～\n1 2 3 4 5 6 7 8 docker-machine create \\ --driver amazonec2 \\ --amazonec2-access-key [Access Key ID] \\ --amazonec2-secret-key [Secret Access Key] \\ --amazonec2-vpc-id [Default SecurityGroup VPC ID] \\ --amazonec2-region ap-southeast-1 \\ --amazonec2-zone b \\ ec2box 最後面的 ec2box 是你要給 docker-machine 的名稱，如果一切都 ok，做完之後你在 EC2 內就會看到一組建立好的 t2.micro instance。\n玩完回收 # 1 docker-machine rm -f ec2box 這個指令會把之前建立的 ec2 instance 以及相關的設定都刪掉 因為 AWS 要錢，所以～如果你只是玩玩就好，記得刪掉～\n否則收到帳單，我也沒辦法\u0026hellip;..\n建立時可能發生的問題 #如果建立時，發生任何問題，沒有建立成功，你可能都要注意以下事情\n有可能 docker-machine 資料已經建立，但是無法連上 EC2，這樣的話，你需要自己手動刪除 docker-machine，指令是 docker-machine rm -f ec2box。你可以先用 docker-machine ls 檢查一下～ 每次建立的時候，docker-machine 會與 EC2 交換 key-pair，所以如果建立到一半失敗，你得先自己手動到 EC2 把 ec2box 這個 keypair 刪掉再重新建立，不然就會發生 keypair 重複的問題。 如果發生 401 的錯誤，請檢查使用者的資料設定是否有問題 (是說按照步驟來應該可以)。 你給的 vpc-id 一定要是你建立那個 region/zone 的 default security group vpc-id，不然會告訴你沒有這個 vpc-id 參考文件 # https://docs.docker.com/machine/?hc_location=ufi#amazon-web-services ","date":"2015年4月12日","permalink":"https://metavige.github.io/2015/04/12/docker-machine-aws-provider/","section":"文章","summary":"\u003cp\u003e前兩天在公司上了 Introduction Docker 的課程，回家就來試驗新的 docker-machine\u003cbr\u003e\n花了一點時間，研究了怎樣連接到 \u003ca href=\"https://aws.amazon.com/tw/\" target=\"_blank\" rel=\"noreferrer\"\u003eAWS\u003c/a\u003e 去～\u003c/p\u003e","title":"docker-machine for AmazonEC2"},{"content":"","date":null,"permalink":"https://metavige.github.io/tags/vagrant/","section":"Tags","summary":"","title":"Vagrant"},{"content":"簡單的在 Mac 安裝 Vagrant 環境\n請先安裝 Homebrew - Mac 必裝工具！ 用 Homebrew 安裝 Cask - brew install caskroom/cask/brew-cask 用 Cask 安裝 Vagrant - sudo brew cask install vagrant 搞定\u0026hellip;.\n","date":"2015年4月11日","permalink":"https://metavige.github.io/2015/04/11/vagrant-install-on-mac/","section":"文章","summary":"\u003cp\u003e簡單的在 Mac 安裝 \u003ca href=\"https://www.vagrantup.com/\" target=\"_blank\" rel=\"noreferrer\"\u003eVagrant\u003c/a\u003e 環境\u003c/p\u003e","title":"安裝 Vagrant 在 Mac 上"},{"content":"","date":null,"permalink":"https://metavige.github.io/tags/csharp/","section":"Tags","summary":"","title":"csharp"},{"content":"為了單元測試，需要作假資料，如果用資料庫來做，就需要考慮測試資料的建立以及 Transaction 的問題，因為建立測試資料後還得刪除。\n所以可以使用 Mock 的方式，把 IDbSet 改成使用 InMemory\n目前 Nuget.org 就有一個套件，可以幫忙你實作 InMemorySet 我目前是安裝 AnotherFakeDbSet，Source 在 Github-AnotherFakeDbSet\n這個 Source 的原始版本是 FakeDbSet 不過我也覺得 AnotherFakeDbSet 改的不錯，而且因為陸續有推出新版本，所以我自己也是採用這個 Nuget Package\n設定 DbContext 的方式，是可以參考他 Source 裡面的 Examples 專案 (所以這邊就不把程式碼貼出來了，可以到 AnotherFakeDbSet 去下載)\n在自己程式裡面，要使用 DbContext 的時候，就不能像是以下的程式一樣直接使用，因為這樣就無法測試了\n1 2 3 using(var context = new BookStoreEntities()) { // Code.... } 我自己的方式，是採用類似 Provider 的概念來做。\n這樣子，你如果測試的時候要抽換就會變得很容易 （當然，如果你要使用類似 IoC 的 framework 實作也 OK~）\n","date":"2015年3月25日","permalink":"https://metavige.github.io/2015/03/25/entityframework-fakedbset/","section":"文章","summary":"\u003cp\u003e為了單元測試，需要作假資料，如果用資料庫來做，就需要考慮測試資料的建立以及 Transaction 的問題，因為建立測試資料後還得刪除。\u003c/p\u003e\n\u003cp\u003e所以可以使用 Mock 的方式，把 \u003cem\u003eIDbSet\u003c/em\u003e 改成使用 InMemory\u003c/p\u003e\n\u003cp\u003e目前 \u003ca href=\"\"\u003eNuget.org\u003c/a\u003e 就有一個套件，可以幫忙你實作 \u003cem\u003eInMemorySet\u003c/em\u003e\n我目前是安裝 \u003ca href=\"https://www.nuget.org/packages/AnotherFakeDbSet/\" target=\"_blank\" rel=\"noreferrer\"\u003eAnotherFakeDbSet\u003c/a\u003e，Source 在 \u003ca href=\"https://github.com/realistschuckle/FakeDbSet\" target=\"_blank\" rel=\"noreferrer\"\u003eGithub-AnotherFakeDbSet\u003c/a\u003e\u003c/p\u003e\n\u003cp\u003e這個 Source 的原始版本是 \u003ca href=\"https://github.com/a-h/FakeDbSet\" target=\"_blank\" rel=\"noreferrer\"\u003eFakeDbSet\u003c/a\u003e\n不過我也覺得 \u003ca href=\"https://www.nuget.org/packages/AnotherFakeDbSet/\" target=\"_blank\" rel=\"noreferrer\"\u003eAnotherFakeDbSet\u003c/a\u003e 改的不錯，而且因為陸續有推出新版本，所以我自己也是採用這個 Nuget Package\u003c/p\u003e","title":"Entity Framework FakeDbSet"},{"content":"","date":null,"permalink":"https://metavige.github.io/tags/EntityFramework/","section":"Tags","summary":"","title":"EntityFramework"},{"content":"","date":null,"permalink":"https://metavige.github.io/tags/log4net/","section":"Tags","summary":"","title":"Log4net"},{"content":"我之前設定網站執行的 Action Log 的一個記錄，有些東西都是拼拼湊湊出來的，並不見得是最佳版本\nlog4net 設定檔\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 \u0026lt;?xml version=\u0026#34;1.0\u0026#34; encoding=\u0026#34;utf-8\u0026#34; ?\u0026gt; \u0026lt;log4net\u0026gt; \u0026lt;root\u0026gt; \u0026lt;level value=\u0026#34;DEBUG\u0026#34; /\u0026gt; \u0026lt;appender-ref ref=\u0026#34;stdout\u0026#34; /\u0026gt; \u0026lt;appender-ref ref=\u0026#34;ActionLogRollingFile\u0026#34; /\u0026gt; \u0026lt;/root\u0026gt; \u0026lt;appender name=\u0026#34;ActionLogRollingFile\u0026#34; type=\u0026#34;log4net.Appender.RollingFileAppender\u0026#34;\u0026gt; \u0026lt;file type=\u0026#34;log4net.Util.PatternString\u0026#34; value=\u0026#34;action.log\u0026#34; /\u0026gt; \u0026lt;appendToFile value=\u0026#34;true\u0026#34; /\u0026gt; \u0026lt;encoding value=\u0026#34;utf-8\u0026#34; /\u0026gt; \u0026lt;maxSizeRollBackups value=\u0026#34;30\u0026#34; /\u0026gt; \u0026lt;rollingStyle value=\u0026#34;Date\u0026#34; /\u0026gt; \u0026lt;datePattern value=\u0026#34;\u0026#39;.\u0026#39;yyyy-MM-dd\u0026#34; /\u0026gt; \u0026lt;layout type=\u0026#34;log4net.Layout.PatternLayout\u0026#34;\u0026gt; \u0026lt;conversionPattern value=\u0026#34;(%date{HH:mm:ss,fff}) [%t] - %u %5property{Duration} %property{StatusCode} %6property{Method} %property{Uri} %m%n \u0026#34; /\u0026gt; \u0026lt;/layout\u0026gt; \u0026lt;filter type=\u0026#34;log4net.Filter.LoggerMatchFilter\u0026#34;\u0026gt; \u0026lt;acceptOnMatch value=\u0026#34;true\u0026#34; /\u0026gt; \u0026lt;LoggerToMatch value=\u0026#34;Sample.Web.Filters.WebApiActionFilter\u0026#34; /\u0026gt; \u0026lt;/filter\u0026gt; \u0026lt;filter type=\u0026#34;log4net.Filter.DenyAllFilter\u0026#34; /\u0026gt; \u0026lt;/appender\u0026gt; \u0026lt;/log4net\u0026gt; 其中要注意的是\nlog4net.Filter.LoggerMatchFilter: 用來設定特定的 class 記錄才使用這個 Appender acceptOnMatch: 這個設定有點像是 blacklist/whitelist，true 表示有 match 才是，相當於 whitelist LoggerToMatch: 設定要篩選的類別名稱 log4net.Filter.DenyAllFilter: 篩選掉其他的 Log，不會在這個 Appender 裡面記錄 C# Filter 程式參考\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 /// \u0026lt;summary\u0026gt; /// 執行 Web Api 的呼叫記錄 /// \u0026lt;/summary\u0026gt; /// \u0026lt;remarks\u0026gt; /// 紀錄資料: /// /// 呼叫時間 /// USER /// URL /// METHOD /// BODY /// StatusCode /// Duration /// /// Logger 的紀錄格式，參考 https://logging.apache.org/log4net/release/sdk/log4net.Layout.PatternLayout.html /// \u0026lt;/remarks\u0026gt; public class WebApiActionFilter : ActionFilterAttribute { private ILog Logger { get; set; } public WebApiActionFilter() { Logger = LogManager.GetLog(GetType()); } public WebApiActionFilter(ILog logger) { Logger = logger; } private ThreadLocal\u0026lt;Stopwatch\u0026gt; counter = new ThreadLocal\u0026lt;Stopwatch\u0026gt;(() =\u0026gt; new Stopwatch()); public static ThreadLocal\u0026lt;Task\u0026lt;string\u0026gt;\u0026gt; RequestBody = new ThreadLocal\u0026lt;Task\u0026lt;string\u0026gt;\u0026gt;(); public override void OnActionExecuting(HttpActionContext actionContext) { RequestBody.Value = actionContext.Request.Content.ReadAsStringAsync(); counter.Value.Start(); } public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext) { counter.Value.Stop(); LogActivity(actionExecutedContext.ActionContext, actionExecutedContext.Response); } private void LogActivity(HttpActionContext actionContext, HttpResponseMessage response) { try { var loggerProp = ThreadContext.Properties; loggerProp[\u0026#34;Uri\u0026#34;] = actionContext.Request.RequestUri.PathAndQuery; loggerProp[\u0026#34;Method\u0026#34;] = actionContext.Request.Method.Method; loggerProp[\u0026#34;Duration\u0026#34;] = counter.Value.ElapsedMilliseconds; loggerProp[\u0026#34;StatusCode\u0026#34;] = (int) response.StatusCode; Logger.Info(RequestBody.Value.Result); } catch (Exception e) { Console.WriteLine(e); } } } /// \u0026lt;summary\u0026gt; /// 用來擷取傳入的 Request Content Body, 因為沒辦法在 Filter 中取得 Body /// \u0026lt;/summary\u0026gt; /// \u0026lt;remark\u0026gt; /// 如果需要抓傳遞資料才需要 /// \u0026lt;/remark\u0026gt; public class LogBodyDelegateHandler : DelegatingHandler { protected async override Task\u0026lt;HttpResponseMessage\u0026gt; SendAsync(HttpRequestMessage request, System.Threading.CancellationToken cancellationToken) { if (request.Content != null) { var requestText = request.Content.ReadAsStringAsync(); WebApiActionFilter.RequestBody.Value = requestText; } // Execute the request, this does not block var response = base.SendAsync(request, cancellationToken); return response.Result; } } 另外從網路上有查到一篇不錯的文章，http://it.tomtang.idv.tw/2013/12/log4net-best-practice.html\n這篇是在說明 log4net 的一些設定建議\n以下是他一個常用設定值的參考\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 \u0026lt;?xml version=\u0026#34;1.0\u0026#34; encoding=\u0026#34;UTF-8\u0026#34;?\u0026gt; \u0026lt;log4net\u0026gt; \u0026lt;appender name=\u0026#34;ErrorFile\u0026#34; type=\u0026#34;log4net.Appender.RollingFileAppender\u0026#34;\u0026gt; \u0026lt;file type=\u0026#34;log4net.Util.PatternString\u0026#34; value=\u0026#34;C:\\applications\\logs\\%property{appname}\\Error-%property{appname}.log\u0026#34; /\u0026gt; \u0026lt;encoding value=\u0026#34;utf-8\u0026#34; /\u0026gt; \u0026lt;appendToFile value=\u0026#34;true\u0026#34; /\u0026gt; \u0026lt;maximumFileSize value=\u0026#34;10000KB\u0026#34; /\u0026gt; \u0026lt;maxSizeRollBackups value=\u0026#34;50\u0026#34; /\u0026gt; \u0026lt;lockingModel type=\u0026#34;log4net.Appender.FileAppender+MinimalLock\u0026#34; /\u0026gt; \u0026lt;layout type=\u0026#34;log4net.Layout.PatternLayout\u0026#34;\u0026gt; \u0026lt;conversionPattern value=\u0026#34;%date\u0026amp;#x9;[%thread]\u0026amp;#x9;%level\u0026amp;#x9;%logger\u0026amp;#x9;%property{appname}\u0026amp;#x9;- %message%newline\u0026#34; /\u0026gt; \u0026lt;/layout\u0026gt; \u0026lt;filter type=\u0026#34;log4net.Filter.LevelRangeFilter\u0026#34;\u0026gt; \u0026lt;levelMin value=\u0026#34;ERROR\u0026#34; /\u0026gt; \u0026lt;levelMax value=\u0026#34;FATAL\u0026#34; /\u0026gt; \u0026lt;/filter\u0026gt; \u0026lt;filter type=\u0026#34;log4net.Filter.DenyAllFilter\u0026#34; /\u0026gt; \u0026lt;/appender\u0026gt; \u0026lt;appender name=\u0026#34;WarnFile\u0026#34; type=\u0026#34;log4net.Appender.RollingFileAppender\u0026#34;\u0026gt; \u0026lt;file type=\u0026#34;log4net.Util.PatternString\u0026#34; value=\u0026#34;C:\\applications\\logs\\%property{appname}\\Warn-%property{appname}.log\u0026#34; /\u0026gt; \u0026lt;encoding value=\u0026#34;utf-8\u0026#34; /\u0026gt; \u0026lt;appendToFile value=\u0026#34;true\u0026#34; /\u0026gt; \u0026lt;maximumFileSize value=\u0026#34;10000KB\u0026#34; /\u0026gt; \u0026lt;maxSizeRollBackups value=\u0026#34;50\u0026#34; /\u0026gt; \u0026lt;lockingModel type=\u0026#34;log4net.Appender.FileAppender+MinimalLock\u0026#34; /\u0026gt; \u0026lt;layout type=\u0026#34;log4net.Layout.PatternLayout\u0026#34;\u0026gt; \u0026lt;conversionPattern value=\u0026#34;%date\u0026amp;#x9;[%thread]\u0026amp;#x9;%level\u0026amp;#x9;%logger\u0026amp;#x9;%property{appname}\u0026amp;#x9;- %message%newline\u0026#34; /\u0026gt; \u0026lt;/layout\u0026gt; \u0026lt;filter type=\u0026#34;log4net.Filter.LevelMatchFilter\u0026#34;\u0026gt; \u0026lt;param name=\u0026#34;LevelToMatch\u0026#34; value=\u0026#34;WARN\u0026#34; /\u0026gt; \u0026lt;/filter\u0026gt; \u0026lt;filter type=\u0026#34;log4net.Filter.DenyAllFilter\u0026#34; /\u0026gt; \u0026lt;/appender\u0026gt; \u0026lt;appender name=\u0026#34;InfoFile\u0026#34; type=\u0026#34;log4net.Appender.RollingFileAppender\u0026#34;\u0026gt; \u0026lt;file type=\u0026#34;log4net.Util.PatternString\u0026#34; value=\u0026#34;C:\\applications\\logs\\%property{appname}\\Info-%property{appname}.log\u0026#34; /\u0026gt; \u0026lt;encoding value=\u0026#34;utf-8\u0026#34; /\u0026gt; \u0026lt;appendToFile value=\u0026#34;true\u0026#34; /\u0026gt; \u0026lt;maximumFileSize value=\u0026#34;10000KB\u0026#34; /\u0026gt; \u0026lt;maxSizeRollBackups value=\u0026#34;50\u0026#34; /\u0026gt; \u0026lt;lockingModel type=\u0026#34;log4net.Appender.FileAppender+MinimalLock\u0026#34; /\u0026gt; \u0026lt;layout type=\u0026#34;log4net.Layout.PatternLayout\u0026#34;\u0026gt; \u0026lt;conversionPattern value=\u0026#34;%date\u0026amp;#x9;[%thread]\u0026amp;#x9;%level\u0026amp;#x9;%logger\u0026amp;#x9;%property{appname}\u0026amp;#x9;- %message%newline\u0026#34; /\u0026gt; \u0026lt;/layout\u0026gt; \u0026lt;filter type=\u0026#34;log4net.Filter.LevelMatchFilter\u0026#34;\u0026gt; \u0026lt;LevelToMatch value=\u0026#34;INFO\u0026#34; /\u0026gt; \u0026lt;/filter\u0026gt; \u0026lt;filter type=\u0026#34;log4net.Filter.DenyAllFilter\u0026#34; /\u0026gt; \u0026lt;/appender\u0026gt; \u0026lt;appender name=\u0026#34;RollingFile\u0026#34; type=\u0026#34;log4net.Appender.RollingFileAppender\u0026#34;\u0026gt; \u0026lt;file type=\u0026#34;log4net.Util.PatternString\u0026#34; value=\u0026#34;C:\\applications\\logs\\%property{appname}\\ALL-%property{appname}.log\u0026#34; /\u0026gt; \u0026lt;encoding value=\u0026#34;utf-8\u0026#34; /\u0026gt; \u0026lt;appendToFile value=\u0026#34;true\u0026#34; /\u0026gt; \u0026lt;maximumFileSize value=\u0026#34;10000KB\u0026#34; /\u0026gt; \u0026lt;maxSizeRollBackups value=\u0026#34;50\u0026#34; /\u0026gt; \u0026lt;lockingModel type=\u0026#34;log4net.Appender.FileAppender+MinimalLock\u0026#34; /\u0026gt; \u0026lt;layout type=\u0026#34;log4net.Layout.PatternLayout\u0026#34;\u0026gt; \u0026lt;conversionPattern value=\u0026#34;%date\u0026amp;#x9;[%thread]\u0026amp;#x9;%level\u0026amp;#x9;%logger\u0026amp;#x9;%property{appname}\u0026amp;#x9;- %message%newline\u0026#34; /\u0026gt; \u0026lt;/layout\u0026gt; \u0026lt;/appender\u0026gt; \u0026lt;appender name=\u0026#34;debugger\u0026#34; type=\u0026#34;log4net.Appender.OutputDebugStringAppender\u0026#34;\u0026gt; \u0026lt;layout type=\u0026#34;log4net.Layout.PatternLayout\u0026#34;\u0026gt; \u0026lt;conversionPattern value=\u0026#34;%date\u0026amp;#x9;[%thread]\u0026amp;#x9;%level\u0026amp;#x9;%logger\u0026amp;#x9;%property{appname}\u0026amp;#x9;- %message%newline\u0026#34; /\u0026gt; \u0026lt;/layout\u0026gt; \u0026lt;/appender\u0026gt; \u0026lt;root additivity=\u0026#34;true\u0026#34;\u0026gt; \u0026lt;level value=\u0026#34;ALL\u0026#34; /\u0026gt; \u0026lt;appender-ref ref=\u0026#34;debugger\u0026#34; /\u0026gt; \u0026lt;appender-ref ref=\u0026#34;RollingFile\u0026#34; /\u0026gt; \u0026lt;appender-ref ref=\u0026#34;InfoFile\u0026#34; /\u0026gt; \u0026lt;appender-ref ref=\u0026#34;WarnFile\u0026#34; /\u0026gt; \u0026lt;appender-ref ref=\u0026#34;ErrorFile\u0026#34; /\u0026gt; \u0026lt;/root\u0026gt; \u0026lt;logger name=「doway.sample.helloworld」\u0026gt; \u0026lt;level value=\u0026#34;WARN\u0026#34; /\u0026gt; \u0026lt;/logger\u0026gt; \u0026lt;/log4net\u0026gt; 參考\nTom\u0026rsquo;s IT notebook http://www.claassen.net/geek/blog/2009/06/log4net-filtering-by-logger.html ","date":"2015年3月25日","permalink":"https://metavige.github.io/2015/03/25/log4net-actionlog/","section":"文章","summary":"\u003cp\u003e我之前設定網站執行的 Action Log 的一個記錄，有些東西都是拼拼湊湊出來的，並不見得是最佳版本\u003c/p\u003e\n\u003cp\u003elog4net 設定檔\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cdiv style=\"color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\n\u003ctable style=\"border-spacing:0;padding:0;margin:0;border:0;\"\u003e\u003ctr\u003e\u003ctd style=\"vertical-align:top;padding:0;margin:0;border:0;\"\u003e\n\u003cpre tabindex=\"0\" style=\"color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\u003ccode\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679\"\u003e 1\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679\"\u003e 2\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679\"\u003e 3\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679\"\u003e 4\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679\"\u003e 5\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679\"\u003e 6\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679\"\u003e 7\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679\"\u003e 8\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679\"\u003e 9\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679\"\u003e10\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679\"\u003e11\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679\"\u003e12\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679\"\u003e13\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679\"\u003e14\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679\"\u003e15\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679\"\u003e16\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679\"\u003e17\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679\"\u003e18\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679\"\u003e19\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679\"\u003e20\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679\"\u003e21\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679\"\u003e22\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679\"\u003e23\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679\"\u003e24\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679\"\u003e25\n\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\n\u003ctd style=\"vertical-align:top;padding:0;margin:0;border:0;;width:100%\"\u003e\n\u003cpre tabindex=\"0\" style=\"color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\u003ccode class=\"language-xml\" data-lang=\"xml\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#8b949e;font-weight:bold;font-style:italic\"\u003e\u0026lt;?xml version=\u0026#34;1.0\u0026#34; encoding=\u0026#34;utf-8\u0026#34; ?\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#7ee787\"\u003e\u0026lt;log4net\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#7ee787\"\u003e\u0026lt;root\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#7ee787\"\u003e\u0026lt;level\u003c/span\u003e value=\u003cspan style=\"color:#a5d6ff\"\u003e\u0026#34;DEBUG\u0026#34;\u003c/span\u003e \u003cspan style=\"color:#7ee787\"\u003e/\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#7ee787\"\u003e\u0026lt;appender-ref\u003c/span\u003e ref=\u003cspan style=\"color:#a5d6ff\"\u003e\u0026#34;stdout\u0026#34;\u003c/span\u003e \u003cspan style=\"color:#7ee787\"\u003e/\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#7ee787\"\u003e\u0026lt;appender-ref\u003c/span\u003e ref=\u003cspan style=\"color:#a5d6ff\"\u003e\u0026#34;ActionLogRollingFile\u0026#34;\u003c/span\u003e \u003cspan style=\"color:#7ee787\"\u003e/\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#7ee787\"\u003e\u0026lt;/root\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#7ee787\"\u003e\u0026lt;appender\u003c/span\u003e name=\u003cspan style=\"color:#a5d6ff\"\u003e\u0026#34;ActionLogRollingFile\u0026#34;\u003c/span\u003e type=\u003cspan style=\"color:#a5d6ff\"\u003e\u0026#34;log4net.Appender.RollingFileAppender\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#7ee787\"\u003e\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#7ee787\"\u003e\u0026lt;file\u003c/span\u003e type=\u003cspan style=\"color:#a5d6ff\"\u003e\u0026#34;log4net.Util.PatternString\u0026#34;\u003c/span\u003e value=\u003cspan style=\"color:#a5d6ff\"\u003e\u0026#34;action.log\u0026#34;\u003c/span\u003e \u003cspan style=\"color:#7ee787\"\u003e/\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#7ee787\"\u003e\u0026lt;appendToFile\u003c/span\u003e value=\u003cspan style=\"color:#a5d6ff\"\u003e\u0026#34;true\u0026#34;\u003c/span\u003e \u003cspan style=\"color:#7ee787\"\u003e/\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#7ee787\"\u003e\u0026lt;encoding\u003c/span\u003e value=\u003cspan style=\"color:#a5d6ff\"\u003e\u0026#34;utf-8\u0026#34;\u003c/span\u003e \u003cspan style=\"color:#7ee787\"\u003e/\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#7ee787\"\u003e\u0026lt;maxSizeRollBackups\u003c/span\u003e value=\u003cspan style=\"color:#a5d6ff\"\u003e\u0026#34;30\u0026#34;\u003c/span\u003e \u003cspan style=\"color:#7ee787\"\u003e/\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#7ee787\"\u003e\u0026lt;rollingStyle\u003c/span\u003e value=\u003cspan style=\"color:#a5d6ff\"\u003e\u0026#34;Date\u0026#34;\u003c/span\u003e \u003cspan style=\"color:#7ee787\"\u003e/\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#7ee787\"\u003e\u0026lt;datePattern\u003c/span\u003e value=\u003cspan style=\"color:#a5d6ff\"\u003e\u0026#34;\u0026#39;.\u0026#39;yyyy-MM-dd\u0026#34;\u003c/span\u003e \u003cspan style=\"color:#7ee787\"\u003e/\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#7ee787\"\u003e\u0026lt;layout\u003c/span\u003e type=\u003cspan style=\"color:#a5d6ff\"\u003e\u0026#34;log4net.Layout.PatternLayout\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#7ee787\"\u003e\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            \u003cspan style=\"color:#7ee787\"\u003e\u0026lt;conversionPattern\u003c/span\u003e value=\u003cspan style=\"color:#a5d6ff\"\u003e\u0026#34;(%date{HH:mm:ss,fff}) [%t] - %u %5property{Duration} %property{StatusCode} %6property{Method} %property{Uri} %m%n \u0026#34;\u003c/span\u003e \u003cspan style=\"color:#7ee787\"\u003e/\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#7ee787\"\u003e\u0026lt;/layout\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#7ee787\"\u003e\u0026lt;filter\u003c/span\u003e type=\u003cspan style=\"color:#a5d6ff\"\u003e\u0026#34;log4net.Filter.LoggerMatchFilter\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#7ee787\"\u003e\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            \u003cspan style=\"color:#7ee787\"\u003e\u0026lt;acceptOnMatch\u003c/span\u003e value=\u003cspan style=\"color:#a5d6ff\"\u003e\u0026#34;true\u0026#34;\u003c/span\u003e \u003cspan style=\"color:#7ee787\"\u003e/\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            \u003cspan style=\"color:#7ee787\"\u003e\u0026lt;LoggerToMatch\u003c/span\u003e value=\u003cspan style=\"color:#a5d6ff\"\u003e\u0026#34;Sample.Web.Filters.WebApiActionFilter\u0026#34;\u003c/span\u003e \u003cspan style=\"color:#7ee787\"\u003e/\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#7ee787\"\u003e\u0026lt;/filter\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#7ee787\"\u003e\u0026lt;filter\u003c/span\u003e type=\u003cspan style=\"color:#a5d6ff\"\u003e\u0026#34;log4net.Filter.DenyAllFilter\u0026#34;\u003c/span\u003e \u003cspan style=\"color:#7ee787\"\u003e/\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#7ee787\"\u003e\u0026lt;/appender\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#7ee787\"\u003e\u0026lt;/log4net\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\u003c/tr\u003e\u003c/table\u003e\n\u003c/div\u003e\n\u003c/div\u003e\u003cp\u003e其中要注意的是\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003elog4net.Filter.LoggerMatchFilter: 用來設定特定的 class 記錄才使用這個 Appender\n\u003cul\u003e\n\u003cli\u003eacceptOnMatch: 這個設定有點像是 blacklist/whitelist，true 表示有 match 才是，相當於 whitelist\u003c/li\u003e\n\u003cli\u003eLoggerToMatch: 設定要篩選的類別名稱\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003cli\u003elog4net.Filter.DenyAllFilter: 篩選掉其他的 Log，不會在這個 Appender 裡面記錄\u003c/li\u003e\n\u003c/ul\u003e","title":"log4net Action Log 設定"},{"content":"OAuth2 現在已經很多的網站都有實作了，像是 Twitter, Google, Facebook, Github 等網站本身都有支援 OAuth2 的驗證與授權機制\n最近因為專案需求，特地去研究了一下有關 Web API 做驗證授權的機制，看到網路上有很多的範例，在講解 AngularJS SPA 的登入作法，想說可以把類似的機制放到 RESTful Service 的驗證上面\n以下是 JWT 的規格文件 (老實說，這份規格並不好懂\u0026hellip;..我看了很久\u0026hellip;..其實還是一知半解)\nJSON Web Token (JWT) Profile for OAuth 2.0 Client Authentication and Authorization Grants\n後來有找到，現在的 Owin.Security Package 中，有一個 Microsoft.Owin.Security.Jwt 的 Nuget package，就是在實作相關的程式，所以找了程式範例，自己下來實作。\n如果沒有特別的需求，其實用 Owin 來做 JWT 的使用與驗證其實還蠻簡單的。\nJWT 簡介 #先大概解釋一下什麼是 JWT\n根據 jwt.io 這個網站的首頁，就說到 JWT (JSON Web Token) 的主要意義是什麼?\nJSON Web Token (JWT) is a compact URL-safe means of representing claims to be transferred between two parties. The claims in a JWT are encoded as a JSON object that is digitally signed using JSON Web Signature (JWS). IETF\nsalesforce 有一份中文解釋\nJSON Web 權杖 (JWT) 是 JSON 型安全性權杖加密，能夠使身分和安全性資訊在安全性網域之間共用。 OAuth 2.0 JWT 承載者權杖流程定義了當用戶端希望運用先前授權時，使用 JWT 以從 Salesforce 要求 OAuth 存取權杖的方式。適用 JWT 的數位簽署會提供已授權應用程式的驗證。\nJWT 角色 # Token Provider - 負責發出 JWT，並且事先要先提供給 Web API 服務端 Signature，用來確認 JWT 有效性 Web API 用戶端 - 提出 API 呼叫的使用者端，呼叫服務端之前，要先跟 Token Provider 要求 JWT。 Web API 服務端 - 先跟 Token Provider 取得 Signature，接收到 JWT 之後，透過 Signature 檢查 JWT 有效性 其中，Token Provider 並不見得一定要是另外的應用服務，若用戶端以及服務端先談好，其實用戶端自己可以簽發 JWT，只要按照一定的方式做就可以\nWhy JWT? #我自己認為，其實 JWT 就是用戶端想一個方式，產生一組 JWT，把使用者相關的身分資料都以 JSON 的格式組合，然後做一個基本的 Sign，這個 Sign 的方式，會是與 Web API Server 事先談好。\nWeb API Server 端在接收到 JWT 之後，只需要以事前約定好的 Signature 做解密，確認 JWT 並未被 Third-Party 竄改，如此便可以信任 Token Provider 所發出的 JWT，並將 JWT 內的資料轉換成 ClaimsIdentity 存入本地端，讓 Web API 可以使用\n這個方式的好處是:\n服務端在驗證的時候，並不需要與 Token Provider 連接確認 JWT 的有效性 避免第三方竄改 JWT 的內容 可以傳遞的資料相對比較多，因為是 JSON 格式 server 可以達到 stateless 的目標 (RESTful) server 的 scalability 可以很大，因為不需要有 mapping table，隻需要有 signature 就可以在 N 台 server 上解開 避免掉過去一些 cookie 的安全性問題，像是 CSRF 效能可能有點不一樣：如果你的 session mapping table 是存在 database，那就看看 access db 的時間和解 sha-256 的時間哪個比較快 JWT 內容 #整個 JWT 分成三個部分，JOSE Header，JWT Claims Set，Sign，這是三個 Base64 的字串，用 . 組合起來\n第一個部分是 JOSE Header，描述了這份 JWT 是採用哪個演算法做 hash，像是下面的範例就說明了這個 JWT 是用 HMAC-256 的加密法\n1 2 3 4 { \u0026#34;typ\u0026#34; : \u0026#34;JWT\u0026#34;, \u0026#34;alg\u0026#34; : \u0026#34;HS256\u0026#34; } 加密後大概是像下面這樣\n1 eyJ0eXAiOiJKV1QiLA0KICJhbGciOiJIUzI1NiJ9 第二個部分是本文，JWT Clams Set則是包含你要傳的資訊（JSON format）\naud: 簽發 JWT 的單位 (Token Provider) exp: 到期時間（單位是秒，所以getTime 是 ms 要除 1000) iat: 申請時間 iss: 申請者，一般用來識別用戶端 範例如下\n1 2 3 4 5 { \u0026#34;iss\u0026#34;: \u0026#34;joe\u0026#34;, \u0026#34;exp\u0026#34;: 1300819380, \u0026#34;http://example.com/is_root\u0026#34;: true } 第三個部分 Sign 則是透過 JOSE Header 的演算法將 JOSE Header 和 JWT Claims Set 一起做 hash 的結果\n(Ref: http://www.sitepoint.com/using-json-web-tokens-node-js/)\nJWT 流程 #主要流程是\nUser 送出request到Server要通過 authentication Server 判斷（檢查密碼等）通過 auth 後，把 user 的資訊包到 token 內 Server 用一個 secret 去 hash 這份 token，並把hash過後的sign放到 token 的最後一個欄位 Server 把 JWT 回傳給 user User 之後送 request 給 server 的時候都在 header 中夾帶這個 token Server 收到有 token 的 request 後，用同一份 secret 去解開這個 token，拿到 userid 並辨識出 user 參考 # Asp.Net Security - 這邊有很多文章很棒，可以參考 JSON Web Token in ASP.NET Web API 2 using Owin - 我程式的範例基本上是參考他文章中的 Source Code 修改的，所以可以下載他原始的版本來看 OBeyTech JWT - JSON Web Token OAuth 2.0 JWT 承載者權杖流程 jwt.io - 這個網站可以讓你很方便的驗證 JWT 的格式與內容 ","date":"2015年3月11日","permalink":"https://metavige.github.io/2015/03/11/2015-03-11-jwt/","section":"文章","summary":"\u003cp\u003e\u003ca href=\"http://oauth.net/2/\" target=\"_blank\" rel=\"noreferrer\"\u003eOAuth2\u003c/a\u003e 現在已經很多的網站都有實作了，像是 \u003ca href=\"https://twitter.com\" target=\"_blank\" rel=\"noreferrer\"\u003eTwitter\u003c/a\u003e, \u003ca href=\"https://www.google.com\" target=\"_blank\" rel=\"noreferrer\"\u003eGoogle\u003c/a\u003e, \u003ca href=\"https://www.facebook.com\" target=\"_blank\" rel=\"noreferrer\"\u003eFacebook\u003c/a\u003e, \u003ca href=\"https://github.com\" target=\"_blank\" rel=\"noreferrer\"\u003eGithub\u003c/a\u003e 等網站本身都有支援 \u003ca href=\"http://oauth.net/2/\" target=\"_blank\" rel=\"noreferrer\"\u003eOAuth2\u003c/a\u003e 的驗證與授權機制\u003c/p\u003e\n\u003cp\u003e最近因為專案需求，特地去研究了一下有關 Web API 做驗證授權的機制，看到網路上有很多的範例，在講解 AngularJS SPA 的登入作法，想說可以把類似的機制放到 RESTful Service 的驗證上面\u003c/p\u003e","title":"JWT"},{"content":"","date":null,"permalink":"https://metavige.github.io/tags/OAuth2/","section":"Tags","summary":"","title":"OAuth2"},{"content":"執行以下指令就可以～\n1 sudo networksetup -setdnsservers \u0026#34;iPhone USB\u0026#34; 8.8.8.8 ","date":"2015年1月22日","permalink":"https://metavige.github.io/2015/01/22/iphone-usb-hotspot-dns-setting/","section":"文章","summary":"\u003cp\u003e執行以下指令就可以～\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cdiv style=\"color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\n\u003ctable style=\"border-spacing:0;padding:0;margin:0;border:0;\"\u003e\u003ctr\u003e\u003ctd style=\"vertical-align:top;padding:0;margin:0;border:0;\"\u003e\n\u003cpre tabindex=\"0\" style=\"color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\u003ccode\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679\"\u003e1\n\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\n\u003ctd style=\"vertical-align:top;padding:0;margin:0;border:0;;width:100%\"\u003e\n\u003cpre tabindex=\"0\" style=\"color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003esudo networksetup -setdnsservers \u003cspan style=\"color:#a5d6ff\"\u003e\u0026#34;iPhone USB\u0026#34;\u003c/span\u003e 8.8.8.8\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\u003c/tr\u003e\u003c/table\u003e\n\u003c/div\u003e\n\u003c/div\u003e","title":"修改 iPhone USB 個人熱點，在 Mac 的 DNS 設定"},{"content":"","date":null,"permalink":"https://metavige.github.io/categories/%E9%9B%BB%E8%85%A6%E7%9B%B8%E9%97%9C/","section":"Categories","summary":"","title":"電腦相關"},{"content":"編輯 /etc/default/docker\n把以下 export http_proxy 的那一行 unmark, 然後改成你的 proxy 設定\n1 2 3 4 5 6 7 8 9 10 11 12 13 # Docker Upstart and SysVinit configuration file # Customize location of Docker binary (especially for development testing). #DOCKER=\u0026#34;/usr/local/bin/docker\u0026#34; # Use DOCKER_OPTS to modify the daemon startup options. #DOCKER_OPTS=\u0026#34;--dns 8.8.8.8 --dns 8.8.4.4\u0026#34; # If you need Docker to use an HTTP proxy, it can also be specified here. export http_proxy=\u0026#34;http://127.0.0.1:3128/\u0026#34; # This is also a handy place to tweak where Docker\u0026#39;s temporary files go. #export TMPDIR=\u0026#34;/mnt/bigdrive/docker-tmp\u0026#34; 重新啟動 docker service \u0026mdash; service docker restart\n(如果你安裝的是 ubuntu 預設的 docker.io，指令就是 service docker.io restart)\n","date":"2014年12月17日","permalink":"https://metavige.github.io/2014/12/17/docker-behind-proxy-ubuntu/","section":"文章","summary":"\u003cp\u003e編輯 \u003ccode\u003e/etc/default/docker\u003c/code\u003e\u003cbr\u003e\n把以下 \u003ccode\u003eexport http_proxy\u003c/code\u003e 的那一行 unmark, 然後改成你的 proxy 設定\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cdiv style=\"color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\n\u003ctable style=\"border-spacing:0;padding:0;margin:0;border:0;\"\u003e\u003ctr\u003e\u003ctd style=\"vertical-align:top;padding:0;margin:0;border:0;\"\u003e\n\u003cpre tabindex=\"0\" style=\"color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\u003ccode\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679\"\u003e 1\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679\"\u003e 2\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679\"\u003e 3\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679\"\u003e 4\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679\"\u003e 5\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679\"\u003e 6\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679\"\u003e 7\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679\"\u003e 8\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679\"\u003e 9\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679\"\u003e10\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679\"\u003e11\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679\"\u003e12\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679\"\u003e13\n\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\n\u003ctd style=\"vertical-align:top;padding:0;margin:0;border:0;;width:100%\"\u003e\n\u003cpre tabindex=\"0\" style=\"color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#8b949e;font-style:italic\"\u003e# Docker Upstart and SysVinit configuration file\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#8b949e;font-style:italic\"\u003e# Customize location of Docker binary (especially for development testing).\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#8b949e;font-style:italic\"\u003e#DOCKER=\u0026#34;/usr/local/bin/docker\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#8b949e;font-style:italic\"\u003e# Use DOCKER_OPTS to modify the daemon startup options.\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#8b949e;font-style:italic\"\u003e#DOCKER_OPTS=\u0026#34;--dns 8.8.8.8 --dns 8.8.4.4\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#8b949e;font-style:italic\"\u003e# If you need Docker to use an HTTP proxy, it can also be specified here.\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eexport \u003cspan style=\"color:#79c0ff\"\u003ehttp_proxy\u003c/span\u003e\u003cspan style=\"color:#ff7b72;font-weight:bold\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#a5d6ff\"\u003e\u0026#34;http://127.0.0.1:3128/\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#8b949e;font-style:italic\"\u003e# This is also a handy place to tweak where Docker\u0026#39;s temporary files go.\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#8b949e;font-style:italic\"\u003e#export TMPDIR=\u0026#34;/mnt/bigdrive/docker-tmp\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\u003c/tr\u003e\u003c/table\u003e\n\u003c/div\u003e\n\u003c/div\u003e\u003cp\u003e重新啟動 docker service \u0026mdash; \u003ccode\u003eservice docker restart\u003c/code\u003e\u003c/p\u003e","title":"Docker service behind proxy - Ubuntu"},{"content":"本身有用 Windows, Mac 在開發系統，我其實是偏好在 Mac 編輯，因為我喜歡 Sublime Text !!!\n不過，在 Windows 中用 Visual Studio 2013, 公司內部是規定使用 TFS （老實說我不是很喜歡，不過整個團隊以及公司都是這樣，也不得不用～）\n有些時候，不想使用笨重的 VS2013，像是開發 Web Project，我還是喜歡用 Sublime Text 編輯，像是 javascript/html 都有不錯的開發經驗～ 所以只好用 git-tf 來做界接～\n要在 Mac 上安裝 git-tf 很容易，你只需要使用 brew 就可以安裝\n1 brew install git-tf 不過在使用 git-tf 的過程中，有些 Windows/Mac 之間的差異性，導致每次我常常要 Checkin 一堆檔案，就算我沒改這些檔案，經過幾番摸索，終於找到大部分的設定，可以讓 Windows/Mac 之間更容易的整合，特此記錄！\n行尾符號 - 因為 Windows 是 CRLF，不過 Mac 只有 LF 1 git config core.autocrlf false 空格問題 - trailing-space 會查找每行結尾的空格, cr-at-eol 告訴 Git carriage returns 是合法的 1 git config core.whitespace trailing-space,cr-at-eol 大小寫問題 - Windows 檔名是沒有大小寫分別的，不過 Mac 有，這就看個人所需了 1 git config core.ignorecase true 以上設定，只需要加上 \u0026ndash;global 就可以設定在全域設定上 (~/.gitconfig)，以上資料參考 git 的配置文件\n","date":"2014年7月16日","permalink":"https://metavige.github.io/2014/07/16/git-tf-collaboration-git-some-small-set/","section":"文章","summary":"\u003cp\u003e本身有用 Windows, Mac 在開發系統，我其實是偏好在 Mac 編輯，因為我喜歡 \u003ca href=\"http://www.sublimetext.com/3\" target=\"_blank\" rel=\"noreferrer\"\u003eSublime Text\u003c/a\u003e !!!\u003c/p\u003e\n\u003cp\u003e不過，在 Windows 中用 Visual Studio 2013, 公司內部是規定使用 TFS （老實說我不是很喜歡，不過整個團隊以及公司都是這樣，也不得不用～）\u003c/p\u003e\n\u003cp\u003e有些時候，不想使用笨重的 VS2013，像是開發 Web Project，我還是喜歡用 \u003ca href=\"http://www.sublimetext.com/3\" target=\"_blank\" rel=\"noreferrer\"\u003eSublime Text\u003c/a\u003e 編輯，像是 javascript/html 都有不錯的開發經驗～\n所以只好用 \u003ca href=\"https://gittf.codeplex.com\" target=\"_blank\" rel=\"noreferrer\"\u003egit-tf\u003c/a\u003e 來做界接～\u003c/p\u003e","title":"Git-Tf 協同作業，Git 的一些小設定"},{"content":"dotNetConf 2014 的影片已經可以在 Channel 9 找到了\n我看了三個項目\n(KeyNote) .Net State Asp.Net MVC 6 New Innovation in .Net 老實說改變很大\n因為之前 Asp.Net, Asp.Net MVC, WebForm 的相似度太大，但是程式碼是分開的，因為是階段性做出來的，所以現在要整合了～感覺上，Java 學起來比較累，但是，就是因為他一切從簡，所以才有擴充的空間。dotNet 的問題大概就是，想整合太多東西，現在包袱很重\u0026hellip;..所以現在要來個大改版～ 終於，dotNet 開源了～而且，也考慮到非 Windows 的用戶，所以 .Net Runtime 也要改版，準備要在不同平台上面 Run 蠻多東西，感覺上 Java 好像都已經做過了，所以表示，語言也是互相學習的。像是 Java 8 最近的新 feature 就是學習 C# 不少～ 你不需要預先安裝 .Net framework，因為 .net 現在也是一個 nuget package, 表示你可以需要的時候再安裝，而且可以不同專案裝不同的 runtime，Good!!! 大家都想跨足到 Cloud，所以表示，你必須要考慮到 Multi-Client，以前的 WebForm 大概\u0026hellip;..會消失了吧～所以 MVC 變成是王道啦～～～還好我在 Java 學的不少～ 另外有幾個部分我自己覺得蠻有趣，也應該是對 Developer 有益的地方\n搭配 VS2014, compile on fly - 這是你可以邊改程式，存檔後，編譯器就會自動幫你編譯，你可以直接整理網頁就好，不需要自己在 Build 一次，這也是我用 VS 覺得最麻煩的地方。因為以前在 Java，都是存檔完後就好，不需要再自己編譯一次 interface, implementation 分離 - 這次 dotNet 設計了一個 OWIN 介面，並且把 .net runtime 整個實做與介面分離，這我覺得大概是最棒的一個部分了。因為這樣，你就可以有不同的 implementation，crossplatform 的未來，就可預見，不像之前，你還要裝個 mono, 但是還要考慮是否可以整合～～～ Asp.Net 已經可以不需要靠 IIS 就可以執行。這點，對開發者是蠻有幫助的，未來在開發的時候，你或許可以在測試的時候，啟動一個 self host 來做整合測試，而不需要把程式部署才能測試。甚至，未來或許你在部署程式的時候，也可以用一段很簡單的scirpt 把程式碼從 DCVS 下載下來，及時編譯後，直接執行便可以使用。 內建整合 DI - 我想這跟 Java 一樣，因為 Spring 紅了，而且也真的讓程式碼的耦合性降低，但是官方本身的 runtime 一直不內建，所以你必須一定要安裝 third party 的套件來支援。現在整個內建支援，表示 runtime 本身也對 DI 做了一些改變，可以直接使用。所以 DI 的觀念一定要有了！！！ ","date":"2014年7月12日","permalink":"https://metavige.github.io/2014/07/12/dotNetConf-2014/","section":"文章","summary":"\u003cp\u003e\u003ca href=\"http://blogs.msdn.com/b/dotnet/archive/2014/07/02/dotnetconf-2014-wrapup.aspx\" target=\"_blank\" rel=\"noreferrer\"\u003edotNetConf 2014\u003c/a\u003e 的影片已經可以在 Channel 9 找到了\u003c/p\u003e\n\u003cp\u003e我看了三個項目\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e\u003ca href=\"http://channel9.msdn.com/Events/dotnetConf/2014/The-State-of-NET\" target=\"_blank\" rel=\"noreferrer\"\u003e(KeyNote) .Net State\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"http://channel9.msdn.com/Events/dotnetConf/2014/MVC-6\" target=\"_blank\" rel=\"noreferrer\"\u003eAsp.Net MVC 6\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"http://channel9.msdn.com/Events/dotnetConf/2014/New-Innovations-in-NET-Runtime\" target=\"_blank\" rel=\"noreferrer\"\u003eNew Innovation in .Net\u003c/a\u003e\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003e老實說改變很大\u003c/p\u003e","title":"dotNetConf 2014"},{"content":"原文出處\n這是前幾天在網路上看到的一篇文章，我懶得翻譯標題的部份 (其實我不太會翻譯) 我個人覺得很有幫助，剛好最近有在寫 SPA 的專案\n以下是我對原文的一些自己的理解，也算是簡單的翻譯啦。\n下面用到的瀏覽器都是 Chrome (不得不說，這真的是 Web 開發者的一大利器，比起 ie 好太多了～又快～)\ndebugger; #如果你需要在程式中，特別在某個區段需要讓程式中斷下來，但是，你又不想設定中斷點，你可以用 debugger; 這個關鍵字\n使用的時候，開啟 Chrome 的開發者工具，當執行到設定 debugger; 的程式地方，開發者工具就會自行中斷，就可以 debug~\n好用！\n如果需要有條件的中斷，參考下方的程式碼 (參考原文也有～)\n1 2 3 4 5 for(var i in [1,2,3,4,5]) { if (i === 2) { debugger; } } 在 DOM 變更的時候中斷 #常常我們在寫前端程式碼的時候，會遇到一個問題，就是畫面上的 DOM 屬性變更了，但是在茫茫程式碼中，你不知道哪段程式碼做了這個屬性的變更，找也要找半天。這時候你可以透過 Chrome 的開發者工具，來設定屬性變更的時候要中斷～\n設定的選項有三種：\n子節點變更的時候 (Subtree Modifications) 屬性變更的時候 (Attributes Modifications) 節點被刪除的時候 (Node Removal) Cool~\nAjax 中斷 #這是用在如果程式中有使用 Ajax 往後端送資料，你可以設定在送出這個 Request 的時候中斷\n模擬不同的 Mobile 裝置 #Chrome 有個蠻不錯的功能，就是能模擬不同的裝置，你可以利用這個功能，來檢查你的網站在不同的裝置中，會有怎麼樣的效果。這對 Responsive Web 的開發者來說，應該特別好用～\n要使用這個功能，你要先開啟開發者工具，然後選擇除了 Console 以外的 Tab，接下來按下 ESC 鍵，就會在下方開啟如圖的小頁籤出來，這時候就可以選擇 Emulation，選擇不同的 Device 來模擬\n讓你的網站效能更好 #一般來說，YSlow 是在開發網頁程式會用到的一個效能評估以及建議改善的好工具，不過 Chrome 自己也有提供一個小工具，叫做 Audit，也可以做到一些簡單的效能改善建議\n以上就是我對原文的一些簡單的理解與說明\n大部分的圖片都是出自於原文，但是因為第四點的圖片我個人認為有點不明顯，所以我就重新剪貼了一張～\n","date":"2014年3月7日","permalink":"https://metavige.github.io/2014/03/07/5-javascript-debugging-tips-youll-start-using-today/","section":"文章","summary":"\u003cp\u003e\u003ca href=\"http://berzniz.com/post/78260747646/5-javascript-debugging-tips-youll-start-using-today\" target=\"_blank\" rel=\"noreferrer\"\u003e原文出處\u003c/a\u003e\u003cbr\u003e\n這是前幾天在網路上看到的一篇文章，我懶得翻譯標題的部份 (其實我不太會翻譯)\n我個人覺得很有幫助，剛好最近有在寫 \u003ca href=\"https://en.wikipedia.org/wiki/Single-page_application\" target=\"_blank\" rel=\"noreferrer\"\u003eSPA\u003c/a\u003e 的專案\u003c/p\u003e\n\u003cp\u003e以下是我對原文的一些自己的理解，也算是簡單的翻譯啦。\u003cbr\u003e\n下面用到的瀏覽器都是 Chrome (不得不說，這真的是 Web 開發者的一大利器，比起 ie 好太多了～又快～)\u003c/p\u003e","title":"5 javascript debugging tips you'll start using today"},{"content":"最近剛好想學 CoffeeScript，前幾天又看到有 hubot 的新聞，所以就想說來玩一下 Hubot\n安裝步驟很簡單\n請先安裝 Nodejs/Redis (Google 一下，有太多資料了\u0026hellip;..) npm install -g hubot coffee-script hubot \u0026ndash;create myfirsthubot cd myfirsthubot bin/hubot -\u0026gt; 啟動！ 由於我時間沒花很多，而且我也還在想說這未來要能幹嘛 所以還在蒐集資料中\u0026hellip;..\n網路上早就有人講安裝的方式了: [教學] hubot install. hubot 機器人安裝\n","date":"2013年12月17日","permalink":"https://metavige.github.io/2013/12/17/hubot-first-try/","section":"文章","summary":"\u003cp\u003e最近剛好想學 CoffeeScript，前幾天又看到有 hubot 的新聞，所以就想說來玩一下 \u003ca href=\"http://hubot.github.com/\" target=\"_blank\" rel=\"noreferrer\"\u003eHubot\u003c/a\u003e\u003c/p\u003e","title":"Hubot first try"},{"content":"","date":null,"permalink":"https://metavige.github.io/tags/IoC/","section":"Tags","summary":"","title":"IoC"},{"content":"最近在學習使用 SharpRepository，剛好專案中有使用 IoC Container - Unity, 所以需要把兩者做一個結合\n不過，實際使用了 SharpRepository.Ioc.Unity 這個 Package，卻發現這個專案並沒實作到與 Unity 結合的部分，實際看了一下程式碼，才發現作者做了以下的註解：\nusing InjectionFactory I can get access to the container but I don\u0026rsquo;t seem to ahve access to a context in the other Ioc\u0026rsquo;s there is a context where I can get access to the type being resolved and get the generic arguments which is what i need\n所以實際上裡面的程式碼直接就拋出 NotImplementedException\n後來經過幾番確認與測試，最後終於找到方式了～ 程式碼如下：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 var RepositoryConfigurationName = \u0026#34;repository\u0026#34;; // 結合 SharpRepository 與 Unity RepositoryDependencyResolver.SetDependencyResolver(new UnityDependencyResolver(Container)); Container.RegisterType(typeof(IRepository\u0026lt;\u0026gt;), new PerResolveLifetimeManager(), new InjectionFactory((c, t, n) =\u0026gt; { var genericParams = t.GetGenericArguments(); return RepositoryFactory.GetInstance(genericParams[0], RepositoryConfigurationName); })); Container.RegisterType(typeof(IRepository\u0026lt;,\u0026gt;), new PerResolveLifetimeManager(), new InjectionFactory((c, t, n) =\u0026gt; { var genericParams = t.GetGenericArguments(); return RepositoryFactory.GetInstance(genericParams[0], genericParams[1], RepositoryConfigurationName); })); // 20140716 補充多主鍵的 Repository Container.RegisterType(typeof(ICompoundKeyRepository\u0026lt;,,\u0026gt;), new PerResolveLifetimeManager(), new InjectionFactory((c, t, n) =\u0026gt; { var genericParams = t.GetGenericArguments(); return RepositoryFactory.GetInstance(genericParams[0], genericParams[1], genericParams[2], RepositoryConfigurationName); })); 重點就在註冊 Type 的類型上面，如果只有 IRepository\u0026lt;\u0026gt; 表示是單一的 Generic Type Argument，如果是 IRepository\u0026lt;,\u0026gt; 就表示有兩個 Generic Type Arguments~\n老實說，真的很少人知道 C# 的 Generic Type 是這樣的～～～\n","date":"2013年11月19日","permalink":"https://metavige.github.io/2013/11/19/unity-sharprepository/","section":"文章","summary":"\u003cp\u003e最近在學習使用 \u003ca href=\"https://github.com/SharpRepository/SharpRepository\" target=\"_blank\" rel=\"noreferrer\"\u003eSharpRepository\u003c/a\u003e，剛好專案中有使用 IoC Container - Unity, 所以需要把兩者做一個結合\u003c/p\u003e","title":"Unity + SharpRepository"},{"content":"","date":null,"permalink":"https://metavige.github.io/tags/MQ/","section":"Tags","summary":"","title":"MQ"},{"content":"官方網站\n這是在官方網站上面 Solve a Problem 的一句話\nMost of us, when we start using ØMQ, come with mental models of how things \u0026ldquo;should\u0026rdquo; work, which are often wrong. If you come looking for specific solutions, you may be disappointed.\n簡單的來說，如果你想要這個 framework 來解決你專案或產品有可能會發生的問題，你可能會失望，因為這個 framework 並不是用來提供 Solution 的，這個 framework 只是用來提供一個可能性\n你可以利用這個 framework 來做到一些基礎的架構，然後你可以自己組合，用來做到你想做的事情～\n我想我還蠻喜歡這件事情的～ 不過當然，你必須要花比較多的時間去了解並熟悉，所以你必須要有一定的程式開發功力會比較好 不然你可能會很累～～～我想\n不過我還是做個記錄，或許以後真的有機會會用到\n相關文章連結\nZeroMQ at Github [架構] ZeroMQ 深度探索（一） 新世紀通訊函式庫– ZeroMQ | 程式設計遇上小提琴 TCP Socket 的替代方案 - ZeroMQ ","date":"2013年9月18日","permalink":"https://metavige.github.io/2013/09/18/zero-records/","section":"文章","summary":"\u003cp\u003e\u003ca href=\"http://zeromq.org/\" target=\"_blank\" rel=\"noreferrer\"\u003e官方網站\u003c/a\u003e\u003c/p\u003e\n\u003cp\u003e這是在官方網站上面 \u003ca href=\"http://zeromq.org/intro:ask-for-help\" target=\"_blank\" rel=\"noreferrer\"\u003eSolve a Problem\u003c/a\u003e 的一句話\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003eMost of us, when we start using ØMQ, come with mental models of how things \u0026ldquo;should\u0026rdquo; work, which are often wrong. If you come looking for specific solutions, you may be disappointed.\u003c/p\u003e\n\u003c/blockquote\u003e","title":"zero-records"},{"content":"在學習 Backbone 的時候，針對 Backbone 比較重要的一些事件、方法與屬性作個記錄\nBackbonejs\n事件 # on/bind 設定事件 off/unbind 移除事件 trigger 觸發事件 once 設定一個只執行一次的事件 listenTo 監聽其他物件的事件 stopListening 移除監聽其他物件的事件 Model #這邊主要要設定的是資料結構，就是 Model 裡面有哪些屬性可以使用\n1 2 3 var MyModel = Backbone.Model.extend({ // 屬性～～～或其他的方法 }); 這邊回傳的會是一個 Function Object 是可以被 new 的～\nset(attributes, [options]) 會觸發 change 事件。\nchange 事件可以指定特定的 attribute，如 change:name 事件，表示 name attribute 變更會觸發\n但是一定是要用 set 方法才可以～\n如果有設定 validate 方法，就會驗證過才存入資料，驗證失敗不會觸發事件。可在 options 傳遞 error callback\nunset(attribute, [options]) - 移除屬性，也會觸發 change 事件，除非 options 傳遞 { silent: true }\nclear 清除所有屬性\nchanged 屬性 - 物件是否有變更～？\nfetch(options) - 與 store 同步，會用 Backbone.sync 中的 read\nHTTP GET - options - 成功或失敗的 callback, 或其他 Ajax 參數\n如果 server 端資料與本地不同，觸發 change 事件\nsave() 存檔，會作 sync\n如果 isNew - HTTP POST, or HTTP PUT\ndestroy - HTTP DELETE { wait: true } - 這樣會讓這個方法等到執行完畢有回應才結束\nurl() - 預設是 collection.url/id，也可以是 urlRoot/id\nurlRoot - 自行設定 url\nCollections #Collections 本身最常用到的應該只是 fetch，如果要讀取 server 端資料\n不會有更新的動作，每個更新的動作都是透過 Model 的 sync 方式～\n所以應該著重在事件觸發的點，以及 fetch 方法\n1 2 3 var MyCollection = Backbone.Collection.extend({ // 屬性～～～或其他的方法 }); url - sync url\nfetch - sync read\n可以傳遞其他參數給後端 ex: { data : { page : 5 } }\nView #展現畫面，重要的是 render 方法，用來展現畫面用～\n1 2 3 4 5 6 var MyView = Backbone.View.extend({ // 屬性～～～或其他的方法 render: function() { // render DOM, page } }); initialize 方法 初始化，應該會拿來作 view 的事件指定～\nrender 方法 修改 el 物件內容，達到畫面變更～\n這個方法最好回傳 this\nel 物件 : DOM 可以在 View 初始化的時候，用 tagName, className, id 或其他參數來指定\n預設為 div 空白的～\n$el - cached jQuery(Zepto) 物件\n就是 View 這個物件本身在畫面上對應的 jQuery 物件\n透過這個屬性可以用 jQuery 的方法來操作 DOM Object\n$( ) 方法，delegate $, query object inside View\n","date":"2013年2月13日","permalink":"https://metavige.github.io/2013/02/13/backbone-study/","section":"文章","summary":"\u003cp\u003e在學習 Backbone 的時候，針對 Backbone 比較重要的一些事件、方法與屬性作個記錄\u003c/p\u003e\n\u003cp\u003e\u003ca href=\"http://backbonejs.org/\" target=\"_blank\" rel=\"noreferrer\"\u003eBackbonejs\u003c/a\u003e\u003c/p\u003e","title":"Backbone Study"},{"content":"","date":null,"permalink":"https://metavige.github.io/tags/backbonejs/","section":"Tags","summary":"","title":"Backbonejs"},{"content":"","date":null,"permalink":"https://metavige.github.io/tags/VCS/","section":"Tags","summary":"","title":"VCS"},{"content":"前幾天跟公司內部同事分享了一些版本管理的觀念\n順便作個紀錄，避免以後還要找資料\n名詞解釋 # Trunk (Baseline) – 主線 Branches – 分支 Tags – 標籤 版本控管觀念 # 版本管理包含文件以及程式 包含工具的使用以及人為規範的遵循 專案中最好有版本控制員角色 版本管理是個循序漸進、持久的過程 項目結構 (目錄) 儘可能保持有邏輯以及意義 (根據專有名詞或階段區分、如 master, release 等) 避免多人修改同一檔案 (文件) 本地端與伺服器端版本不一致 避免手動修改檔案唯讀權限 獲取最新版本時請注意本地端檔案版本 頻繁的簽入或很少簽入 良好的規範與共識 開發前先取得最新版本 確保編譯成功後在簽入 (Commit) 程式 每次簽入的時候都撰寫 log，說明修改的項目與原由 當內容發生衝突時，由歷程紀錄得知上一個修改者，主動討論解決 每日下班前，必須將可建置版本交付出來，避免保留程式碼在簽出 (Checkout) 狀態 避免大量程式碼簽入 參考資源 # http://ihower.tw/blog/archives/5140 http://zh.wikipedia.org/wiki/%E7%89%88%E6%9C%AC%E6%8E%A7%E5%88%B6 http://www.ithome.com.tw/itadm/article.php?c=67969 http://cb.esast.com/cb/wiki/33381 ","date":"2011年7月13日","permalink":"https://metavige.github.io/2011/07/13/about-version-control/","section":"文章","summary":"\u003cp\u003e前幾天跟公司內部同事分享了一些版本管理的觀念\u003cbr\u003e\n順便作個紀錄，避免以後還要找資料\u003c/p\u003e","title":"版本管理的一些觀念說明"},{"content":"","date":null,"permalink":"https://metavige.github.io/tags/architecture/","section":"Tags","summary":"","title":"Architecture"},{"content":"做架構設計幾年了，發現這幾年來，做最多的不是去研究學習怎樣做，而是去說服人家用這個架構。其實，有時候很討厭聽到下面這句話：\n為什麼我們要這樣做？\n說真的，這個問題我聽了很多次，通常會發生在以下幾種狀況的時候\n老闆說要做架構設計：\n有些主管，因為時程趕或者是因為什麼不知名的原因，認為有架構就是好事，所以希望能夠在自己的專案或系統中導入架構，除了或許會有莫名的虛榮心以外，當然真的相信架構設計能帶來一些好處的也不在少數。但是通常上行不下效～ 自己一頭熱：\n做設計的人，往往有時候看到一些技術文章，或者是使用一些 open source 的時候，發現別人這樣用相當好，所以自己一頭熱的想使用它，沒有詳細的去瞭解它的來龍去脈，以及當時這樣用的前因後果，這種狀況在初學者身上尤其明顯，我當年就常常這樣 有人系統做爛了，需要收尾： 救火隊的工程師，大概最有感覺，因為前人種的樹根基不穩，或者是遇到臭氣燻天的程式碼，發現原本的寫作方式已經無法在下去了，期望用新的方式來達到目的，順便重新整理一下程式碼 時程來不及：\n往往專案趕、人力少的時候，希望用一些方式來達到專案時程。架構設計有時候也會是其中的一個選項，因為好的架構其實是可以專注的在 Business Logic 上面，而不是不斷的重複 Copy-Paste ~~~\n遇到這種問題的時候，如果不是說別人是因為虛心求教來問的問題，我自己心裡面，常常都會有一個字先冒出來：『靠』，心情不好的時候就是另外一個 f word～ 所以往往做架構設計這種事情，花最多時間的不是在設計，而是在說服別人要去用。包含為什麼要這樣用，以及要怎樣去用它\n可惜常常事不盡人意，我遇過做架構還會把系統搞爛的可能性，也有七八成～\n你不夠力或者主事者不夠力的時候 你沒辦法後續繼續主導的時候 大家只想把一套設計用到爛的時候 你人緣不好的時候 你的想法，沒辦法落實的時候 在《人月神話》這本書有一句蠻經典的話就是：『沒有銀彈』，所有的架構設計，都會因為時間、人員能力、專案狀況而因地制宜，不可能用一套打死所有的狀況。就像是 [GoF 設計模式](http://zh.wikipedia.org/wiki/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F_(%E8%AE%A1%E7%AE%97%E6%9C%BA) 中的 工廠模式，往往根據不同的狀況，會有不同的實作方式，如採用 Reflection, IoC 等都是很好的實作，而工廠模式並不會告訴你，你應該要怎樣去實作，而是告訴你，你可能的概念方向是什麼。\n做到後期，往往發現做架構其實就是在做人，你有好的思考能力、設計能力，但是如果你不能說服人家心甘情願的來使用，那就跟 0 元的股票是一樣的，擦屁股都嫌硬～\n願不願意使用絕對是重點。如果飽了，或者是食慾不振的時候，不管多美味的食物，都不太能挑起食慾。同理，架構設計用或不用，絕對是初期一定要討論的關鍵點，有人願意用，並且會照著你的方式用，這個架構才是有用的牛刀\n如果你不是主導者，或者你沒有有力的主導者幫你忙，不做有時候比硬要做來的好。你要知道，生命會自己找到出路的～～～沒有你，生活一樣悲慘要過\n你做出來的架構，後續要交給其他人去使用，但是你無法抽出時間或你無法參與調整的時候，往往到最後，你會發現人的創意是無限的，事情往往都會超出你的意料之外\n通常，會想要用架構設計的人少之又少，如果能夠不學新東西就可以把事情做完，這樣的人不在少數，所以，學一套作法，已經會讓很多人為之卻步了，所以如果能夠用一套作法打死所有的系統，那是最好。這種架構，你最好不要署名，不然你會發現，越到後期，你背後的怨氣會越來越重，因為架構已經是一輛重的拖不動的牛車了\n不要沒學會爬就想要飛，市面上能找到架構設計的書百百種，但是不會有人告訴你你的專案或系統應該要用哪種架構，但是不要天真的以為把很知名的設計模式用上去就對了，如果你不是很有經驗或者是你真的理解了那些設計模式背後的意義，你還是乖乖用 Copy-Paste 好了，畢竟做對才是最重要的\n對專案參與者的個性跟能力要熟悉。如果你不知道你的同事，到底是不是能接受這樣的作法，那真的你要花費相當多的精神去瞭解以及預防萬一，避免後期會發生你沒辦法收尾的狀況\n做人真的比做架構重要。你如果是個討人厭的傢伙，不管你的能力再好，架構再漂亮，都不會有人願意去用的～\n最近因為換了新東家，對公司生態人員都還不甚熟悉，在默契還不足的情況下，常常會發生我要花比較長的時間去說服別人使用一些他們以前沒用過的東西，所以最近的感觸特別多～\n找了找最近的文章，發現去年才發過類似的文章：愈來愈不喜歡講架構了，因為講的人痛苦，聽的人也痛苦，我想我現在的心情，大概有經過沈澱，已經慢慢的可以收斂了\n","date":"2011年7月7日","permalink":"https://metavige.github.io/2011/07/07/architecture-think-2/","section":"文章","summary":"\u003cp\u003e做架構設計幾年了，發現這幾年來，做最多的不是去研究學習怎樣做，而是去說服人家用這個架構。其實，有時候很討厭聽到下面這句話：\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003e為什麼我們要這樣做？\u003c/p\u003e\n\u003c/blockquote\u003e","title":"有人用的架構，才是好架構"},{"content":"近幾年來的感覺特別明顯，尤其是跟不太熟的人講的時候\n第一，你不知道對方的程度到那邊，講的太多，他們聽不懂; 講的太少，還是聽不懂\n第二，你做了一些設計，但結果大多數都會聽到：『這跟我當初想的不一樣 ~~~』(靠，那你當初為甚麼不想？要我想？？？)\n第三，在你做之前，會告訴你很多願景：『我希望可以共用』、『我希望未來會有延展性』、『我希望很好維護』。結果，大部份等看到實際的設計之後，都會說：『我們需要搞這樣複雜嗎？』\n第四，有些客戶，因為自己覺得自己很厲害，所以常常會提出他自己的看法：『我覺得這樣改比較好』、『這樣應該比較正確吧？』、『這樣的設計才是我當初的構想』。我很想說：『你早說我不就不用想了嗎？』。如果你覺得你比較厲害，那你應該自己做，不應該要我來做。浪費你的時間，我的腦力\n這幾年愈來愈覺得感慨很深\n大多數人希望軟體就像一灘死水，只要寫完就希望不要再去改，但是這幾年的變化卻是愈來愈快、愈來愈多\n等到要把一灘死水攪活的時候，就得找看不怕臭、不怕髒的人下去搞\n或者是把所有的東西就推給廠商，反正只要出一張嘴，其他那是你家的事，付錢的就是大爺\n死的是誰？死的是這些工程師、還有廣大的使用者，因為軟體愈難用，就愈不會有人想用\n而軟體愈難寫，工程師也就會愈來愈偷懶，愈來愈不想動腦筋\n所以漏洞永遠都在，只是有沒有人會去發現而已\n所以說：客戶，你說的都對，是我的錯，我不應該 over design 的\u0026hellip;.\n","date":"2010年9月20日","permalink":"https://metavige.github.io/2010/09/20/architecture-think-1/","section":"文章","summary":"\u003cp\u003e近幾年來的感覺特別明顯，尤其是跟不太熟的人講的時候\u003c/p\u003e","title":"愈來愈不喜歡講架構了，因為講的人痛苦，聽的人也痛苦"}]