Files
Ramitta-lib/Ramitta/WeComRobot.cs
2025-12-29 11:48:57 +08:00

259 lines
8.9 KiB
C#
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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();
}
/// <summary>
/// 发送文本消息
/// </summary>
public async Task<bool> 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);
}
/// <summary>
/// 发送Markdown消息
/// </summary>
public async Task<bool> SendMarkdownMessageAsync(string content)
{
var message = new
{
msgtype = "markdown",
markdown = new
{
content = content
}
};
return await SendMessageAsync(message);
}
/// <summary>
/// 发送图片消息
/// </summary>
public async Task<bool> SendImageMessageAsync(string base64, string md5)
{
var message = new
{
msgtype = "image",
image = new
{
base64 = base64,
md5 = md5
}
};
return await SendMessageAsync(message);
}
/// <summary>
/// 发送图文消息
/// </summary>
public async Task<bool> SendNewsMessageAsync(Article[] articles)
{
var message = new
{
msgtype = "news",
news = new
{
articles = articles
}
};
return await SendMessageAsync(message);
}
/// <summary>
/// 发送文件消息
/// </summary>
public async Task<bool> SendFileMessageAsync(string mediaId)
{
var message = new
{
msgtype = "file",
file = new
{
media_id = mediaId
}
};
return await SendMessageAsync(message);
}
private async Task<bool> 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<WeComResponse>(responseContent);
return result?.errcode == 0;
}
Console.WriteLine($"发送失败: {responseContent}");
return false;
}
catch (Exception ex)
{
Console.WriteLine($"发送消息异常: {ex.Message}");
return false;
}
}
/// <summary>
/// 上传文件到企业微信并获取media_id (修正版)
/// </summary>
public async Task<string> 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;
}
}
}
/// <summary>
/// 图文消息文章
/// </summary>
public class Article
{
public string title { get; set; }
public string description { get; set; }
public string url { get; set; }
public string picurl { get; set; }
}
/// <summary>
/// 企业微信响应
/// </summary>
public class WeComResponse
{
public int errcode { get; set; }
public string errmsg { get; set; }
}
}