C# WinForm 使用SMS介面傳送手機驗證碼+圖形驗證碼+IP限制
https://blog.csdn.net/IT_xiao_guang_guang/article/details/104299983
前言
1.傳送手機驗證碼用的是網建的SMS介面(http://sms.webchinese.cn/)
2.手機驗證碼簡單的做了以下限制:
①傳送驗證碼1分鐘只能點擊發送1次
②相同IP手機號碼1天最多提交20次(這裡我用的是本地區域網IP)
③加入圖形驗證碼
注:SMS官網上的建議還有要對手機號碼次數進行限制:單個手機號碼30分鐘最多提交10次。(這個和IP限制次數方法是一樣的,我這裡沒加)
功能實現
一、功能介面
二、建立圖形驗證碼類
關於圖形驗證碼,也可以參照我的這篇文章:C# WinForm 登入介面的圖片驗證碼
建立產生圖形驗證碼的類:
①生成隨機驗證碼字串,用的是Random隨機函式,用GUID生成6位隨機數
②建立驗證碼圖片,將該字串畫在PictureBox控制元件中
ImageVeriCodeClass:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Drawing;//繪圖 using System.Windows.Forms; namespace SMSVeriCode { public class ImageVeriCodeClass { #region 圖片驗證碼功能 /// <summary> /// 生成隨機驗證碼字串 /// </summary> public static string CreateRandomImageCode(int CodeLength) { int randNum; char code; string randomCode = String.Empty;//隨機驗證碼 //生成一定長度的隨機驗證碼 //Random random = new Random();//生成隨機數物件 for (int i = 0; i < CodeLength; i++) { //利用GUID生成6位隨機數 byte[] buffer = Guid.NewGuid().ToByteArray();//生成位元組陣列 int seed = BitConverter.ToInt32(buffer, 0);//利用BitConvert方法把位元組陣列轉換為整數 Random random = new Random(seed);//以生成的整數作為隨機種子 randNum = random.Next(); //randNum = random.Next(); if (randNum % 3 == 1) { code = (char)('A' + (char)(randNum % 26));//隨機大寫字母 } else if (randNum % 3 == 2) { code = (char)('a' + (char)(randNum % 26));//隨機小寫字母 } else { code = (char)('0' + (char)(randNum % 10));//隨機數字 } randomCode += code.ToString(); } return randomCode; } /// <summary> /// 建立驗證碼圖片 /// </summary> public static void CreateImage(string strValidCode, PictureBox pbox) { try { int RandAngle = 45;//隨機轉動角度 int MapWidth = (int)(strValidCode.Length * 21); Bitmap image = new Bitmap(MapWidth, 28);//驗證碼圖片大小-寬和高 //建立繪圖物件Graphics Graphics graph = Graphics.FromImage(image); graph.Clear(Color.AliceBlue);//清除繪畫面,填充背景色 graph.DrawRectangle(new Pen(Color.Black, 0), 0, 0, image.Width - 1, image.Height - 1);//畫一個邊框 graph.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;//模式 Random rand = new Random(); //背景噪點生成 Pen blackPen = new Pen(Color.LightGray, 0); for (int i = 0; i < 50; i++) { int x = rand.Next(0, image.Width); int y = rand.Next(0, image.Height); graph.DrawRectangle(blackPen, x, y, 1, 1); } //驗證碼旋轉,防止機器識別 char[] chars = strValidCode.ToCharArray();//拆散字串成單字元陣列 //文字居中 StringFormat format = new StringFormat(StringFormatFlags.NoClip); format.Alignment = StringAlignment.Center; format.LineAlignment = StringAlignment.Center; //定義顏色 Color[] c = { Color.Black, Color.Red, Color.DarkBlue, Color.Green, Color.Orange, Color.Brown, Color.DarkCyan, Color.Purple }; //定義字型 string[] font = { "Verdana", "Microsoft Sans Serif", "Comic Sans MS", "Arial", "宋體" }; for (int i = 0; i < chars.Length; i++) { int cindex = rand.Next(7); int findex = rand.Next(5); Font f = new System.Drawing.Font(font[findex], 13, System.Drawing.FontStyle.Bold);//字型樣式(引數2為字型大小) Brush b = new System.Drawing.SolidBrush(c[cindex]); Point dot = new Point(16, 16); float angle = rand.Next(-RandAngle, RandAngle);//轉動的度數 graph.TranslateTransform(dot.X, dot.Y);//移動游標到指定位置 graph.RotateTransform(angle); graph.DrawString(chars[i].ToString(), f, b, 1, 1, format); graph.RotateTransform(-angle);//轉回去 graph.TranslateTransform(2, -dot.Y);//移動游標到指定位置 } pbox.Image = image; } catch (ArgumentException) { MessageBox.Show("驗證碼圖片建立錯誤"); } } #endregion } }
三、建立儲存IP地址的資料庫表
對於IP次數的限制,我是把IP地址存在了資料庫的表裡。
①先建立一個數據庫SMSVeriCode
②建立tIP表的sql語句:
create table tIP
(
IP nvarchar(32), --IP地址
SendTimes int, --傳送次數
SMSVeriCodeTime datetime2 --手機驗證碼傳送時間
)
四、建立手機驗證碼類
建立傳送手機驗證碼的類:
①傳送手機驗證碼功能
②驗證國內手機號
③根據IP地址限制手機號碼1天最多提交20次
④獲取本地IP地址資訊(區域網IP)
SMSVeriCodeClass:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
//需要用到的名稱空間
using System.IO;
using System.Net;
using System.Text;
using System.Windows.Forms;
using System.Text.RegularExpressions;
using System.Data.SqlClient;
using System.Net.Sockets;
namespace SMSVeriCode
{
public class SMSVeriCodeClass
{
#region 手機驗證碼功能
/// <summary>
/// 獲取隨機驗證碼
/// 利用GUID獲取隨機數
/// </summary>
/// <param name="Phone">手機號</param>
/// <param name="MinSeed">隨機數最小值</param>
/// <param name="MaxSeed">隨機數最大值</param>
/// <returns>隨機驗證碼</returns>
public static string GetSMSValidCode(int MinSeed, int MaxSeed)
{
byte[] pbyte = Guid.NewGuid().ToByteArray();//生成位元組陣列
int seed = BitConverter.ToInt32(pbyte, 0);//利用BitConvert方法把位元組陣列轉換為整數
Random random = new Random(seed);//以生成的整數作為隨機種子
string smsVeriCode = random.Next(MinSeed, MaxSeed).ToString();
return smsVeriCode;
}
/// <summary>
/// 傳送驗證碼的函式
/// </summary>
/// <param name="Phone"></param>
/// <param name="textContent"></param>
/// <returns>返回傳送的結果:數字</returns>
public static string SendSMSVeriCode(string UidName, string KeyMD5, string Phone, string TextContent)
{
string strUrl = "http://utf8.sms.webchinese.cn/?";//UTF-8編碼Url
string strUidName = "Uid=";//SMS平臺的使用者名稱
string strKeyMD5 = "&key=";//SMS平臺的介面密匙;strKeyMD5=介面金鑰32位MD5加密(大寫)
string strPhone = "&smsMob=";//目的手機號碼
string strTextContent = "&smsText="; //簡訊內容;普通簡訊70個字/條
strUrl = strUrl + strUidName + UidName + strKeyMD5 + KeyMD5 + strPhone + Phone + strTextContent + TextContent;
string SMSResult = GetFromUrl(strUrl);//傳送簡訊,得到返回值
//MessageBox.Show(Result);
//string JudgeResult = GetResult(SMSResult);//判斷返回值
//return JudgeResult;
return SMSResult;
}
/// <summary>
/// 傳送簡訊,得到返回值
/// </summary>
public static string GetFromUrl(string url)
{
string strReturn = null;
if (url == null || url.Trim().ToString() == "")
{
return strReturn;
}
else//手機號正確
{
string targeturl = url.ToString();
try
{
HttpWebRequest hr = (HttpWebRequest)WebRequest.Create(targeturl);
hr.UserAgent = "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1)";
hr.Method = "GET";
hr.Timeout = 30 * 60 * 1000; //30分鐘超時
WebResponse hs = hr.GetResponse();
Stream sr = hs.GetResponseStream();
StreamReader ser = new StreamReader(sr, Encoding.Default);
strReturn = ser.ReadToEnd();
}
catch (Exception ex)
{
strReturn = null;
MessageBox.Show(ex.Message);
}
}
return strReturn;
}
/// <summary>
/// 判斷返回值
/// </summary>
public static string GetResult(string strReturn)
{
int result = 0;
try
{
result = int.Parse(strReturn);
switch (result)
{
case -1:
strReturn = "沒有該使用者賬戶";
break;
case -2:
strReturn = "介面金鑰不正確,不是賬戶登陸密碼";
break;
case -21:
strReturn = "MD5介面金鑰加密不正確";
break;
case -3:
strReturn = "簡訊數量不足";
break;
case -11:
strReturn = "該使用者被禁用";
break;
case -14:
strReturn = "簡訊內容出現非法字元";
break;
case -4:
strReturn = "手機號格式不正確";
break;
case -41:
strReturn = "手機號碼為空";
break;
case -42:
strReturn = "簡訊內容為空";
break;
case -51:
strReturn = "簡訊簽名格式不正確,介面簽名格式為:【簽名內容】";
break;
case -52:
strReturn = "簡訊簽名太長";
break;
case -6:
strReturn = "IP限制";
break;
default:
strReturn = "成功傳送" + result + "條簡訊!";
break;
}
}
catch (Exception ex)
{
strReturn = ex.Message;
}
return strReturn;
}
#endregion
/// <summary>
/// 驗證國內手機號
/// </summary>
/// <param name="phone">手機號</param>
/// <returns></returns>
public static bool CheckMobilePhone(string phone)
{
//電信手機號碼正則
string dianxin = @"^1[3578][01379]\d{8}$";
Regex dReg = new Regex(dianxin);
//聯通手機號正則
string liantong = @"^1[34578][01256]\d{8}$";
Regex tReg = new Regex(liantong);
//移動手機號正則
string yidong = @"^(134[012345678]\d{7}|1[34578][012356789]\d{8})$";
Regex yReg = new Regex(yidong);
if (dReg.IsMatch(phone) || tReg.IsMatch(phone) || yReg.IsMatch(phone))
{
return true;
}
return false;
}
#region 根據IP限制手機號碼1天最多提交20次
/// <summary>
/// 傳送次數清零
/// </summary>
public static int ResetSendTimes(string IP)
{
string connString = "server=.;database=SMSVeriCode;uid=test;pwd=test";
SqlConnection pSqlConnection = new SqlConnection(connString);
pSqlConnection.Open();
string sqlReset = "update tIP set SendSMSTimes=0 where IP='" + IP + "'";
SqlCommand sqlcmdReset = new SqlCommand(sqlReset, pSqlConnection);
return sqlcmdReset.ExecuteNonQuery();
}
/// <summary>
/// 更新已傳送的次數
/// </summary>
public static int UpdateSendTimes(string IP)
{
string connString = "server=.;database=SMSVeriCode;uid=test;pwd=test";
SqlConnection pSqlConnection = new SqlConnection(connString);
pSqlConnection.Open();
string sqlUdpate = "update tIP set SendSMSTimes = SendSMSTimes + 1 where IP='" + IP + "'";
SqlCommand sqlcmdUpdate = new SqlCommand(sqlUdpate, pSqlConnection);
return sqlcmdUpdate.ExecuteNonQuery();
}
/// <summary>
/// 更新手機驗證碼傳送時間
/// </summary>
public static int UpdateSMSCodeTime(string IP)
{
string connString = "server=.;database=SMSVeriCode;uid=test;pwd=test";
SqlConnection pSqlConnection = new SqlConnection(connString);
pSqlConnection.Open();
string sqlUpdateTime = "update tIP set SMSVeriCodeTime=getdate() where IP='" + IP + "'";
SqlCommand sqlcmdUpdateTime = new SqlCommand(sqlUpdateTime, pSqlConnection);
return sqlcmdUpdateTime.ExecuteNonQuery();
}
#endregion
/// <summary>
/// 獲取本地IP地址資訊
/// </summary>
public static string GetLocalIP()
{
try
{
string HostName = Dns.GetHostName();//主機名
IPHostEntry ip = Dns.GetHostEntry(HostName);
for (int i = 0; i < ip.AddressList.Length; i++)
{
//AddressFamily.InterNetwork表示IPV4
//AddressFamily.InterNetworkV6表示IPV6
if (ip.AddressList[i].AddressFamily == AddressFamily.InterNetwork)
{
return ip.AddressList[i].ToString();
}
}
return "";
}
catch (Exception ex)
{
MessageBox.Show("GetLocalIP Error!" + ex.Message);
return "";
}
}
}
}
五、在Form1中呼叫以上兩個類中的函式,實現功能
注:
以下程式碼中的"使用者名稱"和"金鑰",要去SMS網站中檢視。
string UidName = "XXXXXXX";//SMS平臺的使用者名稱
string KeyMD5 = "XXXXXXXX";//SMS平臺的介面密匙;
IP次數限制的大致思路——相同IP手機號碼1天最多提交20次:
①使用者傳送驗證碼,獲取其IP地址,判斷資料表中是否已經有了這個IP;
如果沒有就向資料表中插入這個新的IP地址,接著傳送驗證碼,在表中更新發送次數+1;
②如果查詢到表中已經有了這個IP地址,就判斷其是否滿足條件(傳送次數<=20,時間<24小時);
③如果不滿足條件,直接返回,不往下繼續執行程式;
④如果滿足條件,往下執行程式,傳送驗證碼。(同時要注意查詢表中的傳送次數是否為0,要更新發送時間)
Form1.cs:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Data.SqlClient;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace SMSVeriCode
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
#region 圖片驗證碼
private const int ImageVeriCodeLength = 4;//驗證碼長度
private String strImageVeriCode = "";//驗證碼
//更新圖片驗證碼
private void UpdateImageVeriCode()
{
strImageVeriCode = ImageVeriCodeClass.CreateRandomImageCode(ImageVeriCodeLength);//生成隨機驗證碼
if (strImageVeriCode == "") return;
ImageVeriCodeClass.CreateImage(strImageVeriCode, pbox1);//建立驗證碼圖片
}
#endregion
//窗體載入時更新圖片驗證碼
private void Form1_Load(object sender, EventArgs e)
{
UpdateImageVeriCode();
}
//點選PictureBox更新圖片驗證碼
private void pbox1_Click(object sender, EventArgs e)
{
UpdateImageVeriCode();
}
/// <summary>
/// 獲取手機驗證碼
/// </summary>
int seconds1 = 60;//倒計時60s
int seconds2 = 60 * 5;//驗證碼有效時間5分鐘
string strSMSVeriCode;
private void btnSendVeriCode_Click(object sender, EventArgs e)
{
//獲取文字框中的資料
string phone = txtPhone.Text.Trim();//手機號
string imagevericode = txtImageVeriCode.Text.Trim();//圖片驗證碼
strSMSVeriCode = SMSVeriCodeClass.GetSMSValidCode(100000, 999999);//隨機6位SMS驗證碼
int totalTime = 5;//SMS驗證碼有效時長
string smsTextContent = "驗證碼:" + strSMSVeriCode + "," + totalTime + "分鐘內有效,請勿洩漏於他人。如非本人操作,請忽略。";//SMS簡訊文字內容
string UidName = "XXXXXXX";//SMS平臺的使用者名稱
string KeyMD5 = "XXXXXXXX";//SMS平臺的介面密匙;
if (String.IsNullOrEmpty(phone))//判斷是否輸入了手機號
{
MessageBox.Show("請輸入手機號!", "提示", MessageBoxButtons.OK, MessageBoxIcon.Information);
txtPhone.Focus();
}
else if (SMSVeriCodeClass.CheckMobilePhone(phone) == false)//判斷手機號格式是否正確
{
MessageBox.Show("請輸入有效的11位手機號碼", "警告", MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
txtPhone.Focus();
}
else if (String.IsNullOrEmpty(imagevericode))//判斷是否輸入了圖片驗證碼
{
MessageBox.Show("請輸入圖片驗證碼!", "提示", MessageBoxButtons.OK, MessageBoxIcon.Information);
txtImageVeriCode.Focus();
}
else if (imagevericode.ToLower() != strImageVeriCode.ToLower())//判斷圖片驗證碼輸入是否正確;不區分大小寫
{
MessageBox.Show("您輸入的驗證碼有誤!", "警告", MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
UpdateImageVeriCode();
txtImageVeriCode.Text = "";
txtImageVeriCode.Focus();
}
else//傳送SMS驗證碼
{
string ip = SMSVeriCodeClass.GetLocalIP();
string connString = "server=.;database=SMSVeriCode;uid=test;pwd=test";
SqlConnection pSqlConnection = new SqlConnection(connString);
pSqlConnection.Open();
string sqlIP = "select *,datediff(hour,SMSVeriCodeTime,getdate()) as TimeIntervel from tIP where IP='" + ip + "'";
SqlCommand sqlcmdIP = new SqlCommand(sqlIP, pSqlConnection);
SqlDataReader sdr = sqlcmdIP.ExecuteReader();
sdr.Read();
if (sdr.HasRows == true)//表中已有此IP地址
{
if (sdr.GetInt32(1) == 20 && sdr.GetInt32(3) <= 24)//20次,24小時
{
MessageBox.Show("相同IP手機號碼1天最多提交20次!", "提示", MessageBoxButtons.OK, MessageBoxIcon.Information);
UpdateImageVeriCode();
txtImageVeriCode.Text = "";
return;
}
else if (sdr.GetInt32(1) == 20 && sdr.GetInt32(3) > 24)
{
SMSVeriCodeClass.ResetSendTimes(ip);//傳送次數置零
}
if (sdr.GetInt32(1) == 0)//次數為0時
{
string SMSResult = SMSVeriCodeClass.SendSMSVeriCode(UidName, KeyMD5, phone, smsTextContent);//傳送
if (Convert.ToInt32(SMSResult) > 0)//成功傳送SMS驗證碼
{
//倒計時開始
timer1.Interval = 1000;
timer1.Start();
timer2.Interval = 1000;
timer2.Start();
btnSendVeriCode.Enabled = false;
UpdateImageVeriCode();
txtImageVeriCode.Text = "";
SMSVeriCodeClass.UpdateSendTimes(ip);//更新發送次數+1
SMSVeriCodeClass.UpdateSMSCodeTime(ip);//更新發送時間
}
else//傳送SMS驗證碼失敗
{
string JudetResult = SMSVeriCodeClass.GetResult(SMSResult);
MessageBox.Show(JudetResult, "提示", MessageBoxButtons.OK, MessageBoxIcon.Information);
}
}
else
{
string SMSResult = SMSVeriCodeClass.SendSMSVeriCode(UidName, KeyMD5, phone, smsTextContent);//傳送
if (Convert.ToInt32(SMSResult) > 0)//成功傳送SMS驗證碼
{
//倒計時開始
timer1.Interval = 1000;
timer1.Start();
timer2.Interval = 1000;
timer2.Start();
btnSendVeriCode.Enabled = false;
UpdateImageVeriCode();
txtImageVeriCode.Text = "";
SMSVeriCodeClass.UpdateSendTimes(ip);//更新發送次數+1
}
else//傳送SMS驗證碼失敗
{
string JudetResult = SMSVeriCodeClass.GetResult(SMSResult);
MessageBox.Show(JudetResult, "提示", MessageBoxButtons.OK, MessageBoxIcon.Information);
}
}
}
else//表中沒有此IP地址—第一次傳送驗證碼
{
string smsSendTime = DateTime.Now.ToString();//SMS驗證碼傳送時間
//--------向資料表tIP中新增資訊------//
pSqlConnection.Close();
pSqlConnection.Open();
string sqlInsert = "insert into tIP(IP,SendSMSTimes,SMSVeriCodeTime)";
sqlInsert += "values('{0}','{1}','{2}')";
sqlInsert = string.Format(sqlInsert, ip,"0",smsSendTime);
SqlCommand sqlcmdInsert = new SqlCommand(sqlInsert, pSqlConnection);
sqlcmdInsert.ExecuteNonQuery();
//--------------傳送驗證碼----------------//
string SMSResult = SMSVeriCodeClass.SendSMSVeriCode(UidName, KeyMD5, phone, smsTextContent);//傳送
if (Convert.ToInt32(SMSResult) > 0)//成功傳送SMS驗證碼
{
//倒計時開始
timer1.Interval = 1000;
timer1.Start();
timer2.Interval = 1000;
timer2.Start();
btnSendVeriCode.Enabled = false;
UpdateImageVeriCode();
txtImageVeriCode.Text = "";
SMSVeriCodeClass.UpdateSendTimes(ip);//更新發送次數+1
}
else//傳送SMS驗證碼失敗
{
string JudetResult = SMSVeriCodeClass.GetResult(SMSResult);
MessageBox.Show(JudetResult, "提示", MessageBoxButtons.OK, MessageBoxIcon.Information);
}
}
}
}
/// <summary>
/// 倒計時—SMS驗證碼1分鐘只能點擊發送1次
/// </summary>
private void timer1_Tick(object sender, EventArgs e)
{
if (seconds1 > 0)
{
seconds1--;
btnSendVeriCode.Text = "剩餘" + seconds1.ToString() + "秒";
}
else
{
timer1.Stop();
btnSendVeriCode.Text = "獲取驗證碼";
btnSendVeriCode.Enabled = true;
}
}
/// <summary>
/// 手機SMS驗證碼5分鐘內有效;但是如果有新的驗證碼出現,舊驗證碼就會失效
/// </summary>
private void timer2_Tick(object sender, EventArgs e)
{
if (seconds2 == 0)
{
timer2.Stop();
//舊的驗證碼過期,生成一個新的驗證碼
strSMSVeriCode = SMSVeriCodeClass.GetSMSValidCode(100000, 999999);
}
}
/// <summary>
/// 確認按鈕
/// </summary>
private void btnConfirm_Click(object sender, EventArgs e)
{
//獲取文字資料
string smsvericode = txtSMSVeriCode.Text.Trim();//手機SMS驗證碼
if (String.IsNullOrEmpty(smsvericode))
{
MessageBox.Show("請輸入手機驗證碼!", "提示", MessageBoxButtons.OK, MessageBoxIcon.Information);
txtSMSVeriCode.Focus();
}
else if (smsvericode != strSMSVeriCode)//判斷手機SMS驗證碼是否輸入正確
{
MessageBox.Show("您輸入的驗證碼有誤!", "警告", MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
txtSMSVeriCode.Focus();
}
else
{
MessageBox.Show("驗證成功!");
}
}
}
}