­

C#开发BIMFACE系列36 服务端API之:回调机制

  在《C# 开发 BIMFACE 系列文章》中介绍了模型转换、模型对比接口。这2个功能接口比较特殊,发起请求后,逻辑处理是在BIMFACE云端进行的,通常需要5~10分钟。当逻辑处理完成后,BIMFACE通过回调机制通知对比结果。

  BIMFACE支持回调机制。在调用方发起模型转换、模型集成、模型对比、生成离线数据包等操作时,可以通过传入参数callback的方式来启用回调机制。 在BIMFACE处理完相应操作后,根据调用方传入的回调地址通知调用方相应操作的结果。

URL参数:

signature(签名):为了确保回调消息是由BIMFACE发出的,调用方在收到回调消息后,须验证签名。签名的计算方式:MD5(“appKey:appSecret:compareId:status:nonce”),如果调用方计算的签名与BIMFACE返回的签名一致,则证明该消息是安全可靠的。

应用收到回调后,须向BIMFace发送回执,回执消息:HTTP STATUS 200

Callbak示例:

* 调用方对文件1685236328506848发起了模型转换,并且传入的回调地址是:https://my.app.com/callback。    * BIMFACE在模型转换任务处理完成后,会发送一个get请求到调用方的callback地址:   https://my.app.com/callback?fileId=1685236328506848&status=success&thumbnail=38044a282f55cb26e3704643dccd2b55/thumbnail/96.png,38044a282f55cb26e3704643dccd2b55/thumbnail/256.png&reason=&signature=99a6fccb1894dfdb4cce48fd5ec58110&nonce=123abc    * 调用方接收到这条请求后,可以进行signature的验证,并发送回执消息。

特别说明

  BIMFACE的回调机制与微信公众号或者小程序开发类似,需要开发者提供开发者服务器,且有正式合法域名或者外网IP,对外公布一个地址,BIMFACE服务器能访问到该地址才可以。

  如果无法提供有效的回调地址,则只能通过手动调用 模型转换、模型集成、模型对比、生成离线数据包等操作的其他API来获取对应的处理结果。

  在.NET平台下实现该功能可以使用 WebService、一般处理程序、WebAPI等技术方式实现。下面介绍在一般处理程序中实现的思路与步骤。

 1、配置BIMACE开发者账号信息。

  在web.config 或者 app.config 文件中配置开发者账号信息,供验证消息签名时使用。

 2、获取BIMFace服务器发送的回调请求参数。

1 long fileId = context.Request.QueryString["fileId"].ToLong();  // 文件ID  2 string status = context.Request.QueryString["status"];         // 转换的结果  3 string reason = context.Request.QueryString["reason"];         // 若转换失败,则返回失败原因  4 string thumbnail = context.Request.QueryString["thumbnail"];   // 缩略图地址  5 string nonce = context.Request.QueryString["nonce"];           // 回调随机数  6 string signature = context.Request.QueryString["signature"];   // BIMFACE的加密签名

 3、根据请求参数计算签名。

  签名的计算方式:MD5(“appKey:appSecret:compareId:status:nonce”)

 1 /// <summary>   2 ///  根据回调的参数计算签名   3 /// </summary>   4 /// <param name="appKey">开发者秘钥</param>   5 /// <param name="appSecret">开发者密码</param>   6 /// <param name="fileId">BIMFace发出的回调信息:文件ID</param>   7 /// <param name="status">BIMFace发出的回调信息:转换的结果</param>   8 /// <param name="nonce">BIMFace发出的回调信息:回调随机数</param>   9 /// <returns></returns>  10 public static string GetCallbackSignature(string appKey, string appSecret, long fileId, string status, string nonce)  11 {  12     return string.Format("{0}:{1}:{2}:{3}:{4}", appKey, appSecret, fileId, status, nonce).EncryptByMD5();  13 }

 其中使用到的扩展方法 EncryptByMD5() 实现如下:

 1 /// <summary>   2 ///  自定义扩展方法:使用 MD5(不可逆加密) 算法加密字符串。返回二进制形式的字符串。字符串的编码方式为UTF8。   3 /// </summary>   4 /// <param name="this">扩展对象。字符串。编码方式为UTF8</param>   5 /// <param name="caseType">字符串大小写。默认小写</param>   6 /// <returns></returns>   7 public static string EncryptByMD5(this string @this, CaseType caseType = CaseType.Lower)   8 {   9     using (MD5 md5 = MD5.Create())  10     {  11         var sb = new StringBuilder();  12         byte[] hashBytes = md5.ComputeHash(Encoding.UTF8.GetBytes(@this));  13         foreach (byte bytes in hashBytes)  14         {  15             sb.Append(bytes.ToString("X2"));//X2 表示二进制  16         }  17  18         return caseType == CaseType.Upper ? sb.ToString() : sb.ToString().ToLower();  19     }  20 }

View Code

 4、验证签名。

  将步骤3中的计算结果与BIMFace发出的回调消息签名做对比,如果签名一直则证明该消息是安全可靠的。 

 1 /// <summary>   2 ///  验证BIMFace发出的回调消息签名信息是否安全可靠   3 /// </summary>   4 /// <param name="appKey">开发者秘钥</param>   5 /// <param name="appSecret">开发者密码</param>   6 /// <param name="fileId">BIMFace发出的回调信息:文件ID</param>   7 /// <param name="status">BIMFace发出的回调信息:转换的结果</param>   8 /// <param name="nonce">BIMFace发出的回调信息:回调随机数</param>   9 /// <param name="signature">BIMFace发出的回调信息:签名</param>  10 /// <param name="custCalcSignature">输出参数:根据BIMFACE平台的加密规则计算出来的签名信息</param>  11 /// <returns></returns>  12 public static bool CheckCallbackSignature(string appKey, string appSecret, long fileId, string status, string nonce, string signature, out string custCalcSignature)  13 {  14     /* signature(签名):为了确保回调消息是由BIMFace发出的,应用在收到回调消息后,须验证签名。  15     * 签名的计算方式:MD5("appKey:appSecret:fileId:status:nonce"),如果应用计算的签名与BIMFace返回的签名一致,则证明该消息是安全可靠的。  16     */  17     custCalcSignature = GetCallbackSignature(appKey, appSecret, fileId, status, nonce);  18  19     return custCalcSignature == signature;  20 }

 5、根据签名验证结果做出回执响应消息。

      如果验证签名成功则可以将模型转换、模型集成、模型对比、生成离线数据包等操作的处理结果写入数据库保存供后续其他业务逻辑使用。

   签名成功后,须向BIMFace发送回执,回执消息:HTTP STATUS 200。

 1 bool checkSignature = CallbackUtils.CheckCallbackSignature(appKey, appSecret, fileId, status, nonce, signature, out custCalcSignature);   2 if (checkSignature)   3 {   4     tip = "[BIMFace发出的回调信息签名验证成功!]"   5           + Environment.NewLine   6           + callbackResponse;   7     LogUtility.Info(tip);   8   9     //Todo 此处可以根据fileId把相关的信息写入数据库中  10  11     // 回执消息:应用收到回调后,须向BIMFace发送回执,回执消息:HTTP STATUS 200  12     context.Response.Write("HTTP STATUS 200");  13 }  14 else  15 {  16     tip = "[BIMFace发出的回调信息签名验证不通过!]"  17         + Environment.NewLine  18         + callbackResponse  19         + Environment.NewLine  20         + "自定义计算签名 custCalcSignature:" + custCalcSignature;  21  22     LogUtility.Error(tip);  23  24     context.Response.Write(tip);  25 }

如果签名验证失败,则需要将签名信息写入文本日志供分析原因使用。此时通过编码方式实现邮件、短信、微信消息等方式通知开发者回调程序处理结果不正确,使其及时知道业务系统的运行状况。

 6、发布程序并使用该回调地址。

  程序完成后发布到开发者服务器。在模型转换、模型集成、模型对比、生成离线数据包等操作的API接口参数中使用该回调地址。

完整的代码如下:

 1 /// <summary>   2 ///  BimFace回调处理   3 /// </summary>   4 public class BimFaceHandler : IHttpHandler   5 {   6     public void ProcessRequest(HttpContext context)   7     {   8         context.Response.ContentType = "text/plain";   9         context.Response.ContentEncoding = Encoding.UTF8;  10  11         string appKey = ConfigUtility.GetAppSettingValue("BIMFACE_AppKey");  12         string appSecret = ConfigUtility.GetAppSettingValue("BIMFACE_AppSecret");  13         string uid = context.Request.QueryString["uid"];  // SparkBimFace  14  15         #region 校验  16         if (appKey.IsNullOrWhiteSpace())  17         {  18             LogUtility.Error("BIMFace appKey 配置项没有配置!");  19  20             return;  21         }  22  23         if (appSecret.IsNullOrWhiteSpace())  24         {  25             LogUtility.Error("BIMFace appSecret 配置项没有配置!");  26  27             return;  28         }  29  30         if (uid.IsNullOrWhiteSpace())  31         {  32             LogUtility.Error("[非法请求]回调地址Url链接中的参数 uid 没有配置或者配置的值为空!");  33  34             return;  35         }  36         #endregion  37  38         long fileId = context.Request.QueryString["fileId"].ToLong();  // 文件ID  39         string status = context.Request.QueryString["status"];         // 转换的结果  40         string reason = context.Request.QueryString["reason"];         // 若转换失败,则返回失败原因  41         string thumbnail = context.Request.QueryString["thumbnail"];   // 缩略图地址  42         string nonce = context.Request.QueryString["nonce"];           // 回调随机数  43         string signature = context.Request.QueryString["signature"];   // BIMFACE的加密签名  44  45         string callbackResponse = string.Format("fileId:{0},rnstatus:{1},rnreason:{2},rnthumbnail:{3},rnnonce:{4},rnsignature:{5}",  46                                                  fileId, status, reason, thumbnail, nonce, signature);  47         string tip;  48         string custCalcSignature;  49  50         bool checkSignature = CallbackUtils.CheckCallbackSignature(appKey, appSecret, fileId, status, nonce, signature, out custCalcSignature);  51         if (checkSignature)  52         {  53             tip = "[BIMFace发出的回调信息签名验证成功!]"  54                   + Environment.NewLine  55                   + callbackResponse;  56             LogUtility.Info(tip);  57  58             //Todo 此处可以根据fileId把相关的信息写入数据库中  59  60             // 回执消息:应用收到回调后,须向BIMFace发送回执,回执消息:HTTP STATUS 200  61             context.Response.Write("HTTP STATUS 200");  62         }  63         else  64         {  65             tip = "[BIMFace发出的回调信息签名验证不通过!]"  66                 + Environment.NewLine  67                 + callbackResponse  68                 + Environment.NewLine  69                 + "自定义计算签名 custCalcSignature:" + custCalcSignature;  70  71             LogUtility.Error(tip);  72  73             context.Response.Write(tip);  74         }  75  76         context.Response.End();  77     }  78  79     /// <summary>  80     ///  该属性获得一个布尔值,指示另一个请求是否可以使用该HTTP处理程序的实例。  81     /// <para>如果设置为true,能提高性能,但要注意线程之间安全性问题。如果设置为false,则线程是安全的</para>  82     /// </summary>  83     public bool IsReusable  84     {  85         get  86         {  87             return false;  88         }  89     }  90 }