NodeJS 全栈开发
渡一袁老师“nodejs集训营”时间 2020年5月24日
一、初识NodeJS
设置 VScode 终端
1. 文件 - 首选项 - 设置 - 输入 terminal:explorer kind ( terminal 是终端的意思)
2. 选择 external,表示外部终端,默认的 integrated 是集成终端
3. 然后右键在终端中打开
teacher node version
node -v 12.14..0
npm -v 6.13.4
老师说 node 版本有两个分水岭,
一个是 10 一个是 12,这个两个版本更新比较多,更新的动作比较大
node-v12.16.3-x64.msi
window 平台安装的后缀为 .msi
浏览器端有一个全局对象 window,
该对象里面所有的属性可以直接用,比如浏览器里面可以直接用 alert、document
node 里面全局对象是 global
console.log(global);
global 对象里面也有很多属性,这些不属于 ES 语言标准,是浏览器提供的,为了符合我们的开发习惯 node 提供的一些和 window 里面一模一样的全局对象,全局对象都是可以直接用的
console
setTimeout
setInterval
二、CommonJS
CommonJS 是一个规范,node 在 2009 年实现了该标准
CommonJs 规范内容
1. 每一个 js 文件就是一个模块
2. 模块内所有变量均为局部变量,不会污染全局
比如,
var a = 3 定义一个全局变量
console.log(global.a) 全局变量 global 里面没有 a
node index 执行输出 undefined,说明每个模块的变量都是局部使用的,不会污染全局变量 global
3. 模块中提供的内容需要导出,才能被其他模块使用
4. 导出使用 exports.xxx = xxx 或 module.exports.xxx = xxx 或 this.xxx = xxx
5. 使用 require 函数导入其他模块
文件结构
|- my-first-node
|- a.js
|- index.js 我们把要运行的模块叫入口模块,就是以这个模块为起点进行运行跟 java 差不多,可以写 .js 后缀也可以不写
1、导出
第一种方式
exports 导出的相当于一个对象
// a.js
exports.a = 3; // 导出 {a:3}
exports.b = 123; // 再导出一次 {a:3, b:123}第二种方式
module.exports 方式写什么就导出什么,比如导入一个数字 3,导出的就是一个数字 3
// a.js module.exports = 3; // 导出一个数字3
module.exports 往往只写一次,比如下面又重新赋值一个对象,就相对于导出的是一个对象了
// a.js
module.exports = 3;
module.exports = {
  a: "hello"
}还有一种方式 this,
它跟 exports 差不多,很少很少使用
// a.js
this.a = 3; // 导出 {a:3}
this.b = 123; // 上面导出的a不变,在导出一个b {a:3, b:123}2、导入
require("./a") 函数导入
1. 径必须以 ./ 或 ../ 开头
2. a 模块可以不写 .js 后缀名
导入 a 模块,把 a 模块导出的东西返回,而且会把 a 模块执行一次
// index.js
var result = require("./a.js");
console.log(result); // {a:3, b:123}可以定义相同的变量名a,模块使用自己的变量,互相不会干扰
// index.js
var result = require("./a.js");
var a = 3; // 可以定义相同的变量名a,不会互相干扰
console.log(result); // {a:, b:123}a.js 模块导出一个函数,函数名是 sum
index.js 模块导入时,接收的变量可以是任何名字
// a.js
console.log("a模块执行了");
module.exports = function sum(a ,b){ // 导入sum函数
  return a + b;
}
// index.js
var result = require("./a"); // 导入函数时可以是任何名字
console.log(result);node index 执行入口模块文件,输出
a模块执行了
[Function: sum]
3、require 函数的原理
node 实现 commonJS 规范的原理,是通过 require 把每个模块的内容放到一个函数环境中执行
// 下面是 require 的伪代码
function require(modulePath){
  // modulePath模块路径
  var moduleId = getModuleId(modulePath); // 获取模块的绝对路径
  // 是否有缓存(缓存是模块导出的结果)
  if(cache[moduleId]){
    return cache[moduleId]; // 有缓存直接返回缓存结果,就什么也不做了
  }
  // 没有缓存
  // 辅助函数,用于执行一个模块
  function execModule(module, exports, __filename, __dirname){
    // module 用户导出的对象
    // exports 用户导出的对象
    // 导入的模块所在目录的绝对路径
    // 导入的模块的绝对路径
    // require 是函数本身
    // 这里是导入模块a.js的代码
  }
  var module = {
    exports: {}, // 把这个对象当做下面call里面的对象
  }
  // 使用call执行辅助函数
  execModule.call(
    module.exports,
    module,
    module.exports,
    模块目录的绝对路径, // d:\xx\xxx
    模块绝对路径 // d:\xx\xxx\.a.js
  );
  // 模块执行完
  cache[moduleId] = module.exports;  // 把module.exports保存在缓存里
  return module.exports; 
}require 函数原理
1. require(modulePath){} 首先根据 require 的参数,获取模块的id
require("./a,js") id 就是模块的绝对路径 "C:\Users\电脑名\Desktop\nodeStu\a.js"
2. 接下来看一个全局的对象,它是一个缓存,
var cache = {
"C:\\Users\\电脑名\\Desktop\\nodeStu\\a.js": {a:3, b:123}
}
模块的 id 作为该对象的属性名,属性名是模块路径,斜线 \ 需要转义一下 \\,
属性值是模块导出的结果
3. 看一下模块的 id 有没有缓存( if(cache[moduleId]) )
如果有缓存直接导出缓存的结果,什么都不做了,
但是一开始没有缓存的
4. 没有缓存,进行下面的逻辑,先不看辅助函数 execModule
创建一个 module 的对象,该对象里面有一个 exports 的属性是一个 {}
5. 接下来使用 call 方式调用 execModule 辅助函数
execModule.call(
把 module.exports 对象作为 this 传进去了
第一个参数,是整个 module
第二个参数,把 exports 传进去,就是这个 module.exports(第二个参数和 this 是一样的)
第三个参数,是模块目录的绝对路径 C:\Users\summer\Desktop\nodeStu
第四个参数,是模块的绝对路径 C:\Users\summer\Desktop\nodeStu\a.js
)
6. 辅助函数 execModule 里面执行的就是导入的 a.js 模块的代码
这就解释了,
模块里面为什么可以使用 module,exports,和 this(this是call绑定的),
而且模块里面还可以使用 __filename, __dirname
模块里面的代码,实际是在一个函数里面执行,
而函数环境里面有 exports, module, __filename, __dirname 这四个参数
模块里面的代码,实际是在在一个函数里面执行,而函数里面就有上面说的四个参数
// a.js console.log(__dirname); console.log(__filename);
node index
C:\Users\电脑名\Desktop\node
C:\Users\电脑名\Desktop\node\a.js
如果在模块里面输出 arguments 长度的结果是5,实际还有一个参数,袁老师忘了
// a.js console.log(__dirname); console.log(__filename); console.log(arguments.length); // 5
依次打印出的是 execModule 函数的参数,袁老师忘的参数是 require 函数本身
// a.js console.log(arguments[4]); console.log(arguments[3]); console.log(arguments[2]); console.log(arguments[1]); // require函数本身 console.log(arguments[0]);
参数实际的顺序是
function execModule( exports, require, module, __filename, __dirname ){...}
为什么要把 require 函数本身传进去呢?
因为 a.js 模块里面有可能还要导入别的模块,就要用到 require 函数,不传进去,函数找不到 require 函数
其实入口模块 index.js 也是当成一个模块执行的,也是在一个函数环境里面执行的,也是通过这种方式把 require 传给我们的,这就是 node 底层的处理方式
为什么这些不是全局变量,不在全局对象 global 里面,却能够直接执行呢?就是因为这些是在一个函数函数里面,这些全是函数的参数
console.log(global.require); // undefined console.log(global.__filename); // undefined console.log(global.__dirname); // undefined console.log(global.module); // undefined console.log(global.exports); // undefined
node index
7. 模块执行完后
把 module.exports 保存到缓存 cache[moduleId] 里面
模块的绝对路径就是模块 id,作为缓存的属性
值是模块导出的结果
下一次在执行 require 导入的时候,在一开始就直接进入判断了,有缓存就直接返回缓存的结果
4、面试题
1. require导入了6次,123输出几次?
// a.js
console.log(123);
// index.js
require('./a.js');
require('./a.js');
require('./a.js');
require('./a.js');
require('./a.js');
require('./a.js');只打印一次,第二次有缓存了,同一个模块是有缓存结果的
2. 导入a模块的有几个属性?
// a.js
exports.a = 3;
exports.b = 4;
module.exports.c = 3;
// index.js
var result = require("./a");
console.log(result);有三个属性 {a:3, b:4, c:5}
开始给 exports 对象加了 a, b 两个属性,module.exports 是一个对象
var module = {
exports: {a:3, b:4}
}
又通过 module.exports 给对象加了一个属性 c
var module = {
exports: {a:3, b:4, c:5}
}
最终返回的,缓存都是 module.exports
3. 导出几个属性?
// a.js
exports.a = 3;
exports.b = 4;
module.exports = {
  c: 5
};
// index.js
var result = require("./a");
console.log(result);导出一个 {c:5}
开始给对象里面加了 a, b 两个属性,
然后通过 module 找到 exports 给它重新赋值了一个新的对象,
之前加的两个属性 a b 就没有用了
4. 导出几个属性?
// a.js
module.exports = {
  c: 5
};
exports.a = 3;
exports.b = 4;
// index.js
var result = require("./a");
console.log(result);导出的是一个对象 {c:5}
因为,一开始 module.exports 就指向了一个新对象,
exports 指向的还是之前的对象,
而最终导出的是 modules.exports
总结
只要给 module.exports 重新赋值,就跟 exports 毫无关系了,
execModule 函数的 this 绑定的也是 exports,所以跟 this 也没有关系了
5. result.a 输出的是什么?
// a.js文件
module.exports = function(){}
exports.a = 2;
exports.b = 3;
// index.js
const result = require('./a');
console.log(result.a);输出的是 undefined
因为导出的是 module.exports 是一个函数,和之前的 exports 没有关系了
6. a.js文件里面什么都没有,index.js里面导出的什么?
// a.js文件
// module.exports = function(){}
// exports.a = 2;
// exports.b = 3;
// index.js
const  result = require('./a');
console.log(result);输出的是空对象 {}
5、模块的查找
1. 模块路径以 ./ 或 ../ 开头,是从当前的模块路径查找
2. 如果模块路径不是以 ./ 或 ../ 开头,有两种情况
第一种 看一下是不是内置模块
第二种 是否在 node_modules 目录中
第一种情况看一下是不是内置模块,比如 fs 是 nodejs 自带的内置模块
var fs = require("fs");
console.log(fs); // 打印一个对象,对象里面有很多属性第二种情况看是否在 node_modules 目录中
require("abc");首先 abc 不是内置模块,会当做第三方模块,
所有的第三方模块,统一在 node_modules 目录里面
abc.js 文件 console.log("abc");
先找一下 node_modules 里面没有 abc.js ,如果有 node_modules/abc.js 就找到输出 "abc"
abc/index.js 文件 console.log("abc index");
如果没有 abc.js 会找 node_modules下面 abc 的目录,
找到 node_modules/abc 目录下面的 index.js 文件,
这叫缺省文件名,就是不写文件的名字,写目录的名字,会自动去找目录下面的 index.js
如果都同时存在
node_modules/abc.js
node_modules/abc/index.js
一定是文件优先 abc.js,这是一个软件设计的原则,更加精确的东西,优先级是更高的
7、省略
如果模块路径中省略了后缀名,默认后缀名就是 .js,可以不写.js
如果模块路径中省略了文件名,文件名默认是 index.js
比如 require("abc")
找不到 abc.js,就找 abc 目录下面的 index.js
三、npm
首先修改源
npm config set registry https://registry.npmmirror.com
npm init  初始化,生成package.json 文件
npm init -y 如果目录没有中文空格,就是纯英文加短横线,也可以用这种方式,所有的保持默认
初始化其实就是帮我们生成一个 package.json,我们手动加这个文件也可以
npm i lodash 安装依赖
npm i 还原依赖
npm i -D 表示开发依赖
npm i --production 安装 package.json 记录的第三方库,但不安装 devDependencies
为了让 vscode 更好的对 node 代码进行智能提示,建议安装第三方库 @types/node
npm i -D @typs/node
四、nodejs 的内置模块
|- nodeStu
|- sub
| |- a.js
|
|- test.txt 《邓哥是个好人》
|- index.js
path.resolve(...pathsegments);
1. path 模块返回一个对象,该对象里有很多方法,
2. 其中 resolve() 方法可以把多个路径拼接成一个路径,返回一个字符串
var path = require("path");
var result = path.resolve("./sub", "a.js")
console.log(result); // C:\Users\summer\Desktop\nodeStu\sub\a.js注意
path.resolve("./sub", "a.js")
注意 "./sub" 这个 ./ 的含义是当前活动目录,就是命令行的路径,
比如在 Desktop 运行 C:\Users\summer\Desktop>node nodeStu/index.js
返回的路径和上面不一样了 C:\Users\summer\Desktop\sub\a.js
获取某一个模块的绝对路径,比如,找 a.js 的绝对路径
__dirname 表示当前 index.js 所在的目录,
然后从该目录出发去找 sub/a.js
var path = require("path");
var result = path.resolve(__dirname, "sub/a.js"); //  "./sub/a.js" 这个 ./ 是以 __dirname 为出发点,当然也可以不写这个 ./
console.log(result); // C:\Users\summer\Desktop\nodeStu\sub\a.js这样的好处是,
1. 在任何目录运行,
C:\Users\summer\Desktop\nodeStu>node index
C:\Users\summer\Desktop>node nodeStu/index.js
2. 都得到一样的结果
C:\Users\summer\Desktop\nodeStu\sub\a.js.js
内置模块fs
fs.readFile(path, callback) 读取文件内容
path 文件的绝对路径
callback 因为读文件有一个过程,要等读硬盘,所以用回调函数的方式,而不是返回值
err 表示读的文件有没有错误
content 读取文件的内容
var fs = require("fs");
var path = require("path");
var filename = path.resolve(__dirname, "test.txt");
fs.readFile(filename, "utf-8", function(err, content){
  console.log(content);
})如果不写第二个参数文件编码 utf-8,输出的是一个Buffer 二进制格式
五、Mongodb
数据分为三大类
1. 关系型数据库 mysql、sqlserver、oracle等
2. 非关系型数据库 mongodb、redis等,最近几年慢慢流行起来的
3. 面向对象数据库 db4o,非常非常少见,一般超大型的系统会用的
非关系型数据库的特点
1. 大量数据的存取速度快,
速度是关系型数据库的好几倍,甚至几十倍,
存数据,取数据的速度是非常快的,效率非常高
2. 使用简单,学习成本低
3. 难以表达复杂的数据关系
非关系型数据库又分为很多子类别
比如键值对型,文档型等,mongodb是非关系型数据库中的文档型数据库
1、安装 mongodb
https://www.mongodb.com
Community Server 下载社区免费版本
Enterprise Server 商业版本
安装过程中把下面这个钩去掉
Install MongoDB Compass 这是一个UI可视化管理器,不然下载会非常慢
默认安装目录 c:/Program Files/MongoDB/Server/4.2/bin
mongo.exe 双击运行,在弹出窗口就可以写代码了
>show dbs 查看数据库,安装后默认有三个数据库
amdin
config
local
我用的是绿色版 32位 v2.4.6
下载地址 https://xiazai.zol.com.cn/detail/44/433223.shtml
启动方式
mongod.exe --install --logpath=D:\SoftWare\mongodb\mongodb2.4.6\log\log.txt --dbpath=D:\SoftWare\mongodb\mongodb2.4.6\datadb
Tue Oct 08 06:39:16.231 Tue Oct 08 06:39:16.231 warning: 32-bit servers don't have journaling enabled by default. Please use --journal if you want durability. Tue Oct 08 06:39:16.231 Tue Oct 08 06:39:16.231 Trying to install Windows service 'MongoDB' Tue Oct 08 06:39:16.231 Service 'MongoDB' (Mongo DB) installed with command line 'D:\SoftWare\mongodb\bin\mongod.exe --logpath=D:\SoftWare\mongodb\log\log.txt --dbpath=D:\SoftWare\mongodb\datadb --service' Tue Oct 08 06:39:16.231 Service can be started from the command line with 'net start MongoDB'
net start mongodb 启动
net stop mongodb 停止
mongedb 管理工具 Robo 3T
https://robomongo.org/
以前点击 Download Robo 3T 下载,现在变成 Download Studio 3T today
也可以不安装管理工具,找到 mongedb/bin 目录下运行 mongo.exe 文件,直接输入 mongodb 语句
use test;
--查看当前数据库的集合
show collections
-- 插入文档
-- db.集合名称.insert(document);
-- db.stu.insert({name:'gj',gender:1});
-- 查看
-- db.getCollection(集合名).find({}); 
db.getCollection('users').find({});
db.getCollection('news').findById('6678be660da3271fdcf49149');
-- 删除users集合
-- db.集合名.drop();
db.user.drop();附:
https://blog.csdn.net/weixin_43512977/article/details/131777349 增删改查基本操作
https://blog.csdn.net/u012130609/article/details/76228518 基本增删改查语法,写的比较简洁
https://www.jianshu.com/p/cf0c616e6189 使用手册
https://blog.csdn.net/qq_38280242/article/details/123851413 文章里面管理工具的ui设计感不错
https://blog.csdn.net/weixin_54607676/article/details/124541427 官方的mongDB连接
解决无法启动的问题
https://developer.aliyun.com/article/1086736
不同的 Mongoose 版本,对应不同的依赖
https://www.midwayjs.org/en/docs/2.0.0/extensions/mongo
PHP下的管理工具 phpMoAdmin-MongoDB
https://blog.csdn.net/gitblog_00315/article/details/141881343
2、核心概念
db:数据库
collection:集合,就一个仓库里面可以存放各种集合
document:文档,每个集合中的文档,类似于js中的对象,而且每个对象不一定是一样的
Primary key 主建,每个文档的唯一编号,类似于身份证
field:字段,文档中的字段,类似于对象中的属性
db数据库、collection集合、document文档,它们之间的关系
|- mongodb 里面可以创建很多数据库
|
| db 数据库,可以创建很多数据库
|- movie 电影数据库
|- schooll 学校数据库
|
| collections 集合,一个数据库里面可能会有很多的集合
|- books 书籍的集合
|- teachers 老师的集合
|
| documents 文档,每一个集合里面有很多的文档
|- {
name: "成哥",
age: 10,
students: [...],
address: {...},
}
{
name: "邓哥",
age: 90,
students: [...],
address: {...},
loves: ["香菜", "秋葵"] 每个文档不一定是一样的,邓哥就多了loves
}
六、在Node中使用Mongodb
在 node 中使用 mongodb 需要安装一个第三方库 mongoose
npm i mongoose@4 mongoeb v2.4.6 必须安装 mongoose4 的版本
袁老师的课程中的版本
mongodb 4.2.7
mongoose 5.9.15
我用的版本
node v12.22.12
mongodb v2.4.6
mongoose ^4.13.21
Ps:
cmd控制台有卡住的情况,右击菜单【属性】-【选项】-【快速编辑模式】去掉勾
通过mongoose 操作数据库有三个步骤
1. 创建连接
2. 定义 Schema 和 Model
3. CRUD 操作数据库,增删改查
示例代码
转 https://blog.51cto.com/u_16213417/7419089
const mongoose = require('mongoose');
// 建立连接 
mongoose.connect('mongodb://localhost/my_database', {
  useNewUrlParser: true
});
// 定义数据模型
const UserSchema = new mongoose.Schema({
  name: String,
  age: Number,
});
// 创建数据模型
const User = mongoose.model('User', UserSchema);
// 查询数据 
User.find({}, (err, users) = > {
    if (err) throw err;
    console.log(users);
  }
);
//关闭连接 
mongoose.connection.close();4、下面是课程中实现的功能
|- project
|- models 跟数据库相关的模型
| |- createConnection.js 创建连接,执行一遍就完事了,不要导出任何东西
| |- News.js
| |- User.js
| |- index.js
|
|- services 封装一些函数,可以用这些函数方便的操作数据库
| |- serviceUser.js
| |- serviceNews.js
| |- index.js
|
|- index.js
1. 创建连接
主要这两句代码
mongoose.connect("mongodb://localhost/test") 连接数据库test,如果没有会创建一个
mongoose.connection.on("open", callback) 当连接成功的时候运行回调函数,表示连接已经打
该模块不需要导出任何东西
models/createConnection.js
var mongoose = require("mongoose");
mongoose.set("useCreateIndex", true); // 新版本对索引的处理方式有所变化,无此代码会有警告
mongoose.Promise = global.Promise; // 自己加的
mongoose.connect("mongodb://localhost/test", {
  useMongoClient: true, // 自己加的
  useNewUrlParser: true, // 新版本对连接字符串的解析有更好的支持,无此代码会有警告
  useUnifiedTopology: true, // 新版本对数据库的监事引擎有更好的支持,无此代码会有警告
});
mongoose.connection.on("open", () => {
  console.log("连接已打开");
});2. 定义 Schema 和 Model
mongodb 里面有这两个概念,只不过比较弱,在 mongoose 库里面把这两个概念加强了
1. Schema 大概是结构的意思
2. Model 模型
Schema 和 Model 的关系
通过一个 Schema 结构去组成一个 Model 模型,这个模型 Model 对应的是文档 document
什么是模型 Model?
1. 有哪些属性
2. 属性的类型
3. 属性有什么样限制
先通过 Schema 定义模型
1. mongoose 里面的一个构造函数 Schema,
通过 new mongoose.Schema({...}) 创建一个结构对象,
参数里面是结构的配置 loginId、loginPwd、name、age...
2. 定义好结构,返回一个结构对象 userSchema,使用该结构对象定义一个模型,
mongoose.model("模型的名字", 结构对象) 模型的名字就是集合的名字,会自动变成副数,
返回一个结果,后面增删改查都要通过这个结果,这个结果是一个构造函数
3. 后续数据库操作,只需要 User 和 News 这两个模型,所以需要导出两个模型
有 用户 和 新闻 两个模型
定义用户的模型
models/User.js
var mongoose = require("mongoose"); // 这里又导入了mongoose,因为有缓存,不会导致这个第三方库执行两次
// 定义一个用户结构
var userSchema = new mongoose.Schema({
  loginId: { // 登陆账号
    type: String, // 类型是字符串
    required: true, // 必填
    unique: true, // 属性值是唯一的,比如用户的登陆账号是唯一的
    trim: true, // 写入数据时,自动的去掉首位空格
    minlength: 3, // 约束:字符串的最小长度为3
    maxlength: 18, // 约束:字符串的最大长度为18
  },
  loginPwd: { // 登陆密码
    type: String,
    required: true,
    trim: true,
    minlength: 6,
    maxlength: 18,
    select: false, // 后续查询数据的时候,默认情况下一般不要查询密码字段,所以设置不查询该字段
  },
  name: { // 用户名称
    type: String,
    required: true,
    trim: true,
    minlength: 2,
    maxlength: 10,
  },
  age: { // 用户年龄
    type: Number, // 类型是数字
    required: true,
    min: 1, // 年龄的最小值为1
    max: 100, // 年龄的最大值为100
  },
  role: { // 用户角色:管理员 / 普通用户/ VIP 这三个之一
    type: String,
    required: true,
    trim: true,
    enum: ["管理员", "普通用户", "VIP"], // 用户角色是String字符串,值必须是这三个之一
  },
});
// var User = mongoose.model("User", userSchema);
// 通过定义的结构产生一个User模型,有些模型可以共用一个结构
// 由于后续用到的是模型,所以把定义的模型,该表达式的返回结果导出
module.exports = mongoose.model("User", userSchema);袁老师说了一个问题,
用户模块 User.js 导入了一次 mongoose,
连接模块 createConncetion.js 也导入了一个mongoose ,会不会导致这第三方库执行两次?
不会的,因为有缓存,所以它只会执行一次
models/News.js
定义新闻模型
var mongoose = require("mongoose"); 
// 创建一个新闻的结构
var newsSchema = new mongoose.Schema({
  title: {
    type: String,
    required: true,
    trim: true,
  },
  content: {
    type: String,
    trim: true,
  },
  pubDate: {
    type: Date, // 类型的Data
    required: true,
    default: Date.now, // 默认值,可以是一个值,也可以是一个函数
  },
  channel: {
    type: String,
    required: true,
  },
  link: {
    type: String,
    required: true,
  },
});
// var News = mongoose.model("News", newsSchema);
module.exports  = mongoose.model("News", newsSchema);default 表示默认值,可以是一个值或一个函数
default: Date.now
这是一个函数,这样写功能上,由于 default 是一个函数,
比如添加一篇新闻的时候,会调用函数,取出添加新闻时候的时间
default: Date.now()
这样写是一个值,就是把调用函数后返回的值,作为默认值
功能上,这样写时间是定义模型的事件,相当于把定义模型的时间放到默认值这里了
models/index.js
为了使用更加方便,开发的时候通常会进行一下汇总,使用模型只需要导入这个默认的文件就可以了
1. 模型要创建连接,所以肯定要执行一下连接
2. 导出 News 和 User 两个东西
require("./createConnection"); // 执行一次连接
// const News = require("./News"); 先导入
// exports.News = News; 在作为这个汇总模块导出
// 导出的exports.News,来自于 require("./News");
exports.News = require("./News");
exports.User = require("./User");用的时候默认导入的是index.js,导入的是一个对象,可以获取 新闻 和 用户模型
/index.js
var models = require("./models");
console.log(models.User);
console.log(models.News);3. CRUD
CRUD(Create, Retrieve, Update, Delete)简称为增删改查,是对数据库最基本的操作
新增
模型.create(对象)
1. 函数的参数可以是一个对象,可以传一个对象的数组,是新增一个和多个的区别
2. 因为js代码运行在内存,写入到磁盘拖慢了速度,所以是异步的
3. 新增的对象会自动添加两个属性
_id:自动生成,用于表示文档的主键,全球唯一
__v:自动生成,用于表示文档的版本,内部维护,不需要开发者处理
create() 函数创建一个用户信息,当创建完成之后,会运行一个回调函数,该函数有两个参数
err 表示创建中有没有错误
result 表示创建成功的用户对象
/index.js
var models = require("./models");
var mongoose = require("mongoose");
models.User.create(
  {
    loginId: "abcabcefg",
    loginPwd: "1234567",
    name: "Monica",
    age: 18,
    role: "普通用户",
  }, 
  function(err, result){
    if(err){
      console.log(err);
      mongoose.connection.close();
    }else{
      console.log("创建完成", result);
      mongoose.connection.close();
    }
  }
);创建用户成功,返回用户对象,
{
_id: 6677ae7d45313d27a85a8d28,
role: '普通用户',
age: 18,
name: 'Monica',
loginPwd: '1234567',
loginId: 'abcabcefg',
__v: 0
}
自动生成的两个属性
_id: 6677ae7d45313d27a85a8d28 表示文章的主键,根据 时间戳 + MAC + 进程编号 + 自增量 保持全球唯一,绝对不会重复
__v mongoDB 内部它自己控制的,新增是0,后面有了一些操作1 2 3...
ES7 的方式
var models = require("./models");
var mongoose = require("mongoose");
async function test(){
  try{
    var result = await models.User.create({
      loginId: "chenggee",
      loginPwd: "123456",
      name: "Monica",
      age: 18,
      role: "普通用户",
    }); 
    console.log(result);
    mongoose.connection.close();
  }catch(err){
    console.log('错误代码:' , err);
    mongoose.connection.close();
  }
}
test();MongooseError [ValidationError]: User validation failed: role: Path `role` is required
ValidationError 表示验证错误,
定义模型的时候,有一个大堆规则,role 必须要填写
Error [MongoError]: E11000 duplicate key error index: test.users.$loginId_1 dup key: { : "abcabce" }
重复提交报错
因为 loginId 字段是唯一的
批量导入测试数据,先导入两个 json 文件
var models = require("./models");
var mongoose = require("mongoose");
var users = require("../db/users.json");
var news = require("../db/news.json");
async function addUsers(){
  try{
    var result = await models.User.create(users); // 参数是一个数组,数组的每一位是对象
    console.log('添加用户测试数据成功');
    mongoose.connection.close();
  }catch(err){
    console.log('添加用户测试数据失败');
    mongoose.connection.close();
  }
}
async function addNews(){
  var result = await models.News.create(news);  // 同上
  console.log('添加新闻测试数据成功');
  mongoose.connection.close();
}
// addUsers();
addNews();查询
袁老师讲了三种查询方式
1. 模型.findById(id)
据id字符串查询单个文档,查询不到返回null
var models = require("./models");
var mongoose = require("mongoose");
async function test(){
  var result = await models.News.findById('6678be660da3271fdcf4913d');
  console.log(result._doc);
  mongoose.connection.close();
}
test();2. 模型.find(filter, [projection], [options])
根据 投影、配置 条件过滤,可以查询多个
过滤的条件是一个对象,下面是常见的几种写法
// 查询“财经焦点”的频道的新闻
{
  channel: "财经焦点"
}
// 频道是“财经焦点”,并且要title中包含的“中国”的新闻
{
  channel: "财经焦点",
  title: /中国/, // 这是正则表达式,模糊查询
}
// 查询“财经焦点”的频道的新闻,或者title中包含的“中国”的新闻
{
  $or:[
    {
      channel: "财经焦点",
    },
    {
      title: /中国/,
    },
  ]
}
// 查询所有 发布日期 大于等于 昨天此时 的新闻
// $gt大于  $gte大于等于  $lt小于  $lte小于等于  $ne不等于 
// $in其值在某个数组中  $nin其值不在某个数组中
{
  pubDate: {
    $gte: Date.now() - 3600 * 24 * 1000,
  }
}查询结果是一个数组,数组里面是一个一个的对象
var models = require("./models");
var mongoose = require("mongoose");
async function test(){
  var result = await models.News.find({
    channel: "财经焦点",
  });
  console.log(result);
  console.log(result.length);
  mongoose.connection.close();
}
test();或者关系(默认是并且关系),channl:财经焦点 或者 title包含“中国”,要么是财经焦点,要么是标题里面包括中国
async function test(){
  var result = await models.News.find({
    $or:[
      {
        channel: "财经焦点",
      },
      {
        title: /中国/,
      },
    ]
  });
  console.log(result);
  console.log(result.length);
  mongoose.connection.close();
}$gte大于等于,
Date.now() - 3600 * 24 * 1000 当前时间戳减一天的毫秒数,
就是发布日期要大于昨天的时间,昨天之后发布的
async function test(){
  var result = await models.News.find({
    pubDate: {
      $gte: Date.now() - 3600 * 24 * 1000, 
    }
  });
  console.log(result);
  console.log(result.length);
  mongoose.connection.close();
}第一个参数 filter 是对象,
第二个参数 [projection] 是字符串,表示在查询结果中进行投影投影,就是获取想要的字段
"title pubDate" // 仅查询_id title pubDate "-content" // 除了 content 都要查询
下面只查询两个字段的数据,id会自动加进了
async function test(){
  var result = await models.News.find({
    $or:[
      {
        channel: "财经焦点",
        title:  /中国/,
      },
    ]
  }, 
  "title pubDate"
);
  console.log(result);
  console.log(result.length);
  mongoose.connection.close();
}[options]一些额外的配置,相对于mysql的 by pubDate desc limit 0, 2
async function test(){
  var result = await models.News.find(
    {},
    "title pubDate",
    {
      skip: 0, // 跳过0条
      limit: 2, // 取出两条,如果值为1就是最新的一条新闻
      sort: "-pubDate", // 按照发布时间排序,+pubDate生序(默认),-pubDate降序
    }
  );
  console.log(result);
  console.log(result.length);
  mongoose.connection.close();
}3. 模型.count() 查数量
袁老师的版本里面改成 countDocuments()
async function test(){
  var result = await models.News.count();
  console.log(result); // 120
  mongoose.connection.close();
}可以跟条件,比如财经焦点频道的新闻数量
async function test(){
  var result = await models.News.count({
    channel: "财经焦点",
  });
  console.log(result); // 25
  mongoose.connection.close();
}更新
模型.updateOne(filter, doc)  更新单个文档
模型.updateMany(filter, doc) 更新多个文档
filter 和查询中filter含义完全一样
doc 更新的新文档的属性
更新 id 为 '6678be660da3271fdcf4918e' 这篇新闻的 title 标题
返回 {n:1, nModified:1, ok:1}
var models = require("./models");
var mongoose = require("mongoose");
async function update(){
  var result = models.News.updateOne(
    {
      _id:  '6678be660da3271fdcf4918e',
    },
    {
      title: "oo《信条》主演:剧本太复杂真的看不懂oo",
    }
  );
  console.log(result);
  mongoose.connection.close();
}
update();updateOne 条件匹配多个,它也更新也
updateMany 是条件匹配多少,就更新多少了
删除
模型.deleteOne(filter)  删除单个
模型.deleteMany(filter) 删除多个
4、练习
编写下面两个模块,实现对应的函数
userService 模块
newsService 模块
services/userService.js
const User = require("../models").User;
// 注册一个用户
// userObj:用户对象
// 返回:新注册的用户对象
exports.reg = async function(userObj){
  const result = await User.create(userObj);
  return result;
}
// 登录
// loginId: 账号
// loginPwd: 密码
// 返回:登录成功返回用户对象,登录失败返回null
exports.login = async function(loginId, loginPwd){
  const result = await User.find({
    loginId,
    loginPwd
  });
  if(result.length === 0){
    return null;
  }
  return result[0]; // 返回数组的第一项,是用户对象
}
// 查找用户
// id: 用户的唯一编号
// 返回:用户对象,用户不存在返回null
exports.getUser = async function(id){
  const result = await User.findById(id);
  if(result.length === 0){
    return null;
  }
  return result;
}services/newsService.js
const News = require("../models").News;
// 查询新闻,按照发布日期降序排序
// page: 页码
// limit: 页容量
// keyword: 关键字,标题、内容、频道包含该关键字均可
// 返回:查询结果对象 {  total: 总数据量,  datas: 新闻数组 }
exports.getNews = async function(page, limit, keyword){
  // 正则表达式是一个对象,这种方式可以用字符串作为正则表达式的内容
  let reg = new RegExp(keyword); 
  // 查询条件
  const filter = {
    $or: [{title: reg}, {content: reg}, {channel: reg}]
  };
  // 新闻数组
  let datas = await News.find(filter, "title channel content", {
    sort: "-pubDate",
    skip: (page - 1) * limit,
    limit: limit,
  });
  let arr = [];
  for(item of datas){
    // console.log(item._doc)
    arr.push(item._doc);
  }
  // 总数量
  let total = await News.count(filter); // 高版本中用 countDocuments 方法
  return {total, datas:arr}
}
//  分页
// 1 10    skip:   0    limit: 10
// 2 10    skip: 10    limit: 10
// 3 10    skip: 20    limit: 10
// 4 10    skip: (4 - 1 ) * 10    limit: 10services/index.js
exports.userService = require("./userService");
exports.newsService = require("./newsService");七、express
1、模板字符串
es6 模板字符串解决了两个问题
换行
拼接数据
var a = 3;
var b = 4
var str = "agas" + a + "dfa\nsdf" + b + "asdf";
var es6Str = `agas${a}dfa
sdf${b}asdf`;
console.log(str);
console.log(es6Str);2、http协议
“服务器程序”是一个应用程序,跟QQ、微信、浏览器没有本质的区别,它就是一个程序而已,所以我们平时说的“服务器”不是一台计算机,说的是一个程序
服务器是一个程序,浏览器也是一个程序,
两个程序之间进行数据传输,我们称之为网络通信,通信的时候需要满足一个协议
什么是协议?
协议是一个标准,规定了双方怎么说话,就是双方约定的一种格式,这个格式的标准就是协议
http是大部分场景下客户端和服务器的通信协议,它规定了双方传输的内容格式和方式
