前幾日在朋友圈刷到一篇文章 我是怎麼把博客粉絲轉到公衆號的,以爲至關有創意。最近一年在作 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
與我交流,備註交流。另外能夠關注個人公衆號【全棧成長之路】