基于 Unity 的油菜花 Http 协议对接记录

由于公司业务的需要,和“油菜花”进行了 Http 协议接口的对接工作。
这是第一次完全独立对接 Http 协议,也是第一次对接外部协议,所以在此对过程中遇到的主要的重点部分和坑进行下记录。留待日后查询。


1.Decimal 类型数据和精度问题

这是第一次实际使用 decimal 类型数据,总体来说这个类型有很高的精确度,适合于财务和货币计算,具体内容可参考这篇文章
在这里提一下 Decimal 这个东西,主要是因为踩了一个坑,这个坑同时也和 Json 化时的序列化操作有关。
此处进行序列化操作时使用的是 Newtonsoft 这个插件,而不是 Unity 自带的 JsonUtility,原因是貌似 JsonUtility 无法支持这个 Decimal 类型的数据,而如果使用 Float 类型则会出现严重的精度丢失问题,例如一个 1.0f 的数据在进行序列化后,可能显示的是 0.9998641 诸如此种样式的结果。
但是,虽然使用 Decimal 类型数据可以解决精度问题,但是还有一个比较棘手的问题,就是在进行序列化的时候 Decimal 是默认至少保留一位小数的,也就是说即使是一个整数的 5m 在序列化之后也会显示为 5.0
问题是由于需要进行签名加密工作,这个 Decimal 类型数据进行一个 ToString() 的操作,并且保证在 ToString() 后显示的样式要和序列化中显示的样式相同。问题如下:

decimal a = 5M;
Debug.Log(a.ToString()); 		// 显示结果为 5
Debug.Log(a.ToString("#.#"));	// 显示结果为 5.0 但是遇到 a = 1.23 的情况也只能显示 1.2

解决方法:

// 对 Decimal 进行扩展
public static class DeciamlExtension
{
    
    
    /// <summary>
    /// Decimal 扩展方法
    /// 由于 Decimal 在转换为 json 格式时默认至少保留 1 位小数,
    /// 例如:decimal num = 1 在转换成 json 后就是 1.0
    /// 为了在 ToString 时和 json 保持一致,添加此扩展方法
    /// </summary>
    /// <param name="data"></param>
    /// <param name="scale"></param>
    /// <returns></returns>
    public static string ToString_JsonStyle(this decimal data)
    {
    
    
        string strData = data.ToString();
		// 获取小数点的位置,当无法获取到小数点时即数据为整数时,index = -1
        int index = strData.IndexOf(".");
        // 获取数据的总长度
        int dataLength = strData.Length;

        if (index != -1)
        {
    
    
        	// 对小数点前后的数字进行重新拼接
            return string.Format("{0}.{1}", strData.Substring(0, index), strData.Substring(index + 1, dataLength - index - 1));
        }
        else
        {
    
    
        	// 当小数点不存在时,默认显示小数点后一位
            return string.Format("{0}.0", strData);
        }

    }
}

2.关于如何获取 13 位的时间戳的问题

取值到毫秒得到的时间戳就是 13 位,如果只取到秒的话时间戳就是 10 位。一般一个系统会采取 13 位或 10 位的其中一种。

时间戳:时间戳是指格林威治时间 1970 年 01 月 01 日 00 时 00 分 00 秒 (北京时间 1970 年 01 月 01 日 08 时 00 分 00 秒) 起至现在的总秒数。通俗的讲, 时间戳是一份能够表示一份数据在一个特定时间点已经存在的完整的可验证的数据。 它的提出主要是为用户提供一份电子证据, 以证明用户的某些数据的产生时间。 在实际应用上, 它可以使用在包括电子商务、 金融活动的各个方面, 尤其可以用来支撑公开密钥基础设施的 “不可否认” 服务。

1 秒 = 1000 毫秒
1 毫秒 = 1000 微妙
1 微秒 = 1000 纳秒
1 毫秒 = 1000000 纳秒

DateTime.Ticks 表示 0001 年 1 月 1 日午夜 12:00:00 以来所经历的 100 纳秒数,即 Ticks 的属性为 100 纳秒(1 Ticks = 0.0001 毫秒)。

// 设置初始时间
public DateTime StartTime = new DateTime(1970, 1, 1, 0, 0, 0, 0);
// 获取 13 位时间戳
public string GetTimeStamp()
{
    
    
	// 获取当前时间
	DateTime endTime = System.DateTime.UtcNow;
	// 获取时间差值(时间戳)
	TimeSpan timeSpan = endTime - StartTime;
	// 返回 13 位时间戳
	return (timeSpan.Ticks / 10000).ToString();
}

3. 使用 Json 类型数据进行 Http 的 Post 访问

Http 有多种请求方法,而我们需要使用的是 Post 方式进行访问。

基于最新版本的 Unity 已经废弃了 WWW 类的使用,改为更加先进的 UnityWebRequest。

需要特别注意的是关于 http 响应头信息的设置,这个可以参考下 “HTTP 响应头信息”“HTTP content-type” 这两部分的内容做个了解。

就目前来说,这个响应头在 Unity 中的默认设置好像就是 SetRequestHeader("Content-Type", "application/json;charset=utf-8"); 这个样子,查了几篇资料基本都是这种设置方式,顶多有些会把 charset=utf-8 去掉变成 SetRequestHeader("Content-Type", "application/json"); 。或者说通常情况下这么设置就可以了?

但是经过一些资料查阅发现这个具体的设置方式可能也跟服务器那边的配置有关,需要进行一定的协调。至少我们此次使用这种配置是没问题的。

完整代码如下:

 	/// <summary>
    /// Http 使用 Post 方式请求
    /// </summary>
    /// <param name="url"></param>
    /// <param name="json"></param>
    /// <param name="callback"></param>
    public void SendPostRequest(string url, string json, Action<string> callback)
    {
    
    
        StartCoroutine(HttpPostRequest(url, json, callback));
    }

	// 使用协程调用 UnityWebRequest 进行 Http 通信
    private IEnumerator HttpPostRequest(string url, string json, Action<string> callback)
    {
    
    
    	// 将需要提交的数据转换为 UTF-8 编码的 byte 数组
        byte[] dataBody = Encoding.UTF8.GetBytes(json);
		// 创建 UnityWebRequest 实例
		// 使用 using 关键字,可以让程序在结束任务后自动释放 UnityWebRequest 实例,防止内存泄漏
        using (UnityWebRequest unityWebRequest = new UnityWebRequest(url, "POST"))
        {
    
    
            // UploadHander 用于控制数据的上传
            unityWebRequest.uploadHandler = (UploadHandler)new UploadHandlerRaw(dataBody);

            // 设置 http 响应头信息
            unityWebRequest.SetRequestHeader("Content-Type", "application/json;charset=utf-8");

            // DownloadHander 用于控制数据的下载
            unityWebRequest.downloadHandler = (DownloadHandler)new DownloadHandlerBuffer();

            // 正式发送 http 请求
            yield return unityWebRequest.SendWebRequest();

            if (unityWebRequest.isHttpError || unityWebRequest.isNetworkError)
            {
    
    
                if (callback != null) callback(unityWebRequest.downloadHandler.text);
                Debug.LogError("Http Error: " + unityWebRequest.error + "\n" + unityWebRequest.downloadHandler.text);
            }
            else
            {
    
    
                if (unityWebRequest.isDone)
                {
    
    
                    if (callback != null) callback(unityWebRequest.downloadHandler.text);
                }
                else
                {
    
    
                    Debug.LogError("Http 请求失败: " + unityWebRequest.error);
                }
            }
        }
    }

4.关于 Android Q (Android 10) 的 Http 连接问题

在打出测试包后,安装到安卓真机时会出如下问题:

Cleartext HTTP traffic to xxx.xxx.xxx.xx not permitted

其中的 xxx.xxx.xxx.xx 是请求的 http 的 url 地址。

查询后发现,这个问题是自 Android 9 (Android P) 之后开始出现的。为了保证用户数据和设备的安全,Google 针对 Android 系统(Android P 之后的版本)的应用程序,将要求默认使用加密连接,而 http 网络请求则是使用的非加密的明文流量。

有几种解决方案可供参考:查看这篇文章

这里记录一种比较简单粗暴的方法:

在 AndroidManifest.xml 配置文件的 <application> 标签中添加如下内容

	android:usesCleartextTraffic="true

android:usesCleartextTraffic 指示应用程序是否打算使用明文网络流量,例如明文 HTTP。目标 API 级别为 27 或更低的应用程序的默认值为 “true”。面向 API 级别 28 或更高级别的应用默认为“ false”。

当属性设置为 “false” 时,平台组件 (例如,HTTP 和 FTP 堆栈,DownloadManager 和 MediaPlayer) 将拒绝应用程序使用明文流量的请求。强烈建议第三方库也采用此设置。避免明文通信的主要原因是缺乏机密性,真实性和防篡改保护;网络攻击者可以窃听所传输的数据,并且还可以对其进行修改而不会被检测到。


至此,比较重要的问题都回顾了一遍。

猜你喜欢

转载自blog.csdn.net/EverNess010/article/details/108648351
今日推荐