タイマーQuartz1の2--フレームタイミング

1.クォーツいくつかの共通API

スケジューラメインプログラムインターフェイスを備えた1.1 Scheduler--相互作用

        (実行計画のscheduler.schedulerjob方法により配置)スケジューラスケジューラタスク実行スケジュール、およびのみ実行スケジュールされたタスクのジョブのスケジュール、それは時間(タスクトリガートリガー)に予め定義された実行時間は、タスクが実行されます。

Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();

1.2求人

1.2.1。

        私たちは、あなたがカスタマイズすることができ、プログラムの種類によって行うことができるタイムスケジュールタスク将来、事前に定義されていることを願っています。

        ジョブは、タスクインターフェースの作業を予定しており、その提供JDKクラスに似たインタフェースで定義されたexecuteメソッド、RUN TimeTaskメソッドをインタフェースクラスを実装するために必要な、書き込みビジネスロジックタスクはそれで行いました。

例えば:

public class HelloJob implements Job {

	@Override
	public void execute(JobExecutionContext arg0) throws JobExecutionException {
		Calendar calendar = Calendar.getInstance();
		Date time = calendar.getTime();
		String format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(time);
		System.out.println("任务开始执行,时间是:" + format);
	}

}

クォーツのライフサイクルにおける1.2.2ジョブ・インスタンス

        呼び出しが完了すると、関連するジョブオブジェクトインスタンスが解放され、新たなジョブ・インスタンスでexecuteメソッドを呼び出して作成された各ジョブの実行時間スケジューラ、前に、リリースのインスタンスは、ガベージコレクションのメカニズムをリサイクルされます。

例えば:私たちは、ライフサイクルを検証するために、コンストラクタで仕事中の単語を印刷することができます

package cn.bjc.quartz.job;

import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;

import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;

public class HelloJob implements Job {
	
	public HelloJob() {
		super();
		System.out.println("hello Job");
	}

	@Override
	public void execute(JobExecutionContext arg0) throws JobExecutionException {
		Calendar calendar = Calendar.getInstance();
		Date time = calendar.getTime();
		String format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(time);
		System.out.println("任务开始执行,时间是:" + format);
	}

}

図に示す実行結果:

私たちは、各実行コンストラクタはそれがexecuteメソッドを呼び出す前に、ジョブの新しいインスタンスを作成し、示唆、言葉が印刷されますことを発見しました

ジョブ1.2.3ジョブステートフルとステートレス

ステートレスジョブ:ジョブのデフォルトの状態では、新しいJobDataMapを作成する各コールは、このスタックStruts2の値に似ています。

例えば:私たちは、デフォルト値がカウンタとして、0である、jobDetail数のパラメータを渡し、その後、ジョブ内の各実行後にカウント値を印刷します

HelloJob.java


package cn.bjc.quartz.job;

import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;

import org.quartz.Job;
import org.quartz.JobDataMap;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.quartz.TriggerKey;

public class HelloJob implements Job {
	
	// 定义计数器
	private Integer count;
	public void setCount(Integer count) {
		this.count = count;
	}


	@Override
	public void execute(JobExecutionContext context) throws JobExecutionException {
		
		System.out.println("count = " + (++count)); // 计数器+1,并且打印出来
		
		// 将计数器放回到JobDetail的JobDataMap中
		context.getJobDetail().getJobDataMap().put("count", count);
		
		Calendar calendar = Calendar.getInstance();
		Date time = calendar.getTime();
		String format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(time);
		System.out.println("任务开始执行,时间是:" + format);
	}

}

HelloSchedulerDemo.java

package cn.bjc.quartz.main;

import org.quartz.JobBuilder;
import org.quartz.JobDataMap;
import org.quartz.JobDetail;
import org.quartz.Scheduler;
import org.quartz.SimpleScheduleBuilder;
import org.quartz.SimpleTrigger;
import org.quartz.Trigger;
import org.quartz.TriggerBuilder;
import org.quartz.impl.StdSchedulerFactory;

import cn.bjc.quartz.job.HelloJob;

public class HelloSchedulerDemo {

	public static void main(String[] args) throws Exception {
		// 1. 调度器(Scheduler),从工厂获取
		Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler(); // 内部默认是new StdSchedulerFactory()
		// 2. 任务实例(JobDetail),通过JobBuilder创建
		JobDetail jobDetail = JobBuilder.newJob(HelloJob.class)	// 加载任务类,与HelloJob绑定
							.withIdentity("job1", "group1")	// 参数一:任务的名称(唯一实例);参数二:任务组的名称,对任务进行分组
							.usingJobData("msg", "日志打印")  // 传递参数到job
							.usingJobData("count", 0)		// 传入计数器,默认值为0
							.build();
		// 3. 触发器(Trigger)
		Trigger trigger = TriggerBuilder.newTrigger()
							.withIdentity("trigger1", "group1") // 参数一:触发器的名称;参数二:触发器组的名称,对触发器进行分组 
							.startNow()	// 启动时间,这里设置马上启动
							.withSchedule(SimpleScheduleBuilder.simpleSchedule().repeatSecondlyForever(5))// 每5秒重复执行
							.usingJobData("msg", "simpleTrigger")  // 传递参数到job
							.build();
		// 最后,让调度器关联任务和触发器,保证按照触发器定义的条件去执行任务。
		scheduler.scheduleJob(jobDetail, trigger);
		// 启动
		scheduler.start();
		// 关闭
		// scheduler.shutdown();
	}
}

結果:

3の結果が実行されたことを発見、カウント値は1です。これは、デフォルトでステートレス仕事であり、各呼び出しは新しいJobDataMapを作成し、説明するだろう。

ジョブの状態があります:複数のジョブ所有者は、いくつかの状態情報を呼び出すことができます中として理解することができ、ステータス情報がJobDataMapに格納されます。

ステートフルな仕事を実装するには、あなただけでは、ジョブクラスにメモを追加する必要があります@PersistJobDataAfterExecution

例えば:

package cn.bjc.quartz.job;

import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;

import org.quartz.Job;
import org.quartz.JobDataMap;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.quartz.PersistJobDataAfterExecution;
import org.quartz.TriggerKey;

@PersistJobDataAfterExecution
public class HelloJob implements Job {
	
	// 定义计数器
	private Integer count;
	public void setCount(Integer count) {
		this.count = count;
	}


	@Override
	public void execute(JobExecutionContext context) throws JobExecutionException {
		
		System.out.println("count = " + (++count)); // 计数器+1,并且打印出来
		
		// 将计数器放回到JobDetail的JobDataMap中
		context.getJobDetail().getJobDataMap().put("count", count);
		
		Calendar calendar = Calendar.getInstance();
		Date time = calendar.getTime();
		String format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(time);
		System.out.println("任务开始执行,时间是:" + format);
	}

}

 結果:

明らかに、実行カウンタとして累積スケジューラ。 

1.3 JobBuilder

        タスクインスタンスを宣言するために使用されている、あなたはまた、このようなタスク名、グループ名など、タスクの詳細を、定義することができます。この宣言の例には、実行するための実践的な課題となります。

1.4 JobDetail--タスクインスタンス

        JobDetailインスタンスはJobDetail JobBuilderクラスによって作成され、タスクインスタンスのタイミングを定義するために使用されます。

         JobDetailは、プロパティ設定の数を提供し、ジョブ属性のメンバ変数JobDataMapインスタンスは、ジョブ状態情報の具体例を格納するために使用され、スケジューラは、ジョブJobDetailインスタンスによってオブジェクトを追加する必要があります。

例えば:

JobDetail jobDetail = JobBuilder.newJob(HelloJob.class)	// 加载任务类,与HelloJob绑定
							.withIdentity("job1", "group1")	// 参数一:任务的名称(唯一实例);参数二:任务组的名称,对任务进行分组
							.build();

注:指定されたグループ名が存在しない場合は、デフォルトのグループ名は「DEFAULT」であります 

JobDetail重要な属性:名前、グループ、JOBCLASS、jobDataMap

例えば:

System.out.println("任务名称:" + jobDetail.getKey().getName());
System.out.println("任务所属的组:" + jobDetail.getKey().getGroup());
System.out.println("任务类:" + jobDetail.getJobClass().getName());

jobDetailはまた、方法usingJobDataにより、ジョブ・インスタンスにパラメータを搬送することができます

例えば:

JobDetail jobDetail = JobBuilder.newJob(HelloJob.class)	// 加载任务类,与HelloJob绑定
							.withIdentity("job1", "group1")	// 参数一:任务的名称(唯一实例);参数二:任务组的名称,对任务进行分组
							.usingJobData("msg", "日志打印")  // 传递参数到job
							.build();

 

1.5 JobExecutionContext

        スケジューラがジョブを呼び出すと、それはクォーツのランタイム環境と仕事の詳細データへJobExecutionContextオブジェクトへのアクセス自体を通じて仕事JobExecutionContextにexecute()メソッドは、ジョブを渡されます。

1.5.1 JobDetailのコンテンツを取得

public void execute(JobExecutionContext context) throws JobExecutionException {
		
	// 获取JobDetail的内容
	JobKey key = context.getJobDetail().getKey();
	System.out.println("工作任务名称:" + key.getName());		//job1
	System.out.println("工作任务的组:" + key.getGroup());		// group1
	System.out.println("任务类:" + context.getJobDetail().getJobClass().getName());  // cn.bjc.quartz.job.HelloJob
    // 获取JobDataMap
	JobDataMap jobDataMap = context.getJobDetail().getJobDataMap();
	System.out.println(jobDataMap.getString("msg"));
}

コンテンツの1.5.2トリガ取得

JobDataMap jobDataMap2 = context.getTrigger().getJobDataMap();
System.out.println(jobDataMap2.getString("msg"));
TriggerKey key = context.getTrigger().getKey();
System.out.println(key.getName());
System.out.println(key.getGroup());

追加コンテンツのための1.5.3

// 任务本次运行时间
System.out.println(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(context.getFireTime()));  

// 任务下一次运行时间
System.out.println(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(context.getNextFireTime()));  

1.6 JobDataMap

        JobDataMapは(シリアライズ)オブジェクトを含むことができるデータを使用することができるジョブの実行例の時に、データの限られた量ではありません。

1.6.1地図取得

1.は、スケジューリングの際、JobDataMap JobExecutionContextに保存されている、それは非常に簡単に取得することです。

ジョブオブジェクト、それに渡されたオブジェクトパラメータのインスタンスを実行したとき2. JobDataMapは、配列の任意のデータ・オブジェクトをロードするために使用することができます。

3. JobDataMap実装java.util.Mapインターフェースと基本データ型のアクセスに非常に便利な方法が追加されます

たとえば、次のようにJobDataMapに格納されますMSGトリガデータ

JobDataMap jobDataMap2 = context.getTrigger().getJobDataMap();
System.out.println(jobDataMap2.getString("msg"));

注:そのデータを取得するには、usingJobDataによって実行されたときにパラメータを渡す必要があります 

例えば:

Trigger trigger = TriggerBuilder.newTrigger()
			.withIdentity("trigger1", "group1") // 参数一:触发器的名称;参数二:触发器组的名称,对触发器进行分组 
			.startNow()	// 启动时间,这里设置马上启动
		.withSchedule(SimpleScheduleBuilder.simpleSchedule().repeatSecondlyForever(5))// 每5秒重复执行
			.usingJobData("msg", "simpleTrigger")  // 传递参数到job
			.build();

1.6.2 setterメソッド

        JobDataMapを追加するためのキーセッターメソッドに対応するジョブの実装クラスは、水晶フレームJobFactoryデフォルト実装クラスのインスタンスジョブオブジェクトを初期化する自動設定メソッドを呼び出します。

例えば:

package cn.bjc.quartz.job;

import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;

import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;

public class HelloJob implements Job {
	
	private String msg;
	public void setMsg(String msg) {
		this.msg = msg;
	}

	@Override
	public void execute(JobExecutionContext context) throws JobExecutionException {
		
		System.out.println(msg);
		
		Calendar calendar = Calendar.getInstance();
		Date time = calendar.getTime();
		String format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(time);
		System.out.println("任务开始执行,时间是:" + format);
	}

}

注:setterメソッドを使用するときは、同じ名前のキーが発生した場合、トリガは.usingJobDataに.usingJobData JobDetailで説明します

1.7 TriggerBuilder

        たとえば、トリガートリガーを作成します

関連API

1. newTrigger():TriggerBuilderのインスタンスを作成します。

TriggerBuilder triggerBuilder = TriggerBuilder.newTrigger();

2. withIdentity:例TriggerBuilderステータスシンボルを設定します

triggerBuilder .withIdentity("trigger1", "group1"); 

3. startNow():すぐに実行

triggerBuilder.startNow()

3. startAt(日のstartTime):タスクの開始時間を設定します。

triggerBuilder.startAt(new Date())

終了時間を設定したタスク:4 ENDAT(日付endTimeは)

triggerBuilder.endAt(new Date())

5. withSchedule(ScheduleBuilder scheduleBuilder):設定タスクのトリガ条件

// 设置任务每5秒执行一次
triggerBuilder.withSchedule(SimpleScheduleBuilder.simpleSchedule().repeatSecondlyForever(5));

6.ビルド():トリガーを作成

例えば:

Trigger trigger = TriggerBuilder.newTrigger()
			.withIdentity("trigger1", "group1") // 参数一:触发器的名称;参数二:触发器组的名称,对触发器进行分组 
			.startNow()	// 启动时间,马上启动
		.withSchedule(SimpleScheduleBuilder.simpleSchedule().repeatSecondlyForever(5))// 每5秒重复执行
			.build();

1.8 Trigger--トリガー

        ジョブのスケジュール時にトリガオブジェクトは、ジョブの実行の条件を満たすようにそのプロパティを調整し、我々インスタンストリガー、ジョブの実行をトリガするために使用されます。タスクが実行されているもの時点で、タスクの定義は時間の条件はいつでも実行されますスケジュールされたことを示します。たとえば:すべての2S一度実行

Trigger trigger = newTrigger()
      .withIdentity("trigger1", "group1")
      .startNow()
            .withSchedule(simpleSchedule()
              .withIntervalInSeconds(40)
              .repeatForever())            
      .build();

1.8.1トリガはじめに 

石英しかし、TriggerBuilderによって作成された最も使用SimpleTriggerとCronTrigger、例であり、トリガーの異なる種類の数を有しています

関連概念1.8.2トリガーの下で

1. JobKey:ジョブ・インスタンスを示すフラグは、トリガがトリガされたときに、指定されたジョブのインスタンスが実行されます。

// 获取trigger的jobkey
JobKey jobKey = context.getTrigger().getJobKey();
System.out.println("trigger的名称:" + jobKey.getName());
System.out.println("trigger的组:" + jobKey.getGroup());

2.のstartTime:スケジュールフリップフロップは、最初の時間がトリガされるようになった場合、そのデータ型がjava.util.Dateであることを示します

3. endTimeは:指定されたトリガは処罰終了時間は、そのデータ型がjava.util.Dateであります

Trigger trigger = context.getTrigger();
Date startTime = trigger.getStartTime();
Date endTime = trigger.getEndTime();

1.8.3 SimpleTriggerトリガー

        SimpleTriggerを設定し、最も簡単な種類QuartzTriggerを使用します。特定の日付にしてN回の仕事を設計し、繰り返し実行することができる時間間隔で開始時間/ことが必要とされます。

API

1. SimpleScheduleBuilder.repeatSecondlyForever(5):実施し、5秒ごと

2. SimpleScheduleBuilder.withRepeatCount(2):リピート3回

例:5秒ごとに一回、タスクの実行、三回繰り返します

SimpleScheduleBuilder.repeatSecondlyForever(5).withRepeatCount(2)

1.8.4 CronTriggerトリガ - カレンダーベースのジョブスケジューラ

        それは、カレンダーベースのジョブスケジューラですのでSimpleTriggerとして、CronTrigger通常より有用SimpleTrigger超えるごとに指定された時間間隔トリガを、したいのではなく、そのカレンダーとしてスケジュールでタスクをトリガする必要がある場合。

        使用CronTriggerは5分として、毎週金曜日の午前10時00分に9:00の間、あなたは「正午に毎週金曜日」を指定することができますまたは「すべての9:30平日」または「月曜日、水曜日「このスケジュールは、トリガを配置します。

        でもSimpleTrigger同じように、CronTriggerも開始するスケジュールを指定したstartTimeを持って、もはやスケジュールを指定継続する(オプション)endTimeはあります。

1. cronの式

        クーロン発現が構成CronTrigger例に、クーロン式七部分式の文字列であり、各部分式は、単一のスケジュールの詳細について説明に使用され、これらの部分式は、それぞれ、スペースで区切ら

1.秒秒

2.ミニッツ分

3.時間とき

4月のデイ・オブ・月日

5.月の月

6週間の曜日の日

7.年(オプションフィールド)年(オプションのフィールド)

次の表のパラメータ値:

フィールド 必須 許容値 特殊文字を実行します
第2 それはあります 0-59 、 - * /
分割 それはあります 0-59 、 - * /
時間 それはあります 0-23 、 - * /
それはあります 1-31 、 - * /?L W C
それはあります 1-12 JAN-DECまたは 、 - * /
それはあります 1-7またはSUN-SAT 、 - * /?LのC#
ノー 塗りつぶし、または1970-2099ないでください。 、 - * /

単一の式の範囲またはリストを含んでもよいです。

例:曜日でのように表すことができます。

月曜日〜金曜日: "MON-FRI"

"MON、WED、FRI":月曜日、水曜日、金曜日

水曜日と土曜日に月曜日: "MON-WED、SAT"

特殊文字を使用します

シンボル 意味
* ドメインのそれぞれの可能な値を表します。例えば:月*は毎日週の曜日ザ・*には、毎月のドメインを表し、

彼らは、この分野の現在の設定値を気にしないために使用されるシーンを値を指定していないと述べました。週の月と日付の日には、これら2つの要素が相互に排他的であるため、

したがって、あなたがフィールドを設定しないことを示すために疑問符(?)によって設定されるべきです

- 例えば、間隔を表す:時間にセット10-12、10、11は、ポイントトリガーを表します
これは、複数の値を指定表します。例:週のフィールドに設定されている「MON、WED、FRI」、月曜日、水曜日、金曜日トリガーにしました
/ 表示值的增量。例如:如果分钟域中放入‘ 0/15 ’,它表示每个15分钟,从0开始,如果在分钟域中使用' 3/20 '表示小时中每隔20分钟,从第3分钟开始或者另外相同的形式就是' 3,23,43 '
L

可以在Day-of-Month与Day-of-Week中使用,表示last,但是在这两个域中的意义不同。

例如:

1. 在day-of-month中,L表示这个月的最后一天

2. 在day-of-week中,L表示7或者“ SAT ”,但是如果在day-of-week域中,L跟在别的值后面,则表示“当月的最后的周xxx”。例如:6L或者FRIL都表示本月的最后一个周五。

注意:在使用L选项的时候,最重要的是不要指定列表或者值范围,否则可能会导致混乱

W 用来指定给定目标最接近的周几(在day-of-week中指定)。例如:如果你为day-of-month域指定为15W,则表示距离月中15号最近的周几
# 表示月中的第几个周几。例如:day-of-week域中6#3或者FRI#3表示月中的第三个周五

注意:

1.  ?的用法,如果指定了日,那么周就不用指定了,如果指定了周那么日就不用指定了。原因很好理解。 

2.  L和W可以一起使用(企业中可用在工资计算)

3.  #可表示月中第几个周几(企业中用于计算母亲节父亲节)

4.  周字段英文字母不区分大小写,例如:MON=mon

2. 几个cron表达式

表达式 含义
0 0 10,14,16 * * ? 每天上午10点,下午12点,下午四点
0 0/30 9-17 * * ? 朝九晚五工作时间内每半小时,从0分开始每隔30分钟发送一次
0 0 12 ? * WED 每个星期三中午12点
0 0 12 * * ? 每天中午12点
0 15 10 * * ? 每天上午10:15
0 15 10 ? * * 每天上午10:15
0 15 10 * * ? * 每天上午10:15
0 15 10 * * ? 2005 2005年的每天上午10:15
0 * 14 * * ? 在每天下午2点到下午2:59期间每一分钟触发
0 0/55 14 * * ? 在每天下午2点到下午2:55期间,从0开始到55分钟触发
0 0/55 14,18 * * ? 在每天下午2点到下午2:55期间和下午6点带6:55期间,从0开始到55分钟触发
0 0-5 14 * * ? 在每天下午2点到下午2:05期间每1分钟
0 10,44 14 ? 3 WED 每年三月星期三的下午2:10和2:44
0 15 10 ? * MON-FRI 周一至周五的上午10:15触发
0 15 10 15 * ? 每月15日上午10:15触发
0 15 10 L * ? 每月最后一日的上午10:15触发
0 15 10 ? * 6L 每月的最后一个星期五上午10:15
0 15 10 ? * 6L 2002-2005 200-2005年的每月的最后一个星期五上午10:15触发
0 15 10 ? * 6#3 每月的第三个星期五上午10:15触发

     例如:

     JobTriggerCron.java

package cn.bjc.quartz.job;

import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;

import org.quartz.Job;
import org.quartz.JobDataMap;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.quartz.JobKey;
import org.quartz.PersistJobDataAfterExecution;
import org.quartz.Trigger;
import org.quartz.TriggerKey;

public class JobTriggerCron implements Job {

	@Override
	public void execute(JobExecutionContext context) throws JobExecutionException {
		Calendar calendar = Calendar.getInstance();
		Date time = calendar.getTime();
		String format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(time);
		System.out.println("任务开始执行,时间是:" + format);
	}

}

JobTriggerCronMain.java

package cn.bjc.quartz.main;

import java.util.Date;

import org.quartz.CronScheduleBuilder;
import org.quartz.JobBuilder;
import org.quartz.JobDetail;
import org.quartz.Scheduler;
import org.quartz.Trigger;
import org.quartz.TriggerBuilder;
import org.quartz.impl.StdSchedulerFactory;

import cn.bjc.quartz.job.JobTriggerCron;

public class JobTriggerCronMain {
	
	public static void main(String[] args) throws Exception {
		Date startDate = new Date();
		startDate.setTime(startDate.getTime() + 3000);
		Date endDate = new Date();
		endDate.setTime(endDate.getTime() + 10000);
		// 1. 调度器
		Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
		// 2. 任务实例
		JobDetail jobDetail = JobBuilder.newJob(JobTriggerCron.class)
							.withIdentity("job1", "group1")	// 参数一:任务的名称(唯一实例);参数二:任务组的名称,对任务进行分组
							.build();
		// 3. 触发器(Trigger)
		Trigger trigger = TriggerBuilder.newTrigger()
							.withIdentity("trigger1", "group1") // 参数一:触发器的名称;参数二:触发器组的名称,对触发器进行分组 
							//.startAt(startDate)
							//.endAt(endDate)
							.startNow()
							.withSchedule(CronScheduleBuilder.cronSchedule("0/5 * * * * ?"))// 日历每5秒执行
							.build();
		// 最后,让调度器关联任务和触发器,保证按照触发器定义的条件去执行任务。  SimpleScheduleBuilder
		scheduler.scheduleJob(jobDetail, trigger);
		// 启动
		scheduler.start();
	}
}

 运行结果:

 

发布了128 篇原创文章 · 获赞 6 · 访问量 3228

おすすめ

転載: blog.csdn.net/weixin_43318134/article/details/103792703