ホームページ > WeChat アプレット > ミニプログラム開発 > WeChat H5 開発は openApi を呼び出します

WeChat H5 開発は openApi を呼び出します

Y2J
リリース: 2017-05-09 09:54:41
オリジナル
4233 人が閲覧しました

WeChat ハードウェア プラットフォームは、物と人、物と物を接続するために WeChat によって開始された IoT ソリューションです。つまり、WeChatを通じてさまざまなスマートデバイスを制御できるのです。たとえば、一部の Bluetooth デバイス、エアコン、テレビなどです。

私はハードウェアについてはあまり詳しくありません (電子情報を専攻していますが) ハードウェアは北杭大学の 2 人の大学院生によって開発されており、私は H5 カスタム パネルの開発を担当しています。 . 初めて公式ドキュメントを読んだときは混乱しました jssdk、jsapi、Airkiss、openApi、直接接続 SDK、どれを使えばいいのか分かりません 基本的に公式フォーラムで質問しても結果は出ません。私はいくつかの WeChat ハードウェア グループに参加して質問しましたが、多くの開発者が私と同じような質問を複数のグループに投稿しており、その様子はとても悲しかったです。 wxthings にメールを送って質問すると、返事が返ってくると嬉しいですが、たいていは一言だけです。散々文句を言いましたが、やはり問題は解決しなければなりません。結局のところ、WeChat に接続できるというデバイスの機能が大きなセールスポイントであることがわかりましたので、この記事を書きました。

1. アクセスプロセス

つまり、まずパブリックアカウントを取得し、次にデバイス機能を有効にして製品 (つまり、スマートデバイス) を追加する必要があります。これらのプロセスに関する公式文書は比較的明確なので、詳細には触れません。私たちが選択したアクセス ソリューションは、WeChat Hardware Cloud の標準アクセス ソリューションです。

設定パネル

私が話している H5 パネルの開発とは、WeChat で開かれる H5 コントロール ページ、WeChat ハードウェア クラウドとの通信方法、デバイスのステータスを読み取って設定する方法を指します。製品を追加する過程で、設定パネルがあります

標準パネルを選択した場合、WeChat は 3 種類の標準パネルを公式に提供します:

それぞれ、エアコン、スイッチ、照明です。カスタマイズされている場合は、アドレスを入力します。標準パネルの場合はサーバーは必要ありませんが、カスタマイズされたパネルの場合 独自のサーバーが必要です。そうしないと、WeChat クラウド から送信されたメッセージを処理できません。

サーバー構成を有効にする

サーバーアドレスを設定する場合、正常に有効にする前に、要件に従って応答を処理する必要があることに注意してください。

クリックして有効にすると、WeChat Cloud は署名、タイムスタンプ、乱数、およびランダムな文字列検証後にWeChat Cloudに返します。返されたランダムな文字列を取得すると、正常に有効にすることができます。たとえば、定義したアドレスが http://www.xxx.com/device/ReceiveWXMsg の場合、まずコードをサーバーにアップロードし、[WeChat Cloud がこのアドレスにデータを投稿する] をクリックします。 WeChat がサーバーにデータを送信するたびに、この検証が最初に送信されます (つまり、検証なしで返された場合、セキュリティの問題が発生します)

  public string ReceiveWXMsg()
        {            var signature = Request.QueryString["signature"];            var timestamp = Request.QueryString["timestamp"];            var echostr = Request.QueryString["echostr"];            var nonce = Request.QueryString["nonce"];
            Logger.Debug("signature:" + signature);
            Logger.Debug("timestamp:" + timestamp);
            Logger.Debug("nonce:" + nonce);
            Logger.Debug("echostr:" + echostr);            //验证 
            return echostr;
        }
ログイン後にコピー


コードを表示

json

などを追加せずに直接返すだけです。このアドレスは何のためにあるのでしょうか?

2. コミュニケーション方法

Wifi设备和蓝牙设备是不同的,蓝牙使用Airsync协议和微信通信,而wifi设备的话,我们在自己的服务器上调用微信提供的openApi获取或设置设备状态,微信服务器就会把数据以json格式post到我们上面设置的那个地址。硬件方面wifi设备可以使用微信提供的直连SDK来控制设备。

添加完设备,设置好服务器,你还需要硬件的同学打开设备,帮定你的微信。从微信的设置-->设备-->选择设备-->打开面板。你就可以看到设备并进行控制了。

三、调用openApi

说了这么多前提工作,终于进入调用api环节。可以先看一下openApi的官方文档:iot.weixin.qq.com/wiki/doc/hardwarecloud/openapi.pdf

文档里面主要讲了三个方法,一个是查询设备状态,一个是设置设备状态,一个是接受设备状态变化的消息,然后是一些错误信息等。但观察api就会发现我们还需要两个重要的参数,一个是access_token,一个是用户的openid。还说明一点,网页是Asp.net mvc

1.获取access_token

官方有一个接口调试页面:mp.weixin.qq.com/debug/ ,获取access_token需要appid和secret。

而这两个值,是在公众号后台的基本配置中查看,secret是个很重要的参数,所以请保密好。

查看密钥还需要扫二维码得到管理员的确认...  拿到这两个参数了,我们就可以生成token了。注意返回的json是一个token字符串再加一个超时时间。定义一个TokenResult:

public class TokenResult
    {        public string access_token { get; set; }        public int expires_in { get; set; }
    }
ログイン後にコピー

要注意的一点是,token两小时后会过期。所以在我们的代码里面需要检查是否超时,然后自动重新获取。

 public const string AccessTokenUrl = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid={0}&secret={1}";
ログイン後にコピー

getAccessToken方法定义在一个服务类中,比如WxDeviceService

using SendHelp= Senparc.Weixin.CommonAPIs.CommonJsonSend;public TokenResult GetAccessToken()
        {            var url = string.Format(WxDeviceConfig.AccessTokenUrl, WxDeviceConfig.AppId, WxDeviceConfig.APPSECRET);            var res = SendHelp.Send<TokenResult>(null, url, null, CommonJsonSendType.GET);            return res;
        }
ログイン後にコピー

使用Senparc.Weixin框架封装好的api来处理请求。

2.获取openid

这个参数在查询和设置设备状态的时候会用到,对应user参数名。获取openid需要三个参数,access_token已经有了,然后需要device_type和device_id

 public const string GetOpenid ="https://api.weixin.qq.com/device/get_openid?access_token={0}&device_type={1}&device_id={2}";
ログイン後にコピー

而type和id是前端页面传过来的,当用户微信中打开控制面板的时候,微信会自动将这两个参数追加到url后面。而这个url的返回值结构是这样:

{"open_id":["oxa1otw5sk-Azgd8mx1bmBqoM2_E","oxa1ot8-j9j5bYUJJyAexe9d41_Y","oxa1ot5QTdxn0xNQ0DmYzoN0tUp1"],"resp_msg":{"ret_code":0,"error_info":"ok"}}
ログイン後にコピー

openid部分是一个数组,实际使用的是第三个(我目前也不知道前面两个id是干啥的),定义一个openidResult:

   List<> List<> {  _openId??(_openId= List<> { _openId = resp_msg resp_msg { ;
ログイン後にコピー

service中:

  public string GetOpenId(string accessToken,string deviceType,string deviceId)
        {                var url = string.Format(WxDeviceConfig.GetOpenid, accessToken, deviceType, deviceId);                var res = SendHelp.Send<OpenIdResult>(accessToken, url, null, CommonJsonSendType.GET);                return res.GetOpenId();
        }
ログイン後にコピー

如果你的devicetype和deviceId错误,微信会返回一个不太恰当的错误json:

对比错误代码列表,我开始以为微信服务器出错了。其实是我参数填错了。

遇到的错误,将不止官方文档公布的这几个。

3.查询设备状态

查询设备和上面的这两个方法略有不同,因为流程是这的,我们的服务器先像向微信服务器请求,微信接受到请求后马上返回一个确认json,然后微信服务器会马上把数据post我们前面设置的那个地址上。

请求url:

      GetDeviceStatusUrl=
ログイン後にコピー

从官方文档可以看到,查询设备还需要另外一个重要的参数,它包含device_type和device_id,services,user,data。

需要说明一下的就是services,意思是指设备的能力项,也就是你要查询设备的哪些属性,这个在设置设备的时候一样用到。完成的产品能力定义请看:http://iot.weixin.qq.com/wiki/doc/intro/%E4%BA%A7%E5%93%81%E8%83%BD%E5%8A%9B%E5%AE%9A%E4%B9%89%E6%8C%87%E5%BC%95%20V1.2.pdf

因此定义一个RequestData对象以及设备对应的能力项

 public class RequestData
    {        public string device_type { get; set; }        public string device_id { get; set; }        public string user { get; set; }        public Service services { get; set; }        public object data { get; set; }
    }
ログイン後にコピー
 public class Service
    {        public lightbulb lightbulb { get; set; }        public air_conditioner air_conditioner { get; set; }        public power_switch power_switch { get; set; }        public operation_status operation_status { get; set; }
    }
ログイン後にコピー

service包括两个部分,一个是能力部分,好比上面这个service,就包含了三种能力,灯、空调以及开关(这只是测试,不是真正产品的能力)。和一个操作状态。操作状态就是指这个设备是否开着或者关闭了。而每一个能力,又包括两部分,拿灯来说:

    public class lightbulb
    {        public int alpha { get; set; }        public lightbulb_value_range value_range { get; set; }
    }    public class lightbulb_value_range
    {        public string alpha { get; set; }
    }
ログイン後にコピー

灯有一个亮度值,和一个范围属性。范围值中包含了最大和最小值以及单位值。

"lightbulb":{"alpha":10,"value_range":{"alpha":"0|100|1"}},"
ログイン後にコピー

这表示灯的亮度是10,返回是0到100,每次可以调节1个单位。

发送查询请求后,微信返回一个json,定义对象为下:

 public class OpenApiResult
    {        public int error_code { get; set; }        public string error_msg { get; set; }        public string msg_id { get; set; }
    }
ログイン後にコピー

WxDeviceService中:

   public OpenApiResult RequestDeviceStatus(string accessToken, RequestData data)
        {            var url = string.Format(WxDeviceConfig.GetDeviceStatusUrl, accessToken);            return SendHelp.Send<OpenApiResult>(accessToken, url, data);
        }
ログイン後にコピー

4.接受消息

那么问题来了,如何接受post过来的数据,以及如何存储呢。微信post的数据格式如下:

定义了一个WxResponseData对象:

    public class WxResponseData
    {        public int asy_error_code { get; set; }        public string asy_error_msg { get; set; }        public string create_time { get; set; }        public string msg_id { get; set; }        /// <summary>
        /// notify 说明是设备变更        /// set_resp 说明是设置设备        /// get_resp 说明获取设备信息        /// </summary>
        public string msg_type { get; set; }        public string device_type { get; set; }        public string device_id { get; set; }        public object data { get; set; }        public Service services { get; set; }        public string user { get; set; }
    }
ログイン後にコピー

View Code

msg_type代表着不同类型的消息, notify 说明是设备变更,set_resp 说明是设置设备 get_resp 说明获取设备信息。在WxDeviceService中增加GetDeviceStatus方法:

   public T GetWxResponse<T>(HttpRequestBase request)
        {
            Stream postData = request.InputStream;
            StreamReader sRead = new StreamReader(postData);            string postContent = sRead.ReadToEnd();            if (!string.IsNullOrEmpty(postContent))
            {
                Logger.Debug("收到数据:"+postContent);
            }            try
            {                return JsonConvert.DeserializeObject<T>(postContent);
            }            catch (Exception e)
            {
                Logger.Debug(e.Message);                throw;
            }
        }        public WxResponseData GetDeviceStatus(HttpRequestBase request)
        {            return GetWxResponse<WxResponseData>(request);
        }
ログイン後にコピー

需要先读取请求中的json字符串然后转换成C#对象。然后在最初启用的ReceiveWXMsg方法中随时准备接受消息:

  public string ReceiveWXMsg()
        {            var signature = Request.QueryString["signature"];            var timestamp = Request.QueryString["timestamp"];            var echostr = Request.QueryString["echostr"];            var nonce = Request.QueryString["nonce"];
            Logger.Debug("signature:" + signature);
            Logger.Debug("timestamp:" + timestamp);
            Logger.Debug("nonce:" + nonce);
            Logger.Debug("echostr:" + echostr);
            try
            {                var userdata = getUserWxData();                var data = wxDeviceService.GetDeviceStatus(Request);
                userdata.ResponseData = data;                setUserWxData(userdata);
            }            catch (Exception e)
            {
                Logger.Debug(e.Message);
            }            return echostr;
        }
ログイン後にコピー

因为读取到的数据需要及时呈现给页面,所以这里选用了缓存来存储设备信息以及用户相关信息。

UserWxData:

public class UserWxData
    {        private WxResponseData _responseData;        public UserWxData()
        {
            CreateTime = DateTime.Now;
        }        public DateTime CreateTime { get; set; }        public TokenResult AccessToken { get; set; }        public WxResponseData ResponseData
        {            get { return _responseData??(_responseData=new WxResponseData()); }            set { _responseData = value; }
        }        public string OpenId { get; set; }
    }
ログイン後にコピー

View Code

  private UserWxData getUserWxData()
        {            var target = _cacheManager.Get<UserWxData>(userKey) ?? new UserWxData();            return target;
        }        private string userKey
        {            get
            {                return Session.SessionID;
            }
        }        private void setUserWxData(UserWxData data)
        {
            _cacheManager.Set(userKey, data, 7200);
        }
ログイン後にコピー

View Code

缓存是Nop中的MemoryCacheManager:

using System;using System.Collections.Generic;using System.Runtime.Caching;using System.Text.RegularExpressions;namespace Niqiu.Core.Domain.Common
{    /// <summary>
    /// Represents a manager for caching between HTTP requests (long term caching)    /// </summary>
    public partial class MemoryCacheManager : ICacheManager
    {        protected ObjectCache Cache
        {            get
            {                return MemoryCache.Default;
            }
        }        
        /// <summary>
        /// Gets or sets the value associated with the specified key.        /// </summary>
        /// <typeparam name="T">Type</typeparam>
        /// <param name="key">The key of the value to get.</param>
        /// <returns>The value associated with the specified key.</returns>
        public virtual T Get<T>(string key)
        {            return (T)Cache[key];
        }        /// <summary>
        /// Adds the specified key and object to the cache.        /// </summary>
        /// <param name="key">key</param>
        /// <param name="data">Data</param>
        /// <param name="cacheTime">Cache time</param>
        public virtual void Set(string key, object data, int cacheTime)
        {            if (data == null)                return;            var policy = new CacheItemPolicy {AbsoluteExpiration = DateTime.Now + TimeSpan.FromMinutes(cacheTime)};
            Cache.Add(new CacheItem(key, data), policy);
        }        /// <summary>
        /// Gets a value indicating whether the value associated with the specified key is cached        /// </summary>
        /// <param name="key">key</param>
        /// <returns>Result</returns>
        public virtual bool IsSet(string key)
        {            return (Cache.Contains(key));
        }        /// <summary>
        /// Removes the value with the specified key from the cache        /// </summary>
        /// <param name="key">/key</param>
        public virtual void Remove(string key)
        {
            Cache.Remove(key);
        }        /// <summary>
        /// Removes items by pattern        /// </summary>
        /// <param name="pattern">pattern</param>
        public virtual void RemoveByPattern(string pattern)
        {            var regex = new Regex(pattern, RegexOptions.Singleline | RegexOptions.Compiled | RegexOptions.IgnoreCase);            var keysToRemove = new List<String>();            foreach (var item in Cache)                if (regex.IsMatch(item.Key))
                    keysToRemove.Add(item.Key);            foreach (string key in keysToRemove)
            {
                Remove(key);
            }
        }        /// <summary>
        /// Clear all cache data        /// </summary>
        public virtual void Clear()
        {            foreach (var item in Cache)
                Remove(item.Key);
        }
    }
}
ログイン後にコピー

View Code

而为什么不是PerRequestCacheManager呢,想一想~

using System;using System.Collections;using System.Collections.Generic;using System.Text.RegularExpressions;using System.Web;namespace Niqiu.Core.Domain.Common
{    /// <summary>
    /// Represents a manager for caching during an HTTP request (short term caching)    /// </summary>
    public partial class PerRequestCacheManager : ICacheManager
    {        /// <summary>
        /// Ctor        /// </summary>
        /// <param name="context">Context</param>
        //public PerRequestCacheManager(HttpContextBase context)        //{        //    this._context = context;        //}
        
        /// <summary>
        /// Creates a new instance of the NopRequestCache class        /// </summary>
        protected virtual IDictionary GetItems()
        {            if (_context != null)                return _context.Items;            return null;
        }        //不用注入
        private HttpContextBase _context
        {            get { return new HttpContextWrapper(HttpContext.Current); }
        }        /// <summary>
        /// Gets or sets the value associated with the specified key.        /// </summary>
        /// <typeparam name="T">Type</typeparam>
        /// <param name="key">The key of the value to get.</param>
        /// <returns>The value associated with the specified key.</returns>
        public virtual T Get<T>(string key)
        {            var items = GetItems();            if (items == null)                return default(T);            return (T)items[key];
        }        /// <summary>
        /// Adds the specified key and object to the cache.        /// </summary>
        /// <param name="key">key</param>
        /// <param name="data">Data</param>
        /// <param name="cacheTime">Cache time</param>
        public virtual void Set(string key, object data, int cacheTime)
        {            var items = GetItems();            if (items == null)                return;            if (data != null)
            {                if (items.Contains(key))
                    items[key] = data;                else
                    items.Add(key, data);
            }
        }        /// <summary>
        /// Gets a value indicating whether the value associated with the specified key is cached        /// </summary>
        /// <param name="key">key</param>
        /// <returns>Result</returns>
        public virtual bool IsSet(string key)
        {            var items = GetItems();            if (items == null)                return false;            
            return (items[key] != null);
        }        /// <summary>
        /// Removes the value with the specified key from the cache        /// </summary>
        /// <param name="key">/key</param>
        public virtual void Remove(string key)
        {            var items = GetItems();            if (items == null)                return;

            items.Remove(key);
        }        /// <summary>
        /// Removes items by pattern        /// </summary>
        /// <param name="pattern">pattern</param>
        public virtual void RemoveByPattern(string pattern)
        {            var items = GetItems();            if (items == null)                return;            var enumerator = items.GetEnumerator();            var regex = new Regex(pattern, RegexOptions.Singleline | RegexOptions.Compiled | RegexOptions.IgnoreCase);            var keysToRemove = new List<String>();            while (enumerator.MoveNext())
            {                if (regex.IsMatch(enumerator.Key.ToString()))
                {
                    keysToRemove.Add(enumerator.Key.ToString());
                }
            }            foreach (string key in keysToRemove)
            {
                items.Remove(key);
            }
        }        /// <summary>
        /// Clear all cache data        /// </summary>
        public virtual void Clear()
        {            var items = GetItems();            if (items == null)                return;            var enumerator = items.GetEnumerator();            var keysToRemove = new List<String>();            while (enumerator.MoveNext())
            {
                keysToRemove.Add(enumerator.Key.ToString());
            }            foreach (string key in keysToRemove)
            {
                items.Remove(key);
            }
        }
    }
}
ログイン後にコピー

View Code

5.设置设备状态

有了前面几步,这里也好说了。url:

    SetDeviceUrl =
ログイン後にコピー

设置设备的参数和请求是一样的,

 public OpenApiResult SetDevice(string accessToken, RequestData data)
        {            var url = string.Format(WxDeviceConfig.SetDeviceUrl, accessToken);            return SendHelp.Send<OpenApiResult>(accessToken, url, data);
        }
ログイン後にコピー

View Code

调用openApi基本上就这样了,如有不完善的地方还请指正。 这个方法有权限的问题,可以用查询方法代替,同样可以改变设备状态。不知道这api是个什么鬼。

四、常见错误

 

如果硬件通信没有开启这个能力,去查询的会报这个错误。

刚开始看到"device not login"实在没明白什么意思,文档里也没说明这个错误。设备还需要什么登录?原来是硬件同学没有连接设备... ORZ

如果你的requestData结构不对,特别是附加的那个Data参数只能是字符串,不要写成空对象{},就会出现这个错误。

token超时

同一个设备同时被操作。

【相关推荐】

1. 微信公众号平台源码下载

2. 微信投票源码下载

以上がWeChat H5 開発は openApi を呼び出しますの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

関連ラベル:
ソース:php.cn
このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。
人気のチュートリアル
詳細>
最新のダウンロード
詳細>
ウェブエフェクト
公式サイト
サイト素材
フロントエンドテンプレート