建设银行河北省分行官方网站,做欧洲电商看哪个网站,免费制作自己的微网站吗,wordpress 主机销售代码下载
初始化
新建 apiServer 文件夹作为项目根目录#xff0c;并在项目根目录中运行如下的命令#xff0c;初始化包管理配置文件:
npm init -y运行如下的命令#xff0c;安装 express、cors:
npm i express cors在项目根目录中新建 app.js 作为整个项目的入口文件并在项目根目录中运行如下的命令初始化包管理配置文件:
npm init -y运行如下的命令安装 express、cors:
npm i express cors在项目根目录中新建 app.js 作为整个项目的入口文件配置 cors 跨域中间件、解析表单数据的中间件并启动服务器
// 导入 express 模块
const express require(express);
// 创建 express 的服务器实例
const app express();// 配置跨域中间件
const cors require(cors);
app.use(cors());// 配置解析 application/x-www-form-urlencoded 格式的表单数据的中间件
app.use(express.urlencoded({ extended: false }));// 指定端口号并启动web服务器
app.listen(80, function() {console.log(server running at http://127.0.0.1:80);
});因为在处理函数中需要多次调用 res.send() 向客户端响应 处理失败 的结果为了简化代码 可以手动封装一个 res.cc() 函数。在 app.js 中所有路由之前声明一个全局中间件为 res 对象挂载一个 函数 :
// 在 路由 之前自定义中间件处理错误的响应数据
app.use(function(req, res, next) {// status 默认值1表示失败0表示成功// err 可能是错误对象也可能是错误描述字符串res.cc function(err, status 1) {res.send({status: status,message: err instanceof Error ? err.message : err});};next();
});在项目根目录中新建 router 文件夹用来存放所有的 路由 模块路由模块中只存放客户端的请求与处理函数之间的映射关系在项目根目录中新建 routerHandler 文件夹用来存放所有的 路由处理函数模块 路由处理函数模块中专门负责存放每个路由对应的处理函数在项目根目录中新建 db 文件夹用来存放所有的 数据库 模块在项目根目录中新建 middleware 文件夹用来存放所有的 自定义中间件 模块
登录注册
新建 users 表在 my_db_01 数据库中新建 ev_users 表如下:
安装并配置 mysql 模块需要安装并配置 mysql 这个第三方模块来连接和操作 MySQL 数据库运行如下命令安装 mysql 模块:
npm i mysql在项目根目录中 db 文件夹新建 index.js 文件在此自定义模块中创建数据库的连接对象:
const mysql require(mysql);
const db mysql.createPool({host: localhost,port: 3306,user: root,password: admin123,database: my_db_01
});module.exports db;初始化用户路由模块
在 router 文件夹中新建 user.js 文件作为用户的路由模块并初始化代码如下:
const express require(express) // 创建路由对象
const router express.Router()
// 注册新用户
router.post(/signin, (req, res) {res.send(signin OK)
})
// 登录
router.post(/login, (req, res) {res.send(login OK)
})
// 将路由对象共享出去 module.exports router在 app.js 中导入并使用 用户路由模块 :
// 配置路由
const user require(./router/user);
app.use(/api, user);抽离用户路由模块中的处理函数
为了保证 路由模块 的纯粹性所有的 路由处理函数必须抽离到对应的 路由处理函数模块中。
在 /routerHandler/user.js 中使用 exports 对象分别向外共享如下两个 路由处理函数:
/**
* 在这里定义和用户相关的路由处理函数供 /router/user.js 模块进行调用 */// 注册用户的处理函数
exports.signin (req, res) {res.send(signin OK)
}// 登录的处理函数
exports.login (req, res) {res.send(login OK)
}将 /router/user.js 中的代码修改为如下结构
const express require(express);
const router express.Router();
const handler require(../routerHandler/user);// 注册
router.post(/signin, handler.signin);// 登录
router.post(/login, handler.login);module.exports router;joi 表单数据验证
表单验证的原则:前端验证为辅后端验证为主后端 永远不要相信 前端提交过来的 任何内容。
在实际开发中前后端都需要对表单的数据进行合法性的验证而且 后端做为数据合法性验证的最后 一个关口 在拦截非法数据方面起到了至关重要的作用。
单纯的使用 if…else… 的形式对数据合法性进行验证效率低下、出错率高、维护性差。因此 推荐使用 第三方数据验证模块 来降低出错率、提高验证的效率与可维护性 让后端程序员把更多的精力放在核心业务逻辑的处理上。
安装 joi 包为表单中携带的每个数据项定义验证规则:
npm install joi在 /middleware/expressJoi.js 中使用 joi 对象定义并向外共享表单数据验证中间件:
const joi require(joi);const expressJoi function (schemas, options { strict: false }) {// 自定义校验选项// strict 自定义属性默认不开启严格模式会过滤掉那些未定义的参数项// 如果用户指定了 strict 的值为 true则开启严格模式此时不会过滤掉那些未定义的参数项if (!options.strict) {// allowUnknown 允许提交未定义的参数项// stripUnknown 过滤掉那些未定义的参数项options { allowUnknown: true, stripUnknown: true, ...options }}// 从 options 配置对象中删除自定义的 strict 属性delete options.strict// TODO: 用户指定了什么 schema就应该校验什么样的数据return function (req, res, next) {[body, query, params].forEach(key {// 如果当前循环的这一项 schema 没有提供则不执行对应的校验if (!schemas[key]) return// 执行校验const schema joi.object(schemas[key])const { error, value } schema.validate(req[key], options)if (error) {console.log(---------------);// 校验失败throw error} else {// 校验成功把校验的结果重新赋值到 req 对应的 key 上req[key] value}})// 校验通过next()}
}module.exports expressJoi新建 /schema/user.js 用户信息验证规则模块并初始化代码如下
// hapi/joi 包为表单中携带的每个数据项定义验证规则
const joi require(joi);// * string() 值必须是字符串
// * alphanum() 值只能是包含 a-zA-Z0-9 的字符串 * min(length) 最小长度
// * max(length) 最大长度
// * required() 值是必填项不能为 undefined
// * pattern(正则表达式) 值必须符合正则表达式的规则
const username joi.string().alphanum().min(2).max(20).required();
const password joi.string().required().pattern(/^[\S]{6,16}$/);exports.schema {user: {body: {username,password}}
}修改 /router/user.js 中的代码如下
// 导入验证表单数据的中间件
const expressJoi require(../middleware/expressJoi);
// 导入需要的验证规则对象
const { schema } require(../schema/user);// 为 注册新用户 配置表单验证中间件
// 在注册新用户的路由中声明局部中间件对当前请求中携带的数据进行验证
// 数据验证通过后会把这次请求流转给后面的路由处理函数
// 数据验证失败后终止后续代码的执行并抛出一个全局的 Error 错误进入全局错误级别中间件中进行 处理
router.post(/signin, expressJoi(schema.user), handler.signin);// 登录
router.post(/login, expressJoi(schema.user), handler.login);在 app.js 的全局错误级别中间件中捕获验证失败的错误并把验证失败的结果响应给客户端:
// 配置 错误处理 中间件
const joi require(joi);
app.use(function(err, req, res, next) {// console.log(err);if (err instanceof joi.ValidationError) {// 处理数据校验错误} else {// 处理其他未知错误}return res.cc(err);
});加密处理
为了保证密码的安全性不建议在数据库以 明文 的形式保存用户密码推荐对密码进行 加密存储。使用 对用户密码进行加密优点:
加密之后的密码 无法被逆向破解同一明文密码多次加密得到的 加密结果各不相同 保证了安全性
运行如下命令安装 bcryptjs
npm i bcryptjs调用 bcrypt.hashSync(明文密码, 随机盐的长度) 方法对用户的密码进行加密处理:
const bcrypt require(bcryptjs);
// 对用户的密码,进行 bcrype 加密返回值是加密之后的密码字符串
userinfo.password bcrypt.hashSync(userinfo.password, 10);调用 bcrypt.compareSync(用户提交的密码, 数据库中的密码) 方法比较密码是 否一致返回值是布尔值(true 一致、false 不一致)
// 拿着用户输入的密码,和数据库中存储的密码进行对比
const compareResult bcrypt.compareSync(userinfo.password, results[0].password);// 如果对比的结果等于 false, 则证明用户输入的密码错误
if (!compareResult) {console.log(密码错误!);
}注册
检测表单数据是否合法检测用户名是否被占用对密码进行加密处理插入新用户
新建 /middleware/constValue.js 常量数据模块并初始化代码如下
// sql 语句
// 查找用户名
const selectUN select * from users where username ?;
// 插入用户
const insertUser insert into users set ?;module.exports {selectUN,insertUser
}修改 /routerHandler/user.js 中的代码实现最终完整的登录逻辑如下
const bcrypt require(bcryptjs);
const constValue require(../middleware/constValue);
const signin (req, res) {const info req.body;// 数据校验交给 expressJoi 中间件处理// if (info.username info.password) {db.query(constValue.selectUN, info.username, function(err, result) {if (err) {res.cc(err);} else if (result.length 0) {res.cc(用户名被占用请更换其他用户名);} else {// 调用 bcrypt.hashSync(明文密码, 随机盐的长度) 方法对用户的密码进行加密处理info.password bcrypt.hashSync(info.password, 10);db.query(constValue.insertUser, {username: info.username, password: info.password}, function(err, result) {if (err) {res.cc(err);} else if (result.affectedRows 1) {res.cc(注册成功, 0);} else {res.cc(注册失败请稍后重试);}});}});// } else {// res.cc(用户名或密码不能为空);// }
};登录
检测表单数据是否合法根据用户名查询用户的数据判断用户输入的密码是否正确生成 JWT 的 Token 字符串
在 /middleware/constValue.js 定义并导出查询用户数据的 SQL 语句以及 JWT 秘钥、加密算法、无权限表达式
// 查找用户信息
const selectInfo select id, username, nickname, email, user_pic from users where id ?;
// JWT 秘钥
const jwtSecretKey JWTSecretKeyabcABC123!#JWTSecretKey;
// JWT 加密算法
const jwtAlgorithms HS256;
// JWT 无需权限的接口 表达式
const jwtUnlessPath /^\/api\//;运行如下的命令安装生成 Token 字符串的包、解析 Token 的中间件的包:
npm i jsonwebtoke express-jwt核心注意点: 在生成 Token 字符串的时候一定要剔除 密码 和 头像 的值 通过 ES6 的高级语法快速剔除 密码 和 头像 的值将用户信息对象加密成 Token 字符串:
// 通过 ES6 的高级语法快速剔除用户敏感信息后的数据作为 token 的加密数据
const user { ...result[0], password: , user_pic: }
// token 有效期为 10 天
const token jwt.sign(user, constValue.jwtSecretKey, { expiresIn: 10d });在 app.js 中注册路由之前配置解析 Token 的中间件:
// 配置 解析 Token 的中间件
var { expressjwt } require(express-jwt);
const constValue require(./middleware/constValue);
app.use(expressjwt({secret: constValue.jwtSecretKey, algorithms: [constValue.jwtAlgorithms]
}).unless({path: [constValue.jwtUnlessPath]
}));在 app.js 中的 错误级别中间件里面捕获并处理 Token 认证失败后的错误:
app.use(function(err, req, res, next) {// console.log(err);if (err instanceof joi.ValidationError) {// 处理数据校验错误} else if (err.name UnauthorizedError) {// 处理身份认证失败的错误return res.cc(身份验证失败);} else {// 处理其他未知错误}return res.cc(err);
});修改 /routerHandler/user.js 中的代码实现最终完整的登录逻辑如下
const jwt require(jsonwebtoken);
const expressJwt require(express-jwt);
const { use } require(../router/user);
const login (req, res) {const info req.body;// 查询 username 是否存在db.query(constValue.selectUN, info.username, function(err, result) {if (err) {res.cc(err);} else if (result.length 1) {// 判断密码是否正确if (bcrypt.compareSync(info.password, result[0].password)) {// 剔除用户敏感信息后的数据作为 token 的加密数据const user { ...result[0], password: , user_pic: }console.log(user);const token jwt.sign(user, constValue.jwtSecretKey, { expiresIn: 10d });res.send({status: 0,token: Bearer token,message: 登录成功});} else {res.cc(密码错误);}} else {res.cc(登录失败);}});
};个人中心
获取用户的基本信息
初始化 路由 模块初始化 路由处理函数 模块获取用户的基本信息
创建 /router/userinfo.js 路由模块并初始化如下的代码结构:
const express require(express);
const router express.Router();
const handler require(../routerHandler/userinfo);// 获取用户信息
router.get(/userinfo, handler.userinfo);module.exports router;在 app.js 中导入并使用个人中心的路由模块:
const userinfo require(./router/userinfo);
app.use(/my, userinfo);在 /middleware/constValue.js 定义并导出查询用户信息的 SQL 语句
// 查找用户信息
const selectInfo select id, username, nickname, email, user_pic from users where id ?;创建 /routerHandler/userinfo.js 文件实现最终完整的逻辑如下
const db require(../db/index);
const constValue require(../middleware/constValue);const userinfo function(req, res) {console.log(req.auth);db.query(constValue.selectInfo, req.auth.id, function(err, result) {if (err) {res.cc(err);} else if (result.length 1) {res.send({status: 0,data: result[0],message: 用户信息获取成功});} else {res.cc(用户信息获取失败);}});
};module.exports {userinfo
};更新用户的基本信息
定义路由和处理函数验证表单数据实现更新用户基本信息的功能
具体实现见代码……
重置密码
定义路由和处理函数验证表单数据实现重置密码的功能
在 /schema/user.js 验证规则模块中定义 newPassword 的验证规则并使用 exports 向外共享如下的验证规则对象:
const password joi.string().required().pattern(/^[\S]{6,16}$/);
// 1. joi.ref(oldPassword) 表示 newPassword 的值必须和 oldPassword 的值保持一致
// 2. joi.not(joi.ref(oldPwd)) 表示 newPwd 的值不能等于 oldPwd 的值
// 3. .concat() 用于合并 joi.not(joi.ref(oldPwd)) 和 password 这两条验证规则
const newPassword joi.not(joi.ref(oldPassword)).concat(password);exports.schema {updatePassword: {body: {oldPassword: password,newPassword}}
}其他具体实现见代码……
更新用户头像
定义路由和处理函数验证表单数据实现更新用户头像的功能
在 /schema/user.js 验证规则模块中定义 user_pic 的验证规则并使用 exports 向外共享如下的验证规则对象:
// dataUri() 指的是如下格式的字符串数据:
// data:image/png;base64,VE9PTUFOWVNFQ1JFVFM
const user_pic joi.string().dataUri().required();exports.schema {updateAvatar: {body: {user_pic}}
}在 /router/userinfo.js 模块中导入需要的验证规则对象, 修改 更新用户头像 的路由:
const expressJoi require(../middleware/expressJoi);
const { schema } require(../schema/user);
// 更换头像
router.post(/updateAvatar, expressJoi(schema.updateAvatar), handler.updateAvatar);在 /middleware/constValue.js 定义并导出 更新用户头像 的 SQL 语句
// 修改头像
const updateAvatar update users set user_pic ? where id ?;处理 /routerHandler/userinfo.js 中的代码实现最终完整的登录逻辑如下
// 更换头像
const updateAvatar (req, res) {console.log(------------);console.log(req.body);db.query(constValue.updateAvatar, [req.body.user_pic, req.auth.id], (err, result) {if (err) {res.cc(err);} else if (result.affectedRows 1) {res.cc(更换头像成功, 0);} else {res.cc(更换头像失败);}})
}文章分类管理
具体实现见代码……
文章管理
新建 articles 表如下
发布新文章
初始化路由模块初始化路由处理函数模块使用 multer 解析表单数据验证表单数据实现发布文章的功能
创建 /routerHandler/article.js 路由处理函数模块并初始化如下的代码结构:
// 发布新文章的处理函数
exports.addArticle (req, res) {res.send(ok)
}创建 /router/article.js 路由模块并初始化如下的代码结构:
// 导入 express
const express require(express)
// 创建路由对象
const router express.Router()// 导入文章的路由处理函数模块
const handler require(../routerHandler/article);// 发布新文章
routor.post(/addArticle, handler.addArticle);// 向外共享路由对象
module.exports router在 app.js 中导入并使用文章的路由模块:
const article require(./router/article);
app.use(/my, article);使用 multer 解析表单数据
注意: 使用 express.urlencoded() 中间件无法解析 multipart/form-data 格式的请求体 数据。
推荐使用 multer 来解析 multipart/form-data 格式的表单数据。运行如下的终端命令在项目中安装 multer :
npm i multer在 /router/article.js 模块中导入并配置 multer :
// 导入解析 formdata 格式表单数据的包
const multer require(multer)
// 导入处理路径的核心模块
const path require(path)
// 创建 multer 的实例对象通过 dest 属性指定文件的存放路径
const upload multer({ dest: path.join(__dirname, ../uploads) })// 发布新文章的路由
// upload.single() 是一个局部生效的中间件用来解析 FormData 格式的表单数据
// 将文件类型的数据解析并挂载到 req.file 属性中
// 将文本类型的数据解析并挂载到 req.body 属性中
routor.post(/addArticle, uploads.single(cover_img), handler.addArticle);实现发布文章的功能
通过 express-joi 自动验证 req.body 中的文本数据通过 if 判断手动验证 req.file 中的 文件数据。
在 /schema/user.js 验证规则模块中定义数据验证规则并使用 exports 向外共享如下的 验证规则对象:
const id joi.number().integer().min(1).required();
const title joi.string().required();
const content joi.string().required().allow();
const state joi.string().valid(已发布, 草稿).required();exports.schema {addArticle: {body: {title,content,cate_id: id,state}}
}在 /router/article.js 模块中导入需要的验证规则对象并在路由中使用。
在 /middleware/constValue.js 定义并导出 发布文章 的 SQL 语句
// 插入文章
const insertArticle insert into articles set ?;修改 /routerHandler/article.js 中的代码实现最终完整的登录逻辑如下
const addArticle (req, res) {console.log(req.body);console.log(req.file);if (req.file req.file.fieldname cover_img) {const article {...req.body,// 文章封面在服务器端的存放路径cover_img: /uploads/${req.file.filename},pub_date: new Date(),author_id: req.auth.id};db.query(constValue.insertArticle, article, (err, result) {if (err) {res.cc(err);} else if (result.affectedRows 1) {res.cc(文章添加成功, 0);} else {res.cc(文章添加失败);}});} else {res.cc(cover_img 是必选参数);}
}