当前位置: 首页 > wzjs >正文

大专网站建设资料企业网页开发

大专网站建设资料,企业网页开发,建设好网站,装饰装修材料MongoDB索引,事务和安全 文章目录 MongoDB索引,事务和安全一:事务和锁1:MongoDB事务机制:不建议使用2:MongoDB的锁机制 二:MongoDB的索引机制1:初始索引2:索引详解2.1&am…

MongoDB索引,事务和安全

文章目录

  • MongoDB索引,事务和安全
    • 一:事务和锁
      • 1:MongoDB事务机制:不建议使用
      • 2:MongoDB的锁机制
    • 二:MongoDB的索引机制
      • 1:初始索引
      • 2:索引详解
        • 2.1:单列索引
        • 2.2:复合索引
        • 2.3:唯一索引
        • 2.4:部分索引
        • 2.5:TTL索引
        • 2.6:全文索引
        • 2.7:通配符索引
      • 3:explain执行计划
    • 三:安全与权限管理
      • 1:简单的用户创建和使用
      • 2:Mongo中的内置角色
      • 3:Mongo自定义角色

一:事务和锁

1:MongoDB事务机制:不建议使用

MongoDB作为数据库家族的一员,自然也支持事务机制,只不过相较于InnoDB的事务机制而言,MongoDB事务方面并没有那么强大

这倒不是因为官方技术欠缺,而是由于MongoDB的定位是:大数据、高拓展、高可用、分布式

因此在实现事务时,不仅仅要考虑单机事务,而且需要考虑分布式事务,复杂度上来之后,自然无法做到MySQL-InnoDB那种单机事务的强大性。

这里也列出MongoDB事务方面的改进过程,如下:

  • 3.0版本中,引入WiredTiger存储引擎,开始支持单文档事务;
  • 4.0版本中,开始支持多文档事务,以及副本集(主从复制)架构下的事务;
  • 4.2版本中,开始支持分片集群、分片式多副本集架构下的事务。
// 开启一个会话
var session = db.getMongo().startSession({// readPreference:定义读操作的节点优先级和模式// mode:指定读取模式// -> primary:只从主节点读取数据;// -> secondary:只在从节点上读取数据;// -> primaryPreferred:优先从主节点读取,主节点不可用,转到从节点读取;// -> secondaryPreferred:优先在从节点读取,从节点不可用,转到主节点读取;// -> nearest:从可用节点中选择最近的节点进行读取;readPreference:{mode: "primary"}
});
// 开启事务
session.startTransaction({// readConcern:指定事务的读取模式// level:指定一致性级别// --> available:读取已提交的数据,可能包含尚未持久化的事务更改;// --> snapshot:读取事务开始时的一致快照,不包含未提交的事务更改;readConcern: {level:"snapshot"},  // 指定读模式为快照读// writeConcern:指定事务的写入模式// w:指定写操作的确认级别(同步模式)// --> [number]:写操作在写入指定数量的节点后,返回写入成功;// --> majority: 写操作在写入大多数节点(半数以上)后,返回写入成功;// --> tagSetName:写操作在写入指定标签的节点后,返回写入成功// j:写入是否应被持久化到磁盘// --> wtimeout:指定写入确认的超时时间;writeConcern:{w: "majority"} // 写模式的同步模式级别为半同步,即写入半数以上节点后再返回成功}
);
// 获取要操作的集合对象
var trx_coll = session.getDatabase("库名").getCollection("集合名");// 要在事务里执行的CRUD操作
// ......// 回滚事务命令
session.abortTransaction();
// 提交事务命令
session.commitTransaction();
// 关闭会话命令
session.endSession();

上述命令了解即可,毕竟MongoDB本身就不适用于强事务的场景,原因如下:

  • MongoDB的事务必须在60s内完成,超时将自动取消(因为要考虑分布式环境);
  • 涉及到事务的分片集群中,不能有仲裁节点;
  • 事务会影响集群数据同步效率、节点数据迁移效率;
  • 多文档事务的所有操作,必须在主节点上完成,包括读操作;

综上所述,就算MongoDB支持事务,可实际使用起来也会有诸多限制,因此在不必要的情况下,不建议使用其事务机制。

2:MongoDB的锁机制

MongoDB 锁机制是其并发控制的重要组成部分,目的是为了确保多线程多用户访问下数据的完整性和一致性,主要分类两大类:MMAPv1 引擎的锁机制和 WiredTiger 引擎的锁机制。

在使用 MMAPv1 存储引擎的 MongoDB 版本中,全局锁时其主要的并发控制手段。全局锁有两种模式:

  • 读锁:允许多个读操作共享,但组织任何写操作
  • 写锁:独占锁,一旦获取将组织其他读写操作,直至锁释放

WiredTiger 引擎使用了更细粒度的锁机制,主要为:

  • 文档锁:锁定单个文档,允许多个并发读操作,但写操作会互斥。这大大减少了锁竞争,提高了并发写入能力,从而使得在高并发场景下也能保持较好的性能。
  • 多版本并发控制(MVCC):WiredTiger 实现了一种 MVCC 机制,为每个事务创建数据的多个版本。这样,读操作可以不受写操作的影响,看到事务开始时的一致性视图,而写操作则在新版本上进行,直到事务提交后才会对外可见。这增强了系统的并发能力,同时保证了事务的隔离性。
  • 范围锁:在某些情况下,为了保持数据一致性,WiredTiger可能会锁定一个文档范围,防止其他操作修改该范围内的数据。
  • 乐观锁:除了传统的锁机制,WiredTiger还采用了乐观锁策略,尤其在处理读写操作时。乐观锁依赖于文档版本控制,每个文档都有一个内部版本号。写操作前先读取版本号,写入时检查版本号是否改变,若未变则成功,否则重试。这种方式减少了锁的使用,提高了并发效率

非常复杂:有时间研究下大佬的笔记

// 这里简单的列出手动操作锁的命令:
// 获取锁
db.collection.fsyncLock();
// 释放锁
db.collection.fsyncUnlock();

二:MongoDB的索引机制

任何数据库都有索引这一核心功能,MongoDB自然不例外,而且MongoDB在索引方面特别完善,毕竟是新的数据库,肯定汇集百家之长

Mongo索引官方文档

早版本的MongoDB中,索引底层默认使用B-Tree结构

4.x版本后,MongoDB推出了V2版索引,默认使用变种B+Tree来作为索引的数据结构(和MySQL索引的数据结构相同)

1:初始索引

MongoDB会为每个集合生成一个默认的_id字段,该字段在每个文档中必须存在,可以手动赋值

如果不赋值则会默认生成一个ObjectId, 该字段则是集合的主键,MongoDB会基于该字段创建一个默认的主键索引

后续基于_id字段查询数据时,会走索引来提升查询效率。

当咱们基于其他字段查询时,由于未使用_id作为条件,这会导致find语句走全表查询,即从第一条数据开始,遍历完整个集合,从而检索到目标数据。

当集合中的数据量,达到百万、千万、甚至更高时,意味着效率会直线下滑,在这种情况下,必须得由我们手动为频繁作为查询条件的字段建立索引。

Mongo中的索引分类

  • 从字段数量的维度划分:单列,组合,多键,部分
  • 从排序的维度划分:升序,降序,多序
  • 从功能的维度划分:主键,普通,唯一,全文,空间
  • 从数据结构的维度划分:B+Tree,Hash
  • 从存储方式的维度划分:聚簇,非聚簇
  • 从索引性质的维度划分:稀疏,TTL,隐藏,通配符

在这里插入图片描述

2:索引详解

MongoDB中创建索引的命令:

db.collection.createIndex(<key and index type specification>, <options>);

前面提到的所有索引,都是通过这一个方法创建,不同类型的索引,通过里面的参数和选项来区分,下面说明一下参数和可选项。

第一个参数主要是传字段,以及索引类型,这里可以传一或多个字段,用于表示单列/复合索引。

第二个参数表示可选项,如下:

  • background:是否以后台形式创建索引,因为创建索引会导致其他操作阻塞;
  • unique:是否创建成唯一索引;
  • name:指定索引的名称;
  • sparse:是否对集合中不存在的索引字段的文档不启用索引;
  • expireAfterSeconds:指定存活时间,超时后会自动删除文档;
  • v:指定索引的版本号;
  • weights:指定索引的权重值,权值范围是1~99999,当一条语句命中多个索引时,会根据该值来选择;

以下述集合为例,演示各种索引的创建过程

db.animals.insert([{_id:1, name:"肥肥", age:3, hobby:"竹子", color:"黑白色"},{_id:2, name:"花花", color:"黑白色"},{_id:4, name:"黑熊", age:3, food:{name:"黄金竹", grade:"S"}},{_id:5, name:"白熊", age:4, food:{name:"翠绿竹", grade:"B"}},{_id:6, name:"棕熊", age:3, food:{name:"明月竹", grade:"A"}},{_id:7, name:"红熊", age:2, food:{name:"白玉竹", grade:"S"}},{_id:8, name:"粉熊", age:6, food:{name:"翡翠竹", grade:"A"}},{_id:9, name:"紫熊", age:3, food:{name:"烈日竹", grade:"S"}},{_id:10, name:"金熊", age:6, food:{name:"黄金竹", grade:"S"}}
]);
2.1:单列索引
// 基于name字段,创建一个名为idx_name的单列普通索引,排序方式为降序
db.animals.createIndex({name: -1}, {name: "idx_name"}
);

🎉 对于单字段的索引而言,排序方式并不重要,因为索引底层默认是B+Tree,每个文档之间会有双向指针,为此,MongoDB基于单字段索引查询时,既可以向前、也可以向后查找数据

在这里插入图片描述
创建完成后,可以通过db.animals.getIndexes()命令查询索引

在这里插入图片描述

多键索引

例如现在将集合中的爱好字段,变为一个数组:

{_id:1, name:"肥肥", age:3, hobby:["竹子", "睡觉"], color:"黑白色"
}

现在给hobby字段创建一个索引,这时叫啥索引?多键索引!

因为这里是基于单个数组类型的字段在建立索引,所以MongoDB会为数组中的每个元素,都生成索引的条目(即索引键)

由于一个文档的数组字段,拥有多个元素,因此会创建多个索引键,这也是“多键索引”的名字由来。

2.2:复合索引

复合索引是指基于多个字段创建的索引,例如:

db.animals.createIndex({name:-1, age:1}, // 依据name降序,age升序创建一个聚合索引{name:"idx_name_age"} // 复合索引的名称
);

🎉 这个排序就有意义了,MongoDB生成索引键时,会按照指定的顺序,来将索引键插入到树中。

索引键=索引字段的值,比如现在一个文档的name=张三、age=3,索引键为张三3

注意:由于这里的顺序是{name:-1, age:1},所以当排序查询时,支持sort({name:-1,age:1})、sort({name:1,age:-1}),因为这两个顺序和树的组成顺序要么完全相同、相反

而当执行sort({name:-1,age:-1})、sort({name:1,age:1})排序查询时,将不会使用索引,因为这时和树的顺序冲突。

2.3:唯一索引

必须创建在不会出现重复值的字段上,基于唯一索引查找数据时,找到第一个满足条件的数据,就会立马停止匹配,毕竟该字段的值在集合中是唯一的

db.animals.createIndex({name:1}, // 在name字段上正序创建索引{unique: true} // 声明是唯一索引
);

只需要将unique设置为true即可,如果尝试插入已有的name,将会触发报错

两个都是空也认为是冲突

db.animals.insertOne({_id: 66, age: 12}
)db.animals.insertOne({_id: 77, age: 13}
)

在这里插入图片描述

这种情况怎么解决呢,声明name索引是稀疏索引即可

// 将刚才的给删除了
db.animals.dropIndex("name_1");// 在创建一个,这次指明这个索引不但是唯一的,还是稀疏的,允许重复的null值
db.animals.createIndex({name: 1},{unique: true, sparse: true}
)
2.4:部分索引

部分索引即使用字段的一部分开创建索引,但必须要结合partialFilterExpression选项来实现,

db.animals.createIndex({hobby: 1}, // 给 hobby 字段创建索引{partialFilterExpression: {hobby: {// 只为存在hobby字段的文档创建索引$exists: true,// 通过$substr操作符,截取前3个字节作为索引键$expr: {$eq: [{$substr:["$hobby", 0, 3] },"prefix"]}}}}
);

其实这就类似于MySQL中的前缀索引,不过MongoDB的中的部分索引功能更强大,还可以只为集合中的一部分文档创建索引

db.animal.createIndex({age: -1},// 只为集合中年龄大于2岁的文档创建索引{partialFilterExpression: {age: {$gt: 2}}}
);
2.5:TTL索引

可以基于它实现过期自动删除的效果,主要依靠expireAfterSeconds选项来创建

只能在Date、ISODate类型的字段上建立TTL索引,在其他类型的字段上建立TTL索引,文档永远不会过期。

db.test_ttl.insertMany([// new Date()表示插入当前时间{_id:1, time:new Date()},{_id:2, time:new Date()},{_id:3, time:new Date()},{_id:4, time:new Date()},{_id:5, notes:"这条数据用于观察TTL删除特性"} // 10s之后,将只剩这条数据
]);db.test_ttl.find();db.test_ttl.createIndex({time: 1},  // 依据时间字段创建索引{expireAfterSeconds: 10} // 给定的过期时间为10s
);
2.6:全文索引

MySQL中想实现模糊查询,一般会采用like关键字;而在MongoDB中想实现模糊查询,官方并没有提供相关方法与操作符,只能通过自己写正则的形式,实现模糊查找的功能

那有没有更好的方法呢?

答案是有,为相应字段创建全文索引即可。

在数据量不大不小(几百万左右)、查询又不是特别复杂的情况下,直接上ElasticSearch、Solr等中间件,显得有点大材小用

此时全文索引就是这类搜索引擎的平替。相较于MySQLMongoDB提供的全文索引,功能方面会更加强大。

db.animals.createIndex({name: "text"},{name: "full_text_index"}
)

这里对name字段建立了一个全文索引,和创建普通索引的区别在于:在字段后面加了一个text

不过要注意,MongoDB全文索引停用词、词干和词器的规则,默认为英语,想要更改,这里涉及到创建索引时的两个可选项:

  • default_language:指定全文索引停用词、词干和词器的规则,默认为english
  • language_override:指定全文索引语言覆盖的范围,默认为language

不过注意,不管任何技术栈的全文索引,对中文的支持都不太友好,分词方面总会有点不完善

所以MongoDB全文索引直接不支持中文,当你试图通过default_language:"chinese"时,会直接给你返回报错

当然,正是由于MongoDB的全文索引不支持中文,因此就算你给一个字符串字段,建立了全文索引后,也无法实现全文搜索,如下:

db.animals.find({$text: {$search: "熊"}}
);

⚠️ 在前面给出的集合数据中,name包含“熊”的数据有好几条,但这条语句执行之后的结果为null。想要解决这个问题,必须要手动安装第三方的中文分词插件,如mmseg、jieba等。当然,如果你字段中的值是英文,这自然是支持的,什么都不需要。

2.7:通配符索引

在前面提到过“内嵌文档”这个概念,这是指将另一个文档,以字段值的形式嵌入到一个文档中。

结合MongoDB可以动态插入各种字段的特性,每个内嵌文档的字段,也可以灵活变化,例如前面给出的数据:

{_id:4, name:"黑熊", age:3, food:{name:"黄金竹", grade:"S"}},{_id:5, name:"白熊", age:4, food:{name:"翠绿竹", grade:"B"}},
......

这些数据中都内嵌了一个food文档,虽然现在插入的都是固定的name、grade字段,但我们可以随时插入新的字段,例如:

db.animals.insertOne({_id:99, name:"星熊", age: 1,food: {name:"星光竹", grade:"S", quality_inspector: ["竹大","竹二"]}}
);

这时新插入的文档,其food字段又多了一个quality_inspector质检员的属性

对于这种动态变化的字段,可不可以建立索引呢?MongoDB4.2中引入了“通配符索引”来支持对未知或任意字段的查询操作

创建的语法如下:

db.animals.createIndex({"food.$**": 1});

3:explain执行计划

通过该命令,能有效帮咱们分析语句的执行情况,和MySQL的explain作用一致

db.<collection>.find().explain(<verbose>);

explain方法同样有三个模式可选,这里简单列出来:

  • queryPlanner:返回执行计划的详细信息,包括查询计划、集合信息、查询条件、最佳计划、查询方式、服务信息等(默认模式);
  • exectionStats:列出最佳执行计划的执行情况和被拒绝的计划等信息(即语句最终执行的方案);
  • allPlansExecution:选择并执行最佳执行计划,同时输出其他所有执行计划的信息;

一般排查find()查询缓慢问题时,可以先指定第二个模式,查看最佳执行计划的信息;

如果怀疑MongoDB没选择好索引,则可以再指定第三个模式,查看其他执行计划

如果的确是因为走错了索引,这时你可以通过hint强制指定要使用的索引,如下:

db.collection_name.find(查询条件).hint(索引名);

执行explain之后,可以发现输出了很多信息,主要关注的就是这个stage这个值:是最重要的字段,相当于mysql explain中的type

带包这本次查询的类型,该字段可能出现的值以及含义如下:

含义
COLLSCAN扫描整个集合进行查询;
IXSCAN通过索引进行查询;
COUNT_SCAN使用索引在进行count操作;
COUNTSCAN没使用索引在进行count操作;
FETCH根据索引键去磁盘拿具体的数据
SORT执行了sort排序查询;
LIMIT使用了limit限制返回行数;
SKIP使用了skip跳过了某些数据;
IDHACK通过_id主键查询数据;
SHARD_MERGE从多个分片中查询、合并数据;
SHARDING_FILTER通过mongos对分片集群执行查询操作;
SUBPLA未使用索引的$or查询;
TEXT使用全文索引进行查询;
PROJECTION本次查询指定了返回的结果集字段(投影查询)

在这里插入图片描述

// 在age上创建一个索引
db.animals.createIndex({age: -1}, {name: "idx_age"})
// 假设执行的语句如下
db.animals.find({age: {$gt: 6}}).limit(1).skip(1).explain();

在这里插入图片描述

这里咱们只需要带SCAN后缀的,因为其他都属于命令执行的“阶段”,并不属于具体的类型

explain会将一条语句执行的每个阶段,都详细列出来,每个阶段都会有stage字段

我们要做的,就是确保每个阶段都能用上索引即可

如果某一阶段出现COLLSCAN,在数据量较大的情况下,都有可能导致查询缓慢。

其次,咱们需要关心keysExamined、docsExamined两个字段的值(exectionStats模式下才能看到)

  • 前者代表扫描的索引键数量,后者代表扫描的文档数量,前者越大,代表索引字段值的离散性太差
  • 后者的值越大,一般代表着没建立索引。

三:安全与权限管理

1:简单的用户创建和使用

通常为了保证数据安全性,Redis、MySQL、MQ、ES……,通常都会配置账号/密码,一来可以提高安全等级,二来还可以针对不同库、操作设置权限,极大程度上降低了数据的安全风险。

同样,在MongoDB中也支持创建账号、密码,以及分配权限,并且还支持角色的概念,可以先为角色分配权限,再为用户绑定角色,从而节省大量重复的权限分配工作。

同时,MongoDB中还内置了大量常用角色,方便于咱们快速分配权限,不过并没有默认的账号

所以想要启用MongoDB的访问控制,还需要先创建一个账号:

// 1:切换到admin
use admin;// 2:创建一个用户,并且赋予root角色(root角色只能分配给admin库)
db.createUser({"user": "cui", "pwd": "123456", "roles": ["root"]}
)
// 3:退出客户端
quit;

接着再关闭MongoDB服务,重新启动时开启访问控制,必须使用账号密码连接才允许操作:

[root@~]# /soft/mongodb/bin/mongod -shutdown -f /soft/mongodb/conf/standalone/mongodb.conf
[root@~]# /soft/mongodb/bin/mongod -auth -f /soft/mongodb/conf/standalone/mongodb.conf
[root@~]# /soft/mongodb/mongosh/bin/mongosh 192.168.229.135:27017

或者这里也可以直接修改配置文件:

[root@~]# vi /soft/mongodb/conf/standalone/mongodb.conf# 在配置文件结尾加上这两行
security:authorization: enabled

然后通过不带-auth的命令启动,效果同样是相同的。

接着先切换到咱们前面创建的cui库,查询一下animals集合试试看:

use cui;
db.animals.find({_id:1});
MongoServerError: command find requires authentication

此时就会看到对应报错,提示目前未授权,所以无法执行命令,因此这里需要登录一下,不过登录必须要切换到admin库下才可以,否则会提示认证失败:

db.auth("cui", "123456");
{ ok: 1 }use cui;
db.animals.find({_id:1});
[ { _id: 1, name: '肥肥', age: 3, hobby: '竹子', color: '黑白色' } ]

认证成功后,再次切回cui库查询,此时会发现数据依旧可以查询出来。

当然,如果不想每次连接时都切换到admin库下登录,然后再切换回来,此时可以在cui库下再创建一个用户,如下:

// 先切换到cui库
use cui;// 再在cui库下创建一个cui用户,并分配dbOwner角色
db.createUser({"user":"cui", "pwd":"123456", // 这个dbOwner是啥,会在下面的内置角色中说明"roles":[{"role":"dbOwner", "db":"cui"}]}
);// 退出连接
quit;

然后可以再次连接MongoDB服务,这时直接切换到cui库下登录后,也照样可以读写数据

2:Mongo中的内置角色

可以通过下述命令来查询MongoDB所有内置角色:

use admin;db.runCommand({rolesInfo: 1, showBuiltinRoles: true});
角色含义
root超级管理员权限,可以执行任何操作;
read只读用户,不允许对数据库执行写入操作;
readWrite读写用户,允许对数据执行读写操作;
dbAdmin数据库管理员(如创建和删除数据库),不允许读写数据;
userAdmin用户管理员(如创建和删除用户),不允许读写数据;
dbOwner同时拥有dbAdmin、userAdmin两个角色的权限,且允许读写数据;
backup具有备份和恢复权限,不允许读写数据;
restore只具有数据恢复权限,不允许读写数据;
clusterAdmin集群超级管理员,可以执行集群中任意操作,允许读写数据;
clusterManager集群管理员,只可以管理集群节点、配置等;
clusterMonitor集群监视员,允许监控集群的状态和性能,不允许读写数据;

这些内置角色,可以在创建用户的时候分配,一个用户同时可以绑定多个角色。但如果你想要的权限,内置角色并不提供,也可以自定义角色

3:Mongo自定义角色

自定义角色的语法

use cui;db.createRole({// 自定义角色的名称role:"xxxRole",// 自定义角色拥有的权限集privileges: [{// 自定义角色可操作的资源 resource:{// 当前角色可操作cui库db:"cui",// 当前角色具体可操作的集合(多个传数组,所有写"")collection:""},// 当前角色拥有的权限actions: ["find", "update", "insert", "remove"]}],// 当前角色是否继承其他角色,如果指定了其他角色,当前角色自动继承父亲的所有权限roles: []}
);

通过该方式,诸位可以灵活的创建出各种适用于业务的角色,最后再附上一些相关命令:

// 给指定角色增加权限 ---------> grantPrivilegesToRole
db.grantPrivilegesToRole("角色名称",[{resource: {db: "库名",collection: ""}, actions: ["权限1","……"]}]
);// 回收指定角色的权限 ----------> revokePrivilegesFromRole
db.revokePrivilegesFromRole("角色名称",[{resource: {db: "库名",collection: ""}, actions: ["权限1","……"]}]
);// 删除角色(要先进入角色所在的库)
use cui;
db.dropRole("角色名称");// 查看当前库的所有角色
show roles;// 查看当前库中所有用户
show users;

文章转载自:

http://UbvilNjx.txrkq.cn
http://8WmdpPia.txrkq.cn
http://CwSMAtg5.txrkq.cn
http://u481GHFF.txrkq.cn
http://PIT5RN8O.txrkq.cn
http://9wqhdA74.txrkq.cn
http://NVVelDLh.txrkq.cn
http://g5oMcxRO.txrkq.cn
http://u42xlUQ6.txrkq.cn
http://RXJBNuPZ.txrkq.cn
http://cBpkGIUM.txrkq.cn
http://9fwfIQv9.txrkq.cn
http://f6R81zlI.txrkq.cn
http://3oNTUHfM.txrkq.cn
http://lIxQRPwK.txrkq.cn
http://POkW0xIY.txrkq.cn
http://vv3AH6On.txrkq.cn
http://xKXKQyuy.txrkq.cn
http://4QbHV1mT.txrkq.cn
http://vqYtjj57.txrkq.cn
http://xcvX2Mo6.txrkq.cn
http://prb1LgXV.txrkq.cn
http://1M95rWPi.txrkq.cn
http://pdCakNGQ.txrkq.cn
http://3NbH8ezS.txrkq.cn
http://0CkX1vWo.txrkq.cn
http://WflLJfFL.txrkq.cn
http://cRhDyrxH.txrkq.cn
http://ZNX0QwjU.txrkq.cn
http://zmzDUYxo.txrkq.cn
http://www.dtcms.com/wzjs/729392.html

相关文章:

  • xp系统做局域网内网站西安电商网站开发
  • 个人建设电影网站备案深圳市手机网站建设报价
  • 包头做网站的公司招聘信息为什么都用dw做网站
  • 网站建设维护教程凉山州建设局网站
  • 国内外c2c网站有哪些a5wordpress主题
  • 如何申请国外网站做网站在自己电脑建立虚拟机
  • 自学做网站要多久网站开发的关键技术与难点
  • 网站收录引擎虚拟主机可以建设网站吗
  • 手机网站用什么制作新手做网站的注意事项
  • 水贝做网站公司做网站图片尺寸
  • 腾讯做网站建筑企业平台
  • 网站开发税率是多少手机免费网站
  • 铜陵电子商务网站建设公司网站建设费用记什么科目
  • 上海工商网站查询企业信息网线制作过程简述
  • 淄博网站建设电话怎么改版网站
  • 公司管理系统网站模板下载网站开发前景与趋势如何
  • 电子商务网站建设管理答案成都网站制作设计公司
  • 汕头模板开发建站网站班级文化建设方案
  • 新闻资讯网站备案做网站费用会计分录
  • 大连唐朝网站优化公司三网合一网站建设方案
  • 天津技术网站建设中国建设承包商网站
  • 网站优化的推广物流网站建设合同范本
  • 河南5G网站基站建设信息网站建设 李奥贝纳
  • 营销网站建设都是专业技术人员ddos的wordpress插件
  • 桂林网站制作人才招聘新手运营从哪开始学
  • 个人网站制作软件公众号文章怎么添加小程序
  • html5可不可以建设手机网站福建省建设质量安全协会网站
  • 怎么在网站添加关键词开一个网站多少钱
  • 邢台市住房和城乡建设局网站帮别人做违法网站
  • 室内设计联盟电脑版网站建设优化服务精英