ABP+AdminLTE+Bootstrap Table权限管理系统一期

Github:https://github.com/Jimmey-Jiang/ABP-ASP.NET-Boilerplate-Project-CMS

Quartz简介

Quartz.NET是一个开源的作业调度框架,是 OpenSymphonyQuartz API 的.NET移植,它用C#写成,可用于winformasp.net应用中。它提供了巨大的灵活性而不牺牲简单性。你能够用它来为执行一个作业而创建简单的或复杂的调度。它有很多特征,如:数据库支持,集群,插件,支持cron-like表达式等等。非常适合在平时的工作中,定时轮询数据库同步,定时邮件通知,定时处理数据等.

参考

对Quartz.NET不熟悉的可以先看下

官方学习文档:http://www.quartz-scheduler.net/documentation/index.html

使用实例介绍:http://www.quartz-scheduler.net/documentation/quartz-2.x/quick-start.html

官方的源代码下载:http://sourceforge.net/projects/quartznet/files/quartznet/

特性

它一些很好的特性:

  1. 支持集群,作业分组,作业远程管理。
  2. 自定义精细的时间触发器,使用简单,作业和触发分离。
  3. 数据库支持,可以寄宿Windows服务,WebSitewinform等。

Quartz框架的一些基础概念解释:

名称 描述
Scheduler 作业调度器。
IJob 作业接口,继承并实现Execute, 编写执行的具体作业逻辑。
JobBuilder 根据设置,生成一个详细作业信息(JobDetail)。
TriggerBuilder 根据规则,生产对应的Trig

实战

  • Web.config配置

基础概念我就懒得讲了,你也懒得听了,直接上代码吧。

首先配置一下Web.config,其实这步可有可无,可以直接跳过,只是为了配置一些常量,获取固定的时间,但是不是必要的。

    <sectionGroup name="JobList">
<section name="Job" type="System.Configuration.NameValueSectionHandler, System, Version=1.0.5000.0,Culture=neutral, PublicKeyToken=b77a5c561934e089"/>
</sectionGroup>
  <JobList>
<Job>
<!--这里是一个任务节点-->
<add key="Url" value="http://www.baidu.com" />
<!--需要访问的Url-->
<add key="Hour" value="10" />
<!--开始时间小时-->
<!--开始时间小时,注意:这里的小时为0-23,如果是1点的话就是1,而不是01-->
<add key="Minute" value="30" />
<!--开始时间分钟-->
<!--开始时间分钟,注意:同上0-59-->
</Job>
</JobList>
  • 创建service

    创建ISystemSchedulerService以及SystemSchedulerService,代码上面都有详细的注释,我就不重复了。

    接口:
public  interface ISystemSchedulerService: IApplicationService
{
void StartScheduler();
}

service: SystemSchedulerService

    public class SystemSchedulerService : ISystemSchedulerService
{
private IScheduler _scheduler;
public ILogger _Logger { get; set; }
public SystemSchedulerService()
{
_Logger = NullLogger.Instance;
} public void StopScheduler()
{
_scheduler.Shutdown();
} public void StartScheduler()
{
try
{ //这里读取配置文件中的任务开始时间
int hour = int.Parse(((NameValueCollection)ConfigurationManager.GetSection("JobList/Job"))["Hour"]);
int minute = int.Parse(((NameValueCollection)ConfigurationManager.GetSection("JobList/Job"))["Minute"]); ISchedulerFactory schedulerFactory = new StdSchedulerFactory();//内存调度
_scheduler = schedulerFactory.GetScheduler(); //创建一个Job来执行特定的任务
IJobDetail myLogJob = new JobDetailImpl("myLogJob", typeof(MyLogJob));
//创建并定义触发器的规则(每天执行一次时间为:时:分)
ITrigger trigger =
TriggerBuilder.Create()
.WithDailyTimeIntervalSchedule(
a => a.WithIntervalInHours(24).OnEveryDay().StartingDailyAt(TimeOfDay.HourAndMinuteOfDay(hour, minute))).Build(); _scheduler.Clear();
//将创建好的任务和触发规则加入到Quartz中
_scheduler.ScheduleJob(myLogJob, trigger);
//开始
_scheduler.Start(); }
catch (Exception ex)
{
_Logger.Info(ex.Message);
}
} }

Quartz API

Quartz API的关键接口和类是:

  • IScheduler - 与调度程序交互的主要API。

    -IJob - 您希望由调度程序执行的组件实现的接口。
  • IJobDetail - 用于定义作业的实例。
  • ITrigger - 定义执行给定Job的时间表的组件。
  • JobBuilder - 用于定义/构建obDetail实例,它定义了Jobs的实例。
  • TriggerBuilder - 用于定义/构建触发器实例。

一个调度程序的生命周期是由它为界的创作,通过SchedulerFactory和其有关方法的调用。一旦创建了IScheduler接口,就可以使用添加,删除和列出作业和触发器,并执行其他与调度相关的操作(例如暂停触发器)。但是,调度程序不会实际上对任何触发器(执行作业)执行操作,直到使用Start()方法启动它。

构建作业定义的代码块使用使用流畅接口的JobBuilder创建产品IJobDetail。同样,构建触发器的代码块使用TriggerBuilder流畅的接口和特定于触发器类型的扩展方法。可能的时间延长方法是:

  • WithCalendarIntervalSchedule
  • WithCronSchedule
  • WithDailyTimeIntervalSchedule
  • WithSimpleSchedule

示例中的ITrigger 其实可以不读取配置信息的。这就是我说的不用配置Web.config也可以。效果如下,每隔10秒执行一次。

  ITrigger trigger =
TriggerBuilder.Create()
.WithDailyTimeIntervalSchedule(
a => a.WithIntervalInSeconds(10)).Build();

工作和触发器

代码:

  public class MyLogJob : JobBase, ITransientDependency
{
public ILogger _Logger { get; set; }
public MyLogJob()
{
_Logger = NullLogger.Instance; } public override void Execute(IJobExecutionContext context)
{
try
{
_Logger.Info("QuartzJob 任务开始运行"); for (int i = 0; i < 10; i++)
{
_Logger.InfoFormat("QuartzJob 正在运行{0}", i);
} _Logger.Info("QuartzJob任务运行结束");
}
catch (Exception ex)
{
_Logger.Error("运行异常:"+ex.Message, ex);
} }
}

当作业的触发器触发,Execute(..)方法由调度程序的工作线程之一调用。传递给此方法的JobExecutionContext对象为作业实例提供有关其“运行时”环境的信息 ( 调度程序执行的逻辑),触发执行的触发器的逻辑。

JobDetail对象是在Job添加到调度器时由Quartz.NET客户端创建的。它包含Job的各种属性设置,以及一个JobDataMap,它可以用来存储作业类的给定实例的状态信息

触发器对象用于触发作业的执行(或“触发”)。当你想安排一个工作,你实例化一个触发器并“调整”它的属性来提供你想要的调度。触发器也可能有一个与它们相关的JobDataMap - 这对于传递参数到一个特定于触发器的触发的Job是很有用的。Quartz提供了一些不同的触发器类型,但最常用的类型是SimpleTrigger(接口ISimpleTrigger)和CronTrigger(接口ICronTrigger)。

如果您需要“一次性”执行(在某个特定时间只执行一项作业),或者您需要在给定时间开始工作,并且重复执行N次,SimpleTrigger会很方便的T之间执行。如果您希望基于类似日历的时间表(例如“每个星期五,中午”或“每个月的第10天的10:15”)触发,则CronTrigger非常有用。

    public class UserInfoController : ABPCMSControllerBase
{ private readonly ISystemSchedulerService _iSystemSchedulerService;
public UserInfoController(ISystemSchedulerService iSystemSchedulerService)
{ _iSystemSchedulerService = iSystemSchedulerService;
} [HttpGet]
[DontWrapResult]
public async Task<ActionResult> GetUserInfo()
{
_iSystemSchedulerService.StartScheduler();
}
}

然后我们在前台控制器里面调用ISystemSchedulerService StartScheduler()方法。然后运行项目。

然后我们打开日志Web\App_Data\Logs\Logs.txt,看下效果:



每隔十秒钟执行一次。

Abp.Quartz

ABP有內建的持久化后台job队列和后台worker系统。如果对于后台workers你有更高级的计划安排需求,Quartz会是一个更好的选择。对于持久化后台job队列,Hangfire也是一个好的选择。

ABP中Quartz应用其实蛮简单的。

  1. 首先Abp.Quartz nuget包
  2. 然后加上引入到项目中。
   [DependsOn(
typeof(AbpQuartzModule)
)]
public class ABPCMSWebModule : AbpModule
{
}
  1. 创建Jobs

     这里我们就用上面例子中的job,当然创建一个新job,你可以实现QuartzIJob接口,或者继承JobBase类(定义在Abp.Quartz包),这个类包含一些帮助属性和方法(例如日志和本地化)。
public class MyLogJob : JobBase, ITransientDependency
{
public override void Execute(IJobExecutionContext context)
{
Logger.Info("Executed MyLogJob :)");
}
}

4.创建调度作业

IQuartzScheduleJobManager 接口被用来创建调度作业。你可以在类中注入该接口(或者你可以在你的模块的PostInitialize方法中解析和使用它)来调度作业。这里我们把之前创建的MyLogJob引用进来。

效果如下:

  public async Task<ActionResult> ScheduleJob()
{
await _jobManager.ScheduleAsync<MyLogJob>(
job =>
{
job.WithIdentity("MyLogJobIdentity", "MyGroup")
.WithDescription("A job to simply write logs.");
},
trigger =>
{
trigger.StartNow()
.WithSimpleSchedule(schedule =>
{
schedule.RepeatForever()
.WithIntervalInSeconds(5)
.Build();
});
});
_jobManager.Start();
return Content("OK, scheduled!");
}

效果一样。



经过上面四步,就完成Abp.Quartz运用,是挺简单的。

扩展

SimpleTrigger

说一下SimpleTriggerWithCronSchedule()这里有三种方式,一种就是上面说到的Web.config配置,一种是WithCronSchedule设置时间参数。还有一种是WithCronSchedule("") 拥有强大的Cron时间表达式。

SimpleTrigger应满足你的日程安排需求,如果你需要在某个特定时刻及时执行一次作业,或者在特定时间执行一次,然后在特定时间间隔重复执行。如果你想让触发器在2018年1月13日上午11点23分54秒执行,然后再执行5次,每10秒执行一次。

通过这个描述,你可能不会觉得奇怪的是,SimpleTrigger的属性包括:开始时间和结束时间,重复计数和重复间隔。所有这些属性都与您所期望的完全相同,只有一些与结束时间属性相关的特殊注释。

SimpleTrigger实例是使用TriggerBuilder(用于触发器的主属性)和WithSimpleSchedule扩展方法(用于SimpleTrigger特有的属性)构建的。

在特定的时刻建立一个触发器,不要重复:

// trigger builder creates simple trigger by default, actually an ITrigger is returned
ISimpleTrigger trigger = (ISimpleTrigger) TriggerBuilder.Create()
.WithIdentity("trigger1", "group1")
.StartAt(myStartTime) // some Date
.ForJob("job1", "group1") // identify job with name, group strings
.Build();

建立一个特定时刻的触发器,然后每十秒钟重复十次:

trigger = TriggerBuilder.Create()
.WithIdentity("trigger3", "group1")
.StartAt(myTimeToStartFiring) // if a start time is not given (if this line were omitted), "now" is implied
.WithSimpleSchedule(x => x
.WithIntervalInSeconds(10)
.WithRepeatCount(10)) // note that 10 repeats will give a total of 11 firings
.ForJob(myJob) // identify job with handle to its JobDetail itself
.Build();

建立一个触发器,将在未来五分钟内触发一次:

trigger = (ISimpleTrigger) TriggerBuilder.Create()
.WithIdentity("trigger5", "group1")
.StartAt(DateBuilder.FutureDate(5, IntervalUnit.Minute)) // use DateBuilder to create a date in the future
.ForJob(myJobKey) // identify job with its JobKey
.Build();

建立一个触发器,现在会触发,然后每隔五分钟重复一次,直到22:00:

trigger = TriggerBuilder.Create()
.WithIdentity("trigger7", "group1")
.WithSimpleSchedule(x => x
.WithIntervalInMinutes(5)
.RepeatForever())
.EndAt(DateBuilder.DateOf(22, 0, 0))
.Build();

建立一个触发器,将在下一个小时的顶部执行,然后每2小时重复一次:

trigger = TriggerBuilder.Create()
.WithIdentity("trigger8") // because group is not specified, "trigger8" will be in the default group
.StartAt(DateBuilder.EvenHourDate(null)) // get the next even-hour (minutes and seconds zero ("00:00"))
.WithSimpleSchedule(x => x
.WithIntervalInHours(2)
.RepeatForever())
// note that in this example, 'forJob(..)' is not called
// - which is valid if the trigger is passed to the scheduler along with the job
.Build(); await scheduler.scheduleJob(trigger, job);

另外一些常用的停止作业的指令常量

  • MisfireInstruction.IgnoreMisfirePolicy
  • MisfirePolicy.SimpleTrigger.FireNow
  • MisfirePolicy.SimpleTrigger.RescheduleNowWithExistingRepeatCount
  • MisfirePolicy.SimpleTrigger.RescheduleNowWithRemainingRepeatCount
  • MisfirePolicy.SimpleTrigger.RescheduleNextWithRemainingCount
  • MisfirePolicy.SimpleTrigger.RescheduleNextWithExistingCount

在构建SimpleTriggers时,可以将简单的时间表(通过SimpleSchedulerBuilder)指定为停止作业指令:

trigger = TriggerBuilder.Create()
.WithIdentity("trigger7", "group1")
.WithSimpleSchedule(x => x
.WithIntervalInMinutes(5)
.RepeatForever()
.WithMisfireHandlingInstructionNextWithExistingCount())
.Build();

Cron时间表达式。

Cron-Expressions用于配置CronTrigger的实例。Cron-Expressions是由七个子表达式组成的字符串,它们描述了计划的各个细节。这些子表达式用空格分隔,表示:

  • Seconds
  • Minutes
  • Hours
  • Day-of-Month
  • Month
  • Day-of-Week
  • Year (optional field)

示例Cron表达式

下面是一些表达式及其含义的例子 - 你可以在CronTrigger的API文档中找到更多的例子

CronTrigger示例1 - 创建一个触发器的表达式,每5分钟触发一次

"0 0/5 * * * ?"

CronTrigger示例2 - 一个表达式,用于创建在分钟后10秒(即上午10:00:10,上午10:05:10等)每5分钟触发一次的触发器。

"10 0/5 * * * ?"

CronTrigger示例3 - 一个表达式,用于在每个星期三和星期五的10:30,11:30,12:30和13:30创建一个触发器。

"0 30 10-13 ? * WED,FRI"

CronTrigger示例4 - 一个表达式,用于创建一个触发器,在每个月的第5天和第20天的上午8点到上午10点之间每隔半小时触发一次。请注意,触发器不会在上午10点,仅在8点,8点,9点和9点30分

"0 0/30 8-9 5,20 * ?"

请注意,一些调度要求过于复杂,无法用一个触发器来表示 - 例如“上午9点至上午10点之间每5分钟一次,下午1点至10点之间每20分钟一次”。在这种情况下解决方案是简单地创建两个触发器,并注册他们两个运行相同的工作。

CronTrigger实例使用TriggerBuilder(用于触发器的主属性)和WithCronSchedule扩展方法(用于CronTrigger特定的属性)构建。

您也可以使用CronScheduleBuilder的静态方法来创建计划。

建立一个触发器,每隔上午8点到下午5点,每隔一分钟一次:

trigger = TriggerBuilder.Create()
.WithIdentity("trigger3", "group1")
.WithCronSchedule("0 0/2 8-17 * * ?")
.ForJob("myJob", "group1")
.Build();

建立一个触发器,每天在上午10:42开始:

// we use CronScheduleBuilder's static helper methods here
trigger = TriggerBuilder.Create()
.WithIdentity("trigger3", "group1")
.WithSchedule(CronScheduleBuilder.DailyAtHourAndMinute(10, 42))
.ForJob(myJobKey)
.Build();

要么 -

trigger = TriggerBuilder.Create()
.WithIdentity("trigger3", "group1")
.WithCronSchedule("0 42 10 * * ?")
.ForJob("myJob", "group1")
.Build();

建立一个触发器,将在星期三上午10:42,在系统默认的时区以外的其他时间触发:

trigger = TriggerBuilder.Create()
.WithIdentity("trigger3", "group1")
.WithSchedule(CronScheduleBuilder
.WeeklyOnDayAndHourAndMinute(DayOfWeek.Wednesday, 10, 42)
.InTimeZone(TimeZoneInfo.FindSystemTimeZoneById("Central America Standard Time")))
.ForJob(myJobKey)
.Build();

要么 -

trigger = TriggerBuilder.Create()
.WithIdentity("trigger3", "group1")
.WithCronSchedule("0 42 10 ? * WED", x => x
.InTimeZone(TimeZoneInfo.FindSystemTimeZoneById("Central America Standard Time")))
.ForJob(myJobKey)
.Build();

以下指令可用于通知QuartzCronTrigger发生停止作业时应该执行的操作。(本教程的“更多关于触发器”部分介绍了停止作业情况)。这些指令被定义为常量(并且API文档具有对其行为的描述)。说明包括:

  • MisfireInstruction.IgnoreMisfirePolicy
  • MisfireInstruction.CronTrigger.DoNothing
  • MisfireInstruction.CronTrigger.FireOnceNow

所有触发器都有MisfireInstrution.SmartPolicy指令可供使用,并且此指令也是所有触发器类型的默认值。CronTrigger将“智能策略”指令解释为MisfireInstruction.CronTrigger.FireOnceNow。CronTrigger.UpdateAfterMisfire()方法的API文档解释了此行为的确切详细信息。

在构建CronTriggers时,您可以将缺火指令指定为cron时间表的一部分(通过WithCronSchedule扩展方法):

trigger = TriggerBuilder.Create()
.WithIdentity("trigger3", "group1")
.WithCronSchedule("0 0/2 8-17 * * ?", x => x
.WithMisfireHandlingInstructionFireAndProceed())
.ForJob("myJob", "group1")
.Build();

其他

我们可以看一下,源码中对IQuartzScheduleJobManagerScheduleAsync方法的封装,其实就是Scheduler.ScheduleJob处理了一下。

如果要使用模块的PostInitialize方法中解析和使用它来调度作业,也是可以的。

using System.Reflection;
using Abp.Dependency;
using Abp.Modules;
using Abp.Quartz.Configuration;
using Abp.Threading.BackgroundWorkers;
using Quartz; namespace Abp.Quartz
{
[DependsOn(typeof (AbpKernelModule))]
public class AbpQuartzModule : AbpModule
{
public override void PreInitialize()
{
IocManager.Register<IAbpQuartzConfiguration, AbpQuartzConfiguration>(); Configuration.Modules.AbpQuartz().Scheduler.JobFactory = new AbpQuartzJobFactory(IocManager);
} public override void Initialize()
{
IocManager.RegisterAssemblyByConvention(Assembly.GetExecutingAssembly());
} public override void PostInitialize()
{
IocManager.RegisterIfNot<IJobListener, AbpQuartzJobListener>(); Configuration.Modules.AbpQuartz().Scheduler.ListenerManager.AddJobListener(IocManager.Resolve<IJobListener>()); if (Configuration.BackgroundJobs.IsJobExecutionEnabled)
{
IocManager.Resolve<IBackgroundWorkerManager>().Add(IocManager.Resolve<IQuartzScheduleJobManager>());
}
}
}
}

JobBase封装的一些方法,继承自IJob

ABP+AdminLTE+Bootstrap Table权限管理系统一期

Github:https://github.com/Jimmey-Jiang/ABP-ASP.NET-Boilerplate-Project-CMS

ABP module-zero +AdminLTE+Bootstrap Table+jQuery权限管理系统第十七节--Quartz与ABP框架Abp.Quartz及扩展的更多相关文章

  1. ABP module-zero +AdminLTE+Bootstrap Table+jQuery权限管理系统第十二节--小结,Bootstrap Table之角色管理

    返回总目录:ABP+AdminLTE+Bootstrap Table权限管理系统一期 很多人说ABP不适合高并发大型,有一定的道理,但是我觉得还是可以的,就看架构师的能力了,哈哈,我之前公司就是ABP ...

  2. ABP module-zero +AdminLTE+Bootstrap Table+jQuery权限管理系统第十六节--SignalR与ABP框架Abp.Web.SignalR及扩展

    SignalR简介 SignalR是什么? ASP.NET SignalR 是为 ASP.NET 开发人员提供的一个库,可以简化开发人员将实时 Web 功能添加到应用程序的过程.实时 Web 功能是指 ...

  3. ABP module-zero +AdminLTE+Bootstrap Table+jQuery权限管理系统第十五节--缓存小结与ABP框架项目中 Redis Cache的实现

    返回总目录:ABP+AdminLTE+Bootstrap Table权限管理系统一期 缓存 为什么要用缓存 为什么要用缓存呢,说缓存之前先说使用缓存的优点. 减少寄宿服务器的往返调用(round-tr ...

  4. ABP module-zero +AdminLTE+Bootstrap Table+jQuery权限管理系统第十三节--RBAC模式及ABP权限管理(附送福利)

    ABP+AdminLTE+Bootstrap Table权限管理系统一期 Github:https://github.com/Jimmey-Jiang/ABP-ASP.NET-Boilerplate- ...

  5. ABP module-zero +AdminLTE+Bootstrap Table+jQuery权限管理系统第十四节--后台工作者HangFire与ABP框架Abp.Hangfire及扩展

    返回总目录:ABP+AdminLTE+Bootstrap Table权限管理系统一期 HangFire与Quartz.NET相比主要是HangFire的内置提供集成化的控制台,方便后台查看及监控,对于 ...

  6. ABP+AdminLTE+Bootstrap Table权限管理系统一期

       学而时习之,不亦说乎,温顾温知新,可以为师矣. 这也是算是一种学习的方法和态度吧,经常去学习和总结,在博客园看了很多大神的文章,写下一点对于ABP(ABP是“ASP.NET Boilerplat ...

  7. ABP+AdminLTE+Bootstrap Table权限管理系统第十一节--Bootstrap Table用户管理列表以及Module Zero之用户管理

    返回总目录:ABP+AdminLTE+Bootstrap Table权限管理系统一期 用户实体 用户实体代表应用的一个用户,它派生自AbpUser类,如下所示: public class User : ...

  8. ABP+AdminLTE+Bootstrap Table权限管理系统第十一节--bootstrap table之用户管理列表

    这张开始bootstrap table,引入项目有两种方法,一种是直接去官网下载 地址:http://bootstrap-table.wenzhixin.net.cn/ 另一种是Nuget引入. 然后 ...

  9. ABP+AdminLTE+Bootstrap Table权限管理系统第九节--AdminLTE模板页搭建

    AdminLTE 官网地址:https://adminlte.io/themes/AdminLTE/index2.html 首先去官网下载包下来,然后引入项目. 然后我们在web层添加区域Admin以 ...

随机推荐

  1. css3+div画大风车

    今天已经礼拜三了,周天小颖家的佩佩就要结婚啦,小颖要去当伴娘了,哈哈哈哈哈哈,想想都觉得乐开了花,不过之前她给我说让我当她伴娘时,我说我要减肥,不然她那么瘦弱,我站旁边就感觉像一个圆滚滚的小皮球,小颖 ...

  2. 2301: [HAOI2011]Problem b ( 分块+莫比乌斯反演+容斥)

    2301: [HAOI2011]Problem b Time Limit: 50 Sec  Memory Limit: 256 MBSubmit: 6015  Solved: 2741[Submit] ...

  3. Knight Moves

    Problem Description A friend of you is doing research on the Traveling Knight Problem (TKP) where yo ...

  4. Toxophily

    Problem Description The recreation center of WHU ACM Team has indoor billiards, Ping Pang, chess and ...

  5. 一键生成koa/koa2项目:

    一键生成koa/koa2项目: 1. npm install -g koa-generator 2.新建项目目录 koa mytest (koa1项目) koa2 koa2test (koa2项目) ...

  6. 密码学那些事———SHA-512及其C++实现

    SHA-512及其C++实现 转载请注明出处 一.引言 相信大家对于哈希压缩加密算法应该不陌生,在我们用微信或者支付宝接口的时候经常会遇到用这类算法加密,以验证数据的完整性.可以说这类算法无处不在,那 ...

  7. 关于隐藏元素高度的问题 css visibility:hidden 与 display:none的区别

    其实这是一个老问题了,s visibility:hidden 与 display:none 共同点就是都会似的元素不可见.但是 visibility:hidden 的DOM元素是占用空间的,会挤占其他 ...

  8. smm框架整合实现登录功能

    一.准备所需的jar包 1.1所需jar包 1.Spring框架jar包 2.Mybatis框架jar包 3.Spring的AOP事务jar包 4.Mybatis整合Spring中间件jar包 5.a ...

  9. css基础-选择器

    CSS选择符(选择器) 一.各类选择器 选择符表示要定义样式的对象,可以是元素本身,也可以是一类元素或者制定名称的元素. 常用的选择符有十种左右 类型选择符,id选择符,class选择符,通配符,群组 ...

  10. 用CRT查找内存泄漏

    引用原文地址 : https://msdn.microsoft.com/en-us/library/x98tx3cf.aspx 1. 在program中严格按下面顺序include #define _ ...