谈谈cordova 创建plugin

官方
插件开发指南
Plugin.xml
因本人比较熟悉iOS , 所以创建的plugin 以iOS 为主, android 简单的涉及.
我们知道cordova plugin 是原生与html沟通的桥梁, cordova 可以说是一个webView,加载一个大的html 页面.相关的版面以html 网页的形式出现,而cordova plugin ,就好像你要打开手机的相机功能,单靠html是完成不了这个功能的.所以就需要cordova plugin的存在, 它异常的重要.

目标

这样添加plugin

希望我们最后这样使用就可以添加到cordova-plugin: (因未在registry.cordova.io中注册.暂指定备用URL)

cordova plugin add https://github.com/apache/cordova-plugin-console.git
cordova plugin add https://github.com/apache/cordova-plugin-console.git#r0.2.0
  • 如果插件(及其plugin.xml文件)位于git repo中的子目录中,则可以使用:字符指定它。请注意,#仍然需要该 角色:
cordova plugin add https://github.com/someone/aplugin.git#:/my/sub/dir
  • 还可以将git-ref和子目录结合使用:
cordova plugin add https://github.com/someone/aplugin.git#r0.0.1:/my/sub/dir
  • 或者,指定包含该plugin.xml文件的插件目录的本地路径:
cordova plugin add ../my_plugin_dir
删除plugin
cordova plugin rm pluginName(可以打开本地plugin list 看, 也可以执行 cordova plugin list 看)

開始創建我們的Plugin

  • 插件文件夹必须具有plugin.xml清单文件,所以我們可以简单地创建一个plugin.xml文件, 然后我们开始对里面的参数做一一讲解


    plugin.xml

    用工具打开plugin.xml.

<?xml version="1.0" encoding="UTF-8"?>

<!-- xmlns: Required , 插件名称空间,http://apache.org/cordova/ns/plugins/1.0。 如果文档包含来自其他命名空间的XML,例如在Android的情况下要添加到AndroidManifest.xml文件的标记,那么这些命名空间也应该包含在元素中。-->
<!-- id, Required ,通常是this-is-a-plugin的形式,就像cordova-plugin-device(作为示例) -->
<!-- version,Required, 插件的版本号 -->
<plugin xmlns="http://apache.org/cordova/ns/plugins/1.0" 
  xmlns:android="http://schemas.android.com/apk/res/android" id="cordova-plugin-netspay" version="0.1.0">
  <!-- name,用于指定插件的名称。(尚未)处理本地化。-->
  <name>netspay</name>
  <!--description用于指定插件的描述.(尚未)处理本地化。-->
  <description>Cordova Plugin netspay</description>
  <!--author包含插件作者的名称。-->
  <author>peijue chen</author>
  <!--关键字元素的内容,可以用逗号分隔的-->
  <keywords>cordova, plugin, enets, netspay</keywords>
  <!--此元素用于指定插件的许可证。-->
  <license>MIT</license>
  <!---如果有pod , 请添加此, 
    如果不添加这个,请先安装: cordova plugin add cordova-plugin-cocoapod-support --save
  --->
  <dependency id="cordova-plugin-cocoapod-support"/>
  <!-- 指定的cordova 版本, 如果安裝時比它小, cordova 會終止.如果不指定,cordova 会不管三七二十一安装.-->
  <!-- name : cordova/cordova-ios/cordova-android....-->
  <!-- <engines>
  <engine name="cordova" version=">=4.0.0" />
  <engine name="cordova-android" version=">=4.0.0" />
  <engine name="cordova-ios" version=">=1.7.1" />
  </engines> -->

  <!--  Custom frameworks example:-->
  <!--
  <engines>
  <engine name="my_custom_framework" version="1.0.0" platform="android" scriptSrc="path_to_my_custom_framework_version"/>
  <engine name="another_framework" version=">0.2.0" platform="ios|android" scriptSrc="path_to_another_framework_version"/>
  <engine name="even_more_framework" version=">=2.2.0" platform="*" scriptSrc="path_to_even_more_framework_version"/>
  </engines>
  -->

  <!-- js-module 
       src:插件目录中相对于plugin.xml文件;  
       name 提供模块名称的最后一部分.可以是你喜欢的任何东西,如果你想在你的JavaScript代码中使用cordova.require导入插件, 这里可以这样输入: var netspay = require('./netspay')
  <js-module>的模块名称是插件的id,后跟name的值。 -->
  <!-- clobbers: 用于指定插入module.exports的window对象下的命名空间。 您可以拥有任意数量的<clobbers>。 -->
  <!-- e.g: window.cordova.plugins.netspay  这里可以这样使用-->
  <js-module src="www/netspay.js" name="netspay">
    <clobbers target="cordova.plugins.netspay" />
  </js-module>

  <!--  加入多个, 也可以在某个平台单独指定
  <js-module src="www/PositionError.js" name="PositionError">
            <clobbers target="PositionError" />
        </js-module>
-->

  <!--<dependency>标签允许您指定当前插件所依赖的其他插件。 插件由其唯一的 npm id或github url 引用。-->
  <!--
  <dependency id="cordova-plugin-someplugin" url="https://github.com/myuser/someplugin" commit="428931ada3891801" subdir="some/path/here" />
  <dependency id="cordova-plugin-someplugin" version="1.0.1">
  -->

  <!--如果不指定platform,就当作只是JavaScript,可以安装到任何平台-->
  <!--Required ,Allowed values: ios, android, windows, browser, osx -->
  <!-- android -->
  <platform name="android">
    <config-file parent="/*" target="res/xml/config.xml">
      <feature name="NetsPay">
        <param name="android-package" value="cordova-plugin-netspay.NetsPay" />
      </feature>
    </config-file>

    <config-file target="AndroidManifest.xml" parent="/*">
      <uses-permission android:name="android.permission.INTERNET" />
      <uses-permission android:name="android.permission.READ_PHONE_STATE" />
      <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
      <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" android:protectionLevel="normal" />
      <uses-permission android:name="com.nets.netspay.QR_TRANSACTION" />
    </config-file>

    <source-file src="src/android/NetsPay.java" target-dir="src/cordova-plugin-netspay/NetsPay" />

    <framework src="src/android/netspay.gradle" custom="true" type="gradleReference"/>
    <resource-file src="src/android/enetslib_UAT_1.2.1.aar" target="libs/enetslib_UAT_1.2.1.aar"/>
  <!--
  <resource-file src="FooPluginStrings.xml" target="res/values/FooPluginStrings.xml" />
// 添加配置
<config-file target="AndroidManifest.xml" parent="/manifest/application">
    <activity android:name="com.foo.Foo" android:label="@string/app_name">
        <intent-filter>
        </intent-filter>
    </activity>
</config-file>
-->

<!---
 On Android (as of cordova-android@4.0.0), framework tags are used to include Maven dependencies, or to include bundled library projects.
-->
<!-- Depend on latest version of GCM from play services
<framework src="com.google.android.gms:play-services-gcm:+" />
 -->
<!-- Depend on v21 of appcompat-v7 support library 
<framework src="com.android.support:appcompat-v7:21+" />
-->
<!-- Depend on library project included in plugin 
<framework src="relative/path/FeedbackLib" custom="true" />
-->

<!--
Framework can also be used to have custom .gradle files sub-included into the main project's build.gradle file:

<framework src="relative/path/rules.gradle" custom="true" type="gradleReference" />

On Windows, using custom='true' and type='projectReference' will add a reference to the project which will be added to the compile+link steps of the cordova project. This essentially is the only way currently that a 'custom' framework can target multiple architectures as they are explicitly built as a dependency by the referencing cordova application.

<framework src="path/to/project/LibProj.csproj" custom="true" type="projectReference"/>

Examples of using these Windows specific attributes:
<framework src="src/windows/example.dll" arch="x64" />
<framework src="src/windows/example.dll" versions=">=8.0" />
<framework src="src/windows/example.vcxproj" type="projectReference" target="win" />
<framework src="src/windows/example.vcxproj" type="projectReference" target="all" versions="8.1" arch="x86" />
<framework src="src/windows/example.dll" target-dir="bin/x64" arch="x64" custom="true"/>
-->
<!--
Another example of using Windows-specific attributes to add a reference to WinMD components, written in C# and C++, whose API will be available at runtime:
-->
<!-- C# component that consists of one .winmd file 
<framework src="lib\windows\component.winmd" versions="<10.0" />
-->

<!-- C++ component with separated metadata and implementation
<framework src="lib\windows\x86\cppcomponent.winmd"
           implementation="lib\windows\x86\cppcomponent.dll"
           target-dir="component\x86" arch="x86" versions=">=10.0" />
-->
  </platform>

  <!-- ios -->
  <platform name="ios">

    <!--
      这个配置相当重要, feature 中的name 你可以写 上面的id ,
      param 的name 写: ios-package ,  但value : 一定要写你本地的.m /.swift 文件的 class 名字, cordova 要用它来call 方法.
    -->
    <config-file target="config.xml" parent="/*">
      <feature name="NetsPay">
        <param name="ios-package" value="NetsPay" />
      </feature>
    </config-file>

      <!-- 配置info 文件 -->
      <!---往info.plist 中添加东西, 可以设置默认值, 在外面添加plugin 时也可以根据GEOLOCATION_USAGE_DESCRIPTION来添加, 请往下看-->
     <preference name="GEOLOCATION_USAGE_DESCRIPTION" default="Enable access for location helps us to show you nearest stores." />
     <config-file target="*-Info.plist" parent="NSLocationWhenInUseUsageDescription">
        <string>$GEOLOCATION_USAGE_DESCRIPTION</string>
      </config-file> 

    <!-- add url schemes -->
    <config-file target="*-Info.plist" parent="CFBundleURLTypes">
      <array>
        <dict>
          <key>CFBundleURLName</key>
          <string>bundle.id</string>
          <key>CFBundleURLSchemes</key>
          <array>
            <string>netspaySchemes</string>
          </array>
        </dict>
      </array>
    </config-file>

    <!-- 头文件, 只复制文件进cordova项目, 不参与编译 -->
    <header-file src="src/ios/NetsPay.h" />
    <!--安装到项目中的可执行源代码。-->
    <source-file src="src/ios/NetsPay.m" />
    <!--<source-file src="src/ios/Netspay.swift" />-->
    
    <!--compiler-flags: 如果设置,则为特定源文件指定指定的编译器标志。-->
    <!--
    <source-file src="src/ios/someLib.a" compiler-flags="-fno-objc-arc" />
-->

    <!-- 系統庫 -->
    <!-- alipay depen -->
    <!-- <framework src="libc++.tbd" />
    <framework src="libz.tbd" />
    <framework src="SystemConfiguration.framework" />
    <framework src="CoreTelephony.framework" />
    <framework src="QuartzCore.framework" />
    <framework src="CoreText.framework" />
    <framework src="CoreGraphics.framework" />
    <framework src="UIKit.framework" />
    <framework src="Foundation.framework" />
    <framework src="CFNetwork.framework" />
    <framework src="CoreMotion.framework" /> -->

    <!-- pod framework 但仅限静态库, 必须: cordova-ios@4.3.0 and cordova-cli 6.4.0
          如果使用该属性, 请设置好 cordova-ios 版本. 
    -->
    <!---或者这种情况-->
    <pods-config ios-min-version="8.0" use-frameworks="true"/>    
    <pod id="GoogleConversionTracking" version="3.4.0"/>
    <pod id="FirebaseAppIndexing" version="1.0.3"/>
    <!-- <framework src="OrderPlaceSdk" type="podspec" spec="~> 0.1.3" /> -->
     
    <!--包含本地需要说明的文件或头文件-->
    <!-- <header-file src="src/ios/WechtSDK1.8.2/WechatAuthSDK.h" />
    <header-file src="src/ios/WechtSDK1.8.2/WXApi.h" />
    <header-file src="src/ios/WechtSDK1.8.2/WXApiObject.h" />
    <header-file src="src/ios/WechtSDK1.8.2/README.txt" />

    //  如果设置为true,则还将指定的文件作为框架添加到项目中。
    <source-file src="src/ios/WechtSDK1.8.2/libWeChatSDK.a" framework="true" />
    // 这类似于<source-file>元素,但专门针对iOS和Android等平台,用于区分源文件,头文件和资源。
    //  写入xcode 中:  copy bundle resource 中 一般為 xml bundle 文件
    <resource-file src="src/ios/AlipaySDK.bundle" /> -->

    <!--
      .a 静态库, 请往上看, 需要用  source-file
      custom: 指示framework是否包含在插件文件中。也就是本地库, 如果是系统库不需要 custom="true" 
      embed="true"需要配合custom="true"使用, 就是可以将你的动态库嵌入到应用程序中, 但需要: cordova-ios@4.4.0 and cordova-cli@7.0.0 , 如果使用该属性, 请设置好 cordova-ios 版本. 
    <framework src="src/ios/OrderPlaceSDK.framework" custom="true" embed="true" /> 

    <framework src="src/ios/iOS_ENETS_SDK_UAT/Alamofire.framework" custom="true" />
    <framework src="src/ios/iOS_ENETS_SDK_UAT/Caishen.framework" custom="true" />
    <framework src="src/ios/iOS_ENETS_SDK_UAT/CryptoSwift.framework" custom="true" />
    <framework src="src/ios/iOS_ENETS_SDK_UAT/ENETSLib.framework" custom="true" />
    <framework src="src/ios/iOS_ENETS_SDK_UAT/MBProgressHUD.framework" custom="true" />
    <framework src="src/ios/iOS_ENETS_SDK_UAT/Presentr.framework" custom="true" />
    <framework src="src/ios/iOS_ENETS_SDK_UAT/SwiftyJSON.framework" custom="true" />
    -->
    
  </platform>

</plugin>

到此我们的plugin.xml 就配置好了, 这里需要添加的文件就放到对应的文件夹下面就可以
目前我的结构类似这样


不单单这些
不单单这些
不单单这些
  • 其中 LICENSE ,是许可证, 可以用txt 文件写一个,不要后缀,名字是: LICENSE就可以.
  • package.json 可以说是配置文件吧.
  • README.md , 可以写上这个plugin 怎么用之类的, 如果上传到gitHub , 它会插件的首页显示.
  • src: 是放ios/android 原生代码的地方
  • www: 放的是js 文件
package.json
// version/ name/description, 与plugin.xml中保持一致
// repository, 是你放到gitHub 上的仓库
// bugs 是gitHub的issues 讨论区
{
    "version": "0.1.0",
    "name": "cordova-plugin-netspay",
    "cordova_name": "cordova-plugin-netspay",
    "description": "cordova-plugin-netspay",
    "license": "Apache 2.0",
    "author": {
        "name": "Joson",
        "email": "xxxx@xxx.com"
    },
    "repository": {
        "type": "git",
        "url": "https://github.com/PeiJueChen/cordova-plugin-netspay.git"
    },
    "bugs": {
        "url": "https://github.com/PeiJueChen/cordova-plugin-netspay.git/issues"
    },
    "keywords": [
        "cordova",
        "plugin",
        "netspay"
    ]
}

js 文件怎么写 , js 文件一般放在www 文件夹下, 这是规范

/**
 * 
 * exec(callback, errorCallback, pluginName, actionName, argumentArray)
 * callback 插件成功返回时调用,并将本机插件中的任何参数传递给它
 * errorCallback在插件遇到错误时调用。我们在上面省略了这一点
 * pluginName 是本机端的插件类名称。就是类名  (feature 下param 中的value , 它也要和原生代码中的类名对上.)
 * actionName 是我们将在本机方面执行的操作。
 * argumentArray 是传递给本机端的参数数组
 */

// prototype 屬於是為class 添加方法/屬於用到的
var exec = require('cordova/exec'),
    cordova = require('cordova'),
    // utils = require('cordova/utils'),

    // 如果需要引用本地js, 已经在Plugin.xml 中
    // <js-module src="www/PositionError.js" name="PositionError">
    //    <clobbers target="PositionError" />
    //  </js-module>
    //PositionError = require('./PositionError');

var NetsPay = (function () {
    // var platformId = cordova.platformId;  // 'android' 'ios'
    function NetsPay() {
        // console.log("platformId: " + platformId + code);
    }
    // 建议加上cordova调用, 因为之后的版本cordova 提示说需要加cordova在前面调用
    NetsPay.prototype.requestPay = function (echoValue, successCallback, errorCallback) {
        cordova.exec(successCallback, errorCallback, 'NetsPay', 'requestPay', [echoValue]);
    };

    return NetsPay;

})();

var netsPay = new NetsPay();
// 导出属性, 调试时, window. 就会有提示这个属性.
module.exports = netsPay;

下面看看 NetsPay 文件

  • iOS
#import "NetsPay.h"
#import <Cordova/CDVPluginResult.h>
#import <ENETSLib/ENETSLib-Swift.h>
@interface NetsPay ()<PaymentRequestDelegate>
@property (strong, nonatomic) NSString* requestPayCallbackId;
@end

@implementation NetsPay

- (void)requestPay:(CDVInvokedUrlCommand*)command
{
    
    NSString* callbackId = [command callbackId];
    self.requestPayCallbackId = callbackId;
    
    // do something
    [self pay:command];

// 说几点注意的,  传过来的参数可以根据 [command.arguments objectAtIndex:0];  索引拿到
//  想调用回调时: 
/*
CDVCommandStatus_OK , 这个值cordova 提供多种了,可以选择
NSDictionaray *dict = @{@"key":@"value"};
CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK
                                                      messageAsDictionary:dict];
[self.commandDelegate pluginResult callbackId:command.callbackId];
*/
//  如果call back 是异步调用,要
// [pluginResult setKeepCallback:[NSNumber numberWithBool:YES]];

}

*Android

package cordova.plugin.netspay;
import org.apache.cordova.CordovaPlugin;
import org.apache.cordova.CallbackContext;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

public class NetsPay extends CordovaPlugin {

    @Override
    public boolean execute(String action, JSONArray args, CallbackContext callbackContext) throws JSONException {
        if (action.equals("requestPay")) {
            String message = args.getString(0);
            this. requestPay(message, callbackContext);
            return true;
        }
        return false;
    }

    private void requestPay(String message, CallbackContext callbackContext) {
        if (message != null && message.length() > 0) {
            callbackContext.success(message);
        } else {
            callbackContext.error("Expected one non-empty string argument.");
        }
    }
}

到此一个cordova plugin 就完成了

添加

  • 建议先在本地使用, 因为本地安装时,如果写得有问题, 会安装提示失败及原因. 最后测试没问题再Push 到github .
  • 可以把写好plugin 放在与测试工程同一级下, 执行:


cordova plugin add ../cordova-plugin-netspay(插件文件夹名, 要让他能够找到plugin.xml 文件) 

如果你已经push 到github 可以执行

cordova plugin add github地址 --variable GEOLOCATION_USAGE_DESCRIPTION ="写上你想在xcode info.plist文件中对应key的value"

添加成功后.

使用

// cordova.plugins.netspay 这个名字是由
/*
  <js-module src="www/netspay.js" name="netspay">
    <clobbers target="cordova.plugins.netspay" />
  </js-module>
  clobbers中的target 来的
*/
 try {
    (<any>window).cordova.plugins.netspay.requestPay({"hmac": "hmacfdsff3424"},
        (s) => {
          console.log('callback requestPay success', s);
        }, err => {
          console.log('callback requestPay err', err);
    })
  } catch (exception) {
         console.log(' open requestPay error ', exception);
  }

到此, 我们的cordova plugin 就创建结束, 如果想看关于swift怎么创建cordova插件, 请看我的另一篇文章:创建swift 版本的 cordova 插件,它会多几个注意点, 其实就是语法不同导致的.

补允: 如果是app 的使用者, 想添加pod ,可以在config.xml 中做以下配置 , ref

  <platform name="ios">
        <!-- set platform :ios, defaults to 7.0 -->
        <preference name="pods_ios_min_version" value="8.0"/>
        <!-- add use_frameworks! to Podfile, this also disabled bridging headers -->
        <preference name="pods_use_frameworks" value="true"/>
        <!-- use the latest version of a pod -->
        <pod name="LatestPod" />
        <!-- use a specific version of a pod -->
        <pod name="VersionedPod" version="1.0.0" />
        <!-- use a custom repo -->
        <pod name="GitPod1" git="https://github.com/blakgeek/something" tag="v1.0.1" />
        <pod name="GitPod2" git="https://github.com/blakgeek/something" branch="wood" />
        <pod name="GitPod3" git="https://github.com/blakgeek/something" commit="1b33368" />
        <!-- target specific configurations, this can be combined with all other options -->
        <pod name="GitPod2" configuration="debug" />
        <pod name="GitPod2" configurations="release,debug" />
        <!-- add a pod dependency using a custom podspec -->
        <pod name="GitPod2" podspec="https://example.com/JSONKit.podspec" />
        <!-- add a pod dependency using the spec parameter like a Cordova framework -->
        <pod name="GitPod2" spec="~> 2.0.0"/>
        <pod name="GitPod2" spec=":configurations => ['Debug', 'Beta']"/>
        <!-- if pod uses a bundle that isn't compatible with Cocoapods 1.x -->
        <pod name="BadBundle" fix-bundle-path="Bad/Path.bundle"/>
    </platform>
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,014评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,796评论 3 386
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,484评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,830评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,946评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,114评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,182评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,927评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,369评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,678评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,832评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,533评论 4 335
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,166评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,885评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,128评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,659评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,738评论 2 351

推荐阅读更多精彩内容