flutter build aar 及一键打包aar并上传Maven私服(阿里云效)

引子

盼望着,盼望着,华为鸿蒙升级了。�Harmoney OS Next 不再支持 Android 系统了,整个移动端瞬间炸了锅。
作为一个曾经手机市场王者,任何一家公司都无法忽视华为的市场,Android 阵营瞬间就出现了学习鸿蒙系统的激进力量。
同时,怕步子超大了,Harmoney OS Next 还是支持 flutter 跨端技术的。鉴于我一直时做跨端开发的,无论是 React Native、小程序,还是阿里系的 mist、weex 都多少有所涉猎,也就直接把三年前的 flutter 代码拉出来遛遛了:https://github.com/hongyi0609/MeiTuanEdwin
与此同时,也顺手建立另一个 my_fluter 库,把MeiTuanEdwin项目中 my_flutter 模块单独抽出来了:
https://github.com/hongyi0609/my_flutter
弄得有其他人合作开发似的~

背景

大家对郭霖大佬都不陌生吧?!
前一段我刷他的文章,发现他在研究 flutter,然后还分享了一篇读者投稿。以此得知,微软未来会有很多的 flutter 的业务,并且大部分处于起步阶段。所以郭老师就开始之前很少关注的 flutter 研究生涯。
这不是众里寻她千百度,蓦然回首,那人却在微信公众号吗?
我也就立马着手继续自己的 flutter 研究之路了。如果你对 flutter 一知半解,那我劝你先去读读这篇文章:https://juejin.cn/post/7306579782853754932
跟你想的基本一致,我就是读这篇文章的时候发现,搭建环境根本不是那么回事儿。

八卦:大厂是怎么玩儿的?

很多有电商味道的大厂,都是精细化操作,整个App 采用组件化开发,每个组件都是一个单独的小组 客户端+前端,其中客户端由 Android+iOS+跨端(外包)+小程序+h5组成,前端由小程序+h5+web 组成,这样整个大前端团队就形成了。
现在开始卷,客户端团队的架构组经过无数次的迭代重构,终于在 325 下来之前,憋出了 taro 、uniapp或者 morjs。由于实现了多端一体化,开发一次代码的成果可以在 Android、iOS、小程序、Web 端等所有平台运行,那么写跨端(外包)的同学就愉快的下岗了。同时,原来的小程序开发时微信、抖音、支付宝,每个平台一个主要负责同学,现在另外两个同学只要不去劳动局都好商量。
你可能会问,剩余的技术空缺谁来补上。听好啊:
下一年的 KPI主要是两个方向,
1. 客户端同学除了会用 Android、iOS 开发,也要具备跨端开发的能力,并且要互为 backup
2. 前端同学除了自己负责B 端开发业务,也要能够胜任微信小程序各平台的开发工作
基于第一点,我愉快的玩起了 flutter 。

进入Flutter篇

工欲善其事,必先利其器。按照Flutter 开发过程主要是依赖 aar包开发,和依赖源码开发,官方链接:https://docs.flutter.dev/add-to-app/android/project-setup?tab=with-android-studio
通过照猫画虎的骚操作后,发现根本没有卵用,官方给的文档毕竟玩不转。那么问题来了,你通常时怎么使用 aar 包的?

打包

官方打包
flutter build aar
你想要的打包命令
flutter clean & flutter build aar --build-number 1.1.1 --no-profile --no-release

profile解释:在 Android 开发中,"profile" 版本通常指的是应用的性能分析版本。这种版本允许开发者在设备上运行应用程序以进行性能分析和调试。这种版本通常会包含更多的日志记录和性能分析工具,用于分析应用在不同设备上的性能表现,并检测可能存在的性能问题。
Flutter 构建过程中的 --no-profile 标志会指示系统不生成这种性能分析版本。这样做是为了加快构建过程并减小生成的输出文件大小。
通过以上命令,你会在“/your_flutter/build/host/outputs/repo”目录下拿到 aar包,如下:

flutter_debug.aar

那么,aar包怎么用呢?

aar 包导入推荐策略

策略一:官方推荐导入

官方 aar 导入

配置完成你会发现,flutter 引擎的依赖依赖项一个也拿不到,就是下面这个几个货:
flutter 引擎依赖项

你肯定觉得卧槽了:
是的,兵法上没写,但是打仗的时候要用。马谡就是这么丢街亭的~
那《兵法》上咋说的呢?
兵法上说,你按照我说的运行完 flutter build aar 命令,配置你的 project 依赖绝对没问题。看看这句无耻的话:
兵法云

说无耻,其实有些过分了。毕竟人家写兵法的人言简意深来着~
First of all,这兵法主要是给资本主义市场使用的
Secondly,为了迎合中国市场,也告诉你要配置中国镜像

中国市场

问题就在这里,切换镜像之后,flutter 模块的打包aar的依赖没有被上传到指定的服务端。
墙外:
执行flutter build aar 后,在'/Users/edwin/edwin/MeiTuanEdwin/my_flutter/build/host/outputs/repo文件夹下面会生成aar包和pom文件,后者标示了flutter引擎依赖项,如下:
repo目录信息

flutter_debug-1.1.4.pom文件里存放的是依赖项信息,如下:

<?xml version="1.0" encoding="UTF-8"?>
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <!-- This module was also published with a richer model, Gradle metadata,  -->
  <!-- which should be used instead. Do not delete the following line which  -->
  <!-- is to indicate to Gradle or any Gradle module metadata file consumer  -->
  <!-- that they should prefer consuming it instead. -->
  <!-- do_not_remove: published-with-gradle-metadata -->
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.example.my_flutter</groupId>
  <artifactId>flutter_debug</artifactId>
  <version>1.1.4</version>
  <packaging>aar</packaging>
  <dependencies>
    <dependency>
      <groupId>io.flutter</groupId>
      <artifactId>flutter_embedding_debug</artifactId>
      <version>1.0.0-9064459a8b0dcd32877107f6002cc429a71659d1</version>
      <scope>compile</scope>
    </dependency>
    <dependency>
      <groupId>io.flutter</groupId>
      <artifactId>armeabi_v7a_debug</artifactId>
      <version>1.0.0-9064459a8b0dcd32877107f6002cc429a71659d1</version>
      <scope>compile</scope>
    </dependency>
    <dependency>
      <groupId>io.flutter</groupId>
      <artifactId>arm64_v8a_debug</artifactId>
      <version>1.0.0-9064459a8b0dcd32877107f6002cc429a71659d1</version>
      <scope>compile</scope>
    </dependency>
    <dependency>
      <groupId>io.flutter</groupId>
      <artifactId>x86_64_debug</artifactId>
      <version>1.0.0-9064459a8b0dcd32877107f6002cc429a71659d1</version>
      <scope>compile</scope>
    </dependency>
    <dependency>
      <groupId>io.flutter</groupId>
      <artifactId>x86_debug</artifactId>
      <version>1.0.0-9064459a8b0dcd32877107f6002cc429a71659d1</version>
      <scope>compile</scope>
    </dependency>
  </dependencies>
</project>

这些都是Gradle发布的Meta data,最终被发布到storage.googleapis.com 这个服务端,下面的链接试试
https://storage.googleapis.com/download.flutter.io
上面提到flutter库在编译是就已经做了架构适配和引擎依赖构建,原则上这些元数据都应该被发布到服务端,这样才能通过dependencies配置依赖获取相应的aar包,事实上根本没有我们想要下载的依赖包,搜索唯一标记 9064459a8b0dcd32877107f6002cc429a71659d1(以编译生成POM里的version为准) 无法得到想要的依赖。

storage.googleapis.com/download.flutter.io

墙内
配置如下,flutter相关配置如下:
镜像配置

根据官方指导,domain切换后,源文件会发布到 https://storage.flutter-io.cn/download.flutter.io 点击进入这个站点,同样找不到对应的POM文件中生成的依赖信息
storage.flutter-io.cn/download.flutter.io

这就造成了我们在使用aar包时,需要逐个引入POM文件中的依赖项,进行引擎依赖配置:

    implementation 'io.flutter:flutter_embedding_debug:1.0.0-9064459a8b0dcd32877107f6002cc429a71659d1'
    implementation 'io.flutter:armeabi_v7a_debug:1.0.0-9064459a8b0dcd32877107f6002cc429a71659d1'
    implementation 'io.flutter:arm64_v8a_debug:1.0.0-9064459a8b0dcd32877107f6002cc429a71659d1'
    implementation 'io.flutter:x86_64_debug:1.0.0-9064459a8b0dcd32877107f6002cc429a71659d1'
    implementation 'io.flutter:x86_debug:1.0.0-9064459a8b0dcd32877107f6002cc429a71659d1'

我自然是不愿意搞这么多配置的,关于aar包源数据发布上传这部分,我耗了很久反复阅读官方文档,并且在网上查询相关资料,得到如下提示:
1)Stack Overflow
给出的策略,是直接更改flutter SDK包里的 aar_init_script.gradle文件,很多大厂也是这么干的
https://stackoverflow.com/questions/75577692/any-way-to-upload-flutter-local-aar-maven-repository-to-remote-maven-repository
2)华为开发者联盟
分析的很透彻,最后希望你通过脚本实现,一言以蔽之:通过执行 flutter build aar -v 查看flutter build aar 的执行秘密。
https://blog.csdn.net/Ever69/article/details/120494115
我还真就这么干了,好奇心害死猫。咳~~~
3)官方指导文档真坑爹
在我们运行flutter build aar时,其实他是借助于.android项目配置Gradle或者Gradle Wrapper能力,其核心工作在
flutter-SDK-Dir/flutter/packages/flutter_tools/gradle/aar_init_script.gradle文件中完成aar发布工作,具体逻辑如下:

publishing aar

aar 文档相关Gradle 源数据都在本地repo库里,所以通过Maven依赖是断断无法获取的。

综上所述,官方文档给出的关于aar的配置,是指导开发人员使用发布到本地的aar文件的方法,而且还缺少引擎依赖的相关指导
为了能够简化使用flutter模块 aar包的引入流程,我们需要有自己的Maven私服,并且将flutter项目的aar包及相关源文件,发布到云端进行更新和迭代。这里选择阿里云效,完成这个任务

发布AAR包到Maven私服

云端选择阿里云效 ,打开阿里云的云效Packages官网:
https://packages.aliyun.com/maven
你会发现阿里云提供的免费私有Maven库服务,并且介绍了Gradle 7.0前后如何发布项目文档至私有仓库。
1)更改aar_init_script.gradle文件

image.png

这个侵入性比较强,而且每个人都要更改自己的SDK包。哪天编译脚本升级了,大家一起抓瞎~
2)在flutter项目中直接发布
As we all know,aar包之所以能够通过Gradle编译成功,是依赖其my_flutter/.android项目,该项目中的有一个Flutter库Module,是执行flutter build aar时自动生成的。通过flutter build aar -v 可以很清晰的看到,编译过程中,flutter 相关产物都放到了.android/Flutter模块的build文件夹下,看编译日志:

Starting process 'command '/Users/edwin/Library/flutter/bin/flutter''. 
Working directory: /Users/edwin/edwin/MeiTuanEdwin/my_flutter 
Command: /Users/edwin/Library/flutter/bin/flutter  #Flutter 工具的路径
 --verbose #启用详细输出,以便查看更多构建信息
 assemble #执行组装操作,即构建过程
 --no-version-check #禁用版本检查,Flutter 不会检查是否有新版本
 --depfile /Users/edwin/edwin/MeiTuanEdwin/my_flutter/.android/Flutter/build/intermediates/flutter/debug/flutter_build.d #指定依赖文件的路径
 --output /Users/edwin/edwin/MeiTuanEdwin/my_flutter/.android/Flutter/build/intermediates/flutter/debug #指定输出目录的路径
 -dTargetFile=lib/main.dart #指定入口文件的路径
 -dTargetPlatform=android #指定目标平台为 Android
 -dBuildMode=debug #指定构建模式为调试模式
 -dTrackWidgetCreation=true #启用 Flutter 的控件创建跟踪
 debug_android_application

看明白了吧,该命令的目标是构建一个用于调试的 Android 版本的 Flutter 应用程序,output命令指定了输出目录。
我们看看output文件夹所在的环境:


aar包中间产物

这说明我们的 Flutter 模块就是用来生成 aar 包的对应资源而建立的,那我们完全可以在 Flutter 项目的 build.gradle 文件中添加一个 task 用于发布 aar 包,发布任务通过publish.gradle文件完成。实现如下:

// publish flutter-debug.aar
//plugins {
//    id 'java'
//    // Gradle 7.0+ 版本
//    id 'maven-publish'
//}

group 'com.example.my_flutter'
version '1.1.4'
def artifactIdStr = 'flutter_debug'

apply plugin: 'maven-publish'
task comps { // 让我们看看它有哪些组件:
    afterEvaluate {
        println("Components: " + components*.name)  // 代码 1
    }
}
def directoryToPublish = "$rootDir.parentFile/build/host/outputs/repo/com/example/my_flutter/flutter_debug/$project.version"
afterEvaluate { // 代码 2
    publishing {
        publications {
            flutter(MavenPublication) { // 代码 3
                from components.debug
                println("groupId = " + groupId + ",artifactId = " + artifactIdStr + ",version = " + version + ",buildDir = " + buildDir)
                groupId = "$project.group" // 代码 4
                artifactId = artifactIdStr
                version = "$project.version"

//                artifact "$buildDir/outputs/aar/flutter-debug.aar" //aar artifact you want to publish
                // artifact "$directoryToPublish/${artifactIdStr}-${project.version}.aar"
                // artifact "$directoryToPublish/${artifactIdStr}-${project.version}.module"
                // artifact "$directoryToPublish/${artifactIdStr}-${project.version}.pom"

//                artifact("$directoryToPublish/${artifactIdStr}-${project.version}.aar") /*{
//                    classifier 'aar' // 使用 'aar' 作为分类器
//                }*/
//                artifact("$directoryToPublish/${artifactIdStr}-${project.version}.module") /*{
//                    classifier 'module' // 使用 'module' 作为分类器
//                }*/
//                artifact("$directoryToPublish/${artifactIdStr}-${project.version}.pom")/* {
//                    classifier 'pom' // 使用 'pom' 作为分类器
//                    extension 'pom' // 设置为 'pom' 类型
//                }*/
            }
        }
        repositories { // 代码 5
//            maven {
//                url = 'https://packages.aliyun.com/maven/repository/2443959-release-V0Shv7/'
//                credentials {
//                    username = '##############'
//                    password = '*****************'
//                }
//            }
            maven {
                url = 'https://packages.aliyun.com/maven/repository/2443959-snapshot-5T0GnA/'
                credentials {
                    username = '###############'
                    password = '******************'
                }
            }
        }
    }
}

其中, 代码 1 处的 components*.name 用于确认当前Gradle 会构建那些组件用于发布,输出结果如下:

Components

从打印结果来看,可以构建 debug、profile、release 三种组件及其all 组合组件,我们使用 debug 版本
代码 2 处,评估之后开始进入发布工作,依赖 maven-publish 插件进行发布,gradle 要配置成 7.2 以上的版本
代码 3 处,定义了一个 MavenPublication,命名为 "flutter"。from components.debug 表示将 MavenPublication 的内容从 Gradle 构建中的 components.debug 组件中获取。这里的MavenPublication包含了该组件的输出,例如 AAR 文件、POM 文件等。这是为了将 Flutter 模块构建的输出发布到 Maven 仓库
代码 4处配置了 GVA 用于唯一标识当前AAR 包,方便其他项目通过 Maven 或gradle 进行依赖,如下:
GVA

当然,现在依赖还是无法完成,因为aar 包要发布到远程 Maven,
代码 5 处就是用于配置远程服务地址的,我们选用的是阿里云效服务,用起来也算是麻烦+坑爹。
阿里云效:https://packages.aliyun.com/maven
如果玩不来,这里还有篇文章可以参考一如何把 library 发布到阿里云效
https://shawlaw.github.io/Fragmentary/publish-aar-to-aliyun-private-maven/publish-aar.html
发布任务编码完成~
然后在 Flutter 模块的 build.gradle 中 apply publish.gradle代码如下:

def currentTask = gradle.startParameter.taskNames.join(" ")
println("currentTask =" + currentTask)
if (!currentTask.contains("Aar")) {
    apply from: "../../publish.gradle"
}

这段代码的意思就是说,在 Flutter 项目编译过程中,准确的说是评估的时候,把发布任务引入,等compile完成后执行发布任务。
最后,
咱们再执行一遍 flutter build aar 指令,发现.android/Flutter/build/目录下没有生成 flutter 发布文件夹,
逗我呢?!
老铁,这里还得执行一遍 ./gradlew publish 命令,执行发布任务,现在可以愉快的看到如下效果了:


阿里云效snapshot 库

那么,现在赶快在另一个项目里试一下配置依赖吧。
首先在需要使用 aar包的项目根目录下 root.gradle 中配置 Maven 库,参考如下:


Maven 库地址

其次在需要使用 aar包的 module 中,配置 AVG 依赖如下:
dependencies

现在同步代码,等待依赖完成。flutter 引擎的五个依赖如期而至:
Flutter 引擎依赖包

Shell脚本一键打包发布

等等老哥~
道理我都听明白了,上面的代码还是要手动更改 Flutter 项目中的 build.gradle 文件啊。更要命的是,如果执行 flutter clean 清理一遍编译工程,刚刚的文档不都白瞎了了么。你这是让我哭吗?我说老兄你等等,这不是上脚本呢嘛~

  1. 首先,我们把发布文件 publish.gradle 移动到 my_flutter 项目根目录下,并在根目录下创建一个shell 脚本文件 apply_patch.sh,如下:


    root 目录
  2. 然后,将 publish.gradle 导入到 Flutter 项目中的诉求未变,通过 shell 脚本实现如下:
# 获取当前脚本所在目录的绝对路径
SCRIPT_DIR=$(dirname "$(readlink -f "$0")")

# 切换到Flutter项目的根目录
cd "$SCRIPT_DIR/.android/Flutter" || exit
# 在Flutter/build.gradle文件中执行命令:
# 1)在android配置项前插入 apply 发布应用
# 2)-i '.bak',备份原始文件
sed -i '.bak' '/^android {/i\
def currentTask = gradle.startParameter.taskNames.join(" ") \
println("currentTask =" + currentTask)\
if (!currentTask.contains("Aar")) {\
    apply from: "../../publish.gradle"\
}' "$SCRIPT_DIR/.android/Flutter/build.gradle"

通过脚本的形式,将发布任务添加到 .android/Flutter 项目中,脚本的含义都写在了注释里。

  1. 插入完成后,还要进行发布工作,仍然通过脚本完成,如下:
# 切换到 ./android 目录下执行发布任务
cd "$SCRIPT_DIR/.android" || exit
./gradlew publish --info > publish.log 2>&1
if [ $? -ne 0 ]; then
    echo "发布任务失败!查看 publish.log 获取更多信息。"
    exit 1
fi

这里把发布日志都打印在了 publish.log 文档里,如果发布失败./gradlew publish --info 会提供详细的失败信息。

  1. 版本控制,aar 包是有自己的版本号的,比如 1.1.4。为了统一控制 version号,不要手动更改gradle 文件,我们引入如下脚本:
# 替换 publish.gradle 中的版本号
OLD_VERSION=$(grep -o "version '[0-9]\+\.[0-9]\+\.[0-9]\+'" "$SCRIPT_DIR/publish.gradle" | head -n 1)
if [ -z "$OLD_VERSION" ]; then
  echo "无法获取旧版本号!"
  exit 1
fi

sed -i'.bak' "s/$OLD_VERSION/version '$BUILD_NUMBER'/" "$SCRIPT_DIR/publish.gradle" || { echo "版本号替换失败!"; exit 1; }
  1. 合并所有脚本,得到完整的发布脚本apply_patch.sh,脚本如下
作为一个广告引流专业户,还是详见 github 代码库吧~
  1. 执行 ./apply_patch.sh 脚本

到这里整个一键打包发布工作就完成了

FAQ

1.local.properties 类似文件属性配置时,不要加引号,不然在 gradle 文件中获取到的值要处理


image.png
  1. gradle 的版本要配置成 7.5,太高版本不见得稳定,而且低版本调用他的时候存在融合问题:


    image.png
  2. gradle 和 ./gradlew 是两码事,前者是你在全局变量配置的 gradle 版本,后者是gradle wrapper 中配置的 gradle 版本7.5
  3. 脚本可能会有一些warning 提示,没有跑通不要随意更改脚本,不然永远跑不起来了


    image.png

参考文献

  1. my_flutter库地址 https://github.com/hongyi0609/my_flutter
  2. GitHub库地址 https://github.com/hongyi0609/MeiTuanEdwin
  3. flutter 国内镜像 https://flutter.cn/community/china?tab=macos
  4. aar 服务端 https://storage.googleapis.com/download.flutter.io
  5. 华为开发者联盟分析 https://blog.csdn.net/Ever69/article/details/120494115
  6. 私服配置 https://stackoverflow.com/questions/75577692/any-way-to-upload-flutter-local-aar-maven-repository-to-remote-maven-repository
  7. 阿里云效:https://packages.aliyun.com/maven
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,185评论 6 503
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,652评论 3 393
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,524评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,339评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,387评论 6 391
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,287评论 1 301
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,130评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,985评论 0 275
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,420评论 1 313
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,617评论 3 334
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,779评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,477评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,088评论 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,716评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,857评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,876评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,700评论 2 354

推荐阅读更多精彩内容