使用 vue-router 的導航守衛鉤子函數,某些鉤子函數可以讓開發者根據業務邏輯,控制是否進行下一步,或者進入到指定的路由。
例如,后臺管理頁面,會在進入路由前,進行必要登錄、權限判斷,來決定去往哪個路由,以下是偽代碼:
// 全局導航守衛
router.beforEach((to, from, next) => {
if('no login'){
next('/login')
}else if('admin') {
next('/admin')
}else {
next()
}
})
// 路由配置鉤子函數
{
path: '',
component: component,
beforeEnter: (to, from, next) => {
next()
}
}
// 組件中配置鉤子函數
{
template: '',
beforeRouteEnter(to, from, next) {
next()
}
}
調用 next,意味著繼續進行下面的流程;不調用,則直接終止,導致路由中設置的組件無法渲染,會出現頁面一片空白的現象。
鉤子函數有不同的作用,例如 beforEach,afterEach,beforeEnter,beforeRouteEnter,beforeRouteUpdate,beforeRouteLeave,針對這些注冊的鉤子函數,要依次進行執行,并且在必要環節有控制權決定是否繼續進入到下一個鉤子函數中。
以下分析下源碼中實現的方式,而源碼中處理的邊界情況比較多,需要抓住核心點,去掉冗余代碼,精簡出便于理解的實現。
精簡源碼核心功能
總結下核心點:鉤子函數注冊的回調函數,能順序執行,同時會將控制權交給開發者。
先來一個能夠注冊回調函數的類:
class VueRouter {
constructor(){
this.beforeHooks = []
this.beforeEnterHooks = []
this.afterHooks = []
}
beforEach(callback){
return registerHook(this.beforeHooks, callback)
}
beforeEnter(callback){
return registerHook(this.beforeEnterHooks, callback)
}
afterEach(callback){
return registerHook(this.afterHooks, callback)
}
}
function registerHook (list, fn) {
list.push(fn)
return () => {
const i = list.indexOf(fn)
if (i > -1) list.splice(i, 1)
}
}
聲明的類,提供了 beforEach 、beforeEnter 和 afterEach 來注冊必要的回調函數。
抽象出一個 registerHook 公共方法,作用:
注冊回調函數
返回的函數,可以取消注冊的回調函數
使用一下:
const router = new VueRouter()
const beforEach = router.beforEach((to, from, next) => {
console.log('beforEach');
next()
})
// 取消注冊的函數
beforEach()
以上的回調函數會被取消,意味著不會執行了。
router.beforEach((to, from, next) => {
console.log('beforEach');
next()
})
router.beforeEnter((to, from, next) => {
console.log('beforeEnter');
next()
})
router.afterEach(() => {
console.log('afterEach');
})
以上注冊的鉤子函數會依次執行。beforEach 和 beforeEnter 的回調接收內部傳來的參數,同時通過調用 next 可繼續走下面的回調函數,如果不調用,則直接被終止了。
最后一個 afterEach 在上面的回調函數都執行后,才被執行,且不接收任何參數。
先來實現依次執行,這是最簡單的方式,在類中增加 run 方法,手動調用:
class VueRouter {
// ... 其他省略,增加 run 函數
run(){
// 把需要依次執行的回調存放在一個隊列中
let queue = [].concat(
this.beforeHooks,
this.afterHooks
)
for(let i = 0; i < queue.length; i++){
if(queue(i)) {
queue(i)('to', 'from', () => {})
}
}
}
}
// 手動調用
router.run()
打?。?
'beforEach'
'beforeEnter'
上面把要依次執行的回調函數聚合在一個隊列中執行,并傳入必要的參數,但這樣開發者不能控制是否進行下一步,即便不執行 next 函數,依然會依次執行完隊列的函數。
改進一下:
class VueRouter {
// ... 其他省略,增加 run 函數
run(){
// 把需要依次執行的回調存放在一個隊列中
let queue = [].concat(
this.beforeHooks,
this.afterHooks
)
queue[0]('to', 'from', () => {
queue[1]('to', 'from', () => {
console.log('調用結束');
})
})
}
}
router.beforEach((to, from, next) => {
console.log('beforEach');
// next()
})
router.beforeEnter((to, from, next) => {
console.log('beforeEnter');
next()
})
傳入的 next 函數會有調用下一個回調函數的行為,把控制權交給了開發者,調用了 next 函數會繼續執行下一個回調函數;不調用 next 函數,則終止了隊列的執行,所以打印結果是:
'beforEach'
上面實現有個弊端,代碼不夠靈活,手動一個個調用,在真實場景中無法確定注冊了多少個回調函數,所以需要繼續抽象成一個功能更強的方法:
function runQueue (queue, fn, cb) {
const step = index => {
// 隊列執行結束了
if (index >= queue.length) {
cb()
} else {
// 隊列有值
if (queue[index]) {
// 傳入隊列中回調,做一些必要的操作,第二個參數是為了進行下一個回調函數
fn(queue[index], () => {
step(index + 1)
})
} else {
step(index + 1)
}
}
}
// 初次調用,從第一個開始
step(0)
}
runQueue 就是執行隊列的通用方法。
第一個參數為回調函數隊列, 會依次取出來;
第二個參數是函數,它接受隊列中的函數,進行一些其他處理;并能進行下個回調函數的執行;
第三個參數是隊列執行結束后調用。
知道了這個函數的含義,來使用一下:
class VueRouter {
// ... 其他省略,增加 run 函數
run(){
// 把需要依次執行的回調存放在一個隊列中
let queue = [].concat(
this.beforeHooks,
this.beforeEnterHooks
)
// 接收回到函數,和進行下一個的執行函數
const iterator = (hook, next) => {
// 傳給回調函數的參數,第三個參數是函數,交給開發者調用,調用后進行下一個
hook('to', 'from', () => {
console.log('執行下一個回調時,處理一些相關信息');
next()
})
}
runQueue(queue, iterator, () => {
console.log('執行結束');
// 執行 afterEach 中的回調函數
this.afterHooks.forEach((fn) => {
fn()
})
})
}
}
// 注冊
router.beforEach((to, from, next) => {
console.log('beforEach');
next()
})
router.beforeEnter((to, from, next) => {
console.log('beforeEnter');
next()
})
router.afterEach(() => {
console.log('afterEach');
})
router.run();
從上面代碼可以看出來,每次把隊列 queue 中的回調函數傳給 iterator , 用 hook 接收,并調用。
傳給 hook 必要的參數,尤其是第三個參數,開發者在注冊的回調函數中調用,來控制進行下一步。
在隊列執行完畢后,依次執行 afterHooks 的回調函數,不傳入任何參數。
所以打印結果為:
beforEach
執行下一個回調時,處理一些相關信息
beforeEnter
執行下一個回調時,處理一些相關信息
執行結束
afterEach
以上實現的非常巧妙,再看 Vue-router 源碼這塊的實現方式,相信你會豁然開朗。
文章目錄
小白學VUE——快速入門
前言:什么是VUE?
環境準備:
vue的js文件
vscode
Vue入門程序
抽取代碼片段
vue標準語法:
什么是vue指令?
v-bind指令
事件單向綁定
v-model:事件雙向綁定
v-on事件監聽指令
v: on:submit.prevent指令
v-if 判斷指令
v-for 循環渲染指令
Vue.js(讀音 /vju?/, 類似于 view) 是一套構建用戶界面的漸進式框架。 Vue 只關注視圖層, 采用自底向上增量開發的設計。 Vue 的目標是通過盡可能簡單的 API 實現響應的數據綁定和組合的視圖組件。
環境準備:
vue的js文件
使用CDN外部導入方法
以下推薦國外比較穩定的兩個 CDN,把這些網址放進script標簽的src屬性下即可,國內還沒發現哪一家比較好,目前還是建議下載到本地。
Staticfile CDN(國內) : https://cdn.staticfile.org/vue/2.2.2/vue.min.js
unpkg:https://unpkg.com/vue/dist/vue.js, 會保持和 npm 發布的的版本一致。
cdnjs : https://cdnjs.cloudflare.com/ajax/libs/vue/2.1.8/vue.min.js
2.VSCODE軟件
(2).使用內部導入方法(自行下載js文件放進工作區js文件夾即可)
前往vscode官網下載對應版本的vscode
Vue入門程序
首先了解一下什么是插值
插值:數據綁定最常見的形式就是使用 **{{…}}(雙大括號)**的文本插值:
單獨抽出這段來看一下:
Vue即是vue內置的對象,el(element)指的是綁定的元素,可以用#id綁定元素,data指的是定義頁面中顯示的模型數據,還有未展示的methods,指的是方法
var app = new Vue({
el: "#app",//綁定VUE作用的范圍
data: {//定義頁面中顯示的模型數據
message: 'hello vue'
}
});
代碼如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="js/vue.min.js"></script>
</head>
<body>
<!-- 插值表達式 獲取data里面定義的值 {{message}} -->
<div id="app">{{ message }}</div>
<script>
//創建一個VUE對象
var app = new Vue({
el: "#app",//綁定VUE作用的范圍
data: {//定義頁面中顯示的模型數據
message: 'hello vue'
}
});
</script>
</body>
</html>
步驟:文件-首選項-用戶片段
輸入片段名稱回車
{
"vh": {
"prefix": "vh", // 觸發的關鍵字 輸入vh按下tab鍵
"body": [
"<!DOCTYPE html>",
"<html lang=\"en\">",
"",
"<head>",
" <meta charset=\"UTF-8\">",
" <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">",
" <meta http-equiv=\"X-UA-Compatible\" content=\"ie=edge\">",
" <title>Document</title>",
" <script src=\"js/vue.min.js\"></script>",
"</head>",
"",
"<body>",
" <div id=\"app\"></div>",
" <script>",
" var vm=new Vue({",
" el:'#app',",
" data:{},",
" methods:{}",
" });",
" </script>",
"</body>",
"",
"</html>",
],
"description": "vh components"
}
}
此時,新建一個html文件,輸入vh在按下tab鍵即可快速填充內容
vue標準語法:
什么是vue指令?
在vue中提供了一些對于頁面 + 數據的更為方便的輸出,這些操作就叫做指令, 以v-xxx表示
類似于html頁面中的屬性 `
比如在angular中 以ng-xxx開頭的就叫做指令
在vue中 以v-xxx開頭的就叫做指令
指令中封裝了一些DOM行為, 結合屬性作為一個暗號, 暗號有對應的值,根據不同的值,框架會進行相關DOM操作的綁定
下面簡單介紹一下vue的幾個基礎指令: v-bind v-if v-for v-on等
v-bind指令
作用:
給元素的屬性賦值
可以給已經存在的屬性賦值 input value
也可以給自定義屬性賦值 mydata
語法
在元素上 v-bind:屬性名="常量||變量名"
簡寫形式 :屬性名="變量名"
例:
<div v-bind:原屬性名="變量"></div> <div :屬性名="變量"></div>
事件單向綁定,可以用 v-bind:屬性名="常量||變量名,綁定事件,用插值表達式取出值
<body>
————————————————
版權聲明:本文為CSDN博主「熱愛旅行的小李同學」的原創文章,遵循CC 4.0 BY-SA版權協議,轉載請附上原文出處鏈接及本聲明。
原文鏈接:https://blog.csdn.net/m0_46275020/java/article/details/106055312
1. 訪問內部屬性
JavaScript 對象無法以常規方式訪問的內部屬性。內部屬性名由雙方括號[[]]包圍,在創建對象時可用。
內部屬性不能動態地添加到現有對象。
內部屬性可以在某些內置 JavaScript 對象中使用,它們存儲ECMAScript規范指定的內部狀態。
有兩種內部屬性,一種操作對象的方法,另一種是存儲數據的方法。例如:
[[Prototype]] — 對象的原型,可以為null或對象
[[Extensible]] — 表示是否允許在對象中動態添加新的屬性
[[PrivateFieldValues]] — 用于管理私有類字段
2. 屬性描述符對象
數據屬性包含了一個數據值的位置,在這個位置可以讀取和寫入值。也就是說,數據屬性可以通過 對象.屬性 訪問,就是我么平常接觸的用戶賦什么值,它們就返回什么,不會做額外的事情。
數據屬性有4個描述其行為的特性(為了表示內部值,把屬性放在兩對方括號中),稱為描述符對象。
屬性 解釋 默認值
[[Configurable]] 能否通過delete刪除屬性從而重新定義屬性;
能否修改屬性的特性;
能否把屬性修改為訪問器屬性 true
[[Enumerable]] 能否通過for-in循環返回屬性 true
[[Writable]] 能否修改屬性的值 true
[[Value]] 包含這個屬性的數據值 undefined
value 描述符是屬性的數據值,例如,我們有以下對象 :
let foo = {
a: 1
}
那么,a 的value屬性描述符為1。
writable是指該屬性的值是否可以更改。 默認值為true,表示屬性是可寫的。 但是,我們可以通過多種方式將其設置為不可寫。
configurable 的意思是可以刪除對象的屬性還是可以更改其屬性描述符。 默認值為true,這意味著它是可配置的。
enumerable 意味著它可以被for ... in循環遍歷。 默認值為true,說明能通過for-in循環返回屬性
將屬性鍵添加到返回的數組之前,Object.keys方法還檢查enumerable 描述符。 但是,Reflect.ownKeys方法不會檢查此屬性描述符,而是返回所有自己的屬性鍵。
Prototype描述符有其他方法,get和set分別用于獲取和設置值。
在創建新對象, 我們可以使用Object.defineProperty方法設置的描述符,如下所示:
let foo = {
a: 1
}
Object.defineProperty(foo, 'b', {
value: 2,
writable: true,
enumerable: true,
configurable: true,
});
這樣得到foo的新值是{a: 1, b: 2}。
我們還可以使用defineProperty更改現有屬性的描述符。 例如:
let foo = {
a: 1
}
Object.defineProperty(foo, 'a', {
value: 2,
writable: false,
enumerable: true,
configurable: true,
});
這樣當我們嘗試給 foo.a 賦值時,如:
foo.a = 2;
如果關閉了嚴格模式,瀏覽器將忽略,否則將拋出一個錯誤,因為我們將 writable 設置為 false, 表示該屬性不可寫。
我們還可以使用defineProperty將屬性轉換為getter,如下所示:
'use strict'
let foo = {
a: 1
}
Object.defineProperty(foo, 'b', {
get() {
return 1;
}
})
當我們這樣寫的時候:
foo.b = 2;
因為b屬性是getter屬性,所以當使用嚴格模式時,我們會得到一個錯誤:Getter 屬性不能重新賦值。
3.無法分配繼承的只讀屬性
繼承的只讀屬性不能再賦值。這是有道理的,因為我們這樣設置它,它是繼承的,所以它應該傳播到繼承屬性的對象。
我們可以使用Object.create創建一個從原型對象繼承屬性的對象,如下所示:
const proto = Object.defineProperties({}, {
a: {
value: 1,
writable: false
}
})
const foo = Object.create(proto)
在上面的代碼中,我們將proto.a的 writable 描述符設置為false,因此我們無法為其分配其他值。
如果我們這樣寫:
foo.a = 2;
在嚴格模式下,我們會收到錯誤消息。
總結
我們可以用 JavaScript 對象做很多我們可能不知道的事情。
首先,某些 JavaScript 對象(例如內置瀏覽器對象)具有內部屬性,這些屬性由雙方括號包圍,它們具有內部狀態,對象創建無法動態添加。
JavaScript對象屬性還具有屬性描述符,該屬性描述符使我們可以控制其值以及可以設置它們的值,還是可以更改其屬性描述符等。
我們可以使用defineProperty更改屬性的屬性描述符,它還用于添加新屬性及其屬性描述符。
最后,繼承的只讀屬性保持只讀狀態,這是有道理的,因為它是從父原型對象繼承而來的。
web中開發的三個基本技術(html5,css3,JavaScript)
html簡介:html語言是純文本類型的語言,是internet上用來編寫網頁的主要語言,使用HTML語言編寫的網頁文件也是標準的純文本文件(簡單說告訴瀏覽器顯示什么)
.
css簡介:css是一種網頁控制技術,采用css技術,可以有效地對頁面、字體、顏色、背景和其他效果實現更加精準的控制
(簡單的說告訴瀏覽器如何顯示)
.
JavaScript:JavaScript是web頁面中的一種腳本編程語言,也是一種通用的、跨平臺的、基于對象和事件驅動并具有安全性的腳本語言。它不需要進行編譯,而是直接嵌入HTML頁面中,把靜態頁面變成動態頁面。(簡單的來說告訴瀏覽器如何交互)
簡單HTML文件結構
<html>/*文件開始*/ <head>/*文件頭*/ <title>標題</title>/*文件標題*/ </head> <body>內容</body> </html>/*文件結束*/
HTML常用的標記
<br>換行 <p></p>段落 <s></s>刪除線 <b></b>字體粗體 <u></u>下劃線 <em></em>斜體內容 <sub></sub> 下標 <sup></sup>上標 <hr></hr>水平線 <a></a>超鏈接 .....
Elasticsearch(下面簡稱ES)中的bool查詢在業務中使用也是比較多的。在一些非實時的分頁查詢,導出的場景,我們經常使用bool查詢組合各種查詢條件。
Bool查詢包括四種子句,
must
filter
should
must_not
我這里只介紹下must和filter兩種子句,因為是我們今天要講的重點。其它的可以自行查詢官方文檔。
must, 返回的文檔必須滿足must子句的條件,并且參與計算分值
filter, 返回的文檔必須滿足filter子句的條件。但是跟Must不一樣的是,不會計算分值, 并且可以使用緩存
從上面的描述來看,你應該已經知道,如果只看查詢的結果,must和filter是一樣的。區別是場景不一樣。如果結果需要算分就使用must,否則可以考慮使用filter。
光說比較抽象,看個例子,下面兩個語句,查詢的結果是一樣的。
使用filter過濾時間范圍,
GET kibana_sample_data_ecommerce/_search { "size": 1000, "query": { "bool": { "must": [ {"term": { "currency": "EUR" }} ], "filter": { "range": { "order_date": { "gte": "2020-01-25T23:45:36.000+00:00", "lte": "2020-02-01T23:45:36.000+00:00" } } } } } }
filter比較的原理
上一節你已經知道了must和filter的基本用法和區別。簡單來講,如果你的業務場景不需要算分,使用filter可以真的讓你的查詢效率飛起來。
為了說明filter查詢的原因,我們需要引入ES的一個概念 query context和 filter context。
query context
query context關注的是,文檔到底有多匹配查詢的條件,這個匹配的程度是由相關性分數決定的,分數越高自然就越匹配。所以這種查詢除了關注文檔是否滿足查詢條件,還需要額外的計算相關性分數.
filter context
filter context關注的是,文檔是否匹配查詢條件,結果只有兩個,是和否。沒有其它額外的計算。它常用的一個場景就是過濾時間范圍。
并且filter context會自動被ES緩存結果,效率進一步提高。
對于bool查詢,must使用的就是query context,而filter使用的就是filter context。
我們可以通過一個示例驗證下。繼續使用第一節的例子,我們通過kibana自帶的search profiler來看看ES的查詢的詳細過程。
使用must查詢的執行過程是這樣的:
可以明顯看到,此次查詢計算了相關性分數,而且score的部分占據了查詢時間的10分之一左右。
filter的查詢我就不截圖了,區別就是score這部分是0,也就是不計算相關性分數。
除了是否計算相關性算分的差別,經常使用的過濾器將被Elasticsearch自動緩存,以提高性能。
我自己曾經在一個項目中,對一個業務查詢場景做了這種優化,當時線上的索引文檔數量大概是3000萬左右,改成filter之后,查詢的速度幾乎快了一倍。
我們應該根據自己的實際業務場景選擇合適的查詢語句,在某些不需要相關性算分的查詢場景,盡量使用filter context
可以讓你的查詢更加。
前言
文章首次發表在 個人博客
之前寫過一篇 web安全之XSS實例解析,是通過舉的幾個簡單例子講解的,同樣通過簡單得例子來理解和學習CSRF,有小伙伴問實際開發中有沒有遇到過XSS和CSRF,答案是有遇到過,不過被測試同學發現了,還有安全掃描發現了可能的問題,這兩篇文章就是簡化了一下當時實際遇到的問題。
CSRF
跨站請求偽造(Cross Site Request Forgery),是指黑客誘導用戶打開黑客的網站,在黑客的網站中,利用用戶的登陸狀態發起的跨站請求。CSRF攻擊就是利用了用戶的登陸狀態,并通過第三方的站點來做一個壞事。
要完成一次CSRF攻擊,受害者依次完成兩個步驟:
登錄受信任網站A,并在本地生成Cookie
在不登出A的情況,訪問危險網站B
CSRF攻擊
在a.com登陸后種下cookie, 然后有個支付的頁面,支付頁面有個誘導點擊的按鈕或者圖片,第三方網站域名為 b.com,中的頁面請求 a.com的接口,b.com 其實拿不到cookie,請求 a.com會把Cookie自動帶上(因為Cookie種在 a.com域下)。這就是為什么在服務端要判斷請求的來源,及限制跨域(只允許信任的域名訪問),然后除了這些還有一些方法來防止 CSRF 攻擊,下面會通過幾個簡單的例子來詳細介紹 CSRF 攻擊的表現及如何防御。
下面會通過一個例子來講解 CSRF 攻擊的表現是什么樣子的。
實現的例子:
在前后端同域的情況下,前后端的域名都為 http://127.0.0.1:3200, 第三方網站的域名為 http://127.0.0.1:3100,釣魚網站頁面為 http://127.0.0.1:3100/bad.html。
平時自己寫例子中會用到下面這兩個工具,非常方便好用:
http-server: 是基于node.js的HTTP 服務器,它最大的好處就是:可以使用任意一個目錄成為服務器的目錄,完全拋開后端的沉重工程,直接運行想要的js代碼;
nodemon: nodemon是一種工具,通過在檢測到目錄中的文件更改時自動重新啟動節點應用程序來幫助開發基于node.js的應用程序
前端頁面: client.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>CSRF-demo</title>
<style>
.wrap {
height: 500px;
width: 300px;
border: 1px solid #ccc;
padding: 20px;
margin-bottom: 20px;
}
input {
width: 300px;
}
.payInfo {
display: none;
}
.money {
font-size: 16px;
}
</style>
</head>
<body>
<div class="wrap">
<div class="loginInfo">
<h3>登陸</h3>
<input type="text" placeholder="用戶名" class="userName">
<br>
<input type="password" placeholder="密碼" class="password">
<br>
<br>
<button class="btn">登陸</button>
</div>
<div class="payInfo">
<h3>轉賬信息</h3>
<p >當前賬戶余額為 <span class="money">0</span>元</p>
<!-- <input type="text" placeholder="收款方" class="account"> -->
<button class="pay">支付10元</button>
<br>
<br>
<a target="_blank">
聽說點擊這個鏈接的人都賺大錢了,你還不來看一下么
</a>
</div>
</div>
</body>
<script>
const btn = document.querySelector('.btn');
const loginInfo = document.querySelector('.loginInfo');
const payInfo = document.querySelector('.payInfo');
const money = document.querySelector('.money');
let currentName = '';
// 第一次進入判斷是否已經登陸
Fetch('http://127.0.0.1:3200/isLogin', 'POST', {})
.then((res) => {
if(res.data) {
payInfo.style.display = "block"
loginInfo.style.display = 'none';
Fetch('http://127.0.0.1:3200/pay', 'POST', {userName: currentName, money: 0})
.then((res) => {
money.innerHTML = res.data.money;
})
} else {
payInfo.style.display = "none"
loginInfo.style.display = 'block';
}
})
// 點擊登陸
btn.onclick = function () {
var userName = document.querySelector('.userName').value;
currentName = userName;
var password = document.querySelector('.password').value;
Fetch('http://127.0.0.1:3200/login', 'POST', {userName, password})
.then((res) => {
payInfo.style.display = "block";
loginInfo.style.display = 'none';
money.innerHTML = res.data.money;
})
}
// 點擊支付10元
const pay = document.querySelector('.pay');
pay.onclick = function () {
Fetch('http://127.0.0.1:3200/pay', 'POST', {userName: currentName, money: 10})
.then((res) => {
console.log(res);
money.innerHTML = res.data.money;
})
}
// 封裝的請求方法
function Fetch(url, method = 'POST', data) {
return new Promise((resolve, reject) => {
let options = {};
if (method !== 'GET') {
options = {
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(data),
}
}
fetch(url, {
mode: 'cors', // no-cors, cors, *same-origin
method,
...options,
credentials: 'include',
}).then((res) => {
return res.json();
}).then(res => {
resolve(res);
}).catch(err => {
reject(err);
});
})
}
</script>
</html>
實現一個簡單的支付功能:
會首先判斷有沒有登錄,如果已經登陸過,就直接展示轉賬信息,未登錄,展示登陸信息
登陸完成之后,會展示轉賬信息,點擊支付,可以實現金額的扣減
后端服務: server.js
const Koa = require("koa");
const app = new Koa();
const route = require('koa-route');
const bodyParser = require('koa-bodyparser');
const cors = require('@koa/cors');
const KoaStatic = require('koa-static');
let currentUserName = '';
// 使用 koa-static 使得前后端都在同一個服務下
app.use(KoaStatic(__dirname));
app.use(bodyParser()); // 處理post請求的參數
// 初始金額為 1000
let money = 1000;
// 調用登陸的接口
const login = ctx => {
const req = ctx.request.body;
const userName = req.userName;
currentUserName = userName;
// 簡單設置一個cookie
ctx.cookies.set(
'name',
userName,
{
domain: '127.0.0.1', // 寫cookie所在的域名
path: '/', // 寫cookie所在的路徑
maxAge: 10 * 60 * 1000, // cookie有效時長
expires: new Date('2021-02-15'), // cookie失效時間
overwrite: false, // 是否允許重寫
SameSite: 'None',
}
)
ctx.response.body = {
data: {
money,
},
msg: '登陸成功'
};
}
// 調用支付的接口
const pay = ctx => {
if(ctx.method === 'GET') {
money = money - Number(ctx.request.query.money);
} else {
money = money - Number(ctx.request.body.money);
}
ctx.set('Access-Control-Allow-Credentials', 'true');
// 根據有沒有 cookie 來簡單判斷是否登錄
if(ctx.cookies.get('name')){
ctx.response.body = {
data: {
money: money,
},
msg: '支付成功'
};
}else{
ctx.body = '未登錄';
}
}
// 判斷是否登陸
const isLogin = ctx => {
ctx.set('Access-Control-Allow-Credentials', 'true');
if(ctx.cookies.get('name')){
ctx.response.body = {
data: true,
msg: '登陸成功'
};
}else{
ctx.response.body = {
data: false,
msg: '未登錄'
};
}
}
// 處理 options 請求
app.use((ctx, next)=> {
const headers = ctx.request.headers;
if(ctx.method === 'OPTIONS') {
ctx.set('Access-Control-Allow-Origin', headers.origin);
ctx.set('Access-Control-Allow-Headers', 'Content-Type');
ctx.set('Access-Control-Allow-Credentials', 'true');
ctx.status = 204;
} else {
next();
}
})
app.use(cors());
app.use(route.post('/login', login));
app.use(route.post('/pay', pay));
app.use(route.get('/pay', pay));
app.use(route.post('/isLogin', isLogin));
app.listen(3200, () => {
console.log('啟動成功');
});
執行 nodemon server.js,訪問頁面 http://127.0.0.1:3200/client.html
CSRF-demo
登陸完成之后,可以看到Cookie是種到 http://127.0.0.1:3200 這個域下面的。
第三方頁面 bad.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>第三方網站</title>
</head>
<body>
<div>
哈哈,小樣兒,哪有賺大錢的方法,還是踏實努力工作吧!
<!-- form 表單的提交會伴隨著跳轉到action中指定 的url 鏈接,為了阻止這一行為,可以通過設置一個隱藏的iframe 頁面,并將form 的target 屬性指向這個iframe,當前頁面iframe則不會刷新頁面 -->
<form action="http://127.0.0.1:3200/pay" method="POST" class="form" target="targetIfr" style="display: none">
<input type="text" name="userName" value="xiaoming">
<input type="text" name="money" value="100">
</form>
<iframe name="targetIfr" style="display:none"></iframe>
</div>
</body>
<script>
document.querySelector('.form').submit();
</script>
</html>
使用 HTTP-server 起一個 本地端口為 3100的服務,就可以通過 http://127.0.0.1:3100/bad.html 這個鏈接來訪問,CSRF攻擊需要做的就是在正常的頁面上誘導用戶點擊鏈接進入這個頁面
CSRF-DEMO
點擊誘導鏈接,跳轉到第三方的頁面,第三方頁面自動發了一個扣款的請求,所以在回到正常頁面的時候,刷新,發現錢變少了。
我們可以看到在第三方頁面調用 http://127.0.0.1:3200/pay 這個接口的時候,Cookie自動加在了請求頭上,這就是為什么 http://127.0.0.1:3100/bad.html 這個頁面拿不到 Cookie,但是卻能正常請求 http://127.0.0.1:3200/pay 這個接口的原因。
CSRF攻擊大致可以分為三種情況,自動發起Get請求, 自動發起POST請求,引導用戶點擊鏈接。下面會分別對上面例子進行簡單的改造來說明這三種情況
自動發起Get請求
在上面的 bad.html中,我們把代碼改成下面這樣
<!DOCTYPE html>
<html>
<body>
<img src="http://127.0.0.1:3200/payMoney?money=1000">
</body>
</html>
當用戶訪問含有這個img的頁面后,瀏覽器會自動向自動發起 img 的資源請求,如果服務器沒有對該請求做判斷的話,那么會認為這是一個正常的鏈接。
自動發起POST請求
上面例子中演示的就是這種情況。
<body>
<div>
哈哈,小樣兒,哪有賺大錢的方法,還是踏實努力工作吧!
<!-- form 表單的提交會伴隨著跳轉到action中指定 的url 鏈接,為了阻止這一行為,可以通過設置一個隱藏的iframe 頁面,并將form 的target 屬性指向這個iframe,當前頁面iframe則不會刷新頁面 -->
<form action="http://127.0.0.1:3200/pay" method="POST" class="form" target="targetIfr">
<input type="text" name="userName" value="xiaoming">
<input type="text" name="money" value="100">
</form>
<iframe name="targetIfr" style="display:none"></iframe>
</div>
</body>
<script>
document.querySelector('.form').submit();
</script>
上面這段代碼中構建了一個隱藏的表單,表單的內容就是自動發起支付的接口請求。當用戶打開該頁面時,這個表單會被自動執行提交。當表單被提交之后,服務器就會執行轉賬操作。因此使用構建自動提交表單這種方式,就可以自動實現跨站點 POST 數據提交。
引導用戶點擊鏈接
誘惑用戶點擊鏈接跳轉到黑客自己的網站,示例代碼如圖所示
<a >聽說點擊這個鏈接的人都賺大錢了,你還不來看一下么</a>
用戶點擊這個地址就會跳到黑客的網站,黑客的網站可能會自動發送一些請求,比如上面提到的自動發起Get或Post請求。
如何防御CSRF
利用cookie的SameSite
SameSite有3個值: Strict, Lax和None
Strict。瀏覽器會完全禁止第三方cookie。比如a.com的頁面中訪問 b.com 的資源,那么a.com中的cookie不會被發送到 b.com服務器,只有從b.com的站點去請求b.com的資源,才會帶上這些Cookie
Lax。相對寬松一些,在跨站點的情況下,從第三方站點鏈接打開和從第三方站點提交 Get方式的表單這兩種方式都會攜帶Cookie。但如果在第三方站點中使用POST方法或者通過 img、Iframe等標簽加載的URL,這些場景都不會攜帶Cookie。
None。任何情況下都會發送 Cookie數據
我們可以根據實際情況將一些關鍵的Cookie設置 Stirct或者 Lax模式,這樣在跨站點請求的時候,這些關鍵的Cookie就不會被發送到服務器,從而使得CSRF攻擊失敗。
驗證請求的來源點
由于CSRF攻擊大多來自第三方站點,可以在服務器端驗證請求來源的站點,禁止第三方站點的請求。
可以通過HTTP請求頭中的 Referer和Origin屬性。
HTTP請求頭
但是這種 Referer和Origin屬性是可以被偽造的,碰上黑客高手,這種判斷就是不安全的了。
CSRF Token
最開始瀏覽器向服務器發起請求時,服務器生成一個CSRF Token。CSRF Token其實就是服務器生成的字符串,然后將該字符串種植到返回的頁面中(可以通過Cookie)
瀏覽器之后再發起請求的時候,需要帶上頁面中的 CSRF Token(在request中要帶上之前獲取到的Token,比如 x-csrf-token:xxxx), 然后服務器會驗證該Token是否合法。第三方網站發出去的請求是無法獲取到 CSRF Token的值的。
其他知識點補充
1. 第三方cookie
Cookie是種在服務端的域名下的,比如客戶端域名是 a.com,服務端的域名是 b.com, Cookie是種在 b.com域名下的,在 Chrome的 Application下是看到的是 a.com下面的Cookie,是沒有的,之后,在a.com下發送b.com的接口請求會自動帶上Cookie(因為Cookie是種在b.com下的)
2. 簡單請求和復雜請求
復雜請求需要處理option請求。
之前寫過一篇特別詳細的文章 CORS原理及@koa/cors源碼解析,有空可以看一下。
3. Fetch的 credentials 參數
如果沒有配置credential 這個參數,fetch是不會發送Cookie的
credential的參數如下
include:不論是不是跨域的請求,總是發送請求資源域在本地的Cookies、HTTP Basic anthentication等驗證信息
same-origin:只有當URL與響應腳本同源才發送 cookies、 HTTP Basic authentication 等驗證信息
omit: 從不發送cookies.
平常寫一些簡單的例子,從很多細節問題上也能補充自己的一些知識盲點。
當下“兩微一抖”已成為企業品牌傳播必備的渠道策略,可見抖音的江湖地位不容小覷。
抖音,誕生于2016年9月,是一款“音樂創意”短視頻社交軟件。截至2020年1月,其日活躍用戶數突破4億,而同屬短視頻行業的快手的日活躍用戶數也才剛過3億。要知道,在2016年6月,當時抖音還沒上線,快手就坐擁3億注冊用戶數。顯而易見,抖音不過是一個后起之秀,卻能傲視群雄。
短視頻行業的數據統計
也有數據表明,抖音用戶的粘性很大,轉去玩快手的用戶只有55.6%,反而快手用戶轉去玩抖音的高達68.2%??焓指呒壐笨偛民R宏斌也曾透露,快手前100名的大V有70個是抖音用戶,抖音前100名的大V只有50個是快手用戶。
一般認為,一個人80%的成功幾率是由冰山模型水平面以下要素:動機、價值觀/性格和綜合能力決定的,另外20%是由冰山模型水平面以上要素:經驗和技能決定的。
而且,上層要素的生長受到底層要素的制約:一個人有什么樣的動機,就會促使他/她著重培養哪方面的綜合能力;有什么樣的價值觀/性格,對他/她的行為舉止也會產生一定約束力,什么該做,什么不該做。
互聯網產品的冰山模型
如果把一家互聯網公司的品牌視為一個人,那么動機就是品牌價值,價值觀/性格就是品牌人格,綜合能力就是營銷4C中的用戶需求(Customer)和學習成本(Cost),經驗和技能就是營銷4C中的消除障礙(Convenience)和用戶運營(Communication)。
抖音的成功可從冰山模型自下而上進行總結:
從最初的品牌Slogan——讓崇拜從這里開始,以及有一頭條的員工在朋友圈曬出公司的背景墻——讓更多人的美好生活被看見,可以看出抖音成立的動機——幫助更多人提升個人知名度。
字節跳動的公司背景墻
這個動機是基于對社會沖突的深刻洞察:近年來各類選秀節目(如:中國好聲音、超級女聲等)的熱播透露出許多年輕人心藏一個明星夢,但由于家庭出身、人際資源、社會財富等先天條件限制不得不放棄追逐夢想。城市已經發展到需要一個品牌來給這批年輕人創造機會的時候,抖音應運而生。
作為一個創造者,抖音還找到了提升個人知名度應該具備的人格特質——精致(Sophistication),并以這樣的人格特質與城市年輕群體溝通,包括借助明星光環來詮釋何為精致、擲重金在各大城市舉辦美好生活節……這些事件在目標群體里迅速傳開,并取得目標人群的情感認同和品牌信任。
在抖音里明星云集
注:精致(Sophistication)、稱職(Competence)、刺激(Excitement)、堅韌(Ruggedness)和真誠(Sincerity)是J.Aaker通過問卷調查得出的五大最容易被消費者感知的人格特質,而且這五大特質也能概括許多流行品牌在消費者心目中的人格形象。
在站內,抖音致力于幫助更多人提升個人知名度,加強產品功能和用戶體驗,讓更多人得到同等機會展示自我。
在用戶需求(Customer)方面,采用了去中心化內容分發機制,當內容創作者發布一條短視頻后,抖音并不是把這條短視頻發給創作者的所有粉絲用戶,而是小范圍分發測試這條短視頻的潛力,讓可能喜歡這類內容的標簽用戶通過行動來評價。只有獲得更多用戶認可的優質短視頻才能得到二次流量推薦,進而成為爆款,創作者就有機會爆紅。同時,采用了全屏自動播放模式,讓每一條短視頻都能被觀看,不失為一種公平競爭。
抖音的產品功能
在學習成本(Cost)方面,采用了中心化內容生產機制,組建了服務達人的MCN機構,由平臺簽約明星、網紅和大V推出一個個可復制、易模仿的有趣短視頻,降低用戶學習門檻。同時,運營團隊也會不定期發起各類短視頻挑戰賽,提供各種濾鏡、剪輯技能、流行音樂等素材,即使普通用戶沒有創新力,跟風模仿也能拍出看起來酷炫的、專屬于自己的15秒“大片”。
在站外,抖音秉承“精致”理念開展一系列PUGC營銷活動,持續影響用戶的感知,并不斷強化精致的人格特質。
抖音冠名綜藝節目
在消除障礙(Convenience)方面,抖音通過冠名一些和明星互動的綜藝節目,比如中國有嘻哈、快樂大本營、天天向上等,借助節目酷炫的舞臺效果和高度吻合的受眾群體,消除年用戶從產品感知到下載應用之間存在的疑慮——抖音是否有實力助我提升知名度?事實上,抖音已經幫助不少草根明星(如劉宇寧、陸超等)登上主流媒體的舞臺,讓用戶看到了展示自我的機會、成為明星的希望。
在用戶運營(Communication)方面,除了大眾明星入駐,引入其他平臺的網紅和大V,還與多家MCN機構合作,扶持和培養平臺內的潛力用戶,保證內容的質量和持續產出。同時,不定期更新挑戰活動、熱門搜索、加強品牌合作激勵用戶創作,以增強用戶對抖音的依賴和活躍。在參與創作的過程中,用戶也能得到抖音提供的專群維護、不定時禮物、拍攝指導、流量曝光等多方面幫助。
抖音品牌活動及福利
寫到這里,你可能會驚嘆一聲:哦,原來如此!
但是換到自己來做品牌、運營產品,你就會一臉懵逼了。要么無從下手,要么覺得每天的工作就是打雜,不知道做這些運營動作意義何在。你制定的運營計劃也可能是雜亂無章,運營策略多半也是效仿其他大品牌,在各個產品生命周期階段沒有什么不同。
產品運營人的苦惱
產品運營的策略制定
所以,我們應該從抖音身上學如何“做正確的事“,并且”正確地做事“——把產品當作對社會有貢獻的人來培養,尋找能讓它快速被社會接納的人格特質(做正確的事),然后在不同的成長階段制定相應的歷練計劃,并長期堅持做事風格的一致性,而不是一天一個花樣模糊了用戶對品牌的印象(正確地做事)。
行業生命周期曲線
探索期:市場供給量和需求量低,增長速度只有3%左右。只有一部分有研發實力的企業在生產產品,因為試錯率高、生產成本高,使得企業不敢做出太多花樣,產品售價普遍也高。對于消費者來說,老產品的轉換成本高,新產品的功能還不穩定,普遍是觀望態度。
成長期:市場供給量和需求量增加,增長速度達到10%以上。這時候產品技術已取得突破性進展,可大規模復制生產,部分產品還是供不應求。隨著進入市場的企業數量增多,企業的競爭就是價格戰(紅包、補貼)和產品多元化,助長了市場搶購風氣,強化了消費者對價格的敏感性。
成熟期:市場供給量和需求量接近峰值,增長速度在5%以下。企業占有的市場份額越多,其產品銷路越寬賣得越好。企業之間的競爭從瓜分空白市場變成了搶占對手的市場,營銷手段多了起來,導致消費者出現了選擇困難癥,這時企業的口碑開始發揮了作用。
衰退期:市場供給量和需求量下降,增長速度在0%以下。消費者的消費觀念改變了,市場出現了供過于求的現象,而替代品日益增多又帶來沖擊,企業開始大批量清理庫存,行業標桿在現有的口碑基礎上向新產業轉型,小工廠則因資金不足被迫退出市場。
從行業的發展規律來看,只有產品的企業會因行業變化而被淘汰,但是有好口碑的企業卻不會,因為它可以引導用戶消費來應對市場挑戰。
新媒體時代下的需求
首先,根據行業供給和需求特點細分出公司產品的目標用戶群,描繪能給產品創造最大價值的核心人群畫像,然后洞察他們在社交生活中未被得到滿足的需求:在新媒體時代,每個人都想成為“主角”,年輕人更急于尋求個性表達,但自拍效果不夠酷,也沒有太多技能加持。
其次,了解標桿/競爭對手又是如何解決這些現實沖突,對目標用戶群說了什么。作為抖音的頭號競爭對手——快手,以“記錄世界,記錄你”作為Slogan,具有很濃的個人主義色彩,乍一看以為是幫助感情受到挫折的人走出傷痛,學會自我療愈。
最后,順勢而為,不違背或超前于行業發展,將核心人群的需求痛點與公司產品利益點建立連接,從事實主張、認知區隔、情感主張和價值區隔中挑選適合當下的一種廣告內容。
不同生命階段的產品廣告內容
事實主張:根據品牌內部能力或資源的明顯優勢進行突出定位,傳達所具備的事實優勢壁壘,直擊重點。如:早期的抖音——讓崇拜從這里開始。
適用評估:企業在行業中具有先發優勢,在組織資源方面具有強大的科研能力、上市速度快,在產品/服務方面是行業唯一,不容易被模仿/超越。
認知區隔:在受眾心中建立獨特記憶認知,讓受眾對于該品牌和其他某種特定行為或體驗產生強關聯,從而做出選擇。如:現在的抖音——記錄美好生活。
適用評估:行業中出現了新的關鍵驅動因素,消費者心智中還有其他尚未被占領的空間。
情感主張:提煉品牌中蘊含的情感因素,通過向受眾傳遞美好的情感聯想,讓受眾將心中期望憧憬與品牌實現關聯。如:格力——讓世界愛上中國造。
適用評估:除功能性需求外,目標用戶群還有情感性需求未被滿足。相比競爭對手,產品包裝設計、信息傳播更能觸動用戶情感,公司愿景能夠引發消費者共鳴。
價值區隔:傳達品牌對于受眾產生的某種獨特價值觀,且能通過產品體現在受眾心中形成心理認同,產生價值共鳴。如:耐克——Just do it。
適用評估:已經積累了一定數量的用戶群,公司愿景切換用戶心理并引領時代,取得了行業KOL、大V等背書。
品牌人格原型及特質
將創始人/團隊個性特征和公司產品的發展愿景兩方面因素綜合匹配,找到適合公司長遠發展的人格原型。
不同的品牌塑造如同人的成長,不同的基因決定了不同的人格,不同的人格決定了不同的傳播調性,不同的傳播調性又決定了受眾對品牌的不同反應。有生命力的長壽品牌是具有人格原型的。一個成功的品牌人格,可以讓品牌拉近與用戶的情感距離,并且在用戶心智刻畫出鮮明難忘的品牌形象。
所以,確立相應的品牌人格后,找出核心人群最可能聽具有什么樣人格特質的人說的話,然后學習這類人的表達方式、行動風格等,以此作為產品運營的人設模板并長期堅持不斷強化。
產品生命周期曲線
產品所處的生命周期階段一般可從以下幾個方面進行判斷:
探索期:日下載量/銷量極低,市場占有率幾乎為0。該階段主要任務就是尋找高質量的種子用戶,讓用戶參與產品體驗,并對產品提出優化建議。種子用戶一般控制在100個左右,多了會交流不過來,嚴重地會導致分不清主次需求。這些種子用戶可以是有一定圈子影響力的同事或朋友,與產品沾邊的相關領域意見領袖(KOL),微信(群)、微博、垂直論壇等網絡大V。那么如何讓對方搭理我們愿意成為種子用戶?我們得事先準備一個鼓舞人心的品牌故事,愿景是致力于解決某一個非常緊迫的社會難題,現正尋找一群志同道合的人一起攻克。
成長期:日下載量/銷量、 日/月活量、營收增多而且增速較快,但與頭部競品的市場占有率相比還有很大差距。該階段的主要任務就是激發用戶尚未意識到或未被滿足的需求,一旦使用產品后能夠給生活帶來不一樣的感覺。例如,有些年輕人喜歡自拍模仿明星,但鮮有人圍觀,最終淪為自娛自樂。于是抖音推出一條“找呀找呀找愛豆”競猜H5,一下子刷爆了朋友圈,為模仿者,也為抖音帶來了巨大的流量關注。在那之后,抖音開始找一些明星合作增加產品流量和話題,讓更多的用戶也加入到抖音隊伍中來。
讓抖音一炮而紅的宣傳
成熟期:日下載量/銷量基本穩定,已占據了一定市場規模,但日/月活量、營收仍在增加。該階段主要任務就是對用戶進行精細化運營,圍繞用戶的地域、人口特征、消費品類、品牌偏好,購物行為、生活場景和用戶價值七大維度進行用戶分群貼標簽,然后收集用戶貢獻值,如活躍度、購買力情況等數據將用戶按二八原則分為不同等級:1%為重要客戶,19%為主要客戶,30%為普通客戶,50%為長尾客戶,讓不同等級用戶享受到不同的專屬服務,培養用戶的忠誠度和提高客單價。
衰退期:日下載量/銷量、日/月活量、營收均出現明顯下滑。該階段主要任務就是牢牢把握住核心用戶,通過發起有影響力的社會公益活動擴大品牌影響力的邊界,開發出和產品/服務強相關的常用周邊產品,滿足用戶的新需求。同時關注行業的潛在進入者動態,通過升維或降維的方式嘗試創新轉型,升維是指增加自身的實力,給同質化的產品或服務附加超值的東西;降維是指用高級、全套的打法平移到相對落后的空間,如車企推出網約車服務。
人格是貫穿始終的唯一宗旨,包括內容定位、行文風格、推送時間等在長時間內都是保持不變,讓內容集成為一個有人格特質、高辨識度的符號。
內容運營的基本流程
首先,打好內容定位的核心基礎,分析用戶TA是誰、在哪、每天在做什么、有什么偏好等等,制作出不同于競品的差異化內容,如圖文、視頻等類型上的差異,漫畫、直播、彈幕等形式上的差異,獵奇、愉悅、慰藉等需求上的差異,形成產品特有的內容亮點。內容亮點是熱點和特點的集合,熱點源于長期不斷積累的素材庫、百度熱搜和微博熱搜,特點則是產品特性和品牌人格的總結。
其次,建立內容生產的素材庫和人才庫。素材庫的建立主要通過以下三種方式,一是對時事保持敏感,搜集和整理每天的新聞熱點;二是對輿論保持敏感,通過百度搜索風云榜、微博熱搜實時榜等網絡渠道了解當下的熱點;三是保持的運營思維,收藏和分析最近的優秀內容案例。
人才庫的建立通常是先尋找和記錄的優質內容創作者,包括個人和機構,然后聯系和維護這些優質內容創作者,平時也多加留意熱點文章的作者,隨時更新和完善優質內容創作者的信息。
當然人才的關系維護離不開一整套合理的合作機制:找到能夠幫我們傳達信息和組織其他用戶的超級用戶后,要用產品資源對其進行包裝和曝光,如:經紀團隊挖出楊超越“村花”這個事件點后,通過節目再不斷發酵出各種新的事件。然后再找更多的超級用戶,復制這一模式,不斷重復循環操作。也可以從不同角度對內容生產者設置不同的獎項,如:微博之夜,由官方定小獎,用戶投票定大獎,讓80%的用戶參與評選,最終給20%的用戶頒獎。
再次,有組織地進行內容加工和包裝。常見的高級包裝有專題和專欄兩種:專題是特定事件的內容集合(世界杯專題)、特定時間的內容集合(6月原創歌曲排行榜)、特定場景的內容集合(抖in City 美好生活節)。專欄是特定主題的連續內容(今日說法)、特定領域的連續內容(舌尖上的中國)、特定形式的連續內容(焦點訪談)。
一般加工流程是根據內容的定位確定要生產的內容主題,搭建最終想要呈現的內容框架,用最酷或最土的方式擊中用戶。再從素材庫中挑選出有用的元素,撰寫一個優秀的內容策劃方案,在特定的時間推一些能引起目標用戶情感共鳴的話題。也可以把內容框架和素材給到人才庫的創作者,將他們生產的內容包裝成一個欄目。又或者把所有具備共性的內容抽離出來,提煉出一個主題,這個主題是沒有立場之分,能夠引起正反方辯論的。
最后,在用戶可觸達的渠道發布內容。試想在0預算的情況下,借勢熱點利用產品功能以不同的方式(Push、Banner、客服等)內推給用戶,或者在免費平臺(微信微博)建立基礎的外推渠道。也可以在一條或多頭內容上加入玩法(如:評論、直播、彈幕、測試等),引導用戶進行互動傳播。為了能讓用戶有意愿參與,可按用戶分層(人群、年齡、工作標簽、用戶等級等)、內容分層(超級內容、頭部內容、信息流內容)、時間場景(上班、周末、休假等)、空間場景(辦公、餐廳、酒店等)進行內容精準推薦。個人建議,任何欄目或版塊都應有C位內容,把它們放在最顯眼的位置。
根據活動舉辦的周期性,活動類型可分為常規活動、原創活動和節點活動三類。
常規活動,主要包括各種節假日活動、用戶共知的節日活動,比如世界杯、NBA總決賽、奧斯卡頒獎等;
原創活動,主要是一些品牌積累到了一定用戶基礎并有廣泛的品牌認知,結合產品本身特點打造的專屬性活動,比如天貓的雙十一,京東的618等;
節點活動,主要是針對產品生命周期的用戶特點開展的重大運營活動,比如為了需求喚醒、信息互動在拉新階段開展的【新人紅包】,為了培養使用習慣、購買升級在促活階段開展的【每日簽到】,為了增強購買升級、精細服務在轉化階段開展的【首單立減】,為了建立用戶口碑在傳播階段開展的【分享有禮】。
節點活動目的及手段
每一個活動必須要有一個目標,活動運營的目的無非就是提升粘性、加快用戶轉化、增強消費決策、提升用戶復購和擴大活動傳播五個要點。
活動策劃的目的及手段
提升用戶粘性,實現該目標的關鍵點(或者說如何讓用戶養成使用產品的習慣)是讓用戶在產品上消耗時間,有時間投入,或對產品價值有一定預期,常用的手段就是每日簽到各種任務;
加快用戶轉化,實現該目標的關鍵點(或者說如何讓用戶產生付費的意愿)是突出產品在某一方面的實用價值,常用的手段是紅包/優惠券,新人特權;
增強消費決策,實現該目標的關鍵點(或者說如何催促用戶盡快下單)是通過價格沖擊和優惠策略刺激用戶消費,常用的手段是限時秒殺,特殊優惠;
提升用戶復購,實現該目標的關鍵點(或者說如何讓用戶多次購買,買的更多)是為用戶提供信息篩選或新奇有趣的內容,常用的手段是內容/商品精選,特定補貼;
擴大活動傳播,實現該目標的關鍵點(或者說如何讓用戶主動分享產品信息)是賦予產品更多的內容娛樂價值和社交價值,常用的手段是轉發/邀請有獎,測試小游戲。
本著與用戶交朋友的心態服務用戶,循序漸進為用戶謀福利,讓用戶感知到并不斷強化品牌的人格特質。
用戶運營的基本流程
拉新:利用高頻使用、剛需、大眾化需求等流量爆品(如公交刷卡、比價搜索等),以豐富內容形式(如直播、短視頻、吉祥物IP等)來吸引用戶的策略,進行搜索引擎、新媒體平臺、應用商店等線上宣傳,或掃樓發傳單、贈品合作、地面廣告等線下硬廣。但是宣傳渠道并不是越多越好,事前需要考慮到關于流量效率的問題并做好渠道調研,是不是精準流量來的用戶,拉新渠道不精準,獲客成本高,用戶質量差。
在宣傳過程中,適當使用折扣、抽獎、代金券、紅包、限時限量購等營銷工具,或者團購、預售/眾籌、邀請返利、二級分銷等社群裂變方法。值得注意的是每一款產品都應該有一個核心的拉新手段/方式,東打一棒西揍一拳,如此從各個渠道得到的流量不會太多。所以建立快速穩定的熱點擴散渠道是有必要的,推薦一種方法:強控公司員工朋友圈,然后維護核心種子用戶群,再利用雙微一抖等第三方管理工具進行效果檢測,最后對每個熱點創意進行總結、復盤和優化。
盤活:從用戶接觸產品后需要有一個引導文案或指導規范,突出產品核心功能、利益點,讓用戶知道這款產品是干嘛用的,對TA有什么好處,給他什么東西希望TA留下來。常見的引導方案就是設計豐富的新手特權,引導用戶嘗試不同類型資產投資,比如完成簽到、轉發、評論、收藏等各種任務就可以獲得各種獎勵。用戶能夠完整地使用或操作產品全流程,才算是一個有效的、相對穩定的新用戶。如果產品服務體驗不精準/認知度差,新用戶容易流失、產品口碑差。
防流失:一次給用戶發多張券,分為不同的面額和門檻,引導用戶完成5次以上消費,同時設置連續購買多單就給獎勵,縮短付費時間間隔,加速用戶生命周期的進化。還可以搭建常態化的活動體系,在固定的時間點(如:每天10點,每月8日等)開展每日秒殺、限時搶購、會員日等的促銷活動,讓用戶形成具備記憶點的活動預期。
對于一個已經流失的用戶,可以通過電話召回(重點用戶)、短信/App消息、品牌宣傳(老用戶)、大型促銷活動等手段召回,為他們提供限時特權、紅包/抵用券、新品發布/重大改版、重點促銷商品等新體驗。為什么我們寧愿花時間去召回一個老用戶而不是尋找新用戶,因為召回一個老用戶帶來的效益要遠大于一個新用戶的獲取成本,老用戶已經熟悉我們的產品,不需要太多的教育成本,而且能夠直接帶來效益的。當然并不是真的等到用戶已經流失才會采取行動,我們可以通過RFM模型監控用戶活躍情況,對潛在的流失用戶進行預警,并且采取相應的挽留措施。
強粘性:建立用戶成長體系,比如用戶積分體系、積分商城,用戶等級體系、會員體系、勛章、證書等。用戶成長體系是一整套驅動用戶成長的運營機制,是在用戶數據模型的基礎上,比如交易類產品的累計交易金額、交易頻次、交易類型、交易質量等,內容類產品的用戶參與度、內容貢獻度、用戶活躍度等,工具類產品的用戶活躍度、用戶ARPU值、用戶訪問時長等,找到用戶成長的關鍵路徑和核心驅動力,從而搭建用戶成長的激勵通道和連接用戶行為的觸達通道。
促轉化:常見的有提高客單價、增加關聯購買和拓展盈利模式三種手段。提高客單價手段再細化就是滿額立減、滿件立減的滿減優惠,加N元送配件/日化用品的加價購,第二件半價的N件N折。制作場景、季節專題或推出產品套裝就是為了增加關聯購買。在拓展盈利模式里,除了線下探索,將產品和服務延展到線下,尋找產品/服務強相關的常用周邊商品,提高品類的豐富度。還可以設置會員專屬服務,不同等級會員享受不同的服務,首次付費有優惠。
在用戶運營工作中最難的是搭建用戶成長體系。當然不建議產品剛上線就做用戶成長體系,當用戶達到一定量級,知道哪些用戶對產品貢獻值最大后再考慮要不要做、怎么做,而且用戶等級不易超過4級。據調查,抖音至今都沒有對外公開(就當不存在)用戶成長體系,用戶也不會知道自己處于哪個等級,有哪些特殊權益。
用戶成長體系搭建的基本流程
首先建模型,可以沿用用戶漏斗模型,提升從訪客、下單到支付每個環節的運營效率,實時關注用戶反饋,看用戶是在哪個點流失的,這個點就是要優化的地方;也可以沿用用戶生命周期模型,判斷用戶處于哪一階段,設定對應的運營規則;還可以沿用用戶價值模型,根據用戶在不同階段對產品的貢獻度,設定對應的運營規則。
其次搭通道,激勵通道主要有秒殺/限時搶購、抽獎、代金券/紅包、特權等級、積分/成長值/經驗值、任務引導;觸達通道主要包括關鍵訪問路徑、短信、郵件、產品通知功能、微信渠道、個性化Push等。
最后促成長,通常采用補貼策略、在成長節點觸發消息推送和抓牢動力引擎三種方式。動力引擎主要包括:內容類產品的高質量、率、內容形態豐富,電商類產品的低價格、高品質、物流快、品類豐富,教育類產品的優秀師資、系統化教學,金融類產品的供給渠道、電商化、游戲化,出行/外賣類產品的供給驅動、匹配、優質服務,工具類產品的解決具體問題的效率。
了解行業發展及競爭對手動態,及時調整品牌的價值主張,加快催熟成長期的產品,或擴大品牌影響力的邊界延長產品的成熟期。無論運營重心怎么變,但品牌人格和人格特質不能改變。
在擴大品牌影響力的邊界方面,除了借助用戶故事都自帶自傳播的特點,對有代表性的用戶故事進行包裝,前提是故事一定要真實,把品牌人格的塑造得更富有真情實感。個人推薦發起社會公益活動,就像螞蟻森林一樣打破虛擬空間,通過線上攢能量種樹活動,再把4億+用戶的這份參與熱情展現到線下——在沙漠種真實的樹苗。
文章來源:人人都是產品經理 作者:炒冷飯的二叔
前言:
什么是產品的信任感??
指:基于產品為用戶提供‘可靠服務、價值依賴’的一種情感體驗。
這種體驗不僅影響著用戶黏性的強弱、業務目標的實現,也影響著不同生命周期下給產品給來的價值。如圖:
而在產品與用戶間建立信任感的過程中,我驚奇地發現有3個因素貫穿始終:理念 > 內容 > 表現。
‘基于什么樣的理念,向用戶傳遞什么內容,并且怎么表現?!?/strong>
所以,未來3篇文章主要圍繞‘信任感的打造’,希望能系統性地認識它,挖掘更多工作上可實用的小技巧。
今天先分享第一篇:信任感的理念層
信任的本質:是讓人覺得真誠、可靠、放心等。換句話說,它就是一種‘為用戶著想,建立產品溫度’的理念/方向,從而引導后續的內容都圍繞該理念而進行。
那么,如何才能為用戶著想,慢慢建立起對產品的信任感呢??
既然為用戶著想,那么可以試著從‘減少用戶的投入成本’切入。
Part1:減少健康投入
健康投入,指用戶使用我們的產品,可能會對身體上帶來直接或潛在的負擔/影響。比如視頻看久了,眼睛就會感到酸痛。
針對這些負擔與影響進行的一些關懷提示,可以用戶提前消除、減輕這些痛苦,拉進與用戶間的距離。
不同產品類型擁有專有的關懷點,所以比較通用的主要有5個:使用時長、使用姿勢、夜間休息、夜間護眼、音量大小。
a.使用時長提示:
除純工具類產品之外,大部分用戶在產品上都有一定的使用時長(尤其是內容消費類產品),對于‘連續使用N分鐘、或者滿足特定時長’的用戶,可針對該時長進行休息提示。
如有道精品課,在用戶觀看課程滿40分鐘 時有個時長提醒:
b.夜深關懷提示:
深夜本身是一個休息的時間,但還是有大量的‘夜貓子’根本停不下來。不管是主動性的娛樂消費,還是被動性的信息/工作處理,都將手機‘進行到底’。
此時對于‘深夜忙碌’中的用戶,夜間的關懷就是一個切入點:比如企業微信,會在深夜啟動頁上展示 ‘夜深了,xxx’的文案提示。
雖然只是簡單的一句話,但還是能感受到鵝廠對員工的關懷。
而且不管是C端還是B端,只要有用戶在深夜使用產品的可能,都可針對性地給予關懷設計,體現產品的‘人性’。
c.夜間護眼提示:
夜晚周圍的光線會變得幽暗,部分手機屏幕會自動變亮。時間一長會嚴重刺激用戶雙眼,并造成視覺疲勞(尤其是小學生群體)。
此時對用戶進行護眼提示,不失為一個用戶關懷點。
再如有道精品課:
d.使用姿勢提示:
我們日常都會將手機橫過來看視頻、看八卦。而且相信各位都有過這樣的經歷:
當手機長期處于某個屏幕狀態+重力傾向時,用戶難免會出現手酸脖痛的情況。
此時進行使用姿勢的糾正提醒,亦能起到關心用戶的效果,從而建立良好的產品印象。
還是以有道精品課為例:
e.音量提示:
這個大伙都知道,過大的音量會影響耳朵聽力。一般出現在各種音頻、視頻的產品與功能中。
Part2:減少金錢投入
沒有人不會在意自己的錢包(除非你是對錢不感興趣...),金錢上的收入與支出很大程度上會影響 人們對某事物的看法。
產品也是如此,若能幫用戶減少金錢上的支出,或者帶來真實收入。不僅能極大提升用戶對你的信任度與黏性,還能增加產品的競爭力。
比如高德地圖的打車功能,能顯示所有車型的價格預覽,幫助用戶選擇所需價格的車型。
無論是商品優惠券,還是返利。
只要能幫助用戶錢包上的‘節源’或‘開流’,都能引發用戶的信任感,從而信賴產品。
Part3:減少情感投入
情感投入,是指用戶基于內心活動和情緒感受,對某事物所表現出來的一種想法。
這種想法制約著 用戶是否接受我們的產品服務。一般體現在:安全性、性價比、真實性 3個方面。
a.安全性 - 放不放心:
人們面對某事物 可能會帶來的傷害/損失時,都會有一種本能的“警惕感”。
就拿此次疫情來說,對于有‘出差住房’訴求的用戶來說,‘住的安全’是重中之重。因此尋找一家‘無感染、每日消毒、衛生干凈’的安全酒店,可以減低用戶選擇我們的警惕感。
而在酒店列表中,帶有‘嚴格消毒’、‘健康守護’等安全標簽的酒店,會給人帶來一種安全、放心、信賴的心理效應,從而提升該酒店的轉化率。
b.性價比 - 值不值得
性價比是人們衡量‘付出成本與回報價值’間的一種決策依據,沒有誰會喜歡付出小于回報的事物。
而為了讓用戶降低這種決策依據,除了自我服務/實力的展示外,往往需要一種“參照物”來凸顯性價比。
如美團上的‘滿減神器’,通過不同的食物/價錢間的對比,讓用戶買到最具性價比的食物。
而“參照物” 的形式多種多樣,不管是競品數據。
老版本也屬于一種競品
還是是各種優惠信息、額外禮物/禮包、售后服務等等。
目的都是通過該參照物,向用戶傳遞一種‘劃算’、‘值得’的心理效應。
c.真實性 - 真不真實:
光是性價比高還只是片面依據,至于內容是否屬實,成為了我們與用戶建立信任感 中最重要的影響因素。因為沒有人喜歡被騙、喜歡虛假事物。
而真實性的建立,在‘電商領域’應該被運用得最多。如大牌背書、證書授權、專家介紹、明星代言、官方保證、銷量成績、用戶反饋...等等。
展示自己的真實、最具實力的一面即可,別過度吹噓與包裝,用戶又不傻。
Part4:減少腦力投入
人們一向不喜歡復雜的東西,除了不易理解外,更擔心因為自己的理解錯誤,會給帶來意外的損失。
幫助用戶減少記憶負荷、順暢完成操作目標,是每個產品必不可少的設計點。
如微信轉賬,輸入數字時會檢測對應的數額,減去用戶邊輸入 邊計算“這是多少錢”的腦力投入。
而且對比支付寶的轉賬,微信這點確實做到了‘洞察用戶需求’。
再如賬號注冊,提供‘剩余步驟’能讓用戶了解 當前處于哪一步、預測完成整個操作還需多久。
Part5:減少體力投入
除了記憶負荷,‘操作負荷’的減少也是一種‘為用戶著想‘的方式。我們身邊也存在太多這方面的例子:
如手機上,如淘寶的快鏈彈窗、支付寶的轉賬提示,都是前置用戶的目標,縮短操作流程。
如電腦上,如Mac會保存耳機音量。
下次插入耳機時,會將揚聲時的音量,自動調整至上次耳機插入時 所記錄的音量。這樣就免去了重新調整音量的操作。
以網易云音樂為例:注意 揚聲時和耳機插入后 的音量變化.
這些都是幫助用戶快速使用,從而減少體力操作的方式。除了前置用戶目標、保存記錄 外,常見的還有:給予默認值、自動選擇/處理、多選與批量等等。
總結:
以上就是關于理念篇的內容,讓今后的內容設計有了明確的方向。下面是走查表,平時設計功能、制作界面的時候可以看看,增加產品的溫度。領取方式:公眾號回復【信任1】
轉自:ui中國-
大綱
結語:作為設計師有時會聽到需求方表述“這種極少出現的情況,我們可以暫且不管它。”但是細節見真章,所有優秀的體驗設計都必須照顧到方方面面的缺省情況。讓每個用戶的流量價值發揮到最大,產生相互信任的良好的品牌關系。這樣的平臺生態是良性的,這樣的產品會更有流量轉化的商業化價值。
轉自:ui中國-騰訊動漫TCD
「小宇宙」是即刻團隊開發的播客App,目前已上架各大應用商店,僅能通過邀請碼使用,導致一時間微博上出現「一碼難求」的情況,那它與其他播客App有什么不同?
進入App即展示「信息流」推薦,每日更新3條播客「信息流」推薦,聽的時間越長,會推薦越多更加精準的播客內容。那用戶如何判斷推薦的單集是否有自己想要聽的內容呢?如下圖所示,頁面上單集信息展示條目的空間較大,在首屏約能展示2條的單集內容,除了基礎的節目封面、節目名稱及單集名稱外,小宇宙巧妙地通過將熱門評論外顯,輔以展示播放量及評論數量,引導用戶點擊進入二級頁面,進而形成轉化。
不同于傳統播客App僅能搜節目名稱,小宇宙還支持搜索單集和用戶,甚至是節目內頁的內容也可以搜索到。對于那些「我對欄目本身不感興趣,只想聽其中的某一集」或者「我關注某個人或某個話題,想聽聽看關于他的一切」的節目,使用單集搜索功能可以更簡單地直達所需。
讓人驚喜的是,在節目內頁長按選中任意詞匯可以呼出「智能搜索」功能,這個智能體現在它允許用戶選擇用magi搜索和用互動百科搜索相關內容。這讓我想到了Mac上一款很好用的工具PopClip(通過對文本內容的擴展來提升操作效率),小宇宙的智能搜索對于邊聽節目邊扒關鍵詞的「考據黨」來說,無疑會大幅提升在聽節目時獲取資訊的效率。
在單集播放界面,除了常規的進度條拖拽、快速后退/前進等功能外,還有一個點贊功能,用戶聽到精彩的內容或引起共鳴的部分可以通過點贊進行互動,從點贊功能的反饋到進度條的高度的升起都能夠進行實時的反饋,同時也可以幫助其他聽眾了解單集內容的關注點,以此為??營造出符合他當時?為和感知的情景,可以達到提高用戶的參與度的目的。
評論頁的輸入框常駐于頁面底部,點擊后輸入框高度延伸,引導用戶評論互動,此時用戶如果是通過單集播放界面進入的評論頁面,還會出現標記時點的選項,勾選后評論內容即帶上了單集內容的時點。而時點高亮色+下劃線的表現形式可以引導用戶聚焦注意力和點擊進行內容的收聽,當其他用戶點擊帶評論中的時點即可直接跳轉至對應時間點播放單集內容,為??之間的互動建?起羈絆,方便用戶間的討論交流,進而提升了點擊率和單集的收聽率。
另外,在用戶輸入評論時,輸入框并不是以模態的形式出現,在用戶評論的過程中依然可以滾動頁面進行交互操作,這樣做的好處在于不打斷用戶操作的連續性。以iPhone X的屏幕高度為例,除去標題欄+評論輸入框+鍵盤高度外,留給評論本身的空間僅有大約1/3左右,在空間有限的情況下,用戶滾動屏幕查閱感興趣的評論或針對性進行回復的行為非常連貫,非模態的處理可以進一步降低用戶互動的門檻。
這里有一個改進小建議,輸入框內的預制文案可以換成引導性更強的內容或由系統自動生成一個場景適合的評論,這樣不需要用戶自己思考寫什么內容,降低用戶評論操作的成本,提高用戶參與度。
小宇宙目前在社交上的嘗試處于用戶友好型狀態,用戶可以查看他人的播客訂閱列表,發現同好,即「相近信息的收集愛好者容易獲得共鳴」。如果這個有共同興趣愛好的人剛好是用戶收聽的主播,就可以很容易拉近聽眾和主播的距離,主播對于聽眾來說不再是手機屏幕后面冷冰冰的聲音,他更像你的一個朋友,你可以去了解他的喜好,討論共同話題,更好地跟他進行互動。
不同的主題表達了不同情感,針對不同的社會群體的設計風格也會有所不同。
回到產品本身,小宇宙根據播客節目封面的主題色來適配不同播客信息頁的視覺風格,營造出適合不同播客風格的氛圍,以此來傳達不同播客節目的特點。
若在熱門的單集下評論,且評論點贊數最高,小宇宙領航員(官方賬號)會給你留言,告知「你的評論上首頁啦!」,通過這種與用戶互動的方式激起??的情感認同,提升用戶評論的積極性。
點擊「加入播放列表」,通過動效形式給予用戶反饋,讓信息的展示更生動自然,為產品增添趣味性的同時,給予用戶更美好的操作體驗。
下拉刷新動效中以「宇宙」圖形為載體,將產品的Logo融入了其中,賦予產品獨有的個性和靈氣,讓用戶切身感受到這是一個可以探索的「宇宙」,而不是一個冰冷的工具,在減少用戶等待過程中引發的負面情緒的同時強化了品牌形象。
產品中使用了較多無文字按鈕,對于初次使用的用戶來說存在一定認知負荷,不知道這些都是什么功能。
存在相同樣式的圖標承載不同功能操作的問題:「播放列表」功能在「我的播客」頁中點擊后是加入播放列表操作,而在播放列表模態彈窗中,點擊后進入播放列表的編輯狀態,同個樣式的按鈕承載了不同功能操作,容易引起用戶困惑,帶來冗余的學習成本。
當你收藏了一篇文章,收藏按鈕狀態從「收藏」變為「已收藏」,點擊「已收藏」可以取消收藏狀態,這是用戶對于兩個狀態切換已有的認知。而小宇宙的加入播放列表功能,在點擊加入播放列表后,再次點擊「播放列表」按鈕,會提示「已在播放列表」,而不是通常認知中的「取消加入播放列表」,容易帶來認知錯誤。
列表右側的「已訂閱」按鈕,視覺感知上像是不可點的狀態,點擊后提示「已取消」,再次點擊提示「訂閱成功」。對于有明顯狀態變化的功能性操作可以省去toast,這里通過「已訂閱」和「訂閱」狀態的按鈕樣式變化已經給予用戶清晰的傳達反饋,再次提示反而多余。
小宇宙的體驗分析就到這里,總的來說雖然存在一些需要優化的細節(畢竟還處于邀請碼體驗階段),但是設計師可以學習的設計亮點有很多,感興趣的小伙伴可以下載體驗一番,也歡迎大家一起留言交流。
藍藍設計的小編 http://www.syprn.cn