@elf-express/admin-net-mcp 1.0.0 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +83 -88
- package/knowledge/Furion_Teaching_Manual/04-1-/351/205/215/347/275/256.md +442 -0
- package/knowledge/Furion_Teaching_Manual/04-2-/351/201/270/351/240/205.md +363 -0
- package/knowledge/Furion_Teaching_Manual/05-1-/345/213/225/346/205/213WebAPI.md +825 -0
- package/knowledge/Furion_Teaching_Manual/05-2-HttpContext.md +217 -0
- package/knowledge/Furion_Teaching_Manual/05-3-/347/257/251/351/201/270/345/231/250/346/224/224/346/210/252/345/231/250AOP.md +581 -0
- package/knowledge/Furion_Teaching_Manual/05-4-/350/253/213/346/261/202/347/250/275/346/240/270/346/227/245/350/252/214.md +129 -0
- package/knowledge/Furion_Teaching_Manual/05-5-/344/270/255/344/273/213/350/273/237/351/253/224Middleware.md +328 -0
- package/knowledge/Furion_Teaching_Manual/05-6-Vue-React-Angular/344/273/213/351/235/242/344/273/243/347/220/206.md +317 -0
- package/knowledge/Furion_Teaching_Manual/06-1/350/246/217/347/257/204/345/214/226/346/216/245/345/217/243.md +1458 -0
- package/knowledge/Furion_Teaching_Manual/06-2/347/254/254/344/270/211/346/226/271API_Scalar.md +91 -0
- package/knowledge/Furion_Teaching_Manual/07-/345/217/213/345/245/275/344/276/213/345/244/226/350/231/225/347/220/206.md +511 -0
- package/knowledge/Furion_Teaching_Manual/08-1-/350/263/207/346/226/231/351/251/227/350/255/211/345/237/272/347/244/216/344/275/277/347/224/250.md +587 -0
- package/knowledge/Furion_Teaching_Manual/10-1-SqlSugar/346/225/264/345/220/210.md +336 -0
- package/knowledge/Furion_Teaching_Manual/11-SaaS /345/244/232/347/247/237/346/210/266/347/255/206/350/250/230.md" +271 -0
- package/knowledge/Furion_Teaching_Manual/12-furion-dependency-injection.md +408 -0
- package/knowledge/Furion_Teaching_Manual/13-/347/211/251/344/273/266/350/263/207/346/226/231/346/230/240/345/260/204/357/274/210Mapster/357/274/211.md +162 -0
- package/knowledge/Furion_Teaching_Manual/14-/345/210/206/345/270/203/345/274/217/347/274/223/345/255/230.md +311 -0
- package/knowledge/Furion_Teaching_Manual/15-/345/256/211/345/205/250/351/211/264/346/235/203.md +832 -0
- package/knowledge/Furion_Teaching_Manual/17-/346/252/242/350/246/226/347/257/204/346/234/254/345/274/225/346/223/216.md +327 -0
- package/knowledge/Furion_Teaching_Manual/18-/346/227/245/350/252/214/350/250/230/351/214/204.md +639 -0
- package/knowledge/Furion_Teaching_Manual/19-1-HTTP/351/201/240/347/253/257/350/253/213/346/261/202/345/237/272/347/244/216/344/275/277/347/224/250.md +621 -0
- package/knowledge/Furion_Teaching_Manual/19-2-HTTP/351/201/240/347/253/257/350/253/213/346/261/202/351/200/262/351/232/216/346/214/207/345/215/227.md +928 -0
- package/knowledge/Furion_Teaching_Manual/19-3-HTTP/351/201/240/347/253/257/350/253/213/346/261/202/345/270/270/350/246/213/345/225/217/351/241/214.md +362 -0
- package/knowledge/Furion_Teaching_Manual/20-/350/263/207/346/226/231/345/212/240/350/247/243/345/257/206.md +286 -0
- package/knowledge/Furion_Teaching_Manual/20-/351/231/204/351/214/204-KSort/350/263/207/346/226/231/347/260/275/345/220/215/345/256/214/346/225/264/345/216/237/345/247/213/347/242/274.md +305 -0
- package/knowledge/Furion_Teaching_Manual/21-/345/205/250/347/220/203/345/214/226/345/222/214/346/234/254/345/234/260/345/214/226.md +342 -0
- package/knowledge/Furion_Teaching_Manual/22-/344/272/213/344/273/266/345/214/257/346/265/201/346/216/222EventBus.md +448 -0
- package/knowledge/Furion_Teaching_Manual/23-JSON/345/272/217/345/210/227/345/214/226.md +340 -0
- package/knowledge/Furion_Teaching_Manual/24-/345/215/263/346/231/202/351/200/232/350/250/212SignalR.md +247 -0
- package/knowledge/Furion_Teaching_Manual/25-/350/274/224/345/212/251/350/247/222/350/211/262/346/234/215/345/213/231WorkerService.md +295 -0
- package/knowledge/Furion_Teaching_Manual/26-1-/346/216/222/347/250/213/344/275/234/346/245/255/345/256/232/346/231/202/344/273/273/345/213/231.md +631 -0
- package/knowledge/Furion_Teaching_Manual/26-2-Cron/350/241/250/351/201/224/345/274/217.md +203 -0
- package/knowledge/Furion_Teaching_Manual/26-3-/344/273/273/345/213/231/344/275/207/345/210/227TaskQueue.md +215 -0
- package/knowledge/Furion_Teaching_Manual/27-/345/210/206/346/225/243/345/274/217ID/347/224/237/346/210/220.md +86 -0
- package/knowledge/Furion_Teaching_Manual/28-/346/250/241/347/265/204/345/214/226/351/226/213/347/231/274.md +68 -0
- package/knowledge/Furion_Teaching_Manual/29-/346/265/201/350/256/212/347/211/251/344/273/266Clay.md +313 -0
- package/knowledge/Furion_Teaching_Manual/30-/350/204/253/346/225/217/350/231/225/347/220/206/357/274/210Sensitive Detection).md" +168 -0
- package/knowledge/Furion_Teaching_Manual/32-/346/234/203/350/251/261/345/222/214/347/213/200/346/205/213/347/256/241/347/220/206.md +147 -0
- package/knowledge/Furion_Teaching_Manual/33-IPC/347/250/213/345/272/217/351/200/232/350/250/212.md +98 -0
- package/knowledge/Furion_Teaching_Manual/34-2-Docker/351/203/250/347/275/262.md +313 -0
- package/knowledge/Furion_Teaching_Manual/34-3-Nginx/351/203/250/347/275/262.md +157 -0
- package/knowledge/Furion_Teaching_Manual/34-8-WindowsService/351/203/250/347/275/262.md +112 -0
- package/knowledge/Furion_Teaching_Manual/35-1-Docker/347/222/260/345/242/203/346/214/201/347/272/214/351/203/250/347/275/262Jenkins.md +169 -0
- package/knowledge/Furion_Teaching_Manual/36-1-/345/226/256/345/205/203/346/270/254/350/251/246/346/225/264/345/220/210/346/270/254/350/251/246.md +275 -0
- package/knowledge/Furion_Teaching_Manual/36-3-/345/237/272/346/272/226/346/270/254/350/251/246Benchmarking.md +80 -0
- package/knowledge/attributes.md +153 -0
- package/knowledge/config.md +147 -0
- package/knowledge/entity.md +115 -0
- package/knowledge/event.md +124 -0
- package/knowledge/plugin.md +136 -0
- package/knowledge/service.md +146 -0
- package/knowledge/sqlsugar.md +172 -0
- package/knowledge/vue-typescript.md +338 -0
- package/package.json +3 -2
package/knowledge/Furion_Teaching_Manual/29-/346/265/201/350/256/212/347/211/251/344/273/266Clay.md
ADDED
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
# 29. 流變物件(Clay / Shapeless)
|
|
2
|
+
|
|
3
|
+
> **版本說明**:僅適用於 Furion 4.9.7+,不支援 .NET 8 以下版本。Furion 已內建;非 Furion 框架可安裝 `Shapeless` 或 `Shapeless.AspNetCore`。
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 29.1 概述
|
|
8
|
+
|
|
9
|
+
流變物件(Clay)是基於 `DynamicObject` 建立的執行階段動態派生物件,提供類似 JavaScript JSON 物件的靈活操作能力,支援動態增刪改查、Linq/Lambda 查詢。
|
|
10
|
+
|
|
11
|
+
**應用場景**:動態資料操作、第三方 API 整合、CMS、工作流表單、微服務資料整合、快速原型開發、資料分析報表、事件驅動架構、設定管理、範本引擎、電商自訂參數等。
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## 29.2 快速入門
|
|
16
|
+
|
|
17
|
+
### 建立單一物件(`{}`)
|
|
18
|
+
|
|
19
|
+
```csharp
|
|
20
|
+
dynamic clay = new Clay(); // 或 new Clay.Object()
|
|
21
|
+
dynamic clay = Clay.EmptyObject();
|
|
22
|
+
dynamic clay = Clay.Parse("{}");
|
|
23
|
+
dynamic clay = Clay.Parse(new { id = 1 });
|
|
24
|
+
|
|
25
|
+
// 便捷初始化
|
|
26
|
+
dynamic clay = new Clay { ["id"] = 1, ["name"] = "Shapeless" };
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
### 建立集合或陣列(`[]`)
|
|
30
|
+
|
|
31
|
+
```csharp
|
|
32
|
+
dynamic clay = new Clay(ClayType.Array); // 或 new Clay.Array()
|
|
33
|
+
dynamic clay = Clay.EmptyArray();
|
|
34
|
+
dynamic clay = Clay.Parse("[]");
|
|
35
|
+
dynamic clay = Clay.Parse(new List<int> { 1, 2, 3 });
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### 基本操作(單一物件)
|
|
39
|
+
|
|
40
|
+
```csharp
|
|
41
|
+
clay.Id = 1; // 屬性方式新增/設定
|
|
42
|
+
clay["Name"] = "Furion"; // 索引方式
|
|
43
|
+
clay.Author = new { Name = "MonkSoul" }; // 巢狀物件
|
|
44
|
+
clay.Remove("Name"); // 刪除
|
|
45
|
+
clay.Id += 1; // 修改
|
|
46
|
+
Console.WriteLine(clay); // 輸出 JSON
|
|
47
|
+
Console.WriteLine($"{clay:UZC}"); // U=取消Unicode C=camelCase Z=壓縮 P=Pascal
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### 基本操作(集合/陣列)
|
|
51
|
+
|
|
52
|
+
```csharp
|
|
53
|
+
clay.Add(1); // 追加
|
|
54
|
+
clay.AddRange(new object[] { 2, 3 });
|
|
55
|
+
clay.Insert(0, "first"); // 插入
|
|
56
|
+
clay[0] = "modified"; // 修改
|
|
57
|
+
clay.Remove(0); // 刪除
|
|
58
|
+
clay.Pop(); // 移除末項
|
|
59
|
+
clay.Reverse(); // 反轉
|
|
60
|
+
clay[1..^1]; // 截取(Range)
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### 從多種來源建立
|
|
64
|
+
|
|
65
|
+
```csharp
|
|
66
|
+
Clay.Parse("""{"id":1}"""); // JSON 字串
|
|
67
|
+
Clay.Parse(new YourModel { Id = 1 }); // 具體型別
|
|
68
|
+
Clay.Parse(new { id = 1 }); // 匿名物件
|
|
69
|
+
Clay.Parse(new Dictionary<string, object> { {"id", 1} });
|
|
70
|
+
Clay.Parse(byteArray); // byte[]
|
|
71
|
+
Clay.Parse(stream); // Stream
|
|
72
|
+
Clay.Parse(jsonElement); // JsonElement
|
|
73
|
+
Clay.ParseFromFile("path.json"); // 檔案
|
|
74
|
+
Clay.Parse("form=data&key=val"); // URL 表單
|
|
75
|
+
new { id = 1 }.ToClay(); // ToClay() 擴充方法
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
**隱式轉換**(4.9.8.12+):
|
|
79
|
+
|
|
80
|
+
```csharp
|
|
81
|
+
Clay clay = """{"id":1}"""; // string → Clay
|
|
82
|
+
string json = Clay.Parse("{}"); // Clay → string
|
|
83
|
+
Dictionary<string, object?> dic = clay; // Clay → Dictionary
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### 型別轉換
|
|
87
|
+
|
|
88
|
+
```csharp
|
|
89
|
+
ClayModel model = clay; // 隱式轉換
|
|
90
|
+
var model = (ClayModel)clay; // 顯式轉換
|
|
91
|
+
var model = clay.As<ClayModel>(); // As<T>() 方法
|
|
92
|
+
var model = clay.As<ClayModel>(new JsonSerializerOptions { PropertyNameCaseInsensitive = true });
|
|
93
|
+
XElement xml = clay; // 轉 XML
|
|
94
|
+
IActionResult result = clay; // 轉 IActionResult
|
|
95
|
+
|
|
96
|
+
// 屬性型別轉換
|
|
97
|
+
var date = clay.date<DateTime>();
|
|
98
|
+
var val = clay.Get<int>("id");
|
|
99
|
+
var val = clay.Get("id", typeof(int));
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### 輸出格式
|
|
103
|
+
|
|
104
|
+
| 方法 | 說明 |
|
|
105
|
+
|------|------|
|
|
106
|
+
| `clay.ToString()` / `Console.WriteLine(clay)` | JSON(預設格式化 + Unicode 編碼) |
|
|
107
|
+
| `clay.ToString("U")` / `$"{clay:U}"` | 取消中文 Unicode |
|
|
108
|
+
| `clay.ToString("Z")` | 壓縮 JSON |
|
|
109
|
+
| `clay.ToString("C")` / `"P"` | camelCase / PascalCase |
|
|
110
|
+
| `clay.ToString("ZUC")` | 組合使用 |
|
|
111
|
+
| `clay.ToJsonString(options)` | 完整 JsonSerializerOptions 控制 |
|
|
112
|
+
| `clay.ToXmlString(settings)` | XML 輸出 |
|
|
113
|
+
| `clay()` | 等同 `ToJsonString()` |
|
|
114
|
+
|
|
115
|
+
---
|
|
116
|
+
|
|
117
|
+
## 29.3 Clay 型別 API 速查
|
|
118
|
+
|
|
119
|
+
### 內建屬性
|
|
120
|
+
|
|
121
|
+
| 屬性 | 型別 | 說明 |
|
|
122
|
+
|------|------|------|
|
|
123
|
+
| `IsObject` / `IsArray` | `bool` | 型別判斷 |
|
|
124
|
+
| `Type` | `ClayType` | Object / Array |
|
|
125
|
+
| `Count` / `Length` | `int` | 鍵/元素數量 |
|
|
126
|
+
| `IsEmpty` | `bool` | 是否為空 |
|
|
127
|
+
| `Keys` | `IEnumerable<object>` | 鍵/索引列表 |
|
|
128
|
+
| `MemberNames` | `IEnumerable<string>` | 屬性名列表(僅單一物件) |
|
|
129
|
+
| `Values` | `IEnumerable<dynamic?>` | 值/元素列表 |
|
|
130
|
+
| `IsReadOnly` | `bool` | 是否唯讀 |
|
|
131
|
+
|
|
132
|
+
### 標識符(運算子)
|
|
133
|
+
|
|
134
|
+
支援:字串鍵、整數索引、`Index`(`^1`)、`Range`(`1..^2`)、路徑(`AppInfo:Company:Name`)、JSON Path(`$.AppInfo.Company`)。
|
|
135
|
+
|
|
136
|
+
### 核心方法
|
|
137
|
+
|
|
138
|
+
| 方法 | 說明 |
|
|
139
|
+
|------|------|
|
|
140
|
+
| `Get(id)` / `Get<T>(id)` | 取值(支援型別轉換) |
|
|
141
|
+
| `Set(id, value)` | 設值 |
|
|
142
|
+
| `Contains(id)` / `IsDefined(id)` / `HasProperty(name)` | 檢查是否定義 |
|
|
143
|
+
| `FindNode(id)` / `FindNodeByPath(path)` | 查找 JsonNode 節點 |
|
|
144
|
+
| `Add(v)` / `Push(v)` / `Append(v)` / `AddRange(...)` | 追加(陣列) |
|
|
145
|
+
| `Insert(index, v)` / `InsertRange(index, ...)` | 插入(陣列) |
|
|
146
|
+
| `Pop()` | 移除末項(陣列) |
|
|
147
|
+
| `Remove(id)` / `Delete(id)` | 刪除 |
|
|
148
|
+
| `Reverse()` | 反轉 |
|
|
149
|
+
| `Slice(range)` / `this[Range]` | 截取(陣列) |
|
|
150
|
+
| `Combine(clay2, clay3)` | 合併多個流變物件 |
|
|
151
|
+
| `DeepClone()` | 深度克隆 |
|
|
152
|
+
| `Clear()` | 清空資料 |
|
|
153
|
+
| `AsReadOnly()` / `AsMutable()` | 唯讀/可寫模式切換 |
|
|
154
|
+
| `Rebuilt(options)` | 重建(套用新 ClayOptions) |
|
|
155
|
+
| `KSort()` / `KRSort()` | 按鍵升序/降序排序(單一物件) |
|
|
156
|
+
| `Extend(...)` | 擴充資料 |
|
|
157
|
+
| `IndexOf(value)` | 取得元素索引(陣列) |
|
|
158
|
+
| `Equals(other)` / `==` / `!=` | 相等比較 |
|
|
159
|
+
| `WriteTo(Utf8JsonWriter)` | 寫入 JsonWriter |
|
|
160
|
+
|
|
161
|
+
### 路徑操作
|
|
162
|
+
|
|
163
|
+
```csharp
|
|
164
|
+
clay.PathValue("AppInfo:Company:Name"); // 取值
|
|
165
|
+
clay.SetPathValue("AppInfo:Name", "新值"); // 設值(僅更新已存在路徑)
|
|
166
|
+
clay.RemovePathValue("AppInfo:Name"); // 刪除
|
|
167
|
+
clay.ContainsByPath("AppInfo:Name"); // 檢查
|
|
168
|
+
clay.FindNodeByPath("AppInfo:Company:Name"); // 查找 JsonNode
|
|
169
|
+
clay["AppInfo:Name", true]; // 路徑索引(第二參數 true)
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
### 遍歷與查詢
|
|
173
|
+
|
|
174
|
+
```csharp
|
|
175
|
+
// ForEach
|
|
176
|
+
foreach (KeyValuePair<string, dynamic?> item in clay) { } // 單一物件
|
|
177
|
+
foreach (var item in array) { } // 陣列
|
|
178
|
+
|
|
179
|
+
// 解構函數(推薦用於 Lambda/Linq)
|
|
180
|
+
var (clay, enumerable) = Clay.Parse("...");
|
|
181
|
+
var list = enumerable.Where(u => u?.Key == "id").ToList();
|
|
182
|
+
|
|
183
|
+
// Map / Filter
|
|
184
|
+
clay.Map(new Func<dynamic?, object?>(item => new { data = item?.Value }));
|
|
185
|
+
clay.Filter(new Func<dynamic?, bool>(item => item?.Key != "id"));
|
|
186
|
+
|
|
187
|
+
// 管道轉換
|
|
188
|
+
dynamic data = Clay.Parse(json).Pipe(u => u.data);
|
|
189
|
+
dynamic data = Clay.Parse(json).PipeTry(u => u.data2).Pipe(u => u.data);
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
### 動態委託方法(僅單一物件)
|
|
193
|
+
|
|
194
|
+
```csharp
|
|
195
|
+
clay.sayHello = (Func<string>)(() => $"Hello, {clay.name}!");
|
|
196
|
+
Console.WriteLine(clay.sayHello());
|
|
197
|
+
|
|
198
|
+
// 使用 ClayContext 避免閉包問題
|
|
199
|
+
clay.greet = (Func<ClayContext, string>)(ctx => $"Hello, {ctx.Current.name}!");
|
|
200
|
+
Console.WriteLine(clay.greet()); // 無需傳入 ClayContext
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
### 處理 JSON 雙重序列化
|
|
204
|
+
|
|
205
|
+
```csharp
|
|
206
|
+
dynamic clay = Clay.Parse(json).ParseJson("fieldName"); // 指定路徑
|
|
207
|
+
dynamic clay = Clay.Parse(json).ParseJson(); // 自動遍歷(預設深度 3)
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
---
|
|
211
|
+
|
|
212
|
+
## 29.4 ClayOptions 設定
|
|
213
|
+
|
|
214
|
+
| 屬性 | 型別 | 預設值 | 說明 |
|
|
215
|
+
|------|------|--------|------|
|
|
216
|
+
| `ScalarValueKey` | `string` | `"value"` | 非物件/陣列字面量的鍵名 |
|
|
217
|
+
| `AllowMissingProperty` | `bool` | `false` | 允許存取不存在的屬性(回傳 null) |
|
|
218
|
+
| `AllowIndexOutOfRange` | `bool` | `false` | 允許存取越界索引(回傳 null) |
|
|
219
|
+
| `AutoCreateNestedObjects` | `bool` | `false` | 自動建立巢狀物件 |
|
|
220
|
+
| `AutoCreateNestedArrays` | `bool` | `false` | 自動建立巢狀陣列 |
|
|
221
|
+
| `AutoExpandArrayWithNulls` | `bool` | `false` | 超出陣列長度時自動補 null |
|
|
222
|
+
| `ValidateAfterConversion` | `bool` | `false` | 轉換後執行模型驗證 |
|
|
223
|
+
| `DateJsonToDateTime` | `bool` | `false` | 日期字串自動轉 DateTime |
|
|
224
|
+
| `KeyValueJsonToObject` | `bool` | `false` | key/value JSON 轉單一物件 |
|
|
225
|
+
| `PropertyNameCaseInsensitive` | `bool` | `false` | 屬性名不區分大小寫 |
|
|
226
|
+
| `PathSeparator` | `string[]` | `[":"]` | 路徑分隔符 |
|
|
227
|
+
| `ReadOnly` | `bool` | `false` | 唯讀模式 |
|
|
228
|
+
| `JsonSerializerOptions` | — | 預設設定 | JSON 序列化選項 |
|
|
229
|
+
|
|
230
|
+
**快捷設定**:`ClayOptions.Flexible`(AllowMissingProperty + AllowIndexOutOfRange + PropertyNameCaseInsensitive 皆為 true)。
|
|
231
|
+
|
|
232
|
+
---
|
|
233
|
+
|
|
234
|
+
## 29.5 事件監聽
|
|
235
|
+
|
|
236
|
+
```csharp
|
|
237
|
+
Clay clayObject = clay; // 需轉回 Clay 型別
|
|
238
|
+
|
|
239
|
+
clayObject.Changing += (sender, args) => { }; // 變更前
|
|
240
|
+
clayObject.Changed += (sender, args) => { }; // 變更後
|
|
241
|
+
clayObject.Removing += (sender, args) => { }; // 移除前
|
|
242
|
+
clayObject.Removed += (sender, args) => { }; // 移除後
|
|
243
|
+
|
|
244
|
+
// 或使用 AddEvent(無需型別轉換)
|
|
245
|
+
clay.AddEvent("Changed", new ClayEventHandler((sender, args) => { }));
|
|
246
|
+
clay.AddEvent("Changed", new Action<dynamic, ClayEventArgs>((sender, args) => { }));
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
---
|
|
250
|
+
|
|
251
|
+
## 29.2.11 在 ASP.NET 應用中使用
|
|
252
|
+
|
|
253
|
+
### 設定
|
|
254
|
+
|
|
255
|
+
```csharp
|
|
256
|
+
services.AddControllers()
|
|
257
|
+
.AddClayOptions(options => { options.KeyValueJsonToObject = true; });
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
### WebAPI
|
|
261
|
+
|
|
262
|
+
```csharp
|
|
263
|
+
[HttpPost]
|
|
264
|
+
public dynamic PostClay([Clay] dynamic clay) => clay;
|
|
265
|
+
|
|
266
|
+
[HttpPost]
|
|
267
|
+
public YourModel PostData()
|
|
268
|
+
{
|
|
269
|
+
dynamic clay = Clay.Parse("""{"id":1}""");
|
|
270
|
+
return clay; // 自動轉換為目標型別
|
|
271
|
+
}
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
### MVC
|
|
275
|
+
|
|
276
|
+
```csharp
|
|
277
|
+
public IActionResult Index()
|
|
278
|
+
=> View(Clay.Parse("""{"id":1,"name":"Furion"}"""));
|
|
279
|
+
// 視圖:@model dynamic → @Model.id
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
---
|
|
283
|
+
|
|
284
|
+
## 29.7 HTTP 遠端請求中使用
|
|
285
|
+
|
|
286
|
+
```csharp
|
|
287
|
+
// 設定轉換器
|
|
288
|
+
services.AddHttpRemote().ConfigureOptions(opt =>
|
|
289
|
+
opt.JsonSerializerOptions.AddClayConverters());
|
|
290
|
+
|
|
291
|
+
// 發送請求
|
|
292
|
+
dynamic payload = new Clay();
|
|
293
|
+
payload.id = 1;
|
|
294
|
+
var content = await httpRemoteService.PostAsStringAsync(url,
|
|
295
|
+
b => b.SetJsonContent(payload));
|
|
296
|
+
dynamic result = Clay.Parse(content);
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
---
|
|
300
|
+
|
|
301
|
+
## 29.8 常見問題速查
|
|
302
|
+
|
|
303
|
+
| 問題 | 解法 |
|
|
304
|
+
|------|------|
|
|
305
|
+
| 屬性不存在拋異常 | `ClayOptions.Flexible` 或 `AllowMissingProperty = true` |
|
|
306
|
+
| 索引越界拋異常 | `AllowIndexOutOfRange = true` |
|
|
307
|
+
| 屬性大小寫敏感 | `PropertyNameCaseInsensitive = true` |
|
|
308
|
+
| C# 關鍵字鍵名 | 屬性方式加 `@`(如 `clay.@int`),索引方式不需要 |
|
|
309
|
+
| 中文亂碼 | `ToString("U")` 或 `ToJsonString()` |
|
|
310
|
+
| WebAPI 回傳 key/value 格式 | 新增 `.AddClayOptions()` |
|
|
311
|
+
| 捕獲溢出欄位 | 在目標型別加 `[JsonExtensionData] Dictionary<string, JsonElement>` |
|
|
312
|
+
| Newtonsoft.Json 支援 | `.AddNewtonsoftJson(opt => opt.SerializerSettings.Converters.AddClayConverters())` |
|
|
313
|
+
| Swagger Schema 問題 | 用 `<remarks>` + Markdown 描述參數格式 |
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
# Furion 脫敏處理(Sensitive Detection)筆記
|
|
2
|
+
|
|
3
|
+
> 適用版本:Furion 2.4.4+,部分功能需更高版本,詳見各節說明。
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 版本更新重點
|
|
8
|
+
|
|
9
|
+
| 類型 | 說明 | 版本 |
|
|
10
|
+
|------|------|------|
|
|
11
|
+
| 新增 | `[SensitiveDetection]` 特性支援 `ShowSensitiveWords` 屬性顯示命中敏感詞 | 4.9.8.10 |
|
|
12
|
+
| 新增 | `[SensitiveDetection]` 特性支援預設錯誤訊息 | 4.9.8.13 |
|
|
13
|
+
| 新增 | 全模組同步方法(`GetWords`、`IsValid`、`Replace`、`FoundSensitiveWords`) | 4.9.8.10 |
|
|
14
|
+
| 新增 | 支援自訂嵌入詞庫檔名 | 4.9.1.45 |
|
|
15
|
+
| 新增 | `FoundSensitiveWordsAsync`:取得敏感詞及位置 | 4.9.1.45 |
|
|
16
|
+
| **調整** | `VaildedAsync` 更名為 `IsValidAsync` | 4.9.8.9 |
|
|
17
|
+
| 修復 | `[SensitiveDetection]` 不支援格式符 `{0}` | 4.9.8.10 |
|
|
18
|
+
| 修復 | 跨平台換行符差異導致詞彙分割失敗 | 4.9.8.9 |
|
|
19
|
+
| 修復 | 程式集名自訂導致無法載入詞庫資源 | 4.9.7.119 |
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## 1. 註冊服務
|
|
24
|
+
|
|
25
|
+
```csharp
|
|
26
|
+
// 基本註冊
|
|
27
|
+
services.AddSensitiveDetection();
|
|
28
|
+
|
|
29
|
+
// 自訂詞庫檔名(4.9.1.45+)
|
|
30
|
+
services.AddSensitiveDetection(options =>
|
|
31
|
+
{
|
|
32
|
+
options.EmbedFileName = "custom-words.txt";
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
// 自訂 Provider
|
|
36
|
+
services.AddSensitiveDetection<YourSensitiveDetectionProvider>();
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
|
|
41
|
+
## 2. 詞庫檔案(sensitive-words.txt)
|
|
42
|
+
|
|
43
|
+
- 在 Web 啟動層建立 `sensitive-words.txt`
|
|
44
|
+
- 編碼:**UTF-8**(4.8.6.7+ 支援 UTF-8 BOM)
|
|
45
|
+
- 必須設定為**嵌入式資源**
|
|
46
|
+
|
|
47
|
+
```
|
|
48
|
+
// 每行一個詞(換行格式)
|
|
49
|
+
壞人
|
|
50
|
+
無語
|
|
51
|
+
滾開
|
|
52
|
+
|
|
53
|
+
// 或用 | 分隔(3.8.9+,節省空間)
|
|
54
|
+
壞人|無語|滾開|八嘎
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
> ⚠️ 若未設為嵌入式資源,詞庫將無法載入。
|
|
58
|
+
|
|
59
|
+
---
|
|
60
|
+
|
|
61
|
+
## 3. 使用方式
|
|
62
|
+
|
|
63
|
+
### 3.1 Attribute 驗證(自動)
|
|
64
|
+
|
|
65
|
+
```csharp
|
|
66
|
+
public class Content
|
|
67
|
+
{
|
|
68
|
+
// 基本用法
|
|
69
|
+
[SensitiveDetection]
|
|
70
|
+
public string Text { get; set; }
|
|
71
|
+
|
|
72
|
+
// 自訂錯誤訊息
|
|
73
|
+
[SensitiveDetection(ErrorMessage = "包含敏感詞")]
|
|
74
|
+
public string Title { get; set; }
|
|
75
|
+
|
|
76
|
+
// 顯示命中詞(4.9.8.10+)
|
|
77
|
+
[SensitiveDetection(
|
|
78
|
+
ErrorMessage = "{0} 包含敏感詞,命中:{1}",
|
|
79
|
+
ShowSensitiveWords = true)]
|
|
80
|
+
public string Comment { get; set; }
|
|
81
|
+
|
|
82
|
+
// 自動替換為指定字元(3.8.8+ 支援方法參數)
|
|
83
|
+
[SensitiveDetection('*')]
|
|
84
|
+
public string Body { get; set; }
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// DynamicApi / Controller 參數
|
|
88
|
+
public void Post([SensitiveDetection] string text) { }
|
|
89
|
+
public void Post([SensitiveDetection('*')] string text) { }
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
### 3.2 ISensitiveDetectionProvider(手動)
|
|
93
|
+
|
|
94
|
+
```csharp
|
|
95
|
+
public class YourService : IDynamicApiController
|
|
96
|
+
{
|
|
97
|
+
private readonly ISensitiveDetectionProvider _sdp;
|
|
98
|
+
|
|
99
|
+
public YourService(ISensitiveDetectionProvider sdp)
|
|
100
|
+
{
|
|
101
|
+
_sdp = sdp;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// 取得所有敏感詞
|
|
105
|
+
public async Task<IEnumerable<string>> GetWordsAsync()
|
|
106
|
+
=> await _sdp.GetWordsAsync();
|
|
107
|
+
|
|
108
|
+
// 驗證:true = 正常,false = 命中敏感詞
|
|
109
|
+
public async Task<bool> IsValidAsync(string text)
|
|
110
|
+
=> await _sdp.IsValidAsync(text);
|
|
111
|
+
|
|
112
|
+
// 替換命中詞為 *
|
|
113
|
+
public async Task<string> ReplaceAsync(string text)
|
|
114
|
+
=> await _sdp.ReplaceAsync(text, '*');
|
|
115
|
+
|
|
116
|
+
// 取得命中詞與位置(4.9.1.45+)
|
|
117
|
+
public async Task<Dictionary<string, List<int>>> FoundAsync(string text)
|
|
118
|
+
=> await _sdp.FoundSensitiveWordsAsync(text);
|
|
119
|
+
}
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
---
|
|
123
|
+
|
|
124
|
+
## 4. 介面方法速查
|
|
125
|
+
|
|
126
|
+
| 方法 | 回傳值 | 說明 |
|
|
127
|
+
|------|--------|------|
|
|
128
|
+
| `GetWordsAsync()` | `IEnumerable<string>` | 取得所有敏感詞清單 |
|
|
129
|
+
| `IsValidAsync(text)` | `bool` | `true` = 無敏感詞(正常) |
|
|
130
|
+
| `ReplaceAsync(text, '*')` | `string` | 將命中詞替換為指定字元 |
|
|
131
|
+
| `FoundSensitiveWordsAsync(text)` | `Dictionary<string, List<int>>` | 命中詞與其位置索引(4.9.1.45+) |
|
|
132
|
+
|
|
133
|
+
> 4.9.8.10+ 以上方法均有同步版本(去掉 `Async` 後綴)。
|
|
134
|
+
|
|
135
|
+
---
|
|
136
|
+
|
|
137
|
+
## 5. 自訂 Provider
|
|
138
|
+
|
|
139
|
+
```csharp
|
|
140
|
+
public class DbSensitiveProvider : ISensitiveDetectionProvider
|
|
141
|
+
{
|
|
142
|
+
// 從資料庫取詞,建議搭配 MemoryCache / Redis
|
|
143
|
+
public async Task<IEnumerable<string>> GetWordsAsync()
|
|
144
|
+
{
|
|
145
|
+
// 實作:查 DB 或快取
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// true = 正常,false = 命中敏感詞
|
|
149
|
+
public async Task<bool> IsValidAsync(string text)
|
|
150
|
+
{
|
|
151
|
+
// 實作:判斷邏輯
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// 替換命中詞
|
|
155
|
+
public async Task<string> ReplaceAsync(string text, char transfer = '*')
|
|
156
|
+
{
|
|
157
|
+
// 實作:替換邏輯
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// 查找詞與位置(4.9.1.45+)
|
|
161
|
+
public async Task<Dictionary<string, List<int>>> FoundSensitiveWordsAsync(string text)
|
|
162
|
+
{
|
|
163
|
+
// 實作:查找邏輯
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
> 💡 `GetWordsAsync` 若從資料庫取詞,務必搭配快取,避免每次請求都查 DB。
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
# 32. 會話和狀態管理
|
|
2
|
+
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
## 32.1 概述
|
|
6
|
+
|
|
7
|
+
HTTP 是無狀態的協定,預設不保留使用者資料。可透過以下方式保留請求間的使用者資料:
|
|
8
|
+
|
|
9
|
+
| 方式 | 儲存位置 | 生命週期 |
|
|
10
|
+
|------|---------|---------|
|
|
11
|
+
| **Cookie** | 客戶端 | 可設定過期時間 |
|
|
12
|
+
| **Session** | 伺服器端(記憶體/分散式) | 閒置逾時後銷毀 |
|
|
13
|
+
| **Query Strings** | URL 參數 | 單次請求 |
|
|
14
|
+
| **HttpContext.Items** | 伺服器端 | 單次請求結束即銷毀 |
|
|
15
|
+
| **Cache** | 伺服器端 | 依快取策略 |
|
|
16
|
+
| **AsyncLocal\<T\>** | 非同步控制流 | 跨執行緒共享 |
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## 32.2 使用方式
|
|
21
|
+
|
|
22
|
+
### 32.2.1 Cookie
|
|
23
|
+
|
|
24
|
+
```csharp
|
|
25
|
+
// 讀取
|
|
26
|
+
var value = httpContext.Request.Cookies["key"];
|
|
27
|
+
|
|
28
|
+
// 設定
|
|
29
|
+
var option = new CookieOptions { Expires = DateTime.Now.AddMilliseconds(10) };
|
|
30
|
+
httpContext.Response.Cookies.Append(key, value, option);
|
|
31
|
+
|
|
32
|
+
// 刪除
|
|
33
|
+
httpContext.Response.Cookies.Delete(key);
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
> `httpContext` 可透過 `IHttpContextAccessor` 或 `App.HttpContext` 取得。Cookie 還可實現授權及單點登入(SSO)。
|
|
37
|
+
|
|
38
|
+
### 32.2.2 Session
|
|
39
|
+
|
|
40
|
+
**註冊服務**(必須在控制器之前):
|
|
41
|
+
|
|
42
|
+
```csharp
|
|
43
|
+
services.AddSession(options =>
|
|
44
|
+
{
|
|
45
|
+
options.IdleTimeout = TimeSpan.FromSeconds(10);
|
|
46
|
+
options.Cookie.HttpOnly = true;
|
|
47
|
+
options.Cookie.IsEssential = true;
|
|
48
|
+
});
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
**註冊中介軟體**(必須在 `UseRouting` 和 `UseEndpoints` 之間):
|
|
52
|
+
|
|
53
|
+
```csharp
|
|
54
|
+
app.UseRouting();
|
|
55
|
+
app.UseAuthentication();
|
|
56
|
+
app.UseAuthorization();
|
|
57
|
+
app.UseSession(); // ← 在這裡
|
|
58
|
+
app.UseEndpoints(...);
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
**常見操作**:
|
|
62
|
+
|
|
63
|
+
```csharp
|
|
64
|
+
// 讀取
|
|
65
|
+
var str = httpContext.Session.GetString("key");
|
|
66
|
+
var num = httpContext.Session.GetInt32("key");
|
|
67
|
+
|
|
68
|
+
// 設定
|
|
69
|
+
httpContext.Session.SetString("key", "value");
|
|
70
|
+
httpContext.Session.SetInt32("key", 1);
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
**自訂任意型別擴充**:
|
|
74
|
+
|
|
75
|
+
```csharp
|
|
76
|
+
public static class SessionExtensions
|
|
77
|
+
{
|
|
78
|
+
public static void Set<T>(this ISession session, string key, T value)
|
|
79
|
+
=> session.SetString(key, JsonSerializer.Serialize(value));
|
|
80
|
+
|
|
81
|
+
public static T Get<T>(this ISession session, string key)
|
|
82
|
+
{
|
|
83
|
+
var value = session.GetString(key);
|
|
84
|
+
return value == null ? default : JsonSerializer.Deserialize<T>(value);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
**防止 Session ID 改變或失效**:
|
|
90
|
+
|
|
91
|
+
```csharp
|
|
92
|
+
services.Configure<CookiePolicyOptions>(options =>
|
|
93
|
+
{
|
|
94
|
+
options.CheckConsentNeeded = context => false;
|
|
95
|
+
options.MinimumSameSitePolicy = SameSiteMode.None;
|
|
96
|
+
});
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### 32.2.3 Query Strings
|
|
100
|
+
|
|
101
|
+
```csharp
|
|
102
|
+
var value = httpContext.Request.Query["key"];
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
### 32.2.4 HttpContext.Items
|
|
106
|
+
|
|
107
|
+
單次請求間共享資料,請求結束立即銷毀,可儲存任何型別:
|
|
108
|
+
|
|
109
|
+
```csharp
|
|
110
|
+
// 讀取
|
|
111
|
+
var value = httpContext.Items["key"];
|
|
112
|
+
|
|
113
|
+
// 設定
|
|
114
|
+
httpContext.Items["key"] = "任何值包括物件";
|
|
115
|
+
|
|
116
|
+
// 刪除
|
|
117
|
+
httpContext.Items.Remove("key");
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
### 32.2.5 Cache
|
|
121
|
+
|
|
122
|
+
參見 **14. 分散式快取** 章節。
|
|
123
|
+
|
|
124
|
+
### 32.2.6 AsyncLocal\<T\>
|
|
125
|
+
|
|
126
|
+
跨執行緒、非同步控制流中共享資料的利器:
|
|
127
|
+
|
|
128
|
+
```csharp
|
|
129
|
+
static AsyncLocal<string> _asyncLocalString = new AsyncLocal<string>();
|
|
130
|
+
|
|
131
|
+
_asyncLocalString.Value = "Value 1";
|
|
132
|
+
// 在非同步方法中仍可正確讀取 "Value 1"
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
> `AsyncLocal<T>` 會隨非同步控制流傳遞值,而 `ThreadLocal<T>` 在 `await` 之後可能丟失值。
|
|
136
|
+
|
|
137
|
+
**Furion 簡化用法**(v2.18+):
|
|
138
|
+
|
|
139
|
+
```csharp
|
|
140
|
+
// 設定
|
|
141
|
+
CallContext.SetLocalValue("name", "Furion");
|
|
142
|
+
CallContext<int>.SetLocalValue("count", 1);
|
|
143
|
+
|
|
144
|
+
// 讀取
|
|
145
|
+
var name = CallContext.GetLocalValue("name");
|
|
146
|
+
var count = CallContext<int>.GetLocalValue("count");
|
|
147
|
+
```
|