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
的形式启动服务,那么每次我们代码更改,都需要手动停止服务,再重新启动,比较麻烦。
我们可以全局安装这个工具:
npm install -g nodemon
然后启动服务时,用它替换 node:
nodemon ./index.js
他就会使用 node 启动服务,然后实时监听文件的变动,当文件变化,它会帮我们自动重启服务,比较方便。
需要使用npm安装的模块
文件 index.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 中:
// 文件 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 启动服务:
node ./index.js
就开始监听 localhost:1213 端口了。
需要使用npm安装的模块
代码
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 的 express-generator 工具,快速生成一个node静态服务器,流程如下:
# 新建这个项目的项目文件夹
mkdir server
# 进入文件夹
cd server
# 使用 express-generator 初始化项目
npx express-generator
项目框架生成了,安装依赖并启动
# 安装项目依赖
npm install
# 启动项目
npm start
启动后,默认会监听本地的 3000 端口,此时打开浏览器访问 http://localhost:3000
,即可看到页面。
直接打开 app.js 文件,添加下面一行,就能修改为 3005 端口了:
process.env.PORT = 3005;
因为,查看 npm start 指令,它是使用 node 执行了 ./bin/www 文件。
查看 ./bin/www 中代码,发现这如下代码:
/**
* Get port from environment and store in Express.
*/
var port = normalizePort(process.env.PORT || '3000');
app.set('port', port);
所以,我们修改 process.env.PORT 就能修改端口了。
生成的项目的的 routes 和 views 文件夹,是 node 用来生成页面的。
但如果我们是一个纯静态服务器,那么就不需要两个文件夹了,只用有 public 文件夹即可。
所以,如果我们不打算使用 node 来根据路由、用户,生成页面,只想返回固定的静态页面文件,就可以进行以下操作,把多余的功能和工具去掉。
1. 删除 routes 和 views 文件夹。
直接删除两个文件
2. 修改 app.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 就可以了。
npm uninstall jade
需要使用npm安装的模块
代码
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()
}
需要使用npm安装的模块
代码
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()
})
}
})
有时工作中会遇到,拿到了一大把图片地址,需要下载这些图片的情况,这时就需要一个程序,能直接把下载地址们传入,就能批量下载。
这里的文件,包括图片、视频、pdf、js、css、html等常规的可以把链接用浏览器打开的文件。
需要保证系统安装有 curl 指令,可执行 curl -h 查看
需要使用npm安装的模块
下面是是用 node.js 的自带模块,执行 bash 指令,来使用 curl 下载。
curl 更多用法异步:curl 工具使用。
// 引入 child_process 模块
const execSync = require('child_process').execSync
// 下载
execSync('curl -o a.png https://n.sinaimg.cn/spider2020527/752/w681h871/20200527/0d2b-iufmpmn0320156.jpg')
需要使用npm安装的模块
使用方法
// 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)
}
})()
方法封装
// 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}`)
})
})
}
当程序员很多年,无论是个人项目还是公司项目,都有一大堆,当自己的换了电脑,或者电脑初始化,一个一个的拉取这些项目就是一个头大问题。
因为 node 是可以执行 shell 指令的,所以封装了一个方法,来批量拉取 git 项目。
1. 先拿到要拉取的项目的所有克隆地址
这一步比较简单,直接打开对应的远程仓库网站,比如 github,打开自己的所有仓库列表页,打开控制台,使用 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 模式需要),只要其中一个能克隆成功,那就可以开始批量的开搞了。
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)
}
})
}
}
进行前端开发时,偶尔会遇到跨域问题,出现这个问题一般会有两种可能:
Access-Control-Allow-Origin
字段。Access-Control-Allow-Origin
字段设置的就是网站域名。第一种情况,可以找后台说一下,看他们是在程序中设置,还是再网关中设置,需要后台改。
第二种情况,要么是和后台商量,测试环境宽松一些,可以让任何域名访问,要么,就得前端自己想办法克服了。
前端自己克服,解决方法也有:
如果使用的 vue 或 react,它们的脚手架就自带 proxy 代理,可以自己配置。
但万一运气不好,项目不是上面两种,那只能自己使用 node 搭建了。
下面就是使用 node-proxy 搭建一个正向代理服务。
需要使用npm安装的模块
功能:
http://localhost:8000
的请求,都会被代理到 http://localhost:9000
const httpProxy = require('http-proxy')
httpProxy.createProxyServer({
target: 'http://localhost:9000',
// ws: true, // 配置此字段后,将认为此代理的是 socket
}).listen(8000)
以下是等同无脑代理的另一种写法,能够更灵活配置的另一种写法,后面所有的路由功能,都要基于这种写法进行。
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)
下面借助新的写法,进行多样化的配置
封装:
// 封装的 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 代理中:
// 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 这个技术了。
正常使用 socket 时,通常都会用一些服务端/前端配套的 socket 插件,前端和后台都使用这个依赖库,能方便对 socket 进行管理,实现订阅、取消订阅,插件一般还能自带心跳功能、断线重连等功能。
这里不介绍插件,只用尽量原生的方式,使用 node + js 实现一下 socket 功能。
后端需要使用npm安装的模块
1. 服务端代码
// 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. 前端代码
<!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,指定子进程去执行文件。
主进程中执行的:
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:
// 子进程收到信息
process.on('message', async (data) => {
console.log('子进程:收到消息', data)
setTimeout(() => {
if (process.send) process.send('这是子进程发送给父进程的消息')
}, 1000)
})