access_token 為全域唯一憑證,有效期 **7200 秒 (2 小時)**。表 SWX_Config 記錄每個公眾號的 Token 與時間戳:
CREATE TABLE SWX_Config (
UserName NVARCHAR(50) PRIMARY KEY, -- 管理員帳號
AppID NVARCHAR(50) NOT NULL,
APPSecret NVARCHAR(100) NOT NULL,
AccessToken NVARCHAR(500),
TokenExpires DATETIME -- 可選:記錄過期時間,減少探測請求
);
HttpWebRequest GET/POST、檔案上傳。public static class HttpRequestUtil
{
public static string RequestUrl(string url, string method = "POST")
{
var req = (HttpWebRequest)WebRequest.Create(url);
req.Method = method;
req.ContentType = "text/html";
req.Headers.Add("charset", "utf-8");
req.CookieContainer = new CookieContainer();
req.AllowAutoRedirect = true;
using (var resp = (HttpWebResponse)req.GetResponse())
using (var stream = resp.GetResponseStream())
using (var reader = new StreamReader(stream, Encoding.UTF8))
return reader.ReadToEnd();
}
public static string PostUrl(string url, string postData)
{
byte[] data = Encoding.UTF8.GetBytes(postData);
var req = (HttpWebRequest)WebRequest.Create(url);
req.Method = "POST";
req.ContentType = "application/x-www-form-urlencoded";
req.ContentLength = data.Length;
req.CookieContainer = new CookieContainer();
using (var outStream = req.GetRequestStream())
outStream.Write(data, 0, data.Length);
using (var resp = (HttpWebResponse)req.GetResponse())
using (var stream = resp.GetResponseStream())
using (var reader = new StreamReader(stream, Encoding.UTF8))
return reader.ReadToEnd();
}
}
public static class Tools
{
public static string GetJsonValue(string json, string key)
{
if (string.IsNullOrEmpty(json)) return string.Empty;
key = $"\"{key.Trim('\"')}\"";
int idx = json.IndexOf(key);
if (idx < 0) return string.Empty;
idx += key.Length + 1; // skip colon and space
int end = json.IndexOf(',', idx);
if (end < 0) end = json.IndexOf('}', idx);
if (end < 0) return string.Empty;
return json.Substring(idx, end - idx).Trim('"', ' ', '\'');
}
}
public static class WXApi
{
public static bool TokenExpired(string accessToken)
{
string url = $"https://api.weixin.qq.com/cgi-bin/menu/get?access_token={accessToken}";
string json = HttpRequestUtil.RequestUrl(url);
return Tools.GetJsonValue(json, "errcode") == "42001";
}
public static string GetToken(string appId, string secret)
{
string url = $"https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid={appId}&secret={secret}";
string json = HttpRequestUtil.RequestUrl(url);
return Tools.GetJsonValue(json, "access_token");
}
}
public static class AdminUtil
{
public static string GetAccessToken(PageBase page)
{
var user = GetLoginUser(page);
if (user == null) return string.Empty;
string token = user.AccessToken;
if (string.IsNullOrWhiteSpace(token) || WXApi.TokenExpired(token))
{
token = WXApi.GetToken(user.AppID, user.APPSecret);
// 持久化到資料庫
string sql = $"UPDATE SWX_Config SET AccessToken='{token}' WHERE UserName='{user.UserName}'";
MSSQLHelper.ExecuteSql(sql);
}
return token;
}
}
static ConcurrentDictionary + 資料庫,減少 DB 讀寫。Timer 或 HostedService,在過期前 5 分鐘主動刷新,完全避免請求阻塞。本方案以「檢測過期 → 重新獲取 → 持久化」為核心邏輯,配合輕量 HTTP/JSON 工具,零第三方依賴即可穩定支撐微信公眾號高頻介面調用。實際上線後建議加入上述進階優化,進一步提升可用性與擴展性。