前言
需求是:當 gitlab
專案,新建tag釋出時。透過指令碼自動打包成壓縮包,並提供http下載。使得專案釋出新版本時,使用者能夠透過url直接下載
流程圖:
伺服器配置
目前用於實現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埠
5.下載
最後在 /var/www/html
路徑下,刪除 index.html
,上傳自己想要被下載的檔案,再次訪問,就可以進行下載了。
若不想所有人都可以透過該url訪問到伺服器,還可以加上賬號密碼驗證,可以參考賬號密碼驗證
最後,可以透過 域名/檔名 的方式直接給別人一個連結,進行下載。
例如: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
- 透過npm install 和 npm run build 打包構建,生成dist資料夾
- 安裝zip, 並使用zip命令將dist資料夾壓縮成壓縮包
- 安裝sshpass包,使得scp不需要互動式輸入密碼。
- 使用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.效果圖
期間遇到的問題
1.檔名過長,無法正常壓縮
最開始檔名設定的是,年月日_時分秒_專案名字。
例如: 2023-2-13_16:55_myTestProject.zip
但是如圖左邊紅色檔案所示,檔名不全,而且字尾沒了↑。
透過 console.log(zipName);
,卻發現列印正常。 最後歸結原因為 第三方包的問題。
解決: 減少壓縮包的名稱字元
改為如下: 年月日_專案名字
20230214__myTestProject.zip
2.未提供檔名
當時找了很久,因為報錯資訊並沒有很好地顯示問題。只報了failure。
之後去看了報錯的位置,雖然能知道是2699行的原因, 但是程式碼已經太底層了,分析不出原因
最後透過對比網上的用法:
發現是因為:沒有指出檔案的名稱,只指出了檔案路徑
如圖,紅框中的欄位當時沒有寫。寫上後報錯消失。
教訓:瞭解好引數的含義
3.http包的選擇
做到釘釘推送的時候,本來想用request包來傳送請求
然後例如下載如下:
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依賴包被棄用了
官網介紹說,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...