259 lines
8.9 KiB
C#
259 lines
8.9 KiB
C#
|
|
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; }
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
}
|