深度优先

这个家伙好懒,除了文章什么都没留下

0%

整理目前所用过的数组方法,学习了新增的es6方法。

1 arr.push() 从后面添加元素,返回值为添加完后的数组的长度

1
2
3
let arr = [1,2,3,4,5]
console.log(arr.push(5)) // 6
console.log(arr) // [1,2,3,4,5,5]

2 arr.pop() 从后面删除元素,只能是一个,返回值是删除的元素

1
2
3
let arr = [1,2,3,4,5]
console.log(arr.pop()) // 5
console.log(arr) //[1,2,3,4]

3 arr.shift() 从前面删除元素,只能删除一个 返回值是删除的元素

1
2
3
let arr = [1,2,3,4,5]
console.log(arr.shift()) // 1
console.log(arr) // [2,3,4,5]

4 arr.unshift() 从前面添加元素, 返回值是添加完后的数组的长度

1
2
3
let arr = [1,2,3,4,5]
console.log(arr.unshift(2)) // 6
console.log(arr) //[2,1,2,3,4,5]

5 arr.splice(i,n) 删除从i(索引值)开始之后的那个元素。返回值是删除的元素
参数: i 索引值 n 个数

1
2
3
let arr = [1,2,3,4,5]
console.log(arr.splice(2,2)) //[3,4]
console.log(arr) // [1,2,5]

6 arr.concat() 连接两个数组 返回值为连接后的新数组

1
2
3
let arr = [1,2,3,4,5]
console.log(arr.concat([1,2])) // [1,2,3,4,5,1,2]
console.log(arr) // [1,2,3,4,5]

7 str.split() 将字符串转化为数组

1
2
let str = '123456'
console.log(str.split('')) // ["1", "2", "3", "4", "5", "6"]

8 arr.sort() 将数组进行排序,返回值是排好的数组,默认是按照最左边的数字进行排序,不是按照数字大小排序的,见例子。

1
2
3
4
5
6
let arr = [2,10,6,1,4,22,3]
console.log(arr.sort()) // [1, 10, 2, 22, 3, 4, 6]
let arr1 = arr.sort((a, b) =>a - b)
console.log(arr1) // [1, 2, 3, 4, 6, 10, 22]
let arr2 = arr.sort((a, b) =>b-a)
console.log(arr2) // [22, 10, 6, 4, 3, 2, 1]

9 arr.reverse() 将数组反转,返回值是反转后的数组

1
2
3
let arr = [1,2,3,4,5]
console.log(arr.reverse()) // [5,4,3,2,1]
console.log(arr) // [5,4,3,2,1]

10 arr.slice(start,end) 切去索引值start到索引值end的数组,不包含end索引的值,返回值是切出来的数组

1
2
3
let arr = [1,2,3,4,5]
console.log(arr.slice(1,3)) // [2,3]
console.log(arr) // [1,2,3,4,5]

11 arr.forEach(callback) 遍历数组,无return

callback的参数: value –当前索引的值

index –索引

array –原数组

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
let arr = [1,2,3,4,5]
arr.forEach( (value,index,array)=>{
console.log(`value:${value} index:${index} array:${array}`)
})
// value:1 index:0 array:1,2,3,4,5
// value:2 index:1 array:1,2,3,4,5
// value:3 index:2 array:1,2,3,4,5
// value:4 index:3 array:1,2,3,4,5
// value:5 index:4 array:1,2,3,4,5

let arr = [1,2,3,4,5]
arr.forEach( (value,index,array)=>{
value = value * 2
console.log(`value:${value} index:${index} array:${array}`)
})
console.log(arr)
// value:2 index:0 array:1,2,3,4,5
// value:4 index:1 array:1,2,3,4,5
// value:6 index:2 array:1,2,3,4,5
// value:8 index:3 array:1,2,3,4,5
// value:10 index:4 array:1,2,3,4,5
// [1, 2, 3, 4, 5]

12 arr.map(callback) 映射数组(遍历数组),有return 返回一个新数组

callback的参数: value –当前索引的值

index –索引

array –原数组

1
2
3
4
5
6
let arr = [1,2,3,4,5]
arr.map( (value,index,array)=>{
value = value * 2
console.log(`value:${value} index:${index} array:${array}`)
})
console.log(arr)
1
2
3
ps: arr.forEach()和arr.map()的区别 
    1. arr.forEach()是和for循环一样,是代替for。arr.map()是修改数组其中的数据,并返回新的数据。
    2. arr.forEach() 没有return  arr.map() 有return

13 arr.filter(callback) 过滤数组,返回一个满足要求的数组

1
2
3
let arr = [1,2,3,4,5]
let arr1 = arr.filter( (i, v) => i < 3)
console.log(arr1) // [1, 2]

14 arr.every(callback) 依据判断条件,数组的元素是否全满足,若满足则返回ture

1
2
3
4
5
let arr = [1,2,3,4,5]
let arr1 = arr.every( (i, v) => i < 3)
console.log(arr1) // false
let arr2 = arr.every( (i, v) => i < 10)
console.log(arr2) // true

15 arr.some() 依据判断条件,数组的元素是否有一个满足,若有一个满足则返回ture

1
2
3
4
5
let arr = [1,2,3,4,5]
let arr1 = arr.some( (i, v) => i < 3)
console.log(arr1) // true
let arr2 = arr.some( (i, v) => i > 10)
console.log(arr2) // false

16 arr.reduce(callback, initialValue) 迭代数组的所有项,累加器,数组中的每个值(从左到右)合并,最终计算为一个值

参数: callback: previousValue 必选 –上一次调用回调返回的值,或者是提供的初始值(initialValue)

currentValue 必选 –数组中当前被处理的数组项

index 可选 –当前数组项在数组中的索引值

array 可选 –原数组

initialValue: 可选 –初始值

实行方法:回调函数第一次执行时,preValue 和 curValue 可以是一个值,如果 initialValue 在调用 reduce() 时被提供,那么第一个 preValue 等于 initialValue ,并且curValue 等于数组中的第一个值;如果initialValue 未被提供,那么preValue 等于数组中的第一个值.

1
2
3
4
5
let arr = [0,1,2,3,4]
let arr1 = arr.reduce((preValue, curValue) =>
preValue + curValue
)
console.log(arr1) // 10

1
2
let arr2 = arr.reduce((preValue,curValue)=>preValue + curValue,5)
console.log(arr2) // 15

17 arr.reduceRight(callback, initialValue) 与arr.reduce()功能一样,不同的是,reduceRight()从数组的末尾向前将数组中的数组项做累加。

实行方法:reduceRight()首次调用回调函数callbackfn时,prevValue 和 curValue 可以是两个值之一。如果调用 reduceRight() 时提供了 initialValue 参数,则 prevValue 等于 initialValue,curValue 等于数组中的最后一个值。如果没有提供 initialValue 参数,则 prevValue 等于数组最后一个值, curValue 等于数组中倒数第二个值。

1
2
3
4
5
let arr = [0,1,2,3,4]
let arr1 = arr.reduceRight((preValue, curValue) =>
preValue + curValue
)
console.log(arr1) // 10
1
2
let arr2 = arr.reduceRight((preValue,curValue)=>preValue + curValue,5)
console.log(arr2) // 15

ps:

(如果对这两个方法不明白,可以查看大漠老师的实例 http://www.w3cplus.com/javascript/array-part-8.html)

18 arr.indexOf() 查找某个元素的索引值,若有重复的,则返回第一个查到的索引值若不存在,则返回 -1

1
2
3
4
5
let arr = [1,2,3,4,5,2]
let arr1 = arr.indexOf(2)
console.log(arr1) // 1
let arr2 = arr.indexOf(9)
console.log(arr2) // -1

19 arr.lastIndexOf() 和arr.indexOf()的功能一样,不同的是从后往前查找

1
2
3
4
5
let arr = [1,2,3,4,5,2]
let arr1 = arr.lastIndexOf(2)
console.log(arr1) // 5
let arr2 = arr.lastIndexOf(9)
console.log(arr2) // -1

20 Array.from() 将伪数组变成数组,就是只要有length的就可以转成数组。 —es6

1
2
3
4
let str = '12345'
console.log(Array.from(str)) // ["1", "2", "3", "4", "5"]
let obj = {0:'a',1:'b',length:2}
console.log(Array.from(obj)) // ["a", "b"]

21 Array.of() 将一组值转换成数组,类似于声明数组 —es6

1
2
let str = '11'
console.log(Array.of(str)) // ['11']

等价于

1
console.log(new Array('11'))  // ['11]

ps:

但是new Array()有缺点,就是参数问题引起的重载

1
2
console.log(new Array(2))   //[empty × 2]  是个空数组
console.log(Array.of(2)) // [2]

22 arr.copyWithin() 在当前数组内部,将制定位置的数组复制到其他位置,会覆盖原数组项,返回当前数组

参数: target –必选 索引从该位置开始替换数组项

start –可选 索引从该位置开始读取数组项,默认为0.如果为负值,则从右往左读。

end –可选 索引到该位置停止读取的数组项,默认是Array.length,如果是负值,表示倒数

1
2
3
4
5
6
7
let arr = [1,2,3,4,5,6,7]
let arr1 = arr.copyWithin(1)
console.log(arr1) // [1, 1, 2, 3, 4, 5, 6]
let arr2 = arr.copyWithin(1,2)
console.log(arr2) // [1, 3, 4, 5, 6, 7, 7]
let arr3 = arr.copyWithin(1,2,4)
console.log(arr3) // [1, 3, 4, 4, 5, 6, 7]

23 arr.find(callback) 找到第一个符合条件的数组成员

1
2
3
let arr = [1,2,3,4,5,2,4]
let arr1 = arr.find((value, index, array) =>value > 2)
console.log(arr1) // 3

24 arr.findIndex(callback) 找到第一个符合条件的数组成员的索引值

1
2
3
let arr = [1,2,3,4,5]
let arr1 = arr.findIndex((value, index, array) => value > 3)
console.log(arr1) // 3

25 arr.fill(target, start, end) 使用给定的值,填充一个数组,ps:填充完后会改变原数组

参数: target – 待填充的元素

start – 开始填充的位置-索引

end – 终止填充的位置-索引(不包括该位置)

1
2
3
4
5
6
7
8
let arr = [1,2,3,4,5]
let arr1 = arr.fill(5)
console.log(arr1) // [5, 5, 5, 5, 5]
console.log(arr) // [5, 5, 5, 5, 5]
let arr2 = arr.fill(5,2)
console.log(arr2)
let arr3 = arr.fill(5,1,3)
console.log(arr3)

26 arr.includes() 判断数中是否包含给定的值

1
2
3
4
5
6
7
let arr = [1,2,3,4,5]
let arr1 = arr.includes(2)
console.log(arr1) // ture
let arr2 = arr.includes(9)
console.log(arr2) // false
let arr3 = [1,2,3,NaN].includes(NaN)
console.log(arr3) // true
1
2
3
ps:与indexOf()的区别:
1 indexOf()返回的是数值,而includes()返回的是布尔值
2 indexOf() 不能判断NaN,返回为-1 ,includes()则可以判断

27 arr.keys() 遍历数组的键名

1
2
3
4
5
let arr = [1,2,3,4]
let arr2 = arr.keys()
for (let key of arr2) {
console.log(key); // 0,1,2,3
}

28 arr.values() 遍历数组键值

1
2
3
4
5
let arr = [1,2,3,4]
let arr1 = arr.values()
for (let val of arr1) {
console.log(val); // 1,2,3,4
}

29 arr.entries() 遍历数组的键名和键值

1
2
3
4
5
let arr = [1,2,3,4]
let arr1 = arr.entries()
for (let e of arr1) {
console.log(e); // [0,1] [1,2] [2,3] [3,4]
}

entries() 方法返回迭代数组。

迭代数组中每个值 前一个是索引值作为 key, 数组后一个值作为 value。

最后:
这些都是我复习整理出来的数组方法,若有不对的地方,请私信或评论,一起讨论,一起进步。
我把这些数组方法放到github里了,可以下载进行学习验证 https://github.com/sqh17/notes

支持多个文件上传,支持附带Json数据。

后台接口:

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
[HttpPost]
[Route("UploadFile")]
public async Task<IActionResult> UploadFile()
{
var files = Request.Form.Files;
var data = JsonConvert.DeserializeObject<UploadFileEntity>(Request.Form["data"]);

string snPath = $"{_hostingEnvironment.WebRootPath}//{_config["UploadPath"]}//{data.sn}";
foreach (var item in data.files)
{
var processPath = $"{snPath}//{ ConversionPath(item.processPath)}";
if (!Directory.Exists(processPath))
{
Directory.CreateDirectory(processPath);
}
var file = files.Where(p => p.FileName == item.fileName).FirstOrDefault();

if (file!=null && file.Length != 0)
{
// 只保留三份有效文件
DirectoryInfo di = new DirectoryInfo(processPath);
FileInfo[] oldFiles = di.GetFiles($"*{file.FileName}");
string newFileName = $"{(oldFiles.Length + 1)}_{file.FileName}";
if (oldFiles.Length == 3)
{
newFileName = oldFiles.OrderBy(f => f.LastWriteTime).FirstOrDefault().Name;
}
var filePath = $"{processPath}//{newFileName}";
using (var stream = new FileStream(filePath, FileMode.Create))
{
await file.CopyToAsync(stream);
}
}
}
return NoContent();
}

private string ConversionPath(string processPath)
{
return Regex.Replace(processPath.Replace("\\","_"), "/|:|:|“|”|<|>| ", "_").ToLower().TrimEnd('_');
}

参数实体:

1
2
3
4
5
6
7
8
9
10
public class UploadFileEntity
{
public string sn { get; set; }
public List<File> files { get; set; }
}
public class File
{
public string processPath { get; set; }
public string fileName { get; set; }
}

测试例子,用Ajax上传:

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
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<script src="https://code.jquery.com/jquery-2.2.4.min.js"
integrity="sha256-BbhdlvQf/xTY9gja0Dq3HiwQF8LaCRTXxZKRutelT44=" crossorigin="anonymous"></script>
</head>
<body>
<div>
<form id="uploadForm">
DC进程文件上传: <input type="file" name="file" multiple />
<input type="button" value="上传" onclick="doUpload()" />
</form>
</div>
</body>
<script>
function doUpload() {
var formData = new FormData($("#uploadForm")[0]);
const data = {
sn: "19F73025F392DDC2CAD7D436D50AB0DAAADF37932EF11BD846C4542798EEE8B0735A8D34E80AF96E866A3BC20F87871E",
files: [
{
processPath: "C:/abc/def/",
fileName: "新建文本文档 - 副本 (2).txt"
},
{
processPath: "D:/test/def",
fileName: "新建文本文档 - 副本 (3) - 副本.txt"
}
]
}
formData.append("data", JSON.stringify(data));
$.ajax({
url: 'http://192.168.1.231:8844/api/DCClient/UploadFile',
type: 'POST',
data: formData,
contentType: false,
processData: false,
success: function (returndata) {
alert(returndata);
},
error: function (returndata) {
alert(returndata);
}
});
}
</script>
</html>

控制台程序模拟上传:

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
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;

namespace demo
{
/// <summary>
/// 表单数据项
/// </summary>
public class FormItemModel
{
/// <summary>
/// 表单键,request["key"]
/// </summary>
public string Key { set; get; }
/// <summary>
/// 表单值,上传文件时忽略,request["key"].value
/// </summary>
public string Value { set; get; }
/// <summary>
/// 是否是文件
/// </summary>
public bool IsFile
{
get
{
if (FileContent == null || FileContent.Length == 0)
return false;

if (FileContent != null && FileContent.Length > 0 && string.IsNullOrWhiteSpace(FileName))
throw new Exception("上传文件时 FileName 属性值不能为空");
return true;
}
}
/// <summary>
/// 上传的文件名
/// </summary>
public string FileName { set; get; }
/// <summary>
/// 上传的文件内容
/// </summary>
public Stream FileContent { set; get; }
}
}

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
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Text;

namespace demo
{
class Program
{
static void Main(string[] args)
{
var url = "http://127.0.0.1:5000/api/DCClient/UploadFile";
var log1 = @".\新建文本文档 - 副本 (2).txt";
var log2 = @".\新建文本文档 - 副本 (3) - 副本.txt";
var formDatas = new List<FormItemModel>();
//添加文件
formDatas.Add(new FormItemModel()
{
Key = "log1",
Value = "",
FileName = "新建文本文档 - 副本 (2).txt",
FileContent = File.OpenRead(log1)
});
formDatas.Add(new FormItemModel()
{
Key = "log2",
Value = "",
FileName = "新建文本文档 - 副本 (3) - 副本.txt",
FileContent = File.OpenRead(log2)
});
formDatas.Add(new FormItemModel()
{
Key = "data",
Value = "{'sn':'1BB6D070D1B3DAEE3483604CB2DE6646D142E44B3E0927B772AC5B55E614ACA6A554B4A38DF8BC32','files':[{'processPath':'E:\\DataConnect_V1.2.3 - 测试1\\DataConnect_V1.2.3\\Model\\','fileName':'DataMap.map'}]}"
});
//提交表单
var result = PostForm(url, formDatas);
Console.ReadKey();
}

/// <summary>
/// 使用Post方法获取字符串结果
/// </summary>
/// <param name="url"></param>
/// <param name="formItems">Post表单内容</param>
/// <param name="cookieContainer"></param>
/// <param name="timeOut">默认20秒</param>
/// <param name="encoding">响应内容的编码类型(默认utf-8)</param>
/// <returns></returns>
public static string PostForm(string url, List<FormItemModel> formItems, CookieContainer cookieContainer = null, string refererUrl = null, Encoding encoding = null, int timeOut = 20000 * 3)
{
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
#region 初始化请求对象
request.Method = "POST";
request.Timeout = timeOut;
request.Accept = "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8";
request.KeepAlive = true;
request.UserAgent = "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/31.0.1650.57 Safari/537.36";
if (!string.IsNullOrEmpty(refererUrl))
request.Referer = refererUrl;
if (cookieContainer != null)
request.CookieContainer = cookieContainer;
#endregion

string boundary = "----" + DateTime.Now.Ticks.ToString("x");//分隔符
request.ContentType = string.Format("multipart/form-data; boundary={0}", boundary);
//请求流
var postStream = new MemoryStream();
#region 处理Form表单请求内容
//是否用Form上传文件
var formUploadFile = formItems != null && formItems.Count > 0;
if (formUploadFile)
{
//文件数据模板
string fileFormdataTemplate =
"\r\n--" + boundary +
"\r\nContent-Disposition: form-data; name=\"{0}\"; filename=\"{1}\"" +
"\r\nContent-Type: application/octet-stream" +
"\r\n\r\n";
//文本数据模板
string dataFormdataTemplate =
"\r\n--" + boundary +
"\r\nContent-Disposition: form-data; name=\"{0}\"" +
"\r\n\r\n{1}";
foreach (var item in formItems)
{
string formdata = null;
if (item.IsFile)
{
//上传文件
formdata = string.Format(
fileFormdataTemplate,
item.Key, //表单键
item.FileName);
}
else
{
//上传文本
formdata = string.Format(
dataFormdataTemplate,
item.Key,
item.Value);
}

//统一处理
byte[] formdataBytes = null;
//第一行不需要换行
if (postStream.Length == 0)
formdataBytes = Encoding.UTF8.GetBytes(formdata.Substring(2, formdata.Length - 2));
else
formdataBytes = Encoding.UTF8.GetBytes(formdata);
postStream.Write(formdataBytes, 0, formdataBytes.Length);

//写入文件内容
if (item.FileContent != null && item.FileContent.Length > 0)
{
using (var stream = item.FileContent)
{
byte[] buffer = new byte[1024];
int bytesRead = 0;
while ((bytesRead = stream.Read(buffer, 0, buffer.Length)) != 0)
{
postStream.Write(buffer, 0, bytesRead);
}
}
}
}
//结尾
var footer = Encoding.UTF8.GetBytes("\r\n--" + boundary + "--\r\n");
postStream.Write(footer, 0, footer.Length);
}
else
{
request.ContentType = "application/x-www-form-urlencoded";
}
#endregion

request.ContentLength = postStream.Length;

#region 输入二进制流
if (postStream != null)
{
postStream.Position = 0;
//直接写入流
Stream requestStream = request.GetRequestStream();

byte[] buffer = new byte[1024];
int bytesRead = 0;
while ((bytesRead = postStream.Read(buffer, 0, buffer.Length)) != 0)
{
requestStream.Write(buffer, 0, bytesRead);
}

////debug
//postStream.Seek(0, SeekOrigin.Begin);
//StreamReader sr = new StreamReader(postStream);
//var postStr = sr.ReadToEnd();
postStream.Close();//关闭文件访问
}
#endregion

HttpWebResponse response = (HttpWebResponse)request.GetResponse();
if (cookieContainer != null)
{
response.Cookies = cookieContainer.GetCookies(response.ResponseUri);
}

using (Stream responseStream = response.GetResponseStream())
{
using (StreamReader myStreamReader = new StreamReader(responseStream, encoding ?? Encoding.UTF8))
{
string retString = myStreamReader.ReadToEnd();
return retString;
}
}
}
}
}

一、经常在项目会用到定时任务同步数据或更新缓存等操作,在很久以前我们可能经常会用一个多线程或timer来做定时任务,这样能实现比较简单轻量级的任务;对于任务多且都调用频率不一样的任务,我们都会用到Quartz.Net这个组件;

Quartz.NET是一个强大、开源、轻量的作业调度框架,你能够用它来为执行一个作业而创建简单的或复杂的作业调度。它有很多特征,如:数据库支持,集群,插件,支持cron-like表达式等等

二、 接下来简单演示一下Quartz使用:

2.1 首先新建一个AspNet Core API 项目,通过nuget包管理器安装引用Quartz

2.2 新建一个模拟任务类UserInfoSyncjob 必须继承IJob接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
namespace QuartzDemo.Quarzs
{
public class UserInfoSyncjob : IJob
{

public Task Execute(IJobExecutionContext context)
{
return Task.Run(() =>
{
//.....
Console.WriteLine($"{DateTime.Now.ToString()}:开始执行同步第三方数据");
//....同步操作

});
}
}
}

2.2 声明一个启动类QuartzStartup,来控制任务启动关闭等方法

添加启动方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public async Task<string> Start()                                           
{
//1、声明一个调度工厂
_schedulerFactory = new StdSchedulerFactory();
//2、通过调度工厂获得调度器
_scheduler = await _schedulerFactory.GetScheduler();
//3、开启调度器
await _scheduler.Start();
//4、创建一个触发器
var trigger = TriggerBuilder.Create()
.WithSimpleSchedule(x => x.WithIntervalInSeconds(2).RepeatForever())//每两秒执行一次
.Build();
//5、创建任务
var jobDetail = JobBuilder.Create<UserInfoSyncjob>()
.WithIdentity("job", "group")
.Build();
//6、将触发器和任务器绑定到调度器中
await _scheduler.ScheduleJob(jobDetail, trigger);
return await Task.FromResult("将触发器和任务器绑定到调度器中完成");
}

2.3 在网站启动完成时调用QuartzStartup的Start方法开启任务

先注入 Quartz调度类

1
2
3
4
5
6
7
8
#region 注入 Quartz调度类
services.AddSingleton<QuartzStartup>();
services.AddTransient<EarlyWarnQuartzJob>();
//注册ISchedulerFactory的实例。
services.AddSingleton<ISchedulerFactory, StdSchedulerFactory>();

services.AddSingleton<IJobFactory, IOCJobFactory>();
#endregion

添加网站启动开始方法

1
2
3
4
5
6
7
8
9
10
11
//获取前面注入的Quartz调度类
var quartz = app.ApplicationServices.GetRequiredService<QuartzStartup>();
appLifetime.ApplicationStarted.Register(() =>
{
quartz.Start().Wait();
});

appLifetime.ApplicationStopped.Register(() =>
{
quartz.Stop();
});

2.4、运行效果,运行之前将控制台开启(方便查看任务是否在执行,实际环境可写日志)

该调度任务完成,上方定义的触发器是2秒一次,所以该任务每隔2秒执行;(也可以通过配置文件,控制执行平率,cron表达式可以很好控制)

三、第二结简单演示了Quartz的基本用法,本文重点不是主要讲解Quartz的用法,上方只是为了没使用过Quartz的同行有个简单映像,如果想详细学习,博客园有很多类似的文章,也可以和我探讨一下!

本文重点是每个任务类怎么通过注入获取其他类的使用及参数配置类等等;

假如有这样一个需求,UserInfoSyncjob同步任务里面需要配置数据库连接参数和日志记录、缓存记录等,在之前我们可能通过配置类、日志类、缓存类以工厂形式单例创建获取。

在AspNet Core自带IOC容器框架,很多配置类、日志类、缓存类等等,在全局很多地方都会使用,我们现在做法就是把这些类注入到IOC容器中,如果需要的只需要从构造方法中获取;

我们都知道如果一个从构造方法中获取IOC容器里面的类型实例,必须该类型也要主要到IOC容器中,这样我们就要想办法把UserInfoSyncjob通过容器来创建生产;

通过源码发现在Quartz有一个默认的生成job的工厂类Quartz.Simpl.SimpleJobFactory

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
using System;
using Quartz.Logging;
using Quartz.Spi;
using Quartz.Util;
namespace Quartz.Simpl
{
/// <summary>
/// The default JobFactory used by Quartz - simply calls
/// <see cref="ObjectUtils.InstantiateType{T}" /> on the job class.
/// </summary>
/// <seealso cref="IJobFactory" />
/// <seealso cref="PropertySettingJobFactory" />
/// <author>James House</author>
/// <author>Marko Lahma (.NET)</author>
public class SimpleJobFactory : IJobFactory
{
private static readonly ILog log = LogProvider.GetLogger(typeof (SimpleJobFactory));

/// <summary>
/// Called by the scheduler at the time of the trigger firing, in order to
/// produce a <see cref="IJob" /> instance on which to call Execute.
/// </summary>
/// <remarks>
/// It should be extremely rare for this method to throw an exception -
/// basically only the case where there is no way at all to instantiate
/// and prepare the Job for execution. When the exception is thrown, the
/// Scheduler will move all triggers associated with the Job into the
/// <see cref="TriggerState.Error" /> state, which will require human
/// intervention (e.g. an application restart after fixing whatever
/// configuration problem led to the issue with instantiating the Job).
/// </remarks>
/// <param name="bundle">The TriggerFiredBundle from which the <see cref="IJobDetail" />
/// and other info relating to the trigger firing can be obtained.</param>
/// <param name="scheduler"></param>
/// <returns>the newly instantiated Job</returns>
/// <throws> SchedulerException if there is a problem instantiating the Job. </throws>
public virtual IJob NewJob(TriggerFiredBundle bundle, IScheduler scheduler)
{
IJobDetail jobDetail = bundle.JobDetail;
Type jobType = jobDetail.JobType;
try
{
if (log.IsDebugEnabled())
{
log.Debug($"Producing instance of Job '{jobDetail.Key}', class={jobType.FullName}");
}

return ObjectUtils.InstantiateType<IJob>(jobType);
}
catch (Exception e)
{
SchedulerException se = new SchedulerException($"Problem instantiating class '{jobDetail.JobType.FullName}'", e);
throw se;
}
}

/// <summary>
/// Allows the job factory to destroy/cleanup the job if needed.
/// No-op when using SimpleJobFactory.
/// </summary>
public virtual void ReturnJob(IJob job)
{
var disposable = job as IDisposable;
disposable?.Dispose();
}
}
}

SimpleJobFactory 实现了IJobFactory接口,通过源码发现我们如果要替换该工厂来控制job的生成,只需要创建一个IOCJobFactory来替换默认job工厂就行__

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class IOCJobFactory : IJobFactory
{
private readonly IServiceProvider _serviceProvider;
public IOCJobFactory(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}
public IJob NewJob(TriggerFiredBundle bundle, IScheduler scheduler)
{
return _serviceProvider.GetService(bundle.JobDetail.JobType) as IJob;

}

public void ReturnJob(IJob job)
{
var disposable = job as IDisposable;
disposable?.Dispose();

}
}

在调度任务类里面重新设置job工厂 _scheduler.JobFactory = _iocJobfactory;

在IOC中注入 UserInfoSyncjob、StdSchedulerFactory、IOCJobFactory_ _

1
2
3
4
services.AddTransient<UserInfoSyncjob>();      // 这里使用瞬时依赖注入
services.AddSingleton<ISchedulerFactory, StdSchedulerFactory>();//注册ISchedulerFactory的实例。
services.AddSingleton<QuartzStartup>();
services.AddSingleton<IJobFactory,IOCJobFactory>();

修改UserInfoSyncjob任务类,可以通过构造方法注入的方式从容器中拿到日志实现类、缓存类等等

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class UserInfoSyncjob : IJob
{
private readonly ILogger<UserInfoSyncjob> _logger;
// private readonly ICache _cache;
public UserInfoSyncjob(ILogger<UserInfoSyncjob> logger)
{
//_cache = cache;
_logger = logger;// EnginContext.Current.Resolve<ILogger<UserInfoSyncjob>>();
}
public Task Execute(IJobExecutionContext context)
{
return Task.Run(() =>
{
//.....
// Console.WriteLine($"{DateTime.Now.ToString()}:开始执行同步第三方数据");
_logger.LogInformation ($"{DateTime.Now.ToString()}:开始执行同步第三方数据");
//....同步操作
// 我们都知道如果一个从构造方法中获取IOC容器里面的类型,必须该类型也要主要到IOC容器中;

});
}
}

调整后运行截图

具体详细步骤请看源码:https://github.com/lxshwyan/QuartzDemo.git