基于zookeeper和quartz实现分布式定时调度
yuyutoo 2024-10-16 15:46 2 浏览 0 评论
利用zookeeper的特性,来控制quartz实现分布式调度,保证quartz的单点运行,同时解除quartz自身分布式部署对数据库的依赖,保证同一时刻只有一个quartz应用在执行任务。
实现方式
利用zk的分布式独占锁,控制quartz应用执行节点,让拿到独占锁的quartz应用执行调度,没有拿到独占锁的quartz处理等待状态。
类图
核心代码
public class TriggerBean { /** * 标识 */ private String key; /** * 所属组 */ private String group; /** * 描述 */ private String description; /** * 启动时间 */ private String startTime; /** * 结束时间 */ private String endTime; /** * 优先级 */ private Integer priority; /** * 日历名称 */ private String calendarName; /** * 失火指令(参数0,1,2) * MISFIRE_INSTRUCTION_IGNORE_MISFIRE_POLICY = -1 * MISFIRE_INSTRUCTION_SMART_POLICY = 0 (默认) * MISFIRE_INSTRUCTION_FIRE_ONCE_NOW = 1 * MISFIRE_INSTRUCTION_DO_NOTHING = 2 */ private Integer misfireInstruction; /** * 任务代理类 */ private JobDetailProxyBean jobDetail; public String getKey() { return key; } public void setKey(String key) { this.key = key; } public String getGroup() { return group; } public void setGroup(String group) { this.group = group; } public String getDescription() { return description; } public void setDescription(String description) { this.description = description; } public String getStartTime() { return startTime; } public void setStartTime(String startTime) { this.startTime = startTime; } public String getEndTime() { return endTime; } public void setEndTime(String endTime) { this.endTime = endTime; } public Integer getPriority() { return priority; } public void setPriority(Integer priority) { this.priority = priority; } public String getCalendarName() { return calendarName; } public void setCalendarName(String calendarName) { this.calendarName = calendarName; } public Integer getMisfireInstruction() { return misfireInstruction; } public void setMisfireInstruction(Integer misfireInstruction) { this.misfireInstruction = misfireInstruction; } public JobDetailProxyBean getJobDetail() { return jobDetail; } public void setJobDetail(JobDetailProxyBean jobDetail) { this.jobDetail = jobDetail; } } public class CronTriggerBean extends TriggerBean { /** * CRON表达式 */ private String cronExpression; public String getCronExpression() { return cronExpression; } public void setCronExpression(String cronExpression) { this.cronExpression = cronExpression; } } public class SimpleTriggerBean extends TriggerBean { /** * 时间间隔(秒) */ private Integer interval; /** * 重复次数(默认:-1为无限循环) */ private Integer repeatCount; public Integer getInterval() { return interval; } public void setInterval(Integer interval) { this.interval = interval; } public Integer getRepeatCount() { return repeatCount; } public void setRepeatCount(Integer repeatCount) { this.repeatCount = repeatCount; } } public class SchedulerFactoryBean implements InitializingBean { protected static Logger logger = Logger.getLogger(SchedulerFactoryBean.class); /** * 触发器列表 */ private List<Object> triggers; /** * zooKeeper工厂 */ private ZookeeperFactory zooKeeperFactory; /** * Spring初始化方法 * @throws SchedulerException */ public void afterPropertiesSet() throws SchedulerException { this.initSchedulerFactory(); } /** * 初始化调度器工厂 * @throws SchedulerException */ public void initSchedulerFactory() throws SchedulerException { //初始化StdSchedulerFactory StdSchedulerFactory schedulerFactory = SchedulerUtils.initStdSchedulerFactory(); //获取调度器 Scheduler scheduler = schedulerFactory.getScheduler(); //装载调度器 for(Object triggerObject : this.getTriggers()){ if(triggerObject instanceof CronTriggerBean){ CronTriggerBean cronTriggerBean = (CronTriggerBean)triggerObject; //获取任务代理类对象 JobDetailProxyBean jobDetailProxyBean = cronTriggerBean.getJobDetail(); //装配任务 JobDetail jobDetail = SchedulerUtils.assemblyJobDetail(jobDetailProxyBean); //设置zooKeeper连接工厂 jobDetail.getJobDataMap().put("zooKeeperFactory",this.getZooKeeperFactory()); //装配触发器 CronTrigger cronTrigger = SchedulerUtils.assemblyCronTrigger(cronTriggerBean); scheduler.scheduleJob(jobDetail, cronTrigger); // System.out.println("CronTriggerBean"); }else{ SimpleTriggerBean simpleTriggerBean = (SimpleTriggerBean)triggerObject; //获取任务代理类对象 JobDetailProxyBean jobDetailProxyBean = simpleTriggerBean.getJobDetail(); //装配任务 JobDetail jobDetail = SchedulerUtils.assemblyJobDetail(jobDetailProxyBean); //设置zooKeeper连接工厂 jobDetail.getJobDataMap().put("zooKeeperFactory",this.getZooKeeperFactory()); //装配触发器 SimpleTrigger simpleTrigger = SchedulerUtils.assemblySimpleTrigger(simpleTriggerBean); scheduler.scheduleJob(jobDetail, simpleTrigger); // System.out.println("SimpleTriggerBean"); } } scheduler.start(); logger.info("调度器已启动"); } public List<Object> getTriggers() { return triggers; } public void setTriggers(List<Object> triggers) { this.triggers = triggers; } public ZookeeperFactory getZooKeeperFactory() { return zooKeeperFactory; } public void setZooKeeperFactory(ZookeeperFactory zooKeeperFactory) { this.zooKeeperFactory = zooKeeperFactory; } }
package com.ab.scheduling.quartz; import com.ab.scheduling.quartz.constant.Constant; import org.apache.curator.RetryPolicy; import org.apache.curator.framework.CuratorFramework; import org.apache.curator.framework.CuratorFrameworkFactory; import org.apache.curator.framework.state.ConnectionState; import org.apache.curator.framework.state.ConnectionStateListener; import org.apache.curator.retry.ExponentialBackoffRetry; import org.apache.log4j.Logger; import org.apache.zookeeper.CreateMode; import org.apache.zookeeper.KeeperException; import org.apache.zookeeper.ZooDefs; import org.springframework.beans.factory.InitializingBean; import java.util.Collections; import java.util.List; /** * Zookeeper 工厂类 * Date: 14-4-2 * Time: 下午4:03 */ public class ZookeeperFactory implements InitializingBean{ public static Logger logger = Logger.getLogger(ZookeeperFactory.class); /** * zookeeper服务地址 */ private String hosts; /** * 回话的超时时间(毫秒) */ private Integer sessionTimeOut; /** * 连接的超时时间(毫秒) */ private Integer connectionTimeOut; /** * 命名空间 **/ private String nameSpace; /** * zookeeper管理对象 */ private CuratorFramework zkTools; /** * 独享队列节点 */ private String monopolyQueueNode; /** * 连接状态 */ private String connectionState; /** * 会话ID */ private long sessionId; /** * Spring初始化方法 */ public void afterPropertiesSet(){ this.connection(); this.addListener(); } /** * 连接 */ public void connection(){ RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, Integer.MAX_VALUE); zkTools = CuratorFrameworkFactory .builder() .connectString(hosts) .namespace(nameSpace) .retryPolicy(retryPolicy) .connectionTimeoutMs(connectionTimeOut == null ? 30000 : connectionTimeOut) .sessionTimeoutMs(sessionTimeOut == null ? 30000 : sessionTimeOut) .build(); zkTools.start(); } /** * 连接状态监听 */ public void addListener(){ zkTools.getConnectionStateListenable().addListener(new ConnectionStateListener() { public void stateChanged(CuratorFramework client, ConnectionState newState) { if (newState.equals(ConnectionState.CONNECTED)) { logger.info("连接"); connectionState = "CONNECTED"; try { sessionId = zkTools.getZookeeperClient().getZooKeeper().getSessionId(); registerMonopolyQueue(); } catch (Exception e) { logger.error("注册独占队列失败"); } } if (newState.equals(ConnectionState.RECONNECTED)) { logger.info("重新连接"); connectionState = "CONNECTED"; try { if(sessionId != zkTools.getZookeeperClient().getZooKeeper().getSessionId()) { registerMonopolyQueue(); } } catch (Exception e) { logger.error("注册独占队列失败"); } } if (newState.equals(ConnectionState.LOST)) { logger.info("丢失"); connectionState = "LOST"; } if (newState.equals(ConnectionState.SUSPENDED)) { logger.info("暂停"); connectionState = "SUSPENDED"; } if (newState.equals(ConnectionState.READ_ONLY)) { logger.info("只读"); connectionState = "READ_ONLY"; } } }); } /** * 注册独占队列 */ private void registerMonopolyQueue() throws Exception { if(zkTools.checkExists().watched().forPath(Constant.MONOPOLY) == null){ zkTools.create() .withMode(CreateMode.PERSISTENT) .withACL(ZooDefs.Ids.OPEN_ACL_UNSAFE) .forPath(Constant.MONOPOLY); logger.info("创建独享锁队列节点成功!"); } if(monopolyQueueNode == null || (monopolyQueueNode != null && zkTools.checkExists().forPath(monopolyQueueNode)==null)) { monopolyQueueNode = zkTools.create() .withMode(CreateMode.EPHEMERAL_SEQUENTIAL) .withACL(ZooDefs.Ids.OPEN_ACL_UNSAFE) .forPath(Constant.MONOPOLY + Constant.SEPARATOR + Constant.QUEUE_NODE); logger.info("成功加入独享锁队列"); } } /** * 获得独占锁的执行权限 * @return 执行权限标识 * @throws KeeperException * @throws InterruptedException */ public boolean getMonopolyLock() throws Exception { boolean flag = false; if(connectionState != null && (connectionState.equals("CONNECTED") || connectionState.equals("RECONNECTED"))){ List<String> nodes = zkTools.getChildren().watched().forPath(Constant.MONOPOLY); if(nodes.size() > 0){ Collections.sort(nodes); //判断当前应用是否在队列的第一位 if((Constant.SEPARATOR + Constant.MONOPOLY + Constant.SEPARATOR + nodes.get(0)).equals(monopolyQueueNode)){ flag = true; } } } return flag; } /** * 关闭连接 */ public void close(){ if(zkTools != null){ zkTools.close(); zkTools = null; } } public String getHosts() { return hosts; } public void setHosts(String hosts) { this.hosts = hosts; } public Integer getSessionTimeOut() { return sessionTimeOut; } public void setSessionTimeOut(Integer sessionTimeOut) { this.sessionTimeOut = sessionTimeOut; } public Integer getConnectionTimeOut() { return connectionTimeOut; } public void setConnectionTimeOut(Integer connectionTimeOut) { this.connectionTimeOut = connectionTimeOut; } public String getNameSpace() { return nameSpace; } public void setNameSpace(String nameSpace) { this.nameSpace = nameSpace; } }
package com.ab.scheduling.quartz.common; import com.ab.scheduling.quartz.JobDetailProxyBean; import com.ab.scheduling.quartz.CronTriggerBean; import com.ab.scheduling.quartz.SimpleTriggerBean; import org.apache.commons.lang.StringUtils; import org.apache.log4j.Logger; import org.quartz.*; import org.quartz.impl.StdSchedulerFactory; import org.quartz.simpl.SimpleThreadPool; import java.util.Properties; /** * Quartz调度工具类 * Date: 14-5-15 * Time: 下午6:10 */ public class SchedulerUtils { protected static Logger logger = Logger.getLogger(SchedulerUtils.class); /** * 初始化StdSchedulerFactory * @return StdSchedulerFactory */ public static StdSchedulerFactory initStdSchedulerFactory() { StdSchedulerFactory schedulerFactory = null; try{ schedulerFactory = (StdSchedulerFactory) Class.forName(StdSchedulerFactory.class.getName()).newInstance(); Properties mergedProps = new Properties(); // 设置Quartz线程池设置 mergedProps.setProperty(StdSchedulerFactory.PROP_THREAD_POOL_CLASS, SimpleThreadPool.class.getName()); mergedProps.setProperty("org.quartz.threadPool.threadCount", Integer.toString(10)); schedulerFactory.initialize(mergedProps); } catch (Exception e){ logger.error("初始化StdSchedulerFactory失败"); logger.error(e); } return schedulerFactory; } /** * 装配任务 * @param jobDetail 任务代理类 * @return JobDetail */ public static JobDetail assemblyJobDetail(JobDetailProxyBean jobDetail){ JobBuilder jobBuilder = JobBuilder.newJob(jobDetail.getClass()); //设置JobDetail身份标识与所属组 String key = jobDetail.getKey(); if(StringUtils.isNotBlank(key)){ jobBuilder = jobBuilder.withIdentity(key, jobDetail.getGroup()); }else{ jobBuilder = jobBuilder.withIdentity(IdentityUtils.generatorUUID("JOB"), jobDetail.getGroup()); } //设置任务描述 if(StringUtils.isNotBlank(jobDetail.getDescription())){ jobBuilder = jobBuilder.withDescription(jobDetail.getDescription()); } //设置JobDetail数据参数 JobDataMap jobDataMap = new JobDataMap(); jobDataMap.put("targetObject",jobDetail.getTargetObject()); //目标对象 jobDataMap.put("targetMethod",jobDetail.getTargetMethod()); //目标方法 jobDataMap.put("mode", jobDetail.getMode()); //运行模式 jobBuilder = jobBuilder.usingJobData(jobDataMap); return jobBuilder.build(); } /** * 装配表达式触发器 * @param cronTriggerBean 表达式触发器 * @return 表达式触发器 */ public static CronTrigger assemblyCronTrigger(CronTriggerBean cronTriggerBean){ TriggerBuilder triggerBuilder = TriggerBuilder.newTrigger(); //设置触发器身份标识与所属组 String key = cronTriggerBean.getKey(); if(StringUtils.isNotBlank(key)){ triggerBuilder = triggerBuilder.withIdentity(key, cronTriggerBean.getGroup()); }else{ triggerBuilder = triggerBuilder.withIdentity(IdentityUtils.generatorUUID("CronTrigger"), cronTriggerBean.getGroup()); } //设置描述 if(StringUtils.isNotBlank(cronTriggerBean.getDescription())){ triggerBuilder = triggerBuilder.withDescription(cronTriggerBean.getDescription()); } //设置启动时间 if(StringUtils.isNotBlank(cronTriggerBean.getStartTime())){ triggerBuilder = triggerBuilder.startAt(DateUtils.StringToDate(cronTriggerBean.getStartTime(), "yyyy-MM-dd HH:mm:ss")); }else{ triggerBuilder = triggerBuilder.startNow(); //当启动时间为空默认立即启动调度器 } //设置结束时间 if(StringUtils.isNotBlank(cronTriggerBean.getEndTime())){ triggerBuilder = triggerBuilder.endAt(DateUtils.StringToDate(cronTriggerBean.getEndTime(), "yyyy-MM-dd HH:mm:ss")); } //设置优先级 if(cronTriggerBean.getPriority() != null){ triggerBuilder = triggerBuilder.withPriority(cronTriggerBean.getPriority()); } //设置Cron表达式(不允许为空)与集火指令 CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(cronTriggerBean.getCronExpression()); if(cronTriggerBean.getMisfireInstruction() != null){ if(cronTriggerBean.getMisfireInstruction() == Trigger.MISFIRE_INSTRUCTION_IGNORE_MISFIRE_POLICY) { cronScheduleBuilder = cronScheduleBuilder.withMisfireHandlingInstructionIgnoreMisfires(); } if(cronTriggerBean.getMisfireInstruction() == CronTrigger.MISFIRE_INSTRUCTION_FIRE_ONCE_NOW) { cronScheduleBuilder = cronScheduleBuilder.withMisfireHandlingInstructionFireAndProceed(); } if(cronTriggerBean.getMisfireInstruction() == CronTrigger.MISFIRE_INSTRUCTION_DO_NOTHING) { cronScheduleBuilder = cronScheduleBuilder.withMisfireHandlingInstructionDoNothing(); } } triggerBuilder = triggerBuilder.withSchedule(cronScheduleBuilder); return (CronTrigger)triggerBuilder.build(); } /** * 装配简单触发器 * @param simpleTriggerBean 简单触发器 * @return 简单触发器 */ public static SimpleTrigger assemblySimpleTrigger(SimpleTriggerBean simpleTriggerBean){ TriggerBuilder triggerBuilder = TriggerBuilder.newTrigger(); //设置触发器身份标识与所属组 String key = simpleTriggerBean.getKey(); if(StringUtils.isNotBlank(key)){ triggerBuilder = triggerBuilder.withIdentity(key, simpleTriggerBean.getGroup()); }else{ triggerBuilder = triggerBuilder.withIdentity(IdentityUtils.generatorUUID("SimpleTrigger"), simpleTriggerBean.getGroup()); } //设置描述 if(StringUtils.isNotBlank(simpleTriggerBean.getDescription())){ triggerBuilder = triggerBuilder.withDescription(simpleTriggerBean.getDescription()); } //设置启动时间 if(StringUtils.isNotBlank(simpleTriggerBean.getStartTime())){ triggerBuilder = triggerBuilder.startAt(DateUtils.StringToDate(simpleTriggerBean.getStartTime(), "yyyy-MM-dd HH:mm:ss")); }else{ triggerBuilder = triggerBuilder.startNow(); //当启动时间为空默认立即启动调度器 } //设置结束时间 if(StringUtils.isNotBlank(simpleTriggerBean.getEndTime())){ triggerBuilder = triggerBuilder.endAt(DateUtils.StringToDate(simpleTriggerBean.getEndTime(), "yyyy-MM-dd HH:mm:ss")); } //设置优先级 if(simpleTriggerBean.getPriority() != null){ triggerBuilder = triggerBuilder.withPriority(simpleTriggerBean.getPriority()); } //设置简单触发器 时间间隔(不允许为空)、执行次数(默认为-1)与集火指令 SimpleScheduleBuilder simpleScheduleBuilder = SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(20).withRepeatCount(-1); simpleScheduleBuilder = simpleScheduleBuilder.withIntervalInSeconds(simpleTriggerBean.getInterval()); if(simpleTriggerBean.getRepeatCount() != null){ simpleScheduleBuilder = simpleScheduleBuilder.withRepeatCount(simpleTriggerBean.getRepeatCount()); }else{ simpleScheduleBuilder = simpleScheduleBuilder.withRepeatCount(-1); } if(simpleTriggerBean.getMisfireInstruction() != null){ if(simpleTriggerBean.getMisfireInstruction() == Trigger.MISFIRE_INSTRUCTION_IGNORE_MISFIRE_POLICY) { simpleScheduleBuilder = simpleScheduleBuilder.withMisfireHandlingInstructionIgnoreMisfires(); } if(simpleTriggerBean.getMisfireInstruction() == SimpleTrigger.MISFIRE_INSTRUCTION_FIRE_NOW) { simpleScheduleBuilder = simpleScheduleBuilder.withMisfireHandlingInstructionFireNow(); } if(simpleTriggerBean.getMisfireInstruction() == SimpleTrigger.MISFIRE_INSTRUCTION_RESCHEDULE_NEXT_WITH_EXISTING_COUNT) { simpleScheduleBuilder = simpleScheduleBuilder.withMisfireHandlingInstructionNextWithExistingCount(); } if(simpleTriggerBean.getMisfireInstruction() == SimpleTrigger.MISFIRE_INSTRUCTION_RESCHEDULE_NEXT_WITH_REMAINING_COUNT) { simpleScheduleBuilder = simpleScheduleBuilder.withMisfireHandlingInstructionNextWithRemainingCount(); } if(simpleTriggerBean.getMisfireInstruction() == SimpleTrigger.MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_EXISTING_REPEAT_COUNT) { simpleScheduleBuilder = simpleScheduleBuilder.withMisfireHandlingInstructionNowWithExistingCount(); } if(simpleTriggerBean.getMisfireInstruction() == SimpleTrigger.MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_REMAINING_REPEAT_COUNT) { simpleScheduleBuilder = simpleScheduleBuilder.withMisfireHandlingInstructionNowWithRemainingCount(); } } triggerBuilder = triggerBuilder.withSchedule(simpleScheduleBuilder); return (SimpleTrigger)triggerBuilder.build(); } }
spring配置
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd" default-autowire="byName"> <!--定时任务实现类--> <bean id="test1" class="com.jd.scheduling.quartz.test.Test1"/> <!--任务代理--> <bean id="jobDetail1" class="com.ab.scheduling.quartz.JobDetailProxyBean"> <property name="targetObject" ref="test1"/> <property name="targetMethod" value="test"/> </bean> <!--触发器--> <bean id="cronTrigger" class="com.ab.scheduling.quartz.CronTriggerBean"> <property name="jobDetail" ref="jobDetail1"/> <property name="cronExpression" value="0/10 * * * * ?"/> </bean> <!--zk配置--> <bean id="zooKeeperFactory" class="com.ab.scheduling.quartz.ZookeeperFactory"> <property name="hosts" value="127.0.0.1:2181"/> <property name="sessionTimeOut" value="15000"/> <property name="nameSpace" value="zk-scheduling"/> </bean> <!--调度工厂--> <bean id="schdulerFactory" autowire="no" class="com.ab.scheduling.quartz.SchedulerFactoryBean"> <property name="triggers"> <list> <ref bean="cronTrigger" /> </list> </property> <property name="zooKeeperFactory" ref="zooKeeperFactory"/> </bean> </beans>
相关推荐
- jQuery VS AngularJS 你更钟爱哪个?
-
在这一次的Web开发教程中,我会尽力解答有关于jQuery和AngularJS的两个非常常见的问题,即jQuery和AngularJS之间的区别是什么?也就是说jQueryVSAngularJS?...
- Jquery实时校验,指定长度的「负小数」,小数位未满末尾补0
-
在可以输入【负小数】的输入框获取到焦点时,移除千位分隔符,在输入数据时,实时校验输入内容是否正确,失去焦点后,添加千位分隔符格式化数字。同时小数位未满时末尾补0。HTML代码...
- 如何在pbootCMS前台调用自定义表单?pbootCMS自定义调用代码示例
-
要在pbootCMS前台调用自定义表单,您需要在后台创建表单并为其添加字段,然后在前台模板文件中添加相关代码,如提交按钮和表单验证代码。您还可以自定义表单数据的存储位置、添加文件上传字段、日期选择器、...
- 编程技巧:Jquery实时验证,指定长度的「负小数」
-
为了保障【负小数】的正确性,做成了通过Jquery,在用户端,实时验证指定长度的【负小数】的方法。HTML代码<inputtype="text"class="forc...
- 一篇文章带你用jquery mobile设计颜色拾取器
-
【一、项目背景】现实生活中,我们经常会遇到配色的问题,这个时候去百度一下RGB表。而RGB表只提供相对于的颜色的RGB值而没有可以验证的模块。我们可以通过jquerymobile去设计颜色的拾取器...
- 编程技巧:Jquery实时验证,指定长度的「正小数」
-
为了保障【正小数】的正确性,做成了通过Jquery,在用户端,实时验证指定长度的【正小数】的方法。HTML做成方法<inputtype="text"class="fo...
- jquery.validate检查数组全部验证
-
问题:html中有多个name[],每个参数都要进行验证是否为空,这个时候直接用required:true话,不能全部验证,只要这个数组中有一个有值就可以通过的。解决方法使用addmethod...
- Vue进阶(幺叁肆):npm查看包版本信息
-
第一种方式npmviewjqueryversions这种方式可以查看npm服务器上所有的...
- layui中使用lay-verify进行条件校验
-
一、layui的校验很简单,主要有以下步骤:1.在form表单内加上class="layui-form"2.在提交按钮上加上lay-submit3.在想要校验的标签,加上lay-...
- jQuery是什么?如何使用? jquery是什么功能组件
-
jQuery于2006年1月由JohnResig在BarCampNYC首次发布。它目前由TimmyWilson领导,并由一组开发人员维护。jQuery是一个JavaScript库,它简化了客户...
- django框架的表单form的理解和用法-9
-
表单呈现...
- jquery对上传文件的检测判断 jquery实现文件上传
-
总体思路:在前端使用jquery对上传文件做部分初步的判断,验证通过的文件利用ajaxFileUpload上传到服务器端,并将文件的存储路径保存到数据库。<asp:FileUploadI...
- Nodejs之MEAN栈开发(四)-- form验证及图片上传
-
这一节增加推荐图书的提交和删除功能,来学习node的form提交以及node的图片上传功能。开始之前需要源码同学可以先在git上fork:https://github.com/stoneniqiu/R...
- 大数据开发基础之JAVA jquery 大数据java实战
-
上一篇我们讲解了JAVAscript的基础知识、特点及基本语法以及组成及基本用途,本期就给大家带来了JAVAweb的第二个知识点jquery,大数据开发基础之JAVAjquery,这是本篇文章的主要...
- 推荐四个开源的jQuery可视化表单设计器
-
jquery开源在线表单拖拉设计器formBuilder(推荐)jQueryformBuilder是一个开源的WEB在线html表单设计器,开发人员可以通过拖拉实现一个可视化的表单。支持表单常用控件...
你 发表评论:
欢迎- 一周热门
- 最近发表
- 标签列表
-
- mybatis plus (70)
- scheduledtask (71)
- css滚动条 (60)
- java学生成绩管理系统 (59)
- 结构体数组 (69)
- databasemetadata (64)
- javastatic (68)
- jsp实用教程 (53)
- fontawesome (57)
- widget开发 (57)
- vb net教程 (62)
- hibernate 教程 (63)
- case语句 (57)
- svn连接 (74)
- directoryindex (69)
- session timeout (58)
- textbox换行 (67)
- extension_dir (64)
- linearlayout (58)
- vba高级教程 (75)
- iframe用法 (58)
- sqlparameter (59)
- trim函数 (59)
- flex布局 (63)
- contextloaderlistener (56)