Gist网站实现(5)-使用Mongoose数据库实现简单的增删改查

数据库

分类

关系数据库: MySQL、PostgreSQL

文档数据库: mongoDB、CouchDB

区别在于schema

  • 关系数据库在插入数居前,需要先建立表结构,预先制定好字段和类型,即schema。一张表里的字段结构是一致的,一条数据,就是一个记录

  • 文档数据库按文档组织数据,数据格式无需提前声明,各个文档内也无关联。

Mongoose

Mongoose优化了MongoDB,解决对象建模的一系列问题

核心概念:

  • Schema:类似关系数据库建表时的字段设置。描述数据规范,类型、长度等

  • Model:从schema产生的构造器,通过model可以得到document。类似SSM中的sevice层,调用数据库component。可以创建、删除、查找等

  • Document:Document是Model的Instance实例,Document里存的都是数据。

使用Mongoose存储数据

导入Mongoose模块

  1. 新建文件mongoose.js,作为连接数据库的模块用于被其他模块导入

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
        const mongoose = require('mongoose');
    mongoose.connect('mongodb://localhost/nodegist',
    {
    useNewUrlParser:true, useUnifiedTopology:true
    },
    err =>{
    if(err){
    console.log('db error ......');
    process.exit();
    }
    });
    //将连接数据库服务装载入模块,可供其他地方导入使用
    module.exports = mongoose;
    • mongoose.connect(url,options):连接mongoose数据库
      • options可选
      • useNewUrlParser:底层MongoDB已经废弃当前连接字符串解析器。因为这是一个重大的改变,添加了useNewUrlParser标记,则当在用户如果遇到bug,允许用户在新的解析器中返回旧的解析器。因此除非连接阻止设置,否则你应该设置useNewUrlParser: true
      • useUnifiedTopology:当出现“当前服务器发现和监视引擎已弃用,将在将来的版本中删除”的连接MongoDB错误时。要使用新的服务器发现和监视引擎,按提示,要将选项useUnifiedTopology:true传递给mongoclient构造函数,即connect函数
  2. 导入mongoose模块,设计schema创建model及document

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    const mg = require("../lib/mongoose");
    //创建model
    //每调用一次就要创建一次model
    const Gist = mg.model( 'Gist' , {
    name: {type:String},
    type: {type:String},
    code: {type:String},
    author_id: {type:String},
    author: {type:mg.Schema.Types.ObjectId, ref: 'Author'},
    created_at: {type:String}
    } );
    //创建document
    const gist = new Gist({
    name:req.body.name,
    type:req.body.type,
    code:req.body.code,
    author_id:req.session.user.id,
    author:req.session.user._id,
    created_at:Date.now()
    });
  3. 创建保存逻辑
    使用await gist.save()保存gist。

    保存

  4. 优化mongoose创建model逻辑
    由于2中创建model时,每调用一次Gist的model就要创建一次model,不灵活,故将创建schema逻辑独立出来创建为模块,当使用时,直接使用require调用即可。

  5. 优化跳转逻辑,使保存后跳转至“我的代码”页面
    使用res.redirect设置页面跳转。

展示数据

  1. 在“我的代码”页面,使用mongoose的find逻辑对已保存的数据进行展示

    1
    2
    3
    4
    5
    6
    7
    8
      const Gist = require("../model/Gist");
    //设置显示时按创建时间逆序排序展示
    const gists = await Gist.find().sort({'created_at':-1});
    //将gists信息传入页面
    res.render('gists',{
    user:req.session.user,
    gists
    });
  2. 设计“我的代码”页面上代码的展示样式。
    我的代码

使用update逻辑修改数据

  1. 增加修改代码功能,通过点击修改按钮,跳转至修改页面

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    //从路径中获取需要修改的代码片段id
    router.get('/modify/:id',async(req,res)=>{
    const id = req.params.id;
    const Gist = require("../model/Gist");
    //通过id查询要修改的gist
    const gist = await Gist.findById(id);

    //增加校验:是否存在此gist,是否为当前登陆用户所提交的gist
    if(!gist) throw(new Error("gist not found!"));
    if(gist.author_id != req.session.user.id) throw(new Error("只允许修改自己提交的代码!"));

    //修改更新代码
    res.render('modify',{gist,user:req.session.user});
    });
  2. 创建修改代码页面,并增加相关样式。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    form(action="/gists/update/"+gist.id, method="post")
    div
    input(name="name" value=gist.name)
    select(name="type")
    each thetype in ["php","javascript","go","python"]
    -if(gist.type==thetype)
    option(value=thetype selected="true") #{thetype}
    -else
    option(value=thetype) #{thetype}
    div
    textarea(name="code", cols="30", rows="10") #{gist.code}
    div
    button(type="submit") 保存修改
    • input:代码名,直接从url中获取

    • select:从gist中获取并将其选中

      写的时候忘了截图了= =。最终的样子就是表单填了内容的样子。

  3. 添加修改代码update逻辑,修改后点击提交按钮,更新数据库内容
    修改代码页面update逻辑与modify逻辑几乎一样,仅添加了对输入框、选择框等的校验。并将新修改的内容更新。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    if( !req.body.name )  throw( new Error('名称不能为空') );
    if( !req.body.type ) throw( new Error('类型不能为空') );
    if( !req.body.code ) throw( new Error('代码不能为空') );

    gist.name = req.body.name;
    gist.type = req.body.type;
    gist.code = req.body.code;
    gist.author_id = req.session.user.id;
    gist.created_at = Date.now() ;

删除数据

  1. 增加删除代码功能
    删除代码使用mongoose的deleteOne()函数直接删除。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    router.get('/remove/:id', async(req,res)=>{
    const id = req.params.id;
    const Gist = require("../model/Gist");
    const gist = await Gist.findById(id);

    //增加校验:是否存在此gist?是否为当前登陆用户所提交的gist
    if(!gist) throw(new Error("gist not found!"));
    if(gist.author_id != req.session.user.id) throw(new Error("只允许修改自己提交的代码!"));

    await gist.deleteOne();
    res.redirect('/gists/main');
    });
  2. 删除代码时添加提醒是否删除
    在点击按钮时使用javascript:function()调用js函数添加弹窗提醒

    1
    2
    3
    4
    5
    6
    7
    8
    a.btn.btn-danger(href=("javascript:remove_gist('"+gist.id+"');void(0);")) 删除
    //删除提醒弹窗
    function remove_gist(id){
    //若点击确定删除,则执行remove删除逻辑
    if(confirm("真的要删除吗?本操作无法恢复")){
    location = '/gists/remove/'+id;
    }
    }

    删除

搜索数据

  1. 创建搜索逻辑

    • 添加搜索组件,将搜索条件search传入逻辑

      1
      2
      3
      form(action="/gists/main")
      input(type='search', placeholder='关键字', name="search", value=search)
      button(type='submit') 搜索
    • 实现搜索逻辑

      1
      const gists = await Gist.find(search).sort({'created_at':-1});

      搜索

  2. 优化逻辑,设计查询条件(name、code)

    • 查询条件是js对象,需满足查询nameorcode,且查询的范围必须为该用户可查看的范围。

    • 查询条件使用正则表达式,则其查询对象的key为{$regex: new RegExp( search, 'i')}

      • $regex: 启用正则表达式,冒号后面是值
      • new RegExp(): js中创建正则表达式
      • search: 规则,描述字符串的特征
      • i: 正则表达式参数,i表示忽略大小写
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      const search = req.query.search ? 
      {
      //查询条件必须为当前用户可看范围,使用and
      $and:[
      //查询条件name和code为or关系
      {
      $or:[
      {name:{$regex:new RegExp(req.query.search,'i')}},
      {code:{$regex:new RegExp(req.query.search,'i')}}
      ]
      }
      ,{'author_id':req.session.user.id}
      ]
      }
      :{'author_id':req.session.user.id};

展示gist详细信息

展示gist详细信息,需要同时展示作者信息,故需要多个实体。

多实体功能实现方法

对于多实体的功能有三种实现方式:Map、Subdocument、Populate。

  • Map:保存作者信息时,直接使用Map类型,在取数据时,使用get方法。
  • Subdocument:在建立gist的Schema前,建立author的Schema,在gist中直接使用authorSchema嵌套。
    • 这种方式数据更规范。
    • 当其parent Document的save、validate中间件触发时,Subdocument的中间件也被触发。故对数据的控制更有力(有点类似父类和子类)。
    • 访问数据时,直接使用.即可。
  • Populate:Populate的实现是在gist里只存入供查询的author的id(此id为标识author的id,不是人为定义的id)。在查完gist后,再通过此id将author的内容进行扩展。扩展的方法是通过gist的model里为author设置的ref参数,至此就获得了author的全部信息(类似一种二次查询)。
    • Map和Subdocument的问题是都把信息嵌入了document中,若有修改时,则需要全部修改,不够灵活。
    • 此种方法author的信息也是需要存入数据库以供populate查询。
    • 传输时,只传输标识author的id
展示gist详细信息实现步骤(该部分使用Populate方式实现)
  1. 创建Author的Shema

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    const mg = require("../lib/mongoose");

    const AuthorSchema = new mg.Schema({
    name:{type:String},
    avatar:{type:String}
    });

    const Author = mg.model('Author',AuthorSchema);

    module.exports=Author;
  2. 将author信息存入数据库

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    const Author = require("../model/Author");
    //将用户数据存入数据库
    const user_data = user.data;
    let author = await Author.findOne({"id":user_data.id});
    if(!author){//如果用户不存在,则新建一个用户
    author = new Author({
    id:user_data.id,
    name:user_data.name,
    avatar:user_data.avatar_url
    });
    }else{//若存在,则更新为最新信息
    author.name = user_data.name;
    author.avatar = user_data.avatar_url;
    }

    await author.save();

    //将用户_id存入session
    req.session.user = user.data;
    req.session.user._id = author._id;
  3. 展示gist详细信息及作者信息

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    extends layout
    block content
    .d-flex.justify-content-between
    h4 #{gist.name}
    span.badge.badge-secondary #{gist.type}
    div #{gist.author.name}

    pre
    code#thecode.tcode(class=gist.type) #{gist.code}

    .form-group.row
    a.btn.btn-primary(href=("/gists/main") style="") 返回上页

    详细信息展示

  • 版权声明: 本博客所有文章除特别声明外,均采用 Apache License 2.0 许可协议。转载请注明出处!
  • © 2020-2024 Aweso Lynn
  • PV: UV: