做一個跑通前後端的`豆瓣租房`移動端webApp
最近學了 react
,一直想做一個專案,沒有什麼好的主意。因為自己也要租房住,就想到了 租房App
這個idea,參考豆瓣租房小程式,著手了這樣一個簡陋的前後端專案:smile:。
:point_right:線上demo點選這裡
:point_right: ofollow,noindex">專案原始碼點選這裡 ,你可以下載在本地執行,如果對你有幫助可以點一下 star
哈:grin:
// 你可以使用npm或yarn yarn install 執行你的資料庫 // 必須!!! yarn server // 執行伺服器, 連線的資料庫在server目錄下的config.js裡配置 yarn start // 執行專案 複製程式碼
二、前端
- 專案技術棧
react+react-router+react-redux
,用create-react-app
腳手架生成。UI方面採用Ant Design Mobile
:point_right:官方地址。 -
css
方案是css-in-js,採用style-jsx
,:point_right: github地址 ,可參考掘金上的一篇文章:point_right:點選這裡。 - 由於是移動端,避免不了
適配
問題,採用vm/vh
適配,具體同樣可以參考掘金的:point_right:這篇文章。 - 在結合 2、3 兩點時,由於要新增配置項,但我不想在專案中
run eject
彈出,於是用了react-app-rewired
改寫配置,這樣就不用彈出命令了。:point_right: github地址 - 許可權路由。思路是根據遍歷路由配置表,需要許可權的走許可權路由,不需要的走原來的路由。具體可看專案中的 router 部分。
- 圖示採用
iconfont SVG
處理
遇到的問題:
- (未解決)在dev開發環境下修改scss中的css,不會實時編譯更新
- (未解決)IOS下通過
focus
事件不能喚起鍵盤,安卓下可以 。為了有個較好的使用者體驗,我在登入,搜尋
頁面開啟時,讓輸入框自動focus
喚起鍵盤,經IOS
真機實踐,只能觸發focus
事件,但是不會喚起鍵盤,安卓
正常。查閱資料後是IOS
做的限制,(IOS
還有和音訊、視訊不能自動播放的限制)。需要使用者主動點選輸入框後,才可以喚起鍵盤。下一次重新開啟就能自動喚起鍵盤了,很坑的一點:unamused:! 目前無解 - (解決) 從首頁列表點選詳情時候,返回到首頁,會 重新請求載入 ,並且 滾動位置丟失 ,使用者體驗十分不好。這裡為了學習
redux
我用redux
(也可用react
的新context api
)解決,因此在路由方面也用了react-redux-router
,但已不維護,改為connect-react-router
github點這裡 。思路是首次獲取房源列表,然後存入redux
中,下次開啟的時候,從redux
中獲取。 - (解決)
熱載入
後不能儲存redux
中的狀態。解決方法:在store
中,新增以下程式碼, 詳細看這裡
if (module.hot) { // Reload reducers module.hot.accept('./reducers', () => { store.replaceReducer(connectRouter(history)(rootReducer)); }); } 複製程式碼
- (解決??) 用了
react-loadble
載入專案中的搜尋頁面,會有搜尋input
的placeholder
顯示不全的問題,初次開啟會有問題,第二次開啟沒有問題,如下圖。 在dev
環境下不能重現,生產環境
下會有問題。該元件為Ant Design Mobile
的searchBar
。110px
,而錯誤的時候則才80px
。 暫時解決方法:移除該路由懶載入,直接載入:smirk:
專案優化:
- 路由懶載入,方案: react-loadable ,新增
loading
提示 - 圖片懶載入,方案: lazyload
- 封裝成一個元件:point_right: 具體程式碼
- 這裡需要說明在你的網站上載入豆瓣的圖片都是
403
的,因此我們需要用到下面這個網站來載入圖片點選這裡,使用方法https://images.weserv.nl/?url=+圖片原來的地址
, 詳細參考上一步程式碼中的連結
-
ajax
視情況新增loading
提示,新增CSS3
動畫,使互動更加友好。
三、後端
- 採用
koa2
+koa-router
+mongodb
+jsonWebToken
。最主要的是需要注意 非同步和異常處理 的問題。 - 資料庫方面用了
Mongoose
來操作。Mongoose
是在node.js非同步環境下對mongodb
進行便捷操作的物件模型工具。更多詳細說明請看官方文件::point_right:點選這裡
3.1 爬取豆瓣小組資料
- 用到的
http
庫是axios
。 - 定時任務庫,
node-schedule
。github::point_right: 點選這裡 - 爬蟲庫
cheerio
,它的用法十分簡單。
const cheerio = require('cheerio') const $ = cheerio.load('<h2 class="title">Hello world</h2>') 複製程式碼
這裡我們就可以通過 $(selector)
,像 jquery
一樣的方式取到頁面的元素。官方文件::point_right:點選這裡
整個爬取的流程:
- 初始化的時候判斷是否大於最大儲存的資料長度(此專案中設定了資料庫最多儲存
5000
條資料),如果 超過 ,則執行刪除,反之跳過。 - 開啟一個定時任務,每天的
0.00am
開始爬取=>
爬取列表頁面=>
存入資料庫=>
如果 失敗 ,不會爬取該條tid - 的詳情頁, 反之 繼續爬取詳情頁。
- 爬蟲提取資訊用到了一些正則表示式,提取房租、聯絡方式、房型、所在地區等等。具體程式碼::point_right: 點選這裡 。其中參考了:point_right:這篇文章中的一些正則表示式。
注意:豆瓣會限制一個時段內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
列表:point_right: 檢視程式碼 ,每次請求都帶上隨機中的一個。
3.2 存入資料庫
這裡我是一次性插入多條資料,用到的 api
如下
db.Houses.insertMany([your array data]) 複製程式碼
3.3 寫介面(路由)
需要注意的是部分路由(需要使用者登入後才可以訪問的介面) header
中需要傳遞 token
才能訪問,因此新增 路由中介軟體校驗 , 通過校驗
後才允許訪問。 詳細程式碼檢視這裡 。關鍵程式碼如下:point_down:
const jwt = require('jsonwebtoken'); const token = ctx.header['x-token']; if(token){ 解析token得到使用者資訊 進入下一個中介軟體 }else { 返回錯誤需要傳遞token } 複製程式碼
四、資料庫mogodb相關
4.1 修改資料庫相關結構
開始設計資料庫的時候,設定價格欄位 prices
是陣列,後覺得字串就可以了。於是在原資料庫的基礎上修改 資料格式 及 欄位名 prices
=> price
- 批量更新某個欄位
db.getCollection('houses').find().forEach(function(item){ db.getCollection('houses').update({_id:item._id},{$set:{prices: ''+item.prices}}) }) 複製程式碼
- 更改欄位名
// 如將欄位"prices"改為"price" db.getCollection('houses').update({},{$rename:{'prices':'price'}}, false, true) 複製程式碼
4.2 附上一些api.
- 資料庫複製。如複製 douban-house 資料庫到 douban-test
// db.copyDatabase(<from_dbname>, <to_dbname>, <from_hostname>) db.copyDatabase('douban-house', 'douban-test') 複製程式碼
- 查詢資料庫中 陣列長度大/小於n 的資料
// 大於 exists=1 小於exists=0 db.getCollection('houses').find({'imgs.n':{'$exists':1}}) 複製程式碼
- 查詢資料庫中 某個欄位不為null 的資料
// $ne=> not equal db.getCollection('houses').find({'contact':{$ne:null}}) 複製程式碼
- 查詢資料庫中 多條某個欄位 的資料
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. 官方文件連結::point_right:[點選這裡](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" } } 複製程式碼
- 線上環境 nginx 配置代理 :point_right:參考這裡
location/api/ { proxy_passhttp://localhost:3003; } 複製程式碼
六、git相關
有時候提交了錯誤的程式碼又想回退版本,就需要回退遠端 git
倉庫的程式碼,再重新提交。 :point_right:更多用法參考這裡

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