iOS-自动化打包方案


 

树下敲代码的超人 关注

 2.3 2018.03.01 16:53* 字数 776 阅读 8200评论 22喜欢 62

技 术 文 章 / 超 人

自动化打包方案1:xcodebuild打包

使用xcodebuild自动化打包给我的感觉就是用命令行来控制Xcode进行编译打包,xcodebuild打包有3种,一种是adHoc测试打包ipa,一个是上传AppStore打包,一个企业级别打包。具体区别请看下面的步骤中说明

了解xcodebuild打包先从终端操作开始,废话不多说,撸起袖子干。

步骤1:

首先打开终端 cd到当前工程目录

步骤2:

在终端中输入一下内容来完成编译

xcodebuild archive -workspace XXX.xcworkspace -scheme XXX -configuration Release -archivePath ./myArchivePath CONFIGURATION_BUILD_DIR ./dir ODE_SIGN_IDENTITY=证书 PROVISIONING_PROFILE=描述文件UUID

key说明

-workspace XXX.xcworkspaceXXX.xcworkspace需要编译工程的工作空间名称,如果工程不是.xcworkspace的,可以不需要-workspace XXX.xcworkspace这段话

-scheme XXXXXX是工程名称,-scheme XXX是指定构建工程的名称

-configuration Release填入打包的方式是Debug或Release,就跟在Xcode中编译前需要在Edit scheme的Build configuration中选择打出来的包是Debug还是Release包一样,-configuration就是配置编译的Build configuration

-archivePath ./myArchivePath配置生成.xcarchive的路径, ./表示生成在当前目录下,myArchivePath是生成的.Archive文件名称

ODE_SIGN_IDENTITY=证书配置打包的指定证书,如果该工程的Xcode已经配置好了证书,那么不加入这段话也可以,打包出来的证书就是Xcode中配置好的。

PROVISIONING_PROFILE=描述文件UUID配置打包的描述文件,同上,Xcode已经配置好了就不用在填入这段话了

CONFIGURATION_BUILD_DIR配置编译文件的输出路径,如果需要用到.xcarchive文件内部的dSYM等文件,可以使用改字段指定输出路径。

如果工程是勾选了Automatically manage signing,那么就不用在配置ODE_SIGN_IDENTITY和PROVISIONING_PROFILE

Automatically manage signing

获取证书名:

在电脑钥匙串的证书查找需要使用的证书

获取描述文件UUID:

首先在Finder中前往/Users/用户名/Library/MobileDevice/Provisioning Profiles路径

前往

然后在Provisioning Profiles文件夹中选择打包需要的描述文件,一般描述文件的文件名都是由UUID组成的,如果文件名不是由UUID组成的,可以在终端中cd到Provisioning Profiles路径,输入/usr/bin/security cms -D -i 23ec4472-bfc4-4128-a96c-2018021f52f3.mobileprovision,23ec4472-bfc4-4128-a96c-2018021f52f3.mobileprovision是描述文件的名称,然后在终端的解析结果中找到UUID

描述文件

在终端中解析描述文件,查找UUID

输入步骤2的命令后,终端会输出十多秒,在你指定的路径下生成.xcarchive文件

.xcarchive文件

右键点击.xcarchive文件的显示包内容,其中包含dSYM和app

.xcarchive文件内容

步骤3:配置打包的ExportOptions.plist文件

可以在任意一个Xcode工程中新建一个ExportOptions.plist文件。adHoc和AppStore的配置文件内容不一样

image.png

adHoc的配置内容

<?xml version="1.0"encoding="UTF-8"?><!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">compileBitcode//是否编译bitcodemethodad-hoc/provisioningProfiles文件bundle idAdhoc_IDsigningCertificate//证书签名这里填证书签名signingStylemanualstripSwiftSymbolsteamIDAANCCUK4M3//TeamIDthinning<none>

adHoc的配置内容

App Store配置内容:

<?xml version="1.0"encoding="UTF-8"?><!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">methodapp-storeprovisioningProfiles这里填应用的bundle id这里填对应budle id的证书名signingCertificate这里填签名证书signingStylemanualstripSwiftSymbolsteamID这里是teamIDuploadBitcodeuploadSymbols

App Store配置内容

如果实在不知道plist文件如何写,可以自己先手动打一个AdHoc和Appstore的包,生成的文件中就会有一个ExportOptions.plist文件,看看里面的区别,也可以拿过来直接用。

步骤4:工程路径下的终端中输入xcodebuild -exportArchive -archivePath ./myArchivePath.xcarchive -exportOptionsPlist ./ExportOptions.plist -exportPath ./out ,这一步实际就是Xcode编译号后在工程中勾选打包的选择。只是用配置文件的方式来抵销手动选择。

key说明

-archivePath ./myArchivePath.xcarchive指定需要打包的.xcarchive路径,./myArchivePath.xcarchive表示在当前终端路径下的myArchivePath.xcarchive文件

-exportOptionsPlist ./ExportOptions.plist指定打包需要的ExportOptions.plist配置文件路径

-exportPath ./out指定打包输出的路径, ./out表示打包结果输出在终端的当前路径下的out文件家中。如果没有out文件夹会自动创建一个

这样就完成了终端里的打包。

当你明白打包的具体步骤后,我们就可以把上面的步骤放在shell脚本中,然后在终端用一个命令来执行shell脚本来实现自动打包,更加简便。

制作脚本:

首先:cd到需要自动打包的工程下

然后:在终端中输入touch xcodebuild.sh创建xcodebuild.sh脚本文件

然后:双击打开脚本写入下面 脚本内容(请确保所有版本的plist配置文件都写好了)

最后:在终端中输入 ./xcodebuild.sh运行脚本,按照步骤完成打包选择(如果运行的时候出现Permission denied,请先在终端中执行chmod a+x *.文件的后缀名 后,在运行。

)

脚本内容

#author by heyujia#脚本所在的目录必须和WorkSpace或者说工程主目录所在的目录在同一个目录层级中#配置参数#workspace的名字,如果没有则不需要这段话,我测试工程不是一个工作空间所以就没用,如果需要用的话,还需要在编译阶段的脚本代码里加上 -workspace ${Project_Name}.xcworkspace#Workspace_Name="WorkSpace名字"#工程名字Project_Name="Demo"#配置打包方式Release或者DebugConfiguration="Release"#基础主路径BUILD_PATH=./build#不同版本的基础子路径#adHocADHOC_PATH=${BUILD_PATH}/adHoc#appStoreAPPSTORE_PATH=${BUILD_PATH}/appStore#enterpriseENTERPRISE_PATH=${BUILD_PATH}/enterprise#配置编译文件的存放地址#adHocCONFIGURATION_BUILD_PATH_ADHOC=${ADHOC_PATH}/${Configuration}-iphoneos#appStoreCONFIGURATION_BUILD_PATH_APPSTORE=${APPSTORE_PATH}/${Configuration}-iphoneos#企业CONFIGURATION_BUILD_PATH_ENTERPRISE=${ENTERPRISE_PATH}/${Configuration}-iphoneos#下面是获取上传到第三方统计崩溃日志的文件,如果没有用到可以删除该部分#adHocDSYM_PATH_ADHOC=${CONFIGURATION_BUILD_PATH_ADHOC}/${Project_Name}.app.dSYMDSYM_ZIP_PATH_ADHOC=${ADHOC_PATH}/${Project_Name}.app.dSYM.zipDSYM_COPY_PATH_ADHOC=${ADHOC_PATH}/copy#appStoreDSYM_PATH_APPSTORE=${CONFIGURATION_BUILD_PATH_APPSTORE}/${Project_Name}.app.dSYMDSYM_ZIP_PATH_APPSTORE=${APPSTORE_PATH}/${Project_Name}.app.dSYM.zipDSYM_COPY_PATH_APPSTORE=${APPSTORE_PATH}/copy#企业#appStoreDSYM_PATH_ENTERPRISE=${CONFIGURATION_BUILD_PATH_ENTERPRISE}/${Project_Name}.app.dSYMDSYM_ZIP_PATH_ENTERPRISE=${ENTERPRISE_PATH}/${Project_Name}.app.dSYM.zipDSYM_COPY_PATH_ENTERPRISE=${ENTERPRISE_PATH}/copy#配置打包结果输出的路径#AdHoc版本AdHocPrijectOutPath=${ADHOC_PATH}/adHocOut#AppStore版本AppStorePrijectOutPath=${APPSTORE_PATH}/appStoreOut#企业版本EnterprisePrijectOutPath=${ENTERPRISE_PATH}/enterpriseOut#如果工程中配置了Automatically manage signing,那么就不需要证书名和描述文件名,请确保工程中配置的证书名和描述文件是你打包想要用的#ADHOC证书名#描述文件ADHOCCODE_SIGN_IDENTITY="iPhone Distribution: xxxx"ADHOCPROVISIONING_PROFILE_NAME="xxxxx-xxxx-xxxx-xxxx-xxxxxx"#AppStore证书名#描述文件APPSTORECODE_SIGN_IDENTITY="iPhone Distribution: xxxxx"APPSTOREROVISIONING_PROFILE_NAME="xxxxx-xxxx-xxxx-xxxx-xxxxxx"#企业(enterprise)证书名#描述文件ENTERPRISECODE_SIGN_IDENTITY="iPhone Distribution: xxxx"ENTERPRISEROVISIONING_PROFILE_NAME="xxxxx-xxxx-xxx-xxxx"#加载各个版本的plist文件,为了实现一个脚本打包所有版本,这里对不同对版本创建了不同的plist配置文件。等号后面是文件路径,一般把plist文件放在与脚本同一级别的文件层中。我这里测试用所以plist文件都一样,实际使用是请分开配置为不同文件ADHOCExportOptionsPlist="./ExportOptions.plist"AppStoreExportOptionsPlist="./ExportOptions.plist"EnterpriseExportOptionsPlist="./ExportOptions.plist"#在终端中提示 根据输入的序号不同,打包成不同版本的ipaecho"~~~~~~~~~~~~选择打包方式(输入序号)~~~~~~~~~~~~~~~"echo"  1 adHoc"echo"  2 AppStore"echo"  3 Enterprise"# 读取用户在终端中输入并存到变量里readparametersleep 0.5method="$parameter"# 判读用户是否有输入if[ -n"$method"]thenif["$method"="1"]then#这里都执行命令中是在xcworkspace工程中执行的,如果工程不是xcworkspace,可以把-workspace的内容删掉,加入了证书和描述文件,如果不需要请删除#如果用户选择的是1,就执行adhoc脚本#首先清除原来的文件夹rm -rf${BUILD_PATH}#创建文件夹,路径需要一层一层创建,不然会创建失败mkdir${BUILD_PATH}mkdir${ADHOC_PATH}#编译文件mkdir${CONFIGURATION_BUILD_PATH_ADHOC}#打包输出的文件mkdir${AdHocPrijectOutPath}#copymkdir${DSYM_COPY_PATH_ADHOC}#编译xcodebuild archive -scheme$Project_Name-configuration$Configuration-archivePath${ADHOC_PATH}/$Project_Name-adhoc.xcarchive CONFIGURATION_BUILD_DIR=${CONFIGURATION_BUILD_PATH_ADHOC}#如果需要单独设置证书和描述文件需要在编译时加入下面这句话#CODE_SIGN_IDENTITY="${ADHOCCODE_SIGN_IDENTITY}" PROVISIONING_PROFILE="${ADHOCPROVISIONING_PROFILE_NAME}" PRODUCT_BUNDLE_IDENTIFIER="${AdHocBundleID}"#打包xcodebuild  -exportArchive -archivePath${ADHOC_PATH}/$Project_Name-adhoc.xcarchive -exportOptionsPlist$ADHOCExportOptionsPlist-exportPath${AdHocPrijectOutPath}# 选择是压缩还是复制,复制的方法可以用于上传文件到指定服务器,比如上传到git# zip -r 目标路径  源文件路径 ,开始压缩dSYM文件zip -r${DSYM_ZIP_PATH_ADHOC}${DSYM_PATH_ADHOC}# cp -r 源文件路径 目标路径 , 把压缩包拷贝到指定地方cp -r${DSYM_ZIP_PATH_ADHOC}${DSYM_COPY_PATH_ADHOC}# 复制 dSYM 包里的 XXX 文件 上传到指定的地方#TARGET_PATH=${DSYM_COPY_PATH}/${TARGET}.app.dSYM/Contents/Resources/DWARF/${TARGET}#cp ${TARGET_PATH} ${EXPORT_DIR}elif["$method"="2"]then#如果用户选择的是2,就执行appstore脚本#首先清除原来的文件夹rm -rf${BUILD_PATH}#创建文件夹,路径需要一层一层创建,不然会创建失败mkdir${BUILD_PATH}mkdir${APPSTORE_PATH}#编译文件mkdir${CONFIGURATION_BUILD_PATH_APPSTORE}#打包输出的文件mkdir${AppStorePrijectOutPath}#copymkdir${DSYM_COPY_PATH_APPSTORE}xcodebuild archive -scheme$Project_Name-configuration$Configuration-archivePath${APPSTORE_PATH}/$Project_Name-appstore.xcarchive CONFIGURATION_BUILD_DIR=${CONFIGURATION_BUILD_PATH_APPSTORE}xcodebuild  -exportArchive -archivePath${APPSTORE_PATH}/$Project_Name-appstore.xcarchive -exportOptionsPlist$AppStoreExportOptionsPlist-exportPath${AppStorePrijectOutPath}#创建copy文件夹mkdir${DSYM_COPY_PATH_APPSTORE}# 选择是压缩还是复制,复制的方法可以用于上传文件到指定服务器,比如上传到git# zip -r 目标路径  源文件路径 ,开始压缩dSYM文件zip -r${DSYM_ZIP_PATH_APPSTORE}${DSYM_PATH_APPSTORE}# cp -r 源文件路径 目标路径 , 把压缩包拷贝到指定地方cp -r${DSYM_ZIP_PATH_APPSTORE}${DSYM_COPY_PATH_APPSTORE}#验证ipa是否打包成功if[ -e$AppStorePrijectOutPath/$Project_Name.ipa ];thenecho'----ipa包已生成----'open$AppStorePrijectOutPathecho'----打包ipa完成----'echo'**---------------**'echo'****开始发布ipa包****'echo'**---------------**'#验证后上传到App Store# 将-u 后面的XXX替换成自己的AppleID的账号,-p后面的XXX替换成自己的密码altoolPath="/Applications/Xcode.app/Contents/Applications/Application Loader.app/Contents/Frameworks/ITunesSoftwareService.framework/Versions/A/Support/altool""$altoolPath"--validate-app -f${exportIpaPath}/${scheme_name}.ipa -u XXX -p XXX -t ios --output-format xml"$altoolPath"--upload-app -f${exportIpaPath}/${scheme_name}.ipa -u  XXX -p XXX -t ios --output-format xmlelseecho'----ipa包导出失败----'fielif["$method"="3"]then#如果用户选择的是3,就执行企业脚本#首先清除原来的文件夹rm -rf${BUILD_PATH}#创建文件夹,路径需要一层一层创建,不然会创建失败mkdir${BUILD_PATH}mkdir${ENTERPRISE_PATH}#编译文件mkdir${CONFIGURATION_BUILD_PATH_ENTERPRISE}#打包输出的文件mkdir${EnterprisePrijectOutPath}#copymkdir${DSYM_COPY_PATH_ENTERPRISE}xcodebuild archive -scheme$Project_Name-configuration$Configuration-archivePath${ENTERPRISE_PATH}/$Project_Name-enterprise.xcarchive CONFIGURATION_BUILD_DIR=${CONFIGURATION_BUILD_PATH_ENTERPRISE}xcodebuild  -exportArchive -archivePath${ENTERPRISE_PATH}/$Project_Name-enterprise.xcarchive -exportOptionsPlist$EnterpriseExportOptionsPlist-exportPath${EnterprisePrijectOutPath}#创建copy文件夹mkdir${DSYM_COPY_PATH_ENTERPRISE}# 选择是压缩还是复制,复制的方法可以用于上传文件到指定服务器,比如上传到git# zip -r 目标路径  源文件路径 ,开始压缩dSYM文件zip -r${DSYM_ZIP_PATH_ENTERPRISE}${DSYM_PATH_ENTERPRISE}# cp -r 源文件路径 目标路径 , 把压缩包拷贝到指定地方cp -r${DSYM_ZIP_PATH_ENTERPRISE}${DSYM_COPY_PATH_ENTERPRISE}# 将 dSYM 包里的 demo 文件 上传到指定地点 TARGET_PATH=${DSYM_COPY_PATH}/${Project_Name}.app.dSYM/Contents/Resources/DWARF/${Project_Name}cp${TARGET_PATH}${EXPORT_DIR}else#如果是其他输入,则在终端中提示参数无效并退出echo"参数无效...."exit1fifi

注意:在写脚本的时候,要注意=号的前后不要有空格,我写OC代码风格都是喜欢 =号 前后空格,这样间距美观。但是脚本里如果有空格会造成命令无效。

自动化打包方案2

...未完待续

谈钱伤感情,不要花钱打赏,如果觉得文章对您真的有帮助,请评论一句就好,这样我才知道有您的支持。谢谢

赞赏支持

 日记本

© 著作权归作者所有

举报文章

关注树下敲代码的超人 

写了 79685 字,被 283 人关注,获得了 653 个喜欢

不论成败,坚信自己。 风里雨里 成都 欢迎大家~

喜欢


62

更多分享

22条评论 只看作者

按时间倒序按时间正序


king锋

7楼 · 2019.01.16 14:27

基本的套路会了,有个使用scheme打包的问题。项目目前配置了多个scheme进行区分如"dev环境 ,test环境 ,release环境",打包的时候使用"-scheme xxxx" 命令 但是打出的包 全部是release环境的 请各位大佬不吝赐教!

  回复

king锋

 补充下 比如当前的scheme是"xxxx_dev",打包命令行指定的是"-scheme xxxx_dev" 是与项目配置的scheme一一对应的

2019.01.16 14:29  回复

 添加新评论


谭瞎

6楼 · 2018.12.05 21:37

真好

  回复


小米咸鱼

5楼 · 2018.12.03 13:50

ld: library not found for -lAFNetworking

clang: error: linker command failed with exit code 1 (use -v to see invocation)

** ARCHIVE FAILED **

这个怎么解决呀?

  回复

树下敲代码的超人

 @小米咸鱼 你看看项目AFNetworking有没有问题。路径等等,具体情况你要自己查。这个不是脚本的问题哈

2018.12.03 16:33  回复

小米咸鱼

 @树下敲代码的超人 Xcode运行,打包都正常

2018.12.03 16:35  回复

emp_heng

 @小米咸鱼 最后你是怎么解决了的呢,遇到同样的问题,排查中

2019.02.13 17:36  回复

 添加新评论


我的水杯快到了

4楼 · 2018.11.15 11:40

请问ExportOptions.plist文件怎么创建啊?需要创建AdHOC和App Store两个吗?把这创建的plist文件放在哪个位置呢?

  回复


lxiaokai

3楼 · 2018.11.14 11:48

方案2呢(手动滑稽)

  回复

树下敲代码的超人

 @lxiaokai 很久没研究自动化打包了。目前想到的其他方案跟方案1是一样的,都是通过脚本操作,只是脚本内容不一样。所以也就没加上去。等想到了一种不一样的方案再加上去。

2018.11.14 13:48  回复

lxiaokai

 @树下敲代码的超人 xcodebuild -exportArchive -archivePath ./myArchivePath.xcarchive -exportOptionsPlist ./ExportOptions.plist -exportPath ./out

2018-11-14 18:44:16.452 xcodebuild[60949:3979019] [MT] IDEDistribution: -[IDEDistributionLogging _createLoggingBundleAtPath:]: Created bundle at path '/var/folders/r8/g1wdb7h16s11rs_vhczw6fs80000gp/T/gfss_2018-11-14_18-44-16.451.xcdistributionlogs'.

error: exportArchive: The data couldn’t be read because it isn’t in the correct format.

Error Domain=NSCocoaErrorDomain Code=3840 "No value." UserInfo={NSDebugDescription=No value., NSFilePath=/var/folders/r8/g1wdb7h16s11rs_vhczw6fs80000gp/T/ipatool-json-filepath-H6ZbZD}

** EXPORT FAILED **

遇到过吗?上网说关闭bitcode也不行,但是同样的脚本,给我同事使用就可以打包出来

2018.11.14 18:49  回复

我的水杯快到了

 @lxiaokai Couldn't load -exportOptionsPlist: The data couldn’t be read because it isn’t in the correct format.我遇见的是报这个错,不知道怎么创建ExportOptions.plist文件,求大佬帮助

2018.11.15 11:50  回复

 添加新评论 还有9条评论, 展开查看


路过你的全部

2楼 · 2018.04.28 14:08

总结的很好,楼主有时间可以了解下Jenkins自动打包,他可以帮我们做很多事,没这么麻烦,最近研究了下,XCODE新版本不允许jenkins读钥匙圈了,就换成了你上面的那两句终端命令,有点类似!

  回复

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,294评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,493评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,790评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,595评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,718评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,906评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,053评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,797评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,250评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,570评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,711评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,388评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,018评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,796评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,023评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,461评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,595评论 2 350

推荐阅读更多精彩内容