微信/企业微信通知模块 (C#)
基于方糖的微信服务号通知 和 企业微信应用通知
需要安装两个库
- RestSharp
- RestSharp.Serializers.NewtonsoftJson
Program.cs 代码如下:
using Push.Core.Abstracts;
using Push.Core.Implements.FangTang;
using Push.Core.Implements.WeCom;
// 方糖官网:https://sct.ftqq.com/
{
IPusher<FangTangInput, FangTangOutput> push = new FangTangPusher(new FangTangConfig("GCT122141TedAbdWYXmOoFCbEBjaNrJNi6"));
var output = push.Send(new FangTangInput("测试用例", "这是一条测试内容。"));
Console.WriteLine("response: {0}", output.Content);
var push2 = new FangTangPusher("GCT122141TedAbdWYXmOoFCbEBjaNrJNi6");
var desp = @"# test Title
> test case.
1. test one.
2. the test is ok.
3. check the msg.
```
# code block
print '3 backticks or'
print 'indent 4 spaces'
```";
var output2 = push2.Send("test case", desp);
Console.WriteLine("response2: {0}", output2.Content);
}
/**
* output:
* 1. response: {"code":0,"message":"","data":{"pushid":"138222432","readkey":"SCTwh4qB55Q25EY","error":"SUCCESS","errno":0}}
* 2. response: {"message":"[AUTH]错误的Key","code":40001,"info":"错误的Key","args":[null],"scode":461}
*/
// ==============================================================================================
// WeCom:https://developer.work.weixin.qq.com/document/path/91039
{
var weComPush = new WeComPusher(new WeComConfig("wcf4f2154226633cd9", "JCaa7boA_K9cn1HqOPegsnpadsb4-_zdUOXhy123Rx8", "1000012"));
var output = weComPush.Send("hello, this is a test message.");
Console.WriteLine("response: {0}", output.Content);
}
/**
* output:
* 1. response: {"errcode":0,"errmsg":"ok","msgid":"fcLc6UhB2bbaSaoED21VFHkNKrbj3SiK5M65TA5qR_6t6oqvhmm11fWd4K6ws0YPYSjFI3Vfgq2ZpixLAH7Hpg"}
* 2. response: {"errcode":40056,"errmsg":"invalid agentid, hint: [1287746691374881334034345], from ip: 121.168.13.52, more info at https://open.work.weixin.qq.com/devtool/query?e=40056"}
*/
public interface IPusher<in TInput, out TOutput>
{
public TOutput Send(TInput input);
}
public record PusherResponse
{
public string Content { get; set; }
}
方糖微信服务号
using System.Net;
using System.Text.RegularExpressions;
using Push.Core.Abstracts;
using RestSharp;
using RestSharp.Serializers.NewtonsoftJson;
namespace Push.Core.Implements.FangTang;
public class FangTangConfig
{
public FangTangConfig(string key)
{
Key = key;
}
public string Key { get; set; }
}
public class FangTangInput
{
public FangTangInput()
{
}
public FangTangInput(string title, string desp)
{
Title = title;
Desp = desp;
}
public string Title { get; set; }
public string Desp { get; set; }
}
public record FangTangOutput : PusherResponse
{
public string PushId { get; set; }
public string ReadKey { get; set; }
public string Error { get; set; }
public int ErrNo { get; set; }
}
public class FangTangPusher : IPusher<FangTangInput, FangTangOutput>, IDisposable
{
private const string BaseUrl = "https://sctapi.ftqq.com/";
private readonly RestClient _client;
private readonly FangTangConfig _config;
public FangTangPusher(string key)
{
this._config = new FangTangConfig(key);
var options = new RestClientOptions(BaseUrl);
this._client = new RestClient(
options
, configureSerialization: s => s.UseNewtonsoftJson()
);
}
public FangTangPusher(FangTangConfig config)
{
this._config = config;
var options = new RestClientOptions(BaseUrl);
this._client = new RestClient(
options
, configureSerialization: s => s.UseNewtonsoftJson()
);
}
/// <inheritdoc />
public FangTangOutput Send(FangTangInput input)
{
var resource = $"{this._config.Key}.send";
RestRequest restRequest = new RestRequest(resource, Method.Post).AddJsonBody(input);
var restResponse = this._client.ExecutePost<FangTangOutput>(restRequest);
if (!restResponse.IsSuccessful && restResponse.StatusCode != HttpStatusCode.BadRequest)
throw new InvalidOperationException($"Incorrect request. {restResponse.ErrorException?.Message}");
var data = restResponse.Data;
if (data == null)
throw new InvalidDataException("Incorrect request to send the content, please check for the key.");
if (restResponse.Content != null)
data.Content = Regex.Unescape(restResponse.Content);
return data;
}
/// <inheritdoc />
public void Dispose()
{
_client.Dispose();
GC.SuppressFinalize(this);
}
}
public static class FangTangPusherExtensions
{
public static FangTangOutput Send(this FangTangPusher pusher, string title, string description)
=> pusher.Send(new FangTangInput(title, description));
}
企业微信
using Newtonsoft.Json;
using Push.Core.Abstracts;
using RestSharp;
using RestSharp.Serializers.NewtonsoftJson;
namespace Push.Core.Implements.WeCom;
public class WeComConfig
{
public WeComConfig(string corpId, string corpSecret, string agentId)
{
CorpId = corpId;
CorpSecret = corpSecret;
AgentId = agentId;
}
public string CorpId { get; set; }
public string CorpSecret { get; set; }
public string AgentId { get; set; }
public bool EnableDebug { get; set; }
}
public class WeComInput
{
public WeComInput(string content)
{
Content = content;
}
public string Content { get; set; }
}
public record WeComOutput : WeComPusher.WeComBaseModel
{
[JsonProperty(PropertyName = "msgid")]
public string MessageId { get; set; }
}
public class WeComPusher : IPusher<WeComInput, WeComOutput>, IDisposable
{
private const string BaseUrl = "https://qyapi.weixin.qq.com/";
private readonly RestClient _client;
private readonly WeComConfig _config;
private string? _accessToken = string.Empty;
public WeComPusher(WeComConfig config)
{
this._config = config;
var options = new RestClientOptions(BaseUrl);
this._client = new RestClient(
options
, configureSerialization: s => s.UseNewtonsoftJson()
);
}
private void EnsuringAccessToken()
{
const string resource = "cgi-bin/gettoken";
var restRequest = new RestRequest(resource)
.AddQueryParameter("corpid", this._config.CorpId)
.AddQueryParameter("corpsecret", this._config.CorpSecret);
// turn on debug mode
if (this._config.EnableDebug)
restRequest.AddQueryParameter("debug", "1");
var restResponse = this._client.ExecuteGet<AccessTokenModel>(restRequest);
if (!restResponse.IsSuccessful)
throw new InvalidOperationException($"Incorrect request. {restResponse.ErrorException?.Message}");
var data = restResponse.Data;
if (data == null)
throw new InvalidDataException("Incorrect request to obtain access token, please check for CorpId or CorpSecret.");
if (data.ErrorCode == 0)
{
this._accessToken = data.AccessToken;
}
else
{
throw new InvalidDataException(data.ErrorMessage);
}
}
/// <inheritdoc />
public WeComOutput Send(WeComInput input)
{
EnsuringAccessToken();
const string resource = "cgi-bin/message/send";
var requestModel = new WeComRequestModel("@all", "text", this._config.AgentId, input.Content);
var restRequest = new RestRequest(resource, Method.Post)
.AddQueryParameter("access_token", this._accessToken)
.AddJsonBody(requestModel);
// turn on debug mode
if (this._config.EnableDebug)
restRequest.AddQueryParameter("debug", "1");
var restResponse = this._client.ExecutePost<WeComOutput>(restRequest);
if (!restResponse.IsSuccessful)
throw new InvalidOperationException($"Incorrect request. {restResponse.ErrorException?.Message}");
var data = restResponse.Data;
if (data == null)
throw new InvalidDataException("Incorrect request to send the content, please check for AccessToken.");
data.Content = restResponse.Content;
return data;
}
#region internal classes
public record WeComBaseModel : PusherResponse
{
[JsonProperty(PropertyName = "errcode")]
public int ErrorCode { get; set; }
[JsonProperty(PropertyName = "errmsg")]
public string ErrorMessage { get; set; }
}
private record AccessTokenModel : WeComBaseModel
{
[JsonProperty(PropertyName = "access_token")]
public string? AccessToken { get; set; }
[JsonProperty(PropertyName = "expires_in")]
public int ExpiresIn { get; set; }
}
private record WeComRequestModel
{
public WeComRequestModel(string touser, string msgType, string agentId, string text)
{
Touser = touser;
MsgType = msgType;
AgentId = agentId;
Text = new TextRequestModel(text);
}
[JsonProperty(PropertyName = "touser")]
public string Touser { get; set; }
[JsonProperty(PropertyName = "msgtype")]
public string MsgType { get; set; }
[JsonProperty(PropertyName = "agentid")]
public string AgentId { get; set; }
[JsonProperty(PropertyName = "text")]
public TextRequestModel Text { get; set; }
}
private record TextRequestModel(string Content)
{
[JsonProperty(PropertyName = "content")]
public string Content { get; set; } = Content;
}
#endregion
/// <inheritdoc />
public void Dispose()
{
_client.Dispose();
GC.SuppressFinalize(this);
}
}
public static class WeComPusherExtensions
{
public static WeComOutput Send(this WeComPusher pusher, string content)
=> pusher.Send(new WeComInput(content));
}