NodeJS笔记二---kalrry
NodeJS笔记二---kalrry
- NodeJS
- 服务器
- 什么是服务器
- web服务端保存的资源:
- web服务器(软件)的作用:
- nodeJS
- 介绍
- 目标
- 优势
- 劣势
- 特点:
- 环境安装
- 版本
- 运行
- window
- 苹果
- vscode
- webstrom
- **注意**
- web服务器的开发
- 1、搭建服务器 (http模块)
- 2、静态资源托管(fs模块)
- 2.1 fs模块
- 2.2 path模块
- 3、动态资源(接口实现)
- 3.1 url模块
- 3.2 querystring 模块
- 3.3 获取非地址栏的数据:
- 模块化规范 commonJS
- 1、模块化的作用
- 2、常见的模块化
- 3、规范是什么
- 4、commonJS的模块:
- 5、commonJS模块化格式:
- 1)、导入(引入)
- 2)、导出(输出,对外开放)
- 第三方模块化的管理工具
- 1、NPM
- 作用
- 安装到全局(操作系统)环境
- 安装到项目环境(本地安装)
- 初始化项目环境
- 项目依赖(生产依赖)
- 开发依赖
- 查看包
- 安装所有依赖
- 版本约束
- 选择源(选择最快的源进行安装)
- 安装卡顿时
- 发布包
- 2、YARN
- 安装
- 使用
- 3、BOWER
- 安装bower
- 安装包到全局环境
- 安装包到项目环境
- 初始化项目环境
- 项目依赖
- 开发依赖
- EXPRESS框架
- 特点
- 安装
- 搭建服务器
- 静态资源托管
- 动态资源(接口实现)
- 不同请求方式对应的函数
- req 请求体
- res 响应体
- 处理 多个接口的 公共业务
- use函数
- 中间件
- 扩展
- req
- res
- express脚手架
- 1、用express脚手架创建项目
- 2、安装express的依赖
- 3、 启动express的项目:
- 4、访问项目
- 5、脚手架项目解析
- 后端渲染
- 模板引擎
- ejs
- ejs介绍:
- ejs特点:
- **原理**:
- **使用**
- 前后端交互流程
- 数据库
- 1、回顾mysql
- 2、node + mysql客户端
- 3、mongodb
- 4、node+mongodb
- 1)、Mongoose简介
- 2)、安装mongoose
- 3)、mongoose连接数据完成增删改查
- MVC
- web开发中的架构:
- **耦合架构**
- **半分离架构**
- **分离架构**
- restful规范
- 概念:
- 优点:
- 规范
- bcrypt模块
- 介绍:
- 安装:
- 加密代码:
- 身份验证
- session(会话)
- 思想
- 业务场景:
- express-session
- token
- 思想
- 实现
- session vs token
- 如何保存信息给浏览器(cookie)
- 文件上传
- multer中间件
- BSR&&SSR&&SEO
- BSR
- SSR
- SSR与BSR的区别
- SEO
- SOCKET
- socket应用
- socket介绍
- socket的通讯流程
- webSocket
- webSocket对象介绍
- 属性
- 事件
- 方法
- 使用步骤:
- socket.io
- 介绍:
- 思路:
- 代码:
待整理ing
NodeJS
大纲:
1、搭建服务器
2、mongodb 注册和登录增删改查新闻(bootstrap)
3、api server 注册和登录增删改查新闻(bootstrap)
4、bcrypt加密 注册和登录
5、session 登录和其它需要验证身份的模块(如:添加,删除)
6、token 登录,其它需要验证身份的模块(如:添加,删除)
7、上传图片:添加新闻
8、socket :做聊天室
服务器
什么是服务器
服务器就是提供服务的,有web服务器,数据库服务器等等…………
web服务端保存的资源:
静态资源
xx.css
xx.html
xx.js
xx.图片
xx.json
xx.字体
… 等等在前端使用的文件。这些文件的内容每次打开时,都是一样的,所以叫静态资源。
动态资源(也有接口)
后端的程序,如:二阶段学习的php文件
web服务器(软件)的作用:
1、接收前端请求
2、查找文件
3、执行服务器端代码
4、响应
node完成了二阶段中apache和php的功能
nodeJS
介绍
后端的编程语言,与之类似有 php c# java ,python,go。不但是后端的编程语言,还可以搭建服务器,如:apche等。
nodeJS就是ECMAScirpt。在原来学习的ES的基础上增加了后端相关的API,如:HTTP,fs,URL等等。让javascript还可以做后端的开发,即一门语言搞定前后端,即全栈。
目标
数据服务:连接数据库
文件服务:文件上传等等。
web服务:web服务器(如:apache)
优势
性能高,方便、对于前端人员来说入门难度低、大公司都在用(BAT)。
坊间传说:一台node的服务器顶得上86台php服务器。特别是在处理并发的场景,尤为明显。
劣势
- 服务器提供的相对较少
- 相对其他语言,能用的上的学习资料少
- 对程序员的要求高了
特点:
单线程:nodeJS是单线程的,那么如何面对并发,靠的是事件循环。
https://blog.csdn.net/jiang7701037/article/details/95887439
非阻塞 :
NodeJS在访问高IO(input/output)操作后不会等待其完成,而是立即去执行其他代码,操作完成后会使用回调函数返回,保证高效的利用当前线程的cpu 不造成硬件浪费。跟异步差不多。
事件驱动:
通过事件来驱动整个程序的进行, 由于是单线程,所以把高IO的操作 就会移动到事件队列中等待完成,完成后通过回调函数的方式返回给线程来进行处理。这个循环处理的过程称之为:事件循环
环境安装
官网:英文 中文 镜像
测试环境: win+r->命令行(运行->cmd)->node -v
版本
Vx(主).x(子).x(修正)
主版本: 变化了,1/3的API发生巨变 , 使用方式变化了
子版本: API没有删减,使用方式没变化,内部实现发生了变化
修正版: 什么都没变,处理一下bug
V6.8.0 稳定
V6.9.1 非稳定版
beta 测试
rc 、alpha测试稳定
运行
window
a. 找到目标目录-》地址栏输入cmd-》node 文件名.js | node 文件名b. 当前目录->右键->git bash-> node 文件名
苹果
终端->cd 目录-> node 文件名.js | node 文件名
vscode
新建终端->cd 目录->node 文件名.js | node 文件名
调试->运行
webstrom
terminal| run
注意
nodejs
使用的是ECMA
语法,不可使用DOM
,BOM
因为后端开发没有浏览器。
即:前端js代码在浏览器上运行;后端js代码在node环境里运行的。
web服务器的开发
(回顾二阶段)
1、搭建服务器 (http模块)
搭建服务器要使用http模块,http模块,可以创建服务器对象,同时,可以处理请求和响应
1)、引入http模块
let http = require('http')
2)、创建web服务 返回http对象
let app = http.createServer((req,res)=>{req 请求体(请求对象) 浏览器->服务器,服务器端把浏览器发送来的信息整理到req对象里。req.url 地址 提取地址栏数据req.on('data') 提取非地址栏数据 所有的http[s]都会触发end事件req.on('end') res 响应体(响应对象) 服务器->浏览器,服务器端给浏览端发送的信息都整理在res里。res.writeHead(200,{'Content-Type':'text/html;charset=utf-8'});响应头设置res.write(字符/数据<string><buffer>) 返回数据res.end() 结束响应 必须})
3)、监听服务器
app.listen(端口,[地址],[回调])
端口: 1-65535 1024以下系统占用
地址:localhost 真实域名xx.duapp.com,ip地址也行。
回调:监听成功,回调一次
4)、启动服务器的命令:
node 文件名
搭建服务器的示例代码:
// 1、引入http模块(nodeJS官方的模块,是个对象)
const http = require("http");// 2、创建服务器对象
let server = http.createServer((req,res)=>{// req:请求对象(包含所有前端请求的信息,如:url,method,)console.log("req.url",req.url);console.log("req.method",req.method);// res:响应对象(包括响应所使用的函数等等,如:res.write() )res.writeHeader(200,{"Content-type":"text/html;charset=utf-8"})// res.setHeader("Content-type","text/html;charset=utf-8")// res.write():给前端响应数据的res.write("<h1>欢迎光临1!</h1>");res.write("<h1>欢迎光临2!</h1>");res.write("<h1>欢迎光临3!</h1>");res.write("<h1>欢迎光临4!</h1>");res.end();
});// 3、启动服务器
server.listen(9000,"10.35.165.56",()=>{console.log("启动成功,请访问……");
})
热部署和启动命令
由于,每次更新代码后, 需要重新启动服务器,很麻烦
推荐命令行工具:
supervisor
或者nodemon
安装方式:
npm install supervisor -g
或者npm install nodemon -g
启动服务器 nodemon 文件名
2、静态资源托管(fs模块)
fs:fileSystem; 这个模块可以读取文件的内容,给文件写内容,创建文件夹,删除文件夹等等有关文件和文件夹的一切操作。
一般来说,前端的如下代码都会请求一个静态资源
<a href=".."></a>
<img src="..."/>
location.href="..."
body{background:url(....)
}
后端资源读取静态资源文件就要使用fs模块
fs.readFile(文件名,[编码方式],回调(err,data));
2.1 fs模块
磁盘操作,文件操作
读取
异步的方式:fs.readFile('文件路径',[编码方式],(err,data)=>{})
[^err ]: err 错误 ,null没有错误
同步的方式: let data = fs.readFileSync('文件路径')
处理错误
try{
要排错的代码,如果try分支里有错误,那么就会进入到catch分支。
}catch(e){
}
2.2 path模块
操作系统磁盘路径
windows: c:\user\admin
磁盘的路径的分割是: \ 网络上的路径分割是 /
mac: ~/desktop/1901
API
磁盘路径解析 parse
path.parse('c:\\wamp\\xx.png') // string -> object//返回
{root: 'c:\\', 盘符dir: 'c:\\wamp', 目录base: 'xx.png', 文件名ext: '.png', 扩展名name: 'xx' 文件,不含扩展名
}
片段合并join
path.join('磁盘路径1','磁盘路径2','磁盘路径n')
__dirname :nodeJS的官方全局变量, 返回当前文件所在的磁盘路径。
片段合并 resolve
path.resolve('磁盘路径1','磁盘路径n')
合并磁盘片段,右到左找,如果找到了根,那就不再朝前找了;拼接时,是从左到右拼接,如果没有给根路径,以当前文件路径为根(即默认:加上 __dirname)。
静态资源的示例代码:
//一、在目录下建立 www 目录,写几个html文件//二、服务器代码
// 1、引入http模块(nodeJS官方的模块,是个对象)
const http = require("http");
const fs = require("fs");// 2、创建服务器对象
let server = http.createServer((req, res) => {if (req.url != "/favicon.ico") {try {let filename = "./www" + req.url;let data = fs.readFileSync(filename, "utf8");res.write(data);res.end();} catch (error) {res.write("服务器出错了");res.end();} }
});// 3、启动服务器
server.listen(9000, "10.35.165.56", () => {console.log("启动成功,请访问……");
})
3、动态资源(接口实现)
前端
表单:get/post/put/delete/…
js: ajax/jsonp
后端(如同二阶段的php)
处理方式:http[s]
地址栏上的数据: req.url
抓取 get请求的数据 切字符 | url模块
非地址栏的数据 : req.on('data',(chunk)=>{CHUNK==每次收到的数据buffer})
req.on('end',()=>{ 接收完毕 切字符 querystring })
postman 一个不用写前端,就可以发出各种请求的软件 下载
3.1 url模块
作用
处理 url格式的字符串
用法
url.parse(str,true) 返回 对象 true处理query为对象
str -> obj 返回 对象 true
protocol: ‘http:’, 协议
slashes: true, 双斜杠
auth: null, 作者
host: ‘localhost:8002’, 主机
port: ‘8002’, 端口
hostname: ‘localhost’, baidu
hash: ‘#title’, 哈希(锚)
search: ‘?username=sdfsdf&content=234234’, 查询字符串
query: ‘username=sdfsdf&content=234234’, 数据
pathname: ‘/aaa’, 文件路径
path: ‘/aaa?username=sdfsdf&content=234234’, 文件路径
href: ‘http://localhost:8002/aaa?username=sdfsdf&content=234234#title’
url.format(obj) 返回字符
obj -> str 返回str
3.2 querystring 模块
作用
处理查询字符串 如:?key=value&key2=value2
用法
querystring.parse(str) 返回对象
querystring.stringify(obj) 返回字符串
3.3 获取非地址栏的数据:
如:前端使用post发来的数据
使用:
//用req.on绑定事件data:接收一次数据(一般来说:65536字节),触发一次data
req.on('data',(chunk)=>{})//用req.on绑定事件end:接收完毕。
req.on('end',(err)=>{})
动态资源的示例代码:
const http = require("http");
const url = require("url");let server = http.createServer((req, res) => {// 1、根据书籍编号获取书籍详情// url: bookdetail// method:get// params:bookid (书籍的编号)// 返回数据格式:// {// status:"success",// data:{// bookid:"01001",// bookname:"论语",// bookauthor:"敖东",// }// }// console.log("req.url",req.url);let urlObj = url.parse(req.url, true);if (urlObj.pathname === "/bookdetail") {// 1、接收前端的数据let bookid = urlObj.query.bookid;// 2、查询数据库// 3、响应res.setHeader("Conent-type","text/plain");if (bookid == "01001") {res.write(`{status: "success",data: {bookid: "01001",bookname: "论语",bookauthor: "敖东",}}`)}else if (bookid == "01002") {res.write(`{status: "success",data: {bookid: "01002",bookname: "中庸",bookauthor: "王冰芊"}}`)}else{res.write(`{status: "fail"}`) }res.end();}// 2、注册// url: regSave// method:post// params:// username:用户名// userpass:密码// 返回数据格式:// {// status:"success",//fail// }if (urlObj.pathname === "/regSave") {// 1、接收前端发来的数据//用req.on绑定事件data:接收一次数据(一般来说:65536字节),触发一次datalet str = "";req.on("data",(chunk)=>{console.log("chunk",chunk);// chunk: 本次接收到一块的数据str += chunk;});//用req.on绑定事件end:接收完毕。req.on("end",(err)=>{if(!err){console.log("接到数据是:",str);// 2、处理(连接数据库)// 3、响应 let obj = JSON.parse(str);if(obj.username=="敖东" && obj.userpass=="123"){// res.write(`{// status:"success",// }`);// res.end();res.end(`{status:"success",}`);}else{res.end(`{status:"fail",}`);}}});}});server.listen(9000, "localhost", () => {console.log("启动成功,请访问……");
})
模块化规范 commonJS
1、模块化的作用
1)、防止全局变量和全局函数重名,不污染全局变量。
2)、隐藏了细节
3)、js文件引用js文件。如果说前端还可以用html引入js文件的话,那么后端就不可能了,因为后端代码里没有html。
2、常见的模块化
前端模块化规范:CMD,AMD,ES6
后端的模块化规范:commonJS,ES6
3、规范是什么
如:
AMD是个规范,requireJS是AMD规范的体现
CMD是个规范,seaJS是CMD规范的体现
commonJS是个规范,node和webpack是commonJS规范的体现
ECMA是个规范,JS/AS是ECMA实现了它
4、commonJS的模块:
系统模块
http
fs
querystring
url
第三方模块:
gulp
自定义模块:
5、commonJS模块化格式:
1)、导入(引入)
let 变量名 = require('模块名') // 得到的是个对象 ES6中import
let 变量名 = require('模块名').xx 按需引用 //得到的是对象的某个属性
不指定路径:先找系统模块-> 再从项目环境找node_modules|bower_components (依赖模块)->not found
指定路径 : 找指定路径 -> not found
支持任何类型
所以:系统模块和第三方模块不需要写路径。而我们 的自定义模块需要写路径。
2)、导出(输出,对外开放)
exports.自定义属性 = 值( 任意类型) //ES6 的 export
可输出多次
如:exports.name = “张三疯”;
exports.sex = “男”;
exports.person= {
}
module.exports = 值( 任意类型) //ES6 的 export default
只能输出一次,输出的为任意类型的数据,或者类。
如:
module.exports = {
}
module.exports = class Person{
constructor(){
}
}
模块化规范的代码:
把上面的代码进行提取
//一、routes/bookdetail.js
// 1、根据书籍编号获取书籍详情// url: bookdetail// method:get// params:bookid (书籍的编号)// 返回数据格式:// {// status:"success",// data:{// bookid:"01001",// bookname:"论语",// bookauthor:"敖东",// }// }
const url = require("url");module.exports = function(req,res){let urlObj = url.parse(req.url, true);// 1、接收前端的数据let bookid = urlObj.query.bookid;// 2、查询数据库// 3、响应res.setHeader("Conent-type","text/plain");if (bookid == "01001") {res.write(`{status: "success",data: {bookid: "01001",bookname: "论语",bookauthor: "敖东",}}`)}else if (bookid == "01002") {res.write(`{status: "success",data: {bookid: "01002",bookname: "中庸",bookauthor: "王冰芊"}}`)}else{res.write(`{status: "fail"}`) }res.end();
}//二、routes/regSave.js// 2、注册
// url: regSave
// method:post
// params:
// username:用户名
// userpass:密码
// 返回数据格式:
// {// status:"success",//fail
// }
module.exports = function (req, res) {// 1、接收前端发来的数据//用req.on绑定事件data:接收一次数据(一般来说:65536字节),触发一次datalet str = "";req.on("data", (chunk) => {console.log("chunk", chunk);// chunk: 本次接收到一块的数据str += chunk;});//用req.on绑定事件end:接收完毕。req.on("end", (err) => {if (!err) {console.log("接到数据是:", str);// 2、处理(连接数据库)// 3、响应 let obj = JSON.parse(str);if (obj.username == "敖东" && obj.userpass == "123") {// res.write(`{// status:"success",// }`);// res.end();res.end(`{status:"success",}`);} else {res.end(`{status:"fail",}`);}}});}//三、 server.js(服务器diam)
const http = require("http");
const url = require("url");
const bookdetail = require("./routes/bookdetail");
const regSave = require("./routes/regSave");let routes ={"/bookdetail":bookdetail,"/regSave":regSave
}let server = http.createServer((req, res) => {let urlObj = url.parse(req.url);routes[urlObj.pathname](req,res)// if (urlObj.pathname === "/bookdetail") {// bookdetail(req,res);// }else if (urlObj.pathname === "/regSave") {// regSave(req,res);// }});server.listen(9000, "localhost", () => {console.log("启动成功,请访问……");
})
第三方模块化的管理工具
1、NPM
作用
帮助你安装模块(包),并且自动安装所有的依赖,管理包(增,删,更新,项目所有包)。
npm服务器上放置了50多万的包,进行统一管理。
类似: bower yarn
安装到全局(操作系统)环境
- 安装到电脑系统环境下(相当于给操作系统安装了一个软件,只不过,此时用的是npm安装而已)
- 使用时在任何位置都可以使用(因为,默认配置了环境变量)
- 被全局安装的通常是:命令行工具,脚手架
npm install 包名 -g 安装
npm uninstall 包名 -g 卸载
安装到项目环境(本地安装)
只能在当前目录使用,需要使用npm 代 运行
初始化项目环境
npm init //会自动产生package.json文件
package.json文件示例
{"name": "npm", //项目名称"version": "0.0.1", //版本"description": "test and play", //描述"main": "index.js", //入口文件"dependencies": { //项目依赖(生产依赖) 上线也要用"jquery": "^3.2.1"},"devDependencies": { //开发依赖 上线就不用"gulp": "^3.5.2"},"scripts": { //命令行"test": "命令行"},"repository": { //仓库信息"type": "git","url": "git+https://github.com/tianwater.github.io/2017-8-28.git"},"keywords": [ //关键词,github上搜索时,可以使用该关键字"test",'xx','oo'],"author": "tianwater","license": "ISC", //认证"bugs": {"url": "https://github.com/alexwa9.github.io/2017-8-28/issues"//问题提交},"homepage": "https://github.com/alexwa9.github.io/2017-8-28#readme"//首页
}
项目依赖(生产依赖)
不但在当前项目下需要使用,上线了,也需要这个依赖 --save
//安装
npm install 包名 --save //安装最新版本的包
npm install 包名 -S // --save可以简写为 -S
npm install 包名@x.x.x -S //安装指定版本的包//卸载
npm uninstall 包名 --save
npm uninstall 包名 -S
安装了项目依赖后,会产生一个package-lock.json,这个文件千万不要删除
{"name": "nodetest","version": "1.0.0","lockfileVersion": 1,"requires": true,"dependencies": {"jquery": { //包名"version": "3.5.0", //版本号"resolved": "https://registry.npm.taobao.org/jquery/download/jquery-3.5.0.tgz?cache=0&sync_timestamp=1586533502771&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fjquery%2Fdownload%2Fjquery-3.5.0.tgz", //安装来源"integrity": "sha1-mYC5fZ5BlGEcNlMOfcRqWNc0D8k=" //迭代的id}}
}
package-lock.json文件的作用(这个可以以后再理解):
Node.js v8.0 后,npm 也升级到了5.0,其中package-lock.json文件就是npm5开始有的。
- package.json文件下载到的依赖包可能在不同的情况下,各库包的版本语义可能并不相同,有的库包开发者并不严格遵守这一原则:相同大版本号的同一个库包,其接口符合兼容要求。所以说,在不同时间或者不同npm下载源之下,下载的各依赖包版本可能有所不同,因此其依赖库包行为特征也不同,有时候甚至完全不兼容。
- package-lock.json文件所标识的具体版本下载依赖库包(包的安装来源),就能确保到一个新的机器上所有库包与上次的安装完全一样。
- npm的下载源改为私服地址,这样产生的package-lock.json文件的版本号是这个私服上设置好的版本号
场景:
如果在git上下载了别人的代码,或者你的代码拷贝到其它机子上,一般不会拷贝node_modules文件夹。在新的机子上执行命令: npm i 。那么npm会根据package.json里的配置安装所有的依赖,具体的包来自哪个来源,在package-lock.json里。
开发依赖
只能在当前项目下使用 上线了,依赖不需要了 --save-dev
npm install 包名 --save-dev
npm install 包名 -D
npm代运行
即:使用npm执行 package.json文件里的scripts属性里的内容(叫作 npm脚本)
如:
package.json里这样写:
"scripts": {"test": "echo \"Error: no test specified\" && exit 1","start": "node ./app.js","n":"node" //全局"j":"jQuery" //项目依赖},
命令行执行 npm run start 就相当于执行 node ./app.js
另外,start属性可以不用加run,其它属性需要加run。即:npm start 就相当于npm run start
命令行执行 npm run n 就相当于执行 node
命令行执行 npm run j 就相当于执行 jQuery
查看包
npm list 列出项目里所有已装包
npm outdated 版本对比(安装过得包),查看项目中安装的包和npm服务器上的包有没有版本上的不同
npm info 包名 查看当前包概要信息
npm view 包名 versions 查看包历史版本,如果想知道jQuery都有哪些版本:npm view jquery versions
current:当前项目中安装的版本号;
wanted:在你安装的当前主版本号里,推荐你使用的子版本号
latest:最新版本号
安装所有依赖
npm install
安装package.json里面指定的所有包
版本约束
^x.x.x 约束主版本,重新安装时,后面两位找最新
~x.x.x 保持前两位不变,重新安装时,最后一位找最新
* 重新安装时,永远装最新的版本
x.x.x 定死了一个版本
选择源(选择最快的源进行安装)
npm install nrm -g 安装选择源的工具包
nrm ls 查看所有源
nrm test 测试所有源,可以看到当前哪个源的速度快
nrm use 切换源名 ,切换到最快的源
安装卡顿时
ctrl+c -> npm uninstall 包名 -> npm cache clean 清除npm的缓存 -> 换移动数据(开热点)-> npm install 包名
发布包
官网 注册
登录(用命令行)
- 在项目目录下输入:npm login (注意:一定把源切换到npm,不要用其它的镜像)
- 输入 user/password/email
创建包(写项目)
npm init -y
- 创建入口index.js
- 编写,输出
发布
npm publish
注意:发布前,首先需要确定 包名(package.json里的name属性)是否在npm的服务器上存在。进入 npm 官网,搜索一下包名:[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mqYcOR9H-1644364678041)(C:\Users\31759\AppData\Roaming\Typora\typora-user-images\1588408182571.png)]
把目前切换到当前项目下,然后,输入npm publish。
迭代
- 修改版本号
npm publish
删除
npm unpublish
包的发布、迭代、删除,需要在包目录下进行
删除包,有时需要发送邮件
2、YARN
官网
安装
去官网安装
注意:为省事,不要用npm i yarn -g,去安装yarn,而是去下载压缩包,保证注册表和环境变量的硬写入,后期通过yarn安装全局包时方便(否则,可能会装不上)
使用
初始化一个新项目
yarn init
添加依赖包
yarn add [package]
yarn add [package]@[version]
yarn add [package]@[tag]
将依赖项添加到不同依赖项类别中
分别添加到 dependencies
,devDependencies
、peerDependencies
和 optionalDependencies
类别中:
yarn add [package] --save | -S
yarn add [package] --dev | -D
yarn add [package] --peer
yarn add [package] --optional
升级依赖包
yarn upgrade [package]
yarn upgrade [package]@[version]
yarn upgrade [package]@[tag]
移除依赖包
yarn remove [package]
安装项目的全部依赖
yarn
或者
yarn install
安装到全局
yarn global add [package] //global的位置测试不能变,global不能写在最后
yarn global remove [package]
3、BOWER
官网
安装bower
npm install -g bower
安装包到全局环境
bower i 包名 -g 安装
bower uninstall 包名 -g 卸载
安装包到项目环境
初始化项目环境
bower init
bower.json 第三方包管理配置文件
项目依赖
只能在当前项目下使用,上线了,也需要这个依赖 --save
//安装
同npm
bower install 包名#x.x.x -S 指定版本使用#//卸载
同npm
开发依赖
只能在当前项目下使用 ,上线了,依赖不需要了 --save-dev
同npm
EXPRESS框架
nodejs库,不用基础做起,工作简单化,点击进入官网,类似的还有 koa
特点
二次封装,非侵入式,增强形
二次封装:保留了原来的功能,做了进一步的增加。
安装
1、全局安装
npm install express -gnpm install express-generator -g(mac系统中一般没有问题,windows系统下建议安装)express -h 测试是否安装成功
2、搭建项目,局部安装
1)、新建项目目录
2)、初始化项目:npm init -y
3)、本地安装express:npm i express --save
搭建服务器
let express=require('express')
let app=express(); //相当于调用 http.createServer()
app.listen(端口,地址,回调)
静态资源托管
app.use(express.static('./www'));
动态资源(接口实现)
不同请求方式对应的函数
支持各种请求方式:get、post、put、delete…
app.请求方式API(接口名称,处理函数)
app.get(url,(req,res,next)=>{})
app.post(url,(req,res,next)=>{})
...
req 请求体
req 对象表示 HTTP 请求,把前端发送请求的一切信息封装到了该对象里。包含了请求查询字符串,参数,内容,HTTP 头部等属性。
req.query //获取地址栏的数据
req.body //获取非地址栏的数据 依赖中间件(body-parser) req.params //获取动态接口名 如: /api/goods/:id 的请求,那么,用req.params.id 获取
req.method //获取前端提交方式
req.body依赖中间件
中间件使用:body-parser
- npm install body-parser
- let bodyParser = require(‘body-parser’)
- app.use(bodyParser ())
res 响应体
response 对象表示 HTTP 响应,即在接收到请求时向客户端发送的 HTTP 响应数据
res.send(any) //send可以返回任何类型,包括对象 //对等 res.write + end
res.end(string|buffer) //原来的方法还在,这就是非侵入式的意思。
res.json(json) //只返回jsonres.status(404).send({error:1,msg:"Sorry can't find that!"}) //返回一个404,并响应内容,这是链式写法res.sendFile(path.resolve('public/error.html')) //发送文件,如:mp3,mp4等等,html也可以。res.redirect(url) 后端跳转 指向一个接口
处理 多个接口的 公共业务
如:token的处理(前端发来token字符串,后端先验证token是否正确,如果正确,才进行接口的处理)
app.all('/admin/*',(req,res,next)=>{})) 表示任何以admin开始的请求(任何类型)都会经过回调函数
all 处理所有类型的 HTTP 请求(如:get,post,put,delete等等)
需要next 延续后续
// 处理以admin开头请求的公共业务
app.all("/admin/*",(req,res,next)=>{req.name1="admin";res.common = "hi";next();
});// 处理 /admin/a
app.get("/admin/a",(req,res,next)=>{console.log("req.name1",req.name1);console.log( "res.common",res.common);console.log("admin/a");res.json({status:"success",msg:"a"});
});// 处理 /admin/b
app.post("/admin/b",(req,res,next)=>{console.log("req.name1",req.name1);console.log( "res.common",res.common);console.log("admin/b");res.json({status:"success",msg:"b"});
});
use函数
安装中间件、路由。接受一个函数, 中间件其实就是个函数
app.use([地址],中间件|路由|函数体)
app.use('/api/*',(req,res,next)=>{ }) //等价于 app.all('/api/ * ,(req,res,next)=>{});
app.use('/',(req,res,next)=>{ }) //等价于 app.use((req,res,next)=>{ }) )
即:如果不写地址,那么表示 ‘/’ ,即针对任何请求,都会执行该中间件
app.use,app.all,app.get|app.post的关系
app.use :处理中间件(关注点:在完成功能上,如body-parser)
app.all:处理请求
app.get :处理某种类型请求
app.post
app.put
………………
中间件
middleware, 处理自定义业务,只处理从 请求 到 结束响应 的中间部分。
举例
npm i body-parser -S //安装包let bodyParser=require('body-parser')//引入中间件app.use(bodyParser())//安装中间件(插入中间件)
body-parser 使用方式,实时查询 npm,可获得最新
扩展
req
- req.app:当callback为外部文件时,用req.app访问express的实例
- req.baseUrl:获取路由当前安装的URL路径
- req.cookies:Cookies
- req.fresh / req.stale:判断请求是否还「新鲜」
- req.hostname / req.ip:获取主机名和IP地址
- req.originalUrl:获取原始请求URL
- req.path:获取请求路径
- req.protocol:获取协议类型
- req.route:获取当前匹配的路由
- req.subdomains:获取子域名
- req.accepts():检查可接受的请求的文档类型
- req.acceptsCharsets / req.acceptsEncodings / req.acceptsLanguages:返回指定字符集的第一个可接受字符编码
- req.get():获取指定的HTTP请求头
- req.is():判断请求头Content-Type的MIME类型
res
- res.app:同req.app一样
- res.append():追加指定HTTP头
- res.set()在res.append()后将重置之前设置的头
- res.cookie(name,value [,option]):设置Cookie
- opition: domain / expires / httpOnly / maxAge / path / secure / signed
- res.clearCookie():清除Cookie
- res.download():传送指定路径的文件
- res.get():返回指定的HTTP头
- res.location():只设置响应的Location HTTP头,不设置状态码或者close response
- res.render(view,[locals],callback):渲染一个view,同时向callback传递渲染后的字符串,如果在渲染过程中有错误发生next(err)将会被自动调用。callback将会被传入一个可能发生的错误以及渲染后的页面,这样就不会自动输出了。
- res.sendFile(path [,options] [,fn]):传送指定路径的文件 -会自动根据文件extension设定Content-Type
- res.set():设置HTTP头,传入object可以一次设置多个头
- res.status():设置HTTP状态码
- res.type():设置Content-Type的MIME类型
作业
把vue的项目(打包后的)利用express搭建的node服务器来管理,保证资源托管到位,部分接口实现。
express脚手架
1、用express脚手架创建项目
express -e 项目名(如:express -e expressshop)
2、安装express的依赖
npm i
这是pakeage.json的依赖代码:
“dependencies”: {
“body-parser”: “~1.18.2”, //专门解析前端提交的数据
“cookie-parser”: “~1.4.3”, //专门解析cookie的
“debug”: “~2.6.9”, //调试程序的,显示调试信息,代替console.log
“ejs”: “~2.5.7”, //nodeJS express的模板,可以用此模板渲染前端页面
“express”: “~4.15.5”,
“morgan”: “~1.9.0”, // 日志
“serve-favicon”: “~2.4.5” //用来设置根目录下favicon.ico图标。
}
3、 启动express的项目:
npm start
4、访问项目
http://localhost:3000。
5、脚手架项目解析
/bin/www文件:启动服务器
/app.js文件: 所有的请求,都会经过该文件。 处理中间件、设置静态资源, 动态资源( 路径和routes文件夹下的文件的对应关系)
routes文件夹 : 逻辑
后端渲染
通常,前端根据后端返回的json数据,然后来生成html被称为前端渲染,而后端渲染是后端把json与html结合渲染好后返回到浏览器,没前端什么事了。
模板引擎
无论前后谁来渲染页面,都会用到模板引擎,前端渲染页面实际上是操作dom,后端渲染页面是把数据和html字符拼接后丢给浏览器
引擎 | 前端 | 后端 |
---|---|---|
angularJs | √ | × |
vue/mustach | √ | √ |
react | √ | √ |
angularTs/mustach | √ | √ |
jade(现在重命名为了pug) | × | √ |
ejs | × | √ |
jquery + art-template | √ | × |
handlerbars | √ | × |
ejs
ejs介绍:
“E” 代表 “effective”,即【高效】。EJS 是一套简单的模板语言,帮你利用普通的 JavaScript 代码生成 HTML 页面。EJS 没有如何组织内容的教条;也没有再造一套迭代和控制流语法;有的只是普通的 JavaScript 代码而已。
ejs特点:
纯 JavaScript:
js代码可以直接使用
语法简单:
EJS 支持直接在标签内书写简单、直白的 JavaScript 代码。只需让 JavaScript 输出你所需要的 HTML ,完成工作很轻松
易于调试
调试 EJS 错误(error)很容易:所有错误都是普通的 JavaScript 异常,并且还能输出异常发生的位置。
原理:
fs 抓取前端静态页面 + ejs + 数据 -> 返回send(data) -> 浏览器
使用
1)、后端渲染结果
let ejs = require('ejs')
ejs.renderFile('ejs模板文件',renderData,回调(err,data))
ejs模板 : 后缀名为ejs的html文件
renderData:json对象格式。要渲染到ejs模板文件里的数据,这个对象在ejs模块文件里就是个全局对象
err:错误,null代表没有错误
data: 渲染后的字符|流,即:渲染后的结果
2)、后端渲染结果发送给前端
//设置模板文件的路径
app.set('views', path.join(__dirname, 'views'));
//设置模板的引擎
app.set('view engine', 'ejs');res.render('模板文件名',{数据}) //整合页面和数据,完成渲染,发往浏览器,并结束响应
模板文件名:不用写扩展名,直接写文件名就行
数据是json对象
如:let data = {news:[{"id":"01","title":"特朗普又说谎了"},{"id":"02","title":"特朗普又又说谎了"}]}res.render("news",data);
3)、ejs模板文件语法
ejs 结构就是html
语句:所有的JS代码都写在<%与 %>中间,(与:php和html的混合写法一样)
输出: <%= 数据名|属性名|变量名 + 表达式 %>
非转义输出: <%- 数据名|变量名 + 表达式 %>
载入公共:<%- include(’./hd.ejs’,{数据}) %>
判断
<% if (user) { %><h2><%= user.name %></h2><% } %>
其他扩展
前后端交互流程
后端渲染的流程
用户 - > 地址栏(http[s]请求) -> web服务器(收到) - > nodejs处理请求(返回静态、动态)->请求数据库服务(返回结果)->nodejs(接收到数据)->node渲染页面->浏览器(接收页面,完成最终渲染)
前端渲染的流程
用户 - > http[s]请求 -> web服务器(收到) - > nodejs处理请求(返回静态、动态)->请求数据库服务(返回结果)->nodejs(接收到数据)->返回给前端(渲染)->浏览器(接收页面,完成最终渲染)
数据库
1、回顾mysql
关系数据库,二维表,不存在子表,每条数据的字段都是一样的,就算该字段没有值,但是字段依然存在。即:格式是固定的
sql语句
建库
CREATE DATABASE `2017-12-6` DEFAULT CHARACTER SET armscii8 COLLATE armscii8_general_ci;
建表
CREATE TABLE `2020-12-6`.`user` (`name` VARCHAR( 32 ) NOT NULL ,`age` INT( 3 ) NOT NULL ,`address` VARCHAR( 128 ) NOT NULL) ENGINE = INNODB
增
INSERT INTO 表 (字段列表) VALUES(值列表)
INSERT INTO user (name,age,address) VALUES('苏菲',38,'外滩18号')
删
DELETE FROM 表 WHERE 字段名=值
DELETE FROM user WHERE name='alex'
改
UPDATE 表 SET 字段名=值 WHERE 字段名=值
UPDATE user set name='sufei' WHERE name='苏菲'
查
SELECT ? FROM 表
SELECT * FROM user 查所有
2、node + mysql客户端
安装+引入
npm install mysql -S
var mysql = require('mysql');
创建库链接
var connection = mysql.createConnection({host : 'localhost:3306',//主机名user : 'root',password : 'root',database : 'mydb2001'//库名
});connection.connect();
表操作
connection.query('SQL语句', function (error, results, fields) {if (error) throw error;console.log('The solution is: ', results== 查询array|| 增删改object);
});
关闭库
connection.end();
3、mongodb
非关系型数据库,又叫nosql,缓存型,使用场景多是解决大规模数据集合多重数据种类
mongodb使用内存映射文件(memory-mapped file)来处理对磁盘文件中数据的读写请求。真正的存储(朝硬盘存储)交给操作系统
将MongoDB用作内存数据库(in-memory database),也即,根本就不让MongoDB把数据保存到磁盘中
下载 安装帮助
配置数据文件存储位置:
找到安装目录C:\Program Files\MongoDB\Server\4.0\bin -> cmd回车-> mongod --dbpath c:\data\db
这句命令,同时也启动了数据库服务
data和db目录要手动创建
- 服务端启动: 可选
找到安装目录C:\Program Files\MongoDB\Server\4.0\bin -> cmd回车-> mongod 回车
一般开启会默认启动
- 客户端连接:
找到安装目录C:\Program Files\MongoDB\Server\4.0\bin -> cmd回车-> mongo 回车
- 环境变量 可选
为了在任意盘符下去都可以启动 mongod服务端|mongo客户端,把安装目录添加到环境变量
mysql vs mongodb
mysql | mongoDb | |
---|---|---|
database(库) | database(库) | |
table(表) | collection(集合) | |
column(字段) | field(域) | |
row(一条数据) | document(文档) | |
二维表,每次存到磁盘 | json数组,存在缓存,关闭时存到磁盘 | 存储方式 |
mongodb命令行操作 声明式 | obj.api()
库操作
查: show dbsdb 查看当前库
建(存在该库,就是切换): use 库名
集合(表)操作
建:db.createCollection('表名',{配置})//配置:{size:集合的最大容量,capped:true,max:条数|文档数} capped定量//db.表(集合).isCapped() 返回 true/false 是否是定量
查:show collections / db.getCollectionNames()
删:db.集合.drop()
文档(row)操作
增
db.集合.save({}) //添加一条
db.集合.insert({}) //添加一条
db.集合.insertOne({}) //添加一条db.集合.save([{},{}]) //多条
db.集合.insert([{},{}]) //多条
//insert 不会替换相同ID save会
删
db.集合.deleteOne({查询条件}) //一条db.集合.remove({查询条件},true) //一条db.集合.remove({查询条件}) //多条db.集合.remove({}) //清空表
改
db.集合.update({查询条件},{替换条件},[插入false],[全替换false])
查询条件
{age:22} age == 22
{age:{KaTeX parse error: Expected 'EOF', got '}' at position 6: gt:22}̲} age > 22 {…lt:22}} age < 22
{age:{KaTeX parse error: Expected 'EOF', got '}' at position 7: gte:22}̲} age>=22 {a…lte:22}} age<=22
{age:{lte:122,lte:122,lte:122,gte:22}} age<=122 && age>=22
{$or:[{age:22},{age:122}]} age22 or age122
{key:value,key2:value2} key== value && key2==value2
{name:/正则/}替换条件
{set:age:12,set:{age:12},set:age:12,inc:{age:-1}}
查
所有:db.集合.find(条件)条数: db.集合.find().count()db.集合.find({条件},{指定要显示列区域})
指定要显示列区域
username:1 显示这个区域,其他不显示
username:0 不显示这个区域,其他显示
_id 是默认显示
排
db.集合.find().sort({key:1}) //升
db.集合.find().sort({key:-1}) //降
db.集合.find().sort({key1:1,key2:-1}) // 按照key1升序,key1相同时,按照key2降序
限定
db.集合.find().limit(number) //限定
db.集合.find().skip(number) //跳过
db.集合.findOne()//找第一个
db.集合.find().limit(1) //查询第一条
4、node+mongodb
1)、Mongoose简介
mongoose是nodeJS提供连接 mongodb的一个模块,便捷操作MongoDB的对象模型工具(Mongoose的操作是以对象为单位的)
2)、安装mongoose
npm i mongoose --save
3)、mongoose连接数据完成增删改查
//1、conndb.js 连接数据库
const mongoose = require('mongoose');
mongoose.connect('mongodb://localhost:27017/db2106',{useNewUrlParser:true} //使用解析器来解析本次连接
);
module.exports = mongoose;// 2、usersdb.js //针对集合users的所有操作(一般来说,增删改查)
let mongoose = require('./conndb'); //引入连接数据库的代码//模板:创建集合users对应的模板(相当于表结构,在nodejs里创建一个表结构)
let userSchema = new mongoose.Schema({'userName':String,'userPass':String
});//模型:(把数据库中集合users和模板进行对应和绑定)
let UserModel = mongoose.model('users',userSchema);module.exports = {//1)、添加addUser:function(user,success,fail) {//1)、创建实体(要添加到数据库中的数据)let userEntity = new UserModel({userName:"敖东",userPass:"123"});userEntity.save((err,data)=>{if(err){ fail&&fail();}else{ success&&success(); }});}//2)、查询queryUser:function(obj,success,fail){UserModel.find(obj,(err,data)=>{if(err){ fail&&fail(); }else{ success&&success(data); }});},
//3、删除removeUser:function(obj,success,fail){UserModel.remove(obj,(err,data)=>{if(err){fail&&fail();}else{success&&success(data);}});},
//4、修改updateUser:function(condationObj,updateObj,success,fail){UserModel.update(condationObj,updateObj,(err,data)=>{if(err){fail&&fail();}else{success&&success(data);}});}
示例(作业):
1、mongodb 注册和登录增删改查新闻(bootstrap)
2、理解api server和webserver
apiserver:只提供数据(完成的接口) ,上周做的vue联合项目中的java同学做的就是apiserver
webserver:有页面,上周做的vue联合项目中的我们前端做的是webserver
MVC
项目架构,项目分层,不同的层的职责不同。
C:controller,控制器。(控制的就是某个功能的业务流程)
routers文件夹下的文件。
根据业务流程,调用不同的模块完成对应的功能。
V:view:视图(显示)
views文件夹下的文件。完成显示的格式。使用ejs模板,jade模板
M:model,模型(只是数据处理)
业务逻辑处理,更多体现的是数据库的增删改查,完成具体的功能
db文件夹
web开发中的架构:
耦合架构
半分离架构
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-whceM72h-1644364678041)(D:\2110third\02node\nodejs_T_v5\image-20211110115658026.png)]
web的工作流程:
1、打开web,加载静态资源,如HTML,CSS,JS等;
2、发起一个Ajax请求再到服务端请求数据,同时展示loading;
3、得到JSON格式的数据后再根据逻辑选择模板渲染出DOM字符串;
4、将DOM字符串插入HTML页面中渲染出DOM结构;
分离架构
**前端渲染:**vue脚手架的项目是前后端分离,但是用的是前端的渲染
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PdzLosfl-1644364678041)(D:\2110third\02node\nodejs_T_v5\image-20211110115552713.png)]
**后端渲染:**如果要使用后端渲染,那么就需要使用ejs。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Cs1LqEdx-1644364678042)(D:\2110third\02node\nodejs_T_v5\image-20211110115419083.png)]
web的工作流程:
1)浏览器请求服务器端的NodeJS;
2)NodeJS再发起HTTP去请求JSP;
3)JSP的API输出JSON给NodeJS;
4)NodeJS收到JSON后再渲染出HTML页面;
5)NodeJS直接将HTML页面发送给浏览器;
这样,浏览器得到的就是普通的HTML页面,而不用再发Ajax去请求服务器了。
restful规范
概念:
RESTful (资源数据接口规范) 是目前最流行的 API 设计规范,用于 Web 数据接口的设计。
前端设备层出不穷(手机、平板、桌面电脑、其他专用设备…)。因此,必须有一种统一的机制,方便不同的前端设备与后端进行通信。这导致API构架的流行。RESTful API是目前比较成熟的API设计理论。要搞清楚restful规范,必须先了解REST。REST强调HTTP应当以资源为中心,并且规范了资源URI的风格;规范了HTTP请求动作(PUT,POST等)的使用,具有对应的语义。
如:
员工信息的uri :http://www.qianfeng.com/employees
书籍信息的uri :http://www.qianfeng.com/books
编号为01001的书籍信息的uri :http://www.qianfeng.com/books/01001
即:不同的资源(信息)是不同的uri
优点:
遵循REST规范的Web应用将会获得下面好处:
URL具有很强可读性的,
具有自描述性;
资源描述与视图的松耦合
可提供OpenAPI,
便于第三方系统集成,提高互操作性;
如果提供无状态的服务接口,可提高应用的水平扩展性;
规范
URI
URI(Uniform Resource Identifiers) 统一资源标示符,能够统一标识一个资源的符号都是URI
URL(Uniform Resource Locator) 统一资源定位符,是URI的一种体现形式。
在restfulAPI的规范里,要让一个资源可以被识别,需要有个唯一标识,在Web中这个唯一标识就是 URI(Uniform Resource Identifier),
URI的格式定义如下:
URI = scheme “
NodeJS笔记二---kalrry相关推荐
- Vue笔记随笔---kalrry
Vue笔记随笔---kalrry VUE vue框架的两大核心: 一.前端开发历史 二.MV*模式 库 VS 框架 MVC架构模式 MVP架构模式 MVVM架构模式 vue是MVVM 三.开发工具 四 ...
- node笔记随笔---kalrry
node笔记随笔---kalrry nodeJS 服务器 什么是服务器 web服务端保存的资源: web服务器(软件)的作用: nodeJS 介绍 目标 优势 劣势 特点: 环境安装 版本 运行 wi ...
- React笔记随笔---kalrry
React笔记随笔---kalrry React 一.React的简介 1.介绍 2.特点 3.框架对比 二.环境搭建 1.引入文件的方式 2.官方脚手架(模块化) 第三方脚手架 第一个React程序 ...
- nodejs linux模块全局,nodejs笔记一--模块,全局process对象;
一.os模块可提供操作系统的一些基本信息,它的一些常用方法如下: var os = require("os"); var result = os.platform(); //查看操 ...
- Nodejs笔记之易错点整理
Nodejs笔记整理 最近学了在b站学了nodejs,一边看一边敲,现在回过来重新看一下代码,并整理一下笔记.文章可能有不严谨的地方,希望多多指教. 关于结束响应 使用http创建服务器是,res() ...
- qml学习笔记(二):可视化元素基类Item详解(上半场anchors等等)
原博主博客地址:http://blog.csdn.net/qq21497936 本文章博客地址:http://blog.csdn.net/qq21497936/article/details/7851 ...
- oracle直查和call哪个更快,让oracle跑的更快1读书笔记二
当前位置:我的异常网» 数据库 » <>读书笔记二 <>读书笔记二 www.myexceptions.net 网友分享于:2013-08-23 浏览:9次 <> ...
- 【Visual C++】游戏开发笔记二十七 Direct3D 11入门级知识介绍
游戏开发笔记二十七 Direct3D 11入门级知识介绍 作者:毛星云 邮箱: happylifemxy@163.com 期待着与志同道合的朋友们相互交流 上一节里我们介绍了在迈入Dire ...
- [转载]dorado学习笔记(二)
原文地址:dorado学习笔记(二)作者:傻掛 ·isFirst, isLast在什么情况下使用?在遍历dataset的时候会用到 ·dorado执行的顺序,首先由jsp发送请求,调用相关的ViewM ...
最新文章
- python使用imbalanced-learn的KMeansSMOTE方法进行上采样处理数据不平衡问题
- 数据结构5: 链表(单链表)的基本操作及C语言实现
- 详解Android Handler的使用
- Android开发之将Android SVG 转 VectorDrawable矢量图的方法
- php长传文件到数据库,php上传文件并存储到mysql数据库的简单示例
- Spring之JDBCTemplate
- (数据库系统概论|王珊)第九章关系查询处理和关系优化-第三节:查询优化之代数优化
- 2.7.3-YARN-获取debug命令:resourceManager+nodeManager
- python示例_Python中的缩进示例
- PowerShell+you-get批量下载B站视频
- 使用QT5 PrintSupport打印和预览标签
- 【OpenCV4】fatal error: opencv2/core.hpp: No such file or directory 解决方法
- 十分钟掌握Google Guice(上)
- Hexo博客进阶:为 Next 主题添加 Waline 评论系统
- 计算机黑屏然后蓝屏怎么办,突然蓝屏死机开机黑屏怎么办_蓝屏之后重启屏幕黑屏的解决方法...
- Photoshop cs5 永久序列号
- 三角公式以及常见关系
- 教你如何用思维导图把一本书内容绘制成一张A4纸!
- mysql中update和limit_在MySQL中可以将UPDATE查询与LIMIT一起使用吗?
- 【论文精读】CMT: Convolutional Neural Networks MeetVision Transformers