一转眼九月又过去了,最近没怎么写博客是由于事情太多了,感受心一直在路上,历来没有时间停下来栖息。从毕业到如今,刚入职便被大量的业务需求所围绕。看到排期已经排到明年的时候我陷入了沉思,曾幻想着利用工做之余的时间作一些本身喜欢作的事。慢慢的发现弱小的身体根本支撑不住。很早买的《深刻浅出node.js》翻看的次数也寥寥无几,每当想到这些脸上总会带着一丝惆怅。趁着国庆,因而便有机会能够看看node方面的书籍。纸上得来终觉浅,了解大概的node基础知识后蠢蠢欲动,最终咬牙切齿的拿出了我一礼拜的饭钱买了双越老师的:前端晋升全栈工程师必备课程 Node.js 从零开发web server博客项目。接下来的一周饿的时候我都会来看看视频,让知识填充个人肚子。课程讲解的很周到,值得推荐。可是为了避免让你们跟我同样没钱吃饭。因此我就详细的一步步带你们过一遍,相信你会跟我同样收获满满。若是你是高富帅、白富美的话买买体验感会更棒。html
要开发一个博客项目的 server 端,首先要实现技术方案设计中的各个 API。本章主要讲解如何使用原生 nodejs 处理的 http 请求,包括路由分析和数据返回,而后代码演示各个API的开发。可是本章还没有链接数据库,所以 API 返回的都是假数据。前端
npm install nodemon cress-env -d --savenode
新建一个项目名node-blog的文件,用npm init -y 初始化项目。并在package.json配置script,使用 npm run dev 启动咱们的项目mysql
"dev": "cross-env NODE_ENV=dev nodemon ./bin/api.js", "prd": "cross-env NODE_ENV=production nodemon ./bin/api.js" 复制代码
.node-blog
├── bin // 项目启动文件
├── node_modules
├── src
| ├── conf // 数据库配置
| └── controller // 接口api
| └── db // 数据库连接
| └── model // 输出格式
| └──router // 路由
├── app.js
├── package.json
复制代码
👉在bin下新建一个api.js做为node启动一个服务的模块linux
const http = require('http') const serverHandle = require('../app') const PORT = 8000 const server = http.createServer(serverHandle) server.listen(PORT) 复制代码
👉app.js中建立咱们的配置服务配置:git
const serverHandle = (req, res) => { // 设置返回格式 JSON res.setHeader('Content-type', 'application/json') } module.exports = serverHandle 复制代码
👉 在router中建立blog.js、user.js文件,其中blog.js做为博客的路由,这里以获取博客列表的接口为例,其余的接口只是换了一个名字而已:github
const handleBlogRouter = (req, res) => { const method = req.method // GET POST const url = req.url const path = url.split('?')[0] // 获取博客列表 if (method === 'GET' && req.path === '/api/blog/list') { return { msg: '这是获取博客列表的接口' } } } module.exports = handleBlogRouter 复制代码
👉 在app.js中引用建立的路由web
const handleBlogRouter = require('./src/router/blog') const handelUserRouter = require('./src/router/user') const serverHandle = (req, res) => { // 设置返回格式 JSON res.setHeader('Content-type', 'application/json') // 处理 blog 路由 const blogData = handleBlogRouter(req, res) => { if (blogData) { res.end(JSON.stringify(blogData) return } } // 处理 user 路由 const userData = handleUserRouter(req, res) => { if (userData) { res.end(JSON.stringify(userData) return } } // 404 res.writeHead(404, {"Content-type": "text/plain"}) res.write("404 Not Found\n") res.end() } module.exports = serverHandle 复制代码
这里启动咱们的服务,输入对应的接口api,就能拿到咱们返回的假数据了。关于详细的接口开发。在controller中新建blog.js、user.js处理,这里就不展开。详细可查看接口开发redis
API 实现了,就须要链接数据库,实现真正的数据存储和查询,再也不使用假数据。本章主要讲解 mysql 的安装、使用,以及用 nodejs 链接 mysql ,最后将 mysql 应用到各个已经开发完的 API 中。sql
为了下降本幅的篇长,这里将省略如何安装mysql,其实步骤很简单,也不是本文的主要讲解点,这里找了一个还不错的安装教程,能够供你们参考:mysql安装教程,另外不习惯操做控制台的能够自行下个图形化界面。我用的是MySql Workbench。
const mysql = require('mysql') // 建立连接对象 const con = mysql.createConnection( { host: 'localhost', user: 'root', password: '123456', port: '3306', database: 'myblog' } ) // 开始连接 con.connect() // 执行sql语句的函数 function exec(sql) { const promise = new Promise((resolve, reject) => { con.query(sql, (err, result) => { if (err) { reject(err) return } resolve(result) }) }) return promise } module.exports = { exec } 复制代码
在上章中讲到的假数据替换成数据库中的真实数据。在controller目录下的blog、user引入刚建立的mysql.js。在各接口完成mysql语句完成接口对接mysql。全部的接口都是同样的处理,只是执行的sql语句不同,详细可查看各接口对接Mysql,这里以获取数据列表的接口为例:
const getList = (author, keyworld) => { let sql = `select * from blogs where 1=1 ` if (author) { sql += `and author = '${author}' ` } if (keyworld) { sql += `and title like '%${keyworld}%' ` } sql += `order by createtime desc;` return exec(sql) } 复制代码
设置用户名的cookie, 其中getCookieExpires为cookie的过时时间, path=/ 设置全部路由,httpOnly不容许前端更改cookie。
res.setHeader('Set-Cookie',
username=${username}; path=/; httpOnly; expires=${getCookieExpires()}
)
步骤:访问login,将用户名密码传过去,验证登陆,登陆以后将用户信息写入到cookie返回前端。经过cookie测试判断有无登陆。
// 解析cookie req.cookie = {} const cookiestr = req.headers.cookie || '' cookiestr.split(';').forEach(item => { if (!item) { return } const arr = item.split('=') const key = arr[0].trim() const val = arr[1].trim() req.cookie[key] = val }) 复制代码
日志记录和日志分析是 server 端的重要模块,前端涉及较少。本章主要讲解如何使用原生 nodejs 实现日志记录、日志内容分析和日志文件拆分。其中包括 stream readline 和 crontab 等核心知识点。
什么是stream? 👉官方解释
流(stream)是 Node.js 中处理流式数据的抽象接口。 stream 模块用于构建实现了流接口的对象。流能够是可读的(Readable)、可写的(Writable)、或者可读可写的(Duplex)。抽象理解为两个水桶经过水管连接,将其中的一个水桶的水满满流入到另外一个水桶。
stream能作什么? 👉 IO(网络IO和文件IO)操做的性能瓶颈,如何在有限的硬件资源下提升IO的操做效率。
stream(流) 拷贝代码演示 :
const fs = require('fs') const path= require('path') // 两个文件名 const fileName1 = path.resolve(__dirname, 'data.txt') const fileName2 = path.resolve(__dirname, 'data-bak.txt') // 读取文件的 stream 对象 const readStream = fs.createReadStream(fileName1) // 写入文件的 stream 对象 const writeStream = fs.createWriteStream(fileName2) // 执行拷贝,经过pipe readStream.pipe(writeStream) // 监听每次拷贝的内容 readStream.on('data', chunk => { console.log(chunk.toString()) }) // 数据读取完成,即拷贝完成 readStream.on('end', () => { console.log('copy done') }) 复制代码
在blog-node的目录下新建一个logs文件夹,在其下面新建access.log、error.log、event.log。并在src下新建一个utils > log.js 这里就以access为例子,其代码为:
const fs = require('fs') const path = require('path') // 写日志 function writeLog (writeStream, log) { writeStream.write(log + '\n') } // 生成 write stream function createWriteStream (fileName) { const fullFileName = path.join(__dirname, '../', '../', 'logs', fileName) const writeStream = fs.createWriteStream(fullFileName, { flags: 'a' }) return writeStream } // 写访问日志 const accessWriteStream = createWriteStream('access.log') function access (log) { writeLog(accessWriteStream, log) } module.exports = { access } 复制代码
在app.js中引入刚写的log.js文件中access方法并在serverHandle方法中记录access log。当咱们的接口被执行的时候就会记录接口的信息等。
access(
${req.method} -- ${req.url} -- ${req.headers['user-agent']} -- ${Date.now()}
)
在src新建一个utils > copy.sh 写入sh命令。执行sh copy.sh,在上小节建立的logs会多出一个文件名为2019-09-17.access.log 文件名即完成日志拆分。下面是sh命令:
!/bin/sh cd /Users/wusimin7/Documents/jd_code/node-blog/blog-node/logs cp access.log $(date +%Y-%m-%d).access.log echo "" > access.log 复制代码
在咱们的总项目下执行crontab -e 建立定时任务。输入如下内容。wq!保存后经过crontab -l 查看刚建立的crontab命令。
*0 * * * sh /Users/wusimin7/Documents/jd_code/node-blog/blog-node/src/utils/copy.sh
安全是 server 端须要考虑的重点内容,本章主要讲解 nodejs 如何防范 sql 注入,xss 攻击,以及数据库的密码加密 —— 以防被黑客获取明文密码。
攻击方法演示: 👉 在咱们的sql中输入
select username, realname from users where username='zhangsan'-- and password="123";
这个sql语句中能查出用户名"zhangsan",密码就不会显示,被 -- 所注释了。利用这个就能够进行sql注入攻击了
escape 函数预防 👉 利用mysql中的escape函数包裹咱们的登陆名和密码
// db文件夹下导出escape函数。在user.js中引用
escape: mysql.escape
username = escape(username)
password = escape(password)
复制代码
攻击方法演示: 👉 在新建博客的时候标题输入下面内容便可查看本网站的cookie。
<script>alert(document.cookie)</script>
复制代码
在utils > crpy.js 加密文件
const crypto = require('crypto') // 密匙 const SECRET_KEY = 'WJiol_8776#' // md5 加密 function md5(content) { let md5 = crypto.createHash('md5') return md5.update(content).digest('hex') } // 加密函数 function genPassword(password) { const str = `password=${password}&key=${SECRET_KEY}` return md5(str) } module.exports = { genPassword } 复制代码
在controller > user.js中引入genPassword方法对输入的密码加密
password = genPassword(password)
npm install express-generator -g
经过 express express-test 命令生成一个项目。npm install 去下载依赖包运行npm start 访问localhist:3000。再安装监听文件的修改
npm install nodemon cross-env --save-dev
再package.json新增一个scripts命令:
"dev": "cross-env NODE_ENV=dev nodemon ./bin/www"
中间件原理分析:
const http = require('http') const slice = Array.prototype.slice class LikeExpress { constructor() { // 存放中间件列表 this.routes = { all: [], gte: [], post: [] } } register(path) { const info = {} // 分析第一个参数是否为路由 if (typeof path === 'string') { info.path = path // 从第二个参数开始,转换成数组,存入 stack info.stack = slice.call(arguments, 1) // 数组 } else { info.path = '/' // 从第一个参数开始,转换成数组,存入 stack info.stack = slice.call(arguments, 0) // 数组 } 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) } match(method, url) { let stack = [] if (url === '/favico.ico') { return stack } // 获取 routes let curRoutes = [] curRoutes = curRoutes.concat(this.routes.all) curRoutes = curRoutes.concat(this.routes[method]) curRoutes.forEach(routerInfo => { if (url.indexOf(routerInfo.path) === 0) { stack = stack.concat(routerInfo.stack) } }) return stack } // 核心的next机制 handle(req, res, stack) { const next = () => { // 拿到第一个匹配的中间件 const middleware = stack.shift() if (middleware) { // 执行中间件函数 middleware(req, res, next) } } next() } callback() { return (req, res) => { 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) } } listen(...args) { const server = htpp.createServer(this.callback()) server.listen(...args) } } module.exports = () => { return new LikeExpress() } 复制代码
npm install koa-generator -g
经过 koa2 express-koa2 命令生成一个项目。npm install 去下载依赖包运行npm start 访问localhist:3000。再安装监听文件的修改
npm install cross-env --save-dev
再package.json新增一个scripts命令:
"dev": "cross-env NODE_ENV=dev nodemon ./bin/www"
const http = require('http') // 组合中间件 function compose(middlewareList) { return function (ctx) { // 中间件调用 function dispatch(i) { const fn = middlewareList[i] try { return Promise.resolve(fn(ctx, dispatch.bind(null, i+1))) } catch(err) { return Promise.reject(err) } } return dispatch(0) } } class LikeKoa2 { constructor() { this.middlewareList = [] } use(fn) { this.middlewareList.push(fn) return this } createCtx(req, res) { const ctx = { req, res } return ctx } handleRequest(ctx, fn) { return fn(ctx) } callback() { const fn = compose(this.middlewareList) return (req, res) => { const ctx = this.createCtx(req, res) return this.handleRequest(ctx, fn) } } lsiten(...args) { const server = http.createServer(this.callback()) server.listen(...args) } } module.exports = { LikeKoa2 } 复制代码
代码开发完毕要线上运行,而且保证服务稳定性,将使用 PM2 工具。本章讲解 PM2 的配置使用和进程守护,以及 PM2 多进程模型。最后,还介绍了服务器运维的相关方法。
优势: 单个进程内存受限,操做系统会限制一个进程的最大可用内存。没法充分利用多核cpu优点。
缺点: 多进程之间,内存没法共享;多进程访问redis,实现数共享
{ "apps": { "name": "pm2-test-server", "script": "app.js", "watch": true, // 实时监听 "ignore_watch": [ "node_modules", "logs" ], "instances": 4, // 进程数 "error_file": "logs/err.log", "out_file": "logs/out.log", "log_date_format": "YYYY-MM-DD HH:mm:ss" } } 复制代码
看到这里差很少都讲完了,只是大体的说了下步骤。具体的可查看👉源码地点,感兴趣的同窗能够去慕课上学学,这里不是推销,课程仍是很棒的。纸上得来终觉浅,准备讲所学知识加以运用,接下来会出一个node全栈的仿掘金,有兴趣的也能够加入和我一块儿快乐的学习吧。项目目前已经进展到一半多了。