using HelixToolkit.Maths; using NPOI.HPSF; using System; using System.IO; using System.Net; using System.Net.Http; using System.Text; using System.Text.Json; using System.Threading.Tasks; namespace Ramitta { public class WeComRobot { private readonly string _webhookUrl; private readonly HttpClient _httpClient; public WeComRobot(string webhookUrl) { _webhookUrl = webhookUrl; _httpClient = new HttpClient(); } /// /// 发送文本消息 /// public async Task SendTextMessageAsync(string content, string[] mentionedList = null, string[] mentionedMobileList = null) { var message = new { msgtype = "text", text = new { content = content, mentioned_list = mentionedList, mentioned_mobile_list = mentionedMobileList } }; return await SendMessageAsync(message); } /// /// 发送Markdown消息 /// public async Task SendMarkdownMessageAsync(string content) { var message = new { msgtype = "markdown", markdown = new { content = content } }; return await SendMessageAsync(message); } /// /// 发送图片消息 /// public async Task SendImageMessageAsync(string base64, string md5) { var message = new { msgtype = "image", image = new { base64 = base64, md5 = md5 } }; return await SendMessageAsync(message); } /// /// 发送图文消息 /// public async Task SendNewsMessageAsync(Article[] articles) { var message = new { msgtype = "news", news = new { articles = articles } }; return await SendMessageAsync(message); } /// /// 发送文件消息 /// public async Task SendFileMessageAsync(string mediaId) { var message = new { msgtype = "file", file = new { media_id = mediaId } }; return await SendMessageAsync(message); } private async Task SendMessageAsync(object message) { try { var json = JsonSerializer.Serialize(message); var content = new StringContent(json, Encoding.UTF8, "application/json"); var response = await _httpClient.PostAsync(_webhookUrl, content); var responseContent = await response.Content.ReadAsStringAsync(); if (response.IsSuccessStatusCode) { var result = JsonSerializer.Deserialize(responseContent); return result?.errcode == 0; } Console.WriteLine($"发送失败: {responseContent}"); return false; } catch (Exception ex) { Console.WriteLine($"发送消息异常: {ex.Message}"); return false; } } /// /// 上传文件到企业微信并获取media_id (修正版) /// public async Task UploadFileAsync(string filePath,string? fileName=null) { try { // 1. 从webhook地址中提取key var uri = new Uri(_webhookUrl); var key = System.Web.HttpUtility.ParseQueryString(uri.Query)["key"]; string uploadUrl = $"https://qyapi.weixin.qq.com/cgi-bin/webhook/upload_media?key={key}&type=file"; // 2. 准备文件和边界 if(fileName==null) fileName = Path.GetFileName(filePath); var fileBytes = await File.ReadAllBytesAsync(filePath); string boundary = "------------------------" + DateTime.Now.Ticks.ToString("x"); // 随机边界字符串 string startBoundary = "--" + boundary; string endBoundary = "--" + boundary + "--"; // 3. 创建并配置HttpWebRequest HttpWebRequest request = (HttpWebRequest)WebRequest.Create(uploadUrl); request.Method = "POST"; request.ContentType = "multipart/form-data; boundary=" + boundary; request.Timeout = 30000; // 可根据需要调整超时 // 4. 手动构建并写入请求体(关键步骤) using (Stream requestStream = await request.GetRequestStreamAsync()) { using (var writer = new StreamWriter(requestStream, Encoding.UTF8, 1024, true)) { // 4.1 写入开始边界 (必须有回车换行) await writer.WriteAsync(startBoundary + "\r\n"); await writer.FlushAsync(); // 4.2 写入文件头信息 (注意格式中的双引号) string header = $"Content-Disposition: form-data; name=\"media\"; filename=\"{fileName}\"\r\n"; header += "Content-Type: application/octet-stream\r\n"; header += "\r\n"; // 文件头结束后需要一个额外的空行(即两个连续的\r\n) await writer.WriteAsync(header); await writer.FlushAsync(); // 4.3 直接写入文件字节 await requestStream.WriteAsync(fileBytes, 0, fileBytes.Length); await writer.WriteAsync("\r\n"); // 文件内容后需要一个回车换行 await writer.FlushAsync(); // 4.4 写入结束边界 await writer.WriteAsync(endBoundary + "\r\n"); await writer.FlushAsync(); } } // 5. 获取并解析响应 using (HttpWebResponse response = (HttpWebResponse)await request.GetResponseAsync()) using (StreamReader reader = new StreamReader(response.GetResponseStream())) { string responseContent = await reader.ReadToEndAsync(); Console.WriteLine($"上传响应: {responseContent}"); using (var doc = JsonDocument.Parse(responseContent)) { if (doc.RootElement.TryGetProperty("media_id", out var mediaIdElement)) { return mediaIdElement.GetString(); } else { // 解析错误信息 var errcode = doc.RootElement.GetProperty("errcode").GetInt32(); var errmsg = doc.RootElement.GetProperty("errmsg").GetString(); Console.WriteLine($"文件上传失败: errcode={errcode}, errmsg={errmsg}"); return null; } } } } catch (WebException ex) { // 尝试读取错误响应体 if (ex.Response is HttpWebResponse errorResponse) { using (var reader = new StreamReader(errorResponse.GetResponseStream())) { string errorContent = await reader.ReadToEndAsync(); Console.WriteLine($"HTTP请求失败 ({errorResponse.StatusCode}): {errorContent}"); } } else { Console.WriteLine($"网络请求异常: {ex.Message}"); } return null; } catch (Exception ex) { Console.WriteLine($"文件上传过程异常: {ex.Message}"); return null; } } } /// /// 图文消息文章 /// public class Article { public string title { get; set; } public string description { get; set; } public string url { get; set; } public string picurl { get; set; } } /// /// 企业微信响应 /// public class WeComResponse { public int errcode { get; set; } public string errmsg { get; set; } } }