service worker

最終目的是為了實(shí)現(xiàn)消息推送到用戶設(shè)備并可以顯示更新通知。

實(shí)施推送的三個(gè)關(guān)鍵步驟是:

  • 添加客戶端邏輯以訂閱用戶推送(即Web應(yīng)用程序中注冊用戶以推送消息的JavaScript和UI)。
  • 來自后端/應(yīng)用程序的API調(diào)用螃壤,觸發(fā)推送消息到用戶的設(shè)備迅矛。
  • 服務(wù)工作者JavaScript文件,當(dāng)推送到達(dá)設(shè)備時(shí)將收到“推送事件”。在這個(gè)JavaScript中犬绒,您將能夠顯示通知檩禾。

第一步是“訂閱”用戶推送消息挂签。

訂閱用戶需要兩件事。

  • 首先盼产,獲得用戶的許可以向他們發(fā)送推送消息饵婆。
  • 第二,然后PushSubscription從瀏覽器中獲取戏售。

PushSubscription包含向該用戶發(fā)送推送消息所需的所有信息侨核。您可以“將”視為該用戶設(shè)備的ID。

特征檢測

首先灌灾,我們需要檢查當(dāng)前瀏覽器是否實(shí)際支持推送消息搓译。我們可以通過兩個(gè)簡單的檢查來檢查是否支持推送。

  • 在導(dǎo)航器上檢查serviceWorker紧卒。
  • 檢查PushManager的窗口侥衬。
if (!('serviceWorker' in navigator)) {
  // Service Worker isn't supported on this browser, disable or hide UI.
  return;
}

if (!('PushManager' in window)) {
  // Push isn't supported on this browser, disable or hide UI.
  return;
}

注冊服務(wù)工作者

當(dāng)我們注冊服務(wù)工作者時(shí),我們告訴瀏覽器我們的服務(wù)工作者文件在哪里跑芳。該文件仍然只是JavaScript轴总,但瀏覽器將“授予它訪問”服務(wù)工作者API,包括推送博个。
更確切地說怀樟,瀏覽器在服務(wù)工作者環(huán)境中運(yùn)行該文件。
要注冊服務(wù)工作者盆佣,請調(diào)用navigator.serviceWorker.register()往堡,將路徑傳遞給我們的文件

function registerServiceWorker() {
  return navigator.serviceWorker.register('service-worker.js')
  .then(function(registration) {
    console.log('Service worker successfully registered.');
    return registration;
  })
  .catch(function(err) {
    console.error('Unable to register service worker.', err);
  });
}
  • 下載服務(wù)工作文件械荷。
  • 運(yùn)行JavaScript。
  • 如果一切正常并且沒有錯(cuò)誤虑灰,則返回的承諾register() 將解決吨瞎。如果有任何類型的錯(cuò)誤,承諾將拒絕穆咐。

register()時(shí)候颤诀,它返回一個(gè)ServiceWorkerRegistration。我們將使用此來訪問PushManager API对湃。

請求許可

我們已經(jīng)注冊了我們的服務(wù)工作者并準(zhǔn)備訂閱用戶崖叫,下一步是獲得用戶的許可以向他們發(fā)送推送消息。

獲取權(quán)限的API相對(duì)簡單拍柒,缺點(diǎn)是API 最近從回調(diào)變?yōu)榉祷豍romise心傀。問題在于,我們無法分辨當(dāng)前瀏覽器實(shí)現(xiàn)的API版本拆讯,因此您必須實(shí)現(xiàn)這兩個(gè)版本并同時(shí)處理這兩個(gè)版本脂男。

function askPermission() {
  return new Promise(function(resolve, reject) {
    const permissionResult = Notification.requestPermission(function(result) {
      resolve(result);
    });

    if (permissionResult) {
      permissionResult.then(resolve, reject);
    }
  })
  .then(function(permissionResult) {
    if (permissionResult !== 'granted') {
      throw new Error('We weren\'t granted permission.');
    }
  });
}
image.png

Notification.requestPermission()。此方法將向用戶顯示提示:


image.png

一旦許可被接受/允許种呐,關(guān)閉(即點(diǎn)擊彈出窗口上的十字架)或被阻止疆液,我們將以字符串形式給出結(jié)果:'授權(quán)','默認(rèn)'或'拒絕'陕贮。(granted, denied, or default.)

在上面的示例代碼中堕油,askPermission()如果授予了權(quán)限,則通過解析返回的promise 肮之,否則我們會(huì)拋出一個(gè)錯(cuò)誤掉缺,使得promise被拒絕。

您需要處理的一個(gè)邊緣情況是用戶單擊“阻止”按鈕戈擒。如果發(fā)生這種情況眶明,您的網(wǎng)絡(luò)應(yīng)用將無法再次要求用戶獲得許可。他們必須通過更改其權(quán)限狀態(tài)來手動(dòng)“取消阻止”您的應(yīng)用筐高,該權(quán)限狀態(tài)隱藏在設(shè)置面板中搜囱。仔細(xì)考慮如何以及何時(shí)向用戶請求許可,因?yàn)槿绻麄凕c(diǎn)擊阻止柑土,則不是一種簡單的方法來反轉(zhuǎn)該決定蜀肘。

好消息是,大多數(shù)用戶都樂于給予許可稽屏,只要他們知道為什么要求許可扮宠。

使用PushManager訂閱用戶

function subscribeUserToPush() {
  return navigator.serviceWorker.register('service-worker.js')
  .then(function(registration) {
    const subscribeOptions = {
      userVisibleOnly: true,
      applicationServerKey: urlBase64ToUint8Array(
        'BEl62iUYgUivxIkv69yViEuiBIa-Ib9-SkvMeAtA3LFgDzkrxZJjSgSnfckjBJuBkr3qBUYIHBQFLXYp5Nksh8U'
      )
    };

    return registration.pushManager.subscribe(subscribeOptions);
  })
  .then(function(pushSubscription) {
    console.log('Received PushSubscription: ', JSON.stringify(pushSubscription));
    return pushSubscription;
  });
}
  • 您的Web應(yīng)用程序已加載到瀏覽器中,您可以調(diào)用subscribe()狐榔,傳入公共應(yīng)用程序服務(wù)器密鑰坛增。
  • 然后获雕,瀏覽器向推送服務(wù)發(fā)出網(wǎng)絡(luò)請求,推送服務(wù)將生成端點(diǎn)收捣,將此端點(diǎn)與應(yīng)用程序公鑰相關(guān)聯(lián)届案,并將端點(diǎn)返回到瀏覽器。
  • 瀏覽器會(huì)將此端點(diǎn)添加到PushSubscription,通過subscribe()promise 返回的 端點(diǎn)罢艾。
    在調(diào)用subscribe()方法時(shí)萝玷,我們傳入一個(gè)options對(duì)象,它包含必需參數(shù)和可選參數(shù)昆婿。

當(dāng)您以后想要發(fā)送推送消息時(shí),您需要?jiǎng)?chuàng)建一個(gè)Authorization標(biāo)頭蜓斧,其中包含使用您的應(yīng)用程序服務(wù)器的私鑰簽名的信息仓蛆。當(dāng)推送服務(wù)接收到發(fā)送推送消息的請求時(shí),它可以通過查找鏈接到接收請求的端點(diǎn)的公鑰來驗(yàn)證該簽名的授權(quán)報(bào)頭挎春。如果簽名有效看疙,則推送服務(wù)知道它必須來自具有匹配私鑰的應(yīng)用服務(wù)器 。它基本上是一種安全措施直奋,可以防止其他人向應(yīng)用程序的用戶發(fā)送消息能庆。


image.png

PushSubscription對(duì)象包含向該用戶發(fā)送推送消息所需的所有必需信息

{
  "endpoint": "https://some.pushservice.com/something-unique",
  "keys": {
    "p256dh":
"BIPUL12DLfytvTajnryr2PRdAgXS3HGKiLqndGcJGabyhHheJYlNGCeXl1dn18gSJ1WAkAPIxr4gK0_dQds4yiI=",
    "auth":"FPssNDTKnInHVndSTdbKFw=="
  }
}

這endpoint是推送服務(wù)URL。要觸發(fā)推送消息脚线,請對(duì)此URL發(fā)出POST請求搁胆。

該keys對(duì)象包含用于加密通過推送消息發(fā)送的消息數(shù)據(jù)的值

userVisibleOnly選項(xiàng)

當(dāng)推送首次添加到瀏覽器時(shí),開發(fā)人員是否應(yīng)該能夠發(fā)送推送消息而不顯示通知存在不確定性邮绿。這通常被稱為靜默推送渠旁,因?yàn)橛脩舨恢涝诤笈_(tái)發(fā)生了某些事情。

令人擔(dān)憂的是船逮,開發(fā)人員可能會(huì)做一些討厭的事情顾腊,例如在用戶不知情的情況下持續(xù)跟蹤用戶的位置。

為了避免這種情況并讓規(guī)范作者有時(shí)間考慮如何最好地支持此功能挖胃,userVisibleOnly添加了該選項(xiàng)并且傳入值true是與瀏覽器的符號(hào)協(xié)議杂靶,每次收到推送時(shí)Web應(yīng)用程序都會(huì)顯示通知(即沒有沉默的推動(dòng))。

目前你必須傳入一個(gè)值true酱鸭。如果您不包含 userVisibleOnly密鑰或傳入吗垮,false您將收到以下錯(cuò)誤:

Chrome目前僅支持用于訂閱的Push API,這將導(dǎo)致用戶可見的消息凹髓。你可以通過調(diào)用pushManager.subscribe({userVisibleOnly: true})來表明這一點(diǎn) 抱既。有關(guān)詳細(xì)信息,請參閱https://goo.gl/yqv4Q4扁誓。

applicationServerKey選項(xiàng)

推送服務(wù)使用“應(yīng)用服務(wù)器密鑰”來標(biāo)識(shí)訂閱用戶的應(yīng)用程序防泵,并確保相同的應(yīng)用程序正在向該用戶發(fā)送消息蚀之。

應(yīng)用程序服務(wù)器密鑰是公鑰和私鑰對(duì),對(duì)您的應(yīng)用程序而言是唯一的捷泞。私鑰應(yīng)該對(duì)您的應(yīng)用程序保密足删,公鑰可以自由共享。

applicationServerKey傳遞給subscribe()調(diào)用的選項(xiàng)是應(yīng)用程序的公鑰锁右。在訂閱用戶時(shí)失受,瀏覽器將此傳遞到推送服務(wù),這意味著推送服務(wù)可以將應(yīng)用程序的公鑰綁定到用戶的PushSubscription咏瑟。

您可以通過訪問web-push-codelab.glitch.me來創(chuàng)建公共和私有應(yīng)用程序服務(wù)器密鑰集拂到, 也可以 通過執(zhí)行以下操作使用web-push命令行生成密鑰

$ npm install -g web-push
$ web-push generate-vapid-keys

將訂閱發(fā)送到您的服務(wù)器

const subscriptionObject = {
  endpoint: pushSubscription.endpoint,
  keys: {
    p256dh: pushSubscription.getKeys('p256dh'),
    auth: pushSubscription.getKeys('auth')
  }
};

// The above is the same output as:

const subscriptionObjectToo = JSON.stringify(pushSubscription);

function sendSubscriptionToBackEnd(subscription) {
  return fetch('/api/save-subscription/', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json'
    },
    body: JSON.stringify(subscription)
  })
  .then(function(response) {
    if (!response.ok) {
      throw new Error('Bad status code from server.');
    }

    return response.json();
  })
  .then(function(responseData) {
    if (!(responseData.data && responseData.data.success)) {
      throw new Error('Bad response from server.');
    }
  });
}

節(jié)點(diǎn)服務(wù)器接收此請求并將數(shù)據(jù)保存到數(shù)據(jù)庫以供以后使用。

app.post('/api/save-subscription/', function (req, res) {
  if (!isValidSaveRequest(req, res)) {
    return;
  }

  return saveSubscriptionToDatabase(req.body)
  .then(function(subscriptionId) {
    res.setHeader('Content-Type', 'application/json');
    res.send(JSON.stringify({ data: { success: true } }));
  })
  .catch(function(err) {
    res.status(500);
    res.setHeader('Content-Type', 'application/json');
    res.send(JSON.stringify({
      error: {
        id: 'unable-to-save-subscription',
        message: 'The subscription was received but we were unable to save it to our database.'
      }
    }));
  });
});

使用Web推送庫發(fā)送消息

保存訂閱

使用網(wǎng)絡(luò)推送時(shí)的一個(gè)難點(diǎn)是觸發(fā)推送消息非陈肱ⅲ“繁瑣”兄旬。要觸發(fā)推送消息,應(yīng)用程序需要按照Web推送協(xié)議向推送服務(wù)發(fā)出POST請求余寥。要在所有瀏覽器中使用push领铐,您需要使用VAPID (也稱為應(yīng)用程序服務(wù)器密鑰),這基本上需要設(shè)置一個(gè)標(biāo)頭宋舷,其值證明您的應(yīng)用程序可以向用戶發(fā)送消息绪撵。要使用推送消息發(fā)送數(shù)據(jù),需要對(duì)數(shù)據(jù)進(jìn)行 加密并添加特定標(biāo)頭祝蝠,以便瀏覽器可以正確解密消息音诈。

觸發(fā)推送的主要問題是,如果遇到問題绎狭,很難診斷問題改艇。隨著時(shí)間的推移和瀏覽器的廣泛支持,這種情況正在改善坟岔,但這并不容易谒兄。
我們將使用web-push節(jié)點(diǎn)庫

這個(gè)演示使用nedb存儲(chǔ)訂閱社付,它是一個(gè)簡單的基于文件的數(shù)據(jù)庫承疲,但您可以使用您選擇的任何數(shù)據(jù)庫。我們只使用它鸥咖,因?yàn)樗枰阍O(shè)置燕鸽。對(duì)于生產(chǎn),你想要使用更可靠的東西啼辣。(我傾向于堅(jiān)持使用舊的MySQL啊研。)

function saveSubscriptionToDatabase(subscription) {
  return new Promise(function(resolve, reject) {
    db.insert(subscription, function(err, newDoc) {
      if (err) {
        reject(err);
        return;
      }

      resolve(newDoc._id);
    });
  });
};

發(fā)送推送消息

在發(fā)送推送消息時(shí),我們最終需要一些事件來觸發(fā)向用戶發(fā)送消息的過程。一種常見的方法是創(chuàng)建一個(gè)管理頁面党远,讓您配置并觸發(fā)推送消息削解。但是您可以創(chuàng)建一個(gè)在本地運(yùn)行的程序或任何其他允許訪問PushSubscriptions代碼列表并運(yùn)行代碼以觸發(fā)推送消息的方法。

接下來我們需要web-push為我們的Node服務(wù)器安裝模塊:

npm install web-push --save

然后在我們的Node腳本中沟娱,我們需要在web-push模塊中這樣:

const webpush = require('web-push');

首先氛驮,我們需要告訴web-push模塊我們的應(yīng)用服務(wù)器密鑰

const vapidKeys = {
  publicKey:
'BEl62iUYgUivxIkv69yViEuiBIa-Ib9-SkvMeAtA3LFgDzkrxZJjSgSnfckjBJuBkr3qBUYIHBQFLXYp5Nksh8U',
  privateKey: 'UUxI4O8-FbRouAevSmBQ6o18hgE4nSG3qwvJTfKc-ls'
};

webpush.setVapidDetails(
  'mailto:web-push-book@gauntface.com',
  vapidKeys.publicKey,
  vapidKeys.privateKey
);

我們還包括一個(gè)“mailto:”字符串。此字符串必須是URL或mailto電子郵件地址济似。這條信息實(shí)際上將作為觸發(fā)推送請求的一部分發(fā)送到Web推送服務(wù)矫废。這樣做的原因是,如果網(wǎng)絡(luò)推送服務(wù)需要與發(fā)送者聯(lián)系砰蠢,他們會(huì)有一些信息可以讓他們這樣做蓖扑。
有了這個(gè),web-push模塊就可以使用了台舱,下一步是觸發(fā)推送消息律杠。

該演示使用假裝管理面板來觸發(fā)推送消息。


image.png

單擊“觸發(fā)推送消息”按鈕將發(fā)出POST請求柿赊,/api/trigger-push-msg/ 這是我們后端發(fā)送推送消息的信號(hào),因此我們?yōu)榇硕它c(diǎn)創(chuàng)建快速路由:

app.post('/api/trigger-push-msg/', function (req, res) {

收到此請求后幻枉,我們從數(shù)據(jù)庫中獲取訂閱碰声,對(duì)于每個(gè)訂閱,我們會(huì)觸發(fā)推送消息熬甫。

return getSubscriptionsFromDatabase()
  .then(function(subscriptions) {
    let promiseChain = Promise.resolve();

    for (let i = 0; i < subscriptions.length; i++) {
      const subscription = subscriptions[i];
      promiseChain = promiseChain.then(() => {
        return triggerPushMsg(subscription, dataToSend);
      });
    }

    return promiseChain;
  })

triggerPushMsg()然后胰挑,該函數(shù)可以使用Web推送庫向提供的訂閱發(fā)送消息。

const triggerPushMsg = function(subscription, dataToSend) {
  return webpush.sendNotification(subscription, dataToSend)
  .catch((err) => {
    if (err.statusCode === 410) {
      return deleteSubscriptionFromDatabase(subscription._id);
    } else {
      console.log('Subscription is no longer valid: ', err);
    }
  });
};

webpush.sendNotification()將返回一個(gè)承諾椿肩。如果消息已成功發(fā)送瞻颂,則承諾將解決,我們無需執(zhí)行任何操作郑象。如果承諾拒絕嘶是,您需要檢查錯(cuò)誤巩割,因?yàn)樗鼤?huì)告知您PushSubscription是否仍然有效。

要確定推送服務(wù)的錯(cuò)誤類型,最好查看狀態(tài)代碼矾湃。推送服務(wù)之間的錯(cuò)誤消息不同,有些比其他更有幫助匹耕。

在此示例中烛缔,它檢查狀態(tài)代碼'404'和'410',它們是'Not Found'和'Gone'的HTTP狀態(tài)代碼柜砾。如果我們收到其中一個(gè)湃望,則表示訂閱已過期或不再有效。在這些場景中,我們需要從數(shù)據(jù)庫中刪除訂閱证芭。

處理推送事件

在用戶的設(shè)備上接收此推送消息并顯示通知

收到消息后瞳浦,它將導(dǎo)致在服務(wù)工作者中調(diào)度推送事件。

self.addEventListener('push', function(event) {
  if (event.data) {
    console.log('This push event has data: ', event.data.text());
  } else {
    console.log('This push event has no data.');
  }
});

self通常用于服務(wù)工作者的Web Workers檩帐。self指的是全局范圍术幔,類似于window網(wǎng)頁。但對(duì)于網(wǎng)絡(luò)工作者和服務(wù)工作者來說湃密, self指的是工作者本身诅挑。
self.addEventListener()可以將其視為向服務(wù)工作者本身添加事件偵聽器。

// Returns string
event.data.text()

// Parses data as JSON string and returns an Object
event.data.json()

// Returns blob of data
event.data.blob()

// Returns an arrayBuffer
event.data.arrayBuffer()

服務(wù)工作者需要了解的一點(diǎn)是泛源,您幾乎無法控制服務(wù)工作者代碼何時(shí)運(yùn)行拔妥。瀏覽器決定何時(shí)將其喚醒以及何時(shí)終止它。你可以告訴瀏覽器的唯一方法是“嘿达箍,我忙著做重要的事情”没龙,就是將一個(gè)承諾傳遞給event.waitUntil()方法。有了這個(gè)缎玫,瀏覽器將保持服務(wù)工作者運(yùn)行硬纤,直到您傳入的承諾結(jié)束。
對(duì)于推送事件赃磨,還有一個(gè)額外要求筝家,即您必須在傳遞的承諾結(jié)算之前顯示通知。

self.addEventListener('push', function(event) {
  const promiseChain = self.registration.showNotification('Hello, World.');

  event.waitUntil(promiseChain);
});

調(diào)用self.registration.showNotification()是向用戶顯示通知的方法邻辉,它返回一個(gè)在顯示通知后將解析的承諾溪王。
通過網(wǎng)絡(luò)數(shù)據(jù)請求和使用分析跟蹤推送事件的更復(fù)雜示例可能如下所示:

self.addEventListener('push', function(event) {
  const analyticsPromise = pushReceivedTracking();
  const pushInfoPromise = fetch('/api/get-more-data')
    .then(function(response) {
      return response.json();
    })
    .then(function(response) {
      const title = response.data.userName + ' says...';
      const message = response.data.message;

      return self.registration.showNotification(title, {
        body: message
      });
    });

  const promiseChain = Promise.all([
    analyticsPromise,
    pushInfoPromise
  ]);

  event.waitUntil(promiseChain);
});

這里我們調(diào)用一個(gè)返回promise的函數(shù),pushReceivedTracking()為了示例值骇,我們可以假裝向我們的分析提供者發(fā)出網(wǎng)絡(luò)請求莹菱。我們還發(fā)出網(wǎng)絡(luò)請求,獲取響應(yīng)并使用響應(yīng)數(shù)據(jù)顯示通知的標(biāo)題和消息吱瘩。

我們可以確保服務(wù)工作者保持活力道伟,同時(shí)通過將這些承諾與這些承諾相結(jié)合來完成這兩項(xiàng)任務(wù)Promise.all()。產(chǎn)生的承諾將被轉(zhuǎn)換為event.waitUntil() 意味著瀏覽器將等待兩個(gè)承諾完成使碾,然后再檢查是否已顯示通知并終止服務(wù)工作者皱卓。

我們應(yīng)該關(guān)注waitUntil()以及如何使用它的原因是開發(fā)人員面臨的最常見問題之一是,當(dāng)承諾鏈不正確/損壞時(shí)部逮,Chrome會(huì)顯示此“默認(rèn)”通知:


image.png

Chrome只會(huì)顯示“此網(wǎng)站已在后臺(tái)更新”娜汁。收到推送消息時(shí)的通知,并且服務(wù)工作者中的推送事件在傳遞到的承諾event.waitUntil()完成后未顯示通知兄朋。

顯示通知

顯示通知的API是:

<ServiceWorkerRegistration>.showNotification(<title>, <options>);

標(biāo)題是字符串掐禁,選項(xiàng)可以是以下任何一種:

{
  "http://": "Visual Options",
  "body": "<String>",
  "icon": "<URL String>",
  "image": "<URL String>",
  "badge": "<URL String>",
  "vibrate": "<Array of Integers>",
  "sound": "<URL String>",
  "dir": "<String of 'auto' | 'ltr' | 'rtl'>",

  "http://": "Behavioural Options",
  "tag": "<String>",
  "data": "<Anything>",
  "requireInteraction": "<boolean>",
  "renotify": "<Boolean>",
  "silent": "<Boolean>",

  "http://": "Both Visual & Behavioural Options",
  "actions": "<Array of Strings>",

  "http://": "Information Option. No visual affect.",
  "timestamp": "<Long>"
}
image.png

瀏覽器支持:
https://jakearchibald.github.io/isserviceworkerready/

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末怜械,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子傅事,更是在濱河造成了極大的恐慌缕允,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,718評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件蹭越,死亡現(xiàn)場離奇詭異障本,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)响鹃,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,683評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門驾霜,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人买置,你說我怎么就攤上這事粪糙。” “怎么了忿项?”我有些...
    開封第一講書人閱讀 158,207評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵蓉冈,是天一觀的道長。 經(jīng)常有香客問我轩触,道長寞酿,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,755評(píng)論 1 284
  • 正文 為了忘掉前任脱柱,我火速辦了婚禮伐弹,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘褐捻。我一直安慰自己掸茅,他們只是感情好椅邓,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,862評(píng)論 6 386
  • 文/花漫 我一把揭開白布柠逞。 她就那樣靜靜地躺著,像睡著了一般景馁。 火紅的嫁衣襯著肌膚如雪板壮。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 50,050評(píng)論 1 291
  • 那天合住,我揣著相機(jī)與錄音绰精,去河邊找鬼。 笑死透葛,一個(gè)胖子當(dāng)著我的面吹牛笨使,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播僚害,決...
    沈念sama閱讀 39,136評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼硫椰,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起靶草,我...
    開封第一講書人閱讀 37,882評(píng)論 0 268
  • 序言:老撾萬榮一對(duì)情侶失蹤蹄胰,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后奕翔,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體裕寨,經(jīng)...
    沈念sama閱讀 44,330評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,651評(píng)論 2 327
  • 正文 我和宋清朗相戀三年派继,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了宾袜。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,789評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡互艾,死狀恐怖试和,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情纫普,我是刑警寧澤阅悍,帶...
    沈念sama閱讀 34,477評(píng)論 4 333
  • 正文 年R本政府宣布,位于F島的核電站昨稼,受9級(jí)特大地震影響节视,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜假栓,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,135評(píng)論 3 317
  • 文/蒙蒙 一寻行、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧匾荆,春花似錦拌蜘、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,864評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至烤芦,卻和暖如春举娩,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背构罗。 一陣腳步聲響...
    開封第一講書人閱讀 32,099評(píng)論 1 267
  • 我被黑心中介騙來泰國打工铜涉, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人遂唧。 一個(gè)月前我還...
    沈念sama閱讀 46,598評(píng)論 2 362
  • 正文 我出身青樓芙代,卻偏偏與公主長得像,于是被迫代替她去往敵國和親盖彭。 傳聞我的和親對(duì)象是個(gè)殘疾皇子纹烹,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,697評(píng)論 2 351

推薦閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理事甜,服務(wù)發(fā)現(xiàn),斷路器滔韵,智...
    卡卡羅2017閱讀 134,638評(píng)論 18 139
  • 1逻谦、通過CocoaPods安裝項(xiàng)目名稱項(xiàng)目信息 AFNetworking網(wǎng)絡(luò)請求組件 FMDB本地?cái)?shù)據(jù)庫組件 SD...
    陽明先生_X自主閱讀 15,969評(píng)論 3 119
  • Spring Web MVC Spring Web MVC 是包含在 Spring 框架中的 Web 框架,建立于...
    Hsinwong閱讀 22,366評(píng)論 1 92
  • 《修煉職業(yè)化陪蜻,成就精英員工》員工是企業(yè)生存與發(fā)展之本邦马,一流的企業(yè)可以造就優(yōu)秀的員工,優(yōu)秀的員工也能成就一流的企業(yè)宴卖。...
    wuwenjuan閱讀 169評(píng)論 0 0
  • 七月初七適蘭夜滋将, 漢家彩女徵音絕。 羽衣霓裳瑤臺(tái)顧症昏, 且逢良辰遇良緣随闽。【修改于早前七夕舊夢肝谭,遙祝好友兩心相知】
    秦若閱讀 194評(píng)論 0 0