前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >持久化储存(二)

持久化储存(二)

作者头像
一粒小麦
发布2019-07-23 10:40:39
13.3K0
发布2019-07-23 10:40:39
举报
文章被收录于专栏:一Li小麦

mongodb

文档型数据库,nodejs的好基友。

下载地址:https://www.mongodb.com/download-center#community

Mac安装:
下载

进入到下载地址找到对应的版本:

解压后改个自己喜欢的名字:

拷贝文件夹,点击前往文件夹:

在弹出的输入框中输入:/usr/local回车,如下:

将mongodb复制与此文件夹中

配置环境变量

打开终端,输入 open-e.bash_profile(如果没有的话,先 cd~然后 touch.bash_profile创建后打开)

在打开的文件中加入 export

代码语言:javascript
复制
PATH=${PATH}:/usr/local/mongodb/bin

用Command+S保存配置,关闭上面的.bash_profile编辑窗口。

然后在下图中输入

代码语言:javascript
复制
source .bash_profile

使配 置生效。输入 mongod -version ,回车后如果看到下面的版本号则说明mongod已经成功安装到了Mac上。

Tip:MongoDB常用命令:

  • 数据库数据文件路径 mogond--dbpath $dbpath.
  • 日志文件的路径 mongod--logpath $logpath
  • 以追加的方式打开文件 mongod—logappend
  • 将数据库服务放在后台运行) 脚本启动或配置文件启动 mongod—fork
启动mongodb

首先进入Mongodb安装目录,创建data和log目录

执行 mongod--dbpath data--logpath log/mongod.log--logappend—fork 命令

执行 mongo,当看到下面的MongoDB shell version v4.0.5 则说明mongodb已经成功启动

可视化工具 Robomongo

https://studio3t.com/download-now/

下载后无脑安装。

新建connect,localhost:27017即可。

命令行操作

基本测试指令
代码语言:javascript
复制
// 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

代码语言:javascript
复制
npm i mongodb -S

新建一个 hello.js

代码语言:javascript
复制
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开发。目标是是开发一个列表和查询的页面

model层
配置

新建models文件夹,抽取配置:

代码语言:javascript
复制
// config.js
module.exports = {
    url: "mongodb://localhost:27017",
    dbName: 'test',
}
数据库连接

新建db.js,封装数据库连接:

代码语言:javascript
复制
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):

代码语言:javascript
复制
// 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

代码语言:javascript
复制
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来实现:

代码语言:javascript
复制
vue create my-app
cd my-app
vue add element

请求包括2个接口:

  • 获取分类目录(/catergory)
  • 查询列表(/list)
代码语言:javascript
复制
<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文件写接口:

代码语言:javascript
复制
// 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/

查询操作符:提供多种方式定位数据库数据
代码语言:javascript
复制
// 比较 $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);
更新操作符:可以修改数据库数据或添加附加数据
代码语言:javascript
复制
// 字段相关:$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.$": "香甜"} })
聚合操作符:使用aggregate方法,使文档顺序通过管道阶段从而得到最终结果
代码语言:javascript
复制
// 聚合管道阶段:$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();

ODM - Mongoose

对于mongo,有时候也想直接在程序中定义模型。也需要一个odm工具来方便更好的操作。而mongoose就是i 一个良好的工具。

mongoose是一个优雅的nodejs对象文档模型。它是由关系型数据库的思想去应用到非关系型数据库。

文档地址:https://mongoosejs.com/docs/guide.html

安装:

代码语言:javascript
复制
npm install mongoose -S
增删改查使用实例
代码语言:javascript
复制
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);
    }
});
schema操作

比如说一个写一个新的博客:

代码语言:javascript
复制
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);
});
封装

如果我想做工程化,需要定义一些常规的操作,比如说:

  • 根据作者查找(findByAuthor)
  • 获取数据表(BlogModel)
代码语言:javascript
复制
// 定义实例方法
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上:

代码语言:javascript
复制
// 定义静态方法
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);

使用更加优雅。

如果我要实现某篇文章的评论统计的功能。

代码语言:javascript
复制
blogSchema.virtual("commentsCount").get(function() {
  return this.comments.length;
});
r = await BlogModel.findOne({author:'dangjingtao'}); 
console.log("blog留言数:", r.commentsCount);

快速开发工具:keystone

前端定义了一个数据模型。能否快速的实现好一套restful的接口?(也就是说开发者者只管定义模型,接口都不用写了。)

这个需求可以用 keystoneJS(网址:https://keystonejs.com/)来实现.

Keystone是以Express和MongoDB和mongoose为基础搭建的开源的Node.js CMS和web应用程序平台。

Keystone在官网上声称:在Node.js中,用Keystone搭建数据驱动的网站、应用程序和API是最容易的。

之所以出此狂言,背后还是有料的,Keystone自带以下功能:

  1. 内置Express.js和MongoDB
  2. 动态路由
  3. 实用的数据库域类型
  4. 自动生成管理员界面
  5. 基于数据模型的表单处理
  6. 会话管理和认证功能

keystone最牛逼的地方就是根据你定义的模型自动帮你实现后台管理界面,创建、管理、编辑和删除等,这得省掉很多功夫了。这样实现一个网站只要定义model和写前端代码就好了。

代码语言:javascript
复制
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 打开后台管理

代码语言:javascript
复制
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

  • 新增:http://localhost:3000/keystone/api/tests/create
  • 修改:http://localhost:3000/keystone/api/tests/5d33ff20abbd898523282108
  • 删除:http://localhost:3000/keystone/api/tests/delete
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2019-07-21,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 一Li小麦 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • mongodb
    • Mac安装:
      • 下载
      • 配置环境变量
      • 启动mongodb
  • 可视化工具 Robomongo
  • 命令行操作
    • 基本测试指令
      • 基础使用
        • 基础使用
        • 更多文档
    • 实践案例:芒果商城
      • model层
        • 配置
        • 数据库连接
        • 事件分发器
      • 测试数据
        • 前端页面
          • 后端接口
          • 操作符
            • 查询操作符:提供多种方式定位数据库数据
              • 更新操作符:可以修改数据库数据或添加附加数据
                • 聚合操作符:使用aggregate方法,使文档顺序通过管道阶段从而得到最终结果
                • ODM - Mongoose
                  • 增删改查使用实例
                    • schema操作
                      • 封装
                      • 快速开发工具:keystone
                      相关产品与服务
                      云数据库 MongoDB
                      腾讯云数据库 MongoDB(TencentDB for MongoDB)是腾讯云基于全球广受欢迎的 MongoDB 打造的高性能 NoSQL 数据库,100%完全兼容 MongoDB 协议,支持跨文档事务,提供稳定丰富的监控管理,弹性可扩展、自动容灾,适用于文档型数据库场景,您无需自建灾备体系及控制管理系统。
                      领券
                      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档