前文
准备工作
关于钉钉机器人的API文档
nuget安装apilay
新建项目
这种就不在赘述了
返回值
经过一些简单的测试发现几个接口,返回值都是一样的{"errmsg":"ok","errcode":0}
,
所以可以定义一个公共的返回值,
为了保证一些扩展性,可以继承 Dictionary<string,object>
/// <summary>
/// 返回值基类
/// </summary>
public class ResponseResult : Dictionary<string, object>
{
/// <summary>
/// 错误消息, 请求正确返回"ok"
/// </summary>
public string errmsg
{
get => this["errmsg"] as string;
set => this["errmsg"] = value;
}
/// <summary>
/// 错误代码, 请求正确返回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; }
}
请求基类
定义一个请求基类,将一些公共部分提取出来,如Method
,ContentType
,Path
,access_token
参数,Json序列化,返回值判断等操作
public abstract class SendBase : ApRequest<ResponseResult>
{
/// <summary>
/// 将返回值解析为 ResponseResult
/// </summary>
/// <param name="statusCode"> http状态码 </param>
/// <param name="content"> 响应正文 </param>
/// <param name="getHeader"> 用于获取响应头的委托方法 </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 = "请求服务器异常" };
}
return new ResponseResult() { errcode = int.MinValue + 1, errmsg = "服务器返回为空" };
}
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 参数
/// </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>
/// 提交正文内容
/// </summary>
protected abstract object Content { get; }
/// <summary>
/// json字符串转为字节
/// </summary>
public override byte[] Body => Encoding.UTF8.GetBytes(Units.ToJsonString(Content));
}
定义会话
我这里将一个独立的机器人定义为一个会话,所以通过access_token
实例化,
另外可以在会话中设置请求根域名,方便切换环境,
根据需要包装Invoke
方法
public sealed class Robot : ApSession
{
public Robot(string accessToken)
=> AccessToken = accessToken ?? throw new ArgumentNullException(nameof(accessToken));
/// <summary>
/// 机器人标识
/// </summary>
public string AccessToken { get; }
/// <summary>
/// 机器人api域名
/// </summary>
public string BaseUrl => "https://oapi.dingtalk.com/";
/// <summary>
/// 利用机器人发送消息
/// </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);
}
}
}
到这里基本对象都已经定义完了
先尝试定义一个最简单的markdown消息
继承
SendBase
按照要求构造正文参数
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>
/// 首屏会话透出的展示内容
/// </summary>
public string Title { get; set; }
}
发送 markdown消息
自己建一个群,添加一个机器人
在机器人设置中获取机器人的accesstoken
测试代码
static void Main(string[] args)
{
var robot = new Robot("4aa82a4e65b81103****");
robot.Send(new SendMarkdown()
{
Title = "测试一下消息",
Text = "##标题一 \n> 引用 \n\n* 列表1\n* 列表2\n* 列表3"
}).Wait();
}
其他类型的消息
测试成功,就可以编写其他类型的消息了
根据文档写就好了
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>
/// 消息内容
/// </summary>
public string Text { get; set; }
/// <summary>
/// @所有人时:true,否则为:false
/// </summary>
public bool IsAtAll { get; set; }
/// <summary>
/// 被@人的手机号
/// </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>
/// 消息内容。如果太长只会部分展示
/// </summary>
public string Text { get; set; }
/// <summary>
/// 消息标题
/// </summary>
public string Title { get; set; }
/// <summary>
/// 图片URL
/// </summary>
public string PictureUrl { get; set; }
/// <summary>
/// 点击消息跳转的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>
/// 首屏会话透出的展示内容
/// </summary>
public string Title { get; set; }
/// <summary>
/// markdown格式的消息
/// </summary>
public string Text { get; set; }
/// <summary>
/// 是否隐藏发消息者头像
/// </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>
/// 首屏会话透出的展示内容
/// </summary>
public string Title { get; set; }
/// <summary>
/// markdown格式的消息
/// </summary>
public string Text { get; set; }
/// <summary>
/// 是否隐藏发消息者头像
/// </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>
/// 添加项
/// </summary>
/// <param name="title"> 信息文本 </param>
/// <param name="linkUrl"> 跳转链接 </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 });
}
}
测试
测试代码
static void Main(string[] args)
{
var robot = new Robot("4aa82a4e65b8110*****");
robot.Send(new SendMarkdown()
{
Title = "测试一下消息",
Text = "##标题一 \n> 引用 \n\n* 列表1\n* 列表2\n* 列表3"
}).Wait();
robot.Send(new SendText("测试Text")
{
At = { "15657966053" } // @我自己
}).Wait();
robot.Send(new SendLink()
{
Title = "测试Link",
Text = "测试Link",
LinkUrl = "http://baidu.com"
}).Wait();
robot.Send(new SendSingleActionCard()
{
Title = "测试SingleActionCard",
Text = "测试SingleActionCard",
LinkText = "点击我去百度",
LinkUrl = "http://baidu.com"
}).Wait();
robot.Send(new SendMultisActionCard()
{
Title = "测试MultisActionCard",
Text = "测试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();
}
测试结果