关于node.js:基于nodejs-自动打包项目并提供http下载

3次阅读

共计 6729 个字符,预计需要花费 17 分钟才能阅读完成。

前言

需要是:当 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
  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 端口 + 文件名

须要服务器提供 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. 效果图

期间遇到的问题

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);
正文完
 0