系列目录
思维导图
下面我们来看一个思维导图,这样就可以更快了解所需要的功能:
上一节我们利用了一个简单的代码例子,完成了与微信公众号的对话(给公众号发一条信息,并得到回复)
这一节将讲解公众号如何设置,虽然公众号管理只是一张表,但是设计起来还是有一些技巧
1.一个企业可能底下有多个业务公众号在同一个系统中需要处理(用户发起的请求,是对应我们哪个公众号)
2.多个公众号下,后台如何取得操作比较方便(设置当前公众号为默认操作号)
3.可以手动刷新Access_Token,因为我们随时要保持Access_Token可用,这是调用微信接口的主要令牌(我们后面将讲解定时更新,而非手动)
知识点
1.表设计
2.设置为默认公众号
3.生成指定格式的URL资源服务器
4.更新Access_Token
表设计
表的设计没有太多的成分,我们根据公众号的信息,自己建立对应的字段,下面是我已经已建立好的数据表
CREATE TABLE [dbo].[WC_OfficalAccounts]( [Id] [varchar](50) NOT NULL, --主键 [OfficalId] [varchar](200) NULL, --公众号的唯一ID [OfficalName] [varchar](200) NOT NULL, --公众号名称 [OfficalCode] [varchar](200) NOT NULL, --公众号帐号 [OfficalPhoto] [varchar](1000) NULL, --头像 [OfficalKey] [varchar](500) NULL, --EncodingAESKey [ApiUrl] [varchar](1000) NULL, --我们的资源服务器 [Token] [varchar](200) NULL, --Token [AppId] [varchar](200) NULL, --AppId [AppSecret] [varchar](200) NULL, --Appsecret [AccessToken] [varchar](200) NULL, --访问Token [Remark] [varchar](2000) NULL, --说明 [Enable] [bit] NOT NULL, --是否启用 [IsDefault] [bit] NOT NULL, --是否为当前默认操作号 [Category] [int] NOT NULL, --类别(媒体号,企业号,个人号,开发测试号) [CreateTime] [datetime] NOT NULL, --创建时间 [CreateBy] [varchar](50) NOT NULL, --创建人 [ModifyTime] [datetime] NOT NULL, --修改时间 [ModifyBy] [varchar](50) NULL, --修改人 CONSTRAINT [PK_WC_OfficalAcconts] PRIMARY KEY CLUSTERED ( [Id] ASC )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY] ) ON [PRIMARY]
每个字段我都有注释,但是可能还不够明白,我们来看网站对应的数据,这样更加清楚一点
这5个字段是必要的,其他其实都是非必要的。我写了那么多,只是让后台管理能够得知当前操作号的信息情况,所以到时创建的时候,这个是对应的填写字段
程序设计
操作EF的增删该查,我在这里就不做代码演示了,已经在前面的相同功能演示过很多遍(下载尾部代码或者自己动手做起来)
我们来看主要的功能代码。
设置公众号为当前的操作号
这里我以字段IsDefault来标识,哪个公众号为系统当前的操作公众号
第一、设置所有公众号的IsDefault为Flase
第二、设置指定ID的公众号的IsDefault为True
update [dbo].[WC_OfficalAccounts] set IsDefault=0
update [dbo].[WC_OfficalAccounts] set IsDefault=1 where id='XXXXXXX'
public bool SetDefault(string id) { //更新所有为不默认0 ExecuteSqlCommand(@"update [dbo].[WC_OfficalAccounts] set IsDefault=0"); //设置当前为默认1 return ExecuteSqlCommand(@"update [dbo].[WC_OfficalAccounts] set IsDefault=1 where id='"+id+"'")>0; }
我们之前系统并未出现,这种直接执行Sql语句的,所以我写了ExecuteSqlCommand这个方法(还有其他几个,可能以后会用到),扩展在仓储中BaseRepository
/// <summary> /// 执行一条SQL语句 /// </summary> /// <param name="sql"></param> /// <returns></returns> public int ExecuteSqlCommand(string sql) { return Context.Database.ExecuteSqlCommand(sql); } /// <summary> /// 异步执行一条SQL语句 /// </summary> /// <param name="sql"></param> /// <returns></returns> public Task<int> ExecuteSqlCommandAsync(string sql) { return Context.Database.ExecuteSqlCommandAsync(sql); } public DbRawSqlQuery<T> SqlQuery(string sql) { return db.Database.SqlQuery<T>(sql); } /// <summary> /// 查询一条语句返回结果集 /// </summary> /// <param name="sql"></param> /// <returns></returns> public DbRawSqlQuery<T> SqlQuery(string sql,params object[] paras) { return db.Database.SqlQuery<T>(sql,paras); }
这里肯定有人会问,你那个可能会被注入,没错,实际应该用参数的方式,但是我在过滤器处理了SQL的注入。
那么在过滤器如果处理注入?这是扩展出另一个问题了,如果感兴趣,展开下面代码(莫非是对传入的参数进行格式处理)
var actionParameters = filterContext.ActionDescriptor.GetParameters(); foreach (var p in actionParameters) { if (p.ParameterType == typeof(string)) { if (filterContext.ActionParameters[p.ParameterName] != null) { filterContext.ActionParameters[p.ParameterName] = ResultHelper.Formatstr(filterContext.ActionParameters[p.ParameterName].ToString()); } } }
public static string Formatstr(string html) { System.Text.RegularExpressions.Regex regex1 = new System.Text.RegularExpressions.Regex(@"<script[sS]+</script *>", System.Text.RegularExpressions.RegexOptions.IgnoreCase); System.Text.RegularExpressions.Regex regex2 = new System.Text.RegularExpressions.Regex(@" href *= *[sS]*script *:", System.Text.RegularExpressions.RegexOptions.IgnoreCase); System.Text.RegularExpressions.Regex regex3 = new System.Text.RegularExpressions.Regex(@" on[sS]*=", System.Text.RegularExpressions.RegexOptions.IgnoreCase); System.Text.RegularExpressions.Regex regex4 = new System.Text.RegularExpressions.Regex(@"<iframe[sS]+</iframe *>", System.Text.RegularExpressions.RegexOptions.IgnoreCase); System.Text.RegularExpressions.Regex regex5 = new System.Text.RegularExpressions.Regex(@"<frameset[sS]+</frameset *>", System.Text.RegularExpressions.RegexOptions.IgnoreCase); System.Text.RegularExpressions.Regex regex10 = new System.Text.RegularExpressions.Regex(@"select", System.Text.RegularExpressions.RegexOptions.IgnoreCase); System.Text.RegularExpressions.Regex regex11 = new System.Text.RegularExpressions.Regex(@"update", System.Text.RegularExpressions.RegexOptions.IgnoreCase); System.Text.RegularExpressions.Regex regex12 = new System.Text.RegularExpressions.Regex(@"delete", System.Text.RegularExpressions.RegexOptions.IgnoreCase); html = regex1.Replace(html, ""); //过滤<script></script>标记 html = regex2.Replace(html, ""); //过滤href=javascript: (<A>) 属性 html = regex3.Replace(html, " _disibledevent="); //过滤其它控件的on...事件 html = regex4.Replace(html, ""); //过滤iframe html = regex10.Replace(html, "s_elect"); html = regex11.Replace(html, "u_pudate"); html = regex12.Replace(html, "d_elete"); html = html.Replace("'", "’"); html = html.Replace(" ", " "); return html; }
获得当前公众号
业务层
public WC_OfficalAccountsModel GetCurrentAccount() { WC_OfficalAccounts entity = m_Rep.GetCurrentAccount(); if (entity == null) { return new WC_OfficalAccountsModel(); } WC_OfficalAccountsModel model = new WC_OfficalAccountsModel(); model.Id = entity.Id; model.OfficalName = entity.OfficalName; model.OfficalCode = entity.OfficalCode; model.OfficalPhoto = entity.OfficalPhoto; model.ApiUrl = entity.ApiUrl; model.Token = entity.Token; model.AppId = entity.AppId; model.AppSecret = entity.AppSecret; model.AccessToken = entity.AccessToken; model.Remark = entity.Remark; model.Enable = entity.Enable; model.IsDefault = entity.IsDefault; model.Category = entity.Category; model.CreateTime = entity.CreateTime; model.CreateBy = entity.CreateBy; model.ModifyTime = entity.ModifyTime; model.ModifyBy = entity.ModifyBy; return model; }
数据访问层
public WC_OfficalAccounts GetCurrentAccount() { return Context.WC_OfficalAccounts.Where(p=>p.IsDefault).FirstOrDefault(); }
其他代码都由生成器生成,没有争议,也很简单
生成资源服务器的链接
上一节我们用的是一个地址
http://ymnets.imwork.net/WC/WcChat
这次我们这个地址要稍微改变一下,让系统知道请求者发送的请求是来自哪个公众号:
http://ymnets.imwork.net/WC/WcChat?Id=XXXXXXXX (XXXXXXX是我们系统自己定义的GUID)
这样请求的时候就知道是请求哪个ID
所以我们的Post方法必须加上判断代码,并利用这个Id去获取当前公众号的信息
[HttpPost] [ActionName("Index")] public Task<ActionResult> Post(PostModel postModel) { return Task.Factory.StartNew<ActionResult>(() => { WC_OfficalAccountsModel model = account_BLL.GetCurrentAccount(); //没有参数 if (string.IsNullOrEmpty(Request["id"])) { return new WeixinResult("非法路径请求!"); } if (!CheckSignature.Check(postModel.Signature, postModel.Timestamp, postModel.Nonce, model.Token)) { return new WeixinResult("参数错误!"); } postModel.Token = Token; postModel.EncodingAESKey = EncodingAESKey; //根据自己后台的设置保持一致 postModel.AppId = model.AppId; //根据自己后台的设置保持一致 var messageHandler = new CustomMessageHandler(Request.InputStream, postModel, Request["id"], 10); messageHandler.Execute(); //执行微信处理过程 return new FixWeixinBugWeixinResult(messageHandler); }).ContinueWith<ActionResult>(task => task.Result); }
1.判断URL是否是正确的
2.根据获得ID到数据库或者(Redis,缓存)等获得公众号信息
刷新Access_Token
由于我们的访问Token默认是2个小时过时,而且我们不能时刻去微信服务器获取
1.获取的次数是有限制。所以我们需要保存这个Token地址,在下次过期之前更新来永远保持Access_Token的有效
2.这里我保存在表里面,在获取当前操作号的时候顺便可以获得这个Access_Token
他的样子大约是这样的:tZH82Jd6JQL6dPEU1hwhM9_jl0SrLRZV3j6-DV-EOQFUl3r37OXQNq-rpmjXEV2sVj1EQdxLotLCDMz_DdVAhZHQG6vgckOW8k90_9i24jP7giAOJM669zbiqc3HYW6wQDGeAJAYLO
如何获得Token:这里我们使用Senparc.WeiXin SDK的方法,
Senparc.Weixin.MP.CommonAPIs.CommonApi.GetToken(model.AppId, model.AppSecret).access_token;
一句话,获得当前操作号之后,利用AppId和Appsecret,微信服务器将给你一个Token。所以这个两个东西是要打码的,要不很麻烦。
虽然他帮我们封装了,但是不用他的方法,我们自己也可以直接调用微信的接口方法
var url = string.Format("https://api.weixin.qq.com/cgi-bin/token?grant_type={0}&appid={1}&secret={2}", grant_type.AsUrlData(), appid.AsUrlData(), secret.AsUrlData()); AccessTokenResult result = Get.GetJson<AccessTokenResult>(url); return result;
这个接口,只有几个参数,具体参数可以查看帮助文档 传送门 成功返回:{"access_token":"ACCESS_TOKEN","expires_in":7200}
所以我这里是一个更新当前所有公众号的过程
public JsonResult GetToken() { List<WC_OfficalAccountsModel> list = m_BLL.GetList(ref setNoPagerAscById, ""); foreach (var model in list) { if (!string.IsNullOrEmpty(model.AppId) && !string.IsNullOrEmpty(model.AppSecret)) { model.AccessToken = Senparc.Weixin.MP.CommonAPIs.CommonApi.GetToken(model.AppId, model.AppSecret).access_token; model.ModifyTime = ResultHelper.NowTime; m_BLL.Edit(ref errors, model); } } return Json(JsonHandler.CreateMessage(1, "成批更新成功")); }
总结
谢谢大家