编程崽

登录

一叶在编程苦海沉沦的扁舟之上,我是那只激情自射的崽

node基本使用

node基本使用

整体需要安装的基础软件或工具或小语法

  • node.js ( * 必须,我使用的是node v14 的LTS长期支持版)
  • cnpm 或 yarn (非必须 提速用)
  • VS code (非必须 代码编辑器)
  • Chrome(非必须 前端和后台开发最常用的浏览器)

小语法 + 工具

js 复制代码
const path = require('path')

// process.cwd() 启动服务时的,该「项目」的绝对路径
console.log(process.cwd()) // /root/node-demo

// __dirname 「执行这条指令的文件」所在的绝对路径
console.log(__dirname) // /root/node-demo/src/service

// path.join 方法,拼接出路径
console.log(path.join(__dirname, '../')) // /root/node-demo/src/

node服务监听工具 nodemon

node 开发时,如果直接使用 node ./index.js 的形式启动服务,那么每次我们代码更改,都需要手动停止服务,再重新启动,比较麻烦。

我们可以全局安装这个工具:

sh 复制代码
npm install -g nodemon

然后启动服务时,用它替换 node:

sh 复制代码
nodemon ./index.js

他就会使用 node 启动服务,然后实时监听文件的变动,当文件变化,它会帮我们自动重启服务,比较方便。

node 起服务的初始操作

需要使用npm安装的模块

文件 index.js 中:

js 复制代码
// 文件 index.js 中

const http = require('http') // 基础的 http 服务
const urlLib = require('url') // 格式化 get 请求的传参
const controller = require('./controller')

// 创建服务
let server = async function(request, response) {
  response.setHeader('Access-Control-Allow-Origin', '*') // 允许跨域的请求头
  response.setHeader('Access-Control-Allow-Headers', '*') // 允许携带的请求头
  response.setHeader('content-type', 'application/json;charset=utf-8') // 响应的数据格式

  // OPTIONS 请求处理
  if (request.method === 'OPTIONS') {
    response.writeHead(200)
    response.end()
    return
  }

  let urlPase = request.url.substr(0).split('?')[0]
  let result = null
  try {
    if (request.method === 'GET') result = await get(request, urlPase, response)
    else if (request.method === 'POST') result = await post(request, urlPase, response)
    response.end(result)
  } catch(err) {
    console.error(err)
    response.end(JSON.stringify({success: false, data: err.message}))
  }
}

// get请求
async function get(request, urlPase ) {
  if (controller[urlPase]) {
    let query = urlLib.parse(request.url, true).query
    let result = await controller[urlPase](query, request) || null
    return result
  } else {
    throw `没有找到的路由:${urlPase}`
  }
}

// post请求
function post(request, urlPase) {
  return new Promise((resolve, reject) => {
    if (!controller[urlPase]) {
      reject(`没有找到的路由:${urlPase}`)
      return
    }
    let str = ''
    request.on('data', function (data) {
      str += data
    })
    request.on('end', async function (error) {
      if (error) { // 接参出错
        reject('接收失败:' + error)
      } else { // 接参成功
        try {
          params = JSON.parse(str || '{}') // {object} 接收的数据
          let result = await controller[urlPase](params, request) || null
          resolve(result)
        } catch(error) {
          resolve(error)
        }
      }
    })
  })
}

// 监听端口号
http.createServer(server).listen(1213)

文件 controller.js 中:

js 复制代码
// 文件 controller.js 中

// 根据请求来的 url,指定处理程序
module.exports = {
  // 获取名字 get 请求
  '/getNameGet': (params, request) => {
    console.log(params)
    // 返回时转译
    return JSON.stringify({
      msg: '',
      success: true,
      data: 'get 成功'
    })
  },
  // 获取名字 post 请求
  '/getNamePost': (params, request) => {
    console.log(params)
    // 返回时转译
    return JSON.stringify({
      msg: '',
      success: true,
      data: 'post 成功'
    })
  },
}

最后使用 node 启动服务:

sh 复制代码
node ./index.js

就开始监听 localhost:1213 端口了。

express 框架的基本使用

需要使用npm安装的模块

  • express
  • body-parser
  • cookie-parser
  • express-static

代码

js 复制代码
const express = require('express') // 引入express
const server = express() // 实例化express
const bodyParser = require('body-parser') // 中间件,用于获取 post 的传值
const cookieParser = require('cookie-parser') // cookie 操作插件
const static = require('express-static')

server.listen(1234) // 监听 1234 这个端口

// 使用中间件
// 格式化 post 请求的入参,extended 为 false 时,值为数组或string, true为任意数据类型
server.use(bodyParser.urlencoded({ extended: false }))
server.use(cookieParser()) // 使用 cookieParser

// 新建路由
let userRouter = express.Router()
let goodRouter = express.Router()
// 使用路由
server.use('/user', userRouter)
server.use('/good', goodRouter)

// userRouter 路由监听 get 请求 - 设置了接口名
userRouter.get('/get', (req, res, next) => {
  let data = req.query
  console.log(1, data)
  // 设置cookie
  res.cookie(
    'account', // cookie名
    123, // cookie内容
    {
      path: '/get', // cookie 生效的路径
      maxAge: 10000 // 过期时间
    }
  )
  res.paramsData = {
    params: data,
    a: '有人get了'
  }
  if (res.paramsData) {
    console.log(res.paramsData.a, res.paramsData.params)
    res.send(JSON.stringify(res.paramsData.params)) // 发送返回值并结束请求
    return
  }
  res.send('发起了请求') // 发送返回值并结束请求
})

// goodRouter 路由监听 post 请求 - 设置了接口名
goodRouter.post('/post', (req, res, next) => {
  let data = req.body
  res.paramsData = {
    params: data,
    a: '有人post了'
  }
  next()
})


// 可同时监听 get 和 post 请求
server.post('*', (req, res, next) => {
  res.setHeader("Access-Control-Allow-Origin", "*")
  if (res.paramsData) {
    console.log(res.paramsData.a, res.paramsData.params)
    res.send(JSON.stringify(res.paramsData.params)) // 发送返回值并结束请求
    return
  }
  res.send('发起了请求') // 发送返回值并结束请求
})

server.use(static('./www'))

使用 express-generator 快速创建node 静态服务器

可以使用 express 的 express-generator 工具,快速生成一个node静态服务器,流程如下:

sh 复制代码
# 新建这个项目的项目文件夹
mkdir server

# 进入文件夹
cd server

# 使用 express-generator 初始化项目
npx express-generator

项目框架生成了,安装依赖并启动

sh 复制代码
# 安装项目依赖
npm install

# 启动项目
npm start

启动后,默认会监听本地的 3000 端口,此时打开浏览器访问 http://localhost:3000,即可看到页面。

修改端口

直接打开 app.js 文件,添加下面一行,就能修改为 3005 端口了:

js 复制代码
process.env.PORT = 3005;

因为,查看 npm start 指令,它是使用 node 执行了 ./bin/www 文件。

查看 ./bin/www 中代码,发现这如下代码:

js 复制代码
/**
 * Get port from environment and store in Express.
 */

var port = normalizePort(process.env.PORT || '3000');
app.set('port', port);

所以,我们修改 process.env.PORT 就能修改端口了。

删除无用文件和模板工具

生成的项目的的 routesviews 文件夹,是 node 用来生成页面的。

但如果我们是一个纯静态服务器,那么就不需要两个文件夹了,只用有 public 文件夹即可。

所以,如果我们不打算使用 node 来根据路由、用户,生成页面,只想返回固定的静态页面文件,就可以进行以下操作,把多余的功能和工具去掉。

1. 删除 routes 和 views 文件夹。

直接删除两个文件

2. 修改 app.js 中如下的配置

删除多余的路由解析、模板文件获取功能。

js 复制代码
var indexRouter = require('./routes/index');
var usersRouter = require('./routes/users');

app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'jade');

app.use('/', indexRouter);
app.use('/users', usersRouter);

3. 卸载 jade 依赖

node 生成页面的模板使用的是 jade,现在不需要了,卸载此依赖。

如果项目还没 install,那直接在 package.json 文件中,删除这个依赖的引用,在 install 就可以了。

sh 复制代码
npm uninstall jade

node.js发送邮件

需要使用npm安装的模块

  • nodemai

代码

js 复制代码
const nodemailer = require('nodemailer')

// 调用 sendEmail 方法
await sendEmail(
  {
    user: '123456789@qq.com', // 虚构的,用户名
    pass: 'MiLgesCvkuKkhfF' // 虚构的,此处为授权码,并非邮箱密码,需要去邮箱页面中配置
  },
  {
    to: `1422205209@qq.com`, // 目标邮箱
    subject: `${getDate(+nowTime)} 文档备份`, // 标题
    text: `${getDate(+nowTime)} 个人文档备份 见附件`, // 内容
    // html: "<b>Hello world?</b>", // html的内容
    attachments: [{ // 附件列表
      filename: `${fileName}.zip`, // 附件名称
      // content: data,
      path: userZipPath, // 附件地址
    }]
  },
)

/**
 * 发送邮件
 * options 发送的邮件的内容信息
 */
async function sendEmail(account, options) {
  // let testAccount = await nodemailer.createTestAccount();

  // 这里配置的固定使用 qq 邮箱,可以配置其他的
  let transporter = nodemailer.createTransport({
    host: "smtp.ethereal.email",
    service: 'qq',
    // port: 465,
    secure: true, // true for 465, false for other ports
    auth: {
      user: account.user, // generated ethereal user
      pass: account.pass, // generated ethereal password
    },
  });

  await transporter.sendMail({...options, from: account.user})
  
  // 用完了 关闭连接池
  transporter.close()
}

mysql 数据库的连接与使用

需要使用npm安装的模块

  • mysql

代码

js 复制代码
const mysql = require('mysql') // 引入插件

// 设定连接的 mysql 库
const pool = mysql.createPool({
  host: 'localhost', // 网络路径
  port: '3306', // 端口,可不写,默认 3306
  user: 'root', // 数据库登陆账号
  password: '123', // 数据库登陆密码
  database: '20180805' // 链接的库名
})

// 创建一个连接服务
pool.getConnection((err, connection) => {
  if (err) {
    console.log('连接失败:' + err)
    connection.release()
  }
  else {
    console.log('连接成功')
    // 使用方法对数据库进行操作,使用数据库语句
    connection.query('INSERT INTO `userTab` (`user`, `pass`) VALUES("xiaoming", "123789");', (err, data) => {
      if (err) console.log('操作失败:' + err)
      else console.log('操作成功')
      // 由于数据库设置的同时连接数量有限,用完之后,需要及时关闭这个连接
      connection.release()
    })
  }
})

使用node下载文件

有时工作中会遇到,拿到了一大把图片地址,需要下载这些图片的情况,这时就需要一个程序,能直接把下载地址们传入,就能批量下载。

这里的文件,包括图片、视频、pdf、js、css、html等常规的可以把链接用浏览器打开的文件。

执行 bash 指令,借用 curl 下载

需要保证系统安装有 curl 指令,可执行 curl -h 查看

需要使用npm安装的模块

下面是是用 node.js 的自带模块,执行 bash 指令,来使用 curl 下载。

curl 更多用法异步:curl 工具使用

js 复制代码
// 引入 child_process 模块
const execSync = require('child_process').execSync

// 下载
execSync('curl -o a.png https://n.sinaimg.cn/spider2020527/752/w681h871/20200527/0d2b-iufmpmn0320156.jpg')

纯 node 封装的下载功能

需要使用npm安装的模块

使用方法

js 复制代码
// index.js 中使用

const downFile = require('./downFile.js')

;(async () => {
  try {
    let result = await downFile('https://n.sinaimg.cn/spider2020527/752/w681h871/20200527/0d2b-iufmpmn0320156.jpg', './abc')
    console.log(result)
  } catch(err) {
    console.log(err)
  }
})()

方法封装

js 复制代码
// downFile.js 文件中

// 引入需要的模块
const path = require('path')
const http = require('http')
const https = require('https')
const fs = require('fs')
const fsPromises = require('fs').promises
/**
 * DownFile 存储文件方法封装
 * @param { string } fileUrl 文件地址,支持http和https两种
 * @param { string } savePath 文件存储到本地的文件夹地址,值可以为类似 './' | './files/' | '../' | '../files/' 等
 */
module.exports = async function downFile(fileUrl, savePath, name) {
  let REQ = null
  if (/^https/.test(fileUrl)) REQ = https
  else if (/^http/.test(fileUrl)) REQ = http
  if (!REQ) throw new Error(`【失败】:${fileUrl} 不是有效的地址`)
  // 截取文件名称
  let nameArr = fileUrl.split('?')[0].split('/')
  let fileName = name || nameArr[nameArr.length - 1] // 获取文件名字
  let _saveDir = path.join(__dirname, savePath) + (/\/$/.test(savePath) ? '' : '/')

  // 下载拿到文件
  let fileObj = await toDown(REQ, fileUrl)

  let _savePath = _saveDir + fileName // 文件存储路径 + 文件名
  if (fs.existsSync(_savePath)) { // 文件存在,需要改名
    let fileNameArr = fileName.split('.')
    let fileName_2 = fileNameArr.pop()
    let fileName_1 = fileNameArr.join('.')
    _savePath = `${_saveDir}${fileName_1}_${+new Date()}_${parseInt(Math.random() * 1000000)}.${fileName_2}`
  }

  // 创建文件夹目录
  await fsPromises.mkdir(_saveDir, { recursive: true })

  // 写入文件
  try {
    await fsPromises.writeFile(_savePath, fileObj)
  } catch(err) {
    throw new Error(`【失败】:${_savePath} 存储失败`)
  }
  return `【成功】:${fileUrl}`
}

async function toDown(REQ, url) {
  return new Promise((resolve, reject) => {
    //发送请求获取相关文件信息
    REQ.get(url, data => {
      let bufferArr = [] // buffer 数组
      data.on('data', chunk => { // 收到数据,一次请求会收到多次
        bufferArr.push(chunk)
      })
      data.on('end', (err) => { // 数据已经全部返回
        if (err) return reject(`【失败】:文件地址无效:${url}`)
        let dataBuffer = Buffer.from(bufferArr[0]) // 初始化一个buffer对象
        // 如果存储buffer对象的数组长度大于1 就拼接buffer对象
        if (bufferArr.length < 1) reject(`【失败】:文件地址无效:${url}`)
        for (let i = 1; i < bufferArr.length; i++) {
          dataBuffer = Buffer.concat([dataBuffer, bufferArr[i]]) // 将多条buffer对象拼接
        }
        resolve(dataBuffer)
      })
    }).on('error', (err) => {
      reject(`【失败】:网络请求错误:${url}`)
    })
  })
}

批量拉取 git 项目

当程序员很多年,无论是个人项目还是公司项目,都有一大堆,当自己的换了电脑,或者电脑初始化,一个一个的拉取这些项目就是一个头大问题。

因为 node 是可以执行 shell 指令的,所以封装了一个方法,来批量拉取 git 项目。

1. 先拿到要拉取的项目的所有克隆地址

这一步比较简单,直接打开对应的远程仓库网站,比如 github,打开自己的所有仓库列表页,打开控制台,使用 js 获打印此页所有的项目名称或跳转地址,打印后就能复制走了。

Js 方法大致如下,自己查看页面元素更正正确的类名即可:

js 复制代码
JSON.stringify([].map.call(document.querySelectorAll('.project-row a.text-plain'), (item => item.href)), null, 2)

有了这些地址,我们可以自己拼接一下,足可以拼接成我们需要的克隆地址了。

2. 把拼接好的数组调用方法,依次拉取

下面是封装的一个js,把 list 俺格式替换一下,使用 node 执行这个 js,就会依次 clone 项目们了。

这里需要注意,最好先克隆一下任意一个项目,保证自己这台电脑已经登录过(https 模式需要),或者已经配置好了公钥秘钥(ssh 模式需要),只要其中一个能克隆成功,那就可以开始批量的开搞了。

js 复制代码
const process = require('child_process');

// 准备要拉取项目的数组
let list = [
  {
    name: 'wx 项目', // 供打印用,告知项目名称
    ssh_url: "https://gitee.com/some-account/wx.git", // 地址,支持 https 或 ssh,只要是 git clone 支持的,这里就支持
  },
  {
    name: 'qq 项目',
    ssh_url: "git@gitee.com:some-account/qq.git", // 地址,支持 https 或 ssh
  },
]

// 去开始执行
begin(list)

// 封装的方法
function begin(list) {
  console.log('共', list.length, '个')
  let i = 0
  start(i)
  function start(i) {
    let nowObj = list[i]
    if (!nowObj || !nowObj.ssh_url) {
      console.log('全完啦, i = ', i)
      return
    }
    console.log('开始索引值为 ' + i + ' 的 ' + nowObj.name)
    process.exec(`git clone ${nowObj.ssh_url}`,function (error, stdout, stderr) {
      console.log(nowObj.name +  ' 完成')
      if (error) {
        console.log('第 ' + i + ' 个有错误', error)
      } else {
        start(i + 1)
      }
    })
  }
}

使用 http-proxy 搭建正向代理

进行前端开发时,偶尔会遇到跨域问题,出现这个问题一般会有两种可能:

  • 后台没有配置好跨域处理,设置好响应头的 Access-Control-Allow-Origin 字段。
  • 接口本身就不支持跨域,必须使用同域名访问,也就是响应头的 Access-Control-Allow-Origin 字段设置的就是网站域名。

第一种情况,可以找后台说一下,看他们是在程序中设置,还是再网关中设置,需要后台改。

第二种情况,要么是和后台商量,测试环境宽松一些,可以让任何域名访问,要么,就得前端自己想办法克服了。

前端自己克服,解决方法也有:

  • 使用浏览器插件,让插件帮我们改一下接口响应头(忘记那个插件了)。
  • 自己搭建一个正向代理。

如果使用的 vue 或 react,它们的脚手架就自带 proxy 代理,可以自己配置。

但万一运气不好,项目不是上面两种,那只能自己使用 node 搭建了。

下面就是使用 node-proxy 搭建一个正向代理服务。

仓库和文档

需要使用npm安装的模块

  • http-proxy

整体无脑代理

功能:

  • 所有到达 http://localhost:8000 的请求,都会被代理到 http://localhost:9000
  • 不支持分路由代理
  • 如果是 socket,需要设置字段 ws: true
js 复制代码
const httpProxy = require('http-proxy')

httpProxy.createProxyServer({
  target: 'http://localhost:9000',
  // ws: true, // 配置此字段后,将认为此代理的是 socket
}).listen(8000)

另一种写法

以下是等同无脑代理的另一种写法,能够更灵活配置的另一种写法,后面所有的路由功能,都要基于这种写法进行。

js 复制代码
const http = require('http')
const httpProxy = require('http-proxy')

const proxy = httpProxy.createProxyServer()

http.createServer((req, res) => {
  proxy.web(req, res, {
    target: 'http://localhost:9000'
  })
}).listen(8000)

判断路由分别代理

下面借助新的写法,进行多样化的配置

封装:

js 复制代码
// 封装的 proxy.js 文件中

const http = require('http')
const httpProxy = require('http-proxy')

function server(config) {

  const proxy = httpProxy.createProxyServer()
  
  // 获取到配置的代理的路由们
  const configRuleNames = Object.keys(config)

  // 使用代理,请求 target 地址之前,进行的处理
  proxy.on('proxyReq', function(proxyReq, req, res, options) {
    // 设置一些请求头
    // proxyReq.setHeader('X-Special-Proxy-Header', 'foobar')
  })

  // 使用代理,请求 target 地址,拿到响应后,进行处理
  proxy.on('proxyRes', function (proxyRes, req, res) {
    // 处理跨域
    proxyRes.headers['access-control-allow-origin'] = '*'
  })

  // 使用代理发出请求,出现错误时的处理
  proxy.on('error', function (err, req, res) {
    res.writeHead(500, {
      'Content-Type': 'text/plain'
    })
    res.end('Something went wrong. And we are reporting a custom error message.')
  })

  // 创建 http 服务,供开发者在开发时使用
  // 服务会监听端口,当开发者请求时,触发服务,服务里面使用 http-proxy 向 target 地址发起请求
  const service = http.createServer((req, res) => {
    const { url } = req
    let hitRule = false // 是否命中规则

    // 遍历查找命中的规则,只是简单的判断,可自行增加判断逻辑,比如支持正则等
    for (let ruleName of configRuleNames) {
      if (url.indexOf(ruleName) === 0) {
        proxy.web(req, res, config[ruleName])
        hitRule = true
        break
      }
    }

    // 没有命中时,可以做一些处理
    if (!hitRule) {
      // ...
    }
  })

  return service
}

module.exports = server

使用,稍后使用 node 启动该文件即可,然后访问 http://localhost:8088,就会走入到 proxy 代理中:

js 复制代码
// index.js 中

const http = require('http')
const proxy = require('./proxy')

// 个人配置的代理规则,路由内部对象的配置,完全依照 node-proxy 要求的配置即可
const config = {
  '/api': {
    target: 'http://www.abc.com/rest/',
    changeOrigin: true,
    headers: {
      'name': 'One Api',
    },
  },
  '/api/ws/': {
    target: 'wss://www.abc.com/ws/',
    ws: true,
  },
  '/': { // 此条最后写,否则匹配路由时,会先命中此条
    target: 'http://www.abc.com/',
  },
}

proxy(config).listen(8088)

前后端使用 webSocket

一些对实时性有较高要求、或者后端主动推送消息给前端的需求时,我们就需要使用 webSocket 这个技术了。

正常使用 socket 时,通常都会用一些服务端/前端配套的 socket 插件,前端和后台都使用这个依赖库,能方便对 socket 进行管理,实现订阅、取消订阅,插件一般还能自带心跳功能、断线重连等功能。

这里不介绍插件,只用尽量原生的方式,使用 node + js 实现一下 socket 功能。

后端需要使用npm安装的模块

  • nodejs-websocket

1. 服务端代码

js 复制代码
// 1 引入模块
const ws = require('nodejs-websocket')

// 2 创建服务器
const client = ws.createServer((connection) => {
  connection.on('text', (str) => {
    const data = JSON.parse(str)
    console.log('收到的消息', data)
    connection.sendText(JSON.stringify({
      msg: '我是服务端,我给你发的消息',
    }))
  })
  connection.on('close', () => {
    console.error('关闭了')
  })
  connection.on('error', (err) => {
    console.error('error 了', err)
  })
}).listen(8812)

2. 前端代码

html 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body></body>
<script>
  let url = 'ws://localhost:8812'

  // 初始化连接
  let socket = new WebSocket(url)

  // 连接失败
  socket.onclose = event => {
    console.log('连接失败', event)
  }

  // 连接成功
  socket.onopen = function() {
    console.log('连接成功')

    // 连接成功,发送一条消息给后台
    let sendObj = {msg: '我是客户端,我连上你了'}
    socket.send(JSON.stringify(sendObj))

    // 添加事件监听
    socket.onmessage = onmessage

    // 连接断开 - 此时已经是连接成功后,所以是连接断开
    socket.onclose = event => {
      console.log('连接断开', event)
    }

    setTimeout(() => {
      // 要主动断开连接了,修改一下事件
      socket.onclose = () => {
        console.log('前端关闭了连接')
      }
      socket.close()
    }, 1000 * 5);
  }

  // 连接失败 - 此时还没连接成功过,所以是连接失败
  socket.onclose = event => {
    console.log('连接失败', event)
  }

  function onmessage(event) {
    console.log('收到消息:', JSON.parse(event.data))
  }

</script>
</html>

使用子进程

node使用子进程,需要创建一个js,指定子进程去执行文件。

主进程中执行的:

js 复制代码
const { fork } = require('node:child_process')
const path = require('path')

// 创建子进程
const process = fork(path.join(__dirname, './childProcess.js'))

// 收到子进程发送的信息
process.on('message', (data) => {
  console.log('父进程:收到消息', data)
})

// 发一条信息给子进程
process.send('这是父进程发送给子进程的消息')

同级目录创建一个子进程执行的文件 childProcess.js

js 复制代码
// 子进程收到信息
process.on('message', async (data) => {
  console.log('子进程:收到消息', data)
  setTimeout(() => {
    if (process.send) process.send('这是子进程发送给父进程的消息')
  }, 1000)
})