diff --git a/src/SKIT.FlurlHttpClient.Wechat.Work/Extensions/WechatWorkClientExecuteCgibinMediaExtensions.cs b/src/SKIT.FlurlHttpClient.Wechat.Work/Extensions/WechatWorkClientExecuteCgibinMediaExtensions.cs index 43b26551..55974a6d 100644 --- a/src/SKIT.FlurlHttpClient.Wechat.Work/Extensions/WechatWorkClientExecuteCgibinMediaExtensions.cs +++ b/src/SKIT.FlurlHttpClient.Wechat.Work/Extensions/WechatWorkClientExecuteCgibinMediaExtensions.cs @@ -1,8 +1,6 @@ using System; -using System.Collections.Generic; using System.Net.Http; using System.Net.Http.Headers; -using System.Text; using System.Threading; using System.Threading.Tasks; using System.Web; @@ -109,6 +107,65 @@ namespace SKIT.FlurlHttpClient.Wechat.Work return await client.SendRequestAsync(flurlReq, httpContent: httpContent, cancellationToken: cancellationToken); } + /// + /// 异步调用 [POST] /cgi-bin/media/upload_attachment 接口。 + /// REF: https://open.work.weixin.qq.com/api/doc/90000/90135/95098 + /// REF: https://open.work.weixin.qq.com/api/doc/90001/90143/95178 + /// + /// + /// + /// + /// + public static async Task ExecuteCgibinMediaUploadAttachmentAsync(this WechatWorkClient client, Models.CgibinMediaUploadAttachmentRequest request, CancellationToken cancellationToken = default) + { + if (client is null) throw new ArgumentNullException(nameof(client)); + if (request is null) throw new ArgumentNullException(nameof(request)); + + const string TYPE_IMAGE = "image"; + const string TYPE_VIDEO = "video"; + const string TYPE_FILE = "file"; + + if (string.IsNullOrEmpty(request.FileName)) + { + string ext = ""; + if (TYPE_IMAGE.Equals(request.Type)) + ext = ".png"; + else if (TYPE_VIDEO.Equals(request.Type)) + ext = ".mp4"; + else if (TYPE_FILE.Equals(request.Type)) + ext = ".txt"; + + request.FileName = Guid.NewGuid().ToString("N").ToLower() + ext; + } + + if (string.IsNullOrEmpty(request.FileContentType)) + { + if (TYPE_IMAGE.Equals(request.Type)) + request.FileContentType = Utilities.FileNameToContentTypeMapper.GetContentTypeForImage(request.FileName!) ?? "image/png"; + else if (TYPE_VIDEO.Equals(request.Type)) + request.FileContentType = Utilities.FileNameToContentTypeMapper.GetContentTypeForVideo(request.FileName!) ?? "video/mp4"; + else if (TYPE_FILE.Equals(request.Type)) + request.FileContentType = Utilities.FileNameToContentTypeMapper.GetContentTypeForVoice(request.FileName!) ?? "text/plain"; + else + request.FileContentType = "application/octet-stream"; + } + + IFlurlRequest flurlReq = client + .CreateRequest(request, HttpMethod.Post, "cgi-bin", "media", "upload_attachment") + .SetQueryParam("access_token", request.AccessToken) + .SetQueryParam("type", request.Type); + + string boundary = "--BOUNDARY--" + DateTimeOffset.Now.Ticks.ToString("x"); + using var fileContent = new ByteArrayContent(request.FileBytes ?? new byte[0]); + using var httpContent = new MultipartFormDataContent(boundary); + httpContent.Add(fileContent, "\"media\"", $"\"{HttpUtility.UrlEncode(request.FileName)}\""); + httpContent.Headers.ContentType = MediaTypeHeaderValue.Parse("multipart/form-data; boundary=" + boundary); + fileContent.Headers.ContentType = MediaTypeHeaderValue.Parse(request.FileContentType); + fileContent.Headers.ContentLength = request.FileBytes?.Length; + + return await client.SendRequestAsync(flurlReq, httpContent: httpContent, cancellationToken: cancellationToken); + } + /// /// 异步调用 [GET] /cgi-bin/media/get 接口。 /// REF: https://open.work.weixin.qq.com/api/doc/90000/90135/90254 diff --git a/src/SKIT.FlurlHttpClient.Wechat.Work/Models/CgibinMedia/CgibinMediaUploadAttachmentRequest.cs b/src/SKIT.FlurlHttpClient.Wechat.Work/Models/CgibinMedia/CgibinMediaUploadAttachmentRequest.cs new file mode 100644 index 00000000..dfc9061e --- /dev/null +++ b/src/SKIT.FlurlHttpClient.Wechat.Work/Models/CgibinMedia/CgibinMediaUploadAttachmentRequest.cs @@ -0,0 +1,43 @@ +namespace SKIT.FlurlHttpClient.Wechat.Work.Models +{ + /// + /// 表示 [POST] /cgi-bin/media/upload_attachment 接口的请求。 + /// + public class CgibinMediaUploadAttachmentRequest : WechatWorkRequest + { + /// + /// 获取或设置媒体文件类型。 + /// + [Newtonsoft.Json.JsonIgnore] + [System.Text.Json.Serialization.JsonIgnore] + public string Type { get; set; } = string.Empty; + + /// + /// 获取或设置附件类型。 + /// + [Newtonsoft.Json.JsonIgnore] + [System.Text.Json.Serialization.JsonIgnore] + public string AttachmentType { get; set; } = string.Empty; + + /// + /// 获取或设置文件字节数组。 + /// + [Newtonsoft.Json.JsonIgnore] + [System.Text.Json.Serialization.JsonIgnore] + public byte[] FileBytes { get; set; } = new byte[0]; + + /// + /// 获取或设置文件名。如果不指定将由系统自动生成。 + /// + [Newtonsoft.Json.JsonIgnore] + [System.Text.Json.Serialization.JsonIgnore] + public string? FileName { get; set; } + + /// + /// 获取或设置文件 Conent-Type。如果不指定将由系统自动生成。 + /// + [Newtonsoft.Json.JsonIgnore] + [System.Text.Json.Serialization.JsonIgnore] + public string? FileContentType { get; set; } + } +} diff --git a/src/SKIT.FlurlHttpClient.Wechat.Work/Models/CgibinMedia/CgibinMediaUploadAttachmentResponse.cs b/src/SKIT.FlurlHttpClient.Wechat.Work/Models/CgibinMedia/CgibinMediaUploadAttachmentResponse.cs new file mode 100644 index 00000000..4386a83d --- /dev/null +++ b/src/SKIT.FlurlHttpClient.Wechat.Work/Models/CgibinMedia/CgibinMediaUploadAttachmentResponse.cs @@ -0,0 +1,29 @@ +namespace SKIT.FlurlHttpClient.Wechat.Work.Models +{ + /// + /// 表示 [POST] /cgi-bin/media/upload_attachment 接口的响应。 + /// + public class CgibinMediaUploadAttachmentResponse : WechatWorkResponse + { + /// + /// 获取或设置媒体文件类型。 + /// + [Newtonsoft.Json.JsonProperty("type")] + [System.Text.Json.Serialization.JsonPropertyName("type")] + public string Type { get; set; } = default!; + + /// + /// 获取或设置媒体文件标识。 + /// + [Newtonsoft.Json.JsonProperty("media_id")] + [System.Text.Json.Serialization.JsonPropertyName("media_id")] + public string MediaId { get; set; } = default!; + + /// + /// 获取或设置媒体文件上传时间戳。 + /// + [Newtonsoft.Json.JsonProperty("created_at")] + [System.Text.Json.Serialization.JsonPropertyName("created_at")] + public long CreateTimestamp { get; set; } + } +}