Html5進(jìn)階 文件系統(tǒng)

在javascript的世界里無法處理二進(jìn)制數(shù)據(jù)倒脓,如果需要處理过吻,只能使用charCodeAt()方法退腥,一個個字節(jié)地從文字編碼轉(zhuǎn)成二進(jìn)制數(shù)據(jù),還有一種辦法是將二進(jìn)制數(shù)據(jù)轉(zhuǎn)成Base64編碼眠砾,再進(jìn)行處理虏劲。這兩種方法不僅速度慢,而且容易出錯褒颈。ECMAScript 5引入了Blob對象,允許直接操作二進(jìn)制數(shù)據(jù)励堡。

1 Blob對象

Blob(Binary Large Object)對象代表了一段二進(jìn)制數(shù)據(jù)谷丸,提供了一系列操作接口。其他操作二進(jìn)制數(shù)據(jù)的API(比如File對象)应结,都是建立在Blob對象基礎(chǔ)上的刨疼,繼承了它的屬性和方法泉唁。

生成Blob對象有兩種方法:一種是使用Blob構(gòu)造函數(shù),另一種是對現(xiàn)有的Blob對象使用slice方法切出一部分揩慕。
(1)Blob構(gòu)造函數(shù),接受兩個參數(shù)迎卤。第一個參數(shù)是一個包含實際數(shù)據(jù)的數(shù)組拴鸵,第二個參數(shù)是數(shù)據(jù)的類型,這兩個參數(shù)都不是必需的蜗搔。

var htmlParts = ["<a id=\"a\"><b id=\"b\">hey!<\/b><\/a>"];
var myBlob = new Blob(htmlParts, { "type" : "text\/xml" });

下面是一個利用Blob對象劲藐,生成可下載文件的例子。

var a = document.createElement("a");
a.href = window.URL.createObjectURL(myBlob);
a.download = "hello-world.txt";
a.textContent = "Download Hello World!";

body.appendChild(a);

上面的代碼生成了一個超級鏈接樟凄,點擊后提示下載文本文件hello-world.txt聘芜,文件內(nèi)容為“Hello World”。

(2)Blob對象的slice方法缝龄,將二進(jìn)制數(shù)據(jù)按照字節(jié)分塊汰现,返回一個新的Blob對象。

var newBlob = oldBlob.slice(startingByte, endindByte);

下面是一個使用XMLHttpRequest對象叔壤,將大文件分割上傳的例子瞎饲。

function upload(blobOrFile) {
  var xhr = new XMLHttpRequest();
  xhr.open('POST', '/server', true);
  xhr.onload = function(e) { ... };
  xhr.send(blobOrFile);
}

document.querySelector('input[type="file"]').addEventListener('change', function(e) {
  var blob = this.files[0];

  const BYTES_PER_CHUNK = 1024 * 1024; // 1MB chunk sizes.
  const SIZE = blob.size;

  var start = 0;
  var end = BYTES_PER_CHUNK;

  while(start < SIZE) {
    upload(blob.slice(start, end));

    start = end;
    end = start + BYTES_PER_CHUNK;
  }
}, false);
});

這里有個小問問題,分片上傳每次流量為1024*1024百新,也就是1m企软,最后一次上傳的量需要單獨計算,不一定剛好為1m饭望。

var totalSize = file.size  //獲取file的大小  單位字節(jié)
var perchunk = 1024*1024,strart = 0,end = perchunk;
var count = totalSize % perchunk == 0 ? totalSize/perchunk : Math.floor(totalSize/perchunk) + 1   //需要上傳的次數(shù)
var index = 0;
while(index < count ){
         if(index == count -1){
             upload(start , totalSize)
         }else{
             upload(start , end)
         }
         start = end
         end = start + perchunk
         index ++
}

這樣寫才能保證數(shù)據(jù)正確的傳輸

3)Blob對象有兩個只讀屬性:

size:二進(jìn)制數(shù)據(jù)的大小仗哨,單位為字節(jié)。
type:二進(jìn)制數(shù)據(jù)的MIME類型铅辞,全部為小寫厌漂,如果類型未知,則該值為空字符串斟珊。

下面展示一段我學(xué)習(xí)blob的代碼

<!-- html -->
<body>
  <h1>Reading a Binary Large Object (blob)</h1>
  <p>Displays file data between a starting byte value and ending byte value.</p>
  <h3>Select a file:</h3>
  <p><input type="file" id="fileInput" /></p>
  <div id="hidder" style="visibility: hidden;">
    <ul id="byteCount"></ul>  
    <h3>Enter byte range:</h3>
    <p><input type="number" id="byteStart" /> Start byte (counting from the 0<sup>th</sup> byte).</p>
    <p><input type="number" id="byteEnd" /> End byte (counting from the 0<sup>th</sup> byte).</p>
    <p><button id="byteRangeButton">Show File Content for Byte Range</button></p>
    <h3 id="fileOutputHeader"></h3>
    <p id="byteRangeFileContent"></p>
  </div>
  <div id="alert" style="color: red;"><!-- Fills the roll of alert(). --></div>
</body>

js代碼

var globals = 
        {
          selectedFile: null,
          fileSize: 0 
        };
    window.addEventListener('load', init, false);
    
    // 檢測是否支持filereader苇倡,下面會講到
    function fileApiSupportCheck() {
      if (window.File && window.FileReader && window.FileList && window.Blob) {
        // All the File APIs are supported.
      }
      else {
        document.getElementById('alert').innerHTML = '<h2>FILE APIs NOT FULLY SUPPORTED - UPDATE YOUR BROWSER</h2>';
      }
    }

 function init() {
      fileApiSupportCheck();
      document.getElementById('fileInput').addEventListener('change', fileInputHandler, false);
      document.getElementById('byteRangeButton').addEventListener('click', byteRangeButtonHandler, false);
    }

//文件上傳回調(diào)函數(shù)
function fileInputHandler(evt) {
      var f = evt.target.files[0]; // The solo file selected by the user.

      if (!f.size) {
        document.getElementById('alert').innerHTML = 'Please select a file (that contains some content).';
        return;
      }

      globals.selectedFile = f;
      globals.fileSize = f.size;

      document.getElementById('byteCount').innerHTML = '<li><strong>' + f.name + '</strong> (' + (f.type || 'n/a') + ') = <strong>' + f.size + ' bytes</strong></li>';
      document.getElementById('hidder').style.visibility = "visible"; // We've selected a valid file and can now safely show the byte range input markup.
    }

function byteRangeButtonHandler() {
      var start = Number(document.getElementById('byteStart').value);
      var end = Number(document.getElementById('byteEnd').value);
      readBlob(start, end);
    }

(4) 普及一個知識
通過canvas繪制的圖片可以和blob在一起使用

var canvas = document.getElementById('canvas');

canvas.toBlob(function(blob) {
  var newImg = document.createElement('img'),
      url = URL.createObjectURL(blob);

  newImg.onload = function() {
    // no longer need to read the blob so it's revoked
    URL.revokeObjectURL(url);
  };

  newImg.src = url;
  document.body.appendChild(newImg);
});

Note that here we're creating a PNG image; if you add a second parameter to the toBlob() call, you can specify the image type. For example, to get the image in JPEG format:
大概意思是說默認(rèn)是png的格式,我們可以手動制定格式類型囤踩。

canvas.toBlob(function(blob){...}, 'image/jpeg', 0.95); // JPEG at 95% quality

我們講了這么多blob對象的知識旨椒,那么它的應(yīng)用何在,我們接著看

2 FileList對象

File API提供File對象堵漱,它是FileList對象的成員综慎,包含了文件的一些元信息,比如文件名勤庐、上次改動時間示惊、文件大小和文件類型好港。

var selected_file = document.getElementById('input').files[0];

var fileName = selected_file.name;
var fileSize = selected_file.size;
var fileType = selected_file.type;

File對象的屬性值如下。

name:文件名米罚,該屬性只讀钧汹。
size:文件大小,單位為字節(jié)录择,該屬性只讀拔莱。
type:文件的MIME類型,如果分辨不出類型糊肠,則為空字符串辨宠,該屬性只讀。
lastModified:文件的上次修改時間货裹,格式為時間戳嗤形。
lastModifiedDate:文件的上次修改時間,格式為Date對象實例弧圆。

我們聊了這么多赋兵,無非就是想在文件上傳的過程中使用這些api,那么還要繼續(xù)學(xué)習(xí)搔预。

3 FileReader API

FileReader API用于讀取文件霹期,即把文件內(nèi)容讀入內(nèi)存。它的參數(shù)是File對象或Blob對象拯田。

對于不同類型的文件历造,F(xiàn)ileReader提供不同的方法讀取文件。

readAsBinaryString(Blob|File):返回二進(jìn)制字符串船庇,該字符串每個字節(jié)包含一個0到255之間的整數(shù)吭产。
readAsText(Blob|File, opt_encoding):返回文本字符串。默認(rèn)情況下鸭轮,文本編碼格式是’UTF-8’臣淤,可以通過可選的格式參數(shù),指定其他編碼格式的文本窃爷。
readAsDataURL(Blob|File):返回一個基于Base64編碼的data-uri對象邑蒋。
readAsArrayBuffer(Blob|File):返回一個ArrayBuffer對象。

readAsText方法用于讀取文本文件按厘,它的第一個參數(shù)是File或Blob對象医吊,第二個參數(shù)是前一個參數(shù)的編碼方法,如果省略就默認(rèn)為UTF-8編碼逮京。該方法是異步方法遮咖,一般監(jiān)聽onload件,用來確定文件是否加載結(jié)束造虏,方法是判斷FileReader實例的result屬性是否有值御吞。其他三種讀取方法,用法與readAsText方法類似漓藕。

var reader = new FileReader();
reader.onload = function(e) {
  var text = e.target.result;
}
reader.readAsText(file, encoding);

親測了一遍陶珠,這個text在utf-8下是亂碼的。

readAsDataURL方法返回一個data URL享钞,它的作用基本上是將文件數(shù)據(jù)進(jìn)行Base64編碼揍诽。你可以將返回值設(shè)為圖像的src屬性。

var file = document.getElementById('destination').files[0];
if(file.type.indexOf('image') !== -1) {
  var reader = new FileReader();
  reader.onload = function (e) {
    var dataURL = e.target.result;
  }
  reader.readAsDataURL(file);
}

dataURL是一段base64編碼的url栗竖。

什么是 base64 編碼?
我不是來講概念的暑脆,直接切入正題,圖片的 base64 編碼就是可以將一副圖片數(shù)據(jù)編碼成一串字符串狐肢,使用該字符串代替圖像地址添吗。

那么為什么要使用 base64 傳輸圖片文件?上文也有提及份名,因為這樣可以節(jié)省一個 http 請求碟联。圖片的 base64 編碼可以算是前端優(yōu)化的一環(huán)。效益雖小僵腺,但卻能積少成多鲤孵。

readAsBinaryString方法可以讀取任意類型的文件,而不僅僅是文本文件辰如,返回文件的原始的二進(jìn)制內(nèi)容普监。這個方法與XMLHttpRequest.sendAsBinary方法結(jié)合使用,就可以使用JavaScript上傳任意文件到服務(wù)器琉兜。

var reader = new FileReader();
reader.onload = function(e) {
  var rawData = reader.result;
}
reader.readAsBinaryString(file);

FileReader對象采用異步方式讀取文件凯正,可以為一系列事件指定回調(diào)函數(shù)。

onabort方法:讀取中斷或調(diào)用reader.abort()方法時觸發(fā)呕童。
onerror方法:讀取出錯時觸發(fā)漆际。
onload方法:讀取成功后觸發(fā)。
onloadend方法:讀取完成后觸發(fā)夺饲,不管是否成功奸汇。觸發(fā)順序排在 onload 或 onerror 后面。
onloadstart方法:讀取將要開始時觸發(fā)往声。
onprogress方法:讀取過程中周期性觸發(fā)擂找。

下面是一個onprogress事件回調(diào)函數(shù)的例子,主要用來顯示讀取進(jìn)度浩销。

var reader = new FileReader();
reader.onprogress = updateProgress;

function updateProgress(evt) {
  if (evt.lengthComputable) {
    var percentLoaded = Math.round((evt.loaded / evt.totalEric Bidelman) * 100);
    var progress = document.querySelector('.percent');
    if (percentLoaded < 100) {
      progress.style.width = percentLoaded + '%';
      progress.textContent = percentLoaded + '%';
    }
  }
}

下面跟大家講一個很重要的知識點贯涎,拖拽文件上傳的實現(xiàn),相信大家在qq里面已經(jīng)用到過慢洋。

<div id="output" style="min-height: 200px; white-space: pre; border: 1px solid black;"
     ondragenter="document.getElementById('output').textContent = ''; event.stopPropagation(); event.preventDefault();"
     ondragover="event.stopPropagation(); event.preventDefault();"
     ondrop="event.stopPropagation(); event.preventDefault();
     dodrop(event);">
     DROP FILES HERE FROM FINDER OR EXPLORER
</div>

在新的html5 api里面有關(guān)于拖拽講解的塘雳,這里就不說了陆盘,大家可以自行g(shù)oogle。

function dodrop(event)
{
  var dt = event.dataTransfer;
  var files = dt.files;

  var count = files.length;
  output("File Count: " + count + "\n");

    for (var i = 0; i < files.length; i++) {
      var fileReader = new fileReader()
      fileReader.onload = function(e){
            document.createElement('.img').src = e.target.result
      }
      fileReader.readAsDataURL(files[i])
      output(" File " + i + ":\n(" + (typeof files[i]) + ") : <" + files[i] + " > " +
             files[i].name + " " + files[i].size + "\n");
    }
}

function output(text)
{
  document.getElementById("output").textContent += text;
  //dump(text);
}

在ondrop事件上通過event.dataTransfer獲取files對象败明。

4 文件斷點續(xù)傳

<form method="post" id="myForm" action="/fileTest.php" enctype="multipart/form-data">
            <input type="file" id="myFile" multiple>
            <!-- 上傳的文件列表 -->
            <table id="upload-list">
                <thead>
                    <tr>
                        <th width="35%">文件名</th>
                        <th width="15%">文件類型</th>
                        <th width="15%">文件大小</th>
                        <th width="20%">上傳進(jìn)度</th>
                        <th width="15%">
                            <input type="button" id="upload-all-btn" value="全部上傳">
                        </th>
                    </tr>
                </thead>
                <tbody>
                </tbody>
            </table>
        </form>
        <!-- 上傳文件列表中每個文件的信息模版 -->
        <script type="text/template" id="file-upload-tpl">
            <tr>
                <td>{{fileName}}</td>
                  <td>{{fileType}}</td>
                  <td>{{fileSize}}</td>
                  <td class="upload-progress">{{progress}}</td>
                  <td>
                      <input type="button" class="upload-item-btn"  data-name="{{fileName}}" data-size="{{totalSize}}" data-state="default" value="{{uploadVal}}">
                  </td>
              </tr>
        </script>
// 選擇文件-顯示文件信息
            $('#myFile').change(function(e) {
                var file,
                    uploadItem = [],
                    uploadItemTpl = $('#file-upload-tpl').html(),
                    size,
                    percent,
                    progress = '未上傳',
                    uploadVal = '開始上傳';

                for (var i = 0, j = this.files.length; i < j; ++i) {
                    file = this.files[i];

                    percent = undefined;
                    progress = '未上傳';
                    uploadVal = '開始上傳';

                    // 計算文件大小
                    size = file.size > 1024
                        ? file.size / 1024  > 1024
                        ? file.size / (1024 * 1024) > 1024
                        ? (file.size / (1024 * 1024 * 1024)).toFixed(2) + 'GB'
                        : (file.size / (1024 * 1024)).toFixed(2) + 'MB'
                        : (file.size / 1024).toFixed(2) + 'KB'
                        : (file.size).toFixed(2) + 'B';

                    // 初始通過本地記錄隘马,判斷該文件是否曾經(jīng)上傳過
                    percent = window.localStorage.getItem(file.name + '_p');

                    if (percent && percent !== '100.0') {
                        progress = '已上傳 ' + percent + '%';
                        uploadVal = '繼續(xù)上傳';
                    }

                    // 更新文件信息列表
                    uploadItem.push(uploadItemTpl
                        .replace(/{{fileName}}/g, file.name)
                        .replace('{{fileType}}', file.type || file.name.match(/\.\w+$/) + '文件')
                        .replace('{{fileSize}}', size)
                        .replace('{{progress}}', progress)
                        .replace('{{totalSize}}', file.size)
                        .replace('{{uploadVal}}', uploadVal)
                    );
                }

                $('#upload-list').children('tbody').html(uploadItem.join(''))
                    .end().show();
            });

當(dāng)選擇文件后,我們把信息渲染到了table列表里面了妻顶。當(dāng)我們單獨點擊某個文件后該文件就開始上傳酸员。

$(document).on('click', '.upload-item-btn', function() {
                var $this = $(this),
                    state = $this.attr('data-state'),
                    msg = {
                        done: '上傳成功',
                        failed: '上傳失敗',
                        in: '上傳中...',
                        paused: '暫停中...'
                    },
                    fileName = $this.attr('data-name'),
                    $progress = $this.closest('tr').find('.upload-progress'),
                    eachSize = 1024,
                    totalSize = $this.attr('data-size'),
                    chunks = Math.ceil(totalSize / eachSize),
                    percent = window.localStorage.getItem(fileName + '_percent') || 0;
                    chunk = window.localStorage.getItem(fileName + '_chunk') || 0;
                    // 暫停上傳操作
                    isPaused = 0;

                // 進(jìn)行暫停上傳操作
                // 未實現(xiàn),這里通過動態(tài)的設(shè)置isPaused值并不能阻止下方ajax請求的調(diào)用
                if (state === 'uploading') {
                    $this.val('繼續(xù)上傳').attr('data-state', 'paused');
                    $progress.text(msg['paused'] + percent + '%');
                    window.localStorage.setItem(fileName + '_status','0');
                    isPaused = 1;
                    console.log('暫停:', isPaused);
                }
                // 進(jìn)行開始/繼續(xù)上傳操作
                else if (state === 'paused' || state === 'default') {
                    $this.val('暫停上傳').attr('data-state', 'uploading');
                    window.localStorage.setItem(fileName + '_status','1');
                    isPaused = 0;
                }

                // 第一次點擊上傳
                if(ispaused == 0)
                      startUpload(chunk);

                // 上傳操作 times: 第幾次
                function startUpload(chunk) {
                    // 上傳之前查詢是否以及上傳過分片
                    chunk = parseInt(chunk, 10);
                    // 判斷是否為末分片
                    var    isLastChunk = (chunk == (chunks - 1) ? 1 : 0);

                    // 如果第一次上傳就為末分片讳嘱,即文件已經(jīng)上傳完成幔嗦,則重新覆蓋上傳
                    if (times === 'first' && isLastChunk === 1) {
                        window.localStorage.setItem(fileName + '_chunk', 0);
                        chunk = 0;
                        isLastChunk = 0;
                    }
                    
                    
                    // 設(shè)置分片的開始結(jié)尾
                    var    blobFrom = chunk * eachSize, // 分段開始
                        blobTo = (chunk + 1) * eachSize > totalSize ? totalSize : (chunk + 1) * eachSize, // 分段結(jié)尾
                        percent = (100 * blobTo / totalSize).toFixed(1), // 已上傳的百分比
                        timeout = 5000, // 超時時間
                        fd = new FormData($('#myForm')[0]);

                    fd.append('theFile', findTheFile(fileName).slice(blobFrom, blobTo)); // 分好段的文件
                    fd.append('fileName', fileName); // 文件名
                    fd.append('totalSize', totalSize); // 文件總大小
                    fd.append('isLastChunk', isLastChunk); // 是否為末段
                    fd.append('isFirstUpload', times === 'first' ? 1 : 0); // 是否是第一段(第一次上傳)

                    // 上傳
                    $.ajax({
                        type: 'post',
                        url: '/fileTest.php',
                        data: fd,
                        processData: false,
                        contentType: false,
                        timeout: timeout,
                        success: function(rs) {
                            rs = JSON.parse(rs);

                            // 上傳成功
                            if (rs.status === 200) {
                                // 記錄已經(jīng)上傳的百分比
                                window.localStorage.setItem(fileName + '_p', percent);

                                // 已經(jīng)上傳完畢
                                if (chunk === (chunks - 1)) {
                                    $progress.text(msg['done']);
                                    $this.val('已經(jīng)上傳').prop('disabled', true).css('cursor', 'not-allowed');
                                    if (!$('#upload-list').find('.upload-item-btn:not(:disabled)').length) {
                                        $('#upload-all-btn').val('已經(jīng)上傳').prop('disabled', true).css('cursor', 'not-allowed');
                                    }
                                } else {
                                    // 記錄已經(jīng)上傳的分片
                                    window.localStorage.setItem(fileName + '_chunk', ++chunk);

                                    $progress.text(msg['in'] + percent + '%');
                                    // 每回再次執(zhí)行,需要判斷狀態(tài)值
                                    
                                    if (window.localStorage.getItem(fileName + '_status') == 1) {
                                        startUpload(chunk++);
                                    }

                                }
                            }
                            // 上傳失敗沥潭,上傳失敗分很多種情況邀泉,具體按實際來設(shè)置
                            else if (rs.status === 500) {
                                $progress.text(msg['failed']);
                            }
                        },
                        error: function() {
                            $progress.text(msg['failed']);
                        }
                    });
                }
            });

我們這里需要注意一個問題,點擊暫停后改變了狀態(tài)paused = 1叛氨,在localstorage里面設(shè)置了_status為0呼渣,那么之前的ajax請求就需要判斷每次請求的狀態(tài)值是否為1,為1才會在回調(diào)中遞歸執(zhí)行寞埠。

FormData 對象的使用

通過FormData對象可以組裝一組用 XMLHttpRequest
發(fā)送請求的鍵/值對屁置。它可以更靈活方便的發(fā)送表單數(shù)據(jù),因為可以獨立于表單使用仁连。如果你把表單的編碼類型設(shè)置為multipart/form-data 蓝角,則通過FormData傳輸?shù)臄?shù)據(jù)格式和表單通過submit() 方法傳輸?shù)臄?shù)據(jù)格式相同

舉個例子說明吧

<form method="post" id="myForm" action="/fileTest.php" enctype="multipart/form-data">
            <input type="file" id="myFile" multiple>
            <input type="text" name="user">
            <input type="text" name="password">
            <input type='submit' value='submit'>
</form>            

我們通過post方法向服務(wù)器提交數(shù)據(jù),其實我還可以不寫action就是提交地址饭冬。

var formData = new FormData($('#myForm'));
var request = new XMLHttpRequest();
request.open("POST", "fileTest.php");
request.send(formData);

使用FormData對象上傳文件

<form enctype="multipart/form-data" method="post" name="fileinfo">
  <label>Your email address:</label>
  <input type="email" autocomplete="on" autofocus name="userid" placeholder="email" required size="32" maxlength="64" /><br />
  <label>Custom file label:</label>
  <input type="text" name="filelabel" size="12" maxlength="32" /><br />
  <label>File to stash:</label>
  <input type="file" name="file" required />
  <input type="submit" value="Stash the file!" />
</form>
<div></div>

然后使用下面的代碼發(fā)送請求:

var form = document.forms.namedItem("fileinfo");
form.addEventListener('submit', function(ev) {

  var oOutput = document.querySelector("div"),
      oData = new FormData(form);

  oData.append("CustomField", "This is some extra data");

  var oReq = new XMLHttpRequest();
  oReq.open("POST", "stash.php", true);
  oReq.onload = function(oEvent) {
    if (oReq.status == 200) {
      oOutput.innerHTML = "Uploaded!";
    } else {
      oOutput.innerHTML = "Error " + oReq.status + " occurred when trying to upload your file.<br \/>";
    }
  };

  oReq.send(oData);
  ev.preventDefault();
}, false);

構(gòu)造的formdata可以通過append添加額外數(shù)據(jù)使鹅。注意也可以append blob對象。

最后說一下formdata的使用場景:

使用jquery的serializeArray函數(shù)昌抠,但是這個方法對于input[type="file"]是無效的患朱,F(xiàn)ormData可以用來處理帶有multipart/form-data編碼類型的表單,一般都是帶有input[type="file"]的表單.

大家有興趣自己可以去看下表單提交的知識炊苫,有時間我會講一期專題裁厅。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市侨艾,隨后出現(xiàn)的幾起案子执虹,更是在濱河造成了極大的恐慌,老刑警劉巖唠梨,帶你破解...
    沈念sama閱讀 206,311評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件袋励,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)茬故,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評論 2 382
  • 文/潘曉璐 我一進(jìn)店門盖灸,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人均牢,你說我怎么就攤上這事糠雨。” “怎么了徘跪?”我有些...
    開封第一講書人閱讀 152,671評論 0 342
  • 文/不壞的土叔 我叫張陵,是天一觀的道長琅攘。 經(jīng)常有香客問我垮庐,道長,這世上最難降的妖魔是什么坞琴? 我笑而不...
    開封第一講書人閱讀 55,252評論 1 279
  • 正文 為了忘掉前任哨查,我火速辦了婚禮,結(jié)果婚禮上剧辐,老公的妹妹穿的比我還像新娘寒亥。我一直安慰自己,他們只是感情好荧关,可當(dāng)我...
    茶點故事閱讀 64,253評論 5 371
  • 文/花漫 我一把揭開白布溉奕。 她就那樣靜靜地躺著,像睡著了一般忍啤。 火紅的嫁衣襯著肌膚如雪加勤。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,031評論 1 285
  • 那天同波,我揣著相機(jī)與錄音鳄梅,去河邊找鬼。 笑死未檩,一個胖子當(dāng)著我的面吹牛戴尸,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播冤狡,決...
    沈念sama閱讀 38,340評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼孙蒙,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了筒溃?” 一聲冷哼從身側(cè)響起马篮,我...
    開封第一講書人閱讀 36,973評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎怜奖,沒想到半個月后浑测,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,466評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,937評論 2 323
  • 正文 我和宋清朗相戀三年迁央,在試婚紗的時候發(fā)現(xiàn)自己被綠了掷匠。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,039評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡岖圈,死狀恐怖讹语,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情蜂科,我是刑警寧澤顽决,帶...
    沈念sama閱讀 33,701評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站导匣,受9級特大地震影響才菠,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜贡定,卻給世界環(huán)境...
    茶點故事閱讀 39,254評論 3 307
  • 文/蒙蒙 一赋访、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧缓待,春花似錦蚓耽、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,259評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至国葬,卻和暖如春贤徒,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背汇四。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工接奈, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人通孽。 一個月前我還...
    沈念sama閱讀 45,497評論 2 354
  • 正文 我出身青樓序宦,卻偏偏與公主長得像,于是被迫代替她去往敵國和親背苦。 傳聞我的和親對象是個殘疾皇子互捌,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,786評論 2 345

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

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn)行剂,斷路器秕噪,智...
    卡卡羅2017閱讀 134,599評論 18 139
  • 本文詳細(xì)介紹了 XMLHttpRequest 相關(guān)知識,涉及內(nèi)容: AJAX厚宰、XMLHTTP腌巾、XMLHttpReq...
    semlinker閱讀 13,613評論 2 18
  • 前言 HTML5 中提供的文件API在前端中有著豐富的應(yīng)用遂填,上傳、下載澈蝙、讀取內(nèi)容等在日常的交互中很常見吓坚。而且在各個...
    linshuai閱讀 884評論 0 11
  • 國家電網(wǎng)公司企業(yè)標(biāo)準(zhǔn)(Q/GDW)- 面向?qū)ο蟮挠秒娦畔?shù)據(jù)交換協(xié)議 - 報批稿:20170802 前言: 排版 ...
    庭說閱讀 10,869評論 6 13
  • “一個人同時走在兩條路上礁击,是可能的,走在這路上的你逗载,用懷疑的眼光哆窿,打量走在另一條路上的你∷赫辏” 這幾天開學(xué)后更耻,系里面...
    不想栽樹閱讀 431評論 0 1