做一個跑通前後端的`豆瓣租房`移動端webApp

Lvzongqiiiiiii發表於2018-10-06

一、前言

最近學了react,一直想做一個專案,沒有什麼好的主意。因為自己也要租房住,就想到了租房App這個idea,參考豆瓣租房小程式,著手了這樣一個簡陋的前後端專案?。
?線上demo點選這裡
?專案原始碼點選這裡,你可以下載在本地執行,如果對你有幫助可以點一下star哈?

// 你可以使用npm或yarn
yarn install
執行你的資料庫 // 必須!!!
yarn server // 執行伺服器, 連線的資料庫在server目錄下的config.js裡配置
yarn start // 執行專案
複製程式碼

二、前端

  1. 專案技術棧react+react-router+react-redux,用create-react-app腳手架生成。UI方面採用Ant Design Mobile?官方地址
  2. css方案是css-in-js,採用style-jsx ,? github地址,可參考掘金上的一篇文章?點選這裡
  3. 由於是移動端,避免不了適配問題,採用vm/vh適配,具體同樣可以參考掘金的?這篇文章
  4. 在結合2、3兩點時,由於要新增配置項,但我不想在專案中run eject彈出,於是用了react-app-rewired改寫配置,這樣就不用彈出命令了。? github地址
  5. 許可權路由。思路是根據遍歷路由配置表,需要許可權的走許可權路由,不需要的走原來的路由。具體可看專案中的router部分。
  6. 圖示採用iconfont SVG處理
    iconfont
    具體用法檢視官方地址

遇到的問題:

  • (未解決)在dev開發環境下修改scss中的css,不會實時編譯更新
  • (未解決)IOS下通過focus事件不能喚起鍵盤,安卓下可以。為了有個較好的使用者體驗,我在登入,搜尋頁面開啟時,讓輸入框自動focus喚起鍵盤,經IOS真機實踐,只能觸發focus事件,但是不會喚起鍵盤,安卓正常。查閱資料後是IOS做的限制,(IOS還有和音訊、視訊不能自動播放的限制)。需要使用者主動點選輸入框後,才可以喚起鍵盤。下一次重新開啟就能自動喚起鍵盤了,很坑的一點?!目前無解?
  • (解決)從首頁列表點選詳情時候,返回到首頁,會重新請求載入,並且滾動位置丟失,使用者體驗十分不好。這裡為了學習redux我用redux(也可用react的新context api)解決,因此在路由方面也用了react-redux-router,但已不維護,改為connect-react-routergithub點這裡。思路是首次獲取房源列表,然後存入redux中,下次開啟的時候,從redux中獲取。
  • (解決)熱載入後不能儲存redux中的狀態。解決方法:在store中,新增以下程式碼, 詳細看這裡
if (module.hot) {
// Reload reducers
    module.hot.accept('./reducers', () => {
        store.replaceReducer(connectRouter(history)(rootReducer));
    });
}
複製程式碼
  • (解決??) 用了react-loadble載入專案中的搜尋頁面,會有搜尋inputplaceholder顯示不全的問題,初次開啟會有問題,第二次開啟沒有問題,如下圖。 在dev環境下不能重現,生產環境下會有問題。該元件為Ant Design MobilesearchBar
    search_placeholder
    search_placeholder
    上圖我們可以看到是寬度的問題,正常應該為110px,而錯誤的時候則才80px暫時解決方法:移除該路由懶載入,直接載入?

專案優化:

  1. 路由懶載入,方案: react-loadable,新增loading提示
  2. 圖片懶載入,方案:lazyload
    • 封裝成一個元件? 具體程式碼
    • 這裡需要說明在你的網站上載入豆瓣的圖片都是403的,因此我們需要用到下面這個網站來載入圖片 點選這裡,使用方法 https://images.weserv.nl/?url=+圖片原來的地址, 詳細參考上一步程式碼中的連結
  3. ajax視情況新增loading提示,新增CSS3動畫,使互動更加友好。

三、後端

  • 採用koa2+koa-router+mongodb+jsonWebToken。最主要的是需要注意非同步和異常處理的問題。
  • 資料庫方面用了Mongoose來操作。Mongoose是在node.js非同步環境下對mongodb進行便捷操作的物件模型工具。更多詳細說明請看官方文件:?點選這裡

3.1 爬取豆瓣小組資料

  • 用到的http庫是axios
  • 定時任務庫,node-schedule。github:?點選這裡
  • 爬蟲庫cheerio,它的用法十分簡單。
const cheerio = require('cheerio')
const $ = cheerio.load('<h2 class="title">Hello world</h2>')
複製程式碼

這裡我們就可以通過$(selector),像jquery一樣的方式取到頁面的元素。官方文件:?點選這裡

整個爬取的流程:

  • 初始化的時候判斷是否大於最大儲存的資料長度(此專案中設定了資料庫最多儲存5000條資料),如果超過,則執行刪除,反之跳過。
  • 開啟一個定時任務,每天的0.00am開始爬取=>爬取列表頁面=>存入資料庫=>如果失敗,不會爬取該條tid
  • 的詳情頁,反之繼續爬取詳情頁。
  • 爬蟲提取資訊用到了一些正規表示式,提取房租、聯絡方式、房型、所在地區等等。具體程式碼:?點選這裡。其中參考了?這篇文章中的一些正規表示式。

注意:豆瓣會限制一個時段內Ip的訪問次數,因此需要我們做一些調整。

  • 列表頁面每一頁、 詳情頁每一條資料的爬取的間隔時間保證是不同的。(定時器+隨機數時間)(貌似沒什麼卵用?)
// sleep
function sleep(time = 0) {
 return new Promise(resolve => {
   setTimeout(resolve, time);
 });
}
 // 更新資料庫函式
 async updateTopic(tid, resolve, reject) {
   // 睡眠
   await sleep(Math.ceil(Math.random() * 50 * 1000) + 5000);
   // 開始更新
   await this.fetchDetail(tid).then(houseInfo => {...});
 }
複製程式碼
  • 改變請求頭的user-agent。專案中是有個user-agent列表?檢視程式碼,每次請求都帶上隨機中的一個。

3.2 存入資料庫

這裡我是一次性插入多條資料,用到的api如下

db.Houses.insertMany([your array data])
複製程式碼

3.3 寫介面(路由)

需要注意的是部分路由(需要使用者登入後才可以訪問的介面)header中需要傳遞token才能訪問,因此新增路由中介軟體校驗通過校驗後才允許訪問。詳細程式碼檢視這裡。關鍵程式碼如下?

const jwt = require('jsonwebtoken');
const token = ctx.header['x-token'];
if(token){
	解析token得到使用者資訊
	進入下一個中介軟體
}else {
	返回錯誤需要傳遞token
}
複製程式碼

四、資料庫mogodb相關

4.1 修改資料庫相關結構

開始設計資料庫的時候,設定價格欄位prices是陣列,後覺得字串就可以了。於是在原資料庫的基礎上修改資料格式欄位名prices=>price

  1. 批量更新某個欄位
db.getCollection('houses').find().forEach(function(item){
    db.getCollection('houses').update({_id:item._id},{$set:{prices: ''+item.prices}})
 })
複製程式碼
  1. 更改欄位名
// 如將欄位"prices"改為"price"
db.getCollection('houses').update({},{$rename:{'prices':'price'}}, false, true)
複製程式碼

4.2 附上一些api.

  1. 資料庫複製。如複製douban-house資料庫到douban-test
// db.copyDatabase(<from_dbname>, <to_dbname>, <from_hostname>)
 db.copyDatabase('douban-house', 'douban-test')
複製程式碼
  1. 查詢資料庫中陣列長度大/小於n的資料
// 大於 exists=1 小於exists=0
db.getCollection('houses').find({'imgs.n':{'$exists':1}})
複製程式碼
  1. 查詢資料庫中某個欄位不為null的資料
// $ne=> not equal
db.getCollection('houses').find({'contact':{$ne:null}})
複製程式碼
  1. 查詢資料庫中多條某個欄位的資料
db.getCollection('houses').find({'tid':{$in:['這裡是陣列','例如id1','2']}})
複製程式碼

另外:插入欄位數字Number Int型別的資料會儲存為Double型別,會帶有小數點,例如存的是10,存進資料庫之後會變成10.0,可以用NumberInt或者NumberLong來儲存

db.houses.insert({"tid": NumberInt(666)})
複製程式碼

4.3 遇到的問題

爬蟲爬取貼子的時候,會爬到相同的貼子,而我們是不需要這些重複的。這裡的問題是在插入重複值的時候,出現錯誤之後不會繼續插入剩下的資料,這是很坑的一點。下面是解決方法:

  • 先設定mongodb的唯一索引值,在設定的時候也遇到不少的坑,查了很多資料,總結相關的api
    const housesSchema = new mongoose.Schema({
    	tid: String, //我這裡設定的唯一索引是每條貼子的id號
    	...省略
    })
    housesSchema.index({ tid: 1 }, { unique: true });
    複製程式碼
  • 這裡設定好之後,當插入重複的tid時,資料庫會返回錯誤,不插入該條資料。特別需要說的大坑是插入的api無論是insert還是insertMany, 他們的api如下
db.collection.insert(
  <document or array of documents>,
  {
    writeConcern: <document>,
    ordered: <boolean>
  }
)
複製程式碼
這裡需要注意的是`ordered`這個引數, 這是一個可選引數,官方解釋如下
複製程式碼

Optional. A boolean specifying whether the mongod instance should perform an ordered or unordered insert. Defaults to true.

大意就是指定mongod例項是否應執行有序插入。預設為```true```。
**重點是:**當有序插入的時候,如果出現了錯誤,程式會停下當前的插入,不執行插入剩餘的資料。只有當無序插入,也就是設定了```ordered: false```,當出現錯誤之後,才會把剩下的繼續插入。官方說明如下:

> Excluding Write Concern errors, ordered operations stop after an error, while unordered operations continue to process any remaining write operations in the queue.

官方文件連結:?[點選這裡](http://docs.mongodb.com/manual/reference/method/db.collection.insertMany)
複製程式碼

五、部署相關(跨域處理)

  • 開發階段 可在專案中的package.json中新增proxy欄位, 這裡假設http://localhost:3003就是我們的後臺伺服器, http://localhost:3000是react開發時候的伺服器 如:在專案中訪問http://localhost:3000/api/house/125048127就會代理到http://localhost:3003/api/house/125048127, 就沒有跨域問題了
  "proxy": {
    "/api": {
      "target": "http://localhost:3003"
    }
  }
複製程式碼
location  /api/ {
   proxy_pass   http://localhost:3003;
}
複製程式碼

六、git相關

有時候提交了錯誤的程式碼又想回退版本,就需要回退遠端git倉庫的程式碼,再重新提交。 ?更多用法參考這裡

git_reflog

git reflog // 檢視提交列表, 如我需要撤回到第二條提交記錄,也就是紅線下的那條
git reset --soft 3a2a12d // 這裡的引數--soft表示保留本地修改記錄, --hard 代表儲存本地的記錄,如果是--hard 則會清空本地修改記錄,也就是你修改的都沒有了!!切記!!!
git push -f //強制推送到遠端分支
複製程式碼

相關文章