构建Node项目:api_server

 一.初始化项目

(1)服务器准备

1.1初始化包管理配置文件

项目根目录: api_server  在项目根目录中打开终端运行以下代码来初始化包管理配置文件:

   就会得到package.json配置文件

1.2安装express框架

npm i express@4.17.1

1.3创建项目入口文件并配置服务器

将app.js作为本项目的入口文件

//导入express
const express = require("express")

//创建服务器实例对象
const app = express()



//启动服务器
app.listen(3007,()=>{

console.log("api server running at http://127.0.0.1:3007")

})

(2)中间件准备

 2.1配置cors跨域中间件

  在app.js中导入并配置cors中间件:

npm i cors@2.8.5
//导入cors中间件
const cors = require("cors")

//注册cors为全局中间件
app.use(cors()) 

2.2配置解析表单数据的中间件

通过如下代码 配置解析application/x-www-form-urlencoded 格式的表单数据的中间件:

app.use(express.urlencoded({extended:false}))

(3)路由准备

3.1初始化路由相关的文件夹

  在项目的根目录中 新建router文件夹 用来存放所有的路由模块(路由模块中 只存放客户端的请求与处理函数之间的映射关系)

  在项目根目录中 新建router_handler文件夹,用来存放所有的路由处理函数模块(路由处理函数模块中 专门负责存放每个路由对应的处理函数)

3.2初始化用户路由模块

 在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 

  在app.js中导入并使用路由模块

//导入并使用路由模块
  const userRouter = require("./router/user")

  app.use("/api",userRouter) 

3.3抽离用户路由模块中的处理函数(模块化开发)

目的:为了保证路由模块的纯粹性 所有路由处理函数 必须抽离到对应的路由处理函数模块中

在router_handler文件夹中创建user.js文件表示用户路由处理函数模块 使用exports对象共享以下两个路由处理函数:

//在router_handler文件夹下的user.js文件中共享以下两个用户路由处理函数

exports.reguser = (req,res)=>{
 res.send("reguser OK")
}


exports.login = (req,res)=>{
 res.send("login OK")
}

接下来,修改在router文件夹下的user.js文件内使用了这些用户路由模块处理函数的地方:

const express = require("express")

//创建路由对象
const router = express.Router()

//导入并使用路由处理函数模块
const userHandler = require("../router_handler/user")

//注册新用户
router.post("/reguser",userHandler.reguser)


//登录
router.post("/login",userHandler.login)


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

 二.注册用户的API开发

  (1)数据库准备

  1.1创建用户信息表

   在数据库my_db_01中出个那就表ev_users 使用的软件是MySql workbench:

 1.2安装并配置mysql模块(用于连接和操作MySQL数据库)

  执行以下命令安装mysql模块:

npm i mysql@2.18.1

  再在项目根目录中创建db/index.js 文件 在此自定义模块中创建数据库的连接对象:

//导入mysql模块 用于连接和操作数据库
  const mysql = require("mysql")

//创建数据库连接池对象
  const db = mysql.createPool({
  host:"127.0.0.1",
  user: "root",
  password: "admin123",
  database: "my_db_01"
})

//向外共享db数据库连接池对象
  module.exports = db

  (2)注册用户功能的实现(补充reguser处理函数的业务逻辑)

   2.1表单信息合法性验证

  将下面的代码补充到router_handler的user.js文件中的reguser路由处理函数中:

//判断用户名和密码是否为空

//接受表单数据
 const userinfo  = req.body //关键

 //判断数据是否合法
 if(!userinfo.username || !userinfo.password){

   return res.send({
   status:1,
   message:"用户名或密码不能为空"
})
}

   2.2检测用户名是否被占用

    同样的将导入数据库连接池对象的代码补充到router_handler的user.js文件中:

//导入数据库连接池对象 用于操作数据库
const db = require("../db/index")

  用户名查重逻辑如下:

//定义SQL查重语句
 const sql = "select * from ev_users where username = ?"

//执行sql语句进行查重
 db.query(sql,userinfo.username,(err,results)=>{
    //sql语句执行失败
    if(err){
    return res.send({
   status: 1,
   message: err.message
})
}
   //sql语句执行成功,但是用户名重复
   if(results.length > 0){
    return  res.send({
   status: 1,
   message: "用户名重复 请更换用户名!"
})
   //TODO: 用户名可用
   res.send("用户名可用!")
}
}
})

  2.3 对密码进行加密处理

  为了保证密码的安全性 不建议在数据库以明文的形式保存用户密码 推荐密码进行加密存储

  这里推荐使用bcryptjs对用户密码加密:

npm i bcryptjs@2.4.3

  然后,在router_handler中的user.js里面导入该模块,对可用的用户的密码进行加密:

//导入加密模块
const bcrypt = require("bcryptjs")

//TODO:用户名可用
 console.log(userinfo) //查看没加密之前的信息
//调用bcrpy模块的hashSync方法加密 参数1: 要加密的密码 参数2: 随机盐的长度(散列(哈希)运算时添加的一段随机数据 用于增加密码的安全性)
 userinfo.password = bcrypt.hashSync(userinfo.password,10)
 console.log(userinfo) //查看加密之后的信息

2.4 插入新用户(注册成功的结果)

 前面加密的工作完成后,既然注册了新用户 就要向数据库插入一行新的数据:

  下面是插入成功后的结果,可以看到数据库多了一条数据

三.代码优化

  (1)优化res.send()代码

  在处理函数中,需要多次调用res.send()向客户端响应处理失贩的结果,为了简化代码,可以手动封装一个res.cc()函数
  在app.js中,所有路由之前,声明一个全局中间件,为res 对象挂载一个res.cc()函数:

//响应数据的中间件
app.use(function (req,res,next)(
/ status =8为成功status = 1 为失败:默认将status的值设置为 1,方便处理失败的情况
res.cc = function (err,status = 1) (
res.send((
1/ 状态
status,
// 状态描述,判断err是错误对象还是字符串
message:err instanceof Error?err.message:err,
next()

(2)优化表单数据处理

  如果自己书写表单数据验证规则(if else逻辑)会过于繁琐与复杂 准确性也不强,为了提高效率 增强代码的健壮性 建议使用第三方包对表单数据进行验证,这里推荐joi和@escook/express-joi两个包:

const express = require('express')
const app = express()

//导入joi来定义验证规则
const Joi = require("joi")
//1.导入@escook/express-joi
const expressJoi = require("@escook/express-joi")

//解析x-www-form-urlencoded格式的表单数据
app.use(express.urlencoded({ extended: false }))

//2.定义验证规则
const userSchema={
 //2.1校验req.body中的数据
 body:{
  //用户名必须是字符串 且只能是[a-z A-Z 0-9]最短3最长12字符 用户名为必填
     username: Joi.string().alphanum().min(3).max(12).required(),
    password: Joi.string()
      .pattern(/^[\S]{6,15}$/)
      .required(),
    repassword: Joi.ref('password') //确认密码的校验规则
}

 //2.2校验req.query中的数据
 query:{
  name: Joi.string().alphanum().min(3).required(),  
  age: Joi.number().min(1).max(100).required() 
}

 //2.3校验req.params中的数据
 params:{
  id:Joi.number().integer().min(0).required() 
}

}

//3. 在路由中通过expressJoi(userSchema)的方式
//调用中间件进行参数验证  即在路由函数里面传递中间件函数来使用中间件
app.post("/adduser/:id",expressJoi(userSchema),function(req,res){
  const body = req.body
  res.send(body)
})

//4错误级别中间件
app.use(function(err,req,res,next){
 //4.1Joi参数验证失败
 if(err instance of Joi.ValidationError){
  return res.cc(err)
}
//4.2未知错误
res.cc(err)
})

// 调用 app.listen 方法,指定端口号并启动web服务器
app.listen(3001, function () {
  console.log('Express server running at http://127.0.0.1:3001')
})

那么怎么将这两个包应用到我们的项目中呢? 首先创建schema文件夹 并在下面创建user.js文件

user.js初始化代码如下:

//在schema/user.js文件下 我们定义用户相关的验证规则

const joi = require("@hapi/joi")

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

//共享注册和登录表单的验证规则对象
exports.regLoginShema = {
//需要对req.body中的数据进行验证
 body:{
  username,
  password
}
}

既然已经定义好并且共享了注册和登录表单的验证规则,那么我们怎么将其应用到路由中呢?

在我们之前创建的router/user.js 用户路由模块下添加如下内容:

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

//3.注册新用户
//在注册新用户的路由中 声明局部中间件 对当前请求中携带的数据进行验证
//3.1数据验证通过后,会把这次请求流转给后面的路由处理函数
//3.2数据验证没有通过 终止后续代码执行 会抛出一个全局的Error错误 进入全局错误级别中间件进行处理
  router.post("/reguser",expressJoi(regLoginSchema),userHandler.regUser)
 

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

//4错误级别中间件
app.use(function(err,req,res,next){
 //4.1Joi参数验证失败
 if(err instance of Joi.ValidationError){
  return res.cc(err)
}
//4.2未知错误
res.cc(err)
})

  注意:一定要解构出regLoginSchema这个数据验证规则对象 如果直接像我下面这样导入:

const regLoginSchema = require("../schema/user")

相当于regLoginSchema对象是下面这种结构(外面多了一层花括号 我要的是里面的body对象)

regLoginSchema = {
 body:{
  username,
  password
}
}

  四.登录用户的API开发

    将router/user.js 文件加入以下代码:

//登录的路由  expressJoi是自动完成表单数据验证的模块 regLoginSchema是通过Joi模块生成的验证规则
 router.post("/login",expressJoi(regLoginSchema),userHandler.login)

  (1)根据用户名查询用户的数据

    1.1接受表单数据:

const userinfo = req.body //在类似postman这样的模拟接口请求的软件中输入请求体的内容

   1.2 定义SQL语句(用于查询数据库中是否存在该用户名)

const sql = "select*from ev_users where username = ?" //sql中?为占位符 

   1.3执行SQL语句 查询用户的数据

db.query(sql,userinfo.username,(err,results)=>{
   //SQL语句执行失败
   if(err) return res.cc(err)

   //SQL语句执行成功 但是在数据库中没有查询到该用户
   if(results.length !==1) res.cc("该用户不存在 请重新登录!")

   //SQL语句执行成功 且找到了该用户 TODO(接下来要做的)判断用户输入的密码是否和数据库的一致
})

  (2)判断用户输入的密码是否正确

    实现思路: 需要调用bcrypt.compareSync(用户提交的密码,数据库中的密码) 比较密码是否一致

    返回的值是布尔值(true一致 false不一致)  

    具体实现代码如下:

//拿到用户提交的密码和数据库的密码比较
 const compareResult = bcrypt.compareSync(userinfo.password,results[0].password)

//判断两个密码是否一致
 if(!compareResult){
  return res.cc("用户输入的密码不正确,请重试!")
}

//TODO: 登陆成功 服务器端生成一个TOken字符串返回给客户端

  (3)生成JWT的Token字符串

    核心注意点: 在生成Token字符串的时候 一定要剔除密码和头像的值(敏感信息)

   1.通过ES6的高级语法 快速剔除密码和头像的值:

const user = {...results[0],password:"",user_pic:""}

   2.运行如下命令 安装生成Token字符串的包:

npm i jsonwebtoken@8.5.1

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

const jwt = require("jsonwebtoken")

  4.创建config.js文件 并且向外共享加密和还原Token的jwtSecretKey字符串:

module.exports = {
 jwtSecretKey: "Zero Two And Hiro",
 expiresIn: "48h"
}

   5.向客户端返回Token字符串

const config = require("../config")

const tokenStr = jwt.sign(user,config.jwtSecretKey,{
 expiresIn: config.expiresIn
})

  6.将生成的Token返回给客户端

res.send({
 status:0,
 message:"登陆成功!",
 token:"Bearer "+tokenStr
})

  (5)配置解析Token的中间件

    1.运行如下命令 安装解析Token的中间件:

npm i express-jwt@5.3.3

   2.在app.js中注册路由之前 配置解析Token的中间件:

const config = require("./config")

const expressJwt = require("express-jwt")

app.use(expressJwt({secret:config.jwtSecretKey}).unless({path:[/^\/api/]}))

  3.在app.js中的错误级别中间件里面 捕获并处理Token认证失败后的错误:

app.use((err,req,res,next)=>{
 //表单数据验证失败导致的错误
  if(err instanceof joi.ValidationError) return res.cc(err)
  if(err.name == "UnauthorizedError") return res.cc("身份认证失败!")
  //未知错误
  res.cc(err)
})

 五.用户信息API

 (1)获取用户基本信息

  .1.1初始化路由模块

   1.在router下新建userinfo.js  初始化代码如下:

//用户路由模块
const express = require("express")//导入express模块

const router = express.Router() //创建路由对象router

//获取用户信息接口
router.get("/userinfo",(req,res)=>{
 console.log("ok")
})

//导出用户路由模块
module.exports = router

   2.在app.js中导入并使用个人中心的路由模块:

const userInfoRouter = require("./router/userinfo")

app.use("/my",userInfoRouter)

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

   跟之前处理router/user.js一样 在router_handler下创建userinfo.js用于存放用户个人中心模块路由的处理函数,并初始化如下代码:

exports.getUserInfo = (req,res)=>{
  console.log("ok")
}

  然后再在router/userinfo.js文件中导入该路由处理函数

const userInfoHandler = require("../router_handler/userinfo")

router.get("/userinfo",userInfoHandler.getUserInfo)

  1.3数据库获取用户数据

   在router_handler/userinfo.js中引入数据库连接池对象 操作数据库:

const db = require("../schema/index")

exports.getUserInfo = (req,res)=>{
 //为了防止密码泄露 获取用户信息时应该不包括密码
  const sql = "select id,username,nickname,email,user_pic from ev_users where id = ?"
  db.query(sql,req.user.id,(err,results){
  if(err) return res.cc(err)
  if(results.length !== 1) return res.cc("获取用户信息失败!")
  res.send({
  status:0,
  message:"获取用户信息成功!".
  data: results[0]
})
})
}

 (2)更新用户的基本信息

  简要描述: 更新用户的基本信息   请求的URL: /my/userinfo  请求方式: POST

  2.1 定义路由和处理函数

  1.在/router_handler/userinfo.js  模块中 新增更新用户基本信息的路由处理函数:

//更新用户基本信息的处理函数
exports.updateUserInfo = (req,res)=>{
 res.send("ok")
}

  2.在/router/userinfo.js模块中 使用先前定义的路由处理函数

router.post("/userinfo",userInfoHander.updateUserInfo)

  2.2 验证表单数据

  1.在/schema/user.js 验证规则模块中 定义id,nickname,email的验证规则如下:

//之前定义了username和password的验证规则 这里定义id nickname emial的验证规则
const id = joi.number().integer().min(1).required() 
const nickname = joi.string().required()
const email = joi.string().email().required()

   2.并且使用exports向外共享验证所更新的数据的验证规则对象:

//验证规制对象 - 更新用户基本信息的规则对象
 exports.updatUserInfoSchema = {
  body:{
  id,
  nickname,
  email
}
}

  3.在/router/userinfo.js模块中 导入验证数据合法性的中间件:

//导入验证数据合法性的中间件
const expressJoi = require("@escook/express-joi")

 4.在/router/userinfo.js模块中 导入需要的验证规则对象

//导入需要的验证规则对象
  const {updateUserInfoSchema} = require("../schema/user")

  5.在/router/userinfo.js模块中 修改更新用户的基本信息的路由:  

router.post("/userinfo",expressJoi(updateUserInfoSchema),userInfoHandler.updateUserInfo)

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

 1.定义待执行的SQL语句

const sql = "update ev_users set ? where id=?"

2.调用db.query()执行SQL语句并传参

db.query(sql,[req.body,req.body.id],(err,results)=>{
 if(err) return res.cc(err)
 if(results.affectedRows !==1) return res.cc("修改用户基本信息失败!")
 return res.cc("修改用户基本信息成功!",0)
})

 (3)重置用户密码

  3.1定义路由和处理函数

 1.在/router/userinfo.js模块中 新增重置密码的路由:

router.post("/updatepwd",userInfoHander.updatePassword)

  2.在router_handler/userinfo.js模块中配置重置密码的路由处理函数

exports.updatePassword = (req,res)=>{
 console.log("ok")
}

  3.2验证新旧密码规则

  1.在schema/user.js 文件里面设置密码验证规则 并且新旧密码不能一致!

exports.updatePasswordSchema = {
  body:{
   oldPwd: password,
   //joi.ref("oldPwd") 表示新密码必须和旧密码一致 是作为参数传给not()表示 新旧密码不能一致
   // concat(password) 表示新密码还必须符合密码的校验规则
   newPwd: joi.not(joi.ref("oldPwd")).concat(password)
}

}

  2.在/router/userinfo.js模块中 导入需要的验证规则对象

const {updateUserInfoSchema,updatePasswordSchema} = require("../shema/user")

router.post("/updatepwd",expressJoi(updatePasswordSchema),userInfoHandler.updatePassword)

(4)更换用户头像

  请求URL: /my/update/avatar   请求方式: post  请求头: 提供Header认证字段

  4.1定义路由和处理函数

 1.在/router/userinfo.js模块中 新增更新用户头像的路由:

//更新用户头像的路由
router.post("/update/avatar",userInfoHandler.updateAvator)

  2.在/router_handler/userinfo.jg 模块中 定义并向外共享更新用户头像的路由处理函数

//更新用户头像的处理函数
exports.updateAvatar = (req,res)=>{
res.send("OK")
}

 4.2验证表单数据

  1.在/shema/user.js验证规则模块中 定义avatar的验证规则如下:

//dataUrl()指的是如下格式的字符串数据:
//data:image/png:base64,VE9PTUFOWVNFQ1JFVFM=
const avatar = joi.stirng().dataUrl().required()

  2.并使用exports向外共享如下的验证规制对象:

//验证规则对象-更新头像
 exports.updateAvatarSchema={
  body:{
  avatar,
}
}

 3.在/router/userinfo.js 模块中 导入需要的验证规则对象:

const {updateAvatarSchema} = require("../schema/user")

 4.3实现更新用户头像的功能

  1.定义更新用户头像的SQL语句:

const sql = "update ev_users set user_pic=? where id=?"

  2.调用db.query()执行该SQL语句 更新对应用户的头像

db.query(sql,[req.body.avator,req.user.id],(err,results)=>{
   if(err) return res.cc(err)

   if(results.affectedRows !== 1) return res.cc("更新头像失败!")

   res.cc("更新头像成功!",0)
})

六.文章分类管理

 准备工作: 先创建ev_article_cate 表:(alias是别名字段)

 (1)获取文章分类列表API

  功能描述: 获取文章分类列表   URL: /my/article/cates  请求方式: GET  返回示例如下图

  后面的操作可以参考前面我书写的内容 其实都是类似的 这里只把关键步骤写出来(文章表的创建)

  (2)新增文章分类API

    功能描述: 新增文章分类   URL: /my/article/addcates  请求方式: POST  返回示例如下图

     

     关键操作1: 要新增文章分类,必须先定义好验证新增文章分类的规则对象

//导入定义数据验证规则的第三方库Joi
 const joi = require("joi")
 
 const name =  joi.string().required()
 const alias = joi.string.alphanum().required()

 exports.addCateSchema = {
  body:{
   name,
   alias
}
}

  关键操作2: 要新增文章分类 必须先保证原先的数据库里面不存在一样的类名和别名

   

  关键操作3: 新增文章类别SQL语句的定义和执行

const addSql = "insert into ev_article_cate set ?"

db.query(addSql,req.body,(err,results)=>{
 
})

  这里再介绍以下SQL语句插入数据的几种语法:

//1.插入完整行数据
insert into table_name (column1,column2,column3,...) values (value1,value2,value3,...)

//2.省略列名插入数据 当你要插入的数据顺序和表中列的顺序一致,并且要为所有列提供值时,可以省略列名
insert into table_name (value1,value2,value3,...)

//3.插入多行数据
insert into table_name(column1,column2,column3) values(value1_row1),(value1_row2)

//4.从其他表插入数据 还可以从一个表中选取数据并插入到另一个表中,语法如下:
insert into table_name(column1,column2,column3,...) select column1,column2,column3,... from 
another_table where condition

 (3)删除文章分类API

     该API的简要介绍如下:

   

    关键操作1: 定义删除文章规则对象(要对传递的路径参数id进行校验)

const id = joi.number().integer().min(1).required()

exports.deleteCateSchema = {
 params:{
  id //需要对路径参数的id进行规范
}
}

    关键操作2: 标记删除法(为了保证数据的安全性和完整性)

 (4)更新文章分类API

   

     关键操作1: 更新文章分类就是更新文章分类名称(对应name字段) 文章分类别名(对应alias字段)

     更新文章分类规制对象如下:

     

     关键操作2: 在更新文章之前 要判断所更新的内容否重复(即name和alias的值是否已经被占用)

     代码逻辑如下(不仅要弄清是否被占用 还有弄清楚是名称和别名被占用还是都被占用了):

    关键操作3: 是在请求体body中传入id(要更新的文章类别和别名对应的id) name,alias 进行更新  

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值