node博客項目學習

1、 項目準備javascript

  1. npm init -y在項目中初始化npm環境,以後就能夠用npm裏面的方法
  2. node模塊化標準:commonjs,使用npm install某個模塊後,就會多一個node_module文件夾
  3. 使用vscode進行debug,相似於谷歌瀏覽器

2、 node接口處理:html

  1. get請求:http模塊--createServer方法建立服務,其傳入的函數中req有url,method(必須是大寫如GET),headers(content-type)等屬性on方法,querystring模塊--parse方法處理url的參數
  2. post請求:接受數據採用數據流的方式,使用req.on的data方法接收數據,chunk是個二進制的參數,使用res的setHeader方法設置返回頭爲html或json,將chunk轉成字符串後的數據累加接收
const http = require("http")
const server = http.createServer((req, res) => {
    console.log('content-type',req.headers['content-type'])
    //接受數據
    let postData = '';
    req.on('data', chunk =>{
        postData +=chunk.toString()
   })
    req.on('end', () =>{
        console.log(postData)
        res.end("hello world")
    })
})
server.listen(8000);

ps:當終端裏運行了該文件以後,請求中的斷點就不會再進來了,因此想要調試的時候得把終端關掉。且想要看的斷點要是當前打開的窗口才行,否則斷點不會從那裏第一步走前端

3、搭建開發環境java

  1. 使用nodemon檢測文件變化,自動重啓node
  2. 使用cross-env設置環境變量,在package.json中的scripts屬性中根據起服務時傳的dev等值不同,選擇不一樣NODE_ENV的值
  3. 搭建時將server的配置和請求處理相關的配置分別寫在兩個文件中,拆分開後相互引用。而且路由和請求的處理也是分開處理的,這就是模塊化的使用。

www.js文件中:node

const http = require('http');

const PORT = 3000
const serverHandle = require('../app')

const server = http.createServer(serverHandle)
server.listen(PORT)

app.js文件中:mysql

const handleBlogRouter = require('./src/router/blog')
const handleUserRouter = require('./src/router/user')

const serverHandle = (req, res) => {
// 設置返回格式 json
    res.setHeader('Content-type', 'application/json')

    //處理blog路由
    const blogData = handleBlogRouter(req,res)
    const url = req.url
    req.path = url.split("?")[0]

    if(blogData){
        res.end(
            JSON.stringify(blogData)
        )
        return
    }
}
  1. 初始化路由:先作出路由,再將路由和數據處理分離,這樣才符合設計原則。注意:路由是從緊挨端口號的斜槓開始對比的nginx

    路由文件中:es6

const handleBlogRouter = (req, res) => {
    const method = req.method

    //獲取博客列表
    if(req.path ==='/api/blog/list' && method==='GET'){
        return {
            msg: '這是博客列表的接口'
        }
    }
}

module.exports = handleBlogRouter
  • 所犯錯誤:web

    1. 把路由最前面的路徑裏的斜槓忽略了,因此返回404
    2. 請求的method寫成了小寫
    3. 調試的模式下打開的不是監聽請求的文件
    4. req的path賦值放在了調用路由文件方法以後,因此沒賦上值
  1. 開發路由,暫時拆分爲四層:redis

    a>. 網絡端口及服務建立(www.js)。
    b>.請求和返回參數的統一設置(app.js)。
    c>.將app.js中的req和res傳進來,主要負責路由路徑的匹配(router),將該路由匹配到其所須要的處理數據的方法中,可是具體數據的處理方法如何他無論。
    d>.只關心數據的處理(controller),傳入參數,返回數據便可。

6.開發post方式的路由(router負責對應路由,給各個路由分配數據就行,controller負責經過來的參數,處理具體的數據)。 其中接收數據和處理數據是異步的,因此須要用到promise,防止回調地獄(callback-hell),而且這樣能夠將獲取postData的方法和處理postData的方法用.then方法拆分開來,不用嵌套的寫在一塊兒那麼冗雜。

  • *promise講解

  • 永遠返回一個promise實例,這個實例的then方法是成功的時候執行的,即then方法裏是callback方法,函數裏面的參數是形參,resolve裏傳的是實參。

  • .then方法其實是以resolve異步後成功拿到數據的實參來執行的,因此完成了異步,只有兩層嵌套,後面須要的時候再使用.then方法就行,永遠只有兩層,不會再多嵌套

//用於處理post data
const getPostData = (req) => {
    const promise = new Promise((resolve,reject) =>{
        if(req.method !=='POST'){
            resolve({})
            return;
        }
        if(req.headers['content-type'] !== 'application/json'){
            resolve({})
            return
        }
        let postData = ''
        req.on('data',chunk =>{
            postData += chunk.toString()
        })
        req.on('end', ()=>{
            if(!postData) {
                resolve({})
                return
            }
            resolve(
                JSON.parse(postData)
            )
        })
    })
    return promise
}

這樣,在serverHandle中使用:
getPostData(req).then(postData => {})就能引用到上面異步接收到的postData數據。
//示例: 更新博客的返回(controller.js)
const updateBlog = (id, blogData = {})=> {//兼容寫法,空的話就返回空對象
    // id就是要更新博客的id
    //blogData 是一個博客對象,包含title,content屬性
    return true
}
es6的解構定義:
const {username, password} = req.body

4、數據庫的鏈接

  1. mysql的安裝和使用.
    下載路徑:https://dev.mysql.com/downloads/mysql/

  2. 操做數據庫 (使用workbench圖形化界面)

    a> 建庫
    (1)建立myblog數據庫
    (2)執行show databases;查詢
    b> 建表
    NN -- 不能爲空
    PK -- 是否爲主鍵
    AI -- 自動增長
    右鍵左側列表中的「table」,使用drop table 刪除表,使用alter table修改表
    c> 操做數據庫的表
    注意:操做數據庫須要先寫use + 庫名 ,如use myblog,這樣才能訪問到庫裏的數據 在mysql中,兩個橫線加一個空格是註釋 (1)(增、刪、改、查)

注:users是表名稱
增:
insert into users(username,`password`, realname) values ('lisi','123','李四');
刪:
delete from users where username='lisi';
軟刪除:
update users set state='0' where username='lisi'//設置用戶名是lisi的狀態爲0
改:
update users set realname='李四2' where username<>'lisi';//<>這個符號是不等號
查:
select * form users;//查找users表內的所有數據
select * form users where username='lisi';//查找users表內的username是lisi的數據
select id,username from users;//查找id,username這兩列
select id from users order by id desc; //按照id逆序排序插敘
  1. node操做數據庫,鏈接數據庫的文件寫在config文件夾下的db.js中,定義變量env= NODE_ENV區分開發環境和線上環境的配置。此處是非node項目中操做數據庫的代碼,node中操做的在下面的下面顯示。
const mysql = require('mysql')

//建立連接對象
const con = mysql.createConnection({
    host: 'localhost',
    user: 'root',
    password: 'zving10301dbk',
    port: '3306',
    database: 'myblog'//數據庫名稱
})

//開始鏈接
con.connect()

//執行sql語句
//const sql = 'select id,username from users'
//const sql = `update users set realname='李四2' where username='lisi'`
//const sql = `insert into blogs (title,content,createtime,author) value('標題3','內容3',2222,'zhangsan')`
const sql = 'select * from blogs'

con.query(sql, (err,result) => {
    if(err) {
        console.log(err)
        return 
    }
    console.log(result)
})

//關閉鏈接

con.end()

上述代碼在項目中封裝後才能使用,即便用config文件夾中的db.js將環境變量保存好,,以下所示:

const env = process.env.NODE_ENV // 環境變量,直接用就行,不用引用,定義於package.json中

//配置
let MYSQL_CONF
let REDIS_CONF

if(env === 'dev'){
    MYSQL_CONF = {
        host: 'localhost',
        user: 'root',
        password: 'zving10301dbk',
        port: '3306',
        database: 'myblog'//數據庫名稱
    }
    //redis
    REDIS_CONF = {
        port: 6379,
        host: '127.0.0.1'
    }
}

if(env === 'production'){
    MYSQL_CONF = {
        host: 'localhost',
        user: 'root',
        password: 'zving10301dbk',
        port: '3306',
        database: 'myblog'//數據庫名稱
    }
    //redis
    REDIS_CONF = {
        port: 6379,
        host: '127.0.0.1'
    }
}

module.exports = {
    MYSQL_CONF,REDIS_CONF
}

而後使用db文件夾的mysql.js文件將執行查詢的代碼使用promise異步包裝起來,mysql.js文件以下:

//建立鏈接對象
const con = mysql.createConnection(MYSQL_CONF)
//開始鏈接
con.connect()
//統一執行sql的函數,使用promise異步傳遞數據庫中查詢到的數據
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  }
  1. node的api對接mysql
    從controller.js中將返回的數據寫成上面exec返回的promise實例,router的對應文件中就可使用then方法拿到實例中的數據,幷包裹errno後再返回給app.js一個promise實例,app.js中再用promise實例的then方法獲取數據便可,注意都是異步的,須要在異步外return.
//統一執行sql的函數
function exec(sql){
    const promise = new Promise((resolve,reject) => {
        con.query(sql,(err, result) =>{
            if(err){
                reject(err)
                return
            }
            resolve(result)
            // console.log(result)
        })
       
    })
    return promise
}
controller.js中:
const sql =`insert into blogs (title,content,createtime,author) values ('${title}','${content}','${createtime}','${author}')`

    
    return exec(sql).then(insertData=>{
        //console.log(insertData)
        return {id:insertData.insertId}
    })
router.js中:
const result = getList(author, keyword)
return result.then(listData => {
      return new SuccessModel(listData) //then方法裏返回的值會做爲新的promise對象,後面的then方法裏能夠拿到這個值
})

連環使用promise的then時,要記得使用return把promise傳出來 ,才能繼續傳的是promise。且在return中使用then方法進一步處理數據以後再return,後面的then拿到的數據都是上一步處理數據後的promise。(即return了一個promise的then方法,返回的是then方法處理過的promise數據)

5、登錄

  1. cookie
    cookie是存在瀏覽器的一段字符串,最大5kb,跨域不共享。每次發送http請求,都會帶過去,server端能夠修改cookie並返回給瀏覽器。瀏覽器也能夠經過javascript修改cookie(有限制)

客戶端操做cookie:(不經常使用)

document.cookie='k1=v1'  //能夠對cookie字符串進行累加

server端操做cookie用於登錄驗證:

在查詢後,數據庫中存在發過來用戶密碼後,後端將這樣cookie放到請求裏:

res.setHeader('Set-Cookie',`username=${data.username}; path=/;
httpOnly;expires=${getCookieExpires()}`)

//path=/代表全部根目錄下都加該cookie
//httpOnly表示只容許後端改cookie,不容許前端改
//getCookieExpires是過時時間,通常設置當前請求的一天後爲過時

即:經過get方法獲取到username和password,而後後端設置cookie給客戶端,客戶端下次再請求的時候就帶着cookie過來了。這是cookie的經常使用模式。可是很危險,會暴露username。

  1. session
    爲解決上面暴露username的問題,能夠在cookie中存儲userid,server端對應username。這個解決方案就叫session,是一個統稱。即server端存儲用戶信息。
    使用session開發登陸:
    在app.js中設置userId的值,當沒有時將userId設置成時間戳的惟一值,並把對應的session(SESSION_DATA[userId])設置成空對象,當請求登錄接口時把這個對象加上username和realname的key值,之後經過userId就能匹配到對應的username了 關鍵代碼:
//解析session
    let userId = req.cookie.userid
    let needSetCookie = false
    if(userId) {
        if(!SESSION_DATA[userId]){
            SESSION_DATA[userId] = {}
        }
    }else {
        needSetCookie = true
        userId = `${Date.now()}_${Math.random()}`
        SESSION_DATA[userId] = {}
    }
    req.session = SESSION_DATA[userId]

session的原理是切換別的用戶時,userId不會變只是把req.session裏的username和realname的value值改變了,同時也變了SESSION_DATA[userId]。


可是session的問題:
a. session直接是js變量,放在nodejs進程的內存中(web server中)
b. 進程內存有限,訪問量過大,session過大容易致使進程崩潰
c. 正式線上的運行是多線程的,進程之間內存沒法共享
解決方案:redis

  1. redis:
    redis是web server最經常使用的緩存方案,他的數據存放在內存中(容易斷電丟失),相比於mysql這樣的硬盤數據庫,內存訪問速度快。可是成本更高,可存儲的數據量更小(內存的硬傷)。只把session放到redis中,就會解決進程擠爆的問題,而且多個進程訪問同一個redis,也不會出現沒法共享的問題。

ps:爲何session適合用redis?
a. session訪問拼房,對性能要求極高
b. session可不考慮斷電丟失數據的問題(內存的硬傷)(丟了從新登錄就好) c. session數據量不會很大(相比於mysql中存儲的數據)

node鏈接redis:
(1)在config文件夾中寫好get和set方法

//建立客戶端
const redisClient = redis.createClient(REDIS_CONF.port,REDIS_CONF.host)
redisClient.on('error',err =>{
    console.error(err)
})

function set(key, val){
    if(typeof val === 'object'){
        val = JSON.stringify(val)
    }
    redisClient.set(key,val, redis.print)
}

function get(key){
    console.log('key',key)
    const promise = new Promise((resolve, reject) => {
        redisClient.get(key,(err,val)=>{
            console.log('val',val)
            if(err) {
                reject(err)
                return
            }
            if(key == null){
                resolve(null)
                return
            }
            try{
                resolve(
                    JSON.parse(val)
                )
            }catch(ex){
                resolve(val)
            }
            resolve(val)
        })
    }) 
    return promise
}

(2)在app中修改代碼,當cookie中沒有userId時,將userId賦爲隨機數,並用上一步的set方法將這個隨機數寫入redis中,並將對應的value值設置爲一個空對象。
(3)在user.js中,當調用登錄接口時,將從mysql中匹配到的用戶名和密碼使用set方法賦值給redis中上一步的空對象,此後的調用接口時,使用userId去查找,若是redis中對應的userId有非空值,就將這個值賦到req.session上,用來其餘接口的登錄驗證。
(4)從新登錄時,會更新redis中該userId對應的對象值,並賦給req.session,這樣就能夠對別的接口進行驗證是否已登錄。(6-12的視頻就是在app.js中多了查找redis的代碼,和user.js中多了set的代碼。) ps:其實很簡單,就是把session的一個變量(key是隨機的userId,value是一個對象,裏面放的username和realname的一個對象)放redis裏查,每次調接口以前查一下,登錄的時候更新一下值

6、服務端和前端聯調

  1. 登陸功能依賴cookie,必須用瀏覽器聯調。並且cookie跨域不共享,前端和server端必須同域。須要用到nginx作代理,讓先後端同域。
  2. nginx介紹:
    (1)高性能的web服務器,開源免費
    (2)通常用做靜態服務、負載均衡
    (3)反向代理(客戶端控制不了的代理)
    (4)nginx配置:/usr/local/etc/nginx/nginx.conf ,修改配置文件:sudo vi /usr/local/etc/nginx/nginx.conf,i → Insert 模式,按 ESC 回到 Normal 模式. x → 刪當前光標所在的一個字符。 :wq → 存盤 + 退出 (:w 存盤, :q 退出) (陳皓注::w 後能夠跟文件名) dd → 刪除當前行,並把刪除的行存到剪貼板裏 p → 粘貼剪貼板 就在這個文件裏,改了三個地方:(1)是把原來的端口號80改爲8080,(2)是把原來的location根目錄原來代理的文件路徑改掉,改爲本章寫的前端項目所起端口8001,(3)是加上了api路徑的代理,代理到後端所起服務8000端口下。這樣就能夠實現先後端同域聯調了。
    location / {
        	proxy_pass  http://localhost:8001;
        }
        location /api/ {
        	proxy_pass  http://localhost:8000;
        	proxy_set_header Host $host;
        }
    (5)nginx命令:nginx 啓動。nginx -s reload 重啓,nginx -s stop 中止,nginx -t測試配置文件是否正確
相關文章
相關標籤/搜索