文章列表

你開始寫 Spring Boot 測試案例了嗎?
科技新知
MacauYeah・2025-11-29

雖然筆者過往做 spring boot framework 教學中,都有滲入一些測試用例。筆者也曾經困惑了很長一段時間,所以就獨立開一個主題,聊一下筆者在實務上對spring boot test 的理解。 測試案例究竟測試什麼? 測試用例 test case 是確保你的程式碼正確性與穩定性的重要步驟,但在 framework 下,並不是所有功能都很容易寫成測試。所以在討論 framework 測試之前,釐清測試的本質。 function input business logic function output 這意味著我們輸入某些資料(input),然後經過業務邏輯(business logic)的處理,最後產生結果輸出(output)。 我們的測試目標,其實就是確保業務邏輯正確。而我們的手段就是經檢查概定的輸入資料,核對輸出結果。 那麼只要我們可以生成輸入資料,就一定可以檢查輸出結果了吧?其實不是的,因為實務上的輸入和輸出沒有這麼簡單。筆者常接觸到的輸入輸出如下 輸入 function 輸入參數 系統狀態資料,例如:資料庫狀態、外部API結果。 輸出 function 輸出參數 寫入系統(影響到)的資料,例如:資料庫狀態、使用外部API時的輸入參數。 總之就是考慮了狀態機 state machine 的問題,每個狀態外部輸入都是一個測試用例,然後核對狀態機去了下一個什麼狀態。 言下之意,我們就是暴力地生成輸入參數和模擬狀態資料,道理上就是可以進行測試。 Spring boot web framework 中,我們又會測試什麼? function input business logic function output在Spring boot web就變成如下 controller request business logic controller response在 Spring Boot test 中,我們可以用模擬的 MVC MockMvc 測試來驗證 controller 的行為。不過,其實進入 controller 前經過很多系統轉換,而這些道理上跟Framework的技術大相關,與業務邏輯小相關。所以為免折磨自己,可以將業務邏輯單獨封裝成服務(service)。之後直接測試服務 ,易寫也易讀。 controller request service input business logic service output controller response道理上 controller 能做的業務邏輯,服務 service 都可以無腦重現。這樣還可以重用服務,減少測試的數量。 如何實現輸入? 直接 new Object。大部份的情況下,因為業務是自己編寫的,應該都可以直接 new 出來。 經 json 檔讀入。如果輸入的參數量太多,逐個經 java new 是很耗時的,我們可以經 json 反序列化變成 Object。但這亦只限於自己可以操作改寫的類。 Mockito 模擬那些無法簡易經 new 或 json 反序列化的 Object。例如:spring security authentication object 我們在使用時,其實只看到 interface。我們難似自己實現一個可以反序列化的類,那麼我們可以使用 Mockito 來模擬這些資料。一些外部API的結果,我們也可以用使 Mockito 來模擬。 什麼情況下不進行測試? 有些情況下,我們可能選擇不對某些功能進行測試,原因可能包括對功能的了解不足或是單純的懶惰。以下是一些例子: 僅進行配置的Function:如果你的 Function 只是在 Framework 中填寫配置,而且你並不太了解它的運作原理,可能就不需要進行測試了。例如,Spring boot web 中,需要大家配置一個SecurityFilterChain Object,它要求大家將 HttpSecurity 轉換為 SecurityFilterChain 。因為輸入的 HttpSecurity 是系統固定的參數,我們亦沒有檢查它的狀態。這種情況下,它的輸入及輸出,其實我們都沒有真正理解。我們硬測試的話,測試功能可能只流於表面。若我們真的要做測試,也是經過MockMvc進行端到端測試(endtoend testing),測試它在事後的影響範圍。 單純的框架功能:例如資料庫的儲存庫介面(repository interface),雖然是在框架下生成的,對於自己手動調整的部份功能,筆者通常亦不會進行單獨測試,通常都會搭配業務邏輯一起進行。它可以使用 Mockito 進行模擬測試,或用測試環境的真實資料庫進行測試。 面對的挑戰 總括來講,筆者盡可能地把測試用例限定在業務邏輯中,就可以大大地降低寫測試的技術難度。但筆者還是有些問題並未完美解決。 測試用例的數量可能很多,因此共用與維護變得相當困難。逐個用例獨立編寫輸入也是很累的。對於 Mockito 的使用,筆者還是可免則免。因為要逐個功能模擬,編寫量就指數提高,這亦難似配合外部變化。一般來說,能優先使用測試環境或者 Docker 來模擬環境的,就盡量用。 離線開發、離線測試。系統依懶的外部功能越多,想做單機開發的難度就越高。即使前述有 Docker 測試,對於持續整合(CI)來講也是有一定難度。那麼這時,Mockito 就是一個可取的選擇。但這又回到編寫量及難以偵測外部變化問題。 希望這篇文章能幫助你更好地理解測試案例的編寫方向,並在Spring boot web開發中加入你自己的測試!

重入膠坑12-快靚正
手機‧電玩
MacauYeah・2025-11-27

筆者今年度,在高達模型制作上,一直針對在分享非噴塗的制作技巧之中。這主要是因為筆塗或素組所需的工具比較單純,而且筆塗使用水性顔料之下,更不會有異味出現,可以在家中安心操作。之前亦提過素組打磨對於對於水口覆蓋的極限問題,亦一度讓筆者認為[純素組打磨]是貼錢買難受,因為時間花出去,但效果不盡人意,還不算直剪不打磨。 經過一段時間的快速組合後(其實是筆者山積太多,所以快速清山積),筆者盡可能跳過那些效果不佳的步驟,保留一些更差異性明顯的工敍,嘗試做出人工花費少,但自己還能接受的作品。 不做取件表-但留意分部制作 雖然做取件表可以讓自己未來可以更專心剪件、規劃。但除非取件表是他人預制好的,或是自己要重複做同一款高達,不然自己弄,是很耗時間的。如果各位制作的只是HG的量,其實分部位直接剪,也很容易做到減少換版的次數,所以取件表可以不做。 不全部刻線-滲線後再執漏 不刻線,直接滲線,效果一定不完美的。但全部刻線,成本高,容錯也低。筆者經過三隻模的測試,似乎不刻線情況下,可以通過拭擦方式,取得7成的水性顔料滲線成功率。剩下的,就乖乖重新刻線再滲線。整體省了時間,不過就是要接受某些線修可以粗幼不等。這樣做的還有另一個大好處,就是減少刻線的出錯。 點綴式的膠貼或水貼 如果大家制作的作品,原本就有送貼紙(例如RG),一定要記得貼。如果沒有,也可參考市場上有沒有第三方貼紙。如果你買的是HG,雖然第三方水貼只是給RG用,但其實你也可以借來用於HG上。肩位,裙夾位,盾位,腳位,也是點綴的好地方。 薄刃剪二刀流 如果想不打磨,二刀流手法是必不可少的。只使用足夠鋒利的剪鉗,直接使用二刀流也可以大大地減少水口發白的情況。第一刀要保留足夠多的流通,避免拉扯。第二刀貼著水口再剪時,就要足夠慢,減少形變而産生的發白。二刀流可以變為多刀流,看大家需要。神之手也就很推薦的,但始終也是消耗品,如果有價錢上的考慮,可以投資低價位的薄刃剪。水口大一點,小一點,差異不太大,只要不發白,薄刃剪的效果就發揮得好了。 UC系列和最新的HGCE系列 不想補色的話,最好考慮元組的系列,因為結構簡單,所以原本分色做得還不錯。而最近的HGCE,分件也越做越多,想偷懶也是可以的。不過就2018之前推出的非元祖HG,大家就真的慎入,補色工作跑不掉。

docker swarm 回到最基礎的群集組建
科技新知
MacauYeah・2025-11-21

雖然筆者都知道,全世界在講 k8s ,全世界都叫筆者放棄 docker swarm,但無獨有偶,docker swarm 還是有使用的價值。 你只有單個服務在運行,只想要做冗餘或分流。快速地用 docker swarm 做最小可行性産品,推出市場。 傳統的HA功能做到了,但你沒有中央匯整日誌的功能。而你也不想把事情攪得太複雜,使用docker swarm 可以讓你在任何一個管理節點上查看不同 container 的日誌。 你的客戶只提供VM,他可能有自己的k8s平台,但不讓你使用。自建一套docker swarm ,先入場,事後擴展再要求客戶提供k8s,對於客戶來講,先證明系統是有價值的,在金錢成本上或能力上,一定是件比較可以接受的事。 筆者之前介紹過一系列的 docker swarm 教學,但生成群集的部份一直沒有做介紹。因為實在太簡單,所以一直都沒有收納在教學內容當中。但現在考慮其完整性,以及為了讓大家感受一下它有多簡單,所以重新寫了組建群集的步驟。 組成群集 以前各家不同的軟件,想要起一個群集,要左攪右攪,又要重啟。而docker swarm真的很簡單,只要各機中有 docker ,再在各機中順序打指令就好。 node 1 使用docker swarm init docker swarm jointoken manager # node 1 gt; docker swarm init gt; docker swarm jointoken manager To add a manager to this swarm, run the following command docker swarm join token SWMTKN1xxxxxxxxxxxxxxxxxxxxxxx xxx.xxx.xxx.xxx2377 其餘的管理員節點就根據上述的提示,使用 docker swarm join token SWMTKN1xxxxxxxxxxxxxxxxxxxxxxx xxx.xxx.xxx.xxx2377 就好。只要總數的管理員節點有奇數個就可以了(包括當初的node 1)。即是1、3、5等都可以。這是因為在容錯的情況下,必需由管理節點作出多數決,才能容易地知道判斷是哪些節點出現問題。 如果不為容錯,只想增加可工作的機器,那麼我們只需要增加工作節點。我們可以在任何管理員節點生成docker swarm jointoken worker gt; docker swarm jointoken worker To add a worker to this swarm, run the following command docker swarm join token SWMTKN1yyyyyyyyyyyyyyyyyyyyyyy yyy.yyy.yyy.yyy2377 若想要檢查各個節點的工作狀態,在管理員節點上執行 docker node ls 看到了。 docker node ls ID HOSTNAME STATUS AVAILABILITY MANAGER STATUS ENGINE VERSION xxxxxxxxxxxxxxxxxxxxxxxxx node1hostname Ready Active Leader 28.5.1 yyyyyyyyyyyyyyyyyyyyyyyyy node2hostname Ready Active Reachable 28.5.1 全部教學請見 httpsmacauyeah.github.ioAProgrammerPreparesVMDockerNotesSwarmModeCommandCN.html

codeserver 在團隊間開箱即用就是最大的好處
科技新知
MacauYeah・2025-11-20

之前我們就有探討過 vs code 與 codeserver 的差別,初步結論就是 vs code 的 debug 功能比較完善。如果大家懂得 devcontainer 的使用形式,使用 vs code 應該可以得到最大的效益。就在筆者想跳過 codeserver 的時候,又有新朋友對 codeserver 有興趣。最主要的原因還是它可以一體化預安裝所有事,若大家使用筆者的image,有 docker 、有瀏覽器就已經可以開箱即用。 所以這裏,筆者也重新翻新了筆者版本的使用說明。有興趣使用的朋友可以直接跟 github readme 試用。 httpsgithub.comwingzero0codeserverUbuntu 本次翻新,主要加入了常見問題。這些問題部份與 docker 的基本限制有關、部份則是筆者的 env 所限。 常見問題 FAQ 運行 node 應用時很慢 在 windows mac 下,它們的 docker 是經過 VM 建出來的。若使用 bind mount ,其實是經過 VM 層面抄資料夾。普通 java 開發沒有大問題,但如果遇上 node_module ,就會出現極大效能問題。 node_module 最好還是放在 container 內的 mounted volume 中。本 project 預設的 dockercompose.yaml 就已經有 homeubuntusourcecode mounted volume ,有需要可以放在其內直接使用。 linux 則沒有這個問題,因為 docker 只是 linux 的一個 process ,可以直接連到資料夾。 mounted volume 權限問題 如果大家自定義 mounted volume ,注意 docker 預設會是 root 權限,本系統使用 local user ubuntu,有需要改為它。 chown R 'ubuntuubuntu' YOUR_TARGE_FOLDER 若然codeserver異常,需要重啟。在 host 可以使用 docker command,在 container 中,可能殺掉所有 process # at host, outside of codeserver docker compose f dockercompose.local.yaml stop docker compose f dockercompose.local.yaml start # at container, inside of codeserver killall5 9 上下載 上載檔案:可以經過拖拉的方式,把桌面的檔案拖進 codeserver 的 Explorer 區域。 下載檔案:可以點選 codeserver Explorer區域內的檔案,按滑鼠右鍵,選 Download 。

為何 VueJS 除錯如此麻煩?
科技新知
MacauYeah・2025-11-04

前一次,筆者分享了VS code debugging frontend的好功能,也確實了coding anywhere並不是一個普通的notepad language server就可以解決的事。我們還要考慮如何debugging 除錯)的問題。 雖然筆者知道 vscode 可以解決問題,但為何 最原始的 nodejs debugger 不能解決問題。如果node debugger 不能解決問題,那麼 vscode 又做了什麼,它可以解決問題?經過一輪的實驗,筆者懷心疑,也許,強大的並不是 vscode 本身,而瀏覽器才真正的做到 debugger 的功能。而 vscode 只是以更方便的方式,重現那些結果。 為何 backend 的 debugger 不發揮作用? 筆者舉例,現時有一個 vue 3 專案,使用官方建議的方式生成 $ npm create vue@latest 這個專案,在開發模式下,會以 vite 架起一個端口為 5173 的伺服器,讓開發人員可以經過瀏覽器看到vue內容。筆者一直都認為,只要在 vite 的指令中插入 inspect 參數,一切就可以成功,就像 nodejs 一樣,只要在開始時加入參數就可以。結果當然是不行的。 經過對比 VueDevTools 的參考功能,筆者發現了一個出發點的問題。vite 其實是一個伺服器級的程式,也許它只是負責把所以 vue js 動態轉成常見 js,就像 webpack 一樣。我們想要設的中斷點,都不在它的程式上,所以 debug 參數也沒有用。實質,我們要加的中斷點,其實要在客戶端上,也就是瀏覽器上。那因此,VueDevTools 也不包括那些功能。它只是好好地記錄了每個 vue component 或 js 是如何被改寫的過程(就像被 webpack改寫的過程)。 官方又是用什麼來除錯的? 既然我們知道了問題所在,就要看看傳統的 javascript 又是如何除錯的。實際上,因為瀏覽器的配合,設立中斷點的功能,原來早就實現了。 httpsdeveloper.mozilla.orgenUSdocsWebJavaScriptReferenceStatementsdebugger 只要我們在任何 javascript 地方,插入 ldquo;debugger;rdquo; 這個神奇的字,瀏覽器就會在inspect模式下,自動產生中斷點。之後,你可以控制瀏覽器進行watch step into step over 功能。絕對比console.log更有意義。 在發現了這個方法之後,回去找vue3的官方文件,驚訝地發現,它就是提議用這種方式進行除錯。 httpsvuejs.orgguideextrasreactivityindepth.html#reactivitydebugging 未解之謎 雖然我們找到了設定中斷點的方式,但對於vscode是如何做到客戶端、伺服器端通用這件事,筆者還是沒有了解到。就以現在的知訊來看,很大機會就是vscode操控了瀏覽器的除錯模式,把所有資訊都回傳了vscode本身。這也是解譯了為何vscode在起動debugger時,必需要由vscode自己叫起瀏覽器。而codeserver這類雲IDE無法叫起本地瀏覽器,就造成它無法運用除錯功能的原因。 有與趣為codeserver一起搵解決方案的朋友,可以到筆者的 httpsgithub.commacauyeahAProgrammerPrepares ,以文字教學的方提交你的解決方案。 祝願大家可以早日實現coding 自由。

Spring boot web api 異常處理
科技新知
MacauYeah・2025-10-28

我們在編寫程式時,經常會遇到一些極端的情況,不會經過 function 的方式回傳結果。例如一個 function 原本是提供讀檔功能,但用戶傳入的並不是一個有效的檔案路徑,又或是誰路徑權限不足,無法讀取。這些不正常的結果,並不是原本 function 所協定的回傳值。那麼,我們會拋出異常 Exception ,中斷所有被呼叫中的 function ,讓上層用戶去考慮怎樣處理這個問題。 在 Web API 中,這些 Exception 就更常見。要求用戶傳入的參數,用戶就是有時候少了幾個。覆寫資料的時候,原本的資料已被刪除。但我們現在是經過 Web Api,不能像過去一直向上拋出異常就能通知用戶。我們需要的,是把異常轉成對應的 Http Status Code,讓用戶端可以快速識別異常的類型。 java 異常對應 Http Response Code 其實在 spring boot web 中,要做轉譯,是很簡單的。在定義 java Exception的時候,若有@ResponseStatus,spring boot web 就會自動回應對應的 http error code。 @ResponseStatusHttpStatus.FORBIDDEN public class CustomAuthenticationException extends RuntimeException public CustomAuthenticationException public CustomAuthenticationExceptionString message supermessage; 以後,任何一個地方拋出 CustomAuthenticationException (假設上層沒有人攔截)都會把該 Controller 的結果改為 http 403。Spring boot 也很聰明的,把異常中的 message 隱藏 ,免得有網安的問題。 若我們定義 Exception 時,沒有@ResponseStatus,Controller 就會變成 http 500,例如我在 controller 中拋個常見的 IOException,這次的結果就會變成 http 500。 @GetMappingquot;apiioErrorquot; public String forceIOException throws IOException throw new IOExceptionquot;force io errorquot;; 如果某些時候,我們想使用 java Exception 中的 message 欄位作為報錯信息,讓 http 客戶端,可以通過固定的 message 檔位找到問題訊息,我們可以在application.properties中,加入server.error.includemessage=always。有些特殊情況,在開發模式時 mvn springbootrun ,已經可以見到有 Exception message,但在投産後java jar又看不到。主要因為開發模式中, pom 有 optional springbootdevtools,會自動加入了server.error.includemessage=always,但 mvn package 後就沒有,因為 runtime 沒有 springbootdevtools 的覆蓋。 額外處理 異常處理除了想控制 http status code 外,有時還需要做一些額外處理,例如發出通知郵件等。若想做額外處理,需要另做一個 @RestControllerAdvice 的類,在接到指定的 exception 時,可以轉換不同的 http code ,而且還可以執行額外 java code ,改變 http ResponseBody 。 @RestControllerAdvice public class GlobalExceptionHandler @ExceptionHandlervalue = RuntimeException.class @ResponseStatusHttpStatus.INTERNAL_SERVER_ERROR public Map handleRuntimeExceptionException ex return Map.ofquot;retquot;, false, quot;anyfieldsquot;, ex.getMessage; 但要注意,一旦使用@RestControllerAdvice 後,就要考慮有沒有改變了某些預設的行為。例如上述的@ExceptionHandlervalue = RuntimeException.class,代表所有RuntimException.class的子類,都會歸由該 function 所處理。當然,你也可以多加幾個 function 來處理不同的子類。 Reference springbootwebapivalidate

Visual Studio Code 才是 coding anywhere的基礎?
科技新知
MacauYeah・2025-10-25

筆者過去就有發表過使用 VM docker code server 作為 coding anywhere的基礎, 現時也有一直使用。code server 有效,但對於Web App 開發,仍有所不足。 那個藏在瀏覽器的IDE Code Server 使用 code server 的好處,就是筆者只需要一個有瀏覽器的客戶端,就可以連線到雲上的VM中使用 code server 。不論多重的功夫,交給外部的雲去做,自己的客戶端就可以盡可能輕便。不想自己攪一套code server開發環境?github codespaces in browser 也是一個很類似的替代器。它也是隨時經雲建立一台專用的 VM,之後就可以經瀏覽器進行開發。 一切看來都很好,所有東西都可以在 VM docker 中進行。如果你的 VM docker,可以有齊所有除錯工具,應該就真萬能了。現實就是不太美好,因為雲上的 VM ,docker 中的容器,主要都是沒圖形介面的。如果你想要利用的除錯工具,例如 chrome,你就未必可以順利在 headless VM docker conatiner 中安裝了。很多除錯工具,要麼就需要圖形介面,要麼就要有條件連到本地硬碟,所以筆者就 code server 本身,真的沒有太多解法。 Web App 開發,回到原始的基本步 Visual Studio Code 回到原始的基本步,本地Visual Studio Code VM docker ,就好好地可以利用本地的 chrome 等進行 NodeJs 的除錯。它就跟本地Visual Studio Code 本地開發類似,本地能用的 chrome,可以經過 vscode 連到 VM docker 內,只要Remote Development 插件就可以了。筆者測試過,真的很簡單,vscode連線後,會在你的VM docker 內,安裝一個很細的 client。然後其他事就像本地開發一樣了。Remote Development 除了用自己的VM外,官方還稱它可以連上github codespaces。筆者就未有詳細測試,有興趣的朋友可以建立一個codespaces看看。 雖然 Visual Studio Code 並沒有保證完整地解決所有問題,但至少它提供了一個橋樑可以作為接口開發。coding anywhere 還是有條件實現,只是我們的客戶端並不如一開始的單純,只少要有一個完整的桌面電腦環境OS ,可以做到 port forward,做一些簡單的對接。只是單純的移動端 Web 界面,就未能夠做到那些複雜的跨機轉譯。

比 Java Mail 更簡單的 Spring boot email
科技新知
MacauYeah・2025-10-24

使用 Spring boot 對接 SMTP gateway 發 email ,相對是容易的。 基本上,它就是會使用自建的 org.springframework.mail.javamail. , 對接 javax.mail. jakarta.mail. 以前的所有設定值 ,都可以經 spring.mail.properties. 傳入 例如 spring.mail.properties.mail.smtp.auth=true spring.mail.properties.mail.smtp.ssl.enable=true spring.mail.properties.mail.smtp.socketFactory.port=465 就等於過去 java.util.Properties props = new java.util.Properties; props.putquot;mail.smtp.authquot;, quot;truequot;; props.putquot;mail.smtp.ssl.enablequot;, quot;truequot;; props.putquot;mail.smtp.socketFactory.portquot;, quot;465quot;; 一個最簡單可以連去 google smtp 的簡易 code 如下 ### application.properties spring.mail.host=smtp.gmail.com spring.mail.port=587 spring.mail.username= spring.mail.password= spring.mail.properties.mail.smtp.auth=true spring.mail.properties.mail.smtp.starttls.enable=true SpringBootEmailApplicationTests.java @SpringBootTest class SpringBootEmailApplicationTests @Autowired private JavaMailSender javaMailSender; @Valuequot;$spring.mail.usernamequot; private String fromAddress; private static final Logger LOG = LoggerFactory.getLoggerSpringBootEmailApplicationTests.class; @Test void contextLoads try SimpleMailMessage mailMessage = new SimpleMailMessage; mailMessage.setFromfromAddress; mailMessage.setToquot;XXXXXXXXquot;; mailMessage.setTextquot;this is backend email trigger for spring bootquot;; mailMessage.setSubjectquot;spring boot test mailquot;; javaMailSender.sendmailMessage; catch Exception e LOG.errorquot;Error while Sending Mailquot;; throw new RuntimeExceptione; github 原始碼 httpsgithub.commacauyeahspringbootdemotreemainspringboottutorialspringbootemail

Spring Web 異步 Api
科技新知
MacauYeah・2025-10-18

在設計網頁應用時,總會有某些功能,是特別消耗時間的,例如我們的應用要提供報表,或長時間搜索。如果,我們的 Web Api 的連結,要強制客戶端等待結果,那麼中途斷線需要重做的機會就變得很高,客戶端的體驗一定不太好。 面對這些情況,我們最好就把原本一個 API 功能分為三個 API 去做。 工作生成 API 查詢狀態 API 查詢結果 API 如果大家有信心,可以把2和3混合在一起,對於客戶端,也是一件好事。不過,2,3 因為回傳的結構可能不一樣,分開處理,程式碼會更易讀。 以下,筆者就以一個模擬報表生成的應用,去解釋如何設計可以即時回傳的 API。 source code springbootwebapiasync ReportController.java 詳細解析 假設我們有一個 ReportController,它負責處理與報告生成相關的 HTTP 請求,它提供三個核心 API 端點。 啟動報告生成端點 @PostMappingquot;reportJobcreatequot; public ResponseEntity createJob String uuid = String.formatquot;%d_%squot;, new Date.getTime, UUID.randomUUID.toString; CompletableFuture.runAsync gt; try orderStatus.putuuid, PROCESSING; Thread.sleep10000; 10second simulated delay reportService.genAndSaveReportuuid; orderStatus.putuuid, COMPLETED; catch InterruptedException e Thread.currentThread.interrupt; ; return ResponseEntity .accepted .headerHttpHeaders.LOCATION, quot;reportJobstatusquot; uuid .bodyMap.ofquot;uuidquot;, uuid, quot;status apiquot;, quot;apireportJobstatusquot; uuid, quot;download apiquot;, quot;apireportJobdownloadquot; uuid; 運作原理: 立即生成唯一的 uuid 來標識這次任務 在 CompletableFuture.runAsync 運行長時間的操作。 API 本身即時回傳了 HTTP 202 Accepted 狀態,告訴客戶端請求已被接受但尚未完成 在回傳的結果中,還有提示可以查詢狀態和查詢結果的API。 這種設計避免了 HTTP Gateway Timeout,因為回應是即時的 。 檢查進度端點 @GetMappingquot;reportJobstatusuuidquot; public ResponseEntity getStatus@PathVariablequot;uuidquot; String uuid String status = orderStatus.getuuid; if status == null return ResponseEntity.notFound.build; if COMPLETED.equalsstatus return ResponseEntity.statusHttpStatus.SEE_OTHER return ResponseEntity.ok .headerHttpHeaders.LOCATION, quot;apireportJobdownloadquot; uuid .bodyMap.ofquot;statusquot;, COMPLETED; return ResponseEntity.statusHttpStatus.ACCEPTED .bodyMap.ofquot;statusquot;, PROCESSING; 單純以 map orderStatus.getuuid 查看狀態結果。這個map 必需是多線程下使用還是安全的 ConcurrentHashMap。 下載結果端點 @GetMappingquot;reportJobdownloaduuidquot; public ResponseEntity download@PathVariablequot;uuidquot; String uuid String status = orderStatus.getuuid; if status == null COMPLETED.equalsstatus return ResponseEntity.notFound.build; else 下載檔案 如果大家並不計較是否需要重做失敗的請求,這個例子已經可以簡單地達到即時異步回傳的效果。如果大家還需求考慮請求是否有效完成,就需要用到 message queue 或其他 job server ,這就不是同一個網頁應用的操作範圍。 Reference source code springbootwebapiasync Building a LongRunning Async REST API in Spring Boot with 202 303 Status Codes

高達模型,不噴塗還有什麼選擇?
手機‧電玩
MacauYeah・2025-10-09

之前筆者就高達模型中,籠統地比較不同的1144比例產品。現在筆者也正式入手更多不同的系列,看看有沒有哪些適合不同需求的玩家。 SD系列:Mobile Join Gundam 明盒盒蛋,拼裝模型,但不需要剪鉗也可以隨手取件。要補色、滲線或進行一步加工制作。優點是可動性高,官方有提供補色貼紙,但距離足夠分色,還是有一段距離。 筆者並不在意它的分色不完美,以這個不足百元的商品來講,可動性足夠讓筆者快樂一個下午。 SD系列:FW Gundam Converge 明盒盒蛋,有少量件需要拼裝,大部份都已經有預塗裝。因為制作比較精緻,人氣商品比MJG會再貴一點。但可動度就很低,幾乎只有手臂、手碗、頭的平轉,腿腰不可動。筆者購入這個系列的原因,主要是當時已經無力再自行塗裝,把它當完成品直接買回來當擺設,也是一番享受。 兩者二選一的話,筆者更偏好MJG,因為有可動性,強行把玩也勉強可玩,一起擺場景也更耐玩。而FW的話,它的優勢反而是選擇多,方便整個系列收藏。因為有塗裝,而且有少部份可動,想拍照也不是不可以,耐玩度不高就是了。 FW就筆者跟朋友交流,在另一個系列MSE的出現下,FW似乎不太受樂。不過筆者未入手過MSE,難以作比較。但外觀上,似是MJG的另一個版本,但沒有骨架。

小比例高達的選擇: HG vs RG vs R魂 (MR 魂)
手機‧電玩
MacauYeah・2025-09-29

筆者過往一直就關注高達HG 模型的素組制作,以及把玩相關資訊。而眾所週知,HG的精緻度及可動性,其實都不怎麼,想要美觀,必需要花很多心思去制作才行,想要高可動,也要一定的改制能力。 就以不噴塗的前題下,1144比例下,除了HG外,我們還有的選項,就是 RG , Robot 魂 和 Metal Robot 魂。而筆者這兩三年,都陸逐有入一些近期的貨,可以分享一下對比。我們就先各個系列對比 HG 作一些分享,最後再分情境做個綜合評價。 RG vs HG 只要你有薄刃剪 滲線 貼紙,你就會得到一台可以吊打HG的高細節高達模型。貼紙是套件內附送的,只要把貼紙好好貼完,什麼水口阿,都可以無視。這是最最最大的優勢。 缺點就是你的選擇不多,而且高達套件都有軟骨問題。不把玩,全部罰企,是可以接受的。高強度打把,最好選擇RG25號以後的作品,把玩時也要格外小心,因為真的易壞。 Robot 魂 vs HG 課金取得官方代工,買回來就可以即時把玩。最美好的部份是可動性一般比同期HG高(不必然)。手型、特效件也比HG 多。筆者認為開箱即玩擺POSE,是它的最大價值,所買回來一定要開箱把玩、拍照,不然發揮不出它的性價比。 雖然R魂可以視為官方代工,但這個代工的細節並不多,並不包括滲線和貼紙,所以你想拍一些近鏡的大頭相,還是太過素。有需要還是可以自行刻線或另購水貼。 Metal Robot 魂 vs HG MR魂,其實就是R魂的升級版,部份關節或內構,以合成金屬制作,強度會再早高一級。大家可以視為課金取得更高級的代工,外表雖然無淺線刻線,但塗裝移制技術,已足夠表出現細節等。拿在手上,就有一種珍品的感覺。 MR魂好像有錢就什麼都解決了一樣,但其實不是。它的可動性,其實不太特別突出,而且會有掉件問題。筆者入手的兩款通販MR魂,都有群甲容易掉落的問題。所以錢只是花了在塗裝和用料上,把玩體驗可能不比R魂強(特效件少很多) 綜合情境 何時選擇HG 款色限定,因為HG的款色夠多,其他魂系並一定有推出。HG可能是某些機體的唯一選擇。想爽玩,想好看,一定要有些動手能力。 何時選擇RG 如果你覺得帥就完事,但又不想花太多錢和時間,那麼RG就是你的好朋友。只要細心對著說明書拼裝,貼上貼紙,就不用再花心思了。 何時選擇R魂 對於把現有要求的朋友,可以選這個系列。有空就把來扭一扭,做個情境,是何其狀觀的一件事。 何時選擇MR魂 不想動手拼裝,又想帥,但把玩時間都沒有的朋友,可以選這個。開箱上支架,選一個固定的姿勢,長放飾櫃,那是最簡單也最優美的一件事。

git submodule 的那些坑
科技新知
MacauYeah・2025-09-26

submodule 設定 有些時候,我們並不想追蹤submodule的預設分支。對於初次新增時,我們可以 git submodule add b YOUR_BRANCH REPO_URL_OR_RELATIVE_REPO_PATH git submodule add b featuredevcontainer httpsgithub.commacauyeahspringbootmultipledatasource.git git submodule add b featuredevcontainer ..springbootmultipledatasource 若在初始化後期,想改branch,可以直接修改設定檔。首次做,還是建議使用指令方式加入,因為第一次總要把submodule整個歷史記錄取下來。 # file .gitmodules submodule quot;springbootmultipledatasourcequot; path = springbootmultipledatasource url = httpsgithub.commacauyeahspringbootmultipledatasource.git branch = YOUR_BRANCH 關於上述 url 的部份,如果是公開的倉庫,當然可以以完整的方式存取。例如你可以直寫 url = httpsgithub.commacauyeahspringbootmultipledatasource.git。 若為私有倉庫,道理上要本機有權限存取才行,對於持續整合持續部署就有些麻煩。正常解決方向就是 CI Server 有齊所有倉庫的存取權限,具體要根據不同 CI Server 的設定,有時候還要跨 Docker 的方式去接入。那是有夠麻煩的一件事。但若果 main module 與 sub module 剛好為同一個倉,我們也可以使用相對路勁來解決。 # file .gitmodules submodule quot;springbootmultipledatasourcequot; path = springbootmultipledatasource url = ..springbootmultipledatasource.git branch = YOUR_BRANCH 但這是有代價的,我們在本地 checkout 時,也必需要模疑類似的文件夾架構,也就是 sub module 也要獨立 checkout 。

Docker Swarm - Private Registry 私有影像倉庫
科技新知
MacauYeah・2025-09-10

在構建投産環境時,如果 server 群沒有互聯網,又或對私隱很有要求,需要自建一個最簡單的 registry ,可以用這個。當然,那台機第一次必需經互聯網。架起後就可以斷網,並由其他 client 提送新的 registry image更新。 Registry Server 起動方式 最簡單的起動方式,但什麼都不設定。 docker run d p 50005000 name registry registry3 若想要加入 SSL,讓你的 client 不會認為它是不安全的 registry ,最簡易可以寫成 docker compose, 由 docker compose up d 執行。 # dockercompose.yml registry restart always image registry3 ports 50005000 environment REGISTRY_HTTP_TLS_CERTIFICATE certsdomain.crt REGISTRY_HTTP_TLS_KEY certsdomain.key volumes pathdatavarlibregistry pathcertscerts 上述的 environment 中,有條件的話,還請設定需要登入才能訪問限制。最簡單,可以使用 apache http header 驗證方式。 # dockercompose.yml registry restart always image registry3 ports 50005000 environment REGISTRY_HTTP_TLS_CERTIFICATE certsdomain.crt REGISTRY_HTTP_TLS_KEY certsdomain.key REGISTRY_AUTH htpasswd REGISTRY_AUTH_HTPASSWD_PATH authhtpasswd REGISTRY_AUTH_HTPASSWD_REALM Registry Realm volumes pathdatavarlibregistry pathcertscerts pathauthauth REGISTRY_AUTH, REGISTRY_AUTH_HTPASSWD_PATH, REGISTRY_AUTH_HTPASSWD_REALM 的值照抄就好,然後pathauthhtpasswd 就需要以 htpasswd 的格式提供內容 apache password_encryptions。即是以下那個樣子 USERNAME_1BCRYPT_HASH_1 USERNAME_2BCRYPT_HASH_2 USERNAME_3BCRYPT_HASH_3 Client 連線方式 一切都設定好後,在 client 端,就可以登入並推送你的 image,題外話,cli登入的都是以明文的方式存在電腦中,所以不要隨便在公開的地方存入自己的帳號 # login docker login YOUR_DOMAIN5000 # try reupload image docker image tag registry3 YOUR_DOMAIN5000registry3 docker image push YOUR_DOMAIN5000registry3 如果 server 端沒有提供SSL,那麼 client 就只能設定 http 的不安全連線。 httpsdistribution.github.iodistributionaboutinsecure 修改 client 端的 etcdockerdaemon.json Windows Docker Desktop請經 Gui修改,然後重啟 client 端的 docker quot;insecureregistriesquot; quot;YOUR_DOMAIN5000quot; Registry Server 維護 Garbage collection 垃圾回收 當我們設立了自己的 Registry 倉庫之後,少不免就是要維護硬碟的用量。很多過期的 Image ,沒有需要,那就手動刪除,然後進行 Garbage collection 垃圾回收。另一種情況,就如前述教學中,大家使用統一版本號,例如 latest ,表面上看似只有一個 tag ,但其實底下可能已經藏有多個不同的版本,也需要經過Garbage collection來清理空間。 因為回收過程比較危險,所以官方並不建議自動做,以下就簡單講講為了做刪除和回收,設定檔要怎樣改。為方便改設定,我們更新 docker compose yaml 檔,把 server config 都帶到 container 外面。 registry restart always image registry3 ports 50005000 environment REGISTRY_HTTP_TLS_CERTIFICATE certsdomain.crt REGISTRY_HTTP_TLS_KEY certsdomain.key REGISTRY_AUTH htpasswd REGISTRY_AUTH_HTPASSWD_PATH authhtpasswd REGISTRY_AUTH_HTPASSWD_REALM Registry Realm volumes pathdatavarlibregistry pathcertscerts pathauthauth pathconfig.ymletcdistributionconfig.yml config.yml 就如下所示,為了提供 API 刪除 image 的可能,storage.delete.enbled 要為 true,又為著之後進行回收時,可以避免有人於回收中途上載,所以預先加入 storage.maintenance.readonly.enabled 的控制項。回收之前要把readonly改為true,回收後再調為false。 每次修改完,記得重啟一下 docker service 。 storage filesystem rootdirectory varlibregistry delete enabled true maintenance readonly enabled false Garbage collection 指令 # inside container # binregistry garbagecollect dryrun deleteuntagged quiet pathtoconfig.yml binregistry garbagecollect deleteuntagged=true etcdockerregistryconfig.yml # outside container, at host level docker exec it YOUR_CONATINER_NAME binregistry garbagecollect deleteuntagged=true etcdockerregistryconfig.yml