文档型数据库,nodejs的好基友。
下载地址:https://www.mongodb.com/download-center#community
进入到下载地址找到对应的版本:
解压后改个自己喜欢的名字:
拷贝文件夹,点击前往文件夹:
在弹出的输入框中输入:/usr/local回车,如下:
将mongodb复制与此文件夹中。
打开终端,输入 open-e.bash_profile
(如果没有的话,先 cd~
然后 touch.bash_profile
创建后打开)
在打开的文件中加入 export
PATH=${PATH}:/usr/local/mongodb/bin
用Command+S保存配置,关闭上面的.bash_profile编辑窗口。
然后在下图中输入
source .bash_profile
使配 置生效。输入 mongod -version ,回车后如果看到下面的版本号则说明mongod已经成功安装到了Mac上。
Tip:MongoDB常用命令:
mogond--dbpath $dbpath.
mongod--logpath $logpath
mongod—logappend
mongod—fork
首先进入Mongodb安装目录,创建data和log目录
执行 mongod--dbpath data--logpath log/mongod.log--logappend—fork
命令
执行 mongo
,当看到下面的MongoDB shell version v4.0.5 则说明mongodb已经成功启动
https://studio3t.com/download-now/
下载后无脑安装。
新建connect,localhost:27017即可。
// helloworld.js // 查询所有数db据库
show dbs
// 切换/创建数据库,当创建一个集合(table)的时候会自动创建当前数据库
use test
// 对fruits表插入一条数据
db.fruits.save({name:'苹果',price:5})
// 条件查询
db.fruits.find({price:5})
// 得到当前db的所有数据表集合
db.getCollectionNames()
// 查询
db.fruits.find()
可见mongo的语法非常适合前端。
需要安装node原生依赖:https://github.com/mongodb/node-mongodb-native
npm i mongodb -S
新建一个 hello.js
setTimeout(async ()=>{
const {MongoClient:MongoDB}=require('mongodb');
const client= new MongoDB(
'mongodb://localhost:27017',
{
useNewUrlParser:true
}
);
//连接到test数据库
await client.connect();
let db=client.db('test');
// 获取集合fruit文档
const fruitsTable = db.collection("fruits");
let r = await fruitsTable.insertOne({ name: "芒果", price: 20.0 });
console.log("插入成功", r.result);
// 查询全部
let fruits=await fruitsTable.findOne();
console.log('fruits',fruits)
// 更新文档使用`$set`操作符
fruits = await fruitsTable.updateOne(
{ name: "芒果" }, { $set: { name:'广药的芒果' } }
);
console.log("更新成功", r.result);
// 7.删除文档
fruits = await fruitsTable.deleteOne({name:"苹果"});
console.log('删除成功', r.result);
})
打印结果:
菜鸟文档
http://www.runoob.com/mongodb/mongodb-create-database.html
官网
https://docs.mongodb.com/manual/reference/method/
本项目由express开发。目标是是开发一个列表和查询的页面
新建models文件夹,抽取配置:
// config.js
module.exports = {
url: "mongodb://localhost:27017",
dbName: 'test',
}
新建db.js,封装数据库连接:
const conf = require("./config");
const EventEmitter = require("events").EventEmitter;
// 客户端
const MongoClient = require("mongodb").MongoClient;
class Mongodb {
constructor(conf) {
// 保存conf
this.conf=conf;
this.emmiter = new EventEmitter();
// 连接
this.client = new MongoClient(
conf.url,
{ useNewUrlParser: true });
this.client.connect(err => {
if (err) throw err; console.log("连接成功");
this.emmiter.emit("connect");
});
}
col(colName, dbName = conf.dbName) {
return this.client.db(dbName).collection(colName);
}
once(event, cb) {
this.emmiter.once(event, cb);
}
}
// 导出db
module.exports = new Mongodb(conf);
新建事件分发器(eventEmmiter.js):
// eventEmmiter.js
const EventEmitter = require('events').EventEmitter;
const event = new EventEmitter();
event.on('some_event', num => {
console.log('some_event 事件触发:' + num);
});
let num = 0
setInterval(() => {
event.emit('some_event', num++);
}, 1000);
在根目录下创建测试数据:initData.js
const mongodb = require('./models/db')
mongodb.once('connect', async () => {
const col = mongodb.col('fruits')
// 删除已存在
await col.deleteMany()
const data = new Array(100).fill().map((v, i) => {
return {
name: "XXX" + i, price: i,
category: Math.random() > 0.5 ? '蔬菜' : '水果'
}
})
// 插入
await col.insertMany(data);
console.log("插入测试数据成功");
})
前端就用vue-cli+element-ui+axios来实现:
vue create my-app
cd my-app
vue add element
请求包括2个接口:
<template>
<div id="app">
<el-input
placeholder="请输入内容"
v-model="search"
class="input-with-select"
@change="changeHandler">
<el-select v-model="category" @change="getData" slot="prepend" placeholder="请选择">
<el-option v-for="v in categorys" :label="v" :key="v" :value="v"></el-option>
</el-select>
<el-button slot="append" icon="el-icon-search"></el-button>
</el-input>
<el-table :data="fruits" style="width: 100%">
<el-table-column prop="name" label="名称" width="180"></el-table-column>
<el-table-column prop="price" label="价格" width="180"></el-table-column>
<el-table-column prop="category" label="种类"></el-table-column>
</el-table>
<el-pagination layout="prev, pager, next" @current-change="currentChange" :total="total"></el-pagination>
</div>
</template>
<script>
import axios from "axios";
// axios.defaults.baseURL = "http://localhost:3000";
export default {
name: "app",
components: {},
data: function() {
return {
page: 1,
total: 0,
fruits: [],
categorys: [],
category: '',
search: "",
};
},
created: function() {
this.getData();
this.getCategory();
},
methods: {
currentChange: async function(page) {
this.page = page;
await this.getData();
},
changeHandler: async function(val) {
console.log("search...", val);
this.search = val;
await this.getData();
},
getData: async function() {
const res = await axios.get(
`/api/list?page=${this.page}&category=${this.category}&keyword=${this.search}`
);
const data = res.data.data;
this.fruits = data.fruits;
this.total = data.pagination.total;
},
getCategory: async function() {
const res = await axios.get(`/api/category`);
this.categorys = res.data.data;
console.log("category", this.categorys);
}
}
};
</script>
<style>
#app {
font-family: "Avenir", Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
.input-with-select .el-input-group__prepend {
background-color: #fff;
}
</style>
直接在index文件写接口:
// index.js
const express = require('express');
const app = express()
const path = require("path")
const mongo = require("./models/db")
const testdata = require("./initData")
app.get("/api/list", async (req, res) => { // 分页查询
const { page, category, keyword } = req.query
const col = mongo.col("fruits")
// 构造条件对象
const condition = {}
if (category) {
condition.category = category
}
if (keyword) {
condition.name = { $regex: new RegExp(keyword) }
}
// 查询
const total = await col.find(condition).count()
const fruits = await col
.find(condition) // 条件
.skip((page - 1) * 5)
.limit(5)
.toArray()
res.json({ ok: 1, data: { fruits, pagination: { total, page } } })
})
app.get("/api/category", async (req, res) => {
const col = mongo.col("fruits");
const data = await col.distinct('category');
res.json({ ok: 1, data });
})
app.listen(3000)
实现效果如下
那么功能就实现了。
在条件查询中用到了模拟操作符操作符 $regex
。以下是更详细的说明。
https://docs.mongodb.com/manual/reference/operator/query/
// 比较 $eq,$gt,$gte,$in等
await col.find({price:{$gt:10}}).toArray()
// 逻辑 $and,$not,$nor,$or
// price>10 或 price<5
await col.find({$or: [{price:{$gt:10}},{price:{$lt:5}}]}) // price不大于10且price不小于5
await col.find({$nor: [{price:{$gt:10}},{price:{$lt:5}}]})
// 元素$exists,$type
await col.insertOne({ name: "芒果", price: 20.0, stack:true })
await col.find({stack:{$exists:true}})
// 模拟$regex,$text,$expr
await col.find({name:{$regex:/芒/}})
await col.createIndex({name:'text'}) // 验证文本搜索需首先对字段加索引
await col.find({$text:{$search:'芒果'}}) // 按词搜索,单独字查询不出结果
// 数组$all,$elemMatch,$size
col.insertOne({..., tags: ["热带", "甜"]}) // 插入带标签数据
// $all:查询指定字段包含所有指定内容的文档
await col.find({ tags: {$all:['热带','甜'] } }
// $elemMatch: 指定字段数组中至少有一个元素满足所有查询规则
col.insertOne({hisPrice: [20,25,30]}); // 数据准备
col.find({ hisPrice: { $elemMatch: { $gt: 24,$lt:26 } } }) // 历史价位有没有出现在 24~26之间
// 地理空间$geoIntersects,$geoWithin,$near,$nearSphere
// 创建stations集合
const stations = db.collection("stations");
// 添加测试数据,执行一次即可
await stations.insertMany([
{ name: "天安门东", loc: [116.407851, 39.91408] }, { name: "天安门西", loc: [116.398056, 39.913723] }, { name: "王府井", loc: [116.417809, 39.91435] }
]);
await stations.createIndex({ loc: "2dsphere" });
r = await stations.find({
loc: {
$nearSphere: {
$geometry: {
type: "Point",
coordinates: [116.403847, 39.915526]
},
$maxDistance: 1000
}
}
}).toArray();
console.log("天安门附近地铁站", r);
// 字段相关:$set,$unset,$setOnInsert,$rename,$inc,$min,$max,$mul // 更新多个字段
await fruitsColl.updateOne(
{ name: "芒果" },
{ $set: { price: 19.8, category: '热带水果' } },
);
// 更新内嵌字段
{ $set: { ..., area: {city: '三亚'} } }
// 数组相关:$,$[],$addToSet,$pull,$pop,$push,$pullAll
// $push用于新增
insertOne({tags: ['热带','甜']}) //添加tags数组字段
fruitsColl.updateMany({ name: "芒果" }, { $push: {tags: '上火'}}) // $pull,$pullAll用于删除符合条件项,$pop删除首项-1或尾项1
fruitsColl.updateMany({ name: "芒果" }, { $pop: {tags: 1}})
fruitsColl.updateMany({ name: "芒果" }, { $pop: {tags: 1}})
// $,$[]用于修改
fruitsColl.updateMany({ name: "芒果", tags: "甜" }, { $set: {"tags.$": "香甜"} })
// 聚合管道阶段:$group,$count,$sort,$skip,$limit,$project等
// 分页查询
r = await fruitsColl
.aggregate([{ $sort: { price: -1 } }, { $skip: 0 }, { $limit: 2 }])
.toArray();
// 投射:只选择name,price并排除_id
fruitsColl.aggregate([..., {$project:{name:1,price:1,_id:0}}]).toArray();
// 聚合管道操作符:$add,$avg,$sum等
// 按name字段分组,统计组内price总和
fruitsColl.aggregate([{ $group:{_id:"$name",total: {$sum:"$price"}}}]).toArray();
对于mongo,有时候也想直接在程序中定义模型。也需要一个odm工具来方便更好的操作。而mongoose就是i 一个良好的工具。
mongoose是一个优雅的nodejs对象文档模型。它是由关系型数据库的思想去应用到非关系型数据库。
文档地址:https://mongoosejs.com/docs/guide.html
安装:
npm install mongoose -S
const mongoose = require("mongoose");
// 1.连接
mongoose.connect("mongodb://localhost:27017/test", { useNewUrlParser: true });
const conn = mongoose.connection;
conn.on("error", () => console.error("连接数据库失败"));
conn.once("open", async () => {
// 2.定义一个Schema - Table
const Schema = mongoose.Schema({
category: String,
name: String
});
// 3.编译一个Model, 它对应数据库中复数、小写的Collection
const Model = mongoose.model("fruit", Schema);
try {
// 4.创建,create返回Promise
let r = await Model.create({
category: "温带水果", name: "苹果",
price: 5
});
console.log("插入数据:", r);
// 5.查询,find返回Query,它实现了then和catch,可以当Promise使用
// 如果需要返回Promise,调用其exec()
r = await Model.find({ name: "苹果" });
console.log("查询结果:", r);
// 6.更新,updateOne返回Query
r = await Model.updateOne({ name: "苹果" }, { $set: { name: '芒果' } });
// 7.删除,deleteOne返回Query
r = await Model.deleteOne({ name: "苹果" });
} catch (error) {
console.log(error);
}
});
比如说一个写一个新的博客:
const mongoose = require("mongoose");
// 1.连接
mongoose.connect("mongodb://localhost:27017/test", { useNewUrlParser: true });
const conn = mongoose.connection;
conn.on("error", () => console.error("连接数据库失败"));
conn.once("open", async () => {
const blogSchema = mongoose.Schema({
title: { type: String, required: [true, '标题为必填项'] }, // 定义校验规则
author: String,
body: String,
comments: [{ body: String, date: Date }], // 定义对象数组
date: { type: Date, default: Date.now }, // 指定默认值
hidden: Boolean,
meta: {
// 定义对象
votes: Number,
favs: Number
}
});
// 定义多个索引
blogSchema.index({ title: 1, author: 1, date: -1 });
const BlogModel = mongoose.model("blog", blogSchema);
const blog = new BlogModel({
title: "nodejs持久化", author: "dangjingtao", body: "...."
});
const r = await blog.save(); console.log("新增blog", r);
});
如果我想做工程化,需要定义一些常规的操作,比如说:
// 定义实例方法
blogSchema.methods.findByAuthor = function () {
// this.author就是下面blog new出来的实例。
return this.model('blog').find({ author: this.author }).exec();
}
// 获得模型实例
const BlogModel = mongoose.model("blog", blogSchema);
const blog = new BlogModel({...});
// 调用实例方法
r = await blog.findByAuthor();
console.log('findByAuthor', r);
你还可以把这个方法作为静态属性直接绑定到 blogSchema
上:
// 定义静态方法
const BlogModel = mongoose.model("blog", blogSchema);
blogSchema.statics.findByAuthor = function(author) {
return this.model("blog")
.find({ author })
.exec();
};
r=await BlogModel.findByAuthor('dangjingtao')
console.log("findByAuthor", r);
使用更加优雅。
如果我要实现某篇文章的评论统计的功能。
blogSchema.virtual("commentsCount").get(function() {
return this.comments.length;
});
r = await BlogModel.findOne({author:'dangjingtao'});
console.log("blog留言数:", r.commentsCount);
前端定义了一个数据模型。能否快速的实现好一套restful的接口?(也就是说开发者者只管定义模型,接口都不用写了。)
这个需求可以用 keystoneJS
(网址:https://keystonejs.com/)来实现.
Keystone是以Express和MongoDB和mongoose为基础搭建的开源的Node.js CMS和web应用程序平台。
Keystone在官网上声称:在Node.js中,用Keystone搭建数据驱动的网站、应用程序和API是最容易的。
之所以出此狂言,背后还是有料的,Keystone自带以下功能:
keystone最牛逼的地方就是根据你定义的模型自动帮你实现后台管理界面,创建、管理、编辑和删除等,这得省掉很多功夫了。这样实现一个网站只要定义model和写前端代码就好了。
sudo npm install -g generator-keystone
mkdir my-test-project
cd my-test-project
sudo npm install -g yo (安装Yeoman(脚手架是用Yeoman制作的))
sudo yo keystone
node keystone启动
打开http://localhost:3000 在浏览器查看 通过http://localhost:3000/keystone 打开后台管理
var keystone = require('keystone');
var Types = keystone.Field.Types;
var Test = new keystone.List('Test');
Test.add({
name: { type: Types.Name, required: true, index: true },
date:{type:Types.Date},
text:{type:Types.Text},
markdown:{type:Types.Markdown},
color:{type:Types.Color}
});
Test.register();
在modal层下是你定义的数据类型
下面就以Test为例:了解数据模型映射的对应地址
列表:http://localhost:3000/keystone/api/tests?fields=name&limit=100&sort=name&expandRelationshipFields=true