第一天
什么是跨域
就是协议,域名,端口必须相同,只要有一个不同就是跨域
node的安装
下载地址:https://nodejs.org/zh-cn/
查看安装是否成功
安装好后,在终端中查看node版本
node -v
安装node时,会自动安装npm,可在终端中查看npm版本
npm -v
安装插件:Code Runner(就可以用Run Code调用了)
想要出现对node代码的提示,安装第三方库node (输入代码npm i @types/node)
如何运行,右键,在终端中打开;
认识node的全局对象global
像浏览器的全局对象:window
Node的全局对象:global
- global.console
输出 - global.setTimeout
//延迟2秒后打开
setTimeout(function() {
console.log("123");
}, 2000);
- global.setInterval
//每隔两秒执行一次代码,停止按Ctrl+c;
setInterval(function() {
console.log("123");
}, 2000);
认识CommonJS:
CommonJS规范内容:
- 一个js文件就是一个模块
- 模块内所有的变量均为局部变量,不会污染全局
- 模块中需要提供给其他模块使用的内容需要导出
- 导出使用exports.xxx = xxx或module.exports=xxx或this.xxx=xxx
- 其他模块可以使用require函数导入
不同模块之间相互调用:
需要先导出数据,才可以在另外一个模块中调用
导出后被调用的模块会自身执行一次
//a模块
//三种导出方法
var a = 3;
//第一种
exports.a = 3; //导出{ a:3 }
exports.b = 123; //导出{ a:3 b:123 }
//第二种
module.exports = 3; //导出3
module.exports = {
//导出{a:"hello"}
a: "hello"
}
//第三种
this.a = 3; //导出{ a:3 }
this.b = 123; //导出{ a:3 b:123 }
//b模块
//需要用require去调用,"./"是返回上级目录
var result = require("./a");
console.log(result);
注意事项:
exports.a = 1;
exports.b = 2;
module.exports.c = 3;
//调用得到的结果为{ a: 1, b: 2, c: 3 },因为exports没有被重新赋值
exports.a = 1;
exports.b = 2;
module.exports = {
c: 3,
}
//调用得到的结果为{ c: 3 },因为exports被重新赋值
module.exports = {
c: 3,
}
exports.a = 1;
exports.b = 2;
//调用得到的结果为{ c: 3 },具体原因看下图(只要module.exports重新赋值,就跟exports毫无关系)
理解
//模块一
module.exports = function(){
};
exports.a = 1;
exports.b = 2;
//模块二
var result = require("./a");
console.log(result.a);
//输出结果为undefined。只要module.exports被重新赋值,exports将不会有用处
//如果模块a啥都没有,模块b调用模块a的东西
var result = require("./a");
console.log(result);
则结果为{
},不会报错
重点,去理解伪代码
// 下面是伪代码
function require(modulePath){
// modulePath 为模块路径
var moduleId = getModuleId(modulePath); // 获取模块的绝对路径
if(cache[moduleId]){
// 是否有缓存
return cache[moduleId];
}
// 没有缓存
// 该函数用于执行一个模块
function execModule(module, exports, __dirname, __filename){
// module 用于导出的对象
// exports 用户导出的对象
// 导入的模块所在目录的绝对路径
// 导入的模块的绝对路径
这里是导入的模块的代码
}
var module = {
exports: {
}
};
execModule.call(module.exports, module, module.exports, 模块目录绝对路径, 模块绝对路径);
cache[moduleId] = module.exports; // 缓存结果
return module.exports;
}
模块的查找
- 模块路径以./或…/开头:从当前模块路径出发查找
- 模块路径不是以./或…/开头
看是否是内置模块
看是否在node_modules目录中
省略
如果模块路径中省略了后缀名,则认为后缀名是.js
如果模块路径中省略了文件名,则认为文件名是index.js
npm的使用
npm config set registry http://registry.npm.taobao.org/ # 配置npm安装源,加速下载
npm init # 使用npm初始化,生成package.json
npm i 第三方库的名称 # 安装第三方库
npm i # 按照package.json的记录安装第三方库
npm i -D 第三方库的名称 # 使用开发依赖的方式安装第三方库
npm i --production # 按照package.json的记录安装第三方库,但不安装devDependencies
先打开终端运行,输入npm config set registry http://registry.npm.taobao.org/
然后输入npm init
之后一直回车就可以了
然后安装包
输入npm i lodash 可以下载一个包
然后就可以使用了
如何使用第三方库
var _ = require("lodash");
var newArr = _.chunk([1, 2, 3, 4, 5, 6], 2);
console.log(newArr);
//输出结果[ [ 1, 2 ], [ 3, 4 ], [ 5, 6 ] ]
内置模块 path
path.resolve(…pathsegments)
该函数可以将多个路径片段合并为一个完整的绝对路径
注意,片段中的./和…/相对的是工作目录
//第一种写法
var path = require("path");
var result = path.resolve("./nodejs", "a.js"); //把多个路径拼接成一个绝对路径
console.log(result);
//第二种写法(主要这样写)
var path = require("path");
var result = path.resolve(__dirname, "nodejs/a.js"); //把多个路径拼接成一个绝对路径
console.log(result);
//输出d:\谷歌下载地\wed\nodejs\nodejs\a.js
内置模块fs
fs.readFile(path, callback)
读取指定文件的内容
var fs = require("fs");
var path = require("path"); //得到绝对路径
var filename = path.resolve(__dirname, "text.txt");
//要写utf-8,不写的话输出是二进制编码
fs.readFile(filename, "utf-8", function(err, content) {
//第一个参数是错误,第二个参数是文件内容
console.log(content);
});
//输出为《你好啊》
第二天
数据库
数据库(DB)是一种数据持久化的技术
它分为三大类,分别是:
- 关系型数据库:mysql、sqlserver、oracle等
- 非关系型数据库:mongodb、redis等
- 面向对象数据库:db4o
非关系型数据库的特点:
- 大量数据的存取速度快
- 使用简单,学习成本低
- 难以表达复杂的数据关系
非关系型数据库又分为很多子类别,mongodb是非关系型数据库中的文档型数据库
mongodb的安装
mongodb
robo 3T
核心概念
db:数据库
collection:集合,类似于js中的数组
document:每个集合中的文档,类似于js中的对象
- Primary Key:主键,每个文档的唯一编号
- field:文档中的字段,类似于对象中的属性
在node中使用mongodb
安装mongoose
npm i mongoose
- 创建连接
var mongoose = require("mongoose");
mongoose.set("useCreateIndex", true); // 新版本对索引的处理方式有所变化,无此代码会有警告
mongoose.connect("mongodb://localhost/test", {
//连接,(主机名localhost),(数据库名称test),最主要的
useNewUrlParser: true, // 新版本对连接字符串的解析有更好的支持,无此代码会有警告
useUnifiedTopology: true, // 新版本对数据库的监事引擎有更好的支持,无此代码会有警告
});
mongoose.connection.on("open", function(){
console.log("连接已打开");
});
任何数据库操作,都必须建立在连接通道之上,因此,操作数据库必须要有数据库连接
- 定义Schema和Model
Schema组成Model,Model对应mongodb中的文档
比如,我们数据库中需要保存两种模型,分别是用户和新闻
// user
{
loginId: "xxxx", // 登录账号
loginPwd: "xxxx", // 登录密码
name: "xxxx", // 用户姓名
age: 18, // 用户年龄
role: "xxx" // 用户角色:管理员、普通用户、VIP 之一
}
// news
{
title: "", // 新闻标题
content: "", // 新闻内容
pubDate: xxx, // 新闻发布日期
channel: "", // 新闻频道
link: "" // 新闻原始链接地址
}
我们需要先对其进行Schema定义,然后通过Schema定义模型
- 用户
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: {
type: String,
required: true,
trim: true,
enum: ["管理员", "普通用户", "VIP"],//用户角色是一个字符串,该字符串的取值必须是"管理员", "普通用户", "VIP"中的一个
},
});
var User = mongoose.model("User", userSchema);//定义一个模型
//操作数据库时,使用模型进行操作User News(如增加删除查询都需要用到)
- 新闻
var newsSchema = new mongoose.Schema({
title: {
type: String,
required: true,
trim: true,
},
content: {
type: String,
trim: true,
},
pubDate: {
type: Date,
required: true,
default: Date.now,//默认值,可以使一个值,也可以是一个函数(函数的返回值作为默认值)
},
channel: {
type: String,
required: true,
},
link: {
type: String,
required: true,
},
});
var News = mongoose.model("News", newsSchema);
CRUD
CRUD(Create, Retrieve, Update, Delete)简称为增删改查,是对数据库最基本的操作
- 新增
模型.create(对象)
对象可以是单个对象或者是数组
该操作是异步的,可以使用回调函数或ES7的await关键字得到新增的结果
新增的对象会自动添加两个属性:
- _id:自动生成,用于表示文档的主键,全球唯一
- __v:自动生成,用于表示文档的版本,内部维护,不需要开发者处理
- 查询
模型.findById(id); // 根据id字符串查询单个文档,若查找不到,则返回null
模型.find(filter, [projection], [options]); // 根据条件、投影、配置 进行查询
filter
- 过滤条件对象
- api极其丰富
- 下面是一些常见filter写法
// 查询所有 channel="财经焦点" 的新闻
{
channel: "财经焦点"
}
// 查询所有 channel="财经焦点" 并且 title 包含 中国 的新闻
{
channel: "财经焦点",
title: /中国/
}
// 查询所有 channel="财经焦点" 或者 title 包含 中国 的新闻
{
$or: [
{
channel: "财经焦点",
},
{
title: /中国/,
},
],
}
// 查询所有 发布日期 大于等于 昨天此时 的新闻
// $gt 大于 $gte 大于等于 $lt 小于 $lte 小于等于 $ne 不等于
// $in 其值在某个数组中 $nin 其值不在某个数组中
{
pubDate: {
$gte: Date.now() - 3600 * 24 * 1000,
}
}
projection
- 可选参数
- 对象
- 一些额外的配置
// 跳过结果中的 5 条数据,取 6 条
{
skip: 5,
limit: 6,
}
// 按照发布日期的降序排序
{
sort: "-pubDate"
}
模型.count(filter); // 获取指定条件的数量
- 更新
模型.updateOne(filter, doc); // 更新单个文档
模型.updateMany(filter, doc); // 更新多个文档
filter:条件,和查询中的filter含义和用法完全一致
doc:新的文档,新文档中的属性会覆盖旧文档中的对应字段
- 删除
模型.deleteOne(filter); // 删除单个文档
模型.deleteMany(filter); // 删除多个文档
实例:
先创建一个文件夹
连接数据库代码:
项目代码:
归纳代码:
添加数据
- 第一种写法
- 第二种写法(ES7写法)
从json文件中添加数据
json文件代码
添加数据代码
- 查询代码:
- 查询单个
//查询单个
async function test() {
var result = await models.User.findById("5ecc9f2496fd6f0f20d5498f");
console.log(result);
}
test();
- 条件查询
var models = require("./models"); //导出
//查询单个
async function test() {
var result = await models.News.find({
//查询channel=财经焦点的数据
channel: "财经焦点",
});
console.log(result);
}
test();
var models = require("./models"); //导出
//查询单个
async function test() {
var result = await models.News.find({
//查询的时间大于等于这个时间
pubDate: {
$gte: Date.now() - 3600 * 24 * 1000,
}
});
console.log(result);
}
test();
第二个参数
var models = require("./models"); //导出
//查询单个
async function test() {
var result = await models.News.find({
//查询channel=财经焦点的数据
channel: "财经焦点",
}, "title pubDate");//只显示title pubDate这两行的数据
console.log(result);
}
test();
第三个参数
var models = require("./models"); //导出
//查询单个
async function test() {
var result = await models.News.find({
//查询channel=财经焦点的数据
channel: "财经焦点",
}, "title pubDate", {
//跳过0个,取两个数
skip: 0,
limit: 2,
}); //只显示title pubDate这两行的数据
console.log(result);
}
test();
获取最新消息
var models = require("./models"); //导出
//查询单个
//获得最新发布的新闻(1条)
async function test() {
var result = await models.News.find({
//查询channel=财经焦点的数据
}, "title pubDate", {
//用降序
sort: "-pubDate",
limit: 1, //取第一条
}); //只显示title pubDate这两行的数据
console.log(result);
}
test();
- 第三个查询,查数量
var models = require("./models"); //导出
async function test() {
var result = await models.News.countDocuments();
console.log(result);
}
test();
//查指定条件的
- 更新数据
var models = require("./models"); //导出
async function test() {
//吧id等于5ecc9ff46d5c662c28034ba9的文档进行更新
//用{channel:"财经焦点"}覆盖旧文档
var result = await models.News.updateOne({
_id: "5ecc9ff46d5c662c28034ba9",
}, {
channel: "财经焦点",
})
console.log(result);
}
test();
- 删除数据
第三天
http协议
客户端要连接 服务器,需要使用一个url地址来定位服务器,该url地址的格式为:
protocal://hostname:port/path?query#hash
例如:http://localhost:9527/news?page=1&limit=10#2
他们各个部分分别是:
protocal
:http
//协议hostname
:localhost
//主机名,localhost是本机的意思port
:9527
,如果没有写端口号,默认为80path
:/news
//表示我想获取新闻query
:?page=1&limit=10
,表示有两个信息传递过来{page:1, limit:10}
hash
:2
,hash一般用作锚链接,服务器一般不需要这个信息
如果url
地址成功的找到了服务器,客户端会组装一个特别的消息格式发送给服务器,称之为请求
请求内容格式示例:
GET /news?page=1&limit=10#2 HTTP/1.1
Host: localhost:9527
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) ...
Connection: keep-alive
Accept-Encoding: gzip, deflate
<没有消息体>
服务器收到客户端的消息后,会处理该消息,然后返回给客户端一个消息,称之为响应
响应内容格式示例:
HTTP/1.1 200 OK
Content-Type: text/html;charset=utf-8
Server: Express
Cache-Control: no-cache
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<h1>新闻页面</h1>
</body>
</html>
搭建web服务器
web服务器的基本功能:
- 能够监听计算机的某个端口
- 当客户端有请求发送过来时,能够做出相应的处理——处理请求
各种后端技术均可以搭建web服务器,同一种后端技术,搭建web服务器的方式也有多种
在node
环境中,搭建web服务器的方式有:
- 使用
net
或http
内置模块进行搭建 - 使用第三方库,如:
express
、koa2
、egg.js
、nest.js
等
安装
安装express
npm i express
安装nodemon
npm i -D nodemon
创建服务器并监听端口
var express = require("express"); // 导入express
var app = express(); // 创建一个web服务器
app.listen(9527, function(){
// 监听端口
console.log("server listening on 9527");
})
处理请求
// 当请求方法为GET,请求的path为/news时,会交给指定的函数处理,指定的函数称之为 处理函数
app.get("/news", function(req, res){
// req 请求对象,可以获取请求传递过来的信息
// res 响应对象,通过该对象可以控制响应的消息内容
})
// 当请求方法为POST,请求的path为/login时,会交给指定的函数处理
app.post("/login", function(req, res){
})
// 当请求方法为GET,请求的path为/news/ooo/xxx时,会交给指定的函数处理,并把ooo对应到year,把xxx对应到month
app.get("/news/:year/:month", function(req, res){
console.log("req.path", req.path);//获取的格式,如下
})
//要怎么运行,在终端输入npm start
-
req
对象,表示请求对象(request)path
:获取请求的pathmethod
:获取请求行中的请求方法。大部分使用,浏览器都会发出GET
请求,GET
请求一般不会附带请求体,所有的信息都在请求行和请求头中query
:获取请求行中的query
headers
:获取请求头中的键值对params
:获取动态路径部分对应的值
-
res
对象,表示响应对象(response)-
send(data)
:设置响应体,并结束响应 -
header(name, value)
,设置响应头中的键值对,某些键值对会影响到浏览器的行为Content-Type
:告诉浏览器,我给你的响应体是一个什么类型的数据,这个字段会触发浏览器的不同行为text/html
:浏览器会当做页面渲染image/png
:浏览器会当做png
图片渲染application/javascript
:浏览器会当做js
代码text/css
:浏览器会当做css
代码
Locatioin
:见302
消息码
-
type(ext)
,该函数可以快捷的设置Content-Type
res.type('.html') // => 'text/html' res.type('html') // => 'text/html' res.type('json') // => 'application/json' res.type('application/json') // => 'application/json' res.type('png') // => 'image/png'
-
status(code)
,设置响应行中的消息码,不同的消息码会影响到浏览器的行为200
:正常响应404
:资源不存在302
:资源已转移到另外一个地址,另一个地址在响应头的Location
中
-
练习
开发一个静态资源服务器
具体要求:编写一个请求处理函数,该函数能够根据请求的路径响应某个目录中对应的文件,并把文件内容发送给客户端
请求:/
响应文件内容:项目路径/public/index.html
请求:/index.html
响应文件内容:项目路径/public/index.html
请求:/js/index.js
响应文件内容:项目路径/public/js/index.js
可能会用到的函数:
-
path.resolve
-
path.extname(filename)
:获取某个路径的后缀名 -
fs.promises.stat(filename)
:异步函数,需要等待,获取某个文件的状态信息-
若文件不存在,报错
-
返回一个状态信息对象
stats
stats.isDirectory(); // 是否是一个目录 stats.isFile(); // 是否是一个文件
-
-
fs.promises.readFile(filename)
:异步函数,需要等待,获取某个文件的内容
答案:
var express = require("express"); // 导入express
var app = express(); // 创建一个web服务器
var path = require("path");
var fs = require("fs");
/*
/ -> public/index.html
/index.html -> public/index.html
/css/a.css -> public/css/a.css
/js/a.js -> 404
*/
// *表示匹配所有请求
async function handler(req, res) {
//1. 根据请求路径,得到文件路径
// req.path /news public/news
var filename = path.resolve(__dirname, "public" + req.path);
//2. 获取文件的状态信息
try {
var stat = await fs.promises.stat(filename);
if (stat.isFile()) {
// 是一个正常文件
var content = await fs.promises.readFile(filename); //读取文件内容
// 设置响应的消息格式
var extname = path.extname(filename); //获取文件扩展名
res.type(extname);
res.send(content);
} else {
// 是一个目录
console.log("不考虑这种情况");
}
} catch {
// 文件不存在
// 响应404
res.status(404);
res.send("资源不存在");
}
}
app.get("*", handler);
app.listen(9527, function() {
// 监听端口
console.log("server listening on 9527");
});
第四天
express中间件模型
在某些场景中,对请求的处理可能会经过多个步骤,比如:日志记录、安全验证、权限验证、业务处理,为了分割这些不同的处理,express提供了中间件的模型
每个中间件本质上就是一个处理函数,通过app.use
注册
app.use(function(req, res, next){
console.log("中间件1的处理");
next(); //交给下一个匹配的中间件
})
// 注意:use中的路径匹配的是基路径,下面的处理函数可以匹配所有以 /news 开头的路径
app.use("/news", function(req, res, next){
console.log("中间件2的处理");
next(); //交给下一个中间件
})
app.use("/login", function(req, res){
console.log("中间件3的处理,不再往后移交");
})
当我们访问/news/123
时,请求会依次交给中间件1
、中间件2
当我们访问/login/xxxx
时,请求会依次交给中间件1
、中间件3
这样一来,我们就可以把一些通用的逻辑写成中间件,使用app.use
注册即可
常用中间件
中间件就是一个函数
express给我们制作了一些常用的中间件,通过注册这些中间件,可以轻松实现一些通用功能
express.static
// 该函数返回一个中间件
// 它将指定的目录作为静态资源目录
// 当访问服务器时,该中间件会通过 path 对应该目录中的文件
// 如果能够找到文件,则直接响应文件内容,不再向后移交
// 若无法找到文件,向后移交
express.static(dir);
使用示例:
app.use(express.static(path.resolve(__dirname, "public")))
//这行代码运行成功后就不会运行下面的代码了
例子:
var express = require("express"); // 导入express
var app = express(); // 创建一个web服务器
var path = require("path"); //获取绝对路径
// 搭建一个静态资源服务器//就是资源会从这里去找
app.use(express.static(path.resolve(__dirname, "public")));
// Content-Type: application/x-www-form-urlencoded
app.use(express.urlencoded({
extended: true }));
// Content-Type: application/json
app.use(express.json());
// 路由中间件
app.use("/api/user", require("./routes/user"));
app.use("/api/news", require("./routes/news"));
app.listen(9527, function() {
// 监听端口
console.log("server listening on 9527");
});
上面有查询方法,前提是要有这个html界面
express.urlencoded
// 该函数返回一个中间件
// 如果它发现请求头中的 content-type 的值是 application/x-www-form-urlencoded
// 则会把请求体中的内容解析为一个对象,保存到 req.body 中,然后向后移交
// 否则,直接向后移交
express.urlencoded();
使用示例:
// 使用配置 {extended: true},避免报出警告
app.use(express.urlencoded({
extended: true}));
保存文件
express.json
// 该函数返回一个中间件
// 如果它发现请求头中的 content-type 的值是 application/json
// 则会把请求体中的内容解析为一个对象,保存到 req.body 中,然后向后移交
// 否则,直接向后移交
express.json();
使用示例:
app.use(express.json());
保存文件
express.Router
// 该函数返回一个中间件,称之为路由中间件
var router = express.Router();
// 匹配 GET 基路径/
router.get("/", function(req, res){
})
// 匹配 GET 基路径/a
router.get("/a", function(req, res){
})
// 匹配 POST 基路径/
router.post("/", function(req, res){
})
// 当基路径为 /api/user 时,交给该路由处理
// 如果路由没有命中,则它会自动往后移交
app.use("/api/user", router);
使用路由中间件处理请求更加有助于模块拆分
服务器的职责
在不同的场景下,服务器有着不同的职责,极其灵活
很多的中小型系统,一个服务器往往承担着两个职责:
- 提供静态资源
- 提供api接口
我们可以来看一个经典的场景,以梳理服务器和浏览器之间的交互
练习
- 用
express
搭建静态资源服务器,并将给予的静态页面
目录中的内容作为静态资源放到服务器中 - 用
express
路由开发api
接口,规则如下- 针对
用户
的操作- 登录,
POST /api/user/login
,消息体中传递账号和密码 - 注册,
POST /api/user/reg
,消息体中传递注册信息,新注册的用户必定为普通用户
- 登录,
- 针对
新闻
的操作- 分页获取新闻,
GET /api/news
,query
中可能传递page
和limit
- 分页获取新闻,
- 针对
- 开发静态资源中的
/js/practice.js
,想想静态资源中的js是在哪一端运行的,然后补全该文件中下面的函数:getNews
reg
login
登录注册代码:
// 专门处理针对用户的操作
var express = require("express");
var services = require("../services");
var router = express.Router();
router.post("/login", async function (req, res) {
var result = await services.userService.login(
req.body.loginId,
req.body.loginPwd
);
if (result) {
res.send(result);
} else {
res.send({
err: "账号和密码错误",
});
}
});
router.post("/reg", async function (req, res) {
req.body.role = "普通用户";
try {
var result = await services.userService.reg(req.body);
res.send(result);
} catch (err) {
res.send({
err: err.message,
});
}
});
module.exports = router; // 导出路由中间件
2.新闻代码:
// 针对新闻的请求
var express = require("express");
var router = express.Router();
var services = require("../services");
router.get("/", async function (req, res) {
var page = +req.query.page || 1; // 如果没有传递page,默认1
var limit = +req.query.limit || 10; //如果没有传递limit,默认10
var result = await services.newsService.getNews(page, limit, "");
res.send(result);
});
module.exports = router;// 导出路由中间件