海珠高端网站建设,桂林论坛天涯社区,诸几建设银行网站,万户网站建设我们利用nodeexpressmysql开发接口#xff0c;对数据库数据进行简单增、删、查改等操作。
接口是什么#xff1f; 接口是 前后端通信的桥梁 简单理解#xff1a;一个接口就是 服务中的一个路由规则 #xff0c;根据请求响应结果 接口的英文单词是 API (Application Progra…我们利用nodeexpressmysql开发接口对数据库数据进行简单增、删、查改等操作。
接口是什么 接口是 前后端通信的桥梁 简单理解一个接口就是 服务中的一个路由规则 根据请求响应结果 接口的英文单词是 API (Application Program Interface)所以有时也称之为 API 接口 这里的接口指的是『数据接口』 与编程语言JavaGo 等中的接口语法不同 下面做一个书城商店api案例大概有个人信息模块书产品分类模块购物车模块信息资讯利用宝塔工具将api部署到服务器利用cdn访问图片富文本编辑器使用等。vue3一边做uniapp开发小程序一边写接口可能会写后台管理系统看时间。为了节约时间不用ts后面有时间再用ts优化ᥬ᭄。
工具
数据库可视化 接口测试工具Postman 1、初始化
我们这里利用nodeexpressmysql开发一个简单的书城商店API。后面会使用result API规范接口
1、创建项目
新建文件夹server 安装依赖 npm init -y 安装依赖express npm i express 新建app.js作为入口文件创建服务器实例
// 导入 express 模块const express require(express)// 创建 express 的服务器实例const app express()// write your code here...
// 调用 app.listen 方法指定端口号并启动web服务器app.listen(8000, function () {console.log(server running at http://127.0.0.1:8000)
})安装nodemon npm i nodemon 2、配置跨域依赖 npm i cors 在app.js中导入并配置cors中间件
// 导入 cors 中间件const cors require(cors)// 将 cors 注册为全局中间件app.use(cors())
3、配置解析表单数据的中间件
通过如下的代码配置解析application/x-www-form-urlencoded格式的表单数据的中间 件
app.use(express.urlencoded({ extended: false }))
4、初始化路由
在项目根目录中新建 router 文件夹用来存放所有的 路由 模块
路由模块中只存放客户端的请求与处理函数之间的映射关系
在项目根目录中新建 router_handler 文件夹用来存放所有的 路由处理函数模块
路由处理函数模块中专门负责存放每个路由对应的处理函数 例如新建用户user路由模块 在入口文件中注册路由 访问接口确保服务在运行没有运行访问不了服务 2.项目初始化 我们需要在数据库进行存储数据对数据进行操作这里使用Mysql后面改进会使用一个更好用的MongoDB 1.配置MySQL npm i mysql 2.创建数据库book并创建一个用户users表格
新建数据库 创建users表格 2.实现连接数据库功能
新建db文件夹在其创建index.js连接数据库 // 导入 mysql 模块const mysql require(mysql)// 创建数据库连接对象const db mysql.createPool({host: 127.0.0.1,user: root,password: root,database: book,
})// 向外共享 db 数据库连接对象module.exports db3.实现注册功能
1.实现步骤
1. 检测表单数据是否合法
2. 检测用户名是否被占用
3. 对密码进行加密处理
4. 插入新用户
2.检测表单数据是否合法
1. 判断用户名和密码是否为空
// 接收表单数据const userinfo req.body// 判断数据是否合法if (!userinfo.username || !userinfo.password) {return res.send({ status: 1, message: 用户名或密码不能为空 })
}2 检测用户名是否被占用
1. 导入数据库操作模块
const dbrequire(../db/index)
2. 定义 SQL 语句
2.定义SQL语句
const sqlselect * from users where username?
3. 执行 SQL 语句并根据结果判断用户名是否被占用
db.query(sql, [userinfo.username], function (err, results) {// 执行 SQL 语句失败if (err) {return res.send({ status: 1, message: err.message })}// 用户名被占用if (results.length 0) {return res.send({ status: 1, message: 用户名被占用请更换其他用户名 })}// TODO: 用户名可用继续后续流程...})
4.对密码进行加密处理
为了保证密码的安全性不建议在数据库以明文的形式保存用户密码推荐对密码进行加密 存储
在当前项目中使用bcryptjs对用户密码进行加密优点
加密之后的密码无法被逆向破解
同一明文密码多次加密得到的加密结果各不相同保证了安全性 npm i bcryptjs 在/router_handler/user.js中导入bcryptjs const bcryptrequire(bcryptjs) 在注册用户的处理函数中确认用户名可用之后调用bcrypt.hashSync(明文密码, 随机盐的 长度)方法对用户的密码进行加密处理
// 对用户的密码,进行 bcrype 加密返回值是加密之后的密码字符串
userinfo.passwordbcrypt.hashSync(userinfo.password, 10)
5.插入新用户
定义插入用户的 SQL 语句
const sqlinsert into users set ?
调用db.query()执行 SQL 语句插入新用户
db.query(sql, { username: userinfo.username, password: userinfo.password }, function
(err, results) {// 执行 SQL 语句失败if (err) return res.send({ status: 1, message: err.message })// SQL 语句执行成功但影响行数不为 1if (results.affectedRows ! 1) {return res.send({ status: 1, message: 注册用户失败请稍后再试 })}// 注册成功res.send({ status: 0, message: 注册成功 })
})
全部代码
/*** 在这里定义和用户相关的路由处理函数供 /router/user.js 模块进行调用*/
const db require(../db/index)
const bcrypt require(bcryptjs)// 注册用户的处理函数exports.regUser (req, res) {// 接收表单数据const userinfo req.body// 判断数据是否合法if (!userinfo.username || !userinfo.password) {return res.send({ status: 1, message: 用户名或密码不能为空 })}const sql select * from users where username?db.query(sql, [userinfo.username], function (err, results) {// 执行 SQL 语句失败if (err) {return res.send({ status: 1, message: err.message })}// 用户名被占用if (results.length 0) {return res.send({ status: 1, message: 用户名被占用请更换其他用户名 })}// 对用户的密码,进行 bcrype 加密返回值是加密之后的密码字符串userinfo.password bcrypt.hashSync(userinfo.password, 10)const sql insert into users set ?db.query(sql, { username: userinfo.username, password: userinfo.password }, function(err, results) {// 执行 SQL 语句失败if (err) return res.send({ status: 1, message: err.message })// SQL 语句执行成功但影响行数不为 1if (results.affectedRows ! 1) {return res.send({ status: 1, message: 注册用户失败请稍后再试 })}// 注册成功res.send({ status: 0, message: 注册成功 })})})}// 登录的处理函数exports.login (req, res) {res.send(login OK)
}为了方便测试接口我们允许其他字段为空 一开始表里没有数据 我们去测试注册功能一定要配置解析表单数据的中间件 注册功能完成
4.实现登录功能
核心实现思路调用bcrypt.compareSync(用户提交的密码, 数据库中的密码)方法比较密码是 否一致
返回值是布尔值true 一致、false 不一致 const userinfo req.bodyconst sql select * from users where username?db.query(sql, userinfo.username, function (err, results) {// 执行 SQL 语句失败if (err) return res.cc(err)// 执行 SQL 语句成功但是查询到数据条数不等于 1if (results.length ! 1) return res.send(登录失败)// TODO判断用户输入的登录密码是否和数据库中的密码一致// 拿着用户输入的密码,和数据库中存储的密码进行对比const compareResult bcrypt.compareSync(userinfo.password, results[0].password)// 如果对比的结果等于 false, 则证明用户输入的密码错误if (!compareResult) {return res.send(登录失败)}return res.send(登录成功)}) 基础登录接口实现
5、认识JWT
首先我们需要认识token
5.1token 是什么 token 是服务端生成并返回给 HTTP 客户端的一串加密字符串 token 中保存着 用户信息 5.2 token 的作用 实现会话控制可以识别用户的身份主要用于移动端 APP 5.3 token 的工作流程 5.4 token 的特点
服务端压力更小 数据存储在客户端 相对更安全 数据加密 可以避免 CSRF跨站请求伪造 扩展性更强 服务间可以共享 增加服务节点更简单
5.5生成 JWT 的 Token 字符串
JWTJSON Web Token 是目前最流行的跨域认证解决方案可用于基于token的身份验证 JWT 使 token 的生成与校验更规范
我们可以使用jsonwebtoken 包来操作 token
安装生成 Token 字符串的包 npm i jsonwebtoken 在/router_handler/user.js模块的头部区域导入jsonwebtoken包
// 用这个包来生成 Token 字符串
constjwtrequire(jsonwebtoken) 创建config.js文件并向外共享加密和还原 Token 的jwtSecretKey字符串
module.exports { jwtSecretKey: itheima No1. ^_^, } 将用户信息对象加密成 Token 字符串
// 导入配置文件const config require(../config)// 生成 Token 字符串const tokenStr jwt.sign(user, config.jwtSecretKey, {expiresIn: 10h, // token 有效期为 10 个小时})将生成的 Token 字符串响应给客户端
res.send({status: 0,message: 登录成功,// 为了方便客户端使用 Token在服务器端直接拼接上 Bearer 的前缀token: Bearer tokenStr,
})5.6配置解析 Token 的中间件
安装解析 Token 的中间件
npm i express-jwt
在app.js中注册路由之前配置解析 Token 的中间件
// 导入配置文件const config require(./config)// 解析 token 的中间件const expressJWT require(express-jwt)// 使用 .unless({ path: [/^\/api\//] }) 指定哪些接口不需要进行 Token 的身份认证app.use(expressJWT({ secret: config.jwtSecretKey }).unless({ path: [/^\/api\//] 在app.js中的错误级别中间件里面捕获并处理 Token 认证失败后的错误
// 错误中间件app.use(function (err, req, res, next) {// 省略其它代码...// 捕获身份认证失败的错误if (err.name UnauthorizedError) return res.cc(身份认证失败)// 未知错误...})分析
是Express版本和express-jwt的版本不兼容的可能比较大因为这两个版本都是安装最新版本的很可能会出现这个问题通过尝试修改版本并解决了报错问题。 下面附上修改后的版本
express: ^4.17.1,express-jwt: ^5.3.3,jsonwebtoken: ^8.5.1, 重新下载依赖
npm i 认识
6、实现数据库命令增删查改的封装
为什么封装 这样我们一直写数据库操作代码重复太多写项目眼花缭乱
封装目的解决代码冗余
首先我们需要理解ORM 可以在我ORM介绍博客中招 开发效率快
**先学一下ORM 将db/index.js修改为如下代码
const mysql require(mysql);
// 数据库连接设置
let db {host: localhost,//数据库地址port: 3306,user: root,//用户名没有可不填password: root,//密码没有可不填database: book//数据库名称
}let options {};
let tableSQL ;
let isConnect false;function Model(name, option) {this.name name;this.option option;
};/**
* description: 查询数据
* param {} options可选参数
* param {Function} callback :req,results{}
*/
Model.prototype.find function (options, callback) {if (!isConnect) {console.log(options.constructor);this.connect(err {isConnect true;var str ;if (!callback) {str select * from ${this.name};callback options;} else if (options.constructor Array) {str select ${options.join()} from ${this.name};} else if (options.constructor Object) {str select ${options.arr.join()} from ${this.name} where ${options.where};} else {str select * from ${this.name} where ${options};};//console.log(str);connection.query(str, (error, results, fields) {callback(error, results, fields);});return this;})} else {var str ;if (!callback) {str select * from ${this.name};callback options;} else if (options.constructor Array) {str select ${options.join()} from ${this.name};} else {str select * from ${this.name} where ${options};};//console.log(str);connection.query(str, (error, results, fields) {callback(error, results, fields);});return this;}};/**
* description: 分页查询
* param {Object} options : { where:查询条件, number: 当前页数 , count : 每页数量 }
* return:
*/
Model.prototype.limit function (options, callback) {var str ;if (!options.where) {str select * from ${this.name} limit ${(options.number - 1) * options.count},${options.count};} else {str str select * from ${this.name} where ${options.where} limit ${(options.number - 1) * options.count},${options.count};};console.log(str);connection.query(str, (error, results, fields) {callback(error, results, fields);});return this;
};/**
* description: 插入数据
* param {Object} obj:对象或者数组
* param {Function} callback :req,results{}
*/
Model.prototype.insert function (obj, callback) {if (!isConnect) {this.connect(err {if (err) {throw err;} else {connection.query(tableSQL, (error, results, fields) {if (Array.isArray(obj)) {for (var i 0; i obj.length; i) {this.insertObj(obj[i], callback)}} else {this.insertObj(obj, callback)}});}});} else {if (Array.isArray(obj)) {for (var i 0; i obj.length; i) {this.insertObj(obj[i], callback)}} else {this.insertObj(obj, callback)}}};Model.prototype.insertObj function (obj, callback) {let keys [];let values ;for (var key in obj) {keys.push(key);values ${obj[key]},;};values values.replace(/,$/, );let str INSERT INTO ${this.name} (${keys.join()}) VALUES (${values});connection.query(str, (error, results, fields) {callback(error, results);});
}/**
* description: 更新数据
* param {Object} option可选参数 更新条件
* param {Object} obj 修改后的数据
* param {Function} callback :req,results{}
*/
Model.prototype.update function (option, obj, callback) {let str ;if (arguments.length 2) {callback obj;obj option;str UPDATE ${this.name} SET ;for (var key in obj) {str ${key}${obj[key]}, ;};str str.replace(/(, )$/, );} else {str UPDATE ${this.name} SET ;for (var key in obj) {str ${key}${obj[key]}, ;};str str.replace(/(, )$/, );str where ${option};};console.log(str);connection.query(str, (error, results, fields) {callback(error, results, fields);});return this;};/**
* description: 删除数据
* param {Object} option可选参数 删除条件
* param {Function} callback :req,results{}
*/
Model.prototype.delete function (option, callback) {var str ;if (!callback) {str delete from ${this.name};callback option;} else {str delete from ${this.name} where ${option};};console.log(str);connection.query(str, (error, results, fields) {callback(error, results, fields);});return this;
};/**
* description: 执行sql语句
* param {String} str : sql语句
* param {Function} callback :req,results{}
*/
Model.prototype.sql function (str, callback) {connection.query(str, (error, results, fields) {callback(error, results, fields);});return this;
};/**
* description: 删除model表格 慎用
* param {type}
* return:
*/
Model.prototype.drop function (callback) {connection.query(DROP TABLE ${this.name}, (error, results, fields) {callback(error, results, fields);});return this;
};//连接检测
Model.prototype.connect function (callback) {let p1 new Promise((resolve, reject) {connection.connect((err) {if (err) {//console.log(err.stack);//console.log(err);//42000 数据库不存在 28000账号错误//console.log(err.sqlState);//42000 数据库不存在 28000账号错误reject(err);} else {resolve();}});});p1.then(() {callback(null);}, err {if (err.sqlState 42000) {createDatabase(callback);} else if (err.sqlState 28000) {callback(数据库账号或密码错误);} else {callback(err);}});
};//创建数据库
let createDatabase function (callback) {let p2 new Promise((resolve, reject) {connection mysql.createConnection({host: options.host,//数据库地址port: options.port,//端口号user: options.user,//用户名没有可不填password: options.password,//密码没有可不填});connection.connect((err) {//if (err) throw error;if (err) {reject(err);} else {resolve();}});});let p3 new Promise((resolve, reject) {connection.query(CREATE DATABASE ${options.database}, (err, results, fields) {//if (error) throw error;if (err) {reject(err);} else {resolve();}});});let p4 new Promise((resolve, reject) {connection.query(use ${options.database}, (err, results, fields) {if (err) {reject(err);} else {resolve();}});});let pAll Promise.all([p2, p3, p4]);pAll.then(() {callback(null);}).catch((err) {callback(err);});
}let orm {/*** description:连接数据库* param {String} host: 主机名 默认localhost* param {Number} port: 端口号 默认3306* param {String} user: 用户名 * param {String} password: 密码 * param {String} database: 数据库名称 默认og* return: */connect: function ({ host localhost, port 3306, user , password , database og }) {databaseName database;//全局存储当前数据库名称options {host,//数据库地址port,//端口号user,//用户名没有可不填password,//密码没有可不填database//数据库名称};connection mysql.createConnection(options);},/*** description:创建model (表格模型对象)* param {String} name:表格名称* param {Object} options:表格数据结构* return: Model对象负责数据库增删改查*/model: function (name, options) {let str id int primary key auto_increment, ;for (var key in options) {if (options[key] Number) {str ${key} numeric,;} else if (options[key] Date) {str ${key} timestamp,;} else {str ${key} varchar(255),;}};str str.replace(/,$/, );//console.log(CREATE TABLE ${name} (${str}));//console.log(str);tableSQL CREATE TABLE ${name} (${str});return new Model(name, options);}
};orm.connect(db);module.exports orm;新建文件夹handleDB/index.js数据库操作进行封装
const db require(../db/index)function handleDB(res, tableName, methodName, errorMsg, n1, n2) {//数据库操作let Model db.model(tableName); // 表映射为模型, 参数是要操作的这个表的表名let results; //results就收查询到的数据try {results new Promise((resolve, reject) {// Model.find(id15,(err,data){ //直接调用不封装if (!n1) {Model[methodName]((err, data) { //封装的时候使用这种格式 if (err) reject(err); // 失败的时候调用reject()resolve(data); //成功的时候调用resolve() });return}//能够给执行到这里说明n1已经传进来了if (!n2) {Model[methodName](n1, (err, data) { //封装的时候使用这种格式 if (err) reject(err); // 失败的时候调用reject()resolve(data); //成功的时候调用resolve() });return}//能够给执行到这里说明n1和n2已经传进来了Model[methodName](n1, n2, (err, data) { //封装的时候使用这种格式 if (err) reject(err); // 失败的时候调用reject()resolve(data); //成功的时候调用resolve() });})} catch (err) {console.log(err); // 给后台看到的res.send({ errMsg: errorMsg }); //给前端送过去的return}return results
}module.exports handleDB
完善获取数据库数据的写法
asyncawait版本
app.get(/,(req, res){(async function(){let Student db.model(students); //获取学生表模型let results await new Promise((resolve,reject){Student.find(id3,(err,data){if(err)reject(err);resolve(data);}); })res.send(results);})();
})
带捕获异常的版本
(async function(){let Student db.model(students); //获取学生表模型let results try{results await new Promise((resolve,reject){Student.find(id3,(err,data){if(err)reject(err);resolve(data);}); })}catch(err){console.log(err);res.send({errmsg:数据库查询出错})return}res.send(results);
})();
测试注意先把登录和注册接口注释掉 7、注册和登录功能的优化 对注册逻辑代码进行修改 修改代码为
// 注册用户的处理函数exports.regUser (req, res) {(async function () {// 1.接收表单数据console.log(req);const userinfo req.bodyconsole.log(userinfo);// 2.判断数据是否合法if (!userinfo.username || !userinfo.password) {return res.send({ status: 1, message: 用户名或密码不能为空 })}// 3.查询数据库看看是否存在这个用户名let result await handleDB(res, users, find, users数据库查询出错, username${userinfo.username})//username${userinfo.username}这里注意加引号// 4.如果已经存在返回用户名已经被注册 returnif (result.length 0) {res.send({ status: 1, message: 用户名已经被注册,请更换其他用户名 });return}// 5、不存在则往数据库中新增加一条记录// 对用户的密码,进行 bcrype 加密返回值是加密之后的密码字符串userinfo.password bcrypt.hashSync(userinfo.password, 10)let result2 await handleDB(res, users, insert, users数据库新增出错, {username: userinfo.username,password: userinfo.password,})// SQL 语句执行成功但影响行数不为 1if (result2.affectedRows ! 1) {return res.send({ status: 1, message: 注册用户失败请稍后再试 })}res.send({ status: 0, message: 注册成功 })})();
} 对登录逻辑代码进行修改
原来代码 db.query(sql, userinfo.username, function (err, results) {// 执行 SQL 语句失败if (err) return res.cc(err)// 执行 SQL 语句成功但是查询到数据条数不等于 1if (results.length ! 1) return res.send(登录失败)// TODO判断用户输入的登录密码是否和数据库中的密码一致// 拿着用户输入的密码,和数据库中存储的密码进行对比const compareResult bcrypt.compareSync(userinfo.password, results[0].password)// 生成 Token 字符串// 剔除完毕之后user 中只保留了用户的 id, username, nick_name, email等 属性的值const user { ...results[0], password: , }const tokenStr jwt.sign(user, config.jwtSecretKey, {expiresIn: 10h, // token 有效期为 10 个小时})// 如果对比的结果等于 false, 则证明用户输入的密码错误if (!compareResult) {return res.send(登录失败)}return res.send({status: 0,message: 登录成功,// 为了方便客户端使用 Token在服务器端直接拼接上 Bearer 的前缀token: Bearer tokenStr,})})
修改后代码
// 登录的处理函数exports.login (req, res) {(async function () {// 1、获取参数判空 用户名、密码let { username, password } req.bodyif (!username || !password) {res.send({ status: 1, message: 缺少必传参数 });return}// 2、查询数据库,验证手机号码是不是已经注册了let result await handleDB(res, users, find, users数据库查询出错, username${username});// 3、如果没有注册则返回用户名未注册登录失败 returnif (result.length 0) {res.json({ status: 1, message: 用户名未注册无法登录 });return}// 4、校验密码是否正确,如果不正确 就return// 拿着用户输入的密码,和数据库中存储的密码进行对比const compareResult bcrypt.compareSync(password, result[0].password)// 如果对比的结果等于 false, 则证明用户输入的密码错误if (!compareResult) {return res.send({ status: 1, message: 用户名或者密码错误登录失败 })}// 剔除完毕之后user 中只保留了用户的 id, username, 等 属性的值const user { ...result[0], password: , }const tokenStr jwt.sign(user, config.jwtSecretKey, {expiresIn: 10h, // token 有效期为 10 个小时})return res.send({status: 0,message: 登录成功,// 为了方便客户端使用 Token在服务器端直接拼接上 Bearer 的前缀token: Bearer tokenStr,})})();} 在配置cookie和session获取登录用户id保存在会话之中差不多登录和注册接口功能基本完成了。
8.添加cookie和session的配置
详细了解看会话控制篇章
先安装cookie-parser和cookie-session
npm add cookie-parser
npm add cookie-session
app.js中添加设置
const cookieParser require(cookie-parser);
const cookieSession require(cookie-session);// cookie的注册
app.use(cookieParser());
// session的注册
app.use(cookieSession({name:my_session,keys:[%$#^^%TSFR#$TRGDRG$%GFDG%^$#%#^GFDGRDHG$#^Y%],maxAge:1000*60*60*24*2 //过期时间设置为2天
}));// 后面就可以通过 req.session[属性名] 值来 设置session了 测试 设置session用户id 在测试接口中
获取session中的用户id
这样写前端访问其它接口时访问不到已经在session设置的用户ID我忘了怎么解决了后面写项目遇到再解决吧。 修改登录功能如下 因为我们不可能将密码暴露所以我们需要将密码设为空 password: 最后我们再完善一下数据库 登录注册功能实现完毕接下里写个人信息模块