ASP.NET MVC5+EF6+EasyUI 后台管理系统(71)-微信公众平台开发-公众号管理

思维导图

下面我们来看一个思维导图,这样就可以更快了解所需要的功能:

上一节我们利用了一个简单的代码例子,完成了与微信公众号的对话(给公众号发一条信息,并得到回复) 这一节将讲解公众号如何设置,虽然公众号管理只是一张表,但是设计起来还是有一些技巧 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[\s\S]+</script *>", System.Text.RegularExpressions.RegexOptions.IgnoreCase);
            System.Text.RegularExpressions.Regex regex2 = new System.Text.RegularExpressions.Regex(@" href *= *[\s\S]*script *:", System.Text.RegularExpressions.RegexOptions.IgnoreCase);
            System.Text.RegularExpressions.Regex regex3 = new System.Text.RegularExpressions.Regex(@" on[\s\S]*=", System.Text.RegularExpressions.RegexOptions.IgnoreCase);
            System.Text.RegularExpressions.Regex regex4 = new System.Text.RegularExpressions.Regex(@"<iframe[\s\S]+</iframe *>", System.Text.RegularExpressions.RegexOptions.IgnoreCase);
            System.Text.RegularExpressions.Regex regex5 = new System.Text.RegularExpressions.Regex(@"<frameset[\s\S]+</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("&nbsp;", " ");
            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, "成批更新成功"));
 }

总结

谢谢大家

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏数据和云

当SQL Server爱上Linux:配置 SQL Server 2017 上的可用性组初体验

作者 | 张乐奕:Oracle ACE 总监,ACOUG (中国 Oracle 用户组)联合发起人。Oracle 数据库高可用解决方案与 Exadata 一体...

16640
来自专栏乐沙弥的世界

Oracle 表空间与数据文件

SYSAUX --->10g 高并发系统繁忙时,会造成system争用,将工具放到SYSAUX,减轻system的压力,SYSAUX不影响系统(影响性能)

19150
来自专栏数据库新发现

关于checkpoint cnt和checkpoint scn

SQL> alter session set events 'immediate trace name CONTROLF level 10';

15620
来自专栏杨建荣的学习笔记

一个SQL性能问题的优化探索(一)(r11笔记第33天)

今天同事问我一个问题,看起来比较常规,但是仔细分析了一圈,发现实在是有些晕,我隐隐感觉这是一个bug,但是有感觉问题还有很多需要确认和理解的细节。 同事...

35790
来自专栏杨建荣的学习笔记

迁移式升级的测试(三)(r10笔记第36天)

还是继续昨天的任务。 前面的内容可以参见:迁移式升级的一点思考 (r10笔记第27天)、迁移式升级的新方案测试 (r10笔记第30天)、迁移式升级的测试(二)(...

36250
来自专栏编程直播室

删除Maven本地仓库的部分目录

14840
来自专栏GreenLeaves

SQL学习之Insert的特殊用法(插入检索出的数据,表之间的数据复制)

1、插入检索出的数据 select * from dbo.Customers_1 ? 现在有个需求,需要将这张Customers_1表的数据合并到Custome...

21180
来自专栏idba

insert 语句加锁机制

之前的文章里面总结了很多死锁案例,其实里面有几篇文章对于insert加锁流程表述的不准确,而且微信公众号又无法修改,所以通过本文重新梳理insert...

14430
来自专栏杨建荣的学习笔记

system表空间不足的问题分析(二) (r8笔记第5天)

今天收到一条不太起眼的报警邮件,大体内容是某个表空间的空间有些紧张了。大体内容如下: Tablesapce: CMBI_SNZG_DATA: 92.2% [W...

35270
来自专栏hbbliyong

SQLite事务 SQLite插入多条语句为什么这么慢?.net (C#)

今天有个朋友测试 SQLite,然后得出的结论是: SQLite 效率太低,批量插入1000条记录,居然耗时 2 分钟! 下面是他发给我的测试代码。我晕~~~~...

43070

扫码关注云+社区

领取腾讯云代金券