代理转发

项目是前后端分离,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(也就是我们项目开发能处理的地方),我们根据这个高度再去动态设置模态窗的高度,以达到模态窗能出现在当前视图内(此时,弹窗依然是随着鼠标滚动而上下滚动的,但是效果已经好很多了,起码已经能让用户看到了,捂脸~~~)。当然了,写鼠标滚动监听事件会涉及到函数节流,此处就不多阐述了。


无名小贝勒
5.7k 声望324 粉丝

引用和评论

0 条评论