第一步:要用到的接口文档如下
第二步:使用网页服务,公众号需要如下配置
1.设置 授权回调页面域名
2.设置 JS接口安全域名设置(点击公众号左边菜单 公众号设置 进入设置页面 点击 功能设置)
上代码
BLL调用程序先贴上来
系统入口 :指的是进入系统主页之前的一个请求地址,请求到达这里 又用调用微信的接口,通过微信来回调系统主页
///
/// 系统入口( http://jnga.sunmen.cn/Home/Entrance )
///
///
public ActionResult Entrance()
{
string appid, redirect_uri, scope, state;
appid = ConfigurationManager.AppSettings["appid"];
redirect_uri = "http://jnga.sunmen.cn/Home/Index";
scope = "snsapi_userinfo"; //snsapi_base用户无感觉 snsapi_userinfo 弹出授权页面
state = ""; //自定义参数
WebPageAuthorize.RedirectUrl(appid, redirect_uri, scope, state);
return new EmptyResult();
}
///
/// 系统首页
///
/// 预授权code
/// 自定义参数
///
public ActionResult Index(string code, string state)
{
if (Common.IsWeixinBrowser())
{
string weixinJsApiStr = "onMenuShareTimeline,onMenuShareAppMessage";
string appid = ConfigurationManager.AppSettings["appid"];
string secret = ConfigurationManager.AppSettings["secret"];
string weixinJsConfigStr = JsConfig.GetJsConfig(weixinJsApiStr, appid, secret);
ViewBag.JsConfigStr = weixinJsConfigStr;
if (!string.IsNullOrEmpty(code))
{
string openid = Session["openid"] == null ? "" : Session["openid"].ToString();
int userType = 0;
if (string.IsNullOrEmpty(openid))
{
Hashtable ht = WebPageAuthorize.GetWebPageAuthorizeAccessToken(appid, secret, code);
openid = ht["openid"].ToString();
Session["openid"] = openid;
}
//具体业务省略......
}
else
{
return Content("授权码不存在");
}
}
else
{
return Content("请用微信浏览器打开");
}
}
前端页面代码
@{
Layout = null;
}
<html>
<head>
<meta name="viewport" content="width=device-width" />
<title>金华人口流动申报系统title>
<meta charset="utf-8">
<meta name="viewport" content="maximum-scale=1.0,minimum-scale=1.0,user-scalable=0,width=device-width,initial-scale=1.0" />
<meta name="format-detection" content="telephone=no,email=no,date=no,address=no">
<link href="~/res/css/aui.2.0.css" rel="stylesheet" />
head>
<body>
省略.....
body>
html>
<script src="~/Scripts/jquery-1.7.1.min.js">script>
<script src="http://res.wx.qq.com/open/js/jweixin-1.0.0.js">script>
<script type="text/javascript">
$(function () {
@Html.Raw(@ViewBag.JsConfigStr);
//config信息验证后会执行ready方法,
//所有接口调用都必须在config接口获得结果之后,config是一个客户端的异步操作,所以如果需要在页面加载时就调用相关接口,则须把相关接口放在ready函数中调用来确保正确执行。
//对于用户触发时才调用的接口,则可以直接调用,不需要放在ready函数中。
wx.ready(function () {
//获取“分享到朋友圈”按钮点击状态及自定义分享内容接口
wx.onMenuShareTimeline({
title: '金华人口流动申报系统', // 分享标题
link: 'http://jnga.sunmen.cn', // 分享链接
imgUrl: 'http://jnga.sunmen.cn/images/wx.jpg', // 分享图标
success: function () {
// 用户确认分享后执行的回调函数
alert("用户确认分享后--执行的回调函数");
},
cancel: function () {
// 用户取消分享后执行的回调函数
alert("用户取消分享后--执行的回调函数")
}
});
wx.onMenuShareAppMessage({
title: '金华人口流动申报系统', // 分享标题
desc: '平安金华', // 分享描述
link: 'http://jnga.sunmen.com', // 分享链接
imgUrl: 'http://jnga.sunmen.cn/images/wx.jpg', // 分享图标
type: 'link', // 分享类型,music、video或link,不填默认为link
dataUrl: '', // 如果type是music或video,则要提供数据链接,默认为空
success: function () {
// 用户确认分享后执行的回调函数
alert("用户确认分享后--执行的回调函数");
},
cancel: function () {
// 用户取消分享后执行的回调函数
alert("用户取消分享后--执行的回调函数")
}
});
});
wx.error(function (res) {
//config信息验证失败会执行error函数,如签名过期导致验证失败,具体错误信息可以打开config的debug模式查看,也可以在返回的res参数中查看,对于SPA可以在这里更新签名。
});
});
script>
WebPageAuthorize (网页授权类)
using Codeplex.Data;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using WeiXinPublic.utils;
namespace WeiXinPublic.web_page_service
{
///
/// 网页授权
///
public class WebPageAuthorize
{
///
/// 网页授权:第一步:用户同意授权,获取code,该方法在入口处调用
///
/// 公众号的唯一标识
/// 授权后重定向的回调链接地址,请使用urlencode对链接进行处理
/// 应用授权作用域,snsapi_base (不弹出授权页面,直接跳转,只能获取用户openid),snsapi_userinfo (弹出授权页面,可通过openid拿到昵称、性别、所在地。并且,即使在未关注的情况下,只要用户授权,也能获取其信息)
/// 重定向后会带上state参数,开发者可以填写a-zA-Z0-9的参数值,最多128字节
public static void RedirectUrl(string appid, string redirect_uri, string scope, string state)
{
string response_type = "code";
string url = "https://open.weixin.qq.com/connect/oauth2/authorize?appid=" + appid + "&redirect_uri=" + redirect_uri + "&response_type=" + response_type + "&scope=" + scope + "&state=" + state + "#wechat_redirect";
System.Web.HttpContext.Current.Response.Redirect(url);
/* https://open.weixin.qq.com/connect/oauth2/authorize?appid=APPID&redirect_uri=REDIRECT_URI&response_type=code&scope=SCOPE&state=STATE#wechat_redirect
如果用户同意授权,页面将跳转至 redirect_uri/?code=CODE&state=STATE。若用户禁止授权,则重定向后不会带上code参数,仅会带上state参数redirect_uri?state=STATE
code说明 :code作为换取access_token的票据【网页授权特有的接口调用凭证(网页授权access_token)】,每次用户授权带上的code将不一样,code只能使用一次,5分钟未被使用自动过期。
回调页面接收code 和 state
*
* 通过这一步,回调页面 只能再微信浏览器打开
*/
}
///
/// 网页授权:第二步:通过code换取网页授权access_token
///
/// 公众号的唯一标识
/// 公众号的appsecret
/// 填写第一步获取的code参数
///
private static string get_web_page_authorize_accesstoken(string appid, string secret, string code)
{
/*
首先请注意,这里通过code换取的是一个特殊的网页授权access_token,与基础支持中的access_token(该access_token用于调用其他接口)不同。
公众号可通过下述接口来获取网页授权access_token。
如果网页授权的作用域为snsapi_base,则本步骤中获取到网页授权access_token的同时,也获取到了openid,snsapi_base式的网页授权流程即到此为止。
*/
//获取code后,请求以下链接获取access_token:
string res = string.Empty;
string url = "https://api.weixin.qq.com/sns/oauth2/access_token?appid=" + appid + "&secret=" + secret + "&code=" + code + "&grant_type=authorization_code";
res = Sender.Get(url);
return res;
}
///
/// 网页授权:第二步:通过code换取网页授权access_token 实体对象
///
///
///
///
///
public static Hashtable GetWebPageAuthorizeAccessToken(string appid, string secret, string code)
{
string res = get_web_page_authorize_accesstoken(appid, secret, code);
var json = DynamicJson.Parse(res);
var access_token = json.access_token;
var expires_in = json.expires_in;
var refresh_token = json.refresh_token;
var openid = json.openid;
var scope = json.scope;
//var unionid = json.unionid;
//万能字典
Hashtable ht = new Hashtable();
ht.Add("access_token", access_token); //【网页授权】接口调用凭证,注意:此access_token与基础支持的access_token不同
ht.Add("expires_in", expires_in); //access_token接口调用凭证超时时间,单位(秒)
// 刷新access_token
// 由于access_token拥有较短的有效期,当access_token超时后,可以使用refresh_token进行刷新,refresh_token拥有较长的有效期(7天、30天、60天、90天),当refresh_token失效的后,需要用户重新授权。
ht.Add("refresh_token", refresh_token);
//用户唯一标识,请注意,在未关注公众号时,用户访问公众号的网页,也会产生一个用户和公众号唯一的OpenID
ht.Add("openid", openid);
//用户授权的作用域,使用逗号(,)分隔
ht.Add("scope", scope);
//只有在用户将公众号绑定到微信开放平台帐号后,才会出现该字段。详见:获取用户个人信息(UnionID机制)
// ht.Add("unionid", unionid);
return ht;
}
}
}
JsConfig (Js配置生产类)
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using WeiXinPublic.dialog_service;
using WeiXinPublic.utils;
namespace WeiXinPublic.web_page_service
{
///
/// JsConfig
///
public class JsConfig
{
///
/// 3.签名计算 生成签名字符串
///
/// 随机字符串
/// 有效的票据
/// 时间戳
/// 当前网页的URL,不包含#及其后面部分
///
private static string SignatureCompute(string noncestr, string jsapi_ticket, string timestamp, string url)
{
//3.签名算法
/*签名生成规则如下:
参与签名的字段包括 noncestr(随机字符串),
jsapi_ticket(有效的票据),
timestamp(时间戳),
url(当前网页的URL,不包含#及其后面部分)
。对所有待 签名参数 按照字段名的ASCII 码从小到大排序(字典序)后,使用URL键值对的格式(即key1=value1&key2=value2…)拼接成字符串string1。
这里需要注意的是所有参数名均为小写字符。对string1作sha1加密,字段名和字段值都采用原始值,不进行URL 转义。*/
#region 3-1.签名参数 按照字段名的ASCII 码从小到大排序(字典序)后
Dictionary<string, string> dic = new Dictionary<string, string>();
dic["timestamp"] = timestamp;
dic["noncestr"] = noncestr;
dic["url"] = url;
dic["jsapi_ticket"] = jsapi_ticket;
string[] arrkey = new string[] { "timestamp", "noncestr", "url", "jsapi_ticket" };
arrkey = arrkey.OrderBy(n => n).ToArray();
#endregion
#region 3-2.使用URL键值对的格式(即key1=value1&key2=value2…)拼接成字符串string1
string signatureStr = "";
foreach (string key in arrkey)
{
signatureStr += key + "=" + dic[key] + "&";
}
signatureStr = signatureStr.Substring(0, signatureStr.Length - 1);
#endregion
return signatureStr;
}
///
/// 4.签名字符串 SHA1加密
///
///
///
private static string SignatureSha1(string str)
{
var sha1 = System.Security.Cryptography.SHA1.Create();
byte[] bytes = Encoding.UTF8.GetBytes(str);
byte[] bytesArr = sha1.ComputeHash(bytes);
StringBuilder sb = new StringBuilder();
foreach (var item in bytesArr)
{
sb.AppendFormat("{0:x2}", item);
}
return sb.ToString();
}
///
/// 5.创建微信jsApi配置 字符串
///
/// 必填,公众号的唯一标识
/// 必填,生成签名的时间戳
/// 必填,生成签名的随机串
/// 签名字符串 SHA1加密
/// 需要注册的微信js接口 多个接口用,隔开
///
private static string CreateWeiXinJsConfig(string appid, string timestamp, string noncestr, string signatureStr, string weixinJsApiStr)
{
//"onMenuShareTimeline,onMenuShareAppMessage,onMenuShareQQ";
string jsStr = "wx.config({";
jsStr += "debug: false,";
jsStr += "appId: '" + appid + "',";
jsStr += "timestamp: " + timestamp + ",";
jsStr += "nonceStr: '" + noncestr + "',";
jsStr += "signature: '" + signatureStr + "',";
jsStr += "jsApiList: ['" + weixinJsApiStr.Replace(",", "','") + "']";
jsStr += "});";
return jsStr;
}
///
/// 获取微信jsApi配置字符串
///
/// 需要注册的微信js接口 多个接口用,隔开
///
public static string GetJsConfig(string weixinJsApiStr, string appid, string secret)
{
//获取随机字符串 必填,生成签名的随机串
string noncestr = Common.GetRandomStr(32);
//获取时间戳 必填,生成签名的时间戳
string timestamp = Common.GetTimeStamp();
//获取当前网页地址 可以带参数
string url = System.Web.HttpContext.Current.Request.Url.AbsoluteUri;
//1.获取access_token
Hashtable ht_access_token = BasisSupport.GetAccessToken(appid, secret);
string access_token = ht_access_token["access_token"].ToString();
//2.用第一步拿到的access_token
Hashtable ht_jsapi_ticket = BasisApi.GetJsApiTicket(access_token);
string ticket = ht_jsapi_ticket["ticket"].ToString();
//3.签名计算 生成签名字符串
string signatureStr = SignatureCompute(noncestr, ticket, timestamp, url);
//4.签名字符串 SHA1加密 必填,签名,见附录1
signatureStr = SignatureSha1(signatureStr);
return CreateWeiXinJsConfig(appid, timestamp, noncestr, signatureStr, weixinJsApiStr);
}
}
}
Common 公共类
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace WeiXinPublic.utils
{
public class Common
{
///
/// 获取客户端浏览器的原始用户代理信息。
///
///
public static string GetRequestUserAgent()
{
return System.Web.HttpContext.Current.Request.UserAgent.ToString(); //获取客户端浏览器的原始用户代理信息。
}
///
/// 判断是否微信浏览器
///
///
public static bool IsWeixinBrowser()
{
string userAgent = GetRequestUserAgent();
return userAgent.IndexOf("MicroMessenger") > 0 ? true : false;
}
///
/// 获取随机字符串
///
///
///
public static string GetRandomStr(int length)
{
string str = "0123456789qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM";
Random random = new Random();
string res = "";
for (int i = 0; i < length; i++)
{
int index = random.Next(str.Length);
res += str.Substring(index, 1);
}
return res;
}
///
/// 获取时间戳
///
///
public static string GetTimeStamp()
{
TimeSpan ts = DateTime.UtcNow - new DateTime(1970, 1, 1);
return Convert.ToInt64(ts.TotalSeconds).ToString();
}
}
}
BasisApi (基础接口类)
using Codeplex.Data;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using WeiXinPublic.utils;
namespace WeiXinPublic.web_page_service
{
///
/// 基础接口
/// 1.判断当前客户端版本是否支持指定JS接口
/// 2.获取jsapi_ticket
///
public class BasisApi
{
#region 获取jsapi_ticket
///
/// 获得jsapi_ticket 采用http GET方式请求获得jsapi_ticket (有效期7200秒,开发者必须在自己的服务全局缓存jsapi_ticket) 附录1-JS-SDK使用权限签名算法
///
///
///
private static string get_jsapi_ticket(string accesstoken)
{
string res = string.Empty;
object obj = CacheHelper.GetCache("jsapi_ticket");
if (obj != null)
{
res = obj.ToString();
}
else
{
string url = "https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=" + accesstoken + "&type=jsapi"; //GET方式请求
res = Sender.Get(url);
CacheHelper.AddCache("jsapi_ticket", res, DateTime.Now.AddMinutes(100)); //jsapi_ticket的有效期目前为2个小时
}
return res;
}
/* jsapi_ticket
{
"errcode":0,
"errmsg":"ok",
"ticket":"sM4AOVdWfPE4DxkXGEs8VFg9ITyPm9rZNXtk60kDcOW37rgw8PSTCg3DR9CEHATN4k4oIC8uzsCGbhKHykHjdA",
"expires_in":7200
}
*/
///
/// 获取jsapi_ticket
///
///
///
public static Hashtable GetJsApiTicket(string accesstoken)
{
string res = get_jsapi_ticket(accesstoken);
var json = DynamicJson.Parse(res);
var errcode = json.errcode;
var errmsg = json.errmsg;
var ticket = json.ticket;
var expires_in = json.expires_in;
//万能字典
Hashtable ht = new Hashtable();
ht.Add("errcode", errcode);
ht.Add("errmsg", errmsg);
ht.Add("ticket", ticket);
ht.Add("expires_in", expires_in);
return ht;
}
#endregion
}
}
类库结构