Nodejs学习---总结篇

server端和前端的区别
nodejs和JavaScript的区别
node引入/导出语法
http模块
fs模块
path模块
url模块
querystring模块
nodejs处理http请求
express
express-router
express中间件
redis存储session
nginx反向代理
morgan日志
安全

server端和前端的区别

  • 服务的稳定性
  • 需要考虑内存和cpu的优化
    • 客户端独占一个浏览器,内存和cpu都不是问题
    • server端承载很多请求,内存和cpu都是稀缺资源
    • stream写日志是比较节省内存的(优化),redis存储session(扩展)
  • 日志记录
    • server端要记录日志,存储日志,分析日志,前端不用关心
  • 安全
    • server端要随时准备接受各种恶意攻击:越权操作,数据库攻击
    • 预防xss攻击和sql注入
  • 集群和服务拆分
    • 产品发展速度快,承载流量大

nodejs和JavaScript的区别

  • javascript
    • 使用ESMAScript语法规范,外加web API
    • web API : BOM DOM,事件绑定,ajax等
  • nodejs
    • 使用ESMAScript语法规范,外加nodejs API
    • nodejs API:处理http请求,处理文件等

node引入/导出语法

  • 引入模块 require('模块名称')
    • 带路径的: 一般我们自己封装的模块 require('../xxx/yyy'),js文件后缀名可以省略不写。 路径还可以写到文件夹的那层,但文件夹下一定要有一个index.js 系统会默认找index.js require('../xxx/文件夹名称') === > require('../xxx/文件夹名称/index.js')
    • 不带路径的: require('koa') 其实就是找 node_modules文件夹 系统node_modules
  • 导出模块语法 module.exports /exports
    • 每一个Nodejs的执行文件都会自动地创建一个module对象,同时module.exports会创建一个叫exports的属性,初始值为空对象{}exportsmodule.exports指向同一个内存,但require()返回的是module.exports而不是exports
    • module.exports 可以直接等于一个对象 但exports只能给其赋值新属性 不能直接等于一个新对象
//module.exports的形式
module.exports = {
  str,
  arr,
  targetObj
}
//exports的形式
exports.str = str;
exports.arr = arr;
exports.targetObj = targetObj;

http模块

nodejs的运用基础是起码要创建一个服务,在该服务的基础上处理业务操作,对于创建一个服务器最原始的莫过于是使用http模块。
http模块主要用于创建http server服务,其中封装了高效的http服务器和http客户端
http.server是一个基于事件的HTTP服务器,内部是由c++实现的,接口由JavaScript封装
http.request是一个HTTP客户端工具。用户向服务器发送数据。

var http = require('http');
var server = http.createServer((req,res)=>{
	//解决跨域问题
	res.setHeader('Access-Control-Allow-Origin',"*")
    res.setHeader("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");

	//设置 HTTP 头部,状态码是 200,文件类型是 html,字符集是 utf-8
	res.writeHead(200,{"Content-Type":"text/html;charset='utf-8'"});
	res.write('aaa');
	/*如果没有res.end(),服务器会一直等待,运转到一定的时间,
    页面会返回一个错误。提示服务器运行时间超长。*/
	res.end(); // 结束下你的请求
});
server.listen(8080)
  • var http = require('http'); 引入模块
  • http.createServer(callback) request:获取客户端发送的请求数据的信息(请求的地址,请求的方式等),response:返给客户端的相应数据
  • server.listen(post,host,callback) host可以不写 默认为localhost/127.0.0.1

fs模块

异步和同步

Node.js 文件系统(fs 模块)模块中的方法均有异步和同步版本,例如读取文件内容的函数有异步的 fs.readFile() 和同步的 fs.readFileSync()。
异步的方法函数最后一个参数为回调函数,回调函数的第一个参数包含了错误信息(error)。
建议大家使用异步方法,比起同步,异步方法性能更高,速度更快,而且没有阻塞。

// 属于异步操作 不会耽误其他的执行  
 fs.writeFile(path,data,callback);
 fs.readFile(path,callback);
// 同步形式也有 很少用  太慢了
 fs.writeFileSync();
 fs.readFileSync();
var fs = require('fs');
//异步获取
fs.readFile('input.txt',(err,data)=>{
	if(err){
	return console.error(err)
	}
	// data返回的是原始的二进制数据(Buffer) 浏览器是可以解析的 自己看的时候需要toString()
	console.log('异步读取:'+data.toString());
})
var fs = require('fs');
//异步写入
fs.writeFile('./a.txt','xxasxssx',(err)=>{
  if(err){
    console.log('写入失败',err);

  }else{
    console.log('写入成功',err);
  }
});
var fs = require('fs');
//同步获取
var data = fs.readFileSync('input.txt');
console.log("同步读取: " + data.toString());
var fs = require('fs');
//同步写入
var data = fs.writeFileSync('input.txt');
console.log("同步读取: " + data.toString());

fs.writeFile(path,data,callback); //写入文件 路径,要写入的数据,回调函数 (接受一个err参数 )
写入一旦成功 即使没有这个文件 也会在目录中创建这个文件 并把数据写入到文件中 回调中的 err 返回为 null
写入失败 回调中的err会返回一个错误对象 。

fs.readFile(path,callback); // 用来读文件 不需要data 路径,回调函数(接受err,data参数 data是读取到的数据)
data返回的是原始的二进制数据(Buffer) 浏览器是可以解析的 只是你自己看的时候看不懂。通常我们可以data.toString()转为字符串来查看数据。


path模块

path 模块提供用于处理文件路径和目录路径的实用工具。

const path = require('path');

let str = '/root/a/b/1.txt';

//path.dirname(str) 获取目录名称  /root/a/b
console.log(path.dirname(str)); 

//path.extname(str) 获取扩展名 .txt
console.log(path.extname(str));

//path.basename(str) 获取文件名 1.txt
console.log(path.basename(str));

//path.resolve(str) 对路径的解析 类似于命令行的操纵  \root\a\b\c\d\e
console.log(path.resolve('/root/a/b','c','d','e'));
// 常用的是解析当前的绝对路径
console.log(path.resolve(__dirname,'build'));

url模块

url 模块用于处理与解析 URL。

url.parse(url) 这个方法可以将一个url的字符串解析并返回一个url的对象

const url = require('url');

url.parse("http://user:pass@host.com:8080/p/a/t/h?query=string#hash");
/*
返回值:
{
  protocol: 'http:',
  slashes: true,
  auth: 'user:pass',
  host: 'host.com:8080',
  port: '8080',
  hostname: 'host.com',
  hash: '#hash',
  search: '?query=string',
  query: 'query=string',
  pathname: '/p/a/t/h',
  path: '/p/a/t/h?query=string',
  href: 'http://user:pass@host.com:8080/p/a/t/h?query=string#hash'
 }
没有设置第二个参数为true时,query属性为一个字符串类型
*/

url.parse(url,true);

url.parse("http://user:pass@host.com:8080/p/a/t/h?query=string#hash",true);
/*
返回值:
 {
  protocol: 'http:',
  slashes: true,
  auth: 'user:pass',
  host: 'host.com:8080',
  port: '8080',
  hostname: 'host.com',
  hash: '#hash',
  search: '?query=string',
  query: { query: 'string' }, // 第二个参数 为true时 
  pathname: '/p/a/t/h',
  path: '/p/a/t/h?query=string',
  href: 'http://user:pass@host.com:8080/p/a/t/h?query=string#hash'
 }
返回的url对象中,query属性为一个对象
*/

url.format(urlObj) 这个方法是将传入的url对象编程一个url字符串并返回。

url.format({
    protocol:"http:",
    host:"182.163.0:60",
    port:"60"
});
/*
返回值:
'http://182.163.0:60'
*/

url.resolve(from,to) resolve这个方法返回一个格式为"from/to"的字符串

url.resolve("http://whitemu.com","gulu");
/*
返回值:
'http://whitemu.com/gulu'
*/

querystring模块

从字面上的意思就是查询字符串,一般是对http请求所带的数据进行解析。

querystring.parse(str) 将字符串解析为一个对象。
querystring.stringify(strObj) 将字符串对象解析为一个字符串。

const querystring = require('querystring');

console.log(querystring.parse('a=12&b=11&c=11')); //{ a: '12', b: '11', c: '11' }
console.log(querystring.stringify({ a: '12', b: '11', c: '11' })) //a=12&b=11&c=11

querystring.escape(str) escape可使传入的字符串进行编码

querystring.escape("name=慕白");
/*
return:
'name%3D%E6%85%95%E7%99%BD'
*/

querystring.unescape(str) unescape方法可将含有%的字符串进行解码

querystring.unescape('name%3D%E6%85%95%E7%99%BD');
/*
return:
'name=慕白'
*/

nodejs处理http请求

http请求概述

  • DNS解析,建立TCP连接,发送http请求。
  • server接收到http请求,处理,并返回。
  • 客户端接收到返回数据,处理数据(如渲染页面,js)

nodejs处理http请求

  • get请求和querystring
  • post请求和postdata
  • 路由

nodejs处理get请求

  • get请求,即客户端要向server端获取数据
  • 通过querystring来传递数据,如a.html?a=100&b=200
  • 浏览器直接访问,就发送get请求。
const http = require('http');
const querystring = require('querystring');

const server = http.createServer((req,res)=>{
    console.log('method',req.method); //get
    const url = req.url;
    console.log('url',url);
    req.query = querystring.parse(url.split('?')[1]);
    console.log('query',req.query);
    res.end(JSON.stringify(req.query));
})

server.listen(8080,()=>{
    console.log(`server is running `)
});

打印如下:

method GET
url /api/list?author=111
query [Object: null prototype] { author: '111' }
method GET
url /favicon.ico
query [Object: null prototype] {}

nodejs处理post请求

  • post请求,即客户端要向server端传递数据
  • 通过postdata来传递数据
  • 浏览器无法直接模拟,需要手写js,或者用postman
const http = require('http');

const server = http.createServer((req,res)=>{
  if(req.method == 'POST'){
    console.log('req content-type',req.headers['content-type']);

    let postData = '';
    req.on('data',chunk =>{
      postData+=chunk.toString();
    })
    req.on('end',() =>{
      console.log('postData:',postData);
      console.log('postData:',typeof postData); //string
      req.body = JSON.parse(postData); //将post获取到的参数 放到 req.body上
      res.end('hello world')
    })
  }
});

server.listen(8000,()=>{
  console.log('ok')
})

express

安装脚手架

npm i express-generator -g

创建项目

express blog_express
cd blog-express
npm i 
npm start

配置package.json

npm i nodemon cross-env -D
 "scripts": {
    "start": "node ./bin/www", 
    "dev":"cross-env NODE_ENV=development nodemon ./bin/www"
  },

介绍app.js

var createError = require('http-errors'); 对错误页面的处理
var express = require('express');
var path = require('path');
var cookieParser = require('cookie-parser'); 对cookie的解析 通过req.cookies就可以直接访问cookie
var logger = require('morgan');  记录日志

var indexRouter = require('./routes/index');
var usersRouter = require('./routes/users');

var app = express();

// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'jade');

app.use(logger('dev')); 使用logger日志
app.use(express.json()); 可以获取到post json的数据 req.body获取
app.use(express.urlencoded({ extended: false })); 获取post除了json之外格式的数据 都是绑定到req.body上
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public'))); 

app.use('/', indexRouter);
app.use('/users', usersRouter);

// catch 404 and forward to error handler
app.use(function(req, res, next) {
  next(createError(404));
});

// error handler
app.use(function(err, req, res, next) {
  // set locals, only providing error in development
  res.locals.message = err.message;
  res.locals.error = req.app.get('env') === 'development' ? err : {};

  // render the error page
  res.status(err.status || 500);
  res.render('error');
});

module.exports = app;


res相关

res.send() (*****)  // 万能方法  可以响应JSON 字符串 HTML script 
res.json()  // 主要给前端响应 JSON 数据
res.jsonp() // 主要给前端响应 JSON 数据(针对跨域请求)
数据和模板在后端合并渲染 生成HTML 返回给前端
res.render('ejs模板文件', {JSON对象格式的数据})
res.download('要下载的文件的路径', '标题')
res.redirect("要跳转到的新的网址url")
res.status(状态码404).render("ejs模板名字", {JSON对象格式的数据})

req相关

接收 get 方式的请求的参数,req.query.参数的key值
接收 post 方式的请求的参数,req.body.参数的key值
 req.ip  获取浏览器的ip地址

express-router

//app.js
var blogRouter= require('./routes/blog.js');
app.use('/api/blog',blogRouter);

//routes---->blog.js
var express = require('express');
var router = express.Router();

router.get('/list', function (req, res, next) {
  const { username, password } = req.query;
  res.json({
    errno: 0,
    data: {
      username,
      password
    }
  })
})
router.post('/detail', function (req, res, next) {
  const { username, password } = req.body;
  res.json({
    errno: 0,
    data: {
      username,
      password
    }
  })
})

module.exports = router;
  • post请求的数据在req.body
  • get请求的数据在req.query

express-中间件

在 Node.js 中被广泛使用,它泛指一种特定的设计模式、一系列的处理单元、过滤器和处理程序,以函数的形式存在,连接在一起,形成一个异步队列,来完成对任何数据的预处理和后处理。

其实中间件就是请求req和响应res之间的一个应用,请求浏览器向服务器发送一个请求后,服务器直接通过request定位属性的方式得到通过request携带过去的数据,就是用户输入的数据和浏览器本身的数据信息,这中间就一定有一个函数将这些数据分类做了处理,最后让request对象调用使用,这个或者多个的处理函数就是我们所说得中间件。

原理分析
  • app.use用来注册中间件,先收集起来。
  • 遇到http请求,根据path和method判断触发那些。
  • 实现next机制,即上一个通过next触发下一个
实现
  • 创建存放所有路由的对象(all,get,post.。。。),后期对不同的请求处理都会放到这里
  • 声明对应处理的函数use(),get(),post(),register函数统一处理path信息,返回{path:'xxx',stack:['每个path对应中间件函数数组']}
  • use(),get(),post(),将自己的info信息存储到 this.routes
  • listen监听端口
  • callbackcreateServer回调,获取当前url的路由和method
  • match 返回当前url所需要执行的所有中间件集合
  • handle核心方法 实现 next()调用各个中间件
const http = require('http');
const slice = Array.prototype.slice;

class LikeExpress{
  constructor(){
    // 静态属性
    this.routes = {
      all:[],
      get:[],
      post:[]
    }
  }
  // 原型上定义的函数
  
  //中间件公用的地方 定义在register里面
  register(path){
    const info = {};
    if(typeof path === 'string'){
      //如果第一个参数是路由的话
      info.path = path;
      // stack 以数组的形式存放路由后面的中间件
      info.stack = slice.call(arguments,1);
    }else{
      // 如果是app.use((req,res,next)=>{}) 没有第一个参数的形式
      info.path = '/';
      info.stack = slice.call(arguments,0);
    }
    console.log(`info is ${info}`);
    return info;
  }

  use(){
    const info = this.register.apply(this,arguments);
    this.routes.all.push(info);
  }
  get(){
    const info = this.register.apply(this,arguments);
    this.routes.get.push(info);
  }
  post(){
    const info = this.register.apply(this,arguments);
    this.routes.post.push(info);
  }

  // 监听事件
  listen(...args){
   
    const server = http.createServer(this.callback());
    server.listen(...args);
  }
  // 定义server的回调函数
  callback(){
    return (req,res)=>{
      // 定义res.json的使用
      res.json = (data)=>{
        res.setHeader('Content-type','application/json');
        res.end(
          JSON.stringify(data)
        )
      }

      const url = req.url;
      const method = req.method.toLowerCase();

      const resultList = this.match(method,url);
      this.handle(req, res, resultList)
    }
  }

  // match 当前url所需要执行的所以中间件集合
  match(method,url){
    let stack = [];
    if(url === '/favicon.ico'){
      return stack;
    }

    // 获取routes
    let curRoutes =[];
    // 当前路由匹配到的中间件:所有的 all 以及当前url的method对应所有中间件
    curRoutes = curRoutes.concat(this.routes.all);
    curRoutes = curRoutes.concat(this.routes[method]);
    
    curRoutes.forEach(routeInfo =>{
      if(url.indexOf(routeInfo.path) === 0){
        // url === '/api/get-cookie' 且 routeInfo.path === '/'
        // url === '/api/get-cookie' 且 routeInfo.path === '/api'
        // url === '/api/get-cookie' 且 routeInfo.path === '/api/get-cookie'
       
        // 当前路由在curRoutes 中匹配对应自己的所有中间件
        stack = stack.concat(routeInfo.stack)
      }
    })

    return stack
    
  }

  // 核心的 next机制
  handle(req,res,stack){
    const next = ()=>{
      const middleware = stack.shift();
      if(middleware){
        middleware(req, res, next);
      }
    }
    next();
  }
}

module.exports = () => {
  return new LikeExpress()
}
调用
const express = require('./like-express2');

const app = express();

app.use((req,res,next)=>{
  console.log('请求开始....',req.method,req.url);
  next();
})

app.use((req,res,next)=>{
  console.log('处理 cookie');
  req.cookie = {
    userId:'abc123'
  }
  next();
})

app.use('/api',(req,res,next)=>{
  console.log('处理 /api 路由');
  next();
})

app.get('/api',(req,res,next)=>{
  console.log('get 处理 /api 路由');
  next();
})

// 模拟登录验证

function loginCheck(req,res,next){
  setTimeout(() => {
    console.log('模拟登录成功'); 
    next();
  });
}

app.get('/api/get-cookie',loginCheck,(req,res,next)=>{
  console.log('get 处理 /api/get-cookie 路由');
  res.json({
    errno:0,
    data:req.cookie
  }) 
})

app.listen(8000,()=>{
  console.log(`server listen running http://localhost:8000`);
})

//访问 http://localhost:8000/api/get-cookie

//请求开始.... GET /api/get-cookie
//处理 cookie
//处理 /api 路由
//get 处理 /api 路由
//模拟登录成功
//get 处理 /api/get-cookie 路由

redis存储session
为什么session适合用redis?
  • session访问频繁,对性能要求极高
  • session不用担心断电丢失数据的问题
  • session数据量不会很大(相比于mysql中存储的数据)
为什么网站数据不适合raduis
  • 操作频率不是太高(相比于session操作)
  • 断电不能丢失,必须保留
  • 数据量太大,内存成本太高

redis默认端口和地址

REDIS_CONF = {
    port:6379,
    host:'127.0.0.1'
  }
demo
const redis = require('redis');

// 创建客户端
 const redisClient = redis.createClient(6379,'127.0.0.1');

 redisClient.on('error',err =>{
   console.log('err',err);
 })

//  测试
redisClient.set('myname','zhangsan2',redis.print);
redisClient.get('myname',(err,val) => {
  if(err){
    console.log('err2',err);
    return;  
  }
  console.log('val is',val);

  // 退出
  redisClient.quit();
});
封装demo
const redis = require('redis');
const { REDIS_CONF } = require('../conf/db');


// 创建客户端
const redisClient = redis.createClient(REDIS_CONF.port, REDIS_CONF.host);

redisClient.on('error', err => {
  console.log('err', err);
})

function set(key, val) {
  if (typeof val == 'object') {
    val = JSON.stringify(val);
  }
  redisClient.set(key, val, redis.print);
}
function get(key) {
  const promise = new Promise((resolve, reject) => {
    return redisClient.get(key, (err, val) => {
      if (err) {
        reject(err)
        return;
      }
      // resolve(val)

      if(val == null){
        resolve(null)
      }

      try{
        resolve(
          JSON.parse(val)
        )
      }catch(ex){
        resolve(val)
      }
      // 退出
      // redisClient.quit();
    });
  })
  return promise
}
module.exports = {
      set,
      get
    }

nginx反向代理

nginx的配置

C:\nginx\conf --> nginx.conf
 server {
        listen       8080;
        server_name  localhost;

        #charset koi8-r;

        #access_log  logs/host.access.log  main;

        # location / { 
            #root   html;
            #index  index.html index.htm;
        #}
    location /api/ {// nodejs 接口访问地址
		proxy_pass http://localhost:8000; 
		proxy_set_header Host $host; 
	}
    
	location / { //项目访问地址
		proxy_pass http://localhost:5500;	
	}

proxy_pass 设置代理地址

proxy_set_header 设置头部信息

网站输入localhost:8080—代理到localhost:5500,当访问接口/api的时候 请求的是 http://localhost:8000;上的接口.


morgan日志

' dev 开发模式  combined线上模式 '

const ENV = process.env.NODE_ENV
if(ENV !== 'production'){
  // 开发环境
  app.use(logger('dev',{
    stream:process.stdout //默认值 
  }));
}else{
  // 线上环境
  const logFileName = path.join(__dirname,'logs','access.log');
  const writeStream = fs.createWriteStream(logFileName,{
    flags:'a'
  })
  app.use(logger('combined',{
    stream:writeStream
  }));
}

安全

防止sql注入

主要通过mysql.escape,对代码中特殊字符等进行转义处理。

const mysql = require('mysql');
const escape = mysql.escape;

const login = (username,password)=>{
  username = escape(username); 防止sql注入,sql语句中不需要加单引号了
  password = escape(password);
    const sql = `
      select username,realname from users where username=${username} and password=${password}
    `

    return exec(sql).then((data)=>{
      return data[0] || {}
    })
}

防止xss攻击(脚本攻击)

require('xss'),对<>标签等进行转义;比如<转为&lt,这样就不会形成闭合的标签了

const xss = require('xss');

const newBlog = (blogData = {}) => {
  const title = xss(blogData.title);;

密码加密

密码加密后即使窃取到了用户信息,密码也是加密处理的同样无法登录,我这里是通过require('crypto');来对密码进行加密处理。

const crypto = require('crypto');

// 密匙

const SECRET_KEY = 'wjas_1234#';

// md5加密
function md5(content){
  let md5 = crypto.createHash('md5');
  return md5.update(content).digest('hex'); // digest('hex') 是转为16进制
}

// 加密函数
function genPassword(password){
  const str = `password=${password}&key=${SECRET_KEY}`
  return md5(str)
}

module.exports = { 
  genPassword
} 

/

const {genPassword} = require('../utils/cryp');

const login = (username,password)=>{
  username = escape(username);

  password = genPassword(password);加密处理

  password = escape(password);
    const sql = `
      select username,realname from users where username=${username} and password=${password}
    `

    return exec(sql).then((data)=>{
      return data[0] || {}
    })
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值