主頁 > 企業開發 > 打造一款簡單易用功能全面的圖片上傳組件

打造一款簡單易用功能全面的圖片上傳組件

2020-10-28 12:20:49 企業開發

多年前我曾搞過Winform,也被WPF折磨得死去活來,后來我學會了對她們冷眼旁觀,就算老鴇巨硬說又推了一個新頭牌UWP,問我要不要試試,我也不再回應,時代變了,她們古板的舞步已經失去了往日的魅力,那些為了適應潮流勉強加上的幾個動作反而顯得更加可笑、和可悲,我四處流浪,跟著年輕的小伙們去到遠處的移動村、微服務村、AI村,一呆就是幾月幾年,直到某天有人告訴我,有位妙齡女郎孤身一人在那座荒廢的村落安頓下來,她的名字叫——electron,


場景

博主十一宅家寫了一個圖文發布器,關鍵是圖片上傳區域,如下:

該區域功能相對獨立,完全可以封裝為組件以供其它專案使用,且易于維護,本人計劃包含的功能如下:

  1. 可拖拽圖片和檔案夾到上傳區域
  2. 圖片可拖拽調整順序
  3. 可洗掉,可設為封面
  4. 上傳圖片至OSS
  5. 根據圖片大小生成若干比率壓縮圖,同樣上傳至OSS(用戶對此無感知)
  6. 若圖片大小超過閾值,自動分片,分片上傳為不同檔案(為后續并行下載做好準備)
  7. 加密后上傳(防盜鏈、防和諧)
  8. [壓縮、加密、分片、上傳]進度顯示
  9. 秒傳或提示沖突不予上傳(需服務端介面)
  10. 暫停、錯誤提示、重傳等輔助功能

以上功能需求前8潭訓本完成,如果要封裝為組件供第三方使用的話,最好還要支持:

  1. 國際化&本地化
  2. 插件機制
  3. 可自定義模板&皮膚

造輪子?

博主是一個拿來主義者,對盲目造輪子的行為一向嗤之以鼻,考慮到互聯網這么多年,一般網站都有檔案/圖片上傳功能,開源出來的應該不在少數,選一兩款優良的自己再稍微改改,分分鐘搞定,結果網上搜了一圈,出乎意料,都不是很滿意,少數幾個知名點的,要么是工具而非組件形式不好集成(如PicGo),要么功能太簡單(如Layui,不知道上傳組件是否開源,不過我是他家的會員),要么太過復雜和花里胡哨(如bootstrap-fileinput),其實按照我的要求,就算找到勉強湊合的,也要深度改造過,有這時間還不如老老實實自己擼,

當然就算現成的輪子不好轉,借鑒還是可以的,由于幾年前我曾使用bootstrap-fileinput上傳檔案到oss,對它還算有一點了解,github上看了下,發現這個組件一直在更新,官方檔案比記憶中要稍顯清晰些,但巨多的配置項依舊讓我眼花繚亂,深入其原始碼,核心檔案的代碼行數已經6000+,要理清短時間內是不可能了,而且其中關鍵的異步任務(主要是上傳)基于jQuery.Deferred,jQuery.Deferred又是對Promise的封裝,bootstrap-fileinput用起來復雜許多,而我們的異步任務除了上傳外,至少還有壓縮、加密、分片,本著實操ES6之Promise一文打下的良好基礎,這部分代碼就自己寫好了(迷之自信:),所以,剩下能借鑒的就只有邊角料的UI、拖拽代碼了,而這兩塊也著實可以再剪幾刀,


實作

由于本組件一開始是在Nodejs/Electron環境下開發的,所以就沒考慮過一些古老瀏覽器的感受,而是假設執行環境支持File/FileReader/FormData等型別及相關API,

檔案拖拽選擇

重點是在用戶“拖”著[若干]檔案[夾],在拖拽區域內釋放時,如何獲取相關檔案資訊,代碼如下:

    _zoneDrop: async function (e) {
        let dataTransfer = e.originalEvent.dataTransfer,
            files = dataTransfer.files, items = dataTransfer.items, folderCount = this._getDragDropFolderCount(items)
        e.preventDefault()
        if (this._isEmpty(files)) {
            return
        }
        if (folderCount > 0) {
            files = []
            for (let i = 0; i < items.length; i++) {
                let item = items[i].webkitGetAsEntry()
                if (item) {
                    await this._scanDroppedItems(item, files)
                }
            }
        }
        this.$dropZone.removeClass('file-highlighted')

        this.$dropZone.trigger("filesChanged", files)
    }

若拖拽的專案不包含檔案夾,那么直接回傳dataTransfer.files,否則遞回加載所有檔案:

    _scanDroppedItems: async function (item, files, path) {
        path = path || ''
        let self = this
        if (item.isFile) {
            let task = new Promise((resolve, reject) => {
                item.file(function (file) {
                    if (path) {
                        file.relativePath = path + file.name;
                    }
                    resolve(file)
                }, e => reject(e))
            })
            let file = await task.catch(e => { throw e })
            files.push(file)
        } else {
            if (item.isDirectory) {
                let i, dirReader = item.createReader()
                let readDir = function () {
                    return new Promise((resolve, reject) => {
                        dirReader.readEntries(async function (entries) {
                            if (entries && entries.length > 0) {
                                let tasks = []
                                for (i = 0; i < entries.length; i++) {
                                    tasks.push(self._scanDroppedItems(entries[i], files, path + item.name + '/'))
                                }
                                Promise.all(tasks).then(() => resolve()).catch(e => reject(e))
                                // recursively call readDir() again, since browser can only handle first 100 entries.
                                await readDir().catch(e => { throw e })
                            } else
                                resolve()
                        }, e => reject(e))
                    })
                }
                await readDir()
            }
        }
    }

這里理解上的難點是異步遞回呼叫,且同時使用了Promist.then(不阻塞)及await(阻塞)模式,且同時有兩個函式交錯遞回——_scanDroppedItemsreadDir,老實說,這個函式當時也是憑感覺寫,此處就不展開講了,道可道,非常道:)

壓縮

使用了compressorjs庫,代碼如下:

    compress: async function (file, level, quality = 0.8) {
        //以下若干情況不需要壓縮,直接回傳原file
        switch (true) {
            case file.size < 51200:
            case file.size < 524288 && level != 'thumbnail':
            case file.size < 1048576 && level == 'big':
                file.asLevels = file.asLevels || []
                file.asLevels.push(level)
                return file;
        }

        let opt = {
            quality: quality
        }
        let img = await utility.getImage(file.path) //轉成img以得到width/height屬性
        let scale = Math.min(img.width, img.height, this.levels[level])
        opt[img.width < img.height ? 'width' : 'height'] = scale
        return new Promise((resolve, reject) => {
            Object.assign(opt, {
                success(result) {
                    result.level = level
                    resolve(result)
                },
                error(err) {
                    reject(err)
                },
            })
            new Cmp(file, opt)
        })
    }

看注釋,不是所有圖片過來都無腦壓縮,本身size已經在壓縮級別內了就直接回傳,另外scale變數表示短邊長度,是業務需求,可無視,

加密

使用AES加密標準,首先要知道,AES是基于資料塊的加密方式,每個加密塊大小為128位,它又有幾種實作方式:

  • ECB:是一種基礎的加密方式,明文被分割成分組長度相等的塊(不足補齊),然后單獨一個個加密,一個個輸出組成密文,
  • CBC:是一種回圈模式,前一個分組的密文和當前分組的明文異或操作后再加密,這樣做的目的是增強破解難度,需要初始化向量IV,參看加密演算法IV的作用
  • CFB/OFB實際上是一種反饋模式,目的也是增強破解的難度,

使用crypto庫的AES加密,

    _encrypt: async function (file, key, iv, destDir = 'temp') {
        key = key || await this._md5(file) //128bit length
        key = Buffer.from(key, 'hex')
        iv = iv || "stringwith16byte"
        iv = Buffer.from(iv, 'utf8')
        let cipher = crypto.createCipheriv('aes-128-cbc', key, iv)
        cipher.setAutoPadding(true)

        let stm = file.stream()      
        let writerStream = fs.createWriteStream(path.join(__dirname, destDir,file.name))
        stm.pipe(cipher).pipe(writerStream)
    }

cipher.setAutoPadding(true)表示明文分塊后位數不足自動補足,標準的補足演算法有多種,crypto使用PKCS7,設為false的話,就要自己考慮如何補足,參看Node.js Crypto, what's the default padding for AES?

上述代碼采用的是CBC模式,如果考慮到效率,可使用ECB模式,明文分塊之后,各個塊之間相互獨立,互不影響,可并行計算加密,但安全性稍差,不過在我們的場景下夠用了,

ps:OSS提供了對上傳檔案的服務端加密(需要設定x-oss-server-side-encryption),當下載時,OSS會先在服務端解密再傳輸,整個加解密程序可以做到用戶端無感,所以它的目的只是保證檔案在OSS服務器上的安全,怕服務器被盜?還是對OSS本身的存盤安全性不自信?不是很懂OSS工程師的想法,

分片

網上資料欠缺,不知Blob是否把資料全部加載進記憶體中,而沒有其它記憶體方面的考量,至少以URL形式獲取的Blob是如此,參看https://javascript.info/blob#blob-as-url,同時,若手動構造Blob,也只能將所有資料一股腦給出[到記憶體中],而不是更簡單更高效的方式比如傳遞檔案路徑,然后按需獲取資料,當然,這應該是安全方面考量,避免js隨意調動本地檔案,但對我們現在的場景來說就有點麻煩了,

用戶選擇要上傳的檔案后,上一步我們對它們進行了加密,并另存為臨時檔案到磁盤中,此時要再將該檔案主動轉為Blob或File物件[用于后續上傳]就比較麻煩,在Nodejs下還好,大不了將檔案全部加載到記憶體中,通過位元組陣列轉換,但在瀏覽器環境下由于屏蔽了對本地檔案的讀寫,這是不可能的,

fs.createReadStream()可接受Buffer型別的引數,然而并不是用于傳遞檔案內容的,而仍然只能是檔案路徑,You can apparently pass the path in a Buffer object, but it still must be an acceptable OS path when the Buffer is converted to a string.

為了滿足不同場景下的使用,并考慮到開銷問題,最好能以流的形式,邊加密邊上傳,然而OSS的PostObject似乎不支持流模式(PubObject倒是可以,參看流式上傳),不過我們可以實作stream.Writable模擬流上傳,其實內部是分片上傳,但這種方式并不推薦,參看下面stream一節,

所以目前來說最簡單直接有效的方式還是基于Blob.slice()分片,如下:

        let pieces
        if (this.pieceSize && !file.level && file.size > this.pieceSize) { //目前只對原圖進行分片處理
            pieces = []
            let startIndex = 0
            do {
                pieces.push(file.slice(startIndex, startIndex += this.pieceSize))
            }
            while (startIndex < file.size)
        } else
            pieces = [file]

前面說到,分片的目的之一是并行下載,其實Http1.1(RFC2616)引入的Range & Content-Range開始支持獲取檔案的部分內容,這已經為對整個檔案的并行下載以及斷點續傳提供了技術支持,上傳前分片似乎多此一舉了,其實不盡然,現在很多檔案服務提供商會限制單用戶的連接數和傳輸速率,如果基于Http1.1 Range做并行下載,假設服務器限制了同時最多3個連接,就算你開10個執行緒也于事無補;而我們的物理分片可以將一個檔案拆分到不同的服務器甚至不同服務商,自主可控,同時也提高了盜鏈和爬蟲的難度,

上傳

    uploadFile: async function (file, uploadUrl, opt) {
        uploadUrl = uploadUrl || await this._get(this._getOssUploadUrl)
        opt = opt || {
            headers: {
                "Cache-Control": "max-age=2592000",
                'Content-Type': 'multipart/form-data'
            }
        }
        let policy = await this._getPolicy()
        let key = utility.getRandomString(20)
        let formData = https://www.cnblogs.com/newton/archive/2020/10/28/new FormData()
        formData.append('Cache-Control', 'max-age=2592000')
        formData.append('key', key)
        for (let k in policy) {
            formData.append(k, policy[k])
        }
        formData.append('file', file)

        opt.onUploadProgress = evt => opt.processCallback(evt, key)
        axios.post(uploadUrl, formData, opt).then((data) => {
            console.info(data)
        })
    }

其中policy是服務端上傳策略加上簽名回傳給前端的,OSS用其鑒別請求的合法性,


其它

stream

nodejs中,stream有pipe,管道的概念,說白了就是鏈式處理,只不過這里處理的是stream罷了,以前大家都使用through2庫自定義處理器,nodejs在v1.2.0開始引入了Simplified Stream Construction,可以替代through2,它宣告了stream.Writablestream.Readablestream.Duplexstream.Transform四種流型別,

注意stream.Duplexstream.Transform 的區別:stream.Duplex不要求輸入輸出流有關系,它們可以沒一毛錢關系,只要實作stream.Duplex的類既能read又能write就可以了;stream.Transform繼承自stream.Duplex,從字面意思上說就是轉換,很明顯,輸入流經過某種轉換轉變為輸出流,輸入輸出是有關系的,上述加密一節用到的Cipher就實作了stream.Transform,因此我們可以方便地將源檔案加密并另存為一個新檔案,

我們要區分Nodejs的stream定義和HTML5的stream Web API,兩者有相似之處,但不能混用,以ReadableStream為例,前者pipe(WritableStream),回傳的是傳遞的可寫流,后者pipeTo(WritableStream),回傳的是Promise物件,且雖然它們都叫ReadableStream或WritableStream,但它們不是同一個東西,目前也沒有發現能方便轉換它倆的方法,

stream.Writable

原本想通過實作stream.Writable模擬流上傳的形式實作分片上傳,但在我們的場景下其實沒有必要,反而可能影響效率,不過看一下如何實作也無妨,

  1. 在實作類的建構式中增加一行Writable.call(this, this._options.streamOpts)

  2. 給實作類定義_write函式,比如:

        _write: function (chunk, _, callback) {
            console.info(chunk.length) //65536/64k max
            this.block += chunk
            if (block.length >= 1048576) //當到達1M時,開始上傳
            {
                // 上傳代碼,注意可能需要阻塞直到上傳完成,避免block的變化影響到上傳資料
                let blob = new Blob(this.block) //偽代碼
                this._upload(blob, () => {
                    this.block.clear() //偽代碼
                    callback() // 必須,告知已順利執行,否則_write只會被呼叫一次
                }) 
            }
        }
    

    每處理一段資料,就要callback一次,告知程式可以開始處理下一段資料了,如果全域block的變化會影響到上傳,那么我們就必須等待本次上傳成功之后再進行下一個分片的上傳,這就降低了效率,
    如果給callback傳遞了引數,則是表明本次處理發生了錯誤,

    注意上游的ReadableStream并不知道你處理資料的速度,所以如果未做處理的話,可能出現資料積壓(back pressure)的問題,即資料源源不斷地往記憶體輸入卻得不到及時處理的情況,此時highWaterMark選項就派上了用場,當積壓的資料大小超過highWaterMark預設值的話,WritableStream.write()會回傳false,用于告知上游,上游就可以暫停喂資料,同時上游監聽下游的drain事件,當待處理資料大小小于 highWaterMark 時下游會觸發 drain 事件,上游就可以重啟輸出,pipe函式內部已經實作了這部分邏輯,參看NodeJS Stream 四:Writable

    所以,highWaterMark指的并不是單次處理資料的大小,測驗發現,單次write傳入的chunk大小<=64k,這是為啥呢?在w3c專案中也有人對此提出了疑問,參看Define chunk size for ReadableStream created by blob::stream() #144

    還可以實作_writev()函式,用于有積壓資料時一次性處理完所有積壓資料,當然,何時呼叫不需要我們關心,WritableStream會自動處理,具體來說, If implemented and if there is buffered data from previous writes, _writev() will be called instead of _write().`

  3. util.inherits(實作類, Writable)

stream.Readable

上面說到,ReadableStream在w3c標準里和Nodejs里都有,但是不同的類,不能通用,那如果要將Blob.stream()轉成Nodejs里的ReadableStream怎么辦呢?至少我沒找到一鍵轉換的方法,以下是借助ArrayBufferBuffer的轉換實作的,

    let ab = await file.arrayBuffer()
    let buf = Buffer.from(ab)
    let stm = new Readable({
        read() {
            // 空實作
        }
    })
    stm.push(buf)

其實這種方式失去了strem本身的意義,因為資料都已經全部在記憶體中了,直接操作反而來得更加方便,這也是分片實作為什么不這么做的原因之一,期待w3c和Nodejs在stream方面統一的那天吧,

Electron

原本本文是圍繞Electron展開的,題目都取了一陣子了——“Electron構建桌面應用程式實戰指南之實作酷炫圖文發布器”,后來發現其實Electron沒啥好寫的,難點還是在業務的實作,不過有些坑仍然值得一提,

npm與cnpm

npm是 Node.js 標準的軟體包管理器,但由于默認的倉庫地址位于國外,package的下載速度可能會比較慢,

淘寶團隊做了一個npm官方倉庫的鏡像倉庫,同步頻率目前為10分鐘,地址是https://registry.npm.taobao.org,使用npm install -g cnpm --registry=https://registry.npm.taobao.org安裝cnpm命令即可,
一般來說,只要使用npm config set registry https://registry.npm.taobao.org改變默認倉庫地址,就可以使下載速度加快,

打包

打包出現打包過慢(幾個小時),原因很可能是因為依賴包都是通過cnpm安裝,洗掉cnpm安裝的依賴包,替換成npm安裝的依賴包即可,詳情參看electron打包:electron-packager及electron-builder兩種方式實作(for Windows),

[使用electron-packager]打包后所有的代碼及資源檔案會在ProductName\resources\app下,若代碼中是以相對路徑定位依賴檔案,將以ProductName為基目錄查找,會報找不到的錯誤,因此我們一般在代碼中使用path.join(__dirname, 'xxxx'),使得在開發程序中還是部署之后都能正確定位檔案,

打包后就可以生成安裝包,參看【Electron】 NSIS 打包 Electron 生成exe安裝包(asar的步驟可以跳過),如果覺得安裝包的體積過大,可在electron-packager打包前洗掉package-lock.json檔案,這將極大地減少node_modules目錄體積,進而減小最終生成的安裝包大小(網上說的其它一些方法有點復雜,沒有太去了解),

奇怪的問題

本人使用一個名叫node-stream-zip的庫決議zip包,將其中一些操作封裝為Promise模式,然后發現初次加載時可以正常執行,reload后狀態就一直pending了,遇到這種問題可以嘗試設定app.allowRendererProcessReuse = false,猜測是由于electron重用渲染層行程導致某些類別庫例外,相關鏈接https://github.com/electron/electron/issues/18397
但這又會使得node-stream-zip第一次加載無法按預期執行,后來采用先預加載一個空白頁(其它頁面也可以)解決,如下:

window.loadURL('about:blank').then(() => { //同上
	window.loadFile(path.join(__dirname, "package.html"))
})

alert bug

electron有個bug一直沒有得到解決——原生alert彈出框會導致頁面失去焦點,文本框無法輸入,需要整個視窗重新激活下才可以(比如最小化一下再還原,或者滑鼠點擊其它應用后再回傳),可以重定義alert覆寫原生實作,如下示例:

window.alert = function () {
    let $alert = $(`
    <div id="alert"  tabindex="-1">
        <div >
            <div >
                <div >
                    <p ></p>
                </div>
                <div >
                    <button type="button"  data-dismiss="modal">確定</button>
                </div>
            </div>
        </div>
    </div>`).appendTo('body')
    let fun = msg => {
        $alert.find('.alert-msg').text(msg)
        $alert.modal('show')
    }
    return fun
}()

是否開源

由于本組件寫的較為倉促,尚有不完善的地方,一些計劃的功能尚未實作或代碼較為丑陋(丑陋主要是因為依賴的框架、庫和協議標準不一致,各自的“缺陷”使然),且和OSS關聯較為緊密,運行環境也框死在Nodejs下,沒有達到博主心中開源的標準,若關注的朋友較多,那么等忙完了這一陣,空閑時候再考慮完善后開源,


參考資料

Stream highWaterMark misunderstanding
多執行緒下載一個大檔案的速度更快的真正原因是什么?

轉載請註明出處,本文鏈接:https://www.uj5u.com/qiye/195084.html

標籤:其他

上一篇:background背景相關之背景顏色

下一篇:background背景相關之背景顏色

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • IEEE1588PTP在數字化變電站時鐘同步方面的應用

    IEEE1588ptp在數字化變電站時鐘同步方面的應用 京準電子科技官微——ahjzsz 一、電力系統時間同步基本概況 隨著對IEC 61850標準研究的不斷深入,國內外學者提出基于IEC61850通信標準體系建設數字化變電站的發展思路。數字化變電站與常規變電站的顯著區別在于程序層傳統的電流/電壓互 ......

    uj5u.com 2020-09-10 03:51:52 more
  • HTTP request smuggling CL.TE

    CL.TE 簡介 前端通過Content-Length處理請求,通過反向代理或者負載均衡將請求轉發到后端,后端Transfer-Encoding優先級較高,以TE處理請求造成安全問題。 檢測 發送如下資料包 POST / HTTP/1.1 Host: ac391f7e1e9af821806e890 ......

    uj5u.com 2020-09-10 03:52:11 more
  • 網路滲透資料大全單——漏洞庫篇

    網路滲透資料大全單——漏洞庫篇漏洞庫 NVD ——美國國家漏洞庫 →http://nvd.nist.gov/。 CERT ——美國國家應急回應中心 →https://www.us-cert.gov/ OSVDB ——開源漏洞庫 →http://osvdb.org Bugtraq ——賽門鐵克 →ht ......

    uj5u.com 2020-09-10 03:52:15 more
  • 京準講述NTP時鐘服務器應用及原理

    京準講述NTP時鐘服務器應用及原理京準講述NTP時鐘服務器應用及原理 安徽京準電子科技官微——ahjzsz 北斗授時原理 授時是指接識訓通過某種方式獲得本地時間與北斗標準時間的鐘差,然后調整本地時鐘使時差控制在一定的精度范圍內。 衛星導航系統通常由三部分組成:導航授時衛星、地面檢測校正維護系統和用戶 ......

    uj5u.com 2020-09-10 03:52:25 more
  • 利用北斗衛星系統設計NTP網路時間服務器

    利用北斗衛星系統設計NTP網路時間服務器 利用北斗衛星系統設計NTP網路時間服務器 安徽京準電子科技官微——ahjzsz 概述 NTP網路時間服務器是一款支持NTP和SNTP網路時間同步協議,高精度、大容量、高品質的高科技時鐘產品。 NTP網路時間服務器設備采用冗余架構設計,高精度時鐘直接來源于北斗 ......

    uj5u.com 2020-09-10 03:52:35 more
  • 詳細解讀電力系統各種對時方式

    詳細解讀電力系統各種對時方式 詳細解讀電力系統各種對時方式 安徽京準電子科技官微——ahjzsz,更多資料請添加VX 衛星同步時鐘是我京準公司開發研制的應用衛星授時時技術的標準時間顯示和發送的裝置,該裝置以M國全球定位系統(GLOBAL POSITIONING SYSTEM,縮寫為GPS)或者我國北 ......

    uj5u.com 2020-09-10 03:52:45 more
  • 如何保證外包團隊接入企業內網安全

    不管企業規模的大小,只要企業想省錢,那么企業的某些服務就一定會采用外包的形式,然而看似美好又經濟的策略,其實也有不好的一面。下面我通過安全的角度來聊聊使用外包團的安全隱患問題。 先看看什么服務會使用外包的,最常見的就是話務/客服這種需要大量重復性、無技術性的服務,或者是一些銷售外包、特殊的職能外包等 ......

    uj5u.com 2020-09-10 03:52:57 more
  • PHP漏洞之【整型數字型SQL注入】

    0x01 什么是SQL注入 SQL是一種注入攻擊,通過前端帶入后端資料庫進行惡意的SQL陳述句查詢。 0x02 SQL整型注入原理 SQL注入一般發生在動態網站URL地址里,當然也會發生在其它地發,如登錄框等等也會存在注入,只要是和資料庫打交道的地方都有可能存在。 如這里http://192.168. ......

    uj5u.com 2020-09-10 03:55:40 more
  • [GXYCTF2019]禁止套娃

    git泄露獲取原始碼 使用GET傳參,引數為exp 經過三層過濾執行 第一層過濾偽協議,第二層過濾帶引數的函式,第三層過濾一些函式 preg_replace('/[a-z,_]+\((?R)?\)/', NULL, $_GET['exp'] (?R)參考當前正則運算式,相當于匹配函式里的引數 因此傳遞 ......

    uj5u.com 2020-09-10 03:56:07 more
  • 等保2.0實施流程

    流程 結論 ......

    uj5u.com 2020-09-10 03:56:16 more
最新发布
  • 使用Django Rest framework搭建Blog

    在前面的Blog例子中我們使用的是GraphQL, 雖然GraphQL的使用處于上升趨勢,但是Rest API還是使用的更廣泛一些. 所以還是決定回到傳統的rest api framework上來, Django rest framework的官網上給了一個很好用的QuickStart, 我參考Qu ......

    uj5u.com 2023-04-20 08:17:54 more
  • 記錄-new Date() 我忍你很久了!

    這里給大家分享我在網上總結出來的一些知識,希望對大家有所幫助 大家平時在開發的時候有沒被new Date()折磨過?就是它的諸多怪異的設定讓你每每用的時候,都可能不小心踩坑。造成程式意外出錯,卻一下子找不到問題出處,那叫一個煩透了…… 下面,我就列舉它的“四宗罪”及應用思考 可惡的四宗罪 1. Sa ......

    uj5u.com 2023-04-20 08:17:47 more
  • 使用Vue.js實作文字跑馬燈效果

    實作文字跑馬燈效果,首先用到 substring()截取 和 setInterval計時器 clearInterval()清除計時器 效果如下: 實作代碼如下: <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta ......

    uj5u.com 2023-04-20 08:12:31 more
  • JavaScript 運算子

    JavaScript 運算子/運算子 在 JavaScript 中,有一些運算子可以使代碼更簡潔、易讀和高效。以下是一些常見的運算子: 1、可選鏈運算子(optional chaining operator) ?.是可選鏈運算子(optional chaining operator)。?. 可選鏈操 ......

    uj5u.com 2023-04-20 08:02:25 more
  • CSS—相對單位rem

    一、概述 rem是一個相對長度單位,它的單位長度取決于根標簽html的字體尺寸。rem即root em的意思,中文翻譯為根em。瀏覽器的文本尺寸一般默認為16px,即默認情況下: 1rem = 16px rem布局原理:根據CSS媒體查詢功能,更改根標簽的字體尺寸,實作rem單位隨螢屏尺寸的變化,如 ......

    uj5u.com 2023-04-20 08:02:21 more
  • 我的第一個NPM包:panghu-planebattle-esm(胖虎飛機大戰)使用說明

    好家伙,我的包終于開發完啦 歡迎使用胖虎的飛機大戰包!! 為你的主頁添加色彩 這是一個有趣的網頁小游戲包,使用canvas和js開發 使用ES6模塊化開發 效果圖如下: (覺得圖片太sb的可以自己改) 代碼已開源!! Git: https://gitee.com/tang-and-han-dynas ......

    uj5u.com 2023-04-20 08:01:50 more
  • 如何在 vue3 中使用 jsx/tsx?

    我們都知道,通常情況下我們使用 vue 大多都是用的 SFC(Signle File Component)單檔案組件模式,即一個組件就是一個檔案,但其實 Vue 也是支持使用 JSX 來撰寫組件的。這里不討論 SFC 和 JSX 的好壞,這個仁者見仁智者見智。本篇文章旨在帶領大家快速了解和使用 Vu ......

    uj5u.com 2023-04-20 08:01:37 more
  • 【Vue2.x原始碼系列06】計算屬性computed原理

    本章目標:計算屬性是如何實作的?計算屬性快取原理以及洋蔥模型的應用?在初始化Vue實體時,我們會給每個計算屬性都創建一個對應watcher,我們稱之為計算屬性watcher ......

    uj5u.com 2023-04-20 08:01:31 more
  • http1.1與http2.0

    一、http是什么 通俗來講,http就是計算機通過網路進行通信的規則,是一個基于請求與回應,無狀態的,應用層協議。常用于TCP/IP協議傳輸資料。目前任何終端之間任何一種通信方式都必須按Http協議進行,否則無法連接。tcp(三次握手,四次揮手)。 請求與回應:客戶端請求、服務端回應資料。 無狀態 ......

    uj5u.com 2023-04-20 08:01:10 more
  • http1.1與http2.0

    一、http是什么 通俗來講,http就是計算機通過網路進行通信的規則,是一個基于請求與回應,無狀態的,應用層協議。常用于TCP/IP協議傳輸資料。目前任何終端之間任何一種通信方式都必須按Http協議進行,否則無法連接。tcp(三次握手,四次揮手)。 請求與回應:客戶端請求、服務端回應資料。 無狀態 ......

    uj5u.com 2023-04-20 08:00:32 more