標籤: 台中搬家公司

  • 用雲開發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維修中心

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

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

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

  • 墨西哥穩定電力擺第一 新能源測試喊卡

    摘錄自2020年5月4日經濟日報報導

    在疫情蔓延下,墨西哥電力系統主管機關宣布,對乾淨能源新計畫的關鍵測試無限期喊卡,另採取措施,以提高全國電力系統的穩定性,但批評者擔心,這項措施將傷害再生能源業者。

     

    能源議題
    能源轉型
    國際新聞
    墨西哥
    乾淨能源
    武漢肺炎
    綠電
    疫情看氣候與能源
    新能源

    本站聲明:網站內容來源環境資訊中心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維修中心

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

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

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

  • STM32串口打印的那些知識

    STM32串口打印的那些知識

    常規打印方法

    在STM32的應用中,我們常常對printf進行重定向的方式來把打印信息printf到我們的串口助手。在MDK環境中,我們常常使用MicroLIB+fputc的方式實現串口打印功能,即:

    要實現fputc函數的原因是:printf函數依賴於fputc函數,重新實現fputc內部從串口發送數據即可間接地實現printf打印輸出數據到串口。

    不知道大家有沒有看過正點原子裸機串口相關的例程,他們的串口例程里不使用MicroLIB,而是使用標準庫+fputc的方式。相關代碼如:

    #if 1
    #pragma import(__use_no_semihosting)
    //標準庫需要的支持函數
    struct __FILE
    {
        int handle;
    };
    
    FILE __stdout;
    /**
     * @brief	定義_sys_exit()以避免使用半主機模式
     * @param	void
     * @return  void
     */
    void _sys_exit(int x)
    {
        x = x;
    }
    
    int fputc(int ch, FILE *f)
    {
        while((USART1->ISR & 0X40) == 0); //循環發送,直到發送完畢
    
        USART1->TDR = (u8) ch;
        return ch;
    }
    #endif
    

    關於這兩種方法的一些說明可以查看Mculover666兄的重定向printf函數到串口輸出的多種方法這篇文章。這篇文章中不僅包含上面的兩種方法,而且也包含着在GCC中使用標準庫重定向printf的方法。

    自己實現一個打印函數

    以上的幾種方法基本上是改造C庫的printf函數來實現串口打印的功能。其實我們也可以自己實現一個串口打印的功能。

    printf本身就是一個變參函數,其原型為:

    int printf (const char *__format, ...);
    

    所以,我們要重新封裝的一個串口打印函數自然也應該是一個變參函數。具體實現如下:

    1、基於STM32的HAL庫

    #define TX_BUF_LEN  256     /* 發送緩衝區容量,根據需要進行調整 */
    uint8_t TxBuf[TX_BUF_LEN];  /* 發送緩衝區                       */
    void MyPrintf(const char *__format, ...)
    {
      va_list ap;
      va_start(ap, __format);
      
      /* 清空發送緩衝區 */
      memset(TxBuf, 0x0, TX_BUF_LEN);
      
      /* 填充發送緩衝區 */
      vsnprintf((char*)TxBuf, TX_BUF_LEN, (const char *)__format, ap);
      va_end(ap);
      int len = strlen((const char*)TxBuf);
      
      /* 往串口發送數據 */
      HAL_UART_Transmit(&huart1, (uint8_t*)&TxBuf, len, 0xFFFF);
    }
    

    因為我們使用printf函數基本不使用其返回值,所以這裏直接用void類型了。自定義變參函數需要用到va_start、va_end等宏,需要包含頭文件stdarg.h。關於變參函數的一些學習可以查看網上的一些博文,如:

    https://www.cnblogs.com/wulei0630/p/9444062.html

    這裏我們使用的是STM32的HAL庫,其給我們提供HAL_UART_Transmit接口可以直接把整個發送緩衝區的內容給一次性發出去。

    2、基於STM32標準庫

    若是基於STM32的標準庫,就需要一字節一字節的循環發送出去,具體代碼如:

    #define TX_BUF_LEN  256     /* 發送緩衝區容量,根據需要進行調整 */
    uint8_t TxBuf[TX_BUF_LEN];  /* 發送緩衝區                       */
    void MyPrintf(const char *__format, ...)
    {
      va_list ap;
      va_start(ap, __format);
        
      /* 清空發送緩衝區 */
      memset(TxBuf, 0x0, TX_BUF_LEN);
        
      /* 填充發送緩衝區 */
      vsnprintf((char*)TxBuf, TX_BUF_LEN, (const char *)__format, ap);
      va_end(ap);
      int len = strlen((const char*)TxBuf);
      
      /* 往串口發送數據 */
      for (int i = 0; i < len; i++)
      {
    	while(USART_GetFlagStatus(USART1, USART_FLAG_TC)==RESET);    
    	USART_SendData(USART1, TxBuf[i]);
      }
    }
    

    測試結果:

    我們也可以使用我們的MyPrintf函數按照上一篇文章:======的方式封裝一個宏打印函數:

    以上就是我們自定義方式實現的一種串口打印函數。

    但是,我想說:對於串口打印的使用,我們沒必要自己創建一個打印函數。看到這,是不是有人想要打我了。。。。看了半天,你卻跟我說沒必要用。。。

    哈哈,別急,我們不應用在串口打印調試方面,那可以用在其它方面呀。

    (1)應用一:

    比如最近我在實際應用中:我們的MCU跑的是我們老大自己寫的一個小的操作系統+我們公司自己開發的上位機。我們MCU端與上位機使用的是串口通訊,MCU往上位機發送的數據有兩種類型,一種是HEX格式數據,一種是字符串數據。

    但是我們下位機的這兩種數據,在通過串口發送之前都得統一把數據封包交給那個系統通信任務,然後再由通信任務發出去。在這裏,就不能用printf了。老大也針對他的這個系統實現了一個deb_printf函數用於打印調試。

    但是,那個函數既複雜又很雞肋,稍微複雜一點的數據就打印不出來了。因此我利用上面的思路給它新封裝了一個打印調試函數,很好用,完美地兼容了老大的那個系統。具體代碼就不分享了,大體代碼、思路如上。

    (2)應用二:

    我們在使用串口與ESP8266模塊通訊時,可利用類似這樣的方式封裝一個發送數據的函數,這個函數的使用可以像printf一樣簡單。可以以很簡單的方式把數據透傳至服務端,比如我以前的畢設中就有這麼應用:

    以上就是本次的分享,如有錯誤,歡迎指出!謝謝

    我的個人博客:https://www.lizhengnian.cn/

    我的微信公眾號:嵌入式大雜燴

    我的CSDN博客:https://blog.csdn.net/zhengnianli

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

    【其他文章推薦】

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

    ※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

    ※回頭車貨運收費標準

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

    ※超省錢租車方案

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

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

  • 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維修中心

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

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

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

  • 基於 abp vNext 和 .NET Core 開發博客項目 – 博客接口實戰篇(五)

    基於 abp vNext 和 .NET Core 開發博客項目 – 博客接口實戰篇(五)

    系列文章

    1. 基於 abp vNext 和 .NET Core 開發博客項目 – 使用 abp cli 搭建項目
    2. 基於 abp vNext 和 .NET Core 開發博客項目 – 給項目瘦身,讓它跑起來
    3. 基於 abp vNext 和 .NET Core 開發博客項目 – 完善與美化,Swagger登場
    4. 基於 abp vNext 和 .NET Core 開發博客項目 – 數據訪問和代碼優先
    5. 基於 abp vNext 和 .NET Core 開發博客項目 – 自定義倉儲之增刪改查
    6. 基於 abp vNext 和 .NET Core 開發博客項目 – 統一規範API,包裝返回模型
    7. 基於 abp vNext 和 .NET Core 開發博客項目 – 再說Swagger,分組、描述、小綠鎖
    8. 基於 abp vNext 和 .NET Core 開發博客項目 – 接入GitHub,用JWT保護你的API
    9. 基於 abp vNext 和 .NET Core 開發博客項目 – 異常處理和日誌記錄
    10. 基於 abp vNext 和 .NET Core 開發博客項目 – 使用Redis緩存數據
    11. 基於 abp vNext 和 .NET Core 開發博客項目 – 集成Hangfire實現定時任務處理
    12. 基於 abp vNext 和 .NET Core 開發博客項目 – 用AutoMapper搞定對象映射
    13. 基於 abp vNext 和 .NET Core 開發博客項目 – 定時任務最佳實戰(一)
    14. 基於 abp vNext 和 .NET Core 開發博客項目 – 定時任務最佳實戰(二)
    15. 基於 abp vNext 和 .NET Core 開發博客項目 – 定時任務最佳實戰(三)
    16. 基於 abp vNext 和 .NET Core 開發博客項目 – 博客接口實戰篇(一)
    17. 基於 abp vNext 和 .NET Core 開發博客項目 – 博客接口實戰篇(二)
    18. 基於 abp vNext 和 .NET Core 開發博客項目 – 博客接口實戰篇(三)
    19. 基於 abp vNext 和 .NET Core 開發博客項目 – 博客接口實戰篇(四)

    上篇文章完成了文章詳情頁數據查詢和清除緩存的功能。

    本篇繼續完成分類、標籤、友情鏈接的後台操作接口,還是那句話,這些純CRUD的內容,建議還是自己動手完成比較好,本篇將不再啰嗦,直接貼代碼,以供參考。

    分類

    添加接口:查詢分類列表QueryCategoriesForAdminAsync()、新增分類InsertCategoryAsync(...)、更新分類UpdateCategoryAsync(...)、刪除分類DeleteCategoryAsync(...)

    #region Categories
    
    /// <summary>
    /// 查詢分類列表
    /// </summary>
    /// <returns></returns>
    Task<ServiceResult<IEnumerable<QueryCategoryForAdminDto>>> QueryCategoriesForAdminAsync();
    
    /// <summary>
    /// 新增分類
    /// </summary>
    /// <param name="input"></param>
    /// <returns></returns>
    Task<ServiceResult> InsertCategoryAsync(EditCategoryInput input);
    
    /// <summary>
    /// 更新分類
    /// </summary>
    /// <param name="id"></param>
    /// <param name="input"></param>
    /// <returns></returns>
    Task<ServiceResult> UpdateCategoryAsync(int id, EditCategoryInput input);
    
    /// <summary>
    /// 刪除分類
    /// </summary>
    /// <param name="id"></param>
    /// <returns></returns>
    Task<ServiceResult> DeleteCategoryAsync(int id);
    
    #endregion Categories
    

    查詢分類列表需要返回的模型類QueryCategoryForAdminDto.cs

    //QueryCategoryForAdminDto.cs
    namespace Meowv.Blog.Application.Contracts.Blog
    {
        public class QueryCategoryForAdminDto : QueryCategoryDto
        {
            /// <summary>
            /// 主鍵
            /// </summary>
            public int Id { get; set; }
        }
    }
    

    新增分類和更新分類需要的輸入參數EditCategoryInput.cs,直接繼承CategoryDto即可。

    //EditCategoryInput.cs
    namespace Meowv.Blog.Application.Contracts.Blog.Params
    {
        public class EditCategoryInput : CategoryDto
        {
        }
    }
    

    分別實現這幾個接口。

    /// <summary>
    /// 查詢分類列表
    /// </summary>
    /// <returns></returns>
    public async Task<ServiceResult<IEnumerable<QueryCategoryForAdminDto>>> QueryCategoriesForAdminAsync()
    {
        var result = new ServiceResult<IEnumerable<QueryCategoryForAdminDto>>();
    
        var posts = await _postRepository.GetListAsync();
    
        var categories = _categoryRepository.GetListAsync().Result.Select(x => new QueryCategoryForAdminDto
        {
            Id = x.Id,
            CategoryName = x.CategoryName,
            DisplayName = x.DisplayName,
            Count = posts.Count(p => p.CategoryId == x.Id)
        });
    
        result.IsSuccess(categories);
        return result;
    }
    
    /// <summary>
    /// 新增分類
    /// </summary>
    /// <param name="input"></param>
    /// <returns></returns>
    public async Task<ServiceResult> InsertCategoryAsync(EditCategoryInput input)
    {
        var result = new ServiceResult();
    
        var category = ObjectMapper.Map<EditCategoryInput, Category>(input);
        await _categoryRepository.InsertAsync(category);
    
        result.IsSuccess(ResponseText.INSERT_SUCCESS);
        return result;
    }
    

    這裏需要一條AutoMapper配置,將EditCategoryInput轉換為Category,忽略Id字段。

    CreateMap<EditCategoryInput, Category>().ForMember(x => x.Id, opt => opt.Ignore());
    
    /// <summary>
    /// 更新分類
    /// </summary>
    /// <param name="id"></param>
    /// <param name="input"></param>
    /// <returns></returns>
    public async Task<ServiceResult> UpdateCategoryAsync(int id, EditCategoryInput input)
    {
        var result = new ServiceResult();
    
        var category = await _categoryRepository.GetAsync(id);
        category.CategoryName = input.CategoryName;
        category.DisplayName = input.DisplayName;
    
        await _categoryRepository.UpdateAsync(category);
    
        result.IsSuccess(ResponseText.UPDATE_SUCCESS);
        return result;
    }
    
    /// <summary>
    /// 刪除分類
    /// </summary>
    /// <param name="id"></param>
    /// <returns></returns>
    public async Task<ServiceResult> DeleteCategoryAsync(int id)
    {
        var result = new ServiceResult();
    
        var category = await _categoryRepository.FindAsync(id);
        if (null == category)
        {
            result.IsFailed(ResponseText.WHAT_NOT_EXIST.FormatWith("Id", id));
            return result;
        }
    
        await _categoryRepository.DeleteAsync(id);
    
        result.IsSuccess(ResponseText.DELETE_SUCCESS);
        return result;
    }
    

    BlogController.Admin.cs中添加接口。

    #region Categories
    
    /// <summary>
    /// 查詢分類列表
    /// </summary>
    /// <returns></returns>
    [HttpGet]
    [Authorize]
    [Route("admin/categories")]
    [ApiExplorerSettings(GroupName = Grouping.GroupName_v2)]
    public async Task<ServiceResult<IEnumerable<QueryCategoryForAdminDto>>> QueryCategoriesForAdminAsync()
    {
        return await _blogService.QueryCategoriesForAdminAsync();
    }
    
    /// <summary>
    /// 新增分類
    /// </summary>
    /// <param name="input"></param>
    /// <returns></returns>
    [HttpPost]
    [Authorize]
    [Route("category")]
    [ApiExplorerSettings(GroupName = Grouping.GroupName_v2)]
    public async Task<ServiceResult> InsertCategoryAsync([FromBody] EditCategoryInput input)
    {
        return await _blogService.InsertCategoryAsync(input);
    }
    
    /// <summary>
    /// 更新分類
    /// </summary>
    /// <param name="id"></param>
    /// <param name="input"></param>
    /// <returns></returns>
    [HttpPut]
    [Authorize]
    [Route("category")]
    [ApiExplorerSettings(GroupName = Grouping.GroupName_v2)]
    public async Task<ServiceResult> UpdateCategoryAsync([Required] int id, [FromBody] EditCategoryInput input)
    {
        return await _blogService.UpdateCategoryAsync(id, input);
    }
    
    /// <summary>
    /// 刪除分類
    /// </summary>
    /// <param name="id"></param>
    /// <returns></returns>
    [HttpDelete]
    [Authorize]
    [Route("category")]
    [ApiExplorerSettings(GroupName = Grouping.GroupName_v2)]
    public async Task<ServiceResult> DeleteCategoryAsync([Required] int id)
    {
        return await _blogService.DeleteCategoryAsync(id);
    }
    
    #endregion Categories
    

    標籤

    添加接口:查詢標籤列表QueryTagsForAdminAsync()、新增標籤InsertTagAsync(...)、更新標籤UpdateTagAsync(...)、刪除標籤DeleteTagAsync(...)

    #region Tags
    
    /// <summary>
    /// 查詢標籤列表
    /// </summary>
    /// <returns></returns>
    Task<ServiceResult<IEnumerable<QueryTagForAdminDto>>> QueryTagsForAdminAsync();
    
    /// <summary>
    /// 新增標籤
    /// </summary>
    /// <param name="input"></param>
    /// <returns></returns>
    Task<ServiceResult> InsertTagAsync(EditTagInput input);
    
    /// <summary>
    /// 更新標籤
    /// </summary>
    /// <param name="id"></param>
    /// <param name="input"></param>
    /// <returns></returns>
    Task<ServiceResult> UpdateTagAsync(int id, EditTagInput input);
    
    /// <summary>
    /// 刪除標籤
    /// </summary>
    /// <param name="id"></param>
    /// <returns></returns>
    Task<ServiceResult> DeleteTagAsync(int id);
    
    #endregion Tags
    

    查詢標籤列表需要返回的模型類QueryTagForAdminDto.cs

    //QueryTagForAdminDto.cs
    namespace Meowv.Blog.Application.Contracts.Blog
    {
        public class QueryTagForAdminDto : QueryTagDto
        {
            /// <summary>
            /// 主鍵
            /// </summary>
            public int Id { get; set; }
        }
    }
    

    新增標籤和更新標籤需要的輸入參數EditTagInput.cs,直接繼承TagDto即可。

    //EditTagInput.cs
    namespace Meowv.Blog.Application.Contracts.Blog.Params
    {
        public class EditTagInput : TagDto
        {
        }
    }
    

    分別實現這幾個接口。

    /// <summary>
    /// 查詢標籤列表
    /// </summary>
    /// <returns></returns>
    public async Task<ServiceResult<IEnumerable<QueryTagForAdminDto>>> QueryTagsForAdminAsync()
    {
        var result = new ServiceResult<IEnumerable<QueryTagForAdminDto>>();
    
        var post_tags = await _postTagRepository.GetListAsync();
    
        var tags = _tagRepository.GetListAsync().Result.Select(x => new QueryTagForAdminDto
        {
            Id = x.Id,
            TagName = x.TagName,
            DisplayName = x.DisplayName,
            Count = post_tags.Count(p => p.TagId == x.Id)
        });
    
        result.IsSuccess(tags);
        return result;
    }
    
    /// <summary>
    /// 新增標籤
    /// </summary>
    /// <param name="dto"></param>
    /// <returns></returns>
    public async Task<ServiceResult> InsertTagAsync(EditTagInput input)
    {
        var result = new ServiceResult();
    
        var tag = ObjectMapper.Map<EditTagInput, Tag>(input);
        await _tagRepository.InsertAsync(tag);
    
        result.IsSuccess(ResponseText.INSERT_SUCCESS);
        return result;
    }
    

    這裏需要一條AutoMapper配置,將EditCategoryInput轉換為Tag,忽略Id字段。

    CreateMap<EditTagInput, Tag>().ForMember(x => x.Id, opt => opt.Ignore());
    
    /// <summary>
    /// 更新標籤
    /// </summary>
    /// <param name="id"></param>
    /// <param name="dto"></param>
    /// <returns></returns>
    public async Task<ServiceResult> UpdateTagAsync(int id, EditTagInput input)
    {
        var result = new ServiceResult();
    
        var tag = await _tagRepository.GetAsync(id);
        tag.TagName = input.TagName;
        tag.DisplayName = input.DisplayName;
    
        await _tagRepository.UpdateAsync(tag);
    
        result.IsSuccess(ResponseText.UPDATE_SUCCESS);
        return result;
    }
    
    /// <summary>
    /// 刪除標籤
    /// </summary>
    /// <param name="id"></param>
    /// <returns></returns>
    public async Task<ServiceResult> DeleteTagAsync(int id)
    {
        var result = new ServiceResult();
    
        var tag = await _tagRepository.FindAsync(id);
        if (null == tag)
        {
            result.IsFailed(ResponseText.WHAT_NOT_EXIST.FormatWith("Id", id));
            return result;
        }
    
        await _tagRepository.DeleteAsync(id);
        await _postTagRepository.DeleteAsync(x => x.TagId == id);
    
        result.IsSuccess(ResponseText.DELETE_SUCCESS);
        return result;
    }
    

    BlogController.Admin.cs中添加接口。

    #region Tags
    
    /// <summary>
    /// 查詢標籤列表
    /// </summary>
    /// <returns></returns>
    [HttpGet]
    [Authorize]
    [Route("admin/tags")]
    [ApiExplorerSettings(GroupName = Grouping.GroupName_v2)]
    public async Task<ServiceResult<IEnumerable<QueryTagForAdminDto>>> QueryTagsForAdminAsync()
    {
        return await _blogService.QueryTagsForAdminAsync();
    }
    
    /// <summary>
    /// 新增標籤
    /// </summary>
    /// <param name="input"></param>
    /// <returns></returns>
    [HttpPost]
    [Authorize]
    [Route("tag")]
    [ApiExplorerSettings(GroupName = Grouping.GroupName_v2)]
    public async Task<ServiceResult> InsertTagAsync([FromBody] EditTagInput input)
    {
        return await _blogService.InsertTagAsync(input);
    }
    
    /// <summary>
    /// 更新標籤
    /// </summary>
    /// <param name="id"></param>
    /// <param name="input"></param>
    /// <returns></returns>
    [HttpPut]
    [Authorize]
    [Route("tag")]
    [ApiExplorerSettings(GroupName = Grouping.GroupName_v2)]
    public async Task<ServiceResult> UpdateTagAsync([Required] int id, [FromBody] EditTagInput input)
    {
        return await _blogService.UpdateTagAsync(id, input);
    }
    
    /// <summary>
    /// 刪除標籤
    /// </summary>
    /// <param name="id"></param>
    /// <returns></returns>
    [HttpDelete]
    [Authorize]
    [Route("tag")]
    [ApiExplorerSettings(GroupName = Grouping.GroupName_v2)]
    public async Task<ServiceResult> DeleteTagAsync([Required] int id)
    {
        return await _blogService.DeleteTagAsync(id);
    }
    
    #endregion Tags
    

    友鏈

    添加接口:查詢友鏈列表QueryFriendLinksForAdminAsync()、新增友鏈InsertFriendLinkAsync(...)、更新友鏈UpdateFriendLinkAsync(...)、刪除友鏈DeleteFriendLinkAsync(...)

    #region FriendLinks
    
    /// <summary>
    /// 查詢友鏈列表
    /// </summary>
    /// <returns></returns>
    Task<ServiceResult<IEnumerable<QueryFriendLinkForAdminDto>>> QueryFriendLinksForAdminAsync();
    
    /// <summary>
    /// 新增友鏈
    /// </summary>
    /// <param name="input"></param>
    /// <returns></returns>
    Task<ServiceResult> InsertFriendLinkAsync(EditFriendLinkInput input);
    
    /// <summary>
    /// 更新友鏈
    /// </summary>
    /// <param name="id"></param>
    /// <param name="input"></param>
    /// <returns></returns>
    Task<ServiceResult> UpdateFriendLinkAsync(int id, EditFriendLinkInput input);
    
    /// <summary>
    /// 刪除友鏈
    /// </summary>
    /// <param name="id"></param>
    /// <returns></returns>
    Task<ServiceResult> DeleteFriendLinkAsync(int id);
    
    #endregion FriendLinks
    

    查詢友鏈列表需要返回的模型類QueryFriendLinkForAdminDto.cs

    //QueryFriendLinkForAdminDto.cs
    namespace Meowv.Blog.Application.Contracts.Blog
    {
        public class QueryFriendLinkForAdminDto : FriendLinkDto
        {
            /// <summary>
            /// 主鍵
            /// </summary>
            public int Id { get; set; }
        }
    }
    

    新增友鏈和更新友鏈需要的輸入參數EditFriendLinkInput.cs,直接繼承FriendLinkDto即可。

    //EditFriendLinkInput .cs
    namespace Meowv.Blog.Application.Contracts.Blog.Params
    {
        public class EditFriendLinkInput : FriendLinkDto
        {
        }
    }
    

    分別實現這幾個接口。

    /// <summary>
    /// 查詢友鏈列表
    /// </summary>
    /// <returns></returns>
    public async Task<ServiceResult<IEnumerable<QueryFriendLinkForAdminDto>>> QueryFriendLinksForAdminAsync()
    {
        var result = new ServiceResult<IEnumerable<QueryFriendLinkForAdminDto>>();
    
        var friendLinks = await _friendLinksRepository.GetListAsync();
    
        var dto = ObjectMapper.Map<List<FriendLink>, IEnumerable<QueryFriendLinkForAdminDto>>(friendLinks);
    
        result.IsSuccess(dto);
        return result;
    }
    
    /// <summary>
    /// 新增友鏈
    /// </summary>
    /// <param name="input"></param>
    /// <returns></returns>
    public async Task<ServiceResult> InsertFriendLinkAsync(EditFriendLinkInput input)
    {
        var result = new ServiceResult();
    
        var friendLink = ObjectMapper.Map<EditFriendLinkInput, FriendLink>(input);
        await _friendLinksRepository.InsertAsync(friendLink);
    
        // 執行清除緩存操作
        await _blogCacheService.RemoveAsync(CachePrefix.Blog_FriendLink);
    
        result.IsSuccess(ResponseText.INSERT_SUCCESS);
        return result;
    }
    
    /// <summary>
    /// 更新友鏈
    /// </summary>
    /// <param name="id"></param>
    /// <param name="input"></param>
    /// <returns></returns>
    public async Task<ServiceResult> UpdateFriendLinkAsync(int id, EditFriendLinkInput input)
    {
        var result = new ServiceResult();
    
        var friendLink = await _friendLinksRepository.GetAsync(id);
        friendLink.Title = input.Title;
        friendLink.LinkUrl = input.LinkUrl;
    
        await _friendLinksRepository.UpdateAsync(friendLink);
    
        // 執行清除緩存操作
        await _blogCacheService.RemoveAsync(CachePrefix.Blog_FriendLink);
    
        result.IsSuccess(ResponseText.UPDATE_SUCCESS);
        return result;
    }
    
    /// <summary>
    /// 刪除友鏈
    /// </summary>
    /// <param name="id"></param>
    /// <returns></returns>
    public async Task<ServiceResult> DeleteFriendLinkAsync(int id)
    {
        var result = new ServiceResult();
    
        var friendLink = await _friendLinksRepository.FindAsync(id);
        if (null == friendLink)
        {
            result.IsFailed(ResponseText.WHAT_NOT_EXIST.FormatWith("Id", id));
            return result;
        }
    
        await _friendLinksRepository.DeleteAsync(id);
    
        // 執行清除緩存操作
        await _blogCacheService.RemoveAsync(CachePrefix.Blog_FriendLink);
    
        result.IsSuccess(ResponseText.DELETE_SUCCESS);
        return result;
    }
    

    其中查詢友鏈列表和新增友鏈中有兩條AutoMapper配置。

    CreateMap<FriendLink, QueryFriendLinkForAdminDto>();
    
    CreateMap<EditFriendLinkInput, FriendLink>().ForMember(x => x.Id, opt => opt.Ignore());
    

    BlogController.Admin.cs中添加接口。

    #region FriendLinks
    
    /// <summary>
    /// 查詢友鏈列表
    /// </summary>
    /// <returns></returns>
    [HttpGet]
    [Authorize]
    [Route("admin/friendlinks")]
    [ApiExplorerSettings(GroupName = Grouping.GroupName_v2)]
    public async Task<ServiceResult<IEnumerable<QueryFriendLinkForAdminDto>>> QueryFriendLinksForAdminAsync()
    {
        return await _blogService.QueryFriendLinksForAdminAsync();
    }
    
    /// <summary>
    /// 新增友鏈
    /// </summary>
    /// <param name="input"></param>
    /// <returns></returns>
    [HttpPost]
    [Authorize]
    [Route("friendlink")]
    [ApiExplorerSettings(GroupName = Grouping.GroupName_v2)]
    public async Task<ServiceResult> InsertFriendLinkAsync([FromBody] EditFriendLinkInput input)
    {
        return await _blogService.InsertFriendLinkAsync(input);
    }
    
    /// <summary>
    /// 更新友鏈
    /// </summary>
    /// <param name="id"></param>
    /// <param name="input"></param>
    /// <returns></returns>
    [HttpPut]
    [Authorize]
    [Route("friendlink")]
    [ApiExplorerSettings(GroupName = Grouping.GroupName_v2)]
    public async Task<ServiceResult> UpdateFriendLinkAsync([Required] int id, [FromBody] EditFriendLinkInput input)
    {
        return await _blogService.UpdateFriendLinkAsync(id, input);
    }
    
    /// <summary>
    /// 刪除友鏈
    /// </summary>
    /// <param name="id"></param>
    /// <returns></returns>
    [HttpDelete]
    [Authorize]
    [Route("friendlink")]
    [ApiExplorerSettings(GroupName = Grouping.GroupName_v2)]
    public async Task<ServiceResult> DeleteFriendLinkAsync([Required] int id)
    {
        return await _blogService.DeleteFriendLinkAsync(id);
    }
    
    #endregion
    

    Next

    截止本篇,基於 abp vNext 和 .NET Core 開發博客項目 系列的後台API部分便全部開發完成了。

    本博客項目系列是我一邊寫代碼一邊記錄后的成果,並不是開發完成后再拿出來寫的,涉及到東西也不是很多,對於新手入門來說應該是夠了的,如果你從中有所收穫請多多轉發分享。

    在此,希望大家可以關注一下我的微信公眾號:『阿星Plus』,文章將會首發在公眾號中。

    現在有了API,大家可以選擇自己熟悉的方式去開發前端界面,比如目前我博客的線上版本就是用的 ASP.NET Core Web ,感興趣的可以去 release 分支查看。

    關於前端部分,看到有人呼籲vue,說實話前端技術不是很厲害,本職主要是後端開發,可能達不到預期效果。

    所以我準備入坑 Blazor ,接下來就現學現賣吧,一起學習一起做項目一起進步,加油

    開源地址:https://github.com/Meowv/Blog/tree/blog_tutorial

    搭配下方課程學習更佳 ↓ ↓ ↓

    http://gk.link/a/10iQ7

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

    【其他文章推薦】

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

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

    ※回頭車貨運收費標準

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

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

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

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