flowable自定义属性

flowable每个节点自带的属性是有限的,而在实际业务中可能会遇到一些比较适合配置到各个节点属性,但是自带属性里面又没有的,所以这个时候我们就需要自己自定义属性了,这里主要记录下自己处理的过程,方便以后遇到该问题好查阅。

修改前端

  • 修改文件:stencilset_bpmn.json

  • 新增2个属性:

<!--新增2属性-->
{
        "name" : "taskApprovalOperateConfigPackage",
            "properties" : [ {
              "id" : "taskApprovalOperateNode",
              "type" : "Text",
              "title" : "审批操作配置",
              "value" : "",
              "description" : "The descriptive name of the BPMN element.",
              "popular" : true
            } ]
    },{
      "name" : "returnSpecifiedStepConfigPackage",
      "properties" : [ {
        "id" : "returnSpecifiedStepNode",
        "type" : "Text",
        "title" : "退回指定节点",
        "value" : "",
        "description" : "The descriptive name of the BPMN element.",
        "popular" : true
      } ]
    }
<!--配置给usertask节点-->
{
    "type" : "node",
    "id" : "UserTask",
    "title" : "User task",
    "description" : "A manual task assigned to a specific person",
    "view" : "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n<svg\n   xmlns=\"http://www.w3.org/2000/svg\"\n   xmlns:svg=\"http://www.w3.org/2000/svg\"\n   xmlns:oryx=\"http://www.b3mn.org/oryx\"\n   xmlns:xlink=\"http://www.w3.org/1999/xlink\"\n\n   width=\"102\"\n   height=\"82\"\n   version=\"1.0\">\n  <defs></defs>\n  <oryx:magnets>\n  \t<oryx:magnet oryx:cx=\"1\" oryx:cy=\"20\" oryx:anchors=\"left\" />\n  \t<oryx:magnet oryx:cx=\"1\" oryx:cy=\"40\" oryx:anchors=\"left\" />\n  \t<oryx:magnet oryx:cx=\"1\" oryx:cy=\"60\" oryx:anchors=\"left\" />\n  \t\n  \t<oryx:magnet oryx:cx=\"25\" oryx:cy=\"79\" oryx:anchors=\"bottom\" />\n  \t<oryx:magnet oryx:cx=\"50\" oryx:cy=\"79\" oryx:anchors=\"bottom\" />\n  \t<oryx:magnet oryx:cx=\"75\" oryx:cy=\"79\" oryx:anchors=\"bottom\" />\n  \t\n  \t<oryx:magnet oryx:cx=\"99\" oryx:cy=\"20\" oryx:anchors=\"right\" />\n  \t<oryx:magnet oryx:cx=\"99\" oryx:cy=\"40\" oryx:anchors=\"right\" />\n  \t<oryx:magnet oryx:cx=\"99\" oryx:cy=\"60\" oryx:anchors=\"right\" />\n  \t\n  \t<oryx:magnet oryx:cx=\"25\" oryx:cy=\"1\" oryx:anchors=\"top\" />\n  \t<oryx:magnet oryx:cx=\"50\" oryx:cy=\"1\" oryx:anchors=\"top\" />\n  \t<oryx:magnet oryx:cx=\"75\" oryx:cy=\"1\" oryx:anchors=\"top\" />\n  \t\n  \t<oryx:magnet oryx:cx=\"50\" oryx:cy=\"40\" oryx:default=\"yes\" />\n  </oryx:magnets>\n  <g pointer-events=\"fill\" oryx:minimumSize=\"50 40\">\n\t<rect id=\"text_frame\" oryx:anchors=\"bottom top right left\" x=\"1\" y=\"1\" width=\"94\" height=\"79\" rx=\"10\" ry=\"10\" stroke=\"none\" stroke-width=\"0\" fill=\"none\" />\n\t<rect id=\"bg_frame\" oryx:resize=\"vertical horizontal\" x=\"0\" y=\"0\" width=\"100\" height=\"80\" rx=\"10\" ry=\"10\" stroke=\"#bbbbbb\" stroke-width=\"1\" fill=\"#f9f9f9\" />\n\t\t<text \n\t\t\tfont-size=\"12\" \n\t\t\tid=\"text_name\" \n\t\t\tx=\"50\" \n\t\t\ty=\"40\" \n\t\t\toryx:align=\"middle center\"\n\t\t\toryx:fittoelem=\"text_frame\"\n\t\t\tstroke=\"#373e48\">\n\t\t</text>\n\t\n\t<g id=\"userTask\" transform=\"translate(3,3)\">\n\t\t<path oryx:anchors=\"top left\"\n       \t\tstyle=\"fill:#d1b575;stroke:none;\"\n       \t\t d=\"m 1,17 16,0 0,-1.7778 -5.333332,-3.5555 0,-1.7778 c 1.244444,0 1.244444,-2.3111 1.244444,-2.3111 l 0,-3.0222 C 12.555557,0.8221 9.0000001,1.0001 9.0000001,1.0001 c 0,0 -3.5555556,-0.178 -3.9111111,3.5555 l 0,3.0222 c 0,0 0,2.3111 1.2444443,2.3111 l 0,1.7778 L 1,15.2222 1,17 17,17\" \n         />\n\t\t\n\t</g>\n  \n\t<g id=\"parallel\">\n\t\t<path oryx:anchors=\"bottom\" fill=\"none\" stroke=\"#bbbbbb\" d=\"M46 70 v8 M50 70 v8 M54 70 v8\" stroke-width=\"2\" />\n\t</g>\n\t\n\t<g id=\"sequential\">\n\t\t<path oryx:anchors=\"bottom\" fill=\"none\" stroke=\"#bbbbbb\" stroke-width=\"2\" d=\"M46,76h10M46,72h10 M46,68h10\"/>\n\t</g>\n\t\n\n\t<g id=\"compensation\">\n\t\t<path oryx:anchors=\"bottom\" fill=\"none\" stroke=\"#bbbbbb\" d=\"M 62 74 L 66 70 L 66 78 L 62 74 L 62 70 L 58 74 L 62 78 L 62 74\" stroke-width=\"1\" />\n\t</g>\n  </g>\n</svg>",
    "icon" : "activity/list/type.user.png",
    "groups" : [ "Activities" ],
    "propertyPackages" : [ "overrideidpackage", "namepackage", "documentationpackage", "asynchronousdefinitionpackage", "exclusivedefinitionpackage", "executionlistenerspackage", "multiinstance_typepackage", "multiinstance_cardinalitypackage", "multiinstance_collectionpackage", "multiinstance_variablepackage", "multiinstance_conditionpackage", "isforcompensationpackage", "usertaskassignmentpackage", "formkeydefinitionpackage", "formreferencepackage", "duedatedefinitionpackage", "prioritydefinitionpackage", "formpropertiespackage", "tasklistenerspackage", "skipexpressionpackage", "categorypackage" ,"dueTaskHandlerStrategyNodepackage","taskApprovalOperateConfigPackage","returnSpecifiedStepConfigPackage"],
    "hiddenPropertyPackages" : [ ],
    "roles" : [ "Activity", "sequence_start", "sequence_end", "ActivitiesMorph", "all" ]
  }

前端修改之后页面的效果

image

这样配好之后我们去下载xml的时候会发现没有这两个新增的属性,这个时候在流程实例流转的时候也是获取不到新增属性的值的。

image

是因为flowable并没有支持自定义属性的存储,所以这个时候就要自己对自定义属性进行解析了。

后端修改

  • 我们可以从页面找到下载的请求路径。可以看到是app/rest/models/**


    image

因为是源码我这边为了尽量不改他的源码,所以自己修改了请求地址,自己写了下载的实现。

  • 修改url-config跳转地址:app/rest/models/** 改成 XX/app/rest/models/**
 getModelBpmn20ExportUrl: function (modelId) {
        return FLOWABLE.CONFIG.contextRoot + '/XX/app/rest/models/' + modelId + '/bpmn20?version=' + Date.now();
    },
  • 新建ModelController 继承 AbstractModelBpmnResource:
/* Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.flowable.ui.modeler.rest.app;

import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.net.URLEncoder;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import org.flowable.bpmn.model.BpmnModel;
import org.flowable.ui.common.service.exception.BadRequestException;
import org.flowable.ui.common.service.exception.BaseModelerRestException;
import org.flowable.ui.common.service.exception.InternalServerErrorException;
import org.flowable.ui.modeler.domain.AbstractModel;
import org.flowable.ui.modeler.domain.Model;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.flowable.ui.modeler.service.XXModelServiceImpl;

/**
 * @author 
 */
@RestController
@RequestMapping("/xx/app")
public class ModelController extends AbstractModelBpmnResource {


    private static final Logger LOGGER = LoggerFactory.getLogger(ModelController.class);
    @Autowired
    protected XXModelServiceImpl xxModelService;

    /**
     * GET /rest/models/{modelId}/bpmn20 -> Get BPMN 2.0 xml
     */
    @RequestMapping(value = "/rest/models/{processModelId}/bpmn20", method = RequestMethod.GET)
    public void getProcessModelBpmn20Xml(HttpServletResponse response, @PathVariable String processModelId) throws IOException {
        LOGGER.info("开始下载xml文件1");
        if (processModelId == null) {
            throw new BadRequestException("No process model id provided");
        }

        Model model = xxModelService.getModel(processModelId);
        generateBpmn20Xml(response, model);
    }

    protected void generateBpmn20Xml(HttpServletResponse response, AbstractModel model) {
        String name = model.getName().replaceAll(" ", "_") + ".bpmn20.xml";
        String encodedName = null;
        try {
            encodedName = "UTF-8''" + URLEncoder.encode(name, "UTF-8");
        } catch (Exception e) {
            LOGGER.warn("Failed to encode name " + name);
        }

        String contentDispositionValue = "attachment; filename=" + name;
        if (encodedName != null) {
            contentDispositionValue += "; filename*=" + encodedName;
        }

        response.setHeader("Content-Disposition", contentDispositionValue);
        if (model.getModelEditorJson() != null) {
            try {
                ServletOutputStream servletOutputStream = response.getOutputStream();
                response.setContentType("application/xml");

                BpmnModel bpmnModel = xxModelService.getBpmnModel(model);
                byte[] xmlBytes = xxModelService.getBpmnXML(bpmnModel);
                BufferedInputStream in = new BufferedInputStream(new ByteArrayInputStream(xmlBytes));

                byte[] buffer = new byte[8096];
                while (true) {
                    int count = in.read(buffer);
                    if (count == -1) {
                        break;
                    }
                    servletOutputStream.write(buffer, 0, count);
                }

                // Flush and close stream
                servletOutputStream.flush();
                servletOutputStream.close();

            } catch (BaseModelerRestException e) {
                throw e;

            } catch (Exception e) {
                LOGGER.error("Could not generate BPMN 2.0 XML", e);
                throw new InternalServerErrorException("Could not generate BPMN 2.0 xml");
            }
        }
    }

}

  • 新建XXModelServiceImpl 继承 ModelServiceImpl
/* Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.flowable.ui.modeler.service;

import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Service;
import org.flowable.ui.modeler.service.ModelServiceImpl;
import org.flowable.editor.language.json.converter.BpmnJsonConverter;
import org.flowable.editor.language.json.converter.XXBpmnJsonConverter;
import org.springframework.transaction.annotation.Transactional;

/**
 * @author 
 */
@Service("xxModelService")
@Primary
public class XXModelServiceImpl extends ModelServiceImpl {

    protected BpmnJsonConverter  bpmnJsonConverter = new XXBpmnJsonConverter();
}

  • 新建XXBpmnJsonConverter 继承 BpmnJsonConverter
/* Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.flowable.editor.language.json.converter;

import  org.flowable.editor.language.json.converter.XXCustomizeUserTaskJsonConverter;
import  org.flowable.editor.language.json.converter.BpmnJsonConverter;

/**
 * @author 
 */
public class XXBpmnJsonConverter extends BpmnJsonConverter {

    static {
        convertersToBpmnMap.put(STENCIL_TASK_USER,XXCustomizeUserTaskJsonConverter.class);
    }

}

  • 新建自定义userTaskjson解析器XXCustomizeUserTaskJsonConverter 继承UserTaskJsonConverter
/* Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.flowable.editor.language.json.converter;

import java.util.Map;
import org.flowable.editor.language.json.converter.UserTaskJsonConverter;
import org.flowable.editor.language.json.converter.BaseBpmnJsonConverter;
import org.flowable.bpmn.model.BaseElement;
import org.flowable.editor.language.json.converter.ActivityProcessor;
import org.flowable.bpmn.model.BpmnModel;
import org.flowable.bpmn.model.FlowElementsContainer;
import com.fasterxml.jackson.databind.node.ArrayNode;
import org.flowable.bpmn.model.FlowElement;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.core.JsonProcessingException;
import org.flowable.bpmn.model.UserTask;
import org.flowable.bpmn.model.CustomProperty;
import org.apache.commons.lang3.StringUtils;
import org.flowable.editor.language.json.converter.util.CollectionUtils;
import org.flowable.editor.constants.StencilConstants;
import java.util.List;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.Arrays;
import org.flowable.bpmn.model.ExtensionAttribute;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.flowable.editor.language.json.converter.util.ExtensionAttributeUtils;

/**
 * @author 
 */
public class XXCustomizeUserTaskJsonConverter extends UserTaskJsonConverter {

    private static final Logger LOGGER = LoggerFactory.getLogger(XXCustomizeUserTaskJsonConverter.class);

    //任务逾期策略
    private static final String TASK_HANDLER_STRATEGY_NODE="duetaskhandlerstrategynode";
    private static final String TASK_HANDLER_STRATEGY_NODE_KEY="dueTaskHandlerStrategyNode";

    //审批操作配置
    private static final String TASK_APPROVAL_OPERATE_NODE="taskapprovaloperatenode";
    private static final String TASK_APPROVAL_OPERATE_NODE_KEY="taskApprovalOperateNode";

    //可退回操作
    private static final String TASK_HANDLER_STRATEGY_STEP_NODE="returnspecifiedstepnode";
    private static final String TASK_HANDLER_STRATEGY_STEP_KEY="returnSpecifiedStepNode";

    public static void fillBpmnTypes(
            Map<Class<? extends BaseElement>, Class<? extends BaseBpmnJsonConverter>> convertersToJsonMap) {
        convertersToJsonMap.put(UserTask.class, XXCustomizeUserTaskJsonConverter.class);
    }

    @Override
    protected FlowElement convertJsonToElement(JsonNode elementNode, JsonNode modelNode,
                                               Map<String, JsonNode> shapeMap) {
        FlowElement flowElement = super.convertJsonToElement(elementNode, modelNode, shapeMap);
        LOGGER.info("进入自定义属性解析");
        if(flowElement instanceof UserTask){
            ObjectMapper objectMapper = new ObjectMapper();
            UserTask userTask = (UserTask) flowElement;
            try {
                LOGGER.info("节点:" + objectMapper.writeValueAsString(userTask));
            }catch (JsonProcessingException e) {
                LOGGER.error("节点序列化异常.");
            }
            String  taskHandlerStrategyNode = getPropertyValueAsString(TASK_HANDLER_STRATEGY_NODE, elementNode);
            LOGGER.info("新增自定义属性,任务处理策略[" + TASK_HANDLER_STRATEGY_NODE + "]=" + taskHandlerStrategyNode);
            Map<String,List<ExtensionAttribute>> atts = new HashMap<String,List<ExtensionAttribute>>();
            ExtensionAttribute ea1 = ExtensionAttributeUtils.generate(TASK_HANDLER_STRATEGY_NODE_KEY,taskHandlerStrategyNode);

            String taskApprovalOperate = getPropertyValueAsString(TASK_APPROVAL_OPERATE_NODE,elementNode);
            LOGGER.info("新增自定义属性,任务审批结果["+TASK_APPROVAL_OPERATE_NODE+"]="+taskApprovalOperate);
            ExtensionAttribute ea2 =  ExtensionAttributeUtils.generate(TASK_APPROVAL_OPERATE_NODE_KEY,taskApprovalOperate);
            atts.put("XX-FLOWABLE-EXT ",Arrays.asList(ea1,ea2));

            String returnSpecifiedStep = getPropertyValueAsString(TASK_HANDLER_STRATEGY_STEP_NODE,elementNode);
            LOGGER.info("新增自定义属性,任务审批结果["+TASK_HANDLER_STRATEGY_STEP_NODE+"]="+returnSpecifiedStep);
            ExtensionAttribute ea3 =  ExtensionAttributeUtils.generate(TASK_HANDLER_STRATEGY_STEP_KEY,returnSpecifiedStep);
            atts.put("XX-FLOWABLE-EXT ",Arrays.asList(ea1,ea2,ea3));

            flowElement.setAttributes(atts);
        }
        return flowElement;
    }

}

  • 创建ExtensionAttributeUtils 对 拓展属性进行操作
/* Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.flowable.editor.language.json.converter.util;

import java.util.Collection;
import org.flowable.bpmn.model.ExtensionAttribute;

/**
 * @author 
 */
public class ExtensionAttributeUtils {

    public static ExtensionAttribute generate(String key,String val){
        ExtensionAttribute ea = new ExtensionAttribute();
        ea.setNamespace("http://xx.com.cn");
        ea.setName(key);
        ea.setNamespacePrefix("XX");
        ea.setValue(val);
        return ea;
    }

}


修改代码重新部署之后再下载xml:

image

可以看到新增属性再xml里面已经保存成功了。


发起一支流程,去实例中看看能不能取到该新增属性的值。

image
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,847评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,208评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,587评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,942评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,332评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,587评论 1 218
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,853评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,568评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,273评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,542评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,033评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,373评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,031评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,073评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,830评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,628评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,537评论 2 269

推荐阅读更多精彩内容

  • MarkDown版本:https://www.jianshu.com/p/c1fb9324b476 flowabl...
    983364阅读 4,740评论 0 1
  • Swift1> Swift和OC的区别1.1> Swift没有地址/指针的概念1.2> 泛型1.3> 类型严谨 对...
    cosWriter阅读 11,036评论 1 32
  • 最近做了一次对企业/云平台级工作流引擎Activiti的调查: TA,系出名门——由JBoss公司jBPM4引擎的...
    天空之诚阅读 25,267评论 7 93
  • 【Android 动画】 动画分类补间动画(Tween动画)帧动画(Frame 动画)属性动画(Property ...
    Rtia阅读 5,954评论 1 38
  • 你知道吗. 回忆起你真的能让我变成神经病. 回忆前一秒还是嘴角微扬. 下一秒却湿润了眼眶. 昨天我们在熟悉的街遇见...
    是小明丫阅读 253评论 0 2