# DataServer **Repository Path**: libaile/data-server ## Basic Information - **Project Name**: DataServer - **Description**: 一个以 node.js + koa 框架搭建的后台服务 - **Primary Language**: NodeJS - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2023-03-20 - **Last Updated**: 2023-03-26 ## Categories & Tags **Categories**: Uncategorized **Tags**: Nodejs, MySQL, Koa, JWT ## README c一 项目初始化 ### 1.npm初始化 ``` npm init -y 生成package.json 文件 ``` ### 2.git 初始化 ``` git init 生成.git 隐藏文件,git的本地仓库 ``` ## 二 搭建项目 ### 1.安装koa ``` npm install koa --save ``` ### 2.编写最基本的app 创建 `src/main.js` ### 3.测试 ` node ./src/main.js ` ## 三.项目的基本优化 ### 1.自动重启服务 ``` npm i nodemon ``` 编写package.json 运行脚本 ``` "script":{ "start":"nodemon ./src/main.js" } ``` 执行 `npm start ` 启动服务 ### 2.读取配置文件 安装 dotenv ``` npm i dotenv ``` 创建.enc文件 ``` APP_PORT = 8000 ``` 创建`src/config/config.default.js` ## 四.添加路由 路由:根据不同的URL,调用对应处理函数 ### 1.安装 koa-router ``` npm i koa-router ``` 步骤: 1.导入包 2.实例化对象 3.编写路由 4.注册中间件 ### 2.编写路由 创建 `src/router`目录,编写user.route.js ``` const Router = require('koa-router'); const router = new Router({ prefix:'/users' }); router.get('/', function (ctx, next) { ctx.body = 'hello user' }); module.exports = router; ``` ### 3.改写main.js ``` const Koa = require('koa'); const app = new Koa(); const userRouter = require('./router/user.route'); const { APP_PORT } = require('./config/config.default'); app .use(userRouter.routes()) app.listen(3000, () => { console.log(`server is running on http://localhost:${APP_PORT}`); }) ``` ## 五.目录结构优化 ### 1.将 http 服务 和 app业务 拆分 创建` src/app/index.js ` ``` const Koa = require('koa'); const app = new Koa(); const userRouter = require('../router/user.route'); app.use(userRouter.routes()); module.exports = app; ``` 改写 `main .js` ``` const { APP_PORT } = require('./config/config.default'); const app = require('./app'); app.listen(APP_PORT, () => { console.log(`server is running on http://localhost:${APP_PORT}`); }) ``` ### 2.将路由和控制器拆分 路由:解析URL,分发给控制器对应的方法 ``` const Router = require('koa-router'); const { register, login } = require('../controller//user.controller'); const router = new Router({ prefix:'/users' }); // 注册接口 router.post('/register', register); // 登录接口 router.post('/login', login); module.exports = router; ``` 控制器:处理不同的业务 创建` controller/users.controller.js` ``` class UserController { async register(ctx, next) { ctx.body = { iRet:0, message:'用户注册成功'}; } async login(ctx, next) { ctx.body = { iRet:0, message:'用户登录成功'}; } } module.exports = new UserController(); ``` ## 六.解析body ### 1.安装 koa-body ```` npm i koa-body ``` ### 2.注册中间件 改写 app/index.js ![image-20230319150351909](C:\Users\baileli\AppData\Roaming\Typora\typora-user-images\image-20230319150351909.png) ### 3.解析请求数据 改写 `user.controller.js` ``` const { createUser } = require('../service/user.service'); class UserController { async register(ctx, next) { // 1.获取数据 const { user_name, password } = ctx.request.body; // 2.操作数据库 const res = await createUser(user_name, password); // 3.返回结果 ctx.body = res; } async login(ctx, next) { ctx.body = { iRet:0, message:'用户登录成功'}; } } module.exports = new UserController(); ``` ### 4.拆分 service 层 service层主要是做数据库处理 ``` class UserService { async createUser(user_name, password) { // tode:写入数据库 return '写入数据库成功' } } module.exports = new UserService(); ```` ## 七. 数据库操作 sequelize ORM 数据库工具 ORM:对象关系映射 - 数据表映射(对应)一个类 - 数据表中的数据行(记录)对应一个对象 - 数据表字段对应对象的属性 - 数据表的操作对应对象的方法 ### 1.安装 sequelize ``` npm i mysql2 sequelize --save ``` ### 2.连接数据库 创建 `src/db/req.js` ``` const { MYSQL_HOST, MYSQL_PORT, MYSQL_USER, MYSQL_PWD , MYSQL_DB } = require('../config/config.default') const { Sequelize } = require('sequelize'); const seq = new Sequelize(MYSQL_DB, MYSQL_USER, MYSQL_PWD, { localhost: MYSQL_HOST + MYSQL_PORT, dialect: 'mysql' }); // 测试数据库是否连接成功 seq.authenticate().then( () => { console.log('数据库连接成功') }).catch((err) => { console.log(err) }) module.exports = seq; ``` ### 3.编写配置文件 ```javascript APP_PORT = 8000 MYSQL_HOST = 127.0.0.1 MYSQL_PORT = 3306 MYSQL_USER = root MYSQL_PWD = root MYSQL_DB = dataserver ``` ## 八.创建User模型 ### 1.拆分Model层 ```javascript const { DataTypes } = require('sequelize'); const seq = require('../db/seq'); // 创建模型 (Model data_user => data_users) const User = seq.define('data_user', { // id 会被 sequelize 自动创建、管理 user_name: { type: DataTypes.STRING, allowNull: false, unique: true, comment: '用户名,唯一' }, password: { type: DataTypes.CHAR(64), allowNull: false, comment: '用户密码' }, is_admin: { type: DataTypes.BOOLEAN, allowNull: false, defaultValue: 0, comment: '是否为管理员 0 非管理员(默认) 1 管理员' }, }, { // timestamps: false // 是否生成事件戳 }); // 强制同步数据库(创建数据表) // User.sync({ force: true }); module.exports = User; ``` ## 九. 添加用户入库 所有数据库的操作都在 Service 层完成, Service 调用 Model 完成数据库操作 改写`src/service/user.service.js` ```javascript const User = require('../model/use.model') class UserService { async createUser(user_name, password) { // 插入数据 // User.create({ // // 表的字段 // user_name: user_name, // password: password // }) // await表达式: promise对象的值 const res = await User.create({ user_name, password }) // console.log(res) return res.dataValues } } module.exports = new UserService() ``` 同时, 改写`user.controller.js` ```javascript const { createUser } = require('../service/user.service') class UserController { async register(ctx, next) { // 1. 获取数据 // console.log(ctx.request.body) const { user_name, password } = ctx.request.body // 2. 操作数据库 const res = await createUser(user_name, password) // console.log(res) // 3. 返回结果 ctx.body = { code: 0, message: '用户注册成功', result: { id: res.id, user_name: res.user_name, }, } } async login(ctx, next) { ctx.body = '登录成功' } } module.exports = new UserController() ``` ## 十.错误处理 在控制器中, 对不同的错误进行处理, 返回不同的提示错误提示, 提高代码质量 ``` const { createUser, getUerInfo } = require('../service/user.service') class UserController { async register(ctx, next) { // 1. 获取数据 // console.log(ctx.request.body) const { user_name, password } = ctx.request.body // 合法性 if (!user_name || !password) { console.error('用户名或密码为空', ctx.request.body) ctx.status = 400 ctx.body = { code: '10001', message: '用户名或密码为空', result: '', } return } // 合理性 if (getUerInfo({ user_name })) { ctx.status = 409 ctx.body = { code: '10002', message: '用户已经存在', result: '', } return } // 2. 操作数据库 const res = await createUser(user_name, password) // console.log(res) // 3. 返回结果 ctx.body = { code: 0, message: '用户注册成功', result: { id: res.id, user_name: res.user_name, }, } } async login(ctx, next) { ctx.body = '登录成功' } } module.exports = new UserController() ``` 在 service 中封装函数 ``` const User = require('../model/use.model') class UserService { async createUser(user_name, password) { // 插入数据 // await表达式: promise对象的值 const res = await User.create({ user_name, password }) // console.log(res) return res.dataValues } async getUerInfo({ id, user_name, password, is_admin }) { const whereOpt = {} id && Object.assign(whereOpt, { id }) user_name && Object.assign(whereOpt, { user_name }) password && Object.assign(whereOpt, { password }) is_admin && Object.assign(whereOpt, { is_admin }) const res = await User.findOne({ attributes: ['id', 'user_name', 'password', 'is_admin'], where: whereOpt, }) return res ? res.dataValues : null } } module.exports = new UserService() ``` ## 十一. 拆分中间件 为了使代码的逻辑更加清晰, 我们可以拆分一个中间件层, 封装多个中间件函数 ![image-20230321223918833](C:\Users\baileli\AppData\Roaming\Typora\typora-user-images\image-20230321223918833.png) ### 1 拆分中间件 添加`src/middleware/user.middleware.js` ``` const { getUerInfo } = require('../service/user.service') const { userFormateError, userAlreadyExited } = require('../constant/err.type') const userValidator = async (ctx, next) => { const { user_name, password } = ctx.request.body // 合法性 if (!user_name || !password) { console.error('用户名或密码为空', ctx.request.body) ctx.app.emit('error', userFormateError, ctx) return } await next() } const verifyUser = async (ctx, next) => { const { user_name } = ctx.request.body if (getUerInfo({ user_name })) { ctx.app.emit('error', userAlreadyExited, ctx) return } await next() } module.exports = { userValidator, verifyUser, } ``` ### 2 统一错误处理 - 在出错的地方使用`ctx.app.emit`提交错误 - 在 app 中通过`app.on`监听 编写统一的错误定义文件 `app/errHandler.js` ``` module.exports = { userFormateError: { code: '10001', message: '用户名或密码为空', result: '', }, userAlreadyExited: { code: '10002', message: '用户已经存在', result: '', }, } ``` ### 3 错误处理函数 ``` module.exports = (err, ctx) => { let status = 500 switch (err.code) { case '10001': status = 400 break case '10002': status = 409 break default: status = 500 } ctx.status = status ctx.body = err } ``` 改写`app/index.js` ``` const errHandler = require('./errHandler') // 统一的错误处理 app.on('error', errHandler) ``` ## 十二.加密 在将密码保存到数据库之前, 要对密码进行加密处理 123123abc (加盐) 加盐加密 ### 1 安装 bcryptjs ``` npm i bcryptjs ``` ### 2 编写加密中间件 ``` const salt = bcrypt.genSaltSync(10) // hash保存的是 密文 const hash = bcrypt.hashSync(password, salt) ctx.request.body.password = hash await next() } ``` ### 3 在 router 中使用 改写`user.route.j` ``` const Router = require('koa-router') const { userValidator, verifyUser, crpytPassword, } = require('../middleware/user.middleware') const { register, login } = require('../controller/user.controller') const router = new Router({ prefix: '/users' }) // 注册接口 router.post('/register', userValidator, verifyUser, crpytPassword, register) // 登录接口 router.post('/login', login) module.exports = router ``` ## 十三.登录验证 流程: - 验证格式 - 验证用户是否存在 - 验证密码是否匹配 改写`src/middleware/user.middleware.js` ``` const bcrypt = require('bcryptjs') const { getUerInfo } = require('../service/user.service') const { userFormateError, userAlreadyExited, userRegisterError, userDoesNotExist, userLoginError, invalidPassword, } = require('../constant/err.type') const userValidator = async (ctx, next) => { const { user_name, password } = ctx.request.body // 合法性 if (!user_name || !password) { console.error('用户名或密码为空', ctx.request.body) ctx.app.emit('error', userFormateError, ctx) return } await next() } const verifyUser = async (ctx, next) => { const { user_name } = ctx.request.body // if (await getUerInfo({ user_name })) { // ctx.app.emit('error', userAlreadyExited, ctx) // return // } try { const res = await getUerInfo({ user_name }) if (res) { console.error('用户名已经存在', { user_name }) ctx.app.emit('error', userAlreadyExited, ctx) return } } catch (err) { console.error('获取用户信息错误', err) ctx.app.emit('error', userRegisterError, ctx) return } await next() } const crpytPassword = async (ctx, next) => { const { password } = ctx.request.body const salt = bcrypt.genSaltSync(10) // hash保存的是 密文 const hash = bcrypt.hashSync(password, salt) ctx.request.body.password = hash await next() } const verifyLogin = async (ctx, next) => { // 1. 判断用户是否存在(不存在:报错) const { user_name, password } = ctx.request.body try { const res = await getUerInfo({ user_name }) if (!res) { console.error('用户名不存在', { user_name }) ctx.app.emit('error', userDoesNotExist, ctx) return } // 2. 密码是否匹配(不匹配: 报错) if (!bcrypt.compareSync(password, res.password)) { ctx.app.emit('error', invalidPassword, ctx) return } } catch (err) { console.error(err) return ctx.app.emit('error', userLoginError, ctx) } await next() } module.exports = { userValidator, verifyUser, crpytPassword, verifyLogin, } ``` 定义错误类型 ``` module.exports = { userFormateError: { code: '10001', message: '用户名或密码为空', result: '', }, userAlreadyExited: { code: '10002', message: '用户已经存在', result: '', }, userRegisterError: { code: '10003', message: '用户注册错误', result: '', }, userDoesNotExist: { code: '10004', message: '用户不存在', result: '', }, userLoginError: { code: '10005', message: '用户登录失败', result: '', }, invalidPassword: { code: '10006', message: '密码不匹配', result: '', }, } ``` 改写路由 `user.route.js` ```// 登录接口 router.post('/login', userValidator, verifyLogin, login)