AI开发应用 02-nodejs快速入门

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>

要点

  1. 使用res.render()方法渲染视图,并传递数据对象
  2. 在模板中使用{{变量名}}来显示动态内容
  3. 使用{{#数组}}{{/数组}}来循环渲染数组内容
  4. 通过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>

要点

  1. 使用express-session管理用户会话
  2. 使用passport.initialize()passport.session()初始化Passport
  3. 使用LocalStrategy实现本地用户认证
  4. 使用bcrypt对密码进行加密和验证
  5. 实现serializeUserdeserializeUser方法来管理用户会话
  6. 使用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")
      })
  })

要点

  1. 使用中间件函数实现授权控制
  2. 使用loginRequired中间件确保用户已登录
  3. 使用adminRequired中间件检查用户是否为管理员
  4. 在数据库查询中添加条件限制用户只能访问自己的资源
  5. 为管理员提供特殊权限(如查看所有帖子)

第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');
    })

要点

  1. 使用passport-github策略实现GitHub OAuth认证
  2. 配置OAuth应用的clientID和clientSecret
  3. 设置回调URL处理认证结果
  4. 在数据库中存储OAuth提供商和用户ID
  5. 处理新用户和现有用户的不同逻辑

第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)

要点

  1. 使用express-redis-cache实现路由级别的缓存
  2. 使用connect-redis作为会话存储
  3. 为缓存设置过期时间和前缀
  4. 缓存适用于计算密集型或耗时的操作
  5. 缓存可以显著提高应用性能,特别是对于频繁访问但很少变化的数据

第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})
  ]);
};

要点

  1. 使用Knex.js管理数据库迁移和种子数据
  2. 为不同环境(开发、生产)配置不同的数据库连接
  3. 使用migrations定义数据库结构的变更
  4. 使用seeds填充初始数据
  5. 通过knex migrate:latestknex 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)

要点

  1. 使用mongodb模块连接MongoDB数据库
  2. 使用collection()方法访问集合
  3. 使用find()方法查询文档
  4. 使用insert()方法添加新文档
  5. 使用updateOne()方法更新文档
  6. 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

要点

  1. 使用PM2管理Node.js进程
  2. 配置ecosystem.json文件定义应用和部署设置
  3. 使用端口转发将标准HTTP端口(80)映射到Node.js应用端口
  4. 使用NVM管理Node.js版本
  5. 配置自动化部署流程
  6. 设置环境变量区分开发和生产环境

代码在github上,如果觉得有帮助请给我星星

源代码下载

相关推荐
白云~️14 分钟前
js实现宫格布局图片放大交互动画
开发语言·javascript·交互
CDwenhuohuo33 分钟前
滚动提示组件
java·前端·javascript
讨厌吃蛋黄酥41 分钟前
深度解析:useContext + useReducer — React官方状态管理的终极之道
javascript·react.js·前端框架
德育处主任2 小时前
p5.js 线段的用法
javascript·数据可视化·canvas
JuneXcy4 小时前
leetcode933最近的请求次数
开发语言·javascript·ecmascript
Fly-ping11 小时前
【前端】JavaScript 的事件循环 (Event Loop)
开发语言·前端·javascript
在逃的吗喽12 小时前
黑马头条项目详解
前端·javascript·ajax
JHCan33313 小时前
一个没有手动加分号引发的bug
前端·javascript·bug
天涯学馆13 小时前
为什么越来越多开发者偷偷用上了 Svelte?
前端·javascript·svelte
拾光拾趣录14 小时前
为什么浏览器那条“假进度”救不了我们?
前端·javascript·浏览器