用c#開發微信 (7) 微渠道 - 推廣渠道管理系統 2 業務邏輯實現


我們可以使用微信的“生成帶參數二維碼接口”和 “用戶管理接口”,來實現生成能標識不同推廣渠道的二維碼,記錄分配給不同推廣渠道二維碼被掃描的信息。這樣就可以統計和分析不同推廣渠道的推廣效果。

上次介紹了《用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層的結構如下:

image

 

未完待續!!!

 

用c#開發微信 系列匯總


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM