七、MongoDB
1、关系型数据库与非关系型数据库的区别
* 关系型数据库特点:
- sql语句增删改查操作
- 保持事务的一致性,事务机制(回滚)
mysql、sqlserver、db2、oracle
* 非关系型数据库特点:
- no sql:not only sql
- 轻量,高效,自由
mongodb、hbase、redis
* 为啥喜欢mongodb?
- 由于mongodb独特的数据处理方式,可以将热点数据加载到内存,
故而对查询来讲,会非常快(当然也会非常消耗内存);
同时由于采用了bson的方式存储数据,
故而对json格式数据具有非常好的支持性以及友好的表结构修改性,
文档式的存储方式,数据友好可见;
数据库的分片集群负载具有非常好的扩展性以及非常不错的自动故障转移
sql术语/概念 |
mongodb术语/概念 |
解释/说明 |
database |
database |
数据库 |
table |
collection |
数据库表/集合 |
row |
document |
数据记录行/文档 |
column |
field |
数据字段/域 |
index |
index |
索引 |
table joins |
|
表连接,mongodb不支持 |
primary key |
primary key |
主键,mongodb自动将_id字段设置为主键 |
2、安装数据库
* https://docs.mongodb.com/manual/administration/install-community/
3、启动数据库
mongod --dbpath d:/data/db
mongo
mongod --config /usr/local/etc/mongod.conf
mongo
4、在命令行中操作数据库
* help查看命令提示
- help
- db.help()
- db.test.help()
- db.test.find().help()
* 创建/切换数据库
- use music
* 查询数据库
- show dbs
* 查看当前使用的数据库
- db/db.getName()
* 显示当前db状态
- db.stats()
* 查看当前db版本
- db.version()
* 查看当前db的链接机器地址
- db.getMongo()
* 删除数据库
- db.dropDatabase()
* 创建一个聚集集合
- db.createCollection("collName",{size:5242880,capped:true,max:5000});
最大存储空间为5m,最多5000个文档的集合
* 得到指定名称的聚集集合
- db.getCollection("account");
* 得到当前db的所有聚集集合
- db.getCollectionNames();
* 显示当前db所有聚集的状态
- db.printCollectionStats();
* 删除
- db.users.drop();
* 添加
- db.users.save({name:"zhangsan",age:25,sex:true});
- db.users.save([{name:"zhangsan",age:25,sex:true},{name:"kerwin",age:100}]);
* 修改
- db.users.update({age:25},{$set:{name:"changeName"}},false,true);
相当于:update users set name="changeName" where age=25;
- db.users.update({name:"lisi"},{$inc:{age:50}},false,true);
相当于:update users set age=age+50 where name="lisi";
- db.users.update({name:"lisi"},{$inc:{age:50},$set:{name:"kerwin"}},false,true);
相当于:update users set age=age+50,name="hoho" where name="kerwin";
* 删除
- db.users.remove({age:132});删除所有:db.users.remove({})
* 查询所有记录
- db.userInfo.find();
相当于:select * from userInfo;
* 查询某字段去重后数据
- db.userInfo.distinct("name");
相当于:select distinct name from userInfo;
* 查询age=22的记录
- db.userInfo.find({age:22});
相当于:select * from userInfo where age=22;
* 查询age>22的记录
- db.userInfo.find({age:{$gt:22}});
相当于:select * from userInfo where age>22;
* 查询age<22的记录
- db.userInfo.find({age:{$lt:22}});
相当于:select * from userInfo where age<22;
* 查询age>=25的记录
- db.userInfo.find({age:{$gte:25}});
相当于:select * from userInfo where age>=25;
* 查询age<=25的记录
- db.userInfo.find({age:{$lte:25}});
* 查询age>=23并且age<=26
- db.userInfo.find({age:{$gte:23,$lte:26}});
* 查询name中包含mongo的数据
- db.userInfo.find({name:/mongo/});
相当于%%:select * from userInfo where name like "%mongo%";
* 查询name中以mongo开头的
- db.userInfo.find({name:/^mongo/});
相当于:select * from userInfo where name like "mongo%";
* 查询指定列name、age数据(想显示哪列,就将字段设置为1,不想显示就设置成0)
- db.userInfo.find({},{name:1,age:1});
相当于:select name,age from userInfo;
* 查询指定列name、age数据,age>25
- db.userInfo.find({age:{$gt:25}},{name:1,age:1});
相当于:select name,age from userInfo where age>25;
* 按照年龄排序(写成数组就是多列查询)
- 升序:db.userInfo.find().sort({age:1});
- 降序:db.userInfo.find().sort({age:-1});
* 查询name="zhangsan",age=22的数据
- db.userInfo.find({name:"zhangsan",age:22});
相当于:select * from userInfo where name="zhangsan" and age=22;
* 查询前5条数据
- db.userInfo.find().limit(5);
相当于:select top 5 * from userInfo;
* 查询10条以后的数据
- db.userInfo.find().skip(10);
相当于:select * from userInfo where id not in (select top 10 * from userInfo);
* 查询在5-10之间的数据
- db.userInfo.find().skip(5).limit(5);
* or与查询
- db.userInfo.find({$or:[{age:22},{age:25}]});
相当于:select * from userInfo where age=22 or age=25;
* 查询第一条数据
- db.userInfo.findOne();
相当于:select top 1 * from userInfo;
相当于:db.userInfo.find().limit(1);
* 查询某个结果集的记录条数
- db.userInfo.find({age:{$gte:25}}).count();
相当于:select count(*) from userInfo where age>=20;
5、可视化工具进行增删改查
* Robomongo Robo3T adminMongo
6、nodejs连接操作数据库
const mongoose = require("mongoose")
mongoose.connect("mongodb://127.0.0.1:27017/company-system")
const mongoose = require("mongoose")
const Schema = mongoose.Schema
const UserType = {
username: String,
password: String,
gender: Number,
introduction: String,
avatar: String,
role: Number
}
const UserModel = mongoose.model("user", new Schema(UserType))
module.exports = UserModel
UserModel.create({
introduction, username, gender, avatar, password, role
})
UserModel.find({username: "kerwin"}, ["username", "role", "introduction", "password"])
.sort({createTime: -1}).skip(10).limit(10)
UserModel.updateOne({
_id
}, {
introduction, username, gender, avatar
})
UserModel.deleteOne({_id})
八、接口规范与业务分层
1、接口规范
* restful架构:服务器上每一种资源,比如一个文件,一张图片,一部电影,都有对应的url地址,
如果我们的客户端需要对服务器上的这个资源进行操作,就需要通过http协议执行相应的动作来操作它,
比如进行获取,更新,删除。
* 简单来说就是url地址中只包含名词表示资源,使用http动词表示动作进行操作资源
* 举个例子:左边是错误的设计,而右边是正确的
- GET /blog/getArticles --> GET /blog/Articles 获取所有文章
- GET /blog/addArticles --> POST /blog/Articles 添加一篇文章
- GET /blog/editArticles --> PUT /blog/Articles 修改一篇文章
- GET /rest/api/deleteArticles?id=1 --> DELETE /blog/Articles/1 删除一篇文章
* 使用方式
- GET http://www.birjemin.com/api/user # 获取列表
- POST http://www.birjemin.com/api/user # 创建用户
- PUT http://www.birjemin.com/api/user/{id} # 修改用户信息
- DELETE http://www.birjemin.com/api/user/{id} # 删除用户信息
* 过滤信息
- 用于补充规范一些通用字段
- ?limit=10 # 指定返回记录的数量
- ?offset=10 # 指定返回记录的开始位置
- ?page=2&per_page=100 # 指定第几页,以及每页的记录数
- ?sortby=name&order=asc # 指定返回结果按照哪个属性排序,以及排序顺序
- ?state=close # 指定筛选条件
2、业务分层
* router.js:负责将请求分发给C层
* controller.js:C层负责处理业务逻辑(V与M之间的沟通)
* views:V层负责展示页面
* model:M层负责处理数据(增删改查)
MVC架构 |
MVC架构 |
MVC架构 |
MVC架构 |
公司老板-接收客户请求(谈业务) |
产品经理-自己啥也不干,负责将业务通知给项目经理 |
项目经理-分析业务逻辑,把任务分给UI和程序员 |
UI-负责看得见的界面 |
公司老板-接收客户请求(谈业务) |
产品经理-自己啥也不干,负责将业务通知给项目经理 |
项目经理-分析业务逻辑,把任务分给UI和程序员 |
程序员-负责看不见的代码(页面的数据处理) |
MVC架构 |
MVC架构 |
MVC架构 |
MVC架构 |
index.js服务器入口文件-负责接收客户端请求 |
router.js路由-负责将index.js接收到的请求分发给C层 |
C层controller-负责处理请求业务逻辑(把数据model交给视图view) |
V层html文件 |
index.js服务器入口文件-负责接收客户端请求 |
router.js路由-负责将index.js接收到的请求分发给C层 |
C层controller-负责处理请求业务逻辑(把数据model交给视图view) |
M层-hero.js负责数据的增删改查 |
九、登录鉴权
1、Cookie&Session
* 【http无状态】我们知道,http是无状态的。也就是说,http请求方和响应方间无法维护状态,都是一次性的,
它不知道前后的请求都发生了什么。但有的场景下,我们需要维护状态。最典型的,一个用户登陆微博,发布、
关注、评论,都应是在登录后的用户状态下的。【标记】那解决方法时什么呢?
* 浏览器 -> post账号密码 -> 服务端
服务端 -> 校验账号密码 -> 库(User)
库(User) -> 校验成功 -> 服务端
服务端 -> 存session -> 库(session)
服务端 -> Set-Cookie:sessionId -> 浏览器
浏览器 -> 请求接口(Cookie:sessionId) -> 服务端
服务端 -> 查session -> 库(session)
库(session) -> 校验成功 -> 服务端
服务端 -> 接口处理 -> 服务端
服务端 -> 接口返回 -> 浏览器
const express = require("express");
const session = require("express-session");
const MongoStore = require("connect-mongo");
const app = express();
app.use(
session({
secret: "this is session", // 服务器生成 session 的签名
resave: true,
saveUninitialized: true, //强制将为初始化的 session 存储
cookie: {
maxAge: 1000 * 60 * 10,// 过期时间
secure: false, // 为 true 时候表示只有 https 协议才能访问cookie
},
rolling: true, //为 true 表示 超时前刷新,cookie 会重新计时; 为 false 表示在超时前刷新多少次,都是按照第一次刷新开始计时。
store: MongoStore.create({
mongoUrl: 'mongodb://127.0.0.1:27017/kerwin_session',
ttl: 1000 * 60 * 10 // 过期时间
}),
})
);
app.use((req, res, next) => {
if (req.url === "/login") {
next()
return;
}
if (req.session.user) {
req.session.garbage = Date();
next();
} else {
res.redirect("/login")
}
})
2、json web token(jwt)
* 负载均衡 -> 机器A-session,session复制,机器B-session -> cluster
* 负载均衡 -> 机器A-session,cluster,机器B-session -> session存储
* 我为什么要保存这可恶的session呢,只让每个客户端去保存该多好?
- Header-{"UserID":"ab14578"} -> 秘钥(HMAC-SHA256) -> 签名
- Header-{"UserID":"ab14578"}-签名 -> 秘钥(HMAC-SHA256) -> 签名(相等吗?)
* 当然,如果一个人的token被别人偷走了,那我也没办法,我也会认为小偷就是合法用户,这其实和一个人的
session id被别人偷走是一样的。
* 这样一来,我就不保存session id了,我只是生成token,然后验证token,我用我的cpu计算时间获取了
我的session存储空间!
* 解除了session id这个负担,可以说是无事一身轻,我的机器集群现在可以轻松地做水平扩展,用户访问量
增大,直接加机器就行。这种无状态的感觉实在是太好了!
* 占带宽,正常情况下要比session_id更大,需要消耗更多流量,挤占更多带宽,假如你的网站每月有10万次
的浏览器,就意味着要多开销几十兆的流量。听起来并不多,但日积月累也是不小一笔开销。实际上,许多人
会在jwt中存储的信息会更多。
* 无法在服务端注销,那么久很难解决劫持问题。
* 性能问题,jwt的卖点之一就是加密签名,由于这个特性,接收方得以验证jwt是否有效且被信任。对于有着
严格性能要求的web应用,这并不理想,尤其对于单线程环境。
* csrf攻击的原因是浏览器会自动带上cookie,而不会带上token;
* 以csrf攻击为例:
- cookie:用户点击了链接,cookie未失效,导致发起请求后后端以为是用户正常操作,于是进行扣款
操作
- token:用户点击链接,由于浏览器不会自动带上token,所以即使发了请求,后端的tokne验证不会
通过,所以不会进行扣款操作
//jsonwebtoken 封装
const jsonwebtoken = require("jsonwebtoken")
const secret = "kerwin"
const JWT = {
generate(value, exprires) {
return jsonwebtoken.sign(value, secret, {expiresIn: exprires})
},
verify(token) {
try {
return jsonwebtoken.verify(token, secret)
} catch (e) {
return false
}
}
}
module.exports = JWT
//node中间件校验
app.use((req, res, next) => {
// 如果token有效 ,next()
// 如果token过期了, 返回401错误
if (req.url === "/login") {
next()
return;
}
const token = req.headers["authorization"].split(" ")[1]
if (token) {
var payload = JWT.verify(token)
// console.log(payload)
if (payload) {
const newToken = JWT.generate({
_id: payload._id,
username: payload.username
}, "1d")
res.header("Authorization", newToken)
next()
} else {
res.status(401).send({errCode: "-1", errorInfo: "token过期"})
}
}
})
//生成token
const token = JWT.generate({
_id: result[0]._id,
username: result[0].username
}, "1d")
res.header("Authorization", token)
//前端拦截
/*
* @作者: kerwin
* @公众号: 大前端私房菜
*/
import axios from 'axios'
// Add a request interceptor
axios.interceptors.request.use(function (config) {
const token = localStorage.getItem("token")
config.headers.Authorization = `Bearer ${token}`
return config;
}, function (error) {
return Promise.reject(error);
});
// Add a response interceptor
axios.interceptors.response.use(function (response) {
const {authorization} = response.headers
authorization && localStorage.setItem("token", authorization)
return response;
}, function (error) {
const {status} = error.response
if (status === 401) {
localStorage.removeItem("token")
window.location.href = "/login"
}
return Promise.reject(error);
});
十、文件上传管理
* multer是一个node.js中间件,用于处理multipart/form-data类型的表单数据,它主要用于上传文件。
* 注意:multer不会处理任何非multipart/form-data类型的表单数据
* npm install --save multer
* multer会添加一个body对象以及file或files对象到express的request对象中。body对象包含表单的
文本域信息,file或files对象包含对象表单上传的文件信息。
//前后端分离-前端
const params = new FormData()
params.append('kerwinfile', file.file)
params.append('username', this.username)
const config = {
headers: {
"Content-Type": "multipart/form-data"
}
}
http.post('/api/upload', params, config).then(res => {
this.imgpath = 'http://localhost:3000' + res.data
})
const multer = require('multer')
const upload = multer({dest: 'public/uploads/'})
//前后端分离-后端
router.post('/upload', upload.single('kerwinfile'), function (req, res, next) {
console.log(req.file)
})
十一、apidoc-api文档生成工具
* apidoc是一个简单的RESTful API文档生成工具,它从代码注释中提取特定格式的内容生成文档。
支持诸如Go、Java、C++、Rust等大部分开发语言,具体可使用apidoc lang命令行查看所有的支持列表。
* apidoc 拥有以下特点:
- 跨平台,linux、windows、macOS等都支持;
- 支持语言广泛,即使是不支持,也很方便扩展;
- 支持多个不同语言的多个项目生成一份文档;
- 输出模板可自定义;
- 根据文档生成 mock 数据;
* npm install -g apidoc
Add some apidoc comments anywhere in your source code:
/**
* @api {get} /user/:id Request User information
* @apiName GetUser
* @apiGroup User
*
* @apiParam {Number} id User's unique ID.
*
* @apiSuccess {String} firstname Firstname of the User.
* @apiSuccess {String} lastname Lastname of the User.
*/
Now generate the documentation from src/ into doc/.
########
$ apidoc -i src/ -o doc/
########
* 注意
- (1) 在当前文件夹下 apidoc.json
########
{
"name": "****接口文档",
"version": "1.0.0",
"description": "关于****的接口文档描述",
"title": "****"
}
########
- (2)可以利用vscode apidoc snippets 插件创建api