zoukankan      html  css  js  c++  java
  • 微信公众号统一管理平台之微信粉丝定时同步任务

    在开发一套微信公众号统一管理平台,因需要微信粉丝同步功能,结合网上看到的各种方法以及微信公众号提供的接口,使用FluentScheduler定时框架在Asp.Net MVC中实现了多公众号的粉丝定时同步到本地库的功能,实现步骤分享:

    一、本地数据库的创建,因设计多公众号的管理,分析微信接口中的用户实体信息,创建表如下:

    表结构截图

    /*
    Navicat MySQL Data Transfer
    
    Source Server         : 127.0.0.1
    Source Server Version : 50627
    Source Host           : 127.0.0.1:3306
    Source Database       : fensishenghuo
    
    Target Server Type    : MYSQL
    Target Server Version : 50627
    File Encoding         : 65001
    
    Date: 2018-04-20 11:16:26
    */
    
    SET FOREIGN_KEY_CHECKS=0;
    
    -- ----------------------------
    -- Table structure for `wechat_user`
    -- ----------------------------
    DROP TABLE IF EXISTS `wechat_user`;
    CREATE TABLE `wechat_user` (
      `id` varchar(40) NOT NULL DEFAULT '' COMMENT '主键',
      `subscribe` smallint(1) DEFAULT NULL COMMENT '用户是否订阅该公众号标识,值为0时,代表此用户没有关注该公众号,拉取不到其余信息',
      `openid` varchar(40) DEFAULT NULL COMMENT '用户的标识,对当前公众号唯一',
      `nickname` varchar(30) DEFAULT '' COMMENT '用户的昵称',
      `sex` smallint(1) DEFAULT NULL COMMENT '用户的性别,值为1时是男性,值为2时是女性,值为0时是未知',
      `language` varchar(20) DEFAULT NULL COMMENT '用户的语言,简体中文为zh_CN',
      `city` varchar(50) DEFAULT NULL COMMENT '用户所在城市',
      `province` varchar(50) DEFAULT NULL COMMENT '用户所在省份',
      `country` varchar(50) DEFAULT NULL COMMENT '用户所在国家',
      `head_img_url` varchar(200) DEFAULT NULL COMMENT '用户头像,最后一个数值代表正方形头像大小(有0、46、64、96、132数值可选,0代表640*640正方形头像),用户没有头像时该项为空。若用户更换头像,原有头像URL将失效。',
      `subscribe_time` bigint(20) DEFAULT NULL COMMENT '用户关注时间,为时间戳。如果用户曾多次关注,则取最后关注时间',
      `union_id` varchar(40) DEFAULT NULL COMMENT '只有在用户将公众号绑定到微信开放平台帐号后,才会出现该字段',
      `remark` varchar(200) DEFAULT NULL COMMENT '公众号运营者对粉丝的备注,公众号运营者可在微信公众平台用户管理界面对粉丝添加备注',
      `group_id` int(6) DEFAULT NULL COMMENT '用户所在的分组ID(兼容旧的用户分组接口)',
      `tagid_list` varchar(20) DEFAULT NULL COMMENT '用户标签',
      `wechat_account_id` varchar(40) DEFAULT NULL COMMENT '该粉丝是属于哪一个公众号下的粉丝',
      `company_id` varchar(40) DEFAULT NULL COMMENT '公司主键,该微信用户属于哪一个网点的粉丝',
      `company_name` varchar(50) DEFAULT NULL COMMENT '公司名称,该微信用户属于哪一个网点的粉丝',
      `user_id` varchar(255) DEFAULT NULL COMMENT '员工主键,该微信用户属于哪一个员工的粉丝',
      `user_name` varchar(50) DEFAULT NULL COMMENT '员工用户名称,该微信用户属于哪一个用户的粉丝',
      `modified_on` date DEFAULT NULL COMMENT '更新时间',
      `modified_user_id` varchar(40) DEFAULT NULL COMMENT '修改人主键',
      `modified_by` varchar(40) DEFAULT NULL COMMENT '修改人',
      PRIMARY KEY (`id`),
      UNIQUE KEY `idx_wechat_account_id_openid` (`wechat_account_id`,`openid`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='微信公众号关注的用户信息';
    
    -- ----------------------------
    -- Records of wechat_user
    -- ----------------------------

    MySQL数据库粉丝表的创建语句,因nickname存在表情字符,表结构需要做一下调整,将nickname的字符集改为utf8mb4_unicode_ci,当然可以将全部字符集改为utf8mb4_unicode_ci

    CREATE TABLE `wechat_user` (
        `id` VARCHAR(40) NOT NULL DEFAULT '' COMMENT '主键',
        `subscribe` SMALLINT(1) NULL DEFAULT NULL COMMENT '用户是否订阅该公众号标识,值为0时,代表此用户没有关注该公众号,拉取不到其余信息',
        `openid` VARCHAR(40) NULL DEFAULT NULL COMMENT '用户的标识,对当前公众号唯一',
        `nickname` VARCHAR(30) NULL DEFAULT '' COMMENT '用户的昵称' COLLATE 'utf8mb4_unicode_ci',
        `sex` SMALLINT(1) NULL DEFAULT NULL COMMENT '用户的性别,值为1时是男性,值为2时是女性,值为0时是未知',
        `language` VARCHAR(20) NULL DEFAULT NULL COMMENT '用户的语言,简体中文为zh_CN',
        `city` VARCHAR(50) NULL DEFAULT NULL COMMENT '用户所在城市',
        `province` VARCHAR(50) NULL DEFAULT NULL COMMENT '用户所在省份',
        `country` VARCHAR(50) NULL DEFAULT NULL COMMENT '用户所在国家',
        `head_img_url` VARCHAR(200) NULL DEFAULT NULL COMMENT '用户头像,最后一个数值代表正方形头像大小(有0、46、64、96、132数值可选,0代表640*640正方形头像),用户没有头像时该项为空。若用户更换头像,原有头像URL将失效。',
        `subscribe_time` BIGINT(20) NULL DEFAULT NULL COMMENT '用户关注时间,为时间戳。如果用户曾多次关注,则取最后关注时间',
        `union_id` VARCHAR(40) NULL DEFAULT NULL COMMENT '只有在用户将公众号绑定到微信开放平台帐号后,才会出现该字段',
        `remark` VARCHAR(200) NULL DEFAULT NULL COMMENT '公众号运营者对粉丝的备注,公众号运营者可在微信公众平台用户管理界面对粉丝添加备注',
        `group_id` INT(6) NULL DEFAULT NULL COMMENT '用户所在的分组ID(兼容旧的用户分组接口)',
        `tagid_list` VARCHAR(20) NULL DEFAULT NULL COMMENT '用户标签',
        `wechat_account_id` VARCHAR(40) NULL DEFAULT NULL COMMENT '该粉丝是属于哪一个公众号下的粉丝',
        `company_id` VARCHAR(40) NULL DEFAULT NULL COMMENT '公司主键,该微信用户属于哪一个网点的粉丝',
        `company_name` VARCHAR(50) NULL DEFAULT NULL COMMENT '公司名称,该微信用户属于哪一个网点的粉丝',
        `user_id` VARCHAR(255) NULL DEFAULT NULL COMMENT '员工主键,该微信用户属于哪一个员工的粉丝',
        `user_name` VARCHAR(50) NULL DEFAULT NULL COMMENT '员工用户名称,该微信用户属于哪一个用户的粉丝',
        `modified_on` DATE NULL DEFAULT NULL COMMENT '更新时间',
        `modified_user_id` VARCHAR(40) NULL DEFAULT NULL COMMENT '修改人主键',
        `modified_by` VARCHAR(40) NULL DEFAULT NULL COMMENT '修改人',
        PRIMARY KEY (`id`),
        UNIQUE INDEX `idx_wechat_account_id_openid` (`wechat_account_id`, `openid`)
    )
    COMMENT='微信公众号关注的用户信息'
    COLLATE='utf8_general_ci'
    ENGINE=InnoDB
    ;

    对应mysql.data.dll驱动下载:https://files.cnblogs.com/files/hnsongbiao/MySql.Data.zip

    数据库连接字符串:

     <add key="UserCenterDbConnection" value="host=127.0.0.1;uid=root;password=123456;database=shenghuo;pooling=true;charset=utf8mb4;Min Pool Size=5;Max Pool Size=50;" />

    二、定时任务计划实现,微信接口的调用使用了Senparc的SDK。

    //-----------------------------------------------------------------------
    // <copyright file="WechatUserScheduler" company="FenSiShengHuo, Ltd.">
    //     Copyright (c) 2018 , All rights reserved.
    // </copyright>
    //-----------------------------------------------------------------------
    
    using System;
    using System.Collections.Generic;
    using FluentScheduler;
    
    namespace DotNet.WeChat.MVC.Assist
    {
        using DotNet.MVC.Infrastructure.Utilities;
        using DotNet.WeChat.Business;
        using DotNet.WeChat.Model;
        using Senparc.Weixin.MP.AdvancedAPIs;
        using Senparc.Weixin.MP.AdvancedAPIs.User;
        using Utilities;
    
        /// <summary>
        /// WechatUserScheduler
        /// 
        /// 微信粉丝定时同步任务
        /// 
        /// 修改纪录
        /// 
        /// 2018-04-18 版本:1.0 SongBiao 创建文件。     
        /// 
        /// <author>
        ///     <name>SongBiao</name>
        ///     <date>2018-04-18</date>
        /// </author>
        /// </summary>
        public class WechatUserScheduler
        {
            static Registry registry;
            /// <summary>
            /// 静态构造函数
            /// </summary>
            static WechatUserScheduler()
            {
                registry = new Registry();
            }
            static List<string> jobList;
            /// <summary>
            /// 终止任务
            /// </summary>
            public static void RemoveJob()
            {
                try
                {
                    foreach (var jobName in jobList)
                    {
                        JobManager.RemoveJob(jobName);
                    }
                }
                catch (Exception ex)
                {
                    NLogHelper.Warn(ex, "Scheduler.RemoveJob");
                }
            }
    
            /// <summary>
            /// 初始化 启动任务
            /// </summary>
            public static void Initialize()
            {
                jobList = new List<string>();
                try
                {
                    WechatAccountSettingManager manager = new WechatAccountSettingManager();
                    List<WechatAccountSettingEntity> list = manager.GetList<WechatAccountSettingEntity>(new KeyValuePair<string, object>(WechatAccountSettingEntity.FieldEnabled, 1), new KeyValuePair<string, object>(WechatAccountSettingEntity.FieldDeletionStateCode, 0));
                    string jobName = string.Empty;
                    foreach (var model in list)
                    {
                        jobName = "SyncUser_" + model.Id;
                        jobList.Add(jobName);
                        // WithName 给这个定时任务唯一ID,这个任务ID是用于显示控制任务,后面终止任务会用到 立即执行,而且每间隔10秒(Seconds)/10分钟执行一遍
                        registry.Schedule(() => SynchroWechatUser(model)).WithName(jobName).ToRunNow().AndEvery(10).Minutes();
                    }
                    JobManager.Initialize(registry);
                }
                catch (Exception ex)
                {
                    NLogHelper.Warn(ex, "Scheduler.Initialize");
                }
            }
    
            /// <summary>
            /// 设置实体
            /// </summary>
            /// <param name="wechatUser"></param>
            /// <param name="userInfo"></param>
            /// <param name="wechatAccountSetting"></param>
            private static void SetModel(WechatUserEntity wechatUser, UserInfoJson userInfo, WechatAccountSettingEntity wechatAccountSetting)
            {
                wechatUser.Subscribe = userInfo.subscribe;
                wechatUser.Openid = userInfo.openid;
                wechatUser.Nickname = userInfo.nickname;
                wechatUser.Sex = userInfo.sex;
                wechatUser.Language = userInfo.language;
                wechatUser.City = userInfo.city;
                wechatUser.Province = userInfo.province;
                wechatUser.Country = userInfo.country;
                wechatUser.HeadImgUrl = userInfo.headimgurl;
                wechatUser.SubscribeTime = userInfo.subscribe_time;
                wechatUser.UnionId = userInfo.unionid;
                wechatUser.Remark = userInfo.remark;
                wechatUser.GroupId = userInfo.groupid;
                wechatUser.TagidList = string.Join(",", userInfo.tagid_list);
                // 粉丝属于哪一个公众号的
                wechatUser.WechatAccountId = wechatAccountSetting.Id;
                wechatUser.CompanyId = wechatAccountSetting.CompanyId;
                wechatUser.CompanyName = wechatAccountSetting.Company;
            }
    
            /// <summary>
            /// 同步微信用户数据,按公众号各自同步粉丝
            /// 每次同步完毕记录最后一次同步的用户的openId,每隔一段时间继续自动同步
            /// </summary>
            /// <param name="wechatAccountSetting"></param>
            private static void SynchroWechatUser(WechatAccountSettingEntity wechatAccountSetting)
            {
                try
                {
                    var accessToken = Senparc.Weixin.MP.Containers.AccessTokenContainer.TryGetAccessToken(wechatAccountSetting.AppId, wechatAccountSetting.AppSecret);
                    OpenIdResultJson openIdResultJson;
                    OpenIdResultJson_Data openIdResultJsonData;
                    UserInfoJson userInfo;
                    WechatUserManager wechatUserManager = new WechatUserManager();
                    WechatUserEntity wechatUserEntity = null;
                    string tempNextOpenId = wechatAccountSetting.NextOpenid;
                    do
                    {
                        openIdResultJson = UserApi.Get(accessToken, tempNextOpenId);
                        openIdResultJsonData = openIdResultJson.data;
                        foreach (var openId in openIdResultJsonData.openid)
                        {
                            // 根据openId逐个拉取用户
                            userInfo = UserApi.Info(accessToken, openId);
                            // 判断该公众号的openId的粉丝是否存在
                            wechatUserEntity = wechatUserManager.GetObjectByWechatAccountIdByOpenId(wechatAccountSetting.Id, openId);
                            if (wechatUserEntity == null)
                            {
                                wechatUserEntity = new WechatUserEntity();
                                SetModel(wechatUserEntity, userInfo, wechatAccountSetting);
                                wechatUserManager.Add(wechatUserEntity, false, false);
                            }
                            else
                            {
                                SetModel(wechatUserEntity, userInfo, wechatAccountSetting);
                                wechatUserManager.Update(wechatUserEntity);
                            }
                            tempNextOpenId = openId;
                        }
                        // 执行完毕再赋值稳妥一些 上面执行过程失败 下次会继续
                        wechatAccountSetting.NextOpenid = tempNextOpenId;
                    }
                    while (openIdResultJson.count == 10000);
                }
                catch (Exception ex)
                {
                    NLogHelper.Warn(ex, "Scheduler.SynchroWechatUser,model=" + Serializer.ToJson(wechatAccountSetting));
                }
                finally
                {
                    WechatAccountSettingManager manager = new WechatAccountSettingManager();
                    manager.Update(wechatAccountSetting);
                }
            }
        }
    }

    三,定时任务方法的调用,直接集成到Asp.Net MVC 的Global事件里,当然也可以作为Window服务。

    //-----------------------------------------------------------------------
    // <copyright file="MvcApplication.cs" company="FenSiShengHuo, Ltd.">
    //     Copyright (c) 2018 , All rights reserved.
    // </copyright>
    //-----------------------------------------------------------------------
    
    using System;
    using System.Web;
    using System.Web.Mvc;
    using System.Web.Optimization;
    using System.Web.Routing;
    
    namespace DotNet.WeChat.MVC
    {
        using DotNet.Utilities;
        using DotNet.WeChat.MVC.Assist;
    
        /// <summary>
        ///  MvcApplication
        ///  应用全局事件   
        /// 
        /// 修改记录
        ///
        ///        2018-04-20 版本:1.0 SongBiao 创建文件。
        ///
        /// <author>
        ///     <name>SongBiao</name>
        ///     <date>2018-04-20</date>
        /// </author>
        /// </summary>
        public class MvcApplication : System.Web.HttpApplication
        {
            /// <summary>
            /// 应用启动
            /// </summary>
            protected void Application_Start()
            {
                AreaRegistration.RegisterAllAreas();
                FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
                RouteConfig.RegisterRoutes(RouteTable.Routes);
                BundleConfig.RegisterBundles(BundleTable.Bundles);
                // 读取配置
                ConfigurationHelper.GetConfig();
                // 启动微信粉丝定时同步任务
                WechatUserScheduler.Initialize();
            }
    
            /// <summary>
            /// 修改 标头报文 Server
            /// </summary>
            /// <param name="sender"></param>
            /// <param name="e"></param>
            protected void Application_PreSendRequestHeaders(object sender, EventArgs e)
            {
                HttpApplication app = sender as HttpApplication;
                if (app != null &&
                    app.Context != null)
                {
                    app.Context.Response.Headers.Remove("Server");
                }
            }
    
            /// <summary>
            /// 应用程序结束
            /// </summary>
            /// <param name="sender"></param>
            /// <param name="e"></param>
            protected void Application_End(object sender, EventArgs e)
            {
                // 停止微信粉丝定时同步任务
                WechatUserScheduler.RemoveJob();
            }
        }
    }

     

    页面展示效果如上

    欢迎大家参考,指点~~~

  • 相关阅读:
    XMPP资源绑定(Resource Binding)与单台设备登录控制
    解决Eclipse建立Maven项目后无src/main/java资源文件夹的办法
    quartz做集群配置较短的时间间隔会重复执行任务的问题
    同一服务器部署多个tomcat时的端口号修改详情
    Java RSA加密算法生成公钥和私钥
    CSS3动画(重要)
    CSS3 过渡
    CSS3 3D 转换
    CSS3 2D 转换
    CSS3 文本效果(阴影)
  • 原文地址:https://www.cnblogs.com/hnsongbiao/p/8889278.html
Copyright © 2011-2022 走看看