Web全栈架构师(三)——NodeJS+持久化学习笔记(2)

持久化

nodejs中实现持久化的方法

  • 文件系统fs
  • 数据库:
    关系型数据库——MySQL
    文档型数据库——MongoDB
    键值对数据库——Redis

文件系统数据库

  • 自己简单实现一个文件系统读写数据库,代码如下:
// 实现一个文件系统读写数据库
const fs = require('fs')

function get(key) {
  fs.readFile('./db.json', (err, data) => {
    const json = JSON.parse(data)
    console.log(json[key]);
  })
}
function set(key, value){
  fs.readFile('./db.json', (err, data) => {
    // 可能是空文件,则设置为对象
    const json = data ? JSON.parse(data) : {}
    json[key] = value // 设置值
    // 重新写入文件
    fs.writeFile('./db.json', JSON.stringify(json), err => {
      if(err){
        console.log(err)
      }
      console.log('Operation Successfully!')
    })
  })
}

// 命令行接口部分
const readline = require('readline')
const rl = readline.createInterface({
  input: process.stdin,
  output: process.stdout
})
rl.on('line', function(input) {
  const [op, key, value] = input.split(' ')
  if(op === 'get'){
    get(key)
  }else if(op === 'set'){
    set(key, value)
  }else if(op === 'quit'){
    rl.close()
  }else{
    console.log('There is no such operation.')
  }
})

rl.on('close', function() {
  console.log('Program end')
  process.exit(0)
})

在这里插入图片描述

MySQL

资源

安装配置

node.js原生驱动

  • 安装 mysql 模块:npm i mysql --save
  • mysql 模块基本使用
const mysql = require('mysql')
// 连接配置
const cfg = {
  host: 'localhost',
  user: 'root',
  password: 'yw@910714',
  database: 'yw_test_db'
}
// 创建连接对象
const conn = mysql.createConnection(cfg)
// 连接数据库
conn.connect(err => {
  if(err){
    throw err
  }
  console.log('Connection Successfully!')
})

// sql语句
const CREATE_TABLE = `
  create table if not exists test (
    id int not null auto_increment,
    message varchar(45) null,
    primary key(id)
  )
`
const INSERT_SQL = `insert into test(message) values(?)`
const SELECT_SQL = `select * from test`

// 查询 conn.query()
conn.query(CREATE_TABLE, err => {
  if(err){
    throw err
  }
  // 插入数据
  conn.query(INSERT_SQL, 'hello, mysql', (err, result) => {
    if(err){
      throw err
    }
    console.log(result)
    // 查询数据
    conn.query(SELECT_SQL, (err, results) => {
      console.log(results)
      conn.end()  // 若query语句有嵌套,则end需要在此执行
    })
  })
})

在这里插入图片描述

  • query的Promise封装
function query(conn, sql, params=null) {
  return new Promise((resolve, reject) => {
    conn.query(sql, params, (err, results) => {
      if(err) {
        reject(err)
      }else{
        resolve(results)
      }
    })
  })
}
// 测试
conn.query(CREATE_TABLE, err => {
  if(err){
    throw err
  }
  query(conn, SELECT_SQL)
    .then(results => console.log(results))
    .catch(err => console.log(err))
})

Sequelize

  • 概述:基于 Promise 的 ORM,支持多种数据库、事务、关联等,类似于 Java 中的Hibernate
  • 安装:npm i sequelize mysql2 -S
基本使用:
const Sequelize = require('sequelize')

// 建立连接
const sequelize = new Sequelize('yw_test_db', 'root', 'yw@910714', {
  host: 'localhost',
  dialect: 'mysql', // 方言,mysql\postgre\sql server\sql lite ...
  operatorsAliases: false
})

// 定义模型 Model - Table
const Fruit = sequelize.define('Fruit', {
  name: {type: Sequelize.STRING(20), allowNull: false},
  price: {type: Sequelize.FLOAT, allowNull: false},
  stock: {type: Sequelize.INTEGER, defaultValue: 0}
})

// 同步数据库,force:true则会删除已存在表
Fruit.sync().then(() => {
  // 添加测试数据
  return Fruit.create({name: '香蕉', price: 3.5})
}).then(() => {
  // 查询
  Fruit.findAll().then(fruits => {
    console.log(JSON.stringify(fruits));    
  })
})
  • 执行后输入:
(node:23896) [SEQUELIZE0004] DeprecationWarning: A boolean value was passed to options.operatorsAliases. This is a no-op with v5 and should be removed.
Executing (default): CREATE TABLE IF NOT EXISTS `Fruits` (`id` INTEGER NOT NULL auto_increment , `name` VARCHAR(20) NOT NULL, `price` FLOAT NOT NULL, `stock` INTEGER DEFAULT 0, `createdAt` DATETIME NOT NULL, `updatedAt` DATETIME NOT NULL, PRIMARY KEY (`id`)) ENGINE=InnoDB;
Executing (default): SHOW INDEX FROM `Fruits`
Executing (default): INSERT INTO `Fruits` (`id`,`name`,`price`,`stock`,`createdAt`,`updatedAt`) VALUES (DEFAULT,?,?,?,?,?);
Executing (default): SELECT `id`, `name`, `price`, `stock`, `createdAt`, `updatedAt` FROM `Fruits` AS `Fruit`;
[{"id":1,"name":"香蕉","price":3.5,"stock":0,"createdAt":"2019-04-05T14:43:20.000Z","updatedAt":"2019-04-05T14:43:20.000Z"}]

在这里插入图片描述

  • 强制同步:创建表之前先删除已存在的表
Fruit.sync({force: true})
  • 避免自动生成时间戳字段
const Fruit = sequelize.define('Fruit', {...}, {
	timestamps: false
})
  • 指定表名:freezeTableName: truetableName: 'xxx'

设置前者则以 modelName 作为表名;设置后者则按其值作为表名

Getters & Setters

Getters & Setters 可用于定义伪属性或映射待数据库字段的保护属性

// 定义属性的一部分
name: {
  type: Sequelize.STRING(20), 
  allowNull: false,
  get(){
    const fname = this.getDataValue('name')
    const price = this.getDataValue('price')
    const stock = this.getDataValue('stock')
    return `${fname}(价格:¥${price} 库存:${stock}kg)`
  }
}
{
  // 定义模型选项
  ...
  getterMethods: {
    amount(){
      return this.getDataValue('stock') + 'kg'
    }
  },
  setterMethods: {
    amount(val){
      const idx = val.indexOf('kg')
      const v = val.slice(0, idx)
      this.setDataValue('stock', v)
    }
  }
}
// 通过模型实例触发setterMethods
Fruit.findAll().then(fruits => {
  console.log(JSON.stringify(fruits));    
  // 修改 amount,触发 setterMethods
  fruits[0].amount = '150kg'
  fruits[0].save()
})
校验
  • 资料:http://docs.sequelizejs.com/manual/models-definition.html#validations
  • 可以通过校验功能验证模型字段格式、内容,校验会在createupdatesave 时自动运行
price: {
  type: Sequelize.FLOAT,
  allowNull: false,
  validate: {
    isFloat: {msg: '价格字段请输入数字'},
    min: {args: [0], msg: '价格字段必须大于0'}
  }
},
stock: {
  type: Sequelize.INTEGER, 
  defaultValue: 0,
  validate: {
    isNumeric: {msg: '库存字段请输入数字'}
  }
}
模型扩展
  • 可以添加模型实例方法或类方法扩展实例
// 添加类级别方法
Fruit.classify = function(name) {
  const tropicFruits = ['香蕉', '芒果', '椰子'] // 热带水果
  return tropicFruits.includes(name) ? '热带水果' : '其他水果'
}
// 添加实例级别方法
Fruit.prototype.totalPrice = function(count){
  return (this.price * count).toFixed(2)
}
  • 使用
// 使用类方法
const fruitArr = ['香蕉', '草莓']
fruitArr.forEach(f => console.log(f+'是'+Fruit.classify(f)))
// 使用实例方法
Fruit.findAll().then(fruits => {
  console.log(JSON.stringify(fruits));    
  // 修改 amount,触发 setterMethods
  fruits[0].amount = '150kg'
  fruits[0].save()
  // 使用实例方法
  console.log(`买5kg${fruits[0].name}需要¥${fruits[0].totalPrice(5)}`);
})
  • 结果输出:
香蕉是热带水果
草莓是其他水果
(node:24608) [SEQUELIZE0004] DeprecationWarning: A boolean value was passed to options.operatorsAliases. This is a no-op with v5 and should be removed.
Executing (default): DROP TABLE IF EXISTS `Fruits`;
Executing (default): CREATE TABLE IF NOT EXISTS `Fruits` (`id` INTEGER NOT NULL auto_increment , `name` VARCHAR(20) NOT NULL, `price` FLOAT NOT NULL, `stock` INTEGER DEFAULT 0, PRIMARY KEY (`id`)) ENGINE=InnoDB;
Executing (default): SHOW INDEX FROM `Fruits`
Executing (default): INSERT INTO `Fruits` (`id`,`name`,`price`,`stock`) VALUES (DEFAULT,?,?,?);
Executing (default): SELECT `id`, `name`, `price`, `stock` FROM `Fruits` AS `Fruit`;
[{"name":"香蕉(价格:¥3.5 库存:0kg)","id":1,"price":3.5,"stock":0}]
买5kg香蕉(价格:¥3.5 库存:150kg)需要¥17.50
Executing (default): UPDATE `Fruits` SET `stock`=? WHERE `id` = ?
数据查询
  • 通过id查询(API不可用??)
Fruit.findById(1).then(fruit => {
  // fruit是一个Fruit实例,若没有则为null
  console.log('findById', fruit.get());  
})
  • 条件查询 where
Fruit.findOne({where: {name: '香蕉'}}).then(fruit => {
  // fruit是首个配项,若没有则为null
  console.log('条件查询findOne', fruit.get());
})

在这里插入图片描述

  • 获取数据和总条数
Fruit.findAndCountAll().then(result => {
  console.log(result.count);
  console.log(result.rows.length)
})
  • 查询操作符 Sequelize.Op
const op = Sequelize.Op
Fruit.findAll({
  where: {price: {[op.lt]:6, [op.gte]:2}}
}).then(fruits => {
  console.log('查询操作符\n', JSON.stringify(fruits, null, 2))
})
// Executing (default): SELECT `id`, `name`, `price`, `stock` FROM `Fruits` AS `Fruit` WHERE (`Fruit`.`price` < '6' AND `Fruit`.`price` >= '2');

在这里插入图片描述

  • 或语句
Fruit.findAll({
  // where: {[op.or]:[{price: {[op.lt]:4}}, {stock: {[op.gte]: 100}}]}
  where: {price: {[op.or]: [{[op.gt]: 8}, {[op.lt]: 5}]}}
}).then(fruits => {
  console.log('或语句\n', JSON.stringify(fruits, null, 2))
})
// Executing (default): SELECT `id`, `name`, `price`, `stock` FROM `Fruits` AS `Fruit` WHERE (`Fruit`.`price` > '8' OR `Fruit`.`price` < '5');

在这里插入图片描述

  • 分页
Fruit.findAll({offset: 0, limit: 2}).then(fruits => {
  console.log('分页\n', JSON.stringify(fruits, null, 2))
})
// Executing (default): SELECT `id`, `name`, `price`, `stock` FROM `Fruits` AS `Fruit` LIMIT 0, 2;

在这里插入图片描述

  • 排序
Fruit.findAll({order: [['price', 'DESC']]}).then(fruits => {
  console.log('排序\n', JSON.stringify(fruits, null, 2))
})
// Executing (default): SELECT `id`, `name`, `price`, `stock` FROM `Fruits` AS `Fruit` ORDER BY `Fruit`.`price` DESC;

在这里插入图片描述

  • 聚合函数
setTimeout(() => {
  Fruit.max('price').then(max => {
    console.log('聚合函数 max', max)
  })
  Fruit.sum('price').then(sum => {
    console.log('聚合函数 sum', sum)
  })
}, 500)
// Executing (default): SELECT max(`price`) AS `max` FROM `Fruits` AS `Fruit`;
// Executing (default): SELECT sum(`price`) AS `sum` FROM `Fruits` AS `Fruit`;

在这里插入图片描述

更新
  • 方式1
Fruit.findById(1).then(fruit => {
	fruit.price = 4
	fruit.save().then(() => {
		console.log('update !!!')
	})
})
  • 方式2
Fruit.update({price: 6}, {where: {name: '香蕉'}}).then(result => {
  console.log('更新 update', result)
})
// UPDATE `Fruits` SET `price`=? WHERE `name` = ?
删除
  • 方式1
Fruit.findOne({where: {id: 1}}).then(result => {
	result.destroy()
})
  • 方式2
Fruit.destroy({where: {id: 2}}).then(result => {
  console.log('删除 destroy', result)
})
// DELETE FROM `Fruits` WHERE `id` = 2
关联
  • 一对多、多对一
const Player = sequelize.define('player', {
  name: Sequelize.STRING
})
const Team = sequelize.define('team', {
  name: Sequelize.STRING
})
// 建立关系
Player.belongsTo(Team)  // 这里会添加 teamId 到 player 表作为外键
Team.hasMany(Player)
// 同步
sequelize.sync({force: true}).then(async () => {
  await Team.create({name: '火箭'})
  await Player.bulkCreate([{name: '哈登', teamId: 1}, {name: '保罗', teamId: 1}])
  
  // 一对多查询
  const team = await Team.findOne({where: {name: '火箭'}, includes: [Player]})
  console.log('关联查询 一对多', JSON.stringify(team, null, 2))
  // 多对一查询
  const players = await Player.findAll({includes: [Team]})
  console.log('关联查询 多对一', JSON.stringify(players, null, 2))
})
  • 多对多
const Fruit = sequelize.define('fruit', {name: Sequelize.STRING})
const Category = sequelize.define('category', {name: Sequelize.STRING})
Fruit.FruitCategory = Fruit.belongsToMany(Category, {
  through: 'FruitCategory'
})
sequelize.sync({force: true}).then(async () => {
  // 插入测试数据
  await Fruit.create(
    {name: '香蕉', categories: [{id: 1, name: '热带'}, {id: 2, name: '温带'}]},
    {include: [Fruit.FruitCategory]}
  )
  // 多对多联合查询
  const fruit = await Fruit.findOne({
    where: {name: '香蕉'},  
    // 通过 through 指定条件、字段等
    include: [{model: Category, through: {attributes: ['id', 'name']}}]
  })
  console.log('多对多关联查询\n', JSON.stringify(fruit, null, 2))
})
事务

待补充

MongoDB

资源

  • MongoDB:下载
  • node驱动:文档
  • mongoose:文档
  • 可视化工具:https://robomongo.org/

安装配置

  • 安装教程:http://www.runoob.com/mongodb/mongodb-window-install.html
  • 创建 dbpath 文件夹
  • 启动:mongod --dbpath=/data
  • 测试
// 查询所有数据库
show dbs
// 切换、创建数据库,当创建一个集合的时候会自动创建当前数据库
use test
// 得到当前db的所有聚集集合
db.getCollectionNames()
// 查询 
db.fruits.find()
// 插入一条数据
db.fruits.save({name: '苹果', price: 5})
// 条件查询
db.fruits.find({price: 5})
db.fruits.find({price: {$lte: 10}})
  • MongoDB命令行操作:https://www.cnblogs.com/wywnet/p/5102946.html

MongoDB原生驱动

  • 安装 mongodb 模块:npm install mongodb --save
基本使用
  • 连接MongoDB
const MongoClient = require('mongodb').MongoClient
// 连接URL
const url = 'mongodb://127.0.0.1:27017'
// 数据库名
const dbName = 'kaikeba';
(async function(){
  // 0. 创建客户端
  const client = new MongoClient(url, {useNewUrlParser: true})
  try {
    // 1. 连接数据库(异步)
    await client.connect()
    console.log('Connected Successfully!')
    
    // 2. 获取数据库
    const db = client.db(dbName)

    // 3. 获取集合
    const fruitColl = db.collection('fruits')
    
    // 4. 插入文档
    let r;
    r = await fruitColl.insertOne({name: '芒果', price: 20.0})
    console.log('插入成功', r.result)

    // 5. 查询文档
    r = await fruitColl.findOne()
    console.log('查询结果', r)

    // 6. 更新文档
    r = await fruitColl.updateOne({name: '芒果'}, {$set: {name: '苹果'}})
    console.log('更新成功', r.result)

    // 7. 删除文档
    r = await fruitColl.deleteOne({name: '苹果'})
    console.log('删除成功', r.result)
  } catch (error) {
    console.log(error)
  }
  client.close()
})()
实战案例-瓜果超市
  • MongoDB操作封装:
const conf = require('./conf')
const MongoClient = require('mongodb').MongoClient
// 事件派发器
const EventEmittter = require('events').EventEmitter

class MongoDB {
  constructor(conf){
    // 保存conf
    this.conf = conf
    this.emitter = new EventEmittter()
    // 连接
    this.client = new MongoClient(conf.url, {useNewUrlParser: true})
    this.client.connect(err => {
      if(err){
        throw err
      }
      console.log('Connected Successfully!')
      this.emitter.emit('connect')
    })
  }
  // 监听事件方法
  once(event, cb){
    this.emitter.once(event, cb)
  }
  // 获取集合
  col(colName, dbName = this.conf.dbName){
    return this.client.db(dbName).collection(colName)
  }
}
module.exports =  new MongoDB(conf)
  • mock数据:
const mongodb = require('./db')

mongodb.once('connect', async () => {
  const col = mongodb.col('fruits')
  try {
    // 删除已存在的数据
    await col.deleteMany();
    // 插入测试数据
    await col.insertMany([
      {name: '苹果', price: 9.9, category: '水果'},
      {name: '香蕉', price: 5.9, category: '水果'},
      {name: '芒果', price: 11.9, category: '水果'},
      {name: '砂糖橘', price: 12, category: '水果'},
      {name: '土豆', price: 3.5, category: '蔬菜', stack: 100},
      {name: '西红柿', price: 2.8, category: '蔬菜', stack: 100},
      {name: '茄子', price: 2, category: '蔬菜'},
      {name: '韭菜', price: 1.5, category: '蔬菜'}
    ])
    console.log('测试数据插入成功')
  } catch (error) {
    console.log('测试数据插入失败', error)
  }
})
  • 后台Server
const mongo = require('../models/db.js')
const testdata = require('../models/test-data.js')
const express = require('express')
const path = require('path')
const app = express()

app.get('/index', (req, res) => {
  res.sendFile(path.resolve('./index.html'))
})
// 分页查询水果蔬菜数据
app.get('/api/list', async (req, res) => {
  // 分页数据
  const {page} = req.query
  // 查询
  try {
    const col = mongo.col('fruits')
    const fruits = await col.find().skip((page-1)*4).limit(4).toArray()
    // 查询总条数
    const total = await col.find().count()
    res.json({code: '0000', data: {fruits, pagination: {total, page}}})
  } catch (error) {
    console.log(error)
  }
})
app.listen(3000)
  • 前端
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">

  <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
  <script src="https://unpkg.com/element-ui/lib/index.js"></script>
  <script src="https://unpkg.com/axios/dist/axios.min.js"></script>
  <link rel="stylesheet" href="https://unpkg.com/element-ui/lib/theme-chalk/index.css"/>

  <title>瓜果超市</title>
</head>
<body>
  <div id="app">
    <!-- 列表 -->
    <ul>
      <li v-for="fruit in fruits" :key="fruit._id">
        {{fruit.name}} - {{fruit.price}}
      </li>
    </ul>
    <!-- 分页 -->
    <el-pagination layout="prev, pager, next" @current-change="currentChange" :total="total" :page-size="4">
    </el-pagination>
  </div>
  <script>
    new Vue({
      el: '#app',
      data: {
        fruits: [],
        page: 1,
        total: 0
      },
      created() {
        this.getData()
      },
      methods: {
        getData(){
          axios.get(`/api/list?page=${this.page}`).then(res => res.data).then(({data}) => {
            this.fruits = data.fruits
            this.total = data.pagination.total
          })
        },
        currentChange(page){
          this.page = page
          this.getData()
        }
      }
    })
  </script>
</body>
</html>
操作符
  • 资料:https://docs.mongodb.com/manual/reference/operator/query/
查询操作符

提供多种方式定位数据库

  • 比较:$eq、$gt、$gte、$in 【等于、大于、大于等于、包含】
// price > 10
await fruitColl.find({price: {$gt: 10}})
  • 逻辑:$and、$or、$not、$nor 【与、或、非、取反】
// price>10 or pricec<5
await fruitColl.find({$or: [{price: {$gt: 10}}, {price: {$lt: 5}}]})
// price不大于10 且 price不小于5
await fruitColl.find({$nor: [{price: {$gt: 10}}, {price: {$lt: 5}}]})
  • 元素:$exists、$type
fruitsCol.insertOne({name: '芒果', price: 20.0, stack: true})
await fruitColl.find({stack: {$exists: true}})
  • 模拟:$regex、$text、$expr 【正则、全文匹配】
await fruitColl.find({name: {$regex: /芒/}})
// 验证文本搜索需要首先对字段加索引
await fruitColl.createIndex({name: 'text'})
// 按词搜索,单独子查询不出结果
await fruitColl.find({$text: {$search: '芒果'}})
  • 数组:$all、$elemMatch、$size
// 插入带标签数据
fruitColl.insertOne(..., tags: ['热带', '甜'])
// $all:查询指定字段包含所有指定内容的文档
await fruitColl.find({tags: {$all: ['热带', '甜']}})
// $elemMatch:指定字段数组中至少有一个元素满足所有查询规则
fruitColl.insertOne({hisPrice: [20, 25, 30]})
await fruitColl.find({hisPrice: {$elemMatch: {$gt: 24, $lt: 26}}})	// 24~26
  • 地理空间:$geoIntersects、$geoWithin、$near、$nearSphere
// 创建 stations 集合
const stations = db.collection('stations')
// 添加测试数据,执行一次即可
await stations.insertMany([])
// 创建索引
await stations.createIndex({ loc: "2dsphere" });
// 查询方圆1公里以内的地铁站
r = await stations
  .find({
    loc: {
      $nearSphere: {
        $geometry: {
          type: "Point",
          coordinates: [116.403847, 39.915526]
        },
        $maxDistance: 1000
      }
    }
  }).toArray();
更新操作符

可以修改数据库数据或添加附加数据

  • 字段相关:$set、$unset、$setOnInsert、$rename、$inc、$min、$max、$mul
// 更新多个字段
await fruitColl.updateOne(
	{name: '芒果'},
	{$set: {price: 19.8, category: '热带水果'}}
)
// 更新内嵌字段
	{$set: {..., area: {city: '三亚'}}}
  • 数组相关:$、$[]、$addToSet、$pull、$pop、$push、$pullAll
// $push 用于新增
fruitColl.insertOne(tags: ['热带', '甜'])
fruitColl.updateMany({name: '芒果'}, {$push: {tags: '上火'}})
// $pull、$pullAll用于删除符合条件项,$pop删除首项-1或尾项1
fruitColl.updateMany({name: '芒果'}, {$pop: {tags: 1}})
fruitColl.updateMany({name: '芒果'}, {$pop: {tags: 1}})
// $、$[]用于修改
fruitColl.updateMany({name: '芒果', tags: '甜'}, {$set: {'tags.$': '香甜'}})
  • 修改器,常结合数组操作符使用:$each、$opsition、$slice、$sort
$push: {tags: {$each: ['上火', '真香'], $slice: -3}}
聚合操作符

使用 aggregate 方法,使文档顺序通过管道阶段从而得到最终结果

  • 聚合管道阶段:$group、$count、$sort、$skip、$limit、$project
  • 聚合管道操作符:$add、$avg、$sum
let r = await fruitColl.insertOne({ name: "芒果", price: 20.0 });
    r = await fruitColl.insertOne({ name: "百香果", price: 15 });
    // 分组,求和
    r = await fruitColl
      .aggregate([
        { $match: { name: "芒果" } },	// 匹配阶段
        { $group: { _id: "$name", total: { $sum: "$price" } } }	// 分组阶段
      ])
      .toArray();
    console.log(r);
案例使用

瓜果超市按条件查询

  • 查询分类
app.get('/api/category', async (req, res) => {
  const col = mongo.col('fruits')
  const data = await col.distinct('category')
  res.json({code: '0000', data})
})
  • 按分类查询
app.get('/api/list', async (req, res) => {
  // 分页数据
  const {page, category} = req.query
  // 查询
  try {
    const col = mongo.col('fruits')
    // 构造条件
    const condition = {}
    if(category){
      condition.category = category
    }
    const fruits = await col.find(condition).skip((page-1)*4).limit(4).toArray()
    // 查询总条数
    const total = await col.find(condition).count()
    res.json({code: '0000', data: {fruits, pagination: {total, page}}})
  } catch (error) {
    console.log(error)
  }
})
  • 搜索商品
app.get('/api/search', async (req, res) => {
  const {keyword} = req.query
  const col = mongo.col('fruits')
  const data = await col.find({
    name: {$regex: new RegExp(keyword)}
  }).toArray()
  res.json({code: '0000', data})
})

ODM-Mongoose

  • 资料:https://mongoosejs.com/docs/guide.html
  • 概述:优雅的NodeJS对象文档模型 Object Document Model,Mongoose有两个特点:
* 通过关系型数据库的思想来设计非关系型数据库
* 基于mongodb驱动,简化解析
  • 安装:npm install mongoose -S
基本使用
const mongoose = require('mongoose')

// 1. 连接
mongoose.connect('mongodb://127.0.0.1:27017/test', {useNewUrlParser: true})

const conn = mongoose.connection
conn.on('error', () => {
  console.log('连接数据库失败')
})
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. 更新
    r = await Model.updateOne({name: '苹果'}, {$set: {name: '芒果'}})
    console.log('更新结果', r)
    // 7. 删除
    r = await Model.deleteOne({name: '苹果'})
    console.log('删除结果', r)
  } catch (error) {
    console.log(error)
  }
})
Schema
  • 字段定义
const blogSchema = mongoose.Schema({
  // 定义校验规则
  title: {type: String, require: [true, '标题为必填项']},
  author: String,
  body: String,
  // 定义对象数组
  comments: [{body: String, data: Date}],
  // 指定默认值
  date: {type: Date, default: Data.now},
  hidden: Boolean,
  // 定义对象
  meta: {
    votes: Number,
    favs: Number
  }
})
// 定义多个索引,正1升序,负1降序
blogSchema.index({title: 1, author: 1, date: -1})
const BlogModel = mongoose.model('blog', blogSchema)
const blog = new BlogModel({
  title: 'nodejs持久化',
  author: 'jerry',
  body: '...'
})
const r = await blog.save()
console.log('新增blog', r)
  • 定义实例方法:抽象出常用方法便于复用
// 定义实例方法
blogSchema.methods.findAuthor = function(){
  return this.model('blog').find({author: this.author}).exec()
}
// 获得模型实例
const BlogModel = mongoose.model('blog', blogSchema)
const blog = new BlogModel({...})
// 调用实例方法
r = await blog.findAuthor()
console.log('findAuthor', r)
  • 静态方法
// 静态方法
blogSchema.statics.findAuthor = function(author){
  return this.model('blog').find({author}).exec()
}
r = await BlogModel.findAuthor('jerry')
console.log('findAuthor', r)
  • 虚拟属性
blogSchema.virtual('commentsCount').get(function(){
  return this.comments.length
})
let r = await blog.findOne({author: 'jerry'})
console.log('blog留言数:', r.commentsCount)

Redis

资源

Redis:下载
node_redis:文档

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

讲文明的喜羊羊拒绝pua

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值