Просмотр исходного кода

!1033 钉钉集成
Merge pull request !1033 from Hans/DingTalkApi

zuohuaijun 2 лет назад
Родитель
Сommit
6a34ccb95d
23 измененных файлов с 982 добавлено и 3 удалено
  1. 12 0
      Admin.NET/Admin.NET.Application/Configuration/DingTalk.json
  2. 28 0
      Admin.NET/Admin.NET.Core/Const/DingTalkFieldConst.cs
  3. 1 1
      Admin.NET/Admin.NET.Core/Const/SqlSugarConst.cs
  4. 269 0
      Admin.NET/Admin.NET.Core/DingTalk/DingTalkService.cs
  5. 80 0
      Admin.NET/Admin.NET.Core/Entity/SysDingTalkUser.cs
  6. 2 1
      Admin.NET/Admin.NET.Core/GlobalUsings.cs
  7. 47 0
      Admin.NET/Admin.NET.Core/Integrations/DingTalk/Dto/DingTalkBaseResponse.cs
  8. 38 0
      Admin.NET/Admin.NET.Core/Integrations/DingTalk/Dto/DingTalkCardData.cs
  9. 26 0
      Admin.NET/Admin.NET.Core/Integrations/DingTalk/Dto/DingTalkConversationTypeEnum.cs
  10. 33 0
      Admin.NET/Admin.NET.Core/Integrations/DingTalk/Dto/DingTalkEmpFieldDataVo.cs
  11. 28 0
      Admin.NET/Admin.NET.Core/Integrations/DingTalk/Dto/DingTalkEmpRosterFieldVo.cs
  12. 29 0
      Admin.NET/Admin.NET.Core/Integrations/DingTalk/Dto/DingTalkFieldValueVo.cs
  13. 124 0
      Admin.NET/Admin.NET.Core/Integrations/DingTalk/Dto/DingTalkSendInteractiveCardsInput.cs
  14. 25 0
      Admin.NET/Admin.NET.Core/Integrations/DingTalk/Dto/DingTalkSendInteractiveCardsOutput.cs
  15. 17 0
      Admin.NET/Admin.NET.Core/Integrations/DingTalk/Dto/DingTalkSendInteractiveCardsResult.cs
  16. 36 0
      Admin.NET/Admin.NET.Core/Integrations/DingTalk/Dto/GetDingTalkCurrentEmployeesListInput.cs
  17. 23 0
      Admin.NET/Admin.NET.Core/Integrations/DingTalk/Dto/GetDingTalkCurrentEmployeesListOutput.cs
  18. 27 0
      Admin.NET/Admin.NET.Core/Integrations/DingTalk/Dto/GetDingTalkCurrentEmployeesRosterListInput.cs
  19. 47 0
      Admin.NET/Admin.NET.Core/Integrations/DingTalk/Dto/GetDingTalkToken.cs
  20. 59 0
      Admin.NET/Admin.NET.Core/Integrations/DingTalk/IDingTalkApi.cs
  21. 29 0
      Admin.NET/Admin.NET.Core/Option/DingTalkOptions.cs
  22. 1 1
      Admin.NET/Admin.NET.Core/Option/EmailOptions.cs
  23. 1 0
      Admin.NET/Admin.NET.Web.Core/ProjectOptions.cs

+ 12 - 0
Admin.NET/Admin.NET.Application/Configuration/DingTalk.json

@@ -0,0 +1,12 @@
+{
+  "$schema": "https://gitee.com/dotnetchina/Furion/raw/v4/schemas/v4/furion-schema.json",
+  "DingTalk": {
+    "AppId": "",
+    "AgentId": "",
+    // 原 AppKey 和 SuiteKey
+    "ClientId": "xxxx",
+    // 原 AppSecret 和 SuiteSecret
+    "ClientSecret": "xxxx"
+  }
+
+}

+ 28 - 0
Admin.NET/Admin.NET.Core/Const/DingTalkFieldConst.cs

@@ -0,0 +1,28 @@
+// 大名科技(天津)有限公司 版权所有
+//
+// 此源代码遵循位于源代码树根目录中的 LICENSE 文件的许可证
+//
+// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动
+//
+// 任何基于本项目二次开发而产生的一切法律纠纷和责任,均与作者无关
+
+namespace Admin.NET.Core;
+
+/// <summary>
+/// 钉钉字段常量
+/// </summary>
+public class DingTalkFieldConst
+{
+    /// <summary>
+    /// 姓名
+    /// </summary>
+    public const string NameField = "sys00-name";
+    /// <summary>
+    /// 手机号
+    /// </summary>
+    public const string MobileField = "sys00-mobile";
+    /// <summary>
+    /// 工号
+    /// </summary>
+    public const string JobNumberField = "sys00-jobNumber";
+}

+ 1 - 1
Admin.NET/Admin.NET.Core/Const/SqlSugarConst.cs

@@ -27,4 +27,4 @@ public class SqlSugarConst
     /// 默认表主键
     /// </summary>
     public const string PrimaryKey = "Id";
-}
+}

+ 269 - 0
Admin.NET/Admin.NET.Core/DingTalk/DingTalkService.cs

@@ -0,0 +1,269 @@
+// 大名科技(天津)有限公司 版权所有
+//
+// 此源代码遵循位于源代码树根目录中的 LICENSE 文件的许可证
+//
+// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动
+//
+// 任何基于本项目二次开发而产生的一切法律纠纷和责任,均与作者无关
+
+using Admin.NET.Core.Integrations;
+
+namespace Admin.NET.Core.DingTalk;
+/// <summary>
+/// 钉钉服务
+/// </summary>
+[ApiDescriptionSettings(Order = 999)]
+public class DingTalkService : IDynamicApiController, IScoped
+{
+    private readonly UserManager _userManager;
+    private readonly IDingTalkApi _dingTalkApi;
+    private readonly DingTalkOptions _dingTalkOptions;
+    private readonly SqlSugarRepository<SysDingTalkUser> _sysDingTalkUserRepo;
+    private readonly SqlSugarRepository<SysUser> _sysUserRep;
+    private readonly IJsonSerializerProvider _jsonSerializer;
+    public DingTalkService(
+        UserManager userManager,
+        IDingTalkApi dingTalkApi,
+        IOptions<DingTalkOptions> dingTalkOptions,
+        SqlSugarRepository<SysDingTalkUser> sysDingTalkUserRepo,
+        SqlSugarRepository<SysUser> sysUserRep,
+        IJsonSerializerProvider jsonSerializer
+        )
+    {
+        _userManager = userManager;
+        _dingTalkApi = dingTalkApi;
+        _dingTalkOptions = dingTalkOptions.Value;
+        _sysDingTalkUserRepo = sysDingTalkUserRepo;
+        _sysUserRep = sysUserRep;
+        _jsonSerializer = jsonSerializer;
+
+    }
+
+    /// <summary>
+    /// 同步钉钉用户(后面可以做到job中)
+    /// </summary>
+    /// <returns></returns>
+    [HttpGet]
+    [DisplayName("同步钉钉用户")]
+    public async Task SyncDingTalkUser()
+    {
+        // 获取token
+        var param = new GetDingTalkTokenInput()
+        {
+            AppKey = _dingTalkOptions.ClientId,
+            AppSecret = _dingTalkOptions.ClientSecret
+        };
+        var tokenRes = await _dingTalkApi.GetDingTalkToken(param);
+        if (tokenRes.ErrCode != 0)
+        {
+            throw Oops.Oh(tokenRes.ErrMsg);
+        }
+        var offset = 0;
+        while (offset >= 0)
+        {
+            // 获取用户id列表
+            var userIdsRes = await _dingTalkApi.GetDingTalkCurrentEmployeesList(tokenRes.AccessToken, new GetDingTalkCurrentEmployeesListInput
+            {
+                StatusList = "2,3,5,-1",
+                Size = 50,
+                Offset = offset
+            });
+            if (!userIdsRes.Success)
+            {
+                throw Oops.Oh(userIdsRes.ErrMsg);
+            }
+            // 根据用户id获取花名册
+            var rosterRes = await _dingTalkApi.GetDingTalkCurrentEmployeesRosterList(tokenRes.AccessToken, new GetDingTalkCurrentEmployeesRosterListInput()
+            {
+                UserIdList = string.Join(",", userIdsRes.Result.DataList),
+                FieldFilterList = $"{DingTalkFieldConst.NameField},{DingTalkFieldConst.JobNumberField},{DingTalkFieldConst.MobileField}",
+                AgentId = _dingTalkOptions.AgentId
+            });
+            if (!rosterRes.Success)
+            {
+                throw Oops.Oh(rosterRes.ErrMsg);
+            }
+            // 判断新增还是更新
+            var userIds = rosterRes.Result.Select(res => res.UserId).ToList();
+            var uDingTalkUser = await _sysDingTalkUserRepo.AsQueryable()
+                .Where(u => userIds.Contains(u.DingTalkUserId))
+                .ToListAsync();
+            // 需要更新的用户id
+            var uUserIds = uDingTalkUser.Select(u => u.DingTalkUserId);
+            // 需要新增的用户id
+            var iUserIds = userIds.Where(u => !uUserIds.Contains(u));
+
+            #region 保存到钉钉用户表
+            var iUser = rosterRes.Result
+                .Where(res => iUserIds.Contains(res.UserId))
+                .Select(res => new SysDingTalkUser
+                {
+                    DingTalkUserId = res.UserId,
+                    Name = res.FieldDataList
+                    .Where(f => f.FieldCode == DingTalkFieldConst.NameField)
+                    .Select(f => f.FieldValueList.Select(v => v.Value).FirstOrDefault())
+                    .FirstOrDefault(),
+                    Mobile = res.FieldDataList
+                    .Where(f => f.FieldCode == DingTalkFieldConst.MobileField)
+                    .Select(f => f.FieldValueList.Select(v => v.Value).FirstOrDefault())
+                    .FirstOrDefault(),
+                    JobNumber = res.FieldDataList
+                    .Where(f => f.FieldCode == DingTalkFieldConst.JobNumberField)
+                    .Select(f => f.FieldValueList.Select(v => v.Value).FirstOrDefault())
+                    .FirstOrDefault(),
+                }).ToList();
+            if (iUser.Count > 0)
+            {
+                var iUserRes = await _sysDingTalkUserRepo.AsInsertable(iUser).ExecuteCommandAsync();
+                if (iUserRes <= 0)
+                {
+                    throw Oops.Oh("保存钉钉用户错误");
+                }
+            }
+            #endregion
+
+            #region 更新钉钉用户
+            var uUser = rosterRes.Result
+            .Where(res => uUserIds.Contains(res.UserId))
+            .Select(res => new SysDingTalkUser
+            {
+                Id = uDingTalkUser.Where(d => d.DingTalkUserId == res.UserId).Select(d => d.Id).FirstOrDefault(),
+                DingTalkUserId = res.UserId,
+                Name = res.FieldDataList
+                    .Where(f => f.FieldCode == DingTalkFieldConst.NameField)
+                    .Select(f => f.FieldValueList.Select(v => v.Value).FirstOrDefault())
+                    .FirstOrDefault(),
+                Mobile = res.FieldDataList
+                    .Where(f => f.FieldCode == DingTalkFieldConst.MobileField)
+                    .Select(f => f.FieldValueList.Select(v => v.Value).FirstOrDefault())
+                    .FirstOrDefault(),
+                JobNumber = res.FieldDataList
+                    .Where(f => f.FieldCode == DingTalkFieldConst.JobNumberField)
+                    .Select(f => f.FieldValueList.Select(v => v.Value).FirstOrDefault())
+                    .FirstOrDefault(),
+            }).ToList();
+            if (uUser.Count > 0)
+            {
+                var uUserRes = await _sysDingTalkUserRepo.AsUpdateable(uUser)
+                .UpdateColumns(d => new
+                {
+                    d.DingTalkUserId,
+                    d.Name,
+                    d.Mobile,
+                    d.JobNumber,
+                    d.UpdateTime,
+                    d.UpdateUserName,
+                    d.UpdateUserId,
+                }).ExecuteCommandAsync();
+                if (uUserRes <= 0)
+                {
+                    throw Oops.Oh("更新钉钉用户错误");
+                }
+            }
+            #endregion
+
+            // 保存分页游标
+            if (userIdsRes.Result.NextCursor == null)
+            {
+                break;
+            }
+            offset = (int)userIdsRes.Result.NextCursor;
+        }
+
+
+        var sysUser = await _sysUserRep.AsQueryable().Select(x => new
+        {
+            x.Id,
+            x.Account,
+            x.Phone
+        }).ToListAsync();
+
+        var sysDingTalkUser = await _sysDingTalkUserRepo.AsQueryable()
+            .Where(d => sysUser.Any(u => u.Account == d.JobNumber))
+            .Select(x => new
+            {
+                x.Id,
+                x.JobNumber,
+                x.Mobile
+            }).ToListAsync();
+
+        // 更新钉钉用户中系统用户id
+        var uSysDingTalkUser = sysDingTalkUser.Select(d => new SysDingTalkUser
+        {
+            Id = d.Id,
+            SysUserId = sysUser.Where(u => u.Account == d.JobNumber).Select(u => u.Id).FirstOrDefault(),
+        }).ToList();
+        var uSysDingTalkUserRes = await _sysDingTalkUserRepo.AsUpdateable(uSysDingTalkUser)
+        .UpdateColumns(d => new
+        {
+            d.SysUserId,
+            d.UpdateTime,
+            d.UpdateUserName,
+            d.UpdateUserId,
+        }).ExecuteCommandAsync();
+        if (uSysDingTalkUserRes <= 0)
+        {
+            throw Oops.Oh("更新钉钉用户错误");
+        }
+        return;
+
+
+
+
+    }
+
+
+    /// <summary>
+    /// 获取企业内部应用的access_token
+    /// </summary>
+    /// <param name="input"></param>
+    /// <returns></returns>
+    [HttpGet]
+    [DisplayName("获取企业内部应用的access_token")]
+    public async Task<GetDingTalkTokenOutput> GetDingTalkToken([FromQuery] GetDingTalkTokenInput input)
+    {
+        return await _dingTalkApi.GetDingTalkToken(input);
+    }
+
+    /// <summary>
+    /// 获取在职员工列表
+    /// </summary>
+    /// <param name="access_token"></param>
+    /// <param name="input"></param>
+    /// <returns></returns>
+    [HttpPost]
+    [DisplayName("获取在职员工列表")]
+    public async Task<DingTalkBaseResponse<GetDingTalkCurrentEmployeesListOutput>> GetDingTalkCurrentEmployeesList(string access_token, [Required] GetDingTalkCurrentEmployeesListInput input)
+    {
+        return await _dingTalkApi.GetDingTalkCurrentEmployeesList(access_token, input);
+    }
+    /// <summary>
+    /// 获取员工花名册字段信息
+    /// </summary>
+    /// <param name="access_token"></param>
+    /// <param name="input"></param>
+    /// <returns></returns>
+    [HttpPost]
+    [DisplayName("获取员工花名册字段信息")]
+    public async Task<DingTalkBaseResponse<List<DingTalkEmpRosterFieldVo>>> GetDingTalkCurrentEmployeesRosterList(string access_token, [Required] GetDingTalkCurrentEmployeesRosterListInput input)
+    {
+        return await _dingTalkApi.GetDingTalkCurrentEmployeesRosterList(access_token, input);
+    }
+    /// <summary>
+    /// 发送钉钉互动卡片
+    /// </summary>
+    /// <param name="token"></param>
+    /// <param name="input"></param>
+    /// <returns></returns>
+    [HttpPost]
+    [DisplayName("给指定用户发送钉钉互动卡片")]
+    public async Task<DingTalkSendInteractiveCardsOutput> DingTalkSendInteractiveCards(string token, DingTalkSendInteractiveCardsInput input)
+    {
+        return await _dingTalkApi.DingTalkSendInteractiveCards(token, input);
+    }
+
+
+
+}
+
+

+ 80 - 0
Admin.NET/Admin.NET.Core/Entity/SysDingTalkUser.cs

@@ -0,0 +1,80 @@
+// 大名科技(天津)有限公司 版权所有
+//
+// 此源代码遵循位于源代码树根目录中的 LICENSE 文件的许可证
+//
+// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动
+//
+// 任何基于本项目二次开发而产生的一切法律纠纷和责任,均与作者无关
+
+namespace Admin.NET.Core;
+
+/// <summary>
+/// 钉钉用户表
+/// </summary>
+[SugarTable(null, "钉钉用户表")]
+[SysTable]
+public class SysDingTalkUser : EntityBase
+{
+    /// <summary>
+    /// 系统用户Id
+    /// </summary>
+    [SugarColumn(ColumnDescription = "系统用户Id")]
+    public long SysUserId { get; set; }
+
+    /// <summary>
+    /// 系统用户
+    /// </summary>
+    [SugarColumn(IsIgnore = true)]
+    [Navigate(NavigateType.OneToOne, nameof(SysUserId))]
+    [JsonIgnore]
+    public SysUser SysUser { get; set; }
+
+    /// <summary>
+    /// 钉钉用户id
+    /// </summary>
+    [SugarColumn(ColumnDescription = "钉钉用户id", Length = 64)]
+    [Required, MaxLength(64)]
+    public virtual string? DingTalkUserId { get; set; }
+
+    /// <summary>
+    /// UnionId
+    /// </summary>
+    [SugarColumn(ColumnDescription = "UnionId", Length = 64)]
+    [MaxLength(64)]
+    public string? UnionId { get; set; }
+
+    /// <summary>
+    /// 用户名
+    /// </summary>
+    [SugarColumn(ColumnDescription = "用户名", Length = 64)]
+    [MaxLength(64)]
+    public string? Name { get; set; }
+
+    /// <summary>
+    /// 头像
+    /// </summary>
+    [SugarColumn(ColumnDescription = "头像", Length = 256)]
+    [MaxLength(256)]
+    public string? Avatar { get; set; }
+
+    /// <summary>
+    /// 手机号码
+    /// </summary>
+    [SugarColumn(ColumnDescription = "手机号码", Length = 16)]
+    [MaxLength(16)]
+    public string? Mobile { get; set; }
+    /// <summary>
+    /// 工号
+    /// </summary>
+    [SugarColumn(ColumnDescription = "工号", Length = 16)]
+    [MaxLength(16)]
+    public string? JobNumber { get; set; }
+
+    /// <summary>
+    /// 性别
+    /// </summary>
+    [SugarColumn(ColumnDescription = "性别")]
+    public int? Sex { get; set; }
+
+
+}

+ 2 - 1
Admin.NET/Admin.NET.Core/GlobalUsings.cs

@@ -58,7 +58,8 @@ global using System.Linq.Expressions;
 global using System.Reflection;
 global using System.Runtime.InteropServices;
 global using System.Text;
+global using System.Text.Json.Serialization;
 global using System.Text.RegularExpressions;
 global using System.Web;
 global using UAParser;
-global using Yitter.IdGenerator;
+global using Yitter.IdGenerator;

+ 47 - 0
Admin.NET/Admin.NET.Core/Integrations/DingTalk/Dto/DingTalkBaseResponse.cs

@@ -0,0 +1,47 @@
+// 大名科技(天津)有限公司 版权所有
+//
+// 此源代码遵循位于源代码树根目录中的 LICENSE 文件的许可证
+//
+// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动
+//
+// 任何基于本项目二次开发而产生的一切法律纠纷和责任,均与作者无关
+
+namespace Admin.NET.Core.Integrations;
+
+/// <summary>
+/// 钉钉基础响应结果
+/// </summary>
+/// <typeparam name="T">Data</typeparam>
+public class DingTalkBaseResponse<T>
+{
+    /// <summary>
+    /// 返回结果
+    /// </summary>
+    [JsonPropertyName("result")]
+    public T Result { get; set; }
+    /// <summary>
+    /// 返回码
+    /// </summary>
+    [JsonPropertyName("errcode")]
+    public int ErrCode { get; set; }
+    /// <summary>
+    /// 返回码描述。
+    /// </summary>
+    [JsonPropertyName("errmsg")]
+    public string ErrMsg { get; set; }
+    /// <summary>
+    /// 是否调用成功
+    /// </summary>
+    /// <remarks>
+    /// true:成功;
+    /// false:失败;
+    /// </remarks>
+    [JsonPropertyName("success")]
+    public bool Success { get; set; }
+    /// <summary>
+    /// 请求ID。
+    /// </summary>
+    [JsonPropertyName("request_id")]
+    public string RequestId { get; set; }
+
+}

+ 38 - 0
Admin.NET/Admin.NET.Core/Integrations/DingTalk/Dto/DingTalkCardData.cs

@@ -0,0 +1,38 @@
+// 大名科技(天津)有限公司 版权所有
+//
+// 此源代码遵循位于源代码树根目录中的 LICENSE 文件的许可证
+//
+// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动
+//
+// 任何基于本项目二次开发而产生的一切法律纠纷和责任,均与作者无关
+
+namespace Admin.NET.Core.Integrations;
+
+/// <summary>
+/// 卡片公有数据
+/// </summary>
+public class DingTalkCardData
+{
+    /// <summary>
+    /// 卡片模板内容替换参数,普通文本类型。
+    /// </summary>
+    [JsonPropertyName("cardParamMap")]
+    public DingTalkCardParamMap CardParamMap { get; set; }
+    /// <summary>
+    /// 卡片模板内容替换参数,多媒体类型。
+    /// </summary>
+    [JsonPropertyName("cardMediaIdParamMap")]
+    public string CardMediaIdParamMap { get; set; }
+}
+
+/// <summary>
+/// 卡片模板内容替换参数
+/// </summary>
+public class DingTalkCardParamMap
+{
+    /// <summary>
+    /// 片模板内容替换参数
+    /// </summary>
+    [JsonPropertyName("sys_full_json_obj")]
+    public string SysFullJsonObj { get; set; }
+}

+ 26 - 0
Admin.NET/Admin.NET.Core/Integrations/DingTalk/Dto/DingTalkConversationTypeEnum.cs

@@ -0,0 +1,26 @@
+// 大名科技(天津)有限公司 版权所有
+//
+// 此源代码遵循位于源代码树根目录中的 LICENSE 文件的许可证
+//
+// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动
+//
+// 任何基于本项目二次开发而产生的一切法律纠纷和责任,均与作者无关
+
+namespace Admin.NET.Core.Integrations;
+/// <summary>
+/// 钉钉发送的会话类型
+/// </summary>
+[Description("钉钉发送的会话类型")]
+public enum DingTalkConversationTypeEnum
+{
+    /// <summary>
+    /// 单聊
+    /// </summary>
+    [Description("单聊")]
+    SingleChat = 0,
+    /// <summary>
+    /// 群聊
+    /// </summary>
+    [Description("群聊")]
+    GroupChat = 1
+}

+ 33 - 0
Admin.NET/Admin.NET.Core/Integrations/DingTalk/Dto/DingTalkEmpFieldDataVo.cs

@@ -0,0 +1,33 @@
+// 大名科技(天津)有限公司 版权所有
+//
+// 此源代码遵循位于源代码树根目录中的 LICENSE 文件的许可证
+//
+// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动
+//
+// 任何基于本项目二次开发而产生的一切法律纠纷和责任,均与作者无关
+
+namespace Admin.NET.Core.Integrations;
+public class DingTalkEmpFieldDataVo
+{
+    /// <summary>
+    /// 字段名称
+    /// </summary>
+    [JsonPropertyName("field_name")]
+    public string FieldName { get; set; }
+    /// <summary>
+    /// 字段标识
+    /// </summary>
+    [JsonPropertyName("field_code")]
+    public string FieldCode { get; set; }
+    /// <summary>
+    /// 分组标识
+    /// </summary>
+    [JsonPropertyName("group_id")]
+    public string GroupId { get; set; }
+    /// <summary>
+    /// 
+    /// </summary>
+    [JsonPropertyName("field_value_list")]
+    public List<DingTalkFieldValueVo> FieldValueList { get; set; }
+
+}

+ 28 - 0
Admin.NET/Admin.NET.Core/Integrations/DingTalk/Dto/DingTalkEmpRosterFieldVo.cs

@@ -0,0 +1,28 @@
+// 大名科技(天津)有限公司 版权所有
+//
+// 此源代码遵循位于源代码树根目录中的 LICENSE 文件的许可证
+//
+// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动
+//
+// 任何基于本项目二次开发而产生的一切法律纠纷和责任,均与作者无关
+
+namespace Admin.NET.Core.Integrations;
+
+public class DingTalkEmpRosterFieldVo
+{
+    /// <summary>
+    /// 企业的corpid。
+    /// </summary>
+    [JsonPropertyName("corp_id")]
+    public string CorpId { get; set; }
+    /// <summary>
+    /// 返回的字段信息列表
+    /// </summary>
+    [JsonPropertyName("field_data_list")]
+    public List<DingTalkEmpFieldDataVo> FieldDataList { get; set; }
+    /// <summary>
+    /// 员工的userid
+    /// </summary>
+    [JsonPropertyName("userid")]
+    public string UserId { get; set; }
+}

+ 29 - 0
Admin.NET/Admin.NET.Core/Integrations/DingTalk/Dto/DingTalkFieldValueVo.cs

@@ -0,0 +1,29 @@
+// 大名科技(天津)有限公司 版权所有
+//
+// 此源代码遵循位于源代码树根目录中的 LICENSE 文件的许可证
+//
+// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动
+//
+// 任何基于本项目二次开发而产生的一切法律纠纷和责任,均与作者无关
+
+namespace Admin.NET.Core.Integrations;
+
+public class DingTalkFieldValueVo
+{
+
+    /// <summary>
+    /// 第几条的明细标识,下标从0开始
+    /// </summary>
+    [JsonPropertyName("item_index")]
+    public int ItemIndex { get; set; }
+    /// <summary>
+    /// 字段展示值,选项类型字段对应选项的value。
+    /// </summary>
+    [JsonPropertyName("label")]
+    public string Label { get; set; }
+    /// <summary>
+    /// 字段取值,选项类型字段对应选项的key。
+    /// </summary>
+    [JsonPropertyName("value")]
+    public string Value { get; set; }
+}

+ 124 - 0
Admin.NET/Admin.NET.Core/Integrations/DingTalk/Dto/DingTalkSendInteractiveCardsInput.cs

@@ -0,0 +1,124 @@
+// 大名科技(天津)有限公司 版权所有
+//
+// 此源代码遵循位于源代码树根目录中的 LICENSE 文件的许可证
+//
+// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动
+//
+// 任何基于本项目二次开发而产生的一切法律纠纷和责任,均与作者无关
+
+namespace Admin.NET.Core.Integrations;
+public class DingTalkSendInteractiveCardsInput
+{
+    /// <summary>
+    /// 互动卡片的消息模板ID
+    /// </summary>
+    [Required(ErrorMessage = "互动卡片的消息模板Id必填!")]
+    [JsonPropertyName("cardTemplateId")]
+    public string? CardTemplateId { get; set; }
+    /// <summary>
+    /// 群ID
+    /// </summary>
+    /// <remarks>
+    /// 1、基于群模板创建的群。
+    /// 企业内部应用,调用创建群接口获取open_conversation_id参数值。
+    /// 2、安装群聊酷应用的群。
+    /// 企业内部应用,通过群内安装酷应用事件获取回调参数OpenConversationId参数值。
+    /// </remarks>
+    [JsonPropertyName("openConversationId")]
+    public string OpenConversationId { get; set; }
+    /// <summary>
+    /// 接收人userId列表。
+    /// </summary>
+    /// <remarks>
+    /// 单聊:receiverUserIdList填写用户ID,最大值20。
+    /// 群聊:receiverUserIdList填写用户ID,表示当前对应ID的群内用户可见
+    /// receiverUserIdList参数不填写,表示当前群内所有用户可见
+    /// </remarks>
+    [Required(ErrorMessage = "接收人userId列表必填!")]
+    [JsonPropertyName("receiverUserIdList")]
+    public List<string>? ReceiverUserIdList { get; set; }
+    /// <summary>
+    /// 唯一标示卡片的外部编码
+    /// </summary>
+    [Required(ErrorMessage = "唯一标示卡片的外部编码必填!")]
+    [JsonPropertyName("outTrackId")]
+    public string? OutTrackId { get; set; }
+    /// <summary>
+    /// 机器人的编码
+    /// </summary>
+    [JsonPropertyName("robotCode")]
+    public string RobotCode { get; set; }
+    /// <summary>
+    /// 发送的会话类型:
+    /// </summary>
+    [Required(ErrorMessage = "会话类型必填!")]
+    [JsonPropertyName("conversationType")]
+    public DingTalkConversationTypeEnum? ConversationType { get; set; }
+    /// <summary>
+    /// 卡片回调时的路由Key,用于查询注册的callbackUrl
+    /// </summary>
+    [JsonPropertyName("callbackRouteKey")]
+    public string CallbackRouteKey { get; set; }
+    /// <summary>
+    /// 卡片公有数据。
+    /// </summary>
+    [Required(ErrorMessage = "卡片公有数据必填!")]
+    [JsonPropertyName("cardData")]
+    public DingTalkCardData CardData { get; set; }
+}
+
+
+public class GetDingTalkCardMessageReadStatusInput
+{
+    /// <summary>
+    /// 机器人的编码
+    /// </summary>
+    [JsonPropertyName("robotCode")]
+    public string robotCode { set; get; }
+    /// <summary>
+    /// 消息唯一标识,可通过批量发送人与机器人会话中机器人消息接口返回参数中processQueryKey字段获取。
+    /// </summary>
+    [JsonPropertyName("processQueryKey")]
+    public string processQueryKey { set; get; }
+}
+
+public class GetDingTalkCardMessageReadStatusOutput
+{
+    /// <summary>
+    /// 消息发送状态
+    /// </summary>
+    /// <remarks>
+    /// SUCCESS:成功
+    /// RECALLED:已撤回
+    /// PROCESSING: 处理中
+    /// </remarks>
+    [JsonPropertyName("sendStatus")]
+    public string sendStatus { get; set; }
+    [JsonPropertyName("messageReadInfoList")]
+    public DingTalkCardMessageReadInfoList messageReadInfoList { get; set; }
+}
+/// <summary>
+/// 钉钉卡片消息已读情况
+/// </summary>
+public class DingTalkCardMessageReadInfoList
+{
+    /// <summary>
+    /// 消息接收者名称
+    /// </summary>
+    [JsonPropertyName("name")]
+    public string name { set; get; }
+    /// <summary>
+    /// 消息接收者的userId
+    /// </summary>
+    [JsonPropertyName("userId")]
+    public string userId { set; get; }
+    /// <summary>
+    /// 已读状态:
+    /// </summary>
+    /// <remarks>
+    /// READ:已读
+    /// UNREAD:未读
+    /// </remarks>
+    [JsonPropertyName("readStatus")]
+    public string readStatus { set; get; }
+}

+ 25 - 0
Admin.NET/Admin.NET.Core/Integrations/DingTalk/Dto/DingTalkSendInteractiveCardsOutput.cs

@@ -0,0 +1,25 @@
+// 大名科技(天津)有限公司 版权所有
+//
+// 此源代码遵循位于源代码树根目录中的 LICENSE 文件的许可证
+//
+// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动
+//
+// 任何基于本项目二次开发而产生的一切法律纠纷和责任,均与作者无关
+
+namespace Admin.NET.Core.Integrations;
+/// <summary>
+/// 发送钉钉互动卡片返回
+/// </summary>
+public class DingTalkSendInteractiveCardsOutput
+{
+    /// <summary>
+    /// 返回结果
+    /// </summary>
+    [JsonPropertyName("success")]
+    public bool Success { get; set; }
+    /// <summary>
+    /// 创建卡片结果
+    /// </summary>
+    [JsonPropertyName("result")]
+    public DingTalkSendInteractiveCardsResult Result { get; set; }
+}

+ 17 - 0
Admin.NET/Admin.NET.Core/Integrations/DingTalk/Dto/DingTalkSendInteractiveCardsResult.cs

@@ -0,0 +1,17 @@
+// 大名科技(天津)有限公司 版权所有
+//
+// 此源代码遵循位于源代码树根目录中的 LICENSE 文件的许可证
+//
+// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动
+//
+// 任何基于本项目二次开发而产生的一切法律纠纷和责任,均与作者无关
+
+namespace Admin.NET.Core.Integrations;
+public class DingTalkSendInteractiveCardsResult
+{
+    /// <summary>
+    /// 用于业务方后续查看已读列表的查询key。
+    /// </summary>
+    [JsonPropertyName("processQueryKey")]
+    public string ProcessQueryKey { get; set; }
+}

+ 36 - 0
Admin.NET/Admin.NET.Core/Integrations/DingTalk/Dto/GetDingTalkCurrentEmployeesListInput.cs

@@ -0,0 +1,36 @@
+// 大名科技(天津)有限公司 版权所有
+//
+// 此源代码遵循位于源代码树根目录中的 LICENSE 文件的许可证
+//
+// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动
+//
+// 任何基于本项目二次开发而产生的一切法律纠纷和责任,均与作者无关
+
+namespace Admin.NET.Core.Integrations;
+/// <summary>
+/// 获取在职员工列表参数
+/// </summary>
+public class GetDingTalkCurrentEmployeesListInput
+{
+    /// <summary>
+    /// 在职员工状态筛选,可以查询多个状态。不同状态之间使用英文逗号分隔。
+    /// </summary>
+    /// <remarks>
+    /// 2:试用期
+    /// 3:正式
+    /// 5:待离职
+    /// -1:无状态
+    /// </remarks>
+    [JsonPropertyName("status_list")]
+    public string StatusList { get; set; }
+    /// <summary>
+    /// 分页游标,从0开始。根据返回结果里的next_cursor是否为空来判断是否还有下一页,且再次调用时offset设置成next_cursor的值。
+    /// </summary>
+    [JsonPropertyName("offset")]
+    public int Offset { get; set; }
+    /// <summary>
+    /// 分页大小,最大50。
+    /// </summary>
+    [JsonPropertyName("size")]
+    public int Size { get; set; }
+}

+ 23 - 0
Admin.NET/Admin.NET.Core/Integrations/DingTalk/Dto/GetDingTalkCurrentEmployeesListOutput.cs

@@ -0,0 +1,23 @@
+// 大名科技(天津)有限公司 版权所有
+//
+// 此源代码遵循位于源代码树根目录中的 LICENSE 文件的许可证
+//
+// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动
+//
+// 任何基于本项目二次开发而产生的一切法律纠纷和责任,均与作者无关
+
+namespace Admin.NET.Core.Integrations;
+public class GetDingTalkCurrentEmployeesListOutput
+{
+    /// <summary>
+    /// 查询到的员工userid列表
+    /// </summary>
+    [JsonPropertyName("data_list")]
+    public List<string> DataList { get; set; }
+    /// <summary>
+    /// 下一次分页调用的offset值,当返回结果里没有next_cursor时,表示分页结束。
+    /// </summary>
+    [JsonPropertyName("next_cursor")]
+    public int? NextCursor { get; set; }
+
+}

+ 27 - 0
Admin.NET/Admin.NET.Core/Integrations/DingTalk/Dto/GetDingTalkCurrentEmployeesRosterListInput.cs

@@ -0,0 +1,27 @@
+// 大名科技(天津)有限公司 版权所有
+//
+// 此源代码遵循位于源代码树根目录中的 LICENSE 文件的许可证
+//
+// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动
+//
+// 任何基于本项目二次开发而产生的一切法律纠纷和责任,均与作者无关
+
+namespace Admin.NET.Core.Integrations;
+public class GetDingTalkCurrentEmployeesRosterListInput
+{
+    /// <summary>
+    /// 员工的userid列表,多个userid之间使用逗号分隔,一次最多支持传100个值。
+    /// </summary>
+    [JsonPropertyName("userid_list")]
+    public string UserIdList { get; set; }
+    /// <summary>
+    /// 需要获取的花名册字段field_code值列表,多个字段之间使用逗号分隔,一次最多支持传100个值。
+    /// </summary>
+    [JsonPropertyName("field_filter_list")]
+    public string FieldFilterList { get; set; }
+    /// <summary>
+    /// 应用的AgentId
+    /// </summary>
+    [JsonPropertyName("agentid")]
+    public string AgentId { get; set; }
+}

+ 47 - 0
Admin.NET/Admin.NET.Core/Integrations/DingTalk/Dto/GetDingTalkToken.cs

@@ -0,0 +1,47 @@
+// 大名科技(天津)有限公司 版权所有
+//
+// 此源代码遵循位于源代码树根目录中的 LICENSE 文件的许可证
+//
+// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动
+//
+// 任何基于本项目二次开发而产生的一切法律纠纷和责任,均与作者无关
+
+namespace Admin.NET.Core.Integrations;
+public class GetDingTalkTokenInput
+{
+    /// <summary>
+    /// 应用的唯一标识key 
+    /// </summary>
+    [JsonPropertyName("appkey")]
+    public string AppKey { get; set; }
+    /// <summary>
+    /// 应用的密钥。AppKey和AppSecret可在钉钉开发者后台的应用详情页面获取。
+    /// </summary>
+    [JsonPropertyName("appsecret")]
+    public string AppSecret { get; set; }
+}
+
+public class GetDingTalkTokenOutput
+{
+    /// <summary>
+    /// 生成的access_token 
+    /// </summary>
+    [JsonPropertyName("access_token")]
+    public string AccessToken { get; set; }
+    /// <summary>
+    /// access_token的过期时间,单位秒
+    /// </summary>
+    [JsonPropertyName("expires_in")]
+    public int ExpiresIn { get; set; }
+    /// <summary>
+    /// 返回码描述。
+    /// </summary>
+    [JsonPropertyName("errmsg")]
+    public string ErrMsg { get; set; }
+    /// <summary>
+    /// 返回码。
+    /// </summary>
+    [JsonPropertyName("errcode")]
+    public int ErrCode { get; set; }
+}
+

+ 59 - 0
Admin.NET/Admin.NET.Core/Integrations/DingTalk/IDingTalkApi.cs

@@ -0,0 +1,59 @@
+// 大名科技(天津)有限公司 版权所有
+//
+// 此源代码遵循位于源代码树根目录中的 LICENSE 文件的许可证
+//
+// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动
+//
+// 任何基于本项目二次开发而产生的一切法律纠纷和责任,均与作者无关
+
+using Furion.RemoteRequest;
+
+namespace Admin.NET.Core.Integrations;
+public interface IDingTalkApi : IHttpDispatchProxy
+{
+    /// <summary>
+    /// 获取企业内部应用的access_token
+    /// </summary>
+    /// <returns></returns>
+    [Get("https://oapi.dingtalk.com/gettoken")]
+    Task<GetDingTalkTokenOutput> GetDingTalkToken([QueryString] GetDingTalkTokenInput input);
+    /// <summary>
+    /// 获取在职员工列表
+    /// </summary>
+    /// <param name="access_token">调用该接口的应用凭证</param>
+    /// <param name="input"></param>
+    /// <returns></returns>
+    [Post("https://oapi.dingtalk.com/topapi/smartwork/hrm/employee/queryonjob")]
+    Task<DingTalkBaseResponse<GetDingTalkCurrentEmployeesListOutput>> GetDingTalkCurrentEmployeesList([QueryString] string access_token,
+        [Body, Required] GetDingTalkCurrentEmployeesListInput input);
+    /// <summary>
+    /// 获取员工花名册字段信息
+    /// </summary>
+    /// <param name="access_token">调用该接口的应用凭证</param>
+    /// <param name="input"></param>
+    /// <returns></returns>
+    [Post("https://oapi.dingtalk.com/topapi/smartwork/hrm/employee/v2/list")]
+    Task<DingTalkBaseResponse<List<DingTalkEmpRosterFieldVo>>> GetDingTalkCurrentEmployeesRosterList([QueryString] string access_token,
+        [Body, Required] GetDingTalkCurrentEmployeesRosterListInput input);
+    /// <summary>
+    /// 发送钉钉互动卡片
+    /// </summary>
+    /// <param name="token">调用该接口的访问凭证</param>
+    /// <param name="input"></param>
+    /// <returns></returns>
+    [Post("https://api.dingtalk.com/v1.0/im/interactiveCards/send")]
+    Task<DingTalkSendInteractiveCardsOutput> DingTalkSendInteractiveCards(
+        [Headers("x-acs-dingtalk-access-token")] string token,
+        [Body] DingTalkSendInteractiveCardsInput input);
+    /// <summary>
+    /// 获取钉钉卡片消息读取状态
+    /// </summary>
+    /// <param name="token"></param>
+    /// <param name="input"></param>
+    /// <returns></returns>
+    [Get("https://api.dingtalk.com/v1.0/robot/oToMessages/readStatus")]
+    Task<GetDingTalkCardMessageReadStatusOutput> GetDingTalkCardMessageReadStatus(
+    [Headers("x-acs-dingtalk-access-token")] string token,
+    [QueryString] GetDingTalkCardMessageReadStatusInput input);
+
+}

+ 29 - 0
Admin.NET/Admin.NET.Core/Option/DingTalkOptions.cs

@@ -0,0 +1,29 @@
+// 大名科技(天津)有限公司 版权所有
+//
+// 此源代码遵循位于源代码树根目录中的 LICENSE 文件的许可证
+//
+// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动
+//
+// 任何基于本项目二次开发而产生的一切法律纠纷和责任,均与作者无关
+
+namespace Admin.NET.Core;
+
+public sealed class DingTalkOptions : IConfigurableOptions
+{
+    /// <summary>
+    /// AppId
+    /// </summary>
+    public string AppId { get; set; }
+    /// <summary>
+    /// AgentId
+    /// </summary>
+    public string AgentId { get; set; }
+    /// <summary>
+    /// 原 AppKey 和 SuiteKey
+    /// </summary>
+    public string ClientId { get; set; }
+    /// <summary>
+    /// 原 AppSecret 和 SuiteSecret
+    /// </summary>
+    public string ClientSecret { get; set; }
+}

+ 1 - 1
Admin.NET/Admin.NET.Core/Option/EmailOptions.cs

@@ -57,4 +57,4 @@ public sealed class EmailOptions : IConfigurableOptions
     /// 默认邮件标题
     /// </summary>
     public string DefaultFromName { get; set; }
-}
+}

+ 1 - 0
Admin.NET/Admin.NET.Web.Core/ProjectOptions.cs

@@ -39,6 +39,7 @@ public static class ProjectOptions
         services.AddConfigurableOptions<CryptogramOptions>();
         services.AddConfigurableOptions<SMSOptions>();
         services.AddConfigurableOptions<EventBusOptions>();
+        services.AddConfigurableOptions<DingTalkOptions>();
         services.Configure<IpRateLimitOptions>(App.Configuration.GetSection("IpRateLimiting"));
         services.Configure<IpRateLimitPolicies>(App.Configuration.GetSection("IpRateLimitPolicies"));
         services.Configure<ClientRateLimitOptions>(App.Configuration.GetSection("ClientRateLimiting"));