学习笔记-flowable

flowable

插件

  • Flowable BPMN visualizer

依赖

        <dependency>
            <groupId>org.flowable</groupId>
            <artifactId>flowable-engine</artifactId>
            <version>6.5.0</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.18</version>
        </dependency>

xml标签

  • - 表示一个完整的工作流程
  • - 工作流中起点位置
  • - 工作流中结束位置
  • - 代表一个任务审核节点 - flowable:assignee 属性,这表示这个节点该由谁来处理
  • - 服务任务,在具体的实现中,这个任务可以做任何事情
  • - 逻辑判断节点
  • - 链接各个节点的线条 - sourceRef 属性表示线的起始节点 - targetRef 属性表示线指向的节点

流程图

<?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:flowable="http://flowable.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.flowable.org/processdef">
  <process id="process1" name="process1" isExecutable="true">
    <startEvent id="start" name="start"/>
    <userTask id="apply" name="apply" flowable:assignee="10001"/>
    <sequenceFlow id="edge_1" sourceRef="start" targetRef="apply" name="edge_1"/>
    <exclusiveGateway id="gateway" name="gateway"/>
    <sequenceFlow id="edge_2" sourceRef="apply" targetRef="gateway" name="edge_2"/>
    <sequenceFlow id="agree" sourceRef="gateway" targetRef="holiday_system" name="agree">
      <conditionExpression xsi:type="tFormalExpression">${approved}</conditionExpression>
    </sequenceFlow>
    <sequenceFlow id="rejected" sourceRef="gateway" targetRef="e_mail" name="rejected">
      <conditionExpression xsi:type="tFormalExpression">!${approved}</conditionExpression>
    </sequenceFlow>
    <endEvent id="refuse_end" name="refuse_end"/>
    <sequenceFlow id="edge_5" sourceRef="e_mail" targetRef="refuse_end" name="edge_5"/>
    <userTask id="holiday_apply" name="holiday_apply"/>
    <sequenceFlow id="edge_3" sourceRef="holiday_system" targetRef="holiday_apply" name="edge_3"/>
    <endEvent id="agree_end" name="agree_end"/>
    <sequenceFlow id="edge_4" sourceRef="holiday_apply" targetRef="agree_end" name="edge_4"/>
    <serviceTask id="e_mail" flowable:exclusive="true" name="e_mail" flowable:class="com.sws.flowable.SendEmail"/>
    <serviceTask id="holiday_system" flowable:exclusive="true" name="holiday_system" flowable:class="com.sws.flowable.CallExternalSystem"/>
  </process>
  <bpmndi:BPMNDiagram id="BPMNDiagram_process1">
    <bpmndi:BPMNPlane bpmnElement="process1" id="BPMNPlane_process1">
      <bpmndi:BPMNShape id="shape-cf32fcf2-b052-4374-82b8-a8c1c4bf85d5" bpmnElement="start">
        <omgdc:Bounds x="-225.0" y="-85.0" width="30.0" height="30.0"/>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape id="shape-0e717dd9-30ac-4c4c-b055-a3bf5d6bfecf" bpmnElement="apply">
        <omgdc:Bounds x="-75.0" y="-110.0" width="100.0" height="80.0"/>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNEdge id="edge-eed13fc9-0ffb-4bff-a0aa-e285d8b7d7f9" bpmnElement="edge_1">
        <omgdi:waypoint x="-195.0" y="-70.0"/>
        <omgdi:waypoint x="-74.99999" y="-70.0"/>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNShape id="shape-a4f56a19-2439-44d3-b8a3-b775ad52e2a4" bpmnElement="gateway">
        <omgdc:Bounds x="130.0" y="-89.99999" width="40.0" height="40.0"/>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNEdge id="edge-d5f2fe06-7a50-4973-8511-72fa8925cbec" bpmnElement="edge_2">
        <omgdi:waypoint x="25.0" y="-70.0"/>
        <omgdi:waypoint x="130.0" y="-69.99999"/>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge id="edge-8753897d-4678-4e99-b9ce-642f43e7f525" bpmnElement="agree">
        <omgdi:waypoint x="170.0" y="-69.99999"/>
        <omgdi:waypoint x="260.0" y="-70.0"/>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge id="edge-2b5a2ac0-1286-4d5f-af1e-48e998ce9f1b" bpmnElement="rejected">
        <omgdi:waypoint x="150.0" y="-50"/>
        <omgdi:waypoint x="150.0" y="80.0"/>
        <omgdi:waypoint x="250" y="80"/>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNShape id="shape-7a97d6b5-57ba-41bc-8dc1-717aab6aa854" bpmnElement="refuse_end">
        <omgdc:Bounds x="460.0" y="65.0" width="30.0" height="30.0"/>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNEdge id="edge-27898ca3-1476-4654-9004-a8bc78995894" bpmnElement="edge_5">
        <omgdi:waypoint x="349.99997" y="80.00001"/>
        <omgdi:waypoint x="460.0" y="80.0"/>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNShape id="shape-ef7ac29e-8c1f-4c49-8f8d-6a78163eeebd" bpmnElement="holiday_apply">
        <omgdc:Bounds x="470.0" y="-109.99999" width="100.0" height="80.0"/>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNEdge id="edge-c2087a6f-f798-43ba-916c-cca48749601c" bpmnElement="edge_3">
        <omgdi:waypoint x="360.0" y="-69.99999"/>
        <omgdi:waypoint x="470.0" y="-69.99999"/>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNShape id="shape-36c2a15b-54c0-4418-ba70-a04be63c2db4" bpmnElement="agree_end">
        <omgdc:Bounds x="675.0" y="-85.0" width="30.0" height="30.0"/>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNEdge id="edge-04236214-6d53-4358-a229-43b194cc81ae" bpmnElement="edge_4">
        <omgdi:waypoint x="570.0" y="-69.99999"/>
        <omgdi:waypoint x="675.0" y="-70.0"/>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNShape id="shape-df0c0f71-78a2-42df-90c5-51fa6b518cc0" bpmnElement="e_mail">
        <omgdc:Bounds x="250.0" y="40.0" width="100.0" height="80.0"/>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape id="shape-a2af75ca-9706-423d-97b6-94121cea7903" bpmnElement="holiday_system">
        <omgdc:Bounds x="260.0" y="-109.99999" width="100.0" height="80.0"/>
      </bpmndi:BPMNShape>
    </bpmndi:BPMNPlane>
  </bpmndi:BPMNDiagram>
</definitions>

  • 一般数据
    • act_ge_bytearry
      • 通用流程定义和流程资源,流程资源表,流程部署的bpmn文件和png图片
      • id_
        • 主键
      • rev_
        • 版本号
      • name_
        • 名称
      • deployment_id_
        • 部署id
      • bytes_
        • 字节
      • generated_
        • 0:用户上传
        • 1:自动生成
    • act_ge_property
      • 系统相关属性
  • 流程历史记录
    • act_hi_actinst
      • 流程实例,执行历史
    • act_hi_attachment
      • 流程附件
    • act_hi_comment
      • 说明性信息
    • act_hi_detail
      • 流程过程中的详细信息
    • act_hi_identitylink
      • 流程的参与用户的历史信息
    • act_hi_procinst
      • 流程实例历史信息
    • act_hi_taskinst
      • 流程任务历史信息
    • act_hi_varinst
      • 变量信息
  • 流程定义表
    • act_re_deployment
      • 流程定义部署表,每部署一次就增加一条记录
    • act_re_procdef
      • 流程定义表,部署每个新流程就会增加一条记录
    • act_re_model
      • 模型信息
  • 运行实例表
    • act_ru_event_subscr
      • 运行时事件
    • act_ru_execution
      • 流程执行信息
    • act_ru_identitylink
      • 流程的参与用户信息
    • act_ru_task
      • 运行时任务信息
    • act_ru_job
      • 运行时作业
    • act_ru_variable
      • 运行时变量

概念&类型

  • ProcessEngineConfiguration
    • 工作流引擎配置
    • ProcessEngine
      • 流程引擎对象
        • RepositoryService
          • 资源
        • ProcessDefinition
          • 流程的定义
        • RuntimeService
          • 运行时服务,可以启动流程实例
          • ProcessInstance
            • 流程的一个实例
        • TaskService
        • HistoryService
        • ManagementService
          • 引擎管理类
  • Activity
    • 流程中的每一个步骤都是一个Activity
  • Execution
    • 流程的执行线路
    • 通过Execution可以获得当前ProcessInstance当前执行到哪个Activity
  • Task
    • 当前要做的工作

test

  • 获取ProcessEngine
    public void testProcessEngine(){
        // 获取ProcessEngineConfiguration对象
        ProcessEngineConfiguration configuration = new StandaloneProcessEngineConfiguration();
        // 配置连接信息
        configuration.setJdbcDriver("com.mysql.cj.jdbc.Driver");
        configuration.setJdbcUsername("root");
        configuration.setJdbcPassword("123456");
        configuration.setJdbcUrl("jdbc:mysql://localhost:3306/flowable?characterEncoding=UTF-8&useSSL=false&allowPublicKeyRetrieval=true");
        // 如果数据库表结构不存在,就新建
        configuration.setDatabaseSchemaUpdate(ProcessEngineConfiguration.DB_SCHEMA_UPDATE_TRUE);
        // 构建ProcessEngine
        ProcessEngine processEngine =configuration.buildProcessEngine();
    }
  • 流程部署
    • act_re_deployment
      • 一次部署操作就产生一次数据
    • act_re_procdef
      • 一次部署的多个流程定义文件就会产生多个数据
    • act_ge_bytearry
      • 多少资源就产生多少记录
    //    部署流程
    public void testDeploy(){
        // 构建ProcessEngine
        ProcessEngine processEngine =configuration.buildProcessEngine();
        // 获取RepositoryService
        RepositoryService repositoryService = processEngine.getRepositoryService();
        Deployment deploy = repositoryService.createDeployment()
                .addClasspathResource("process1.bpmn20.xml")// 关联需要部署的文件
                .name("请求流程")
                .deploy();
        System.out.println(deploy.getId());
        System.out.println(deploy.getName());
    }
  • 查看流程定义
    /**
     * 查看流程定义
     */
    @Test
    public void testDeployQuery(){
        // 获取流程引擎对象
        ProcessEngine processEngine = configuration.buildProcessEngine();
        // 部署流程 获取RepositoryService对象
        RepositoryService repositoryService = processEngine.getRepositoryService();
        // 获取流程定义对象
        ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery()
                .deploymentId("2501")
                .singleResult();
        System.out.println("processDefinition.getId() = " + processDefinition.getId());
        System.out.println("processDefinition.getName() = " + processDefinition.getName());
        System.out.println("processDefinition.getDeploymentId() = " + processDefinition.getDeploymentId());
        System.out.println("processDefinition.getDescription() = " + processDefinition.getDescription());
    }
  • 挂起和激活
ProcessEngine processEngine = configuration.buildProcessEngine();
// 部署流程 获取RepositoryService对象
RepositoryService repositoryService = processEngine.getRepositoryService();
// 获取流程定义对象
ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery()
        .deploymentId("xxx")
        .singleResult();
// 获取当前流程定义状态信息
boolean suspended = processDefintion.isSuspended();
if(suspended){
    // 当前流程已挂起
    // 激活
    repositoryService.activateProcessDefinitionById("xxx");
}else{
    // 当前流程已激活
    // 挂起
    repositoryService.suspendProcessDefinitionByKey("xxx");
}
  • 删除流程
    public void testDelete(){
        // 构建ProcessEngine
        ProcessEngine processEngine =configuration.buildProcessEngine();
        // 获取RepositoryService
        RepositoryService repositoryService = processEngine.getRepositoryService();
        // 删除部署的流程 参数:流程id
        repositoryService.deleteDeployment("xxx");
    }
  • 启动流程实例
    • act_ru_deadletter_job
      • 正在运行的任务表
    • act_ru_event_subscr
      • 运行时事件
    • act_ru_history_job
      • 历史作业表
    • act_ru_job
      • 运行时作业
    • act_ru_suspended_job
      • 暂停作业表
    • act_ru_timer_job
      • 定时作业表
    • act_ru_execution
      • 流程执行信息
    • act_ru_variable
      • 运行时变量
    • act_ru_task
      • 运行时任务信息
    • act_ru_identitylink
      • 流程的参与用户信息
    public void testRunProcess(){
        // 获取流程引擎对象
        ProcessEngine processEngine = configuration.buildProcessEngine();
        // 启动流程实例通过 RuntimeService 对象
        RuntimeService runtimeService = processEngine.getRuntimeService();
        // 构建流程变量
        Map<String,Object> variables = new HashMap<>();
        variables.put("employee","张三") ;// 谁申请请假
        variables.put("nrOfHolidays",3); // 请几天假
        variables.put("description","工作累了,想出去玩玩"); // 请假的原因
        // 启动流程实例,第一个参数是流程定义的id
        ProcessInstance processInstance = runtimeService
                .startProcessInstanceByKey("process1", variables);// 启动流程实例
        // 输出相关的流程实例信息
        System.out.println("流程定义的ID:" + processInstance.getProcessDefinitionId());
        System.out.println("流程实例的ID:" + processInstance.getId());
        System.out.println("当前活动的ID:" + processInstance.getActivityId());
    }
  • 查看任务
    public void testQueryTask(){
        // 获取流程引擎对象
        ProcessEngine processEngine = configuration.buildProcessEngine();
        TaskService taskService = processEngine.getTaskService();
        List<Task> list = taskService.createTaskQuery()
                .processDefinitionKey("process1")
                .taskAssignee("10001")
                .list();
        for (Task task : list) {
            System.out.println("task.getProcessDefinitionId() = " + task.getProcessDefinitionId());
            System.out.println("task.getId() = " + task.getId());
            System.out.println("task.getAssignee() = " + task.getAssignee());
            System.out.println("task.getName() = " + task.getName());
        }
    }
  • 触发任务
// 定义邮件发送任务
// com.sws.flowable.SendEmail
public class SendEmail implements JavaDelegate {
    /**
     * 触发发送邮件的操作
     * @param delegateExecution
     */
    @Override
    public void execute(DelegateExecution delegateExecution) {
        System.out.println("拒绝");
    }
}
    /**
     * 拒绝
     */
    public void testCompleteTask(){
        // 获取流程引擎对象
        ProcessEngine processEngine = configuration.buildProcessEngine();
        TaskService taskService = processEngine.getTaskService();
        Task task = taskService.createTaskQuery()
                .processDefinitionKey("process1")
                .taskAssignee("10001")
                .singleResult();
        // 添加流程变量
        Map<String,Object> variables = new HashMap<>();
        variables.put("approved",false); // 拒绝请假
        // 完成任务
        taskService.complete(task.getId(),variables);
    }
  • 查看历史
    public void testQueryHistory(){
        // 获取流程引擎对象
        ProcessEngine processEngine = configuration.buildProcessEngine();
        HistoryService historyService = processEngine.getHistoryService();
        List<HistoricActivityInstance> list = historyService.createHistoricActivityInstanceQuery()
                .processDefinitionId("process1:2:10004")
                .finished()
                .orderByHistoricActivityInstanceEndTime().asc()
                .list();
        for (HistoricActivityInstance historicActivityInstance : list) {
            System.out.println(historicActivityInstance.getActivityId() + " took " + historicActivityInstance.getDurationInMillis() + " milliseconds");
        }

    }

变量

  • 流程创建时
    • ProcessInstance startProcessInstanceByKey(String processDefinitionKey, Map<String, Object> variables)
  • 流程执行中
    • void setVariable(String executionId, String variableName, Object value)
    • void setVariableLocal(String executionId, String variableName, Object value)
    • void setVariables(String executionId, Map<String, ? extends Object> variables)
    • void setVariablesLocal(String executionId, Map<String, ? extends Object> variables)

用户维护

    • ACT_ID_USER
      • 用户信息
    • ACT_ID_GROUP
      • 用户组
    • ACT_ID_MEMBERSHIP
      • 用户与组对应表
    • act_id_bytearry
      • 二进制数据表
    • act_id_info
      • 用户信息详情表
    • act_id_priv
      • 权限表
    • act_id_priv_mapping
      • 用户与权限关系表
    • act_id_property
      • 属性表
    • act_id_token
      • 用户token

test

   // 用户
    public void createUser(){
        // 获取流程引擎对象
        ProcessEngine processEngine = configuration.buildProcessEngine();
        // 通过 IdentityService 完成相关的用户和组的管理
        IdentityService identityService = processEngine.getIdentityService();
        User user = identityService.newUser("111112222222");
        user.setFirstName("111");
        user.setLastName("222");
        user.setEmail("1221122121@qq.com");
        identityService.saveUser(user);
    }
    // 用户组
    public void createGroup(){
        // 获取流程引擎对象
        ProcessEngine processEngine = configuration.buildProcessEngine();
        IdentityService identityService = processEngine.getIdentityService();
        // 创建Group对象并指定相关的信息
        Group group = identityService.newGroup("111");
        group.setName("aaaa");
        group.setType("bbb");
        // 创建Group对应的表结构数据
        identityService.saveGroup(group);

    }

    // 用户分配用户组
    public void userGroup(){
        // 获取流程引擎对象
        ProcessEngine processEngine = configuration.buildProcessEngine();
        IdentityService identityService = processEngine.getIdentityService();
        // 根据组的编号找到对应的Group对象
        Group group = identityService.createGroupQuery().groupId("111").singleResult();
        List<User> list = identityService.createUserQuery().list();
        for (User user : list) {
            // 将用户分配给对应的组
            identityService.createMembership(user.getId(),group.getId());
        }
    }

流程设置组

  • flowable:candidateGroups=“${group1}”
IdentityService identityService = processEngine.getIdentityService();
Group group = identityService.createGroupQuery().groupId("group1").singleResult();
RuntimeService runtimeService = processEngine.getRuntimeService();
// 给流程定义中的UEL表达式赋值
Map<String,Object> variables = new HashMap<>();
variables.put("group1",group.getId()); // 给流程定义中的UEL表达式赋值
runtimeService.startProcessInstanceById("xxx",variables);

登录人查询

IdentityService identityService = processEngine.getIdentityService();
// 当前用户所在的组
Group group = identityService.createGroupQuery().groupMember("xxxx").singleResult();
TaskService taskService = processEngine.getTaskService();
List<Task> list = taskService.createTaskQuery()
        .processDefinitionId("xxxxx")
        .taskCandidateGroup(group.getId())
        .list();

拾取任务


String userId = "xxxx";
// 根据当前登录的用户找到对应的组
IdentityService identityService = processEngine.getIdentityService();
// 当前用户所在的组
Group group = identityService.createGroupQuery().groupMember(userId).singleResult();
TaskService taskService = processEngine.getTaskService();
Task task = taskService.createTaskQuery()
        .processDefinitionId("xxxxxxx")
        .taskCandidateGroup(group.getId())
        .singleResult();
if(task != null) {
    // 任务拾取
    taskService.claim(task.getId(),userId);
    System.out.println("任务拾取成功");
}

归还任务


String userId = "xxxx";
// 根据当前登录的用户找到对应的组
IdentityService identityService = processEngine.getIdentityService();
// 当前用户所在的组
Group group = identityService.createGroupQuery().groupMember(userId).singleResult();
TaskService taskService = processEngine.getTaskService();
Task task = taskService.createTaskQuery()
        .processDefinitionId("xxxxxxx")
        .taskAssignee("xxx")
        .singleResult();
if(task != null) {
    // 任务归还
    taskService.unclaim(task.getId());
    System.out.println("任务归还");
}

交接任务


String userId = "xxxx";
// 根据当前登录的用户找到对应的组
IdentityService identityService = processEngine.getIdentityService();
// 当前用户所在的组
Group group = identityService.createGroupQuery().groupMember(userId).singleResult();
TaskService taskService = processEngine.getTaskService();
Task task = taskService.createTaskQuery()
        .processDefinitionId("xxxxxxx")
        .taskAssignee("xxx")
        .singleResult();
if(task != null) {
    // 交接任务
    taskService.setAssignee(task.getId(),"xxx");
    System.out.println("交接任务");
}

完成任务

String userId = "xxxx";
// 根据当前登录的用户找到对应的组
IdentityService identityService = processEngine.getIdentityService();
// 当前用户所在的组
Group group = identityService.createGroupQuery().groupMember(userId).singleResult();
TaskService taskService = processEngine.getTaskService();
Task task = taskService.createTaskQuery()
        .processDefinitionId("xxxxxxx")
        .taskCandidateGroup(group.getId())
        .singleResult();
if(task != null) {
    taskService.complete(task.getId());
    System.out.println("完成Task");
}

监听器

public class MyTaskListener implements TaskListener{
    
    public void notify(DelegateTask delegateTask){
        // delegateTask.getName()
        // delegateTask.getEventName()
    }
}

网关

  • 排他网关
    • 按照所有出口顺序流定义的顺序对它们进行计算
    • 选择第一个条件计算为true的顺序流
    • 当没有设置条件时,认为顺序流为true
  • 并行网关
    • fork分支
      • 并行后的所有外出顺序流,为每个顺序流都创建一个并发分支。
    • join汇聚
      • 所有到达并行网关,在此等待的进入分支
      • 直到所有进入顺序流的分支都到达以后, 流程就会通过汇聚网关。
  • 包含网关
    • 排他网关和并行网关的结合体
  • 事件网关
    • 事件网关允许根据事件判断流向

事件

  • 启动事件
    • 空启动事件
  • 中间事件
  • 边界事件
  • 结束事件
    • 错误结束事件
    • 中断结束事件
    • 取消结束事件

springboot整合

依赖

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.3.4.RELEASE</version>
</parent>
<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <!--flowable工作流依赖-->
    <dependency>
        <groupId>org.flowable</groupId>
        <artifactId>flowable-spring-boot-starter</artifactId>
        <version>6.3.0</version>
    </dependency>
    <!--mysql依赖-->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>5.1.45</version>
    </dependency>

</dependencies>

application.yml

flowable:
  async-executor-activate:false
  database-schema-update:true

自动部署

  • resources/process/xxx.xml
    • process下自动部署

手动部署

//@Autowwired
//private RepositoryService repositoryService;

Deployment deploy = repositoryService.createDeployment()
            .addClasspathResource("process1.bpmn20.xml")// 关联需要部署的文件
            .name("请求流程")
            .deploy();

mysql

 docker run -d -p 3306:3306 --name mysql -e MYSQL_ROOT_PASSWORD=123456 -v F:\\docker\\mysql\\conf:/etc/mysql/conf.d -v F:\\docker\\mysql\\data:/var/lib/mysql  -v F:\\docker\\mysql\\mysql-file:/var/lib/mysql-files mysql

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值