OA自动化办公: springboot集成Activiti7

一.场景引入

        我在实际开发中遇到过这样一个需求:公民登录建议征集系统向大冶市某个人大代表提建议,但建议提出后人大代表未能及时响应,此时如果建议越来越多,就会导致建议被搁置。很多建议不会被处理,公民自然也就得不到反馈,这对于汇聚民生民意和征集建议显然是不利的。于是,我开始构思:能否利用工作流的技术,当有公民的建议提交给人大代表的时候就创建一个流程实例,如果建议超过时间没有被处理,系统自动将建议转交到人大组,同组的人大可以拾取和处置该建议(我以地区作为分组,例如:张三,李四,王五为大冶市人大代表,赵六,王伟为黄石港区人大代表。公民提交建议到张三账号后张三未能及时对建议作出处理,建议超过7天就会自动转交到与张三同为大冶市人大代表的人大组代表中。也就是说,建议提出后7天内建议由张三处理,李四,王五以及其他人大无权查看和处置。建议超过7天后,李四,王五(大冶市人大代表)账号可以看到这条超时未被处理的建议,拾取和处置建议,张三可以重新拾取和处置已经超时的建议(任务);赵六,王伟(黄石港区人大)账号始终不能看到提交到大冶市人大代表的建议)。

       还有,对于超时的建议(任务),人大代表必须先拾取再处置,如果拾取后的建议并不擅长处理,可以将建议(任务)归还,此后(建议)任务回到可拾取任务队列中,可以由同组的其他人大拾取和处置。如此往复......直至建议被解决。

      定下需求后,我开始着手于实现。但是我发现网上的资源较少,不能支持我完整的开发出这样的功能,于是我开始自己摸索,买书,看网课,不断测试......完成了这个需求。

      下面是我使用springboot框架集成Activiti7完成开发的过程:

1.开发所要用到的Maven依赖

<dependency>
    <groupId>org.activiti</groupId>
    <artifactId>activiti-spring-boot-starter</artifactId>
    <version>7.1.0.M4</version>
    <exclusions>
        <exclusion>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
        </exclusion>
    </exclusions>
</dependency>

<dependency>
    <groupId>org.activiti.dependencies</groupId>
    <artifactId>activiti-dependencies</artifactId>
    <version>7.1.0.M4</version>
    <type>pom</type>
</dependency>

<!-- 生成流程图 -->
<dependency>
    <groupId>org.activiti</groupId>
    <artifactId>activiti-image-generator</artifactId>
    <version>7.1.0.M4</version>
</dependency>

<dependency>
    <groupId>org.activiti</groupId>
    <artifactId>activiti-process-validation</artifactId>
    <version>7.1.0.M4</version>
</dependency>

 2.下载BPMN流程图插件,我用的是2022年的IDE,大家可以参考。

3.根据业务需求设计流程图,如下图:

图3-1项目资源目录下新建processes->xxx.bpmn20.xml文件

            图3-2 在bpmn20.xml文件中右键->选择View BPMN(Activiti)Diagram在视图中打开

 图3-3 在打开的视图中右键选择流程符号定义流程图

流程图的定义因需求和场景各有差异,对于流程的定义大家可以看看我整理的 《BPMN流程符号介绍》 ,设计好自己的业务流程

下面是我设计好的工作流流程图,用于建议征集过程的自动化办公

3.业务代码实现个人任务查询、组任务查询,任务处理,任务归还,任务拾取以及完成任务。BusinessKey应该和自己的业务主键id相关联,我这里与自己数据库中建议表的主键id相关联,所以可以由BusinessKey对建议表中数据进行查询。

控制层 

package com.yqj.imli.controller;

import com.yqj.imli.controller.Request.MotionRequest;
import com.yqj.imli.controller.Request.TaskRequest;
import com.yqj.imli.entity.Group;
import com.yqj.imli.entity.HistoryTask;
import com.yqj.imli.entity.Motion;
import com.yqj.imli.entity.Result;
import com.yqj.imli.service.impl.MyHistoryService;
import com.yqj.imli.service.impl.QueryCandidateUsersService;
import com.yqj.imli.service.impl.QueryTaskService;
import lombok.extern.slf4j.Slf4j;
import org.activiti.engine.HistoryService;
import org.activiti.engine.RuntimeService;
import org.activiti.engine.TaskService;
import org.activiti.engine.runtime.ProcessInstance;
import org.activiti.engine.task.Task;
import org.apache.ibatis.annotations.Param;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import java.text.SimpleDateFormat;
import java.util.*;


@Slf4j
@RestController
@CrossOrigin
@RequestMapping("/task")

public class TaskController {
    //提供对流程定义和部署存储库的访问服务
   @Autowired
    HistoryService historyService;

    //运行时的接口
    @Autowired
    RuntimeService runtimeService;

    // 任务处理接口
    @Autowired
    TaskService taskService;

    @Autowired
    MyHistoryService myHistoryService;

    @Autowired
    QueryTaskService queryTaskService;

    @Autowired
    QueryCandidateUsersService queryCandidateUsersService;

    /**
     *  用户提交议案时生成流程实例, 用户完成任务
     */
    @PostMapping("/startProcess")
    public Result startProcess(@RequestBody MotionRequest motionRequest) {
        System.out.println("motionRequest: " +motionRequest);

        Map<String, Object> variablesMap = new HashMap<>();

        String ID = String.valueOf(motionRequest.getId());
        List<String> candidateUsers = queryCandidateUsersService.queryCandidateUsers(motionRequest.getAddress());
        System.out.println("candidateUsers: " +candidateUsers);
        variablesMap.put("candidateUserList", candidateUsers);
        variablesMap.put("user", motionRequest.getFrom());
        variablesMap.put("NPC", motionRequest.getTo());

        //通过流程定义ID启动一个流程实例
        ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("myMotion", ID, variablesMap);
        log.info("流程实例:{}", processInstance);
        Task task = taskService.createTaskQuery()
                .processDefinitionKey("myMotion")
                .taskAssignee(motionRequest.getFrom())
                .singleResult();
        taskService.complete(task.getId());
        myHistoryService.saveHistoryTask(Integer.valueOf(processInstance.getBusinessKey()), task.getAssignee(), new SimpleDateFormat("YYYY年MM月dd日 HH:mm").format(new Date()), "提交待处理", null, null);
        return Result.success("议案提交成功");
    }

    /**
     *  完成个人任务
     */
    @PostMapping("/completeTask")
    public Result completeTask(@RequestBody TaskRequest taskRequest) {
        System.out.println("taskRequest: " +taskRequest);
        Task task = taskService.createTaskQuery()
                .processInstanceBusinessKey(String.valueOf(taskRequest.getId()))
                .singleResult();
        taskService.complete(task.getId());
        myHistoryService.saveHistoryTask(taskRequest.getId(), task.getAssignee(), new SimpleDateFormat("YYYY年MM月dd日 HH:mm").format(new Date()), "完成任务", taskRequest.getHandingRes() , taskRequest.getHandingOpinions());
        return Result.success("");
    }

    /**
     * 查询个人任务
     */

    @GetMapping("/staticByAssigneeTask")
    public Result staticByAssigneeTask(String NPC) {
        Integer task = queryOwnerTask(NPC).size();

        return Result.success(task);
    }


    @GetMapping("/queryByAssigneeTask")
    public Result queryByAssigneeTask(@Param("NPC") String NPC, @Param("size") Integer size, @Param("page") Integer page) {
        System.out.println("size: " +size);
        System.out.println("page: " +page);
        List<Task> taskList = queryOwnerTask(NPC);
        if(taskList == null || taskList.size() == 0) {
            System.out.println("个人任务不存在");
            return Result.error("个人任务不存在");
        }
        List<Integer> businessKeyList = new ArrayList<>();
        for (Task task : taskList) {
//            使用task对象获取实例id
            String processInstanceId = task.getProcessInstanceId();
//            使用实例id, 获取流程实例对象
            ProcessInstance processInstance = runtimeService.createProcessInstanceQuery()
                    .processInstanceId(processInstanceId)
                    .singleResult();
//            获取业务主键
            businessKeyList.add(Integer.valueOf(processInstance.getBusinessKey()));
        }

//        通过businessKey查询任务中所对应的业务内容

        List<Motion> motions = queryTaskService.queryMotionByBusinessKey(businessKeyList, size, page);
        Map<String, Object> taskMap = new HashMap<>();
        taskMap.put("number", taskList.size());
        taskMap.put("motions", motions);
        return Result.success(taskMap);
    }

    @GetMapping("/staticAssigneeTask")
    public Result staticAssigneeTask(String NPC) {
        List<Task> taskList = queryOwnerTask(NPC);
        if(taskList == null || taskList.size() == 0) {
            System.out.println("个人任务不存在");
            return Result.error("个人任务不存在");
        }
        List<Integer> businessKeyList = new ArrayList<>();
        for (Task task : taskList) {
//            使用task对象获取实例id
            String processInstanceId = task.getProcessInstanceId();
//            使用实例id, 获取流程实例对象
            ProcessInstance processInstance = runtimeService.createProcessInstanceQuery()
                    .processInstanceId(processInstanceId)
                    .singleResult();
//            获取业务主键
            businessKeyList.add(Integer.valueOf(processInstance.getBusinessKey()));
        }
        List<Group> groups = queryTaskService.staticAssigneeTask(businessKeyList);
        Map<String, Object> taskMap = new HashMap<>();
        taskMap.put("number", taskList.size());
        taskMap.put("groups", groups);
        return Result.success(taskMap);
    }


    /**
     * 查询已完成任务
     */

    @GetMapping("/staticFinishedTask")
    public Result staticFinishedTask(String NPC) {
        Integer completedTask = myHistoryService.queryFinishedTask(NPC).size();
        List<Group> groups = myHistoryService.groupFinishedBySort(NPC);
        System.out.println("分组后: " +groups);
        Map<String, Object> completedTaskNumbermap = new HashMap<>();
        completedTaskNumbermap.put("completedTask", completedTask);
        completedTaskNumbermap.put("groups", groups);
        return Result.success(completedTaskNumbermap);
    }

    @GetMapping("/queryFinishedTask")
    public Result queryFinishedTask(String NPC) {
        //        查询历史任务
        List<Motion> motions = myHistoryService.queryFinishedTask(NPC);
        for (Motion motion : motions) {
            HistoryTask historyTask = myHistoryService.queryHandingMessage(motion.getMotion());
            if (historyTask != null) {
                motion.setHandingRes(historyTask.getHandingRes());
                motion.setHandingOpinion(historyTask.getHandingOpinions());
            }
        }

        return Result.success(motions);

    }

    @PutMapping("/removeFinished")
    public Result remove (@RequestBody List<TaskRequest> taskRequestList) {
        for (TaskRequest finishedTask : taskRequestList) {
            myHistoryService.remove(finishedTask.getId());
        }
        return Result.success();
    }

    /**
     *
     * @param id
     * @return
     */

    @GetMapping("/hiddenFinishedTask/{id}")
    public Result hiddenFinishedTask(@PathVariable Integer id) {
        myHistoryService.updateHistoryTask(id);
        return Result.success("");
    }


    /**
     * 查询组任务
     */

    @GetMapping("/staticGroupTask")
    public Result staticGroupTask(String NPC) {
        Integer claimableTask = taskService.createTaskQuery()
                .processDefinitionKey("myMotion")
                .taskCandidateUser(NPC)
                .list()
                .size();
        return Result.success(claimableTask);
    }

    @GetMapping("/checkGroupTask")
    public Result checkGroupTask(@Param("NPC") String NPC, @Param("size") Integer size, @Param("page") Integer page) {
//        securityUtil.logInAs(NPC);
        List<Task> tasks = taskService.createTaskQuery()
                .processDefinitionKey("myMotion")
                .taskCandidateUser(NPC)
                .list();
        if (tasks == null || tasks.size() == 0) {
            return Result.error("任务不存在");
        }
        List<Integer> businessKeyList = new ArrayList<>();
        for (Task task : tasks) {
            String processInstanceId = task.getProcessInstanceId();
//            使用实例id, 获取流程实例对象
            ProcessInstance processInstance = runtimeService.createProcessInstanceQuery()
                    .processInstanceId(processInstanceId)
                    .singleResult();
//            获取业务主键
            businessKeyList.add(Integer.valueOf(processInstance.getBusinessKey()));
        }
        List<Motion> motions = queryTaskService.queryMotionByBusinessKey(businessKeyList, size, page);
        Map<String, Object> taskMap = new HashMap<>();
        taskMap.put("number", tasks.size());
        taskMap.put("motions", motions);
        return Result.success(taskMap);
    }

    @GetMapping("/claimTask")
    public Result claimTask(Integer id,String NPC) {
        Task task = taskService.createTaskQuery()
                .processInstanceBusinessKey(String.valueOf(id))
                .singleResult();
        taskService.claim(task.getId(), NPC);
        myHistoryService.saveHistoryTask(id, NPC, new SimpleDateFormat("YYYY年MM月dd日 HH:mm").format(new Date()), "拾取任务", null , null);
        return Result.success("任务拾取成功");
    }

    /**
    *  归还任务
     */
    @GetMapping("/{id}")
    public Result returnTask(@PathVariable Integer id) {
        Task task = taskService.createTaskQuery()
                .processInstanceBusinessKey(String.valueOf(id))
                .singleResult();
        taskService.claim(task.getId(), null);
        myHistoryService.saveHistoryTask(id, task.getAssignee(), new SimpleDateFormat("YYYY年MM月dd日 HH:mm").format(new Date()), "归还任务", null , null);
        return Result.success("归还任务成功");
    }

    @GetMapping("/queryHistoryTask/{comment}")
    public Result queryHistoryTask (@PathVariable Integer comment) {
        List<HistoryTask> historyTasks = myHistoryService.queryHistoryTask(comment);
        return Result.success(historyTasks);
    }

   public  List<Task> queryOwnerTask(String NPC) {
       List<Task> taskList = taskService.createTaskQuery()
               //代办人姓名
               .taskAssignee(NPC)
               //活动状态
               .active()
               .list();
       return taskList;
   }
}

业务层     MyHistoryService.Java用于记录建议的所有状态,以便流程公开透明。具体代码根据自己的真实需求和数据库表设计编写,也可以没有。其他业务层方法由Activiti7相关jar包直接导入(不用编写,导入Maven依赖后可用)。

package com.yqj.imli.service.impl;


import com.yqj.imli.entity.Group;
import com.yqj.imli.entity.HistoryTask;
import com.yqj.imli.entity.Motion;
import com.yqj.imli.mapper.MyHistoryMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;


@Service("historyService")
public class MyHistoryService {

    @Autowired
    MyHistoryMapper myHistoryMapper;



    public void saveHistoryTask(Integer id, String assignee, String time, String activityName, String handingRes, String handingOpinions) {
        myHistoryMapper.saveHistoryTask(id, assignee, time, activityName, handingRes, handingOpinions);

    }

    public List<HistoryTask> queryHistoryTask(Integer comment) {
        List<HistoryTask> historyTasks = myHistoryMapper.queryHistoryTask(comment);
        return historyTasks;
    }

    public List<Motion> queryFinishedTask(String NPC) {
        List<Motion> motions = myHistoryMapper.queryFinishedTask(NPC);
        return motions;
    }

    public List<Group> groupFinishedBySort(String NPC) {
        List<Group> groups = myHistoryMapper.groupFinishedBySort(NPC);
        return groups;
    }

    public HistoryTask queryHandingMessage(Integer id) {
        HistoryTask historyTask = myHistoryMapper.queryHandingMessage(id);
        return historyTask;
    }

    public void updateHistoryTask(Integer id) {
        myHistoryMapper.updateHistoryTask(id);
    }

    public void remove(Integer id) {
        myHistoryMapper.remove(id);
    }
}

Mapper层

package com.yqj.imli.mapper;

import com.yqj.imli.entity.Group;
import com.yqj.imli.entity.HistoryTask;
import com.yqj.imli.entity.Motion;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;

import java.util.List;

@Mapper
public interface MyHistoryMapper {
    void saveHistoryTask(@Param("id") Integer id, @Param("assignee") String assignee, @Param("time") String time, @Param("activityName") String activityName,@Param("handingRes") String handingRes, @Param("handingOpinions") String handingOpinions);

    List<HistoryTask> queryHistoryTask(@Param("id") Integer id);

    List<Motion> queryFinishedTask(@Param("NPC") String NPC);

    List<Group> groupFinishedBySort(@Param("NPC") String NPC);

    HistoryTask queryHandingMessage(@Param("id") Integer id);

    void updateHistoryTask(@Param("motionId") Integer motionId);

    void remove(@Param("id") Integer id);
}
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.yqj.imli.mapper.MyHistoryMapper">


    <insert id="saveHistoryTask">
        insert into history_task (MOTION_ID, assignee, activity_name, time, handing_res, handing_opinions, `show`)
            VALUES (#{id}, #{assignee}, #{activityName}, #{time}, #{handingRes}, #{handingOpinions}, 1)
    </insert>

    <update id="updateHistoryTask">
        update history_task
            set `show` = 0
                where MOTION_ID = #{motionId}
    </update>
    <update id="remove">
        update history_task
            set `show` = 0
                where MOTION_ID = #{id}
    </update>

    <select id="queryHistoryTask" resultType="com.yqj.imli.entity.HistoryTask">
        select id, assignee, activity_name, time, handing_res, handing_opinions
            from history_task
            <where>
                <if test="id != null and id!= ''">
                    MOTION_ID = #{id}
                </if>
            </where>
                order by
                    unix_timestamp(time) asc
    </select>

    <select id="queryFinishedTask" resultType="com.yqj.imli.entity.Motion">
        select * from motion
            where id in (select MOTION_ID from history_task
                                         where assignee = #{NPC}
                                           and activity_name = '完成任务' and `show` = 1)
    </select>

    <select id="groupFinishedBySort" resultType="com.yqj.imli.entity.Group">
        select sort, count(id) as num from motion
            where id in (select MOTION_ID from history_task
                                             where assignee = #{NPC}
                                                and activity_name = '完成任务' and `show` = 1)
                             group by
                                            sort
    </select>

    <select id="queryHandingMessage" resultType="com.yqj.imli.entity.HistoryTask">
        select handing_res , handing_opinions from history_task
            where
                MOTION_ID = #{id}
                    and
                        activity_name = '完成任务'
    </select>
</mapper>

在启动过程中如果遇到下面的报错,在数据库中添加缺少的字段就可以了。

以上就是我开发的所有过程了,谢谢大家的阅读,希望能够对大家有所帮助。 

  • 53
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: Spring Boot 是一个流行的 Java Web 框架,而 Activiti 是一个流程引擎,可用于管理和执行业务流程。Spring Boot 可以很容易地与 Activiti 集成,使得使用 Activiti 更加简单方便。 以下是 Spring Boot 集成 Activiti 的基本步骤: 1. 引入 Activiti 依赖:在项目的 pom.xml 文件中添加 Activiti 的依赖。 2. 配置数据源:在 application.properties 或 application.yml 文件中配置数据源。 3. 配置 Activiti:创建一个 Activiti 配置类,使用 @Configuration 注解,并配置 Activiti 的数据源、流程图字体、ID 生成器等。 4. 部署流程定义:在项目启动时,自动部署 Activiti 的流程定义。可以通过实现 CommandLineRunner 接口,在 run 方法中编写自动部署的代码。 5. 编写业务代码:编写业务代码时,可以使用 Activiti 的 API 来管理和执行业务流程。 以上是 Spring Boot 集成 Activiti 的基本步骤。通过集成,可以更加方便地管理和执行业务流程。 ### 回答2: Spring Boot 是一个轻量级的 Java 框架,它通过提供一系列的开箱即用的特性,极大的简化了基于 Spring 框架的应用开发。Activiti 是一个流程引擎框架,它可以帮助开发者轻松地创建和管理业务流程。 为了将 Activiti 集成Spring Boot 中,我们需要执行以下步骤: 1. 添加依赖:在 pom.xml 文件中,添加 ActivitiSpring Boot Starter Activiti 的依赖。 2. 配置数据库:我们需要在 Spring Boot 应用中配置一个数据库,用于存储 Activiti 的流程相关数据。这可以在 application.properties 或 application.yml 文件中进行配置。 3. 配置 ActivitiActiviti 需要一些配置,这可以在 application.properties 或 application.yml 文件中进行配置。例如,我们需要指定 Process Engine 的名称,并定义 Activiti 的数据库模式。 4. 创建流程定义:使用 Activiti Modeler 或者 Activiti Designer 创建一个工作流程定义,然后将其导出为 BPMN 2.0 文件。 5. 部署流程定义:在 Spring Boot 应用中使用 Activiti API 部署流程定义。这可以通过 Java 代码或者 REST API 实现。 6. 运行工作流:使用 Activiti API 在应用中运行流程实例。这可以通过 Java 代码或者 REST API 实现。 除了以上步骤,我们还可以在 Spring Boot 中使用 Activiti 定义一个自定义表单,并将其嵌入到流程中。此外,我们还可以使用 Activiti 监视器来监控流程实例的运行状态。 综上所述,将 Activiti 集成Spring Boot 中可以有效地提高开发人员的工作效率和代码重用性,并加速业务流程的开发和管理。 ### 回答3: Spring Boot作为现代的Java Web开发框架,在开发中得到了广泛应用,而Activiti7作为流程引擎的代表则是业务流程中不可或缺的角色。集成Activiti7和Spring Boot能够为我们提供一个更加方便和可靠的业务流程实现方式。 Spring Boot提供了许多Spring集成的方式,而Activiti7提供了官方的Spring Boot Starter,该Starter通过引入Activiti7及其依赖,为我们提供快速和简单的Activiti7集成Spring Boot项目的方式。具体来说,我们可以通过以下步骤完成Spring BootActiviti7的集成: 1. 添加Activiti7 Spring Boot Starter依赖: ```xml <dependency> <groupId>org.activiti</groupId> <artifactId>activiti-spring-boot-starter-basic</artifactId> <version>7.0.0.Beta2</version> </dependency> ``` 2. 添加Activiti7配置,例如引入流程图扩展名,设置任务历史级别等: ```yaml spring: ## ## Activiti 7 configuration ## activiti: process-definition-location-prefix: classpath:/processes/ process-definition-location-suffix: .bpmn20.xml id-generator: simple history-level: full ``` 3. 编写Activiti7的流程定义文件,例如以下的示例: ```xml <?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:activiti="http://activiti.org/bpmn" targetNamespace="http://www.activiti.org/processdef"> <process id="approve" name="approve" isExecutable="true"> <startEvent id="start" name="Start"/> <userTask id="usertask1" name="Manager Approval" activiti:candidateGroups="managers"/> <userTask id="usertask2" name="HR Approval" activiti:candidateGroups="hr"/> <endEvent id="end" name="End"/> <sequenceFlow id="flow1" sourceRef="start" targetRef="usertask1"/> <sequenceFlow id="flow2" sourceRef="usertask1" targetRef="usertask2"/> <sequenceFlow id="flow3" sourceRef="usertask2" targetRef="end"/> </process> </definitions> ``` 4. 在Spring Boot应用程序中使用Activiti7,例如创建并启动一个流程实例: ```java @Autowired private RuntimeService runtimeService; public void startProcess(String processDefinitionKey, Map<String, Object> variables) { runtimeService.startProcessInstanceByKey(processDefinitionKey, variables); } ``` 以上步骤是Spring Boot集成Activiti7的基础,我们还可以通过扩展Activiti7服务接口,为我们的业务流程提供更加定制化和个性化的实现方式。总之,在Spring Boot项目中集成Activiti7,能够为我们提供更加高效、便捷和可靠的业务流程实现平台。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值