dsh-devship:把服務啓動、打包與多環境發版收進 DSH 右側欄

前言

在 DSH 裏做日常研發,一件麻煩事是工作區裏那些 Maven 模塊和前端工程的生命週期散落在各處:項目在 IDEA 裏配一套運行配置,發包另開一個終端敲 mvn package,傳到服務器又得手動開 scp 或者寫腳本。每次切換 DSH 會話之後,這些動作都不在眼前。

dsh-devship 的處理方式是把這件事收進一個面板:在 DSH 右側欄加一個「應用運行」頁籤,自動識別當前工作區裏可啓動的項目,一行一個卡片,啓動、停止、打包、看日誌、部署、發佈都在卡片上,不需要跳出 DSH。下面介紹這個插件的安裝、面板行爲和配置項。

這是什麼

dsh-devship(插件內自稱「研發直髮臺」)由 dragoncdj 維護,是一個 DSH 客戶端側插件。它做的事可以拆成三段:

  1. 掃描工作區,找出 Java(Spring Boot 主類所在模塊)和前端(package.json 帶 scripts.dev)項目;
  2. 在右側欄面板裏提供啓動 / 停止 / 打包 / 日誌,並支持批量與脫離模式;
  3. 通過 SSH/SFTP 把產物發到多臺命名的部署環境,重啓容器並校驗狀態。

插件在社區目錄裏歸類爲「客戶端」,GitHub 倉庫顯示 1 star,許可證是 MIT。這裏說的「客戶端」指它掛在 DSH 客戶端界面裏,不是獨立命令行工具。

技術實現上,狀態查詢與命令執行在 Host 側(routes.js / lib/*.js),右側欄界面由 client.js 渲染,二者走同一個插件進程的接口。

安裝與啓用

安裝命令直接來自 README,按你用的 DSH 形態選一條:

# DSH Desktop
dsh plugin --profile desktop add dsh-devship

# dsh web
dsh plugin --profile web add dsh-devship

裝完需要重啓 DSH,右側欄會出現「應用運行」頁籤,插件激活後默認自動打開。如果面板被關掉,可以從右側欄頂部的「+」菜單裏重新打開。

開發插件本體時可以直接指向本地目錄,改完代碼重啓即生效:

dsh plugin --profile desktop add E:\workspace\dsh-devship

面板與項目識別

掃描規則

插件按固定規則發現項目,不需要手工登記:

  1. Java:根 pom.xml 的 <modules> 裏、且在 src/main/java 下存在 *Application.java 的模塊,二級模塊(父模塊 / 子模塊-server)也算;端口從 application-dev.yaml 頂層的 server.port 讀。
  2. 前端:根目錄或兩層子目錄下帶 scripts.dev 的 package.json(典型如 ui/*、web/*),用 pnpm dev 啓動。

掃描結果緩存 5 分鐘(SCAN_TTL_MS),同一工作區的重複掃描會合並。面板右上「刷新」按鈕強制重新掃描,新增模塊不需要重啓插件。

卡片與批量操作

每個項目一張卡片,包含勾選框、狀態點、類型徽標(Java / 前端)、端口、實際會執行的命令,以及 啓動 / 停止 / 打包 / 日誌 / 清除 / 隱藏 按鈕。

批量操作分三層:

  1. 頂部「全選 / 啓動選中 / 停止選中」配合卡片勾選框;啓動按順序逐個執行,避免多個 Maven 同時寫本地倉庫,失敗項彙總提示「哪個項目 + 什麼原因」;
  2. 底部「執行任務」列表右上角有「一鍵清除」,一次清掉所有打包 / 部署 / 發佈記錄;有任務在跑時按鈕就地變成「確認清除?會中斷任務」+ 是 / 否(3 秒不點自動收回),點「是」先中斷子進程 / SSH 上傳再移除條目,不會出現「列表裏沒了、進程還在後臺跑」的情況;
  3. 「完成自動清除」爲打包 / 部署 / 發佈各一個開關,另加一個延遲(立即 / 5 / 15 / 30 / 60 秒);開關存在 <工作區>/.dsh-devship/prefs.json,由 Host 側計時,面板關掉也生效。失敗的任務不會自動清除;開了自動清除的任務狀態後面顯示「N 秒後收起」倒計時。

另外每行有「隱藏 / 取消隱藏」,隱藏列表同樣持久化在 prefs.json;頂部按需出現「顯示已隱藏 (n)」開關,隱藏項半透明顯示。

端口被外部實例(IDEA / 終端起的)佔用時,該行會標「端口占用」,點啓動會先給出明確提示,而不是白跑一遍 Maven。

輪詢與連接佔用

面板每 2.5 秒拉一次運行狀態和日誌尾部,有文件在傳時降到 1.2 秒。輪詢是「一輪只允許一條在路上」:上一條 /status 沒回來就跳過這一跳,每條請求 9 秒超時,連續失敗按 2.5 → 5 → 10 → 15 秒退避,瀏覽器標籤頁在後臺時不輪詢。

這個限制是有意的:瀏覽器對同一個源最多開 6 條 HTTP/1.1 連接,面板把連接佔滿之後,DSH 自己的接口就要排隊,連接生成超過 15 秒會被判超時重連。走 HTTPS 局域網訪問時最容易撞上,表現就是頁面反覆「重新連接中 → 連接成功」。標籤頁設置了 keepMounted,切走再切回不會丟輪詢狀態。

日誌區固定 260px 高、內部滾動,不隨內容伸縮。

啓動與停止

Java 的啓動流程

啓動 Java 項目分兩個階段,共用一份日誌尾部,進入運行階段時清掉構建日誌只留應用輸出:

  1. mvn -pl <模塊> -am -DskipTests -Dspring-boot.repackage.skip=true install
    - -am 先構建上游模塊,避免本地倉庫裏是過期快照;
    - -Dspring-boot.repackage.skip=true 讓模塊 jar 保持普通 jar,BOOT-INF 嵌套包會讓聚合殼啓動時缺 Bean。
  2. mvn -pl <模塊> spring-boot:run,可帶 -Dspring-boot.run.profiles=<profile>。

啓動前用 800ms 探測端口,已被佔用就直接提示「可能已在運行」,不再白跑一遍 Maven。

構建和運行共用日誌尾部這一點值得注意:構建階段的輸出不會留在運行日誌裏,排查啓動問題時要回到「執行任務」列表看日誌文件。

Windows 上的窗口與進程

Windows 上啓動不彈黑色控制台窗口:.cmd 由隱藏窗口的 powershell -WindowStyle Hidden 執行,DSH 自己起的子進程也是隱藏啓動的,退出碼照常回傳。

停止時兩種模式對應兩種收尾方式:

模式 停止方式
附帶(隨 DSH 結束) handle.terminate(),DSH 的作業對象連帶整棵 powershell → cmd → mvn → java / node 一起結束
脫離(關 DSH 不停止) taskkill /T /F 結束整棵樹

啓動模式:關 DSH 不停止

面板上「clean / 快速打包」同一行有「關 DSH 不停止」複選框,默認勾選。它決定了服務是否隨 DSH 一起退出。

  1. 勾選(默認,脫離模式):寫一個 .cmd,輸出重定向到 logs/run-*.detached.log,外面套一層 .ps1 認領 pid,再用 WMI Win32_Process.Create 起 wscript.exe 隱藏啓動。WMI 起的進程父進程是系統服務 WmiPrvSE,不在 DSH 的作業對象裏,所以 DSH 關了也不受影響;啓動腳本里用 WScript.Shell.Run(..., 0, False)(窗口樣式 0 = 隱藏)起 powershell,全程不彈控制台窗口。要跟蹤的進程是那個 powershell,它第一件事就是把自己的 PID 寫進 run-*-detached.pid,Host 輪詢這個文件認領。WSH 被策略關掉時退回直接起 powershell(-WindowStyle Hidden),實測同樣沒有窗口。
  2. 不勾(附帶模式):走 ctx.subprocess 管道,由隱藏窗口的 powershell 執行 .cmd,DSH 退出後服務一併結束,日誌走管道。

脫離模式下日誌改爲讀日誌文件(增量)。

打包與發佈

打包命令

  1. Java:mvn -pl <模塊> -am package -DskipTests -T 1C(多線程),勾 clean 變 clean package;
  2. 「快速打包」勾上後去掉 -am,只編目標模塊(依賴須已 install 過),保留 -T 1C;
  3. 前端:直接 node ./node_modules/vite/bin/vite.js build --mode prod(不經 pnpm,避免多開可見窗口);
  4. 全量打包:根 reactor 執行 mvn package -DskipTests -T 1C。

前端打包繞過 pnpm 的原因是它會再開一個可見窗口,而插件的目標是全程不出窗。

一鍵發佈

卡片上的「發佈」把四步合到一個任務條目裏:打包 → 部署 → 重啓容器 → 校驗,日誌帶階段編號:

15:22:10 ① 打包(快速,不帶 -am) …
15:22:10 ① 打包完成
15:22:10 ② 部署 …
15:22:10 產物:...\target\xxx.jar
15:22:10 部署完成:1 個文件,155.88 MB
15:22:10 容器 xxx 已重啓,校驗狀態 …
15:22:10 容器狀態:Up 8 seconds
✔ 發佈完成

發佈期間任務列表裏會出現三張卡:打包(build)、部署(deploy)、發佈(release)。發佈卡是父卡,把子卡的日誌和上傳進度鏡像過來,因此:

  1. 進度條只有一條,畫在「發佈」卡上,「部署」子卡帶 progressInParent 標記、不再重複畫;單獨點「部署」時那張卡照常顯示進度;
  2. 狀態文案按任務種類區分:打包卡「打包中 / 打包完成」,部署卡「部署中 / 部署完成 / 部署失敗」,發佈卡「發佈中 / 發佈完成 / 發佈失敗」;
  3. 子卡保留是爲了各自的日誌與「檢出 / 中斷」,跑完按「完成自動清除」的設置收起。

部署到多臺服務器

面板右上「部署配置」打開一個多環境編輯器,配置存在 <工作區>/.dsh-devship/deploy.json。一臺服務器是一個環境,最多 12 臺;每臺有名稱、id、地址、端口、用戶、密碼、dsh-ssh 別名、重啓命令、「受保護」勾選、「設爲默認」、「刪除」。

環境選擇與受保護

推送環境的方式是「先選後發」:

  1. 配了多臺時,點「部署 / 發佈」會就地展開一排環境按鈕(發到哪臺? [開發] [測試] [生產],10 秒不點自動收回),點哪臺才真發;
  2. 只配了一臺(老 deploy.json)時不彈,點了就發;
  3. 按住 Shift 點按鈕跳過選擇器,直接發到「默認發佈環境」下拉選中的那臺;Shift 直髮、卡片上的遠端目錄、user@host:port 都指向這臺,按鈕懸停標題會點名 生產 · root@10.0.0.9 → /data/apps/demo/server;
  4. 「受保護」勾選後,選擇器裏點「生產」第一下變成紅色的「確認發到 生產」,再點一次才真發;部署 / 發佈 / 批量四條路都同樣攔,發完或切環境自動復位。

「拆成開發 / 測試 / 生產」一次生成三臺(現在這臺填進「開發」,「生產」默認勾上受保護),也可以「加一個環境」自己加。只有一臺老配置時會被讀成 id 爲 default 的一個環境。

遠端目錄與產物

目錄解析順序是 servers[環境].targets[項目].remoteDir → 頂層 targets[項目].remoteDir。每臺環境有自己的覆蓋表,留空就用頂層 targets;兩邊都沒配的項目,卡片上不出現「部署」按鈕。環境的 id 只能用小寫字母、數字和 -,最長 24。

對應 JSON:

{
  "defaultServer": "dev",
  "servers": [
    {
      "id": "dev", "label": "開發", "host": "10.0.0.9", "port": 22, "user": "root", "password": "***",
      "targets": { "app-server": { "remoteDir": "/data/apps/dev/server" } }
    },
    {
      "id": "prod", "label": "生產", "alias": "prod-ssh", "protected": true,
      "restartCommand": "docker restart {container}",
      "targets": { "app-server": { "remoteDir": "/data/apps/demo/server", "container": "demo-server" } }
    }
  ],
  "targets": {
    "app-server": { "remoteDir": "/data/apps/demo/server" },
    "ui/admin-web": { "remoteDir": "/data/apps/demo/web/admin" }
  }
}

產物默認是 Java = <模塊>/target/<artifactId>.jar,前端 = dist-prod / dist,可以用 artifact 覆蓋(每個環境各一份)。重啓命令裏的 {container} 會被替換成目標容器名。

部署走 ssh2 / SFTP:目錄先本地打 tar(不壓縮,局域網帶寬足,比 gzip 快)→ 傳一個包 → 遠端解包;tar 不可用時退回 8 路併發逐文件上傳;單個 jar 直接 put,上傳前會 cp x.jar x.jar.bak 留回滾副本。jar 尾部找不到 BOOT-INF/ 時日誌裏給 ⚠ 警告(不是 Spring Boot 可執行包)。重啓後用 docker ps 校驗容器狀態,不在運行列表就帶 docker logs --tail 20 報錯。批量部署時多個項目可以同時上傳(不再是串行),一條 SSH 會話可複用。

上傳進度反映在任務卡上:百分比、「已傳 / 總量」、實時速度;日誌裏每 2 秒記一行 ↑ 42% 12.3 MB / 29.1 MB · 4.8 MB/s,傳完一定記最後一行。速度按最近 5 秒窗口算,不是整輪平均。每次部署日誌第一行是 目標:生產 → root@10.0.0.9:22 · 受保護(alias 只顯示別名,實際連哪臺由 dsh-ssh 決定),建連後還有 連接 root@… / SSH 已連接 兩行,翻日誌可以覈對發到了哪臺。

任務與日誌分開:默認那臺用 deploy:<項目> → logs/deploy-<項目>.log,其它環境用 deploy_<環境>_<項目> → logs/deploy_<環境>_<項目>.log;同一個項目發兩臺是兩條獨立任務,日誌不會互相覆蓋。批量「部署選中 / 發佈選中」同樣先展開環境選擇器,把選中的項目全發到你點的那臺(不跨環境扇出),按順序逐個執行,失敗項彙總提示。

老形狀配置(頂層一臺 + 一份 targets)讀進來會合成默認那一臺,任務 id、日誌文件名、不帶 serverId 的請求行爲都和以前逐字節一致。有一個限制要注意:2.1.x 舊面板只會髮頂層字段,用它「保存配置」會把多環境配置寫回成單臺形狀(默認那臺的密碼和重啓命令繼承下來,不會丟憑據);用當前版本面板保存就不會回落。

可選配置

工作區根目錄配置

工作區根目錄放 .dsh-devship.json,可覆蓋自動探測結果:

{
  "maven": "D:/ProgramFiles/apache-maven-3.9.15/bin/mvn.cmd",
  "pnpm": "C:/Users/me/AppData/Roaming/npm/pnpm.cmd",
  "javaHome": "C:/Users/me/.jdks/ms-25.0.4"
}

不寫也能用:Maven 依次找 PATH → M2HOME / MAVEN_HOME 的 bin → 常見安裝目錄 → IDEA 自帶 Maven。JDK 按根 pom 的 java.version 找匹配版本,常見目錄按平臺給:Windows 是 ~/.jdks、C:\Program Files\Java、Adoptium / Zulu / Corretto 等;macOS 是 /Library/Java/JavaVirtualMachines(含 Contents/Home 那一層)和 Homebrew;Linux 是 /usr/lib/jvm 與 sdkman(JAVA_HOME 版本不合適時會自動換)。

運行時產生的啓動腳本放在 <工作區>/.dsh-devship/。

偏好與部署配置

prefs.json 可以手工改:

{
  "hidden": ["ui/admin"],
  "autoClear": { "build": true, "deploy": true, "release": false, "delayMs": 15000 }
}

delayMs 只認 0 / 5000 / 15000 / 30000 / 60000,其它值回落到 15000。自動清除只處理成功和已停止的收尾,失敗的任務留在列表裏,仍可手動清除或「一鍵清除」。

適用場景與注意

適合的場景:Java 多模塊 + Vite 前端的工作區,日常要反覆起停本地服務、打包、往開發 / 測試 / 生產機發包並重啓容器,希望這些動作不離開 DSH 界面。面板上的批量啓動、按環境拆分的部署、受保護環境、脫離模式關掉 DSH 不停止,都是爲這類重複操作準備的。

幾點需要知道:

  1. 這個插件運行在當前 DSH 進程的權限下,能起子進程、寫工作區文件、經 SSH 把包傳到你指定的服務器。它的行爲決定了你要把哪些目錄、哪些機器交給它;
  2. 裝之前先讀一遍源碼和許可證(MIT),確認啓動命令、部署配置和命令執行路徑符合你的工作區約定;
  3. 部署配置默認是空的,README 裏的服務器地址都是示例,需要自己填;
  4. 端口占用提示只針對外部啓動的實例,服務如由別的工具拉起,插件不會替你接管;
  5. SFTP 走 ssh2,遠端需要有可用的 shell 與 tar;tar 不可用時會退回逐文件上傳,速度快慢取決於環境;
  6. 脫離模式依賴 Windows WMI 與 WSH,被策略關掉時會退回直接起 powershell(隱藏窗口)。

收尾

回到開頭的問題:dsh-devship 的價值不在於又寫了一套啓動腳本,而在於把「一個工作區裏哪些服務在跑、包發到了哪臺機器、容器有沒有重啓」這些狀態集中到 DSH 右側欄的一個頁籤裏,並且可以用同一份配置把開發 / 測試 / 生產分開管理。識別規則是約定式的(*Application.java 就算 Java,scripts.dev 就算前端),遇到非常規結構時用 .dsh-devship.json 覆蓋即可。

目錄頁:https://www.skillhub.cn/plugins/dragoncdj/dsh-devship
源碼與文檔:https://github.com/dragoncdj/dsh-devship

羽毛球分组比赛记分
小程序二维码

欢迎使用《羽毛球分组比赛记分》微信小程序

小夜