DoraCMS框架说明
- 在看该文档之前,请先看完
DoraCMS
的README.md
文件,并且完成安装,然后这里是对框架的一点补充。 bin
文件夹是初始化的项目的文件,里面是端口等的预定义,运行项目使用指令:supervisor ./bin/www
该指令需要安装superviser
,用它替代node指令的话,每次启动项目的时候,如果项目有修改,可以不用重启nodejs,它会自动重启,所以修改代码更方便。model
层是模型层,主要负责和数据库的交互的功能,关于MVC框架的具体架构可以看这里,这里用的数据库是mongodb。具体文件的功能说明如下:db
文件夹:adminFunc.js
是后台与数据库交互的核心代码,setting.js
是全局设置,siteFunc.js
是前台网页与数据库交互的核心代码。Dbopt.js
文件:mongodb数据库增删改查的函数写在此处。其他文件
:mongodb数据库的表定义,这里因为是非关系型数据库,所以有点像java类的定义,因为我们不用mongodb的数据库,所以这个可以不用管。node_modules
:使用cnmp install
自动生成的库文件,可以不用管,这里要用cnmp而不用nmp指令的原因是因为cnmp用的好像好像是阿里那边的库,中国速度更快,nmp指令等死人还装不好。public
:公共文件夹,其中plugins
是插件,stylesheets
是样式表,themes
是主题,ueditor
是编辑器,upload
是文件上传的位置,javascripts
是angularjs和jquery的引用文件,这些基本都不用动,重要的是public\javascripts\backstage
目录下的controller.js
文件和dora.backstage.js
文件,controller.js
文件是AngularJS的控制器文件,是MVC架构中的C,用来处理逻辑,即我像数据库要什么数据,让它在前台的哪个地方显示等等。而dora.backstage.js
中是一些控制器函数的定义,有时候需要在里面添加新的控制器。routes
是路由文件夹,用来处理不同的请求,即我访问什么页面会有什么神奇的反应啥的是坐在这里,其实路由也属于控制器。Admin.js
后台所有模块管理路由Content.js
前台文档相关Index.js
首页相关(也包含文档列表和文档相亲)System.js
系统操作的相关路由 (比如文件上传、邮件发送等)Users.js
用户中心的相关请求走这里util
是公用库的文件夹,基本也不用动view
是视图层,manage
是后台视图文件,web
是前台视图文件,这里是直接用模板的。app.js
文件入口package.json
这里写项目信息和依赖库等等
预备知识
- NodeJS后台:运行在服务端的javascript,他通过路由和访问一些特定地址的方式获取数据库信息,进行交互,使得javascript可以运行在后台。
- Express框架:NodeJS的一个成熟框架,可以快速搭建一个功能完整的网页,照着这个教程撸一遍你们基本可以知道这些文件夹为啥这么放。
- AngularJS框架:该项目的前端框架,务必仔细看教程,这样你会知道controller这些控制器的由来,也大概能知道为什么有的代码那么些,知道怎么动态的进行页面显示,AngularJS可以创造丰富的页面变化。
- BmobRestful:bmob交互方式是在model层中发起网络请求,调用Bmob的RestAPI,获得数据,所以这个数据库要认真看,在实际操作过程中我们使用的是NodeJS的
request
库,本来NodeJS自带的发起网络请求用的是http,request丰富了http的功能,并且使用起来更方便,具体可以撸request教程。这个很重要,仔细看你才知道怎么和后台交互。 - Ejs模板:前端模板,仔细看下,可以了解前端语法,注意里面的
render
函数和一些自带的函数的使用。 - JSON:简单看下,预备知识
- Git:好好学,很重要。
关于编程工具
- 工欲善其事,必先利其器,因为DoraCMS这个Demo内容很多,MVC架构比较分明,代码复用率极高,所以找东西也很难找,我用的是
sublimeText3
编辑器,并且安装了以下插件,总之有一个插件很重要,就是我在任何一个函数或者变量上右击,有一个gotoDefinition
按键,可以让我直接调到该函数或者变量的定义处,这样会方便很多,不然是找不到东西的。此外要善于使用DoraCMS的全局搜索,一开始啥也不懂,就是追着一个逻辑,然后搜索关键词在哪里还出现了,来改代码的。
在DoraCMS中添加新菜单
- 先到
views/manage
下加入新的页面,比如我要新建一个bmob用户的页面,我就基于regUsersList.ejs新
建了一个叫做bmobregUsersList.ejs
的文件,并且修改了里面第一行的ng-controller
的名字为bmobregUsersLists
。 - 到
views/manage/public/adminTemp.ejs
页面添加菜单的UI,bmob会员列表的内容加在209行,这里注意新菜单的注意cid也要修改,添加好了之后,manage主页面顶部状态栏以及左边菜单栏里出现新菜单,但此时刷新应该还是无法访问的,继续下一步。 - 到
models/db/settings.js
第69行加入站点基础配置信息,此时该页面存在,但是弹出对不起你无权操作bmob用户管理模块。这是因为你这个管理员并没有得到访问该部分的权限,所以需要执行下一步。 - 在
routes/admin.js
202行添加bmob用户管理list - 在
model/db/adminFunc.js
202行修改,添加目标obj - 在
public/javascripts/backstage/controller.js
中的1013行修改,添加bmobregUsersList的控制器 public/javascripts/backstage/dora.bacjstage.js
的413行添加内容,这是要先在admingrouplist中添加超级管理员的权限,添加好后到用户组界面个给超级管理员这个权限,才能正常打开新加入的界面,此时界面现实的还是regUsersList.ejs界面的内容。
接入Bmob数据库并且显示
- 到上一步我们已经弄好了菜单,那么接下里应该怎么让他显示bmob的数据呢,具体如下:
- 首先我们会发现,
regUsersList.ejs
的ng-controller是regUsersList
,这是控制器,即链接前台和后台数据库的东西,所以我们可以先全局搜索regUserList在哪里,可以发现藏在/public/javascripts/backstage/controller.js
的第980行,这里面一共有四个函数,分别是获取用户信息,删除用户,修改用户,添加新用户的作用。而页面用户显示很明显与initPagination
函数有关,所以我们看一下该函数的定义。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
//会员管理 doraApp.controller("regUsersList",['$scope','$http','pageData','getItemService',function($scope,$http,pageData,getItemService){ $scope.formData = {}; //获取注册用户列表信息 initPagination($scope,$http); //删除用户 initDelOption($scope,$http,'您确认要删除选中的会员吗?'); // 修改用户 $('#addNewRegUser').on('show.bs.modal', function (event) { var obj = $(event.relatedTarget); var editId = obj.data('whatever'); // 如果不为空则为编辑状态 if(editId){ getItemService.itemInfo(pageData.bigCategory,editId).success(function(result){ $scope.formData = result; $scope.targetID = editId; }) }else{ $scope.formData = {}; } }).on('hidden.bs.modal', function (e) { // 清空数据 clearModalData($scope,$(this)); }); // 添加新用户或修改用户 $scope.processForm = function(isValid){ angularHttpPost($http,isValid,getTargetPostUrl($scope,pageData.bigCategory),$scope.formData,function(data){ initPagination($scope,$http); }); }; }]); |
initPagination
函数在/public/javascripts/backstage/dora.backstage.js
的113行,它有两个函数组成,initPageInfo
是初始化函数,我们不用管,而getPageInfos
函数是控制翻页的,我们可以看一下getPageInfos
具体功能。
1 2 3 4 5 6 |
<br /> //初始化普通列表分页 function initPagination($scope,$http){ initPageInfo($scope); getPageInfos($scope,$http,"/admin/manage/getDocumentList/"+$('#currentCate').val(),'normalList'); } |
getPageInfos
在/public/javascripts/backstage/dora.backstage.js
的153行,它是一个翻页组件,里面最关键的点在195页的http的调用,我们可以发现他调用了url加后面那一串参数,而这个url其实就是getPageInfos的传参,实际上在/public/javascripts/backstage/dora.backstage.js中的所有函数都是起到控制的作用,不承担任何逻辑,而且这里面console.log也是没有输出的,因为不执行。我本来想把bmob的request的调用加在这个地方,替代http这一部分,直接返回数据,后面发现并不行。所以我开始聚焦于geiPageInfos函数调用的url地址/admin/manage/getDocumentList
,并且在全局搜索getDocumentList。果不其然发现它藏在/routes/admin.js
的第231行,所以这一部分的http实际上是再次调用了一个路由/admin/manage/getDocumentList/userManage_user
,并且触发了这个路由的函数功能。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
router.get('/manage/getDocumentList/:defaultUrl',function(req,res){ var targetObj = adminFunc.getTargetObj(req.params.defaultUrl); var params = url.parse(req.url,true); var keywords = params.query.searchKey; var area = params.query.area; var keyPr = []; //携带可能的查询条件 if(keywords){ var reKey = new RegExp(keywords, 'i'); if(targetObj == Content){ keyPr.push({'comments' : { $regex: reKey } }); keyPr.push({'title' : { $regex: reKey } }); }else if(targetObj == AdminUser){ keyPr = {'userName' : { $regex: reKey} }; }else if(targetObj == User){ keyPr.push({'userName' : { $regex: reKey } }); keyPr.push({'name' : { $regex: reKey } }); }else if(targetObj == ContentTags){ keyPr.push({'alias' : { $regex: reKey } }); keyPr.push({'name' : { $regex: reKey } }); }else if(targetObj == Ads){ keyPr.push({'name' : { $regex: reKey } }); } } keyPr = adminFunc.setQueryByArea(req,keyPr,targetObj,area); DbOpt.pagination(targetObj,req, res,keyPr); }); |
- 然后在以上函数里的各种地方console.log输出信息,发现前面那一部分都是查找携带的查询条件的,因为我们的查询其实是放在bmob数据库的request中做,所以实际上可以不用这个,在没有查询的时候你也会发现KeyPr的值为空,所以重要的是
DbOpt.pagination(targetObj,req,res,keyPr);
这一句,我们调到这个函数里。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 |
pagination : function(obj,req,res,conditions){ var params = url.parse(req.url,true); var startNum = (params.query.currentPage - 1)*params.query.limit + 1; var currentPage = Number(params.query.currentPage); var limit = Number(params.query.limit); var pageInfo; //根据条件查询记录(如果有条件传递,则按条件查询) var query; if(conditions && conditions.length > 1){ query=obj.find().or(conditions); } else if(conditions){ query=obj.find(conditions); } else{ query=obj.find({}); } query.sort({'date': -1}); if(obj === Message){ query.populate('author').populate('replyAuthor').populate('adminAuthor'); }else if(obj === AdminUser){ query.populate('group'); }else if(obj === UserNotify){ query.populate('user').populate('notify'); }else if(obj === Content){ query.populate('category').populate('author'); }else if(obj === ContentCategory){ query.populate('contentTemp'); }else if(obj === ContentTemplate){ query.populate('items'); }else if(obj === Ads){ query.populate('items'); } query.exec(function(err,docs){ if(err){ console.log(err) }else { pageInfo = { "totalItems" : docs.length, "currentPage" : currentPage, "limit" : limit, "startNum" : Number(startNum) }; console.log(docs); console.log('dadamemeda'); console.log(pageInfo); return res.json({ docs : docs.slice(startNum - 1,startNum + limit -1), pageInfo : pageInfo }); } }) }, |
- 终于到了这个函数里,这里也是用
console.log
大法,我们可以发现pageInfo基本是前面传进来的参数,可以不动,而下面是调用mongodb数据库的操作,前面是查询,后面是找数据并返回。于是我们只要修改这一部分的内容,让他访问bmob数据库即可。并且还是把最后的结果传进res。这一段可谓历经波折,首先要注意,我们无论使用NodeJS自带的http调用还是用request库调用,都需要在文件头加上var request=require("request");
或者var http=require("http");
才能使用这些库,我用的是request库。 - 我调用bmob数据库的代码如下,其中options是参数,其实有where之类的限制条件也是加在这里面,具体可以看bmob的restful教程,callback是反馈函数,得到数据之后可以把它封装成json回传到res里,在这个地方价格console.log基本就可以知道自己与数据库的交互是不是正确,并且可以和他本身的数据库生成的json比对。最后使用request函数得到数据并且回传。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
var options = { url: '********************', method:"GET", headers: { "X-Bmob-Application-Id": "******************************", "X-Bmob-REST-API-Key": "******************************", } }; function callback(error, response, body) { if (!error && response.statusCode == 200) { var info = JSON.parse(body); //console.log(info.results); pageInfo = { "totalItems" : info.results.length, "currentPage" : currentPage, "limit" : limit, "startNum" : Number(startNum) }; //console.log(info.results); //console.log('dadamemeda'); //console.log(pageInfo); return res.json({ docs : info.results.slice(startNum - 1,startNum + limit -1), pageInfo : pageInfo }); } } request(options, callback); |
- 以上时间你都要多看
GitBash
端的输出信息,方便自己找问题和调试。而且为了保留以前的函数,我基本都是把不能复用的函数在原函数下面新建(其实基本上所有的函数都能复用,我们需要做的只是增加一个controller(controller里面的所有东西都可以不变),然后找到对应触发的路由,直接在路由中添加判断targetobj的条件语句,再到Dbopt.js中添加一个专门针对bmob数据库的函数即可,其实我删,改,查的函数都添加好了,都是可以直接复用的,所以这里其实也不用动,大家看我的代码就能理解),这样撸一遍上面的流程,都完成之后,回到bmobregUsersList.ejs
文件中,修改显示的参数的内容,这个后台数据也是用{{}}
符号封装的。这是AngularJS的框架,也和微信小程序有类似之处。 - 在以上步骤都完成之后你会发现点击bmob页面还是不能显示,这是因为
/public/javascripts/backstage/controller.js
中对应bmobregUsersList
的controller中一共有四个函数,我们只是修改好了获取注册用户列表信息的函数,而删除用户,添加新用户,修改用户啥的都没改,所以会出问题,把下面的注释掉,就可以显示了。这里虽然只做了一个数据显示的过程,但是详细的分析了整个流程,链路也打通了,后面就是依样画葫芦把其他功能也做掉就行。 - 大家尽快把Git学好,然后根据DoraCMS的框架和我写的文档配置好DoraCMS,并且跑起来,大家可以直接用我github上的代码来运行,流程是一样的。跑完之后根据本教程撸一遍流程,了解其中的原理,就可以开始分工干活了。DoraCMS框架还是很好地,MVC架构分明,并且代码复用率很高,还是很有学习价值。我们接下来要抽时间一起讨论一下后台的逻辑,定掉后台的UI与每个页面的逻辑线,而且还有这么多框架要看,工作量还是挺大的。