某公司现有短信平台处理程序已经成熟运行,发送短信是在数据库表中插入一条记录表示要发送的短信。接收短信也是在数据库表中,公司内部系统非常多,有OA、客户、物流揽收、质量考核。。。。部分系统已经有了短信接收处理的需求并实现,但基本上是每个应用程序均是独立开发一个WINDOWS应用程序,自动定时运行去检测数据库中是否有接收到的短信,并进行处理,这样的方式耗费了大量的系统资源和数据库服务器资源。而且随着短信处理的需求增加此类应用程序的维护量也变得不可控。
基于此需求完全有必要开发统一的短信处理服务,减少短信处理需求的开发量并节约系统资源,降低维护成本。
二、短信服务设计
短信服务为一个WINDOWS服务,通过注册实现各个短信频道的处理类来实现各个短信频道的自动处理。新增频道时只需开发一个实现短信接口的短信处理类,并注册即可。短信服务可以应用于所有公司应用程序短信自动处理需求。
结构如下:
设计说明:
Ø 短信服务为WINDOWS应用程序,实现加载具体短信处理类、系统日志及检索是否有新短信,并通知新短信到来的处理程序,检索新短信由SMSDP的GetReceiveSMS方法实现
Ø SMSDP为短信处理程序,直接连接短信数据库中tb_smsrecev表,检索是否有已经注册频道的新短信,有新短信时触发新短信处理事件 smsNewSMSEvent
Ø 短信处理事件判断频道并产生对应的频道的线程代理类并执行处理线程
Ø 具体短信处理模块执行具体频道的短信处理,并可以扩展频道
三、短信服务开发接口说明
短信处理由各个应用系统自行开发系统短信所涉及的频道的新短信,处理类需要实现INewMessageDeal接口,并注册在短信服务程序的配置文件RoutDataSMSService.exe.config中即可
1、配置文件范例
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<configSections>
<section name="Channels" type="System.Configuration.DictionarySectionHandler"/>
</configSections>
<Channels>
<add key="185" value="RoutData.Test.SMS185.NewMessageDeal185,RoutData.Test.SMS185" />
<add key="998" value="RoutData.Test.SMS185.NewMessageDeal998,RoutData.Test.SMS185" />
<!--<add key="998" value="RoutData.Rise.SqlDP" /> -->
</Channels>
<appSettings>
<add key="ConnectionString" value="Provider=MSDAORA.1;Data Source=tmh.itb.web;user id=tmhuser;password=tmhtdxt" />
<add key="ConnectionString998" value="server=10.194.129.225;user id=EpUser;password=123;database=Epoweroa1;max pool size=100" />
<add key="SMS998Help" value="回复短信格式说明:998#操作类别#内容编号#操作参数,操作类别1表示确认接收2表示交接9表示帮助,当操作类别为2时操作参数为交接人的登录帐户,其它操作类别不需要带操作参数。谢谢" />
<add key="MaxCount" value="10" />
<add key="TimerInterval" value="5000" />
</appSettings>
</configuration>
新增处理频道时在 Channels节中添加频道配置信息
Key :频道号
Value:类名称和程序集名称.
其它所需的配置信息添加到 appSetting 节中
并可以在应用程序中通过
System.Configuration.ConfigurationSettings.AppSettings[key]; 获取
2、INewMessageDeal 接口说明
public virtual void NewMessageDeal(SMSDP sms,long lngID,string sSrcNO,string sChannel,string sBody)
{
}
参数说明:
n Sms:SMSDP类的实例,通过此类实例实现短信的回复功能等
n lngID:对应的短信编号,long 型
n sSrcNo:发送短信者的移动电话号码,string 类型
n sChannel: 短信频道号,string 类型
n sBody: 短信内容,string 类型
四、源代码及范例
4.1 windows服务主程序
主要功能: 加载短信处理具体实现类及服务调度程序等
public class Service1 : System.ServiceProcess.ServiceBase
{
private System.Data.OleDb.OleDbConnection ocn;
private System.ComponentModel.IContainer components;
System.Timers.Timer timer1;
private static SMSDP sms;
private static long lngMaxID = 0;
private static int lngMaxCount = 10;
private static INewMessageDeal[] inmd;
string[] ChannelKeys;
string[] ChannelValues;
private System.Diagnostics.EventLog myLog;
private static string strDateTime = "2006-08-15 06:00";
public Service1()
{
// 该调用是 Windows.Forms 组件设计器所必需的。
InitializeComponent();
if (!System.Diagnostics.EventLog.SourceExists("MySource"))
{
System.Diagnostics.EventLog.CreateEventSource(
"MySource","MyNewLog");
}
myLog.Source = "MySource";
myLog.Log = "MyNewLog";
}
// 进程的主入口点
static void Main()
{
System.ServiceProcess.ServiceBase[] ServicesToRun;
// 同一进程中可以运行多个用户服务。若要将
//另一个服务添加到此进程,请更改下行
// 以创建另一个服务对象。例如,
//
// ServicesToRun = New System.ServiceProcess.ServiceBase[] {new Service1(), new MySecondUserService()};
//
ServicesToRun = new System.ServiceProcess.ServiceBase[] { new Service1() };
System.ServiceProcess.ServiceBase.Run(ServicesToRun);
}
/**//// <summary>
/// 设计器支持所需的方法 - 不要使用代码编辑器
/// 修改此方法的内容。
/// </summary>
private void InitializeComponent()
{
this.timer1 = new System.Timers.Timer();
this.ocn = new System.Data.OleDb.OleDbConnection();
this.myLog = new System.Diagnostics.EventLog();
((System.ComponentModel.ISupportInitialize)(this.timer1)).BeginInit();
((System.ComponentModel.ISupportInitialize)(this.myLog)).BeginInit();
//
// timer1
//
this.timer1.Elapsed += new System.Timers.ElapsedEventHandler(this.timer1_Elapsed);
//
// Service1
//
this.ServiceName = "RoutDataSMSService";
((System.ComponentModel.ISupportInitialize)(this.timer1)).EndInit();
((System.ComponentModel.ISupportInitialize)(this.myLog)).EndInit();
}
/**//// <summary>
/// 清理所有正在使用的资源。
/// </summary>
protected override void Dispose( bool disposing )
{
if( disposing )
{
if (components != null)
{
components.Dispose();
}
}
base.Dispose( disposing );
}
/**//// <summary>
/// 设置具体的操作,以便服务可以执行它的工作。
/// </summary>
protected override void OnStart(string[] args)
{
string strConnectionString = System.Configuration.ConfigurationSettings.AppSettings["ConnectionString"];
lngMaxCount = int.Parse(System.Configuration.ConfigurationSettings.AppSettings["MaxCount"]);
int lngTimerInterval = int.Parse(System.Configuration.ConfigurationSettings.AppSettings["TimerInterval"]);
//获取频道相关资料
IDictionary IDChannels = (IDictionary)System.Configuration.ConfigurationSettings.GetConfig("Channels");
ChannelKeys=new string[IDChannels.Keys.Count];
ChannelValues=new string[IDChannels.Keys.Count];
IDChannels.Keys.CopyTo(ChannelKeys,0);
IDChannels.Values.CopyTo(ChannelValues,0);
inmd = new INewMessageDeal[IDChannels.Count];
for(int i=0;i<IDChannels.Count;i++)
{
string strAssemblyName;
string[] strAssembly;
string strPath;
strPath = Assembly.GetExecutingAssembly().Location;
strAssembly = ChannelValues[i].Split(",".ToCharArray());
strAssemblyName = strPath.Substring(0,strPath.LastIndexOf(@"/")+1) + strAssembly[1] + ".dll";
string strTypeName = strAssembly[0];
//object newInstance=Assembly.GetExecutingAssembly().CreateInstance(ChannelValues[i]);
//Activator.CreateInstance();
Assembly asmSample = Assembly.LoadFrom(strAssemblyName);
Type typSample = asmSample.GetType(strTypeName);
object newInstance=Activator.CreateInstance(typSample);
//object newInstance=Activator.CreateInstance(@"E:/Project/DoNet/RoutDataSMSService/RoutDataSMSService/bin/Debug/RoutData.Test.SMS185.dll","RoutData.Test.SMS185.NewMessageDeal185");
//object newInstance=Activator.CreateInstance(strAssemblyName,strTypeName);
//newInstance = new RoutData.Test.SMS185.NewMessageDeal185();
inmd[i] = (INewMessageDeal)newInstance;
myLog.WriteEntry("成功加载程序集" + strAssemblyName + " " + ChannelKeys[i]);
}
ocn.ConnectionString = strConnectionString;
if (ocn.State == ConnectionState.Closed)
ocn.Open();
sms = new SMSDP(ocn);
sms.myNewSMSEvent +=new RoutDataSMSService.BaseDP.SMSDP.NewSMSHandler(sms_myNewSMSEvent);
this.timer1.Enabled = true;
if(lngTimerInterval > 1000)
{
this.timer1.Interval = lngTimerInterval;
}
else
{
this.timer1.Interval = 5000;
}
}
/**//// <summary>
/// 停止此服务。
/// </summary>
protected override void OnStop()
{
// TODO: 在此处添加代码以执行停止服务所需的关闭操作。
this.timer1.Enabled = false;
if (ocn.State == ConnectionState.Closed)
ocn.Open();
sms = null;
//inmd = null;
}
private void timer1_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
{
timer1.Enabled = false;
try
{
sms.GetReceiveSMS(lngMaxID,strDateTime,lngMaxCount,ChannelKeys);
//myLog.WriteEntry("获取记录成功" + lngMaxID.ToString());
System.GC.Collect();
}
catch
{
}
timer1.Enabled = true;
}
private void sms_myNewSMSEvent(DataTable dt)
{
long lngID = 0;
string strSrcNo = "";
string strChannel = "";
string strBody = "";
foreach(DataRow row in dt.Rows)
{
lngID = long.Parse(row["autoid"].ToString());
strSrcNo = row["srcno"].ToString();
strChannel = row["channel"].ToString().Trim();
strBody = row["body"].ToString();
lngMaxID = lngID;
//myLog.WriteEntry("开始处理" + strSrcNo + "频道号" + strChannel);
for(int j=0;j<inmd.Length;j++)
{
if(strChannel.ToUpper() == ChannelKeys[j].ToUpper())
{
//更新状态
sms.UpdateSMSDealState(lngID);
INewMessageDeal obj = (INewMessageDeal)inmd[j];
if (obj != null)
{
try
{
using(HandleAgent ha = new HandleAgent(sms,obj,lngID,strSrcNo,strChannel,strBody))
{
Thread thread1 = new Thread(new ThreadStart(ha.DoAction));
thread1.Start();
}
}
catch
{
//错误暂时不处理
}
}
}
}
}
dt.Dispose();
}
4.2 短信处理类
主要作用: 检测是否有新的短信息 ,有则调用对应的具体实现类进行短信处理
public class SMSDP
{
private static OleDbConnection ocn;
public delegate void NewSMSHandler(DataTable dt);
public event NewSMSHandler myNewSMSEvent;
static string strSQL="";
static string strSQL1="";
public SMSDP(OleDbConnection cn)
{
ocn = cn;
}
private void ConfigConnect()
{
if(ocn.State == ConnectionState.Closed)
ocn.Open();
}
/**//// <summary>
/// 检测新短信
/// </summary>
/// <param name="lngAutoID">已经处理过的最大系列号,为0时表示重新部署了,取一个时间之后</param>
/// <param name="strDateTime">当前时间 yyyy-mm-dd HH24:MI</param>
/// <param name="MaxCount">最大处理数</param>
public void GetReceiveSMS(long lngAutoID,string strDateTime,int MaxCount,string[] strKeys)
{
if(strKeys.Length == 0)
{
return;
}
ConfigConnect();
string strChannels = "";
for(int i=0;i<strKeys.Length;i++)
{
strChannels += StringTool.SqlQ(strKeys[i].ToLower()) + ",";
}
if(strChannels.EndsWith(","))
{
strChannels = strChannels.Substring(0,strChannels.Length-1);
}
strChannels = "(" + strChannels + ")";
if(lngAutoID == 0)
{
strSQL = "select * from tb_smsrecv where rownum <=" + MaxCount.ToString() + " and (procflag is null or procflag = 0) and tm > to_date('" + strDateTime + "','yyyy-mm-dd HH24:MI') and lower(channel) in " + strChannels + " order by autoid";
}
else
{
strSQL = "select * from tb_smsrecv where rownum <=" + MaxCount.ToString() + " and (procflag is null or procflag = 0) and autoid > " + lngAutoID.ToString() + " and lower(channel) in " + strChannels + " order by autoid";
}
DataTable dt = OleDbHelper.ExecuteDataset(ocn,CommandType.Text,strSQL).Tables[0];
if(dt.Rows.Count > 0)
{
if(myNewSMSEvent != null)
myNewSMSEvent(dt);
}
else
{
//
dt.Dispose();
}
}
/**//// <summary>
/// 更新短信接收资料的处理状态
/// PROCFLAG = 10
/// </summary>
/// <param name="lngID">autoid</param>
public void UpdateSMSDealState(long lngID)
{
ConfigConnect();
strSQL1="UPDATE tb_smsrecv SET procflag = 10 WHERE autoid = "+ lngID.ToString();
try
{
OleDbHelper.ExecuteNonQuery(ocn,CommandType.Text,strSQL1);
}
catch(Exception e)
{
//暂时忽略发短信错误
//throw e;
}
}
发送短消息#region 发送短消息
/**//// <summary>
/// 发送短消息
/// </summary>
/// <param name="ToNo"></param>
/// <param name="sBody"></param>
/// <param name="strChannel"></param>
public void SendMessage(string ToNo,string sBody,string strChannel)
{
if(sBody.Length<=70)
{
Send(ToNo,sBody,strChannel);
return;
}
else
{
string sF=sBody.Substring(0,66);
Send(ToNo,sF+"(待续)",strChannel);
string sL=sBody.Substring(66);
while(true)
{
if(sL.Length<=67)
{
Send(ToNo,"续上:"+sL,strChannel);
return;
}
else
{
sF=sL.Substring(0,67);
Send(ToNo,"续上:"+sF,strChannel);
sL=sL.Substring(67);
}
}
}
}
/**//// <summary>
/// 发送短信
/// </summary>
/// <param name="ToNo"></param>
/// <param name="sBody"></param>
private void Send(string ToNo,string sBody,string strChannel)
{
string sToNo=ToNo.TrimStart("86".ToCharArray());
string sMode="1";//1:联通 0:移动
string sSrcNo="191185";//发短信的源号码,联通191185 ,移动 0755185
int nRef=StringTool.String2Int(sToNo.Substring(0,3));
if((nRef<130 || nRef >=140) && nRef !=159)
{
//小灵通
sMode="2";
sSrcNo = "120185";
}
else
{
if(nRef>=130 && nRef<=133) //联通
{
sMode="1";
sSrcNo="191185";
}
if((nRef>=134 && nRef<=139) || nRef==159) //移动
{
sMode="0";
sSrcNo="0755185";
}
}
string sSql="Insert Into tb_smssend(msgmode,srcno,destno,body,channel)values("+
sMode+","+
StringTool.SqlQ(sSrcNo)+","+
StringTool.SqlQ(ToNo)+","+
StringTool.SqlQ(sBody)+"," + StringTool.SqlQ(strChannel) + ")";
try
{
OleDbHelper.ExecuteNonQuery(ocn,CommandType.Text,sSql);
}
catch(Exception e)
{
//暂时忽略发短信错误
//throw e;
}
finally
{
}
}
/**//// <summary>
/// 获取短信回复号码
/// </summary>
/// <param name="sToNo"></param>
/// <returns></returns>
public string GetReturnSMSDestNo(string sToNo)
{
int nRef=StringTool.String2Int(sToNo.Substring(0,3));
string sSrcNo="0755185";
if((nRef<130 || nRef >=140) && nRef !=159)
{
//小灵通
sSrcNo = "120185";
}
else
{
if(nRef>=130 && nRef<=133) //联通
{
sSrcNo="191185";
}
if((nRef>=134 && nRef<=139) || nRef==159) //移动
{
sSrcNo="0755185";
}
}
return sSrcNo;
}
#endregion
E8.Net工作流平台 提升企业战略执行力
4.3 短信处理父类,起到接口作用
{
public virtual void NewMessageDeal(SMSDP sms,long lngID,string sSrcNO,string sChannel,string sBody)
{
}
}
4.4 具体实现类范例
/**//// <summary>
/// NewMessageDeal185 的摘要说明。
/// </summary>
public class NewMessageDeal998:INewMessageDeal
{
private SqlConnection ocn;
private void ConfigConnect()
{
if(ocn.State == ConnectionState.Closed)
ocn.Open();
}
public NewMessageDeal998()
{
ocn = new SqlConnection();
ocn.ConnectionString = System.Configuration.ConfigurationSettings.AppSettings["ConnectionString998"];
}
/**//// <summary>
/// 处理大客户服务系统回复的短信
/// </summary>
/// <param name="sms"></param>
/// <param name="lngID"></param>
/// <param name="sSrcNO"></param>
/// <param name="sChannel"></param>
/// <param name="sBody"></param>
public override void NewMessageDeal(SMSDP sms,long lngID,string strSrcNO,string sChannel,string sBody)
{
// 1 获取数据 格式正确 通过 否则 直接退出
// 2 正确的短信校验合法性 ,不通过直接退出
// 2.1 验证消息状态 为待办 或没有找到均直接退出
// 2.2 如果操作类别为2(交接),判断参数的合法性,否则直接退出
// 3 处理
// 4 日志记录
//短信内容 数组下标 0 表示 频道号 1表示 消息ID及校验抹 2 表示操作类别 3 表示扩展数据 4 保留
string[] strDatas = new string[5];
string sSrcNO = "";
if(strSrcNO.StartsWith("86") == true)
{
sSrcNO = strSrcNO.Substring(2);
}
else
{
sSrcNO = strSrcNO;
}
long lngMessageID = 0;
string strValidate ="";
string strOP = "";
SqlDataReader dr;
string strSQL;
string[] strSplit;
try
{
strSplit = sBody.Split("#".ToCharArray());
for(int i = 0;i<strSplit.Length;i++)
{
strDatas[i] = strSplit[i];
}
strOP = strDatas[1].Trim();
if(strOP != "9")
{
lngMessageID = long.Parse(strDatas[2].Substring(0,strDatas[2].Length - 4));
strValidate = strDatas[2].Substring(strDatas[2].Length - 4);
}
}
catch
{
//格式有误导致错误直接退出
return;
}
if(strOP == "1" || strOP == "2" || strOP == "9")
{
}
else
{
//不属于规定的操作
return;
}
try
{
if(strOP == "9")
{
//获取帮助不校验
string strHelp =System.Configuration.ConfigurationSettings.AppSettings["SMS998Help"];
sms.SendMessage(sSrcNO,strHelp,sChannel);
}
else
{
bool blnHasData = false;
strSQL = "SELECT validate FROM Ris_SMSCheck WHERE messageid=" + lngMessageID.ToString();
dr = SqlTool.ExecuteReader(ocn,CommandType.Text,strSQL);
while(dr.Read())
{
if(strValidate == dr.GetString(0).Trim())
{
blnHasData = true;
}
break;
}
dr.Close();
if(blnHasData == false)
{
//校验不通过
return;
}
//验证消息状态 (及参数的合法性)
int iMessageStatus = 0;
string strRecName = "";
long lngReceiverID = 0;
string strSubject = "";
strSQL = "SELECT a.status,b.userid,b.name,c.subject FROM es_message a,ts_user b,es_flow c WHERE a.receiverid = b.userid and a.flowid = c.flowid and a.messageid =" + lngMessageID.ToString();
dr = SqlTool.ExecuteReader(ocn,CommandType.Text,strSQL);
while(dr.Read())
{
iMessageStatus = dr.GetInt32(0);
lngReceiverID = (long)dr.GetDecimal(1);
strRecName = dr.GetString(2);
strSubject = dr.GetString(3);
break;
}
dr.Close();
if(iMessageStatus != 20)
{
return;
}
//处理开始
//回复确认短信
//插入处理日志
if(strOP == "1")
{
strSQL = "UPDATE es_message SET isread = 1,readtime = getdate() WHERE messageid =" + lngMessageID.ToString();
SqlTool.ExecuteNonQuery(ocn,CommandType.Text,strSQL);
strSQL = "INSERT INTO ris_smsLog values(" +
lngMessageID.ToString() + "," +
lngReceiverID.ToString() + "," +
StringTool.SqlQ(strRecName) + "," +
StringTool.SqlQ(sSrcNO) + "," +
StringTool.SqlQ(sChannel) + "," +
StringTool.SqlQ(sBody) + "," +
StringTool.SqlQ("已确认接收") + ",getdate())";
SqlTool.ExecuteNonQuery(ocn,CommandType.Text,strSQL);
sms.SendMessage(sSrcNO,"编号:" + strDatas[2] +"的短信确认回复系统已接收,谢谢",sChannel);
}
if(strOP == "2")
{
//没有判断交接的人是否符合交接范围 **** 未做 ***
long lngUserID =0;
long lngDeptID =0;
long lngOrgID = 0;
string strName = "";
string strMobile="";
strSQL = "SELECT a.userid,b.deptid,c.orgid,a.name,a.mobile FROM ts_user a,ts_userdept b,ts_dept c " +
" WHERE a.userid = b.userid and b.relation = 0 and b.deptid = c.deptid " +
" AND a.deleted = 0 and a.loginname =" + StringTool.SqlQ(strDatas[3]);
dr = SqlTool.ExecuteReader(ocn,CommandType.Text,strSQL);
while(dr.Read())
{
lngUserID = (long)dr.GetDecimal(0);
lngDeptID = (long)dr.GetDecimal(1);
lngOrgID = (long)dr.GetDecimal(2);
strName = dr.GetString(3);
strMobile = dr.GetString(4);
break;
}
dr.Close();
if(lngUserID == 0)
{
//没有找到退出并发送短信回复提示
sms.SendMessage(sSrcNO,"登录帐号名:" + strDatas[3] +"无效,请检查交接人的登录帐号是否正确,谢谢",sChannel);
return;
}
strSQL = "UPDATE es_message SET receiverid = " + lngUserID.ToString() +
",recdeptid = " + lngDeptID.ToString() +
",recorgid =" + lngOrgID.ToString() +
" WHERE messageid =" + lngMessageID.ToString();
SqlTool.ExecuteNonQuery(ocn,CommandType.Text,strSQL);
strSQL = "INSERT INTO ris_smsLog values(" +
lngMessageID.ToString() + "," +
lngReceiverID.ToString() + "," +
StringTool.SqlQ(strRecName) + "," +
StringTool.SqlQ(sSrcNO) + "," +
StringTool.SqlQ(sChannel) + "," +
StringTool.SqlQ(sBody) + "," +
StringTool.SqlQ("已交接给" + strName) + ",getdate())";
SqlTool.ExecuteNonQuery(ocn,CommandType.Text,strSQL);
sms.SendMessage(sSrcNO,"编号:" + strDatas[2] +"的短信回复已经处理,并交接此事项给" + strName + ",谢谢",sChannel);
if(strMobile.Length > 0)
{
string strMessage = "您有待办事项:" + strSubject + ",编号:" + lngMessageID.ToString() +
strValidate + " 请尽快处理,并回复998#1#" + lngMessageID.ToString() + strValidate + "到" + sms.GetReturnSMSDestNo(strMobile) + "确认,获取短信帮助请回复998#9";
sms.SendMessage(strMobile,strMessage,sChannel);
}
}
}
}
catch(Exception e)
{
//暂时忽略发短信错误
//throw e;
}
}
}
我们在思考和解决企业某个应用问题时尽量多思考些对将来留下些可重用的部件,尽量有利于扩展.
以上代码已经在企业中稳定运行几年,提供大家交流和学习