Jenkins-iOS自動打包流程

Jenkins iOS自動打包流程

背景

在開發(fā)中我們經(jīng)常需要打測試包叙赚,然后上傳至蒲公英等三方平臺,這其中需要經(jīng)歷的操作為:

  • Git倉庫拉取代碼
  • 設(shè)置項目的打包環(huán)境
  • 利用 xcode 進行打包
  • 上傳至蒲公英等三方平臺

每一次打包,上面的過程必不可少岭参,而且都是手工的,本篇文章我們將采用CD(Continuous Delivery)持續(xù)交付和CI(Continuous Integration)持續(xù)集成來進行自動化打包一鍵操作,解放雙手顷歌,拒絕手動的重復(fù)低效率勞動。

本篇文章講解如何使用 Jenkins + shell腳本 以及 Jenkins + fastlane 自動打包發(fā)布至蒲公英等三方平臺幔睬。

CI/CD(持續(xù)集成眯漩、持續(xù)交付/持續(xù)部署)

CI/CD 是一種通過在應(yīng)用開發(fā)階段引入自動化來頻繁向客戶交付應(yīng)用的方法。

CI/CD 的核心概念是持續(xù)集成麻顶、持續(xù)交付和持續(xù)部署赦抖。作為一個面向開發(fā)和運營團隊的解決方案,CI/CD 主要針對在集成新代碼時所引發(fā)的問題辅肾。

具體而言摹芙,CI/CD 可讓持續(xù)自動化和持續(xù)監(jiān)控貫穿于應(yīng)用的整個生命周期(從集成和測試階段,到交付和部署)宛瞄。這些關(guān)聯(lián)的事務(wù)通常被統(tǒng)稱為“CI/CD 管道”浮禾,由開發(fā)和運維團隊以敏捷方式協(xié)同支持交胚。

  • CI(Continuous Integration)是指持續(xù)集成,它屬于開發(fā)人員的自動化流程盈电。成功的 CI 意味著應(yīng)用代碼的新更改會定期構(gòu)建蝴簇、測試并合并到共享存儲庫中。該解決方案可以解決在一次開發(fā)中有太多應(yīng)用分支匆帚,從而導(dǎo)致相互沖突的問題熬词。

  • CD(Continuous Delivery/Deployment)是指持續(xù)交付或持續(xù)部署

    • 持續(xù)交付通常是指開發(fā)人員對應(yīng)用的更改會自動進行錯誤測試并上傳到存儲庫(如 GitHub 或容器注冊表),然后由運維團隊將其部署到實時生產(chǎn)環(huán)境中吸重。這旨在解決開發(fā)和運維團隊之間可見性及溝通較差的問題互拾。因此,持續(xù)交付的目的就是確保盡可能減少部署新代碼時所需的工作量嚎幸。

    • 持續(xù)部署(另一種“CD”)指的是自動將開發(fā)人員的更改從存儲庫發(fā)布到生產(chǎn)環(huán)境颜矿,以供客戶使用。它主要為了解決因手動流程降低應(yīng)用交付速度嫉晶,從而使運維團隊超負荷的問題骑疆。持續(xù)部署以持續(xù)交付的優(yōu)勢為根基,實現(xiàn)了管道后續(xù)階段的自動化替废。

詳細流程

20210304175817690.png

自動化打包流程

graph LR
    1[本地代碼] --提交--> 2[遠程倉庫] --> 3[Jenkins自動打包] --> 4[部署發(fā)布到蒲公英/Testflight] --> 5[結(jié)果郵件/釘釘/QQ通知]

Jenkins概述

Jenkins 是一款流行的開源持續(xù)集成(Continuous Integration)工具箍铭,廣泛用于項目開發(fā),具有自動化構(gòu)建椎镣、測試和部署等功能诈火。

安裝Jenkins環(huán)境

使用 Homebrew 軟件包管理器安裝Jenkins

沒有Homebrew的請先安裝Homebrew,網(wǎng)上資料很多状答,這里不再贅述柄瑰。

安裝命令

  • 安裝最新的LTS版本:brew install jenkins-lts
  • 安裝特定的LTS版本:brew install jenkins-lts@YOUR_VERSION
  • 啟動 Jenkins 服務(wù):brew services start jenkins-lts
  • 重新啟動 Jenkins 服務(wù):brew services restart jenkins-lts
  • 更新 Jenkins 版本:brew upgrade jenkins-lts

安裝步驟

  • 啟動 Jenkins 服務(wù)后,瀏覽 http://localhost:8080 剪况,該頁面需要確認是管理員安裝教沾,讓我們輸入密碼,根據(jù)提示獲取管理員密碼译断,點擊繼續(xù)授翻。
  • 選擇安裝推薦的插件,等待插件安裝完畢孙咪。
  • 插件安裝完成后會自動跳轉(zhuǎn)到配置管理員賬戶頁堪唐,配置完成點擊保存并完成注冊。
  • 瀏覽器輸入 http://localhost:8080 打開Jenkins翎蹈,登錄即可淮菠。

Jenkins 配置-iOS

安裝相關(guān)插件

構(gòu)建xcode項目需要安裝的插件

  • Git Parameter:分支管理

在Jenkins首頁->系統(tǒng)管理->插件管理搜索相關(guān)插件進行安裝即可。

環(huán)境變量配置

選擇系統(tǒng)管理 -> 系統(tǒng)配置 -> 全局屬性 -> 勾選環(huán)境變量選項
鍵:PATH
值:在終端中輸入echo $PATH將輸出內(nèi)容復(fù)制填寫荤堪。

image-20220605203314715.png

新建任務(wù)

創(chuàng)建任務(wù)

在Jenkins首頁->新建任務(wù)中輸入任務(wù)名稱合陵,選擇構(gòu)建一個自由風(fēng)格的軟件項目枢赔,點擊確定。

image-20220604085359715.png

任務(wù)相關(guān)配置

選擇General -> 參數(shù)化構(gòu)建過程 -> Git參數(shù)

image-20220604085859262.png

填寫名稱拥知,選擇類型為分支類型踏拜,默認origin/master

image-20220604090215644.png

選擇源碼管理 -> Git,輸入倉庫地址低剔,添加賬號密碼速梗。

image-20220604095423461.png

選擇構(gòu)建 -> 增加構(gòu)建步驟 -> 執(zhí)行shell

image-20220605190845531.png

添加打包腳本,保存襟齿。

Fastlane打包

1姻锁、簡介
Fastlane是用Ruby語言編寫的一套自動化工具集和框架,每一個工具實際都對應(yīng)一個Ruby腳本猜欺,用來執(zhí)行某一個特定的任務(wù)位隶,而Fastlane核心框架則允許使用者通過類似配置文件的形式,將不同的工具有機而靈活的結(jié)合在一起替梨,從而形成一個個完整的自動化流程。

2装黑、原理

  • Fastlane命令執(zhí)行的底層并不是自己實現(xiàn)的副瀑,而是調(diào)用其他的插件或者工具執(zhí)行的。
  • 核心一:打包命令恋谭,F(xiàn)astlane中的gym工具只是xcodebuild工具的一個封裝糠睡。因此安裝Fastlane的步驟里有安裝IDE的指令集。
  • 核心二:iTC(即iTunesConnect 蘋果的賬號證書應(yīng)用管理平臺)命令疚颊。蘋果除了提供圖形化可操作的網(wǎng)頁之外狈孔,還提供相對應(yīng)的一整套底層 API 給開發(fā)者使用,F(xiàn)astlane的底層操作是封裝的這套API材义。

我們iOS開發(fā)者均抽,感覺最繁瑣的事就是打包上架了,打包過程不僅繁瑣還特別耗費時間其掂。那么有沒有工具能將我們解放出來呢油挥?有,答案就是Fastlane款熬。Fastlane是移動端App開發(fā)的腳本工具深寥。
使用Fastlane自動打包上傳的基本步驟如下:
1、安裝Fastlane -> 2贤牛、Fastlane初始化 -> 3惋鹅、配置證書和描述文件 -> 4、一鍵上傳

  • Fastlane的安裝有多種途徑殉簸,其中之一就是使用sudo gem install fastlane命令安裝闰集,其他途徑這里不再贅述沽讹,也是一行命令的事。

  • 可以用命令fastlane --version來查看是否安裝成功

  • 在項目中初始化fastlane 使用命令fastlane init返十,根據(jù)提示一步步進行即可妥泉。

  • 初始化成功之后,在項目根目錄下會多幾個文件洞坑,Appfile蘋果賬號信息盲链、Fastfile填寫打包腳本的地方(重點、重點迟杂、重點)刽沾。

  • 若使用蒲公英上傳,需安裝蒲公英插件排拷,在終端輸入fastlane add_plugin pgyer命令侧漓,即可安裝蒲公英的 fastlane 插件。

Fastlane打包腳本示例(使用Ruby語言)

default_platform(:iOS)

#蒲公英api_key和user_key
api_key = "your api_key"
user_key = "your user_key"
configuration = "Release"#Debug_Production_Server

platform :iOS do
  desc "??????????打包并上傳蒲公英????????"
  lane :pgybeta do
  #指定項目的scheme名稱
  scheme = "TuWanApp"
  #更新描述
  update_desc = "更新描述"
  gym(
    #輸出的ipa名稱
    output_name:"#{scheme}_#{Time.new.strftime("%Y%m%d%H%M")}",
    # 是否清空以前的編譯信息 true:是
    clean:true,
    scheme:scheme,
    # 指定打包方式监氢,Release 或者 Debug
    configuration:"#{configuration}",
    # 指定打包所使用的輸出方式布蔗,目前支持app-store, package, ad-hoc, enterprise, development
    export_method:"ad-hoc",
    # 指定輸出文件夾
    output_directory:"./build/pgy",
    )
  pgyer(api_key: "#{api_key}", user_key: "#{user_key}", update_description: "#{update_desc}")
  end
end

Jenkins + Fastlane配置

Jenkins shell腳本

#!/bin/bash
 
export LANG=en_US.UTF-8
 
export LANGUAGE=en_US.UTF-8
 
export LC_ALL=en_US.UTF-8
 
set -e
 
#輸出錯誤
function echo_error() {
    echo "?????????? $1 ??????????"
}
 
#輸出信息
function echo_info() {
    echo "==================== $1 ===================="
}

#輸出成功
function echo_success() {
    echo "?????????? $1 ??????????"
}

#json解析
function parseJson() {
    local json=$1
    local key=$2
    if [ "$key" == "code" ]; then
        echo "$json" | sed -E 's/,/\n/g' | grep -E "${key}" | sed -E 's/:/\n/g' | sed -E '1d' | sed -E 's/"http://g'
    else
        echo "$json" | tr "\n" " " | sed -E 's/.*"'"${key}"'" *: *"([^"]*)".*/\1/g' | sed -E 's/\\\//\//g'
    fi
}

#檢測IPA包是否上傳成功
function checkIPA() {
    echo_info "檢測應(yīng)用是否發(fā)布完成,并獲取發(fā)布應(yīng)用的信息"
    result=$(curl -D - --form-string "_api_key=$1" --form-string "buildKey=$2" "https://www.pgyer.com/apiv2/app/buildInfo")
    #解析數(shù)據(jù)浪腐,生成下載地址
    resultCode=$(parseJson "$result" "code")
    resultMessage=$(parseJson "$result" "message")
    resultBuildKey=$(parseJson "$result" "buildKey")
    buildShortcutUrl=$(parseJson "$result" "buildShortcutUrl") 
    buildQRCodeURL=$(parseJson "$result" "buildQRCodeURL")
    if [ "$resultCode" == 1246 ] || [ "$resultCode" == 1247 ]; then
        echo_info "code: $resultCode"
        echo_error "message: $resultMessage"
        sleep 5
        checkIPA $1 $2
    elif [ -n "$resultBuildKey" ]; then
        echo_success "上傳成功" 
        echo_info "二維碼地址=> ${buildQRCodeURL} <=二維碼地址"
        echo_info "app下載地址=> https://www.pgyer.com/${buildShortcutUrl} <=app下載地址"
    else
        echo_info "code: $resultCode"
        echo_error "message: $resultMessage"
        exit $resultCode
    fi
}
 
#打包
function packaging() {
    echo_info "開始打包"
    #參數(shù)校驗 $#  添加到Shell的參數(shù)個數(shù)
    if [ $# != "1" ] || ([ $1 != "debug" ] && [ $1 != "release" ] && [ $1 != "appleStore" ] && [ $1 != "appleStore_beta" ]); then
       echo_error '請指定打包類型:debug, release, appleStore, appleStore_beta'
       exit 1
    fi
 
    #定義一些符變量
    export v_env=$1
    export v_project_name="TuWanApp"
    export pgy_ipa_path="./build/pgy"
    export pgy_api_key="蒲公英API Key"
    export pgy_install_pwd="123456"
 
    echo_info "安裝pod依賴"
    #安裝pod依賴
    #rm -rf './Pods/Local Podspecs'
    #pod update
    pod install --repo-update
     
    #構(gòu)建
    echo_info "開始構(gòu)建"
    #fastlane打包
    if ([ -n "${version}" ] && [ -n "${build}" ]); then
    fastlane ${v_env} v:${version} b:${build}
    elif [ -n "${version}" ]; then
    fastlane ${v_env} v:${version}
    else
    fastlane ${v_env}
    fi
    
    #上傳二進制包到蒲公英
    if ([ $1 != "appleStore" ] && [ $1 != "appleStore_beta" ]); then
        echo_info "上傳二進制包到蒲公英"
        tokenResp=$(curl -D - --form-string "_api_key=${pgy_api_key}" --form-string 'buildType=ios' --form-string 'buildInstallType=2' --form-string "buildPassword=${pgy_install_pwd}" --form-string "buildUpdateDescription=${updateContent}" "https://www.pgyer.com/apiv2/app/getCOSToken")
        echo_info "獲取預(yù)上傳 url 和相關(guān)的簽名參數(shù)=${tokenResp}"
        echo_info "解析預(yù)上傳數(shù)據(jù)"
        code=$(parseJson "$tokenResp" "code")
        message=$(parseJson "$tokenResp" "message")
        key=$(parseJson "$tokenResp" "key")
        signature=$(parseJson "$tokenResp" "signature")
        xcossecuritytoken=$(parseJson "$tokenResp" "x-cos-security-token")
        endpoint=$(parseJson "$tokenResp" "endpoint")
        echo_info "預(yù)上傳數(shù)據(jù)解析結(jié)果"
        echo_info "code:$code"
        echo_info "message:$message"
        echo_info "key:$key"
        echo_info "signature:$signature"
        echo_info "xcossecuritytoken:$xcossecuritytoken"
        echo_info "endpoint:$endpoint"
        if [ "$code" != 0 ]; then
            echo_error "$message"
            exit $code
        else
            echo_info "開始上傳IPA文件"
            cd ${pgy_ipa_path}
            pgy_ipa_name=`ls -lt | grep .ipa | head -n 1 | awk '{print $9}'`
            echo_info "IPA文件名:${pgy_ipa_name}"
            resp=$(curl -D - --form-string "key=${key}" --form-string "signature=${signature}" --form-string "x-cos-security-token=${xcossecuritytoken}" -F "file=@${pgy_ipa_name}" ${endpoint})
            echo_info "pgy_resp=$resp"
            sleep 5
            checkIPA ${pgy_api_key} ${key}
        fi  
    fi
    
}
 
export FASTLANE_XCODEBUILD_SETTINGS_TIMEOUT=120
 
#執(zhí)行打包
packaging ${BuildType}

上傳符號表到bugly

if ([ "${BuildType}" == "appleStore" ] || [ "${BuildType}" == "appleStore_beta" ]); then
    cd /Users/tuwanmini/.jenkins/workspace/TuwanApp/build/appStore
    rm -rf dsym_tmp
    mkdir -p dsym_tmp
    filename=`ls -lt | grep .zip | head -n 1 | awk '{print $9}'`
    version=$(echo ${filename} | awk -F "_" '{print $2}')
    unzip ${filename} -d dsym_tmp

    cd /Users/tuwanmini/Desktop/buglyqq-upload-symbol/
    java -jar buglyqq-upload-symbol.jar -appid xxxxxxxxx -appkey xxxxxxxxx -bundleid xxxxxxxxx -version ${version} -platform IOS -inputSymbol /Users/tuwanmini/.jenkins/workspace/TuwanApp/build/appStore/dsym_tmp
else
    cd /Users/tuwanmini/.jenkins/workspace/TuwanApp/build/pgy
    rm -rf dsym_tmp
    mkdir -p dsym_tmp
    filename=`ls -lt | grep .zip | head -n 1 | awk '{print $9}'`
    version=$(echo ${filename} | awk -F "_" '{print $2}')
    unzip ${filename} -d dsym_tmp

    cd /Users/tuwanmini/Desktop/buglyqq-upload-symbol/
    java -jar buglyqq-upload-symbol.jar -appid xxxxxxxxx -appkey xxxxxxxxx -bundleid xxxxxxxxx -version ${version} -platform IOS -inputSymbol /Users/tuwanmini/.jenkins/workspace/TuwanApp/build/pgy/dsym_tmp
fi

發(fā)送釘釘消息

注:使用非text消息類型纵揍,需在消息內(nèi)容中添加"@xxx"的文本內(nèi)容,@功能才能生效

#!/bin/bash

# 定義釘釘機器人 webhook 地址和 access_token
webhook_url="你的釘釘機器人webhook"
appName="你的應(yīng)用名稱"
downloadShortUrl="你的蒲公英下載地址短連接"
installPSW="你的蒲公英安裝密碼"
atUserMobiles="@你要艾特的釘釘用戶手機號"
# 定義消息內(nèi)容
pgyContent="### ${appName} \
\n版本號: ${version} (${build})\n \
\n更新內(nèi)容:\n \
\n${updateContent}\n \
\n下載地址: https://www.pgyer.com/${downloadShortUrl}\n \
\n安裝密碼: ${installPSW}\n \
\n![掃碼下載](https://www.pgyer.com/app/qrcode/${downloadShortUrl})\n \
\n${atUserMobiles}"

appStoreContent="### ${appName}提審包上傳成功 \
\n版本號:${version}(${build})\n \
\n${atUserMobiles}"

releaseBateContent="### ${appName}隊長試用包上傳成功 \
\n版本號:${version}(${build})\n \
\n${atUserMobiles}"

if [ "${BuildType}" == "appleStore" ]; then
# 發(fā)送 HTTP 請求议街,向釘釘機器人發(fā)送報警消息
curl ${webhook_url} \
-H 'Content-Type: application/json' \
-d "{\"at\": {\"atMobiles\": [${atUserMobile}],\"isAtAll\": false},\"msgtype\": \"markdown\",\"markdown\": {\"title\": \"${appName}\", \"text\": \"${appStoreContent}\"}}"
elif [ "${BuildType}" == "appleStore_beta" ]; then
# 發(fā)送 HTTP 請求泽谨,向釘釘機器人發(fā)送報警消息
curl ${webhook_url} \
-H 'Content-Type: application/json' \
-d "{\"at\": {\"atMobiles\": [${atUserMobile}],\"isAtAll\": false},\"msgtype\": \"markdown\",\"markdown\": {\"title\": \"${appName}\", \"text\": \"${releaseBateContent}\"}}"
else
# 發(fā)送 HTTP 請求,向釘釘機器人發(fā)送報警消息
curl ${webhook_url} \
-H 'Content-Type: application/json' \
-d "{\"at\": {\"atMobiles\": [${atUserMobile}],\"isAtAll\": false},\"msgtype\": \"markdown\",\"markdown\": {\"title\": \"${appName}\", \"text\": \"${pgyContent}\"}}"
fi

構(gòu)建打包任務(wù)

選擇已創(chuàng)建的任務(wù)

image-20220605201225547.png

選擇Build with Parameters -> 分支 -> 開始構(gòu)建特漩。

image-20220605201319733.png

查看控制臺輸出吧雹。

image-20220605202754537.png

補充說明

找不到/ExportOptions/XXXExportOptions.plist文件

  • AdHocExportOptions.plist,AppStoreExportOptios.plist涂身,EnterpriseExportOptions.plist雄卷,DevelopmentExportOptions.plist文件可通過Xcode -> Product -> Archive打包生成的ExportOptions.plist文件添加相應(yīng)前綴重命名即可獲得。
  • 在Jenkins -> workspace -> 項目根目錄下新建名為ExportOptions的文件夾蛤售,將相應(yīng)plist文件拷貝到改文件夾中即可龙亲。
image-20220605204922279.png

證書及描述文件

在Xcode中配置完成即可。

大文件拉取git-lfs報錯

  1. 運行brew install git-lfs即可安裝

  2. 若不再需要大文件支持悍抑,可前往項目根目錄 -> .git -> hooks -> 移除post-checkout文件即可鳄炉。

拉取代碼出錯

cd到Jenkins -> workspace目錄下,手動通過git clone將工程clone到本地搜骡,重啟Jenkins拂盯,再次執(zhí)行打包任務(wù)。

局域網(wǎng)使用IP訪問jenkins

使用brew安裝jenkins會避免很多其他安裝方式產(chǎn)生的用戶權(quán)限問題记靡,但是會將httpListenAddress默認設(shè)置為127.0.0.1谈竿,這樣我們雖然可以在本地用localhost:8080訪問团驱,但是本機和局域網(wǎng)均無法用ip訪問。

解決辦法為修改兩個路徑下的plist配置:

  • ~/Library/LaunchAgents/homebrew.mxcl.jenkins.plist
  • /usr/local/opt/jenkins/homebrew.mxcl.jenkins.plist

第二個路徑找不到的話空凸,cmd + shift + G 輸入/opt/homebrew/opt/jenkins嚎花,找到homebrew.mxcl.jenkins.plist

將上面兩個plist中的httpListenAddress后的ip地址,修改為本機IP或者0.0.0.0呀洲,修改完成后紊选,重啟Jenkins,接下來就可以使用IP訪問了道逗。

fastlane版本升級注意事項

  1. 需要修改項目中Gemfile文件中的fastlane版本為當前要升級的版本兵罢;
  2. cd到項目目錄下,執(zhí)行bundle install滓窍,重新安裝您的 gem 依賴卖词;
  3. 再次運行 bundle update fastlane ;
  4. 重啟電腦吏夯,環(huán)境變量生效此蜈;
  5. 若Jenkins中的fastlane版本仍然沒有升級到最新,可以使用Jenkins新建任務(wù)的方式噪生,在新建任務(wù)中添加構(gòu)建shell腳本裆赵,執(zhí)行如下代碼:
echo 你的電腦密碼 | sudo -S bundle update fastlane
fastlane --version
  1. 若版本仍然沒有更新,請前往ruby安裝目錄下杠园,卸載fastlane其他舊版本顾瞪,只留新版本即可舔庶。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末抛蚁,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子惕橙,更是在濱河造成了極大的恐慌瞧甩,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,183評論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件弥鹦,死亡現(xiàn)場離奇詭異肚逸,居然都是意外死亡,警方通過查閱死者的電腦和手機彬坏,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,850評論 3 399
  • 文/潘曉璐 我一進店門朦促,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人栓始,你說我怎么就攤上這事务冕。” “怎么了幻赚?”我有些...
    開封第一講書人閱讀 168,766評論 0 361
  • 文/不壞的土叔 我叫張陵禀忆,是天一觀的道長臊旭。 經(jīng)常有香客問我,道長箩退,這世上最難降的妖魔是什么离熏? 我笑而不...
    開封第一講書人閱讀 59,854評論 1 299
  • 正文 為了忘掉前任,我火速辦了婚禮戴涝,結(jié)果婚禮上滋戳,老公的妹妹穿的比我還像新娘。我一直安慰自己喊括,他們只是感情好胧瓜,可當我...
    茶點故事閱讀 68,871評論 6 398
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著郑什,像睡著了一般府喳。 火紅的嫁衣襯著肌膚如雪跨琳。 梳的紋絲不亂的頭發(fā)上箕宙,一...
    開封第一講書人閱讀 52,457評論 1 311
  • 那天务甥,我揣著相機與錄音卵蛉,去河邊找鬼能曾。 笑死髓介,一個胖子當著我的面吹牛榕暇,可吹牛的內(nèi)容都是我干的蛆封。 我是一名探鬼主播剃法,決...
    沈念sama閱讀 40,999評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼碎捺,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了贷洲?” 一聲冷哼從身側(cè)響起收厨,我...
    開封第一講書人閱讀 39,914評論 0 277
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎优构,沒想到半個月后诵叁,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,465評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡钦椭,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,543評論 3 342
  • 正文 我和宋清朗相戀三年拧额,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片彪腔。...
    茶點故事閱讀 40,675評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡侥锦,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出德挣,到底是詐尸還是另有隱情恭垦,我是刑警寧澤,帶...
    沈念sama閱讀 36,354評論 5 351
  • 正文 年R本政府宣布,位于F島的核電站署照,受9級特大地震影響祸泪,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜建芙,卻給世界環(huán)境...
    茶點故事閱讀 42,029評論 3 335
  • 文/蒙蒙 一没隘、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧禁荸,春花似錦右蒲、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,514評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至映砖,卻和暖如春间坐,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背邑退。 一陣腳步聲響...
    開封第一講書人閱讀 33,616評論 1 274
  • 我被黑心中介騙來泰國打工竹宋, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人地技。 一個月前我還...
    沈念sama閱讀 49,091評論 3 378
  • 正文 我出身青樓蜈七,卻偏偏與公主長得像,于是被迫代替她去往敵國和親莫矗。 傳聞我的和親對象是個殘疾皇子飒硅,可洞房花燭夜當晚...
    茶點故事閱讀 45,685評論 2 360

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