緣起
幾個(gè)同行相約用業(yè)余時(shí)間開發(fā)一款社交應(yīng)用,客戶端就是微信公眾號(hào)H5,因?yàn)槭莻€(gè)人項(xiàng)目,我就不太想用自己擅長(zhǎng)的C++了,麻煩不說(shuō),寫起來(lái)也挺沒趣,之前斷斷續(xù)續(xù)也用golang寫了幾個(gè)服務(wù),覺得很適合用來(lái)寫后臺(tái),于是技術(shù)選型就敲定golang啦.
考慮到最好需要業(yè)務(wù)中用戶之間并無(wú)太多交集,用非關(guān)系型的nosql數(shù)據(jù)庫(kù)開發(fā)效率更高,在redis和mongodb之間猶豫了許久,最終選了mongodb,原因無(wú)他,redis實(shí)在是太熟悉,對(duì)這東西沒啥興趣了,而且redis純內(nèi)存對(duì)服務(wù)器成本要求也太大,萬(wàn)一到時(shí)候用戶量上去沒有日活,我redis開著吃那么多內(nèi)存浪費(fèi)錢嘛.所以就選了mongodb.
需求
產(chǎn)品需求大概就是提供一個(gè)看別人發(fā)的信息,同時(shí)可以領(lǐng)取紅包的功能
對(duì)于發(fā)送信息方,付出少量的金錢可以使自己的信息得到傳播,比如房屋招租, 尋狗啟事等
對(duì)于接收信息方,查閱信息即可以瓜分到現(xiàn)金,如果幫助傳播,則自己的份額+1,可以獲得更多的現(xiàn)金
盈利模式就是在用戶提現(xiàn)時(shí)收取幾個(gè)點(diǎn)的手續(xù)費(fèi).
任務(wù)拆分
根據(jù)以上需求,可以大概預(yù)算到,這樣一個(gè)項(xiàng)目后臺(tái)的核心功能
1.用戶體系
2.微信支付以及提現(xiàn)
3.便簽核心能力
4.用戶錢包,維護(hù)用戶余額
用戶體系
1.新用戶進(jìn)入自動(dòng)注冊(cè)和登錄
2.老用戶進(jìn)入自動(dòng)登錄
3.提供gin中間件,給每個(gè)登錄態(tài)接口鑒權(quán),檢查用戶登錄態(tài)是否有效
微信支付以及提現(xiàn)
- 用戶發(fā)布紅包便簽時(shí)提供微信支付功能
- 用戶通過(guò)查看便簽獲得的紅包,在存入余額后申請(qǐng)?zhí)岈F(xiàn),可以通過(guò)企業(yè)紅包成功提現(xiàn)到自己的微信號(hào)上
- 提現(xiàn)功能因?yàn)樾枰褂米C書,而微信官方并沒有提供golang的sdk, 所以借助于python實(shí)現(xiàn),通過(guò)celery任務(wù)管理框架和golang進(jìn)行通信
便簽核心能力
- 提供管理用戶便簽的能力,具體如下
- 發(fā)布便簽,先發(fā)布一個(gè)便簽,狀態(tài)設(shè)置為未支付,對(duì)未支付便簽進(jìn)行限制,不允許通過(guò)關(guān)注未支付便簽瓜分紅包和分享. 使用微信統(tǒng)一下單,獲取支付參數(shù)下發(fā)給前端,前端通過(guò)支付參數(shù)包調(diào)起微信支付,支付成功后收到回調(diào),回調(diào)中把便簽支付狀態(tài)修改為已支付,完成整個(gè)發(fā)布流程.
- 訂閱便簽,用戶通過(guò)訂閱和查看其他人發(fā)布的便簽獲得紅包瓜分份額,自己訂閱份額+1,分享出去后別人通過(guò)你的分享進(jìn)來(lái)便簽頁(yè),自己的訂閱份額也+1.
- 瓜分紅包,當(dāng)某個(gè)便簽的到達(dá)指定時(shí)間點(diǎn)后,用戶可以根據(jù)自己的份額進(jìn)行瓜分,瓜分到的現(xiàn)金存入余額.
用戶錢包,維護(hù)用戶余額
- 提供取現(xiàn)功能和存入余額功能
- 提現(xiàn)功能注意處理數(shù)據(jù)一致性,不能讓用戶通過(guò)并發(fā)手段把一份錢取多次,這里借助了mongodb的findAndModify功能,并發(fā)去寫入一個(gè)flag,最終寫入成功的只有一個(gè),只有寫成功的線程才可以繼續(xù)執(zhí)行下面的提現(xiàn)操作.
- 存入余額這里也需要注意數(shù)據(jù)一致性,不能讓用戶通過(guò)并發(fā)手段把一個(gè)紅包應(yīng)該分得的錢多次存入余額,這里借助了mongodb的findAndModify功能,并發(fā)去寫入一個(gè)flag,最終寫成功的只有一個(gè),只有寫成功的線程才可以繼續(xù)執(zhí)行下面的存入余額操作.
項(xiàng)目結(jié)構(gòu):
├── Authorize //用戶認(rèn)證,包括用戶登錄,登陸后token鑒權(quán)
├── CeleryTasks //Celery腳本, 用于訪問(wèn)微信api和海報(bào)生成
├── CeleryWrapper //go訪問(wèn)celery的wrapper
├── cert //微信證書
├── Common //公共代碼,很多地方需要使用的公共函數(shù)放在這里,如從頭中獲取userId,生成一個(gè)新的userId,獲取一個(gè)mongodb的session等
├── config //配置文件,如微信支付的相關(guān)配置,mongodb地址的配置等
├── ConfigManager //配置管理器,用于在任何地方都可以方便的讀取配置
├── ErrorCodes //錯(cuò)誤碼定義
├── Handlers //消息處理代碼
├── ImageProcessor //圖片處理器,因?yàn)楹?bào)生成部分最后使用celery配合wkhtmltopdf把html轉(zhuǎn)圖片,所以這部分代碼廢棄
├── Logger //日志相關(guān)代碼,日志打印使用了logrus庫(kù),這里把logrus包裝成一個(gè)middleware和gin框架結(jié)合使用,最終的目的是讓gin框架把請(qǐng)求和回包日志用logrus打印出來(lái)
├── NoteManager //便簽管理,和便簽相關(guān)的代碼都在這里面
├── Pay //支付相關(guān)
│ └── WechatPay //目前只支持微信支付,和微信支付相關(guān)的代碼都在這里
├── Resources //圖片等靜態(tài)資源
└── UserManager //用戶管理,和用戶相關(guān)的代碼都在這里
一些隨想
- 通過(guò)開發(fā)這個(gè)項(xiàng)目,讓自己對(duì)golang有一個(gè)比較全面的了解,熟悉了golang代碼的組織形式,第三方庫(kù)的路徑之類的golang開發(fā)環(huán)境特有的一些問(wèn)題
- golang嚴(yán)格定義了一些其他語(yǔ)言里面靈活的東西,比如括號(hào)問(wèn)題,函數(shù)的開始括號(hào)一定要在函數(shù)聲明的第一行,這一點(diǎn)我覺得很贊,其實(shí)很多東西不太需要自由,方案a也行b也行,但是如果沒人拍板就會(huì)制造混亂,如果硬性規(guī)定統(tǒng)一一下就很好.
- golang的警告不完全消除就不允許編譯通過(guò),這個(gè)特性我看到的時(shí)候心里默默點(diǎn)了個(gè)贊,但是開發(fā)完一套程序就想說(shuō)mmp了,很多時(shí)候?qū)憸y(cè)試代碼時(shí),經(jīng)常會(huì)屏蔽某些代碼,就會(huì)導(dǎo)致notused的warnning,然后導(dǎo)致程序無(wú)法編譯通過(guò),只有把聲明變量或者import的語(yǔ)句注釋掉才可以編譯過(guò),很難受.
- golang的代碼風(fēng)格是使用返回值來(lái)返回錯(cuò)誤碼, 因?yàn)間olang支持多返回值,所以通常第一個(gè)返回值表示真正要返回的數(shù)據(jù),第二個(gè)返回值表示錯(cuò)誤碼, 這個(gè)會(huì)導(dǎo)致錯(cuò)誤處理的代碼冗余,如果層級(jí)調(diào)用過(guò)深,每個(gè)層級(jí)都需要處理很多錯(cuò)誤碼.但是好處是編寫代碼的時(shí)候會(huì)自覺的處理好能想到的異常情況
- golang弱化了面向?qū)ο蟮母拍?其實(shí)我覺得面向?qū)ο笤趲啄昵昂芑?但是很多中小型項(xiàng)目基本上用不到,所以弄出什么單例模式,其實(shí)就是面向?qū)ο蟮耐嘶?golang可以允許函數(shù)單獨(dú)的存在,這一點(diǎn)我覺得非常贊.畢竟后臺(tái)開發(fā)更重要的是并發(fā)而不是所謂的模式.
- 從語(yǔ)言層面提供goroutine這種功能真的特別贊.有人說(shuō)C++也有協(xié)程(windows上叫纖程(Fiber)),這玩意其實(shí)不新鮮,2012年我們研發(fā)游戲時(shí)就用到過(guò),當(dāng)時(shí)沒有第三方庫(kù)可參考,都是自己手?jǐn)]代碼,后來(lái)在16年的時(shí)候看到騰訊出了一個(gè)libco,通過(guò)hook socket相關(guān)函數(shù)的方式無(wú)痛改造,把所有的socket操作都變成異步的. 我馬上把這個(gè)庫(kù)用在了當(dāng)時(shí)項(xiàng)目的thrift框架上, 記得當(dāng)時(shí)的成績(jī)是讓8核機(jī)器可以跑滿7個(gè)核, 比全同步只能跑滿4個(gè)核好了不少, 吞吐量也上去了, 但是費(fèi)了不少勁. 把thrift的線程池改造成了協(xié)程池. 所以用C++搞協(xié)程,要操心很多地方,主要是第三方庫(kù)不好改, 騰訊的libco算是另辟蹊徑了. golang的偉大之處是把這東西固化到語(yǔ)言里面,直接讓所有第三方庫(kù)全異步.
- golang作為最近幾年大火的語(yǔ)言,第三方庫(kù)卻始終跟不上,讓人很奇怪,比如mongodb這么多年了,竟然都沒有一個(gè)官方的drive, 最流行的mgo是一個(gè)第三方庫(kù).很多功能如果借助python實(shí)現(xiàn)會(huì)方便很多.這里不得不感慨python確實(shí)是開發(fā)效率很高的開發(fā)環(huán)境.