項目需要,做個微信公眾號,之前從未做過,前期挺懵的,再次記錄一下,一切困難都是紙老虎(哈哈)
服務號是公司申請的微信公共賬號,訂閱號是個人申請的。建議開發者自己申請一個測試賬號,方便使用,但是測試賬號不能測試使用支付功能,如果牽扯到支付的功能,建議先用測試賬號把其他的做好,然后使用正式的賬號,測試支付的功能(個人思路哈)
好了,接下來是項目了(以MVC項目為例):
引用的dll
測試賬號的申請
地址:https://mp.weixin.qq.com/debug/cgi-bin/sandbox?t=sandbox/login
需要填寫Url和token:注意這里的Url是有限制的,需要講默的頁面配置為Url!
接下來便是代碼了:
首先你需要建立一個CustomMessageHandler類,繼承自dll中的類,如下:
/// /// 微信消息處理 /// /// 根據微信原始Id, 判斷具體推送給哪一個用戶 /// /// public partial class CustomMessageHandler : MessageHandler<MessageContext<Senparc.Weixin.MP.Entities.IRequestMessageBase, Senparc.Weixin.MP.Entities.IResponseMessageBase>> { private static string AppID = ConfigManager.Config.Wechat.AppID; static object _locker = new object(); public CustomMessageHandler(Stream inputStream, PostModel postModel = null, int maxRecordCount = 0) : base(inputStream, postModel, maxRecordCount) { base.CurrentMessageContext.ExpireMinutes = 10; } public CustomMessageHandler(XDocument requestDocument, PostModel postModel = null, int maxRecordCount = 0) : base(requestDocument, postModel, maxRecordCount) { } public CustomMessageHandler(RequestMessageBase requestMessageBase, PostModel postModel = null, int maxRecordCount = 0) : base(requestMessageBase, postModel, maxRecordCount) { } #region 用戶關注公眾號 /// /// 用戶關注公眾號 /// /// /// public async override Task OnEvent_SubscribeRequestAsync(RequestMessageEvent_Subscribe requestMessage) { try { if (AppID != null) { var userInfo = await UserApi.InfoAsync(AppID, base.WeixinOpenId); if (userInfo == null) { Sprite.Agile.Logger.LoggerManager.Instance.Logger_Info($"獲取用戶信息失敗"); } else { Sprite.Agile.Logger.LoggerManager.Instance.Logger_Info($"獲取用戶信息,OpenId:{userInfo.openid}, WeixinOpenId:{WeixinOpenId}"); } #region 處理用戶信息 if (requestMessage.Event == Event.subscribe) { var user = usersRepository.GetUserByOpenId(userInfo.openid); Sprite.Agile.Logger.LoggerManager.Instance.Logger_Info($"二維碼場景值{userInfo.qr_scene}"); if (user == null) { user = new 你的用戶類 { OpenId = userInfo.openid, WeChatNickName = userInfo.nickname, HeadImg = userInfo.headimgurl, Status = Sprite.Agile.Domain.Status.Disabled, UserType=Sprite.Agile.Model.Domain.Entities.UserType.User, IsSubscribe = true }; //判斷是否通過掃描進入關注 if (userInfo.subscribe_scene == "ADD_SCENE_QR_CODE") { //暫時通過場景Id識別上級 var parent = usersRepository.GetUserByScanId(userInfo.qr_scene); if(parent!=null) { user.ParentId = parent.Id; } } usersRepository.AddUser(user); } else { user.OpenId = userInfo.openid; user.HeadImg = userInfo.headimgurl; user.WeChatNickName = userInfo.nickname; user.IsSubscribe = true; usersRepository.SaveUser(user); } } #endregion var responseMessage = requestMessage.CreateResponseMessage(); responseMessage.Content = $" 感謝您的關注"; return responseMessage; } else { Sprite.Agile.Logger.LoggerManager.Instance.Logger_Info($"用戶關注事件, 微信商戶信息為空:{requestMessage.ToUserName}"); return new ResponseMessageNoResponse(); } } catch (Exception ex) { Sprite.Agile.Logger.LoggerManager.Instance.Logger_Error(ex); return new ResponseMessageNoResponse(); } } #endregion #region 用戶取消關注 /// /// 用戶取消關注 /// /// /// public async override Task OnEvent_UnsubscribeRequestAsync(RequestMessageEvent_Unsubscribe requestMessage) { try { var wechat = new UserBaseController(); if (wechat != null) { var userInfo = await UserApi.InfoAsync(wechat.AppId, base.WeixinOpenId); #region 處理用戶信息 if (requestMessage.Event == Event.unsubscribe) { var user = usersRepository.GetUserByOpenId(userInfo.openid); if (user != null) { user.IsSubscribe = false; usersRepository.SaveUser(user); } } #endregion } else { Sprite.Agile.Logger.LoggerManager.Instance.Logger_Info($"用戶取消關注事件, 微信商戶信息為空:{requestMessage.ToUserName}"); } return new ResponseMessageNoResponse(); } catch (Exception ex) { Sprite.Agile.Logger.LoggerManager.Instance.Logger_Error(ex); return new ResponseMessageNoResponse(); } } #endregion #region 處理文字請求 /// /// 處理文字請求 /// /// /// public async override Task OnTextRequestAsync(RequestMessageText requestMessage) { Sprite.Agile.Logger.LoggerManager.Instance.Logger_Info($"處理文字請求,ToUserName:{requestMessage.ToUserName}, WeixinOpenId:{WeixinOpenId}"); try { return await Task.Factory.StartNew(() => { var responseMessage = requestMessage.CreateResponseMessage(); //responseMessage.Content = $"文本消息,ToUserName:{requestMessage.ToUserName}, WeixinOpenId:{WeixinOpenId}"; responseMessage.Content = $"歡迎*****微信公眾號"; return responseMessage; } ); } catch (Exception) { return new ResponseMessageNoResponse(); }; } #endregion public async override Task OnEvent_ScanRequestAsync(RequestMessageEvent_Scan requestMessage) { //通過掃描關注 var responseMessage = CreateResponseMessage(); responseMessage.Content = $" 您已經是我們的用戶啦!" ; return responseMessage; } #region 默認觸發事件 /// /// 默認觸發事件 /// /// /// public async override Task DefaultResponseMessageAsync(IRequestMessageBase requestMessage) { try { return await Task.Factory.StartNew(() => { var responseMessage = requestMessage.CreateResponseMessage(); responseMessage.Content = $"默認,ToUserName:{requestMessage.ToUserName}, WeixinOpenId:{WeixinOpenId}"; return responseMessage; } ); } catch (Exception) { return new ResponseMessageNoResponse(); }; } /// /// 默認觸發事件 /// /// /// public override IResponseMessageBase DefaultResponseMessage(IRequestMessageBase requestMessage) { try { var responseMessage = requestMessage.CreateResponseMessage(); responseMessage.Content = "你需要推送的內容"; return responseMessage; } catch (Exception) { return new ResponseMessageNoResponse(); }; } #endregion }
然后,你需要建立一個基礎的Controller,來進行攔截判斷權限
/// /// 微信認證模塊 /// [AllowAnonymous] public class BaseController : Controller { //這些文件一般配置zai webconfig中 private static string Token = ConfigManager.Config.Wechat.Token; private static string AppID = ConfigManager.Config.Wechat.AppID; private static string AppSecret = ConfigManager.Config.Wechat.AppSecret; private static string EncodingAESKey = ConfigManager.Config.Wechat.EncodingAESKey; IYueSaoUserService userServices; public BaseController() { userServices = Sprite.Agile.Plugins.PluginManager.Resolve(); } #region // 生成隨機文件名 readonly Func _getRandomFileName = () => DateTime.Now.ToString("yyyyMmdd-HHmmss") + Guid.NewGuid().ToString("n").Substring(0, 6); [HttpGet] [ActionName("Index")] public Task Get(string signature, string timestamp, string nonce, string echostr) { return Task.Factory.StartNew(() => { if (CheckSignature.Check(signature, timestamp, nonce, Token)) { return echostr; //返回隨機字符串則表示驗證通過 } else { return "failed:" + signature + "," + CheckSignature.GetSignature(timestamp, nonce, Token) + "。" + "如果你在瀏覽器中看到這句話,說明此地址可以被作為微信公眾賬號后台的Url,請注意保持Token一致。"; } }).ContinueWith(task => Content(task.Result)); } /// /// 用戶發送消息后,微信平台自動Post一個請求到這里,並等待響應XML /// [HttpPost] [ActionName("Index")] public async Task Post(PostModel postModel) { if (!CheckSignature.Check(postModel.Signature, postModel.Timestamp, postModel.Nonce, Token)) { return new WeixinResult("參數錯誤!"); } #region 打包 PostModel 信息 postModel.Token = Token; postModel.EncodingAESKey = EncodingAESKey; //根據自己后台的設置保持一致 postModel.AppId = AppID; //根據自己后台的設置保持一致 #endregion var messageHandler = new CustomMessageHandler(Request.InputStream, postModel, 10); #region 設置消息去重 /* 如果需要添加消息去重功能,只需打開OmitRepeatedMessage功能,SDK會自動處理。 * 收到重復消息通常是因為微信服務器沒有及時收到響應,會持續發送2-5條不等的相同內容的RequestMessage*/ messageHandler.OmitRepeatedMessage = true;//默認已經開啟,此處僅作為演示,也可以設置為false在本次請求中停用此功能 #endregion try { #region 記錄 Request 日志 var logPath = Server.MapPath(string.Format("~/App_Data/MP/{0}/", DateTime.Now.ToString("yyyy-MM-dd"))); if (!Directory.Exists(logPath)) { Directory.CreateDirectory(logPath); } //測試時可開啟此記錄,幫助跟蹤數據,使用前請確保App_Data文件夾存在,且有讀寫權限。 messageHandler.RequestDocument.Save(Path.Combine(logPath, string.Format("{0}_Request_{1}_{2}.txt", _getRandomFileName(), messageHandler.RequestMessage.FromUserName, messageHandler.RequestMessage.MsgType))); if (messageHandler.UsingEcryptMessage) { messageHandler.EcryptRequestDocument.Save(Path.Combine(logPath, string.Format("{0}_Request_Ecrypt_{1}_{2}.txt", _getRandomFileName(), messageHandler.RequestMessage.FromUserName, messageHandler.RequestMessage.MsgType))); } #endregion await messageHandler.ExecuteAsync(); //執行微信處理過程 //messageHandler.Execute(); //執行微信處理過程 #region 記錄 Response 日志 //測試時可開啟,幫助跟蹤數據 //if (messageHandler.ResponseDocument == null) //{ // throw new Exception(messageHandler.RequestDocument.ToString()); //} if (messageHandler.ResponseDocument != null) { messageHandler.ResponseDocument.Save(Path.Combine(logPath, string.Format("{0}_Response_{1}_{2}.txt", _getRandomFileName(), messageHandler.ResponseMessage.ToUserName, messageHandler.ResponseMessage.MsgType))); } if (messageHandler.UsingEcryptMessage && messageHandler.FinalResponseDocument != null) { //記錄加密后的響應信息 messageHandler.FinalResponseDocument.Save(Path.Combine(logPath, string.Format("{0}_Response_Final_{1}_{2}.txt", _getRandomFileName(), messageHandler.ResponseMessage.ToUserName, messageHandler.ResponseMessage.MsgType))); } #endregion //return Content(messageHandler.ResponseDocument.ToString());//v0.7- return new WeixinResult(messageHandler);//v0.8+ //return new FixWeixinBugWeixinResult(messageHandler);//為了解決官方微信5.0軟件換行bug暫時添加的方法,平時用下面一個方法即可 } catch (Exception ex) { #region 異常處理 //WeixinTrace.Log("MessageHandler錯誤:{0}", ex.Message); using (TextWriter tw = new StreamWriter(Server.MapPath("~/App_Data/Error_" + _getRandomFileName() + ".txt"))) { tw.WriteLine("ExecptionMessage:" + ex.Message); tw.WriteLine(ex.Source); tw.WriteLine(ex.StackTrace); //tw.WriteLine("InnerExecptionMessage:" + ex.InnerException.Message); if (messageHandler.ResponseDocument != null) { tw.WriteLine(messageHandler.ResponseDocument.ToString()); } if (ex.InnerException != null) { tw.WriteLine("========= InnerException ========="); tw.WriteLine(ex.InnerException.Message); tw.WriteLine(ex.InnerException.Source); tw.WriteLine(ex.InnerException.StackTrace); } tw.Flush(); tw.Close(); } return Content(""); #endregion } } #endregion #region 微信授權 /// /// 微信授權登錄頁面 /// ///跳轉地址 /// public async Task Login(string returnUrl) { var appId = AppID; return await Task.Factory.StartNew(() => { var doMain = Request.Url.Scheme + "://" + Request.Url.Host; var redirect_uri = $"{doMain}{Url.Action("Callback", new { returnUrl = returnUrl })}"; var state = "FenXiao-" + DateTime.Now.Millisecond; Session["State"] = state; #region 通過參數信息,拿到用戶訪問的appid #endregion var oauthUrl = OAuthApi.GetAuthorizeUrl(appId, redirect_uri, state, OAuthScope.snsapi_userinfo); return oauthUrl; }).ContinueWith(task => { var url = task.Result; return Redirect(task.Result); }); } /// /// 微信回調 /// /// /// /// /// public ActionResult Callback(string code, string state, string returnUrl) { Sprite.Agile.Logger.LoggerManager.Instance.Logger_Info($"微信回調{returnUrl}"); var url = string.Empty; var doMain = Request.Url.Scheme + "://" + Request.Url.Host; if (string.IsNullOrEmpty(code)) { url = Url.Action("Msg", "Msg", new { msg = "您拒絕了授權!" }); return Redirect($"{doMain}/{url}"); } if (state != Session["State"] as string) { Session["State"] = null; url = Url.Action("Msg", "Msg", new { msg = "驗證失敗!請從正規途徑進入!" }); return Redirect($"{doMain}/{url}"); } OAuthAccessTokenResult result = null; try { result = OAuthApi.GetAccessToken(AppID, AppSecret, code); } catch (Exception ex) { url = Url.Action("Msg", "Msg", new { msg = ex.Message }); return Redirect($"{doMain}/{url}"); } if (result.errcode != Senparc.Weixin.ReturnCode.請求成功) { url = Url.Action("Msg", "Msg", new { msg = result.errmsg }); return Redirect($"{doMain}/{url}"); } Session["State"] = null; //下面2個數據也可以自己封裝成一個類,儲存在數據庫中(建議結合緩存) //如果可以確保安全,可以將access_token存入用戶的cookie中,每一個人的access_token是不一樣的 Session["OAuthAccessTokenStartTime"] = DateTime.Now; Session["OAuthAccessToken"] = result; var oauthAccessToken = result.access_token; var openId = result.openid; var userInfo = OAuthApi.GetUserInfo(oauthAccessToken, openId); Session["OpenId"] = openId; //Session["AppId"] = appId; #region 查找數據庫看用戶信息是不是存在, 根據用戶信息存在與否執行不同操作 try { var user = userServices.GetUserByOpenId(openId); if (user == null) { user = new Sprite.Agile.Model.Domain.Entities.YueSaoUserManage.YueSaoUser { OpenId = openId, WeChatNickName = userInfo.nickname, HeadImg = userInfo.headimgurl, UserType = Sprite.Agile.Model.Domain.Entities.UserType.User, Status = Sprite.Agile.Domain.Status.Disabled, }; userServices.AddUser(user); } else { #region 跟新用戶頭像 & 昵稱 user.WeChatNickName = userInfo.nickname; user.HeadImg = userInfo.headimgurl; userServices.SaveUser(user); #endregion } Session["UserId"] = user.Id; if (user.ParentId != null) Session["ParentId"] = user.ParentId; Sprite.Agile.Logger.LoggerManager.Instance.Logger_Info($"old:{user.Id} new:{ Session["UserId"]}"); Session["HeadImg"] = user.HeadImg; Session["WeChatNickName"] = user.WeChatNickName; } catch (Exception ex) { Sprite.Agile.Logger.LoggerManager.Instance.Logger_Info($"回調錯誤信息{ex.Message}"); url = Url.Action("Msg", "Msg", new { msg = "從DB中獲取用戶信息失敗" }); return Redirect($"{doMain}/{url}"); } #endregion if (returnUrl.IndexOf('?') != -1) { returnUrl += "&appId=" + AppID; } else { returnUrl += "?appId=" + AppID; } return Redirect(returnUrl); } #endregion #region 菜單配置 /// /// 微信創建菜單 /// /// /// [HttpGet] public async Task CreateMenu(string keyValue) { var data = "成功"; try { if (keyValue.IsEmputy()) { data = "參數錯誤"; } else if (AppID != null) { var accessToken = await AccessTokenContainer.GetAccessTokenAsync(AppID); var wechatWeb = 你的Url;// $"{Request.Url.Scheme}://{Request.Url.Host}"; Sprite.Agile.Logger.LoggerManager.Instance.Logger_Debug($"菜單地址:{wechatWeb}"); ButtonGroup bg = new ButtonGroup(); bg.button.Add(new SingleViewButton() { url = $"{wechatWeb}/Home/Index?appId={AppID}", name = "首頁" }); bg.button.Add(new SingleViewButton() { url = $"{wechatWeb}/TrainingRegistration/NannyTrain?appId={AppID}", name = "*****" }); bg.button.Add(new SingleViewButton() { url = $"{wechatWeb}/YueSaoUser/PersonalCenters?appId={AppID}", name = "個人中心" }); var result = CommonApi.CreateMenu(accessToken, bg); if (result.errcode == ReturnCode.請求成功) { data = "菜單同步成功"; } else { data = "菜單同步失敗"; } } } catch (Exception e) { data = e.Message; } ViewBag.Message = data; return View(); } #endregion }
注意上面的菜單配置,需要調用一下接口才會有效果的!