RxHttp - 轻量级、可扩展、易使用、完美兼容MVVM、MVC架构的网络封装类库

前言

RxHttp是基于RxJava2+Retrofit 2.9.0+OkHttp 4.9.0实现的轻量级,完美兼容MVVM架构的网络请求封装类库,小巧精致,简单易用,轻轻松松搞定网络请求。

GitHub

https://github.com/kongpf8848/RxHttp

亮点

  • 代码量极少,类库大小不足100kb,但足以胜任大部分APP的网络请求任务,浓缩的都是精华啊^

  • 完美兼容MVVM,MVC架构,兼容Kotlin和Java,Kotlin+MVVM+RxHttp组合使用更酸爽,MVVM官方推荐,抱紧Google大腿就对了

  • 完美解决泛型类型擦除的棘手问题,还原泛型的真实类型

  • 天生支持网络请求和Activity,Fragment生命周期绑定,界面销毁时自动取消网络请求回调

  • 天生支持多BaseUrl,支持动态传入Url

  • 支持自定义OkHttpClient.Builder,可高度自定义网络请求参数

  • 支持Glide等和网络请求公用一个OkHttpClient,充分利用OkHttpClient的线程池和连接池,大部分情况下一个App一个OkHttpClient就够了

  • 支持GET,POST,PUT,DELETE等请求方式,支持文件上传及进度监听,支持同时上传多个文件,支持Uri上传

  • 支持文件下载及进度监听,支持大文件下载,支持断点下载

使用要求

项目基于AndroidX,Java8+,minSdkVersion>=21

使用

implementation 'com.github.kongpf8848:RxHttp:1.0.11'

配置(可选)

  RxHttpConfig.getInstance()
    /**
     * 失败重试次数
     */
    .maxRetries(3)
    /**
     * 每次失败重试间隔时间
     */
    .retryDelayMillis(200)
    /**
     * 自定义OkHttpClient.Builder(),RxHttp支持自定义OkHttpClient.Builder(),
     * 如不定义,则使用RxHttp默认的OkHttpClient.Builder()
     */
    .builder(OkHttpClient.Builder().apply {
        connectTimeout(60, TimeUnit.SECONDS)
        readTimeout(60, TimeUnit.SECONDS)
        writeTimeout(60, TimeUnit.SECONDS)
        /**
         * DEBUG模式下,添加日志拦截器,建议使用RxHttp中的FixHttpLoggingInterceptor,使用OkHttp的HttpLoggingInterceptor在上传下载的时候会有IOException问题
         */
        if (BuildConfig.DEBUG) {
            addInterceptor(FixHttpLoggingInterceptor().apply {
                level = FixHttpLoggingInterceptor.Level.BODY
            })
        }
    })

基础使用

  • GET/POST/PUT/DELETE/上传请求
   RxHttp.getInstance()
    /**
     * get:请求类型,可为get,post,put,delete,upload,分别对应GET/POST/PUT/DELETE/上传请求
     * context:上下文,可为Context,Activity或Fragment类型,当context为Activity或Fragment时网络请求和生命周期绑定
     */
    .get(context)
    /**
     * 请求url,如https://www.baidu.com
     */
    .url("xxx")
    /**
     *请求参数键值对,类型为Map<String, Any?>?,如hashMapOf("name" to "jack")
     */
    .params(map)
    /**
     *每个网络请求对应的tag值,可为null,用于后续手动根据tag取消指定网络请求
     */
    .tag("xxx")
    /**
     * HttpCallback:网络回调,参数xxx为返回数据对应的数据模型,
     * 类似RxJava中的Observer,onComplete只有在onNext回调之后执行,如发生错误则只会回调onError而不会执行onComplete
     */
    .enqueue(object : HttpCallback<xxx>() {
        /**
         * http请求开始时回调
         */
        override fun onStart() {

        }

        /**
         * http请求成功时回调
         */
        override fun onNext(response: xxx?) {

        }

        /**
         * http请求失败时回调
         */
        override fun onError(e: Throwable?) {

        }

        /**
         * http请求成功完成时回调
         */
        override fun onComplete() {

        }

        /**
         * 上传进度回调,请求类型为upload时才会回调
         */
        override fun onProgress(readBytes: Long, totalBytes: Long) {
        
        }
    })
  • 下载请求
   RxHttp.getInstance()
      /**
       * download:请求类型,下载请求
       * context:上下文,如不需要和生命周期绑定,应该传递applicationContext
       */
      .download(context)
      /**
       * 保存路径
       */
      .dir(dir)
      /**
       *保存文件名称
       */
      .filename(filename)
      /**
       * 是否为断点下载,默认为false
       */
      .breakpoint(true)
      /**
       * 下载地址,如http://study.163.com/pub/ucmooc/ucmooc-android-official.apk
       */
      .url(url)
      /**
       * 请求Tag
       */
      .tag(null)
      /**
       * 下载回调
       */
      .enqueue(object: DownloadCallback() {
          /**
           * 下载开始时回调
           */
          override fun onStart() {
    
          }
    
          /**
           * 下载完成时回调
           */
          override fun onNext(response: DownloadInfo?) {
    
          }
    
          /**
           * 下载失败时回调
           */
          override fun onError(e: Throwable?) {
    
          }
    
          /**
           * 下载完成之后回调
           */
          override fun onComplete() {
    
          }
    
          /**
           * 下载进度回调
           */
          override fun onProgress(readBytes: Long, totalBytes: Long) {
    
          }
    
      })

  • 取消请求
    /**
     * tag:Any?,请求Tag,对应网络请求里的Tag值
     * 如不为null,则取消指定网络请求,
     * 如为null,则取消所有网络请求
     */
    RxHttp.getInstance().cancelRequest(tag)

项目实战

此处假设服务端返回的数据格式为{"code":xxx,"data":T,"msg":""},其中code为响应码,整型,等于200时为成功,其余为失败,data对应的数据类型为泛型(boolean,int,double,String,对象{ },数组[ ]等类型)

{
   "code": 200,
   "data":T,
   "msg": ""
}

对应的Response类为

class TKResponse<T>(val code:Int,val msg: String?, val data: T?) : Serializable {
    companion object{
        const val STATUS_OK=200
    }
    fun isSuccess():Boolean{
        return code== STATUS_OK
    }
}
  • MVC项目

    • 定义MVCHttpCallback,用于将网络请求结果回调给UI界面
    abstract class MVCHttpCallback<T> {
    
        private val type: Type
    
        init {
            val arg = TypeUtil.getType(javaClass)
            type = TypeBuilder
                    .newInstance(TKResponse::class.java)
                    .addTypeParam(arg)
                    .build()
        }
    
        fun getType(): Type {
            return this.type
        }
    
        /**
         * 请求开始时回调,可以在此加载loading对话框等,默认为空实现
         */
        open fun onStart() {}
       
        /**
         * 抽象方法,请求成功回调,返回内容为泛型,对应TKResponse的data
         */
        abstract fun onSuccess(result: T?)
    
        /**
         * 抽象方法,请求失败回调,返回内容为code(错误码),msg(错误信息)
         */
        abstract fun onFailure(code: Int, msg: String?)
    
        /**
         * 上传进度回调,默认为空实现
         */
        open fun onProgress(readBytes: Long, totalBytes: Long) {}
    
        /**
         * 请求完成时回调,请求成功之后才会回调此方法,默认为空实现
         */
        open fun onComplete() {}
    
    }
    
    • 定义网络接口,封装GET/POST等网络请求
    object MVCApi {
    
        /**
         * GET请求
         * context:上下文
         * url:请求url
         * params:参数列表,可为null
         * tag:标识一个网络请求
         * callback:网络请求回调
         */
        inline fun <reified T> httpGet(
                context: Context,
                url: String,
                params: Map<String, Any?>?,
                tag: Any? = null,
                callback: MVCHttpCallback<T>
        ) {
            RxHttp.getInstance().get(context)
                    .url(url)
                    .params(params)
                    .tag(tag)
                    .enqueue(simpleHttpCallback(callback))
        }
    
        /**
         * POST请求
         * context:上下文
         * url:请求url
         * params:参数列表,可为null
         * tag:标识一个网络请求
         * callback:网络请求回调
         */
        inline fun <reified T> httpPost(
                context: Context,
                url: String,
                params: Map<String, Any?>?,
                tag: Any? = null,
                callback: MVCHttpCallback<T>
        ) {
            RxHttp.getInstance().post(context)
                    .url(url)
                    .params(params)
                    .tag(tag)
                    .enqueue(simpleHttpCallback(callback))
        }
        
        ......
        
         inline fun <reified T> simpleHttpCallback(callback: MVCHttpCallback<T>): HttpCallback<TKResponse<T>> {
            return object : HttpCallback<TKResponse<T>>(callback.getType()) {
                override fun onStart() {
                    super.onStart()
                    callback.onStart()
                }
    
                override fun onNext(response: TKResponse<T>?) {
                    if (response != null) {
                        if (response.isSuccess()) {
                            callback.onSuccess(response.data)
                        } else {
                            return onError(ServerException(response.code, response.msg))
                        }
    
                    } else {
                        return onError(NullResponseException(TKErrorCode.ERRCODE_RESPONSE_NULL, TKErrorCode.ERRCODE_RESPONSE_NULL_DESC))
                    }
    
                }
    
                override fun onError(e: Throwable?) {
                    handleThrowable(e).run {
                        callback.onFailure(first, second)
                    }
                }
    
                override fun onComplete() {
                    super.onComplete()
                    callback.onComplete()
                }
    
                override fun onProgress(readBytes: Long, totalBytes: Long) {
                    super.onProgress(readBytes, totalBytes)
                    callback.onProgress(readBytes, totalBytes)
                }
            }
        }
    
    • 在View层如Activity中调用网络接口
    MVCApi.httpGet(
        context = baseActivity,
        url = TKURL.URL_GET,
        params = null,
        tag = null, 
        callback = object : MVCHttpCallback<List<Banner>>() {
        override fun onStart() {
            LogUtils.d(TAG, "onButtonGet onStart() called")
        }
    
        override fun onSuccess(result: List<Banner>?) {
            Log.d(TAG, "onButtonGet onSuccess() called with: result = $result")
        }
    
        override fun onFailure(code: Int, msg: String?) {
            Log.d(TAG, "onButtonGet onFailure() called with: code = $code, msg = $msg")
        }
    
        override fun onComplete() {
            Log.d(TAG, "onButtonGet onComplete() called")
        }
    
    })
    

    具体使用可以参考demo代码,demo中有详细的示例演示MVC项目如何使用RxHttp

  • MVVM项目

    • 定义Activity基类BaseMvvmActivity
    abstract class BaseMvvmActivity<VM : BaseViewModel, VDB : ViewDataBinding> : AppCompatActivity(){
    
       lateinit var viewModel: VM
       lateinit var binding: VDB
    
       protected abstract fun getLayoutId(): Int
    
       final override fun onCreate(savedInstanceState: Bundle?) {
           onCreateStart(savedInstanceState)
           super.onCreate(savedInstanceState)
           binding = DataBindingUtil.setContentView(this, getLayoutId())
           binding.lifecycleOwner = this
           createViewModel()
           onCreateEnd(savedInstanceState)
       }
    
       protected open fun onCreateStart(savedInstanceState: Bundle?) {}
       protected open fun onCreateEnd(savedInstanceState: Bundle?) {}
    
       /**
        * 创建ViewModel
        */
       private fun createViewModel() {
           val type = findType(javaClass.genericSuperclass)
           val modelClass = if (type is ParameterizedType) {
               type.actualTypeArguments[0] as Class<VM>
           } else {
               BaseViewModel::class.java as Class<VM>
           }
           viewModel = ViewModelProvider(this).get(modelClass)
       }
    
       private fun findType(type: Type): Type?{
           return when(type){
               is ParameterizedType -> type
               is Class<*> ->{
               findType(type.genericSuperclass)
               }
               else ->{
               null
               }
           }
       }
    
    }
    
    • 定义ViewModel的基类BaseViewModel
    open class BaseViewModel(application: Application) : AndroidViewModel(application) {
    
       /**
        * 网络仓库
        */
       protected val networkbaseRepository: NetworkRepository = NetworkRepository.instance
       
       /**
        * 上下文
        */
       protected var context: Context = application.applicationContext
    
    }
    
    • 定义网络仓库,封装网络接口
    /**
    * MVVM架构网络仓库
    * UI->ViewModel->Repository->LiveData(ViewModel)->UI
    */
    class NetworkRepository private constructor() {
    
       companion object {
           val instance = NetworkRepository.holder
       }
    
       private object NetworkRepository {
           val holder = NetworkRepository()
       }
    
       inline fun <reified T> wrapHttpCallback(): MvvmHttpCallback<T> {
           return object : MvvmHttpCallback<T>() {
    
           }
       }
    
       inline fun <reified T> newCallback(liveData: MutableLiveData<TKState<T>>): HttpCallback<TKResponse<T>> {
           val type = wrapHttpCallback<T>().getType()
           return object : HttpCallback<TKResponse<T>>(type) {
               override fun onStart() {
               liveData.value = TKState.start()
               }
    
               override fun onNext(response: TKResponse<T>?) {
               liveData.value = TKState.response(response)
               }
    
               override fun onError(e: Throwable?) {
               liveData.value = TKState.error(e)
               }
    
               override fun onComplete() {
    
               /**
                * 亲,此处不要做任何操作,不要给LiveData赋值,防止onNext对应的LiveData数据被覆盖,
                * 在TKState类handle方法里会特别处理回调的,放心好了
                */
               }
    
               override fun onProgress(readBytes: Long, totalBytes: Long) {
               liveData.value = TKState.progress(readBytes, totalBytes)
               }
           }
       }
    
       inline fun <reified T> httpGet(
           context: Context,
           url: String,
           params: Map<String, Any?>?,
           tag: Any? = null
       ): MutableLiveData<TKState<T>> {
           val liveData = MutableLiveData<TKState<T>>()
           RxHttp.getInstance()
               .get(context)
               .url(url)
               .params(params)
               .tag(tag)
               .enqueue(newCallback(liveData))
           return liveData
       }
    
    
       inline fun <reified T> httpPost(
           context: Context,
           url: String,
           params: Map<String, Any?>?,
           tag: Any? = null
       ): MutableLiveData<TKState<T>> {
           val liveData = MutableLiveData<TKState<T>>()
           RxHttp.getInstance().post(context)
               .url(url)
               .params(params)
               .tag(tag)
               .enqueue(newCallback(liveData))
           return liveData
       }
    
       inline fun <reified T> httpPostForm(
           context: Context,
           url: String,
           params: Map<String, Any?>?,
           tag: Any? = null
       ): MutableLiveData<TKState<T>> {
           val liveData = MutableLiveData<TKState<T>>()
           RxHttp.getInstance().postForm(context)
               .url(url)
               .params(params)
               .tag(tag)
               .enqueue(newCallback(liveData))
           return liveData
       }
    
       inline fun <reified T> httpPut(
           context: Context,
           url: String,
           params: Map<String, Any?>?,
           tag: Any? = null
       ): MutableLiveData<TKState<T>> {
           val liveData = MutableLiveData<TKState<T>>()
           RxHttp.getInstance().put(context)
               .url(url)
               .params(params)
               .tag(tag)
               .enqueue(newCallback(liveData))
           return liveData
       }
    
       inline fun <reified T> httpDelete(
           context: Context,
           url: String,
           params: Map<String, Any?>?,
           tag: Any? = null
       ): MutableLiveData<TKState<T>> {
           val liveData = MutableLiveData<TKState<T>>()
           RxHttp.getInstance().delete(context)
               .params(params)
               .url(url)
               .tag(tag)
               .enqueue(newCallback(liveData))
           return liveData
       }
    
    
       /**
        *上传
        *支持上传多个文件,map中对应的value类型为File类型或Uri类型
        *支持监听上传进度
           val map =Map<String,Any>()
           map.put("model", "xiaomi")
           map.put("os", "android")
           map.put("avatar",File("xxx"))
           map.put("video",uri)
        */
       inline fun <reified T> httpUpload(
           context: Context,
           url: String,
           params: Map<String, Any?>?,
           tag: Any? = null
       ): MutableLiveData<TKState<T>> {
           val liveData = MutableLiveData<TKState<T>>()
           RxHttp.getInstance().upload(context)
               .url(url)
               .params(params)
               .tag(tag)
               .enqueue(newCallback(liveData))
           return liveData
       }
    
    
       /**
        * 下载
        * context:上下文,如不需要和生命周期绑定,应该传递applicationContext
        * url:下载地址
        * dir:本地目录路径
        * filename:保存文件名称
        * callback:下载进度回调
        * md5:下载文件的MD5值
        * breakpoint:是否支持断点下载,默认为true
        */
       fun httpDownload(context: Context, url: String, dir: String, filename: String, callback: DownloadCallback, md5: String? = null, breakPoint: Boolean = true, tag: Any? = null) {
           RxHttp.getInstance().download(context).dir(dir).filename(filename).breakpoint(breakPoint).md5(md5).url(url).tag(tag).enqueue(callback)
       }
    
    }
    
    • 定义TKState类,用于将网络回调转化为LiveData
    /**
     *将HttpCallback回调转化为对应的LiveData
    */
    class TKState<T> {
    
       var state: Int = 0
       var code = TKErrorCode.ERRCODE_UNKNOWN
       var msg: String? = null
       var data: T? = null
       var progress: Long = 0
       var total: Long = 0
    
       @JvmOverloads
       constructor(state: Int, data: T? = null, msg: String? = "") {
           this.state = state
           this.data = data
           this.msg = msg
       }
    
       constructor(state: Int, throwable: Throwable?) {
           this.state = state
           handleThrowable(throwable).run {
               this@TKState.code = first
               this@TKState.msg = second
           }
       }
    
       constructor(state: Int, progress: Long, total: Long) {
           this.state = state
           this.progress = progress
           this.total = total
       }
    
       fun handle(handleCallback: HandleCallback<T>.() -> Unit) {
           val callback = HandleCallback<T>()
           callback.apply(handleCallback)
           when (state) {
               START -> {
               callback.onStart?.invoke()
               }
               SUCCESS -> {
               callback.onSuccess?.invoke(data)
               }
               FAIL -> {
               callback.onFailure?.invoke(code, msg)
               }
               PROGRESS -> {
               callback.onProgress?.invoke(progress, total)
               }
           }
           if (state == SUCCESS || state == FAIL) {
               callback.onComplete?.invoke()
           }
       }
    
       open class HandleCallback<T> {
           var onStart: (() -> Unit)? = null
           var onSuccess: ((T?) -> Unit)? = null
           var onFailure: ((Int, String?) -> Unit)? = null
           var onComplete: (() -> Unit)? = null
           var onProgress: ((Long, Long) -> Unit)? = null
    
           fun onStart(callback: (() -> Unit)?) {
               this.onStart = callback
           }
    
           fun onSuccess(callback: ((T?) -> Unit)?) {
                this.onSuccess = callback
           }
    
           fun onFailure(callback: ((Int, String?) -> Unit)?) {
               this.onFailure = callback
           }
    
           fun onComplete(callback: (() -> Unit)?) {
               this.onComplete = callback
           }
    
           fun onProgress(callback: ((Long, Long) -> Unit)?) {
               this.onProgress = callback
           }
       }
    
       companion object {
           const val START = 0
           const val SUCCESS = 1
           const val FAIL = 2
           const val PROGRESS = 3
    
           fun <T> start(): TKState<T> {
            return TKState(START)
           }
    
           fun <T> response(response: TKResponse<T>?): TKState<T> {
               if (response != null) {
               if (response.isSuccess()) {
                   return TKState(SUCCESS, response.data, null)
               } else {
                   return error(ServerException(response.code, response.msg))
               }
    
               } else {
               return error(NullResponseException(TKErrorCode.ERRCODE_RESPONSE_NULL, TKErrorCode.ERRCODE_RESPONSE_NULL_DESC))
               }
    
           }
    
           fun <T> error(t: Throwable?): TKState<T> {
               return TKState(FAIL, t)
           }
    
           fun <T> progress(progress: Long, total: Long): TKState<T> {
               return TKState(PROGRESS, progress, total)
           }
       }
    
    }
    
    • 经过一系列封装,最后在View层如Activity中ViewModel调用Repository中的接口
     viewModel.testPost(hashMapOf(
               "name" to "jack",
               "location" to "shanghai",
               "age" to 28)
       )
       .observeState(this) {
           onStart {
             LogUtils.d(TAG, "onButtonPost() onStart called")
           }
           onSuccess {
             LogUtils.d(TAG, "onButtonPost() onSuccess called:${it}")
           }
           onFailure { code, msg ->
             ToastHelper.toast("onButtonPost() onFailure,code:${code},msg:${msg}")
           }
           onComplete {
             LogUtils.d(TAG, "onButtonPost() onComplete called")
           }
       }
    

    具体使用还要参考demo代码,demo中有详细的示例演示MVVM项目如何使用RxHttp

强烈建议下载Demo代码,Demo中有详细的示例,演示MVVM及MVC架构如何使用RxHttp,如果本文对你有帮助,可以考虑给我点赞哦

Demo

https://github.com/kongpf8848/RxHttp

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