独辟蹊径:我是如何用Java自创一套工作流引擎的(下)

作者:后端小肥肠

创作不易,未经允许严禁转载。

姊妹篇:独辟蹊径:我是如何用Java自创一套工作流引擎的(上)_java工作流引擎-CSDN博客

1. 前言

在上一篇博客中,我们详细介绍了如何利用Java语言从零开始打造一套工作流引擎的基础架构。通过设计核心表结构和实现基础代码框架,我们建立了一个坚实的理论基础。今天,我们迈入《独辟蹊径:我是如何用Java自创一套工作流引擎的(下)》,将深入探讨这一引擎在实际项目中的应用和效果。

2. 项目场景模拟

本章我们将以实际项目场景来模拟自研工作流引擎的使用,分别是申请数据资源的流程和请假申请流程。

2.1. 申请数据资源流程

2.1.1. 技术流程

假设申请数据的审批级数为2级。申请数据资源的流程图如下:

在上述流程图中,从普通用户一级审批人员二级审批人员视角呈现了申请数据资源的整体流程;

1. 用户提交审批数据表单,填入申请人信息(姓名、电话),申请理由和需要申请的数资源;

2. 一级审批人员收到用户提交的申请后进行审批,如果驳回则整个流程结束,如果通过则进入下一审批环节;

3. 一级审批通过后二级审批人员可进行审批,如果驳回则整个流程结束,如何通过则开放数据下载链接,用户可根据链接下载申请的数据。

2.2. 请假申请流程

2.2.1. 技术流程

请假流程如下:

在上述流程中,从用户一级审批人员的角度呈现了整个请假流程:

1. 用户提交审批数据表单,填入申请人信息(姓名、电话)、申请理由、请假天数;

2. 一级审批人员收到用户提交的申请后进行审批,如果驳回则整个流程结束,同时通知用户流程未通过,如果通过则结束流程。

2.3. 技术实现

要在工作流中集成以上两套流程,需要基于一下几个步骤实现:

1. 设计流程定义,在business_approval_workflow新建数据审批流程和请假流程。

2. 设计流程细节,设计数据审批流程和请假流程的节点细节。

上图中,申请业务数据包含两个流程节点,第一个节点审批人为admin,第二个节点审批人为super;请假流程包含一个流程节点,审批人为admin。

3. 编写提交申请接口。

提交申请业务数据流程接口直接使用《独辟蹊径:我是如何用Java自创一套工作流引擎的(上)》中提交申请接口就行:

    public Boolean addRequest(RequestDTO requestDTO) {
        Request request= BeanCopyUtils.copyBean(requestDTO,Request.class);
        request.setStatus("1");//设置整个流程状态为正在审核
        // 1. 插入数据到 request 表
        baseMapper.insert(request);


        // 2. 根据 workflow_id 查询业务流程的节点信息,找到 serial_number 为 1 的节点,即流程开始时的第一个节点
        BusinessApprovalWorkflowDetail firstNode = workflowDetaiSlService.findFirstNodeByWorkflowId(request.getWorkflowId());
        //获取下一级节点 填充下级节点审批人
        BusinessApprovalWorkflowDetail nextNode=workflowDetaiSlService.getNextNodeByPreNode(firstNode);

        if (firstNode != null) {
            // 创建一个 approval_detail 记录示例,需要根据具体情况设置字段值
            ApprovalDetail approvalDetail = new ApprovalDetail();
            approvalDetail.setRequestId(request.getId()); // 假设设置关联的 request_id
            approvalDetail.setApproverUsername(firstNode.getNodeUsername()); // 设置首次节点的审批人用户名
            approvalDetail.setApprovalTime(new Date());
            approvalDetail.setNextApproverUsername(nextNode.getNodeUsername());//设置下游节点的审批人用户名
            approvalDetail.setStatus("1"); // 设置初始状态为待审批
            approvalDetail.setWorkflowId(request.getWorkflowId());
            approvalDetail.setNodeName(firstNode.getNodeName());
            approvalDetail.setNextNodeName(nextNode.getNodeName());

            // 插入数据到 approval_detail 表
            approvalDetailService.save(approvalDetail);
        } else {
            // 如果未找到对应的节点,根据实际需求进行错误处理或日志记录
            throw new RuntimeException("Unable to find the first node for workflow id: " + request.getWorkflowId());
        }
        return true;
    }

提交请假流程接口,在编写提交请假流程接口前,需要先明确请假申请表的表结构:

CREATE TABLE "public"."leave_request" (
  "id" varchar(32) COLLATE "pg_catalog"."default" NOT NULL,
  "workflow_id" varchar(32) COLLATE "pg_catalog"."default" NOT NULL,
  "purpose" varchar(900) COLLATE "pg_catalog"."default" NOT NULL,
  "leave_days" int2 NOT NULL,
  "applicant_name" varchar(50) COLLATE "pg_catalog"."default",
  "applicat_username" varchar(50) COLLATE "pg_catalog"."default",
  "applicant_phone" varchar(11) COLLATE "pg_catalog"."default",
  "version" int4 DEFAULT 1,
  "is_deleted" int4 DEFAULT 0,
  "create_time" timestamp(6) NOT NULL,
  "update_time" timestamp(6)
)
;

ALTER TABLE "public"."leave_request" 
  OWNER TO "postgres";

COMMENT ON COLUMN "public"."leave_request"."workflow_id" IS '业务流程id';

COMMENT ON COLUMN "public"."leave_request"."purpose" IS '请假理由';

COMMENT ON COLUMN "public"."leave_request"."leave_days" IS '请假天数';

COMMENT ON COLUMN "public"."leave_request"."applicant_name" IS '申请人姓名';

COMMENT ON COLUMN "public"."leave_request"."applicat_username" IS '申请人用户名';

COMMENT ON COLUMN "public"."leave_request"."applicant_phone" IS '申请人电话';

编写controller层:

    @GetMapping("")
    public Boolean addRequest(@Validated @RequestBody LeaveRequestDTO leaveRequestDTO){
      return leaveRequestService.addRequest(leaveRequestDTO);
    }

编写LeaveRequestDTO:

@Data
@AllArgsConstructor
@NoArgsConstructor
public class LeaveRequestDTO {
    private String workflowId;
    private String purpose;
    private Integer leaveDays;
    private String applicantName;
    private String applicantPhone;
    private String applicatUsername;
}

编写service层:

    public Boolean addRequest(LeaveRequestDTO leaveRequestDTO) {
        LeaveRequest leaveRequest= BeanCopyUtils.copyBean(leaveRequestDTO,LeaveRequest.class);
        // 1. 插入数据到 request 表
        baseMapper.insert(leaveRequest);
        // 2. 根据 workflow_id 查询业务流程的节点信息,找到 serial_number 为 1 的节点,即流程开始时的第一个节点
        BusinessApprovalWorkflowDetail firstNode = workflowDetaiSlService.findFirstNodeByWorkflowId(leaveRequest.getWorkflowId());
        //获取下一级节点 填充下级节点审批人
        BusinessApprovalWorkflowDetail nextNode=workflowDetaiSlService.getNextNodeByPreNode(firstNode);

        if (firstNode != null) {
            // 创建一个 approval_detail 记录示例,需要根据具体情况设置字段值
            ApprovalDetail approvalDetail = new ApprovalDetail();
            approvalDetail.setRequestId(leaveRequest.getId()); // 假设设置关联的 request_id
            approvalDetail.setApproverUsername(firstNode.getNodeUsername()); // 设置首次节点的审批人用户名
            approvalDetail.setApprovalTime(new Date());
            approvalDetail.setNextApproverUsername(nextNode.getNodeUsername());//设置下游节点的审批人用户名
            approvalDetail.setStatus("1"); // 设置初始状态为待审批
            approvalDetail.setWorkflowId(leaveRequest.getWorkflowId());
            approvalDetail.setNodeName(firstNode.getNodeName());
            approvalDetail.setNextNodeName(nextNode.getNodeName());

            // 插入数据到 approval_detail 表
            approvalDetailService.save(approvalDetail);
        } else {
            // 如果未找到对应的节点,根据实际需求进行错误处理或日志记录
            throw new RuntimeException("Unable to find the first node for workflow id: " + leaveRequest.getWorkflowId());
        }
        return true;
    }

上述代码实现了提交请假申请的功能:首先将从DTO转换后的请假请求数据插入数据库,然后根据流程ID查询流程的第一个节点信息,设置首次节点的审批人并插入到审批详情表中,状态设置为待审批。 

4. 基于策略模式优化审批接口。

审批申请方法在上篇中如下:

 @Transactional
    @Override
    public Boolean approvalApplication(ApprovalDTO approvalDTO) {
        // 这里我写死了,实际获取应该走权限框架获取当前在线用户 username
        String username = "xfc";
//        审批人姓名,从用户表中获取
        String name="小肥肠";
        //查询出当前任务节点
        ApprovalDetail approvalDetail = baseMapper.selectById(approvalDTO.getId());
        //获取当前审批的申请信息
        Request request = requestMapper.selectById(approvalDetail.getRequestId());
        if(request==null){
            throw new RuntimeException("申请id有误");
        }

        // 审批通过
        if (approvalDTO.getStatus().equals("2")) {
            // 根据 workflow_id 和 node_name 联查 business_approval_workflow_detail 表,获取当前流程是否为最后节点即 is_final=1
            BusinessApprovalWorkflowDetail currentWorkflowDetail = businessApprovalWorkflowDetailService.findByWorkflowIdAndNodeName(approvalDTO.getWorkflowId(), approvalDetail.getNodeName());
            if (currentWorkflowDetail != null && currentWorkflowDetail.getIsFinal().equals("1")) {
                // 如果是最后节点,则删除该条数据,填充 approval_history 表,根据 request 表修改 request 数据的 status 为 2
                baseMapper.deleteById(approvalDetail.getId()); // 删除当前审批记录
                // 更新 request 表中的状态为 2(通过)
                request.setStatus("2");
                requestMapper.updateById(request);

            } else {
                // 如果不是最后节点,则更新 business_approval_workflow_detail 为下一个节点审批信息
                BusinessApprovalWorkflowDetail nextNode = businessApprovalWorkflowDetailService.getNextNodeByPreNode(currentWorkflowDetail);
//                获取下一级节点的更下一级
                BusinessApprovalWorkflowDetail nextNextNode= businessApprovalWorkflowDetailService.getNextNodeByPreNode(nextNode);
                // 更新当前 approval_detail 表中的审批人和下一个审批人信息
                approvalDetail.setApproverUsername(nextNode.getNodeUsername());
                approvalDetail.setNodeName(nextNextNode.getNodeName());
                approvalDetail.setNextApproverUsername(nextNextNode!=null?nextNextNode.getNodeUsername():"");
                approvalDetail.setNextNodeName(nextNextNode!=null?nextNextNode.getNodeName():"");
                approvalDetail.setApprovalTime(new Date());
                approvalDetail.setStatus("1"); // 设置为待审批状态
                baseMapper.updateById(approvalDetail);

            }
            // 填充 approval_history 表
            ApprovalHistory approvalHistory = new ApprovalHistory();
            approvalHistory.setRequestId(request.getId());
            approvalHistory.setApproverName(name); // 设置审批人姓名,或者从用户表中获取
            approvalHistory.setApprovalTime(new Date());
            approvalHistory.setStatus("2"); // 通过
            approvalHistory.setRemark(approvalDTO.getRemark());
            approvalHistory.setWorkflowId(approvalDTO.getWorkflowId());
            approvalHistory.setApplicantPhone(request.getApplicantPhone());
            approvalHistory.setPurpose(request.getPurpose());
            approvalHistory.setApplicantName(request.getApplicantName());
            approvalHistory.setApproverUsername(username); // 设置审批人用户名,或者从用户表中获取
            approvalHistoryMapper.insert(approvalHistory); // 插入审批历史记录
        } else if (approvalDTO.getStatus().equals("3")) {
            // 审批驳回
            baseMapper.deleteById(approvalDetail.getId()); // 删除当前审批记录

            // 填充 approval_history 表
            ApprovalHistory approvalHistory = new ApprovalHistory();
            approvalHistory.setRequestId(request.getId());
            approvalHistory.setApproverName(name); // 设置审批人姓名,或者从用户表中获取
            approvalHistory.setApprovalTime(new Date());
            approvalHistory.setStatus("3"); // 驳回
            approvalHistory.setRemark(approvalDTO.getRemark());
            approvalHistory.setWorkflowId(approvalDTO.getWorkflowId());
            approvalHistory.setApplicantPhone(request.getApplicantPhone());
            approvalHistory.setPurpose(request.getPurpose());
            approvalHistory.setApplicantName(request.getApplicantName());
            approvalHistory.setApproverUsername(username); // 设置审批人用户名,或者从用户表中获取
            approvalHistoryMapper.insert(approvalHistory); // 插入审批历史记录

            // 更新 request 表中的状态为 3(驳回)
                request.setStatus("3");
            requestMapper.updateById(request);

        }
        return true; // 或者根据实际需求返回其他业务逻辑
    }

上述代码其实是针对申请数据资源流程的审批操作,那如果要申请别的流程该怎么做呢?常规操作应该是在上述代码中增加if-else判断操作,根据不通业务进行士审批操作,但是随着业务流程增加,就会新增许多if-else操作,代码会十分雍总,代码可读性较差,可以通过引入策略工厂来解决上述问题。步骤如下:

1.新增审批策略工厂

@Service
public class ApprovalFactory {
    @Autowired
    ApprovalDataRequestService approvalDataRequestService;
    @Autowired
    ApprovalLeaveRequestService approvalLeaveRequestService;
    private static Map<String, Function<ApprovalDTO,Boolean>> approvalMap = null;
    @PostConstruct
    public void init(){
        approvalMap=new HashMap<>();
        approvalMap.put("2",approvalDTO->approvalDataRequestService.approvalApplication(approvalDTO));
        approvalMap.put("1",approvalDTO ->approvalLeaveRequestService.approvalApplication(approvalDTO));
    }

    public Boolean approvalApplication(ApprovalDTO approvalDTO) {
       return approvalMap.get(approvalDTO.getWorkflowId()).apply(approvalDTO);
    }
}

上述代码为针对审批操作的策略工厂,在类初始化过程中(使用 @PostConstruct 注解的 init() 方法),通过静态的 approvalMap 对象将审批动作和对应的处理函数关联起来。具体来说:

  • workflowId 为 "2" 时,映射到 approvalDataRequestServiceapprovalApplication 方法处理数据请求的审批逻辑。
  • workflowId 为 "1" 时,映射到 approvalLeaveRequestServiceapprovalApplication 方法处理请假请求的审批逻辑。

最后,approvalApplication(ApprovalDTO approvalDTO) 方法根据传入的 approvalDTO 中的 workflowIdapprovalMap 中获取相应的处理函数,并执行该函数来完成审批操作,返回处理结果,controller层直接调用策略工厂即可:

    @PostMapping("/approval")
    public Boolean approvalApplication(@Validated @RequestBody ApprovalDTO approvalDTO) {
        return approvalFactory.approvalApplication(approvalDTO);
    }

2. 新增数据资源审批类

@Service
@Slf4j
public class ApprovalDataRequestService {
    @Autowired
    IBusinessApprovalWorkflowDetailService businessApprovalWorkflowDetailService;
    @Autowired
    RequestMapper requestMapper;
    @Autowired
    ApprovalHistoryMapper approvalHistoryMapper;
    @Autowired
    ApprovalDetailMapper approvalDetailMapper;

    @Transactional
    public Boolean approvalApplication(ApprovalDTO approvalDTO) {
        // 这里我写死了,实际获取应该走权限框架获取当前在线用户 username
        String username = "xfc";
//        审批人姓名,从用户表中获取
        String name="小肥肠";
        //查询出当前任务节点
        ApprovalDetail approvalDetail = approvalDetailMapper.selectById(approvalDTO.getId());
        //获取当前审批的申请信息
        Request request = requestMapper.selectById(approvalDetail.getRequestId());
        if(request==null){
            throw new RuntimeException("申请id有误");
        }

        // 审批通过
        if (approvalDTO.getStatus().equals("2")) {
            // 根据 workflow_id 和 node_name 联查 business_approval_workflow_detail 表,获取当前流程是否为最后节点即 is_final=1
            BusinessApprovalWorkflowDetail currentWorkflowDetail = businessApprovalWorkflowDetailService.findByWorkflowIdAndNodeName(approvalDTO.getWorkflowId(), approvalDetail.getNodeName());
            if (currentWorkflowDetail != null && currentWorkflowDetail.getIsFinal().equals("1")) {
                // 如果是最后节点,则删除该条数据,填充 approval_history 表,根据 request 表修改 request 数据的 status 为 2
                approvalDetailMapper.deleteById(approvalDetail.getId()); // 删除当前审批记录
                // 更新 request 表中的状态为 2(通过)
                request.setStatus("2");
                requestMapper.updateById(request);

            } else {
                // 如果不是最后节点,则更新 business_approval_workflow_detail 为下一个节点审批信息
                BusinessApprovalWorkflowDetail nextNode = businessApprovalWorkflowDetailService.getNextNodeByPreNode(currentWorkflowDetail);
//                获取下一级节点的更下一级
                BusinessApprovalWorkflowDetail nextNextNode= businessApprovalWorkflowDetailService.getNextNodeByPreNode(nextNode);
                // 更新当前 approval_detail 表中的审批人和下一个审批人信息
                approvalDetail.setApproverUsername(nextNode.getNodeUsername());
                approvalDetail.setNodeName(nextNextNode.getNodeName());
                approvalDetail.setNextApproverUsername(nextNextNode!=null?nextNextNode.getNodeUsername():"");
                approvalDetail.setNextNodeName(nextNextNode!=null?nextNextNode.getNodeName():"");
                approvalDetail.setApprovalTime(new Date());
                approvalDetail.setStatus("1"); // 设置为待审批状态
                approvalDetailMapper.updateById(approvalDetail);

            }
            // 填充 approval_history 表
            ApprovalHistory approvalHistory = new ApprovalHistory();
            approvalHistory.setRequestId(request.getId());
            approvalHistory.setApproverName(name); // 设置审批人姓名,或者从用户表中获取
            approvalHistory.setApprovalTime(new Date());
            approvalHistory.setStatus("2"); // 通过
            approvalHistory.setRemark(approvalDTO.getRemark());
            approvalHistory.setWorkflowId(approvalDTO.getWorkflowId());
            approvalHistory.setApplicantPhone(request.getApplicantPhone());
            approvalHistory.setPurpose(request.getPurpose());
            approvalHistory.setApplicantName(request.getApplicantName());
            approvalHistory.setApproverUsername(username); // 设置审批人用户名,或者从用户表中获取
            approvalHistoryMapper.insert(approvalHistory); // 插入审批历史记录
        } else if (approvalDTO.getStatus().equals("3")) {
            // 审批驳回
            approvalDetailMapper.deleteById(approvalDetail.getId()); // 删除当前审批记录

            // 填充 approval_history 表
            ApprovalHistory approvalHistory = new ApprovalHistory();
            approvalHistory.setRequestId(request.getId());
            approvalHistory.setApproverName(name); // 设置审批人姓名,或者从用户表中获取
            approvalHistory.setApprovalTime(new Date());
            approvalHistory.setStatus("3"); // 驳回
            approvalHistory.setRemark(approvalDTO.getRemark());
            approvalHistory.setWorkflowId(approvalDTO.getWorkflowId());
            approvalHistory.setApplicantPhone(request.getApplicantPhone());
            approvalHistory.setPurpose(request.getPurpose());
            approvalHistory.setApplicantName(request.getApplicantName());
            approvalHistory.setApproverUsername(username); // 设置审批人用户名,或者从用户表中获取
            approvalHistoryMapper.insert(approvalHistory); // 插入审批历史记录

            // 更新 request 表中的状态为 3(驳回)
            request.setStatus("3");
            requestMapper.updateById(request);

        }
        return true; // 或者根据实际需求返回其他业务逻辑
    }


}

3.新增请假审批类

@Service
public class ApprovalLeaveRequestService {
    public Boolean approvalApplication(ApprovalDTO approvalDTO) {
        /**
         * 一样的逻辑,把对request表的操作改为leave_request
         */
        return true;
    }
}

3. 结语

在本文中,针对《独辟蹊径:我是如何用Java自创一套工作流引擎的(上)》中的工作流基础代码进行了结合实际项目的扩展,本工作流引擎适用于任何相对简单的审批场景,有新的业务流程仅需针对申请表单和审批逻辑进行接口新增和策略工厂扩展即可,如本文对你有帮助请一键三连哦~

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

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

相关文章

Zookeeper 四、Zookeeper应用场景

Zookeeper是一个典型的发布/订阅模式的分布式数据管理与协调框架&#xff0c;我们可以使用它来进行分布式数据的发布与订阅。另一方面&#xff0c;通过对Zookeeper中丰富的数据节点类型进行交叉使用&#xff0c;配合Watcher事件通知机制&#xff0c;可以非常方便地构建一系列分…

C++:typeid4种cast转换

typeid typeid typeid是C标准库中提供的一种运算符&#xff0c;它用于获取类型的信息。它主要用于类型检查和动态类型识别。当你对一个变量或对象使用typeid运算符时&#xff0c;它会返回一个指向std::type_info类型的指针&#xff0c;这个信息包含了关于该类型名称、大小、基…

利用 Docker 简化 Nacos 部署:快速搭建 Nacos 服务

利用 Docker 简化 Nacos 部署&#xff1a;快速搭建 Nacos 服务 引言 在微服务架构中&#xff0c;服务注册与发现是确保服务间通信顺畅的关键组件。Nacos&#xff08;Dynamic Naming and Configuration Service&#xff09;作为阿里巴巴开源的一个服务发现和配置管理平台&…

Gin框架基础

1、一个简单的Gin示例 下载并安装Gin: go get -u github.com/gin-gonic/gin1.1 一个简单的例子 package mainimport ("net/http""github.com/gin-gonic/gin" )func main() {// 创建一个默认的路由引擎r : gin.Default()// 当客户端以GET方式访问 /hello…

昇思25天学习打卡营第10天 | 基于MindNLP+MusicGen生成自己的个性化音乐

基于MindNLPMusicGen生成自己的个性化音乐 MusicGen是来自Meta AI的Jade Copet等人提出的基于单个语言模型&#xff08;LM&#xff09;的音乐生成模型&#xff0c;能够根据文本描述或音频提示生成高质量的音乐样本&#xff0c;相关研究成果参考论文《Simple and Controllable …

C程序设计谭浩强第五版

程序习题 第一章1、第5题2、第6题 第三章1、第2题2、第2题3、第3题4、第4题Tips 第一章 1、第5题 编写一个C程序,运行时输出以下图形: #include <stdio.h> int main() {for (int i 0; i < 4; i) // 输出4行循环控制{for (int j 0; j < i; j) //第几行就输出几…

jenkins中执行docker命令

1. 修改docker.sock文件的所属组 命令如下&#xff1a; sudo chown root:root docker.sock 2. 对这个文件赋予权限&#xff0c;供其他用户使用&#xff0c;给定权限命令如下&#xff1a; sudo chmod orw docker.sock 3. docker容器映射 这里需要两个文件&#xff1a; 一个…

Selenium时间控件的处理

我们经常在做web自动化测试过程中会遇到时间控件&#xff0c;那么对于时间控件如何处理&#xff0c;我们可以来分析下。 对于时间控件一般分为两种&#xff1a; 1、普通的时间控件 直接通过send_keys就可以解决 d.get("https://www.ctrip.com/?sid155952&alliancei…

华三(H3C)交换机堆叠配置

目录 一、相关理论 二、实验需求 三、实验组网 四、具体配置 4.1 堆叠配置 4.2 查看堆叠相关配置 4.3 MAD 检测配置 一、相关理论 H3C的堆叠称为IRF&#xff08;Intelligent Resilient Framework&#xff0c;智能弹性架构&#xff09; IRF中每台设备都称为成员设备。成…

架构师篇-7、企业安全架构设计及实践

摘要&#xff1a; 认识企业安全架构企业安全案例分析及实践 内容&#xff1a; 为什么做企业安全架构怎么做好安全架构设计案例实践分析&随堂练 为什么要做企业安全架构 安全是麻烦制造者&#xff1f; 整天提安全需求增加开发工作增加运维要求增加不确定性延后业务上线…

Datawhale机器学习day-1

赛题 在当今科技日新月异的时代&#xff0c;人工智能&#xff08;AI&#xff09;技术正以前所未有的深度和广度渗透到科研领域&#xff0c;特别是在化学及药物研发中展现出了巨大潜力。精准预测分子性质有助于高效筛选出具有优异性能的候选药物。以PROTACs为例&#xff0c;它是…

理想汽车提出3DRealCar:首个大规模3D真实汽车数据集

理想提出3DRealCar&#xff0c;这是第一个大规模 3D 实车数据集&#xff0c;包含 2500 辆在真实场景中拍摄的汽车。我们希望 3DRealCar 可以成为促进汽车相关任务的宝贵资源。 理想汽车提出3DRealCar&#xff1a;首个大规模3D真实汽车数据集! 我们精心策划的高质量3DRealCar数…

基于公有云部署wordpress

云平台选择 腾讯云 阿里云 华为云 项目部署 一、架构讲解 1.1、定义与组成 LNMP是Linux、Nginx、MySQL&#xff08;或MariaDB&#xff09;和PHP&#xff08;或Perl、Python&#xff09;的首字母缩写&#xff0c;代表在Linux系统下使用Nginx作为Web服务器&#xff0c;MySQL作为…

【SGX系列教程】(八)Intel-SGX 官方示例分析(SampleCode)——Seal Unseal

文章目录 一.Seal Unseal原理介绍1.1 Intel SGX supported Sealing Policies 二.源码分析2.1 README2.2 重点代码分析2.2.1 主要代码模块交互流程分析2.2.2 App/App.cpp2.2.3 Enclave_Seal/Enclave_Seal.cpp2.2.4 Enclave_Unseal/Enclave_Unseal.cpp 2.3 总结 三.参考文献四.感…

PMBOK® 第六版 结束项目或阶段

目录 读后感—PMBOK第六版 目录 不论是阶段的收尾还是项目整体的收尾&#xff0c;都应是令人振奋的事。然而&#xff0c;在实际生活中&#xff0c;收尾工作却相当艰难。会遭遇负责人调离、换任&#xff0c;导致不再需要已购产品&#xff1b;项目收尾时对照招标文件或合同&…

基于python的房价多元线性回归分析

1.导入必要的库 import pandas as pd import numpy as np import statsmodels.api as sm from sklearn.model_selection import train_test_split from sklearn.metrics import r2_score import matplotlib.pyplot as plt # 忽略Matplotlib的警告&#xff08;可选&…

SpringBoot实现文章点赞功能

提示&#xff1a;今日是2024年的6月30日&#xff0c;未来的你看到这篇文章&#xff0c;希望你依旧快乐 文章目录 前言 首先在这里前缀部分我就不做要求了,比如说登录信息什么的 数据库表格 这里实现点赞功能&#xff0c;主要是围绕论坛项目完成的 user_info代表用户信息表 for…

20240630每日一题-组合数学-平均分组问题

更多资源请关注纽扣编程微信公众号 将6个小球&#xff0c;其中1个红球&#xff0c;2个黑球&#xff0c;3个白球拍成一列&#xff0c;相同颜色的球没区别&#xff0c;那么有多少种排法&#xff1f; 答案 60种 分析 相同颜色的小球可以看作平均分组&#xff0c;去除对应排序的…

Zookeeper:Zookeeper JavaAPI操作与分布式锁

文章目录 一、Zookeeper JavaAPI操作1、Curator介绍2、创建、查询、修改、删除节点3、Watch事件监听 二、Zookeeper分布式锁原理 一、Zookeeper JavaAPI操作 1、Curator介绍 Curator是Apache Zookeeper的Java客户端。常见的Zookeeper Java API&#xff1a; 原生Java API。ZkC…

基于PHP的酒店管理系统(改进版)

有需要请加文章底部Q哦 可远程调试 基于PHP的酒店管理系统(改进版) 一 介绍 此酒店管理系统(改进版)基于原生PHP开发&#xff0c;数据库mysql&#xff0c;前端jquery插件美化。系统角色分为用户和管理员。系统在原有基础上增加了注册登录注销功能&#xff0c;增加预订房间图片…