Quartz-Spring定时任务器持久化,通过Service动态添加,删除,启动暂停任务
原文地址:https://blog.csdn.net/ljqwstc/article/details/78257091
首先添加maven的依赖:
<!--quartz定时任务-->
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz</artifactId>
<version>2.2.1</version>
</dependency>
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz-jobs</artifactId>
<version>2.2.1</version>
spring配置文件中添加如下bean
<!--
自动装配类
@Autowired
private Scheduler scheduler;
-->
<!-- quartz持久化存储 -->
<!--实现动态配置只需定义一下schedulerbean就可以了-->
<bean name="schedulerFactoryBean"
class="org.springframework.scheduling.quartz.SchedulerFactoryBean" lazy-init="true">
<property name="dataSource">
<ref bean="dataSource"/>
</property>
<property name="applicationContextSchedulerContextKey" value="applicationContext"/>
<property name="quartzProperties">
<props>
<prop key="org.quartz.scheduler.instanceId">AUTO</prop>
<!-- 线程池配置 -->
<prop key="org.quartz.threadPool.class">${org.quartz.threadPool.class}</prop>
<prop key="org.quartz.threadPool.threadCount">${org.quartz.threadPool.threadCount}</prop>
<prop key="org.quartz.threadPool.threadPriority">${org.quartz.threadPool.threadPriority}</prop>
<prop key="org.quartz.jobStore.misfireThreshold">${org.quartz.jobStore.misfireThreshold}</prop>
<!-- JobStore 配置 -->
<prop key="org.quartz.jobStore.class">${org.quartz.jobStore.class}</prop>
<!-- 集群配置 -->
<prop key="org.quartz.jobStore.isClustered">true</prop>
<prop key="org.quartz.jobStore.clusterCheckinInterval">15000</prop>
<prop key="org.quartz.jobStore.maxMisfiresToHandleAtATime">1</prop>
<!-- 数据表设置 -->
<prop key="org.quartz.jobStore.tablePrefix">${org.quartz.jobStore.tablePrefix}</prop>
<prop key="org.quartz.jobStore.dataSource">${org.quartz.jobStore.dataSource}</prop>
</props>
</property>
</bean>
1.一般公司项目中都已经定义好了dataSource,比如我们公司是用DruidDataSource,所以直接拿来用就可以了。如果没有的话,需要配置一个dataSource,这里就不阐述了。
3.Scheduler简单说相当于一个容器一样,里面放着jobDetail和Trriger
4.其他的一些配置可以根据自己的业务需求,到quartz的官网查看配置文档进行添加。
这个是quartz.properties文件
#============================================================================
# Configure Datasources
#============================================================================
#JDBC驱动
#org.quartz.dataSource.myDS.driver = com.mysql.jdbc.Driver
#org.quartz.dataSource.myDS.URL = jdbc:mysql://localhost:3306/quartz?characterEncoding=utf-8
#org.quartz.dataSource.myDS.user = root
#org.quartz.dataSource.myDS.password = root
#org.quartz.dataSource.myDS.maxConnections =5
#集群配置
org.quartz.scheduler.instanceName: DefaultQuartzScheduler
org.quartz.scheduler.rmi.export: false
org.quartz.scheduler.rmi.proxy: false
org.quartz.scheduler.wrapJobExecutionInUserTransaction: false org.quartz.threadPool.class: org.quartz.simpl.SimpleThreadPool
org.quartz.threadPool.threadCount: 10
org.quartz.threadPool.threadPriority: 5
org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread: true org.quartz.jobStore.misfireThreshold: 60000 #============================================================================
# Configure JobStore
#============================================================================ #默认配置,数据保存到内存
#org.quartz.jobStore.class: org.quartz.simpl.RAMJobStore
#持久化配置
org.quartz.jobStore.class:org.quartz.impl.jdbcjobstore.JobStoreTX
org.quartz.jobStore.driverDelegateClass:org.quartz.impl.jdbcjobstore.StdJDBCDelegate
org.quartz.jobStore.useProperties:true #数据库表前缀
org.quartz.jobStore.tablePrefix:qrtz_
org.quartz.jobStore.dataSource:myDS
这个是quartz官方的表,运行就可以了,就建表成功了。
#
# Quartz seems to work best with the driver mm.mysql-2.0.7-bin.jar
#
# PLEASE consider using mysql with innodb tables to avoid locking issues
#
# In your Quartz properties file, you'll need to set
# org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.StdJDBCDelegate
# DROP TABLE IF EXISTS QRTZ_FIRED_TRIGGERS;
DROP TABLE IF EXISTS QRTZ_PAUSED_TRIGGER_GRPS;
DROP TABLE IF EXISTS QRTZ_SCHEDULER_STATE;
DROP TABLE IF EXISTS QRTZ_LOCKS;
DROP TABLE IF EXISTS QRTZ_SIMPLE_TRIGGERS;
DROP TABLE IF EXISTS QRTZ_SIMPROP_TRIGGERS;
DROP TABLE IF EXISTS QRTZ_CRON_TRIGGERS;
DROP TABLE IF EXISTS QRTZ_BLOB_TRIGGERS;
DROP TABLE IF EXISTS QRTZ_TRIGGERS;
DROP TABLE IF EXISTS QRTZ_JOB_DETAILS;
DROP TABLE IF EXISTS QRTZ_CALENDARS; CREATE TABLE QRTZ_JOB_DETAILS
(
SCHED_NAME VARCHAR(120) NOT NULL,
JOB_NAME VARCHAR(200) NOT NULL,
JOB_GROUP VARCHAR(200) NOT NULL,
DESCRIPTION VARCHAR(250) NULL,
JOB_CLASS_NAME VARCHAR(250) NOT NULL,
IS_DURABLE VARCHAR(1) NOT NULL,
IS_NONCONCURRENT VARCHAR(1) NOT NULL,
IS_UPDATE_DATA VARCHAR(1) NOT NULL,
REQUESTS_RECOVERY VARCHAR(1) NOT NULL,
JOB_DATA BLOB NULL,
PRIMARY KEY (SCHED_NAME,JOB_NAME,JOB_GROUP)
)ENGINE=MyISAM DEFAULT CHARSET=utf8; CREATE TABLE QRTZ_TRIGGERS
(
SCHED_NAME VARCHAR(120) NOT NULL,
TRIGGER_NAME VARCHAR(200) NOT NULL,
TRIGGER_GROUP VARCHAR(200) NOT NULL,
JOB_NAME VARCHAR(200) NOT NULL,
JOB_GROUP VARCHAR(200) NOT NULL,
DESCRIPTION VARCHAR(250) NULL,
NEXT_FIRE_TIME BIGINT(13) NULL,
PREV_FIRE_TIME BIGINT(13) NULL,
PRIORITY INTEGER NULL,
TRIGGER_STATE VARCHAR(16) NOT NULL,
TRIGGER_TYPE VARCHAR(8) NOT NULL,
START_TIME BIGINT(13) NOT NULL,
END_TIME BIGINT(13) NULL,
CALENDAR_NAME VARCHAR(200) NULL,
MISFIRE_INSTR SMALLINT(2) NULL,
JOB_DATA BLOB NULL,
PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
FOREIGN KEY (SCHED_NAME,JOB_NAME,JOB_GROUP)
REFERENCES QRTZ_JOB_DETAILS(SCHED_NAME,JOB_NAME,JOB_GROUP)
)ENGINE=MyISAM DEFAULT CHARSET=utf8; CREATE TABLE QRTZ_SIMPLE_TRIGGERS
(
SCHED_NAME VARCHAR(120) NOT NULL,
TRIGGER_NAME VARCHAR(200) NOT NULL,
TRIGGER_GROUP VARCHAR(200) NOT NULL,
REPEAT_COUNT BIGINT(7) NOT NULL,
REPEAT_INTERVAL BIGINT(12) NOT NULL,
TIMES_TRIGGERED BIGINT(10) NOT NULL,
PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
)ENGINE=MyISAM DEFAULT CHARSET=utf8; CREATE TABLE QRTZ_CRON_TRIGGERS
(
SCHED_NAME VARCHAR(120) NOT NULL,
TRIGGER_NAME VARCHAR(200) NOT NULL,
TRIGGER_GROUP VARCHAR(200) NOT NULL,
CRON_EXPRESSION VARCHAR(200) NOT NULL,
TIME_ZONE_ID VARCHAR(80),
PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
)ENGINE=MyISAM DEFAULT CHARSET=utf8; CREATE TABLE QRTZ_SIMPROP_TRIGGERS
(
SCHED_NAME VARCHAR(120) NOT NULL,
TRIGGER_NAME VARCHAR(200) NOT NULL,
TRIGGER_GROUP VARCHAR(200) NOT NULL,
STR_PROP_1 VARCHAR(512) NULL,
STR_PROP_2 VARCHAR(512) NULL,
STR_PROP_3 VARCHAR(512) NULL,
INT_PROP_1 INT NULL,
INT_PROP_2 INT NULL,
LONG_PROP_1 BIGINT NULL,
LONG_PROP_2 BIGINT NULL,
DEC_PROP_1 NUMERIC(13,4) NULL,
DEC_PROP_2 NUMERIC(13,4) NULL,
BOOL_PROP_1 VARCHAR(1) NULL,
BOOL_PROP_2 VARCHAR(1) NULL,
PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
)ENGINE=MyISAM DEFAULT CHARSET=utf8; CREATE TABLE QRTZ_BLOB_TRIGGERS
(
SCHED_NAME VARCHAR(120) NOT NULL,
TRIGGER_NAME VARCHAR(200) NOT NULL,
TRIGGER_GROUP VARCHAR(200) NOT NULL,
BLOB_DATA BLOB NULL,
PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
)ENGINE=MyISAM DEFAULT CHARSET=utf8; CREATE TABLE QRTZ_CALENDARS
(
SCHED_NAME VARCHAR(120) NOT NULL,
CALENDAR_NAME VARCHAR(200) NOT NULL,
CALENDAR BLOB NOT NULL,
PRIMARY KEY (SCHED_NAME,CALENDAR_NAME)
)ENGINE=MyISAM DEFAULT CHARSET=utf8; CREATE TABLE QRTZ_PAUSED_TRIGGER_GRPS
(
SCHED_NAME VARCHAR(120) NOT NULL,
TRIGGER_GROUP VARCHAR(200) NOT NULL,
PRIMARY KEY (SCHED_NAME,TRIGGER_GROUP)
)ENGINE=MyISAM DEFAULT CHARSET=utf8; CREATE TABLE QRTZ_FIRED_TRIGGERS
(
SCHED_NAME VARCHAR(120) NOT NULL,
ENTRY_ID VARCHAR(95) NOT NULL,
TRIGGER_NAME VARCHAR(200) NOT NULL,
TRIGGER_GROUP VARCHAR(200) NOT NULL,
INSTANCE_NAME VARCHAR(200) NOT NULL,
FIRED_TIME BIGINT(13) NOT NULL,
SCHED_TIME BIGINT(13) NOT NULL,
PRIORITY INTEGER NOT NULL,
STATE VARCHAR(16) NOT NULL,
JOB_NAME VARCHAR(200) NULL,
JOB_GROUP VARCHAR(200) NULL,
IS_NONCONCURRENT VARCHAR(1) NULL,
REQUESTS_RECOVERY VARCHAR(1) NULL,
PRIMARY KEY (SCHED_NAME,ENTRY_ID)
)ENGINE=MyISAM DEFAULT CHARSET=utf8; CREATE TABLE QRTZ_SCHEDULER_STATE
(
SCHED_NAME VARCHAR(120) NOT NULL,
INSTANCE_NAME VARCHAR(200) NOT NULL,
LAST_CHECKIN_TIME BIGINT(13) NOT NULL,
CHECKIN_INTERVAL BIGINT(13) NOT NULL,
PRIMARY KEY (SCHED_NAME,INSTANCE_NAME)
)ENGINE=MyISAM DEFAULT CHARSET=utf8; CREATE TABLE QRTZ_LOCKS
(
SCHED_NAME VARCHAR(120) NOT NULL,
LOCK_NAME VARCHAR(40) NOT NULL,
PRIMARY KEY (SCHED_NAME,LOCK_NAME)
)ENGINE=MyISAM DEFAULT CHARSET=utf8; commit;
ok,到这里,我们需要的配置基本完成,接下来,就是怎么样去创建任务,实现任务的动态添加,修改。
下面详细解释下实现方法:
public class ScheduleJob implements Serializable {
private static final long serialVersionUID = -5115028108119830917L;
/**
* 任务名称
*/
private String jobName;
/**
* 任务分组
*/
private String jobGroup;
/**
* 触发器名称(默认和任务名称相同)
*/
private String triggerName;
/**
* 触发器分组(默认和任务分组相同)
*/
private String triggerGroup;
/**
* 任务需要调用的是哪个类的类名
*/
private String className;
/**
* 任务需要调用的是哪个类的方法名
*/
private String methodName;
/**
* 方法所需参数数组
*/
private ArrayList paramArray;
/**
* 任务运行时间表达式
*/
private String cron;
/**
* 任务运行时间(特指只运行一次的任务)
*/
private String runDate;
/**
* 任务描述
*/
private String desc;
getter and setter.....
}
@PersistJobDataAfterExecution
public class QuartzJobFactory implements Job { /**
* The constant logger.
*/
private static final Logger logger = LoggerFactory.getLogger(Constants.LOG_SYSTEM_RUN); public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
ScheduleJob scheduleJob = (ScheduleJob) jobExecutionContext.getMergedJobDataMap().get("scheduleJob");
//获取service层接口名和接口方法名
String springBeanName = scheduleJob.getClassName();
String methodName = scheduleJob.getMethodName();
//获取参数数组和参数类型数组
ArrayList paramArray = scheduleJob.getParamArray();
Class[] paramType = new Class[paramArray.size()];
for (int i = 0; i < paramArray.size(); i++) {
paramType[i] = paramArray.get(i).getClass();
}
/**
* 反射运行service层的方法
*/
Object bean = SpringContextsUtil.getBean(springBeanName);
Method method = ReflectionUtils.findMethod(bean.getClass(), methodName, paramType);
ReflectionUtils.invokeMethod(method, bean, paramArray.toArray()); logger.info("任务名称 = [" + scheduleJob.getJobName() + "]," +
"任务调用的类=[" + scheduleJob.getClassName() + "]," +
"任务调用的方法=[" + scheduleJob.getMethodName() + "]---->>成功启动运行");
}
}
@PersistJobDataAfterExecution
如果不支持并发
@disallowconcurrentexecution
@Component
public class SpringContextsUtil implements ApplicationContextAware { private static ApplicationContext applicationContext; public static Object getBean(String beanName) throws BeansException {
return applicationContext.getBean(beanName);
} public static <T> T getBean(String beanName, Class<T> clazs) {
return applicationContext.getBean(beanName, clazs);
} @Override
public void setApplicationContext(ApplicationContext applicationContext)
throws BeansException {
SpringContextsUtil.applicationContext = applicationContext;
}
}
public interface QuartzProducer {
/**
* 添加简单任务,只运行一次的任务
*
* @param jobName
* @param jobGroup
* @param className
* @param methodName
* @param paramArray
* @param runDateTime 格式:yyyyMMddHHmmss
* @throws SchedulerException
* @throws ParseException
*/
public void addSimpleJob(String jobName, String jobGroup, String className, String methodName, ArrayList paramArray, String runDateTime) throws SchedulerException, ParseException;
/**
* 添加循环任务,特定时间循环运行,例如每个星期3,12点运行等
*
* @param jobName
* @param jobGroup
* @param className
* @param methodName
* @param paramArray
* @param cron
* @throws SchedulerException
*/
public void addCronJob(String jobName, String jobGroup, String className, String methodName, ArrayList paramArray, String cron) throws SchedulerException;
/**
* 修改简单任务,一般指修改运行时间
*
* @param jobName
* @param jobGroup
* @param runDateTime 格式:yyyyMMddHHmmss
*/
public void updateSimpleJob(String jobName, String jobGroup, String runDateTime) throws SchedulerException, ParseException;
/**
* 修改cron任务,一般指修改循环运行时间
*
* @param jobName
* @param jobGroup
* @param cron
*/
public void updateCronJob(String jobName, String jobGroup, String cron) throws SchedulerException;
/**
* 移除任务
*
* @param jobName
* @param jobGroup
*/
public void deleteJob(String jobName, String jobGroup) throws SchedulerException;
/**
* 移除所有任务
*/
public void deleteAll() throws SchedulerException;
/**
* 暂停任务
*
* @param jobName
* @param jobGroup
*/
public void pauseJob(String jobName, String jobGroup) throws SchedulerException;
/**
* 暂停所有任务
*/
public void pauseAll() throws SchedulerException;
/**
* 恢复某个任务
*
* @param jobName
* @param jobGroup
*/
public void resumeJob(String jobName, String jobGroup) throws SchedulerException;
/**
* 恢复所有
*/
public void resumeAll() throws SchedulerException;
/**
* 关闭任务调度器
*/
public void shutDown() throws SchedulerException;
/**
* 开启任务调度器
*/
public void startScheduler() throws SchedulerException;
}
实现类:
//定义该bean的name为"quartzProducer"
@Service("quartzProducer")
public class QuartzProducerImpl implements QuartzProducer { //虽然在Spring配置中配置的是SchedulerFactoryBean这个类,但是我们自动转配就写这样,一样是可以使用的
@Autowired
private Scheduler scheduler; @Override
public void addSimpleJob(String jobName, String jobGroup, String className, String methodName, ArrayList paramArray, String runDateTime) throws SchedulerException, ParseException {
//判断是否已存在相同jobName,jobGroup,若存在则删除
if (scheduler.getJobDetail(JobKey.jobKey(jobName, jobGroup)) != null) {
deleteJob(jobName, jobGroup);
}
JobDetail jobDetail = JobBuilder.newJob(QuartzJobFactory.class).withIdentity(jobName, jobGroup).build();
//任务具体执行的内容封装,返回给job统一入口
ScheduleJob job = new ScheduleJob(jobName, jobGroup, jobName, jobGroup, className, methodName, paramArray, null, runDateTime, null);
//将我自定义的POJO类存放到DataMap中
jobDetail.getJobDataMap().put("scheduleJob", job);
//创建SimpleTrigger,在特定时间仅运行一次
Date runDate = DateUtils.parseDate(runDateTime, DateStyle.YYYYMMDDHHMMSS.getValue());
SimpleTrigger trigger = (SimpleTrigger) TriggerBuilder.newTrigger().withIdentity(jobName, jobGroup).
startAt(runDate).build();
scheduler.scheduleJob(jobDetail, trigger);
} }
Quartz-Spring定时任务器持久化,通过Service动态添加,删除,启动暂停任务的更多相关文章
- jquery动态添加删除div--事件绑定,对象克隆
我想做一个可以动态添加删除div的功能.中间遇到一个问题,最后在manong123.com开发文摘 版主的热心帮助下解答了(答案在最后) 使用到的jquery方法和思想就是:事件的绑定和销毁(unbi ...
- 编辑 Ext 表格(一)——— 动态添加删除行列
一.动态增删行 在 ext 表格中,动态添加行主要和表格绑定的 store 有关, 通过对 store 数据集进行添加或删除,就能实现表格行的动态添加删除. (1) 动态添加表格的行 gridS ...
- 用Javascript动态添加删除HTML元素实例 (转载)
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/ ...
- js实现网页收藏功能,动态添加删除网址
<html> <head> <title> 动态添加删除网址 </title> <meta charset="utf-8"&g ...
- jQuery动态添加删除CSS样式
jQuery框架提供了两个CSS样式操作方法,一个是追加样式addClass,一个是移除样式removeClass,下面通过一个小例子讲解用法. jQuery动态追加移除CSS样式 <!DOCT ...
- JS动态添加删除html
本功能要求是页面传一个List 集合给后台而且页面可以动态添加删除html代码需求如下: 下面是jsp页面代码 <%@ page language="java" pageEn ...
- C#控制IIS动态添加删除网站
我的目的是在Winform程序里面,可以直接启动一个HTTP服务端,给下游客户连接使用. 查找相关技术,有两种方法: 1.使用C#动态添加网站应用到IIS中,借用IIS的管理能力来提供HTTP接口.本 ...
- Angular-表单动态添加删除
angular本身不允许去操作DOM,在angular的角度来说,所有操作都以数据为核心,剩下的事情由angular来完成.所以说,想清楚问题的根源,解决起来也不是那么困难. 前提 那么,要做的这个添 ...
- jQuery动态添加删除select项
// 添加 function col_add() { var selObj = $("#mySelect"); var value="value"; var t ...
随机推荐
- JavaScript or JQuery 获取服务器时间
用js做时间校正,获取本机时间,是存在bug的. 使用js也可获取到服务器时间,原理是使用 ajax请求,返回的头部信息就含有服务器端的时间信息,获取到就可以了(有的IE下扔不会正常获取,还是更建议走 ...
- 特别篇:Hyper-v群集模拟实战演示
介绍 由于前面几张的都是直接整理了下 九叔的hyper-v电子书发上来的,个人觉得他写的不是最详细,因此今天我按照自己的实际情况来写个模拟的实战演示.所有的东西都通过VMware WorkStatio ...
- MC9SD64单片机快速入门 I/O寄存器
I/O的使用 数据方向寄存器和数据寄存器的配置 I/O输入输出的使用: 数据方向寄存器与数据寄存器 寄存器的概念: 寄存器,是集成电路中非常重要的一种存储单元,通常由触发器组成.在集成电路设计中,寄存 ...
- 第七章 LED将为我闪烁:控制发光二级管
LED驱动开发实验 如图所示,LED1-LED2 分别与GPC0_3.GPC0_4 相连,通过GPC0_3.GPC0_4 引脚的高低电平来控制三极管的导通性,从而控制LED 的亮灭. 根据三极管的特性 ...
- February 21st, 2018 Week 8th Wednesday
Our life is what our thoughts make it. 我们的思想成就了我们的生活. The mind is everything. What you think, you be ...
- vue开发常见命令
1.安装脚手架 安装脚手架命令:npm install -global vue-cli 2.升级脚手架 有时候需要把整个脚手架升级一下,这个用到命令npm install --global vue-c ...
- #006 C语言大作业学生管理系统第三天
还差最后两部分 读取文件 恢复删除的学生信息 先学会处理文件的 知识点,再继续跟着视频做这个作业. 应该明天周六能把视频里手把手教的学生管理系统敲完 第二周尽量自己能完成C语言课本最后面那道学生管理系 ...
- Thread.currentThread()和this的区别——《Java多线程编程核心技术》
前言:在阅读<Java多线程编程核心技术>过程中,对书中程序代码Thread.currentThread()与this的区别有点混淆,这里记录下来,加深印象与理解. 具体代码如下: pub ...
- nginx学习笔记(一)
select模型主要是apache用 FD 文件描述符 soa架构 安装nginx ping baidu.com netstat -lntup 查看端口 cat /etc/redhat-rel ...
- 正则表达式工具RegexBuddy
1 下载 RegexBuddy 并安装 安装后的界面如下: 2 切换布局 点击右上角的彩色格子图标,选择 Side by Side Layout: 这种布局的好处是,Create 面板 ...