三方web应用对接公众号流程

67 阅读3分钟

三方web应用对接公众号流程

1.申请申请微信公众号 测试平台

平台链接微信公众平台

image.png 填写对应信息,我们在测试平台单数url是要公网的,这个是时候可以内网穿透 我们的服务推荐natapp 不推荐ngrok 填写完对于信息之后,我们就可以体验公众号提供的接口了!!

2.后端要提供给公众号接口验证

image.png 这里我以c#代码为例子

 public string VerifyWxConfig(string signature, string timestamp, string nonce, string echostr)
 {
     if (CheckSignature(signature, timestamp, nonce))
     {
         return echostr;
     }
     return string.Empty;
 }

 private bool CheckSignature(string signature, string timestamp, string nonce)
 {
     // 将token、timestamp、nonce三个参数进行字典序排序
     string[] arr = new[] {Config.MyConfig.token, timestamp, nonce }.OrderBy(x => x).ToArray();
     
     // 将三个参数字符串拼接成一个字符串
     string tempStr = string.Join("", arr);
     
     // 进行sha1加密
     using (var sha1 = SHA1.Create())
     {
         var hashBytes = sha1.ComputeHash(Encoding.UTF8.GetBytes(tempStr));
         var tempSignature = BitConverter.ToString(hashBytes).Replace("-", "").ToLower();
         
         // 返回验证结果
         return tempSignature == signature;
     }
 }

参数配置项 建议定义一个config文件

image.png

3.条用公众号接口

前端请求

 let origin = window.location.origin;
 let config = null;

 (async function init() {
     // 1. 加载配置
     await getConfig();
     // 2. 执行后续逻辑
     await getInfo();
     // 3. 其他初始化操作
     console.log('初始化完成', config);
 })();

 let getInfo = () => {
     // 是否为回调页面
     let paramsObj = getUrlParams()
     console.log('paramsObj', paramsObj);

     // 防止刷新页面时,数据丢失,故需要回填数据
     let name = window.localStorage.getItem("nickname")
     let openid = window.localStorage.getItem("openid")

     if (paramsObj && paramsObj.code) {
         // 如果有code参数,说明是微信回调,需要获取用户信息
         getUserInfoByCode(paramsObj.code)
     } else if (name && openid) {
         // 如果本地有用户信息,直接使用
         config.openid = openid;
         // 获取我的其他资料,图片等等
         getUserInfo(openid)
     } else {
         // 初次页面,需要调起授权页面
         let backUrl = encodeURIComponent(config.redirectUrl)
         let url = `https://open.weixin.qq.com/connect/oauth2/authorize?appid=${config.appId}&redirect_uri=${backUrl}&response_type=code&scope=snsapi_userinfo&state=STATE#wechat_redirect`
         window.location.href = url;
     }
 }

 // 获取配置信息
 async function getConfig() {
     try {
         const response = await axios.get(`${origin}/open/getConfig`, {
             headers: {
                 'Cache-Control': 'no-cache'
             },
             timeout: 5000
         });

         console.log('配置获取成功:', response.data);
         config = response.data;
         return config;
     } catch (error) {
         console.error('配置获取失败:', error);
         throw new Error('系统配置加载失败');
     }
 }

 // 获取用户数据
 async function getUserInfo(openid) {
     try {
         const response = await axios.post(`${origin}/open/getUserInfoByOpenid`,
             { openid },
             {
                 headers: {
                     'Content-Type': 'application/json',
                     'Authorization': `Bearer ${config.token}`
                 }
             }
         );
         console.log('用户信息获取成功:', response.data);
         return response.data;
     } catch (error) {
         console.error('用户信息获取失败:', error);
         throw new Error('用户信息获取失败');
     }
 }

 // 通过code获取用户信息
 async function getUserInfoByCode(code) {
     try {
         const response = await axios.get(`${origin}/open/getUserInfo?code=${code}`);
         console.log('通过code获取用户信息成功:', response.data);

         // 保存用户信息到本地存储
         window.localStorage.setItem('nickname', response.data.nickname);
         window.localStorage.setItem('openid', response.data.openid);
         window.localStorage.setItem('aiUrl', response.data.aiUrl);

         // 设置到config中
         config.openid = response.data.openid;

         // 获取其他用户资料
         await getUserInfo(response.data.openid);

         // 清除URL中的code参数,防止刷新时重复获取
         window.history.replaceState({}, document.title, window.location.pathname);

         // 显示提示信息
         showToast(response.data.aiUrl ? `欢迎回来,${response.data.nickname}` : '您暂无权限使用AI助手,请申请权限');

         // 直接使用返回的aiUrl加载聊天机器人
         loadChatBot(response.data.aiUrl);

     } catch (error) {
         console.error('通过code获取用户信息失败:', error);
         throw new Error('获取用户信息失败');
     }
 }

后端代码

        /// <summary>
        /// 获取微信授权URL
        /// </summary>
        /// <param name="state">状态参数</param>
        /// <returns>授权URL</returns>
        public string getAuthorizeUrl(string state = "STATE")
        {
            string redirectUri = System.Web.HttpUtility.UrlEncode(Config.MyConfig.redirectUrl);
            return $"https://open.weixin.qq.com/connect/oauth2/authorize?appid={Config.MyConfig.appId}&redirect_uri={redirectUri}&response_type=code&scope=snsapi_userinfo&state={state}#wechat_redirect";
        }

        /// <summary>
        /// 获取配置信息
        /// </summary>
        /// <returns>配置信息</returns>
        public string getConfig()
        {
            return JsonSerializer.Serialize(Config.MyConfig);
        }

        /// <summary>
        /// 获取用户信息
        /// </summary>
        /// <param name="code">微信授权code</param>
        /// <returns>用户信息</returns>
        public async Task<WxUserInfo> GetUserInfoAsync(string code)
        {
            try
            {
                // 1. 通过code获取access_token和openid
                string url = $"https://api.weixin.qq.com/sns/oauth2/access_token?appid={Config.MyConfig.appId}&secret={Config.MyConfig.appSecret}&code={code}&grant_type=authorization_code";
                var response = await _httpClient.GetAsync(url);
                response.EnsureSuccessStatusCode();

                var result = await response.Content.ReadAsStringAsync();
                var tokenResponse = JsonSerializer.Deserialize<WxOAuthResponse>(result);
                Console.WriteLine($"获取access_token成功:{tokenResponse?.AccessToken}");

                if (tokenResponse?.ErrCode != 0)
                {
                    Console.WriteLine($"获取access_token失败:{tokenResponse?.ErrMsg}");
                    return null;
                }

                // 2. 通过access_token和openid获取用户信息
                string userInfoUrl = $"https://api.weixin.qq.com/sns/userinfo?access_token={tokenResponse.AccessToken}&openid={tokenResponse.OpenId}&lang=zh_CN";
                var userInfoResponse = await _httpClient.GetAsync(userInfoUrl);
                userInfoResponse.EnsureSuccessStatusCode();

                var userInfoResult = await userInfoResponse.Content.ReadAsStringAsync();
                var userInfo = JsonSerializer.Deserialize<WxUserInfo>(userInfoResult);

                if (userInfo?.ErrCode != 0)
                {
                    Console.WriteLine($"获取用户信息失败:{userInfo?.ErrMsg}");
                    return null;
                }
                Console.WriteLine($"获取用户信息成功:{userInfo}");
                if (Config.MyConfig.idList.Contains(userInfo?.OpenId))
                {
                    byte[] plainTextBytes = Encoding.UTF8.GetBytes(Config.MyConfig.AIurl);
                    string base64String = Convert.ToBase64String(plainTextBytes);
                    userInfo.AIurl = base64String;
                }
                return userInfo;
            }
            catch (Exception ex)
            {
                Console.WriteLine($"获取用户信息异常:{ex.Message}");
                return null;
            }
        }

        /// <summary>
        /// 通过openid获取用户信息
        /// </summary>
        public async Task<WxUserInfo> GetUserInfoByOpenidAsync(string openid)
        {
            try
            {
                // 1. 获取全局access_token
                string accessToken = await GetAccessTokenAsync();
                if (string.IsNullOrEmpty(accessToken))
                {
                    return null;
                }

                // 2. 通过access_token和openid获取用户信息
                string userInfoUrl = $"https://api.weixin.qq.com/cgi-bin/user/info?access_token={accessToken}&openid={openid}&lang=zh_CN";
                var userInfoResponse = await _httpClient.GetAsync(userInfoUrl);
                userInfoResponse.EnsureSuccessStatusCode();

                var userInfoResult = await userInfoResponse.Content.ReadAsStringAsync();
                var userInfo = JsonSerializer.Deserialize<WxUserInfo>(userInfoResult);

                if (userInfo?.ErrCode != 0)
                {
                    Console.WriteLine($"获取用户信息失败:{userInfo?.ErrMsg}");
                    return null;
                }
                Console.WriteLine($"获取用户信息成功:{userInfo}");
                if (Config.MyConfig.idList.Contains(userInfo?.OpenId))
                {
                    byte[] plainTextBytes = Encoding.UTF8.GetBytes(Config.MyConfig.AIurl);
                    string base64String = Convert.ToBase64String(plainTextBytes);
                    userInfo.AIurl = base64String;
                }
                return userInfo;
            }
            catch (Exception ex)
            {
                Console.WriteLine($"获取用户信息异常:{ex.Message}");
                return null;
            }
        }

        /// <summary>
        /// 获取全局access_token
        /// </summary>
        private async Task<string> GetAccessTokenAsync()
        {
            try
            {
                // 如果access_token未过期,直接返回
                if (!string.IsNullOrEmpty(_accessToken) && DateTime.Now < _tokenExpireTime)
                {
                    return _accessToken;
                }

                // 获取新的access_token
                string url = $"https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid={Config.MyConfig.appId}&secret={Config.MyConfig.appSecret}";
                var response = await _httpClient.GetAsync(url);
                response.EnsureSuccessStatusCode();

                var result = await response.Content.ReadAsStringAsync();
                var tokenResponse = JsonSerializer.Deserialize<WxTokenResponse>(result);

                if (tokenResponse?.ErrCode != 0)
                {
                    Console.WriteLine($"获取access_token失败:{tokenResponse?.ErrMsg}");
                    return null;
                }

                _accessToken = tokenResponse.AccessToken;
                _tokenExpireTime = DateTime.Now.AddSeconds(tokenResponse.ExpiresIn - 10); // 提前10秒过期
                return _accessToken;
            }
            catch (Exception ex)
            {
                Console.WriteLine($"获取access_token异常:{ex.Message}");
                return null;
            }
        }

调试是否获取到我们需要下载微信开发工具 这里更换开发模式,选择公众号网页

image.png 测试完之后 部署上线,我要更改config设置 换成正式环境的域名

image.png 查看微信接口权限

image.png 获取用户信息 推送消息等接口都需要微信认证,也是在设置与开发 下面,微信认证需要企业资质,费用大概300/年。