ThinkJS搭建網(wǎng)頁(yè)版即時(shí)聊天后臺(tái)

簡(jiǎn)單的寫(xiě)寫(xiě)程序邏輯盅惜。

緣由

因網(wǎng)站需求窃爷,要一個(gè)Web版的聊天程序,前端方面選擇了LayIM,只購(gòu)買(mǎi)了前端程序嘹裂,后臺(tái)得自己實(shí)現(xiàn)豁延。
于是利用Express+MongoDB+Socket.io搭建了一個(gè)即時(shí)聊天的后臺(tái)丐重。但因?yàn)槟菚r(shí)候LayIM剛出mobile版硼婿,只有一個(gè)對(duì)話(huà)窗口。自己寫(xiě)了一個(gè)簡(jiǎn)陋的消息接受列表俗慈,將就的用了一段時(shí)間姑宽。
但畢竟是第一次接觸Node.JS,各方面代碼總是不太滿(mǎn)意闺阱。第一次接觸炮车,因?yàn)楦鞣N教程版本不同,導(dǎo)致許多地方留下嘗試性設(shè)置酣溃。加上不支持異步瘦穆,換著方式實(shí)現(xiàn)功能,但最后還是添加上了es6赊豌,可此前的一些實(shí)現(xiàn)沒(méi)隨之更改扛或,整個(gè)項(xiàng)目還是比較雜亂的。剛好LayIM也出了新版碘饼,更新了完整的移動(dòng)端界面熙兔,于是開(kāi)始動(dòng)手重寫(xiě)后臺(tái)悲伶。

前期準(zhǔn)備

  • 重寫(xiě)打算不再使用Express,一是很關(guān)鍵的對(duì)異步不支持住涉,二是Express太過(guò)基礎(chǔ)麸锉,雖說(shuō)自定義性很高,但只是實(shí)現(xiàn)一個(gè)小后臺(tái)舆声,找一些集成度高的框架淮椰,避免自己寫(xiě)基礎(chǔ)性代碼,也讓代碼更加有條理纳寂。
    最終選中了ThinkJS,一款國(guó)產(chǎn)的框架泻拦,集成度比較高毙芜,功能夠用。其中也有一部分原因争拐,因其名字與ThinkPHP相似腋粥,框架結(jié)構(gòu)似乎有借鑒,之后也打算看看ThinkPHP架曹,所以先用這個(gè)嘗試一下隘冲。
  • 數(shù)據(jù)庫(kù)方面,MongoDB支持JSON格式绑雄,存取數(shù)據(jù)很方便展辞。只是在多表聯(lián)合查詢(xún)上并不方便,于是新版的查詢(xún)中選擇了常見(jiàn)的MySQL(查看資料說(shuō)5.7版以后支持了JSON万牺,但服務(wù)器上的數(shù)據(jù)庫(kù)似乎不支持)罗珍。
  • WebSocket還是使用Socket.io,對(duì)瀏覽器兼容性有處理脚粟。
  • 前端使用LayIM覆旱,支持PC和Mobile,兩者參數(shù)字段都統(tǒng)一核无,這次處理起來(lái)比較方便扣唱。

ThinkJS配置

ThinkJS是MVC框架,默認(rèn)使用ES6/7特性团南,可以使用async/await解決異步嵌套問(wèn)題噪沙。具體可查看文檔
我們只有一個(gè)IM功能,所以就不必再添加新的模塊已慢,使用默認(rèn)生成的home模塊就行曲聂。

首先需要配置,src/common/config/config.js文件中可以設(shè)置公用的配置信息佑惠,如果線(xiàn)上環(huán)境與開(kāi)發(fā)環(huán)境配置不一樣朋腋,可以在src/common/config/env目錄下找到development.js齐疙,在其中寫(xiě)入開(kāi)發(fā)環(huán)境所需的配置,production.js對(duì)應(yīng)為線(xiàn)上環(huán)境所需的配置旭咽。
數(shù)據(jù)庫(kù)配置信息在src/common/config/db.js文件中配置贞奋。
模板引擎使用默認(rèn)的ejs,路由配置也是使用默認(rèn)設(shè)置穷绵。

還需要在項(xiàng)目中打開(kāi)WebSocket轿塔,找到src/common/config/目錄下websocket.js文件,其中on設(shè)置為true仲墨,并且messages下寫(xiě)入WebSocket事件對(duì)應(yīng)的地址勾缭。例如:

"use strict";
export default {
  on: true, //是否開(kāi)啟 WebSocket
  type: 'socket.io', //使用的 WebSocket 庫(kù)類(lèi)型,默認(rèn)為 socket.io
  allow_origin: '', //允許的 origin
  adp: undefined, // socket 存儲(chǔ)的 adapter目养,socket.io 下使用
  path: '', //url path for websocket
  messages: {
    open: 'home/socketio/open',
    close: 'home/socketio/close',
    message: 'home/socketio/message' // message事件俩由,在home目錄下socketio.js文件中的message函數(shù)
  }
};

文件

所有的代碼獨(dú)立存放在各種的文件中。
WebSocket相關(guān)代碼放在home/controller/socketio.js文件癌蚁。
后臺(tái)管理相關(guān)代碼存放在home/controller/manage.js文件幻梯。
LayIM所需的用戶(hù)登陸信息獲取、存入努释,查看聊天記錄等代碼存放在home/controller/api.js文件碘梢。
home/logic目錄下,與controller目錄下同名的js文件伐蒂,是提交數(shù)據(jù)效驗(yàn)代碼煞躬,提交數(shù)據(jù)可以在這一層里先經(jīng)過(guò)校驗(yàn)判斷或是過(guò)濾處理。

其他

src/common/bootstarp/global.js文件中可以寫(xiě)入全局函數(shù)逸邦。
跨域ThinkJS也提供了說(shuō)明汰翠,查看文檔

數(shù)據(jù)庫(kù)設(shè)計(jì)

按LayIM所需字段昭雌,額外增加一個(gè)用于記錄登陸時(shí)間的字段复唤。

user表:idusername烛卧、avatar佛纫、signstatus总放、logintime
id唯一呈宇,判斷用戶(hù)的依據(jù)。username雖然隨著更新局雄,但實(shí)際上主站并不讓更改甥啄。
logintime用于給后臺(tái)管理時(shí)查看
idusername炬搭、avatar三個(gè)字段是LayIM必需的字段

好友功能本身沒(méi)實(shí)現(xiàn)的必要蜈漓,因?yàn)榱奶於际峭ㄟ^(guò)網(wǎng)站點(diǎn)擊一下在線(xiàn)按鈕穆桂,已經(jīng)有了歷史會(huì)話(huà)列表,好友列表并不是很重要(淘寶移動(dòng)端也是顯示歷史會(huì)話(huà)列表)融虽。
但考慮到以后會(huì)不會(huì)有其他需求享完,而且單獨(dú)一個(gè)歷史會(huì)話(huà)列表界面也并不是很好看,于是添加上了好友功能有额。

group表:gid般又、groupnameid巍佑、frienduid茴迁。
gid是表的id
groupname是好友表的名字
id是使用者自己的id
frienduid是好友的id
一個(gè)好友就是一條記錄,通過(guò)多表聯(lián)合查詢(xún)萤衰,可以直接將好友列表的結(jié)果一次性讀取出來(lái)笋熬。增刪都很方便。

之后便是存儲(chǔ)聊天信息腻菇。大致按LayIM本身的信息結(jié)構(gòu)存入就行,只是額外添加上一個(gè)自增字段昔馋,一個(gè)用于判斷是否推送離線(xiàn)消息的字段筹吐。

msg表:midid秘遏、username丘薛、avatarcontent邦危、toid洋侨、tousernametype倦蚪、timestamp希坚、push
當(dāng)push為1時(shí),表式推送消息陵且。

后臺(tái)需要一個(gè)管理界面裁僧,所以需要一個(gè)表用于記錄后臺(tái)管理員賬號(hào)信息。

manager表:id慕购、username聊疲、passwordlogintime
password為加鹽的二次MD5值沪悲。

后臺(tái)消息收發(fā)邏輯

登陸
  • 因?yàn)橹髡臼鞘褂肞HP获洲,即時(shí)聊天使用Node.JS提供API,所以不用設(shè)計(jì)注冊(cè)登陸功能殿如,只需要接收用戶(hù)相關(guān)字段并更新就行贡珊。為了避免用戶(hù)手動(dòng)修改前端的配置信息最爬,所以IM接收的是一串hash字符,IM后端接收到后利于這字符向主站拿取用戶(hù)信息飞崖。
    需要嚴(yán)格按LayIM要求的格式組裝好JSON烂叔。LayIM文檔
layim.config({
    init: {
    url: '' // API的地址,以get形式提交hash
    ,data: {}
    }
……

用戶(hù)登陸除了向主站取得用戶(hù)信息固歪,還需要查詢(xún)數(shù)據(jù)庫(kù)中的好友數(shù)據(jù)蒜鸡,需要usergroup兩個(gè)表聯(lián)合查詢(xún)牢裳,ThinkJS中提供model.join(join)來(lái)聯(lián)合查詢(xún)逢防,但始終避免不了要自己寫(xiě)上表前綴,如果修改表蒲讯,此處容易被忽略忘朝。

let group = await modelGroup.where({ id: mine.id}).distinct('gid').field('gid,groupname').select();

distinct是去重的字段,field是篩選字段判帮。

WebSocket

幾個(gè)主要的地方

  • 主要是用戶(hù)信息與SocketID的對(duì)應(yīng)局嘁,利用一個(gè)變量來(lái)記錄所有的SocketID與用戶(hù)信息的對(duì)應(yīng),方便查詢(xún)晦墙。
  • 同時(shí)需要PC端悦昵、Mobile端兩端都能正常接收信息,并且PC端重復(fù)打開(kāi)頁(yè)面晌畅,只發(fā)送信息給最后一個(gè)打開(kāi)的頁(yè)面但指。
  • 前端連接上socket的時(shí)候,發(fā)送用戶(hù)信息中包括登陸端類(lèi)型(PC抗楔、Mobile)棋凳,然后利用兩個(gè)變量分別記錄PC端和Mobile端的用戶(hù)。
  • 由于Mboile正常情況需要點(diǎn)擊進(jìn)入聊天界面才知道是否有新消息连躏,所以額外增加一個(gè)事件剩岳,用來(lái)推送未讀提醒。

其他功能同樣以此方式調(diào)取用戶(hù)信息執(zhí)行相關(guān)操作入热。添加好友卢肃、刪除好友,由于不是主要功能才顿,所以以最簡(jiǎn)單的方式莫湘,附加在工具欄上實(shí)現(xiàn)。

Socket登記流程

當(dāng)用戶(hù)連接上socket時(shí)郑气,會(huì)默認(rèn)執(zhí)行connect事件幅垮,其中寫(xiě)入一連接成功即發(fā)送用戶(hù)數(shù)據(jù)至后臺(tái)。

socket.emit('init', { jointype: 'PC', key: 'hash'});

由于LayIM的ready事件需要使用url獲取用戶(hù)信息才會(huì)觸發(fā)尾组,同時(shí)Mobile中也沒(méi)有提供這樣事件忙芒,所以全部由socket.io的connect事件替代示弓。
后臺(tái)接收到數(shù)據(jù),并驗(yàn)證數(shù)據(jù)可用呵萨。然后以SocketID為下標(biāo)奏属,存入變量中,這樣每個(gè)通過(guò)驗(yàn)證的連接都可以知曉是屬于哪個(gè)用戶(hù)潮峦。
但是這樣不利于查詢(xún)一個(gè)用戶(hù)下的所有連接囱皿,同時(shí)LayIM發(fā)送信息中只提供了用戶(hù)ID。所以需要再以用戶(hù)ID為下標(biāo)忱嘹,分別將PC與Mobile存入不同的變量中嘱腥,以便管理。
這樣只要檢查變量中是否存在用戶(hù)ID拘悦,便可以知道這個(gè)用戶(hù)是否在線(xiàn)齿兔,因?yàn)槭?code>push存入信息,所以最后一個(gè)必然是最新的SocketID础米。

前端發(fā)送用戶(hù)信息至后臺(tái) ---> 后臺(tái)驗(yàn)證數(shù)據(jù)是否可用 ---> 可用則以SocketID為下標(biāo)存入變量  ---> 再以用戶(hù)ID為下標(biāo)存入變量 ---> 讀取離線(xiàn)消息等其他操作
Socket退出流程

退出會(huì)響應(yīng)close事件分苇,在close函數(shù)中可以獲得用戶(hù)的SocketID,通過(guò)這個(gè)可以獲取此次退出的是哪個(gè)用戶(hù)屁桑。先刪除SocketID為下標(biāo)的變量中的數(shù)據(jù)医寿,再刪除以用戶(hù)ID為下標(biāo)的PC、Mobile兩個(gè)變量中的數(shù)據(jù)掏颊。
為了避免各種意外狀況,每次退出時(shí)最后再將在線(xiàn)用戶(hù)列表中的SocketID在所有連接對(duì)象thinkCache(thinkCache.WEBSOCKET)中進(jìn)行檢測(cè)艾帐,不存在則刪除乌叶。

發(fā)送消息流程

LayIM發(fā)送的消息會(huì)自動(dòng)打包成指定格式,其中包含發(fā)送用戶(hù)信息柒爸、接收用戶(hù)信息准浴、發(fā)送消息類(lèi)型等。
后端接收到數(shù)據(jù)捎稚,先判斷消息類(lèi)型乐横,以分別執(zhí)行對(duì)應(yīng)操作。如果是friend類(lèi)型今野,則提取消息發(fā)送出去葡公。
先會(huì)將消息組織成數(shù)據(jù)庫(kù)可存入的結(jié)構(gòu)、消息發(fā)送的結(jié)構(gòu)条霜,然后通過(guò)用戶(hù)ID判斷用戶(hù)是否在線(xiàn)催什,在線(xiàn)則通過(guò)用戶(hù)ID提取出最新的SocketID。ThinkJS把當(dāng)前所有的連接對(duì)象都存在了thinkCache(thinkCache.WEBSOCKET)只需要以SocketID為下標(biāo)宰睡,調(diào)出對(duì)象蒲凶,就可以調(diào)用emit向指定連接發(fā)送事件气筋。
如果有執(zhí)行發(fā)送,則push字段值修改為0旋圆,否則按默認(rèn)為1宠默。下次用戶(hù)登陸后會(huì)先讀取push1的數(shù)據(jù)推送至前端。
最后將數(shù)據(jù)存入數(shù)據(jù)庫(kù)灵巧。

接收到消息  ---> 重新拆分按指定格式重組數(shù)據(jù)  ---> 根據(jù)ID判斷用戶(hù)是否在線(xiàn)  ---> 將數(shù)據(jù)存入數(shù)據(jù)庫(kù)

后臺(tái)管理頁(yè)面

LayIM中本身就需要引入LayUI搀矫,所以就直接使用LayUI,找了一些默認(rèn)的頁(yè)面元素實(shí)現(xiàn)了前端頁(yè)面孩等。

登陸

直接訪(fǎng)問(wèn)IM服務(wù)器地址會(huì)出現(xiàn)簡(jiǎn)單的服務(wù)器信息艾君,點(diǎn)擊圖片會(huì)跳轉(zhuǎn)入登陸頁(yè)面(本來(lái)沒(méi)打算做管理頁(yè)面,常用的服務(wù)器信息都在此頁(yè)面)肄方。


密碼考慮到雖然前端經(jīng)過(guò)MD5無(wú)意義冰垄,但總是可以避免明文密碼在傳輸過(guò)程中被泄露,到后臺(tái)再經(jīng)過(guò)加鹽二次MD5权她,存入數(shù)據(jù)庫(kù)虹茶。


用戶(hù)登陸頁(yè)

前端的登陸信息,傳到后臺(tái)與數(shù)據(jù)庫(kù)比對(duì)隅要,正確即在session中標(biāo)記登陸蝴罪。若錯(cuò)誤,則以json返回錯(cuò)誤信息步清。退出登陸則清除session中存儲(chǔ)的信息要门。
ThinkJS提供了this.success()this.fail()廓啊、this.json()函數(shù)用來(lái)輸出JSON欢搜。頁(yè)面跳轉(zhuǎn)可以使用http.redirect(),其中輸入需要跳轉(zhuǎn)的頁(yè)面地址即可谴轮。

用戶(hù)管理

用戶(hù)管理主要實(shí)現(xiàn)可以查看所有用戶(hù)炒瘟,查看每個(gè)用戶(hù)的聊天對(duì)象,以及一對(duì)一聊天的聊天記錄第步。
沒(méi)有添加疮装、刪除、修改功能粘都,因?yàn)樗杏脩?hù)數(shù)據(jù)都是在主站上廓推,IM僅僅只是接收,并且每次登陸即更新翩隧,添加受啥、刪除、修改都沒(méi)意義。
ThinkJS中有提供分頁(yè)查詢(xún)函數(shù)model.page(page, listRows)滚局,page為頁(yè)數(shù)居暖、listRoes為每頁(yè)條數(shù),會(huì)自動(dòng)轉(zhuǎn)化為limit語(yǔ)句藤肢。
在此頁(yè)面有個(gè)功能并未做到網(wǎng)頁(yè)中太闺。打開(kāi)開(kāi)發(fā)者工具,會(huì)輸出登陸的用戶(hù)嘁圈、PC端省骂、Mobile端、移動(dòng)端未讀信息連接最住,四個(gè)變量中的數(shù)據(jù)钞澳,方便調(diào)試時(shí)查看網(wǎng)站情況。

用戶(hù)管理頁(yè)
聊天記錄
后臺(tái)賬號(hào)管理

沒(méi)有需要設(shè)置的地方涨缚,做一個(gè)演示功能轧粟。可添加新賬號(hào)脓魏、修改自己賬號(hào)密碼兰吟、刪除賬號(hào)


后臺(tái)設(shè)置-修改密碼中

上線(xiàn)

上線(xiàn)需要啟動(dòng)www/production.js文件,不再是www/development.js茂翔,并且修改文件后不會(huì)再自動(dòng)編譯運(yùn)行載入混蔼,要手動(dòng)編程、重啟珊燎。
其中ThinkJS默認(rèn)會(huì)關(guān)閉靜態(tài)資源訪(fǎng)問(wèn)惭嚣,需要在src/common/config/env/production.js文件中的resource_on改為true
有使用HTTPS可以使用Nginx作反向代理悔政。

后記

第一次接觸ThinkJS框架晚吞,理解上花了一些時(shí)間。LayIM因一些不知緣由的小錯(cuò)誤卓箫,也處理了好些時(shí)間(大部分是數(shù)據(jù)裝后不規(guī)范)载矿。
socket部分因?yàn)槭亲约嚎刂频顷懶畔⒙⒊薄z測(cè)用戶(hù)權(quán)限烹卒,所以邏輯上比較多。雖然總感覺(jué)可以寫(xiě)的更加簡(jiǎn)練弯洗,但目前沒(méi)找到更適合的方法旅急。就程序本身來(lái)說(shuō)并沒(méi)太多難的地方。

起初重寫(xiě)IM后臺(tái)時(shí)牡整,打算寫(xiě)一篇詳細(xì)的入門(mén)教程藐吮。但寫(xiě)寫(xiě)文章時(shí)發(fā)現(xiàn),有很多細(xì)節(jié)零散的東西,寫(xiě)入教程變動(dòng)啰嗦影響閱讀谣辞,不寫(xiě)入又不算是入門(mén)教程迫摔,于是最后改成僅僅記錄一些主要的邏輯。大部分地方需要配合源碼與文檔一起看泥从。

源碼

暫未開(kāi)放

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末句占,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子躯嫉,更是在濱河造成了極大的恐慌纱烘,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,723評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件祈餐,死亡現(xiàn)場(chǎng)離奇詭異擂啥,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)帆阳,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,485評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門(mén)哺壶,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人舱痘,你說(shuō)我怎么就攤上這事变骡。” “怎么了芭逝?”我有些...
    開(kāi)封第一講書(shū)人閱讀 152,998評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵塌碌,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我旬盯,道長(zhǎng)台妆,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,323評(píng)論 1 279
  • 正文 為了忘掉前任胖翰,我火速辦了婚禮接剩,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘萨咳。我一直安慰自己懊缺,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,355評(píng)論 5 374
  • 文/花漫 我一把揭開(kāi)白布培他。 她就那樣靜靜地躺著鹃两,像睡著了一般。 火紅的嫁衣襯著肌膚如雪舀凛。 梳的紋絲不亂的頭發(fā)上俊扳,一...
    開(kāi)封第一講書(shū)人閱讀 49,079評(píng)論 1 285
  • 那天,我揣著相機(jī)與錄音猛遍,去河邊找鬼馋记。 笑死号坡,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的梯醒。 我是一名探鬼主播宽堆,決...
    沈念sama閱讀 38,389評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼茸习!你這毒婦竟也來(lái)了日麸?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 37,019評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤逮光,失蹤者是張志新(化名)和其女友劉穎代箭,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體涕刚,經(jīng)...
    沈念sama閱讀 43,519評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡嗡综,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,971評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了杜漠。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片极景。...
    茶點(diǎn)故事閱讀 38,100評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖驾茴,靈堂內(nèi)的尸體忽然破棺而出盼樟,到底是詐尸還是另有隱情,我是刑警寧澤锈至,帶...
    沈念sama閱讀 33,738評(píng)論 4 324
  • 正文 年R本政府宣布晨缴,位于F島的核電站,受9級(jí)特大地震影響峡捡,放射性物質(zhì)發(fā)生泄漏击碗。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,293評(píng)論 3 307
  • 文/蒙蒙 一们拙、第九天 我趴在偏房一處隱蔽的房頂上張望稍途。 院中可真熱鬧,春花似錦砚婆、人聲如沸械拍。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,289評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)坷虑。三九已至,卻和暖如春验夯,著一層夾襖步出監(jiān)牢的瞬間猖吴,已是汗流浹背摔刁。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,517評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工挥转, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,547評(píng)論 2 354
  • 正文 我出身青樓绑谣,卻偏偏與公主長(zhǎng)得像党窜,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子借宵,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,834評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容

  • 點(diǎn)擊查看原文 Web SDK 開(kāi)發(fā)手冊(cè) SDK 概述 網(wǎng)易云信 SDK 為 Web 應(yīng)用提供一個(gè)完善的 IM 系統(tǒng)...
    layjoy閱讀 13,675評(píng)論 0 15
  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 171,516評(píng)論 25 707
  • 蒼南一個(gè)私人皮膚門(mén)診內(nèi)幌衣,看著丁醫(yī)生被圍得水泄不通,暗暗慶幸自己一大早就打電話(huà)取了號(hào)壤玫,還有兩個(gè)就輪到了豁护。等啊等...
    曉卉669閱讀 1,126評(píng)論 1 0
  • 安裝了vmware-tools和open-vm-tools后,發(fā)現(xiàn)能找到hgfs但是還是找不到分享文件夾 解決方法 原文
    silentsvv閱讀 4,190評(píng)論 0 1
  • 今天是陰歷九月三十欲间。每年的這個(gè)日子楚里,我都會(huì)去看您,和您說(shuō)說(shuō)心里話(huà)猎贴。今天班缎,我又如約而至。 現(xiàn)在她渴,我已經(jīng)接受...
    玫蘭妮閱讀 4,018評(píng)論 0 1