通过node.js可以实现中小型项目的后端搭建,基于js的语法达到全栈开发的效果。
1.创建项目
在文件夹中自定义命名一个文件,在终端打开,初始化包管理配置文件:
npm init -y安装新版本的express:
npm i express在项目根目录下创建app.js文件作为整个项目的入口文件(端口号自定义,避免冲突):
const express = require('express') const app = express() -----中间部分------ app.listen(8082,()=>{ console.log('Express server running at http://127.0.0.1:8082'); })配置cors跨域,安装cors中间件:
npm i cors在app.js中导入并配置cors中间件:
const cors = require('cors') app.use(cors())配置解析表单数据的中间件(解析application/x-www-form-urlencoded格式的表单数据):
app.use(express.urlencoded({extended:false}))在项目根目录中创建router文件夹,用来存放所有的路由模块;创建router_handler文件夹,用来存放所有的路由处理函数模块
在router文件夹中创建user.js文件,作为用户的路由模块;
const express = require('express') const router = express.Router() router.post('/reguser',(req,res)=>{ res.send('注册成功') }) router.post('/login',(req,res)=>{ res.send('登录成功') }) module.exports = router然后在app.js文件中导入使用用户路由模块
const userRouter = require('./router/user') app.use('/api',userRouter)在router_handler文件夹下创建user.js文件,抽离用户路由模块中的处理函数,使用exports对象,向外共享两个处理函数:
exports.reguser = (req,res) => { res.send('注册成功') } exports.login = (req,res) => { res.send('登录成功') }将/router/user.js中的代码进行修改
const express = require('express') const router = express.Router() // 导入用户路由处理模块 const userHandler = require('../router_handler/user') router.post('/reguser',userHandler.register) //注册 router.post('/login',userHandler.login) //登录 module.exports = router2.创建数据库(推荐使用Navicat)
定义数据库名称,新建数据表,设计表结构,如图所示:
id为主键,默认为自增,其他字段统一字符集:utf8mb4,排序规则:utf8mb4_0900_ai_ci
在项目终端中安装mysql模块:
npm i mysql2在根目录创建db/index.js文件,用来创建数据库的连接对象
const mysql = require('mysql2') const db = mysql.createPool({ host:'127.0.0.1', user:'root', password:'123456', database:'my_db_01' }) module.exports = db3.注册
首先检测表单数据合法性:
// 获取客户端提交到服务的用户信息 const userinfo = req.body // 对表单中的数据进行合法校验 if(!userinfo.username || !userinfo.password){ return res.send({ status:1, message:'用户名或密码不合法!' }) }检测用户名是否被占用,导入数据库操作模块,定义SQL语句:
// 导入数据库操作模块 const db = require('../db/index') // 定义sql语句,查询用户名是否被占用 const sqlStr = 'select * from ev_users where username=?'执行SQL语句判断用户名是否被占用:
db.query(sqlStr,userinfo.username,(err,result)=>{ // 执行SQL语句失败 if(err){ return res.send({ status:1, message:err.message }) } // 判断用户名是否被占用 if(result.length > 0){ return res.send({ status:1, message:'用户名已被占用,请重新输入!' }) } })对密码进行加密处理:安装指定版本的bcryptjs
npm install bcryptjs在/router_handler/user.js文件中导入bcryptjs
// 导入bcryptjs模块,用于密码加密 const bcrypt = require('bcryptjs')调用 bcrypt.hashSync()对密码进行加密
userinfo.password = bcrypt.hashSync(userinfo.password,10)插入新用户,定义插入的SQL语句:
const sql = 'insert into ev_users set ?'调用db.query()执行SQL语句,插入新用户:
db.query(sql,{username:userinfo.username,password:userinfo.password},(err,result)=>{ // 判断SQL语句是否执行成功 if(err)return res.send({ status:1,message:err.message}) // 判断影响行数是否为1 if(result.affectedRows !== 1)return res.send({ status:1,message:'注册失败!'}) // 注册成功 res.send({ status:0,message:'注册成功!'}) })对res.send()代码进行优化:
在app.js中,在所有路由之前声明一个全局中间件,为res对象挂载一个res.cc()的函数
app.use((req,res,next)=>{ // status默认值为1,表示失败的情况 // err的值,可能是一个错误对象,也可能是一个字符串 res.cc = (err,status=1)=>{ res.send({ status, message:err instanceof Error ? err.message : err }) } next() })然后将所有的res.send()代码进行修改:
// 注册用户的处理函数 exports.reguser = (req,res)=>{ // 获取客户端提交到服务的用户信息 const userinfo = req.body // 定义sql语句,查询用户名是否被占用 const sqlStr = 'select * from ev_users where username=?' db.query(sqlStr,userinfo.username,(err,result)=>{ // 执行SQL语句失败 if(err){ return res.cc(err) } // 判断用户名是否被占用 if(result.length > 0){ return res.cc('用户名已被占用,请重新输入!') } // 调用 bcrypt.hashSync()对密码进行加密 userinfo.password = bcrypt.hashSync(userinfo.password,10) // 定义插入新用户的SQL语句 const sql = 'insert into ev_users set ?' db.query(sql,{username:userinfo.username,password:userinfo.password},(err,result)=>{ // 判断SQL语句是否执行成功 if(err)return res.cc(err) // 判断影响行数是否为1 if(result.affectedRows !== 1)return res.cc('注册用户失败!请稍后重试') // 注册成功 res.cc('注册成功!') }) }) }优化表单数据验证:
安装@hapi/joi包
npm i @hapi/joi安装@escook/express-joi中间件,实现自动对表单数据进行验证
npm i @escook/express-joi新建/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 } }修改router/user.js中的代码
const express = require('express') const router = express.Router() // 导入用户路由处理函数模块 const userHandler = require('../router_handler/user') // 导入验证数据的中间件 const expressJoi = require('@escook/express-joi') // 导入需要的验证规则对象 const {reg_login_schema} = require('../schema/user') // 注册新用户 router.post('/reguser',expressJoi(reg_login_schema),userHandler.reguser) // 登录 router.post('/login',userHandler.login) module.exports = router在app.js的全局错误级别中间件中,捕获验证失败的错误,并把验证失败的结果响应给客户端
const joi = require('joi') // 定义错误级别的中间件 app.use((err,req,res,next)=>{ // 验证失败导致的错误 if(err instanceof joi.ValidationError) return res.cc(err) // 未知的错误 res.cc(err) })4.登录
验证登录表单的数据是否合法
修改router/user.js中的登录代码:
/ 登录 router.post('/login',expressJoi(reg_login_schema),userHandler.login)在router_handler文件中,根据用户名查询用户的数据
// 登录的处理函数 exports.login = (req,res)=>{ // 接收表单数据 const userinfo = req.body // 定义SQL语句 const sql = `select * from ev_users where username=?` // 执行SQL语句,根据用户名查询用户的信息 db.query(sql,userinfo.username,(err,results)=>{ // 执行SQL语句失败 if(err)return res.cc(err) // 执行SQL语句成功但是获取到的数据条数不等于1 if(results.length !== 1)return res.cc('登录失败!') }) }判断用户输入的密码是否正确
const compareResult = bcrypt.compareSync(userinfo.password,results[0].password) if(!compareResult)return res.cc('登录失败!')生成JWT的token字符串
通过ES6的高级语法,快速剔除密码和头像的值
const user = { ...results[0] , password:'' , user_pic:''}安装生成token字符串的包:
npm i jsonwebtoken在/router_handler/user.js 头部导入jsonwebtoken包
// 导入生成token的包 const jwt = require('jsonwebtoken')创建config.js 文件,并向外共享加密和还原Token的jwtSecretKey字符串
// 这是一个全局的配置文件 module.exports = { // 这是加密和解密token的密钥 secretKey:'fengrui', // 这是token的过期时间 expiresIn:'10h' }在router_handler/user.js文件中,将用户信息对象加密成Token字符串
// 导入配置文件 const config = require('../config') // 对用户的信息进行加密,生成token字符串 const tokenStr = jwt.sign(user,config.secretKey,{expiresIn:config.expiresIn})将生成的token字符串响应给客户端
// 调用res.send()将token响应给客户端 res.send({ status:0, message:'登录成功!', token: 'Bearer ' + tokenStr })配置解析token的中间件
npm i express-jwt在app.js中注册路由之前,配置解析token的中间件:
// 一定要在路由之前配置解析token的中间件 const { expressjwt } = require('express-jwt'); const config = require('./config'); // 配置 JWT 解析中间件 app.use( expressjwt({ secret: config.secretKey, algorithms: ['HS256'], }).unless({ path: [/^\/api/] }) // 排除 /api 开头的接口 );在app.js中的错误级别中间件里面,捕获并处理Token认证失败后的错误
// 定义错误级别的中间件 app.use((err,req,res,next)=>{ // 验证失败导致的错误 if(err instanceof joi.ValidationError) return res.cc(err) // 解析token失败导致的错误 if(err.name === 'UnauthorizedError') return res.cc('身份认证失败!') // 未知的错误 res.cc(err) })5.Apipost测试
测试注册接口
测试登录接口
测试其他接口
6.相关的安装包版本信息
仅供参考(package.json文件)
{ "name": "api_server", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "keywords": [], "author": "", "license": "ISC", "type": "commonjs", "dependencies": { "@escook/express-joi": "^1.1.1", "bcryptjs": "^3.0.3", "cors": "^2.8.6", "express": "^4.21.1", "express-jwt": "^8.4.1", "joi": "^17.13.3", "jsonwebtoken": "^9.0.3", "mysql2": "^3.22.2" } }