从0学习node,并完成接口的创建使用

227 阅读9分钟

工具:node.js vscode apifox

一、使用express框架搭建框架

  • 了解express

    Express它是一个NodeJS平台下面的框架,主要用于构于Web服务器项目,它是一个第三方的模块

  • 安装特定版本express

    npm i express@4.17.1 or yarn add express@4.17.1 or npm i express or yarn add express

  • 初始化项目

    npm init --yes or yarn init --yes //加了--yes以后,它会直接帮你生成,不再询问你的配置信息

二、了解可能用到的依赖

名称

版本

作用

nodemon

-

npm i -g nodemon or npm install -global nodemon 用来监视node.js应用程序中的任何更改并自动重启服务

express

4.17.1

npm i express@4.17.1 Express它是一个NodeJS平台下面的框架,主要用于构于Web服务器项目,它是一个第三方的模块

cors

2.8.5

npm i cors@2.8.5 处理跨域问题

MySQL

2.18.1

npm i mysql 通过这个模块可以与MySQL数据库建立连接、执行查询等操作

sm-crypto

-

npm i sm-crypto 加密解密

bcryptjs

2.4.3

npm i bcryptjs 加密解密

express-jwt

5.3.3

npm install express-jwt 验证指定http请求的JsonWebTokens的有效性,如果有效就将JsonWebTokens的值设置到req.user里面,然后路由到相应的router

multer

1.4.2

npm i multer@1.4.2 解析表单数据

三、开始nodejs服务器的创建

  • 1.创建app.js作为启动节点

    touch app.js

  • 2.对app.js进行添加内容

    • 导入express并初始化app.js

    // 导入 express const express = require('express') // 创建服务器的实例对象 const app = express()

    /*

    • 在创建和启动中间添加中间件、路由、错误中间件等

    • 错误中间件:在启动服务器的上面,其他中间件、路由的下面 */ /存放中间件/

    /存放路由/

    /存放错误中间件/

    // 启动服务器 app.listen(9000, () => { console.log('api server running at http://127.0.0.1:9000') }) // 通过nodemon启动服务器 nodemon ./app.js

  • 添加一个get路由

    app.get('/', (req, res) => { // 通过res.send方法返回给前端数据 // res对象 www.jc2182.com/nodejs/node… res.send(res.ResultConfig("获取127.0.0.1:9000/数据成功").success()) })

  • 导入cors中间件

    // 安装依赖:npm i cors@2.8.5

    // 导入 cors 中间件 const cors = require('cors') // 将 cors 注册为全局中间件 app.use(cors())

  • 添加封装统一返回结果中间件【在路由之前】

    • 1.新建utils/public.js存放统一封装的方法

    module.exports = class ReturnConfig { code; data; msg; constructor([...args]) { if (args.length == 1) { this.data = ""; this.msg = args[0]; } if (args.length == 2) { this.data = args[0]; this.msg = args[1]; } if (args.length == 3) { this.code = args[0]; this.data = args[1]; this.msg = args[2]; } } success() { return { code: this.code || 0, data: this.data, msg: this.msg }; } error() { return { code: this.code || -1, data: this.data, msg: this.msg }; } publicError() { return { code: this.code || -1, data: "未知错误", msg: this.msg }; } publicUserError() { return { code: this.code || -1, data: "请确认参数传递方式", msg: this.msg }; } }

    • 2.导入返回结果中间件

    // 一定要在路由之前,封装 统一返回结果 函数 const ReturnConfig = require("./utils/public") app.use((_, res, next) => { res.ResultConfig = function () { return new ReturnConfig(arguments); } next() })

  • 修改"/"路由使用统一的封装方法

    app.get('/', (req, res) => { res.send(res.ResultConfig("成功通过get方式访问路由为'/'的接口").success()) })

  • 错误级别中间件

    • 定义错误级别中间件

    // 不定义错误级别中间件报错会暂停服务 app.use((err, req, res, next) => { // 身份认证失败后的错误 if (err.name === 'UnauthorizedError') return res.send(res.ResultConfig("401", err.message, "身份认证失败").error()) // 未知的错误 if (err) return res.send(res.ResultConfig(err, "未知错误").error()); })

    • 测试可能出现的错误

      • 1.将错误中间件中用不到的参数req、next换成 _ 【下划线】

    English:SyntaxError: Duplicate parameter name not allowed in this context Chinese:语法错误:在此上下文中不允许重复的参数名称

    • 2.在接口中的异步操作数据库时,在外层使用send发送会数据【程序崩溃】

    报错:[nodemon] app crashed - waiting for file changes before starting...[应用程序崩溃-正在等待文件更改,然后再启动] 解决方法: // 在错误级别中间件上方添加 process.on('uncaughtException', function (err, res) { console.log('Caught exception: ', err); });

  • 导入路由模块

    • 创建router/index.js router/user/index.js两个文件

      • router/user/index.js

    // 获取所有人员列表 router.get("/selectAll", (req, res) => { return res.send(res.ResultConfig(result, "获取成功").success()) }) // 将路由对象共享出去 module.exports = router

    • router/index.js

    const userRouter = require('./user/index') module.exports = { userRouter };

    • 将user路由添加到中间件

    // 导入用户路由模块 const Router = require('./router/index') app.use('/user', Router.userRouter)

  • 3.添加MySQL相关内容

    • 安装依赖

    npm i mysql

  • 新建db/index.js

    const mysql = require('mysql')

    const db = mysql.createPool({ host: '127.0.0.1', user: 'root', password: '2306545528', database: 'ningblog', })

    module.exports = db

  • 使用数据库可能出现的错误

    错误: English:ER_NOT_SUPPORTED_AUTH_MODE: Client does not support authentication protocol requested by server; consider upgrading MySQL client Chinese:English:ER_NOT_SUPPORTED_AUTH_MODE: 客户端不支持服务器请求的身份验证协议;考虑升级MySQL客户端 原因:mysql8.0以上加密方式,Node还不支持。 解决: alter user 'root'@'localhost' identified with mysql_native_password by '1234';

  • 优化user路由中的获取所有人员列表

    • 添加查询数据库操作

    // 获取所有人员列表 router.get("/selectAll", (req, res) => { const getUserDetailSQL = select id, name, head_image ,iphone, creat_time from user db.query(getUserDetailSQL, "", (err, result) => { if (err) { return res.send(res.ResultConfig(err.message).publicError()) } return res.send(res.ResultConfig(result, "获取成功").success()) }) // 在条用数据库时,不可以使用send发送数据,会导致程序崩溃 // return res.send(res.ResultConfig(result, "获取成功").success()) })

    • 可能出现的问题
  • 4.添加jwt鉴权

    • 创建config/jwtConfig.js

    module.exports = { jwtSecretKey: 'NingBest.', }

  • 配置解析token中间件【!不同版本的jwt使用方法不同!】

    // 配置解析 Token 的中间件 npm install express-jwt@5.3.3[不同的jwt会导致用法不同] // 配置后默认需要在请求头中添加Authorization:Bearer token const expressJWT = require('express-jwt') const jwtConfig = require("./config/jwtConfig.js") // 可以使用正则表达式进行匹配 /^/api/ --> [/^/api/] app.use(expressJWT({ secret: jwtConfig.jwtSecretKey }) .unless({ path: ["/", "/user/loginUser", "/user/register", /^/profile/upload/[\s\S]{1,}/, "/file/download" ] }))

  • 再次调用获取所有人员列表接口

    • 配置中间件:

    { "code": "401", "data": "No authorization token was found", "msg": "身份认证失败" }

    • 不配置中间件:

  • 注意点

    使用: 请求头中添加的token会自动解析,并且存放在req.user中 req.user = { xxxxx, iat:1652159597,exp: 1653023597} iat: jwt的签发时间 exp: jwt的过期时间 前端使用: Authorization:Bearer token 注: 不同版本的jwt使用方法不同

  • 5.完善用户表其他接口

    • 注册新用户

      • user/index.js注册新用户

    // 注册新用户 router.post('/register', (req, res) => { const userinfo = req.body if (!userinfo.name) return res.send(res.ResultConfig().publicUserError()) const getUserDetailSQL = select * from user where name=? const setUserDetailSQL = insert into user set ?; // 查询是否存在相同用户名 db.query(getUserDetailSQL, userinfo.name, function (err, result) { if (err) { res.send(res.ResultConfig(err.message).publicError()) } if (result.length > 0) { return res.send(res.ResultConfig("该用户名已存在").error()) } db.query(setUserDetailSQL, { name: userinfo.name, password: userinfo.password }, function (err, result) { if (err && results.affectedRows !== 1) { return res.send(res.ResultConfig(err.message, "新增失败").error()) } return res.send(res.ResultConfig("新增成功").success()) }) }) })

    • 注意点

      • app.js

    // 配置解析表单数据的中间件,注意:这个中间件,只能解析 application/x-www-form-urlencoded 格式的表单数据 // app.use(express.urlencoded({ extended: false })) // 通过 express.json() 这个中间件,解析表单中的 JSON 格式的数据 app.use(express.json())

    • index.js

    //前端发送通过json发送的数据存放在req.body中 db.query方法 第一个参数:MySQL语句[string] 第二个参数:any[eg:number|string|[object,number]] 第三个参数:回调函数[参数1,参数2] 参数1:报错信息err 参数2:返回结果

  • 登录

    • user/index.js登录

    // 登录 router.post('/loginUser', (req, res) => { const userinfo = req.body console.log('userinfo: ', userinfo); // 导入jwt const config = require("../../config/jwtConfig")

    const getUserDetailSQL = select * from user where name=? db.query(getUserDetailSQL, userinfo.name, function (err, result) { if (err) { res.send(res.ResultConfig(err.message, "登陆失败").error()) } const user = result[0]; // 生成 Token 字符串 const tokenStr = jwt.sign({ id: user.id, name: user.name, head_image: user.head_image, iphone: user.iphone, creat_time: user.creat_time }, config.jwtSecretKey, { expiresIn: '240h', // token 有效期 }) // 解密 if (userinfo.password == user.password) { res.send(res.ResultConfig(tokenStr, "登陆成功").success()) } else { res.send(res.ResultConfig("登陆失败").error()) } }) })

    • 注意点

      • token

    // 生成 Token 字符串 const tokenStr = jwt.sign(user, config.jwtSecretKey, { expiresIn: timeout, // token 有效期 }) eg: user:{ id: user.id, name: user.name } timeout:'240h' eg: const tokenStr = jwt.sign({ id: user.id, name: user.name }, config.jwtSecretKey, { expiresIn: '240h', // token 有效期 })

    • 密码

    //前端加密,后端解密。

    //md5加密:完全相同的一段数据,不论时间地点

    //正规做法:前端加密,后端解密,后端通过自己的加密方式再次进行加密,存储到数据库 eg: 一:npm i sm-crypto 加密:sm4.encrypt(privateMsg, "2306545528NingBest19981222230654") 解密:sm4.decrypt(privateMsg, "2306545528NingBest19981222230654") 二:npm i bcryptjs@2.4.3 加密:userinfo.password = bcrypt.hashSync(userinfo.password, 10) 判断:bcrypt.compareSync(用户提交的密码, 数据库中的密码)

  • 获取指定人的个人信息

    • user/index.js获取指定人的个人信息

    // 获取指定人的个人信息 router.get("/getUserDetail/:id", (req, res) => { const id = req.params.id; const getUserDetailSQL = select id, name, head_image ,iphone, creat_time from user where id=? db.query(getUserDetailSQL, id, (err, result) => { if (err) { return res.send(res.ResultConfig(err.message).publicError()) } return res.send(res.ResultConfig(result[0], "获取成功").success()) }) })

    • 注意点

      • 获取对应参数

    通过/getUserDetail/2方式传参[/getUserDetail/:id] 获取:res.params.id

  • 添加公共方法

    • 创建utils/index.js

    const dayjs = require('dayjs') function getNowTime() { return dayjs(new Date()).format("YYYY-MM-DD HH:mm:ss"); }

    module.exports = { getNowTime }

  • 6.添加上传下载文件

    • 创建router/upload/index.js

      • router/upload/index.js

    const express = require('express') // 创建路由对象 const router = express.Router() const db = require("../../db/index") // 导入解析 formdata 格式表单数据的包 const multer = require('multer') // 导入处理路径的核心模块 const path = require('path') // 创建 multer 的实例对象,通过 dest 属性指定文件的存放路径 const upload = multer({ dest: path.join(__dirname, '../../upload') }) const fs = require("fs") //注:导入的db模块未使用,未添加数据库表进行上传记录

    // 将路由对象共享出去 module.exports = router

    • app.js

    //配置静态资源目录中间件[通过域名可以访问其中的文件] app.use("/profile/upload", express.static("upload"))

  • 添加上传文件

    • 上传文件

    // 上传头像/文件 router.post('/upload', upload.single('file'), (req, res) => { //读取路径(req.file.path) const size = req.file.size / 1024 / 1024; if (size > 200) { return res.send(res.ResultConfig("", "文件过大,上传失败").error()) } // if(req.file.size > ) fs.readFile(req.file.path, (err, data) => { //读取失败,说明没有上传成功 if (err) { return res.send('上传失败') } const suffix = "." + req.file.originalname.split(".")[1]; const newName = "Ning" + Date.parse(new Date()) + suffix; fs.writeFile(path.join(__dirname, '../../upload/' + newName), data, (err) => { if (err) { return res.send('写入失败') } fs.unlink(path.join(__dirname, '../../upload/' + req.file.filename), (err) => { }) res.send(res.ResultConfig("/profile/upload/" + newName, "上传成功").success()) }) }) })

    • 注意点

    1.使用fs模块进行对文件的操作 2.限制文件大小 3.上传时进行对文件的重命名 4.重命名后重新写入一个相同的文件 5.写入成功后,在其中删除最初的文件

  • 添加下载文件

    • 下载文件

    // 下载头像/文件 router.get("/download", (req, res) => { const filePath = path.join(__dirname, req.query.name) const name = req.query.name.replace(//profile/upload//, "") const realPath = filePath.replace(/\router\upload\profile/, "") fs.readFile(realPath, (fileErr, fileData) => { if (fileErr) return res.send(res.ResultConfig("", "文件读取失败").error()) res.writeHead(200, { 'Content-Type': 'application/octet-stream', //告诉浏览器这是一个二进制文件
    'Content-Disposition': 'attachment; filename=' + name, //告诉浏览器这是一个需要下载的文件
    }) res.end(fileData); }) })

    • 注意点

    1.替换公共前缀 2.找到真正的路径地址 3.通过fs模块的readFile方法读取文件 4.添加head,并且动态写入name[浏览器下载的默认文件名] 5.通过res.end发送数据

  • 注意点

    使用 multer 解析表单数据:npm i multer@1.4.2

  • 7.MySQL语句

    • 添加测试MySQL接口

      • 在user接口中添加测试MySQL接口【/router/user/index.js】

    // 测试MySQL接口 router.get("/test", (req, res) => { const obj = { selectAll: { mysql: select * from user, params: 1 }, selectSome: { mysql: select id,name from user, params: 1 }, insert: { mysql: insert into user set ?, params: { name: "测试", password: "123456", head_image: "测试", } }, update: { mysql: update user set ? where id = ?, params: [ { name: "修改后的name", head_image: "修改后的头像", }, 10000014 ] }, delete: { mysql: delete from user where id =?, params: 10000014 }, } let name = "selectAll" // name = "selectSome" // name = "insert" // name = "update" // name = "delete" db.query(obj[name].mysql, obj[name].params, (err, result) => { if (err) { return res.send(res.ResultConfig(err.message).publicError()) } return res.send(res.ResultConfig(result, name + "成功").success()) }) })

  • 基本语句

    查询select 插入insert 更新update 删除delete

  • 8.注意点

    • nodeJs中res.end和res.send 区别

    官方说明: res.end() 终结响应处理流程。 res.send() 发送各种类型的响应。 res.end() 只接受服务器响应数据,如果是中文则会乱码 res.send() 发送给服务端时,会自动发送更多的响应报文头,其中包括 Content-Tpye: text/html; charset=uft-8,所以中文不会乱码

  • db.query是异步操作

  • 在通过db.query请求时,中间传参不能直接使用res.params.id,需要使用中间变量