定时启动运行在 WPS 上的业务流程的两种方式
2010-06-24 00:00:00 来源:WEB开发网引言
通常情况下,当一个业务流程模板启动后,它便进入了等待状态。当客户端发起请求或者有触发事件的时候,便基于业务流程模板新建一个流程实例。但某些情况下,流程不通过客户端来触发,而是需要系统自动、定期地启动。例如,一些完成系统维护功能的业务流程,或者是一些需要定期执行的业务流程。这种情况下,就需要应用某些机制,来完成流程的自动定期触发。
本文主要介绍如何定期启动运行在 WebSphere Process Server(WPS)上的业务流程。我们可以通过如下两种方式来完成流程的定期启动:
使用 WebSphere Application Server 的调度程序服务(Scheduler)机制
使用 EJB 2.1 的计时器功能
在本文中,我们将首先创建一个简单的业务流程。然后我们将详细介绍如何应用上述两种机制来完成业务流程的定期启动。
创建业务流程
为简化起见,在这一部分,我们将在 WID 中创建一个非常简单的业务流程―― AutoProcess,这个流程的主要任务就是接收请求并将请求数据打印出来。
图 1. 业务流程
在 snippet log1 中,我们将流程的输入打印出来:
清单 1. snippet log1 代码
System.out.println("----Auto process invoked----");
System.out.println("----Input field1 is: " + input1.getString("field1") + "----");
System.out.println("----Input field2 is: " + input1.getInt("field2") + "----");
在 snippet create output 中,我们对流程的 output 进行赋值:
清单 2. snippet create output 代码
input1.setString("field1", "return: " + input1.getString("field1"));
input1.setInt("field2", input1.getInt("field2"));
在 snippet log2 中,我们将流程的输出打印出来:
清单 3. snippet log2 代码
System.out.println("----Output field1 is: " + input1.getString("field1") + "----");
System.out.println("----Output field2 is: " + input1.getInt("field2") + "----");
流程的接口如下 :
图 2. 业务流程接口
查看原图(大图)
使用 WebSphere Application Server 的调度程序服务机制定时启动业务流程
调度程序服务(Scheduler)是一个负责在特定时刻或时间间隔启动相应操作的 WebSphere 变成扩展。调度程序是持久的事务计时器服务,它们运行 Enterprise JavaBean 方法或使用任何 J2EE 服务器应用程序发送 java 服务消息。调度程序服务通过充分利用现有的计算资源,有助于将 IT 成本降至最低、提高应用程序速度和响应。
使用调度程序服务包括如下步骤:
创建和配置调度程序、创建和配置调度程序的数据库以及管理调度程序的指示信息
开发调度任务
向调度程序提交任务
管理调度任务
在这一部分中,我们将详细介绍如何用过这四个步骤来完成业务流程的定时启动。
创建调度程序服务
我们可以在 WebSphere Process Server 的管理控制台中创建和配置调度程序服务。每个配置后的调度程序是一个独立任务调度引擎,它拥有唯一的 java 命名与目录接口(JNDI)名称、持久存贮设备和守护程序。
调度程序的运行依赖于一个数据库,以用于存贮它的持久信息。因此,在创建和配置调度程序服务之前,请先确保创建了数据库,并在 WebSphere Process Server 的管理控制台中创建了相应的数据源。
接下来,我们就可以通过如下步骤来创建和配置调度程序服务了:
打开 WebSphere Process Server 的管理控制台,单击 Resources->Scheduler,新建一个调度程序服务
图 3. 新建调度程序服务
查看原图(大图)
填写调度程序服务的属性
图 4. 调度程序服务的属性
上图中:
scope:调度程序服务定义的范围,本文中,将调度程序服务定义在 server1 范围内
Name:新建的调度程序服务的名称
JNDI name:新建的调度程序服务的 JNDI 名称。这个名称很重要,在程序里调用调度程序服务的时候需要用到这个名称
Description:这一项是可选项。是调度程序服务的描述。
Category:可选项。表示调度程序服务的类别
Data source JDNI name:这一项用来指定调度程序服务使用的数据库的数据源。这个数据源需要事先定义好
Data source alias:用来连接到数据源的 J2C 别名。这个别名需要事先定义好。
Table prefix:指定数据库中表名的前缀
Poll interval:这一项用来指定调度程序服务轮巡数据库的时间间隔
Work manager JNDI name:用来指定调度程序服务使用的 Work Manager 的名称。本文中我们使用默认的 Work Manager
为调度程序服务创建数据库表
图 5. 为调度程序服务创建数据库表
查看原图(大图)
验证调度程序服务的数据库表
图 6. 验证调度程序服务的数据库表
查看原图(大图)
至此,我们已经创建并配置好了一个新的调度程序服务。在使用调度程序服务之前,我们还需要重起 WebSphere Process Server,以使新建的调度程序服务生效
开发调度任务
调度程序服务可以调度两种方式的任务:会话 bean 任务和发送 java 消息的服务。本文采用第一种方式,创建一个会话 bean,在这个会话 bean 中启动业务流程。为使会话 bean 能够被调度程序服务使用,会话 bean 必须使用如下主接口和远程接口:
清单 4. 会话 bean 需要实现的主接口和远程接口
com.ibm.websphere.scheduler.TaskHandlerHome
com.ibm.websphere.scheduler.TaskHandler
调度程序服务在运行的时候,会定期调用 TaskHandler 中的 process() 方法,因此,会话 bean 需要实现 TaskHandler 接口中定义的 process() 方法,并在 process() 方法中启动业务流程。
接下来我们就详细介绍一下开发调度任务的具体步骤:
在 WID 中新建一个 EJB 工程
图 7. 新建 EJB 工程
在 AutoProcessInvokerEJB 项目中,新建一个会话 bean--AutoProcessInvokerBean,将 TaskHandlerHome 和 TaskHandler 指定为 bean 的主接口和远程接口
图 8. 新建会话 bean -- AutoProcessInvokerBean
在 AutoProcessInvokerBean 中实现 TaskHandler 接口的 process() 方法,并在该方法中,启动业务流程,代码如下:
清单 5. TaskHandler 接口的 process() 方法实现代码
public void process(TaskStatus status) {
String sourceMethod = "process";
System.out.println("----AutoprocessInvoker called by Scheduler----");
// 查找 Business Flow Managerd 的引用,以启动流程
LocalBusinessFlowManager bfm = null;
try {
Context context = new InitialContext();
Object result = context
.lookup("java:comp/env/ejb/LocalBusinessFlowManagerHome");
LocalBusinessFlowManagerHome home =
(LocalBusinessFlowManagerHome) javax.rmi.PortableRemoteObject
.narrow(result, LocalBusinessFlowManagerHome.class);
bfm = home.create();
} catch (Exception e) {
System.out.println("----ERROR getting Business Flow Manager reference----");
e.printStackTrace();
return;
}
// 如果没有找到引用,则返回
if (null == bfm) {
System.out.println("----ERROR getting Business Flow Manager reference----");
return;
}
// 查找名称为 AutoProc 的业务流程模版
String whereClause = "PROCESS_TEMPLATE.NAME = 'AutoProc'";
String orderByClause = "PROCESS_TEMPLATE.VALID_FROM DESC";
try {
ProcessTemplateData[] templates = bfm.queryProcessTemplates(
whereClause, orderByClause, null, null);
if (templates != null && templates.length > 0) {
ProcessTemplateData template = templates[0];
// 生成流程的输入消息
ClientObjectWrapper cow =
bfm.createMessage(template.getID(), template.getInputMessageTypeName());
DataObject input = (DataObject) cow.getObject();
DataObject processRequest = input.createDataObject("input1");
processRequest.setString("field1", "called at " + System.currentTimeMillis());
processRequest.setInt("field2", 10);
// 将流程的输入消息打印到控制台上
BOXMLSerializer serializer = null; //!!! reference used in multiple scopes
serializer = (BOXMLSerializer) ServiceManager.
INSTANCE.locateService("com/ibm/websphere/bo/BOXMLSerializer");
serializer.
writeDataObject(processRequest, processRequest.getType().getURI(), "", System.out);
// 启动流程并获取流程的返回消息
ClientObjectWrapper cowOut = bfm.call("AutoProc", cow);
DataObject response = (DataObject) cowOut.getObject();
DataObject responseData = response.getDataObject("output1");
serializer
.writeDataObject(responseData, responseData.getType().getURI(), "", System.out);
}
} catch (Exception e) {
System.out.println("----error getting Scheduler!----");
e.printStackTrace();
}
}
在这段代码中,我们首先获取 Business Flow Manager 对象,然后创建流程的输入数据(在实际项目中,流程的输入数据也可以从文件中读取),最后启动流程,并获取流程的返回数据。
至此,我们已经成功创建了调度任务,接下来,我们需要将调度任务提交给调度程序服务,并启动该调度任务。
向调度程序服务提交任务
我们在创建调度程序服务的时候,曾经指定了一个 JNDI 名字(test/scheduler)。通过查找这个 JNDI 名称,我们可以获取一个 com.ibm.websphere.scheduler.Scheduler 的 java 对象.利用这个对象,我们可以对任务执行创建、暂挂、取消等操作。
在本文中,我们利用 startup bean 向调度程序服务提交任务。Startup bean 使业务逻辑能在启动或停止应用程序时运行。我们可以使用 home 接口 com.ibm.websphere.startupservice.AppStartUpHome 来将一个 bean 指定成应用程序的 startup bean。是用远程接口 com.ibm.websphere.startupservice.AppStartUp 来对该会话 bean 定义 start() 和 stop() 方法。当应用程序启动时,会启动 bean 的 start() 方法;当应用程序停止时,会启动 bean 的 stop() 方法。
接下来,我们就详细介绍如何创建 startup bean 将调度任务提交给调度程序服务。
在 AutoProcessInvokerEJB 项目中新建一个会话 bean – AutoProcessStarterBean,将 AppStartUpHome 指定为 bean 的主接口;将 AppStartUp 指定为 bean 的远程接口。
图 9. 新建会话 bean – AutoProcessStarterBean
在 AutoProcessStarterBean 中实现 AppStartUp 接口的 start() 方法,再次访法中,将调度任务提交给调度程序服务,代码如下:
清单 6. AppStartUp 接口的 start() 方法实现代码
public boolean start() {
String sourceMethod = "start";
System.out.println("----starting my StartupBean----");
// initialize
InitialContext initialContext = null;
Scheduler scheduler = null;
// get Scheduler
try {
initialContext = new InitialContext();
scheduler = (Scheduler) initialContext.lookup("test/scheduler");
} catch (NamingException e) {
System.out.println("----error getting Scheduler!----");
e.printStackTrace();
}
if (scheduler == null)
return false;
try {
// create class BeanTaskInfo
BeanTaskInfo taskInfo = (BeanTaskInfo) scheduler
.createTaskInfo(BeanTaskInfo.class);
// lookup sessoin bean using its jndi name
Object obj = initialContext
.lookup("ejb/com/ibm/websphere/scheduler/AutoProcessInvoker");
// Narrow session bean
TaskHandlerHome home = (TaskHandlerHome) PortableRemoteObject
.narrow(obj, TaskHandlerHome.class);
// add the schedule implementation into taskinfo class
taskInfo.setTaskHandler(home);
// set UserCalendar for task
Calendar now = Calendar.getInstance();
taskInfo.setStartTime(new Date());
taskInfo.setRepeatInterval("0 * * * * ?");
taskInfo.setNumberOfRepeats(3);
taskInfo
.setUserCalendar(
"com/ibm/websphere/scheduler/calendar/DefaultUserCalendarHome",
"CRON");
// submit the task to scheduler
TaskStatus ts = scheduler.create(taskInfo);
System.out.println("----Task created----");
} catch (Exception e) {
e.printStackTrace();
}
return true;
}
在以上代码中,我们执行了如下操作:
查找 JNDI 名字―― test/scheduler 获取调度程序服务对象,并创建一个调度任务
查找 JNDI 名字―― ejb/com/ibm/websphere/scheduler/AutoProcessInvoker,获取 AutoProcessInvokerBean 对象的 TaskHandlerHome 接口
用 setTaskHandler 方法,将任务的逻辑实现提交给调度程序服务
用 setStartTime 方法设定任务的启动时间为当前系统时间
用 setRepeatInterval 方法,设定任务重复间隔时间为1分钟
用 setNumberOfRepeats 方法,设定任务的重复次数为3次
至此,我们便利用 startup bean,在应用程序启动的时候,将任务提交给调度程序服务
管理调度任务
在本文中,我们将利用 startup bean 的 stop() 方法,在停止应用程序的时候,取消并清除调度任务,同时打印出调度任务的信息。
在 AutoProcessStarterBean 中实现 AppStartUp 接口的 stop() 方法,代码如下:
清单 7. AppStartUp 接口的 stop() 方法实现代码
public void stop() {\
String sourceMethod = "stop";
System.out.println("----stopping ProcessScheudler----");
InitialContext context;
try{>
context=new InitialContext();
//lookup schedule using its jndi name
Scheduler scheduler=(Scheduler)context.lookup("test/scheduler");
Iterator taskit = scheduler.findTasksByName("%");
while (taskit.hasNext()){
BeanTaskInfo taskinfo = (BeanTaskInfo)taskit.next();
String taskid = taskinfo.getTaskId();
System.out.println("----task id is: " + taskid);
System.out
.println("----task start time is: " + taskinfo.getStartTime().toString() + "----");
System.out.println("----task interval is: " + taskinfo.getRepeatInterval() + "----");
scheduler.cancel(taskid, true);
}
}
catch(Exception e){
e.printStackTrace();
}
}
在这段代码中,我们通过查找 JNDI 名字―― test/scheduler 获取调度程序服务对象,然后依次查找该调度程序服务中的任务,调用方法 cancel 取消并清除任务。
部署并运行流程和 EJB 项目
将业务流程模块 AutoProcessApp 部署到 WebSphere Process Server 上,并启动流程模版
将 EJB 工程 AutoPorcessInvoker 部署到 WebSphere Process Server 上,并启动该应用
查看控制台,如果有如下输出,说明任务已经被调度程序服务启动,从而达到了定期自动启动流程的目的
清单 8. 控制台输出
[10-2-24 14:06:37:937 CST] 00000ab3 SystemOut O ----starting my StartupBean----
[10-2-24 14:06:38:515 CST] 00000ab3 SystemOut O ----Task created----
[10-2-24 14:06:38:546 CST] 00000ab3 ApplicationMg A
WSVR0221I: 应用程序已启动:AutoProcessInvoker
[10-2-24 14:06:41:031 CST] 0000006c SystemOut O
----AutoprocessInvoker called by Scheduler----
[10-2-24 14:06:41:562 CST] 0000006c SystemOut O
<?xml version="1.0" encoding="UTF-8"?>
<p: xsi:type="p:AutoData" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<field1>called at 1266991601484</field1>
<field2>10</field2>
</p:>
[10-2-24 14:06:42:500 CST] 0000006c SystemOut O ----Auto process invoked----
[10-2-24 14:06:42:500 CST] 0000006c SystemOut O
----Input field1 is: called at 1266991601484----
[10-2-24 14:06:42:500 CST] 0000006c SystemOut O ----Input field2 is: 10----
[10-2-24 14:06:42:515 CST] 0000006c SystemOut O
----Output field1 is: return: called at 1266991601484----
[10-2-24 14:06:42:515 CST] 0000006c SystemOut O ----Output field2 is: 10----
[10-2-24 14:06:42:593 CST] 0000006c SystemOut O
<?xml version="1.0" encoding="UTF-8"?>
<p: xsi:type="p:AutoData" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<field1>return: called at 1266991601484</field1>
<field2>10</field2>
</p:>
使用 EJB 2.1 计时器功能定时启动业务流程
以上我们介绍了如何用 WebSphere 扩展的调度程序服务功能来定时启动业务流程。在这一部分中,我们将介绍定时启动业务流程的另外一种方式:利用 EJB 2.1 计时器功能定时启动业务流程。
同前一种方法一样,我们需要创建一个会话 bean,用这个会话 bean 来启动业务流程。要实现 EJB 2.1 计时器功能,这个会话 bean 需要实现 javax.ejb.TimedObject 接口。这个接口中的方法―― ejbTimeout 将定期被调用。因此,我们将在 ejbTimeout 方法中启动业务流程。要想启动计时器功能,还需要为计时器指定 Timer。同前一种方法一样,我们选择在启动应用的时候,启用计时器功能,在停止应用的时候,取消计时器功能。因此,这个会话 bean 还需要使用 AppStartUpHome 作为主接口,并实现远程接口 AppStartUp 的 start() 和 stop() 方法。
具体步骤如下:
新建一个 EJB 工程―― TimerInvokerEJB
在 TimerInvokerEJB 项目中,新建一个会话 bean – TimerInvokerBean
将 AppStartUpHome 指定为会话 bean 的主接口;将 AppStartUp 指定为会话 bean 的远程接口
图 10. TimerInvokerBean 的接口
修改 TimerInvokerBean,使其实现 TimedObject 接口
实现 TimedObject 的 ejbTimeout 方法,在该方法中启动业务流程,代码如下:
清单 9. TimedObject 接口的 ejbTimeout 方法实现代码:
public void ejbTimeout(Timer arg0) {
// TODO Auto-generated method stub
System.out.println("----EJB timer invoked----");
// looking up reference to Business Flow Manager
LocalBusinessFlowManager bfm = null;
try {
Context context = new InitialContext();
Object result = context
.lookup("java:comp/env/ejb/LocalBusinessFlowManagerHome");
LocalBusinessFlowManagerHome home =
(LocalBusinessFlowManagerHome) javax.rmi.PortableRemoteObject
.narrow(result, LocalBusinessFlowManagerHome.class);
bfm = home.create();
} catch (Exception e) {
System.out.println("----ERROR getting Business Flow Manager reference----");
e.printStackTrace();
return;
}
if (null == bfm) {
System.out.println("----ERROR getting Business Flow Manager reference----");
return;
}
String whereClause = "PROCESS_TEMPLATE.NAME = 'AutoProc'";
String orderByClause = "PROCESS_TEMPLATE.VALID_FROM DESC";
try {
ProcessTemplateData[] templates = bfm.queryProcessTemplates(
whereClause, orderByClause, null, null);
if (templates != null && templates.length > 0) {
ProcessTemplateData template = templates[0];
ClientObjectWrapper cow =
bfm.createMessage(template.getID(), template.getInputMessageTypeName());
DataObject input = (DataObject) cow.getObject();
DataObject processRequest = input.createDataObject("input1");
processRequest.setString("field1", "called at " + System.currentTimeMillis());
processRequest.setInt("field2", 10);
BOXMLSerializer serializer = null; //!!! reference used in multiple scopes
serializer = (BOXMLSerializer) ServiceManager.
INSTANCE.locateService("com/ibm/websphere/bo/BOXMLSerializer");
serializer.
writeDataObject(processRequest, processRequest.getType().getURI(), "", System.out);
ClientObjectWrapper cowOut = bfm.call("AutoProc", cow);
DataObject response = (DataObject) cowOut.getObject();
DataObject responseData = response.getDataObject("output1");
serializer
.writeDataObject(responseData, responseData.getType().getURI(), "", System.out);
}
} catch (Exception e) {
System.out.println("----error getting Scheduler!----");
e.printStackTrace();
}
}
实现 AppStartUp 接口的 start() 方法,在该方法中,为定时器指定 Timer 对象,从而在应用程序启动的时候启动定时器。代码如下:
清单 10. AppStartUp 接口的 start() 方法实现代码
public boolean start() {
System.out.println("----Startup method called----");
TimerService ts = mySessionCtx.getTimerService();
long interval = 60000;
String timerName = "EJBTimer";
ts.createTimer(new Date(), interval, timerName);
return true;
}
在这段代码中,我们为定时器指定了 Timer 对象:启动时间为当前系统时间;重复间隔为1分钟;计时器的名字为 EJBTimer
实现 AppStartUp 接口的 stop() 方法,在停止应用程序的时候取消计时器,代码如下:
清单 11. AppStartUp 接口的 stop() 方法实现代码
public void stop() {
System.out.println("----Stop method called----");
try {
TimerService ts = mySessionCtx.getTimerService();
Collection timers = ts.getTimers();
Iterator it = timers.iterator();
while (it.hasNext()) {
Timer myTimer = (Timer) it.next();
System.out.println("----Timer info: " + myTimer.getInfo()
+ "----");
myTimer.cancel();
}
} catch (Exception e) {
System.out.println("Exception after cancelling timer : "
+ e.toString());
}
}
部署并启动 TimerInvoker 应用
查看控制台,如果有如下输出,说明计时器已经启动,从而达到了定时启动业务流程的目的
清单 12. 控制台输出
[10-2-24 14:50:26:000 CST] 000002af SystemOut O ----EJB timer invoked----
[10-2-24 14:50:26:859 CST] 000002af SystemOut O
<?xml version="1.0" encoding="UTF-8"?>
<p: xsi:type="p:AutoData" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<field1>called at 1266994226828</field1>
<field2>10</field2>
</p:>
[10-2-24 14:50:26:921 CST] 000002af SystemOut O ----Auto process invoked----
[10-2-24 14:50:26:921 CST] 000002af SystemOut
O ----Input field1 is: called at 1266994226828----
[10-2-24 14:50:26:921 CST] 000002af SystemOut O ----Input field2 is: 10----
[10-2-24 14:50:26:921 CST] 000002af SystemOut
O ----Output field1 is: return: called at 1266994226828----
[10-2-24 14:50:26:921 CST] 000002af SystemOut O ----Output field2 is: 10----
[10-2-24 14:50:26:937 CST] 000002af SystemOut O
<?xml version="1.0" encoding="UTF-8"?>
p: xsi:type="p:AutoData" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<field1>return: called at 1266994226828</field1>
<field2>10</field2>
</p:>
对比定时启动业务流程的两种机制
以上介绍了定时启动业务流程的两种机制:调度程序服务和 EJB 2.1 的计时器功能,下表给出了两种机制的对比,读者可以根据自己的需求,选择合适的机制:
表 1. 两种机制的对比
调度程序 | EJB 计时器 |
运行无状态会话 EJB 组件并发送 JMS 消息 | 运行所有 EJB 类型(有状态会话 bean 除外) |
持久性、事务性和高可用性。 | 持久性、事务性和高可用性。 |
确保任务仅运行一次 | 如果计时器 EJB 使用容器管理的全局事务,计时器确保仅运行一次 |
使用任何计算规则运行重复任务 | 使用以毫秒为单位定义的重复时间间隔运行重复任务 |
使用修改过的固定延迟时间计算来确定重复时间间隔(根据前一个任务的开始时间确定下一次运行时间) | 使用定量时间计算来确定重复时间间隔(根据原先计划的时间确定下一个任务的时间)。 |
配合使用 NotificationSink 无状态会话 EJB 的程序化任务监视 | 无程序化计时器监视 |
放弃运行后期任务或对时间敏感的任务 | 放弃运行后期任务或对时间敏感的任务(通过在 javax.ejb.TimedObject 实现中手动检测来实现)。 |
管理任何任务生命周期(通过程序或 java 管理扩展(JMX)来查找、暂挂、继续、取消和清除任务)。 | 通过程序查找和取消它的计时器。管理员使用命令行实用程序来查找和取消计时器。 |
存储数据的定量文本,如 Name(外部存储的任意数据)。 | 使用计时器存储任意数据 |
示例代码简介
本文的示例代码中包含如下内容:
业务流程模块―― AutoProcessApp
使用调度程序服务定期启动业务流程的 EJB 工程―― AutoProcessStarter
使用 EJB 2.1 计时器功能定期启动业务流程的 EJB 工程―― TimerInvoker
下载的文件是一个 Project Interchange 文件,读者可以直接在 WID 6.2 中导入该文件,编译并部署。
总结
本文主要介绍了自动、定期启动流程的两种方式:使用调度程序服务和 EJB 2.1 的定时器功能来自动启动流程。这两种方式相比较,使用调度程序服务的方式能够实现更加负责的一些功能,例如,发送 JMS 消息,实现程序化任务的监视等。读者可根据自己的需要选择合适的机制。
- ››定时显示远程计算机的桌面
- ››定时启动运行在 WPS 上的业务流程的两种方式
- ››运行于以太网的基础协议
- ››运行 Android 的魅族 M9 参数细节出炉
- ››启动各种 App Android
- ››运行 Android Emulator 的方法
- ››运行Word总默认打开上次文档怎么办?
- ››运行于 Nexus One 的完整版 Firefox 截图曝光
- ››运行于 Nexus One 的 Opera Mobile for Android 演...
- ››启动数据库的管理工具Sybase Central
- ››运行 ASP.NET 的进程帐户必须具有IIS 元数据库权限...
- ››启动Windows 7“Jump List”方法
更多精彩
赞助商链接