Selaa lähdekoodia

!1940 钉钉对接新增审批相关功能
Merge pull request !1940 from PChief/v2

zuohuaijun 4 kuukautta sitten
vanhempi
commit
253155bfba

+ 45 - 0
Admin.NET/Plugins/Admin.NET.Plugin.DingTalk/Entity/DingTalkDept.cs

@@ -0,0 +1,45 @@
+// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
+//
+// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
+//
+// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
+
+namespace Admin.NET.Plugin.DingTalk;
+
+/// <summary>
+/// 钉钉部门信息
+/// </summary>
+[SugarTable("ding_talk_dept", "钉钉部门表")]
+public class DingTalkDept
+{
+    /// <summary>
+    /// 部门id
+    /// </summary>
+    [SugarColumn(ColumnName = "Id", ColumnDescription = "部门id", IsPrimaryKey = true, IsIdentity = false)]
+    [Required]
+    public long dept_id { get; set; }
+    /// <summary>
+    /// 上级部门id
+    /// </summary>
+    [SugarColumn(ColumnDescription = "上级部门id")]
+    [Required]
+    public virtual long parent_id { get; set; }
+
+    /// <summary>
+    /// 部门名
+    /// </summary>
+    [SugarColumn(ColumnDescription = "部门名", Length = 64)]
+    [MaxLength(64)]
+    public string? name { get; set; }
+    /// <summary>
+    /// 创建时间
+    /// </summary>
+    [SugarColumn(ColumnDescription = "创建时间", IsNullable = true, IsOnlyIgnoreUpdate = true)]
+    public virtual DateTime CreateTime { get; set; }
+
+    /// <summary>
+    /// 更新时间
+    /// </summary>
+    [SugarColumn(ColumnDescription = "更新时间")]
+    public virtual DateTime? UpdateTime { get; set; }
+}

+ 23 - 0
Admin.NET/Plugins/Admin.NET.Plugin.DingTalk/Entity/DingTalkWokerflowConfig.cs

@@ -0,0 +1,23 @@
+// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
+//
+// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
+//
+// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
+
+namespace Admin.NET.Plugin.DingTalk;
+
+[SugarTable("ding_talk_wokerflow_config", "审批配置表")]
+public class DingTalkWokerflowConfig
+{
+    /// <summary>
+    /// 审批名
+    /// </summary>
+    [SugarColumn(ColumnDescription = "审批名", IsPrimaryKey = true, IsIdentity = false)]
+    public string WorkflowName { get; set; }
+
+    /// <summary>
+    /// 审批单Id
+    /// </summary>
+    [SugarColumn(ColumnDescription = "审批单Id")]
+    public string ProcessCode { get; set; }
+}

+ 86 - 0
Admin.NET/Plugins/Admin.NET.Plugin.DingTalk/Entity/DingTalkWokerflowLog.cs

@@ -0,0 +1,86 @@
+// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
+//
+// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
+//
+// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
+
+namespace Admin.NET.Plugin.DingTalk;
+
+[SugarTable("ding_talk_wokerflow_log", "钉钉审批日志")]
+public class DingTalkWokerflowLog
+{
+    /// <summary>
+    /// 审批实例ID
+    /// </summary>
+    [SugarColumn(ColumnDescription = "审批实例ID", IsPrimaryKey = true, IsIdentity = false)]
+    public string instanceId { get; set; }
+
+    /// <summary>
+    /// 审批单号
+    /// </summary>
+    [SugarColumn(ColumnDescription = "审批单号")]
+    public string? WorkflowId { get; set; }
+
+    /// <summary>
+    /// 来源单据
+    /// </summary>
+    [SugarColumn(ColumnDescription = "来源单据")]
+    public string SourceDocument { get; set; }
+
+    /// <summary>
+    /// 审批完成时间
+    /// </summary>
+    [SugarColumn(ColumnDescription = "审批完成时间")]
+    public DateTime? EndTime { get; set; }
+
+    /// <summary>
+    /// 其他信息
+    /// </summary>
+    [SugarColumn(ColumnDescription = "其他信息", IsJson = true)]
+    public Dictionary<string, object>? other_info { get; set; }
+
+    /// <summary>
+    /// 是否回传结果给第三方
+    /// </summary>
+    [SugarColumn(ColumnDescription = "是否回传结果")]
+    public bool? isReturn { get; set; }
+
+    /// <summary>
+    /// 审批状态
+    /// </summary>
+    /// <remarks>
+    /// RUNNING:审批中 TERMINATED:已撤销 COMPLETED:审批完成
+    /// /// </remarks>
+    [SugarColumn(ColumnDescription = "审批状态")]
+    public string Status { get; set; }
+
+    /// <summary>
+    /// 任务ID
+    /// </summary>
+    [SugarColumn(ColumnDescription = "任务ID")]
+    public long? taskId { get; set; }
+
+    /// <summary>
+    /// 审批结果 agree:同意 refuse:拒绝
+    /// </summary>
+    [SugarColumn(ColumnDescription = "审批结果")]
+    public string? Result { get; set; }
+
+    /// <summary>
+    /// 创建者姓名
+    /// </summary>
+    [SugarColumn(ColumnDescription = "创建者姓名", Length = 64, IsOnlyIgnoreUpdate = true)]
+    public string? CreateUserName { get; set; }
+
+    /// <summary>
+    /// 创建时间
+    /// </summary>
+    [SugarColumn(ColumnDescription = "创建时间", IsNullable = true, IsOnlyIgnoreUpdate = true)]
+    public DateTime CreateTime { get; set; }
+
+    /// <summary>
+    /// 更新时间
+    /// </summary>
+    [SugarColumn(ColumnDescription = "更新时间")]
+    public virtual DateTime? UpdateTime { get; set; }
+}

+ 95 - 0
Admin.NET/Plugins/Admin.NET.Plugin.DingTalk/Job/SyncDingTalkDeptJob.cs

@@ -0,0 +1,95 @@
+// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
+//
+// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
+//
+// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
+
+using Admin.NET.Plugin.DingTalk;
+using Furion.Schedule;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging;
+
+namespace Admin.NET.Plugin.Job;
+
+/// <summary>
+/// 同步钉钉角色job,自动同步触发器请在web页面按需求设置
+/// </summary>
+[JobDetail("SyncDingTalkDeptJob", Description = "同步钉钉部门", GroupName = "default", Concurrent = false)]
+[Daily(TriggerId = "SyncDingTalkDeptTrigger", Description = "同步钉钉部门")]
+public class SyncDingTalkDeptJob : IJob
+{
+    private readonly IServiceScopeFactory _scopeFactory;
+    private readonly IDingTalkApi _dingTalkApi;
+    private readonly ILogger _logger;
+    private readonly SqlSugarRepository<DingTalkDept> 部门信息;
+
+    public SyncDingTalkDeptJob(
+        IServiceScopeFactory scopeFactory,
+        IDingTalkApi dingTalkApi,
+        SqlSugarRepository<DingTalkDept> _部门信息,
+        ILoggerFactory loggerFactory)
+    {
+        _scopeFactory = scopeFactory;
+        _dingTalkApi = dingTalkApi;
+        部门信息 = _部门信息;
+        _logger = loggerFactory.CreateLogger(CommonConst.SysLogCategoryName);
+    }
+
+    public async Task ExecuteAsync(JobExecutingContext context, CancellationToken stoppingToken)
+    {
+        using var serviceScope = _scopeFactory.CreateScope();
+        var _dingTalkOptions = serviceScope.ServiceProvider.GetRequiredService<IOptions<DingTalkOptions>>();
+
+        // 获取Token
+        var tokenRes = await _dingTalkApi.GetDingTalkToken(_dingTalkOptions.Value.ClientId, _dingTalkOptions.Value.ClientSecret);
+        if (tokenRes.ErrCode != 0)
+            throw Oops.Oh(tokenRes.ErrMsg);
+
+        var dingTalkDeptList = new List<DingTalkDept>();
+        // 获取部门列表
+        var deptIdsRes = await _dingTalkApi.GetDingTalkDept(tokenRes.AccessToken, new GetDingTalkDeptInput
+        { dept_id = 1 });
+        if (deptIdsRes.ErrCode != 0)
+        {
+            _logger.LogError(deptIdsRes.ErrMsg);
+            throw Oops.Oh(deptIdsRes.ErrMsg);
+        }
+        dingTalkDeptList.AddRange(deptIdsRes.Result.Select(d => new DingTalkDept
+        {
+            dept_id = d.dept_id,
+            name = d.name,
+            parent_id = d.parent_id
+        }));
+        foreach (var item in deptIdsRes.Result)
+        {
+            dingTalkDeptList.AddRange(await 获取部门列表(tokenRes.AccessToken, item.dept_id));
+        }
+        部门信息.InsertOrUpdateAsync(dingTalkDeptList);
+        var originColor = Console.ForegroundColor;
+        Console.ForegroundColor = ConsoleColor.Blue;
+        Console.WriteLine("【" + DateTime.Now + "】同步钉钉部门");
+        Console.ForegroundColor = originColor;
+    }
+
+    private async Task<List<DingTalkDept>> 获取部门列表(string token, long dept_id)
+    {
+        List<DingTalkDept> listTemp = new List<DingTalkDept>();
+        var deptIdsRes = await _dingTalkApi.GetDingTalkDept(token, new GetDingTalkDeptInput
+        {dept_id= dept_id });
+        if (deptIdsRes.ErrCode != 0)
+        {
+            return null;
+        }
+        listTemp.AddRange(deptIdsRes.Result.Select(x => new DingTalkDept
+        {
+            dept_id = x.dept_id,
+            name = x.name,
+            parent_id = x.parent_id
+        }));
+        foreach (var item in deptIdsRes.Result)
+        {
+            listTemp.AddRange(await 获取部门列表(token, item.dept_id));
+        }
+        return listTemp;
+    }
+}

+ 98 - 0
Admin.NET/Plugins/Admin.NET.Plugin.DingTalk/Job/SyncWokerflowLogJob.cs

@@ -0,0 +1,98 @@
+// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
+//
+// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
+//
+// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
+
+using Admin.NET.Plugin.DingTalk;
+using Furion.Schedule;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging;
+
+namespace Admin.NET.Plugin.Job;
+
+/// <summary>
+/// 同步钉钉角色job,自动同步触发器请在web页面按需求设置
+/// </summary>
+[JobDetail(
+    "SyncWokerflowLogJob",
+    Description = "同步钉钉审批状态",
+    GroupName = "default",
+    Concurrent = false
+)]
+[Daily(TriggerId = "SyncWokerflowLogTrigger", Description = "同步钉钉审批状态")]
+public class SyncWokerflowLogJob : IJob
+{
+    private readonly IServiceScopeFactory _scopeFactory;
+    private readonly IDingTalkApi _dingTalkApi;
+    private readonly ILogger _logger;
+    private readonly SqlSugarRepository<DingTalkDept> 部门信息;
+    private readonly SqlSugarRepository<DingTalkWokerflowLog> 钉钉审批记录;
+
+    public SyncWokerflowLogJob(
+        IServiceScopeFactory scopeFactory,
+        IDingTalkApi dingTalkApi,
+        SqlSugarRepository<DingTalkDept> _部门信息,
+        SqlSugarRepository<DingTalkWokerflowLog> _钉钉审批记录,
+        ILoggerFactory loggerFactory
+    )
+    {
+        _scopeFactory = scopeFactory;
+        _dingTalkApi = dingTalkApi;
+        部门信息 = _部门信息;
+        钉钉审批记录 = _钉钉审批记录;
+        _logger = loggerFactory.CreateLogger(CommonConst.SysLogCategoryName);
+    }
+
+    public async Task ExecuteAsync(JobExecutingContext context, CancellationToken stoppingToken)
+    {
+        using var serviceScope = _scopeFactory.CreateScope();
+        var _dingTalkOptions = serviceScope.ServiceProvider.GetRequiredService<
+            IOptions<DingTalkOptions>
+        >();
+
+        // 获取Token
+        var tokenRes = await _dingTalkApi.GetDingTalkToken(
+            _dingTalkOptions.Value.ClientId,
+            _dingTalkOptions.Value.ClientSecret
+        );
+        if (tokenRes.ErrCode != 0)
+            throw Oops.Oh(tokenRes.ErrMsg);
+
+        var dingTalkDeptList = new List<DingTalkDept>();
+        // 获取未完成审批列表
+        List<DingTalkWokerflowLog> flow_list = await 钉钉审批记录.GetListAsync(t =>
+            t.Status == "RUNNING"
+        );
+        List<DingTalkWokerflowLog> update_list = new List<DingTalkWokerflowLog>();
+        if (flow_list?.Count > 0)
+        {
+            foreach (var item in flow_list)
+            {
+                var flow = await _dingTalkApi.GetProcessInstances(
+                    tokenRes.AccessToken,
+                    item.instanceId
+                );
+                if (flow.Result.Status != item.Status)
+                {
+                    item.Status = flow.Result.Status;
+                    item.UpdateTime = DateTime.Now;
+                    item.WorkflowId = flow.Result.BusinessId;
+                    item.taskId = flow
+                        .Result.Tasks.FirstOrDefault(t => t.Status == "RUNNING")
+                        ?.TaskId;
+                    update_list.Add(item);
+                }
+            }
+
+            if (update_list.Count > 0)
+            {
+                钉钉审批记录.UpdateRangeAsync(update_list);
+            }
+            var originColor = Console.ForegroundColor;
+            Console.ForegroundColor = ConsoleColor.Blue;
+            Console.WriteLine("【" + DateTime.Now + "】同步钉钉审批记录状态");
+            Console.ForegroundColor = originColor;
+        }
+    }
+}

+ 67 - 8
Admin.NET/Plugins/Admin.NET.Plugin.DingTalk/Service/DingTalkService.cs

@@ -4,6 +4,8 @@
 //
 // 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
 
+using static SKIT.FlurlHttpClient.Wechat.Api.Models.CgibinExpressIntracityQueryFlowResponse.Types;
+
 namespace Admin.NET.Plugin.DingTalk.Service;
 
 /// <summary>
@@ -14,12 +16,17 @@ public class DingTalkService : IDynamicApiController, IScoped
 {
     private readonly IDingTalkApi _dingTalkApi;
     private readonly DingTalkOptions _dingTalkOptions;
+    private readonly SqlSugarRepository<DingTalkWokerflowLog> 钉钉审批记录;
 
-    public DingTalkService(IDingTalkApi dingTalkApi,
-        IOptions<DingTalkOptions> dingTalkOptions)
+    public DingTalkService(
+        IDingTalkApi dingTalkApi,
+        IOptions<DingTalkOptions> dingTalkOptions,
+        SqlSugarRepository<DingTalkWokerflowLog> _钉钉审批记录
+    )
     {
         _dingTalkApi = dingTalkApi;
         _dingTalkOptions = dingTalkOptions.Value;
+        钉钉审批记录 = _钉钉审批记录;
     }
 
     /// <summary>
@@ -29,7 +36,10 @@ public class DingTalkService : IDynamicApiController, IScoped
     [DisplayName("获取企业内部应用的access_token")]
     public async Task<GetDingTalkTokenOutput> GetDingTalkToken()
     {
-        var tokenRes = await _dingTalkApi.GetDingTalkToken(_dingTalkOptions.ClientId, _dingTalkOptions.ClientSecret);
+        var tokenRes = await _dingTalkApi.GetDingTalkToken(
+            _dingTalkOptions.ClientId,
+            _dingTalkOptions.ClientSecret
+        );
         if (tokenRes.ErrCode != 0)
         {
             throw Oops.Oh(tokenRes.ErrMsg);
@@ -44,7 +54,12 @@ public class DingTalkService : IDynamicApiController, IScoped
     /// <param name="input"></param>
     /// <returns></returns>
     [HttpPost, DisplayName("获取在职员工列表")]
-    public async Task<DingTalkBaseResponse<GetDingTalkCurrentEmployeesListOutput>> GetDingTalkCurrentEmployeesList(string access_token, [Required] GetDingTalkCurrentEmployeesListInput input)
+    public async Task<
+        DingTalkBaseResponse<GetDingTalkCurrentEmployeesListOutput>
+    > GetDingTalkCurrentEmployeesList(
+        string access_token,
+        [Required] GetDingTalkCurrentEmployeesListInput input
+    )
     {
         return await _dingTalkApi.GetDingTalkCurrentEmployeesList(access_token, input);
     }
@@ -56,7 +71,12 @@ public class DingTalkService : IDynamicApiController, IScoped
     /// <param name="input"></param>
     /// <returns></returns>
     [HttpPost, DisplayName("获取员工花名册字段信息")]
-    public async Task<DingTalkBaseResponse<List<DingTalkEmpRosterFieldVo>>> GetDingTalkCurrentEmployeesRosterList(string access_token, [Required] GetDingTalkCurrentEmployeesRosterListInput input)
+    public async Task<
+        DingTalkBaseResponse<List<DingTalkEmpRosterFieldVo>>
+    > GetDingTalkCurrentEmployeesRosterList(
+        string access_token,
+        [Required] GetDingTalkCurrentEmployeesRosterListInput input
+    )
     {
         return await _dingTalkApi.GetDingTalkCurrentEmployeesRosterList(access_token, input);
     }
@@ -68,7 +88,10 @@ public class DingTalkService : IDynamicApiController, IScoped
     /// <param name="input"></param>
     /// <returns></returns>
     [DisplayName("给指定用户发送钉钉互动卡片")]
-    public async Task<DingTalkSendInteractiveCardsOutput> DingTalkSendInteractiveCards(string token, DingTalkSendInteractiveCardsInput input)
+    public async Task<DingTalkSendInteractiveCardsOutput> DingTalkSendInteractiveCards(
+        string token,
+        DingTalkSendInteractiveCardsInput input
+    )
     {
         return await _dingTalkApi.DingTalkSendInteractiveCards(token, input);
     }
@@ -80,8 +103,44 @@ public class DingTalkService : IDynamicApiController, IScoped
     /// <param name="input"></param>
     /// <returns></returns>
     [DisplayName("给指定用户发送钉钉消息卡片")]
-    public async Task<DingTalkCreateAndDeliverOutput> DingTalkCreateAndDeliver(string token, DingTalkCreateAndDeliverInput input)
+    public async Task<DingTalkCreateAndDeliverOutput> DingTalkCreateAndDeliver(
+        string token,
+        DingTalkCreateAndDeliverInput input
+    )
     {
         return await _dingTalkApi.DingTalkCreateAndDeliver(token, input);
     }
-}
+
+    [DisplayName("用于发起OA审批实例")]
+    public async Task<DingTalkWorkflowProcessInstancesOutput> DingTalkWorkflowProcessInstances(
+        string token,
+        DingTalkWorkflowProcessInstancesInput input
+    )
+    {
+        var temp = await _dingTalkApi.DingTalkWorkflowProcessInstances(token, input);
+        return temp;
+    }
+
+    [DisplayName("查询审批实例")]
+    public async Task<DingTalkGetProcessInstancesOutput> DingTalkWorkflowProcessInstances(
+        string token,
+        string input
+    )
+    {
+        var temp = await _dingTalkApi.GetProcessInstances(token, input);
+        DingTalkWokerflowLog flow = await 钉钉审批记录.GetFirstAsync(t =>
+            t.Status == "RUNNING" && t.instanceId == input
+        );
+
+        if ((flow != null) && (temp.Result.Status != flow.Status))
+        {
+            flow.Status = temp.Result.Status;
+            flow.UpdateTime = DateTime.Now;
+            flow.WorkflowId = temp.Result.BusinessId;
+            flow.Result = temp.Result.Result;
+            flow.taskId = temp.Result.Tasks.FirstOrDefault(t => t.Status == "RUNNING")?.TaskId;
+            钉钉审批记录.UpdateAsync(flow);
+        }
+        return temp;
+    }
+}

+ 30 - 0
Admin.NET/Plugins/Admin.NET.Plugin.DingTalk/Service/Dto/DingTalkDeptOutput.cs

@@ -0,0 +1,30 @@
+// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
+//
+// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
+//
+// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
+
+
+namespace Admin.NET.Plugin.DingTalk;
+public class DingTalkDeptOutput
+{
+
+    /// <summary>
+    /// 上级部门Id
+    /// </summary>
+    [JsonProperty("parent_id")]
+    [System.Text.Json.Serialization.JsonPropertyName("parent_id")]
+    public long parent_id { get; set; }
+    /// <summary>
+    /// 部门名
+    /// </summary>
+    [JsonProperty("name")]
+    [System.Text.Json.Serialization.JsonPropertyName("name")]
+    public string name { get; set; }
+    /// <summary>
+    /// 部门Id
+    /// </summary>
+    [JsonProperty("dept_id")]
+    [System.Text.Json.Serialization.JsonPropertyName("dept_id")]
+    public long dept_id { get; set; }
+}

+ 73 - 0
Admin.NET/Plugins/Admin.NET.Plugin.DingTalk/Service/Dto/DingTalkGetProcessInstancesOutput.cs

@@ -0,0 +1,73 @@
+// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
+//
+// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
+//
+// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
+
+namespace Admin.NET.Plugin.DingTalk;
+
+public class DingTalkGetProcessInstancesOutput
+{
+    public ResultData Result { get; set; }
+    public bool Success { get; set; }
+}
+
+public class OperationRecord
+{
+    public DateTime? Date { get; set; }
+    public string Result { get; set; }
+    public List<object> Images { get; set; } // 图片可能是字符串 URL 或对象
+    public string ShowName { get; set; }
+    public string Type { get; set; }
+    public string UserId { get; set; }
+}
+
+// 表格行中的子项(用于 TableField 的解析)
+public class TableRowItem
+{
+    public string BizAlias { get; set; }
+    public string Label { get; set; }
+    public string Value { get; set; }
+    public string Key { get; set; }
+    public bool Mask { get; set; }
+}
+
+// 完整的一行表格数据
+public class TableRow
+{
+    public List<TableRowItem> RowValue { get; set; }
+    public string RowNumber { get; set; }
+}
+
+public class TaskItem
+{
+    public string Result { get; set; }
+    public string ActivityId { get; set; }
+    public string PcUrl { get; set; }
+    public DateTime? CreateTime { get; set; }
+    public string MobileUrl { get; set; }
+    public string UserId { get; set; }
+    public long TaskId { get; set; }
+    public string Status { get; set; }
+}
+
+public class ResultData
+{
+    public List<string> AttachedProcessInstanceIds { get; set; }
+    public string BusinessId { get; set; }
+    public string Title { get; set; }
+    public string OriginatorDeptId { get; set; }
+    public List<OperationRecord> OperationRecords { get; set; }
+    public List<FormComponentValue> FormComponentValues { get; set; }
+
+    /// <summary>
+    /// 审批结果 agree:同意 refuse:拒绝
+    /// </summary>
+    public string Result { get; set; }
+    public string BizAction { get; set; }
+    public DateTime? CreateTime { get; set; }
+    public string OriginatorUserId { get; set; }
+    public List<TaskItem> Tasks { get; set; }
+    public string OriginatorDeptName { get; set; }
+    public string Status { get; set; }
+}

+ 105 - 0
Admin.NET/Plugins/Admin.NET.Plugin.DingTalk/Service/Dto/DingTalkWorkflowProcessInstancesInput.cs

@@ -0,0 +1,105 @@
+// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
+//
+// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
+//
+// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
+
+namespace Admin.NET.Plugin.DingTalk;
+
+public class DingTalkWorkflowProcessInstancesInput
+{
+    /// <summary>
+    /// 发起人用户ID
+    /// </summary>
+    public string OriginatorUserId { get; set; }
+
+    /// <summary>
+    /// 审批模板的流程编码
+    /// </summary>
+    public string ProcessCode { get; set; }
+
+    /// <summary>
+    /// 部门ID
+    /// </summary>
+    public long DeptId { get; set; }
+
+    /// <summary>
+    /// 微应用AgentId
+    /// </summary>
+    public long MicroappAgentId { get; set; }
+
+    /// <summary>
+    /// 审批人列表(支持多节点)
+    /// </summary>
+    public List<Approver> Approvers { get; set; }
+
+    /// <summary>
+    /// 抄送人列表
+    /// </summary>
+    public List<string> CcList { get; set; }
+
+    /// <summary>
+    /// 抄送位置:START(开始),MIDDLE(中间),END(结束)
+    /// </summary>
+    public string CcPosition { get; set; }
+
+    /// <summary>
+    /// 目标动态选择办理人(用于会签或或签等场景)
+    /// </summary>
+    public List<TargetSelectActioner> TargetSelectActioners { get; set; }
+
+    /// <summary>
+    /// 表单组件值列表
+    /// </summary>
+    public List<FormComponentValue> FormComponentValues { get; set; }
+
+    /// <summary>
+    /// 请求ID,用于幂等控制
+    /// </summary>
+    public string RequestId { get; set; }
+}
+
+/// <summary>
+/// 审批人信息
+/// </summary>
+public class Approver
+{
+    /// <summary>
+    /// 节点类型:AGREE(同意),REFUSE(拒绝)等
+    /// </summary>
+    public string ActionType { get; set; }
+
+    /// <summary>
+    /// 该节点的审批人用户ID列表
+    /// </summary>
+    public List<string> UserIds { get; set; }
+}
+
+/// <summary>
+/// 动态选择办理人
+/// </summary>
+public class TargetSelectActioner
+{
+    /// <summary>
+    /// 办理人Key,对应表单中的人员选择控件的key
+    /// </summary>
+    public string ActionerKey { get; set; }
+
+    /// <summary>
+    /// 该控件选中的用户ID列表
+    /// </summary>
+    public List<string> ActionerUserIds { get; set; }
+}
+
+/// <summary>
+/// 表单组件值
+/// </summary>
+public class FormComponentValue
+{
+    public string ComponentType { get; set; }
+    public string Name { get; set; }
+    public string BizAlias { get; set; }
+    public string Id { get; set; }
+    public string Value { get; set; }
+    public string ExtValue { get; set; }
+}

+ 27 - 0
Admin.NET/Plugins/Admin.NET.Plugin.DingTalk/Service/Dto/DingTalkWorkflowProcessInstancesOutput.cs

@@ -0,0 +1,27 @@
+// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
+//
+// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
+//
+// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
+
+namespace Admin.NET.Plugin.DingTalk;
+
+public class DingTalkWorkflowProcessInstancesOutput
+{
+    /// <summary>
+    /// 请求Id
+    /// </summary>
+    [Newtonsoft.Json.JsonProperty("request_id")]
+    [System.Text.Json.Serialization.JsonPropertyName("request_id")]
+    public string RequestId { get; set; }
+
+    public string code { get; set; }
+    public string message { get; set; }
+
+    /// <summary>
+    /// 是否还有更多数据
+    /// </summary>
+    [JsonProperty("instanceId")]
+    [System.Text.Json.Serialization.JsonPropertyName("instanceId")]
+    public string instanceId { get; set; }
+}

+ 12 - 0
Admin.NET/Plugins/Admin.NET.Plugin.DingTalk/Service/Dto/GetDingTalkDeptInput.cs

@@ -0,0 +1,12 @@
+// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
+//
+// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
+//
+// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
+
+
+namespace Admin.NET.Plugin.DingTalk;
+public class GetDingTalkDeptInput
+{
+    public long dept_id { get; set; }
+}

+ 70 - 11
Admin.NET/Plugins/Admin.NET.Plugin.DingTalk/Service/IDingTalkApi.cs

@@ -24,8 +24,13 @@ public interface IDingTalkApi : IHttpDeclarative
     /// <param name="input"></param>
     /// <returns></returns>
     [Post("https://oapi.dingtalk.com/topapi/smartwork/hrm/employee/queryonjob")]
-    Task<DingTalkBaseResponse<GetDingTalkCurrentEmployeesListOutput>> GetDingTalkCurrentEmployeesList([Query] string access_token,
-        [Body(ContentType = "application/json", UseStringContent = true), Required] GetDingTalkCurrentEmployeesListInput input);
+    Task<
+        DingTalkBaseResponse<GetDingTalkCurrentEmployeesListOutput>
+    > GetDingTalkCurrentEmployeesList(
+        [Query] string access_token,
+        [Body(ContentType = "application/json", UseStringContent = true), Required]
+            GetDingTalkCurrentEmployeesListInput input
+    );
 
     /// <summary>
     /// 获取员工花名册字段信息
@@ -34,8 +39,13 @@ public interface IDingTalkApi : IHttpDeclarative
     /// <param name="input"></param>
     /// <returns></returns>
     [Post("https://oapi.dingtalk.com/topapi/smartwork/hrm/employee/v2/list")]
-    Task<DingTalkBaseResponse<List<DingTalkEmpRosterFieldVo>>> GetDingTalkCurrentEmployeesRosterList([Query] string access_token,
-        [Body(ContentType = "application/json", UseStringContent = true), Required] GetDingTalkCurrentEmployeesRosterListInput input);
+    Task<
+        DingTalkBaseResponse<List<DingTalkEmpRosterFieldVo>>
+    > GetDingTalkCurrentEmployeesRosterList(
+        [Query] string access_token,
+        [Body(ContentType = "application/json", UseStringContent = true), Required]
+            GetDingTalkCurrentEmployeesRosterListInput input
+    );
 
     /// <summary>
     /// 发送钉钉互动卡片
@@ -51,7 +61,9 @@ public interface IDingTalkApi : IHttpDeclarative
     [Obsolete]
     Task<DingTalkSendInteractiveCardsOutput> DingTalkSendInteractiveCards(
         [Header("x-acs-dingtalk-access-token")] string token,
-        [Body(ContentType = "application/json", UseStringContent = true)] DingTalkSendInteractiveCardsInput input);
+        [Body(ContentType = "application/json", UseStringContent = true)]
+            DingTalkSendInteractiveCardsInput input
+    );
 
     /// <summary>
     /// 获取钉钉卡片消息读取状态
@@ -62,7 +74,8 @@ public interface IDingTalkApi : IHttpDeclarative
     [Get("https://api.dingtalk.com/v1.0/robot/oToMessages/readStatus")]
     Task<GetDingTalkCardMessageReadStatusOutput> GetDingTalkCardMessageReadStatus(
         [Header("x-acs-dingtalk-access-token")] string token,
-        [Query] GetDingTalkCardMessageReadStatusInput input);
+        [Query] GetDingTalkCardMessageReadStatusInput input
+    );
 
     /// <summary>
     /// 获取角色列表
@@ -71,8 +84,11 @@ public interface IDingTalkApi : IHttpDeclarative
     /// <param name="input"></param>
     /// <returns></returns>
     [Post("https://oapi.dingtalk.com/topapi/role/list")]
-    Task<DingTalkBaseResponse<DingTalkRoleListOutput>> GetDingTalkRoleList([Query] string access_token,
-        [Body(ContentType = "application/json", UseStringContent = true), Required] GetDingTalkCurrentRoleListInput input);
+    Task<DingTalkBaseResponse<DingTalkRoleListOutput>> GetDingTalkRoleList(
+        [Query] string access_token,
+        [Body(ContentType = "application/json", UseStringContent = true), Required]
+            GetDingTalkCurrentRoleListInput input
+    );
 
     /// <summary>
     /// 获取指定角色的员工列表
@@ -81,8 +97,11 @@ public interface IDingTalkApi : IHttpDeclarative
     /// <param name="input"></param>
     /// <returns></returns>
     [Post("https://oapi.dingtalk.com/topapi/role/simplelist")]
-    Task<DingTalkBaseResponse<DingTalkRoleSimplelistOutput>> GetDingTalkRoleSimplelist([Query] string access_token,
-        [Body(ContentType = "application/json", UseStringContent = true), Required] GetDingTalkCurrentRoleSimplelistInput input);
+    Task<DingTalkBaseResponse<DingTalkRoleSimplelistOutput>> GetDingTalkRoleSimplelist(
+        [Query] string access_token,
+        [Body(ContentType = "application/json", UseStringContent = true), Required]
+            GetDingTalkCurrentRoleSimplelistInput input
+    );
 
     /// <summary>
     /// 创建并投放钉钉消息卡片
@@ -93,5 +112,45 @@ public interface IDingTalkApi : IHttpDeclarative
     [Post("https://api.dingtalk.com/v1.0/card/instances/createAndDeliver")]
     Task<DingTalkCreateAndDeliverOutput> DingTalkCreateAndDeliver(
         [Header("x-acs-dingtalk-access-token")] string token,
-        [Body(ContentType = "application/json", UseStringContent = true)] DingTalkCreateAndDeliverInput input);
+        [Body(ContentType = "application/json", UseStringContent = true)]
+            DingTalkCreateAndDeliverInput input
+    );
+
+    /// <summary>
+    /// 获取部门列表列表
+    /// </summary>
+    /// <param name="access_token">调用该接口的应用凭证</param>
+    /// <param name="input"></param>
+    /// <returns></returns>
+    [Post("https://oapi.dingtalk.com/topapi/v2/department/listsub")]
+    Task<DingTalkBaseResponse<List<DingTalkDeptOutput>>> GetDingTalkDept(
+        [Query] string access_token,
+        [Body(ContentType = "application/json", UseStringContent = true), Required]
+            GetDingTalkDeptInput input
+    );
+
+    /// <summary>
+    /// 发起审批实例
+    /// </summary>
+    /// <param name="access_token">用于发起OA审批实例</param>
+    /// <param name="input"></param>
+    /// <returns></returns>
+    [Post("https://api.dingtalk.com/v1.0/workflow/processInstances")]
+    Task<DingTalkWorkflowProcessInstancesOutput> DingTalkWorkflowProcessInstances(
+        [Header("x-acs-dingtalk-access-token")] string token,
+        [Body(ContentType = "application/json", UseStringContent = true), Required]
+            DingTalkWorkflowProcessInstancesInput input
+    );
+
+    /// <summary>
+    /// 查询审批实例
+    /// </summary>
+    /// <param name="access_token"></param>
+    /// <param name="input">审批实例Id</param>
+    /// <returns></returns>
+    [Get("https://api.dingtalk.com/v1.0/workflow/processInstances")]
+    Task<DingTalkGetProcessInstancesOutput> GetProcessInstances(
+        [Header("x-acs-dingtalk-access-token")] string token,
+        [Query] string processInstanceId
+    );
 }