npm init -yexpressapp.js 作为整个项目的入口文件,并初始化如下的代码// 导入 express 模块
const express = require('express')
// 创建 express 的服务器实例
const app = express()
// write your code here...
// 调用 app.listen 方法,指定端口号并启动web服务器
app.listen(3007, function () {
console.log('api server running at http://127.0.0.1:3007')
})
cors 中间件npm i cors@2.8.5app.js 中导入并配置 cors 中间件// 导入 cors 中间件
const cors = require('cors')
// 将 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('reguser OK')
})
// 登录
router.post('/login', (req, res) => {
res.send('login OK')
})
// 将路由对象共享出去
module.exports = router
app.js 中,导入并使用 用户路由模块// 导入并注册用户路由模块
const userRouter = require('./router/user')
app.use('/user', userRouter)
为了保证 路由模块 的纯粹性,所有的 路由处理函数,必须抽离到对应的 路由处理函数模块 中
exports.register = (req,resp)=>{
resp.send("发送成功")
console.log('注册成功');
}
exports.login =(req,resp)=>{
resp.send("登录成功!")
}
const express = require("express")
const router = express.Router()
const userHandle = require("../router_handle/user.js")
router.get("/register",userHandle.register)
router.post("/login",userHandle.login)
module.exports = router

npm install mysqlconst mysql = require("mysql")
const db = mysql.createPool({
host:'127.0.0.1',
user:'root',
password:'root',
database:'node_demo'
})
// 向外共享 db 数据库连接对象
module.exports = db

res.body 需要安装body-parser中间件,并且需要在引入路由之前引用//app.js
var bodyParser = require('body-parser')
app.use(bodyParser.urlencoded({ extended: false }))
app.use(bodyParser.json())
//router_handle/user.js
exports.register = (req, resp) => {
// 接收表单数据
const userinfo = req.body
// 判断数据是否合法
if (!userinfo.username || !userinfo.password) {
return res.send({
status: 500,
message: '用户名或密码不能为空!'
})
}
}
// 检测用户名是否被占用
db.query('select * from user where username = ?',userinfo.username,function(err,result){
if(err){
return res.send({ code:500,msg:err.message})
}
if(result.length > 0){
return res.send({ code:-1,msg:"用户名已被占用,请更换其他用户名!"})
}
res.send("新增成功")
})
bcryptjs 对用户密码进行加密,优点:运行如下命令,安装指定版本的 bcryptjs
npm i bcryptjs@2.4.3
在 /router_handler/user.js 中,导入 bcryptjs
const bcrypt = require('bcryptjs')
在注册用户的处理函数中,确认用户名可用之后,调用 bcrypt.hashSync(明文密码, 随机盐的长度) 方法,对用户的密码进行加密处理
// 对用户的密码,进行 bcrype 加密,返回值是加密之后的密码字符串
userinfo.password = bcrypt.hashSync(userinfo.password, 10)
处理函数中,需要多次调用 res.send() 向客户端响应 处理失败 的结果,为了简化代码,可以手动封装一个 res.cc() 函数,需要定义在路由之前
app.use(function(req,res,next){
res.cc = function (err,code = 500){
res.send({
code,
msg:err instanceof Error ? err.message : err
})
}
next()
})
此时,注册代码简化为
exports.register = (req, res) => {
// 接收表单数据
const userinfo = req.body
// 判断数据是否合法
if (!userinfo.username || !userinfo.password) {
return res.cc('用户名或密码不能为空!')
}
// 检测用户名是否被占用
db.query('select * from user where username = ?',userinfo.username,function(err,result){
if(err){
return res.cc(err)
}
if(result.length > 0){
return res.cc("用户名已被占用,请更换其他用户名!",-1)
}
})
//对用户密码进行加密
userinfo.password = bcrypt.hashSync(userinfo.password, 10)
userinfo['nickName'] = '新用户'
userinfo['userPic'] = ''
// 插入新用户
db.query("insert into user set ?",userinfo,function(err,result){
if(err){
return res.cc(err)
}
if(result.affectedRows != 1){
return res.cc("注册用户失败,请稍后重试!")
}
return res.cc("新增用户成功",200)
})
}
单纯的使用 if...else... 的形式对数据合法性进行验证,效率低下、出错率高、维护性差。因此,推荐使用第三方数据验证模块,来降低出错率、提高验证的效率与可维护性。
@hapi/joi 包,为表单中携带的每个数据项,定义验证规则:npm install @hapi/joi@17.1.0
@escook/express-joi 中间件,来实现自动对表单数据进行验证的功能:npm i @escook/express-joi
/schema/user.js 用户信息验证规则模块,并初始化代码如下:const Joi = require('@hapi/joi')
const joi = require('@hapi/joi')
/**
* string() 值必须是字符串
* alphanum() 值只能是包含 a-zA-Z0-9 的字符串
* min(length) 最小长度
* max(length) 最大长度
* required() 值是必填项,不能为 undefined
* pattern(正则表达式) 值必须符合正则表达式的规则
*/
// 用户名的验证规则
const username = joi.string().alphanum().min(1).max(10).required()
// 密码的验证规则
const password = joi
.string()
.pattern(/^[\S]{6,12}$/)
.required()
// 注册和登录表单的验证规则对象
exports.reg_login_schema = {
// 表示需要对 req.body 中的数据进行验证
body: {
username,
password,
repassword:Joi.ref('password')
},
}
修改 /router/user.js 中的代码如下:
// 1. 导入验证表单数据的中间件
const expressJoi = require('@escook/express-joi')
// 2. 导入需要的验证规则对象
const { reg_login_schema } = require('../schema/user')
router.post("/register",expressJoi(reg_login_schema),userHandle.register)
在 app.js 的全局错误级别中间件中,捕获验证失败的错误,并把验证失败的结果响应给客户端:
// 错误中间件
app.use(function (err, req, res, next) {
// 数据验证失败
if (err instanceof joi.ValidationError) return res.cc(err)
// 未知错误
res.cc(err)
})
调用 bcrypt.compareSync(用户提交的密码, 数据库中的密码) 方法比较密码是否一致,返回值为布尔类型
if(!bcrypt.compareSync(userinfo.password,result[0].password)){
return res.cc('密码错误,请重新输入','-1')
}
在生成 Token 字符串的时候,一定要剔除 密码 和 头像 的值
// 剔除完毕之后,user 中只保留了用户的 id, username, nickname, email 这四个属性的值
const user = { ...results[0], password: '', user_pic: '' }
知识链接—扩展运算符(…)
对象中的扩展运算符(...)用于取出参数对象中的所有可遍历属性,拷贝到当前对象之中let bar = { a: 1, b: 2 };
let baz = { …bar }; // { a: 1, b: 2 }如果用户自定义的属性,放在扩展运算符后面,则扩展运算符内部的同名属性会被覆盖掉
let bar = {a: 1, b: 2};
let baz = {…bar, …{a:2, b: 4}}; // {a: 2, b: 4}如果将扩展运算符用于数组赋值,只能放在参数的最后一位,否则会报错。
运行如下的命令,安装生成 Token 字符串的包
npm i jsonwebtoken@8.5.1
在 /router_handler/user.js 模块的头部区域,导入 jsonwebtoken 包:
// 用这个包来生成 Token 字符串
const jwt = require('jsonwebtoken')
config.js 文件,并向外共享 加密 和 还原 Token 的 jwtSecretKey 字符串module.exports = {
jwtSecretKey: 'abcdefg. ^_^',
}
// 导入配置文件
const config = require('../config')
// 生成 Token 字符串
const tokenStr = jwt.sign(user, config.jwtSecretKey, {
expiresIn: '10h', // token 有效期为 10 个小时
})
res.send({
status: 200,
message: '登录成功!',
// 为了方便客户端使用 Token,在服务器端直接拼接上 Bearer 的前缀
token: 'Bearer ' + tokenStr,
})