从0学习node,并完成接口的创建使用【包含登录、注册、上传下载】

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

目录

一、使用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应用程序中的任何更改并自动重启服务
express4.17.1npm i express@4.17.1 Express它是一个NodeJS平台下面的框架,主要用于构于Web服务器项目,它是一个第三方的模块
cors2.8.5npm i cors@2.8.5 处理跨域问题
MySQL2.18.1npm i mysql 通过这个模块可以与MySQL数据库建立连接、执行查询等操作
sm-crypto-npm i sm-crypto 加密解密
bcryptjs2.4.3npm i bcryptjs 加密解密
express-jwt5.3.3npm install express-jwt 验证指定http请求的JsonWebTokens的有效性,如果有效就将JsonWebTokens的值设置到req.user里面,然后路由到相应的router
multer1.4.2npm 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对象 https://www.jc2182.com/nodejs/nodejs-res.html
        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,需要使用中间变量

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

不写bug的柠宝

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值