C#制作定时任务工具执行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添加一句就可以了
php
if(version_compare(PHP_VERSION,'5.3.0','<')) die('require PHP > 5.3.0 !');
//添加这一句就可以了
define('MODE_NAME', 'cli');
...其他不变
- 配置后网络容器和CMD都可以执行thinkphp,CMD执行语句:
cmd
D:\phpStudy\php\php-7.0.12-nts\php.exe D:\phpStudy\WWW\tp3\index.php Home/Index/queryAndWrite
说明:绝对路径找到php.exe,去执行thinkphp中的方法
另外,如果不采用自定义基座(定时任务工具),可以使用win自带的计划任务也行,但是计划任务会弹出cmd框,所以在cmd命令旁添加一个vb命令,执行这个vb命令就不会有弹窗了:
cmd
Set ws = CreateObject("Wscript.Shell")
ws.run "cmd /c times.bat",vbhide
winform
-
项目结构:
说明:
引用:类似java的maven包
Form1.cs:窗口1,其中Form1.Designer.cs是编译器自动生成的布局代码,和Form是分步类,等同一个类分成2个文件
IniFunc.cs:读取ini配置文件
Job.cs:具体任务,这里只有一个任务,但是通过不同的触发器传值形成不同任务分身
Promgram.cs:程序入口
-
任务类中调用Form1的控件
- From1定义为静态类
java
public static Form1 form1;
public Form1()
{
InitializeComponent();
form1 = this;
}
-
控件设置成public
-
Job通过静态类访问From1控件
java
var c = Form1.form1.textBox1.Text;
MessageBox.Show(c);
执行CMD命令
java
//需要引入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();
}
读取ini配置文件
- Debug目录下新建config.ini
js
[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
- 定义文件路径,在Form1事件load中选择Form1_Load,然后按钮1点击后获取配置文件内容,job1和job2是两个定时任务,去执行thinkphp中的数据库操作
js
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);
}
定时任务Quartz.Net
- 安装:
- 右键项目,点击管理NuGet
- 浏览中搜索quartz,点击安装
- 安装成功后在项目引用中会有quartz.dll
- 构建定时任务大概分为4步:
- 构建scheduler(任务管理器)并开启
- 创建job,添加job
- 构建触发器
- scheduler中添加job
完整代码
Job.cs
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();
}
}
}
IniFunc.cs
cs
using System.Runtime.InteropServices;
using System.Text;
//https://blog.csdn.net/qq_38693757/article/details/121675847
namespace WindowsFormsApp1
{
public static class IniFunc
{
/// <summary>
/// 获取值
/// </summary>
/// <param name="section">段落名</param>
/// <param name="key">键名</param>
/// <param name="defval">读取异常是的缺省值</param>
/// <param name="retval">键名所对应的的值,没有找到返回空值</param>
/// <param name="size">返回值允许的大小</param>
/// <param name="filepath">ini文件的完整路径</param>
/// <returns></returns>
[DllImport("kernel32.dll")]
private static extern int GetPrivateProfileString(
string section,
string key,
string defval,
StringBuilder retval,
int size,
string filepath);
/// <summary>
/// 写入
/// </summary>
/// <param name="section">需要写入的段落名</param>
/// <param name="key">需要写入的键名</param>
/// <param name="val">写入值</param>
/// <param name="filepath">ini文件的完整路径</param>
/// <returns></returns>
[DllImport("kernel32.dll")]
private static extern int WritePrivateProfileString(
string section,
string key,
string val,
string filepath);
/// <summary>
/// 获取数据
/// </summary>
/// <param name="section">段落名</param>
/// <param name="key">键名</param>
/// <param name="def">没有找到时返回的默认值</param>
/// <param name="filename">ini文件完整路径</param>
/// <returns></returns>
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();
}
/// <summary>
/// 写入数据
/// </summary>
/// <param name="section">段落名</param>
/// <param name="key">键名</param>
/// <param name="val">写入值</param>
/// <param name="filename">ini文件完整路径</param>
public static void writeString(string section, string key, string val, string filename)
{
WritePrivateProfileString(section, key, val, filename);
}
}
}
Form1.cs
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("任务开始");
}
}
}
config.ini
cs
[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
简易定时任务工具雏形
官网的例子才是经典的,去看看: