1. 创建自定义全局指令
1.1 新建一个空的文件夹, 创建一个cli.js文件
1.2 在cli.js写入内容
/usr/bin/env就是让系统使用node来执行你的脚本文件。
#! /usr/bin/env node
1.3 执行终端指令
// 在文件夹 node-project 的终端下执行指令
npm init
执行完后package.json结构如下, 其中aaa是创建指令时package name
命令完成后,在你的终端输入bin下面的属性值,比如我就应该输入aaa
如上图, 这时候就会输出aaa。如果你能正确的输出内容,那么你的全局自定义命令就创建好了。
2. 使用commander指令
2.1 处理help选项(option)
#! /usr/bin/env node
// 安装commander
npm i commander
// 在cli.js内使用
const {program} = require('commander')
// 使用aaa --help后, Option选项将会添加下方配置, 效果图如下图
program.option('-f --framwork <framwork>', '设置框架')
program.parse(process.argv)
2.2 自定义命令参数处理(command)
#! /usr/bin/env node
// 按照commander
npm i commander
// 在cli.js内使用
const {program} = require('commander')
// 需要自定义创建aaa create xxx a b c d指令时, 需要使用command
// 使用aaa --help后, Command选项将会添加下方配置, 效果图如下图
program.command('create <project> [other...]')
.alias('crt') // 给create起别名
.description('创建项目') // 描述
.action((project, args) => {
console.log(project) // xxx
console.log(args) [a, b, c, d]
}) // 业务逻辑
program.parse(process.argv)
2.3 对上方代码进行模块拆分
// cli.js
#! /usr/bin/env node
const {program} = require('commander')
// 对模块进行拆分
// 导入help.js
const help = require('../lib/core/help')
help(program)
// 导入commander.js
const commander = require('../lib/core/commander')
commander(program)
program.parse(process.argv)
// action.js
const myAction = (project, args) => {
// 命令行的执行逻辑代码
console.log(project, args);
}
module.exports = myAction
// commander.js
const myAction = require('./action')
const commander = (program) => {
// 需要创建create指令时需要使用command函数, 然后控制台可以打印 aaa create xxx k g x d
program.command('create <project> [other...]')
// 给create起别名
.alias('crt')
// 描述
.description('创建项目')
// 业务逻辑
.action(myAction)
}
module.exports = commander
// help.js
const help = (program) => {
return program.option('-f --framwork <framwork>', '设置框架')
}
module.exports = help
2.4. 命令行问答交互: 使用 inquirer 库
// 安装 inquirer 8及以下版本用法
npm install --save inquirer@^8.0.0
// config.js
module.exports = {
framework: ['express', 'koa', 'egg']
}
// action.js
const inquirer = require('inquirer')
const config = require('../../config')
const myAction = (project, args) => {
// 命令行的执行逻辑代码
inquirer.prompt([
{
type: 'list',
name: 'framework',
choices: config.framework,
message: '请选择框架'
}
]).then(answer => {
// 用户回答的内容
console.log(answer);
})
}
module.exports = myAction
// 安装 inquirer 9及以上版本用法
npm install --save inquirer
// config.js
module.exports = {
// framework: [
// {
// name: 'express',
// value: '选项一'
// },
// {
// name: 'koa',
// value: '选项二'
// },
// {
// name: 'egg',
// value: '选项三'
// }
// ],
framework: ['express', 'koa', 'egg'] // 和上面那种写法一样
}
// action.js
const inquirer = require('inquirer').default
const config = require('../../config')
const myAction = (project, args) => {
// 命令行的执行逻辑代码
inquirer.prompt([
{
type: 'list',
name: 'framework',
message: '请选择框架',
choices: config.framework
}
]).then(answer => {
// 用户回答的内容
console.log(answer);
})
}
module.exports = myAction
2.5. download-git-repo: 下载原创仓库模块代码
// 安装
npm i download-git-repo
// action.js
const inquirer = require('inquirer').default
const downloadFn = require('./download')
const config = require('../../config')
const myAction = async (project, args) => {
// project 是项目名字
// 命令行的执行逻辑代码
const answer = await inquirer.prompt([
{
type: 'list',
name: 'framework',
message: '请选择框架',
choices: config.framework
}
])
// 用户回答的内容
downloadFn(config.frameworkUrl[answer.framework], project)
}
module.exports = myAction
// download.js
const download = require('download-git-repo')
const config = require('../../config')
const downloadFn = (url, project) => {
/**
*
* 第一个参数: direct: 我们的仓库不在他默认的仓库下, 所以需要设置一个前缀, 后面再跟上你的仓库地址
* 第二个参数: 指定下载的路径
* 第三个参数: 以什么样的方式下载(clone: 以克隆方式下载)
* 第四个参数: 错误提示
*/
download(`direct:${url}`, project, {clone: true}, err => {
console.log(err);
})
}
module.exports = downloadFn
2.5. ora: 下载等待提示交互
// 安装(6及以上使用import, commonjs要6以下)
npm i ora@5
// download.js
const download = require('download-git-repo')
const ora = require('ora')
const downloadFn = (url, project) => {
const spinner = ora().start()
spinner.text = '代码正在下载...'
/**
*
* 第一个参数: direct: 我们的仓库不在他默认的仓库下, 所以需要设置一个前缀, 后面再跟上你的仓库地址
* 第二个参数: 指定下载的路径
* 第三个参数: 以什么样的方式下载(clone: 以克隆方式下载)
* 第四个参数: 错误提示
*/
download(`direct:${url}`, project, {clone: true}, err => {
if(!err) {
spinner.succeed('下载成功')
console.log('Done! you run:');
console.log('cd' + project);
console.log('npm install');
console.log('npm run dev');
} else {
spinner.fail('下载失败')
}
})
}
module.exports = downloadFn
2.6. chalk: 命令行样式输出
// 安装, 5及以上版本使用import, 4及以下使用commonjs
npm i chalk@4
// download.js
const download = require('download-git-repo')
const ora = require('ora')
const chalk = require('chalk')
const downloadFn = (url, project) => {
const spinner = ora().start()
spinner.text = '代码正在下载...'
/**
*
* 第一个参数: direct: 我们的仓库不在他默认的仓库下, 所以需要设置一个前缀, 后面再跟上你的仓库地址
* 第二个参数: 指定下载的路径
* 第三个参数: 以什么样的方式下载(clone: 以克隆方式下载)
* 第四个参数: 错误提示
*/
download(`direct:${url}`, project, {clone: true}, err => {
if(!err) {
spinner.succeed('下载成功')
console.log(chalk.green('Done! you run:'));
console.log(chalk.green('cd' + project));
console.log(chalk.green('npm install'));
console.log(chalk.green('npm run dev'));
} else {
spinner.fail('下载失败')
console.log(chalk.red.bold('失败的颜色'));
}
})
}
module.exports = downloadFn
3. Web服务器项目开发
3.1 使用node创建HTTP服务器
// server.js
// nodemon 检测代码更改, 一旦有代码改变就会重新运行 npm i -g nodemon
// nodemon server.js
// 导入http模块
const http = require('http')
// 创建服务器
// 获取到服务器的实例对象
const server = http.createServer()
server.listen(3030, () => {
console.log('http://127.0.0.1:3030');
console.log('创建成功');
})
// 监听客户端信息
server.on('request', (req, res) => {
res.write('哈哈')
res.end()
})
3.2 服务器响应不同数据类型
// nodemon 检测代码更改, 一旦有代码改变就会重新运行 npm i -g nodemon
// nodemon server.js
// 导入http模块
const http = require('http')
const fs = require('fs')
// 创建服务器
// 获取到服务器的实例对象
const server = http.createServer()
server.listen(3030, () => {
console.log('http://127.0.0.1:3030');
console.log('创建成功');
})
// 监听客户端信息
server.on('request', (req, res) => {
// 第一种发送内容方式
// res.setHeader('Content-Type', 'text/plain;charset=utf-8') // 例子:哈哈
// res.setHeader('Content-Type', 'text/html;charset=utf-8') // 例子: <div>哈哈</div>
// res.write('哈哈')
// res.end()
// 第二种发送内容方式
console.log(req.url, 'req');
if (req.url === '/') {
// 如果路径是当前地址
fs.readFile('./index.html', 'utf-8', (err, data) => {
res.write(data)
res.end()
})
} else {
// 处理其他图片等静态文件
fs.readFile('./1.jpg', (err, data) => {
res.end(data)
})
}
})
// index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div>我是HTML的文件的div内容</div>
<img src="./1.jpg">
<img src="./1.jpg">
</body>
</html>
3.3 HTTP的不同请求方法处理(http方法和响应码)
3.4 接收并处理GET和POST请求入参
// GET 和 POST
// 导入http模块
const http = require('http')
const fs = require('fs')
const url = require('url')
// 创建服务器
// 获取到服务器的实例对象
const server = http.createServer()
server.listen(3030, () => {
console.log('http://127.0.0.1:3030');
console.log('创建成功');
})
// 监听客户端信息
server.on('request', (req, res) => {
// GET请求url后的参数获取
if (req.method === 'GET') {
// url.parse第一个参数是地址, 第二个参数设置成true时返回参query
// 会把路径后面的参数转成对象形式, 比如路径是localhost:3000/user?id=100,
// 那么query的值是{id: 100}
const id = url.parse(req.url, true).query.id // 获取GET请求url后的参数
console.log(url.parse(req.url, true).query);
if (req.url === '/') {
// 如果路径是当前地址
fs.readFile('./index.html', 'utf-8', (err, data) => {
res.write(data)
res.end()
})
} else {
// 处理其他图片等静态文件
fs.readFile('./1.jpg', (err, data) => {
res.end(data)
})
}
} else if (req.method === 'POST') {
let data = '';
// 绑定data事件, 每当有数据的时候都会触发
req.on('data', _data => {
// 每当客户端发送数据过来的时候就存到data上
data += _data // _data是Buffer, 里面是二进制数据
})
// 所有数据都发完之后触发事件
req.on('end', () => {
// 对数据进行转换
const dataString = require('querystring').parse(data);
console.log(dataString);
res.end()
})
}
})
3.5 服务器代码模块化拆分
// server.js
// 导入http模块
const http = require('http')
const router = require('./router')
// 创建服务器
// 获取到服务器的实例对象
const server = http.createServer()
server.listen(3030, () => {
console.log('http://127.0.0.1:3030');
console.log('创建成功');
})
// 监听客户端信息
server.on('request', (req, res) => {
router(req, res)
})
// router.js
const fs = require('fs')
const url = require('url')
const controller = require('./controller')
module.exports = (req, res) => {
// GET请求url后的参数获取
if (req.method === 'GET') {
// url.parse第一个参数是地址, 第二个参数设置成true时返回参query会把路径后面的参数转成对象形式, 比如路径是localhost:3000/user?id=100, 那么query的值是{id: 100}
const id = url.parse(req.url, true).query.id // 获取GET请求url后的参数
console.log(url.parse(req.url, true).query);
if (req.url === '/') {
// 业务逻辑处理: 处理主页的内容
controller.index(res)
} else {
// 处理其他图片等静态文件
fs.readFile('./1.jpg', (err, data) => {
res.end(data)
})
}
} else if (req.method === 'POST') {
let data = '';
// 绑定data事件, 每当有数据的时候都会触发
req.on('data', _data => {
// 每当客户端发送数据过来的时候就存到data上
data += _data // _data是Buffer, 里面是二进制数据
})
// 所有数据都发完之后触发事件
req.on('end', () => {
// 对数据进行转换
const dataString = require('querystring').parse(data);
// 业务逻辑处理: 处理用户数据信息
controller.user(dataString, res)
})
res.end()
}
}
// controller.js
const fs = require('fs')
module.exports = {
index(res) {
// 处理主页信息的返回
fs.readFile('./index.html', 'utf-8', (err, data) => {
res.write(data)
res.end()
})
},
user (data, res) {
// 处理用户数据信息
console.log(data);
}
}
4. Express框架
4.1 项目构建和使用及管理用户数据信息(GET和POST)
// 初始化项目, 先创建express-fm文件夹, 然后打开这个文件夹的终端, 执行下面指令
npm init -y
npm i express
// 创建express脚手架工具, 使用下面指令
npx express-generator
npm i
// app.js
const express = require('express')
const fs = require('fs')
const {promisify} = require('util')
// 对fs.readFile进行Promise化
const readFile = promisify(fs.readFile)
// 获取express全局应用
const app = express();
// 接收到客户端发送过来的类型数据
app.use(express.urlencoded()) // 用于接收x-www-form-urlencodeed
app.use(express.json()) // 用于接收json
// 监听get请求, 第一个参数是请求路径为/,
app.get('/', async (req, res) => {
// 读取db.json文件
try {
const data = await readFile('./db.json', 'utf8')
console.log(res);
// 如果没报错, 发送数据
// 对data数据进行整理, 只需要返回 users 的数据
const back = JSON.parse(data)
res.send(back.users)
} catch (error) {
res.status(500).json({error})
}
})
// 监听POST请求, 第一个参数是请求路径为/,
app.post('/', (req, res) => {
// 获取客户端不同类型入参
console.log(req.body); // 必须使用app.use对数据类型进行处理
})
// 对服务器进行监听
app.listen(3030, () => {
console.log('Run http://127.0.0.1:3030');
})
4.2 添加用户信息到db.json文件(对文件进行读取(get)和写入(post))
// 对文件进行读取和写入项目
// GET请求: http://127.1.1.0:3030
// POST请求: http://127.1.1.0:3030, 格式json: {"username": "hony","age": 12}
// app.js
const express = require('express')
const db = require('./db')
// 获取express全局应用
const app = express();
// 接收到客户端发送过来的类型数据
app.use(express.urlencoded()) // 用于接收x-www-form-urlencodeed
app.use(express.json()) // 用于接收json
// 监听get请求, 第一个参数是请求路径为/,
app.get('/', async (req, res) => {
// 读取db.json文件
try {
const data = await db.getDb()
// 如果没报错, 发送数据
// 对data数据进行整理, 只需要返回 users 的数据
res.send(data.users)
} catch (error) {
res.status(500).json({error})
}
})
// 监听POST请求, 第一个参数是请求路径为/,
app.post('/', async (req, res) => {
// 获取客户端不同类型入参
const body = req.body;
if (!body) {
res.status(403).json({error: '缺少客户信息'})
}
// 添加
const dataObj = await db.getDb()
body.id = dataObj.users[dataObj.users.length - 1].id + 1;
dataObj.users.push(body)
// 对db.json数据进行添加, 使用文件写入
try {
const r = await db.serveDb(dataObj)
if (!r) {
// 写入成功时, r的值为undefined, 给一个响应为200
res.status(200).send({code: 8, msg: '添加成功'})
} else {
// 有错误的情况
}
} catch (error) {
res.status(500).json({error})
}
})
// 对服务器进行监听
app.listen(3030, () => {
console.log('Run http://127.0.0.1:3030');
})
// db.js
// 该文件用户读取文件和写入操作
const fs = require('fs')
const {promisify} = require('util')
// 对fs.readFile进行Promise化
const readFile = promisify(fs.readFile)
const writeFile = promisify(fs.writeFile)
exports.getDb = async () => {
const data = await readFile('./db.json', 'utf8')
return JSON.parse(data);
}
exports.serveDb = async (data) => {
const stringData = JSON.stringify(data)
return await writeFile('./db.json', stringData)
}
// db.json
{
"users": [
{
"username": "Li Lei",
"age": 18,
"id": 1
}
]
}
4.3 修改用户信息(PUT)
// app.js
const express = require('express')
const db = require('./db')
// 获取express全局应用
const app = express();
// 接收到客户端发送过来的类型数据
app.use(express.urlencoded()) // 用于接收x-www-form-urlencodeed
app.use(express.json()) // 用于接收json
// 修改用户信息
app.put('/:id', async (req, res) => {
let id = req.params.id; // 获取url后面的参数id值
const body = req.body; // 获取到body数据 这里使用的是json格式的
id = Number.parseInt(id);
try {
const userInfo = await db.getDb();
const user = userInfo.find(item => item.id === id)
if (!user) {
// 如果用户不存在
res.status(403).json({error: '用户信息不存在'})
}
// 存在则替换
user.username = body.username ? body.username : user.username // 客户端可以传username也可以不传
user.age = body.age ? body.age : user.age // 客户端可以传age也可以不传
// 替换userInfo内对应的id内容
userInfo.forEach(item => {
if (item.id === id) {
item.username = user.username
item.age = user.age
}
});
// 然后写入操作
const r = await db.serveDb(userInfo)
if (!r) {
res.status(201).json({msg: '修改成功'})
}
} catch (error) {
res.status(500).json({error})
}
})
// 对服务器进行监听
app.listen(3030, () => {
console.log('Run http://127.0.0.1:3030');
})
5. MongoDB数据库进行数据持久化存储
5.1 在不同操作系统中安装MongoDB(偶数稳定版, 基数测试版)
// 下载mongoDB地址:
https://www.mongodb.com/try/download/community
// 安装时下载路径尽量不去动, 因此会下载在c盘, 注意mongoDB运行环境, 要配置环境变量, 具体下方用图片展示说明
5.2 第三方客户端Navicat链接MongoDB服务器
// 安装navicat地址:
https://www.navicat.com.cn/products
5.3 使用navicat创建一个新的数据库以及一些常见指令
// 常见一些指令, 在运行内测试
// use mytest
// db.cc.insert({x: 1, y: 2}) // 创建cc
// db.ff.insert({name: 'lilei'}) // 创建ff
// db.ff.drop() // 删除ff
// db.dropDatabase() // 删除mytest
// show collections // 查看mytest集合名称 cc ff
5.4 MongoDB基本增删改查操作
// 基本操作
// use mytest
// show collections // 查看mytest集合名称 cc ff
// db.ff.drop() // 删除ff
// db.dropDatabase() // 删除mytest
// 添加数据操作
// db.cc.insert({x: 1, y: 2}) // 创建cc
// db.ff.insert({name: 'lilei'}) // 创建ff
// db.cc.insertOne({username: 'haha', age: 12}) // 往cc插入一条数据
// db.cc.insertMany([ // 插入多条数据
// {username: 'haha1', age: 121},
// {username: 'haha2', age: 122},
// {username: 'haha3', age: 123}
// ])
// 查找数据操作
// db.cc.find() // 查找cc内所有数据
// db.cc.find({username: 'haha'}) // 查找cc内username的值为haha的数据
// db.cc.find({age: {$gt:15}}) // 查找cc内age大于15的数据
// db.cc.findOne({age: {$gt:15}}) // 查找cc内age大于15的数据, 且只要一条数据
// 修改数据操作
// 修改满足条件的一条数据
// db.cc.updateOne({username: 'haha1'}, {$set:{age:30}}) // 找到username为haha1的数据, 将age改成18 (原数据是{username: 'haha1', age: 12})
// 修改满足条件的多条数据
// db.cc.updateMany({age: {$gt:15}}, {$set:{username: 'Monica'}}) // 把年龄大于15的数据, 都改成Monica
// 删除数据操作
// db.cc.deleteOne({age: 12}) // 删除年龄等于12的一条数据
db.cc.deleteMany({age: {$gt:30}}) // 删除年龄大于30的多条数据
// 根据ID找到数据并更新这条数据
db.cc.findByIdAndUpdate(id, [{...}], {new: true}) // 第三个参数{new: true}不设置的话返回值是旧数据
5.5 使用Node链接MongoDB
// 安装mongodb
npm i mongodb
// index.js 进行连接数据库操作
const {MongoClient} = require('mongodb')
// 第一个参数是mongodb的地址
const client = new MongoClient('mongodb://127.0.0.1:27017')
// 连接数据库
const main = async () => {
// 链接操作
await client.connect();
// 拿到数据库名字
const db = client.db('mytest')
// collection是操作的具体的集合
const cc = db.collection('cc')
// 查找
const d = await cc.find()
const array = await d.toArray() // 将数据转换成数据, 是一个异步操作
console.log(array);
}
main()
5.6 使用Node对MongoDB进行增删改查操作
const {MongoClient} = require('mongodb')
const client = new MongoClient('mongodb://127.0.0.1:27017') // 第一个参数是mongodb的地址
const clientFun = async (c) => {
await client.connect(); // 连接操作
const db = client.db('mytest') // 拿到数据库名称
return db.collection(c) // 操作的具体集合
}
// 连接数据库
const main = async () => {
const cc = await clientFun('cc')
console.log(cc);
// 查询操作
// const d = await cc.findOne({x: {$gt:1}}) // 查找x大于1的一条数据
// console.log(d);
// const d = await cc.find({x: {$gt:1}}) // 查找x大于1的多条数据
// const array = await d.toArray() // 将数据转换成数据, 是一个异步操作
// console.log(array);
// 添加操作
// const d = await cc.insertOne({x: 2, y: 2}) // 插入一条数据
// console.log(d);
// const e = await cc.insertMany([{x: 3, y: 3}, {x: 4, y: 4}]) // 插入多条数据
// 更新操作
// const d = await cc.updateOne({x: {$gt: 1}}, {$set: {y: 22}}) // 将x大于1的一条数据更新y的值
// console.log(d);
// const d = await cc.updateMany({x: {$lt: 3}}, {$set: {y: 33}}) // 将x小于2的多条数据, 将y更新成11
// console.log(d);
// 删除操作
// const d = await cc.deleteOne({x: {$lt: 2}}) // 删除x小于2的一条数据
// console.log(d);
// const d = await cc.deleteMany({x: {$lt: 2}}) // 删除x小于2的所有数据
// console.log(d);
// 根据ID找到数据并更新这条数据
cc.findByIdAndUpdate(id, [{...}], {new: true}) // 第三个参数{new: true}不设置的话返 回值是旧数据
}
main().finally(() => client.close())
6. Express中间件
6.1 中间件的基本使用(含处理服务端代码报错,路由地址404等基本用法)
// app.js
const express = require('express')
const app = express()
const router = require('./router')
// const videoRouter = require('./router/video')
const PORT = process.env.PORT || 3000
// 中间件
// app.use((req, res, next) => {
// console.log(req.method, req.url, new Date().getTime());
// next(); // next() 执行完后交给下一步请求操作
// })
// 第一种方法
// 在函数里面使用next后, 后面可以继续使用函数, 直到最后一个不需要next了就直接发送send
// app.get('/user', (req, res, next) => {
// console.log(req.method);
// next()
// }, (req, res, next) => {
// console.log(req.method);
// next()
// }, (req, res) => {
// res.send('/user')
// })
// 第二种方法, 先匹配/user, 然后在匹配router的路径
app.use('/user', router)
// app.use('/video', videoRouter) // 可以使用多个use匹配路径
// 全局处理接口地址不存在的情况
app.use((req, res, next) => {
res.status(404).send("404 Not Found")
})
// 错误处理中间件, 有4个参数则代表是错误处理中间件
app.use((err, req, res, next) => {
res.status(500).send('service Error')
})
// 监听端口号
app.listen(PORT, () => {
console.log(`Serve is running at http://127.0.0.1:${PORT}`);
})
// router.js
const express = require('express')
const router = express.Router()
router.get('/users', (req, res, next) => {
console.log(req.method);
next()
}, (req, res, next) => {
console.log(req.method);
next()
}, (req, res, next) => {
res.send('/user')
})
router.get('/', (req, res, next) => {
console.log(req.method);
next()
}, (req, res, next) => {
console.log(req.method);
next()
}, (req, res, next) => {
res.send('/')
})
module.exports = router
6.2 Express路由和响应方法
const express = require('express')
const app = express()
const PORT = process.env.PORT || 3000
// 处理所有请求方式写法, 如GET POST PUT...
// app.all('/xx', (req, res) => {
// res.send('get post put等所有方法都能返回')
// })
// ?表示问号前面的第一个单词可写可不写, 如果我请求一个uer的地址, 也不会报错, 地址就是/uer
// app.get('/us?er', (req, res) => {
// res.send(req.url)
// })
// // +表示加号后面可以重复添加多个字母, 比如下方这个例子, 加号前面的字母是s所以只能写s, 比如usssssssser, 而不是useeeeer
// app.get('/us+er', (req, res) => {
// res.send(req.url)
// })
// *号表示通配符, 只能在*前面和后面一个字幕添加, 比如地址是 usfnnfenfenfenfener, usfnnfenfenfenfene2r这种就不行
// app.get('/user', (req, res) => {
// res.send(req.url)
// })
// 获取url后面的参数
app.get('/user/:id/video/:vid', (req, res) => {
// 地址: http://127.0.0.1:3000/user/1/video/10
console.log(req.params); // {id: 1, vid: 10}
res.send(req.url)
})
app.listen(PORT, () => {
console.log(`Serve is running at http://127.0.0.1:${PORT}`);
})
7. MongoDB实战(使用了mongoose)
7.1 项目基础设计搭建优化
使用这个接口地址验证自己写的有没有问题:
http://127.0.0.1:3000/api/v1/user/users
// app.js
const express = require('express')
const cors = require('cors')
const morgan = require('morgan')
const router = require('./router')
const app = express()
const PORT = process.env.PORT || 3000
// 解析客户端请求, json和form格式
app.use(express.json())
app.use(express.urlencoded())
// 处理跨域问题
app.use(cors())
// 记录日志, 开发模式下传入dev, 代表开发模式下记录日志
app.use(morgan('dev'))
// 使用路由, 并对路由进行封装
app.use('/api/v1', router)
app.listen(PORT, () => {
console.log(`Serve is running at http://127.0.0.1:${PORT}`);
})
// router.js
const express = require('express')
const router = express.Router()
router.use('/user', require('./user'))
module.exports = router
// user.js
const express = require("express");
const userController = require('./userController')
const router = express.Router();
router.get("/users", userController.list)
.get("/", userController.current);
module.exports = router
// userController.js
// 对user的业务逻辑处理
exports.list = async (req, res) => {
console.log(req.method);
res.send(req.url)
}
exports.current = async (req, res) => {
console.log(req.method);
res.send(req.url)
}
7.2 mongoose基本使用
const mongoose = require('mongoose')
async function main () {
// mongodb数据库地址
await mongoose.connect('mongodb://localhost:27017/mytest')
}
// 链接mongodb数据库地址
main().then(res => {
console.log('mongo链接成功');
}).catch(err => {
console.log(err);
console.log('mongo链接失败');
})
// 得到模型, 设置集合类型 Schema: 模式
const user = new mongoose.Schema({
username: {
type: String,
require: true
},
age: {
type: Number,
require: true
}
})
// 操作模型, 第一个参数是集合的名字, 第一个字母习惯以大写的方式来写
// 如果不存在这个集合, 会对创建这个单词后面加s的集合, 比如这个例子, 不存在就自动创建users集合
const userModel = mongoose.model('User', user)
// 保存数据
const u = new userModel({username: 'lilei', age: 18})
u.save()
7.3 用户注册数据入库
// model/userController.js
// 第一步, 设置字段(前提是链接成功后, 才叫第一步)
const mongoose = require('mongoose')
// 设置用户需要哪些字段
const userSchema = new mongoose.Schema({
username: {
type: String,
required: true
},
age: {
type: String,
required: true
},
createTime: {
type: Date,
default: Date.now
}
})
module.exports = userSchema
// model/index.js
const mongoose = require('mongoose')
async function main () {
// mongodb数据库地址
await mongoose.connect('mongodb://localhost:27017/express-user') // express-user 库的名字
}
// 链接mongodb数据库地址
main().then(res => {
console.log('mongo链接成功');
}).catch(err => {
console.log(err);
console.log('mongo链接失败');
})
// 第二步, 导出模型
module.exports = {
User: mongoose.model('User', require('./userController'))
}
// 第三步, 有接口调用时, 进行保存
// 这个是router.post("/registry", userController.registry)监听到调用接口时的业务逻辑
const router = express.Router();
const { User } = require('./model/index')
exports.registry = async (req, res) => {
const userModel = new User(req.body)
const data = await userModel.save();
res.status(201).json(data)
}
7.4 用户注册密码加密问题和剔除某个字段后返回给客户端(md5加密)
// md5.js
const crypto = require('crypto') // node内置模块无需安装
// 以md5的方式进行加密, digest的第一个参数代表加密算法的方式,
module.exports = str => {
return crypto.createHash('md5').update(`by${str}`).digest('hex')
}
// userController.js内设置的密码(password), 因此在这里设置就可以了
const mongoose = require('mongoose')
const md5 = require('../md5')
const baseModel = require('./baseModel')
// 设置用户需要哪些字段
const userSchema = new mongoose.Schema({
username: {
type: String,
required: true
},
password: {
type: String,
required: true,
select: false, // 这个字段不返回给前端, 剔除某个字段使用
set: val => md5(val) // 对密码进行md5加密, 加这个就可以了
},
age: {
type: String,
required: true
},
// createTime这个在很多模块都可能被使用到, 因此封装到baseModel内, 然后各个页面引入使用即可
...baseModel
// createTime: {
// type: Date,
// default: Date.now
// }
})
module.exports = userSchema
// baseModel.js
module.exports = {
createTime: {
type: Date,
default: Date.now
}
}
// 业务逻辑, 返回的信息不能含有密码的明文
const { User } = require('./model/index')
exports.registry = async (req, res) => {
const userModel = new User(req.body)
const data = await userModel.save();
const dataJSON = data.toJSON()
// 删除返回给前端的password参数
delete dataJSON.password;
res.status(201).json({
user: dataJSON
})
}
7.5 客户端提交数据安全校验(使用express-validator中间件)
// 在使用路由的位置进行处理
const express = require("express");
const userController = require("./userController");
const router = express.Router();
const userValidator = require('./middleware/validator/userValidator')
// 在路由当中进行匹配
const { body, validationResult } = require("express-validator");
// 对用户传过来的username判断是否为空
router.post("/registry",
userValidator.registry,
// 在errorBack.js进行统一验证错误处理了, 所以这里可以不用写
// (req, res, next) => {
// const errors = validationResult(req)
// console.log(errors, 'errors');
// // 判断是否存在错误信息
// if (!errors.isEmpty()) {
// // 有错误则返回
// return res.status(401).json({error: errors.array()})
// }
// // 没有错误则继续执行下面的function userController.registry
// next()
// },
userController.registry);
// .get("/users", userController.list)
// .get("/", userController.current);
module.exports = router;
// 上面的注释是因为封装等相关校验所以去掉了, 原始写法是上面那种, 我这里重新再整理一份没注释的
const express = require("express");
const userController = require("./userController");
const router = express.Router();
const userValidator = require("./middleware/validator/userValidator");
// 对用户传过来的username判断是否为空
router.post("/registry", userValidator.registry, userController.registry);
// .get("/users", userController.list)
// .get("/", userController.current);
module.exports = router;
// validator/userValidator.js
const {body, validationResult} = require('express-validator')
const validate = require('./errorBack')
// bail表示验证通过则继续往下走, 不通过就不往下验证
module.exports.registry = validate([
body('username')
.notEmpty().withMessage('用户名不能为空').bail()
.isLength({min: 3}).withMessage('用户名长度不能小于3'),
body('email')
.notEmpty()
.isEmail()
])
// validator/errorBack.js
// 专门处理验证错误消息
const {validationResult} = require('express-validator')
module.exports = validator => {
return async (req, res, next) => {
await Promise.all(validator.map(validate => validate.run(req)))
const errors = validationResult(req)
if (!errors.isEmpty()) {
return res.status(401).json({error: errors.array()})
}
next()
}
}
7.6 数据唯一性验证
const {body, validationResult} = require('express-validator')
const validate = require('./errorBack')
const {User} = require('../../model/index')
// bail表示验证通过则继续往下走, 不通过就不往下验证
module.exports.registry = validate([
body('username')
.notEmpty().withMessage('用户名不能为空').bail()
.isLength({min: 3}).withMessage('用户名长度不能小于3').bail()
// 数据唯一性验证书写位置
.custom(async val => {
const usernameValidate = await User.findOne({username: val})
if (usernameValidate) {
return Promise.reject('邮箱已被注册')
}
}),
body('email')
.notEmpty().bail()
.isEmail().bail()
])
7.7 Restful API接口设计规范
7.8 JWT用户身份认证(jsonwebtoken)
// jwt 的基本使用
const jwt = require('jsonwebtoken')
// 生成jwt, 第一个是加密的值, 第二个参数是自己添加的私钥
const token = jwt.sign({foo: 'hello'}, '555')
console.log(token);
const res = jwt.verify(token, '555')
console.log(res); // iat 是有效的时间戳
// 登录接口用法
const { User } = require('./model/index')
const jwt = require("jsonwebtoken")
// 注册
exports.registry = async (req, res) => {
const userModel = new User(req.body)
const data = await userModel.save();
const dataJSON = data.toJSON()
// 删除返回给前端的password参数, mongoose.Schema类型添加select: false是在查询时去除这个内容, 这里无效所以要自己删除
delete dataJSON.password;
res.status(201).json({
user: dataJSON
})
}
// 登录
exports.login = async (req, res) => {
let dbBack = await User.findOne(req.body)
if (!dbBack) {
return res.status(402).json({error: '邮箱或者密码不正确'})
}
// 登录成功, 给用户生成token并返回给客户端
dbBack = dbBack.toJSON()
dbBack.token = await createToken(dbBack)
res.status(200).json(dbBack)
}
const {promisify} = require('util')
const toJwt = promisify(jwt.sign)
const verify = promisify(jwt.verify)
// a808888888888888888888888888是随机数, 最好使用uuid去随机生成或者自己创建一个文件夹用来管理这个随机字符串
// 创建token
async function createToken(userInfo) {
// expiresIn 60 表示60秒后过期
await toJwt({userInfo}, 'a808888888888888888888888888', {expiresIn: 60})
}
// 验证token
async function verifyToken(req, res, next) {
let token = req.headers.authorization
token = token ? token.split('Bearer ')[1] : null
if (!token) {
res.status(402).json({error: '请传入token'})
}
try {
const userInfo = await verify(token, 'a808888888888888888888888888')
next()
} catch (error) {
res.status(402).json({error: '无效的token'})
}
}
9. fs读写模块
const fs = require('fs')
// 读取文件内容
fs.readFile('./a.txt', 'utf8', (err, data) => {
console.log(err);
console.log(data);
})
// 写入文件内容
fs.writeFile('./a.txt', 'aaaaaaa', (e) => {
console.log(e);
})
// 追加文件内容
fs.readFile('./a.txt', 'utf8', (e, data) => {
if (!e) {
const newData = `${data}???`;
fs.writeFile('./a.txt', newData, e => {
if (!e) {
console.log('追加内容成功啦');
}
})
}
})
// 读取文件夹
const files = fs.readdirSync('books')