仿activiti实现一个简版工作流

简版工作流

  • 前言
  • 功能实现
  • 1.需求及实现
  • 2.代码实现
    • 1.流程设置
    • 2.项目权限设置
    • 1.表设计
  • 3.任务流程处理
    • 1.表设计
    • 2.代码实现
  • 4.任务流程记录
  • 5.接口说明

前言

本文代码实现是仿照工作流实现的,由于业务需求的特殊性,没有直接使用工作流。

功能实现

功能实现
功能说明
任务查询1.个人任务查询
2.节点任务查询
3.组内任务查询
上述3种查询可组合查询
任务驳回1.驳回到任意节点
2.节点跳跃(可忽略驳回点与驳回至节点之间的任意节点)
任务分配(拾取)将任务分配给某个人
任务归还这个操作在项目中没有具体使用,不过已经实现相关功能
任务删除1.删除当前任务
2.删除任务流转记录
支持强制删除任务(默认进行中的任务无法删除),自由设置任务流程记录是否删除
流程记录1.流程记录查询
2.流程记录添加
3.流程记录删除
1.责任人与组为一对多关系
2.不支持网关相关逻辑

1.需求及实现

以下根据实际项目进行需求举例说明,以下截图为功能部分截图,仅供参考

需求说明实现效果
1️⃣业务流程设置1.管理员可以对任意流程节点进行开启关闭操作
2.节点设置,仅对之后的流程实例生效,进行中的仍按之前的流程节点进行
2️⃣项目权限(节点任务组)设置1.为不同的人员 ,在项目节点下设置不同的任务组权限
将这个功能带入到工作流中,可以理解为在某个节点设置任务组,把人员分到不同的组中,只有当前组下的人员才能看到任务

说明: 此图仅供参考,为本人实际项目部分图,项目权限为流程权限,其中包含了非流程节点。
1.项目权限:表示流程节点
2.类别名称:可以理解为任务 组。

2.代码实现

1.流程设置

这里的流程设置,相当于画任务流程节点图,我是将节点都放在了数据字典中在这里插入图片描述
然后将需要的节点存储即可,这里比较简单,就不列代码了。

2.项目权限设置

流程节点下任务组,责任人设置

1.表设计

在这里插入图片描述
operator_auth,用于存放当前检测项目(任务组)下不同节点的权限。这个不是本文的重点,这里就不列出具体的代码了。

3.任务流程处理

1.表设计

1.detection_flow_task

detection_flow_task 任务主表
类型 注释
model_type varchar(255) 流程节点
test_code varchar(255) 检测项目编号(任务组)
business_key bigint(20) 业务id
signee bigint(20) 责任人
variable varchar(1000)变量
next_node varchar(255)下个节点
operate int(11)操作类型 0驳回 1通过

2.detection_flow_nodes
存储任务流程实时节点数据

detection_flow_task 任务主表
类型 注释
model_type varchar(255)任务节点
business_key bigint(20) 业务id

3.detection_flow_his

detection_flow_his 任务流程历史记录
类型 注释
model_type varchar(255)流程节点
business_key bigint(20) 业务id
variable varchar(1000)变量
next_node varchar(255)下个节点
operate int(11)操作类型 0驳回 1通过

2.代码实现

代码较多,这里只贴出核心代码
1.FlowTaskController

@Tag(name = "管理后台 - 任务流程-主表-当前任务")
@RestController
@RequestMapping("/detection/flow-task")
@Validated
public class FlowTaskController {
    @Resource
    private FlowTaskService flowTaskService;
    
    /**
     * @description发起流程 ,默认从起始节点开始
     * @author lvyq
     * @throws
     * @time 2023-09-15 10:10
     */
    @PostMapping("/startFlow")
    @Operation(summary = "发起任务流程")
    public CommonResult startFlow(@Valid @RequestBody FlowTaskCreateReqVO completeVO){
        flowTaskService.startFlow(completeVO.getBusinessKey(),completeVO.getSignee(),completeVO.getStartNode(),completeVO.getTestCode());
        return success(true);
    }
    
    /**
     * @description  完成当前流程
     * @author lvyq
     * @param[1] createReqVO
     * @throws
     * @return CommonResult<Long>
     * @time 2023-08-15 11:50
     */

    @PutMapping("/complete")
    @Operation(summary = "完成任务")
    public CommonResult<Boolean> complete(@Valid @RequestBody FlowTaskCompleteVO completeVO) {
        flowTaskService.complete(completeVO);
        return success(true);
    }
    /**
     * @description 任务驳回
     * @author lvyq
     * @param[1] completeVO
     * @throws
     * @return CommonResult<Boolean>
     * @time 2023-08-22 13:33
     */
    @PutMapping("/reject")
    @Operation(summary = "任务驳回")
    public CommonResult<Boolean> reject(@Valid @RequestBody FlowTaskRejectVO vo) {
        flowTaskService.reject(vo);
        return success(true);
    }
    /**
     * @description 获取当前节点的待办任务
     * @author lvyq
     * @param[1] pageVO
     * @throws
     * @return CommonResult<PageResult<FlowTaskRespVO>>
     * @time 2023-08-15 15:03
     */
    @GetMapping("/page")
    @Operation(summary = "获得任务流程-主表-当前任务分页")
    @TaskFlowAuth
    public CommonResult getFlowTaskPage(@Valid FlowTaskPageReqVO pageVO) {
        return success(flowTaskService.getFlowTaskPage(pageVO));
    }
    /**
     * @description
     * @author lvyq
     * @param[1] pageVO 指派任务
     * @throws
     * @return CommonResult
     * @time 2023-09-25 14:25
     */
    @PutMapping("/assignTask")
    public CommonResult assignTask(@Valid @RequestBody AssignTaskVo completeVO){
        flowTaskService.assignTask(completeVO);
        return success(true);
    }

    /**
     * @description  重新发起流程
     * @author lvyq
     * @param[1] completeVO
     * @throws
     * @return CommonResult
     * @time 2023-09-26 15:05
     */
    @PutMapping("/reStartFlow")
    @Operation(summary = "重新发起任务流程")
    public void reStartFlow(@Valid @RequestBody ReStartFlowTaskVo vo){
        flowTaskService.reStartFlow(vo.getBusinessKey(),vo.getDelHis());
    }
}

2.FlowTaskServiceImpl

@Service
@Validated
public class FlowTaskServiceImpl implements FlowTaskService {

    @Resource
    private FlowTaskMapper flowTaskMapper;

    @Resource
    private FlowHisMapper flowHisMapper;
    @Resource
    private FlowHisService flowHisService;

    @Resource
    private FlowNodesMapper flowNodesMapper;


    @Resource
    private StringRedisTemplate stringRedisTemplate;

    @Override
    public void deleteFlowTask(Long id) {
        // 校验存在
        validateFlowTaskExists(id);
        // 删除
        flowTaskMapper.deleteById(id);
    }

    private void validateFlowTaskExists(Long id) {
        if (flowTaskMapper.selectById(id) == null) {
            throw exception(FLOW_TASK_NOT_EXISTS);
        }
    }

    @Override
    public FlowTaskDO getFlowTask(Long id) {
        return flowTaskMapper.selectById(id);
    }

    @Override
    public List<FlowTaskDO> getFlowTaskList(Collection<Long> ids) {
        return flowTaskMapper.selectBatchIds(ids);
    }

    @Override
    public PageResult<FlowTaskRespVO> getFlowTaskPage(FlowTaskPageReqVO pageReqVO) {
        IPage<FlowTaskRespVO> page = new Page<>(pageReqVO.getPageNo(), pageReqVO.getPageSize());
        List<FlowTaskRespVO> flowTaskDOList = flowTaskMapper.getFlowTaskPageForDetection(page,pageReqVO);
        List<String> nodes= getNodes();
        flowTaskDOList.forEach(ls->{
            Map<String,Object> map = new HashMap<>();
            //历史节点
            String flowNodes = ls.getFlowNodes();
            if (!StringUtils.isEmpty(flowNodes)){
                String[] split = flowNodes.split(",");
                List<String> list = Arrays.asList(split);
                nodes.forEach(no->{
                    if (list.contains(no)){
                        map.put(no,1);
                    }else {
                        map.put(no,0);
                    }
                });
            } else {
                nodes.forEach(node->{
                    map.put(node,0);
                });
            }
            //设置当前节点为否
            map.put(ls.getModelType(),0);
            ls.setFlowStatus(map);
        });
        return new PageResult<>(flowTaskDOList,page.getTotal());

    }


    /**
     * @return boolean
     * @throws
     * @description 检测任务是否存在
     * @author lvyq
     * @time 2023-08-15 13:42
     */
    public boolean checkTask(Long businessKey) {
        boolean state = false;
        FlowTaskDO flowTaskDO = flowTaskMapper.selectOne(FlowTaskDO::getBusinessKey, businessKey);
        if (flowTaskDO != null) state = true;
        return state;
    }


    /**
     * @throws
     * @description
     * @author lvyq  审核通过
     * @param[1] completeVO
     * @time 2023-08-22 10:52
     */
    @Override
    @Transactional
    public void  complete(FlowTaskCompleteVO completeVO) {
        //获取当前任务
        FlowTaskDO taskDO = validateFlowTask(completeVO.getBusinessKey());
        //信息补全
        taskDO.setId(null);
        taskDO.setVariable(completeVO.getVariable());
        taskDO.setRemark(completeVO.getRemark());
        taskDO.setSignee(completeVO.getSignee());
        taskDO.setOperate(1);
        //流程节点
        List<String> nodes = flowNodesMapper.selectOne(FlowNodesDO::getBusinessKey, completeVO.getBusinessKey()).getModelType();
        FlowHisCreateReqVO flowHisCreateReqVO = BeanUtil.copyProperties(taskDO, FlowHisCreateReqVO.class);
        //当前流程没有下个环节,则表示流程通过后即结束当前流程
        flowTaskMapper.deleteByBusinessKey(taskDO.getBusinessKey());
        if (StringUtils.isEmpty(taskDO.getNextNode())) {
            flowHisCreateReqVO.setRemark("流程结束");
        } else {
            //前往下个流程
            completeTask(nodes, taskDO);
        }
        flowHisService.createFlowHis(flowHisCreateReqVO);
    }

    /**
     * @throws
     * @description 审核通过处理
     * @author lvyq
     * @time 2023-09-15 9:47
     */
    private void completeTask(List<String> nodes, FlowTaskDO taskDO) {
        //即将前往的流程节点
        int currentNodeIndex = getNodeIndex(nodes,taskDO.getNextNode());
        taskDO.setModelType(taskDO.getNextNode());
        if (currentNodeIndex >= nodes.size() - 1) {
            //即将前往的节点为流程的最后一个节点,则不存在下个节点
            taskDO.setNextNode(null);
        } else {
            taskDO.setNextNode(nodes.get(currentNodeIndex + 1));
        }
        flowTaskMapper.insert(taskDO);
    }


    /**
     * @throws
     * @description 审核驳回
     * @author lvyq
     * @param[1] completeVO
     * @time 2023-08-22 13:45
     */
    @Override
    @Transactional
    public void reject(FlowTaskRejectVO completeVO) {
        taskValCheck(completeVO);
        //获取当前任务
        FlowTaskDO taskDO = validateFlowTask(completeVO.getBusinessKey());
        //处理最新的流程节点集合
        List<String> nodes = checkCurrentNodes(completeVO);
        // variable 设置
        taskDO.setVariable(completeVO.getVariable());
        taskDO.setId(null);
        taskDO.setRemark(completeVO.getRemark());
        taskDO.setOperate(0);//驳回
        FlowHisCreateReqVO flowHisCreateReqVO = BeanUtil.copyProperties(taskDO, FlowHisCreateReqVO.class);
        //历史记录节点处理
        //1.发起节点 为未变更时taskDO的当前任务节点,下个节点为变更后的taskDo的当前节点
        if (!StringUtils.isEmpty(completeVO.getRejectModelType())) {
            if (!nodes.contains(completeVO.getRejectModelType())) {
                throw exception(new ErrorCode(10001, "驳回节点不存在,驳回失败"));
            }
            //指定回退节点
            rejectSpecifyNode(nodes, taskDO, completeVO.getRejectModelType(), completeVO.getIsInOrder());
        } else {
            //按序驳回,禁止忽略流程节点操作,不对相应数据进行处理
            rejectInOrder(nodes, taskDO);
        }
        //删除当前任务数据
        flowTaskMapper.deleteByBusinessKey(taskDO.getBusinessKey());
        flowTaskMapper.insert(taskDO);
        //flowHisCreateReqVO.setOperate(0);
        flowHisCreateReqVO.setNextNode(taskDO.getModelType());
        flowHisService.createFlowHis(flowHisCreateReqVO);
    }

    /**
     * @return
     * @throws
     * @description 发起流程
     * @author lvyq
     * @param[1] businessKey 业务id
     * @param[2] signee  责任人
     * @param[3] startNode 流程起始节点,缺省时默认第一个节点
     * @time 2023-09-15 10:43
     */
    @Override
    public void startFlow(Long businessKey, Long signee, String startNode, String testCode) {
        //查询当前流程是否存在
        if (checkTask(businessKey)){
            throw exception(FLOW_IS_CREATED);
        }
        List<String> nodes = getNodes();
        FlowNodesDO nodesDO = new FlowNodesDO();
        //流程节点存储
        nodesDO.setBusinessKey(businessKey);
        nodesDO.setModelType(nodes);
        //任务主表
        FlowTaskDO flowTask = new FlowTaskDO();
        flowTask.setSignee(signee);
        flowTask.setBusinessKey(businessKey);
        //通过
        flowTask.setOperate(1);
        //检测项目
        flowTask.setTestCode(testCode);
        FlowHisCreateReqVO flowHisCreateReqVO = BeanUtil.copyProperties(flowTask, FlowHisCreateReqVO.class);
        if (startNode != null) {
            flowTask.setModelType(startNode);
            //下个节点获取 ,即将前往的流程节点
            int currentNodeIndex = getNodeIndex(nodes,startNode);
            if (currentNodeIndex >= nodes.size() - 1) {
                //即将前往的节点为流程的最后一个节点,则不存在下个节点
                flowTask.setNextNode(null);
            } else {
                flowTask.setNextNode(nodes.get(currentNodeIndex + 1));
            }
        } else {
            flowTask.setModelType(nodes.get(0));
            flowTask.setNextNode(nodes.get(1));
        }
        //创建任务记录
        flowTaskMapper.insert(flowTask);
        flowNodesMapper.insert(nodesDO);
        //流程历史记录处理补全

        //起始环节为空
        flowHisCreateReqVO.setModelType(null);
        //历史记录中的下个环节设置为当前所在的环节-需求设计
        flowHisCreateReqVO.setNextNode(flowTask.getModelType());
        flowHisCreateReqVO.setRemark("发起流程");

        flowHisService.createFlowHis(flowHisCreateReqVO);
    }





    /**
     * @description
     * @author lvyq
     * @param[1] completeVO
     * @throw 接口增强,追加判断,当前判断方法可有可无,之后的实现已根据该方法中的逻辑进行了相应处理,
     * 此方法仅做为接口增强
     * @time 2023-09-15 9:20
     */
    private void taskValCheck(FlowTaskRejectVO completeVO) {
        Integer isInOrder = completeVO.getIsInOrder();
        if (completeVO.getRejectModelType() != null) {
            //指定节点退回,
            //isOrder允许缺省,缺省时默认为1,按序流转  0 非按序流转
            if (isInOrder != null &&  isInOrder != 1 && isInOrder != 0) {
                throw exception(new ErrorCode(20230915, "非法参数{isInOrder}"));
            }
        } else {
            //非指定节点退回,判断,【需求设计】不允许移除流程节点,isInOrder缺省处理.以下逻辑已在相应实现中进行了处理,此处仅作为提示使用
            if (completeVO.getExcludeModelType() != null && completeVO.getExcludeModelType().size() > 0) {
                throw exception(new ErrorCode(20230915, "非法参数{excludeModelType}"));
            }
            //isInOrder 缺省处理即可,{1} 按序
            if (isInOrder != null) {
                throw exception(new ErrorCode(20230915, "非法参数{isInOrder}"));
            }

        }
    }


    /**
     * @throws
     * @description
     * @author lvyq
     * 按顺驳回
     * @time 2023-09-15 9:10
     */
    private void rejectInOrder(List<String> nodes, FlowTaskDO taskDO) {
        //非指定回退 默认退回上一步 下标位置
        int oldNodeIndex = getNodeIndex(nodes,taskDO.getModelType());
        //当前节点
        if (oldNodeIndex == 0) {
            //首节点,无法驳回
            throw exception(FLOW_ILLEGAL_OPERATION);
        } else {
            taskDO.setNextNode(nodes.get(oldNodeIndex));
            taskDO.setModelType(nodes.get(oldNodeIndex - 1));
        }
    }

    /**
     * @throws
     * @description
     * @author lvyq
     * 驳回到指定节点
     * @time 2023-09-15 8:45
     */
    private void rejectSpecifyNode(List<String> nodes, FlowTaskDO taskDO, String rejectModelType, Integer isInOrder) {
        //将要驳回至的节点
        String nextNode = taskDO.getModelType();
        taskDO.setModelType(rejectModelType);
        if (isInOrder == 0) {
            //非顺序 即回到驳回点
            taskDO.setNextNode(nextNode);
        } else {
            //按序
            int nodeIndex = getNodeIndex(nodes,rejectModelType);
            taskDO.setNextNode(nodes.get(nodeIndex + 1));
        }
    }

    /**
     * @return
     * @throws
     * @description
     * @author lvyq
     * 校验数据是否存在,存在则返回信息
     * @time 2023-09-14 17:24
     */
    private FlowTaskDO validateFlowTask(@NotNull(message = "businessKey不能为空") Long businessKey) {
        FlowTaskDO taskDO = flowTaskMapper.selectOne(FlowTaskDO::getBusinessKey, businessKey);
        if (taskDO == null) throw exception(FLOW_TASK_NOT_EXISTS);
        return taskDO;
    }

    /**
     * @return List<String>
     * @throws
     * @description
     * @author lvyq
     * 最新流程节点处理
     * @time 2023-09-14 17:03
     */
    private List<String> checkCurrentNodes(FlowTaskRejectVO completeVO) {
        //TODO 流程节点,获取全部流程节点
        List<String> nodesBefore = getNodes();
        List<String> currentNodes = new ArrayList<>();
        if (completeVO.getExcludeModelType() != null && completeVO.getExcludeModelType().size() > 0) {
            //更新流程节点
            nodesBefore.stream().filter(str -> !completeVO.getExcludeModelType().contains(str)).forEach(currentNodes::add);
        } else {
            currentNodes = nodesBefore;
        }
        //bug -2023.09.18  解决通过其它节点,未设置移除的节点时,仍使用上一个驳回所设置的流程节点问题。每次驳回都进行流程节点设置
        updateFlowNodes(currentNodes,completeVO.getBusinessKey());
        return currentNodes;
    }


    /**
     * @throws
     * @description
     * @author lvyq
     * 更新FlowNodes ,用于在审核通过时查询最新的流程
     * @time 2023-09-14 17:10
     */
    private void updateFlowNodes(@NotNull(message = "非法操作,流程节点不可为空") List<String> nodes, @NotNull(message = "onlineDataId不能为空") Long onlineDataId) {
        FlowNodesDO nodesDO = flowNodesMapper.selectOne(FlowNodesDO::getBusinessKey, onlineDataId);
        nodesDO.setModelType(nodes);
        flowNodesMapper.updateById(nodesDO);
    }

    private List<String> getNodes() {
        List<String> nodes = new ArrayList<>();
        String flowNodes = stringRedisTemplate.opsForValue().get("flowNode:"+SecurityFrameworkUtils.getLoginUser().getTenantId().toString());
        String[] split = flowNodes.split(",");
        for (String s : split) {
            nodes.add(s);
        }
        return nodes;
    }

    /**
     * @description 任务指派 -当前节点任务指派
     * @author lvyq
     * @param[1] completeVO
     * @throws

     * @time 2023-09-25 14:26
     */
    @Override
    public void assignTask(AssignTaskVo completeVO) {
        FlowTaskDO taskDO = validateFlowTask(completeVO.getBusinessKey());
        //修改数据
        taskDO.setSignee(completeVO.getSignee());
        flowTaskMapper.updateById(taskDO);
    }

    /**
     * @description  获取节点所在的下标
     * @author lvyq
     * @throws
     * @return Integer
     * @time 2023-09-26 11:40
     */
    public Integer getNodeIndex(List<String> nodes,String node){
        int[] nodeIndexArr = ListUtil.indexOfAll(nodes, node::equals);
        int nodeIndex = nodeIndexArr[0];
        return nodeIndex;
    }

    /**
     * @description 重新发起流程-逻辑抽离
     * @author lvyq
     * @param[1] businessKey
     * @param[2] delHis
     * @throws
     * @time 2023-09-26 15:23
     */
    @Override
    @Transactional
    public void reStartFlow(Long businessKey, Integer delHis) {
        //TODO 判断节点, 需求 - 审核节点,待优化成可配选项
        String reSetNode="doExamine";
        //获取当前任务节点
        FlowTaskDO taskDO = flowTaskMapper.selectOne(FlowTaskDO::getBusinessKey, businessKey);
        FlowHisCreateReqVO flowHisCreateReqVO = BeanUtil.copyProperties(taskDO, FlowHisCreateReqVO.class);
        //存在任务节点,判断是否需要修改节点
        if (taskDO!=null){
            String modelType = taskDO.getModelType();
            //判断当前节点是否是审核后的节点,如果是审核后的则将流程数据流转至审核节点上,同时将业务流转节点重置
            List<String> nodes = flowNodesMapper.selectOne(FlowNodesDO::getBusinessKey, businessKey).getModelType();
            Integer nodeIndex = getNodeIndex(nodes, modelType);
            Integer reSetIndex = getNodeIndex(nodes, reSetNode);
            if (delHis==1){
                flowHisMapper.deleteByBusinessKey(businessKey);
            }
            if (nodeIndex<=reSetIndex){
                flowHisCreateReqVO.setNextNode(taskDO.getModelType());
                flowHisCreateReqVO.setModelType(null);
                flowHisCreateReqVO.setRemark("试验修改【流程不变】");

            }else {
                //重置
                flowTaskMapper.deleteByBusinessKey(businessKey);
                flowNodesMapper.deleteByBusinessKey(businessKey);

                //流程节点存储
                List<String> newNodes = getNodes();
                FlowNodesDO nodesDO = new FlowNodesDO();
                nodesDO.setBusinessKey(businessKey);
                nodesDO.setModelType(newNodes);
                //任务主表
                FlowTaskDO flowTask = new FlowTaskDO();
                flowTask.setBusinessKey(businessKey);
                flowTask.setTestCode(taskDO.getTestCode());
                Integer nodeIndex1 = getNodeIndex(newNodes, reSetNode);
                flowTask.setModelType(newNodes.get(nodeIndex1));
                flowTask.setNextNode(newNodes.get(nodeIndex1+1));

                //创建任务记录
                flowTaskMapper.insert(flowTask);
                flowNodesMapper.insert(nodesDO);
                //起始环节为老数据的当前环节
                flowHisCreateReqVO.setModelType(taskDO.getModelType());
                //下个环节设置为当前所在的环节-需求设计
                flowHisCreateReqVO.setNextNode(flowTask.getModelType());
                flowHisCreateReqVO.setRemark("试验修改【流程变更】");
                }
            flowHisService.createFlowHis(flowHisCreateReqVO);

        }
        }
}

4.任务流程记录

任务流程记录比较简单,只是简单的增删改查。就不具体列举了。
在这里插入图片描述

5.接口说明

主要接口功能说明,更多操作看service

1️⃣ 发起流程

/startFlow发起流程
参数 说明
businessKey 业务key
signee 处理人
startNode 流程起始节点,缺省 nodes[0]
testCode 项目编号(任务组)
2️⃣ 完成当前流程
/complete完成当前流程
参数 说明
businessKey 业务key
signee 处理人(可缺省)
startNode 流程起始节点,缺省 nodes[0]
variable变量
3️⃣任务驳回
/reject任务驳回
参数 说明
businessKey 业务key
variable变量
isInOrder是否按序流转
0 否 ,退回至某个节点>完成>流转至退回前节点 权重为1
1.是, 按序流转,配合 excludeModelType 为按序流转节点中需要剔除的流程节点
rejectModelType指定退回节点 缺省,驳回到上个节点,并按序流转
excludeModelType 剔除节点,与 isInOrder=1 时,搭配使用 缺省时按1处理
4️⃣任务代办列表
/page任务代办列表
参数 说明
businessKey 业务key
operate操作类型 0 驳回 1 通过
modelType流程节点
testCode任务组
5️⃣任务指派
/assignTask任务指派
参数 说明
businessKey 业务key
signee被指派人

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

3款免费次数多且功能又强大的国产AI绘画工具

hi&#xff0c;同学们&#xff0c;本期是我们第55 期 AI工具教程 最近两个月&#xff0c;国内很多AI绘画软件被关停&#xff0c;国外绝大部分AI绘画工具费用不低&#xff0c;因此 这两天我 重新整理 国产 AI绘画 工具 &#xff0c; 最终 筛选了 3款功能强大&#xf…

GoLand 2023.2.5(GO语言集成开发工具环境)

GoLand是一款专门为Go语言开发者打造的集成开发环境&#xff08;IDE&#xff09;。它能够提供一系列功能&#xff0c;如代码自动完成、语法高亮、代码格式化、代码重构、代码调试等等&#xff0c;使编写代码更加高效和舒适。 GoLand的特点包括&#xff1a; 1. 智能代码补全&a…

Java常量池理论篇:Class常量池、运行时常量池、String常量池、基本类型常量池,intern方法1.6、1.7的区别

文章目录 Class常量池运行时常量池String常量池基本类型常量池Integer 常量池Long 常量池 加餐部分 Class常量池 每个Class字节码文件中包含类常量池用来存放字面量以及符号引用等信息。 运行时常量池 java文件被编译成class文件之后&#xff0c;也就是会生成我上面所说的 …

WordPress用sql命令批量删除所有文章

有时我们需要将一个网站搬迁到另一个服务器。我们只想保留网站的模板样式&#xff0c;而不需要文章内容。一般情况下我们可以在后台删除已发表的文章&#xff0c;但如果有很多文章&#xff0c;我们则需要一次删除所有文章。 WordPress如何批量删除所有文章 进入网站空间后台&a…

webshell之基于框架免杀

thinkphp array_map_recursive函数 array_map_recursive函数分析 这里存在一个call_user_func命令执行函数 免杀效果 B函数 免杀效果 B函数分析 exec函数分析 在exec函数用存在有个类调用&#xff0c;且所有的参数都可控 smarty_php_tag函数 免杀效果 smarty_php_tag函数分析…

2023年项目管理工具排行榜:最佳项目管理工具推荐

如果你正在寻找一款项目管理软件&#xff0c;以下是你的最佳选择。让项目量化、可视化&#xff0c;资源合理分配、跟踪项目进度&#xff0c;帮助你的团队协作完成项目。 1、进度猫 进度猫是一款以甘特图为向导的轻量级在线免费项目进度管理工具。 基于甘特图、进度管理、任…

“AI就绪”新计划,亚马逊云科技到2025年向200万人提供免费AI技能培训

AI就绪&#xff08;AI Ready&#xff09;计划 到2025年为全球200万人提供 免费人工智能&#xff08;AI&#xff09;技能培训和教育资源 亚马逊云科技宣布启动“AI就绪&#xff08;AI Ready&#xff09;”计划&#xff0c;旨在到2025年为全球200万人提供免费人工智能&#xff08…

NPU、CPU、GPU算力及算力计算方式

NVIDIA在9月20日发布的NVIDIA DRIVE Thor 新一代集中式车载计算平台&#xff0c;可在单个安全、可靠的系统上运行高级驾驶员辅助应用和车载信息娱乐应用。提供 2000 万亿次浮点运算性能&#xff08;2000 万亿次8位浮点运算&#xff09;。NVIDIA当代产品是Orin&#xff0c;算力是…

最全的电商API接口|全面淘宝平台数据接口参数和文档说明

淘宝联盟“工具服务商”开放招募 为了支持生态淘宝客业务提效及新业务模式拓展&#xff0c;淘宝联盟针对各工具业务模式招募若干工具服务商团队&#xff0c;仅限符合该招募规则的开发者申请创建工具服务商AppKey&#xff0c;并针对新引入的工具服务商将开放对应模式所需要的“服…

SQL Server Profiler基础使用

文章目录 SQL Server Profiler基础使用简介如何打开直接打开Microsoft SQL Server Management Studio工具栏打开 配置跟踪新建跟踪跟踪属性配置常规配置事件选择 启动跟踪跟踪时执行脚本跟踪记录 暂停跟踪停止跟踪 SQL Server Profiler基础使用 简介 一个图形界面工具&#x…

亚马逊云科技向量数据库助力生成式AI成功落地实践探秘(二)

向量数据库选择哪种近似搜索算法&#xff0c;选择合适的集群规模以及集群设置调优对于知识库的读写性能也十分关键&#xff0c;主要需要考虑以下几个方面&#xff1a; 向量数据库算法选择 在 OpenSearch 里&#xff0c;提供了两种 k-NN 的算法&#xff1a;HNSW (Hierarchical…

ArkUI开发进阶—@Builder函数@BuilderParam装饰器的妙用与场景应用

ArkUI开发进阶—Builder函数BuilderParam装饰器的妙用与场景应用 HarmonyOS&#xff0c;作为一款全场景分布式操作系统&#xff0c;为了推动更广泛的应用开发&#xff0c;采用了一种先进而灵活的编程语言——ArkTS。ArkTS是在TypeScript&#xff08;TS&#xff09;的基础上发展…

Ubuntu18.4中安装wkhtmltopdf + Odoo16配置【二】

deepin Linux 安装wkhtmltopdf 1、先从官网的链接里下载linux对应的包 wkhtmltopdf/wkhtmltopdf 下载需要的版本&#xff0c;推荐版本&#xff0c;新测有效&#xff1a; wkhtmltox-0.12.4_linux-generic-amd64.tar.xz 2、解压下载的文件 解压后会有一个wkhtmltox文件夹 3…

多行业万能预约门店小程序源码系统 轻松预约 出行无忧 附带完整的搭建教程

大家好啊&#xff0c;罗峰来给大家分享好用的源码系统了。今天要给大家分享的是一款多行业万能预约门店小程序源码系统。在现实生活中&#xff0c;许多服务行业需要在线预约&#xff0c;如美发店、健身房、旅行社等。然而&#xff0c;对于商家来说&#xff0c;每个行业都有其独…

服务器系列之 成功解决 com.jcraft.jsch.JSchException: Auth fail

我 | 在这里 &#x1f575;️ 读书 | 长沙 ⭐软件工程 ⭐ 本科 &#x1f3e0; 工作 | 广州 ⭐ Java 全栈开发&#xff08;软件工程师&#xff09; &#x1f383; 爱好 | 研究技术、旅游、阅读、运动、喜欢流行歌曲 &#x1f3f7;️ 标签 | 男 自律狂人 目标明确 责任心强 ✈️公…

前置微小信号放大器在生物医学中有哪些应用

前置微小信号放大器在生物医学领域中具有广泛的应用。生物医学信号通常具有较小的振幅和较低的幅频响应&#xff0c;因此需要借助放大器来增强信号以便进行准确的测量、监测和分析。以下是前置微小信号放大器在生物医学中的主要应用。 心电图&#xff08;ECG&#xff09;放大器…

[Linux] 冯诺依曼体系结构 与 操作系统

文章目录 1、冯诺依曼体系结构2、操作系统 1、冯诺依曼体系结构 冯诺依曼结构也称普林斯顿结构&#xff0c;是一种将程序指令存储器和数据存储器合并在一起的存储器结构。程序指令存储地址和数据存储地址指向同一个存储器的不同物理位置&#xff0c;因此程序指令和数据的宽度相…

最好的猫罐头品牌有哪些?精选的5款口碑好的猫罐头推荐!

对于一个刚入门的养猫小白来说&#xff0c;面对市面上琳琅满目的猫罐头选择确实让人头大。我们总想选到营养价值高的罐头&#xff0c;但又怕猫咪不喜欢吃&#xff0c;也担心选到不安全的产品。 最好的猫罐头品牌有哪些&#xff1f;根据我开宠物店7年的经验&#xff0c;今天我将…

2016年2月9日 Go生态洞察:Go语言中的语言和地区匹配

&#x1f337;&#x1f341; 博主猫头虎&#xff08;&#x1f405;&#x1f43e;&#xff09;带您 Go to New World✨&#x1f341; &#x1f984; 博客首页——&#x1f405;&#x1f43e;猫头虎的博客&#x1f390; &#x1f433; 《面试题大全专栏》 &#x1f995; 文章图文…

2023算力专题研究:算力租赁算力国产化机会

今天分享的是算力系列深度研究报告&#xff1a;《2023算力专题研究&#xff1a;算力租赁&算力国产化机会》。 &#xff08;报告出品方&#xff1a;华泰证券&#xff09; 报告共计&#xff1a;18页 关注算力租赁&算力国产化机会 中美 AI 产业算力层存在差距&#xff…
最新文章