现状: 冗余
在web开发中,我们是否常常会使用不同的编程语言实现相同的功能?
如一个文件上传功能,需要对上传文件进行文件格式限制。我们通常会使用后缀名做限制。
前端
为了用户体验,会在页面对用户选择的文件进行判断,合法才让用户可以上传。
function is_filetype(filename, types) {
types = types.split(‘,’);
var pattern = '.(';
for(var i=0; i<types.length; i++) {
if(0 != i) {
pattern += '|’;
}
pattern += types[i].trim();
}
pattern += ')$’;
return new RegExp(pattern, ‘i’).test(filename);
};
// 此处省略N行代码
if(!is_filetype($(‘#uploadfile’).val(), ‘doc,pdf,txt,wps,odf,md,png,gif,jpg’)){
can_submit = false; // 不允许上传
$(‘#uploadfile’).val(‘’);
alert('只允许上传: ' + constant.RESUME_FILETYPES);
}
// 此处省略N行代码
后端
由于担心恶意上传,无法避免地需要重新对用户上传的文件进行判断。
于是我又用python写了一个判断文件后缀的逻辑
import re
def is_filetype(filename, types):
types = types.split(‘,’)
pattern = '.(' + '|’.join([t.strip() for t in types]) + ')$’;
return re.search(pattern, filename, re.I) != None
# web request handler
# 此处省略N行代码
导致这样重复工作的原因是为什么?
- 前端永远不可信;
- 前端和后端使用不同的编程语言;
这样的冗余会带来什么代价?
- 修改业务逻辑,需要重复做2次:如突然发现少支持了 docx 文件类型,需要同时修改javascript代码和python代码
- 增加确保javascript代码和python代码业务逻辑一致的代价。需要分别写2种测试,unit test跑多一倍。
nodejs时代:DRY
Use nodejs no more DRY !
一份代码,前端后端同时运行
// constant.js
(function(exports){
exports.RESUME_FILETYPES = 'doc,docx,pdf,txt,wps,odf,md,png,gif,jpg’;
})( (function(){
if(typeof exports === ‘undefined’) {
window.constant = {};
return window.constant;
} else {
return exports;
}
})() );
// util.js
(function(exports){
/**
* 移除字符串两端的空白字符
*
* [[[[[[[[[[[[[[[[[[[[[[[@return](/user/return)](/user/return)](/user/return)](/user/return)](/user/return)](/user/return)](/user/return)](/user/return)](/user/return)](/user/return)](/user/return)](/user/return)](/user/return)](/user/return)](/user/return)](/user/return)](/user/return)](/user/return)](/user/return)](/user/return)](/user/return)](/user/return)](/user/return) {String}
* [[[[[[[[[[[[[[[[[[[[[[[[@api](/user/api)](/user/api)](/user/api)](/user/api)](/user/api)](/user/api)](/user/api)](/user/api)](/user/api)](/user/api)](/user/api)](/user/api)](/user/api)](/user/api)](/user/api)](/user/api)](/user/api)](/user/api)](/user/api)](/user/api)](/user/api)](/user/api)](/user/api)](/user/api) public
/
String.prototype.trim = function(){
return this.replace(/(^\s)|(\s*$)/g, “”);
};
/**
* 判断是否自定类型的文件
*
* [[[[[[[[[[[[[[[[[[[[[[[[@param](/user/param)](/user/param)](/user/param)](/user/param)](/user/param)](/user/param)](/user/param)](/user/param)](/user/param)](/user/param)](/user/param)](/user/param)](/user/param)](/user/param)](/user/param)](/user/param)](/user/param)](/user/param)](/user/param)](/user/param)](/user/param)](/user/param)](/user/param)](/user/param) {String}filename
* [[[[[[[[[[[[[[[[[[[[[[[[@param](/user/param)](/user/param)](/user/param)](/user/param)](/user/param)](/user/param)](/user/param)](/user/param)](/user/param)](/user/param)](/user/param)](/user/param)](/user/param)](/user/param)](/user/param)](/user/param)](/user/param)](/user/param)](/user/param)](/user/param)](/user/param)](/user/param)](/user/param)](/user/param) {String}types, 多个类型使用,号分隔,如 doc,docx,txt
* [[[[[[[[[[[[[[[[[[[[[[[@return](/user/return)](/user/return)](/user/return)](/user/return)](/user/return)](/user/return)](/user/return)](/user/return)](/user/return)](/user/return)](/user/return)](/user/return)](/user/return)](/user/return)](/user/return)](/user/return)](/user/return)](/user/return)](/user/return)](/user/return)](/user/return)](/user/return)](/user/return) {Boolean} true or false
* [[[[[[[[[[[[[[[[[[[[[[[[@api](/user/api)](/user/api)](/user/api)](/user/api)](/user/api)](/user/api)](/user/api)](/user/api)](/user/api)](/user/api)](/user/api)](/user/api)](/user/api)](/user/api)](/user/api)](/user/api)](/user/api)](/user/api)](/user/api)](/user/api)](/user/api)](/user/api)](/user/api)](/user/api) public
*/
var is_filetype = exports.is_filetype = function(filename, types) {
types = types.split(‘,’);
var pattern = '.(';
for(var i=0; i<types.length; i++) {
if(0 != i) {
pattern += '|’;
}
pattern += types[i].trim();
}
pattern += ')$’;
return new RegExp(pattern, ‘i’).test(filename);
};
})( (function(){
if(typeof exports === ‘undefined’) {
window.util = {};
return window.util;
} else {
return exports;
}
})() );
前端
<script src="/js/util.js"></script>
<script src="/js/constant.js"></script>
// 此处省略N行代码
if(!util.is_filetype($(‘#uploadfile’).val(), constant.RESUME_FILETYPES)){
can_submit = false; // 不允许上传
$(‘#uploadfile’).val(‘’);
alert('只允许上传: ' + constant.RESUME_FILETYPES);
}
// 此处省略N行代码
后端
var util = require(‘./public/js/util.js’),
constant = require(‘./public/js/constant.js’);
app.post('/resume/upload/:job_id’, function(req, res, next){
req.form.complete(function(err, fields, files){
if(!util.is_filetype(filepath, constant.RESUME_FILETYPES)) {
// 由于客户端已做判断,所以这样的情况都是恶意上传的,直接提示
res.send(‘文件格式错误: ' + filepath
+ ' , 请上传’ + constant.RESUME_FILETYPES + ‘格式的文件’);
return;
}
// save file …
// 此处省略N行代码
});
});
wow,没冗余了吧!done
其他常用场景
- 常量定义
- 各种有用的工具模块,如字符串操作
有爱
^_^ 希望本文对你有帮助
22 回复
本人平常一直在强调并信奉前端工程师的职责范围包括:客户端开发(PC/手持设备)、Web页面制作、与用户的交互(JS/AS/…)、与服务器的交互(Ajax/JSONP/…)、服务器端应用展现层开发。
虽然因人而异、因业务而异,只要认可这一点,那个人就会最大限度的利用各种技术和资源来提高复用和开发效率,推广本nodejs也会更加自然。
本来所有用户的输入都是不可信的,你所谓的第一个担忧实际上只是一个原则不要完全相信用户输入,由于前端的验证等都可能被绕过。
而正常逻辑下,用户在页面上填写的内容没有填写完毕,即可立即进行客户端校验,这只是减轻服务器端压力的一种做法,你完全可以客户端不做任何验证,但是这一方面增加服务器压力,另外一方面也在一定程度上降低了可用性。
Google搜索Nodejs路过。
对JS不太过敏,还是习惯用Python,Java类的语言来写后端。而且前后台根本不存在DRY的问题,前台作后缀分析只是为了降低一部分后端的负担,无论前端是否作判断,后端都一样要作判断。
而且这种趋势感觉会让WEB入门难度更大,同样的语言,分别用来开发前台和后台,对于刚刚入门的人可能会感到异常困惑…~而且JS的prototype继承方式也感觉比较怪。
支持前后端可以共享一小部分逻辑。
不过,个人认为前后端同样的处理逻辑不可取:
1. 前端主要是相对简单的校验,提高反应速度和减轻后端负担 (庞大的JS也影响载入速度)
2. 后端主要是完整的检验,保证安全性和一致性