代理转发
项目是前后端分离,api和node不是部署在同一个域名下,所以前端的server.js就用到了代理转发来解决跨域请求api接口的问题。
// server.js
const proxy = require('express-http-proxy');
// api接口转发,config.api是接口的实际地址
app.use('/api', proxy(config.api, {
forwardPath: (req, res) => {
return require('url').parse(req.url).path;
}
}));
jsonp
大赛的首页,由于PV量比较大,为了减轻服务器的压力,首页涉及到调用json数据的接口一律转化为静态化数据。
后端把首页需要展示的json数据处理成一份txt文件(其他文件格式也可),内容格式如下:
// finalUserList.txt
// json数据外面包裹了一个函数名,finalList(jsonData)
finalList(
{
vstockZoneName: "总决赛",
page: {
pageNo: 1,
pageSize: 10,
totalItems: 20,
totalPages: 2
},
vstockZoneId: 1000000,
list: [
{
dayRevenueRate: 0.07487,
positionRatio: 0.86,
revenueRank: 1,
revenueRate: 0.2763,
totalAsset: 1276301.8823,
totalRevenueRate: 0.2763,
userNick: "筷子兄弟",
weekRevenueRate: 0.10865
}
],
issueDate: 20170321,
weekDefines: [
{
beginDate: 20170313,
endDate: 20170317,
periodTypeStr: "WEEK",
zoneId: 1000000
},
{
beginDate: 20170320,
endDate: 20170321,
periodTypeStr: "WEEK",
zoneId: 1000000
}
]
issueDateStr: "2017-03-21 15:00",
showPage: "true",
zoneId: 1000000
}
)
定义一个回调函数,然后用jsonp请求这份txt文件:
function finalList(data) {
console.log(data); // jsonp请求成功时,执行此回调函数,即拿到了json数据
}
定义的回调函数的名字和txt文件中的函数名字是一样的,这样才能拿到数据。实际上,回调函数的名字前端应该是可以自定义的,比如这样:
<script src="htpp://abc.com/finalUserList.txt?callback=callbackFuncName"></script>
后端拿到callback参数值,再自动生成对应的函数名,但这样无疑又增加了服务端的工作量。由于项目中调用的静态化数据接口也不算多,我们就直接约定死了回调函数的名字。
jsonp的原理:Web页面上调用js文件时不受是否跨域的影响(不仅如此,我们还发现凡是拥有”src”这个属性的标签都拥有跨域的能力,比如<script>、<img>、<iframe>)。
因此,我们在html文件中动态创建<script>标签即可获取跨域资源:
var script = document.createElement('script');
script.setAttribute('src', 'http://abc.com/finalUserList.txt');
// 把script标签加入head,此时调用开始
document.getElementsByTagName('head')[0].appendChild(script);
function finalList(data) {
console.log(data)
}
postMessage
postMessage()
方法允许来自不同源的脚本采用异步方式进行有限的通信,可以实现跨文本档、多窗口、跨域消息传递。
页面与其打开的新窗口通信
多窗口之间通信
页面与嵌套iframe通信
项目中单点登录功能采用的是嵌套iframe形式。我们有模拟炒股大赛和实盘炒股大赛两个项目,域名不一样,是分开部署的。但是大赛之间又有联系,同属于“xxx”项目,用户在网页之间跳转的时候是不区分模拟大赛还是实盘大赛的,所以,单点登录的需求就产生了:当用户在模拟盘登录成功后,跳转到实盘页面的时候,实盘也实现了自动登录,反之亦然。
我们采用的方法是:在模拟盘登录的地方,嵌套了一个opacity: 0
的iframe,其src属性地址为实盘的某一个页面URL。当模拟盘登录成功的时候,会返回一个包含用户信息的token,修改iframe的src = URL + '?' + token
。然后在实盘对应的页面写js逻辑:获取URL中的token值,把token值传给后端以实现登录功能。
单点登录功能理论上是实现了,但是如果模拟盘登录的时候,一登录成功就立即跳转到其他页面的话(一般逻辑也是这样的,有个单独的登录页面,登录成功之后跳转到某一个默认的页面),登录页就消失了,所嵌入的iframe也消失了。一旦iframe消失,实盘项目对应的登录实现也就停止了。
所以,模拟盘登录成功之后不能立马就跳转,要等实盘那边返回一个登录成功与否的信息,二者之间的通信用的就是postMessage
。
// 实盘登录的处理
API.singleLogin({'token': token}).then(function(data) {
if (data.errorNo == 0) {
// 实盘登录成功,向模拟盘发送状态信息
window.parent.postMessage('autoLoginSuceess', '*')
...
// 写cookie等其他操作
...
}
});
// 模拟登录的处理
API.login({ mobile: xxx, password: *** }).then(function (data) {
if (data.errorNo == 0) {
...
// 写cookie等其他操作
...
// 单点登录相关处理
$('iframe').attr('src',URL + data.token); // URL加上token参数
var timesRun = 0;
var interval = setInterval(function() {
// 监听实盘iframe传回的信息
window.addEventListener('message', function(ev){
// 一旦返回信息,则立即执行跳转操作
if (ev.data == 'autoLoginSuceess') {
clearInterval(interval);
// 跳转页面操作
}
}, false);
timesRun += 1;
// 如果1s之后实盘还没返回信息,则模拟盘正常跳转
if (timesRun > 5) {
clearInterval(interval);
// 跳转页面操作
}
}, 200);
}
});
大致处理过程就如上述代码所示,关于postMessage更详细的用法请自行查阅资料。
其实项目中用到postMessage的还有另一个地方,也更简介地说明了postMessage的用法。上面我记述了登录中用到的postMessage,一是为了说明postMessage的用法,第二个也是想记录一下单点登录的做法。
现在简要地说一下另一处用到postMessage的地方。
我们的项目是三方合作的那种,我们做出的页面最后都是嵌入在qq域名下的,所以也是一种iframe嵌入。Bootstrap的模态窗很好用,当在本地开发时是完全ok的:页面不能滚动,弹窗的位置是固定在当前视窗内的。但是一旦项目被嵌入到qq域名下,就出问题了——弹窗是随着鼠标滚动而上下滚动的,如果用户滚动到页面比较靠下的位置,点击按钮弹出模态窗的话,此时模态窗是靠在页面顶端的,造成一种用户看不到弹窗的情况。
我们采取的措施是:在qq域名下的页面写鼠标滚动的监听事件,监听鼠标滚动了多高,然后把这个高度传给iframe(也就是我们项目开发能处理的地方),我们根据这个高度再去动态设置模态窗的高度,以达到模态窗能出现在当前视图内(此时,弹窗依然是随着鼠标滚动而上下滚动的,但是效果已经好很多了,起码已经能让用户看到了,捂脸~~~)。当然了,写鼠标滚动监听事件会涉及到函数节流,此处就不多阐述了。