用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层的结构如下:
未完待续!!!
用c#开发微信 (7) 微渠道 - 推广渠道管理系统 2 业务逻辑实现的更多相关文章
- 用c#开发微信 (8) 微渠道 - 推广渠道管理系统 3 UI设计及后台处理
我们可以使用微信的“生成带参数二维码接口”和 “用户管理接口”,来实现生成能标识不同推广渠道的二维码,记录分配给不同推广渠道二维码被扫描的信息.这样就可以统计和分析不同推广渠道的推广效果. 前面二篇& ...
- 用c#开发微信 (11) 微统计 - 阅读分享统计系统 1 基础架构搭建
微信平台自带的统计功能太简单,有时我们需要统计有哪些微信个人用户阅读.分享了微信公众号的手机网页,以及微信个人用户访问手机网页的来源:朋友圈分享访问.好友分享消息访问等.本系统实现了手机网页阅读.分享 ...
- C#开发微信门户及应用(10)--在管理系统中同步微信用户分组信息
在前面几篇文章中,逐步从原有微信的API封装的基础上过渡到微信应用平台管理系统里面,逐步介绍管理系统中的微信数据的界面设计,以及相关的处理操作过程的逻辑和代码,希望从更高一个层次,向大家介绍微信的应用 ...
- 用c#开发微信 (6) 微渠道 - 推广渠道管理系统 1 基础架构搭建
我们可以使用微信的“生成带参数二维码接口”和 “用户管理接口”,来实现生成能标识不同推广渠道的二维码,记录分配给不同推广渠道二维码被扫描的信息.这样就可以统计和分析不同推广渠道的推广效果. 本系统使用 ...
- 用c#开发微信 (9) 微渠道 - 推广渠道管理系统 4 部署测试 (最终效果图)
我们可以使用微信的“生成带参数二维码接口”和 “用户管理接口”,来实现生成能标识不同推广渠道的二维码,记录分配给不同推广渠道二维码被扫描的信息.这样就可以统计和分析不同推广渠道的推广效果. 本文是微渠 ...
- 用c#开发微信 (15) 微活动 1 大转盘
微信营销是一种新型的营销模式,由于微信更重视用户之间的互动,故而这种营销推广不不能盲目地套用微博营销的单纯大量广告推送方式.这种方式在微信营销中的效果非常差,会令用户反感,继而取消去企业或商家的微信公 ...
- 用c#开发微信 (16) 微活动 2 刮刮卡
微信营销是一种新型的营销模式,由于微信更重视用户之间的互动,故而这种营销推广不不能盲目地套用微博营销的单纯大量广告推送方式.这种方式在微信营销中的效果非常差,会令用户反感,继而取消去企业或商家的微信公 ...
- 用c#开发微信 (12) 微统计 - 阅读分享统计系统 2 业务逻辑实现
微信平台自带的统计功能太简单,有时我们需要统计有哪些微信个人用户阅读.分享了微信公众号的手机网页,以及微信个人用户访问手机网页的来源:朋友圈分享访问.好友分享消息访问等.本系统实现了手机网页阅读.分享 ...
- 用c#开发微信 (13) 微统计 - 阅读分享统计系统 3 UI设计及后台处理
微信平台自带的统计功能太简单,有时我们需要统计有哪些微信个人用户阅读.分享了微信公众号的手机网页,以及微信个人用户访问手机网页的来源:朋友圈分享访问.好友分享消息访问等.本系统实现了手机网页阅读. ...
随机推荐
- SQL Server添加MDW性能监控报表(转载)
10.2 Data Collector与MDW Data Collection功能是SQL SERVER 2005版本提供的数据库监控报表的功能,通过定时地对数据库的语句运行情况,服务器各种资源的监控 ...
- 8.8 CSS知识点1
什么是CSS CSS(Cascading Style Sheet) 层叠样式表 CSS3在CSS2的基础上增加了很多强大的新功能,目前主流浏览器都支持CSS3大部分功能.为了更好的向前兼容,不同的浏览 ...
- Javascript类型
Javascript 有两中类型:原始类型和对象类型. 原始类型包括:数字,字符串,布尔值,null和undefined.其余的都是对象类型. 原始类型 数字.Javascript采用IEEE754标 ...
- Multipart to single part feature
Multipart to single part feature Explode Link: http://edndoc.esri.com/arcobjects/8.3/?URL=/arcobject ...
- sql中列数据横着显示
列数据横着显示:CREATE TABLE StudenScore(stuname VARCHAR(25) , kc VARCHAR(25) , fs INT)INSERT INTO StudenSco ...
- iframe自动适应高度1
js: function iFrameHeight() { var ifm= document.getElementById("iframepage"); var subWeb = ...
- step6----->往工程中添加spring boot项目------->修改pom.xml使得我的project是基于spring boot的,而非直接基于spring framework
文章内容概述: spring项目组其实有多个projects,如spring IO platform用于管理external dependencies的版本,通过定义BOM(bill of mater ...
- Linux系统下 解决Qt5工程打不开的方法
一.问题现象 打开Qt工程的时候,控制台报错:Could not find qmake configuration file default. 二.问题原因 我碰到这种问题的原因是我的Linux系统装 ...
- python 基础理解...
class obj(object): def __getattribute__(self, *args, **kwargs): # 访问属性就会被调用 print("__getattribu ...
- UTF-8 <==> unicode(WCHAR)
static int fetchWordFromUTF8(const chConstStringA& strText, WCHAR& result) { int nLength = s ...