Word報告自動生成(例如 導出資料庫結構)

来源:https://www.cnblogs.com/FlyLolo/archive/2018/11/16/WordReport.html
-Advertisement-
Play Games

將很早之前寫的一個小組件重新整理優化一下,做成一個通用的功能。適用於導出資料庫的結構(表、欄位等)到Word或將體檢數據自動生成Word版的體檢報告等。代碼:Github 一、主要需要完成功能: 1. 靈活的配置規則及word樣式設置(文本、表格、圖表、顏色等). 2. 支持表格. 3. 支持圖表. ...


        將很早之前寫的一個小組件重新整理優化一下,做成一個通用的功能。適用於導出資料庫的結構(表、欄位等)到Word或將體檢數據自動生成Word版的體檢報告等。代碼:Github

一、主要需要完成功能:

1. 靈活的配置規則及word樣式設置(文本、表格、圖表、顏色等).

2. 支持表格.

3. 支持圖表.

4. 支持章節內容迴圈生成.

5. 支持目錄.

6.支持文檔結構圖

7.更新指定位置的文字

8.支持pdf導出.

 

最後結果如下:

                                                                            圖一

                                    圖二

                                         圖三

二、需求分析與實現方式

   功能主要涉及3個比較重要的部分:數據源、Word樣式、配置規則。 

     為了簡單,數據源決定採用一個存儲過程返回Dataset的方式, 整張報告的數據來源於此Dataset的多個Datatable.

  樣式與配置:首先想到的是寫一個config文件,所有配置都放到一個文件里,然後將數據按照這個規則生成word。但無疑這樣的配置項太多了,關鍵是“樣式”問題,比如字體、顏色、表格寬度.....想想就頭大。而且沒有“所見即所得”的效果,配置完都不知道啥樣。

後來決定採取修改的方式, 先以一個word文件作為模板,在模板中定義好上面提到的“樣式”,然後在模板中做一個個標記,然後將數據按照規則更新到對應的標記。

                                                       圖四

    而這個標記我們用到了word的一個叫【書簽】的功能,打開word按ctrl+shift+F5, 打開書簽功能,如下圖:

                                 圖五

這樣將【規則】通過一系列規則的【書簽】定義到word模板中。

三、規則配置

  思路確定了,那就開始設計如何通過【書簽】將規則定義到word模板中去,這裡決定將所有規則都通過【書簽】實現,而放棄config文件的方式,這個更統一而且直觀一些。

A.迴圈

      以圖四為例,資料庫有多少張表是不固定的,我們在製作模板的時候不可能先畫好N(N為表的總數)個表格等待數據填充, 這裡就會需要遍曆數據源中提供的所有表結構數據,然後逐一形成表格。這裡就需要將圖四中的表格迴圈一下,自動複製生成多個這樣的表格。當然,這隻是一種情況,還有可能會出現迴圈嵌套迴圈的情況,那麼我將這個迴圈定義成一個書簽的時候按照這樣的格式:

      loop_級別_表序號_filter_名稱

含義如下:

     loop:代表這是一個迴圈。

     級別:預設文檔級別為0,出現的第一層迴圈為1,其內部若再次嵌套迴圈則級別為2,依次類推。

     表序號:取Dataset中的第幾張表(從1開始)

     filter:迴圈的時候可能會用到對datatable的查找過濾,在此寫出,多個欄位用XX隔開(因為此處不允許有下劃線外其他特殊字元, 就用這個XX吧 )

     名稱:loop名稱,方便與其他 loop區別

 B.更新指定位置的文字

    如圖四中的【伺服器名】、【表總數】等,只需要替換對應的文字即可:

    label_級別_名稱

含義如下:

     label:代表這是一個label。

     級別:預設文檔級別為0,出現的第一層迴圈為1,其內部若再次嵌套迴圈則級別為2,依次類推。

     名稱:label名稱

     註意這裡省略了表序號,當級別為0的時候 ,自動取最後一個datatable中的數據,因為這個label經常會用到其他表彙總的數據,可能會用到之前幾張表的數據,所以放在其他表都處理好後。當級別為1的時候,自然取該級別迴圈的行數據。

C.表格

     表格的配置原本也想對錶格添加書簽,後來發現有個表格屬性, 覺得寫在這裡更好一些。

 如上圖所示,【標題】格式為:table_級別_取Dataset中的第幾張表(從1開始)_filter欄位多個用XX隔開(此處不允許有下劃線外其他特殊字元, 就用這個XX吧 )_名稱

【說明】為可選項,若需要合計行, 則需要標識, summary或縮寫s: [合計]行是模板中表格的第幾行   summaryfilter或縮寫sf:數據集進一步filter到summary行的條件(因為一個表格只取一個Datatable,通過一個標識指定了哪些datarow是用來作為合計的)

D.圖表

同樣為了方便將配置寫在了【標題】,圖表生成後會將名稱修改過來。

配置格式為:chart_級別_取Dataset中的第幾張表(從1開始)_filter欄位多個用XX隔開(此處不允許有下劃線外其他特殊字元, 就用這個XX吧 )_chart名稱_是否將Datatable的columnName作為第一行_從datatable第幾列開始(列起始為1)_截止列,

如下圖所示配置即可。

 

E.目錄

無需標識, 模板中添加目錄, 當內容處理完成之後, 會根據處理後的結果動態更新目錄.

 

四、主要代碼

  1 using System;
  2 using System.Collections.Generic;
  3 using System.Data;
  4 using System.Diagnostics;
  5 using System.IO;
  6 using System.Linq;
  7 using System.Reflection;
  8 using Excel = Microsoft.Office.Interop.Excel;
  9 using Word = Microsoft.Office.Interop.Word;
 10 
 11 namespace FlyLolo.WordReport.Demo
 12 {
 13     public class WordReportHelper
 14     {
 15         private Word.Application wordApp = null;
 16         private Word.Document wordDoc = null;
 17         private DataSet dataSource = null;
 18         private object line = Word.WdUnits.wdLine;
 19         private string errorMsg = "";
 20 
 21         /// <summary>
 22         /// 根據模板文件,創建數據報告
 23         /// </summary>
 24         /// <param name="templateFile">模板文件名(含路徑)</param>
 25         /// <param name="newFilePath">新文件路徑)</param>
 26         /// <param name="dataSource">數據源,包含多個datatable</param>
 27         /// <param name="saveFormat">新文件格式:</param>
 28         public bool CreateReport(string templateFile, DataSet dataSource, out string errorMsg, string newFilePath, ref string newFileName, int saveFormat = 16)
 29         {
 30             this.dataSource = dataSource;
 31             errorMsg = this.errorMsg;
 32             bool rtn = OpenTemplate(templateFile)
 33                 && SetContent(new WordElement(wordDoc.Range(), dataRow: dataSource.Tables[dataSource.Tables.Count - 1].Rows[0]))
 34                 && UpdateTablesOfContents()
 35                 && SaveFile(newFilePath, ref newFileName, saveFormat);
 36 
 37             CloseAndClear();
 38             return rtn;
 39         }
 40 
 41         /// <summary>
 42         /// 打開模板文件
 43         /// </summary>
 44         /// <param name="templateFile"></param>
 45         /// <returns></returns>
 46         private bool OpenTemplate(string templateFile)
 47         {
 48             if (!File.Exists(templateFile))
 49             {
 50                 return false;
 51             }
 52 
 53             wordApp = new Word.Application();
 54             wordApp.Visible = true;//使文檔可見,調試用
 55             wordApp.DisplayAlerts = Word.WdAlertLevel.wdAlertsNone;
 56             object file = templateFile;
 57             wordDoc = wordApp.Documents.Open(ref file, ReadOnly: false);
 58             return true;
 59         }
 60 
 61         /// <summary>
 62         /// 為指定區域寫入數據
 63         /// </summary>
 64         /// <param name="element"></param>
 65         /// <returns></returns>
 66         private bool SetContent(WordElement element)
 67         {
 68             string currBookMarkName = string.Empty;
 69             string startWith = "loop_" + (element.Level + 1).ToString() + "_";
 70             foreach (Word.Bookmark item in element.Range.Bookmarks)
 71             {
 72                 currBookMarkName = item.Name;
 73 
 74                 if (currBookMarkName.StartsWith(startWith) && (!currBookMarkName.Equals(element.ElementName)))
 75                 {
 76                     SetLoop(new WordElement(item.Range, currBookMarkName, element.DataRow, element.GroupBy));
 77                 }
 78 
 79             }
 80 
 81             SetLabel(element);
 82 
 83             SetTable(element);
 84 
 85             SetChart(element);
 86 
 87             return true;
 88         }
 89 
 90         /// <summary>
 91         /// 處理迴圈
 92         /// </summary>
 93         /// <param name="element"></param>
 94         /// <returns></returns>
 95         private bool SetLoop(WordElement element)
 96         {
 97             DataRow[] dataRows = dataSource.Tables[element.TableIndex].Select(element.GroupByString);
 98             int count = dataRows.Count();
 99             element.Range.Select();
100 
101             //第0行作為模板  先從1開始  迴圈後處理0行;
102             for (int i = 0; i < count; i++)
103             {
104 
105                 element.Range.Copy();  //模板loop複製
106                 wordApp.Selection.InsertParagraphAfter();//換行 不會清除選中的內容,TypeParagraph 等同於回車,若當前有選中內容會被清除. TypeParagraph 會跳到下一行,InsertParagraphAfter不會, 所以movedown一下.
107                 wordApp.Selection.MoveDown(ref line, Missing.Value, Missing.Value);
108                 wordApp.Selection.Paste(); //換行後粘貼複製內容
109                 int offset = wordApp.Selection.Range.End - element.Range.End; //計算偏移量
110 
111                 //複製書簽,書簽名 = 模板書簽名 + 複製次數
112                 foreach (Word.Bookmark subBook in element.Range.Bookmarks)
113                 {
114                     if (subBook.Name.Equals(element.ElementName))
115                     {
116                         continue;
117                     }
118 
119                     wordApp.Selection.Bookmarks.Add(subBook.Name + "_" + i.ToString(), wordDoc.Range(subBook.Start + offset, subBook.End + offset));
120                 }
121 
122                 SetContent(new WordElement(wordDoc.Range(wordApp.Selection.Range.End - (element.Range.End - element.Range.Start), wordApp.Selection.Range.End), element.ElementName + "_" + i.ToString(), dataRows[i], element.GroupBy));
123             }
124 
125             element.Range.Delete();
126 
127             return true;
128         }
129 
130         /// <summary>
131         /// 處理簡單Label
132         /// </summary>
133         /// <param name="element"></param>
134         /// <returns></returns>
135         private bool SetLabel(WordElement element)
136         {
137             if (element.Range.Bookmarks != null && element.Range.Bookmarks.Count > 0)
138             {
139                 string startWith = "label_" + element.Level.ToString() + "_";
140                 string bookMarkName = string.Empty;
141                 foreach (Word.Bookmark item in element.Range.Bookmarks)
142                 {
143                     bookMarkName = item.Name;
144 
145                     if (bookMarkName.StartsWith(startWith))
146                     {
147                         bookMarkName = WordElement.GetName(bookMarkName);
148 
149                         item.Range.Text = element.DataRow[bookMarkName].ToString();
150                     }
151                 }
152             }
153 
154             return true;
155         }
156 
157         /// <summary>
158         /// 填充Table
159         /// </summary>
160         /// <param name="element"></param>
161         /// <returns></returns>
162         private bool SetTable(WordElement element)
163         {
164             if (element.Range.Tables != null && element.Range.Tables.Count > 0)
165             {
166                 string startWith = "table_" + element.Level.ToString() + "_";
167                 foreach (Word.Table table in element.Range.Tables)
168                 {
169                     if (!string.IsNullOrEmpty(table.Title) && table.Title.StartsWith(startWith))
170                     {
171                         WordElement tableElement = new WordElement(null, table.Title, element.DataRow);
172 
173                         TableConfig config = new TableConfig(table.Descr);
174 
175                         object dataRowTemplate = table.Rows[config.DataRow];
176                         Word.Row SummaryRow = null;
177                         DataRow SummaryDataRow = null;
178                         DataTable dt = dataSource.Tables[tableElement.TableIndex];
179                         DataRow[] dataRows = dataSource.Tables[tableElement.TableIndex].Select(tableElement.GroupByString); ;
180 
181                         if (config.SummaryRow > 0)
182                         {
183                             SummaryRow = table.Rows[config.SummaryRow];
184                             SummaryDataRow = dt.Select(string.IsNullOrEmpty(tableElement.GroupByString) ? config.SummaryFilter : tableElement.GroupByString + " and  " + config.SummaryFilter).FirstOrDefault();
185                         }
186 
187                         foreach (DataRow row in dataRows)
188                         {
189                             if (row == SummaryDataRow)
190                             {
191                                 continue;
192                             }
193 
194                             Word.Row newRow = table.Rows.Add(ref dataRowTemplate);
195                             for (int j = 0; j < table.Columns.Count; j++)
196                             {
197                                 newRow.Cells[j + 1].Range.Text = row[j].ToString(); ;
198                             }
199 
200                         }
201 
202                         ((Word.Row)dataRowTemplate).Delete();
203 
204                         if (config.SummaryRow > 0 && SummaryDataRow != null)
205                         {
206                             for (int j = 0; j < SummaryRow.Cells.Count; j++)
207                             {
208                                 string temp = SummaryRow.Cells[j + 1].Range.Text.Trim().Replace("\r\a", "");
209 
210                                 if (!string.IsNullOrEmpty(temp) && temp.Length > 2 && dt.Columns.Contains(temp.Substring(1, temp.Length - 2)))
211                                 {
212                                     SummaryRow.Cells[j + 1].Range.Text = SummaryDataRow[temp.Substring(1, temp.Length - 2)].ToString();
213                                 }
214                             }
215                         }
216 
217                         table.Title = tableElement.Name;
218                     }
219 
220 
221                 }
222             }
223 
224             return true;
225         }
226 
227         /// <summary>
228         /// 處理圖表
229         /// </summary>
230         /// <param name="element"></param>
231         /// <returns></returns>
232         private bool SetChart(WordElement element)
233         {
234             if (element.Range.InlineShapes != null && element.Range.InlineShapes.Count > 0)
235             {
236                 List<Word.InlineShape> chartList = element.Range.InlineShapes.Cast<Word.InlineShape>().Where(m => m.Type == Word.WdInlineShapeType.wdInlineShapeChart).ToList();
237                 string startWith = "chart_" + element.Level.ToString() + "_";
238                 foreach (Word.InlineShape item in chartList)
239                 {
240                     Word.Chart chart = item.Chart;
241                     if (!string.IsNullOrEmpty(chart.ChartTitle.Text) && chart.ChartTitle.Text.StartsWith(startWith))
242                     {
243                         WordElement chartElement = new WordElement(null, chart.ChartTitle.Text, element.DataRow);
244 
245                         DataTable dataTable = dataSource.Tables[chartElement.TableIndex];
246                         DataRow[] dataRows = dataTable.Select(chartElement.GroupByString);
247 
248                         int columnCount = dataTable.Columns.Count;
249                         List<int> columns = new List<int>();
250 
251                         foreach (var dr in dataRows)
252                         {
253                             for (int i = chartElement.ColumnStart == -1 ? 0 : chartElement.ColumnStart - 1; i < (chartElement.ColumnEnd == -1 ? columnCount : chartElement.ColumnEnd); i++)
254                             {
255                                 if (columns.Contains(i) || dr[i] == null || string.IsNullOrEmpty(dr[i].ToString()))
256                                 {
257 
258                                 }
259                                 else
260                                 {
261                                     columns.Add(i);
262                                 }
263                             }
264                         }
265                         columns.Sort();
266                         columnCount = columns.Count;
267                         int rowsCount = dataRows.Length;
268 
269                         Word.ChartData chartData = chart.ChartData;
270 
271                         //chartData.Activate();
272                         //此處有個比較疑惑的問題, 不執行此條,生成的報告中的圖表無法再次右鍵編輯數據. 執行後可以, 但有兩個問題就是第一會彈出Excel框, 處理完後會自動關閉. 第二部分chart的數據range設置總不對
273                         //不知道是不是版本的問題, 誰解決了分享一下,謝謝
274 
275                         Excel.Workbook dataWorkbook = (Excel.Workbook)chartData.Workbook;
276                         dataWorkbook.Application.Visible = false;
277 
278                         Excel.Worksheet dataSheet = (Excel.Worksheet)dataWorkbook.Worksheets[1];
279                         //設定範圍  
280                         string a = (chartElement.ColumnNameForHead ? rowsCount + 1 : rowsCount) + "|" + columnCount;
281                         Console.WriteLine(a);
282 
283                         Excel.Range tRange = dataSheet.Range["A1", dataSheet.Cells[(chartElement.ColumnNameForHead ? rowsCount + 1 : rowsCount), columnCount]];
284                         Excel.ListObject tbl1 = dataSheet.ListObjects[1];
285                         //dataSheet.ListObjects[1].Delete(); //想過重新刪除再添加  這樣 原有數據清掉了, 但覺得性能應該會有所下降
286                         //Excel.ListObject tbl1 = dataSheet.ListObjects.AddEx();
287                         tbl1.Resize(tRange);
288                         for (int j = 0; j < rowsCount; j++)
289                         {
290                             DataRow row = dataRows[j];
291                             for (int k = 0; k < columnCount; k++)
292                             {
293                                 dataSheet.Cells[j + 2, k + 1].FormulaR1C1 = row[columns[k]];
294                             }
295                         }
296 
297                         if (chartElement.ColumnNameForHead)
298                         {
299                             for (int k = 0; k < columns.Count; k++)
300                             {
301                                 dataSheet.Cells[1, k + 1].FormulaR1C1 = dataTable.Columns[columns[k]].ColumnName;
302                             }
303                         }
304                         chart.ChartTitle.Text = chartElement.Name;
305                         //dataSheet.Application.Quit();
306                     }
307                 }
308             }
309 
310             return true;
311         }
312 
313         /// <summary>
314         /// 更新目錄
315         /// </summary>
316         /// <returns></returns>
317         private bool UpdateTablesOfContents()
318         {
319             foreach (Word.TableOfContents item in wordDoc.TablesOfContents)
320             {
321                 item.Update();
322             }
323 
324             return true;
325         }
326 
327         /// <summary>
328         /// 保存文件
329         /// </summary>
330         /// <param name="newFilePath"></param>
331         /// <param name="newFileName"></param>
332         /// <param name="saveFormat"></param>
333         /// <returns></returns>
334         private bool SaveFile(string newFilePath, ref string newFileName, int saveFormat = 16)
335         {
336             if (string.IsNullOrEmpty(newFileName))
337             {
338                 newFileName = DateTime.Now.ToString("yyyyMMddHHmmss");
339 
340                 switch (saveFormat)
341                 {
342                     case 0:// Word.WdSaveFormat.wdFormatDocument
343                         newFileName += ".doc";
344                         break;
345                     case 16:// Word.WdSaveFormat.wdFormatDocumentDefault
346                         newFileName += ".docx";
347                         break;
348                     case 17:// Word.WdSaveFormat.wdFormatPDF
349                         newFileName += ".pdf";
350                         break;
351                     default:
352                         break;
353                 }
354             }
355 
356             object newfile = Path.Combine(newFilePath, newFileName);
357             object wdSaveFormat = saveFormat;
358             wordDoc.SaveAs(ref newfile, ref wdSaveFormat);
359             return true;
360         }
361 
362         /// <summary>
363         /// 清理
364         /// </summary>
365         private void CloseAndClear()
366         {
367             if (wordApp == null)
368             {
369                 return;
370             }
371             wordDoc.Close(Word.WdSaveOptions.wdDoNotSaveChanges);
372             wordApp.Quit(Word.WdSaveOptions.wdDoNotSaveChanges);
373             System.Runtime.InteropServices.Marshal.ReleaseComObject(wordDoc);
374             System.Runtime.InteropServices.Marshal.ReleaseComObject(wordApp);
375             wordDoc = null;
376             wordApp = null;
	   

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

-Advertisement-
Play Games
更多相關文章
  • 這是一個學生選課信息管理系統,使用VS2010+SQL2008編寫,VS2017正常使用。 項目源碼下載地址 https://gitee.com/whuanle/xkgl 學生選課管理系統 項目介紹 這是一個學生選課信息管理系統,使用VS2010+SQL2008編寫。 在 VS2017+SQL201 ...
  • VS2015,ASP.NET MVC5 中使用spring.net ...
  • 框架與依賴 框架:.NET Standard 2.0 依賴:DotNetCore.NPOI https://github.com/dotnetcore/NPOI http://www.cnblogs.com/savorboard/p/netcore npoi.html Excel導入(ExcelIm ...
  • 1.LINQ代表語言集成查詢。 2.LINQ是.NET框架擴展,允許我們以使用SQL查詢資料庫的方式來查詢數據集合。 3.使用LINQ,你可以從資料庫、程式對象的集合以及xml文檔中查詢數據。 demo: LINQ中需要用到匿名類型,創建匿名類型的變數使用相同的形式,但是沒有類名和構造函數。 匿名類 ...
  • 一個偶爾出現令人不爽的WPF界面顯示問題:“未將對象引用設置到對象的實例”。 ...
  • 使用VIM編輯commit註釋信息在命令輸入模式下麵,輸入字母”i”,則VIM進入到插入模式,接著輸入自己的註釋內容;完成註釋後需要退出:1)按鍵Esc,如果無效,連續按兩次2)當底部提示行出現空白時,輸入冒號“:”3)再輸入字母“q”,回車 (輸入wq,為保存退出)但是實際上使用vim非常不方便,... ...
  • 因項目需要,傳輸數據需要加密,因此有了一些經驗,現簡易抽出來分享! 請求:前端cryptojs用rsa/aes 或 rsa/des加密,後端.net 解密返回後端.net用rsa/aes 或 rsa/des加密,前端cryptojs解密 圖示: 數據發送加密: 返回數據加密: 開源代碼分享:http ...
  • 前言 core也用了很長一段時間了,發現很多小伙伴不知道如何調試core的代碼。 可想而知,以前使用mvc的時候,不需要發佈代碼,直接iis地址指向項目源碼,然後附加到進程w3wp.exe就可以調試了。 在core的項目裡面已經不能這樣玩了... 正文 1. 安裝Open Command Line ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...