0%

谈一谈博客的关注解锁文章功能

在这个互联网时代,拥有流量就仿佛于拥有了一切。 我大约在 2014 年底开了自己的个人博客,当时就是想自己记录点学习总结,一个是方便查阅,二是锻炼一下自己写总结或者文章的能力,最初就是记录一些日常生活、编程学习的小知识点什么的。 一次偶然的机会我接触了爬虫,当时用 Python 写爬虫的仿佛也不多,正好有一位学长有研究,我也就跟着他学了起来,学的时候也是自己总结,然后把一些文章发表到博客上,累积了十几篇左右。不知道是什么原因,渐渐地好像爬虫火了起来,Python 也火了起来,不知不觉地我发现我的博客慢慢地流量涨起来了,一天几百、几千一直到现在上万的浏览量,SEO 也逐渐好了起来,说实话我当时都没有想到,感觉还是不少运气成分在里面的。 两年前左右我开了一个公众号,开始在公众号上面发一些文章,自己也逐渐从博客转战到公众号上面了,因为公众号的环境总体来说还是很不错的,尤其是对原创作者来说非常友好,非常尊重原创。转载文章需要开白、原创命中和保护机制、洗稿检测、及时的投诉处理让越来越多的技术人员也转到公众号上来了。所以越来越多的技术开发者都拥有了自己的公众号,变成了一个人人公号的时代。这导致了一个什么结果?竞争日益激烈,读者的可选择范围太多了,大家的涨粉之路也走得越来越艰辛了。 同样地我也遭受着同样的苦恼,这时候我突然想起来,我似乎还有个博客呢?最近写文都专注于公众号,没太有心思打理自己博客了。我在想要是能够把我的博客流量转换到我的公众号上来该多好呢?一来我的博客读者可以关注到我的公号平时发的文章或通知,二来也着实能为自己的公号涨一点粉丝,这样该多好啊? 思来想去我想到了一个法子,就是在浏览博客文章的时候,把后续内容的隐藏,留一个二维码,可以通过关注公众号解锁。 当时设想效果图就是这样子的: image-20190914224503383 文章在某个位置会渐变隐藏,同时浮现一个公众号的样子,需要扫码才能解锁。这时候读者扫码自动关注了公号,博客文章也自然而然地解锁,这样博客的读者就自然关注到公号上面来了。

功能要点

一听到这样的法子,大家肯定就骂起来了,文章还要解锁来看?每篇文章都要解锁一遍吗?以后如果再打开还需要次次解锁吗? 如果真的是这样,那我情愿不做这个功能,因为这太损伤「用户体验」了。为了尽量减少用户体验的损失,这个功能必须要满足以下几点:

  • 不要添加用户登录注册机制,一旦增加了这个机制,流程可能会大大复杂化,导致用户体验急剧下降。
  • 不能每打开一个页面都要解锁一次,读者访问了我的博客,只需要一次解锁,即可全面解锁博客所有文章。
  • 读者在关闭浏览器再重新打开浏览器浏览博客的时候,同样不能让读者再解锁一遍,要直接可看。
  • 读者在手机或其他移动设备上不方便操作,手机站点禁止启用本功能。

如果满足了这些条件,读者在一篇文章里面只要扫码解锁了一次,那么就可以永久解锁全站文章了,没有繁琐的登录注册功能,也不需要次次频繁解锁,这样用户体验就非常好了。 为了达成这个目的,我就开始开发这个功能了。

识别用户

那么怎么来实现呢?要实现上面的功能,其实最重要的就是来识别是哪一个用户,也就是说,我怎么知道到底是谁在浏览我的博客呢?我怎么来专门针对这个用户解锁呢? 有的同学可能说那就用 IP 地址呗,技术角度是可以实现的,但是其实仔细想想,用 IP 地址是很不友好的。一来是很多用户可能都是内网的 IP 地址,多个公户共享一个公网 IP 地址,所以假如两台设备接入了同一个公网 IP,我是无法判断到底解锁哪一台设备的。二来是,如果一个用户换了其他的地方或者用了 VPN,IP 地址变了,原本解锁的设备又变成非解锁状态了。这样也不好。 那么最方便简单的用来标识一个浏览设备的东西是什么?当然是 Cookies。Cookies 里面保存了浏览网页时自动生成的 Session ID,而且每一个用户都是不一样的,这样不就可以来唯一标识一台浏览设备了吗?

解锁逻辑

好,那有了用户的 ID,我怎么才能把用户 ID 和我的公众号关联起来呢?当然是把这个 ID 发到公众号后台,我来存起来就好了。然后博客这边定时检测我这边有没有把这个 ID 保存,如果保存了,那就呈现解锁状态,如果没有保存,那就呈现非解锁状态。 最开始我就设想,既然公众号要扫码关注,那么我能不能把这个 ID 也糅到二维码里面呢?这样关注公众号的时候既能查询到公众号,有传递过来一个 ID 作为参数,然后后台处理一下存起来就好了。 你别说还真有这个功能,我在微信平台官方文档里面查询到了一个「生成带参数的公众号二维码」,生成的二维码里面可以指定任意的参数,然后生成的二维码图案就是公众号的二维码,然后处理一下关注公众号的回调函数就可以执行某一些操作了。看到之后我就想起来了很多关注公众号自动登录的功能就是这么做的。 但是经过一系列操作,发现了一个很悲伤的事情,只有服务号才有这个功能,我一小小的订阅号,是没有这个权限的,不能生产带参数的二维码。哎,难道凉了吗? 不,没有,既然这个参数不能通过二维码传递,那就只好麻烦读者手动把这 ID 输入到我的公众号了,我的小小的订阅号还是有处理消息的功能的。我的公众号后台接收到消息,然后处理下这个消息 ID,然后存起来,那不就好了吗? 说干就干!

隐藏文章

怎么开始做呢?那就从隐藏文章开始做吧。首先这个隐藏不能是真正的后台的隐藏,需要在前台隐藏。如果是后台隐藏的话,搜索引擎所能爬到的我的网站内容就会缺失了,会影响 SEO 的。所以只需要前台 CSS 隐藏一下就好了。 怎样看起来隐藏得比较自然呢?就取文章的的一半的地方,把文章的下面部分用 CSS 藏起来,然后加个渐变效果就好了。 比如要隐藏一半的内容吧,首先可以获取文章区块的高度,然后把文章页面高度用 CSS 强制设置为原来的一半就好了,这个很好操作,然后再在最底下加个渐变的样子,仿佛底下还有文字的样子。 这个 CSS 用 background 属性就能实现了,参考代码如下:

1
2
3
4
5
#locker {
height: 240px;
width: 100%;
background: -webkit-gradient(linear, 0 top, 0 bottom, from(rgba(255, 255, 255, 0)), to(#fff));
}

这里就是设置个 240 像素的区块,然后从上面到下面是透明度渐变颜色就好了,整体效果是下面这个样子: image-20190914231647956 好,既然隐藏了,那么下面就加个提示吧,把公众号的二维码先放上,然后把那个 Session ID 放上,提示用户关注公众号后发送这个 ID 就能解锁了,但这个 ID 又不能太长,多少呢?六位吧那就。 类似做成这样的样子: image-20190914231805506 好,那么这个 ID 怎么获取的呢? 刚才说了,从 Cookies 里面获取就行了,找那个能够标识 Session ID 的一个 Cookies 字段,然后摘取其值的其中几位就行了,摘取的位置也有讲究,前几位仿佛重复率很高的样子,后面几位几乎不重复,那就截取最后六位数字吧。 好,然后我就在博客里面加了这么一点 JavaScript 代码来实现这个 ID 的提取:

1
2
3
4
5
6
7
8
9
10
11
12
13
function getCookie(name) {
var value = "; " + document.cookie;
var parts = value.split("; " + name + "=");
if (parts.length == 2) return parts.pop().split(";").shift();
}

function getToken() {
let value = getCookie('UM_distinctid')
if (!value) {
return defaultToken
}
return value.substring(value.length - 6).toUpperCase()
}

这里 getCookie 方法是用某个名字获取一个 Cookies 字段,getToken 方法是截取了 Cookies 这个字段值的后六位并做了大写处理。 这里我的一个可以用来标识 Session ID 的 Cookies 字段叫做 UM_distinctid,就用它了。 这样一来,每个用户浏览的时候就能生成这样的一个 ID 了,六位的。 胜利似乎越来越近了。

持久化存储

这里就又遇到一个问题,刚才不是说还要在用户关闭浏览器之后再重新打开,依然能保持解锁状态吗?这就要求这个 ID 在用户关闭又打开浏览器的时候是不变的。 这个怎么解?很简单,反正已经是从 Cookies 里面读了,这个 Cookies 持久化就行了,只要不在浏览器关闭后清除就行了,怎么办?设置个过期时间就好。 由于我的站点是 WordPress 做的,所以这个功能自动有了,如果没有的话用一些插件也能实现的。

公众号处理

好,现在 ID 也有了,用户扫码把这个 ID 发到公众号后台就行了吧,然后公众号对接开发者模式处理一下就好了。 这里就其实就很简单了,其实仅仅就是把用户的 OpenID 和这个码存到了一个数据库里面。我后台是用 Django 写的,所以用了 Django 里面的 Model,实现逻辑如下:

1
2
3
4
5
6
7
8
9
10
def unlock(source, target, content):
"""
解锁博客
:param target: 微信平台
:param source: 用户
:param content: 用户发来的码
:return:
"""
Unlock.objects.get_or_create(openid=source, token=content.upper())
return reply_text(source, target, '恭喜您已经解锁博客全部文章~')

就是这么两行,插入了一条数据,然后返回了一个信息提示。 插入之后怎么办呢?博客得知道我已经把这条数据插入进来了呀?那就再提供一个 API 查询吧,实现如下:

1
2
3
4
5
6
7
8
9
10
11
def is_locked(request):
"""
判断是否已经解锁
:param request: 包含token的请求
:return:
"""
token = request.GET.get('token')
result = Unlock.objects.filter(token=token.upper()).first()
return JsonResponse({
'locked': False if result else True
})

把这个方法对接一个 API 接口,比如 /api/locked?token=xxxxx,就可以知道是否解锁了。 所以,在公众号后台我就用开发者模式对接了这么两个功能,一个用来存,一个用来查。只要用户发送了这个能够用来表示自己浏览设备的码,我就存下来,然后博客定时请求这个 API 查询状态,如果返回结果是未解锁状态,那就继续锁住,如果是解锁状态。那就把博客解开。

博客端处理

那么博客端具体怎么来处理呢?就基本的轮询就好了,定时几秒查一次 API,然后把这个码当做参数传过去,然后根据查询结果执行解锁或非解锁操作就好了。 核心代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
(articleSelector).ready(function () {
var articleElement = $(articleSelector)[0]
if (articleElement) {
var height = articleElement.clientHeight
var halfHeight = height * 0.3
var token = getToken()
$('#locker').find('.token').text(token)
function detect() {
$.ajax({
url: 'https://weixin.cuiqingcai.com/api/locked/',
method: 'GET',
data: {
token: token
},
success: function (data) {
if (data.locked === true || data.locked === false) {
locked = data.locked
}
},
error: function (data) {
locked = false
}
})
}
}
})

这里就用基本的 jQuery 实现的,其实就是调了个 Ajax,也没啥高深的技巧。这里唯一值得注意的一点设计就是,如果 API 请求失败,这基本上证明我的 API 服务挂掉了,这里就需要把 locked 设置为 false,证明为解锁状态。这样,万一我的 API 后台挂了,博客会直接是解锁状态,这样就避免了读者永远无法解锁了。这是一个细节上的设计。 至此,一些技术上的问题就基本解决了。

手机端处理

最后回过头来看看,那个需求还没有满足? 读者在手机或其他移动设备上不方便操作,手机站点禁止启用本功能。那么怎么实现呢?很简单,判断一下浏览器的 User-Agent 就好了,这里实现了一个判断是否是 PC 的方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var os = function () {
var ua = navigator.userAgent,
isWindowsPhone = /(?:Windows Phone)/.test(ua),
isSymbian = /(?:SymbianOS)/.test(ua) || isWindowsPhone,
isAndroid = /(?:Android)/.test(ua),
isFireFox = /(?:Firefox)/.test(ua),
isChrome = /(?:Chrome|CriOS)/.test(ua),
isTablet = /(?:iPad|PlayBook)/.test(ua) || (isAndroid && !/(?:Mobile)/.test(ua)) || (isFireFox && /(?:Tablet)/.test(ua)),
isPhone = /(?:iPhone)/.test(ua) && !isTablet,
isPc = !isPhone && !isAndroid && !isSymbian;
return {
isTablet: isTablet,
isPhone: isPhone,
isAndroid: isAndroid,
isPc: isPc
}
}()

这样一来,调用 os.isPC 就可以知道当前浏览器是不是手机浏览器了。 在处理的时候加上这个条件判断,就可以实现手机功能的解除了。

效果

可能大家想知道效果是如何的,这里就截图看看了,现在这个功能已经在我的博客 cuiqingcai.com 上线了,大家可以进去体验一下。 首先进去文章是这个样子的: image-20190914234237147 然后关注了公号,发送了代码: image-20190914234421831 发送完毕之后,大约一两秒之后,抬头看看博客,就是这个样子了: image-20190914234406442 这已经就完成了解锁和转化,读者可以全站永久解锁我的博客文章,我也增长了粉丝。 现在过一段时间就会有读者发来代码解锁,同时成为了我的粉丝,订阅号助手看到消息如下: image-20190914234720018 以上便是这个博客转化的思路分享和实现,大家也可以到我的博客体验一下,谢谢!