Mongoose是一个JavaScript框架,通常在带有MongoDB数据库的Node.js应用程序中使用。 在本文中,我将向您介绍Mongoose和MongoDB,更重要的是,这些技术适合您的应用程序。

什么是MongoDB?

让我们从MongoDB开始。 MongoDB是一个将您的数据存储为文档的数据库。 最常见的是,这些文档类似于JSON的结构:

{firstName: "Jamie",lastName: "Munro"
}

然后将文档放置在集合中。 作为示例,以上文档示例定义了一个user对象。 这样,该user对象通常将成为名为users的集合的一部分。

MongoDB的关键因素之一是它在结构上的灵活性。 即使在第一个示例中, user对象包含firstNamelastName属性,在属于users集合的每个user文档中也不需要这些属性。 这就是使MongoDB与SQL数据库(例如MySQL或Microsoft SQL Server)有很大不同的原因,该SQL数据库要求对其存储的每个对象进行严格定义的数据库架构。

创建可作为文档存储在数据库中的动态对象的功能是Mongoose发挥作用的地方。

什么是猫鼬?

猫鼬是一个对象文档映射器(ODM)。 这意味着Mongoose允许您使用映射到MongoDB文档的强类型模式定义对象。

猫鼬在创建和使用模式方面提供了大量功能。 Mongoose当前包含八个SchemaType,将属性持久化到MongoDB时会将其保存。 他们是:

  1. 日期
  2. 缓冲
  3. 布尔型
  4. 混合的
  5. 对象编号
  6. 数组

每种数据类型都允许您指定:

  • 默认值
  • 自定义验证功能
  • 表示必填字段
  • 一个get函数,使您可以在数据作为对象返回之前进行操作
  • 设置函数,可让您在将数据保存到数据库之前进行操作
  • 创建索引以允许更快地获取数据

除了这些常用选项之外,某些数据类型还允许您进一步自定义如何存储数据以及如何从数据库中检索数据。 例如, String数据类型还允许您指定以下附加选项:

  • 将其转换为小写
  • 将其转换为大写
  • 在保存之前修剪数据
  • 一个正则表达式,可以限制在验证过程中允许保存的数据
  • 一个可以定义有效字符串列表的枚举

NumberDate属性都支持指定该字段允许的最小值和最大值。

您应该非常熟悉这八种允许的数据类型。 但是,可能会跳出一些异常,例如BufferMixedObjectIdArray

Buffer数据类型允许您保存二进制数据。 二进制数据的常见示例是图像或编码文件,例如PDF文档。

Mixed数据类型将属性转换为“一切”字段。 由于没有定义的结构,因此该字段类似于可以使用MongoDB的开发人员数量。 请谨慎使用此数据类型,因为它会丢失Mongoose提供的许多出色功能,例如数据验证和检测实体更改以在保存时自动知道更新属性。

ObjectId数据类型通常指定到数据库中另一个文档的链接。 例如,如果您有书籍和作者的集合,则书籍文档可能包含一个ObjectId属性,该属性引用文档的特定作者。

Array数据类型允许您存储类似JavaScript的数组。 使用Array数据类型,您可以对它们执行常见JavaScript数组操作,例如推,弹出,移位,切片等。

快速回顾

在继续并生成一些代码之前,我只想回顾一下我们刚刚学到的东西。 MongoDB是一个数据库,允许您存储具有动态结构的文档。 这些文档保存在集合中。

Mongoose是一个JavaScript库,允许您使用强类型数据定义架构。 定义架构后,Mongoose允许您基于特定架构创建模型。 然后,通过模型的架构定义将Mongoose模型映射到MongoDB文档。

定义了模式和模型后,Mongoose将包含许多不同的函数,这些函数使您可以使用常见的MongoDB函数来验证,保存,删除和查询数据。 我将在后面的具体代码示例中对此进行更多讨论。

安装MongoDB

在开始创建Mongoose模式和模型之前,必须先安装和配置MongoDB。 我建议访问MongoDB的下载页面 。 有几种不同的选项可供安装。 我已链接到社区服务器。 这使您可以安装特定于您的操作系统的版本。 MongoDB还提供企业服务器和云支持安装。 由于可以编写有关安装,调试和监视MongoDB的全部书籍,因此我将继续使用Community Server。

为您选择的操作系统下载并安装MongoDB之后,您将需要启动数据库。 建议不要访问MongoDB,而是建议访问MongoDB有关如何安装MongoDB Community Edition的文档。

在您配置MongoDB时,我将在这里等待。 准备就绪后,我们可以继续设置Mongoose以连接到新安装的MongoDB数据库。

设置猫鼬

猫鼬是一个JavaScript框架,我将在Node.js应用程序中使用它。 如果已经安装了Node.js,则可以继续执行下一步。 如果您尚未安装Node.js,建议您首先访问Node.js下载页面,然后为您的操作系统选择安装程序。

设置好Node.js并准备就绪后,我将创建一个新应用程序,然后安装Mongoose NPM软件包。

通过将命令提示符设置为希望将应用程序安装到的位置,可以运行以下命令:

mkdir mongoose_basics
cd mongoose_basics
npm init

对于应用程序的初始化,我将所有内容保留为其默认值。 现在,我将如下安装猫鼬软件包:

npm install mongoose --save

配置了所有先决条件后,让我们连接到MongoDB数据库。 我将以下代码放置在index.js文件中,因为我选择将其作为应用程序的起点:

var mongoose = require('mongoose');mongoose.connect('mongodb://localhost/mongoose_basics');

代码的第一行包括mongoose库。 接下来,我使用connect函数打开到名为mongoose_basics的数据库的connect

connect函数接受其他两个可选参数。 第二个参数是选项的对象,如果需要,您可以在其中定义用户名和密码之类的内容。 第三个参数,如果没有选项,也可以是第二个参数,它是尝试连接后的回调函数。 回调函数可以通过以下两种方式之一使用:

mongoose.connect(uri, options, function(error) {// Check error in initial connection. There is no 2nd param to the callback.});// Or using promisesmongoose.connect(uri, options).then(() => { /** ready to use. The `mongoose.connect()` promise resolves to undefined. */ },err => { /** handle initial connection error */ });

为了避免潜在地介绍JavaScript Promises ,我将使用第一种方法。 以下是更新的index.js文件:

var mongoose = require('mongoose');mongoose.connect('mongodb://localhost/mongoose_basics', function (err) {if (err) throw err;console.log('Successfully connected');});

如果连接到数据库时发生错误,则会引发异常,并停止所有进一步的处理。 当没有错误发生时,我已将成功消息记录到控制台。

现在已经建立了Mongoose并将其连接到名为mongoose_basics的数据库。 我的MongoDB连接未使用用户名,密码或自定义端口。 如果您需要在连接期间设置这些选项或任何其他选项,建议您阅读有关连接的Mongoose文档 。 该文档提供了许多可用选项的详细说明,以及如何创建多个连接,连接池,副本等。

成功建立连接后,让我们继续定义猫鼬模式。

定义猫鼬模式

在介绍中,我展示了一个包含两个属性的user对象: firstNamelastName 。 在以下示例中,我将该文档转换为Mongoose模式:

var userSchema = mongoose.Schema({firstName: String,lastName: String
});

这是一个非常基本的架构,仅包含两个属性,没有与之关联的属性。 让我们将第一和最后一个名称的属性是一个的子对象在本例中展开name属性。 name属性将包含名字和姓氏。 我还将添加一个created的类型为Date属性。

var userSchema = mongoose.Schema({name: {firstName: String,lastName: String},created: Date
});

如您所见,Mongoose允许我创建非常灵活的架构,并具有许多不同的组织数据方式的组合。

在下一个示例中,我将创建两个新的模式,这些模式将演示如何创建与另一个模式的关系: authorbookbook模式将包含对author模式的引用。

var authorSchema = mongoose.Schema({_id: mongoose.Schema.Types.ObjectId,name: {firstName: String,lastName: String},biography: String,twitter: String,facebook: String,linkedin: String,profilePicture: Buffer,created: { type: Date,default: Date.now}
});

上面的author架构扩展了我在上一个示例中创建的user架构的概念。 要将Author和Book链接在一起, author架构的第一个属性是_id属性,它是ObjectId架构类型。 _id是在Mongoose和MongoDB中创建主键的常用语法。 然后,像user模式一样,我定义了一个name属性,其中包含作者的名字和姓氏。

user模式上展开时, author包含其他几种String模式类型。 我还添加了可以容纳作者个人资料图片的Buffer模式类型。 最终属性保存作者的创建日期; 但是,您可能会注意到它的创建方式略有不同,因为它已将默认值定义为“ now”。 当作者保留在数据库中时,此属性将设置为当前日期/时间。

为了完成模式示例,我们使用ObjectId模式类型创建一个包含对作者的引用的book模式:

var bookSchema = mongoose.Schema({_id: mongoose.Schema.Types.ObjectId,title: String,summary: String,isbn: String,thumbnail: Buffer,author: { type: mongoose.Schema.Types.ObjectId, ref: 'Author' },ratings: [{summary: String,detail: String,numberOfStars: Number,created: { type: Date,default: Date.now}}],created: { type: Date,default: Date.now}
});

book模式包含String类型的几个属性。 如上所述,它包含对author架构的引用。 为了进一步证明了强大的模式定义, book架构还包含一个Arrayratings 。 每个评分numberOfStars summarydetailnumberOfStarscreated日期属性组成。

Mongoose使您可以灵活地创建对其他模式的引用来创建模式,或者如上例中带有ratings属性的示例一样,它允许您创建可以包含在相关模式(如书本到作者)中的子属性Array 。内联,如上面的示例(将书添加到rating Array )。

创建和保存猫鼬模型

由于authorbook模式展示了Mongoose的模式灵活性,因此我将继续使用这些模式并从中派生出AuthorBook模型。

var Author = mongoose.model('Author', authorSchema);var Book = mongoose.model('Book', bookSchema);

Mongoose模型在保存时会在MongoDB中创建一个文档,该文档具有由其派生的架构定义的属性。

为了演示如何创建和保存对象,在下一个示例中,我将创建几个对象:一个Author Model和几个Book Model。 一旦创建,这些对象将使用Model的save方法save到MongoDB中。

var jamieAuthor = new Author {_id: new mongoose.Types.ObjectId(),name: {firstName: 'Jamie',lastName: 'Munro'},biography: 'Jamie is the author of ASP.NET MVC 5 with Bootstrap and Knockout.js.',twitter: 'https://twitter.com/endyourif',facebook: 'https://www.facebook.com/End-Your-If-194251957252562/'
};jamieAuthor.save(function(err) {if (err) throw err;console.log('Author successfully saved.');var mvcBook = new Book {_id: new mongoose.Types.ObjectId(),title: 'ASP.NET MVC 5 with Bootstrap and Knockout.js',author: jamieAuthor._id,ratings:[{summary: 'Great read'}]};mvcBook.save(function(err) {if (err) throw err;console.log('Book successfully saved.');});var knockoutBook = new Book {_id: new mongoose.Types.ObjectId(),title: 'Knockout.js: Building Dynamic Client-Side Web Applications',author: jamieAuthor._id};knockoutBook.save(function(err) {if (err) throw err;console.log('Book successfully saved.');});
});

在上面的示例中,我无耻地插入了对我的两本书的引用。 该示例首先创建并保存从Author Model创建的jamieObject 。 在jamieObjectsave函数内部,如果发生错误,则应用程序将输出异常。 保存成功后,将在save功能内部创建并保存两个书本对象。 与jamieObject相似,如果保存时发生错误,则会输出错误。 否则,在控制台中输出成功消息。

为了创建对Author的引用,book对象都在book schema的author属性中引用了author schema的_id主键。

保存前验证数据

最终将创建模型的数据由网页上的表单填充的情况非常普遍。 因此,在将模型保存到MongoDB之前,先验证此数据是一个好主意。

在下一个示例中,我更新了以前的作者架构,以添加对以下属性的验证: firstNametwitterfacebooklinkedin

var authorSchema = mongoose.Schema({_id: mongoose.Schema.Types.ObjectId,name: {firstName: {type: String,required: true},lastName: String},biography: String,twitter: {type: String,validate: {validator: function(text) {return text.indexOf('https://twitter.com/') === 0;},message: 'Twitter handle must start with https://twitter.com/'}},facebook: {type: String,validate: {validator: function(text) {return text.indexOf('https://www.facebook.com/') === 0;},message: 'Facebook must start with https://www.facebook.com/'}},linkedin: {type: String,validate: {validator: function(text) {return text.indexOf('https://www.linkedin.com/') === 0;},message: 'LinkedIn must start with https://www.linkedin.com/'}},profilePicture: Buffer,created: { type: Date,default: Date.now}
});

firstName属性已使用required属性进行属性。 现在,当我调用save函数时,Mongoose将返回一条错误消息,并指出需要firstName属性。 我选择不设置所需的lastName属性,以防雪儿或麦当娜成为我数据库中的作者。

twitterfacebooklinkedin属性都应用了非常相似的自定义验证器。 它们每个都确保值以社交网络各自的域名开头。 这些字段不是必需的,因此仅在为该属性提供数据时才应用验证器。

搜索和更新数据

如果没有搜索记录并更新该对象的一个​​或多个属性的示例,对Mongoose的介绍就不会完整。

猫鼬提供了几种不同的功能来查找特定模型的数据。 这些函数是findfindOnefindById

findfindOne函数都接受一个对象作为输入,允许进行复杂的搜索,而findById仅接受带有回调函数的单个值(稍后将举一个示例)。 在下一个示例中,我将演示如何查找标题中包含字符串“ mvc”的所有书籍。

Book.find({title: /mvc/i
}).exec(function(err, books) {if (err) throw err;console.log(books);
});

find函数中,我正在title属性上搜索不区分大小写的字符串“ mvc”。 这可以通过使用与JavaScript搜索字符串相同的语法来完成。

find函数调用还链接到其他查询方法,例如where and or limitsortany等。

让我们扩展前面的示例,将结果限制在前五本书中,并以创建日期降序排序。 这将返回标题中包含“ mvc”的最新五本书。

Book.find({title: /mvc/i
}).sort('-created')
.limit(5)
.exec(function(err, books) {if (err) throw err;console.log(books);
});

应用后find功能,因为所有的链接功能被编译成一个单一的查询,而不是执行,直到其他功能的顺序并不重要exec函数被调用。

正如我前面提到的, findById的执行方式findById不同。 它立即执行并接受回调函数,而不是允许使用一系列函数。 在下一个示例中,我通过_id查询特定作​​者。

Author.findById('59b31406beefa1082819e72f', function(err, author) {if (err) throw err;console.log(author);
});

您的情况下的_id可能略有不同。 当查找书名中带有“ mvc”的书籍列表时,我从以前的console.log复制了此_id

返回对象后,您可以修改其任何属性以更新它。 进行必要的更改后,就可以调用save方法,就像创建对象时一样。 在下一个示例中,我将扩展findbyId示例并更新作者的linkedin属性。

Author.findById('59b31406beefa1082819e72f', function(err, author) {if (err) throw err;author.linkedin = 'https://www.linkedin.com/in/jamie-munro-8064ba1a/';author.save(function(err) {if (err) throw err;console.log('Author updated successfully');});
});

成功检索作者后,将设置linkedin属性并调用save功能。 Mongoose能够检测到linkedin属性已更改,并且它将仅对已修改的属性发送一条更新语句到MongoDB。 如果保存时发生错误,将引发异常并停止应用程序。 成功后,将成功消息记录到控制台。

Mongoose还提供了两个附加函数,这些函数可以查找对象并使用适当命名的函数一步将其保存: findByIdAndUpdatefindOneAndUpdate 。 让我们更新前面的示例以使用findByIdAndUpdate

Author.findByIdAndUpdate('59b31406beefa1082819e72f', { linkedin: 'https://www.linkedin.com/in/jamie-munro-8064ba1a/' }, function(err, author) {if (err) throw err;console.log(author);
});

在前面的示例中,要更新的属性作为对象提供给findByIdAndUpdate函数的第二个参数。 现在,回调函数是第三个参数。 更新成功后,返回的author对象将包含更新的信息。 这被记录到控制台以查看更新的作者的属性。

最终样本代码

在整个本文中,我提供了一些代码片段,这些代码片段标识了非常具体的操作,例如创建模式,创建模型等。让我们将它们全部放到一个完整的示例中。

首先,我创建了两个附加文件: author.jsbook.js 这些文件包含其各自的架构定义和模型创建。 代码的最后一行使该模型可在index.js文件中使用。

让我们从author.js文件开始:

var mongoose = require('mongoose');var authorSchema = mongoose.Schema({_id: mongoose.Schema.Types.ObjectId,name: {firstName: {type: String,required: true},lastName: String},biography: String,twitter: {type: String,validate: {validator: function(text) {return text.indexOf('https://twitter.com/') === 0;},message: 'Twitter handle must start with https://twitter.com/'}},facebook: {type: String,validate: {validator: function(text) {return text.indexOf('https://www.facebook.com/') === 0;},message: 'Facebook must start with https://www.facebook.com/'}},linkedin: {type: String,validate: {validator: function(text) {return text.indexOf('https://www.linkedin.com/') === 0;},message: 'LinkedIn must start with https://www.linkedin.com/'}},profilePicture: Buffer,created: { type: Date,default: Date.now}
});var Author = mongoose.model('Author', authorSchema);module.exports = Author;

接下来是book.js文件:

var mongoose = require('mongoose');var bookSchema = mongoose.Schema({_id: mongoose.Schema.Types.ObjectId,title: String,summary: String,isbn: String,thumbnail: Buffer,author: { type: mongoose.Schema.Types.ObjectId, ref: 'Author' },ratings: [{summary: String,detail: String,numberOfStars: Number,created: { type: Date,default: Date.now}}],created: { type: Date,default: Date.now}
});var Book = mongoose.model('Book', bookSchema);module.exports = Book;

最后,更新的index.js文件:

var mongoose = require('mongoose');var Author = require('./author');
var Book = require('./book');mongoose.connect('mongodb://localhost/mongoose_basics', function (err) {if (err) throw err;console.log('Successfully connected');var jamieAuthor = new Author({_id: new mongoose.Types.ObjectId(),name: {firstName: 'Jamie',lastName: 'Munro'},biography: 'Jamie is the author of ASP.NET MVC 5 with Bootstrap and Knockout.js.',twitter: 'https://twitter.com/endyourif',facebook: 'https://www.facebook.com/End-Your-If-194251957252562/'});jamieAuthor.save(function(err) {if (err) throw err;console.log('Author successfully saved.');var mvcBook = new Book({_id: new mongoose.Types.ObjectId(),title: 'ASP.NET MVC 5 with Bootstrap and Knockout.js',author: jamieAuthor._id,ratings:[{summary: 'Great read'}]});mvcBook.save(function(err) {if (err) throw err;console.log('Book successfully saved.');});var knockoutBook = new Book({_id: new mongoose.Types.ObjectId(),title: 'Knockout.js: Building Dynamic Client-Side Web Applications',author: jamieAuthor._id});knockoutBook.save(function(err) {if (err) throw err;console.log('Book successfully saved.');});});
});

在上面的示例中,所有Mongoose操作都包含在connect函数中。 在包含mongoose库之后, authorbook文件便包含在require函数中。

随着MongoDB的运行,您现在可以使用以下命令运行完整的Node.js应用程序:

node index.js

将一些数据保存到数据库后,我使用查找功能更新了index.js文件,如下所示:

var mongoose = require('mongoose');var Author = require('./author');
var Book = require('./book');mongoose.connect('mongodb://localhost/mongoose_basics', function (err) {if (err) throw err;console.log('Successfully connected');Book.find({title: /mvc/i}).sort('-created').limit(5).exec(function(err, books) {if (err) throw err;console.log(books);});Author.findById('59b31406beefa1082819e72f', function(err, author) {if (err) throw err;author.linkedin = 'https://www.linkedin.com/in/jamie-munro-8064ba1a/';author.save(function(err) {if (err) throw err;console.log('Author updated successfully');});});Author.findByIdAndUpdate('59b31406beefa1082819e72f', { linkedin: 'https://www.linkedin.com/in/jamie-munro-8064ba1a/' }, function(err, author) {if (err) throw err;console.log(author);});
});

您可以再次使用以下命令运行该应用程序: node index.js

摘要

阅读本文之后,您应该能够创建极其灵活的Mongoose架构和模型,应用简单或复杂的验证,创建和更新文档,最后搜索创建的文档。

希望您现在可以轻松使用Mongoose。 如果您想了解更多信息,我建议您阅读《 猫鼬指南》 ,其中深入探讨了诸如人口,中间件,承诺等更高级的主题。

狩猎快乐(可怜的猫鼬动物参考)!

翻译自: https://code.tutsplus.com/articles/an-introduction-to-mongoose-for-mongodb-and-nodejs--cms-29527

MongoDB和Node.js的Mongoose简介相关推荐

  1. Mongodb 数据库基本操作语句,结合 Node.js + express + mongoose (实现增、删、改、查,批量增加、修改等,创建临时表,多表查询......)

    版本说明 "MongoDB": "4.0.9", // cmd命令窗输入:mongo --version "node": 10.13.0, ...

  2. Node.js使用mongoose操作mongodb

    软件配置: 1.node v8.9.3 2. npm 5.5.1 3. mongoose及MongoDB版本见下package.json // package.json {   "name& ...

  3. Node.js 常用Mongoose方法

    Node.js 手册查询-Mongoose 方法 一.Schema 一种以文件形式存储的数据库模型骨架,无法直接通往数据库端,也就是说它不具备对数据库的操作能力.可以说是数据属性模型(传统意义的表结构 ...

  4. 基于Node.js + jade + Mongoose 模仿gokk.tv

    原文摘自我的前端博客,欢迎大家来访问 http://www.hacke2.cn 关于gokk 大学的娱乐活动基本就是在寝室看电影了→_→,一般都会选择去goxiazai.cc上看,里面的资源多,质量高 ...

  5. Node.js学习-12nodejs简介

    一.nodejs简介 1.代码:语言 c/c++ windows linux swift object-c iOS java 安卓 html/css/js 浏览器 javascript node.js ...

  6. Node.js对MongoDB进行增删改查操作

    MongoDB简介 MongoDB是一个开源的.文档型的NoSQL数据库程序.MongoDB将数据存储在类似JSON的文档中,操作起来更灵活方便.NoSQL数据库中的文档(documents)对应于S ...

  7. 路漫漫其修远兮:js的成长经历(二十五)—— Node.js中的MongoDB

    目录 MongoDB简介 MongoDB安装教程 mongoDB基本组成 mongoDB的基本指令 安装可视化操作软件 熟悉使用MongoDB的基本指令(增删改查等) Node进阶-Mongoose ...

  8. MongoDB的Mongoose简介

    by Nick Karnik 尼克·卡尼克(Nick Karnik) MongoDB的Mongoose简介 (Introduction to Mongoose for MongoDB) Mongoos ...

  9. Node.js+Express+MongoDB 实现学生增删改查

    前言 选用Node.js,Express,MongoDB来实现一个学生信息的增删改查. Express框架搭建服务器 art-template模板实现页面 MongoDB数据库 Mongoose操作数 ...

最新文章

  1. Python变量作用域的规则以及如何搜索内置作用域
  2. 生成jpg的缩略图并添加水印
  3. jQuery中的slideUp()、slideDown()、hide()、show()
  4. Unity—AssetBundle的打包及四种加载资源方式
  5. python--类与GUI编程框架
  6. 国防科大JAVA工程师笔试题_国防科大人工智能考博题答案
  7. Kerberos加密级别不支持的问题
  8. #pragma once 和 #ifndef ... #define ... #endif 的区别
  9. 2017.9.12 人员雇佣 失败总结
  10. Android入门:通过JSON数据与服务器进行通信
  11. php悬浮框,PopupWindow(悬浮框)的基本使用
  12. 去掉softmax后Transformer会更好吗?复旦华为诺亚提出SOFT:轻松搞定线性近似
  13. 软件那么多,恢复数据还靠它
  14. Hibernate二级缓存
  15. 工程上为什么常用3dB带宽?而不是1dB或者2dB
  16. 网络安全与渗透:sql注入,一文详解(九)此生无悔入华夏,男儿何不带吴钩
  17. Arduino Nano开发板设备描述符无法识别等问题汇总
  18. mysql双活脑裂_从两地三中心到双活数据中心
  19. 数学笔记24——分部积分
  20. 谷粒商城--环境部署(2022/7/28最新)

热门文章

  1. Windows 7 无法登陆网银
  2. 激励和梦想永远是我前进路上的左右手(名人名言)
  3. python时间格式化/时间格式转换
  4. baidupcsgo安卓_分享一个开源的网盘下载工具BaiduPCS-Go
  5. react的路由切换动画
  6. 464页PPT!南航李丕绩教授的《ChatGPT的前世今生》
  7. lc_other_8_myAtoi
  8. vim中let与set的区别
  9. 抓出卡顿元凶,从分析掉帧开始
  10. 今天公布!英语四六级考试成绩