標籤: 台中搬家

  • 用雲開發Cloudbase,實現小程序多圖片內容安全監測

    用雲開發Cloudbase,實現小程序多圖片內容安全監測

    前言

    相比於文本的安全檢測,圖片的安全檢測要稍微略複雜一些,當您讀完本篇,將get到

    • 圖片安全檢測的應用場景
    • 解決圖片的安全校驗的方式
    • 使用雲調用方式對圖片進行檢測
    • 如何對上傳圖片大小進行限制
    • 如何解決多圖上傳覆蓋問題

    示例效果

    當用戶上傳敏感違規圖片時,禁止用戶上傳發布,並且做出相對應的用戶友好提示

    應用場景

    通常,在校驗一張圖片是否含有違法違規內容相比於文本安全的校驗,同樣重要,有如下應用

    • 圖片智能鑒黃:涉及拍照的工具類應用(如美拍,識圖類應用)用戶拍照上傳檢測;電商類商品上架圖片檢測;媒體類用戶文章里的圖片檢測等
    • 敏感人臉識別:用戶頭像;媒體類用戶文章里的圖片檢測;社交類用戶上傳的圖片檢測等,凡是有用戶自發生產內容的都應當提前做檢測

    解決圖片的安全手段

    在小程序開發中,提供了兩種方式

    • HTTPS調用
    • 雲調用

    HTTPS 調用的請求接口地止

    https://api.weixin.qq.com/wxa/img_sec_check?access_token=ACCESS_TOKEN
    

    檢測圖片審核,根據官方文檔得知,需要兩個必傳的參數:分別是:access_token(接口調用憑證),media(要檢測的圖片文件)

    對於HTTPS調用方式,願意折騰的小夥伴可以參考文本內容安全檢測(上篇)的處理方式,處理大同小異,本篇主要以雲開發的雲調用為主

    功能實現:小程序端邏輯

    對於wxml與wxss,大家可以自行任意修改,本文重點在於圖片安全的校驗

    <view class="image-list">
    <!-- 显示圖片 -->
       <block wx:for="{{images}}" wx:key="*this"><view class="image-wrap">
           <image class="image" src="{{item}}" mode="aspectFill" bind:tap="onPreviewImage" data-imgsrc="{{item}}"></image><i class="iconfont icon-shanchu" bind:tap="onDelImage" data-index="{{index}}"></i></view>
       </block>
       <!-- 選擇圖片 -->
       <view class="image-wrap selectphoto" hidden="{{!selectPhoto}}" bind:tap="onChooseImage"><i class="iconfont icon-add"></i></view>
       </view>
       <view class="footer"><button class="send-btn"  bind:tap="send">發布</button>
       </view>
    

    對應的wxss代碼

    .footer {
      display: flex;
      align-items: center;
      width: 100%;
      box-sizing: border-box;
      background: #34bfa3;
    }
    
    .send-btn {
      width: 100%;
      color: #fff;
      font-size: 32rpx;
      background: #34bfa3;
    }
    
    button {
      border-radius: 0rpx;
    }
    
    button::after {
      border-radius: 0rpx !important;
    }
    
    /* 圖片樣式 */
    .image-list {
      display: flex;
      flex-wrap: wrap;
      margin-top: 20rpx;
    }
    
    .image-wrap {
      width: 220rpx;
      height: 220rpx;
      margin-right: 10rpx;
      margin-bottom: 10rpx;
      position: relative;
      overflow: hidden;
      text-align: center;
    }
    
    .image {
      width: 100%;
      height: 100%;
    }
    
    .icon-shanchu {
      position: absolute;
      top: 0;
      right: 0;
      width: 40rpx;
      height: 40rpx;
      background-color: #000;
      opacity: 0.4;
      color: #fff;
      text-align: center;
      line-height: 40rpx;
      font-size: 38rpx;
      font-weight: bolder;
    }
    
    .selectphoto {
      border: 2rpx dashed #cbd1d7;
      position: relative;
    }
    
    .icon-add {
      position: absolute;
      top: 50%;
      left: 50%;
      transform: translate(-50%, -50%);
      color: #cbd1d7;
      font-size: 60rpx;
    }
    

    最終呈現的UI,由於只是用於圖片檢測演示,UI方面可忽略,如下所示

    對應的JS代碼

    /*
    * 涉及到的API:wx.chooseImage  從本地相冊選擇圖片或使用相機拍照
    *(https://developers.weixin.qq.com/miniprogram/dev/api/media/image/wx.chooseImage.html)
    
    
    *
    *
    */// 最大上傳圖片數量
    const MAX_IMG_NUM = 9;
    
    const db = wx.cloud.database(); // 初始化雲數據庫
    Page({
    
      /**
       * 頁面的初始數據
       */
      data: {
        images: [],  // 把上傳的圖片存放在一個數組對象裏面
        selectPhoto: true, // 添加+icon元素是否显示
      },
    
      /**
       * 生命周期函數--監聽頁面加載
       */
      onLoad: function (options) {
    
      },
    
      // 選擇圖片
      onChooseImage() {
        // 還能再選幾張圖片,初始值設置最大的數量-當前的圖片的長度
        let max = MAX_IMG_NUM - this.data.images.length; 
        wx.chooseImage({
          count: max,               // count表示最多可以選擇的圖片張數
          sizeType: ['original', 'compressed'], //  所選的圖片的尺寸
          sourceType: ['album', 'camera'],  // 選擇圖片的來源
          success: (res) => {                     // 接口調用成功的回調函數console.log(res)
            this.setData({                       // tempFilePath可以作為img標籤的src屬性显示圖片,下面是將后添加的圖片與之前的圖片給追加起來
              images: this.data.images.concat(res.tempFilePaths)
            })
            // 還能再選幾張圖片
            max = MAX_IMG_NUM - this.data.images.length
            this.setData({
              selectPhoto: max <= 0 ? false : true  // 當超過9張時,加號隱藏
            })
          },
        })
      },
    
      // 點擊右上方刪除圖標,刪除圖片操作
      onDelImage(event) {
        const index = event.target.dataset.index;
        // 點擊刪除當前圖片,用splice方法,刪除一張,從數組中移除一個                                                       
        this.data.images.splice(index, 1)
        this.setData({
          images: this.data.images
        })
        // 當添加的圖片達到設置最大的數量時,添加按鈕隱藏,不讓新添加圖片
        if (this.data.images.length == MAX_IMG_NUM - 1) {
          this.setData({
            selectPhoto: true,
          })
        }
      },
    })
    

    最終實現的前端UI效果如下所是:

    您現在看到的效果,沒有任何雲函數代碼,只是前端的純靜態展示,對於一些涉嫌敏感圖片,是有必要進行做過濾處理的

    功能實現:雲函數側邏輯

    在cloudfunctions目錄文件夾下創建雲函數imgSecCheck

    並在該目錄下創建config.json,配置參數如下所示

    {
      "permissions": {
        "openapi": [
          "security.imgSecCheck"
        ]
      }
    }
    

    配置完后,在主入口index.js中,如下所示,通過security.imgSecCheck接口,並傳入media對象

    // 雲函數入口文件
    const cloud = require('wx-server-sdk');
    cloud.init({
      env: cloud.DYNAMIC_CURRENT_ENV
    })
    
    // 雲函數入口函數
    exports.main = async (event, context) => {
      const wxContext = cloud.getWXContext()
      try {
        const result = await cloud.openapi.security.imgSecCheck({
          media: {
            contentType: 'image/png',
            value: Buffer.from(event.img)   // 這裏必須要將小程序端傳過來的進行Buffer轉化,否則就會報錯,接口異常
          }
          
        })
    
        if (result && result.errCode.toString() === '87014') {
          return { code: 500, msg: '內容含有違法違規內容', data: result }
        } else {
          return { code: 200, msg: '內容ok', data: result }
        }
      } catch (err) {
        // 錯誤處理
        if (err.errCode.toString() === '87014') {
          return { code: 500, msg: '內容含有違法違規內容', data: err }
        }
        return { code: 502, msg: '調用imgSecCheck接口異常', data: err }
      }
    }
    

    您會發現在雲函數端,就這麼幾行代碼,就完成了圖片安全校驗

    而在小程序端,代碼如下所示

    // miniprogram/pages/imgSecCheck/imgSecCheck.js
    // 最大上傳圖片數量
    const MAX_IMG_NUM = 9;
    
    const db = wx.cloud.database()
    Page({
    
      /**
       * 頁面的初始數據
       */
      data: {
        images: [],
        selectPhoto: true, // 添加圖片元素是否显示
      },
    
      /**
       * 生命周期函數--監聽頁面加載
       */
      onLoad: function (options) {
    
      },
      // 選擇圖片
      onChooseImage() {
        // const that = this;  // 如果下面用了箭頭函數,那麼這行代碼是不需要的,直接用this就可以了的// 還能再選幾張圖片,初始值設置最大的數量-當前的圖片的長度
        let max = MAX_IMG_NUM - this.data.images.length; 
        wx.chooseImage({
          count: max,
          sizeType: ['original', 'compressed'],
          sourceType: ['album', 'camera'],
          success: (res) => {  // 這裏若不是箭頭函數,那麼下面的this.setData的this要換成that上面的臨時變量,作用域的問題,不清楚的,可以看下this指向相關的知識
           console.log(res)
           // tempFilePath可以作為img標籤的src屬性显示圖片
            const  tempFiles = res.tempFiles;
            this.setData({
              images: this.data.images.concat(res.tempFilePaths)
            })
            // 在選擇圖片時,對本地臨時存儲的圖片,這個時候,進行圖片的校驗,當然你放在最後點擊發布時,進行校驗也是可以的,只不過是一個前置校驗和後置校驗的問題,我個人傾向於在選擇圖片時就進行校驗的,選擇一些照片時,就應該在選擇時階段做安全判斷的, 小程序端請求雲函數方式// 圖片轉化buffer后,調用雲函數
            console.log(tempFiles);
            tempFiles.forEach(items => {
              console.log(items);
              // 圖片轉化buffer后,調用雲函數
              wx.getFileSystemManager().readFile({
                filePath: items.path,
                success: res => {
                      console.log(res);
                       wx.cloud.callFunction({  // 小程序端請求imgSecCheck雲函數,並傳遞img參數進行檢驗
                        name: 'imgSecCheck',
                        data: {
                          img: res.data
                        }
                })
                .then(res => {
                   console.log(res);
                   let { errCode } = res.result.data;
                   switch(errCode) {
                     case 87014:
                       this.setData({
                          resultText: '內容含有違法違規內容'
                       })
                       break;
                     case 0:
                       this.setData({
                         resultText: '內容OK'
                       })
                       break;
                     default:
                       break;
                   }
     
                })
                .catch(err => {
                   console.error(err);
                })
                },
                fail: err => {
                  console.error(err);
                }
              })
            })
            
                
            // 還能再選幾張圖片
            max = MAX_IMG_NUM - this.data.images.length
            this.setData({
              selectPhoto: max <= 0 ? false : true  // 當超過9張時,加號隱藏
            })
          },
        })
      },
    
      // 刪除圖片
      onDelImage(event) {
        const index =  event.target.dataset.index;
        // 點擊刪除當前圖片,用splice方法,刪除一張,從數組中移除一個
        this.data.images.splice(index, 1);
        this.setData({
          images: this.data.images
        })
        // 當添加的圖片達到設置最大的數量時,添加按鈕隱藏,不讓新添加圖片
        if (this.data.images.length == MAX_IMG_NUM - 1) {
          this.setData({
            selectPhoto: true,
          })
        }
      },
    })
    

    示例效果如下所示:

    至此,關於圖片安全檢測就已經完成了,您只需要根據檢測的結果,做一些友好的用戶提示,或者做一些自己的業務邏輯判斷即可

    常見問題

    如何對上傳的圖片大小進行限制

    有時候,您需要對用戶上傳圖片的大小進行限制,限制用戶任意上傳超大圖片,那怎麼處理呢,在微信小程序裏面,主要藉助的是wx.chooseImage這個接口成功返回后臨時路徑的res.tempFiles中的size大小判斷即可進行處理

    具體實例代碼如下所示

    // 選擇圖片
      onChooseImage() {
        // 還能再選幾張圖片,初始值設置最大的數量-當前的圖片的長度
        let max = MAX_IMG_NUM - this.data.images.length; 
        wx.chooseImage({
          count: max,
          sizeType: ['original', 'compressed'],
          sourceType: ['album', 'camera'],
          success: (res) => {
            console.log(res)
            const  tempFiles = res.tempFiles;
            this.setData({
              images: this.data.images.concat(res.tempFilePaths)  // tempFilePath可以作為img標籤的src屬性显示圖片
            })
            // 在選擇圖片時,對本地臨時存儲的圖片,這個時候,進行圖片的校驗,當然你放在最後點擊發布時,進行校驗也是可以的,只不過是一個前置校驗和後置校驗的問題,我個人傾向於在選擇圖片時就進行校驗的,選擇一些照片時,就應該在選擇時階段做安全判斷的, 小程序端請求雲函數方式// 圖片轉化buffer后,調用雲函數
            console.log(tempFiles);
            tempFiles.forEach(items => {
              if (items && items.size > 1 * (1024 * 1024)) {  // 限製圖片的大小
                wx.showToast({
                  icon: 'none',
                  title: '上傳的圖片超過1M,禁止用戶上傳',
                  duration: 4000
                })
                // 超過1M的圖片,禁止用戶上傳
              }
              console.log(items);
              // 圖片轉化buffer后,調用雲函數
              wx.getFileSystemManager().readFile({
                filePath: items.path,
                success: res => {
                      console.log(res);
                       wx.cloud.callFunction({   // 請求調用雲函數imgSecCheck
                        name: 'imgSecCheck',
                        data: {
                          img: res.data
                        }
                })
                .then(res => {
                   console.log(res);
                   let { errCode } = res.result.data;
                   switch(errCode) {
                     case 87014:
                       this.setData({
                          resultText: '內容含有違法違規內容'
                       })
                       break;
                     case 0:
                       this.setData({
                         resultText: '內容OK'
                       })
                       break;
                     default:
                       break;
                   }
                })
                .catch(err => {
                   console.error(err);
                })
                },
                fail: err => {
                  console.error(err);
                }
              })
            })
           
            // 還能再選幾張圖片
            max = MAX_IMG_NUM - this.data.images.length
            this.setData({
              selectPhoto: max <= 0 ? false : true  // 當超過9張時,加號隱藏
            })
          },
        })
      },
    

    注意: 使用微信官方的圖片內容安全接口進行校驗,限製圖片大小限制:1M,否則的話就會報錯

    也就是說,對於超過1M大小的違規圖片,微信官方提供的這個圖片安全接口是無法進行校驗的

    這個根據自己的業務而定,在小程序端對用戶上傳圖片的大小進行限制如果您覺得微信官方提供的圖片安全接口滿足不了自己的業務需求,那麼可以選擇一些其他的圖片內容安全校驗的接口的

    這個圖片安全校驗是非常有必要的,用戶一旦上傳非法圖片,一旦通過網絡進行傳播,產生了社會影響,平台是有責任的,這種前車之鑒是有的

    如何解決多圖上傳覆蓋的問題

    對於上傳圖片來說,這個wx.cloud.uploadFileAPI接口只能上傳一張圖片,但是很多時候,是需要上傳多張圖片到雲存儲當中的,當點擊發布的時候,我們是希望將多張圖片都上傳到雲存儲當中去的

    這個API雖然只能每次上傳一張,但您可以循環遍歷多張圖片,然後一張一張的上傳的

    在cloudPath上傳文件的參數當中,它的值:需要注意:文件的名稱

    那如何保證上傳的圖片不被覆蓋,文件不重名的情況下就不會被覆蓋

    而在選擇圖片的時候,不應該上傳,因為用戶可能有刪除等操作,如果直接上傳的話會造成資源的浪費

    而應該在點發布按鈕的時候,才執行上傳操作,文件不重名覆蓋的示例代碼如下所示

          let promiseArr = []
          let fileIds = []      // 將圖片的fileId存放到一個數組中
          let imgLength = this.data.images.length;
          // 圖片上傳
          for (let i = 0; i < imgLength; i++) {
            let p = new Promise((resolve, reject) => {
            let item = this.data.images[i]
              // 文件擴展名
              let suffix = /\.\w+$/.exec(item)[0]; // 取文件后拓展名
              wx.cloud.uploadFile({      // 利用官方提供的上傳接口
                cloudPath: 'blog/' + Date.now() + '-' + Math.random() * 1000000 + suffix,  // 雲存儲路徑,您也可以使用es6中的模板字符串進行拼接的
                filePath: item,   // 要上傳文件資源的路徑
                success: (res) => {
                  console.log(res);
                  console.log(res.fileID)
                  fileIds = fileIds.concat(res.fileID)       // 將新上傳的與之前上傳的給拼接起來
                  resolve()
                },
                fail: (err) => {
                  console.error(err)
                  reject()
                }
              })
            })
            promiseArr.push(p)
          }
          // 存入到雲數據庫,其中這個Promise.all(),等待裏面所有的任務都執行之後,在去執行後面的任務,也就是等待上傳所有的圖片上傳完后,才能把相對應的數據存到數據庫當中,具體與promise相關問題,可自行查漏
          Promise.all(promiseArr).then((res) => {
              db.collection('blog').add({ // 查找blog集合,將img,時間等數據添加到這個集合當中
                data: {
                  img: fileIds,
                  createTime: db.serverDate(), // 服務端的時間
                }
              }).then((res) => {
                console.log(res);
                this._hideToastTip();
                this._successTip();
              })
            })
            .catch((err) => {
              // 發布失敗console.error(err);
            })
    

    上面通過利用當前時間+隨機數的方式進行了一個區分,規避了上傳文件同名的問題

    因為這個上傳接口,一次性只能上傳一張圖片,所以需要循環遍歷圖片,然後一張張的上傳

    一個是上傳到雲存儲中,另一個是添加到雲數據庫集合當中,要分別注意下這兩個操作,雲數據庫中的圖片是從雲存儲中拿到的,然後再添加到雲數據庫當中去的

    示例效果如下所示:

    將上傳的圖片存儲到雲數據庫中

    注意:添加數據到雲數據庫中,需要手動創建集合,不然是無法上傳不到雲數據庫當中的,會報錯

    至此,關於敏感圖片的檢測,以及多圖片的上傳到這裏就已經完成了

    如下是完整的小程序端邏輯示例代碼

    // miniprogram/pages/imgSecCheck/imgSecCheck.js
    // 最大上傳圖片數量
    const MAX_IMG_NUM = 9;
    const db = wx.cloud.database()
    Page({
    
      /**
       * 頁面的初始數據
       */
      data: {
        images: [],
        selectPhoto: true, // 添加圖片元素是否显示
      },
    
      /**
       * 生命周期函數--監聽頁面加載
       */
      onLoad: function (options) {
    
      },
    
      // 選擇圖片
      onChooseImage() {
        // 還能再選幾張圖片,初始值設置最大的數量-當前的圖片的長度
        let max = MAX_IMG_NUM - this.data.images.length;
        wx.chooseImage({
          count: max,
          sizeType: ['original', 'compressed'],
          sourceType: ['album', 'camera'],
          success: (res) => {
            console.log(res)
            const tempFiles = res.tempFiles;
            this.setData({
              images: this.data.images.concat(res.tempFilePaths) // tempFilePath可以作為img標籤的src屬性显示圖片
            })
            // 在選擇圖片時,對本地臨時存儲的圖片,這個時候,進行圖片的校驗,當然你放在最後點擊發布時,進行校驗也是可以的,只不過是一個前置校驗和後置校驗的問題,我個人傾向於在選擇圖片時就進行校驗的,選擇一些照片時,就應該在選擇時階段做安全判斷的, 小程序端請求雲函數方式
            // 圖片轉化buffer后,調用雲函數
            console.log(tempFiles);
            tempFiles.forEach(items => {
              if (items && items.size > 1 * (1024 * 1024)) {
                wx.showToast({
                  icon: 'none',
                  title: '上傳的圖片超過1M,禁止用戶上傳',
                  duration: 4000
                })
                // 超過1M的圖片,禁止上傳
              }
              console.log(items);
              // 圖片轉化buffer后,調用雲函數
              wx.getFileSystemManager().readFile({
                filePath: items.path,
                success: res => {
                  console.log(res);
                  this._checkImgSafe(res.data); // 檢測圖片安全校驗
                },
                fail: err => {
                  console.error(err);
                }
              })
            })
    
    
            // 還能再選幾張圖片
            max = MAX_IMG_NUM - this.data.images.length
            this.setData({
              selectPhoto: max <= 0 ? false : true // 當超過9張時,加號隱藏
            })
          },
        })
      },
    
      // 刪除圖片
      onDelImage(event) {
        const index = event.target.dataset.index;
        // 點擊刪除當前圖片,用splice方法,刪除一張,從數組中移除一個
        this.data.images.splice(index, 1);
        this.setData({
          images: this.data.images
        })
        // 當添加的圖片達到設置最大的數量時,添加按鈕隱藏,不讓新添加圖片
        if (this.data.images.length == MAX_IMG_NUM - 1) {
          this.setData({
            selectPhoto: true,
          })
        }
      },
    
      // 點擊發布按鈕,將圖片上傳到雲數據庫當中
      send() {
        const images = this.data.images.length;
        if (images) {
          this._showToastTip();
          let promiseArr = []
          let fileIds = []
          let imgLength = this.data.images.length;
          // 圖片上傳
          for (let i = 0; i < imgLength; i++) {
            let p = new Promise((resolve, reject) => {
              let item = this.data.images[i]
              // 文件擴展名
              let suffix = /\.\w+$/.exec(item)[0]; // 取文件后拓展名
              wx.cloud.uploadFile({   // 上傳圖片至雲存儲,循環遍歷,一張張的上傳
                cloudPath: 'blog/' + Date.now() + '-' + Math.random() * 1000000 + suffix,
                filePath: item,
                success: (res) => {
                  console.log(res);
                  console.log(res.fileID)
                  fileIds = fileIds.concat(res.fileID)
                  resolve()
    
                },
                fail: (err) => {
                  console.error(err)
                  reject()
                }
              })
            })
            promiseArr.push(p)
          }
          // 存入到雲數據庫
          Promise.all(promiseArr).then((res) => {
              db.collection('blog').add({ // 查找blog集合,將數據添加到這個集合當中
                data: {
                  img: fileIds,
                  createTime: db.serverDate(), // 服務端的時間
                }
              }).then((res) => {
                console.log(res);
                this._hideToastTip();
                this._successTip();
              })
            })
            .catch((err) => {
              // 發布失敗
              console.error(err);
            })
        } else {
          wx.showToast({
            icon: 'none',
            title: '沒有選擇任何圖片,發布不了',
          })
        }
    
      },
    
      // 校驗圖片的安全
      _checkImgSafe(data) {
        wx.cloud.callFunction({
            name: 'imgSecCheck',
            data: {
              img: data
            }
          })
          .then(res => {
            console.log(res);
            let {
              errCode
            } = res.result.data;
            switch (errCode) {
              case 87014:
                this.setData({
                  resultText: '內容含有違法違規內容'
                })
                break;
              case 0:
                this.setData({
                  resultText: '內容OK'
                })
                break;
              default:
                break;
            }
          })
          .catch(err => {
            console.error(err);
          })
      },
    
      _showToastTip() {
        wx.showToast({
          icon: 'none',
          title: '發布中...',
        })
      },
    
      _hideToastTip() {
        wx.hideLoading();
      },
    
      _successTip() {
        wx.showToast({
          icon: 'none',
          title: '發布成功',
        })
      },
    })
    

    完整的示例wxml,如下所示

    <view class="image-list">
    <!-- 显示圖片 -->
    <block wx:for="{{images}}" wx:key="*this">
         <view class="image-wrap"><image class="image" src="{{item}}" mode="aspectFill" bind:tap="onPreviewImage" data-imgsrc="{{item}}"></image><i class="iconfont icon-shanchu" bind:tap="onDelImage" data-index="{{index}}"></i>
         </view>
    </block>
    <!-- 選擇圖片 -->
    <view class="image-wrap selectphoto" hidden="{{!selectPhoto}}" bind:tap="onChooseImage"><i class="iconfont icon-add"></i></view>
    </view>
    <view class="footer">
       <button class="send-btn"  bind:tap="send">發布</button>
    </view>
    <view>
        檢測結果显示: {{ resultText }}
    </view>
    

    您可以根據自己的業務邏輯需要,一旦檢測到圖片違規時,禁用按鈕狀態,或者給一些用戶提示,都是可以的,在發布之前或者點擊發布時,進行圖片內容安全的校驗都可以,一旦發現圖片有違規時,就不讓繼續後面的操作的

    結語

    本文主要通過藉助官方提供的圖片security.imgSecCheck

    接口,實現了對圖片安全的校驗,實現起來,是相當的方便的,對於基礎性的校驗,利用官方提供的這個接口,已經夠用了的,但是如果想要更加嚴格的檢測,可以引入一些第三方的內容安全強強校驗,確保內容的安全

    實現了如何對上傳的圖片大小進行限制,以及解決同名圖片上傳覆蓋的問題

    如果大家對文本內容安全校驗以及圖片安全校驗仍然有什麼問題,可以在下方留言,一起探討。

    雲開發公眾號:騰訊云云開發

    雲開發技術文檔:https://cloudbase.net

    雲開發技術交流加Q群:601134960

    更多精彩
    掃描二維碼了解更多

    本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

    ※回頭車貨運收費標準

    ※產品缺大量曝光嗎?你需要的是一流包裝設計!

    ※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

    ※推薦評價好的iphone維修中心

    ※教你寫出一流的銷售文案?

    台中搬家公司教你幾個打包小技巧,輕鬆整理裝箱!

    台中搬家遵守搬運三大原則,讓您的家具不再被破壞!

  • 山地大猩猩的家園不平靜 剛果維龍加國家公園12名護管員遭殺害

    環境資訊中心綜合外電;姜唯 編譯;林大利 審校

    本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

    ※別再煩惱如何寫文案,掌握八大原則!

    網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

    ※超省錢租車方案

    ※教你寫出一流的銷售文案?

    網頁設計最專業,超強功能平台可客製化

    ※產品缺大量曝光嗎?你需要的是一流包裝設計!

    台中搬家遵守搬運三大原則,讓您的家具不再被破壞!

  • 非洲豬瘟迫降! 韓媒爆北韓豬瘟再起 邊境恐成傳播溫床

    摘錄自2020年5月5日自由時報報導

    北韓在去年5月底爆出非洲豬瘟,9月時更爆出豬瘟疫情全面失控,但之後卻未有通報案例。不過南韓媒體爆料,非洲豬瘟在北韓又再度爆發,尤其是黃海北道、平安南道以及平安北道三處。

    由脫北者經營的南韓媒體《每日北韓》報導,平安南道消息人士透露,非洲豬瘟再次席捲北韓,獸醫機構視其為緊急狀態,尤其是黃海北道、平安南道以及平安北道三處的私人豬舍或合作經營農場。

    由於北韓當局最近發布《非洲豬瘟防疫指南》,因此有分析指出,北韓非洲豬瘟疫情仍持續。北韓官報也向民眾宣導牲畜疾病防疫對民眾的重要性。

    生活環境
    國際新聞
    北韓
    非洲豬瘟
    食品安全

    本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

    網頁設計最專業,超強功能平台可客製化

    ※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

    ※回頭車貨運收費標準

    ※推薦評價好的iphone維修中心

    ※教你寫出一流的銷售文案?

    台中搬家公司教你幾個打包小技巧,輕鬆整理裝箱!

    台中搬家公司費用怎麼算?

  • 疫情下的奇景!西班牙西北部棕熊出沒 150年來首見

    摘錄自2020年5月5日自由時報報導

    根據《CNN》報導,位於西班牙西北部加利西亞(Galicia)奧倫塞的「O Invernadeiro」自然保護區,近日透過架設在園內的攝影機拍到一頭年輕公熊活動的畫面,據分析,這頭公熊年紀在3至5歲左右,健康狀況良好。

    園方表示,棕熊是西班牙原生物種,從1973年起便被列入野生動物保護名單,過往雖有文獻紀錄常出沒在加利西亞地區,但這次是150年內首度有棕熊被觀測到在加利西亞南部出現,意義非凡。

    生態保育
    生物多樣性
    國際新聞
    西班牙
    棕熊
    動物與大環境變遷
    武漢肺炎

    本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

    ※產品缺大量曝光嗎?你需要的是一流包裝設計!

    ※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

    ※回頭車貨運收費標準

    ※推薦評價好的iphone維修中心

    ※超省錢租車方案

    台中搬家遵守搬運三大原則,讓您的家具不再被破壞!

    ※推薦台中搬家公司優質服務,可到府估價

  • 麥肯錫報告:後疫情時代下的氣候變遷

    轉載自台大風險社會與政策研究中心;編譯:倪茂庭(風險中心助理研究員)、吳玗恂(風險中心助理研究員)

    本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

    ※回頭車貨運收費標準

    ※產品缺大量曝光嗎?你需要的是一流包裝設計!

    ※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

    ※推薦評價好的iphone維修中心

    ※教你寫出一流的銷售文案?

    台中搬家公司教你幾個打包小技巧,輕鬆整理裝箱!

    台中搬家遵守搬運三大原則,讓您的家具不再被破壞!

  • 這些配備了省油利器的自主SUV僅7萬起

    這些配備了省油利器的自主SUV僅7萬起

    99萬作為一款小型SUV,森雅R7擁有着圓潤飽滿的外觀,小巧時尚的設計很討人喜歡,前臉不規則的中網樣式搭配着造型別緻的大燈,增添了幾分硬朗的氣息。內飾的設計很有層次感,黑銀色搭配拉絲面板,給人很運動的感覺,9英寸的中控大屏是一大亮點,包含了手機互聯和導航等功能,自動防炫目后視鏡出現在尊貴型車型上,檔次感瞬間提升了不少。

    隨着科技的發展越來越迅速,汽車技術也在不斷的進步,自動變速箱的出現解決了我們黃金左腳的命運,使駕駛者在擁堵的城市中輕鬆地駕駛車輛,那麼隨着燃油價格的不斷提升,人們有沒有想出比較省油的汽車技術呢?

    答案是肯定的,那就是發動機自動啟停系統,在車輛臨時停車等紅燈的時候,會自動熄火,待汽車需要重新啟動時,又能快速啟動發動機,大大的減小了油耗和廢氣的排放,綜合下來此項技術可以節約車子一年5%-15%的燃油哦,來看一下哪些自主品牌SUV都有配備這項技術的吧!

    奇瑞汽車-瑞虎7

    指導價:9.79-15.39萬

    說瑞虎7是奇瑞目前最好的SUV一點也不為過,時尚精緻的外觀,凌厲的腰線和車身比例非常的協調,三叉戟式的大燈和造型獨特的進氣格柵使其看上去辨識度很高。

    內飾無論是做工還是用料都給人留下深刻的印象,大量帶縫線的皮質材料和軟質搪塑工藝材料,豪華感十足,簡潔的中控大屏、自動頭燈(LED光源)、座椅加熱、無鑰匙進入/啟動等配置十分齊全。

    2650mm的軸距雖在同級別對手中並不佔優,但是實際的乘坐感受還是表現很出色的,座椅的包裹性好,肩部支撐很到位,動力方面提供1.5T+6擋手動/雙離合變速器,或者2.0L+CVT變速箱的組合,懸架方面則採用了常規的前麥弗遜后多連桿式獨立懸架。

    一汽吉林-森雅R7

    指導價:6.89-9.99萬

    作為一款小型SUV,森雅R7擁有着圓潤飽滿的外觀,小巧時尚的設計很討人喜歡,前臉不規則的中網樣式搭配着造型別緻的大燈,增添了幾分硬朗的氣息。

    內飾的設計很有層次感,黑銀色搭配拉絲面板,給人很運動的感覺,9英寸的中控大屏是一大亮點,包含了手機互聯和導航等功能,自動防炫目后視鏡出現在尊貴型車型上,檔次感瞬間提升了不少。

    森雅R7的軸距為2600mm,在這個價位車型中比較有優勢,無論是前後排的頭部空間還是腿部空間都相當寬敞;動力方面全系搭載1.6L自然吸氣發動機,最大功率116馬力,匹配5擋手動或者6擋手自一體變速器,全系標配發動機啟停功能,油耗表現更出色。

    長安汽車-長安CS15

    指導價:5.79-7.79萬

    長安CS15的外觀充滿了個性化的設計元素,稜角分明的造型和豐富的線條相互搭配,看上去顯得更為硬朗,中網的造型也是獨樹一幟,側面較高的腰線設計,使得其車門肌肉感十足,整車是偏向運動的設計路線。

    內飾為飛翼式的家族設計風格,紅色縫線的三幅式方向盤、炮筒式的儀錶盤有着濃厚的運動味道,製作工藝堪比合資車,胎壓監測、無鑰匙進入/啟動、上坡輔助、倒車影像等配置一應俱全。

    雖然CS15是一款小型SUV,軸距也只有2510mm,但是內部空間完全超出你的想象,乘坐感受相當舒適,大大小小的儲物格達到39處之多,便利性很強,全系採用1.5L+5擋手動/5擋雙離合的動力組合,8萬塊買自動擋性價比是相當高的。

    總結:瑞虎7的價格相對來說有些高,但畢竟是跨級別的,做工水平整體很高,堪比合資車,森雅R7的表現中規中矩,全系標配發動機啟停非常厚道,長安CS15的性價比最高,麻雀雖小五臟俱全,適合年輕人的第一台車。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

    ※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

    網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

    ※想知道最厲害的網頁設計公司"嚨底家"!

    ※別再煩惱如何寫文案,掌握八大原則!

    ※產品缺大量曝光嗎?你需要的是一流包裝設計!

    ※回頭車貨運收費標準

    台中搬家公司費用怎麼算?

  • 【Spring註解驅動開發】自定義TypeFilter指定@ComponentScan註解的過濾規則

    寫在前面

    Spring的強大之處不僅僅是提供了IOC容器,能夠通過過濾規則指定排除和只包含哪些組件,它還能夠通過自定義TypeFilter來指定過濾規則。如果Spring內置的過濾規則不能夠滿足我們的需求,那麼我們就可以通過自定義TypeFilter來實現我們自己的過濾規則。

    項目工程源碼已經提交到GitHub:https://github.com/sunshinelyz/spring-annotation

    FilterType中常用的規則

    在使用@ComponentScan註解實現包掃描時,我們可以使用@Filter指定過濾規則,在@Filter中,通過type指定過濾的類型。而@Filter註解的type屬性是一個FilterType枚舉,如下所示。

    package org.springframework.context.annotation;
    
    public enum FilterType {
    	ANNOTATION,
    	ASSIGNABLE_TYPE,
    	ASPECTJ,
    	REGEX,
    	CUSTOM
    }
    

    每個枚舉值的含義如下所示。

    (1)ANNOTATION:按照註解進行過濾。

    例如,使用@ComponentScan註解進行包掃描時,按照註解只包含標註了@Controller註解的組件,如下所示。

    @ComponentScan(value = "io.mykit.spring", includeFilters = {
        @Filter(type = FilterType.ANNOTATION, classes = {Controller.class})
    }, useDefaultFilters = false)
    

    (2)ASSIGNABLE_TYPE:按照給定的類型進行過濾。

    例如,使用@ComponentScan註解進行包掃描時,按照給定的類型只包含PersonService類(接口)或其子類(實現類或子接口)的組件,如下所示。

    @ComponentScan(value = "io.mykit.spring", includeFilters = {
        @Filter(type = FilterType.ASSIGNABLE_TYPE, classes = {PersonService.class})
    }, useDefaultFilters = false)
    

    此時,只要是PersonService類型的組件,都會被加載到容器中。也就是說:當PersonService是一個Java類時,Person類及其子類都會被加載到Spring容器中;當PersonService是一個接口時,其子接口或實現類都會被加載到Spring容器中。

    (3)ASPECTJ:按照ASPECTJ表達式進行過濾

    例如,使用@ComponentScan註解進行包掃描時,按照ASPECTJ表達式進行過濾,如下所示。

    @ComponentScan(value = "io.mykit.spring", includeFilters = {
        @Filter(type = FilterType.ASPECTJ, classes = {AspectJTypeFilter.class})
    }, useDefaultFilters = false)
    

    (4)REGEX:按照正則表達式進行過濾

    例如,使用@ComponentScan註解進行包掃描時,按照正則表達式進行過濾,如下所示。

    @ComponentScan(value = "io.mykit.spring", includeFilters = {
        @Filter(type = FilterType.REGEX, classes = {RegexPatternTypeFilter.class})
    }, useDefaultFilters = false)
    

    (5)CUSTOM:按照自定義規則進行過濾。

    如果實現自定義規則進行過濾時,自定義規則的類必須是org.springframework.core.type.filter.TypeFilter接口的實現類。

    例如,按照自定義規則進行過濾,首先,我們需要創建一個org.springframework.core.type.filter.TypeFilter接口的實現類MyTypeFilter,如下所示。

    public class MyTypeFilter implements TypeFilter {
        @Override
        public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
            return false;
        }
    }
    

    當我們實現TypeFilter接口時,需要實現TypeFilter接口中的match()方法,match()方法的返回值為boolean類型。當返回true時,表示符合規則,會包含在Spring容器中;當返回false時,表示不符合規則,不會包含在Spring容器中。另外,在match()方法中存在兩個參數,分別為MetadataReader類型的參數和MetadataReaderFactory類型的參數,含義分別如下所示。

    • metadataReader:讀取到的當前正在掃描的類的信息。
    • metadataReaderFactory:可以獲取到其他任務類的信息。

    接下來,使用@ComponentScan註解進行如下配置。

    @ComponentScan(value = "io.mykit.spring", includeFilters = {
        @Filter(type = FilterType.CUSTOM, classes = {MyTypeFilter.class})
    }, useDefaultFilters = false)
    

    在FilterType枚舉中,ANNOTATION和ASSIGNABLE_TYPE是比較常用的,ASPECTJ和REGEX不太常用,如果FilterType枚舉中的類型無法滿足我們的需求時,我們也可以通過實現org.springframework.core.type.filter.TypeFilter接口來自定義過濾規則,此時,將@Filter中的type屬性設置為FilterType.CUSTOM,classes屬性設置為自定義規則的類對應的Class對象。

    實現自定義過濾規則

    在項目的io.mykit.spring.plugins.register.filter包下新建MyTypeFilter,並實現org.springframework.core.type.filter.TypeFilter接口。此時,我們先在MyTypeFilter類中打印出當前正在掃描的類名,如下所示。

    package io.mykit.spring.plugins.register.filter;
    
    import org.springframework.core.io.Resource;
    import org.springframework.core.type.AnnotationMetadata;
    import org.springframework.core.type.ClassMetadata;
    import org.springframework.core.type.classreading.MetadataReader;
    import org.springframework.core.type.classreading.MetadataReaderFactory;
    import org.springframework.core.type.filter.TypeFilter;
    
    import java.io.IOException;
    
    /**
     * @author binghe
     * @version 1.0.0
     * @description 自定義過濾規則
     */
    public class MyTypeFilter implements TypeFilter {
        /**
         * metadataReader:讀取到的當前正在掃描的類的信息
         * metadataReaderFactory:可以獲取到其他任務類的信息
         */
        @Override
        public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
            //獲取當前類註解的信息
            AnnotationMetadata annotationMetadata = metadataReader.getAnnotationMetadata();
            //獲取當前正在掃描的類的信息
            ClassMetadata classMetadata = metadataReader.getClassMetadata();
            //獲取當前類的資源信息,例如:類的路徑等信息
            Resource resource = metadataReader.getResource();
            //獲取當前正在掃描的類名
            String className = classMetadata.getClassName();
            //打印當前正在掃描的類名
            System.out.println("-----> " + className);
            return false;
        }
    }
    

    接下來,我們在PersonConfig類中配置自定義過濾規則,如下所示。

    @Configuration
    @ComponentScans(value = {
            @ComponentScan(value = "io.mykit.spring", includeFilters = {
                    @Filter(type = FilterType.CUSTOM, classes = {MyTypeFilter.class})
            }, useDefaultFilters = false)
    })
    public class PersonConfig {
    
        @Bean("person")
        public Person person01(){
            return new Person("binghe001", 18);
        }
    }
    

    接下來,我們運行SpringBeanTest類中的testComponentScanByAnnotation()方法進行測試,輸出的結果信息如下所示。

    -----> io.mykit.spring.test.SpringBeanTest
    -----> io.mykit.spring.bean.Person
    -----> io.mykit.spring.plugins.register.controller.PersonController
    -----> io.mykit.spring.plugins.register.dao.PersonDao
    -----> io.mykit.spring.plugins.register.filter.MyTypeFilter
    -----> io.mykit.spring.plugins.register.service.PersonService
    org.springframework.context.annotation.internalConfigurationAnnotationProcessor
    org.springframework.context.annotation.internalAutowiredAnnotationProcessor
    org.springframework.context.annotation.internalCommonAnnotationProcessor
    org.springframework.context.event.internalEventListenerProcessor
    org.springframework.context.event.internalEventListenerFactory
    personConfig
    person
    

    可以看到,已經輸出了當前正在掃描的類的名稱,同時,除了Spring內置的bean名稱外,只輸出了personConfig和person,沒有輸出使用@Repository、@Service、@Controller註解標註的組件名稱。這是因為當前PersonConfig上標註的@ComponentScan註解是使用自定義的規則,而在MyTypeFilter自定義規則的實現類中,直接返回了false值,將所有的bean都排除了。

    我們可以在MyTypeFilter類中簡單的實現一個規則,例如,當前掃描的類名稱中包含有字符串Person,就返回true,否則返回false。此時,MyTypeFilter類中match()方法的實現如下所示。

        @Override
        public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
            //獲取當前類註解的信息
            AnnotationMetadata annotationMetadata = metadataReader.getAnnotationMetadata();
            //獲取當前正在掃描的類的信息
            ClassMetadata classMetadata = metadataReader.getClassMetadata();
            //獲取當前類的資源信息,例如:類的路徑等信息
            Resource resource = metadataReader.getResource();
            //獲取當前正在掃描的類名
            String className = classMetadata.getClassName();
            //打印當前正在掃描的類名
            System.out.println("-----> " + className);
            return className.contains("Person");
        }
    

    此時,在io.mykit.spring包下的所有類都會通過MyTypeFilter類的match()方法,來驗證類名是否包含Person,如果包含則返回true,否則返回false。

    我們再次運行SpringBeanTest類中的testComponentScanByAnnotation()方法進行測試,輸出的結果信息如下所示。

    -----> io.mykit.spring.test.SpringBeanTest
    -----> io.mykit.spring.bean.Person
    -----> io.mykit.spring.plugins.register.controller.PersonController
    -----> io.mykit.spring.plugins.register.dao.PersonDao
    -----> io.mykit.spring.plugins.register.filter.MyTypeFilter
    -----> io.mykit.spring.plugins.register.service.PersonService
    org.springframework.context.annotation.internalConfigurationAnnotationProcessor
    org.springframework.context.annotation.internalAutowiredAnnotationProcessor
    org.springframework.context.annotation.internalCommonAnnotationProcessor
    org.springframework.context.event.internalEventListenerProcessor
    org.springframework.context.event.internalEventListenerFactory
    personConfig
    person
    personController
    personDao
    personService
    

    此時,結果信息中輸出了使用@Repository、@Service、@Controller註解標註的組件名稱,分別為:personDao、personService和personController。

    好了,咱們今天就聊到這兒吧!別忘了給個在看和轉發,讓更多的人看到,一起學習一起進步!!

    項目工程源碼已經提交到GitHub:https://github.com/sunshinelyz/spring-annotation

    寫在最後

    如果覺得文章對你有點幫助,請微信搜索並關注「 冰河技術 」微信公眾號,跟冰河學習Spring註解驅動開發。公眾號回復“spring註解”關鍵字,領取Spring註解驅動開發核心知識圖,讓Spring註解驅動開發不再迷茫。

    本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

    ※別再煩惱如何寫文案,掌握八大原則!

    網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

    ※超省錢租車方案

    ※教你寫出一流的銷售文案?

    網頁設計最專業,超強功能平台可客製化

    ※產品缺大量曝光嗎?你需要的是一流包裝設計!

    台中搬家遵守搬運三大原則,讓您的家具不再被破壞!

  • ASP.NET Core中間件與HttpModule有何不同

    前言

    在ASP.NET Core中最大的更改之一是對Http請求管道的更改,在ASP.NET中我們了解HttpHandlerHttpModule但是到現在這些已經被替換為中間件那麼下面我們來看一下他們的不同處。

    HttpHandler

    Handlers處理基於擴展的特定請求,HttpHandlers作為進行運行,同時做到對ASP.NET響應請求。他是一個實現System.Web.IHttphandler接口的類。任何實現IHttpHandler接口的類都可以作為Http請求處理響應的目標程序。
    它提供了對文件特定的擴展名處理傳入請求,
    ASP.NET框架提供了一些默認的Http處理程序,最常見的處理程序是處理.aspx文件。下面提供了一些默認的處理程序。

    Handler Extension Description
    Page Handler .aspx handle normal WebPages
    User Control Handler .ascx handle Web user control pages
    Web Service Handler .asmx handle Web service pages
    Trace Handler trace.axd handle trace functionality

    創建一個自定義HttpHandler

    
    public class CustomHttpHandler:IHttpHandler
    {
        
        public bool IsReusable
        {
            //指定是否可以重用處理程序
            get {return true;}
        }
        
        public void ProcessRequest(HttpContext context)
        {
            //TODO
            throw new NotImplementedException();
        }
    }
    
    

    在web.config中添加配置項

    <!--IIS6或者IIS7經典模式-->  
      <system.web>  
        <httpHandlers>  
          <add name="mycustomhandler" path="*.aspx" verb="*" type="CustomHttpHandler"/>  
        </httpHandlers>  
      </system.web>  
       
    <!--IIS7集成模式-->  
      <system.webServer>  
        <handlers>  
           <add name="mycustomhandler" path="*.aspx" verb="*" type="CustomHttpHandler"/>  
        </handlers>  
      </system.webServer>  
    

    異步HttpHandlers

    異步的話需要繼承HttpTaskAsyncHandler類,HttpTaskAsyncHandler類實現了IHttpTaskAsyncHandlerIHttpHandler接口

    public class CustomHttpHandlerAsync:HttpTaskAsyncHandler
    {
        
        public override Task ProcessRequestAsync(HttpContext context)
        {
    
            throw new NotImplementedException();
        }
    }
    

    HttpModule

    下面是來自MSDN

    Modules are called before and after the handler executes. Modules enable developers to intercept, participate in, or modify each individual request. Modules implement the IHttpModule interface, which is located in the System.Web namespace.

    HttpModule類似過濾器,它是一個基於事件的,在應用程序發起到結束的整個生命周期中訪問事件

    自定義一個HttpModule

    public class CustomModule : IHttpModule
        {
            public void Dispose()
            {
                throw new NotImplementedException();
            }
    
            public void Init(HttpApplication context)
            {
                context.BeginRequest += new EventHandler(BeginRequest);
                context.EndRequest += new EventHandler(EndRequest);
            }
            void BeginRequest(object sender, EventArgs e)
            {
                ((HttpApplication)sender).Context.Response.Write("請求處理前");
            }
    
            void EndRequest(object sender, EventArgs e)
            {
                ((HttpApplication)sender).Context.Response.Write("請求處理結束后");
            }
        }
    
    
    

    web.config中配置

    <!--IIS6或者IIS7經典模式-->  
    <system.web>  
        <httpModules>  
          <add name="mycustommodule" type="CustomModule"/>  
        </httpModules>  
      </system.web>  
    <!--IIS7集成模式-->  
    <system.webServer>  
        <modules>  
          <add name="mycustommodule" type="CustomModule"/>  
        </modules>  
    </system.webServer>  
    

    中間件

    中間件可以視為集成到Http請求管道中的小型應用程序組件,它是ASP.NET中HttpModule和HttpHandler的結合,它可以處理身份驗證、日誌請求記錄等。

    中間件和HttpModule的相似處

    中間件和HttpMoudle都是可以處理每個請求,同時可以配置進行返回我們自己的定義。

    中間件和httpModule之間的區別

    HttpModule 中間件
    通過web.config或global.asax配置 在Startup文件中添加中間件
    執行順序無法控制,因為模塊順序主要是基於應用程序生命周期事件 可以控制執行內容和執行順序按照添加順序執行。
    請求和響應執行順序保持不變 響應中間件順序與請求順序相反
    HttpModules可以附件特定應用程序事件的代碼 中間件獨立於這些事件

    中間件示例

     public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
      {
          if (env.IsDevelopment())
          {
              app.UseDeveloperExceptionPage();
          }
    
          app.UseHttpsRedirection();
    
          app.UseRouting();
    
          app.UseAuthorization();
    
          app.UseEndpoints(endpoints =>
          {
              endpoints.MapControllers();
          });
      }
    

    在如上代碼片段中我們有一些中間件的添加,同時也有中間件的順序。

    Reference

    How ASP.NET Core 1.0 Middleware is different from HttpModule

    https://support.microsoft.com/en-us/help/307985/info-asp-net-http-modules-and-http-handlers-overview

    本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

    網頁設計最專業,超強功能平台可客製化

    ※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

    ※回頭車貨運收費標準

    ※推薦評價好的iphone維修中心

    ※教你寫出一流的銷售文案?

    台中搬家公司教你幾個打包小技巧,輕鬆整理裝箱!

    台中搬家公司費用怎麼算?

  • 詳解SpringBoot(2.3)應用製作Docker鏡像(官方方案)

    詳解SpringBoot(2.3)應用製作Docker鏡像(官方方案)

    關於《SpringBoot-2.3容器化技術》系列

    《SpringBoot-2.3容器化技術》系列,旨在和大家一起學習實踐2.3版本帶來的最新容器化技術,讓咱們的Java應用更加適應容器化環境,在雲計算時代依舊緊跟主流,保持競爭力;

    全系列文章分為主題和輔助兩部分,主題部分如下:

    1. 《體驗SpringBoot(2.3)應用製作Docker鏡像(官方方案)》;
    2. 《詳解SpringBoot(2.3)應用製作Docker鏡像(官方方案)》;
    3. 《掌握SpringBoot-2.3的容器探針:基礎篇》;
    4. 《掌握SpringBoot-2.3的容器探針:深入篇》;
    5. 《掌握SpringBoot-2.3的容器探針:實戰篇》;

    輔助部分是一些參考資料和備忘總結,如下:

    1. 《SpringBoot-2.3鏡像方案為什麼要做多個layer》;
    2. 《設置非root賬號不用sudo直接執行docker命令》;
    3. 《開發階段,將SpringBoot應用快速部署到K8S》;

    本篇簡介

    在前文,咱們快速體驗了官方推薦的docker鏡像製作方案,但也產生了幾個疑問:

    1. SpringBoot-2.3版本推薦的鏡像構建方案和舊版本比有什麼不同?
    2. pom.xml中spring-boot-maven-plugin插件新增的參數,到底做了什麼?
    3. Dockerfile中,java -Djarmode=layertools -jar application.jar extract這個操作啥意思?

    本篇的目標就是解答上述問題,在尋找答案的過程中不斷補全知識點,提升自己;

    關鍵知識點:鏡像layer

    前文多次提到的鏡像layer到底是什麼,為什麼會有多層layer?有必要先把這個知識點夯實了,請參考文章《SpringBoot-2.3鏡像方案為什麼要做多個layer》

    老版本SpringBoot的官方方案

    SpringBoot-2.2.0.RELEASE版本為例,官方文檔(
    https://docs.spring.io/spring-boot/docs/2.2.0.RELEASE/reference/pdf/spring-boot-reference.pdf)給出的做法如下:

    1. 將SpringBoot工程編譯構建,在target目錄得到jar;
    2. 在target目錄新建dependency文件夾;
    3. 將jar解壓到dependency文件夾;
    4. 編寫Dockerfile文件,內容如下:
    FROM openjdk:8-jdk-alpine
    VOLUME /tmp
    ARG DEPENDENCY=target/dependency
    COPY ${DEPENDENCY}/BOOT-INF/lib /app/lib
    COPY ${DEPENDENCY}/META-INF /app/META-INF
    COPY ${DEPENDENCY}/BOOT-INF/classes /app
    ENTRYPOINT ["java","-cp","app:app/lib/*","com.example.MyApplication"]
    
    1. 可見,官方推薦的做法是將整個jar文件解壓,在Dockerfile中多次用COPY命令分別複製,這樣做的好處顯而易見:多個layer,如果鏡像的新版本中只修改了應用代碼,那麼下載鏡像時只會下載/app這個layer,其他部分直接使用本地緩存,這是docker鏡像的常規優化手段;
    2. 上述方案有個小問題:麻煩!!!
    3. 於是2.3.0.RELEASE版本做了些優化,讓事情變得簡單些;

    2.3.0.RELEASE版本方案和舊版的區別

    2.3.0.RELEASE版本構建Docker的步驟如下:

    1. pom.xml中的spring-boot-maven-plugin插件增加一個配置項;
      2.編譯構建生成jar;
    2. 編寫Dockerfile,裏面用到了多階段構建(multi-stage builds),用工具從jar中提取拆分后,再多次執行COPY命令將拆分后的內容放入鏡像,達到多個layer的目的;

    因此,2.3.0.RELEASE版本和舊版本相比有如下變化:

    1. pom.xml中多了個參數;
    2. 構建好jar后,無需自己解壓jar;
    3. Dockefile內容不一樣,舊版是手動解壓jar,再在Dockerfile分別複製,2.3.0.RELEASE是通過java命令從jar中提取出各部分內容

    搞清楚了新舊版本的區別,咱們繼續研究下一個問題吧;

    pom.xml中spring-boot-maven-plugin插件新增的參數

    1. pring-boot-maven-plugin插件新增參數如下圖所示:

    2. 上述參數有啥用?我這邊編譯構建了兩次jar,第一次有上述參數,第二次沒有,將兩次生成的jar解壓后對比,發現用了上述參數后,生成的jar會多出下圖紅框中的兩個文件:

    1. 看看layers.idx文件的內容,如下圖:
    1. 上圖中的內容分別是什麼意思呢?官方已給出了詳細解釋,如下圖紅框:
    1. 綜上所述,layers.idx文件是個清單,裏面記錄了所有要被複制到鏡像中的信息,接下來看看如何使用layers.idx文件,這就涉及到jar包中新增的另一個文件:spring-boot-jarmode-layertools-2.3.0.RELEASE.jar

    spring-boot-jarmode-layertools工具

    1. 前面已經介紹過jar中除了layers.idx,還多了個文件:spring-boot-jarmode-layertools-2.3.0.RELEASE.jar ,來看看這個文件的用處;
    2. 進入工程的target目錄,這裏面是編譯后的jar文件(我這裏文件名為dockerlayerdemo-0.0.1-SNAPSHOT.jar),注意此時的spring-boot-maven-plugin插件是帶上了下圖紅框中的參數的:
    1. 執行以下命令:
    java -Djarmode=layertools -jar dockerlayerdemo-0.0.1-SNAPSHOT.jar list
    
    1. 得到結果如下圖所示,是layers.idx文件的內容:
    1. 來看看官方對這個layertools的解釋,list參數的作用上面我們已經體驗過了,重點是紅框中的extract參數,它的作用是從jar中提取構建鏡像所需的內容:
    1. 看到這裏,您是否想到了《體驗SpringBoot(2.3)應用製作Docker鏡像(官方方案)》中Dockerfile的內容,請看下圖的紅框和紅字,是否有種恍然大悟的感覺:jar構建生成清單layers.idx,Dockerfile中根據清單從jar提取文件放入鏡像:

    至此,三個問題都已經找到了答案,小結一下:

    SpringBoot-2.3.0.RELEASE推薦的鏡像構建方案和舊版本相比有什麼不同

    1. pom.xml中的spring-boot-maven-plugin插件增加一個配置項;
    2. 構建好jar后,舊版本要自己解壓jar,新版不需要;
    3. 新版本的jar中,多了個文件清單layers.idx和鏡像文件處理工具spring-boot-jarmode-layertools-2.3.0.RELEASE.jar
    4. 舊版的Dockefile內容:因為前面解壓好了,所有在Dockerfile里直接複製前面解壓的內容,這裏就有個風險:前一步解壓和當前複製的文件位置要保證一致;
    5. 新版的Dockerfile內容:使用工具spring-boot-jarmode-layertools-2.3.0.RELEASE.jar,根據的layers.idx內容從jar中提取文件,複製到鏡像中;
    6. 新版的Dockerfile中,由於使用了分階段構建,因此從jar提取文件的操作不會保存到鏡像的layer中;

    pom.xml中spring-boot-maven-plugin插件新增的參數,到底做了什麼

    spring-boot-maven-plugin插件新增的參數,使得編譯構建得到jar中多了兩個文件,如下圖所示:

    Dockerfile中,java -Djarmode=layertools -jar application.jar extract這個操作啥意思

    1. java -Djarmode=layertools -jar application.jar extract的作用是從jar中提取文件,這些文件是docker鏡像的一部分;
    2. 上述操作的參數是extract,另外還有兩個參數,官方解釋它們的作用如下:

    至此,問題已全部澄清,相信您對SpringBoot-2.3.0.RELEASE官方的鏡像構建方案也足夠了解了,最後是我根據自己的認識畫的流程圖,幫助您快速理解整個構建流程:

    歡迎訪問我的GitHub

    • 地址:https://github.com/zq2599/blog_demos
    • 內容:原創文章分類匯總,及配套源碼,涉及Java、Docker、K8S、DevOPS等

    歡迎關注我的公眾號:程序員欣宸

    https://github.com/zq2599/blog_demos

    本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

    ※產品缺大量曝光嗎?你需要的是一流包裝設計!

    ※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

    ※回頭車貨運收費標準

    ※推薦評價好的iphone維修中心

    ※超省錢租車方案

    台中搬家遵守搬運三大原則,讓您的家具不再被破壞!

    ※推薦台中搬家公司優質服務,可到府估價

  • 小師妹學JavaIO之:文件系統和WatchService

    小師妹學JavaIO之:文件系統和WatchService

    目錄

    • 簡介
    • 監控的痛點
    • WatchService和文件系統
    • WatchSerice的使用和實現本質
    • 總結

    簡介

    小師妹這次遇到了監控文件變化的問題,F師兄給小師妹介紹了JDK7 nio中引入的WatchService,沒想到又順道普及了一下文件系統的概念,萬萬沒想到。

    監控的痛點

    小師妹:F師兄最近你有沒有感覺到呼吸有點困難,后領有點涼颼颼的,說話有點不順暢的那種?

    沒有啊小師妹,你是不是秋衣穿反了?

    小師妹:不是的F師兄,我講的是心裏的感覺,那種莫須有的壓力,還有一絲悸動纏繞在心。

    別繞彎子了小師妹,是不是又遇到問題了。

    更多精彩內容且看:

    • 區塊鏈從入門到放棄系列教程-涵蓋密碼學,超級賬本,以太坊,Libra,比特幣等持續更新
    • Spring Boot 2.X系列教程:七天從無到有掌握Spring Boot-持續更新
    • Spring 5.X系列教程:滿足你對Spring5的一切想象-持續更新
    • java程序員從小工到專家成神之路(2020版)-持續更新中,附詳細文章教程

    更多內容請訪問www.flydean.com

    小師妹:還是F師兄懂我,這不上次的Properties文件用得非常上手,每次修改Properties文件都要重啟java應用程序,真的是很痛苦。有沒有什麼其他的辦法呢?

    辦法當然有,最基礎的辦法就是開一個線程定時去監控屬性文件的最後修改時間,如果修改了就重新加載,這樣不就行了。

    小師妹:寫線程啊,這麼麻煩,有沒有什麼更簡單的辦法呢?

    就知道你要這樣問,還好我準備的比較充分,今天給你介紹一個JDK7在nio中引入的類WatchService。

    WatchService和文件系統

    WatchService是JDK7在nio中引入的接口:

    監控的服務叫做WatchService,被監控的對象叫做Watchable:

    WatchKey register(WatchService watcher,
                          WatchEvent.Kind<?>[] events,
                          WatchEvent.Modifier... modifiers)
            throws IOException;
    WatchKey register(WatchService watcher, WatchEvent.Kind<?>... events)
            throws IOException;
    

    Watchable通過register將該對象的WatchEvent註冊到WatchService上。從此只要有WatchEvent發生在Watchable對象上,就會通知WatchService。

    WatchEvent有四種類型:

    1. ENTRY_CREATE 目標被創建
    2. ENTRY_DELETE 目標被刪除
    3. ENTRY_MODIFY 目標被修改
    4. OVERFLOW 一個特殊的Event,表示Event被放棄或者丟失

    register返回的WatchKey就是監聽到的WatchEvent的集合。

    現在來看WatchService的4個方法:

    1. close 關閉watchService
    2. poll 獲取下一個watchKey,如果沒有則返回null
    3. 帶時間參數的poll 在等待的一定時間內獲取下一個watchKey
    4. take 獲取下一個watchKey,如果沒有則一直等待

    小師妹:F師兄,那怎麼才能構建一個WatchService呢?

    上次文章中說的文件系統,小師妹還記得吧,FileSystem中就有一個獲取WatchService的方法:

    public abstract WatchService newWatchService() throws IOException;
    

    我們看下FileSystem的結構圖:

    在我的mac系統上,FileSystem可以分為三大類,UnixFileSystem,JrtFileSystem和ZipFileSystem。我猜在windows上面應該還有對應的windows相關的文件系統。小師妹你要是有興趣可以去看一下。

    小師妹:UnixFileSystem用來處理Unix下面的文件,ZipFileSystem用來處理zip文件。那JrtFileSystem是用來做什麼的?

    哎呀,這就又要扯遠了,為什麼每次問問題都要扯到天邊….

    從前當JDK還是9的時候,做了一個非常大的改動叫做模塊化JPMS(Java Platform Module System),這個Jrt就是為了給模塊化系統用的,我們來舉個例子:

    public void useJRTFileSystem(){
            String resource = "java/lang/Object.class";
            URL url = ClassLoader.getSystemResource(resource);
            log.info("{}",url);
        }
    

    上面一段代碼我們獲取到了Object這個class的url,我們看下如果是在JDK8中,輸出是什麼:

    jar:file:/Library/Java/JavaVirtualMachines/jdk1.8.0_171.jdk/Contents/Home/jre/lib/rt.jar!/java/lang/Object.class
    

    輸出結果是jar:file表示這個Object class是放在jar文件中的,後面是jar文件的路徑。

    如果是在JDK9之後:

    jrt:/java.base/java/lang/Object.class
    

    結果是jrt開頭的,java.base是模塊的名字,後面是Object的路徑。看起來是不是比傳統的jar路徑更加簡潔明了。

    有了文件系統,我們就可以在獲取系統默認的文件系統的同時,獲取到相應的WatchService:

    WatchService watchService = FileSystems.getDefault().newWatchService();
    

    WatchSerice的使用和實現本質

    小師妹:F師兄,WatchSerice是咋實現的呀?這麼神奇,為我們省了這麼多工作。

    其實JDK提供了這麼多類的目的就是為了不讓我們重複造輪子,之前跟你講監控文件的最簡單辦法就是開一個獨立的線程來監控文件變化嗎?其實…..WatchService就是這樣做的!

    PollingWatchService() {
            // TBD: Make the number of threads configurable
            scheduledExecutor = Executors
                .newSingleThreadScheduledExecutor(new ThreadFactory() {
                     @Override
                     public Thread newThread(Runnable r) {
                         Thread t = new Thread(null, r, "FileSystemWatcher", 0, false);
                         t.setDaemon(true);
                         return t;
                     }});
        }
    

    上面的方法就是生成WatchService的方法,小師妹看到沒有,它的本質就是開啟了一個daemon的線程,用來接收監控任務。

    下面看下怎麼把一個文件註冊到WatchService上面:

    private void startWatcher(String dirPath, String file) throws IOException {
            WatchService watchService = FileSystems.getDefault().newWatchService();
            Path path = Paths.get(dirPath);
            path.register(watchService, ENTRY_MODIFY);
    
            Runtime.getRuntime().addShutdownHook(new Thread(() -> {
                try {
                    watchService.close();
                } catch (IOException e) {
                    log.error(e.getMessage());
                }
            }));
    
            WatchKey key = null;
            while (true) {
                try {
                    key = watchService.take();
                    for (WatchEvent<?> event : key.pollEvents()) {
                        if (event.context().toString().equals(fileName)) {
                            loadConfig(dirPath + file);
                        }
                    }
                    boolean reset = key.reset();
                    if (!reset) {
                        log.info("該文件無法重置");
                        break;
                    }
                } catch (Exception e) {
                    log.error(e.getMessage());
                }
            }
        }
    

    上面的關鍵方法就是path.register,其中Path是一個Watchable對象。

    然後使用watchService.take來獲取生成的WatchEvent,最後根據WatchEvent來處理文件。

    總結

    道生一,一生二,二生三,三生萬物。一個簡簡單單的功能其實背後隱藏着…道德經,哦,不對,背後隱藏着道的哲學。

    本文的例子https://github.com/ddean2009/learn-java-io-nio

    本文作者:flydean程序那些事

    本文鏈接:http://www.flydean.com/java-io-file-watchservice/

    本文來源:flydean的博客

    歡迎關注我的公眾號:程序那些事,更多精彩等着您!

    本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

    ※回頭車貨運收費標準

    ※產品缺大量曝光嗎?你需要的是一流包裝設計!

    ※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

    ※推薦評價好的iphone維修中心

    ※教你寫出一流的銷售文案?

    台中搬家公司教你幾個打包小技巧,輕鬆整理裝箱!

    台中搬家遵守搬運三大原則,讓您的家具不再被破壞!