Node.js学习笔记(14-21章)
目录
第14章:动态视图
概述
本章介绍如何在Express应用中实现动态视图,使用Hogan.js(hjs)作为模板引擎,结合MySQL数据库来渲染动态内容。
关键代码
服务器设置
javascript
const express = require("express")
const bodyParser = require("body-parser")
const knex = require("knex")
const staticAssets = __dirname + "/public";
const db = knex({
client: "mysql",
connection: {
host: "127.0.0.1",
user: "root",
database: "test",
}
})
express()
.use(bodyParser.json())
.set("view engine", "hjs")
.use(express.static(staticAssets))
.get("/", (req, res, next) => {
db("users").then((users) => {
res.render("users", {
title: "All Users",
users,
})
})
})
.get("/viewtweets/:user_id", (req, res, next) => {
const { user_id } = req.params;
db("tweets")
.where("user_id", user_id)
.then((tweets) => {
res.render("tweets", {
title: "My Users Tweets",
tweets,
})
})
})
// ... 其他路由
.listen(3000)
视图模板
users.hjs
html
<html>
<body>
<h1>{{title}}</h1>
<ul>
{{#users}}
<li>
<a href="/viewtweets/{{id}}">{{username}}</a>
</li>
{{/users}}
</ul>
</body>
</html>
tweets.hjs
html
<html>
<body>
<h1>{{title}}</h1>
<ul>
{{#tweets}}
<li>
{{text}}
</li>
{{/tweets}}
</ul>
</body>
</html>
要点
- 使用
res.render()
方法渲染视图,并传递数据对象 - 在模板中使用
{{变量名}}
来显示动态内容 - 使用
{{#数组}}
和{{/数组}}
来循环渲染数组内容 - 通过URL参数获取动态数据(如用户ID)
第15章:认证
概述
本章介绍如何在Express应用中实现用户认证,使用Passport.js作为认证中间件,结合express-session管理用户会话。
关键代码
服务器设置
javascript
const express = require("express")
const bodyParser = require("body-parser")
const session = require("express-session")
const passport = require("passport")
const db = require("./db")
require("./passport")
express()
.set("view engine", "hjs")
.use(bodyParser.json())
.use(bodyParser.urlencoded({extended: false}))
.use(session({ secret: "i love dogs", resave: false, saveUninitialized: false }))
.use(passport.initialize())
.use(passport.session())
.get("/", (req, res, next) => {
res.send({
session: req.session,
user: req.user,
authenticated: req.isAuthenticated(),
})
})
.get("/login", (req, res, next) => {
res.render("login")
})
.post("/login", passport.authenticate("local", {
successRedirect: "/",
failureRedirect: "/login",
}))
.get("/logout", (req, res, next) => {
req.session.destroy((err) => {
res.redirect("/login")
})
})
.get("/signup", (req, res, next) => {
res.render("signup")
})
.post("/signup", passport.authenticate("local-register", {
successRedirect: "/",
failureRedirect: "/signup",
}))
.listen(3000)
Passport配置
javascript
const bcrypt = require("bcrypt-nodejs")
const db = require("./db")
const passport = require("passport")
const LocalStrategy = require("passport-local").Strategy
passport.use(new LocalStrategy(authenticate))
passport.use("local-register", new LocalStrategy({passReqToCallback: true}, register))
function authenticate(email, password, done){
db("users")
.where("email", email)
.first()
.then((user) => {
if (!user || !bcrypt.compareSync(password, user.password)) {
return done(null, false, {message: "invalid user and password combination"})
}
done(null, user)
}, done)
}
function register(req, email, password, done) {
db("users")
.where("email", email)
.first()
.then((user) => {
if (user) {
return done(null, false, {message: "an account with that email has already been created"})
}
if (password !== req.body.password2) {
return done(null, false, {message: "passwords don't match"})
}
const newUser = {
first_name: req.body.first_name,
last_name: req.body.last_name,
email: email,
password: bcrypt.hashSync(password)
};
db("users")
.insert(newUser)
.then((ids) => {
newUser.id = ids[0]
done(null, newUser)
})
})
}
passport.serializeUser(function(user, done) {
done(null, user.id)
})
passport.deserializeUser(function(id, done) {
db("users")
.where("id", id)
.first()
.then((user) => {
done(null, user)
}, done)
})
登录表单
html
<form action="/login" method="post">
<div>
<label>Email:</label>
<input type="text" name="username"/>
</div>
<div>
<label>Password:</label>
<input type="password" name="password"/>
</div>
<div>
<input type="submit" value="Log In"/>
</div>
</form>
要点
- 使用
express-session
管理用户会话 - 使用
passport.initialize()
和passport.session()
初始化Passport - 使用
LocalStrategy
实现本地用户认证 - 使用
bcrypt
对密码进行加密和验证 - 实现
serializeUser
和deserializeUser
方法来管理用户会话 - 使用
req.isAuthenticated()
检查用户是否已认证
第16章:授权
概述
本章介绍如何在Express应用中实现用户授权,控制用户对特定资源的访问权限。
关键代码
服务器设置
javascript
const express = require("express")
const bodyParser = require("body-parser")
const session = require("express-session")
const passport = require("passport")
const authRoutes = require("./routes/auth")
const postsRoutes = require("./routes/posts")
const db = require("./db")
require("./passport")
express()
.set("view engine", "hjs")
.use(bodyParser.json())
.use(bodyParser.urlencoded({extended: false}))
.use(session({ secret: "i love dogs", resave: false, saveUninitialized: false }))
.use(passport.initialize())
.use(passport.session())
.use(authRoutes)
.use(postsRoutes)
.get("/", (req, res, next) => {
res.send({
session: req.session,
user: req.user,
authenticated: req.isAuthenticated(),
})
})
.listen(3000)
授权中间件
javascript
function loginRequired(req, res, next) {
if (!req.isAuthenticated()) {
return res.redirect("/login")
}
next()
}
function adminRequired(req, res, next) {
if (!req.user.is_admin) {
return res.render("403")
}
next()
}
帖子路由
javascript
router
.get("/posts", loginRequired, (req, res, next) => {
db("posts")
.where("user_id", req.user.id)
.then((posts) => {
res.render("posts", {
title: "your posts",
posts: posts,
})
})
})
.get("/allPosts", loginRequired, adminRequired, (req, res, next) => {
db("posts")
.then((posts) => {
res.render("posts", {
title: "all user posts",
posts: posts,
})
})
})
.get("/deletePost/:id", loginRequired, (req, res, next) => {
const query = db("posts").where("id", req.params.id)
if (!req.user.is_admin) {
query.andWhere("user_id", req.user.id)
}
query
.delete()
.then((result) => {
if (result === 0) {
return res.send("Error, could not delete post")
}
res.redirect("/posts")
})
})
要点
- 使用中间件函数实现授权控制
- 使用
loginRequired
中间件确保用户已登录 - 使用
adminRequired
中间件检查用户是否为管理员 - 在数据库查询中添加条件限制用户只能访问自己的资源
- 为管理员提供特殊权限(如查看所有帖子)
第17章:OAuth认证
概述
本章介绍如何在Express应用中实现OAuth认证,允许用户使用第三方服务(如GitHub)进行登录。
关键代码
Passport OAuth配置
javascript
passport.use(new GitHubStrategy({
clientID: "56608c7dcfff5b9ad908",
clientSecret: "3e6343a749ca8c0013dd1510409c9bff6427ad53",
callbackURL: "http://localhost:3000/auth/github/callback"
},
function(accessToken, refreshToken, profile, done) {
db("users")
.where("oauth_provider", "github")
.where("oauth_id", profile.username)
.first()
.then((user) => {
if (user) {
return done(null, user)
}
const newUser = {
oauth_provider: "github",
oauth_id: profile.username,
};
return db("users")
.insert(newUser)
.then((ids) => {
newUser.id = ids[0]
done(null, newUser)
})
})
}
))
OAuth路由
javascript
router
.get('/auth/github',
passport.authenticate('github', { scope: [ 'user:email' ] }))
.get('/auth/github/callback',
passport.authenticate('github', { failureRedirect: '/login' }),
function(req, res) {
res.redirect('/posts');
})
要点
- 使用
passport-github
策略实现GitHub OAuth认证 - 配置OAuth应用的clientID和clientSecret
- 设置回调URL处理认证结果
- 在数据库中存储OAuth提供商和用户ID
- 处理新用户和现有用户的不同逻辑
第18章:Redis缓存
概述
本章介绍如何在Express应用中使用Redis缓存来提高性能,减少数据库查询和计算密集型操作的负担。
关键代码
Redis配置
javascript
// cache.js
module.exports = require("express-redis-cache")()
服务器设置
javascript
const express = require("express")
const bodyParser = require("body-parser")
const session = require("express-session")
const RedisStore = require('connect-redis')(session)
const passport = require("passport")
const authRoutes = require("./routes/auth")
const postsRoutes = require("./routes/posts")
const cache = require("./cache")
const db = require("./db")
require("./passport")
express()
.set("view engine", "hjs")
.use(bodyParser.json())
.use(bodyParser.urlencoded({extended: false}))
.use(session({
store: new RedisStore(),
secret: "i love dogs",
resave: false,
saveUninitialized: false
}))
.use(passport.initialize())
.use(passport.session())
.use(authRoutes)
.use(postsRoutes)
.get("/", cache.route({expire: 200, prefix: "home"}), (req, res, next) => {
setTimeout(() => {
const headlines = [
"Fuschia is the New Black",
"What will the Pacific Ocean do Next?",
"Wall Street to Build Even More Walls",
];
res.render("headlines", {headlines: headlines})
}, 2000)
})
.listen(3000)
要点
- 使用
express-redis-cache
实现路由级别的缓存 - 使用
connect-redis
作为会话存储 - 为缓存设置过期时间和前缀
- 缓存适用于计算密集型或耗时的操作
- 缓存可以显著提高应用性能,特别是对于频繁访问但很少变化的数据
第19章:数据库自动化
概述
本章介绍如何使用Knex.js实现数据库迁移和种子数据,自动化数据库结构和初始数据的管理。
关键代码
Knex配置
javascript
module.exports = {
development: {
client: "mysql",
connection: {
host: "127.0.0.1",
user: "root",
database: "test",
}
},
production: {
client: "mysql",
connection: {
host: "production",
user: "production",
database: "test",
}
}
}
数据库迁移
javascript
exports.up = function(knex, Promise) {
return knex.raw(`
CREATE TABLE users (
id int(11) unsigned NOT NULL AUTO_INCREMENT,
email varchar(255) DEFAULT NULL,
password varchar(255) DEFAULT NULL,
first_name varchar(255) DEFAULT NULL,
last_name varchar(255) DEFAULT NULL,
is_admin tinyint(1) DEFAULT NULL,
oauth_provider varchar(255) DEFAULT NULL,
oauth_id varchar(255) DEFAULT NULL,
PRIMARY KEY (id)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=latin1;
`)
};
exports.down = function(knex, Promise) {
return knex.schema.dropTable("users")
};
种子数据
javascript
const bcrypt = require('bcrypt-nodejs')
const pass = bcrypt.hashSync("test")
exports.seed = function(knex, Promise) {
// Deletes ALL existing entries
return Promise.all([
// Inserts seed entries
knex('users').insert({id: 1, email: 'test@test.com', password: pass}),
knex('users').insert({id: 2, email: 'test2@test.com', password: pass}),
knex('users').insert({id: 3, email: 'test3@test.com', password: pass})
]);
};
要点
- 使用Knex.js管理数据库迁移和种子数据
- 为不同环境(开发、生产)配置不同的数据库连接
- 使用
migrations
定义数据库结构的变更 - 使用
seeds
填充初始数据 - 通过
knex migrate:latest
和knex seed:run
命令执行迁移和种子
第20章:MongoDB
概述
本章介绍如何在Node.js应用中使用MongoDB,一种流行的NoSQL文档数据库。
关键代码
MongoDB连接
javascript
const MongoClient = require("mongodb").MongoClient
MongoClient.connect("mongodb://localhost:27017/test", (err, connection) => {
if (err) { console.log(err) }
module.exports.db = connection
})
服务器设置
javascript
const express = require("express")
const mongo = require("./db")
express()
.get("/", (req, res, next) => {
mongo.db.collection("users")
.find()
.toArray((err, users) => {
res.send(users)
})
})
.get("/create/:first_name/:last_name/:email/:password", (req, res, next) => {
mongo.db.collection("users")
.insert(req.params, (err, result) => {
res.send(result)
})
})
.get("/updateEmail/:email/:newEmail", (req, res, next) => {
mongo.db.collection("users")
.updateOne(
{email: req.params.email},
{$set: {email: req.params.newEmail}},
(err, result) => {
res.send(result)
}
)
})
.listen(3000)
要点
- 使用
mongodb
模块连接MongoDB数据库 - 使用
collection()
方法访问集合 - 使用
find()
方法查询文档 - 使用
insert()
方法添加新文档 - 使用
updateOne()
方法更新文档 - MongoDB使用JSON格式存储数据,非常适合JavaScript应用
第21章:部署
概述
本章介绍如何部署Node.js应用到生产环境,使用PM2进行进程管理和自动化部署。
关键代码
简单应用
javascript
require("express")()
.get("/", (req, res, next) => {
res.send("hello world")
})
.listen(3000)
PM2配置
json
{
"apps" : [
{
"name" : "app",
"script" : "app.js",
"env_production" : {
"NODE_ENV": "production"
}
}
],
"deploy" : {
"production" : {
"user" : "dep",
"host" : "104.236.108.202",
"ref" : "origin/master",
"repo" : "git@bitbucket.org:willrstern/example-deployment.git",
"path" : "~/node-app",
"post-deploy" : "nvm install && npm install --production && /home/dep/.nvm/versions/node/v6.9.1/bin/pm2 startOrRestart ecosystem.json --env production"
}
}
}
服务器设置脚本
bash
# 端口转发(将80端口转发到3000端口)
apt-get update
apt-get install iptables-persistent
iptables -t nat -I PREROUTING -p tcp --dport 80 -j REDIRECT --to-ports 3000
# 安装NVM和Node.js
curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.32.1/install.sh | bash
nvm install 6
# 安装PM2
npm i -g pm2
pm2 startup ubuntu
要点
- 使用PM2管理Node.js进程
- 配置
ecosystem.json
文件定义应用和部署设置 - 使用端口转发将标准HTTP端口(80)映射到Node.js应用端口
- 使用NVM管理Node.js版本
- 配置自动化部署流程
- 设置环境变量区分开发和生产环境