开始之前
主题: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