基於node.js 自動打包專案,並提供http下載

weiweiyi發表於2023-02-14

前言

需求是:當 gitlab 專案,新建tag釋出時。透過指令碼自動打包成壓縮包,並提供http下載。使得專案釋出新版本時,使用者能夠透過url直接下載

流程圖:
image.png

伺服器配置

目前用於實現http服務的軟體有許多,包括主流的Apache、Nginx,還有微軟的IIS等。這裡使用apache。

在學習HTTP協議的時候,我們知道URL實際上指定的就是伺服器上特定的檔案,所以使用apche中的httpd這個輕量的HTTP server實現一個簡單的檔案下載服務是再合適不過的了。

1.安裝
apt-get install apache2
若有子包選擇,則選擇httpd

2.啟動

/etc/init.d/apache2 start

3.檢視啟動狀態:
/etc/init.d/apache2 status

4.嘗試訪問
然後,訪問伺服器的公網ip或域名,就可以看到類似如下介面,此時說明Apache正常工作:
若需要更改埠, 可以看這篇 修改apache2埠

image.png

5.下載

最後在 /var/www/html 路徑下,刪除 index.html,上傳自己想要被下載的檔案,再次訪問,就可以進行下載了。

image.png

若不想所有人都可以透過該url訪問到伺服器,還可以加上賬號密碼驗證,可以參考賬號密碼驗證

image.png

最後,可以透過 域名/檔名 的方式直接給別人一個連結,進行下載。

例如:xxxxx.com:port/20230214_myTestProject.zip, 訪問該url就可以下載。

指令碼開發

首先需要藉助 gitlab 的 CI/CD

GitLab CI/CD

CI ( Continuous Integration) ,中文即持續整合。
CD ( Continuous Delivery / Continuous Deployment) 持續交付 或 持續佈署。
官方文件

首先需要安裝gitlab-runner,官方安裝文件

編寫.gitlab-ci.yml

設定執行規則

workflow:
  # 當有tag或merge request時執行
  rules:
    - if: '$CI_PIPELINE_SOURCE == "merge_request_event" || $CI_COMMIT_TAG != null'

# 設定步驟(步驟間序列)
stages:
   - pack-web

命令列寫法

最開始我是透過直接命令列的形式編寫:

angular-pack:
  tags:
    - docker
  stage: pack-web
  # 找一個提供node的映象
  image: registry.cn-beijing.aliyuncs.com/mengyunzhi/node-chrome:14.16.0
  variables:
   # 定義壓縮包名字,日期為字首,專案名稱為字尾
   ZIP_NAME: "`date +%Y-%m-%d-%H-%M-%S`$CI_PROJECT_NAME"
  before_script:
   - cd web
  script:
    - env
    - npm install -dd
    - npm run build
    - apt install zip
    - zip -r $ZIP_NAME.zip dist
    - apt install sshpass -y
    - sshpass -p <password> scp -P 5022 -o StrictHostKeyChecking=no $ZIP_NAME.zip  <username>@<Server IP>:/var/www/html
  rules:
    - if: $CI_COMMIT_TAG != null
  1. 透過npm install 和 npm run build 打包構建,生成dist資料夾
  2. 安裝zip, 並使用zip命令將dist資料夾壓縮成壓縮包
  3. 安裝sshpass包,使得scp不需要互動式輸入密碼。
  4. 使用scp命令, 將資料夾傳送到伺服器上。 填上伺服器的ip,ssh埠,使用者名稱密碼,以及傳輸的目標位置

直接使用命令很不方便,我們可以編寫指令碼

編寫node.js指令碼

1.引入第三方模組

# 提供ssh連線
const {NodeSSH} = require('node-ssh'); // 提供ssh連線
const compressing = require('compressing'); //檔案壓縮
const ChatBot = require('dingtalk-robot-sender'); //提供釘釘推送

2.初始化變數

// 初始化變數
const projectName = process.env.CI_PROJECT_NAME; // 由環境變數獲取的工程名稱
const appDir = `/var/www/html`;       // 應用根位置
const host = process.env.HOST;        // 伺服器ip
const sshPort = process.env.SSHPORT;    // ssh埠
const port = process.env.PORT;         // http下載埠
const username = process.env.USERNAME; // 伺服器使用者名稱
const password = process.env.PASSWORD; // 伺服器密碼
const bashUrl = "https://oapi.dingtalk.com/robot/send?access_token=";
const dingToken = process.env.DINGTOKEN; // 釘釘機器人token

3.連線伺服器

console.log('嘗試連線生產伺服器');
const ssh = new NodeSSH();
await ssh.connect({
  host: `${host}`,
  port: `${sshPort}`,
  username: `${username}`,
  password: `${password}`
});

4.壓縮檔案

// 定義壓縮包名字為 日期_專案名稱
const d_t = new Date();
let year = d_t.getFullYear();
let month = ("0" + (d_t.getMonth() + 1)).slice(-2);
let day = ("0" + d_t.getDate()).slice(-2);
const zipName = year + month + day + "_" + projectName;

// 壓縮檔案,引數1:要壓縮的資料夾, 引數2: 壓縮後的名字
await compressing.zip.compressDir('dist', `${zipName}.zip`)
  .then(() => {
    console.log('壓縮成功');
  })
  .catch(err => {
    console.error(err);
  });

5.上傳到伺服器

 console.log('開始上傳壓縮包');
 // 引數1:為上傳的檔名稱, 引數2:伺服器的路徑以及檔名
 await ssh.putFile(`${zipName}.zip`,`${appDir}/${zipName}.zip`)

6.釘釘推送下載地址

 await dingSendSuccess(zipName);

const dingSendSuccess = async function (zipName) {
  try {
    const robot = new ChatBot({
      webhook: bashUrl + dingToken
    });
    
    let downloadUrl = host + ":" + port + "/" + zipName + ".zip";
    let title = "#### ? ? ? ? ?";
    let text = "## ? ? ? ? ?\n" +
        `> ### ${projectName} 打包成功!  [下載地址](http://${downloadUrl}) \n`;

    await robot.markdown(title, text, {}).then((res) => {
      console.log("響應資訊:" + res.data);
    });
  } catch (e) {
    console.log('推送錯誤', e);
  } finally {
    process.exit(0);
  }
}

下載地址為: 伺服器ip + http埠 + 檔名

7.編寫失敗的釘釘推送

const dingSendError = async function (error) {
  try {
    const robot = new ChatBot({
      webhook: bashUrl + dingToken
    });
    let title = "#### ? ? ? ?";
    let text = "## ? ? ? ?\n" +
        `> ### ${projectName} 打包失敗!  \n` +
        `> #### 錯誤資訊: ${error}  \n`;

    await robot.markdown(title, text, {}).then((res) => {
      console.log("響應資訊:" + res);
    });

  } catch (e) {
    console.log('推送錯誤', e);
  } finally {
    process.exit(0);
  }
}

8.完整程式碼

// 引入第三方模組
const {NodeSSH} = require('node-ssh');
const compressing = require('compressing');
const ChatBot = require('dingtalk-robot-sender');

// 初始化變數
const projectName = process.env.CI_PROJECT_NAME; // 由環境變數獲取的工程名稱
const appDir = `/var/www/html`;       // 應用根位置
const host = process.env.HOST;
const sshPort = process.env.SSHPORT;    // 下載埠
const port = process.env.PORT;    // 下載埠
const username = process.env.USERNAME;
const password = process.env.PASSWORD;
const bashUrl = "https://oapi.dingtalk.com/robot/send?access_token=";
const dingToken = process.env.DINGTOKEN;

// 定義開始函式,在檔案結尾呼叫
const start = async function () {
  try {
    console.log('嘗試連線生產伺服器');
    const ssh = new NodeSSH();
    await ssh.connect({
      host: `${host}`,
      port: `${sshPort}`,
      username: `${username}`,
      password: `${password}`
    });

    const d_t = new Date();

    let year = d_t.getFullYear();
    let month = ("0" + (d_t.getMonth() + 1)).slice(-2);
    let day = ("0" + d_t.getDate()).slice(-2);
    const zipName = year + month + day + "_" + projectName;

    await compressing.zip.compressDir('dist', `${zipName}.zip`)
        .then(() => {
          console.log('壓縮成功');
        })
        .catch(err => {
          console.error(err);
        });

    console.log('開始上傳壓縮包');
    await ssh.putFile(`${zipName}.zip`,
        `${appDir}/${zipName}.zip`)
    await dingSendSuccess(zipName);

  } catch (e) {
    console.log('打包發生錯誤', e);
    await dingSendError(e.message);
  } finally {
    process.exit(0);
  }
}

const dingSendSuccess = async function (zipName) {
  try {
    const robot = new ChatBot({
      webhook: bashUrl + dingToken
    });
    
    let downloadUrl = host + ":" + port + "/" + zipName + ".zip";
    let title = "#### ? ? ? ? ?";
    let text = "## ? ? ? ? ?\n" +
        `> ### ${projectName} 打包成功!  [下載地址](http://${downloadUrl}) \n`;

    await robot.markdown(title, text, {}).then((res) => {
      console.log("響應資訊:" + res.data);
    });

  } catch (e) {
    console.log('推送錯誤', e);
  } finally {
    process.exit(0);
  }

}

const dingSendError = async function (error) {
  try {
    const robot = new ChatBot({
      webhook: bashUrl + dingToken
    });
    let title = "#### ? ? ? ?";
    let text = "## ? ? ? ?\n" +
        `> ### ${projectName} 打包失敗!  \n` +
        `> #### 錯誤資訊: ${error}  \n`;

    await robot.markdown(title, text, {}).then((res) => {
      console.log("響應資訊:" + res);
    });

  } catch (e) {
    console.log('推送錯誤', e);
  } finally {
    process.exit(0);
  }

}

start().then().catch(function (error) {
  console.log('發生錯誤', error);
  process.exit(1);
});

9.使用

將檔案傳到專案資料夾下,然後將可以透過node pack.js 使用

angular-pack:
  tags:
    - docker
  stage: pack-web
  image: registry.cn-beijing.aliyuncs.com/mengyunzhi/node-chrome:14.16.0
  before_script:
   - cd web 
  script:
    - env
    - npm install -dd
    - npm run build
    - node pack.js
  rules:
    - if: $CI_COMMIT_TAG != null

10.效果圖
image.png

期間遇到的問題

1.檔名過長,無法正常壓縮

1676181683242_A61BAD5A-4F79-4eb9-AA5C-A3D0CC54788E.png

最開始檔名設定的是,年月日_時分秒_專案名字

例如: 2023-2-13_16:55_myTestProject.zip

但是如圖左邊紅色檔案所示,檔名不全,而且字尾沒了↑

image.png

透過 console.log(zipName);卻發現列印正常。 最後歸結原因為 第三方包的問題。

解決: 減少壓縮包的名稱字元

改為如下: 年月日_專案名字
20230214__myTestProject.zip

2.未提供檔名

1676207150660_2B76D62B-086C-4341-88CA-9D1F385D5A93.png

當時找了很久,因為報錯資訊並沒有很好地顯示問題。只報了failure。

之後去看了報錯的位置,雖然能知道是2699行的原因, 但是程式碼已經太底層了,分析不出原因

image.png

最後透過對比網上的用法:

發現是因為:沒有指出檔案的名稱,只指出了檔案路徑

如圖,紅框中的欄位當時沒有寫。寫上後報錯消失。
image.png

教訓:瞭解好引數的含義

3.http包的選擇

做到釘釘推送的時候,本來想用request包來傳送請求

1676209191129_F2948ED0-0F1D-42e2-A885-0BBAEFAED3C1.png

然後例如下載如下:

const request = require('request');
request('http://www.google.com', function (error, response, body) {
  console.error('error:', error); // Print the error if one occurred
  console.log('statusCode:', response && response.statusCode); // Print the response status code if a response was received
  console.log('body:', body); // Print the HTML for the Google homepage.
});

但是發現: request依賴包被棄用了

image.png

官網介紹說,2020年2月11日,npm依賴包就完全被棄用了(即不會有任何新的改變了);

代替方法找到了幾個比較推薦的:

  • Axios,Axios有更多的日/周/月下載量,github上獲得更多星;更多的followers和更多的fork,支援在browser上使用,但進度展示僅支援在瀏覽器上顯示
  • Got,有更多的版本,更頻繁的更新
  • Node.js標準庫裡的HTTP 和 HTTPS模組,無需安裝外部軟體包,但是同樣地也會帶來弊端就是使用起來並不友好,會繁瑣很多。

第一種用的人比較多,比較推薦,

但是僅僅為了釘釘推送的話,有個更方便的包 dingtalk-robot-sender文件

使用也很方便

let content = '我就是我, 是不一樣的煙火';
let at = {
   "atMobiles": [
    "156xxxx8827", 
    "189xxxx8325"
  ], 
  "isAtAll": false
};
// 快速傳送文字訊息
robot.text(content, at);

將該node.js環境配置成docker映象,可以參考https://segmentfault.com/a/11...

相關文章