<dfn id="hx5t3"><strike id="hx5t3"><em id="hx5t3"></em></strike></dfn>

    <thead id="hx5t3"></thead><nobr id="hx5t3"><font id="hx5t3"><rp id="hx5t3"></rp></font></nobr>

    <listing id="hx5t3"></listing>

    <var id="hx5t3"></var>
    <big id="hx5t3"></big>

      
      

      <output id="hx5t3"><ruby id="hx5t3"></ruby></output>
      <menuitem id="hx5t3"><dfn id="hx5t3"></dfn></menuitem>

      <big id="hx5t3"></big>

        健兒

        健兒 查看完整檔案

        杭州編輯北方民族大學  |  化工機械 編輯公司  |  前端搬磚 編輯填寫個人主網站
        編輯

        掌握好原生js。

        個人動態

        健兒 發布了文章 · 2月24日

        從圖解來理解vue設計的雙向綁定架構圖

        先看別人繪制的圖
        image
        我的手繪圖
        image
        其實一共是3+1+view渲染模塊
        3:
        observer:監聽器 vue2.0 用Object.defineProty、vue3.0 用proxy
        watcher:訂閱器
        compile:解析器
        Dep:訂閱器收集器
        view:渲染dom

        通過看圖,先找有幾條通路
        a、observer-->Dep-->watcher-->view
        b、compile-->watcher-->Dep
        c、compile-->view

        c路徑是最簡單的,c主要實現是解析指令如v-model等,初始化到view視圖層。

        b路徑compile-->watcher 形成初始化的訂閱器,watcher-->Dep訂閱器被添加到訂閱器容易里,這里實際是在observer 階段eg:vue2.0的Object.defineProty的get方法的時候被添加的,但為何沒有observer這個回路呢?設計者用了一個兼容的寫法,強制執行了data監聽器的observer方法里Object.defineProty的get方法。

        a路徑observer-->Dep-->watcher 是通知變化,此階段eg: vue2.0的Object.defineProty的set方法觸發的,最后watcher-->view 變化反應到view層

        不難發現,到能到底view層的就只有complie和watcher,一個是模板初始化到view層,一個是update后更新到view層。不管是哪條路徑到達view層實際都是對dom進行原生事件的操作(watcher是在第三個參數回調函數里更改view)
        如:

        node.textContent = typeof value == 'undefined' ? '' : value;

        具體看看watcher大致實現:

        function Watcher(vm, exp, cb) { 
              this.cb = cb; 
              this.vm = vm;  // vm data對象
              this.exp = exp;  // exp data對象的鍵key
              this.value = this.get();  // 將自己添加到訂閱器的操作
        }
         
        Watcher.prototype = {
            update: function() {
               this.run();
            },
            run: function() { 
               var value = this.vm.data[this.exp]; 
               var oldVal = this.value; 
               if (value !== oldVal) { 
                 this.value = value; 
                 this.cb.call(this.vm, value, oldVal);
               }
            },
            get: function() {
                Dep.target = this;  // 緩存自己
                var value = this.vm.data[this.exp]  // 強制執行監聽器里的get函數
                Dep.target = null;  // 釋放自己
                return value;
            }
        };

        理解了這個回路,然后再逐一去理解回路實現的邏輯就很容易理解和記憶了。

        查看原文

        贊 0 收藏 0 評論 0

        健兒 發布了文章 · 2月24日

        徹底搞懂--水平垂直居中

        水平垂直居中是面試??嫉沫h節
        今天就一步一步分析下是怎么做到的,以前呢多少有點死記硬背的感覺,沒真正去梳理過。

        先看看整個實現的流程圖片吧。
        image

        1、當left:50%如圖1
        2、當top:50% 如圖2
        3、要想呈現如圖3的情況,有兩種實現方案
        (1)方案1
        在已知目標對象寬高的情況下,可以通過設置margin-left:-寬/2
        margin-top:-高/2就可以。
        (2)方案2
        在未知目標對象寬高情況下,用transform:translate(-50%. -50%)
        即可。
        為何translate(-50%. -50%)這樣設置就能實現呢?
        因為translate(x,y,z)移動是相對自己的中心位置來偏移的,
        2圖可看出,目標對象中心位置正好距離視圖中心位置x,y相差-50%*目標對象的寬或高,所以這種方法可以實現。
        這里就稍微對過程的實現步驟和translate做了進一步的解釋。

        查看原文

        贊 0 收藏 0 評論 0

        健兒 贊了文章 · 2月23日

        js節流和防抖的區別

        在監聽某類事件時,如監聽元素滾動或表單input事件時,處理函數在短時間內被頻繁調用。
        如果處理函數還需要調用后臺接口,那么可能上次還沒有響應,下一次請求又來了。這樣無意中增加了服務器的壓力,而且對用戶來說,也會造成卡頓,這不是用戶和程序員想要的。
        節流和防抖大家應該都用過,但一點我不明白,節流和防抖都是延遲執行,那么它們的區別在哪里?

        先看看節流的實現
        // 節流
        var canNext = true;
        function throttle () {
            if(!canNext) {
                return;
            }
        
            canNext = false;
            setTimeout(function () {
                console.log('節流方法執行了')
                canNext = true;
            }, 200)
        };

        canNext變量作為狀態記錄者,當它的值為 false 時,表示上一次調用正在執行,直接跳出本次調用,直到上一次的執行完畢,把true 賦給canNext,這時如果有調用,會執行這次調用。

        下面我們再看看防抖的實現
        // 防抖
        var timer = null;
        function debounce () {
            clearTimeout(timer);
            timer = setTimeout(function() {
                console.log('防抖方法執行了')
            }, 200)
        }

        第一次 timer變量保存著這個定時器的標識符(用于關閉當前定時器),如果在200毫秒內調用多次,只會執行最后一次

        在以勻速滾動時,兩個方法執行結果如圖

        防抖

        image

        節流

        image

        大家應該看出區別了,雖然都是延時執行,但兩個方法在執行次數上還是有區別。
        節流為什么會出現多次執行?我們再看看代碼

        // 節流
        var canNext = true;
            function throttle () {
                if(!canNext) {
                    return;
                }
        
                canNext = false;
                setTimeout(function () {
                    console.log('節流方法執行了')
                    canNext = true;
                }, 200)
            };

        在密集調用時,節流方法相當于每200毫秒執行一次,再看看防抖。

        // 防抖
        var timer = null;
        function debounce () {
            clearTimeout(timer);
            timer = setTimeout(function() {
                console.log('防抖方法執行了')
            }, 200)
        }

        防抖方法在200以內調用,總是執行最后一次的調用,~~~~這下我總算明白了。

        那么它們各自的使用場景有哪些呢?
        防抖

        • 短信驗證碼
        • 提交表單
        • resize 事件
        • input 事件(當然也可以用節流,實現實時關鍵字查找)

        節流

        • scroll 事件,單位時間后計算一次滾動位置
        • input 事件(上面提到過)
        • 播放事件,計算進度條

        暫時只能想到這些,未盡之處,望大家指正。

        查看原文

        贊 3 收藏 1 評論 0

        健兒 發布了文章 · 2月4日

        node 插件 中間件 學習記錄(一)文件模塊

        1、用node語法實現一個插件,可以讀文件夾里所有的文件,并修改文件。
        分析:
        “所有”肯定要遞歸
        “讀”和“寫”肯定用node的fs模塊

        貼代碼分析:

        const fs = require("fs");
        const path = require("path");
        
        let readFile = (filePath = "./dist") => {
          // 讀取文件夾
          fs.readdir(filePath, (err, files) => {
            if (err) {
              console.log("不存在目錄");
            }
            console.log("files", files);
        
            files.forEach((file) => {
              let fileDir = path.join(filePath, file)
              console.log('fileDir', fileDir)
              fs.stat(fileDir, (err, status) => {
                 if (err) {
                   console.warn('獲取文件stats失敗');
                   return
                 }
                 // 判斷是否是文件
                 let isFile = status.isFile();
                 // 判斷是否是文件夾
                 let isDir = status.isDirectory();
                 if (isFile) {
                    // 讀取文件
                    let content = fs.readFileSync(fileDir, 'utf-8')
                    if (content.match(/<!-- built files will be auto injected -->/)) {
                      let str = '<!-- built files will be auto injected -->\n<script data-original="a.js"></script>\n<script data-original="b.js"></script>'
                      let data = content.replace('<!-- built files will be auto injected -->', str)
                      console.log('data', data)
                      // 寫入文件
                      fs.writeFile(fileDir, data, 'utf-8', (err) => {
                        // 第二個參數 是被插入的內容
                        if (err) {
                          throw err;
                        }
                      })
                    }
                 }
                 if (isDir) {
                     console.log('----文件---')
                     // 如果文件里還是文件,則遞歸調用函數
                     readFile(fileDir)
                 }
              })
            });
          });
        };
        
        readFile();

        總結:
        所用fs模塊,api有

        readdir、stat、readFileSync、writeFile
        查看原文

        贊 0 收藏 0 評論 0

        健兒 贊了文章 · 1月21日

        NestJs學習之路(2)- 配置文件

        前言

        Typeorm使用章節中,我們將數據庫連接信息直接寫在app.module.ts中,但在實際開發中,會根據環境變量來加載不同配置項,也不會將一些配置信息直接寫在代碼中,為此nest官方提供了@nestjs/config這個模塊來實現上述需求。

        一、.env文件加載環境變量

        @nestjs/config依賴于dotenv,可以通過key=value形式配置環境變量,項目會默認加載根目錄下的.env文件,我們只需在app.module.ts中引入ConfigModule,再使用ConfigModule.forRoot()方法即可。

        .env

        DB_HOST=localhost
        DB_PORT=3306
        DB_USERNAME=root
        DB_PASSWORD=root
        DB_NAME=blogs

        src/app.module.ts

        import { Module } from '@nestjs/common';
        import { ConfigModule } from '@nestjs/config';
        
        @Module({
          imports: [
            ConfigModule.forRoot(),
            TypeOrmModule.forRootAsync({
              useFactory: () => ({
                type: 'mysql',
                host: process.env.DB_HOST,
                port: process.env.DB_PORT,
                username: process.env.DB_USERNAME,
                password: process.env.DB_PASSWORD,
                database: process.env.DB_NAME,
                timezone: 'UTC',
                charset: 'utf8mb4',
                entities: ['./**/*.entity.js'],
                synchronize: true,
                logging: true,
            })})],
          // 也可使用自定義.env文件
          /*
          imports: [ConfigModule.forRoot({
            envFilePath: '.development.env',
          })];
          */
        })
        export class AppModule {}

        二、加載自定義配置文件

        上面我們把數據庫的信息都寫在了環境變量中,再通過process.env.key形式去獲取,但可能存在下面一種需求:config文件夾下存在development.ts,test.ts,production.ts三個配置文件,需根據NODE_ENV加載不同配置文件;此外需要一種擴展性更好的配置文件格式支持,比如.ts,.js文件,這時可以將數據庫配置項等作為一個對象,而不僅僅是key=value的格式,同時也能附加注釋、說明等內容。下面我們在config文件夾中增加配置文件,app.module.ts文件再作修改,數據庫信息等移至配置文件中。

        增加 src/config/development.ts

        export default {
            // 端口
            port:  parseInt(process.env.PORT, 10) || 3000,
            // 是否開啟swagger
            enableSwagger: true,
            // 數據庫配置
            DATABASE_CONFIG: {
                type: 'mysql',
                host: 'localhost',
                port: 3306,
                username: 'root',
                password: '',
                database: 'blogs',
                timezone: 'UTC',
                charset: 'utf8mb4',
                entities: ['./**/*.entity.js'],
                synchronize: true,
                logging: true,
            },
        };

        增加 src/config/index.ts

        import developmentConfig from './development';
        import testConfig from './test';
        import productionConfig from './production';
        
        const configs = {
            development: developmentConfig,
            test: testConfig,
            production: productionConfig,
        };
        const env = process.env.NODE_ENV || 'development';
        
        export default () => configs[env];
        

        修改 src/app.module.ts

        import { Module } from '@nestjs/common';
        import { AppController } from './app.controller';
        import { AppService } from './app.service';
        import { TypeOrmModule } from '@nestjs/typeorm';
        import { ArticleModule } from './controllers/admin/article/article.module';
        import { ConfigModule, ConfigService } from '@nestjs/config';
        import customConfig from './config';
        
        @Module({
          imports: [
              ConfigModule.forRoot({
                isGlobal: true, // 作用于全局
                load: [customConfig], // 加載自定義配置項
              }),
              TypeOrmModule.forRootAsync({
                imports: [ConfigModule], // 數據庫配置項依賴于ConfigModule,需在此引入
                useFactory: (configService: ConfigService) => configService.get('DATABASE_CONFIG'),
                inject: [ConfigService], // 記得注入服務,不然useFactory函數中獲取不到ConfigService
            }),
            ArticleModule,
          ],
          controllers: [AppController],
          providers: [AppService],
        })
        export class AppModule {}

        根據官方文檔configuration和自己的需求,目前通過上面一種形式實現,如果您有更好的實踐方式歡迎多多分享。

        查看原文

        贊 7 收藏 4 評論 0

        健兒 發布了文章 · 1月16日

        再次理解異步setTimeout 方法

        先看一段代碼:

        console.log('-',new Date().getTime())
            for(let i = 0;i<100;i++){
                setTimeout(function(){
                  console.log('exeute');
                  },100);
            }
            console.log('i',new Date().getTime())

        執行結果:

        - 1610778978900
        i 1610778978901
        100 exeute

        看第一個log跟第二個log時間只相差了1ms,時間短到幾乎間隔為0.

        再看一段代碼:

        console.log('-',new Date().getTime())
            for(let i = 0;i<100000;i++){
                setTimeout(function(){
                  console.log('exeute');
                  },100);
            }
            console.log('i',new Date().getTime())

        執行結果

        - 1610779277393
        i 1610779278304
        4466 exeute
        
        過一會再去看,期間程序一直在執行,電腦還差點卡死了...
        - 1610779277393
        i 1610779278304
        13188 exeute
        不出意外的情況下,你會最終看見打印結果逐漸增加到10萬。
        
        規律感覺如下:
        一開始很快的增長到上千上萬(幾乎是同時進行的)
        然后逐步遞增到10萬。

        第二段代碼log時間相差了911ms。
        從eventLoop異步原理,先進先出,后進后出邏輯來看確實結果如我們所愿。但為何在第一段代碼里,我們會同時看見打印了100次exeute呢?

        log和時間time的曲線似乎呈現如下:
        image
        在前面很短相同的時間里,同時打印excute,隨后time逐漸增加。
        其實在A這個區塊內,也是滿足先進先出后進后出的邏輯的,一段近乎水平
        的增長曲線(可以理解成前半部分,比如i取100個的時候,“幾乎”是同時log,這里無窮小的時間可以理解成0,所以在A這個區域,我們肉眼看見的先進先出沒觀察到

        這里用到setTimeout的知識點:
        1、待加入隊列的消息和一個時間值(可選,默認為 0)。這個時間值代表了消息被實際加入到隊列的最小延遲時間。如果隊列中沒有其它消息并且棧為空,在這段延遲時間過去之后,消息會被馬上處理(能看肉眼看到的一致)。但是,如果有其它消息,setTimeout 消息必須等待其它消息處理完(所以為何10萬級for循環log的時候,我們會源源不斷的看見被打印)。因此第二個參數僅僅表示最少延遲時間,而非確切的等待時間

        查看原文

        贊 0 收藏 0 評論 0

        健兒 發布了文章 · 1月15日

        記錄arr.map 和arr.foreach 遇到的回調函數(異步和同步問題)的坑

        大致問題是:
        通過map或者foreach循環的回調函數操作數組,回調函數內部有ajax異步函數,通過await同步的寫法來調用的。console.log打印出數組跟實際渲染到view層的頁面始終不一致。
        后來改寫成for循環就好了。

        所以總結出來,基礎知識,在理解map和foreach這個api不夠深刻,也對await 關鍵詞的使用場景理解不透徹(雖然是for循環,但作用域函數可以找到頂層的函數作用域,所以不會報錯)

        貼出代碼:

        錯誤代碼

        adBannerList.map(async (item) => {
          // 異步函數 轉同步的寫法
          let res = await this.getAliyunAds();
          // 異步函數執行成功后,改變此數組的值
          item.imageUrl = res.imgUrl;
          item.linkUrl = res.actUrl;
        })
        // map 循環結束后,再執行雙向綁定
        this.setData({
          bannerList: adBannerList,
        });
        console.log('bannerList', this.data.bannerList)

        期望結果是map循環結束后,才會執行雙向綁定setData函數。
        實際結果是:還未等map循環完(還未等內部的await 后面的異步函數執行完),setData 就執行了。

        先搞清下async-await:
        后來查詢資料:await是異步轉同步的寫法,但并不會阻塞主線程的同步進行的代碼,只會阻塞異步代碼
        但看如下代碼:

        async function timeout(ms) {
          await new Promise((resolve) => {
            setTimeout(resolve, ms);
          });
        }
         
        async function asyncPrint(value, ms) {
          await timeout(ms);
          console.log(value);
        }
         
        asyncPrint('hello world', 3000);
        // hello world (3s后被打印)

        所以:

        在async語句里,同步和await異步代碼不會以Eventloop事件方式進行區分(也就不存在先執行同步 后執行異步的操作),帶有await的異步和同步代碼統一被視為同步代碼,更準確的說是以同步式阻塞方式 從上至下依次執行

        回到正題:
        forEach、map這樣的高級循環遍歷函數,在循環的同時,是不能更改內部item對象的(map更改后,返回的是新數組,forEach是原數組被更改)。
        推薦看forEach 和for循環的區別,這篇文章
        for和forEach的區別
        此時console.log 數組所看見的結果,跟view渲染呈現的結果不一致。
        這里需要去深入理解下,引用類型和簡單類型的console.log,以及console.log的機制。
        推薦看這篇文章:console.log遇見的坑

        正確代碼:

        for(let i = 0;i < adBannerList.length; i++) {
          let item = adBannerList[i]
          // 這里await關鍵詞可以用是因為for循環內部沒函數作用域,
          //所以會向上找函數作用域,只要有async就可以。
          let res = await this.getAliyunAds();
          item.imageUrl = res.imgUrl;
          item.linkUrl = res.actUrl;
        }
        
        this.setData({
          bannerList: adBannerList,
        });

        知識點總結:
        1.map和foreach(應該還有更多的類似函數)的回調函數是一個同步函數,非異步函數。

        需要升入的知識點:
        1、console.log的機制。尤其是針對引用類型
        2、類型map和foreach 這樣的循環函數機制。

        至于為何for循環可以解決我的業務問題,我其實還沒完全搞懂。
        其實查詢資料發現,阮一峰在介紹async-await的時候,就舉例
        說過類似的問題。只是在運用在實際工作中,翻車后才能真正掌握其知識點,并嘗試去理解為什么?
        如果有讀者知道怎么解釋for和forEach 為何前者可以滿足我的業務,歡迎留言。

        摘抄阮一峰一章節的例子:
        下面的代碼也可以解決我的問題,其實無非就是接口都請求成功后,按順序更改我的目標數組罷了。

        async function dbFuc(db) {
          let docs = [{}, {}];
          let promises = docs.map((doc) => db.post(doc));
        
          let results = await Promise.all(promises);
          console.log(results);
        }
        // 或者使用下面的寫法 
        async function dbFuc(db) {
          let docs = [{}, {}];
          let promises = docs.map((doc) => db.post(doc));
        
          let results = [];
          for (let promise of promises) {
            results.push(await promise);
          }
          console.log(results);
        }

        參考文獻:
        阮一峰async-await方法
        解釋async-await

        查看原文

        贊 1 收藏 1 評論 0

        健兒 關注了專欄 · 2020-12-17

        有贊美業前端團隊

        關注 2793

        健兒 收藏了文章 · 2020-11-04

        【koa】koa2+mongoose,接口開發實戰第一槍

        初始化項目

        使用 koa-generator 腳手架工具

        npm install koa-generator -g #全局安裝
        koa2 demo #創建demo項目
        cd demo && npm install #安裝依賴

        默認生成項目結構如下

        clipboard.png

        修改配置

        用腳手架生成的項目,默認是服務器渲染,即響應的是html視圖。而我們要開發接口,響應的是json數據。所以要刪除渲染視圖的代碼。增加響應json的配置。

        首先刪除views文件夾,接下來就是修改 app.js

        1. 刪除視圖配置

        以下是要刪除的代碼

        const views = require('koa-views')
        app.use(views(__dirname + '/views', {
          extension: 'pug'
        }))

        2. 修改路由的注冊方式,通過遍歷routes文件夾讀取文件

        const fs =  require('fs')
        fs.readdirSync('./routes').forEach(route=> {
            let api = require(`./routes/${route}`)
            app.use(api.routes(), api.allowedMethods())
        })

        3. 添加jwt認證,同時過濾不需要認證的路由,如獲取token

        const jwt = require('koa-jwt')
        app.use(jwt({ secret: 'yourstr' }).unless({
            path: [
                /^\/$/, /\/token/, /\/wechat/,
                { url: /\/papers/, methods: ['GET'] }
            ]
        }));

        4. 全局錯誤捕獲并響應

        // error
        app.use(async (ctx, next) => {
            try {
                await next()
            } catch(err) {
                ctx.status = err.statusCode || err.status || 500;
                ctx.body = err.message
                ctx.app.emit('error', err, ctx);
            }
        })

        5. 跨域處理

        當接口發布到線上,前端通過ajax請求時,會報跨域的錯誤。koa2使用koa2-cors這個庫非常方便的實現了跨域配置,使用起來也很簡單

        const cors = require('koa2-cors');
        app.use(cors());

        連接數據庫

        我們使用mongodb數據庫,在koa2中使用mongoose這個庫來管理整個數據庫的操作。

        1. 創建配置文件

        根目錄下新建config文件夾,新建mongo.js

        // config/mongo.js
        const mongoose = require('mongoose').set('debug', true);
        const options = {
            autoReconnect: true
        }
        
        // username 數據庫用戶名
        // password 數據庫密碼
        // localhost 數據庫ip
        // dbname 數據庫名稱
        const url = 'mongodb://username:password@localhost:27017/dbname'
        
        module.exports = {
            connect: ()=> {            
                mongoose.connect(url,options)
                let db = mongoose.connection
                db.on('error', console.error.bind(console, '連接錯誤:'));
                db.once('open', ()=> {
                    console.log('mongodb connect suucess');
                })
            }
        }

        然后在app.js中引入

        const mongoConf = require('./config/mongo');
        mongoConf.connect();

        連接數據庫完成!

        2. Schema 和 Model

        mongoose有兩個核心概念,SchemaModel。Schema是預先定義的數據庫模型骨架,定義集合存儲的數據結構;Model是依據Schema構造生成的模型,有數據庫操作的能力。

        下面是案例,新建一個用戶集合:

        根目錄下新建models文件夾,新建users.js

        const mongoose = require('mongoose');
        mongoose.Promise = global.Promise;
        
        const Schema = mongoose.Schema
        const ObjectId = mongoose.Types.ObjectId
        
        // Schema
        const usersSchema = new Schema({
            _id: { type: ObjectId }, // 默認生成,不加也可以
            username: { type: String, required: [true,'username不能為空'] },
            sex: { type: String, enum: ['man','woman'], required: [true,'sex不能為空'] },
            age: { type: Number required: [true,'age不能為空'] },
            avatar: { type: String, default: '' }
        })
        
        // Model
        const users = mongoose.model('users',usersSchema);
        
        module.exports = users;

        這樣,一個用戶集合的model就建好了,接下來在路由中直接引入這個model,就可以進行數據庫操作了

        注冊路由

        按照app.js的配置,routes文件夾下每一個文件都要注冊成為一個路由

        1. 基本結構

        一個基本的, RESTful風格的用戶增查改刪接口路由文件如下,包含了增查改刪的通用寫法

        const router = require('koa-router')()
        
        router.prefix('/users')
        
        router.get('/', function (ctx, next) {
            ctx.body = '獲取所有用戶'
        })
        
        router.get('/:id', function (ctx, next) {
            let id = ctx.params.id
            ctx.body = '獲取單個用戶'
        })
        
        router.post('/', function (ctx, next) {
            let data = ctx.request.body
            ctx.body = '新增用戶'
        })
        
        router.put('/:id', function (ctx, next) {
            let id = ctx.params.id
            ctx.body = '修改用戶信息'
        })
        
        router.delete('/:id', function (ctx, next) {
            let id = ctx.params.id
            ctx.body = '刪除用戶'
        })
        
        module.exports = router
        

        2. 請求參數驗證

        任何一次請求接口傳來的參數,首先要在最前面做一次參數驗證。如果請求參數不合法,那么直接返回錯誤信息,無需繼續走下面的業務邏輯。

        參數驗證,推薦使用 Joi 這個庫,它是 hapijs 內置的參數驗證模塊,也是使用Schema定義數據結構。

        比如創建用戶接口,對用戶傳來的數據進行驗證

        const Joi = require('joi')
        
        router.post('/', function (ctx, next) {
            let data = ctx.request.body
            let schema = Joi.object().keys({
                username: Joi.string().required(),
                sex: Joi.string().required(),
                age: Joi.number().required()
            })
            let result = Joi.validate(data, schema);
            if(result.error) {
                return ctx.body = result.error.details
            }
            let reqdata = result.value;  #經過驗證后的數據
        })

        這樣就完成了一個簡單的驗證,具體驗證請看joi文檔

        3. 數據庫操作

        之前已經創建了users的model,現在我們可以直接在路由文件中使用該模塊,進行數據庫操作

        // 引入上一步創建的model
        const Users = require('../models/users');
        
        router.get('/', async (ctx, next)=> {
            ctx.body = await Users.find()
        })
        
        router.get('/:_id', async (ctx, next)=> {
            let _id = ctx.params._id
            ctx.body = await Users.findOne({_id})
        })
        
        // 創建用戶
        router.post('/', async (ctx, next)=> {
            let data = ctx.request.body
            ctx.body = await Users.create(data)
        })
        
        router.put('/:_id', async (ctx, next)=> {
            let _id = ctx.params._id
            let data = ctx.request.body
            ctx.body = await Users.update({_id}, { $set: data }, {
                runValidators: true
            })
        })
        
        router.delete('/:_id', async (ctx, next)=> {
            let _id = ctx.params._id
            ctx.body = await Users.deleteOne({_id})
        })
        

        這是基本的增查改刪的操作。這里用到了koa2最大的亮點,就是async,await異步處理方案。只要await 后面返回的是 promise,就可以直接使用。這比在 express 框架上使用 then 方法處理要優雅的多的多。

        上一步講的請求參數驗證,是第一層驗證。其實在model操作中,數據進入數據庫時,也會進行一層驗證,就是mongoose的Schema定義的數據類型和字段,如果不匹配,則會拒絕寫入并返回錯誤。這就是mongoose的好處之一,可以防止寫入不規范的數據。

        這里不做mongoose的詳解,附上文檔地址:mongoose

        運行與部署

        1. pm2

        應用啟動文件是 www/bin,所以我們可以通過 node www/bin 開啟動應用,默認是9000端口。
        這樣啟動會有一個問題,就是在修改代碼之后,程序不會自動運行最新的代碼,需要手動斷開重新啟動。

        pm2的誕生就是用來解決這個問題。他是一個nodejs應用程序管理器,通過pm2啟動nodejs應用,開啟監聽模式,它就可以自動監聽文件變化重啟應用。

        當然pm2的作用還有很多,首先啟動應用:

        pm2 start --name koa2 bin/www --watch

        結果如下:

        clipboard.png

        然后可以刪除或重啟:

        pm2 list  # 查看進程列表
        pm2 restart koa2 # 重啟名稱為koa2的應用
        pm2 delete koa2 # 刪除名稱為koa2的進程
        pm2 logs koa2 # 查看名稱為koa2的進程日志

        2. nginx配置

        koa2應用在linux服務端部署,首先需要pm2來啟動應用。但是koa2項目啟動一般不使用80端口,所以在線上部署時,還需要nginx做反向代理以及域名配置。

        配置如下:

        server {
            listen 80;
            server_name api.xxxxx.com;
            location / {
                proxy_pass http://127.0.0.1:9000;
                #兼容scoket.io
                proxy_http_version 1.1;
                proxy_set_header Upgrade $http_upgrade;
                proxy_set_header Connection "upgrade";
                proxy_set_header Host $host;
            }
        }

        這樣訪問 api.xxxxx.com 就能訪問koa2的應用了!

        如果是https,需要修改下配置:

        server {
            listen 80;
            server_name api.xxxxx.com;
            rewrite ^(.*) https://$host$1 permanent;
        }
        server {
            listen 443 ssl;
            server_name api.ruidoc.com;
        
            ssl on;
            ssl_certificate 1_api.xxxxx.com_bundle.crt; #ssl證書
            ssl_certificate_key 2_api.xxxxx.com.key;
        
            location / {
                proxy_pass http://127.0.0.1:9000;
                #兼容scoket.io
                proxy_http_version 1.1;
                proxy_set_header Upgrade $http_upgrade;
                proxy_set_header Connection "upgrade";
                proxy_set_header Host $host;
            }
        }

        此時你可以上傳你的應用了!

        3. 快速上傳源碼

        一般上傳源碼的時候,可能會用ftp服務之類的。如果是Mac用戶,那么可以配置一條快捷命令,直接上傳,方便省事

        rsync -avz ./* root@1.23.45.67:/home/api

        root 是服務器用戶名;
        1.23.45.67 是服務器IP地址;
        /home/api 是源碼在服務器的存放路徑

        然后輸入服務器密碼,就可以快速上傳了,而且只會上傳修改過的源碼,非??焖?。

        還可以將命令配置到package.json文件的scripts下,如

        "scripts": {
            "upload": "rsync -avz ./* root@1.23.45.67:/home/api"
        }

        此時你可以這樣上傳:

        npm run upload

        這樣配置上傳效率非常高,一起試試吧!

        本文由 楊成功 原創,更多原創文章關注 楊成功的全棧之路
        查看原文

        健兒 贊了文章 · 2020-11-04

        【koa】koa2+mongoose,接口開發實戰第一槍

        初始化項目

        使用 koa-generator 腳手架工具

        npm install koa-generator -g #全局安裝
        koa2 demo #創建demo項目
        cd demo && npm install #安裝依賴

        默認生成項目結構如下

        clipboard.png

        修改配置

        用腳手架生成的項目,默認是服務器渲染,即響應的是html視圖。而我們要開發接口,響應的是json數據。所以要刪除渲染視圖的代碼。增加響應json的配置。

        首先刪除views文件夾,接下來就是修改 app.js

        1. 刪除視圖配置

        以下是要刪除的代碼

        const views = require('koa-views')
        app.use(views(__dirname + '/views', {
          extension: 'pug'
        }))

        2. 修改路由的注冊方式,通過遍歷routes文件夾讀取文件

        const fs =  require('fs')
        fs.readdirSync('./routes').forEach(route=> {
            let api = require(`./routes/${route}`)
            app.use(api.routes(), api.allowedMethods())
        })

        3. 添加jwt認證,同時過濾不需要認證的路由,如獲取token

        const jwt = require('koa-jwt')
        app.use(jwt({ secret: 'yourstr' }).unless({
            path: [
                /^\/$/, /\/token/, /\/wechat/,
                { url: /\/papers/, methods: ['GET'] }
            ]
        }));

        4. 全局錯誤捕獲并響應

        // error
        app.use(async (ctx, next) => {
            try {
                await next()
            } catch(err) {
                ctx.status = err.statusCode || err.status || 500;
                ctx.body = err.message
                ctx.app.emit('error', err, ctx);
            }
        })

        5. 跨域處理

        當接口發布到線上,前端通過ajax請求時,會報跨域的錯誤。koa2使用koa2-cors這個庫非常方便的實現了跨域配置,使用起來也很簡單

        const cors = require('koa2-cors');
        app.use(cors());

        連接數據庫

        我們使用mongodb數據庫,在koa2中使用mongoose這個庫來管理整個數據庫的操作。

        1. 創建配置文件

        根目錄下新建config文件夾,新建mongo.js

        // config/mongo.js
        const mongoose = require('mongoose').set('debug', true);
        const options = {
            autoReconnect: true
        }
        
        // username 數據庫用戶名
        // password 數據庫密碼
        // localhost 數據庫ip
        // dbname 數據庫名稱
        const url = 'mongodb://username:password@localhost:27017/dbname'
        
        module.exports = {
            connect: ()=> {            
                mongoose.connect(url,options)
                let db = mongoose.connection
                db.on('error', console.error.bind(console, '連接錯誤:'));
                db.once('open', ()=> {
                    console.log('mongodb connect suucess');
                })
            }
        }

        然后在app.js中引入

        const mongoConf = require('./config/mongo');
        mongoConf.connect();

        連接數據庫完成!

        2. Schema 和 Model

        mongoose有兩個核心概念,SchemaModel。Schema是預先定義的數據庫模型骨架,定義集合存儲的數據結構;Model是依據Schema構造生成的模型,有數據庫操作的能力。

        下面是案例,新建一個用戶集合:

        根目錄下新建models文件夾,新建users.js

        const mongoose = require('mongoose');
        mongoose.Promise = global.Promise;
        
        const Schema = mongoose.Schema
        const ObjectId = mongoose.Types.ObjectId
        
        // Schema
        const usersSchema = new Schema({
            _id: { type: ObjectId }, // 默認生成,不加也可以
            username: { type: String, required: [true,'username不能為空'] },
            sex: { type: String, enum: ['man','woman'], required: [true,'sex不能為空'] },
            age: { type: Number required: [true,'age不能為空'] },
            avatar: { type: String, default: '' }
        })
        
        // Model
        const users = mongoose.model('users',usersSchema);
        
        module.exports = users;

        這樣,一個用戶集合的model就建好了,接下來在路由中直接引入這個model,就可以進行數據庫操作了

        注冊路由

        按照app.js的配置,routes文件夾下每一個文件都要注冊成為一個路由

        1. 基本結構

        一個基本的, RESTful風格的用戶增查改刪接口路由文件如下,包含了增查改刪的通用寫法

        const router = require('koa-router')()
        
        router.prefix('/users')
        
        router.get('/', function (ctx, next) {
            ctx.body = '獲取所有用戶'
        })
        
        router.get('/:id', function (ctx, next) {
            let id = ctx.params.id
            ctx.body = '獲取單個用戶'
        })
        
        router.post('/', function (ctx, next) {
            let data = ctx.request.body
            ctx.body = '新增用戶'
        })
        
        router.put('/:id', function (ctx, next) {
            let id = ctx.params.id
            ctx.body = '修改用戶信息'
        })
        
        router.delete('/:id', function (ctx, next) {
            let id = ctx.params.id
            ctx.body = '刪除用戶'
        })
        
        module.exports = router
        

        2. 請求參數驗證

        任何一次請求接口傳來的參數,首先要在最前面做一次參數驗證。如果請求參數不合法,那么直接返回錯誤信息,無需繼續走下面的業務邏輯。

        參數驗證,推薦使用 Joi 這個庫,它是 hapijs 內置的參數驗證模塊,也是使用Schema定義數據結構。

        比如創建用戶接口,對用戶傳來的數據進行驗證

        const Joi = require('joi')
        
        router.post('/', function (ctx, next) {
            let data = ctx.request.body
            let schema = Joi.object().keys({
                username: Joi.string().required(),
                sex: Joi.string().required(),
                age: Joi.number().required()
            })
            let result = Joi.validate(data, schema);
            if(result.error) {
                return ctx.body = result.error.details
            }
            let reqdata = result.value;  #經過驗證后的數據
        })

        這樣就完成了一個簡單的驗證,具體驗證請看joi文檔

        3. 數據庫操作

        之前已經創建了users的model,現在我們可以直接在路由文件中使用該模塊,進行數據庫操作

        // 引入上一步創建的model
        const Users = require('../models/users');
        
        router.get('/', async (ctx, next)=> {
            ctx.body = await Users.find()
        })
        
        router.get('/:_id', async (ctx, next)=> {
            let _id = ctx.params._id
            ctx.body = await Users.findOne({_id})
        })
        
        // 創建用戶
        router.post('/', async (ctx, next)=> {
            let data = ctx.request.body
            ctx.body = await Users.create(data)
        })
        
        router.put('/:_id', async (ctx, next)=> {
            let _id = ctx.params._id
            let data = ctx.request.body
            ctx.body = await Users.update({_id}, { $set: data }, {
                runValidators: true
            })
        })
        
        router.delete('/:_id', async (ctx, next)=> {
            let _id = ctx.params._id
            ctx.body = await Users.deleteOne({_id})
        })
        

        這是基本的增查改刪的操作。這里用到了koa2最大的亮點,就是async,await異步處理方案。只要await 后面返回的是 promise,就可以直接使用。這比在 express 框架上使用 then 方法處理要優雅的多的多。

        上一步講的請求參數驗證,是第一層驗證。其實在model操作中,數據進入數據庫時,也會進行一層驗證,就是mongoose的Schema定義的數據類型和字段,如果不匹配,則會拒絕寫入并返回錯誤。這就是mongoose的好處之一,可以防止寫入不規范的數據。

        這里不做mongoose的詳解,附上文檔地址:mongoose

        運行與部署

        1. pm2

        應用啟動文件是 www/bin,所以我們可以通過 node www/bin 開啟動應用,默認是9000端口。
        這樣啟動會有一個問題,就是在修改代碼之后,程序不會自動運行最新的代碼,需要手動斷開重新啟動。

        pm2的誕生就是用來解決這個問題。他是一個nodejs應用程序管理器,通過pm2啟動nodejs應用,開啟監聽模式,它就可以自動監聽文件變化重啟應用。

        當然pm2的作用還有很多,首先啟動應用:

        pm2 start --name koa2 bin/www --watch

        結果如下:

        clipboard.png

        然后可以刪除或重啟:

        pm2 list  # 查看進程列表
        pm2 restart koa2 # 重啟名稱為koa2的應用
        pm2 delete koa2 # 刪除名稱為koa2的進程
        pm2 logs koa2 # 查看名稱為koa2的進程日志

        2. nginx配置

        koa2應用在linux服務端部署,首先需要pm2來啟動應用。但是koa2項目啟動一般不使用80端口,所以在線上部署時,還需要nginx做反向代理以及域名配置。

        配置如下:

        server {
            listen 80;
            server_name api.xxxxx.com;
            location / {
                proxy_pass http://127.0.0.1:9000;
                #兼容scoket.io
                proxy_http_version 1.1;
                proxy_set_header Upgrade $http_upgrade;
                proxy_set_header Connection "upgrade";
                proxy_set_header Host $host;
            }
        }

        這樣訪問 api.xxxxx.com 就能訪問koa2的應用了!

        如果是https,需要修改下配置:

        server {
            listen 80;
            server_name api.xxxxx.com;
            rewrite ^(.*) https://$host$1 permanent;
        }
        server {
            listen 443 ssl;
            server_name api.ruidoc.com;
        
            ssl on;
            ssl_certificate 1_api.xxxxx.com_bundle.crt; #ssl證書
            ssl_certificate_key 2_api.xxxxx.com.key;
        
            location / {
                proxy_pass http://127.0.0.1:9000;
                #兼容scoket.io
                proxy_http_version 1.1;
                proxy_set_header Upgrade $http_upgrade;
                proxy_set_header Connection "upgrade";
                proxy_set_header Host $host;
            }
        }

        此時你可以上傳你的應用了!

        3. 快速上傳源碼

        一般上傳源碼的時候,可能會用ftp服務之類的。如果是Mac用戶,那么可以配置一條快捷命令,直接上傳,方便省事

        rsync -avz ./* root@1.23.45.67:/home/api

        root 是服務器用戶名;
        1.23.45.67 是服務器IP地址;
        /home/api 是源碼在服務器的存放路徑

        然后輸入服務器密碼,就可以快速上傳了,而且只會上傳修改過的源碼,非??焖?。

        還可以將命令配置到package.json文件的scripts下,如

        "scripts": {
            "upload": "rsync -avz ./* root@1.23.45.67:/home/api"
        }

        此時你可以這樣上傳:

        npm run upload

        這樣配置上傳效率非常高,一起試試吧!

        本文由 楊成功 原創,更多原創文章關注 楊成功的全棧之路
        查看原文

        贊 13 收藏 7 評論 5

        認證與成就

        • 獲得 22 次點贊
        • 獲得 5 枚徽章 獲得 0 枚金徽章, 獲得 1 枚銀徽章, 獲得 4 枚銅徽章

        擅長技能
        編輯

        開源項目 & 著作
        編輯

        (??? )
        暫時沒有

        注冊于 2016-08-23
        個人主頁被 1.5k 人瀏覽

        一本到在线是免费观看_亚洲2020天天堂在线观看_国产欧美亚洲精品第一页_最好看的2018中文字幕