设计一个友情链接模块的业务逻辑,并没有想象中简单!

前几天为了seo,交换了一些友情链接,关于友情链接的模块也有一些大概常人没注意到的经验,于是把这个一步步完善的过程中一些实践分享出来。

你可能会好奇,一个这么简单的友情链接模块有什么可以值得写一篇长篇大论的?非也,我相信看完这篇文章后,说得自信一点:99%的开发者不会在业务逻辑里处理到我的最终的设计版本。

背景:我的站是没有后台的,自然每个模块都要自己写。通常我是渐进式的开发,一些功能常常先简单实现譬如静态处理,直到需要考虑效率问题时才继而开发成模块。

一、静态处理

由浅入深,从头讲起。我一开始静态处理,写死在变量,所谓的硬编码hard code:

  //controller
  let friendlink = [
    {
      url:`https://sxg.kuashou.com`,
      name:`苏晓光博客`
    },
    {
      url:`https://tennis.kuashou.com`,
      name:`小网球`
    }
  ]
  await ctx.render('page', {
    //...
    friendlink
  })

  //view
  {% for item in friendlink %}
    <li><a href="{{item.url}}" target="_blank">{{item.name}}</a></li>
  {% endfor %}

二、数据持久化

随后自然是需要交换得多了,肯定不能每次都去后台文件修改提交重启项目。这时就得考虑数据持久化,即写入数据库:

  //controller
  let friendlink = await friendLinkModel.findAll()
  await ctx.render('page', {
    //...
    friendlink
  })

  //view
  {% for item in friendlink %}
    <li><a href="{{item.url}}" target="_blank">{{item.name}}</a></li>
  {% endfor %}

经过步骤二的数据持久化后这篇文章就end了吗?当然不。

三、处理友情链接的请求状态

考虑到友情链接的特性,我想到很多网站交换友情链接之后,运营人员如果不常常检查,可能会存在对方网站链接失效(服务器问题,网站关闭等等原因)的现象。

这对于seo是一个负面的评价项,搜索引擎会在爬友情链接时检查出站链接的情况,一旦出站链接404或者其他什么原因,会给予本站一定的降权影响:不负责的形容大概就是株连九族的意思吧。

那这个时候可以优化成,每次获取数据库链接时检查一下友情链接的状态,这里用到axios模块:

  //controller
  const axios = require('axios')
  /**
   * 检查链接方法
  */
  const checkUrl = website => new Promise((resolve,reject)=>axios.get(website.url).then(res=>{
    resolve(Object.assign(website,{status:true}))
  })).catch(e=>resolve(Object.assign(website,{status:false}))))

  let data = await friendLinkModel.findAll()
  let urlRequest = await Promise.all(data.map(checkUrl))
  //过滤返回成功的网站链接
  let friendlink = urlRequest.filter(item=>item.status===true)
  await ctx.render('page', {
    //...
    friendlink
  })

  //view
  {% for item in friendlink %}
    <li><a href="{{item.url}}" target="_blank">{{item.name}}</a></li>
  {% endfor %}

经过步骤三这时候就可以在对方网站404或者请求不到时,渲染页面就不会将该网站链接渲染出来,直到对方网站可以恢复正常访问。

四、处理友情链接的反链状态

这里又进一步抛出一个新的问题:即对方网站虽然可以访问,但是对方撤掉友情链接了怎么办?

确实会出现这个情况,很多对方站长一开始会大量的寻找友情链接,但是将来保不齐会哪天就撤掉本方的链接(也或者其他原因譬如是站长人品问题、譬如对方权重高了嫌弃一些低权重的都很正常)。有人品一点会通知对方,更多的就是没人品默默的撤掉。

那这种情况要解决起来也很方便,我们最后完善一下这个友情链接的模块:

  //controller
  const axios = require('axios')
  /**
   * 检查链接方法
  */
  const checkUrl = website => new Promise((resolve,reject)=>axios.get(website.url).then(res=>{
    if(res.data.includes('sxg.kuashou.com')){ //请求的body里看有没有己方链接,这里应该用正则,我示例一下就好
      resolve(Object.assign(website,{status:true}))
    }else{
      resolve(Object.assign(website,{status:false}))
    } 
  })).catch(e=>resolve(Object.assign(website,{status:false}))))

  let data = await friendLinkModel.findAll()
  let urlRequest = await Promise.all(data.map(checkUrl))
  //过滤返回成功的网站链接
  let friendlink = urlRequest.filter(item=>item.status===true)
  await ctx.render('page', {
    //...
    friendlink
  })

  //view
  {% for item in friendlink %}
    <li><a href="{{item.url}}" target="_blank">{{item.name}}</a></li>
  {% endfor %}

经过步骤四这时候就可以在对方网站404或者请求不到并且首页找不到a标签指向己方网站时不将该网站链接渲染出来,直到对方网站恢复正常访问并且正常加上己方链接。

最后的一点可能的疑问是,每一次渲染页面时都需要等待这么多的请求操作,会不会导致页面相应很慢?

并不避讳的说,会的。如果有50个友情链接,那就需要发送50次的网站检查,如果每次请求消耗最少一秒,那也将近一分钟的响应时长,这当然是不可接受的。

简单想到两个解决方案:

一、将检查逻辑移到其他进程

可以启动一个定时任务去间隔检查友情链接的情况,具体实现方法这里不表。

二、缓存静态化页面

这也是我的方案,我的首页做了CDN缓存,缓存失效时间是一天,这样在失效前返回客户端的都是缓存页面,不会存在后台发送请求的消耗。而每天会经历一次缓存失效,需要再次检查友情链接情况,我是主动发送请求预热缓存。

至此,一个友情链接模块的开发就基本完毕,主要还是讲的是业务逻辑上的设计,并不是产品形态上的设计。