Spring學(xué)習(xí)之整合Activiti(一)

上一篇:Spring學(xué)習(xí)之整合MyBatis
下一篇:Spring學(xué)習(xí)之整合Activiti(二)

1. 背景

Activiti是現(xiàn)在應(yīng)用很廣的一個(gè)流程框架,自己在學(xué)習(xí)過(guò)程中看到官網(wǎng)有Activiti Modeler可以使用頁(yè)面管理Activiti流程集漾,所以試著自己整合SpringMVC+Activiti Modeler裆站。

1.1. 工作流與工作流引擎

工作流(workflow)就是工作流程的計(jì)算模型铃慷,即將工作流程中的工作如何前后組織在一起的邏輯和規(guī)則在計(jì)算機(jī)中以恰當(dāng)?shù)哪P瓦M(jìn)行表示并對(duì)其實(shí)施計(jì)算。它主要解決的是“使在多個(gè)參與者之間按照某種預(yù)定義的規(guī)則傳遞文檔桂躏、信息或任務(wù)的過(guò)程自動(dòng)進(jìn)行宋渔,從而實(shí)現(xiàn)某個(gè)預(yù)期的業(yè)務(wù)目標(biāo),或者促使此目標(biāo)的實(shí)現(xiàn)”督暂。(我的理解就是:將部分或者全部的工作流程、邏輯讓計(jì)算機(jī)幫你來(lái)處理穷吮,實(shí)現(xiàn)自動(dòng)化)

所謂工作流引擎是指workflow作為應(yīng)用系統(tǒng)的一部分逻翁,并為之提供對(duì)各應(yīng)用系統(tǒng)有決定作用的根據(jù)角色、分工和條件的不同決定信息傳遞路由捡鱼、內(nèi)容等級(jí)等核心解決方案八回。

1.2. BPMN2.0規(guī)范

BPMN(Business Process Model and Notation)--業(yè)務(wù)流程模型與符號(hào)。

BPMN是一套流程建模的標(biāo)準(zhǔn)驾诈,主要目標(biāo)是被所有業(yè)務(wù)用戶容易理解的符號(hào)缠诅,支持從創(chuàng)建流程輪廓的業(yè)務(wù)分析到這些流程的最終實(shí)現(xiàn),知道最終用戶的管理監(jiān)控乍迄。

通俗一點(diǎn)其實(shí)就是一套規(guī)范管引,畫(huà)流程模型的規(guī)范。流程模型包括:流程圖闯两、協(xié)作圖褥伴、編排圖、會(huì)話圖漾狼。詳細(xì)信息請(qǐng)google重慢。

1.3. Activiti概述

1.3.1. Activiti由來(lái)

學(xué)習(xí)過(guò)Activiti的朋友都知道,Activiti的創(chuàng)始人也就是JBPM(也是一個(gè)優(yōu)秀的BPM引擎)的創(chuàng)始人逊躁,從Jboss離職后開(kāi)發(fā)了一個(gè)新的BPM引擎:Activiti似踱。所以,Activiti有很多地方都有JBPM的影子稽煤。所以核芽,據(jù)說(shuō)學(xué)習(xí)過(guò)JBPM的朋友學(xué)起Activiti來(lái)非常順手。

由于本人之前沒(méi)有工作流及JBPM的相關(guān)基礎(chǔ)念脯,剛開(kāi)始學(xué)習(xí)Activiti的時(shí)候可以說(shuō)是無(wú)比痛苦的狞洋,根本不知道從何下手,這里也建議大家先進(jìn)行工作流及BPMN2.0規(guī)范的學(xué)習(xí)绿店,有了一定的基礎(chǔ)后吉懊,再著手學(xué)習(xí)Activiti庐橙。

1.3.2. Activiti簡(jiǎn)介

Activiti是一個(gè)開(kāi)源的工作流引擎,它實(shí)現(xiàn)了BPMN 2.0規(guī)范借嗽,可以發(fā)布設(shè)計(jì)好的流程定義态鳖,并通過(guò)api進(jìn)行流程調(diào)度。

Activiti 作為一個(gè)遵從 Apache 許可的工作流和業(yè)務(wù)流程管理開(kāi)源平臺(tái)恶导,其核心是基于 Java 的超快速浆竭、超穩(wěn)定的 BPMN2.0 流程引擎,強(qiáng)調(diào)流程服務(wù)的可嵌入性和可擴(kuò)展性惨寿,同時(shí)更加強(qiáng)調(diào)面向業(yè)務(wù)人員邦泄。

Activiti 流程引擎重點(diǎn)關(guān)注在系統(tǒng)開(kāi)發(fā)的易用性和輕量性上。每一項(xiàng) BPM 業(yè)務(wù)功能 Activiti 流程引擎都以服務(wù)的形式提供給開(kāi)發(fā)人員裂垦。通過(guò)使用這些服務(wù)顺囊,開(kāi)發(fā)人員能夠構(gòu)建出功能豐富、輕便且高效的 BPM 應(yīng)用程序蕉拢。

2. 前期準(zhǔn)備

本文是在Spring學(xué)習(xí)之整合MyBatis的基礎(chǔ)上完成的特碳,所以不清楚的可以點(diǎn)擊查看

2.1. Activiti所需環(huán)境

使用Activiti,首先當(dāng)然要有jdk了晕换!6+版本就可以了午乓。其次,要有一款I(lǐng)DE闸准,我們當(dāng)然會(huì)使用Eclipse益愈。然后,web容器當(dāng)然也要有恕汇,這里使用Tomcat7.0版本腕唧。然后就是Activiti的Eclipse插件了,這個(gè)后面再介紹瘾英。

2.2. 下載Activiti Demo包

下載activiti-5.22.0.rar枣接,官網(wǎng)地址大家可以自行百度,但是下載會(huì)被墻缺谴,網(wǎng)盤(pán)地址:https://pan.baidu.com/s/1XVTammPbIrbzU1MK7TBFOA

2.3. 配置pom.xml文件

新增activiti依賴:

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <junit.version>4.10</junit.version>
    <spring.version>4.1.3.RELEASE</spring.version>
    <jackson.version>2.7.4</jackson.version>
    <activiti.version>5.22.0</activiti.version>
  </properties>

<!-- 其他依賴省略... -->
<!-- activiti start -->
    <dependency>
        <groupId>org.activiti</groupId>
        <artifactId>activiti-engine</artifactId>
        <version>${activiti.version}</version>
    </dependency>

    <dependency>
        <groupId>org.activiti</groupId>
        <artifactId>activiti-spring</artifactId>
        <version>${activiti.version}</version>
    </dependency>
    <dependency>
        <groupId>org.activiti</groupId>
        <artifactId>activiti-bpmn-model</artifactId>
        <version>${activiti.version}</version>
    </dependency>
    <dependency>
        <groupId>org.activiti</groupId>
        <artifactId>activiti-bpmn-layout</artifactId>
        <version>${activiti.version}</version>
    </dependency>
    <dependency>
        <groupId>org.activiti</groupId>
        <artifactId>activiti-common-rest</artifactId>
        <version>${activiti.version}</version>
    </dependency>
    <dependency>
        <groupId>org.activiti</groupId>
        <artifactId>activiti-crystalball</artifactId>
        <version>${activiti.version}</version>
    </dependency>
    <dependency>
        <groupId>org.activiti</groupId>
        <artifactId>activiti-diagram-rest</artifactId>
        <version>${activiti.version}</version>
    </dependency>
    <dependency>
        <groupId>org.activiti</groupId>
        <artifactId>activiti-explorer</artifactId>
        <version>${activiti.version}</version>
    </dependency>
    <dependency>
        <groupId>org.activiti</groupId>
        <artifactId>activiti-json-converter</artifactId>
        <version>${activiti.version}</version>
    </dependency>
    <dependency>
        <groupId>org.activiti</groupId>
        <artifactId>activiti-modeler</artifactId>
        <version>${activiti.version}</version>
    </dependency>
    <dependency>
        <groupId>org.activiti</groupId>
        <artifactId>activiti-simple-workflow</artifactId>
        <version>${activiti.version}</version>
    </dependency>
    <dependency>
        <groupId>org.apache.xmlgraphics</groupId>
        <artifactId>xmlgraphics-commons</artifactId>
        <version>1.2</version>
    </dependency>
    <!-- activiti end -->

Batik包 在添加activiti-modeler依賴后自動(dòng)加載但惶,不用顯式添加依賴

2.4. 配置spring-activiti.xml文件

在resources/spring/ 新建spring-activiti.xml配置文件:

image.png

spring-activiti.xml文件內(nèi)容如下:

<?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:context="http://www.springframework.org/schema/context" xmlns:mvc="http://www.springframework.org/schema/mvc"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.1.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.1.xsd
        http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.1.xsd">
        

    <!-- ==================== Activiti配置 start =================== -->
    <!-- 單例json對(duì)象 -->
    <bean id="objectMapper" class="com.fasterxml.jackson.databind.ObjectMapper"/>

    <!-- activiti的processEngine配置 -->
    <bean id="processEngineConfiguration" class="org.activiti.spring.SpringProcessEngineConfiguration">
        <property name="dataSource" ref="jdbcDataSource" />
        <property name="transactionManager" ref="transactionManager" />
        <!-- 自動(dòng)創(chuàng)建表 -->
        <property name="databaseSchemaUpdate" value="false" />
        <!-- 是否激活A(yù)ctiviti的任務(wù)調(diào)度 -->
        <property name="jobExecutorActivate" value="false" />
        <!-- 是否開(kāi)啟工作的數(shù)據(jù)日志 -->
        <!-- <property name="enableDatabaseEventLogging" value="true" /> -->
        <!--<property name="history" value="full"/>-->
        <property name="processDefinitionCacheLimit" value="10"/>
        <!-- mail -->
        <!-- <property name="mailServerHost" value="localhost"/>
        <property name="mailServerUsername" value="kafeitu"/>
        <property name="mailServerPassword" value="000000"/>
        <property name="mailServerPort" value="2025"/> -->
        <!-- UUID作為主鍵生成策略  -->
        <!-- <property name="idGenerator" ref="uuidGenerator" /> -->
        
        <!-- 生成流程圖的字體 -->
        <property name="activityFontName" value="宋體"/>
        <property name="labelFontName" value="宋體"/>
        
        <!-- 緩存支持
        <property name="processDefinitionCache">
            <bean class="me.kafeitu.demo.activiti.util.cache.DistributedCache" />
        </property>-->

        <!-- 自動(dòng)部署 -->
        <!-- <property name="deploymentResources">
            <list>
                <value>classpath*:/deployments/*</value>
            </list>
        </property> -->

        <!-- 自定義表單字段類型 -->
        <!-- <property name="customFormTypes">
            <list>
                <bean class="me.kafeitu.demo.activiti.activiti.form.UsersFormType"/>
            </list>
        </property> -->
        <!--不創(chuàng)建identity表 -->
        <property name="dbIdentityUsed" value="false"/>
        <!-- 自定義用戶管理 -->
        <property name="customSessionFactories">
            <list>
                <bean class="com.zr.workflow.activiti.utils.CustomUserEntityManagerFactory">
                    <property name="customUserEntityManager" ref="customUserEntityManager"></property>
                </bean>
                <bean class="com.zr.workflow.activiti.utils.CustomGroupEntityManagerFactory">
                    <property name="customGroupEntityManager" ref="customGroupEntityManager"></property>
                </bean>
            </list>
        </property>
    </bean>
    <!-- 注冊(cè)自定義用戶管理類 -->
    <bean id="customUserEntityManager" class="com.zr.workflow.activiti.utils.CustomUserEntityManager"></bean>
    <bean id="customGroupEntityManager" class="com.zr.workflow.activiti.utils.CustomGroupEntityManager"></bean>
    
    
    <!-- 加載activiti引擎processEngine --> 
    <bean id="processEngine" class="org.activiti.spring.ProcessEngineFactoryBean" destroy-method="destroy">
        <property name="processEngineConfiguration" ref="processEngineConfiguration" />
    </bean>
    
    <!-- activiti的7大服務(wù)接口 -->
    <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="formService" factory-bean="processEngine" factory-method="getFormService" />
    <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" /> -->
    <!-- ==================== Activiti配置 end =================== -->
</beans>

其中,需要注意的是:
1湿蛔、databaseSchemaUpdate膀曾,項(xiàng)目啟動(dòng)是否自動(dòng)創(chuàng)建數(shù)據(jù)表(activiti的23張表),這里我設(shè)置成false阳啥,因?yàn)槲覜](méi)有用activiti的用戶管理表添谊,而是采用自定義的用戶管理表,所以在項(xiàng)目啟動(dòng)之前需要將所有需要的表創(chuàng)建完成察迟。
2斩狱、dbIdentityUsed:是否創(chuàng)建identity用戶相關(guān)表耳高;由于項(xiàng)目已經(jīng)有一套用戶管理表,所以這里設(shè)置成false所踊;
3泌枪、新增property customSessionFactories 指定自定義用戶管理工廠,包括:用戶管理和組管理秕岛。
其中班套,用戶管理工廠CustomUserEntityManagerFactory.java如下:

package com.zr.workflow.activiti.util;

import javax.annotation.Resource;

import org.activiti.engine.impl.interceptor.Session;
import org.activiti.engine.impl.interceptor.SessionFactory;
import org.activiti.engine.impl.persistence.entity.UserIdentityManager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

/**
 * 自定義用戶管理罚舱,用戶session
 * 這里的屬性需要與`spring-activiti.xml`中的該類的property一致
 * 不能使用identityService
 * @author Administrator
 *
 */
@Service
public class CustomUserEntityManagerFactory implements SessionFactory {
    
    @Resource
    private CustomUserEntityManager customUserEntityManager;
    

    @Override
    public Class<?> getSessionType() {
        return UserIdentityManager.class;
    }

    @Override
    public Session openSession() {
        return customUserEntityManager;
    }

    
    @Autowired
    public void setCustomUserEntityManager(CustomUserEntityManager customUserEntityManager) {
        this.customUserEntityManager = customUserEntityManager;
    }

}


組管理工廠 CustomGroupEntityManagerFactory.java:

package com.zr.workflow.activiti.util;

import javax.annotation.Resource;

import org.activiti.engine.impl.interceptor.Session;
import org.activiti.engine.impl.interceptor.SessionFactory;
import org.activiti.engine.impl.persistence.entity.GroupIdentityManager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

/**
 * 自定義用戶管理顷蟆,用戶組session
 * 這里的屬性需要與spring-activiti.xml中的該類的property一致
 * 不能使用identityService
 * @author Administrator
 *
 */
@Service
public class CustomGroupEntityManagerFactory implements SessionFactory {
    
    @Resource
    private CustomGroupEntityManager customGroupEntityManager;
    
    
    @Override
    public Class<?> getSessionType() {
        return GroupIdentityManager.class;
    }

    @Override
    public Session openSession() {
        return customGroupEntityManager;
    }

    @Autowired  
    public void setCustomGroupEntityManager(CustomGroupEntityManager customGroupEntityManager) {
        this.customGroupEntityManager = customGroupEntityManager;
    }
}


4爽待、注冊(cè)自動(dòng)義用戶管理類。

由于我們這個(gè)項(xiàng)目中設(shè)置任務(wù)執(zhí)行人時(shí)沒(méi)有涉及到候選組遏考,都是指定具體的執(zhí)行人或獲取某個(gè)角色組中所有的人員叠殷,然后調(diào)用setCandidateUsers方法將某個(gè)候選組所有的成員加進(jìn)去,如果項(xiàng)目需要設(shè)置候選組诈皿,請(qǐng)擴(kuò)展這兩個(gè)類:CustomUserEntityManagerCustomGroupEntityManager,重寫(xiě)其中的
hasUser()(必須實(shí)現(xiàn))像棘、
getUserName()(可選)稽亏、
getUserPassword()(可選)、
getUserEmail()(可選)缕题、
getRoleList()(必須實(shí)現(xiàn))截歉。
然后將實(shí)現(xiàn)類注冊(cè)到spring_activiti.xml中,替換以下兩行:

    <!-- 注冊(cè)自定義用戶管理類 -->
    <bean id="customUserEntityManager" class="com.zr.workflow.activiti.utils.CustomUserEntityManager"></bean>
    <bean id="customGroupEntityManager" class="com.zr.workflow.activiti.utils.CustomGroupEntityManager"></bean>
    

CustomUserEntityManager.java代碼如下:

package com.zr.workflow.activiti.util;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import org.activiti.engine.identity.Group;
import org.activiti.engine.identity.User;
import org.activiti.engine.impl.persistence.entity.GroupEntity;
import org.activiti.engine.impl.persistence.entity.UserEntity;
import org.activiti.engine.impl.persistence.entity.UserEntityManager;
import org.springframework.dao.EmptyResultDataAccessException;


import org.springframework.stereotype.Component;

@Component
public class CustomUserEntityManager extends UserEntityManager {


    public User findUserById(String userId) {
        System.out.println("CustomUserEntityManager  findUserById userId:" + userId);
        if (userId == null)
            return null;
        try {
            UserEntity userEntity = new UserEntity();
            boolean hasUser = hasUser(userId);
            if (!hasUser)return null;

            userEntity.setId(userId);
            userEntity.setFirstName(getUserName(userId));
            userEntity.setPassword(getUserPassword(userId));
            userEntity.setEmail(getUserEmail(userId));
            userEntity.setRevision(1);
            return userEntity;
        } catch (EmptyResultDataAccessException e) {
            e.printStackTrace();
            return null;
        }
    }

    public boolean hasUser(String userId) {
        return true;
    }
    
    public List<Group> findGroupsByUser(String userId) {
        System.out.println("CustomUserEntityManager  findGroupsByUser userId:" + userId);
        if (userId == null)
            return null;
        boolean hasUser = hasUser(userId);
        if (!hasUser)return null;
        List<Map<String,Object>> roleList = getRoleList(userId);
        List<Group> groupEntitys = new ArrayList<Group>();
        if(null != roleList) {
            for (Map<String, Object> role : roleList) {
                String roleCode = null == role.get("roleCode")?"":role.get("roleCode").toString();
                String roleName = null == role.get("roleName")?"":role.get("roleName").toString();
                GroupEntity groupEntity = toActivitiGroup(roleCode,roleName);
                groupEntitys.add(groupEntity);
            }
        }
        return groupEntitys;
    }

    public static GroupEntity toActivitiGroup(String roleCode,String roleName) {
        GroupEntity groupEntity = new GroupEntity();
        groupEntity.setRevision(1);
        groupEntity.setType("assignment");
        groupEntity.setId(roleCode);
        groupEntity.setName(roleName);
        return groupEntity;
    }
    
    public String getUserName(String userId) {
        return "";
    }
    
    public String getUserPassword(String userId) {
        return "";
    }
    
    public String getUserEmail(String userId) {
        return "";
    }
    
    public List<Map<String, Object>> getRoleList(String userId) {
        return null;
//      List<Map<String,Object>> roleList = new ArrayList<>();
//      List<MisUserRole> misUserRoleList = misUserRoleDao.findByUserId(userId);
//      for (MisUserRole misUserRole : misUserRoleList) {
//          final String roleId = misUserRole.getRoleId();
//          boolean isExitRole = misRoleDao.findRoleById(roleId) != null && misRoleDao.findRoleById(roleId).size()>0;
//          MisRole role = isExitRole ? misRoleDao.findRoleById(roleId).get(0) : null;
//          Map<String, Object> roleMap = new HashMap<>();
//          if(role != null){
//              roleMap.put("roleCode", role.getCode());
//              roleMap.put("roleName", role.getName());
//              
//          }
//          roleList.add(roleMap);
//      }
//      return roleList;
    }

}

CustomGroupEntityManager.java代碼如下:

package com.zr.workflow.activiti.util;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import org.activiti.engine.identity.Group;
import org.activiti.engine.impl.persistence.entity.GroupEntity;
import org.activiti.engine.impl.persistence.entity.GroupEntityManager;
import org.springframework.stereotype.Component;


@Component
public class CustomGroupEntityManager extends GroupEntityManager {

    public boolean hasUser(String userId) {
        return true;
    }
    
    public List<Group> findGroupsByUser(String userId) {
        System.out.println("CustomUserEntityManager  findGroupsByUser userId:" + userId);
        if (userId == null)
            return null;
        boolean hasUser = hasUser(userId);
        if (!hasUser)return null;
        List<Map<String,Object>> roleList = getRoleList(userId);
        List<Group> groupEntitys = new ArrayList<Group>();
        if(null != roleList) {
            for (Map<String, Object> role : roleList) {
                String roleCode = null == role.get("roleCode")?"":role.get("roleCode").toString();
                String roleName = null == role.get("roleName")?"":role.get("roleName").toString();
                GroupEntity groupEntity = toActivitiGroup(roleCode,roleName);
                groupEntitys.add(groupEntity);
            }
        }
        return groupEntitys;
    }

    public static GroupEntity toActivitiGroup(String roleCode,String roleName) {
        GroupEntity groupEntity = new GroupEntity();
        groupEntity.setRevision(1);
        groupEntity.setType("assignment");
        groupEntity.setId(roleCode);
        groupEntity.setName(roleName);
        return groupEntity;
    }
    
    public List<Map<String, Object>> getRoleList(String userId) {
        return null;
//      List<Map<String,Object>> roleList = new ArrayList<>();
//      List<MisUserRole> misUserRoleList = misUserRoleDao.findByUserId(userId);
//      for (MisUserRole misUserRole : misUserRoleList) {
//          final String roleId = misUserRole.getRoleId();
//          boolean isExitRole = misRoleDao.findRoleById(roleId) != null && misRoleDao.findRoleById(roleId).size()>0;
//          MisRole role = isExitRole ? misRoleDao.findRoleById(roleId).get(0) : null;
//          Map<String, Object> roleMap = new HashMap<>();
//          if(role != null){
//              roleMap.put("roleCode", role.getCode());
//              roleMap.put("roleName", role.getName());
//              
//          }
//          roleList.add(roleMap);
//      }
//      return roleList;
    }
}

3. 開(kāi)始整合

3.1. 代碼拷貝

把Activiti-webapp-explorer2項(xiàng)目的resources下的stencilset.json文件拷至我的項(xiàng)目中的resources目錄下:

image.png

解壓出activiti-5.22.0.rar烟零,看到如下目錄:

  1. database:里面存放的是Activiti使用到的數(shù)據(jù)庫(kù)信息的sql文件瘪松,它支持的數(shù)據(jù)庫(kù)類型如下圖,使用時(shí)只需執(zhí)行你自己的數(shù)據(jù)庫(kù)類型的文件即可锨阿。如:你的數(shù)據(jù)庫(kù)是mysql宵睦,那么就執(zhí)行activiti.mysql.create.*.sql即可。( 注意: 這里由于采用的是自定義用戶管理墅诡,則去掉activiti其中創(chuàng)建用戶相關(guān)表的sql語(yǔ)句壳嚎,最終的sql文件請(qǐng)見(jiàn)項(xiàng)目中的init_activities_empty.sql:):
image.png

注意:
sql中新增表act_cus_user_task:


-- ----------------------------
-- Table structure for act_cus_user_task
-- ----------------------------
DROP TABLE IF EXISTS `act_cus_user_task`;
CREATE TABLE `act_cus_user_task` (
  `ID` int(11) NOT NULL AUTO_INCREMENT,
  `PROC_DEF_KEY` varchar(255) DEFAULT NULL COMMENT '流程id',
  `PROC_DEF_NAME` varchar(255) DEFAULT NULL COMMENT '流程名',
  `TASK_DEF_KEY` varchar(255) DEFAULT NULL COMMENT '節(jié)點(diǎn)id',
  `TASK_NAME` varchar(255) DEFAULT NULL COMMENT '節(jié)點(diǎn)名',
  `ACTIVITY_TYPE` varchar(255) DEFAULT '' COMMENT '當(dāng)前Activiti節(jié)點(diǎn)類型:N-普通用戶任務(wù);M-多實(shí)例節(jié)點(diǎn)',
  `TASK_TYPE` varchar(255) DEFAULT NULL COMMENT '節(jié)點(diǎn)的處理人員類型:assignee(人員)末早、candidateUser(候選人)烟馅、candidateGroup(候選組)',
  `CANDIDATE_NAME` varchar(255) DEFAULT NULL COMMENT '執(zhí)行人名',
  `CANDIDATE_IDS` varchar(255) DEFAULT NULL COMMENT '執(zhí)行人id',
  `GROUP_ID` varchar(255) DEFAULT NULL COMMENT '組id',
  `GROUP_NAME` varchar(255) DEFAULT NULL COMMENT '組名稱',
  PRIMARY KEY (`ID`)
) ENGINE=InnoDB AUTO_INCREMENT=465 DEFAULT CHARSET=utf8;

act_cus_user_task:業(yè)務(wù)用戶信息關(guān)聯(lián)至工作流用戶任務(wù)表

  1. docs:毫無(wú)疑問(wèn),api文檔然磷。

  2. libs:使用Activiti所需要的所有的jar包和源文件郑趁。

  3. wars:官方給我們提供的示例Demo,通過(guò)使用Demo可以更加快速的了解Activiti姿搜。

找到wars目錄下的 activiti-explorer.war寡润, 將其拷貝到Tomcat 的 webapps目錄下捆憎,然后運(yùn)行tomcat: /bin/statup.bat(如果啟動(dòng)時(shí)控制臺(tái)一閃而過(guò),請(qǐng)看Tomcat7中雙擊bin文件的startup.bat一閃而過(guò)解決辦法)悦穿,啟動(dòng)tomcat 之后攻礼,會(huì)自動(dòng)將activiti-explorer.war 解壓。

image.png

進(jìn)入到下圖目錄中栗柒,將diagram-viewer礁扮,editor-app和modeler.html拷貝到自己工程的webapp目錄下。

image.png

將下圖路徑中的StencilsetRestResource.class瞬沦。和下圖路徑中的ModelEditorJsonRestResource.class太伊,ModelSaveRestResource.class。反編譯.

image.png
image.png

在自己的項(xiàng)目中新建對(duì)應(yīng)的class逛钻,將反編譯內(nèi)容復(fù)制進(jìn)去僚焦,如圖:

image.png

其中,StencilsetRestResource類,是加載模板設(shè)置,加載resources下的stencilset.json,其代碼如下:

package com.zr.activiti.controller.design.model;

import java.io.InputStream;
import org.activiti.engine.ActivitiException;
import org.apache.commons.io.IOUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class StencilsetRestResource
{
  @RequestMapping(value={"/editor/stencilset"}, method={org.springframework.web.bind.annotation.RequestMethod.GET}, produces={"application/json;charset=utf-8"})
  public String getStencilset()
  {
      System.out.println("StencilsetRestResource.getStencilset-----------");
    InputStream stencilsetStream = getClass().getClassLoader().getResourceAsStream("stencilset.json");
    try {
      return IOUtils.toString(stencilsetStream, "utf-8");
    } catch (Exception e) {
      throw new ActivitiException("Error while loading stencil set", e);
    }
  }
}

ModelEditorJsonRestResource類是根據(jù)模型名稱讀取以Json存儲(chǔ)在act_ge_bytearray表中的source,編輯器接收到之后解析展示為圖片,其核心代碼如下:

 package com.zr.activiti.controller.design.model;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import org.activiti.editor.constants.ModelDataJsonConstants;
import org.activiti.engine.ActivitiException;
import org.activiti.engine.RepositoryService;
import org.activiti.engine.repository.Model;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class ModelEditorJsonRestResource
  implements ModelDataJsonConstants
{
  protected static final Logger LOGGER = LoggerFactory.getLogger(ModelEditorJsonRestResource.class);

  @Autowired
  private RepositoryService repositoryService;

  @Autowired
  private ObjectMapper objectMapper;

  @RequestMapping(value={"/model/{modelId}/json"}, method={org.springframework.web.bind.annotation.RequestMethod.GET}, produces={"application/json"})
  public ObjectNode getEditorJson(@PathVariable String modelId) { ObjectNode modelNode = null;

    System.out.println("ModelEditorJsonRestResource.getEditorJson---------");
    Model model = this.repositoryService.getModel(modelId);

    if (model != null) {
      try {
        if (StringUtils.isNotEmpty(model.getMetaInfo())) {
          modelNode = (ObjectNode)this.objectMapper.readTree(model.getMetaInfo());
        } else {
          modelNode = this.objectMapper.createObjectNode();
          modelNode.put("name", model.getName());
        }
        modelNode.put("modelId", model.getId());
        ObjectNode editorJsonNode = (ObjectNode)this.objectMapper.readTree(new String(this.repositoryService
          .getModelEditorSource(model
          .getId()), "utf-8"));
        modelNode.put("model", editorJsonNode);
      }
      catch (Exception e) {
        LOGGER.error("Error creating model JSON", e);
        throw new ActivitiException("Error creating model JSON", e);
      }
    }
    return modelNode;
  }
}

ModelSaveRestResource就是將經(jīng)過(guò)編輯器編輯過(guò)的模型保存起來(lái),核心代碼如下:

 package com.zr.activiti.controller.design.model;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;


import org.activiti.editor.constants.ModelDataJsonConstants;
import org.activiti.engine.ActivitiException;
import org.activiti.engine.RepositoryService;
import org.activiti.engine.repository.Model;
import org.apache.batik.transcoder.TranscoderInput;
import org.apache.batik.transcoder.TranscoderOutput;
import org.apache.batik.transcoder.image.PNGTranscoder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.util.MultiValueMap;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class ModelSaveRestResource
  implements ModelDataJsonConstants
{
  protected static final Logger LOGGER = LoggerFactory.getLogger(ModelSaveRestResource.class);

  @Autowired
  private RepositoryService repositoryService;

  @Autowired
  private ObjectMapper objectMapper;
  
  @RequestMapping(value={"/model/{modelId}/save"}, method={org.springframework.web.bind.annotation.RequestMethod.PUT})
  @ResponseStatus(HttpStatus.OK)
  public void saveModel(@PathVariable String modelId, @RequestBody MultiValueMap<String, String> values) { 
      try { 
          Model model = this.repositoryService.getModel(modelId);
          System.out.println("ModelSaveRestResource.saveModel----------");
          ObjectNode modelJson = (ObjectNode)this.objectMapper.readTree(model.getMetaInfo());
    
          modelJson.put("name", (String)values.getFirst("name"));
          modelJson.put("description", (String)values.getFirst("description"));
          model.setMetaInfo(modelJson.toString());
          model.setName((String)values.getFirst("name"));
          model.setKey(modelId);
    
          this.repositoryService.saveModel(model);
    
          this.repositoryService.addModelEditorSource(model.getId(), ((String)values.getFirst("json_xml")).getBytes("utf-8"));
    
          InputStream svgStream = new ByteArrayInputStream(((String)values.getFirst("svg_xml")).getBytes("utf-8"));
          TranscoderInput input = new TranscoderInput(svgStream);
    
          PNGTranscoder transcoder = new PNGTranscoder();
    
          ByteArrayOutputStream outStream = new ByteArrayOutputStream();
          TranscoderOutput output = new TranscoderOutput(outStream);
    
          transcoder.transcode(input, output);
          byte[] result = outStream.toByteArray();
          this.repositoryService.addModelEditorSourceExtra(model.getId(), result);
          outStream.close();
        } catch (Exception e){
          LOGGER.error("Error saving model", e);
          throw new ActivitiException("Error saving model", e);
        }
  } 
}

3.2. 核心組件介紹

3.2.1. 關(guān)鍵對(duì)象
  1. Deployment:流程部署對(duì)象,部署一個(gè)流程時(shí)創(chuàng)建曙痘。

  2. ProcessDefinitions:流程定義芳悲,部署成功后自動(dòng)創(chuàng)建。

  3. ProcessInstances:流程實(shí)例边坤,啟動(dòng)流程時(shí)創(chuàng)建名扛。

  4. Task:任務(wù),在Activiti中的Task僅指有角色參與的任務(wù)茧痒,即定義中的UserTask肮韧。

  5. Execution:執(zhí)行計(jì)劃,流程實(shí)例和流程執(zhí)行中的所有節(jié)點(diǎn)都是Execution旺订,如UserTask弄企、ServiceTask等。

3.2.2. 服務(wù)接口
  1. ProcessEngine:流程引擎的抽象区拳,通過(guò)它我們可以獲得我們需要的一切服務(wù)拘领。

  2. RepositoryService:Activiti中每一個(gè)不同版本的業(yè)務(wù)流程的定義都需要使用一些定義文件,部署文件和支持?jǐn)?shù)據(jù)(例如BPMN2.0 XML文件劳闹,表單定義文件院究,流程定義圖像文件等),這些文件都存儲(chǔ)在Activiti內(nèi)建的Repository中本涕。RepositoryService提供了對(duì) repository的存取服務(wù)业汰。

  3. RuntimeService:在Activiti中,每當(dāng)一個(gè)流程定義被啟動(dòng)一次之后菩颖,都會(huì)生成一個(gè)相應(yīng)的流程對(duì)象實(shí)例样漆。RuntimeService提供了啟動(dòng)流程、查詢流程實(shí)例晦闰、設(shè)置獲取流程實(shí)例變量等功能放祟。此外它還提供了對(duì)流程部署鳍怨,流程定義和流程實(shí)例的存取服務(wù)。

  4. TaskService: 在Activiti中業(yè)務(wù)流程定義中的每一個(gè)執(zhí)行節(jié)點(diǎn)被稱為一個(gè)Task跪妥,對(duì)流程中的數(shù)據(jù)存取鞋喇,狀態(tài)變更等操作均需要在Task中完成。TaskService提供了對(duì)用戶Task 和Form相關(guān)的操作眉撵。它提供了運(yùn)行時(shí)任務(wù)查詢侦香、領(lǐng)取、完成纽疟、刪除以及變量設(shè)置等功能罐韩。

  5. IdentityService: Activiti中內(nèi)置了用戶以及組管理的功能,必須使用這些用戶和組的信息才能獲取到相應(yīng)的Task污朽。IdentityService提供了對(duì)Activiti 系統(tǒng)中的用戶和組的管理功能散吵。

  6. ManagementService: ManagementService提供了對(duì)Activiti流程引擎的管理和維護(hù)功能,這些功能不在工作流驅(qū)動(dòng)的應(yīng)用程序中使用蟆肆,主要用于Activiti系統(tǒng)的日常維護(hù)矾睦。

  7. HistoryService: HistoryService用于獲取正在運(yùn)行或已經(jīng)完成的流程實(shí)例的信息,與RuntimeService中獲取的流程信息不同炎功,歷史信息包含已經(jīng)持久化存儲(chǔ)的永久信息顷锰,并已經(jīng)被針對(duì)查詢優(yōu)化。

現(xiàn)在至少要知道有這些對(duì)象和接口亡问。并結(jié)合Activiti Api這一章節(jié)來(lái)看,你就會(huì)對(duì)部署流程肛宋、啟動(dòng)流程州藕、執(zhí)行任務(wù)等操作有一個(gè)基本的概念。

3.3. 代碼修改

1酝陈、在editor-app目錄下找到app-cfg.js文件床玻,將'contextRoot' : '/activiti-explorer/service',修改為本項(xiàng)目的路徑。如下所示:

/*
 * Activiti Modeler component part of the Activiti project
 * Copyright 2005-2014 Alfresco Software, Ltd. All rights reserved.
 * 
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.

 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 */
'use strict';

var ACTIVITI = ACTIVITI || {};
ACTIVITI.CONFIG = {
        'contextRoot' : appContextRoot+'/service',
    };
//ACTIVITI.CONFIG = {
//  'contextRoot' : '/activiti-explorer/service',
//};

2沉帮、修改modeler.html
去掉Activiti Afresco的logo標(biāo)題欄锈死,并且把樣式上的空白欄去掉:
注意不要把該文本刪除,建議加style=”display:none”,刪除后其會(huì)造成底層下的一些內(nèi)容有40個(gè)像數(shù)的東西顯示不出來(lái)穆壕。:

image.png

在load app-cfg.js之前加上以下腳本:

    <script type="text/javascript">
        var pathName = window.document.location.pathname;
        var appContextRoot=pathName.substring(0,pathName.substr(1).indexOf('/')+1);;
    </script>

如圖所示:

image.png

3.4. 測(cè)試

在瀏覽器中輸入以下地址:
http://localhost:8080/ActivitiWorkFlowDemo(自己項(xiàng)目名)/model/create 請(qǐng)求創(chuàng)建模型接口
獲取到modelId后待牵;
再輸入http://localhost:8080/ActivitiWorkFlowDemo/modeler.html?modelId=獲取到的modelId 即可進(jìn)入modeler.html創(chuàng)建模型頁(yè)面。
結(jié)果如下:

image.png

如果報(bào):
Failed to load resource: the server responded with a status of 404 (Not Found)
首先確認(rèn)路徑?jīng)]有錯(cuò)喇勋。

在請(qǐng)求分發(fā)時(shí)缨该,沒(méi)有找到"/ActivitiWorkFlowDemo/modeler.html"的映射(No mapping found for ...)。原來(lái)是spring mvc攔截了頁(yè)面對(duì)靜態(tài)資源的請(qǐng)求川背,但你的controller中又沒(méi)有這個(gè)路徑的映射贰拿,所以頁(yè)面對(duì)靜態(tài)資源文件的請(qǐng)求并沒(méi)有正確下發(fā)蛤袒,那么該怎么解決這個(gè)問(wèn)題呢?下面我給出參考的方法:

解決方案:

1.采用<mvc:default-servlet-handler />膨更。 在spring mvc的xml配置文件上加上一句:<mvc:default-servlet-handler />妙真。如下圖所示:


    <!-- 靜態(tài)資源文件,不會(huì)被Spring MVC攔截 -->
    <mvc:default-servlet-handler />
    <mvc:resources location="/resources/" mapping="/resources/**" />

加入之后荚守,spring mvc就會(huì)對(duì)進(jìn)入DispatcherServlet的URL進(jìn)行篩查珍德,如果發(fā)現(xiàn)是靜態(tài)資源的請(qǐng)求,就將該請(qǐng)求轉(zhuǎn)由Web應(yīng)用服務(wù)器默認(rèn)的Servlet處理健蕊,如果不是靜態(tài)資源的請(qǐng)求菱阵,才由DispatcherServlet繼續(xù)處理。這個(gè)方法是最快捷的缩功。

2.采用<mvc:resources />晴及。可以使用<mvc:resources />嫡锌,并將靜態(tài)資源放在WEB-INF目錄下(或者其他你喜歡的地方)虑稼,然后在springMVC-servlet中添加如下配置:

<mvc:resources location="/文件路徑" mapping="/映射路徑"/>

根據(jù)實(shí)際情況填寫(xiě)路徑。

3.在web.xml文件中將spring mvc的攔截路徑改為/springmvc/*("springmvc"可以替換成你喜歡的路徑)

附加知識(shí)點(diǎn):Activiti工作流的自帶數(shù)據(jù)表的含義

  1. 資源庫(kù)流程規(guī)則表
    1)act_re_deployment 部署信息表
    2)act_re_model 流程設(shè)計(jì)模型部署表
    3)act_re_procdef 流程定義數(shù)據(jù)表

  2. 運(yùn)行時(shí)數(shù)據(jù)庫(kù)表
    1)act_ru_execution 運(yùn)行時(shí)流程執(zhí)行實(shí)例表
    2)act_ru_identitylink 運(yùn)行時(shí)流程人員表势木,主要存儲(chǔ)任務(wù)節(jié)點(diǎn)與參與者的相關(guān)信息
    3)act_ru_task 運(yùn)行時(shí)任務(wù)節(jié)點(diǎn)表
    4)act_ru_variable 運(yùn)行時(shí)流程變量數(shù)據(jù)表

  3. 歷史數(shù)據(jù)庫(kù)表
    1)act_hi_actinst 歷史節(jié)點(diǎn)表
    2)act_hi_attachment 歷史附件表
    3)act_hi_comment 歷史意見(jiàn)表
    4)act_hi_identitylink 歷史流程人員表
    5)act_hi_detail 歷史詳情表蛛倦,提供歷史變量的查詢
    6)act_hi_procinst 歷史流程實(shí)例表
    7)act_hi_taskinst 歷史任務(wù)實(shí)例表
    8)act_hi_varinst 歷史變量表

  4. 組織機(jī)構(gòu)表
    1)act_id_group 用戶組信息表
    2)act_id_info 用戶擴(kuò)展信息表
    3)act_id_membership 用戶與用戶組對(duì)應(yīng)信息表
    4)act_id_user 用戶信息表
    這四張表很常見(jiàn),基本的組織機(jī)構(gòu)管理啦桌,關(guān)于用戶認(rèn)證方面建議還是自己開(kāi)發(fā)一套溯壶,組件自帶的功能太簡(jiǎn)單,使用中有很多需求難以滿足

  5. 通用數(shù)據(jù)表
    1)act_ge_bytearray 二進(jìn)制數(shù)據(jù)表
    2)act_ge_property 屬性數(shù)據(jù)表存儲(chǔ)整個(gè)流程引擎級(jí)別的數(shù)據(jù),初始化表結(jié)構(gòu)時(shí)甫男,會(huì)默認(rèn)插入三條記錄

  6. 自定義數(shù)據(jù)表
    act_cus_user_task:流程各節(jié)點(diǎn)執(zhí)行人信息表且改,啟動(dòng)流程之前初始化流程節(jié)點(diǎn)信息時(shí)插入節(jié)點(diǎn)key、節(jié)點(diǎn)名稱板驳、業(yè)務(wù)key和已知的各節(jié)點(diǎn)執(zhí)行人信息又跛。

:代碼已上傳至GitHub:
https://github.com/ruoki/ActivitiWorkFlowDemo
歡迎star

上一篇:Spring學(xué)習(xí)之整合MyBatis
下一篇:Spring學(xué)習(xí)之整合Activiti(二)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市若治,隨后出現(xiàn)的幾起案子慨蓝,更是在濱河造成了極大的恐慌,老刑警劉巖端幼,帶你破解...
    沈念sama閱讀 206,214評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件礼烈,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡婆跑,警方通過(guò)查閱死者的電腦和手機(jī)济丘,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人摹迷,你說(shuō)我怎么就攤上這事疟赊。” “怎么了峡碉?”我有些...
    開(kāi)封第一講書(shū)人閱讀 152,543評(píng)論 0 341
  • 文/不壞的土叔 我叫張陵近哟,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我鲫寄,道長(zhǎng)吉执,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,221評(píng)論 1 279
  • 正文 為了忘掉前任地来,我火速辦了婚禮戳玫,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘未斑。我一直安慰自己咕宿,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,224評(píng)論 5 371
  • 文/花漫 我一把揭開(kāi)白布蜡秽。 她就那樣靜靜地躺著府阀,像睡著了一般。 火紅的嫁衣襯著肌膚如雪芽突。 梳的紋絲不亂的頭發(fā)上试浙,一...
    開(kāi)封第一講書(shū)人閱讀 49,007評(píng)論 1 284
  • 那天,我揣著相機(jī)與錄音寞蚌,去河邊找鬼田巴。 笑死,一個(gè)胖子當(dāng)著我的面吹牛挟秤,可吹牛的內(nèi)容都是我干的固额。 我是一名探鬼主播,決...
    沈念sama閱讀 38,313評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼煞聪,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了逝慧?” 一聲冷哼從身側(cè)響起昔脯,我...
    開(kāi)封第一講書(shū)人閱讀 36,956評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎笛臣,沒(méi)想到半個(gè)月后云稚,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,441評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡沈堡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,925評(píng)論 2 323
  • 正文 我和宋清朗相戀三年静陈,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,018評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡鲸拥,死狀恐怖拐格,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情刑赶,我是刑警寧澤捏浊,帶...
    沈念sama閱讀 33,685評(píng)論 4 322
  • 正文 年R本政府宣布,位于F島的核電站撞叨,受9級(jí)特大地震影響金踪,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜牵敷,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,234評(píng)論 3 307
  • 文/蒙蒙 一胡岔、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧枷餐,春花似錦靶瘸、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,240評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至村生,卻和暖如春惊暴,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背趁桃。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,464評(píng)論 1 261
  • 我被黑心中介騙來(lái)泰國(guó)打工辽话, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人卫病。 一個(gè)月前我還...
    沈念sama閱讀 45,467評(píng)論 2 352
  • 正文 我出身青樓油啤,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親蟀苛。 傳聞我的和親對(duì)象是個(gè)殘疾皇子益咬,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,762評(píng)論 2 345