Java解析Excel之應用Reflection等技術實現動態讀取

来源:https://www.cnblogs.com/wind-june/archive/2018/09/23/9690743.html
-Advertisement-
Play Games

要實現Excel動態解析,實現解析與業務代碼邏輯相解耦;那麼我們不難會想起一個Java的一個關鍵技術-Reflection(反射原理),Python、Ruby等是動態語言而理論上Java是一門靜態語言,但是Java引入了Reflection技術實現了動態性。反射原理我們都比較熟悉,就是在運行期間動態... ...


 目錄樹

  • 背景
  • 技術選型
  • 問題分析
  • 技術要點及難點分析
  • 源碼分析
  • 測試用例

 


 

背景

Tip:因為產品提的需求我都開發完了,進行了項目提測;前天老大走過來說:你用spring-boot開發一個解析Excel的jar包.....詳細對話如下:

A:世生,你用spring-boot開發一個解析Excel的jar包。

B:為什麼不在原來的項目上進行開發呢?(很納悶,這個東西不是一般用於前端上傳數據的嘛,這是後端層,咋搞這個)

A:因為xxxx這個需求有比較多的數據需要初始化,這個jar是作為一個上線的數據初始化腳本

B:嗯,好的

 


 

技術選型

   畢竟是第一次寫解析Excel代碼,問了度娘說是有兩種方式可以做到。一是利用wso2的jxl解析二是利用apache的poi解析;我去maven repository官網搜索了這兩個jar包,對比了下jml的最新版本是2.6.12竟然是2011.05月更新的,而poi的最新版本是4.0.x是2018.08更新的;個人覺得jml最新版本都是2011.05更新的,相對於apache的poi包來說更不靠譜;不斷持續更新的開源項目或者開源jar包不管是bug還是相容性相對來說是越來越好。所以最終選定用apache大佬的poi進行開發。

 


 

問題分析

  解析Excel的關鍵點是在於從Excel表格中讀取數據到記憶體(解析Excel),然後可能是校驗數據、通過業務邏輯分析數據、最終持久化到資料庫中;其實這其中最重要的不是解析Excel,而是將解析出的數據持久化到資料庫中以備有用之需。而解析Excel的這塊功能只能算是一個Util類,不能與業務代碼耦合一起;然後我看到很多的Excel解析相關的代碼都是在解析數據中混淆業務代碼邏輯,其實這些都是不可取的,這會導致解析Excel邏輯與業務邏輯相耦合也就是冗雜、代碼重用率低、可擴展性低等問題。因為之前在做項目的時候遇到過一個問題:我負責的模塊是一個中間模塊(通訊採用Dubbo),其他系統要依賴我這個介面進行請求的轉發可我調用其他系統返回的結果對象卻各個都不同,我叫其他系統負責人說要統一調用介面返回的對象,但是其他系統的負責人都不是同一個人執行起來效率太低了,在歷經一個星期都無果的情況下我只能撒下自己的殺手鐧了;在這種極端條件下最終我不管其他數據的介面返回的對象是什麼,我直接用Object接收返回類型,通過反射獲取決定請求成功與否的屬性值(欣慰的是當時必傳屬性值倒是一樣的)。通過這種方法我可以少些很多的代碼(當時其他系統有15+),不然的話每調用不同系統的介面我都需要進行邏輯判斷或者是乾脆對於調用他們不同的系統我採用不同介面進行轉發,但選用這種方法卻便利多了。

  以上問題分析及一個場景的描述很好理解,但是通過Object接收返回信息得這個場景事實上卻有所欠佳;返回對象不同的這個問題最好的處理方案就是統一介面,我那個方案是在需求推動但別人無法及時配合的極端條件下使用的,是沒辦法中的辦法,但這也是一個沒有辦法中的一個最優的處理方案,相容性比較強。以下我就用圖來分析這兩種情況的比較:

  1.非動態模式:將Excel數據載入到記憶體與業務代碼邏輯混合,數據在解析期間交叉傳遞。弊端:每新增一個需要解析的Excel,解析Excel代碼塊就需要重新開發,代碼復用率底下、可擴展性也低。

  

 

  2.動態模式:將Excel數據載入到記憶體與業務代碼邏輯分開;Excel數據載入到記憶體之後才將數據傳遞給業務代碼邏輯處理,解析Excel與業務代碼之間分開;優點:將解析Excel的這部分代碼封裝為一個ExcelUtil,代碼復用率明顯提高,而且解析與業務代碼間實行解耦,可擴展性增強。

       


 

技術要點及難點分析

  要實現動態解析,實現解析與業務代碼邏輯相解耦;那麼我們不難會想起一個Java的一個關鍵技術-Reflection(反射原理),Python、Ruby等是動態語言而理論上Java是一門靜態語言,但是Java引入了Reflection技術實現了動態性。反射原理我們都比較熟悉,就是在運行期間動態獲取類的所有屬性及其方法,可以對這些數據進行相關的操作。以上動態解析Excel的實現就需要用到Java這項的高級技術了,通過這項技術可以實現動態解析、解析與業務邏輯解耦等。為了實現動態解析的目的我應用了Java反射技術,但是在開發的過程我發現反射執行一行數據結束的時候如何保存呢?換句話說就是:解析的時候一行的Excel數據封裝後就是一個bean,一個Excel表格就是多個bean 即“beans”;如果我們直接將反射執行後的結果保存至List中,當解析整個Excel結束後我們會發現,整個List裡面的對象的值完全一樣的?what?這是什麼原因導致的呢?這就是類似於:Object obj=new Object(),我們每次解析都只是把 obj 放在List中,List中的每一個對象都是同一個 obj(引用不變,實例也不變),所以自然也就相同了;因為當一個類執行反射的時候其實它的運行時狀態只有一個,也就是類似於只有一個實例,而傳統的解析Excel是解析出一條數據就new一個對象進行封裝數據,然後將bean存放至List。然而有什麼方法能夠解決這一類問題呢?那就是Object 的native clone()方法了,clone()這個大佬是比較牛逼的一個人物,在不創建對象的情況下將屬性值複製給另一個對象,具體實現需要實現Cloneable介面並重寫clone()。而解決這個問題的方式就是在每解析完一行Excel數據的時候,反射調用該對象的clone方法。動態解析具體實現應用了Apache POI、 LRUCache(LRU緩存)、Reflection(反射)、java的Clone等技術。如果以上技術沒有瞭解過的朋友可以去自行瞭解,這裡不加贅述。

  前提條件:因為要實現動態解析,動態設置值,那麼我們在反射執行set操作的時候就需要知道相應的setMethod(),那麼我們可以在Excel規定第一行就是屬性欄位,並且欄位名稱跟bean的名稱一樣,讀取的時候先把第一行的數據放在一個String []數組中。具體實現請參照以下源碼。我已經把相關代碼打包成Jar,需要的朋友可以自行下載;Jar包下載鏈接:https://pan.baidu.com/s/1fKCCh54S3ZtHfv66T2pk2w 密碼:nur8

  使用方法:新建bean用於存儲Excel數據信息,每個屬性需要有get、set操作,屬性與Excel首行相同,最重要的一點是要實現Clonesble介面重寫clone方法Excel使用Office編輯,親測Wps編輯的Excel某些屬性值有問題。在new ReadExcelUtil 的時候只需要將對象類型與Excel文件路徑傳入構造函數即可,然後調用 ReadExcelUtil的getObjectList即可得到解析後的所有對象。至於這個對象你可以用任何的對象,你可以換成Teacher、OrderInfo、UserInfo......但是前面提到的:Excel第一行的屬性欄位需要與bean的屬性欄位一致,否則無法調用目標方法,具體可參見ReflectionInitValue的方法。具體實現請參見:文章末尾的Test類測試。 

 


 

 源碼分析

  • 前提條件:引入Apache POI 的Maven倉庫坐標,我這裡使用的是3.25版本的。

 


1
<!-- https://mvnrepository.com/artifact/org.apache.poi/poi -->
 2 <dependency>
 3     <groupId>org.apache.poi</groupId>
 4     <artifactId>poi</artifactId>
 5     <version>3.15</version>
 6 </dependency>
 7 <dependency>
 8     <groupId>org.apache.poi</groupId>
 9     <artifactId>poi-ooxml</artifactId>
10     <version>3.15</version>
11 </dependency>

 

  • 主要類:Common.java、LRUCache.java、LRUCacheException.java、ResolveFileException.java、ReadExcelUtil.java、ReflectionInitValue.java、Student、Test
  • Common.java:基礎常量池,主要用於反射執行Method方法時判斷Method的參數類型的常量。

 

 1 package com.hdbs.common;
 2 
 3 /**
 4  * @author :cnblogs-WindsJune
 5  * @version :1.1.0
 6  * @date :2018年9月20日 下午6:33:54
 7  * @comments :解析Excel公共類常量類
 8  */
 9 
10 public class Common {
11     
12     public static final String OFFICE_EXCEL_2003_POSTFIX_xls = "xls";
13     public static final String OFFICE_EXCEL_2010_POSTFIX_xlsx = "xlsx";
14     public static final String DATA_TYPE_long ="long";
15     public static final String DATA_TYPE_boolean ="boolean";
16     public static final String DATA_TYPE_int ="int";
17     public static final String DATA_TYPE_float ="float";
18     public static final String DATA_TYPE_double ="double";
19     public static final String DATA_TYPE_Long ="class java.lang.Long";
20     public static final String DATA_TYPE_Integer ="class java.lang.Integer";
21 
22 
23 }

 

 

  • LRUCacheException.java;LRU緩存自定義異常類。
 1 package com.hdbs.exceptions;
 2 
 3 /**
 4  * Creater: cnblogs-WindsJune
 5  * Date: 2018/9/21
 6  * Time: 10:04
 7  * Description: No Description
 8  */
 9 public class LRUCacheException extends  Exception{
10     /**
11      * 錯誤碼
12      */
13     private String errorCode;
14 
15     /**
16      * 錯誤描述
17      */
18     private String errorMessage;
19 
20     public LRUCacheException(String errorCode, String errorMessage) {
21         this.errorCode = errorCode;
22         this.errorMessage = errorMessage;
23     }
24 
25     public LRUCacheException(String message) {
26         super(message);
27         this.errorMessage = errorMessage;
28     }
29 
30     public String getErrorCode() {
31         return errorCode;
32     }
33 
34     public void setErrorCode(String errorCode) {
35         this.errorCode = errorCode;
36     }
37 
38     public String getErrorMessage() {
39         return errorMessage;
40     }
41 
42     public void setErrorMessage(String errorMessage) {
43         this.errorMessage = errorMessage;
44     }
45 }

 

  • ResolveFileException.java;解析Excel自定義異常類。

 

 1 package com.hdbs.exceptions;
 2 /**
 3  * Creater: cnblogs-WindsJune
 4  * Date: 2018/9/20
 5  * Time: 19:44
 6  * Description: 解析Excel的公共異常類
 7  */
 8 
 9 public class ResolveFileException extends RuntimeException{
10 
11     /**
12      * 錯誤碼
13      */
14     private String errorCode;
15 
16     /**
17      * 錯誤描述
18      */
19     private String errorMessage;
20 
21     public ResolveFileException(String errorCode, String errorMessage) {
22         this.errorCode = errorCode;
23         this.errorMessage = errorMessage;
24     }
25 
26     public ResolveFileException(String message) {
27         super(message);
28         this.errorMessage = errorMessage;
29     }
30 
31     public String getErrorCode() {
32         return errorCode;
33     }
34 
35     public void setErrorCode(String errorCode) {
36         this.errorCode = errorCode;
37     }
38 
39     public String getErrorMessage() {
40         return errorMessage;
41     }
42 
43     public void setErrorMessage(String errorMessage) {
44         this.errorMessage = errorMessage;
45     }
46 }

 

 

  • LRUCache.java:LRU緩存池,主要用於不同線程反射獲取的Methods,減少相同線程反射執行次數,減輕應用的負載、提高執行效率。我這裡是基於LinkedHashMap實現的LRU緩存,你也可以用數組或者其他方式實現該演算法。以下代碼邏輯如果不能理解的可以先去瞭解LinkedHashSet的源碼。

 

 1 package com.hdbs.common;
 2 
 3 import com.hdbs.exceptions.LRUCacheException;
 4 import org.slf4j.Logger;
 5 import org.slf4j.LoggerFactory;
 6 
 7 import java.lang.reflect.Method;
 8 import java.util.LinkedHashMap;
 9 import java.util.Map;
10 
11 /**
12  * Creater: cnblogs-WindsJune
13  * Date: 2018/9/20
14  * Time: 19:44
15  * Description: LinkedHashMap實現LRU緩存不同線程反射獲取的Method方法
16  */
17 public class LRUCache {
18     private  static  final Logger LOGGER=LoggerFactory.getLogger(LRUCache.class);
19     //緩存容量
20     private static final int cacheSize = 10;
21 
22     private static final Map<Integer,Method[]> cacheMap = new LinkedHashMap<Integer, Method[]>((int) Math.ceil(cacheSize / 0.75f) + 1, 0.75f, true){
23         @Override
24         protected boolean removeEldestEntry(Map.Entry<Integer,Method[]> eldest){
25 
26             return  size()> cacheSize;
27             
28         }
29     };
30 
31     /**
32      * 設置緩存
33      * @param key
34      * @param methods
35      * @return boolean
36      */
37     public static boolean set (Integer key,Method [] methods) throws LRUCacheException {
38         try {
39                cacheMap.put(key,methods);
40                return true;
41         }
42         catch ( Exception e ){
43               throw new LRUCacheException("Set LRU緩存異常!");
44         }
45     }
46 
47     /**
48      * 獲取緩存的Method
49      * @param key
50      * @return Method
51      */
52     public static Method[] get(Integer key) throws LRUCacheException {
53         Method[] methods=null;
54         try {
55             methods=cacheMap.get(key);
56         }catch ( Exception e ){
57                throw new LRUCacheException("Get LRU緩存異常!{}");
58         }
59         return methods;
60     }
61 }

 

 

  • ReadExcelUtil.java;解析Excel數據工具類(將Excel載入到記憶體)

 

  1 package com.hdbs.resolver;
  2 
  3 import com.hdbs.common.Common;
  4 import com.hdbs.exceptions.ResolveFileException;
  5 import org.apache.commons.lang3.StringUtils;
  6 import org.apache.poi.hssf.usermodel.HSSFCell;
  7 import org.apache.poi.hssf.usermodel.HSSFRow;
  8 import org.apache.poi.hssf.usermodel.HSSFSheet;
  9 import org.apache.poi.hssf.usermodel.HSSFWorkbook;
 10 import org.apache.poi.xssf.usermodel.XSSFCell;
 11 import org.apache.poi.xssf.usermodel.XSSFRow;
 12 import org.apache.poi.xssf.usermodel.XSSFSheet;
 13 import org.apache.poi.xssf.usermodel.XSSFWorkbook;
 14 import org.slf4j.Logger;
 15 import org.slf4j.LoggerFactory;
 16 
 17 import java.io.File;
 18 import java.io.FileInputStream;
 19 import java.io.IOException;
 20 import java.io.InputStream;
 21 import java.lang.reflect.InvocationTargetException;
 22 import java.util.ArrayList;
 23 import java.util.HashMap;
 24 import java.util.List;
 25 import java.util.Map;
 26 
 27 /**
 28  * @author :cnblogs-WindsJune
 29  * @version :1.1.0
 30  * @date :2018年9月20日 下午6:13:43
 31  * @comments :
 32  */
 33 
 34 public class ReadExcelUtil {
 35 
 36     private static final Logger LOGGER = LoggerFactory.getLogger(ReadExcelUtil.class);
 37   //存放屬性集
 38     private  Map<Integer,String []> fieldsMap=new HashMap<>();
 39   //存放解析後的對象List
 40     private List<Object> objectsList = new ArrayList<>();
 41   //反射運行時對象
 42     private Object object=null;
 43   //Excel文件路徑
 44     private  String path =null;
 45   //獲取解析後的對象集
 46     public List<Object> getObjectsList() {
 47         return this.objectsList;
 48     }
 49 
 50     public ReadExcelUtil(Object object,String path) throws InvocationTargetException, NoSuchMethodException, InstantiationException, IllegalAccessException, IOException {
 51         this.object=object;
 52         this.path=path;
 53         readExcel();
 54     }
 55 
 56     /**
 57      * 添加Object到List中
 58      * @param object
 59      * @return
 60      */
 61     public boolean addListObject(Object object){
 62         boolean isSucceed=this.objectsList.add(object);
 63         return  isSucceed;
 64     }
 65 
 66     /**
 67      * 讀取excel,判斷是xls結尾(2010之前);還是xlsx結尾(2010以後)的Excel
 68      * 
 69      * @return
 70      * @throws IOException
 71      */
 72     public boolean readExcel() throws IOException, InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException {
 73         if (StringUtils.isEmpty(path)) {
 74             return false;
 75         } else {
 76             // 截取尾碼名,判斷是xls還是xlsx
 77             String postfix = path.substring(path.lastIndexOf(".") + 1);
 78             if (!StringUtils.isEmpty(postfix)) {
 79                 if (Common.OFFICE_EXCEL_2003_POSTFIX_xls.equals(postfix)) {
 80                     return readXls();
 81                 } else if (Common.OFFICE_EXCEL_2010_POSTFIX_xlsx.equals(postfix)) {
 82                     return readXlsx();
 83                 }
 84             } else {
 85                 LOGGER.error("文件尾碼名有誤!");
 86                 throw new ResolveFileException("文件尾碼名有誤!" + "[" + path + "]");
 87             }
 88         }
 89         return false;
 90     }
 91 
 92     /**
 93      * 讀取xls(2010)之後的Excel
 94      * 
 95      * @return
 96      * @throws IOException
 97      */
 98     public  boolean readXlsx() throws IOException{
 99         File file = new File(path);
100         InputStream is = new FileInputStream(file);
101         XSSFWorkbook xssfWorkbook = new XSSFWorkbook(is);
102         // 遍歷sheet頁
103         for (int numSheet = 0; numSheet < xssfWorkbook.getNumberOfSheets(); numSheet++) {
104             XSSFSheet xssfSheet = xssfWorkbook.getSheetAt(numSheet);
105             String [] fields=null;
106             if (xssfSheet == null) {
107                 continue;
108             }
109             // 迴圈行
110             for (int rowNum = 0; rowNum <= xssfSheet.getLastRowNum(); rowNum++) {
111                 XSSFRow xssfRow = xssfSheet.getRow(rowNum);
112                 int cloumns=xssfRow.getLastCellNum();
113                 int i=0;
114                 //獲取第一行的所有屬性
115                 if (rowNum == 0){
116                     fields=getFields(xssfRow,cloumns);
117                     fieldsMap.put(numSheet,fields);
118                     continue;
119                 }
120                 //遍曆數據,反射set值
121                 while (i<cloumns){
122                     XSSFCell field=xssfRow.getCell(i);
123                     String value=getValue(field);
124                     try {
125                         ReflectionInitValue.setValue(object,fields[i],value);
126                     }catch ( Exception e ){
127                         throw new ResolveFileException(e.getMessage());
128                     }
129                     i++;
130                 }
131                 //通過反射執行clone複製對象
132                 Object result=ReflectionInitValue.invokeClone(object,"clone");
133                 this.addListObject(result);
134                // System.out.println(object.toString());
135             }
136         }
137         return true;
138     }
139 
140     /**
141      * 讀取xls(2010)之前的Excel
142      * 
143      * @return
144      * @throws IOException
145      */
146     public boolean readXls() throws IOException, ResolveFileException {
147         InputStream is = new FileInputStream(path);
148         HSSFWorkbook hssfWorkbook = new HSSFWorkbook(is);
149         // 遍歷sheet頁
150         for (int numSheet = 0; numSheet < hssfWorkbook.getNumberOfSheets(); numSheet++) {
151             HSSFSheet hssfSheet = hssfWorkbook.getSheetAt(numSheet);
152             String[] fields = null;
153             if (hssfSheet == null) {
154                 continue;
155             }
156             // 迴圈行Row
157             for (int rowNum = 0; rowNum <= hssfSheet.getLastRowNum(); rowNum++) {
158                 HSSFRow hssfRow = hssfSheet.getRow(rowNum);
159                 int cloumns=hssfRow.getLastCellNum();
160                 int i=0;
161                 //獲取第一行的所有屬性
162                 if (rowNum == 0){
163                     //獲取屬性欄位
164                     fields=getFields(hssfRow,cloumns);
165                     fieldsMap.put(numSheet,fields);
166                     continue;
167                 }
168                 //遍曆數據,反射set值
169                 while (i<cloumns){
170                     HSSFCell field=hssfRow.getCell(i);
171                     String value=getValue(field);
172                     try {
173                         ReflectionInitValue.setValue(object,fields[i],value);
174                     }catch ( Exception e ){
175                         throw  new ResolveFileException(e.getMessage());
176                     }
177                     i++;
178                 }
179                 //通過反射執行clone複製對象
180                 Object result=ReflectionInitValue.invokeClone(object,"clone");
181                 this.addListObject(result);
182             }
183         }
184         return true;
185     }
186 
187     /**
188      * xlsx -根據數據類型,獲取單元格的值
189      * @param xssfRow
190      * @return
191      */
192     @SuppressWarnings({ "static-access" })
193     private static String getValue(XSSFCell xssfRow) {
194         String value=null;
195         try {
196             if (xssfRow.getCellType() == xssfRow.CELL_TYPE_BOOLEAN) {
197                 // 返回布爾類型的值
198                 value=String.valueOf(xssfRow.getBooleanCellValue()).replace(" ","");
199             } else if (xssfRow.getCellType() == xssfRow.CELL_TYPE_NUMERIC) {
200                 // 返回數值類型的值
201                 value= String.valueOf(xssfRow.getNumericCellValue()).replace(" ","");
202             } else {
203                 // 返回字元串類型的值
204                 value= String.valueOf(xssfRow.getStringCellValue()).replace(" ","");
205             }
206         } catch (Exception e) {
207             //單元格為空,不處理
208             value=null;
209             LOGGER.error("單元格為空!");
210         }
211         return value;
212     }
213 
214     /**
215      * xls-根據數據類型,獲取單元格的值
216      * @param hssfCell
217      * @return
218      */
219     @SuppressWarnings({ "static-access" })
220     private static String getV

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

-Advertisement-
Play Games
更多相關文章
  • Cookie和Session在Node.JS中的實踐(一) Cookie和Session是一個非常有趣的概念,也是一個老生常談的話題。然而,作者看了許多文章,也翻看了幾本書籍,它們對Cookie和Session的概念、機制的描述都各不一致,大多文章都只談到了Cookie和Session其中之一,但在 ...
  • React作為受歡迎的前端框架之一,大多數人只會用而不理解其原理,本系列屬於筆記,來源於深入React技術棧,並會適時加入補充內容,以便讀者能理解React底層實現,包括virtual DOM機制、生命周期等。 ...
  • 對於前端程式員來說閉包還是比較難以理解的, 閉包的形成與變數的作用域以及變數的生產周期密切相關,所以要先弄懂變數的作用域和生存周期。 1.變數作用域 變數的作用域,就是指變數的有效範圍,通常我們指的作用域就是函數作用域(畢竟全局的作用域沒有要指的意義,關鍵哪都能訪問) 聲明變數的時候推薦使用es6語 ...
  • 1. url 發送請求的地址。為空表示當前頁。 1 $.ajax({ 2 type: "post", 3 data: studentInfo, 4 contentType: "application/json", 5 url: "/Home/Submit",//請求的介面 6 beforeSend: ...
  • 1.event.stopPropagation()方法 這是阻止事件的冒泡方法,不讓事件向documen上蔓延,但是預設事件任然會執行,當你掉用這個方法的時候,如果點擊一個連接,這個連接仍然會被打開, 2.event.preventDefault()方法 這是阻止預設事件的方法,調用此方法是,連接不 ...
  • 首先遲到的祝大家中秋快樂。 最近一周多沒有更新了。其實我一直想憋一個大招,分享一些大家感興趣的乾貨。 鑒於最近我個人的工作內容,於是利用這三天小長假憋了一個出來(其實是玩了兩天
  • 反正這個概念我一般都是不去記得,首先看一下什麼是依賴: 有一個類是Animal,然後我定義了一個BlackCat類,類裡面有一個BlackCat方法,那麼這裡的BlackCat就依賴Animal BlackCat類實例化的時候需要一個Animal的對象作為構造函數的參數,那麼BlackCat就依賴A ...
  • 本文來自 旭日Follow_24 的CSDN 博客 ,全文地址請點擊:https://blog.csdn.net/xuri24/article/details/81106656?utm_source=copy 一、單例模式 基本概念:保證一個類僅有一個實例,並提供一個訪問它的全局訪問點。 常見寫法: ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...