asp.net 之微信自定义菜单创建
前段时间由于公司需要,申请了大批量的微信公众账号,并要求每个公众账号都需要创建自定义菜单,由于数量相当庞大,单个创建简直是个巨大的工作量,于是我开动脑筋,搞了一个自义定菜单管理后台,实现一键添加、删除自定义菜单:
- 管理后台是基于asp.net+MVC+dwz来写,至于什么是dwz自己百度,很简单也很实用的国产开源框架
-公众账号列表页面:
<div class="pageContent">
<div class="panelBar">
<ul class="toolBar">
<li><a class="icon" href="#" onclick="GetID()"><span>创建菜单</span></a></li>
<li><a class="delete" href="@Url.Content("~/WeiXin/DelMenu")" target="selectedTodo" title="确定删除该账号的自定义菜单吗?">
<span>删除菜单</span></a></li>
</ul>
</div>
<table class="table" width="100%" layouth="135">
<thead>
<tr>
<th width="2%">
<input type="checkbox" class="checkboxCtrl" group="ids" />
</th>
<th width="5%">
序号
</th>
<th width="auto">
区域名称
</th>
<th width="auto">
账号
</th>
<th width="10%">
密码
</th>
<th width="5%">
AppID
</th>
<th width="5%">
AppSecret
</th>
<th width="8%">
是否认证
</th>
<th width="10%">
是否创建菜单
</th>
<th width="8%">
查看菜单结构
</th>
<th width="10%">
开通时间
</th>
</tr>
</thead>
<tbody>
@{
if (dt != null)
{
foreach (DataRow Dr in dt.Rows)
{
<tr target="ID" rel="@Dr["ID"].ToString()">
<td>
@if (Convert.ToInt32(Dr["CerTification"]) == 1)
{
<input type="checkbox" name="ids" value="@Dr["ID"]"/>
}
else
{
<input type="checkbox" name="ids" disabled="disabled" value="-1" />
}
</td>
<td align="center">@Dr["RowNumber"]
</td>
<td align="center">@Dr["AccountName"]
</td>
<td align="center">@Dr["Account"]
</td>
<td align="center">@Dr["PassWord"]
</td>
<td align="center">@Dr["AppID"]
</td>
<td align="center">@Dr["AppSecret"]
</td>
<td align="center">
@if (Convert.ToInt32(Dr["CerTification"]) == 1)
{
<a class="btnSelect" title="已认证"></a>
}
else
{
<a class="btnDel" title="未认证"></a>
}
</td>
<td align="center">
@if (Convert.ToInt32(Dr["Status"]) == 2)
{
<a class="btnSelect" title="已创建"></a>
}
else
{
<a class="btnDel" title="未创建"></a>
}
</td>
<td>
<a class="btnLook" href="@Url.Content("~/WeiXin/FindMenu?ID=" + Dr["ID"])" target="dialog" mask="true" width="800" height="600" title="查看菜单结构" >
</a>
</td>
<td align="center">@Dr["AddTime"]
</td>
</tr>
}
}
}
</tbody>
</table>
<div class="panelBar">
<div class="pages">
<span>每页显示: @ViewBag.fTable.NumPerPage 条, </span><span>共 @ViewBag.fTable.RecordCount
条</span>
</div>
<div class="pagination" targetType="navTab" totalCount="@ViewBag.fTable.RecordCount" numPerPage="@ViewBag.fTable.NumPerPage" pageNumShown="10" currentPage="@ViewBag.fTable.PageNum">
</div>
</div>
</div>
-公众号列表为了展示所有需要创建自定义菜单的公众账号,每条数据都加了复选框,允许多个账号同时创建菜单
创建菜单按钮事件控制器:
public ActionResult CreateMenu(string s1 = "")
{
DWZJson djson = null;
if (string.IsNullOrWhiteSpace(s1))
{
djson = new DWZJson() { message = "请选择要创建菜单的公众号", statusCode = "300" };
return Content(djson.ToJsonString());
}
//string[] ids = s1.Split(new char[1] { ',' }, StringSplitOptions.RemoveEmptyEntries);
//获取选中的公众账号名称
DataTable dt_accout = DALWeiXin.GetAccountList(s1);
StringBuilder sb = new StringBuilder();
if (dt_accout != null && dt_accout.Rows.Count > 0)
{
for (int i = 0; i < dt_accout.Rows.Count; i++)
{
sb.AppendFormat("<li class='{0}' style=\"display: inline; margin-right: 10px;\"><strong>☑ {1}</strong></li>", dt_accout.Rows[i]["ID"], HttpUtility.HtmlDecode(Convert.ToString(dt_accout.Rows[i]["AccountName"])));
}
}
ViewData["AccountList"] = sb.ToString();
//所有菜单列表
DataTable dt = DALWeiXin.getMenuList();
ViewBag.Table = dt;
ViewBag.accountID = s1;//对应公众账号id
return View();
}
选择之后弹出菜单选择窗口,菜单以树形结构分两级展示,根据直观
弹窗页面:
@using System.Data;
@{
ViewBag.Title = "CreateMenu";
DataTable dt = ViewBag.Table;//菜单集合
}
<div class="pageContent">
<form id="Menuform" method="post" action="@Url.Content("~/WeiXin/CreateMenuSubmit")" enctype="multipart/form-data" class="pageForm required-validate"
onsubmit="return iframeCallback(this,dialogAjaxDone);">
<input type="hidden" name="ids" id="ids" value="@ViewBag.accountID" />
<div class="pageFormContent" layouth="56" style="overflow: auto;">
<div style="width: 640px; border: 1px solid rgb(204, 204, 204); line-height: 21px;
background: none repeat scroll 0% 0% rgb(255, 255, 255); height: auto;">
<ul style="margin: 15px 15px 15px; list-style: circle;">
@Html.Raw(Convert.ToString(ViewData["AccountList"]))
</ul>
</div>
<div style="float: left; display: block; overflow: auto; width: 640px; border: 1px solid rgb(204, 204, 204);
line-height: 21px; background: none repeat scroll 0% 0% rgb(255, 255, 255); height: auto;
margin-top: 20px;">
<ul class="tree treeFolder treeCheck expand">
@if (dt != null && dt.Rows.Count > 0)
{
for (var i = 0; i < dt.Rows.Count; i++)
{
DataRow[] foundRow = dt.Select("PID=" + dt.Rows[i]["MID"]);
if (Convert.ToInt32(dt.Rows[i]["PID"]) == -1 && foundRow.Length == 0)
{
<li><a tname="PMenu" tvalue="@dt.Rows[i]["MID"]">@dt.Rows[i]["MName"]@Html.Raw("(一级)")</a></li>
}
else if (Convert.ToInt32(dt.Rows[i]["PID"]) == -1 && foundRow.Length > 0)
{
<li><a tname="PMenu" tvalue="@dt.Rows[i]["MID"]">@dt.Rows[i]["MName"]@Html.Raw("(一级)")</a><ul>
@for (var j = 0; j < foundRow.Length; j++)
{
<li><a tname="CMenu" tvalue="@foundRow[j]["MID"]">@foundRow[j]["MName"]@Html.Raw("(二级)")</a>
</li>
}
</ul>
</li>
}
}
}
</ul>
</div>
</div>
<div class="formBar">
<ul>
<li>
<div class="buttonActive">
<div class="buttonContent">
<button type="submit">
提交</button></div>
</div>
</li>
<li>
<div class="button">
<div class="buttonContent">
<button type="button" class="close">
取消</button></div>
</div>
</li>
</ul>
</div>
</form>
</div>
选择对应菜单之后提交事件控制器
public ActionResult CreateMenuSubmit(FormCollection collection)
{
DWZJson djson = null;
var pmid = collection["PMenu"] == null ? "" : collection["PMenu"];//父级菜单id
var cmid = collection["CMenu"] == null ? "-1" : collection["CMenu"];//子级菜单id
var account = collection["ids"];//创建菜单所属对象(公众号)
if (string.IsNullOrWhiteSpace(pmid) && cmid == "-1")
{
djson = new DWZJson() { message = "请选择菜单", statusCode = "300" };
return Content(djson.ToJsonString());
}
//根据菜单编号查询所有菜单
DataTable dt = DALWeiXin.GetMenu(pmid, cmid);
if (dt != null && dt.Rows.Count > 0)
{
try
{
//构建拼接字符串
StringBuilder sb = new StringBuilder();
//begin create menu
sb.Append("{'button':[");
for (int i = 0; i < dt.Rows.Count; i++)
{
DataRow[] foundRow = dt.Select("PID=" + dt.Rows[i]["MID"], "Seq DESC");
//设置按钮类型,view: url 否则:key
var cont = Convert.ToString(dt.Rows[i]["MType"]) == "view" ? "url" : "key";
if (Convert.ToInt32(dt.Rows[i]["PID"]) == -1 && foundRow.Length == 0)
{
sb.Append("{");
sb.AppendFormat("'name':'{0}','type':'{1}','{3}':'{2}'", dt.Rows[i]["MName"], dt.Rows[i]["MType"], dt.Rows[i]["MContent"], cont);
sb.Append("},");
}
else if (Convert.ToInt32(dt.Rows[i]["PID"]) == -1 && foundRow.Length > 0)
{
sb.Append("{");
sb.AppendFormat("'name':'{0}','sub_button':[", dt.Rows[i]["MName"]);
for (int j = 0; j < foundRow.Length; j++)
{
cont = Convert.ToString(foundRow[j]["MType"]) == "view" ? "url" : "key";
sb.Append("{");
sb.AppendFormat("'name':'{0}','type':'{1}','{3}':'{2}'", foundRow[j]["MName"], foundRow[j]["MType"], foundRow[j]["MContent"], cont);
if (j == foundRow.Length - 1)
sb.Append("}");
else
sb.Append("},");
}
sb.Append("]},");
}
}
sb.Append("]}");
sb.Replace("'", "\"");
//获取appid appsecret 集合
DataTable Tab = DALWeiXin.GetAccountList(account);
if (Tab != null && Tab.Rows.Count > 0)
{
//消息提示,记录创建菜单返回结果
var ShowMsg = "";
//记录创建成功的公众账号,修改状态
var successAccount = "";
for (int t = 0; t < Tab.Rows.Count; t++)
{
//创建菜单
string ResStr = CreateMemu(GetToken(Convert.ToString(Tab.Rows[t]["AppID"]), Convert.ToString(Tab.Rows[t]["AppSecret"])), sb.ToString());
//解析微信后台处理之后的json字符串,提取状态值
NameValueCollection msg = ParseJson(ResStr);
if (msg["errcode"] == "0,")
{
//拼接已经创建成功的公众账号id
successAccount += Tab.Rows[t]["ID"] + ",";
}
//拼接每个公众账号创建菜单的返回值
ShowMsg += "☑" + Tab.Rows[t]["AccountName"] + ":" + msg["errmsg"];
}
//更改菜单创建成功的公众账号状态,记录该账号已经创建菜单 状态置为2
bool result = DALWeiXin.UpdateAccountStatus(successAccount, 2);
djson = new DWZJson() { message = ShowMsg, statusCode = "200" };
return Content(djson.ToJsonString());
}
else
{
djson = new DWZJson() { message = "系统异常", statusCode = "300" };
return Content(djson.ToJsonString());
}
}
catch (Exception ex)
{
djson = new DWZJson() { message = "系统异常" + ex.ToString(), statusCode = "300" };
return Content(djson.ToJsonString());
}
}
return Content(djson.ToJsonString());
}
/// <summary>
/// 创建自定义菜单
/// </summary>
/// <param name="weixin_token">token</param>
/// <param name="Json">json结构的菜单</param>
/// <returns></returns>
public static string CreateMemu(string weixin_token, string Json)
{
string CreateMenuUrl = "https://api.weixin.qq.com/cgi-bin/menu/create?access_token={0}";//创建自定义菜单链接
string url = string.Format(CreateMenuUrl, weixin_token);
return HttpPost(url, Json); //returnData 返回的是 {"errcode":0,"errmsg":"ok"} 此时,你取消服务号关注,在添加关注,菜单就生成了
}
/// <summary>
/// HTTP POST方式请求数据
/// </summary>
/// <param name="url">URL.</param>
/// <param name="param">POST的数据</param>
/// <returns></returns>
public static string HttpPost(string url, string param)
{
HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(url);
request.Method = "POST";
request.ContentType = "application/x-www-form-urlencoded";
request.Accept = "*/*";
request.Timeout = 15000;
request.AllowAutoRedirect = false;
StreamWriter requestStream = null;
WebResponse response = null;
string responseStr = null;
try
{
requestStream = new StreamWriter(request.GetRequestStream());
requestStream.Write(param);
requestStream.Close();
response = request.GetResponse();
if (response != null)
{
StreamReader reader = new StreamReader(response.GetResponseStream(), Encoding.UTF8);
responseStr = reader.ReadToEnd();
//File.WriteAllText(Server.MapPath("~/") + @"\test.txt", responseStr);
reader.Close();
}
}
catch (Exception)
{
throw;
}
finally
{
request = null;
requestStream = null;
response = null;
}
return responseStr;
}
/// <summary>
/// 获取access_token
/// </summary>
/// <returns></returns>
public static string GetToken(string AppID, string AppSecret)
{
string access_token = "";
string url = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=" + AppID + "&secret=" + AppSecret + "";//你成为开发者后会有一个appID和appSecret
string response = HttpGet(url);
JavaScriptSerializer json = new JavaScriptSerializer(); //实例化一个能够序列化数据的类
ToJson list = json.Deserialize<ToJson>(response); //将json数据转化为对象类型并赋值给list
access_token = list.access_token; //获取JSON里access_token值
return access_token;
}
-至于后台取数据的方法就不贴了,不同程序不同用法,以上有些代码可以删除,比喻修改状态云云,就不多说了。以上有不到位的欢迎指正。