別)
1. 先搞清楚一件事wxid不是你輸入的那串號幾乎每個做過賬號相關(guān)開發(fā)或者數(shù)據(jù)處理的人都碰到過這個場景拿到一份歷史數(shù)據(jù)里面有一列叫wxid值長這樣wxid_a1b2c3d4e5f6g7你想拿它去搜個人、加個好友、或者做個對賬結(jié)果在搜索框里敲進去回車什么都出不來。這時候大部分人第一反應是是不是我少打了一位或者這個人是不是注銷了。實際上這兩者都不是wxid這個東西從設(shè)計之初就不是給人搜的。微信原始ID業(yè)界常說的 wxid全稱一般理解為 weixin ID 的內(nèi)部標識是賬號在系統(tǒng)內(nèi)部的一個唯一標識符。它跟你在個人資料頁看到的微信號完全是兩回事。微信號是你可以自定義、可以改、可以展示給別人的社交名片wxid 是系統(tǒng)自己發(fā)號時生成的一串字符用戶既看不見也沒法改它的作用是在數(shù)據(jù)庫里唯一鎖定一行賬號記錄。搜索限制也不是什么臨時起意的風控策略而是這套標識體系分工之后必然產(chǎn)生的結(jié)果。這篇內(nèi)容想解決的問題很具體把 wxid 這個字符串從看起來像亂碼變成能讀懂的工程產(chǎn)物。我會從它的字符串結(jié)構(gòu)、生成邏輯的合理推斷、為什么搜不到、以及它跟 openid、unionid 的關(guān)系幾個角度拆開講。適合的人群大概有三類做過賬號體系設(shè)計的技術(shù)人員、處理過歷史數(shù)據(jù)遷移的運維或數(shù)據(jù)同學、以及單純好奇為什么我的ID長這樣的重度用戶。前兩類能拿到可復用的判斷方法第三類能把這套東西的邊界搞明白避免被網(wǎng)上那些不靠譜的說法帶偏。我先把最容易混淆的一點擺到臺面上。很多人把 wxid 和微信號混為一談是因為早期版本的客戶端里這兩者在某些界面上確實出現(xiàn)過混用。但從數(shù)據(jù)模型上看它們是兩個獨立字段一個由系統(tǒng)發(fā)號器產(chǎn)生、全局唯一、永不變更一個由用戶申請或系統(tǒng)兜底分配、允許修改、需要唯一性校驗。搞清楚這個區(qū)別后面所有的為什么搜不到為什么換設(shè)備就變了為什么對不上賬才有解釋的落點。1.1 三類標識符的職責邊界把賬號體系里的標識符攤開其實只有三類角色各自負責一件事標識類型誰能看到是否可變主要用途wxid原始ID系統(tǒng)內(nèi)部 / 部分客戶端數(shù)據(jù)基本不變數(shù)據(jù)庫主鍵、消息路由、關(guān)系鏈歸屬微信號自定義ID用戶可見、他人可搜可修改有次數(shù)限制社交展示、被搜索、被添加昵稱所有人可見隨時可改展示不承擔唯一性這三者的關(guān)系有點像公司里的員工wxid 是工號入職就定了一輩子跟著你微信號是你自己起的花名可以改但得別人沒占用昵稱是你今天想叫什么都行甚至一天換三次。工號不會拿去做對外名片花名才會。這就是為什么產(chǎn)品方從來不在界面上暴露 wxid——它一旦暴露等于把內(nèi)部主鍵泄露出去后續(xù)所有基于這個主鍵的權(quán)限判斷都會變得不可控。還有一層容易被忽略wxid 的唯一性范圍是全局的。也就是說哪怕兩個賬號昵稱一樣、微信號差一個字符它們的 wxid 也絕不會撞。這個特性決定了它在消息投遞、好友關(guān)系存儲里的地位——關(guān)系表里存的不是你是你的 wxid。所以當你換了頭像、改了昵稱、甚至連微信號都改掉之后好友關(guān)系依然在因為底下掛的那根線沒動。1.2 wxid在賬號體系里扮演的角色再具體一點。一個賬號注冊完成系統(tǒng)要給它分配三樣東西內(nèi)部主鍵、對外可搜標識、展示名。內(nèi)部主鍵就是 wxid它必須在注冊那一刻就確定因為后續(xù)所有的寫操作——發(fā)第一條消息、加第一個好友、上傳第一張頭像——都要往這條記錄上掛。如果主鍵延遲生成就會出現(xiàn)賬號已存在但數(shù)據(jù)掛不上的中間態(tài)這是分布式系統(tǒng)里最不想看到的情況。所以 wxid 的生成時機基本可以鎖定在注冊落庫的瞬間由服務端統(tǒng)一發(fā)號客戶端拿到的只是結(jié)果。這也解釋了一個現(xiàn)象同一個人在不同時期注冊的兩個賬號wxid 的形態(tài)可能不一樣因為發(fā)號器的實現(xiàn)隨著版本迭代調(diào)整過。但同一個賬號的 wxid從注冊那天起就不會再變哪怕跨設(shè)備、跨系統(tǒng)、跨版本。這就引出一個實踐中的判斷依據(jù)如果你在兩份數(shù)據(jù)里看到同一個人的 wxid 不一樣那基本可以斷定這是兩個賬號而不是同一個人換了ID。這個結(jié)論在排查為什么好友列表里出現(xiàn)了兩個重復的人這類問題時特別管用。2. 拆開看字符串wxid_ 前綴后面那一截是什么第一次認真看 wxid 的人注意力通常會被那個wxid_前綴吸走。這個下劃線加前綴的組合本質(zhì)上是命名空間標記——告訴所有讀這個字段的程序這一列的值屬于微信ID體系別跟別的ID搞混。這種手法在工程上很常見加個前綴讓類型自解釋省得在文檔里反復寫注釋。真正有意思的是前綴后面那一截。以常見的形式為例wxid_后面跟著 12 到 16 位不等的字符字符集大致是a-z、0-9偶爾能看到下劃線。注意這里說的是觀察到的形式不是官方規(guī)范——官方從來沒公布過發(fā)號規(guī)則下面所有的推斷都來自對大量樣本的歸納屬于合理推測而非定論。2.1 前綴與字符集的外部觀察把幾百個樣本排在一起看能發(fā)現(xiàn)幾個穩(wěn)定特征。第一前綴wxid_是固定的沒出現(xiàn)過變體。第二后綴基本是小寫字母和數(shù)字的混合幾乎不見大寫字母這一點跟很多系統(tǒng)的做法不同——大寫字母在人工抄寫時容易跟數(shù)字混淆O和0、I和1去掉大寫等于降低了一半的誤讀風險。第三后綴長度有波動早期樣本偏短后來的樣本偏長。字符集收斂到什么程度可以直接倒推設(shè)計意圖。假設(shè)只用小寫字母加數(shù)字可用字符是 36 個。如果長度取 12 位總空間是 36 的 12 次方也就是大約 4.7×10^18四千多億億種組合。這個量級對一個用戶規(guī)模在十億級的系統(tǒng)來說空間綽綽有余。之所以不把字符集做大是因為沒必要用復雜度換空間——36 個字符已經(jīng)夠了硬塞大寫和符號只會增加出錯概率。長度波動的現(xiàn)象也能解釋。一種是發(fā)號器本身升級過新老賬號的位數(shù)不同另一種是后綴里可能拼了額外的校驗位或版本位。我傾向認為兩者都有。你在老備份數(shù)據(jù)里翻到的短 wxid大概率是早期注冊的賬號新注冊的賬號則普遍落在更長的區(qū)間里。這個規(guī)律不能當鐵律用但做數(shù)據(jù)清洗時拿它做個粗略的時間分層比瞎猜靠譜。2.2 非自增特征說明了什么最關(guān)鍵的觀察點在這里wxid 不是遞增的。你把一堆 wxid 按字典序排一遍再按注冊時間排一遍兩個順序?qū)Σ簧?。這意味著它不是一個全局自增序列而是一個看起來隨機的標識。這個設(shè)計選擇背后有非?,F(xiàn)實的理由。假設(shè)用的是自增號那么任何人拿到一個 wxid就能反推出這個賬號的注冊序號——注冊序號又大致對應注冊時間再結(jié)合公開信息很容易推算出這個賬號是第多少個用戶平臺在某年某月的增長曲線。這種信息泄露在商業(yè)上是很敏感的等于把運營數(shù)據(jù)半公開了。還有更直接的麻煩。自增號在分布式環(huán)境里沒法用——多臺發(fā)號服務器同時發(fā)號不靠中心化的鎖根本保證不了不重復。而中心化的鎖在高并發(fā)注冊場景下就是瓶頸。所以絕大多數(shù)現(xiàn)代系統(tǒng)都會繞開自增改用隨機 去重校驗或者時間戳 機器號 序列號這類方案。wxid 的形態(tài)更接近前者。2.3 長度差異與歷史版本關(guān)于長度差異我做過一次不太嚴謹?shù)慕y(tǒng)計把手上能拿到的樣本按長度分組短的那批幾乎全部關(guān)聯(lián)到很早的注冊時間長的那批則集中在近幾年。這個分布很符合發(fā)號器升級的假設(shè)——早期用戶量小短 ID 夠用后來用戶量上去了加上安全要求提高就把位數(shù)拉長了。但這里有個坑要提醒。不要拿長度去做強判斷。有些賬號的 wxid 后面還帶一段后綴看起來更長但那可能是某一層系統(tǒng)追加的標記不是原始發(fā)號的結(jié)果。我在一次數(shù)據(jù)對賬時就吃過這個虧把帶后綴的當成新版賬號結(jié)果發(fā)現(xiàn)那批全是同一個渠道導入的跟注冊時間沒有半點關(guān)系。后來改成看整體字符分布才對上。判斷一個 wxid 屬于哪個時期靠長度不如靠它出現(xiàn)在哪份數(shù)據(jù)里。數(shù)據(jù)庫導出、客戶端緩存、第三方系統(tǒng)的冗余字段這三個來源的可信度是遞減的。真要溯源應該先確認字段來源再談其他。3. 生成邏輯的合理推斷為什么不能用遞增號這一段是我最想展開的部分因為它能解釋很多看似莫名其妙的設(shè)計。前面說了 wxid 不是自增那它到底是什么在沒有官方文檔的前提下我們只能從工程約束倒推。而工程約束這種東西其實是有標準答案的——不管你做什么產(chǎn)品只要用戶量上了規(guī)模發(fā)號器的可選方案就那么幾種每種方案的優(yōu)缺點都是公開的。3.1 分布式發(fā)號器的三個死穴先說說為什么自增這條路走不通。很多人直覺上覺得自增最簡單來一個注冊計數(shù)器加一完事。單機場景下確實如此但放到真實系統(tǒng)里立刻暴露三個問題。第一個是并發(fā)。注冊請求不是排隊來的同一毫秒可能有幾千個請求打到不同機器上。要讓它們共享一個計數(shù)器就必須有一把全局鎖。鎖一加吞吐量直接掉一個數(shù)量級而且鎖本身會成為單點——它掛了整個注冊鏈路就停了。這個代價沒人愿意付。第二個是可預測。自增號的信息量太大了。拿到1000001和1000002你就知道這倆是連著注冊的。這在風控上是個大漏洞批量注冊的人只要觀察自己拿到的號就能推算出平臺的注冊速率進而判斷什么時候下手不容易被檢測。反過來說隨機化的 ID 讓這種推算失效。第三個是遷移成本。自增號一旦分庫分表跨庫的唯一性就崩了。你得引入額外的號段分配機制復雜度立刻上去了。而隨機 ID 天然支持水平擴展多加一臺發(fā)號機只是多一個隨機源不影響全局唯一性。三個死穴疊在一起結(jié)論就很清楚了規(guī)模化系統(tǒng)不會用自增做主鍵。這不是偏好問題是能不能跑起來的問題。3.2 碰撞概率與隨機源那隨機 ID 怎么保證不重復答案是空間足夠大 落庫時校驗。這里可以算一筆賬看看空間到底要多大才夠。假設(shè)字符集 36 個小寫字母 數(shù)字長度 12 位總空間約 4.7×10^18。用戶規(guī)模按 10 億算用生日問題估算碰撞概率公式是n2 / (2N)代入得(10^9)2 / (2 × 4.7×10^18)約等于1.06×10^-4也就是萬分之一點零六。長度字符集總空間10億用戶下的碰撞概率8 位362.8×10^12約 17.8%太高10 位363.7×10^15約 0.0135%12 位364.7×10^18約 0.000106%14 位366.1×10^21約 8×10^-8%這張表說明一件事12 位是一個性價比很高的位置。再短碰撞概率就進入每年都要處理幾萬次重號的區(qū)間雖然可以靠重試解決但重試本身也是成本再長收益遞減用戶看到的字符串變長抄寫和存儲成本上升。實際系統(tǒng)里還會加一層落庫時唯一索引校驗 沖突重試所以哪怕碰撞概率再高一個數(shù)量級也能兜住。這里順帶說一個常見的誤解。有人覺得隨機生成就一定會有重復所以必須用某種確定性算法。實際上現(xiàn)代系統(tǒng)普遍采用高熵隨機源 唯一性校驗的組合隨機源保證不可預測唯一索引保證不出錯。這兩件事不矛盾一個是概率層面一個是確定層面。3.3 生成時機與生命周期生成時機這件事從產(chǎn)品行為上能反推出一些線索。比如兩個賬號在同一秒注冊wxid 的字符分布沒有明顯相關(guān)性——說明發(fā)號彼此獨立沒有共享狀態(tài)。再比如同一個賬號在換設(shè)備登錄后 wxid 不變——說明它不是設(shè)備指紋派生的是賬號級的。生命周期上wxid 基本是一次生成終身不變。注銷之后重新注冊拿到的是全新的 wxid跟舊賬號沒有任何關(guān)聯(lián)。這個設(shè)計有它的道理如果允許復用那么舊的聊天記錄、舊的好友關(guān)系、舊的舉報記錄都會掛到新用戶頭上出了事根本說不清。不復用是底線不是優(yōu)化。我在處理一批注銷賬號的歷史數(shù)據(jù)時驗證過這一點。那批數(shù)據(jù)里有些 wxid 對應的賬號已經(jīng)不存在了但消息表里還留著指向它們的引用。當時第一反應是數(shù)據(jù)臟了后來才想明白消息是歷史事實賬號注銷不等于歷史記錄該被抹掉。wxid 作為那根不動的錨正好承擔了這個職責。4. 搜索限制的底層原因為什么這個ID搜不到人回到最開始那個場景。你在搜索框里輸入一長串wxid_開頭的東西點了搜索結(jié)果什么都沒有。這不是bug是設(shè)計。要理解這個設(shè)計得先明白搜索這個動作在產(chǎn)品里意味著什么。搜索是一個把輸入映射到賬號的過程映射關(guān)系越寬能搜到的人越多同時被騷擾、被撞庫、被惡意定位的風險也越大。所以任何產(chǎn)品在設(shè)計搜索入口時都要在好用和安全之間劃一條線。這條線畫在哪里決定了哪些字段可以被搜。4.1 可搜索字段是被刻意收窄的從產(chǎn)品行為上看能被搜到的維度就那么幾個微信號、綁定的手機號、綁定的其他社交賬號、群內(nèi)昵稱。這幾個維度的共同點是——它們要么是用戶主動設(shè)置的要么是用戶主動綁定的而且都帶有某種我愿意被找到的默示同意。微信號是你自己起的手機號是你自己綁的這些都能理解為一種授權(quán)。wxid 完全不符合這個特征。它是系統(tǒng)分配的用戶既不知道它的存在也無法控制它。拿一個用戶不知情的標識去做搜索入口等于繞過了用戶的知情權(quán)。這不是技術(shù)做不到是產(chǎn)品倫理上不該做。還有一層更實際的原因。如果 wxid 可搜那么所有拿到過 wxid 的第三方——數(shù)據(jù)合作方、歷史導出、日志殘留——都能反查到賬號。這意味著一個泄露點就能擴散成全網(wǎng)可查。收窄搜索面本質(zhì)上是在切斷這條擴散鏈路。4.2 如果放開會怎樣三個角度推演不妨做個思想實驗。假設(shè)某天產(chǎn)品方突然開放 wxid 搜索會發(fā)生什么。從騷擾成本角度看騷擾者只需要拿到一批 wxid就能批量嘗試添加而用戶完全無從防御——你連自己的 wxid 是什么都不知道怎么設(shè)置不允許通過此方式找到我現(xiàn)有的一堆隱私開關(guān)全部失效。從數(shù)據(jù)黑產(chǎn)角度看wxid 在各類導出數(shù)據(jù)、緩存文件、第三方SDK日志里出現(xiàn)的頻率遠高于微信號。一旦可搜這些零散數(shù)據(jù)立刻變成可用的賬號字典價值翻好幾倍。從誤傷角度看wxid 是一串沒有語義的字符用戶搜的時候極易打錯一位。打錯一位就落到另一個真實賬號上這種事多了會變成客訴災難。三個角度指向同一個結(jié)論不開這個口子收益遠大于損失。所以搜索限制不是暫時沒做而是不打算做。4.3 拿到wxid也加不上好友這件事這一條很多人沒意識到。就算你從某份數(shù)據(jù)里拿到了某人的 wxid你也加不上他。原因很簡單添加好友的接口接受的參數(shù)是可搜標識不是內(nèi)部主鍵。你手里的 wxid 在接口層面根本不是合法輸入。網(wǎng)上流傳的一些所謂用 wxid 加好友的方法我建議一律不要信也不要試。原因有兩方面。一是技術(shù)上那些方法大多依賴對客戶端行為的非正常干預隨時會失效而且極可能觸發(fā)風控把自己的賬號搭進去。二是合規(guī)上繞過正常的產(chǎn)品路徑去觸達他人本身就踩在邊界線上。你手里的 wxid 唯一能證明的事情是這個賬號存在過僅此而已。我在實際工作中遇到過有人拿著 wxid 來找我對賬問這個人是誰。我的標準回答是查不到也不該查。真要定位一個賬號走正規(guī)路徑——讓對方提供微信號或者掃碼比在數(shù)據(jù)里翻找高效得多也安全得多。5. 開發(fā)者容易混淆的地方wxid、openid、unionid如果你的工作涉及公眾號、小程序或者任何開放平臺的能力對接那你一定見過 openid 和 unionid。這兩個東西跟 wxid 經(jīng)常被混著叫但它們完全是不同層次的東西。混用的后果很實際——對賬對不上、用戶身份串號、遷移后數(shù)據(jù)全亂。5.1 三條ID鏈路各自的用途我把三者的定位擺成一張表看起來最清楚標識作用域是否跨應用典型用途wxid整個賬號體系是內(nèi)部主鍵、關(guān)系鏈、消息路由openid單個應用公眾號/小程序否應用內(nèi)識別用戶、發(fā)消息、埋點unionid同一主體下的多個應用是限同主體跨應用打通同一用戶關(guān)鍵差異在于作用域。同一個用戶在 A 小程序里的 openid 和 B 小程序里的 openid 是兩個不同的值哪怕這兩個小程序?qū)儆谕患夜?。要讓兩個應用認出這是同一個人就得靠 unionid。而 wxid 的層級比這兩個都高它是賬號本身openid 和 unionid 都是在這個賬號之上派生出來的應用層視圖。打個比方。wxid 是身份證號openid 是你在某家銀行的客戶號unionid 是同一家銀行集團下各子公司的統(tǒng)一客戶號。你在不同銀行有不同的客戶號但身份證號只有一個。銀行之間不會拿身份證號互相對賬它們用的是自己的客戶號體系。5.2 數(shù)據(jù)遷移時踩過的坑這個坑我印象很深。有次做數(shù)據(jù)遷移源庫里有一列叫user_id遷移腳本直接當成 wxid 處理了。跑完之后發(fā)現(xiàn)用戶頭像全串了——A 的頭像掛到了 B 頭上。排查了半天才定位到問題源庫那列存的其實是 openid不是 wxid。為什么會這樣因為兩個字段在形態(tài)上有相似之處都是長字符串都沒有明顯語義。寫腳本的人看到列名user_id想當然認為是主鍵。這就是典型的字段名騙人。后來我總結(jié)了一條硬規(guī)矩任何遷移腳本在處理 ID 類字段之前必須先做形態(tài)抽樣。具體做法是從源庫隨機取 100 行把值打印出來看前綴。wxid_開頭的一眼就能認出來o開頭的多半是 openid純數(shù)字或者 UUID 形態(tài)的又是另一回事。這一步花不了五分鐘能省掉后面幾天的返工。還有個更隱蔽的坑某些第三方系統(tǒng)會把 wxid 和 openid 拼在一起存用分隔符隔開。這種字段做字符串匹配時特別容易出問題因為分隔符本身也可能是 ID 字符集的一部分。穩(wěn)妥做法是先按分隔符切分再對每一段做形態(tài)校驗別偷懶直接LIKE。6. 老設(shè)備、老備份里那些殘留的wxid聊完原理說點更貼近日常的。很多人接觸到 wxid 不是在開發(fā)場景而是在整理自己的舊手機備份、導聊天記錄、或者換機遷移的時候。這些場景下冒出來的 wxid處理起來有一套自己的門道。6.1 遷移場景下的標識錯位換機遷移的時候最常遇到的現(xiàn)象是好友列表里出現(xiàn)了不認識的賬號。點進去看昵稱是空的頭像是默認的唯一能看到的標識就是一串wxid_開頭的東西。這種情況多半是歷史關(guān)系殘留。你曾經(jīng)加過某個人后來對方注銷或者改了資料關(guān)系記錄還在但展示信息已經(jīng)取不到了。系統(tǒng)只能把內(nèi)部標識拿出來頂上于是你看到了一串亂碼一樣的字符。它不是新出現(xiàn)的東西是一直在只是以前被昵稱和頭像蓋住了。還有一種情況是群成員列表里的僵尸賬號。群還在但某些成員早已不活躍展示信息為空只留下 wxid。這類記錄不建議手動清理——你沒法確認對方是不是只是長期未登錄。等系統(tǒng)自己處理比動手強。6.2 幾個流傳很廣的錯誤認知最后清理幾個流傳很廣的說法這些都是我在實際交流中被問到過多次的。說法一wxid 可以改。不行。它由系統(tǒng)發(fā)號用戶側(cè)沒有任何修改入口。你看到的改ID操作的永遠是微信號那一欄。說法二wxid 里藏著注冊時間。從形態(tài)上看不出時間信息。它更像純隨機串沒有可解析的時間戳段。有人聲稱能解碼基本都是事后附會。說法三wxid 可以用來找回賬號。不行。賬號找回走的是手機號、郵箱、或者好友輔助驗證這幾條路徑wxid 不在其中。你要是只記得 wxid那基本等于什么都沒記住。說法四不同人的 wxid 長度一樣就說明是同一批注冊的。前面說過長度受發(fā)號器版本影響不能反推注冊批次。這個推論在數(shù)據(jù)量小的時候尤其不可靠。我個人在實際處理這類數(shù)據(jù)時的習慣是把 wxid 當成一個不可解讀的、只用于精確匹配的字符串。不做形態(tài)推斷不做前綴之外的任何解析不在它身上附加業(yè)務含義。需要業(yè)務信息的時候去關(guān)聯(lián)其他字段別指望從這串字符里挖出什么。這個態(tài)度看起來保守但能避免絕大多數(shù)誤判。順便提一個實用的小技巧。如果你需要在本地數(shù)據(jù)里區(qū)分這是賬號主鍵還是這是應用層ID最省事的辦法是建一個字段形態(tài)白名單前綴wxid_歸主鍵前綴o且長度在 28 左右的歸 openid純 32 位十六進制的歸另一類。這個規(guī)則不完美但覆蓋八成場景足夠剩下的靠人工抽樣兜底。規(guī)則寫完記得寫注釋說明這是經(jīng)驗規(guī)則不是規(guī)范免得后來的人當成標準照抄。這套東西說到底就是個分工問題誰負責唯一性誰負責可搜誰負責展示。分清楚了那些看起來莫名其妙的限制就都有了合理的解釋。真要說有什么體會就是別跟系統(tǒng)的主鍵較勁——它不是給你用的你也就沒必要想辦法去用它。