记录node所学,项目实战环节
一、数据库方面
1.使用mysql创建本次数据表
create database coderhub;
2.创建用户数据表users
CREATE TABLE IF NOT EXISTS `users`(
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(20) NOT NULL UNIQUE,
password VARCHAR(50) NOT NULL,
createAt TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updateAt TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);
二、项目文件夹创建
1.创建项目目录coderhub文件夹
2.在终端打开生成package.json文件 :npm init -y;
3.安装koa2 : npm install koa;
4.新建src文件夹并在内部新建main.js文件作为整个项目的入口
5.在src文件夹下创建app文件夹作为全局相关的文件夹(配置信息、错误处理等)
6.在src文件夹下创建controller文件夹作为全局的控制器文件夹
7.在src文件夹下创建service文件夹作为数据库操作的相关文件夹
8.在src文件夹下创建router文件夹作为路由相关的文件夹
9.在src文件夹下创建utils文件夹作为项目的工具文件夹
10.后续有需要继续创建…
此时项目文件夹预览
三、编写main.js和项目基本结构的搭建
1.在main.js中写入以下代码先把项目跑起来
const Koa=require('koa');
const app= new Koa();
app.listen(8000,()=>{
console.log("服务器启动成功~");
})
2.为了更方便的启动项目可编辑package.json文件添加对应指令,和下载node代码热更新插件nodemon
npm install nodemon --save-dev
在package.json文件中编辑scripts设置启动(start)命令
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"start": "nodemon ./src/main.js"
},
3.在终端输入npm start 项目启动成功!
四、拆解项目结构,模块化划分
1.在app文件夹中新建文件index.js并把main.js中的app代码部分移到app/index.js,在main.js文件中引入app/index.js文件(目的:在开发以后,app.use()需要很多中间件,比如:路由,错误处理等)
此时,main.js代码
const app=require('./app/index');
app.listen(8000,()=>{
console.log("服务器启动成功~")
})
此时app/index.js代码
const Koa = require('koa');
const app = new Koa();
module.exports = app
五、程序监听端口8000是写死的(需要抽离出单独的配置文件—将配置信息写入到环境变量)
1.在coderhub(也就是项目根目录)创建.env文件并写入
APP_PORT=8000
2.安装dotenv插件可以帮助我们将根目录下的.env文件加载到项目的环境变量process.env里面
npm install dotenv
3.在app文件夹下创建config.js文件导入dotenv并配置后导出
// 使用此模块可导出根目录下.env里的变量
const dotenv=require('dotenv');
dotenv.config();
// console.log(process.env.APP_PORT)
module.exports={
APP_PORT
}=process.env
然后在需要用到变量的文件中引入就可以使用了
例如:在main.js中引入app/config.js文件并使用
const app = require('./app/index');
const config = require('./app/config');
app.listen(config.APP_PORT, () => {
console.log(`coderHub服务器${config.APP_PORT}启动成功!`)
});
六、编写用户注册接口
1.安装koa-router插件
npm install koa-router
2.因为 第四部 把app.use()中间件的业务从main.js中抽离到 app/index.js文件中,所以我现在只需要在app/index.js编写对应代码
const Koa = require('koa');
const Router = require('koa-router');
const app = new Koa();
const userRouter = new Router({
prefix: '/users'
})
userRouter.post('/', (ctx, next) => {
ctx.body = "创建用户成功~"
})
app.use(userRouter.routes());
// 判断请求方式有没有,如果没有的话返回不被允许,或不被支持的接口
app.use(userRouter.allowedMethods());
module.exports = app
现在已经可以使用localhost:8000/users访问端口的,
但是还需要完善很多东西,项目的接口肯定不止一个,将来都写到这个文件中,代码将变得非常乱,还需要进一步作出抽离
3.在router文件夹下新建user.router.js文件把刚刚users路由相关的代码抽离出来
//! 用户注册接口
const Router = require('koa-router');
const userRouter = new Router({
prefix: "/users"
});
userRouter.post('/', (ctx, next) => {
ctx.body = "创建用户成功~"
})
module.exports = userRouter;
在app/index.js中导入
const Koa = require('koa');
// 导入users路由
const userRouter=require('../router/user.router')
const app = new Koa();
// 使用users路由
app.use(userRouter.routes());
// 判断请求方式有没有,如果没有的话返回不被允许,或不被支持的接口
app.use(userRouter.allowedMethods());
module.exports = app
但是,到时候users下的接口多了,router/user.router.js中的代码量也是比较大的,例如:用户账号密码校验逻辑,查询数据库逻辑等具体的处理逻辑,所以将router/user.router.js文件也进行代码的抽离拆分
4.在controller文件夹下新建user.controller.js文件,把抽离出来的逻辑写入这个文件,如下:
class UserController {
async create(ctx, next) {
// 1.获取用户请求传递的参数
// 2.查询数据库
// 3.返回客户端数据
}
}
module.exports = new UserController
在router/user/router.js中引入及使用,如下
//! 用户注册接口
const Router = require('koa-router');
const {
create
} = require('../controller/user.controller')
const userRouter = new Router({
prefix: "/users"
});
// create 使用controller/user.controller.js中的方法
// 就相当于给这个路由添加了一个中间件
userRouter.post('/', create)
module.exports = userRouter;
但是,可以看到controller/user.controller.js文件需要做的事情也是非常多的,又需要获取用户传入的数据进行处理(如:密码加密),又需要查询数据库(如:用户名是否已存在or验证用户账号密码)等,所以需要继续抽离
5.获取用户传入的参数
安装koa-bodyparser插件并在app/index.js文件中导入使用
npm install koa-bodyparser
在app/index.js中引入代码
const Koa=require('koa');
// 导入bodyparser插件
const bodyParser = require('koa-bodyparser');
// 导入路由
const userRouter=require('../router/user.router')
// 导入错误处理函数
const errorHandler=require('./error-handle')
const app=new Koa();
// 必须在注册路由中间件的前面执行这个中间件
app.use(bodyParser())
// 使用路由
app.use(userRouter.routes());
app.use(userRouter.allowedMethods())
module.exports=app
这样,就可以解析到用户传过来的数据了
回到controller/user.controller.js中获取用户传过来的参数:如下
class UserController {
async create(ctx, next) {
// 获取用户请求传递的参数
const user=ctx.request.body;
// 查询数据库
// 返回客户端数据
}
}
module.exports = new UserController
6.【抽离查询数据库这块】在service文件夹下新建user.service.js文件 如:将user存储到数据库中,或查询数据库是否已存在此用户,并返回结果
class UserService {
// 将user存储到数据库中
async create(user) {
// console.log(`将用户${user.name}保存到数据库中`)
return "创建用户成功~"
}
}
module.exports = new UserService();
然后在controller/user.controller.js文件中导入
const service = require('../service/user.service')
class UserController {
async create(ctx, next) {
// 获取用户请求传递的参数
const user=ctx.request.body;
// 查询数据库
const result = await service.create(user)
// 返回客户端数据
ctx.body=result;
}
}
module.exports = new UserController
这样就差不多完成了一个注册接口的闭合,下面就差查询添加用户信息到数据库,以及对用户传入的账号密码进行校验
七、连接数据库
1.安装mysql2
npm install mysql2
2.在app文件夹下新建database.js文件创建数据库连接池,但是mysql的用户名,密码,端口及ip尽量不要写死,单独提取出来,放到环境变量里面(也就是.env文件里)
.env代码:
APP_PORT=8000
MYSQL_HOST=localhost
MYSQL_PORT=3306
MYSQL_DATABASE=coderhub
MYSQL_ROOT=root
MYSQL_PASSWORD=root
app/database.js代码
const mysql = require('mysql2');
const config = require('./config');
const connections = mysql.createPool({
host: config.MYSQL_HOST,
port: config.MYSQL_PORT,
database: config.MYSQL_DATABASE,
user: config.MYSQL_ROOT,
password: config.MYSQL_PASSWORD
})
connections.getConnection((err, conn) => {
conn.connect((err) => {
if (err) {
console.log('连接失败', err)
} else {
console.log('数据库连接成功!')
}
})
})
module.exports = connections.promise();
现在,就可以使用连接池将用户传过来的用户信息写入数据库,实现用户注册功能了
八、将用户信息插入数据库
1.service/user.service.js文件中引入数据库连接池文件app/database.js
2.获取到用户名及密码
3.写对应sql语句
整改后代码如下:
const connection = require('../app/database');
class UserService {
// 将user存储到数据库中
async create(user) {
const {
name,
password
} = user;
const statement = `INSERT INTO users (name,password) VALUES (?,?);`
const result = await connection.execute(statement, [name, password]);
return result[0];
}
}
module.exports = new UserService();
用户信息就可以保存到数据库中了
九、用户名、密码校验
1.在src文件夹下创建middleware文件夹并在里面创建user.middleware.js文件用作校验用户账号密码的中间件文件
代码如下:
const verifyUser = async (ctx, next) => {
// 1.获取用户名和密码
const {
name,
password
} = ctx.request.body;
// 2.判断用户名或者密码不能为空
// 3.判断这次注册的用户名是没有被注册过的
await next();
}
在router/user.router.js中使用如下
//! 用户注册接口
const Router = require('koa-router');
const {
create
} = require('../controller/user.controller')
const {
verifyUser
} = require('../middleware/user.middleware')
const userRouter = new Router({
prefix: "/users"
});
// userRouter.post('/', (ctx, next) => {
// ctx.body = "创建用户成功~"
// })
// 分离出去写法
// verifyUser 验证用户,不能为空,不能被别人使用过
// verifyUser 是个在create前面的中间件 next()
// 就相当于给这个路由添加了一个中间件
userRouter.post('/', verifyUser, create)
module.exports = userRouter;
写这个模块会碰到错误处理问题,和查询用户名是否存在的问题,先解决错误处理模块,在写对应的查询sql
1.在app文件夹下创建error-handle.js文件
2.创建错误常量,在src文件夹下新建contants文件夹内部新建error-types.js用于存放错误常量
contants/error-types.js代码如下:(主要是用变量保存,防止出错写错)
const NAME_OR_PASSWORD_IS_REQUIRED = "name_or_password_id_required";
const USER_ALREADY_EXISTS = "user_already_exists";
module.exports = {
NAME_OR_PASSWORD_IS_REQUIRED,
USER_ALREADY_EXISTS
}
定义错误处理信息app/error-handle.js
代码如下:
const errorTypes = require('../constants/error-types');
// 错误处理函数
const errorHandler = (error, ctx) => {
let status, message;
switch (error.message) {
case errorTypes.NAME_OR_PASSWORD_IS_REQUIRED:
status = 400; //Bad Request
message = "用户名或者密码不能为空~";
break;
case errorTypes.USER_ALREADY_EXISTS:
status = 409; //conflict
message = "用户名已存在~"
break;
default:
status = 404;
message = "NOT FOUND"
}
ctx.status = status;
ctx.body = message
}
module.exports = errorHandler
在验证出错的地方引入错误常量并使用(如,上面校验用户账号密码的时候就可以这样写)
整改user/user.middleware.js代码如下:
const errorTypes = require('../constants/error-types')
const verifyUser = async (ctx, next) => {
// 1.获取用户名和密码
const {
name,
password
} = ctx.request.body;
// 2.判断用户名或者密码不能为空
if (!name || !password) {
// 返回错误信息
const error = new Error(errorTypes.NAME_OR_PASSWORD_IS_REQUIRED)
console.log('发生了错误')
return ctx.app.emit('error', error, ctx)
}
// 3.判断这次注册的用户名是没有被注册过的
await next();
}
这里使用了ctx.app.emit(“error”,error,ctx);
所以要在app/index.js文件夹下监听错误,需要引入刚刚创建的错误处理函数方法app/error-handle.js的errorHandler()
app/index.js代码整理如下:
const Koa=require('koa');
const bodyParser = require('koa-bodyparser');
// 导入路由
const userRouter=require('../router/user.router')
// 导入错误处理函数
const errorHandler=require('./error-handle')
const app=new Koa();
app.use(bodyParser())
// 使用路由
app.use(userRouter.routes());
app.use(userRouter.allowedMethods())
// 错误处理
app.on('error',errorHandler)
module.exports=app
解决了用户名与密码校验,在判断用户名是否已存在
在service/user.service.js文件中添加对应查询方法
整改代码如下:
const connection = require('../app/database');
class UserService {
// 将user存储到数据库中
async create(user) {
const {
name,
password
} = user;
const statement = `INSERT INTO users (name,password) VALUES (?,?);`
const result = await connection.execute(statement, [name, password]);
return result[0];
}
// 查询用户名是否存在
async getUserByName(name) {
const statement = `SELECT * FROM users WHERE name=?;`;
const result = await connection.execute(statement, [name]);
return result[0];
}
}
module.exports = new UserService();
middleware/user.middleware.js代码引入getUserByName方法
整改代码如下:
const errorTypes = require('../constants/error-types')
const service = require('../service/user.service')
const verifyUser = async (ctx, next) => {
// 1.获取用户名和密码
const {
name,
password
} = ctx.request.body;
// 2.判断用户名或者密码不能为空
if (!name || !password) {
// 返回错误信息
const error = new Error(errorTypes.NAME_OR_PASSWORD_IS_REQUIRED)
console.log('发生了错误')
return ctx.app.emit('error', error, ctx)
}
// 3.判断这次注册的用户名是没有被注册过的
const result = await service.getUserByName(name);
if (result.length) {
const error = new Error(errorTypes.USER_ALREADY_EXISTS);
return ctx.app.emit('error', error, ctx);
}
await next();
}
module.exports = {
verifyUser
}
此时注册接口基本完全结束,但用户密码还是明文显示的
十、md5用户密码加密
1.和密码校验差不多,这也是一个中间件,需要在router/user.router.js注册路由那里使用
2.在utils文件夹下创建password-handle.js文件,用于加密用户密码
代码如下:
// node自带加密模块crypto
const crypto = require('crypto');
const md5password = (password) => {
const md5 = crypto.createHash('md5');
// digest 默认为二进制 参数hex代表转成16进制
const result = md5.update(password).digest('hex');
return result;
}
module.exports = md5password;
2.在middleware/user.middleware.js文件中引入utils/password-handle.js中的md5password 方法,然后添加handlePassword函数
整改后,代码如下:
const errorTypes = require('../constants/error-types')
const service = require('../service/user.service')
const md5password = require('../utils/password-handle')
const verifyUser = async (ctx, next) => {
// 1.获取用户名和密码
const {
name,
password
} = ctx.request.body;
// 2.判断用户名或者密码不能为空
if (!name || !password) {
// 返回错误信息
const error = new Error(errorTypes.NAME_OR_PASSWORD_IS_REQUIRED)
console.log('发生了错误')
return ctx.app.emit('error', error, ctx)
}
// 3.判断这次注册的用户名是没有被注册过的
const result = await service.getUserByName(name);
if (result.length) {
const error = new Error(errorTypes.USER_ALREADY_EXISTS);
return ctx.app.emit('error', error, ctx);
}
await next();
}
const handlePassword = async (ctx, next) => {
const {
password
} = ctx.request.body
// password=密码加密函数(password)
ctx.request.body.password = md5password(password);
await next();
}
module.exports = {
verifyUser,
handlePassword
}
3.在router/user.router.js中引入刚刚在middleware/user.middleware.js文件中创建的handlePassword中间件并使用
整改代码如下:
//! 用户注册接口
const Router = require('koa-router');
const {
create
} = require('../controller/user.controller')
const {
verifyUser,
handlePassword
} = require('../middleware/user.middleware')
const userRouter = new Router({
prefix: "/users"
});
// userRouter.post('/', (ctx, next) => {
// ctx.body = "创建用户成功~"
// })
// 分离出去写法
// verifyUser 验证用户,不能为空,不能被别人使用过
// verifyUser 是个在create前面的中间件 next()
// handlePassword 用于加密用户密码的中间件
// 就相当于给这个路由添加了一个中间件
userRouter.post('/', verifyUser, handlePassword, create)
module.exports = userRouter;