.NET WeChat SDK 기반 기사 공유
1. 서문
저는 최근 위챗 개발을 유난히 싫어하는 사람입니다. 다른 사람이 작성한 일부 WeChat 개발 "프레임워크"를 다운로드했지만 구현이 너무 부풀려져서 역겨웠습니다.
제가 가장 싫어하는 것은 WeChat에서 반환한 xml 메시지를 엔터티 클래스로 조립하는 것입니다. 그래서 요즘은 경량화가 옹호되기 때문에 큰 문제를 피할 수 있는 방법이 없습니다. 엔터티 클래스의 수?
물론 패키징은 상대적으로 복잡합니다. 공식 API를 읽은 후 "프레임워크"를 보면 사람들이 혼란스러워지고 충분히 명확하지 않게 됩니다 .
2. 내 구현 아이디어
내 WeChat SDK(감히 프레임워크라고 부르지는 않음)에서 가장 중요한 것은 두 가지 목표를 달성하는 것입니다.
1 .Lightweight는 엔터티 클래스를 포기하고 Entity를 최대한 적게 선언하며 SDK의 크기를 줄이는 것을 의미합니다.
2. 간단하고 명확합니다. 즉, SDK 클래스 구분이 공식과 일치합니다. API를 한눈에 쉽게 알 수 있도록 하신 의도를 이해합니다.
사실 WeChat을 호출하는 작동 원리는 프레임워크 등을 고안할 필요가 전혀 없으며 자격을 갖춘 프로그래머라면 누구나 할 수 있다고 믿습니다.
우리 서버는 WeChat과 통신하기 위해 GET과 POST만 필요합니다. 이런 관점에서 보면 디자인이 상당히 사용자 친화적입니다. GET은 WeChat 서비스의 확인 및 확인에 연결하는 데 사용되며 POST는 WeChat 서버에서 메시지를 수신한 다음 응답을 조합하여 반환하는 데 사용됩니다.
3. 코드 업로드알았어, 더 이상 말도 안 되는 소리는 하지 마세요.
WeChat 서버 Post는 xml 메시지를 주고 json을 반환하므로 서로 전송해야 합니다. 이렇게 세 가지 유형의 형식이 있습니다. 이는 프레임워크에서 정의한 엔터티 클래스가 많아 프레임워크가 충분히 가볍지 않은 이유이기도 합니다.
첫 번째 목표를 달성하기 위해 .net Framework 4.0의 Dynamic 기능을 주로 사용했고, xml 문자열을 Dynamic Object로 자동 변환하는 DynamicXml.cs 클래스
와DynamicJson.cs를 사용했습니다. json 문자열을 동적 개체로 자동 변환하는 클래스입니다. 열심히 검색한 끝에 드디어 원하는 것을 찾았습니다.
1. 다음은 DynamicXml.cs 클래스이며, 파일 헤더에는 원저작자의 저작권 정보가 포함되어 있습니다.
/*-------------------------------------------------------------------------- * https://www.captechconsulting.com/blog/kevin-hazzard/fluent-xml-parsing-using-cs-dynamic-type-part-1 * 博客园网友 夜の魔王 友情借用此代码,用于微信开发。 * http://www.cnblogs.com/deepleo/*--------------------------------------------------------------------------*/using System;using System.Collections.Generic;using System.Linq;using System.Dynamic;using System.Xml.Linq;using System.Collections;public class DynamicXml : DynamicObject, IEnumerable { private readonly List<XElement> _elements; public DynamicXml(string text) { var doc = XDocument.Parse(text); _elements = new List<XElement> { doc.Root }; } protected DynamicXml(XElement element) { _elements = new List<XElement> { element }; } protected DynamicXml(IEnumerable<XElement> elements) { _elements = new List<XElement>(elements); } public override bool TryGetMember(GetMemberBinder binder, out object result) { result = null; if (binder.Name == "Value") result = _elements[0].Value; else if (binder.Name == "Count") result = _elements.Count; else { var attr = _elements[0].Attribute(XName.Get(binder.Name)); if (attr != null) result = attr; else { var items = _elements.Descendants(XName.Get(binder.Name)); if (items == null || items.Count() == 0) return false; result = new DynamicXml(items); } } return true; } public override bool TryGetIndex(GetIndexBinder binder, object[] indexes, out object result) { int ndx = (int)indexes[0]; result = new DynamicXml(_elements[ndx]); return true; } public IEnumerator GetEnumerator() { foreach (var element in _elements) yield return new DynamicXml(element); } }


이 코드는 자세히 보지는 않았지만, 어쨌든 작동하고 아무것도 잘못되지 않았습니다.
2.다음은 DynamicJson.cs 클래스입니다. 파일 헤더에는 원저작자의 저작권 정보가 있습니다.
/*-------------------------------------------------------------------------- * DynamicJson * ver 1.2.0.0 (May. 21th, 2010) * * created and maintained by neuecc <ils@neue.cc> * licensed under Microsoft Public License(Ms-PL) * http://neue.cc/* http://dynamicjson.codeplex.com/ * 博客园网友 夜の魔王 友情借用此代码,用于微信开发。 * http://www.cnblogs.com/deepleo/*--------------------------------------------------------------------------*/using System;using System.Collections;using System.Collections.Generic;using System.Diagnostics;using System.Dynamic;using System.IO;using System.Linq;using System.Reflection;using System.Runtime.Serialization.Json;using System.Text;using System.Xml;using System.Xml.Linq;namespace Codeplex.Data { public class DynamicJson : DynamicObject { private enum JsonType { @string, number, boolean, @object, array, @null } // public static methods /// <summary>from JsonSring to DynamicJson</summary> public static dynamic Parse(string json) { return Parse(json, Encoding.Unicode); } /// <summary>from JsonSring to DynamicJson</summary> public static dynamic Parse(string json, Encoding encoding) { using (var reader = JsonReaderWriterFactory.CreateJsonReader(encoding.GetBytes(json), XmlDictionaryReaderQuotas.Max)) { return ToValue(XElement.Load(reader)); } } /// <summary>from JsonSringStream to DynamicJson</summary> public static dynamic Parse(Stream stream) { using (var reader = JsonReaderWriterFactory.CreateJsonReader(stream, XmlDictionaryReaderQuotas.Max)) { return ToValue(XElement.Load(reader)); } } /// <summary>from JsonSringStream to DynamicJson</summary> public static dynamic Parse(Stream stream, Encoding encoding) { using (var reader = JsonReaderWriterFactory.CreateJsonReader(stream, encoding, XmlDictionaryReaderQuotas.Max, _ => { })) { return ToValue(XElement.Load(reader)); } } /// <summary>create JsonSring from primitive or IEnumerable or Object({public property name:property value})</summary> public static string Serialize(object obj) { return CreateJsonString(new XStreamingElement("root", CreateTypeAttr(GetJsonType(obj)), CreateJsonNode(obj))); } // private static methods private static dynamic ToValue(XElement element) { var type = (JsonType)Enum.Parse(typeof(JsonType), element.Attribute("type").Value); switch (type) { case JsonType.boolean: return (bool)element; case JsonType.number: return (double)element; case JsonType.@string: return (string)element; case JsonType.@object: case JsonType.array: return new DynamicJson(element, type); case JsonType.@null: default: return null; } } private static JsonType GetJsonType(object obj) { if (obj == null) return JsonType.@null; switch (Type.GetTypeCode(obj.GetType())) { case TypeCode.Boolean: return JsonType.boolean; case TypeCode.String: case TypeCode.Char: case TypeCode.DateTime: return JsonType.@string; case TypeCode.Int16: case TypeCode.Int32: case TypeCode.Int64: case TypeCode.UInt16: case TypeCode.UInt32: case TypeCode.UInt64: case TypeCode.Single: case TypeCode.Double: case TypeCode.Decimal: case TypeCode.SByte: case TypeCode.Byte: return JsonType.number; case TypeCode.Object: return (obj is IEnumerable) ? JsonType.array : JsonType.@object; case TypeCode.DBNull: case TypeCode.Empty: default: return JsonType.@null; } } private static XAttribute CreateTypeAttr(JsonType type) { return new XAttribute("type", type.ToString()); } private static object CreateJsonNode(object obj) { var type = GetJsonType(obj); switch (type) { case JsonType.@string: case JsonType.number: return obj; case JsonType.boolean: return obj.ToString().ToLower(); case JsonType.@object: return CreateXObject(obj); case JsonType.array: return CreateXArray(obj as IEnumerable); case JsonType.@null: default: return null; } } private static IEnumerable<XStreamingElement> CreateXArray<T>(T obj) where T : IEnumerable { return obj.Cast<object>() .Select(o => new XStreamingElement("item", CreateTypeAttr(GetJsonType(o)), CreateJsonNode(o))); } private static IEnumerable<XStreamingElement> CreateXObject(object obj) { return obj.GetType() .GetProperties(BindingFlags.Public | BindingFlags.Instance) .Select(pi => new { Name = pi.Name, Value = pi.GetValue(obj, null) }) .Select(a => new XStreamingElement(a.Name, CreateTypeAttr(GetJsonType(a.Value)), CreateJsonNode(a.Value))); } private static string CreateJsonString(XStreamingElement element) { using (var ms = new MemoryStream()) using (var writer = JsonReaderWriterFactory.CreateJsonWriter(ms, Encoding.Unicode)) { element.WriteTo(writer); writer.Flush(); return Encoding.Unicode.GetString(ms.ToArray()); } } // dynamic structure represents JavaScript Object/Array readonly XElement xml; readonly JsonType jsonType; /// <summary>create blank JSObject</summary> public DynamicJson() { xml = new XElement("root", CreateTypeAttr(JsonType.@object)); jsonType = JsonType.@object; } private DynamicJson(XElement element, JsonType type) { Debug.Assert(type == JsonType.array || type == JsonType.@object); xml = element; jsonType = type; } public bool IsObject { get { return jsonType == JsonType.@object; } } public bool IsArray { get { return jsonType == JsonType.array; } } /// <summary>has property or not</summary> public bool IsDefined(string name) { return IsObject && (xml.Element(name) != null); } /// <summary>has property or not</summary> public bool IsDefined(int index) { return IsArray && (xml.Elements().ElementAtOrDefault(index) != null); } /// <summary>delete property</summary> public bool Delete(string name) { var elem = xml.Element(name); if (elem != null) { elem.Remove(); return true; } else return false; } /// <summary>delete property</summary> public bool Delete(int index) { var elem = xml.Elements().ElementAtOrDefault(index); if (elem != null) { elem.Remove(); return true; } else return false; } /// <summary>mapping to Array or Class by Public PropertyName</summary> public T Deserialize<T>() { return (T)Deserialize(typeof(T)); } private object Deserialize(Type type) { return (IsArray) ? DeserializeArray(type) : DeserializeObject(type); } private dynamic DeserializeValue(XElement element, Type elementType) { var value = ToValue(element); if (value is DynamicJson) { value = ((DynamicJson)value).Deserialize(elementType); } return Convert.ChangeType(value, elementType); } private object DeserializeObject(Type targetType) { var result = Activator.CreateInstance(targetType); var dict = targetType.GetProperties(BindingFlags.Public | BindingFlags.Instance) .Where(p => p.CanWrite) .ToDictionary(pi => pi.Name, pi => pi); foreach (var item in xml.Elements()) { PropertyInfo propertyInfo; if (!dict.TryGetValue(item.Name.LocalName, out propertyInfo)) continue; var value = DeserializeValue(item, propertyInfo.PropertyType); propertyInfo.SetValue(result, value, null); } return result; } private object DeserializeArray(Type targetType) { if (targetType.IsArray) // Foo[] { var elemType = targetType.GetElementType(); dynamic array = Array.CreateInstance(elemType, xml.Elements().Count()); var index = 0; foreach (var item in xml.Elements()) { array[index++] = DeserializeValue(item, elemType); } return array; } else // List<Foo> { var elemType = targetType.GetGenericArguments()[0]; dynamic list = Activator.CreateInstance(targetType); foreach (var item in xml.Elements()) { list.Add(DeserializeValue(item, elemType)); } return list; } } // Delete public override bool TryInvoke(InvokeBinder binder, object[] args, out object result) { result = (IsArray) ? Delete((int)args[0]) : Delete((string)args[0]); return true; } // IsDefined, if has args then TryGetMember public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result) { if (args.Length > 0) { result = null; return false; } result = IsDefined(binder.Name); return true; } // Deserialize or foreach(IEnumerable) public override bool TryConvert(ConvertBinder binder, out object result) { if (binder.Type == typeof(IEnumerable) || binder.Type == typeof(object[])) { var ie = (IsArray) ? xml.Elements().Select(x => ToValue(x)) : xml.Elements().Select(x => (dynamic)new KeyValuePair<string, object>(x.Name.LocalName, ToValue(x))); result = (binder.Type == typeof(object[])) ? ie.ToArray() : ie; } else { result = Deserialize(binder.Type); } return true; } private bool TryGet(XElement element, out object result) { if (element == null) { result = null; return false; } result = ToValue(element); return true; } public override bool TryGetIndex(GetIndexBinder binder, object[] indexes, out object result) { return (IsArray) ? TryGet(xml.Elements().ElementAtOrDefault((int)indexes[0]), out result) : TryGet(xml.Element((string)indexes[0]), out result); } public override bool TryGetMember(GetMemberBinder binder, out object result) { return (IsArray) ? TryGet(xml.Elements().ElementAtOrDefault(int.Parse(binder.Name)), out result) : TryGet(xml.Element(binder.Name), out result); } private bool TrySet(string name, object value) { var type = GetJsonType(value); var element = xml.Element(name); if (element == null) { xml.Add(new XElement(name, CreateTypeAttr(type), CreateJsonNode(value))); } else { element.Attribute("type").Value = type.ToString(); element.ReplaceNodes(CreateJsonNode(value)); } return true; } private bool TrySet(int index, object value) { var type = GetJsonType(value); var e = xml.Elements().ElementAtOrDefault(index); if (e == null) { xml.Add(new XElement("item", CreateTypeAttr(type), CreateJsonNode(value))); } else { e.Attribute("type").Value = type.ToString(); e.ReplaceNodes(CreateJsonNode(value)); } return true; } public override bool TrySetIndex(SetIndexBinder binder, object[] indexes, object value) { return (IsArray) ? TrySet((int)indexes[0], value) : TrySet((string)indexes[0], value); } public override bool TrySetMember(SetMemberBinder binder, object value) { return (IsArray) ? TrySet(int.Parse(binder.Name), value) : TrySet(binder.Name, value); } public override IEnumerable<string> GetDynamicMemberNames() { return (IsArray) ? xml.Elements().Select((x, i) => i.ToString()) : xml.Elements().Select(x => x.Name.LocalName); } /// <summary>Serialize to JsonString</summary> public override string ToString() { // <foo type="null"></foo> is can't serialize. replace to <foo type="null" /> foreach (var elem in xml.Descendants().Where(x => x.Attribute("type").Value == "null")) { elem.RemoveNodes(); } return CreateJsonString(new XStreamingElement("root", CreateTypeAttr(jsonType), xml.Elements())); } } }


아직 이 코드를 자세히 살펴보지는 않았지만 어쨌든 오류 없이 작동합니다.
이 핵심 장애물이 해결되면 나머지는 자연스럽게 진행됩니다.
3. API 패키징 기본 지원
/*-------------------------------------------------------------------------- * BasicAPI.cs *Auth:deepleo * Date:2013.12.31 * Email:2586662969@qq.com *--------------------------------------------------------------------------*/using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Net.Http;using Codeplex.Data;using System.IO;namespace Deepleo.Weixin.SDK { /// <summary> /// 对应微信API的 "基础支持" /// </summary> public class BasicAPI { /// <summary> /// 检查签名是否正确: /// http://mp.weixin.qq.com/wiki/index.php?title=%E6%8E%A5%E5%85%A5%E6%8C%87%E5%8D%97 /// </summary> /// <param name="signature"></param> /// <param name="timestamp"></param> /// <param name="nonce"></param> /// <param name="token">AccessToken</param> /// <returns> /// true: check signature success /// false: check failed, 非微信官方调用! /// </returns> public static bool CheckSignature(string signature, string timestamp, string nonce, string token, out string ent) { var arr = new[] { token, timestamp, nonce }.OrderBy(z => z).ToArray(); var arrString = string.Join("", arr); var sha1 = System.Security.Cryptography.SHA1.Create(); var sha1Arr = sha1.ComputeHash(Encoding.UTF8.GetBytes(arrString)); StringBuilder enText = new StringBuilder(); foreach (var b in sha1Arr) { enText.AppendFormat("{0:x2}", b); } ent = enText.ToString(); return signature == enText.ToString(); } /// <summary> /// 获取AccessToken /// http://mp.weixin.qq.com/wiki/index.php?title=%E8%8E%B7%E5%8F%96access_token /// </summary> /// <param name="grant_type"></param> /// <param name="appid"></param> /// <param name="secrect"></param> /// <returns>access_toke</returns> public static dynamic GetAccessToken( string appid, string secrect) { var url = string.Format("https://api.weixin.qq.com/cgi-bin/token?grant_type={0}&appid={1}&secret={2}", "client_credential", appid, secrect); var client = new HttpClient(); var result = client.GetAsync(url).Result; if (!result.IsSuccessStatusCode) return string.Empty; var token = DynamicJson.Parse(result.Content.ReadAsStringAsync().Result); return token; } /// <summary> /// 上传多媒体文件 /// http://mp.weixin.qq.com/wiki/index.php?title=%E4%B8%8A%E4%BC%A0%E4%B8%8B%E8%BD%BD%E5%A4%9A%E5%AA%92%E4%BD%93%E6%96%87%E4%BB%B6 /// 1.上传的媒体文件限制: ///图片(image) : 1MB,支持JPG格式 ///语音(voice):1MB,播放长度不超过60s,支持MP4格式 ///视频(video):10MB,支持MP4格式 ///缩略图(thumb):64KB,支持JPG格式 ///2.媒体文件在后台保存时间为3天,即3天后media_id失效 /// </summary> /// <param name="token"></param> /// <param name="type"></param> /// <param name="file"></param> /// <returns>media_id</returns> public static string UploadMedia(string token, string type, string file) { var url = string.Format("http://api.weixin.qq.com/cgi-bin/media/upload?access_token={0}&type={1}&filename={2}", token, type, Path.GetFileName(file)); var client = new HttpClient(); var result = client.PostAsync(url, new StreamContent(new FileStream(file, FileMode.Open, FileAccess.Read))); if (!result.Result.IsSuccessStatusCode) return string.Empty; var media = DynamicJson.Parse(result.Result.Content.ReadAsStringAsync().Result); return media.media_id; } } }


4.发送消息包装
/*-------------------------------------------------------------------------- * SendMessageAPI.cs *Auth:deepleo * Date:2013.12.31 * Email:2586662969@qq.com *--------------------------------------------------------------------------*/using System;using System.Collections.Generic;using System.Linq;using System.Text;using Codeplex.Data;using System.Net;using System.Net.Http;namespace Deepleo.Weixin.SDK { /// <summary> /// 对应微信API的 "发送消息” /// </summary> public class SendMessageAPI { /// <summary> /// 被动回复消息 /// </summary> /// <param name="message">微信服务器推送的消息</param> /// <param name="executor">用户自定义的消息执行者</param> /// <returns></returns> public static string Relay(WeixinMessage message, IWeixinExecutor executor) { return executor.Execute(message); } /// <summary> /// 主动发送客服消息 /// http://mp.weixin.qq.com/wiki/index.php?title=%E5%8F%91%E9%80%81%E5%AE%A2%E6%9C%8D%E6%B6%88%E6%81%AF /// 当用户主动发消息给公众号的时候 /// 开发者在一段时间内(目前为24小时)可以调用客服消息接口,在24小时内不限制发送次数。 /// </summary> /// <param name="token"></param> /// <param name="msg">json格式的消息,具体格式请参考微信官方API</param> /// <returns></returns> public static bool Send(string token, string msg) { var client = new HttpClient(); var task = client.PostAsync(string.Format("https://api.weixin.qq.com/cgi-bin/message/custom/send?access_token={0}", token), new StringContent(msg)).Result; return task.IsSuccessStatusCode; } } }
View Code
5.其他代码就不一一贴出来了。可以在文章最后自行下载完整代码查阅。
6.处理与微信服务器通信的WeixinController.cs,WeixinExecutor.cs
using System;using System.Collections.Generic;using System.IO;using System.Linq;using System.Text;using System.Web;using System.Web.Mvc;using System.Xml.Linq;using Deepleo.Weixin.SDK;using Deepleo.NewTon.Web.Services;using System.Xml;using Deepleo.NewTon.Framework.Services.Admin;using Deepleo.Log;namespace Deepleo.NewTon.Web.Controllers { public class WeixinController : Controller { public WeixinController() { } /// <summary> /// 微信后台验证地址(使用Get),微信后台的“接口配置信息”的Url /// </summary> [HttpGet] [ActionName("Index")] public ActionResult Get(string signature, string timestamp, string nonce, string echostr) { var token = new SettingsService().Get().Token; if (string.IsNullOrEmpty(_token)) return Content("请先设置Token!"); var ent = ""; if (!BasicAPI.CheckSignature(signature, timestamp, nonce, _token, out ent)) { new WeixinLogService().Create(new Framework.Entities.WeixinLog("get(failed)", string.Format("(get weixin)signature:{0},timestamp:{1},nonce:{2},echostr:{3},ent:{4},token:{5}", signature, timestamp, nonce, echostr, ent, _token))); return Content("参数错误!"); } return Content(echostr); //返回随机字符串则表示验证通过 } /// <summary> /// 用户发送消息后,微信平台自动Post一个请求到这里,并等待响应XML。 /// </summary> [HttpPost] [ActionName("Index")] public ActionResult Post(string signature, string timestamp, string nonce, string echostr) { WeixinMessage message = null; using (var streamReader = new StreamReader(Request.InputStream)) { message = AcceptMessageAPI.Parse(streamReader.ReadToEnd()); } var response = new WeixinExecutor().Execute(message); return new ContentResult { Content = response, ContentType = "text/xml", ContentEncoding = System.Text.UTF8Encoding.UTF8 }; } } }
View Code
/*-------------------------------------------------------------------------- * WeixinExecutor.cs *Auth:deepleo * Date:2013.12.31 * Email:2586662969@qq.com *--------------------------------------------------------------------------*/using System;using System.Collections.Generic;using System.Linq;using System.Web;using Deepleo.Weixin.SDK;using System.Text;using System.Text.RegularExpressions;namespace Deepleo.Weixin { public class WeixinExecutor : IWeixinExecutor { public WeixinExecutor() { } public string Execute(WeixinMessage message) { var result = ""; string openId = message.Body.FromUserName.Value; var myUserName = message.Body.ToUserName.Value; switch (message.Type) { case WeixinMessageType.Text: string userMessage = message.Body.Content.Value; result = RepayText(openId, myUserName, "欢迎使用"); break; case WeixinMessageType.Event: string eventType = message.Body.Event.Value.ToLower(); string eventKey = message.Body.EventKey.Value; switch (eventType) { case "subscribe": result = RepayText(openId, myUserName, "欢迎订阅"); break; case "unsubscribe": result = RepayText(openId, myUserName, "欢迎再来"); break; case "scan": result = RepayText(openId, myUserName, "欢迎使用"); break; case "location"://用户进入应用时记录用户地理位置 #region location var lat = message.Body.Latitude.Value.ToString(); var lng = message.Body.Longitude.Value.ToString(); var pcn = message.Body.Precision.Value.ToString(); #endregion break; case "click": switch (eventKey)//refer to: Recources/menu.json { case "myaccount": #region 我的账户 result = RepayText(openId, myUserName, "我的账户."); #endregion break; default: result = string.Format("<xml><ToUserName><![CDATA[{0}]]></ToUserName>" + "<FromUserName><![CDATA[{1}]]></FromUserName>" + "<CreateTime>{2}</CreateTime>" + "<MsgType><![CDATA[text]]></MsgType>" + "<Content><![CDATA[{3}]]></Content>" + "</xml>", openId, myUserName, DateTime.Now.ToBinary(), "没有响应菜单事件"); break; } break; } break; default: result = string.Format("<xml><ToUserName><![CDATA[{0}]]></ToUserName>" + "<FromUserName><![CDATA[{1}]]></FromUserName>" + "<CreateTime>{2}</CreateTime>" + "<MsgType><![CDATA[text]]></MsgType>" + "<Content><![CDATA[{3}]]></Content>" + "</xml>", openId, myUserName, DateTime.Now.ToBinary(), string.Format("未处理消息类型:{0}", message.Type)); break; } return result; } private string RepayText(string toUserName, string fromUserName, string content) { return string.Format("<xml><ToUserName><![CDATA[{0}]]></ToUserName>" + "<FromUserName><![CDATA[{1}]]></FromUserName>" + "<CreateTime>{2}</CreateTime>" + "<MsgType><![CDATA[text]]></MsgType>" + "<Content><![CDATA[{3}]]></Content>" + "</xml>", toUserName, fromUserName, DateTime.Now.ToBinary(), content); } private string RepayNews(string toUserName, string fromUserName, List<WeixinNews> news) { var couponesBuilder = new StringBuilder(); couponesBuilder.Append(string.Format("<xml><ToUserName><![CDATA[{0}]]></ToUserName>" + "<FromUserName><![CDATA[{1}]]></FromUserName>" + "<CreateTime>{2}</CreateTime>" + "<MsgType><![CDATA[news]]></MsgType>" + "<ArticleCount>{3}</ArticleCount><Articles>", toUserName, fromUserName, DateTime.Now.ToBinary(), news.Count )); foreach (var c in news) { couponesBuilder.Append(string.Format("<item><Title><![CDATA[{0}]]></Title>" + "<Description><![CDATA[{1}]]></Description>" + "<PicUrl><![CDATA[{2}]]></PicUrl>" + "<Url><![CDATA[{3}]]></Url>" + "</item>", c.Title, c.Description, c.PicUrl, c.Url )); } couponesBuilder.Append("</Articles></xml>"); return couponesBuilder.ToString(); } } public class WeixinNews { public string Title { set; get; } public string Description { set; get; } public string PicUrl { set; get; } public string Url { set; get; } } }
위 내용은 .NET WeChat SDK 기반 기사 공유의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

핫 AI 도구

Undresser.AI Undress
사실적인 누드 사진을 만들기 위한 AI 기반 앱

AI Clothes Remover
사진에서 옷을 제거하는 온라인 AI 도구입니다.

Undress AI Tool
무료로 이미지를 벗다

Clothoff.io
AI 옷 제거제

Video Face Swap
완전히 무료인 AI 얼굴 교환 도구를 사용하여 모든 비디오의 얼굴을 쉽게 바꾸세요!

인기 기사

뜨거운 도구

메모장++7.3.1
사용하기 쉬운 무료 코드 편집기

SublimeText3 중국어 버전
중국어 버전, 사용하기 매우 쉽습니다.

스튜디오 13.0.1 보내기
강력한 PHP 통합 개발 환경

드림위버 CS6
시각적 웹 개발 도구

SublimeText3 Mac 버전
신 수준의 코드 편집 소프트웨어(SublimeText3)

빌드 22523용 새로운 Windows 11 SDK는 Microsoft가 Windows 11을 위한 새로운 흐림 효과를 개발하고 있음을 밝혔습니다. 이 효과는 Tabbed라고 하며 아크릴과 운모에 추가됩니다. 22523 SDK의 새로운 DWMWA_SYSTEMBACKDROP_TYPE, Mica, Acrylic의 공개 Win32 API 및 이상한 새로운 "탭" 혼합: pic.twitter.com/dbsu7ZFiIi — It's All Back(@StartIsBack) 2021년 12월 15일 다음 SDK의 샘플 애플리케이션에서 사용 가능

Java Hikvision SDK의 2차 개발을 위한 필수 기술을 습득합니다. 소개: 정보 기술의 급속한 발전으로 영상 감시 시스템이 다양한 분야에서 널리 사용되고 있습니다. 국내 최고의 영상 감시 솔루션 제공업체로서 Hikvision의 제품과 기술은 항상 시장에서 중요한 위치를 차지하고 있습니다. 다양한 프로젝트의 요구 사항을 충족하기 위해 Hikvision은 개발자가 2차 개발을 수행할 수 있는 SDK를 제공합니다. 이 기사에서는 Java Hikvision SDK의 2차 개발을 마스터하기 위한 몇 가지 필수 기술을 소개하고 해당 코드 예제를 첨부합니다. 1. 하이크비전 이해하기

SDK의 전체 이름은 "소프트웨어 개발 키트"이며 중국어로 "소프트웨어 개발 키트"를 의미합니다. 하드웨어 플랫폼, 운영 체제(OS) 또는 프로그래밍 언어 제조업체가 제공하는 도구 세트입니다. SDK는 소프트웨어 개발자가 특정 플랫폼, 시스템 또는 프로그래밍 언어용 애플리케이션을 만드는 데 도움을 줍니다. 기본 SDK는 일반적으로 컴파일러, 디버거 및 API(애플리케이션 프로그래밍 인터페이스)로 구성되지만 문서, 라이브러리, 런타임/개발 환경, 테스트/분석 도구, 네트워크 프로토콜 등과 같은 다른 콘텐츠도 포함될 수 있습니다.

WindowsAppSDK는 개발자가 Windows 애플리케이션에서 Windows 10(버전 1809 이상) 및 Windows 11을 사용하는 다양한 장치에 "일관된" 기능을 제공하는 데 사용할 수 있는 도구 및 API 세트입니다. .NET 또는 Windows SDK와 같은 기존 애플리케이션 유형을 대체하는 것이 아니라 기존 애플리케이션을 보완하는 데 사용할 수 있는 통합 API 도구 세트를 제공한다는 점을 이해하는 것이 정말 중요합니다. 오늘 Microsoft는 많은 새로운 기능이 포함된 Windows App SDK 버전 1.2를 출시했습니다. 이번 릴리스의 하이라이트는 타사 개발자일 수 있습니다.

PHP는 웹 개발 및 서버 측 프로그래밍, 특히 WeChat 개발에 널리 사용되는 오픈 소스 스크립팅 언어입니다. 오늘날 점점 더 많은 회사와 개발자가 WeChat 개발에 PHP를 사용하기 시작하고 있습니다. PHP는 배우기 쉽고 사용하기 쉬운 개발 언어이기 때문입니다. WeChat 개발에서 메시지 암호화 및 복호화는 데이터 보안과 관련되어 있기 때문에 매우 중요한 문제입니다. 암호화 및 복호화 방법이 없는 메시지의 경우 해커가 쉽게 데이터를 획득할 수 있어 사용자에게 위협이 될 수 있습니다.

WeChat 미니 프로그램 PHPSDK 설치 및 사용 모바일 인터넷의 급속한 발전과 함께 WeChat 미니 프로그램은 점점 더 많은 기업이 비즈니스를 수행하고 제품을 홍보하는 새로운 방법이 되었습니다. WeChat Mini 프로그램 PHPSDK는 개발자에게 편리하고 빠른 개발 도구를 제공하여 개발 효율성을 크게 향상시킬 수 있습니다. 이 기사에서는 WeChat 애플릿 PHPSDK의 설치 및 사용 방법을 소개합니다. 1. SDK 설치 1. GitHub에서 프로젝트 파일을 다운로드합니다. WeChat 애플릿 PHPSDK는 오픈 소스 프로젝트입니다. GitHub에서 다운로드할 수 있습니다.

이 기사에서는 개발자가 몇 가지 간단한 단계로 SDK 설치를 완료할 수 있도록 PHP Alipay SDK에 대한 자세한 설치 가이드를 제공합니다.

Linux의 SDK는 컴파일러, 디버거, 라이브러리 파일, 헤더 파일 등과 같은 도구와 리소스가 포함된 폴더입니다. SDK는 소프트웨어 개발 키트(Software Development Kit)의 약어로, 개발자가 특히 Linux 운영 체제에서 실행되는 응용 프로그램을 개발하고 구축할 수 있도록 제공되는 통합 환경입니다.
