全国免费咨询:

13245491521

VR图标白色 VR图标黑色
X

中高端软件定制开发服务商

与我们取得联系

13245491521     13245491521

2022-10-09_Koa+mongoDb手把手实战「前端发布平台」

您的位置:首页 >> 新闻 >> 行业资讯

Koa+mongoDb手把手实战「前端发布平台」 声明:文章为稀土掘金技术社区首发签约文章,14天内禁止转载,14天后未获授权禁止转载,侵权必究! 纯前端不了解全栈开发但想学?赶紧看过来!本文通过node server实战分享,带你快速上手全栈开发,掌握后端开发实战技巧。通过koa2 + mongo实现服务端功能以完成前端发布平台的业务需求。小前端也有一个全栈梦!!! 本文是《实战前端发布平台,打开CICD黑盒》专栏的第二篇——实战前端发布平台后端。文章之间存在关联,对整个系列感兴趣的朋友可以关注专栏把其余文章也一起看看~ 系列文章: 总览前端自动化部署流程,如何实现前端发布平台?文章链接 前端发布平台node server实战! 前端发布平台jenkins实战!如何实现前端自动化部署? 前端发布平台全栈实战(前后端开发完整篇)!开发一个前端发布平台 本文为第二篇「前端发布平台node server实战」的实战记录分享,主要内容是通过 Koa + mongoDb 实现项目构建配置的CRUD功能,如项目的仓库信息、构建分支、打包命令等...为后续的前端自动化部署奠定基础。 快速看源码git仓库地址 一、Koa在上一篇文章中,已经用koa2+@koa/router搭建了一个初始化的后端项目了,本文将在之前的基础上进行扩展和完善,let's go! 1. 路由模块回顾上一篇文章,笔者已经用@koa/router实现了简单的路由,并且可以通过 postman 中发送get、post请求成功响应。接下来,我们需要把 route 模块进行系统化的整理和抽离,总不能都把路由信息写到入口文件吧。 // 入口js const Koa = require('koa') const Router = require('@koa/router') const router = new Router() const app = new Koa(); router.get('/test', (ctx, next) = { ctx.body = { code: 0, data: { name: '井柏然-get' } } }) // 待抽离整理 router.post('/test', (ctx, next) = { ctx.body = { code: 0, data: { name: '井柏然-post' } } }) // 待抽离整理 app.use(router.routes()).use(router.allowedMethods()) app.listen(3000); 整理思路: 路由分类。按照功能模块进行路由分类,如案例的 test 模块,一会要用的存放配置的 config 模块。 命名统一。清晰整个mvc链路,如请求routes的test模块,其执行函数是在controller目录 中的test。 接下来,开始整理,将原首页的内容放到routes/test.js文件中,包进执行函数initTestRoute中,并导出。 // test路由文件 routes/test.js function initTestRoute (router) { router.get('/test', (ctx, next) = { ctx.body = { code: 0, data: { name: '井柏然-get' } } }) router.post('/test', (ctx, next) = { ctx.body = { code: 0, data: { name: '井柏然-post' } } }) } module.exports = { initTestRoute // 导出 test 模块的路由初始化函数 } 在routes/index.js中导入每个模块的init函数,放在一个全局 init 的函数中统一执行,最后导出全局路由init函数。(就是将每个模块的init统一管理调用,在routes的入口中汇集而已) // 路由入口文件 routes/index.js const { initTestRoute } = require('./test') const { initConfigRoute } = require('./config') function initGlobalRoute (router) { initTestRoute(router) // 调用 test 模块路由注册 initConfigRoute(router) // 调用 config 模块路由注册 } module.exports = { initGlobalRoute // 导出全局路由注册 } 最后,在 koa 入口中调用initGlobalRoute函数,如下代码: const Koa = require('koa') const Router = require('@koa/router') const { initGlobalRoute } = require('./routes/index') const router = new Router() const app = new Koa(); initGlobalRoute(router) // 注册全局路由 app.use(router.routes()).use(router.allowedMethods()) app.listen(3000); 紧接着,我们再使用 postman 对 test 模块进行请求: 可以看到,post、get请求都有了正确的返回。接下来,我们把整个后端处理链路(每一层都按照这个模块划分规范)统一整理一波! 2. 整理controller+services+model层从路由层的“抛砖引玉”,我们把这种模块划分的整理思路套到每个分层中! 回顾一下上一篇中提到的后端分层。用户发起请求 - route层 - controller层 - service层 - db 我们依照这个分层,将上文中的按模块整理的思路放到每一个分层中。 首先是controller层(处理业务逻辑): // test路由文件 routes/test.js const controller = require('../controller/test') function initTestRoute (router) { router.get('/test', controller.get) router.post('/test', (ctx, next) = { ctx.body = { code: 0, data: { name: '井柏然-post' } } }) } module.exports = { initTestRoute } 很简单,对比上文实现,笔者只是将原本处理get请求的函数抽离到了controller/test中,在routes层中去调用controller层的方法。 // controller层的实现 controller/test.js function get (ctx, next) { ctx.body = { code: 0, data: { name: '井柏然-get(放在controller里啦!)' } // 这里改了文案跟上面形成对比 } next() } module.exports = { get } 这时候看postman的请求结果。跟预想中的返回一样: 好,按照这样的思路,对整个后端的分层、模块进行整理。如services层,也是按照当前模块划分,进行相应的数据库数据查询操作;model层按照模块划分,定义不同模块中的数据模型。因为思路都是类似的,笔者就不再展开赘述了,只要按照这个思路将各层按模块划分好即可。 3. 中间件Koa中间件大家可能多多少少都有听过,但是可能没自己玩过!这里笔者借着实战的场景,把中间件也用上,通过场景更加加深大家对中间件的使用理解。 中间件使用场景: 需要对服务器处理的每个请求的返回-response做一层拦截,以便后续拓展,如需要对一些错误信息进行统一收集等。 对每个进入的请求都要进行登陆校验、权限校验,以统一进行相应的逻辑处理。 讲到Koa中间件,一定要讲一下洋葱模型: 光看图可能很难 get 到要点,笔者这里根据官网的解释外加一个 demo 来跟大家一起体会一下这幅图的含义。首先看看官网的一段解释:重点看笔者划线的部分:调用next()该函数暂停执行,控制传递给下一个中间件,并且没有其他中间件时,恢复执行。 这么说可能还是有点懵,直接上demo。官网的案例还是复杂了点,直接撸个简单的demo。笔者直接在上述的入口文件中添加如下代码,通过console.log来输出 1-4 的数字。 app.use(function fn1 (ctx, next) { console.log(1) next() console.log(2) }) app.use(function fn2 (ctx, next) { console.log(3) next() console.log(4) }) 这里大家不妨先试着想一下输出结果,结合 Koa 官网对next函数的解释(当前暂停执行,控制传递下一中间件),应该都能想到答案。 如上图蓝色圈,输出的结果:1-3-4-2。毫无意外是不是!细心的童鞋可能已经发现,笔者在传入中间件的function中都加了函数名fn1、fn2,为的就是把next()机制转换成伪代码方便大家理解。如下: function fn1 () { console.log(1) fn2() // 把原本的 next 替换成 fn2 console.log(2) } function fn2 () { console.log(3) next() // 如果还有 fn3 那就一直这样嵌套调用下去而已 console.log(4) } 换成这样的写法,是不是就很清晰了。其实next就是实现了一个函数嵌套调用。这样一来,再结合上文提到的洋葱模型的图片,应该就能很好的理解中间件的执行机制了。最后,笔者再撸个请求-响应流的图跟大家一起巩固一下洋葱模型! 紧接着,笔者通过实现一个中间件对所有的响应进行拦截,来完善整个请求-响应流的返回结果。根据 Koa 官网推荐的命名空间,笔者在这里对整个controller层的返回结果进行一个约定,约定请求处理的结果按照规定字段放在ctx.state.apiResponse中。 中间件代码实现如下: import { RESPONSE_CODE } from '../constant' export function handleResponse () { return async function (ctx, next) { await next() // controller 层的结果处理结果放置 ctx.state.apiResponse 中 const { code, data, msg } = ctx.state.apiResponse ctx.body = getResult(code, data, msg) } } function getResult (code, data, msg) { const result = { code, data: null, msg: null } if (code === RESPONSE_CODE.SUC) { result.data = data // 响应成功 } if (code === RESPONSE_CODE.ERR) { result.msg = msg // 响应失败的msg } return result } 最后,我们需要在入口app中使用我们的中间件handleResponse(权限的就不演示了,每个童鞋的场景都不一样,只要按照这个思路,自己整个也是没问题的~),这里需要注意一点就是,我们这个中间件是需要对所有的返回做拦截的,按照洋葱模型的请求流向,handleResponse的use的位置应该在koa-router中间件的前面。代码如下: // index 入口文件 const Koa = require('koa') const Router = require('@koa/router') const { initGlobalRoute } = require('./routes/index') const { handleResponse } = require('./middleware') const router = new Router() const app = new Koa(); initGlobalRoute(router) app.use(handleResponse()) // 中间件实现返回拦截 app.use(router.routes()).use(router.allowedMethods()) // 路由相关 app.listen(3000); 紧接着,我们对 test 模块的 get请求按照刚才的约定,进行一定的代码改造,再通过postman进行请求,看看返回的结果。 const { RESPONSE_CODE } = require('../constant') function get (ctx, next) { // 按照约定,把返回的内容包裹在 ctx.state.apiResponse 中 ctx.state.apiResponse = { code: RESPONSE_CODE.SUC, // 约定的字段 code data: { name: '井柏然-get(放在controller里啦!)' } // 约定的字段 data } next() } module.exports = { get } 通过 get 请求可以得到我们期望的返回: ok,讲到这里,整个Koa的一些基础开发工作就算是差不多了,基本可以在这个基础上进行相应的业务开发了。紧接着我们进入到下一个阶段——数据库。上手完数据库~我们就能进行愉快的crud操作了!!! 二、mongoDb到了这个阶段,我们首先要做的事情就是各种安装、配置!配置的话是因为我们dev、prod环境中所需要连接的数据库地址不一样,所以趁着这次的配置,顺便在这里把项目的一些配置都给整一整。 1. nodemon"scripts": { "dev": "node ./src/index.js" // 这样每次修改文件都需要重新启动 }, 当前项目仅是通过node 入口文件的方式去执行的,所以每当代码修改,都需要重启服务。这时候你一定很想念开发前端项目时,打包工具给我们提供的HMR功能!这个时候不用我多说了,nodemon就是node的HMR!修改代码后会自动重启服务进程。 接着我们安装、配置一下nodemon就ok啦。配置啥的就不展开往下说啦,毕竟不是这个章节的重点,想详细了解的童鞋可以看笔者的 github源码~ "scripts": { "dev": "nodemon ./src/index.js --watch server --exec babel-node" // 自动重启香 }, 细心的伙伴可能发现启动命令后面带了一大段参数,作用是让我们能在koa中就可以用es6模块导入。哈哈哈,由于笔者之前都没写过commonjs模块化,所以这次 demo 特地写了一下cjs,结果发现自己还是写习惯了esm,趁着这次机会,换回来换回来,不得不说啊,笔者真的喜欢折腾~ 2. 安装、连接数据库首先得安装个mongoDb。安装这一块就不演示了,毕竟大家系统不一样,安装的方式都不同,笔者这里自己撸一个~安装成功后,通过mongod -version能看到相关的信息。(当然也可以通过docker下载安装mongo,笔者是用docker下的)这里说多一句,刚开始笔者下的是mongodb 6.0+折腾了一会,发现遇到一些问题网上都没找到解决的办法,可能是版本比较新吧,毕竟是才发布没多久的,所以后来笔者久用回5版本的了~ 现在安装好数据库,先把数据库服务进程给启起来,然后要为这次发布平台项目新建一个数据库,就命名为cicd吧。 启动的方式各异,笔者也不对这个点进行展开说了。可以用命令的,或者像笔者一样通过docker去启动也是可以的。在镜像中找到 mongodb ,然后运行。 首次运行完后,后续要启动数据库可以直接在容器中去启动,非常方便。 浏览器访问http://localhost:27017/出现如下界面证明数据库启动成功了。需要注意的是,端口27017为默认端口,如果使用了自定义的端口记得要换过来。 接着通过shell命令,可以在命令行窗口执行新建、切换数据库等各种操作: # 输入以下命令进入 docker root docker exec -it xbird-mongo bash # 输入 mongo 后进入交互式程序 mongo # 查看数据库 show dbs # 创建、切换到 cicd 数据库 use cicd 讲到这里,数据库的安装就算完了,此时我们还需要再装一个mongoose的包,配合着 Koa 项目使用爽得很!详细了解可以戳下他的github。笔者自己先安装了,大家自己动动手吧~ 安装好之后,我们可以在项目层面进行数据库的连接了!回到项目代码中,开始进行简单的数据库连接。等连通成功后,再去接着搞一波环境配置。整个的代码非常简单,核心就是通过mongoose.connect去连接数据库,传入对应的mongodb地址即可,笔者直接贴出来代码: import mongoose from 'mongoose' const db = mongoose.connection export const connect = function () { mongoose.connect('mongodb://localhost:27017/cicd') // 暂时写死数据库uri db.on('error', console.error.bind(console, 'mongodb connect error')) db.once('open', console.log.bind(console, 'mongodb connect success')) } 现在,我们在入口中使用上面这段代码进行数据库连接,然后 pnpm dev 试试!ok,控制台完美输出,那接下来就到下一步搞个环境配置,完了就可以开始令人心动的实战了~ 3. 环境变量配置之前有提到,数据库配置在不同的环境下肯定也是不同的,所以我们这里还要顺带把数据库的各环境配置也给准备好,后续的开发、部署上线就能省点事了。 首先,在之前预留的config文件中添加几个js文件,如下图所示:我们分别把需要针对环境进行不同配置的数据配置到.env.js文件中。 // 开发环境 development export default { db: { uri: 'mongodb://localhost:27017/cicd' } } // 生产环境 production export default { db: { uri: 'mongodb://xxx_xxx_xxx_prod/cicd' } } 然后,我们只需要根据process.env.NODE_ENV去取当前环境的配置即可,直接给出需要改动的地方吧,详细的也不展开说了,这一块大家应该也比较熟悉了。 // packge.json 启动命令添加 环境变量 "scripts": { "dev": "export NODE_ENV=development && nodemon ./src/index.js --exec babel-node" } // 连接数据库中写死的地址改为变量 export const connect = function () { mongoose.connect(config.db.uri) // 将写死的 uri 该成变量,跟随环境变量变化 db.on('error', console.error.bind(console, 'mongodb connect error')) db.once('open', console.log.bind(console, 'mongodb connect success')) } 搞定了mongodb的基础工作之后,就可以进入业务阶段的开发了。接下来通过实战:构建配置crud 的方式跟大家一起熟悉整个后端的开发模式,并为后续实现前端发布平台 + jenkins实现自动化部署功能做一个基础的铺垫。 三、实战构建配置CRUD进入实战阶段,首先需要一个明确的目标,当然也就是提需求阶段!有了需求,才好去落实。那么回顾上一篇文章中提到的一些构建需要的配置,笔者在此再进行一个罗列: 项目名称 项目源码(git仓库地址) 需要构建的分支 需要执行的打包命令 上传到文件服务器的目录 上述这5点,就是本次实战中需要实现的构建配置,我们需要实现配置的 保存 、 修改 、 删除 操作以满足我们发布平台的业务需求。 1. 定义SchemaSchema大概就是定义一个数据的基本格式,是一个集合。笔者是这么理解的,好比一张表有一些字段,每个字段是什么类型的、如何定义它而已,就是一个静态的数据格式。比如,笔者就为上述的配置定义个Schema。 import mongoose from 'mongoose' const configSchema = new mongoose.Schema({ projectName: { type: String // 项目名称 }, gitUrl: { type: String // 项目源码(git仓库地址) }, gitBranch: { type: String // 需要构建的分支 }, buildCommand: { type: String // 需要执行的打包命令 }, uploadPath: { type: String // 上传到文件服务器的目录 } }) 通过定义个这么个 schema ,把笔者需要的配置信息的数据模型就已经定义好了,接下来就是通过 crud 去操作数据库的这张表了。 我们把表名命为jobConfig,并导出mongoose.model(表名, Schema)。 export default mongoose.model('jobConfig', configSchema) 对于mongoose.Schema和mongoose.model,笔者理解他们的区别就是Schema是定义数据结构的,而model是根据这个结构去操作数据的,我们的crud就是通过model这一层去实现的。 2. 实现配置保存定义好数据模型后,我们首先来尝试实现一个保存配置的功能。我们一步一步来实现,按照之前约定,一层一层去实现。我们可以通过下图回顾一下分层,然后就要开始开工啦: routes 层。新建jobConfig.js文件,提供一个post的路由入口,调用controller层的save方法 export function initConfigRoute (router) { router.post('/job/save', controller.save) } controller 层。也是新建jobConfig文件,实现保存相关的业务逻辑。 这里补充说明一下,由于需要解析post请求的request body,所以我们还需要安装个 koa-body 的包来帮助我们~pnpm i koa-body跑起来。安装完成后,在入口文件中使用该中间件。 // 入口文件 app.use(KoaBody({ multipart: true })); 那么,接下来就是 controller 层的逻辑了,我们直接看代码(每一步都有注释) export async function save (ctx, next) { // 首先拿到 request body const requestBody = ctx.request.body try { // 调用 services 层的 save 方法。这个下面会展开代码 await services.save(requestBody) // 这里是保存成功后的处理 ctx.state.apiResponse = { code: RESPONSE_CODE.SUC, data: null } // 还记得之前写返回拦截中间件时定义的 apiResponse 的返回规范吗?这里就这样用了 } catch (e) { // 处理保存失败的返回 ctx.state.apiResponse = { code: RESPONSE_CODE.ERR, msg: '配置数据保存失败' } } // koa-router 也是一种中间件模式,所以我们这里要加个next /** * 比如路由是这样写的 router.get('/test', conttoller1, controller2) * 那 controller2 就需要 controller1 提供一个 next 才会执行到了 **/ next() } services 层。上面controller层调用了services层的save,那我们就需要在services层提供一个save方法。save的实现按照mongosse的用法来写即可,代码实现如下: import JobModel from '../model/jobConfig' /** * 这里导入的 JobModel 就是我们在 model 层定义 Schema 时,导出的 model * 没错,就是 export default mongoose.model('jobConfig', configSchema) 这句代码 **/ // params 就是保存的参数,在 request body 中获得的 export function save (params) { // 这里就时 mongoose 保存数据,操作 model 的用法。 return new JobModel(params).save() } ok,以上就是所有我们要实现的 save 的业务代码了,很简单有没有。那这个时候,我们打开postman来验证一下,看看能不能把配置成功保存到数据库里吧。 这里贴出需要保存的配置参数: { "projectName": "cicd-project", "gitUrl": "git://xxx", "gitBranch": "master", "buildCommand": "pnpm run build", "uploadPath": "/static" } 话不多说,直接用postmanpost一下!看起来好像成功了 接着,我们通过命令去查看一下,是否真的把我们的配置信息存到数据库里面了。 # 切换数据库 use cicd # 查看所有数据集合 show collections # 查看集合中的所有数据 db.jobConfigs.find() 结果如下(为了演示效果,笔者先通过db.jobConfigs.drop()把jobConfigs的集合删除):根据结果可以看到,我们通过postman发送的 post 请求,保存项目名称projectName为"cicd-project"的配置信息已经成功保存到数据库中了! 3. 实现配置更新 & 删除由于我们已经把保存功能实现了,配置的更新、删除也就是在数据库操作那一层会有点不一样而已,所以笔者这里不会详细的展开,只放上核心的实现代码。大家如果想详细了解的话,可以到 github 上去看整个后端的源码~废话不多说,我们接着撸起来,就快完工啦~ routes 层。添加update、delete的路由。 router.post('/job/update', controller.update) router.post('/job/delete', controller.del) controller 层。实现update、delete的业务逻辑,并调用services层实现数据库操作。这一层的实现基本跟save是一样的,所以不展开啦~ services 层。这里稍有不同,就是我们需要调用的数据库操作的方法不一样。我们可以通过mongoose中的findByIdAndUpdate、findByIdAndDelete实现更新、删除。 export function update (id, params) { // 这里需要2个参数:一个id(到时候前端是能拿到的),一个新的配置 return JobModel.findByIdAndUpdate(id, params) } export function deleteById (id) { // 通过 id 删除配置 return JobModel.findByIdAndDelete(id) } 好了,代码都撸完了,捣鼓一下postman去。首先来试试update的: 参数除了笔者圈出来的都一致,当然还多了一个id的参数哈哈~看到 postman 的返回结果是成功的,接着我们去数据库那里查询一下。没有意外,重新查询后,库里的projectName的数据值已经更新了。删除的功能也是可以的,笔者就不再演示啦,结果都符合预期! 到这里,整个实战Koa + mongoDb后端的篇章就完结啦,接下来笔者会接着分享node server打通jenkins实现自动化部署核心流程的文章,主要是通过后端调 jenkins 的openapi 实现freestyle job的新建、替换、构建执行,完成整个前端发布平台的自动化部署核心功能。大家可以关注笔者或者关注下该专栏,笔者一定快马加鞭的撸文章给大家~ 写在最后有些时候技术这东西真的不是难不难的说,更多可能是机遇的问题。像笔者做了几年了都是纯前端,也就今年才开始接触node server。工作实战中没有机会接触后端,也就只能一时想学node server就去学学,写个koa、express案例 这样子。但是效果并不好,隔段时间不接触,又什么都不会了。所以,机遇还是很重要的,但是话说回来,有时候工作中真的没有机会搞node server,那就只能靠自己了,其实可以跟着这篇文章的思路走,自己给自己出个需求,做个发布平台啊啥的,一定是有实战意义的,然后需要从0-1去搭建一个后端,使用多种技术栈~最关键是自己提需求,自己实现需求。在实现需求的过程中,就跟实战场景很相似了,会遇到很多坑,要用各种方法、技术不断地解决问题。当通过折腾把问题解决了,印象就很深刻了,等过了这个阶段,你会发现自己已经具备了一定的node server开发能力。所以,还是得自己多动手去实现,实在没项目可做的,照着笔者的方向自己去做一个发布平台也是会有收获的,大家一起加油吧~ 阅读原文

上一篇:2020-01-12_腾讯创始人Tony:科技向善,是一种产品能力 下一篇:2023-03-24_之江实验室杰出青年学者计划

TAG标签:

17
网站开发网络凭借多年的网站建设经验,坚持以“帮助中小企业实现网络营销化”为宗旨,累计为4000多家客户提供品质建站服务,得到了客户的一致好评。如果您有网站建设网站改版域名注册主机空间手机网站建设网站备案等方面的需求...
请立即点击咨询我们或拨打咨询热线:13245491521 13245491521 ,我们会详细为你一一解答你心中的疑难。
项目经理在线

相关阅读 更多>>

猜您喜欢更多>>

我们已经准备好了,你呢?
2022我们与您携手共赢,为您的企业营销保驾护航!

不达标就退款

高性价比建站

免费网站代备案

1对1原创设计服务

7×24小时售后支持

 

全国免费咨询:

13245491521

业务咨询:13245491521 / 13245491521

节假值班:13245491521()

联系地址:

Copyright © 2019-2025      ICP备案:沪ICP备19027192号-6 法律顾问:律师XXX支持

在线
客服

技术在线服务时间:9:00-20:00

在网站开发,您对接的直接是技术员,而非客服传话!

电话
咨询

13245491521
7*24小时客服热线

13245491521
项目经理手机

微信
咨询

加微信获取报价