iT邦幫忙

2025 iThome 鐵人賽

DAY 11
0

後端站台在成功驗證並取得請求來源的身份資訊後,可以開始整合基本人員組織資料。
過去取得人資檔案需要先掌握全公司的人事與組織資料,模式通常是每日同步一份公司人事與組織的文檔,並寫入資料庫中。每個不同的系統就得重複這個流程一次。這次,我們想嘗試不一樣的做法:

  • 人事與組織資料改為寫入快取,以加快讀取速度。
  • 取得人事資料的方式改為透過 API 服務,並且可開放給後續其他專案與系統共享此服務。

因此需要增加一個服務站台,專門提供員工基本資料 API,未來也可以開放給其他系統使用。加入 HR API Service 服務後,單一 Request 流程如下:
圖11-1
圖11-1:合併人事資料後的資料流

開始實作 HR API Service 吧

  1. 建立 HR Service 專案。由於該服務相對單純,僅提供基本員工與組織資料,因此可以選擇最精簡的模式minimal
 dotnet new webapi -n <project name> --use-minimal-apis
  1. 開啟 appsettings.json ,新增讀取人員組織資料的位置設定
  "HRFilePath": {
    "EmployeeFilePath": "files/xxxxx_EMP",
    "DepartmentFilePath": "files/XXXX_DEPT.v1.1" ,
  }
  1. 開啟專案下的 Program.cs,綁定讀取人員組織資料的位置,並啟用 Swagger。
  builder.Services.Configure<HRFilePath>(builder.Configuration.GetSection("HRFilePath"));
  app.UseSwagger();            
  c.SwaggerEndpoint("/ISMS_APSYS_HRAPI/swagger/v1/swagger.json", "My API V1");
  c.RoutePrefix = "swagger"; 
  1. 新增 DataReader.cs 用來讀取員工資料,並將讀取後的結果寫入 List<Employee>,存放於 cache 中。因人事資料每天都會異動, cache 資料需要定期清除與更新,需滿足以下條件:
    5. 每日於固定時間(假設為 07:00)重新讀取人事資料。
    6. 為避免人事資料來源端提供的檔案有問題,文檔中的資料量需大於 1 萬筆,才能清除 cache。
    7. 若低於 1 萬筆,則不執行清除 cache,讓 API 服務先提供前一天的人事資料。
public static class CsvReader
    {
        public static List<Employee> Read(IMemoryCache cache, ILogger logger, FilePaths FilePaths)
        {
           // 確認是否需要清除快取
           // 設置快取條目並傳遞 ILogger
           // 計算下次 07:00 的時間
            DateTime now = DateTime.Now;
            DateTime next7am = now.Hour < 7
                    ? now.Date.AddHours(7)
                    : now.Date.AddDays(1).AddHours(7);
             if (lines.Length > 10000) //確認文檔資料內容筆數正常
             {
                    var cacheOptions = new MemoryCacheEntryOptions
                    {
                        AbsoluteExpiration = next7am, // 每天早上七點清除快取
                        PostEvictionCallbacks = // 添加回調以在快取條目過期時記錄日誌
                        {
                            new PostEvictionCallbackRegistration
                            {
                                EvictionCallback = (key, value, reason, state) =>
                                {
                                    var log = state as ILogger;
                                    log?.LogInformation($"Cache entry '{key}' was evicted. Reason: {reason}");

                                },
                                State = logger // 傳遞 ILogger 作為 State
                            }
                        }
                    };
                    cache.Set("employees", employees, cacheOptions);
                    logger.LogInformation("Cache entry 'employees' was set.");
                }
            }
            //如果快取為空值,則重新讀取人事組織文檔
            if (!cache.TryGetValue("employees", out List<Employee> employees))
            {
                employees = new List<Employee>();
                csvFilePaths.syncFileDate();
                var lines = File.ReadAllLines(csvFilePaths.EmployeeFilePath);
                // 讀取第一行作為欄位名稱
                var headers = lines.First().Split(',');
                foreach (var line in lines.Skip(1)) // Skip header line
                {
                   
                     case "EMAIL_ADDR":
                                employee.Email = values[i];
                                break;
                     case "NAME":
                                employee.Name = values[i];
                                break;
                      case "EMPLID":
                                employee.EMPLID = values[i];
                 }
                  employees.Add(employee);

                }
            return employees;
        }
  1. Program.cs 新增取得員工資訊 API
app.MapGet("/Employee", (string? email, string? emplid, IMemoryCache cache, ILogger<Program> logger, IOptions<FilePaths> FilePaths) 
{

    //透過 CsvReader.ReadCsv 讀取員工檔案資料
    var employees = DataReader.Read(cache, logger, csvFilePaths.Value);
    // 讀取完畢後進行資料搜尋,將搜尋結果回傳
    Employee? employee = null;
    if (!string.IsNullOrEmpty(email))
    {
        employee = employees.FirstOrDefault(p => p.Email == email );
    }
    else if (!string.IsNullOrEmpty(emplid))
    {
        employee = employees.FirstOrDefault(p => p.EMPLID == emplid);
    }
    if (employee == null)
    {
        return Results.NotFound();
    }
    var employeeJson = JsonSerializer.Serialize(employee,options1);
    return Results.Ok(employee);

})

.WithName("GetEmployeeInfo")
.WithOpenApi()
.Produces<Employee>(StatusCodes.Status200OK) // 指定返回的對象類型
.Produces(StatusCodes.Status404NotFound); // 指定 404 狀態碼

確認結果

  • 啟動服務
    圖11-2
    圖11-2:成功啟動 HR API人事資料服務

  • 執行測試呼叫 API 服務
    圖11-3
    圖11-3:可取得 API 回應

  • 檢視回應時間
    圖11-4
    圖11-4:檢視回應時間為 15.15 秒,因為需要讀取文檔並寫入快取,時間較長。

  • 重新測試呼叫 API 服務,第 2 次呼叫可直接由快取資料中讀取並回應,回應時間為 0.02 秒。
    圖11-5
    圖11-5:第 2 次呼叫可直接由快取資料中回應時間縮短,縮短15.13 秒 🎊🎊🎊🎊


Ending Remark

一樣來個圖清楚了解知道每個環節的作用
圖11-6
圖11-6:員工資料 API 查詢與快取機制流程


上一篇
Day10 開始與後端進行互動
系列文
全端工程師團隊的養成計畫11
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言