码迷,mamicode.com
首页 > 微信 > 详细

用c#开发微信 (7) 微渠道 - 推广渠道管理系统 2 业务逻辑实现

时间:2015-05-28 09:20:27      阅读:333      评论:0      收藏:0      [点我收藏+]

标签:

我们可以使用微信的“生成带参数二维码接口”和 “用户管理接口”,来实现生成能标识不同推广渠道的二维码,记录分配给不同推广渠道二维码被扫描的信息。这样就可以统计和分析不同推广渠道的推广效果。

上次介绍了《用c#开发微信 (6) 微渠道 - 推广渠道管理系统 1 基础架构搭建》,主要介绍了数据访问层的实现。本文是微渠道的第二篇,主要介绍如下内容:

1. 各个实体具体业务实现

2. 同步微信个人用户信息

 

下面是详细的实现方法:

1. 各个实体具体业务实现

1) 渠道业务逻辑
public class ChannelBll
{
    /// <summary>
    /// 获取渠道列表
    /// </summary>
    /// <returns></returns>
    public List<ChannelEntity> GetEntities()
    {
        var entities = new ChannelDal().GetByPredicate(p => p.ID > 0).ToList();
        var viewEntity = new ChannelEntity();
        return entities.Select(p => viewEntity.GetViewModel(p)).ToList();
    }
 
    /// <summary>
    /// 根据ID获取渠道
    /// </summary>
    /// <param name="id">渠道ID</param>
    /// <returns></returns>
    public ChannelEntity GetEntityById(int id)
    {
        var entity = new ChannelDal().GetSingleByPredicate(p => p.ID == id);
        var viewEntity = new ChannelEntity();
        return viewEntity.GetViewModel(entity);
    }
 
    /// <summary>
    /// 添加或修改渠道
    /// </summary>
    /// <param name="viewEntity">渠道实体</param>
    /// <returns></returns>
    public bool UpdateOrInsertEntity(ChannelEntity viewEntity)
    {
        if (viewEntity.ID > 0)
        {
            var entity = viewEntity.GetDataEntity(viewEntity);
            var dbEntity = new ChannelDal().GetSingleByPredicate(p => p.ID == entity.ID);
            entity.SceneId = dbEntity.SceneId;
            entity.Qrcode = dbEntity.Qrcode;
            return new ChannelDal().Update(entity);
        }
        else
        {
            //新增渠道时,需要获取渠道的二维码
            GetQrcode(viewEntity);
            var entity = viewEntity.GetDataEntity(viewEntity);
            return new ChannelDal().InsertAndReturn(entity).ID > 0;
        }
    }
 
    /// <summary>
    /// 根据ID删除渠道
    /// </summary>
    /// <param name="id">渠道ID</param>
    /// <returns></returns>
    public bool DeleteEntityById(int id)
    {
        //var entity = new ChannelDal().GetSingleByPredicate(p => p.ID == id);
        return new ChannelDal().Delete(c=>c.ID == id);
    }
 
    /// <summary>
    /// 根据SceneId获取二维码id
    /// </summary>
    /// <param name="sceneId">扫描的二维码的参数</param>
    /// <returns></returns>
    public int GetChannelIdBySceneId(int sceneId)
    {
        var entity = new ChannelDal().GetSingleByPredicate(p => p.SceneId == sceneId);
        return entity == null ? 0 : entity.ID;
    }
 
    /// <summary>
    /// 判断渠道名称是否存在
    /// </summary>
    /// <param name="channelName">渠道名称</param>
    /// <param name="id">渠道ID</param>
    /// <returns></returns>
    public bool IsExitChannelName(string channelName, int id)
    {
        var channelCount = new ChannelDal().GetByPredicate(c => c.Name == channelName && c.ID == id).Count();
        return channelCount > 0;
    }
 
    /// <summary>
    /// 获取渠道的二维码
    /// </summary>
    /// <param name="channelName">渠道实体</param>
    /// <returns></returns>
    private void GetQrcode(ChannelEntity entity)
    {
        //获取微信公众平台接口访问凭据
        string accessToken = AccessTokenContainer.TryGetToken(ConfigurationManager.AppSettings["appID"], ConfigurationManager.AppSettings["appsecret"]);
        //找出一个未被使用的场景值ID,确保不同渠道使用不同的场景值ID
        int scenid = GetNotUsedSmallSceneId();
        if (scenid <= 0 || scenid > 100000)
        {
            throw new Exception("抱歉,您的二维码已经用完,请删除部分后重新添加");
        }
        CreateQrCodeResult createQrCodeResult = QrCodeApi.Create(accessToken, 0, scenid);
        if (!string.IsNullOrEmpty(createQrCodeResult.ticket))
        {
            using (MemoryStream stream = new MemoryStream())
            {
                //根据ticket获取二维码
                QrCodeApi.ShowQrCode(createQrCodeResult.ticket, stream);
                //将获取到的二维码图片转换为Base64String格式
                byte[] imageBytes = stream.ToArray();
                string base64Image = System.Convert.ToBase64String(imageBytes);
                //由于SqlServerCompact数据库限制最长字符4000,本测试项目将二维码保存到磁盘,正式项目中可直接保存到数据库
                string imageFile = "QrcodeImage" + scenid.ToString() + ".img";
                File.WriteAllText(System.Web.HttpContext.Current.Server.MapPath("~/App_Data/") + imageFile, base64Image);
                entity.Qrcode = imageFile;
                entity.SceneId = scenid;
            }
        }
        else
        {
            throw new Exception("抱歉!获取二维码失败");
        }
    }
 
    /// <summary>
    /// 找出没有用的最小SceneId
    /// </summary>
    /// <returns></returns>
    private int GetNotUsedSmallSceneId()
    {
        var listSceneId = new ChannelDal().GetByPredicate(p => p.ID > 0).Select(p => p.SceneId).OrderBy(p => p);
        for (int i = 1; i <= 100000; i++)
        {
            var sceneId = listSceneId.Any(e => e == i);
            if (!sceneId)
            {
                return i;
            }
        }
        return 0;
    }
}

这里的一些增删改查就不说了,需要注意的是:

  • 新增渠道时,要确保场景值ID不重复
  • 为避免每次下载二维码时去请求微信服务器,在新增渠道时,把二维码保存到本地,并在数据库中保存其路径

     

    2) 扫描记录业务逻辑

    微信公众平台要求微信公众号服务器必须在5秒内返回相应结果,否则会重新发送请求,一共重试三次;为了避免微信公众号服务器重复接收到同一条扫描记录,造成数据重复,导致统计失真,这里将保存扫描记录的操作放到线程池中异步执行,尽快返回相应结果给微信服务器

    public class ChannelScanBll
    {
    /// <summary>
    /// 保存扫描记录
    /// </summary>
    /// <param name="openId">微信用户OpenId</param>
    /// <param name="sceneId">扫描的二维码的参数</param>
    /// <param name="scanType">扫描类型</param>
    public void SaveScan(string openId, int sceneId, ScanType scanType)
    {
        //微信公众平台要求微信公众号服务器必须在5秒内返回相应结果,否则会重新发送请求,一共重试三次
        //为了避免微信公众号服务器重复接收到同一条扫描记录,造成数据重复,导致统计失真,这里将保存扫描记录的操作放到线程池中异步执行,尽快返回相应结果给微信服务器
        ThreadPool.QueueUserWorkItem(e =>
        {
            int channelId = new ChannelBll().GetChannelIdBySceneId(sceneId);
            if (channelId <= 0)
            {
                return;
            }
            ChannelScanEntity entity = new ChannelScanEntity()
            {
                ChannelId = channelId,
                ScanTime = DateTime.Now,
                OpenId = openId,
                ScanType = scanType
            };
            new ChannelScanDal().Insert(entity.GetDataEntity(entity));
        });
    }
     
    /// <summary>
    /// 获取渠道的扫描记录
    /// </summary>
    /// <param name="channelId">渠道ID</param>
    /// <returns></returns>
    public List<ChannelScanDisplayEntity> GetChannelScanList(int channelId)
    {
        //获取渠道扫描记录
        var entities = new ChannelScanDal().GetByPredicate(p => p.ChannelId == channelId).ToList();
        var viewEntity = new ChannelScanEntity();
        var result = entities.Select(p => new ChannelScanDisplayEntity() { ScanEntity = viewEntity.GetViewModel(p) }).ToList();
        //获取每条渠道扫描记录对应的微信用户信息
        var openIds = result.Select(p => p.ScanEntity.OpenId).ToArray();
        //在渠道扫描记录中包含微信用户信息,便于前端页面显示
        var userinfoEntities = new WeixinUserInfoDal().GetByPredicate(p => openIds.Contains(p.OpenId)).ToList();
        var userinfoViewEntity = new WeixinUserInfoEntity();
        var userinfoViewEnities = userinfoEntities.Select(p => userinfoViewEntity.GetViewModel(p)).ToList();
        result.ForEach(e =>
        {
            e.UserInfoEntity = userinfoViewEnities.Where(p => p.OpenId == e.ScanEntity.OpenId).FirstOrDefault();
        });
        return result;
    }
    }

     

    3) 渠道类型业务逻辑
    public class ChannelTypeBll
    {
        /// <summary>
        /// 获取渠道类型列表
        /// </summary>
        /// <returns></returns>
        public List<ChannelTypeEntity> GetEntities()
        {
            var entities = new ChannelTypeDal().GetByPredicate(p => p.ID > 0).ToList();
            var viewEntity = new ChannelTypeEntity();
            return entities.Select(p => viewEntity.GetViewModel(p)).ToList();
        }
     
        /// <summary>
        /// 根据ID获取渠道类型
        /// </summary>
        /// <param name="id">渠道类型ID</param>
        /// <returns></returns>
        public ChannelTypeEntity GetEntityById(int id)
        {
            var entity = new ChannelTypeDal().GetSingleByPredicate(p => p.ID == id);
            var viewEntity = new ChannelTypeEntity();
            return viewEntity.GetViewModel(entity);
        }
     
        /// <summary>
        /// 添加或修改渠道类型
        /// </summary>
        /// <param name="viewEntity">渠道类型实体</param>
        /// <returns></returns>
        public bool UpdateOrInsertEntity(ChannelTypeEntity viewEntity)
        {
            var entity = viewEntity.GetDataEntity(viewEntity);
            if (entity.ID > 0)
            {
                return new ChannelTypeDal().Update(entity);
            }
            else
            {
                return new ChannelTypeDal().InsertAndReturn(entity).ID > 0;
            }
        }
     
        /// <summary>
        /// 根据ID删除渠道类型
        /// </summary>
        /// <param name="id">渠道类型ID</param>
        /// <returns></returns>
        public bool DeleteEntityById(int id)
        {
            var entity = new ChannelTypeDal().GetSingleByPredicate(p => p.ID == id);
            return new ChannelTypeDal().Delete(entity);
        }
    }

     

    4) 用户信息业务逻辑
    public class WeixinUserInfoBll
    {
        /// <summary>
        /// 静态构造函数
        /// </summary>
        static WeixinUserInfoBll()
        {
            WeixinUserInfoSynchronize.Synchronize();
        }
     
        /// <summary>
        /// 获取微信用户信息列表
        /// </summary>
        /// <returns></returns>
        public List<WeixinUserInfoEntity> GetEntities()
        {
            var entities = new WeixinUserInfoDal().GetByPredicate(p => p.OpenId != "").ToList();
            var viewEntity = new WeixinUserInfoEntity();
            return entities.Select(p => viewEntity.GetViewModel(p)).ToList();
        }
    }

    这里定义一个静态构造函数,用于下面同步微信个人用户信息时,只会开启一个全局唯一的同步线程。

     

    2. 同步微信个人用户信息

    当微信用户扫描二维码时,只会传递openid,这时就需要调用“用户信息接口”来获取用户的信息。当保存完用户的信息后,有可能用户修改了自己的基本资料,这时就要有个机制去定时同步用户的信息。具体思路如下:

    1) 定义一个“同步微信用户信息”的静态类WeixinUserInfoSynchronize

    当网页第一次被访问时,开启一个进程内全局唯一的同步的线程,并使用单例模式确保同步线程不会被调用多次,因为网页可能被同时访问。

    /// <summary>
    /// 同步微信用户信息线程
    /// </summary>
    private static Thread SynchronizeWeixinUserThread = null;
    /// <summary>
    /// 锁
    /// </summary>
    private static object lockSingal = new object();
     
    /// <summary>
    /// 开启同步微信用户信息线程
    /// 单例模式
    /// </summary>
    public static void Synchronize()
    {
        if (SynchronizeWeixinUserThread == null)
        {
            lock (lockSingal)
            {
                if (SynchronizeWeixinUserThread == null)
                {
                    // 开启同步微信用户信息的后台线程
                    ThreadStart start = new ThreadStart(SynchronizeWeixinUserCircle);
                    SynchronizeWeixinUserThread = new Thread(start);
                    SynchronizeWeixinUserThread.Start();
                }
            }
        }
    }

     

     

    2) 定义一个每隔一段时间执行一次微信用户信息同步方法
    private static void SynchronizeWeixinUserCircle()
    {
        try
        {
            SynchronizeWeixinUser();
            Thread.Sleep(60*60*1000);
        }
        catch (Exception ex)
        {
            m_Log.Error(ex.Message, ex);
        }
    }

     

    3) 实现微信用户信息同步方法:
    • 首先获取微信公众号所有关注者的OpenId,比较数据库中是否存在
    • 如果不存在就插入
    • 如果存在就更新
    • 如果在数据库中,但不在关注者列表中的OpenId,就要删除这些已取消关注的用户
    /// <summary>
    /// 微信用户信息同步方法
    /// </summary>
    /// <returns></returns>
    private static void SynchronizeWeixinUser()
    {
        OpenIdResultJson weixinOpenIds = GetAllOpenIds();
        
     
        //获取已同步到数据库中的微信用户的OpenId
        List<string> dataOpenList = new WeixinUserInfoDll().LoadEntities(p => p.ID > 0).Select(e => e.OpenId).ToList();
     
        m_Log.Info("获取已同步到数据库中的微信用户的Data OpenId: " + dataOpenList.Count.ToString());
     
        List<string> insertOpenIdList = new List<string>();
        List<string> updateOpenIdList = new List<string>();
        List<string> deleteOpenIdList = new List<string>();
        //判断每个微信用户需要执行的操作
        for (int index = 0; index < weixinOpenIds.data.openid.Count; index++)
        {
            var weixinOpenId = weixinOpenIds.data.openid[index];
            var user = dataOpenList.Find(e => e == weixinOpenId);
            if (user == null)
            {
                //不存在数据库中的,插入
                insertOpenIdList.Add(weixinOpenId);
     
                m_Log.Info("insert open id: " + weixinOpenId);
            }
            else
            {
                //已存在数据库中的,修改
                updateOpenIdList.Add(weixinOpenId);
     
                m_Log.Info("update open id: " + weixinOpenId);
            }
        }
        //已取消关注该微信公众号的,删除
        insertOpenIdList.ForEach(e => dataOpenList.Remove(e));
        updateOpenIdList.ForEach(e => dataOpenList.Remove(e));
        deleteOpenIdList.AddRange(dataOpenList);
     
        //插入失败的openId列表,用于失败重试
        List<string> failedInsert = new List<string>();
        //修改失败的openId列表,用于失败重试
        List<string> failedUpdate = new List<string>();
        //插入新的微信用户
        foreach (var openId in insertOpenIdList)
        {
            ExecuteWeixinUser(openId, new WeixinUserInfoDll().Insert, failedInsert);
        }
        //更新已有微信用户
        foreach (var openId in updateOpenIdList)
        {
            ExecuteWeixinUser(openId, new WeixinUserInfoDll().Update, failedUpdate);
        }
        if (deleteOpenIdList.Count > 0)
        {
            //删除已取消关注该微信公众号的微信用户
            foreach (var openId in deleteOpenIdList)
            {
                new WeixinUserInfoDll().DeleteByOpenId(openId);
            }
        }
        //插入失败,重试一次
        if (failedInsert.Count > 0)
        {
            List<string> fail = new List<string>();
            foreach (var openId in failedInsert)
            {
                ExecuteWeixinUser(openId, new WeixinUserInfoDll().Insert, fail);
            }
        }
        //更新失败,重试一次
        if (failedUpdate.Count > 0)
        {
            List<string> fail = new List<string>();
            foreach (var openId in failedInsert)
            {
                ExecuteWeixinUser(openId, new WeixinUserInfoDll().Update, fail);
            }
        }
    }

    插入或更新失败,重试一次。

     

    4) 获取所有关注者的OpenId信息
    private static OpenIdResultJson GetAllOpenIds()
    {
        string accessToken = AccessTokenContainer.TryGetToken(ConfigurationManager.AppSettings["appID"], ConfigurationManager.AppSettings["appsecret"]);
        OpenIdResultJson openIdResult = User.List(accessToken, null);
        while (!string.IsNullOrWhiteSpace(openIdResult.next_openid))
        {
            OpenIdResultJson tempResult = User.List(accessToken, openIdResult.next_openid);
            openIdResult.next_openid = tempResult.next_openid;
            if (tempResult.data != null && tempResult.data.openid != null)
            {
                openIdResult.data.openid.AddRange(tempResult.data.openid);
            }
        }
        return openIdResult;
    }
     
    5) 获取openId对应的用户信息并存入数据库
    /// <summary>
    /// 获取openId对应的用户信息并存入数据库
    /// </summary>
    /// <param name="openId">微信用户openId</param>
    /// <param name="execute">修改、删除或插入操作</param>
    /// <param name="failList">未成功获取到用户信息的openId列表</param>
    private static void ExecuteWeixinUser(string openId, GetExecute execute, List<string> failList)
    {
        string accessToken = AccessTokenContainer.TryGetToken(ConfigurationManager.AppSettings["appID"], ConfigurationManager.AppSettings["appsecret"]);
        var userInfo = User.Info(accessToken, openId);
        if (userInfo.errcode != ReturnCode.请求成功)
        {
            failList.Add(openId);
     
            m_Log.Warn("fial open id: " + openId);
        }
        else
        {
            WeixinUserInfo entity = new WeixinUserInfo()
            {
                City = userInfo.city,
                Province = userInfo.province,
                Country = userInfo.country,
                HeadImgUrl = userInfo.headimgurl,
                Language = userInfo.language,
                Subscribe_time = userInfo.subscribe_time,
                Sex = (Int16)userInfo.sex,
                NickName = userInfo.nickname,
                OpenId = userInfo.openid
     
            };
     
            m_Log.Info("execute user info: " + userInfo.nickname);
     
            execute(entity);
        }
    }

     

    最后BLL层的结构如下:

    技术分享

     

    未完待续!!!

     

    用c#开发微信 系列汇总

    用c#开发微信 (7) 微渠道 - 推广渠道管理系统 2 业务逻辑实现

    标签:

    原文地址:http://www.cnblogs.com/fengwenit/p/4535020.html

    (0)
    (0)
       
    举报
    评论 一句话评论(0
    登录后才能评论!
    © 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
    迷上了代码!