宮格用來平鋪展示很多頻道或者欄目。那么如何用Axure畫出該功能呢?
常用場景有“微信APP-我-錢包”、“淘寶APP-首頁-頻道”、“支付寶APP-首頁”。
九宮格通常包含N個格子,每行3個,每個格子由按鈕和圖標組成。
1. 從默認元件庫拖動“中繼器”到工作區合適位置。
2. 雙擊該中繼器進入內部,即可編輯每一個格子。
3. 先畫按鈕。選擇矩形并修改尺寸為125*125px(每行3個,則單個寬度=375/3)。雙擊輸入文字“標題”,然后對齊方式為底部對齊。
4. 再畫圖標。從默認元件庫拖動“圖片”到合適位置,修改尺寸為75*75px。
5. 點擊空白區域即可設置中繼器屬性,添加更多的行,直到9個。這里輸入的內容直接代表原型中的每個格子名稱。
6. 設置中繼器的樣式,布局改成水平,勾選網格分布,每行項目數為3。
7. 回到工作區,就可以看到九宮格效果。
8. 生成原型HTML并在瀏覽器中查看效果。
九宮格的常見交互效果:點擊每個格子,跳轉到新頁面。
接下來以“微信APP-我-錢包”來作為案例詳細講解。
9. 修改每個格子的標題,雙擊Column0的每一行,依次輸入對應的標題。
10. 然后畫出每個宮格對應的頁面,總共9個,依次創建。
11. 雙擊該中繼器進入內部,選中按鈕和圖標,進行組合。
12. 選擇該組合,設置它的交互事件“鼠標單擊時”,但是很容易發現無法設置它跳轉到哪個頁面。因為這個組合代表九個格子,所以理論上應該設置跳轉到9個子頁面。然后需要利用中繼器的屬性進行設置。點擊column0,然后點擊“左側添加列”,生成Column1這一列。
13. 選中Column1的第一行,然后右鍵點擊“引用頁面”,然后選擇第一個格子對應的子頁面。同理設置下面8個行。
14. 設置該組合的交互事件“鼠標單擊時”,添加動作“鏈接-打開鏈接-當前窗口”,組織動作“選中鏈接到url或文件”,點擊“fx”打開“編輯值”彈窗,然后點擊“插入變量或函數”,然后選中“中繼器/數據集-Column1”,最后確定。
15. 生成原型HTML并在瀏覽器中查看效果。
不同場景下的九宮格功能,標題不一樣,樣式相對固定。
根據多年PM經驗,總結出2種常用的“九宮格”,添加到APP元件庫,供后續調用。
本文章中所有內容僅供學習交流使用,不用于其他任何目的,不提供完整代碼,抓包內容、敏感網址、數據接口等均已做脫敏處理,嚴禁用于商業用途和非法用途,否則由此產生的一切后果均與作者無關!
本文章未經許可禁止轉載,禁止任何修改后二次傳播,擅自使用本文講解的技術而導致的任何意外,作者均不負責,若有侵權,請在公眾號【K哥爬蟲】聯系作者立即刪除!
目標:百 X 網數字九宮格驗證碼逆向分析
網址:aHR0cHM6Ly9iZWlqaW5nLmJhaXhpbmcuY29tL296L3M5dmVyaWZ5X2h0bWw=
本例中的驗證碼不是很難,但網站埋了點兒坑,容易出現識別正確、參數也正確,但仍然請求不成功的情況。訪問主頁響應碼為 307,接著請求了一個 bf.js 和兩個 s.webp 的圖片,然后又跳轉到首頁出現驗證碼。如果你沒有以上步驟,請求主頁直接就是 200 出現驗證碼,則需要清除 cookie 后再訪問,因為第一次 307 到請求 bf.js 再到兩次 s.webp 都是在設置 cookie。
第一次請求主頁,response headers 會設置一個名為 _trackId 和 __city 的 cookie,如下圖所示:
然后帶著這兩個 cookie 請求了一個 bf.js,這個 js 用于后續兩個 s.webp 請求參數的加密,這里注意,第一個坑,雖然可以直接調試 js 扣算法下來后面直接用就行了,但是這個 js 必須得請求一遍,不然后面請求主頁的時候一直是 307。
然后請求第一個 s.webp,get 請求,有三個參數:cf、s 和 f,明顯是加密得來的,同時請求的 cookie 也多了三個值:c0fc276cce08ba22dc、c1fc276cce08ba22dc 和 bxf,如下圖所示:
然后請求第二個 s.webp,和第一個類似,get 請求也有三個參數:cf、s 和 f,cookie 和第一個一樣,但第二次請求返回了一個名為 sbxf 的新 cookie,其值和 bxf、c1fc276cce08ba22dc 其實是一樣的,如下圖所示:
然后帶上 __trackId、__city、c0fc276cce08ba22dc、c1fc276cce08ba22dc、bxf 和 sbxf 這六個 cookie 再次訪問主頁,就是驗證碼頁面了,返回的 html 里有個新的 js,很長一串,如下圖所示:
然后觀察這個 js,里面包含了驗證碼圖片的 URL,以及需要點擊的數字,如下圖所示:
點擊驗證后,會給 verify_url 發一個 get 請求,請求參數主要有一個 data,即點擊坐標(這個坐標也有講究,有可能你的值是對的,但有時候也不成功,這個后文再細說),cookie 和前面的請求一樣,如果驗證成功,會返回 ret 為 0,且有一個 code 供后續請求使用,如下圖所示:
這里再注意一點,所有的請求,header 只需要 Referer 和 User-Agent 就行了,不要亂加,比如多了個 Host 也有可能導致后續請求不成功。
想要拿驗證碼,得先搞定 cookie,總體流程如下:
前兩步倒沒有啥,第 3、4 步都有加密參數 s 和 f,觀察這兩個 s.webp 都是 fetch 請求,所以我們直接一個 fetch 斷點,斷下后可以看到 cb 就是我們需要的兩個參數:
觀察 bf.js 是一個小小的類似 OB 混淆,可以 AST 解一下混淆,但這個邏輯不是很復雜,所以直接硬看也行,關鍵語句 cb=c3['s'](c7, c8),c7 是定值一個字符串 fc276cce08ba22dc,c8 也是定值表示顏色的字符串 rgba(255, 0, 0, 255):
主要是 c3['s']() 這個方法,跟進去,首先會取一下 c0fc276cce08ba22dc、c1fc276cce08ba22dc 和 bxf 三個值,如果有的話,直接返回,如果沒有的話,會生成新的,生成方法主要是 c6 這個函數,如下圖所示:
繼續跟到 c6 方法里,首先對字符串 rgba(255, 0, 0, 255) 做了一個操作,生成了一張圖片的 base64 字符串:
這里其實很明顯是 canvas 繪圖的一些操作,跟到 c7 看看確實是這樣的:
這里對于我們扣算法來說,其實就不需要管了,因為同一臺設備的同一個瀏覽器,按照相同的規則繪制的圖片,base64 值是一樣的,所以我們直接忽略 c7 這個方法,直接把生成的 base64 值拿來用就行了。
然后又將 base64 值進行了一個 c3["hash"]() 的操作,根據最終的值,或者跟到方法里去看,很容易發現這個其實就是個 MD5 的操作:
接著往下看,八個字符串為一組,將 md5 值分為四組,然后四組之間用 0 或者 1 連接,拼接成新的 35 位字符串,拼接的是 0 還是 1,取決于中間的三目語句,判斷是否為 true,支持情況下都是 true,所以扣算法的話根本就沒必要再跟進去看是怎么判斷的,直接用 1 拼接就完事兒了。然后將固定的字符串 fc276cce08ba22dc 和這 35 位字符串拼接起來再一次 MD5,就得到了參數 s 的值,而參數 f 的值則是這個 35 位字符串。
第一個 s.webp fetch 操作就完成了,接著是第二個 s.webp,就在第一個 fetch 附近,如下圖的 ce 就是第二次的 s、f 參數的值:
這里生成的方法大致是一樣的,首先 cd 是一個新的圖片的 base64 值,這個值是第一次 s.webp 請求成功返回的,先把這個新的 base64 MD5 加密一下,生成一個新的字符串,相當于替換了第一次請求固定的字符串 fc276cce08ba22dc,后續的流程和第一次都一樣了:
這兩次生成 s 和 f 的值的流程可以精簡成以下 js 實現:
MD5=require("md5")
var baseImg="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAANwAAADcCAYAAAAbWs+BAAAAAXNSR0IArs4c6QAAIABJREFUeF7tnQm..."
function getParams(c8) {
var cb=MD5(baseImg)
, cc=cb.substring(0, 8)
, cd=cb.substring(8, 16)
, ce=cb.substring(16, 24)
, cf=cb.substring(24, 32)
, cg=cc + 1 + cd + 1 + ce + 1 + cf;
return {
"s": MD5(c8 + cg),
"f": cg
};
}
function getFirstParams() {
return getParams("fc276cce08ba22dc")
}
function getSecondParams(img) {
return getParams(MD5(img))
}
console.log(getFirstParams())
console.log(getSecondParams("data:image/png;base64,dqyixSOIJuJN0IRG288itylhqNFFXVqL"))
然后這個 cookie 值,你可以去 Hook 一下看看,但實際上觀察一下就可以發現 c0fc276cce08ba22dc 就是第一次 s.webp 請求的 s 參數,c1fc276cce08ba22dc 和 bxf 就是第一次 s.webp 請求的 f 參數,所以直接拿來用就行了。
帶上前面生成的正確的 cookie,再次請求主頁,響應碼為 200,然后在返回的 html 里可以看到有個超長的 js 地址,這個 js 直接把 .js 替換成 .jpg 就是驗證碼地址,替換成 .valid 就是驗證結果的地址,這個 js 返回的內容里面就包含了要點擊的數字。
最終提交的坐標是長這樣的:
由于這個圖片是九宮格的樣式,一般的識別都是一排,所以這里可以將九宮格裁剪后重新排列一下(當然自己會搞深度學習的話可以單獨給這種九宮格訓練一下,就不用重新裁剪排列了),重新排列前后對比如下:
這一步的利用 Python 的 PIL 庫很容易實現:
from PIL import Image
# 打開九宮格驗證碼
captcha=Image.open("captcha.jpg")
# 將圖片等分成三份,每份長寬為 150px 和 50px
part1=captcha.crop((0, 0, 150, 50))
part2=captcha.crop((0, 50, 150, 100))
part3=captcha.crop((0, 100, 150, 150))
part1.save("part1.jpg")
part2.save("part2.jpg")
part3.save("part3.jpg")
# 創建新的圖片,長寬為 450px 和 50px
new_captcha=Image.new("RGB", (450, 50))
# 將三份圖片按順序拼接到新的圖片上
new_captcha.paste(part1, (0, 0))
new_captcha.paste(part2, (150, 0))
new_captcha.paste(part3, (300, 0))
# 保存新的圖片
new_captcha.save("captcha_new.jpg")
這樣處理后,怎樣得到對應的坐標呢?以上圖為例,假設我們需要點擊 question=[1, 8, 3, 6],我們識別 captcha_new.jpg 結果為 recognition_result="172958643",生成最后的坐標流程如下:
import random
question=[1, 8, 3, 6] # 要點擊的數字
recognition_result="172958643" # captcha_new.jpg 識別的結果
mapping_table={
"0": f"{str(random.randint(15, 35))},{str(random.randint(15, 35))}|",
"1": f"{str(random.randint(65, 85))},{str(random.randint(15, 35))}|",
"2": f"{str(random.randint(115, 135))},{str(random.randint(15, 35))}|",
"3": f"{str(random.randint(15, 35))},{str(random.randint(65, 85))}|",
"4": f"{str(random.randint(65, 85))},{str(random.randint(65, 85))}|",
"5": f"{str(random.randint(115, 135))},{str(random.randint(65, 85))}|",
"6": f"{str(random.randint(15, 35))},{str(random.randint(115, 135))}|",
"7": f"{str(random.randint(65, 85))},{str(random.randint(115, 135))}|",
"8": f"{str(random.randint(115, 135))},{str(random.randint(115, 135))}|",
}
answer=""
for q in question:
for r in recognition_result:
if q==int(r):
answer +=mapping_table[str(recognition_result.index(r))]
print(answer)
每一個數字的圖片大小是 50x50,如果我要點擊上圖中的數字 1,那么我的 x、y 坐標范圍就應該為 [0~50, 0~50],如果我要點擊上圖中的數字 8,那么我的 x、y 坐標范圍就應該為 [100~150, 50~100]。
但是進過多次測試,點擊區域要靠正中心一點,成功率才高,所以坐標范圍前后各增加、減少了 15。對應數字 1 的坐標范圍就應該是 [15~35, 15~35],數字 8 的坐標范圍就應該是 [115~135, 65~85]。
這里為了簡便,直接定義了一個映射表 mapping_table,如果我點擊數字 8,那么 captcha_new.jpg 識別結果 172958643 中,8 的位置是 5,對應 mapping_table["5"],也就是 random.randint(115, 135), str(random.randint(65, 85)。
常在某些 app 中看到這樣的九宮格設計。當縮略圖不足 9 張時,正常排列,當超過 9 張時,會提示還剩多少張,如下:
如何使用純 CSS 實現這一效果呢?一起來看看吧
布局就很簡單了,一個很普通的九宮格布局,這里使用 grid
<ul class="list">
<li class="item"></li>
<li class="item"></li>
<li class="item"></li>
...
</ul>
這里正方形可以用aspect-ratio[1]簡易實現,對應的 CSS 如下
.list{
position: relative;
display: grid;
width: 300px;
margin: auto;
grid-template-columns: repeat(3,1fr);
list-style: none;
padding: 0;
gap: 2px;
}
.item{
aspect-ratio: 1;/*寬高比1:1*/
}
效果如下
那么,如何實現在超過9張時自動提示剩余張數呢?接著往下看
提到序列,自然會想到 CSS 計數器[2],現在我們加上計數器
.list{
/*...*/
counter-reset: count; /*初始化*/
}
然后在每一個 .item顯示數字,可以用到偽元素::after
.item{
counter-increment: count;
}
.item::after{
content: counter(count);
/*其他樣式*/
display: grid;
height: 100%;
place-content: center;
font-size: 30px;
color: #fff;
}
這樣可以得到如下效果
數字是顯示出來了,不過現在還有兩個問題:
1.數量超過9個時,不會隱藏超過的圖片2.這個數字不是超出圖片的數量,而是總數
這個其實非常容易,由于數量是固定的,只需要利用選擇器nth-child配合~就能實現
.item:nth-child(9)~.item{
/*選擇第9個以后的元素*/
visibility: hidden;
}
這個地方是通過 visibility: hidden隱藏超過的圖片,原因是該屬性不會影響計數器的計算,如果使用display:none則會跳過計數
目前由于是從第1個開始計數,所以最后統計的是整個列表的數量,但是我們可以指定從第10個才開始計數,會得到什么效果呢?為了方便演示,暫時把隱藏打開
.item{
/*counter-increment: count;*/
}
.item:nth-child(9)~.item{
/*從第10個開始計數*/
counter-increment: count;
}
.item:nth-child(9)~.item::after{
content: counter(count);
}
可以看到,從第10個開始計數后,最后一個數字就表示還剩余多少張
現在把最后一張放在右下角就行了(絕對定位),最后一張可以用.item:nth-child(9)~.item:last-child來選擇,表示第9張后面的最后一張圖片,實現如下
.item:nth-child(9)~.item{
position: absolute;
width: calc(100% / 3 - 1px);
counter-increment: count;
visibility: hidden;
right: 0;
bottom: 0;
}
.item:nth-child(9)~.item:last-child::after{
visibility: visible;
background-color: rgba(0,0,0,.2);
}
這樣就實現了純 CSS 自動提示剩余圖片的效果,演示如下
這里的 add 和 remove 是演示動態修改節點數量,與交互邏輯無關
完整代碼可訪問 list-counter (codepen.io)[3]
在上一種實現方式中,我們是手動指定從第 10 個元素開始計數的
.item:nth-child(9)~.item{
/*從第10個開始計數*/
counter-increment: count;
}
其實,還有一種方式也值得一試,那就是直接指定計數器的初始值,默認為0,現在改為 -9 就可以了,實現如下
.list{
/*...*/
counter-reset: count -9; /*初始化為-9*/
}
不一樣的初始化思路,剩下的就和之前一樣的邏輯了,完整代碼可訪問 list-counter-reset (codepen.io)[4]
這個案例到這里就結束了,一個低成本的 CSS 小技巧,雖然不多,但是非常實用,尤其是選擇器的運用,說不定將來哪次就會用上了。CSS 計數器可以說是非常靈活和強大了,仔細挖掘應該還能實現更多實用的效果,這里總結一下:
如果覺得還不錯,對你有幫助的話,歡迎點贊、收藏、轉發???
[1] aspect-ratio: https://www.zhangxinxu.com/wordpress/2021/02/css-aspect-ratio/
[2] CSS 計數器: https://developer.mozilla.org/zh-CN/docs/Web/CSS/CSS_Lists_and_Counters/Using_CSS_counters
[3] list-counter (codepen.io): https://codepen.io/xboxyan/pen/NWjroNz
[4] list-counter-reset (codepen.io): https://codepen.io/xboxyan/pen/rNmWxyp
*請認真填寫需求信息,我們會在24小時內與您取得聯系。