Koa + MongoDB 安装 创建服务 mongoose 使用 常用查询 populate 链表、内嵌填充

Mongoose DB

 

下载地址:https://www.mongodb.com/try/download/community

相关文档

官网手册:https://docs.mongodb.com/manual/

使用文档:https://mongoosejs.com/docs/

中文版:http://www.mongoosejs.net/docs/api.html

nodejs node-mongodb-native 教程:http://mongodb.github.io/node-mongodb-native/3.4/quick-start/quick-start/  3.4版本的

Robo 3T: the hobbyist GUI 

连接操作MongoDB 的图形化界面。简单好用。

下载地址:https://robomongo.org/download

MongoDB 安装

选择需要安装的目录,最好保持默认,系统盘。在其他盘可能会有意想不到的问题出现。

选择是否安装MongoD为Windows服务(安装完成,自动以服务的方式启动)

说明(https://docs.mongodb.com/manual/tutorial/install-mongodb-on-windows/):

从 MongoDB 4.0 开始,默认情况下,你可以在安装期间配置和启动 MongoDB 作为服务,并在成功安装后启动 MongoDB 服务。也就是说,MongoDB 4.0 已经不需要像以前版本那样输入一堆命令行来将 MongoDB 配置成 Windows 服务来自动运行了,方便了很多。

如果你选择不将 MongoDB 配置为服务,请取消选中 Install MongoD as a Service

如果你选择将 MongoDB 配置为服务,则可以指定以下列用户之一运行服务:

  网络服务用户:即 Windows 内置的 Windows 用户帐户
  本地或域用户:
    对于现有本地用户帐户,Account Domain 指定为 .,并为该用户指定 Account Name 和 Account Password。
    对于现有域用户,请为该用户指定 Account Domain,Account Name 和 Account Password。

指定 Service Name(Windows 服务名称):如果你已拥有具有指定名称的服务,则必须选择其他名称,不能重名。
指定 Data Directory(数据保存目录):对应于 --dbpath。如果该目录不存在,安装程序将创建该目录并为服务用户设置访问权限。可以通过配置修改。
指定 Log Directory(日志保存目录):该目录对应于 --logpath。如果该目录不存在,安装程序将创建该目录并为服务用户设置访问权限。可以通过配置修改。

不安装官网提供的图形化工具(安装非常慢)

完成即可查看

koa 创建服务

最简化实现,什么其他的都没有,需要自己创建目录、结构等:

新建文件夹 koaMongoD,打开编辑器终端:npm init 。一路回车,自动引导创建package.json。

新建文件 app.js 。安装koa:npm i koa。

// app.js
// 导入koa,和koa 1.x不同,在koa2中,我们导入的是一个class,因此用大写的Koa表示:
const Koa = require('koa');

// 创建一个Koa对象表示web app本身:
const app = new Koa();

// 对于任何请求,app将调用该异步函数处理请求:
app.use(async (ctx, next) => {
    await next();
    ctx.response.type = 'text/html';
    ctx.response.body = '<h1>Hello, koa2!</h1>';
});
//app.use() 参数就相当于一个"中间件",干点什么事情

// 在端口3000监听:
app.listen(3000);
console.log('app started at port 3000...');

此时即可运行并查看:node app

结构化实现,通过 koa-generator 快速搭建:

编辑器打开想要创建项目位置的父目录 koa-generator :npm i koa-generator -g

创建项目:koa2 koaMongoDB

可以带上模板引擎参数:koa2 koaMongoDB -e  使用 ejs (默认使用的 pug) 

如果报错:

koa2 : 无法加载文件 C:UsersHPAppDataRoaming
pmkoa2.ps1,因为在此系统上禁止运行脚本。有关详细信息,请参阅 https:/go.microsoft.com/fwlink/?LinkID=135170 中的 about_Execution_Policies。
所在位置 行:1 字符: 1
+ koa2 koaMongoDB
+ ~~~~
    + CategoryInfo          : SecurityError: (:) [],PSSecurityException
    + FullyQualifiedErrorId : UnauthorizedAccess

直接到文件目录 C:UsersHPAppDataRoaming pm   删除  koa2.ps1 文件即可(好多类似的这个问题都可以这么解决)。

进入目录:cd koaMongoDB

安装依赖:npm install

运行:npm start

koa 使用MongoDB

安装 Robo 3T :

输入个人信息的界面直接点击 finish 即可,然后直接连接默认的MongoDB:

可以右键 New Connection*(或者自己修改的名字) ,点击 Create Database 新建一个数据集合:

可以点击Refresh、或绿色三角刷新,右键选中要操作的集合,即可增删改查数据、删除集合(drop)等:

koa项目安装mongoose:npm i mongoose

根目录创建 db 文件夹(Database);db文件夹内创建 db.js;根目录 app.js 引用 db.js:

const Koa = require('koa')
const app = new Koa()
const views = require('koa-views')
const json = require('koa-json')
const onerror = require('koa-onerror')
const bodyparser = require('koa-bodyparser')
const logger = require('koa-logger')

// 连接 MongoDB 测试
require(`./db/db`)

const index = require('./routes/index')
const users = require('./routes/users')

// error handler
onerror(app)

// middlewares
app.use(bodyparser({
  enableTypes:['json', 'form', 'text']
}))
app.use(json())
app.use(logger())
app.use(require('koa-static')(__dirname + '/public'))

app.use(views(__dirname + '/views', {
  extension: 'pug'
}))

// logger
app.use(async (ctx, next) => {
  const start = new Date()
  await next()
  const ms = new Date() - start
  console.log(`${ctx.method} ${ctx.url} - ${ms}ms`)
})

// routes
app.use(index.routes(), index.allowedMethods())
app.use(users.routes(), users.allowedMethods())

// error-handling
app.on('error', (err, ctx) => {
  console.error('server error', err, ctx)
});

module.exports = app

db.js 内容,一个简单的连接、增删改查服务,npm start 测试(也可以直接 node db.js 测试):  

const mongoose = require('mongoose')

const url = 'mongodb://localhost/testChat'
const options = {
  useNewUrlParser: true,
  useUnifiedTopology: true,
  poolSize: 6, // The maximum size of the individual server pool. default 5
  keepAlive: 250 // The number of milliseconds to wait before initiating keepAlive on the TCP socket. default 30000
}
// 指向 testChat, 如果数据库没有 testChat ,会自动创建
mongoose.connect(url, options)

let db = mongoose.connection
db.on('connected', function () {
  console.log('Mongoose connected ' + url)
})

db.on('error', function (err) {
  console.log('Mongoose connection error: ' + err)
})
db.on('disconnected', function () {
  console.log('Mongoose connection disconnected')
})

//创建骨架
const usersSchema = new mongoose.Schema(
  {
    indexId: Number, // 数字indexId
    account: String, // 账号
    username: String // 用户名
  },
  {
    versionKey: false
  }
)
/**
 *  @params name: string, 可以通过 mongoose.model(`users`) 获取到创建的 model
 *  @params schema?: Schema<T, U>, 创建 model 使用的 schema
 *  @params collection?: string, 实际上对应的 集合的名字 此处操作的 集合名字是  usersTest 不是 users
 *  @params skipInit?: boolean  true or false 都会在 userTest 不存在时 新建 userTest
 */
const usersModel = mongoose.model('users', usersSchema, 'usersTest', true) //根据骨架创建模版

const usersModel_ = mongoose.model(`users`)
console.log(JSON.stringify(usersModel) == JSON.stringify(usersModel_)) // true


// create 新建一条数据,如果数据库没有集合 users ,会自动创建
usersModel.create(
  {
    indexId: 0,
    account: 'String0',
    username: 'String0'
  },
  function (err, user) {
    console.log(err, user) // null { _id: 60e81cc30287645cd4a1a918, indexId: 0, account: 'String0', username: 'String0' }
  }
)
// 回调和then方法不一样
usersModel
  .create({
    indexId: 1,
    account: 'String1',
    username: 'String1'
  })
  .then(function (user, err) {
    console.log(user, err) // { _id: 60e81ff7584cc04c7468d88a, indexId: 0, account: 'String0', username: 'String0' } undefined
  })

// 查找 只会返回符合的所有 []
usersModel.find(
  {
    indexId: 0
  },
  function (err, user) {
    console.log(err, user) // null [{ _id: 60e81ff7584cc04c7468d88b, indexId: 0, account: 'String0', username: 'String0' }]
  }
)
// 修改  依据表内的 _id 来修改
usersModel.updateOne(
  { _id: '60e81ff7584cc04c7468d88b' },
  {
    $set: { indexId: 9, account: 'String9', username: 'String9' }
  },
  function (err, user) {
    console.log(err, user) // null { n: 1, nModified: 1, ok: 1 }
  }
)
// 删除  依据表内的 _id 来删除
usersModel.deleteOne({ _id: '60e81ff7584cc04c7468d88a' }, function (err, user) {
  console.log(err, user) // null { n: 1, ok: 1, deletedCount: 1 }
})

// 翻页
async function getPage(pager) {
  //skip((pager.curPage - 1) * pager.eachPage) 跳过条数 limit(pager.eachPage) 获取条数
  let data = await usersModel
    .find()
    .skip((pager.curPage - 1) * pager.eachPage)
    .limit(pager.eachPage)
  pager.rows = data
  let count = await usersModel.countDocuments()
  pager.total = count
  pager.maxPage = Math.ceil(count / pager.eachPage)
  return pager
}
getPage({ curPage: 1, eachPage: 1 }).then(function (users, err) {
  console.log(users, err) // { curPage: 1, eachPage: 1, total: 9, maxPage: 9, rows: [{_id: 60e81fe8eabfcd029061519f, indexId: 0, account: 'String0', username: 'String0'}] } undefined
})

模块化,并编写接口操作MongoDB

db.js 同目录创建 models、daos 文件夹,models 存放声明的多个 schema,并创建导出 model;daos 存放各个 models 的增删改查方法(不同的查询配置、参数修改、数据处理),

db.js 里面引用所有的 model:

const mongoose = require('mongoose')

const url = 'mongodb://localhost/testChat'
const options = {
  useNewUrlParser: true,
  useUnifiedTopology: true,
  poolSize: 6, // The maximum size of the individual server pool. default 5
  keepAlive: 250 // The number of milliseconds to wait before initiating keepAlive on the TCP socket. default 30000
}
mongoose.connect(url, options)

let db = mongoose.connection
db.on('connected', function () {
  console.log('Mongoose connected ' + url)
})

db.on('error', function (err) {
  console.log('Mongoose connection error: ' + err)
})
db.on('disconnected', function () {
  console.log('Mongoose connection disconnected')
})

require(`./models/usersModel`)
require(`./models/friendsModel`)
require(`./models/groupsModel`)

创建三个Model,并编写mockData.js(引用mock.js)模拟数据方便测试,在routes文件夹内新建三个Model对应的接口,并在app.js内使用;

现目录:

三个model:

// usersModel.js
const mongoose = require('mongoose')

const usersSchema = new mongoose.Schema(
  {
    account: String, // 账号
    age: Number, // 年龄
    phone: Number, // 电话
    name: String // 用户名
  },
  {
    versionKey: false
  }
)
const usersModel = mongoose.model('users', usersSchema, 'users') //根据骨架创建模版
module.exports = usersModel

// groupsModel.js
const mongoose = require('mongoose')

const groupsSchema = new mongoose.Schema(
  {
    //创建骨架
    groupName: String, // 群聊名称
    profilePhoto: Array, // 群聊头像
    // groupHolder: String, // 群主
    // administrators: Array, // 管理员
    users: [
      {
        _id: { type: mongoose.Schema.Types.ObjectId, ref: 'users' },
        position: String, // 职位
        remarks: String // 备注
      }
    ] //群成员
    // {
    //   _id: ObjectId, // 用户ID
    //   position: String, // 职位
    //   remarks: String, // 备注
    // }
  },
  { versionKey: false }
)
const groupsModel = mongoose.model('groups', groupsSchema, 'groups') //根据骨架创建模版
module.exports = groupsModel

// friendsModel.js
const mongoose = require('mongoose')

const friendsSchema = new mongoose.Schema(
  {
    //创建骨架
    ownerId: String, // 所属用户 ID
    type: Number, // 用户类型: 0 ; 群类型: 1
    subgroup: Number, // 分组;默认好友: 0 ; 亲密好友?...
    remarks: String, // 备注
    user: { type: mongoose.Schema.Types.ObjectId, ref: 'users' }, // 用户类型好友
    group: { type: mongoose.Schema.Types.ObjectId, ref: 'groups' } // 群类型好友
  },
  { versionKey: false }
)
const friendsModel = mongoose.model('friends', friendsSchema, 'friends') //根据骨架创建模版
module.exports = friendsModel

ps:数据都会有一个_id 的属性,要么自己传,不传就会自动生成。

用户表独立,好友表关联用户或群,群表里面users数组内嵌关联 用户。

三个 dao:

// usersDao.js
const mongoose = require(`mongoose`)
const usersModel = mongoose.model(`users`)

const getUsers = async pager => {
  let data = await usersModel
    .find()
    .skip((pager.curPage - 1) * pager.eachPage) // 跳过条数 
    .limit(pager.eachPage) // 获取条数
    .sort({ age: 1 }) // 按年龄 递增排序
  pager.rows = data
  let count = await usersModel.countDocuments()
  pager.total = count
  pager.maxPage = Math.ceil(count / pager.eachPage)
  return pager
}
const getUser = async query => {
  let data = await usersModel.find(query)
  return data
}

const modifyUser = async (_id, user) => {
  delete user._id
  let data = await usersModel.updateOne(_id, {
    $set: user
  })
  return data
}

const deleteUser = async _id => {
  let data = await usersModel.deleteOne(_id)
  return data
}

const addUser = async user => {
  let data = await usersModel.create(user)
  return data
}

module.exports = {
  getUsers,
  getUser,
  addUser,
  modifyUser,
  deleteUser
}

// groupsDao.js
const mongoose = require(`mongoose`)
const groupsModel = mongoose.model(`groups`)

const createGroup = async group => {
  let data = await groupsModel.create(group)
  return data
}

const getGroup = async _id => {
  // populate('users._id',) 指定 users 数组里面的元素的 _id ,去关联查询用户信息
  let data = await groupsModel.find(_id).populate('users._id')
  
  return data
}

module.exports = {
  createGroup,
  getGroup
}

// friendsDao.js
const mongoose = require(`mongoose`)
const friendsModel = mongoose.model(`friends`)
const usersModel = mongoose.model(`friends`)

const createFriends = async friends => {
  let data = await friendsModel.create(friends)
  return data
}

const getFriends = async id => {
  let data = await friendsModel
    .find(id)
    .populate('user')
    .populate({
      // 理论上可以多次内嵌链表查询
      path: 'group', // 指定 group 属性 去 链表查询 群信息
      populate: { path: 'users._id' } // 指定 通过group查询出来的 users 数组里面的元素的 _id 属性 去 链表查询 用户信息
    })

  data = data.map(v => {
    console.log(v)
    return {
      _id: v._id,
      ownerId: v.ownerId,
      type: v.type,
      subgroup: v.subgroup,
      remarks: v.remarks,
      user: v.user,
      group: v.group
    }
  })
  return data
}
module.exports = {
  getFriends,
  createFriends
}

routes里面对应的接口:

// routes/users.js
const router = require('koa-router')()
// 本来应该 抽一层 services 出来 ,对查询参数 和 返回 数据做处理,然后这里引用 usersDaos 才对,省略了。
const usersDao = require(`../db/daos/usersDao`)

router.prefix('/users')

router.post('/', async function (ctx, next) {
  let user = ctx.request.body
  let data = await usersDao.addUser(user)
  ctx.body = data
})

router.get('/', async function (ctx, next) {
  const _id = ctx.request.query
  let data = await usersDao.getUser({ _id })
  ctx.body = data
})
router.get('/page', async function (ctx, next) {
  let pager = ctx.request.query
  pager.curPage = pager.curPage - 0
  pager.eachPage = pager.eachPage - 0
  const data = await usersDao.getUsers(pager)
  ctx.body = data
})

router.put('/', async function (ctx, next) {
  let user = ctx.request.body
  console.log(user)
  let data = await usersDao.modifyUser({ _id: user._id }, user)
  ctx.body = data
})

//动态路由参数 ctx.params
router.delete('/:_id', async function (ctx, next) {
  const _id = ctx.params._id
  let data = await usersDao.deleteUser({ _id })
  ctx.body = data
})

module.exports = router

// routes/groups.js
const router = require('koa-router')()
// 本来应该 抽一层 services 出来 ,对查询参数 和 返回 数据做处理,然后这里引用 usersServices 才对,省略了。
const groupsDao = require(`../db/daos/groupsDao`)

router.prefix('/groups')

router.post('/', async function (ctx, next) {
  let group = ctx.request.query
  let data = await groupsDao.createGroup(group)
  ctx.body = data
})

router.get('/', async function (ctx, next) {
  const query = ctx.request.query
  let data = await groupsDao.getGroup(query)
  ctx.body = data
})

module.exports = router

// routes/friends.js
const router = require('koa-router')()
// 本来应该 抽一层 services 出来 ,对查询参数 和 返回 数据做处理,然后这里引用 usersServices 才对,省略了。
const friendsDao = require(`../db/daos/friendsDao`)

router.prefix('/friends')

router.post('/', async function (ctx, next) {
  let friends = ctx.request.query
  let data = await friendsDao.createFriends(friends)
  ctx.body = data
})

router.get('/', async function (ctx, next) {
  const query = ctx.request.query
  let data = await friendsDao.getFriends(query)
  ctx.body = data
})

module.exports = router

在 app.js 里面使用:

const Koa = require('koa')
const app = new Koa()
const views = require('koa-views')
const json = require('koa-json')
const onerror = require('koa-onerror')
const bodyparser = require('koa-bodyparser')
const logger = require('koa-logger')

// 连接 MongoDB 测试
require(`./db/db`)

const index = require('./routes/index')
const users = require('./routes/users')
// 新建的路由api
const friends = require('./routes/friends')
const groups = require('./routes/groups')

// error handler
onerror(app)

// middlewares
app.use(bodyparser({
  enableTypes:['json', 'form', 'text']
}))
app.use(json())
app.use(logger())
app.use(require('koa-static')(__dirname + '/public'))

app.use(views(__dirname + '/views', {
  extension: 'pug'
}))

// logger
app.use(async (ctx, next) => {
  const start = new Date()
  await next()
  const ms = new Date() - start
  console.log(`${ctx.method} ${ctx.url} - ${ms}ms`)
})

// routes
app.use(index.routes(), index.allowedMethods())
app.use(users.routes(), users.allowedMethods())
// 使用新建的路由api
app.use(friends.routes(), friends.allowedMethods())
app.use(groups.routes(), groups.allowedMethods())

// error-handling
app.on('error', (err, ctx) => {
  console.error('server error', err, ctx)
});

module.exports = app

新建的模拟数据,mockData.js:

const mongoose = require('mongoose')
const url = 'mongodb://localhost/testChat'
const options = {
  useNewUrlParser: true,
  useUnifiedTopology: true,
  poolSize: 6, // The maximum size of the individual server pool. default 5
  keepAlive: 250 // The number of milliseconds to wait before initiating keepAlive on the TCP socket. default 30000
}
mongoose.connect(url, options)

let db = mongoose.connection
db.on('connected', function () {
  console.log('Mongoose connected ' + url)
  const usersDao = require('./daos/usersDao.js')
  const groupsDao = require('./daos/groupsDao.js')
  const friendsDao = require('./daos/friendsDao.js')

  // 使用 Mock
  const Mock = require('mockjs')
  const result = Mock.mock({
    'data|15': [
      // 15条数据
      {
        name: `@cname`, // 中文名称
        account: '@string(11)', // 输出 11 个字符长度的字符串
        age: '@integer(18, 38)', // 18 到 38 的整数
        'phone|1958-6849': 6849 // 1958-6849 整数
      }
    ]
  })
  // 输出结果
  // console.log(JSON.stringify(result, null, 2))

  let users = []
  function createUsers() {
    let promisesUsers = []
    // 新建用户
    result.data.forEach(user => {
      let curUser = { ...user, _id: new mongoose.Types.ObjectId() }
      users.push({ ...curUser })
      promisesUsers.push(
        usersDao.addUser({ ...curUser }).then((res, err) => {
          console.log(err)
        })
      )
    })
    return promisesUsers
  }

  let groups = []
  function createGroups() {
    let promisesGroups = []
    // 初始群:每个账户以自己名字建一个群 ,群成员只有自己
    users.forEach(user => {
      let curGroup = {
        _id: new mongoose.Types.ObjectId(),
        groupName: user.name + '的初始群',
        profilePhoto: [''],
        users: [
          {
            _id: user._id,
            position: '群主',
            remarks: user.name + '自己'
          }
        ]
      }
      groups.push({ ...curGroup })
      promisesGroups.push(
        groupsDao.createGroup({ ...curGroup }).then((res, err) => {
          console.log(err)
        })
      )
    })
    return promisesGroups
  }
  let friends = []
  function createFriends() {
    let promisesFriends = []
    // 初始好友:每个账户 添加自己、自己初始群为好友
    users.forEach((user, index) => {
      promisesFriends.push(
        friendsDao
          .createFriends({
            _id: new mongoose.Types.ObjectId(),
            ownerId: user._id,
            type: 0,
            subgroup: 0,
            remarks: user.name + '添加自己为好友',
            user: user._id,
            group: null
          })
          .then((res, err) => {
            friends.push(res)
          })
      )
      promisesFriends.push(
        friendsDao
          .createFriends({
            _id: new mongoose.Types.ObjectId(),
            ownerId: user._id,
            type: 1,
            subgroup: 0,
            remarks: user.name + '添加自己初始群类型好友',
            user: null,
            group: groups[index]._id
          })
          .then((res, err) => {
            friends.push(res)
          })
      )
    })
    return promisesFriends
  }
  async function initData() {
    await Promise.all(createUsers())
    await Promise.all(createGroups())
    await Promise.all(createFriends())
    console.log(friends)
    console.log('数据模拟完成')
  }
  initData()
})

db.on('error', function (err) {
  console.log('Mongoose connection error: ' + err)
})
db.on('disconnected', function () {
  console.log('Mongoose connection disconnected')
})

require(`./models/usersModel`)
require(`./models/friendsModel`)
require(`./models/groupsModel`)

直接 node mockData.js,即可插入模拟数据。

查询测试

用户翻页查询:

通过 孙平的 _id 修改 孙平的个人信息:

删除成功时返回这样(直接把 _id 参数在地址路径上给了):

通过孙平查询他的好友:

可以看到,关联的表信息都查出来了,在通过群好友查询群:

返回的信息是没有处理的,处理一下群返回的信息,修改groupsDao.js:

const mongoose = require(`mongoose`)
const groupsModel = mongoose.model(`groups`)

const createGroup = async group => {
  let data = await groupsModel.create(group)
  return data
}

const getGroup = async _id => {
  // populate('users._id',) 指定 users 数组里面的元素的 _id ,去关联查询用户信息
  let data = await groupsModel.find(_id).populate('users._id')
  return data.map(v => ({
    _id: v._id,
    groupName: v.groupName,
    profilePhoto: v.profilePhoto,
    users: v.users.map(user => ({
      userId: user._id._id,
      name: user._id.name,
      account: user._id.account,
      age: user._id.age,
      phone: user._id.phone,
      position: user.position,
      remarks: user.remarks
    }))
  }))
}

module.exports = {
  createGroup,
  getGroup
}

再次查询:

populate还可以指定链表时,填充的数据的字段 比如:
populate('users._id','name -_id')
即为,只查询获取 name 字段,并且不要 _id 字段
 
原文地址:https://www.cnblogs.com/jiayouba/p/14991981.html