導出微信iOS數據庫

說明

本文是作者Lefe所創,轉載請注明出處,如果你在閱讀的時候發現問題歡迎一起討論。本文會不斷更新。

正文

微信絕對是在IM領域的領軍人物,無論是從性能還是用戶體驗方面,它都是非常棒的。所以作者打算拆一拆微信的包,一探究竟。本文主要從數據庫方面來聊一聊微信數據庫的設計,也許有不對的地方,希望讀者可以指出。微信的數據庫中主要記錄了消息,好友,漂流瓶,表情的數據,至于朋友圈這種數據,

一、如何獲取微信的數據庫

  • 1.手機連接到iTunes,把手機的數據加密備份,記得要記住密碼,數據恢復的時候會用到,然后點擊立即備份,等待備份完成。
屏幕快照 2017-03-12 上午9.59.36.png
  • 2.把剛才備份的數據導出,Lefe使用的是 iPhone Backup Extractor,使用免費版的就可以,不過有10秒的廣告和每次只能導出4個文件的限制。下載后直接安裝,用USB連接到手機,打開 iPhone Backup Extractor。將會顯示:
屏幕快照 2017-03-12 上午10.11.08.png

輸入密碼后點擊,【Check】,點擊后耐心等待,時間比較長。

  • 3.找到微信的包,在目錄Application Domains/com.tencent.xin/{UUID}/DB/MM.sqlite下,直接將MM.sqlite導出即可。
屏幕快照 2017-03-12 上午10.36.20.png

二、分析數據庫

  • 1.下載sqlite數據庫工具 SqliteStudio,有了這個工具,我們就可以看到數據庫中的數據了。關于這個客戶端的使用,Lefe就不一一介紹了。

  • 2.添加數據庫,數據庫整體結構如下:

數據庫.png
  • 3.通過觀察數據庫可以發現,微信會根據每一個會話創建一張表。Chat_006ea3832f24de6e294058a8046a7041,這是一張聊天表,006ea3832f24de6e294058a8046a7041應該是根據某一規則生成的一個會話ID,來唯一標記一個會話。這張表中記錄了與某一個人或者某個群的聊天信息。如果有對方消息,將會生成另外一張表ChatExt2_006ea3832f24de6e294058a8046a7041,這張表中僅記錄了對方的聊天記錄。具體記錄如下:
    Chat_006ea3832f24de6e294058a8046a7041 表中的數據:
屏幕快照 2017-03-12 上午10.45.58.png

字段說明:
CREATE TABLE Chat_006ea3832f24de6e294058a8046a7041 ( TableVer INTEGER DEFAULT 1, // 表的版本,應該是數據表升級使用 MesLocalID INTEGER PRIMARY KEY AUTOINCREMENT, // 本地消息ID,是主鍵,這里會有與沙盒中的數據有關聯 MesSvrID BIGINT DEFAULT 0, // 服務端的消息ID CreateTime INTEGER DEFAULT 0, // 創建時間 Message TEXT, // 具體消息內容,這里可以是普通字符串,也可以是XML文件,具體不知道微信使用XML文件有什么好處 Status INTEGER DEFAULT 0, // 消息狀態,比如發送失敗,成功,正在發送 ImgStatus INTEGER DEFAULT 0, // 圖片的狀態 Type INTEGER, // 消息類型 Des INTEGER // 是否為自己發的消息 );
ChatExt2_006ea3832f24de6e294058a8046a7041表中數據

屏幕快照 2017-03-12 上午10.52.51.png

這張表中的數據不知道具體做什么業務邏輯,不過估計是和服務端的一個交互,它僅僅和發消息有關。

  • 4.索引,如果想提高查詢速度,創建索引是必不可少的,微信消息表中的索引主要有:
屏幕快照 2017-03-12 上午11.45.57.png

三、對于不確定字段的消息使用XML

不知道微信出于何種目的使用XML來存儲消息而不是用Json。這是一條語音消息的XML,主要記錄與語音相關的一些數據。
<msg> <voicemsg endflag="1" length="5191" voicelength="2765" clientmsgid="413732333061346137623033623761000210050311171040be667d4103" fromusername="wxid_hhu2ojejexmy22" downcount="0" cancelflag="0" voiceformat="4" forwardflag="0" bufid="435693659307967035"/> </msg>

四、沙盒與數據庫的關系

關鍵點就是ID:006ea3832f24de6e294058a8046a7041MesLocalID,尋找沙盒中的文件會根據這兩個ID來找到對應的資源文件,比如音頻和視頻。這樣可以很方便的找到某一條消息對應的資源。

屏幕快照 2017-03-12 上午11.06.10.png

五、好友表

CREATE TABLE Friend ( TableVer INTEGER DEFAULT 1, // 表的版本 UsrName TEXT NOT NULL PRIMARY KEY ON CONFLICT REPLACE,// 用戶名,唯一 NickName TEXT, // 昵稱 Uin INTEGER DEFAULT 0, Email TEXT, Mobile TEXT, Sex INTEGER DEFAULT 0, FullPY TEXT, ShortPY BLOB, Img TEXT, Type INTEGER DEFAULT 0, LastChatTime INTEGER DEFAULT 0, // 最后聊天時間 Draft TEXT // 草稿 );
這里主要說明一下:PRIMARY KEY ON CONFLICT REPLACE,這句話的意思是說如果沖突了,就替換的。

微信的好友表是把所有的用戶放到了一張表,不管是好友還是非好友。這張表中包含的用戶有好友,群組,公眾號等,總的來說是客戶端所有用戶的一個集合,想想做密語的時候,為什么要多個表呢?如果是一個表,是不取所有用戶的昵稱,頭像等信息時就不需要進行連表查詢了。而且僅使用一個 Model 既可以搞定,這樣不會設置到不同用戶 Model 之間的轉換。

還有一個問題比較好奇,微信用戶的頭像不會及時更新,只有進入詳情后會更新。
它有個字段叫 imgStatus 標記著頭像的當前狀態,猜測是為了更新頭像用。

CREATE TABLE Friend (
    userName               TEXT    PRIMARY KEY ON CONFLICT REPLACE,
    type                   INTEGER DEFAULT 0,
    certificationFlag      INTEGER DEFAULT 0,
    imgStatus              INTEGER DEFAULT 0,
    encodeUserName         TEXT,
    dbContactLocal         BLOB,
    dbContactOther         BLOB,
    dbContactRemark        BLOB, // 昵稱或好友的備注
    dbContactHeadImage     BLOB,
    dbContactProfile       BLOB,
    dbContactSocial        BLOB,
    dbContactChatRoom      BLOB, // 所有群成員
    dbContactBrand         BLOB,
    _packed_DBContactTable BLOB
);

六、消息表類型

通過下面對微信消息的分析可以得出以下結論:
微信消息類型主要分為:

  • 系統消息:1000
  • 文本消息,包含小表情:1
  • 圖片消息,相機中的照片和配置有不同,從相冊中發送的消息中會保留一個 MMAsset,如同 PAAset:3
  • 位置消息: 48
  • 語音消息:34
  • 名片消息,公眾號名片和普通名片用的是同一種類型:42
  • 大表情:47
  • 分享消息,這種消息會含有多種類型,比如分享的收藏,分享的小程序,微信紅包等等。這種消息類型可以避免不斷添加多種消息類型,像這種預先定義一種消息類型,預留一些字段,這樣產品添加消息類型的時候,UI 可以任意組合:49

系統消息

type: 1000
content: 你邀請武卓、田向陽、memory、劉運新加入了群聊

文本消息

type: 1
content: 測試個東西,不要發消息[微笑]

圖片消息

type: 3
content:

<msg>
  <img 
  hdlength="0" 
  length="25739" 
  cdnbigimgurl=""
  cdnmidimgurl="加密過的 url" 
  aeskey="7dae3aef046a444d88a5cc679738c10b" 
  cdnthumburl="加密過的 url" 
  cdnthumblength="3312" 
  cdnthumbwidth="120" 
  cdnthumbheight="70" 
  cdnthumbaeskey="7dae3aef046a444d88a5cc679738c10b" 
  encryver="1" 
  md5="69b3f7f0554618cc5ad94b0924dcb79d"/>
  
  <MMAsset>
    <m_assetUrlForSystem><![CDATA[34340C09-0423-4DDC-AEC7-5AEABD083C28/L0/001]]></m_assetUrlForSystem>
    <m_isNeedOriginImage>0</m_isNeedOriginImage>
    <m_isFailedFromIcloud>0</m_isFailedFromIcloud>
    <m_isLoadingFromIcloud>0</m_isLoadingFromIcloud>
  </MMAsset>
</msg>

相機圖片

type:3
content: 

<msg>
  <img 
  hdlength="590953" 
  length="61171" 
  cdnbigimgurl="加密過的 url" 
  cdnmidimgurl="加密過的 url"
   aeskey="6d7f1c6d4e4d4bd3a2f94994646ebc17" 
   cdnthumburl="加密過的 url" 
   cdnthumblength="3540" 
   cdnthumbwidth="67" 
   cdnthumbheight="120" 
   cdnthumbaeskey="6d7f1c6d4e4d4bd3a2f94994646ebc17" 
   encryver="1" 
   md5="e8510edd66d6594c560fcd32be886ad5"/>
</msg>

位置消息:

type: 48
content:

<msg>
<location 
x="39.955407" 
y="116.458604" 
scale="15.010000" 
label="北京市朝陽區三元橋天元港中心(東三環北路)" 
poiname="朝陽區三元橋天元港中心(東三環北路)" 
maptype="roadmap" 
infourl="" 
fromusername="" />
</msg>

微信紅包(發)

type: 49
content:

<msg>
  <appmsg appid="" sdkver="0">
    <title>微信紅包</title>
    <des>我給你發了一個紅包,趕緊去拆! 祝:恭喜發財,大吉大利!</des>
    <action/>
    <type>2001</type>
    <showtype>0</showtype>
    <soundtype>0</soundtype>
    <mediatagname/>
    <messageext/>
    <messageaction/>
    <content/>
    <contentattr>0</contentattr>
    <url>https://wxapp.tenpay.com/mmpayhb/wxhb_personalreceive?showwxpaytitle=1&msgtype=1&channelid=1&sendid=1000039401201707207016154830099</url>
    <lowurl/>
    <dataurl/>
    <lowdataurl/>
    <appattach>
      <totallen>0</totallen>
      <attachid/>
      <emoticonmd5/>
      <fileext/>
      <cdnthumbaeskey/>
      <aeskey/>
    </appattach>
    <extinfo/>
    <sourceusername/>
    <sourcedisplayname/>
    <thumburl>http://wx.gtimg.com/hongbao/1701/hb.png</thumburl>
    <md5/>
    <statextstr/>
    <wcpayinfo>
      <paysubtype>10001</paysubtype>
      <feedesc><![CDATA[(null)]]></feedesc>
      <transcationid><![CDATA[(null)]]></transcationid>
      <transferid><![CDATA[(null)]]></transferid>
      <invalidtime>0</invalidtime>
      <effectivedate>0</effectivedate>
      <begintransfertime>0</begintransfertime>
      <templateid>7</templateid>
      <url><![CDATA[https://wxapp.tenpay.com/mmpayhb/wxhb_personalreceive?showwxpaytitle=1&msgtype=1&channelid=1&sendid=1000039401201707207016154830099]]></url>
      <nativeurl><![CDATA[wxpay://c2cbizmessagehandler/hongbao/receivehongbao?msgtype=1&channelid=1&sendid=1000039401201707207016154830099&sendusername=wxid_5lg2yjtnadtk21&transid=8fb3e6d42021e3496f471c0e9652f1f0e80f669946d0e33b9e5b2d5f2412a3442ffacce6c5f2cc4a291fb39ff52acdd8]]></nativeurl>
      <iconurl><![CDATA[http://wx.gtimg.com/hongbao/1701/hb.png]]></iconurl>
      <locallogoicon><![CDATA[c2c_hongbao_icon_cn]]></locallogoicon>
      <receivertitle><![CDATA[恭喜發財,大吉大利]]></receivertitle>
      <sendertitle><![CDATA[紅包已被領完]]></sendertitle>
      <hinttext><![CDATA[(null)]]></hinttext>
      <scenetext><![CDATA[微信紅包]]></scenetext>
      <sceneid>1002</sceneid>
      <redenvelopetype>-1</redenvelopetype>
      <redenvelopereceiveamount>-1</redenvelopereceiveamount>
      <senderdes><![CDATA[查看詳情]]></senderdes>
      <receiverdes><![CDATA[領取紅包]]></receiverdes>
      <total_fee><![CDATA[(null)]]></total_fee>
      <fee_type><![CDATA[(null)]]></fee_type>
      <innertype>0</innertype>
      <paymsgid><![CDATA[1000039401201707207016154830099]]></paymsgid>
      <pay_memo><![CDATA[(null)]]></pay_memo>
      <imageid><![CDATA[]]></imageid>
      <imageaeskey><![CDATA[]]></imageaeskey>
      <imagelength>0</imagelength>
      <newaa>
        <billno><![CDATA[(null)]]></billno>
        <newaatype>0</newaatype>
        <launchertitle><![CDATA[(null)]]></launchertitle>
        <receivertitle><![CDATA[(null)]]></receivertitle>
        <receiverlist><![CDATA[(null)]]></receiverlist>
        <payertitle><![CDATA[(null)]]></payertitle>
        <payerlist><![CDATA[(null)]]></payerlist>
        <notinertitle><![CDATA[(null)]]></notinertitle>
        <launcherusername><![CDATA[(null)]]></launcherusername>
      </newaa>
    </wcpayinfo>
  </appmsg>
  <fromusername>wxid_5lg2yjtnadtk21</fromusername>
  <appinfo>
    <version>0</version>
    <appname/>
    <isforceupdate>1</isforceupdate>
  </appinfo>
</msg>

好友領取紅包

type: 1000
content:

![](SystemMessages_HongbaoIcon.png)  劉運新領取了你的<_wc_custom_link_ color="#FD9931" href="weixin://weixinhongbao/opendetail?sendid=1000039401201707207016154830099">紅包</_wc_custom_link_>

![](SystemMessages_HongbaoIcon.png)  memory領取了你的<_wc_custom_link_ color="#FD9931" href="weixin://weixinhongbao/opendetail?sendid=1000039401201707207016154830099">紅包</_wc_custom_link_>

![](SystemMessages_HongbaoIcon.png)  田向陽領取了你的<_wc_custom_link_ color="#FD9931" href="weixin://weixinhongbao/opendetail?sendid=1000039401201707207016154830099">紅包</_wc_custom_link_>,你的紅包已被領完 

語音消息

type: 34
content:
<msg>
<voicemsg voicelength="3920" voiceformat="4" forwardflag="0" />
</msg>

名片消息

type: 42
content:

<msg 
username="wxid_0td2kgz84pg921" 
nickname="memory" 
fullpy="memory" 
shortpy="" 
alias="xuehaoxia1111" 
imagestatus="3" 
scene="17" 
province="山東" 
city="中國" 
sign="" 
sex="2" 
certflag="0" 
certinfo="" 
brandIconUrl="" 
brandHomeUrl="" 
brandSubscriptConfigUrl="" 
brandFlags="0" 
regionCode="CN_Shandong_Yantai"/>

轉發收藏消息

type:
content: 和 微信紅包(發)消息格式一樣

大表情

type: 47
content:
<msg>
  <emoji 
  md5="11454a2b7038f07a5512f9c62daac0cf" 
  type="2" 
  len="14732"
  productid="com.tencent.xin.emoticon.person.stiker_14749712227df9bdb9bfc1cd40" 
  width="240" 
  height="240"/>
  <gameext type="0" content="0"/>
</msg>

分享小程序

type: 49
content: 和 微信紅包(發)消息格式一樣

公眾號名片

type: 42
content: 和普通名片消息的結構一樣

七、總結

關于IM本地數據庫中的消息表非常重要,微信采用了分表的方式來提高性能及速度,但是對于小型的IM APP來說,這種設計方式會增加操作的復雜度,比如全局搜索。但是通過這次分析微信的數據庫,我們可以借鑒他的優點。比如對于不確定的字段個數,可以作為一個XML來保存成一個字段,或者JSON,消息和本地的資源更好的聯系起來。

本文主要參考:
https://github.com/Unknwon/wuwen.org/issues/15
http://www.race604.com/sqlite-insert-or-replace/

===== 我是有底線的 ======
喜歡我的文章,歡迎關注我的新浪微博 Lefe_x,我會不定期的分享一些開發技巧

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,825評論 6 546
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,814評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,980評論 0 384
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 64,064評論 1 319
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,779評論 6 414
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 56,109評論 1 330
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,099評論 3 450
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,287評論 0 291
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,799評論 1 338
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,515評論 3 361
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,750評論 1 375
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,221評論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,933評論 3 351
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,327評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,667評論 1 296
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,492評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,703評論 2 380

推薦閱讀更多精彩內容