




已阅读5页,还剩11页未读, 继续免费阅读
版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
Quartz.NETQuartz.NET 是一个开源的作业调度框架,能够应用在小到轻量级的应用程序,大到重量级的企业级系统中的全功能的开源任务调度系统。一、特点 API 操作简单,只要几行简单的代码你就可以在应用程序里面实现自己的作业调度,并实时监视作业执行情况。 触发器功能强大,比 Windows 的任务计划提供更细的触发粒度,可以使用“Cron表达式”(类似于正则表达式)。 良好的可扩展性,它基于接口编程,你可以实现自己的 Schedule 调度器,Job 作业,以及 Trigger 触发器等 。 作业可以保存在 RAM 中,也可以持久化到数据库,支持多种数据库类型:SqlServer、Oracle、MySql等。 集群,这是一个高级应用,可以在多台计算机之间创建负载平衡、容错处理。 支持两种途径配置应用程序的运行时属性:声明式和编程式。二、Quartz的目录结构内容目录名 存放内容Quartz.Collection 数据结构Quartz.Core 调度器,线程池Quartz.Impl 接口实现类Quartz.Job 作业Quartz. Listener 监听器Quartz. Plugin 扩展插件Quartz. Simpl 简单的调试器、线程池Quartz. SPI 接口Quartz. Util 实用方法Quartz. Xml Xml配置操作类三、作业流程的定制作业流程是在调度器的统一调度下完成的,它可以调度多个作业,触发器提供作业执行的条件(每天 8:00 am),触发器与作业关联,它们是 1:N 的关系。1. JobJob 每一个 Quartz Job 必须有一个实现IJob接口的具体类。这个接口仅有一个要在 Job 中实现的方法,execute(),方法 execute() 的原型如下: voidExecute(JobExecutionContextcontext); 可以在 execute() 方法中执行你的业务逻辑:例如,也许你会调用其他构造的实例上的方法,发送一个电子邮件、FTP 传一个文件、调用一个 Web 服务、执行一个工作流等。当 Quartz调用 execute() 方法,会传递一个 JobExecutionContext 上下文变量,里面封装有 Quartz 的运行时环境和当前正执行的 Job。通过 JobExecutionContext,你可以访问到调度器的信息,作业和作业上的触发器的信息,还有更多更多的信息。例如:public class TestJobImpl : IExtendedJob private string jobContent; public string JobContent get return jobContent; set jobContent = value; public TestJobImpl() : base() public void Execute(JobExecutionContext context) jobContent = context.MergedJobDataMap.Get(JobContent).ToString(); System.Diagnostics.ProcessStartInfo startInfo = new ProcessStartInfo(); startInfo.FileName = jobContent; startInfo.WindowStyle = ProcessWindowStyle.Normal; Process pro = new Process(); pro.StartInfo = startInfo; pro.Start(); pro.WaitForExit(5000); pro.Kill(); IStatefulJob(有状态Job) 一个 Job 实例可以被定义为“有状态的”或者“无状态的”。在执行无状态的任务过程中任何对 JobDataMap 所作的更改都将丢失。有状态的任务恰好相反,它在任务的每次执行之后重新存储 JobDataMap 。有状态任务的一个缺点就是它不能并发执行。也就是说,如果任务有状态,那么当触发器试图触发它,触发器就会被阻塞直到前面的执行完成。想使任务有状态,它就要实现 IStatefulJob 接口而不是实现IJob接口。IStatefulJob接口仅仅是扩展了 Job 接口,未加入新的方法。你只需要通过使用与 Job 接口相同的 execute() 方法简单的实现 IStatefulJob接口即可。假如你有已存在的 Job 类,你所有要做的只是改变 Job 的接口为 IStatefulJob。Public interfaceIStatefulJob:IJobIInterruptableJob(中断 Job)它扩展了普通的 Job 接口并提供了一个 interrupt() 方法:有时候需要能中断一个 Job,尤其是对于一个长时间执行的 Job。例如,假定你有一个 Job 运行过程要花费一个小时,你发现在 5 分钟的时候因某个非受控的错误被中断需要接着执行。你或许也会中断 Job,修复问题,然后又继续运行。 public interfaceIInterruptableJob:IJobvoidInterrupt();2. TriggerTrigger 的责任就是触发一个 Job 去执行。当用 Scheduler 注册一个 Job 的时候要创建一个 Trigger 与这个 Job 相关联。Quartz打包了很多不同类型的Trigger,但最常用的Trigge类是SimpleTrigger和CronTrigger。SimpleTrigger 是两个之中简单的那个,它主要用来激发单事件的 Job,Trigger 在指定时间激发,并重复 n 次-两次激发时间之间的延时为 m,然后结束作业。构造函数:/ 触发器名/ 触发器组名/ 绑定的任务名/ 绑定的任务组名/ 开始执行时间/ 结束执行时间/ 重复触发次数/ 触发间隔SimpleTrigger(string name, string group, string jobName, string jobGroup, DateTime startTimeUtc, NullableDateTime endTimeUtc,int repeatCount, TimeSpan repeatInterval);实例:(每两小时执行一次,重复执行3次,开始执行时间为当前时间)SimpleTrigger trig = new SimpleTrigger(); trig.Name = trigName; trig.RepeatInterval = 2;trig.RepeatCount = 3;CronTrigger 非常复杂且强大。它是基于通用的公历,当需要用一种较复杂的时间表去执行一个 Job 时用到。例如,四月至九月的每个星期一、星期三、或星期五的午夜。Cron 表达式包括以下 7 个字段(1 个可选)秒 分 小时 月内日期 月 周内日期 年(可选)表达式的每个数值域都是一个有最大值和最小值的集合,如:秒域和分钟域的集合是0-59,日期域是1-31,月份域是1-12。注意:秒、分、小时字段是从小到大排序的,在使用的时候要小心,不要颠倒过来。表1. Cron 表达式允许值及对应表字段允许值允许的特殊字符秒0-59, - * /分0-59, - * /小时0-23, - * /月内日期1-31, - * ? / L W C月1-12 或者 JAN-DEC, - * /周内日期1-7 或者 SUN-SAT, - * ? / L C #年(可选)留空, 1970-2099, - * /表2特殊字符意义对应表特殊字符说明*匹配所有的值。如:*在分钟的字段域里表示 每分钟?只在日期域和星期域中使用。它被用来指定“非明确的值”-指定一个范围。如:“10-12”在小时域意味着“10点、11点、12点”,指定几个可选值。如:“MON,WED,FRI”在星期域里表示“星期一、星期三、星期五” /指定增量。如:“0/15”在秒域意思是没分钟的0,15,30和45秒。“5/15”在分钟域表示没小时的5,20,35和50。符号“*”在“/”前面(如:*/10)等价于0在“/”前面(如:0/10)L表示day-of-month和day-of-week域,但在两个字段中的意思不同,例如day-of-month域中表示一个月的最后一天。如果在day-of-week域表示7或者SAT,如果在day-of-week域中前面加上数字,它表示一个月的最后几天,例如6L就表示一个月的最后一个星期五W只允许日期域出现。这个字符用于指定日期的最近工作日。例如:如果你在日期域中写 “15W”,表示:这个月15号最近的工作日。所以,如果15号是周六,则任务会在14号触发。如果15好是周日,则任务会在周一也就是16号触发。如果是在日期域填写“1W”即使1号是周六,那么任务也只会在下周一,也就是3号触发,“W”字符指定的最近工作日是不能够跨月份的。字符“W”只能配合一个单独的数值使用,不能够是一个数字段,如:1-15W是错误的LWL和W可以在日期域中联合使用,LW表示这个月最后一周的工作日#只允许在星期域中出现。这个字符用于指定本月的某某天。例如:“6#3”表示本月第三周的星期五(6表示星期五,3表示第三周)。“2#1”表示本月第一周的星期一。“4#5”表示第五周的星期三C允许在日期域和星期域出现。这个字符依靠一个指定的“日历”。也就是说这个表达式的值依赖于相关的“日历”的计算结果,如果没有“日历”关联,则等价于所有包含的“日历”。如:日期域是“5C”表示关联“日历”中第一天,或者这个月开始的第一天的后5天。星期域是“1C”表示关联“日历”中第一天,或者星期的第一天的后1天,也就是周日的后一天(周一)示例:0 0 0 1 1 ?” 每年元旦1月1日 0 点触发0 15 10 * * ? * 每天上午10:15触发 0 15 10 * * ? 2005 2005年的每天上午10:15触发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 ? * 6#3 每月的第三个星期五上午10:15触发为更简单的使用 Trigger,Quartz 包含了一个工具类,叫做 Quartz.TriggerUtils 提供了许多便捷的方法简化了构造和配置 trigger如:Trigger trigger = MakeSecondlyTrigger();Trigger trigger = MakeHourlyTrigger(“TriggerText”,2, 3); /相当于上面SimpleTrigger的例子3. SchdulerSchduler 是框架的心脏与灵魂。所有的 Job 都通过 Schduler 注册;必要时,Scheduler 也会创建 Job 类的实例,并执行实例的 execute() 方法。使用scheduler之前应首先实例化它。使用SchedulerFactory可以完成scheduler的实例化。用户可直接地实例化这个工厂类并且直接使用工厂的实例。 一旦一个scheduler被实例化,它就可以被启动(start),并且处于驻留模式,直到被关闭(shutdown)。注意,一旦scheduler被关闭(shutdown),则它不能再重新启动,除非重新实例化它。除非scheduler 被启动或者不处于暂停状态,否则触发器不会被触发(任务也不能被执行)。 JobDetail对象由Quartz客户端在Job被加入到scheduler时创建。它包含了Job的各种设置属性(如:对Job的具体描述,包括Job的名称、所属的Group名称、以及Job的Type描述)以及一个JobDataMap对象,用以Quartz.NET通过反射实现对Job的调用。1) 编程式配置应用程序的运行时属性例如:private void scheduleJob(Scheduler scheduler) /初始化调度器工厂 ISchedulerFactory sf = new StdSchedulerFactory(); /获取默认调度器 IScheduler scheduler = sf.GetScheduler(); /作业 JobDetail job = new JobDetail(TestJob, TestJobGrp, typeof(QM.NFoundation.APP.Job.Impl.TestJobImpl); job.JobDataMap.Put(JobContent, sdo.GetString(JobContent); /触发器 Trigger trigger = TriggerUtils.makeSecondlyTrigger(10); /关联任务和触发器 scheduler.ScheduleJob(job, trigger); /开始任务 scheduler.Start(); 2) 声明式配置应用程序的运行时属性a) 可以设置一个quartz_jobs.xml文件,例如: NameValueCollection properties = new NameValueCollection(); propertiesquartz.scheduler.instanceName = XmlConfiguredInstance; / 设置线程池信息 propertiesquartz.threadPool.type = Quartz.Simpl.SimpleThreadPool, Quartz; propertiesquartz.threadPool.threadCount = 5; propertiesquartz.threadPool.threadPriority = Normal; propertiesquartz.plugin.xml.type = Quartz.Plugin.Xml.JobInitializationPlugin, Quartz; propertiesquartz.plugin.xml.fileNames = /quartz_jobs.xml; ISchedulerFactory sf = new StdSchedulerFactory(properties); IScheduler sched = sf.GetScheduler(); sched.Start();b) quartz_jobs.xml内容: jobName1 jobGroup1 jobDesciption1 Quartz.Job.NoOpJob, Quartz false true false key0 value0 key1 value1 key2 value2 simpleName simpleGroup SimpleTriggerDescription SmartPolicy cal1 false jobName1 jobGroup1 1982-06-28T18:15:00.0Z 2010-05-04T18:13:51.0Z 100 3000 这样,在启动Castle的时候,Quartz.Plugin.Xml.JobInitializationPlugin就会自动读取perties这个配置文件,并初始化调度信息,启动Scheduler。一个Job类,一个perties文件,一个quertz_job.xml文件,非常简单灵活。四、Job 存储和持久化Quartz 用 JobStores 对 Job、Trigger、calendar 和Schduler 数据提供一种存储机制。前面的例子,当你停止了 Scheduler 后,那些有关哪些 Job 已经运行和哪些 Job没有运行的信息就会丢失掉。实际上,所有的关于正在运行中的 Job 的信息也被销毁。 当程序被重启后,Trigger 和 Job 的信息被加回去,且所有的一切又都正常了。我们假定,有一个 Job 是安排为 5 PM 执行,然而 Scheduler 在这个时间之前的五分钟(4:55 PM) 时停掉了。如果你在 5:05 PM 时重新启动了 Scheduler 的话将会发生什么事情呢?Scheduler 还会记得要在 5 PM 触发这个 Job 的吗?答案就是看它是依赖于你使用的哪种类型的 JobStore,以及是如何对它配置的。Quartz 支持对 Scheduler 信息的几种不同类型的存储机制。在 Quartz 中两种可用的 Job 存储类型是:内存(非持久化) 存储 、持久化存储。Q缺省使用的就是RAMJobStore。1) RAMJobStore(非持久化存储)RAMJobStore是最简单的JobStore,也是性能最好的(根据CPU时间)。从名字就可以直观地看出,RAMJobStore将所有的数据都保存在RAM中。这就是为什么它闪电般的快速和如此容易地配置。缺点就是当应用结束时所有的日程信息都会丢失,这意味着RAMJobStore不能满足Jobs和Triggers的持久性(“non-volatility”)。对于有些应用来说,这是可以接受的,甚至是期望的行为。但是对于其他应用来说,这将是灾难。 相关配置quartz.jobStore.type = Quartz.Simpl.RAMJobStore, Quartz2) ADO.NET Job Store (持久化存储)AdoJobStore的命名也非常得体,它将所有的数据通过ADO.NET保存到数据库可中。它的配置要比RAMJobStore稍微复杂,同时速度也没有那么快。但是性能的缺陷不是非常差,尤其是如果你在数据库表的主键上建立索引。AdoJobStore几乎可以在任何数据库上工作。要使用AdoJobStore,首先必须创建一套Quartz使用的数据库表,可以在Quartz 的databasetables找到创建库表的SQL脚本。如果没有找到你的数据库类型的脚本,那么找到一个已有的,修改成为你数据库所需要的。需要注意的一件事情就是所有Quartz库表名都以QRTZ_作为前缀(例如:表QRTZ_TRIGGERS,及QRTZ_JOB_DETAIL)。实际上,可以你可以将前缀设置为任何你想要的前缀,只要你告诉AdoJobStore那个前缀是什么即可(在你的Quartz属性文件中配置)。对于一个数据库中使用多个scheduler实例,那么配置不同的前缀可以创建多套库表,十分有用。 一旦数据库表已经创建,在配置和启动AdoJobStore之前,就需要作出一个更加重要的决策。你要决定在你的应用中需要什么类型的事务。如果不想将scheduling命令绑到其他的事务上,那么你可以通过对JobStore使用JobStoreTX来让Quartz帮你管理事务(这是最普遍的选择)。 最后的疑问就是如何建立获得数据库联接的数据源(DataSource)。Quartz属性中定义数据源是通过提供所有联接数据库的信息,让Quartz自己创建和管理数据源。 要使用AdoJobStore(假定使用StdSchedulerFactory),首先需要设置Quartz配置中的quartz.jobStore.type属性为Quartz.Impl.AdoJobStore.JobStoreTX, Quartz。 quartz.jobStore.type= Quartz.Impl.AdoJobStore.JobStoreTX, Quartz下一步,需要为JobStore 选择一个DriverDelegate , Quartz 依赖于某个 DriverDelegate 来与给定数据库进行通信。顾名思义,从 Scheduler 通过 JobStore 对数据库的调用是委托给一个预配置的 DriverDelegate 实例。这个代理负责做指定数据库的所有ADO.NET工作。StdADO.NETDelegate是一个使用vanilla ADO.NET代码(以及SQL语句)来完成工作的代理。如果数据库没有其他指定的代理,那么就使用这个代理。只有当使用StdADO.NETDelegate发生问题时,我们才会使用数据库特定的代理(这看起来非常乐观。其他的代理可以在Quartz.Impl.AdoJobStor命名空间找到)。其他的代理包括PostgreSQLDelegate ( 专为PostgreSQL 7.x)。quartz.jobStore.driverDelegateType= Quartz.Impl.AdoJobStore.SqlServerDelegate, Quartz接下来,需要为JobStore指定所使用的数据库表前缀。 quartz.jobStore.tablePrefix= QRTZ_然后需要设置JobStore所使用的数据源。必须在Quartz属性中定义已命名的数据源,比如,我们指定Quartz使用名为default的数据源(在配置文件的其他地方定义)。 quartz.jobStore.dataSource= default最后,需要配置数据源的使用的A数据提供者和数据库连接串,数据库连接串是标准的A 数据库连接的连接串。数据库提供者是关系数据库同Q之间保持低耦合的数据库的连接提供者。quartz.dataSource.default.connectionString=Server=(local);Database=quartz;Trusted_Connection=True;quartz.dataSvider= SqlServer-20;所有的JobStores都实现了IJobStore接口,如果捆绑的JobStores不能满足你的要求,可以自己开发一个JobStores 。五、实现 Quartz 监听器在某个所关注事件发生时,监听器提供了一种方便且非侵入性的机制来获得这一通知。1.TriggerListeners接收与triggers相关的事件:TriggerFired:当与监听器相关联的 Trigger 被触发,Job 上的 execute() 方法将要被执行时,Scheduler 就调用这个方法。在全局 TriggerListener 情况下,这个方法为所有 Trigger 被调用。VetoJobExecution:在 Trigger 触发后,Job 将要被执行时由 Scheduler 调用这个方法。TriggerListener 给了一个选择去否决 Job 的执行。假如这个方法返回 true,这个 Job 将不会为此次 Trigger 触发而得到执行。TriggerMisfired:Scheduler 调用这个方法是在 Trigger 错过触发时。如这个方法的 JavaDoc 所指出的,你应该关注此方法中持续时间长的逻辑:在出现许多错过触发的 Trigger 时,长逻辑会导致骨牌效应。你应当保持这上方法尽量的小。TriggerComplete:Trigger 被触发并且完成了 Job 的执行时,Scheduler 调用这个方法。这不是说这个 Trigger 将不再触发了,而仅仅是当前 Trigger 的触发(并且紧接着的 Job 执行) 结束时。这个 Trigger 也许还要在将来触发多次的。2.JobListeners则接收与Job相关的事件JobToBeExecuted:Scheduler 在 JobDetail 将要被执行时调用这个方法。JobExecutionVetoed:Scheduler 在 JobDetail 即将被执行,但又被 TriggerListener 否决了时调用这个方法。JobWasExecuted:Scheduler 在 JobDetail 被执行之后调用这个方法。3. SchedulerListeners则接收与Job相关的事件jobScheduled和 jobUnscheduled方法:Scheduler 在有新的 JobDetail 部署或卸载时调用这两个中的相应方法。TriggerFinalized:当一个 Trigger 来到了再也不会触发的状态时调用这个方法。除非这个 Job 已设置成了持久性,否则它就会从 Scheduler 中移除。triggersPaused: Scheduler 调用这个方法是发生在一个 Trigger 或 Trigger 组被暂停时。假如是 Trigger 组的话,triggerName 参数将为 null。TriggersResumed: Scheduler 调用这个方法是发生成一个 Trigger 或 Trigger 组从暂停中恢复时。假如是 Trigger 组的话,triggerName 参数将为 null。JobsPaused: 当一个或一组 JobDetail 暂停时调用这个方法。jobsResumed: 当一个或一组 Job 从暂停上恢复时调用这个方法。假如是一个 Job 组,jobName 参数将为 null。SchedulerError: 在 Scheduler 的正常运行期间产生一个严重错误时调用这个方法。错误的类型会各式的,但是下面列举了一些错误例子: 初始化 Job 类的问题 试图去找到下一 Trigger 的问题 JobStore 中重复的问题 数据存储连接的问题SchedulerShutdown: Scheduler 调用这个方法用来通知 SchedulerListener Scheduler 将要被关闭。4. 实现监听实现监听器的方法通用于所有的三种类型。可以分成以下步骤: 创建一个类,实现监听器接口 用应用中特定的逻辑实现监听器接口的所有方法 注册监听器监听器可以被注册为“全局”的或者“非全局”。“全局”监听器接收所有triggers/jobs产生的事件,而“非全局”监听器只接受那些通过TriggerListenerNames属性 或 JobListenerNames()方法显式指定监听器名的triggers/jobs所产生的事件。例如:public class Job1Listener : IJobListener private readonly ILog log; public Job1Listener() log = LogManager.GetLogger(GetType(); public virtual string Nameget return job1_to_job2; public virtual void JobToBeExecuted(JobExecutionContext inContext)log.Info(Job1Listener says: Job Is about to be executed.);public virtual void JobExecutionVetoed(JobExecutionContext inContext)log.Info(Job1Listener says: Job Execution was vetoed.);public virtual void JobWasExecuted(JobExecutionContext inContext, JobExecutionException inException)log.Info(Job1Listener says: Job was executed.);JobDetail job2 = new JobDetail(job2, SchedulerConstants.DefaultGroup, typeof(SimpleJob2);SimpleTrigger trigger = new SimpleTrigger(job2Trigger, SchedulerConstants.DefaultGroup, DateTime.UtcNow, null, 0, TimeSpan.Zero);tryinContext.Scheduler.ScheduleJob(job2, trigger);catch (SchedulerException e)log.Warn(Unable to schedule job2!);Console.Error.WriteLine(e.StackTrace);正如上面所说的那样,监听器在运行时向scheduler注册,并且不被存储在jobs 和triggers的JobStore中。Jobs和Trigger只存储了与他们相关的监听器的名字。因此,每次应用运行的时候,都需要向scheduler重新注册监听器。向 Scheduler中加入一个JobListener scheduler.AddGlobalJobListener(myJobListener); 或者 scheduler.AddJobListener(myJobListener); privatevoidscheduleJob(Schedulerscheduler)ISchedulerFactory sf = new StdSchedulerFactory();IScheduler sched = sf.GetScheduler();JobDetail job = new JobDetail(job1, group1, typeof(SimpleJob1);SimpleTrigger trigger = new SimpleTrigger(trigger1, group1, DateTime.UtcNow, null, 0, TimeSpan.Zero);IJobListener listener = new Job1Listener();sched.AddJobListener(listener);/注册非全局监听器job.AddJobListener(listener.Name);sched.ScheduleJob(job, trigger);sched.Start();六、Quartz 插件Quartz提供了一个接口(ISchedulerPlugin)来插入附加的功能,使Quartz 变得很容易被扩展和定制化来适应你的需要。随Quartz打包儿来的插件有很多有用的功能,它们在Quartz.Plugins命名空间中找到。他们提供了诸如自动安排任务的日程,将任务和触发器事件的历史记入日志(LoggingJobHistoryPlugin、LoggingTriggerHistoryPlugin)以及虚拟机退出时确保干净地关闭(ShutdownHookPlugin),从 XML 文件中加载 Job 和 Trigger 信息的插件(JobInitializationPlugin)等的功能。 创建插件的步骤: 创建一个实现了 ISchedulerPlugin 接口的实现类。 public interface ISchedulerPlugin void Initialize(string pluginName, IScheduler sched); void Start(); void Shutdown(); ISchedulerPlugin的方法是在 Scheduler 的初始化和启动期间被调用。Scheduler 会调用每一个已注册插件的这三个方法。initialize() 方法:initialize() 方法在 Scheduler 的创建期间被调用。当 StdSchedulerFactory 的 getScheduler() 方法被调用后,这个工厂就调用所有注册的插件的 initialize() 方法。(注:当这个方法在调用的时候,Scheduler 还未完全初始化好,因此它和 Scheduler 的交互应保持在尽量短的时间。例如,你不应该试图在 initialize() 方法中去部署任何 Job。)start()方法:Scheduler 实例调用 start() 方法让插件知道它可以执行任何需要的启动动作了。例如,假如你有 Job 要部署,那这个时候就可以部署他们了。shutdown() 方法:shutdown() 方法被调用来通知插件 Scheduler 将要关闭了。这是给插件的一个机会去清理任何打开的资源。例如,数据库连接或是打开的文件应该要关闭。 把这个插件
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 离婚协议书中特定子女教育支出分担及保障协议范本
- 体育赛事赞助担保合同风险规避与权益保障
- 电力施工合同签订与施工期合同履行及竣工验收规范
- 物业服务合同终止与社区环境修复及恢复协议
- 智算中心扩建项目技术方案
- 化工车间安全员考核及答案
- 博望区营销推广计划方案
- 恩施环保应急预案(3篇)
- 教育质量评估与认证体系2025年行业标准化与认证体系构建策略报告
- 2025年通信基站储能电池梯次利用技术标准与规范报告
- 1.2 连续分类(课件)数学青岛版二年级上册(新教材)
- 《汽车电工与电子技术基础》课件(共七章节)
- 补漏协议书范本
- 风电场定检项目
- “智慧城市”项目规划设计书(总体规划方案)
- 中国诗词协会入会申请表
- 《高级管理学》PPT课件.ppt
- 实现离心泵自动吸水的方法
- (完整word版)汉密尔顿抑郁量表——评定方法
- 部编版道德与法治六年级上册全册教案
- 新突破大学英语综合教程1unit1
评论
0/150
提交评论