一个完整的流程表单流转

news2024/11/18 23:27:20

1.写在前面

一个完整的流程表单审批(起表单-->各环节审批-->回退-->重新审批-->完成),前端由Vue2+js+Element UI升级为Vue3+ts+Element Plus,后端流程框架使用Flowable,项目参考了ruoyi-vue-pro(https://gitee.com/zhijiantianya/ruoyi-vue-pro)项目。

2.视频演示

3.表单

3.1表单的设计

依据业务需求,完全自定义表单,可以依据流程节点设置表单中每个属性的读写,实现原理是读取审批节点的编码,依据编码控制每个属性的读写。

<el-tree-select
     :disabled="!required"
     v-model="form.deptCode"
     :data="deptList"
     :props="defaultPropsForData"
      check-strictly
      node-key="id"
      placeholder="请选择集团成员单位"
 />


/**
 * 根据不同参数控制表单属性的可读可写
 * @param pattern
 */
const changeRequiredByPattern = (pattern: string) => {
  if (pattern === 'create') {
    //说明增加
    required.value = true
  } else if (pattern === 'update' || pattern === 'starter') {
    //修改
    required.value = true
    getBid()
  } else {
    //只读
    required.value = false
    getBid()
  }
}

3.2.表单与流程关联

通过设计流程时定义的流程编码,在创建表单的后端服务调用中,实现表单与流程的绑定。

/**
     * 投标对应的流程定义 KEY
     */
    public static final String PROCESS_KEY = "bidApproval";


//发起BPM流程
        Map<String, Object> processInstanceVariables = new HashMap<>();
        processInstanceVariables.put("deptId", bidDO.getDeptId());
        processInstanceVariables.put("bidMoney", bidDO.getBidMoney());
        String processInstanceId = processInstanceApi.createProcessInstance(getLoginUserId(),
                new BpmProcessInstanceCreateReqDTO().setProcessDefinitionKey(PROCESS_KEY)
                        .setVariables(processInstanceVariables).setBusinessKey(bidDO.getProjectName()));

3.3.表单与前端流程实例的关联

在通过待办打开需要审批的表单时,不同的流程实例如何对应不同的表单,并在页面展示具体的表单数据了?答案是通过定义流程时填写的表单组件名称,利用Vue的<component>元组件来实现。

<component
          ref="formDetailRef"
          v-if="processInstance.id !== undefined"
          :is="processInstance.processDefinition.formComponentName"
          :processInstanceId="processInstance.id"
          :pattern="runningTasks.length > 0 ? runningTasks[0].definitionKey : 'readOnly'"
          @success="getDetail"
        />

3.4.表单的保存

表单的保存分为提交时的保存与不提交的保存。不提交的保存用于修改数据但流程不需要提交到下一节点审批的情况,方便保存数据进入待办里面进行后续的修改。提交的时,流程会自动调用保存接口,先进行业务数据的保存,然后再进行流程的提交。

<el-button color="#626aef" @click="handleSave">
                <Icon icon="ep:coin" />
                保存
              </el-button>
              <el-button type="success" @click="handleApproval(item)">
                <Icon icon="ep:select" />
                提交
              </el-button>

/** 处理保存表单的操作 只更新表单数据 不提交流程任务 */
const formDetailRef = ref()
const handleSave = () => {
  formDetailRef.value.submitForm()
}

/** 处理审批通过的操作 */
const approvalRef = ref()
const handleApproval = async (item) => {
  approvalRef.value.open(item)
}
@Override
    @Transactional(rollbackFor = Exception.class)
    public Long updateBid(BidUpdateReqVO updateReqVO) {
        BidDO bidDO = BidConvert.INSTANCE.convert3(updateReqVO);
        //checkDeptIsMateTendererDept(deptRespDTO, tendererDeptRespDTO);
        bidDO.setDeptId(findDeptByCode(updateReqVO.getDeptCode()).getId());
        bidDO.setTendererId(findDeptByName(updateReqVO.getTendererName()).getId());
        fileApi.deleteFile(bidMapper.selectById(bidDO.getId()).getFiles(), bidDO.getFiles());
        bidMapper.updateById(bidDO);
        return bidDO.getId();
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void approveProcessTask(BidUpdateReqVO bidUpdateReqVO, BpmProcessTaskApprovalDTO bpmProcessTaskApprovalDTO) {
        //需要根据流程中不同的节点 更改对应的表单信息 比如 需要在流程最后一个节点点击提交时 更改流程状态为完成
        BidDO bidDO = BidConvert.INSTANCE.convert3(bidUpdateReqVO);
        bidDO.setDeptId(findDeptByCode(bidUpdateReqVO.getDeptCode()).getId());
        bidDO.setTendererId(findDeptByName(bidUpdateReqVO.getTendererName()).getId());
        updateFlowInfoByProcessInstanceState(bidDO);
        fileApi.deleteFile(bidMapper.selectById(bidDO.getId()).getFiles(), bidDO.getFiles());
        bidMapper.updateById(bidDO);
        bpmProcessTaskApi.approvalTask(bpmProcessTaskApprovalDTO);
    }

4.流程的审批

流程的审批按照设计流程的审批节点依次进行流转,不支持夸环节提交,支持流程的自由回退。提交当前审批任务时,进行下一节点的人员选择,在流程设计时,每个节点审批的人员的选择逻辑已经确定,也支持自由选择组织中的所有人员。

4.1.流程审批人员的设置

流程审批节点的人员设置主要思路为给定一个角色,让审批人员提交任务时,从角色中选择一个人员,这样可以缩小选择的范围。如果表单有对应的部门属性,可以设置审批人员是某个角色中且部门与表单部门属性相同的人员。

// 选择角色中的人员
private Set<Long> calculateTaskCandidateUsersByRole(BpmTaskAssignRuleDO rule) {
        return permissionApi.getUserRoleIdListByRoleIds(rule.getOptions());
    }

//依据角色选择表单部门中的所属人员
private Set<Long> calculateTaskCandidateUsersByRolePerson(Map<String, Object> variables, BpmTaskAssignRuleDO rule) {
        Long deptId = (Long) variables.get("deptId");
        //获取流程实例变量的部门
        //从角色中获取属于该部门的人员
        Set<Long> userIdsByRoleId = permissionApi.getUserRoleIdListByRoleIds(rule.getOptions());
        Set<Long> userIdsByDeptId = userApi.getUsersByDeptId(deptId);
        return new HashSet<>(CollUtil.intersection(userIdsByDeptId, userIdsByRoleId));
    }

4.2.流程的流转

流程的流转其实没有什么好说的,就是按照流程设计的审批节点依次往下走,遇到网关时,根据前期设计好的条件读取对应的属性跳转到不同审批支线。本例中,会根据投标金额是否大于500万做判断,走不同的分支,而500万的属性,在创建流程时就已经传入了流程实例的变量中。

流程的回退,流程的回退依据流程的节点图,不管流程流转了多少圈,回退只允许回退当前审批节点的前面节点。

public Set<BpmDoneUserTaskNodeRespVO> getDoneUserTaskNodes(String taskId) {
        Set<BpmDoneUserTaskNodeRespVO> resultList = new HashSet<>();
        //获取流程实例id
        Task task = getTask(taskId);
        // 校验流程实例存在
        ProcessInstance instance = processInstanceService.getProcessInstance(task.getProcessInstanceId());
        //获取历史任务实例 条件为 流程实例 未完成 按照任务开始时间降序排列
        List<HistoricTaskInstance> historicTaskInstances = historyService.createHistoricTaskInstanceQuery()
                .processInstanceId(instance.getProcessInstanceId())
                .finished()
                .orderByHistoricTaskInstanceEndTime()
                .desc()
                .list();
        //需要做一个筛选,只能选择当前任务节点之前的节点进行回退
        // 1. 获取流程模型实例 BpmnModel
        BpmnModel bpmnModel = bpmProcessDefinitionService.getBpmnModel(task.getProcessDefinitionId());
        // 2. 通过任务节点id,来获取当前节点信息
        FlowElement flowElement = bpmnModel.getFlowElement(task.getTaskDefinitionKey());
        // 3.获取下一个节点(或者多个节点的)信息,需要去重,因为并行的节点之前的节点会找多遍
        Set<FlowElement> flowElements = new HashSet<>();
        // 4.获取流程实例的变量
        Map<String, Object> variables = taskService.getVariables(taskId);
        getBeforeNodes(flowElement, flowElements, variables);
        if (flowElements.isEmpty()){
            //说明处于第一个节点,此时不能回退
            throw exception(TASK_ROLLBACK_FORBIDDEN);
        }
        //找交集
        for(FlowElement f: flowElements) {
            HistoricTaskInstance h = historicTaskInstances.stream()
                    .filter( hi -> hi.getTaskDefinitionKey().equals(f.getId()))
                    .findFirst().orElse(null);
            if (h == null){
                //没有获取到最新的的节点审批信息,是不正常的情况
                throw exception(TASK_ROLLBACK_APPROVED_INFO_NULL);
            }
            //获取 审批人员的编号
            Long assignee = Long.valueOf(h.getAssignee());
            //获取人员信息
            UserRespDTO userRespDTO = adminUserApi.getUser(assignee);
            //获取部门信息
            DeptRespDTO deptRespDTO = deptApi.getDept(userRespDTO.getDeptId());

            resultList.add(BpmTaskConvert.INSTANCE.convertBpmDoneUserTaskNodeRespVO(h, userRespDTO, deptRespDTO));
        }
        return resultList;
    }

5.流程审批详情

流程审批详情包括审批记录与流程图的展示。

5.1.审批记录详情

审批记录根据审批的先后顺序展示数据,状态根据提交回退的不同使用不同颜色的标签显示。

<el-table v-loading="loading" :data="tasks" border>
          <el-table-column align="center" prop="name" label="审批环节" width="200" />
          <el-table-column align="center" prop="assigneeUser.nickname" label="审批人" width="180" />
          <el-table-column
            label="任务开始时间"
            align="center"
            prop="createTime"
            width="180"
            :formatter="dateFormatter"
          />
          <el-table-column
            label="任务结束时间"
            align="center"
            prop="endTime"
            width="180"
            :formatter="dateFormatter"
          />
          <el-table-column label="任务耗时" align="center" prop="durationInMillis" width="150">
            <template #default="scope">
              <span>{{ formatPast2(scope.row.durationInMillis) }}</span>
            </template>
          </el-table-column>
          <el-table-column label="审批结果" align="center" prop="result" width="150">
            <template #default="scope">
              <el-tag :type="getTimelineItemType(scope.row)"
                >{{ getDictLabel(DICT_TYPE.BPM_PROCESS_INSTANCE_RESULT, scope.row.result) }}
              </el-tag>
            </template>
          </el-table-column>
          <el-table-column label="审批意见" align="center" prop="reason" width="250" />
        </el-table>
public List<BpmTaskRespVO> getTaskListByProcessInstanceId(String processInstanceId) {
        // 获得任务列表
        List<HistoricTaskInstance> tasks = historyService.createHistoricTaskInstanceQuery()
                .processInstanceId(processInstanceId)
                .orderByHistoricTaskInstanceStartTime().desc() // 创建时间倒序
                .list();
        if (CollUtil.isEmpty(tasks)) {
            return Collections.emptyList();
        }

        // 获得 TaskExtDO Map
        List<BpmTaskExtDO> bpmTaskExtDOs = taskExtMapper.selectListByTaskIds(convertSet(tasks, HistoricTaskInstance::getId));
        Map<String, BpmTaskExtDO> bpmTaskExtDOMap = convertMap(bpmTaskExtDOs, BpmTaskExtDO::getTaskId);
        // 获得 ProcessInstance Map
        HistoricProcessInstance processInstance = processInstanceService.getHistoricProcessInstance(processInstanceId);
        // 获得 User Map
        Set<Long> userIds = convertSet(tasks, task -> NumberUtils.parseLong(task.getAssignee()));
        userIds.add(NumberUtils.parseLong(processInstance.getStartUserId()));
        Map<Long, UserRespDTO> userMap = adminUserApi.getUserMap(userIds);
        // 获得 Dept Map
        Map<Long, DeptRespDTO> deptMap = deptApi.getDeptMap(convertSet(userMap.values(), UserRespDTO::getDeptId));

        // 拼接数据
        return BpmTaskConvert.INSTANCE.convertList3(tasks, bpmTaskExtDOMap, processInstance, userMap, deptMap);
    }

5.2.流程图显示

流程图显示比较复杂,可以查看对应的代码,主要就是使用了bpmn-js库,根据后端的数据,进行不同的展示,核心代码个人理解是这一块。

const highlightDiagram = async () => {
  const activityList = activityLists.value
  if (activityList.length === 0) {
    return
  }
  // 参考自 https://gitee.com/tony2y/RuoYi-flowable/blob/master/ruoyi-ui/src/components/Process/index.vue#L222 实现
  // 再次基础上,增加不同审批结果的颜色等等
  let canvas = bpmnModeler.get('canvas')
  let todoActivity: any = activityList.find((m: any) => !m.endTime) // 找到待办的任务
  let endActivity: any = activityList[activityList.length - 1] // 获得最后一个任务
  // debugger
  bpmnModeler.getDefinitions().rootElements[0].flowElements?.forEach((n: any) => {
    let activity: any = activityList.find((m: any) => m.key === n.id) // 找到对应的活动
    if (!activity) {
      return
    }
    if (n.$type === 'bpmn:UserTask') {
      // 用户任务
      // 处理用户任务的高亮
      const task: any = taskList.value.find((m: any) => m.id === activity.taskId) // 找到活动对应的 taskId
      if (!task) {
        return
      }
      // 高亮任务
      canvas.addMarker(n.id, getResultCss(task.result))

      // 如果非通过,就不走后面的线条了
      if (task.result !== 2) {
        return
      }
      // 处理 outgoing 出线
      const outgoing = getActivityOutgoing(activity)
      outgoing?.forEach((nn: any) => {
        // debugger
        let targetActivity: any = activityList.find((m: any) => m.key === nn.targetRef.id)
        // 如果目标活动存在,则根据该活动是否结束,进行【bpmn:SequenceFlow】连线的高亮设置
        if (targetActivity) {
          canvas.addMarker(nn.id, targetActivity.endTime ? 'highlight' : 'highlight-todo')
        } else if (nn.targetRef.$type === 'bpmn:ExclusiveGateway') {
          // TODO 芋艿:这个流程,暂时没走到过
          canvas.addMarker(nn.id, activity.endTime ? 'highlight' : 'highlight-todo')
          canvas.addMarker(nn.targetRef.id, activity.endTime ? 'highlight' : 'highlight-todo')
        } else if (nn.targetRef.$type === 'bpmn:EndEvent') {
          // TODO 芋艿:这个流程,暂时没走到过
          if (!todoActivity && endActivity.key === n.id) {
            canvas.addMarker(nn.id, 'highlight')
            canvas.addMarker(nn.targetRef.id, 'highlight')
          }
          if (!activity.endTime) {
            canvas.addMarker(nn.id, 'highlight-todo')
            canvas.addMarker(nn.targetRef.id, 'highlight-todo')
          }
        }
      })
    } else if (n.$type === 'bpmn:ExclusiveGateway') {
      // 排它网关
      // 设置【bpmn:ExclusiveGateway】排它网关的高亮
      canvas.addMarker(n.id, getActivityHighlightCss(activity))
      // 查找需要高亮的连线
      let matchNN: any = undefined
      let matchActivity: any = undefined
      const outgoing = getActivityOutgoing(activity)
      outgoing.forEach((nn: any) => {
        let targetActivity = activityList.find((m: any) => m.key === nn.id)
        if (!targetActivity) {
          return
        }
        // 特殊判断 endEvent 类型的原因,ExclusiveGateway 可能后续连有 2 个路径:
        //  1. 一个是 UserTask => EndEvent
        //  2. 一个是 EndEvent
        // 在选择路径 1 时,其实 EndEvent 可能也存在,导致 1 和 2 都高亮,显然是不正确的。
        // 所以,在 matchActivity 为 EndEvent 时,需要进行覆盖~~
        if (!matchActivity || matchActivity.type === 'endEvent') {
          matchNN = nn
          matchActivity = targetActivity
        }
      })
      if (matchNN && matchActivity) {
        canvas.addMarker(matchNN.id, getActivityHighlightCss(matchActivity))
      }
    } else if (n.$type === 'bpmn:ParallelGateway') {
      // 并行网关
      // 设置【bpmn:ParallelGateway】并行网关的高亮
      canvas.addMarker(n.id, getActivityHighlightCss(activity))
      const outgoing = getActivityOutgoing(activity)
      outgoing.forEach((nn: any) => {
        // 获得连线是否有指向目标。如果有,则进行高亮
        const targetActivity = activityList.find((m: any) => m.key === nn.targetRef.id)
        if (targetActivity) {
          canvas.addMarker(nn.id, getActivityHighlightCss(targetActivity)) // 高亮【bpmn:SequenceFlow】连线
          // 高亮【...】目标。其中 ... 可以是 bpm:UserTask、也可以是其它的。当然,如果是 bpm:UserTask 的话,其实不做高亮也没问题,因为上面有逻辑做了这块。
          canvas.addMarker(nn.targetRef.id, getActivityHighlightCss(targetActivity))
        }
      })
    } else if (n.$type === 'bpmn:StartEvent') {
      // 开始节点 流程只要发起 开始节点就是完成状态
      let targetActivity = activityList.find((m) => m.key === n.id)
      if (targetActivity) {
        canvas.addMarker(n.id, 'highlight') // 高亮【bpmn:StartEvent】开始节点(自己)
      }
      // 开始节点
      const outgoing = getActivityOutgoing(activity)
      outgoing.forEach((nn) => {
        // outgoing 例如说【bpmn:SequenceFlow】连线
        // 获得连线是否有指向目标。如果有,则进行高亮
        let targetActivity = activityList.find((m: any) => m.key === nn.targetRef.id)
        if (targetActivity) {
          canvas.addMarker(nn.id, 'highlight') // 高亮【bpmn:SequenceFlow】连线
          canvas.addMarker(n.id, 'highlight') // 高亮【bpmn:StartEvent】开始节点(自己)
        }
      })
    } else if (n.$type === 'bpmn:EndEvent') {
      // 结束节点
      if (!processInstance.value || processInstance.value.result === 1) {
        return
      }
      canvas.addMarker(n.id, getResultCss(processInstance.value.result))
    } else if (n.$type === 'bpmn:ServiceTask') {
      //服务任务
      if (activity.startTime > 0 && activity.endTime === 0) {
        //进入执行,标识进行色
        canvas.addMarker(n.id, getResultCss(1))
      }
      if (activity.endTime > 0) {
        // 执行完成,节点标识完成色, 所有outgoing标识完成色。
        canvas.addMarker(n.id, getResultCss(2))
        const outgoing = getActivityOutgoing(activity)
        outgoing?.forEach((out) => {
          canvas.addMarker(out.id, getResultCss(2))
        })
      }
    }
  })
}

6.写在最后

本文简单的介绍了一个OA办公系统表单审批的全过程,行文比较粗糙,代码只展示了很少的一部分,如果有兴趣一起研究讨论的,欢迎留言批评指教。

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

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

相关文章

使用python读取yaml文件数据

使用python读取yaml文件&#xff1a; yaml文件数据&#xff1a;data.yaml login_data:url: http://www.baidu.comcase1:user1: password1: 12345errorText: 请输入用户名case2:user2: adminpassword2: errorText: 请输入密码case3:user3: adminpassword3: 123456errorText: 登…

视频监控设备通过onvif协议接入到视频监控平台

目 录 一、什么是onvif规范 1、onvif的定义 2、onvif的优势 二、AS-V1000监控平台对onvif的支持程度 二、通过onvif接入视频监控设备 1、onvif维护主页面 2、设备发现 3、设备验证 4、设备录入系统 5、通道配置 6、权限分配 三、对onvif设备进行…

【设计模式-6】建造者模式的实现与框架中的应用

建造者模式又被成为生成器模式&#xff0c;是一种使用频率比较低&#xff0c;相对复杂的创建型模式&#xff0c;在很多源码框架中可以看到建造者的使用场景&#xff0c;稍后我们会在本文末尾展示几个框架的使用案例。  建造者模式所构造的对象通常是比较复杂而且庞大的&#x…

C++ n皇后问题 || 深度优先搜索模版题

n− 皇后问题是指将 n 个皇后放在 nn 的国际象棋棋盘上&#xff0c;使得皇后不能相互攻击到&#xff0c;即任意两个皇后都不能处于同一行、同一列或同一斜线上。 现在给定整数 n &#xff0c;请你输出所有的满足条件的棋子摆法。 输入格式 共一行&#xff0c;包含整数 n 。 …

SpringCloud 之HttpClient、HttpURLConnection、OkHttpClient切换源码

SpringCloud 之HttpClient、HttpURLConnection、OkHttpClient切换源码 HttpClient、HttpURLConnection、OkHttpClient区别切换HttpClient 源码分析总结切换HttpClient源码验证切换是否成功okHttpClient 切换源码分析总结 okHttpClient 切换源码同时开启 okHttp 与httpClient 会…

【数字人】8、EAT | 为数字人引入情感表情(ICCV2023)

论文&#xff1a;Efficient Emotional Adaptation for Audio-Driven Talking-Head Generation 代码&#xff1a;https://yuangan.github.io/eat/ 出处&#xff1a;ICCV2023 特点&#xff1a;能引入表情&#xff0c;但无法眨眼&#xff0c;需要 音频 pose 图片 同时作为输入…

Java文件自动生成文档

说明 此文章根据Gemini Pro 生成资料整理。 生成文档 javadoc -d mydoc -author -version HelloWorld.java javadoc -d mydoc -author -version HelloWorld.java 命令用于生成 Java 源文件的javadoc文档&#xff0c;并将javadoc文档输出到 mydoc 目录中。 javadoc&#xf…

Linux学习之网络编程2(socket,简单C/S模型)

写在前面 Linux网络编程我是看视频学的&#xff0c;Linux网络编程&#xff0c;看完这个视频大概网络编程的基础差不多就掌握了。这个系列是我看这个Linux网络编程视频写的笔记总结。 网络字节序 小端法&#xff1a;pc本地存储&#xff0c;高位存高地址&#xff0c;低位存低地…

AI技术已经发现了一种新材料,可以在电池制造中减少对锂的需求

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗&#xff1f;订阅我们的简报&#xff0c;深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同&#xff0c;从行业内部的深度分析和实用指南中受益。不要错过这个机会&#xff0c;成为AI领…

Docker 安装:在linux系统CentOS7 版本 安装Docker

目录 一&#xff0c;Docker介绍&#xff1a; 1.1Docker是什么&#xff1f; 1.2Docker组成 二&#xff0c;Docker安装&#xff1a; 三&#xff0c;Docker基本使用 3.1服务 3.2镜像 3.3容器 &#x1f389;&#x1f389;欢迎来到我的CSDN主页&#xff01;&#x1f389;&am…

UniApp调试支付宝沙箱(安卓)

先看下这里完整的交互的图&#xff1a;小程序文档 - 支付宝文档中心 一、打包 不管怎样&#xff0c;先打个包先。可以直接使用云端证书、云端打包&#xff0c;只需要指定包名即可。 二、在支付宝开放平台创建应用 这个参考官方的过程就可以了&#xff0c;只要有刚才打的包&…

【REST2SQL】08 日志重构增加输出到文件log.txt

【REST2SQL】01RDB关系型数据库REST初设计 【REST2SQL】02 GO连接Oracle数据库 【REST2SQL】03 GO读取JSON文件 【REST2SQL】04 REST2SQL第一版Oracle版实现 【REST2SQL】05 GO 操作 达梦 数据库 【REST2SQL】06 GO 跨包接口重构代码 【REST2SQL】07 GO 操作 Mysql 数据库 原来…

解决:ModuleNotFoundError: No module named ‘pymysql’

解决&#xff1a;ModuleNotFoundError: No module named ‘pymysql’ 文章目录 解决&#xff1a;ModuleNotFoundError: No module named pymysql背景报错问题报错翻译报错位置代码报错原因解决方法方法一&#xff0c;直接安装方法二&#xff0c;手动下载安装方法三&#xff0c;…

php多小区智慧物业管理系统源码带文字安装教程

多小区智慧物业管理系统源码带文字安装教程 运行环境 服务器宝塔面板 PHP 7.0 Mysql 5.5及以上版本 Linux Centos7以上 统计分析以小区为单位&#xff0c;统计如下数据&#xff1a;小区总栋数、小区总户数、小区总人数、 小区租户数量、小区每月收费金额统计、小区车位统计、小…

C#,入门教程(14)——字符串与其他数据类型的转换

上一篇&#xff1a; C#&#xff0c;入门教程(13)——字符&#xff08;char&#xff09;及字符串&#xff08;string&#xff09;的基础知识https://blog.csdn.net/beijinghorn/article/details/123928151 数据只有可视化才能更好地体现其价值&#xff0c;因而 string 与 image…

【软件测试】学习笔记-微服务模式下API测试

这篇文章探讨当下最热门的技术领域的API测试&#xff0c;即微服务模式下的API测试。微服务架构下&#xff0c;API测试的最大挑战来自于庞大的测试用例数量&#xff0c;以及微服务之间的相互耦合。这篇文章探讨这两个问题的本质&#xff0c;以及如何基于消费者契约的方法来应对这…

SQL-数据类型

目录 DDL-表操作-数据类型 数值类型 字符串类型 举例&#xff1a; 案例&#xff1a; 日期时间类型 案例 表操作-案例 &#x1f389;欢迎您来到我的MySQL基础复习专栏 ☆* o(≧▽≦)o *☆哈喽~我是小小恶斯法克&#x1f379; ✨博客主页&#xff1a;小小恶斯法克的博客 &a…

【常用的简单功能及算法】拦截器 加盐算法 深克隆 时间日期格式化 加盐算法 sql分页算法 验证码

1.实现拦截器 Interceptor (以登录拦截器为例) 1.1 写一个登录拦截器普通类 实现HandlerInterceptor接口重写preHandle方法 //检验登录状态拦截器 //实现接口HandlerInterceptor 重写方法preHandle public class LoginInterceptor implements HandlerInterceptor {/** 该方…

网络安全保险发展起始阶段的挑战及应对措施

文章目录 前言一、网络安全保险的有序发展二、当前我国网络安全保险发展的初期态势&#xff08;一&#xff09;网络安全风险类型&#xff08;二&#xff09;网络安全保险的作用&#xff08;三&#xff09;与外国网络安全保费的规模对比 三、我国网络安全保险发展初期面临的挑战…

一天一个设计模式---适配器模式

概念 适配器模式是一种结构型设计模式&#xff0c;用于将一个类的接口转换成客户端所期望的另一个接口。它允许不兼容的接口之间进行协同工作&#xff0c;使得原本由于接口不匹配而无法合作的类能够一起工作。 具体内容 适配器模式主要包括以下几个要素&#xff1a; 目标接…