利用Jenkins持續集成iOS項目

前言

眾所周知,現在App的競爭已經到了用戶體驗為王,質量為上的白熱化階段。用戶們都是很挑剔的。如果一個公司的推廣團隊好不容易砸了重金推廣了一個APP,好不容易有了一些用戶,由于一次線上的bug導致一批的用戶在使用中紛紛出現閃退bug,輕則,很可能前期推廣砸的錢都白費了,重則,口碑不好,未來也提升不起用戶量來了。靜下心來分析一下問題的原因,無外乎就是質量沒有過關就上線了。除去主觀的一些因素,很大部分的客觀因素我覺得可以被我們防范的。根據大神們提出的一套開發規范建議,CI + FDD,就可以幫助我們極大程度的解決客觀因素。本文接下來主要討論Continuous Integration持續集成(簡稱CI)

目錄

1.為什么我們需要持續集成

2.持續化集成工具——Jenkins

3.iOS自動化打包命令——xcodebuild + xcrun 和 fastlane - gym 命令

4.打包完成自動化上傳 fir / 蒲公英 第三方平臺

5.完整的持續集成流程

6.Jenkins + Docker

一. 為什么我們需要持續集成

談到為什么需要的問題,我們就需要從什么是來說起。那什么是持續集成呢。

持續集成是一種軟件開發實踐:許多團隊頻繁地集成他們的工作,每位成員通常進行日常集成,進而每天會有多種集成。每個集成會由自動的構建(包括測試)來盡可能快地檢測錯誤。許多團隊發現這種方法可以顯著的減少集成問題并且可以使團隊開發更加快捷。

CI是一種開發實踐。實踐應該包含3個基本模塊,一個可以自動構建的過程,自動編譯代碼,可以自動分發,部署和測試。一個代碼倉庫,SVN或者Git。最后一個是一個持續集成的服務器。通過持續集成,可以讓我們通過自動化等手段高頻率地去獲取產品反饋并響應反饋的過程。

那么持續集成能給我們帶來些什么好處呢?這里推薦一篇文章,文章中把Continuous integration(CI) andtest-driven development(TDD)分成了12個步驟。然而帶來的好處成倍增加,有24點好處。

我來說說用了CI以后帶來的一些深有體會的優點。

1. 縮減開發周期,快速迭代版本

每個版本開始都會估算好開發周期,但是總會因為各種事情而延期。這其中包括了一些客觀因素。由于產品線增多,迭代速度越來越快,給測試帶來的壓力也越來越大。如果測試都在開發完全開發完成之后再來測試,那就會影響很長一段時間。這時候由于集成晚就會嚴重拖慢項目節奏。如果能盡早的持續集成,盡快進入上圖的12步驟的迭代環中,就可以盡早的暴露出問題,提早解決,盡量在規定時間內完成任務。

2. 自動化流水線操作帶來的高效

其實打包對于開發人員來說是一件很耗時,而且沒有很大技術含量的工作。如果開發人員一多,相互改的代碼沖突的幾率就越大,加上沒有產線管理機制,代碼倉庫的代碼質量很難保證。團隊里面會花一些時間來解決沖突,解決完了沖突還需要自己手動打包。這個時候如果證書又不對,又要耽誤好長時間。這些時間其實可以用持續集成來節約起來的。一天兩天看著不多,但是按照年的單位來計算,可以節約很多時間!

3. 隨時可部署

有了持續集成以后,我們可以以天為單位來打包,這種高頻率的集成帶來的最大的優點就是可以隨時部署上線。這樣就不會導致快要上線,到處是漏洞,到處是bug,手忙腳亂弄完以后還不能部署,嚴重影響上線時間。

4. 極大程度避免低級錯誤

我們可以犯錯誤,但是犯低級錯誤就很不應該。這里指的低級錯誤包括以下幾點:編譯錯誤,安裝問題,接口問題,性能問題。

以天為單位的持續集成,可以很快發現編譯問題,自動打包直接無法通過。打完包以后,測試掃碼無法安裝,這種問題也會立即被暴露出來。接口問題和性能問題就有自動化測試腳本來發現。這些低級問題由持續集成來暴露展現出來,提醒我們避免低級錯誤。

二. 持續化集成工具——Jenkins

Jenkins 是一個開源項目,提供了一種易于使用的持續集成系統,使開發者從繁雜的集成中解脫出來,專注于更為重要的業務邏輯實現上。同時 Jenkins 能實施監控集成中存在的錯誤,提供詳細的日志文件和提醒功能,還能用圖表的形式形象地展示項目構建的趨勢和穩定性。

根據官方定義,Jenkins有以下的用途:

構建項目

跑測試用例檢測bug

靜態代碼檢測

部署

關于這4點,實際使用中還是比較方便的:

1.構建項目自動化打包可以省去開發人員好多時間,重要的是,Jenkins為我們維護了一套高質量可用的代碼,而且保證了一個純凈的環境。我們經常會出現由于本地配置出錯而導致打包失敗的情況。現在Jenkins就是一個公平的評判者,它無法正確的編譯出ipa,那就是有編譯錯誤或者配置問題。開發人員沒必要去爭論本地是可以運行的,拉取了誰誰誰的代碼以后就不能運行了。共同維護Jenkins的正常編譯,因為Jenkins的編譯環境比我們本地簡單的多,它是最純凈無污染的編譯環境。開發者就只用專注于編碼。這是給開發者帶來的便利。

2.這個可以用來自動化測試。在本地生成大批的測試用例。每天利用服務器不斷的跑這些用例。每天每個接口都跑一遍。看上去沒必要,但是實際上今天運行正常的系統,很可能由于今天的代碼改動,明天就出現問題了。有了Jenkins可以以天為單位的進行回歸測試,代碼只要有改動,Jenkins就把所有的回歸測試的用例全部都跑一遍。在項目工期緊張的情況下,很多情況測試都不是很重視回歸測試,畢竟很可能測一遍之后是徒勞的“無用功”。然而由于回歸測試不及時,就導致到最后發版的時候系統不可用了,這時候回頭查找原因是比較耗時的,查看提交記錄,看到上百條提交記錄,排查起來也是頭疼的事情。以天為單位的回歸測試能立即發現問題。測試人員每天可以專注按單元測試,一周手動一次回歸測試。這是給測試者帶來的便利。

3.這個是靜態代碼分析,可以檢測出很多代碼的問題,比如潛在的內存泄露的問題。由于Jenkins所在環境的純凈,還是可以發現一些我們本地復雜環境無法發現的問題,進一步的提高代碼質量。這是給質檢帶來的便利。

4.隨時部署。Jenkins在打包完成之后可以設定之后的操作,這個時候往往就是提交app到跑測試用例的系統,或者部署到內測平臺生成二維碼。部署中不能安裝等一些低級問題隨之立即暴露。測試人員也只需要掃一下二維碼即可安裝,很方便。這也算是給測試帶來的便利。

以下的例子以2016-07-24 22:35的Weekly Release 2.15的版本為例。

我們來開始安裝Jenkins。從官網https://jenkins.io/上下載最新的pkg安裝包。

也可以下載jenkins.war, 然后運行Java -jar jenkins.war,進行安裝。

安裝完成之后,Safari可能會自動打開,如果沒有自動打開,打開瀏覽器,輸入http://localhost:8080

這個時候可能會報一個錯誤。如果出現了這面的問題。出現這個問題的原因就是Java環境有問題,重新Java環境即可。

這個時候如果你重啟電腦會發現Jenkins給你新增了一個用戶,名字就叫Jenkins,不過這個時候你不知道密碼。你可能會去試密碼,肯定是是不對的,因為初始密碼很復雜。這個時候正確做法是打開http://localhost:8080會出現下圖的重設初始密碼的界面。

按照提示,找到/Users/Shared/Jenkins/Home/ 這個目錄下,這個目錄雖然是共享目錄,但是有權限的,非Jenkins用戶/secrets/目錄是沒有讀寫權限的。

打開initialAdminPassword文件,復制出密碼,就可以填到網頁上去重置密碼了。如下圖

一路安裝過來,輸入用戶名,密碼,郵件這些,就算安裝完成了。

還是繼續登錄localhost:8080? ,選擇“系統管理”——“管理插件”,我們要先安裝一些輔助插件。

安裝GitLab插件

因為我們用的是GitLab來管理源代碼,Jenkins本身并沒有自帶GitLab插件,所以我們需要依次選擇系統管理->管理插件,在“可選插件”中選中“GitLab Plugin”和“Gitlab Hook Plugin”這兩項,然后安裝。

安裝Xcode插件

同安裝GitLab插件的步驟一樣,我們依次選擇系統管理->管理插件,在“可選插件”中選中“Xcode integration”安裝。

安裝完了這個,我們就可以配置一個構建項目了。

點擊新建好的項目,進來配置一下General參數。

這里可以設置包的保留天數還有天數。

接著設置源碼管理

由于現在我用到的是GitLab,先配置SSH Key,在Jenkins的證書管理中添加SSH。在Jenkins管理頁面,選擇“Credentials”,然后選擇“Global credentials (unrestricted)”,點擊“Add Credentials”,如下圖所示,我們填寫自己的SSH信息,然后點擊“Save”,這樣就把SSH添加到Jenkins的全局域中去了。

如果正常的配置正確的話,是不會出現下圖中的那段紅色的警告。如果有下圖的提示,就說明Jenkins還沒有連通GitLab或者SVN,那就請再檢查SSH Key是否配置正確。

構建觸發器設置這里是設置自動化測試的地方。這里涉及的內容很多,暫時我也沒有深入研究,這里暫時先不設置。有自動化測試需求的可以好好研究研究這里的設置。

不過這里有兩個配置還是需要是配置的

Poll SCM(poll source code management)? 輪詢源碼管理

需要設置源碼的路徑才能起到輪詢的效果。一般設置為類似結果: 0/5每5分鐘輪詢一次

Build periodically(定時build)

一般設置為類似: 00 20*? 每天 20點執行定時build 。當然兩者的設置都是一樣可以通用的。

格式是這樣的

分鐘(0-59) 小時(0-23) 日期(1-31) 月(1-12) 周幾(0-7,0和7都是周日)更加詳細的設置看這里

構建環境設置

iOS打包需要簽名文件和證書,所以這部分我們勾選“Keychains and Code Signing Identities”和“Mobile Provisioning Profiles”。

這里我們又需要用到Jenkins的插件,在系統管理頁面,選擇“Keychains and Provisioning Profiles Management”。

進入Keychains and Provisioning Profiles Management頁面,點擊“瀏覽”按鈕,分別上傳自己的keychain和證書。上傳成功后,我們再為keychain指明簽名文件的名稱。點擊“Add Code Signing Identity”,最后添加成功后如下圖所示:

注意:我第一次導入證書和Provisioning Profiles文件,就遇到了一點小“坑”,我當時以為是需要證書,但是這里需要的Keychain,并不是cer證書文件。這個Keychain其實在/Users/管理員用戶名/Library/keychains/login.keychain,當把這個Keychain設置好了之后,Jenkins會把這個Keychain拷貝到/Users/Shared/Jenkins/Library/keychains這里,(Library是隱藏文件)。Provisioning Profiles文件也直接拷貝到/Users/Shared/Jenkins/Library/MobileDevice文件目錄下。

這樣Adhoc證書和簽名文件就在Jenkins中配置好了,接下來我們只需要在item設置中指定相關文件即可。

回到我們新建的item,找到構建環境,按下圖選好自己的相關證書和簽名文件。

接下來在進行構建的設置

我們這里選擇執行一段打包腳本。腳本在下一章節詳細的講解。

構建后操作

這里我們選擇Execute a set of scripts,這里也是一個腳本,這個腳本用來上傳自動打包好的ipa文件。腳本在第四章節有詳細的講解。

至此,我們的Jenkins設置就全部完成了。點擊構建,就會開始構建項目了。

構建一次,各個顏色代表的意義如下:

天氣的晴雨表代表了項目的質量,這也是Jenkins的一個特色。

如果構建失敗了,可以去查看Console Output可以查看log日志。

三. iOS自動化打包命令——xcodebuild + xcrun 和 fastlane - gym 命令

在日常開發中,打包是最后上線不可缺少的環節,如果需要把工程打包成 ipa 文件,通常的做法就是在 Xcode 里點擊 「Product -> Archive」,當整個工程 archive 后,然后在自動彈出的 「Organizer」 中進行選擇,根據需要導出 ad hoc,enterprise 類型的 ipa 包。雖然Xcode已經可以很完美的做到打包的事情,但是還是需要我們手動點擊5,6下。加上我們現在需要持續集成,用打包命令自動化執行就順其自然的需要了。

1. xcodebuild + xcrun命令

Xcode為我們開發者提供了一套構建打包的命令,就是xcodebuild

和xcrun命令。xcodebuild把我們指定的項目打包成.app文件,xcrun將指定的.app文件轉換為對應的.ipa文件。

具體的文檔如下,xcodebuild官方文檔xcrun官方文檔

NAME

xcodebuild – build Xcode projects and workspaces

SYNOPSIS

1. xcodebuild [-project name.xcodeproj] [[-target targetname] … | -alltargets] [-configuration configurationname] [-sdk [sdkfullpath | sdkname]] [action …] [buildsetting=value …] [-userdefault=value …]

2. xcodebuild [-project name.xcodeproj] -scheme schemename [[-destination destinationspecifier] …] [-destination-timeout value] [-configuration configurationname] [-sdk [sdkfullpath | sdkname]] [action …] [buildsetting=value …] [-userdefault=value …]

3. xcodebuild -workspace name.xcworkspace -scheme schemename [[-destination destinationspecifier] …] [-destination-timeout value] [-configuration configurationname] [-sdk [sdkfullpath | sdkname]] [action …] [buildsetting=value …] [-userdefault=value …]

4. xcodebuild -version [-sdk [sdkfullpath | sdkname]] [infoitem]

5. xcodebuild -showsdks

6. xcodebuild -showBuildSettings [-project name.xcodeproj | [-workspace name.xcworkspace -scheme schemename]]

7. xcodebuild -list [-project name.xcodeproj | -workspace name.xcworkspace]

8. xcodebuild -exportArchive -archivePath xcarchivepath -exportPath destinationpath -exportOptionsPlist path

9. xcodebuild -exportLocalizations -project name.xcodeproj -localizationPath path [[-exportLanguage language] …]

10. xcodebuild -importLocalizations -project name.xcodeproj -localizationPath path

上面10個命令最主要的還是前3個。

接下來來說明一下參數:

-project -workspace:這兩個對應的就是項目的名字。如果有多個工程,這里又沒有指定,則默認為第一個工程。

-target:打包對應的targets,如果沒有指定這默認第一個。

-configuration:如果沒有修改這個配置,默認就是Debug和Release這兩個版本,沒有指定默認為Release版本。

-buildsetting=value ...:使用此命令去修改工程的配置。

-scheme:指定打包的scheme。

上面這些是最最基本的命令。

上面10個命令的第一個和第二個里面的參數,其中 -target

和 -configuration 參數可以使用 xcodebuild -list

獲得,-sdk 參數可由 xcodebuild -showsdks

獲得,[buildsetting=value ...] 用來覆蓋工程中已有的配置。可覆蓋的參數參考官方文檔Xcode Build Setting Reference

build

Build the target in the build root (SYMROOT). This is the default action, and is used if no action is given.

analyze

Build and analyze a target or scheme from the build root (SYMROOT). This requires specifying a scheme.

archive

Archive a scheme from the build root (SYMROOT). This requires specifying a scheme.

test

Test a scheme from the build root (SYMROOT). This requires specifying a scheme and optionally a destination.

installsrc

Copy the source of the project to the source root (SRCROOT).

install

Build the target and install it into the target’s installation directory in the distribution root (DSTROOT).

clean

Remove build products and intermediate files from the build root (SYMROOT).

上面第3個命令就是專門用來打帶有Cocopods的項目,因為這個時候項目工程文件不再是xcodeproj了,而是變成了xcworkspace了。

再來說說xcrun命令。

Usage:

PackageApplication [-s signature] application [-o output_directory] [-verbose] [-plugin plugin] || -man || -help

Options:

[-s signature]: certificate name to resign application before packaging

[-o output_directory]: specify output filename

[-plugin plugin]: specify an optional plugin

-help: brief help message

-man: full documentation

-v[erbose]: provide details during operation

參數不多,使用方法也很簡單,xcrun -sdk iphoneos -v PackageApplication? + 上述一些參數。

參數都了解之后,我們就來看看該如何用了。下面這個是使用了xcodebuild + xcrun命令寫的自動化打包腳本

# 工程名

APP_NAME="YourProjectName"

# 證書

CODE_SIGN_DISTRIBUTION="iPhone Distribution: Shanghai ******* Co., Ltd."

# info.plist路徑

project_infoplist_path="./${APP_NAME}/Info.plist"

#取版本號

bundleShortVersion=$(/usr/libexec/PlistBuddy -c "print CFBundleShortVersionString" "${project_infoplist_path}")

#取build值

bundleVersion=$(/usr/libexec/PlistBuddy -c "print CFBundleVersion" "${project_infoplist_path}")

DATE="$(date +%Y%m%d)"

IPANAME="${APP_NAME}_V${bundleShortVersion}_${DATE}.ipa"

#要上傳的ipa文件路徑

IPA_PATH="$HOME/${IPANAME}"

echo ${IPA_PATH}

echo "${IPA_PATH}">> text.txt

//下面2行是沒有Cocopods的用法

echo "=================clean================="

xcodebuild -target "${APP_NAME}"? -configuration 'Release' clean

echo "+++++++++++++++++build+++++++++++++++++"

xcodebuild -target "${APP_NAME}" -sdk iphoneos -configuration 'Release' CODE_SIGN_IDENTITY="${CODE_SIGN_DISTRIBUTION}" SYMROOT='$(PWD)'

//下面2行是集成有Cocopods的用法

echo "=================clean================="

xcodebuild -workspace "${APP_NAME}.xcworkspace" -scheme "${APP_NAME}"? -configuration 'Release' clean

echo "+++++++++++++++++build+++++++++++++++++"

xcodebuild -workspace "${APP_NAME}.xcworkspace" -scheme "${APP_NAME}" -sdk iphoneos -configuration 'Release' CODE_SIGN_IDENTITY="${CODE_SIGN_DISTRIBUTION}" SYMROOT='$(PWD)'

xcrun -sdk iphoneos PackageApplication "./Release-iphoneos/${APP_NAME}.app" -o ~/"${IPANAME}"

2. gym 命令

說到gym,就要先說一下fastlane。

fastlane是一套自動化打包的工具集,用 Ruby 寫的,用于 iOS 和 Android 的自動化打包和發布等工作。gym是其中的打包命令。

fastlane 的官網看這里, fastlane 的 github 看這里

要想使用gym,先要安裝fastlane。

sudo gem install fastlane --verbose

fastlane包含了我們日常編碼之后要上線時候進行操作的所有命令。

deliver:上傳屏幕截圖、二進制程序數據和應用程序到AppStore

snapshot:自動截取你的程序在每個設備上的圖片

frameit:應用截屏外添加設備框架

pem:可以自動化地生成和更新應用推送通知描述文件

sigh:生成下載開發商店的配置文件

produce:利用命令行在iTunes Connect創建一個新的iOS app

cert:自動創建iOS證書

pilot:最好的在終端管理測試和建立的文件

boarding:很容易的方式邀請beta測試

gym:建立新的發布的版本,打包

match:使用git同步你成員間的開發者證書和文件配置

scan:在iOS和Mac app上執行測試用例

整個發布過程可以用fastlane描述成下面這樣

lane :appstore do

increment_build_number

cocoapods

xctool

snapshot

sigh

deliver

frameit

sh "./customScript.sh"

slack

end

Ps:這里可能大家還會聽過一個命令叫xctool

xctool是官方xcodebuild命令的一個增強實現,輸出的內容比xcodebuild直觀可讀得多。通過brew即可安裝。

brew install xctool

使用gym自動化打包,腳本如下

#計時

SECONDS=0

#假設腳本放置在與項目相同的路徑下

project_path=$(pwd)

#取當前時間字符串添加到文件結尾

now=$(date +"%Y_%m_%d_%H_%M_%S")

#指定項目的scheme名稱

scheme="DemoScheme"

#指定要打包的配置名

configuration="Adhoc"

#指定打包所使用的輸出方式,目前支持app-store, package, ad-hoc, enterprise, development, 和developer-id,即xcodebuild的method參數

export_method='ad-hoc'

#指定項目地址

workspace_path="$project_path/Demo.xcworkspace"

#指定輸出路徑

output_path="/Users/your_username/Documents/"

#指定輸出歸檔文件地址

archive_path="$output_path/Demo_${now}.xcarchive"

#指定輸出ipa地址

ipa_path="$output_path/Demo_${now}.ipa"

#指定輸出ipa名稱

ipa_name="Demo_${now}.ipa"

#獲取執行命令時的commit message

commit_msg="$1"

#輸出設定的變量值

echo "===workspace path: ${workspace_path}==="

echo "===archive path: ${archive_path}==="

echo "===ipa path: ${ipa_path}==="

echo "===export method: ${export_method}==="

echo "===commit msg: $1==="

#先清空前一次build

gym --workspace ${workspace_path} --scheme ${scheme} --clean --configuration ${configuration} --archive_path ${archive_path} --export_method ${export_method} --output_directory ${output_path} --output_name ${ipa_name}

#輸出總用時

echo "===Finished. Total time: ${SECONDS}s==="

四. 打包完成自動化上傳 fir / 蒲公英 第三方平臺

要上傳到 fir / 蒲公英 第三方平臺,都需要注冊一個賬號,獲得token,之后才能進行腳本化操作。

1. 自動化上傳fir

安裝fir-clifir的命令行工具

需要先裝好ruby再執行

gem install fir-cli

#上傳到fir

fir publish ${ipa_path} -T fir_token -c "${commit_msg}"

2.自動化上傳蒲公英

#蒲公英上的User Key

uKey="7381f97070*****c01fae439fb8b24e"

#蒲公英上的API Key

apiKey="0b27b5c145*****718508f2ad0409ef4"

#要上傳的ipa文件路徑

IPA_PATH=$(cat text.txt)

rm -rf text.txt

#執行上傳至蒲公英的命令

echo "++++++++++++++upload+++++++++++++"

curl -F "file=@${IPA_PATH}" -F "uKey=${uKey}" -F "_api_key=${apiKey}" http://www.pgyer.com/apiv1/app/upload

五. 完整的持續集成流程

經過上面的持續化集成,現在我們就擁有了如下完整持續集成的流程

六. Jenkins + Docker

關于Jenkins的部署,其實是分以下兩種:

單節點(Master)部署

這種部署適用于大多數項目,其構建任務較輕,數量較少,單個節點就足以滿足日常開發所需。

多節點(Master-Slave)部署

通常規模較大,代碼提交頻繁(意味著構建頻繁),自動化測試壓力較大的項目都會采取這種部署結構。在這種部署結構下,Master通常只充當管理者的角色,負責任務的調度,slave節點的管理,任務狀態的收集等工作,而具體的構建任務則會分配給slave節點。一個Master節點理論上可以管理的slave節點數是沒有上限的,但通常隨著數量的增加,其性能以及穩定性就會有不同程度的下降,具體的影響則因Master硬件性能的高低而不同。

但是多節點部署又會有一些缺陷,當測試用例變得海量以后,會造成一些問題,于是有人設計出了下面這種部署結構,Jenkins + Docker

由于筆者現在的項目還處于單節點(Master)部署,關于多節點(Master-Slave)部署也沒有實踐經驗,改進版本的Docker更是沒有接觸過,但是如果有這種海量測試用例,高壓力的大量復雜的回歸測試的需求的,那推薦大家看這篇文章

最后

以上就是我關于Jenkins持續集成的一次實踐經驗。分享給大家,如果里面有什么錯誤,歡迎大家多多指教。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,238評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,430評論 3 415
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,134評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,893評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,653評論 6 408
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,136評論 1 323
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,212評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,372評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,888評論 1 334
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,738評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,939評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,482評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,179評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,588評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,829評論 1 283
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,610評論 3 391
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,916評論 2 372

推薦閱讀更多精彩內容