個人用戶管理是業務系統中非常基礎且重要的一個公共服務系統,我們寫的絕大多數應用都和個人用戶或會員有關,用戶(會員)數據安全無小事,必須有一個完備的用戶管理平臺系統。 因為不同公司的主業務不同,個人用戶管理的側重點也會有不同,PowerDotNet這裡介紹的個人用戶管理平臺,只是個人用戶管理系統中很基 ...
個人用戶管理是業務系統中非常基礎且重要的一個公共服務系統,我們寫的絕大多數應用都和個人用戶或會員有關,用戶(會員)數據安全無小事,必須有一個完備的用戶管理平臺系統。
因為不同公司的主業務不同,個人用戶管理的側重點也會有不同,PowerDotNet這裡介紹的個人用戶管理平臺,只是個人用戶管理系統中很基礎的通用功能的一部分。
當然,在我自己開發過的所有公共服務系統中,PCRM是中規中矩一般複雜甚至我個人認為是架構很簡單的系統,真正混亂且困難的是訂單、支付、財務、結算、庫存、生產加工、配送等複合型系統。
曾經在某司接手過一個複雜繁瑣另類但其實不中看更不中用的個人用戶管理系統,功能極其凌亂,劃分非常隨意,實現也很惡劣,行業水平之低令人髮指,總之就是稀爛到自己人都看不下去,咩哈哈。
因為個人在支付、財務、結算、賬戶和票券等系統有豐富的開發經驗,對PCRM和這些系統的緊密聯繫耳濡目染,所以也認同某些模塊或功能放到PCRM更合理,比如混合支付中的公司賬戶和積分功能。
在介紹支付平臺的文章中,我們提到混合支付的概念,認為“不同的公司有不同的虛擬貨幣系統,混合支付是指公司的虛擬貨幣和外部的支付方式一起支付完成支付請求的一種玩法”。
混合支付設計和實現有很大的技術挑戰,我已經不止一次開發維護過混合支付代碼,尤其是涉及到虛擬貨幣(賬戶)、積分和票券等系統,恐怖程度堪稱夜能止小兒啼哭,日可令惡犬夾尾,咩哈哈。
虛擬貨幣(賬戶)系統每家公司實現的方式可能都不一樣,本來可以另開篇幅說明,但根據我的開發經驗,這部分工作量也不小,為了快速開發需要,只要設計抽象合理,不拆分也能高效使用,所以就偷懶了^_^。
考慮到虛擬貨幣(賬戶)系統和個人用戶的緊密聯繫,將這兩個系統劃分到一起也無可厚非,我就把用戶虛擬貨幣(賬戶)系統抽象到PCRM中進行講解,當然要獨立出來也很容易,根據數據表結構首碼拆分即可。
有一種類似賬戶系統的票券系統,主要處理優惠券相關的業務邏輯,雖然處理方式可以參考虛擬貨幣系統部分處理邏輯,但又和訂單、活動等系統“耦合”較為緊密,這個可以抽象出獨立系統,等有空再寫寫這方面的內容。
根據個人經驗,賬戶和票券系統,設計和實現的不好,往往會造成業務災難,比如支付金額不准確,財務對賬不匹配,同時賬戶和票券系統容易滋生腐爛代碼,這也對社會主義精神文明建設有直接傷害。
支付平臺、財務平臺和個人用戶管理平臺在個人多年開發經驗里算是投入較多鑽研較深入的系統了。我亦無他,唯手熟爾,除了公共組件,業務邏輯同樣非常考驗程式設計實現水平。
支付、財務和CRM設計實現的不好或者考慮不周到,很容易出錯,而根據墨菲定律,任何可能出錯的事情最終都會出錯。業務邏輯型公共服務的穩定性和可擴展性也非常重要,值得深度開發和積累。
PowerDotNet的設計和實現做了很多取捨,很多特定行業或定製化業務沒有吸收進去,畢竟PowerDotNet的重要目標是要實現一套能夠被絕大多數公司通用的公共服務。
環境準備
1、(必須).Net Framework4.5+
2、(必須)關係型資料庫MySQL或SqlServer或PostgreSQL或MariaDB四選一
3、(必須)PowerDotNet資料庫管理平臺,主要使用DBKey功能
4、(必須)PowerDotNet配置中心Power.ConfigCenter
5、(必須)PowerDotNet註冊中心Power.RegistryCenter
6、(必須)PowerDotNet緩存平臺Power.Cache
7、(必須)PowerDotNet消息平臺Power.Message
8、(必須)PowerDotNet文件平臺Power.File
9、(必須)PowerDotNet支付平臺Power.Payment
10、(必須)PowerDotNet財務平臺Power.Finance
11、(必須)PowerDotNet人員管理平臺Power.HCRM
12、(必須)PowerDotNet基礎數據平臺Power.BaseData
一、用戶管理
1、用戶基礎
對於用戶的基本管理,包括基礎資料、等級、狀態、黑名單等。
針對用戶的資料管理:
考慮到用戶基礎信息非常多,借鑒了經典的“分表”思想,將常用的不常更新的欄位(user_info)和不常用且會變更的欄位(user_extend)分兩張數據表管理起來,這應該是比較常見的拆分方法,我待過的公司至少有兩家是這樣實現的。
用戶黑名單的設計也是重要的功能:
下麵再介紹下個人用戶管理的一些常見功能。
修改密碼(支持簡訊和郵件提醒):
找回密碼:
用戶頭像:
其他如用戶變更記錄、登錄等等基礎功能,常見的人員關係場景管理後臺都要有完善支持。
PCRM經常會有各種各樣“我的XX”功能,“我的XX”功能和系統邊界及系統解耦有直接關係。通常我們會根據需要劃分出兩個主要模塊:用戶單據和用戶社交。
2、用戶單據
有些用戶業務數據,和用戶關係看上去也比較緊密,但界面展示通常直接調用各個平臺系統提供的介面就行,不需要直接在PCRM中進行數據維護。
典型的如我的訂單、我的購物車、我的錢包、我的卡包、我的票券、我的財務、我的配送地址、我的開票公司信息、我的常旅客管理等功能模塊可以直接劃歸到分解好的各個平臺系統中去。
當然,有些用戶電商購物單據劃歸到PCRM中也行得通,目前PowerDotNet實現的PCRM就將用戶配送地址和開票公司信息直接劃歸到PCRM中管理,當然把它們分別放到配送平臺和財務平臺才是最合理的。
根據個人開發經驗,我的XX數據經常因為劃分和歸屬而產生各種各樣的推諉扯皮,尤其是中大型的電商項目,互聯互通非常複雜,公共服務性質的共用系統就經常面臨模塊和數據劃分及歸屬管理的困境。
我的XX數據一個簡單的經驗之談,穩定的變動極少的我的XX數據可以優先劃歸到PCRM中進行管理,比如我的配送地址或我的開票公司,這些就是改動較少的靜態數據,放到PCRM中其實是很合適的。
當然穩定的變動極少的數據放到某系統中這個做法只是經驗之談,並不通用,具體劃歸到哪裡,還是需要按照實際業務需要和成本控制來確定,比如我的銀行卡號之類的靜態數據放在支付平臺管理更好。
3、用戶社交
用戶社交數據通常都和公司主業務強相關,我們可以將這些數據維護在PCRM中,也可以獨立開發出子系統,很顯然普通公司的用戶社交和PCRM聯繫更緊密。
比如我的評論、我的隨筆、我的投訴、我的相冊、我的消息、我的關註、我的粉絲、我的好友、我的收藏、我的點贊、我的標簽等可以直接劃歸到PCRM系統中。
用戶單據和社交的設計和實現非常考驗開發人員的水平,所謂夏蟲不可語冰,井蛙不可語海,凡夫不可語道,沒寫過這方面代碼的人很容易不知不覺中造成系統重大缺陷,這也是個人多年開發填坑經驗之談。
二、用戶賬戶管理
根據個人開發過的虛擬貨幣系統,這裡再提取抽象簡化為個人用戶賬戶系統,站在巨人的肩膀上,我也積累了一套完善的通用的用戶賬戶處理單據流程。
用戶賬戶和金錢有直接關係,這樣就會產生這樣那樣的流水,以及對賬紅沖等常見功能。
基本的對賬功能:
在產生流水的過程中,可以抽象出不同的流水單據進行處理。
用戶賬戶的主要單據如下:
1、充值訂單
充值訂單支持對支付成功的訂單記錄進行財務入賬和申請開票等操作。
2、充值流水
3、凍結流水
4、解凍流水
5、扣款(消費)流水
6、退款流水
每個處理單據,都有基本的對賬功能。這裡僅以退款示例下:
1、一次全額退款
2、多次部分退款,直至全額退款成功
3、部分退款
對於用戶賬戶系統的提現功能,需要抽象出提現單,不過這個支付平臺功能更緊密,這裡不列在主要單據里。
三、賬戶提現
如果支付平臺做得好,賬戶提現完全可以在支付平臺里搞定,不需要在業務系統里單獨開發功能。
PowerDotNet的支付平臺完全可以應付常規的賬戶提現需求,之所以在這裡還抽象出提現單進行介紹,主要就是:
在某廠我這麼實現過,而且用戶的反饋極其良好,不介紹有點可惜^_^
1、提現申請單
終端用戶通過PCRM介面創建好提現申請單,介面支持同步和非同步處理
上述兩個介面基本滿足了提現的常規需求。
2、提現流水
創建好的提現申請單,最終需要向支付平臺發起真實退款請求才能完成用戶的提現需求,提現成功後,提現申請單會對應產生相關提現流水。
3、提現補償工具
有時候退款會發生這樣或那樣的問題,比如自己開發的支付系統在發佈或維護,用戶賬戶異常風控不通過,銀行服務維護,支付寶、微信等賬戶註銷或鎖定,秘鑰過期等等原因,這樣用戶提現需求就要進行補償重試或者人工處理。
PowerDotNet結合個人開發和運營經驗,開發了幾個補償小工具,能幫助開發和支持人員快速定位並解決問題。
上述補償工具,嚴格來說是對支付平臺介面的再次封裝和調用。
再次說一遍,PowerDotNet的支付平臺完全可以應付常規的賬戶提現需求,真沒必要又包一層產生這麼多單據,就是這麼自信,咩哈哈。
四、財務記賬
對於用戶賬戶充值和提現,從財務角度看是一對典型的往來賬,PCRM也將用戶賬戶充值和提現設計成便於理解兼顧靈活性的入賬和出賬流程。
1、收款單
PCRM賬戶充值,用戶通過支付平臺完成賬戶充值後,支付平臺回調通知PCRM扣款結果,PCRM完成充值業務邏輯後調用財務平臺介面創建財務收款單,財務系統完成入賬邏輯。
2、應收退款單
PCRM賬戶提現,用戶通過支付平臺完成賬戶提現後,支付平臺回調通知PCRM退款結果,PCRM完成提現業務邏輯後調用財務平臺介面創建財務應收退款單,財務系統完成出賬邏輯。
註意,對於用戶下訂單使用賬戶金額消費的場景,雖然賬戶金額減少,但不用PCRM創建財務應收退款單,而應該由訂單系統(商戶)主動調用財務介面創建收款單。
3、申請開票
PCRM用戶提交開票申請,調用財務平臺介面創建應收發票,財務審核後完成開票。
在PCRM中進行開票申請:
在財務平臺審核用戶開票信息:
五、用戶積分
嚴格來說,用戶積分也是虛擬貨幣的一種,不過很多公司都會獨立出來,畢竟積分相對虛擬賬戶而言,有一些特殊處理方式,比如積分有效期、積分比率、特定活動動態支付參數,單獨抽象出來未嘗不可。
PowerDotNet抽象出來的積分處理方式,借鑒了前廠的部分邏輯,並且去掉了具體業務有關的特殊邏輯,和用戶虛擬賬戶已經有極大多數相同的處理單據。
在產生流水的過程中,可以抽象出不同的積分流水單據進行處理。
1、積分充值流水
2、積分凍結流水
3、積分解凍流水
4、積分扣款(消費)流水
5、積分退款流水
對賬功能也基本和用戶賬戶相同,不過因為絕大多數積分有效期的關係,有些功能,如退款和解凍的對賬功能需要調整。
根據我的個人開發經驗,積分有效期這個東西是個巨大的陷阱,因為很多業務邏輯和性能問題都由它引起,設計積分系統的時候,如果沒有有效期,那真可以減少百分之八十的工作量。
六、混合支付
混合支付功能其實我是非常拒絕再寫的,個人已對接過的混合支付特別費力不討好,還容易造成現有系統出錯,哪怕你給對方提需求並且定義了標準介面,奈何現狀所限,擋不住出問題,效果聊勝於無。
1、介面抽象
混合支付在支付平臺里已經簡單介紹過,本文就簡單梳理下混合支付常用的凍結、解凍、扣款、退款四個介面:
現實中,用戶賬戶和用戶積分是分別處理的,無法保證事務性。完善的後臺管理系統,是混合支付處理的關鍵保障。如果在特定定時任務都無法保證混合支付正常的情況下,通過後臺人工介入也是很輕鬆的事情。
2、流水日誌
對混合支付,需要有完善的日誌,便於排查問題。
交易的每一筆流水都要記的一清二楚,必要時可對日誌進行數據統計,本文省略數據統計。
3、支付風控
嚴格來說,任何支付功能,包括虛擬貨幣(如賬戶、積分等)的充值、消費和提現都應該有嚴格的風控,獨立的風控系統是必不可少的,但是鑒於某些弱雞公司孱弱的研發能力,風控聊勝於無。
小結:用戶賬戶管理、賬戶提現、財務記賬、用戶積分、混合支付等模塊設計實現很容易造成各種混亂,尤其是賬戶系統,我已經不止一次維護過這種容易造成用戶投訴的類虛擬貨幣系統。
沒有結果的愛情往往更加動人,缺少規範的系統常常麻煩纏身,個人經驗中,只要系統形象磕磣,往往公司的技術管理水平極其低下,流程規範嚴重缺失,哪怕是規模龐大的上市公司。
七、信息安全
PCRM中有大量的根據用戶Id或者用戶名查詢用戶信息、賬戶、用戶單據或用戶社交數據的介面,這些涉及到用戶數據的介面,一定要註意信息安全管理。
1、越權漏洞
越權訪問(Broken Access Control,簡稱BAC)是應用程式中一種常見的漏洞,越權,顧名思義,就是獲得了本不應該有的許可權。
越權漏洞是指應用在檢查授權時存在紕漏,使得攻擊者在獲得低許可權用戶賬戶後,利用一些方式繞過許可權檢查,訪問或者操作其他用戶或者更高許可權。
越權漏洞往往是基於業務邏輯的漏洞,常見的比如在PCRM系統中有大量的CRUD介面,由於過分相信介面消費者而遺漏了許可權的判定,這樣的漏洞很難被WAF保護。
在實際的代碼審計中,這種越權漏洞往往很難通過工具進行自動化監測,開發(甚至測試)理所應當的認為介面調用方便是第一位的,只要功能正常,授權管理是調用者的事情,這種想法危害極大。
根據越權漏洞經驗統計,目前存在著兩種顯而易見的越權操作類型,即橫向越權操作(水平越權)和縱向越權操作(垂直越權)。
(1)、水平越權
水平越權指相同許可權下不同的用戶可以互相訪問。典型示例:用戶A登錄後,用戶A操作卻影響到B用戶。
水平越權是相當常見的漏洞,多年前在某電商工作的時候,碰到過一個訂單頁面get方式傳入UserId查看用戶訂單的漏洞,正常情況是登錄用戶只能查看當前自己的訂單,但後端頁面業務邏輯缺少這個校驗。
(2)、垂直越權
垂直越權指使用許可權低的用戶可以訪問到許可權較高的用戶。典型示例:低許可權用戶使用高許可權用戶的功能,比如普通用戶可以使用管理員的功能。
2、越權防範
避免越權漏洞,尤其是水平越權,需要遵循一個簡單的原則:用戶只能看到屬於自己的內容。
個人總結的常見防範越權漏洞措施包括:
(1)、服務治理平臺做好介面認證、鑒權和授權,比如驗簽和(或)校驗token
(2)、嚴格的功能許可權控制,許可權到頁面或者按鈕,調用功能前驗證用戶是否有許可權
(3)、數據許可權控制,執行關鍵操作前必須驗證用戶身份,驗證用戶是否具備操作數據的許可權
(4)、雙重驗證,前後端同時對用戶輸入信息進行校驗,永遠不要相信來自用戶的輸入,對於可控參數進行嚴格的檢查與過濾
(5)、對於系統中各種有規律的流水號,比如自增,設計時取捨務必小心,防止窮舉漏洞攻擊,對流水型資源ID加密,防止攻擊者窮舉ID
(6)、敏感信息脫敏,在配置中心做好敏感信息脫敏開關,防止意外發生時可及時兜底處理
八、水平分表
個人碰到過的PCRM的最大挑戰在於用戶數據量過大,一些數據量巨大的表需要分庫分表處理。
常見的分庫分表類型包括垂直分庫、垂直分表、水平分庫、水平分表。具體到PCRM分庫分表,通常可使用水平分表策略。
水平分表是指將單張表的數據分片拆分到多台伺服器節點上,每個節點具有相同的庫與表,表結構都一樣,每個表的數據不一樣,沒有交集,所有表的並集是全量數據。
比如用戶信息user_info和用戶擴展user_extend兩張表,單庫不足以支撐的時候必須要水平分表(常見演算法如用戶id取模、用戶id範圍分區等),幾乎均勻的分片保存到不同伺服器PCRM資料庫中。
水平分表能夠有效的緩解單機和單庫的性能瓶頸和壓力,突破IO、CPU、記憶體、連接數、其他硬體資源等的限制,但是也帶來了業務邏輯上的很多挑戰,比如:連接查詢、事務、跨節點分頁、排序等。
舉例來說,在PCRM中,水平分表後,以前單庫單表相對簡單的登錄功能就會變得很複雜。
常見的PCRM支持用戶名、手機號、郵箱、證件號碼、OpenId等進行登錄,沒有水平分表前,很可能一個SQL連接查詢就能搞定,分庫分表後就需要考慮分區、跨庫連接甚至引入其他中間件的查詢問題。
正如我們所知道那樣,軟體的很多問題都可以通過加一層來解決,如果加一層還不能解決,那就再加一層^_^。
解決水平分表造成的查詢問題的常見方法是添加冗餘關係索引表,通常分為兩種:
1、冗餘全量數據
PCRM支持手機號登錄,那麼我們就把用戶信息user_info和用戶擴展user_extend兩張表根據手機號分庫分表。
優點:只需要查詢一次就能定位數據。
缺點:占用空間大,每增加一個維度查詢就得增加一倍的空間, 數據存儲與變更時需要維護多張表。
2、冗餘關係索引
PCRM支持手機號登錄,那麼我們就維護一個手機號和用戶id的關係映射表。
優點:占用空間比冗餘全量數據小很多。
缺點:必須查詢兩次, 先從映射表中查到用戶id, 再根據用戶id定位數據,性能略有下降。
冗餘關係索引可以存儲在RDBMS中,也可以存儲在Redis、MongoDB、Cassandra、ElasticSearch等NewSQL中,得益於NewSQL集群海量數據存儲和快速查詢能力,功能性能都能滿足業務需要。
3、PowerDotNet分表
PowerDotNet自研的ORM工具集成了常用的分庫分表功能,配合代碼生成器和DBKey服務,很容易實現分表。我們以PCRM資料庫為例,ORM實現的分庫分表常見三種情形:
同庫不同表:PCrmDB下UserInfo0、UserInfo1...UserInfoN表
不同庫相同表:PCrmDB1下UserInfo0、UserInfo1...表, PCrmDB2下UserInfo0、UserInfo1...表
不同庫不同表:PCrmDB1下UserInfo0、UserInfo1...表, PCrmDB2下UserInfo2、UserInfo3...表
目前PCRM的用戶和用戶擴展信息就是根據用戶Id進行哈希取模實現不同庫相同表的分表形式,對分片的用戶和用戶擴展等表,不建議複雜連接查詢,單表查詢也能滿足業務同時也保證線上運行穩定可靠。
PowerDotNet自研的ORM雖然看上去遠沒有TDDL、ShardingSphere、MyCat等強大,卻也基本滿足中小公司的分庫分表需求,配合DBKey服務和代碼生成器走出了自己的特色,等我有空再詳細介紹。
九、系統交互
前面在用戶管理一節已經提過各種“我的XX”管理,PCRM也和很多內部業務系統保持互通關係,尤其是ToC業務離開PCRM基本活不下去,整理下個人開發和對接過的幾種常見互聯繫統。
1、訂單系統
經典的”我的訂單“模塊。直接需求就是在界面查詢我的訂單而不是別人的訂單,記得業務邏輯不嚴謹導致不同用戶查詢串號這種經典漏洞也不是沒有發生過,咩哈哈。
2、購物車系統
”我的購物車”和”我的訂單”類似,查詢不能串號,同時還需要維護一個不同終端如PC、APP等未登錄用戶購物車和實際登錄用戶購物車的關係,這種也非常考驗開發者的設計和實現水平。
3、支付系統
支付調用用戶的地方還是比較頻繁的,如果用戶賬戶和積分也設計集成在PCRM中,那麼混合支付的地方就必然和PCRM交互,還有比如查詢用戶基本信息如手機號、郵件等發送消息,這種也很常見。
4、風控系統
風控到人,懂的都懂。
5、財務系統
”我的財務”,很常見,基本需求比如查詢統計下個人某個時間段內收入支出消費趨勢曲線啥的。
6、票券系統
”我的票券”,各種電商活動送出的優惠券、購物券等等,設計實現不同,和個人用戶的關係也不同,有些票券系統設計基本很少直接調用PCRM介面,因為可能不需要直接綁定到個人用戶。
7、消息系統
發送驗證碼,這種需求隨處可見,比如改密、找回密碼等。
8、其他系統
其他系統,諸如開放平臺、活動等,和PCRM沒有那麼緊密,但是又需要直接或間接調用PCRM介面。
十、其他
寫到這裡才發現前面畫風有點偏賬戶、積分和混合支付了,PCRM真正的核心功能還沒有介紹完全,限於篇幅,本文截圖沒有介紹的PowerDotNet的PCRM已經實現的公共服務基本功能,包括:
1、驗證碼管理
包括常見的字元驗證碼、運算驗證碼和滑動驗證碼等。
2、登錄註冊管理
支持傳統的用戶名(手機、郵箱)密碼登錄,掃碼登錄,以及OAuth登錄,還包括單點登錄(Single Sign On,簡稱SSO)功能,LDAP(輕量級目錄訪問協議)也有簡易實現。
登錄註冊在CRM中算是非常普通常見的功能,但是千萬別小瞧登錄註冊,至少個人接觸過的PCRM系統,絕大多數登錄註冊都只是充斥HelloWorld級代碼的CRUD玩具型Demo而已。
雖然有很多可以借鑒的經驗和實踐,但架不住亂來的需求和變更,看上去簡單的登錄註冊,改來改去也容易事件多發。正如黑格爾所說,人類從歷史中得到的唯一教訓就是人類無法從歷史中學到任何教訓。
3、基於token+redis的token管理
萬能token管理,主要實現基於加密token+redis,早期實現的jwt token可按照配置啟用,絕大多數場景下不需要再啟用jwt。
4、第三方關聯用戶管理
第三方關聯用戶業務場景也比較常見,通常涉及到不同公司的用戶或會員的數據打通,設計實現考慮不周到,容易有安全風險。
==========分隔線==========
驗證碼管理、登錄註冊管理、基於token+redis的token管理在HCRM中已經被廣泛使用,可復用的公共模塊沒有再次說明的必要,沒錯,我就是覺得簡單懶得介紹了^_^。
個人用戶管理是和具體公司具體業務緊密關聯的公共服務業務系統,比如綜合性電商、垂直類電商、酒店、機票、銀行、保險、社交網站等等,它們對應的個人用戶管理側重點就不一樣,所以有很多相關業務功能都需要按照業務要求去開發。
根據我的個人開發經驗,個人用戶或會員管理在業務領域內能衍生出很多業務功能子系統,比如賬戶、積分、票券、活動、投訴、論壇、廣告營銷、信息安全、開放平臺、推薦演算法等都和PCRM有千世萬縷的聯繫,想寫好也要面對很多的挑戰,對於一線開發人員,學業務做設計堆代碼,一個都不能少,是非常需要花時間積累和投入的公共服務。
基礎設施和公共服務建設對系統穩定至關重要,我們欣賞並尊重少說多做嚴謹務實工作和實踐的人,最討厭不切實際誇誇其談自以為比別人高明同時還不虛心學習也沒什麼邏輯和執行能力的人。
有了支付、財務和CRM的系統開發經驗,在各種形態的公司或組織里開發管錢和管人的系統基本上都不會有太大問題,這就是PowerDotNet和PowerDotNetCore專註開發通用型公共服務的價值所在。
作者:Jeff Wong
出處:http://jeffwongishandsome.cnblogs.com/
本文版權歸作者和博客園共有,歡迎圍觀轉載。轉載時請您務必在文章明顯位置給出原文鏈接,謝謝您的合作。