如果你玩过微信小程序的云开发或者学过MySQL,这玩意儿相对要好学很多。作为非关系型数据库,MongDB由 C++ 语言编写。旨在为 WEB 应用提供可扩展的高性能数据存储解决方案。MongDB其实使用面还是比较广的,每条记录有点像json数据,一般和node.js在一起作为后端架构,在Java中也有一定的使用空间(主要用于存储可丢失的数据)。好,简单介绍完毕,接下来就是基本的使用介绍了。
首先自然是(从官网下载)了,应该需要注册啥的,这里用的是社区版,企业版应该会收费,学习的话用社区版应该足够了。下载时,选择custom,修改文件路径到E盘,节省C盘空间。下载完成之后配置环境变量(eg.E:\MongoDB\bin
)。配置完成之后:
//查询数据库
show dbs
//创建或者切换数据库
use 数据库名称
//查看当前使用的数据库
db/db.getName()
//显示当前的数据库状态
db.stats()
//查看当前的数据库版本
db.version()
//查看当前数据库连接的主机地址
db.getMongo()
//删除数据库
db.dropDatabase()
//创建集合
db.createCollection("collName",{size:20,capped:true,max:100})
//创建一个名为collName的集合,大小为20(字节,capped表示固定集合,即使是true也需要指定size的值),
//max表示的是集合中包含文档的最大数量,也就是MySQL中常说的一条记录。
//展示当前数据库下面的所有集合
db.getCollectionNames()
//展示当前数据库所有集合的状态
db.printCollectionStats()
//删除集合
db.collection名称.drop()
//假定被操作的集合名为test
//插入数据
db.test.insert({name:"张三",age:22})
//注意,save就是insert的别名,和thinkPHP中的save,insert不一样。
//数据修改
db.test.update({name:"张三",age:22},{$set:{age:19}});
db.test.update({name:"张三",age:22},{$inc:{age:100}});
//对于数据的修改,第一个表示的是匹配文档
//第二个参数是修改内容,其中$set表示设置成新的值,$inc表示值的增加
//第三个参数表示如果没有找到数据就创建数据(默认为false)
//第四个参数表示的是修改全部匹配的文档(默认为false)
//数据删除
db.test.remove({data:1},false)
//注意,这里的集合不能是固定集合(固定集合不能删除数据)
//第二个参数是是否只删除一条数据(默认为true)
//数据查询
db.test.find()//查询所有数据
db.test.distinct('name')//去重查询
db.test.find({data:1})//基本条件查询
db.test.find({data:{$gt:1}})//查询data>1
db.test.find({data:{$gte:1}})//查询data>=1
db.test.find({data:{$lt:1}})//查询data<1
db.test.find({data:{$gt:1,$lt:5}})//查询(1,5)
db.test.find({name:/nightowl/})//查询name中包含有nightowl的数据
db.test.find({name:/^nightowl/})//查询name中以nightowl开头的数据
db.test.find({name:/nightowl$/})//查询name中以nightowl结尾的数据
db.test.find({},{name:0,_id:0})//查询并返回的数据中去除name和_id字段
db.test.find().sort(data:1)//查询并按照data升序排序
db.test.find().sort(data:-1)//查询并按照data降序排序
db.test.find().limit(5)//查询并限制返回数据的条数
db.test.find().skip(3)//查询并跳过指定数据条数
db.test.find({$or:[data:{$gt:1},data:{$lte:-3}]})//查询data(-∞,-3]和(1,+∞]的文档
db.test.findOne()//查询第一条数据
db.test.find({条件}).count()//查询并得到符合条件的文档条数
首先,简单说一下JWT(Json Web Token),主要应用场景是前后端鉴权。为了保护数据库里面的数据,避免非法用户攻击,前端首次登录之后,后端会利用某种加密算法,将用户名(或者其他能够唯一标识用户的信息,当然为了提高安全性,也可能涉及其他信息)生成一个token(这里的token是一般使用的代号,其他名字也可以),将token发送给前端,然后前端在请求数据的时候需要携带token信息,后端会再次加密这些信息,将两次token进行验证,验证通过才进一步调用model层获取数据库里面的数据或者向数据库里面写入数据。
当然更深入的,后端还可以利用像redis一样的高速缓存工具,快速验证前端传过来的token。其基本是实现原理是前端第一次登录,将生成的token存入redis,之后用户再次请求,直接查找redis是否含有先前生成好的token,有便通过验证。下面主要介绍利用JWT实现的对称加密和非对称加密。
首先是对称加密,为了方便更加熟悉RMVC模式(router、model、view、controller),我们回顾一下前面搭建目录的过程:
//server.js导入各种模块
const express = require("express");
const app = express();
const router = require("./route/index");
app.use("/", router);
app.listen(3000, () => {
console.log("localhost:3000.");
});
//route/index.js配置路由模块
const express = require("express");
const router = express.Router();
const loadToken = require("../controller/token");
router.get("/api/token", loadToken);
module.exports = router;
//controller/index.js配置controller控制层
const jwt = require("jsonwebtoken");
const token = (req, res, next) => {
const loadToken = jwt.sign({ username: "张三、李四、王五牛逼!**nightowl**" },
"nightowl"
);
//const result = jwt.verify(loadToken, "nightowl");
res.send(loadToken);
};
module.exports = token;
补充说明,这里需要导入的模块有:express、express-generator(前两者主要是为了引入express极简框架)、jsonwebtoken(引入JWT加密模块)、nodemon(项目热加载,省得每次修改代码之后需要重启)。这里使用的是对称加密,jwt.sgin()
方法含有两个参数,第一个参数是被加密的数据,第二个参数是加密的私钥(补充:所谓的对称加密指的是加密和解密用的私钥和密钥是一样的
)。这里介绍一个(JWT官网),可以用来解密JWT生成的数据。解密效果如下:
其中的蓝色部分是私钥,又被成为数字签证,可以通过右边的输入框来更改。当然jwt自身提供了解密方法jwt.verify()
,这种对称加密其实不算安全,因为如果别人知道了密钥,信息就很容易泄露,加上公钥与私钥一直,增大了泄露的风险。
接下来说一下非对称加密。非对称加密有点类似于git的环境搭建,首先需要生成私钥和公钥,然后将公钥上传到自己的git远程仓库上。在jsonwebtoken中,非对称加密的实现方法为:
(1)(下载openSSL)(用来生成签证);
(2)在cmd中执行 genrsa -out rsa_private_key.pem 2048
,生成2048位私钥;
(3)执行rsa -in rsa_private_key.pem -pubout -out rsa_public_key.pem
生成公钥;
生成完成之后的文件如下所示:
之后修改对应的加密算法:
//controller/token.js
const jwt = require("jsonwebtoken");
const fs = require("fs");
const path = require("path");
const token = (req, res, next) => {
const privateKey = fs.readFileSync(
path.join(__dirname, "../rsa_private_key.pem")
);
const publicKey = fs.readFileSync(
path.join(__dirname, "../rsa_public_key.pem")
);
const loadToken = jwt.sign({ username: "张三" }, privateKey, {
algorithm: "RS256",
});
const result = jwt.verify(loadToken, publicKey);
console.log("输出解密之后的数据:", result);
res.send(loadToken);
};
module.exports = token;
程序运行结果如下:
里面展示的时间iat其实就是时间戳,对应当前密文生成的时间。在策略上,可以将公钥发送给后端,当前端上前数据时先用私钥加密,等数据到达后端之后,利用后端传入的公钥解密数据或者两次加密数据看数据是否相等。
socket编程更偏向于计算机网络数据传输方面的底层知识,这里可以作为了解内容,因为现在框架对于socket的封装程度已经比较高了。数据传输分为双工通信、单工通信和半双工通信,其中单工通信就类似于数码电视,只接受另外一段传输过来的数据,双工通信就类似于QQ,信息的接受和发送能够同时进行,半双工通信类似于老式手机“大哥大”,需要一段给出回应才能够继续发送。接下来简单说说如果撤掉各种框架的封装,使用最底层的socket如何实现数据通信:
socket属于node.js中内置模块Net模块,程序举例如下:
//server.js
const net = require("net");
const server = net.createServer((socket) => {//创建服务端,创建之后像连接的客户端发送信息
socket.write("hello client!");
socket.on("data", (chunk) => {
console.log("来自客户端的数据:", chunk.toString());
});
});
server.on("error", (err) => {
console.log("服务端错误", err);
});
server.listen("8080", () => {//设置服务端对外开放的端口号
console.log("opened server on:", server.address());
});
//client.js
const net = require("net");
const client = net.createConnection({ port: 8080 }, () => {
//建立连接,这里使用的主机一致,所以不涉及ip信息
console.log("connect to server!");
client.write("hello server!");
});
client.on("data", (chunk) => {
console.log("来自服务端的数据:", chunk.toString());
});
client.on("error", (err) => {
console.log("客户端错误", err);
});
//server.js
const net = require("net");
const server = new net.createServer();
let clients = {};
let clientName = 0;
server.on("connection", (client) => {
client.name = ++clientName;
console.log("客户端NO", client.name, "获得与server的连接!");
clients[client.name] = client;
client.on("data", (msg) => {
broadcast(client, msg.toString());
});
//绑定数据监听,每有数据获取之后广播
client.on("close", () => {
delete clients[client.name];
console.log("客户端NO" + client.name, "与server断开连接!");
});
});
function broadcast(client, msg) {
for (var key in clients) {
if(key!=client.name){
clients[key].write("客户端NO" + client.name + "说:" + msg);
}
}
}
server.listen(8080, () => {
console.log("server 开启!");
});
//client.js
const net = require("net");
const readline = require("readline");
let socket = new net.Socket();
const port = 8080;
const host = "127.0.0.1";
socket.setEncoding = "UTF-8";
socket.connect(port, host, () => {
socket.write("hello.");
});
socket.on("data", (msg) => {
console.log(msg.toString());
say();
});
const r1 = readline.createInterface({
input: process.stdin,
output: process.stdout,
});
function say() {
r1.question("请输入向server传输的数据:\n", (inputMsg) => {
if (inputMsg != "bye") {
socket.write(inputMsg + "\n");
} else {
socket.destroy();
r1.close();
}
});
}
socket.on("error", (err) => {
console.log("client error", err);
socket.end();
});
难点,数据之间的传输,一个传输对应一个监听处理,坑点:wirte()
函数不能直接传输对象,因此不能使用,
来凭借多个字符串。
实现效果如下:
除了接近底层外,socket中我们能够发现一个特点,server端可以直接向服务端发送数据,也就可以解决消息推送等问题,实现真正意义的双工通信,http是不能够做到这一点的,http协议下只能由client请求。讲道理我之前还没深入到WebSocket,可以看看(阮老师的博文),看到WebSocket之后才发现自己前端也就学了点皮毛,WebSocket的协议标识为ws
,加密之后用的是wss
,无同源策略。现在还学的比较少,应该用不上这么多,但是需要知道有这个东西。需要注意的是web浏览器中由WebSocket,在node.js中如果需要用到WebSocket,需要使用第三方依赖,安装方式:npm install ws -S
,程序代码如下:
服务端代码:
//server.js
const webSocket = require("ws");
const ws = new webSocket.Server({ port: 8080 });
let clients = {};
let num = 0;
function broadcast(client, msg) {
for (let key in clients) {
clients[key].send("来自客户端NO" + client.name + "的数据:" + msg);
}
}
ws.on("connection", (client) => {
client.name = ++num;
clients[client.name] = client;
client.on("open", () => {
ws.send("来自客户端的hello");
});
client.on("message", (msg) => {
console.log("来自客户端NO" + client.name + "的数据:", msg.toString());
broadcast(client, msg);
});
client.on("close", () => {
console.log("客户端NO" + client.name + "已断开连接~");
delete clients[client.name];
});
});
ws.on("close", () => {
console.log("服务端已经关闭!");
});
客户端代码:
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>websockettitle>
<script src="./client.js">script>
head>
<body>
<div>我是一个websocket测试客户端div>
body>
html>
//client.js
const ws = new WebSocket("ws://localhost:8080");
ws.onopen = () => {
console.log("client open");
ws.send("你好呀,服务端");
};
ws.onmessage = (socket) => {
console.log("来自服务端的数据:", socket.data);
};
ws.onerror = () => {
console.log("客户端错误");
};
ws.onclose = () => {
console.log("客户端关闭连接");
};
其实这个代码比较简单,很好理解,需要注意server.js
里面的client
不能理解成客户端,应该理解成服务端那边用来管理与客户端会话的管理者
。这个只是一个框子,我自己也没有实践过由server端主动向client端发信息,后续还得通过实际项目进一步练习。
有些浏览器不支持H5,这使得有些时候WebSocket的应用收到了一定的限制,WebSocket可以理解成socket在浏览器端应用场景下的一种优化封装,而socket.io就是对socket更加系统的优化,WebSocket,可以算作是socket.io的一个子集。如果使用socket.io的话,程序代码如下:
服务端:
//server.js
var express = require("express");
var app = express();
var server = require("http").Server(app);
var io = require("socket.io")(server, { cors: true });
io.on("connection", function(socket) {
console.log("有客户端与服务端建立连接");
socket.on("receive", (msg) => {
console.log("输出服务端收到的数据:", msg);
//向客户端发送数据
socket.emit("message", "收到 over !");
});
});
server.listen(8080, () => {
console.log("localhost:8080");
});
客户端:
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>socket.iotitle>
<script src="./node_modules/socket.io/client-dist/socket.io.js" charset="utf-8">script>
head>
<body>
<div>
<input type="text" id="msg" style="width: 200px;">
div>
<button id="submit">点击发送表单消息button>
<script>
var socket = io.connect('http://127.0.0.1:8080');
const content = document.getElementById('content')
document.querySelector('#submit')
.addEventListener('click', () => {
let content = document.getElementById("msg").value;
//向server发送数据
socket.emit('receive', content);
})
socket.on('message', (msg) => {
console.log("输出客户端收到的数据:", msg);
})
script>
body>
html>
需要注意的是,消息的传递都需要绑定一个信息传输标识,比如上面的"message"和"receive",如果遇到了跨域问题,直接在创建server的时候允许跨域。相比于WebSocket在写法上更加简单了