【问题标题】:Parse JSON to scripting.dictionary and pass via COM to VBA将 JSON 解析为 scripting.dictionary 并通过 COM 传递给 VBA
【发布时间】:2019-08-08 02:00:14
【问题描述】:

背景:我目前正在使用VBA-JSON 将 json 字符串解析为 VBA(Access)中的字典对象。这很慢,一个采样过程需要 18 秒。

在 VB.NET 中,JavaScriptSerializer Deserialize 方法需要 0.5 秒来处理相同的数据。

我希望通过 COM Interop 为我的 VBA 代码提供 VB.NET 方法的性能。但是 COM 不能传递通用对象,虽然我已经读到解决方案涉及封送处理,但我无法理解该选项。

当我手动生成一个 scripting.dictionary 类型并可以在 VBA 中使用时,我可以成功地从我的 VB.NET COM 类传递它。

    Public Function GetData2() As Scripting.Dictionary

        Dim dict As New Scripting.Dictionary

        dict.Add("a", "Athens")
        dict.Add("b", "Belgrade")
        Return dict

    End Function

但是 JavaScriptSerializer Deserialize 方法返回类型 IDictionary,而不是 scripting.dictionary。

所以我必须找到一种方法将 json 反序列化为 scripting.dictionary,或者将 IDictionary 转换为 scripting.dictionary。

我该怎么做?
鉴于我的总体目标,对替代方法有什么建议吗?

编辑。

该项目将专有的 REST API 用于会计系统。我想创建通用工具来简化和加速各种不同的任务,包括 Access、Excel、vbscript 等环境。API 设计人员想要的一切,但从通常对 REST API 编程不友好的工具开始。

用途可以从系统读取和写入数据,或将数据加载到另一个数据库,在 Excel 中创建自定义报告,导入订单等。

这是销售订单的一些 JSON。

{
  "id": 7,
  "orderNo": "0000102692",
  "division": "000",
  "location": "",
  "profitCenter": "",
  "invoiceNo": "",
  "customer": {
    "id": 1996,
    "code": "ER118",
    "customerNo": "ER118",
    "name": "E R Partridge Inc"
  },
  "currency": null,
  "status": "O",
  "type": "O",
  "hold": false,
  "orderDate": "2015-02-13",
  "invoiceDate": null,
  "requiredDate": "2015-02-13",
  "address": {
    "id": 2045,
    "type": "B",
    "linkTable": "SORD",
    "linkNo": "0000102692",
    "shipId": "",
    "name": "E R Partridge Inc",
    "line1": "1531 St Jean Baptiste St",
    "line2": "",
    "line3": "",
    "line4": "",
    "city": "St Ulric",
    "postalCode": "G0J 3H0",
    "provState": "QC",
    "country": "CAN",
    "phone": {
      "number": "4187370284",
      "format": 1
    },
    "fax": {
      "number": "",
      "format": 1
    },
    "email": "van@erpart.com",
    "website": "",
    "shipCode": "",
    "shipDescription": "",
    "salesperson": {
      "code": "",
      "name": ""
    },
    "territory": {
      "code": "",
      "description": ""
    },
    "sellLevel": 1,
    "glAccount": "41100",
    "defaultWarehouse": "VA",
    "created": "2014-08-26T11:44:57.930000",
    "modified": "2015-02-16T09:30:08",
    "contacts": [
      {
        "name": "Van Coon",
        "email": "",
        "phone": {
          "number": "",
          "format": 1
        },
        "fax": {
          "number": "",
          "format": 1
        }
      },
      {
        "name": "",
        "email": "",
        "phone": {
          "number": "",
          "format": 1
        },
        "fax": {
          "number": "",
          "format": 1
        }
      },
      {
        "name": "",
        "email": "",
        "phone": {
          "number": "",
          "format": 1
        },
        "fax": {
          "number": "",
          "format": 1
        }
      }
    ],
    "salesTaxes": [
      {
        "code": 1,
        "exempt": ""
      },
      {
        "code": 2,
        "exempt": ""
      },
      {
        "code": 0,
        "exempt": ""
      },
      {
        "code": 0,
        "exempt": ""
      }
    ]
  },
  "shippingAddress": {
    "id": 2044,
    "type": "S",
    "linkTable": "SORD",
    "linkNo": "SORD0000102692          S",
    "shipId": "",
    "name": "E R Partridge Inc",
    "line1": "1531 St Jean Baptiste St",
    "line2": "",
    "line3": "",
    "line4": "",
    "city": "St Ulric",
    "postalCode": "G0J 3H0",
    "provState": "QC",
    "country": "CAN",
    "phone": {
      "number": "4187370284",
      "format": 1
    },
    "fax": {
      "number": "",
      "format": 1
    },
    "email": "",
    "website": "",
    "shipCode": "",
    "shipDescription": "",
    "salesperson": {
      "code": "",
      "name": ""
    },
    "territory": {
      "code": "",
      "description": ""
    },
    "sellLevel": 1,
    "glAccount": "41100",
    "defaultWarehouse": "VA",
    "created": "2014-08-26T11:44:57.930000",
    "modified": "2014-08-26T11:44:57.930000",
    "contacts": [
      {
        "name": "Van Coon",
        "email": "",
        "phone": {
          "number": "",
          "format": 1
        },
        "fax": {
          "number": "",
          "format": 1
        }
      },
      {
        "name": "",
        "email": "",
        "phone": {
          "number": "",
          "format": 1
        },
        "fax": {
          "number": "",
          "format": 1
        }
      },
      {
        "name": "",
        "email": "",
        "phone": {
          "number": "",
          "format": 1
        },
        "fax": {
          "number": "",
          "format": 1
        }
      }
    ],
    "salesTaxes": [
      {
        "code": 1,
        "exempt": ""
      },
      {
        "code": 2,
        "exempt": ""
      },
      {
        "code": 0,
        "exempt": ""
      },
      {
        "code": 0,
        "exempt": ""
      }
    ]
  },
  "contact": {
    "name": "",
    "email": "",
    "phone": {
      "number": "",
      "format": 0
    },
    "fax": {
      "number": "",
      "format": 0
    }
  },
  "customerPO": "",
  "batchNo": 0,
  "fob": "Your dock",
  "referenceNo": "",
  "shippingCarrier": "",
  "shipDate": null,
  "trackingNo": "",
  "termsCode": "",
  "termsText": "",
  "freight": "41.95",
  "taxes": [
    {
      "code": 1,
      "name": "G.S.T.",
      "shortName": "G.S.T.",
      "rate": "5",
      "exemptNo": "",
      "total": "44.05"
    },
    {
      "code": 2,
      "name": "P.S.T.",
      "shortName": "BC P.S.T.",
      "rate": "7",
      "exemptNo": "",
      "total": "61.67"
    },
    {
      "code": 0,
      "name": "",
      "shortName": "",
      "rate": "0",
      "exemptNo": "",
      "total": 0
    },
    {
      "code": 0,
      "name": "",
      "shortName": "",
      "rate": "0",
      "exemptNo": "",
      "total": 0
    }
  ],
  "subtotal": "839",
  "subtotalOrdered": "839",
  "discount": "0",
  "totalDiscount": "0",
  "total": "986.67",
  "totalOrdered": "986.67",
  "grossProfit": "346.26",
  "items": [
    {
      "id": 8,
      "orderNo": "0000102692",
      "sequence": 1,
      "inventory": {
        "id": 40,
        "whse": "VA",
        "partNo": "INSDB30",
        "description": "InSpire Dumbbell 30"
      },
      "serials": null,
      "whse": "VA",
      "partNo": "INSDB30",
      "description": "InSpire Dumbbell 30",
      "comment": "",
      "orderQty": "4",
      "committedQty": "4",
      "backorderQty": "0",
      "retailPrice": "70",
      "unitPrice": "70",
      "discountable": true,
      "discountPct": "0",
      "discountAmt": "0",
      "taxFlags": [
        true,
        true,
        false,
        false
      ],
      "sellMeasure": "EA",
      "vendor": "INSPIRE",
      "levyCode": "",
      "requiredDate": "2015-08-26",
      "extendedPriceOrdered": "280",
      "extendedPriceCommitted": "280",
      "suppress": false
    },
    {
      "id": 9,
      "orderNo": "0000102692",
      "sequence": 2,
      "inventory": {
        "id": 27,
        "whse": "VA",
        "partNo": "NATACCBAL",
        "description": "National Accupressure Balls"
      },
      "serials": null,
      "whse": "VA",
      "partNo": "NATACCBAL",
      "description": "National Accupressure Balls",
      "comment": "",
      "orderQty": "5",
      "committedQty": "5",
      "backorderQty": "0",
      "retailPrice": "22",
      "unitPrice": "22",
      "discountable": true,
      "discountPct": "0",
      "discountAmt": "0",
      "taxFlags": [
        true,
        true,
        false,
        false
      ],
      "sellMeasure": "EA",
      "vendor": "NATPRO",
      "levyCode": "",
      "requiredDate": "2015-08-26",
      "extendedPriceOrdered": "110",
      "extendedPriceCommitted": "110",
      "suppress": false
    },
    {
      "id": 10,
      "orderNo": "0000102692",
      "sequence": 3,
      "inventory": {
        "id": 33,
        "whse": "VA",
        "partNo": "SPAB",
        "description": "Springfield Ab Toner"
      },
      "serials": null,
      "whse": "VA",
      "partNo": "SPAB",
      "description": "Springfield Ab Toner",
      "comment": "",
      "orderQty": "1",
      "committedQty": "1",
      "backorderQty": "0",
      "retailPrice": "45",
      "unitPrice": "45",
      "discountable": true,
      "discountPct": "0",
      "discountAmt": "0",
      "taxFlags": [
        true,
        true,
        false,
        false
      ],
      "sellMeasure": "EA",
      "vendor": "SPRFIT",
      "levyCode": "",
      "requiredDate": "2015-08-26",
      "extendedPriceOrdered": "45",
      "extendedPriceCommitted": "45",
      "suppress": false
    },
    {
      "id": 11,
      "orderNo": "0000102692",
      "sequence": 4,
      "inventory": {
        "id": 46,
        "whse": "VA",
        "partNo": "INSDB50",
        "description": "InSpire Dumbbell 50"
      },
      "serials": null,
      "whse": "VA",
      "partNo": "INSDB50",
      "description": "InSpire Dumbbell 50",
      "comment": "",
      "orderQty": "2",
      "committedQty": "2",
      "backorderQty": "0",
      "retailPrice": "118",
      "unitPrice": "118",
      "discountable": true,
      "discountPct": "0",
      "discountAmt": "0",
      "taxFlags": [
        true,
        true,
        false,
        false
      ],
      "sellMeasure": "EA",
      "vendor": "INSPIRE",
      "levyCode": "",
      "requiredDate": "2015-08-26",
      "extendedPriceOrdered": "236",
      "extendedPriceCommitted": "236",
      "suppress": false
    },
    {
      "id": 12,
      "orderNo": "0000102692",
      "sequence": 5,
      "inventory": {
        "id": 42,
        "whse": "VA",
        "partNo": "INSDB15",
        "description": "InSpire Dumbbell 15"
      },
      "serials": null,
      "whse": "VA",
      "partNo": "INSDB15",
      "description": "InSpire Dumbbell 15",
      "comment": "",
      "orderQty": "3",
      "committedQty": "3",
      "backorderQty": "0",
      "retailPrice": "34",
      "unitPrice": "34",
      "discountable": true,
      "discountPct": "0",
      "discountAmt": "0",
      "taxFlags": [
        true,
        true,
        false,
        false
      ],
      "sellMeasure": "EA",
      "vendor": "INSPIRE",
      "levyCode": "",
      "requiredDate": "2015-08-26",
      "extendedPriceOrdered": "102",
      "extendedPriceCommitted": "102",
      "suppress": false
    },
    {
      "id": 13,
      "orderNo": "0000102692",
      "sequence": 6,
      "inventory": {
        "id": 9,
        "whse": "VA",
        "partNo": "INSWP50",
        "description": "InSpire Weight Plate 50"
      },
      "serials": null,
      "whse": "VA",
      "partNo": "INSWP50",
      "description": "InSpire Weight Plate 50",
      "comment": "",
      "orderQty": "1",
      "committedQty": "1",
      "backorderQty": "0",
      "retailPrice": "66",
      "unitPrice": "66",
      "discountable": true,
      "discountPct": "0",
      "discountAmt": "0",
      "taxFlags": [
        true,
        true,
        false,
        false
      ],
      "sellMeasure": "EA",
      "vendor": "INSPIRE",
      "levyCode": "",
      "requiredDate": "2015-08-26",
      "extendedPriceOrdered": "66",
      "extendedPriceCommitted": "66",
      "suppress": false
    }
  ],
  "payments": [
    
  ],
  "createdBy": "SS",
  "modifiedBy": "SS",
  "created": "2014-08-26T11:44:57.930000",
  "modified": "2015-02-20T08:09:55",
  "links": {
    "notes": "https://localhost:10880/api/v2/companies/INSPIRE/sales/orders/7/notes/"
  }
}

编辑 2

Erik A 的回答向我展示了如何按需或“流式传输”进行解析。只有当您请求元素时才会进行解析。

我想我误解了 VB.NET 如何反序列化 JSON 的本质。它必须做同样的事情。所以当我看到 500 毫秒而不是 18 秒时,我不知道我在看什么。我怀疑如果我在 VB.NET 中遍历反序列化的 json 并检查每个元素,则需要更长的时间。我用于性能测试的样本数据实际上是 124 个 JSON 样本的集合,其中一些包含更多的项目,因此需要 18 秒。 这是正确的吗?

Albert 的回答向我展示了我最初想做的事情,但无法让它发挥作用。很好的完整答案,我将在接下来深入研究。

【问题讨论】:

  • 您可以将 .NET 的 IDictionary 转换为 .NET COM 对象中的 Scripting.Dictionary,因为它可以访问这两种类型(当然,您必须递归地执行此操作)。
  • 你能分享你想做的操作,以及示例 JSON 数据吗? VBA-JSON 有其局限性,我一直在研究一种尚未完全测试但可能适合您的需求的替代方案。
  • 但这有关系吗?您可以使用类型对象并将其传回。但是为什么不将 DAO 记录集传递给您的 .net 例程,并让 .net 代码将数据写到表中呢?目前尚不清楚您在何处/何时使用 json 数据,但我假设您有一个 json 字符串,并希望将该字符串展开到 Access 表中,对吗?编辑:您还可以从 .net 将集合作为您在 VBA 中定义的类型类传回。或者,您可以传回一种 iList,它是在 VBA 中使用的一种体面的数据类型。
  • 为什么不让 vb.net 为您创建课程?在 vb.net 中创建一个类(然后删除所有文本。现在从文本文件中剪切 Jason,然后选择编辑->选择性粘贴,选择 json。VB.net 将为您生成基于该 Jason 的类。现在, 你可以在 vb.net 中创建 4 行类,它接受字符串并将类返回到 VBA。这样做的好处是你实际上在 VBA 中获得了带有 json 数据所有列的 intel-sense。编辑你的帖子和提供一个示例 json 字符串。我将发布可在 Access 中使用的工作 vb 代码。
  • 我的第一次尝试是 Albert 将 json 结构的完整模型创建为 VB.NET 中的一个类。 json 是一个深层结构,我发现(来自本网站的另一篇文章)我无法以我想要的方式实现子类。因此,例如不能从 VBA 一直使用智能感知来引用“company.invoice(1).ShipAddress.AddressLine(2)”。我将尝试您的解决方案来传递 Dictionary 对象。

标签: vba vb.net ms-access com


【解决方案1】:

为什么不直接使用 .net 字典?您可能只需要计数、设置或按键拉动。

所以,这段代码应该没问题:

Imports System.Runtime.InteropServices
Imports Newtonsoft.Json

<ClassInterface(ClassInterfaceType.AutoDual)>
Public Class MyJSON

   Private m_DICT As New Dictionary(Of String, String)

   Public Function ToJson() As String
       Dim s As String = ""
       s = JsonConvert.SerializeObject(m_DICT)
       Return s
   End Function

   Public Sub JsonToDict(s As String)
       m_DICT = JsonConvert.DeserializeObject(Of Dictionary(Of String, String))(s)
   End Sub

   Public Sub Add(sKey As String, sValue As String)
       m_DICT.Add(sKey, sValue)
   End Sub

   Public Function ix(s As String) As String
       Return m_DICT(s)
   End Function

   Public Function Count() As Integer
       Return m_DICT.Count
   End Function

End Class

只需确保将上述项目设置为 x86。选中复选框以注册 com interop,然后您就可以参加比赛了。我用的是NewtonSOFT json,不清楚你用的是什么序列化库。

所以,现在你的 VBA 代码变成了:

Sub TEstMyCom()

  Dim MyJSON  As New TestCom2.MyJSON
   
  MyJSON.Add "a", "aaaaa"
  MyJSON.Add "b", "bbbbb"
  MyJSON.Add "c", "ccccc"
      
  Dim ss As String
  ss = MyJSON.toJSON
  
  Debug.Print MyJSON.toJSON
  
  ' convert the string to array (dict)
  
  Dim MyJSON2 As New TestCom2.MyJSON
 
  MyJSON2.JsonToDict ss
 
 Debug.Print MyJSON2.ix("c")
 
End Sub

输出:

{"a":"aaaaa","b":"bbbbb","c":"ccccc"}
ccccc

请注意,即使是 VBA 编辑器中的智能感知也适用于上述内容。因此,只需公开一些额外的方法来使用 .net dict,您甚至不必费心使用 VBA 中的脚本库 - 无论如何使用该库有点痛苦。因此,您可以像我上面所做的那样简单地使用 .net dict 对象来摆脱一个库引用。

编辑

现在用户已经提供了示例 json?

要处理给定的数据,步骤如下。

在.net 中创建一个新的空白类 ctrl-a,del 键。有了这个空类,我们就有了文本文档中的示例 json。 ctrl-a, ctrl-c。现在在 Visual Studio (VS) 中,编辑->特殊粘贴,粘贴为 JSON 类。

此时,您的课程会自动为您生成。由于 Neutonsoft 解析器的限制,它不支持 arrays()。 (我可能会很伤心)。

所以,搜索 (),找到数组并替换它们。

所以

Public Property taxes() As Tax

变成

Public Property taxes As IList(Of Tax)

我们使用 iList 代替 List 是因为我们需要这里的读/写能力。正如我所说,我们只做这项工作是因为 NeutonSoft simple 不喜欢数组。

好的,上面大约有​​ 4 个。这需要不到 1 分钟的时间进行修改。

但是,如果想要在 VBA 中获得非常好的智能感知,则需要重新公开上述内容。所以,让我们为这些列表添加一些类。

同样,只有大约 4 个。 所以我们有这个:

Public Property salesTaxes As IList(Of Salestax)

Public Property salesTaxesN(ix As Integer) As Salestax
    Get
        Return salesTaxes(ix)
    End Get
    Set(s As Salestax)
        salesTaxes(ix) = s
    End Set

End Property

以上将给我们很好的情报。我们不必执行上述操作,但在额外的 2 分钟内,我们现在可以在 VBA 中以 intel-sense 方式遍历数据。

我们需要一个计数(虽然 iList 出现在 VBA 中,但由于某种原因它没有公开计数)。我对其他建议持开放态度,但让我们在上面添加以下内容:

Public Function salesTaxesNCout() As Integer

    Return salesTaxes.Count

End Function

做了以上改动了吗?我们的时间还不到 5 分钟。如果您在 VS 中使用 paste as json 功能,那么您执行上述操作的次数越多,您做出这些更改的效果就越好。如前所述,您会发现不到 5 分钟的时间。

再次由于需要 VBA 中的 intel-sense,因此在每个类之前粘贴此内容

<ClassInterface(ClassInterfaceType.AutoDual)>

现在这是一遍又一遍的快速粘贴。所以这就是代码的样子(我们类的示例片段)。

<ClassInterface(ClassInterfaceType.AutoDual)>
Public Class Salesperson
    Public Property code As String
    Public Property name As String
End Class
<ClassInterface(ClassInterfaceType.AutoDual)>
Public Class Territory
    Public Property code As String
    Public Property description As String
End Class

以上只是一个简短的片段。同样,这真的很快。

好的,我们完成了!

我们的主类现在看起来像这样:

imports System.Runtime.InteropServices
imports Newtonsoft.Json

<ClassInterface(ClassInterfaceType.AutoDual)>
Public Class MyJSON

   Public MyCust As New Jcust

   Public Function ToJson() As String

       Dim s As String = ""
       s = JsonConvert.SerializeObject(MyCust)
       Return s

   End Function

   Public Sub JsonToCust(s As String)

       MyCust = JsonConvert.DeserializeObject(Of Jcust)(s)

   End Sub

End Class

就是这样!您现在有了一个很好的工作设置。

我们在上面使用的 VBA 代码现在是这样的:

Sub custTest()

  Dim strJSON    As String
  Dim intF       As Integer
  Dim strF       As String
  strF = "c:\test2\cust.txt"
  ' read in that file
  intF = FreeFile()
  Open strF For Input As #intF
  strJSON = input(LOF(intF), intF)
  Close intF
  
  Dim cCust As New MyJSON
      
  cCust.JsonToCust strJSON
  
  Debug.Print cCust.MyCust.Address.salesTaxesN(1).Code
  
  Debug.Print cCust.MyCust.Address.City
  
  
End Sub

运行上述? 输出:

 2 
St Ulric

我应该指出,intel-sense 一直有效。

现在我想真正的问题是,如果有人知道如何让 Newtonsoft 使用 Arrays,那么我们将在 2 分钟内完成这项工作,而不是花费我 5 分钟。

事实上,我想问这个问题已经有 1 年了,今天晚些时候我会问的。在上面我确实将 rootobject 重命名为 MyCust。

【讨论】:

    【解决方案2】:

    我个人一直致力于为 VBA 开发一个快速、灵活的 JSON 解释器,它可能适合您的需求。

    它由两个不同的对象组成:clsStringBuilder(一个非常基本的字符串生成器)和JSONInterpreter(允许您使用 JSON 的主要对象)。

    您可以找到项目here。请注意,我已经为这个问题快速上传了它,目前它缺少很多东西(文档、测试等)。

    示例代码:

    Dim jsi As New JSONInterpreter
    jsi.JSON = SomeString
    Debug.Print jsi.item("company", "invoice", 1, "ShipAddress", "AddressLine", 2).VBAVariant
    

    如果您在较大对象中大量使用特定数组或对象,我建议您检索 VBAVariant 属性并进一步使用它,或者创建一个子对象。

    例子:

    Dim jsi As New JSONInterpreter
    jsi.JSON = SomeString
    Dim shipAddressJSI as JSONInterpreter
    Set shipAddressJSI = jsi.item("company", "invoice", 1, "ShipAddress")
    Debug.Print shipAddressJSI.item("AddressLine", 1).VBAVariant
    Debug.Print shipAddressJSI.item("AddressLine", 2).VBAVariant
    

    请注意,如果您考虑使用它,我强烈建议您先进行一些测试。在示例实现中找到的 WalkJSON 子可以帮助解决这个问题。

    对您共享的 JSON 文档的快速性能测试表明,在我的系统上将整个文档(存储在 Excel 的工作表单元格中)移动到字典需要 0.08 到 0.11 秒(这是低端的最好的)。仅将文档的一部分移动到字典中当然可以提高性能,特别是如果您可以使用位置(而不是键名)来指定您想要的部分。

    【讨论】:

    • 感谢您发布代码。我已经检查过了,并从中学到了很多东西。起初我不明白您的代码的“流式传输”性质是什么意思。我认为这意味着在您调用一个元素之前不会进行任何解析。如果是这样的话,这是一个非常酷的选择。
    • 感谢您发布代码。我已经检查过了,并从中学到了很多东西。起初我不明白您的代码的“流式传输”性质是什么意思。我认为这意味着在您调用一个元素之前不会进行任何解析。我拿走了你的演练代码并删除了调试并在 JSON 上运行它。您的代码 0.152 秒。 VBA-JSON 0.066 秒。如果我想要所有数据,VBA-JSON 似乎更快。如果我只想要某些元素,您的代码会更快。我可以看到两者都需要。感谢您抽出宝贵时间分享您的项目。我还有话要说,但字数不多了。
    • streaming 的本质主要是它不会解析整个文件。它从头开始,然后读取它需要的内容,当您不需要读取整个文件时提高速度(因为未解析结尾)。 WalkJSON 代码非常非常低效,它主要是一种验证整个文件是否可以正确读取的方法。如果您需要将整个文件作为字典,只需在加载文件后使用.VBAVariant。在我的基准测试中,在任何工作负载中正确使用我的代码都比 VBA-JSON 快得多,但如果你分享你的实际工作负载,我可以验证。
    • 请注意,我的方法是非缓存的。如果您需要从一个大文件中读取多个元素,那么将整个内容快速移动到字典中会更有效。
    • 工作负载 json 示例:workload
    猜你喜欢
    • 2014-09-12
    • 2012-05-15
    • 1970-01-01
    • 2016-01-26
    • 2016-10-07
    • 1970-01-01
    • 1970-01-01
    • 2012-12-11
    • 1970-01-01
    相关资源
    最近更新 更多