[{"content":"","date":"2026年10月15日","externalUrl":null,"permalink":"/tags/ai/","section":"Tags","summary":"","title":"Ai","type":"tags"},{"content":"","date":"2026年10月15日","externalUrl":null,"permalink":"/","section":"Alvin Chen Club","summary":"","title":"Alvin Chen Club","type":"page"},{"content":"","date":"2026年10月15日","externalUrl":null,"permalink":"/categories/","section":"Categories","summary":"","title":"Categories","type":"categories"},{"content":"","date":"2026年10月15日","externalUrl":null,"permalink":"/tags/claude/","section":"Tags","summary":"","title":"Claude","type":"tags"},{"content":"","date":"2026年10月15日","externalUrl":null,"permalink":"/posts/","section":"Posts","summary":"","title":"Posts","type":"posts"},{"content":"","date":"2026年10月15日","externalUrl":null,"permalink":"/tags/","section":"Tags","summary":"","title":"Tags","type":"tags"},{"content":"","date":"2026年10月15日","externalUrl":null,"permalink":"/categories/%E6%8A%80%E8%A1%93%E6%96%87%E7%AB%A0/","section":"Categories","summary":"","title":"技術文章","type":"categories"},{"content":" 前言 # 現在我大部分的程式都是跟 Claude Code 一起寫的。一個專案做下來，claude 開了又關、--continue 接了又接，一個月少說幾十個 session。某天要寫月報，我才發現一個尷尬的事實：這個月到底做了什麼，最完整的紀錄其實就在這些對話裡，但我完全不知道它們存在哪，也沒辦法一次撈出來看。\n這篇記錄我怎麼從「這些對話存在哪裡？」一路做到一個一行指令就能安裝的小工具 recap，以及過程中踩到的幾個坑。\n為什麼現成的方法不夠用？ # 先講我一開始試過的幾條路。\nclaude --resume 可以列出這個資料夾的歷史 session，但它是拿來「跳回某個對話」的，不是拿來「回顧一段時間」的。一次只能看一個，沒辦法依月份彙整。\n社群上也有像 ccusage 這類工具，可以依日、依月統計 token 用量和費用。很實用，但它回答的是「花了多少」，不是「做了什麼」。\n最致命的是第三件事：Claude Code 預設只保留 30 天的對話紀錄，超過的會在啟動時被自動清掉。也就是說，就算我寫了工具，等我想回顧上一季的時候，資料早就不在了。\n所以我要的東西其實很明確：先讓紀錄留得住，再把它依專案、依月份撈出來，最後讓 Claude 自己讀完寫摘要。\n第一步：找到紀錄在哪裡 # Claude Code 會把每個 session 存成一個 JSONL 檔：\n~/.claude/projects/\u0026lt;專案路徑編碼\u0026gt;/\u0026lt;session-id\u0026gt;.jsonl 資料夾名稱是把專案路徑的 / 換成 -，例如 /workspace/next/snq-client 會變成 -workspace-next-snq-client。每一行是一筆紀錄，裡面有 type（user / assistant）、timestamp、cwd、sessionId 和訊息內容。\n直覺上會想「把目前路徑編碼一下，去找對應的資料夾」，但我的 Claude Code 是跑在 Docker 裡的：\nvolumes: - \u0026#34;${STORAGE_PATH}:/workspace\u0026#34; - ~/.claude-data:/root/.claude 主機上的路徑跟容器內的路徑根本不一樣，而且編碼規則也不是完全可逆的。後來的解法是不靠資料夾名稱，直接比對每一筆紀錄裡的 cwd 欄位。這樣不管在哪個環境跑、路徑長什麼樣子，只要 cwd 落在目前專案底下（含子資料夾），就算這個專案的紀錄。\n順帶一提，同一個設定目錄下用 /logout、/login 切換帳號，紀錄會寫在同一個地方，所以兩個帳號的開發紀錄會一起被收錄。\n第二步：讓紀錄留得住 # 保留天數是 settings.json 裡的 cleanupPeriodDays：\n{ \u0026#34;cleanupPeriodDays\u0026#34;: 365 } 改這一行就好，但要注意兩件事。第一，它只能保住「之後」的紀錄，已經被清掉的找不回來，我自己 8 月的紀錄就是這樣沒了大半。第二，我的設定目錄是從主機掛載進容器的，所以要改的是主機上的 ~/.claude-data/settings.json，不是容器裡另外找一份。\n第三步：匯出腳本的設計 # 1. 每個月一個檔案 # .recap/ ├── 2026-08.md ├── 2026-09.md ├── 2026-10.md └── summary-2026-08_2026-10.md 跨月的 session（例如 10/31 晚上開、11/1 繼續）會依每則訊息的時間拆到各自月份。全部收進 .recap/ 資料夾，.gitignore 加一行就好，不會把專案根目錄弄亂。\n2. 只留文字，不留工具呼叫 # JSONL 裡有大量的 tool_use 和 tool_result，讀檔、跑指令、改檔案的完整內容都在裡面。全部匯出的話檔案會膨脹好幾倍，而且對「回顧做了什麼」幫助不大。所以腳本只保留使用者和 Claude 的文字內容。\n3. 用 Node 寫，而不是 Python # 跑 Claude Code 的地方一定有 Node**，所以用 Node 寫、零相依套件，在任何能跑 Claude Code 的環境都能直接執行。\n為什麼不直接叫 Claude 去讀 JSONL？ # 直接說「幫我整理這個月的對話紀錄」不就好了？\n1. 原始資料太大 # JSONL 裡有大量工具呼叫的內容，一個 session 動輒幾 MB。讓 Claude 直接讀，大部分的 context 都花在它不需要的東西上，真正的對話反而被擠掉。\n2. 篩選這件事不該交給模型 # 「哪些紀錄屬於這個專案、哪些落在這個月」是純粹的資料處理，有明確的對錯。交給腳本做，結果每次都一樣；交給模型做，就多了一個可能出錯的環節，而且很難察覺。\n3. 分工清楚，各做擅長的事 # 腳本負責把資料整理乾淨、切好月份；Claude 負責它真正擅長的部分：讀懂對話脈絡，判斷哪些是功能、哪些是 bug、哪些討論最後變成了決策。\n第四步：用 Slash Command 當入口 # 腳本能跑之後，我不想每次都切出去下指令，所以做成 Slash Command。指令檔放在 ~/.claude/commands/recap/ 底下，Claude Code 會自動轉成命名空間的形式：\ncommands/recap/month.md → /recap:month commands/recap/export.md → /recap:export 放在使用者層級的 ~/.claude/commands/，所有專案都能用；而且 ! 執行的指令是在目前專案目錄下跑的，所以在哪個專案輸入 /recap:month，就只會撈出那個專案的紀錄。\n整個流程變成：\n/recap:month 2026-08 2026-10 ↓ 腳本匯出 .recap/2026-08.md、2026-09.md、2026-10.md ↓ Claude 逐月讀完 ↓ 寫出 .recap/summary-2026-08_2026-10.md 第五步：打包成 npm，一行安裝 # npx @virtualorz/recap install 安裝程式會依照跟 Claude Code 相同的規則找設定目錄（CLAUDE_CONFIG_DIR，沒有就用 ~/.claude），然後：\n把 cleanupPeriodDays 改成 365。修改前自動備份；原本設得更長就不動；settings.json 格式壞掉就整個中止，不留半套。\n把腳本複製到設定目錄裡，執行時不依賴 npx，也不需要網路。\n安裝兩個全域指令。如果遇到同名但不是 recap 裝的指令檔，先備份再覆蓋。\n另外加了一個 doctor 指令，專門回答「紀錄到底在哪裡」這個問題，會列出設定目錄、紀錄數量、目前的保留天數和安裝版本。\n紀錄不是為了留著，是為了能被讀 # 做完之後回頭看，這個工具真正解決的不是「匯出」，而是把散落在幾十個 session 裡的開發過程，變成一份可以回頭讀、可以交接、可以拿去寫月報的東西。而讀這些紀錄、整理重點這件最花時間的事，剛好又可以交回給 Claude 自己做。\n如果你也想試試 recap，可以用一行指令安裝：\nnpx @virtualorz/recap install npm：https://www.npmjs.com/package/@virtualorz/recap\ngithub : https://github.com/virtualorz/recap\n","date":"2026年10月15日","externalUrl":null,"permalink":"/2026/10/15/%E6%8A%8A-claude-code-%E5%B0%8D%E8%A9%B1%E7%B4%80%E9%8C%84%E6%95%B4%E7%90%86%E6%88%90%E9%96%8B%E7%99%BC%E5%9B%9E%E9%A1%A7-recap/","section":"Posts","summary":"","title":"把 Claude Code 對話紀錄整理成開發回顧 : recap","type":"posts"},{"content":"","date":"2026年10月15日","externalUrl":null,"permalink":"/categories/%E5%85%B6%E4%BB%96%E9%A1%9E%E5%9E%8B/","section":"Categories","summary":"","title":"其他類型","type":"categories"},{"content":" 前言 # 最近在嘗試把 OpenSpec 導入日常開發,目標是在 Claude Code 寫程式前先做規格對齊\n但我發現OpenSpec不適合用在我的開發情境。與其勉強自己用,不如基於 OpenSpec 的核心精神,重新設計一個適合自己的版本。\n整個過程從「為什麼現成工具不適合」到「該怎麼設計才適合」,中間踩過不少坑記錄一下。\n為什麼 OpenSpec 不適合? # OpenSpec 是個跨工具、跨團隊的規格協議,設計目標是讓不同 AI 工具(Claude Code、Cursor、Aider 等)在做架構規劃時有一致的提案格式。它強迫開發者走完整的 propose → apply → archive 三階段流程,每個階段都有完整的文件產出。\n對於有十幾個工程師的大型專案,這種儀式感是合理的。但對於我這種小團隊、單人主導的日常開發,價值密度太低。\n舉個具體的例子:有一次我只想改 30 行 Controller 裡的快取邏輯,OpenSpec 卻產出了好幾萬字的提案文件要我審查。我看完那份文件的時間,比我直接改 code 還久。\n其實我只需要做到兩件事:\n1.動手前讓我看清楚 Claude 的設計意圖\n2.對齊我的專案規範\n於是我決定動手做一個輕量版本 : specflow。\n第一步:用 Skill 讓 Claude 認識我的工作流 # 在動手寫之前,得先了解 Claude Code 的兩個關鍵機制: Skill 與 Slash Command\nSkill 是 Anthropic 推出讓 Claude 能在特定情境自動載入「補充指示」的機制。它的本質是一個資料夾,結構長這樣:\n.claude/ └── skills/ └── specflow/ ├── SKILL.md ← 主入口 └── templates/ ← 模板檔 ├── issue.md ├── design.md └── task.md 主要看 SKILL.md 開頭的 YAML:\nname: specflow description: A lightweight spec-driven development workflow for Laravel projects. Use this skill when the user invokes /spec:new, /spec:design, /spec:task, or /spec:run commands, refers to files in the specflow/ folder... 這個 description 就是觸發機制 —— Claude 在跟你對話時,會根據你的訊息內容判斷「這個 skill 跟當前對話有沒有關係」。如果有,Claude 會\u0026quot;自動載入\u0026quot; SKILL.md 的內容當作補充指示。\n但有個重要觀察:Skill 的觸發是模糊的。如果使用者用自然語言說「幫我規劃一下這個重構」,Skill 可能被觸發、也可能沒有。即使被觸發了,Claude 也只是「參考」這份 SKILL.md,不一定會嚴格照著執行。\n對於需要精確控制每一步行為的工作流,光靠 Skill 不夠。所以我加上第二層機制:Slash Command。\n第二步:用 Slash Command 取代自然語言觸發 # Slash Command 是 Claude Code 的另一個機制,讓使用者可以用 /指令名 觸發特定行為。它的存放位置是 .claude/commands/。\n這次我設計了四個指令:\n.claude/ └── commands/ └── spec/ ├── new.md → /spec:new ├── design.md → /spec:design ├── task.md → /spec:task └── run.md → /spec:run 注意檔案結構 —— 子資料夾 spec/ 加上檔名 new.md,Claude Code 就會把它對應成 /spec:new 這個指令(冒號分組)。\n每個 .md 檔案的內容就是「這個指令該做什麼」的 prompt。例如 new.md 會明確指示:\n驗證 task name 是否符合命名規則(只允許小寫英文 + hyphen)\n檢查資料夾是否已存在\n建立資料夾、複製 issue.md template\n提示使用者填寫\n為什麼用Slash Command 比自然語言要好? # 我一開始也覺得「不就一個指令名稱嗎,跟自然語言差在哪?」實際做下去才發現差距很大,主要有三點:\n1. 意圖明確，而且可以少打很多字 # 如果用自然語言,「issue.md 寫好了」跟「幫我看一下 issue」對 Claude 來說很可能觸發同一個流程,但你期待的行為不同 —— 前者是「請進入 design 階段」,後者是「請審查 issue」。\n用 /spec:design 之後,意圖是強制宣告的,沒有解釋空間。\n2. Prompt 內容固定,行為穩定 # Slash command 的 prompt 是寫死的檔案。你執行 /spec:design 一百次,Claude 收到的指示一字不差。\n對比自然語言:你今天說「幫我設計這個」,明天說「幫我規劃一下」,雖然意思一樣,但 Claude 的解讀可能微妙不同,行為跟著飄。\n3. 可以掛 bash 命令做檢查 # Slash command 的 prompt 可以用 ! 前綴執行 bash 命令,把輸出注入到指令的執行 context。例如我在 /spec:design 加了:\n!`test -f specflow/project.md` 如果 project.md 不存在,Claude 會看到 exit code != 0,立刻停下來提醒使用者建檔。純自然語言很難做到。\n第三步:三個 MD 檔的設計 # specflow 的核心是三個 markdown 檔案:issue.md、design.md、task.md,每個檔案職責清楚分離。\nissue.md:使用者寫的需求描述 # 這是唯一由使用者填寫的檔案,template設計已經規範使用者需要填寫哪些資料\n講白話文就是把prompt寫在這個檔案內當成開發紀錄\n# Issue: \u0026lt;一句話標題\u0026gt; ## 想解決的問題 \u0026lt;2~3 句話描述現況的痛點\u0026gt; ## 期望的結果 \u0026lt;改完之後系統應該變成什麼樣\u0026gt; ## 範圍限制(必填) - 只動: - 不動: - 不處理(留待後續): ## 額外提示(選填) \u0026ldquo;範圍限制必填\u0026quot;這條規則。這是治療 Claude「過度設計」的關鍵防線，沒有明確邊界,Claude 永遠會傾向多做一些;有了邊界,它知道該在哪停下來。\ndesign.md:Claude 提出的設計決策 # 這是 /spec:design 產出的檔案。我要claude強制用 checkbox 形式列出每條設計決策,而且每條決策都要附「理由 + 替代方案」:\n## 決策清單 - [ ] **資料表命名採 Laravel 複數慣例**:`meeting_rooms`、`schedules` - 理由:Laravel Eloquent 預設以複數表名對應單數 Model - 替代方案:照 issue.md 字面用單數 —— 否決原因為偏離 Laravel 生態慣例 開發者只要確認每一項描述都是正確的就在[ ]中打 X 讓claude知道這條沒問題\n有問題的部分也可以填寫在下方問題清單持續跟cladue討論\ntask.md:Claude 寫的、Claude 執行的清單 # 這是 /spec:task 產出的檔案,顆粒度規定「每項任務 5 分鐘內可完成」:\n## 執行清單 - [ ] 1. 建立 `app/Contracts/CacheableApiClientContract.php` - 檔案路徑:`app/Contracts/CacheableApiClientContract.php`(新增) - 內容:定義介面,包含 `get(string $key): mixed` 跟 `set(string $key, mixed $value, int $ttl): void` 顆粒度越細,Claude 越難偏離計畫。一個 5 分鐘的任務就是「在 X 檔案新增 Y method」,沒有自由發揮的空間。\n對比 OpenSpec 那種「每個任務 2 小時內完成」的設計,5 分鐘版本看似囉唆,但實際執行時 Claude 幾乎不會跑偏。這是用形式上的繁瑣換實質上的可控性。\n整套工作流串起來:從 issue 到 code # 現在所有零件都備齊了,完整的工作流長這樣:\n/spec:new \u0026lt;task-name\u0026gt; 建立資料夾 + issue.md template ↓ (使用者填寫 issue.md) /spec:design \u0026lt;task-name\u0026gt; 讀 project.md + issue.md → 產出 design.md ↓ (使用者審查並勾選決策清單) /spec:task \u0026lt;task-name\u0026gt; 讀 design.md → 產出 task.md ↓ (使用者審查執行清單) /spec:run \u0026lt;task-name\u0026gt; 逐項執行 + 寫執行後備註 每個階段中間都有 checkpoint,Claude 必須等使用者明確下指令才會繼續。\n還有一個關鍵設計:討論模式 # 審查 design.md 的時候,難免會有疑問:「決策 4 為什麼選這個?」、「決策 5 要不要加 is_active 欄位?」\n我在 design.md template 加了一個「待討論問題」區塊,讓使用者直接寫下疑問。/spec:task 執行時會先檢查這個區塊:\n| 條件 1(決策清單) | 條件 2(待討論問題) | 執行的流程 | |---|---|---| | 全勾選 | 無問題 | 產 task 模式 | | 全勾選 | 有問題 | 討論模式 | | 未全勾選 | 無問題 | 提示勾選 | | 未全勾選 | 有問題 | 討論模式(優先處理討論) | 進入討論模式後,Claude 會逐題回答、修改受影響的決策、把該決策的 checkbox 重置為 [ ]、清空「待討論問題」區塊、最後把問題摘要搬到「已討論問題」區塊保留脈絡。\n這樣設計有幾個優點\n/spec:task 不需要靠 Claude「記得停下來」,它的閘門條件是兩個檔案狀態:決策清單全勾 + 待討論清空。狀態不滿足就不可能產出 task,這是檔案決定的、不是記憶決定的。\n傳統做法是討論散落在對話 history 裡,3 個月後沒人記得。我讓討論摘要自動進到 design.md 的「已討論問題」區塊,跟著 git 一起保存,變成決策演進的歷史。\nRead 工具的快取陷阱 # 實作才知道read的bug\n1. 我跑 `/spec:design`,Claude 產出 design.md 2. 我在編輯器修改 design.md(勾選決策、寫討論問題) 3. 我跑 `/spec:task`,Claude 卻說「沒有變更,issue.md 還是 template 原文」 明明我改了檔案,Claude 卻看不到。\n找半天原來是Claude Code 的 Read 工具有快取機制，果 session 中讀過某個檔案,再次 Read 會回傳快取版本\n解法是強制用 cat 命令繞過快取:\n!`cat specflow/changes/$ARGUMENTS/issue.md` 工具是來服務你的,不是反過來 # 過去我會覺得「現成工具用就對了,自己做太花時間」。但這次讓我發現,在 LLM 工具普及的時代,造一個小輪子的成本已經低到不該成為阻力。從設計到 npm publish,我跟 Claude 來回討論大約 4 小時就完成了。\n如果你也想試試 specflow,可以用一行指令安裝:\nnpx @virtualorz/specflow init 附上專案Github : https://github.com/virtualorz/specflow\n","date":"2026年4月30日","externalUrl":null,"permalink":"/2026/04/30/%E7%94%A8-claude-code-%E6%89%93%E9%80%A0%E5%B1%AC%E6%96%BC%E8%87%AA%E5%B7%B1%E7%9A%84%E8%BC%95%E9%87%8F%E8%A6%8F%E6%A0%BC%E9%A9%85%E5%8B%95%E5%B7%A5%E4%BD%9C%E6%B5%81-specflow/","section":"Posts","summary":"","title":"用 Claude Code 打造屬於自己的輕量規格驅動工作流 : specflow","type":"posts"},{"content":"套件名稱 : jsadways/operationRecord\n開發年份 : 2025\ngitHub連結 :https://github.com/JsAdways/operationRecord\n內容簡介 :\n透過包裝好的trait提供快速的寫入與讀出操作紀錄到mongoDB中，同時支援同步與非同步寫入，讀取的部分支援scoprFilter套件中的格式輕鬆撰寫邏輯\nLaravel：100% 安裝方式 :\ncomposer require jsadways/operationrecord 詳細說明:\n安裝完成後可以透過以下指令直接在專案生成用於寫入與讀出紀錄的Model檔案\nphp artisan make:operation-record Record --table=example_record 其他mongoDB架設說明與trait,facade,service使用方式可以參考:\nhttps://packagist.org/packages/jsadways/operationrecord\n","date":"2026年1月12日","externalUrl":null,"permalink":"/2026/01/12/%E4%BD%9C%E5%93%81laravel-packeage-operationrecord-%E7%B0%A1%E6%98%93%E5%AF%AB%E5%85%A5%E6%93%8D%E4%BD%9C%E7%B4%80%E9%8C%84/","section":"Posts","summary":"","title":"[作品][Laravel Packeage] operationRecord : 簡易寫入操作紀錄","type":"posts"},{"content":"","date":"2026年1月12日","externalUrl":null,"permalink":"/tags/laravel/","section":"Tags","summary":"","title":"Laravel","type":"tags"},{"content":"","date":"2026年1月12日","externalUrl":null,"permalink":"/tags/laravel-pacakge/","section":"Tags","summary":"","title":"Laravel Pacakge","type":"tags"},{"content":"","date":"2026年1月12日","externalUrl":null,"permalink":"/categories/laravel-package/","section":"Categories","summary":"","title":"Laravel Package","type":"categories"},{"content":"","date":"2026年1月12日","externalUrl":null,"permalink":"/categories/%E4%BD%9C%E5%93%81%E6%95%B4%E7%90%86/","section":"Categories","summary":"","title":"作品整理","type":"categories"},{"content":"套件名稱 : jsadways/laravel-sdk\n開發年份 : 2025\ngitHub連結 :https://github.com/JsAdways/laravel-sdk\n內容簡介 :\n因應更快速地開始開發，集合過往Controller , Model , Repository , Dto 以及scopeFilter, DataApi等套件，集合而成\n一道指令即可開始寫商業邏輯，兩道指令完成基礎建置\nLaravel：100% 安裝方式 :\ncomposer require jsadways/laravel-sdk 詳細說明:\n安裝完成後可以開始撰寫migration，接著透過init指令依照mirgration後的內容來產生需要的文件\n例如: BaseController, 所有的Mode.php但有_schema()內容, Repository, 以及帶有Create, Update, Read的route/api.php\nphp artisan laravel-sdk:code-init API 詳細說明可參閱\nhttps://packagist.org/packages/jsadways/laravel-sdk\n","date":"2025年8月5日","externalUrl":null,"permalink":"/2025/08/05/%E4%BD%9C%E5%93%81laravel-packeage-laravel-sdk-%E5%BF%AB%E9%80%9F%E9%96%8B%E7%99%BC%E5%A5%97%E4%BB%B6/","section":"Posts","summary":"","title":"[作品][Laravel Packeage] laravel-sdk : 快速開發套件","type":"posts"},{"content":"","date":"2025年8月5日","externalUrl":null,"permalink":"/tags/%E4%BD%9C%E5%93%81%E9%9B%86/","section":"Tags","summary":"","title":"作品集","type":"tags"},{"content":"","date":"2025年8月1日","externalUrl":null,"permalink":"/categories/laravel/","section":"Categories","summary":"","title":"Laravel","type":"categories"},{"content":"","date":"2025年8月1日","externalUrl":null,"permalink":"/tags/%E5%B7%A5%E4%BD%9C%E9%9B%86/","section":"Tags","summary":"","title":"工作集","type":"tags"},{"content":" 前言: # 繼上一篇提到，將Controller, Model, Repository的命名統一之後\nhttps://www.alvinchen.club/2025/07/25/controller-model-repository-%e6%90%ad%e9%85%8ddto%e7%9a%84%e5%bf%ab%e9%80%9f%e9%96%8b%e7%99%bc%e6%9e%b6%e6%a7%8b/\n我們可以得到一個非常簡單的Create流程\npublic function create(Request $request): Model { return $this-\u0026gt;repo-\u0026gt;create(new $this-\u0026gt;__dto__(...$request-\u0026gt;json())); } 而我們做系統，最常遇到的不外乎Create , Update, Delete, Read 四種資料流程了\n因此我們更近一步的除了Create以外，也把其他流程做完，並且包含在BaseController中，也就成了一個GenenralApi了\n怎麼做: # 依照上面的邏輯，我們統整如下\npublic function create(Request $request): Model { return $this-\u0026gt;repo-\u0026gt;create(new $this-\u0026gt;__dto__(...$request-\u0026gt;validated())); } public function read_list(ReadListRequest $request): LengthAwarePaginator|Collection { return $this-\u0026gt;repo-\u0026gt;read_models(new $this-\u0026gt;__dto__(...$request-\u0026gt;validated())); } public function update(Request $request): Model { return $this-\u0026gt;repo-\u0026gt;update(new $this-\u0026gt;__dto__(...$request-\u0026gt;validated())); } public function delete(Request $request): array { $this-\u0026gt;repo-\u0026gt;delete_model($request-\u0026gt;json()-\u0026gt;get(\u0026#39;id\u0026#39;)); } 可以發現，基本上就是呼叫repository中預先定義好的方法，然後傳入該有的Dto資訊\n為了方便管理，我們將以上寫成Traits，大概像這樣子\ntrait GeneralApi { public function create(Request $request): Model public function read_list(ReadListRequest $request): LengthAwarePaginator|Collection public function update(Request $request): Model public function delete(Request $request): array } 然後在BaseController中引用進來\nclass BaseController extends Controller { use GeneralApi; } 這樣一來，所有繼承了這個BaseController的Controller都已經擁有預設的CRUD Api邏輯了\n如果照上一篇的範例來看，我的CustomerController幾乎不用寫，只要extend BaseController即可\n例外的調整怎麼辦 : # 寫到這邊一定有人會說，哪有可能這麼剛好每一個api都只要寫入資料就可以\n是的，沒有錯當然事情不會這麼簡單，我們還是舉CustomerController為例\n當今天要新增一筆客戶資料的時候，需要多做額外的商業邏輯可以怎麼做呢？\nclass CustomerController extends BaseController { public function create(Request $request): Model { //這裡寫其他需要處理的邏輯 //$payload = ........... $request-\u0026gt;replace($payload); return parent::create($request); } } 答案就是，覆寫就可以了，這個範例覆寫了create function，在這個之中可以客製化的描述所需要的邏輯\n最後，將處理完要入庫的資料放在$payload中，replace回$request\n最後一行，我們還是呼叫parent::create來做入庫的動作\n這樣一來，無論需要做到多複雜的邏輯CRUD基本上就已經做完\n","date":"2025年8月1日","externalUrl":null,"permalink":"/2025/08/01/%E7%B3%BB%E7%B5%B1%E9%96%8B%E7%99%BC%E6%87%B6%E4%BA%BA%E5%A4%A7%E7%B5%95%E6%8B%9B-generalapi/","section":"Posts","summary":"","title":"系統開發懶人大絕招: GeneralAPI","type":"posts"},{"content":" 前言： # 在開發系統的這段期間，我們寫了N支API，然而每個操作的動作都離不開Create, Update, Read, Delete 這些動作\n為了加速以及更懶惰(誤)，我們團隊討論出一個夠嚴謹也可以更快開發的架構\n結構說明： # Controller : 作爲Api的入口也作為整個資料流程的控管，其他的事情都讓Service、Repository來處理\nModel : 定義資料表內容、定義資料驗證規則、定義表與表之間的relation\nRepository : 作為資料操作的動作定義，直接控制Model做資料存取\nDto們 : 作為資料傳遞的介面，嚴格定義傳入Controller或者傳入Service的資料結構，這樣做可以一定程度的避免錯誤\n一切的開端: # 當每個API都需要Create, Update, Read, Delete這些方法的時候，我們第一個想到的是\n那就寫個抽象類別來繼承好了，這樣就可以把需要的方法都先定義好，大約可以解決6成以上重複性的原始碼\n但是面臨的就是下一個問題，舉例來說每一次的Create如果沒有特別需要處理資料那麼都是從Controller中呼叫Reposiroty的Create function就結束了\n這樣的話，是不是可以再寫的更簡單一些?\n加速原則 : # 相同的命名原則，讓開發可以更快\n舉個簡單的例子先，如果我們要對Customer做Create，也就是新增一筆客戶資料\n那麼資料流應該會長這樣子\nCustomerController -\u0026gt; CustomerRepository(Create function) -\u0026gt; Customer Model (write data) 因此我們寫了一個BaseController，在其中定義好repository_name, model_name 以及Dto\n而且預設就是跟Controller相同的名字，整理一下上述案例，在CustomerController中，我們只要繼承了BaseController就會擁有以下\nCustomerRepository CustomerModel CreateCustomerDto \u0026lt;--- 以Create作為範例的話 為了要在Controller一初始化就把以上物件準備好，我們會需要用上之前提過的callAction\nhttps://www.alvinchen.club/2025/06/09/laravelcontroller%e4%b8%ad%e7%9a%84callaction/\n我們在BaseController中先定義幾件事情\nprivate string $__model_root__ = \u0026#39;App\\\\Models\\\\\u0026#39;; //存放Model的路徑 private string $__dto_root__ = \u0026#39;App\\\\Core\\\\Repository\\\\\u0026#39;; //存放Dto的路徑 private BaseModel|Authenticatable $__main_model__; //預設跟Controller同名的Model private ?string $__dto__; //需要用上的Dto 名字 private Repository $repo; //預設跟Contrller同名的Repository private string $name; //預設就是Controller的名字 接著定義幾個init_開頭的function，用來初始化以上的變數內容\nprotected function init_start(): static { $this-\u0026gt;name = str_replace(\u0026#39;Controller\u0026#39;, \u0026#39;\u0026#39;, class_basename(static::class)); return $this; } protected function init_db_source(): static { if (class_exists($this-\u0026gt;__model_root__.$this-\u0026gt;name)) { $this-\u0026gt;__main_model__ = new ($this-\u0026gt;__model_root__.$this-\u0026gt;name); $this-\u0026gt;repo = $this-\u0026gt;repository($this-\u0026gt;name); } return $this; } protected function init_dto(): static { $this-\u0026gt;__dto__ = match ($this-\u0026gt;get_method()) { \u0026#39;create\u0026#39; =\u0026gt; \u0026#34;{$this-\u0026gt;__dto_root__}{$this-\u0026gt;name}\\\\Dtos\\\\Create{$this-\u0026gt;name}Dto\u0026#34;, \u0026#39;update\u0026#39; =\u0026gt; \u0026#34;{$this-\u0026gt;__dto_root__}{$this-\u0026gt;name}\\\\Dtos\\\\Update{$this-\u0026gt;name}Dto\u0026#34;, \u0026#39;read_list\u0026#39; =\u0026gt; ReadListParamsDto::class, default =\u0026gt; Null }; return $this; } 簡單說明一下這幾個function，分別是一句Controller的名稱初始化name , main_model , repo , dto 等等這幾個變數\n接著在我們覆寫掉callAction這個function，讓以上幾個function可以正確的執行過一次\npublic function callAction($method, $parameters) { foreach (get_class_methods($this) as $method) { if (str_starts_with($method, \u0026#39;init_\u0026#39;)) { $this-\u0026gt;{$method} } } return parent::callAction($method, $parameters); } 以上這幾行，就是簡單的把init_開頭的function找到，然後透過動態呼叫全部執行一次\nhttps://www.alvinchen.club/2025/06/13/php-%e5%8b%95%e6%85%8b%e5%91%bc%e5%8f%ab%e5%87%bd%e6%95%b8variable-functions%e8%88%87see/\n那Repository做什麼用？ # Repository作為直接操作Model進行資料存取的元件，我們也是做了一層抽象處理，這樣這樣一來基本的Create, Update, Delete, Read都可以描述完成\n每個各別的Repository繼承後只要描述關於relation的部分就可以了，大致像這樣子\npublic function create(Dto $params, ?Model $model=Null, ?array $relation=Null): Model { $payload = $params-\u0026gt;to_array(); $relations = $relation !== Null ? $relation : $this-\u0026gt;__create_relations__; $model = $model ? new ($model) : $this-\u0026gt;__model__; try{ DB::beginTransaction(); $instance = $model-\u0026gt;create($payload); $this-\u0026gt;_relation_processor($instance, $payload, $relations); $instance-\u0026gt;load($relations); DB::commit(); return $instance; }catch (Throwable $throwable) { DB::rollBack(); throw new Exception(\u0026#34;{$model}新增錯誤: {$throwable-\u0026gt;getMessage()}\u0026#34;); } } 舉Create的例子來說，透過定義好的Dto傳入需要寫入的正確資料，然後讓relation_processor處理相關聯表單所需要寫入的資料\n這麼一來，我們回到CustomerController的Create function中，就只剩下以下內容\npublic function create(Request $request): Model { return $this-\u0026gt;repo-\u0026gt;create(new $this-\u0026gt;__dto__(...$request-\u0026gt;json())); } 邏輯上是把傳入值塞入CreateCustomerDto中，如果這時候傳入的資料不是預期的欄位就會直接報錯\n接著將new出來的CreateCustomerDto交給CustomerRepository的Create function處理\n是不是變得有夠簡單好寫呢？\n","date":"2025年7月25日","externalUrl":null,"permalink":"/2025/07/25/controller-model-repository-%E6%90%AD%E9%85%8Ddto%E7%9A%84%E5%BF%AB%E9%80%9F%E9%96%8B%E7%99%BC%E6%9E%B6%E6%A7%8B/","section":"Posts","summary":"","title":"Controller Model Repository 搭配Dto的快速開發架構","type":"posts"},{"content":" 前言: # 上一篇文章先列下了關於data_api的composer套件，這邊來記錄一下這個套件的開發緣由以及提供哪些功能\nhttps://www.alvinchen.club/2025/07/07/%e4%bd%9c%e5%93%81laravel-packeage-data_api-%e8%b7%a8%e7%b3%bb%e7%b5%b1%e8%b3%87%e6%96%99%e5%ad%98%e5%8f%96%e5%8d%94%e8%ad%b0/\n還記得更早之前的一個套件scopFilter，他的出現是為了在大部分的時間更簡單的寫query條件\nhttps://www.alvinchen.club/2024/06/21/%e4%bd%9c%e5%93%81laravel-packeage-scopefilter-%e8%90%ac%e7%94%a8%e7%9a%84%e5%bf%ab%e9%80%9f%e8%b3%87%e6%96%99%e6%90%9c%e5%b0%8b%e5%a4%96%e6%8e%9b/\n沒想到因為這個filter可以再更近一步被使用，因為我們開發的每一套系統都已經安裝的scopFilter，所以我們可以透過定義一個介面來驅動scopeFiter\n近一步做到跨系統的資料存取，這也是data_api發展的最主要原因\n# 規格 # 首先來說說data_api的規格，我們定義了幾項入口\nfetch入口 get入口 fetch作為讓所有有裝data_api的系統統一呼叫的入口，開發者不用想太多直接call fetch就對了，而get才是目標系統實際上去把資料取出的地方\n# 分類 # data_api可以取得哪些資料呢？\n目標系統透過filter可以拿到的範圍都沒有問題 目標系統有開放出來的process api，帶入token以及參數可以正確取得 存取n8n上設計好的訊息通知流程 使用方法 # 後端透過定義好的crossService可以直接傳入特定Dto存取特定資料 後端透過定義好的crossFacade也可以少去new service的步驟傳入特定Dto存取特定資料 後端透過command line 也提供各方法可用參數查詢 特色說明 # 我們在CrossService中設計了一個固定流程\n$this-\u0026gt;_set_payload($payload) -\u0026gt;_fetch_system_host() -\u0026gt;_prepare_data_stream() -\u0026gt;_send(); 也就每一個請求無論你要存取哪種資料都回經過這幾個步驟\n儲存傳進來的payload 尋找目標系統URL 整理要送出的資料內容 實際將請求送出 這邊有幾點特別的可以說明，也就是上面提到的第三點跟第四點\n這邊我們實作了兩個執行器，讓我來一一說明\n第一個\nprotected function _prepare_data_stream(): static { $this-\u0026gt;dataStreamService = $this-\u0026gt;dataStreamManager-\u0026gt;get($this-\u0026gt;system_host,$this-\u0026gt;payload); return $this; } dataStreamManager是第一個執行器，我們後透過傳進去給get的第二的參數判定這個參數的型態而動態生成所需要的Dto物件\npublic function get(string $system_host,PayloadContract $payload): DataStreamContract {} 這個get function收的第二個三數只是要實作PayloadContract的class物件都可以，而return值則是實作了DataStreamContract的class物件都可以\n這樣一來我們就可以透過實際上傳入的Payload物件來判斷需要生成的Stream物件該是哪個型態\n另一方面也可以規範好要實作PayloadContract的所有Payload都需要有一個fecth的方法，下一個步驟會提到\n第二個\nprotected function _send(): array { return $this-\u0026gt;dataStreamService-\u0026gt;fetch(); } 第四個步驟send是實際把資料送出去的環節，因為每個需求所需要的目標都不同，因此這邊還是留了一個彈性\n透過上一個步驟生成的dataStreamServer物件，統一都呼叫fetch方法，這樣就能夠讓不同類型的物件實際上透過不同邏輯來發出請求\n很特別的東西 # 1.因應各系統開放出來的不同api文件需求，我們設計透過command來取得不同系統中process api的參數使用方式\n首先，各系統要開出來的process api都需要在controller function上加入attribute，就像這樣子\n#[ServiceAPI(name:\u0026#39;id_number\u0026#39;,type: \u0026#39;string\u0026#39;,required: true,description: \u0026#39;統一編號\u0026#39;)] #[ServiceAPI(name:\u0026#39;name\u0026#39;,type: \u0026#39;string\u0026#39;,required: true,description: \u0026#39;公司名字\u0026#39;)] public function company_verify(Request $request): bool {} 透過command lined 可以查詢每個系統的process api所需參數\nphp artisan data-api:list 2.因應n8n上串接的每個通知平台所需要的資料格式完全不同，即便是json也不好無腦寫出來，因此定義了一系列的Dto\n透過這些Dto，我們可以輕鬆產出各平台所需要的json apyload\n例如:\nnew CrossNotificationDto( system: \u0026#39;n8n\u0026#39;, token: \u0026#39;LOGIN TOKEN\u0026#39;, platform: Platform::Slack, payload: (new SlackBlocksPayloadDto( blocks:[ new SlackContextDto( elements: [ ] ) ] ))-\u0026gt;get() )); 這樣的payload內容就會是slack的block訊息\n","date":"2025年7月11日","externalUrl":null,"permalink":"/2025/07/11/%E8%B7%A8%E7%B3%BB%E7%B5%B1%E8%B3%87%E6%96%99%E4%BB%A5%E5%8F%8Aapi%E5%AD%98%E5%8F%96%E6%A0%B8%E5%BF%83-data_api-%E5%A5%97%E4%BB%B6/","section":"Posts","summary":"","title":"跨系統資料以及API存取核心 data_api 套件","type":"posts"},{"content":"套件名稱 : js-adways/data_api\n開發年份 : 2025\ngitHub連結 : https://github.com/JsAdways/dataApi\n內容簡介 :\n在我們開發的多套系統中，因應各系統之前資料存取的方便性而延伸出來的套件\nLaravel：100% 安裝方式 :\ncomposer require jsadways/data-api 詳細說明:\ndata_api的入口統一為\n(new CrossService())-\u0026gt;fetch() fetch中會根據傳入不同類型的參數而有以下幾個方向的資料存取\n1.CrossDataDto : 取得跨系統filter可取得的資料\n2.CrossProcessDto : 存取跨系統processAPI所開放出來的資料，這邊需要帶入token做驗證\n3.CrossNotificationDto : 將資料送往n8n server傳遞各平台通知訊息\n","date":"2025年7月7日","externalUrl":null,"permalink":"/2025/07/07/%E4%BD%9C%E5%93%81laravel-packeage-data_api-%E8%B7%A8%E7%B3%BB%E7%B5%B1%E8%B3%87%E6%96%99%E5%AD%98%E5%8F%96%E5%8D%94%E8%AD%B0/","section":"Posts","summary":"","title":"[作品][Laravel Packeage] data_api : 跨系統資料存取協議","type":"posts"},{"content":"套件名稱 : js-adways/authenticator\n開發年份 : 2024\ngitHub連結 : https://github.com/JsAdways/js_Authenticator\n內容簡介 :\n多系統開發中很重要的一環，負責各系統使用者登入以及使用權限資料取得的統一處理套件\nLaravel：100% 安裝方式 :\ncomposer require jsadways/authenticator publish 設定擋到專案中\nphp artisan vendor:publish --provider=\u0026#34;Js\\Authenticator\\Providers\\AuthServiceProvider\u0026#34; 在專案.env中加入以下資訊\n1.驗證中心的URL\n2.本系統前端露出權限json的URL\nJS_AUTH_HOST=\u0026#39;http://authenticate.tw\u0026#39; FORESTAGE_URL=\u0026#39;http://172.16.1.156:3100/struct\u0026#39; 使用方式，在router/api.php或router/web.php中加入middleware\n// 在需驗證位置加入 js-authenticate-middleware-alias 中間件 Route::middleware([\u0026#39;js-authenticate-middleware-alias\u0026#39;])-\u0026gt;group(function () { // 路徑 }); 其他說明可參考 : Laravel 環境多系統登入驗證架構設計\n","date":"2025年6月25日","externalUrl":null,"permalink":"/2025/06/25/%E4%BD%9C%E5%93%81laravel-packeage-authenticator-%E8%B7%A8%E7%B3%BB%E7%B5%B1%E7%99%BB%E5%85%A5%E9%A9%97%E8%AD%89%E5%9F%B7%E8%A1%8C%E5%99%A8/","section":"Posts","summary":"","title":"[作品][Laravel Packeage] authenticator : 跨系統登入驗證執行器","type":"posts"},{"content":"前言：\n因應公司內部系統開發，我們將每個單位所使用的系統拆分開發，分散運行\n因此在使用者登入檢查這一塊我們也開發了獨立的驗證、取得權限的套件，方便前端開發者可以直接使用登入以及取得權限的URL\n系統架構: # 如上圖所示，我們首先開發第一套驗證中心系統，主要邏輯負責管理人員、使用權限等資料\n緊接著開發Auth套件，這個套件負責發出以及接收登入、取用權限等等API資料流\n未來在每一套系統中，只要安裝這個Auth套件後端就已經完成了登入處理的作業\n資料流: # 由前端發起登入需求，送出帳號密碼資訊到自己系統(A)後端api/auth/login (紅線) Auth套件收到需求後會找到驗證中心的Auth URL進行資料驗證 (紅線) 驗證中心處理完成後返回jwt token到(A)系統後端 (綠線) (A)系統收到jwt token後返回給前端 (綠線) 前端再次發起取用(A)系統權限到自己係統(A)後端api/auth/permission/{system} (紫線) Auth套件收到需求後會找到驗證中心的Permission URL進行權限資料存取 (紫線) 驗證中心處理完成後返回登入者所有使用權限到(A)系統後端 (黃線) (A)系統收到權限json後返回給前端 (黃線) 以上8個步驟完成所有登入流程\n詳細可參考下篇 : [package]\n","date":"2025年6月23日","externalUrl":null,"permalink":"/2025/06/23/laravel-%E7%92%B0%E5%A2%83%E5%A4%9A%E7%B3%BB%E7%B5%B1%E7%99%BB%E5%85%A5%E9%A9%97%E8%AD%89%E6%9E%B6%E6%A7%8B%E8%A8%AD%E8%A8%88/","section":"Posts","summary":"","title":"Laravel 環境多系統登入驗證架構設計","type":"posts"},{"content":" 前言: # 今天想來記錄一下近期在Laravel專案中用到策略模式的實際演練，這樣的方式無非是解耦每個class所負責的事情，以及可以更加彈性的設計每個class的內容\n外部參考資訊 : https://www.runoob.com/design-pattern/strategy-pattern.html\n範例: # 今天的範例是我們需要設計出訊息傳送的中央平台，可以將資料傳遞到Email、SMS、Slack等三個平台，也需要預留未來擴充其他平台的可能性\n過往的寫法:\n以往我們可以會設計三個ServiceClass，分別用於寄送三個平台的訊息例如: EmailService、SMSService、SlackService，然後在Controller中呼叫他像這個樣子\nclass EmailService { public function Send(string $receiver, string $content){ .... /* 實際實行寄送Email */ .... } } class SMSService { public function Send(string $receiver, string $content){ .... /* 實際實行寄送SMS */ .... } } class SlackService { public function Send(string $receiver, string $content){ .... /* 實際實行寄送Slack */ .... } } 然後在Controller中呼叫\nclass MessageController extend Controller { public function index(Request $request){ if($request-\u0026gt;get(\u0026#39;platform) === \u0026#39;email\u0026#39;){ new EmailService()-\u0026gt;send(...$request-\u0026gt;all()); } if($request-\u0026gt;get(\u0026#39;platform) === \u0026#39;SMS\u0026#39;){ new SMSService()-\u0026gt;send(...$request-\u0026gt;all()); } if($request-\u0026gt;get(\u0026#39;platform) === \u0026#39;Slack\u0026#39;){ new SlackService()-\u0026gt;send(...$request-\u0026gt;all()); } } } 當然，我們也可以調整成上一篇提到的動態呼叫的寫法，讓原始碼更簡潔，像是這個樣子\nhttps://www.alvinchen.club/2025/06/13/php-%e5%8b%95%e6%85%8b%e5%91%bc%e5%8f%ab%e5%87%bd%e6%95%b8variable-functions%e8%88%87see/\nclass MessageController extend Controller { public function index(Request $request){ $platform = $request-\u0026gt;get(\u0026#39;platform\u0026#39;).\u0026#39;Service\u0026#39;; new $platform(...$request-\u0026gt;all()); } } 只是在彈性上與邏輯的完整度上都比較缺乏，今天就記錄一下策略模式的使用方式\n策略模式範例 : # 實際上這個範例使用了策略模式+工廠模式來完整處理資料流\n定義策略\n跟先前相同，我們需要定義三個PlatformClass，分別處理不同平台的資料，並且都需要實作同一個Interface\nInterface PlatformContract { public function send(); } class EmailPlatform implements PlatformContract { public function __construct(public string $receiver, public string $content){ } public function send(){ //實際執行處 } } class SMSPlatform implements PlatformContract { public function __construct(public string $receiver, public string $content){ } public function send(){ //實際執行處 } } class SlackPlatform implements PlatformContract { public function __construct(public string $receiver, public string $content){ } public function send(){ //實際執行處 } } 下一步我們要定義一個執行器，概念上是三個Platform的上層邏輯，負責處理整個寄送資訊的資料流，也是從Controller呼叫的入口\nclass notificationService { protected array $payloadData; protected PlatformContract $platform; protected Manager $manager; public function __construct( $this-\u0026gt;manager = new Manager(); ){} public function trigger(array $payload){ $this-\u0026gt;_set_payload(payload)-\u0026gt;_prepare()-\u0026gt;_send(); } protected function _set_payload(array $payload){ $this-\u0026gt;payloadData = $payload; return $this; } protected function _prepare(){ $this-\u0026gt;platform = $this-\u0026gt;manager($this-\u0026gt;payloadData[\u0026#39;platform\u0026#39;],$this-\u0026gt;payloadData)-\u0026gt;create(); return $this; } protected function _send(){ return $this-\u0026gt;platform-\u0026gt;send(); } } 簡單說明一下內容\nManager的部分後續說明，他是負責把platform new出來的class\nTrigger是Notification的入口，只要將需要的參數傳入，後續會執行\n1._set_payload，儲存參數 # 2._prepare，生成平台 # 3._send，呼叫平台的send()實際將資料送出 # 我們要說明的重點在_prepare身上，\n我們不在_prepare身上直接new platform，而是透過Manager來處理，Manager就是類似工廠的概念，負責產出需要的物件\nManager大概可以長這個樣子\nclass Manager { protected string $platform; protected array $payload; public function __construct(string $platform_name, array $payload){ $this-\u0026gt;platform = $platform_name.\u0026#34;Platform\u0026#34;; $this-\u0026gt;payload = $payload; } public function create(){ $__NAMESPACE__ = \u0026#34;App\\\\Service\\\\\u0026#34;; $target = $__NAMESPACE__.$this-\u0026gt;platform; return new $target($this-\u0026gt;payload); } } 大概就是透過create() function將需要的Object物件new好回傳，這樣子可以更解耦每個class負責的事情\n那麼我們就可以在Controller中這樣子呼叫\nclass MessageController extend Controller { public function index(Request $request){ new NotificationService()-\u0026gt;trigger(...$request-\u0026gt;all()); } } 當然如果要再更嚴謹一些的話，就可以把所有payload都改成透過Dto 物件來傳遞\n總結以上: # 透過NotificationService作為服務入口 在Notification中透過Manager將需要的平台Object new出來 統一透過_send function來呼叫每個平台的Object的send() function來實際將資料送出 以上這樣子設計，如果需要多一個平台，只要多設計一個XXXXPlatform就可以了喔\n","date":"2025年6月17日","externalUrl":null,"permalink":"/2025/06/17/larvel%E4%BD%BF%E7%94%A8%E7%AD%96%E7%95%A5%E6%A8%A1%E5%BC%8F-%E7%AF%84%E4%BE%8B-%E8%A8%8A%E6%81%AF%E5%AF%84%E9%80%81%E5%9F%B7%E8%A1%8C%E5%99%A8/","section":"Posts","summary":"","title":"Larvel使用策略模式 - 範例 : 訊息寄送執行器","type":"posts"},{"content":"","date":"2025年6月13日","externalUrl":null,"permalink":"/tags/php/","section":"Tags","summary":"","title":"Php","type":"tags"},{"content":"前言：\n我們無論在原生的PHP或者Laravel專案中經常會依據參數名稱不同而動態的去呼叫function名稱，例如像這樣\npublic function create(string $type,string $name) { $this-\u0026gt;{\u0026#39;_\u0026#39;.ucfirst($type)}($name); } protected function _member(string $name) { } protected function _manager(string $name) { } 透過create作為入口，將要create的類型與名字傳入，再依照傳入的類型呼叫不同的protected function來實際執行邏輯\n這樣不僅是可以節省使用if else 或者 switch的複雜度，未來也比較好增加其他項目\n但是這樣子寫一直有個問題，那就是編輯器無時無刻都在提醒 _member與_menager兩個function沒有被使用\n這時候需要請出@see # public function create(string $type,string $name) { /** @see _member */ /** @see _meneger */ $this-\u0026gt;{\u0026#39;_\u0026#39;.ucfirst($type)}($name); } protected function _member(string $name) { } protected function _manager(string $name) { } 這時候就可以觀察到，編輯器對於這兩個protected function已經亮燈，代表又被使用過\n也可以按住command用滑鼠點@see 可以直接跳到該function，方便後續排查問題\n","date":"2025年6月13日","externalUrl":null,"permalink":"/2025/06/13/php-%E5%8B%95%E6%85%8B%E5%91%BC%E5%8F%AB%E5%87%BD%E6%95%B8variable-functions%E8%88%87see/","section":"Posts","summary":"","title":"PHP 動態呼叫函數(variable functions)與@see","type":"posts"},{"content":"前言:\n我們都知道在Laravel的router中定義好路徑，並且指定到要執行的controller method中，連到該路徑就會進入指定的method中執行，\n但是在這之前Laravel做了多少事情才讓資料流進入到到controller method中呢？今天我們來往前走一步，那就是controller中的callAction method\n參考資料\nhttps://mesak.tw/code/php/11888/php-laravel-controller-callaction\ncallAction做了什麼 # 在我們自己定義的Controller中，都會去繼承Laravel framework中的BaseController，裡面就有個method大致上如下\npublic function callAction($method, $parameters) { // ... 你的邏輯 return $this-\u0026gt;{$method}(...array_values($parameters)); } 大致上就是用動態呼叫的方式把call你在router中定義的method name，以及將參數送過去\n簡單來說，callAction會是你定義的controller method的進入點以及離開點，所以我們可以複寫controller中的callAction method來達到進入controller處理邏輯前做一些，以及邏輯處理完return資料前再做一些事情\n我們改了什麼 # 我們先覆寫controller中的callAction method\npublic function callAction($method, $parameters): array|ServerResponse { $ctx = new RequestCtx(method: $method, parameters: $parameters); $this-\u0026gt;_pre_call($ctx); $this-\u0026gt;_call($ctx); return $this-\u0026gt;_post_call($ctx); } 其中，我們自行定義一個RequestCtx Class，目的在於存下原本controller的傳入值以及預計要return的回傳值，方便處理\n我們分成三個動作，_pre_call, _call, 以及_post_call 三個動作，分別是預先處理資料、真正呼叫controller method 做事情，最後處理retrun 值\nprotected function _pre_call(RequestCtx $ctx): RequestCtx { ... return $ctx; } _pre_call比較不重要，這邊就先不描述，主要就是針對傳入值做一些進入controller前需要的init或是validate\nprotected function _call(RequestCtx $ctx): RequestCtx { $result = parent::callAction($ctx-\u0026gt;get_method(), $ctx-\u0026gt;get_parameters()); $ctx-\u0026gt;set_result($result); return $ctx; } _call，就是實際上去呼叫parent::callAction，將原本該有的method name以及parameter傳入後儲存結果\nprotected function _post_call(RequestCtx $ctx): ServerResponse|array { $result = $ctx-\u0026gt;get_result(); if ($result instanceof ServerResponse) { return $result; } $content = $this-\u0026gt;_serialize_content( result: $result, parsers: $this-\u0026gt;_get_serializers() ); return [\u0026#39;data\u0026#39; =\u0026gt; $content]; } _post_call會處理在return前的狀況，這邊處理的內容是，如果controller處理完已經是一個ServerResponse那麼就直接return\n如果不是，我們另外將資料序列化做好，放入data中再回傳，確保未來回傳的資料每個method都相同\n","date":"2025年6月9日","externalUrl":null,"permalink":"/2025/06/09/laravelcontroller%E4%B8%AD%E7%9A%84callaction/","section":"Posts","summary":"","title":"Laravel Controller中的callAction","type":"posts"},{"content":"參考資料 :\nhttps://vocus.cc/article/5ff66eb9fd897800018728b3\n前言 :\n為什麼要這樣做? Laravel的Model並沒有_schema()這個method，完全是我們開發團隊自行定義出來的\n原因如下\n為了統一在Model中描述每個欄位的資料該如何被驗證 # 滿足Model中的fillable欄位描述 # 會這樣子做就是因為我們發現在Model中本來就需要描述一次filleble，而在資料寫入的controller中又需要在描述一次所有欄位的驗證內容\n因此把欄位名稱當成key，驗證資料當成value寫在_schema() method中\n像這樣\n\u0026lt;?php namespace App\\Models; class Customer extends Model { protected $table = \u0026#39;customer\u0026#39;; public function _schema(): array { return [ \u0026#39;name\u0026#39; =\u0026gt; \u0026#39;required|string|max:60\u0026#39;, \u0026#39;address\u0026#39; =\u0026gt; \u0026#39;required|string|max:60\u0026#39;, \u0026#39;status\u0026#39; =\u0026gt; \u0026#39;required|boolean\u0026#39; ]; } } 這樣一來，我們只要在controller中這樣做，就可以直接取得驗證資料\npublic function create(Request $request){ $payload = $request-\u0026gt;validate((new Customer)-\u0026gt;_schema()); } 而在原本的Customer model中你應該會發現其實並沒有描述fillable的部分，實際上是因為在Customer繼承的Model中我們已經做過處理\nabstract class Model extends BaseModel { public function __construct(array $attributes = []) { $this-\u0026gt;fillable = array_keys($this-\u0026gt;_schema()); parent::__construct($attributes); } } 因此每個繼承這個Model的Model Class只需要描述好_schema() method就可以啦\n","date":"2025年6月4日","externalUrl":null,"permalink":"/2025/06/04/%E6%96%B0%E5%AE%9A%E7%BE%A9-laravel-model%E4%B8%AD%E7%9A%84_schema-method/","section":"Posts","summary":"","title":"新定義 Laravel Model中的_schema() method","type":"posts"},{"content":" 前言 # PHP Reflection 最早出在php 5.0的時代就已經有了，Reflection可以用來讀取某個class的原始碼片段描述，或者function內容等等資訊，這次要來整理一下幾個應用場景\n1.讀取Model class中記錄的自行定義的資料\n2.讀取Model class中原有定義的Relation，例如has many, has one 等等\n3.讀取某個function定義的Attribute\n參考連結 # https://developer.aliyun.com/article/33171\nhttps://www.cnblogs.com/phpphp/p/17807799.html\nhttps://carlalexander.ca/php-reflection-api-fundamentals/\n# 讀取Model class中記錄的自行定義的資料 # 在某次的需求中，我們做到了讓controller自動判定哪些前端傳過來的欄位需要做檔案處理，因此我們將這些需要被當成檔案處理的資訊記錄在Model中，如以下\nclass Customer extends Model { ..... protected array $file_columns = [ \u0026#39;files\u0026#39; ]; ..... } 這樣子描述，我們就可以知道要寫入Customer這張表的資料有一個叫做files的欄位需要經過檔案處理才能寫入資料庫，這樣一來我們就可以在controller中像這樣處理它\nclass CustomerController extends Controller { public function create(Request $request) { $model_class = new (Customer::class); $target_model = new ReflectionClass($model_class); if($target_model-\u0026gt;hasProperty(\u0026#39;file_columns\u0026#39;)){ $file_columns = $target_model-\u0026gt;getProperty(\u0026#39;file_columns\u0026#39;)-\u0026gt;getValue($model_class); //處理檔案的其他原始碼 } } } 這時候可以預期$file_columns會是Customer Model中寫的\u0026quot;files\u0026quot;一個欄位\n這樣就可以搭配前端傳過來的欄位照到對應的\u0026quot;files\u0026quot;來做剩下的檔案處理啦[可以參考這篇]\n讀取Model class中原有定義的Relation，例如has many, has one 等等 # 如果你想到取得一個Model Class中定義的relation list，可以像以下這樣做\nModel Class中我們先把relation定義好\nclass Customer extends Model { ..... public function contact_list(): HasMany { return $this-\u0026gt;hasMany(CustomerContact::class, \u0026#39;customer_id\u0026#39;, \u0026#39;id\u0026#39;); }, public function identity_list(): HasMany { return $this-\u0026gt;hasMany(CustomerIdentity::class, \u0026#39;customer_id\u0026#39;, \u0026#39;id\u0026#39;); } ..... } 接著在Controller中就可以透過以下操作取得contact_list,以及identity_list兩者\nclass CustomerController extends Controller { public function create(Request $request) { $model_class = new (Customer::class); $target_model = new ReflectionClass($model_class); $relations = array_keys(array_reduce( $target_model-\u0026gt;getMethods(ReflectionMethod::IS_PUBLIC), function ($result, ReflectionMethod $method) { // If this function has a return type ($returnType = (string) $method-\u0026gt;getReturnType()) \u0026amp;\u0026amp; // And this function returns a relation is_subclass_of($returnType, Relation::class) \u0026amp;\u0026amp; // Add name of this method to the relations array ($result = array_merge($result, [$method-\u0026gt;getName() =\u0026gt; $returnType])); return $result; }, [] )); } } 讀取某個function定義的Attribute # 上一篇，我們提到了如何定義Attribute，並且把它拿出來做使用\nhttps://www.alvinchen.club/2025/05/23/php-attribute-laravel%e9%96%8b%e7%99%bc%e7%92%b0%e5%a2%83%e6%87%89%e7%94%a8%e9%9b%86/\n這邊我們就詳細來看一下怎麼透過Reflection取得Attribute\n假設我們有一個Controller定義如下\nclass APIController extends Controller { #[APITag(name:\u0026#39;id_number\u0026#39;,type: \u0026#39;string\u0026#39;,required: true,description: \u0026#39;統一編號\u0026#39;)] #[APITag(name:\u0026#39;name\u0026#39;,type: \u0026#39;string\u0026#39;,required: true,description: \u0026#39;公司名字\u0026#39;)] public function company_verify(Request $request): bool { } } 那麼，你可以寫一個Artisan Command就能查詢所有API的參數條件了，大概像這樣子\n$all_routes = [];//你的所有API route $Api_routes_data = []; foreach ($all_routes as $route){ list($controller,$method) = explode(\u0026#39;@\u0026#39;,$route-\u0026gt;getActionName()); $controller_method = new ReflectionMethod($controller,$method); $attributes = $controller_method-\u0026gt;getAttributes(APITag::class); $params = array_map(function ($attribute) { return $attribute-\u0026gt;getArguments(); },$attributes); $api_element = [ \u0026#39;method\u0026#39; =\u0026gt; implode(\u0026#39;|\u0026#39;, $route-\u0026gt;methods()), \u0026#39;uri\u0026#39; =\u0026gt; $route-\u0026gt;uri(), \u0026#39;param\u0026#39; =\u0026gt; $params, ]; $Api_routes_data[] = $api_element; } $Api_routes_data儲存的結果就是所有API的Controller中Attribute所描述的內容以及URi , method name等等資訊啦\n以上，近期開發中運用Reflection的紀錄\n","date":"2025年5月28日","externalUrl":null,"permalink":"/2025/05/28/php-reflection-%E6%87%89%E7%94%A8%E9%9B%86-%E7%94%A8%E4%BE%86%E8%AE%80%E5%8F%96function-class-%E5%85%A7%E5%AE%B9%E8%B3%87%E6%96%99/","section":"Posts","summary":"","title":"PHP Reflection 應用集 用來讀取function, class 內容資料","type":"posts"},{"content":" 前言 # php8.0以後引入了Attribute的寫法，也就是我們可以在class的頭上或者function的頭上用\n#[......] 的方式來描述其他的可讀元素，本次將說明我們將Attribute運用在以下幾個地方\n1.用於描述Controller中Request參數的內容\n2.用於取有特定的Tag的Function來執行\n3.用於Emun的其他描述\n用於Request的參數描述 # 首先，我們現在app/Attributes/APITag.php 中描述以下\n\u0026lt;?php namespace app\\Attributes; use Attribute; #[Attribute(Attribute::TARGET_METHOD | Attribute::IS_REPEATABLE)] class APITag { public function __construct( protected string $name, protected string $type, protected bool $required, protected string $description, ) { } } 在這個Class中，我們定義它是用在Method上，而且可以重複\n並且賦予他四個欄位，分別描述名稱、類型、必填以及說明，這四個欄位正是我們controller傳入值所需要描述的部分\n接著在Controller中就可以這樣子定義參數\nclass APIController extends Controller { #[APITag(name:\u0026#39;id_number\u0026#39;,type: \u0026#39;string\u0026#39;,required: true,description: \u0026#39;統一編號\u0026#39;)] #[APITag(name:\u0026#39;name\u0026#39;,type: \u0026#39;string\u0026#39;,required: true,description: \u0026#39;公司名字\u0026#39;)] public function company_verify(Request $request): bool { } } 假設這個Method要做的是情就是驗正公司的統編正確性，因此傳入值就會是統編與公司名字兩者\n我們可以分別去描述每個參數的欄位名稱、型態、是否必填以及中文說明，當有多個參數的時候就堆疊多條就行了\n那麼後續除了開發人員清楚知道參數規範之外，也可以透過ReflectionMethod或ReflectionClass來讀取\n可以詳見後續描述 PHP Reflection 應用集 用來讀取function, class 內容\n用於取特定的Tag值來執行 # 首先我們一樣在app/Attributes/InitTag.php 中描述以下\n\u0026lt;?php namespace app\\Attributes; use Attribute; #[\\Attribute(\\Attribute::TARGET_METHOD)] class InitTag {} 因為這個案例是只要有放Tag就取出，因此我們沒有定義Attribute需要的內容，單純給上class name\n接著在專案中任何一個class method加上這個Attribute，如下\n#[InitTag] protected function init_start(): static { } 應用的方式也相同，可以透過ReflectionMethod取得後透過動態呼叫來執行這些Method\n用於Enum的其他描述 # php8.1中提供了Enum方法，簡單來說是可以讓我們明確定義出固定某些參數與儲存於資料庫中的數值對應\n舉例來說，我需要將客戶分類為 1: 有合作過的客戶，2: 沒有合作過的客戶\n那麼這些中文字也很有可能被前端拿來做畫面渲染使用，因此我們不讓前端工程師獨立管理這些文字避免混淆可以像以下這樣做\n首先，一樣在app/Attributes/Description.php 中描述以下\nnamespace app\\Attributes; #[\\Attribute] class Description { public function __construct(...$params){} } 這次我們用更彈性的作法，直解把\u0026hellip;$params傳入，就可以愛寫多少參數就寫多少\n接著在app/Enums/Customer.php 來定義客戶的分類\nnamespace App\\Enums; enum Customer: int { #[Description(name: \u0026#39;沒合作過\u0026#39;)] case No = 2; #[Description(name: \u0026#39;有合作過\u0026#39;)] case Yes = 1; } 這樣子除了原始碼中可以清楚描述客戶的狀態外，也可以透過API一樣用ReflectionMethod來取得客戶該有的狀態中文字以及對應數值提供給前端使用\n讓我們淋漓盡致的使用Attribute吧\n","date":"2025年5月23日","externalUrl":null,"permalink":"/2025/05/23/php-attribute-laravel%E9%96%8B%E7%99%BC%E7%92%B0%E5%A2%83%E6%87%89%E7%94%A8%E9%9B%86/","section":"Posts","summary":"","title":"PHP Attribute Laravel開發環境應用集","type":"posts"},{"content":" 前言: # 檔案上傳是非常常見的需求邏輯，如果是很基礎的將檔案存放在系統資料夾內，那麼問題不大\n問題在於，基於無狀態的系統運作架構如K8s，我們都會希望將檔案另外儲存起來，如GCS\n這時候就會遇上，當資料變更時該如何好好的同步來處理這個檔案\n下面將著重在後端對於檔案的處理方式\n資料處理流程大致如下\n前端 # 在Nuxt的結構下我們已經設計好一個名為File 的Component\n\u0026lt;File ref=\u0026#34;file_upload\u0026#34; :multiple=\u0026#34;true\u0026#34; :files=\u0026#34;submit_data.files\u0026#34;\u0026gt;\u0026lt;/File\u0026gt; 並且在ㄓFile這個Component中，設計好一個method名為upload如下，用於將檔案往後端URL送出\nasync upload(){ let upload_url = process.env.MIX_API_URL+\u0026#39;/api/file_upload\u0026#39; let error_message = \u0026#39;\u0026#39; let upload_results = [] try { for (let index in this.storedFiles) { let formData = new FormData() formData.append(\u0026#39;file\u0026#39;, this.storedFiles[index]) let upload_result = await axios({ method: \u0026#39;post\u0026#39;, url: upload_url, data: formData }) if(typeof upload_result.data !== \u0026#39;undefined\u0026#39;){ upload_results.push(upload_result.data.data) } } }catch (err) { error_message = err; if (err?.response?.data) { error_message = err.response.data.message; } throw TypeError(error_message) } if(error_message !== \u0026#39;\u0026#39;){ upload_results = false this.$swal({ title: \u0026#39;檔案上傳錯誤\u0026#39;, html: error_message, icon: \u0026#39;warning\u0026#39;, confirmButtonText: \u0026#39;我知道了\u0026#39;, showCancelButton: false }) throw TypeError(error_message) } return upload_results } 內容很簡單，就是透過axios將檔案post到後端，如果有錯誤訊息就提示，沒有的話將後端回傳的結果return\nupload method被呼叫的時間點會在前端將資料送出的前一刻，透過refs來將檔案送出\nlet create_files = await this.$refs.file_upload.upload() 最後，將create_files連同表單其他資料再送往後端做資料儲存即可\n後端 # 後端的邏輯是今天的重點，我們拆成幾個步驟來看\n當前端的File送出檔案後，我們將檔案cache起來，並且還給前端一把key 當前端表單所有資料送出後，我們使用key將cache起來的檔案讀出，並且送至GCS中實際儲存 因此，我們設計了三個Service來處理事情\n首先，因為如果在後端Controller中寫死哪幾個column需要做檔案處理那麼彈性就太過不足，因此我們在Model中描述了$file_columns，借此透過抽象邏輯來處理每張資料表要儲存的檔案欄位\nFileColumnProcessService 作為入口，負責處理CRUD四個動作時檔案的欄位值提取，以及處理動作\nFileHandleService 實際處理檔案從cache中取出、還原、上傳到GCS等等操作\nImageProcessService 作為檔案處理的一環，對於圖片的所有處理，包含壓縮、變更長寬，以及iOS的heic檔案處理等等\n實際檔案的儲存還是透過跟這篇相同的套件直接傳入GCS中 https://www.alvinchen.club/2019/08/28/laravel-%e4%b8%8a%e5%82%b3%e6%aa%94%e6%a1%88%e5%88%b0google-cloud-plateform-storage-gcs/\nService # 接著我們以create來舉例檔案處理的步驟\nclass FileColumnProcessService { public function create(array $payload, String $model): array { $all_file_columns = $this-\u0026gt;_get_file_columns($model); return $this-\u0026gt;_process_create(structure: $all_file_columns,payload: $payload); } } 其中，_get_file_columns會透過 RelectionClass取出指定的model中定義的file_columns，這樣就會之到前端送來的資料中有哪幾個column是需要處理的\n_process_create中會呼叫FileHandleService實際處理每一個需要將檔案正確擺放到GCS上的邏輯\n接著讓我們來看FileHandleService中定義的幾個重要method\nclass FileHandleService { public function cache(UploadedFile $file): string { $key = Str::uuid()-\u0026gt;toString(); $org_name = $file-\u0026gt;getClientOriginalName(); if(Cache::has(\u0026#39;file_name_cache_\u0026#39;.$org_name)){ $key = Cache::get(\u0026#39;file_name_cache_\u0026#39;.$org_name); }else{ Cache::set(\u0026#39;file_name_cache_\u0026#39;.$org_name, $key, $this-\u0026gt;file_cache_ttl); Cache::set($key, [\u0026#39;name\u0026#39;=\u0026gt;$org_name,\u0026#39;content\u0026#39;=\u0026gt;$file-\u0026gt;get()], $this-\u0026gt;file_cache_ttl); } return $key; } public function store(array|string $file_key): array { $result = []; $upload_files = $this-\u0026gt;_make_files(key:$file_key); foreach($upload_files as $upload_file){ $org_name = $upload_file-\u0026gt;getClientOriginalName(); $upload_file = $this-\u0026gt;imageProcessService-\u0026gt;make($upload_file); $stored_path = $upload_file-\u0026gt;store(env(\u0026#39;UPLOAD_DIR\u0026#39;)); $size = Storage::size($stored_path); $info = pathinfo($stored_path); $result[] = [........]; } return $result; } } cache用於對應前端File Component送出的請求，將檔案直接透過Laravel 的cacheh存下來\nstore用於對於前端表單資料的請求，實際上就是將檔案從cache中讀出，然後送到GCS上，最後依照需要儲存的檔案資料組成$result返回就行\n過程中，讀出cache中的資訊，寫入temp file中轉換成UploadedFile，再藉由Larvel原生的store function將檔案儲存到GCS上 這邊就不多做描述，有興趣可以至github上查閱\nhttps://github.com/JsAdways/laravel-sdk/blob/master/src/Stubs/services/FileColumnProcess/FileColumnProcessService.stub\nhttps://github.com/JsAdways/laravel-sdk/blob/master/src/Stubs/services/FileHandle/FileHandleService.stub\nhttps://github.com/JsAdways/laravel-sdk/blob/master/src/Stubs/services/FileHandle/ImageProcessService.stub\nController # 因此，在controller中，我們就能濃縮成這樣子來描述檔案欄位的處理\n$this-\u0026gt;file_column_process_service-\u0026gt;create(payload:$payload,model:CUSTOMER_MODEL::class); payload 為前端傳入的所有資料，CUSTOMER_MODEL即是本次要寫入檔案資料的表單model\n","date":"2025年5月19日","externalUrl":null,"permalink":"/2025/05/19/laravel-file-upload-gcs-%E6%AA%94%E6%A1%88%E4%B8%8A%E5%82%B3%E8%99%95%E7%90%86%E9%82%8F%E8%BC%AF/","section":"Posts","summary":"","title":"laravel file upload GCS 檔案上傳處理邏輯","type":"posts"},{"content":"我是一位擁有 10 年開發經驗的 Laravel 全端工程師，專精於打造可快速開發、高可維護性的 Web 應用程式與系統架構。\n不僅熟稔 Laravel 後端開發，更斜槓學習 Nuxt.js / Next.js 具備前端實作能力，能獨立完成專案。熱衷於透過撰寫技術部落格分享知識，對學習新知抱持高度熱情，並有實際開發 Laravel 套件及解決複雜系統整合問題的實戰經驗。\n","date":"2025年5月16日","externalUrl":null,"permalink":"/resume/alvin/","section":"Alvin Chen Club","summary":"","title":"Alvin Chen 個人履歷","type":"page"},{"content":" 基本概念 # 將原始碼寫在.stub檔中，內容中如果有需要替換的則使用{{ }} 標記，例如 namespace {{ namespace }};\nGeneratoreCommand會讀出.stub檔案將需要替換的文字替換完成後依照namespace寫入.php檔案\n幾個需要理解的method與參數\n首先參數的部分，固定第一個為name，也就是要產出的檔案名稱\nphp artisan GeneratorCommand {name} method: getStub() # protected function getStub(): string 需要return .stub檔案的路徑，如果.stub檔案在vendor資料夾中則可以透過上篇說明來取得package中的檔案\nhttps://www.alvinchen.club/2025/04/08/%e9%80%8f%e9%81%8enamespace%e6%89%be%e5%88%b0composer-package%e4%b8%ad%e7%9a%84%e6%aa%94%e6%a1%88%e8%b7%af%e5%be%91/\n範例可以像這樣子，class中依照上一篇說明定義好get_file_path後，就可以找到指定的Vendor\\package\\路徑下的檔案了\n如果是在同一個專案目錄中，則直接return專案中路徑即可\nprotected function getStub(): string { return $this-\u0026gt;get_file_path(\u0026#34;/http/Controllers\u0026#34;); } method: getDefaultNamespace() # protected function getDefaultNamespace($rootNamespace): string 需要return 產出php檔正確擺放位置的namespace，例如\nprotected function getDefaultNamespace($rootNamespace): string { return $rootNamespace . \u0026#34;\\Http\\\\Controllers\u0026#34;; } 這樣子就會把產出的php檔案放在app\\Http\\Controllers中\nmethod: getNameInput() # protected function getNameInput(): string return值作為檔案的名稱，也就是return command第一個參數name就行了，通常會把class name做大駝峰處理，範例如下\nprotected function getNameInput(): string { return Str::studly(trim($this-\u0026gt;argument(\u0026#39;name\u0026#39;))); } method: buildClass() # protected function buildClass($name): array|string 如果產出的php檔沒有其他需要替換的標記，或者沒有其他邏輯需要處理是可以不必處理buildClass()這個method，這邊提供一個範例\n把stub內容中多組tag紀錄在$this-\u0026gt;replace_tags中，然後再buildClass method中替換掉他\nprotected function buildClass($name): array|string { $stub = $this-\u0026gt;files-\u0026gt;get($this-\u0026gt;getStub()); $namespace = $this-\u0026gt;replaceNamespace($stub, $name)-\u0026gt;replaceClass($stub, $name); $replacedStub = $namespace; foreach ($this-\u0026gt;replace_tags as $tag =\u0026gt; $text){ $replacedStub = str_replace(\u0026#34;{{ {$tag} }}\u0026#34;, $text, $replacedStub); } return $replacedStub; } method: handle() # public function handle(): ?bool 如果在產出檔案前，有需要對class的其他參數做調整，可以複寫handle() method，記得最後要return parent::handle()就好\n例如像這樣\npublic function handle(): ?bool { #你想要的內容寫在這裡 return parent::handle(); // TODO: Change the autogenerated stub } method: interact() # protected function interact(InputInterface $input, OutputInterface $output) 假設需要對輸入的參數做一些調整，可以透過複寫interact來達成，下面的範例處理了name參數，讓他變成首字大寫\n/** * Override * 重新處理name參數 */ protected function interact(InputInterface $input, OutputInterface $output) { parent::interact($input, $output); if ($input-\u0026gt;hasArgument(\u0026#39;name\u0026#39;)) { $name = implode(\u0026#39;/\u0026#39;,array_map(\u0026#39;ucfirst\u0026#39;,explode(\u0026#39;/\u0026#39;,$input-\u0026gt;getArgument(\u0026#39;name\u0026#39;)))); $input-\u0026gt;setArgument(\u0026#39;name\u0026#39;,$name); } } 最後提供一個完整範例 # 將virtualorz\\mysdk中的 Stuns/http/controllers.stub 生成 /app/Http/Controllers/CustomerController.php\n#command.php \u0026lt;?php namespace App\\Console\\Commands; use Illuminate\\Console\\GeneratorCommand; use Illuminate\\Support\\Str; class MakeCustomerController extends GeneratorCommand { protected $name = \u0026#39;make:my-controller\u0026#39;; protected $description = \u0026#39;Create my controller class\u0026#39;; protected string $stub_path = \u0026#39;http/ClassController\u0026#39;; protected string $target_path = \u0026#39;\\Http\\\\Controllers\u0026#39;; protected array $replace_tags = [\u0026#39;use_string\u0026#39;=\u0026gt;\u0026#39;use App\\Services\\Config\\ConfigService\u0026#39;]; protected function getStub(): string { return $this-\u0026gt;get_file_path($this-\u0026gt;stub_path); } protected function getDefaultNamespace($rootNamespace): string { return $rootNamespace . $this-\u0026gt;target_path; } protected function getNameInput(): string { return Str::studly(trim($this-\u0026gt;argument(\u0026#39;name\u0026#39;))); } protected function buildClass($name): array|string { $stub = $this-\u0026gt;files-\u0026gt;get($this-\u0026gt;getStub()); $namespace = $this-\u0026gt;replaceNamespace($stub, $name)-\u0026gt;replaceClass($stub, $name); $replacedStub = $namespace; foreach ($this-\u0026gt;replace_tags as $tag =\u0026gt; $text){ $replacedStub = str_replace(\u0026#34;{{ {$tag} }}\u0026#34;, $text, $replacedStub); } return $replacedStub; } protected function get_file_path($path): string { $loader = $this-\u0026gt;_getClassLoader(); $psr4 = $loader-\u0026gt;getPrefixesPsr4(); $namespace = \u0026#39;virtualorz\\\\mysdk\\\\\u0026#39;; $stubFilename = \u0026#34;Stubs/{$path}.stub\u0026#34;; // Stub 檔案名稱 $stub_path_location = \u0026#39;\u0026#39;; foreach ($psr4 as $prefix =\u0026gt; $paths) { if (str_starts_with($namespace, $prefix)) { foreach ($paths as $path) { // 處理多個路徑 $relativePath = str_replace($prefix, \u0026#39;\u0026#39;, $namespace); $relativePath = str_replace(\u0026#39;\\\\\u0026#39;, \u0026#39;/\u0026#39;, $relativePath); $stubPath = $path . \u0026#39;/\u0026#39; . $relativePath . $stubFilename; if (file_exists($stubPath)) { $stub_path_location = $stubPath; } } } } return $stub_path_location; } protected function _getClassLoader() { return require base_path(\u0026#39;vendor/autoload.php\u0026#39;); } } Controllers.stub \u0026lt;?php namespace {{ namespace }}; {{ use_string }} class {{ class }} extends Controller { }值 執行命令 # php artisan make:my-controller CustomerController ","date":"2025年4月15日","externalUrl":null,"permalink":"/2025/04/15/larvel%E9%80%8F%E9%81%8Egeneratorcommand%E4%BD%BF%E7%94%A8artisan%E7%94%A2%E5%87%BAphp%E5%9F%B7%E8%A1%8C%E6%AA%94/","section":"Posts","summary":"","title":"Larvel透過GeneratorCommand使用artisan產出php執行檔","type":"posts"},{"content":"透過namespace來找到Vendor資料夾中安裝的composer package中的某一個檔案路徑，而不是直接寫死路徑在vendor/VENDOR_NAME/PACKAGE_NAME/src\n範例: # 找到namespace為virtualorz\\mysdk\n中的config/abc.php檔案\nprotected function _getClassLoader() { return require base_path(\u0026#39;vendor/autoload.php\u0026#39;); } protected function get_file_path(string $path): string { $loader = $this-\u0026gt;_getClassLoader(); $psr4 = $loader-\u0026gt;getPrefixesPsr4(); $namespace = \u0026#39;virtualorz\\\\mysdk\\\\\u0026#39;; $target_file_name = \u0026#34;config/abc.php\u0026#34;; // 要找的檔案名稱 $path_location = \u0026#39;\u0026#39;; foreach ($psr4 as $prefix =\u0026gt; $paths) { if (str_starts_with($namespace, $prefix)) { foreach ($paths as $path) { // 處理多個路徑 $relativePath = str_replace($prefix, \u0026#39;\u0026#39;, $namespace); $relativePath = str_replace(\u0026#39;\\\\\u0026#39;, \u0026#39;/\u0026#39;, $relativePath); $path = $path . \u0026#39;/\u0026#39; . $relativePath . $stubFilename; if (file_exists($path)) { $path_location = $path; } } } } return $path_location; } 說明: # 透過$psr4取得autoload中的所有路徑\n$psr4 = $loader-\u0026gt;getPrefixesPsr4(); 找到開頭跟定義好的$namesapce相同的路徑，最後帶入要找的$target_file_name，確認檔案是不是存在\n存在的話這個檔案路徑就是目標了\n","date":"2025年4月8日","externalUrl":null,"permalink":"/2025/04/08/%E9%80%8F%E9%81%8Enamespace%E6%89%BE%E5%88%B0composer-package%E4%B8%AD%E7%9A%84%E6%AA%94%E6%A1%88%E8%B7%AF%E5%BE%91/","section":"Posts","summary":"","title":"透過namespace找到composer package中的檔案路徑","type":"posts"},{"content":"相關連結\nhttps://hackmd.io/@SuFrank/BJOe3Fi3c#docker-compose%E8%85%B3%E6%9C%AC\nhttps://medium.com/kidd88/laravel-%E5%90%8C%E6%99%82%E4%BD%BF%E7%94%A8mongodb%E8%88%87mysql-19d0c8ca4604\n安裝與啟動MongoDB # 在docker的環境下非常容易，如下yml\nversion: \u0026#34;3.3\u0026#34; services: mongo: container_name: mongo image: mongo:7.0.12 platform: linux/arm64 restart: unless-stopped environment: - MONGO_INITDB_ROOT_USERNAME=admin - MONGO_INITDB_ROOT_PASSWORD=${MYSQL_ROOT_PASSWORD} ports: - 27017:27017 volumes: - mongo-data:/data/db networks: - dev networks: dev: volumes: mongo-data: 說明一下項目\n1.因為開發機是Mac M1 CPU因此在platform上選擇使用linux/arm64版本\n各位可以因應自己機器架構、作業系統而選則不同的image或者platform，可以參考MongoDB的dockerHub\n2.在environment中提供mongoDB所要使用的帳號與密碼，這邊我把密碼存放在env中，與MySQL使用同一組密碼\n3.mongoDB預設使用27017 port，也可以自由變更本地端對應的port\n4.提供一個volumn，mongo-data 作為資料持久話儲存的媒介\ncontainer起來之後就可以用localhost:27017來存取mongoDB或者在同一個network中的其他container直接使用service name: mongo來存取\nPHP執行環境安裝mongoDB相關套件 # 在docker環境中，我們編輯 PHP的Dockerfile，增加下面這一行\nRUN pecl install mongodb 重新build image後在php.ini中加入以下，用來打開extention\nextension=mongodb.so 在專案中安裝相關套件 # 回到專案的根目錄下使用以下指令安裝\ncomposer require mongodb/laravel-mongodb:^4.7 配置env與其他設定檔 # 在config/app.php中增加\n\u0026#39;providers\u0026#39; =\u0026gt; ServiceProvider::defaultProviders()-\u0026gt;merge([ ... MongoDB\\Laravel\\MongoDBServiceProvider::class, ... ])-\u0026gt;toArray(), 在config/database.php中增加\n\u0026#39;connections\u0026#39; =\u0026gt; [ ... \u0026#39;mongodb\u0026#39; =\u0026gt; [ \u0026#39;driver\u0026#39; =\u0026gt; \u0026#39;mongodb\u0026#39;, \u0026#39;dsn\u0026#39; =\u0026gt; env(\u0026#39;MONGO_DB_URI\u0026#39;), \u0026#39;database\u0026#39; =\u0026gt; env(\u0026#39;MONGO_DB_DATABASE\u0026#39;, \u0026#39;forge\u0026#39;), ] ... ] 這樣做可以把連線資訊(dsn)與要連結的資料庫(database)都改由env檔案提供資訊\n在env檔案中增加\nMONGO_DB_URI=mongodb://USER:PASSWORD@LOCATION MONGO_DB_DATABASE=USE_DB_NAME 說明如下\n1.MONGO_DB_URI 中的USER 與 PASSWORD就是上方docker containter建構時提供的帳號密碼\n2.MONGO_DB_URI 中的LOCATION可以替換成mongoDB的所在位置，如果依照上面的docker範例，可以是本地端IP或者直接用service name，mongo就行\n3.MONGO_DB_DATA_BASE可以自由只定要用的database，跟原本在MySQL的環境中需要先建立DB與tabel的概念不同\n建立Model來對mongoDB中的collection做存取 # 這裡的collection指的是mongoDB中的collection，就是MySQL中table的概念\n我們可以用artisan指令建立model\nphp artisan maek:model department 這裡使用一個範例建立department model用來存取mongoDB中的department collection\n接著編輯department model\n將 use Illuminate\\Database\\Eloquent\\Model; 替換成 use MongoDB\\Laravel\\Eloquent\\Model; 完成範例如下\nnamespace App\\Models; use MongoDB\\Laravel\\Eloquent\\Model; class department extends Model { protected $connection = \u0026#39;mongodb\u0026#39;; protected $fillable = [ \u0026#39;name\u0026#39;, \u0026#39;location\u0026#39;, \u0026#39;manager_name\u0026#39; ]; } 我們需要將$connection指定到mongodb，以及先定義好需要用到的collection key在$fillable中，用法就如同MySQL中的欄位\n接下來的用法就與原本使用model存取MySQL資料庫相同了，提供幾個範例如下\n範例 # 1.寫入一筆資料\ndepartment::create([ \u0026#39;name\u0026#39; =\u0026gt; \u0026#39;人力資源部\u0026#39;, \u0026#39;location\u0026#39; =\u0026gt; \u0026#39;台北市辦公室本部\u0026#39;, \u0026#39;manager_name\u0026#39; =\u0026gt; \u0026#39;艾利爾\u0026#39; ]); 取得location = \u0026lsquo;台北市辦公室本部\u0026rsquo;的list $departments = department::where(\u0026#39;location\u0026#39;,\u0026#39;台北市辦公室本部\u0026#39;)-\u0026gt;get(); ","date":"2024年8月7日","externalUrl":null,"permalink":"/2024/08/07/laravel%E5%90%8C%E6%99%82%E4%BD%BF%E7%94%A8mysql%E8%88%87mongodb/","section":"Posts","summary":"","title":"Laravel同時使用MySQL與MongoDB","type":"posts"},{"content":"","date":"2024年7月4日","externalUrl":null,"permalink":"/categories/docker/","section":"Categories","summary":"","title":"Docker","type":"categories"},{"content":"","date":"2024年7月4日","externalUrl":null,"permalink":"/tags/docker/","section":"Tags","summary":"","title":"Docker","type":"tags"},{"content":"參考文章\nhttps://medium.com/@life-is-short-so-enjoy-it/docker-how-to-build-and-push-multi-arch-docker-images-to-docker-hub-64dea4931df9\nhttps://github.com/docker/buildx/issues/1986\nhttps://stackoverflow.com/questions/73812700/docker-error-internal-load-metadata-for-docker-io\n大致上的步驟如下 # 準備好要build的Dockerfile 事先登入docker hub 透過內建的docker buildx來處理 建立一個builder 使用docker buildx 一次build多個平台image 並且推到docker hub上 注意事項 # 首先，確認一下docker hub desktop中的 設定 \u0026gt; docker engine ，feature.buildkit設定為false，如果build中有出現錯誤請查閱這個設定 注意建立builder的driver，下面會提到 準備Dockerfile # FROM node:22.0 COPY ./ /var/project RUN apt-get update \u0026amp;\u0026amp; apt-get install -y vim nano chromium RUN apt-get install -y fonts-arphic-ukai fonts-arphic-uming fonts-ipafont-mincho fonts-ipafont-gothic fonts-unfonts-core RUN npm i -g npm RUN npm i -g pm2 EXPOSE 5000 ENV HOST=0.0.0.0 WORKDIR /var/project CMD [\u0026#34;node\u0026#34;, \u0026#34;./server.js\u0026#34;] 登入docker hub # 可以透過指令\ndocker login 也可以透過docker desktop介面先登入\n建立Builder # docker buildx create --name container --driver-opt \u0026#39;image=moby/buildkit:v0.12.1-rootless\u0026#39; --driver=docker-container 其中，如果沒有加入\u0026ndash;driver-opt參數，不指定用rootless的image很奇怪的Dockerfile中的apt-get指令會出現錯誤\nBuild指令 # docker buildx build --tag MY_IMAGE_NAME:IMAGE_TAG --platform linux/arm64,linux/amd64 --builder container --push --no-cache . 其中，\u0026ndash;platform 可以依照你要的平台來指定，當然必須要是來源的image就已經支援的平台，這邊舉例build arm64與amd64兩個版本\n\u0026ndash;builder 指定我們剛剛建立的builder name\n最後，我們就會在docker hub上面看到兩個不同OS的image了\n","date":"2024年7月4日","externalUrl":null,"permalink":"/2024/07/04/docker-hub-push-multiple-os-image-with-same-tag/","section":"Posts","summary":"","title":"docker hub push multiple OS image with same tag","type":"posts"},{"content":"套件名稱 : js-adways/scopefilter\n開發年份 : 2024\ngitHub連結 : https://github.com/JsAdways/scopeFilter\n內容簡介 :\nscopefilter作為laravel Model的traits，只需要在Model中use traits，就可以用萬用的operator取得相對應資料\nLaravel：100% 安裝方式 :\ncomposer require jsadways/scopefilter 使用方式 : (舉例 在user model中)\nclass User extends Model { ... use ScopeFilterTrait; ... } 取得資料Example :\n$filter = [ \u0026#39;status_eq\u0026#39; =\u0026gt; 1, \u0026#39;keyword_or =\u0026gt; [ \u0026#39;name_k\u0026#39; =\u0026gt; \u0026#39;johnny\u0026#39;, \u0026#39;name_en =\u0026gt; \u0026#39;johnny\u0026#39; ], \u0026#39;title_or\u0026#39; =\u0026gt; [ \u0026#39;sub_title_k\u0026#39; =\u0026gt; \u0026#39;master\u0026#39;, \u0026#39;title_company_k\u0026#39; =\u0026gt; \u0026#39;master\u0026#39; ] ]; $user_list = User::filter($filter)-\u0026gt;get(); 方法簡介 :\n$filter參數的結構\n[column_operator =\u0026gt; value] 在要搜尋的column名稱後面加入\u0026quot;底線\u0026quot; \u0026ldquo;運算子\u0026rdquo; 作為$filter的key，value為要搜尋的值\n\u0026ldquo;or\u0026quot;作為一個特殊的運算子，可以帶入一個Array，裡面的條件將做or查詢，其他條件皆為and查詢\n可用運算子如下 :\nk =\u0026gt; like \u0026#39;%{value}%\u0026#39; ipp =\u0026gt; like \u0026#39;%{value}\u0026#39; iel =\u0026gt; ilike \u0026#39;%{value}\u0026#39; in =\u0026gt; whereIn() nin =\u0026gt; whereNotIn() ge =\u0026gt; \u0026gt;= gt =\u0026gt; \u0026gt; ne =\u0026gt; != eq =\u0026gt; = lt =\u0026gt; \u0026lt; le =\u0026gt; \u0026lt;= nl =\u0026gt; whereNull() nnl =\u0026gt; whereNotNull() cge =\u0026gt; whereColumn(\u0026gt;=) cgt =\u0026gt; whereColumn(\u0026gt;) cne =\u0026gt; whereColumn(!=) ceq =\u0026gt; whereColumn(=) clt =\u0026gt; whereColumn(\u0026lt;) cle =\u0026gt; whereColumn(\u0026lt;=) dr =\u0026gt; whereBetween(,value[0],value[1]) ","date":"2024年6月21日","externalUrl":null,"permalink":"/2024/06/21/%E4%BD%9C%E5%93%81laravel-packeage-scopefilter-%E8%90%AC%E7%94%A8%E7%9A%84%E5%BF%AB%E9%80%9F%E8%B3%87%E6%96%99%E6%90%9C%E5%B0%8B%E5%A4%96%E6%8E%9B/","section":"Posts","summary":"","title":"[作品][Laravel Packeage] ScopeFilter : 萬用的快速資料搜尋外掛","type":"posts"},{"content":"","date":"2024年5月24日","externalUrl":null,"permalink":"/categories/nuxt/","section":"Categories","summary":"","title":"Nuxt","type":"categories"},{"content":"","date":"2024年5月24日","externalUrl":null,"permalink":"/tags/nuxt/","section":"Tags","summary":"","title":"Nuxt","type":"tags"},{"content":"本篇主要解決後端Laravel 所以提供的BinaryFileResponse 檔案讓前端Nuxt可以正確下載並且賦予後端提供的檔案名稱\n參考資料 # https://stackoverflow.com/questions/70911768/laravel-axios-file-download\nhttps://www.cnblogs.com/theblogs/p/17477046.html\nhttps://blog.johnsonlu.org/use-zip-archive-with-php/\n# 首先，後端Laravel的部分 # Excel的範例 # 假設今天要回傳的檔案是Laravel Excel產生的一個.xlsx檔案的話，我們通常會這樣做\nExcel::download(new EXPORT_CLASS(),DOWNLOAD_FILE_NAME); EXPORT_CLASS是將資料處理成excel檔案的一個class，這邊就不多提，DOWNLOAD_FILE_NAME就是設定好要讓瀏覽器下載的檔案名稱\n這時候如果適用post man 或者瀏覽器直接瀏覽這個後端URL都會直接下載到一個.xlsx檔案\n那如果要讓前端正確下載到這個檔案名稱可以這樣做\nExcel::download(new EXPORT_CLASS(),DOWNLOAD_FILE_NAME,\\Maatwebsite\\Excel\\Excel::XLSX,[\u0026#39;Access-Control-Expose-Headers\u0026#39;=\u0026gt;\u0026#39;Content-Disposition\u0026#39;]); 簡單解釋一下參數\n第三個參數:\\Maatwebsite\\Excel\\Excel::XLSX 代表要輸出是xlsx檔案\n第四個參數是header，主要是要將Content-Disposition曝露出來前端Axios才能夠正確抓到檔案名稱\n注意一下，這邊執行的Excel::download的會傳值是一個 Symfony\\Component\\HttpFoundation\\BinaryFileResponse\n如果後端API有自訂response格式也可以調整一下讓檔案回傳這支API符合BinaryFileResponse類型就可以\nZIP的範例 # 後端第二個範例是，如果要下載的是一個zip檔，我們可以這樣做\n$file = tempnam(sys_get_temp_dir(),\u0026#39;\u0026#39;); $tmp = explode(\u0026#39;/\u0026#39;,$file); $download_name = $tmp[count($tmp)-1].\u0026#39;.zip\u0026#39;; $zip = new ZipArchive(); if($zip-\u0026gt;open($file,ZipArchive::CREATE | ZipArchive::OVERWRITE)){ $zip-\u0026gt;addFromString(); //或 $zip-\u0026gt;addFile(); } $zip-\u0026gt;close(); return Response::download($file,$download_name,[\u0026#39;Access-Control-Expose-Headers\u0026#39;=\u0026gt;\u0026#39;Content-Disposition\u0026#39;]); 說明一下幾件事情\ntempnam 產出一個檔案在指定路徑底下，sys_get_temp_dir()則是取得當下系統的暫存檔資料夾，所以這樣子可以產生一個php的暫存檔\n$download_name存下系統產生的這個暫存檔檔名不含路徑\n$zip-\u0026gt;addFromString(); 或是 $zip-\u0026gt;addFile(); 就自行依照情況取用，可參考上方外部連結資訊\n最後Response::download 參數與Excel::download() 大致相同，都是記得在header中把Content-Disposition曝露出來即可\n接著，前端的做法 # Request部分 # 後端回傳的資料格式為BinaryFileResponse，前端再透過Axios呼叫的時候也記得指定responseType，例如以下\nawait axios.post(url,payload,{responseType: \u0026#39;blob\u0026#39;}) 那麽，拿到response之後，後端原本提供的檔案名稱可以在header中這樣取得\nres.headers[\u0026#39;content-disposition\u0026#39;].split(\u0026#34;filename=\u0026#34;)[1] Download部分 # 最後，要讓檔案下載的方式可以參考下方範例\nlet file_name = res.headers[\u0026#39;content-disposition\u0026#39;].split(\u0026#34;filename=\u0026#34;)[1] file_name = file_name.substring(1,file_name.length) const url = window.URL.createObjectURL(new Blob([res.data])); const link = document.createElement(\u0026#39;a\u0026#39;); link.href = url; link.setAttribute(\u0026#39;download\u0026#39;, file_name); //or any other extension document.body.appendChild(link); link.click(); 同樣說明一下內容，首先拿到file_name之後，很神奇的資料會變成_XXX.zip 多了一個底下，因此用substring稍微處理了一下\n剩下的邏輯就是在頁面上產生一個\u0026lt;a\u0026gt; Tag，並且把response放入，最後點擊連結讓瀏覽器下載檔案\n以上\n","date":"2024年5月24日","externalUrl":null,"permalink":"/2024/05/24/%E5%89%8D%E7%AB%AFnuxt-%E4%B8%8B%E8%BC%89-%E5%BE%8C%E7%AB%AF-larvel-%E6%8F%90%E4%BE%9B%E7%9A%84-excel-zip-%E7%AD%89binaryfileresponse-%E6%AA%94%E6%A1%88/","section":"Posts","summary":"","title":"前端Nuxt 下載 後端 Larvel 提供的 Excel Zip 等BinaryFileResponse 檔案","type":"posts"},{"content":"L5 swagger 讓我們可以直接在laravel 原始碼中填寫所需要的文件生成指令，透過php artisan 直接產生api使用文件\n並且帶有swagger-ui介面，可以直接查閱也可以在上面測試api\n沒用過swagger? 可以直接到官網試試看精美的介面\nhttps://swagger.io/tools/swagger-ui/\n先說\nL5 swagger 8.0版本以後已經不能將annotation獨立一個檔案寫了，需要跟controller寫在一起\n請見作者回覆 : https://github.com/DarkaOnLine/L5-Swagger/issues/443\n參考資料 # https://hosomikai.medium.com/laravel-%E4%BD%BF%E7%94%A8swagger-%E7%94%A2%E5%87%BAapi%E6%96%87%E4%BB%B6-2f2a934c3b25\nhttps://dtl625.medium.com/laravel-%E4%BD%BF%E7%94%A8l5-swagger%E8%A3%BD%E4%BD%9Capi%E6%96%87%E4%BB%B6-%E5%AE%89%E8%A3%9D-%E8%A8%AD%E5%AE%9A%E7%AF%87-241d751e079\nhttps://jamie-life-coding.site/2022/09/implement-l5-swagger-to-laravel/\n安裝: # laravel 5.5以後的版本只需要\ncomposer require darkaonline/l5-swagger php artisan vendor:publish --provider \u0026#34;L5Swagger\\L5SwaggerServiceProvider\u0026#34; 舊版本請參閱參考資料連結\npublish指令將設定檔複製到config資料夾中，可以依照需求來修改\n撰寫Annotation # annotation中描述的是每一隻API所要處理的事情、傳入的參數、回覆的內容的等等資訊\n首先，打開 app/Http/Controller.php\n在class Controller extend BaseController 上方加入\n/** * @OA\\Info( * version=\u0026#34;1.0.0\u0026#34;,//版本描述 * title=\u0026#34;L5 OpenApi\u0026#34;,//文件標題 * description=\u0026#34;L5 Swagger OpenApi description\u0026#34;,//文件描述 * @OA\\Contact( * email=\u0026#34;developer@example.com\u0026#34; * ), * @OA\\License( * name=\u0026#34;Apache 2.0\u0026#34;, * url=\u0026#34;http://www.apache.org/licenses/LICENSE-2.0.html\u0026#34; * ) * ) * @OA\\PathItem( * path=\u0026#34;/\u0026#34; * ) * * @OA\\server(//測試主機URL * url = \u0026#34;https://api-host.dev.app\u0026#34;, * description=\u0026#34;測試區主機\u0026#34; * ) * @OA\\server(//正式主機URL，可以不要寫 * url = \u0026#34;https://api-host.production.app\u0026#34;, * description=\u0026#34;正式區主機\u0026#34; * ) * @OA\\server(//開發端主機URL * url = \u0026#34;http://localhost/XXXXX\u0026#34;, * description=\u0026#34;Localhost\u0026#34; * ) * @OA\\SecurityScheme(//如果API需要驗證token請加入 * securityScheme=\u0026#34;Authorization\u0026#34;, * type=\u0026#34;apiKey\u0026#34;, * in=\u0026#34;header\u0026#34;, * name=\u0026#34;Authorization\u0026#34; * ) */ 到這邊為止我們已經處理好swagger所需要的基本元素，透過以下指令就可以自動產生文件\nphp artisan l5-swagger:generate 此時透過PROJECT_URL/api/documentation 就可以查看到swagger-ui產出的文件，大概像這樣子\n當然，查看文件的路徑也可以在config/l5-swagger.php中的decumentatiopns.routes.api 去修改\n接著開始是重點\n針對每一支API請在每個class function中填入以下所需要的描述資訊\n/** * @OA\\Get ( * path=\u0026#34;/{name}\u0026#34;,//router中的path * tags={\u0026#34;find\u0026#34;},//給一個或多個tag方便搜尋用 * summary=\u0026#34;這個api的summary\u0026#34;, * description=\u0026#34;這個api的詳細說明\u0026#34;, * security={ * { * \u0026#34;Authorization\u0026#34;: {} * } * }, * @OA\\Parameter (//輸入參數，如果有多個參數請輸入多個 @OA\\Parameter * name=\u0026#34;帶入的參數名稱\u0026#34;, * description=\u0026#34;帶入的參數說明\u0026#34;, * required=false,//是否必填 * in=\u0026#34;path\u0026#34;,//path 或 request 參數帶在網址或者呼叫傳入 * @OA\\Schema ( * type=\u0026#34;string\u0026#34;//參數型態 * ) * ), * @OA\\Response (//回應狀態，如果有多個回應狀態請填係寫多個 @OA\\Response * response=200, * description=\u0026#34;成功\u0026#34; * ) * ) */ 全部的API function寫完之後再重新產生一次文件就可以了\n","date":"2023年12月26日","externalUrl":null,"permalink":"/2023/12/26/l5-swagger-%E6%95%B4%E5%90%88api%E6%96%87%E4%BB%B6%E5%88%B0laravel%E5%B0%88%E6%A1%88%E4%B8%AD/","section":"Posts","summary":"","title":"L5 Swagger 整合API文件到Laravel專案中","type":"posts"},{"content":"參考資訊\nhttps://ld246.com/article/1582954122248\nhttps://www.kandaoni.com/news/36063.html\nhttps://www.cnblogs.com/qing123/p/15144337.html\n系統必要軟體 # 修改Dockerfile 增加以下，install grpc可能會跑超過1000秒以上需要一些時間\n#grpc RUN pecl install grpc RUN pecl install protobuf RUN apt-get install -yq protobuf-compiler 修改php.ini，增加以下extension\nextension=grpc.so extension=protobuf.so 專案套件 # Laravel 專案中安裝以下套件\ncomposer require grpc/grpc composer require google/protobuf composer require ext-grpc gRPC資料準備 # 準備gRPC server端提供的proto 檔案，範例如下\n#notify_rpc.proto syntax = \u0026#34;proto3\u0026#34;; //package name package Notify; //service name service Notifier { //function name rpc send_message (NotifyRequest) returns (NotifyResponse); } //method1 message NotifyRequest { string message = 1; string platform = 2; } //method2 message NotifyResponse { string message = 1; } 在專案資料夾中建立以下資料夾\nprotobuf/build\nprotobuf/src\n將notify_rpc.proto檔案放到protobuf/src中\n執行以下指令生成相對應的php檔案\nprotoc --proto_path=protobuf --php_out=protobuf/build protobuf/src/notify_rpc.proto \u0026ndash;php_out指定生成的位置\n最後一個參數告訴他proto檔案的位置在哪\n這時候會看到專案資料夾/protobuf/build中產生了以下幾個檔案\nGPBMetadata/Src/NotifyRpc.php Notify/NotifyRequest.php Notify/NotifyResponse.php 根據外部連結的說明文件來看，我們還缺一個 Nofity/NotifyClient.php檔案，原因是還需要透過grpc_php_plugin來生成，但是這個plugin一直build不起來\n不過NofityClient.php不難，就手刻一個\n\u0026lt;?php //namespace 與NofityRequest.php中相同 namespace Notify; class NotifyClient extends \\Grpc\\BaseStub { public function __construct($hostname, $opts, $channel = null) { parent::__construct($hostname, $opts, $channel); } //send_message就是proto檔案中定義的，傳入值為NofityRequest，回傳值為NofityResponse public function send_message(\\Notify\\NotifyRequest $argument, $metadata = [], $options = []) { return $this-\u0026gt;_simpleRequest(\u0026#39;/Notify.Notifier/send_message\u0026#39;, $argument, [\u0026#39;\\Notify\\NotifyResponse\u0026#39;, \u0026#39;decode\u0026#39;], $metadata, $options); } } gRPC服務呼叫 # 完成以上設定後就可以在Controller中呼叫Notify gRPC服務，範例如下\n\u0026lt;?php namespace App\\Http\\Controllers; use Illuminate\\Http\\Request; use Notify\\NotifyRequest; use Notify\\NotifyClient; use Grpc\\ChannelCredentials; class IndexController extends Controller { // public function index(){ //grpc_test $client = new NotifyClient(\u0026#39;GRPC_SERVER_IP:PORT\u0026#39;,[ \u0026#39;credentials\u0026#39; =\u0026gt; ChannelCredentials::createInsecure(), ]); $request = new NotifyRequest(); //將需要傳送的兩個參數分別透過setMessage以及setPlatform加入 $request-\u0026gt;setMessage(\u0026#39;hello123\u0026#39;); $request-\u0026gt;setPlatform(\u0026#39;slack\u0026#39;); $get = $client-\u0026gt;send_message($request)-\u0026gt;wait(); return view(\u0026#39;index\u0026#39;); } } ","date":"2023年12月13日","externalUrl":null,"permalink":"/2023/12/13/laravel-grpc-client-on-docker/","section":"Posts","summary":"","title":"Laravel gRPC Client on Docker","type":"posts"},{"content":"","date":"2023年2月15日","externalUrl":null,"permalink":"/categories/home-assistant/","section":"Categories","summary":"","title":"Home Assistant","type":"categories"},{"content":"","date":"2023年2月15日","externalUrl":null,"permalink":"/tags/home-assistant/","section":"Tags","summary":"","title":"Home Assistant","type":"tags"},{"content":"","date":"2023年2月15日","externalUrl":null,"permalink":"/tags/shelly/","section":"Tags","summary":"","title":"Shelly","type":"tags"},{"content":"如果你還沒有安裝好Home Assistant 請先閱讀\n智慧家庭第一步 在Synology上架設Home Assistant\n如果你還沒在Home Assistant上安裝HACS 請先閱讀\n智慧家庭第二步 Home Assistant安裝 HACS\n如果以上兩個步驟都已經完成，那麼我們就往下繼續看吧\nShelly控制器在台灣並不常見，常見的幾乎都是大陸品牌居多，今天要說明的是Shelly 1PM\n從名稱看起來就會知道，這顆可以控制一個迴路燈具且具有耗電量統計，Shelly1PM走的連線方式是Wifi直連，沒有透過其他網關之類的裝置走Zigbee，相對的就必須更確保家裡的無線網路必須要穩定不斷線，否則就會常看到Shelly離線電燈無法控制。\nShelly的優勢在於，原生就支援Home Assistant以及從Amazon購買運回來一顆大約台幣600多\n等於說，全屋子假設有20個電燈迴路需要控制器那也就是一萬多塊的硬體費用，相對便宜滿多的，另一方面Shelly屬於迴路控制開關不需要替換牆上原本的實體開關，這樣子做可以保留牆上開關使用，萬一智能的部分斷線還是可以使用實體開關來控制燈具，比較保險。\n但是缺點就是Shelly並非單火線產品，需要額外電力供給Shelly本身運作，而且體積雖然小但是應該還是塞不下開關盒\n因此，安裝上就必須要在電燈處(天花板內)多增加一組火線供電給Shelly才行\nOK，看到這裡需要說明的是本篇教學只僅於提供Shelly的接法以及設定方式，最終因為天花板內無法供電因此目前尚未正式使用\n第一步 : 安裝Shelly APP # 安裝Shelly APP之前，我們先來看一下搞懂上面些接點是什麼意思\n這是我認為最困難的一步，首先我們先搞懂上面些接點是什麼意思\nL : 代表的是Shelly本身的火線電源\nL1 : 可以接回開關供電，因為開關處本身已經有電所以L1基本上可以不接\nSW : 由開關來的控制線，也就是原本接在電燈上的那條\n0 : 接電燈正極\nN : 接電燈的負極，也是Shelly的負極\n接下來，我們就先接上L與N讓Shelly過電吧\n插上插頭後會看到Shelly亮燈開機\n等幾秒鐘開幾完成後先用手機Wifi介面檢查一下Shelly是不是已經打出訊號來等連接了\n到這部分如果沒問題的話，就依照自己手機的系統把Shelly cloud App裝起來吧\nAPP安裝好登入Shelly cloud帳號之後，首先我們必須建立一個空間名稱\n這邊範例我們建立一個叫做\u0026quot;書房\u0026quot;的空間\n空間建立好之後，繼續往下我們要將Shelly 1PM加入書房這個空間來\n接著確認目前連線的wifi帳號密碼，app會把這些資訊寫入Shelly讓他可以連上家裡的wifi\n接著下一步，選擇要接入的shelly裝置，今天要接入的裝置事1PM\n接著APP會切換到我們剛剛看到的Shelly自己打出來的Wifi訊號\n最後會建議如果有update，就把它裝完吧，整個完成之後就會在Shelly的App中看到裝置上線啦\n其實如果沒有要使用其他平台控制Shelly的話，這時候已經可以透過APP來控制Shelly的通電狀態了\n不過，還沒完成接下來我們要來說明一下Shelly對電燈的正確接法，最後再帶大家把Shelly接上Home Assistant\n第二步 : 研究一下Shelly 如何接 # Shelly可以放在牆內開關旁，也可以放在天花板內燈具旁，有鑒於牆內空間幾乎是不夠Shelly擺放的，下圖的接法適合放在燈具附近\n圖中，插座的部分模擬供火線電源給Shelly使用，零線的部分就跟電燈接一起就行\n這樣接上後，如果一切正常的話開關也是可以正常啓閉電燈的，當然手機APP也行\n第三步 : 接入Home Assistant吧 # 接入Home Assistant大平台之後可以讓Shelly更容易跟其他IOT裝置互動，設定不同場景會比直接用Shelly App來的豐富\n而Shelly本身就支援Home Assistant，因此接入的方式非常容易\n首先我們來到HACS的Integrations\n選擇右下角的Explorer 按鈕來尋找安裝Shelly套件\n接著輸入Shelly，應該就能找到ShellyForHass\n進入頁面後點選右下角Download\n接著就跟安裝一般HACS套件一樣，選擇要安裝的版本，通常都是直接安裝最新版\n安裝完成後，來到畫面的設定 \u0026ndash;\u0026gt; 裝置與服務\n點選右下角新增整合\n搜尋關鍵字Shelly，找到剛剛裝上的整合\n開始安裝之前，請確認Shelly目前的IP位置，至於如何找到IP位置就不再多做說明\n前幾篇其他設備安裝時都有提到，請到自己的Wifi分享器中找到Shelly裝置，查看IP後，輸入到畫面中\n設定完成後，我們就能夠在整合中看到Shelly啦\nShelly有很多數據可以讀出，因此裝置雖然只有一個，卻有14個實體可以讀取資料，點進去裝置畫面就能看到\u0026quot;控制\u0026quot;這個區域出現了Shelly的開關\n請記住目前實體的名稱，等等還會用上它\n直接點選這個開關也是可以控制Shelly開關燈的，只是每次都要這樣近來太麻煩了，所以我們要把這個開關設定到總覽畫面上\n我們來到總覽畫面，點選右上角的編輯主面板按鈕\n這次我把燈具開關都放一起，所以在開關這邊選擇編輯\n在眾多實體中，搜尋shelly吧，找到我們剛剛在整合中看到的開關名稱\n選進來之後，就會看到在面板中的開關多了一個Shelly1pm，這樣以後就可以直接在總覽畫面點選開關shelly的燈了\n總結 : # Shelly1PM 是在智能家庭社團中滿多人推薦可以使用的迴路開關，但是這樣種開關真的只適合使用在裝潢前的房子，像我只是買來玩玩的話就不太適合，畢竟Shelly需要多一條火線供電，除非自己有水電經驗可以自行從天花板找到其他有電的火線給Shelly使用外，一般人還是不要輕易嘗試自己亂接。\n最後，測試了一段時間Shelly連線滿穩定的，也歸功於家裡的Unifi無線網路非常穩定不斷線，未來Shelly會是裝修新家做燈具控制的參考項目之一，下一步將要帶大家把Home Assistant中的裝置帶入Home kit中，畢竟Home kit的操作介面還是比較簡潔一些，相對的其他家人要操作可能更容易。\n","date":"2023年2月15日","externalUrl":null,"permalink":"/2023/02/15/%E6%99%BA%E6%85%A7%E5%AE%B6%E5%BA%AD%E7%AC%AC%E4%B8%83%E6%AD%A5-home-assistant%E6%8E%A5%E5%85%A5shelly%E4%BE%86%E6%8E%A7%E5%88%B6%E4%BD%A0%E7%9A%84%E9%9B%BB%E7%87%88%E5%90%A7/","section":"Posts","summary":"","title":"智慧家庭第七步 Home Assistant接入Shelly來控制你的電燈吧","type":"posts"},{"content":"Laravel的Eloquent中，orderBy column並無法提供relationship table column，因此通常需要使用relationship表單中的欄位來排序時我們會使用sortBy 或者sortByDESC來處理，但是如果今天遇上了分頁，我們將無法透過pagination的結果來做sort\n概念 # 概念上來說，如果遇到需要透過relationship column來做排序的狀況我們先透過get()將資料取出，再經由sortBy或者sortByDESC來排序，這時候會得到一個排序完成的collection，最後再將這個collection做pagination即可\n因此本篇的重點將會是如何將collection做pagination\n參考資料 # https://gist.github.com/simonhamp/549e8821946e2c40a617c85d2cf5af5e\nhttps://stackoverflow.com/questions/72287090/how-to-paginate-laravel-collection\nhttps://www.pakainfo.com/eloquent-order-by-relation-column-in-laravel/\npagination注入 # AppServiceProvider.php boot function中加入以下，讓collection支援pagination\n/** * Paginate a standard Laravel Collection. * * @param int $perPage * @param int $total * @param int $page * @param string $pageName * @return array */ Collection::macro(\u0026#39;paginate\u0026#39;, function($perPage, $total = null, $page = null, $pageName = \u0026#39;page\u0026#39;) { $page = $page ?: LengthAwarePaginator::resolveCurrentPage($pageName); return new LengthAwarePaginator( $this-\u0026gt;forPage($page, $perPage), $total ?: $this-\u0026gt;count(), $perPage, $page, [ \u0026#39;path\u0026#39; =\u0026gt; LengthAwarePaginator::resolveCurrentPath(), \u0026#39;pageName\u0026#39; =\u0026gt; $pageName, ] ); }); 因此AppServiceProvider.php將會是以下的內容\n\u0026lt;?php namespace App\\Providers; use Illuminate\\Support\\ServiceProvider; use Illuminate\\Support\\Collection; use Illuminate\\Pagination\\LengthAwarePaginator; class AppServiceProvider extends ServiceProvider { /** * Register any application services. * * @return void */ public function register() { // } /** * Bootstrap any application services. * * @return void */ public function boot() { // /** * Paginate a standard Laravel Collection. * * @param int $perPage * @param int $total * @param int $page * @param string $pageName * @return array */ Collection::macro(\u0026#39;paginate\u0026#39;, function($perPage, $total = null, $page = null, $pageName = \u0026#39;page\u0026#39;) { $page = $page ?: LengthAwarePaginator::resolveCurrentPage($pageName); return new LengthAwarePaginator( $this-\u0026gt;forPage($page, $perPage), $total ?: $this-\u0026gt;count(), $perPage, $page, [ \u0026#39;path\u0026#39; =\u0026gt; LengthAwarePaginator::resolveCurrentPath(), \u0026#39;pageName\u0026#39; =\u0026gt; $pageName, ] ); }); } } 範例 # 接著我們來看一下範例\n$articles = Article::with(\u0026#39;writer\u0026#39;)-\u0026gt;get()-\u0026gt;sortBy(function($query){ return $query-\u0026gt;writer-\u0026gt;name; })-\u0026gt;pagination(15); 先將Article中的資料用get()取出後透過sortBy依照writer relationship中的name來排序，最後得到collection再透過上一步驟增加的pagination function來替collection分頁\n","date":"2022年12月13日","externalUrl":null,"permalink":"/2022/12/13/laravel-order-by-relationship-column-with-pagination/","section":"Posts","summary":"","title":"Laravel order by relationship column with pagination","type":"posts"},{"content":"Nuxt Auth的文件寫的真的有點零散，而且目前V5的網路資源似乎有點少，因此整理一篇關於使用Nuxt Auth套件如何做到Local 登入得整個過程以及概念\n參考文件: # https://auth.nuxtjs.org/\nhttps://blog.csdn.net/qq_21567385/article/details/108459773\n基礎概念: # Nuxt Auth幫我們做到了以下幾件事情\n跟後台驗證帳號密碼，並取得認證後的token 透過token取得登入者的相關資訊 將登入資訊儲存於cookie或者localStorage中 還有一個觀念一定要記住，Auth並不會記錄下使用者資訊，他是每次reload的時候都透過api來取得資訊\nAuth的概念是，如果驗證帳號密碼通過有取得token，並且正確取得使用者資料，那麼就算是登入成功，其他狀態都會是失敗的\n只有登入成功的狀況下，Auth.loggedIn會是true，其他狀態都會是false\n因此有幾個後端的URL必須先準備好\n登入驗證帳號密碼用的URL，這邊為了方便將它取為/api/login 透過token取得使用資料的URL，這邊為了方便將它取為/api/login/member/get 登出的URL，其實後端並沒有作用，但是還是必須要有這個URL，這邊為了方便將它取為/api/logout 在開始之前，先說明一下這篇文章總共做到幾件事情\n透過Auth登入 將token帶入Axios header中，方便每次透過api取得資訊都可以帶入 安裝與基本設定 # 根據官方資料安裝非常間單，這邊的範例使用npm來安裝\nnpm install @nuxtjs/auth-next 接著需要設定nuxt.config.js\nmodules中，如果沒有安裝axios也需要一並安裝起來\nmodules: [ ... \u0026#39;@nuxtjs/axios\u0026#39;, \u0026#39;@nuxtjs/auth-next\u0026#39;, ... ] Auth中 :\nauth: { strategies: { webMemberLogin: { scheme: \u0026#39;local\u0026#39;, token: { property: \u0026#39;token\u0026#39;, maxAge: 60 * 60 * 24 }, user: { property: \u0026#39;data\u0026#39;, maxAge: 60 * 60 * 24 }, endpoints: { login: { url: \u0026#39;/api/login\u0026#39;, method: \u0026#39;post\u0026#39; }, logout: { url: \u0026#39;/api/logout\u0026#39;, method: \u0026#39;post\u0026#39; }, user: { url: \u0026#39;/api/login/member/get\u0026#39;, method: \u0026#39;post\u0026#39; }, } } }, cookie: { options: { prefix: \u0026#39;auth.\u0026#39;, maxAge: 60 * 60 * 24 } }, plugins:[ { src: \u0026#39;~/plugins/axios\u0026#39;, ssr: true } ], localStorage: false }, 說明一下上面的Object代表什麼\nstrategies中，需要去定義要用什麼方式登入，依照官網文件提供多種驗證方式，這邊就不多說明，本次範例使用的是Local登入\nscheme選擇內建的local，\ntoken:property定義從後端登入URL拿到的JSON Object，哪個欄位是token，maxAge定義要存活多久，單位是秒，也就是可以登入多久\nuser:property定義從後端取得使用者資訊的JSON Object，哪個欄位是使用者資訊，maxAge定義要存活多久，單位是秒，也就是可以登入多久\nendpoints: 分別需要填入上方說明的，登入驗證、取得使用者、登出等三條URL\nCookie中，定義prefix，以及過期資訊，範例中我設定位一天過期\nplugin中，需要將plugins/axios加入，因為會需要在axios plugin中讀取$auth物件資料，並且將token帶入header中\nlocalStorage，我設定為false，代表不記錄到localStorage中\n執行登入 # 在需要登入的地方執行\nthis.$auth.loginWith(\u0026#39;webMemberLogin\u0026#39;,{ data:{ account:this.account, password:this.password } }) 其中，webMemberLogin就是在nuxt.config.js中，定義好的strategies名稱\n特別需要注意的是，執行this.$auth.loginWith 這行，Auth會執行兩件事情\n先執行login URL 驗證通過後再執行user URL Axios設定 # 到目前為止，應該是可以正確透過帳號密碼驗證完畢，但是如過使用token取得使用者資訊呢？\n我們必須要把拿到的token加入到axios plugin中\nimport axios from \u0026#34;axios\u0026#34;; export default function({$auth}) { if(typeof $auth !== \u0026#39;undefined\u0026#39; \u0026amp;\u0026amp; typeof $auth.strategy.token.get() !== \u0026#39;undefined\u0026#39;){ //header 預設帶入Login拿到的token axios.defaults.headers.common[\u0026#34;Authorization\u0026#34;] = $auth.strategy.token.get() } } 兩點說明\n有在nuxt.conf.js中，auth的plugins加入~/plugins/axios才能正確讀取到$auth這個Object 透過$auth.strategy.token.get()加入到header中的Authorization，Laravel後端api可以透過$request-\u0026gt;header(\u0026lsquo;Authorization\u0026rsquo;) 來取得 資料驗證 # 登入後，在Vuex中的auth過可以看到loggedIn已經是true\n並且有user資料存在，執得一提的是當網頁重新整理之後，user資料會透過設定好的user URL重新取得一次喔\n","date":"2022年10月31日","externalUrl":null,"permalink":"/2022/10/31/nuxt-auth-%E7%99%BB%E5%85%A5%E5%A5%97%E4%BB%B6-step-by-step/","section":"Posts","summary":"","title":"Nuxt Auth 登入套件 step by step","type":"posts"},{"content":"如果你還沒有安裝好Home Assistant 請先閱讀\n智慧家庭第一步 在Synology上架設Home Assistant\n如果你還沒在Home Assistant上安裝HACS 請先閱讀\n智慧家庭第二步 Home Assistant安裝 HACS\n如果以上兩個步驟都已經完成，那麼我們就往下繼續看吧\n本篇以switchbot Thermometer/Hygrometer sensor為範例，透過switchbot提供的 cloud API取得資料回到Home Assistant中\n參考資訊 ：\nhttps://www.amazon.co.jp/-/en/SwitchBot-Learning-Remote-Control-Compatible/dp/B07L4QNZVF/ref=sr_1_5?crid=3JNAM9XU6X8YO\u0026keywords=%E3%82%B9%E3%82%A4%E3%83%83%E3%83%81%E3%83%9C%E3%83%83%E3%83%88\u0026qid=1663145809\u0026sprefix=switchbiot%2Caps%2C295\u0026sr=8-5\u0026th=1\nhttps://www.youtube.com/watch?v=ZLsTgXILabE\u0026ab_channel=ThisSmartHouse\n前言 : # switchbot 溫濕度計其實只需要購買本體，不需要購買其他配件就可以在手機APP中加入裝置並且看到資訊，而這次要做的是需要透過Wifi接入Home Assistant中，所以必須多購入switchbot hub mini，未來如果在其他空間中也需要擺放溫濕度計，那麼就只要買溫濕度計本體就行，只需要有一個hub存在即可。\n這大概是目前最便宜的解決方案\ngithub上有另一個專案是透過Home Assistant本身的藍芽裝置去搜尋附近的switchbot裝置，這個方法也是可行，只是我Home Assistant是裝在虛擬機中，無法使用藍牙，而且藍芽有距離限制，因此本次採用的是switchbot cloud API方式來取得資料。\nStep1 : 將溫濕度計/Hub mini 加入switchbot APP中\nswitchbot的溫濕度計擁有網路以及藍芽兩個介面，因此加入方式超簡單，安裝好APP登入後，按右上角的+號就能找到裝置\n這時候，手機是透過藍芽跟溫濕度計連線的，所以在裝置附近的話才能夠看到數值，有測試過隔一個房間就會收不到了\n接下來，再將hub mini用一樣的方式加入switchbot app中，接著點入hub mini頁面，選擇右上角的設定按鈕\n進入畫面後，選擇wifi settings\n進到這個畫面之後，請選擇要讓hub mini連接上的無線網路SSID，以及輸入密碼\n這樣子就完成了初期設定了，這時候神奇的事情來了，同一個App底下的裝置都會走跟Hub mini一樣的網路設定連接到家中的wifi\n這時候就可以發現，手機已經不是用藍芽連接到溫濕度計上了，而是透過網路介面連接到switchbot cloud上\nStep2 : 取得API token # 接下來就是要取得API token了，這樣子才能讓Home Assistant透過這組API token向switchbot cloud取得資料\n點選App右下角的Profile\n選擇Perferences\n接著找到，底下的App Version，對著這個按鈕點10下，對你沒有看錯就是對他點10下\n然後，畫面上就會出現Developer Options這個選項\n進入畫面之後，產生一組新的API Token，然後複製起來，這組token很重要，接下來很多步驟都會用到它\nStep3 : 取得 device ID # device ID就是你的溫濕度計裝置的ID，拿到這個ID之後，下一步就是透過API Token以及device ID來取得數值內容了\n首先如果你是windows用戶，請在搜尋中打上cmd，打開命令模式，如果是Mac用戶，請使用\u0026quot;終端機\u0026quot;\n接下來需要輸入一個指令，基本上不難照貼就好\ncurl -H “Authorization:[API_TOKEN]” https://api-switch-bot.com/v1.0/devices/ 在終端機中貼上上面的指令，把[API_TOKEN]這幾個字換成你剛剛在APP中找到的就好，按下Enter之後你應該會看到類似這樣的畫面，找到deviceId後面那串用中括號刮起來的文字就是我們要的deviceId了\n接著可以用下一行指令來看看這個device可以提供我們什麼樣的資料\ncurl -H “Authorization:[API_TOKEN]” https://api-switch-bot.com/v1.0/devices/[DEVICE_ID]/status 同樣的把[API_TOKEN]以及[DEVICE_ID]替換成你的就行了，接著你應該可以看到類似下面的畫面，其中有看到humidity以及temperature兩個數值就是我們會用在Home Assistant中的了\nStep 4 : 設定Home Assistant YAML擋 # 首先，我們剛剛拿到了很多token deviceId以及網址之類的東西，我們先找個地方存下來\n在根目錄底下新增一個secrets.yaml檔案，把剛剛拿到的資料存下\nswitchbot_api: “[TOKEN]”\nswitchbot_device_status_url: https://api.switch-bot.com/v1.0/devices/[DEVICE_ID]/status\nswitchbot_device_deviceId: [DEVICE_ID]\n如果你有多個裝置就放多個deviceId跟url這樣\n接著，新增一個rests.yaml檔案在根目錄，輸入以下資訊\nresource: !secret switchbot_book_room_status_url method: GET\nscan_interval: 120\nheaders:\nAuthorization: !secret switchbot_api\nContent-Type: \u0026ldquo;application/json\u0026rdquo;\nsensor:\nname: \u0026ldquo;book_room_temperature\u0026rdquo; value_template: \u0026ldquo;{{value_json.body.temperature | float}}\u0026rdquo;\nunique_id: \u0026ldquo;book_room_temperature\u0026rdquo;\nunit_of_measurement: °C\nname: \u0026ldquo;book_room_humidity\u0026rdquo; value_template: \u0026ldquo;{{value_json.body.humidity | float}}\u0026rdquo;\nunique_id: \u0026ldquo;book_room_humidity\u0026rdquo;\nunit_of_measurement: \u0026ldquo;%\u0026rdquo;\n簡單說明一下，看到!secret開頭的就是我們剛剛放在secret.yaml中的資訊，而value_template則是描述API拿回來的資訊格式\n我們需要兩個資訊，一個是溫度一個是濕度，所以在sensor中我們建了兩個，分別去取得value_json中的bosy.temperature以及body.humidity兩個，unit_of_measurement則是去定義這個數值的單位是什麼，unique_id則是去表達在Home Assistant這個sensor的唯一ID，自訂不重複即可\nStep 5：重新啟動Home Assistant # 再重新啟動Home Assistant後，我們就可以在\u0026quot;實體\u0026quot;中看到剛剛新增進去的兩個sensor了喔\n","date":"2022年9月15日","externalUrl":null,"permalink":"/2022/09/15/%E6%99%BA%E6%85%A7%E5%AE%B6%E5%BA%AD%E7%AC%AC%E5%85%AD%E6%AD%A5-home-assistant%E6%8E%A5%E5%85%A5%E8%B6%85%E4%BE%BF%E5%AE%9Cswitchbot%E6%BA%AB%E6%BF%95%E5%BA%A6%E8%A8%88/","section":"Posts","summary":"","title":"智慧家庭第六步 Home Assistant接入switchbot超便宜溫濕度計","type":"posts"},{"content":"如果你還沒有安裝好Home Assistant 請先閱讀\n智慧家庭第一步 在Synology上架設Home Assistant\n如果你還沒在Home Assistant上安裝HACS 請先閱讀\n智慧家庭第二步 Home Assistant安裝 HACS\n如果以上兩個步驟都已經完成，那麼我們就往下繼續看吧\n本篇以帶有溫濕度感應器的BroadLink RM4 Pro為範例，接入Home Assistant後，顯示溫濕度資訊並且學習吸頂燈遙控器，透過Home Assistant就可以控制吸頂燈了，並且可以偵測客廳中的溫溼度變化\nStep 1 : 連接BroadLink設備到手機APP中 # 這部分就不多做說明，連接方式很簡單，跟很多大陸的IoT設備相同，先下載BroadLink App後，按住RM4 Pro 的reset後進入AP模式\n將設備加入\nStep 2 : Home Assistant 安裝BroadLink 套件 # 首先到HACS中搜尋BroadLink，找到BroadLink s2c and s1c sensors套件，並按下右下角的download\n進入畫面之後，點選右下角\u0026quot;download this repository with hacs\u0026quot;\n最後按下download\n下載完成後，我們到設定 \u0026ndash;\u0026gt; 整合 \u0026ndash;\u0026gt; 新增整合中，就可以找到broadlink\nStep 3 : 將RP4 Pro 接入Home Assistant\n首先，我們還是要到wifi 分享器中查看BroadLink RM4 Pro在Step1連接到家裡的wifi之後，拿到的IP是多少\n接著回到整合中的BroadLink，進入之後，輸入RM4 Pro的IP位置\n接著下一步給他一個好記的名稱\n最後一步，按下完成就已經接入完畢了，這次夠簡單的吧\n這時候，已經將RM4 Pro接入Home Assistant了，因為它帶有溫濕度感應器，所以已經可以在感測器中找到我們剛剛命名的wifi 智能遙控傳回的環境溫度跟濕度了\nStep 4 : 學習原有遙控器上的按鈕編碼 # 最後，也是比較困難的一步是要讓RM4 Pro發出跟原本控制吸頂燈遙控器一樣的編碼才行，所以我們必須要把原本遙控器上每一個按鈕的編碼都學習到Home Assistant中\n我們到開發工具中的服務頁籤，在第一個欄位\u0026quot;目標\u0026quot;中按下\u0026quot;+選擇實體\u0026quot;，找到我們剛剛命名的wifi-智能遙控 romote\n第二個欄位Device打勾，給他的名稱叫做light\n第三個欄位Command打勾，給他一個指令名稱叫做on\n第四個欄位Command Type打勾，下拉選擇ir\n按下執行服務後，你會發現RM4 Pro上的燈號有些變化，這時候就拿著原有的遙控器對RM4 Pro按下你想學習的按鈕，他就會把編碼錄下來\nStep 5 : 加一個按鈕讓Home Assistant幫你透過RM4 Pro發出紅外線指令 # 我們到總覽中增加一個按鈕來使用，實體欄位中選擇到剛剛設定好的遙控器名稱\n名稱的部分打上我們好辨識的文字\n點選觸發欄位選擇\u0026quot;執行服務\u0026quot;\n服務的欄位選擇remote.send_command\n接下來一樣透過\u0026quot;+選擇實體\u0026quot;按鈕，找到我們接入的wifi-智能遙控 remote\nDevice欄位輸入我們上個步驟錄指令的時候輸入的device name : light\nCommand欄位輸入我們上個步驟錄指令的時候輸入的command name : on\n以上整串的邏輯代表著，我們想要透過按鈕去執行服務，服務內容是透過wifi-智能遙控去發出我們錄製好的light - on 指令\n按下儲存後，就紀錄好這顆按鈕要做什麼樣的事情了\n這邊，就只示範一個按鈕的錄製以及發送，如果有很多顆按鈕需要使用的話就重複step4以及step5就行了\n後記 : # RM4 Pro很方便的地方是它帶有溫度以及濕度的感應器，雖然跟遙控器本身沒有相關，但是這部分本來就是我想要接入的資訊，剛好這樣就可以少接一項裝置。\n這類型的紅外線萬用遙控器方便的是可以去控制你家所有使用紅外線遙控器的家電，全部都可以接到Home Assistant內，缺點是Home Assistant無法知道透過紅外線遙控器發出指令後機器是不是有正確接收到指令並且執行了，也就是說依照我們上面的例子按下開燈後如果電燈沒有接收到萬用遙控器發送過來的開封訊號的話，他就不會開燈而Home Assistant也不會知道電燈到底亮了沒。\n","date":"2022年9月8日","externalUrl":null,"permalink":"/2022/09/08/%E6%99%BA%E6%85%A7%E5%AE%B6%E5%BA%AD%E7%AC%AC%E4%BA%94%E6%AD%A5-home-assistant%E6%8E%A5%E5%85%A5%E8%90%AC%E7%94%A8%E9%81%99%E6%8E%A7%E5%99%A8%E9%82%84%E6%9C%89%E6%BA%AB%E6%BF%95%E5%BA%A6%E8%B3%87/","section":"Posts","summary":"","title":"智慧家庭第五步 Home Assistant接入萬用遙控器還有溫濕度資訊","type":"posts"},{"content":"如果你還沒有安裝好Home Assistant 請先閱讀\n智慧家庭第一步 在Synology上架設Home Assistant\n如果你還沒在Home Assistant上安裝HACS 請先閱讀\n智慧家庭第二步 Home Assistant安裝 HACS\n如果以上兩個步驟都已經完成，那麼我們就往下繼續看吧\n本次以sonoff basic 刷tasmato韌體，讓他可以連接到Home Assistant上，韌體的部分這邊就不多做描述，\n可以在各賣場找找，有賣家直接賣刷好的，也有買刷好可以接Homekit的版本\n本篇的概念是在Home Assistant中安裝MQTT server，然後Sonff會透過MQTT通訊協定跟Home Assistant溝通\nStep 1 安裝MQTT # 首先在Supervisor中找到mosquitto broker\n找到她後，點選進去按下\u0026quot;安裝\u0026quot;\n安裝完成後，記得勾選\u0026quot;於啟動時開啟\u0026quot;，這樣才能讓MQTT server在Home Assistant啟動時也一併啟動\n接著，到\u0026quot;設定\u0026quot;頁籤中，設定好伺服器連線的帳號以及密碼，待會在sonoff中會用到這組連線帳號密碼，輸入完畢後記得按下儲存按鈕\n接著，回到\u0026quot;資訊\u0026quot;頁籤按下啟動，來啟動MQTT server服務\nStep 2 Home Assistant 啟用MQTT服務 # 將畫面移到設定 \u0026ndash;\u0026gt; 整合\n畫面上應該會掃描到已經安裝好的MQTT，點選進入設定\n跳出提示時，點選\u0026quot;傳送\u0026quot;\n出現成功，代表Home Assustant中MQTT已經設定完成，接下來就要來設定Sonoff了\nStep 3 將Sonoff接上電力 # Sonoff連接方式超簡單，他不需要中性線，因此你家所有的電燈登能夠安裝，裝置上就四個鎖點兩進兩出，如下圖示\nSonoff 兩邊線路接口有文字說明，input這邊接上牆上的電線，output則接往電燈\n為了方便先設定網路，因此我先找了條電線，把Sonoff插上插頭通電\n電源連接的方式用螺絲起子擰起壓入電線後鎖緊即可\nStep 4 設定Sonoff連上家中wifi # Sonoff通電之後會進入AP模式，拿起手機在wifi中應該就能找到tasmota_XXX的SSID\n連接上去之後就會自動進入設定畫面，畫面中，我們要選擇sonoff要連接上的家中Wifi SSID以及輸入wifi密碼，之後按下儲存即可\n接下來畫面上就會看到裝置開始連接到家中的wifi網路\n直到出現成功，就是設定完成了\nStep 5 將Sonoff連接上Step 1中安裝好的MQTT Server # 我們先到家中wifi路由器找到Sonoff連上後的IP位置，當然上圖中，連線成功之後也會顯示IP\n接著用瀏覽器輸入sonoff的所在IP位置，例如:http://192.168.10.65，就可以進入設定畫面，進入後點選\u0026quot;設定\u0026quot;\n接著進入MQTT設定\n進入設定畫面之後，我們要修改的地方有三個，分別是主機IP位置、連線帳號、密碼，帳號密碼就是在Step1中設定檔中輸入的帳號密碼，而IP位置就是Home Assistant所在的IP位置\nStep 6 設定Tasmota整合 # 連線最後一個步驟，回到Home Assistant中，安裝Tasmato整合套件，讓他可以來接收MQTT的訊息控制開關\n我們回到Home Assistant中的設定 \u0026ndash;\u0026gt; 整合，按下新增整合\n搜尋Tasmato應及就能找到\n點選進入後按下\u0026quot;傳送\u0026quot;\n最後會看到成功訊息\n這時候，就可以在總覽中新增一個開關來控制電燈啦\nStep 7 實際將Snoff接上陽台上的燈吧 # 拆下LED燈泡的底座之後也把燈泡先卸除\n燈座底下應該可以找到兩條線接在電燈底座上，一條火線一條燈線\n我們將兩者分離開來，中間跨接上sonoff\n從開關來的線插上input，往電燈的線插上output即可，最後將燈具重新安裝上去就完工啦\n後記: # Sonoff 這類型的開關不需要另外拉中性線，也不需要另外給控制器電源，只需要很簡單的把控制器跨接到開關與電燈之間\n但是這樣的安裝方式也有著很明確的缺點，就是我牆上的開關就必須隨時保持在\u0026quot;開\u0026quot;的狀態，因為這樣子控制器才有電源可以運作\n不過在不異動任何插座開關裝置的情況下確實可以將電燈透過Home Assistant來控制，也不失一個折衷的做法\n","date":"2022年9月4日","externalUrl":null,"permalink":"/2022/09/04/%E6%99%BA%E6%85%A7%E5%AE%B6%E5%BA%AD%E7%AC%AC%E4%B8%89%E6%AD%A5%E7%94%A8-home-assistant%E4%BE%86%E6%8E%A7%E5%88%B6%E4%BD%A0%E7%9A%84%E9%9B%BB%E7%87%88%E5%90%A7/","section":"Posts","summary":"","title":"智慧家庭第四步用 Home Assistant來控制你的電燈吧","type":"posts"},{"content":"這是這兩天無意間遇到的問題，因為在測試MUX API發現資料一直送不出去\n不知道什麼是MUX ? 可以先看這篇 Laravel video upload streaming 影片上傳以及編碼解決方案 MUX\n但是其實MUX不重要，只是剛好我卡在這個API而已\n一直以來都有用php curl, guzzleHTTP, Laravel Http Client 跟其他API溝通傳遞資料的經驗，不曾遇到出這樣的錯誤訊息\nrecv()錯誤是由Nginx丟出來了，目前的開發環境是架構在docker-compose底下，經由Nginx pass 到php-fpm container\n所以，遇上這個問題我一直都認為是Nginx 或者php.ini內的設定值修改後就可以排除\n也google到很多解法，概念都是php-fpm中斷執行導致Nginx找不到可以byPass的地方，所以延長php-fpm執行時間就可以解決\n解決方案 : # 更新php curl 版本\n會發現更新版本後就能解決也是意外，因為已經卡了好幾天沒有解法了才想說不然試試看新的Laravel 9 + PHP 8.1.4環境試試看\n沒想到直接過關，觀察curl版本發現，php-fpm 8.1.4版的docker image安裝的是curl 7.74.0\n而我原本在pfp-fpm 7.4.16版的docker image中安裝的是7.64.0，其實也才差0.1版本\n安裝新版curl : # 本次安裝的環境是重新build php-fpm 7.4.16 image，加入以下\n#curl update to 7.74 version RUN apt-get purge --auto-remove -y curl libcurl3 \\ \u0026amp;\u0026amp; wget https://curl.haxx.se/download/curl-7.74.0.tar.gz \\ \u0026amp;\u0026amp; tar -xzf curl-7.74.0.tar.gz \\ \u0026amp;\u0026amp; cd curl-7.74.0 \\ \u0026amp;\u0026amp; ./configure --with-ssl --prefix=/usr \\ \u0026amp;\u0026amp; make \u0026amp;\u0026amp; make install \\ \u0026amp;\u0026amp; ldconfig \\ \u0026amp;\u0026amp; cd .. \u0026amp;\u0026amp; rm -rf curl-7.74.0 \\ \u0026amp;\u0026amp; rm -rf /var/lib/apt/lists/* 重新build完成就可以了\n","date":"2022年3月29日","externalUrl":null,"permalink":"/2022/03/29/php-curl-post-https-recv-failed-104-connection-reset-by-peer-%E9%8C%AF%E8%AA%A4%E8%99%95%E7%90%86/","section":"Posts","summary":"","title":"PHP cURL post HTTPS recv() failed (104: Connection reset by peer) 錯誤處理","type":"posts"},{"content":"","date":"2022年3月29日","externalUrl":null,"permalink":"/tags/php-fpm/","section":"Tags","summary":"","title":"Php-Fpm","type":"tags"},{"content":"智慧家庭第三步 Home Assistant 接入小米系統\n如果你還沒有安裝好Home Assistant 請先閱讀\n智慧家庭第一步 在Synology上架設Home Assistant\n如果你還沒在Home Assistant上安裝HACS 請先閱讀\n智慧家庭第二步 Home Assistant安裝 HACS\n如果以上兩個步驟都已經完成，那麼我們就往下繼續看吧\n本次接入的小米系統會先以\u0026quot;小米PM2.5偵測器\u0026quot;為範例\nStep1 HACS安裝小米IoT套件\n首先，我們到HACS中的整合(Integrations)\n點選右下角按鈕，來搜尋套件\n在畫面中輸入\u0026quot;Xiaomi\u0026quot; 來搜尋所有小米的相關套件，這次要使用的套件是\u0026quot;Xiaomi MioT\u0026quot;\n點選進入之後，可以看到關於套件的說明內容，接著點選右下角的download來安裝\n下一步，畫面上會讓你選擇套件的版本，通常不用改就選擇預設的就行，按下\u0026quot;downloadf\u0026quot;\nStep2 設定Xiaomi MioT\n接著到 設定頁籤，點選右下角的\u0026quot;新增整合\u0026quot;找到剛剛加入的Xiaomi MioT\n接著，依照畫面提示可以選擇用小米帳號來接入裝置，也可以用裝置IP以及token來接入，這邊選的比較間單的方式\n登入小米帳號就行\n輸入已經綁定裝置的小米帳號、密碼以及選擇地區，目前地區還不支援台灣，如果要使用綁定在台灣地區的裝置\n可能暫時只能用IP以及token來接入了\n登入小米帳號之後，就會看到目前這個帳號底下綁定的裝置，這次就選擇空氣探測儀\n接入裝置後，接下來再次到設定頁籤，選擇\u0026quot;實體\u0026quot;\n在系統的實體列表中，就會看到好幾個空氣探測儀的數值了，畫面中我們需要的數值就是空氣探測儀的PM2.5數值\n這邊也可以讀取到小米空氣探測儀的電池數據\n總結 :\n有了Xioami MioT套件後，目前接入小米的裝置簡單非常的多\n雖然目前不支援台灣地區的裝置，我相信接下來肯定會支援的\n找到了PM2.5數值資料之後，接下來就可以依照情應應用啦\n","date":"2022年1月19日","externalUrl":null,"permalink":"/2022/01/19/%E6%99%BA%E6%85%A7%E5%AE%B6%E5%BA%AD%E7%AC%AC%E4%B8%89%E6%AD%A5-home-assistant-%E6%8E%A5%E5%85%A5%E5%B0%8F%E7%B1%B3%E7%B3%BB%E7%B5%B1-xiaomi-miot/","section":"Posts","summary":"","title":"智慧家庭第三步 Home Assistant 接入小米系統 Xiaomi MioT","type":"posts"},{"content":"從Intel的Mac轉換到Appple 自家M1 Pro晶片在使用docker開發的狀況底下會採哪些坑呢？\n今天想要紀錄一下兩個點\nMySQL 5.X版的沒有ARM版本 2.docker-compose 請使用V2版本\n第一點 : # 關於第一點其實很簡單，在docker-compose.yml中加入\nplatform: linux/amd64 讓docker去抓image的時候記得抓amd64的版本，不然會報錯\n所以我的MySQL5.7在docker-compose.yml中就會長的像這樣子\nmysql57: container_name: mysql57 image: mysql:5.7 platform: linux/amd64 restart: unless-stopped environment: - MYSQL_ROOT_PASSWORD=${MYSQL_ROOT_PASSWORD} ports: - 3306:3306 volumes: - mysql57-data:/var/lib/mysql networks: - dev 第二點 : # 不曉得為什麼Mac版的docker desktop中 User Docker Compose V2的選項突然被關掉了，使得docker-compose指令一直報錯\ndocker-compose-v1: bad CPU type in executable 網路上目前沒有找到相關討論，還好有看到docker desktop中有這個選項，打勾後重啟docker就可以正常使用了\n","date":"2022年1月14日","externalUrl":null,"permalink":"/2022/01/14/macbook-pro-14-m1-pro%E9%96%8B%E7%99%BC%E8%B8%A9%E5%9D%91-docker/","section":"Posts","summary":"","title":"Macbook Pro 14 M1 Pro開發踩坑 docker","type":"posts"},{"content":"","date":"2021年11月25日","externalUrl":null,"permalink":"/tags/%E6%99%BA%E6%85%A7%E5%AE%B6%E5%BA%AD/","section":"Tags","summary":"","title":"智慧家庭","type":"tags"},{"content":"如果你還沒有安裝好Home Assistant 請先閱讀\n智慧家庭第一步 在Synology上架設Home Assistant\n我們在上一個步驟安裝完成Home Assistant後，已經有Supervisor的權限了，有了supervisor就已經可以安裝一些附加元件來使用\n接下來要介紹的HACS全名是Home Assistant Community Store，這裡面包含了更多各式各樣開放原始碼的套件可以讓我們更簡單設定好Home Assistant\n首先，開始之前必須先說明\n安裝HACS必須要擁有GitHub帳號，什麼是GitHub? 他是原始碼的管理工具\n不懂也沒關係，到GitHub官網上免費註冊一個帳號即可，註冊好之後請先在瀏覽器登入GitHub待會兒會用上\n步驟一 : 先裝個samba share套件 # 這個步驟是為了讓我們方便把HACS的檔案丟進Home Asssistant虛擬機中，首先在Supervisor頁面中選擇附加元件商店，找到Samba share套件\n點開之後選擇\u0026quot;安裝\u0026quot;\n等待安裝完成後，先不啟用，先點選\u0026quot;設定\u0026quot;頁籤，在畫面上填入你要來登入samaba的帳號密碼\nusername:\npassword:\n輸入儲存完成後回到\u0026quot;資訊\u0026quot;頁籤，按下啟動\n步驟二 : 下載HACS檔案 # 先到HACS的GitHub網站 https://hacs.xyz/docs/setup/prerequisites\n下載最新版本的zip壓縮檔\n下載完成之後，如果你是MAC電腦可以透過連接伺服器，輸入smb://Home Assistant的IP\n如果是windows電腦，則可以在\u0026quot;檔案總管中\u0026quot;，網址列輸入\\Home Assistant的IP\n連接到之後，進入config資料夾\n在config這個資料夾中建立\u0026quot;custom_components\u0026quot;資料夾，如果已經存在則可以跳過此步驟\n接著將剛剛下載hacs.zip解壓所開來之後放入custom_components這個資料夾內\n步驟三 : 重啟Home Assistant伺服器 # 不用重開虛擬機或者NAS，只要在\u0026quot;設定\u0026quot;-\u0026gt;\u0026ldquo;伺服器控制\u0026quot;點選即可\n點選進入畫面之後，選擇\u0026quot;重啟\u0026quot;按鈕\n系統會跳出警告視窗，按下\u0026quot;是\u0026quot;Home Assistant即將重新啟動\n步驟四 : 設定HACS整合 # 點選\u0026quot;設定\u0026rdquo; \u0026ndash;\u0026gt; \u0026ldquo;整合\u0026rdquo;\n選擇右下角\u0026quot;新增整合\u0026quot;\n搜尋HACS，應該可以看到結果\n點選進入之後，選擇\u0026quot;傳送\u0026quot;\n這時候畫面上會跳出來一個URL連結以及一組數字，請點選該連結會打開之前註冊好的GitHub網站授權畫面\n打開GitHub畫面之後會出現需要填入數字的畫面，這時候把HACS裡面看到的數字填入即可\n如果輸入正確，Github將授權你的HACS可以存取資料，按下綠色的\u0026quot;Authorize hacs\u0026quot;按鈕\n順利完成後可以看到這樣的畫面\n接著回到Home Assistant畫面上，按下\u0026quot;完成\u0026quot;即可\n這時候就可以在左邊選單看到HACS了，等待幾秒鐘HACS啟用完畢就可以開始使用\n最後，如果你看到這個錯誤訊息的畫面也不用緊張，因為GItHub我們申請的是免費帳號，免費帳號有一定的存取限制，通常超過限制之後會被限制存取一段時間，等一個小時後再來就可以了\n以上，希望對於有點小複雜的HACS安裝初學者有些幫助\n","date":"2021年11月25日","externalUrl":null,"permalink":"/2021/11/25/%E6%99%BA%E6%85%A7%E5%AE%B6%E5%BA%AD%E7%AC%AC%E4%BA%8C%E9%83%A8-home-assistant%E5%AE%89%E8%A3%9D-hacs/","section":"Posts","summary":"","title":"智慧家庭第二步 Home Assistant安裝 HACS","type":"posts"},{"content":"往往說起智慧家庭，很多人第一個想到的就是Apple的Home kit與Siri或者Google Home\n而今天要介紹的Home Assistant是我認為更適合所有人使用的一套智慧家庭中樞，因為它可以是Home kit的中介把其他的智慧家庭設備接入home kit中。\nHome Assistant 中可以串接各式各樣，各種不同廠牌的設備，比起Home kit或者google home來說，彈性大上許多，不必被限制在同一個廠牌或者同一個平之中，但是也因為這樣子的彈性使得在建置過程以及設備接入上會比其他平台還要來得複雜些，接下來一系列文章會記錄下關於我的智慧家庭如何一步一步打造出來。\n安裝Home Assistant中樞 # 如果你手邊有會一直開機的電腦，或者像我一樣有一台不會關機的NAS的話，就可以不必再多買一台樹莓派來安裝，雖然官方最推薦的方式是透過樹莓派沒錯，只是呢手邊現有的硬體拿來使用也是不錯的解決方案\n今天要講的方式是透過Synology NAS上的虛擬機功能將官方提供的虛擬機器直接部署在NAS上，然而一定有人會說為什麼不直接用Docker跑就好了? 沒錯docker可以跑但是在功能上，docker少了很多擴充性比如說附加元件商店以及HACS這些功能，少了這些Home Assistant可以說是被砍了一半一上的功能，所以還是推薦用虛擬機的方式安裝來使用所有的功能。\n安裝頁面: https://www.home-assistant.io/installation/\n安裝頁面上可以依據自己不同的平台來選擇不同的安裝方式，本次要安裝在Synology NAS上，可以直接拉到最後一個選項\n進入畫面之後可以看到，官方提供多種VM映像檔可以讓我們下載使用，在Synology NAS上請選擇最後一個.ova檔案\n等待檔案下載完成之後，請連接到你的NAS上，並找到Virtual Machine Manager(VMM)，如果沒有請先到附加元件商店裡安裝\n打開之後，選擇左手邊選單的虛擬機器，並且點選新增\u0026ndash;\u0026gt;匯入\n選擇由OVA檔案匯入，也就是我們剛剛從Home Assistant官網上下載下來的\n選擇從電腦上傳，並且透過瀏覽按鈕找到剛剛下載的Home Assistant OVA檔案\n接下來是一系列VM機器的設定值，基本上都走預設就可以了\n接下來這一步請特別注意，開機韌體的地方要選擇UEFI\n權限的部分，請記得把Admin以及你登入使用的帳號打勾即可\n最後依照指示開始匯入資料後就會看到你的虛擬機器列表中，多了一台Home Asssistant顯示\u0026quot;已關機\u0026quot;，那麼我們就點選他，然後按一下\u0026quot;開機\u0026quot;\n等待幾秒鐘之後，機器狀態會顯示\u0026quot;執行中\u0026quot;，並且在IP欄位已經顯示出來就代表機器已經成功啟動了\n設定IP位置 # 接著，先不要急著打開Home Assistant，因為IP目前是走DHCP的動態虛擬IP，對於未來管理會比較不方便，因此我們需要到網路分享器內，找到homeassistant這台裝置，把他的IP設定為固定比較好，我自己是使用Unifi的系統，所以可以在裝置列表中找到並且設定，這邊就要依照狀況自行設定了\n這邊舉例，我將IP設定為192.168.1.103，設定成固定並且儲存\n這時候，回到Synology NAS的VM管理畫面上，把剛剛Home Assistant這台機器按下\u0026quot;關機\u0026quot;，等機器關閉後再重新\u0026quot;開機\u0026quot;，就會看到IP位置已經改變，變成我們剛剛設定的固定IP 192.168.1.103了\n設定登入帳號密碼 # 接著打開瀏覽器，輸入Home Assistant的IP:8123，就可以看到建立帳號的頁面，第一次進入系統都會要求輸入帳號以及密碼等資訊\n第一次創建帳號需要一點時間，可能幾秒鐘也可能幾分鐘，要看NAS機器的等級規格不一定，等畫面全部跑完就可以看到系統最初的畫面了，也就是代表已經安裝完畢，左邊選單的部分可以看到有Supervisor選項，這邊可以安裝更多Home Assistant相關的其他套件來使用，如果是使用docker跑的話，就沒有這個選單了喔\n好的，安裝的部分就在這邊告一個段落，接下來的文章會帶大家安裝幾個常用的套件以及我目前使用的設備導入喔，敬請期待。\n","date":"2021年11月20日","externalUrl":null,"permalink":"/2021/11/20/%E6%99%BA%E6%85%A7%E5%AE%B6%E5%BA%AD%E7%AC%AC%E4%B8%80%E6%AD%A5-%E5%9C%A8synology%E4%B8%8A%E6%9E%B6%E8%A8%ADhome-assistant/","section":"Posts","summary":"","title":"智慧家庭第一步 在Synology上架設Home Assistant","type":"posts"},{"content":"","date":"2021年10月25日","externalUrl":null,"permalink":"/tags/mysql/","section":"Tags","summary":"","title":"Mysql","type":"tags"},{"content":"之前已經有一篇文章，教大家怎麼自動化備份MySQL到遠端機器上了\nMySQL備份 MySQL 定期自動備份到遠端機器\n之前那篇說的是，將mysql dump出來之後直接以SSH的方式將檔案傳輸到遠端機器上儲存\n我自己的用法是把伺服器上的MySQL資料dump回家裡的NAS儲存，這樣就必須把NAS的SSH打開，並且對外port也必須要打開\n有鑒於前陣子QNAP中了很多勒索病毒，考量安全性之下我把對外port全數關閉只能VPN回到NAS上之後才能夠存取資料\n這樣一來便造成外部伺服器無法將備份檔案傳回NAS上\n今天要講的方法是透過NAS上的Docker建立SFTP伺服器，將對外連線開給docker上的SFTP，伺服器端一樣透過連接到SFTP伺服器將檔案傳輸回NAS上\n這樣子做好處是不需要將對外port直接開給NAS，就算真的中了病毒範圍也只限縮在docker這台虛擬機中，相對還是安全許多\n接著我們就來進行安裝吧\n本次進行的環境將在Synology DSM中進行\nSTEP1 : 安裝sftp server on docker # 這次使用的image是 atmoz/sftp\n直接在倉庫伺服器中就可以找到他\n點選下載，完成後就會在映像檔的頁面上看到，選擇image後點選\u0026quot;部署\u0026quot;\n開始部署先選擇進階設定\n1.儲存空間的頁籤需要設定一個NAS上的資料夾給docker做檔案儲存，這樣才能將docker中的檔案存回NAS中、\n掛載路徑指的是docker中的資料夾路徑，根據作者的文件需要掛到\n/home/使用者名稱/自定一個資料夾名稱，這樣子\n網路的部分可以不用特別設定，勾選與docker host使用相同網路即可\n最後一個環境設定，這裡根據作者文件，必須要帶入command\n因此在命令的地方輸入 使用者:密碼:uid(任意指定即可)\n按下套用後，等待設定完成即可\n這時候sftp已經架設好在nas_ip的22port\n接著，必須要到分享器來設定對外port的開放才能夠讓外部連線進來，在port的地方任意輸入埠號，對應到192.168.1.100的22 port\n192.168.1.100是我的NAS IP\n這樣設定後，外部就可以使用IP:XXXX來連線到docker上的sftp了\nSTEP2 : 伺服器端安裝lfpt # lftp是一個可以連線到sftp伺服器的client，當然也可以不安裝，直接用sftp指令來傳檔案，只是我覺得這樣子比較方便使用而已\n安裝方式很簡單\nsudo apt-get update -y sudo apt-get install -y lftp 安裝完成後，就可以來設定最後一步了\nSTEP 3 : 設定自動備份檔案 # 只要用改上次用來自動備份MySQL的sh批次執行檔修改幾行就可以了\n#!/bin/sh now=\u0026#34;$(date +\u0026#39;%Y_%m_%d_%H_%M_%S\u0026#39;)\u0026#34; FILENAME=\u0026#34;XXXXX_$now.sql.gz\u0026#34; backup_path=\u0026#34;/PATH_TO_DIR_FOR_DUMP_MYSQL\u0026#34; cd $backup_path mysqldump -u root -pMYSQLPASS --routines MYSQL_DB_NAMEE | gzip -c \u0026gt; $FILENAME lftp -e \u0026#34;set sftp:auto-confirm yes\u0026#34; -u SFTP_USER,SFTP_PASS -p PORT sftp://SFTP_IP_ADDRESS \u0026lt;\u0026lt; EOF cd DIR put $backup_path/$FILENAME EOF rm -f $backup_path/$FILENAME 逐行說明:\nFILENAME 定義了dump出來的備份檔檔案名稱，我這邊使用XXXX_時間來作為檔案的命名\nbackup_path這邊是dump出來的備份檔所載路徑\n接著切換到dump檔需要存在的路徑後執行mysqldump，並且壓縮成zip檔，儲存檔名就是FILENAME定義的\n下一個步驟是透過lftp連線到sftp\n第一次連線你可以會收到這樣的錯誤訊息\nbash Fatal error: Host key verification failed 可以參考這篇，手動連線一次之後應該就可解決\nFTP上的操作就是切換到要儲存的資料夾路徑，並且講檔案放入\n結束之後記得將檔案rm，移除掉伺服器上的備份檔，才不會早成容量越來越大\n如果執行\nbash XXX.sh 可以順利完成那就代表沒有問題了，應該可以在sftp伺服器上看到備份的檔案，最後一個步驟\n設定讓他可以自動執行，我們一樣透過crontab -e\n0 16 * * * bash /PATH_TO_THE_XXX.sh 我這邊這樣設定的話就是讓系統自動在每天的00:16 執行XXX.sh\n這樣的話就可以達成每天自動備份了\n","date":"2021年10月25日","externalUrl":null,"permalink":"/2021/10/25/mysql%E5%82%99%E4%BB%BD-mysql-%E5%AE%9A%E6%9C%9F%E8%87%AA%E5%8B%95%E5%82%99%E4%BB%BD%E5%88%B0%E9%81%A0%E7%AB%AF%E6%A9%9F%E5%99%A8sftp/","section":"Posts","summary":"","title":"MySQL備份 MySQL 定期自動備份到遠端機器(SFTP)","type":"posts"},{"content":"","date":"2021年10月25日","externalUrl":null,"permalink":"/categories/%E6%9E%B6%E7%AB%99/","section":"Categories","summary":"","title":"架站","type":"categories"},{"content":"","date":"2021年8月6日","externalUrl":null,"permalink":"/tags/gcp/","section":"Tags","summary":"","title":"Gcp","type":"tags"},{"content":"","date":"2021年8月6日","externalUrl":null,"permalink":"/categories/gcp/","section":"Categories","summary":"","title":"GCP","type":"categories"},{"content":"還記得之前介紹過的pptraas吧？\nhttp://www.alvinchen.club/2019/10/16/html-to-pdf-%e8%bc%b8%e5%87%ba%e6%9c%80%e6%bc%82%e4%ba%ae%e7%9a%84%e5%ae%8c%e6%95%b4pdf/\n細節就不再多做說明，因為是採用跟上次相同的概念，透過puppeteer來實作，這次提供一個更好的docker image來使用以及透過GCP Cloud run 的服務來達成無伺服器產出的目標\n參考文件\nhttps://hub.docker.com/r/zenato/puppeteer-renderer\nhttps://github.com/puppeteer/puppeteer/blob/v1.1.0/docs/api.md#pagepdfoptions\nhttps://ithelp.ithome.com.tw/articles/10244413\nhttps://peihsinsu.gitbooks.io/docker-note-book/content/gcp-free-registry-use.html\n首先，來講一下為什麼這次改用 zenato/puppeteer-renderer 這個docker image\n之前使用的 minuux/pptraas.com 其實也能做一模一樣的事情，只是他有幾個問題讓我這次決定做了更換\n內建的Chrome版本太舊了\n不曉得為什麼開發機的Nuxt Server他怎麼樣都連不上，別台伺服器同樣架構同樣在docker底下卻可以\n直接輸出PDF會有很多CSS讀取不到，需要先截圖後再排列圖片輸出PDF\n本次更換為 zenato/puppeteer-renderer 後，可以直接用網址輸出PDF真的方便很多，而且依照作者提供的文件還可以套用puppeteer更多的參數實在方便！\n如何使用 # 依照文件，只要將docker image跑起來即可\ndocker run -d --name renderer -p 9999:3000 zenato/puppeteer-renderer 其中，9999 port可以自定義就好，我只是習慣放9999\n接下來只要在瀏覽器輸入以下網址，或者透過其他程式語言經由get都可以直接產出檔案\nhttp://localhost:9999/?type=pdf\u0026amp;url=THE_URL_TO_PDF 這樣就可以了，這邊說明一下\ntype參數可以有兩個，PDF會直接匯出PDF檔案，而screenshot則是會匯出圖片檔\n後面呢還可以接上不同的參數來達到擷取不同大小以及範圍的作法，詳細可以參考puppeteer document\n進階做法 # 之前一直有想要嘗試GCP的Cloud Run服務，但苦於沒有機會，這次總算抓緊時間實驗了一番，確實可行！！！\nGCP Cloud Run服務是一種無伺服器的服務，不必自己管理伺服器，只要想需要跑的image上傳到GCR上並且架設好Cloud Run\nCloud Run會提供一組網址，有需求時直接對網址做Request就行了\n而Cloud Run本身平常並不會運行，處於待命或關機的狀態，只有在收到Request的同時才會將docker image跑起來並產出結果，只要一段時間沒有Request又會再次關閉\n這樣的好處是，不運行的時候GCP也不會收費，因此這部分的成本支出是想對便宜的\n但是缺點就是第一次的Request會因為要叫起docker image，所以反應時間會比較長一些\n步驟 # 第一個步驟，是需要將 zenato/puppeteer-renderer image push到GCR上，根據GCP上的文件，我們必須安裝gcloud SDK後才能透過指令來處理\n這邊就懶惰一點，直接使用gcp提供的cloud shell來處理docker image，至於cloud shell是什麼樣的概念呢? 可以直接參考說明文件\n進入到GCP之後可以直接點選右上角的終端機符號來打開cloud shell\n接著，你就可以在畫面下方看到黑色的終端機畫面了，這時候你可以直接把這台機器當成一台在gcp上的免費VM來使用，而且是在已經安裝好gcloud SDK的情況下\n接下來就透過幾個指令來處理docker image吧\n1.下載\ndocker pull zenato/puppeteer-renderer 2.增加gcr tag\ndocker tag zenato/puppeteer-renderer:latest asia.gcr.io/PROJECT_ID/puppeteer-renderer:latest 就是將原本的image加上asis.gcp.io/PROJECT_ID，並且標注latest，當然你要用不同的版本號也是可以的\nPROJECT_ID就是你在GCP上的專案的ID，就自行調整\n3.上傳\ndocker push asia.gcr.io/PROJECT_ID/puppeteer-renderer:latest 這樣就已經把image上傳到GCR上了，接下來就可以在Cloud Run來處理其 進入到Cloud Run的業面之後，按下建立服務按鈕，開始填寫資料\n服務名稱以及區域可以自訂，這次我就填寫與image相同的名稱，地區選擇台灣，按下一步\n接下來要選擇需要拿來執行的image，也就是上個步驟上傳到GCR的那個image，點選\u0026quot;選取\u0026quot;開啟介面選則 image\n畫面中就可以看到剛剛上傳的image了，選取他\n接著再進階設定中不需要修改任合設定值，當然可以依照自己需求來調整資源以及通訊埠設定\n最後一步，先偷懶跳過驗證，選擇\u0026quot;允許未經驗證的叫用要求\u0026quot;，接著按下建立\n最後，等他跑完就可以看到畫面上打綠色的勾勾，確定image已經跑起來了\n這時候就可以透過上面的網址來存取你要的DPF檔了喔，搭配第一部分提供的參數接上type以及URL即可\n要注意的是，第一部分我們有設定9999 port，在這邊是不需要的喔，只要將參數接再網址後面即可\n舉例:\nhttps://puppeteer-renderer-dngjjjdsss.a.run.app/?type=pdf\u0026amp;url=http://www.alvinchen.club 484超級方便的，以上經驗提供給大家參考\n","date":"2021年8月6日","externalUrl":null,"permalink":"/2021/08/06/html-to-pdf-%E8%BC%B8%E5%87%BA%E6%9B%B4%E6%BC%82%E4%BA%AE%E6%9B%B4%E5%AE%8C%E6%95%B4%E7%9A%84%E5%AE%8C%E6%95%B4pdf/","section":"Posts","summary":"","title":"html to pdf 輸出更漂亮更完整的完整PDF","type":"posts"},{"content":"首先 : 會發生SSH無法連線的狀況很多，這個方法不一定適用所有的狀況\n可以先到GCP VM中打開\u0026quot;啟用序列埠連線\u0026quot;，就可以看到VM中目前的錯誤狀況為何\n參考文件\nhttps://www.cloudbooklet.com/attach-and-mount-disks-to-vm-instance-in-google-cloud/\nhttps://discuss.devopscube.com/t/solved-wrong-fs-type-bad-option-bad-superblock-on-dev-sdb-error/206\nhttp://it.givemepower.net/linux-command-du-view-files-and-directories-usage-space/\nhttps://askubuntu.com/questions/859787/how-to-unmount-and-mount-pen-drive-in-ubuntu-via-command-line\n確認錯誤狀況 # 從控制介面中點入無法SSH登入的主機，選擇右上角編輯\n在畫面中打勾\u0026quot;啟用序列埠連線\u0026quot;\n按下儲存後，回到VM列表，點選\u0026quot;重置\u0026quot;讓機器重新啟動一次，接著再點選進入VM中\u0026quot;連線至序列控制台\u0026quot;就可以開啟畫面，看到目前VM上的錯誤訊息了\n如果你跟我一樣看到 \u0026ldquo;no space left on device\u0026rdquo;，那麼就是跟我一樣的狀況了，boot device已經沒有空間\n處理邏輯 # 1.將這台VM的boot device退出\n2.開一台新的VM(或其他可用VM都行)，把這顆硬碟掛入其他其他硬碟中\n3.進入這台新的VM將硬碟磁區掛入Linux目錄\n4.這時候就可以開始整理就硬碟了，整理好之後將硬碟退出\n5.將舊硬碟退出新的VM，再掛回原VM的開機硬碟中\n6.順利開機\n首先，要注意的是，開一台新的VM必須要跟原本無法登入的那台VM在同一個地區，這樣才有辦法掛他的硬碟\n將boot device退出 # 進入VM後點選\u0026quot;編輯\u0026quot;，找到\u0026quot;boot disk\u0026quot;，按下右邊的X移除掉\n最後按下儲存就可以了，放心這個最後處理完成後可以再將這顆硬碟綁回來\n綁定到新VM # 接著，開一台新的VM(或既有的VM都可以)，一樣點進去\u0026quot;編輯\u0026quot;\n找到\u0026quot;其他磁碟\u0026quot;這個區域，按下\u0026quot;連接現有磁碟\u0026quot;，然後在畫面下拉選單中，找到剛剛被你按下X移除掉的硬碟\n如果在這個畫面沒有看到那顆硬碟，那就有可能VM在不通區域看不到，或者原本的硬碟還沒有解除綁定\n可以到左選單中的\u0026quot;儲存空間\u0026quot; \u0026ndash;\u0026gt; \u0026ldquo;磁碟\u0026rdquo;，看一下這顆硬碟目前是不是在使用者這個欄位不是空的\n硬碟Mount到OS中\n這個步驟要注意的是，不必綁定整個硬碟，只要綁定已經沒有空間可以使用的那個資料夾即可\n可以用以下指令來看目前的磁碟綁定狀態\nlsblk 假設sda1 是已經要滿的那個資料夾，那我們就只要綁定這個就可以了(實際上掛上去的硬碟應該會是sdbXX之類的)\n接著，我們把sda1這個資料夾掛載到/mnt/new_disk 這個資料夾中，就可存取到了\nsudo mkdir /mnt/new_disk sudo mount /dev/sda1 /mnt/New_disk 這樣子就完成了\n清理資料 # 這時候就可以進入到/mnt/new_disk這個目錄就可以看到原硬碟的資料了\n掛回原VM中開機 # mount完之後，接著要umount\nsudo umount -l /mnt/new_disk 然後一樣回到GCP的控制畫面中，對新的VM按下編輯，找到剛剛掛上的硬碟在他旁邊按下X後儲存即可\n儲存完畢後回到原本不能登入的VM中，一樣按下編輯找到\u0026quot;boot disk\u0026quot;，按下\u0026quot;+\u0026ldquo;符號，從下拉選單中就可以看到原本的硬碟了\n選擇他，按下儲存後開機即可\n接著，應該就可以正常使用了\n開機後，如果你想要擴增硬碟空間，可以直接參考這篇的做法\nhttps://neostation.net/gcp-vm-add-space/\n","date":"2021年7月27日","externalUrl":null,"permalink":"/2021/07/27/gcp-vm-%E7%84%A1%E6%B3%95ssh%E9%80%A3%E7%B7%9A-no-space-left-on-device/","section":"Posts","summary":"","title":"GCP VM 無法SSH連線: no space left on device","type":"posts"},{"content":"使用vue-month-picker in Nuxt專案\n參考文件\nhttps://vuejsexamples.com/a-simple-and-elegant-month-picker-for-vue/\nhttps://nuxtjs.org/docs/2.x/directory-structure/plugins\nhttps://github.com/anteriovieira/vue-youtube/issues/33\n以上三個參考文件分別是vue-month-picker安裝\n以及分別在nuxt會遇到的兩個問題解決方案\n首先，將vue plugin 新增到nuxt\n依照官網文件，我們在plugins資料夾新增一個vue-month-picker.js\nimport Vue from \u0026#39;vue\u0026#39; import MonthPicker from \u0026#39;vue-month-picker\u0026#39; import MonthPickerInput from \u0026#39;vue-month-picker\u0026#39; Vue.use(MonthPicker) Vue.use(MonthPickerInput) 接著在nuxt.config.js中，plugins中加入\n\u0026#34;~plugins/vue-month-picker.js\u0026#34; 但是，這時候會出現錯誤訊息\nnuxt Cannot use import statement outside a module 依照官網上的錯誤資訊解決方法，我們在nuxt.config.js中，build中加入\ntranspile: [\u0026#39;vue-month-picker\u0026#39;] 接著會遇到另一個錯誤訊息\nVue.component is not a function 依照github上的文章，我們在page頁面中import稍做修改\n將\nimport MonthPickerInput from \u0026#39;vue-month-picker\u0026#39; 修改為\nimport {MonthPickerInput} from \u0026#39;vue-month-picker\u0026#39; 最後在需要顯示month picker的地方加入以下component\n\u0026lt;month-picker-input\u0026gt;\u0026lt;/month-picker-input\u0026gt; 總結以上，page頁面大約會長這個樣子\n\u0026lt;template\u0026gt; \u0026lt;div\u0026gt; \u0026lt;month-picker-input\u0026gt;\u0026lt;/month-picker-input\u0026gt; \u0026lt;/div\u0026gt; \u0026lt;/template\u0026gt; \u0026lt;script\u0026gt; import {MonthPickerInput} from \u0026#39;vue-month-picker\u0026#39; export default { data(){ return { } }, components: { MonthPickerInput } } \u0026lt;/script\u0026gt; ","date":"2021年4月27日","externalUrl":null,"permalink":"/2021/04/27/vue-month-picker-in-nuxt/","section":"Posts","summary":"","title":"vue-month-picker in Nuxt","type":"posts"},{"content":"首先，在舊版本的Docker engine中，Nuxt.js直接執行npm run dev是可以hot code reload的\n也就是程式碼修改後會直接開到console畫面正在重新build，畫面上也就會相對的修改了\n但是不曉得哪個版本後npm run dev再也無法hot code reload了\n目前在Nuxt.js官網下找到的解決方案如下\n參考網址 :\nhttps://nuxtjs.org/docs/2.x/configuration-glossary/configuration-watchers\nhttps://webpack.js.org/configuration/watch/#watchoptions\nhttps://github.com/nuxt/nuxt.js/issues/2481\n根據以上解決方案，在nuxt.config.js中加入以下即可\nwatchers: { webpack: { aggregateTimeout: 300, poll: 1000 } }, 其中，\naggregateTimeout代表第一次watch的delay時間，單位是millisecond\npoll代表watch的間隔時間，單位也是millisecond\n上面的例子就是當npm run dev建置完成後，300毫秒後會開始持續watch程式碼是否改變\n而每次的間隔是1000毫秒\n","date":"2021年4月9日","externalUrl":null,"permalink":"/2021/04/09/%E8%A7%A3%E6%B1%BAnuxt-js%E5%9C%A8docker%E4%B8%ADhot-code-reload%E6%B2%92%E5%8F%8D%E6%87%89%E7%9A%84%E5%95%8F%E9%A1%8C/","section":"Posts","summary":"","title":"解決Nuxt.js在Docker中Hot code reload沒反應的問題","type":"posts"},{"content":" 為你的nuxt專案掛上https吧 # 如果你不知道怎麼建立nuxt專案請參考\nhttp://www.alvinchen.club/2020/07/17/laravel-nuxt%e5%bf%ab%e9%80%9f%e4%b8%8a%e6%89%8b/\n環境說明: # 本次執行的環境全部在docker底下，專案包含laravel開發的API、mysql server，以及nginx server、前端nuxt專案\n全部都在docker環境底下執行\n邏輯概念 : # 因為nuxt專案要掛上https依照官網的做法有點麻煩，一直沒有成功\n詳細資料可以參考:\nhttps://nuxtjs.org/docs/2.x/configuration-glossary/configuration-server\nhttps://ithelp.ithome.com.tw/articles/10230052\n後來找到比較退而求其次的做法，就是透過nginx做reverse proxy\n讓nginx來處理SSL，然後再把資料轉傳至nuxt build好的3000 port上\n資料準備 : # 先進入docker nginx 來產出csr檔案，可以參考這邊怎麼做\nhttps://www.liquidweb.com/kb/generating-certificate-signing-request-csr-ubuntu-16-04/\n關於SSL憑證如何產生就不再多說明，這邊舉例透過SSL for Free取得的憑證是ca_bundle.crt以及certificate.crt\n如何把這兩個放在ngix上也可以參考官方文件，基本上照做即可\nhttps://help.zerossl.com/hc/en-us/articles/360015620100-Installing-SSL-Certificate-on-NGINX\nnginx config :\nserver { listen 443 ssl default_server; server_name MY_DOMAIN_NAME; # 憑證與金鑰的路徑 ssl on; ssl_certificate /etc/ssl/certificate-ssl.crt; #這個是教學中cat合併起來的檔案 ssl_certificate_key /etc/ssl/private.key; location / { proxy_pass http://MY_DOMAIN_NAME:3000; # 伺服器程式位址與連接埠 proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection \u0026#39;upgrade\u0026#39;; proxy_set_header Host $http_host; proxy_set_header X-NginX-Proxy true; } } 原則上不難懂，就是443 port由nginx來處理SSL，接著proxy_pass到nuxt已經build好的3000 port上\n就完成了，才怪～\n特別要注意的是，nuxt掛上了https之後，如果你的資料來源API URL不是https，那麼nuxt在切換頁面的時候\n就會被瀏覽器擋下來，因此最後記得把資料API的URL也一起掛上https吧\n如果你跟我一樣，都是在docker環境下開發，那麼剛剛產生的憑證就直接拿來放在資料api的nginx伺服器吧\n當然，前提是nuxt reverse proxy跟資料api伺服器是同一台nginx\n同場加映 : # https架設起來後，其實原本的http還是可以連線的，那要怎麼把連到http的用戶全部轉到https呢?\nnginx再加一個設定即可\nserver { listen 80 default_server; server_name MY_DOMAIN_NAME; return 301 https://MY_DOMAIN_NAME$request_uri; } 用return 301轉到htpps即可\n","date":"2020年11月5日","externalUrl":null,"permalink":"/2020/11/05/%E7%82%BA%E4%BD%A0%E7%9A%84nuxt%E5%B0%88%E6%A1%88%E6%8E%9B%E4%B8%8Ahttps%E5%90%A7/","section":"Posts","summary":"","title":"為你的nuxt專案掛上https吧","type":"posts"},{"content":" docker環境定時啟動selenium爬蟲抓取資料 # 今天要說明的是在環境沒有安裝python selenium的情況底下，如何透過定時啟動docker的方式來啟動爬蟲抓取資料\n環境準備 : # 環境的部分，一定要安裝docker，還不知道怎麼安裝可以參考\nMAC安裝Docker\n當然，一般來說伺服器的環境是在Linux底下，也可以參考\nhttps://phoenixnap.com/kb/how-to-install-docker-on-ubuntu-18-04\n接著，我們在python 2.7的docker環境下安裝所需要的套件，Dockerfile如下，\n也可以直接從我的docker hub上拉下來\nhttps://hub.docker.com/r/virtualorz/python_selenium\nFROM python:2.7 # install google chrome RUN wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add - RUN sh -c \u0026#39;echo \u0026#34;deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main\u0026#34; \u0026gt;\u0026gt; /etc/apt/sources.list.d/google-chrome.list\u0026#39; RUN apt-get -y update RUN apt-get install -y google-chrome-stable # install Chromium RUN apt-get update # install chromedriver RUN apt-get install -yqq unzip RUN wget -O /tmp/chromedriver.zip http://chromedriver.storage.googleapis.com/`curl -sS chromedriver.storage.googleapis.com/LATEST_RELEASE`/chromedriver_linux64.zip RUN unzip /tmp/chromedriver.zip chromedriver -d /usr/local/bin/ # install xvfb RUN apt-get install -yqq xvfb RUN pip install pyvirtualdisplay # set display port and dbus env to avoid hanging ENV DISPLAY=:99 ENV DBUS_SESSION_BUS_ADDRESS=/dev/null # upgrade pip RUN pip install --upgrade pip # install selenium RUN pip install selenium #install request RUN pip install requests #install python-dotenv RUN pip install -U python-dotenv 可執行SH檔 : # 環境準備好了，下一個步驟就是要準備一個可以讓linux定期叫起來執行的sh檔\n這個檔案就是啟動docker並且執行我們要的python程式，這樣子而已\ncd PATHI_TO_PYTHON_SCRIPT docker rm python_selenium docker run --name python_selenium -it -d -w /usr/workspace -v $(pwd):/usr/workspace virtualorz/python_selenium python getEP.py OK，我逐行來說明一下\n跳到我們存放python程式的資料夾，因為我們要直接把這個資料夾掛進docker的WORKDIR\n移除掉python_selenium這個容易，當然名字會因為docker run \u0026ndash;name設定不同而不同\n透過docker run 把virtualorz/python_selenium給容器化叫起來，並且\n-w : 設定WORKDIR為 /usr/workspace\n-v : 將當前目錄 $(pwd)，掛載到容器的 /usr/workspace，這就是為什麼第一行要先切換到儲存python程式的資料夾原因\npython getEP.py : 這邊是給容器起來之後的程式命令，命令他去執行 \u0026ldquo;python getEP.py\u0026quot;也就會執行我們要的爬蟲了\n這邊是利用docker執行的特性，一般我們執行docker run 都會在後面接上bash ，讓他停在bash上等\n而這邊直接讓他執行python，執行完畢後容器就會被關閉，也就是第二行的先移除上次執行的容器\ncrontab 排程 : # 最後，交給linux crontab排程，假設我們要每分鐘都執行\n* * * * * /bin/bash /PATH_TO_SH_FILE 使用crontab -e 來編輯，請bash來叫起剛剛我們寫好的.sh檔就行了\n這樣一來我們就可以透過crontab 設定多久的時間需要排程執行python爬蟲，原理就是透過docker叫起容器執行完畢後會自行關閉\n","date":"2020年11月4日","externalUrl":null,"permalink":"/2020/11/04/docker%E7%92%B0%E5%A2%83%E5%AE%9A%E6%99%82%E5%95%9F%E5%8B%95selenium%E7%88%AC%E8%9F%B2%E6%8A%93%E5%8F%96%E8%B3%87%E6%96%99/","section":"Posts","summary":"","title":"docker環境定時啟動selenium爬蟲抓取資料","type":"posts"},{"content":"","date":"2020年11月4日","externalUrl":null,"permalink":"/tags/python/","section":"Tags","summary":"","title":"Python","type":"tags"},{"content":" Nuxt.js使用quill編輯器上傳圖片及直接編輯HTML # 如果你還沒看過如何部署Nuxt專案，請先閱讀\nLaravel+Nuxt快速上手\n本篇主要介紹如何使用vue-quill-editor文字編輯器，並且開啟獨立上傳圖片不使用內建的將圖片直接壓成base64寫入html中，主要是因為這種方法會讓html異常的肥大，另一方面也會處理如何開啟直接編輯html的功能。\n參考文件 :\nhttps://www.itread01.com/content/1543370884.html\nhttps://github.com/benwinding/quill-html-edit-button\nhttps://www.leixuesong.com/3860\n安裝quill editor # 安裝方式其實依照文件中照打就可以完成不難\n安裝vue-quill-editor npm install vue-quill-editor --save 引入vue plugin到nuxt專案中 在plugin資料夾中建立 : vue-quill-editor.js，內容如下\nimport Vue from \u0026#39;vue\u0026#39; import VueQuillEditor from \u0026#39;vue-quill-editor/dist/ssr\u0026#39; Vue.use(VueQuillEditor) 編輯nuxt.conf.js，引入該plugin以及CSS檔案 css: [ \u0026#39;quill/dist/quill.snow.css\u0026#39;, \u0026#39;quill/dist/quill.bubble.css\u0026#39;, \u0026#39;quill/dist/quill.core.css\u0026#39; ], plugins: [ { src: \u0026#34;~plugins/vue-quill-editor.js\u0026#34;, ssr: false }, ], 基本上到此已經安裝完畢，此時在component或page中的template加入以下\n\u0026lt;div class=\u0026#34;quill-editor\u0026#34; :content=\u0026#34;content\u0026#34; v-quill:myQuillEditor=\u0026#34;editorOption\u0026#34; @change=\u0026#34;onEditorChange($event)\u0026#34; style=\u0026#34;height: 250px;\u0026#34;\u0026gt; \u0026lt;/div\u0026gt; 用來定義編輯器該出現的位置，關鍵在class=\u0026ldquo;quill-editor\u0026rdquo;，\nv-quill: 定義工具列該出現哪些，詳細可以參考官網說明，範例如下\n@change : 定義當內容異動時該如何處理，這邊我會定義一個method讓他把html存回data\neditorOption = { bounds: \u0026#39;app\u0026#39;, placeholder: \u0026#39;\u0026#39;, modules: { toolbar: { container: [ [\u0026#39;bold\u0026#39;, \u0026#39;italic\u0026#39;, \u0026#39;underline\u0026#39;, \u0026#39;strike\u0026#39;], [\u0026#39;blockquote\u0026#39;, \u0026#39;code-block\u0026#39;], [{ \u0026#39;header\u0026#39;: 1 }, { \u0026#39;header\u0026#39;: 2 }], [{ \u0026#39;list\u0026#39;: \u0026#39;ordered\u0026#39;}, { \u0026#39;list\u0026#39;: \u0026#39;bullet\u0026#39; }], [{ \u0026#39;script\u0026#39;: \u0026#39;sub\u0026#39;}, { \u0026#39;script\u0026#39;: \u0026#39;super\u0026#39; }], [{ \u0026#39;indent\u0026#39;: \u0026#39;-1\u0026#39;}, { \u0026#39;indent\u0026#39;: \u0026#39;+1\u0026#39; }], [{ \u0026#39;direction\u0026#39;: \u0026#39;rtl\u0026#39; }], [{ \u0026#39;size\u0026#39;: [\u0026#39;small\u0026#39;, false, \u0026#39;large\u0026#39;, \u0026#39;huge\u0026#39;] }], [{ \u0026#39;header\u0026#39;: [1, 2, 3, 4, 5, 6, false] }], [{ \u0026#39;color\u0026#39;: [] }, { \u0026#39;background\u0026#39;: [] }], [{ \u0026#39;font\u0026#39;: [] }], [{ \u0026#39;align\u0026#39;: [] }], [\u0026#39;clean\u0026#39;], [\u0026#39;image\u0026#39;] ] } } } methods:{ onEditorChange({editor,html,text}){ this.editorContent = html } } 完整範例大概會像這樣子\nexport default { data () { return { editorOption: { } } }, beforeMount () { const VueQuillEditor = require(\u0026#39;vue-quill-editor/dist/ssr\u0026#39;); const Quill = require(\u0026#39;quill\u0026#39;); this.editorOption = { bounds: \u0026#39;app\u0026#39;, placeholder: \u0026#39;\u0026#39;, modules: { toolbar: { container: [ [\u0026#39;bold\u0026#39;, \u0026#39;italic\u0026#39;, \u0026#39;underline\u0026#39;, \u0026#39;strike\u0026#39;], [\u0026#39;blockquote\u0026#39;, \u0026#39;code-block\u0026#39;], [{ \u0026#39;header\u0026#39;: 1 }, { \u0026#39;header\u0026#39;: 2 }], [{ \u0026#39;list\u0026#39;: \u0026#39;ordered\u0026#39;}, { \u0026#39;list\u0026#39;: \u0026#39;bullet\u0026#39; }], [{ \u0026#39;script\u0026#39;: \u0026#39;sub\u0026#39;}, { \u0026#39;script\u0026#39;: \u0026#39;super\u0026#39; }], [{ \u0026#39;indent\u0026#39;: \u0026#39;-1\u0026#39;}, { \u0026#39;indent\u0026#39;: \u0026#39;+1\u0026#39; }], [{ \u0026#39;direction\u0026#39;: \u0026#39;rtl\u0026#39; }], [{ \u0026#39;size\u0026#39;: [\u0026#39;small\u0026#39;, false, \u0026#39;large\u0026#39;, \u0026#39;huge\u0026#39;] }], [{ \u0026#39;header\u0026#39;: [1, 2, 3, 4, 5, 6, false] }], [{ \u0026#39;color\u0026#39;: [] }, { \u0026#39;background\u0026#39;: [] }], [{ \u0026#39;font\u0026#39;: [] }], [{ \u0026#39;align\u0026#39;: [] }], [\u0026#39;clean\u0026#39;], [\u0026#39;image\u0026#39;] ] } } } Vue.use(VueQuillEditor) }, methods:{ onEditorChange({editor,html,text}){ this.editorContent = html } } } 安裝圖片上傳套件 # 因為預設上傳圖片會將圖片直接壓成base64寫入html中，這樣會使存下來的html超級長，所以我還是希望是上傳到伺服器後，html中只保留url即可\n1.安裝quill-image-extend-module\nnpm install quill-image-extend-module --save-dev 接著修改一下剛剛寫在beforMount的內容 beforeMount () { const VueQuillEditor = require(\u0026#39;vue-quill-editor/dist/ssr\u0026#39;); const Quill = require(\u0026#39;quill\u0026#39;); const {ImageExtend, QuillWatch} = require(\u0026#39;quill-image-extend-module\u0026#39;); this.editorOption = { bounds: \u0026#39;app\u0026#39;, placeholder: \u0026#39;\u0026#39;, modules: { ImageExtend: { loading: true, name: \u0026#39;file\u0026#39;, // 後端接收的檔名稱 action: process.env.MIX_API_URL+\u0026#39;/api/fileUpload\u0026#39;, // 後端接收檔案api response: (res) =\u0026gt; { return res.data.url // 此處返回的值一定要直接是後端回饋的圖片在伺服器的儲存路徑如：/images/xxx.jpg } }, toolbar: { handlers: { \u0026#39;image\u0026#39;: function () { QuillWatch.emit(this.quill.id) } }, container: [ [\u0026#39;bold\u0026#39;, \u0026#39;italic\u0026#39;, \u0026#39;underline\u0026#39;, \u0026#39;strike\u0026#39;], [\u0026#39;blockquote\u0026#39;, \u0026#39;code-block\u0026#39;], [{ \u0026#39;header\u0026#39;: 1 }, { \u0026#39;header\u0026#39;: 2 }], [{ \u0026#39;list\u0026#39;: \u0026#39;ordered\u0026#39;}, { \u0026#39;list\u0026#39;: \u0026#39;bullet\u0026#39; }], [{ \u0026#39;script\u0026#39;: \u0026#39;sub\u0026#39;}, { \u0026#39;script\u0026#39;: \u0026#39;super\u0026#39; }], [{ \u0026#39;indent\u0026#39;: \u0026#39;-1\u0026#39;}, { \u0026#39;indent\u0026#39;: \u0026#39;+1\u0026#39; }], [{ \u0026#39;direction\u0026#39;: \u0026#39;rtl\u0026#39; }], [{ \u0026#39;size\u0026#39;: [\u0026#39;small\u0026#39;, false, \u0026#39;large\u0026#39;, \u0026#39;huge\u0026#39;] }], [{ \u0026#39;header\u0026#39;: [1, 2, 3, 4, 5, 6, false] }], [{ \u0026#39;color\u0026#39;: [] }, { \u0026#39;background\u0026#39;: [] }], [{ \u0026#39;font\u0026#39;: [] }], [{ \u0026#39;align\u0026#39;: [] }], [\u0026#39;clean\u0026#39;], [\u0026#39;image\u0026#39;] ] } } } Quill.register(\u0026#39;modules/ImageExtend\u0026#39;, ImageExtend); Vue.use(VueQuillEditor) }, 其中定義在modules ImageExtend的參數\nname 是傳送到後端伺服器接收到變數名稱\naction是後端伺服器接收檔案的API URL，這邊我是將伺服器URL定義在.env中，後面接上已經設計好的URL位置\nresponse中的return值必須是這張圖片存在伺服器上的URL，所以必須注意API回傳中是不是有包含圖片URL\n這樣子，點選圖片上傳後就回直接上傳到伺服器上，並且將\u0026lt;img src=\u0026ldquo;URL\u0026rdquo; /\u0026gt; 寫入html中。\n安裝編輯html套件 # quill-editor功能中並不包含可以直接編輯html的功能，但是這個功能卻是很常用上的，因此我們透過其他套件來完成\n安裝quill-html-edit-button npm install quill-html-edit-button 接著來改一下剛剛beforMont中的內容 beforeMount () { const VueQuillEditor = require(\u0026#39;vue-quill-editor/dist/ssr\u0026#39;); const Quill = require(\u0026#39;quill\u0026#39;); const {ImageExtend, QuillWatch} = require(\u0026#39;quill-image-extend-module\u0026#39;); const {htmlEditButton} = require(\u0026#39;quill-html-edit-button\u0026#39;); this.editorOption = { bounds: \u0026#39;app\u0026#39;, placeholder: \u0026#39;\u0026#39;, modules: { ImageExtend: { loading: true, name: \u0026#39;file\u0026#39;, // 後端接收的檔名稱 action: process.env.MIX_API_URL+\u0026#39;/api/fileUpload\u0026#39;, // 後端接收檔案api response: (res) =\u0026gt; { return res.data.url // 此處返回的值一定要直接是後端回饋的圖片在伺服器的儲存路徑如：/images/xxx.jpg } }, htmlEditButton: {buttonHTML: \u0026#34;\u0026lt;span class=\u0026#39;ql-header ql-picker\u0026#39; style=\u0026#39;margin-left:-35px;margin-top: -2px\u0026#39;\u0026gt;HTML\u0026lt;/span\u0026gt;\u0026#34;}, toolbar: { handlers: { \u0026#39;image\u0026#39;: function () { QuillWatch.emit(this.quill.id) } }, container: [ [\u0026#39;bold\u0026#39;, \u0026#39;italic\u0026#39;, \u0026#39;underline\u0026#39;, \u0026#39;strike\u0026#39;], [\u0026#39;blockquote\u0026#39;, \u0026#39;code-block\u0026#39;], [{ \u0026#39;header\u0026#39;: 1 }, { \u0026#39;header\u0026#39;: 2 }], [{ \u0026#39;list\u0026#39;: \u0026#39;ordered\u0026#39;}, { \u0026#39;list\u0026#39;: \u0026#39;bullet\u0026#39; }], [{ \u0026#39;script\u0026#39;: \u0026#39;sub\u0026#39;}, { \u0026#39;script\u0026#39;: \u0026#39;super\u0026#39; }], [{ \u0026#39;indent\u0026#39;: \u0026#39;-1\u0026#39;}, { \u0026#39;indent\u0026#39;: \u0026#39;+1\u0026#39; }], [{ \u0026#39;direction\u0026#39;: \u0026#39;rtl\u0026#39; }], [{ \u0026#39;size\u0026#39;: [\u0026#39;small\u0026#39;, false, \u0026#39;large\u0026#39;, \u0026#39;huge\u0026#39;] }], [{ \u0026#39;header\u0026#39;: [1, 2, 3, 4, 5, 6, false] }], [{ \u0026#39;color\u0026#39;: [] }, { \u0026#39;background\u0026#39;: [] }], [{ \u0026#39;font\u0026#39;: [] }], [{ \u0026#39;align\u0026#39;: [] }], [\u0026#39;clean\u0026#39;], [\u0026#39;image\u0026#39;] ] } } } Quill.register(\u0026#39;modules/ImageExtend\u0026#39;, ImageExtend); Quill.register(\u0026#39;modules/htmlEditButton\u0026#39;, htmlEditButton); window.Quill = Quill Vue.use(VueQuillEditor) }, 兩個地方特別需要注意\n1.一定要加入這行，不然nuxt會丟錯誤訊息\nwindow.Quill = Quill 2.顯示在toolbar上的按鈕在這邊定義\nhtmlEditButton: {buttonHTML: \u0026#34;\u0026lt;span class=\u0026#39;ql-header ql-picker\u0026#39; style=\u0026#39;margin-left:-35px;margin-top: -2px\u0026#39;\u0026gt;HTML\u0026lt;/span\u0026gt;\u0026#34;} github上面提供的範例實際跑出來會有點歪掉，我猜想是因為quill-editor經過改版後每個按鈕的寬度有重新定義過，所以這邊我自己加了class以及margin讓畫面好看些。\n","date":"2020年9月7日","externalUrl":null,"permalink":"/2020/09/07/nuxt-js%E4%BD%BF%E7%94%A8quill%E7%B7%A8%E8%BC%AF%E5%99%A8%E4%B8%8A%E5%82%B3%E5%9C%96%E7%89%87%E5%8F%8A%E7%9B%B4%E6%8E%A5%E7%B7%A8%E8%BC%AFhtml/","section":"Posts","summary":"","title":"Nuxt.js使用quill編輯器上傳圖片及直接編輯HTML","type":"posts"},{"content":"在上一篇中已經說明了如何開發laravel+Nuxt前後端分離\nhttp://www.alvinchen.club/2020/07/17/laravel-nuxt%e5%bf%ab%e9%80%9f%e4%b8%8a%e6%89%8b/\n接下來要說明的部分是如何在docker中部署專案\n我們知道在開發模式中可以透過npm run dev來開說3000port除錯模式，而開發完成後需要職行\nnpm run build編譯專案以及npm run start 就可以把自帶的node js伺服器打開\n但是，如果在正式的伺服器上每次都要特別去開node js伺服器確實有點麻煩\n這次要說明的是透過pm2來管理nodejs，應用的環境在docker-compose中\n讓我們可以做到docker-compose up -d 在叫起docker的同時自動啟動Nuxt\nPM2\npm2是一套nodejs的管理工具，除了可以幫我們開關nodejs server以外，還可以做到cluster，這是我覺得最方便的地方\n安裝方式很簡單\nnpm install pm2 可以裝在global環境中即可，而像本次是在docker環境中的話我就直接寫在Dockerfile中\n啟動指令\npm2 start -i 2 npm --name \u0026#39;nuxt\u0026#39; -- run start 其中 -i 的部分是指cluster的數量，可以依照伺服器核心數來自動\n\u0026ndash;name 中需要填入nuxt專案中package.json中的‘’name\n完整的docker-compose環境可以參考我的github\nhttps://github.com/virtualorz/docker_compose_for_nuxt.git\n這個環境是php7.3的laravel環境 + mysql5.7 以及nginx\n可以在php73_laravel的Dockerfile中看到\nRUN npm i -g pm2 EXPOSE 3000 安裝了pm2並且把3000 port expose出來\n最後可以在docker-compose.yml中看到\nphp73_laravel的command中，加入了pm2 start讓，docker啟動時可以一併叫起Nuxt\nport的部分，把80轉到內部的3000\n以及，8081轉到內部的80 port 讓laravel API可以正確運作\n這時候只要docker-compose up -d 就可以直接access到Nuxt以及8081 port可以access到API的部分，實際狀況可以依照需求自行調整docker-compose.yml中的設定\n","date":"2020年7月30日","externalUrl":null,"permalink":"/2020/07/30/%E5%9C%A8docker%E4%B8%AD%E9%83%A8%E7%BD%B2nuxt%E5%B0%88%E6%A1%88/","section":"Posts","summary":"","title":"在docker中部署Nuxt專案","type":"posts"},{"content":"前言 :\n因為前一篇laravel 7 + VueJs開發紀錄\n完成了一個網站前後台開發作業，但VueJs的SEO問題卻有點難處理，下個專案必須考慮SEO狀況因此單純用VueJs處理前端就已經不敷使用\n研究了一下SSR的處理方式，決定透過Nuxt來處理Vue的SSR讓SEO更友善，本篇將紀錄初期開發踩坑全記錄\n如何整合Laravel? # Nuxt是不是可以像Vue一樣，將前後端兩個project整合成單一網站來開發?\n一開始是這麼想到，畢竟開發者只有我一個人如果還有拆兩塊寫有點麻煩，因此找到gitHub上已經有人整合好的plugin可以使用\nhttps://github.com/m2sd/nuxt-laravel\nhttps://github.com/m2sd/laravel-nuxt\n先說結論 : 目前無法使用Nuxt universal模式，也就是無法處理SSR的部分，因此我放棄了\n最終我只有在laravel資料夾底下的resources/nuxt 來建立Nuxt專案，整合git repo到同一個專案底下方便管理\n建立Nuxt專案 # 依照Nuxt官網提示很容易只要一個指令就可以將Nuxt初始化安裝起來\nnpx create-nuxt-app PROJECT_NAME 切換到laravel/resources資料夾底下下指令\nnpx create-nuxt-app nuxt 當然踩坑了npx丟了錯誤出來，還好gitHub上也不少人遇到一樣的錯誤\nhttps://github.com/nuxt/create-nuxt-app/issues/383\nhttps://github.com/serverless/serverless/issues/6989\n先把create-nuxt-app裝起來就可以了\nsudo npm install -g create-nuxt-app 接著系統會提示使用的專案資料夾名稱以及其他需要的相依套件\n這次我只有裝axios且server使用NodeJs，有需要其他套件就自行選擇\n安裝好之後且換到laravel/resources/nuxt資料夾\nnpm run dev 應該就可以透過 http://localhost:3000 看到Nuxt網站已經建立完成\nDocker環境也會踩坑嗎? # 當然不缺席，如果你跟我一樣開發環境是架構在docker-compose(php7 + nginx)的話需要注意幾個地方\n為了避免每次重開docker都需要安裝一次create-nuxt-app 所以我將指令加入php的Dockerfile RUN npm i -g create-nuxt-app 因為Nuxt測試與正式環境都架構在3000 port底下所以記得要把php的Dockerfile expose 3000 port EXPOSE 3000 docker-compose記得將3000 port加入對應的php container ports: - 3000:3000 最後，因為Nuxt 並非扔回3000 port而已，而是http://localhost:3000/，所以要將php的Dockerfile加入 ENV HOST 0.0.0.0 這樣應該就可以透過http://localhost:3000連線了，雖然Nuxt在build的時候會顯示的是container的IP位置\nNuxt的Router怎麼處理 # 基本上Nuxt不必像Vue一樣需要手動建立每一個連結的Route設定，他會按照page資料夾底下的文件結構自己長出來，基本上如果沒有特殊需求依照官網的指示來設定page資料夾底下的vue檔案就可以了\nhttps://nuxtjs.org/guide/routing/\nBut! 我個人習慣依照Route的結構來設定全站的使用權限，因此我還是走自行建立Route的方式\nhttps://github.com/nuxt-community/router-module\n首先，安裝需要的package\nnpm install --save-dev @nuxtjs/router 接著到resources/nuxt也就是nuxt的根目錄找到nuxt.conf.js，在buildModules 陣列中加入以下\n@nuxtjs/router 接著就可以在nuxt根目錄建立router.js 建立自己需要的路由資訊了Example\nimport Index from \u0026#39;~/pages/backend/index\u0026#39; Vue.use(Router) export function createRouter () { return new Router({ mode: \u0026#39;history\u0026#39;, routes: [ { path:\u0026#39;/\u0026#39;, name: \u0026#39;index\u0026#39;, component: Index } ] }) } Nuxt如何處裡.env檔 # .env可以用來記錄後端API的位置以及其他環境資訊，使用.env可以分開正式上線後跟開發階段的不同參數，vue整合到laravel後，可以直接讀取laravel的.env檔案，那Nuxt怎麼處理?\nhttps://github.com/nuxt-community/dotenv-module\n安裝套件\nnpm install --save-dev @nuxtjs/dotenv 接著到nuxt.conf.js的buildModules加入以下\n@nuxtjs/dotenv 最後，就可以在nuxt跟目錄底下建立.env檔案，如果有個變數名稱為MIX_API_NAME的話個可已透過process.env.MIX_API_NAME來取得參數值\nNuxt的localStorage # 在vue中，可以將需要存在瀏覽器內的資料(ex:登入資訊)存放到localstorage中，但Nuxt是沒有的，如果需要儲存資料可以使用server side的session 或cookie，畢竟Nuxt是Server與Client並存的，所幸也有人已經完成package可以存取localstorage，但是有些需要注意的地方\nhttps://github.com/rubystarashe/nuxt-vuex-localstorage\n首先，安裝套件\nnpm i nuxt-vuex-localstorage 接著編輯nuxt.conf.js中的models，加入以下資訊\n[\u0026#39;nuxt-vuex-localstorage\u0026#39;,{localStorage: [\u0026#39;adminUser\u0026#39;]}] 其中，\u0026lsquo;adminUser\u0026rsquo;是我會用來存取localstorage的vuex module，可以把所有有用上的module都填在這個陣列中\n以這個範例來看，adminUser中的state資料就會被存放到localstorage中不會揮發\n但是，需要注意的是如果Nuxt跑在usiversal模式底下，網頁載入的時候是從server端將資料渲染好再傳回瀏覽器，這時候因為localstorage屬於client端的資料就會無法取得\n如果component需要取得localsotrage資料我會在mounted()中將存在localstorage中的資訊取出存回到component的data中，方便頁面渲染資料時直接使用data中的資料\nNuxt使用vue的套件 # https://zh.nuxtjs.org/guide/plugins/\n根據官網描述，Nuxt是可以直接使用vue的npm套件的，舉例來說如果我們需要用到vue-simple-alert\nhttps://github.com/constkhi/vue-simple-alert\n首先，一樣先安裝套件\nnpm i vue-simple-alert 接著在nuxt/plugin資料夾中建立一個檔案命名為vue-simple-alert.js，內容為\nimport Vue from \u0026#34;vue\u0026#34;; import VueSimpleAlert from \u0026#34;vue-simple-alert\u0026#34;; Vue.use(VueSimpleAlert); 接著編輯跟目錄底下的nuxt.conf.js，在plugins中加入\n\u0026#39;~/plugins/vue-simple-alert\u0026#39; 在component中就可以依照vue套件的原生方式使用了，但我偶爾會遇到錯誤，目前還不知道發生什麼事情\nNuxt中的Server / Client 取得API資料\nNuxt中asyncData與fetch可以說是精華所在，Server端會先跑過這兩個function，換句話說可以在這邊先呼叫api取得資料這樣就可以正確在server端渲染完成了\n詳細可以參考這篇文章\nhttps://medium.com/@jackercleaninglab/ssr-nuxt-js-%E8%B6%85%E5%85%A5%E9%96%80-84a0823b45ed\n舉例來說，我會這樣子取得資料\nasync fetch ({ store, params, query }) { await store.dispatch(\u0026#34;MODULE_NAME/ACTION_NAME\u0026#34;,其他需要傳送的參數) }, 其中，有幾個地方說明一下\nparams : 是指route中設定好的參數，舉例來說route中如果設定了/:id\n就可以透過params.id 來取得網址中帶入的id資料\nquery: 網址中的_GET參數，如果網址中有?page=2\n就可以透過query.page來取得\n但是，這次遇到一個狀況是取得資料後我想放回component中的data而不是渲染到網頁中\n這部分做法就回到跟vue相同，在mounted中透過dispatch取得資料後watch變數變更的時候再寫回data中即可\n如果還有更好的做法會再更新上來\n以上幾點，紀錄一下初次開發\n下一步將會是在docker中獨立建立container處理Nuxt 伺服器，並且做到docker-compose up後可以自動build 並start Nuxt\n","date":"2020年7月17日","externalUrl":null,"permalink":"/2020/07/17/laravel-nuxt%E5%BF%AB%E9%80%9F%E4%B8%8A%E6%89%8B/","section":"Posts","summary":"","title":"Laravel+Nuxt快速上手","type":"posts"},{"content":"參考文件 : https://vuex.vuejs.org/zh/\nhttps://jigsawye.com/2017/08/30/vuex-module-namespacing/\nLaravel 5.8後需要另外安裝laravel/ui\ncomposer require laravel/ui php artisan ui vue 這時候可以看到 /resource/js/components/ExampleComponent.vue 已經產生\n接著安裝前端路由套件與資料管理套件vue-router , vuex\nnpm install vue-router npm install vuex --save 基礎 vue router 設定\n概念 : 先將Laravel web router都固定指向同一個page，由此page來生成vue渲染\n1.修改routs/web.php\nRoute::get(\u0026#39;/{any}\u0026#39;, \u0026#39;SinglePageController@index\u0026#39;)-\u0026gt;where(\u0026#39;any\u0026#39;, \u0026#39;.*\u0026#39;); 建立controller SinglePageController，把index指向app public function index() { return view(\u0026#39;app\u0026#39;); } 建立/resources/views/app.blade.php 重點有幾個地方\n一定要有\u0026lt;div id=\u0026ldquo;app\u0026rdquo;\u0026gt;\u0026lt;/div\u0026gt; 這是vue的掛載點\n一定要引入\u0026lt;script src=\u0026quot;{{asset(\u0026lsquo;js/app.js\u0026rsquo;)}}\u0026quot;\u0026gt;\u0026lt;/script\u0026gt;\n一定要有\u0026lt;router-view\u0026gt;\u0026lt;/router-view\u0026gt;作為vue-router切換頁面內容渲染區域\n\u0026lt;router-link to=\u0026quot;/s\u0026quot;\u0026gt;Task\u0026lt;/router-link\u0026gt;作為vue router頁面切換範例連結\n建立/resources/js/router.js import Vue from \u0026#39;vue\u0026#39;; import VueRouter from \u0026#39;vue-router\u0026#39;; import Index from \u0026#39;./views/index\u0026#39;; import NotFound from \u0026#39;./views/notFound\u0026#39;; Vue.use(VueRouter); const router = new VueRouter({ mode: \u0026#39;history\u0026#39;, routes:[ { path: \u0026#39;/s\u0026#39;, name: \u0026#39;index\u0026#39;, component: Index }, { path: \u0026#39;*\u0026#39;, name: \u0026#39;404\u0026#39;, component: NotFound } ] }); export default router; 建立兩個路由連結，第一個連結到/s 顯示 /resource/views/index.vue的內容\n另一個為router中沒有定義的所有畫面皆導向/resource/views/NotFound.vue的內容\n修改/resources/js/app.js import Routes from \u0026#39;./routes.js\u0026#39;; 將上面定義好的路由引入\nconst app = new Vue({ el: \u0026#39;#app\u0026#39;, router: Routes }); 原app中也要將router放入\n最後，執行\nnpm run watch 就會在瀏覽器上看到畫面了\n接著，Vuex的使用方式\nVuex概念為儲存網站內會使用的資料，比如遠端API取得的資料或者遠端登入後取得的資料之類，避免每個頁面要取得某筆資料的時候都需要獨立寫axios\nstate,mutations, actions是什麼樣的定義就不再多做說明，定義範例可以參考官網有完整的範例，這邊要說明的是，一個網站通常有多的遠端取得的資料，總不能全部都塞在store/index.js 會很難維護，因此我本把不同類別的資料拆成module變成這樣子的資料結構\nresorece/js/store ------/module --------ATyle.js --------BType.js ------index.js store資料夾底下只保留index.js作為資料整合使用，其他資料定義都放module資料夾底下\nOK，簡單一個範例來示範AType.js\nimport axios from \u0026#34;axios\u0026#34;; const state = () =\u0026gt;({ VarA : [], BarB : \u0026#34;\u0026#34; }) const mutations = { FUNCTION_NAME (state,payload) { state.VarA = payload } } const actions = { async FUNCTION_NAME ({commit}) { //do something commit(\u0026#39;FUNCTION_NAME\u0026#39;,payload) //紀錄一下加了async是因為用非同步的axios去API拿資料 } } export default { namespaced: true,//用namespace 叫資料比較方便 state, mutations, actions } BType.js也是一樣自行定義資料流怎麼跑，接著在index.js做資料整合\nimport Vue from \u0026#39;vue\u0026#39; import Vuex from \u0026#39;vuex\u0026#39; import kolData from \u0026#39;./module/AType\u0026#39; import userData from \u0026#39;./module/BType\u0026#39; Vue.use(Vuex) const storeData = new Vuex.Store({ modules: { AType:AType, BType:BType } }) export default storeData 把AType以及BType註冊進入vuex的module就行了，接著說明一下在app.js中以如何引入vuex\n以下為範例\nimport storeData from \u0026#39;./store/index\u0026#39; const store = storeData const app = new Vue({ el: \u0026#39;#app\u0026#39;, router: Routes , store: store }); 概念上是引入store/index.js 然後將它注入app中就可以存取了\n最後，再來一個範例如何在component中使用vuex的資料，下面舉一個component中的script為範例\n\u0026lt;script\u0026gt; import { mapState , mapActions } from \u0026#39;vuex\u0026#39; export default { created(){ }, mounted() { this.$store.dispatch(\u0026#39;AType/FUNCTION_NAME\u0026#39;) this.$store.dispatch(\u0026#39;BType/FUNCTION_NAME\u0026#39;) }, methods:{ ...mapActions(\u0026#39;AType\u0026#39;,[ \u0026#39;FUNCTION_NAME\u0026#39; ]), ...mapActions(\u0026#39;BType\u0026#39;,[ \u0026#39;FUNCTION_NAME\u0026#39; ]), }, computed:{ ...mapState(\u0026#39;AType\u0026#39;,[ \u0026#39;VARABLE\u0026#39; ]), ...mapState(\u0026#39;BType\u0026#39;,[ \u0026#39;VARABLE\u0026#39; ]) } } \u0026lt;/script\u0026gt; 以上\n","date":"2020年5月12日","externalUrl":null,"permalink":"/2020/05/12/laravel-7-vuejs/","section":"Posts","summary":"","title":"laravel 7 + VueJs開發紀錄","type":"posts"},{"content":"上篇提到如何將打包好的image上傳到GCR上，接著有了image我們拿來部署到Kubernetes上\n如果你還沒看過之前的文章請先閱讀:\n第一篇 : Drone 自動打包Laravel imgae 並上傳到docker hub上\n第二篇 : Drone 自動打包Laravel imgae 並上傳到Google Cloud Registry(GCR)上\n其他參考文件 :\nhttps://kubernetes.io/docs/tasks/administer-cluster/access-cluster-api/\nhttp://plugins.drone.io/mactynow/drone-kubernetes/\nhttps://ithelp.ithome.com.tw/articles/10226404\nhttps://jimmysong.io/kubernetes-handbook/guide/auth-with-kubeconfig-or-token.html\n先說說今天要達成的目的有兩個\n一是建立Kupernetes叢集並部署Nginx+php7.3-fpm 跑Laravel專案\n二是當程式碼更新後，透過Drone重新打包image並且rolling update到Kubernetes Deployment上\n一. 建立Kubernetes Laravel可運行環境 # 一開始先來看一下kubernetes結構會長什麼樣子\n基本上就是php + nginx 分別在兩個Deployment然後架構在同一個叢集上\n建立叢集的部分就不多談，依照介面上提示選擇節點數以及機器規格就行了\n接著，建立php7.3-fpm Deployment 用來Run PHP的核心\n進入叢集後點選\u0026quot;部署\u0026quot;，在容器的部分選擇上一篇文章中上傳到GCR的docker_compose_php73_laravel_build 最新版\n接著，下一頁編輯應用程式名稱以及namespace，namespace很重要最後在做Rolling update時會用上\n等待部署完畢後，接著必須把php73-fpm建立服務才能夠給其他Deployment使用，點選\u0026quot;公開\u0026quot;\n有幾個地方要注意，php-fpm預設port在9000，所以必須要把9000 port公開出來，另外因為php-fpm並沒有要提供叢集外的對外服務，因此只要服務類型選擇\u0026quot;叢集IP\u0026quot;即可，不用掛平衡器\n最後命名是最重要的步驟，Nginx config中已經設定好PHP 會Pass到php73:9000，因此命名必須要正確\n完成php-fpm的部署後，接著是nginx\nnginx的部分跟php相同，使用上一篇上傳到GCR的image，要注意的只有namespace需要填入與php-fpm相同的值\nnginx部署完成後，相同需要公開service才能夠提供服務\nnginx就必須提供對外服務了，所以port的部分選擇80，服務類型選擇\u0026quot;負載平衡器\u0026quot;\n最後，其實步驟都已經完成了，回過頭來看一下nginx中的config，重點是設定好root以及fastcgi_pass php73:9000\nserver { listen 80 default_server; server_name _; root /var/project/public; index index.html index.htm index.php; charset utf-8; location / { try_files $uri $uri/ /index.php?$query_string; } access_log /var/log/nginx/access.log; error_log /var/log/nginx/error.log; error_page 404 /index.php; sendfile off; location ~\\.php$ { fastcgi_split_path_info ^(.+\\.php)(/.+)$; fastcgi_pass php73:9000; fastcgi_index index.php; include fastcgi_params; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; #fastcgi_param DB_HOST mysql56; } location ~ /\\.ht { deny all; } client_max_body_size 120M; } 另一方面，在nginx的Dockerfile中也必須加入把config複製到image內\nFROM virtualorz/dev_compose_nginx COPY . /var/project COPY ./dockerfiles/dockerfile_for_nginx/default.conf /etc/nginx/conf.d/default.conf php-fpm 的部分也需要注意一下 dockerfile，因為git上的資料不包含 .env 檔以及 composer 資料，因此需要加入以下\nCOPY ./.env.example /var/project/.env RUN cd /var/project \u0026amp;\u0026amp; composer install RUN cd /var/project \u0026amp;\u0026amp; php artisan key:generate RUN chown -R www-data:www-data \\ /var/project/storage \\ /var/project/bootstrap/cache 以上步驟就可以透過nginx的負載平衡器IP連線到Laravel網站了\n二. 透過Drone執行rolling update # 概念上是這樣子的，當程式碼更新後git push到git hub後Drone會自動build image並且更新Deployment到新版的image\n首先，要使用的Drone Plugin為Kubernetes Deployments\n根據官方文件上的資訊，我們需要下列幾項參數\nkubernetes_server kubernetes_token namespace deployment repo container tag 但是文件中並沒有辦法很清楚的讓我這種新手了解資訊可以從哪邊挖出來，下面我會詳細說明\n1.kubernetes_server\n回到叢集中把cloud shell打開，透過指令\nkubectl config view 內容中就可以找到資訊\n2.kubernetes_token\n第一步的指令中也可以找到kubernetes_token，但是24小時後就會失效，我們需要另外建立kubernetes_token\n與第一步相同的是，在cloud sehell中執行以下指令，其中$NAMESPACE就是我們在佈建Deployment時填入的namespace\n$ROLEBINDING_NAME為自定義名稱\nkubectl create rolebinding $ROLEBINDING_NAME --clusterrole=admin --serviceaccount=$NAMESPACE:default --namespace=$NAMESPACE 接著用指令來顯示token\nkubectl describe secret $ROLEBINDING_NAME 2.namespace\n就是剛剛我們部署php-fpm以及nginx時所輸入的namespace\n3.deployment\n就是剛剛我們部署php-fpm以及nginx時所輸入的名稱\n4.repo\n就是要更新上去的新image，如果還記得上一篇如何推上GCR應該還可以找到\n以台灣為例 : asia.gcr.io/PROJECT_NAME/IMAGE_NAME\n5.container\ncontainter名稱可以在deployment中的\u0026quot;詳細資料\u0026quot; Pod規格內找到\n6.tag\n要替換的image tag，這邊注意不要填lastst，每次上傳到GCR可以自訂tag，請輸入相對應的tag就行\n所以.drone.yml 中的steps大概會像這樣子\n- name: k8s_nginx_deployee image: quay.io/honestbee/drone-kubernetes settings: kubernetes_server: https://XX.XX.XX.XX kubernetes_token: YOUR_TOKEN namespace: laravel deployment: nginx-1 repo: asia.gcr.io/PROJECT_NAME/docker_compose_nginx_build container: docker-compose-nginx-build-sha256-1 tag: 0.888 如此一來，結合上一篇就可做到git push Drone 自動 Rolling update了\n","date":"2020年2月25日","externalUrl":null,"permalink":"/2020/02/25/drone-%E8%87%AA%E5%8B%95%E6%89%93%E5%8C%85laravel-imgae-rolling-update-%E5%88%B0gke%E4%B8%8A/","section":"Posts","summary":"","title":"Drone 自動打包Laravel imgae rolling update 到GKE上","type":"posts"},{"content":"本篇主要是延伸上一篇 Drone 自動打包Laravel imgae 並上傳到docker hub上\n的內容，將打包完成的docker image轉存到GCR上，有關Docker的建置方式請直接參考上一篇\n其他參考文件:\nhttp://plugins.drone.io/drone-plugins/drone-gcr/\nOK，直接進入主題，要上傳到GCR上第一個步驟是需要到GCP上先產生憑證檔，讓Drone可以拿到憑證來上傳image\n產生憑證檔 # 到GCP \u0026gt; API和憑證 點\u0026quot;建立憑證\u0026quot;\n接著選擇服務帳戶\n輸入自訂的服務帳戶名稱後，按下建立\n接著選擇角色:\n儲存空間 \u0026gt; Storage管理員 按下繼續\n接著按下\u0026quot;建立金鑰\u0026quot;\n右邊選單會跳出選項來，選擇JSON\n按下建立後，會跳出下載檔案，把JSON檔存下後續步驟會需要資訊\nDrone Plugin : Drone GCR # 這個plugin 是Drone docker 的延伸，其他參數可以參考Drone docker\n範例中，我們跟上次一樣，把build好的image改上傳到GCR中\n- name: publish_nginx image: plugins/gcr settings: registry: asia.gcr.io repo: PROJECT_ID/docker_compose_nginx_build tags: latest dockerfile: dockerfiles/dockerfile_for_nginx/Dockerfile json_key: \u0026gt; { \u0026#34;type\u0026#34;: \u0026#34;service_account\u0026#34;, \u0026#34;project_id\u0026#34;: \u0026#34;XXX\u0026#34;, \u0026#34;private_key_id\u0026#34;: \u0026#34;XXX\u0026#34;, \u0026#34;private_key\u0026#34;: \u0026#34;XXX\u0026#34;, \u0026#34;client_email\u0026#34;: \u0026#34;access-storage@XXX.iam.gserviceaccount.com\u0026#34;, \u0026#34;client_id\u0026#34;: \u0026#34;XXX\u0026#34;, \u0026#34;auth_uri\u0026#34;: \u0026#34;https://accounts.google.com/o/oauth2/auth\u0026#34;, \u0026#34;token_uri\u0026#34;: \u0026#34;https://oauth2.googleapis.com/token\u0026#34;, \u0026#34;auth_provider_x509_cert_url\u0026#34;: \u0026#34;https://www.googleapis.com/oauth2/v1/certs\u0026#34;, \u0026#34;client_x509_cert_url\u0026#34;: \u0026#34;https://www.googleapis.com/robot/v1/metadata/x509/access-storage%40XXX.iam.gserviceaccount.com\u0026#34; } 這個step中，\nregistry為asia.gcr.io是google cloud registry亞洲區的伺服器位置\nrepo 為 PROJECT_ID加上你的image name\njson_key的內容就是我們上個步驟下載的JSON檔內容，貼上即可\n以上全部完成後，只要將這個.drone.yml 隨著git push的github上就可以在image打包完成後直接上傳到GCR上了\n上傳完成後，你就可以在GCP \u0026gt; container registry \u0026gt; 映像檔中 找到\n下一步，就要來完成透過上傳的image讓k8s 可以rolling update了\n","date":"2020年2月20日","externalUrl":null,"permalink":"/2020/02/20/drone-%E8%87%AA%E5%8B%95%E6%89%93%E5%8C%85laravel-imgae-%E4%B8%A6%E4%B8%8A%E5%82%B3%E5%88%B0google-cloud-registrygcr%E4%B8%8A/","section":"Posts","summary":"","title":"Drone 自動打包Laravel imgae 並上傳到Google Cloud Registry(GCR)上","type":"posts"},{"content":"先說說Drone的概念，Drone可以分為Server與Agent兩者\nServer負責與Git hub等版本庫溝通，然後依照pipeline指令分派給Agent執行\n而Agent在執行時則是完全透過docker containter的環境完成所有指令\n簡單說就是pipeline中定義的每一個step都是給定一個image來執行指定的command\n今天要完成的是利用Drone連結github，當git push後將程式碼重新封裝到新的image然後上傳到dockerhub上\n參考資料\nhttps://docs.drone.io/configure/pipeline/\nhttps://ithelp.ithome.com.tw/articles/10223277\n如果你沒過Docker請先閱讀MAC安裝Docker\n安裝Drone # 安裝前 因為要連結github，所以先到github上設定好 OAuth Apps\n路徑為 : Settings \u0026gt; Developer settings \u0026gt; OAuth Apps \u0026gt; New OAuth App\nhttps://github.com/settings/applications/new\n填寫資訊如下\n上面的資訊，Drone可為自行設定的APP名稱，MY_SERVER_URL則是架設Drone的伺服器IP port或者網址，最後一個Authorization callback URL 則只需要在MY_SERVER_URL後面加入/login 即可\n按下 Register application後會拿到Client ID 以及Client Secret，這兩者會在下方設定檔用上\n接著Drone的安裝其實很簡單，可以按照官網上的說明啟動兩個docker就可以了(Server + Agent)，也可以透過docker compose一次啟動兩個containter\n備妥以下docker-compose.yml 以及.env設定檔即可\nersion: \u0026#39;3\u0026#39; services: drone-server: image: drone/drone:latest #指定server為drone最新版 ports: - 80:80 volumes: - ./:/data restart: always environment: - DRONE_SERVER_HOST=${DRONE_SERVER_HOST} #Drone server url 填寫在.env檔案中 - DRONE_SERVER_PROTO=${DRONE_SERVER_PROTO} #Drone server protocol 填寫在.env檔案中 - DRONE_RPC_SECRET=${DRONE_RPC_SECRET} #自定義隨機碼，填寫在.env檔案中 - DRONE_AGENTS_ENABLED=true # Github Config - DRONE_GITHUB_CLIENT_ID=${DRONE_GITHUB_CLIENT_ID} #github client ID，填寫在.env檔案中 - DRONE_GITHUB_CLIENT_SECRET=${DRONE_GITHUB_CLIENT_SECRET} #github client secret，填寫在.env檔案中 drone-agent: image: drone/drone-runner-docker:latest ports: - 3000:3000 restart: always depends_on: - drone-server volumes: - /var/run/docker.sock:/var/run/docker.sock environment: - DRONE_RPC_PROTO=http - DRONE_RPC_HOST=drone-server - DRONE_RPC_SECRET=${DRONE_RPC_SECRET} - DRONE_RUNNER_NAME=${DRONE_HOSTNAME} #自訂host name 填寫在.env檔案中 - DRONE_RUNNER_CAPACITY=2 .env\nDRONE_SERVER_HOST=MY_SERVER_URL #Drone server的網址或者IP DRONE_SERVER_PROTO=http DRONE_RPC_SECRET=gEY6Ys1knt3fJnAsda #自行定義亂數即可 DRONE_GITHUB_CLIENT_ID=1234567 #上面github app 拿到的client id DRONE_GITHUB_CLIENT_SECRET=7654321 #上面github app 拿到的client secret DRONE_HOSTNAME=test_host #自行定義的host name 檔案編寫完成後即可用\ndocker-compose up -d 來啟動Drone server 以及Drone Agent兩台container\n認證啟動專案 # Drone啟動後，透過瀏覽器連線到MY_SERVER_URL，畫面會跳到github上作認證，完成後就可以看到像這樣子在github上的列表了\n要啟動哪個專案就按下右邊的\u0026quot;ACTIVATE\u0026quot;按鈕來設定\n(如果按下ACTIVATIVE沒有反應肯定是.evn裡面變數設定有錯誤，請檢查)\n1.0版後的Drone可以設定.drone.yml的位置，如果沒有要修改的話，按下\u0026quot;SAVE\u0026quot;按鈕就行了\nPIPELINE # 專案連結上後，接著就是要讓Drone知道收到git push後該做什麼事情，假設現在要設定drone_test2這個專案，在專案根目錄建立.drone.yml，加入git跟著一起push上github即可\n--- kind: pipeline type: docker #在docker環境中執行 name: test #自定義pipleline名稱 platform: os: linux arch: amd64 steps: #工作列表 - name: build-nginx-image #自訂工作名稱 pull: if-not-exists #要不要pull指定的image image: plugins/docker #指定要拿來執行command的image或者plugin settings: registry: registry.hub.docker.com #dockerhub registry url repo: registry.hub.docker.com/virtualorz/docker_compose_nginx_build #打包完要上傳的docker repo名稱 dockerfile: dockerfiles/dockerfile_for_nginx/Dockerfile #根據指定的Docker file打包image auto_tag: true #自動下tag username: DOCKERHUB_USER #dockerhub user name password: DOCKERHUB_PASSWORD #dockerhub password - name: build-php-image pull: if-not-exists image: plugins/docker settings: registry: registry.hub.docker.com repo: registry.hub.docker.com/virtualorz/dev_compose_php73_laravel_build dockerfile: dockerfiles/dockerfile_for_laravel/Dockerfile auto_tag: true username: DOCKERHUB_USER password: DOCKERHUB_PASSWORD 上面的yml檔一共做兩件事情，都寫在step中\n一共打包兩個image檔，完成後都上傳到dockerhub中\n其中，Dockerfile我只做了一件事情，就是把上傳到dithub的程式碼搬移到image中，像這樣子\nFROM virtualorz/dev_compose_nginx COPY . /var/project Drone在在動的時後會先將github上的repo先clone到根目錄中，所以COPY路徑指定 . 即可\n隨著.drone.yml push到github上後，Drone隨即會開始作動\n可以看到左邊的選單一共有三個工作，兩個是.drone.yml中定義的，第一個clone則是上面提到Drone會先將repo clone到根目錄中\n如果工作都正常完成會打上綠色勾勾，如果有問題會在右方顯示錯誤訊息，並且顯示紅色叉叉\n等到所有工作完成後，你會在dockerhub上看到多了兩個新上傳的image!\n以上，完成今天的任務目標，以後只要git push 就可以自動的打包成image了\n","date":"2020年2月4日","externalUrl":null,"permalink":"/2020/02/04/drone-%E8%87%AA%E5%8B%95%E6%89%93%E5%8C%85laravel-imgae-%E4%B8%A6%E4%B8%8A%E5%82%B3%E5%88%B0docker-hub%E4%B8%8A/","section":"Posts","summary":"","title":"Drone 自動打包Laravel imgae 並上傳到docker hub上","type":"posts"},{"content":"關於docker容器化的開發環境之前已經有多篇說明\n使用laradock的方式 : laradock : laravel專案容器化開發實作\n自建php5.6 docker image : docker 容器化 : PHP5.6專案容器化開發實作\n自建ubuntu php7.3 for laravel : [作品][docker image]ubuntu18.04-php7.3-apache-for-laravel\n今天要說明的是將以上幾件事情整合，透過docker compose 將容器整合並且降低附載資源\n參考資料 :\nhttps://learnku.com/laravel/t/10292/how-do-i-access-multiple-laravel-projects-under-localhost\nhttps://gist.github.com/noeldiaz/08a1211a7c47f21f7083\n好的，參考資料列出後接下來要說明一下本篇文章架構出來的開發環境與上述先前文章時做的有哪些不同\n首先，之前的做法是透過不同PHP環境需求開啟多組docker，以及不同MySQL版本需求開啟多個不同版本的MySQL\n而這次，則是透過docker compose 撰寫yml檔案來控制多組docker打開與關閉，另外，本次也更新的laravel以及PHP5.6的image環境\n透過PHP7.3-fpm以及PHP5.6-fpm兩個兩個docker image提供純PHP環境，而伺服器的部分則是統一透過一台NginX提供服務，不再是不同環境開啟多台Apache伺服器，降低系統所需要的資源\n文件結構 # dev_compose +.env +docker-compose.yml nginx conf.d +default.conf +Dockerfile php56 +Dockerfile php73_laravel +Dockerfile dev_compose 資料夾存放docker-compose.yml用來描述docker compose 如何開啟多組docker container，而.env則是用來儲存會需要用的路徑參數\nnginx 資料夾提供一個Dockerfile用來描述nginx image如何build以及一個default.conf作為nginx的設定檔掛載進入container環境中\nphp56以及php73_laravel分別提供php5.6以及php7.3所需要的Dockerfile即可\n另外，MySQL5.6以及MySQL5.7沒有特別描述，只有拉取docker hub上相對應的版本即可，所以不用任何Dockerfile描述檔\n接著，我們一個環境一個環境來看\nphp73_laravel # 主要透過php:7.3-fpm image，再另外安裝其他我們需要的套件進去即可，Dockerfile可以參考如下\nFROM php:7.3-fpm RUN apt-get update \u0026amp;\u0026amp; apt-get install -yq libzip-dev zip git libfreetype6-dev \\ libjpeg62-turbo-dev \\ libmcrypt-dev \\ libssl-dev \\ libmemcached-dev \\ libz-dev \\ zlib1g-dev \\ libsqlite3-dev \\ libxml2-dev \\ libcurl3-dev \\ libedit-dev \\ libpspell-dev \\ libldap2-dev \\ unixodbc-dev \\ libpq-dev \\ wget \\ git \\ vim \\ nano \\ sudo \\ cron \\ net-tools \\ iputils-ping \\ procps \\ nodejs \\ npm # composer RUN curl -sS https://getcomposer.org/installer | php RUN mv composer.phar /usr/local/bin/composer #docker ext RUN docker-php-ext-configure zip --with-libzip RUN docker-php-ext-install mysqli pdo pdo_mysql zip mbstring tokenizer pcntl soap RUN docker-php-ext-configure gd --with-freetype-dir=/usr/include/ --with-jpeg-dir=/usr/include/ \\ \u0026amp;\u0026amp; docker-php-ext-install gd RUN pecl install memcached \\ \u0026amp;\u0026amp; docker-php-ext-enable memcached #timeZone ENV TZ=Asia/Taipei RUN ln -snf /usr/share/zoneinfo/${TZ} /etc/localtime \u0026amp;\u0026amp; echo ${TZ} \u0026gt; /etc/timezone # nvm node npm RUN rm /bin/sh \u0026amp;\u0026amp; ln -s /bin/bash /bin/sh RUN curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.35.2/install.sh | bash ENV NVM_DIR /root/.nvm ENV NODE_VERSION v12.14.0 RUN /bin/sh -c \u0026#34;. ~/.bashrc\u0026#34; RUN . $NVM_DIR/nvm.sh \\ \u0026amp;\u0026amp; nvm install $NODE_VERSION \\ \u0026amp;\u0026amp; nvm alias default $NODE_VERSION \\ \u0026amp;\u0026amp; nvm use default php56 # 與php7.3項同，主要透過php5.6-fpm在安裝其他需要的套件，Dockerfile如下\nFROM php:5.6-fpm RUN apt-get update \u0026amp;\u0026amp; apt-get install -y libzip-dev zip git libfreetype6-dev \\ libjpeg62-turbo-dev \\ libmcrypt-dev \\ libssl-dev \\ libmemcached-dev \\ libz-dev \\ zlib1g-dev \\ libsqlite3-dev \\ libxml2-dev \\ libcurl3-dev \\ libedit-dev \\ libpspell-dev \\ libldap2-dev \\ unixodbc-dev \\ libpq-dev \\ wget \\ vim \\ nano RUN apt-get install -y libzip-dev zip RUN docker-php-ext-configure zip --with-libzip RUN docker-php-ext-install mysqli pdo pdo_mysql mysql zip nginx # nginx相對複雜一些，透過Dockerfile抓取nginx:1.17.7版本，並且安裝vim以及nano\nFROM nginx:1.17.7 RUN apt-get update \u0026amp;\u0026amp; apt-get install -y vim nano default.conf用來描述nginx需要啟動多少個伺服器，以及port號，還有相對應要處理PHP的位置，詳細說明直接參考註解即可\n80port 用於laravel開發，8800port則用於php5.6舊專案的維護，值得一提的是如何在80port中透過不同的專案路徑來瀏覽不同的laravel專安是本次花最多時間鑽研的項目\nserver {#80port伺服器，給laravel專案使用 listen 80 default_server; server_name _; root /var/project/laravel;#nginx的預設根目錄是/var/project/ 我將laravel專案放在預設根目錄的laravel資料夾 index index.html index.htm index.php; charset utf-8; location / { try_files $uri $uri/ /index.php?$query_string; } location ^~ /PROJECT1_NAME/ {#第一個project位置，如果有多個project請複製填寫此區塊並且填入相對其他專案的路徑名稱 : PROJECT1_NAME為專案路徑名稱 alias /var/project/laravel/PROJECT1_NAME/public/;#給定路徑到專案資料匣底下的public資料夾，這樣網址就可以不必打\u0026#34;public\u0026#34; try_files $uri $uri/ @PROJECT1_NAME; location ~\\.php$ { fastcgi_split_path_info ^(.+\\.php)(/.+)$; fastcgi_pass php73_laravel:9000; #php73_laravel為處理php的docker container名稱，這部分會在docker compose 的yml檔中描述出現 fastcgi_index index.php; include fastcgi_params; fastcgi_param SCRIPT_FILENAME /var/project/laravel/PROJECT1_NAME/public/index.php; #把SCRIPT_FILENAME直接設定到專案public資料夾底下的index.php fastcgi_param DB_HOST mysql57; #給定DB_HOST參數為mysql57，mysql57同樣為docker container名稱 } } location @PROJECT1_NAME {#每個專案要獨立設定rewrite路徑，指定到專案資料夾底下的index.php rewrite /PROJECT1_NAME/(.*)$ /PROJECT1_NAME/index.php?/$1 last; } #記錄檔位置，會在docker compose yml描述中將檔案映射到本機資料夾方便查看 access_log /var/log/nginx/access.log error_log /var/log/nginx/error.log; error_page 404 /index.php; sendfile off; location ~\\.php$ { #描述如果非子資料夾下的php檔如何處理，當然還是丟給php73_laraavel fastcgi_split_path_info ^(.+\\.php)(/.+)$; fastcgi_pass php73_laravel:9000; fastcgi_index index.php; include fastcgi_params; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; fastcgi_param DB_HOST mysql57; } location ~ /\\.ht { deny all; } client_max_body_size 120M; } server {#8800port伺服器，保留給php5.6專案使用 listen 8800 default_server; server_name _; root /var/project/php;#伺服器根目錄指定到/var/project/php中 index index.html index.htm index.php; charset utf-8; location / { try_files $uri $uri/ /index.php?$query_string; } access_log /var/log/nginx/access.log; error_log /var/log/nginx/error.log; error_page 404 /index.php; sendfile off; location ~\\.php$ {#php5.6專案，php執行的方式將交由php56 docker container來負責 fastcgi_split_path_info ^(.+\\.php)(/.+)$; fastcgi_pass php56:9000; fastcgi_index index.php; include fastcgi_params; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; fastcgi_param DB_HOST mysql56; } location ~ /\\.ht { deny all; } client_max_body_size 120M; } dev_compose # .env將儲存yml檔案中需要用到的參數\nSTORAGE_PATH=專案資料路徑(我這邊放lravel專案與php5.6專案資料夾的上層) LOG_PATH=掛載錯誤資訊的資料夾 MYSQL_ROOT_PASSWORD=MySQL資料庫預設密碼 docker-compose.yml 將是本次的重點，如下\nversion: \u0026#34;3.3\u0026#34; services: php73_laravel: container_name: php73_laravel build: ../php73_laravel/ restart: unless-stopped volumes: - \u0026#34;${STORAGE_PATH}:/var/project\u0026#34; depends_on: - mysql57 networks: - dev php56: container_name: php56 build: ../php56/ restart: unless-stopped volumes: - \u0026#34;${STORAGE_PATH}:/var/project\u0026#34; depends_on: - mysql56 networks: - dev mysql57: container_name: mysql57 image: mysql:5.7 restart: unless-stopped environment: - MYSQL_ROOT_PASSWORD=${MYSQL_ROOT_PASSWORD} ports: - 3306:3306 volumes: - mysql57-data:/var/lib/mysql networks: - dev mysql56: container_name: mysql56 image: mysql:5.6 restart: unless-stopped environment: - MYSQL_ROOT_PASSWORD=${MYSQL_ROOT_PASSWORD} ports: - 3307:3306 volumes: - mysql56-data:/var/lib/mysql networks: - dev nginx: container_name: nginx build: ../nginx restart: unless-stopped ports: - 80:80 - 8800:8800 volumes: - \u0026#34;${STORAGE_PATH}:/var/project\u0026#34; - \u0026#34;${LOG_PATH}:/var/log/nginx\u0026#34; - ../nginx/conf.d/default.conf:/etc/nginx/conf.d/default.conf networks: - dev networks: dev: volumes: mysql57-data: mysql56-data: 抓重點 :\n這次總共開啟php73_laravel php56 mysql56 mysql57 nginx 等五個container來使用\n其中build參數，將是指定要用哪個路徑底下的Dockerfile來產生container，但是像mysql只要指定image就可以了\nvolumes參數如同docker run 中的-v，無論是php還是nginx都必須將${STORAGE_PATH}指定到/var/project\n而我也將所有container network 設定為dev，讓所有container可以互通\n最後volumes中指定mysql57-data mysql56-data 兩者用於儲存資料庫檔案，避免container關閉後資料遺失\n最後，完成後使用 docker compose up -d 及可以將所有container全部開啟，docker compose down 可以全部關閉\n當然第一次開啟的時候docker會自動透過build所有image\n以上，打完收工！\n","date":"2020年1月20日","externalUrl":null,"permalink":"/2020/01/20/docker-nginx-%E5%A4%9A%E7%AB%99%E5%8F%B0%E5%A4%9A%E7%89%88%E6%9C%ACphp%E9%96%8B%E7%99%BC%E7%92%B0%E5%A2%83/","section":"Posts","summary":"","title":"docker nginx 多站台多版本php開發環境","type":"posts"},{"content":"","date":"2019年12月11日","externalUrl":null,"permalink":"/tags/ubuntu/","section":"Tags","summary":"","title":"Ubuntu","type":"tags"},{"content":"Ubuntu FTP server 架設\n目的 : 設定好一個根目錄讓使用者可以透過FTP上傳檔案到這個資料夾來\n參考資料 :\nhttps://www.itread01.com/p/150850.html\nhttps://www.mobibrw.com/2018/15389\n除此之外，您還可以參考\nubuntu-基礎架站\nubuntu-架站進階分享\n首先，安裝vsftpd\nsudo apt-get update sudo apt-get install vsftpd 如果以前有裝過vsftpd 或者已經移除，如果有發現\u0026quot;/etc/vsftpd.conf\u0026quot;這個檔案不存在的話，請移除vsftpd再重新安裝一次\nsudo apt-get remove --purge vsftpd 接著編輯這個檔案\nsudo nano /etc/vsftpd.conf 找到這幾個地方修改\nwrite_enable=YES local_umask=022 allow_writeable_chroot=YES /*加入以下資訊*/ local_root=/var/www/html/static /*(這是登入的根目錄)*/ pasv_enable=Yes pasv_min_port=40000 pasv_max_port=40100 接著重開vsftpd\nsudo service vsftpd restart 你也可以隨時查看FTP目前的狀態\nservice vsftpd status 接著建立一位不可登入系統的使用者來登入FTP\nsudo useradd -m john -s /usr/sbin/nologin 然後設定密碼\nsudo passwd john 然後編輯這個檔案/etc/shells\nsudo nano /etc/shells 在最後一行加入\n/usr/sbin/nologin 以上，伺服器的部分差不多設定完畢\n最後一個步驟你必須確保防火牆21,40000-40100可以進入即可\n如果遇到檔案權限問題，請確認剛剛\u0026quot;local_root\u0026quot;中的路徑是不是可以正確寫入\n","date":"2019年12月11日","externalUrl":null,"permalink":"/2019/12/11/ubuntu-ftp-server-%E6%9E%B6%E8%A8%AD/","section":"Posts","summary":"","title":"Ubuntu FTP server 架設","type":"posts"},{"content":"本來以為在docker化的環境中，更新OS應該沒什麼狀況才對\n沒想到還是踩了坑，而且滿多人踩的 記錄一下\n參考網址 :\nhttps://stackoverflow.com/questions/58290566/install-ext-zip-for-mac\n透過composer安裝套件時出現\nThe requested PHP extension ext-zip * is missing from your system. 錯誤是php-zip extension沒有安裝，你可以透過\nphp -m 看一下是不是沒有zip套件\n解法如下 :\nbrew update brew install php@7.3 brew link php@7.3 但是最後一步會出錯，提示需要\u0026ndash;force，那就\u0026ndash;force吧\nbrew link php@7.3 --force 接著還是錯XD\nError: Could not symlink sbin/php-fpm\u0026#34; \u0026#34;/usr/local/sbin is not writable. 還好也是有人遇過了，透過\nbrew doctor 指令，會提示兩條指令，建立一個資料夾，並且設定好權限(基本複製貼上就行了)\n之後就可以順利使用composer了\n","date":"2019年12月2日","externalUrl":null,"permalink":"/2019/12/02/macos-10-15-catalina-%E6%9B%B4%E6%96%B0%E5%BE%8Cphp%E7%9A%84%E4%B8%80%E4%BA%9B%E5%9D%91/","section":"Posts","summary":"","title":"MacOS 10.15 catalina 更新後PHP的一些坑","type":"posts"},{"content":"Hi, 你好\n\u0026lt;!\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\n我是一個資訊業的網站工程是，在這行10年了，也許你有聽過網路上的人說學學寫程式以後的工作比較好找之類的話，我希望你如果對資訊業有憧憬，可以聽聽我的經驗。\n首先，我換過三份工作，第一個是學校的研究助理就跟打工一樣輕鬆零負擔，而且在學校整個跟學生一樣，第二份工作在專案公司，所謂專案公司就是負責外包其他公司的網站、軟體等等的，負責製作，在這邊因為時間壓力可以用超快的速度學到非常多東西，但是相對的也是學不深，很廣但是深度不夠，目前的工作是在廣告行銷公司當資訊人員，負責ERP的開發工作，所謂ERP就是公司內大小事的作業流程，就像在學校午餐大家打飯，依序夾菜、添飯、舀湯的流程制定工作，其實用上資訊專業的層面並不多，在這行業跟個部門主管協調、統整意見佔了很大半的工作時間。\n資訊科技業還有分軟體或硬體業，就我所知軟體業大多集中在台北或台中(據說台北薪資相對高些)，而硬體比較多在新竹(眾所皆知的竹科園區)，我本身是軟體業，軟體業可以說是看不到摸不著，很需要想像空間才能揣摩虛擬畫面中需要長成什麼樣子，而硬體業我本身沒有待過無法分享什麼。\n如果你還不知道未來要做什麼，我只能跟你說資訊這行業不會讓你餓死，絕對有工作而且薪資不會太低，缺點也是相對的就是餓不死而已，如果無法自己求變求生存那麼薪資將永遠都是那樣。\n而如果你對資訊科技業有很大憧憬，掃描右邊的QR Code吧，我相信你會的\n我把寶藏都埋在那裡了\n\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026ndash;\u0026gt;\n如果你透過網址或者QR Code來到這一頁，恭喜你，下面是想要給你參考的話\n首先，不要輕信網路上網友跟你說來學寫程式吧，之後工作會有多輕鬆這種話，學寫程式不難，要寫好一支程式也不難，但是如果你不喜歡無中生有也不喜歡幫別人解決問題，那就勸你別踏入這一行，也不要像我一樣覺得跟人溝通比較麻煩轉而進入資訊領域，感覺上電腦比較好溝通，那麼，你就錯了。\n在資訊這行業，不想與人溝通那你就永遠是最底層的碼農(整天寫著程式碼的農夫)，如果你想要像電視上科技公司受訪的主管那樣，那你就必須學會管理、溝通、協調、掌控與佈局，這些都不是學校會教給你的，也不是資訊類書籍上可以看到的。\n簡單來說，如果你想踏入資訊業發光發熱，那麼程式能力只是基礎中的基礎而已，並不像巨匠電腦或者聯成電腦的廣告那樣，告訴你來這邊補習協助考到證照後你就前途無量，你要學會的是如何跟客戶溝通、跟主管協調、掌握專案進度、佈局專案整體架構，這些都建立在紮實的程式基礎上。\n喔對了，如果你覺得可以一招打天下那就又大錯特錯了，大家都知道資訊爆炸的年代產品更新率有多快吧?手機是不是一年一年的變快又變大了，那麼APP的開發工程師就必須每年跟著最新的規格、最新的架構、最新的通訊協定往前走，身為一個工程師每天吸收新知識也是必須要，學無止盡活到老學到老這句話真的確確實實的發生在我們身上，雖然我們沒有考試的壓力但是只要比別人慢了那麼將會被公司淘汰，而且沒有人會教你該學什麼，這都要自己很主動的去挖掘去學習。\n最後，如果你還沒打退堂鼓，還是覺得想要朝資訊業發展的話，那麼恭喜你身懷多絕技也是必備的，這年頭許多功能要的都是跨領域的能力，不要只是會寫程式而已，你可以嘗試找到另一個你有興趣的主題多往那邊邁進，別忘了我上面講的程式能力只是基礎中的基礎，如何拿來運用端看個人創造力的展現，像是這幾年比較熱門的AI人工智慧、大數據、區塊鏈，能夠運用這些技術創造需求的人，未來肯定是業界膜拜的神人，而你也要加入這個資訊洪流嗎？ 隨時歡迎你在部落格上聯絡我，我可以跟你聊更多！\n","date":"2019年12月2日","externalUrl":null,"permalink":"/%E7%B5%A6%E5%90%91%E4%B8%8A%E5%9C%8B%E4%B8%AD/","section":"Alvin Chen Club","summary":"","title":"給向上國中","type":"page"},{"content":"會有這篇文章是想要解決一個問題 : 在網站上想要上傳影片保存後在線上播放該如何處理?\n本篇使用Laravel 5.8並且將檔案儲存在Google Cloud Storage GCS\n如果你還沒有使用過GCS請先閱讀\nLaravel 上傳檔案到Google Cloud Plateform Storage GCS\n前言 : # 目前最有名氣的大概就是Youtube，可是總不能為了幾支影片複製手刻一個吧？\n所以目標朝向把影片轉存到各大影音網站上 像是 Youtube Vimeo Dailymotion 等這類型有提供API的網站\n下面先列出幾個已經嘗試過的平台給大家參考不用走冤枉路\n1.Youtube :\nYoutube應該會是大家想到的第一個平台了，雖說早已預計要付費來儲存影片，但是Youtube API 讓我非常失落\n雖說直接在GCP平台上開啟Youtube API Key就可以使用了，但是每天給予的額度大概只夠讓你上傳三部影片\n是的你沒有看錯就是大概三部左右，有興趣的可以到這裡按按計算機\nhttps://developers.google.com/youtube/v3/determine_quota_cost\n上傳一部影片就要花費1600點，而每日的額度只有5000點而已，實在不夠用\n官方有提供超額申請，只是要填超多表格填完後等待好幾天都沒有下文就讓我放棄了\n2.VImeo :\nVImeo提供的API使用方便程度真的不如Youtube，而必須要說的是\u0026hellip;.我正在嘗試中帳號就已經被鎖定Orz\u0026hellip;.\n並沒有超額，我大概只是傳了四部相同的影片大約5秒鐘的測試片，就被鎖帳號了，所以Vimeo就交給其他勇者去嘗試\n3.Dailymotion :\nDailymotion 同樣有API使用的上限，詳細可以自行參考官方連結\nhttps://developer.dailymotion.com/api/#guidelines\nDailymotion的問題在於，沒有付費機制，也沒有明確找到超額後該如何處理? 單價怎麼計算等問題，因此還是放棄他\n以免之後衍伸其他問題\n4.Flickr :\nFlickr也是可以上傳影片的，而且免費額度就可以使用1000部，之後也只要購買Pro就可以持續上傳，但是Flickr的問題來自於兩點\n1是API真的不好用 2是影片要嵌入網頁必須公開且可以看到同相本中其他的影片\n基於隱私權的問題，我還是決定放棄Flickr\n最終，我在api.video 以及MUX 中做選擇，兩者都有完整的SDK提供以及API文件、計價方式等\napi.video可以完全免費開始，累積上傳1小時的影片才收你5美元，聽起來很吸引人\n缺點是最高只有720P，而且要轉付費全功能的話門檻高達每個月390美元，這點就有點令人擔憂\n最終的最終，我選擇了使用MUX，先說說MUX有哪些優勢\n1 API文件完整，且有composer pacakge支援的SDK\n2 API使用邏輯跟其他站比起來較為先進\n3 完整的管理後台可以知道目前的使用額度以及管理每隻影片\n4 沒有一個前台可以連結到同帳號上傳的其他影片\n5 收費機制明確，且剛註冊就給你20塊美金的使用額度，用完才開始扣錢\n要說缺點大概是服務還很新，SDK版本超新去年才上線，希望不要很快就倒閉才好\n註冊使用 : # 其實透過官方的文件就已經很清楚的說明該如何上傳影片到MUX上，下面我整理本次在Laravel底下用到的項目\n首先，註冊完成後填入信用卡升級開通使用權限到後台左選單找到 Setting \u0026gt; Access Token，設定好一組token用於上傳認證\n點選Generate new token來產生一組新的token\nenvironment 可以選擇 development 或者 production，就依照使用環境來選擇\npermission中 Mux Video勾選 Full Access 才能夠上傳並且編輯影片\nAccess token name就隨意取了\n完成後請複製 Token ID 以及 Token Secret 後續要貼到程式碼中\n安裝SDK # 透過composer很容易\ncomposer require muxinc/mux-php 接著在Controller中引入SDK(會用到GuzzleHttp)\nuse MuxPhp; use GuzzleHttp; 接下來就可以依照文件中的方法來上傳第一支影片了\n// Authentication Setup $config = MuxPhp\\Configuration::getDefaultConfiguration() -\u0026gt;setUsername(\u0026#39;YOUR TOKEN ID\u0026#39;) -\u0026gt;setPassword(\u0026#39;YOUR TOKEN SECRET\u0026#39;); // API Client Initialization $assetsApi = new MuxPhp\\Api\\AssetsApi( new GuzzleHttp\\Client(), $config ); // Create Asset Request $input = new MuxPhp\\Models\\InputSettings([\u0026#34;url\u0026#34; =\u0026gt; \u0026#39;YOUR VIDEO URL\u0026#39;]); $createAssetRequest = new MuxPhp\\Models\\CreateAssetRequest([\u0026#34;input\u0026#34; =\u0026gt; $input, \u0026#34;playback_policy\u0026#34; =\u0026gt; [MuxPhp\\Models\\PlaybackPolicy::PUBLIC_PLAYBACK_POLICY], \u0026#34;mp4_support\u0026#34; =\u0026gt; \u0026#34;standard\u0026#34; ]); // Ingest $result = $assetsApi-\u0026gt;createAsset($createAssetRequest); // Print URL print \u0026#34;Playback URL: https://stream.mux.com/\u0026#34; . $result-\u0026gt;getData()-\u0026gt;getPlaybackIds()[0]-\u0026gt;getId() . \u0026#34;.m3u8\\n\u0026#34;; 有幾個值得說明的地方\n1 YOUR TOKEN ID / YOUR TOKEN SECRET 就貼上上一個步驟申請完成的字串\n2 VIDEO URL 不是檔案路徑，而是URL，也就是可以直接填入Storage中的公開連結即可，也就是可以使用Laravel的Storage::url()即可\n3 CreateAssetRequest中我多增加了\u0026quot;mp4_support\u0026quot; =\u0026gt; \u0026ldquo;standard\u0026quot;參數，目的是讓MUX產生.mp4檔，目前測試mp4檔才能讓手機瀏覽器正確播放影片\n4 最後一行會印出播放連結，上傳的影片會有兩個ID，一個是影片ID一個是播放ID，大家可自行dd $result結果出來看看\n如何播放影片? # 下面是官方提供的web playback範例\n\u0026lt;video id=\u0026#34;myVideo\u0026#34; controls\u0026gt;\u0026lt;/video\u0026gt; \u0026lt;!-- Use HLS.js to support the HLS format in browsers. --\u0026gt; \u0026lt;script src=\u0026#34;https://cdn.jsdelivr.net/npm/hls.js@0.8.2\u0026#34;\u0026gt;\u0026lt;/script\u0026gt; \u0026lt;script\u0026gt; (function(){ // Replace with your asset\u0026#39;s playback ID var playbackId = \u0026#34;YOUR_PLAYBACK_ID\u0026#34;; var url = \u0026#34;https://stream.mux.com/\u0026#34;+playbackId+\u0026#34;.m3u8\u0026#34;; // HLS.js-specific setup code if (Hls.isSupported()) { var video = document.getElementById(\u0026#34;myVideo\u0026#34;); var hls = new Hls(); hls.loadSource(url); hls.attachMedia(video); } })(); \u0026lt;/script\u0026gt; 其實只要把剛剛拿到的playback id 放進去就能夠順利播放了，但是根據官方文件HLS Playback的支援列表\nhttps://docs.mux.com/docs/hls-playback-support\n居然沒有mobile chrome !!!!，測試後也真的有問題\n所以，我才在上傳的參數中加入了mp4 sport\n根據官方文件\nhttps://docs.mux.com/docs/mp4-support\nmp4檔案需要一些時間產生，而產生後就可以透過下面格式的連結直接用瀏覽器播放\nhttps://stream.mux.com/{PLAYBACK_ID}/low.mp4 https://stream.mux.com/{PLAYBACK_ID}/medium.mp4 https://stream.mux.com/{PLAYBACK_ID}/hight.mp4 而系統會根據解析度自動產生對應的連結，雖然麻煩了點但是可以用@get_headers看看哪個連結可以使用就拿來播放了\n計費方式 # 其實MUX還支援live stream，但是本次沒用上就不提了，影片的範圍內\n所有影片累積1分鐘就會收取0.05美元的encoding費用，\n所有影片累積起來1分鐘的影片每個月會收取0.003美元，\n而播放影片每分鐘則收取0.0013美元\n這樣的計價方式我認為還算合理，用多少就收多少，而且註冊就送20美元的使用額度\n頁面上也有計算機可以簡易試算一下每個月大約會花多少錢\n而且在後台 Setting \u0026gt; Billing 中也可以直接看到目前的使用量被計費了多少\n以上，本次嘗試處理影片上傳提供服務的API串接使用方式\n提供給需要的人參考\n","date":"2019年11月5日","externalUrl":null,"permalink":"/2019/11/05/laravel-video-upload-streaming-%E5%BD%B1%E7%89%87%E4%B8%8A%E5%82%B3%E4%BB%A5%E5%8F%8A%E7%B7%A8%E7%A2%BC%E8%A7%A3%E6%B1%BA%E6%96%B9%E6%A1%88-mux/","section":"Posts","summary":"","title":"Laravel video upload streaming 影片上傳以及編碼解決方案 MUX","type":"posts"},{"content":"前陣子才介紹過透過puppeteer來將網頁儲存成PDF檔案，如果還沒看過可以參考\npuppeteer docker image 中文網頁產生PDF\n本篇要更新的部分有兩個\n透過GoogleChromeLabs 提供的puppeteer as a service docker image ，可以不必再處理中文問題 網址直接輸出PDF的列印格式通常不會太漂亮的解決方案 參考連結\nhttps://github.com/GoogleChromeLabs/pptraas.com\nhttps://hub.docker.com/r/minuux/pptraas.com\n首先pptraas.com直接可以支援中文輸出，沒有語系上的問題\ndocker run -d -p 9999:8080 minuux/pptraas.com 完成後就可以在9999 port上使用，經過測試無論是直接輸出PDF還是畫面截圖，皆可以正確顯示中文字\n接著，該如何完整個將畫面完整的輸出到PDF上而不會跑版或者背景、圖片不見之類的狀況呢?\n這次，我會使用到pptraas.com所提供的兩個方法\nRender page as a PNG Render page as PDF 因為puppeteer所提供的page to pdf 方法是透過chrome的列印成PDF功能來實現，所以有很多狀況會變成這樣子，我們用一個例子來看\n原本的畫面是這樣子\nmamigogo 而我們直接從chrome按下列印，看到的預覽畫面就是puppeteer所產生的PDF檔\n會變成這樣子\nmamigogo 雖然可以很快的產生PDF檔，但是這樣子的內容格式根本不是我要的，接下來就來想辦法把格式保留下來產生PDF吧\n首先！概念上是這樣子的，要保留樣式我們透過截圖的方式把圖片存下來，最後再將各分頁多張圖片合併輸出成PDF，當然你也可以直接截圖整頁，不做紙張分頁也是可以完整保留內容樣式的\n我們從pptraas.com提供的範例中知道如何將url轉換成截圖\nhttps://pptraas.com/screenshot?url=https://developers.google.com\u0026amp;size=400,400 重點有兩個\nsize=650,850 這樣截下來的圖剛好可以塞進A4大小中，如果你有其他紙張大小需求也可以自行調整 網址回傳的內容是PNG圖檔內容，所以我將圖片內容透過base64_encode存下來放到cache 下面是一段laravel的範例\n$url = env(\u0026#39;PPTRAAS_URL\u0026#39;).\u0026#39;screenshot?size=650,850\u0026amp;url=THE_URL_YOU_WANT_TO_PRINT\u0026#39;; $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); $output = curl_exec($ch); curl_close($ch); $images = base64_encode($output); $uuid = Str::uuid()-\u0026gt;toString(); //把base64 image放到快取中，並設定life time只有一分鐘 Cache::put(\u0026#39;pdf_images_\u0026#39;.$uuid, $images, now()-\u0026gt;addMinutes(1)); 範例中，我把pptraas.com docker image 的url放在env中方便使用，並且將網址截圖後放到cache中\n接著你需要另一個URL來產生剛剛截好圖的PDF，而這個頁面很簡單，只需要將cache中的圖檔拿出來放好就行了\n以下是laravel blade的範例\n\u0026lt;style\u0026gt; @media print { .pagebreak { page-break-before: always; } /* page-break-after works, as well */ } \u0026lt;/style\u0026gt; @foreach($images as $k =\u0026gt;$v) \u0026lt;img src=\u0026#34;data:image/png;base64,{{$v}}\u0026#34; style=\u0026#34;width:800px;\u0026#34;\u0026gt; @if($k != count($images)-1) \u0026lt;div class=\u0026#34;pagebreak\u0026#34;\u0026gt; \u0026lt;/div\u0026gt; @endif @endforeach 範例中呈現的是多張images轉存成一份PDF的概念，因此在每一張圖的最後加上\u0026lt;div class=\u0026ldquo;pagebreak\u0026rdquo;\u0026gt; \u0026lt;/div\u0026gt;來達成換頁\n但是最後一張圖就不需要再加入換頁了\n最終，這個頁面會把你產生的base64圖檔全部排列出來，然後我們就可以透過這個網址來產生PDF了\nhttps://pptraas.com/pdf?url=THE_URL_ABOVE 透過上面的網址一樣使用curl來呼叫，你可以得到一個PDF內容\n接著只要把內容return 回去就可以下載啦\n$fileName = \u0026#39;THE PDF NAME YOU WANT\u0026#39;; return response($output, 200, [ \u0026#39;Content-Type\u0026#39; =\u0026gt; \u0026#39;application/json\u0026#39;, \u0026#39;Content-Disposition\u0026#39; =\u0026gt; \u0026#39;attachment; filename=\u0026#34;\u0026#39;.$fileName.\u0026#39;.pdf\u0026#34;\u0026#39;, ]); 範例中，$output就是上面網址產生的PDF內容\n以上，就可以把URL內容輕鬆轉換成PDF了\n","date":"2019年10月16日","externalUrl":null,"permalink":"/2019/10/16/html-to-pdf-%E8%BC%B8%E5%87%BA%E6%9C%80%E6%BC%82%E4%BA%AE%E7%9A%84%E5%AE%8C%E6%95%B4pdf/","section":"Posts","summary":"","title":"html to pdf 輸出最漂亮的完整PDF","type":"posts"},{"content":"2019/10/17 :補充更新\n參考這篇就可以了，pptraas.com可以處理中文問題\nhtml to pdf 輸出最漂亮的完整PDF\n原則上，\nHTML 產出 PDF的方法很多種，最出名的大概就是wkhtmltopdf了\n今天要介紹的是另一種我認為目前最簡便的方式 :\n透過Chrome Headless 模擬將網頁列印成PDF 透過現成的docker image (puppeteer)啟動Chrome Headless服務 如果不曉得什麼事puppeteer可以先參考\nhttps://ithelp.ithome.com.tw/articles/10199544\n其實只要依照下方參考文件直接啟動puppeteer docker image就可以產出PDF了，本篇的重點在於\u0026quot;中文\u0026quot;\n需要在套件中多安裝中文語言套件才可以正確將中文網頁轉換成PDF檔案，否則中文字都會變成亂碼\n如果你對docker還不熟悉，可以先閱讀以下文章\nMAC安裝Docker\ndocker 小工具 : Kitematic 與 懶人指令包\n用docker架FTP vsftpd on docker\n參考文件\nhttps://hub.docker.com/r/zenato/puppeteer-renderer\nhttps://github.com/zenato/puppeteer-renderer\nhttps://github.com/GoogleChrome/puppeteer/blob/master/docs/troubleshooting.md\n如何加入中文字型套件? # 這就是今天的重點了，首先到原作的github上，將整個專案下載下來\n你會找到一個Dockerfile 檔案，透過編輯器來編輯他\nFROM zenato/puppeteer USER root COPY . /app RUN cd /app \u0026amp;\u0026amp; npm install --quiet EXPOSE 3000 WORKDIR /app CMD npm run start 我們必須要將安裝中文字型檔的指令加入Dockerfile中，並且重新打包image\n根據puppeteer github上issue的討論需要加入以下指令\nRUN wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add - \\ \u0026amp;\u0026amp; sh -c \u0026#39;echo \u0026#34;deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main\u0026#34; \u0026gt;\u0026gt; /etc/apt/sources.list.d/google.list\u0026#39; \\ \u0026amp;\u0026amp; apt-get update \\ \u0026amp;\u0026amp; apt-get install -y google-chrome-unstable fonts-ipafont-gothic fonts-wqy-zenhei fonts-thai-tlwg fonts-kacst fonts-freefont-ttf \\ --no-install-recommends \\ \u0026amp;\u0026amp; rm -rf /var/lib/apt/lists/* 因此編輯完成的Dockerfile將變成\nFROM zenato/puppeteer USER root RUN wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add - \\ \u0026amp;\u0026amp; sh -c \u0026#39;echo \u0026#34;deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main\u0026#34; \u0026gt;\u0026gt; /etc/apt/sources.list.d/google.list\u0026#39; \\ \u0026amp;\u0026amp; apt-get update \\ \u0026amp;\u0026amp; apt-get install -y google-chrome-unstable fonts-ipafont-gothic fonts-wqy-zenhei fonts-thai-tlwg fonts-kacst fonts-freefont-ttf \\ --no-install-recommends \\ \u0026amp;\u0026amp; rm -rf /var/lib/apt/lists/* COPY . /app RUN cd /app \u0026amp;\u0026amp; npm install --quiet EXPOSE 3000 WORKDIR /app CMD npm run start 接著重新打包docker image\ndocker build -t IMAGENAME . --no-cache 以上，完成\n最後稍微說明一下如何使用\n透過docker run 啟動image\ndocker run -d --name renderer -p 9999:3000 IMAGENAME 當然，IMAGENAME就是剛剛build時輸入的，將著等待容器啟動後就可以透過URL來轉換PDF\nhttp://localhost:9999/?url=https://www.google.com 如果將上面的url輸入瀏覽器有看到網頁的話代表docker正確執行，後面接上參數就可以依照指令執行\nName Required Value Description Usage url yes Target URL http://puppeteer-renderer?url=http://www.google.com type pdf or screenshot Rendering another type. http://puppeteer-renderer?url=http://www.google.com\u0026amp;type=pdf (Extra options) Extra options (see puppeteer API doc) http://puppeteer-renderer?url=http://www.google.com\u0026amp;type=pdf\u0026amp;scale=2 範例1 :\nhttp://localhost:9999/?url=https://www.google.com\u0026amp;type=screenshot 範例2 :\nhttp://localhost:9999/?url=https://www.google.com\u0026amp;type=pdf\u0026amp;printBackground=true option可以接哪些參數可以參考 puppeteer API doc\n","date":"2019年9月25日","externalUrl":null,"permalink":"/2019/09/25/puppeteer-docker-image-%E4%B8%AD%E6%96%87%E7%B6%B2%E9%A0%81%E7%94%A2%E7%94%9Fpdf/","section":"Posts","summary":"","title":"puppeteer docker image 中文網頁產生PDF","type":"posts"},{"content":"作品名稱 : ubuntu18.04-php7.3-apache-for-laravel\n開發年份 : 2019\ngitHub連結 : https://github.com/virtualorz/ubuntu18.04-php7.3-apache-for-laravel\n內容簡介 :\n參考自 : https://github.com/t3kit/ubuntu18.04-php7.2-apache\n修改php版本為7.3版，並移除不需要的套件\n加入000-default.conf覆蓋預設config file\n建立image方式 :\n如果你對Docker不熟悉，可以先參考下面連結\nMAC安裝Docker\nlaradock : laravel專案容器化開發實作\ndocker 容器化 : PHP5.6專案容器化開發實作\n先clone Dockerfile 以及000-default.conf\ngit clone https://github.com/t3kit/ubuntu18.04-php7.2-apache.git Docker image build\ncd ubuntu18.04-php7.2-apache.git docker build -t=\u0026#34;YOUR IMAGE NAME\u0026#34; --no-cache . 等待大約10分鐘build完後就可以用image來啟用Laravel網站啦\nDocker run -d -p 80:80 --name CONTAINTER_NAME -v PATH_TO_LARAVEL_PROJECCT:/var/www/html -v PATH_TO_PHP_LOG_DIR:/var/log/apache2 YOUR_IMAGE_NAME 把大寫字替換成你要的參數就可以了\n","date":"2019年9月10日","externalUrl":null,"permalink":"/2019/09/10/%E4%BD%9C%E5%93%81docker-imageubuntu18-04-php7-3-apache-for-laravel/","section":"Posts","summary":"","title":"[作品][docker image]ubuntu18.04-php7.3-apache-for-laravel","type":"posts"},{"content":"","date":"2019年9月10日","externalUrl":null,"permalink":"/categories/laravel-%E4%BD%9C%E5%93%81/","section":"Categories","summary":"","title":"Laravel 作品","type":"categories"},{"content":"套件名稱 : VIrtualorz/PdfSignature\n開發年份 : 2019\ngitHub連結 :https://github.com/virtualorz/pdfSignature\n內容簡介 :\nPdfSignature是透過FPDI套件來簽名檔加入PDF檔案的laravel套件，只需要插入圖檔路徑以及座標位置就可以完成\nLaravel：95% 安裝方式 :\ncomposer require virtualorz/pdfSignature 編輯 config/app.php\n\u0026#39;providers\u0026#39; =\u0026gt; [ ... Virtualorz\\PdfSignature\\PdfSignatureServiceProvider::class ] \u0026#39;aliases\u0026#39; =\u0026gt; [ ... \u0026#39;PdfSignature\u0026#39; =\u0026gt; Virtualorz\\PdfSignature\\Facades\\PdfSignature::class, ] 方法簡介 :\nsign($pdf_path, $signature_path, $signature_location, $download_file_name)\n$pdf_path : 要簽名的pdf檔實際路徑，\n$signature_path : 簽名檔實際路徑\n$signature_location : 簽名位置陣列，範例\n[ [ \u0026#39;page\u0026#39; =\u0026gt; 1, \u0026#39;x\u0026#39; =\u0026gt; 15, \u0026#39;Y\u0026#39; =\u0026gt; 210 ], ] 可以透過陣列指定要那一頁的x,y座標插入簽名檔\n","date":"2019年9月6日","externalUrl":null,"permalink":"/2019/09/06/%E4%BD%9C%E5%93%81laravel-package-pdfsignature-pdf%E5%8A%A0%E5%85%A5%E7%B0%BD%E5%90%8D%E6%AA%94%E8%B6%85%E7%B0%A1%E5%96%AE/","section":"Posts","summary":"","title":"[作品][Laravel Package] PdfSignature : PDF加入簽名檔超簡單","type":"posts"},{"content":"","date":"2019年8月28日","externalUrl":null,"permalink":"/tags/gcs/","section":"Tags","summary":"","title":"Gcs","type":"tags"},{"content":"參考網址 :\nhttps://github.com/Superbalist/laravel-google-cloud-storage\n基礎概念\n如果檔案是要在網頁上直接使用可以將權限直接設為公開 透過laravel-google-cloud-storage套件來達成 需要在GCP上申請並下載驗證金鑰 安裝laravel-google-cloud-storage # composer require superbalist/laravel-google-cloud-storage 如果是Laravel 5.4以下的版本請在app/config.php 中加入以下，否則不需要\n\u0026#39;providers\u0026#39; =\u0026gt; [ // ... Superbalist\\LaravelGoogleCloudStorage\\GoogleCloudStorageServiceProvider::class, ] 接著在config/filesystems.php中加入以下\n\u0026#39;gcs\u0026#39; =\u0026gt; [ \u0026#39;driver\u0026#39; =\u0026gt; \u0026#39;gcs\u0026#39;, \u0026#39;project_id\u0026#39; =\u0026gt; env(\u0026#39;GOOGLE_CLOUD_PROJECT_ID\u0026#39;, \u0026#39;your-project-id\u0026#39;), //GCP上的 project id \u0026#39;key_file\u0026#39; =\u0026gt; env(\u0026#39;GOOGLE_CLOUD_KEY_FILE\u0026#39;, null), //金鑰json檔絕對路徑 \u0026#39;bucket\u0026#39; =\u0026gt; env(\u0026#39;GOOGLE_CLOUD_STORAGE_BUCKET\u0026#39;, \u0026#39;your-bucket\u0026#39;), //GCS上要使用的bucket \u0026#39;path_prefix\u0026#39; =\u0026gt; env(\u0026#39;GOOGLE_CLOUD_STORAGE_PATH_PREFIX\u0026#39;, null), // optional: /default/path/to/apply/in/bucket 目前看起來不需要填寫 \u0026#39;storage_api_uri\u0026#39; =\u0026gt; env(\u0026#39;GOOGLE_CLOUD_STORAGE_API_URI\u0026#39;, null), // see: Public URLs below 目前看起來不需要填寫 \u0026#39;visibility\u0026#39; =\u0026gt; \u0026#39;public\u0026#39;, // optional: public|private ], 2022/04/07 更新：\nLaravel 9 以後版本請改用一下package\nhttps://github.com/spatie/laravel-google-cloud-storage\ncomposer require spatie/laravel-google-cloud-storage config/filesystems.php中加入以下\n\u0026#39;gcs\u0026#39; =\u0026gt; [ \u0026#39;driver\u0026#39; =\u0026gt; \u0026#39;gcs\u0026#39;, \u0026#39;key_file_path\u0026#39; =\u0026gt; env(\u0026#39;GOOGLE_CLOUD_KEY_FILE\u0026#39;, null), // optional: /path/to/service-account.json \u0026#39;key_file\u0026#39; =\u0026gt; [], // optional: Array of data that substitutes the .json file (see below) \u0026#39;project_id\u0026#39; =\u0026gt; env(\u0026#39;GOOGLE_CLOUD_PROJECT_ID\u0026#39;, \u0026#39;your-project-id\u0026#39;), // optional: is included in key file \u0026#39;bucket\u0026#39; =\u0026gt; env(\u0026#39;GOOGLE_CLOUD_STORAGE_BUCKET\u0026#39;, \u0026#39;your-bucket\u0026#39;), \u0026#39;path_prefix\u0026#39; =\u0026gt; env(\u0026#39;GOOGLE_CLOUD_STORAGE_PATH_PREFIX\u0026#39;, \u0026#39;\u0026#39;), // optional: /default/path/to/apply/in/bucket \u0026#39;apiEndpoint\u0026#39; =\u0026gt; env(\u0026#39;GOOGLE_CLOUD_STORAGE_API_URI\u0026#39;, null), // see: Public URLs below \u0026#39;visibility\u0026#39; =\u0026gt; \u0026#39;public\u0026#39;, // optional: public|private \u0026#39;metadata\u0026#39; =\u0026gt; [\u0026#39;cacheControl\u0026#39;=\u0026gt; \u0026#39;public,max-age=86400\u0026#39;], // optional: default metadata ], 其餘設定大同小異\n取得GCP存取金鑰 # 1.API和服務 \u0026gt; 憑證\ngcs 2.建立憑證 \u0026gt; 服務帳戶金鑰\ngcs 3.選擇新增服務帳戶\ngcs 4.填寫自訂義服務名稱，角色選擇為 : 儲存空間 \u0026gt; 儲存空間管理員\ngcs 點選\u0026quot;建立\u0026quot;後會下載一個JSON檔 gcs 將此JSON檔放到專的中的Storage目錄下，並且將絕對路徑填入env中，舉例 GOOGLE_CLOUD_KEY_FILE=/var/www/project/storage/test-5a1f0d2e5287.json 7.最後只要將env中的 FILESYSTEM_DRIVE修改為 gcs 就可以使用Storage物件來存取gcs上的檔案了\n最後，如何把GCS bucket設定為公開 # 1.Storager \u0026gt; 瀏覽器\ngcs public 2.在你要設定為公開的bucket右側點選功能鍵 \u0026gt; 編輯bucket權限\ngcs public 權限頁籤選擇\u0026quot;新增成員\u0026quot; gcs public 4.新增成員欄位填入allUsers(會自動帶出)，角色選擇 :儲存空間 \u0026gt; 儲存空間檢視者\ngcs public 按下儲存後就完成了\n","date":"2019年8月28日","externalUrl":null,"permalink":"/2019/08/28/laravel-%E4%B8%8A%E5%82%B3%E6%AA%94%E6%A1%88%E5%88%B0google-cloud-plateform-storage-gcs/","section":"Posts","summary":"","title":"Laravel 上傳檔案到Google Cloud Plateform Storage GCS","type":"posts"},{"content":"","date":"2019年8月14日","externalUrl":null,"permalink":"/tags/google-cloud-storage/","section":"Tags","summary":"","title":"Google Cloud Storage","type":"tags"},{"content":"Google Cloud Storage 設定支援 CROS\n其實方法並不難，只是對於GCS不熟悉造成找了很多資料\n參考連結 :\nhttps://cloud.google.com/storage/docs/configuring-cors?hl=zh-tw\nhttps://coder.tw/?p=6985\n首先第一個步驟，CROS並沒有辦法GCP後台直接操作，必須要先安裝gsutil工具\n安裝步驟可以參考 : https://cloud.google.com/storage/docs/gsutil_install\n安裝步驟完成後接著開始設定storage cros\n先準備一個cross-json-file.json 檔案，存檔在 Documents資料夾中 [ { \u0026#34;origin\u0026#34;: [\u0026#34;*\u0026#34;], \u0026#34;responseHeader\u0026#34;: [\u0026#34;Content-Type\u0026#34;], \u0026#34;method\u0026#34;: [\u0026#34;GET\u0026#34;, \u0026#34;HEAD\u0026#34;, \u0026#34;DELETE\u0026#34;], \u0026#34;maxAgeSeconds\u0026#34;: 3600 } ] 其中，origin的部分是重點\norigin陣列請填入要可以cros存取GCS的domain，範例中填入*則可以允許所有domain存取\nconsole切換到Documents資料夾後，透過以下指令設定CROS gsutil cors set cors-json-file.json gs://example-bucket 其中example-bucket就是你要設定可以CROS存取的bucket名稱\n其實這樣就已經完成，最後你可以透過下面的指令來確認CROS是否已經設定完成\ngsutil cors get gs://example-bucket 以上，打完收工！\n","date":"2019年8月14日","externalUrl":null,"permalink":"/2019/08/14/google-cloud-storage-%E8%A8%AD%E5%AE%9A%E6%94%AF%E6%8F%B4-cros/","section":"Posts","summary":"","title":"Google Cloud Storage 設定支援 CROS","type":"posts"},{"content":"套件名稱 : VIrtualorz/Every8DSMS\n開發年份 : 2019\ngitHub連結 : https://github.com/virtualorz/every8DSMS\n內容簡介 :\nEvery8DSMS將every8d 提供的web api 透過composer封裝成laravel可以使用的套件，方便引入套件後可以直接透過一行程式碼就將簡訊送出，並且提供餘額查詢功能\nLaravel：95% 安裝方式 :\ncomposer require virtualorz/every8DSMS 編輯 config/app.php\n\u0026#39;providers\u0026#39; =\u0026gt; [ ... Virtualorz\\Every8DSMS\\Every8DSMSServiceProvider::class ] \u0026#39;aliases\u0026#39; =\u0026gt; [ ... \u0026#39;ActionLog\u0026#39; =\u0026gt; Virtualorz\\Every8DSMS\\Facades\\Every8DSMS::class, ] 發布資料 :\nphp artisan vendor:publish --provider=\u0026#34;Virtualorz\\Every8DSMS\\Every8DSMSServiceProvider\u0026#34; 編輯Config :\n請編輯config/every8DSMS.php檔案，account是你在every8d申請的帳號，password是你在every8d登入的密碼，請記得不要修改\u0026quot;Api-url\u0026quot;\n方法簡介 :\n1.send($message , $phone , $subject)\n將$message內容送到$phone號碼手機中，手機號碼必須是09XX開頭，支援多隻號碼可以這樣填寫 \u0026ldquo;0933039384,0988765432\u0026rdquo;，$subject不會隨簡訊送出可以不必填寫\n2.getCredit()\n查詢剩餘點數\n最新版本請參考gitHub連結說明\n","date":"2019年7月12日","externalUrl":null,"permalink":"/2019/07/12/%E4%BD%9C%E5%93%81laravel-package-every8dsms-%E5%82%B3%E9%80%81%E7%B0%A1%E8%A8%8A%E8%B6%85%E7%B0%A1%E5%96%AE/","section":"Posts","summary":"","title":"[作品][Laravel Package] Every8DSMS : 傳送簡訊超簡單","type":"posts"},{"content":"先前已經紀錄過兩篇關於模組開發的文章，如果你還沒看過這兩篇請先閱讀\n模組化 套件 開發自己的Package\n模組化 開發自己的package 進階版\n本篇會從頭再帶一次整個開發流程，以及更多的使用方式，包含publish migration以及view的用法\n1.建立開發測試用的專案 # 這個專案目的在於透過laravel本身直接執行package程式碼，確認package本身沒有問題\ncomposer create-project laravel/laravel packageDev --prefer-dist 建立一個名為packageDev的Laravel專案來使用\n接著建立\u0026quot;packages\u0026quot;資料夾，作為開發套件的根目錄\n2.建立套件資料夾 # 接著將套件使用的資料夾建立起來後，資料夾結構會長這個樣子\npackageDev/packages/vendor_name/package_name/src 接著建立起src/Facades資料夾以及package_name.php 檔案，讓套件method可以使用static的方式來呼叫使用\n用一個範例來看，packages資料夾底下結構會是這個樣子\ncomposer package 假設在sitemap套件底下，你會一個Sitemap.php作為主要邏輯檔案，一個SitemapServiceProvider.php註冊為service provider使用\n以及Facades資料夾底下的SItemap.php\n3.建立套件的composer.json檔 # 以上方範例來說\n切換到packageDev/packages/virtualorz/sitemap資料夾後透過以下指令建立composer.json\ncomposer init 這個指令會帶你幾個步驟將資料填寫完畢即可，最主要依定要填入name, author\n接著請編輯這個composer.json檔案，加入autoload的psr-4描述，讓這個package可以被載入\n以上方的範例來說，編輯完過後的composer.json會長這個樣子\n{ \u0026#34;name\u0026#34;: \u0026#34;virtualorz/sitemap\u0026#34;, \u0026#34;authors\u0026#34;: [ { \u0026#34;name\u0026#34;: \u0026#34;virtualorz\u0026#34;, \u0026#34;email\u0026#34;: \u0026#34;virtualorz@gmail.com\u0026#34; } ], \u0026#34;require\u0026#34;: {}, \u0026#34;autoload\u0026#34;: { \u0026#34;psr-4\u0026#34;: { \u0026#34;Virtualorz\\\\Sitemap\\\\\u0026#34;: \u0026#34;src/\u0026#34; } } } 接著，如果你有其他必須要依賴使用的套件請編輯\u0026quot;require\u0026quot;區域，舉例像是底下的範例就需要依賴使用 actionLogin 0.0.8版本以上以及sitemap0.0.3版本以上\n{ \u0026#34;name\u0026#34;: \u0026#34;virtualorz/permission\u0026#34;, \u0026#34;authors\u0026#34;: [ { \u0026#34;name\u0026#34;: \u0026#34;virtualorz\u0026#34;, \u0026#34;email\u0026#34;: \u0026#34;virtualorz@gmail.com\u0026#34; } ], \u0026#34;require\u0026#34;: { \u0026#34;virtualorz/actionLog\u0026#34;: \u0026#34;^0.0.8\u0026#34;, \u0026#34;virtualorz/sitemap\u0026#34;: \u0026#34;^0.0.3\u0026#34; }, \u0026#34;autoload\u0026#34;: { \u0026#34;psr-4\u0026#34;: { \u0026#34;Virtualorz\\\\Permission\\\\\u0026#34;: \u0026#34;src/\u0026#34; } } } 接著，必須要回到packageDev根目錄中更新autoload\ncomposer dump-autoload 4.建立以及編輯套件的serviceProvider檔 # php artisan make:provider SitemapServiceProvider 建立好serviceProvider需要將檔案從packageDerv/app/providers底下移動到packageDev/packages/virtualorz/src 底下\n然後編輯一下namespace，以及register的部分，大概會像這個樣子\ncomposer package 其中，register的部分註冊讓package method可以透過facades資料夾中定義好的方式靜態呼叫\n$this-\u0026gt;app-\u0026gt;singleton(\u0026#39;sitemap\u0026#39;,function(){ return new Sitemap(); }); 5.編輯Facades/package_name.php # 以上面的範例來看，Facades/Sitemap.php會長這個樣子\n\u0026lt;?php namespace Virtualorz\\Sitemap\\Facades; use Illuminate\\Support\\Facades\\Facade; class Sitemap extends Facade { protected static function getFacadeAccessor() { return \u0026#39;sitemap\u0026#39;; } } 6.註冊serviceProvider以及aliases # 以上面的範例，需要在packageDev/config/app.php 中註冊以下資料\n\u0026#39;providers\u0026#39; =\u0026gt; [ ..... Virtualorz\\Sitemap\\SitemapServiceProvider::class, ..... ], \u0026#39;aliases\u0026#39; =\u0026gt; [ ..... \u0026#39;Sitemap\u0026#39; =\u0026gt; Virtualorz\\Sitemap\\Facades\\Sitemap::class, ..... ] 將套件sitemp帶入到packageDev專案中使用\n現在，你就可以跟在github上面看到的套件一樣在controller中呼叫你的package method了\n像是這個Stitemap套件，就可以在packageDev專案中這樣子使用\n\u0026lt;?php namespace App\\Http\\Controllers; use Illuminate\\Http\\Request; use Sitemap; class TestController extends Controller { public function index(Request $request){ Sitemap::getMenu(); } } 7.如果需要將套件中設定檔或者其他JS檔複製到專案資料夾中使用，可以這樣做 # 舉例來說，permission套件中需要將src/asset以及src/config打下的檔案複製到pacakageDev底下使用\n資料夾結構會是這樣子\ncomposer package 可以在permissionServiceProder.php中的boot() 寫入以下資訊，就可以將asset中所有檔案複製到packageDev中的config資料夾\n以及將config資料夾中的檔案複製到packageDev中的public/vender/treeView\n\u0026lt;?php namespace Virtualorz\\Permission; use Illuminate\\Support\\ServiceProvider; class PermissionServiceProvider extends ServiceProvider { /** * Register services. * * @return void */ public function register() { // $this-\u0026gt;app-\u0026gt;singleton(\u0026#39;permission\u0026#39;,function(){ return new Permission(); }); } /** * Bootstrap services. * * @return void */ public function boot() { // $this-\u0026gt;publishes([ __DIR__.\u0026#39;/config\u0026#39; =\u0026gt; base_path(\u0026#39;config\u0026#39;), __DIR__.\u0026#39;/asset/treeView\u0026#39; =\u0026gt; public_path(\u0026#39;vendor/treeView\u0026#39;), ]); } } 8.如果需要用到vview以及route，也可以這麼做 # 舉例來說fileupload套件中會有自己定義好的route讓專案執行某個路徑就可以處理檔案上傳的資料\n以及在fileupload套件中也會有個method透過view將html產生回傳給controller\n因此我們可以這樣定義資料結構\ncomposer package src/routes.php是定義上傳檔案所需要用到的url，會長這個樣子\nRoute::post(\u0026#39;/virtualorz/upload\u0026#39;, [ \u0026#39;as\u0026#39; =\u0026gt; \u0026#39;virtualorz.upload\u0026#39; , \u0026#39;uses\u0026#39; =\u0026gt; \u0026#39;Virtualorz\\Fileupload\\Fileupload@index\u0026#39;, \u0026#39;permission\u0026#39; =\u0026gt; false ]); 而view/uploadArea.blade.php，就是預先定義好拿來產生html的blade file\n接著需要在FileuploadServiceProvider.php中定義\n\u0026lt;?php namespace Virtualorz\\Fileupload; use Illuminate\\Support\\ServiceProvider; class FileuploadServiceProvider extends ServiceProvider { /** * Register services. * * @return void */ public function register() { // $this-\u0026gt;app-\u0026gt;singleton(\u0026#39;fileupload\u0026#39;,function(){ return new Fileupload(); }); } /** * Bootstrap services. * * @return void */ public function boot() { // $this-\u0026gt;loadViewsFrom(__DIR__.\u0026#39;/view\u0026#39;, \u0026#39;fileupload\u0026#39;); $this-\u0026gt;loadRoutesFrom(__DIR__.\u0026#39;/routes.php\u0026#39;); $this-\u0026gt;publishes([ __DIR__.\u0026#39;/asset/fileupload\u0026#39; =\u0026gt; public_path(\u0026#39;vendor/fileupload\u0026#39;) ]); } } 其中loadViewsFrom 是讓系統知道可以從那邊存取view blade\nloadRoutesFrom是讓系統知道可以從哪邊存取route檔案\n所以這時候在如果存取 http://package_url//virtualorz/upload 就可以使用package中定義好的 \u0026lsquo;Virtualorz\\Fileupload\\Fileupload@index\u0026rsquo;\n9.如果你需要定義資料表來存取資料，你可以這麼做 # 舉例來說，aactionLog套件需要建立一張\u0026rsquo;system_log\u0026rsquo;資料表來儲存記錄訊息\n所以我們先建立system_log migration\nphp artisan make:migrate create_system_log 接著把檔案從 packageDev/database/migrations/ 搬移到 packageDev/packages/virtualorz/actionLog/migrations中\n接著編輯好migrate檔案後，在ActionLogServiceProvider中註冊\n\u0026lt;?php namespace Virtualorz\\ActionLog; use Illuminate\\Support\\ServiceProvider; class ActionLogServiceProvider extends ServiceProvider { /** * Register services. * * @return void */ public function register() { // $this-\u0026gt;app-\u0026gt;singleton(\u0026#39;actionLog\u0026#39;,function(){ return new ActionLog(); }); } /** * Bootstrap services. * * @return void */ public function boot() { // $this-\u0026gt;loadMigrationsFrom(__DIR__.\u0026#39;/migrations\u0026#39;); $this-\u0026gt;loadViewsFrom(__DIR__.\u0026#39;/view\u0026#39;, \u0026#39;actionLog\u0026#39;); $this-\u0026gt;publishes([ __DIR__.\u0026#39;/config\u0026#39; =\u0026gt; base_path(\u0026#39;config\u0026#39;), ]); } } 其中，loadMigrationsFrom讓系統知道可以從哪邊讀取migrate檔案，這麼一來執行\nphp artisan migration時就會將這個指定的路徑納入\n10.最後，請記得編輯readme.md # readme.md是整個套件使用的入門，readme.md檔是markdown語法構成的，不熟悉markdown可以參考\nhttps://markdown.tw/\n回到套件根目錄後透過touch readme.md 可以建立空的檔案，然後直接使用編輯器編寫即可\n接下來的步驟就是將git push到github上，以及設定好tag version，然後submit到packagist上就可以透過composer找到你的套件了\n步驟不多，也就不再詳述可以直接參考 模組化 套件 開發自己的Package\n以上範例皆存在gihub上，可以自由參考\nfileupload\nsitemap\nactionLog\npermission\n","date":"2019年7月9日","externalUrl":null,"permalink":"/2019/07/09/composer-package-%E5%A5%97%E4%BB%B6%E9%96%8B%E7%99%BC%E7%AC%AC%E4%B8%89%E7%AF%87/","section":"Posts","summary":"","title":"Composer Package 套件開發第三篇","type":"posts"},{"content":"套件名稱 : VIrtualorz/Fileupload\n開發年份 : 2019\ngitHub連結 : https://github.com/virtualorz/fileupload\n內容簡介 :\nFileupload 提供一個快速產生上傳檔案介面的js以及儲存檔案並且產生縮圖的laravel controller，本套件須依賴PHP imgick套件來產生縮圖，上傳的檔案將放置在/bublic/upload/的自訂資料夾名稱底下\nLaravel：90% Jquery：50% HTML：55% 安裝方式 :\ncomposer require virtualorz/fileupload 編輯 config/app.php\n\u0026#39;providers\u0026#39; =\u0026gt; [ ... Virtualorz\\Fileupload\\FileuploadServiceProvider::class ] \u0026#39;aliases\u0026#39; =\u0026gt; [ ... \u0026#39;Fileupload\u0026#39; =\u0026gt; Virtualorz\\Fileupload\\Facades\\Fileupload::class, ] 發布資料 :\nphp artisan vendor:publish --provider=\u0026#34;Virtualorz\\Fileupload\\FileuploadServiceProvider\u0026#34; 編輯 .env\nUPLOADDIR : public/upload/底下的自訂資料夾名稱，用於儲存上傳的檔案 編輯config/filesystems\n編輯\u0026#39;disk\u0026#39;區塊如下 public\u0026#39; =\u0026gt; [ \u0026#39;driver\u0026#39; =\u0026gt; \u0026#39;local\u0026#39;, \u0026#39;root\u0026#39; =\u0026gt; public_path(\u0026#39;uploads\u0026#39;), \u0026#39;url\u0026#39; =\u0026gt; env(\u0026#39;APP_URL\u0026#39;).\u0026#39;uploads\u0026#39;, \u0026#39;visibility\u0026#39; =\u0026gt; \u0026#39;public\u0026#39;, ], 使用方式 :\nIn view: \u0026lt;script src=\u0026#34;{{ asset(\u0026#39;vendor/fileupload/fileupload.js\u0026#39;) }}\u0026#34;\u0026gt;\u0026lt;/script\u0026gt; And need an file element like : \u0026lt;input type=\u0026#34;file\u0026#34; name=\u0026#34;file\u0026#34; id=\u0026#34;file\u0026#34; class=\u0026#34;file_input\u0026#34; accept=\u0026#34;.jpeg\u0026#34;\u0026gt; accept attribute can edit to the file type you want And after file element call {!! Fileupload::createUploadArea($files)!!} 方法簡介 :\n1.createUploadArea($files = null)\n產生檔案上傳後畫面顯示管理區域的HTML，方便用於view中\n備註 :\n檔案上傳後的顯示方式\nlaravel fileupload 最新版本請參考gitHub連結說明\n","date":"2019年7月3日","externalUrl":null,"permalink":"/2019/07/03/%E4%BD%9C%E5%93%81laravel-package-fileupload-%E5%BF%AB%E9%80%9F%E7%94%A2%E7%94%9F%E6%AA%94%E6%A1%88%E4%B8%8A%E5%82%B3%E4%BB%8B%E9%9D%A2%E5%8F%8A%E7%AE%A1%E7%90%86/","section":"Posts","summary":"","title":"[作品][Laravel Package] Fileupload : 快速產生檔案上傳介面及管理","type":"posts"},{"content":"套件名稱 : Virtualorz/Permission\n開發年份 : 2019\ngitHub連結 : https://github.com/virtualorz/permission\n依賴套件 : Virtualorz/ActionLog , Virtualorz/Sitemap\n內容簡介 :\n提供最基礎的登入檢查、權限檢查以及快速權限設定畫面產生以及CRUD內容，依賴bootstrap，可以快速的產生CRUD畫面以及功能\n依賴於Virtualorz/Sitemap結構，可以從laravel rout中直接產生所有頁面權限樹狀圖，並且依賴Virtualorz/ActionLog寫下權限CRUD的操作紀錄\nLaravel：90% Jquery：50% HTML：55% 安裝方式 : # composer require virtualorz/permission 編輯 config/app.php\n\u0026#39;providers\u0026#39; =\u0026gt; [ ... Virtualorz\\Permission\\PermissionServiceProvider::class ] \u0026#39;aliases\u0026#39; =\u0026gt; [ ... \u0026#39;Permission\u0026#39; =\u0026gt; Virtualorz\\Permission\\Facades\\Permission::class, ] 發布資料 : # php artisan vendor:publish --provider=\u0026#34;Virtualorz\\Permission\\PermissionServiceProvider\u0026#34; 執行Migration : # php artisan migrate --path=/vendor/virtualorz/actionlog/src/migrations 編輯 config/permission_identity\n\u0026#39;identity\u0026#39; =\u0026gt; [ 1 =\u0026gt; \u0026#39;Member\u0026#39;, 2 =\u0026gt; \u0026#39;Manager\u0026#39;, 3 =\u0026gt; \u0026#39;Administrator\u0026#39; ], 三種不同權限類型可以自行命名 編輯 .env\n加入以下參數 LOGINSESSION : 後台管理員登入儲存用session變數名稱 LOGINSESSION_CUSTOMER : 前台會員登入儲存用session變數名稱 LOGINPAGE : 後台管理員登入頁面Route名稱 LOGINPAGE_CUSTOMER : 前台客戶登入頁面Route名稱 使用方式 : # 建立middleware,在middleware中呼叫 \u0026lsquo;checkLogin\u0026rsquo; , \u0026lsquo;checkLoginCustomer\u0026rsquo; , \u0026lsquo;checkPermission\u0026rsquo; 登方法，用於確認管理員、客戶是否登入，以及管理員是否有頁面使用權限\n使用範例 : # 1.在middleware中使用checkLogin\n$result = Permission::checkLogin($request); if($result !== true){ return $result; } else{ return $next($request); } 2.在middleware中使用checkLoginCustomer\n$result = Permission::checkLoginCustomer($request,[\u0026#39;hash\u0026#39;=\u0026gt;\u0026#39;0098765\u0026#39;]); if($result !== true){ return $result; } else{ return $next($request); } 3.在middleware中使用checkPermission\n$result = Permission::checkPermission(); if($result){ return $next($request); } 4.在VIew中建立權限樹狀圖\ninclude in blade \u0026lt;link rel=\u0026#34;stylesheet\u0026#34; href=\u0026#34;{{ asset(\u0026#39;vendor/treeView/bootstrap-treeview.css\u0026#39;) }}\u0026#34;\u0026gt; \u0026lt;script src=\u0026#34;{{ asset(\u0026#39;vendor/treeView/bootstrap-treeview.js\u0026#39;) }}\u0026#34;\u0026gt;\u0026lt;/script\u0026gt; \u0026lt;script src=\u0026#34;{{ asset(\u0026#39;vendor/treeView/permission_tree.js\u0026#39;) }}\u0026#34;\u0026gt;\u0026lt;/script\u0026gt; in HTML \u0026lt;input type=\u0026#34;hidden\u0026#34; id=\u0026#34;tree_node\u0026#34; value=\u0026#34;{{ $sitemap }}\u0026#34;\u0026gt; \u0026lt;!-- this to generate tree--\u0026gt; \u0026lt;input type=\u0026#34;hidden\u0026#34; name=\u0026#34;permission\u0026#34; id=\u0026#34;permission\u0026#34; value=\u0026#34;[]\u0026#34;\u0026gt; \u0026lt;!-- thid to save tree value--\u0026gt; before form submit $(\u0026#34;#permission\u0026#34;).val(JSON.stringify($(\u0026#39;#treeview\u0026#39;).treeview(\u0026#39;getChecked\u0026#39;))); in Controller, use sitemap to generate JSON $sitemap = Sitemap::getTreeView(); $sitemap = Sitemap::routStruct(\u0026#39;root\u0026#39;,$sitemap); $sitemap = json_encode($sitemap); 方法簡介 : # 1.checkLogin($request)\n檢查管理員是否已經登入，如果是回傳true, 否則依據env(\u0026lsquo;LOGINPAGE)重新導向到登入頁面\n2.checkLoginCustomer($request,$parameter)\n檢查前台會員是否已經登入，如果是回傳true, 否則依據env(\u0026lsquo;LOGINPAGE_CUSTOMER\u0026rsquo;)重新導向到登入頁面，並且帶入$parameter到route參數\n3.checkPermission\n檢查管理員是否有使用此頁面的權限，如果有則會傳true，沒有則直接跳出403沒有權限頁面\n4.groupList($keyword = null,$page = 15)\n用於後台建立權限群組CRUD，列表所有全組名稱，$keyword用於搜尋，$page用於分頁顯示每個頁面多少筆資料\n5.groupAdd($column)\n用於後台建立權限群組CRUD，新增一個權限群組，\n$column[\u0026rsquo;name\u0026rsquo;] : 群組名稱\n$column[\u0026lsquo;identity\u0026rsquo;] : 身份別，定義於config/permission_identity中\n$column[\u0026lsquo;permission] : 權限樹狀圖回傳之陣列(JSON)\n6.getGroupItem($id)\n用於後台建立權限群組CRUD，取得id = $id 之群組資料\n7.groupEdit($column)\n用於後台建立權限群組CRUD，編輯一個權限群組，\n$column[\u0026rsquo;name\u0026rsquo;] : 群組名稱\n$column[\u0026lsquo;identity\u0026rsquo;] : 身份別，定義於config/permission_identity中\n$column[\u0026lsquo;permission] : 權限樹狀圖回傳之陣列(JSON)\n$column[\u0026lsquo;id\u0026rsquo;] : 要編輯之資料ID\n8.groupDelete($column)\n用於後台建立權限群組CRUD，刪除id = $column[\u0026lsquo;id\u0026rsquo;]之資料\n9.permissionList($keyword = null,$page = 15)\n用於後台建立人員權限CRUD，列表所有人員名稱，$keyword用於搜尋，$page用於分頁顯示每個頁面多少筆資料\n10.getPermissionItem($id)\n用於後台建立人員權限CRUD，取得人員id = $id 之權限資料\n11.permissionEdit($column)\n用於後台建立人員權限CRUD，編輯人員權限\n$column[\u0026lsquo;select\u0026rsquo;] : 已選擇之權限群組ID(Array)\n$column[\u0026lsquo;id\u0026rsquo;] : 人員ID\n12.permissionDelete($column)\n用於後台建立人員權限CRUD，刪除人員ID = $id之權限資料\n備註 :\n權限樹狀圖使用bootstrap tree-view套件處理，顯示如下\nlaravel permission 最新版本請參考gitHub連結說明\n","date":"2019年7月1日","externalUrl":null,"permalink":"/2019/07/01/%E4%BD%9C%E5%93%81laravel-package-permission-%E7%82%BA%E7%B6%B2%E7%AB%99%E5%8A%A0%E5%85%A5%E6%AC%8A%E9%99%90%E7%AE%A1%E7%90%86/","section":"Posts","summary":"","title":"[作品][Laravel Package] Permission : 為網站加入權限管理","type":"posts"},{"content":"套件名稱 : Virtualorz/ActionLog\n開發年份 : 2019\ngitHub連結 : https://github.com/virtualorz/actionLog\n內容簡介 :\nActionLog提供一個比較簡單的方式記錄下資料新增、編輯、刪除等等的過程，只需要在新增、編輯、刪除或者有任何資料異動的時候加入一行程式碼就可以將before after值記錄下來，本套件也提供直接產生查看紀錄畫面的方法，可以在佈建後台時更快速的將log呈現頁面完成。\nLaravel：90% MySQL：85% 安裝方式 : # composer require virtualorz/actionLog 編輯 config/app.php\n\u0026#39;providers\u0026#39; =\u0026gt; [ ... Virtualorz\\ActionLog\\ActionLogServiceProvider::class ] \u0026#39;aliases\u0026#39; =\u0026gt; [ ... \u0026#39;ActionLog\u0026#39; =\u0026gt; Virtualorz\\ActionLog\\Facades\\ActionLog::class, ] 發布資料 : # php artisan vendor:publish --provider=\u0026#34;Virtualorz\\ActionLog\\ActionLogServiceProvider\u0026#34; 執行Migration : # php artisan migrate --path=/vendor/virtualorz/actionlog/src/migrations 編輯設定檔 : # 編輯 config/actionLog_logAction.php\n其中，陣列包含三種類型操作動作，可自行編輯文字，但必須保留key值不變\n\u0026#39;log_action\u0026#39; =\u0026gt; [ 0 =\u0026gt; \u0026#39;刪除\u0026#39;, 1 =\u0026gt; \u0026#39;新增\u0026#39;, 2 =\u0026gt; \u0026#39;異動\u0026#39; ] 方法簡介 : # 1.pushBefore($key,$value)\n手動加入before資料值到陣列中，key可以重複，重複的key也會加入變成一個陣列\n2.pushAfter($key,$value)\n手動加入after資料值到陣列中，key可以重複，重複的key也會加入變成一個陣列 3.save($page,$action,$remark,$object = null,$target_id = null)\n將before after資料寫入資料庫，其中\n$page 代表的是page name，如果有使用sitemap套件可以直接帶入sitemap中的name\n$action 代表的是動作id，也就是log_action中的key\n$remark 可隨意寫入備註文字\n$object 代表的是Laravel model物件，如果為空值才會去讀手動存下的befor after值，如果不為空則會直接存取model中提供的前後值儲存\n$target_id 代表的是本次紀錄資料的id值，可填可不填，未填寫則由model物件中取得\n4.logContent($id)\n產生before after值比較內容view html，可以使用{!! $RESULT_HTML !!} 將logContent產稱的結果印出在view中\n範例 :\n手動加入before after\nctionLog::pushBefore(\u0026#39;system_permission\u0026#39;, system_permission::where(\u0026#39;member_id\u0026#39;, $request-\u0026gt;get(\u0026#39;id\u0026#39;))-\u0026gt;get()); ... //do some thing ... ActionLog::pushAfter(\u0026#39;system_permission\u0026#39;, system_permission::where(\u0026#39;member_id\u0026#39;, $request-\u0026gt;get(\u0026#39;id\u0026#39;))-\u0026gt;get()); ActionLog::save(Route::getCurrentRoute()-\u0026gt;action[\u0026#39;parent\u0026#39;],0,\u0026#39;remark text\u0026#39;,null,$request-\u0026gt;get(\u0026#39;id\u0026#39;)); 從model中取得before after\nActionLog::save(Route::getCurrentRoute()-\u0026gt;action[\u0026#39;parent\u0026#39;],2,\u0026#39;remark text\u0026#39;,$system_permission); logContent產生結果\n備註 : 最新版本請參考gitHub連結說明\n","date":"2019年6月28日","externalUrl":null,"permalink":"/2019/06/28/%E4%BD%9C%E5%93%81laravel-package-actionlog-%E7%B4%80%E9%8C%84%E7%B6%B2%E7%AB%99%E6%AF%8F%E7%AD%86%E8%B3%87%E6%96%99%E6%93%8D%E4%BD%9C%E8%A8%98%E9%8C%84/","section":"Posts","summary":"","title":"[作品][Laravel Package] ActionLog : 紀錄網站每筆資料操作記錄","type":"posts"},{"content":"套件名稱 : Virtualorz/Sitmap\n開發年份 : 2019\ngitHub連結 : https://github.com/virtualorz/sitemap\n內容簡介 :\n因應Laravel每條URL都需要寫進Rote中，而幾次的後台專案開發後如果結構性選單需要獨立儲存的話會變得非常麻煩，\n比如說新增加一個頁面就必須修改route以及結構選單資料兩者，Virtualorz/Sitemap就是為了這個生出來的，\n他可以直接透過route資料檔來產生網站結構性資料，並且生成麵包屑以及選單結構資料，\n當然，相對的route的寫法就必須要加入其他必要參數，但是已經變得簡易許多\nLaravel：100% 安裝方式 : # composer require virtualorz/sitemap 編輯 config/app.php\n\u0026#39;providers\u0026#39; =\u0026gt; [ ... Virtualorz\\Sitemap\\SitemapServiceProvider::class ] \u0026#39;aliases\u0026#39; =\u0026gt; [ ... \u0026#39;Sitemap\u0026#39; =\u0026gt; Virtualorz\\Sitemap\\Facades\\Sitemap::class, ] 方法簡介 : # 1.getTreeView\n從route/web.php 結構取得全站結構，依照web.php中設定的parent參數可以重新整理知道所有頁面的上下層關係，進而整理成為key為parent name的陣列回傳\n2.getNaviPat($pageName = null)\n回傳麵包屑各node的陣列,如果$pageName是null,則回傳當頁面的navi path, 否則回傳指定頁面的\n3.getParents($pageName = null)\n回傳此頁的上層節點名稱,如果$pageName是null,則回傳當頁面到parent,否則回傳指定頁面的\n4.getMenu($parmissionArray= null)\n回傳全站左選單巢狀menu 結構陣列,如果$parmissionArray是nulll,則回傳全部的menu,否則只回傳包含在$parmissionArray中的\nroute/web.php範例\nRoute::get(\u0026#39;/customer\u0026#39;, [ \u0026#39;as\u0026#39; =\u0026gt; \u0026#39;backend.customer.index\u0026#39; ,// 頁面名稱，作為ID使用 \u0026#39;uses\u0026#39; =\u0026gt; \u0026#39;backend\\CustomerController@index\u0026#39;, //使用Controller名稱 \u0026#39;parent\u0026#39; =\u0026gt; \u0026#39;backend.index\u0026#39;, //上層頁面ID \u0026#39;name\u0026#39; =\u0026gt; \u0026#39;客戶清單\u0026#39;, //此頁面單名稱文字 \u0026#39;label\u0026#39; =\u0026gt;\u0026#39;資料管理\u0026#39;, //選單Label文字 \u0026#39;fa\u0026#39; =\u0026gt; \u0026#39;fa-database\u0026#39; //選單fa-icon ]); 建立tree view範例\n$sitemap = Sitemap::getTreeView(); $sitemap = Sitemap::routStruct(\u0026#39;root\u0026#39;,$sitemap); 備註 : 最新版本請參考gitHub連結說明\n","date":"2019年6月26日","externalUrl":null,"permalink":"/2019/06/26/%E4%BD%9C%E5%93%81laravel-package-sitemap-%E4%BD%BF%E7%94%A8laravel-route%E7%94%A2%E7%94%9F%E7%B6%B2%E7%AB%99%E7%B5%90%E6%A7%8B%E9%99%A3%E5%88%97/","section":"Posts","summary":"","title":"[作品][Laravel Package] Sitemap : 使用Laravel Route產生網站結構陣列","type":"posts"},{"content":" 用docker架FTP vsftpd on docker # 如果你需要很迅速的架起一個FTP伺服器，docker確實是個很好的方式\n一條指令就完成伺服器建立以及使用者設定\n這次遇到的狀況是公司換了新的NAS之後，原本NAS上的FTP卻無法讓傳真機連接\n原本公司的傳真機跟印表機都可以連線到NAS，收到傳真後直接透過SMB的方式將檔案轉存，印表機也是可以直接掃描儲存到NAS上\n而這次更換了NAS硬體後印表機的部分連線沒有問題，傳真機卻掛了\nSMB無法認證，FTP無法建立檔案，經過了一天從設定值中找不到任何解法後我決定轉用docker試著來處理這個問題\n概念是 : 透過QNAP的docker station架設FTP後將原本儲存傳真的資料夾綁定到docker FTP的根目錄中\n參考連結 :\nhttps://hub.docker.com/r/fauria/vsftpd/\nhttps://www.5yun.org/17060.html\nhttps://devanswers.co/vsftp-default-permissions-uploaded-files-folders-600/\n如果你還沒有閱讀過以下文章，請先閱讀\nMAC安裝docker\nReverse Proxy on Nginx docker 使用QNAP Container Station\n1. vsftpd docker參數設定 # vsftp有許多可設定的參數，先整理幾個比較重要的參數\n參數 用途 預設值 FTP_USER 可登入使用者 亂數 FTP_PASS 登入使用者密碼 亂數 PASV_MIN_PORT 被動模式port最小值 21100 PASV_MAX_PORT 被動模式port最大值 21110 LOCAL_UMASK 檔案權限遮罩 077 上面幾個參數是本次設定有用到的，其他參數可以上docker hub參考描述\n2. 啟動FTP # 所以，基於以上幾個參數我們就可以很輕易地透過下列使令來啟動FTP\ndocker run -p 21:21 -e FTP_USER=fax -e FTP_PASS=fax -v /share/fax:/home/vsftpd/fax --name vsftpd fauria/vsftpd 指令的部分跟containser station的畫面我就不再截圖說明，-v 就是container station中的共用資料夾，-e 就是container station中的環境變數，而-p 則可以在命令中直接設定\n另外，值得一提的是 -v參數，/home/vsftpd/ 資料夾是vsftp給使用者的預設根目錄，但是在這層目錄下還會區分不同的使用者，因此fax使用者的根目錄就是 /home/vsftpd/fax，所以我們必須把這個資料夾映射到原本儲存傳真資料的資料夾中\n就這樣子，傳真機已經順利連線到這個新架設好的FTP上，並且順利將檔案存入，但是緊接著問題還是來了\n3. 設定檔案權限 # 按照以上設定好的方式，上傳的所有檔案權限都會是600，導致除了admin管理員以外其他NAS上的使用這會沒有辦法存取檔案\n當然如果你只有給個人使用那有枚有這樣子的問提，要解決這個問題就必須將上傳的檔案權限修改為755才能讓其他非擁有者也可以存取\n所以，我們講指令改成以下\ndocker run -p 21:21 -e FTP_USER=fax -e FTP_PASS=fax -e LOCAL_UMASK=022 -v /share/fax:/home/vsftpd/fax --name vsftpd fauria/vsftpd 將 LOCAL_UMASK設定為022即可\n","date":"2019年5月31日","externalUrl":null,"permalink":"/2019/05/31/%E7%94%A8docker%E6%9E%B6ftp-vsftpd-on-docker/","section":"Posts","summary":"","title":"用docker架FTP vsftpd on docker","type":"posts"},{"content":"本篇是近期遇到的問題，紀錄一下處理方式\n如果你還沒使用過laradock，請先閱讀以下文章\nlaradock : laravel專案容器化開發實作\n如果你還沒嘗試過laravel排程處理，請先閱讀以下文章\nSchedule 任務排程，讓Laravel 自動執行排定任務\n因為laradocker已經很人性化的幫我們連結起apache(或Nginx)容器以及Mysql(或其他資料庫)容器，這邊以MySQL為例\n在專案的.env檔中，只要將DB_HOST變數設定為mysql就可以連結到laradock中啟動的MySQL服務\n但是近期遇到的是當我要測試schedule是否可以執行時，我執行php artisan emplyee:check 來啟動Kernael中定義好的commands，\n但是這時卻會出現這樣子的錯誤\n是的，資料庫無法連上\n解決方案如下:\n我們可以在執行php artisan之前，先異動DB_HOST以及DB_PORT，讓artisan可以順利連接上資料庫\nenv DB_HOST=127.0.0.1 DB_PORT=3307 php artisan employee:check ","date":"2019年5月15日","externalUrl":null,"permalink":"/2019/05/15/laradock-php-artisan-php_network_getaddresses-%E7%84%A1%E6%B3%95%E9%80%A3%E7%B5%90%E5%88%B0%E8%B3%87%E6%96%99%E5%BA%AB/","section":"Posts","summary":"","title":"laradock php artisan php_network_getaddresses 無法連結到資料庫","type":"posts"},{"content":"","date":"2019年3月22日","externalUrl":null,"permalink":"/tags/facebook/","section":"Tags","summary":"","title":"Facebook","type":"tags"},{"content":"本篇要說明的不是強大的Facbook爬蟲，而是簡易的案例分享，帶一點點基礎入門\n參考連結 :\nhttps://wsvincent.com/install-python3-mac/\nhttp://www.hoamon.info/blog/2007/02/01/pythonpdb.html\n1. 安裝python # 要可以執行python首先第一步要先在自己的開發機上將python安裝起來\n如果你的環境是MAC，那麼應該已經安裝了python 2.7，如果想要使用phthon 3 那麼需要以下步驟來安裝\n#先測試一下版本 python --version ##Python 2.7.10 python3 --version ##Python 3.7.2 如果都有出現版本，那麼恭喜你基礎使用環境已經安裝完畢，如果需要安裝python3環境可以參考上面的詳細連結\n如果MAC已經安裝了homebrew，那麼只要一行指令即可\nbrew install python3 安裝需要一點時間，可以先倒杯咖啡等一下\n2. 安裝所需要的套件 # 接著，我想列出本次案例有用到的幾個套件\nrequests、BeautifulSoup、pdb\n首先所有的套件都依賴pip來管理，所以必須先安裝pip3 (python3的環境底下)\nbrew postinstall python3 pip3 -v #如果指令有找到代表已經成功 request套件可以模擬發出http request，來取的遠端URL的內容\nBeautifulSoup套件則是用來解析HTML\npdb則是方便用來debug\n3. 爬蟲流程 # 假設今天有一個Facebook 的公開文章，我們想要拿到這篇文章的讚數、留言數、分享數 該怎麼處理？\nres = requests.get(url) soup = BeautifulSoup(res.text, \u0026#34;lxml\u0026#34;) 首先，用request取得url的內容，透過BeautifulSoup格式化內容\nlikecount = soup.text.split(\u0026#39;i18n_reaction_count:\u0026#39;) likecount = likecount[1].split(\u0026#39;,\u0026#39;) if likecount[0] != \u0026#39;null\u0026#39;: likecount = likecount[0].split(\u0026#39;\u0026#34;\u0026#39;) likes = likecount[1] else: # 為null 沒有人按讚 likes = 0 按讚數可以透過\u0026quot;i18n_reaction_count:\u0026ldquo;來取得，我想要的是\u0026quot;i18n_reaction_count:\u0026ldquo;後面接的數字\n相同概念，留言數以及分享數可以用\u0026quot;i18n_comment_count:\u0026ldquo;以及\u0026quot;i18n_share_count:\u0026ldquo;關鍵字來取得\n接著，就可以把檔案儲存成.py，透過終端機來執行了\npython3 /User/local/getFB.py 4. python debug的方式 # pdb可以協助設定中斷點，舉例來說\nurl = url.replace(\u0026#34;m.facebook.com\u0026#34;, \u0026#34;www.facebook.com\u0026#34;) res = requests.get(url) pdb.set_trace() soup = BeautifulSoup(res.text, \u0026#34;lxml\u0026#34;) 我們在第三行設定了中斷點，那麼程式執行到第三行就會停下來，像這樣子\n畫面上會停下來進入pdb，並且顯示下一行要執行的程式碼，接下來就可以透過pdb的指令來顯示想要知道的內容\nq(quit)：離開\np some variable：秀某個變數的值\nn(next line)：下一行\nc(continue)：繼續下去\ns(step into)：進入函式\nr(return): 到本函式的return敘述式\nl(list)：秀出目前所在行號\n!： 改變變數的值\n","date":"2019年3月22日","externalUrl":null,"permalink":"/2019/03/22/python-facebook-%E7%88%AC%E8%9F%B2-%E7%B0%A1%E6%98%93%E6%A1%88%E4%BE%8B%E8%AA%AA%E6%98%8E/","section":"Posts","summary":"","title":"Python Facebook 爬蟲 簡易案例說明","type":"posts"},{"content":"","date":"2019年3月22日","externalUrl":null,"permalink":"/tags/%E7%88%AC%E8%9F%B2/","section":"Tags","summary":"","title":"爬蟲","type":"tags"},{"content":"","date":"2019年3月12日","externalUrl":null,"permalink":"/tags/jquery/","section":"Tags","summary":"","title":"JQuery","type":"tags"},{"content":"jQuery Validation , 自訂錯誤訊息位置\n有時候想要透過 jQuery Validation 做簡單的做表單驗證，但是偏偏錯誤訊息顯示在輸入框位置的下方導致HTML排版跑板了\n官方文件中提供的方法可以解決這個問題\n參考連結 : https://jqueryvalidation.org/documentation/\n舉例說明\n\u0026lt;div class=\u0026#34;form-question\u0026#34;\u0026gt; \u0026lt;h3\u0026gt;1. 請問您現在的居住地區是？\u0026lt;span\u0026gt;\u0026lt;/span\u0026gt;\u0026lt;/span\u0026gt;\u0026lt;/h3\u0026gt; \u0026lt;div class=\u0026#34;inputGroup\u0026#34;\u0026gt; \u0026lt;label class=\u0026#34;container\u0026#34;\u0026gt;北部(雙北/基隆/桃竹苗) \u0026lt;input type=\u0026#34;radio\u0026#34; name=\u0026#34;q_1[]\u0026#34; value=\u0026#34;1\u0026#34; class=\u0026#34;required\u0026#34;\u0026gt; \u0026lt;span class=\u0026#34;checkmark\u0026#34;\u0026gt;\u0026lt;/span\u0026gt; \u0026lt;/label\u0026gt; \u0026lt;label class=\u0026#34;container\u0026#34;\u0026gt;中部(中彰投/雲林) \u0026lt;input type=\u0026#34;radio\u0026#34; name=\u0026#34;q_1[]\u0026#34; value=\u0026#34;2\u0026#34; class=\u0026#34;required\u0026#34;\u0026gt; \u0026lt;span class=\u0026#34;checkmark\u0026#34;\u0026gt;\u0026lt;/span\u0026gt; \u0026lt;/label\u0026gt; \u0026lt;label class=\u0026#34;container\u0026#34;\u0026gt;南部(嘉南/高屏) \u0026lt;input type=\u0026#34;radio\u0026#34; name=\u0026#34;q_1[]\u0026#34; value=\u0026#34;3\u0026#34; class=\u0026#34;required\u0026#34;\u0026gt; \u0026lt;span class=\u0026#34;checkmark\u0026#34;\u0026gt;\u0026lt;/span\u0026gt; \u0026lt;/label\u0026gt; \u0026lt;label class=\u0026#34;container\u0026#34;\u0026gt;東部(宜/花/東) \u0026lt;input type=\u0026#34;radio\u0026#34; name=\u0026#34;q_1[]\u0026#34; value=\u0026#34;4\u0026#34; class=\u0026#34;required\u0026#34;\u0026gt; \u0026lt;span class=\u0026#34;checkmark\u0026#34;\u0026gt;\u0026lt;/span\u0026gt; \u0026lt;/label\u0026gt; \u0026lt;label class=\u0026#34;container\u0026#34;\u0026gt;離島(澎湖/金/馬) \u0026lt;input type=\u0026#34;radio\u0026#34; name=\u0026#34;q_1[]\u0026#34; value=\u0026#34;5\u0026#34; class=\u0026#34;required\u0026#34;\u0026gt; \u0026lt;span class=\u0026#34;checkmark\u0026#34;\u0026gt;\u0026lt;/span\u0026gt; \u0026lt;/label\u0026gt; \u0026lt;/div\u0026gt; \u0026lt;/div\u0026gt; 假設我們有一段表單HTML如上，此時想要顯示必填錯誤訊息在問題之後而非選項之後，下列方法可以簡單做到\n首先，在HTML上增加一個\u0026lt;sapn\u0026gt;\u0026lt;/span\u0026gt;，用來顯示必填錯誤訊息\n\u0026lt;label class=\u0026#34;container\u0026#34;\u0026gt;北部(雙北/基隆/桃竹苗) \u0026lt;span class=\u0026#34;display_error\u0026#34;\u0026gt;\u0026lt;/span\u0026gt; \u0026lt;input type=\u0026#34;radio\u0026#34; name=\u0026#34;q_1[]\u0026#34; value=\u0026#34;1\u0026#34; class=\u0026#34;required\u0026#34;\u0026gt; \u0026lt;span class=\u0026#34;checkmark\u0026#34;\u0026gt;\u0026lt;/span\u0026gt; \u0026lt;/label\u0026gt; 接著在script的地方加入參數\n$(\u0026#34;#form\u0026#34;).validate({ errorPlacement: function(error, element) { var placement = $(element).data(\u0026#39;error\u0026#39;); if (placement) { $(placement).append(error) } else { element.parents(\u0026#34;.form-question\u0026#34;).first().find(\u0026#39;.display_error\u0026#39;).append(error); } } }); 其中，error是錯誤訊息的dom元件，element則是觸發必填錯誤的元件\n所以我們依照HTML結構找到觸發錯誤的元件網上層找到.display_error span，將錯誤訊息append在這個span中，就完成啦～\n其他方式可以參考官網文件\n","date":"2019年3月12日","externalUrl":null,"permalink":"/2019/03/12/jquery-validation-%E8%87%AA%E8%A8%82%E9%8C%AF%E8%AA%A4%E8%A8%8A%E6%81%AF%E4%BD%8D%E7%BD%AE/","section":"Posts","summary":"","title":"jQuery Validation , 自訂錯誤訊息位置","type":"posts"},{"content":"本篇文章以一個需求案例提供方向給需要匯出Excel的朋友參考，內文不會詳細說明文件細項，只針對有用到的method說明\n參考連結\nhttps://docs.laravel-excel.com/3.0/exports/collection.html\nhttps://phpoffice.github.io/PhpSpreadsheet/1.4.1/PhpOffice/PhpSpreadsheet/Worksheet/Drawing.html\nhttps://laracasts.com/discuss/channels/laravel/storing-image-file-retrieved-from-external-url?page=1\nhttps://laraveldaily.com/laravel-excel-export-formatting-and-styling-cells/\nhttps://phpspreadsheet.readthedocs.io/en/latest/topics/recipes/#setting-a-columns-width\n案例需求:\n將資料匯出到excel須包含圖片以及文字 必須設定圖片大小與cell大小 要匯出的圖片只有url link並不存在伺服器端 一. 透過Laravel Excel 匯出資料 # 根據官網提供的步驟如下\n透過composer安裝 Laravel Excel\ncomposer require maatwebsite/excel 在config/app.php中加入provider 以及Alias\n\u0026#39;providers\u0026#39; =\u0026gt; [ /* * Package Service Providers... */ Maatwebsite\\Excel\\ExcelServiceProvider::class, ] \u0026#39;aliases\u0026#39; =\u0026gt; [ ... \u0026#39;Excel\u0026#39; =\u0026gt; Maatwebsite\\Excel\\Facades\\Excel::class, ] 最後，publish config\nphp artisan vendor:publish --provider=\u0026#34;Maatwebsite\\Excel\\ExcelServiceProvider\u0026#34; 二. 匯出一個基本的excel資料 # Laravel Excel 提供多種快速的匯出功能，本次介紹最容易上手的From View\nFrom View，共名思義就是透過view blade的表格資料直接mapping到excel table中\n在App\\Exports 建立一個匯出資料的class\nnamespace App\\Exports; use Illuminate\\Contracts\\View\\View; use Maatwebsite\\Excel\\Concerns\\FromView; class InvoicesExport implements FromView { public function view(): View { return view(\u0026#39;exports.invoices\u0026#39;, [ \u0026#39;invoices\u0026#39; =\u0026gt; Invoice::all() ]); } } 簡單說明就是透過Invoice Eloquent取出所有資料，並且放入export.invoice view中\n接著在 resource\\view\\export 中建立invoice.blade.php，用來顯示資料\n\u0026lt;table\u0026gt; \u0026lt;thead\u0026gt; \u0026lt;tr\u0026gt; \u0026lt;th\u0026gt;Name\u0026lt;/th\u0026gt; \u0026lt;th\u0026gt;Email\u0026lt;/th\u0026gt; \u0026lt;/tr\u0026gt; \u0026lt;/thead\u0026gt; \u0026lt;tbody\u0026gt; @foreach($users as $user) \u0026lt;tr\u0026gt; \u0026lt;td\u0026gt;{{ $user-\u0026gt;name }}\u0026lt;/td\u0026gt; \u0026lt;td\u0026gt;{{ $user-\u0026gt;email }}\u0026lt;/td\u0026gt; \u0026lt;/tr\u0026gt; @endforeach \u0026lt;/tbody\u0026gt; \u0026lt;/table\u0026gt; 內容其實就是一般的view blade\n資料與view都準備好之後就可以在controller中呼叫來下載了\nuse App\\Exports\\UsersExport; use Maatwebsite\\Excel\\Facades\\Excel; use App\\Http\\Controllers\\Controller; class UsersController extends Controller { public function export() { return Excel::download(new InvoicesExport, \u0026#39;invoice.xlsx\u0026#39;); } } 在usercontroller中呼叫Excel::download，並且new InvoicesExport Class，第二個參數是指定下載的檔案名稱\n到目前為止已經透過 Laravel Excel 間單的匯出了資料\n三. 透過Drawing 匯出圖片到excel中，並且設定好cell長寬 # 要在excel中插入圖片必須透過PhpSpreadsheet的Drawing 將圖片繪入\n範例如下\npublic function registerEvents():array { return [ AfterSheet::class =\u0026gt; function(AfterSheet $event){ $drawing = new \\PhpOffice\\PhpSpreadsheet\\Worksheet\\Drawing(); $drawing-\u0026gt;setName(\u0026#39;submit_img\u0026#39;); $drawing-\u0026gt;setDescription(\u0026#39;submit_img\u0026#39;); $drawing-\u0026gt;setPath(storage_path(\u0026#39;001.jpg\u0026#39;)); $drawing-\u0026gt;setWidth(\u0026#39;500\u0026#39;); $drawing-\u0026gt;setOffsetX(\u0026#39;10\u0026#39;); $drawing-\u0026gt;setOffsetY(\u0026#39;10\u0026#39;); $drawing-\u0026gt;setCoordinates(\u0026#39;C1\u0026#39;); //設定高度 $event-\u0026gt;sheet-\u0026gt;getRowDimension(2)-\u0026gt;setRowHeight($drawing-\u0026gt;getHeight()); $drawing-\u0026gt;setWorksheet($event-\u0026gt;sheet-\u0026gt;getDelegate()); //調整欄位寬度 $event-\u0026gt;sheet-\u0026gt;getColumnDimension(\u0026#39;A\u0026#39;)-\u0026gt;setWidth(5); $event-\u0026gt;sheet-\u0026gt;getColumnDimension(\u0026#39;B\u0026#39;)-\u0026gt;setWidth(18); $event-\u0026gt;sheet-\u0026gt;getColumnDimension(\u0026#39;C\u0026#39;)-\u0026gt;setWidth(70); }, ]; } 簡單說明一下邏輯，首先我們在第二部分撰寫的InvoicesExport class中必須新增一個function \u0026ldquo;registerEvents\u0026rdquo;，用來調整excel的內容，並且透過AfterSheet 來控制sheet中cell的大小、顏色、邊筐等等設定\n接著針對幾個設定項目來做說明\n$drawing-\u0026gt;setPath(storage_path(\u0026#39;001.jpg\u0026#39;)); //設定圖片的位置，範例是將圖片放在storage\\app\\001.jpg $drawing-\u0026gt;setWidth(\u0026#39;500\u0026#39;); //設定圖片寬度 $drawing-\u0026gt;setOffsetX(\u0026#39;10\u0026#39;); //設定圖片距離cell邊界位移 $drawing-\u0026gt;setOffsetY(\u0026#39;10\u0026#39;); //設定圖片距離cell邊界位移 $drawing-\u0026gt;setCoordinates(\u0026#39;C1\u0026#39;); //設定圖片要放在哪個cell上 $event-\u0026gt;sheet-\u0026gt;getRowDimension(2)-\u0026gt;setRowHeight($drawing-\u0026gt;getHeight()); //設定第二行行高為圖片的高度 $event-\u0026gt;sheet-\u0026gt;getColumnDimension(\u0026#39;A\u0026#39;)-\u0026gt;setWidth(5); //設定A column寬度為5 注意\nregisterEvents 執行時間會在view 之前，所以無法在view中先整理好資料邏輯再由registerEvents來排列excel規格\n四. 遠端圖片該如何插入Excel中 # Drawing只能插入本地端的圖片，但是本次任務是將遠端圖片插入excel中，因此必須先下載圖片儲存後才能插入excel中\n$contents = file_get_contents($url); $name = \u0026#39;001.jpg\u0026#39;; Storage::put($name, $contents); 其實只要透過file_get_contents，去得遠端內容後，再透過Storage就可以輕鬆地將圖片儲存在本地端\n透過Storage::pu，檔案會儲存在Storage\\app 中\n串連以上，就可以透過 Laravel Excel 將遠端圖片插入到excel sheet的指定cell中了！\n","date":"2019年3月9日","externalUrl":null,"permalink":"/2019/03/09/laravel-excel-%E5%8C%AF%E5%87%BA%E7%B2%BE%E7%BE%8E%E7%9A%84excel%E5%9C%96%E6%96%87%E6%AA%94%E6%A1%88/","section":"Posts","summary":"","title":"Laravel Excel 匯出精美的excel圖文檔案","type":"posts"},{"content":"","date":"2019年2月14日","externalUrl":null,"permalink":"/tags/composer/","section":"Tags","summary":"","title":"Composer","type":"tags"},{"content":"","date":"2019年2月14日","externalUrl":null,"permalink":"/tags/packag/","section":"Tags","summary":"","title":"Packag","type":"tags"},{"content":"本篇重點 : 不是教你使用 Laravel Dusk，而是帶你如何看Laravel Dusk套件的原始碼\n講者介紹 : Recca 使用 laravel 開發網站，生性懶惰健忘，認為自動測試是自己還能勉強待在程式業的理由之一。\n延伸閱讀 :\nPHP 也有 Day #41 – 人生不能重來，但 Git 可以之「如何華麗的做一個後悔的動作」\n外部參考連結:\nhttps://speakerdeck.com/starrocket/php-ye-you-day-number-42-laravel-dusk-shi-yong-ji-yan-jiu\n課程大綱\n什麼是laravel Dusk 怎麼配置 優缺點 怎麼測試 什麼是laravel Dusk?\nAuto browser test\n自動由電腦進行瀏覽器測試\n不需要selenium puppeteer testcafe(有包裝facebook/webdrive 已經含有selenium)\n如何安裝?\n看官網，composer install\n在laradock官網下可以配置\nPhp artisan dusk:install 會出現browser資料夾\n*整合測試vs brower test? 一個是測試功能面，一個是完整的瀏覽器測試 包含view js 等等\n缺點：不支援多瀏覽器 docker設置困難 彈性相對較差\nGit hub連結？\nLarval dusk 研究\nComposer.js 宣告套件測試 相依套件\n找到程式進入點 : serviceProvider\nBoot中有三個 Rout::get()套件中沒有說明\ninstallCommand\nHandle() 建立資料夾 處理stubs 建檔 回傳完成 Fire() 不支援 很接近的command (make page component)\nDusk command\n$this-\u0026gt;withDuskEnvironment()\nDusk 與dusk:fail 去執行phpunit\n套件中已經包含chrome drive，三個系統對應的執行檔中都有\n用process直接跑對應系統的執行檔\nassertTitle\n用phpunit::assertEquals 實作\nConcerns ?\nLaradock 裡面去找 dockerfile\n**只看到測試結果還是有過程? 與phpunit相同，只有出錯的時候多做了screenshot有截圖\n總結 :\n套件訊息可以在packige上面找\n通常程式碼在github上同名(但有例外)\n參考前輩別人的寫法思考這樣設計的好處\nseviceprovider是進入點\nSelenium 比起dusk 前期設定要比較久，其餘差不多\n","date":"2019年2月14日","externalUrl":null,"permalink":"/2019/02/14/php-%E4%B9%9F%E6%9C%89-day-42-laravel-dusk-%E4%BD%BF%E7%94%A8%E5%8F%8A%E7%A0%94%E7%A9%B6/","section":"Posts","summary":"","title":"PHP 也有 Day #42 - Laravel Dusk 使用及研究","type":"posts"},{"content":"本篇將介紹幾個實用的小工具，以及自己使用的小撇步來節省打指令的時間\n參考資料 : https://xenby.com/b/160-%E6%8E%A8%E8%96%A6-%E8%A6%96%E7%AA%97%E7%89%88%E4%BB%8B%E9%9D%A2%E5%8C%96docker%E7%AE%A1%E7%90%86%E5%B7%A5%E5%85%B7-kitematic\nhttps://iphone4.tw/forums/showthread.php?t=163827\n如果你還沒有看過前面的文章請先閱讀 :\nMAC安裝Docker\ndocker 容器化 : PHP5.6專案容器化開發實作\nlaradock : laravel專案容器化開發實作\nKitematic : # Kitematic是一套圖像化的docker管理套件，如果你跟我一樣懶得打指令，那麼可以透過這個套件管理目前的docker container 直接到官網上下載即可 列表上左邊就可以看到目前正在跑的docker container 點入docker中可以看到右邊會出現目前的連結縮圖，以及映射資料夾 當然所有的參數設定都可以在\u0026quot;設定\u0026quot;這個tab中處理完成\n懶人指令包 # 如果你覺得圖形化介面太過於不好管理，而且也太過麻煩的話也可以將指令存檔成為.command檔案，點兩下讓他執行就可以了 .command檔在MAC中就像是window的.bat一樣，可以執行像要的指令集合，舉例來說\ndocker run -d -p 8800:80 --name apache_service -v /var/Documents/PHP/my_project:/var/www/html -v /var/Documents/PHP/logs:/var/log/apache2 my_apache 如果每次都需要打這麼多指令還真的有點煩人，因此我將檔案存成 project.on.command 放在桌面上 接著給予他執行的權限\nchmod 755 ./project.on.command 那麼只要滑鼠按兩下docker就跑了起來，相對的還可以做一個project.off.command\ndocker stop apache_service docker rm apache_service 點兩下就可以把docker關閉，這樣似乎比較適合開發人員 3. ## 常用指令\n#docker 啟動 docker run IMAGE_NAME #docker 停止 docker stop NAME #docker-compose 啟動 docker-compose up #docker-compose 停止 docker-compose down #查看所有docker docker ps -a #停掉所有docker docker stop $(docker ps -a -q) docker rm $(docker ps -a -q) ","date":"2019年1月24日","externalUrl":null,"permalink":"/2019/01/24/docker-%E5%B0%8F%E5%B7%A5%E5%85%B7-kitematic-%E8%88%87-%E6%87%B6%E4%BA%BA%E6%8C%87%E4%BB%A4%E5%8C%85/","section":"Posts","summary":"","title":"docker 小工具 : Kitematic 與 懶人指令包","type":"posts"},{"content":"本篇將紀錄如何透過 laradock 將laravel專案容易化的過程\n參考資料 :\nhttps://medium.com/@yfancc20/laradock-%E8%BC%95%E9%87%8F-laravel-%E7%92%B0%E5%A2%83%E7%9A%84%E5%98%97%E8%A9%A6-%E5%B8%B8%E8%A6%8B%E9%8C%AF%E8%AA%A4-2fc6f0c21433\nhttps://adon988.logdown.com/posts/7800794-docker-installation-and-laradock-introduction\n如果你還沒有看過前面的文章請先閱讀 MAC安裝Docker docker 容器化 : PHP5.6專案容器化開發實作\nLaradock安裝 # 安裝的步驟將會是直接從git hub上clone下來，如下面指令\ncd /var/www git clone https://github.com/Laradock/laradock.git 先切換到/var/www/ 資料夾，然後將laradock專案clone下來就會在www/資料夾底下產生一個laradock資料夾 接著進到laradock資料夾內，將預設的env-example改名為.env檔案\ncd laradoc/ cp env-example .env 注: 這裡的.env是laradock所使用的.env，並非laravel專案中的.env檔案，千萬別弄混了 接著透過docker-composer 將所需要的docker image一次全部叫起來，舉例來說，我需要nginx mysql phpmyadmin 三個環境\ndocker-compose up -d nginx mysql phpmyadmin 要注意的是 : workplace可以提供近到docker虛擬機中下指令，但是workplace 容器會動起來不需要加在指令中，接下來的時間你可以去到一杯咖啡，也可以躺一下因為需要一些時間才能把所有相關的image都下載並且執行起來 如果完成了你應該會在畫面上看到類似這樣的訊息\nStarting laradock_workspace_1 ... done Creating laradock_mysql_1 ... done Creating laradock_phpmyadmin_1 ... done Starting laradock_php-fpm_1 ... done Creating laradock_nginx_1 ... done 這時候，其實已經完成，我們可以在剛剛修改檔名的.env中找到，docker所對應的其實是把laradock所在的目錄上一層對應到docker中的/var/www 2. ## 進入到workplace中\n接著，我們可以打開另一個終端機畫面(也可用同一個啦)，透過指令進入到workplace中\n\u0026gt; docker-compose exec workspace bash :/var/www# 舉例來說，如果要新建一個Laravel project，可以透過workplace中的composer指令來完成\ncomposer create-project --prefer-dist laravel/laravel my_project 這時候你會發現，在本機的laradock資料夾旁邊也出現了一個my_project的資料夾名稱，換句話說，如果是既有的專案可以直接放到與laradock同一層資料夾就可以正確執行了 以上面的例子來看，打開瀏覽器輸入網址 http://localhost/my_priject/public/ 應該就可以看到laravel的初始畫面了\n3. ## 自由調整你想要啟用的docker images\n比如說，我不想要用nginx，要用apache，而且還會用到redis，那麼就可以透過這樣的指令來完成啦\ndocker-compose up -d apache2 mysql phpmyadmin redis ","date":"2019年1月21日","externalUrl":null,"permalink":"/2019/01/21/laradock-laravel%E5%B0%88%E6%A1%88%E5%AE%B9%E5%99%A8%E5%8C%96%E9%96%8B%E7%99%BC%E5%AF%A6%E4%BD%9C/","section":"Posts","summary":"","title":"laradock : laravel專案容器化開發實作","type":"posts"},{"content":"本篇將紀錄如何將PHP5.6專案移至docker中開發，並且重新包裝docker image加入需要的php extension\n參考資料 :\nhttps://www.minwt.com/webdesign-dev/html/20606.html\nhttps://adon988.logdown.com/posts/7800794-docker-installation-and-laradock-introduction\nhttps://ithelp.ithome.com.tw/articles/10191016\n如果你還沒有看過前面的文章請先閱讀 : MAC安裝Docker\n選擇適當的docker image # 要將PHP專案放入docker中運行我們可以直接選擇現成的docker image來跑就行了，所以可以在docker hub上找到許許多多與PHP相關的image，理論上直接pull下來都可以執行， 我這邊挑選image的重點有兩個，1 最好是official 官方的image 2 版本一定要符合 所以在docker hub上找到了由官方提供的PHP + Apache的image，而且幾乎所有版本都有 點進去後就可以看到右手邊有提示如何開始 : docker pull php 但是呢我們必須要指定使用PHP的版本因此可以使用\ndocker pull php:5.6-apache 當然也可以直接docker run image，反正local找不到會自己上docker hub尋找image\ndocker run -d -p 8800:80 -v /var/html:/var/www/html php:5.6-apache --name my_apache 但是，很遺憾的跑當然能跑，這時候只要在電腦的/var/html/資料夾中加入一個info.php(\u0026lt;?php phpinfo(); ?\u0026gt;) 然後打開瀏覽器 : http://localhost:8800/，就可以看到下面的畫面\n代表PHP可以正確執行，但是實際將專案放入資料夾中會發現，確實可執行但是少了許多的PHP 以及apache 的extension 所以，我們必須開始著手把缺少的extension加入到image中才可以讓專案正常的跑起來，接下來的範例將舉例把pdo mysql_pdo mysql mysql_li 這些extension以及apache urlrewrite mode加入到image中 2. ## 重新打包docker image\n首先，需要準備一個名為Dockerfile的文字檔，內容描述要在哪個image中加入哪些指令，舉例來說我們在/var/docker/ 這個資料夾中加入Dockerfile檔案，內容如下\nFROM php:5.6-apache RUN docker-php-ext-install mysqli pdo pdo_mysql mysql RUN a2enmod rewrite FROM 表示要以哪個docker image為基礎重新包中 RUN docker-php-install 表示要加入的php extension RUN a2enmod rewrite 表示開啟apache的 url rewrite功能 檔案存檔後接下來透過終端機切換到這個檔案的目錄\ncd /var/docker/ 接著透過下面的指令重新包裝docker image\ndocker build -t my_apache . --no-cache 其中my_apache是重新包裝過後的docker image name，可以自由命名，\u0026ndash;no-cache表示不要讀取快取檔案 接下來就等執行完畢就可以重新使用新的image了 3. ## 使用新的docker image\n接下來當然就是用重新包裝過的image來跑了，舉例來說像是下面的指令\ndocker run -d -p 8800:80 --name apache_service -v /var/Documents/PHP/my_project:/var/www/html -v /var/Documents/PHP/logs:/var/log/apache2 my_apache \u0026ndash;name 表示docker 容器跑起來後的名稱，可以透過docker ps -a 中查詢到 兩個-v 分別是把專案資料夾映射到docker中的document root以及把docker中的apache log帶回到本機資料夾，方便查閱錯誤訊息 最後的my_apache就是重新包裝過的docker image名稱了 最後就可以在瀏覽器中透過http://localhost:8800/ 看到你的網站了，而且extension也已經正確啟動了 ","date":"2019年1月17日","externalUrl":null,"permalink":"/2019/01/17/docker-%E5%AE%B9%E5%99%A8%E5%8C%96-php5-6%E5%B0%88%E6%A1%88%E5%AE%B9%E5%99%A8%E5%8C%96%E9%96%8B%E7%99%BC%E5%AF%A6%E4%BD%9C/","section":"Posts","summary":"","title":"docker 容器化 : PHP5.6專案容器化開發實作","type":"posts"},{"content":"","date":"2019年1月17日","externalUrl":null,"permalink":"/tags/php-5.6/","section":"Tags","summary":"","title":"Php 5.6","type":"tags"},{"content":"","date":"2019年1月15日","externalUrl":null,"permalink":"/tags/line/","section":"Tags","summary":"","title":"Line","type":"tags"},{"content":"很難得有機會可以有機會參加這類型的活動，本篇以當天議程筆記為主\n參考連結:\nhttps://techpulse.line.me/\n本篇共分成以下幾個章節\nLine的下個階段發展策略 Line Platform API 打造chatbot Line Pay 一卡通支付新體驗 Line smart portal 台北市政府使用體驗 Line Things IoT平台 Line 區塊鏈 ：代幣生態系統 ，LINK Chain \u0026amp; LINK介紹 Line smart portal 防災整合應用 : 行動化防災資訊運用 Line today 團隊，敏捷開發測試技巧 line的私有雲中 Kubernetes Line chatbot 活動報名報到設計 日本開發者大會分享 Line protostart Line 技術推廣 Line 技術應用分享 - 實戰經驗分享 Line的下個階段發展策略 # a. 串連 : 打造平台生態系，線下生態系，下個階段 :探索以及人性化介面，更人性化的人機介面，AI，重點發展項目之一\nb. 互惠生態圈 : Open API, LIFF : 嵌入聊天室中，chatbot engine 可以重複訓練AI模型，區塊鏈, 數位貨幣LINK Chain\n** Clova AI tech demo : https://clova.ai/techdemo\n** API費用? 2019將推出官方帳號2.0計畫（明年）以訊息量計費 ** Reuse 開發方式 ? 服務應用 —\u0026gt; 平台 —\u0026gt;找合作夥伴 —\u0026gt; API\nLine Platform API 打造chatbot Line developer 官方網站 : https://developers.line.biz/en/ # Line訊息格式:\nFlex message : 更有彈性的呈現訊息 Video imagemap : 可以支援影音格式內容 Quick reply : 預設提供回覆按鈕 LIFF : web應用直接放到message視窗中，也可以直接填寫表單 techpulse 2018 techpulse 2018 techpulse 2018 Line與用戶如何連結?\nSOCIAL API LINE LOGIN 做帳號綁定 Message api , account link 如果用戶沒有綁定，透過Matching機制比對同一個人 Switcher API : 在不同endpoint 切換，舉例 ：客服中機器人與真人切換 ICON Switch API 可用工具 :\nBot designer : 打造chatbot Flex simulator : 更有效率掌控訊息呈現的樣子 LIFF Console : 官方developer中直接有介面 下個階段發展:\nNew official account 計畫 ：所有account都有messenger api功能 , 以量計價 Chatbot engine : new official account中自帶，自動偵測語意分析 Line Pay 一卡通支付新體驗 # 原本只有信用卡+ line points，加入一卡通後變成ipass電子支付 + 電子票證\n一卡通加入的部分是 ：ipss電子支付\n**Line pay 模組 與 ipass模組完全獨立 從後段到client全部獨立\n**line無法存取一卡通服務用戶資料\ntechpulse 2018 新加入的功能 :\n轉帳 ，用戶間轉帳免手續費 一對一轉帳與一對多分帳功能 生活繳費 : 手機直接掃描條碼繳費，也可直接綁定車號 水號 會推播提醒繳費 乘車碼 : 手機QRCode可以直接掃描搭高雄捷運，進站速度與硬體更新是問題，閘門是offline 用async 到伺服器 Line wallet : 集合消費資訊（錢包可以做到的事情） 一卡通帳戶可以直接連結銀行帳戶 : 可以自動儲值，也可以ATM匯款，也可以即時提領入帳 一卡通帳戶中的金額都放在專用信託帳戶中 Line pay付款方式增加可以使用一卡通帳戶付款 Line pay open API : 也直接整合一卡通帳戶付款 Api : resulf 格式 API techpulse 2018 Line smart portal 台北市政府使用體驗 # 對於政府來說 通訊軟體 安全性問題？ 群組問題?(太多了)\n定調 ：把line當成大聲公，line上沒有秘密\n重點 : 重新分類市政服務以及尋找目標客群（59.6%的三民治世代)\n解決民眾的痛點 :\n看不到 : 官方用語跟民眾用語落差 用不到 : 沒有個人化服務 沒跟到 : 錯失申請時機 查不到 : 資訊不連貫，資料散在各局處 改變 : 先了解再設計服務\nChatbot?\n翻轉公務人員思考模式，目前只有一個項目可用 : 助你好孕\nline的優勢 : 轉換成本低，沿襲line的操作模式\n**台北市政府API平台 明年開放\ntechpulse 2018 Line Things IoT平台 # 為什麼需要IOT平台? 不同設備不同APP 不同裝置有不同的通訊協定 使用者體驗不佳，沒有辦法在一個地方管理裝置 使用LINE Things可以直接在對話中設定裝置 LEFF可以直接使用web技術開發就可以了 techpulse 2018 服務架構 Offline ：家電 等等 Online : 有wifi連接能力 techpulse 2018 線上使用案例: 遠端搖控器，門鎖開啟推播，都是透過message api傳送訊息 線下使用: 使用藍牙模組溝通，使用line beacon =\u0026gt; 走到商店附近就傳送推播 techpulse 2018 3. LIFF BLE 掃描qrcade 同意 開始使用 使用javascript開發\nLine 區塊鏈 ：代幣生態系統 ，LINK Chain \u0026amp; LINK介紹 # Line smart portal 防災整合應用 : 行動化防災資訊運用 # 各部會防災資料集中於大平台，透過line即時傳送到民眾手上\n即時警示 訂閱 相關連結\nLine today 團隊，敏捷開發測試技巧 # 團隊面臨的問題 :\n時間短暫 修復問題更消耗時間 重複的工作 techpulse 2018 測試團隊面對送交測試的project是黑盒子，資訊不足所以全部重測\n測試方式 :\n看code 了解結構 修改範圍 techpulse 2018 line測試團隊重視:\nCode coverage ：每個function都有unit test Code review : GitHub code review，每次不超過200行 持續進步 具體方法:\ntechpulse 2018 如何定義工作完成?\ntechpulse 2018 總結:\n掌控每次變化 跟著開發流程跑 Devops 管理 工程師緊密配合 更有效率 techpulse 2018 line的私有雲中 Kubernetes # techpulse 2018 Line chatbot 活動報名報到設計 # 線下服務 :\n捷運站收到公車資訊 超商買貼圖 新北耶誕城line now服務 傳統報名系統使用痛點:\n註冊麻煩，忘記帳號密碼 排隊報到排很久 為了解決這個問題line可以做什麼?\n報名 報到 會場互動 **報名\n從web導回到line 官方帳號中，對話中可以切換rich menu\n在line中就完成所有事情\n報名填表使用LIFF\n優點 : 簡單 容易觸及 容易推播\n**報到\nBeacon : BT4.0應用\nQRCode\nBeacon訊號無法側錄\n**會場互動\n即時訊息 : 即時推播用flex message（JSON file編寫就可以完成排版)\n互動booth : 廣告大型看板，互動完可以傳回line or IOT ：QRcode掃描，訊息回到line上\n問卷 : line官方網站後台直接設計完問卷後推播\ntechpulse 2018 日本開發者大會分享 # **既有service 開發APP?\n以today app為例 :\n快速推出 找到使用者痛點 持續改善 1.0版本直接把web view嵌入APP\n2.0版本改善nav bar 最常用的功能抽出\n3.0版改善影音想關功能體驗，針對video改為原生開發\n如何在分散式資料庫系統中 Kafka如何提高搜尋精準度?\ntoday中的Buzz : 使用者分享的文章\nHbase考慮擴充性與速度\nHbase 沒有transection ,只有Raw key 有index\n建立secondery index table\nKafka ,\n**Line beacon 應用 以及快速建構？\n台北捷運推播\n捷運新聞報 在地化優惠券 拼圖遊戲，不同站點推送拼圖 台北捷運公車資訊 實作方式:\n架構\ntechpulse 2018 **200多顆beacons如何管理\n分到不同的group，每個group有不同的工作\n**Handle beacon event?\n多顆beacon感測到同一個人，同時傳送到後台後，由後台透過keep lock 只傳送一次\n**如何加速？\n有快取機制，分層做查詢\nLine protostart # 透過line技術服務幫助企業打造服務\n針對 資源分配不足問題\n提供給新創團隊line : 免費官方帳號、免費business account(包含message API)、LOGIN line pay api, 技術專家支援\n針對 商業輔導\n中華開發創新加速器協助、協助找資源（創投等等）、CCIA訓練營\n給我創業者被line投資的機會\n針對技術business model 以及line結合的機會做評估(protostars 優先)\n潔客幫 跑跑腿\n如何加入？\n公司五年內 , target是台灣市場\nLine 技術推廣 # Developer community\nLine developer website\nLine engineering blog\n新的API介紹、如何使用、有什麼不一樣、developer小聚介紹\nLine open source software\nLine start oss 快速打造工具\nLine 技術應用分享 - 實戰經驗分享 # **直通國際 - BC hub\nRadis\nLine mock 網站\n**阿甘科技\n主動個人化通知(push api)\n即時個人化查詢(chatbot)\nRich menu api應用(可建立動態選單、個人化內容)\nCall to line 應用 ：客服語音轉到line的功能(透過手機號碼發送到客戶line)\n繳費平台應用 ：生活繳費王 可以繳交各式各樣的費用\n**仁大資訊\n報名系統，蒐集名單\n電子發票模組 ：拍發票 分析內容 線上集點\n","date":"2019年1月15日","externalUrl":null,"permalink":"/2019/01/15/line-taiwan-techpulse-2018-%E9%96%8B%E7%99%BC%E8%80%85%E5%A4%A7%E6%9C%83/","section":"Posts","summary":"","title":"Line taiwan techpulse 2018 開發者大會","type":"posts"},{"content":"php-fpm restart , php short open tag 昨天踩了兩個小坑，記錄一下防範未然\n參考連結:\nhttps://www.awaimai.com/2391.html\nhttps://stackoverflow.com/questions/2185320/how-to-enable-php-short-tags\n如果你還沒有閱讀過之前的文章，請先參考\nPHP 版本更新, Apache上如何設定\n以及\nPHP 在Apache 上跑多個版本，以PHP 5.6與PHP 7.1為例\n1. php-fpm restart # 正常來說，我們修改了php.ini後應該要把apache 或者Nginx重新啟動後就可以讀取到新的php.ini設定\n但是如果你使用php-fpm跑了多個版本的php在同一台伺服器上，那麼就必須要連同php-fpm都重新啟動，否則是沒有辦法讀到新的php.ini\n首先，php.ini的位置，以5.6版本的php-fpm來說會在\n/etc/php/5.6/fpm/php.ini 當然不同的版本就會存在不同的資料夾內，例如php 7.1就會存在\n/etc/php/7.1/fpm/php.ini 編輯完成之後，接著必須重新啟動相對應版本的php-fpm，以php 5.6來說就必須要重新啟動php-fpm5.6\n下方條列幾項常用的指令\nsudo service php5.6-fpm start #啟動 sudo service php5.6-fpm stop #停止 sudo service php5.6-fpm restart #重啟 sudo service php5.6-fpm reload #重載 2. php short open tag # 一直以來都不知道short open tag是需要開啟的，short open tag指的是可以把\n\u0026lt;?php echo $variable; ?\u0026gt; 寫成\n\u0026lt;?= $variable; ?\u0026gt; 如果你不知道php short open tag開啟了沒，可以用phpinfo()找到這行\nshort open tag 接著在相對應版本的php.ini中找到 short_open_tag\n如果是關閉的會顯示\nshort_open_tag = Off; 請改成\nshort_open_tag = On; 接著重新依照上個步驟重新啟動apache 或者 php-fpm 即可\n","date":"2019年1月11日","externalUrl":null,"permalink":"/2019/01/11/php-fpm-restart-%E8%88%87-php-short-open-tag-%E5%B9%BE%E5%80%8Bphp%E7%9A%84%E5%B0%8F%E5%9D%91/","section":"Posts","summary":"","title":"php-fpm restart 與 php short open tag : 幾個PHP的小坑","type":"posts"},{"content":"","date":"2018年12月26日","externalUrl":null,"permalink":"/tags/git/","section":"Tags","summary":"","title":"Git","type":"tags"},{"content":"Git釐清 - 大家都會用Git，但是很多時候因為不熟悉而造成專案重建之類的鳥事相信大家都遇過，釐清Git的分支與Reset如何使用觀念上會清楚許多\n講者介紹 : 高見龍 ／ 五倍紅寶石 執行長\n外部參考連結 :\nhttps://community.laravel-dojo.com/phptheday/2018-12-20\nhttps://ubin.io/git-talk-x/ 取得密碼 : password\n工商時間 :\n為你自己寫Git : https://gitbook.tw/\n線上教學課程 : https://iamcoding.tw/\n網址縮小燈 : https://ubin.io/\nConcepts : https://concepts.app/zh-hant/\n本章節重點 ： # 關於分支 ：釐清分支的正確觀念、commit 、head與master 關於Reset : 不要被英文翻譯誤導，這裡的reset本意就是goto、become的意思 關於分支: # 分支中，一個圈圈就是一個commit，分支不是一條一條而是一片一片的貼紙，貼在圈圈上，標記目前分支最後一個commit在哪裡 分支無法合併，可以合併的是分支所指向的commit(也就是圈圈)。 分支正確的樣子不要被軟體所誤導，其實並不是很多條，而是一條直線，如下圖 關於Reset: # Reset 就是goto, become 的意思，簡單說就是把貼紙貼到某個commit節點上 Git reset C3(變成C3那個節點) \u0026ndash;harf(多出來的檔案都不要)(後面的參數會決定檔案的去留) 關於Checkout: # Git checkout . （目錄回到最近的commit狀態)（從暫存區拿資料回來) Git checkout 代名詞(或commit序號)或檔案(可以指定就會某個檔案或者回到某個commit) 如何拆掉commit重做: # Git reset HEAD^（回到現在的上一步)（分支往前移一格)等同拆掉目前的commit，其中^代表往前一步，往前兩步可以用^^依此類推，如果步數太多可以用 ~100就是100步的意思 後悔了要重做回到剛剛的狀態可以 git reset C4 \u0026ndash;hard(假設拆掉之前是C4，就是回到拆掉之前)，如下圖 關於合併節點: # C合併D與D合併C的差異在於，誰合併誰誰就往前走，\nex : Ｃ合併D，則C往前走，如果要取消就是把C倒退回去原本的節點(也就是git reset XXX)\n關於Rebase指令: # rebase指的是節點往前拉直，拉直後要回到原來的節點該如何做？有兩種做法\nGit Reset ORIG_HEAD ，回到上個紀錄點上，但是ORIG_HEAD只會紀錄最後一次，如果要回到很多步以前要用下一個方法 Git reflog，列出Git的每個動作，然後用reset回到節點上 如果Hard reset該怎麼辦? # 同上點的做法，Git reflog找到commit序號，然後reset\n如果分支還沒合併就刪掉了該怎麼辦? # 刪除分支只是將貼紙拿下來分支看不到而已，其實還在，一樣可以用reflog找序號，貼回去\n指令 : Git branch new_dog 053(把new_dog分支貼在053節點上)\nQA時間 ： # 什麼是Git Cherry pick? 把其他分之傑點剪過來合併 Checkout vs Reset Checkout只移動head, Reset會聯同master都移動 Merge vs Rebase 從結果看是相同的，Merge會有新的brench最後合併起來，Rebase不會 何時該commit? 做完一個小功能commit或者下班前，做完某件事情滿意了就commit 什麼是Revert? 取消某一個commit 什麼是Fork? 以github為例，fork可以複製整個專案到個人帳號中 什麼是PR? pull request,帶merge，原作可以選擇merge commit, squash mergr, rebase mergr ","date":"2018年12月26日","externalUrl":null,"permalink":"/2018/12/26/php-%E4%B9%9F%E6%9C%89-day-41-%E4%BA%BA%E7%94%9F%E4%B8%8D%E8%83%BD%E9%87%8D%E4%BE%86%EF%BC%8C%E4%BD%86-git-%E5%8F%AF%E4%BB%A5%E4%B9%8B%E3%80%8C%E5%A6%82%E4%BD%95%E8%8F%AF%E9%BA%97%E7%9A%84%E5%81%9A/","section":"Posts","summary":"","title":"PHP 也有 Day #41 - 人生不能重來，但 Git 可以之「如何華麗的做一個後悔的動作」","type":"posts"},{"content":"之前一直有個想法，因為自己會用多台機器或者多台docker架設不同服務，但是80 port跟固定IP只有一個\n如果在不同的機器下以前都是用不同的port來區隔，讓分享器把不同的port轉到不同的機器上來達成需求\n但是一直覺得這樣做不是一個好的做法\n後來找到了reverse proxy可以辦到我想要透過不同的url來區隔在不同docker上的網站\n比如說nax.xxx 連回到nas管理頁面，plex.xxx連線到plex伺服器，www.xxx連到個人網站 等等\n雖然這些都站都存在同一個IP且在同一台機器上，只是服務的docker不同\n因此這次就要用QNAP上的container station 啟動一台Nginx來負責轉接這些服務\n參考資料:\nhttps://blog.techbridge.cc/2018/03/17/docker-build-nginx-tutorial/\nhttps://github.com/vfarcic/continuous-deployment/issues/2\n1. 概念 : # 概念上，我需要不同的URL轉接到不同的port，但是對外都是走80 port，這樣url比較好記\n而且不希望用一台ubuntu架設Ngin來做這件事情感覺吃太多資源了，因此只透過QNAP的caontainer station\nmapping一個nginx.conf設定檔到docker來達成目標\n2. 準備好要給Nginx的config檔 # 這邊並不要是要寫Nginx中sites-available中的檔案，而是必須要寫nginx.conf，參考資料如下\nuser www-data; worker_processes auto; pid /run/nginx.pid; events { worker_connections 768; # multi_accept on; } http { ## # Basic Settings ## sendfile on; tcp_nopush on; tcp_nodelay on; keepalive_timeout 65; types_hash_max_size 2048; # server_tokens off; # server_names_hash_bucket_size 64; # server_name_in_redirect off; #include /etc/nginx/mime.types; default_type application/octet-stream; ## # SSL Settings ## ssl_protocols TLSv1 TLSv1.1 TLSv1.2; # Dropping SSLv3, ref: POODLE ssl_prefer_server_ciphers on; ## # Logging Settings ## access_log /var/log/nginx/access.log; error_log /var/log/nginx/error.log; ## # Gzip Settings ## gzip on; gzip_disable \u0026#34;msie6\u0026#34;; # gzip_vary on; # gzip_proxied any; # gzip_comp_level 6; # gzip_buffers 16 8k; # gzip_http_version 1.1; # gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript; server { # php/fastcgi listen 8879; server_name xxx.alvinchen.club; location / { proxy_pass http://192.168.1.100:10084; # 伺服器程式位址與連接埠 proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection \u0026#39;upgrade\u0026#39;; proxy_set_header Host $http_host; proxy_set_header X-NginX-Proxy true; } } server { # php/fastcgi listen 8879; server_name 123.alvinchen.club; location / { proxy_pass http://192.168.1.100:12300; # 伺服器程式位址與連接埠 proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection \u0026#39;upgrade\u0026#39;; proxy_set_header Host $http_host; proxy_set_header X-NginX-Proxy true; } } } 重點是中間server那塊，我將8879 port上的xxx.alvinchen.club轉到內網中192.168.1.100機器上，port是10084\n另一條是8879 port上的123.alvinchen.club轉到內網中192.168.1.100機器上，port是12300\n這樣就達成我要的透過不同網址來轉發不同的服務\n最後，檔案存檔成nginx.conf然後儲存在路徑：\n/share/CACHEDEV1_DATA/ContainerStation/container-station-data/reverse_proxy 之後我們要把這個路徑mapping到docker中\n3. 在Container Station中建立一台Nginx # 首先搜尋nginx，我是裝docker hub上的最新版本\n名稱上可以自己隨意輸入，為了好記我就打上nginx-for-reverse_proxy，也為了不要佔太多資源CPU以及RAM都降低\n接下來，很重要的一點是網路的部分一定要使用Bridge，然後給一個固定IP，最後才能設定分享器將8879 port轉到.253上\n接下來是最重要的部分，我們必須將剛剛的nginx.conf所在資料夾 mapping到docker中的/etc/nginx資料夾\n等同執行docker run 時的-v\n幾秒鐘後就可以看到總覽中有一個docker跑起來了\n這時候就可以去看一下xxx.alvinchen.club:8879 以及123.alvinchen.club:8879是不是已經轉到想要的位置上了\n到這個步驟為止，應該已經沒有問題\n最後補充一下如果要修改nginx.conf怎麼辦呢？\n可以安裝Text Editor，就可以直接在FileStation中直接對檔案做編輯，像這樣子\n以上希望對於Container Station或者Docker不熟悉的使用者有一些幫助\n","date":"2018年11月6日","externalUrl":null,"permalink":"/2018/11/06/reverse-proxy-on-nginx-docker-%E4%BD%BF%E7%94%A8qnap-container-station/","section":"Posts","summary":"","title":"Reverse Proxy on Nginx docker 使用QNAP Container Station","type":"posts"},{"content":"MAC安裝Docker，基於容器化的開發前提下，漸漸的需要去熟悉docker的操作才行，雖然NAS上已經提供了很簡易的Docker操作環境\n但畢竟還是有很多限制在，因此開發環境還是在本機裝上docker吧～\nMAC上安裝Docker其實超級容易，有兩種安裝方式\n1.透過Homebrew安裝\nbrew cask install docker 2.手動下載安裝\nhttps://store.docker.com/editions/community/docker-ce-desktop-mac\n登入docker store後下載img檔\n點開後將圖示拉進Application即可，就跟一般的應用程式安裝相同\n安裝完成後在應用程式中找到docker圖示點選就可以執行\n開始執行後，會在工作列上看到鯨魚的圖示\n這時候就可以透過終端機來檢查一下docker的版本啦，順帶一提，mac的安裝版本都是已經包含docker-compose的，所以不必另外安裝docker-compose\ndocker --version docker-compose --version docker-machine --version 如果以上指令都沒有問題，那麼就可以開始run docker image啦～\n","date":"2018年10月24日","externalUrl":null,"permalink":"/2018/10/24/mac%E5%AE%89%E8%A3%9Ddocker/","section":"Posts","summary":"","title":"MAC安裝Docker","type":"posts"},{"content":"Uninstall MySQL ? 會有這次的主題是因為前幾天重新安裝系統後下載MySQL的時候沒有注意到版本，原來已經到了MySQL 8\n但是MySQL 8除了帶來更好的效能外，也帶來了很多未知的坑，因此決定將MySQL完整移除後重新安裝MySQL 5\n步驟一 : 由新增移除程式解除安裝\n點選到控制台 \u0026gt; 新增移除程式\n將所有MySQL相關的全數移除\n步驟二 : 找出這些路徑把資料全數刪除\nC:\\Program Files\\MySQL C:\\Program Files (x86)\\MySQL C:\\ProgramData\\MySQL C:\\Users\\[User-Name]\\AppData\\Roaming\\MySQL 把上述路徑資料夾全數刪除，如遇到無法刪除的檔案請重新開機後再刪除\n步驟三 : 重新啟動windows\nWindows 重新啟動後，即可安裝新的MySQL\n","date":"2018年10月4日","externalUrl":null,"permalink":"/2018/10/04/uninstall-mysql-on-windows/","section":"Posts","summary":"","title":"Uninstall MySQL On Windows","type":"posts"},{"content":"Facebook API 今年八月二號後FB對於API取得權限的審核又變得更嚴格了，原本已經取得的權限也因為規則改變而必須要重新送審\n本次重新送審了user_gender, user_birthday, user_likes\n送審除了必須要描述清楚如何使用FB權限以外，還必須針對各權限拍攝使用影片強調該權限如何提升使用者體驗\n而且必須非常明確的提供對比才比較容易通過，下面將針對不同的權限紀錄送審過程\n1. Facebook登入 # 一開始，要使用全部的權限之前必須要先登入，而FB登入也必須遵守文件上描述的使用規範才行\n大概需要遵守以下幾點：\n必須清楚使用Facbook規範按鈕以及顏色 登入前必須清楚說明所要取得的權限用途說明 為此，我們做了以下改變\n修改登入按鈕顏色以及文字以符合FB規範(使用藍色以及圓角) 修改登入流程，先顯示各權限使用規範文字再讓使用者按下登入按鈕 參考連結 ：https://developers.facebook.com/docs/facebook-login/review/examples\n2. user_gender # user_gender 可以取得使用者的性別資料，根據FB的文件說明只能使用在以下三種狀況\n用於正確呈現代詞，例如：\u0026ldquo;他\u0026quot;或\u0026quot;她\u0026rdquo; 像其他人顯示性別，例如：用於約會 個人化用戶體驗，例如：依據不同性別顯示不同產品 本次送審依據的就是第三點，透過不同的性別來顯示不同的用戶活動資料，所以我們必須在影片中清楚顯示兩個不同的性別會有什麼樣的差距\n所以影片中我們先登入男性帳號，再登入女性帳號\n然後透過文字清楚描述兩者的不同\n3. user_birthday # user_birthday 可以取得使用者的生日資料，根據FB的文件說明只能使用在以下兩種狀況\n根據生日提供年齡相關內容 為年齡不足的人提供相關的內容 但是如果只是要檢測是否滿18隨這樣的條件則需要使用user_range\n本次送審依據的就是依照生日提供不同年齡層的內容給使用者，因此我們很清楚地用兩段影片來呈現不同年齡的用戶所看到不同的內容\n4. user_likes # user_likes 可以取得使用者按過哪些粉絲團的讚，根據FB的文件說明只能使用在以下兩種狀況\n透過使用者的興趣來提供不同的內容 根據相同的興趣做用戶的連結 但是不能用來檢查用戶是否對某個粉絲團按過讚\n本次送審依據就是根據不同的興趣的用戶提供不同的內容給使用者，因此我們很清楚地用兩段影片來呈現不同興趣的用戶所看到不同的內容\n以上幾個步驟完成後應該就會進入下個階段\u0026quot;商家\u0026quot;的審查\n商家的審查不難，只要申請好企業管理平台，並且依照提示上傳公司相關證明文件\n通常都是會通過的，除非你跟我一樣企業管理平台是前一手已離職同事申請的然後拿不到帳號\u0026hellip;.(這又是另一個故事了～\n","date":"2018年10月1日","externalUrl":null,"permalink":"/2018/10/01/facebook-api-%E6%AC%8A%E9%99%90%E9%80%81%E5%AF%A9%E6%95%B4%E7%90%86/","section":"Posts","summary":"","title":"Facebook API 權限送審整理","type":"posts"},{"content":"","date":"2018年9月27日","externalUrl":null,"permalink":"/tags/apache/","section":"Tags","summary":"","title":"Apache","type":"tags"},{"content":"前陣子的文章已經提過 如何在Apache上安裝SSL憑證，如果還沒看過請先參考這篇\n讓你的 apache支援ssl\n這次要介紹的重點在於TWCA的憑證，以及Nginx的處理方式\n1. 伺服器上產生CSR檔以及Key並提交至TWCA # openssl req -new -newkey rsa:2048 -nodes -keyout yourdomainname.key -out yourdomainname.csr 務必保留好CSR以及Key，並且將CSR提交至TWCA上，詳細步驟可參考上篇文章\n當收到TWCA核發的憑證時會有以下幾個檔案\n主機憑證：root.cer 網域憑證：server.cer 中繼憑證1：uca_1.cer 中繼憑證2：uca_2.cer 2. 在Apache上安裝 # 要在Apache上安裝必須先處理uca檔案，我們需要把uca_1.cer以及uca_2.cer合併成uca.cer\ncp uca_2.cer uca.cer ; cat uca_1.cer \u0026gt;\u0026gt; uca.cer 請注意順序，先uca_2再uca_1，當然也可以透過編輯器將兩者內容貼在同一個檔案就好\n接著編輯Apache設定檔增加資料如下\nSSLEngine On SSLCertificateFile /etc/ssl/server.cer SSLCertificateKeyFile /etc/ssl/server.key SSLCertificateChainFile /etc/ssl/uca.cer 當然路徑需要依據檔案的位置而定，接著重開Apache即完成\n3. 在Nginx上安裝 # 要在Nginx上安裝有一些不同，需要合併server,cer , uca_1.cer 以及uca_2.cer 三個檔案\ncp server.cer server.pem;cat uca_2.cer \u0026gt;\u0026gt; server.pem ; cat uca_1.cer \u0026gt;\u0026gt; server.pem 注意順序是server再uca_2再uca_1，相同的也可以用編輯器將內容貼成一個檔案\n接著編輯Nginx設定檔\nssl on; ssl_certificate /etc/ssl/server.pem; ssl_certificate_key /etc/ssl/server.key; ssl_session_timeout 5m; ssl_protocols SSLv3 TLSv1 TLSv1.1 TLSv1.2; ssl_ciphers \u0026#34;HIGH:!aNULL:!MD5 or HIGH:!aNULL:!MD5:!3DES\u0026#34;; ssl_prefer_server_ciphers on; 要注意的是server.key就是一開始與CSR一起產生的那個檔案\n","date":"2018年9月27日","externalUrl":null,"permalink":"/2018/09/27/apache-nginx%E4%B8%8A%E5%AE%89%E8%A3%9D-twca%E6%86%91%E8%AD%89/","section":"Posts","summary":"","title":"Apache Nginx上安裝 TWCA憑證","type":"posts"},{"content":"上週有幸參加講座，本篇文章大致上以當天聽到且記錄下來的筆記分享為主\n參考資料 ：https://www.slideshare.net/LaravelDojo/effective-cloud-native-design-with-php-on-aws/\nhttps://www.facebook.com/permalink.php?story_fbid=10156328298660469\u0026id=660510468\u0026hc_location=ufi\nA. 概念 ： # 新世代架站盡可能地簡單、抽象、height level，把硬體、作業系統、伺服器等較為低階的概念全部抽象畫，盡可能專注在自己的程式碼邏輯開發上\nB. 工具 : # openResty : 抽象層認證，由nginx開發，通常介於load balance之下，php伺服器之上，且可以直接存取radis當作reverse proxy使用，降低主要伺服器負擔 Container : 抽象容器，輕量化的VM，可撇除OS層級問題只要開發的Container與正式服務的Container相同就不會有問題，容器化後將會focus在服務之間如何溝通 PHP + Nginx 以及 PHP + Caddy(go語言寫的) 都可以找到docker image C. 多個container如何管理 # (如何跑一堆的docker? 總不能一個一個docker run ==\u0026gt; Container Orchestration)\nAWS ECS , Google Kubernetes, Amazon EKS, AWS Fragate mode Fragate CLI : 提供命令介面直接跑起Nginx或其他container Amazon API getway : 是一個空殼網址，其他可以自行定義，可以防止ddos等攻擊，也可做service intergation，也可以直接將運算服務放在公司伺服器由API gateway呼叫 AWS Lambda : 10毫秒內啟動的計算服務，收費只收有運算的時間，11月更新將加入PHP等其他語言 D. 總結 ： # 代碼越少越好\n抽象主機 作業系統 等等會分心的東西，專注寫好核心代碼\n打造Even-driven服務(用Lambda function)\nBe a builder, not a coder\n","date":"2018年8月27日","externalUrl":null,"permalink":"/2018/08/27/aws-%E6%96%B0%E4%B8%96%E4%BB%A3%E6%9E%B6%E7%AB%99%E6%A6%82%E5%BF%B5%E5%88%86%E4%BA%AB/","section":"Posts","summary":"","title":"AWS 新世代架站概念分享","type":"posts"},{"content":"MySQL備份 大家都知道可以使用mysqldump，但是應該沒有人會記得天天自己上去機器備份吧\n因此要做到自動備份就要仰賴作業系統，無論是windows排程還是Linux corntab\n接著除了自動備份以外，如果備份檔案還是存在於同一台機器上，那麼當機器無法連線時再多的備份都無用\n因此今天還要再加入自動備份到遠端機器上\n接下來的動作會用到幾項東西\nmysqldump : 備份用應該沒有疑慮 crontab : Linux排程器 ssh遠端免密碼設定：要讓script直接ssh將檔案送至遠端所以無法設定密碼驗證，需要透過金鑰 如果你還沒有看過架站文章請先參考\nubuntu基礎架站\n參考文件 ：\nhttps://gwokae.mewggle.com/wordpress/2010/08/%E8%B6%85%E6%98%93-%E5%85%A9%E8%A1%8C%E6%8C%87%E4%BB%A4%E6%90%9E%E5%AE%9A-linux-ssh%E7%99%BB%E5%85%A5%E5%85%8D%E5%AF%86%E7%A2%BC/\nhttps://blog.lupopi.com/2012/10/mysql-sq.html\nhttps://code.kpman.cc/2015/02/11/%E5%88%A9%E7%94%A8-crontab-%E4%BE%86%E5%81%9A-Linux-%E5%9B%BA%E5%AE%9A%E6%8E%92%E7%A8%8B/\nhttps://stackoverflow.com/questions/19664893/linux-shell-script-for-database-backup\n情境說明：\n本次要做的範例是將一台Softlayer虛擬伺服器上的MySQL在每天凌晨2點時自動備份到我房間的NSA主機上，至於NAS如何開啟SSH在這邊就不多做說明，每個品牌不同可以自行google搜尋\n步驟一 ：先設定遠端SSH免密碼 # 為什麼要做這個步驟？首先因為要透過shell scrip直接用SSH將檔案送到遠端伺服器，因此必須要將金要先設定好讓Softlayer主機可以直接發送SSH到NAS上，所以我們要設定Softlayer的金鑰送到NAS上儲存\n指令一 ：Softlayer產生金鑰組\nssh-keygen -t rsa 指令二 ：將金鑰組設定到NAS的信任清單中\ncat ~/.ssh/id_rsa.pub | ssh nas-user@nas-host \u0026#34;cat \u0026gt;\u0026gt; ~/.ssh/authorized_keys\u0026#34; 指令三 ：因為金鑰可編輯有點危險，因此必須設定唯讀權限\nssh nas-user@nas-host \u0026#34;chmod 400 ~/.ssh/authorized_keys\u0026#34; 步驟二 ：撰寫shell script檔案 # 接著必須寫一個sh檔，讓crontab定期去執行它\n#!/bin/sh now=\u0026#34;$(date +\u0026#39;%d_%m_%Y_%H_%M_%S\u0026#39;)\u0026#34; FILENAME=\u0026#34;filename_$now.sql.gz\u0026#34; mysqldump -u USER -pPASSWORD --routines DBNAME | gzip -c | ssh nas-user@nas-host \u0026#39;cat \u0026gt; /PATH_TO_STORE/\u0026#39;$FILENAME exit 0 要注意的地方有幾個\n我是用時間去組合備份檔名，這部分可以自由設計 USER : 代表登入mysql的帳號 PASSWORD : 代表登入mysql的密碼，-P與PASSWORD之間並沒有空白 \u0026ndash;routines : 代表著需要連stored procedure都匯出 後面的意思就是將備份檔案壓縮起來之後透過SSH送到遠端，接著利用cat將檔案放到你要的位置上 最後存檔為mysql.sh\n步驟三 ：必須要讓mysql.sh具有執行權限 # 透過指令修改mysql.sh的權限\nchmod +x mysql.sh 步驟四：設定crontab # 透過crontab -e 打開檔案編輯，本次範例加入一行\n0 2 * * * bash PATH_TO_SH/mysql.sh 讓系統在每天的2點去執行 bash PATH_TO_SH/mysql.sh\n當然你也可以手動執行sh檔，就會在預計要儲存備份檔的機器看到.gz壓縮檔了\n","date":"2018年7月31日","externalUrl":null,"permalink":"/2018/07/31/mysql%E5%82%99%E4%BB%BD-mysql-%E5%AE%9A%E6%9C%9F%E8%87%AA%E5%8B%95%E5%82%99%E4%BB%BD%E5%88%B0%E9%81%A0%E7%AB%AF%E6%A9%9F%E5%99%A8/","section":"Posts","summary":"","title":"MySQL備份 MySQL 定期自動備份到遠端機器","type":"posts"},{"content":"Photon 是由Jetpack提供的免費圖片分流\n上一篇跟大家介紹過了分流解法第一招 ：CloudFlare 免費 WordPress 分流外掛，最佳化設定\n今天要介紹的部分是wordpress獨有而且是免費的CDN分流\n參考文件 ： https://free.com.tw/jetpack-photon-cdn/\n首先，必須先說明的項目有\nPhoton無快取失效時間：目前的圖片快取時間為「永久」，如果你想更新某張圖片，必須重新命名圖片。 這項服務只支援 JPG、GIF、PNG 三種圖片格式，且 Port 為 80 的鏈結 無法放大圖片，如果你的圖片原始寬度為 1000px，我們不會提供你 5000px 的圖片大小 如果你的伺服器回應時間超過 10 秒以上，Photon 會放棄連線，可能會出現破圖情形。若有此情形，可以試著把圖片縮小 步驟一 ： 首先把Jetpack外掛安裝起來 # 安裝啟用之後必須連結到wordpress.com的帳號，如果還沒有帳號可以趁這時候申請一個\n步驟二 ： 找到這個設定\u0026quot;由我們提供圖片\u0026quot;，打開就行了 # 接著我們隨便打開一篇文章看到圖片的連結已經變成 https://i2.wp.com/\n開頭的就代表已經完成了喔\n","date":"2018年6月13日","externalUrl":null,"permalink":"/2018/06/13/photon-%E5%85%8D%E8%B2%BBwordpress-cdn%E5%9C%96%E7%89%87%E5%88%86%E6%B5%81/","section":"Posts","summary":"","title":"Photon 免費wordpress CDN圖片分流","type":"posts"},{"content":"","date":"2018年6月13日","externalUrl":null,"permalink":"/tags/wordpress/","section":"Tags","summary":"","title":"Wordpress","type":"tags"},{"content":"","date":"2018年6月13日","externalUrl":null,"permalink":"/categories/wordpress/","section":"Categories","summary":"","title":"WordPress","type":"categories"},{"content":"網站名稱 : Advise 台灣健康促進學校問卷調查系統\n開發年分 : 2013年\n開發語言 : PHP -CakePHP framework\n資料庫 : MSQL\n運行環境 : Windoes Server 2012 IIS\n正式營運連結 : \u0026ndash;\n作品連結 :http://works.alvinchen.club:8056/cakephp/BTSProject/app/advise/questionnaireList/index/\n內容簡介 : 台灣健康促進學校問卷調查系統是為台灣師範大學客製化製作的專案，問卷系統採每個問卷獨立切版完成，前台提供輔導紀錄單與問卷填答兩個功能，問卷可區分為教師與校長兩個類別，後台僅查看問卷填寫狀況與資料分析匯出。\n前台功能 輔導紀錄單 : 提供輔導委員登入後填寫各學校健康議題輔導紀錄表單\n問卷填答 : 在活動區間內，可以在此區域填答問卷內容\n後台功能 輔導紀錄單 : 可以查看輔導紀錄單填寫狀況，並且提供輸出簡易、完整資料兩種excel表單匯出功能\n問卷列表 : 提供設定目前正在填寫的問卷名稱與位置，前台會顯示相對的標題與連結\n問卷輸出 : 提供問卷填寫結果輸出為excel下載功能\n輔導委員 : 輔導委員名單設定，設定後輔導委員可登入前台輔導委員專區填寫內容，每位輔導委員可以設定負責的區域學校\n管理員 : 後台管理員名單管理\n延伸閱讀 : 其他相關CakePHP作品\n","date":"2018年6月11日","externalUrl":null,"permalink":"/2018/06/11/%E4%BD%9C%E5%93%81cackephp-advise-%E9%9D%9C%E6%85%8B%E5%95%8F%E5%8D%B7%E5%A1%AB%E5%AF%AB%E5%88%86%E6%9E%90%E7%B3%BB%E7%B5%B1/","section":"Posts","summary":"","title":"[作品][CackePHP] Advise 靜態問卷填寫分析系統","type":"posts"},{"content":"","date":"2018年6月11日","externalUrl":null,"permalink":"/tags/cakephp/","section":"Tags","summary":"","title":"Cakephp","type":"tags"},{"content":"","date":"2018年6月11日","externalUrl":null,"permalink":"/categories/cakephp/","section":"Categories","summary":"","title":"CakePHP","type":"categories"},{"content":"網站名稱 : 台灣健康促進學校\n開發年分 : 2012年\n開發語言 : PHP -CakePHP framework\n資料庫 : MSQL\n運行環境 :Windoes Server 2012 IIS\n正式營運連結 : \u0026ndash;\n作品連結 : http://works.alvinchen.club:8056/cakephp/BTSProject/app/hps2/\n內容簡介 : 台灣健康促進學校問卷系統是為台灣師範大學客製化製作的專案系統，問卷可以依照老師需求自行設計題目樣式可以為單選題、多選題、是非題、問答題 等等多種形式，前台可以依照各縣市學生填答狀況顯示以及學生也可以登入前台直接填寫問卷內容\n前台功能 首頁 : 依照各縣市學生問卷填答狀況顯示資料\n年度縣市指標 : 可以依據搜尋條件以及指標類型以及健康議題搜尋以圖表的方式呈現指標\n縣市績效指標 :可以依據搜尋條件以及指標類型以及健康議題搜尋以圖表的方式呈現指標\n教師專區 : 此區域提供老師可以在線上直接編輯問卷內容，編排方式類似Google表單，可以依照需求填寫每個題目的樣式，包含單選題、多選題、是非題、問答題等等\n學生專區 : 此區域提供給學生填答老師編輯好的問卷內容\n學校代表專區 : 學校代表專區提供給學校編輯維護自己的基本資料\n後台功能 對象設定 : 可以設定此系統目標問卷對象\n健康議題 : 設定問卷系統要分析的各大健康議題項目\n指標類型 : 設定問卷指標類型與變項內容\n成果指標 : 設定問卷分析指標，可依照各年度設定不同指標\n新增問卷 : 後台管理員可於此新增問卷內容\n問卷分析 : 此頁面提供各問卷分析統計資料\n會員管理 : 管理網站前台教師、學生、學校會員與後台管理員帳號基本資料\n延伸閱讀 : 其他相關CakePHP作品\n","date":"2018年6月8日","externalUrl":null,"permalink":"/2018/06/08/%E4%BD%9C%E5%93%81cakephp-%E5%8F%B0%E7%81%A3%E5%81%A5%E5%BA%B7%E4%BF%83%E9%80%B2%E5%AD%B8%E6%A0%A1-%E5%95%8F%E5%8D%B7%E7%B3%BB%E7%B5%B1-%E5%8B%95%E6%85%8B%E8%87%AA%E7%94%B1%E7%94%A2%E7%94%9F/","section":"Posts","summary":"","title":"[作品][CakePHP] 台灣健康促進學校 問卷系統 動態自由產生問卷","type":"posts"},{"content":"","date":"2018年6月6日","externalUrl":null,"permalink":"/tags/javascript/","section":"Tags","summary":"","title":"JavaScript","type":"tags"},{"content":"今天要介紹的元件是JQuery的擴充套件，可以透過它列印出網頁上的一個元素，比如說一個\u0026lt;div\u0026gt;的內容，但是下面的內容會更衍生\n範例將會印出一個任意排列好的版型，比如可以排好一張報名表列印出來\n參考文件 ：jQuery Print Plugin\n步驟一 ：下載 JQuery Print 並include到頁面上\n下載Github上的專案，解壓縮後放到自己專案的資料夾上，並且include到頁面中\n\u0026lt;script type=\u0026#34;text/JavaScript\u0026#34; src=\u0026#34;path/to/jquery.print.js\u0026#34; /\u0026gt; 步驟二 ：使用方式\n依照作者的說明文件，可以這麼做，透過element.print()帶入列印參數就可以在瀏覽器上帶出列印畫面\n$(\u0026#34;#myElementId\u0026#34;).print({ globalStyles: true, mediaPrint: false, stylesheet: null, noPrintSelector: \u0026#34;.no-print\u0026#34;, iframe: true, append: null, prepend: null, manuallyCopyFormValues: true, deferred: $.Deferred(), timeout: 750, title: null, doctype: \u0026#39;\u0026lt;!doctype html\u0026gt;\u0026#39; }); 步驟三 ：印出一張表單\n印要出表單我們必須要準備幾項東西\n要印出的表單HTML 給JQuery print列印的element 首先假設我印要出來的表單HTML長這樣子，當然你們可以隨意的排版\n\u0026lt;table\u0026gt; \u0026lt;tr\u0026gt; \u0026lt;td\u0026gt;Name :\u0026lt;/td\u0026gt; \u0026lt;td\u0026gt;\u0026lt;/td\u0026gt; \u0026lt;/tr\u0026gt; \u0026lt;tr\u0026gt; \u0026lt;td\u0026gt;Email :\u0026lt;/td\u0026gt; \u0026lt;td\u0026gt;\u0026lt;/td\u0026gt; \u0026lt;/tr\u0026gt; \u0026lt;/table\u0026gt; 接著下面的步驟將透過JQuery產生一個id=print_area的div，並且將要列印的html放入，print之後再將print_area移除掉\nvar print_area = $(\u0026#34;\u0026lt;div\u0026gt;\u0026lt;/div\u0026gt;\u0026#34;).attr(\u0026#39;id\u0026#39;, \u0026#34;print_area\u0026#34;).html(html); $(\u0026#34;body\u0026#34;).append(print_area); $(\u0026#34;#print_area\u0026#34;).print({globalStyles: false, iframe: false}); $(\u0026#34;#print_area\u0026#34;).remove(); 其中globalStyles: false 代表列印時不帶入網頁中的style，這樣可以避免列印出來的表單因為網頁上的css設定而有不一樣的外觀\niframe: false代表不透過iframe\n以上這個做就會看到瀏覽器另外跳出視窗帶出直接列印的功能啦\n","date":"2018年6月6日","externalUrl":null,"permalink":"/2018/06/06/jquery-print-%E9%80%8F%E9%81%8Ejquery%E5%88%97%E5%8D%B0%E5%87%BA%E8%A1%A8%E5%96%AE/","section":"Posts","summary":"","title":"JQuery Print, 透過JQuery列印出表單","type":"posts"},{"content":"網站名稱 : Mamigogo 2012年版\n開發年分 : 2012年\n開發語言 : PHP -Smarty\n資料庫 : MSQL\n運行環境 :Windoes Server 2012 IIS\n正式營運連結 : \u0026ndash;\n作品連結 : http://works.alvinchen.club:8056/smarty/mamigogo/\n內容簡介 : Mamigogo 網站並不是客戶委託而是自架自行營運的網站，2012年的版本不支援手機瀏覽，功能方面比較著重在寶寶成長檔案的線上製作，僅提供會員自由註冊新增編輯寶寶成長檔案、親子相簿、討論區文章，保母媒合平台等功能，不提供分享到社群網站等功能\n前台功能 首頁 : 經由簡單的區塊顯示網站功能，包含找保母、找家長、親子專區、資源分享以及關於我們\n我是保姆 : 內容由搜尋項目顯示特定條件的家長會員資料，讓保母會員可以透過這樣的媒合平台來找到適合的托育小朋友，當然保母會員也可以自行將資料刊登至媒合區\n我是家長 : 內容由搜尋項目顯示特定條件的保母會員資料，讓家長會員可以透過這樣的媒合平台來找到適合的保母，當然家長會員也可以自行將資料刊登至媒合區\n資源分享 : 由管理員不定期新增圖文資料於此區域，可細分為最新消息、活動訊息以及學習資訊\n親子專區 : 可分為討論區，親子相簿與成長檔案三個部分，原本預留個人部落格功能已於2016年改版時移除\n討論區 : 討論區沒有分類，提供會員與管理員自由發文，文章可由圖文編輯器產生\n親子相簿 : 親子相簿可由會員自由上傳，相簿可自訂名稱與顯示圖片，相簿圖片沒有數量限制\n成長檔案 : 紀錄0 ~ 6歲小朋友各階段發展里程碑與身高體重牙齒飲食等基本資料、並且提供每個月成長檔案上傳相片功能可供紀錄小朋友每個階段的成長回憶，成長檔案具有權限功能，可由托育者決定是否將小朋友資料公開於平台上，未公開的資料則不會顯示\n後台功能 前台管理 : 提供管理員自訂前台資料、包含管於我們、聯絡我們、合作提案、隱私權政策等靜態頁面文字編輯\n媒合平台管理 : 列表管理保姆與家長媒合平台資料，可以關閉或者隱藏某會員資料讓其他會員無法搜尋\n會員管理 : 管理前台註冊保母以及家長帳號資料維護\n討論區管理 : 管理討論區文章內容，包含刪除以及隱藏功能\n寶寶管理 : 管理成長檔案寶寶資料、包含可以編輯、刪除寶寶資料護者成長檔案資料內容\n相簿管理 : 管理前台親子相簿內容與相片內容\n延伸閱讀 : 其他相關Smarty作品\n","date":"2018年6月4日","externalUrl":null,"permalink":"/2018/06/04/%E4%BD%9C%E5%93%81smarty-mamigogo-2012%E5%B9%B4%E7%89%88-%E5%B0%88%E6%A5%AD%E7%9A%84%E4%BF%9D%E6%AF%8D%E7%B6%B2%E7%AB%99/","section":"Posts","summary":"","title":"[作品][Smarty] Mamigogo 2012年版 專業的保母網站","type":"posts"},{"content":"","date":"2018年6月4日","externalUrl":null,"permalink":"/categories/smarty/","section":"Categories","summary":"","title":"Smarty","type":"categories"},{"content":"","date":"2018年6月4日","externalUrl":null,"permalink":"/tags/smarty/","section":"Tags","summary":"","title":"Smarty","type":"tags"},{"content":"網站名稱 : YH-food ERP\n開發年分 : 2017年\n開發語言 : PHP - Laravel framework\n資料庫 : MSQL\n運行環境 : Windows Server 2011 IIS\n正式營運連結 : \u0026ndash;\n作品連結 : http://works.alvinchen.club:8071/laravel/yuhsuan_erp/public/backend\n接洽討論 50% 頁面規劃 40% 程式撰寫 60% API串接 100% 教育訓練 50% 後續維護 100% 內容簡介 : yh-food ERP系統為屏東裕軒食品所委託客製化ERP系統，本系統包含人事、進銷、財務，三大操作系統，ERP系統也與映傑資訊POS系統做API資料串接以及帝商科技手持裝置API資料串接，提供給使用人員方便的操作性。人事系統方面包含企業組織設定、員工基本資料、任職設定、獎金計算等功能。進銷系統包含商店、販促區域、銀行、倉庫、硬體等基本設定，以及供應商、客戶、商品等資料設定，進貨採購管理、庫存盤點拆解組合功能、銷售、門市紀錄、促銷優惠方案等等，以及報表列印。財務系統則包含公司帳本、會計科目、加減項目、票據設定、手開發票等等基本設定項目，收付款單據、進退貨單據處理、收付款單據處理、進收款發票管理、折讓單與收據管理、應收應付門市帳款管理、帳務查詢、月結功能以及報表列印。\nLaravel：90%\nMySQL：85%\nJquery：50%\nHTML：55%\n人事系統功能\n公司基本資料 : 採表格與結構圖並行的方式，使用者可以自由編輯公司的組織與部門項目。\n銀行資料 : 設定系統收付款銀行詳細資料，包含帳號以及銀行聯絡窗口等項目。\n員工資料 : 設定公司員工基本資料，特殊欄位包含可以設定員工是否有權使用POS機功能，以及是否可以查看公司庫存品批發價金額。\n職位職等設定 : 設定人員職位與職等項目資料。\n人才庫 : 提供管理公司人才資料，包含面試過位錄取人員資料，如錄取後資料會移往員工資料。\n業績獎金設定 : 設定業務人員業績獎金計算公式。\n業績獎金計算 : 系統自動計算業績獎金後的列表。\n個人基本資料維護 : 提供會員編輯自己的基本資料，包含登入密碼、學經歷等資料。\n進銷系統功能 商店基本資料 : 如公司基本資料設定為商店者此頁面可以編輯商店相關進階資料欄位。\n技術人員資料 : 此頁面可管理前台聯絡我們中各儀器管理員的聯絡方式資料。\n販促區域設定 : 可將不同商店歸類於同一販促區域，可用群組的方式設定商品定價與優惠執行項目。\n倉庫設定 : 系統倉庫資料設定頁面，倉庫可設定至倉、儲、櫃三個階層。\n硬體設定 : 包含收銀機以及掃描器編號設定，可用於操作紀錄查詢對映得知哪台機器操作的資料。\n供應商名單 : 供應商詳細資料，包含統編、發票、聯絡人、結帳方式、可退貨期限等等詳細資料，用於採購動作時自動帶入單據。\n供應商分類 : 設定供應商分類資料，包含名稱與編號兩者。\n客戶名單 : 客戶名單為批發客戶資料，包含負責人、業務、送貨人員、發票、聯絡人、付款方式等詳細資料，用於批發訂單作業時自動帶入單據。\n會員名單 : 會員名單為實體商店客戶資料，包含姓名、手機、電話、聯絡人等基本資料。\n其他名單 : 其他名單為自由設定之資料，名單欄位與會員名單相同。\n名單分類設定 : 設定會員分類，包含名稱與代碼兩者。\n商品資料 : 系統商品管理頁面，商品資料可分為分類、名稱、單位、條碼等基本資料、自由編輯的詳細資料、零售、批發、紅利、經銷價格等銷售價格設定以及供應商採購基準價格等資料欄位，另外也客製化提供商品串接標籤機功能，可以直接線上列印商品標籤以及商品上架標價卡功能。\n商品分類 : 商品分類提供無限制多層次分類結構，可透過參數測定要如何分層，左半部提供樹狀圖快速瀏覽結構。\n品牌管理 : 商品品牌資料設定管理頁。\n商品單位設定 : 提供給商品資料設定勾選之單位名稱，商品設定中也可設定多重單位，可用於商品拆解時使用。\n組合商品 : 基本欄位與商品完全相同，只要多設定子件是由那些商品組合而成，此頁面程式僅繼承商品資料頁面。\n請購單 : 管理請購單據，請購單不必經由審核就可以直接成立，主管人員可以有權限直接將請購單轉換成採購單。\n採購單 : 管理採購單據，採購單必須經由主管審核通過後才會成立，採購單成立後因應客戶需求設計成可多次編輯，無論是否已經採購完畢都可以編輯內容，但如果已經將商品入庫則採購單編輯之商品數量不得小於入庫數量。\n退貨單 : 管理採購退貨單據，採購退貨單必須經由主管審核通過後才會成立，退貨單可經由採購單找出商品退貨，也可經由庫存資料直接找出商品做退貨。\n庫存查詢 : 提供目前系統各倉庫各品項的即時庫存資料，也可以單獨查詢單一品項載各倉庫之出庫、入庫紀錄。\n庫存盤點 : ERP 頁面提供庫存盤點單建立與編輯功能，實際盤點操作由帝商科技提供之手持裝置盤點，手持裝置可以在盤點後將盤點資料送回ERP。\n入庫單 : 管理入庫單據，當採購單叫貨後就會產生一張相對映入庫單據，入庫單須配合帝商科技提供之手持裝置透過條碼掃描的方式來將商品紀錄進入庫存。\n出庫單 : 管理出庫單據，當銷貨單產生或者寄庫叫貨時會產商一張相對應的出庫單，出庫單須配合帝商科技提供之手持裝置透過條碼掃描的方式將商品紀錄扣除庫存。\n調撥單 : 管理倉庫間調撥與倉位間調撥單據資料，調撥單據因應客戶需求調整為可跨店調撥與同倉庫調撥兩種，單據須配合帝商科技提供之手持裝置透過條碼掃描的方式將商品異動庫存紀錄。\n拆解單 : 管理拆解單據，拆解單據可透過商品之多重單位而產生，讓業者可以自由將大包裝商品拆成小包裝商品銷售，單據須配合帝商科技提供之手持裝置透過條碼掃描的方式將商品異動庫存紀錄。\n組合單 : 管理組合單據，組合單據可以經由組合商品的設定值而產生，讓業者可以自由組合不同商品來銷售，單據須配合帝商科技提供之手持裝置透過條碼掃描的方式將商品異動庫存紀錄。\n報廢單 : 管理報廢單據，報廢單據須經由主管審核後才能成立，報廢單須配合帝商科技提供之手持裝置透過條碼掃描的方式將商品扣除庫存。\n領用單 : 管理領用單據，領用單與報廢單流程相同，領用單須配合帝商科技提供之手持裝置透過條碼掃描的方式將商品扣除庫存。\n估價單 : 估價單不須經由主管審核即可成立，估價單也提供主管可以直接轉換成銷售單的功能。\n銷貨單 : 銷貨單因應客戶需求，可不經由主管審核即成立，銷貨單可設定客戶購買商品單價與給予的折扣以及結帳收款、配送方式。\n銷售退貨管理 : 銷售退貨單可經由銷貨單選擇商品來成立單據，銷貨單成立後會一併產生入庫單，以利倉儲人員順利將貨物收回。\n門市銷售紀錄 : 管理由映傑資訊提供之門市POS機串接回ERP系統的詳細資料，包含每筆銷售紀錄的商品、客戶、統編、優惠、總金額、付款方式等等資料。\n批發優惠管理 : 此部分依照客戶需求客製化，包含某客戶固定送贈品、買多少送多少等優惠結帳演算法。\n門市優惠管理 : 門市優惠部分配合映傑資訊提供POS機既有的優惠演算法， ERP 部分僅提供設定值編輯，在透過API的方式串接到POS機上。\n進銷報表 : 由客戶提供客製化產生之五大張報表，每張報表包含列印以及匯出Excel功能。\n財務系統功能 公司帳本 : 提供人事系統已設定好之公司資料詳細編輯公司帳本欄位。\n會計科目 : 提供編輯管理會計類型與會計科目兩項資料欄位，左半部畫面提供樹狀圖快速查詢瀏覽。\n加減項目 : 加減項目資料用於帳單補充資料使用，正負向會影響帳單金額加減。\n票據設定 : 提供支票銀行詳細資料設定項目，包含支票帳戶與固定發放日等詳細資料。\n手開發票設定 : 設定系統中未使用電子發票的部分，可設定每間公司不同月份可使用的發票號碼區間，用於帳單開發票時如超出設定好的發票號碼區間則帳單會無法成立。\n收款單 : 收款單據邏輯為客戶依照現有作業流程客製化流程，經由多張銷售單夾入收據、發票、儲值金額、加減金額等等資料而產生，並且限制只能同一客戶同一月份合併產生收款單據。\n付款單 : 付款單據邏輯為客戶依照現有作業流程客製化流程，經由多張採購單夾入收據、發票、儲值金額、加減金額等等資料而產生，並且限制只能同一供應商同一月份合併產生付款單據。\n進貨退款單 : 此項目為客戶依照現有狀況客製化流程，不經由付款單的方式將貨款退回給供應商。\n銷貨退款單 : 此項目為客戶依照現有狀況客製化流程，不經由收款單的方式將貨款由客戶收回。\n付款票據 : 付款給供應商的支票管理，提供多張支票新增功能，也提供直接串接點陣式印表機直接線上列印發票功能。\n收款票據 : 客戶付款之支票，ERP僅提供支票管理功能，不提供其他項目。\n進項發票 : 提供供應商開出的發票項目管理。\n銷項發票 : 提供開立發票功能，如為非電子發票僅提供資料管理，電子發票可由串接至電子發票機直接將發票印出，發票號碼將由電子發票機回傳提供。\n進貨折讓單 : 進貨折讓單可由進貨發票選擇開立，開立後的折讓單可用於付款單據中折抵金額。\n銷貨折讓單 : 銷貨折讓單可由銷貨發票選擇開立，開立後的折讓單可用於收款單據中折抵金額。\n進項收據 : 此項目為客戶客製化流程，使用流程與進項發票相同，差別在於付款單須對收據額外處理。\n銷項收據 : 此項目為客戶客製化流程，使用流程與銷項發票相同，差別在於收款單須對收據額外處理。\n應收帳款 : 當收款單據成立時，會一併建立一筆應收帳款，帳款可以設定多次、多筆收款紀錄，當收款完畢後會自動轉跳為歷史紀錄儲存。\n應付帳款 : 當付款單據成立時，會一併建立一筆應付帳款，帳款可以設定多次、多筆付款紀錄，當付款完畢後會自動轉跳為歷史紀錄儲存。\n預收帳款 : 此項目為客戶客製化流程，提供客戶預先簽約儲存一定金額於帳戶內，可用於收款單據產生時直接勾選折抵金額。\n預付帳款 : 此項目為客戶客製化流程，提供公司預先簽約儲存一定金額於帳戶內，可用於付款單據產生時直接勾選折抵金額。\n門市帳款 : 紀錄POS機每天回傳之整理帳條，項目內容由POS系統經由API傳送之資料，無法被異動修改，每日每筆資料皆須人員審核後生效。\n收款查詢 : 提供收款記錄歷史查詢用，可分為收款單、進貨退款單以及未收款清單三項查詢。\n付款查詢 : 提供付款記錄歷史查詢用，可分為付款單、銷貨退款單以及未付款清單三項查詢。\n月結作業 : 提供公司月份結帳功能，結帳後該月份帳務資料將無法再異動，需透過反結算後開啟功能才可異動該月分資料。\n財務報表 : 由客戶提供客製化產生之三大張報表，每張報表包含列印以及匯出Excel功能。\n功能截圖 系統首頁\n人事系統 - 公司基本資料\n進銷系統 - 商品資料\n進銷系統 - 銷貨單管理\n財務系統 - 銷項發票\n財務系統 - 付款查詢\n延伸閱讀 : 其他相關Laravel作品\n","date":"2018年6月2日","externalUrl":null,"permalink":"/2018/06/02/%E4%BD%9C%E5%93%81laravel-yh-food-erp-erp%E5%BE%8C%E7%AB%AF%E7%AE%A1%E7%90%86%E7%B3%BB%E7%B5%B1/","section":"Posts","summary":"","title":"[作品][Laravel] YH-food ERP ERP後端管理系統","type":"posts"},{"content":"網站名稱 : BTS EIP\n開發年分 : 2014年\n開發語言 : PHP -CakePHP framework\n資料庫 : MSQL\n運行環境 : Windoes Server 2012 IIS\n正式營運連結 : \u0026ndash;\n作品連結 : http://works.alvinchen.club:8056/cakephp/BTSProject/app/btseip/\n內容簡介 : BTS EIP 是一套客製化的後端簡易操作系統，內容包含公司人員出勤、請假、打卡、組織編制、人員排班、事件簽核、補教課程管理、學員名單管理、課程開班、學員繳費、講師維護、活動資料維護、參與人員管理、活動報名報到、活動資料統計、通路行銷資料發送、行銷專案績效考核查詢、個人行事曆便利貼等功能。\n全站功能 首頁 : 登入後呈現目前系統最新消息與各大操作系統連結包含人事、補教、活動、行銷、會員、個人\n人事系統 : 此系統可以設定公司組織結構與人員任職結構，包含員工請假、排班、打卡、出勤、面試、考核與統計報表\n補教系統 : 此系統可以設定課程與學員名單以及講師名單，包含課程資料、課程購買方案、教室通路設定、業務人員月目標、開課查詢管理、學員上課繳費登記查詢、學員上課紀錄查詢、講師職缺、應徵、名單管理，與業績、註冊分析、營業收支統計報表\n活動系統 : 此系統可以設定活動、廠商、會計科目，包含活動頁面、郵件設定、目標客戶與合作廠商名單設定、通路來源名單設定、活動開辦與報名報到設定、明細查詢與活動統計報表\n會員系統 : 此系統可以設定合作廠商名單、客戶名單與目標名單設定\n個人專區 : 此系統提供個人行事曆查詢與便利貼提醒設定\n系統設定 : 此區域可以設定全站會計科目項目、通路來源項目、首頁公告與系統公告，並可以下載操作手冊\n延伸閱讀 : 其他相關CakePHP作品\n","date":"2018年6月1日","externalUrl":null,"permalink":"/2018/06/01/%E4%BD%9C%E5%93%81cackephp-bts-eip-%E5%BE%8C%E7%AB%AF%E7%B0%A1%E6%98%93erp%E7%B3%BB%E7%B5%B1/","section":"Posts","summary":"","title":"[作品][CackePHP] BTS EIP 後端簡易ERP系統","type":"posts"},{"content":"網站名稱 : Techcomm\n開發年分 : 2017年\n開發語言 : PHP - Laravel framework\n資料庫 : MSQL\n運行環境 : Windows Server 2011 IIS\n正式營運連結 : http://tcbs1.lifescience.ntu.edu.tw/\n作品連結 : http://works.alvinchen.club:8071/laravel/techcomm/public/\n接洽討論 50% 頁面規劃 80% 程式撰寫 100% 後續維護 50% 內容簡介 : 本站儀器預約系統為台灣大學科技共同中心所委託製作，宗旨為解決現存精密儀器預約排程不確實且收費困難的狀況，網站重點在於如何將貴重儀器有效率且有門檻的透過預約系統讓學生可以使用，且需要良好的費用計算公式。因此我們在前台提供兩大功能 1. 活動預約 2. 儀器預約，儀器預約時需檢查確認學生是否已經上過某些課程，而後台提供儀器預約排程管理、活動管理(含學生現場報到功能)，以及台大校內外老師實驗室費用計算公式與繳費單據列印。\nLaravel：90%\nMySQL：80%\nJquery：60%\nHTML：55%\n前台功能\n首頁 : 因應訴求，第一大區塊即提供關鍵字搜尋，可以指定找活動或者找儀器即可進入報名或預約，下半部則顯示系統公告以及最新的活動訊息。\n參與活動 : 此頁面列出目前正開放的活動以及詳細資料，列表中即可顯示目前的報名人入，已結束的活動也可提供查詢但無法報名，進入活動畫面後點選預約登入系統後即可報名參加活動。\n預約儀器 : 此頁面列表出系統中設定好可提供預約的儀器相關資訊，在未登入的情況下只能查看儀器資料或者點選畫面上的管理員連結與管理員聯絡，登入後可提供預約功能。\n聯絡我們 : 列表科技共同中心各儀器管理員之聯絡方式，此頁面可由後台編輯資料。\n後台功能 首頁公告 : 提供RWD編輯器用於編輯首頁下方公告區塊資料內容。\n技術人員資料 : 此頁面可管理前台聯絡我們中各儀器管理員的聯絡方式資料。\n指導教授列表 : 可以自訂系統中的指導教授資料，這些資料將用於學生的所屬指導教授，儀器預約費用將以指導教授為單位統一計算時數單價與優惠。\n場地設定 : 管理儀器儲藏場地資料。\n使用時段 : 管理員可以自由設定儀器需要被開放預約的時段，各時段也可以透過時段類別來歸類使用時數費率。\n耗材設定 : 管理系統上除了儀器預約的使用時數費外，結帳時需要加入的耗材費用，每項耗材可因學生身份別不同而設定不同單價，耗材可於結帳時一併設定費用。\n排休設定 : 透過月份搜尋出蓋月份排班表後，可直接在月曆上使用下拉選單方式設定休假模式，系統會根據不同休假模式自動判別哪個身份的學生會無法預約或者全館休息所有人都無法預約。\n儀器所屬平台設定 : 類似儀器的分類設定，可以用來歸類不同種類的儀器。\n儀器資料設定 : 管理系統上所有的儀器詳細資料，包含儀器所在場地、管理員資料、開放時段、預約上限人數、通知選項、取消預約選項以及儀器單獨排休設定。\n預約資料管理 : 提供管理員處理學生預約資料，每筆資料都可以設定耗材使用量、實際使用時間與學生未出席等等設定值。\n繳費資料查詢 : 此頁面會在每個月五號計算上個月各實驗室的帳單，帳單內容管理員可以調整折價或者優惠後確認帳單就會寄送通知信給該實驗室的教授與聯絡窗口。\n護照列表 : 即為前台學生帳號管理，後台可以手動新增護照資料，也可以學生於前台註冊後將表單列印經由指導教授簽核送交主管單位後，再由管理員手動開通使用帳號，此頁面也可以設定學生發表的期刊達標後可以享有折扣，也可以發送通知訊息到學生的email以及查看活動紀錄。\n異常提醒 : 篩選錯誤紀錄顯示於此提供給管理員參考，學生若於前台登入錯誤過多次就會顯示紀錄。\n管理員權限設定 : 設定不同類別管理權限，透過樹狀圖的方式勾選每個頁面所需要開放的功能。\n管理員名單 : 管理後台管理員的名單，每個管理員帳號都可以獨立設定不同的操作權限。\n管理員操作記錄 : 記錄後台管理員所有使用歷程。\n畫面截圖 前台 - 首頁\n前台 - 活動列表\n前台 - 儀器列表\n前台 - 儀器預約\n後台 - 登入\n後台 - 活動報到處理\n後台 - 儀器預約帳務處理\n延伸閱讀 : 其他相關Laravel作品\n","date":"2018年5月31日","externalUrl":null,"permalink":"/2018/05/31/%E4%BD%9C%E5%93%81laravel-techcomm-%E5%84%80%E5%99%A8%E6%8E%92%E7%A8%8B%E9%A0%90%E7%B4%84%E7%B3%BB%E7%B5%B1/","section":"Posts","summary":"","title":"[作品][Laravel] Techcomm 儀器排程預約系統","type":"posts"},{"content":"網站名稱 : Mamigogo 寶寶成長紀錄\n開發年分 : 2016年\n開發語言 : PHP - Laravel framework\n資料庫 : MSQL\n運行環境 : Softlayer Ubuntu 16.04 Apache + Object Storage\n正式營運連結 : http://www.mamigogo.com/\n作品連結 : http://works.alvinchen.club:8071/laravel/mamigogo/public/\n主題發想 100% 頁面規劃 100% 程式撰寫 100% 後續維護 100% 行銷推廣 100% 內容簡介 : 我們在2016年執行了第一次的大改版作業，除了版面整個換然一新以外更是加入了手機版操作的支援，以及更方便的通知和數據統計功能，除了原有的成長檔案、親子相簿、討論區、媒合平台以外，我們更是加入了個人專頁展示，另外也改善了討論區的操作畫面，成長檔案的部分還是主軸，除了原有的功能外對於照片功能也加入了更多的可編輯選項，以及更好操作的寶寶成長檔案分享邏輯，當然每個頁面也都一併加入了留言以及社群媒體分享的功能。在安全性方面，每個寶寶的資料都可被定義為公開或者私人兩種，會員帳號也加入了email提供以及忘記密碼重設等功能，在硬體方面此次升級搬移至IBM SoftLayer位於新加坡的虛擬主機，除了安全性提升以外效能也獲得了大幅度提升。\nLaravel：90%\nMySQL：80%\nJquery：50%\nHTML：75%\n前台功能\n首頁 : 一共可以分為四個區塊資料，第一塊Banner作為形象展示以及合作廠商資訊曝光使用。第二區塊作為成長檔案快速開始的捷徑，未登入帳號的使用者可以直接透過姓名以及出生資料來開始製作成長檔案，以登入會員則會看到成長檔案的製作提醒。第三區塊為討論區置頂以及最新的文章列表，提供給使用者最新的訊息更新，第四區塊為相片展示，使用者在親子相簿上傳的照片會在這個區塊被展示出來，點選連結可以直接查看該相簿。\n討論區 : 討論區除了會員可以發文以外，管理員更是有將文章置頂的權限，每篇文章都提供分享到社群媒體的按鈕，當然除了發文以外討論區當然也可以讓會員自由回復文章，而且可以提供階層式的文章回覆功能。\n成長檔案 : 作爲網站的主軸成長檔案頁面是最多人使用的地方，此頁面每筆資料皆為會員同意將資訊公開之寶寶照片，然而公開之資料也僅限於照片本身而已，其他詳細資料只有寶飽的托育者以及家長可以查看，畫面上我們透過顏色來區別寶寶的身份，藍色即為公開資料粉紅色即為自己製作的寶寶成長檔案，如為自己製作的檔案則可以查看所有相係內容包含年齡、飲食、成長紀錄以及照片，若為公開資料只能看到照片，成長檔案中除了每個月填入的成長紀錄外，目前系統提供繪製完成的身高體重成長曲線圖給會員參考。\n親子相簿 : 親子相簿功能提供給會員無限制的上傳照片，限制每張照片大小為3MB，但是沒有張數以及相簿容量的限制，每張照片皆提供留言以及分享至社群媒體的功能，在照片上傳的部分相簿與成長檔案相同皆提供照片旋轉編輯功能避免手機拍出歪斜照片而無法修正。\n會員專區 - 基本資料 : 提供會員基本資料維護功能，如果有填寫email系統會定期發送email通知，若無填寫則不會。\n會員專區 - 我的話題 : 提供個人在討論區所發表過的所有主題以及回覆。\n會員專區 - 我的相簿 : 提供會員上傳以及編輯親子相簿的功能，此頁面為會員自己發表過的相簿，可以維護過往資料也可上傳新相簿。\n會員專區 - 我的寶寶 : 提供Mamigogo 會員管理自己的寶寶成長檔案，可以新增編輯寶寶個人資料，也可管理寶寶每個月的成長檔案，每份成長檔案皆可設定分享給寶寶的家長功能，以及隱私項目設定是否為公開資料。\n會員專區 - 我的收藏 : 收藏資料為媒合平台收藏，保母帳號可以收藏家長帳號，相反的家長帳號也可以收藏保母帳號，方便未來可以快速照到資料。\n會員專區 - 我的評價 : 2016年版新增評價功能，保母會員可評價家長帳號，相反的家長帳號也可以評價保母帳號，評價除了給出星星數以外也可以留下留言，所有資料將會顯示在此頁面。\n會員專區 - 我的專頁 : 2016年版的新功能，每位會員都會有此頁面蒐集個人發表過的相簿、寶寶以及討論區文章，版面也提供部分可調整功能包含Banner以及背景圖皆可自由調整。\n後台功能 靜態資料管理 : 提供管理員設定前台網站靜態資料頁面，包含關於我們、隱私群、服務條款、常見問題、首頁Banner等資料，首頁Banner可以針對圖片點選後的連結做設定也可以自由排序圖片輪播順序。\n回饋資料管理 : 管理前台聯絡我們回饋資料以及活動贈品回饋資料，聯絡我們資料管理員可以直接查看後回覆給使用者。\n商業廣告 : 此功能可以設定前台頁面下方之商業廣告內容，商業廣告只提供一個格式廣告提供刊登，每支廣告會根據優先順序以及刊登位置自動計算刊登費用，資料填寫完畢後前台首頁或者內頁就會出現橫幅廣告。\n相簿管理 : 列表前台會員上傳的所有相簿，相簿管理只提供相簿內容查看以及下架功能，管理員無法編輯使用者上傳的相片，下架功能可以單純下架一張相片或者相簿整體下架。\n已下架相簿 : 如在管理頁面中設定相簿護者相片下架則資料會移動到此區域，待使用者調整完畢後透過回覆功能再次將相片或者相簿上架。\n成長檔案管理 : 列表前台使用者新增的每一個月寶寶成長檔案，管理員僅提供下架功能無法編輯內容。\n已下架成長檔案 : 下架後的Mamigogo 成長檔案管理，可通知會員修改內容後於此區功能再次將成長檔案上架。\n討論區板塊管理 : 前台討論區的討論分區板塊，目前前台的功能因文章數量不足暫時將板塊關閉。\n討論區文章管理 : 列表前台討論區所有的文章，管理員僅提供下架功能無法編輯使用者文章。\n討論區文章下架項目 : 下架後的討論區文章會移動至此，待前台會員修正內容符合規定後管理員可透過此區域功能重新將文章上架。\n違規檢舉專區 : 前台提供相簿、相片、討論區文章三個項目的檢舉功能，一但有人按下檢舉則此區會出現資料，待管理員確認後可以直接將被檢舉內容刪除或者下架。\n媒合刊登管理 : 此區域管理前台家長以及保母刊登媒合資訊資料，管理員提供查看以及下架功能，無法編輯前台使用者資料內容，下架後一樣提供下架管理專區可以再透過此功能將內容上架刊登。\n會員群組管理 : 前台會員群組資料管理，目前前台會員自由註冊的資料皆不在群組中，群組僅提供可管理前台資料之帳號使用。\n會員名單查詢 : 可管理前台會員名單資料，包含新增、編輯、查看以及停權功能，此頁面可以查看會員詳細資料包含文章數量、相簿數量以及成長檔案數量等等資料。\n會員停權名單 : 此頁面可以管理已被停權的會員名單，可透過此頁面將停權會員重新恢復登入權限。\n操作記錄報表 : 提供查詢前台Mamigogo 會員操作記錄以及錯誤紀錄資料。\n後台管理員群組 : 可以自由設定每個管理員群組所擁有的操作權限。\n後台管理員名單 : 可以管理登入後台之帳號管理員，包含帳號以及權限群組設定以及停權管理。\n後台管理員停權查詢 : 查詢已被停權管理員名單，可透過此區域功能將管理員重新回復登入權限。\n操作記錄報表 : 提供查詢後台管理員操作記錄以及錯誤紀錄資料。\n功能截圖 前台 - 首頁\n前台 - 討論區\n前台 - 成長檔案\n前台 - 會員專區\n後台 - 資料列表\n後台 - 資料內容\n延伸閱讀 : 其他相關Laravel作品\n","date":"2018年5月31日","externalUrl":null,"permalink":"/2018/05/31/%E4%BD%9C%E5%93%81laravel-mamigogo-2016%E5%B9%B4%E7%89%88-%E7%85%A7%E9%A1%A7%E5%AF%B6%E5%AF%B6%E7%9A%84%E5%A5%BD%E5%B9%AB%E6%89%8B/","section":"Posts","summary":"","title":"[作品][Laravel] Mamigogo 2016年版 照顧寶寶的好幫手","type":"posts"},{"content":"AddToAny 是一套可以分享文章到社群媒體以及可以設定個人的社群媒體連結的外掛工具，下面將介紹如何安裝以及使用AddToAny\n如果你還沒有安裝好wordpress可以先閱讀 ：安裝wordpress, 30秒架好你的網站\n參考文件 ：https://www.addtoany.com/\n步驟一 ：到外掛搜尋AddToAny 找到它 # 步驟二 ：設定AddToAny 細項 # AddToAny 的顯示項目非常的多，這邊只提出一點比較惱人我一裝完就改掉的地方\n我習慣只勾第一個\u0026quot;dispaly at the \u0026ldquo;XXX\u0026rdquo; of posts，其他全部拿掉，這樣才不會首頁每一篇文章底下都被夾了分享按鈕看起來很亂\n這時候我們回到文章內容去看就會發現，在文章的最底下多出了一排按鈕可以直接分享文章到各大平台了喔\n步驟三 ：設定個人社群連結 # 社群連結包含自己個FB Twitter Toutube 等等各大社群媒體都有支援，可以透過小工具的方式把社群媒體的按鈕放在網頁上\n首先在小工具中想好要將連結按鈕放在哪個區塊位置，接著到小工具的頁面將AddToAny Follow 區塊拉到想要的地方\n接著輸入標題以及各大社群媒體的ID到空格中就可以了，沒有填寫的媒體就不會出現頁面上，填寫完成後就可以看到在頁面上出現按鈕了\n","date":"2018年5月30日","externalUrl":null,"permalink":"/2018/05/30/addtoany-wordpress-%E7%A4%BE%E7%BE%A4%E5%88%86%E4%BA%AB%E9%80%A3%E7%B5%90%E5%A4%96%E6%8E%9B/","section":"Posts","summary":"","title":"AddToAny Wordpress 社群分享連結外掛","type":"posts"},{"content":"網站名稱 : 輔大 HPS\n開發年分 : 2012年\n開發語言 : PHP -Smarty\n資料庫 : MSQL\n運行環境 : Ubuntu 16.04 on Docker\n正式營運連結 : hps101\n作品連結 : http://works.alvinchen.club:8056/smarty/hps101\n內容簡介 : 輔大HPS為2012年開發的網站，直至2017年為止都還在持續更新細部功能以及增加其他操作功能，輔大HPS除了提供中英文雙語介面之外，更有審查委員、學校兩種類型的會員帳戶，兩種會員帳戶登入後可以查看的功能不完全相同，我們提供學校會員可以每年登入網站上傳健康評估資料、而審查委員帳號則可以確認審核內容或者通知退件處理。\n前台功能 首頁 : 呈現網站最新消息列表與登入畫面\n最新消息 : 可呈現三種分類由選單中自由選擇不同類型的消息，最新消息分類為不可編輯資料\n關於我們 : 關於我們可分為兩類，內容由後台編輯器產生，類別為不可編輯資料\nHPS簡介 : HPS簡介可分為四大類型，內容同樣由後台編輯器產生，類別為不可編輯資料\n認證流程及標準 : 由後台編輯器產生\n文件下載 : 由後台上傳文件檔案提供年度分類給前台使用者下載\n活動集錦 : 可分為照片與影音兩大類，照片為後台上傳檔案可自由設定相簿名稱與照片內容，影音只能填寫youtube連結，直接將youtube影片嵌入畫面中播放\n發表 : 發表為後台設定之資料，僅提供查閱不提供其它附件係下載功能\n相關連結 : 由後台提供之國內外兩大分類連結資料\n聯絡我們 : 透過表單填寫管理員可以透過Email查閱資料\n語言切換 : 透果網址參數提供不同語言資料查閱\n後台功能 前台管理 - 活動照片 : 作為前台右下角照片管理區域，可以提供兩張照片上傳\n前台管理 - 跑馬燈 : 首頁Banner下方跑馬燈公告資料管理\n前台管理 - 線上複審2016 : 提供年度資料複審活動啟用與停止編輯時間設定\n前台管理 - 其他頁面 ：對應前台其他頁面資料管理上傳，因應多語言網站，每個欄位對應有兩個輸入空需要填寫\n會員管理 : 管理前台審查委員群組、人員，學校帳號與管理員帳號，也提供學校與審查委員配對，在資料複審時可直接將送審資料發送給配對人員\n延伸閱讀 : 其他相關Smarty作品\n","date":"2018年5月28日","externalUrl":null,"permalink":"/2018/05/28/%E4%BD%9C%E5%93%81smarty-%E8%BC%94%E5%A4%A7-hps-%E5%81%A5%E5%BA%B7%E4%BF%83%E9%80%B2%E5%AD%B8%E6%A0%A1%E5%9C%8B%E9%9A%9B%E8%AA%8D%E8%AD%89%E4%B8%AD%E5%BF%83/","section":"Posts","summary":"","title":"[作品][Smarty] 輔大 HPS 健康促進學校國際認證中心","type":"posts"},{"content":"網站名稱 : Biomap\n開發年分 : 2016年\n開發語言 : PHP - Laravel framework\n資料庫 : MSQL\n運行環境 : Windows Server 201 IIS\n正式營運連結 :https://biomap.nptu.edu.tw/\n作品連結 : http://works.alvinchen.club:8071/laravel/biomap/public/\n接洽討論 100% 頁面規劃 100% 程式撰寫 100% 後續維護 100% 內容簡介 : Biomap 生物多樣性互動式活動網站是國立屏東大學科普傳播學系林曉雯教授所委託製作之網站，計畫宗旨是想要透過手機當作教學媒介，讓學生帶著手機透過系統提示與分組討論互動在校園中的指定地點找到指定的生物並拍照上傳完成任務，因此網站介面設計就以手機回主要對象，PC版就變得比較不重要些。在功能面上畫面提供學生一個互動式的討論區塊，以及拍照存取相機，照片上傳後會自動讀取GPS位置參數記錄下來，而老師則可以透過後台設定來啟動或者停止學生的活動，當然為了教學研究目的我們也提供活動資料匯出功能。\nLaravel：85%\nMySQL：65%\nJquery：85%\nHTML：75%\n前台功能\n首頁 : 未登入的狀態下呈現計畫說明文字以及右方保留學生登入畫面，登入網站後可以看到自己正參與的活動連結。\n基本資料管理 : 學生可以在此頁面維護自己的基本資料，包含密碼email以及大頭照。\n活動管理 : 此頁面列表出網站上正在進行的活動項目，學生可以自由參與不同的活動，點選加入活動後輸入老師在課堂上提供的密碼即可開始與其他組員進行遊戲任務，而如果登入者身分為老師則可以在此頁面新增活動，包含設定要提供給學生加入的活動密碼。\n活動內容頁 : 活動內容頁為整個網站的主軸，上半部呈現google map配合手機定位可以標記學生目前所在的位置，右半部保留為討論群組，討論群組中可以發送文字訊息與圖片訊息兩種，下半部為此活動中上傳的所有資料，上傳的資料可以是照片，也可以是youtube連結，上傳資料的時候可以提供物種名稱以及說明文字，物種名稱會有AutoComplete來提示學生輸入的文字，另一個輔助功能是提供了google 以圖搜圖的功能，如果答案不確定可以透過以圖搜圖的方式來參考。上傳完成後下半部會顯示圖片，點選可以再次編輯內容。\n填寫結論 : 學生如認為所有活動目標皆達成可以點選右上角的填寫結論，此頁面提供一個RWD編輯器讓學生可以透過圖文的方式來回答老師設定好的活動結論問題。\n後台功能 系統簡介 : 此頁提供RWD編輯器讓老師可以直接透過畫面編輯前台首頁的說明文字。\n生物多樣性 : 此頁面預設匯入台灣生物多樣性資料庫物種資料，可以提供前台學生活動進行時的AutoComplete提示文字，此頁面可以提供搜尋、編輯以及刪除三個功能。\n活動列表 : 列表前台老師所建立的所有活動，管理員可以搜尋或者下載活動資料，也可以強制開始或者停止活動。\n會員管理 : 可以搜尋、編輯、查看前台老師、學生會員基本資料。\n管理員名單 : 此頁面提供管理後台會員資料、包含搜尋、編輯、刪除等功能，但不包含權限區隔設定。\n功能截圖 前台 - 首頁 Mobile 前台 - 活動管理頁 Mobile 前台 - 活動進行頁 Mobile - 地圖Section Mobile - 簡介Section Mobile - 照片Section Mobile - 討論Section 前台 - 活動結論 Mobile 後台 - 登入頁 後台 - 資料列表 後台 - 資料處理頁 延伸閱讀 : 其他相關Laravel作品\n","date":"2018年5月27日","externalUrl":null,"permalink":"/2018/05/27/%E4%BD%9C%E5%93%81laravel-biomap-%E7%94%9F%E7%89%A9%E5%A4%9A%E6%A8%A3%E6%80%A7%E4%BA%92%E5%8B%95%E5%BC%8F%E6%B4%BB%E5%8B%95%E7%B6%B2%E7%AB%99/","section":"Posts","summary":"","title":"[作品][Laravel] Biomap 生物多樣性互動式活動網站","type":"posts"},{"content":"網站名稱 : SOLID II\n開發年分 : 2016年\n開發語言 : PHP -CakePHP framework\n資料庫 : MSQL\n運行環境 : Windoes Server 2012 IIS\n正式營運連結 : SOLID II Social Online Learning Integrated Discussion\n作品連結 : http://works.alvinchen.club:8056/cakephp/BTSProject/app/solid/\n內容簡介 : SOLID II 是與屏東大學科普傳播學系合作之教學實驗網站，透過網站後台的學員班級、分組，以及學習活動流程設定，可以讓學生在課堂上透過網站的各項提示一同合作來完成課題，老師也可以透過後台操作記錄LOG來分析學生實際表現狀況。網站上提供創造性解題模式(CPS)與學員自由討論兩種課程模式，以及學員提示知識庫設定、過濾提醒策略與自我省思策略等幾種策略設定讓老師可以透過設定好的策略模式進行研究分析。\n前台功能 首頁 : 呈現計畫主旨說明要件以及提供學員註冊與登入\n活動列表 : 此頁面必須要學生登入後才可查看，可以看到目前被老師安排的活動與組員\n活動進行頁面 : 透過後台設定好的活動流程此頁面提供學生線上即時操作功能，可以透過圖表提示與討論板塊兩個功能來共同完成作業\n後台功能 首頁設定 : 設定前台首頁要顯示的最新消息、關於我們，以及輪播大圖\n學生班級管理 : 管理學生名單、班級以及分組\n課程管理 : 提供PCS課程與自由討論課程兩中課程操作模式的步驟設定\n活動流程管理 : 提供老師管理目前學生活動步驟若有問題可以回到上一步驟處理\n策略設定處理 : 提供學生資料分析處理策略設定與錯誤知識庫比對資料庫設定，可操作過濾提醒策略分析處理與發送自我省思提醒Email\n活動紀錄 : 提供活動紀錄檔下載與FB社群討論資料庫合併至系統功能以及離題字比對結果下載\n名單管理 ：管理老師與學生的名單資料\n管理員 ：管理後台管理員帳號密碼設定\n討論區管理 ：管理線上討論區板塊與文章處理\n學生專區 ：提拱給學生管理個人資料與參與的活動紀錄以及自我省思策略處理頁面\n延伸閱讀 : 其他相關CakePHP作品\n","date":"2018年5月25日","externalUrl":null,"permalink":"/2018/05/25/%E4%BD%9C%E5%93%81cackephp-solid-ii-%E7%B7%9A%E4%B8%8A%E7%A4%BE%E7%BE%A4%E5%AD%B8%E7%BF%92%E7%B6%B2%E7%AB%99/","section":"Posts","summary":"","title":"[作品][CackePHP] SOLID II 線上社群學習網站","type":"posts"},{"content":"wordpress換網址 該如何做呢? 原本以為需要很複雜的技術性，後來發現其實不需要呢\n只需要一個步驟就搞定了!\n首先如果你還沒看過如何架wordpress可以先閱讀 安裝wordpress, 30秒架好你的網站\n接著到後台的設定\u0026ndash;\u0026gt;一般\n接著在wordpress位址與網站位址兩個地方修改成你要的網址按下儲存就搞定了\n儲存後系統會帶你到新的網址重新登入，所以要先確定一下心的網址是可以使用的喔!\n","date":"2018年5月23日","externalUrl":null,"permalink":"/2018/05/23/wordpress%E6%8F%9B%E7%B6%B2%E5%9D%80-%E8%AE%8A%E6%9B%B4%E4%BD%A0%E7%9A%84wordpress%E7%B6%B2%E5%9D%80/","section":"Posts","summary":"","title":"wordpress換網址 , 變更你的wordpress網址","type":"posts"},{"content":"網站名稱 : Yobond 悠債網\n開發年分 : 2015年\n開發語言 : PHP -CakePHP framework\n資料庫 : MSQL\n運行環境 : Microsoft Azure with Windoes Server 2012 IIS\n正式營運連結 : 悠債網—可轉債投資策略與操作的專家\n作品連結 : http://works.alvinchen.club:8056/cakephp/BTSProject/app/yobond/\n內容簡介 : Yobond 悠債網 是專業的可轉債投資專家，網頁上提供最新以及歷史各檔期可轉債價格整理，也提供討論區不定期發表專業的可轉債操作項目。全站採Material Design風格所有項目皆是方塊組成，且首頁各區塊項目可由後台自訂顯示方式，可轉債決策系統為本站精華所在，內容各模組為站長多年來在可轉債領域操作心得，提供會員在線上直接操作模組部分免費以及部分付費模組，透過即時的可轉債資料串接可以即時計算各檔期可轉債詳細資料，新聞模組直接串接google 財經新聞RSS會自動連接到google更新資料免除管理員需要定期上資料的困擾。\n前台功能 首頁 : 呈現google最新財經新聞以及下方自訂區塊顯示，區塊可自訂標題、顯示類型、顯示大小、顏色等參數\n新聞 : 直接串接google news rss資料，定期自動更新並抓取圖片免除管理員上資料的困擾\n討論區 : 提供自訂板塊、與多階層版塊功能，各版塊可設定查看權限，可透過此設定來提供付費會員更多服務\n可轉債流通總表 : 流通總表資料來源為cmoney資料串接模組，此模組會定期將可轉債資料透過文字檔的方式傳入Yobond 悠債網 伺服器\n可轉債發行公告 : 部分為cmoney提供之資料模組，部分為聯外資料連結\n可轉債決策系統 : 經由後端即時資料與設定好的邏輯演算法，會員可以直接使用免費與付費模組來計算各檔可轉債詳細資訊，決策模組可由後台自由編輯設定\n會員專區 : 提供會員資料維護\n後台功能 首頁設定 : 可以設定顯示區塊的類型以及項目，顯示項目可以自訂大小、類型、顏色 等等資訊\n新聞設定 : 提供關鍵字搜尋google news rss，以及目前存在系統上的新聞列表以及編輯功能\n表單設定 : 提供可轉債歷史資料上傳以及決策模組設定，決策模組可透過既有的資料欄位組合出上千種的決策判別模組，系統提供極大彈性給版主透過經驗來設計關鍵決策模組\n聯絡我們 : 前台聯絡表單內容，後台可提供回復功能\n討論區管理 : 提供上下階層關係的板塊管理，各版塊可自訂排序、查看權限以及發表權限\n會員管理 : 可管理前後台會員，前台會員可設定第一次登入需要看到的畫面與VIP會員綁訂\n延伸閱讀 : 其他相關CakePHP作品\n","date":"2018年5月21日","externalUrl":null,"permalink":"/2018/05/21/%E4%BD%9C%E5%93%81cackephp-yobond-%E6%82%A0%E5%82%B5%E7%B6%B2-%E5%8F%AF%E8%BD%89%E5%82%B5%E5%8F%8A%E6%99%82%E9%81%8B%E7%AE%97%E5%88%86%E6%9E%90/","section":"Posts","summary":"","title":"[作品][CackePHP] Yobond 悠債網 可轉債即時運算分析","type":"posts"},{"content":"CloudFlare 是架站者常用的免費CDN分流服務，其實 CloudFlare 提供的服務不止CDN分流這麼簡單也可以幫你代管DNS，也有很多是需要付費的功能，這篇文章這裡就不多談，今天只講CDN分流的部分。\n參考網址 ：https://free.com.tw/cloudflare-wordpress-plugin/\n如果你還沒有看過前面的文章，請先閱讀 ：安裝wordpress, 30秒架好你的網站\n一般我們在雲端空間架站的時候都會想要把檔案或者圖片的流量導到外部伺服器去，因為雲端所有的容量跟流量都是錢堆出來的，因此很多元雲端服務商也都有推出儲存體的功能，讓主機上比較單純的只做運算跟服務，檔案的流量就交給儲存體來負責，而wordpress上如要加入儲存體這樣的服務除非自己動手開發外掛，目遣只有看到Amazon S3已經有現成的可以使用，那麼如果撇除掉儲存體這個選項，還有哪些可以使用呢\n使用外部FTP主機來分流圖片檔案 使用CDN服務透過CDN伺服器來分流 今天要介紹的就是第二種服務，首先外部CDN服務百百種，今天要介紹的只是其一 ： CloudFlare\n步驟一 ： 先註冊CloudFlare 帳號吧 # 先到 https://www.cloudflare.com\n點選sign up\n接著，畫面上會要你填入email與密碼，這組email與密碼也是未來登入CloudFlare 用的\n完成後緊接著填入你要使用的站台網址\n基本上，帳號建立到這邊就完成了，最後畫面上會要你挑選要使用的方案，點選第一個免費的就可以了\n確認後畫面會要你開始設定DNS代管，其實就可以不必理會了\n步驟二 ：取的CloudFlare API Key # API Key就像你的密碼一樣，千萬不要讓別人知道了\n先從右上角的帳號按鈕點下去，然後點選MyProfile\n接著畫面往下捲就會看到API的項目\n點選view他會要你輸入一次密碼，之後把API Key複製起來就可以了\n步驟三 ：設定DNS # 我們需要將DNS設定修改到CloudFlare上面代管，這樣才會達到分流的效果\n首先我們到DNS購買的位置修改內容，每家不同這邊以Godaddy為範例\n找到網域名稱伺服器的位置，點選變更 下拉選單選到自訂，然後輸入名稱伺服器\n完成後像這樣子\n步驟四 ：安裝CloudFlare wordpress外掛 # 在wordpress外掛頁面搜尋CloudFlare 就可以找到了\n安裝完成啟用後就會在設定的選單裡面多一頁 CloudFlare\n進到CloudFlare 的設定頁面，我們要把帳號連結起來，如果還沒有帳號也可以在這邊做免費註冊，我們剛剛已經註冊完了所以可以直接點選登入按鈕\n透過API Key登入後設定方式很簡單，只要按下自動最佳化的\u0026quot;Apply\u0026quot;就行了\n按下去之後會看到畫面上提示9分鐘後就可以完成\n這樣一來，網站的流量就可以由原本的伺服器導向CloudFlare 的CDN節點了，除了節省一些流量的費用以外還可以因為世界各地不同的人瀏覽兒讓他自己存取該地的資料提升網站速度呢\n","date":"2018年5月18日","externalUrl":null,"permalink":"/2018/05/18/cloudflare-%E5%85%8D%E8%B2%BB-wordpress-%E5%88%86%E6%B5%81%E5%A4%96%E6%8E%9B%EF%BC%8C%E6%9C%80%E4%BD%B3%E5%8C%96%E8%A8%AD%E5%AE%9A/","section":"Posts","summary":"","title":"CloudFlare 免費 WordPress 分流外掛，最佳化設定","type":"posts"},{"content":"模組化 確實可以將一些常用的功能整理成一包，方便以後拿出來重複利用，如果你還沒看過上篇請先閱讀\n模組化 套件 開發自己的Package\n接著這邊將紀錄比較進階的用法\n參考網址 ：【Laravel 5】撰寫你的package\n我們在上一篇介紹過了用一個很簡單的方式，網址上加個hello，就可以執行已經在套件中寫好的程式碼內容\n接下來的做法比較貼近套件該有的使用方式\nPart 1 : 建立Controller # 前半段與上次相同就不再多提，根據上篇文章的說明應該可以很容易做到建立Controller之前的步驟，接著我們來建立這次範例要用的PlusConttoller\n完成後會像這樣子的結構\nps. 本次是有建立ServiceProvider，目前還沒用上，後續會有說明\n接著我們來修改一下routs/web.php 多增加一個路徑來執行Plus-\u0026gt;result();\nuse Virtualorz\\Plus\\Plus; Route::get(\u0026#39;plus\u0026#39;, function () { $p = new Plus(111, 22); return $p-\u0026gt;result(); }); 接著打開瀏覽器就會看到畫面上印出133，也就是正確執行無誤了！\nPart 2 : 如果套件有修改更新怎麼辦? # 因為Packagist是抓Github上面的tag資料來判斷目前是哪個版本，所以套件更新了內容就需要做兩個步驟\nCommit push上Github後記得加上tag資訊 ：git tag -a X.X.X -m \u0026ldquo;version content” 到packagist上按下\u0026quot;Update\u0026rdquo;，強制去GitHub取得目前最新的版本 Part 3 : 如何在套件中加入Migration # 如果有用過Composer的部分套件可能都會遇過，作者在install區塊文件都會寫需要下Migrate指令來讓系統建立資料表\nMigration 基礎教學文件 ：https://kejyuntw.gitbooks.io/laravel-5-learning-notes/database/migration/database-migration-README.html\n重點概念 ：\n每次定義好的migration不可再修改，不然會造成已經完成的migration沒動作 要修改資料須建立新的migration去調整 在套件的ServiceProvider:boot()中可以定義從哪個位置Loading Migration檔案 $this-\u0026gt;loadMigrationsFrom(__DIR__.\u0026#39;/database/migrations\u0026#39;); ","date":"2018年5月16日","externalUrl":null,"permalink":"/2018/05/16/%E6%A8%A1%E7%B5%84%E5%8C%96-%E9%96%8B%E7%99%BC%E8%87%AA%E5%B7%B1%E7%9A%84package-%E9%80%B2%E9%9A%8E%E7%89%88/","section":"Posts","summary":"","title":"模組化 開發自己的package 進階版","type":"posts"},{"content":"網站名稱 : G9Park\n開發年分 : 2016年\n開發語言 : PHP - Laravel framework\n資料庫 : MSQL\n運行環境 : \u0026ndash;\n正式營運連結 : \u0026ndash;\n作品連結 : http://works.alvinchen.club:8071/laravel/g9park/public/\n負責範圍 :\n版面調整 40% 程式撰寫 100% 內容簡介 : G9park 為嘉酒文創園區委託製作之單頁式活動網站，僅作為短期活動使用，因此規劃網站為單頁區塊顯示，各區塊皆為滿版顯示圖文資訊，最後一區塊保留作為表單填寫區塊，填寫完的資料會寄送至指定的email，因此網站並沒有可用之資料管理後台。\nLaravel：50%\nMySQL：20%\nJquery：40%\nHTML：70%\n前台功能\n第一區塊 : 多張形象圖輪播，圖片與文字皆為固定文案不提供管理員編輯功能。\n第二區塊、第三區塊 : RWD顯示圖文資料，電腦版與手機版經過設計後透過不同的呈現方式讓圖文可以清楚顯示。\n第四區塊 : RWD顯示google map以及交通描述文字。\n第五區塊 : 招商填寫表單，表單內容填寫完畢後會直接將內容寄送到指定的eamil信箱中\n功能截圖 首頁\n延伸閱讀 : 其他相關Laravel作品\n","date":"2018年5月15日","externalUrl":null,"permalink":"/2018/05/15/%E4%BD%9C%E5%93%81laravel-g9park-%E5%98%89%E9%85%92%E6%96%87%E5%89%B5%E5%9C%92%E5%8D%80/","section":"Posts","summary":"","title":"[作品][Laravel] G9park 嘉酒文創園區","type":"posts"},{"content":"網站名稱 : 台大演講網\n開發年分 : 2016年\n開發語言 : PHP - Laravel framework\n資料庫 : MSQL\n運行環境 : Windoes Server 2012 IIS\n正式營運連結 : https://speech.ntu.edu.tw/ntuspeech/\n作品連結 : http://works.alvinchen.club:8071/laravel/ntuspeech/public/\n負責範圍 :\n接洽討論 40% 程式開發 100% 後續維護 100% 內容簡介 : 台大演講網 為台灣大學教務處委託製作開發時間約6個月，其中影片來源為Youtube以及台大舊伺服器影片檔皆有，網站前台提供學生透過類型、主題、關鍵字、演講時間等條件搜尋相關影片，也提供各處室單位線上申請影片上架後直接列印申請表格送至負責單位，每支影片皆可設定是否開放留言以及相關標籤，另外前台也提供訂閱電子報功能。後台提供管理員設定網站相關顯示參數、媒體影片資料、靜態顯示資料、影片申請、電子報、會員管理等等功能，後台會員可依照群組不同透過樹狀圖勾選相對可以開放的功能，影片部分，除前台各處室相關申請以外也開放後台管理員直接新增台大伺服器影片以及Youtube影片功能。\nLaravel：85%\nMySQL：75%\nJquery：80%\nHTML：60%\n前台功能\n首頁 : 依據不同裝置分成兩大區塊，第一大區塊電腦版展示入口影片，手機版因考量流量問題改採用靜態影片處理，第二大區塊顯示六大精選主題影片。\n六大分類影片 : 分別依照影片設定的分類列表顯示影片縮圖以及標題，分別可依據瀏覽人次、喜歡人次以及影片上架時間來排序，並可以以照關鍵字、演講時間兩個條件來篩選出像要的影片。\n影片內容 : 影片內容頁面左半部為影片嵌入播放器，右半部顯示影片詳細資料，包含日期、標題、作者、簡介，也可點選推薦影片、同標籤影片或者透過留言套件提供留言，每隻影片都可以分享至社群媒體。\n拍攝申請 : 拍攝申請頁面必須經由API透過台大校園認證系統登入後取得認證才可以開始填寫資料，資料填寫時只允許填寫Youtube影片網址，並且必須要將申請表格列印後送交至相關處室，後台管理員在收到申請單後審核通過即可將影片上架。\n後台功能 SEO設定 : 提供管理員設定網站需要顯示的Title Keyword Description。\n六大分類設定 : 六大分類指的是前台Menu上的六個影片分類，此區域提供管理員自由設定分類名稱以及選單前後順序排序。\n拍攝申請流程說明 : 此頁面為RWD編輯器頁面，直接提供一個編輯器讓管理員可以自由編輯顯示內容，前台相對應為一個靜態顯示的頁面。\n背景顏色設定 : 提供一個調色盤功能，可以自由設定前台顯示背景色，如未設定可預設顯示CSS定義好的顏色。\n滿版影片 : 此處可上傳一個或者多個入口影片以及手機版的靜態圖片展示。\n圖片輪播 : 此處提供上傳多張第二層入口顯示之輪播圖片，可自由設定標題、大標、小標、圖片、連結、上線日期以及自由排序。\n社群ICON : 可自訂網站下方社群媒體連結顯示按鈕、樣式、連結與排序。\n影片綜覽 : 前台所有影片管理頁面，提供管理員自由編輯每個影片的相關欄位，各處室申請上的資料也會在此頁出現，管理員可直接於此頁面審核資料確認影片是否要上架。\n標籤管理 : 影片標籤茲了管理頁面。\n精選主題清單設定 : 首頁下方精選主題清單管理，可提供設定相關影片、主題排序、顯示圖示等等項目。\n關於我們、版權聲明、常見問題 : 此區塊為前台靜態頁面資料顯示管理，提供編輯器與列表資料管理可直接編輯前台相對應頁面資料。\n電子報框架管理 : 提供管理員編輯電子報框架HTML內容，可透過RWD編輯器自由編輯表頭以及表尾內容，往後可以直接套用到要發送的電子報。\n電子報發送內容管理 : 提供直接RWD編輯器直接編輯電子報內容，經由主管審核後便可透過排程發送至已訂閱用戶email，也提供測試發送功能，可以在發送前確認用戶收到的內容正確。\n電子報會員管理 : 管理已訂閱電子報用戶名單，也可於此頁面新增訂閱用戶名單。\n權限設定 : 後台管理員權限設定頁面，提供透過樹狀圖的方式勾選權限群組設定，每個頁面每項功能都可獨立管理是否開放使用。\n管理員 : 編輯管理後台管理員資料，每位管理員可設定套用到不同的權限登入後就會顯示相對應的功能按鈕。\n功能截圖 前台 \u0026ndash; 首頁\n前台 \u0026ndash; 影片列表頁\n前台 \u0026ndash; 影片內容頁\n後台 \u0026ndash; 系統登入頁\n後台 \u0026ndash; 資料管理列表頁\n延伸閱讀 : 其他相關Laravel作品\n","date":"2018年5月15日","externalUrl":null,"permalink":"/2018/05/15/%E4%BD%9C%E5%93%81laravel-%E5%8F%B0%E5%A4%A7%E6%BC%94%E8%AC%9B%E7%B6%B2-%E5%BD%B1%E7%89%87%E5%88%86%E9%A1%9E%E5%B1%95%E7%A4%BA%E8%88%87%E4%B8%8A%E6%9E%B6%E7%94%B3%E8%AB%8B/","section":"Posts","summary":"","title":"[作品][Laravel] 台大演講網 影片分類展示與上架申請","type":"posts"},{"content":"網站名稱 : 鹿林茶園\n開發年分 : 2012年\n開發語言 : PHP -Smarty\n資料庫 : MSQL\n運行環境 : Ubuntu 16.04 on Docker\n正式營運連結 : Taiwan Lulin\n作品連結 : http://works.alvinchen.club:8056/smarty/lulin\n內容簡介 : 鹿林茶園位於台中市大甲區的鄉下農村，當初依照客戶需求透過僅有的預算簡易且劇有繁簡切換功能的網站，而頁面編排也是透過免費版型替換圖片來呈現\n雖然功能簡易且內容版型不夠完善，但是基於客戶超級有限的預算下，我們透過免費的工具以及設計符合需求的前後台功能滿足客人基本需求。\n前台功能 首頁 : 呈現茶莊圖片\n作品展示 : 展示販售茶葉商品以及售價圖片\n關於我們 : 由後台編輯器上圖文資料\n關於張書曜 : 同樣由後台編輯器上圖文資料\n聯絡我們 : 可由後台編輯圖片以及連絡方式\n繁簡切換 : 透過Script 直接切換語言\n後台功能 作品管理 : 管理作品展示中的圖片以及描述內容\n關於我們 : 編輯前台關於我們描述內容\n關於張書曜 : 編輯前台關於張書曜呈現內容\n聯絡我們 : 編輯前台聯絡我們呈現內容\n會員管理 : 管理可登入後台帳號密碼以及操作權限\n延伸閱讀 : 其他相關Smarty作品\n","date":"2018年5月14日","externalUrl":null,"permalink":"/2018/05/14/%E4%BD%9C%E5%93%81smarty-%E9%B9%BF%E6%9E%97%E8%8C%B6%E5%9C%92%E7%B0%A1%E6%98%93%E5%91%88%E7%8F%BE%E7%B6%B2%E7%AB%99/","section":"Posts","summary":"","title":"[作品][Smarty] 鹿林茶園 簡易呈現網站","type":"posts"},{"content":"Guzzle is a PHP HTTP client that makes it easy to send HTTP requests and trivial to integrate with web services.\n參考網址 : https://github.com/guzzle/guzzle\n是的，有時候碰到特殊的需求可能必須要透過其他主機或者網址來取得或者發送資料的時候，就必須要在Controller中送出POST或者GET Request\n這時候Guzzle 就幫上大忙了，他是模擬了http client來發送request，這樣就能夠順利取得遠端資料，使用方式也很簡單\n下面將介紹如何透過Composer 安裝以及使用Guzzle 如果你還沒有碰過Composer，請先閱讀以下文章\nComposer 幫PHP套件找一個家\n步驟一 : 透過Composer安裝 Guzzle\nComposer require guzzlehttp/guzzle 步驟二 : 在Controller最前段加入use\nuse GuzzleHttp\\Client; 步驟三 : 開始使用\n$client = new \\GuzzleHttp\\Client(); $res = $client-\u0026gt;request(\u0026#39;GET\u0026#39;, \u0026#39;https://api.github.com/repos/guzzle/guzzle\u0026#39;); 這時候就已經把request送出去到指定的網址了，而$res變數就是收到來自遠端伺服器的response\n如果要發送非同步request可以這樣做\n$request = new \\GuzzleHttp\\Psr7\\Request(\u0026#39;GET\u0026#39;, \u0026#39;http://httpbin.org\u0026#39;); $promise = $client-\u0026gt;sendAsync($request)-\u0026gt;then(function ($response) { echo \u0026#39;I completed! \u0026#39; . $response-\u0026gt;getBody(); }); $promise-\u0026gt;wait(); 另外，github上也提供了幾個function可以使用\n$res-\u0026gt;getStatusCode(); // 取得狀態，成功是200 $res-\u0026gt;getHeaderLine(\u0026#39;content-type\u0026#39;); // 取得表頭 ex:\u0026#39;application/json; charset=utf8\u0026#39; echo $res-\u0026gt;getBody(); //取得回傳內容 ","date":"2018年5月11日","externalUrl":null,"permalink":"/2018/05/11/guzzle-%E8%AE%93laravel-%E5%9C%A8controller%E4%B8%AD%E7%99%BC%E5%87%BApost-request/","section":"Posts","summary":"","title":"Guzzle, 讓Laravel 在Controller中發出post request","type":"posts"},{"content":"網站名稱 : 奕祺科技 官網\n開發年分 : 2016年\n開發語言 : PHP - Laravel framework\n資料庫 : MSQL\n運行環境 : Ubuntu 16.04 Apache\n正式營運連結 : \u0026ndash;\n作品連結 : http://works.alvinchen.club:8071/laravel/ichi/public/\n負責範圍 :\n接洽討論 100% 頁面規劃 100% 程式撰寫 100% 內容簡介 : 弈祺科技官網是依照客戶需求透過比較簡單的方式來呈現公司形象以及簡易兩項產品呈現，版面配置上透過單頁式的排版提供不同區塊展示不同的資訊，並且提供後台編輯功能，讓管理員可以輕鬆編輯各區塊所要顯示的內容、圖片，最底下的區塊也提供聯絡表單功能，填寫後除了會寄送通知信件至指定的信箱外也可以在後台的聯絡我們頁面看到資訊。\nLaravel：90%\nMySQL：60%\nJquery：75%\nHTML：50%\n前台功能\n首頁 : 提供單頁式RWD區塊頁面，各區塊顯示文字與圖片皆可由後台管理系統上傳與編輯，第一區塊為輪播圖片後台可提供編輯圖片、大標、小標以及輪播順序，第二、第三、第四區塊為相同類型區塊，提供展示後台設定之圖片、標題以及內容，作為公司簡介以及簡易產品展示，最後一個區塊作為連絡表單使用，前台客戶填寫完資料後會將內容寄送至指定的管理員信箱，而後台的聯絡我們頁面也可以查看與回覆資訊。\n後台功能 主視覺 : 設定首頁第一區塊輪播圖片內容，每筆資料可以設定圖片、第一大標、第二大標、副標、以及狀態，並可提供輪播排序功能。\n公司簡介 : 設定首頁第二區塊，提供圖片以及內容，FB連結三個欄位設定值，FB連結會顯示在網站右上角的社群連結。\n產品類型一 : 設定前台首頁的第三區塊，提供圖片、標題、內容三個欄位設定值，此區塊資料如未設定則首頁不會顯示此區塊。\n產品類型二 : 設定前台首頁的第四區塊，提供圖片、標題、內容三個欄位設定值，此區塊資料如未設定則首頁不會顯示此區塊。\n產品類型三 : 設定前台首頁的第五區塊，提供圖片、標題、內容三個欄位設定值，此區塊資料如未設定則首頁不會顯示此區塊。\n聯絡我們 : 列表顯示前台表單填寫內容，每一封訊息皆可查看並回覆給使用者。\n管理員名單 : 後台管理員帳號列表管理功能，此版本不提供權限編輯功能，儘管理可登入後台帳號資料。\n功能截圖 前台 - 首頁\n後台 - 登入頁\n後台 - 資料列表頁\n後台 - 資料處理頁\n延伸閱讀 : 其他相關Laravel作品\n","date":"2018年5月10日","externalUrl":null,"permalink":"/2018/05/10/%E4%BD%9C%E5%93%81laravel-%E5%A5%95%E7%A5%BA%E7%A7%91%E6%8A%80-%E5%96%AE%E9%A0%81%E5%BC%8Frwd%E5%85%AC%E5%8F%B8%E5%BD%A2%E8%B1%A1/","section":"Posts","summary":"","title":"[作品][Laravel] 奕祺科技 單頁式RWD公司形象","type":"posts"},{"content":"網站名稱 : 世界門 代標\n開發年分 : 2011年\n開發語言 : PHP -CakePHP framework\n資料庫 : MSQL\n運行環境 : Windoes Server 2012 IIS\n正式營運連結 : \u0026ndash;\n作品連結 : http://works.alvinchen.club:8056/cakephp/worlddoor/\n內容簡介 : 世界門 是專業的進出口貿易商，此網站平台是建構在既有的進口管道上將商品提供到網路上販售給消費者，而消費者也可以自行在網站上選購需要的商品，透過代標工具不必自行註冊日本拍賣平台的帳號也可以將需要的商品買回，而且不必擔心運送管道問題，費用上也如網站上所示清楚明瞭，讓消費者可以更方心，2011年的版本僅提供日本Yahoo拍賣代標，具有自動定期下載日本拍賣商品以及定時定額投標功能，得標後藉由人工處理訂單的方式與消費者聯絡並將商品從日本運送回台。\n前台功能 首頁 : 呈現目前系統上最多人下標的商品與最新商品\n商品列表 : 直接呈現目前日本Yahoo拍賣最新的內容\n商品內容 : 呈現目前日本Yahoo拍賣最新的內容\n會員專區 : 提供會員資料維護、個人訂單維護與紀錄查詢\n購物車 : 可由世界門 直接下標日本Yahoo拍賣商品，並自行設定要藉由還運還是空運來台，並且透過金流串接可直接付款\n翻譯 : 透過Script 直接翻譯日文\n後台功能 商品管理 : 透過API直接與日本Yahoo拍賣連結\n關於我們、使用條款、購物指南、費用說明 等靜態頁面 : 提供多功能編輯器可直接編輯前台顯示內容\n會員管理 : 提供已註冊會員權限以及資料管理\n訂單管理 : 前台會員下標商品狀態以及付款、運送設定管理\n會員管理 : 管理可登入後台帳號密碼以及操作權限\n延伸閱讀 : 其他相關CakePHP作品\n","date":"2018年5月9日","externalUrl":null,"permalink":"/2018/05/09/%E4%BD%9C%E5%93%81cackephp-%E4%B8%96%E7%95%8C%E9%96%80-%E6%97%A5%E6%9C%AC%E6%8B%8D%E8%B3%A3%E4%BB%A3%E6%A8%99%E7%B6%B2%E7%AB%99/","section":"Posts","summary":"","title":"[作品][CackePHP] 世界門 日本拍賣代標網站","type":"posts"},{"content":"","date":"2018年5月7日","externalUrl":null,"permalink":"/tags/php-7.1/","section":"Tags","summary":"","title":"Php 7.1","type":"tags"},{"content":"PHP 在Apache上或者應該說在一台伺服器上通常只會有一個版本，但是有時可能會遇到像是不同的專案需要不同版本，但是卻跑在同一台機器上\n這時候就需要多個版本的PHP 支援了\n然而這件事情在Windows IIS上很容易的可以用PHP Manager套件直接對不同的站體設定不同版本的PHP\n在Linux 上確實沒有這麼容易可以用個小設定就解決，但是也不難就是了，這次要介紹的是php-fpm 的做法\n首先，如果你的PHP 版本太舊會建議先升到5.6版，如果不知道怎麼升級請參考上一篇文章 ：PHP 版本更新, Apache上如何設定\n參考文件 ：Run multiple PHP version on the same server using php-fpm and xdebug on Ubuntu 16.04\n步驟一 ：將要使用的PHP 版本以及相關套件安裝完成 # sudo su add-apt-repository ppa:ondrej/php apt-get update apt-get install libapache2-mod-fastcgi php5.6-fpm php5.6 php5.6-dev php5.6-mcrypt php5.6-mbstring php5.6-mysql php5.6-zip php5.6-gd php5.6-xml php7.1-fpm libapache2-mod-fastcgi php7.1-fpm php7.1 php7.1-dev php7.1-mbstring php7.1-mysql php7.1-zip php7.1-gd php7.1-xml php7.1-curl php7.1-intl php7.1-json php7.1-mcrypt 這個範例跟上一篇要安裝PHP 5.6時相同，需要先將軟體源ppa:ondrej/php 加入，最後一行指令安裝了很多東西，如果你已經是PHP 5.6\n或者透過上一篇的方式升級到PHP 5.6的朋友安裝的時候可以跳過PHP 5.6版的內容，大概會像以下這樣\nsudo apt-get install libapache2-mod-fastcgi php5.6-fpm php7.1-fpm libapache2-mod-fastcgi php7.1-fpm php7.1 php7.1-dev php7.1-mbstring php7.1-mysql php7.1-zip php7.1-gd php7.1-xml php7.1-curl php7.1-intl php7.1-json php7.1-mcrypt 但是 php 5.6-fpm還是必須要的！\n接下來必須啟用兩個模組\nsudo a2enmod actions sudo a2enmod fastcgi 基本上安裝到這邊結束，接下來就是設定Apache的部分了\n步驟二 ：設定Apache 虛擬站台config # 原作者說，需要在每個站台的config 加入下面這段設定值，但是經過測試，有會站台因為這段設定值而無法啟動\n所以如果加了這段設定值後無法啟用站台請移除他\n\u0026lt;IfModule mod_fastcgi.c\u0026gt; AddHandler php56-fcgi-www .php Action php56-fcgi-www /php56-fcgi-www Alias /php56-fcgi-www /usr/lib/cgi-bin/php56-fcgi-www FastCgiExternalServer /usr/lib/cgi-bin/php56-fcgi-www -socket /run/php/php5.6-fpm.sock -pass-header Authorization \u0026lt;Directory \u0026#34;/usr/lib/cgi-bin\u0026#34;\u0026gt; Require all granted \u0026lt;/Directory\u0026gt; \u0026lt;/IfModule\u0026gt; \u0026lt;IfModule mod_fastcgi.c\u0026gt; AddHandler php71-fcgi-www .php Action php71-fcgi-www /php71-fcgi-www Alias /php71-fcgi-www /usr/lib/cgi-bin/php71-fcgi-www FastCgiExternalServer /usr/lib/cgi-bin/php71-fcgi-www -socket /run/php/php7.1-fpm.sock -idle-timeout 1800 -pass-header Authorization \u0026lt;Directory \u0026#34;/usr/lib/cgi-bin\u0026#34;\u0026gt; Require all granted \u0026lt;/Directory\u0026gt; \u0026lt;/IfModule\u0026gt; \u0026lt;IfModule mod_fastcgi.c\u0026gt; \u0026lt;FilesMatch \u0026#34;.+\\.ph(p[345]?|t|tml)$\u0026#34;\u0026gt; SetHandler php71-fcgi-www \u0026lt;/FilesMatch\u0026gt; \u0026lt;/IfModule\u0026gt; 接著，在每個站台的config 加入設定值，指定用哪個版本的PHP來執行\n\u0026lt;VirtualHost *:80\u0026gt; ... \u0026lt;IfModule mod_fastcgi.c\u0026gt; \u0026lt;FilesMatch \u0026#34;.+\\.ph(p[345]?|t|tml)$\u0026#34;\u0026gt; SetHandler php56-fcgi-www \u0026lt;/FilesMatch\u0026gt; \u0026lt;/IfModule\u0026gt; ... \u0026lt;/VirtualHost\u0026gt; 範例是指定用PHP 5.6來執行所以指定為php 56-fcgi-www\n如果要用PHP 7.1 那麼請指定為php 71-fcgi-www\n接著重新啟動apache 就可以了\n最後來看一下結果是不是對的\n首先我們將Config 中的setHandler 設定為php 56-fcgi-www，透過phpinfo() 來看一下實際跑的版本是什麼\n確實沒有錯，接著變更成7.1\n也沒有問題，那麼就大功告成了！\n","date":"2018年5月7日","externalUrl":null,"permalink":"/2018/05/07/php-%E5%9C%A8apache-%E4%B8%8A%E8%B7%91%E5%A4%9A%E5%80%8B%E7%89%88%E6%9C%AC%EF%BC%8C%E4%BB%A5php-5-6%E8%88%87php-7-1%E7%82%BA%E4%BE%8B/","section":"Posts","summary":"","title":"PHP 在Apache 上跑多個版本，以PHP 5.6與PHP 7.1為例","type":"posts"},{"content":"","date":"2018年5月4日","externalUrl":null,"permalink":"/tags/package/","section":"Tags","summary":"","title":"Package","type":"tags"},{"content":"因為幾年的接案開發經驗，了解過許多客戶的需求後發現其實有多數人都擁有相同的基本需求\n舉例來說，這幾年大大小小的案子做下來都不知道已經寫過幾次的討論區，寫過幾次的最新消息，寫過幾次的會員系統了\n這些開發過的專案中大約有八成的內容都差不多，那為何每次都需要重新做呢？\n因此才有了這次想要把一些基本常用功能模組化的想法\n然侯在以前沒接觸過framework開發的時候，所謂模組化也僅僅是把class獨立出來寫好，以後可以繼續拿來用\n但是被改了哪寫東西，版本經過多少人修改根本都無法追蹤，現在透過Composer 幾乎可以專心在自己的邏輯處理了，剩下的版本問題就叫給Composer\n前面幾篇文章已經介紹過如何來使用Composer，如果你還沒看過請先查看\nComposer 幫PHP套件找一個家\n用Laravel 來建立你的第一個網站\n接下來的內容會帶大家透過Composer來製作一個Hello Laravel套件，僅作示範內容只有Hello\n參考文件 ：\nhttp://oomusou.io/laravel/laravel-package-hello-world/\nhttps://medium.com/back-ends/laravel-5-%E6%92%B0%E5%AF%AB%E4%BD%A0%E7%9A%84package-458c93c279bc\n步驟一 ：先說明邏輯概念 # 本篇範例會先建立一個主要的Larvel Project，作為管理和開發套件的專案，我會把之後所有要開發的套件全部都放在這個Project的packages資料夾底下\n步驟二 ： 建立主要Project # 我們先建立一個用來管理開發套件的主要Porject，名叫MyPackage\ncomposer create-project laravel/laravel MyPackage --prefer-dist 接著建立\u0026quot;packages\u0026quot;目錄，之後會把所有開發的套件資料全部放在這裡，套件資料夾的結構會長的像這樣 packages/vendor_name/package_name/src\n建立完成之後內容大概會像這樣子，範例使用virtualorz/hello/src\n步驟三 ：建立composer.json # 每一個套件都需要建立自己的composer.json，用來描述相依性以及namespace，先切換到hello資料夾然後透過composer指令來建立composer.json\ncd cd MyPackage/packages/virtualorz/hello composer init 接著按照提示填入package name等等的資訊\n系統會產生一個composer.json檔，像這樣子\n步驟四 ：設定PSR-4命名空間 # 我們必須回到上述管理package的project根目錄將剛剛建立好的package加入到composer.json中，讓他可以在這個project中運行\n請注意PSR-4大小寫的問題\n接著回到MyProject根目錄來產生新的autoload檔案\ncomposer dump-autoload 步驟五 ：建立ServiceProvider # 接下來要建立HelloServiceProvidor.php，套件與專案Project會透過ServiceProvider連結起來\nphp artisan make:provider HelloServiceProvider 這時候會在MyPackage/app/providers中建立好HelloServiceProvider.php 但是因為我們是要套件中使用\n所以必須要將這個檔案移動到 MyPackage/packages/virtualorz/hello/src 底下，也因為移動了所以必須要修改namespace\n大概會像下面這樣子\n接著在config/app.php 中註冊HelloServiceProvidor\n步驟六 ：建立Cntroller # 一樣可以透過指令的方式建立Controller，相同的建立完成之後我們會將Controller移動到 MyPackage/packages/virtualorz/hello/src 底下\nphp artisan make:controller HelloController 同樣的也必須要修改namespace，也因為extend Controller的關係，所以必須將App\\Http\\Controllers\\Controller 拿進來用\n步驟七 ：建立Route # Route可以根據不同的狀況來建立所需要的存取路徑，本次範例在 MyPackage/virtualorz/hello/src 底下建立routes.php，填入以下路徑\nRoute::get(\u0026#39;hello\u0026#39;, \u0026#39;VIrtualorz\\Hello\\HelloController@index\u0026#39;); 步驟八 ：建立View # 本次範例也不特別建立view，僅拿現成的welcome頁面來使用，我們將resources/views/welcome.blade.php 複製到 package/virtualorz/hello/src/views\n並且小修一下內容用來顯示controller中的message\n步驟九 ：修改ServiceProvider # 最後我們還要修改一下SeviceProvidor檔案，讓系統可以知道從哪邊將view讀進來\n步驟十 ：測試package是否可行 # 打開瀏覽器，輸入MyPackage所在的網址後面夾入/hello，本次的範例是這樣子的\nhttp://127.0.0.1:8888/hello 如果有看到這樣的畫面就是正確的，我們把message字串接在Laravel後面\n步驟十一 ：上傳到github並提交是packagist # 因為之後提交到packagist後使用者可以自行下載，當然不能讓使用者自行修改PSR-4才能使用，因此我們將hello 資料夾中的composer.json修改一下\n{ \u0026#34;name\u0026#34;: \u0026#34;virtualorz/hello\u0026#34;, \u0026#34;authors\u0026#34;: [ { \u0026#34;name\u0026#34;: \u0026#34;virtualorz\u0026#34;, \u0026#34;email\u0026#34;: \u0026#34;virtualorz@gmail.com\u0026#34; } ], \u0026#34;require\u0026#34;: {}, \u0026#34;autoload\u0026#34;: { \u0026#34;psr-4\u0026#34;: { \u0026#34;Virtualorz\\\\Hello\\\\\u0026#34;: \u0026#34;src/\u0026#34; } } } 接著在hello 資料夾下建立git，並且push到遠端github上\ngit init git add . git commit -m \u0026#34;Initial commit.\u0026#34; git remote add origin git@github.com:virtualorz/hello.git git push -u origin master git tag -a 1.0.0 -m \u0026#34;First version\u0026#34; git push --tags 請一定要記得下tag，並且在之後所有push的版本都加上tag，這樣packagist才有依據要抓哪一個版本的程式碼給使用者\n最後記得提交到Packagist上就可以啦\n注意事項 ：在開發過程中如果要變更版本號碼請記得砍掉composer.lock，讓系統重新抓composer.json中定義的版本號碼，這樣才會重新下載新的\n","date":"2018年5月4日","externalUrl":null,"permalink":"/2018/05/04/%E6%A8%A1%E7%B5%84%E5%8C%96-%E5%A5%97%E4%BB%B6-%E9%96%8B%E7%99%BC%E8%87%AA%E5%B7%B1%E7%9A%84package/","section":"Posts","summary":"","title":"模組化 套件 開發自己的Package","type":"posts"},{"content":"","date":"2018年5月2日","externalUrl":null,"permalink":"/tags/youtube/","section":"Tags","summary":"","title":"Youtube","type":"tags"},{"content":"Youtube 大家應該都不陌生，而上傳影片到Youtube 有用過的人大致上也不會太陌生\n可是呢，要透過程式也就是API的方式上傳可能就沒這麼容易了，當然google有提供了一系列的API可以使用其中也包含了youtube api\n只是實際使用上並不容易，還好已經有人寫好套件可以使用了，今天要來記錄一下 Laravel 5 - YouTube Video Upload\n參考網址 ： https://github.com/JoeDawson/youtube\n如果你還沒看過前幾篇文章可以先參考 ：\nComposer 幫PHP套件找一個家\nLaravel 的幾個重要資料夾\n步驟一 ：先安裝套件 # 跟安裝其他Composer套件相同，不難使用\ncomposer require dawson/youtube 接著修改config/app.php\n\u0026#39;providers\u0026#39; =\u0026gt; [ ... Dawson\\Youtube\\YoutubeServiceProvider::class, ], \u0026#39;aliases\u0026#39; =\u0026gt; [ ... \u0026#39;Youtube\u0026#39; =\u0026gt; Dawson\\Youtube\\Facades\\Youtube::class, ], 接著publish config\nphp artisan vendor:publish --provider=\u0026#34;Dawson\\Youtube\\YoutubeServiceProvider\u0026#34; 步驟二 ：產生資料表 # 透過migration 將資料表建立到你的資料庫中\n當然，在這之前你必須先設定好資料庫的連線參數\nphp artisan migrate 上面指令完成後就可以看到像這樣子的資料表已經產生\n步驟三 ：建立你的youtube api 專案 # 這個步驟比較麻煩，因為youtube api 必須要經過認證後才可以拿到使用權，因此必須要登入 Google\u0026rsquo;s Developer Console\n先建立一個專案，用來上傳你的youtube 影片使用\n完成後應該就會在列表上看到專案存在了\n點選進去可以看到這個project的一些細節，目前來說這些細節是不重要的\n接著我們要取得憑證ID以及金鑰，點選左上角的三條線選擇API和服務 \u0026gt; 憑證\n因為先在一張憑證都沒有，因此我們要選擇 建立憑證 \u0026gt; OAuth 用戶端 ID 這樣才能給網站使用\n應用程式類型的部分就選擇網路應用程式即可\n建立完成後就會看到下面兩項資訊，這兩樣很重要下個步驟需要加到程式碼中\n最後點選進去剛剛建立的憑證內容，我們需要填入\u0026quot;已授權的重新導向URI\u0026quot;\n內容請填入你的網站網址後面加上\u0026quot;youtube/callback\u0026quot;，例如 ：\nhttp://www.mydomain.com.tw/youtube/callback 步驟四 ：認證youtube api 並取得使用權限 # 首先將上個步驟取得的ID以及金鑰填入到.env變數中\nGOOGLE_CLIENT_ID=你的用戶端ID GOOGLE_CLIENT_SECRET=你的用戶端金鑰 接著我們必須編輯config/youtube.php 檔案\n將 \u0026lsquo;routes\u0026rsquo; =\u0026gt; [\u0026rsquo;enable\u0026rsquo; =\u0026gt; false] 改為true，要注意的是下個步驟認證完畢後請務必改回false\n接下來就可以在瀏覽器輸入以下網址開始進行認證\nhttp://www.mydomain.com.tw/youtube/auth 舉例來說，本次舉例的網址像這樣\n接著瀏覽器會跳出提示，請選擇需要登入google的帳號，如果瀏覽器有登入多個google帳號就會像這樣子有很多個，請挑選剛剛建立api專案的帳號登入\n登入後系統會跳出提示需要允許此帳號來管理您的youtube\n完成後，我們回到資料表來看可以發現已經將access_token寫入了，接著就可以來上傳影片啦\n步驟五 ：上傳你的youtube 影片 # 無論是在controller中還是直接寫在router中都可以，請記得$fullPathToVideo填入實際上電腦中影片的路徑即可\n標題、說明、tag都可以隨自己喜歡來定義影片\n$video = Youtube::upload($fullPathToVideo, [ \u0026#39;title\u0026#39; =\u0026gt; \u0026#39;My Awesome Video\u0026#39;, \u0026#39;description\u0026#39; =\u0026gt; \u0026#39;You can also specify your video description here.\u0026#39;, \u0026#39;tags\u0026#39;\t=\u0026gt; [\u0026#39;foo\u0026#39;, \u0026#39;bar\u0026#39;, \u0026#39;baz\u0026#39;], \u0026#39;category_id\u0026#39; =\u0026gt; 10 ]); return $video-\u0026gt;getVideoId(); 更多的參數可以參考原作者github上的說明文件\n到這邊為止就已經可以順利將影片上傳到Youtube 上啦，值得注意的是youtube api並不是完全免費的，使用時請注意每日存取量，超過就必須要付費啦～\n","date":"2018年5月2日","externalUrl":null,"permalink":"/2018/05/02/youtube-%E4%B8%8A%E5%82%B3%E5%BD%B1%E7%89%87-%E7%94%A8laravel-%E4%B8%8A%E5%82%B3%E5%BD%B1%E7%89%87%E5%88%B0youtube/","section":"Posts","summary":"","title":"Youtube 上傳影片 -  用Laravel 上傳影片到Youtube","type":"posts"},{"content":"","date":"2018年4月30日","externalUrl":null,"permalink":"/tags/linux/","section":"Tags","summary":"","title":"Linux","type":"tags"},{"content":"PHP 版本更新, 會遇到是因為原本正在跑的伺服器是已經多年前的版本了，為了新加入的功能最起碼必須更新到 PHP 5.6\n其實也不難，紀錄一下免得下次要找要找不到資料，範例是由5.5.9更新至5.6\n參考文件 ：https://www.digitalocean.com/community/questions/how-to-upgrade-from-php-v-5-5-9-to-v-5-6\n如果你還不熟悉架站，請先閱讀前面的文章\nubuntu 基礎架站\nubuntu 進階架站分享\nsudo add-apt-repository ppa:ondrej/php sudo apt-get update sudo apt-get install php5.6 php5.6-dev php5.6-mcrypt php5.6-mbstring php5.6-mysql php5.6-zip php5.6-gd php5.6-xml sudo a2dismod php5 sudo a2enmod php5.6 sudo service apache2 restart OK，到這邊會遇到第一個問題，根本就沒有add-apt-repository ppa:ondrej/php 這個指令阿，原來不是內建的呢，我們必須先安裝兩樣東西\nsudo apt-get install python-software-properties sudo apt-get install software-properties-common 這樣就可以繼續往下走了\n其實概念很簡單，因為是透過apt-get將 php 5.6安裝起來的，所以必須先講資料原加入也就是ppa:ondrej/php\n接著apt-get更新之後將該裝的php 5.6相關元件裝起來(當然也可以不需要裝這麼多)\n最後停用舊版啟用新版再將apache重新啟動就可以了\n","date":"2018年4月30日","externalUrl":null,"permalink":"/2018/04/30/php-%E7%89%88%E6%9C%AC%E6%9B%B4%E6%96%B0-apache%E4%B8%8A%E5%A6%82%E4%BD%95%E8%A8%AD%E5%AE%9A/","section":"Posts","summary":"","title":"PHP 版本更新, Apache上如何設定","type":"posts"},{"content":"我是Laravel 後端工程師 ，相信寫Code可以改變世界\n近期都在寫公司內部使用的系統，斜槓前端Nuxt.js、Next.js\n喜歡新鮮事\n喜歡嘗鮮\n支持台灣新創產業\nEmail : virtualorz@gmail.com\nTel : 0986687852\n","date":"2018年4月28日","externalUrl":null,"permalink":"/%E6%88%91%E6%98%AFalvin-chen/","section":"Alvin Chen Club","summary":"","title":"我是Alvin Chen","type":"page"},{"content":" Laravel 85% PHP 80% MySQL 75% Nuxt 85% Wordpress 50% 我是Alvin Chen 目前是一名 Laravel + Nuxt 全端工程師, 對於PHP程式開發與MySQL資料庫設計都有多年開發經驗。\n畢業於中央大學資訊工程學系碩士班，在中央研究院資訊所服務過接近四年，網站系統開發公司服務接近5年，以及行銷公司系統開發超過5年\n在中央研究院服務的那幾年以學術研究搭配網站工具為主要工作內容\n在網站系統開發公司的那幾年除了協助公司導入Git版本控制、雲端化客戶系統伺服器以外\n也積極在Laravel Framework 上帶入更多客戶使用框架式開發\n從Laravel 4.2 - 5.X 一共累積超過10個以上的系統開發經驗\n近幾年在系統開發上開始導入Nuxt框架，從Nuxt2到最新世代的Nuxt3，協助公司前後端工程師可以更細緻去分工\n將前端使用者介面做到更貼近內勤部門所需要的提示、排版、美化與結構性\n後端還是採用Laravel Framework，從Laravel 7 一路進化到Laravel 11\n除了開發上更愉快以外，也讓系統資料運算更謹慎且安全\n工作之餘，我個人也很喜歡到戶外曬曬太陽踏青，休閒時間多一點可以讓腦袋盡情放空，回到工作崗位後更容易擦出其他的火花或想法，\n也因此我非常支持台灣新創產業，無論是創意或者大膽的勇氣都令人佩服，2015年接觸到Gogoro後毫不猶豫的買下了一台，到目前為止它還是我的好夥伴，\n新創公司待久了會漸漸的讓自己更有想法，多與其他人互動也容易讓自己的視野更寬廣，所以我也積極參與Laravel相關的研討會與活動，\n期望學習到更多知識與吸收更多不同想法。\n這1~2年來，工作重心逐漸轉往跨部門的系統開發PM角色，雖然因為人手不足的關係還是要安排自己寫code的時間，但也因為對於系統面的結構了解程度夠高，\n才能夠快速地提供給各部門主管工作流程上在系統上的SOP操作流程以及資料運作的邏輯解釋，\n未來的方向還是會持續深耕Laravel 以及Nuxt框架，並持續精進自己的專業能力與溝通能力。\n過往作品整理\n","date":"2018年4月28日","externalUrl":null,"permalink":"/%E5%80%8B%E4%BA%BA%E7%B0%A1%E6%AD%B7-alvin-chen-laravel-%E5%B7%A5%E7%A8%8B%E5%B8%AB/","section":"Alvin Chen Club","summary":"","title":"個人簡歷 Alvin Chen Laravel 工程師","type":"page"},{"content":"Schedule 任務排程可以在很多種情況下使用到，比如說需要定期檢查某件事情完成與否，或扯定期將資料送往另一台伺服器\n又或者最常見的將電子報寄送，都是透過這種排成任務的方式來執行\n參考文件 ：https://kejyuntw.gitbooks.io/laravel-5-learning-notes/services/scheduling/services-scheduling-README.html\n基本概念 # 對Laravel 還不是很熟悉的朋友請先看過以下文章喔\nComposer 幫PHP套件找一個家\n用Laravel 來建立你的第一個網站\n首先，最基本的觀念是透過作業系統的定期排程工作來完成事件的觸發\n在windows上可以用排程工作設定需要定期執行哪個檔案(通常可以寫成.bat)\n在linux上可以透過\ncrontab -e 來編輯需要排成的工作指令，舉例\n# 每天凌晨 3 點做資料備份 0 3 * * * /usr/bin/php /home/document/project/artisan cronjob:backup 值得注意的是，前面0 3 跟 * 代表的是時間，分別說明如下\n分（0-59） 時（0-23） 每個月第幾天（1-31） 月份（1-12） 每週的第幾天（0-6），0代表星期日其他依序 參考範例\n# 每小時的第 18 分鐘執行 18 * * * * # 8 點 10 分執行 10 8 * * * # 8 點的每分鐘執行一次（共執行 60 次） * 8 * * * # 在每個禮拜二每小時的第 18 分鐘執行 18 * * * 2 # 你也可以每隔一段時間去執行 crontab # 如果我們每 15 分鐘要去執行，你可以用這樣的格式 */15 # 這樣的意思是將分鐘數，切割成（除以）每 15 分鐘執行 */15 * * * * # 每 2 小時執行 0 */2 * * * # 每 2 小時又 20 分鐘執行 */20 */2 * * * Larvel 實作 # 透過作業系統排程每分鐘執行 schedule:run 指令\n* * * * * php /path/to/artisan schedule:run \u0026gt;\u0026gt; /dev/null 2\u0026gt;\u0026amp;1 舉例說明 # 我們將要排程的工作寫在app/Console/Command/test.php\n\u0026lt;?php namespace App\\Console\\Commands; use Illuminate\\Console\\Command; use DB; class TestLog extends Command { // 命令名稱 protected $signature = \u0026#39;test:insert\u0026#39;; // 說明文字 protected $description = \u0026#39;[測試] 寫入資料庫\u0026#39;; public function __construct() { parent::__construct(); } // Console 執行的程式 public function handle() { DB::table(\u0026#39;log\u0026#39;) -\u0026gt;insert([ \u0026#39;time\u0026#39; =\u0026gt; date(\u0026#39;Y-m-d H:i:s\u0026#39;) ]) } } 重點 ：需要執行工作的地方寫在handle()中\n以上設定完成之後可以透過指令直接執行\nphp artisan test:insert 接著要在laravel中定義此排程工作，編輯檔案 app/Console/Command/Kernal.php\n大略會長成這樣子\n\u0026lt;?php namespace App\\Console; use Illuminate\\Console\\Scheduling\\Schedule; use Illuminate\\Foundation\\Console\\Kernel as ConsoleKernel; class Kernel extends ConsoleKernel { // 定義應用程式的 Artisan 指令 protected $commands = [ \\App\\Console\\Commands\\test::class, ]; // 定義應用程式的排程 protected function schedule(Schedule $schedule) { // 每分鐘執行 Artisan 命令 test:Log $schedule-\u0026gt;command(\u0026#39;test:insert\u0026#39;)-\u0026gt;everyMinute(); } } 接著就會在資料庫中看到每分鐘記錄下來的資料了\n避免重複執行 # 有些排程工作可能需要一點時間才能做完，修改$schedule指令可以避免工作還沒完成又再次進入工作\n$schedule-\u0026gt;command(\u0026#39;test:insert\u0026#39;)-\u0026gt;withoutOverlapping(); ","date":"2018年4月27日","externalUrl":null,"permalink":"/2018/04/27/schedule-%E4%BB%BB%E5%8B%99%E6%8E%92%E7%A8%8B%EF%BC%8C%E8%AE%93laravel-%E8%87%AA%E5%8B%95%E5%9F%B7%E8%A1%8C%E6%8E%92%E5%AE%9A%E4%BB%BB%E5%8B%99/","section":"Posts","summary":"","title":"Schedule 任務排程，讓Laravel 自動執行排定任務","type":"posts"},{"content":"","date":"2018年4月25日","externalUrl":null,"permalink":"/tags/wkhtmltopdf/","section":"Tags","summary":"","title":"Wkhtmltopdf","type":"tags"},{"content":"wkhtmltopfd 是轉換html成pdf的工具，在上一篇 ：Snappy PDF 用來產生PDF與圖片的外掛套件 已經介紹過在Larvel 中如何使用已開發好的套件來來將view或者html轉換成pdf了，\n接著在使用上會遇到的幾個問題也在這邊一併紀錄說明\n1. Linux上可能會遇到的問題 # 前次的專案第一次將含有wkhtmltopdf的外掛專案放上linux，原本預期應該沒什麼問題的，只是真的不會這麼順利，出現了錯誤\nwkhtmltopdf: error while loading shared libraries: libXrender.so.1: cannot open shared object file: No such file or directory 當然這種問題我不可能是第一個遇到的，也很快的在stackoverflow上找到的解答 ：Error running wkhtmltopdf — (error while loading shared libraries)\n還好不是什麼大問題，就只差在一個相依性的軟體沒有安裝\nsudo apt-get install libxrender1 很快的一個指令將軟體裝上就處理完成了\n2. 中文內容的HTML如果碰上亂碼怎麼辦 # 通常發生中文或者其他語言亂碼的問題都是因為伺服器上的編碼錯誤或者字型沒有安裝造成的，\n在網頁上顯示中文沒問題，但是要將中文字寫入檔案就會考驗到這台伺服器是不是有安裝中文字型了，通常像是我們在國外租的Linux伺服器是不會有中文字型的，因此需要將它安裝上去\n參考文件 ：http://cnedelcu.blogspot.tw/2015/04/wkhtmltopdf-chinese-character-support.html\n首先安裝字型\napt-get install fonts-wqy-microhei ttf-wqy-microhei fonts-wqy-zenhei ttf-wqy-zenhei 接著更新字型快取\nfc-cache -f -v 原則上問題就已解決\n希望兩個遇過的問題可以幫助到有需要的人\n","date":"2018年4月25日","externalUrl":null,"permalink":"/2018/04/25/wkhtmltopfd-linux%E5%95%8F%E9%A1%8C%E4%BB%A5%E5%8F%8A%E4%B8%AD%E6%96%87%E4%BA%82%E7%A2%BC%E8%99%95%E7%90%86/","section":"Posts","summary":"","title":"wkhtmltopfd Linux問題以及中文亂碼處理","type":"posts"},{"content":"安裝wordpress, 本篇文章參考了wordpress官網的 引以為傲的五分鐘快速安裝\n內容稍作修改，適合可以透過SSH連線到遠端伺服器的朋友們使用，為什麼說30秒呢，請閱讀完步驟後查看最後的超懶人架站法\n本篇安裝wordpress 的範例建構在Ubuntu 16.04 且已安裝好LAMP 的情況下建立wordpress網站，\n如您還沒將網站建立好請先閱讀 :\nubuntu 基礎架站\nubuntu 進階架站分享\n在開始之前請先確定兩件事情\n可以連線到資料庫，我指的是待會兒要給wordpress使用的資料庫伺服器\n可以透過SSH連線到遠端伺服器並登入\n步驟一 : 先建立wordpress要使用的資料庫 # 你可以透過phpMyAdmin來建立或者跟這次示範的相同使用其他軟體來連線，本次使用Navicat for MySQL來連線到遠端資料庫\n先建立一個資料庫名稱為wordpress_test\n步驟二 : 透過SSH連線到遠端伺服器並登入，下載wordpress # 接著，就可以開始決定要安裝wordpress 在哪個資料夾了，這次的範例示範安裝wordpress 在 /var/www/html/worpress/ 底下\n因此先切換到該目錄底下\ncd /var/www/html/wordpress 接下來到官網上下載最新版的wordpress 查看下載頁面\n本次範例已4.9版的zip檔做示範，下載檔案並解壓縮\nsudo wget https://tw.wordpress.org/wordpress-4.9-zh_TW.zip sudo unzip wordpress-4.9-zh_TW.zip 解壓所後你會看到有個wordpress的資料夾\n接著，我會建議將wordpress 資料夾改名子比較好區別的名字，本次範例就修改為wordpress_test\n並且呢，需要修改寫入權限，讓待會安裝wordpress 的時候可以順利寫入wp-config.php檔案\nsudo mv wordpress wordpress_test sudo chmod 777 wordpress_test 步驟三 : 透過連覽器連結到wordpress並且開始安裝wordpress # 舉例來說，我的遠端伺服器位置為 192.168.1.111，那麼我就可以透過瀏覽器瀏覽\nhttp://192.168.1.111/wordpress/wordpress_test/ 就會看到這樣子的畫面\n按下\u0026quot;衝吧\u0026quot;，下一步將資料填寫完畢大致上就完成了!\n這邊需要填寫資料庫連線的內容，也就是我們步驟一設定好的內容\n如果設定資料都沒有問題，那就會看到下面這個畫面，就可以開始進行wordpress 安裝 了\n按下執行安裝，就可以完成安裝wordpress 手續了\n最後只要設定好你的網站標題 使用者名稱密碼以及電子郵件，安裝wordpress 手續就差不多結束了\n最後，最後還記得我們剛剛有修改些入權限嗎? 安裝wordpress 完成之後為了安全起見我們還是將權限修改回來，並且開放檔案上傳資料夾的權限避免無法上傳檔案\nsudo chmod 755 wordpress_test sudo chmod 777 -R wordpress_test/wp-content 接著就可以開始享受你的wordpress部落格網站了!\n補充 ：wordpress 更新需要FTP的問題 # 安裝完成後發現，要更新或者安裝主題會跳出來要求FTP上傳帳號密碼，google後發現應該是權限造成的問題，因此我們需要修改資料夾權限\nsudo chown -R www-data:www-data /var/www/html/wordpress/wordpress_test 補充 ：超懶人架站法 # 如果你跟我一樣很懶，覺得上面指令步驟好多那麼，可以下載這個sh檔就可以將事情完成，下載之後記得要修改PROJECT_NAME 改成你要的名字\nwget http://www.alvinchen.club:8082/wp_install.sh 修改PROJECT_NAME\nsudo nano wp_install.sh 執行sh檔\nsh wp_install.sh 另外! 在執行期間 會有提示請在網頁安裝完成後再按下Enter\n請務必注意!! 一定要於網頁安裝完成後再按下enter，否則必須要刪除資料夾重新執行sh檔\n刪除專案資料夾\nsudo rm -rf /var/www/html/wordpress/你改的PROJECT_NAME ","date":"2018年4月23日","externalUrl":null,"permalink":"/2018/04/23/%E5%AE%89%E8%A3%9Dwordpress-30%E7%A7%92%E6%9E%B6%E5%A5%BD%E4%BD%A0%E7%9A%84%E7%B6%B2%E7%AB%99/","section":"Posts","summary":"","title":"安裝wordpress, 30秒架好你的網站","type":"posts"},{"content":"Laravel Snappy PDF是用來將網頁HTML轉換成PDF或者圖片下載的外掛工具，可以用在需要讓使用者下載的表單中需要帶入使用者的姓名或者其他動態資料的狀況，因此就不能將PDF做成單純的檔案下載，而是將HTML樣板製作完畢，然後下載前才將使用者資料帶入HTML之後轉換成PDF讓使用者下載\n外掛github ：https://github.com/barryvdh/laravel-snappy\n如果你還沒有看過Larvel請參考之前的文章 ：\nLaravel ,用Laravel 來建立你的第一個網站\n第一步 ：安裝 wkhtmltopdf\n參考網頁 ：https://github.com/KnpLabs/snappy#wkhtmltopdf-binary-as-composer-dependencies\n最簡單的安裝方式是透過Composer安裝\ncomposer require h4cc/wkhtmltopdf-i386 0.12.x composer require h4cc/wkhtmltoimage-i386 0.12.x composer require h4cc/wkhtmltopdf-amd64 0.12.x composer require h4cc/wkhtmltoimage-amd64 0.12.x 可以依照自己的需求選擇安裝x86或者x64的版本，也可選擇只裝轉換PDF的套件或者圖片的套件\n第二步 ：將wkhtmltopdf binary檔案放到適當的位置\n我是將binary放到 MyProject/vendor/vendor/wkhtmltopdf 底下\n可以根據不同的作業系統建立不同的資料夾來存放，因為不同作業系統的binary檔是不同的，比如說\nLinux : MyProject/vendor/vendor/wkhtmltopdf/linux-64/bin/wkhtmltopdf windows : MyProject/vendor/vendor/wkhtmltopdf/win-32/bin/wkhtmltopdf.exe (還有wkhtmltox.dll)\n第三步 ：讓這個檔案具有執行權限\n在linux上\nchmod +x MyProject/vendor/vendor/wkhtmltopdf/linux-64/bin/wkhtmltopdf windows上就對檔案按右鍵\u0026gt;內容\u0026gt;將執行權限打勾即可\n第四步 ：安裝snappy 套件\n一樣透過Composer 就可以了\ncomposer require barryvdh/laravel-snappy 第五步 ：設定Laravel 的config/app.php檔案\n將service provider以及alias加進來\n\u0026#39;providers\u0026#39; =\u0026gt; [ //略 Barryvdh\\Snappy\\ServiceProvider::class, ], \u0026#39;aliases\u0026#39; =\u0026gt; [ //略 \u0026#39;PDF\u0026#39; =\u0026gt; Barryvdh\\Snappy\\Facades\\SnappyPdf::class, ], 第六步 ：publish config\nphp artisan vendor:publish --provider=\u0026#34;Barryvdh\\Snappy\\ServiceProvider\u0026#34; 第七步 ：編輯snappy config檔\nreturn array( \u0026#39;pdf\u0026#39; =\u0026gt; array( \u0026#39;enabled\u0026#39; =\u0026gt; true, \u0026#39;binary\u0026#39; =\u0026gt; \u0026#39;\u0026#34;\u0026#39; . base_path(\u0026#39;vendor/vendor/wkhtmltopdf/\u0026#39;.env(\u0026#39;OS_TYPE\u0026#39;).\u0026#39;-\u0026#39; . env(\u0026#39;OS_BIT\u0026#39;) . \u0026#39;/bin/wkhtmltopdf\u0026#39;) . \u0026#39;\u0026#34;\u0026#39;, \u0026#39;timeout\u0026#39; =\u0026gt; false, \u0026#39;options\u0026#39; =\u0026gt; array(), ), \u0026#39;image\u0026#39; =\u0026gt; array( \u0026#39;enabled\u0026#39; =\u0026gt; true, \u0026#39;binary\u0026#39; =\u0026gt; \u0026#39;\u0026#34;\u0026#39; . base_path(\u0026#39;vendor/vendor/wkhtmltopdf/\u0026#39;.env(\u0026#39;OS_TYPE\u0026#39;).\u0026#39;-\u0026#39; . env(\u0026#39;OS_BIT\u0026#39;) . \u0026#39;/bin/wkhtmltoimage\u0026#39;) . \u0026#39;\u0026#34;\u0026#39;, \u0026#39;timeout\u0026#39; =\u0026gt; false, \u0026#39;options\u0026#39; =\u0026gt; array(), ), ); 這邊會分兩個部分，一個是pdf一個是image，分別代表處理pdf以及圖片的兩個binary檔，範例中我將binary =\u0026gt; 指定到第二步驟所設定的位置\n其中因應專案可能跑在不同的位置上所以作業系統的資料夾就用.env中的參數來帶入\n第八步 ：開心使用\n$pdf = PDF::loadView(\u0026#39;pdf.invoice\u0026#39;, $data); return $pdf-\u0026gt;download(\u0026#39;invoice.pdf\u0026#39;); 範例中直接講 pdf.invoice 這個view帶入$data變數，然後直接產生invoice.pdf提供下載\n其中$data就是要顯示在pdf.invoice 中的變數\n","date":"2018年4月21日","externalUrl":null,"permalink":"/2018/04/21/snappy-pdf-%E7%94%A8%E4%BE%86%E7%94%A2%E7%94%9Fpdf%E8%88%87%E5%9C%96%E7%89%87%E7%9A%84%E5%A4%96%E6%8E%9B%E5%A5%97%E4%BB%B6/","section":"Posts","summary":"","title":"Snappy PDF 用來產生PDF與圖片的外掛套件","type":"posts"},{"content":"Laravel 可以很簡單的透過Composer 將所有資料下載到開發機器上，如果還沒看過，請參考上篇 Laravel ,用Laravel 來建立你的第一個網站\n但是其中的資料夾卻是非常的複雜，本篇只帶入們其他更詳細的內容可以參考 Laravel官網文件\n第一點 ：Router # Router是我覺得用Framework開發網站可能面臨的第一個邏輯問題，因為以往透過PHP直接開發的網站並沒有Router這樣的概念\n在Laravel 上，Router就是先定義好你的網站有哪些頁面，分別接收哪些參數使用哪幾個Controller等等的設定項目，舉個例子\n在剛剛安裝好的Laravel 專案中有個router這樣寫\nRoute::get(\u0026#39;/\u0026#39;, function () { return view(\u0026#39;welcome\u0026#39;); }); 意思就是說，當今天進入網站的第一個page，也就是\u0026quot;/\u0026quot;，則需要回傳welcome.blade.php 這個view的內容，view的部分後面再說明，可以先想像成一個靜態的HTML畫面即可\n那當然我們就可以在這上面做一些手腳了，比如說這樣子\nRoute::get(\u0026#39;profile\u0026#39;, [ \u0026#39;uses\u0026#39; =\u0026gt; \u0026#39;UserController@showProfile\u0026#39; ]); 當連結到網址 ：http://yourdomain.com/profile 時，我們會執行UserController的showProfile function\n當然Router還有許多進階的使用方式，可以在熟悉之後嘗試使用\n第二點 ：app/Http/Controllers # Controller在Laravel 中扮演著重要的角色，基本上每個頁面所需要的邏輯、資料處理都是在Controller處理完成，也因此Controller可以說是整個網站最重要的一個部分，舉個例子來看\nclass UserController extends Controller { public function getIndex() { // } public function postProfile() { // } public function anyLogin() { // } } UserController中包含三個function，如果搭配上面的router來看就可以指定三個路徑分別呼叫UserController中的三個function做事情\n第三點 ：resources/views # View代表著使用者眼睛看到的視覺部分，Laravel 的view是透過blade來製作的，通常會搭配Controller將PHP處理好的變數放進View中顯示在網頁上\n舉個例子：\n\u0026lt;?php namespace App\\Http\\Controllers; use Illuminate\\Http\\Request; use Illuminate\\Routing\\Controller; class UserController extends Controller { /** * Store a new user. * * @param Request $request * @return Response */ public function store(Request $request) { $name = $request-\u0026gt;input(\u0026#39;name\u0026#39;); $this-\u0026gt;view-\u0026gt;with(\u0026#39;name\u0026#39;, $name); return $this-\u0026gt;view; } } Controller中把request收到name放進變數name中\n\u0026lt;html\u0026gt; \u0026lt;body\u0026gt; \u0026lt;h1\u0026gt;Hello, {{ $name }}\u0026lt;/h1\u0026gt; \u0026lt;/body\u0026gt; \u0026lt;/html\u0026gt; View中用{{ }} 大括號將想要顯示在畫面上的變數印出\n第四點 ：vendor # vendor資料夾中存放的就是在這個專案中會用到的許許多多套件了，也就是在 Composer 幫PHP套件找一個家\n這篇中所介紹的Compoer管理的套件，套件種類非常多元，安裝方式通常作者都會提供只要依照提示操作通常就可以使用了\n以上幾點大概是Larvel 入門最常用的幾個資料夾\n","date":"2018年4月19日","externalUrl":null,"permalink":"/2018/04/19/laravel-%E7%9A%84%E5%B9%BE%E5%80%8B%E9%87%8D%E8%A6%81%E8%B3%87%E6%96%99%E5%A4%BE/","section":"Posts","summary":"","title":"Laravel 的幾個重要資料夾","type":"posts"},{"content":"","date":"2018年4月17日","externalUrl":null,"permalink":"/tags/btsdesign/","section":"Tags","summary":"","title":"Btsdesign","type":"tags"},{"content":"","date":"2018年4月17日","externalUrl":null,"permalink":"/tags/laravelconf/","section":"Tags","summary":"","title":"Laravelconf","type":"tags"},{"content":"LaravelConf Taiwan又來啦～ 去年2017是LaravelConf Taiwan第一次舉辦，活動內容真的超級精彩，又真的可以跟神人們學習到有用的資訊\n今年當然不例外持續參加啦，雖然名額有增加但是相對的報名人數應該也是會暴增的 活動日期在2018/07/08\n地點在張榮發國際會議中心\n票價可以分為幾種\n盲鳥票：最便宜的日場券，但是完全不知道議程就先買了，1500 早鳥票：顧名思義議程一邊公布中最早買的票，1800 一般票：議程完整後正常的購票管道，2200 鐵粉團票：三人團購，5280 交流趴入場券：會議後的交流會，400 佛心票：個人贊助的意思，包含交流趴入場券，3500 盲鳥票4/18中午開搶啦，數量有限有興趣的大家趕快來吧！\n有興趣的朋友其他細節可參考：https://medium.com/laraveldojo/laravelconf-taiwan-2018-ticket-announcement-f9540b20b71\n期待今年可以學習到更多新知！\n如果你還沒用過Larvel，請先查看： Laravel ,用Laravel 來建立你的第一個網站\n當然，在這之前還是必須先閱讀 ：Composer 幫PHP套件找一個家\n最後祝福大家都能順利買到票！\n","date":"2018年4月17日","externalUrl":null,"permalink":"/2018/04/17/laravelconf-taiwan-2018-%E4%BE%86%E5%95%A6%EF%BD%9E%EF%BD%9E/","section":"Posts","summary":"","title":"LaravelConf Taiwan 2018 來啦～～","type":"posts"},{"content":"Laravel 安裝需要用到Composer如果你還沒有看過上一篇Composer ：Composer 幫PHP套件找一個家 先依照教學步驟將Composer安裝起來，因為Laravel 可以透過Composer的一個指令就將相對應的所有檔案下載到您的資料夾，直接就可以完成所有檔案的下載。\nLaravel 官網通常建議用虛擬機直接建立好所有的開發環境，但是個人習慣是直接建立實際的開發環境，一來是建立其實不會太難，二來是開發完畢後還是必須要實際建立營運的環境，那不如一開始的開發環境就實際跑一次也避免網站要上線時出現非預期的錯誤\n其他資源可以參考 ：LARAVEL 道場\n步驟一：透過Composer將Laravel 專案資料夾下載到電腦中 # 範例以專案資料夾 Documents/Laravel 作為示範，將建立一個MyProject專案在這個目錄下\ncomposer create-project laravel/laravel MyProject --prefer-dist 接著Composer會開始執行相對應的資料下載，完成後你會看到類似這樣子的資料結構\n步驟二：將伺服器的根目錄指定到 Documents/Laravel/MyProject/Public/ 資料夾 # 如果不曉得怎麼把網站根目錄指定到這個位置，請參考 ubuntu 基礎架站\n設定完成之後應該就可以看到以下畫面了喔\n這時候代表著網站本身已經建立起來了，是不是很簡單呢\n接著下篇再跟大家簡單介紹一下個資料夾的作用\n","date":"2018年4月16日","externalUrl":null,"permalink":"/2018/04/16/laravel-%E7%94%A8laravel-%E4%BE%86%E5%BB%BA%E7%AB%8B%E4%BD%A0%E7%9A%84%E7%AC%AC%E4%B8%80%E5%80%8B%E7%B6%B2%E7%AB%99/","section":"Posts","summary":"","title":"Laravel ,用Laravel 來建立你的第一個網站","type":"posts"},{"content":"Composer 是什麼？我想這會是第一次看到這個名詞所有人的疑問，因為當我第一次接觸到composer 時也是滿臉問號\nComposer 是PHP的套件管理器，講白的就是當你今天用別人寫好的套件時如果沒有一個好用的管理器其實版本永遠就不會更新了\n透過Composer 可以做到用簡單的介面管理所使用的套件，並且可以用一個指令就講所有套件檢查更新，一但作者有更新也不會永遠都停留在最初的版本\n其實第一次接觸到Composer 並不是在以前剛開始學PHP或者是拿PHP接案賺錢的時候，是直到這幾年接觸了Laravel才知道還有Composer 這樣的套件管理器存在，所以我將這篇文章歸類在Laravel中，而因為開始安裝Laravel之前必須透過Composer 來下載相關的檔案，因此就先從這篇文章開始吧\n焦點ㄧ：如何安裝Composer # Composer 的安裝方式會因為各系統不同而有不一樣的方式，這邊就介紹兩個常用：windows版本跟Mac版本\n剩下的就請參考Composer 官網吧 ：https://getcomposer.ycnets.com/\nWindows 安裝Composer 的方式 Windows的安裝方式相對簡單一些，只要下載執行檔 Composer-Setup.exe\n下載執行這個檔案，完成後會自動建立好相對應的路徑，但要注意的是安裝完成後必須關掉重新啟動命令提示字元Composer 才會有動作\nMac 安裝Composer 的方式 Mac的安裝方式相對複雜一些些，下面紀錄一下google查到的神人筆記，自己也是按照這樣的方式裝起來的，感覺比較容易懂\n首先開啟terminal來下載Composer.phar\ncurl -sS https://getcomposer.org/installer | php 接著composer.phar 移動到 usr/local/bin\nsudo mv composer.phar /usr/local/bin/composer 焦點二：測試一下Composer 是不是正常運作 # composer -v 如果有出現類似下面的畫面大致上就是安裝完成了\n焦點三：接下來就是可以開始安裝套件了 # composer require \u0026lt;package-name\u0026gt; 最後，在開始熟悉Composer 之前先看看如何架起伺服器吧 ubuntu-基礎架站\n","date":"2018年4月14日","externalUrl":null,"permalink":"/2018/04/14/composer-%E5%B9%ABphp%E5%A5%97%E4%BB%B6%E6%89%BE%E4%B8%80%E5%80%8B%E5%AE%B6/","section":"Posts","summary":"","title":"Composer 幫PHP套件找一個家","type":"posts"},{"content":"","date":"2018年4月14日","externalUrl":null,"permalink":"/tags/awstats/","section":"Tags","summary":"","title":"Awstats","type":"tags"},{"content":"awstats 是一套流量計算軟體\n參考網址 ：https://blogging.dragon.org.uk/installing-awstats-on-ubuntu-16-04-lts/\n參考網址2 ：http://carl830.pixnet.net/blog/post/66466219-ubuntu-%E5%AE%89%E8%A3%9D-awstats-%E5%88%86%E6%9E%90%E7%B6%B2%E7%AB%99%E6%B5%81%E9%87%8F\n參考網址3：http://koo.fi/blog/2012/12/01/awstats-multi-site-setup/\n依照前起篇的教學如果網站架設好了，當然你會想要了解有多少人來你的網站看過，這時候Google Analytics是一個很好用的工具，只是GA無法知道整體的流量有多少，舉個例子好了，我想要知道使用者從我的伺服器下載多少流量的檔案，這時候GA就無法告訴你了，所以需要依賴第三方軟體Awstats\n步驟一： 安裝Awstats # sudo apt-get install awstats libgeo-ipfree-perl libnet-ip-perl 步驟二： 設定Awstats # 把預設的config複製一份出來，為了以後識別方便因此將檔案名稱設定成domain name，nano打開編輯\nsudo cp /etc/awstats/awstats.conf /etc/awstats/awstats.yourdomain.conf sudo nano /etc/awstats/awstats.yourdomain.conf 接著注意幾個需要修改的地方\nLogFormat=1 SiteDomain=\u0026#34;yourdomain.com\u0026#34; HostAliases=\u0026#34;localhost 127.0.0.1 example.com\u0026#34; LoadPlugin=\u0026#34;tooltips\u0026#34; LoadPlugin=\u0026#34;graphgooglechartapi\u0026#34; LoadPlugin=\u0026#34;geoipfree\u0026#34; SkipDNSLookupFor=\u0026#34;REGEX[^192\\.168\\.]\u0026#34; AllowFullYearView=3 LogFile=\u0026#34;/var/log/apache2/access.log\u0026#34; 「LogFormat=」的部份，將預設的4修改為1，如：LogFormat=1 (如果後面執行的時候顯示格式有錯，可以改回4，但是可能有些東西會沒辦法分析)\n「LogFile=」的部份，修改並加上加上你要讀的access log檔\n「SiteDomain=」的部份，設定你網站的網址\n其他應該是不需要異動\n步驟三： 移除現有檔案紀錄 # 由於awstats 是透過分析Apache的紀錄檔進而計算流量，並且會將紀錄寫入awstats 的記錄檔，因此在開始之前清除既有的紀錄檔，可以避免計算有誤\nsudo mv /etc/cron.d/awstats /root sudo rm /var/lib/awstats/* 步驟四：手動執行一次 # 基本上awstats 的設定已經完成，先手動跑一次看看有沒有錯誤的狀況\nsudo /usr/lib/cgi-bin/awstats.pl -config=yourdomain.com -update 基本上如果沒有錯誤的話應該會看到類似以下的畫面\n步驟五：設定定期自動讀取檔案 # 基本上不可能時常手動的執行上面的指令讓awstats 去分析檔案，因此我們必須要設定一個排程讓ubuntu自動去執行指令\nsudo mv /root/awstats /etc/cron.d sudo nano /etc/cron.d/awstats 透過nano編輯cron.d底下的asstats檔案，裡面只需要加入一行，每10分鐘去執行一次指令\n*/10 * * * * root /usr/lib/cgi-bin/awstats.pl -update \u0026gt; /dev/null 步驟六：設定Apache Config，讓網頁可以看到資料 # 基本上可以直接修改000-default.conf就可以了，當然也可以新增一個awstats .conf，範例用新增一個awstats .conf來說明\nsudo nano /etc/apache2/sites-available/awstats.conf 接著在檔案輸入以下內容\nAlias /awstatsclasses \u0026#34;/usr/share/awstats/lib/\u0026#34; Alias /awstats-icon/ \u0026#34;/usr/share/awstats/icon/\u0026#34; Alias /awstatscss \u0026#34;/usr/share/doc/awstats/examples/css\u0026#34; ScriptAlias /cgi-bin/ /usr/lib/cgi-bin/ ScriptAlias /awstats/ /usr/lib/cgi-bin/ Options +ExecCGI -MultiViews +SymLinksIfOwnerMatch 如果呢，你想要讓awstats 執行在其他的port上那麼請參考Ubuntu基礎架站 的步驟四，設定好port的部分\n步驟五：開啟apache cgi mode，並重新啟動 # sudo a2enmod cgi sudo service apache2 restart 以上步驟執行完畢就可以打開瀏覽器\nhttp://yourdomain.com/cgi-bin/awstats.pl?config=yourdomain 就可以看到網站的流量數字啦，基本上會像下圖這樣子\n","date":"2018年4月14日","externalUrl":null,"permalink":"/2018/04/14/awstats-%E8%AE%93%E4%BD%A0%E7%9F%A5%E9%81%93%E7%B6%B2%E7%AB%99%E6%B5%81%E9%87%8F%E6%9C%89%E5%A4%9A%E5%B0%91/","section":"Posts","summary":"","title":"Awstats 讓你知道網站流量有多少","type":"posts"},{"content":"ubuntu 進階架站, 前兩篇分享了如何在一台空的ubuntu架起網站伺服器\n以及如何讓Apache可以支援SSL的設定\n接下來我想要分享幾個比較進階會遇到的問題\n第一點 ：禁止使用者直接從IP存取網站 # 參考網址 ：https://edmondscommerce.github.io/apache/blocking-direct-ip-access-in-apache.html\n如果你的網站架設起來了，有了網址，當然這時候你不會希望使用者直接透過IP來存取網站，而是透過網址來存取\n這時候就必須要修改Apache Config，讓使用者無法直接透過IP存取網站\n\u0026lt;VirtualHost *:80\u0026gt; ServerName 123.123.123.123 Redirect 403 / ErrorDocument 403 \u0026#34;No\u0026#34; DocumentRoot /dev/null/ UseCanonicalName Off UserDir disabled \u0026lt;/VirtualHost\u0026gt; 這樣子做，如果直接從IP存取網站就會出現403頁面\n第二點：設定MySQL可以遠端連線 # 參考網址 ：https://askubuntu.com/questions/272077/port-3306-appears-to-be-closed-on-my-ubuntu-server\n通常網站架設完畢後為了確保資料安全，MySQL資料庫是不會開放給遠端連線的，但是有時候因為管理上的需要每次都需要連線到遠端處理也是麻煩\n因此通常在維護初期我們都會設定開放讓自己的工作機可以連線到遠端資料庫\n可以透過phpMyAdmin新建一個使用者讓他可以從任何一台主機存取就行了\n\u0026#39;mytestuser\u0026#39;@\u0026#39;%\u0026#39; 切換到使用者帳號的頁面，從列表上可以看到目前的主機名稱都是localshot，也就是代表著這些帳號都只能從本積存去\n接著按下新增使用者\n要注意的是在主機名稱的地方填入%，代表的是這個帳號可以從任意主機存取資料庫\n當然權限的部分也可以自己依照需求設定，於開發階段當然都是全數勾選啦\n接著我們必須修改mysql的binding address讓其他IP也可以連線\n修改/etc/mysql/mysql.conf.d/mysqld.cnf 檔案\nsudo nano /etc/mysql/mysql.conf.d/mysqld.cnf 找到這行\nbind-address = 127.0.0.1 在前面加上#字號變成\n#bind-address = 127.0.0.1 接著存檔重新啟動mysql即可\nsudo service mysql restart 這麼一來，root這個帳號就可以從任意一台機器存取MySQL了\n第三點：建立FTP伺服器 # 參考網址：https://www.digitalocean.com/community/tutorials/how-to-configure-vsftpd-to-use-ssl-tls-on-an-ubuntu-vps\n透過vsftp來建立FTP伺服器\nsudo apt-get install vsftpd 編輯vsftp設定檔\nsudo nano /etc/vsftpd.conf 注意以下幾項參數\nanonymous_enable=NO local_enable=YES write_enable=YES chroot_local_user=YES 接著建立FTP使用者\nsudo adduser ftpuser sudo chown root:root /home/ftpuser sudo mkdir /home/ftpuser/files sudo chown ftpuser:ftpuser /home/ftpuser/files 基本上就完成了！\n最後記得重新啟動ftp伺服器\nsudo service vsftpd restart ","date":"2018年4月13日","externalUrl":null,"permalink":"/2018/04/13/ubuntu-%E6%9E%B6%E7%AB%99%E9%80%B2%E9%9A%8E%E5%88%86%E4%BA%AB/","section":"Posts","summary":"","title":"ubuntu 進階架站 分享","type":"posts"},{"content":"","date":"2018年4月13日","externalUrl":null,"permalink":"/tags/https/","section":"Tags","summary":"","title":"Https","type":"tags"},{"content":"","date":"2018年4月13日","externalUrl":null,"permalink":"/tags/ssl/","section":"Tags","summary":"","title":"Ssl","type":"tags"},{"content":"Apache支援SSL ，完成了基礎架站之後，如果有設定SSL 讓你的Apache 可以透過Https連線的需求可直接參考以下做法\n首先您必須先購買ssl憑證，購買憑證其實不難只是等級分非常多，簡單分辨的方法呢，如果你的網站沒有金流串接，那麼基本上不會有太大的問題\n國內購買憑證有很多管道，只是單價不怎麼便宜，國外的便宜許多，只是礙於網站是全英文的也許很多人會卻步，個人是習慣在Godaddy直接購買SSL 或者是網址\n除了網站是中文的比較親民以外，價格上也不算太貴而且設定很方便喔！\nhttps://tw.godaddy.com/\n如果順利購買完成應該就會看到像這樣子的畫面\n那麼SSL 憑證購買完成後，接下來就讓我們來看看如何設定Apache 吧\n步驟一 ：產生CSR # CSR是產生伺服器的認證檔案，每一台伺服器都是唯一的，因此如果日後網站換了伺服器(無論是換到Apache 或者IIS )就必須要重新產生CSR再與憑證商重新認證下載憑證\nopenssl req -new -newkey rsa:2048 -nodes -keyout yourdomainname.key -out yourdomainname.csr 接下來會有一大段的提示，請依照您的網站資料填寫即可，完成後會產生兩個檔案*.key以及*.csr\n這時您必須打開csr將內容提交給憑證廠商用於下載憑證檔\nsudo nano your_domain.csr 內容大致上會類似這樣子，會是一段看不懂的亂碼，只要將其中的文字全數複製起來依照憑證商的指示繼續操作就可以了\n步驟二 ：打開Apache 的SSL mode，並重新啟動Apache # sudo a2enmod ssl sudo service apache2 restart 步驟三 ：設定Apache config # 將我們剛剛產生的key以及從憑證商下載的crt檔 bundle檔收好，此範例會放在/var/ssl/ 這個資料架底下，接著修改Apache config檔\n我們就拿上次的範例來看\n#/etc/apache2/sites-available/site1.conf Listen 8080 \u0026lt;VirtualHost *:8080\u0026gt; ServerAdmin admin@example.com ServerName example.com ServerAlias www.example.com DocumentRoot /var/www/example.com/public_html ErrorLog ${APACHE_LOG_DIR}/error.log CustomLog ${APACHE_LOG_DIR}/access.log combined SSLEngine on SSLCertificateFile /var/ssl/your_name.crt SSLCertificateKeyFile /var/ssl/your_domain_name.key SSLCertificateChainFile /var/ssl/boundle_name.crt \u0026lt;Directory /var/www/html\u0026gt; Options Indexes FollowSymLinks MultiViews AllowOverride All Order allow,deny allow from all \u0026lt;/Directory\u0026gt; \u0026lt;/VirtualHost\u0026gt; 加入了SSLEngine 這一塊，記得檔案路徑與名稱要對，接著再次重新啟動Apache\n步驟四：重新導向Http到Https # 既然都已經設定好了Https，那就沒有理由讓User繼續瀏覽Http了，所以要將連線到Http的用戶直接導向Https\n一樣修改上面的Apache Config\n#/etc/apache2/sites-available/site1.conf \u0026lt;VirtualHost *:443\u0026gt; RewriteEngine On RewriteCond %{HTTPS} off RewriteRule (.*) https://%{HTTP_HOST}%{REQUEST_URI} ServerAdmin admin@example.com ServerName example.com ServerAlias www.example.com DocumentRoot /var/www/example.com/public_html ErrorLog ${APACHE_LOG_DIR}/error.log CustomLog ${APACHE_LOG_DIR}/access.log combined SSLEngine on SSLCertificateFile /var/ssl/your_name.crt SSLCertificateKeyFile /var/ssl/your_domain_name.key SSLCertificateChainFile /var/ssl/boundle_name.crt \u0026lt;Directory /var/www/html\u0026gt; Options Indexes FollowSymLinks MultiViews AllowOverride All Order allow,deny allow from all \u0026lt;/Directory\u0026gt; \u0026lt;/VirtualHost\u0026gt; 加入了RewriteEngin On 這一塊，將Http的使用者導入Https，同樣的每次修改玩Config都必須要重新啟動Apache\n重新啟動完成後，這一切就大功告成啦～\n","date":"2018年4月13日","externalUrl":null,"permalink":"/2018/04/13/%E8%AE%93%E4%BD%A0%E7%9A%84apache%E6%94%AF%E6%8F%B4ssl/","section":"Posts","summary":"","title":"讓你的 apache支援ssl","type":"posts"},{"content":"如果有任何問題有問題可以透過以下表單 聯絡 到我，我會盡快回覆您的問題\n（聯絡表單建置中）\n","date":"2018年4月12日","externalUrl":null,"permalink":"/%E8%81%AF%E7%B5%A1%E6%8E%A5%E6%B4%BD/","section":"Alvin Chen Club","summary":"","title":"聯絡 接洽","type":"page"},{"content":"ubuntu 基礎架站 ,這裡將簡單記錄一下過往多次在Ubuntu 或者linux上架站 所遇到的問題\n安裝好Ubuntu 後\n步驟一 ：安裝LAMP(包含apache mysql php)，基本上照做伺服器就裝起來了 # 參考連結 ：https://magiclen.org/lamp/\nLAMP 代表著Linux Apache Mysql PHP，其實要分開裝也不是不行，如果想省時間省事的話就可以將這幾項一併安裝並且設定好\n先安裝Tasksel\nps. Tasksel有提供LAMP，因此我們先安裝Tasksel，完成後再透過Tasksel來安裝LAMP\nsudo apt-get install tasksel 執行Tasksel來LAMP\nsudo tasksel install lamp-server 重點指令筆記:\napache啟動 停者 重新啟動\nsudo service apache2 start sudo service apache2 stop sudo service apache2 restart 其實到這邊網站伺服器已經安裝完成，此時透過瀏覽器輸入\nhttp:/127.0.0.1/ 應該已經可以看到以下畫面，也就是代表網站伺服器已經順利啟動啦\n安裝phpMyAdmin\n我想有用Mysql的各位應該都對phpMyAdmin不陌生，這也是第一次接觸MySQL常用的管理工具，以往在windows上只要下載了phpmyadmin zip壓縮檔\n解壓縮後放到網站根目錄就可以使用了，其實在Ubuntu 上更為簡單，只要一個指令就可了\nsudo apt-get install phpmyadmin 安裝過程中會要輸入步驟一安裝時所輸入的MySQL帳號密碼，安裝完成後一樣透過瀏覽器輸入網址就可以使用了\nhttp://127.0.0.1/phpmyadmin/ 步驟二 : 設定virtualhost # 注意apache2.4後的設定方式不太一樣，注意不要看到舊版的\n參考連結 ：http://blog.fhps.tp.edu.tw/fhpsmis/?p=845\n參考連結2 :https://www.digitalocean.com/community/tutorials/how-to-set-up-apache-virtual-hosts-on-ubuntu-14-04-lts\nvirtualorhost可以讓Apache在同一台主機的情況下分別架設多個網站，我想這也是非常常用的一項功能\nApache2.4之後必須增加或者修改此資料夾底下的檔案\n/etc/apache2/sites-available/XXXX.conf config範例\n#/etc/apache2/sites-available/site1.conf Listen 8080 \u0026lt;VirtualHost *:8080\u0026gt; ServerAdmin admin@example.com ServerName example.com ServerAlias www.example.com DocumentRoot /var/www/example.com/public_html ErrorLog ${APACHE_LOG_DIR}/error.log CustomLog ${APACHE_LOG_DIR}/access.log combined \u0026lt;Directory /var/www/html\u0026gt; Options Indexes FollowSymLinks MultiViews AllowOverride All Order allow,deny allow from all \u0026lt;/Directory\u0026gt; \u0026lt;/VirtualHost\u0026gt; config寫完後要記得啟用，並且重新啟動Apache\nsudo a2ensite default sudo service apache2 restart 步驟三 ：設定URL rewrite # urlrewrite可以讓網址更有彈性的運用，比如本篇將標題直接設定在網址列上就是其中之一的範例，當然其中設定的細節又有很多故事可說有機會再開一篇來說明\n參考網址 : https://www.digitalocean.com/community/tutorials/how-to-set-up-mod_rewrite-for-apache-on-ubuntu-14-04\n開啟rewrite並重新啟動apache\nsudo a2enmod rewrite sudo service apache2 restart Apache config檔如上述範例需填寫\u0026lt;Directory\u0026gt;\u0026lt;/Directory\u0026gt;\n需要讓這個目錄可以有rewrite的權限\n步驟四 ：如需要使用不同的port記得修改Apache config # 參考網址 ：https://askubuntu.com/questions/224407/how-can-i-open-specific-ports-for-my-apache-server\nListen 80 Listen 8000 基礎建設完成了，緊接著來看下篇讓你的Apache也支援SSL\n","date":"2018年4月12日","externalUrl":null,"permalink":"/2018/04/12/ubuntu-%E5%9F%BA%E7%A4%8E%E6%9E%B6%E7%AB%99/","section":"Posts","summary":"","title":"ubuntu 基礎架站","type":"posts"},{"content":"工程師, 這是我多年來工作的職稱，我是一個相信寫Code可以改變世界的工程師 ，致力於網站與網路服務開發\n近年來在Laravel的世界打滾，Laravel將PHP整體層次拉高了許多，讓開發者專心於自己的商業邏輯開發\n這也是我喜歡Laravel的原因\n網際網路串起了無限可能，現在人人都有可上網的設備，我始終相信透過網既網路的力量可以讓科技為人類的生活帶來很多改變\n因此這幾年持續從事網際網路相關工作，希望可以用自己的力量為這個社會盡一點點微薄之力\n這邊會記錄下工作階段中遇到的問題，無論是Laravel或者是未來遇到的不同framework，我都想盡可能記錄下來\n作為自己成長的一部分\n延伸閱讀 : 個人簡歷 Alvin Chen\n","date":"2018年4月12日","externalUrl":null,"permalink":"/%E9%97%9C%E6%96%BC%E6%9C%AC%E7%AB%99-%E5%B7%A5%E7%A8%8B%E5%B8%AB-%E7%9A%84%E7%A2%8E%E7%A2%8E%E5%BF%B5/","section":"Alvin Chen Club","summary":"","title":"關於本站 - 工程師 的碎碎念","type":"page"},{"content":"","externalUrl":null,"permalink":"/authors/","section":"Authors","summary":"","title":"Authors","type":"authors"},{"content":"","externalUrl":null,"permalink":"/series/","section":"Series","summary":"","title":"Series","type":"series"}]