Chapter01.简介
- NodeJS是让JavaScript脱离浏览器运行在服务器的一个平台,不是语言。
- NodeJS采用的JS引擎来自GoogleChromeV8,运行在浏览器外不用考虑JS兼容性。
- NodeJS采用单线程、异步IO、事件驱动的设计来实现高并发
- NodeJS内建HTTP服务器
安装
// 测试命令
node -v
npm -v
NPM
NPM(Node Package Modules)用于安装丰富的NodeJs库来完成开发需求。
// 查看帮助
npm help
npm h
// 安装模块
npm install <module_name>
// 全局安装模块
npm install -g <module name>
// 卸载模块
npm uninstall <module_name>
// 已安装模块
npm list
Chapter02.事件模块
events事件是NodeJS重要模块,events模块提供events.EventEmitter对象用于事件发射和事件监听。
NodeJS大部分模块都继承自Events模块。与DOM事件不同的是,Events不存在事件冒泡和逐层捕获等行为。
EventEmitter支持若干事件监听器,当事件发射时注册到此事件的事件监听器被依次调用,事件参数作为回调函数参数传递。
// 加载模块
require('events');
Chapter03.模块
核心模块
NodeJS提供核心模块编译成二进制文件,是用require('module_name')去获取,核心模块具有最高的加载优先级。
文件模块
NodeJS的文件模块可以是JS代码、JSON文件、C/C++编辑过的文件。
文件模块访问是通过 require('dir/file.ext') 来访问。
NodeJS加载优先级是:js文件>json文件>node文件
案例:自定义计数器模块
Chapter04.与MySQL交互
NodeJS与MySQL交互操作有很多库,暂时选择 felixge/node-mysql
。
// 安装
npm install mysql
执行CURD
//加载mysql库
var mysql = require('mysql');
//创建连接
var dbcfg = {
host:'127.0.0.1',
user:'root',
password:'root',
port:'3306',
database:'test'
};
var link = mysql.createConnection(dbcfg);
link.connect(function(err){
if(err){
console.log(err);
return;
}
console.log('mysql create connect success!');
});
//执行sql语句
var sql = 'SELECT 1+1 AS solution';
link.query(sql,function(err,rows,fields){
if(err){
console.log(err);
return;
}
var msg = 'The solution is '+rows[0].solution;
console.log(msg);
});
//插入数据
var sql = "INSERT INTO user(username,password) VALUES(?,?)";
var para = ['alice','123456'];
link.query(sql,para,function(err,ret){
if(err){
console.log(err.message);
return;
}
console.log(ret);
});
//关闭连接
link.end(function(err){
if(err){
return;
}
console.log('mysql connect end!');
});
//断线重连
function handleDisconnect(){
link = mysql.createConnection(dbcfg);
link.connect(function(err){
if(err){
console.log('数据库断线重连:'+new Date());
setTimeout(handleDisconnect,2000);//定时2秒重连
return;
}
console.log('数据库连接成功:'+new Date());
});
//监听数据库连接是否断掉
link.on('error',function(err){
console.log('数据库连接断线: ',err);
if(err.code === 'PROTOCOL_CONNECTION_LOST'){
handleDisconnect();
} else{
throw err;
}
});
}
handleDisconnect();
执行事务
关闭连接
- end() 在执行query()后执行,end()接收一个回调函数,query()执行出错仍结束连接,错误会返回给回调函数err参数可在回调函数中出力。
- destory() 暴力关闭,无回调函数,立即执行不管query() 是否完成。
断线重连
[错误代码] error code: PROTOCOL_CONNECTION_LOST
Chapter05.Express框架与EJS模板引擎
NodeJS提供HTTP模块,HTTP模块提供底层接口但不便于开发。从Express框架着手去进行Web开发,Express实现更好更高层的接口,使Web开发更加便捷。
Express是一个轻量级、简洁、易用的NodeJS Web MVC开发框架,Express基于NodeJS原有模块并对Web开发所需的功能封装。
安装
// -g 表示全局安装
npm install -g express-generator
express -V
案例:创建express的web应用
express webapp
下载依赖
npm install
添加端口监听
app.listen(8100,function(){
console.log('server start');
});
安装supervisor每次修改后自动重启
npm install -g supervisor
supervisor app.js
express默认实用模板引擎为jade
模板引擎ejs
创建express+ejs项目
express -e webapp
cd webapp && npm install
webapp/app.js 添加8100端口监听
app.listen(8100,function(){
console.log('server start');
});
webapp/routes/index.js修改路由
router.get('/', function(req, res, next) {
res.render('index', { title: 'Express', list:[{username:'alice'},{username:'ben'},{username:'carl'}] });
});
webapp/view/index.ejs修改模板
<!DOCTYPE html>
<html>
<head>
<title><%= title %></title>
<link rel='stylesheet' href='/stylesheets/style.css' />
</head>
<body>
<h1><%= title %></h1>
<% list.forEach(function(item){ %>
<p><%=item.username%></p>
<% }) %>
<p>Welcome to <%= title %></p>
</body>
</html>
浏览器查看结果
http://localhost:8100
Chapter06.构建网络应用基础
案例1:接收GET提交
创建应用
cd workspace
express -e webapp
cd webappp && npm install
添加路由
# webapp/app.js
var subform = require('./routes/subform');
app.use('/subform', subform);
监听端口
# webapp/app.js
app.listen(8100,function(){
console.log('server start');
});
创建路由文件
# webapp/routes/subform.js
var express = require('express');
var router = express.Router();
router.get('/',function(req,res){
/*
//接收GET参数并输入控制台
var username = req.query.username;
var password = req.query.password;
var username = req.param('username');
var password = req.param('password');
console.log(username,password);
*/
//渲染页面
res.render('subform',{title:'提交表单'});
});
module.exports = router;
创建公共模板
# webapp/views/nav.ejs
<ul class="list-group">
<li class="list-group-item"><a href="/">首页</a></li>
<li class="list-group-item"><a href="/subform">提交表单</a></li>
<li class="list-group-item"><a href="/session">回话控制</a></li>
<li class="list-group-item"><a href="/cookie">Cookie设置</a></li>
<li class="list-group-item"><a href="/crypto">字符串加密</a></li>
</ul>
创建表单模样
# webapp/views/subform.ejs
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title><%=title%></title>
<link rel="stylesheet" href="/stylesheets/bootstrap.css"/>
</head>
<body>
<% include nav %>
<form role="form">
<p class="form-group">
<label for="username">账户</label>
<input type="text" class="form-control" id="username" name="username" placeholder="账户"/>
</p>
<p class="form-group">
<label for="password">密码</label>
<input type="password" class="form-control" name="password" id="password" placeholder="密码"/>
</p>
<p class="form-group">
<label for="repassword">重复密码</label>
<input type="password" class="form-control" name="repassword" id="repassword" placeholder="重复密码"/>
</p>
<button class="btn btn-primary" id="submit">注册</button>
<a href="/login" class="btn btn-default">登录</a>
</form>
</body>
</html>
案例2:接收POST提交
路由文件:webapp/routes/subform.js
router.post('/',function(req,res){
//接收参数
// var username = req.body.username;
// var password = req.body.password;
var username = req.param('username');
var password = req.param('password');
console.log(username,password);
res.render('subform',{title:'表单提交'});
});
小结
GET和POST方式接收值,从直接效果上来看。
- req.query 接收GET方式提交参数
- req.body 接收POST方式提交参数
- req.params 接收GET和POST提交参数
关于req.body
Express出力post请求是通过中间件bodyParser来完成的,从app.js文件可发现:
var bodyParser = require('body-parser');
...
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
bodyParser中间件分析 application/x-www-form-urlencoded
和application/json
请求,并将变量存入 req.body后才能获取。
案例3:字符串加密
表单提交后比如密码等敏感信息需进行加密出力,NodeJS提交加密模块crypto
。
入口文件添加路由
webapp/app.js
var crypto = require('./routes/crypto');
app.use('/crypto', crypto);
创建路由
webapp/routes/crypto.js
var express = require('express');
var router = express.Router();
var crypto = require('crypto');//载入加密模块
// 获取GET参数
router.get('/',function(req,res){
res.render('crypto', {title:'字符串加密'});
});
//处理POST提交
router.post('/',function(req,res){
//获取参数
var username = req.body.username;
var password = req.body.password;
//生成口令的散列值
var md5 = crypto.createHash('md5');
var md5pwd = md5.update(password).digest('hex');
console.log(username,password,md5pwd);
res.render('crypto',{title:'加密处理'});
});
//暴露接口
module.exports = router;
创建视图
webapp/views/crypto.ejs
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title><%=title%></title>
<link rel="stylesheet" href="/stylesheets/bootstrap.css"/>
</head>
<body>
<% include nav %>
<form method="post">
<p class="form-group">
<label for="username">账户</label>
<input type="text" class="form-control" id="username" name="username" placeholder="账户"/>
</p>
<p class="form-group">
<label for="password">密码</label>
<input type="password" class="form-control" name="password" id="password" placeholder="密码"/>
</p>
<button class="btn btn-primary" type="submit">登录</button>
</form>
</body>
</html>
小结:
实用crypto提供的createHash(algorithm),采用给定算法生成hash对象。NodeJS的Hash算法提供了md5、sha1、sha264等。
update(data,[input_encoding])通过指定的input_encoding和传入的data数据更新hash对象,input_encoding为可选参数,无传入则作为buffer处理。
案例:会话控制
Internet通讯协议分为stateful和stateless,http是stateless协议及客户端发送请求到服务器建立连接,请求得到响应后立即终端,服务器不记录状态,服务器想确定是那个客户端提交的请求,就必须借助于session和cookie。session存在于服务器端,需cookie协助才能完成,服务端和客户端通过session_id来建立联系。
express可实用中间件express-session来实用session。
思路:登录判断,不同页面中判断是否具有session,若有则表示登录,无则表示未登录。
步骤1:npm安装中间件
webapp/package.json
"dependencies": {
"express-session":"latest",
}
执行并下载更新
npm install
步骤2:入口文件添加session模块
webapp/app.js
//加载回话模块
var session = require('express-session');
//传入秘钥加session_id
app.use(cookieParser('junchow'));
//实用中间件
app.use(session({secret:'junchow'}));
步骤3:入口添加登录控制路由
webapp/app.js
var login = require('./routes/login');//表单提交
app.use('/login',login);
步骤4:创建登录路由控制
webapp/routes/login.js
var express = require('express');
var router = express.Router();
var title = '登录';
router.get('/',function(req,res){
//清除session
//req.session.destroy();
//判断session中是否具有登录标识符isLogin
if(req.session.isLogin){
//控制台输出
console.log('session isLogin : '+req.session.isLogin);
//页面分配变量
res.locals.isLogin = req.session.isLogin;
}
//页面渲染
res.render('login',{title:title});
});
router.post('/',function(req,res){
//提交成功并写入session
req.session.isLogin = true;
req.locals.isLogin = req.session.isLogin;
res.render('login',{title:title});
});
module.exports = router;
步骤5:创建登录视图
webapp/views/login.ejs
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title><%=title%></title>
<link rel="stylesheet" href="/stylesheets/bootstrap.css"/>
</head>
<body>
<% include nav %>
<% if(locals.isLogin){ %>
<p class="alert alert-success">用户已登录</p>
<% }else{ %>
<form method="post">
<input type="submit" value="登录" class="btn btn-primary">
</form>
<% } %>
</body>
</html>
出现问题:
Cannot set property 'isLogin' of undefined
案例:Cookies
cookies保存在客户端安全性比较低,一般要存入加密后的信息,建议要设置实用过期时间或不使用时删除。cookies使用场景例如登录中“记录密码”或“自动登录”。
步骤1:webapp/app.js
var cookie = require('./routes/cookie');
app.use('/cookie',cookie);
步骤2:webapp/routes/cookie.js
var express = require('express');
var router = express.Router();
var title = 'cookies';
router.get('/',function(req,res){
if(req.cookies.isLogin){
console.log('cookies isLogin: '+req.cookies.isLogin);
req.session.isLogin = req.cookies.isLogin;
}
if(req.session.isLogin){
console.log('cookies isLogin: ' + req.session.isLogin);
res.locals.isLogin = req.session.isLogin;
}
res.render('cookie',{title:title});
});
router.post('/',function(req,res){
req.session.isLogin = true;
res.locals.isLogin = req.session.isLogin;
//设置cookie过期时长为60000毫秒(1分钟)
res.cookie('isLogin',true,{maxAge:60000});
res.render('cookie',{title:title});
});
module.exports = router;
步骤3:webapp/views/cookie.ejs
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title><%=title%></title>
<link rel="stylesheet" href="/stylesheets/bootstrap.css"/>
</head>
<body>
<% include nav %>
<% if(locals.isLogin){ %>
<p class="alert alert-warning">已登录</p>
<% }else{ %>
<form method="post">
<input type="submit" value="登录" class="btn btn-primary">
</form>
<% } %>
</body>
</html>
小结:清除session和cookie
// 清除cookies
res.clearCookie('isLogin');
// 清除session
req.session.destroy();
Chatper07.构架网络应用案例
步骤1:创建项目
cd workspace
express -e webapp
cd webapp && npm install
步骤2:创建数据库
CREATE DATABASE IF NOT EXISTS `test` CHARSET utf8;
USE test;
SET FOREIGN_KEY_CHECKS=0;
DROP TABLE IF EXISTS `user`;
CREATE TABLE IF NOT EXISTS `user`(
id int(11) unsigned NOT NULL PRIMARY KEY COMMENT '主键',
username varchar(20) NOT NULL DEFAULT '' COMMENT '账户',
password varchar(64) NOT NULL DEFAULT '' COMMENT '密码'
)ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='用户表';
步骤3:安装模块
修改package.json安装session和mysql模块
{
"name": "webapp",
"version": "0.0.0",
"private": true,
"scripts": {
"start": "node ./bin/www"
},
"dependencies": {
"body-parser": "~1.17.1",
"cookie-parser": "~1.4.3",
"debug": "~2.6.3",
"ejs": "~2.5.6",
"express": "~4.15.2",
"morgan": "~1.8.1",
"serve-favicon": "~2.4.2",
"express-session":"latest",
"mysql":"latest"
}
}
安装模块
npm install
webapp/app.js添加代码
//加载回话
var session = require('express-session');
//传入秘钥加session_id
app.use(cookieParser('junchow'));
//实用中间件
app.use(session({secret:'junchow'}));
步骤4:前端资源
npm install bootstrap
npm install jquery
从webapp/node_module/中寻找bootstrap和jquery提取文件加入public目录。
步骤5.规划路由
首页:/
注册:register
routes/register.js
views/register.ejs
登录:login
routes/login.js
views/login.ejs
安全退出:logout
routes/logout.js
views/logout.ejs
公共页面:
header.ejs
步骤6:具体实现
创建数据访问方法
webapp/models/user.js
var mysql = require('mysql');
var dbname = 'test';
//创建连接
var dbcfg = {host:'127.0.0.1', user:'root', password:'root'};
var pool = mysql.createPool(dbcfg);
pool.on('connection', function(connection){
connection.query('SET SESSION auto_increment_increment = 1');
});
pool.getConnection(function(err,connection){
//选择数据库
var sql = "USE "+ dbname;
connection.query(sql, function(err){
if(err){
console.log("USE ERROR: "+err.message);
return;
}
console.log("USE SUCCESS");
});
//保存数据
User.prototype.save = function save(callback){
var obj = {username:this.username, password:this.password};
var sql = "INSERT INTO user(username,password) VALUES(?,?)";
connection.query(sql, [obj.username, obj.password], function(err,result){
if(err){
console.log("save error: "+sql, err.message);
return;
}
connection.release();
console.log("invoked save");
callback(err,result);
});
};
// 根据账户获取用户数目
User.getUserNumByName = function getUserNumByName(username,callback){
var sql = "SELECT COUNT(1) AS num FROM user WHERE username=?";
connection.query(sql,[username],function(err,result){
if(err){
console.log("getUserNumByName error:"+err.message);
return;
}
console.log("invoked getUserNumByName");
callback(err,result);
});
};
//根据账户获取用户信息
User.getUserByUsername = function getUserByUsername(username,callback){
var sql = "SELECT * FROM user WHERE 1=1 AND username=?";
connection.query(sql, [username], function(err,result){
if(err){
console.log("getUserByUsername error: "+err.message);
return;
}
connection.release();
console.log("invoked getUserByUsername");
callback(err,result);
});
};
});
//暴露接口
function User(obj){
this.username = obj.username;
this.password = obj.password;
}
module.exports = User;
注册模块
webapp/app.js
var register = require('./routes/register');
app.use('/register',register);
webapp/routes/register.js
var express = require('express');
var router = express.Router();
var User = require('../models/user.js');
var crypto = require('crypto');
var title = '注册';
router.get('/', function(req, res, next) {
res.render('register', { title: title});
});
router.post('/', function(req,res){
var username = req.body['username'];
var password = req.body['password'];
var repassword = req.body['repassword'];
var md5 = crypto.createHash('md5');
password = md5.update(password).digest('hex');
var obj = new User({username:username, password:password});
//检查账户是否存在
User.getUserNumByName(obj.username, function(err,result){
if(result!=null && result[0]['num']>0){
err = '账户已存在';
}
if(err){
res.locals.error = err;
res.render('register', {title:title});
return;
}
obj.save(function(err,result){
console.log(err,result);
if(err){
res.locals.error = err;
res.render('register',{title:title});
return;
}
if(result.insertId > 0){
res.locals.success = '注册成功,请<a class="btn btn-link" href="/login" role="button">登录</a>!';
}else{
res.locals.error = err;
}
res.render('register',{title:title});
});
});
});
module.exports = router;
webapp/views/register.ejs
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title><%=title%></title>
<link rel="stylesheet" href="/stylesheets/bootstrap.css"/>
</head>
<body>
<div id="container" class="container">
<h2 class="page-header"><%=title%></h2>
<% if(locals.success){%>
<div id="success" class="alert alert-success"><%-success%></div>
<%}%>
<% if(locals.error){ %>
<div id="warning" class="alert alert-warning"><%=error%></div>
<%}%>
<form role="form" method="post">
<p class="form-group">
<label for="username">账户</label>
<input type="text" class="form-control" id="username" name="username" placeholder="账户"/>
</p>
<p class="form-group">
<label for="password">密码</label>
<input type="password" class="form-control" name="password" id="password" placeholder="密码"/>
</p>
<p class="form-group">
<label for="repassword">重复密码</label>
<input type="password" class="form-control" name="repassword" id="repassword" placeholder="重复密码"/>
</p>
<button class="btn btn-primary" id="submit">注册</button>
<a href="/login" class="btn btn-default">登录</a>
</form>
</div>
<script src="/javascripts/jquery.js"></script>
<script>
String.prototype.format = function(args){
var that = this;
if(arguments.length > 0){
if(arguments.length == 1 && typeof(args)=='object'){
for(var k in args){
if(args[k] != undefined){
var reg = new RegExp('({'+k+'})', 'g');
that = that.replace(reg, args[k]);
}
}
}else{
for(var i=0; i<arguments.length; i++){
if(arguments[i] != undefined){
var reg = new RegExp('({)'+i+'(})','g');
that = that.replace(reg, arguments[i]);
}
}
}
}
return that;
}
//表单验证
$(function(){
$('#submit').on('click',function(){
var username = $('#username');
var usernameVal = $.trim(username.val());
var password = $('#password');
var passwordVal = $.trim(password.val());
var repassword = $('#repassword');
var repasswordVal = $.trim(repassword.val());
var tip = '<div id="tip" class="alert alert-warning">{0}</div>';
$('#success,#warning,#tip').remove();
if(usernameVal.length == 0){
$('#container').prepend(tip.format('账户不能为空!'));
username.focus();
return false;
}
if(passwordVal.length == 0){
$('#container').prepend(tip.format('密码不能为空!'));
password.focus();
return false;
}
if(repasswordVal.length == 0){
$('#container').prepend(tip.format('重复密码不能为空!'));
repassword.focus();
return false;
}
if(passwordVal != repasswordVal){
$('#container').prepend(tip.format('两次密码不一致!'));
repassword.focus();
return false;
}
return true;
});
});
</script>
</body>
</html>
登录模块
webapp/app.js
var login = require('./routes/login');
app.use('/login',login);
webapp/routes/login.js
var express = require('express');
var router = express.Router();
var crypto = require('crypto');
var User = require('../models/user.js');
var title = '登录';
router.get('/',function(req,res){
res.render('login',{title:title});
});
router.post('/',function(req,res){
var username = req.body['username'];
var password = req.body['password'];
var rem = req.body['rem'];
var md5 = crypto.createHash('md5');
User.getUserByUsername(username,function(err,ret){
if(ret==''){
res.locals.error = '账户不存在';
res.render('login',{title:title});
return;
}
password = md5.update(password).digest('hex');
if(ret[0].username!=username || ret[0].password!=password){
res.locals.error = '账户或密码错误';
res.render('login',{title:title});
return;
}else{
if(rem){
res.cookie('isLogin', username, {maxAge:60000});
}
res.locals.username = username;
req.session.username = res.locals.username;
console.log(req.session.username);
res.redirect('/home');
return;
}
});
});
module.exports = router;
webapp/views/login.ejs
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title><%=title%></title>
<link rel="stylesheet" href="/stylesheets/bootstrap.css"/>
</head>
<body>
<div id="container" class="container">
<h2 class="page-header"><%=title%></h2>
<% if(locals.success){%>
<div id="success" class="alert alert-success"><%-success%></div>
<%}%>
<% if(locals.error){ %>
<div id="warning" class="alert alert-warning"><%=error%></div>
<%}%>
<form role="form" method="post">
<p class="form-group">
<label for="username">账户</label>
<input type="text" class="form-control" id="username" name="username" placeholder="账户"/>
</p>
<p class="form-group">
<label for="password">密码</label>
<input type="password" class="form-control" name="password" id="password" placeholder="密码"/>
</p>
<p class="form-group">
<label class="checkbox"><input type="checkbox" name="rem" id="rem">自动登录</label>
</p>
<button class="btn btn-primary" id="submit">登录</button>
<a href="/register" class="btn btn-default">注册</a>
</form>
</div>
<script src="/javascripts/jquery.js"></script>
<script>
String.prototype.format = function(args){
var that = this;
if(arguments.length > 0){
if(arguments.length == 1 && typeof(args)=='object'){
for(var k in args){
if(args[k] != undefined){
var reg = new RegExp('({'+k+'})', 'g');
that = that.replace(reg, args[k]);
}
}
}else{
for(var i=0; i<arguments.length; i++){
if(arguments[i] != undefined){
var reg = new RegExp('({)'+i+'(})','g');
that = that.replace(reg, arguments[i]);
}
}
}
}
return that;
}
//表单验证
$(function(){
$('#submit').on('click',function(){
var username = $('#username');
var usernameVal = $.trim(username.val());
var password = $('#password');
var passwordVal = $.trim(password.val());
var tip = '<div id="tip" class="alert alert-warning">{0}</div>';
$('#success,#warning,#tip').remove();
if(usernameVal.length == 0){
$('#container').prepend(tip.format('账户不能为空!'));
username.focus();
return false;
}
if(passwordVal.length == 0){
$('#container').prepend(tip.format('密码不能为空!'));
password.focus();
return false;
}
return true;
});
});
</script>
</body>
</html>
首页模块
webapp/app.js
var home = require('./routes/home');
app.use('/home',home);
webapp/routes/home.js
var express = require('express');
var router = express.Router();
var title = '主页';
router.get('/',function(req,res){
if(req.cookies.isLogin){
console.log('cookies isLogin: '+req.cookies.isLogin);
req.session.username = req.cookies.isLogin;
}
if(req.session.username){
console.log('session username : '+req.session.username);
res.locals.username = req.session.username;
}else{
res.redirect('/login');
return;
}
res.render('home',{title:title});
});
module.exports = router;
webapp/views/home.ejs
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title><%=title%></title>
<link rel="stylesheet" href="/stylesheets/bootstrap.css"/>
</head>
<body>
<% include header %>
<div id="container" class="container">
</div>
<script src="/javascripts/jquery.js"></script>
</body>
</html>
webapp/views/header.ejs
<div class="navbar navbar-default navbar-static-top" role="navigation">
<div class="container">
<div class="navbar-header"><a href="/home" class="navbar-brand">home</a></div>
<div class="navbar-collapse collapse">
<ul class="nav navbar-nav navbar-right">
<% if(locals.username){%><li><a href="#"><%=username%></a></li><%}%>
<li><a href="/logout">安全退出</a></li>
</ul>
</div>
</div>
</div>
退出模块
webapp/app.js
var logout = require('./routes/logout');
app.use('/logout',logout);
webapp/routes/logout.js
var express = require('express');
var router = express.Router();
router.get('/',function(req,res){
req.session.destroy();
res.redirect('/home');
});
module.exports = router;
Chatper08.文件上传
formidable的文件上传模块
安装模块:webapp/package.json
"formidable":"latest"
安装依赖
npm install
入口文件添加:webapp/app.js
var upload = require('./routes/upload');
app.use('/upload',upload);
路由处理文件:web/routes/upload.js
var express = require('express');
var router = express.Router();
//上传处理类
var formidable = require('formidable');
var fs = require('fs');
var UPLOAD_FOLDER = '/upload/';
var title = '上传';
router.get('/',function(req,res){
res.render('upload',{title:title});
});
router.post('/',function(req,res){
//创建上传表单
var form = new formidable.IncomingForm();
//设置字符集
form.encoding = 'utf-8';
//设置上传陌路
form.uploadDir = 'public'+UPLOAD_FOLDER;
//是否保留文件后缀
form.keepExtensions = true;
//显示上传文件大小
form.maxFieldsSize = 2*1024*1024;
//上传处理
form.parse(req, function(err,fields,files){
if(err){
res.locals.error = true;
res.render('upload', {title:title});
return;
}
var ext = '';
switch(files.type){
case 'image/pjpeg': ext = 'jpg'; break;
case 'image/jpeg': ext = 'jpg'; break;
case 'image/png': ext = 'png'; break;
case 'image/x-png': ext = 'png'; break;
}
if(ext.length == 0){
res.locals.error = '仅支持png或jpg格式图片';
res.render('upload',{title:title});
return;
}
var picname = Math.random()+'.'+ext;
var picfile = form.uploadDir+picname;
fs.renameSync(files.path, picfile);
});
res.locals.success = '上传成功';
res.render('upload', {title:title});
});
module.exports = router;
模板文件:webapp/views/upload.ejs
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title><%=title%></title>
<link rel="stylesheet" href="/stylesheets/bootstrap.css"/>
</head>
<body>
<div id="container" class="container">
<form class="form" role="form" method="post" enctype="multipart/form-data">
<h2 class="page-header">文件上传</h2>
<p class="input-group">
<input type="file" name="pid" id="pic" class="form-control"/>
<span class="input-group-btn">
<button class="btn btn-primary" type="submit" id="submit">上传</button>
</span>
</p>
</form>
</div>
<script src="/javascripts/jquery.js"></script>
<script>
String.prototype.format = function(args){
var that = this;
if(arguments.length > 0){
if(arguments.length == 1 && typeof(args) == 'object'){
for(var i in args){
if(args[i] != undefined){
var reg = new RegExp("({"+i+"})", "g");
that = that.replace(reg, args[i]);
}
}
}else{
for(var i=0; i<arguments.length;i++){
if(arguments[i] != undefined){
var reg = new RegExp("({)"+i+"(})","g");
that = that.replace(reg,arguments[i]);
}
}
}
}
return that;
}
$(function(){
$('#submit').on('click',function(){
var picVal = $('#pic').val();
var tip = '<div id="tip" class="alert alert-warning">{0}</div>';
if(picVal.length == 0){
$('#container').prepend(tip.format('请选择上传文件'));
return false;
}
var ext = picVal.substring(picVal.lastIndexOf('.'), picVal.length).toLowerCase();
console.log(ext);
if(ext!='.png' && ext!='.jpg'){
$('#container').prepend(tip.format('仅支持png或jpg格式图片!'));
return false;
}
return true;
});
});
</script>
</body>
</html>
运行中错误
Error: Can't set headers after they are sent.
at ServerResponse.OutgoingMessage.setHeader (_http_outgoing.js:357:11)
at ServerResponse.header (D:\nodejs\webapp\node_modules\express\lib\response
.js:725:10)
at ServerResponse.send (D:\nodejs\webapp\node_modules\express\lib\response.j
s:170:12)
at done (D:\nodejs\webapp\node_modules\express\lib\response.js:962:10)
at tryHandleCache (D:\nodejs\webapp\node_modules\ejs\lib\ejs.js:208:10)
at View.exports.renderFile [as engine] (D:\nodejs\webapp\node_modules\ejs\li
b\ejs.js:412:10)
at View.render (D:\nodejs\webapp\node_modules\express\lib\view.js:128:8)
at tryRender (D:\nodejs\webapp\node_modules\express\lib\application.js:640:1
0)
at EventEmitter.render (D:\nodejs\webapp\node_modules\express\lib\applicatio
n.js:592:3)
at ServerResponse.render (D:\nodejs\webapp\node_modules\express\lib\response
.js:966:7)
Chapter09.与Redis交互
Redis简介
在windows中下载redis服务端解压后运行redis-server.exe,redis.conf作为配置文件。
安装node_redis
npm install redis
npm install hiredis redis
区别:hiredis是非阻塞的,速度更快。node_redis默认以它作为解释器,若没安装则会用纯javascript解释器。
创建连接
//连接到本地redis服务器
var redis = require('redis');
var client = redis.createClient();//返回RedisClient对象
//连接到远程redis服务器
var redis = require('redis');
var RDS_PORT = 6379;
var RDS_HOST = '127.0.0.1';
var RDS_OPTS = {};
var client = redis.createClient(RDS_PORT,RDS_HOST,RDS_OPTS);//返回RedisClient对象
设置远程连接密码
在redis.conf配置文件打开requirepass foobared
,其中foobared是密码。
密码认证
//连接到redis服务器并认证
var redis = require('redis');
var RDS_PORT = 6379;
var RDS_HOST = '127.0.0.1';
var RDS_PWD = 'foobared';
var RDS_OPTS = {auth_pass:RDS_PWD};
//返回RedisClient对象
var client = redis.createClient(RDS_PORT,RDS_HOST,RDS_OPTS);
//密码认证
client.auth(RDS_PWD,function(){
console.log('通过认证');
});
触发事件
//当与redis服务器连接成功后触发ready事件
//表示已准备好接收命令,当此事件出发之前client命令会存在的队列中,当一切准备就绪后调用。
client.on('ready',function(err){
console.log('ready',err);
});
//在不设置client.options.no_ready_check情况下,客户端触发connect同时会发出ready。
//若设置了client.options.no_ready_check,当这个stream被连接时会触发connect,此时就可以自由尝试发命令。
client.on('connect',function(){
//单值设置
client.set('author', 'junchow', redis.print);//设置单个key和value
client.get('author',redis.print);//通过key获取value
//多值设置
client.hmset('short',{'js':'javascript', 'C#':'C sharp'}, redis.print);
client.hmset('short', 'SQL','Structured Query Language', 'HTML', 'HyperText Markup Language', redis.print);
client.hgetall('short',function(err,res){
if(err){
console.log('Error:' + err);
return;
}
console.dir(res);
});
});
Chapter10.与MongoDB交互
MongoDB高性能的NoSQL数据库,支持索引、集群、复制、故障转移、各种语言驱动程序,高伸缩性。
MongoDB的NodeJS驱动:node-mongodb-native
[下载MongoDB](http://www.mongodb.org/downloads
安装服务端
步骤1:创建数据库和日志存放目录
在MongoDB目录下创建db和log两个文件夹,并在log目录下新建mongodb.log文件。
步骤2:创建配置文件
在MongoDB下的bin目录下创建mongo.conf文件,添加配置
##数据库目录
dbpath=D:\MongoDB\db
##日志输出文件
logpath=D:\MongoDB\log\mongodb.log
步骤3:添加环境变量
将D:\MongoDB\bin
添加到环境变量path中
步骤4:安装服务
mongod --config "D:\MongoDB\bin\mongo.conf" --install
步骤5:启动服务
net start mongodb
步骤6:打开服务端
mongo
备注:MongoDB默认端口为27017
库操作
//查看数据库列表
show dbs
//检查当前选择的数据库
db
//创建数据库
use test
//use创建的数据库需插入数据后才能显示
db.user.insert({"name":"alice"})
//删除当前数据库
db.dropDatabase()
集合操作
//创建集合
db.createCollection(name,options)
//查看集合列表
show collections
//删除集合
db.collection.drop()
//向集合中插入数据
db.user.insert({"name":"alice"})
//获取集合数据
db.user.find()
//获取集合条数
db.user.find().count()
NodeJS操作MongoDB
安装
npm install mongodb
创建连接
var MongoClient = require('mongodb').MongoClient;
var MongoLink = 'mongodb://127.0.0.1:27017/test';
//连接数据库
MongoClient.connect(MongoLink,function(err,db){
console.log('连接成功');
});
插入数据
var MongoClient = require('mongodb').MongoClient;
var MongoLink = 'mongodb://127.0.0.1:27017/test';
//插入数据
var insertData = function(db,callback){
//连接集合
var collection = db.collection('user');
//插入数据
var data = [
{"name":"alice","age":18},
{"name":"ben","age":19},
{"name":"carl","age":20}
];
collection.insert(data,function(err,res){
if(err){
console.log(err);
return;
}
callback(res);
});
};
//连接数据库
MongoClient.connect(MongoLink,function(err,db){
console.log('连接成功');
//插入数据
insertData(db,function(res){
console.log(res);
db.close();//关闭数据库
});
});
查询数据
var MongoClient = require('mongodb').MongoClient;
var MongoLink = 'mongodb://127.0.0.1:27017/test';
//查询数据
var findData = function(db,callback){
//连接集合
var collection = db.collection('user');
//查询条件
var where = {"name":"alice"};
//查询数据
collection.find(where).toArray(function(err,res){
if(err){
console.log(err);
return;
}
callback(res);
});
};
//连接数据库
MongoClient.connect(MongoLink,function(err,db){
console.log('连接成功');
//查询数据
findData(db,function(res){
console.log(res);
db.close();
});
});
更新数据
var MongoClient = require('mongodb').MongoClient;
var MongoLink = 'mongodb://127.0.0.1:27017/test';
//修改数据
var updateData = function(db,callback){
var collection = db.collection('user');
var where = {"name":"alice"};
var data = {$set:{"age":30}};
collection.update(where, data, function(err,res){
if(err){
console.log(err);
return;
}
callback(res);
});
};
//连接数据库
MongoClient.connect(MongoLink,function(err,db){
console.log('连接成功');
//更新数据
updateData(db,function(res){
console.log(res);
db.close();
});
});
注意:以上更新发现仅更新一条
删除数据
var MongoClient = require('mongodb').MongoClient;
var MongoLink = 'mongodb://127.0.0.1:27017/test';
//删除数据
var removeData = function(db,callback){
var collection = db.collection('user');
var where = {"name":"alice"};
collection.remove(where,function(err,res){
if(err){
console.log(err);
return;
}
callback(res);
});
};
//连接数据库
MongoClient.connect(MongoLink,function(err,db){
console.log('连接成功');
//删除数据
removeData(db,function(res){
console.log(res);
db.close();
});
});
Chapter11.数据采集器
使用request和cheerio发送请求并实用正则解析数据来完成数据采集。
- request 用于http请求
- cheerio 用于提取request返回的html中所需数据
步骤1:安装
webapp/package.json中依赖中添加
"request":"*",
"cheerio":"*"
执行命令
cd webapp && npm install
采集代码
思路:实用request一个get请求,请求回调中返回body即html代码,通过cherrio库的jquery语法操作解析,获取所需要的数据。
/*数据采集*/
var request = require('request');
var cheerio = require('cheerio');
var url = 'http://36kr.com';
//开启数据采集器
function startup(){
doRequest(url);
}
//请求数据
function doRequest(url){
request({url:url,method:'GET'},function(err,res,body){
if(err){
console.log(err,res,body);
return;
}
//解析数据
doParse(body);
});
}
//解析数据
function doParse(body){
var $ = cheerio.load(body);
var articles = $('article');
for(var i=0; i=articles.length; i++){
var article = articles[i];
var desc = $(article).find('.desc');
if(desc.length==0){
continue;
}
var coverDom = $(article).children().first();
var titleDom = $(desc).find('.info_flow_news_title');
var timeagoDom = $(desc).find('.timeago');
var titleVal = titleDom.text();
var urlVal = titleDom.attr('href');
var timeVal = timeDom.attr('title');
var timeSecs = new Date(timeVal).getTime()/1000;
var converUrl = converDom.attr('data-lazyload');
if(urlVal!=undefined){
console.info('----------------------------------');
console.info('标题:'+titleVal);
console.info('地址:'+urlVal);
console.info('时间:'+timeSecs);
console.info('封面:'+converUrl);
}else{
console.info('error');
}
}
}
startup();
升级版本:加入代理
若需长期实用为避免网站屏蔽,还需添加一个代理列表。
/*数据采集*/
var request = require('request');
var cheerio = require('cheerio');
var url = 'http://36kr.com';
//长期采集为防止网站屏蔽加入代理列表
var proxy = require('./proxylist.js');
//开启数据采集器
function startup(){
doRequest(url);
}
//请求数据
function doRequest(url){
//实用代理发送请求
request({url:url,method:'GET',proxy:proxy.getProxy()},function(err,res,body){
if(err){
console.log(err,res,body);
return;
}
//解析数据
doParse(body);
});
}
//解析数据
function doParse(body){
var $ = cheerio.load(body);
var articles = $('article');
for(var i=0; i=articles.length; i++){
var article = articles[i];
var desc = $(article).find('.desc');
if(desc.length==0){
continue;
}
var coverDom = $(article).children().first();
var titleDom = $(desc).find('.info_flow_news_title');
var timeagoDom = $(desc).find('.timeago');
var titleVal = titleDom.text();
var urlVal = titleDom.attr('href');
var timeVal = timeDom.attr('title');
var timeSecs = new Date(timeVal).getTime()/1000;
var converUrl = converDom.attr('data-lazyload');
if(urlVal!=undefined){
console.info('----------------------------------');
console.info('标题:'+titleVal);
console.info('地址:'+urlVal);
console.info('时间:'+timeSecs);
console.info('封面:'+converUrl);
}else{
console.info('error');
}
}
}
startup();
setInterval(startup,10000);//间隔执行
升级版本:支持HTTPS
//请求数据
function doRequest(url){
//支持HTTPS的header头
request({url:url,method:'GET',headers:{'User-Agent':'wilson'}},function(err,res,body){
if(err){
console.log(err,res,body);
return;
}
//解析数据
doParse(body);
});
}
Chapter12.定时任务
定时任务应用场景一般包括定时导出数据、定时发送消息、定时发送邮件给用户、定时备份文件。
目标:使用node-schedule来完成定时任务
安装:
npm install node-schedule
定时任务
var schedule = require('node-schedule');
function cron(){
//每隔30秒触发
schedule.scheduleJob('30 * * * * *',function(){
console.log(new Date());
})
}
cron();
递归定时器
var schedule = require('node-schedule');
function cron(){
//递归定时器
var rule = new schedule.RecurrenceRule();
rule.second = 0;
schedule.scheduleJob(rule,function(){
console.log(new Date());
});
}
cron();
对象文本定时器
var schedule = require('node-schedule');
function cron(){
var cfg = {hour:1, minute:1, dayOfWeek:1};
schedule.scheduleJob(cfg,function(){
console.log(new Date());
});
}
cron();
取消定时器
var schedule = require('node-schedule');
function cron(){
var counter = 1;
var i = schedule.scheduleJob('* * * * * *',function(){
console.log(counter);
counter++;
});
setTimeout(function(){
console.log('cancel');
i.cancel();
},5000);
}
cron();