摘要:多返回值開(kāi)始變得越來(lái)越與眾不同了允許函數(shù)返回多個(gè)結(jié)果。這種情況函數(shù)沒(méi)有足夠的返回值時(shí)也會(huì)用來(lái)補(bǔ)充。中的索引習(xí)慣以開(kāi)始。
為什么值得入手?
Nginx作為現(xiàn)在使用最廣泛的高性能后端服務(wù)器,Openresty為之提供了動(dòng)態(tài)預(yù)言的靈活,當(dāng)性能與靈活走在了一起,無(wú)疑對(duì)于被之前陷于臃腫架構(gòu),苦于提升性能的工程師來(lái)說(shuō)是重大的利好消息,本文就是在這種背景下,將初入這一未知的領(lǐng)域之后的一些經(jīng)驗(yàn)與大家分享一下,若有失言之處,歡迎指教。
安裝現(xiàn)在除了能在 Download里面下載源碼來(lái)自己編譯安裝,現(xiàn)在連預(yù)編譯好的包都有了, 安裝也就分分鐘的事了。
hello world/path/to/nginx.conf, conftent_by_lua_file里面的路徑請(qǐng)根據(jù)lua_package_path調(diào)整一下。
location / { content_by_lua_file ../luablib/hello_world.lua; }
/path/to/openresty/lualib/hello_world.lua
ngx.say("Hello World")
訪問(wèn)一下, Hello World~.
HTTP/1.1 200 OK Connection: keep-alive Content-Type: application/octet-stream Date: Wed, 11 Jan 2017 07:52:15 GMT Server: openresty/1.11.2.2 Transfer-Encoding: chunked Hello World
基本上早期的Openresty相關(guān)的開(kāi)發(fā)的路數(shù)也就大抵如此了, 將lua庫(kù)發(fā)布到lualib之下,將對(duì)應(yīng)的nginx的配置文件發(fā)布到nginx/conf底下,然后reload已有的Openresty進(jìn)程(少數(shù)需要清空Openresty shared_dict數(shù)據(jù)的情況需要重啟), 如果是測(cè)試環(huán)境的話,那更是簡(jiǎn)單了,在http段將lua_code_cache設(shè)為off, Openresty不會(huì)緩存lua腳本,每次執(zhí)行都會(huì)去磁盤(pán)上讀取lua腳本文件的內(nèi)容,發(fā)布之后就可以直接看效果了(當(dāng)然如果配置文件修改了,reload是免不了了),是不是找到一點(diǎn)當(dāng)初apache寫(xiě)php的感覺(jué)呢:)
開(kāi)發(fā)語(yǔ)言Lua的大致介紹環(huán)境搭建完畢之后,接下來(lái)就是各種試錯(cuò)了,關(guān)于Lua的介紹,網(wǎng)上的資料比如:Openresty最佳實(shí)踐(版本比較多,這里就不放了)。 寫(xiě)的都會(huì)比較詳細(xì),本文就不在這里過(guò)多解釋了,只展示部分基礎(chǔ)的Lua的模樣。下面對(duì)lua一些個(gè)性有趣的地方做一下分享,可能不會(huì)涉及到lua語(yǔ)言比較全面或者細(xì)節(jié)的一些部分,作為補(bǔ)充,讀者可以翻閱官方的<
-- 單行注釋以兩個(gè)連字符開(kāi)頭 --[[ 多行注釋 --]] -- 變量賦值 num = 13 -- 所有的數(shù)字都是雙精度浮點(diǎn)型。 s = "單引號(hào)字符串" t = "也可以用雙引號(hào)" u = [[ 多行的字符串 ]] -- 控制流程,和python最明顯的差別可能就是冒號(hào)變成了do, 最后還得數(shù)end的對(duì)應(yīng) -- while while n < 10 do n = n + 1 -- 不支持 ++ 或 += 運(yùn)算符。 end -- for for i = 0, 9 do print(i) end -- if語(yǔ)句: f n == 0 then print("no hits") elseif n == 1 then print("one hit") else print(n .. " hits") end --只有nil和false為假; 0和 ""均為真! if not aBoolValue then print("false") end -- 循環(huán)的另一種結(jié)構(gòu): repeat print("the way of the future") num = num - 1 until num == 0 -- 函數(shù)定義: function add(x, y) return x + y end -- table 用作鍵值對(duì) t = {key1 = "value1", key2 = false} print(t.key1) -- 打印 "value1". -- 使用任何非nil的值作為key: u = {["@!#"] = "qbert", [{}] = 1729, [6.28] = "tau"} print(u[6.28]) -- 打印 "tau" -- table用作列表、數(shù)組 v = {"value1", "value2", 1.21, "gigawatts"} for i = 1, #v do -- #v 是列表的大小 print(v[i]) end -- 元表 f1 = {a = 1, b = 2} -- 表示一個(gè)分?jǐn)?shù) a/b. f2 = {a = 2, b = 3} -- 這會(huì)失?。?-- s = f1 + f2 metafraction = {} function metafraction.__add(f1, f2) local sum = {} sum.b = f1.b * f2.b sum.a = f1.a * f2.b + f2.a * f1.b return sum end setmetatable(f1, metafraction) setmetatable(f2, metafraction) s = f1 + f2 -- 調(diào)用在f1的元表上的__add(f1, f2) 方法 -- __index、__add等的值,被稱為元方法。 -- 這里是一個(gè)table元方法的清單: -- __add(a, b) for a + b -- __sub(a, b) for a - b -- __mul(a, b) for a * b -- __div(a, b) for a / b -- __mod(a, b) for a % b -- __pow(a, b) for a ^ b -- __unm(a) for -a -- __concat(a, b) for a .. b -- __len(a) for #a -- __eq(a, b) for a == b -- __lt(a, b) for a < b -- __le(a, b) for a <= b -- __index(a, b)for a.b -- __newindex(a, b, c) for a.b = c -- __call(a, ...) for a(...)
以上參考了
learn lua in y minute ,做了適當(dāng)?shù)牟眉魜?lái)做說(shuō)明。
作為lua里面唯一標(biāo)準(zhǔn)的數(shù)據(jù)結(jié)構(gòu), 直接打印居然只有一個(gè)id狀的東西,這里說(shuō)這一點(diǎn)沒(méi)有抱怨的意思,只是讓讀者做好倒騰的心理準(zhǔn)備,畢竟倒騰一個(gè)簡(jiǎn)潔語(yǔ)言終歸是有代價(jià)的,了解決定背后的原因,有時(shí)候比現(xiàn)成的一步到位的現(xiàn)成方案這也是倒騰的另一面好處吧,這里給出社區(qū)里面的討論
舉個(gè)例子: lua里面一般使用#table來(lái)獲取table的長(zhǎng)度,究其原因,lua對(duì)于未定義的變量、table的鍵,總是返回nil,而不像python里面肯定是拋出異常, 所以#來(lái)計(jì)算table長(zhǎng)度的時(shí)候只會(huì)遍歷到第一個(gè)值為nil的地方,畢竟他不能一直嘗試下去,這時(shí)候就需要使用table.maxn的方式來(lái)獲取了。
Good or Bad? 自動(dòng)類型轉(zhuǎn)換如果你在python里面去把一個(gè)字符串和數(shù)字相加,python必定以異?;貞?yīng)。
>>> "a" + 1 Traceback (most recent call last): File "", line 1, in TypeError: cannot concatenate "str" and "int" objects
但是Lua覺(jué)得他能搞定。
> = "20" + 10 30
如果你覺(jué)得Lua選擇轉(zhuǎn)換加號(hào)操作符的操作數(shù)成數(shù)字類型去進(jìn)行求值顯得不可思議的,下面這種情況下,這種轉(zhuǎn)換又貌似是可以有點(diǎn)用的了,print("hello" .. 123),這時(shí)你不用手動(dòng)去將所有參數(shù)手工轉(zhuǎn)換成字符串類型。尚沒(méi)有定論說(shuō)這項(xiàng)特性就是一無(wú)是處,但是這種依賴語(yǔ)言本身不明顯的特性的代碼筆者是不希望在項(xiàng)目里面去踩雷的。
多返回值Lua開(kāi)始變得越來(lái)越與眾不同了:允許函數(shù)返回多個(gè)結(jié)果。
function foo0() end --無(wú)返回值 function foo1() return "a" end -- 返回一個(gè)結(jié)果 function foo2() return "a","b" end -- 返回兩個(gè)結(jié)果 -- 多重賦值時(shí), 函數(shù)調(diào)用是最后一個(gè)表達(dá)式時(shí) -- 保留盡可能多的返回值 x, y = foo2() -- x="a", y="b" x = foo2() -- x="a", "b"被丟棄 x,y,z = 10,foo2() -- x=10, y="a", z="b" -- 如果多重賦值時(shí),函數(shù)調(diào)用不是最后一個(gè)表達(dá)式時(shí) -- 只產(chǎn)生一個(gè)值 x, y = foo2(),20 -- x="a", y=20 x,y = foo0(), 20, 30 -- x=nil, y= 20,30被丟棄,這種情況當(dāng)函數(shù)沒(méi)有返回值時(shí),會(huì)用nil來(lái)補(bǔ)充。 x,y,z = foo2() -- x="a", y="b", z=nil, 這種情況函數(shù)沒(méi)有足夠的返回值時(shí)也會(huì)用nil來(lái)補(bǔ)充。 -- 同樣在函數(shù)調(diào)用、table聲明中 函數(shù)調(diào)用作為最后的表達(dá)式,都會(huì)竟可能多的填充返回值,如果不是最后,則只返回一個(gè) print(foo2(), 1) --> a 1 print(1, foo2()) --> 1 a b t = {foo2(), 1} --> {"a", 1} t = {1, foo2()} --> {1, "a", "b"} -- 阻止這種參數(shù)順序搞事: print(1, (foo2())) -- 1 a 加一層括號(hào),強(qiáng)制只返回一個(gè)值真?zhèn)€性: 模式匹配
簡(jiǎn)潔的Lua容不下行數(shù)比自己實(shí)現(xiàn)語(yǔ)言行數(shù)還多的正則表達(dá)式實(shí)現(xiàn)(無(wú)論是POSIX, 還是Perl正則表達(dá)式),于是乎有了獨(dú)樹(shù)一幟的模式與匹配,下面只用模式匹配來(lái)做URL解碼、編碼功能實(shí)現(xiàn)的演示。
-- 解碼 function unescape(s) s = string.gsub(s, "+", " ") s = string.gsub(s, "%%(%x%x)", function (h) return string.char(tonumber(h, 16)) end) return s end print(unescape("a%2Bb+%3D+c")) ---> a+b =c cgi = {} function decode(s) for name,value in string.gmatch(s, "([^&=]+)=([^&=]+)") do name = unescape(name) value = unescape(value) cgi[name] = value end end -- 編碼 function escape(s) s = string.gsub(s, "[&=+%%%c]", function(c) return string.format("%%%02X", string.byte(c)) end) s = string.gsub(s, " ", "+") return s end function encode(t) local b = {} for k,v in pairs(t) do b[#b+1] = (escape(k) .. "=" .. escape(v)) end return table.concat(b,"&") end
模式匹配實(shí)現(xiàn)的功能是足夠強(qiáng)大,但是工程上是否值得投入,還值得商榷,沒(méi)有通用性,只此lua一家用,雖然正則表達(dá)式也是不好調(diào)試,但是至少知道了解的人多,可能到最后筆者也不會(huì)多深入lua的模式匹配,但是如此單純?yōu)榱藴p少代碼而放棄正則表達(dá)式現(xiàn)成的庫(kù),自己又玩了一套,這也是沒(méi)誰(shuí)了。
與c的天然親密一言不合,就拿c寫(xiě)一個(gè)庫(kù)給lua用,由此可見(jiàn)兩門(mén)語(yǔ)言是多么哥兩好了,如果舉個(gè)例子的話就是lua5.1里面的位操作符,luajit就是這樣提供的解決方案, Lua Bit Operations Module, 有興趣的讀者可以下載源碼看一下,完全就是用lua的c api包裝了c里面的位操作符出來(lái)用,除了加了些限制的話(ex.位移出來(lái)的必然是32位有符)。lua除了數(shù)據(jù)結(jié)構(gòu)上面的過(guò)于簡(jiǎn)潔外,其他的控制結(jié)構(gòu)、操作符這些語(yǔ)言特性基本該有的都有了,唯獨(dú)缺了位操作符,5.1為什么當(dāng)時(shí)選擇了不實(shí)現(xiàn)位操作符呢?有知道出處或者原因的讀者歡迎留言告知。(順帶一提,lua5.2里面有官方的bit32庫(kù)可以用,lua5.3已經(jīng)補(bǔ)上了位操作符,另外Openresty在lua版本的選擇上是選擇停留在5.1,這點(diǎn)在github的Issue里面有回答過(guò),且沒(méi)有升級(jí)的打算)
雜只有nil、false為布爾假。
lua中的索引習(xí)慣以1開(kāi)始。
沒(méi)有整型,所有數(shù)字都是浮點(diǎn)數(shù)。
當(dāng)函數(shù)的參數(shù)是單引號(hào)或者雙引號(hào)的字符串或者table定義的時(shí)候,可以省略外面的(), 所以require "cookie"并不是代表require是個(gè)關(guān)鍵字。
table[index] 等價(jià)于 table [index]。
構(gòu)建公司層面完整的Openresty生態(tài) 開(kāi)發(fā)助手:成長(zhǎng)中的resty命令習(xí)慣了動(dòng)態(tài)語(yǔ)言的解釋器的立即反饋,哪怕是熟悉lua的同學(xué),初入Openresty的時(shí)候似乎又想起了編譯->執(zhí)行->修改的無(wú)限循環(huán)的記憶,因?yàn)槊看味夹枰薷呐渲梦募?、reload、測(cè)試再如此重復(fù)個(gè)幾次才能寫(xiě)對(duì)一段函數(shù),resty命令無(wú)疑期待,筆者也希望resty命令能夠更加完善、易用。
另外提一個(gè)小遺憾,現(xiàn)在resty命令不能玩Openresty里面的shared_dict共享內(nèi)存, 這可能跟目前resty使用的nginx配置的模板是固定有關(guān)吧。
環(huán)境:可能不再需要重新編譯Nginx有過(guò)Nginx維護(hù)開(kāi)發(fā)經(jīng)驗(yàn)的同學(xué)可能都熟悉這么一個(gè)過(guò)程,因?yàn)槎喟霑?huì)做業(yè)務(wù)的拆分,除了小公司外,基本都不會(huì)把一個(gè)Nginx的所有可選模塊都編譯進(jìn)去,每次有新的Nginx相關(guān)的功能的增減,都免不了重新編譯,重新部署上線,Openresty是基于Nginx的,如果是新增Nginx本身的功能,重新編譯增加功能沒(méi)什么好說(shuō)的,如何優(yōu)雅的更新Nginx服務(wù)進(jìn)程,Nginx有提供方案、各家也有各家的服務(wù)可靠性要求,具體怎么辦這里就不贅述了。
發(fā)布部署Openresty本身的發(fā)布部署跟Nginx本身沒(méi)有太大的不同,Openresty本身的發(fā)布部署官方也推出了linux平臺(tái)的預(yù)編譯好的包,在這樣的基礎(chǔ)上構(gòu)建環(huán)境就更加便捷,環(huán)境之上,首先是lua腳本和nginx配置文件的發(fā)布,在版本管理之下,加上自動(dòng)構(gòu)建的發(fā)布平臺(tái),Openresty的應(yīng)用分分鐘就可以上線了:),這個(gè)流程本身無(wú)關(guān)Openresty,但是簡(jiǎn)而言之一句話,當(dāng)重復(fù)性的東西自動(dòng)化之后,我們才有精力去解決更有趣的問(wèn)題,不是么?
第三方庫(kù)的安裝、管理以前: 自己找個(gè)第三方庫(kù)編譯之后扔到Openresty的lualib目錄,luajit是否兼容、是否lua5.1兼容都得自己來(lái)測(cè)試一遍。
之前: 對(duì)于解決前一個(gè)問(wèn)題,Openresty是通過(guò)給出lua里面Luarocks的安裝使用來(lái)解決的,但是這種方式不能解決上面所說(shuō)的第二個(gè)問(wèn)題,所以現(xiàn)在這種方式已經(jīng)不推薦使用了,下面貼一下官網(wǎng)的說(shuō)明,只做內(nèi)容收集、展示用, 最新的具體說(shuō)明參見(jiàn)using luarocks。
wget http://luarocks.org/releases/luarocks-2.0.13.tar.gz tar -xzvf luarocks-2.0.13.tar.gz cd luarocks-2.0.13/ ./configure --prefix=/usr/local/openresty/luajit --with-lua=/usr/local/openresty/luajit/ --lua-suffix=jit --with-lua-include=/usr/local/openresty/luajit/include/luajit-2.1 make sudo make install
安裝第三方庫(kù)示例: sudo /usr/local/openresty/luajit/luarocks install md5。
現(xiàn)在: Openresty提供了解決這兩個(gè)問(wèn)題的完整方案,自己的包管理的規(guī)范和倉(cāng)庫(kù)opm。
詳細(xì)的標(biāo)準(zhǔn)說(shuō)明, 請(qǐng)移步: https://github.com/openresty/... 這里就不多做介紹了,關(guān)于第三方庫(kù)的質(zhì)量,Openresty官網(wǎng)上也有了專門(mén)的QA頁(yè)面,至少保證了第三方庫(kù)一些實(shí)現(xiàn)的下限,不像python里面安裝某些第三方包,比如aerospike的, 里面安裝python客戶端,每次要去網(wǎng)上拉個(gè)c的客戶端下來(lái)之類的稀奇古怪的玩法,期待opm未來(lái)更加完善。
關(guān)于單元測(cè)試關(guān)于自動(dòng)化測(cè)試的話,就筆者的試用經(jīng)驗(yàn)而言,感覺(jué)還不是特別的順手,官方提供的Test:Nginx工具已經(jīng)提供簡(jiǎn)潔強(qiáng)大的功能,但是如果作為T(mén)DD開(kāi)發(fā)中的測(cè)試驅(qū)動(dòng)的工具而言,筆者覺(jué)得報(bào)錯(cuò)信息的有效性上面可能是唯一讓人有點(diǎn)覺(jué)得有點(diǎn)捉雞的地方,尚不清楚是否是筆者用的有誤,一般Test:Nginx的報(bào)錯(cuò)多半無(wú)法幫助調(diào)試代碼,還是要走調(diào)試、修改的老路子。但是Test:Nginx的真正價(jià)值筆者覺(jué)得是講實(shí)例代碼和測(cè)試完美的結(jié)合,由此養(yǎng)成了看每個(gè)Openresty相關(guān)的項(xiàng)目代碼都必先閱讀里面的Test:Nginx的測(cè)試,當(dāng)然最多最豐富的還是Openresty本身的測(cè)試。
舉個(gè)實(shí)際的例子,在使用Test:Nginx之前,之前對(duì)于Nginx的日志輸出,一切的測(cè)試依據(jù),對(duì)于外面的運(yùn)行環(huán)境跑的測(cè)試只能通過(guò)http的請(qǐng)求和返回來(lái)做測(cè)試的判斷條件,這時(shí)候?qū)τ谝恍┣闆r就束手無(wú)策了, 比如處理某種錯(cuò)誤情況可能需要在log里面記錄一下,這種測(cè)試就無(wú)法保證,另外也有類似lua-resty-test這樣通過(guò)提供組件的方式來(lái)進(jìn)行,但是我們一旦接觸的了Test:Nginx的測(cè)試方法之后,這些就顯得相形見(jiàn)絀了,我們舉個(gè)實(shí)際的例子。
# vim:set ft= ts=4 sw=4 et fdm=marker: use Test::Nginx::Socket::Lua; #worker_connections(1014); #master_process_enabled(1); #log_level("warn"); #repeat_each(2); plan tests => repeat_each() * (blocks() * 3 + 0); #no_diff(); no_long_string(); #master_on(); #workers(2); run_tests(); __DATA__ === TEST 1: lpush & lpop --- http_config lua_shared_dict dogs 1m; --- config location = /test { content_by_lua_block { local dogs = ngx.shared.dogs local len, err = dogs:lpush("foo", "bar") if len then ngx.say("push success") else ngx.say("push err: ", err) end local val, err = dogs:llen("foo") ngx.say(val, " ", err) local val, err = dogs:lpop("foo") ngx.say(val, " ", err) local val, err = dogs:llen("foo") ngx.say(val, " ", err) local val, err = dogs:lpop("foo") ngx.say(val, " ", err) } } --- request GET /test --- response_body push success 1 nil bar nil 0 nil nil nil --- no_error_log [error]
以上是隨便選取的lua-nginx-module的測(cè)試文件145-shdict-list.t中的一段做說(shuō)明,測(cè)試文件分為3個(gè)部分,__DATA__以上的部分編排測(cè)試如何運(yùn)行, __DATA__作為分隔符, __DATA__以下的是各個(gè)測(cè)試的說(shuō)明部分. 測(cè)試部分如果具體細(xì)分的話,一般由====TEST 1: name開(kāi)始到下一個(gè)測(cè)試的聲明;然后是配置nginx配置的http_config、config、...的部分;接著是模擬請(qǐng)求的部分,基本就是http請(qǐng)求報(bào)文的設(shè)定,功能不限于這里的request部分;最后是輸出部分,這時(shí)候不僅是http報(bào)文的body部分之類的http響應(yīng)、還有nginx的日志的輸出這樣的測(cè)試條件,對(duì)于這樣清晰可讀、還能順帶把使用例子寫(xiě)的清楚的單元測(cè)試的框架,pythoner真的難道不羨慕么?
關(guān)于調(diào)試、性能調(diào)優(yōu)這一塊筆者還沒(méi)有深入研究過(guò),所以,這里就不多說(shuō)了,這里就做一下相關(guān)知識(shí)的鏈接歸納,方便大家整理資料吧。
lua語(yǔ)言本身提供的調(diào)試就比較簡(jiǎn)潔、加上Openresty是嵌入Nginx內(nèi)部的,這就更給排查工作帶來(lái)了困難。
官方的調(diào)試頁(yè)面
官方的性能調(diào)優(yōu)頁(yè)面
通過(guò)systemtap探查在線的Nginx work進(jìn)程
額外的工具庫(kù)stap++
工具火焰圖Flame Graphs的介紹
Linux Kernel Performance: Flame Graphs
反爬蟲(chóng)
作者 toyld 豈安科技搬運(yùn)代碼負(fù)責(zé)人
主導(dǎo)各處的挖坑工作,擅長(zhǎng)挖坑于悄然不息,負(fù)責(zé)生命不息,挖坑不止。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/39428.html
摘要:為了幫助用戶更好地完成消費(fèi)決策閉環(huán),馬蜂窩上線了大交通業(yè)務(wù)。現(xiàn)在,用戶在馬蜂窩也可以完成購(gòu)買(mǎi)機(jī)票火車(chē)票等操作。第二階段架構(gòu)轉(zhuǎn)變及服務(wù)化初探從年開(kāi)始,整個(gè)大交通業(yè)務(wù)開(kāi)始從架構(gòu)向服務(wù)化演變。 交通方式是用戶旅行前要考慮的核心要素之一。為了幫助用戶更好地完成消費(fèi)決策閉環(huán),馬蜂窩上線了大交通業(yè)務(wù)?,F(xiàn)在,用戶在馬蜂窩也可以完成購(gòu)買(mǎi)機(jī)票、火車(chē)票等操作。 與大多數(shù)業(yè)務(wù)系統(tǒng)相同,我們一樣經(jīng)歷著從無(wú)到有...
摘要:為了幫助用戶更好地完成消費(fèi)決策閉環(huán),馬蜂窩上線了大交通業(yè)務(wù)?,F(xiàn)在,用戶在馬蜂窩也可以完成購(gòu)買(mǎi)機(jī)票火車(chē)票等操作。第二階段架構(gòu)轉(zhuǎn)變及服務(wù)化初探從年開(kāi)始,整個(gè)大交通業(yè)務(wù)開(kāi)始從架構(gòu)向服務(wù)化演變。 交通方式是用戶旅行前要考慮的核心要素之一。為了幫助用戶更好地完成消費(fèi)決策閉環(huán),馬蜂窩上線了大交通業(yè)務(wù)。現(xiàn)在,用戶在馬蜂窩也可以完成購(gòu)買(mǎi)機(jī)票、火車(chē)票等操作。 與大多數(shù)業(yè)務(wù)系統(tǒng)相同,我們一樣經(jīng)歷著從無(wú)到有...
閱讀 1609·2021-10-18 13:35
閱讀 2390·2021-10-09 09:44
閱讀 851·2021-10-08 10:05
閱讀 2751·2021-09-26 09:47
閱讀 3619·2021-09-22 15:22
閱讀 460·2019-08-29 12:24
閱讀 2022·2019-08-29 11:06
閱讀 2878·2019-08-26 12:23