前文
準(zhǔn)備工作
關(guān)于釘釘機(jī)器人的API文檔
nuget安裝apilay
新建項(xiàng)目
這種就不在贅述了
返回值
經(jīng)過(guò)一些簡(jiǎn)單的測(cè)試發(fā)現(xiàn)幾個(gè)接口狱意,返回值都是一樣的{"errmsg":"ok","errcode":0}
椒涯,
所以可以定義一個(gè)公共的返回值,
為了保證一些擴(kuò)展性先口,可以繼承 Dictionary<string,object>
/// <summary>
/// 返回值基類(lèi)
/// </summary>
public class ResponseResult : Dictionary<string, object>
{
/// <summary>
/// 錯(cuò)誤消息, 請(qǐng)求正確返回"ok"
/// </summary>
public string errmsg
{
get => this["errmsg"] as string;
set => this["errmsg"] = value;
}
/// <summary>
/// 錯(cuò)誤代碼, 請(qǐng)求正確返回0
/// </summary>
public int errcode
{
get => this["errcode"] is int i ? i : int.TryParse(this["errcode"] + "", out var code) ? code : int.MinValue;
set => this["errcode"] = value.ToString();
}
/// <summary>
/// 返回值原文
/// </summary>
public string OriginalResult { get; set; }
}
請(qǐng)求基類(lèi)
定義一個(gè)請(qǐng)求基類(lèi)赚瘦,將一些公共部分提取出來(lái),如Method
,ContentType
,Path
理澎,access_token
參數(shù),Json序列化曙寡,返回值判斷等操作
public abstract class SendBase : ApRequest<ResponseResult>
{
/// <summary>
/// 將返回值解析為 ResponseResult
/// </summary>
/// <param name="statusCode"> http狀態(tài)碼 </param>
/// <param name="content"> 響應(yīng)正文 </param>
/// <param name="getHeader"> 用于獲取響應(yīng)頭的委托方法 </param>
/// <returns></returns>
public sealed override ResponseResult GetData(int statusCode, byte[] content, Func<string, string> getHeader)
{
if (content == null || content.Length == 0)
{
if (statusCode != 200 && statusCode != 0)
{
return new ResponseResult() { errcode = statusCode, errmsg = "請(qǐng)求服務(wù)器異常" };
}
return new ResponseResult() { errcode = int.MinValue + 1, errmsg = "服務(wù)器返回為空" };
}
var json = Encoding.UTF8.GetString(content, 0, content.Length);
try
{
var result = (ResponseResult)Units.ToJsonObject(json, typeof(ResponseResult));
result.OriginalResult = json;
return result;
}
catch (Exception e)
{
return new ResponseResult() { errcode = statusCode == 200 || statusCode == 0 ? int.MinValue : statusCode, errmsg = e.Message, OriginalResult = json };
}
}
/// <summary>
/// url_query 參數(shù)
/// </summary>
[QueryValue(Name = "access_token")]
public string AccessToken { get; set; }
public override string Method => "POST";
public override string ContentType => "application/json;charset=utf-8";
public override string Path => "/robot/send";
/// <summary>
/// 提交正文內(nèi)容
/// </summary>
protected abstract object Content { get; }
/// <summary>
/// json字符串轉(zhuǎn)為字節(jié)
/// </summary>
public override byte[] Body => Encoding.UTF8.GetBytes(Units.ToJsonString(Content));
}
定義會(huì)話
我這里將一個(gè)獨(dú)立的機(jī)器人定義為一個(gè)會(huì)話,所以通過(guò)access_token
實(shí)例化寇荧,
另外可以在會(huì)話中設(shè)置請(qǐng)求根域名举庶,方便切換環(huán)境,
根據(jù)需要包裝Invoke
方法
public sealed class Robot : ApSession
{
public Robot(string accessToken)
=> AccessToken = accessToken ?? throw new ArgumentNullException(nameof(accessToken));
/// <summary>
/// 機(jī)器人標(biāo)識(shí)
/// </summary>
public string AccessToken { get; }
/// <summary>
/// 機(jī)器人api域名
/// </summary>
public string BaseUrl => "https://oapi.dingtalk.com/";
/// <summary>
/// 利用機(jī)器人發(fā)送消息
/// </summary>
/// <param name="request"></param>
public async Task Send(SendBase request)
{
if (request == null) throw new ArgumentNullException(nameof(request));
request.AccessToken = AccessToken;
var result = await Invoke(BaseUrl, request, CancellationToken.None);
if (result.errcode != 0)
{
throw new ApRequestException(result.errcode, result.errmsg);
}
}
}
到這里基本對(duì)象都已經(jīng)定義完了
先嘗試定義一個(gè)最簡(jiǎn)單的markdown消息
文檔說(shuō)明
繼承
SendBase
按照要求構(gòu)造正文參數(shù)
public sealed class SendMarkdown : SendBase
{
protected override object Content => new
{
msgtype = "markdown",
markdown = new
{
title = Title?.Length > 20 ? Title.Substring(0, 17) + "..." : Title ?? "",
text = Text ?? "",
}
};
/// <summary>
/// markdown格式的消息
/// </summary>
public string Text { get; set; }
/// <summary>
/// 首屏?xí)捦赋龅恼故緝?nèi)容
/// </summary>
public string Title { get; set; }
}
發(fā)送 markdown消息
自己建一個(gè)群揩抡,添加一個(gè)機(jī)器人
在機(jī)器人設(shè)置中獲取機(jī)器人的accesstoken
測(cè)試代碼
static void Main(string[] args)
{
var robot = new Robot("4aa82a4e65b81103****");
robot.Send(new SendMarkdown()
{
Title = "測(cè)試一下消息",
Text = "##標(biāo)題一 \n> 引用 \n\n* 列表1\n* 列表2\n* 列表3"
}).Wait();
}
其他類(lèi)型的消息
測(cè)試成功户侥,就可以編寫(xiě)其他類(lèi)型的消息了
根據(jù)文檔寫(xiě)就好了
Text
public sealed class SendText : SendBase
{
public SendText(string text)
{
Text = text;
}
protected override object Content => new
{
msgtype = "text",
text = new
{
content = Text,
},
at = new
{
atMobiles = IsAtAll ? EmptyList : At ?? EmptyList,
isAtAll = IsAtAll,
},
};
/// <summary>
/// 消息內(nèi)容
/// </summary>
public string Text { get; set; }
/// <summary>
/// @所有人時(shí):true,否則為:false
/// </summary>
public bool IsAtAll { get; set; }
/// <summary>
/// 被@人的手機(jī)號(hào)
/// </summary>
public List<string> At { get; set; } = new List<string>();
static readonly List<string> EmptyList = new List<string>();
}
Link
public sealed class SendLink : SendBase
{
protected override object Content => new
{
msgtype = "link",
link = new
{
text = Text,
title = Title?.Length > 20 ? Title.Substring(0, 17) + "..." : Title ?? "",
picUrl = PictureUrl,
messageUrl = LinkUrl,
},
};
/// <summary>
/// 消息內(nèi)容。如果太長(zhǎng)只會(huì)部分展示
/// </summary>
public string Text { get; set; }
/// <summary>
/// 消息標(biāo)題
/// </summary>
public string Title { get; set; }
/// <summary>
/// 圖片URL
/// </summary>
public string PictureUrl { get; set; }
/// <summary>
/// 點(diǎn)擊消息跳轉(zhuǎn)的URL
/// </summary>
public string LinkUrl { get; set; }
}
SingleActionCard
public sealed class SendSingleActionCard : SendBase
{
protected override object Content => new
{
msgtype = "actionCard",
actionCard = new
{
title = Title?.Length > 20 ? Title.Substring(0, 17) + "..." : Title ?? "",
text = Text,
hideAvatar = HideAvatar ? "1" : "0",
btnOrientation = VerticalButton ? "0" : "1",
singleTitle = LinkText ?? "查看詳情",
singleURL = LinkUrl,
},
};
/// <summary>
/// 首屏?xí)捦赋龅恼故緝?nèi)容
/// </summary>
public string Title { get; set; }
/// <summary>
/// markdown格式的消息
/// </summary>
public string Text { get; set; }
/// <summary>
/// 是否隱藏發(fā)消息者頭像
/// </summary>
public bool HideAvatar { get; set; }
/// <summary>
/// 是否豎直排列按鈕, false為橫向排列
/// </summary>
public bool VerticalButton { get; set; } = true;
/// <summary>
/// 鏈接文字
/// </summary>
public string LinkText { get; set; }
/// <summary>
/// 鏈接URL
/// </summary>
public string LinkUrl { get; set; }
}
MultisActionCard
public sealed class SendMultisActionCard : SendBase
{
protected override object Content => new
{
msgtype = "actionCard",
actionCard = new
{
title = Title?.Length > 20 ? Title.Substring(0, 17) + "..." : Title ?? "",
text = Text,
hideAvatar = HideAvatar ? "1" : "0",
btnOrientation = VerticalButton ? "0" : "1",
btns = Links.Select(x => new
{
title = x.Key,
actionURL = x.Value,
}).ToList(),
},
};
/// <summary>
/// 首屏?xí)捦赋龅恼故緝?nèi)容
/// </summary>
public string Title { get; set; }
/// <summary>
/// markdown格式的消息
/// </summary>
public string Text { get; set; }
/// <summary>
/// 是否隱藏發(fā)消息者頭像
/// </summary>
public bool HideAvatar { get; set; }
/// <summary>
/// 是否豎直排列按鈕, false為橫向排列
/// </summary>
public bool VerticalButton { get; set; } = true;
/// <summary>
/// 超鏈接組
/// </summary>
public Dictionary<string, string> Links = new Dictionary<string, string>();
}
FeedCard
public class SendFeedCard : SendBase
{
protected override object Content => new
{
msgtype = "feedCard",
feedCard = new { links = _items }
};
readonly List<object> _items = new List<object>();
/// <summary>
/// 添加項(xiàng)
/// </summary>
/// <param name="title"> 信息文本 </param>
/// <param name="linkUrl"> 跳轉(zhuǎn)鏈接 </param>
/// <param name="pictureUrl"> 圖片URL </param>
public void AddItem(string title, string linkUrl, string pictureUrl)
{
if (string.IsNullOrWhiteSpace(title)) throw new ArgumentNullException(nameof(title));
if (string.IsNullOrWhiteSpace(linkUrl)) throw new ArgumentNullException(nameof(linkUrl));
if (string.IsNullOrWhiteSpace(pictureUrl)) throw new ArgumentNullException(nameof(pictureUrl));
_items.Add(new { title, messageURL = linkUrl, picURL = pictureUrl });
}
}
測(cè)試
測(cè)試代碼
static void Main(string[] args)
{
var robot = new Robot("4aa82a4e65b8110*****");
robot.Send(new SendMarkdown()
{
Title = "測(cè)試一下消息",
Text = "##標(biāo)題一 \n> 引用 \n\n* 列表1\n* 列表2\n* 列表3"
}).Wait();
robot.Send(new SendText("測(cè)試Text")
{
At = { "15657966053" } // @我自己
}).Wait();
robot.Send(new SendLink()
{
Title = "測(cè)試Link",
Text = "測(cè)試Link",
LinkUrl = "http://baidu.com"
}).Wait();
robot.Send(new SendSingleActionCard()
{
Title = "測(cè)試SingleActionCard",
Text = "測(cè)試SingleActionCard",
LinkText = "點(diǎn)擊我去百度",
LinkUrl = "http://baidu.com"
}).Wait();
robot.Send(new SendMultisActionCard()
{
Title = "測(cè)試MultisActionCard",
Text = "測(cè)試MultisActionCard",
HideAvatar = true,
VerticalButton = false,
Links =
{
["去百度"] = "http://baidu.com",
["去谷歌"] = "http://google.cn",
}
}).Wait();
var feedCard = new SendFeedCard();
feedCard.AddItem("去百度", "http://baidu.com", "https://www.baidu.com/img/bd_logo1.png");
feedCard.AddItem("去谷歌", "http://google.cn", "http://www.google.cn/landing/cnexp/google-search.png");
robot.Send(feedCard).Wait();
}
測(cè)試結(jié)果