2019独角兽企业重金招聘Python工程师标准>>>
传统JavaWeb项目前后分离实践
很荣幸公司能分配给我前后分离的任务给我!给我这个js只会基础语法,也就能做做页面效果的人。到现在前后分离基本完成的阶段依然对nodejs基础一知半解,写这篇文章除了分享经验,也为能够抛砖引玉,能与大家探讨分离的技术
- 前后分离的目的
- 前后分离的过程
- 发现脚手架项目
- 启动脚手架
- 路由、处理404,500等错误
- 模块 request,log4js
- SpringMvc增加接口,jsp转html 以及 过滤器
- js跨域问题以及部署
- 总结
前后分离的目的
我们公司是做会议网站,有一整套的会议邀请、会场互动、场外互动、会后传播的流程,每个模块下又有子模块,例如会场中抽奖,场外图文直播等,这些都是可以作为单独的服务来提供,并且可以单独客户定制某一模块。所以为保证灵活性和快捷开发,后台只提供接口,前端页面只让前端开发就好了,至少这样,,前端写页面时不用再启动eclipse了,后台去给前端配置环境!
前后分离的过程
1. 发现脚手架项目
讲真,作为一个javaer去搞前端框架开始有点不知道如何下手!唯一知道点的js开发就是NODEJS,因为我用来测试API的一个小软件就是nodeJs开发的,所以当时特意百度了解下。万幸google‘前后分离’,让我搜出来个nodejs前后分离的脚手架代码front-end-separate,让我有种刚了解java servlet后发现struts2感觉~
2. 启动脚手架
脚手架源码front-end-separate,它是基于express和grunt的前后端分离框架,引擎使用的是nunjucks。不知道express,grunt,nunjucks 这3是啥玩意,不管,先启动。
按照文档:
$ npm i grunt-cli -g
$ npm i
$ grunt
提示我better-npm-run不是内部命令什么的,google+百度,也没查出个大概来,无奈给作者发邮件询问,不能干等回复,又去了官网看better-npm-run文档,英文,不懂,试着执行了
$ npm i better-npm-run
$ npm i
$ grunt
启动成功,看见首页啦!
3. 路由
nodejs的项目从代码看,还是挺简单的。
跟踪下脚手架里路由的配置文件。(我对这个了解过程与我写的顺序正好相反,因为开始我不懂express是什么)
var routes = require('./routes/routes');
var app = express();
routes(app);
通过这段代码,追踪到./routes/routes.js文件
module.exports = function (app) {
require('./web/site')(app);
require('./web/common')(app);
// catch 404 and forward to error handler
app.use(function (req, res, next) {
var err = new Error('Not Found');
err.status = 404;
next(err);
});
};
没有研究过nodejs模块具体怎么弄,但通过这块的代码,忍不住猜想,nodejs中模块引用,通过
require(path)
获取 path对应文件中module.exports
,可以是方法或者是对象
跟踪:./web/site.js
到这里就是正常请求的路由配置了! 在springMvc,写法如下:
@RequestMapping("/{projectId}/{status}.html")
public String tmoduleData(@PathVariable String status,@PathVariable Long projectId,Model model){
特意查了下nodejs中路径中带有参数怎么弄,参考expressjs文档和众网友通过正则等获取参数方式 nodjs代码如下:
//`:projctId` 可以通过 req.params.projectId 获得其值,如果其后面没有正则表达式(\\d+)限制为数字,则可以是任意字符~
app.get('/f/event/:projectId(\\d+)/:status(\\w+).html', function (req, res) {
var request = require('request');
request(app.get('APIDomain') + 'event/index?projectId=' + req.params.projectId + '&status=' + req.params.status, function (error, response, body) {
if (!error && response.statusCode == 200) {
var d = JSON.parse(body);
d.projectId = req.params.projectId;
d.status = req.params.status;
d.EVENT_SITE_APIDomain = EVENT_SITE_APIDomain;
res.render(d.data.returnUrl, d);
}
});
});
关于其他参数的获取,例如post提交的参数等建议参考expressjs文档
4. 模块request、log4js
回顾上述的过程,就避不开2个开发中的需求
- 如何发出http请求java后台数据
- 如何记录日志
搜索关于nodejs中关于http的方法,深感原生的麻烦!如同以前开发网页时原生ajax请求和jquery.ajax()的对比,搜到request,cp代码,开始用!FUCK,我重启了好几遍grunt,为啥就是跑不通,就是不走我cp的代码!直到我npm i request
才跑通!
为了让Node.js的文件可以相互调用,Node.js提供了一个简单的模块系统。 模块是Node.js 应用程序的基本组成部分,文件和模块是一一对应的。换言之,一个 Node.js 文件就是一个模块,这个文件可能是JavaScript 代码、JSON 或者编译过的C/C++ 扩展。
踩过request这个坑后,也就对nodejs模块化机制不是一无所知了,搜索刚刚的问题,还有一点,就是我没有错误的输出,控制台很空空如也!那么在nodejs中是否也有日志系统呢?
发现nodejs也有log4j,叫log4js 官方文档 参考众多网友的博客 如:http://blog.fens.me/nodejs-log4js/ 也写出了自己的第一个模块! 其功能有
- 所有日志在控制台输出的同时也输出到日志文件中
- 日志分类 并 输出到不同的日志文件中
具体参数参考文档吧,我就不啰嗦了
function getLog4js(){
var log4js = require('log4js');
log4js.configure({
appenders : [{
type : 'console'
},
{
type : 'file',
filename : 'logs/console.log',
maxLogSize : 104800,
backups : 100,
category : 'console'
},
{
type : 'file',
filename : 'logs/log4js.log',
maxLogSize : 104800,
backups : 100,
category : ['[default]','eventSite','wxCaht']
},
{
type : 'file',
filename : 'logs/access.log',
maxLogSize : 104800,
backups : 100,
category : 'access'
}
],
replaceConsole : true
});
return log4js;
}
module.exports = getLog4js();
使用demo:
var log4js = require('../../config/log4js');
app.get('/', function (req, res) {
console.log("app.get('env'): " + app.get('env'));
log4js.getLogger().info("默认 test");//默认的日志类型为'[default]'
log4js.getLogger("console").info("console test", EVENT_SITE_APIDomain);//效果与console.log()一样
log4js.getLogger("access").info("access test");
log4js.getLogger("eventSite").info("eventSite test");
res.render("index")
});
//访问日志
app.use(log4js.connectLogger(log4js.getLogger('access'), {level:'INFO', format:':method :url'}));
5. SpringMvc增加接口,jsp转html
从jsp转html,我的思路是重构下代码,保留之前的功能不变,新增ajax请求的方法,因为无论springMvc的Model还是request.Attribute 都与Map结构类似,所以之前的要传给jsp的参数都put 到Map中并把这部分代码提取到一个方法中,然后之前的渲染jsp的方法和新的ajax方法都使用该方法return的MAP作为数据传给Modle或序列化就可以了
也是为了快速分离,所以我用了这种方法,实际上,有些地方应该再拆分的。我这样只能一个页面一个接口!
数据源解决了,开始把jsp改html吧!
jsp变html其实很简单,把jstl改为nunjucks模版的表达式就好了
做到这,不知道nunjucks是啥东西也就知道是啥了,这也算是个学习方法吧,一边用一边学,工作效率可能不高,但学习效率很高
遇到了几个坑:
1. 过滤器 slice 的使用
官方文档原文,真心读不懂,原谅我渣渣的英文!多次调用下,真心没搞懂,,请懂的大神给讲讲
slice(value, slices, fill_with=None)
Slice an iterator and return a list of lists containing those items. Useful if you want to create a div containing three ul tags that represent columns
没弄懂,最后还是自定义了fifter
env.addFilter('sliceArray', function (a, start, end) {
if (a instanceof Array) {
start = start | 0;
return a.slice(start, end);
}
return a;
});
通过自定义的一些过滤器可以把jsp完美转成html
2. 在jsp中可以动态的include,而在nunjucks 没有找到类似的方法。
3. 渲染html的参数,没有全局的参数,js碰见跨域问题
渲染html的参数只能通过res.render(path, data);
中data设置,全局参数是传不进去的,不知道其他人是怎么解决的,例如,我想在html中用到一个全局的参数,开发环境下页面AJAX请求的Domain和线上不一致问题,总不能上线前修改每个页面的domain吧
我的临时解决方案在下面部署部分说
6. js跨域问题以及部署
在自己的项目中,jsp里写的ajax url 无非在一个tag.jsp set了basePath,或window.jsPath,然后${basePath}/url。 而我们的更加简单粗暴,在同域名下的jsp都是相对路径 /url。
现在在nodjs开发过程中就出问题了,nodejs监听3001端口,tomcat端口9010,在localhost:3001下的页面访问loalhost:9010的ajax受到跨域限制!!!当然这是我开发的时候,前端开发时ajax肯定是个局域网内的一个服务器。
解决方案:
- 局域网内配置nginx
add_header Access-Control-Allow-Origin *;
- 线上的策略简单多了,附上我的NGINX配置(我本地模拟上线时的场景)
server {
listen 80;
server_name song.yang.o;
location / {
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header Host $host:80;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_pass http://127.0.0.1:9010;
}
#处理nodjs路由的请求
location ^~ /f/ {
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header Host $host:80;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_pass http://127.0.0.1:3001;
}
#nodejs下静态文件交由nginx处理
location ^~ /static/ {
root E:\\frontSeparate\eventSite\dist;
}
}
这样跨域问题就得到解决!
在说部署
吐槽下连个运维都没有的公司。。。
之前写的博客nodjs 服务器部署以及守护进程 这部分我没有啥发言权,我发邮件请教了脚手架作者,给回复:“线上建议pm2”
待我了解了pm2后再更新这里
总结
总结是啥玩意~~~
欢迎吐槽讨论~~