---
# System prepended metadata

title: API資料串接

---

# API資料串接

## 流程說明
此筆記紀錄與銀行互相用api傳遞資料，其中包含
* **AES加密演算法(EncryptorService )**
* **資料存取txt檔**
* **讀txt檔(ProcessAllTxtFilesInDirectory)**
* **寄信功能**
* **使用引數自動執行專案**

## UI設計
2個Combobox、1個DateTimtPicker、2個button以及textBoxStatus

![](https://hackmd.io/_uploads/SyWhdZHzT.png)

## Form.cs

在排程自動化下，無法藉由手動來按下"執行"按鈕，因此可以藉由**引數**帶值，自動執行專案，同時在執行專案的開始及結束這兩個執行點用Log做紀錄。

### Form1_Load_1 事件處理函數

#### **功能：**
* 當 Form1 載入時會自動觸發這個函數。
* 依照命令列參數來判斷是否手動執行或是批次執行。

#### **程式碼解析**：

1. 日誌記錄
   * BatchLogService.WriteLog("執行開始")，紀錄「執行開始」到日誌。
2. 隱藏表單
   * this.Visible = false;: 預設讓 Form1 不顯示。
3. 取得命令列參數
   * string[] args = Environment.GetCommandLineArgs();: 獲得命令列參數。
4. 判斷執行類型
   * 如果 args.Count() == 1，代表是手動執行。
        - 表單會變為可見。
        - 初始化下拉選單（comboBox）。
   * 否則，代表是批次執行。
        - 嘗試執行特定操作。
        - 如果操作成功，寫入「執行結束」到日誌。
        - 如果操作失敗，寫入「排程執行失敗」和錯誤訊息到日誌。

```csharp=
   private void Form1_Load_1(object sender, EventArgs e)
   {
      BatchLogService.WriteLog("執行開始");
      //// 不顯示表單
      this.Visible = false;
      //// 取得命令列參數
      string[] args = Environment.GetCommandLineArgs();

       //// args.Count == 1, 代表是手動執行
       //// args.Count != 1, 代表是批次執行
       if (args.Count() == 1)
       {
            //// 顯示表單
            this.Visible = true;
            comboBox1.Items.Add(new ComboItem("收檔結果查詢服務", "TxFileResult"));
            comboBox2.Items.Add(new ComboItem("指示", "I"));
            comboBox2.Items.Add(new ComboItem("取消", "C"));
        }
        else
        {
            try
            {
                string action = args[1].ToUpper();
                ExecAction(action, today);
                BatchLogService.WriteLog("執行結束");
            }
            catch (Exception ex)
            {
                BatchLogService.WriteLog("排程執行失敗" + ex.ToString(), 2);
            }
        }
    }
```

### ExecAction 函數 

#### **功能：**
* 根據傳入的 action(引數) 和日期 yyyyMMdd，執行相對應的動作。
* 這個函數是由 Form1_Load_1 呼叫的，當 args.Count() != 1（批次執行）時。
#### **程式碼解析**：
1. 動作判斷
   * 使用傳入的 action 字串來判斷要執行哪一種動作。
2. 收檔結果查詢服務 ( TXFILERESULT )
   * 寫入 "執行收檔結果查詢服務" 到日誌。
   * 讀取特定目錄下的所有 txt 檔案。
   * 如果有找到 txt 檔案，則進行相應處理。
   * 如果沒有找到，則顯示和寫入 "無txt檔可查詢"。
3. 信託指示付款 - 指示 ( I )
   * 寫入 "執行信託指示付款_指示" 到日誌。
   * 讀取和處理 txt 檔案。
   * 更新狀態為 "執行結束"。
4. 信託指示付款 - 取消 ( C )
   * 寫入 "執行信託指示付款_取消" 到日誌。
   * 讀取和處理 txt 檔案。
   * 更新狀態為 "執行結束"。
5. 錯誤處理
   * 若在任何階段發生錯誤，寫入具體的錯誤訊息到日誌。
6. 程式結束
   * Application.Exit( ); 結束應用程式。
   
```csharp=
 private void ExecAction(string action, string yyyyMMdd)
 {
     try
     {
        if (action == "TXFILERESULT")
        {
           BatchLogService.WriteLog("執行收檔結果查詢服務");
           TxtFileResultService _txtFileResultService = new TxtFileResultService();
           try
           {
              string folderPath = "D:\\ftproot\\SinoPacsftp\\Instruction";
              var txtFiles = GetAllTxtFiles(folderPath);
              if (txtFiles.Count > 0)
              {
                  _txtFileResultService.ProcessAllTxtFilesInDirectory(txtFiles);
              }
              else
              {
                  textBoxStatus.Text = "無txt檔可查詢";
                  BatchLogService.WriteLog("無txt檔可查詢");
              }
           }
           catch (Exception ex)
           {
              textBoxStatus.Text = "執行失敗";
              BatchLogService.WriteLog("發生錯誤:" + ex.ToString(), 2);
           }
         }
          else if (action == "I")
          {
             TxFileCheckService _txtFileCheckService = new TxFileCheckService(action);
             BatchLogService.WriteLog("執行信託指示付款_指示");
             _txtFileCheckService.ReadTxtFileResult();
             textBoxStatus.Text = "執行結束";
          }

          else if (action == "C")
          {
             TxFileCheckService _txtFileCheckService = new TxFileCheckService(action);
             BatchLogService.WriteLog("執行信託指示付款_取消");
             _txtFileCheckService.ReadTxtFileResult();
             textBoxStatus.Text = "執行結束";
          }
          Application.Exit();
     }
     catch (Exception ex)
     {
       BatchLogService.WriteLog("ExecAction 內部發生錯誤" + ex.ToString(), 2);
     }
 }
```

### GetAllTxtFiles 函數 (獲取當天txt檔)


#### **功能：**
* 從指定的資料夾中讀取所有 .txt 結尾的文件。
* 只返回包含今日日期（格式為 "yyyyMMdd"）在文件名中的 .txt 文件。
#### **程式碼解析**：
1. 初始化變數
    * 創建一個 List<string> 來存儲符合條件的 .txt 文件的路徑。
2. 讀取文件
    * 使用 Directory.GetFiles( ) 從 folderPath 讀取所有 .txt 文件。
3. 獲取今天日期
    * 使用 DateTime.Now.ToString("yyyyMMdd") 獲取今天的日期字符串。
4. 過濾文件
    * 遍歷所有 .txt 文件。
    * 判斷文件名是否包含今天的日期。
    * 如果包含，則添加到 txtFilesToday 這個 List 中。
5. 返回結果
    * 返回 txtFilesToday，這個 List 包含了所有符合條件的 .txt 文件的路徑。

```csharp=
 private List<string> GetAllTxtFiles(string folderPath)
 {
     List<string> txtFilesToday = new List<string>();
     string[] txtFiles = Directory.GetFiles(folderPath, "*.txt");

     string todayDateString = DateTime.Now.ToString("yyyyMMdd");
     foreach (var filePath in txtFiles)
     {
        string fileName = Path.GetFileName(filePath);
        if (fileName.Contains(todayDateString))
        {
            txtFilesToday.Add(filePath);
        }
     }
    
     return txtFilesToday;
}
```

### button1_Click 事件處理函數(執行)

#### **功能：**
* 當使用者點擊 button1 時觸發這個事件(不用排程執行專案時，就可以觸發Button_Click執行專案)。
* 根據日期選擇器（dateTimePicker1）和兩個下拉選單（comboBox1 和 comboBox2）的選擇來執行相對應的動作。

#### **程式碼解析**：
1. 獲取選擇日期
    * 使用 dateTimePicker1.Value.ToString("yyyyMMdd") 獲取使用者選擇的日期。
2. 獲取下拉選單選擇
    * 從 comboBox1 和 comboBox2 獲取使用者選擇的動作（execAction 和 execAction2）。
3. 執行動作
    * 調用 ExecAction 函數，將 execAction 和 yyyyMMdd 作為參數。
    * 再次調用 ExecAction 函數，但這次使用 execAction2 和 yyyyMMdd 作為參數。
4. 錯誤處理
    * 若在任何階段發生錯誤，使用 BatchLogService.WriteLog 寫入具體的錯誤訊息到日誌。

```csharp=
private void button1_Click(object sender, EventArgs e)
{
  try
  {
     //// 執行的日期
     string yyyyMMdd = dateTimePicker1.Value.ToString("yyyyMMdd");

     //// 執行選擇的動作
     string execAction = ((ComboItem)comboBox1.Items[comboBox1.SelectedIndex]).Value;
     ExecAction(execAction, yyyyMMdd);

     string execAction2 = ((ComboItem)comboBox2.Items[comboBox2.SelectedIndex]).Value;
     ExecAction(execAction2, yyyyMMdd);
   }

   catch (Exception ex)
   {
      BatchLogService.WriteLog("執行失敗" + ex.ToString(), 2);
   }
}
```

### button2_Click 事件處理函數(清除)

#### **功能：**
* 當使用者點擊 button2 時觸發這個事件。
* 這個函數主要用於清除或重置表單上的某些控件到其預設狀態。

#### **程式碼解析**：

1. 清除下拉選單（ComboBox）
   * 將 comboBox1 和 comboBox2 的 SelectedIndex 設為 -1，這樣就不會有選定的項目。
2. 重置日期選擇器（DateTimePicker）
   * 將 dateTimePicker1.Value 設為當前的日期和時間（DateTime.Now）。
```csharp=
 private List<string> GetAllTxtFiles(string folderPath)
{
   List<string> txtFilesToday = new List<string>();
   string[] txtFiles = Directory.GetFiles(folderPath, "*.txt");
   string todayDateString = DateTime.Now.ToString("yyyyMMdd");
   foreach (var filePath in txtFiles)
   {
      string fileName = Path.GetFileName(filePath);
      if (fileName.Contains(todayDateString))
      {
          txtFilesToday.Add(filePath);
      }
   }
    return txtFilesToday;
}
```
    
## Service
### TxFileCheckService(讀txt檔->組ResqestBody->Api)  
#### **ProcessAllTxtFilesInDirectory(List<string> txtFiles)**
這個方法做了以下幾件事：

* 遍歷傳入的TXT文件列表。
* 對於每一個文件：
    * 讀取文件內容。
    * 判斷是否有 "沒有需要送出的資料" 的文本，如果有，則設置 noDataToSend 為 true。
    * 生成 TxFilerequestBodyList。
    * 上傳到API。
    
**參數**
* List<string> txtFiles：一個字符串列表，每個字符串都是一個TXT文件的本地路徑。
    
**變量**
* bool noDataToSend = false：用於標記是否有數據需要發送。
* string lastProcessedFilePath = null：存儲最後一個被處理的文件路徑。
    
**主體**
* foreach (var localFilePath in txtFiles)：遍歷每個TXT文件的路徑。
   * DateTime txDate = DateTime.Now：獲取當前的時間。
   * string fileNameOnly = Path.GetFileName(localFilePath)：獲取不包含路徑的檔名。
   * string textFromFile = File.ReadAllText(localFilePath)：讀取文件內容。
* 判斷文本是否包含 "沒有需要送出的資料"。
   * 如果是，設置 noDataToSend = true，並跳出循環。
* 如果不是，則處理文本並上傳到API。
    
**處理完成後**
* 如果 noDataToSend 是 true，則觸發 UploadStatusChanged 事件，發送電子郵件，並寫日誌。
* 移動處理過的文件到備份目錄。

**其他細節**
* 使用了 ConfigurationManager.AppSettings 來讀取配置文件中的資料。
    
```csharp=
public void ProcessAllTxtFilesInDirectory(List<string> txtFiles)
{
   bool noDataToSend = false;
   string lastProcessedFilePath = null;
   foreach (var localFilePath in txtFiles)
   {
      DateTime txDate = DateTime.Now;
      //自身產生的txt檔(不包含路徑)
      string fileNameOnly = Path.GetFileName(localFilePath);
      //讀檔
      string textFromFile = File.ReadAllText(localFilePath);
      lastProcessedFilePath = localFilePath;
      if (textFromFile.Contains("沒有需要送出的資料"))
      {
         noDataToSend = true;
         lastProcessedFilePath = localFilePath;
         break;
      }
      var TxFilerequestBodyList = TxFileResultRequestBody(txDate, textFromFile);

      // 上傳檔案到API
      foreach (var TxFilerequestBody in TxFilerequestBodyList)
      {
         string TxtfileName = TxFilerequestBody.OriFileName;
         UploadFileDirectlyFromSftpToApi(localFilePath, TxtfileName, selectedApiEndpoint, TxFilerequestBody);
      }
   }
   if (noDataToSend)
   {

      UploadStatusChanged?.Invoke("無提領檔可查詢");
      string subject = "【aa & bb】aa信託收檔結果查詢服務通知" + DateTime.Now.ToString("yyyy/MM/dd");
      string body = "無提領檔可查詢";
      emailService.SendEmail(subject, body);

      #region 所有操作成功後，移動文件
      // 原本txt的路徑檔
      string sourceDirectory = ConfigurationManager.AppSettings["SinoPacsftp_Instruction"];

      //要移置txt的路徑檔
      string targetDirectory = ConfigurationManager.AppSettings["SinoPacInstructionBackUp"];

     //確保資料夾存在
     if (!Directory.Exists(targetDirectory))
     {
         Directory.CreateDirectory(targetDirectory);
     }
     string fileName = Path.GetFileName(lastProcessedFilePath);
     string datepart = DateTime.Now.ToString("yyyyMMddHHmmss");
     string newFileName = datepart + "_" + fileName;
     string sourceFilePath = Path.Combine(sourceDirectory, fileName);
     string targetFilePath = Path.Combine(targetDirectory, newFileName);
     File.Move(sourceFilePath, targetFilePath);
     #endregion

     BatchLogService.WriteLog("無提領檔可查詢");
   }
}
```

#### **TxFileResultRequestBody(DateTime txDate, string textFromFile)**
* 基於交易日期和TXT文件內容生成request body列表。
* 將TXT文件解析成多行，並對每一行進行分割，以獲取文件名。
* 為每個文件名生成一個模型並加入到列表中。
    
**參數**
* DateTime txDate：交易日期。
* string textFromFile：從文件讀取的文本。
    
**返回值**
* List<GetPayTxFileResultRequestBodyModel>：一個由 GetPayTxFileResultRequestBodyModel 對象組成的列表。
    
**主要步驟**
1. 初始化一個空的 GetPayTxFileResultRequestBodyModel 對象列表 requestBodyList。
2. 格式化 txDate 為 "yyyyMMdd"。
3. 使用 Split('\n') 將 textFromFile 分割為多行。
4. 遍歷這些行，對每一行使用 Split('\t') 進行分割，並提取第一個標記（在這個情境中，應該是檔案名）。
5. 對每一個提取出的 TxtfileName，創建一個 GetPayTxFileResultRequestBodyModel 對象，然後將其添加到 requestBodyList。
6. 為每個 TxtfileName，創建一個電子郵件主題和正文，然後使用 emailService.SendEmail() 方法來發送郵件。
7. 最後，使用 BatchLogService.WriteLog() 方法寫日誌。   
    
    
    
    
    
    
    
#### **UploadFileDirectlyFromSftpToApi(...)**
這個方法做了以下幾件事：
*  初始化API端點和加密金鑰。
*  檢查本地文件是否存在。
*  序列化 TxFilerequestBody 到JSON。
*  調用API。
*  根據API的響應進行後續操作，例如寫入日誌或移動文件。
    
**參數**
* string localFilePath：本地檔案路徑。
* string TxtfileName：檔案名。
* string selectedApiEndpoint：選定的 API 端點。
* GetPayTxFileResultRequestBodyModel TxFilerequestBody：要上傳的請求主體。
    
**主要流程**
1. 設定 API 端點：selectedApiEndpoint 用於存儲API的端點URL。
2. 金鑰和向量初始化：
   * k1：讀取配置中的密鑰，並將其從 Base64 格式解碼。
   * iv：初始化向量，生成隨機 12 字節數據。
3. JSON 序列化和加密：
   * TxFilerequestBody 被序列化為 JSON 格式。
   * 使用 k1 和 iv 對 JSON 進行加密。
4. API 調用：
   * 這個步驟會調用一個 API 並獲取響應。
   * 如果 API 調用成功，執行以下操作：
     * 將返回的數據寫入一個 .txt 檔案。
     * 移動原始的 Intruction.txt 文件到備份目錄。
5. 異常處理：
   * 如果 API 調用失敗或出現異常，相應的錯誤將被寫入日誌並發送通過電子郵件。

```csharp=
public void UploadFileDirectlyFromSftpToApi(string localFilePath, string TxtfileName, string selectedApiEndpoint, GetPayTxFileResultRequestBodyModel TxFilerequestBody)
{
   selectedApiEndpoint = "https://apisbx............";
   // 解碼Base64格式的k1金鑰
   string key = ConfigurationManager.AppSettings["MySecretKey"];
   byte[] k1 = Convert.FromBase64String(key);

   //初始化向量（IV）
   byte[] iv = new byte[12];
   using (RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider())
   {
      rng.GetBytes(iv);
   }
   try
   {
      if (File.Exists(localFilePath))
      {
          //序列化為 JSON
          string jsonPayload = JsonConvert.SerializeObject(TxFilerequestBody);

          //調用加密
          EncryptorService.FlattenedJose flattenedJose = EncryptorService.EncodeFlattenedJoseContainer(jsonPayload, k1, iv);
          string flattenedJoseJson = JsonConvert.SerializeObject(flattenedJose);

          //先調用查詢api
          List<GetPayTxFileResultResponseBodyModel> TxFileResultApi = new List<GetPayTxFileResultResponseBodyModel>();
          apiService apiService = new apiService();
          var apiResponse =  apiService.CallApi(selectedApiEndpoint, flattenedJoseJson);
          if (apiResponse.Success == true)
          {
              //TxFileResultResponse寫入到txt
              string folderPath = ConfigurationManager.AppSettings["TxFileResultResponsePath"];
              string TxFileResultfileName = "TxFileResultResponse_" + DateTime.Now.ToString("yyyyMMddHHmmss") + ".txt";
              string fullPath = Path.Combine(folderPath, TxFileResultfileName);
              if (!Directory.Exists(folderPath))
              {
                  Directory.CreateDirectory(folderPath);
              }
              File.WriteAllText(fullPath, apiResponse.Data);

              // Intruction.txt檔丟backup資料夾
              string sourceDirectory = ConfigurationManager.AppSettings["SinoPacsftp_Instruction"];
              string targetDirectory = ConfigurationManager.AppSettings["SinoPacInstructionBackUp"];
              if (!Directory.Exists(targetDirectory))
              {
                  Directory.CreateDirectory(targetDirectory);
              }
              string fileName = Path.GetFileName(localFilePath);
              string datepart = DateTime.Now.ToString("yyyyMMddHHmmss");
              string newFileName = datepart + "_" + fileName;
              string sourceFilePath = Path.Combine(sourceDirectory, fileName);
              string targetFilePath = Path.Combine(targetDirectory, newFileName);
              System.IO.File.Move(sourceFilePath, targetFilePath);
        }
        else
        {
           UploadStatusChanged?.Invoke("API 1呼叫失敗");
           string errorMessage = "API 呼叫失敗 " + apiResponse.StatusCode;
           //寄信
           string subject = "【aa & bb】aaa信託收檔結果查詢服務_" + DateTime.Now.ToString("yyyy/MM/dd");
           string body = "API 呼叫失敗，請至AllPayLog查看原因";
           emailService.SendErrorEmail(subject, body);
           BatchLogService.WriteLog(errorMessage);
           apiResponse.Success = false;
         }
      }
   }
   catch (Exception ex)
   {
       UploadStatusChanged?.Invoke("未預期的異常");
       BatchLogService.WriteLog("發生錯誤: " + ex.ToString(), 2);
   }
}
```
    
###  ApiService (API接應)
    
**建構子 (Constructor)**
在建構子中，你設定了 HttpClient 的一些預設請求頭，並初始化了 HttpClient 實例。這個設計有助於所有的 API 請求都使用相同的 HTTP 頭資訊。
```csharp=
public apiService()
{
   ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
   httpClient = new HttpClient();
   httpClient.DefaultRequestHeaders.Add("X-xxxxxx", "x");
   httpClient.DefaultRequestHeaders.Add("X-xxxxx", "xxxxx");
   httpClient.DefaultRequestHeaders.Add("xxxx", "xxxxxxxxxxxxxxxx");
}
```    
**CallApi 方法**
這個方法接受兩個參數：API 的 endpoint (selectedApiEndpoint) 和加密過的請求體 (encryptedResquestBody)。
1. 發送 API 請求：使用 HttpClient 的 PostAsync 方法來發送一個 POST 請求。
2. 處理 API 響應：
   * 如果 API 請求成功並且返回了資料，將返回的資料解密並儲存。
   * 如果 API 請求成功但查無資料，寫入 Log 並返回一個不成功的響應。
3. 異常處理：對於 HttpRequestException、IOException，和一般的 Exception，分別進行了處理。
4. 日誌和通知：利用 BatchLogService 和 EmailService 記錄事件和發送通知。

```csharp=
public ResponseBodyModel CallApi(string selectedApiEndpoint, string encryptedResquestBody)
{
  ResponseBodyModel apiResponse = new ResponseBodyModel();
  try
  {
     StringContent content = new StringContent(encryptedResquestBody, Encoding.UTF8, "application/json");
     HttpResponseMessage apiresponse = Task.Run(() => httpClient.PostAsync(selectedApiEndpoint, content)).GetAwaiter().GetResult();
     if (apiresponse.IsSuccessStatusCode == true)
     {
        string responseBody =  apiresponse.Content.ReadAsStringAsync().GetAwaiter().GetResult(); 
        if (responseBody.Contains("查無資料"))
        {
           // 寫入 Log 並終止後續程式
           BatchLogService.WriteLog("API 請求成功但查無資料. Response Body:" + responseBody,2);
           apiResponse.Success = false;
           apiResponse.Data = "查無資料";
        }
        else
        {
            // 正常處理
            string key = ConfigurationManager.AppSettings["MySecretKey"];
            byte[] k1 = Convert.FromBase64String(key);
            EncryptorService.FlattenedJose encryptedData = JsonConvert.DeserializeObject<EncryptorService.FlattenedJose>(responseBody);
            string decodedData = DecodeDataService.DecodeFlattenedJoseContainer(encryptedData, k1);
            apiResponse.Success = true;
            apiResponse.Data = decodedData;
            BatchLogService.WriteLog("API 請求成功. Response Body:" + apiResponse.Data);
         }
      }
      else
      {
         string responseBody = apiresponse.Content.ReadAsStringAsync().GetAwaiter().GetResult();
         string statusCode = ((int)apiresponse.StatusCode).ToString();
         string errorMessage = "API 請求失敗，狀態碼: " + statusCode + ", reason " + apiresponse.ReasonPhrase + ", and response body " + responseBody;

         #region 寄信
         string subject = "【aa & bb】aa信託信件通知" + DateTime.Now.ToString("yyyy/MM/dd");
         string body = "API 呼叫失敗，請至AllPayLog查看原因";
         EmailService emailService = new EmailService();
         emailService.SendErrorEmail(subject, body);
         #endregion

         BatchLogService.WriteLog(errorMessage, 2);
         apiResponse.Success = false;
         apiResponse.Data = errorMessage;
      }
    }
    catch (HttpRequestException httpRequestException)
    {
       apiResponse.Success = false;
       BatchLogService.WriteLog("HTTP請求失敗: " + httpRequestException.Message, 2);
       if (httpRequestException.InnerException != null)
       {
           BatchLogService.WriteLog("內部異常:"+ httpRequestException.InnerException, 2);
       }
    }
    catch (IOException ioException)
    {
        apiResponse.Success = false;
        BatchLogService.WriteLog("IO異常:" +ioException.Message, 2);
        if (ioException.InnerException != null)
        {
           BatchLogService.WriteLog("內部異常:"+ioException.InnerException, 2);
        }
    }
    catch (Exception ex)
    {
        apiResponse.Success = false;
        BatchLogService.WriteLog("一般異常:" + ex.ToString(), 2);
    }
    return apiResponse;
}
```

### EncryptorService(AES加密演算法)    
    
    
**FlattenedJose 類別**
這個內嵌類別用於保存 Jose 容器的各個部分：_protected、iv（初始化向量）、ciphertext（密文）和 tag   
    
```csharp=
public class FlattenedJose
{
    [JsonProperty(propertyName: "protected")]
    public string _protected { get; set; }
    public string iv { get; set; }
    public string ciphertext { get; set; }
    public string tag { get; set; }
}
```
 **EncodeFlattenedJoseContainer 函數**
    
 接受三個參數：

* jsonPayload: 要加密的原始文本。
* key: 用於加密的金鑰。
* iv: 初始化向量。
該函數使用 GCM（Galois/Counter Mode）進行加密，這是一種使用對稱密鑰進行加密和附加認證數據（AAD）的模式。   
    
 **Base64UrlEncode 函數**
負責將輸入的字節數組進行 Base64 Url 編碼。  
```csharp=
public static string Base64UrlEncode(byte[] input)
{
    string output = Convert.ToBase64String(input);
    output = output.Split('=')[0]; // 移除任何尾隨的 '='
    output = output.Replace('+', '-'); // '+' => '-'
    output = output.Replace('/', '_'); // '/' => '_'
    return output;
}
```    
**流程**  
**1. 初始化保護頭部（_protected）**
這部分定義了加密的參數，如加密算法（alg）、金鑰識別符（kid）和加密模式（enc）。這些參數被序列化為一個 JSON 對象，並進行 Base64Url 編碼。 
```csharp=
var _protected = JsonConvert.SerializeObject(new Dictionary<string, string>
{
    { "alg","dir"},
    { "kid","K1"},
    { "enc","A128GCM"}
});
_protected = Base64UrlEncode(Encoding.UTF8.GetBytes(_protected));

```
**2. 附加認證數據（AAD )**   
 _protected 數據會被轉換為 ASCII 字節數組，這將作為 GCM 加密中的附加認證數據（AAD）。   
```csharp=
var aad = Encoding.ASCII.GetBytes(_protected);
```   
**3. 準備要加密的文本**
輸入的 JSON payload（jsonPayload）被轉換為一個 UTF-8 字節數組。   
```csharp=
var text = Encoding.UTF8.GetBytes(jsonPayload);
```    
**4. 初始化 GCM cipher**
這裡使用了 BouncyCastle 的 GcmBlockCipher 和 AesEngine。AES 加密算法和 GCM 模式是在這一步中設置的   
```csharp=
GcmBlockCipher cipher = new GcmBlockCipher(new AesEngine());
AeadParameters parameters = new AeadParameters(new KeyParameter(key), 128, iv, aad);
cipher.Init(true, parameters);
```    
**5.執行加密** 
在初始化後，cipher 對象用於對文本（text）進行加密。 
```csharp=
byte[] ciphertext = new byte[cipher.GetOutputSize(text.Length)];
int len = cipher.ProcessBytes(text, 0, text.Length, ciphertext, 0);
cipher.DoFinal(ciphertext, len);
```    
**6.提取認證標記（Tag）** 
最後 16 個字節是 GCM 的認證標記，它用於後續的消息完整性檢查。   
```csharp=
byte[] tag = new byte[16];
Array.Copy(ciphertext, ciphertext.Length - 16, tag, 0, 16); 
```    
**7.封裝結果**  
所有這些數據（_protected，iv，ciphertext 和 tag）被封裝到一個 FlattenedJose 對象中，然後返回。  
```csharp=
return new FlattenedJose
{
    _protected = _protected,
    iv = Base64UrlEncode(iv),
    ciphertext = Base64UrlEncode(ciphertext.Take(ciphertext.Length - 16).ToArray()),
    tag = Base64UrlEncode(tag)
};

```    
    
**完整版**
```csharp=
public class EncryptorService
{
    public static FlattenedJose EncodeFlattenedJoseContainer(string jsonPayload, byte[]key,byte[]iv)
    {
       var _protected = JsonConvert.SerializeObject(new Dictionary<string, string>
        {
             { "alg","dir"}, Encryption）
             { "kid","K1"}, 
             { "enc","A128GCM"}  
         });
         _protected = Base64UrlEncode(Encoding.UTF8.GetBytes(_protected));
         var aad = Encoding.ASCII.GetBytes(_protected);
         var text = Encoding.UTF8.GetBytes(jsonPayload);
         GcmBlockCipher cipher = new GcmBlockCipher(new AesEngine());
         AeadParameters parameters = new AeadParameters(new KeyParameter(key), 128, iv, aad);
         cipher.Init(true, parameters);
         byte[] ciphertext = new byte[cipher.GetOutputSize(text.Length)];
         int len = cipher.ProcessBytes(text, 0, text.Length, ciphertext, 0);
         cipher.DoFinal(ciphertext, len);
         byte[] tag = new byte[16];
         Array.Copy(ciphertext, ciphertext.Length - 16, tag, 0, 16);
         return new FlattenedJose
         {
             _protected = _protected,
             iv = Base64UrlEncode(iv),
             ciphertext = Base64UrlEncode(ciphertext.Take(ciphertext.Length - 16).ToArray()),
             tag = Base64UrlEncode(tag)
          };
     }
}
```

    
    
    
    
    
    
### DecodeDataService(AES解密演算法)
    
   **流程：**
**1. 解碼和驗證 _protected 字段**
使用 Base64Url 解碼 _protected 字段並將其解析為字典。
    
**2. 算法和金鑰標識符驗證**
驗證解碼後的字典是否包含 "alg", "kid", 和 "enc" 字段，以及它們是否具有預期的值。

**3. 初始化變數**
* aad: 由 _protected 字段轉換為 ASCII 字節數組。
* iv: 初始化向量，由 encyryptedData.iv 解碼。
* ciphertext: 密文，由 encyryptedData.ciphertext 解碼。
* tag: 標記（或許可字元），由 encyryptedData.tag 解碼。
* key: 解密金鑰，就是輸入的 k1。
    
**4. 初始化 GCM cipher**
使用 AesEngine 初始化 GcmBlockCipher，並設置參數。
    
**5. 進行解密**
* 結合密文和標記以形成一個連續的字節數組。
* 使用 cipher 進行解密。
    
**6. 返回解密後的文本**
將解密後的字節數組轉換為 UTF-8 字符串。 
    
    
```csharp=
class DecodeDataService
{
    public static string DecodeFlattenedJoseContainer(FlattenedJose encyryptedData, byte[] k1)
    {
        
        var _prot = JsonConvert.DeserializeObject<Dictionary<string, string>>(
        Encoding.UTF8.GetString(DecodeBase64Url(encyryptedData._protected)));
        if (!(_prot.ContainsKey("alg") && _prot.ContainsKey("kid") && _prot.ContainsKey("enc")))
            throw new InvalidCastException("Invalid protected header");
            if (!(_prot["alg"] == "dir" && _prot["enc"] == "A128GCM"))
            throw new InvalidCastException("Unsupported algorithms");

            var aad = Encoding.ASCII.GetBytes(encyryptedData._protected);
            var iv = DecodeBase64Url(encyryptedData.iv);
            var ciphertext = DecodeBase64Url(encyryptedData.ciphertext);
            var tag = DecodeBase64Url(encyryptedData.tag);
                byte[] key = k1;
            var cipher = new GcmBlockCipher(new AesEngine());
            var parameters = new AeadParameters(new KeyParameter(key), 128, iv, aad);
            cipher.Init(false, parameters);
            byte[] ciphertextWithTag = ciphertext.Concat(tag).ToArray();
            byte[] plaintextBytes = new byte[cipher.GetOutputSize(ciphertext.Length + tag.Length)];
            int length = cipher.ProcessBytes(ciphertextWithTag, 0, ciphertextWithTag.Length, plaintextBytes, 0);
                cipher.DoFinal(plaintextBytes, length);

                return Encoding.UTF8.GetString(plaintextBytes).TrimEnd('\0');
    }
    public static byte[] DecodeBase64Url(string base64Url)
    {
        base64Url = base64Url.Replace('-', '+').Replace('_', '/');

        int padding = (4 - (base64Url.Length % 4)) % 4;
        base64Url += new string('=', padding);
        return Convert.FromBase64String(base64Url);
    }
}
```