node写接口

438 阅读11分钟

1.初始化

1.1创建项目

  1. 新建api_server文件夹作为项目根目录,并运行如下命名,创建包管理文件
npm init -y
  1. 运行如下命名,安装express:
npm i express 
  1. 在跟项目中创建app.js作为项目入口,并初始化如下代码
//导入express模块
const express=require('express')
//创建expres的服务实例
const app=express()
//调用app.listen方法,指定端口号并启动服务
app.listen(80,()=>{
    console.log('http://127.0.0.1')
})

1.2配置cors跨域

  1. 运行如下命令,安装cors中间件:
npm i cors
  1. app.js中导入并配置cors中间件:
//导入cors模块
const cors=require('cors')
//将cors注册为全局中间件
app.use(cors())

1.3配置解析表单数据中间件

  1. 通过如下代码,配置解析application/x-www-form-urlencoded格式的表单数据的中间件:
app.use(express.urlencoded({extended:false}))

1.4初始化路由相关文件夹

  1. 在项目根目录下新建router文件夹,用来存放所有路由模块

  2. 在项目根目录下新建router_handler文件夹,用来存放所有的路由处理函数

1.5初始化用户路由模块

  1. router文件夹中,新建一个user.js文件,作为用户的路由模块,并初始化如下代码
const express= require('express')

const router=express.Router()
//注册
router.post('/reguser',(req,res)=>{
    res.send('reguser OK')
})
//登录
router.post('/login',(req,res)=>{
    res.send('login OK')
})

module.exports=router
  1. app.js中,导入并使用用户路由模块
//导入并使用用户路由模块
const userRouter=require('./router/user')
app.use('/api',userRouter)

1.6抽离用户模块中的处理函数

  • 目的:为了保证路由模块的纯粹性,所有的路由处理函数,必须抽离到对应的路由处理函数模块
  1. /router_handler/user.js中,使用exports对象,分别向外共享如下两个路由处理函数
//注册的处理函数
exports.regUser=(req,res)=>{
    res.send('reguser OK')
}
//登录的处理函数
exports.login=(req,res)=>{
    res.send('login OK')
}
  1. /router/user.js中的代码修改成如下结构
const express= require('express')

const router=express.Router()

//导入用户路由处理函数对应的模块
const user_handler=require('../router_handler/user')
//注册
router.post('/reguser',user_handler.regUser)
//登录
router.post('/login',user_handler.login)

module.exports=router

2.登录与注册

2.1创建ev_user表

image.png

2.2安装并配置mysql模块

  1. 运行如下命令,安装mysql
npm i mysql
  1. 在项目中新建/db/index.js文件,在此自定义模板中创建数据库的链接对象
const mysql= require('mysql')

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

module.exports=db

2.3注册

  1. 检测表单数据是否合法
  2. 检测用户名是否占用
  3. 对密码进行加密处理
  4. 插入新用户

2.3.1检测表单数据是否合法

  1. 判断用户名和密码是否为空
//获取客户端提交到服务器的信息
const userInfo=req.body
//对表单数据进行合法验证
if(!userInfo.username||!userInfo.password){
    return res.send({status:1,message:'用户名或密码不合法'})
}

2.3.2检测用户名是否占用

  1. 导入数据库模块
const db=require('../db/index')
  1. 定义SQL语句
const sqlStr='select * from ev_user where username=?'
  1. 执行SQL语句并根据结果判断用户名是否被占用
db.query(sqlStr,userInfo.username,(err,results)=>{
    if(err){
        return res.send({status:1,message:err.message})
    }
    if(results.length>0){
        return res.send({status:1,message:'用户被占用,请使用其他用户名'})
    }
})

2.3.3对密码进行加密处理

使用bcrypt.js对用户密码进行加密,优点

  • 加密之后密码,无法被逆向破解
  • 同一明文密码多次加密,得到的加密结果各不相同,保证了安全性
  1. 运行如下命令,安装bcryptjs
npm i bcryptjs
  1. /router_handler/user.js中,导入bcryptjs
const bcrypt=require('bcryptjs')
  1. 在注册用户的处理函数中,确认用户名可用之后,调用bcrypt.hashSync(明文密码,随机值的长度)方法,对用户的密码进行加密处理。
//调用bcrypt.hashSync()对密码进行加密
userInfo.password=bcrypt.hashSync(userInfo.password,10)

2.3.4插入用户

  1. 定义一个插入用户的SQL语句
//定义插入用户的SQL语句
const sql='insert into ev_user set ?'

2.使用db.query()执行SQL语句,插入新用户:

//调用db.query()执行sql
db.query(sql,{username:userInfo.username,password:userInfo.password},(err,results)=>{
    if(err){
        return res.send({status:1,message:err.message})
    }
    //判断影响函数是否为1
    if(results.affectedRows!==1){
        return res.send({status:1,message:'注册用户失败,请稍后再试!'})
    }
    //注册用户成功
    return res.send({status:0,message:'注册用户成功!'})
})

2.4优化res.send()代码

  1. 在app.js中,所有路由之前,声明一个中间件,为res对象挂载一个res.cc()函数
//一定要在路由之前,封装res.cc函数
app.use((req,res,next)=>{
    //status默认值为1,表示失败情况
    //err的值,可能是一个错误的对象,也有可能是一个错误的描述字符串
    res.cc=function(err,status=1){
        res.send({
            status,
            message:err instanceof Error?err.message:err 
        })
    }
    next()
})

2.5优化表单数据验证

  1. 安装joi包,为表单中携带的每个数据项,定义验证规则:
npm i joi
  1. 安装@escook/express-joi中间件,来实现自动对表单数据进行验证功能:
npm i @escook/express-joi
  1. 新建/schema/user.js用户信息验证模块,并初始化代码如下:
//导入定义验证规则的包
const joi=require('joi')

//定义用户名和密码的验证规则
const username=joi.string().alphanum().min(1).max(10).required()
const password=joi.string().pattern(/^[\S]{6,12}$/).required()

//定义验证注册和登录表单数据规则对象
exports.reg_login_schema={
    body:{
        username,
        password
    }
}
  1. 修改/router/user.js中的代码如下:
const express= require('express')

const router=express.Router()

//导入用户路由处理函数对应的模块
const user_handler=require('../router_handler/user')

//1.导入验证数据的中间件
const expressJoi=require('@escook/express-joi')
//2.导入需要的验证规则对象
const {reg_login_schema}= require('../schema/user')

//注册
router.post('/reguser',expressJoi(reg_login_schema),user_handler.regUser)

//登录
router.post('/login',user_handler.login)

module.exports=router

5.在app.js的全局错误级别中间件中,捕获验证失败的错误,并把验证失败的结果响应给客户端:

const joi= require('joi')

//定义错误级别的中间件
app.use((err,req,res,next)=>{
    //验证失败导致的错误
    if(err instanceof joi.ValidationError) return res.cc(err)
    //未知错误
    res.cc(err)
})

2.6登录

实现步骤

  1. 检测表单数据是否合法
  2. 根据用户名查询用户信息
  3. 判断用户输入的密码是否正确
  4. 生成JWT的Token

2.6.1检测表单数据是否合法

1.将/router/user.js登录的路由代码修改如下

router.post('/login',expressJoi(reg_login_schema),user_handler.login)

2.6.2根据用户名查询用户的数据

  1. 接受表单数据:
const userInfo=req.body
  1. 定义SQL语句:
const sqlStr='select * from ev_user where username=?'
  1. 执行SQL语句,查询用户的数据
db.query(sqlStr,userInfo.username,(err,results)=>{
    if(err) return res.cc(err)
    if(results.length!==1) return res.cc('用户名不存在')
    //TODO:判断用户输入的登录密码是否和数据库中的密码一致
})

2.6.3判断用户输入的密码是否正确

核心思路:调用调用bcrypt.compareSync(用户提交的密码,数据库中的密码)方法比较密码是否一致。返回true一致,false不一致。

具体实现代码如下

//判断密码是否正确
const  compareResult= bcrypt.compareSync(userInfo.password,results[0].password)

if(!compareResult) return  res.cc('登录失败!')

//TODO:登录成功,生成Token字符串

2.6.4生成JWT的Token字符串

注意:在生成Token字符串的时候,一定要剔除密码头像值

  1. 通过ES6的高级语法,快速剔除密码头像
//登录成功,生成Token字符串
const user={...results[0],password:'',user_pic:''}
  1. 运行如下命令,安装生成Token字符串的包:
npm i jsonwebtoken

3.在/router_handler/user.js模块的头部区域,导入jsonwebtoken

//用这个包生成Token字符串
const jwt=require('jsonwebtoken')
  1. 创建config.js文件,并向外共享加密和还原Token的jwtSecreKey字符串:
module.exports={
    jwtSecretKey:'zyt',//加密解密的秘钥
    expiresIn:'10h'//token有效期为10个小时
}
  1. 将用户信息对象加密成Token字符串:
const config=require('../config')

const token=jwt.sign(user,config.jwtSecretKey,{
    expiresIn:config.expiresIn
})
  1. 将生成的Token字符串相应给客户端:
res.send({
    status:0,
    message:'登录成功',
    //为方便客户端使用token,在服务器端直接拼接上Bearer前缀
    token:'Bearer '+token
})

2.7配置解析Token中间件

  1. 运行如下命令,安装解析Token的中间件:
npm i express-jwt@7.7.1
  1. app.js中注册路由之前,配置解析Token中间件:
//一定要在路由之前配置解析Token的中间件
const { expressjwt: jwt }=require('express-jwt')

const config=require('./config')

app.use(jwt({secret:config.jwtSecretKey,algorithms: ["HS256"]}).unless({path:[/^\/api/]}))
  1. app.js中的错误级别中间件里面,捕获并处理Token身份认证
//定义错误级别的中间件
app.use((err,req,res,next)=>{
    //其他验证失败导致的错误
    //...

    //捕获身份认证失败的错误
    if(err.name==='UnauthorizedError') return res.cc('身份认证失败')

    //未知错误
    res.cc(err)
})

3个人中心

3.1获取用户的基本信息

实现步骤

  1. 初始化路由模块
  2. 初始化路由处理函数模块
  3. 获取用户的基本信息

3.1.1初始化路由模块

  1. /router/userinfo.js路由模块,并初始化如下代码结构:
const express= require('express')

const router=express.Router()

router.get('/userinfo',(req,res)=>{
    res.send('ok')
})

module.exports=router
  1. app.js中导入并使用个人中心的路由模块
//导入并使用用户信息路由模块
const  userinfoRouter=require('./router/userinfo')
//注意:以/my开头的接口,都是有权限的接口,需要进行Token身份认证
app.use('/my',userinfoRouter)

3.1.2初始化路由处理函数模块

  1. 创建/router_handler/userinfo.js路由处理函数模块,并初始化如下代码结构:
exports.getUserInfo=(req,res)=>{
    res.send('ok')
}

2.修改/router/userinfo.js中的代码:

const express= require('express')

const router=express.Router()
//导入用户信息处理函数
const userinfo_handler=require('../router_handler/userinfo')
//获取用户的基本信息
router.get('/userinfo',userinfo_handler.getUserInfo)

module.exports=router

3.1.3获取用户基本信息

  1. /router_handler/userinfo.js头部导入数据库:
//导入数据库
const db= require('../db/index')
  1. 定义SQL语句:
//注意:为防止用户密码泄露,需要排除password字段
const sqlStr='select id,username,nickname,email,user_pic from ev_user where id=?'
  1. 调用db.query()执行SQL语句:
//注意req对象上有auth属性,是Token解析成功,express-jwt中间件帮我们挂载上去了
db.query(sqlStr,req.auth.id,(err,results)=>{
    if(err) return res.cc(err)
    if(results.length!==1) return res.cc('获取用户信息失败')
    //将用户信息相应给客户端
    res.send({
        status:0,
        message:'获取用户信息成功',
        data:results[0]
    })
})

3.2.1定义路由和处理函数

  1. /router/userinfo.js模块中,新增更新用户基本信息的路由
//更新用户的基本信息
router.post('/userinfo',userinfo_handler.updataUserInfo)
  1. /router_handler/userinfo.js模块中,定义并向外共享更新用户基本信息的路由处理函数:
//更新用户基本信息的处理函数
exports.updataUserInfo=(req,res)=>{
    res.send('ok')
}

3.2.2验证表单数据

  1. /schema/user.js验证规则模块中,定义idnickname,email,的验证规则如下:
//定义id,nickname,email的验证规则
const id=joi.number().integer().min(1).required()
const nickname=joi.string().required()
const email=joi.string().email().required()
  1. 使用exports向外共享如下验证规则对象
//验证规则对向-更新用户基本信息
exports.update_userinfo_schema={
    //需要对req.body里面的数据进行验证
    body:{
        id,
        nickname,
        email
    }
}
  1. /router/userinfo.js模块,导入验证数据合法性中间件
//导入验证数据的中间件
const expressJoi=require('@escook/express-joi')
  1. /router/userinfo.js模块,导入需要的验证规则对象:
//导入需要的验证规则对象
const {update_userinfo_schema}= require('../schema/user')
  1. /router/userinfo.js模块,修改更新用户基本信息路由
//更新用户的基本信息
router.post('/userinfo',expressJoi(update_userinfo_schema),userinfo_handler.updataUserInfo)

3.2.3实现更新用户基本信息功能

  1. 定义待执行的SQL语句:
const sqlStr='update ev_user set ? where id=?'
  1. 调用db.query()执行SQL语句并传参:
db.query(sqlStr,[req.body,req.body.id],(err,results)=>{
    if(err) return res.cc(err)
    if(results.affectedRows!==1) res.cc('更新用户基本信息失败')
    //成功
    res.cc('更新用户成功!',0)
})

3.3重置密码

实现步骤:

  1. 定义一个路由和处理函数
  2. 验证表单数据
  3. 实现重置密码的功能

3.3.1定义一个路由和处理函数

  1. /router/userinfo.js模块中,新增重置密码的路由:
router.post('/updatepwd',userinfo_handler.updatePassword)
  1. /router_handler/userinfo.js模块中,定义并向外共享重置密码的路由处理函数
exports.updatePassword=(req,res)=>{
    res.send('ok')
}

3.3.2验证表单数据

旧密码与新密码,必须符合密码规范,并且新密码不能与旧密码一致!

  1. /schema/user,js模块中,使用exports向外共享如下的验证规则对象
//验证规则对象-重置密码
exports.update_password_schema={
    //需要对req.body里面的数据进行验证
    body:{
       oldPwd:password,
        // 1. joi.ref('oldPwd') 表示 newPwd 的值必须和 oldPwd 的值保持一致
        // 2. joi.not(joi.ref('oldPwd')) 表示 newPwd 的值不能等于 oldPwd 的值
        // 3. .concat() 用于合并 joi.not(joi.ref('oldPwd')) 和 password 这两条验证规则
       newPwd:joi.not(joi.ref('oldPwd')).concat(password)
    }
}
  1. /router/userinfo.js模块,导入需要的验证规则对象:
//导入需要的验证规则对象
const {update_userinfo_schema,update_password_schema}= require('../schema/user')
  1. 并在重置密码路由中,使用update_password_schema规则验证表单数据
//密码重置
router.post('/updatepwd',expressJoi(update_password_schema),userinfo_handler.updatePassword)

3.3.3实现重置密码的功能

  1. 根据id查询用户是否存在
exports.updatePassword=(req,res)=>{
    const sqlStr='select * from ev_user where id=?'

    db.query(sqlStr,req.auth.id,(err,results)=>{
        if(err) return res.cc(err)
        if(results.length!==1) return res.cc('用户不存在')

        //TODO判断用户输入的旧密码对否正确
    })
}
  1. 判断旧密码是否存在
const bcryptjs= require('bcryptjs')

//判断用户输入的旧密码对否正确
const compareResult= bcryptjs.compareSync(req.body.oldPwd,results[0].password)
if(!compareResult) return res.cc('原密码错误')
  1. 对新密码加密,放入数据库中
const sqlStr='update ev_user set password=? where id=?'
//对新密码加密
const newPwd=bcryptjs.hashSync(req.body.newPwd,10)

db.query(sqlStr,[newPwd,req.auth.id],(err,results)=>{
    if(err) return res.cc(err)
    if(results.affectedRows!==1) return res.cc('修改密码失败')
    res.cc('修改密码成功',0)
})