WWF3的持续化<第五篇>

本文介绍WWF工作流框架的持续化功能实现方法,包括使用SqlWorkflowPersistenceService类存储和恢复工作流实例状态,以及如何通过外部事件触发工作流活动。

  WWF提供的持续化功能会自动记录工作流实例以及它包含的所有活动的执行状态,这些状态并不是指工作流上流转的表单所呈现的业务逻辑状态。WWF持续化功能就是将未执行完成的工作流实例以及该实例中各种活动的状态,以文件或数据库方式进行存储,待需要的时候再重新将其加载回工作流运行时容器Runtime中。运行完毕才删除。

  在具体操作中通过"SqlWorkflowPersistenceService"类来实现持续化的功能,基于数据库,其他数据库需重新实现接口。

一、创建SqlPersistenceService数据库

  要在SQLServer实现工作流的保存、恢复功能,需要创建一些相关表与存储过程。默认情况下在C:\Windows\Microsoft.NET\Framework\v3.5\SQL\EN 路径下有SqlPersistenceProviderSchema.sql和SqlPersistenceProviderLogic.sql两个文件,打开SQLServer创建一个名叫SqlPersistenceService的数据库,按顺序执行以上两个sql脚本。此时,SqlPersistenceService数据库就创建了几个表与存储过程。

  如果实在不行,就把SqlPersistenceService数据库附加进来。http://files.cnblogs.com/kissdodog/WWF%E6%8C%81%E7%BB%AD%E5%8C%96%E6%95%B0%E6%8D%AE%E5%BA%93.rar

  接口如下:

   [ExternalDataExchange]  //using System.Workflow.Activities;
    public interface IEvent
    {
        event EventHandler<ExternalDataEventArgs> PMApproved;
        event EventHandler<ExternalDataEventArgs> VPApproved;
        event EventHandler<ExternalDataEventArgs> StaffSubmit;
    }

  新建一个工作流如下:

  

  里面是3个HandleExternalEvent活动,分别于刚刚定义的接口事件相绑定。

  

  在bin目录里面添加一个XML文件,其代码如下:

<WorkflowInstances />

  新建一个Winform程序,界面如下:

  

  代码如下:

    public partial class Form1 : Form, ClassLibrary1.IEvent
    {
        //创建一个WinForm引用程序,然后继承并实现接口中定义的事件
        public event EventHandler<ExternalDataEventArgs> PMApproved;
        public event EventHandler<ExternalDataEventArgs> VPApproved;
        public event EventHandler<ExternalDataEventArgs> StaffSubmit;

        private WorkflowRuntime runtime;
        //SQLServer中的"SqlPersistenceService"数据库是用来存储工作流实例及其活动的运行状态。
        //业务逻辑状态也能够以XML文件形式进行存储,当程序刚运行无流程在运行时,添加一个空xml文件
        //名为:workflowInstances.xml直接放在bin目录
        const string instanceFilename = "workflowInstances.xml";

        WorkflowInstance instance;

        public Form1()
        {
            InitializeComponent();

            this.runtime = new WorkflowRuntime();

            runtime.WorkflowCompleted += new EventHandler<WorkflowCompletedEventArgs>(runtime_WorkflowCompleted);
            runtime.WorkflowIdled += OnWorkflowIdled;

            //应用程序启动时以"SqlWorkflowPersistenceService"为对象来实例化"WorkflowPersistenceService"类,
            //并通过"SqlWorkflowPersistenceService"来连接SQLServer数据库,同时将工作流的持续化服务加载到工作流运行时容器Runtime中。
            WorkflowPersistenceService persistenceService =
                        new SqlWorkflowPersistenceService(
                        "server=CZZ;database=SqlPersistenceService;uid=sa;pwd=123");
            runtime.AddService(persistenceService);

            ExternalDataExchangeService dataService = new ExternalDataExchangeService();
            runtime.AddService(dataService);

            dataService.AddService(this);

            LoadWorkflowData();

            runtime.StartRuntime();


        }
        //在工作流空闲状态时并将其从内存中卸载出去
        //重载工作流的"WorkflowIdled"事件,当工作流处于等待状态下时将通过该事件来调用工作流实例的"TryUnload()"方法,
        //使它激发工作流的"WorkflowPersisted"事件,这样工作流就可以通过"WorkflowPersistenceService"服务将信息自动保存到SQL Server数据库,以实现持续化功能。
        static void OnWorkflowIdled(object sender, WorkflowEventArgs e)
        {
            e.WorkflowInstance.TryUnload();
        }

        //当工作流完成后,将界面列表中的相应数据删除
        //当工作流运行结束时还需要对界面列表中的相关信息进行删除,用户可以通过重载工作流的"WorkflowCompleted"事件来实现。
        //要注意分清楚,事件是工作流的事件,而删除界面是WinForm程序的事。工作流与应用程序不在同一个线程,因此不能直接调用。这种情况可以通过委托来实现。
        void runtime_WorkflowCompleted(object sender, WorkflowCompletedEventArgs e)
        {
            RemoveListViewItem remove = new RemoveListViewItem(RemoveListViewItemAsync);
            Invoke(remove, e.WorkflowInstance.InstanceId);
        }
        private delegate void RemoveListViewItem(Guid instanceId);
        private void RemoveListViewItemAsync(Guid instanceId)
        {
            foreach (ListViewItem item in ListViewExisting.Items)
            {
                if (item.SubItems[0].Text.Equals(instanceId.ToString()))
                    ListViewExisting.Items.Remove(item);
            }
        }

        //用户提交请假单的按钮事件
        private void btnSubmit_Click(object sender, EventArgs e)
        {
            instance = runtime.CreateWorkflow(typeof(WorkflowConsoleApplication1.Workflow1));
            instance.Start();

            StaffSubmit(null, new ExternalDataEventArgs(instance.InstanceId));

            ListViewExisting.Items.Add(new ListViewItem(new String[] { instance.InstanceId.ToString(), this.txtDay.Text, "提交请假单" }));

        }
        //当窗体关闭时,将列表中请假单的信息保存到XML中
        private void Form1_FormClosing(object sender, FormClosingEventArgs e)
        {
            SaveWorkflowData();
            runtime.Dispose();
        }
        //项目经理审批的按钮
        private void btnPMArrpoved_Click(object sender, EventArgs e)
        {
            instance = runtime.GetWorkflow(new Guid(ListViewExisting.SelectedItems[0].Text));

            PMApproved(null, new ExternalDataEventArgs(instance.InstanceId));

            foreach (ListViewItem item in ListViewExisting.Items)
            {
                if (item.SubItems[0].Text.Equals(instance.InstanceId.ToString()))
                    item.SubItems[2].Text = "等待VP审批";
            }
        }
        //副总经理审批的按钮
        private void btnVPApproved_Click(object sender, EventArgs e)
        {
            instance = runtime.GetWorkflow(new Guid(ListViewExisting.SelectedItems[0].Text));
            VPApproved(null, new ExternalDataEventArgs(instance.InstanceId));
            foreach (ListViewItem item in ListViewExisting.Items)
            {
                if (item.SubItems[0].Text.Equals(instance.InstanceId.ToString()))
                    item.SubItems[2].Text = "审批通过";
            }
        }
        #region 读写XML的方法
        /// <summary>
        /// 从XML文件中读取已经钝化的工作流实例的信息
        /// </summary>
        public void LoadWorkflowData()
        {
            XmlTextReader reader = new XmlTextReader(instanceFilename);
            try
            {
                while (reader.Read())
                {
                    if (reader.Name.Equals("WorkflowInstance"))
                    {
                        ListViewExisting.Items.Add(
                            new ListViewItem(
                                new String[] { reader.GetAttribute("InstanceId"),
                                            reader.GetAttribute("Day"),
                                    reader.GetAttribute("State")}));
                    }
                }
                reader.Close();
            }
            catch (FileNotFoundException) { }
        }
        /// <summary>
        /// 保存工作流实例的信息到XML文件中
        /// </summary>
        public void SaveWorkflowData()
        {
            XmlTextWriter writer = new XmlTextWriter(instanceFilename, Encoding.Unicode);
            writer.WriteStartElement("WorkflowInstances");
            foreach (ListViewItem item in ListViewExisting.Items)
            {
                writer.WriteStartElement("WorkflowInstance");
                writer.WriteAttributeString("InstanceId", item.SubItems[0].Text);
                writer.WriteAttributeString("Day", item.SubItems[1].Text);
                writer.WriteAttributeString("State", item.SubItems[2].Text);
                writer.WriteEndElement();
            }
            writer.WriteEndElement();
            writer.Flush();
            writer.Close();
        }
        #endregion
    }

  执行效果如下:

  

  由于在关闭窗口的时候,会将当前工作流的执行状态保存到XML文件和数据库中,而下次打开的时候,回去读取上次执行到的状态,所以当工作流执行到某一步关闭窗口再打开,会显示上次保存的状态。

<think>我们有一个XML字符串,需要从中提取AuthCode节点的值。 根据引用[1]中提到的jsoup库,它是用于解析HTML的,而我们的需求是解析XML。虽然jsoup也可以解析XML,但通常对于XML解析,我们更倾向于使用专门的XML解析器,如DOM、SAX或者JDOM等。 但是,考虑到用户之前的问题是关于从XML中读取ToUserName,并且已经使用了DOM解析器,这里我们继续使用标准库的DOM解析器来解析XML字符串。 另外,引用[2]是关于拦截器的配置,与当前问题无关,可以忽略。 步骤: 1. 将XML字符串转换为输入流(或使用字符串作为源)。 2. 使用DocumentBuilder解析该字符串,构建Document对象。 3. 通过标签名获取AuthCode节点,并读取其文本内容。 注意:XML字符串中可能包含多个节点,我们假设AuthCode是根节点下的一个直接子节点,或者我们可以通过遍历来获取。 示例XML字符串(假设): <xml> <AuthCode>123456</AuthCode> ...其他节点... </xml> 如果AuthCode节点在更深层级,我们需要调整获取方式(例如通过遍历或使用XPath)。 由于用户没有提供完整的XML结构,我们假设AuthCode是根节点下的一个直接子节点。 代码示例:</think>### Java解析XML字符串获取AuthCode节点值 要从XML字符串中提取AuthCode节点的值,可以使用Java的标准XML解析库(DOM或SAX)。以下是两种实现方法: #### 方法1:使用DOM解析器(推荐) ```java import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.NodeList; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import java.io.ByteArrayInputStream; public class XmlAuthCodeParser { public static void main(String[] args) { String xmlString = "<xml><AuthCode>ABC123XYZ</AuthCode></xml>"; // 替换为实际XML try { // 1. 创建DOM解析器 DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); DocumentBuilder builder = factory.newDocumentBuilder(); // 2. 将字符串转换为输入流 ByteArrayInputStream input = new ByteArrayInputStream(xmlString.getBytes()); // 3. 解析XML Document doc = builder.parse(input); doc.getDocumentElement().normalize(); // 4. 获取AuthCode节点 NodeList nodeList = doc.getElementsByTagName("AuthCode"); if (nodeList.getLength() > 0) { // 5. 提取节点值 Element element = (Element) nodeList.item(0); String authCode = element.getTextContent(); System.out.println("AuthCode: " + authCode); // 输出: ABC123XYZ } else { System.out.println("AuthCode节点未找到"); } } catch (Exception e) { e.printStackTrace(); } } } ``` #### 方法2:使用SAX解析器(适合大文件) ```java import org.xml.sax.Attributes; import org.xml.sax.InputSource; import org.xml.sax.SAXException; import org.xml.sax.helpers.DefaultHandler; import javax.xml.parsers.SAXParser; import javax.xml.parsers.SAXParserFactory; import java.io.StringReader; public class SaxAuthCodeParser { public static void main(String[] args) { String xmlString = "<xml><AuthCode>DEF456UVW</AuthCode></xml>"; // 替换为实际XML try { SAXParserFactory factory = SAXParserFactory.newInstance(); SAXParser saxParser = factory.newSAXParser(); // 自定义处理器 DefaultHandler handler = new DefaultHandler() { boolean isAuthCode = false; public void startElement(String uri, String localName, String qName, Attributes attributes) { if (qName.equalsIgnoreCase("AuthCode")) { isAuthCode = true; } } public void characters(char[] ch, int start, int length) { if (isAuthCode) { String authCode = new String(ch, start, length); System.out.println("AuthCode: " + authCode); // 输出: DEF456UVW isAuthCode = false; } } }; // 解析字符串 saxParser.parse(new InputSource(new StringReader(xmlString)), handler); } catch (Exception e) { e.printStackTrace(); } } } ``` ### 关键步骤说明: 1. **XML字符串处理**: - 使用`ByteArrayInputStream`(DOM)或`StringReader`(SAX)将字符串转换为可解析格式 - 确保XML格式正确(标签闭合、编码一致) 2. **节点定位技巧**: - 如果AuthCode不在根目录下,需遍历父节点: ```java Element parent = (Element) doc.getElementsByTagName("ParentNode").item(0); String authCode = parent.getElementsByTagName("AuthCode").item(0).getTextContent(); ``` 3. **命名空间处理**: ```java factory.setNamespaceAware(true); // 启用命名空间支持 // 解析时使用带前缀的标签名:<ns:AuthCode> ``` 4. **错误处理建议**: - 添加空值检查:`if(nodeList != null && nodeList.getLength() > 0)` - 捕获特定异常:`ParserConfigurationException | SAXException | IOException` > **提示**:如果XML结构复杂,建议使用XPath表达式: > ```java > XPath xpath = XPathFactory.newInstance().newXPath(); > String authCode = xpath.evaluate("//AuthCode/text()", doc); > ``` ### 性能对比 | 方法 | 适用场景 | 内存占用 | 速度 | |------|----------|----------|------| | DOM | 小型XML | 高 | 快 | | SAX | 大型XML | 低 | 中等 | | XPath| 复杂查询 | 中等 | 慢 | 根据实际需求选择合适的方法,对于简单的AuthCode提取,DOM解析器是最直接高效的解决方案[^1]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值