简体   繁体   English

Javascript在大对象中查找嵌套键

[英]Javascript find nested key in a large object

I'm trying to get the value of eth0 nested inside this JSON object我正在尝试获取嵌套在此 JSON 对象中的eth0的值

"addresses": {
  "eth0": [
    "10.0.3.188"
  ]
},

I was using underscore.js to simplify the process我使用 underscore.js 来简化过程

var _ = require('underscore')._;

var jsonData = {
    "plays": [{
        "play": {
            "id": "d10aae34-6713-4e14-8ad5-fa2fbf6aa2b5",
            "name": "lxc"
        },
        "tasks": [{
                "hosts": {
                    "lxc.server.com": {
                        "_ansible_no_log": false,
                        "_ansible_parsed": true,
                        "changed": true,
                        "cmd": "lxc copy base \"bar69\"",
                        "delta": "0:00:01.417533",
                        "end": "2017-01-10 18:01:28.692981",
                        "invocation": {
                            "module_args": {
                                "_raw_params": "lxc copy base \"bar69\"",
                                "_uses_shell": true,
                                "chdir": null,
                                "creates": null,
                                "executable": null,
                                "removes": null,
                                "warn": true
                            },
                            "module_name": "command"
                        },
                        "rc": 0,
                        "start": "2017-01-10 18:01:27.275448",
                        "stderr": "",
                        "stdout": "",
                        "stdout_lines": [],
                        "warnings": []
                    }
                },
                "task": {
                    "id": "297bf7b7-9ee7-4517-8763-bc3b15baa6e2",
                    "name": "clone from base"
                }
            },
            {
                "hosts": {
                    "lxc.server.com": {
                        "_ansible_no_log": false,
                        "_ansible_parsed": true,
                        "changed": true,
                        "cmd": "lxc config set \"bar69\" security.privileged true",
                        "delta": "0:00:00.053403",
                        "end": "2017-01-10 18:01:32.270750",
                        "invocation": {
                            "module_args": {
                                "_raw_params": "lxc config set \"bar69\" security.privileged true",
                                "_uses_shell": true,
                                "chdir": null,
                                "creates": null,
                                "executable": null,
                                "removes": null,
                                "warn": true
                            },
                            "module_name": "command"
                        },
                        "rc": 0,
                        "start": "2017-01-10 18:01:32.217347",
                        "stderr": "",
                        "stdout": "",
                        "stdout_lines": [],
                        "warnings": []
                    }
                },
                "task": {
                    "id": "bc63ad6f-1808-48b8-a1de-729153d2b0c5",
                    "name": "Promote to privileged ct"
                }
            },
            {
                "hosts": {
                    "lxc.server.com": {
                        "_ansible_no_log": false,
                        "_ansible_parsed": true,
                        "actions": [
                            "start"
                        ],
                        "addresses": {
                            "eth0": [
                                "10.0.3.188"
                            ]
                        },
                        "changed": true,
                        "invocation": {
                            "module_args": {
                                "architecture": null,
                                "cert_file": "/root/.config/lxc/client.crt",
                                "config": null,
                                "description": null,
                                "devices": null,
                                "ephemeral": null,
                                "force_stop": false,
                                "key_file": "/root/.config/lxc/client.key",
                                "name": "bar69",
                                "profiles": null,
                                "source": null,
                                "state": "started",
                                "timeout": 30,
                                "trust_password": null,
                                "url": "unix:/var/lib/lxd/unix.socket",
                                "wait_for_ipv4_addresses": true
                            },
                            "module_name": "lxd_container"
                        },
                        "log_verbosity": 0,
                        "old_state": "stopped"
                    }
                },
                "task": {
                    "id": "466c0da9-6cbf-4196-aea9-109218c3ed5f",
                    "name": "Start CT"
                }
            },
            {
                "hosts": {
                    "lxc.server.com": {
                        "_ansible_no_log": false,
                        "_ansible_verbose_always": true,
                        "changed": false,
                        "invocation": {
                            "module_args": {
                                "msg": [
                                    "10.0.3.188"
                                ]
                            },
                            "module_name": "debug"
                        },
                        "msg": [
                            "10.0.3.188"
                        ]
                    }
                },
                "task": {
                    "id": "978c490e-59c3-41d2-818d-ab4b557ad803",
                    "name": ""
                }
            }
        ]
    }],
    "stats": {
        "lxc.server.com": {
            "changed": 3,
            "failures": 0,
            "ok": 4,
            "skipped": 0,
            "unreachable": 0
        }
    }
}

This is what I have tried so far but no luck!这是我迄今为止尝试过但没有运气的方法!

console.log(_.findKey(_.values(jsonData.tasks)));

Your help is highly appreciated非常感谢您的帮助

You can do this without underscore.您可以不使用下划线执行此操作。 An approach you can take is take the given property string and a task, split the property based on the deliminator (you cannot use '.' because you have dotted properties like 'lxc.server.com' ), and recursively examine the object until you find the value (or not).您可以采用的方法是采用给定的属性字符串和任务,根据分隔符拆分属性(您不能使用'.'因为您有像'lxc.server.com'这样'lxc.server.com'点属性),然后递归检查对象,直到你找到了价值(或没有)。

Note: The solutions bellow assume you want to pass a delimited property string but you can just as easily pass an array of props directly and not wrap the helper function.注意:下面的解决方案假设您想传递一个分隔的属性字符串,但您可以直接传递一个 props 数组而不是包装辅助函数。

Tail-cail recursive solution尾尾递归解决方案

 var jsonData={plays:[{play:{id:"d10aae34-6713-4e14-8ad5-fa2fbf6aa2b5",name:"lxc"},tasks:[{hosts:{"lxc.server.com":{_ansible_no_log:!1,_ansible_parsed:!0,changed:!0,cmd:'lxc copy base "bar69"',delta:"0:00:01.417533",end:"2017-01-10 18:01:28.692981",invocation:{module_args:{_raw_params:'lxc copy base "bar69"',_uses_shell:!0,chdir:null,creates:null,executable:null,removes:null,warn:!0},module_name:"command"},rc:0,start:"2017-01-10 18:01:27.275448",stderr:"",stdout:"",stdout_lines:[],warnings:[]}},task:{id:"297bf7b7-9ee7-4517-8763-bc3b15baa6e2",name:"clone from base"}},{hosts:{"lxc.server.com":{_ansible_no_log:!1,_ansible_parsed:!0,changed:!0,cmd:'lxc config set "bar69" security.privileged true',delta:"0:00:00.053403",end:"2017-01-10 18:01:32.270750",invocation:{module_args:{_raw_params:'lxc config set "bar69" security.privileged true',_uses_shell:!0,chdir:null,creates:null,executable:null,removes:null,warn:!0},module_name:"command"},rc:0,start:"2017-01-10 18:01:32.217347",stderr:"",stdout:"",stdout_lines:[],warnings:[]}},task:{id:"bc63ad6f-1808-48b8-a1de-729153d2b0c5",name:"Promote to privileged ct"}},{hosts:{"lxc.server.com":{_ansible_no_log:!1,_ansible_parsed:!0,actions:["start"],addresses:{eth0:["10.0.3.188"]},changed:!0,invocation:{module_args:{architecture:null,cert_file:"/root/.config/lxc/client.crt",config:null,description:null,devices:null,ephemeral:null,force_stop:!1,key_file:"/root/.config/lxc/client.key",name:"bar69",profiles:null,source:null,state:"started",timeout:30,trust_password:null,url:"unix:/var/lib/lxd/unix.socket",wait_for_ipv4_addresses:!0},module_name:"lxd_container"},log_verbosity:0,old_state:"stopped"}},task:{id:"466c0da9-6cbf-4196-aea9-109218c3ed5f",name:"Start CT"}},{hosts:{"lxc.server.com":{_ansible_no_log:!1,_ansible_verbose_always:!0,changed:!1,invocation:{module_args:{msg:["10.0.3.188"]},module_name:"debug"},msg:["10.0.3.188"]}},task:{id:"978c490e-59c3-41d2-818d-ab4b557ad803",name:""}}]}],stats:{"lxc.server.com":{changed:3,failures:0,ok:4,skipped:0,unreachable:0}}}; function getNestedPropHelper(obj, [first, ...rest]) { // base case if (typeof obj !== 'object' || !obj) return undefined; return rest.length === 0 // if we only have one property ? obj[first] // return the value : getNestedPropHelper(obj[first], rest); // otherwise recursively return the rest } function getNestedProp(obj, prop, delim = '|') { return getNestedPropHelper(obj, prop.split(delim)); } // extract the tasks const tasks = jsonData.plays.reduce((arr, play) => arr.concat(play.tasks), []); // get the eth0 property for each task const props = tasks.map(task => getNestedProp(task, 'hosts|lxc.server.com|addresses|eth0') ); // log eth0 properties for each task (only the third one actually has the value) console.log(props);

You can also do this iteratively, which should usually be faster (although not much in environments that support tail-calls):您也可以迭代地执行此操作,这通常应该更快(尽管在支持尾调用的环境中不多):

Iterative solution迭代解决方案

 var jsonData={plays:[{play:{id:"d10aae34-6713-4e14-8ad5-fa2fbf6aa2b5",name:"lxc"},tasks:[{hosts:{"lxc.server.com":{_ansible_no_log:!1,_ansible_parsed:!0,changed:!0,cmd:'lxc copy base "bar69"',delta:"0:00:01.417533",end:"2017-01-10 18:01:28.692981",invocation:{module_args:{_raw_params:'lxc copy base "bar69"',_uses_shell:!0,chdir:null,creates:null,executable:null,removes:null,warn:!0},module_name:"command"},rc:0,start:"2017-01-10 18:01:27.275448",stderr:"",stdout:"",stdout_lines:[],warnings:[]}},task:{id:"297bf7b7-9ee7-4517-8763-bc3b15baa6e2",name:"clone from base"}},{hosts:{"lxc.server.com":{_ansible_no_log:!1,_ansible_parsed:!0,changed:!0,cmd:'lxc config set "bar69" security.privileged true',delta:"0:00:00.053403",end:"2017-01-10 18:01:32.270750",invocation:{module_args:{_raw_params:'lxc config set "bar69" security.privileged true',_uses_shell:!0,chdir:null,creates:null,executable:null,removes:null,warn:!0},module_name:"command"},rc:0,start:"2017-01-10 18:01:32.217347",stderr:"",stdout:"",stdout_lines:[],warnings:[]}},task:{id:"bc63ad6f-1808-48b8-a1de-729153d2b0c5",name:"Promote to privileged ct"}},{hosts:{"lxc.server.com":{_ansible_no_log:!1,_ansible_parsed:!0,actions:["start"],addresses:{eth0:["10.0.3.188"]},changed:!0,invocation:{module_args:{architecture:null,cert_file:"/root/.config/lxc/client.crt",config:null,description:null,devices:null,ephemeral:null,force_stop:!1,key_file:"/root/.config/lxc/client.key",name:"bar69",profiles:null,source:null,state:"started",timeout:30,trust_password:null,url:"unix:/var/lib/lxd/unix.socket",wait_for_ipv4_addresses:!0},module_name:"lxd_container"},log_verbosity:0,old_state:"stopped"}},task:{id:"466c0da9-6cbf-4196-aea9-109218c3ed5f",name:"Start CT"}},{hosts:{"lxc.server.com":{_ansible_no_log:!1,_ansible_verbose_always:!0,changed:!1,invocation:{module_args:{msg:["10.0.3.188"]},module_name:"debug"},msg:["10.0.3.188"]}},task:{id:"978c490e-59c3-41d2-818d-ab4b557ad803",name:""}}]}],stats:{"lxc.server.com":{changed:3,failures:0,ok:4,skipped:0,unreachable:0}}}; function getNestedPropHelper(obj, props) { const isObject = (obj) => typeof obj === 'object' && obj; if (!isObject(obj)) return undefined; // keep extracting the properties for (const prop of props) { obj = obj[prop]; // of we come across a non-object property before we're done, the property path is invalid if (!isObject(obj)) return undefined; } // if we reached this point, we found the value return obj; } function getNestedProp(obj, prop, delim = '|') { return getNestedPropHelper(obj, prop.split(delim)); } // extract the tasks const tasks = jsonData.plays.reduce((arr, play) => arr.concat(play.tasks), []); // get the eth0 property for each task const props = tasks.map(task => getNestedProp(task, 'hosts|lxc.server.com|addresses|eth0') ); // log eth0 properties for each task (only the third one actually has the value) console.log(props);

Curried recursive solution same approach can be taken for the iterative one柯里化递归解决方案可以对迭代解决方案采取相同的方法

You could also curry the function so you can build getters for certain property paths.你也可以讨好的功能,这样你就可以建立一定的财产路径干将。

 var jsonData={plays:[{play:{id:"d10aae34-6713-4e14-8ad5-fa2fbf6aa2b5",name:"lxc"},tasks:[{hosts:{"lxc.server.com":{_ansible_no_log:!1,_ansible_parsed:!0,changed:!0,cmd:'lxc copy base "bar69"',delta:"0:00:01.417533",end:"2017-01-10 18:01:28.692981",invocation:{module_args:{_raw_params:'lxc copy base "bar69"',_uses_shell:!0,chdir:null,creates:null,executable:null,removes:null,warn:!0},module_name:"command"},rc:0,start:"2017-01-10 18:01:27.275448",stderr:"",stdout:"",stdout_lines:[],warnings:[]}},task:{id:"297bf7b7-9ee7-4517-8763-bc3b15baa6e2",name:"clone from base"}},{hosts:{"lxc.server.com":{_ansible_no_log:!1,_ansible_parsed:!0,changed:!0,cmd:'lxc config set "bar69" security.privileged true',delta:"0:00:00.053403",end:"2017-01-10 18:01:32.270750",invocation:{module_args:{_raw_params:'lxc config set "bar69" security.privileged true',_uses_shell:!0,chdir:null,creates:null,executable:null,removes:null,warn:!0},module_name:"command"},rc:0,start:"2017-01-10 18:01:32.217347",stderr:"",stdout:"",stdout_lines:[],warnings:[]}},task:{id:"bc63ad6f-1808-48b8-a1de-729153d2b0c5",name:"Promote to privileged ct"}},{hosts:{"lxc.server.com":{_ansible_no_log:!1,_ansible_parsed:!0,actions:["start"],addresses:{eth0:["10.0.3.188"]},changed:!0,invocation:{module_args:{architecture:null,cert_file:"/root/.config/lxc/client.crt",config:null,description:null,devices:null,ephemeral:null,force_stop:!1,key_file:"/root/.config/lxc/client.key",name:"bar69",profiles:null,source:null,state:"started",timeout:30,trust_password:null,url:"unix:/var/lib/lxd/unix.socket",wait_for_ipv4_addresses:!0},module_name:"lxd_container"},log_verbosity:0,old_state:"stopped"}},task:{id:"466c0da9-6cbf-4196-aea9-109218c3ed5f",name:"Start CT"}},{hosts:{"lxc.server.com":{_ansible_no_log:!1,_ansible_verbose_always:!0,changed:!1,invocation:{module_args:{msg:["10.0.3.188"]},module_name:"debug"},msg:["10.0.3.188"]}},task:{id:"978c490e-59c3-41d2-818d-ab4b557ad803",name:""}}]}],stats:{"lxc.server.com":{changed:3,failures:0,ok:4,skipped:0,unreachable:0}}}; function getNestedPropHelper(obj, [first, ...rest]) { // base case if (typeof obj !== 'object' || !obj) return undefined; return rest.length === 0 // if we only have one property ? obj[first] // return the value : getNestedPropHelper(obj[first], rest); // otherwise recursively return the rest } function getNestedProp(prop, delim = '|') { const props = prop.split(delim); return function(obj) { return getNestedPropHelper(obj, props); } } // now you have a getter that will extract eth0 for any task const getEth0 = getNestedProp('hosts|lxc.server.com|addresses|eth0'); // extract the tasks const tasks = jsonData.plays.reduce((arr, play) => arr.concat(play.tasks), []); // extract eth0 from each task const props = tasks.map(getEth0); // log eth0 properties for each task (only the third one actually has the value) console.log(props);

You could use a few functions like _.pluck() to get the objects at key host , _.property() to see if each host object has a key addresses , .map() to get a mapping of the values, ._filter() to see if a value was returned from the mapping, etc.:你可以使用一些函数,比如_.pluck()来获取关键主机上的对象, _.property()来查看每个主机对象是否有一个关键地址.map()来获取值的映射, ._filter ()以查看是否从映射等中返回了值:

var hosts = _.pluck(jsonData.plays[0].tasks, 'hosts');
var mapping = _.map(hosts, function(host) {
    var keys = _.keys(host);
    if (_.size(keys)) {
      var nestedHost = host[_.first(keys)];
      if (_.property('addresses')(nestedHost)) {
        if (_.property('eth0')(nestedHost.addresses)) {
          return nestedHost.addresses.eth0[0];
        }
      }
    }
  });
console.log(_.filter(mapping));

See it in action in this plunker , as well as the example below:这个 plunker 中查看它的实际效果,以及下面的示例:

 var jsonData = { "plays": [{ "play": { "id": "d10aae34-6713-4e14-8ad5-fa2fbf6aa2b5", "name": "lxc" }, "tasks": [{ "hosts": { "lxc.server.com": { "_ansible_no_log": false, "_ansible_parsed": true, "changed": true, "cmd": "lxc copy base \\"bar69\\"", "delta": "0:00:01.417533", "end": "2017-01-10 18:01:28.692981", "invocation": { "module_args": { "_raw_params": "lxc copy base \\"bar69\\"", "_uses_shell": true, "chdir": null, "creates": null, "executable": null, "removes": null, "warn": true }, "module_name": "command" }, "rc": 0, "start": "2017-01-10 18:01:27.275448", "stderr": "", "stdout": "", "stdout_lines": [], "warnings": [] } }, "task": { "id": "297bf7b7-9ee7-4517-8763-bc3b15baa6e2", "name": "clone from base" } }, { "hosts": { "lxc.server.com": { "_ansible_no_log": false, "_ansible_parsed": true, "changed": true, "cmd": "lxc config set \\"bar69\\" security.privileged true", "delta": "0:00:00.053403", "end": "2017-01-10 18:01:32.270750", "invocation": { "module_args": { "_raw_params": "lxc config set \\"bar69\\" security.privileged true", "_uses_shell": true, "chdir": null, "creates": null, "executable": null, "removes": null, "warn": true }, "module_name": "command" }, "rc": 0, "start": "2017-01-10 18:01:32.217347", "stderr": "", "stdout": "", "stdout_lines": [], "warnings": [] } }, "task": { "id": "bc63ad6f-1808-48b8-a1de-729153d2b0c5", "name": "Promote to privileged ct" } }, { "hosts": { "lxc.server.com": { "_ansible_no_log": false, "_ansible_parsed": true, "actions": [ "start" ], "addresses": { "eth0": [ "10.0.3.188" ] }, "changed": true, "invocation": { "module_args": { "architecture": null, "cert_file": "/root/.config/lxc/client.crt", "config": null, "description": null, "devices": null, "ephemeral": null, "force_stop": false, "key_file": "/root/.config/lxc/client.key", "name": "bar69", "profiles": null, "source": null, "state": "started", "timeout": 30, "trust_password": null, "url": "unix:/var/lib/lxd/unix.socket", "wait_for_ipv4_addresses": true }, "module_name": "lxd_container" }, "log_verbosity": 0, "old_state": "stopped" } }, "task": { "id": "466c0da9-6cbf-4196-aea9-109218c3ed5f", "name": "Start CT" } }, { "hosts": { "lxc.server.com": { "_ansible_no_log": false, "_ansible_verbose_always": true, "changed": false, "invocation": { "module_args": { "msg": [ "10.0.3.188" ] }, "module_name": "debug" }, "msg": [ "10.0.3.188" ] } }, "task": { "id": "978c490e-59c3-41d2-818d-ab4b557ad803", "name": "" } }] }], "stats": { "lxc.server.com": { "changed": 3, "failures": 0, "ok": 4, "skipped": 0, "unreachable": 0 } } }; document.addEventListener('DOMContentLoaded', function() { var hosts = _.pluck(jsonData.plays[0].tasks, 'hosts'); var mapping = _.map(hosts, function(host) { var keys = _.keys(host); if (_.size(keys)) { var nestedHost = host[_.first(keys)]; if (_.property('addresses')(nestedHost)) { if (_.property('eth0')(nestedHost.addresses)) { return nestedHost.addresses.eth0[0]; } } } }); document.getElementById('console').innerHTML = _.filter(mapping); });
 <script data-require="underscore.js@*" data-semver="1.8.3" src="//cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.3/underscore-min.js"></script> Address(es): <div id="console"></div>

If you want to access it directly, try the following:如果您想直接访问它,请尝试以下操作:

jsonData.plays[0].tasks[2].hosts["lxc.server.com"].addresses.eth0[0] jsonData.plays[0].tasks[2].hosts["lxc.server.com"].addresses.eth0[0]

Edit: This was tested.编辑:这是经过测试的。 The post that has tasks[3] is incorrect.有任务[3] 的帖子不正确。

We use object-scan for many data processing tasks.我们将对象扫描用于许多数据处理任务。 It's powerful and fast once you wrap your head around it.一旦你将头环绕在它周围,它就会强大而快速。 Here is how you could solve your question这是您解决问题的方法

 // const objectScan = require('object-scan'); const find = (input) => objectScan(['**.addresses.eth0[0]'], { abort: true, rtn: 'value' })(input); const jsonData = { plays: [{ play: { id: 'd10aae34-6713-4e14-8ad5-fa2fbf6aa2b5', name: 'lxc' }, tasks: [{ hosts: { 'lxc.server.com': { _ansible_no_log: false, _ansible_parsed: true, changed: true, cmd: 'lxc copy base "bar69"', delta: '0:00:01.417533', end: '2017-01-10 18:01:28.692981', invocation: { module_args: { _raw_params: 'lxc copy base "bar69"', _uses_shell: true, chdir: null, creates: null, executable: null, removes: null, warn: true }, module_name: 'command' }, rc: 0, start: '2017-01-10 18:01:27.275448', stderr: '', stdout: '', stdout_lines: [], warnings: [] } }, task: { id: '297bf7b7-9ee7-4517-8763-bc3b15baa6e2', name: 'clone from base' } }, { hosts: { 'lxc.server.com': { _ansible_no_log: false, _ansible_parsed: true, changed: true, cmd: 'lxc config set "bar69" security.privileged true', delta: '0:00:00.053403', end: '2017-01-10 18:01:32.270750', invocation: { module_args: { _raw_params: 'lxc config set "bar69" security.privileged true', _uses_shell: true, chdir: null, creates: null, executable: null, removes: null, warn: true }, module_name: 'command' }, rc: 0, start: '2017-01-10 18:01:32.217347', stderr: '', stdout: '', stdout_lines: [], warnings: [] } }, task: { id: 'bc63ad6f-1808-48b8-a1de-729153d2b0c5', name: 'Promote to privileged ct' } }, { hosts: { 'lxc.server.com': { _ansible_no_log: false, _ansible_parsed: true, actions: ['start'], addresses: { eth0: ['10.0.3.188'] }, changed: true, invocation: { module_args: { architecture: null, cert_file: '/root/.config/lxc/client.crt', config: null, description: null, devices: null, ephemeral: null, force_stop: false, key_file: '/root/.config/lxc/client.key', name: 'bar69', profiles: null, source: null, state: 'started', timeout: 30, trust_password: null, url: 'unix:/var/lib/lxd/unix.socket', wait_for_ipv4_addresses: true }, module_name: 'lxd_container' }, log_verbosity: 0, old_state: 'stopped' } }, task: { id: '466c0da9-6cbf-4196-aea9-109218c3ed5f', name: 'Start CT' } }, { hosts: { 'lxc.server.com': { _ansible_no_log: false, _ansible_verbose_always: true, changed: false, invocation: { module_args: { msg: ['10.0.3.188'] }, module_name: 'debug' }, msg: ['10.0.3.188'] } }, task: { id: '978c490e-59c3-41d2-818d-ab4b557ad803', name: '' } }] }], stats: { 'lxc.server.com': { changed: 3, failures: 0, ok: 4, skipped: 0, unreachable: 0 } } }; console.log(find(jsonData)); // => 10.0.3.188
 .as-console-wrapper {max-height: 100% !important; top: 0}
 <script src="https://bundle.run/object-scan@13.8.0"></script>

Disclaimer : I'm the author of object-scan免责声明:我是对象扫描的作者

you can use this code to loop on object properties .您可以使用此代码循环对象属性。

   for (key in jsonData){
       if(key == "eth0" ){
          console.log(jsonData[key].toString())
       }
    }

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM