• 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 字段
     
  • 相关阅读:
    LINQ的from子句和foreach语句的区别
    mysql连接错误10061
    iframe嵌套iframe阻塞
    2016-12有感,微信,组建
    js进阶篇学习
    html5的学习
    rocketmq集群(三)
    rocketmq发送普通消息(二)
    rocketmq安装(一)
    kafka stream及interceptor(四)
  • 原文地址:https://www.cnblogs.com/jiayouba/p/14991981.html
Copyright © 2020-2023  润新知