出於安全考慮,幾乎每一個動態網站都具有IP
地址屏蔽功能,而網上流傳的不少關於該功能的教程大都採用字符串保存和驗證IP
地址,我認爲這是不太科學的,我試圖找到最佳的設計方案。
「IP
地址的長度爲32
位,分爲4
段,每段8
位,用十進制數字表示,每段數字範圍爲0
~255
,段與段之間用句點隔開。」
由此咱們瞭解到,IP
地址其實是一個32
位正整數,在C#
中可使用
uint
類型來表示,但SQLServer
數據庫裏好像沒有對應的類型;轉而使用數據庫支持的
int
類型的話,則會出現溢出的狀況;所以咱們作出妥協:使用
long(
bigint)
類型。
TIP:
int
取值範圍:-2,147,483,648 到 2,147,483,647
uint
取值範圍:0
到 4,294,967,295
long
取值範圍:-9,223,372,036,854,775,808
到 9,223,372,036,854,775,807
那麼如何將IP
地址轉爲整數呢?咱們看到IPAddress
類中有一個「[
否決的]
」實例屬性Address
,這個屬性的確能夠返回一個
long
值,可是測試一下,獲得的數據確實這樣的:
「127.0.0.1
」 -> 16777343
「127.0.0.2
」 –> 33554559
的確該讓它「否決」,這樣的整數對咱們來講毫無心義,咱們是沒法經過這樣的方法比較傳入的IP
是否介於兩個IP
值之間的。
那麼只有本身動手了,咱們將經過IPAddress
類的GetAddressBytes()
實例方法獲取IP
的4
個段的值,而後將它們組合爲一個整數,下面將提供這個擴展方法:
/// <summary>
///
將
IP
地址轉爲整數形式
/// </summary>
/// <returns>
整數</returns>
public
static long
轉換爲整數(
this IPAddress ip)
{
int x = 3;
long o = 0;
foreach (byte f in ip.GetAddressBytes())
{
o += (long)f << 8 * x--;
}
return o;
}
你能夠這樣使用這個擴展方法:
IPAddress
.Parse("127.0.0.1").
轉換爲整數()
這裏還有一個用於逆轉換的擴展方法,用於將
long
轉回IPAddress
:
/// <summary>
///
將整數轉爲
IP
地址
/// </summary>
/// <returns>
IP
地址</returns>
public
static IPAddress
轉換爲IP
地址(
this long l)
{
var b = new byte[4];
for (int i = 0; i < 4; i++)
{
b[3 - i] = (byte)(l >> 8 * i & 255);
}
return new IPAddress(b);
}
這樣咱們就能夠經過計算獲得正確並有意義的整數了:
「127.0.0.1
」 -> 2130706433
「127.0.0.2
」 –> 2130706434
OK
,確立了方案核心,下面開始設計SQLServer
數據表:
這樣設計後,在添加時將起始和終止IP
地址轉爲long
類型並存入,並指定一個過時時間。
在驗證時只須要獲取全部未過時的條目,比較傳入的IP
地址是否介於起始值和終止值之間便可。
以往經過字符串存儲和驗證的方案中,屏蔽時要麼屏蔽一個精確的IP
地址,要麼就屏蔽一段或兩段IP
,如「192.168.*.*
」,要想屏蔽「192.168.1.200
」到「192.168.4.64
」之間的IP
的話,將會很是麻煩;
而咱們這樣設計就能夠輕鬆實現:「192.168.1.200
」在數據庫裏存儲的是「3232235976
」,「192.168.4.64
」在數據庫中是「3232236608
」,即便使用肉眼也能極快地判斷傳入的地址是否介於它們之間,更不要說計算機查詢了。
下面爲數據表生成EDM
模型:
添加IP
屏蔽記錄的代碼:
/// <summary>
///
添加一個新的
IP
屏蔽區段
/// </summary>
/// <param name="IP
區段起始值">
起始
IP
,如
61.51.200.0</param>
/// <param name="IP
區段終止值">
終止
IP
,如
61.51.255.255</param>
/// <param name="
過時時間">
屏蔽截止時間</param>
/// <returns>
ID
號</returns>
public
static Guid
添加(
string IP
區段起始值,
string IP
區段終止值,
DateTime
過時時間)
{
var id = Guid.NewGuid();
var sip = IPAddress.Parse(IP
區段起始值)
.
轉換爲整數();
var eip = IPAddress.Parse(IP
區段終止值)
.
轉換爲整數();
using (var c = new SiteMainEntities())
{
//
檢測是否已存在相同的
IP
屏蔽記錄
var a = c.IP
地址屏蔽
.Where(f
=> f.
區段起始值
== sip && f.
區段終止值
== eip);
//
若是存在則更新其過時時間
if (a.Count()>0)
{
var l = a.First();
if (l.
過時時間
<
過時時間
) l.
過時時間
=
過時時間;
}
//
不存在則正常添加一個新的屏蔽記錄
else c.AddToIP
地址屏蔽(
new IP
地址屏蔽 { ID
= id,
過時時間
=
過時時間
,
區段起始值
= sip,
區段終止值
= eip });
c.SaveChanges();
}
return id;
}
檢測指定IP
地址是否被屏蔽的代碼:
/// <summary>
///
檢測指定
IP
地址是否已受到屏蔽
/// </summary>
/// <param name="IP
地址">
要檢測的
IP
地址</param>
/// <returns>
是否屬於已屏蔽的
IP</returns>
public
static bool
檢測是否被屏蔽(
string IP
地址)
{
var ip = IPAddress.Parse(IP
地址)
.
轉換爲整數();
using (var c = new SiteMainEntities())
{
return c.IP
地址屏蔽
.Count(f
=> f.
過時時間
> DateTime.Now && ip >= f.
區段起始值
&& ip <= f.
區段終止值)
> 0;
}
}
這種方案比起以往的字符串驗證方案來講優雅了許多,並能夠提升數據庫查詢的效率,建議各位在往後的網站開發中都採用此方案。