PowerShell自定义对象详解

什么是对象

在面向对象编程的领域有一句话叫:万事万物皆为对象。

我们生活中的每一件东西都可以看作是一个对象,包括我们本身都是一个对象。

这是一种抽象的思想,我们将一件事物提取出特征和行为,将特征定义为属性,将行为封装成方法

对象在程序开发中的概念可以说都一样。

对象通常有自己的属性和方法,比如一个人,它的属性有肤色 /身高/ 性别 等,方法有 走路 /躺下 /呼喊/吃饭/睡觉/打豆豆 等。

什么是PowerShell对象

在powershell中提供了对对象的定义方法;使用这样的方法创建的变量就是powershell对象。

PowerShell是基于面向对象化的,不像传统的shell那样基于文本。这其中最主要的原因就是因为Win平台在管理操作上主要以面向对象为主,因此为了符合系统特点和我们的操作习惯,PowerShell也继承了这一特色。因此,不像传统的shell,在PowerShell中,我们可以随意地与对象进行互动。

1.创建

PS C:\Users\Administrator> $aa = New-Object object
PS C:\Users\Administrator> $aa.GetType()

IsPublic IsSerial Name                                     BaseType
-------- -------- ----                                     --------
True     True     Object


PS C:\Users\Administrator> $aa | Get-Member *


 TypeName:System.Object

Name        MemberType Definition
----        ---------- ----------
Equals      Method     bool Equals(System.Object obj)
GetHashCode Method     int GetHashCode()
GetType     Method     type GetType()
ToString    Method     string ToString()


PS C:\Users\Administrator>

2.添加属性:方法1

PS C:\Users\Administrator> $aa | Add-Member -MemberType NoteProperty -Name "Name" -Value "Jack"
PS C:\Users\Administrator> $aa

Name
----
Jack


PS C:\Users\Administrator> $aa | Get-Member *


 TypeName:System.Object

Name        MemberType   Definition
----        ----------   ----------
Equals      Method       bool Equals(System.Object obj)
GetHashCode Method       int GetHashCode()
GetType     Method       type GetType()
ToString    Method       string ToString()
Name        NoteProperty string Name=Jack


PS C:\Users\Administrator>

3.添加属性:方法2

PS C:\Users\Administrator> Add-Member -InputObject $aa -MemberType NoteProperty -Name "Language" -Value "Powershell"
PS C:\Users\Administrator> $aa | Get-Member *


 TypeName:System.Object

Name        MemberType   Definition
----        ----------   ----------
Equals      Method       bool Equals(System.Object obj)
GetHashCode Method       int GetHashCode()
GetType     Method       type GetType()
ToString    Method       string ToString()
Language    NoteProperty string Language=Powershell
Name        NoteProperty string Name=Jack


PS C:\Users\Administrator> $aa

Name Language
---- --------
Jack Powershell


PS C:\Users\Administrator>

4.添加方法

PS C:\Users\Administrator> $aa | Add-Member -MemberType ScriptMethod -Name "sayhello" -Value { write-host "hello" }
PS C:\Users\Administrator> $aa | Get-Member *


 TypeName:System.Object

Name        MemberType   Definition
----        ----------   ----------
Equals      Method       bool Equals(System.Object obj)
GetHashCode Method       int GetHashCode()
GetType     Method       type GetType()
ToString    Method       string ToString()
Language    NoteProperty string Language=Powershell
Name        NoteProperty string Name=Jack
sayhello    ScriptMethod System.Object sayhello();


PS C:\Users\Administrator> $aa.sayhello()
hello
PS C:\Users\Administrator>
PS C:\Users\Administrator> $S = { $this.Name.ToUpper() }
PS C:\Users\Administrator> $aa | Add-Member -MemberType ScriptMethod -Name "UpperMyName" -Value $S
PS C:\Users\Administrator> $aa

Name Language
---- --------
Jack Powershell


PS C:\Users\Administrator> $aa.UpperMyName()
JACK
PS C:\Users\Administrator>
PS C:\Users\Administrator> $word = "Goodbye!!!"
PS C:\Users\Administrator> $sayword = { Write-Host $word }
PS C:\Users\Administrator> $aa | Add-Member -MemberType ScriptMethod -Name "sayBye" -Value $sayword
PS C:\Users\Administrator> $aa.sayBye()
Goodbye!!!
PS C:\Users\Administrator>

传参(注意$this和传入参数的调用)

$block = {
 param(
 $var
 )
 $result = "{0},{1} I'm {2}" -f $var,"hello world!!",$this.Name
 return $result
}
$aa | Add-Member -MemberType ScriptMethod -Name "sayAnything" -Value $block

PS C:\Users\Administrator> $aa | Add-Member -MemberType ScriptMethod -Name "sayAnything" -Value $block
PS C:\Users\Administrator> $aa.sayAnything("Tom")
Tom,hello world!! I'm Jack
PS C:\Users\Administrator>

若下个验证代码块是否可以正常运行可以使用:

PS C:\Users\Administrator> &$block
,hello world!!
PS C:\Users\Administrator>

或者

PS C:\Users\Administrator> &$block -var "qq"
qq,hello world!!
PS C:\Users\Administrator>

5.移除方法

PS C:\Users\Administrator> $aa | Get-Member


 TypeName:System.Object

Name        MemberType   Definition
----        ----------   ----------
Equals      Method       bool Equals(System.Object obj)
GetHashCode Method       int GetHashCode()
GetType     Method       type GetType()
ToString    Method       string ToString()
Language    NoteProperty string Language=Powershell
Name        NoteProperty string Name=Jack
sayAnything ScriptMethod System.Object sayAnything();
sayBye      ScriptMethod System.Object sayBye();
sayhello    ScriptMethod System.Object sayhello();
UpperMyName ScriptMethod System.Object UpperMyName();


PS C:\Users\Administrator> $aa.PsObject.Members.Remove("sayAnything")
PS C:\Users\Administrator> $aa | Get-Member


 TypeName:System.Object

Name        MemberType   Definition
----        ----------   ----------
Equals      Method       bool Equals(System.Object obj)
GetHashCode Method       int GetHashCode()
GetType     Method       type GetType()
ToString    Method       string ToString()
Language    NoteProperty string Language=Powershell
Name        NoteProperty string Name=Jack
sayBye      ScriptMethod System.Object sayBye();
sayhello    ScriptMethod System.Object sayhello();
UpperMyName ScriptMethod System.Object UpperMyName();


PS C:\Users\Administrator>

什么是PowerShell自定义对象

自定义对象可以完全根据自己的设计,定义对象的属性和方法。

PowerShell自定义对象有什么作用

自定义对象可以更符合使用者的要求,因为他的属性和方法完全由使用者自己定义设定;比默认的对象更加灵活。自己创建的自定义对象完完全全根据自己的意愿赋予属性和方法,可以完全做到独一无二。

PowerShell自定义对象的常见用法

1.创建自定义对象

tips:需要powershell 3.0及以上版本

$myObject = [PSCustomObject]@{
 Name = 'Kevin'
 Language = 'PowerShell'
 State    = 'Texas'
}
##创建
PS C:\WINDOWS\System32> $myObject = [PSCustomObject]@{
>> Name = 'Kevin'
>> Language = 'PowerShell'
>> State    = 'Texas'
>> }
PS C:\WINDOWS\System32>

##查看
PS C:\WINDOWS\System32> $myObject

Name  Language   State
----  --------   -----
Kevin PowerShell Texas

##查看属性
PS C:\WINDOWS\System32> $myObject.name
Kevin
PS C:\WINDOWS\System32>

上面这些利用hashtable也能实现基本一样的效果,那我们来做下对比:

$myHashtable = @{
 Name = 'Kevin'
 Language = 'PowerShell'
 State    = 'Texas'
}

##创建
PS C:\WINDOWS\System32> $myHashtable = @{
>> Name = 'Kevin'
>> Language = 'PowerShell'
>> State    = 'Texas'
>> }
PS C:\WINDOWS\System32>
#查看
PS C:\WINDOWS\System32> $myHashtable

Name                           Value
----                           -----
Name                           Kevin
Language                       PowerShell
State                          Texas


##查看属性
PS C:\WINDOWS\System32> $myHashtable.Name
Kevin
PS C:\WINDOWS\System32>

2.自定义对象 vs. HashTable

类型不同

PS C:\WINDOWS\System32> $myObject.GetType()

IsPublic IsSerial Name                                     BaseType
-------- -------- ----                                     --------
True     False    PSCustomObject                           System.Object


PS C:\WINDOWS\System32>
PS C:\WINDOWS\System32> $myHashtable.GetType()

IsPublic IsSerial Name                                     BaseType
-------- -------- ----                                     --------
True     True     Hashtable                                System.Object


PS C:\WINDOWS\System32>

可用的方法不同

PS C:\WINDOWS\System32> $myHashtable.Count
3
PS C:\WINDOWS\System32> $myObject.Count
PS C:\WINDOWS\System32>

Member大大不同

##查看Member
PS C:\WINDOWS\System32> $myObject | Get-Member


 TypeName:System.Management.Automation.PSCustomObject

Name        MemberType   Definition
----        ----------   ----------
Equals      Method       bool Equals(System.Object obj)
GetHashCode Method       int GetHashCode()
GetType     Method       type GetType()
ToString    Method       string ToString()
Language    NoteProperty string Language=PowerShell
Name        NoteProperty string Name=Kevin
State       NoteProperty string State=Texas

##利用select
PS C:\WINDOWS\System32> $myObject | select name

Name
----
Kevin


PS C:\WINDOWS\System32>



##查看Member
PS C:\WINDOWS\System32> $myHashtable | Get-Member


 TypeName:System.Collections.Hashtable

Name              MemberType            Definition
----              ----------            ----------
Add               Method                void Add(System.Object key, System.Object value), void IDictionary.Add(System.Object key, System.Object value)
Clear             Method                void Clear(), void IDictionary.Clear()
Clone             Method                System.Object Clone(), System.Object ICloneable.Clone()
Contains          Method                bool Contains(System.Object key), bool IDictionary.Contains(System.Object key)
ContainsKey       Method                bool ContainsKey(System.Object key)
ContainsValue     Method                bool ContainsValue(System.Object value)
CopyTo            Method                void CopyTo(array array, int arrayIndex), void ICollection.CopyTo(array array, int index)
Equals            Method                bool Equals(System.Object obj)
GetEnumerator     Method                System.Collections.IDictionaryEnumerator GetEnumerator(), System.Collections.IDictionaryEnumerator IDictionary.GetEnumerator(), System.Collections.IEnume...
GetHashCode       Method                int GetHashCode()
GetObjectData     Method                void GetObjectData(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context), void ISerializable.GetObj...
GetType           Method                type GetType()
OnDeserialization Method                void OnDeserialization(System.Object sender), void IDeserializationCallback.OnDeserialization(System.Object sender)
Remove            Method                void Remove(System.Object key), void IDictionary.Remove(System.Object key)
ToString          Method                string ToString()
Item              ParameterizedProperty System.Object Item(System.Object key) {get;set;}
Count             Property              int Count {get;}
IsFixedSize       Property              bool IsFixedSize {get;}
IsReadOnly        Property              bool IsReadOnly {get;}
IsSynchronized    Property              bool IsSynchronized {get;}
Keys              Property              System.Collections.ICollection Keys {get;}
SyncRoot          Property              System.Object SyncRoot {get;}
Values            Property              System.Collections.ICollection Values {get;}

##利用select
PS C:\WINDOWS\System32>
PS C:\WINDOWS\System32> $myHashtable | select Name

Name
----



PS C:\WINDOWS\System32>

注意:对于hashtable而言,我们的例子中演示的结果虽然也能通过$myHashtable.Name显示出与$myObject.Name一样的结果;但是,$myHashtable.Name真正的写法应该是$myHashtable.Item("Name")

因为hashtable仅仅是键值对,所以无法通过使用Format-TableExport-CSVSelect的方法对数据进行处理。

3.HashTable转换成PSCustomObject

PS C:\WINDOWS\System32> $myHashtable = @{
>>     Name     = 'Kevin'
>>     Language = 'PowerShell'
>>     State    = 'Texas'
>> }
>> $myObject = [pscustomobject]$myHashtable
PS C:\WINDOWS\System32> $myObject

Name  Language   State
----  --------   -----
Kevin PowerShell Texas


PS C:\WINDOWS\System32>

注意:强烈建议如果在需要使用自定义对象的情况时从一开始使用;因为像这种从hashtable转换的PSCustomObject,实际上肯恶搞并不完全保障对象属性的顺序。

当然,这种情况目前可以通过[ordered]@()来解决,但这不时本片文章的讨论重点。

将哈希表保存到文件的最佳方法是将其保存为 JSON。 可以将其导入回 [PSCustomObject]

$myObject | ConvertTo-Json -depth 1 | Set-Content -Path $Path
$myObject = Get-Content -Path $Path | ConvertFrom-Json

4.历史遗留问题

$myHashtable = @{
 Name     = 'Kevin'
 Language = 'PowerShell'
 State    = 'Texas'
}

$myObject = New-Object -TypeName PSObject -Property $myHashtable

老版本的powershell使用上述方法创建自定义对象;但这种方法创建的速度比较慢,而且时公认要逐步废弃的方法;除非你是老版本,否则请务必精良使用新的方法创建。

5.自定义对象的属性

5.1添加属性

PS C:\WINDOWS\System32> $myObject

Name  Language   State
----  --------   -----
Kevin PowerShell Texas


PS C:\WINDOWS\System32>
PS C:\WINDOWS\System32>
PS C:\WINDOWS\System32> $myObject | Add-Member -MemberType NoteProperty -Name 'ID' -Value 'KevinMarquette'
PS C:\WINDOWS\System32>
PS C:\WINDOWS\System32> $myObject

Name  Language   State ID
----  --------   ----- --
Kevin PowerShell Texas KevinMarquette


PS C:\WINDOWS\System32>
PS C:\WINDOWS\System32> $myObject.ID
KevinMarquette
PS C:\WINDOWS\System32>

5.2移除属性

PS C:\WINDOWS\System32> $myObject.psobject.properties.remove('ID')
PS C:\WINDOWS\System32> $myObject

Name  Language   State
----  --------   -----
Kevin PowerShell Texas


PS C:\WINDOWS\System32>

.psobject 是内部成员,它使你能够访问基对象元数据。

5.3列出属性名称

PS C:\WINDOWS\System32> $myObject | Get-Member -MemberType NoteProperty | Select -ExpandProperty Name
Language
Name
State

PS C:\WINDOWS\System32> $myObject | Get-Member


 TypeName:System.Management.Automation.PSCustomObject

Name        MemberType   Definition
----        ----------   ----------
Equals      Method       bool Equals(System.Object obj)
GetHashCode Method       int GetHashCode()
GetType     Method       type GetType()
ToString    Method       string ToString()
Language    NoteProperty string Language=PowerShell
Name        NoteProperty string Name=Kevin
State       NoteProperty string State=Texas


PS C:\WINDOWS\System32>

或者

PS C:\WINDOWS\System32> $myobject.psobject.properties.name
Name
Language
State
PS C:\WINDOWS\System32>

5.4动态访问属性

$myObject.Name
or
$myObject.'Name'

都是允许的方法

PS C:\WINDOWS\System32> $myObject.Name
Kevin
PS C:\WINDOWS\System32> $myObject.'Name'
Kevin
PS C:\WINDOWS\System32>


因此我们可以利用第二种方式并定义变量的形式实现动态访问自定义对象的属性

$property = 'Name'
$myObject.$property

PS C:\WINDOWS\System32> $property = 'Name'
PS C:\WINDOWS\System32> $myObject.$property
Kevin
PS C:\WINDOWS\System32>

6.PSCustomObject转换为HshTable

PS C:\WINDOWS\System32> $hashtable = @{}
PS C:\WINDOWS\System32> foreach( $property in $myobject.psobject.properties.name )
>> {
>>     $hashtable[$property] = $myObject.$property
>> }
PS C:\WINDOWS\System32>
PS C:\WINDOWS\System32> $hashtable

Name                           Value
----                           -----
Name                           Kevin
Language                       PowerShell
State                          Texas


PS C:\WINDOWS\System32>

7.自定义对象的方法(Method)

如果需要将脚本方法添加到对象,则可以使用 Add-MemberScriptBlock 执行此操作。 必须使用 this 自动变量引用当前对象。 下面是将对象转换为哈希表的 scriptblock

$ScriptBlock = {
 $hashtable = @{}
 foreach( $property in $this.psobject.properties.name )
 {
 $hashtable[$property] = $this.$property
 }
 return $hashtable
}

然后

PS C:\WINDOWS\System32> $memberParam = @{
>>     MemberType = "ScriptMethod"
>>     InputObject = $myobject
>>     Name = "ToHashtable"
>>     Value = $scriptBlock
>> }
PS C:\WINDOWS\System32> Add-Member @memberParam
PS C:\WINDOWS\System32>
PS C:\WINDOWS\System32> $myObject

Name  Language   State
----  --------   -----
Kevin PowerShell Texas


PS C:\WINDOWS\System32> $myObject | Get-Member


 TypeName:System.Management.Automation.PSCustomObject

Name        MemberType   Definition
----        ----------   ----------
Equals      Method       bool Equals(System.Object obj)
GetHashCode Method       int GetHashCode()
GetType     Method       type GetType()
ToString    Method       string ToString()
Language    NoteProperty string Language=PowerShell
Name        NoteProperty string Name=Kevin
State       NoteProperty string State=Texas
ToHashtable ScriptMethod System.Object ToHashtable();


PS C:\WINDOWS\System32>

使用自定义的Method

PS C:\WINDOWS\System32> $myObject.ToHashtable()

Name                           Value
----                           -----
Name                           Kevin
Language                       PowerShell
State                          Texas


PS C:\WINDOWS\System32>

8.自定义对象类型的 PSTypeName

既然我们有了一个对象,我们还可以用它做一些可能不那么显而易见的事情。 第一件事就是为它指定 PSTypeName。 以下是我看到的最常用的方法:

$myObject.PSObject.TypeNames.Insert(0,"My.Object")


PS C:\Users\Administrator> $myObject.PSObject.TypeNames
My.Object
System.Management.Automation.PSCustomObject
System.Object
PS C:\Users\Administrator>

或者

$myObject1 = [PSCustomObject]@{
 PSTypeName = 'My.Object'
 Name       = 'Kevin'
 Language   = 'PowerShell'
 State      = 'Texas'
}

PS C:\Users\Administrator> $myObject.PSObject.TypeNames
My.Object
System.Management.Automation.PSCustomObject
System.Object
PS C:\Users\Administrator>
##我本人更新换这种方式

9.自定义对象结合array使用的例子(一般写脚本收集数据时比较常用)

$myObject1 = [PSCustomObject]@{
 Name     = 'Arthur'
 Language = 'PowerShell'
 State    = 'Texas'
}


$myObject2 = [PSCustomObject]@{
 Name     = 'Bill'
 Language = 'Python'
 State    = 'Texas'
}
$a = @()

$a += $myObject1
$a += $myObject2

PS C:\Users\Administrator> $a

Name     Language   State
----     --------   -----
Arthur   PowerShell Texas
Bill     Python     Texas


PS C:\Users\Administrator>
PS C:\Users\Administrator> $a | select Name

Name
----
Arthur
Bill

PS C:\Users\Administrator>

Powershell自定义变量进阶篇

1.Update-TypeData

PowerShell 为我们决定默认情况下显示哪些属性。 很多本机命令都有一个可以完成所有繁重工作的 .ps1xml格式化文件

1.1带有 DefaultPropertySet 的 Update-TypeData

$TypeData = @{
 TypeName = 'My.Object'
 DefaultDisplayPropertySet = 'Name','Language'
}
Update-TypeData @TypeData

$myObject = [PSCustomObject]@{
 PSTypeName = 'My.Object'
 Name       = 'Kevin'
 Language   = 'PowerShell'
 State      = 'Texas'
}

PS C:\Users\Administrator> $myObject | Format-List
Name     : Kevin
Language : PowerShell

PS C:\Users\Administrator> $myObject | Format-List *
Name     : Kevin
Language : PowerShell
State    : Texas

PS C:\Users\Administrator>

现在,我可以轻松地创建具有很多属性的对象,并在从 shell 中查看时,为其提供一个经过定制的干净清爽的视图。

1.2带有 ScriptProperty 的 Update-TypeData

如何为你的对象创建脚本属性呢?

$myObject = [PSCustomObject]@{
 PSTypeName = 'My.Object'
 Name       = 'Kevin'
 Language   = 'PowerShell'
 State      = 'Texas'
}

$TypeData = @{
 TypeName = 'My.Object'
 MemberType = 'ScriptProperty'
 MemberName = 'UpperCaseName'
 Value = {$this.Name.toUpper()}
}
Update-TypeData @TypeData


PS C:\Users\Administrator> $myObject | Format-List *


Name          : Kevin
Language      : PowerShell
State         : Texas
UpperCaseName : KEVIN



PS C:\Users\Administrator>

你可以在创建对象之前或之后执行此操作,它仍可正常工作。 这就是将 Add-Member 与脚本属性一起使用带来的不同之处。 当你通过我前面提及的方式使用 Add-Member 时,它只存在于对象的特定实例上。 此方法适用于具有此 TypeName 的所有对象。

本文如有考虑步骤出现任何纰漏,欢迎大家不吝指正。
——夏明亮

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

推荐阅读更多精彩内容