热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

网盘功能实现思路_使用Koa2从零开始实现一个具备基本功能的后端服务器的过程与思路

快速创建一个服务器安装koanpminstallkoa-S基本配置constKoarequire(koa);let{Port}require(.config);letappnewK

快速创建一个服务器

安装koa

npm install koa -S

基本配置

const Koa = require('koa');let { Port } = require('./config');let app = new Koa();// responseapp.use(ctx => { ctx.body = 'Hello Koa';});// 监听服务器启动端口app.listen(Port, () => { console.log(`服务器启动在${ Port }端口`);});

测试

就这样一个node.js服务器就启动起来了,

6d48263472fe8169d50428dd02c94db2.png

使用postman测试一下

5f08d4d2c38ca564f1eca87550c9fa21.png

路由中间件

思路:

  • 使用koa-router中间件处理路由;
  • 如果把所有的路由写在一起,将会非常拥挤,不利于后期维护,所以为每个业务模块配置模块子路由;
  • 然后把所有的模块子路由汇总到./src/roters/index.js;
  • 再在入口文件require('./routers')。

路由中间件目录

└── src # 源代码目录 └── routers # 路由目录 └── router # 子路由目录 ├── usersRouter.js # 用户模块子路由 ├── ... # 更多的模块子路由 ├── index.js # 路由入口文件

安装koa-router

npm install koa-router -S

模块子路由设计

const Router = require('koa-router');// 导入控制层const usersController = require('../../controllers/usersController');let usersRouter = new Router();usersRouter .post('/users/login', usersController.Login)module.exports = usersRouter;

模块子路由汇总

const Router = require('koa-router');let Routers = new Router();const usersRouter = require('./router/usersRouter');Routers.use(usersRouter.routes());module.exports = Routers;

使用路由中间件

// 使用路由中间件const Routers = require('./routers');app.use(Routers.routes()).use(Routers.allowedMethods());

接口测试

使用postman测试接口localhost:5000/users/login

dd2fef67878f4596d440af95bec8b8ad.png

数据库连接封装

思路:

  • 后端与数据库的交互是非常频繁的,如果是一个接一个地创建和管理连接,将会非常麻烦;
  • 所以使用连接池的方式,封装一个连接池模块;
  • 对连接进行集中的管理(取出连接,释放连接);
  • 执行查询使用的是connection.query(),对connection.query()进行二次封装,统一处理异常;
  • 向外导出一个db.query()对象,使用的时候,只需要传入sql语句、查询参数即可,例如:

db.query('select * from users where userName = ? and password = ?', ['userName', 'password'])

安装mysql依赖包

npm install mysql -S

配置连接选项

在config.js添加如下代码,然后在db.js引入

// 数据库连接设置dbConfig: { connectionLimit: 10, host: 'localhost', user: 'root', password: '', database: 'storeDB'}

连接池封装

创建"./src/models/db.js"

var mysql = require('mysql');const { dbConfig } = require('../config.js');var pool = mysql.createPool(dbConfig);var db = {};db.query = function (sql, params) { return new Promise((resolve, reject) => { // 取出连接 pool.getConnection(function (err, connection) { if (err) { reject(err); return; } connection.query(sql, params, function (error, results, fields) { console.log(`${ sql }=>${ params }`); // 释放连接 connection.release(); if (error) { reject(error); return; } resolve(results); }); }); });}// 导出对象module.exports = db;

更多的信息请参考mysql文档。

请求体数据处理

思路:

  • 使用koa-body中间件,可以很方便的处理请求体的数据,例如

let { userName, password } = ctx.request.body;

安装koa-body中间件

npm install koa-body -S

使用koa-body中间件

在config.js配置上传文件路径

uploadDir: path.join(__dirname, path.resolve('../public/')), // 上传文件路径

在app.js使用koa-body中间件

const KoaBody = require('koa-body');let { uploadDir } = require('./config');

// 处理请求体数据app.use(KoaBody({ multipart: true, // parsedMethods默认是['POST', 'PUT', 'PATCH'] parsedMethods: ['POST', 'PUT', 'PATCH', 'GET', 'HEAD', 'DELETE'], formidable: { uploadDir: uploadDir, // 设置文件上传目录 keepExtensions: true, // 保持文件的后缀 maxFieldsSize: 2 * 1024 * 1024, // 文件上传大小限制 onFileBegin: (name, file) => { // 文件上传前的设置 // console.log(`name: ${name}`); // console.log(file); } }}));

异常处理

思路:

  • 程序在执行的过程中难免会出现异常;
  • 如果因为一个异常服务器就挂掉,那会大大增加服务器的维护成本,而且体验极差;
  • 所以在中间件的执行前进行一次异常处理。

在app.js添加如下代码

// 异常处理中间件app.use(async (ctx, next) => { try { await next(); } catch (error) { console.log(error); ctx.body = { code: '500', msg: '服务器未知错误' } }});

静态资源服务器

思路:

  • 前端需要大量的静态资源,后端不可能为每条静态资源的请求都写一份代码;
  • koa-static可以非常方便的实现一个静态资源服务器;
  • 只需要创建一个文件夹统一放静态资源,例如./public;
  • 那么就可以通过http://localhost:5000/public/文件夹/文件名直接访问。

安装koa-static中间件

npm install koa-static -S

使用koa-static中间件

在config.js配置静态资源路径

staticDir: path.resolve('../public'), // 静态资源路径

在app.js使用koa-static中间件

const KoaStatic = require('koa-static');let { staticDir } = require('./config');

// 为静态资源请求重写urlapp.use(async (ctx, next) => { if (ctx.url.startsWith('/public')) { ctx.url = ctx.url.replace('/public', ''); } await next();});// 使用koa-static处理静态资源app.use(KoaStatic(staticDir));

接口测试

使用浏览器测试接口http://localhost:5000/public/imgs/a.png

44c21cf43a34bb74b3375470cdd4f815.png

session实现

思路:

  • 使用koa-session中间件实现session的操作;
  • 用于登录状态的管理;
  • 本例子使用内存存储的方案,适用于session数据量小的场景;
  • 如果session数据量大,建议使用外部存储介质存放session数据 。

安装koa-session中间件

npm install koa-session -S

使用koa-session中间件

创建"./src/middleware/session.js"

let store = { storage: {}, set (key, session) { this.storage[key] = session; }, get (key) { return this.storage[key]; }, destroy (key) { delete this.storage[key]; }}let CONFIG = { key: 'koa:session', maxAge: 86400000, autoCommit: true, // 自动提交标头(默认为true) overwrite: true, // 是否可以覆盖(默认为true httpOnly: true, // httpOnly与否(默认为true) signed: true, // 是否签名(默认为true) rolling: false, // 强制在每个响应上设置会话标识符COOKIE。到期重置为原始的maxAge,重置到期倒数 renew: false, // 在会话即将到期时更新会话,因此我们始终可以使用户保持登录状态。(默认为false) sameSite: null, // 会话COOKIE sameSite选项 store // session池}module.exports = CONFIG;

在app.js使用koa-session中间件

const Session = require('koa-session');// sessionconst CONFIG = require('./middleware/session');app.keys = ['session app keys'];app.use(Session(CONFIG, app));

登录拦截器

思路:

  • 系统会有一些模块需要用户登录后才能使用的;
  • 接口设计是,需要登录的模块api均以/user/开头;
  • 那么只需要在全局路由执行前判断api是否以/user/;
  • 如果是,则判断是否登录,登录了就放行,否则拦截,直接返回错误信息;
  • 如果不是,直接放行。

在"./src/middleware/isLogin.js",创建一个验证是否登录的函数

module.exports = async (ctx, next) => { if (ctx.url.startsWith('/user/')) { if (!ctx.session.user) { ctx.body = { code: '401', msg: '用户没有登录,请登录后再操作' } return; } } await next();};

在app.js使用登录拦截器

// 判断是否登录const isLogin = require('./middleware/isLogin');app.use(isLogin);

分层设计

思路:

  • 路由负责流量分发;
  • 控制层负责业务逻辑处理,及返回接口json数据;
  • 数据持久层负责数据库操作;
  • 下面以用户模块的登录、注册、用户名查找接口的实现为例说明。

目录结构

└── src # 源代码目录 └── routers # 路由目录 └── router # 子路由目录 ├── usersRouter.js # 用户模块子路由 ├── ... # 更多的模块子路由 ├── index.js # 路由入口文件 └── controllers # 控制层目录 ├── usersController.js # 用户模块控制层 ├── ... # 更多的模块控制层 └── models # 数据持久层目录 └── dao # 模块数据持久层目录 ├── usersDao.js # 用户模块数据持久层 ├── ... # 更多的模块数据持久层 ├── db.js # 数据库连接函数 ├── app.js # 入口文件

用户模块接口实现

接口文档

数据库设计

create database storeDB;use storeDB;create table users( user_id int primary key auto_increment, userName char (20) not null unique, password char (20) not null, userPhoneNumber char(11) null);

路由设计

const Router = require('koa-router');// 导入控制层const usersController = require('../../controllers/usersController');let usersRouter = new Router();usersRouter .post('/users/login', usersController.Login) .post('/users/findUserName', usersController.FindUserName) .post('/users/register', usersController.Register)module.exports = usersRouter;

控制层设计

const userDao = require('../models/dao/usersDao');const { checkUserInfo, checkUserName } = require('../middleware/checkUserInfo');module.exports = { /** * 用户登录 * @param {Object} ctx */ Login: async ctx => { let { userName, password } = ctx.request.body; // 校验用户信息是否符合规则 if (!checkUserInfo(ctx, userName, password)) { return; } // 连接数据库根据用户名和密码查询用户信息 let user = await userDao.Login(userName, password); // 结果集长度为0则代表没有该用户 if (user.length === 0) { ctx.body = { code: '004', msg: '用户名或密码错误' } return; } // 数据库设置用户名唯一 // 结果集长度为1则代表存在该用户 if (user.length === 1) { const loginUser = { user_id: user[0].user_id, userName: user[0].userName }; // 保存用户信息到session ctx.session.user = loginUser; ctx.body = { code: '001', user: loginUser, msg: '登录成功' } return; } //数据库设置用户名唯一 //若存在user.length != 1 || user.length!=0 //返回未知错误 //正常不会出现 ctx.body = { code: '500', msg: '未知错误' } }, /** * 查询是否存在某个用户名,用于注册时前端校验 * @param {Object} ctx */ FindUserName: async ctx => { let { userName } = ctx.request.body; // 校验用户名是否符合规则 if (!checkUserName(ctx, userName)) { return; } // 连接数据库根据用户名查询用户信息 let user = await userDao.FindUserName(userName); // 结果集长度为0则代表不存在该用户,可以注册 if (user.length === 0) { ctx.body = { code: '001', msg: '用户名不存在,可以注册' } return; } //数据库设置用户名唯一 //结果集长度为1则代表存在该用户,不可以注册 if (user.length === 1) { ctx.body = { code: '004', msg: '用户名已经存在,不能注册' } return; } //数据库设置用户名唯一, //若存在user.length != 1 || user.length!=0 //返回未知错误 //正常不会出现 ctx.body = { code: '500', msg: '未知错误' } }, Register: async ctx => { let { userName, password } = ctx.request.body; // 校验用户信息是否符合规则 if (!checkUserInfo(ctx, userName, password)) { return; } // 连接数据库根据用户名查询用户信息 // 先判断该用户是否存在 let user = await userDao.FindUserName(userName); if (user.length !== 0) { ctx.body = { code: '004', msg: '用户名已经存在,不能注册' } return; } try { // 连接数据库插入用户信息 let registerResult = await userDao.Register(userName, password); // 操作所影响的记录行数为1,则代表注册成功 if (registerResult.affectedRows === 1) { ctx.body = { code: '001', msg: '注册成功' } return; } // 否则失败 ctx.body = { code: '500', msg: '未知错误,注册失败' } } catch (error) { reject(error); } }};

数据持久层设计

const db = require('../db.js');module.exports = { // 连接数据库根据用户名和密码查询用户信息 Login: async (userName, password) => { const sql = 'select * from users where userName = ? and password = ?'; return await db.query(sql, [userName, password]); }, // 连接数据库根据用户名查询用户信息 FindUserName: async (userName) => { const sql = 'select * from users where userName = ?'; return await db.query(sql, [userName]); }, // 连接数据库插入用户信息 Register: async (userName, password) => { const sql = 'insert into users values(null,?,?,null)'; return await db.query(sql, [userName, password]); }}

校验用户信息规则函数

module.exports = { /** * 校验用户信息是否符合规则 * @param {Object} ctx * @param {string} userName * @param {string} password * @return: */ checkUserInfo: (ctx, userName = '', password = '') => { // userName = userName ? userName : ''; // password = password ? password : ''; // 判断是否为空 if (userName.length === 0 || password.length === 0) { ctx.body = { code: '002', msg: '用户名或密码不能为空' } return false; } // 用户名校验规则 const userNameRule = /^[a-zA-Z][a-zA-Z0-9_]{4,15}$/; if (!userNameRule.test(userName)) { ctx.body = { code: '003', msg: '用户名不合法(以字母开头,允许5-16字节,允许字母数字下划线)' } return false; } // 密码校验规则 const passwordRule = /^[a-zA-Z]w{5,17}$/; if (!passwordRule.test(password)) { ctx.body = { code: '003', msg: '密码不合法(以字母开头,长度在6~18之间,只能包含字母、数字和下划线)' } return false; } return true; }, /** * 校验用户名是否符合规则 * @param {type} * @return: */ checkUserName: (ctx, userName = '') => { // 判断是否为空 if (userName.length === 0) { ctx.body = { code: '002', msg: '用户名不能为空' } return false; } // 用户名校验规则 const userNameRule = /^[a-zA-Z][a-zA-Z0-9_]{4,15}$/; if (!userNameRule.test(userName)) { ctx.body = { code: '003', msg: '用户名不合法(以字母开头,允许5-16字节,允许字母数字下划线)' } return false; } return true; }}

测试

登录测试

65b1c30df5fe14be6ea31a720206821c.png

注册测试

ab1b6649e30df9d81fefd24681155142.png

查找用户名测试

b9ffa17027f881eacc2bb6e24d7ae7ab.png

结语
  • 一个node.js(Koa)后端服务器快速启动模板到这里已经搭建好了;
  • 需要使用的时候只需要分模块的添加一些接口并实现,就可以快速的构建起来一个后端服务器;
  • 后面还打算加一个文件上传(续传)模块;
  • 项目源代码仓库:koa2-start-basic,如果你觉得还不错,可以到Github点Star支持一下哦;
  • 笔者还在不断的学习中,如果有表述错误或设计错误,欢迎提意见。
  • 感谢你的阅读!


作者:hai_27
链接:https://juejin.im/post/5e6de50f6fb9a07cae137bf5



推荐阅读
  • 本文讨论了一个关于cuowu类的问题,作者在使用cuowu类时遇到了错误提示和使用AdjustmentListener的问题。文章提供了16个解决方案,并给出了两个可能导致错误的原因。 ... [详细]
  • Webpack5内置处理图片资源的配置方法
    本文介绍了在Webpack5中处理图片资源的配置方法。在Webpack4中,我们需要使用file-loader和url-loader来处理图片资源,但是在Webpack5中,这两个Loader的功能已经被内置到Webpack中,我们只需要简单配置即可实现图片资源的处理。本文还介绍了一些常用的配置方法,如匹配不同类型的图片文件、设置输出路径等。通过本文的学习,读者可以快速掌握Webpack5处理图片资源的方法。 ... [详细]
  • 使用nodejs爬取b站番剧数据,计算最佳追番推荐
    本文介绍了如何使用nodejs爬取b站番剧数据,并通过计算得出最佳追番推荐。通过调用相关接口获取番剧数据和评分数据,以及使用相应的算法进行计算。该方法可以帮助用户找到适合自己的番剧进行观看。 ... [详细]
  • 本文介绍了Python高级网络编程及TCP/IP协议簇的OSI七层模型。首先简单介绍了七层模型的各层及其封装解封装过程。然后讨论了程序开发中涉及到的网络通信内容,主要包括TCP协议、UDP协议和IPV4协议。最后还介绍了socket编程、聊天socket实现、远程执行命令、上传文件、socketserver及其源码分析等相关内容。 ... [详细]
  • Spring源码解密之默认标签的解析方式分析
    本文分析了Spring源码解密中默认标签的解析方式。通过对命名空间的判断,区分默认命名空间和自定义命名空间,并采用不同的解析方式。其中,bean标签的解析最为复杂和重要。 ... [详细]
  • Nginx使用(server参数配置)
    本文介绍了Nginx的使用,重点讲解了server参数配置,包括端口号、主机名、根目录等内容。同时,还介绍了Nginx的反向代理功能。 ... [详细]
  • 向QTextEdit拖放文件的方法及实现步骤
    本文介绍了在使用QTextEdit时如何实现拖放文件的功能,包括相关的方法和实现步骤。通过重写dragEnterEvent和dropEvent函数,并结合QMimeData和QUrl等类,可以轻松实现向QTextEdit拖放文件的功能。详细的代码实现和说明可以参考本文提供的示例代码。 ... [详细]
  • android listview OnItemClickListener失效原因
    最近在做listview时发现OnItemClickListener失效的问题,经过查找发现是因为button的原因。不仅listitem中存在button会影响OnItemClickListener事件的失效,还会导致单击后listview每个item的背景改变,使得item中的所有有关焦点的事件都失效。本文给出了一个范例来说明这种情况,并提供了解决方法。 ... [详细]
  • Mac OS 升级到11.2.2 Eclipse打不开了,报错Failed to create the Java Virtual Machine
    本文介绍了在Mac OS升级到11.2.2版本后,使用Eclipse打开时出现报错Failed to create the Java Virtual Machine的问题,并提供了解决方法。 ... [详细]
  • 在说Hibernate映射前,我们先来了解下对象关系映射ORM。ORM的实现思想就是将关系数据库中表的数据映射成对象,以对象的形式展现。这样开发人员就可以把对数据库的操作转化为对 ... [详细]
  • 本文介绍了使用PHP实现断点续传乱序合并文件的方法和源码。由于网络原因,文件需要分割成多个部分发送,因此无法按顺序接收。文章中提供了merge2.php的源码,通过使用shuffle函数打乱文件读取顺序,实现了乱序合并文件的功能。同时,还介绍了filesize、glob、unlink、fopen等相关函数的使用。阅读本文可以了解如何使用PHP实现断点续传乱序合并文件的具体步骤。 ... [详细]
  • 本文介绍了计算机网络的定义和通信流程,包括客户端编译文件、二进制转换、三层路由设备等。同时,还介绍了计算机网络中常用的关键词,如MAC地址和IP地址。 ... [详细]
  • 本文介绍了通过ABAP开发往外网发邮件的需求,并提供了配置和代码整理的资料。其中包括了配置SAP邮件服务器的步骤和ABAP写发送邮件代码的过程。通过RZ10配置参数和icm/server_port_1的设定,可以实现向Sap User和外部邮件发送邮件的功能。希望对需要的开发人员有帮助。摘要长度:184字。 ... [详细]
  • Linux如何安装Mongodb的详细步骤和注意事项
    本文介绍了Linux如何安装Mongodb的详细步骤和注意事项,同时介绍了Mongodb的特点和优势。Mongodb是一个开源的数据库,适用于各种规模的企业和各类应用程序。它具有灵活的数据模式和高性能的数据读写操作,能够提高企业的敏捷性和可扩展性。文章还提供了Mongodb的下载安装包地址。 ... [详细]
  • 本文总结了在开发中使用gulp时的一些技巧,包括如何使用gulp.dest自动创建目录、如何使用gulp.src复制具名路径的文件以及保留文件夹路径的方法等。同时介绍了使用base选项和通配符来保留文件夹路径的技巧,并提到了解决带文件夹的复制问题的方法,即使用gulp-flatten插件。 ... [详细]
author-avatar
手机用户2502904705
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有