当前位置:   article > 正文

Python 图算法系列8-图数据库阅读及简单实验_图数据科学算法实验环境

图数据科学算法实验环境

说明

看看这本书,里面还是有不少常识和启发的。
在这里插入图片描述
本篇会摘取一些里面比较好的内容,然后做一个简单但比较有用的应用。

Mysql这样的数据库,在需要多个join(递归)时表现会很差。在例如「商品推荐」这样的问题时就会碰到比较大的问题。

例如在库中寻找朋友关系

层数Mysql执行时间Neo4j返回记录数
20.0160.01~2500
330.2670.168~11000
41543.5051.359~600000
5未完成2.132~800000

可以看出来两种库的差别。

模型是为了让不规则的领域的一些具体方面变成结构化的,可操纵的空间。对于事物实际存在的方式,并没有一种天然的表达方式,我们只能有目的地选择、抽象和简化,一些方法能更好地满足特定的目标。

这个定义是我见过比较好的一种(现在的图像和语音等都是转为矩阵计算的)。和另一个异曲同工:

所有的模型都是错的,但有一些模型有用(可以用)。

关于图数据库的启发性:

图数据库减少了分析和实现之间的阻抗不匹配问题,这个问题已经困扰关系型数据库很多年了。特别有意思的一点是,图模型不仅描绘除了我们是如何看待事物时间的关系,还清晰地表达出了我们可能在这个领域出提出的问题。

关于动态系统的描述和安排,例如一个数据中心的部署,用图来表示是非常自然的(回头我们会继续说)

关系型数据库因为死板的模式以及复杂的建模特性,使得它对于快速变化的需求束手无策。我们想要的模型是一个可以和领域保持高度一致,不用牺牲性能,同时还能支撑业务的发展,也可以在快速的变化和增长之中保持数据的完整性的模型。

上面这段话的意义更大。如果有过系统的实践和应对甲方爸爸的需求,感受会更深刻。

如何验证一个图数据库是否构建好了?

1 最简单的方式是观察图是不是通俗易懂。找一个起始节点,沿着练习找他相邻的节点,同时读出每个节点的标签和每个联系的名字,看是否能连成一个有意义的句子。
2 我们还需要考虑什么样的查询会在这个图上运行。

平时怎么去应用图呢?

使用复杂事件处理(CEP)来应对底层需求,当重要领域更新时才更新我们的图。

图就像是大脑中枢,最终的处理一定是汇聚成一个有向、无环的,关系上统计归一的主图。(类似于主表、宽表)之前也讨论过py2neo和cypher处理关系的差别。py2neo默认会把同种关系的属性合并在一起,就是主图的概念;而cypher允许在两个节点间创建同种关系的多条边,类似日志图。

都有用,但我们在决策时肯定是根据主图(一个或多个)进行建模和决策的。

商业洞察力往往依赖于我们队复杂价值链背后的网络效应的理解。为了到达一定程度的理解,我们需要联合多个领域,同时又不能让每个领域的细节失真或者牺牲掉。属性图为这种情况提供了解决方案。使用属性图,我们可以给一个价值链建模,使其成为一个图的集合,每张图里都由具体的联系关联其子领域,又能将其区别开来。

不仅仅是商业,真正的知识图谱也有赖与如上所述的特点。

关系是图数据库里的一等公民,标签是属性里的一等公民。关系反映了变化,标签表示了集合(分类)

图的查询(先明确要查的是什么,节点,还是关系):

  • 1 通过match确定锚点(可以是多个节点,多个关系,用逗号分开)
  • 2 用where约束匹配(这个sql的where没啥差别)
  • 3 用return返回结果 function result as rename_result
  • 4 使用with组成查询链

信心交流模式Fenix是一个经典的图问题,它涉及用图去发现领域专家、关键影响力以及信息传播的通信通道。

反过来,也可以用于找坏人。

建立图模型最关键的一步是区分哪些被抽象为实体(对应的关系也就顺理成章了)。我认为,凡是可以“持久化”的就是实体,而影响“持久化”的则是关系。例如文中提到发现垃圾邮件问题,邮件(虽然不是实物),但是从数据上是一种可持久化的虚拟实体,而不是被归为“发邮件”这样的动作中。当然,视具体的问题而定,图的抽象可能也会不同,不过先建立一个小的原则更有助于思考。
文中的建议是:

常用的名字可以作为标签。(点)
带有宾语的动词作为联系名称。(边)

这两条建议很对,顺带的想起了我的另一套东西( 修饰+名字-> 泛名词;修饰+动词 -> 泛动词),以后有机会再写。

用节点表示事物,用联系表示结构

  • 1 使用节点表示实体,即我们感兴趣的事物,他们可以被标签和分组。
  • 2 使用联系表示节点之间的关联,并为每个实体建立语义上下文,从而构建领域。
  • 3 使用有向联系进一步阐明语义联系。属性图中常常存在有向联系,其原因是练习的不对称性。对于双向连接,我们通过在查询中忽略方向,而非使用两个练习。
  • 4 使用节点属性以及任何有必要的实体元数据,如时间戳、版本号等,来表示实体的属性
  • 5 使用联系属性以及必要的关系元数据,如果时间戳、版本号,来表达联系的强度、权重或质量

这些概念对应了图的一些创建概念,可以参考我的另一篇文章

当两个或多个领域实体在一段时间内交互,事实(Fact)就出现了。依据结果,即行为(Action)产生的事物用来建模行为。

关系除了有起始时间,可能还可以设置终止时间。
实体之间发生关系,可能生成新的实体。(道生一,一生二,二生三,三生万物)例如A和B在一起洽谈,生成了一个新的实体(合同)。

两种常用的图结构:

  • 1 时间轴树
  • 2 链表
  • 1 时间轴树可以反映事件随时间的变化。顺着时间轴“翻书”是一件很正常的事情,可以做例如日程安排和用户每日上传文件的分类存放。

    • 1 创建一个根节点(e.g. timeline), 这个可以理解为一本书的壳子
    • 2 年份节点,理解为书的章
    • 3 月份节点,书的节
    • 4 日节点,页
    • 5 事件节点,页里面的内容
  • 2 链表反映事物的时序变化。 这个结构对于很多工艺流程比较长的工作来说非常有用,例如建模过程。

从数据到形成模型的过程非常复杂,最大的难度是“深”。例如之前提到的FuncChain(函数链)就很适合用Neo4j来实现。
虽然可以不基于任何数据库来完成FuncChain, 但是实际使用可能会有障碍。

  • 1 使用者很难看到全貌。如果不基于Neo4j,那么整个流程链条(可能有很多)很难看清楚。当然我们也可以自己构建web来展示,但总之不适合在终端中使用文字查看。Neo4j自己提供了一个web端,所以初期我觉得没必要自己折腾(目前我看到自定义的图显示做的都不如neo4j美观)

  • 2 持续性工作很难。因为不方便看到内容,要继续之前的工作要自己重新load所有的数据要花不少时间,这导致了往前继续工作很难。

  • 3 汇总分析很难。假设整个环境是一个多用户多任务的系统,那么分析哪些功能/数据用的多,哪些函数用的多,哪里容易出问题是很难被统计的。

如果程序结合Neo4j,这个过程可能可以变的非常有趣。(如果把关系理解为变化的话,那么变化就是图数据库的一等公民,而建模过程恰恰是要处理多种变化)

  • 1 数据节点。严格上来说,任何的属性都可以只分为Meta(元数据)和Data(数据)两部分。但是为了使用上的方便,我们把Meta下面的Status(状态)和MSG(消息)单独拿出来,方便使用者随时看到。而且因为Neo4j似乎不太方便存层级。(可以通过类似网站路由的方式定义单层字典关键字的复杂意思)
    • meta部分:基本的属性,和可用的方法信息(例如数据大小,help, 解释函数)
    • data部分:data_uri下缀开头的表示需要使用uri解析的数据,否则就是普通数据
    • status部分:比较重要的状态,例如true表示数据正常(被创建)。涉及状态迁移、和状态机的关键属性。
    • msg部分:需要用户了解的交互信息。(例如出错信息)
  • 2 函数节点。
    • meta部分。函数属于哪个包,哪个应用,什么版本。
    • data部分:函数的主体。可以编辑,最大的应用在于将这部分开放为git协作。
    • status部分:正常、停用。以及其他重要状态
    • msg部分:例如告诉因为停用,所以执行无法进行等。

一个简单的应用过程:

节点名称节点类型作用
F1函数节点根据路径地址,读入Excel,变为DataFrame,然后存为Pickle
D1数据节点里面只有原始字符串,文件路径
  • 1 通过D1-[Data In]->F1 , F1-[Data Out]->生成 D2
  • 2 D2-[FromFunc]-F1
  • 3 D2-[FromData]-D1

最终的D2也是一个数据节点,可以增加标签 Pkl。通过大类的标签和小类的标签,可以让节点的性质一目了然。

如果函数本身也是通过串行生成的,那么有可能是这样的

  • F1-[Nest To]->F2
  • F2-[Nest From]->F1

按照一个标准的python函数包的组织方式,通过from … import xx 很容易建立上述的函数依赖关系。

通过这样的网络,我们可以实现数据节点和和函数节点的初始化,并可以不断进行变化。需要做的是把python程序和neo4j的数据库融合起来:

  • 1 函数字典化。F节点本质上是通过函数名称进行调用。使用时只不过是通过图的路径遍历,形成轨迹函数运行的文本列表[ (function1 ,variable1) ,((function1 ,variable1), ...]
  • 2 数据URI化。D节点通过一种哈希码来对应某个数据实体。通常可考虑[Tag] + MD5 + 毫秒级时间戳。
    • Tag 是基于逻辑分类。起到分类的作用,可以大约知道是何种类型的数据。(以及sample, batch_sample, all )这样的数据分类,减少不必要的数据传输。
    • MD5 基于内容计算出的摘要。用于唯一识别数据省份。
    • 毫秒级时间戳。给出时间信息,并加强识别唯一性,避免MD5撞车。
  • 3 Session属性
    • session 对应了唯一的一组数据、函数状态
    • 指明了操作的期间,保持一段时间的状态(数据,函数)
  • 4 Schema标签
    • 指明了一系列的操作,固化为schema,可以视为一个有意义阶段的操作模型。例如从原始数据到清洗完成,这样一个阶段的一系列操作可视为一个schema。(下一次原始数据直接通过schema就获得了清洗结果)
    • 和Session相比,Schema更「静态」一些,而Session更「动态」,时刻可能修改
    • Schema是Session探索的一个结果,是知识性的,不存数据(除了需要静态载入的部分)
  • 5 数据库的读取与调用
    • 在某次交互期间(ipython),数据可能频繁的在终端与数据库间来回,实时展现
    • 某次会话结果后,可能进行schema的创建或者融合
      • 1 一种是将本次会话的内容直接覆盖当前schema(一定是在schema下进行工作的)
      • 2 将本次会话的内容,融合之前的schema生成一个新的schema
      • 3 仅将本次会话的内容作为一个schema,以后可以进行schema之间的融合或串行形成一个更大的schema。

可以想象,在实际使用的时候,很多操作会与数据库交互。观察操作的结果既会观察数据结果,也会观察结构变化。

目前图数据库已经在六个领域的应用中取得了成功(有待验证,应该可行):

  • 1 社交
  • 2 推荐
  • 3 地理空间
  • 4 主数据管理
  • 5 网络和数据中心管理
  • 6 授权和访问控制

首先至少这几个领域的确是传统结构化库比较难搞的,从图的角度来看也比较make sense, 而且实际上已经有不少公司已经利用图做到了一些事情。特别的:

  • 1 地理空间。目前很多应用并没有很好的利用地理空间与其他维度的约束关系,加入图以后应该会很好。
  • 2 主数据管理。其实这个问题更加普遍,市场潜力大得吓人,不过目前也都还处于探索阶段,用图(可能会以结合的方式)会是一个很好的选择。
  • 3 网络和数据中心管理。我在构建自有算网中已经罗列了一些主机,在这些主机上未来会其非常多的容器。如何管理好机器本身,以及优化管理容器是一个蛮难的问题,感觉图应该可以做到我想做的。(我并不想用一些成熟的商业套件,不够智能,而且学习成本高,未来也没什么可扩展的)
  • 4 授权和访问控制。现有的结构化库可以处理简单的权限控制,不过确实如果通过图来做会比较fancy。
实验:自有算网的图表示

下面就以表达和构建自有算网的结构来尝试一下图的方式。
目标有三点:

  • 1 结构表达
  • 2 实时抓取(更新)
  • 3 BI展示(这块在别的文章里写,Flask向neo4j请求数据后展示)

实现的是我可以通过portal看到所有设备的物理载荷和逻辑载荷,并可以直接在web上方便的调节。最终整个的部署和调节会通过算法进行运筹优化,自动配置合适的冗余,提高服务效率。

我梳理了一下关系(关系是一等公民):
在关系中,不宜存节点的属性,最主要的元素应该是有4个。

  • from : source节点ID
  • to: target节点ID
  • rid: 关系的唯一标志ID
  • reltype: 关系类型
    在这里插入图片描述
    我通过cypher+py2neo做了一个函数,直接读入excel表就会存向目标主机,先看效果
    在这里插入图片描述
    在上面的属性很容统计除了一共有4类13个节点。
  • Machine : 对应着一台设备(云主机、物理机都算)
  • User: 用户(目前只有user0,可以认为是超级用户)
  • Port: 端口。一台机器真正的资源划分是ip+端口,所以端口也独立成一类逻辑节点。
  • DCAPP:docker-compose封装的应用程序。应用通过DCAPP部署到机器上,使用的时候启动就可以了。

还有节点的状态在实验里暂时没有更新,例如prj1部署在了m2上,但是可以有一个状态(节点属性)表示prj1此时是启动的还是停止的。
通过cypher查询就可以很快知道当前网络的资源配置及使用情况。(这里的图本质上是主图,即两个节点的同类关系只会有一条边,边的属性可以变动)

真正在生产使用时可以使用mongo记录日志,同步地使用最新的记录更新neo4j的主图。
注意:节点的ID和标签,关系的ID和标签不允许有任何空格,最好按abc123的方式存储。

以下是实现的代码:

  • 1 建立一个基础的j2模板
  • 2 在程序中根据模板生成了另一个模板字符串
  • 3 将数据填入模板字符串变为可执行的cypher命令
  • 4 neo4j提交cypher命令

unwind_rel_base.j2

with 
[

{{for_start}}
{{loop_if}}

    {

    {{if_attr_list}}

    }

{{for_end}}
] as data 
UNWIND data as row
merge (n:{{from_node_label}}{ {{node_id_var}}:row.{{from_node_id_var}}})
merge (n1:{{to_node_label}}{ {{node_id_var}}:row.{{to_node_id_var}}})
create unique (n)-[r:{{reltype}}]->(n1)
set {{set_attr_list}}
return r.{{rel_id_var}}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

test3.py(操作的函数没有进行进一步封装,所以代码比较冗长)

# https: // www.dazhuanlan.com/2019/12/09/5dee0b4e3cf23/
import copy
import DataManipulation as dm
import json
import pandas as pd
import numpy as np
import time
from py2neo import Graph, Node, Relationship, Subgraph, NodeMatcher, RelationshipMatcher
# 使用文本字符串直接生成结果
from jinja2 import Template
env = 'prod'
if env.lower() == 'local':
    url = "http://127.0.0.1:7474"
    pwd = '111'
else:
    # 腾讯云
    url = "http://111.111.111.111:7474"
    pwd = '111'


graph = Graph(url,
              username="neo4j",
              password=pwd)
# j2_str_dict
j2_str_dict = {}
# 字符型变量if模板
j2_str_dict['str_if_template_obj'] = '''{%% if obj['%s'] %%}
            %s%s:'{{obj['%s']}}'
            {%% endif %%}'''
# 数值型变量if模板
j2_str_dict['num_if_template_obj'] = '''{%% if obj['%s'] %%}
            %s%s:{{obj['%s']}}
            {%% endif %%}'''

# 是用关系模板 unwind_rel_base.j2
j2_str_dict['loop_if'] = '''{%if not loop.first%}
,
{%endif%}'''
j2_str_dict['for_start'] = '''{% for obj in data_list %}
'''
j2_str_dict['for_end'] = '{% endfor %}'


def j2_module_if_str_attr_list(str_attr_template, str_attr_list):
    _if_attr_list = ''
    for i, v in enumerate(str_attr_list):
        if i == 0:
            tems = str_attr_template % (v, '', v, v)
        else:
            tems = str_attr_template % (v, ',', v, v)
        _if_attr_list += tems
    return _if_attr_list


def j2_module_if_num_attr_list(num_attr_template, num_attr_list):
    _if_attr_list = ''
    for i, v in enumerate(num_attr_list):
        if i == 0:
            tems = num_attr_template % (v, '', v, v)
        else:
            tems = num_attr_template % (v, ',', v, v)
        _if_attr_list += tems
    return _if_attr_list


def j2_module_set_attr_list(attr_list):
    set_attr_list = ''
    for i, attr in enumerate(attr_list):
        if i == 0:
            tems = 'r.{0}=row.{0}'.format(attr)
        else:
            tems = ',r.{0}=row.{0}'.format(attr)
        set_attr_list += tems
    return set_attr_list


# 如果声明了属性列表,第一个不得为空(数值型的还不允许为0)


rel_template = {
    'searchpath': './',
    'template_name': 'unwind_rel_base.j2',
    'if_attr_list': None,
    'set_attr_list': None,
    'loop_if': j2_str_dict['loop_if'],
    'for_start': j2_str_dict['for_start'],
    'for_end': j2_str_dict['for_end'],
    'from_node_label': 'Machine',
    'to_node_label': 'Machine',
    'reltype': 'Frp_to',
    'node_id_var': 'eid',
    'from_node_id_var': 'from',
    'to_node_id_var': 'to',
    'rel_id_var': 'rid'
}


def j2_module_save_rel_list(data_list, str_attr_list, set_rel_attrs_list, num_attr_list=None, rel_template=rel_template):
    if num_attr_list is None:
        if_attr_list = j2_module_if_str_attr_list(
            j2_str_dict['str_if_template_obj'], str_attr_list)
    else:
        if_attr_list = j2_module_if_str_attr_list(
            j2_str_dict['str_if_template_obj'], str_attr_list) + ',' + j2_module_if_num_attr_list(j2_str_dict['num_if_template_obj'], num_attr_list)
    set_attr_list = j2_module_set_attr_list(set_rel_attrs_list)

    temp_dict = copy.deepcopy(rel_template)
    temp_dict['if_attr_list'] = if_attr_list
    temp_dict['set_attr_list'] = set_attr_list

    template_s = dm.gen_by_j2(**temp_dict)
    template = Template(template_s)
    the_cypher = template.render(data_list=data_list)
    # print(the_cypher)
    return graph.run(the_cypher).data()


rel1_df = pd.read_excel('主机间连接关系.xlsx', skiprows=1)
rel1_df = rel1_df.dropna(subset=['rid'])
rel1_df['from'] = rel1_df['from'].apply(lambda x:x.strip())
rel1_df['to'] = rel1_df['to'].apply(lambda x: x.strip())
rel1_df['rid'] = rel1_df['rid'].apply(lambda x: x.strip())

# 1 第一种关系
rel_frp_df = rel1_df[rel1_df.reltype == 'FrpTo']
rel_frp_dict = json.loads(
    rel_frp_df[['from', 'to', 'rid', 'create_time']].fillna(1).to_json(orient='index'))


data_list = list(rel_frp_dict.values())
str_attr_list = ['from', 'to', 'rid', 'reltype']
num_attr_list = ['create_time']
set_rel_attrs_list = ['rid', 'create_time']

j2_module_save_rel_list(data_list, str_attr_list,
                        set_rel_attrs_list, num_attr_list)

# 2 第二种关系
rel_frp_df = rel1_df[rel1_df.reltype == 'SSHTo']
rel_frp_dict = json.loads(
    rel_frp_df[['from', 'to', 'rid', 'create_time']].fillna(1).to_json(orient='index'))


data_list = list(rel_frp_dict.values())
str_attr_list = ['from', 'to', 'rid', 'reltype']
num_attr_list = ['create_time']
set_rel_attrs_list = ['rid', 'create_time']
rel_template['reltype'] = 'SSHTo'

j2_module_save_rel_list(data_list, str_attr_list,
                        set_rel_attrs_list, num_attr_list, rel_template=rel_template)

# 3 第三种关系
rel_frp_df = rel1_df[rel1_df.reltype == 'ImplementedTo']
rel_frp_dict = json.loads(rel_frp_df[['from', 'to', 'rid', 'create_time']].fillna(1).to_json(orient='index'))


data_list = list(rel_frp_dict.values())
str_attr_list = ['from', 'to', 'rid', 'reltype']
num_attr_list = ['create_time']
set_rel_attrs_list = ['rid', 'create_time']
rel_template['reltype'] = 'ImplementedTo'
rel_template['from_node_label'] = 'DCApp'

j2_module_save_rel_list(data_list, str_attr_list,
                        set_rel_attrs_list, num_attr_list, rel_template=rel_template)

# 4 第四种关系
rel_frp_df = rel1_df[rel1_df.reltype == 'BelongsTo']
rel_frp_dict = json.loads(rel_frp_df[['from', 'to', 'rid', 'create_time']].fillna(1).to_json(orient='index'))


data_list = list(rel_frp_dict.values())
str_attr_list = ['from', 'to', 'rid', 'reltype']
num_attr_list = ['create_time']
set_rel_attrs_list = ['rid', 'create_time']
rel_template['reltype'] = 'BelongsTo'
rel_template['from_node_label'] = 'Port'
rel_template['to_node_label'] = 'Machine'

j2_module_save_rel_list(data_list, str_attr_list,
                        set_rel_attrs_list, num_attr_list, rel_template=rel_template)

# 5 第五种关系
rel_frp_df = rel1_df[rel1_df.reltype == 'VisitTo']
rel_frp_dict = json.loads(rel_frp_df[['from', 'to', 'rid', 'create_time']].fillna(1).to_json(orient='index'))


data_list = list(rel_frp_dict.values())
str_attr_list = ['from', 'to', 'rid', 'reltype']
num_attr_list = ['create_time']
set_rel_attrs_list = ['rid', 'create_time']
rel_template['reltype'] = 'VisitTo'
rel_template['from_node_label'] = 'User'
rel_template['to_node_label'] = 'Port'

j2_module_save_rel_list(data_list, str_attr_list,
                        set_rel_attrs_list, num_attr_list, rel_template=rel_template)


  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • 179
  • 180
  • 181
  • 182
  • 183
  • 184
  • 185
  • 186
  • 187
  • 188
  • 189
  • 190
  • 191
  • 192
  • 193
  • 194
  • 195
  • 196
  • 197
  • 198
  • 199
  • 200
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/凡人多烦事01/article/detail/264580
推荐阅读
相关标签
  

闽ICP备14008679号