• 接口开发之使用C#插件Quartz.Net定时执行CMD任务工具


    概要

    • 很多时候写接口上线后还会遇到很多修改,类似JAVA,C#,delphi制作的接口上线后难以修改,测试也有困难。
    • 为了接口便于制作和修改,采用动态语言编写接口+定时任务基座的处理方法,例如:PHP写的接口内容,使用定时任务工具定时执行,这样即使接口上线后也可以随意修改PHP这种解释性脚本,方便修改和定位错误。
    • 定时任务工具+python也是很好的解决方案
    • 定时任务可以采用JAVA或者C#来构建,目前采用C#构建定时任务桌面工具

    准备

    • vs2019+C# winform
    • phpstudy2016+thinkphp3.2.3
    • quartz.net 3.7.2

    知识点

    实现原理

    • thinkphp启用cmd执行程序
    • C#利用定时任务框架quartz.net去执行CMD命令

    thinkphp配置

    • 开发阶段可以使用phpstudy环境,部署阶段采用cmd命令可以不使用网络容器
    • thinkphp开启cli模式:入库文件index.php添加一句就可以了
    if(version_compare(PHP_VERSION,'5.3.0','<'))  die('require PHP > 5.3.0 !');
    //添加这一句就可以了
    define('MODE_NAME', 'cli');
    ...其他不变
    
    • 1
    • 2
    • 3
    • 4
    • 配置后网络容器和CMD都可以执行thinkphp,CMD执行语句:
    D:\phpStudy\php\php-7.0.12-nts\php.exe  D:\phpStudy\WWW\tp3\index.php Home/Index/queryAndWrite
    
    • 1

    说明:绝对路径找到php.exe,去执行thinkphp中的方法
    另外,如果不采用自定义基座(定时任务工具),可以使用win自带的计划任务也行,但是计划任务会弹出cmd框,所以在cmd命令旁添加一个vb命令,执行这个vb命令就不会有弹窗了:

    Set ws = CreateObject("Wscript.Shell")    
    ws.run "cmd /c times.bat",vbhide
    
    • 1
    • 2

    在这里插入图片描述

    winform

    • 项目结构:
      在这里插入图片描述
      说明:
      引用:类似java的maven包
      Form1.cs:窗口1,其中Form1.Designer.cs是编译器自动生成的布局代码,和Form是分步类,等同一个类分成2个文件
      IniFunc.cs:读取ini配置文件
      Job.cs:具体任务,这里只有一个任务,但是通过不同的触发器传值形成不同任务分身
      Promgram.cs:程序入口

    • 任务类中调用Form1的控件

    1. From1定义为静态类
     		public static Form1 form1;
            public Form1()
            {
                InitializeComponent();
                form1 = this;
            }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    1. 控件设置成public
      在这里插入图片描述

    2. Job通过静态类访问From1控件

    var c = Form1.form1.textBox1.Text;
    MessageBox.Show(c);
    
    • 1
    • 2

    执行CMD命令

    //需要引入using System.Diagnostics;
    private void cmd(String t)
       {
           var p = new Process();
           p.StartInfo.FileName = "cmd.exe";
           p.StartInfo.RedirectStandardInput = true;
           p.StartInfo.UseShellExecute = false;
           p.StartInfo.CreateNoWindow = true;
           p.Start();
           p.StandardInput.WriteLine(t);
           //p.StandardInput.WriteLine("exit");
           p.StandardInput.Flush();
       }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    读取ini配置文件

    1. Debug目录下新建config.ini
    [Information]
    job1=D:\phpStudy\php\php-7.0.12-nts\php.exe  D:\phpStudy\WWW\tp3\index.php Home/Index/queryAndWrite
    job2=D:\phpStudy\php\php-7.0.12-nts\php.exe  D:\phpStudy\WWW\tp3\index.php Home/Index/queryAndWrite2
    
    • 1
    • 2
    • 3
    1. 定义文件路径,在Form1事件load中选择Form1_Load,然后按钮1点击后获取配置文件内容,job1和job2是两个定时任务,去执行thinkphp中的数据库操作
    		private string filename = null;
    
            private void Form1_Load(object sender, EventArgs e)
            {
    
                filename = Application.StartupPath + "\\config.ini";
                //MessageBox.Show(filename);
            }
    
            private void button1_Click(object sender, EventArgs e)
            {
                //this.textBox1.Text = "777";
                string job1 = IniFunc.getString("Information", "job1", null, filename);
                string job2 = IniFunc.getString("Information", "job2", null, filename);
                textBox1.Text = job1;
                textBox2.Text = job2;
                //Task(job1,job2);
            }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    定时任务Quartz.Net

    • 安装:
    1. 右键项目,点击管理NuGet
    2. 浏览中搜索quartz,点击安装
    3. 安装成功后在项目引用中会有quartz.dll
    • 构建定时任务大概分为4步:
    1. 构建scheduler(任务管理器)并开启
    2. 创建job,添加job
    3. 构建触发器
    4. scheduler中添加job

    完整代码

    Job.cs

    using Quartz;
    using System;
    using System.Diagnostics;
    using System.Threading.Tasks;
    using System.Windows.Forms;
    
    namespace WindowsFormsApp1
    {
        public class Job : IJob
        {
            //public static readonly JobKey Key = new JobKey("customer-process", "group");//这里是定义job唯一key
            public async Task Execute(IJobExecutionContext context)
            {
                var customerId = context.MergedJobDataMap.GetString("CustomerId");//获取trggier传来的值,同一个job通过trggier值不同而执行不同任务
                await Task.Run(() =>
                {
                    //Random rd = new Random();
                    try
                    {
                        //MessageBox.Show($"CustomerId={customerId}");
                        cmd(customerId);
                    }
                    catch (System.Exception e)
                    {
                        MessageBox.Show(e.Message);
                    }
                    //try
                    //{
                    //    var c = Form1.form1.textBox1.Text;//获取界面文本值
                    //    cmd(c);
                    //}
                    //catch (System.Exception e)
                    //{
                    //    MessageBox.Show(e.Message);
                    //}
                });
            }
    
    		//执行一个cmd命令
            private void cmd(String t)
            {
                var p = new Process();
                p.StartInfo.FileName = "cmd.exe";
                p.StartInfo.RedirectStandardInput = true;
                p.StartInfo.UseShellExecute = false;
                p.StartInfo.CreateNoWindow = true;//不显示窗口
                p.Start();
                p.StandardInput.WriteLine(t);
                //p.StandardInput.WriteLine("exit");//执行退出,可以不要
                p.StandardInput.Flush();
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53

    IniFunc.cs

    using System.Runtime.InteropServices;
    using System.Text;
    //https://blog.csdn.net/qq_38693757/article/details/121675847
    namespace WindowsFormsApp1
    {
        public static class IniFunc
        {
            /// 
            /// 获取值
            /// 
            /// 段落名
            /// 键名
            /// 读取异常是的缺省值
            /// 键名所对应的的值,没有找到返回空值
            /// 返回值允许的大小
            /// ini文件的完整路径
            /// 
            [DllImport("kernel32.dll")]
            private static extern int GetPrivateProfileString(
                string section,
                string key,
                string defval,
                StringBuilder retval,
                int size,
                string filepath);
    
            /// 
            /// 写入
            /// 
            /// 需要写入的段落名
            /// 需要写入的键名
            /// 写入值
            /// ini文件的完整路径
            /// 
            [DllImport("kernel32.dll")]
            private static extern int WritePrivateProfileString(
                string section,
                string key,
                string val,
                string filepath);
            /// 
            /// 获取数据
            /// 
            /// 段落名
            /// 键名
            /// 没有找到时返回的默认值
            /// ini文件完整路径
            /// 
            public static string getString(string section, string key, string def, string filename)
            {
                StringBuilder sb = new StringBuilder(1024);
                GetPrivateProfileString(section, key, def, sb, 1024, filename);
                return sb.ToString();
            }
    
            /// 
            /// 写入数据
            /// 
            /// 段落名
            /// 键名
            /// 写入值
            /// ini文件完整路径
            public static void writeString(string section, string key, string val, string filename)
            {
                WritePrivateProfileString(section, key, val, filename);
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68

    Form1.cs

    using Quartz;
    using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Data;
    using System.Drawing;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    using System.Windows.Forms;
    using System.Threading.Tasks;
    using Quartz.Impl;
    
    namespace WindowsFormsApp1
    {
        public partial class Form1 : Form
        {
        	//定义静态类,便于外部访问,类似单例
            public static Form1 form1;
            public Form1()
            {
                InitializeComponent();
                form1 = this;
            }
    
            private string filename = null;
    		//获取ini文件路径
            private void Form1_Load(object sender, EventArgs e)
            {
                filename = Application.StartupPath + "\\config.ini";
                //MessageBox.Show(filename);
            }
    		//按钮点击后,显示ini,同时执行定时任务
            private void button1_Click(object sender, EventArgs e)
            {
            	//获取ini值
                string job1 = IniFunc.getString("Information", "job1", null, filename);
                string job2 = IniFunc.getString("Information", "job2", null, filename);
                //显示在界面上
                textBox1.Text = job1;
                textBox2.Text = job2;
                //执行定时任务
                Task(job1,job2);
            }
    
    		//执行定时任务
            public async void Task(string cmd1,string cmd2) {
            	//构建scheduler管理器
                StdSchedulerFactory factory = new StdSchedulerFactory();
                IScheduler scheduler = await factory.GetScheduler();
                await scheduler.Start();//3.7.2版本官网是先执行,再加入任务,意思是可以动态添加,老博客都是后执行
    			//定义任务:WithIdentity("a")是任务的识别码Key,这个主要和trigger关联用,可以是KV,也可以是K
                IJobDetail job = JobBuilder.Create<Job>()
                    .WithIdentity("a")
                    .Build();
    			//这里的模式是一个job对于若干个trigger,所以需要先添加job,然后trigger去关联这个job
                await scheduler.AddJob(job, replace: true, storeNonDurableWhileAwaitingScheduling: true);
    			//定义trigger,并关联job,并使用JobData(JobDataMap)传值
                ITrigger trigger = TriggerBuilder.Create()
                    .WithIdentity("trigger1")
                    //.StartNow()
                    .ForJob("a")
                    .UsingJobData("CustomerId", cmd1)
                    .WithSimpleSchedule(x => x
                        .WithIntervalInSeconds(5)//5秒一次
                        .RepeatForever())
                    .Build();
    
                ITrigger trigger2 = TriggerBuilder.Create()
                    .WithIdentity("trigger2")
                    //.StartNow()
                    .ForJob("a")
                    .UsingJobData("CustomerId", cmd2)
                    .WithSimpleSchedule(x => x
                        .WithIntervalInSeconds(7)//7秒一次
                        .RepeatForever())
                    .Build();
    
    			//添加触发器,普通多任务是这样的await scheduler.ScheduleJob(job,trigger),但是这里是单任务多触发
                await scheduler.ScheduleJob(trigger);
                await scheduler.ScheduleJob(trigger2);
                MessageBox.Show("任务开始");
            }
    
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86

    config.ini

    [Information]
    job1=D:\phpStudy\php\php-7.0.12-nts\php.exe  D:\phpStudy\WWW\tp3\index.php Home/Index/queryAndWrite
    job2=D:\phpStudy\php\php-7.0.12-nts\php.exe  D:\phpStudy\WWW\tp3\index.php Home/Index/queryAndWrite2
    
    • 1
    • 2
    • 3

    简易定时任务工具雏形

    在这里插入图片描述

    官网的例子才是经典的,去看看:

    • quartz.net:https://www.quartz-scheduler.net/documentation/best-practices.html#static-job-key
    • API:https://quartznet.sourceforge.io/apidoc/3.0/html/
    • 参考:https://blog.csdn.net/qq_46104221/article/details/130578236
  • 相关阅读:
    【零代码工具】15 款企业级零代码开发平台推荐,总有一款是你心仪的
    各类软件docker安装
    1554_AURIX_TC275_时钟监控功能以及时钟紧急行为
    Only file and data URLs are supported by the default ESM loader
    Talk | ACL‘23 杰出论文,MultiIntruct:通过多模态指令集微调提升VLM的零样本学习
    【数据结构--八大排序】之归并排序
    第二部分—C语言提高篇_12. 动/精态库的封装和使用
    STL--vector(使用)
    【LeetCode】Python | 1760. 袋子里最少数目的球(二分)
    pcl基于最小切割的点云分割
  • 原文地址:https://blog.csdn.net/wayhb/article/details/134279205