前几日在朋友圈刷到一篇文章 我是怎么把博客粉丝转到公众号的,以为至关有创意。最近一年在作 toC 产品,一直在谈拉新留存转化。这恰好能够做为一个黑客增加的成功案例。javascript
鉴于我本身也有一个博客,而且日均UV在200左右,决定来试一试。整理了一下思路,差很少与短信验证码的逻辑类似,因而花了一天时间搞定。css
另外,在此以前我也花了一天时间调研了 serverless
。因此你彻底能够零成本实现从博客到公众号引流的功能。html
先来谈一谈需求点:前端
需求很简单,如图下所示。你也能够去个人网站 天天学习一点点 查看实现效果vue
不得不说,弹窗实在是一件伤用户体验的事情了。但也有一些措施,可以让用户体验变得稍微优化一点java
简单过一遍技术栈,博客采用了 vuepress
,前端的技术栈就是 vue
了。node
因为是一个小小的服务,后端则尽量轻量,因而我选择了 koa
。为了方便后端迁移,如我之后将会迁移到 serverless
,则使用无状态服务,即不依赖数据存储。若是没有状态,那怎么认证用户呢?使用 jwt
git
关于部署,则使用 docker
,docker-compose
以及 traefik
。至于部署这块,我有一个基础设施很完善的服务器环境,能够参考文章 当我有一台服务器时我作了什么。github
这下子实现思路就很清晰了:面试
docker compose
和 docker
因为无状态,很容易迁移到 serverless。且在微信环境下很容易制做测试环境与生产环境traefik
,方便自动服务发现与证书管理在前端控制内容被二维码遮挡的主要实如今于 CSS,而CSS主要控制两点
咱们博客大部分使用静态生成器,从 markdown
生成,生成的 html
大体长这个样子。
<div class="content">
<h1></h1>
<p></p>
<p></p>
</div>
复制代码
如今前端的发展趋势是状态即UI,咱们使用一个变量 isLock
来控制全部样式。咱们在 vue template
中加入弹框。
<div :class="{ lock: isLock }">
<!-- markdown内容 -->
<Content />
<!-- 弹框 -->
<div class="content-lock">
</div>
</div>
复制代码
弹框的显示隐藏容易控制,那 Content
即文章内容的呢,如何控制只显示前N段?
这确定难不倒曾经三个月的工做只写 CSS
的我,使用 nth-child
。stylus
代码以下
.theme-default-content.lock
.content__default
:nth-child(3)
opacity .5
:nth-child(4)
opacity .2
:nth-child(n+5)
display none
.content-lock
display block
复制代码
另外,咱们也加了点渐变效果提高观感:前两段内容显示,第三段第四段渐变,五段之后所有隐藏。具体见 css
代码
在 vue
中主要控制状态 isLock
,除了真实解锁逻辑还有一个随机性的弹窗。this.lock
表明是否解锁,this.isLock
表明是否显示弹窗
{
data () {
return {
lock: false,
code: ''
}
},
computed: {
isLock () {
return this.lock ? Math.random() > 0.5 : false
}
},
}
复制代码
口令须要是持久化的:保证每次刷新页面口令都是一致的。所以口令存储于 localStorage
中,随机生成四位数字,代码以下
function getCode () {
if (localStorage.code) {
return localStorage.code
}
const code = Math.random().toString().slice(2, 6)
localStorage.code = code
return code
}
复制代码
接下来的逻辑是
先看在微信这边的逻辑。我对微信开发封装成了简单的路由形式,核心逻辑以下:当接收到数字码时存储到 cache
中,这里使用了一个简单的内存 lru,只存储口令三分钟
cache
中存储了 code: userOpenId
键值对
function handleCode (message) {
const { FromUserName: from, Content: code } = message
// 对于 code,存储三分钟
cache.set(code, from, 3 * 60 * 1000)
return '您好,在三分钟内刷新网站便可无限制浏览全部文章'
}
const routes = [{
default: true,
handle: handleDefault
}, {
text: /\d{4}/,
handle: handleCode
}]
复制代码
此时的用户认证就很简单了,传统形式的基于 session
的用户认证。后端采用 koa
开发,代码以下,此时还使用了 JOI
作了简单的输入检验
exports.verifyCode = async function (ctx) {
const { code } = Joi.attempt(ctx.request.body, Joi.object({
code: Joi.string().pattern(/\d{4}/)
}))
if (!cache.get(code)) {
ctx.body = ''
return
}
const from = cache.get(code)
ctx.body = jwt.sign({ from }, secret, { expiresIn: '3y' })
}
复制代码
不会,因为在服务端用户口令只在内存中存在三分钟,因此冲突的可能性很小。那三分钟以后,如何进行用户状态的持久化呢?
可是此时有一个问题,cache
只能存储维护三分钟数据状态。这个问题如何解决?
此时使用 JWT
来作用户认证。所以校验口令时返回生成的 jwt
,在浏览器端持久化,使用它来保持用户状态。关于 JWT
,能够看我之前的文章 JWT 深刻浅出
exports.verifyCode = async function (ctx) {
// ...
ctx.body = jwt.sign({ from }, secret, { expiresIn: '3y' })
}
复制代码
持久化用户认证逻辑前端部分
async function verifyToken (token) {
const { data: { data: verify } } = await request.post('/api/verifyToken', {
token
})
return verify
}
async mounted () {
const code = getCode()
this.code = code
if (!localStorage.token) {
this.lock = true
const token = await verifyCode(code)
if (token) {
localStorage.token = token
this.lock = false
}
} else {
const token = localStorage.token
const verify = await verifyToken(token)
if (!verify) {
this.lock = true
}
}
}
复制代码
持久化用户认证逻辑后端部分
exports.verifyToken = async function (ctx) {
const { token } = Joi.attempt(ctx.request.body, Joi.object({
token: Joi.string().required()
}))
ctx.body = jwt.verify(token, secret)
}
复制代码
当后端使用 jwt
用户认证时,服务器端收到 openId
,根据 openId
获取用户信息,若是没有获取到,说明用户取消了订阅。
可是获取用户信息此项权限我的公众号不曾拥有
开发完成以后使用 docker
及 docker-compose
部署,traefik
作服务发现,经过 https://we.shanyue.tech
暴露服务,这三者在本系列文章中有所介绍
Dockerfile
较为简单,配置文件以下
FROM node:10-alpine
WORKDIR /code
ADD package.json /code RUN npm install --production
ADD . /code
CMD npm start 复制代码
docker-compose.yaml
配置文件以下
version: '3'
services:
wechat:
build: .
restart: always
labels:
- traefik.http.routers.wechat.rule=Host(`we.shanyue.tech`)
- traefik.http.routers.wechat.tls=true
- traefik.http.routers.wechat.tls.certresolver=le
expose:
- 3000
networks:
default:
external:
name: traefik_default
复制代码
当咱们须要测试微信公众号时,直接使用本身的公众号不太合适,特别是当已有上线内容时。微信官方提供了测试公众号,咱们能够从新填写 域名
以及 token
。在测试环境使用域名 https://we.dev.shanyue.tech
咱们在 docker-compose
中使用 service
中的 wechat
表明生产环境,wechat-dev
表明测试环境
wechat-dev
经过文件挂载提供服务,能够更新重启应用,即可以作到实时更新代码,并实时在测试公众号中看到效果。
docker-compose.yaml
配置文件以下
version: '3'
services:
wechat:
build: .
restart: always
labels:
- traefik.http.routers.wechat.rule=Host(`we.shanyue.tech`)
- traefik.http.routers.wechat.tls=true
- traefik.http.routers.wechat.tls.certresolver=le
expose:
- 3000
wechat-dev:
image: 'node:10-alpine'
restart: always
volumes:
- .:/code
working_dir: /code
command: npm run dev
labels:
- traefik.http.routers.wechat-dev.rule=Host(`we.dev.shanyue.tech`)
- traefik.http.routers.wechat-dev.tls=true
- traefik.http.routers.wechat-dev.tls.certresolver=le
expose:
- 3000
networks:
default:
external:
name: traefik_default
复制代码
关于前端代码,皆在 shfshanyue/Daily-Question 中
关于后端代码,皆在 shfshanyue/wechat 中
而本篇文章,属于 我的服务器运维指南 的案例篇,关于 docker
,compose
及 traefik
等基础设施的搭建均在本系列中有所介绍
除了经过扫码回复公众号口令得到文章解锁外,这里也有一个永久 token 能够解锁。复制如下代码到控制台刷新便可解锁所有文章
localStorage.token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE1Nzc2MjI1MjEsImV4cCI6MTY3MjI5NTMyMX0.tB-CgK6aIo3whD-mAu3X37XT8q9v2bVXxG6llodznws'
复制代码
我是山月,能够加我微信
shanyue94
与我交流,备注交流。另外能够关注个人公众号【全栈成长之路】