潮流特區

焦點文章

CyberCom3合1充電數據線 — 一條線搞掂晒,充電超方便!
科技新知
Lifemagtechie・2025-06-12

而家大家手上嘅電子產品多到數唔清,手機、平板、藍牙耳機、遊戲機hellip;hellip;每部機都有自己嘅充電線,仲有啲好舊款用USB,iPhone用Lightning,最新又係TypeC,搞到充電時成日手忙腳亂,尤其去旅行,帶幾多條線都唔夠! 呢條 CyberCom3合1充電數據線,就係為咗解決呢個煩惱而生。佢集合多種接口於一身,無論你係用緊iPhone定Android,甚至其他電子裝置,都可以用一條線搞掂晒,方便又慳位,真係你嘅生活充電好幫手! 一條線,多種接口,無懼設備多 CyberCom3合1充電數據線支援 Micro USB、USB A、Type C 同 Lightning 四大接口,無論你係iPhone、Samsung定係其他品牌,甚至係藍牙耳機、遊戲機,都可以用呢條線充電同傳輸數據。旅行唔使再帶一大堆線,行李又有位可以擺多啲戰利品啦! 快充快傳,效率up up! bull; 支援QC快充技術,最高60W輸出,手機、平板、智能手錶都可以超快充滿,唔使等成日。 bull; 傳輸速度高達480Mbps,無論係工作文件定係娛樂影片,一link就傳,節省你寶貴時間。
 耐用又防纏繞,攜帶超方便 bull; 採用彈性TPE物料,線身唔易打結,收納方便,唔怕亂晒。 bull; 線材耐用又有彈性,日常用或者出街旅行都好啱用。
 產品小百科 bull; 1米長度,無論係屋企、公司定旅行都啱用。 bull; 建議零售價 $99,依家優惠價$79就可以帶走。
 由此開始,充電更快更簡單! 設計貼心,性能強勁,係你工作同生活嘅好拍擋。無論係手機、平板、遊戲機定藍牙耳機,呢條線都幫你搞掂晒充電同數據傳輸,令你嘅數碼生活更輕鬆自在。了解CyberCom3合1充電數據線嘅詳情同優惠,幫你打造更智能、更方便嘅充電新體驗!購買網站 www.cyberportcom.com查詢電郵 info@cyberportcom.com

最新文章

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

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

升級 Spring Boot WebClient SSL (Reactor Netty 1.2.6):重新配置 SSL 設定
科技新知
MacauYeah・2025-08-27

因為SSL provider 更新了的關係,好多 HttpClient WebClient 設定SSL的部份都要重寫以免出現 deprecated 問題 reactor.netty.http.client.HttpClient 在 1.0.x, 中可以這樣自行設定SSL逾時的部份,但當中的spec.sslContext.defaultConfiguration 在新版本,例如1.1.x後就會出現 deprecated。 deprecated version HttpClient.create .securespec gt; spec.sslContextSslContextBuilder.forClient .defaultConfigurationSslProvider.DefaultConfigurationType.TCP .handshakeTimeoutDuration.ofSeconds30 .closeNotifyFlushTimeoutDuration.ofSeconds10 .closeNotifyReadTimeoutDuration.ofSeconds10; 觀看各大網站,都未有更新,唯有自行研究官方說明。 筆者撰寫本文的時候,netty 發行版本為 1.2.6, 1.3.0 還里程碑M6的階段。所有參考皆來自1.2.6版本,實際上我們要使用新的後綴為ContextSpec類,看Class名應該有分http 1.1, 2, 3的版本,筆者就試用最基本的http 1.1。Http11SslContextSpec, 有條件的朋友可以試用Http2SslContextSpec, Http3SslContextSpec import reactor.netty.http.Http11SslContextSpec; import reactor.netty.http.client.HttpClient; import java.time.Duration; import org.springframework.web.reactive.function.client.WebClient; import org.springframework.http.client.reactive.ReactorClientHttpConnector; ... Http11SslContextSpec http11SslContextSpec = Http11SslContextSpec.forClient; HttpClient httpClient = HttpClient.create .securespec gt; spec.sslContexthttp11SslContextSpec .handshakeTimeoutDuration.ofSeconds30 .closeNotifyFlushTimeoutDuration.ofSeconds10 .closeNotifyReadTimeoutDuration.ofSeconds10; WebClient webClient = WebClient.builder.clientConnectornew ReactorClientHttpConnectorhttpClient .build; ... 雖然這個寫法來看netty 1.2.6,但似乎1.1.x 通用。大家有需要可以交互測試一下。 Reference netty 1.2.6 httpclienttimeout 的設定 netty 1.1.30 timeoutconfiguration 的設定 netty 1.2.6 java api doc netty release version 更多筆者的程式開發分享,見請 github

Spring Boot Web App 更新期間的維護模式:從唯讀到全鎖的解決方案
科技新知
MacauYeah・2025-08-25

在營運 Web App 的時候,雖然我們有 Docker K8s 可以滾動更新,但難保用戶在更新的過程中,有一半訪問去到了舊版,另一半去了新版。如果可以,Web App 本身自帶維護模式,可以自我判斷什麼時候應該忽略新的訪問,當然最好。但要做到這一點,前期需要很多規劃。狠心一點,可以直接關掉對外的服務,讓用戶無法訪問。 但在另一些情況下,例如升級搬遷的情況,下線時間比較長,完全關掉服務並不是一個很好的方向,我們至少還可以提供唯讀的選擇。而且這個可以從資料庫出發,讓 Web App 少處理一點邏輯。 如果 Web App 背後的資料庫是 MSSQL 或 MySQL,唯讀這件事應該是簡單的,只要你把 service account 的權限改變就好。但如果你用Oracle,就要想想辦法。 筆者想到的方法,暫時有兩個。第一個就需要大家寫寫 Script ,一口氣把所有 Table 給鎖起來。例如 第二個,就是生成一個新的唯讀 User schema,給他所有Select的權限。然後更新 Web App 使用那個唯讀 User schema存取資料。 兩個方法有什麼差異呢? 前者就全部鎖起來,沒有任何一個資料庫用戶可以改寫資料。如果你的業務沒有差異性,全部一起封起來就完事。但如果你只想 Web App 轉成唯讀,但其他背景程式還可以執行更新。那你就只能用後者了。但後著也不是百分百的完全無痛,至少你 Web App 要支援登入與操作的 Schema分離。 例如用Spring boot JPA的話,可以在 application.properties 可以讓登入及操作的Schema不一樣。 spring.datasource.username=READ_ONLY_USER spring.jpa.properties.hibernate.default_schema=ORIGINAL_SCHEMA 又或者在 java 層面指定。 @Tableschema = quot;ORIGINAL_SCHEMAquot; 這看上去,是很有彈性的。但其實也是有些局限。如果你本來的JPA有寫特制的 JPQL 或 Raw Query,又或者你在Java層面加了 @Subselect,由於這些都是程式原作者所 hard code 的,JPA沒法幫你改寫。改來改去,可能還是前述寫Script的方法,一口氣把所有 Table 給鎖起來實際一些。 Reference 更多筆者的程式開發分享,見請 github

Galera 4 (Mariadb cluster) 的冷開機
科技新知
MacauYeah・2025-08-20

前次我們介紹了 Galera 4 在Ubuntu 24的架設方式,這次我們來補充一個最常見的問題Cold Start 冷開機 cold start 平常, Cluster 中只有其中一個 node 需要更新重啟,基本上所有節點回覆正常後,都可以互相通訊。而有些情況,例如斷電問題,需要所有節點全數關機,那麼 Galera cluster 就需要一定的方式重啟系統。那是一些狀態的保護機制,因為在全關機後再同步,系統不知道哪台機才有最新的狀態,它也不敢貿然同步因為正常使用下, Galera cluster 只有兩台機也會開步。所以需要人手介入,指定以某台機作為 cluster 的起始點。 舉個最簡單的例子,前述三台機 pocdbnode3 , pocdbnode2 , pocdbnode1 順序關閉,那麼 pocdbnode1 應該就會有最新的資訊。 在ubuntu中,可以查看 varlibmysqlgrastate.dat 中的 safe_to_bootstrap是否為1。如果是1,代表當初它有最後的 transaction ,以它為起始點重新起 cluster。 $ cat varlibmysqlgrastate.dat # GALERA saved state version 2.1 uuid 0c38b6dd7bdb11f0a4dd1f4be36a6ea9 seqno 1 safe_to_bootstrap 1 我們使用galera_recovery, galera_new_cluster, 就可以把該機器重新救起mariadb process。 $ galera_recovery WSREP Recovered position 0c38b6dd7bdb11f0a4dd1f4be36a6ea911 wsrep_start_position=0c38b6dd7bdb11f0a4dd1f4be36a6ea911 $ galera_new_cluster 然後其餘兩個 node 可以直接重啟 mariadb 服務 # node 2 $ systemctl start mariadb # node 3 $ systemctl start mariadb Reference Getting Started with MariaDB Galera Cluster 官方文件 How to Set up MariaDB Galera Clusters on Ubuntu 22.04 How to Bootstrap MySQL or MariaDB Galera Cluster ndash; Updated 還有比較複雜的救機狀況,例如safe_to_bootstrap全為0,即是可能是全部node都沒有好好地關掉,就掛了。大家有需要可以看看這個link的解決