http://mongoosejs.com/docs/api.html#index-js
mongoose是nodejs环境下操作mongodb的模块封装,使用mongoose之后,实际上只需要在mongodb中创建好数据库与用户,集合的定义、创建、操作等直接使用mongoose即可。

  • 一、连接
  • 二、重要概念
  • 三、基本操作
    • 1、Schema
    • 2、Model
    • 3、实例化document
    • 4、保存数据
    • 5、文档查询
    • 6、文档更新
    • 7、文档删除
    • 8、自定义方法
    • 9、虚拟属性
    • 10、前置与后置钩子

一、连接

let mongoose = require('mongoose');//连接mongodb
//非auth模式
//mongoose.connect('mongodb://localhost:27017/mall');
//auth模式
/**
mongodb 为协议
第一个mall: 连接数据库的用户名
123456: 用户的密码
localhost: mongodb地址
27017: mongodb端口号
第二个mall: 数据库名字
**/
mongoose.connect('mongodb://mall:123456@localhost:27017/mall');//连接失败
mongoose.connection.on('connected',() => {console.log('mongodb connected!');
});//连接成功
mongoose.connection.on('error',(err) => {console.log('mongodb connect fail:'+err);
});//连接断开
mongoose.connection.on('disconnected',() => {console.log('mongodb connect disconnected!');
});// connection的事件列表可查看:http://mongoosejs.com/docs/api.html#connection_Connection
// 或 ./node_modules/mongoose/lib/connection.js#Connection()// 关闭的两种方式
// mongoose.connection.close(); 等同于 db.close();
// mongoose.disconnect();

二、重要概念


  Mongooose中,有三个比较重要的概念,Schema、Model、Document。Schema生成Model,Model实例化成为Document。

  Schema用于定义数据库的结构。类似创建表时的数据定义(不仅仅可以定义文档的结构和属性,还可以定义文档的实例方法、静态模型方法、复合索引等),每个Schema会映射到mongodb中的一个collection,Schema不具备操作数据库的能力。

  Model是由Schema编译而成的构造器,具有抽象属性和行为,可以对数据库进行增删查改。Model的每一个实例(instance)就是一个文档document。

  Document是new Model后创建的实例,它的操作会影响数据库。

三、基本操作

1、Schema

Schema是对mongodb中某个集合的结构描述,它定义一个集合中应该包含哪些数据项,每个数据项的数据类型与检查约束。可以想象成一个抽象类,只有定义,没有实现。就是规定好某个集合的框架应该是怎么样的。也可以理解是mysql中的表结构。
可以为数据项指定的数据类型有8种:

String      字符串
Number      数字
Date        日期
Buffer      二进制
Boolean     布尔值
Mixed       混合类型
ObjectId    对象ID
Array       数组

创建Schema

const mongoose = require('mongoose')
//获取Schema
const Schema = mongoose.Schema;//声明一个Schema实例,实际就是创建一个集合的结构框架
let animalSchema = new Schema({title: String, //类型可以是首字母大写age: 'number', //类型也可以写成字符串全小写food: [{ //数组name: String}]
});//如果需要为animalSchema增加属性,可以使用add
animalSchema.add({sex: {type: String,enum: ['male', 'female'] //规定,字段值为枚举约束,只能填male/female}
});

创建Schema时可以有一系列的约束条件,类似mysql中字段的非空、唯一等约束。
基本语法是:

{name: {type:String, validator:value}}

常用的约束有:

required: 数据必须填写
default: 默认值
min: 最小值(只适用于数字)
max: 最大值(只适用于数字)
match: 正则匹配(只适用于字符串)
enum:  枚举匹配(只适用于字符串)
validate: 自定义匹配

其中validate是自定义约束
例如要自定义检查长度

// 自定义长度检查
let lengthValidate = (param) => {return param.length <= 10
};//如果需要为animalSchema增加属性,可以使用add
animalSchema.add({sex: {type: String,enum: ['male', 'female'], //规定,字段值为枚举约束,只能填male/femalevalidate: lengthValidate}
});

2、Model

model是根据Schema定义的结构框架生成一个映射mongodb的数据模型,可以把它理解是一个类,是根据Schema创建的一个类,他的数据结构是依据Schema生成的,这个类用来生成document实例。

//通过schema构建model,第一个参数是给model起个名字,第二个参数是构造这个model所依据的schema
let animalModel = mongoose.model('animalModel', animalSchema);

3、实例化document

document是相当于是mongodb中的每一条数据了,它是通过new一个model得到的。

  // 通过实例化model得到document,document的数据项需根据schema定义的结构来写// 通过实例化model得到document,document的数据项需根据schema定义的结构来写let cat = new animalModel({title: '猫咪',food: [{name: '鱼'},{name: '火腿'}],sex: 'male'});

到这一步,mongodb中还没有animalMode集合以及数据。

4、保存数据

  • 1、save()
    在mongoose中保存数据,实际就是保存某个document,document通过model实例化来创建。document的save()方法可以将document映射保存到mongodb中,save()方法中可以传递一个回调函数作为参数。因为是在document上调用,自然一次只保存一条文档。
    方法原型:
save(function (err, document) {}) //回调函数中第二个参数是使用保存的数据形成的对象

保存cat:

//保存document,映射成为一条记录
cat.save((err, catObj) => {if (err) {console.log('保存失败');console.info(err);}  else {console.info(catObj);}
});

保存后打印:

{ title: '猫咪',food: [ { name: '鱼', _id: 5a96555e95f678530054f918 },{ name: '火腿', _id: 5a96555e95f678530054f917 } ],sex: 'male',_id: 5a96555e95f678530054f919,__v: 0 }

保存后这个回调中的第二个参数就被填充好值,其中的主键_id也在其中,__v是mongoose用来管理数据版本号的标识,自己不用动。
这时看mongodb中已有集合,集合中已经有一条数据(文档)了。

注意:
1、从上图可以看出来,集合的名字是根据mongoose.model('animalModel', animalSchema)中第一个参数来的,变成小写且复数形式,如果结尾是数字则不变。比如这个地方如果第一个参数给的是’animal’,则生成的集合名称就应该是’animals’。
2、在mongoose中操作mongodb,因为有了schema、model、ducument的概念,就不是像mysql一样,事先创建好表结构,而是根据数据结构设计,在代码中声明好schema,再生成model、创建document,进行数据操作。

  • 2、create()
    和save()不一样的是,create()不是在document上调用的,而是在model上调用的,并且可以一次保存多条文档。
    接着用上面的animalModel,这次不去new它了,也就是不去操作document了,直接用mongoose.model出来的model。同时保存两条狗。
    let dog1 = {title: '1号狗狗',food: [{name: '骨头'},{name: '肉肉'},],sex: 'male'};let dog2 = {title: '2号狗狗',food: [{name: '骨头'},{name: '肉肉'},],sex: 'male'};animalModel.create(dog1, dog2, (err, rsObj1, rsObj2) => {if (err) {console.log('保存失败');console.info(err);} else {console.log('保存成功');console.info(rsObj1);console.info(rsObj2);}});

打印的结果:

保存成功
{ title: '1号狗狗',food: [ { name: '骨头', _id: 5a96607b5ce650532494f010 },{ name: '肉肉', _id: 5a96607b5ce650532494f00f } ],sex: 'male',_id: 5a96607b5ce650532494f011,__v: 0 }
{ title: '2号狗狗',food: [ { name: '骨头', _id: 5a96607b5ce650532494f013 },{ name: '肉肉', _id: 5a96607b5ce650532494f012 } ],sex: 'male',_id: 5a96607b5ce650532494f014,__v: 0 }

再看看mongodb中:

注意:这里也验证了一个问题,就是如果model对应的集合已经存在于mongodb中了,则会直接往这个集合中添加文档,如果还没有,则会创建集合。所以在上面保存猫咪的时候,同时创建了集合与添加猫咪文档,而这里的狗狗,直接添加文档。

  • 3、insertMany()
    insertMany()也是model的方法,一次插入多条数据,注意回调中的返回值是所有结果对象的数组。
    let monkey1 = {title: '1号猴猴',food: [{name: '香蕉'},{name: '桃'},],sex: 'male'};let monkey2 = {title: '2号猴猴',food: [{name: '香蕉'},{name: '桃'},],sex: 'male'};animalModel.insertMany([monkey1, monkey2], (err, rsArr) => {if (err) {console.log('保存失败');console.info(err);} else {console.log('保存成功');console.info(rsArr);}});

打印结果:

保存成功
[ { title: '1号猴猴',food: [ [Object], [Object] ],sex: 'male',_id: 5a967c3f44d6d753b6cd6354,__v: 0 },{ title: '2号猴猴',food: [ [Object], [Object] ],sex: 'male',_id: 5a967c3f44d6d753b6cd6357,__v: 0 } ]

5、文档查询

mongodb中的数据查询就是查文档。

  • 1、find()
    find()是用在model上的,这一点很容易理解,既然是查询数据,那么当前肯定是不明确要得到的文档结果是怎样的,方法也就不会在document上。
    find()方法在官方文档中的描述是:
Model.find()Parameters [callback] «Function»
Returns:«Query»Finds documents
The conditions are cast to their respective SchemaTypes before the command is sent.Example:
// named john and at least 18
MyModel.find({ name: 'john', age: { $gte: 18 }});// executes immediately, passing results to callback
MyModel.find({ name: 'john', age: { $gte: 18 }}, function (err, docs) {});// name LIKE john and only selecting the "name" and "friends" fields, executing immediately
MyModel.find({ name: /john/i }, 'name friends', function (err, docs) { })// passing options
MyModel.find({ name: /john/i }, null, { skip: 10 })// passing options and executing immediately
MyModel.find({ name: /john/i }, null, { skip: 10 }, function (err, docs) {});// executing a query explicitly
var query = MyModel.find({ name: /john/i }, null, { skip: 10 })
query.exec(function (err, docs) {});// using the promise returned from executing a query
var query = MyModel.find({ name: /john/i }, null, { skip: 10 });
var promise = query.exec();
promise.addBack(function (err, docs) {});

官方示例find的用法非常清晰了,find方法最简的参数是只给一个回调函数,表示查询所有结果,相当于是sql中的select * from 表名。
find()方法返回值是query对象,query对象有exec和一系列方法,exec()可以执行准备好的find()查询,回调得到结果。

回到animal示例中:
查询animalmodels集合中所有的文档:

//方式一:
animalModel.find((err, rs) => {if (err) {console.log('查询失败');console.info(err);} else {console.log('查询成功');console.info(rs);}});//方式二:let animalQuery = animalModel.find();animalQuery.exec((err, rs) => {if (err) {console.log('查询失败');console.info(err);} else {console.log('查询成功');console.info(rs);}});//两种方式的结果是一样的,返回的rs是一个包含集合中所有文档的数组

find()的完整参数列表是:
Model.find([查询条件], [返回字段列表], [设置选项], [回调函数]);
对于第一个参数,比如age: { $gte: 18 },表示要查询年龄大于等于18的文档。常用的条件操作符有:

$lt、$lte、$gt、$gte、$in、$nin、$eq、$ne、$or(model.find({$or:[{ color: 'red' }, { status: 'emergency' }]}))、$and、$not、$nor、$exits(值是否存在,model.find({name:{$exits:true}}))、$all(通常用来匹配数组里面的键值,匹配多个值(同时具有) $all:[“apple”,“banana”,“peach”]})、$size(用来查询数组的长度值 model.find({name:{$size:3}}); 匹配name的数组长度为3)

有一个特殊的操作符$where,可以灵活的设置查询条件,$where后可以直接写任何的js作为查询条件,但实际上完全可以使用model.where()方法来代替,where()会更加方便。

{$where:"this.x == this.y"}
{$where:function(){return obj.x !== obj.y;
}}

这些操作符在find的第一个参数中使用,query中也有一一对一个的方法。

对于第二个参数,可以设置要返回的字段列表,{name:1,_id:0}表示返回name字段,不返回_id字段。

对于第三个参数,官方示例中用到了一个skip,还有limit、sort。

除了find()外,还有findById()、findOne(),与find的用法都是一样的。只不过findById的第一个参数是准确的id值。这两个方法都只返回一条文档。

  • 2、where()
    官网上这么介绍的
Model.where()
Parameters [val] «Object» optional valueReturns:«Query»
Creates a Query, applies the passed conditions, and returns the Query.

官方示例:

User.find({age: {$gte: 21, $lte: 65}}, callback);
等同于
User.where('age').gte(21).lte(65).exec(callback);

因为where返回的是query对象,所以可以链式调用:

User
.where('age').gte(21).lte(65)
.where('name', /^b/i)
... etc

所以在实际的使用过程中,用query对象来进行查询是比较方便的,用find()的返回值query链式调用其他方法,或者用where()的返回值query链式调用其他方法,最后用exec(callback)得到结果,这样比较清晰。query有一系列的方法,异步官方文档。

  • 3、query常用方法
sort     排序
skip     跳过
limit    限制
select   显示字段
exect    执行
count    计数
distinct 去重

关于分页与排序,query的三个方法:
query.limit(20); //只返回前20个内容
query.skip(2); //跳过多少条,实际就是开始的索引
query.sort({name:1,age:-1}); //1升序,2降序

6、文档更新

  • 1、update()
    Model.update(conditions, doc, [options], [callback])
    第一个参数conditions为查询条件,第二个参数doc为需要修改的数据,第三个参数options为控制选项,第四个参数是回调函数。
    options有以下选项:
    safe (boolean): 默认为true。安全模式。upsert (boolean): 默认为false。如果不存在则创建新记录。multi (boolean): 默认为false。是否更新多个查询记录。runValidators: 如果值为true,执行Validation验证。setDefaultsOnInsert: 如果upsert选项为true,在新建时插入文档定义的默认值。strict (boolean): 以strict模式进行更新。overwrite (boolean): 默认为false。禁用update-only模式,允许覆盖记录。

把名字叫猫咪的文档的名字更新成猫猫:

    animalModel.update({title :{$eq: '猫咪'}}, {title: '猫猫'}, (err, rs) => {if (err) {console.log('更新失败');console.info(err);} else {console.log('更新成功');console.info(rs);}});

如果不设置multi的话,即使有多条符合条件的文档,也只更新一条。
如果设置options里的upsert参数为true,若没有符合查询条件的文档,mongo将会综合第一第二个参数向集合插入一个新的文档。

  • 2、updateMany()
    updateMany()与update()的区别是更新多个文档,即使设置{multi:false}也更新多个。

  • 3、updateOne()
    updateOne()只更新找到的第一条数据,即使设置{multi:true}只更新一个。

  • 4、数组更新
    update第二个参数,实际可以写成{$set: {title: '猫猫'}}$set操作符表示设值,如果要更新的是数组,可以有以下操作符:

$addToSet  // 当且仅当待添加到数组的元素是当前数组所没有时,才会去添加
$pop       // 当指定数组的修饰符值为-1时,删除该数组的第1个元素;当值为1时,删除最后一个元素
$pull      // 删除指定查询条件下的数组元素
$pullAll   // 删除数组中符合给定的集合的值的所有元素,与$pull的区别于目标删除元素是被逐一列举出来的
$push      // 将元素添加到指定的数组中

给猫添加一个食物:

    animalModel.update({title :{$eq: '喵星人'}},{$addToSet:{food: {name: '蛋黄派'}}},(err,rs) =>{if (err) {console.info("保存失败");console.info(err);} else {console.info("保存成功");console.info(rs);}});

结果:

保存成功
{ n: 1, nModified: 1, ok: 1 }
  • 5、其他
    关于其他更新方法还有findOneAndUpdate()、findByIdAndUpdate()。
    通过查询+保存操作也可以实现更新,查询的结果实际是一个document对象,调用save方法,猜想是通过_id与_v来进行的比对,将直接更新本条记录。
animalModel.where('title').eq('猫猫').exec((err, rs) => {if (err) {console.info(err);} else {console.info(rs);rs.forEach(function(item,index,array){item.title = "喵星人";item.save((err,rs) =>{if (err) {console.info("保存失败");console.info(err);} else {console.info("保存成功");console.info(rs);}});});}});

7、文档删除

  • 1、remove()
    remove方法存在两个位置,一个是model上,一个是ducument上,model上就是根据条件去删除集合内某些文档,文档上就是删除自己。
model.remove(conditions, [callback])
document.remove([callback])

示例:

 //model删除animalModel.remove({title :{$eq: '喵星人'}},(err,rs) =>{if (err) {console.info("删除失败");console.info(err);} else {console.info("删除成功");console.info(rs);}});//document删除animalModel.where('title').eq('2号猴猴').exec((err, rs) => {if (err) {console.info(err);} else {console.info(rs);rs.forEach(function(item,index,array){item.remove((err,rs) =>{if (err) {console.info("删除失败");console.info(err);} else {console.info("删除成功");console.info(rs);}});});}});
  • 2、findOneAndRemove()
    model的remove()会删除符合条件的所有数据,如果只删除符合条件的第一条数据,可以使用model的findOneAndRemove()方法
Model.findOneAndRemove(conditions, [options], [callback])
  • 3、findByIdAndRemove()
    通过id删除文档
Model.findByIdAndRemove(id, [options], [callback])
  • 4、其他
    所有删除方法的回调函数都不能省略,否则不成功。要么写在方法的回调函数参数中,如果不写回调函数参数,则可以在最后调用.exec()。

8、自定义方法

mongoose中可以利用已有的api,自定义自己的方法来处理特定的业务需要。

  • 1、实例方法
    通过new model得到的document实例有很多增删改查的方法,如上面的save()方法用来保存文档,我们可以给实例增加自定义方法来实现特定的业务逻辑,增加之后就可以像调用save一样来调用自己的方法,给document增加自定义方法是通过扩展Schema的methods属性来完成的。

为animalSchema扩展document实例方法:

//为document实例添加eat方法,返回所有喜欢吃的食物
animalSchema.methods.eat = function () {let foodList = this.food;let foodStr = '';foodList.forEach(function (item, index, arr) {if (index == 0) {foodStr += item.name;} else {foodStr += ","+item.name;}});return foodStr;
};

document实例调用:

    animalModel.findById('5a96607b5ce650532494f011').exec((err, rs) => {if (err) {console.info("查询失败");console.info(err);} else {console.info("查询成功");console.info(rs);let eatStr =  rs.eat();console.info(eatStr); //骨头,肉肉}});
  • 2、静态方法
    静态方法,是不需要实例化得到document,而是直接在model上调用的方法。
    所以,实例方法,是操作文档(mongodb中的某一条记录)数据用的,静态方法是操作集合用的。

为animalSchema添加静态方法:

//为model添加findDogs方法
animalSchema.statics.findDogs = function (backfun){this.find({title: /狗/}).exec(backfun);
};

model调用:

    animalModel.findDogs((err, rs) => {if (err) {console.info("查询失败");console.info(err);} else {console.info("查询成功");console.info(rs); //打印出两条狗的document}});

-3、查询方法
扩展schema对象的query属性,给model添加查询方法,主要为了扩展query的个性化方法需要。

//为query添加findMonkey方法
animalSchema.query.findMonkey = function (backfun){this.find({title: /猴/}).exec(backfun);
};

query调用:

    let rsQuery = animalModel.find();rsQuery.findMonkey((err, rs) => {if (err) {console.info("查询失败");console.info(err);} else {console.info("查询成功");console.info(rs); //打印出一只猴子的document}});

9、虚拟属性

schema上除了可以设置方法以外,还可以设置虚拟属性,就像vue的getter一样,实际数据库中没有这个属性,但可以通过虚拟属性机制自定义一个经过处理的值,虽然用方法同样能实现,但虚拟属性效率更高。

//添加虚拟属性nameAndSex
animalSchema.virtual('info').get(function () {return this.title + "," +this.sex;
});

调用:

    animalModel.find({title: /狗/}).exec((err, rs) => {if (err) {console.info("查询失败");console.info(err);} else {console.info("查询成功");rs.forEach( function (item, index, arr) {console.info(item.info);});}});

打印结果:

1号狗狗,male
2号狗狗,male

10、前置与后置钩子

通过schema可以为指定的某些方法添加前置钩子函数与后置钩子函数。前置钩子函数在指定方法开始前调用,后置钩子函数不是在指定方法结束后操作,二是在指定方法开始前的最后一步操作。
前置钩子是pre()
后置钩子是post()

//为find方法指定前置操作1
animalSchema.pre('find', function (next) {console.info('前置操作1');next();
});//为find方法指定前置操作2
animalSchema.pre('find', function (next) {console.info('前置操作2');next();
});//为find方法指定后置操作1
animalSchema.post('find', function () {console.info('后置操作1');
});//为find方法指定后置操作2
animalSchema.post('find', function () {console.info('后置操作2');
});

调用find()方法

    animalModel.find({title: /狗/}).exec((err, rs) => {if (err) {console.info("查询失败");console.info(err);} else {console.info("查询成功");console.info(rs);}});

得到打印结果:

前置操作1
前置操作2
后置操作1
后置操作2
查询成功
[ { food: [ [Object], [Object] ],_id: 5a96607b5ce650532494f011,title: '1号狗狗',sex: 'male',__v: 0 },{ food: [ [Object], [Object] ],_id: 5a96607b5ce650532494f014,title: '2号狗狗',sex: 'male',__v: 0 } ]

注意,前置钩子中有一个next函数,如果方法内不调用next(),被设置前置钩子的方法(比如find())执行到钩子时不会继续向下执行。

可以添加前后钩子的方法有:

init
validate
save
remove
count
find
findOne
findOneAndRemove
findOneAndUpdate
insertMany
update

以上是mongoose的常用基本操作,还有索引、聚合等等操作需要积累补充。

mongoose操作mongodb相关推荐

  1. Node.js使用mongoose操作mongodb

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

  2. Egg 中结合Mongoose操作MongoDB

    1. 安装模块 npm i egg-mongoose --save 2. 配置 egg-mongoose 插件 // config/plugin.js 'use strict'; exports.ej ...

  3. 使用mongoose 在 Node中操作MongoDB数据库

    MongoDB 关系型和非关系型数据库 关系型数据库(表就是关系,或者说表与表之间存在关系). 所有的关系型数据库都需要通过sql语言来操作 所有的关系型数据库在操作之前都需要设计表结构 而且数据表还 ...

  4. MONGOOSE – 让NODE.JS高效操作MONGODB(转载)

    Mongoose库简而言之就是在node环境中操作MongoDB数据库的一种便捷的封装,一种对象模型工具,类似ORM,Mongoose将数据库中的数据转换为JavaScript对象以供你在应用中使用. ...

  5. MongoDB数据库操作---mongoose操作

    Mongoose认知概念 Mongoose是MongoDB的一个对象模型工具,其可以在一部环境下执行.同时它也是针对MongoDB操作的一个队形模型库,封装了MongoDB对文档的一些增删改查等常用方 ...

  6. 使用第三方包mongoose来操作MongoDB数据库,解决报错:MongooseError

    使用第三方包mongoose来操作MongoDB数据库 官方网站 配置文件 异常 异常描述: MongooseError: Operation cats.insertOne() buffering t ...

  7. nodejs操作mongodb数据库(mongoose)

    准备 在上一篇的基础上,通过npm安装mongoose. 关于mongoose Mongoose是MongoDB的一个对象模型工具,是基于node-mongodb-native开发的MongoDB n ...

  8. 58 Node.js中操作mongoDB数据库

    技术交流 QQ 群:1027579432,欢迎你的加入! 欢迎关注我的微信公众号:CurryCoder 的程序人生 1.数据库概述及环境搭建 1.1 为什么要使用数据库 动态网站中的数据都是存储在数据 ...

  9. koa --- mongoose连接mongoDB

    使用Mongoose对MongoDB进行操作 const mongoose = require('mongoose'); mongoose.connect('mongodb://localhost/t ...

最新文章

  1. iOS UI-IOS开发中Xcode的一些使用技巧
  2. 自动挂载ios_Ubuntu自动挂载iso文件 | 学步园
  3. Java其他API介绍
  4. LightCounting预测以太网光模块市场未来5年18%增速
  5. Linux下安装Perl模块
  6. Java中判断字符串是否为数字的五种方法
  7. 国考最热岗位报录比20602:1?还是数据库知识挑战赛适合我
  8. codeforces#253 D - Andrey and Problem里的数学知识
  9. c语言二级考试笔试真题,全国计算机等级考试二级C语言笔试真题及答案.doc
  10. python的parse函数没有执行——问题已解决
  11. java——jui的应用
  12. 阈值Java_阈值处理(深入学习)
  13. java判断子串重复_判断字符串是否是由子串重复多次构成
  14. 3 Linux虚拟机创建修改删除文件和文件夹
  15. 系统崩溃,TCP协议栈
  16. 刚工作2年时15k运维工程师-简历
  17. 送 10 本签名书!
  18. 不优雅地解决pytorch模型测试阶段显存溢出问题
  19. 2022最新软件测试面试题(含答案)
  20. python小游戏经典猫和老鼠

热门文章

  1. [二叉树]二叉搜索树转换为双向链表(剑指Offer26)
  2. 使用C#编程解决数独求解过程(从图片识别到数独求解)第二篇
  3. sublime text2快捷键
  4. MySQL 中 AUTO_INCREMENT 的“坑” --重复值问题
  5. asp.net 中ashx、axd的区别
  6. 无监督学习 k-means_无监督学习-第3部分
  7. 就是想让你无法无动于衷:观瑞士的“行为艺术”
  8. 更新fielddata为true_线程与更新UI,细谈原理
  9. java语言_JAVA语言
  10. externalreferences 命令在 sdi 模式下不可用_一个适合新手交互式Git命令学习项目