开始之前

主题:VBigger 前后端分离实践

那么问题来了,Why ? 何必蛋疼的做这个呢?

传统的开发过程


* 技术选型
* 项目创建
* 接口定义
	* 数据类型
	* 数据格式
	* 数据传输方式
* 项目开发(以ThinkPHP为例)
	* 前端,做Demo页面
	* 后端,$this->assign(data,$data),传递数据
	* 后端,$this->display('view_name'),显然视图
	* 前端,改Demo页面,把data加入到html中
* 验收测试
	* 发现问题,回到项目开发阶段,重复上述步骤
	* 暂无问题
* 项目部署(后端部署)
* 兼容测试
	* 发现问题,回到项目开发阶段,重复上述步骤
	* 暂无问题
* 项目完成

目前是很友好很和睦的过程,问题相对较少,这个状态大多出现在静态页面制作,后端需要配合做得工作还是比较少得,大概就是每次发现兼容问题的时候,后端需要重复部署几次,还能接受。


	YY: 顺顺,部署。
	SS: 嗯,好了,你测试。
	YY: 又有问题,重新部署。
	SS: 嗯,好了,你再测试。
	YY: 好像还有问题,S哥帮忙再部署一次吧
	SS: 再试。

Scene One

Scene Two


	YY: 顺顺,部署。
	SS: 嗯,好了,你测试。
	YY: Bug,重新部署。
	SS: 嗯,好了,你再测试。
	YY: 还是不行,是不是你数据有问题?
	SS: 重新部署了,你再测试。
	YY: 还是不行啊,不对。
	SS: 检查了,没问题,你没分词库,给你了,你再测试。
	YY: 嗯,还是不行,你看看。
	SS: 你没配置文件。
	YY: ...

这样问题就来了吧~

Scene Three


	YY: 顺哥,刚那个环境配置怎么弄?
	SS: 哦,那个是我自己编译的,挺不好弄的~
	YY: 顺哥啊,那回头帮我装下Redis吧
	SS: 嗯,我到时候把我改的东西弄下给你看...
	YY: 这东西怎么那么烦?可以不装么?
	SS: 你要在本地跑得话就要啊!
	YY: ...

好的,我们来聊聊~


    YY: 顺哥(xxxxx),本地运行环境这么烦?
    SS: 你要本地跑,必然要有运行环境和配置啊,对吧?
    YY: 你后端环境关我毛事?我(老子)就关心html,css,js和数据啊,其他真tm和我没什么关系啊!
    SS: 要本地跑,就要有运行环境和配置。
    YY: 可是我就关心与用户直接关联的啊~
    SS: 要本地跑,要有环境和配置。
    YY: 可是对我没什么用啊!
    SS: 要本地,就装!
    YY: NMB...
    SS: GNMGJB...

NEXT ?

比较明显的问题有哪里?

依赖严重


	public function index() {
		$detect = new \Org\Net\MobileDetect();
		if($detect->isMobile()) {
			$this->display('index-mobile');
		} else {
			$this->display('index');
		}
	}

举例来说,实现一个静态页,往往需要前后端配合,体现在代码上得就是后端需要写如下代码,接着前端需要在页面中显示各种各样的数据,并且在开发过程中需要后端不断的配合。

职责相关

为什么这么说呢?我们来说说MVC,所谓的MVC指的是Model,View与Controller,而实际开发过程中,后端最主要的职责显然是负责数据的处理,并将数据传递到前端页面,最终呈现给用户;但是在这个过程中,Controller负责了从Model获取数据并加入到View中,这个过程根据实际情况的不同,复杂程度不一,也许能够像上面一样简单,但也可能是另外的样子。比如说:

	/**
         * 播放器外嵌
         * @author:capjason
         * @datetime:2014-11-20T10:19:41+0800
         */
        public function embed() {
            $shortcut = I('path.2');
            $eventinfo = $this->eventModel->eventinfo($shortcut);
            if(!$eventinfo) {
                header('HTTP/1.1 404 Not Found');
                echo "404 Not Found,访问<a href='".C('WEB_HOST')."'>V直播主页</a>";
                exit;
            }

            $detect   = new \Org\Net\MobileDetect();
            $isMobile = $detect->isMobile();
            if(!$isMobile) {
                $event_state = intval($eventinfo['state']);
                //查找$livepost
                $livepost = $this->eventModel->getPosts($eventinfo['id'],0,1,POST_STATE_LIVE | POST_STATE_INTERRUPTED,false,'all');
                $livepost = empty($livepost) ? null : $livepost[0];
                switch ($event_state) {
                    case EVENT_STATE_NORMAL:
                        if($livepost) {
                            $state = POST_STATE_INTERRUPTED;
                            $this->assign('livepost',$livepost);
                        } else {
                            $transcode = $this->eventModel->getPosts($eventinfo['id'],0,1,POST_STATE_TRANSCODE,false,'all');
                            if(empty($transcode)) {
                                $state = -1; //未开始的状态
                            } else {
                                $state = POST_STATE_TRANSCODE;
                            }
                        }
                        break;
                    case EVENT_STATE_LIVE:
                        $state = POST_STATE_LIVE;
                        $this->assign('livepost',$livepost);
                        break;
                    case EVENT_STATE_ARCHIVE:
                        $state = POST_STATE_SAVED;
                        break;
                    default:
                        # code...
                        break;
                }
            }  

            $this->assign('state',$state);
            $this->assign('eventinfo',$eventinfo);
            $this->assign('isMobile',$isMobile);
            if($isMobile) {
                $this->display('index-embed-mobile');
            } else {
                $this->display('index-embed');
            }
            $this->eventModel->incViewCount($eventinfo['id']);
        }

也许你可以告诉我Controller可以写的更简单,把其中的一些逻辑放入逻辑层或者服务层,但问题在于,这件事情,始终还是让后端做了。​后端的职责我们说过,是对数据的处理。C的职责,显然是负责拿数据并把数据传递给View。

 

前端是干嘛的呢?html,css,js?对,还可以加上data,总结下,他做了什么?——拿数据,填数据。有没有发现他做得事情和C做得是惊人的相似呢?​而且,对传统MVC有点认识的人差不多都听过这样一句话,“请保证你得Controller足够干净,尽量不要在C中做太多逻辑相关的工作”。

 

对于职责的问题,不仅仅可以在MVC中看到,更是可以在团队管理,公司管理中体现,大家有时间可以再去看看工头写的那篇文章。

部署测试

部署往往需要前后端的配合,在我们V直播的开发过程中,大概小伙伴们经常听到月月这句话吧?——“顺顺,部署”/“顺哥(MB),部署了嘛?”,或者是这样的情景,╮(╯▽╰)╭?这东西怎么有问题,来来来,看下console.log(),惊喜的发现原来是js忘记build了,ok啊,简单,cd public/js && spm buid, “顺哥,再部署一下喽,刚忘记build js了”,“好了(NMB)!”。

 

ok,也不是不能解决,我们可以也给前端权限嘛,对吧?可是,你分支和我分支不一样啊,我切过去呗,可是,我(老子)还有一些.gitignore中得文件,你他么有么?没关系,东西是死的,人是活的呗,我们先在内网测试喽,这样大家都可以上去改了,测试好了我从内网推,这样没问题了吧?但是,问题来了,(云服务器哪家强?)到外网你特么不一样还得再做一次测试么...有啥区别?搞得内网就不需要拉代码似的...本质上是没区别的好吧?

 

好像有点跑题,这个小话题的意思是说,部署测试,也是需要前后端配合,而且重复量比较大。

改进/更优解决方案

好的,现在我们请出今天的主角,

也就是这个分享的主题:

前后端分离。

接着我们来看看前后端分离的开发过程。

分离的开发过程



* 技术选型
* 项目创建(前后端各自创建)
* 接口定义
	* 数据类型
	* 数据格式
	* 数据传输方式
* 项目开发
	* 后端,$this->ajaxReturn($data);
	* 前端,做Demo页面,并加入测试数据
	* 前端,从接口获取数据并替换测试数据为实际数据
* 验收测试
	* 发现问题,回到项目开发阶段(根据问题情况,通知前端/后端),重复上述步骤
	* 暂无问题
* 项目部署
	* 前端部署
	* 后端部署
* 兼容测试
	* 发现问题,回到项目开发阶段(根据问题情况,通知前端/后端),重复上述步骤
	* 暂无问题
* 项目完成

小伙伴玩耍得很愉快~


	SS: 用户接口好了,去看接口文档
	ME: ok,看到了,没问题,在用了。
	YY: 你们各自测试都好了么?没问题的话部署测试吧
	SS: 我没问题。
	ME: 我也没问题。
	YY: ok,齐哥测试了没问题,后头有问题再通知你们。
	YY: 自从分离以后,麻痹我再也不需要什么库什么蛋疼的配置了~
	SS: 是哦,老子再也不用帮你配置环境了,真尼玛幸福。
	ME: ...
	QI: ...
	AL: TWO SD. 

Scene Only

前后端的结构与说明

2014.12.12

前端

我们对前后端的定义实际上对其职责的划分,所以,这里的前端是指与用户直接相关的内容的集合,就包含了以下的过程:​

# STEPS

* 浏览器访问页面
* 服务器路由判断
* 跳转对应控制器
* 从接口获取数据
* 渲染页面的数据
* 浏览器显示页面

后端

对数据进行处理,然后通过ajax之类的方式返回json格式数据

# Job

* 制定接口
* 获取数据(Database)
* 返回数据(Request)


# Return

{
	err: null,
	msg: 'hello Everyone !'
}

Node.JS

example

路由:

    /**
     * Created by thonatos on 14/11/27.
     */
    
    var express = require('express');
    var router = express.Router();
    
    var docController = require('../controller/docController').docController;
    
    router.route('/')
        .get(function (req, res) {
            // List Post
            docController.getMulti(req, res);
        });
    
    router.route('/:category')
        .get(function (req, res) {
            // List Post
            docController.getMulti(req, res);
        });
    
    router.route('/:category/:document')
        .get(function (req, res) {
            // Show Post
            docController.getSingle(req, res);
        });
    
    
    module.exports = router;
控制器:

    /**
     * Created by thonatos on 14/12/7.
     */
    
    var queryService = require('../service/queryService');
    var renderService = require('../service/renderService');
    
    var api = require('../conf/document').config;
    var template = require('../conf/template').template;
    
    var routerHandler = {
    
        getMulti: function (req, res) {
    
            // Query lists
            var _category = req.param('category') || '';
    
            template.templateType = 'doc/multi';
            template.templateName = 'doc-multi';
    
            var query = {
                host: api.host,
                port: api.port,
                path: api.path + _category
            };
    
            this.renderData(_category || 'Doc', query, res);
    
        },
    
        getSingle: function (req, res) {
    
            // Query Single
            var _category = req.param('category');
            var _document = req.param('document');
    
            //console.log(_document);
    
            var _path = _category + '/' + _document;
    
            template.templateType = 'doc/single';
            template.templateName = 'doc-single';
    
            var query = {
                host: api.host,
                port: api.port,
                path: api.path + _path
            };
    
            this.renderData(_document, query, res);
        },
    
        renderData: function (pageTitle, queryString, res) {
    
            //console.log(queryString);
    
            queryService.get(queryString, function (err, data) {
    
                var _gotData = !err;
                var _template = template;
                var _content = {};
    
    
                if (_template.templateType == 'doc/single') {
                    _content = renderService.renderMarkdown(JSON.parse(data));
    
                } else if (_template.templateType == 'doc/multi') {
                    _content = eval(data);
    
                    // Remove ignore list file.
    
                    if (_content[0].name == '.gitignore') {
                        _content = _content.slice(1);
                    }
                }
    
                res.render(_template.templateType, {
                    pageTitle: pageTitle,
                    pageName: _template.templateName,
                    pageContent: {
                        render: _gotData,
                        title: pageTitle,
                        content: _content
                    }
                });
    
            });
        }
    };
    
    exports.docController = routerHandler;
服务:

    /**
     * Created by thonatos on 14/11/27.
     */
    
    var https = require('https');
    
    exports.get = function (queryString, callback) {
    
        var _options = {
            hostname    :   queryString.host || '',
            port        :   queryString.port || 443,
            path        :   queryString.path || '/',
            agent       :   false,
            headers     :   {
                                'Connection': 'keep-alive',
                                'User-Agent':'MT.Server'
                            }
        };
    
        var _protect = {
    
            receive: function (response) {
    
                var _chunks = '';
                var _length = 0;
    
                //console.log('STATUS: ' + response.statusCode);
                //console.log('HEADERS: ' + JSON.stringify(response.headers));
    
                response.setEncoding('utf8');
                response.on('data', function (chunk) {
    
                    //console.log("Got data: " + chunk);
    
                    _chunks += chunk;
                    _length += chunk.length;
    
                }).on('end', function () {
    
                    //console.log('QueryService: End.');
                    callback( null, _chunks);
                });
            }
        };
    
        var req = https.get(_options, _protect.receive)
            .on('error', function(e) {
                console.log("Got error: " + e.message);
                req.abort();
            });
    
        req.end();
    
    };

OVER

THANKS FOR TING WO BB . T.T~

Vbigger 前后端分离的结构与说明

By Thonatos.Yang

Vbigger 前后端分离的结构与说明

  • 3,929