工作流直译就是工作的流程,但是怎样设计与实现并应用到程序中还是有一些难度,不然为什么有些公司可以靠工作流起家呢?其中大量的逻辑充斥其中,稍不注意就会出错。
本次在Eclipse中使用了Activity插件,方便使用。
1.首先引入工作流相关的pom包,由于其中会有冲突,我将所有的都贴出来:
<properties> <c3p0.version>0.9.1.2</c3p0.version> <!-- spring版本号 --> <spring.version>4.1.9.RELEASE</spring.version> <!-- mybatis版本号 --> <mybatis.version>3.2.6</mybatis.version> <!-- log4j日志文件管理包版本 --> <slf4j.version>1.7.7</slf4j.version> <log4j.version>1.2.17</log4j.version> <cxf.version>2.2.3</cxf.version> <activti.engine.version>6.0.0</activti.engine.version> </properties> <dependencies> <!-- c3p0 --> <dependency> <groupId>c3p0</groupId> <artifactId>c3p0</artifactId> <version>${c3p0.version}</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.11</version> <!-- 表示开发的时候引入,发布的时候不会加载此包 --> <scope>test</scope> </dependency> <dependency> <groupId>cglib</groupId> <artifactId>cglib</artifactId> <version>3.2.5</version> <exclusions> <exclusion> <groupId>org.ow2.asm</groupId> <artifactId>asm</artifactId> </exclusion> </exclusions> </dependency> <!-- spring核心包 --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-core</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-web</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-oxm</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-tx</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aop</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context-support</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-orm</artifactId> <version>${spring.version}</version> </dependency> <!-- spring mvc aop --> <dependency> <groupId>aspectj</groupId> <artifactId>aspectjrt</artifactId> <version>1.5.3</version> </dependency> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.8.4</version> </dependency> <dependency> <groupId>aopalliance</groupId> <artifactId>aopalliance</artifactId> <version>1.0</version> </dependency> <!-- mybatis核心包 --> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.4.1</version> </dependency> <!-- mybatis/spring包 --> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis-spring</artifactId> <version>1.3.2</version> </dependency> <!-- 导入java ee jar 包 --> <dependency> <groupId>javax</groupId> <artifactId>javaee-api</artifactId> <version>7.0</version> <scope>provided</scope> </dependency> <!-- 导入Mysql数据库链接jar包 --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.30</version> </dependency> <!-- 导入dbcp的jar包,用来在applicationContext.xml中配置数据库 --> <dependency> <groupId>commons-dbcp</groupId> <artifactId>commons-dbcp</artifactId> <version>1.2.2</version> </dependency> <!-- JSTL标签类 --> <dependency> <groupId>jstl</groupId> <artifactId>jstl</artifactId> <version>1.2</version> </dependency> <!-- 日志文件管理包 --> <!-- log start --> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>${log4j.version}</version> </dependency> <!-- 格式化对象,方便输出日志 --> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.1.41</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>${slf4j.version}</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-log4j12</artifactId> <version>${slf4j.version}</version> </dependency> <!-- log end --> <!-- 映入JSON --> <dependency> <groupId>org.codehaus.jackson</groupId> <artifactId>jackson-mapper-asl</artifactId> <version>1.9.13</version> </dependency> <!-- 上传组件包 --> <dependency> <groupId>commons-fileupload</groupId> <artifactId>commons-fileupload</artifactId> <version>1.3.1</version> </dependency> <dependency> <groupId>commons-io</groupId> <artifactId>commons-io</artifactId> <version>2.4</version> </dependency> <dependency> <groupId>commons-codec</groupId> <artifactId>commons-codec</artifactId> <version>1.9</version> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-core</artifactId> <version>2.1.0</version> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.1.0</version> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-annotations</artifactId> <version>2.1.0</version> </dependency> <dependency> <groupId>net.sf.ehcache</groupId> <artifactId>ehcache</artifactId> <version>2.4.3</version> <type>pom</type> </dependency> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-ehcache</artifactId> <version>5.2.10.Final</version> </dependency> <dependency> <groupId>com.github.pagehelper</groupId> <artifactId>pagehelper</artifactId> <version>4.1.4</version> </dependency> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-lang3</artifactId> <version>3.8.1</version> </dependency> <!-- webservice --> <dependency> <groupId>org.apache.cxf</groupId> <artifactId>cxf-rt-frontend-jaxws</artifactId> <version>${cxf.version}</version> </dependency> <dependency> <groupId>org.apache.cxf</groupId> <artifactId>cxf-rt-transports-http</artifactId> <version>${cxf.version}</version> </dependency> <!-- 工作流 --> <dependency> <groupId>org.activiti</groupId> <artifactId>activiti-engine</artifactId> <version>${activti.engine.version}</version> <exclusions> <exclusion> <groupId>de.odysseus.juel</groupId> <artifactId>juel-spi</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>org.activiti</groupId> <artifactId>activiti-spring</artifactId> <version>${activti.engine.version}</version> </dependency> </dependencies>
2. 写入相关的jdbc.properties和spring-mvc-activiti.xml
#this properties -- spring-mybatis.xml&config.xml ,so some properties are distinct. mysql.driverclass=com.mysql.jdbc.Driver mysql.jdbcurl=jdbc:mysql://localhost:3306/repairsystem?characterEncoding=utf-8 mysql.user=root mysql.driver=com.mysql.jdbc.Driver mysql.url=jdbc:mysql://localhost:3306/repairsystem?useUnicode=true&characterEncoding=utf-8 mysql.username=root mysql.password=123456 mysql.initialSize=0 mysql.maxActive=20 mysql.maxIdle=20 mysql.minIdle=1 mysql.maxWait=60000 jdbc.activiti.driverClass=com.mysql.jdbc.Driver jdbc.activiti.jdbcUrl=jdbc:mysql://localhost:3306/activitidb?useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull jdbc.activiti.user=root jdbc.activiti.password=123456 # basic confog jdbc.minPoolSize=10 jdbc.maxPoolSize=10 jdbc.initialPoolSize=5 jdbc.acquireIncrement=5 # manage connection config jdbc.maxIdleTime=1800 jdbc.maxConnectionAge=7200 jdbc.maxIdleTimeExcessConnections=0 # connection test config, timeunit second jdbc.idleConnectionTestPeriod=600 jdbc.testConnectionOnCheckin=true jdbc.testConnectionOnCheckout=false jdbc.preferredTestQuery=SELECT 1 FROM DUAL # recovery from database outages config, timeunit millisecond jdbc.acquireRetryAttempts=5 jdbc.acquireRetryDelay=1000 jdbc.breakAfterAcquireFailure=false # statement pool config TODO jdbc.maxStatements=0 jdbc.maxStatementsPerConnection=0 jdbc.statementCacheNumDeferredCloseThreads=0 # connection leak config jdbc.debugUnreturnedConnectionStackTraces=false jdbc.unreturnedConnectionTimeout=0 # other config jdbc.checkoutTimeout=10000 jdbc.numHelperThreads=3
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd"> <context:property-placeholder location="classpath*:jdbc.properties" ignore-unresolvable="false" /> <bean id="transactionManager_activiti" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource_activiti"></property> </bean> <!-- activiti datasource --> <bean id="dataSource_activiti" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close"> <property name="driverClass" value="${jdbc.activiti.driverClass}"/> <property name="jdbcUrl" value="${jdbc.activiti.jdbcUrl}"/> <property name="user" value="${jdbc.activiti.user}"/> <property name="password" value="${jdbc.activiti.password}"/> <property name="minPoolSize" value="${jdbc.minPoolSize}"/> <property name="maxPoolSize" value="${jdbc.maxPoolSize}"/> <property name="initialPoolSize" value="${jdbc.initialPoolSize}"/> <property name="acquireIncrement" value="${jdbc.acquireIncrement}"/> <property name="maxIdleTime" value="${jdbc.maxIdleTime}"/> <property name="maxConnectionAge" value="${jdbc.maxConnectionAge}"/> <property name="maxIdleTimeExcessConnections" value="${jdbc.maxIdleTimeExcessConnections}"/> <property name="idleConnectionTestPeriod" value="${jdbc.idleConnectionTestPeriod}"/> <property name="testConnectionOnCheckin" value="${jdbc.testConnectionOnCheckin}"/> <property name="testConnectionOnCheckout" value="${jdbc.testConnectionOnCheckout}"/> <property name="preferredTestQuery" value="${jdbc.preferredTestQuery}"/> <property name="acquireRetryAttempts" value="${jdbc.acquireRetryAttempts}"/> <property name="acquireRetryDelay" value="${jdbc.acquireRetryDelay}"/> <property name="checkoutTimeout" value="${jdbc.checkoutTimeout}"/> <property name="numHelperThreads" value="${jdbc.numHelperThreads}"/> </bean> <bean id="processEngineConfiguration" class="org.activiti.spring.SpringProcessEngineConfiguration"> <property name="dataSource" ref="dataSource_activiti"/> <property name="transactionManager" ref="transactionManager_activiti"/> <property name="databaseSchemaUpdate" value="true"/> <property name="asyncExecutorActivate" value="true"/> <property name="deploymentResources" value="classpath*:activiti/*.bpmn"/> <!-- <property name="jobExecutorActivate" value="false" /> --> <property name="activityFontName" value="宋体"/> <property name="labelFontName" value="宋体"/> <property name="annotationFontName" value="宋体"/> <!-- mail --> <property name="mailServerHost" value="localhost"/> <property name="mailServerUsername" value="kafeitu"/> <property name="mailServerPassword" value="000000"/> <property name="mailServerPort" value="2025"/> <!-- 缓存支持 <property name="processDefinitionCache"> <bean class="me.kafeitu.demo.activiti.util.cache.DistributedCache" /> </property>--> <!-- 自定义表单字段类型 --> <property name="customFormTypes"> <list> <!-- <bean class="me.kafeitu.demo.activiti.activiti.form.UsersFormType"/> --> </list> </property> <!-- JPA --> <!-- <property name="jpaEntityManagerFactory" ref="entityManagerFactory" /> --> <!-- <property name="jpaHandleTransaction" value="false" /> --> <!-- <property name="jpaCloseEntityManager" value="false" /> --> <!-- 全局事件 --> <property name="typedEventListeners"> <map> <entry key="VARIABLE_CREATED" > <list> <!-- <ref bean="variableCreateListener"/> --> </list> </entry> </map> </property> </bean> <bean id="processEngine" class="org.activiti.spring.ProcessEngineFactoryBean"> <property name="processEngineConfiguration" ref="processEngineConfiguration"/> </bean> <bean id="repositoryService" factory-bean="processEngine" factory-method="getRepositoryService"/> <bean id="runtimeService" factory-bean="processEngine" factory-method="getRuntimeService"/> <bean id="taskService" factory-bean="processEngine" factory-method="getTaskService"/> <bean id="historyService" factory-bean="processEngine" factory-method="getHistoryService"/> <bean id="managementService" factory-bean="processEngine" factory-method="getManagementService"/> <bean id="identityService" factory-bean="processEngine" factory-method="getIdentityService"/> </beans>
3. 准备工作中还需要添加一个activitiDB数据库,这个好像是activiti自带可以生成的。
package clack.activity; import org.activiti.engine.ProcessEngine; import org.activiti.engine.ProcessEngineConfiguration; import org.junit.Test; public class GenDB { public void createActivitiEngineByCode() { /* * *1.通过代码形式创建 - 取得ProcessEngineConfiguration对象 - 设置数据库连接属性 - 设置创建表的策略 * (当没有表时,自动创建表) - 通过ProcessEngineConfiguration对象创建 ProcessEngine 对象 */ // 取得ProcessEngineConfiguration对象 ProcessEngineConfiguration engineConfiguration = ProcessEngineConfiguration .createStandaloneProcessEngineConfiguration(); // 设置数据库连接属性 engineConfiguration.setJdbcDriver("com.mysql.jdbc.Driver"); engineConfiguration.setJdbcUrl("jdbc:mysql://localhost:3306/activitiDB?createDatabaseIfNotExist=true" + "&useUnicode=true&characterEncoding=utf8"); engineConfiguration.setJdbcUsername("root"); engineConfiguration.setJdbcPassword("123456"); // 设置创建表的策略 (当没有表时,自动创建表) // public static final java.lang.String DB_SCHEMA_UPDATE_FALSE = // "false";//不会自动创建表,没有表,则抛异常 // public static final java.lang.String DB_SCHEMA_UPDATE_CREATE_DROP = // "create-drop";//先删除,再创建表 // public static final java.lang.String DB_SCHEMA_UPDATE_TRUE = // "true";//假如没有表,则自动创建 engineConfiguration.setDatabaseSchemaUpdate("true"); // 通过ProcessEngineConfiguration对象创建 ProcessEngine 对象 ProcessEngine processEngine = engineConfiguration.buildProcessEngine(); System.out.println("流程引擎创建成功!"); } public void createActivitiEngine() { ProcessEngineConfiguration engineConfiguration = ProcessEngineConfiguration .createProcessEngineConfigurationFromResource("spring-context-activiti.xml"); // 从类加载路径中查找资源 activiti.cfg.xm文件名可以自定义 ProcessEngine processEngine = engineConfiguration.buildProcessEngine(); System.out.println("使用配置文件Activiti.cfg.xml获取流程引擎"); } public static void main(String[] args) { new GenDB().createActivitiEngineByCode(); } }
4. 接着在activiti视图中创建一张自定义的流程图,并添加相应的监听类,用xml编辑器打开如下:
<?xml version="1.0" encoding="UTF-8"?> <definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:activiti="http://activiti.org/bpmn" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:omgdc="http://www.omg.org/spec/DD/20100524/DC" xmlns:omgdi="http://www.omg.org/spec/DD/20100524/DI" typeLanguage="http://www.w3.org/2001/XMLSchema" expressionLanguage="http://www.w3.org/1999/XPath" targetNamespace="http://www.activiti.org/test"> <process id="apply" name="apply" isExecutable="true"> <startEvent id="startevent1" name="Start" activiti:initiator="userId"> <extensionElements> <activiti:executionListener event="start" class="clack.activity.listener.MyExecutionListener"></activiti:executionListener> </extensionElements> </startEvent> <userTask id="usertask1" name="组长审批" activiti:assignee="${userId}" xmlns:activiti="http://activiti.org/bpmn" activiti:class="clack.activity.task.TeamApplyServiceTask"></userTask> <userTask id="usertask2" name="经理审批" activiti:assignee="${userId}" xmlns:activiti="http://activiti.org/bpmn" activiti:class="clack.activity.task.ManagerApplyServiceTask"></userTask> <serviceTask id="servicetask1" name="人事归档" activiti:class="clack.activity.task.HumanResouceServiceTask"></serviceTask> <endEvent id="endevent1" name="End"> <extensionElements> <activiti:executionListener event="start" class="clack.activity.listener.MyExecutionListener"></activiti:executionListener> </extensionElements> </endEvent> <sequenceFlow id="flow1" sourceRef="startevent1" targetRef="usertask1"></sequenceFlow> <sequenceFlow id="flow2" sourceRef="usertask1" targetRef="usertask2"></sequenceFlow> <sequenceFlow id="flow3" sourceRef="usertask2" targetRef="servicetask1"></sequenceFlow> <sequenceFlow id="flow4" sourceRef="servicetask1" targetRef="endevent1"></sequenceFlow> </process> <bpmndi:BPMNDiagram id="BPMNDiagram_apply"> <bpmndi:BPMNPlane bpmnElement="apply" id="BPMNPlane_apply"> <bpmndi:BPMNShape bpmnElement="startevent1" id="BPMNShape_startevent1"> <omgdc:Bounds height="35.0" width="35.0" x="270.0" y="260.0"></omgdc:Bounds> </bpmndi:BPMNShape> <bpmndi:BPMNShape bpmnElement="usertask1" id="BPMNShape_usertask1"> <omgdc:Bounds height="55.0" width="105.0" x="410.0" y="250.0"></omgdc:Bounds> </bpmndi:BPMNShape> <bpmndi:BPMNShape bpmnElement="usertask2" id="BPMNShape_usertask2"> <omgdc:Bounds height="55.0" width="105.0" x="650.0" y="250.0"></omgdc:Bounds> </bpmndi:BPMNShape> <bpmndi:BPMNShape bpmnElement="servicetask1" id="BPMNShape_servicetask1"> <omgdc:Bounds height="55.0" width="105.0" x="880.0" y="250.0"></omgdc:Bounds> </bpmndi:BPMNShape> <bpmndi:BPMNShape bpmnElement="endevent1" id="BPMNShape_endevent1"> <omgdc:Bounds height="35.0" width="35.0" x="1100.0" y="260.0"></omgdc:Bounds> </bpmndi:BPMNShape> <bpmndi:BPMNEdge bpmnElement="flow1" id="BPMNEdge_flow1"> <omgdi:waypoint x="305.0" y="277.0"></omgdi:waypoint> <omgdi:waypoint x="410.0" y="277.0"></omgdi:waypoint> </bpmndi:BPMNEdge> <bpmndi:BPMNEdge bpmnElement="flow2" id="BPMNEdge_flow2"> <omgdi:waypoint x="515.0" y="277.0"></omgdi:waypoint> <omgdi:waypoint x="650.0" y="277.0"></omgdi:waypoint> </bpmndi:BPMNEdge> <bpmndi:BPMNEdge bpmnElement="flow3" id="BPMNEdge_flow3"> <omgdi:waypoint x="755.0" y="277.0"></omgdi:waypoint> <omgdi:waypoint x="880.0" y="277.0"></omgdi:waypoint> </bpmndi:BPMNEdge> <bpmndi:BPMNEdge bpmnElement="flow4" id="BPMNEdge_flow4"> <omgdi:waypoint x="985.0" y="277.0"></omgdi:waypoint> <omgdi:waypoint x="1100.0" y="277.0"></omgdi:waypoint> </bpmndi:BPMNEdge> </bpmndi:BPMNPlane> </bpmndi:BPMNDiagram> </definitions>
其中对应的一些监听类及任务类如下:
package clack.activity.listener; import org.activiti.engine.delegate.DelegateExecution; import org.activiti.engine.delegate.ExecutionListener; /** *@description executionListern主要用于流程的开始、结束和连线的监听 * 共有三个值:"start"、"end"、"take"。 * 其中start和end用于整个流程的开始和结束,take用于连线 *@auth panmingshuai *@time 2018年4月5日下午8:59:16 * */ public class MyExecutionListener implements ExecutionListener{ /** * */ private static final long serialVersionUID = 1L; @Override public void notify(DelegateExecution execution){ //得到现在事件阶段的值,用"start".endsWith(eventName)来判断 String eventName = execution.getEventName(); if("start".endsWith(eventName)){ System.out.println("-------------------流程开始-------------------"); } else if("end".equals(eventName)){ System.out.println("-------------------流程结束-------------------"); } } }
package clack.activity.task; import org.activiti.engine.delegate.DelegateExecution; import org.activiti.engine.delegate.JavaDelegate; public class TeamApplyServiceTask implements JavaDelegate { @Override public void execute(DelegateExecution execution) { // TODO Auto-generated method stub } }
package clack.activity.task; import org.activiti.engine.delegate.DelegateExecution; import org.activiti.engine.delegate.JavaDelegate; public class ManagerApplyServiceTask implements JavaDelegate { @Override public void execute(DelegateExecution execution) { // TODO Auto-generated method stub // String eventName = execution.getEventName(); // if("start".endsWith(eventName)){ // System.out.println("-------------------流程开始-------------------"); // } else if("end".equals(eventName)){ // System.out.println("-------------------流程结束-------------------"); // } } }
package clack.activity.task; import org.activiti.engine.delegate.DelegateExecution; import org.activiti.engine.delegate.JavaDelegate; public class HumanResouceServiceTask implements JavaDelegate { @Override public void execute(DelegateExecution execution) { // TODO Auto-generated method stub } }
5. 接着处理部署方法,由于在一个流程中仅需要部署一次(部署多次没有意义),所以需要将部署方法剥离出来放到监听器中,在运行了一次后将其注释掉,相关的初始部署方法、监听器方法、以及web.xml中的改动如下:
package clack.init; import java.io.File; import java.io.InputStream; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import org.activiti.bpmn.model.BpmnModel; import org.activiti.bpmn.model.FlowNode; import org.activiti.bpmn.model.SequenceFlow; import org.activiti.engine.HistoryService; import org.activiti.engine.IdentityService; import org.activiti.engine.ProcessEngine; import org.activiti.engine.ProcessEngineConfiguration; import org.activiti.engine.RepositoryService; import org.activiti.engine.RuntimeService; import org.activiti.engine.TaskService; import org.activiti.engine.history.HistoricActivityInstance; import org.activiti.engine.history.HistoricProcessInstance; import org.activiti.engine.impl.RepositoryServiceImpl; import org.activiti.engine.impl.persistence.entity.ProcessDefinitionEntity; import org.activiti.engine.repository.Deployment; import org.activiti.engine.runtime.ProcessInstance; import org.activiti.image.ProcessDiagramGenerator; import org.apache.commons.io.FileUtils; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; public class ActivityInit { public static ProcessEngine processEngine; // 得到流程存储服务实例 public static RepositoryService repositoryService; // 得到运行时服务组件 public static RuntimeService runtimeService; // 得到历史服务组件 public static HistoryService historyService; // 用户 分组信息服务 可以不使用 public static IdentityService identityService; public static TaskService taskService; static { // ProcessEngineConfiguration engineConfiguration = // ProcessEngineConfiguration // .createProcessEngineConfigurationFromResource("spring-context-activiti.xml"); ProcessEngineConfiguration engineConfiguration = ProcessEngineConfiguration .createStandaloneProcessEngineConfiguration(); // 设置数据库连接属性 engineConfiguration.setJdbcDriver("com.mysql.jdbc.Driver"); engineConfiguration.setJdbcUrl("jdbc:mysql://localhost:3306/activitiDB?createDatabaseIfNotExist=true" + "&useUnicode=true&characterEncoding=utf8"); engineConfiguration.setJdbcUsername("root"); engineConfiguration.setJdbcPassword("123456"); engineConfiguration.setActivityFontName("宋体"); engineConfiguration.setAnnotationFontName("宋体"); engineConfiguration.setLabelFontName("宋体"); engineConfiguration.setDatabaseSchemaUpdate("true"); // 通过ProcessEngineConfiguration对象创建 ProcessEngine 对象 processEngine = engineConfiguration.buildProcessEngine(); // 仓储服务 repositoryService = processEngine.getRepositoryService(); // 得到运行时服务组件 runtimeService = processEngine.getRuntimeService(); // 得到历史服务组件 historyService = processEngine.getHistoryService(); identityService = processEngine.getIdentityService(); taskService = processEngine.getTaskService(); } // 1.参考模板 启动流程 会写入相关数据库,key为自定义编号 public static Deployment deployeeProcess(String bpmnName, String key) { // 指定执行我们刚才部署的工作流程 // RepositoryService repositoryService = // processEngine.getRepositoryService(); Deployment deployment = repositoryService.createDeployment()// 创建一个部署的构建器 .addClasspathResource("clack/activity/" + bpmnName + ".bpmn")// 从类路径中添加资源,一次只能添加一个资源 .name("项目审批")// 设置部署的名称 .category("审批类别")// 设置部署的类别 .key(key).deploy(); org.springframework.web.context.ContextLoaderListener xx; System.out.println("部署的id" + deployment.getId()); System.out.println("部署的名称" + deployment.getName()); return deployment; } }
package clack.activity.listener; import javax.servlet.ServletContextEvent; import javax.servlet.ServletContextListener; import clack.init.ActivityInit; public class ApplyListener implements ServletContextListener { //1.静态的启动部分放在init中,该方法用于部署工作流,目前仅需要启动服务时配置一次,所以 //只需要第一次启动时加载ApplyListener中的类,过后注释掉。 @Override public void contextInitialized(ServletContextEvent sce) { // TODO Auto-generated method stub //ActivityInit.deployeeProcess("apply", "user001"); } @Override public void contextDestroyed(ServletContextEvent sce) { // TODO Auto-generated method stub } }
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://xmlns.jcp.org/xml/ns/javaee" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" id="WebApp_ID" version="3.1"> <display-name>Archetype Created Web Application</display-name> <context-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:spring-context*.xml</param-value> </context-param> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> <listener> <listener-class>org.springframework.web.context.request.RequestContextListener</listener-class> </listener> <listener> <listener-class>clack.activity.listener.ApplyListener</listener-class> </listener> <!--注意,由于activiti.xml应该在springmvc中启动,所以已经改名让SpringMVC访问读取-->
6. 然后就是Controller层与JSP页面的交互,大量的逻辑就是在这个文件中,里面还包含了同时将流程图例绘制到服务器上和本地磁盘上,耗时耗力,并在处理activiti数据库中不同的ID之间的对应与取值浪费了许多时间,
package clack.controller; import java.io.File; import java.io.InputStream; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; import javax.servlet.http.HttpServletRequest; import org.activiti.bpmn.model.BpmnModel; import org.activiti.bpmn.model.FlowNode; import org.activiti.bpmn.model.SequenceFlow; import org.activiti.engine.HistoryService; import org.activiti.engine.IdentityService; import org.activiti.engine.ProcessEngine; import org.activiti.engine.ProcessEngineConfiguration; import org.activiti.engine.RepositoryService; import org.activiti.engine.RuntimeService; import org.activiti.engine.TaskService; import org.activiti.engine.history.HistoricActivityInstance; import org.activiti.engine.history.HistoricProcessInstance; import org.activiti.engine.impl.RepositoryServiceImpl; import org.activiti.engine.impl.persistence.entity.ProcessDefinitionEntity; import org.activiti.engine.repository.Deployment; import org.activiti.engine.runtime.ProcessInstance; import org.activiti.engine.task.Task; import org.activiti.image.ProcessDiagramGenerator; import org.apache.commons.io.FileUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody; @Controller @RequestMapping("/apply") public class ActivityController { private String savePath = "activity"; public String getSavePath() { return savePath; } public void setSavePath(String savePath) { this.savePath = savePath; } @Autowired public ProcessEngine processEngine; // 得到流程存储服务实例 @Autowired public RepositoryService repositoryService; // 得到运行时服务组件 @Autowired public RuntimeService runtimeService; // 得到历史服务组件 @Autowired public HistoryService historyService; // 用户 分组信息服务 可以不使用 @Autowired public IdentityService identityService; @Autowired public TaskService taskService; // static { // // // ProcessEngineConfiguration engineConfiguration = // // ProcessEngineConfiguration // // // .createProcessEngineConfigurationFromResource("spring-context-activiti.xml"); // // ProcessEngineConfiguration engineConfiguration = // ProcessEngineConfiguration // .createStandaloneProcessEngineConfiguration(); // // 设置数据库连接属性 // engineConfiguration.setJdbcDriver("com.mysql.jdbc.Driver"); // engineConfiguration.setJdbcUrl("jdbc:mysql://localhost:3306/activitiDB?createDatabaseIfNotExist=true" // + "&useUnicode=true&characterEncoding=utf8"); // engineConfiguration.setJdbcUsername("root"); // engineConfiguration.setJdbcPassword("root"); // engineConfiguration.setActivityFontName("宋体"); // engineConfiguration.setAnnotationFontName("宋体"); // engineConfiguration.setLabelFontName("宋体"); // engineConfiguration.setDatabaseSchemaUpdate("true"); // // // 通过ProcessEngineConfiguration对象创建 ProcessEngine 对象 // // processEngine = engineConfiguration.buildProcessEngine(); // // 仓储服务 // repositoryService = processEngine.getRepositoryService(); // // 得到运行时服务组件 // runtimeService = processEngine.getRuntimeService(); // // 得到历史服务组件 // historyService = processEngine.getHistoryService(); // // identityService = processEngine.getIdentityService(); // // taskService = processEngine.getTaskService(); // // } // 1.参考模板 启动流程 会写入相关数据库,key为自定义编号 //1.静态的启动部分放在init中,该方法用于部署工作流,目前仅需要启动服务时配置一次,所以 //只需要第一次启动时加载ApplyListener中的类,过后注释掉。 public Deployment deployeeProcess(String bpmnName, String key) { // 指定执行我们刚才部署的工作流程 // RepositoryService repositoryService = // processEngine.getRepositoryService(); Deployment deployment = repositoryService.createDeployment()// 创建一个部署的构建器 .addClasspathResource("com/activity/" + bpmnName + ".bpmn")// 从类路径中添加资源,一次只能添加一个资源 .name("项目审批")// 设置部署的名称 .category("审批类别")// 设置部署的类别 .key(key).deploy(); org.springframework.web.context.ContextLoaderListener xx; System.out.println("部署的id" + deployment.getId()); System.out.println("部署的名称" + deployment.getName()); return deployment; } // 2.启动流程 写入相关数据库,processDefinitionKey 为自定义流程模板名称 @RequestMapping(value = "/startProcess") @ResponseBody public List<String> startProcess(String appid, String businessKey, String processDefinitionKey, HttpServletRequest request) { // String processDefinitionKey = "leave"; // 取运行时服务 System.err.println("userId" + " " + appid + " " + businessKey + " " + processDefinitionKey); // RuntimeService runtimeService = processEngine.getRuntimeService(); Map<String, Object> variables = new HashMap<String, Object>(); //userId为bpmn文件中xml编辑器中自定义的用户变量 variables.put("userId", appid); // 设置流程启动用户 identityService.setAuthenticatedUserId(appid); // 取得流程实例 ProcessInstance pi = runtimeService.startProcessInstanceByKey(processDefinitionKey, businessKey, variables);// 通过流程定义的key // 来执行流程 System.out.println("流程实例id:" + pi.getId());// 流程实例id System.out.println("流程定义id:" + pi.getProcessDefinitionId());// 输出流程定义的id String imgsrc = getActivitiProccessImage(pi.getId(), request); System.err.println("pi:" + pi); //list中存放的三个值分别为服务器上的图片生成名称、流程实例的Id、以及获取流程图像 List<String> list = new ArrayList<String>(); list.add(imgsrc); list.add(pi.getId()); //getActivitiByPiId(pi.getId()); list.add(getActivitiByPiId(pi.getId())); return list; } @RequestMapping(value = "/viewImage") @ResponseBody public void viewImage(String deploymentId) throws Exception { // 创建仓库服务对对象 // RepositoryService repositoryService = // processEngine.getRepositoryService(); // 从仓库中找需要展示的文件 List<String> names = repositoryService.getDeploymentResourceNames(deploymentId); String imageName = null; for (String name : names) { if (name.indexOf(".png") >= 0) { imageName = name; } } if (imageName != null) { System.out.println(imageName); File f = new File("d:/" + imageName); // 通过部署ID和文件名称得到文件的输入流 InputStream in = repositoryService.getResourceAsStream(deploymentId, imageName); FileUtils.copyInputStreamToFile(in, f); } } public String getDeployeeIdByPid(String piInstancetId) { HistoricProcessInstance historicProcessInstance = historyService.createHistoricProcessInstanceQuery() .processInstanceId(piInstancetId).singleResult(); String deployeeId = ""; if (historicProcessInstance == null) { System.out.println("未获取到"); } else { // 获取流程定义 ProcessDefinitionEntity processDefinition = (ProcessDefinitionEntity) ((RepositoryServiceImpl) repositoryService) .getDeployedProcessDefinition(historicProcessInstance.getProcessDefinitionId()); String piId = processDefinition.getId(); deployeeId = processDefinition.getDeploymentId(); } return deployeeId; } /** * 获取流程图像,已执行节点和流程线高亮显示 */ public String getActivitiByPiId(String piInstancetId) { String imageName = null; List<String> executedActivityIdList = new ArrayList<String>(); String newFileName = ""; System.out.println("piInstancetId" + piInstancetId); // logger.info("[开始]-获取流程图图像"); try { // 获取历史流程实例 HistoricProcessInstance historicProcessInstance = historyService.createHistoricProcessInstanceQuery() .processInstanceId(piInstancetId).singleResult(); System.err.println("his:" + historicProcessInstance); if (historicProcessInstance == null) { // throw new BusinessException("获取流程实例ID[" + pProcessInstanceId // + "]对应的历史流程实例失败!"); } else { // 获取流程定义 ProcessDefinitionEntity processDefinition = (ProcessDefinitionEntity) ((RepositoryServiceImpl) repositoryService) .getDeployedProcessDefinition(historicProcessInstance.getProcessDefinitionId()); // 获取流程历史中已执行节点,并按照节点在流程中执行先后顺序排序 List<HistoricActivityInstance> historicActivityInstanceList = historyService .createHistoricActivityInstanceQuery().processInstanceId(piInstancetId) .orderByHistoricActivityInstanceId().asc().list(); // 已执行的节点ID集合 //List<String> executedActivityIdList = new ArrayList<String>(); System.out.println("aa:" + executedActivityIdList.size()); int index = 1; // logger.info("获取已经执行的节点ID"); for (HistoricActivityInstance activityInstance : historicActivityInstanceList) { executedActivityIdList.add(activityInstance.getTaskId()); System.out.println("a:"+activityInstance.getActivityId()+" "+activityInstance.getId()+" "+activityInstance.getTaskId()); // logger.info("第[" + index + "]个已执行节点=" + // activityInstance.getActivityId() + " : " // +activityInstance.getActivityName()); index++; } } } catch (Exception e) { // TODO: handle exception } return executedActivityIdList.get(executedActivityIdList.size()-1); } public String getActivitiProccessImage(String piInstancetId, HttpServletRequest request) { String imageName = null; String newFileName = ""; System.out.println("piInstancetId" + piInstancetId); // logger.info("[开始]-获取流程图图像"); try { // 获取历史流程实例 HistoricProcessInstance historicProcessInstance = historyService.createHistoricProcessInstanceQuery() .processInstanceId(piInstancetId).singleResult(); System.err.println("his:" + historicProcessInstance); if (historicProcessInstance == null) { // throw new BusinessException("获取流程实例ID[" + pProcessInstanceId // + "]对应的历史流程实例失败!"); } else { // 获取流程定义 ProcessDefinitionEntity processDefinition = (ProcessDefinitionEntity) ((RepositoryServiceImpl) repositoryService) .getDeployedProcessDefinition(historicProcessInstance.getProcessDefinitionId()); // 获取流程历史中已执行节点,并按照节点在流程中执行先后顺序排序 List<HistoricActivityInstance> historicActivityInstanceList = historyService .createHistoricActivityInstanceQuery().processInstanceId(piInstancetId) .orderByHistoricActivityInstanceId().asc().list(); // 已执行的节点ID集合 List<String> executedActivityIdList = new ArrayList<String>(); System.out.println("aa:" + executedActivityIdList.size()); int index = 1; // logger.info("获取已经执行的节点ID"); for (HistoricActivityInstance activityInstance : historicActivityInstanceList) { executedActivityIdList.add(activityInstance.getActivityId()); // logger.info("第[" + index + "]个已执行节点=" + // activityInstance.getActivityId() + " : " // +activityInstance.getActivityName()); index++; } BpmnModel bpmnModel = repositoryService.getBpmnModel(historicProcessInstance.getProcessDefinitionId()); // 已执行的线集合 List<String> flowIds = new ArrayList<String>(); // 获取流程走过的线 (getHighLightedFlows是下面的方法) flowIds = getHighLightedFlows(bpmnModel, processDefinition, historicActivityInstanceList); // System.out.println(flowIds.size()+" dddddddd"); // 获取固定图像名称 String deploymentId = getDeployeeIdByPid(piInstancetId); List<String> names = repositoryService.getDeploymentResourceNames(deploymentId); for (String name : names) { if (name.indexOf(".png") >= 0) { imageName = name; } } System.out.println("imageName" + imageName); if (imageName != null) { String uploadPath = request.getRealPath(getSavePath()); System.out.println("path:" + uploadPath); newFileName = "" + piInstancetId + ".png"; String path = uploadPath + "/"; System.err.println("path:" + path); File fileparent = new File(path); if (!fileparent.exists()) { fileparent.mkdirs(); } File img = new File(path + newFileName); System.out.println("img:" + img); File f = new File("d:/" + imageName); // 获取流程图图像字符流 ProcessDiagramGenerator pec = processEngine.getProcessEngineConfiguration() .getProcessDiagramGenerator(); // 配置字体 InputStream in = pec.generateDiagram(bpmnModel, "png", executedActivityIdList, flowIds, "宋体", "微软雅黑", "黑体", null, 2.0); InputStream in1 = pec.generateDiagram(bpmnModel, "png", executedActivityIdList, flowIds, "宋体", "微软雅黑", "黑体", null, 2.0); // response.setContentType("image/png"); // OutputStream os = response.getOutputStream(); FileUtils.copyInputStreamToFile(in, f); FileUtils.copyInputStreamToFile(in1, img); in.close(); in1.close(); } // int bytesRead = 0; // byte[] buffer = new byte[8192]; // while ((bytesRead = imageStream.read(buffer, 0, 8192)) != -1) // { // os.write(buffer, 0, bytesRead); // } // os.close(); // imageStream.close(); // String deploymentId = getDeployeeIdByPid(piInstancetId); // List<String> names = // repositoryService.getDeploymentResourceNames(deploymentId); // String imageName = null; // for (String name : names) { // if (name.indexOf(".png") >= 0) { // imageName = name; // } // } // if (imageName != null) { // System.out.println(imageName); // File f = new File("e:/" + imageName); // // 通过部署ID和文件名称得到文件的输入流 // InputStream in = // repositoryService.getResourceAsStream(deploymentId, // imageName); // FileUtils.copyInputStreamToFile(in, f); // } } // logger.info("[完成]-获取流程图图像"); } catch (Exception e) { System.err.println("eee:" + e.getMessage()); // throw new BusinessException("获取流程图失败!" + e.getMessage()); } System.out.println("img2" + newFileName); return newFileName; } public List<String> getHighLightedFlows(BpmnModel bpmnModel, ProcessDefinitionEntity processDefinitionEntity, List<HistoricActivityInstance> historicActivityInstances) { SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); // 24小时制 List<String> highFlows = new ArrayList<String>();// 用以保存高亮的线flowId for (int i = 0; i < historicActivityInstances.size() - 1; i++) { // 对历史流程节点进行遍历 // 得到节点定义的详细信息 FlowNode activityImpl = (FlowNode) bpmnModel.getMainProcess() .getFlowElement(historicActivityInstances.get(i).getActivityId()); List<FlowNode> sameStartTimeNodes = new ArrayList<FlowNode>();// 用以保存后续开始时间相同的节点 FlowNode sameActivityImpl1 = null; HistoricActivityInstance activityImpl_ = historicActivityInstances.get(i);// 第一个节点 HistoricActivityInstance activityImp2_; for (int k = i + 1; k <= historicActivityInstances.size() - 1; k++) { activityImp2_ = historicActivityInstances.get(k);// 后续第1个节点 if (activityImpl_.getActivityType().equals("userTask") && activityImp2_.getActivityType().equals("userTask") && df.format(activityImpl_.getStartTime()).equals(df.format(activityImp2_.getStartTime()))) // 都是usertask,且主节点与后续节点的开始时间相同,说明不是真实的后继节点 { } else { sameActivityImpl1 = (FlowNode) bpmnModel.getMainProcess() .getFlowElement(historicActivityInstances.get(k).getActivityId());// 找到紧跟在后面的一个节点 break; } } sameStartTimeNodes.add(sameActivityImpl1); // 将后面第一个节点放在时间相同节点的集合里 for (int j = i + 1; j < historicActivityInstances.size() - 1; j++) { HistoricActivityInstance activityImpl1 = historicActivityInstances.get(j);// 后续第一个节点 HistoricActivityInstance activityImpl2 = historicActivityInstances.get(j + 1);// 后续第二个节点 if (df.format(activityImpl1.getStartTime()).equals(df.format(activityImpl2.getStartTime()))) {// 如果第一个节点和第二个节点开始时间相同保存 FlowNode sameActivityImpl2 = (FlowNode) bpmnModel.getMainProcess() .getFlowElement(activityImpl2.getActivityId()); sameStartTimeNodes.add(sameActivityImpl2); } else {// 有不相同跳出循环 break; } } List<SequenceFlow> pvmTransitions = activityImpl.getOutgoingFlows(); // 取出节点的所有出去的线 for (SequenceFlow pvmTransition : pvmTransitions) {// 对所有的线进行遍历 FlowNode pvmActivityImpl = (FlowNode) bpmnModel.getMainProcess() .getFlowElement(pvmTransition.getTargetRef());// 如果取出的线的目标节点存在时间相同的节点里,保存该线的id,进行高亮显示 if (sameStartTimeNodes.contains(pvmActivityImpl)) { highFlows.add(pvmTransition.getId()); } } } return highFlows; } public String getPiIdbyTid(String taskId) { System.err.println("taskId1:" + taskId); Task task = taskService.createTaskQuery() // 创建任务查询 .taskId(taskId) // 根据任务id查询 .singleResult(); String piInstancetId = task.getProcessInstanceId(); // 获取流程定义id return piInstancetId; } @RequestMapping(value = "/completeTaskByTaskId") @ResponseBody public List<String> completeTaskByTaskId(String taskId, HttpServletRequest request) { System.out.println("taskId:" + taskId); String piInstancetId = getPiIdbyTid(taskId); taskService.complete(taskId); String img = getActivitiProccessImage(piInstancetId, request); System.err.println("img3" + img); List<String> list=new ArrayList<String>(); list.add(img); list.add(getActivitiByPiId(piInstancetId)); return list; } }
7. 最后,添加一个简单的JSP用ajax调用Controller中的方法便大功告成。
但是:其中最坑的一点是jsp页面读取图片(图片名称一样,只是走流程时图片发生了变化)需要等待几秒点击下一个按钮才会生效,不然图片在当前页面不会刷新,但是其实图片的样子已经改变,这其中的原理或许是因为我传的值不好,又或者是浏览器缓存问题,反正需要等待几秒,最后,放一张效果图。
<%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding="utf-8"%> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%> <% String path = request.getContextPath(); String basePath = request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort() + path + "/"; %> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <base href="<%=basePath%>" /> <meta charset="UTF-8"> <title>首页</title> <meta http-equiv="Pragma" content="no-cache"> <meta http-equiv="Cache-Control" content="no-cache"> <meta http-equiv="Expires" content="0"> <link rel="shortcut icon" href="images/favicon.ico" type="image/x-icon" /> <style type="text/css"> .startli,.teamli,.managerli{ display:none; } </style> <script type="text/javascript" src="js/jquery-1.12.4.min.js"></script> <script type="text/javascript"> $(function() { $('.btnStart').click(function() { //alert($('.btnStart').attr("alt")); $.ajax({ url : "apply/startProcess", type : 'post', data : {"appid":$('.btnStart').attr("alt"),"businessKey":$('.btnStart').attr("alt1"),"processDefinitionKey":$('.btnStart').attr("alt2")}, //提交给服务器的参数 dataType : 'json', //返回值类型,服务器out对象输出的内容 success : function(objs) { //回调函数 ,服务器有返回输出的时候调用的函数 //alert(objs[0]+"=========="+objs[1]); $('.Zu').val(objs[2]); $('.img').attr("src",'activity/'+objs[0]); $('.startli').text("审批已开启,提交给组长中,请稍后..."); $('.startli').show(); } }); }); $('.Zu1').click(function() { //alert($('.Zu').val()); $.ajax({ url : "apply/completeTaskByTaskId", type : 'post', data : {"taskId":$('.Zu').val()}, //提交给服务器的参数 dataType : 'json', //返回值类型,服务器out对象输出的内容 success : function(objs) { //回调函数 ,服务器有返回输出的时候调用的函数 $('.Xj').val(objs[1]); var attr=$('.img').attr('attr'); //alert(parseInt(attr+1)); var $parent=$('.img').parent(); $parent.html(''); var m="<img src='activity/"+objs[0]+"?attr="+new Date().getTime()+"'/>"; $parent.append(m); $('.teamli').text('组长审批通过,提交给经理中,请稍后...'); $('.teamli').show(); } }); }); $('.Xj1').click(function() { $.ajax({ url : "apply/completeTaskByTaskId", type : 'post', data : {"taskId":$('.Xj').val()}, //提交给服务器的参数 dataType : 'json', //返回值类型,服务器out对象输出的内容 success : function(objs) { //回调函数 ,服务器有返回输出的时候调用的函数 //var attr=$('.img').attr('attr'); //alert(parseInt(attr+1)); //var $parent=$('.img').parent(); $(".imgDiv").html(''); var m="<img src='activity/"+objs[0]+"?attr="+new Date().getTime()+"'/>"; $(".imgDiv").append(m); $('.managerli').text('经理审批通过,正在自动人事归档,请稍后...'); $('.managerli').show(); } }); }); }); </script> </head> <body> <h3>项目审批工作:</h3> <ul> <li><input name="btnStart" class="btnStart" alt="1" alt1="user001" alt2="apply" type="hidden"> <button class="btnStart">开始审批</button></li> <li class="startli"></li> <li><button class="Zu1">组长审批</button> <input name="Zu" class="Zu" type="hidden"></li> <li class="teamli"></li> <li><button class="Xj1">经理审批</button> <input name="Xj" class="Xj" type="hidden"></li> <li class="managerli"></li> <li>人事归档</li> </ul> <div class="imgDiv"><img src="" class="img" id="img1" attr="1"></div> </body> </html>
当然,其中还有很多小坑,以后还需慢慢完善。