WebService调用的开发记事(广州玄武科技)

阅读本文必备知识(Http协议、Postman的使用方法),话说这两项都不懂,你都不好意思说自己是码畜😓(真的不懂Http的,我向你推荐罗剑锋老师的【透视Http协议】,至于postman,懂Http后自然就懂了)

本次接到任务要开发发送短信功能,短信服务商是“广州玄武科技”,业务只要求开发单发短信的功能,所以我在文档中找到了我需要的信息,截图如下

 

下面是整个处理过程,首先声明,本人在开发前对WebService根本不懂

1、既然不懂,先上网搜一搜吧,大致了解到,WebService也是一种应用层传输协议,基于Http。那好办,发个请求就完事了,但。。。请求的uri,请求头,body怎么写,这些我看完文档是完全不懂的(也许学过WebService的人能懂)

2、既然不能确定如何发请求,就上客户的服务器上,用postman发几个请求试试,首先先发这个我在文档上看到的第一个uri吧【http://IP :PORT/Service/WebService.asmx?wsdl】(Get请求,虽然文档没写Get还是Post,但两种都试一下就知道了,一般不会出现第三种,至少我码了3年代码都没出现过),结果如下,有东西返回,不错。返回值是xml格式,里面有文档提到的那些接口,估计这得是一个接口的说明文档

 

3、尝试调用其他的接口,先找个“查询”的接口吧(GetAccountInfo),我想不同方法会不会是直接在uri中拼上,类似“http://192.168.5.103:8888/Service/GetAccountInfo”或“http://192.168.5.103:8888/Service/WebService.asmx/GetAccountInfo”,残念ながら、間違ったみたい。两个调用都失败了

 

 

但第二种不是404,值得再研究,又观察第二种的返回跟不加GetAccountInfo的是一样的

 
于是就猜测,这个方法名称可能不是加在url里的,那么还可能是uri传参或者body传参传过去

4、寻找方法名传递的方法,上网搜寻WebService的postman调用,没有发现有人说是uri传参的,那就只剩下body传参,既然是body传参,就要顺便抄抄怎么写,以下是其中一个博文的截图

 这才恍然大悟,そうですね、試してみようか,不管对不对,赶紧试一下,请求方法自然是Post,因为Get没有body。注意一点,既然是要在body传xml了,请求头自然要加上(不然就会返回415 Unsupported Media Type)


5、编写body,抄归抄,不能完全抄,更不要复制粘贴,因为有个“0宽空格”,之前就踩过这个坑。我结合获取文档的返回、错误信息的返回,编写了如下请求(当然也不是一次就改成这样,也失败了几次,期间发送过去的请求一般返回xml解析失败的错误,然后看一下自己哪里格式写错了,改改)

 

终于看到上面的返回值,为找不到分派方法,估计是这个xmlns的锅,猜测是这个xmlns不对,我重新看到【http://IP :PORT/Service/WebService.asmx?wsdl】的返回值,发现在方法名后面有个tns,于是我就想,这个可能是这个方法的一个域,只有在这个域里面才有这个方法

然后,我加了一个xmlns:tns,跟返回值一样的,把方法前面也改成tns。终于,功夫不负有心人,成功了

<S:Envelope xmlns:S="http://schemas.xmlsoap.org/soap/envelope/" xmlns:tns="http://www.139130.net">
	<S:Header></S:Header>
	<S:Body>
		<tns:GetAccountInfo>
			<tns:account>XXX</tns:account>
			<tns:password>YYY</tns:password>
		</tns:GetAccountInfo>
	</S:Body>
</S:Envelope>

其他的方法参照这个body改

6、postman都通了,一切都迎刃而解,下面附上C#的代码,其他语言的照着发http请求就好了,这个不是难事

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;
using System.Xml;

namespace App.SmsService.Business
{
    class Program
    {
        static void Main(string[] args)
        {
            try
            {
                var responseXml = PostSingle("http://192.168.5.103:8888/Service/WebService.asmx", XXX, YYY, "13800138000", "万般皆下品,小倉唯最高", "", "");
                var xmlElement = responseXml.ToXmlElement();
                var res = xmlElement.GetInnerText();
                if (res != "0")
                {
                    throw new Exception(GetMtCodeMsg(res));
                }
            }
            catch (Exception ex)
            {
                throw new Exception(ex.Message);
            }
        }
        
        /// <summary>
        /// 单发短信并返回Http响应体
        /// </summary>
        /// <param name="url"></param>
        /// <param name="account"></param>
        /// <param name="password"></param>
        /// <param name="mobile"></param>
        /// <param name="content"></param>
        /// <param name="phraseIdentify"></param>
        /// <param name="subid"></param>
        /// <returns></returns>
        private static string PostSingle(string url, string account, string password, string mobile, string content, string phraseIdentify, string subid)
        {
            var parameters = new Dictionary<string, string>
            {
                {"account", account},
                {"password", password},
                {"mobile", mobile},
                {"content", content},
                {"phraseIdentify", phraseIdentify},
                {"subid", subid}
            };

            var parameterStr = parameters.Select(c => $"<tns:{c.Key}>{c.Value}</tns:{c.Key}>").JoinAsString(" ");
            var body =
                $"<S:Envelope xmlns:S=\"http://schemas.xmlsoap.org/soap/envelope/\" xmlns:tns=\"http://www.139130.net\"> <S:Header> </S:Header> <S:Body> <tns:PostSingle> {parameterStr} </tns:PostSingle> </S:Body> </S:Envelope>";

            var responseXml = HttpExtension.PostHttp(url, body, ContentType.Xml.ToHeader());
            return responseXml;
        }
        
        /// <summary>
        /// 根据mtCode获取信息
        /// </summary>
        /// <param name="mtCode"></param>
        /// <returns></returns>
        private static string GetMtCodeMsg(string mtCode)
        {
            return mtCode switch
            {
                "0" => "成功",
                "-1" => "账号无效",
                "-2" => "参数:无效",
                "-3" => "连接不上服务器",
                "-5" => "无效的短信数据,号码格式不对",
                "-6" => "用户名密码错误",
                "-7" => "旧密码不正确",
                "-9" => "资金账户不存在",
                "-11" => "包号码数量超过最大限制",
                "-12" => "余额不足",
                "-13" => "账号没有发送权限",
                "-14" => "模板校验没有通过",
                "-99" => "系统内部错误",
                "-100" => "其它错误",
                _ => $"未知错误,mtCode:{mtCode}",
            };
        }
    }

    public static class HttpExtension
    {
        /// <summary>
        /// 发送Get请求
        /// </summary>
        /// <param name="uri"></param>
        /// <param name="requestBody"></param>
        /// <param name="headers"></param>
        /// <returns></returns>
        public static string GetHttp(string uri, string requestBody, Dictionary<string, string> headers = null)
        {
            return SendHttp(uri, requestBody, "GET", headers);
        }

        /// <summary>
        /// 发送Post请求
        /// </summary>
        /// <param name="uri"></param>
        /// <param name="requestBody"></param>
        /// <param name="headers"></param>
        /// <returns></returns>
        public static string PostHttp(string uri, string requestBody, Dictionary<string, string> headers = null)
        {
            return SendHttp(uri, requestBody, "POST", headers);
        }

        /// <summary>
        /// 发送http报文,并返回响应报文的实体
        /// </summary>
        /// <param name="uri">请求uri</param>
        /// <param name="requestBody">请求报文的实体</param>
        /// <param name="method"></param>
        /// <param name="headers">需要额外设置的请求头</param>
        /// <returns></returns>
        private static string SendHttp(string uri, string requestBody, string method = "POST",
            Dictionary<string, string> headers = null)
        {
            var data = Encoding.UTF8.GetBytes(requestBody);
            HttpWebRequest request = (HttpWebRequest)WebRequest.Create(uri);
            request.Method = method;
            //request.ContentType = "application/soap+xml; charset=utf-8";
            if (headers != null && headers.Any())
            {
                foreach (var header in headers)
                {
                    request.Headers.Add(header.Key, header.Value);
                }
            }

            request.Timeout = 30000;
            request.ContentLength = data.Length;
            Stream requestStream = request.GetRequestStream();
            requestStream.Write(data, 0, data.Length);
            requestStream.Close();
            WebResponse webResponse = request.GetResponse();
            StreamReader streamReader =
                new StreamReader(webResponse.GetResponseStream() ?? throw new InvalidOperationException(),
                    Encoding.UTF8);
            var responseBody = streamReader.ReadToEnd();
            streamReader.Close();
            return responseBody;
        }

        /// <summary>
        /// Content-Type转化为Header字典
        /// </summary>
        /// <param name="contentType"></param>
        /// <param name="charSet"></param>
        /// <returns></returns>
        public static Dictionary<string, string> ToHeader(this ContentType contentType, string charSet = "utf-8")
        {
            var dic = new Dictionary<string, string>
            {
                {"Content-Type", $"{contentType.GetValue<ContentTypeAttribute>()}; charset={charSet}"}
            };

            return dic;
        }
    }

    /// <summary>
    /// 常见Content-Type
    /// </summary>
    public enum ContentType
    {
        /// <summary>
        /// HTML格式
        /// </summary>
        [ContentType("text/html")]
        Html,
        /// <summary>
        /// 纯文本
        /// </summary>
        [ContentType("text/plain")]
        Plain,
        /// <summary>
        /// Xml格式
        /// </summary>
        [ContentType("text/xml")]
        Xml,
        /// <summary>
        /// WebService 1.2用的Xml
        /// </summary>
        [ContentType("text/soap+xml")]
        Soap12,
        /// <summary>
        /// Json格式
        /// </summary>
        [ContentType("application/json")]
        Json,
        /// <summary>
        /// 表单中默认的encType,表单数据被编码为key/value格式发送到服务器
        /// </summary>
        [ContentType("application/x-www-form-urlencoded")]
        FormUrlencoded,
        /// <summary>
        /// 表单数据
        /// </summary>
        [ContentType("multipart/form-data")]
        FormData,
        /// <summary>
        /// 二进制流数据
        /// </summary>
        [ContentType("application/octet-stream")]
        Stream
    }

    [AttributeUsage(AttributeTargets.All)]
    public abstract class AttributeBase : Attribute
    {
        protected AttributeBase(string value)
        {
            Value = value;
        }

        public string Value { get; protected set; }
    }
    /// <summary>
    /// 特性的拓展
    /// </summary>
    public static class AttributeExtension
    {
        /// <summary>
        /// 获取特性值
        /// </summary>
        /// <param name="value"></param>
        /// <returns></returns>
        public static string GetValue<TAttributeType>(this Enum value) where TAttributeType : AttributeBase
        {
            var str = string.Empty;
            try
            {
                TAttributeType userTypeAttribute = (TAttributeType)((IEnumerable<object>)value.GetType().GetField(value.ToString()).GetCustomAttributes(typeof(TAttributeType), false)).FirstOrDefault<object>();
                str = userTypeAttribute == null ? value.ToString() : userTypeAttribute.Value;
            }
            catch
            {
                // ignore
            }

            return str;
        }
    }

    public class ContentTypeAttribute : AttributeBase
    {
        public ContentTypeAttribute(string value) : base(value)
        {
        }
    }
    
    public static class XmlExtension
    {
        /// <summary>
        /// 字符串转XmlElement
        /// </summary>
        /// <param name="xml"></param>
        /// <returns></returns>
        public static XmlElement ToXmlElement(this string xml)
        {
            var xmlDocument = new XmlDocument();
            xmlDocument.LoadXml(xml);
            var root = xmlDocument.DocumentElement;
            return root;
        }

        /// <summary>
        /// 获取InnerText
        /// </summary>
        /// <param name="element"></param>
        /// <param name="path">形如:./XX/YY</param>
        /// <returns></returns>
        public static string GetInnerText(this XmlElement element, string path = ".")
        {
            var innerText = element.SelectSingleNode(path)?.InnerText;
            return innerText;
        }
    }
}
C#发短信(广州玄武)

 

最后,我再吐个槽,这些个短信厂商、单点登录厂商都根本不会编写文档,又不是什么人都看得懂你们说的话(技术点),但一定所有码农都懂Http,你放一张postman的截图、wireshark的包亦或者是Http报文,都好过你花那么大力气写那么一大坨

posted @ 2021-06-17 15:39  小倉唯  阅读(208)  评论(0编辑  收藏  举报