[原創] 分享我們自己搭建的微信小程式開發框架——wframe及設計思想詳解

来源:https://www.cnblogs.com/leotsai/archive/2018/06/08/wframe-complete-documentation.html
-Advertisement-
Play Games

wframe不是控制項庫,也不是UI庫,她是一個微信小程式面向對象編程框架,代碼只有幾百行。她的主要功能是規範小程式項目的文件結構、規範應用程式初始化、規範頁面載入及授權管理的框架,當然,wframe也提供了一些封裝好了的函數庫,方便開發者調用。 wframe目前已實現的核心功能: 1. 應用程式初始 ...


 

wframe不是控制項庫,也不是UI庫,她是一個微信小程式面向對象編程框架,代碼只有幾百行。她的主要功能是規範小程式項目的文件結構、規範應用程式初始化、規範頁面載入及授權管理的框架,當然,wframe也提供了一些封裝好了的函數庫,方便開發者調用。

 

wframe目前已實現的核心功能:

1. 應用程式初始化自動從伺服器獲取配置,ajax成功後觸發ready事件;

2. 每個頁面對象可以配置是否requireLogin屬性,如果需要登錄,則每個頁面在進入ready方法之前會自動完成授權、獲取用戶信息、伺服器端登錄;

3. 完成ajax全局封裝:如果用戶已經登錄,則會自動在http-header添加token信息,如果session過期則會重新進入登錄流程;

 

本文的閱讀對象:想要自己搭建小程式框架的人,相信本文會給你提供一些思路。

 

我們為什麼要開發wframe?

 

我們開發的小程式越來越多,小程式也越來越複雜,於是我們就想將每個小程式重覆在寫的那一部分代碼提出來,變成一個公共的函數庫,一個跟每個項目的業務邏輯完全不相關的函數庫。除了在新項目中可以節省代碼之外,有一些複雜的代碼邏輯由於提到了公共的函數庫,我們將其優化得更優雅、更健壯。

 

說wframe是一個函數庫雖說也可以,但wframe更像一個框架。我們通常把一些靜態方法、靜態對象、僅處理頁面內容的JS文件集稱作函數庫,比如jQuery;我們通常把處理了應用程式和頁面生命周期,以及使用了大量的面向對象編程技術的JS文件集稱作框架。因此,wframe其實是一個框架。

 

重要說明:wframe框架用到了大量的面向對象編程知識,比如實例、繼承、覆寫、擴展、抽象方法等等,因此對開發人員,特別是項目中的架構師,的面向對象編程能力有較高要求。

 

項目源碼已上傳到GitHub並會持續更新:https://github.com/leotsai/wframe

 

一、wframe項目結構

wframe的最核心的職責就是規範項目文件結構。

 

為什麼需要規範呢?因為我們小程式越來越多,如果每個小程式的文件結構都不一樣的話,那定是一件很難受的事。另外,wframe由於其框架的身份,其本職工作就是定義一個最好的文件結構,這樣基於wframe創建的所有小程式都將自動繼承wframe的優秀品質。

 

1. _core文件夾

wframe框架源碼,與業務毫不相干,每個小程式都可以直接將_core文件夾複製到項目中,而當wframe更新版本時,所有小程式可以直接覆蓋_core完成升級。用下劃線“_”開頭的目的有2個:

(a) 將此文件夾置頂;

(b) 標記此文件夾是一個特殊文件夾,本框架中還有其他地方也會用到下劃線開頭為文件夾/文件。

 

2. _demo文件夾

業務核心文件夾,比如定義一些擴展wframe框架的類,同時這些類又被具體的業務類繼承使用,比如ViewModelBase等。

 

3. pages文件夾

與微信小程式官方文檔定義一致:放置頁面的地方。

 

4. app.js

程式主入口,只不過基於wframe的小程式的app.js跟官方的長得很不一樣,我們定義了一個自己的Applicaiton類,然後再new的一個Application實例。稍後詳解。

 

5. mvcApp.js

幾乎每個js文件都會require引入的一個文件,因為這相當於是項目的靜態入口,其包含了所有的靜態函數庫,比如對wx下麵方法的封裝、Array類擴展、Date類擴展、網路請求(ajax)封裝等等。mvcApp.js幾乎只定義了一個入口,其內部的很多對象、方法都是通過require其他JS引入的。因此,大多數情況下,我們只需要require引入mvcApp.js就夠了。

 

寫到這裡,分享一個我們的編程思想:入口要少。小程式里有哪些入口:this、getApp()、wx、mvcApp。其實也就是每一行代碼點號“.”前面的都叫代碼入口。

 

我們還有另一個編程規範(強制):每個文件不能超過200行代碼(最好不超100行)。這就是要求每個程式員必須學會拆分,拆分也是我們的另一個編程思想。通過拆分,每個JS文件職責清晰,極大的提高了代碼閱讀率。

 

二、詳解

1. app.js和Application類詳解

app.js定義了程式入口。

 1 var mvcApp = require('mvcApp.js');
 2 var Application = require('_core/Application.js');
 3 
 4 function MvcApplication() {
 5     Application.call(this);
 6     this.initUrl = 'https://www.somdomain.com/api/client-config/get?key=wx_applet_wframe';
 7     this.host = 'http://localhost:18007';
 8     this.confgis = {
 9         host: 'http://localhost:18007',
10         cdn: 'https://images.local-dev.cdn.somedomain.com'
11     };
12     this.mock = true;
13     this.accessToken = null;
14     this.useDefaultConfigsOnInitFailed = false;
15 };
16 
17 MvcApplication.prototype = new Application();
18 
19 MvcApplication.prototype.onInitialized = function (configs) {
20     if (configs != null && configs !== '') {
21         this.configs = JSON.parse(configs);
22         this.host = this.configs.host;
23     }
24 };
25 
26 App(new MvcApplication());

 

 可以看到app.js定義了一個MvcApplication類,繼承自框架中的Application類,同時重寫了父類的onInitialized方法。

 

下麵是框架中的Application類:

 1 var WebClient = require('http/WebClient.js');
 2 var AuthorizeManager = require('weixin/AuthorizeManager.js');
 3 var weixin = require('weixin.js');
 4 
 5 
 6 function Application() {
 7     this.initUrl = '';
 8     this.host = '';
 9     this.session = null;
10     this.initialized = false;
11     this.mock = false;
12     this.useDefaultConfigsOnInitFailed = false;
13     this.authorizeManager = new AuthorizeManager();
14     this._userInfo = null;
15     this._readyHandlers = [];
16 };
17 
18 Application.prototype = {
19     onLaunch: function () {
20         var me = this;
21         if(this.initUrl === ''){
22             throw 'please create YourOwnApplication class in app.js that inerits from Application class and provide initUrl in constructor';
23         }
24         var client = new WebClient();
25         client.post(this.initUrl, null, function(result){
26             if (result.success || me.useDefaultConfigsOnInitFailed){
27                 me.initialized = true;
28                 me.onInitialized(result.success ? result.value : null);
29                 me.triggerReady();
30             }
31             else{
32                 weixin.alert('小程式初始化失敗', result.message);
33             }
34         }, '初始化中...');
35     },
36     onShow: function () {
37 
38     },
39     onHide: function () {
40 
41     },
42     onError: function () {
43 
44     },
45     onPageNotFound: function () {
46 
47     },
48     ready: function (callback) {
49         var me = this;
50         if (this.initialized === true) {
51             callback && callback();
52             return;
53         }
54         this._readyHandlers.push(callback);
55     },
56     triggerReady: function () {
57         for (var i = 0; i < this._readyHandlers.length; i++) {
58             var callback = this._readyHandlers[i];
59             callback && callback();
60         }
61         this._readyHandlers = [];
62     },
63     onInitialized: function(configs){
64 
65     },
66     getUserInfo: function(callback){
67         var me = this;
68         if(this._userInfo != null){
69             callback && callback(this._userInfo.userInfo);
70             return;
71         }
72         this.authorizeManager.getUserInfo(function(result){
73             me._userInfo = result;
74             callback && callback(me._userInfo.userInfo);
75         });
76     },
77     getCurrentPage: function(){
78         var pages = getCurrentPages();
79         return pages.length > 0 ? pages[0] : null;
80     }
81 };
82 
83 module.exports = Application;

 

Applicaiton類(及其子類)在wframe框架中的主要工作:

1. 應用程式初始化的時候從伺服器獲取一個配置,比如伺服器功能變數名稱(實現功能變數名稱實時切換)、CDN功能變數名稱,以及其他程式配置信息;

2. 全局存儲用戶的授權信息和登陸之後的會話信息;

3. 全局mock開關;

4. 其他快捷方法,比如獲取當前頁面等。

 

Application類核心執行流程:

1. 應用程式初始化時首先從伺服器獲取客戶端配置信息;

2. 獲取完成之後會觸發onInitialized方法(在子類中覆寫)和ready方法。

 

2. PageBase類詳解

PageBase類是所有頁面都會繼承的一個基類。先看代碼:

 1 console.log("PageBae.js entered");
 2 
 3 const app = getApp();
 4 
 5 function PageBase(title) {
 6     this.vm = null;
 7     this.title = title;
 8     this.requireLogin = true;
 9 };
10 
11 PageBase.prototype = {
12     onLoad: function (options) {
13         var me = this;
14         if (this.title != null) {
15             this.setTitle(this.title);
16         }
17         this.onPreload(options);
18         app.ready(function () {
19             if (me.requireLogin && app.session == null) {
20                 app.getUserInfo(function (info) {
21                     me.login(info, function (session) {
22                         app.session = session;
23                         me.ready(options);
24                     });
25                 });
26             }
27             else {
28                 me.ready(options);
29             }
30         });
31     },
32     ready: function (options) {
33 
34     },
35     onPreload: function(options){
36 
37     },
38     render: function () {
39         var data = {};
40         for (var p in this.vm) {
41             var value = this.vm[p];
42             if (!this.vm.hasOwnProperty(p)) {
43                 continue;
44             }
45             if (value == null || typeof (value) === 'function') {
46                 continue;
47             }
48             if (value.__route__ != null) {
49                 continue;
50             }
51             data[p] = this.vm[p];
52         }
53         this.setData(data);
54     },
55     go: function (url, addToHistory) {
56         if (addToHistory === false) {
57             wx.redirectTo({ url: url });
58         }
59         else {
60             wx.navigateTo({ url: url });
61         }
62     },
63     goBack: function () {
64         wx.navigateBack({});
65     },
66     setTitle: function (title) {
67         this.title = title;
68         wx.setNavigationBarTitle({ title: this.title });
69     },
70     login: function (userInfo, callback) {
71         throw 'please implement PageBase.login method.';
72     },
73     getFullUrl: function () {
74         var url = this.route.indexOf('/') === 0 ? this.route : '/' + this.route;
75         var parts = [];
76         for (var p in this.options) {
77             if (this.options.hasOwnProperty(p)) {
78                 parts.push(p + "=" + this.options[p]);
79             }
80         }
81         if (parts.length > 0) {
82             url += "?" + parts.join('&');
83         }
84         return url;
85     },
86     isCurrentPage: function(){
87         return this === getApp().getCurrentPage();
88     }
89 };
90 
91 PageBase.extend = function (prototypeObject) {
92     var fn = new PageBase();
93     for (var p in prototypeObject) {
94         fn[p] = prototypeObject[p];
95     }
96     return fn;
97 };
98 
99 module.exports = PageBase;

 

 由於微信小程式Application類的onLaunch不支持回調,也就是說,在wframe框架中,雖然我們在onLaunch時發起了ajax調用,但是程式並不會等待ajax返回就會立即進入Page對象的onLoad方法。這是一個非常重要的開發小程式的知識前提,但是官方文檔並沒有重要說明。

 

PageBase類的三個實例屬性:

1. vm:即ViewModel實例,可以理解為官方文檔中的Page實例的data屬性;

2. title:頁面標題

3. requireLogin:是否需要登錄,如果設置為true,則頁面onLoad執行後自動進入登錄流程,登錄完成後才會觸發頁面的ready方法;

 

PageBase類的實例方法:

1. onLoad:對應官方文檔中的onLoad事件。wframe框架自動會處理requireLogin屬性,處理完成後才觸發ready方法;

2. ready:每個業務級頁面的主入口,每個業務級頁面都應該實現ready方法,而不一定實現onLoad方法;

3. onPreload:在執行onLoad之前執行的方法,不支持非同步;

4. render:非常常用的方法,功能是將ViewModel(即data)呈現到頁面上,在業務頁面中直接使用this.render()即可將更新的數據呈現出來;

5. go:頁面跳轉,相比官方的wx.navigateTo簡化了很多;

6. goBack:等於wx.navigateBack;

7. setTitle:直接設置頁面標題;

8. login:可以理解成抽象方法,必須由子類實現,在我們demo中由業務級框架中的DemoPageBase實現;

9. getFullUrl:獲取頁面完整地址,包括路徑和參數,便於直接跳轉;

10. isCurrentPage:判斷該頁面實例是否在應用程式頁面棧中處於當前頁面,主要用於setInterval函數中判斷用戶是否已離開了頁面;

 

 3. DemoPageBase類詳解

 這是業務層級的框架內容。我們建議每個頁面都繼承自該類,這個類可以封裝跟業務相關的很多邏輯,方便子類(業務頁面)直接通過this調用相關方法。

 

在wframe的demo框架中,我們實現了PageBase類的抽象方法login。

 

這裡請註意同目錄的api.js文件。在我們的編碼規範中,所有ajax訪問都需要提到專門的api.js文件,通常與頁面類處於同一目錄,這是為了方便mock API。請看示例代碼:

 1 var mvcApp = require('../mvcApp.js');
 2 
 3 var api = {
 4     login: function (userInfo, code, callback) {
 5         var data = mvcApp.serializeToKeyValues(userInfo) + "&code=" + code;
 6         mvcApp.ajax.busyPost('/demo/api/login', data, function(result){
 7             callback(result.value);
 8         }, '登陸中...', true);
 9     }
10 };
11 if (getApp().mock) {
12     var api = {
13         login: function (userInfo, code, callback) {
14             setTimeout(function(){
15                 callback({
16                     token: '98c2f1bd7beb3bef3b796a5ebf32940498cb5586ddb4a5aa8e'
17                 });
18             }, 2000);
19         }
20     };
21 }
22 
23 module.exports = api;

 

 4. 頁面類的實現

 請看pages/index目錄下的文件列表:

1. IndexViewModel:該頁面的ViewModel;

2. api.js:該頁面所有ajax的封裝;

3. index.js:頁面入口;

4. index.wxml:HTML;

5. index.wxss:樣式;

 

先看入口index.js,代碼如下:

 1 var mvcApp = require('../../mvcApp.js');
 2 var DemoPageBase = require('../DemoPageBase.js');
 3 var IndexViewModel = require('IndexViewModel.js');
 4 
 5 function IndexPage() {
 6     DemoPageBase.call(this, 'index');
 7 };
 8 
 9 IndexPage.prototype = new DemoPageBase();
10 
11 IndexPage.prototype.onPreload = function(options){
12     this.vm = new IndexViewModel(this);
13     this.render();
14 };
15 
16 IndexPage.prototype.ready = function () {
17     var me = this;
18     this.vm.load();
19 };
20 
21 IndexPage.prototype.goDetails = function (e) {
22     var item = e.target.dataset.item;
23     wx.navigateTo({
24         url: '/pages/details/details?id=' + item.id
25     });
26 };
27 
28 Page(new IndexPage());

 

index.js核心邏輯:繼承自DemoPageBase,onPreload時設置了ViewModel,ready時(自動登錄完成後)調用ViewModel的數據載入方法,完成。

 

5. ViewModel的實現

在微信小程式官方文檔中,並沒有提ViewModel的概念,這會導致一些稍微有點複雜的頁面的data對象的處理變得很凌亂,更別說複雜頁面的data處理,那根本無從維護。ViewModel的設計思想是專門用來封裝視圖數據的一層代碼,不管是MVC,還是MVVM,ViewModel都是拆分數據層代碼的最佳實踐。因此,wframe框架強烈建議每個頁面都建一個對應的ViewModel,封裝數據結構,以及獲取、處理數據。

 

在我們的編程思想中,ViewModel不僅僅是放數據的地方,更是封裝業務邏輯的最佳位置之一。所以我們的ViewModel會很肥(fat model),會包含相關的很多業務邏輯處理。

 

如果項目需要,還可以封裝一個DemoViewModelBase類,將其他頁面ViewModel常用的方法封裝進來,比如this.getUserName()等方法。

 

請看示例代碼:

 1 var api = require('api.js');
 2 var mvcApp = require('../../mvcApp.js');
 3 
 4 function IndexViewModel(page){
 5     this.users = [];
 6     this.showLoading = true;
 7     this.males = 0;
 8     this.females = 0;
 9     this.page = page;
10 };
11 
12 IndexViewModel.prototype.load = function(){
13     var me = this;
14     api.getUsers(function(users){
15         me.showLoading = false;
16         me.females = users._count(function(x){
17             return x.gender === 'female';
18         });
19         me.males = users._count(function (x) {
20             return x.gender === 'male';
21         });
22         me.users = users._orderByDescending(null, function(first, second){
23             if(first.gender === 'male'){
24                 if(second.gender === 'male'){
25                     return first.birthYear > second.birthYear;
26                 }
27                 return true;
28             }
29             if(second.gender === 'female'){
30                 return first.birthYear > second.birthYear;
31             }
32             return false;
33         });
34         me.page.render();
35     });
36 };
37 
38 module.exports = IndexViewModel;

 

api.js就不貼代碼了,跟上一小節中的api.js一樣的。html和css部分也忽略不講。

 

至此,頁面級實現就完成了。

 

下麵,筆者再對wframe框架中的其他特殊部分進行特殊說明。繼續。

 

6. pages/_authorize文件夾

這個文件夾定義了一個授權頁面,這是因為新版小程式API強制要求用戶自己點授權按鈕才能彈出授權。這個雖然集成在wframe框架中,但是每個項目應該自行修改此頁面的樣式以符合項目UI設計。

 

這個目錄下麵只有一個_authorize.js值得貼一下代碼,其實都非常簡單:

 1 var DemoPageBase = require('../DemoPageBase.js');
 2 
 3 
 4 function AuthPage() {
 5     DemoPageBase.call(this, 'auth');
 6     this.requireLogin = false;
 7 };
 8 
 9 AuthPage.prototype = new DemoPageBase();
10 
11 AuthPage.prototype.onPreload = function (options) {
12     this.returnUrl = decodeURIComponent(options.returnUrl);
13 };
14 
15 AuthPage.prototype.onGotUserInfo = function (event) {
16     var me = this;
17     if (event.detail.userInfo == null) {
18         return;
19     }
20     var app = getApp();
21     app._userInfo = event.detail;
22     DemoPageBase.prototype.login.call(this, app._userInfo.userInfo, function () {
23         me.go(me.returnUrl, false);
24     })
25 }
26 
27 Page(new AuthPage())

 

請註意onPreload方法中對returnUrl的獲取,以及獲取用戶授權信息後對DemoPageBase.login方法的調用。

 

 7. _core文件夾其他文件詳解

 _core文件夾之前已經講了Application和PageBase類。繼續。

 

1. weixin.js

主要封裝了toast、busy(增加延時功能)、alert、confirm方法,後期可能會增加更多常用方法的封裝。代碼如下:

 1 var weixin = {
 2     _busyTimer: null,
 3     _busyDelay: 1500,
 4     toast: function (message, icon) {
 5         wx.showToast({
 6             title: message,
 7             icon: icon == null || icon == '' ? 'none' : icon
 8         });
 9     },
10     toastSuccess: function (message) {
11         this.toast(message, 'success');
12     },
13     busy: function (option, delay) {
14         clearTimeout(this._busyTimer);
15         if (option === false) {
16             wx.hideLoading();
17             return;
18         }
19         if (delay === 0) {
20             wx.showLoading({
21                 title: option,
22                 mask: true
23             });
24         }
25         else {
26             this._busyTimer = setTimeout(function () {
27                 wx.showLoading({
28                     title: option,
29                     mask: true
30                 });
31             }, delay == null ? this._busyDelay : delay);
32         }
33     },
34     alert: function (title, content, callback) {
35         content = content == undefined ? '' : content;
36         wx.showModal({
37             title: title,
38             content: content,
39             showCancel: false,
40             confirmText: "確定",
41             success: res => {
42                 callback && callback();
43             }
44         });
45     },
46     confirm: function (title, content, buttons) {
47         var buttonList = [];
48         for (var p in buttons) {
49             if (buttons.hasOwnProperty(p)) {
50                 buttonList.push({
51                     text: p,
52                     handler: buttons[p]
53                 })
54             }
55         }
56         content = content == undefined ? '' : content;
57         wx.showModal({
58             title: title,
59             content: content,
60             showCancel: true,
61             cancelText: buttonList[0].text,
62             confirmText: buttonList[1].text,
63             success: res => {
64                 if (res.confirm) {
65                     buttonList[1].handler && buttonList[1].handler();
66                 } else if (res.cancel) {
67                     buttonList[0].handler && buttonList[0].handler();
68                 }
69             }
70         });
71     }
72 };
73 
74 module.exports = weixin;
View Code

 

2. extensions/ArrayExtensions.js

一大堆數組擴展方法,非常常用,非常好用。引入mvcApp的業務層代碼均可直接使用。代碼如下:

  1 var ArrayExtensions = {};
  2 
  3 Array.prototype._each = function (func) {
  4     for (var i = 0; i < this.length; i++) {
  5         var item = this[i];
  6         var result = func(i, item);
  7         if (result === false) {
  8             return;
  9         }
 10     }
 11 };
 12 
 13 Array.prototype._sum = function (propertyOrFunc) {
 14     var total = 0;
 15     var isFunc = typeof (propertyOrFunc) == "function";
 16     this._each(function (i, item) {
 17         if (isFunc) {
 18             total += propertyOrFunc(item);
 19         } else {
 20             var value = item[propertyOrFunc];
 21             if (value != undefined) {
 22                 value = value * 1;
 23                 if (!isNaN(value)) {
 24                     total += value;
 25                 }
 26             }
 27         }
 28 	   

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

-Advertisement-
Play Games
更多相關文章
  • 1.什麼是audio標簽? 播放音頻 格式: <audio src=""> </audio> 也是由於同樣的適配問題,所以出現了第二種格式 <audio> <source src="" type=""> </audio> 2.註意點: audio標簽的使用和video標簽的使用基本一樣,video中 ...
  • 由於視頻數據非常非常的重要,所以五大瀏覽器廠商都不願意支持別人都視頻格式,所以導致了沒有一種視頻格式是所有瀏覽器都支持的,這個時候W3C為瞭解決這個問題,所以推出了第二種video標簽的格式 如何查看瀏覽器支持什麼格式的視頻? W3C HTML/CSS HTML5 HTML5視頻 m 我們從水平方向 ...
  • style.css: ...
  • 場景:開發微信內的H5活動,需要進行微信授權,我們採用的是在一個靜態頁面(只有js,所以是個空白頁面)內進行授權,授權後再跳轉到活動主頁。 客戶需求:從活動主頁返回時不顯示這個授權頁面(空白頁面),直接退出。 解決方案:(方案一為踩過的坑;方案二為放在前端處理的方案) 方案一:(不可行) 直接監聽需 ...
  • 這節課來學習一下html5中新增的標簽,我們先來看一下,html5中新增了哪些標簽? 打開W3school的網頁,點擊參考手冊中的HTML/HTML5標簽,有一個按字母順序排列的標簽,但凡標簽後面帶有5標記的,都是html5新增的標簽。 而有一些標簽是css才用到的,這裡先不學 有一些是用不到的,如 ...
  • //計算天數差的函數,通用 function DateDiff(sDate1, sDate2){ //sDate1和sDate2是2006-12-18格式 var aDate, oDate1, oDate2, iDays aDate = sDate1.split("-") oDate1 = new ...
  • 來做一個這個界面 fieldset和legend標簽在企業中用到的非常少,在企業里的邊框都要求非常好看 ...
  • ref:https://blog.csdn.net/wuxiaobingandbob/article/details/78642020?fps=1&locationNum=1 http://www.cnblogs.com/imyalost/p/6792724.html http://www.cnbl ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...