微信硬件平台是微信推出连接物与人,物与物的IOT解决方案。也就是说可以通过微信控制各种智能设备。比如一些蓝牙设备、空调、电视等等。

我本身不懂硬件(虽然是电子信息专业),硬件是北航的两个研究生在弄,小团队里我负责开发H5自定义面板,刚开始看官方文档各种迷糊,对于jssdk、jsapi、Airkiss、openApi、直连SDK都不知道该用哪个做,官方论坛问问题基本上没结果,加了几个微信硬件群问问题,发现好些开发者和我一样,同一个问题,发到几个群里问,画面好心酸。给wxthings发邮件问,能回复就不错了,往往还是只言片语。吐槽了这么多,还是得去解决问题,毕竟设备能搭上微信是一大卖点,最近摸索出来一些东西,于是有了此文。

一、接入流程

也就是说,首先你得有一个公众号,然后开通设备功能、添加产品(也就是你的智能设备)。这些过程官方文档比较清楚,我就不讲了。接入方案我们选择的是微信硬件云标准接入方案。

设置面板

而我要说的H5面板开发,指的就是在微信中打开的一个H5控制页面,它如何和微信硬件云通信,如何读取和设置设备的状态。在添加产品的过程中有一栏设置面板

如果选择标准面板,微信官方给出了三类标准面板:

分别是空调、开关和灯,如果是自定义,则输入地址即可。如果是标准面板,你是不需要服务器,但如果是自定义的面板,你就需要有自己的服务器,不然你无法处理微信云发过来的消息

启用服务器配置

在设置服务器地址的时候要注意,你必须按照它要求方式处理响应了,你才能启用成功。

你点击启用的时候,微信云会发过来一个签名、一个时间戳、一个随机数和一个随机字符串,验证之后,返回那个随机字符串,微信云收到你返回的随机字符串了,就能启用成功。比如,如果你定义的地址是http://www.xxx.com/device/ReceiveWXMsg,那么先把代码上传服务器,然后再点击启用,微信云会向这个地址post数据。 每一次微信向服务器发送数据时,都会先发这验证(也就是说如果不校验就返回会有安全问题)

  1. public string ReceiveWXMsg()
  2. {
  3. var signature = Request.QueryString["signature"];
  4. var timestamp = Request.QueryString["timestamp"];
  5. var echostr = Request.QueryString["echostr"];
  6. var nonce = Request.QueryString["nonce"];
  7. Logger.Debug("signature:" + signature);
  8. Logger.Debug("timestamp:" + timestamp);
  9. Logger.Debug("nonce:" + nonce);
  10. Logger.Debug("echostr:" + echostr);
  11. //验证
  12. return echostr;
  13. }

直接返回就行,不要加个json什么的。这个地址是干嘛的呢,往下看。

二、通信方式

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

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

三、调用openApi

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

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

1.获取access_token

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

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

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

  1. public class TokenResult
  2. {
  3. public string access_token { get; set; }
  4. public int expires_in { get; set; }
  5. }

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

  1. public const string AccessTokenUrl = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid={0}&secret={1}";

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

  1. using SendHelp= Senparc.Weixin.CommonAPIs.CommonJsonSend;
  2. public TokenResult GetAccessToken()
  3. {
  4. var url = string.Format(WxDeviceConfig.AccessTokenUrl, WxDeviceConfig.AppId, WxDeviceConfig.APPSECRET);
  5. var res = SendHelp.Send<TokenResult>(null, url, null, CommonJsonSendType.GET);
  6. return res;
  7. }

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

2.获取openid

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

  1. 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的返回值结构是这样:

  1. {"open_id":["oxa1otw5sk-Azgd8mx1bmBqoM2_E","oxa1ot8-j9j5bYUJJyAexe9d41_Y","oxa1ot5QTdxn0xNQ0DmYzoN0tUp1"],"resp_msg":{"ret_code":0,"error_info":"ok"}}

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

  1. public class OpenIdResult
  2. {
  3. private List<string> _openId;
  4.  
  5. public List<string> open_id
  6. {
  7. get { return _openId??(_openId=new List<string>()); }
  8. set { _openId = value; }
  9. }
  10.  
  11. public resp_msg resp_msg { get; set; }
  12.  
  13. public string GetOpenId()
  14. {
           // 将数组转换成string
  15. return "";
  16. }
  17. }

service中:

  1. public string GetOpenId(string accessToken,string deviceType,string deviceId)
  2. {
  3. var url = string.Format(WxDeviceConfig.GetOpenid, accessToken, deviceType, deviceId);
  4. var res = SendHelp.Send<OpenIdResult>(accessToken, url, null, CommonJsonSendType.GET);
  5. return res.GetOpenId();
  6. }

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

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

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

3.查询设备状态

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

请求url:

  1. public const string GetDeviceStatusUrl="https://api.weixin.qq.com/hardware/mydevice/platform/get_device_status?access_token={0}";

从官方文档可以看到,查询设备还需要另外一个重要的参数,它包含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对象以及设备对应的能力项

  1. public class RequestData
  2. {
  3. public string device_type { get; set; }
  4. public string device_id { get; set; }
  5. public string user { get; set; }
  6. public Service services { get; set; }
  7. public object data { get; set; }
  8. }
  1. public class Service
  2. {
  3. public lightbulb lightbulb { get; set; }
  4.  
  5. public air_conditioner air_conditioner { get; set; }
  6.  
  7. public power_switch power_switch { get; set; }
  8.  
  9. public operation_status operation_status { get; set; }
  10. }

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

  1. public class lightbulb
  2. {
  3. public int alpha { get; set; }
  4. public lightbulb_value_range value_range { get; set; }
  5. }
  6.  
  7. public class lightbulb_value_range
  8. {
  9. public string alpha { get; set; }
  10. }

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

  1. "lightbulb":{"alpha":10,"value_range":{"alpha":"0|100|1"}},"

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

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

  1. public class OpenApiResult
  2. {
  3. public int error_code { get; set; }
  4. public string error_msg { get; set; }
  5.  
  6. public string msg_id { get; set; }
  7. }

WxDeviceService中:

  1. public OpenApiResult RequestDeviceStatus(string accessToken, RequestData data)
  2. {
  3. var url = string.Format(WxDeviceConfig.GetDeviceStatusUrl, accessToken);
  4. return SendHelp.Send<OpenApiResult>(accessToken, url, data);
  5. }

4.接受消息

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

定义了一个WxResponseData对象:

  1. public class WxResponseData
  2. {
  3. public int asy_error_code { get; set; }
  4.  
  5. public string asy_error_msg { get; set; }
  6.  
  7. public string create_time { get; set; }
  8.  
  9. public string msg_id { get; set; }
  10.  
  11. /// <summary>
  12. /// notify 说明是设备变更
  13. /// set_resp 说明是设置设备
  14. /// get_resp 说明获取设备信息
  15. /// </summary>
  16. public string msg_type { get; set; }
  17.  
  18. public string device_type { get; set; }
  19. public string device_id { get; set; }
  20. public object data { get; set; }
  21.  
  22. public Service services { get; set; }
  23.  
  24. public string user { get; set; }
  25. }

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

  1. public T GetWxResponse<T>(HttpRequestBase request)
  2. {
  3. Stream postData = request.InputStream;
  4. StreamReader sRead = new StreamReader(postData);
  5. string postContent = sRead.ReadToEnd();
  6. if (!string.IsNullOrEmpty(postContent))
  7. {
  8. Logger.Debug("收到数据:"+postContent);
  9. }
  10. try
  11. {
  12. return JsonConvert.DeserializeObject<T>(postContent);
  13. }
  14. catch (Exception e)
  15. {
  16. Logger.Debug(e.Message);
  17. throw;
  18. }
  19. }
  20.  
  21. public WxResponseData GetDeviceStatus(HttpRequestBase request)
  22. {
  23. return GetWxResponse<WxResponseData>(request);
  24. }

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

  1. public string ReceiveWXMsg()
  2. {
  3. var signature = Request.QueryString["signature"];
  4. var timestamp = Request.QueryString["timestamp"];
  5. var echostr = Request.QueryString["echostr"];
  6. var nonce = Request.QueryString["nonce"];
  7. Logger.Debug("signature:" + signature);
  8. Logger.Debug("timestamp:" + timestamp);
  9. Logger.Debug("nonce:" + nonce);
  10. Logger.Debug("echostr:" + echostr);
  11. try
  12. {
  13. var userdata = getUserWxData();
  14. var data = wxDeviceService.GetDeviceStatus(Request);
  15. userdata.ResponseData = data;
  16. setUserWxData(userdata);
  17. }
  18. catch (Exception e)
  19. {
  20. Logger.Debug(e.Message);
  21. }
  22. return echostr;
  23. }

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

UserWxData:

  1. public class UserWxData
  2. {
  3. private WxResponseData _responseData;
  4.  
  5. public UserWxData()
  6. {
  7. CreateTime = DateTime.Now;
  8. }
  9. public DateTime CreateTime { get; set; }
  10. public TokenResult AccessToken { get; set; }
  11.  
  12. public WxResponseData ResponseData
  13. {
  14. get { return _responseData??(_responseData=new WxResponseData()); }
  15. set { _responseData = value; }
  16. }
  17.  
  18. public string OpenId { get; set; }
  19. }
  1. private UserWxData getUserWxData()
  2. {
  3. var target = _cacheManager.Get<UserWxData>(userKey) ?? new UserWxData();
  4. return target;
  5. }
  6.  
  7. private string userKey
  8. {
  9. get
  10. {
  11. return Session.SessionID;
  12. }
  13. }
  14.  
  15. private void setUserWxData(UserWxData data)
  16. {
  17. _cacheManager.Set(userKey, data, );
  18. }

缓存是Nop中的MemoryCacheManager:

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Runtime.Caching;
  4. using System.Text.RegularExpressions;
  5.  
  6. namespace Niqiu.Core.Domain.Common
  7. {
  8. /// <summary>
  9. /// Represents a manager for caching between HTTP requests (long term caching)
  10. /// </summary>
  11. public partial class MemoryCacheManager : ICacheManager
  12. {
  13. protected ObjectCache Cache
  14. {
  15. get
  16. {
  17. return MemoryCache.Default;
  18. }
  19. }
  20.  
  21. /// <summary>
  22. /// Gets or sets the value associated with the specified key.
  23. /// </summary>
  24. /// <typeparam name="T">Type</typeparam>
  25. /// <param name="key">The key of the value to get.</param>
  26. /// <returns>The value associated with the specified key.</returns>
  27. public virtual T Get<T>(string key)
  28. {
  29. return (T)Cache[key];
  30. }
  31.  
  32. /// <summary>
  33. /// Adds the specified key and object to the cache.
  34. /// </summary>
  35. /// <param name="key">key</param>
  36. /// <param name="data">Data</param>
  37. /// <param name="cacheTime">Cache time</param>
  38. public virtual void Set(string key, object data, int cacheTime)
  39. {
  40. if (data == null)
  41. return;
  42.  
  43. var policy = new CacheItemPolicy {AbsoluteExpiration = DateTime.Now + TimeSpan.FromMinutes(cacheTime)};
  44. Cache.Add(new CacheItem(key, data), policy);
  45. }
  46.  
  47. /// <summary>
  48. /// Gets a value indicating whether the value associated with the specified key is cached
  49. /// </summary>
  50. /// <param name="key">key</param>
  51. /// <returns>Result</returns>
  52. public virtual bool IsSet(string key)
  53. {
  54. return (Cache.Contains(key));
  55. }
  56.  
  57. /// <summary>
  58. /// Removes the value with the specified key from the cache
  59. /// </summary>
  60. /// <param name="key">/key</param>
  61. public virtual void Remove(string key)
  62. {
  63. Cache.Remove(key);
  64. }
  65.  
  66. /// <summary>
  67. /// Removes items by pattern
  68. /// </summary>
  69. /// <param name="pattern">pattern</param>
  70. public virtual void RemoveByPattern(string pattern)
  71. {
  72. var regex = new Regex(pattern, RegexOptions.Singleline | RegexOptions.Compiled | RegexOptions.IgnoreCase);
  73. var keysToRemove = new List<String>();
  74.  
  75. foreach (var item in Cache)
  76. if (regex.IsMatch(item.Key))
  77. keysToRemove.Add(item.Key);
  78.  
  79. foreach (string key in keysToRemove)
  80. {
  81. Remove(key);
  82. }
  83. }
  84.  
  85. /// <summary>
  86. /// Clear all cache data
  87. /// </summary>
  88. public virtual void Clear()
  89. {
  90. foreach (var item in Cache)
  91. Remove(item.Key);
  92. }
  93. }
  94. }

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

  1. using System;
  2. using System.Collections;
  3. using System.Collections.Generic;
  4. using System.Text.RegularExpressions;
  5. using System.Web;
  6.  
  7. namespace Niqiu.Core.Domain.Common
  8. {
  9. /// <summary>
  10. /// Represents a manager for caching during an HTTP request (short term caching)
  11. /// </summary>
  12. public partial class PerRequestCacheManager : ICacheManager
  13. {
  14.  
  15. /// <summary>
  16. /// Ctor
  17. /// </summary>
  18. /// <param name="context">Context</param>
  19. //public PerRequestCacheManager(HttpContextBase context)
  20. //{
  21. // this._context = context;
  22. //}
  23.  
  24. /// <summary>
  25. /// Creates a new instance of the NopRequestCache class
  26. /// </summary>
  27. protected virtual IDictionary GetItems()
  28. {
  29. if (_context != null)
  30. return _context.Items;
  31.  
  32. return null;
  33. }
  34.  
  35. //不用注入
  36. private HttpContextBase _context
  37. {
  38. get { return new HttpContextWrapper(HttpContext.Current); }
  39. }
  40.  
  41. /// <summary>
  42. /// Gets or sets the value associated with the specified key.
  43. /// </summary>
  44. /// <typeparam name="T">Type</typeparam>
  45. /// <param name="key">The key of the value to get.</param>
  46. /// <returns>The value associated with the specified key.</returns>
  47. public virtual T Get<T>(string key)
  48. {
  49. var items = GetItems();
  50. if (items == null)
  51. return default(T);
  52.  
  53. return (T)items[key];
  54. }
  55.  
  56. /// <summary>
  57. /// Adds the specified key and object to the cache.
  58. /// </summary>
  59. /// <param name="key">key</param>
  60. /// <param name="data">Data</param>
  61. /// <param name="cacheTime">Cache time</param>
  62. public virtual void Set(string key, object data, int cacheTime)
  63. {
  64. var items = GetItems();
  65. if (items == null)
  66. return;
  67.  
  68. if (data != null)
  69. {
  70. if (items.Contains(key))
  71. items[key] = data;
  72. else
  73. items.Add(key, data);
  74. }
  75. }
  76.  
  77. /// <summary>
  78. /// Gets a value indicating whether the value associated with the specified key is cached
  79. /// </summary>
  80. /// <param name="key">key</param>
  81. /// <returns>Result</returns>
  82. public virtual bool IsSet(string key)
  83. {
  84. var items = GetItems();
  85. if (items == null)
  86. return false;
  87.  
  88. return (items[key] != null);
  89. }
  90.  
  91. /// <summary>
  92. /// Removes the value with the specified key from the cache
  93. /// </summary>
  94. /// <param name="key">/key</param>
  95. public virtual void Remove(string key)
  96. {
  97. var items = GetItems();
  98. if (items == null)
  99. return;
  100.  
  101. items.Remove(key);
  102. }
  103.  
  104. /// <summary>
  105. /// Removes items by pattern
  106. /// </summary>
  107. /// <param name="pattern">pattern</param>
  108. public virtual void RemoveByPattern(string pattern)
  109. {
  110. var items = GetItems();
  111. if (items == null)
  112. return;
  113.  
  114. var enumerator = items.GetEnumerator();
  115. var regex = new Regex(pattern, RegexOptions.Singleline | RegexOptions.Compiled | RegexOptions.IgnoreCase);
  116. var keysToRemove = new List<String>();
  117. while (enumerator.MoveNext())
  118. {
  119. if (regex.IsMatch(enumerator.Key.ToString()))
  120. {
  121. keysToRemove.Add(enumerator.Key.ToString());
  122. }
  123. }
  124.  
  125. foreach (string key in keysToRemove)
  126. {
  127. items.Remove(key);
  128. }
  129. }
  130.  
  131. /// <summary>
  132. /// Clear all cache data
  133. /// </summary>
  134. public virtual void Clear()
  135. {
  136. var items = GetItems();
  137. if (items == null)
  138. return;
  139.  
  140. var enumerator = items.GetEnumerator();
  141. var keysToRemove = new List<String>();
  142. while (enumerator.MoveNext())
  143. {
  144. keysToRemove.Add(enumerator.Key.ToString());
  145. }
  146.  
  147. foreach (string key in keysToRemove)
  148. {
  149. items.Remove(key);
  150. }
  151. }
  152. }
  153. }

5.设置设备状态

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

  1. public const string SetDeviceUrl ="https://api.weixin.qq.com/hardware/mydevice/platform/ctrl_device?access_token={0}";

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

  1. public OpenApiResult SetDevice(string accessToken, RequestData data)
  2. {
  3. var url = string.Format(WxDeviceConfig.SetDeviceUrl, accessToken);
  4. return SendHelp.Send<OpenApiResult>(accessToken, url, data);
  5. }

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

四、常见错误

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

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

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

token超时

同一个设备同时被操作。

小结:以上是硬件面板开发过程中遇到的种种“经验”总结。限于篇幅这一节只讲了openapi的调用。下一节将如何用seajs构建h5控制面板。敬请关注!

微信硬件H5面板开发(一) ---- 调用openApi的更多相关文章

  1. 微信硬件H5面板开发(二) ---- 实现一个灯的控制

    在第一节中讲解了openApi的调用,这一篇讲一下如何实现一个灯的控制.就用微信提供的lamp例子来做,将代码扒下来(实在是没办法,没有示例),整合到自己的项目中.lamp源码:http://file ...

  2. 能挣钱的微信JSSDK+H5混合开发

    H5喊了那么久,有些人都说不实用,有些人却利用在微信中开发H5应用赚得盆满钵满.微信JSSDK + HTML 5,让移动Web开发与微信结合轻而易举!跨平台.零成本,让大众创业变得更方便. 我觉得现在 ...

  3. C#开发微信门户及应用(44)--微信H5页面开发的经验总结

    在我们开发微信页面的时候,需要大量用到了各种呈现的效果,一般可以使用Boostrap的效果来设计不同的页面,不过微信团队也提供很多这方面的资源,包括JSSDK的接口,以及Weui的页面样式和相关功能页 ...

  4. ASP.NET MVC 开发微信支付H5(外置浏览器支付)

    H5支付是指商户在微信客户端外的移动端网页展示商品或服务,用户在前述页面确认使用微信支付时,商户发起本服务呼起微信客户端进行支付. 主要用于触屏版的手机浏览器请求微信支付的场景.可以方便的从外部浏览器 ...

  5. Java微信公众平台开发之公众号支付(微信内H5调起支付)

    官方文档点击查看准备工作:已通过微信认证的公众号,必须通过ICP备案域名(否则会报支付失败)借鉴了很多大神的文章,在此先谢过了 整个支付流程,看懂就很好写了 一.设置支付目录 在微信公众平台设置您的公 ...

  6. 微信开发,调用js-SDK接口

    微信开发,调用js-SDK接口<!DOCTYPE html><html><head lang="en"> <meta charset=&q ...

  7. h5直播开发之旅总结

    前言 关于直播,有很多相关技术文章,这里不多说. 作为前端,我们比较关心我们所需要的. 直播的大致流程: APP端调用摄像头 -> 拍摄视频 -> 实时上传视频 -> 服务器端获取视 ...

  8. 微信公众号支付开发全过程 --JAVA

    按照惯例,开头总得写点感想 ------------------------------------------------------------------ 业务流程 这个微信官网说的还是很详细的 ...

  9. 到处是坑的微信公众号支付开发(java)

    之前公司项目开发中支付是用阿里的支付做的,那叫一个简单,随意:悲催的是,现在公司开发了微信公众号,所以我步入了全是坑的微信支付开发中... ------------------------------ ...

随机推荐

  1. ASP.NET Core 之 Identity 入门(一)

    前言 在 ASP.NET Core 中,仍然沿用了 ASP.NET里面的 Identity 组件库,负责对用户的身份进行认证,总体来说的话,没有MVC 5 里面那么复杂,因为在MVC 5里面引入了OW ...

  2. PHP实现RTX发送消息提醒

    RTX是腾讯公司推出的企业级即时通信平台,大多数公司都在使用它,但是我们很多时候需要将自己系统或者产品的一些通知实时推送给RTX,这就需要用到RTX的服务端SDK,建议先去看看RTX的SDK开发文档( ...

  3. 前端框架 EasyUI (2)页面布局 Layout

    在 Web 程序中,页面布局对应用程序的用户体验至关重要. 在一般的信息管理类的 Web 应用程序中,页面结构通常有一个主工作区,然后在工作区上下左右靠近边界的区域设置一些边栏,用于显示信息或放置一些 ...

  4. Android Ormlite 学习笔记2 -- 主外键关系

    以上一篇为例子,进行主外键的查询 定义Users.java 和 Role.java Users -- Role 关系为:1对1 即父表关系 Role -- Users 关系为:1对多 即子表关系 下面 ...

  5. android通过webview调起支付宝app支付

    webview在加载网页的时候会默认调起手机自带的浏览器加载网页,用户体验不好.但当用户设置浏览器客户端(setWebViewClient)设置这样的监听事件之后,当请求url的时候就不会打开手机自带 ...

  6. 【热门技术】EventBus 3.0,让事件订阅更简单,从此告别组件消息传递烦恼~

    一.写在前面 还在为时间接收而烦恼吗?还在为各种组件间的消息传递烦恼吗?EventBus 3.0,专注于android的发布.订阅事件总线,让各组件间的消息传递更简单!完美替代Intent,Handl ...

  7. 我大中华微软MVP中国区人才库

    刘海峰:国内知名微软开源技术网站51Aspx 创始人,十年以上的Asp.net从业经验,微软MSDN特约讲师.Teched讲师.ImagineCup大赛评委.人大出版社研修班特约讲师,曾多次受邀访问美 ...

  8. thinkphp数据的查询和截取

    public function NewsList(){ $this->assign('title','news'); $p = I('page',1); $listRows = 6; $News ...

  9. BPM配置故事之案例5-必填与水印文本

    物资申请表改好了,但是没过两天老李又找来了. 老李:这个表格每次都是各个部门发给我们,再由我们采购部来填,太影响效率了,以后要让他们自己填. 小明:那就让他们填呗,他们有权限啊. 老李:可是他们说不会 ...

  10. 使用MonoTouch.Dialog简化iOS界面开发

    MonoTouch.Dialog简称MT.D,是Xamarin.iOS的一个RAD工具包.它提供易于使用的声明式API,不需要使用导航控制器.表格等ViewController来定义复杂的应用程序UI ...