.net C#微信公众号开发
#一、开发准备工作
打开微信公众平台,主页左侧找到 “开发”栏目,选择基本配置,获取AppId,appsecret。
开发者密码需要管理员授权查看。
1、主页左侧找到 “开发”栏目,选择基本配置,下半页“服务器配置”模块下,点击修改配置,需要填写URL和Token,这两个参数下面会有讲到
URL是指我们用来验证我们服务端和微信发送的Token是否一致,总之就是为了验证服务器资源真实性。注:URL必须是域名下的验证地址。
EncodingAESKey选择随机模式即可;
消息加解密方式根据业务需求,我这边是兼容模式。
下面我们看一下Token验证的实现
namespace WeiXinWeb.ZCWeChat.sqsb
{
public partial class AddCasePage : System.Web.UI.Page
{
public readonly string Token = "ymcgxm";//与微信公众账号后台的Token设置保持一致,区分大小写。
protected void Page_Load(object sender, EventArgs e)
{
Valid();
}
private void Valid()
{
string echoStr = Request.QueryString["echoStr"].ToString();//echoStr微信传递的字符串
WriteLogs("parmlog", "echoStr", echoStr);
if (CheckSignature())
{
if (!string.IsNullOrEmpty(echoStr))
{
Response.Write(echoStr);
Response.End();
}
}
}
private bool CheckSignature()
{//微信服务端会生成signature、timestamp,nonce三个参数,通过我们本地的Token和timestamp 、nonce 进行加密,生成的结果和signature(微信也是这样生成)是否一样
string signature = Request.QueryString["signature"].ToString();
string timestamp = Request.QueryString["timestamp"].ToString();
string nonce = Request.QueryString["nonce"].ToString();
string[] ArrTmp = { Token, timestamp, nonce };
WriteLogs("parmlog", "signature--timestamp", signature.ToString() + "***" + timestamp);
Array.Sort(ArrTmp); //字典排序
string tmpStr = string.Join("", ArrTmp);
tmpStr = FormsAuthentication.HashPasswordForStoringInConfigFile(tmpStr, "SHA1");
tmpStr = tmpStr.ToLower();
if (tmpStr == signature)
{
return true;
}
else
{
return false;
}
}
///
/// 日志部分
///
///
///
///
public static void WriteLogs(string fileName, string type, string content)
{
string path = AppDomain.CurrentDomain.BaseDirectory;
if (!string.IsNullOrEmpty(path))
{
path = AppDomain.CurrentDomain.BaseDirectory + fileName;
if (!Directory.Exists(path))
{
Directory.CreateDirectory(path);
}
path = path + "\\" + DateTime.Now.ToString("yyyyMMdd");
if (!Directory.Exists(path))
{
Directory.CreateDirectory(path);
}
path = path + "\\" + DateTime.Now.ToString("yyyyMMdd") + ".txt";
if (!File.Exists(path))
{
FileStream fs = File.Create(path);
fs.Close();
}
if (File.Exists(path))
{
StreamWriter sw = new StreamWriter(path, true, System.Text.Encoding.Default);
sw.WriteLine(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff") + type + "-->" + content);
// sw.WriteLine("----------------------------------------");
sw.Close();
}
}
}
}
}
代码完成以后,在服务器上发布验证网站,设置用域名访问,回到微信公众号服务器配置下,配置改地址,点击提交,成功表示通过验证,否则失败,失败原因可以通过微信端调试工具查看原因,《微信web开发工具》。
2、完成服务器验证以后,进入正式开发阶段,为了方便配置,我们将Appid,appsecret配置到web.config下
第一步获取关注者用户信息
namespace WeiXinWeb.ZCWeChat.sqsb
{
public partial class CaseRegiestPage : System.Web.UI.Page
{
public string openid = "2121212";
//公众号信息部分
public string appid = ConfigurationManager.AppSettings["AppId"];
public string appsecret = ConfigurationManager.AppSettings["AppSecret"];
public string redirect_uri = HttpUtility.UrlEncode("http://域名/ZCWeChat/sqsb/CaseRegiestPage.aspx");
public string scope = "snsapi_base";//以snsapi_userinfo为scope发起的网页授权,是用来获取用户的基本信息的。但这种授权需要用户手动同意,并且由于用户同意过,所以无须关注,就可在授权后获取该用户的基本信息。
public string accesstoken = "";
public static string access_token = "";//票据所需的token
public string nickname;
public string sex;
public string headimgurl;
public string province;
public string country;
public string language;
public string city;
protected void Page_Load(object sender, EventArgs e)
{
string code = Request.QueryString["code"].ToString();
if (string.IsNullOrEmpty(code))
{
//如果code没获取成功,重新拉取一遍
OpenAccess();
}
GetWXConfig();
WriteLogs("access_token", "access_token====", access_token);
GetAuthriseAccess_Token(code);
}
///
/// 获取网页授权用户信息
///
public void GetAuthriseAccess_Token(string code)
{
string UserInfo = "";
string html = string.Empty;
string url = "https://api.weixin.qq.com/sns/oauth2/access_token?appid=" + appid + "&secret=" + appsecret + "&code=" + code + "&grant_type=authorization_code";
HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(url);
request.Method = "GET";
HttpWebResponse response = request.GetResponse() as HttpWebResponse;
Stream ioStream = response.GetResponseStream();
StreamReader sr = new StreamReader(ioStream, Encoding.UTF8);
html = sr.ReadToEnd();
sr.Close();
ioStream.Close();
response.Close();
JObject outputObj = JObject.Parse(html);
openid = outputObj["openid"].ToString();
accesstoken = outputObj["access_token"].ToString();
// accesstoken = AccessTokenDAL.IsExistAccess_Token();
WriteLogs("用户get结果", "Useropen", html);
WriteLogs("openid", "signature--openid", code.ToString() + "***" + openid);
Session["openid"] = outputObj["openid"];
UserInfo = GetUserInfos();
JObject outputObj1 = JObject.Parse(UserInfo);
nickname = outputObj1["nickname"].ToString(); //昵称
sex = outputObj1["sex"].ToString(); //性别什么的
headimgurl = outputObj1["headimgurl"].ToString(); //头像url
province = outputObj1["province"].ToString(); ;
country = outputObj1["country"].ToString(); ;
language = outputObj1["language"].ToString(); ;
city = outputObj1["city"].ToString();
GetWXConfig();
WriteLogs("UserINfos", "nickname", nickname.ToString() + "***" + openid);
}
//获取微信客户端需要的配置文件参数
public string signature = "";
public string timestamp = "";
public string nonce = "";
public string jsapi_ticket = "";
public void GetWXConfig() {
string url = Request.Url.ToString();
jsapi_ticket = AccessTokenDAL.GetTicket();
timestamp = GetTimeStamp();
nonce = GenerateCheckCodeNum(16);
string[] ArrTmp = { timestamp, nonce };
// WriteLogs("urllog", "url====", url);
Array.Sort(ArrTmp); //字典排序
string tmpStr = string.Join("", ArrTmp);
string wxconfigStr = "jsapi_ticket=" + jsapi_ticket + "&noncestr=" + nonce + "×tamp=" + timestamp + "&url=" + url + "";
tmpStr = AccessTokenDAL.Sha1(wxconfigStr);
WriteLogs("wxconfigStr", "wxconfigStr====", wxconfigStr);
signature = tmpStr;
}
///
/// 获取时间戳
///
///
public static string GetTimeStamp()
{
TimeSpan ts = DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, 0);
return Convert.ToInt64(ts.TotalSeconds).ToString();
}
///
/// 生成随机数字字符串
///
/// 待生成的位数
/// 生成的数字字符串
///
private int rep = 0;
private string GenerateCheckCodeNum(int codeCount)
{
string str = string.Empty;
long num2 = DateTime.Now.Ticks + this.rep;
this.rep++;
Random random = new Random(((int)(((ulong)num2) & 0xffffffffL)) | ((int)(num2 >> this.rep)));
for (int i = 0; i < codeCount; i++)
{
int num = random.Next();
str = str + ((char)(0x30 + ((ushort)(num % 10)))).ToString();
}
return str;
}
public string GetUserInfos()
{
string UserURL = string.Format("https://api.weixin.qq.com/sns/userinfo?access_token={0}&openid={1}&lang=zh_CN", accesstoken, openid);
string html = string.Empty;
HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(UserURL);
request.Method = "GET";
HttpWebResponse response = request.GetResponse() as HttpWebResponse;
Stream ioStream = response.GetResponseStream();
StreamReader sr = new StreamReader(ioStream, Encoding.UTF8);
html = sr.ReadToEnd();
WriteLogs("userlog", "userlog====", html);
sr.Close();
ioStream.Close();
response.Close();
return html;
}
public void OpenAccess()
{
//判断session不存在
if (Session["openid"] == null)
{
//认证第一步:重定向跳转至认证网址
string url = string.Format("https://open.weixin.qq.com/connect/oauth2/authorize?appid={0}&redirect_uri={1}&&response_type=code&scope=snsapi_userinfo&m=oauth2#wechat_redirect", appid, redirect_uri);
Response.Redirect(url);
}
//判断session存在
else
{
//跳转到前端页面.aspx
Response.Redirect(Request.Url.ToString());
}
}
///
/// 日志部分
///
///
///
///
public static void WriteLogs(string fileName, string type, string content)
{
string path = AppDomain.CurrentDomain.BaseDirectory;
if (!string.IsNullOrEmpty(path))
{
path = AppDomain.CurrentDomain.BaseDirectory + fileName;
if (!Directory.Exists(path))
{
Directory.CreateDirectory(path);
}
path = path + "\\" + DateTime.Now.ToString("yyyyMMdd");
if (!Directory.Exists(path))
{
Directory.CreateDirectory(path);
}
path = path + "\\" + DateTime.Now.ToString("yyyyMMdd") + ".txt";
if (!File.Exists(path))
{
FileStream fs = File.Create(path);
fs.Close();
}
if (File.Exists(path))
{
StreamWriter sw = new StreamWriter(path, true, System.Text.Encoding.Default);
sw.WriteLine(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff") + type + "-->" + content);
// sw.WriteLine("----------------------------------------");
sw.Close();
}
}
}
}
}
注:通过上面代码可以获取关注者的昵称、省市,头像的微信基本信息。```
由于获取用户信息时需要先获取Access_Token,以及他的有效期expires_in,同时也有一定次数限制,客户端的每个动作都会去访问服务器,导致Access_Token一直是变化的,会出现无效token,因此需要将Access_Token,expires_in存储起来,进行比对,如果超过时限,再去服务端获取新的Access_Token,expires_in同时替换原来的,具体代码如下
```
namespace WeiXinWeb.BaseManager.DALClass
{
public class AccessTokenDAL
{
///
/// 根据当前日期 判断Access_Token 是否超期 如果超期返回新的Access_Token 否则返回之前的Access_Token
///
///
///
public static string IsExistAccess_Token()
{
string Token = string.Empty;
DateTime YouXRQ;
// 读取XML文件中的数据,并显示出来 ,注意文件路径
string filepath = HttpContext.Current.Server.MapPath("../") + "\\Access_token.xml";
StreamReader str = new StreamReader(filepath, Encoding.UTF8);
XmlDocument xml = new XmlDocument();
xml.Load(str);
str.Close();
str.Dispose();
Token = xml.SelectSingleNode("xml").SelectSingleNode("Access_Token").InnerText;
YouXRQ = Convert.ToDateTime(xml.SelectSingleNode("xml").SelectSingleNode("Access_YouXRQ").InnerText);
//TimeSpan st1 = new TimeSpan(YouXRQ.Ticks); //最后刷新的时间
//TimeSpan st2 = new TimeSpan(DateTime.Now.Ticks); //当前时间
//TimeSpan st = st2 - st1; //两者相差时间
if (DateTime.Now > YouXRQ)
{
DateTime _youxrq = DateTime.Now;
Access_token mode = GetAccess_token();
xml.SelectSingleNode("xml").SelectSingleNode("Access_Token").InnerText = mode.access_token;
_youxrq = _youxrq.AddSeconds(int.Parse(mode.expires_in));
xml.SelectSingleNode("xml").SelectSingleNode("Access_YouXRQ").InnerText = _youxrq.ToString();
xml.Save(filepath);
Token = mode.access_token;
}
CaseRegiestPage.access_token = Token;
return Token;
}
///
/// 获取Access_token
///
///
private static Access_token GetAccess_token()
{
string appid = ConfigurationManager.AppSettings["AppId"];//微信公众号appid
string secret = ConfigurationManager.AppSettings["AppSecret"]; //微信公众号appsecret
string strUrl = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=" + appid + "&secret=" + secret;
Access_token mode = new Access_token();
HttpWebRequest req = (HttpWebRequest)HttpWebRequest.Create(strUrl); //用GET形式请求指定的地址
req.Method = "GET";
using (WebResponse wr = req.GetResponse())
{
//HttpWebResponse myResponse = (HttpWebResponse)req.GetResponse();
StreamReader reader = new StreamReader(wr.GetResponseStream(), Encoding.UTF8);
string content = reader.ReadToEnd();
reader.Close();
reader.Dispose();
//在这里对Access_token 赋值
Access_token token = new Access_token();
token = JsonHelper.ParseFromJson(content);
mode.access_token = token.access_token;
mode.expires_in = token.expires_in;
}
return mode;
}
///
/// 基于Sha1的自定义加密字符串方法:输入一个字符串,返回一个由40个字符组成的十六进制的哈希散列(字符串)。
///
/// 要加密的字符串
/// 加密后的十六进制的哈希散列(字符串)
public static string Sha1(string str)
{
var buffer = Encoding.UTF8.GetBytes(str);
var data = SHA1.Create().ComputeHash(buffer);
var sb = new StringBuilder();
foreach (var t in data)
{
sb.Append(t.ToString("x2"));
}
return sb.ToString();
}
///
/// 获取js票据
///
///
///
public static string GetTicket() {
string token = IsExistAccess_Token();
string jsapi_ticket = "";//唯一凭证
string jsurl = "https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=" + token + "&type=jsapi";
HttpWebRequest req = (HttpWebRequest)HttpWebRequest.Create(jsurl); //用GET形式请求指定的地址
req.Method = "GET";
using (WebResponse wr = req.GetResponse())
{
//HttpWebResponse myResponse = (HttpWebResponse)req.GetResponse();
StreamReader reader = new StreamReader(wr.GetResponseStream(), Encoding.UTF8);
string content = reader.ReadToEnd();
//jsapi_ticket = content;
reader.Close();
reader.Dispose();
JObject outputObj = JObject.Parse(content);
jsapi_ticket = outputObj["ticket"].ToString();
}
WriteLogs("piaoju","ticket",jsapi_ticket);
return jsapi_ticket;
}
///
/// 日志部分
///
///
///
///
public static void WriteLogs(string fileName, string type, string content)
{
string path = AppDomain.CurrentDomain.BaseDirectory;
if (!string.IsNullOrEmpty(path))
{
path = AppDomain.CurrentDomain.BaseDirectory + fileName;
if (!Directory.Exists(path))
{
Directory.CreateDirectory(path);
}
path = path + "\\" + DateTime.Now.ToString("yyyyMMdd");
if (!Directory.Exists(path))
{
Directory.CreateDirectory(path);
}
path = path + "\\" + DateTime.Now.ToString("yyyyMMdd") + ".txt";
if (!File.Exists(path))
{
FileStream fs = File.Create(path);
fs.Close();
}
if (File.Exists(path))
{
StreamWriter sw = new StreamWriter(path, true, System.Text.Encoding.Default);
sw.WriteLine(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff") + type + "-->" + content);
// sw.WriteLine("----------------------------------------");
sw.Close();
}
}
}
}
}
```我的存储方式是将这两个值存在文件中
15_6fpcNNpRI8NIQz1BdV6UF4-5uEhP93yaUju50U3YH8mK3_XgHYJLP_eq1RHsxe60HMDUjfS89AVy5KXOrAz_bhDEiTl2OyetAiWn_T9lLqXUC5lcutpm0bKpLzUBTKjAJAHZQ
2018/11/23 20:37:51
以上代码基本实现微信公众号的验证和用户信息的获取,我们将代码发布到域名下,下面进行白名单配置,客户端要访问Access_Token需要将服务端的IP设置为白名单,具体如下
多个ip换行即可(enter键)
接下来我们需要配置自己的菜单,用来在客户端进行访问
点击 微信公众号 左侧主菜单 “自定义菜单”项目,添加菜单即可
添加域名下的网址,如果需要获取用户信息,网址需要带上验证参数,如下
https://open.weixin.qq.com/connect/oauth2/authorize?appid=appid&redirect_uri=http://域名/ZCWeChat/sqsb/CaseRegiestPage.aspx&response_type=code&scope=snsapi_userinfo&m=oauth2&connect_redirect=1#wechat_redirect
即可获得昵称 头像等信息。
接下来需要在微信公众号接口权限下配置域名访问权限,具体如下
接口权限下进行配置,点击修改

设置以上三个域名地址,完成域名设置。