Skip to content

Commit a8a474c

Browse files
committed
- 封装导入导出业务,目前仅支持 Excel,后续将支持 CSV 以及其他业务
- 配置特性即可控制相关逻辑和显示结果,无需修改逻辑代码 - 推荐配合 DTO 使用 - 导出支持列头自定义处理以便支持多语言等场景 - 导出支持文本自定义过滤或处理 - 导入支持自动根据 DTO 生成导入模板及进行模板验证 - 导入支持数据验证逻辑 - 导入支持数据下拉选择 - 导入数据支持前后空格以及中间空格处理,允许指定列进行设置 - 导入提供统一错误封装,包含异常、模板错误和行数据错误 - 支持导入表头位置设置,默认为1 - 支持导入列乱序,无需按顺序一一对应 - 支持导入指定列索引,默认自动识别
1 parent 233df88 commit a8a474c

32 files changed

+890
-303
lines changed

README.md

Lines changed: 55 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Magicodes.ExporterAndImporter
22

3-
导入导出通用库
3+
导入导出通用库,目前仅支持导入导出Excel。
44

55
### 特点
66

@@ -13,6 +13,10 @@
1313
- 导入支持数据验证逻辑
1414
- 导入支持数据下拉选择
1515
- 导入数据支持前后空格以及中间空格处理,允许指定列进行设置
16+
- 导入提供统一错误封装,包含异常、模板错误和行数据错误
17+
- 支持导入表头位置设置,默认为1
18+
- 支持导入列乱序,无需按顺序一一对应
19+
- 支持导入指定列索引,默认自动识别
1620

1721
### 相关官方Nuget包
1822

@@ -23,22 +27,34 @@
2327

2428
### VNext
2529

26-
- 统一导入错误信息,支持统一返回模板校验错误信息和数据校验错误信息
30+
- 导入模板必填项支持自定义配置
31+
- 对错误数据进行标注
2732

2833
### 更新历史
2934

35+
#### 2019.9.18
36+
37+
- 【导入】重构导入模块,统一导入错误消息
38+
- Exception :导入异常信息
39+
- RowErrors : 数据错误信息
40+
- TemplateErrors :模板错误信息,支持错误分级
41+
- 【导入】基础类型必填自动识别,比如int、double等不可为空类型自动识别,无需额外设置Required
42+
- 【导入】修改Excel模板的Sheet名称
43+
- 【导入】支持导入表头位置设置,默认为1
44+
- 【导入】支持列乱序
45+
- 【导入】支持列索引设置
46+
3047
#### 2019.9.11
3148

32-
- 导入支持自动去除前后空格,默认启用,可以针对列进行关闭,具体见AutoTrim设置
33-
- 导入Dto的字段允许不设置ImporterHeader,支持通过DisplayAttribute特性获取列名
34-
- 导入的Excel移除对Sheet名称的约束,默认获取第一个Sheet
35-
- 导入增加对中间空格的处理支持,需设置FixAllSpace
36-
- 导入完善对日期类型的支持
37-
- 完善导入的单元测试
49+
- 【导入】导入支持自动去除前后空格,默认启用,可以针对列进行关闭,具体见AutoTrim设置
50+
- 【导入】导入Dto的字段允许不设置ImporterHeader,支持通过DisplayAttribute特性获取列名
51+
- 【导入】导入的Excel移除对Sheet名称的约束,默认获取第一个Sheet
52+
- 【导入】导入增加对中间空格的处理支持,需设置FixAllSpace
53+
- 【导入】导入完善对日期类型的支持
54+
- 【导入】完善导入的单元测试
3855

3956
### 导出 Demo
4057

41-
4258
---
4359
#### Demo1-1
4460

@@ -54,7 +70,7 @@
5470
public string Name3 { get; set; }
5571
public string Name4 { get; set; }
5672
}
57-
73+
5874
var result = await Exporter.Export(filePath, new List<ExportTestData>()
5975
{
6076
new ExportTestData()
@@ -73,7 +89,6 @@
7389
}
7490
});
7591

76-
7792
---
7893
#### Demo1-2
7994

@@ -87,16 +102,16 @@
87102
{
88103
[ExporterHeader(DisplayName = "加粗文本", IsBold = true)]
89104
public string Text { get; set; }
90-
105+
91106
[ExporterHeader(DisplayName = "普通文本")]
92107
public string Text2 { get; set; }
93-
108+
94109
[ExporterHeader(DisplayName = "忽略", IsIgnore = true)]
95110
public string Text3 { get; set; }
96-
111+
97112
[ExporterHeader(DisplayName = "数值", Format = "#,##0")]
98113
public double Number { get; set; }
99-
114+
100115
[ExporterHeader(DisplayName = "名称", IsAutoFit = true)]
101116
public string Name { get; set; }
102117
}
@@ -140,16 +155,16 @@
140155
{
141156
[ExporterHeader(DisplayName = "加粗文本", IsBold = true)]
142157
public string Text { get; set; }
143-
158+
144159
[ExporterHeader(DisplayName = "普通文本")]
145160
public string Text2 { get; set; }
146-
161+
147162
[ExporterHeader(DisplayName = "忽略", IsIgnore = true)]
148163
public string Text3 { get; set; }
149-
164+
150165
[ExporterHeader(DisplayName = "数值", Format = "#,##0")]
151166
public double Number { get; set; }
152-
167+
153168
[ExporterHeader(DisplayName = "名称", IsAutoFit = true)]
154169
public string Name { get; set; }
155170
}
@@ -161,10 +176,10 @@
161176
}
162177
return "未知语言";
163178
}).Build();
164-
179+
165180
var filePath = Path.Combine(Directory.GetCurrentDirectory(), "testAttrsLocalization.xlsx");
166181
if (File.Exists(filePath)) File.Delete(filePath);
167-
182+
168183
var result = await Exporter.Export(filePath, new List<AttrsLocalizationTestData>()
169184
{
170185
new AttrsLocalizationTestData()
@@ -194,29 +209,29 @@
194209
});
195210

196211
### 导入 Demo
197-
>导入特性(**ImporterHeader**):
198-
199-
+ **Name**:表头显示名称(不可为空)。
200212

201-
+ **Description**:表头添加注释。
213+
#### 导入特性(**ImporterAttribute**):
202214

203-
+ **Author**:注释作者,默认值为X.M。
204-
205-
>导入结果(**ImportModel\<T>**):
206-
207-
+ **Data*****IList\<T>*** 导入的数据集合。
215+
- **HeaderRowIndex**:表头位置
208216

209-
+ **ValidationResults*****IList\<ValidationResultModel>*** 数据验证结果。
217+
#### 导入列头特性(**ImporterHeader**):
210218

211-
+ **HasValidTemplate*****bool*** 模板验证是否通过。
219+
+ **Name**:表头显示名称(不可为空)。
220+
+ **Description**:表头添加注释。
221+
+ **Author**:注释作者,默认值为“麦扣”。
222+
+ **AutoTrim**:自动过滤空格,默认启用。
223+
+ **FixAllSpace**:处理掉所有的空格,包括中间空格。默认false。
224+
+ **ColumnIndex**:列索引,一般不建议设置。
212225

213-
>数据验证结果(**ValidationResultModel**):
226+
#### 导入结果(ImportResult):
214227

215-
+ **Index*****int*** 错误数据所在行。
228+
+ **Data*****IList\<T>*** 导入的数据集合。
229+
+ **RowErrors*****IList<DataRowErrorInfo>*** 数据行错误。
230+
+ **HasError*****bool*** 是否存在导入错误。
231+
+ **Exception**:异常信息
232+
+ **TemplateErrors**:模板错误信息
216233

217-
+ **Errors*****IDictionary<string, string>*** 整个Excel错误集合。目前仅支持数据验证错误。
218234

219-
+ **FieldErrors*****IDictionary<string, string>*** 数据验证错误。
220235

221236
---
222237
#### Demo2-1 普通模板
@@ -310,7 +325,7 @@
310325
/// </summary>
311326
[ImporterHeader(Name = "类型")]
312327
public ImporterProductType Type { get; set; }
313-
328+
314329
/// <summary>
315330
/// 是否行
316331
/// </summary>
@@ -399,7 +414,7 @@
399414
/// </summary>
400415
[ImporterHeader(Name = "类型")]
401416
public ImporterProductType Type { get; set; }
402-
417+
403418
/// <summary>
404419
/// 是否行
405420
/// </summary>
@@ -448,10 +463,10 @@ Dockerfile Demo
448463
COPY . .
449464
WORKDIR "/src/src/web/Admin.Host"
450465
RUN dotnet build "Admin.Host.csproj" -c Release -o /app
451-
466+
452467
FROM build AS publish
453468
RUN dotnet publish "Admin.Host.csproj" -c Release -o /app
454-
469+
455470
FROM base AS final
456471
WORKDIR /app
457472
COPY --from=publish /app .

src/Magicodes.ExporterAndImporter.Core/Extension/TypeExtensions.cs

Lines changed: 65 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
/// </summary>
1717
public static class TypeExtensions
1818
{
19-
19+
2020
/// <summary>
2121
/// 获取显示名
2222
/// </summary>
@@ -94,6 +94,32 @@ public static bool AttributeExists<T>(this ICustomAttributeProvider assembly, bo
9494
return assembly.GetCustomAttributes(typeof(T), inherit).Any(m => (m as T) != null);
9595
}
9696

97+
/// <summary>
98+
/// 是否必填
99+
/// </summary>
100+
/// <param name="propertyInfo"></param>
101+
/// <returns></returns>
102+
public static bool IsRequired(this PropertyInfo propertyInfo)
103+
{
104+
if (propertyInfo.GetAttribute<RequiredAttribute>(true) != null)
105+
{
106+
return true;
107+
}
108+
//Boolean、Byte、SByte、Int16、UInt16、Int32、UInt32、Int64、UInt64、Char、Double、Single
109+
if (propertyInfo.PropertyType.IsPrimitive)
110+
{
111+
return true;
112+
}
113+
switch (propertyInfo.PropertyType.Name)
114+
{
115+
case "DateTime":
116+
case "Decimal":
117+
return true;
118+
default:
119+
break;
120+
}
121+
return false;
122+
}
97123

98124
/// <summary>
99125
/// 获取当前程序集中应用此特性的类
@@ -145,6 +171,30 @@ public static List<Tuple<string, int, string>> GetEnumDefinitionList(this Type t
145171
return list;
146172
}
147173

174+
/// <summary>
175+
/// 获取枚举显示名称
176+
/// </summary>
177+
/// <param name="type"></param>
178+
/// <returns></returns>
179+
public static IDictionary<string, int> GetEnumDisplayNames(this Type type)
180+
{
181+
if (!type.IsEnum) throw new InvalidOperationException();
182+
var names = Enum.GetNames(type);
183+
IDictionary<string, int> displayNames = new Dictionary<string, int>();
184+
foreach (var name in names)
185+
{
186+
var displayAttribute = type.GetField(name)
187+
.GetCustomAttributes(typeof(DisplayAttribute), false)
188+
.SingleOrDefault() as DisplayAttribute;
189+
if (displayAttribute != null)
190+
{
191+
var value = (int)Enum.Parse(type, name);
192+
displayNames.Add(displayAttribute.Name, value);
193+
}
194+
}
195+
return displayNames;
196+
}
197+
148198
/// <summary>
149199
/// 获取类的所有枚举
150200
/// </summary>
@@ -160,5 +210,19 @@ public static Dictionary<string, List<Tuple<string, int, string>>> GetClassEnumD
160210
}
161211
return dic;
162212
}
213+
214+
215+
public static string GetCSharpTypeName(this Type type)
216+
{
217+
var sb = new StringBuilder();
218+
var name = type.Name;
219+
if (!type.IsGenericType) return name;
220+
sb.Append(name.Substring(0, name.IndexOf('`')));
221+
sb.Append("<");
222+
sb.Append(string.Join(", ", type.GetGenericArguments()
223+
.Select(t => t.GetCSharpTypeName())));
224+
sb.Append(">");
225+
return sb.ToString();
226+
}
163227
}
164228
}

src/Magicodes.ExporterAndImporter.Core/IExporter.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ public interface IExporter
1515
/// <param name="fileName">文件名称</param>
1616
/// <param name="dataItems">数据</param>
1717
/// <returns>文件</returns>
18-
Task<ExcelFileInfo> Export<T>(string fileName, IList<T> dataItems) where T : class;
18+
Task<TemplateFileInfo> Export<T>(string fileName, IList<T> dataItems) where T : class;
1919

2020
/// <summary>
2121
/// 导出Excel

src/Magicodes.ExporterAndImporter.Core/IImporter.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ public interface IImporter
1515
/// </summary>
1616
/// <typeparam name="T"></typeparam>
1717
/// <returns></returns>
18-
Task<ExcelFileInfo> GenerateTemplate<T>(string fileName) where T : class;
18+
Task<TemplateFileInfo> GenerateTemplate<T>(string fileName) where T : class;
1919

2020
/// <summary>
2121
/// 生成Excel导入模板

src/Magicodes.ExporterAndImporter.Core/ImporterAttribute.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,10 @@ public class ImporterAttribute : Attribute
88
public ImporterAttribute()
99
{
1010
}
11+
12+
/// <summary>
13+
/// 表头位置
14+
/// </summary>
15+
public int HeaderRowIndex { get; set; } = 1;
1116
}
1217
}

src/Magicodes.ExporterAndImporter.Core/ImporterHeaderAttribute.cs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ public ImporterHeaderAttribute()
2323
/// <summary>
2424
/// 作者
2525
/// </summary>
26-
public string Author { set; get; } = "X.M";
26+
public string Author { set; get; } = "麦扣";
2727

2828
/// <summary>
2929
/// 自动过滤空格,默认启用
@@ -34,5 +34,10 @@ public ImporterHeaderAttribute()
3434
/// 处理掉所有的空格,包括中间空格
3535
/// </summary>
3636
public bool FixAllSpace { get; set; }
37+
38+
/// <summary>
39+
/// 列索引,如果为0则自动计算
40+
/// </summary>
41+
public int ColumnIndex { get; set; }
3742
}
3843
}

src/Magicodes.ExporterAndImporter.Core/Magicodes.ExporterAndImporter.Core.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
<Import Project="..\..\common.props"></Import>
33
<PropertyGroup>
44
<TargetFramework>netstandard2.0</TargetFramework>
5-
<Version>1.1.0</Version>
5+
<Version>1.1.1</Version>
66
<PackageId>Magicodes.IE.Core</PackageId>
77
<Authors>湖南心莱信息科技有限公司</Authors>
88
<PackageProjectUrl>https://github.com/xin-lai/Magicodes.ExporterAndImporter</PackageProjectUrl>
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
using System.Collections.Generic;
2+
3+
namespace Magicodes.ExporterAndImporter.Core.Models
4+
{
5+
/// <summary>
6+
/// 数据行错误信息
7+
/// </summary>
8+
public class DataRowErrorInfo
9+
{
10+
/// <summary>
11+
/// Initializes a new instance of the <see cref="DataRowErrorInfo"/> class.
12+
/// </summary>
13+
public DataRowErrorInfo()
14+
{
15+
FieldErrors = new Dictionary<string, string>();
16+
}
17+
18+
/// <summary>
19+
/// 序号
20+
/// </summary>
21+
public int RowIndex { get; set; }
22+
23+
/// <summary>
24+
/// 字段错误信息
25+
/// </summary>
26+
public IDictionary<string, string> FieldErrors { get; set; }
27+
}
28+
}

0 commit comments

Comments
 (0)