分享程式代碼相關筆記
目前文章總數:157 篇
最後更新:2024年 12月 07日
1.顯示頁面,這邊假設整個網站有2個頁面 (使用者點擊任一頁面都會紀錄IP)
2. 網站建構:網站程序初始化的入口點,並且註冊注入、配置Middleware
3. 中介層:處理在頁面請求與響應回覆之間處理的工作,做邊紀錄用戶IP
4. 資料流:處理資料紀錄,與DB的工作
5. Sqlite靜態工具:為一個靜態類,基本的Sqlite + Dapper 的整併工具
頁面基本上只模擬用戶進入網站,可能頁面A
,也可能頁面B
但都會經過中介層紀錄IP
頁面A、B代碼:
@page "/"
<PageTitle>頁面A</PageTitle>
<h1>頁面A</h1>
<p role="status">範例頁面A</p>
<div>
點擊此頁面會記錄當前操作者IP (頁面B同)
</div>
@code {
}
創建初始化網站程序所需的注入
在CreateBuilder中添加以下:
builder.Services.AddSingleton<ISqliteRepository, SqliteRepository>();
builder.Services.AddHttpContextAccessor();
使用Build中,添加以下,實現中介層:
app.UseMiddleware<UserIPLoggingMiddleware>();
在生命週期中,中介層會在靠近Resonse的地方處理,
確認正確性、路由、驗證後,確認該用戶資料都OK,才進行紀錄IP的動作
4-1. 實現Invoke,在每次用戶點擊網頁都會進入此層
public async Task Invoke(HttpContext context)
{
var now = DateTime.Now;
//1. 取得IP-關鍵代碼
string userIpAddress = GetUserIP();
//2. 存在才紀錄
if (!string.IsNullOrEmpty(userIpAddress))
{
var row = new Model.UserIpRecordModel()
{
UserIp = userIpAddress,
Date = now.ToString("yyyyMMdd")
};
_respoitory.CreatedRow(row);
}
await _next(context);
}
4-2. 取得IP-此為關鍵代碼,必須依照順序
取得Header資料
※基本認知:所有瀏覽器傳到伺服器的Browser資料永遠都可以偽造(機器人),但正常操作的用戶還是可以取得正確IP
X-Forwarded-For :反向代理伺服器的多個IP
X-Real-IP : 用戶真實IP - 代理伺服器預設位置
HttpContext.HttpContext.Connection.RemoteIpAddress 取得Http上下文IP位址。
private string GetUserIP()
{
//1. 取得"X-Forwarded-For" => 反向代理伺服器的多個IP,以;區隔,最左邊是用戶的IP
string userIPAddress = _httpContextAccessor.HttpContext.Request.Headers["X-Forwarded-For"].FirstOrDefault();
//2-1. 假設沒有經過反向代理
if (string.IsNullOrEmpty(userIPAddress))
{
//2-2. 取得"X-Real-IP" => 常見的一些代理伺服器會將最終的用戶真實IP放在此標頭
userIPAddress = _httpContextAccessor.HttpContext.Request.Headers["X-Real-IP"].FirstOrDefault();
if (string.IsNullOrEmpty(userIPAddress))
{
//3. 預設瀏覽器請求的IP
userIPAddress = _httpContextAccessor.HttpContext.Connection.RemoteIpAddress?.ToString();
}
}
return userIPAddress;
}
中介層確定可寫入資料後,會進入Repository將Insert SQL寫入
實際寫入才會呼叫Util,這邊是用Sqlite做個範例
※即使是小型專案,也盡量避免用INSERT OR REPLACE INTO,這個在叫用率高的情境下,語法效能不佳,對DB是負擔
這是Sqlite專用語法。
/// <summary>
/// 更新IP資料
/// </summary>
public void CreatedRow(UserIpRecordModel row)
{
var sql = $@"
INSERT OR REPLACE INTO UserIpRecord (UserIp, Date, CreatedTime)
VALUES (@UserIp, @Date, datetime('now', 'localtime'))
;";
SqlLiteDbUtil.Master.Execute(sql, row);
}
這邊實現一個Dapper的Sqlite資料庫操作。
資料表相對簡單,主鍵為以下:
UserIp:用戶IP
Date:屬於年月日 (yyyyMMdd)
public static class SqlLiteDbUtil
{
//1. 宣告初始化建構資料庫的位址 ※同專案位置
public const string DatabaseFileName = @"MyUserLoginIp.db";
public const string ConnectionString = "Data Source=" + DatabaseFileName;
//2-1. 靜態只會觸發第一個初始化,所以這邊進行建構
static SqlLiteDbUtil()
{
Master = new SQLiteConnection(ConnectionString);
CreateDatabaseIfNotExists();
}
//2-2. 利用Dapper的IDbConnection 實現資料庫的操作
public static IDbConnection Master { get; private set; }
#region 資料庫建構
private static void CreateDatabaseIfNotExists()
{
//3. 如果資料庫不存在就建立初始的表
if (!File.Exists(DatabaseFileName))
{
//Create Local Database
Master.Open();
//Initial Tables
CreateDatabase();
}
void CreateDatabase()
{
Master.Execute($@"
CREATE TABLE UserIpRecord (
UserIp TEXT NOT NULL,
Date TEXT NOT NULL,
CreatedTime DATETIME,
PRIMARY KEY (UserIp, Date)
);");
}
#endregion
}
}
這是使用的範例檔案,Github連結
然後將檔案部署起來,然後透過兩個位置做連結
下載範例檔案後執行建置運行
IP:71.23
下載範例檔案後執行建置運行
IP:51.70
我們連到Server看到Sqlite產生的檔案,用Sqlite Browser工具開啟檢視
可以看到IP都有紀錄,且在相同IP位址時,使用者即使切換頁面操作IP也不會重複
※實務上應用緩存、Redis等紀錄,若重複時不對資料庫做任何操作