如何格式化具有多个对象返回的 JSON 结构?(动态)

huangapple go评论83阅读模式
英文:

How do I format a json struct with multiple object returns? (Dynamic)

问题

以下是翻译好的内容:

我有一个API调用,返回的结果如下:

{
    "result": {
        "720268538": {
            "icon_url": "-9a81dlWLwJ2UUGcVs_nsVtzdOEdtWwKGZZLQHTxDZ7I56KU0Zwwo4NUX4oFJZEHLbXU5A1PIYQNqhpOSV-fRPasw8rsUFJ5KBFZv668FFEuh_KQJTtEuI63xIXbxqOtauyClTMEsJV1jruS89T3iQKx_BBqa2j3JpjVLFH1xpp0EQ",
            "icon_url_large": "",
            "icon_drag_url": "",
            "name": "Chroma Case",
            "market_hash_name": "Chroma Case",
            "market_name": "Chroma Case",
            "name_color": "D2D2D2",
            "background_color": "",
            "type": "Base Grade Container",
            "tradable": "1",
            "marketable": "1",
            "commodity": "1",
            "market_tradable_restriction": "7",
            "fraudwarnings": "",
            "descriptions": {
                "0": {
                    "type": "html",
                    "value": " ",
                    "app_data": ""
                },
                "1": {
                    "type": "html",
                    "value": "Container Series #38",
                    "color": "99ccff",
                    "app_data": ""
                },
                "2": {
                    "type": "html",
                    "value": " ",
                    "app_data": ""
                },
                "3": {
                    "type": "html",
                    "value": "Contains one of the following:",
                    "app_data": ""
                },
                "4": {
                    "type": "html",
                    "value": "Glock-18 | Catacombs",
                    "color": "4b69ff",
                    "app_data": ""
                },
                "5": {
                    "type": "html",
                    "value": "M249 | System Lock",
                    "color": "4b69ff",
                    "app_data": ""
                },
                "6": {
                    "type": "html",
                    "value": "MP9 | Deadly Poison",
                    "color": "4b69ff",
                    "app_data": ""
                },
                "7": {
                    "type": "html",
                    "value": "SCAR-20 | Grotto",
                    "color": "4b69ff",
                    "app_data": ""
                },
                "8": {
                    "type": "html",
                    "value": "XM1014 | Quicksilver",
                    "color": "4b69ff",
                    "app_data": ""
                },
                "9": {
                    "type": "html",
                    "value": "Dual Berettas | Urban Shock",
                    "color": "8847ff",
                    "app_data": ""
                },
                "10": {
                    "type": "html",
                    "value": "Desert Eagle | Naga",
                    "color": "8847ff",
                    "app_data": ""
                },
                "11": {
                    "type": "html",
                    "value": "MAC-10 | Malachite",
                    "color": "8847ff",
                    "app_data": ""
                },
                "12": {
                    "type": "html",
                    "value": "Sawed-Off | Serenity",
                    "color": "8847ff",
                    "app_data": ""
                },
                "13": {
                    "type": "html",
                    "value": "AK-47 | Cartel",
                    "color": "d32ce6",
                    "app_data": ""
                },
                "14": {
                    "type": "html",
                    "value": "M4A4 | 龍王 (Dragon King)",
                    "color": "d32ce6",
                    "app_data": ""
                },
                "15": {
                    "type": "html",
                    "value": "P250 | Muertos",
                    "color": "d32ce6",
                    "app_data": ""
                },
                "16": {
                    "type": "html",
                    "value": "AWP | Man-o'-war",
                    "color": "eb4b4b",
                    "app_data": ""
                },
                "17": {
                    "type": "html",
                    "value": "Galil AR | Chatterbox",
                    "color": "eb4b4b",
                    "app_data": ""
                },
                "18": {
                    "type": "html",
                    "value": "or an Exceedingly Rare Special Item!",
                    "color": "ffd700",
                    "app_data": ""
                },
                "19": {
                    "type": "html",
                    "value": " ",
                    "app_data": ""
                },
                "20": {
                    "type": "html",
                    "value": "",
                    "color": "00a000",
                    "app_data": {
                        "limited": "1"
                    }
                }
            },
            "owner_descriptions": "",
            "tags": {
                "0": {
                    "internal_name": "CSGO_Type_WeaponCase",
                    "name": "Container",
                    "category": "Type",
                    "category_name": "Type"
                },
                "1": {
                    "internal_name": "set_community_6",
                    "name": "The Chroma Collection",
                    "category": "ItemSet",
                    "category_name": "Collection"
                },
                "2": {
                    "internal_name": "normal",
                    "name": "Normal",
                    "category": "Quality",
                    "category_name": "Category"
                },
                "3": {
                    "internal_name": "Rarity_Common",
                    "name": "Base Grade",
                    "category": "Rarity",
                    "color": "b0c3d9",
                    "category_name": "Quality"
                }
            },
            "classid": "720268538"
        },
        "success": true
    }
}

结果可能有多个返回值,我想知道结构应该如何设计,我目前的设计返回的是空值。

type AssetInfo struct {
    Result `json:"result"`
}

type Result struct {
    Asset   map[string]Asset `json:"asset"`
    Success bool             `json:"success,omitempty"`
}

type Asset struct {
    IconUrl           string                   `json:"icon_url,omitempty"`
    IconUrlLarge      string                   `json:"icon_url_large,omitempty"`
    IconDragUrl       string                   `json:"icon_drag_url,omitempty"`
    Name              string                   `json:"name,omitempty"`
    MarketHashName    string                   `json:"market_hash_name,omitempty"`
    MarketName        string                   `json:"market_name,omitempty"`
    NameColor         string                   `json:"name_color,omitempty"`
    BGColor           string                   `json:"background_color,omitempty"`
    Type              string                   `json:"type,omitempty"`
    Tradable          string                   `json:"tradable,omitempty"`
    Marketable        string                   `json:"marketable,omitempty"`
    Commodity         string                   `json:"commodity,omitempty"`
    TradeRestrict     string                   `json:"market_tradeable_restriction,omitempty"`
    FraudWarnings     string                   `json:"fraudwarnings,omitempty"`
    Descriptions      map[string]*Descriptions `json:"descriptions,omitempty"`
    OwnerDescriptions string                   `json:"owner_descriptions,omitempty"`
    Tags              map[string]*Tags         `json:"tags,omitempty"`
    ClassId           string                   `json:"classid,omitempty"`
}

type Descriptions struct {
    Type    string `json:"type"`
    Value   string `json:"value"`
    Color   string `json:"color,omitempty"`
    AppData string `json:"appdata"`
}

type Tags struct {
    InternalName string `json:"internal_name"`
    Name         string `json:"name"`
    Category     string `json:"category"`
    Color        string `json:"color,omitempty"`
    CategoryName string `json:"category_name"`
}

如果有人能告诉我我的结构有什么问题,那就太好了,谢谢。

让我来看看你的结构是否正确。

英文:

I have an API call and it returns like this:

{
"result": {
"720268538": {
"icon_url": "-9a81dlWLwJ2UUGcVs_nsVtzdOEdtWwKGZZLQHTxDZ7I56KU0Zwwo4NUX4oFJZEHLbXU5A1PIYQNqhpOSV-fRPasw8rsUFJ5KBFZv668FFEuh_KQJTtEuI63xIXbxqOtauyClTMEsJV1jruS89T3iQKx_BBqa2j3JpjVLFH1xpp0EQ",
"icon_url_large": "",
"icon_drag_url": "",
"name": "Chroma Case",
"market_hash_name": "Chroma Case",
"market_name": "Chroma Case",
"name_color": "D2D2D2",
"background_color": "",
"type": "Base Grade Container",
"tradable": "1",
"marketable": "1",
"commodity": "1",
"market_tradable_restriction": "7",
"fraudwarnings": "",
"descriptions": {
"0": {
"type": "html",
"value": " ",
"app_data": ""
},
"1": {
"type": "html",
"value": "Container Series #38",
"color": "99ccff",
"app_data": ""
},
"2": {
"type": "html",
"value": " ",
"app_data": ""
},
"3": {
"type": "html",
"value": "Contains one of the following:",
"app_data": ""
},
"4": {
"type": "html",
"value": "Glock-18 | Catacombs",
"color": "4b69ff",
"app_data": ""
},
"5": {
"type": "html",
"value": "M249 | System Lock",
"color": "4b69ff",
"app_data": ""
},
"6": {
"type": "html",
"value": "MP9 | Deadly Poison",
"color": "4b69ff",
"app_data": ""
},
"7": {
"type": "html",
"value": "SCAR-20 | Grotto",
"color": "4b69ff",
"app_data": ""
},
"8": {
"type": "html",
"value": "XM1014 | Quicksilver",
"color": "4b69ff",
"app_data": ""
},
"9": {
"type": "html",
"value": "Dual Berettas | Urban Shock",
"color": "8847ff",
"app_data": ""
},
"10": {
"type": "html",
"value": "Desert Eagle | Naga",
"color": "8847ff",
"app_data": ""
},
"11": {
"type": "html",
"value": "MAC-10 | Malachite",
"color": "8847ff",
"app_data": ""
},
"12": {
"type": "html",
"value": "Sawed-Off | Serenity",
"color": "8847ff",
"app_data": ""
},
"13": {
"type": "html",
"value": "AK-47 | Cartel",
"color": "d32ce6",
"app_data": ""
},
"14": {
"type": "html",
"value": "M4A4 | 龍王 (Dragon King)",
"color": "d32ce6",
"app_data": ""
},
"15": {
"type": "html",
"value": "P250 | Muertos",
"color": "d32ce6",
"app_data": ""
},
"16": {
"type": "html",
"value": "AWP | Man-o'-war",
"color": "eb4b4b",
"app_data": ""
},
"17": {
"type": "html",
"value": "Galil AR | Chatterbox",
"color": "eb4b4b",
"app_data": ""
},
"18": {
"type": "html",
"value": "or an Exceedingly Rare Special Item!",
"color": "ffd700",
"app_data": ""
},
"19": {
"type": "html",
"value": " ",
"app_data": ""
},
"20": {
"type": "html",
"value": "",
"color": "00a000",
"app_data": {
"limited": "1"
}
}
},
"owner_descriptions": "",
"tags": {
"0": {
"internal_name": "CSGO_Type_WeaponCase",
"name": "Container",
"category": "Type",
"category_name": "Type"
},
"1": {
"internal_name": "set_community_6",
"name": "The Chroma Collection",
"category": "ItemSet",
"category_name": "Collection"
},
"2": {
"internal_name": "normal",
"name": "Normal",
"category": "Quality",
"category_name": "Category"
},
"3": {
"internal_name": "Rarity_Common",
"name": "Base Grade",
"category": "Rarity",
"color": "b0c3d9",
"category_name": "Quality"
}
},
"classid": "720268538"
},
"success": true
}
}

Result can have multiple returns I was wondering how the struct should look, this is what I have so far but it returns like nothing.

type AssetInfo struct {
Result `json:"result"`
}
type Result struct {
Asset   map[string]Asset `json:"asset"`
Success bool             `json:"success,omitempty"`
}
type Asset struct {
IconUrl           string                   `json:"icon_url,omitempty"`
IconUrlLarge      string                   `json:"icon_url_large,omitempty"`
IconDragUrl       string                   `json:"icon_drag_url,omitempty"`
Name              string                   `json:"name,omitempty"`
MarketHashName    string                   `json:"market_hash_name,omitempty"`
MarketName        string                   `json:"market_name,omitempty"`
NameColor         string                   `json:"name_color,omitempty"`
BGColor           string                   `json:"background_color,omitempty"`
Type              string                   `json:"type,omitempty"`
Tradable          string                   `json:"tradable,omitempty"`
Marketable        string                   `json:"marketable,omitempty"`
Commodity         string                   `json:"commodity,omitempty"`
TradeRestrict     string                   `json:"market_tradeable_restriction,omitempty"`
FraudWarnings     string                   `json:"fraudwarnings,omitempty"`
Descriptions      map[string]*Descriptions `json:"descriptions,omitempty"`
OwnerDescriptions string                   `json:"owner_descriptions,omitempty"`
Tags              map[string]*Tags         `json:"tags,omitempty"`
ClassId           string                   `json:"classid,omitempty"`
}
type Descriptions struct {
Type    string `json:"type"`
Value   string `json:"value"`
Color   string `json:"color,omitempty"`
AppData string `json:"appdata"`
}
type Tags struct {
InternalName string `json:"internal_name"`
Name         string `json:"name"`
Category     string `json:"category"`
Color        string `json:"color,omitempty"`
CategoryName string `json:"category_name"`
}

If anybody could tell me what's wrong with my struct that would be great thanks.

The thing that confuses me is like how Descriptions returns not an array but multiple objects that could range from 0-20 how do I prepare a struct for this when I don't know how many objects are going to return, as well result can return multiple "720616831" so how should this look?

答案1

得分: 2

你将为第一个错误感到后悔——你的JSON中有result,但你的结构标签却是response

第二个问题比较棘手。问题在于你声明了Asset映射嵌套在结果中,键名为"asset",但事实并非如此。实际上,它只是结果中除了success/error之外的所有键的集合。不幸的是,encoding/json没有任何方法来表达这一点。你可以声明result是一个map[string]interface{},然后success/error(如果存在)将是bool/string,而assets将是更多的map[string]interface{},其中包含其他所有字段的键。这样做是可行的,但有点丑陋/低效,并且如果你想将其转换为适当的结构类型以便可以在其上定义方法,那将是很多工作。

也许更好的方法是解码为以下类型:

type AssetIntermediate struct {
    Result map[string]json.RawMessage `json:"result"`
}

以及

type Asset struct { /* 所有这些字段 */ }
type AssetInfo struct {
    Success bool
    Error   string
    Assets  map[string]Asset
}

然后你可以这样做:

var intermediate AssetIntermediate
err := json.Unmarshal(data, &intermediate)
/* 处理错误 */

var ai AssetInfo

/* 此时,intermediate.Result是一个字符串到json.RawMessage的映射,
 * json.RawMessage实际上是一个包含尚未解码JSON的[]byte。
 * 我们想要将每个字段放入ai中的相应位置。
 */
for k, v := range intermediate.Result {
    var err error
    // 错误和成功键解码到相应的字段
    if k == "error" {
        err = json.Unmarshal(v, &ai.Error)
    } else if k == "success" {
        err = json.Unmarshal(v, &ai.Success)
    } else {
        // 否则,我们有一个资产。首先解码它...
        var asset Asset
        err = json.Unmarshal(v, &asset)
        if err == nil {
            // 如果尚不存在,则创建Assets映射
            if ai.Assets == nil {
                ai.Assets = map[string]Asset{}
            }
            // 并将资产存储在映射中,键为k。
            ai.Assets[k] = asset
        }
    }
    /* 处理非nil的错误 */
}

这比单个Decode调用要复杂得多,但基本上应该能够正常工作。

如果所有这些都在一个返回(*AssetInfo, error)的函数内部,那么替换/* 处理错误 */的正确做法可能是:

if err != nil {
    return nil, err
}
英文:

You're going to kick yourself for the first mistake -- your JSON has result but your struct tag has response.

The second problem is a trickier one. The thing is that you declared that your Asset map is nested inside the result as a key called "asset", but that's not true. Actually it's just all of the keys of the result other than success/error. Unfortunately, encoding/json doesn't have any way to express this. You can say that result is a map[string]interface{}, and then success/error if they exist will be bool/string, and the assets will be more map[string]interface{}s containing keys for all of the other fields. That works, but it's a bit ugly/inefficient, and a lot of work if you want to convert into a proper struct type so that you can have methods on it.

Maybe a better way to go is to decode into this type:

type AssetIntermediate struct {
Result map[string]json.RawMessage `json:"result"`
}

as well as having

type Asset struct { /* all those fields */ }
type AssetInfo struct {
Success bool
Error string
Assets map[string]Asset
}

Then you can do

var intermediate AssetIntermediate
err := json.Unmarshal(data, &intermediate)
/* handle err */
var ai AssetInfo
/* At this point, intermediate.Result is a map
* of strings to json.RawMessage, which is just a []byte
* containing not-yet-decoded JSON. We want to take
* each field and put it into ai where it belongs.
*/
for k, v := range intermediate.Result {
var err error
// error and success keys are decoded into the respective fields
if k == "error" {
err = json.Unmarshal(v, &ai.Error)
} else if k == "success" {
err = json.Unmarshal(v, &ai.Success)
} else {
// Otherwise, we have an asset. First decode it...
var asset Asset
err = json.Unmarshal(v, &asset)
if err == nil {
// Create the Assets map if it doesn't exist yet
if ai.Assets == nil {
ai.Assets = map[string]Asset{}
}
// And store the asset in the map under the key k.
ai.Assets[k] = asset
}
}
/* handle err if non-nil */
}

which is a lot more work than a single Decode call, but it should basically do the right thing.

If all of this is inside a function that returns (*AssetInfo, error) then the right thing to put in place of /* Handle err */ might be

if err != nil {
return nil, err
}

答案2

得分: 1

我建议使用基本的map[string]interface{}类型,并从JSON字符串中进行迭代,直到构建整个结构体。对于那些你不太了解键的部分,比如0-20,可能只能使用map。

例如,假设你的示例字符串存储在变量j中,你可以这样开始:

type AssetInfo struct {
    Result map[string]interface{}
}

// 反序列化并打印以找到完整的结构体
var ai AssetInfo
json.Unmarshal([]byte(j), &ai)
fmt.Printf("%#v\n", ai)

这将打印出反序列化后构建的整个map。尝试在预先构建如此复杂的结构体之前,很难得到结果。

编辑:如果你像对待Descriptions一样将Result视为map[string]struct,那么Success布尔值将会“丢失”,并进入map,但其余的JSON将正确反序列化。我没有找到同时支持Success布尔值和其余结构体的map的方法。至于带有数字键的部分,如果你需要一个切片,在反序列化后需要进行一些处理。

type AssetInfo struct {
    Result map[string]Numbered
}

type Numbered struct {
    IconUrl          string                 `json:"icon_url"`
    IconUrlLarge     string                 `json:"icon_url_large"`
    IconDragUrl      string                 `json:"icon_drag_url"`
    Name             string                 `json:"name"`
    MarketHashName   string                 `json:"market_hash_name"`
    MarketName       string                 `json:"market_name"`
    NameColor        string                 `json:"name_color"`
    BGColor          string                 `json:"background_color"`
    Type             string                 `json:"type"`
    Tradable         string                 `json:"tradable"`
    Marketable       string                 `json:"marketable"`
    Commodity        string                 `json:"commodity"`
    TradeRestrict    string                 `json:"market_tradeable_restriction"`
    FraudWarnings    string                 `json:"fraudwarnings"`
    Descriptions     map[string]description `json:"descriptions"`
    OwnerDescription string                 `json:"owner_descriptions"`
    Tags             map[string]tag         `json:"tags"`
    ClassID	         string                 `json:"classid"`
}

type description struct {
    Type    string `json:"type"`
    Value   string `json:"value"`
    Color   string `json:"color,omitempty"`
    AppData string `json:"appdata"`
}

type tag struct {
    InternalName string `json:"internal_name"`
    Name         string `json:"name"`
    Category     string `json:"category"`
    Color        string `json:"color,omitempty"`
    CategoryName string `json:"category_name"`
}

func main() {
    var ai AssetInfo
    json.Unmarshal([]byte(j), &ai)
    fmt.Printf("%+v\n", ai.Result["720268538"].Tags)
}

Tags的输出为map[0:{InternalName:CSGO_Type_WeaponCase Name:Container Category:Type Color: CategoryName:Type} 1:{InternalName:set_community_6 Name:The Chroma Collection Category:ItemSet Color: CategoryName:Collection} (...)

编辑2:在评论中讨论后,我得到了与hobbs的答案非常相似的代码。他的代码可能组织得更好。

由于"success"true值无法放入Numbered结构体中,我们需要使用json.RawMessage

type AssetInfo struct {
    Result map[string]json.RawMessage
}

err := json.Unmarshal([]byte(j), &ai)
fmt.Println("outer error", err) // 输出 <nil>
for k, v := range ai.Result {
    // 这与文档中的示例非常相似:https://golang.org/pkg/encoding/json/#RawMessage
    var dst interface{} 
    if k == "success" {
        dst = new(bool)
    } else {
        dst = new(Numbered)
    }
    err := json.Unmarshal(v, dst)
    fmt.Println("inner error", err) // 输出 <nil>
    if k == "success" {
        b := dst.(*bool)
        fmt.Printf("%t\n", *b) // 输出 true
    } else {
        fmt.Printf("%+v\n", dst) // 输出整个Numbered结构体
    }
}
英文:

I suggest playing with the base map[string]interface{} type and iterating from the json string until you can build the whole struct. For the parts where you don't really know the keys, such as the 0-20, the map may be all you'll have.

For example, suppose the string your example is in variable j. You could start with:

type AssetInfo struct {
Result map[string]interface{}
}

and start unmarshaling and printing in order to find the full struct:

var ai AssetInfo
json.Unmarshal([]byte(j), &amp;ai)
fmt.Printf(&quot;%#v\n&quot;, ai)

This prints the entire map built after unmarshaling. Trying to build the whole struct beforehand with such a complex structure is unlikely to get you results.

EDIT: If you treat Result as a map[string]struct like you did with Descriptions the Success bool is "lost" and goes into the map but the rest of the json unmarshals correctly. I don't see a way to support the Success bool as well as a map for the rest of the structure. As for the numbered keys, if you need a slice, you'll have to do some work after the unmarshal.

type AssetInfo struct {
Result map[string]Numbered
}
type Numbered struct {
IconUrl          string                 `json:&quot;icon_url&quot;`
IconUrlLarge     string                 `json:&quot;icon_url_large&quot;`
IconDragUrl      string                 `json:&quot;icon_drag_url&quot;`
Name             string                 `json:&quot;name&quot;`
MarketHashName   string                 `json:&quot;market_hash_name&quot;`
MarketName       string                 `json:&quot;market_name&quot;`
NameColor        string                 `json:&quot;name_color&quot;`
BGColor          string                 `json:&quot;background_color&quot;`
Type             string                 `json:&quot;type&quot;`
Tradable         string                 `json:&quot;tradable&quot;`
Marketable       string                 `json:&quot;marketable&quot;`
Commodity        string                 `json:&quot;commodity&quot;`
TradeRestrict    string                 `json:&quot;market_tradeable_restriction&quot;`
FraudWarnings    string                 `json:&quot;fraudwarnings&quot;`
Descriptions     map[string]description `json:&quot;descriptions&quot;`
OwnerDescription string                 `json:&quot;owner_descriptions&quot;`
Tags             map[string]tag         `json:&quot;tags&quot;`
ClassID	         string                 `json:&quot;classid&quot;`
}
type description struct {
Type    string `json:&quot;type&quot;`
Value   string `json:&quot;value&quot;`
Color   string `json:&quot;color,omitempty&quot;`
AppData string `json:&quot;appdata&quot;`
}
type tag struct {
InternalName string `json:&quot;internal_name&quot;`
Name         string `json:&quot;name&quot;`
Category     string `json:&quot;category&quot;`
Color        string `json:&quot;color,omitempty&quot;`
CategoryName string `json:&quot;category_name&quot;`
}
func main() {
var ai AssetInfo
json.Unmarshal([]byte(j), &amp;ai)
fmt.Printf(&quot;%+v\n&quot;, ai.Result[&quot;720268538&quot;].Tags)
}

The output of the Tags is map[0:{InternalName:CSGO_Type_WeaponCase Name:Container Category:Type Color: CategoryName:Type} 1:{InternalName:set_community_6 Name:The Chroma Collection Category:ItemSet Color: CategoryName:Collection} (...)

EDIT 2: After discussion in the comments I ended up at some code which is very similar to hobbs' answer. His might be better organized.

We need to use json.RawMessage since the true value for &quot;success&quot; can't go into the Numbered struct.

type AssetInfo struct {
Result map[string]json.RawMessage
}
err := json.Unmarshal([]byte(j), &amp;ai)
fmt.Println(&quot;outer error&quot;, err) // prints &lt;nil&gt;
for k, v := range ai.Result {
// this is very similar to the example on the docs: https://golang.org/pkg/encoding/json/#RawMessage
var dst interface{} 
if k == &quot;success&quot; {
dst = new(bool)
} else {
dst = new(Numbered)
}
err := json.Unmarshal(v, dst)
fmt.Println(&quot;inner error&quot;, err) // prints &lt;nil&gt;
if k == &quot;success&quot; {
b := dst.(*bool)
fmt.Printf(&quot;%t\n&quot;, *b) // true
} else {
fmt.Printf(&quot;%+v\n&quot;, dst) // the whole Numbered struct
}
}

huangapple
  • 本文由 发表于 2015年8月29日 09:05:20
  • 转载请务必保留本文链接:https://go.coder-hub.com/32281711.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定