2019-03-03

2019-12-09  本文已影响0人  hc3001

需求介绍:

1. 系统权限管理,数据系统管理,各个系统权限联通。
2. 可以对不同用户设置角色,赋予不同权限,显示看到不同的页面。
3. 需要在不改代码的前提下,增加外部链接菜单页面。
4. 决策系统数据系统部分迁移到天眼。

项目命名

项目命名为bigdata-admin-vue

技术栈

1. vue-cli3;
2. vue-router、vuex、element、axios;
3. 权限基于RBAC权限管理模型,即用户→角色→权限管理模型;

系统目录

[站外图片上传中...(image-87a9ff-1575879535075)]

1路由控制和菜单显示

vue权限路由实现方式:

* 视图(View) 使用xtpl模板引擎

##路由解析,根据URL寻找对应的控制器和行为

```javascript
app.post('/template|/templatePC|/user-defined|/user-defined-pc',control.postRander);  //post入口,页面编辑、报表、热图、abtest报表页面渲染
app.get('/modules/*/index',control.moduleRander); //用于本地单个无线模块开发调试
app.get('/modules_pc/*/index',control.moduleRander);//用于本地单个pc模块开发调试
app.get('/template/design(_second)?|design_pc(_second)?',control.pagePreview); // 所有iframe的入口,预览
app.get(/personalized$|personalized\/[^\/]*?$/,control.personalized); //个性化
app.get('/AB_RedirectServer?',control.abtest); //ab

//保存页面请求
app.post('/savePage|savePagePc/index',control.pageShow);

//js combo
app.get('/jscombo/',combo.jsCombo);

app.get('*',control.errorPage);

3.渲染部分:

编辑页渲染父级通过调用function dataSend(tag,viewModule,callback) 里的ajax将数据发送给cms postDataProcessing函数处理,

returnData = self.dataRender(data,equipment,xtplPath+data.template_file+'.xtpl',moduleName);
if(data.flagForPage == 0 || data.mustDocumentWrite == true){
    xtpl.renderFile('./views/'+xtplPath+data.template_file+'.xtpl',returnData,function(error,content){
        res.writeHead(200, {"Content-Type": "text/plain"});
        res.write(content);
    });
}else{
    res.send(returnData);
}

iframe在展示页面之初,默认模板是index.xtpl,如果要使用用户自定义的xtpl模板,因为页面结构是默认的index.xtpl的结构,需要对页面进行重新写入。

1. 通过dataRender将处理好的返回数据,赋值给returnData

2. if语句里的代码对模板进行的渲染,得到的数据是字符串,并且通过res.write将数据返回个浏览器对应的浏览器部分代码处理如下

document.open(); //打开文档流
document.write(data); //将默认的iframe里页面内容替换
document.close(); //关闭文档流

3. else语句里将returnData直接返回给浏览器

对应代码处理如下:

$('#css-box').html(data.css);
$('#components-box').html(data.xtplHTml);
$("#body-content-box").attr({
    "data-page-style": data.style,
    "data-is-publish": data.isModule,
    "ppcc-id": data.pageId_peopleId_childPageId_childPeopleId,
    "idsite": data.idsite,
    "page-id": (data['digital'] ? data['digital'].page_group_id : null) || data.id,
    "is-personalized": data.isPersonalized,
    "people-id": data.peopleId,
    "data-view-module": (data.viewModule ? data.viewModule : "")
});
company_id = !!data.tenant_id ? data.tenant_id : (!!data.company_id ? data.company_id : 0);
//设置页面背景
(function () {
    var style = $('body').attr('data-page-style');
    if (style != '') {
        $('body').css('background', style);
    }
})();
$(getIframe()).attr("isSuccess", "true");
$('#components-box').append("<script>" + data.jsContainer + "</\script>");
if ($(".module_bottom-nav").length === 0) {
    $("body").css("padding", 0);
}
if (data.config.length > 0 || data.show_user_defined) {
    $(".prompts").hide();
} else {
    $(".prompts").show();
}
if (tag.c_data && tag.r_data) {
    reportClickData(tag);
}

('#css-box').html(data.css);iframe里的css样式替换 \('#components-box').html(data.xtplHTml);将iframe里的html替换
$('#components-box').append("<script>" + data.jsContainer + "</script>");将iframe里的js替换

4.个性化

个性化是一个比较复杂的系统,但是cms只做了渲染,复杂的逻辑都在我们系统编辑部分做了处理,个性化页面没有生成静态的文件,通过接口获取数据,并有dataRender渲染展示页面

5.Abtest

函数abtest对其做的处理
目前有两种情况is_out 为 0 和is_out 为 1
为0的时候表示内部页面,也就是我们站点生成的页面,在调用dataRender方法做处理展示页面
为1的时候表示外部页面,也就是客户的页面,直接res.write渲染

6.页面生成机制

当页面保存的时候触发,将接收的数据里的配置信息解析,查找对应的xtpl模板,
在dist查找对应模块css和js并导入数据,生成个一个字符串的页面数据\

  1. pageSave函数执行;
  2. saveToMakeFile普通页面保存,就生成一个html,以页面id为命名
  3. makeImageFiles函数来生成图片文件
  4. separateFiles函数做文件分离处理,将html、css、js、图片剥离开,变成单个文件
  5. getlistening函数将监听文件添加到页面
  6. tongjiFn函数设置统计参数

7.日志管理:

require log4js

log4js.configure({
  appenders: [
    { type: 'console' }, //控制台输出
    {
      type: 'dateFile', //文件输出
      filename: './log/',  //文件保存在log目录下
      maxLogSize: 20480,
      backups:3,
      category: 'log_date',
      alwaysIncludePattern: true,
      pattern: "yyyy_MM_dd.log"  //文件命名规范
    }
  ],
  replaceConsole: true
});
var logger = log4js.getLogger('log_date');
logger.setLevel(log4js.levels.INFO);

8.pm2多进程服务

pm2 是一个带有负载均衡功能的Node应用的进程管理器,把独立代码利用全部的服务器上的所有CPU,并保证进程永远都存活,0秒的重载;
线上服务在运行的时候会出现异常,虽然代码里已经用try catch 来捕获异常,但是还是会有一些情况会导致服务崩溃,pm2开启多进程,可以保证代码运行稳定

9.Redis

只要用于用户预览的时候数据存储,在用户点击预览的时候将保存在redis中,以时间戳和随机6位随机数字为key存储数据,用户可以通过手机扫描二维码访问到页面,pc预览页面可以通过新窗口打开,目前页面预览有效时间设置为20分钟,预览的时候通过key来读取数据数据

10.监听部分:listening

1. 监听包括piwik和sdk,piwik已不再使用。页面滚动手机pv数据、页面到达率数据,点击搜集点击数据,热图数据;
2. 页面编辑状态下,预览状态下不会添加监听代码,发布的页面、ab页面、个性化页面会引入监听代码,

11.打包工具:gulp

  1. watch监听了模块里的js和css文件,当模块里的js或css做修改时,自动打包压缩到dist目录下
  2. gulp bulid可以生成打包后的代码,用于部署客户服务器上

12.模板引擎:xtpl

  1. xtpl是淘宝kissy架构里的模板渲染引擎,介绍:https://github.com/xtemplate/xtemplate/blob/master/docs/tutorial/introduce.md
  2. 在系统开发之初,我是想用ejs作为渲染模板引擎,由于项目的时间紧迫,我对xtpl比较熟悉些,经过商议还是决定用xtpl。
  3. xtpl的语法功能并不强,但也是可以满足我们的渲染需求,目前在项目里xtpl运行稳定。
    npm包链接:https://www.npmjs.com/package/xtpl
    gitbub链接:https://github.com/xtemplate/xtemplate

13.模块(组件)

  1. 页面是由一个或者多个模块构成的,模块是构成页面基础单位
  2. 模块js代码基础框架
(function() {  //闭包函数自执行
    function Mod() { //函数定义 Mod构造函数
        this.init.apply(this, arguments);
    }
    Mod.prototype = { //原型链
        init: function(container) {
            var self = this;
            self._node = $(container); // 模块容器
            self.data = self._node.find('.J-dataJson').html(); // 模块数据
            self.data = JSON.parse(self.data);
        }
    };
    new Mod("#clickplus-module-box"); //don't modify this line of code
    // new一个构造函数
})();

基础框架不允许改动,当页面里有多个相同模块的时候,构造函数就发挥作用了,我会new多个Mod
new Mod("#clickplus-module-1496965951574")
new Mod("#clickplus-module-1496965951575")
.......
后面跟上模块创建的时间戳,保证唯一性,这样就避免了重复写入相同的模块代码,减少页面的代码量

  1. 模块xtpl
    精简代码示例:
<textarea class="J-dataJson" style="display: none">{{newTextarea}}</textarea>
<div class="area-choose">
    <div class="main" style="{{#if(config.data[0].height)}}margin-bottom:{{config.data[0].height/64}}rem;{{/if}}">
    </div>
</div>

当你页面里需要cms返回的模块数据时可以添加textarea的代码
class="main"也是固定的

  1. 模块样式
    精简代码示例:
@r: 64rem;
.area-choose {
   min-width: 320px;
      width: 640/@r;
      margin: 0 auto;
      .main {
          width: 640/@r;
          min-width: 320px;
      }
}

@r: 64rem;作为页面适配基础单位
用实际的尺寸除以@r得到适配的rem尺寸,就可以适配各个手机


14.nginx应用

主要做了8080端口的代理,转发到80,设置了cms的

server { 
    listen 80; 
    server_name node.clickplus.cn; 
    server_name cms.clickplus.cn; 
    server_name cms-test.clickplus.cn; 
    location / { 
        proxy_pass http://127.0.0.1:8080; 
        fastcgi_connect_timeout 60;           
        fastcgi_send_timeout 180; 
        fastcgi_read_timeout 180;
        fastcgi_buffer_size 128k;
        fastcgi_buffers 256 16k;
        client_body_buffer_size 4096k;
    }
} 

15.报表部分

  1. 先前版本是将报表的渲染处理放在controller.js里的,带来一个很大的问题就是,当切换有效点击或者无效点击、选着日期等操作的时候其实要重新渲染整个页面
  2. 现在的逻辑是页面只渲染一次,做切换数据的操作时页面不再刷新,只将数据刷新
    footSettings.xtpl文件里:
    reportClickData、
    reportCalculate、
    reportForNav、
    reportForNavGoods、
    reportForGoods
    函数对报表的数据做了处理

16.页面适配原理

对font-size进行了重新定义,以rem作为一个单位,64为基数,

!function(e) {
    function t() {
        var t = n.clientWidth,
            r = "}";
        !navigator.userAgent.match(/Android|BlackBerry|iPhone|iPad|iPod|Opera Mini|IEMobile/i) && t > 1024 && (t = 640, r = ";max-width:" + t + "px;margin-right:auto!important;margin-left:auto!important;}"), e.rem = t / 10, /ZTE U930_TD/.test(navigator.userAgent) && (e.rem = 1.13 * e.rem), /Android\s+4\.4\.4;\s+M351\s/.test(navigator.userAgent) && (e.rem = e.rem / 1.05), i.innerHTML = "html{font-size:" + e.rem + "px!important;}body{font-size:" + 12 * (t / 320) + "px" + r
    }
    var n = document.documentElement,
        i = document.createElement("style");
    n.firstElementChild.appendChild(i), e.addEventListener("resize", function() {
        t()
    }, !1), e.addEventListener("pageshow", function(e) {
        e.persisted && t()
    }, !1), t()
}(window);

17.调试

  1. 可以通过webstorm来调试
  2. 可用node debug index.js 打debugger断点
  3. 基于Chrome浏览器的调试器 npm install -g node-inspector 导入安装路径到环境变量。 node-inspector 默认情况下node-inspector的端口是8080,可以通过参数--web-port=[port]来设置端口。 node-debug --web-port=8000 index.js启动程序 node --debug-brk

18.环境安装

  1. 在cms根目录里执行npm install,安装依赖包
  2. 安装全局pm2模块 sudo npm install -g pm2 用于开启多进程,保证node服务不停止工作,线上环境一点要安装并执行 pm2 start index.js -i 4
  3. 安装全局gulp模块 sudo npm install -g gulp
  4. 安装全局nginx sudo npm install -g nginx 用于将8080端口发送到80端口
  5. 打开sudo gedit /etc/nginx/etc/nginx ,找到http,在里面加上如下代码,server_name 为你本地host设置的127.0.0.1对应的名称 ,将8080的端口请求指向80
server { 
    listen 80; 
    server_name node.clickplus.cn; 
    server_name cms.clickplus.cn; 
    server_name cms-test.clickplus.cn; 
    location / { 
        proxy_pass http://127.0.0.1:8080; 
    } 
}

nginx的buffer设定 开发中可能会发现post请求传输问题net::ERR_INCOMPLETE_CHUNKED_ENCODING 请在server添加buffer设定,并且设置gzip为on;

server { 
    listen 80; 
    server_name node.clickplus.cn; 
    server_name cms.clickplus.cn; 
    server_name cms-test.clickplus.cn; 
    location / { 
        proxy_pass http://127.0.0.1:8080; 
        fastcgi_connect_timeout 60;           
        fastcgi_send_timeout 180; 
        fastcgi_read_timeout 180;
        fastcgi_buffer_size 128k;
        fastcgi_buffers 256 16k;
        client_body_buffer_size 4096k;
    }
} 

19.数据结构

模块数据以对象数组形式存放在在config里,动态的配置信息存放在模块的config里

1.普通页面数据结构

{
    "app_url": null,    //页面app链接
    "body_foot": null,  //用户自定尾部
    "body_head": "",    //用户自定头部
    "cdn_url": "https://huodong.clickplus.cn",  //cdn地址,用户域名
    "children": null,
    "component_num": null,
    "config": [  //存放页面模板的数据
        {
            "id": 546,
            "img_path": "http://huodong.clickplus.cn/image/tuwenrequ.jpg",
            "title": "图文",
            "stick": false,
            "basic": 1,
            "timeIndex": 1496288530354,
            "create_time": 1477459545000,
            "device": 0,
            "path": "/modules/area-choose-text",
            "config": { //动态配置信息
                "data": [
                    {
                        "wrapHeight": 640,
                        "wrapWidth": 640,
                        "image": "http://huodong.clickplus.cn/image/hjhg.jpg",
                        "iHeight": 640,
                        "iWidth": 640
                    }
                ],
                "areaData": []
            },
            "tenant_id": 3,
            "type": 12
        }
    ],
    "create_time": 1495789835000,
    "custom_body": "<script src=\"//res.wx.qq.com/open/js/jweixin-1.2.0.js\"></script>\n<script type=\"text/javascript\">\nvar shareURL = window.location.href;\nvar sTitle = document.title;\nvar sDesc = document.getElementsByTagName('meta')['description'].content;\nvar imgUrl = $(\"#body-content-box > div:nth-child(1) > img\")[0].src;\n$.ajax({\n    url: '//weixintest.clickplus.cn/sign/0',\n    type: 'get',\n    dataType: 'jsonp',\n    async:false,\n    data: {\n        url: location.href.split('#')[0]\n    },\n    success: function(data) {\n        wx.config({\n            debug: false,\n            appId: data.appid,\n            timestamp: data.timestamp,\n            nonceStr: data.nonceStr,\n            signature: data.signature,\n            jsApiList: [\n                // 所有要调用的 API 都要加到这个列表中\n                'onMenuShareTimeline',\n                'onMenuShareAppMessage'\n            ]\n        });\n        wx.ready(function(){\n             wx.onMenuShareTimeline({\n                title: sTitle, // 分享标题\n                desc: sDesc, // 分享描述\n                link: shareURL, // 分享链接\n                imgUrl: imgUrl, // 分享图标\n                success: function () {\n                },\n                cancel: function () {\n                }\n            });\n            wx.onMenuShareAppMessage({\n                title: sTitle, // 分享标题\n                desc: sDesc, // 分享描述\n                link: shareURL, // 分享链接\n                imgUrl: imgUrl, // 分享图标\n                type: '', // 分享类型,music、video或link,不填默认为link\n                dataUrl: '', // 如果type是music或video,则要提供数据链接,默认为空\n                success: function () {\n                },\n                cancel: function () {\n                }\n            }); \n            wx.error(function(res){\n                console.log(\"errorMSG-1:\"+res);\n            });\n        })\n    },\n    error:function(e){\n        console.log(e)\n    }\n});\n</script>",
    "custom_head": "",
    "data_collection_type": 1,
    "default_page_id": null,
    "description": "adadasddasd",
    "device": 0,
    "file_name": "zhuxiyu_1028",
    "hav_app": 0,
    "id": 5228,
    "idsite": 31,
    "im_position": "0",
    "im_text": "",
    "is_deleted": null,
    "is_group": 0,
    "keywords": null,
    "pageMainWidth": "auto",
    "path": null,
    "relation": null,
    "release_time": null,
    "setting": null,
    "share_img": "https://huodong.clickplus.cn/image/1496891043699.png",
    "site_type": 1,
    "smart_component_setting": 0,
    "stat_position": "0",
    "stat_text": "<script>\nvar _hmt = _hmt || [];\n(function() {\n  var hm = document.createElement(\"script\");\n  hm.src = \"https://hm.baidu.com/hm.js?b5e1a9760cd46dbf098ad3e8db6f15a2\";\n  var s = document.getElementsByTagName(\"script\")[0]; \n  s.parentNode.insertBefore(hm, s);\n})();\n</script>",
    "status": 1,
    "style": "#eeeeee",
    "templateSettingItem": null,
    "template_file": "index",
    "template_id": 0,
    "tenant_id": 3,
    "tenant_logo": "https://huodong.clickplus.cn/image/1495612416087.jpg",
    "thumbnail": "http://huodong.clickplus.cn/file/1496909741687.jpg",
    "title": "zhuxiyu_1028",
    "type": 0,
    "type_name": null,
    "update_time": 1496909741000,
    "url": "https://huodong.clickplus.cn/html/zhuxiyu_1028.html",
    "wx_app_id": "",
    "only": "149691787773263078",
    "isEdit": true,
    "mustDocumentWrite": false,
    "flagForPage": "2"
}

2.个性化页面数据结构

{
    "config": [  //组件数据
        {
            "basic": 1,
            "config": {
                "data": [
                    {
                        "image": "http://huodong.clickplus.cn/image/hjhg.jpg",
                        "wrapHeight": 640,
                        "wrapWidth": 640,
                        "iWidth": 640,
                        "iHeight": 640
                    }
                ],
                "areaData": []
            },
            "create_time": 1477459545000,
            "device": 0,
            "id": 546,
            "img_path": "http://huodong.clickplus.cn/image/tuwenrequ.jpg",
            "path": "/modules/area-choose-text",
            "stick": false,
            "tenant_id": 0,
            "title": "图文",
            "type": 12,
            "timeIndex": 1496918752643,
            "fromComponent": "component1496918752643",
            "fromText": "default1496918752643"
        }
    ],
    "id": "4454",
    "img_path": "",
    "path": "",
    "tenant_id": 3,
    "title": "",
    "style": "#eeeeee",
    "company_id": "3",
    "isEdit": true,
    "template_file": "index",
    "device": 0,
    "only": "149691875264302242",
    "mustDocumentWrite": false,
    "flagForPage": "2"
}

参考文献

pm2:https://www.douban.com/note/314200231/
《深入浅出 node.js》作者:朴灵

上一篇下一篇

猜你喜欢

热点阅读