SpringBoot整合Flowable/Activiti

news2025/1/18 2:14:49

SpringBoot版本: 2.0.1.RELEASE

Flowable版本: 6.3.1

Activiti版本: 6.0.0

一.添加pom依赖

    因为之前我整合的时候有报错关于sqlsession的错误,后面查询文章才发现flowable要排除掉mybatis,又没说具体排除哪一个,所以我这干脆全部排除了

<!-- Flowable dependencies -->
            <dependency>
                <groupId>org.flowable</groupId>
                <artifactId>flowable-spring-boot-starter</artifactId>
                <version>6.3.1</version>
                <exclusions>
                    <exclusion>
                        <groupId>org.mybatis</groupId>
                        <artifactId>mybatis</artifactId>
                    </exclusion>
                </exclusions>
            </dependency>
            <dependency>
                <groupId>org.flowable</groupId>
                <artifactId>flowable-engine</artifactId>
                <version>6.3.1</version>
                <exclusions>
                    <exclusion>
                        <groupId>org.mybatis</groupId>
                        <artifactId>mybatis</artifactId>
                    </exclusion>
                </exclusions>
            </dependency>
            <dependency>
                <groupId>org.flowable</groupId>
                <artifactId>flowable-spring</artifactId>
                <version>6.3.1</version>
                <exclusions>
                    <exclusion>
                        <groupId>org.mybatis</groupId>
                        <artifactId>mybatis</artifactId>
                    </exclusion>
                </exclusions>
            </dependency>
            <dependency>
                <groupId>org.flowable</groupId>
                <artifactId>flowable-rest</artifactId>
                <version>6.3.1</version>
                <exclusions>
                    <exclusion>
                        <groupId>org.mybatis</groupId>
                        <artifactId>mybatis</artifactId>
                    </exclusion>
                </exclusions>
            </dependency>
            <dependency>
                <groupId>org.flowable</groupId>
                <artifactId>flowable-cmmn-spring</artifactId>
                <version>6.3.1</version>
                <exclusions>
                    <exclusion>
                        <groupId>org.mybatis</groupId>
                        <artifactId>mybatis</artifactId>
                    </exclusion>
                </exclusions>
            </dependency>
            <dependency>
                <groupId>org.flowable</groupId>
                <artifactId>flowable-dmn-spring</artifactId>
                <version>6.3.1</version>
                <exclusions>
                    <exclusion>
                        <groupId>org.mybatis</groupId>
                        <artifactId>mybatis</artifactId>
                    </exclusion>
                </exclusions>
            </dependency>

<!--activiti modeler start-->
        <dependency>
            <groupId>org.activiti</groupId>
            <artifactId>activiti-json-converter</artifactId>
            <version>6.0.0</version>
            <exclusions>
                <exclusion>
                    <groupId>org.activiti</groupId>
                    <artifactId>activiti-bpmn-model</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

二.yml配置flowable/Activiti相关配置项

  至于层级关系,都是和spring同级的,我一般放在mybatis配置项下面,类似下面这样

  flowable:
    #关闭定时任务JOB
    async-executor-activate: false
    #  将databaseSchemaUpdate设置为true。当Flowable发现库与数据库表结构不一致时,会自动将数据库表结构升级至新版本。
    database-schema-update: true




  # activiti 模块
  activiti:
    # 解决启动报错:class path resource [processes/] cannot be resolved to URL because it does not exist
    check-process-definitions: false
    # 检测身份信息表是否存在
    db-identity-used: true
    # 默认为false
      # false:项目启动时,如果数据库中没有表将抛出异常(上线后使用)
      # true:项目启动时,如果数据库中没有表将自动生成(开发和测试时使用)
    database-schema-update: true

 三.数据库配置项中url添加&nullCatalogMeansCurrent=true配置

        因为我之前用过activiti工作流,如果不加这个配置工作流会默认查询整个数据库连接下所有的库,只要发现有相关的act表就会认为已经自动生成了. 导致你在A库有了工作流的表之后想在B库再去自动生成就会发现生成不了.加上这个配置就可以了,不知道flowable影不影响,我都习惯给加上了

#  nullCatalogMeansCurrent=true
#  因为mysql使用schema标识库名而不是catalog,因此mysql会扫描所有的库来找表,
#  如果其他库中有相同名称的表,activiti就以为找到了,本质上这个表在当前数据库中并不存在。
#  解决办法就是在数据库链接的配置后面加上 nullCatalogMeansCurrent=true,标识之搜索当前链接的库。
url: jdbc:mysql://192.168.*.*:3306/flowable?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&serverTimezone=GMT%2B8&nullCatalogMeansCurrent=true

四.启动项目 自动生成数据表

        这个会根据flowable的版本而来 实际可能不一致 我的flowable版本是6.3.1,生成了66张表

Activiti版本是6.0.0,生成了28张表

一些常用的需要关注的表的含义(注明了注释)

 

五.IDEA中安装Flowable/Activiti插件

        安装插件: Flowable BPMN visualizer / Activiti BPMN visualizer

        然后在resources目录下新建processes目录

        右键新建一个基于BOMN 2.0协议的文件

        绘制流程图 

 

        注意: flowable/Activiti都会将处于processes目录下的符合后缀名要求的文件自动部署

                 不想要这个功能你可以修改目录名或者查询网站搜索下相关的关闭命令

六.工作流提供的几个Service

Flowable和Activiti都给我们提供了几个Service

因为本身Flowable的团队就是从Activiti团队中过来的,所以Service的命名及使用基本都是一致的,会一个就会另一个

    // 运行时执行管理Service
    @Autowired
    private RuntimeService runtimeService;

    // 工作流任务管理Service
    @Autowired
    private TaskService taskService;

    // 管理流程定义Service
    @Autowired
    private RepositoryService repositoryService;

    // 核心类 可以理解为流程引擎对象
    @Autowired
    private ProcessEngine processEngine;

    // 历史管理Service
    @Autowired
    private HistoryService historyService;

七.常用方法

因为几乎所有业务都需要用到"PROC_INST_ID_" 也就是 "流程实例Id"

以及当前任务id-TASK_ID

所以我建议把这两个id都放在业务表中和你的业务表一对一关联(一般都是一个业务开一个流程实例)

1.动态部署

(提供一个文件,通过repositoryService创建部署对象去读取xml文件的内容来自动部署)

部署完之后ACT_RE_DEPLOYMENT,ACT_RE_PROCDEF两个表中会有数据

MultipartFile file; // 提供一个二进制文件对象
Deployment deployment = null;
        try{
            DeploymentBuilder deploymentBuilder =
                    repositoryService.createDeployment()
.name(Objects.requireNonNull(file.getOriginalFilename()).substring(0, file.getOriginalFilename().indexOf(".bpmn20.xml")))
                            .addInputStream(file.getOriginalFilename(),
                                    file.getInputStream());
            deployment = deploymentBuilder
                            .deploy();
        }catch (Exception e){
            e.printStackTrace();
            throw new CustomException("文件部署失败");
        }


// 部署完后可以获取部署ID
// 根据部署ID查询流程定义
ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery().deploymentId(deployment.getId()).singleResult();
System.out.println("流程【"+processDefinition.getName()+"】部署成功, 部署ID为: "+processDefinition.getDeploymentId()+"流程定义Key为: "+processDefinition.getKey());

2.流程启动

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

        variables.put("startUser", SecurityUtils.getLoginUser().getUser().getUserId().toString());
        variables.put("data", processInit.getData());
        variables.put("remark", processInit.getRemark());

        ProcessInstance processInstance = null;

        try{
            // 根据部署ID查询流程定义
            ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery().deploymentId(processInit.getDeployId()).singleResult();

            //启动流程实例,同时还要指定业务标识businessKey  项目Id
            //第一个参数:是指流程定义key
            //第二个参数:业务标识businessKey
            processInstance = runtimeService.startProcessInstanceByKey(
                    processDefinition.getKey(),processInit.getWorkId().toString(),variables
            );

            runtimeService.setProcessInstanceName(processInstance.getId(),processDefinition.getName());

        启动后ACT_RU_EXECUTION会看到流程实例当前执行的节点概况,包括历史节点和当前时节点

PROC_INST_ID_是流程实例Id 

 

这两个表也可以看到实例相关节点信息

 

3.执行下一任务节点并添加审核备注

核心方法为: 

taskService.complete(task.getId());

需要注意的是:如果没有执行人,需要先认领任务而后才去审核任务

SysUser user = SecurityUtils.getLoginUser().getUser();
        String taskId; // 当前任务Id
        String processInstanceId; // 流程实例Id
        ProcessInstance processInstance = runtimeService.createProcessInstanceQuery().processInstanceId(processInstanceId).singleResult();
        String businessKey = processInstance.getBusinessKey(); // 流程实例的业务Id-businessKey

        // 获取当前待办任务
        Task task = taskService.createTaskQuery().processInstanceId(processInstanceId).taskId(taskId).singleResult();
        if(task == null){
            throw new CustomException(CustomExceptionEnums.DATA_NOT_FOUND);
        }

        String assignee = task.getAssignee();
        if(StringUtils.isEmpty(assignee)){
            // 如果没有执行人 需要先认领任务
            taskService.claim(task.getId(),user.getUserId().toString());
        }
        if(StringUtils.isNotEmpty(processApproved.getRemark())){
            // 添加审批备注
            taskService.addComment(taskId, processInstanceId, ActivityCommentType.APPROVED_REMARK.getType(), processApproved.getRemark());
        }
        // 完成当前任务
        taskService.complete(task.getId());

 通过后可通过taskService获取下一个任务节点Id

// 获取下一个任务ID
List<Task> taskList = taskService.createTaskQuery().processInstanceId(processInstanceId).list();

4.获取待办/已办

因为Activiti和Flowable中并没有获取待办任务和已办任务的直接方法可以调用

所以需要我们自己写sql语句来获取

需要用到以下的表

待办任务所需

"ACT_RU_TASK"运行时任务节点表

"ACT_RU_IDENTITYLINK"运行时流程人员表

"ACT_HI_PROCINST"历史流程实例表

已办任务所需

"ACT_HI_TASKINST"历史任务节点表

"ACT_HI_PROCINST"历史流程实例表

然后我自己通过union将结果合并,并通过task_status来区分待办和已办

-- 待办和已办union all查询
select * from
(
select 0 as task_status,
RES.ID_ as task_id,RES.NAME_ as task_name,RES.CREATE_TIME_ as start_time,null as end_time,RES.PROC_INST_ID_ as process_instance_id,RES.ASSIGNEE_ as assignee,P.BUSINESS_KEY_  as business_key ,P.NAME_ as process_instance_name ,I.TYPE_ as type,I.USER_ID_ as user_id, I.GROUP_ID_ as group_id ,RES.TASK_DEF_KEY_ as task_def_key
from ACT_RU_TASK RES 
left join ACT_RU_IDENTITYLINK I on I.TASK_ID_ = RES.ID_ 
left join ACT_HI_PROCINST P on P.PROC_INST_ID_ = RES.PROC_INST_ID_ 

						
						
						UNION ALL
						
select 1 as task_status,
RES.ID_ as task_id,RES.NAME_ as task_name,RES.START_TIME_ as start_time,RES.END_TIME_ as end_time,RES.PROC_INST_ID_ as process_instance_id,RES.ASSIGNEE_ as assignee,P.BUSINESS_KEY_  as business_key ,P.NAME_ as process_instance_name ,null as type,null as user_id, null as group_id,RES.TASK_DEF_KEY_ as task_def_key
from ACT_HI_TASKINST RES 
left join ACT_HI_PROCINST P on P.PROC_INST_ID_ = RES.PROC_INST_ID_  and RES.END_TIME_ is not null
)v

然后可以通过条件查询,比如"执行人","执行部门","任务节点定义key"等来进行查询

(task_status : 待办/已办)

(assignee : 执行人Id)

(task_def_key : 任务节点定义key)

(type : 表示候选用户 

  • candidate: 表示候选用户或用户组。在任务尚未被领取时,这些用户或用户组可以领取任务。
  • participant: 表示任务的参与者。通常情况下,当任务被领取后,参与者就是具体执行任务的用户。
  • owner: 表示任务的所有者。在某些情况下,任务可能会指定一个所有者,通常是在任务创建时指定,这个所有者有特殊权限或责任来处理任务。

)

(group_id  : 部门Id)

(start_time : 任务节点生成时间)

(end_time : 任务节点完成时间)

比如以下的where条件可以用作待办任务的筛选查询

WHERE v.task_status = 0 and v.assignee = '1'  and v.task_def_key=  'sid-5db27d85-b516-4784-8b58-0f5c9e1c3235' or (v.assignee is null and v.type = 'candidate' and (v.user_id = '1' or v.group_id IN ( '土整中心','123' ) )) order by v.start_time desc

比如以下的where条件可以用作已办任务的筛选查询 

WHERE v.task_status = 1 and v.assignee = '1' and v.task_def_key=  'sid-8abc3887-40c5-49c2-b8c0-b4d3fdefb7a0' and v.end_time is not null order by v.end_time desc


5. 通过taskId获取该任务的备注

/**
     * 通过taskId获取该任务的备注
     * @param taskId 任务Id
     * @param commentType 备注类型
     * @return 备注详情
     */
    @Override
    public List<Comment> getCommentByTaskId(String taskId,String commentType){
        if(StringUtils.isNotEmpty(commentType)){
            // 带有type筛选
            return taskService.getTaskComments(taskId, commentType);
        }
        return taskService.getTaskComments(taskId);
    }

6.查询截止目前可驳回的节点列表

我的逻辑是查询当前已完成的用户任务,不包括"开始"节点(因为"开始"节点不是用户任务)

然后有一些特殊情况需要处理:

        比如多次驳回,因为每一次驳回实际上都是将当前任务节点完成然后再加一条"目标驳回任务节点"用作下一个节点

        比如多次驳回之后取最新的节点,不可能出现可驳回列表中出现两个key相同的节点,这不符合逻辑

/**
     * 查询截止目前可驳回的节点列表
     * @param processInstanceId 流程ID
     * @return 结果
     */
    @Override
    public List<ActHistoricActivityInstanceVo> getAllActInstByInstanceIdToSimple(String processInstanceId,String taskDefKey){
        // 只要用户任务
        // 目前已完成的用户任务
        List<HistoricActivityInstance> userTask = historyService.createHistoricActivityInstanceQuery().processInstanceId(processInstanceId).orderByHistoricActivityInstanceStartTime().asc().activityType("userTask").finished().list();

        // 流程定义时的用户任务
        ProcessInstance processInstance = runtimeService.createProcessInstanceQuery().processInstanceId(processInstanceId).singleResult();
        String processDefinitionId = processInstance.getProcessDefinitionId();
        ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery().processDefinitionId(processDefinitionId).singleResult();
        // 定义时的所有节点
        Collection<FlowElement> flowElements = repositoryService.getBpmnModel(processDefinitionId).getMainProcess().getFlowElements();
        List<ActHistoricActivityInstanceVo> userTasks = new ArrayList<>();

        // 循环
        for (HistoricActivityInstance historicActivityInstance : userTask) {

            for (FlowElement flowElement : flowElements) {
                if (flowElement instanceof UserTask) {
                    UserTask definitionUserTask = (UserTask) flowElement;
                    String activityName = definitionUserTask.getName();
                    String activityId = definitionUserTask.getId();
                    String activityTaskDefKey = definitionUserTask.getId();
                    // 可以查看其他用户任务属性,如Assignee、Candidate Users、Candidate Groups等

                    // 获取任务定义对象信息
                    // 入参taskDefKey对应的对象
                    SysProcessTaskDef processTaskDef = sysProcessTaskDefMapper.selectSysProcessTaskDefById(taskDefKey);
                    // 当前循环体中taskDefKey对应的对象
                    SysProcessTaskDef currentTaskDef = sysProcessTaskDefMapper.selectSysProcessTaskDefById(activityTaskDefKey);
                    if(currentTaskDef.getSort() >= processTaskDef.getSort()){
                        // 不能驳回到后面的任务去(防止二次提交后可驳回的任务列表乱套)
                        continue;
                    }

                    // 如果Id匹配 就加入自定义集合返回
                    if(activityId.equals(historicActivityInstance.getActivityId()) && !activityTaskDefKey.equals(taskDefKey)){

                        ActHistoricActivityInstanceVo activityInstanceVo = new ActHistoricActivityInstanceVo();

                        activityInstanceVo.setActivityId(activityId);
                        activityInstanceVo.setActivityName(activityName);
                        // 对重复的跳过 (针对多次驳回导致存在多个相同节点的情况)
//                        if(userTasks.contains(activityInstanceVo)) continue;
                        ActHistoricActivityInstanceVo actHistoricActivityInstanceVo = userTasks.stream().
                                filter(u -> u.getActivityId().equals(activityInstanceVo.getActivityId())).findAny().orElse(null);
                        if(actHistoricActivityInstanceVo != null && actHistoricActivityInstanceVo.getStartTime().compareTo(historicActivityInstance.getStartTime()) < 0){
                            // 存在并且时间早于这一次就删除 (针对多次驳回导致存在多个相同节点的情况,取最新的节点审核情况)
                            userTasks.remove(actHistoricActivityInstanceVo);
                        }
                        // 转换赋值
                        BeanUtils.copyProperties(historicActivityInstance, activityInstanceVo);
                        // 获取type为isFillSteps的comment(是否为填报步骤:提交填报时的那个步骤)
                        List<Comment> comments = taskService.getTaskComments(activityInstanceVo.getTaskId(), "isFillSteps");
                        if(comments != null && comments.size() > 0){
                            activityInstanceVo.setFillSteps(true);
                        }
                        userTasks.add(activityInstanceVo);
                    }
                }
            }
        }
        if(userTasks.size() > 0){
            userTasks = userTasks.stream().sorted(Comparator.comparing(ActHistoricActivityInstanceVo::getStartTime)).collect(Collectors.toList());
        }
        return userTasks;
    }

 

7.驳回到指定节点

提供流程实例Id,当前任务Id,驳回目标任务Id



    /**
     * 驳回到指定节点
     * @param actRollbackIo 驳回对象
     */
    @Override
    @Transactional(rollbackFor = Throwable.class,propagation = Propagation.REQUIRES_NEW)
    public void rejectToNode(ActRollbackIo actRollbackIo){
        String processInstanceId = actRollbackIo.getProcessInstanceId();
        String currentTaskId = actRollbackIo.getTaskId(); // 当前任务
        String targetTaskId = actRollbackIo.getTargetTaskId(); // 驳回目标任务

        //  获取该流程所有的活动实例
        List<HistoricActivityInstance> hisActivityList = historyService.createHistoricActivityInstanceQuery()
                .processInstanceId(processInstanceId).list();

        ProcessInstance processInstance = runtimeService.createProcessInstanceQuery().processInstanceId(processInstanceId).singleResult();
        String processDefinitionId = processInstance.getProcessDefinitionId();
        //  当前任务
        Task currentTask = taskService.createTaskQuery().taskId(currentTaskId).singleResult();

        //  驳回目标任务
        HistoricTaskInstance targetTask = historyService.createHistoricTaskInstanceQuery().finished().taskId(targetTaskId).singleResult();

        if(currentTask == null || targetTask == null){
            throw new CustomException(CustomExceptionEnums.MISSING_DATA_ERROR);
        }

        try{
            //  当前活动
            HistoricActivityInstance currentActivity = hisActivityList.stream().filter(e -> currentTask.getId().equals(e.getTaskId())).collect(Collectors.toList()).get(0);
            //  驳回目标活动
            HistoricActivityInstance targetActivity = hisActivityList.stream().filter(e -> targetTask.getId().equals(e.getTaskId())).collect(Collectors.toList()).get(0);

            //  获取xml文件对象
            BpmnModel bpmnModel = repositoryService.getBpmnModel(processDefinitionId);

            //  获取驳回目标活动节点
            FlowNode targetFlowNode = (FlowNode) bpmnModel.getMainProcess().getFlowElement(targetActivity.getActivityId());
            //  获取当前活动节点
            FlowNode currentFlowNode = (FlowNode) bpmnModel.getMainProcess().getFlowElement(currentActivity.getActivityId());

            //  临时保存当前活动的原始方向
            List<SequenceFlow> originalSequenceFlowList = new ArrayList<>();
            originalSequenceFlowList.addAll(currentFlowNode.getOutgoingFlows());
            //  清理活动方向
            currentFlowNode.getOutgoingFlows().clear();

            //  建立新方向
            SequenceFlow newSequenceFlow = new SequenceFlow();
            newSequenceFlow.setId("newSequenceFlowId");
            newSequenceFlow.setSourceFlowElement(currentFlowNode);
            newSequenceFlow.setTargetFlowElement(targetFlowNode);
            List<SequenceFlow> newSequenceFlowList = new ArrayList<>();
            newSequenceFlowList.add(newSequenceFlow);
            //  当前节点指向新的方向
            currentFlowNode.setOutgoingFlows(newSequenceFlowList);

            //  完成当前任务
            taskService.claim(currentTaskId,SecurityUtils.getLoginUser().getUser().getUserId().toString());
            taskService.complete(currentTaskId);

            //  重新查询当前任务
            Task nextTask = taskService.createTaskQuery().processInstanceId(processInstanceId).singleResult();
            String assignee = (StringUtils.isNull(actRollbackIo.getAssignee())) ? targetTask.getAssignee() : actRollbackIo.getAssignee().toString();
            if (null != nextTask) {
                System.out.println("最新任务=="+nextTask);
                taskService.setAssignee(nextTask.getId(), assignee);
            }else{
                throw new CustomException(CustomExceptionEnums.OPERATION_FAILED);
            }

            //  恢复原始方向
            currentFlowNode.setOutgoingFlows(originalSequenceFlowList);
}

8.撤回 删除流程

    /**
     * 撤回 删除流程
     * @param processInstanceId 流程实例Id
     * @param fillId 任务填报Id
     * @param message 撤回备注信息
     */
    @Override
    @Transactional(rollbackFor = Throwable.class)
    public int revoke(String processInstanceId,Long fillId,String message){
        runtimeService.deleteProcessInstance(processInstanceId, message);
        historyService.deleteHistoricProcessInstance(processInstanceId);
        return landProjectFillService.cleanProcessInstance(fillId);
    }

后续有新的方法再来补充

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1569211.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Servlet 的基本理解

Servlet 是JavaEE规范的一种&#xff0c;主要是为了扩展Java作为Web服务的功能&#xff0c;统一接口。由其他内部厂商如tomcat&#xff0c;jetty内部实现web的功能。如一个http请求到来&#xff1a;容器将请求封装为servlet中的HttpServletRequest对象&#xff0c;调用init()&a…

Spring Security 实现后台切换用户

Spring Security version 后端代码&#xff1a; /*** author Jerry* date 2024-03-28 17:47* spring security 切换账号*/RestController RequiredArgsConstructor RequestMapping("api/admin") public class AccountSwitchController {private final UserDetailsSe…

即将截稿 CCF-A多媒体顶会ACM MM‘24 北京时间4月9日提交摘要

会议之眼 快讯 第32届ACM MM (ACM MULTIMEDIA)即国际多媒体会议将于 2024 年 10月28 -日11月1日在澳大利亚墨尔本隆重举行&#xff01;MM是由ACM&#xff08;Association for Computing Machinery&#xff0c;计算机协会&#xff09;主办的国际性学术会议&#xff0c;是计算机…

应对苹果商店审核失败:解决Flutter应用被拒绝的常见情况与解决方案

引言 Flutter是一款由Google推出的跨平台移动应用开发框架&#xff0c;其强大的性能和流畅的用户体验使其备受开发者青睐。然而&#xff0c;开发一款应用只是第一步&#xff0c;将其成功上架到苹果商店才是实现商业目标的关键一步。本文将详细介绍如何使用Flutter将应用程序上…

Day81:服务攻防-开发框架安全SpringBootStruts2LaravelThinkPHPCVE复现

目录 PHP-框架安全-Thinkphp&Laravel Laravel CVE-2021-3129 RCE Thinkphp 版本3.X RCE-6.X RCE 版本6.X lang RCE J2EE-框架安全-SpringBoot&Struts2 Struct2 旧漏洞(CVE-2016-0785等) struts2 代码执行 &#xff08;CVE-2020-17530&#xff09;s2-061 Str…

为什么要学习数学/科学史?

一、说明 哈代的经典著作《数学家的道歉》&#xff0c;他在书中为自己选择的数学职业辩护&#xff0c;他坦诚了自己一生贡献之微不足道。事实是&#xff0c;哈代没什么可道歉的。哈代是一位真正的顶级数学家&#xff0c;完全有资格获得他选择的头衔&#xff0c;并且以伯乐之能…

HarmonyOS NEXT应用开发之ForEach:循环渲染

ForEach接口基于数组类型数据来进行循环渲染&#xff0c;需要与容器组件配合使用&#xff0c;且接口返回的组件应当是允许包含在ForEach父容器组件中的子组件。例如&#xff0c;ListItem组件要求ForEach的父容器组件必须为 List组件 。 说明&#xff1a; 从API version 9开始&a…

蓝桥杯嵌入式备考笔记

keil配置 LED-KEY-LCD 留下这几个 按键 创建俩个文件写代码&#xff0c;记得把这两个文件加进工程 led uwTick 1ms执行一次 写错了 不是1000 是100 PD2 SET 表示打开锁存器 可以操作LED LED对应PC引脚拉高是熄灭 key 如果要在main使用需要extern LCD最多21位 …

C语言进阶课程学习记录-第21课 - 宏定义与使用分析

C语言进阶课程学习记录-第21课 - 宏定义与使用分析 宏定义的本质实验-字面量比较宏定义表达式实验-表达式有效性宏的作用域实验-作用域分析内置宏内置宏演示小结 本文学习自狄泰软件学院 唐佐林老师的 C语言进阶课程&#xff0c;图片全部来源于课程PPT&#xff0c;仅用于个人学…

012:vue3使用vue-i18n实现国际化

文章目录 1. 安装 vue-i18n2. 创建文件存储翻译的语言3. 注册i18n实例4. 在main.ts中引入vue-i18n实例5. 在组件模板中使用6. 在js中使用7. locale.value 实现国际化语言切换8. vue3 中ref里面的国际化值没生效问题 1. 安装 vue-i18n cnpm i --save vue-i18n2. 创建文件存储翻…

进销存管理系统:食品批发零售迈向数字化未来-亿发

随着消费逐步复苏&#xff0c;食品批发零售行业也迎来了客流的回升&#xff0c;实体店重新焕发了生机。然而&#xff0c;随着数字化时代的来临&#xff0c;传统的食品批发零售企业面临着新的挑战和机遇。些企业正积极实施数字化转型&#xff0c;通过布局线上线下多业态的融合发…

jQuery(一)

文章目录 1. 基本介绍2.原理示意图3.快速入门1.下载jQuery2.创建文件夹&#xff0c;放入jQuery3.引入jQuery4.代码实例 4.jQuery对象与DOM对象转换1.基本介绍2.dom对象转换JQuery对象3.JQuery对象转换dom对象4.jQuery对象获取数据获取value使用val&#xff08;&#xff09;获取…

翻译: 硅谷软件工程师面试:准备所需的一切

没有人有时间去做成百上千道LeetCode题目&#xff0c;好消息是你实际上并不需要做那么多题目就能够在FAANG公司找到工作&#xff01; 我曾经在Grab工作&#xff0c;这是东南亚的一家共享出行公司&#xff0c;但我对工作感到沮丧&#xff0c;想要进入FAANG公司&#xff0c;但我…

编译好的C++应用程序拷贝到其它电脑,提示dll未找到依赖项的解决方法。

编译好的C应用程序拷贝到其它电脑上&#xff0c;运行时出现提示dll未找到依赖项。 由于dll依赖于其它dll&#xff0c;在开发用电脑上的环境不能完全与其它电脑相同。 解决办法是找到调用到的dll依赖的所有dll&#xff0c;拷贝到运行目录下。 在开发电脑上&#xff1a; 1、开…

一款功能强大且易于使用的视频剪辑应用程序

一款功能强大且易于使用的视频剪辑应用程序&#xff0c;它提供了丰富多样的转场特效和滤镜&#xff0c;让用户能够轻松地为视频添加各种炫酷的效果。与其他视频编辑软件相比&#xff0c;剪映国际版的最大亮点在于其完全免费使用。首先&#xff0c;剪映国际版为用户提供了丰富的…

ChatGPT/GPT4科研应用与绘图技术及论文写作

2023年随着OpenAI开发者大会的召开&#xff0c;最重磅更新当属GPTs&#xff0c;多模态API&#xff0c;未来自定义专属的GPT。微软创始人比尔盖茨称ChatGPT的出现有着重大历史意义&#xff0c;不亚于互联网和个人电脑的问世。360创始人周鸿祎认为未来各行各业如果不能搭上这班车…

初识MySQL(中篇)

使用语言 MySQL 使用工具 Navicat Premium 16 代码能力快速提升小方法&#xff0c;看完代码自己敲一遍&#xff0c;十分有用 目录 1.SQL语言 1.1 SQL语言组成部分 2.MySQL数据类型 2.1 数值类型 2.2 字符串类型 2.3 日期类型 3.创建数据表 3.1 创建数据表方法1 …

Lan仿朋友圈系统开源源码 表白墙 打造朋友圈工具 仿朋友圈界面 朋友圈模拟器在线

(购买本专栏可免费下载栏目内所有资源不受限制,持续发布中,需要注意的是,本专栏为批量下载专用,并无法保证某款源码或者插件绝对可用,介意不要购买!购买本专栏住如有什么源码需要,可向博主私信,第二天即可发布!博主有几万资源) Lan仿朋友圈系统开源,可用于表白墙等…

【DA-CLIP】test.py解读,调用DA-CLIP和IRSDE模型复原计算复原图与GT图SSIM、PSNR、LPIPS

文件路径daclip-uir-main/universal-image-restoration/config/daclip-sde/test.py 代码有部分修改 导包 import argparse import logging import os.path import sys import time from collections import OrderedDict import torchvision.utils as tvutilsimport numpy as…

clickhouse 源码编译部署

clickhouse 源码编译部署 版本 21.7.9.7 点击build project&#xff0c;编译工程&#xff0c;经过一定时间&#xff08;第一次编译可能几个小时&#xff0c;后续再编译&#xff0c;只编译有改动的文件&#xff09;生成release目录 在cmake-build-release → programs目录下…