.38-淺析webpack源碼之babel-loader轉換js文件

来源:https://www.cnblogs.com/QH-Jimmy/archive/2018/03/02/8494394.html
-Advertisement-
Play Games

經過非常非常長無聊的流程,只是將獲取到的module信息做了一些緩存,然後生成了loaderContext對象。 這裡上個圖整理一下這節的流程: 這一節來看webpack是如何將babel-loader與js文件結合的,首先總覽一下runLoaders函數: 傳入的4個參數都很直白: 1、待處理文件 ...


  經過非常非常長無聊的流程,只是將獲取到的module信息做了一些緩存,然後生成了loaderContext對象。

  這裡上個圖整理一下這節的流程:

  這一節來看webpack是如何將babel-loader與js文件結合的,首先總覽一下runLoaders函數:

/*
    options => 
    {
        resource: 'd:\\workspace\\doc\\input.js',
        loaders: [ { loader: 'd:\\workspace\\node_modules\\babel-loader\\lib\\index.js' } ],
        context: loaderContext,
        readResource: fs.readFile.bind(fs)
    }
*/
exports.runLoaders = function runLoaders(options, callback) {
    // read options
    var resource = options.resource || "";
    var loaders = options.loaders || [];
    var loaderContext = options.context || {};
    var readResource = options.readResource || readFile;

    // 簡單講就是獲取入口文件的絕對路徑、參數、目錄
    var splittedResource = resource && splitQuery(resource);
    var resourcePath = splittedResource ? splittedResource[0] : undefined;
    var resourceQuery = splittedResource ? splittedResource[1] : undefined;
    var contextDirectory = resourcePath ? dirname(resourcePath) : null;

    // execution state
    var requestCacheable = true;
    var fileDependencies = [];
    var contextDependencies = [];

    // prepare loader objects
    loaders = loaders.map(createLoaderObject);

    // 將屬性都掛載到loaderContext上面
    loaderContext.context = contextDirectory;
    loaderContext.loaderIndex = 0;
    loaderContext.loaders = loaders;
    loaderContext.resourcePath = resourcePath;
    loaderContext.resourceQuery = resourceQuery;
    loaderContext.async = null;
    loaderContext.callback = null;
    loaderContext.cacheable = function cacheable(flag) {
        if (flag === false) {
            requestCacheable = false;
        }
    };
    loaderContext.dependency = loaderContext.addDependency = function addDependency(file) {
        fileDependencies.push(file);
    };
    loaderContext.addContextDependency = function addContextDependency(context) {
        contextDependencies.push(context);
    };
    loaderContext.getDependencies = function getDependencies() {
        return fileDependencies.slice();
    };
    loaderContext.getContextDependencies = function getContextDependencies() {
        return contextDependencies.slice();
    };
    loaderContext.clearDependencies = function clearDependencies() {
        fileDependencies.length = 0;
        contextDependencies.length = 0;
        requestCacheable = true;
    };
    // 定義大量的特殊屬性
    Object.defineProperty(loaderContext, "resource", {
        enumerable: true,
        get: function() {
            if (loaderContext.resourcePath === undefined)
                return undefined;
            return loaderContext.resourcePath + loaderContext.resourceQuery;
        },
        set: function(value) {
            var splittedResource = value && splitQuery(value);
            loaderContext.resourcePath = splittedResource ? splittedResource[0] : undefined;
            loaderContext.resourceQuery = splittedResource ? splittedResource[1] : undefined;
        }
    });
    // ...大量Object.defineProperty

    // finish loader context
    if (Object.preventExtensions) {
        Object.preventExtensions(loaderContext);
    }

    var processOptions = {
        resourceBuffer: null,
        readResource: readResource
    };
    iteratePitchingLoaders(processOptions, loaderContext, function(err, result) {
        if (err) {
            return callback(err, {
                cacheable: requestCacheable,
                fileDependencies: fileDependencies,
                contextDependencies: contextDependencies
            });
        }
        callback(null, {
            result: result,
            resourceBuffer: processOptions.resourceBuffer,
            cacheable: requestCacheable,
            fileDependencies: fileDependencies,
            contextDependencies: contextDependencies
        });
    });
};

  傳入的4個參數都很直白:

1、待處理文件絕對路徑

2、文件尾碼對應的loader入口文件絕對路徑

3、對應的loaderContext對象

4、fs對象

  前面所有的事都是為了生成前3個屬性,在這裡整合在一起開始做轉換處理。

createLoaderObject

  這裡有一個需要簡單看的地方,就是對loaders數組做了一封封裝:

// prepare loader objects
loaders = loaders.map(createLoaderObject);

  簡單看一下這個函數:

function createLoaderObject(loader) {
    var obj = {
        path: null,
        query: null,
        options: null,
        ident: null,
        normal: null,
        pitch: null,
        raw: null,
        data: null,
        pitchExecuted: false,
        normalExecuted: false
    };
    // 定義request屬性的get/set
    Object.defineProperty(obj, "request", {
        enumerable: true,
        get: function() {
            return obj.path + obj.query;
        },
        set: function(value) {
            if (typeof value === "string") {
                var splittedRequest = splitQuery(value);
                obj.path = splittedRequest[0];
                obj.query = splittedRequest[1];
                obj.options = undefined;
                obj.ident = undefined;
            } else {
                // value => { loader: 'd:\\workspace\\node_modules\\babel-loader\\lib\\index.js' }
                if (!value.loader)
                    throw new Error("request should be a string or object with loader and object (" + JSON.stringify(value) + ")");
                // 這麼多行代碼其實只有第一行有用
                // 即obj.path = 'd:\\workspace\\node_modules\\babel-loader\\lib\\index.js'
                obj.path = value.loader;
                obj.options = value.options;
                obj.ident = value.ident;
                if (obj.options === null)
                    obj.query = "";
                else if (obj.options === undefined)
                    obj.query = "";
                else if (typeof obj.options === "string")
                    obj.query = "?" + obj.options;
                else if (obj.ident)
                    obj.query = "??" + obj.ident;
                else if (typeof obj.options === "object" && obj.options.ident)
                    obj.query = "??" + obj.options.ident;
                else
                    obj.query = "?" + JSON.stringify(obj.options);
            }
        }
    });
    // 這裡會觸發上面的set
    obj.request = loader;
    // 封裝
    if (Object.preventExtensions) {
        Object.preventExtensions(obj);
    }
    return obj;
}

  最後做封裝,然後返回一個obj。

 

  將屬性全部掛載在loaderContext上面,最後也是調用Object.preventExtensions將屬性凍結,禁止添加任何新的屬性。

  完成對象的安裝後,最後調用了迭代器方法,這裡看一下iteratePitchingLoaders方法內部實現:

function iteratePitchingLoaders(options, loaderContext, callback) {
    // abort after last loader
    // loaderIndex初始為0
    if (loaderContext.loaderIndex >= loaderContext.loaders.length)
        return processResource(options, loaderContext, callback);

    // 取出之前的obj
    var currentLoaderObject = loaderContext.loaders[loaderContext.loaderIndex];

    // iterate
    // 預設是false 代表當前loader未被載入過
    if (currentLoaderObject.pitchExecuted) {
        loaderContext.loaderIndex++;
        return iteratePitchingLoaders(options, loaderContext, callback);
    }

    // load loader module
    loadLoader(currentLoaderObject, function(err) {
        // ...
    });
}

  取出來loader對象後,調用loadLoader來載入loader,看一眼:

module.exports = function loadLoader(loader, callback) {
    // 不知道這個System是什麼環境下的變數
    // node環境是global
    // 瀏覽器環境是window
    if (typeof System === "object" && typeof System.import === "function") {
        // ...
    } else {
        try {
            // 直接嘗試讀取路徑的文件
            var module = require(loader.path);
        } catch (e) {
            // it is possible for node to choke on a require if the FD descriptor
            // limit has been reached. give it a chance to recover.
            // 因為可能出現阻塞情況 所以這裡會進行重試
            if (e instanceof Error && e.code === "EMFILE") {
                var retry = loadLoader.bind(null, loader, callback);
                if (typeof setImmediate === "function") {
                    // node >= 0.9.0
                    return setImmediate(retry);
                } else {
                    // node < 0.9.0
                    return process.nextTick(retry);
                }
            }
            return callback(e);
        }
        if (typeof loader !== "function" && typeof loader !== "object")
            throw new Error("Module '" + loader.path + "' is not a loader (export function or es6 module))");
        // babel-loader返回的module是一個function
        loader.normal = typeof module === "function" ? module : module.default;
        loader.pitch = module.pitch;
        loader.raw = module.raw;
        if (typeof loader.normal !== "function" && typeof loader.pitch !== "function")
            throw new Error("Module '" + loader.path + "' is not a loader (must have normal or pitch function)");
        callback();
    }
};

  這裡就涉及到loader的返回值,通過直接讀取babel-loader的入口文件,最後返回了一個function,後面兩個屬性babel-loader並沒有給,是undefined。

  這裡把babel-loader返回值掛載到loader上後,就調用了無參回調函數,如下:

loadLoader(currentLoaderObject, function(err) {
    if (err) return callback(err);
    // 剛纔也說了這個是undefined
    var fn = currentLoaderObject.pitch;
    // 這個表明loader已經被調用了 下次再遇到就會直接跳過
    currentLoaderObject.pitchExecuted = true;
    if (!fn) return iteratePitchingLoaders(options, loaderContext, callback);

    runSyncOrAsync(
        fn,
        loaderContext, [loaderContext.remainingRequest, loaderContext.previousRequest, currentLoaderObject.data = {}],
        function(err) {
            if (err) return callback(err);
            var args = Array.prototype.slice.call(arguments, 1);
            if (args.length > 0) {
                loaderContext.loaderIndex--;
                iterateNormalLoaders(options, loaderContext, args, callback);
            } else {
                iteratePitchingLoaders(options, loaderContext, callback);
            }
        }
    );
});

  這裡把loader的一個標記置true,然後根據返回函數是否有pitch值來決定流程,很明顯這裡直接遞歸調用自身了。

  第二次進來時,由於loader已經被載入,所以loaderIndex加1,然後再次遞歸。

  第三次進來時,第一個判斷中表明所有的loader都被載入完,會調用processResource方法。

processResource

  這裡的遞歸由於都是尾遞歸,所以在性能上不會有問題,直接看上面的方法:

// options => 包含fs方法的對象
// loaderContext => 包含loader路徑、返回值等的對象
function processResource(options, loaderContext, callback) {
    // 從後往前調用loader
    loaderContext.loaderIndex = loaderContext.loaders.length - 1;

    // 獲取入口文件路徑
    var resourcePath = loaderContext.resourcePath;
    if (resourcePath) {
        /*
            loaderContext.dependency = loaderContext.addDependency = function addDependency(file) {
                fileDependencies.push(file);
            };
        */
        loaderContext.addDependency(resourcePath);
        // readResource => fs.readFile
        options.readResource(resourcePath, function(err, buffer) {
            if (err) return callback(err);
            options.resourceBuffer = buffer;
            iterateNormalLoaders(options, loaderContext, [buffer], callback);
        });
    } else {
        iterateNormalLoaders(options, loaderContext, [null], callback);
    }
}

  這個獲取入口文件路徑並調用fs模塊進行文件內容讀取,返迴文件的原始buffer後調用了iterateNormalLoaders方法。

function iterateNormalLoaders(options, loaderContext, args, callback) {
    // 當所有loader執行完後返回
    if (loaderContext.loaderIndex < 0)
        return callback(null, args);
    // 取出當前的loader
    var currentLoaderObject = loaderContext.loaders[loaderContext.loaderIndex];

    // iterate
    // 預設為false 跟另外一個標記類似 代表該loader在此方法是否被調用過
    if (currentLoaderObject.normalExecuted) {
        loaderContext.loaderIndex--;
        return iterateNormalLoaders(options, loaderContext, args, callback);
    }
    // 讀取返回的module
    var fn = currentLoaderObject.normal;
    // 標記置true
    currentLoaderObject.normalExecuted = true;
    if (!fn) {
        return iterateNormalLoaders(options, loaderContext, args, callback);
    }
    /*
        function convertArgs(args, raw) {
            if (!raw && Buffer.isBuffer(args[0]))
                args[0] = utf8BufferToString(args[0]);
            else if (raw && typeof args[0] === "string")
                args[0] = new Buffer(args[0], "utf-8");
        }
        function utf8BufferToString(buf) {
            var str = buf.toString("utf-8");
            if (str.charCodeAt(0) === 0xFEFF) {
                return str.substr(1);
            } else {
                return str;
            }
        }
    */
    // 該方法將原始的buffer轉換為utf-8的字元串
    convertArgs(args, currentLoaderObject.raw);

    runSyncOrAsync(fn, loaderContext, args, function(err) {
        if (err) return callback(err);

        var args = Array.prototype.slice.call(arguments, 1);
        iterateNormalLoaders(options, loaderContext, args, callback);
    });
}

  這裡的normal就是處理普通的js文件了,在讀取入口文件後將其轉換為utf-8的格式,然後依次獲取loader,調用runSyncOrAsync。

  源碼如下:

/*
    fn => 讀取babel-loader返回的函數
    context => loader的輔助對象
    args => 讀取入口文件返回的字元串
*/
function runSyncOrAsync(fn, context, args, callback) {
    var isSync = true;
    var isDone = false;
    var isError = false; // internal error
    var reportedError = false;
    context.async = function async() {
        if (isDone) {
            if (reportedError) return; // ignore
            throw new Error("async(): The callback was already called.");
        }
        isSync = false;
        return innerCallback;
    };
    // 封裝成執行一次的回調函數
    var innerCallback = context.callback = function() {
        if (isDone) {
            if (reportedError) return; // ignore
            throw new Error("callback(): The callback was already called.");
        }
        isDone = true;
        isSync = false;
        try {
            callback.apply(null, arguments);
        } catch (e) {
            isError = true;
            throw e;
        }
    };
    try {
        // 可以可以
        // 老子看了這麼久源碼就是等這個方法
        // 還裝模作樣的弄個IIFE
        var result = (function LOADER_EXECUTION() {
            return fn.apply(context, args);
        }());
        if (isSync) {
            isDone = true;
            if (result === undefined)
                return callback();
            // 根據轉換後的類型二次處理
            if (result && typeof result === "object" && typeof result.then === "function") {
                return result.catch(callback).then(function(r) {
                    callback(null, r);
                });
            }
            return callback(null, result);
        }
    } catch (e) {
        if (isError) throw e;
        if (isDone) {
            // loader is already "done", so we cannot use the callback function
            // for better debugging we print the error on the console
            if (typeof e === "object" && e.stack) console.error(e.stack);
            else console.error(e);
            return;
        }
        isDone = true;
        reportedError = true;
        callback(e);
    }
}

  看了那麼多的垃圾代碼,終於來到了最關鍵的方法,可以看出,本質上loader就是將讀取到的字元串傳入,然後返回對應的字元串或者一個Promise。

  這裡一路將結果一路返回到了最初的runLoaders方法中:

iteratePitchingLoaders(processOptions, loaderContext, function(err, result) {
    if (err) {
        return callback(err, {
            cacheable: requestCacheable,
            fileDependencies: fileDependencies,
            contextDependencies: contextDependencies
        });
    }
    /*
        result => babel-loader轉換後的字元串
        resourceBuffer => JS文件的原始buffer
        cacheable => [Function]
        fileDependencies => ['d:\\workspace\\doc\\input.js']
        contextDependencies => []
    */
    callback(null, {
        result: result,
        resourceBuffer: processOptions.resourceBuffer,
        cacheable: requestCacheable,
        fileDependencies: fileDependencies,
        contextDependencies: contextDependencies
    });
});

  因為案例比較簡單,所以返回的東西也比較少,這裡繼續callback,返回到doBuild:

doBuild(options, compilation, resolver, fs, callback) {
    this.cacheable = false;
    const loaderContext = this.createLoaderContext(resolver, options, compilation, fs);
    runLoaders({
        resource: this.resource,
        loaders: this.loaders,
        context: loaderContext,
        readResource: fs.readFile.bind(fs)
    }, (err, result) => {
        // result => 上面的對象
        if (result) {
            this.cacheable = result.cacheable;
            this.fileDependencies = result.fileDependencies;
            this.contextDependencies = result.contextDependencies;
        }

        if (err) {
            const error = new ModuleBuildError(this, err);
            return callback(error);
        }
        // 獲取對應的原始buffer、轉換後的字元串、sourceMap
        const resourceBuffer = result.resourceBuffer;
        const source = result.result[0];
        // null
        const sourceMap = result.result[1];

        if (!Buffer.isBuffer(source) && typeof source !== "string") {
            const error = new ModuleBuildError(this, new Error("Final loader didn't return a Buffer or String"));
            return callback(error);
        }
        /*
            function asString(buf) {
                if (Buffer.isBuffer(buf)) {
                    return buf.toString("utf-8");
                }
                return buf;
            }
        */
        this._source = this.createSource(asString(source), resourceBuffer, sourceMap);
        return callback();
    });
}

  這次獲取處理完的對象屬性,然後調用另外一個createSource方法:

createSource(source, resourceBuffer, sourceMap) {
    // if there is no identifier return raw source
    if (!this.identifier) {
        return new RawSource(source);
    }

    // from here on we assume we have an identifier
    // 返回下麵這個東西 很久之前拼接的
    // d:\workspace\node_modules\babel-loader\lib\index.js!d:\workspace\doc\input.js
    const identifier = this.identifier();
    // 下麵兩個屬性根本沒出現過
    if (this.lineToLine && resourceBuffer) {
        return new LineToLineMappedSource(
            source, identifier, asString(resourceBuffer));
    }

    if (this.useSourceMap && sourceMap) {
        return new SourceMapSource(source, identifier, sourceMap);
    }
    // 直接進這裡
    /*
        class OriginalSource extends Source {
            constructor(value, name) {
                super();
                this._value = value;
                this._name = name;
            }

            //...原型方法
        }
    */
    return new OriginalSource(source, identifier);
}

  因為都比較簡單,所以直接看註釋就好了,沒啥好解釋的。

  所有的new都只看看構造函數,方法那麼多,又不是全用。

  返回的對象賦值給了NormalModule對象的_source屬性,然後又是callback,這次回到了build那裡:

build(options, compilation, resolver, fs, callback) {
    this.buildTimestamp = Date.now();
    this.built = true;
    this._source = null;
    this.error = null;
    this.errors.length = 0;
    this.warnings.length = 0;
    this.meta = {};

    return this.doBuild(options, compilation, resolver, fs, (err) => {
        this.dependencies.length = 0;
        this.variables.length = 0;
        this.blocks.length = 0;
        this._cachedSource = null;

        // if we have an error mark module as failed and exit
        if (err) {
            this.markModuleAsErrored(err);
            return callback();
        }

        // check if this module should !not! be parsed.
        // if so, exit here;
        // undefined跳過
        const noParseRule = options.module && options.module.noParse;
        if (this.shouldPreventParsing(noParseRule, this.request)) {
            return callback();
        }

        try {
            this.parser.parse(this._source.source(), {
                current: this,
                module: this,
                compilation: compilation,
                options: options
            });
        } catch (e) {
            const source = this._source.source();
            const error = new ModuleParseError(this, source, e);
            this.markModuleAsErrored(error);
            return callback();
        }
        return callback();
    });
}

  基本上不知道module.noParser選項哪個人會用,所以這裡一般都是直接跳過然後調用那個可怕對象parser對象的parse方法,開始進行解析。

 

  這節的內容就這樣吧,總算是把loader跑完了,這個系列的目的也就差不多了。

  其實總體來說過程就幾步,但是代碼的複雜程度真的是不想說了……


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 這個pthread.h文件可以在NDK環境里創建子線程,並對線程能夠做出互斥所、等待、銷毀等控制。寫這個博客的原因是我要寫如何使用FFmpeg播放視頻,因為同時需要播放音頻和視頻所以需要開啟線程,並設置生產者和消費者的關係。好了直接上整體 1.開啟和銷毀線程 pthread_create函數能夠創建 ...
  • iOS應用打包離不開描述文件,也就是mobileprovision文件。 一般我們的操作是雙擊,Xcode就會運行該文件。但是具體文件里是什麼,Xcode又是否真的載入了該文件?文件里又描述了什麼呢?以下步驟都有利於問題的回答。 一:查看描述文件的位置 所有的描述文件安裝後,都保存在Provisio ...
  • 一、javascript簡介 1.1 javascript簡史 javascript誕生於1995年。當時它的主要目的是處理以前由伺服器端語言負責的一些輸入驗證操作。 1.2 javaScript實現 一個完整的JavaScript實現 = 核心(ECMAScript)+文檔對象模型(DOM)+瀏覽 ...
  • 這是效果圖 看起來很簡單是不是 之前一直寫Jquery代碼 總是想著 DOM 操作 思維感覺沒有切換過來 想了很久,最後使用Vue的屬性進行控制,實現了多選計算屬性的功能 直接上源碼! index.html index.js style.css 這樣的功能使用Vue,代碼簡單易懂,相對於原生代碼,無 ...
  • 一、文本樣式 首行縮進 text-indent 首行縮進是將段落的第一行縮進,這是常用的文本格式化效果。一般地,中文寫作時開頭空兩格。[註意]該屬性可以為負值;應用於: 塊級元素(包括block和inline-block) 字間隔 word-spacing 字間隔是指單詞間距,用來設置文字或單詞之間 ...
  • 什麼是Jquery? Jquey就是一款 跨主流瀏覽器的JavaScript庫,簡化JavaScript對HTML操作 就是封裝了JavaScript,能夠簡化我們寫代碼的一個JavaScript庫 為什麼要使用Jquery? 我覺得非常重要的理由就是: 它能夠相容市面上主流的瀏覽器, 我們學習AJ ...
  • 最近做了幾個項目,用js操作二進位數據,通過socket與後臺進行傳輸。在此用博客做個記錄 首先是新建一個socket: 接著定義socket打開,連接之後執行的函數: websocket有個屬性binaryType,可將其設置為“blob”或者“arraybuffer”,預設格式為“blob”,做 ...
  • 自己簡單總結了一下,歡迎大家補充! ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...