IT輪子系列(六)——Excel上傳與解析,一套代碼解決所有Excel業務上傳,你Get到了嗎
前言
在日常開發當中,excel的上傳與解析是很常見的。根據業務不同,解析的數據模型也都不一樣。不同的數據模型也就需要不同的校驗邏輯,這往往需要寫多套的代碼進行字段的檢驗,如必填項,數據格式。為了避免重復編寫邏輯檢驗代碼,於是有了這篇文章。
第一步、讀取Excel表格數據
1 public ActionResult UploadExcel() 2 { 3 ResultInfo<List<User>> result = new ResultInfo<List<User>>();前端界面-上傳Excel4 5 var files = Request.Files; 6 var form = Request.Form; 7 if (files.Count > 0) 8 { 9 var file = files[0]; 10 //獲取文件擴展名 11 var ext = System.IO.Path.GetExtension(file.FileName); 12 varnewPath = ""; 13 try 14 { 15 //獲取文件路徑 16 var path = HttpContext.Server.MapPath("/Upload/Excels/"); 17 if (!System.IO.Directory.Exists(path)) 18 { 19 System.IO.Directory.CreateDirectory(path);20 } 21 //保存文件 22 var newFileName = System.Guid.NewGuid().ToString("N") + ext;//新文件名 23 newPath = path + newFileName; 24 file.SaveAs(newPath); 25 DataTable dt = FileUtil.ExcelToDataTable(newPath, true); 26 result = ValidateExcelData<User>(dt); 27 } 28 catch (Exception ex) 29 { 30 result.IsError = true; 31 result.Msg = ex.Message; 32 } 33 finally 34 { 35 //清除文件 36 System.IO.File.Delete(newPath); 37 } 38 } 39 var obj = new { 40 success = result.IsError, 41 msg = result.Msg 42 }; 43 return Json(obj); 44 }
PS:這裏的上傳,使用了前面文章中關於文件上傳的,如有不明白,自行翻看前面IT輪子系列的文章拉。
關於excel的讀取,使用的是NPOI組件,網上關於NPOI的文章也很多,這裏也不再重復。代碼(這代碼也是從網上copy來的,已經找不到連接了,在這裏感謝一下這位博友): )如下:
1 /// <summary> 2 /// 將excel導入到datatable 3 /// </summary> 4 /// <param name="filePath">excel路徑</param> 5 /// <param name="isColumnName">第一行是否是列名</param> 6 /// <returns>返回datatable</returns> 7 public static DataTable ExcelToDataTable(string filePath, bool isColumnName) 8 { 9 DataTable dataTable = null; 10 FileStream fs = null; 11 DataColumn column = null; 12 DataRow dataRow = null; 13 IWorkbook workbook = null; 14 ISheet sheet = null; 15 IRow row = null; 16 ICell cell = null; 17 int startRow = 0; 18 try 19 { 20 using (fs = File.OpenRead(filePath)) 21 { 22 // 2007版本 23 if (filePath.IndexOf(".xlsx") > 0) 24 workbook = new XSSFWorkbook(fs); 25 // 2003版本 26 else if (filePath.IndexOf(".xls") > 0) 27 workbook = new HSSFWorkbook(fs); 28 29 if (workbook != null) 30 { 31 sheet = workbook.GetSheetAt(0);//讀取第一個sheet,當然也可以循環讀取每個sheet 32 dataTable = new DataTable(); 33 if (sheet != null) 34 { 35 int rowCount = sheet.LastRowNum;//總行數 36 if (rowCount > 0) 37 { 38 IRow firstRow = sheet.GetRow(0);//第一行 39 int cellCount = firstRow.LastCellNum;//列數 40 41 //構建datatable的列 42 if (isColumnName) 43 { 44 startRow = 1;//如果第一行是列名,則從第二行開始讀取 45 for (int i = firstRow.FirstCellNum; i < cellCount; ++i) 46 { 47 cell = firstRow.GetCell(i); 48 if (cell != null) 49 { 50 if (cell.StringCellValue != null) 51 { 52 column = new DataColumn(cell.StringCellValue); 53 dataTable.Columns.Add(column); 54 } 55 } 56 } 57 } 58 else 59 { 60 for (int i = firstRow.FirstCellNum; i < cellCount; ++i) 61 { 62 column = new DataColumn("column" + (i + 1)); 63 dataTable.Columns.Add(column); 64 } 65 } 66 67 //填充行 68 for (int i = startRow; i <= rowCount; ++i) 69 { 70 row = sheet.GetRow(i); 71 if (row == null) continue; 72 73 dataRow = dataTable.NewRow(); 74 for (int j = row.FirstCellNum; j < cellCount; ++j) 75 { 76 cell = row.GetCell(j); 77 if (cell == null) 78 { 79 dataRow[j] = ""; 80 } 81 else 82 { 83 //CellType(Unknown = -1,Numeric = 0,String = 1,Formula = 2,Blank = 3,Boolean = 4,Error = 5,) 84 switch (cell.CellType) 85 { 86 case CellType.Blank: 87 dataRow[j] = ""; 88 break; 89 case CellType.Numeric: 90 short format = cell.CellStyle.DataFormat; 91 //對時間格式(2015.12.5、2015/12/5、2015-12-5等)的處理 92 if (format == 14 || format == 31 || format == 57 || format == 58) 93 dataRow[j] = cell.DateCellValue; 94 else 95 dataRow[j] = cell.NumericCellValue; 96 break; 97 case CellType.String: 98 dataRow[j] = cell.StringCellValue; 99 break; 100 } 101 } 102 } 103 dataTable.Rows.Add(dataRow); 104 } 105 } 106 } 107 } 108 } 109 return dataTable; 110 } 111 catch (Exception) 112 { 113 if (fs != null) 114 { 115 fs.Close(); 116 } 117 return null; 118 } 119 }NPOI讀取Excel
第二步、使用泛型檢驗數據
這裏所說的通用,只不過是把檢驗的規則、配置放到了數據庫中。然後根據類名從數據庫讀取配置規則。在這篇文章中,配置是沒有使用到數據庫的,直接使用了一個配置類,代碼如下:
1 /// <summary> 2 /// excel導入配置表 3 /// </summary> 4 public class Sys_ExcelImportConfig 5 { 6 /// <summary> 7 /// 表名 8 /// </summary> 9 public string TableName { get; set; } 10 /// <summary> 11 /// 表列名 12 /// </summary> 13 public string TableColumnName { get; set; } 14 /// <summary> 15 /// excel列名 16 /// </summary> 17 public string ExcelColumnName { get; set; } 18 /// <summary> 19 /// 數據類型 20 /// </summary> 21 public string DataType { get; set; } 22 /// <summary> 23 /// 是否必填項 24 /// </summary> 25 public bool IsRequired { get; set; } 26 /// <summary> 27 /// 是否值類型 28 /// </summary> 29 public bool IsValueType { get; set; } 30 /// <summary> 31 /// 是否檢驗數據格式,如手機號、email 32 /// </summary> 33 public bool IsDataChecked { get; set; } 34 /// <summary> 35 /// 正則表達式 若IsDataChecked為true則使用正則進行校驗 36 /// </summary> 37 public string Reg { get; set; } 38 39 }配置數據模型
PS:在實際項目中,可以在後臺添加一個配置界面。這個配置數據模型就對應數據庫中一個表。因此,如果系統需要導入多個業務模型的Excel 只需做好配置就OK拉。
數據解析的泛型方法
代碼如下:
1 /// <summary> 2 /// 驗證excel數據的格式 3 /// </summary> 4 /// <typeparam name="T">泛型</typeparam> 5 /// <param name="dt">NPOI讀取的數據表</param> 6 /// <returns>泛型結果集</returns> 7 private ResultInfo<List<T>> ValidateExcelData<T>(DataTable dt) where T : new() 8 { 9 ResultInfo<List<T>> result = new ResultInfo<List<T>>(); 10 //從數據庫讀取本次導入的excel配置表 11 /* 12 * 這裏的demo就直接寫到程序裏,不做數據庫配置 13 * 在實際項目中,list可以從一個表獲取,類的屬性就對應 14 * 表中配置的字段 15 */ 16 //1、定義配置數據源,這裏配置三列 17 List<Sys_ExcelImportConfig> configList = new List<Sys_ExcelImportConfig> { 18 new Sys_ExcelImportConfig{ 19 TableName="User", 20 TableColumnName="Name", 21 DataType=typeof(System.String).FullName, 22 ExcelColumnName="姓名", 23 IsRequired = true 24 }, 25 new Sys_ExcelImportConfig{ 26 TableName="User", 27 TableColumnName="Position", 28 DataType=typeof(System.String).FullName, 29 ExcelColumnName="職位", 30 IsRequired = true 31 }, 32 new Sys_ExcelImportConfig{ 33 TableName="User", 34 TableColumnName="Age", 35 DataType=typeof(int).FullName, 36 ExcelColumnName="年齡", 37 IsValueType = true 38 } 39 }; 40 //2、遍歷數據源 41 var count = dt.Rows.Count; 42 var isError = false; 43 var msg = ""; 44 if (count > 0) 45 { 46 //獲取所有的公共屬性 47 var proInfos = typeof(T).GetProperties(); 48 //遍歷所有的行 49 for (int i = 0; i < count; i++) 50 { 51 T obj = new T(); 52 /* 53 * 遍歷所有的配置列 54 * 不在配置列中都不導入 55 */ 56 foreach (Sys_ExcelImportConfig config in configList) 57 { 58 var isContain = dt.Columns.Contains(config.ExcelColumnName); 59 if (isContain)//如果包含 60 { 61 //讀取該行該列的值 62 var value = dt.Rows[i][config.ExcelColumnName].ToString(); 63 if (config.IsRequired)//是否為表填項 64 { 65 if ("".Equals(value))//為空,退出循環體 66 { 67 isError = true; 68 //返回錯誤信息 69 msg = string.Format("Excel表中第{0}中{1}的值不允許為空", i, config.ExcelColumnName); 70 break; 71 } 72 } 73 if (config.IsValueType)//是否值類型:是,則驗證數據格式 74 { 75 Type methodType = Type.GetType(config.DataType); 76 77 //這裏關鍵的是& 引用類型的參數 78 Type[] parameters = new Type[2] { typeof(string), Type.GetType(config.DataType + "&") }; 79 var method = methodType.GetMethod("TryParse", parameters); 80 Object[] paraObjs = new Object[2]; 81 paraObjs[0] = value; 82 var objResult = method.Invoke(null, paraObjs); 83 //值類型是否轉換成功 84 if (!(bool)objResult) 85 { 86 isError = true; 87 //返回錯誤信息 88 msg = string.Format("Excel表中第{0}行中[{1}]的值數據格式不正確", i + 1, config.ExcelColumnName); 89 break; 90 } 91 } 92 93 //給公共屬性並賦值 94 var property = proInfos.FirstOrDefault(t => t.Name == config.TableColumnName); 95 if (property != null) 96 { 97 property.SetValue(obj, value); 98 } 99 } 100 } 101 if (isError) 102 { 103 //退出所有的循環 104 break; 105 } 106 //驗證通過 107 //添加到數據列表 108 result.Data.Add(obj); 109 } 110 } 111 /* 112 * 這裏可以返回isError和msg 113 */ 114 result.IsError = isError; 115 result.Msg = msg; 116 return result; 117 }泛型解析方法
代碼中註釋寫的很詳細,如有看不明白,歡迎砸磚頭和留言......
對於其他業務的EXCEL上傳,只需在後臺做配置就可以了,將數據模型傳到方法中,如demo中的User. 在拿到返回的List後 可以做進一步的處理,如寫入到數據庫。在實際項目中,為了做到共用,可以將這個泛型方法放到一個common項目中,這樣別的項目可以直接引用這個common項目。
1 public class ResultInfo<T> 2 { 3 public ResultInfo() 4 { 5 IsError = false; 6 Msg = "操作成功"; 7 Data = default(T); 8 } 9 /// <summary> 10 /// 狀態true/false 11 /// </summary> 12 public bool IsError { get; set; } 13 /// <summary> 14 /// 結果信息 15 /// </summary> 16 public string Msg { get; set; } 17 /// <summary> 18 /// 數據 19 /// </summary> 20 public T Data { get; set; } 21 }ResultInfo數據模型
後記
從10月初,確切的說9月29號起,也寫了7、8篇技術類文章。有的文章也有幾百的閱讀量,可評論留言的人卻少之又少,點贊、推薦就更沒有。如果這些文章確實幫到了你,對你的工作有那麽一點點的用,希望路過的兄弟姐妹們可以有贊的點個贊,有推薦的來個推薦,有轉載的來個轉載,為我這個博客園增添點人氣。
謝謝拉。。。。。。。 GOOD NIGHT.
ps:最後來張閱讀量的截圖:
IT輪子系列(六)——Excel上傳與解析,一套代碼解決所有Excel業務上傳,你Get到了嗎