云尚办公项目学习

news2024/11/6 3:08:31

完整的笔记可以参考这个专栏,写的挺详细的:云尚办公课件笔记,come on boy

文章目录

    • form-create
    • 表设计
    • 步骤
      • 1,创建审批类型
      • 2,创建审批类型下的审批模板
      • 3,为指定的审批模板设置模板名称,表单项,
      • 4,上传审批模板对应的流程定义文件
      • 5,发布审批模板
      • 6,发起审批
      • 7、审批记录
      • 8,审批人查询待办任务
      • 9,发起审批测试
      • 10,审批人查看审批单的审批详情信息
      • 11,审批人对当前审批单进行审批
        • 同意
        • 驳回
      • 12,已处理
      • 13,已发起

form-create

form-create前端组件
formProps记录了表单有哪些表单项,分别是哪些类型(下拉,单选,输入框)
formOptions记录了表单的一些配置项,比如label-width等

表设计

在这里插入图片描述
mybatisplus生成代码

oa_process_type 审批类型
oa_process_template 审批模板

oa_prcess 审批列表(关联审批流流程实例id)

步骤

1,创建审批类型

2,创建审批类型下的审批模板

3,为指定的审批模板设置模板名称,表单项,

4,上传审批模板对应的流程定义文件

为指定的审批模板上传流程定义文件,将该文件保存到指定的目录下,并保存到审批模板的流程定义path字段,流程定义文件的文件名(不包括后缀名)作为审批模板的流程定义key字段

5,发布审批模板

发布该审批模板,就是将此审批模板流程定义path字段所指定的路径所对应的文件读取成流,部署到activiti中,修改审批模板为已发布状态

6,发起审批

发起审批,首先获取审批模板对应的表单,申请人填写表单内容,表单内容会转换为json数据传给后台,后台根据审批模板将数据保存到业务审批表,申请人填写的表单内容就保存在业务审批表的form_values字段中,这样就可以得到业务id了,然后activiti使用审批模板所保存的流程定义key启动流程实例,并传入业务id,同时传入将申请人填写的表单内容json数据转为的map存入到key为data的map中,这样流程实例就启动了,并且和业务id绑定了,并且也有了申请人填写的表单内容作为流程变量。

在流程实例启动后,立即查询当前流程实例的下一个节点的审批人(有可能有多个),取到这些审批人的assignee,给这些审批人推送消息以便于提醒他们尽快去审批。

推送完消息之后,将流程实例id保存到业务审批表的流程实例id字段。

//启动流程
@Override
public void startUp(ProcessFormVo processFormVo) {
    //1 根据当前用户id获取用户信息
    SysUser sysUser = sysUserService.getById(LoginUserInfoHelper.getUserId());

    //2 根据审批模板id把模板信息查询
    ProcessTemplate processTemplate = processTemplateService.getById(processFormVo.getProcessTemplateId());

    //3 保存提交审批信息到业务表,oa_process
    Process process = new Process();
    //processFormVo复制到process对象里面
    BeanUtils.copyProperties(processFormVo,process);
    //其他值
    process.setStatus(1); //审批中
    String workNo = System.currentTimeMillis() + "";
    process.setProcessCode(workNo);
    process.setUserId(LoginUserInfoHelper.getUserId());
    process.setFormValues(processFormVo.getFormValues());
    process.setTitle(sysUser.getName() + "发起" + processTemplate.getName() + "申请");
    baseMapper.insert(process);

    //4 启动流程实例 - RuntimeService
    //4.1 流程定义key
    String processDefinitionKey = processTemplate.getProcessDefinitionKey();
    
    //4.2 业务key  processId
    String businessKey = String.valueOf(process.getId());

    //4.3 流程参数 form表单json数据,转换map集合
    String formValues = processFormVo.getFormValues();
    //formData
    JSONObject jsonObject = JSON.parseObject(formValues);
    JSONObject formData = jsonObject.getJSONObject("formData");
    //遍历formData得到内容,封装map集合
    Map<String,Object> map = new HashMap<>();
    for(Map.Entry<String,Object> entry:formData.entrySet()) {
        map.put(entry.getKey(),entry.getValue());
    }
    Map<String,Object> variables = new HashMap<>();
    variables.put("data",map);
    //启动流程实例
    ProcessInstance processInstance = runtimeService.startProcessInstanceByKey(processDefinitionKey,
            businessKey, variables);

    //5 查询下一个审批人
    //审批人可能多个
    List<Task> taskList = taskService.createTaskQuery().processInstanceId(processInstance.getId()).list();
    List<String> nameList = new ArrayList<>();
    for(Task task : taskList) {
        String assigneeName = task.getAssignee();
        SysUser user = sysUserService.getUserByUserName(assigneeName);
        String name = user.getName();
        nameList.add(name);
        //推送消息
        messageService.pushPendingMessage(process.getId(),user.getId(),task.getId());
    }
    process.setProcessInstanceId(processInstance.getId());
    process.setDescription("等待"+ StringUtils.join(nameList.toArray(), ",")+"审批");
    //7 业务和流程关联  更新oa_process数据
    baseMapper.updateById(process);

    //记录操作审批信息记录
    processRecordService.record(process.getId(),1,"发起申请");
}


7、审批记录

用来记录1个审批所走过的轨迹,首先添加审批记录表,如下
在这里插入图片描述
在开启流程实例的方法中,生成一条审批记录存入到审批记录表当中(调用处在上面)

@Override
public void record(Long processId, Integer status, String description) {
    Long userId = LoginUserInfoHelper.getUserId();
    SysUser sysUser = sysUserService.getById(userId);
    ProcessRecord processRecord = new ProcessRecord();
    processRecord.setProcessId(processId);
    processRecord.setStatus(status);
    processRecord.setDescription(description);
    processRecord.setOperateUser(sysUser.getName());
    processRecord.setOperateUserId(userId);
    baseMapper.insert(processRecord);
}

8,审批人查询待办任务

使用activiti工作流提供的taskService根据当前用户作为assignee查询当前用户的的任务列表(并且使用了分页),遍历这个任务列表,可以拿到每个任务对应的流程实例,然后拿到流程实例对应的业务key(其实,通过task就可以直接拿到businessKey),拿到businessKey之后,就可以查询业务审批表,来获取到申请人所填写的申请表单内容了。这样当前审批人就可以根据申请人提交的表单内容做审批了。注意,工作流的taskId也带上去了。

//查询待处理任务列表
@Override
 public IPage<ProcessVo> findfindPending(Page<Process> pageParam) {
     //1 封装查询条件,根据当前登录的用户名称
     TaskQuery query = taskService.createTaskQuery()
             .taskAssignee(LoginUserInfoHelper.getUsername())
             .orderByTaskCreateTime()
             .desc();

     //2 调用方法分页条件查询,返回list集合,待办任务集合
     //listPage方法有两个参数
     //第一个参数:开始位置  第二个参数:每页显示记录数
     int begin = (int)((pageParam.getCurrent()-1)*pageParam.getSize());
     int size = (int)pageParam.getSize();
     List<Task> taskList = query.listPage(begin, size);
     long totalCount = query.count();

     //3 封装返回list集合数据 到 List<ProcessVo>里面
     //List<Task> -- List<ProcessVo>
     List<ProcessVo> processVoList = new ArrayList<>();
     for(Task task : taskList) {
         //从task获取流程实例id
         String processInstanceId = task.getProcessInstanceId();
         //根据流程实例id获取实例对象
         ProcessInstance processInstance = runtimeService.createProcessInstanceQuery()
                                                         .processInstanceId(processInstanceId)
                                                         .singleResult();
         //从流程实例对象获取业务key---processId
         String businessKey = processInstance.getBusinessKey();
         if(businessKey == null) {
             continue;
         }
         //根据业务key获取Process对象
         long processId = Long.parseLong(businessKey);
         Process process = baseMapper.selectById(processId);
         //Process对象 复制 ProcessVo对象
         ProcessVo processVo = new ProcessVo();
         BeanUtils.copyProperties(process,processVo);
         processVo.setTaskId(task.getId());
         //放到最终list集合processVoList
         processVoList.add(processVo);
     }

     //4 封装返回IPage对象
     IPage<ProcessVo> page = new Page<ProcessVo>(pageParam.getCurrent(),
                              pageParam.getSize(),totalCount);
     page.setRecords(processVoList);
     return page;
 }

9,发起审批测试

测试步骤如下,

先添加审批模板,然后发布,使用admin填写审批表单发起1个审批,然后下一节点审批人zhangsan查看自己的待办任务
在这里插入图片描述
张三查看自己的待办任务列表如下
在这里插入图片描述

10,审批人查看审批单的审批详情信息

前面张三查看到了自己的待办任务列表,待办任务列表数据的id是业务表的id,因此前端将此id传进来,来查询审批详情信息(比如用户什么时间提的申请,填写的表单内容)。

接着,继续使用业务表的id查询审批记录表中的审批记录(前面发起审批时,记录了,当前自己审批时,也须往这个表中记录)

接着,根据审批业务表的id得到审批模板信息

接着,查询当前人是否可以审批该审批流程实例(这里要推敲下)

下面这个代码感觉有点问题:上面查询待办的时候,拿的就是当前人作为assignee查询的任务,那么查到的审批单对应的审批人是一定包括当前人的(可能有多个审批人),并且,也是有查询到对应的taskId的,我觉得这里应该让前端把taskId传过来,就可以得到这个taskId对应的assignee是不是当前人。但是好好想想,他这样做也没啥问题,他其实就是在判断当前人是否是这个流程实例的当前所在节点的审批人,如果是的话,那就可以审批,不是的话,就不可以审批

问:假设某个节点是个会签节点的话,那么这个会签节点的这个审批记录该如何查询封装呢?如何展示呢?是放到同1个节点中并标名这是个会签节点,还是不管它是会签节点还是非会签节点就特么按时间来排序组成审批轨迹。很显然,下面这种就是第二种情况。

//查看审批详情信息
@Override
public Map<String, Object> show(Long id) {

    //1 根据流程id获取流程信息Process
    Process process = baseMapper.selectById(id);

    //2 根据流程id获取流程记录信息
    LambdaQueryWrapper<ProcessRecord> wrapper = new LambdaQueryWrapper<>();
    wrapper.eq(ProcessRecord::getProcessId,id);
    List<ProcessRecord> processRecordList = processRecordService.list(wrapper);

    //3 根据模板id查询模板信息
    ProcessTemplate processTemplate = processTemplateService.getById(process.getProcessTemplateId());

    //4 判断当前用户是否可以审批
    //可以看到信息不一定能审批,不能重复审批
    boolean isApprove = false;
    List<Task> taskList = this.getCurrentTaskList(process.getProcessInstanceId());
    for(Task task : taskList) {
        //判断任务审批人是否是当前用户
        String username = LoginUserInfoHelper.getUsername();
        if(task.getAssignee().equals(username)) {
            isApprove = true;
        }
    }

    //5 查询数据封装到map集合,返回
    Map<String,Object> map = new HashMap<>();
    map.put("process", process);
    map.put("processRecordList", processRecordList);
    map.put("processTemplate", processTemplate);
    map.put("isApprove", isApprove);
    return map;
}

张三点击审批详情后,进入到如下页面

在这里插入图片描述

11,审批人对当前审批单进行审批

最重要的一步来了,审批人对当前审批单进行审批(同意or驳回)。驳回即审批不通过,流程结束。
如果当前审批人 对 此审批单 审批通过,那么流程引擎会自动将任务流转到下1个审批人那里(如果还有下一个任务节点的话),并通知下一个审批人(可能是多个人),下一个人就可以查询到自己的待办任务。

如果当前审批人 对 此审批单 驳回,那么流程结束。结束流程的代码是固定模式的。

不管当前审批人 对 此审批单 是审批通过 还是驳回,都要记录到审批记录表,并且更新审批的业务表。

同意
//审批
@Override
public void approve(ApprovalVo approvalVo) {

    //1 从approvalVo获取任务id,根据任务id获取流程变量
    String taskId = approvalVo.getTaskId();
    Map<String, Object> variables = taskService.getVariables(taskId);
    for(Map.Entry<String,Object> entry:variables.entrySet()) {
        System.out.println(entry.getKey());
        System.out.println(entry.getValue());
    }

    //2 判断审批状态值
    if(approvalVo.getStatus() == 1) {
        //2.1 状态值 =1  审批通过
        Map<String, Object> variable = new HashMap<>();
        taskService.complete(taskId,variable);
    } else {
        //2.2 状态值 = -1 驳回,流程直接结束
        this.endTask(taskId);
    }

    //3 记录审批相关过程信息 oa_process_record
    String description = approvalVo.getStatus().intValue() ==1 ? "已通过" : "驳回";
    processRecordService.record(approvalVo.getProcessId(),approvalVo.getStatus(),description);

    //4 查询下一个审批人,更新流程表记录 process表记录
    Process process = baseMapper.selectById(approvalVo.getProcessId());
    //查询任务
    List<Task> taskList = this.getCurrentTaskList(process.getProcessInstanceId());
    if(!CollectionUtils.isEmpty(taskList)) {
        List<String> assignList = new ArrayList<>();
        for(Task task : taskList) {
            String assignee = task.getAssignee();
            SysUser sysUser = sysUserService.getUserByUserName(assignee);
            assignList.add(sysUser.getName());

            //TODO 公众号消息推送
        }
        //更新process流程信息
        process.setDescription("等待" + StringUtils.join(assignList.toArray(), ",") + "审批");
        process.setStatus(1);
    } else {
        if(approvalVo.getStatus().intValue() == 1) {
            process.setDescription("审批完成(通过)");
            process.setStatus(2);
        } else {
            process.setDescription("审批完成(驳回)");
            process.setStatus(-1);
        }
    }
    baseMapper.updateById(process);
}
驳回

驳回就是手动创建从当前节点到结束节点之间的流向,然后完成当前task,此时,流程引擎就会结束当前流程实例,流程处于完结状态了

//结束流程
private void endTask(String taskId) {
    //1 根据任务id获取任务对象 Task
    Task task = taskService.createTaskQuery().taskId(taskId).singleResult();

    //2 获取流程定义模型 BpmnModel
    BpmnModel bpmnModel = repositoryService.getBpmnModel(task.getProcessDefinitionId());

    //3 获取结束流向节点
    List<EndEvent> endEventList = bpmnModel.getMainProcess().findFlowElementsOfType(EndEvent.class);
    if(CollectionUtils.isEmpty(endEventList)) {
        return;
    }
    FlowNode endFlowNode = (FlowNode)endEventList.get(0);

    //4 当前流向节点
    FlowNode currentFlowNode = (FlowNode)bpmnModel.getMainProcess().getFlowElement(task.getTaskDefinitionKey());

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

    //6 创建新流向
    SequenceFlow newSequenceFlow = new SequenceFlow();
    newSequenceFlow.setId("newSequenceFlow");
    newSequenceFlow.setSourceFlowElement(currentFlowNode);
    newSequenceFlow.setTargetFlowElement(endFlowNode);

    //7 当前节点指向新方向
    List newSequenceFlowList = new ArrayList();
    newSequenceFlowList.add(newSequenceFlow);
    currentFlowNode.setOutgoingFlows(newSequenceFlowList);

    //8 完成当前任务
    taskService.complete(task.getId());
}

12,已处理

当前用户查询自己审批过的任务(注明:通过或驳回的都算,但用户发起审批不算1个已处理的任务)

此处的实现,使用的是工作流提供的HistoryService根据taskAssignee是当前人,并且是已完成的历史任务实例。

//已处理
@Override
public IPage<ProcessVo> findProcessed(Page<Process> pageParam) {

    //封装查询条件
    HistoricTaskInstanceQuery query = historyService.createHistoricTaskInstanceQuery()
            .taskAssignee(LoginUserInfoHelper.getUsername())
            .finished().orderByTaskCreateTime().desc();

    //调用方法条件分页查询,返回list集合
    // 开始位置  和  每页显示记录数
    int begin = (int)((pageParam.getCurrent()-1)*pageParam.getSize());
    int size = (int)pageParam.getSize();
    
    List<HistoricTaskInstance> list = query.listPage(begin, size);
    long totalCount = query.count();

    //遍历返回list集合,封装List<ProcessVo>
    List<ProcessVo> processVoList = new ArrayList<>();
    
    for(HistoricTaskInstance item : list) {
    
        //流程实例id
        String processInstanceId = item.getProcessInstanceId();
        
        //根据流程实例id查询获取process信息
        LambdaQueryWrapper<Process> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(Process::getProcessInstanceId,processInstanceId);        
        Process process = baseMapper.selectOne(wrapper);
        
        // process -- processVo
        ProcessVo processVo = new ProcessVo();
        BeanUtils.copyProperties(process,processVo);
        
        processVo.setTaskId("0");
        
        //放到list
        processVoList.add(processVo);
    }

    //IPage封装分页查询所有数据,返回
    IPage<ProcessVo> pageModel =
            new Page<ProcessVo>(pageParam.getCurrent(),pageParam.getSize(),
                    totalCount);
    pageModel.setRecords(processVoList);
    return pageModel;
}

13,已发起

查询当前用户发起的审批任务,其实就记录在oa_process表,根据发起人用户id查询即可

//已发起
@Override
public IPage<ProcessVo> findStarted(Page<ProcessVo> pageParam) {
    ProcessQueryVo processQueryVo = new ProcessQueryVo();
    processQueryVo.setUserId(LoginUserInfoHelper.getUserId());
    IPage<ProcessVo> pageModel = baseMapper.selectPage(pageParam, processQueryVo);
    for (ProcessVo item : pageModel.getRecords()) {
        item.setTaskId("0");
    }
    return pageModel;
}

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

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

相关文章

Python笔记01-你好Python

文章目录 Python简介环境安装Hello world开发工具 Python简介 python的诞生 1989年&#xff0c;为了打发圣诞节假期&#xff0c;Gudio van Rossum吉多 范罗苏姆&#xff08;龟叔&#xff09;决心开发一个新的解释程序&#xff08;Python雏形&#xff09; 1991年&#xff0c;第…

CSS 压重按钮 效果

<template><view class="cont"><div class="container"><div class="pane"><!-- 选项1 --><label class="label" @click="handleOptionClick(0)":style="{ color: selectedOption ==…

【机器学习】循环神经网络(二)-LSTM示例(keras)国际航空乘客问题的回归问题...

使用 Keras 在 Python 中使用 LSTM 循环神经网络进行时间序列预测 国际航空乘客问题的回归问题 这个文件是一个CSV格式的数据集&#xff0c;它包含了从1949年1月到1960年12月的每个月的国际航空乘客的总数&#xff08;以千为单位&#xff09;。第一行是列名&#xff0c;分别是&…

Open CASCADE学习|入门Hello world

目录 1、新建项目 2、写代码 3、配置 3.1配置头文件 3.2配置静态库文件 3.3配置动态库文件 4、编译运行 1、新建项目 新建一个Win32控制台应用程序&#xff0c;取名为HelloWorld&#xff0c;如下图所示&#xff1a; 2、写代码 测试所用的代码如下&#xff1a; // Use T…

数据分析基础之《numpy(6)—IO操作与数据处理》

了解即可&#xff0c;用panads 一、numpy读取 1、问题 大多数数据并不是我们自己构造的&#xff0c;而是存在文件当中&#xff0c;需要我们用工具获取 但是numpy其实并不适合用来读取和处理数据&#xff0c;因此我们这里了解相关API&#xff0c;以及numpy不方便的地方即可 2…

Rockchip平台双屏异显功能实现(基于Android13)

Rockchip平台双屏异显功能实现(基于Android13) 1. 异显实现方案 Rockchip SDK平台支持两种不同的异显方案&#xff1a;Android Presentation和Android Activity指定屏幕启动。 使用Android Presentation方案&#xff0c;需要在APP开发中调用相应接口以使指定视图&#xff08…

ATTCK视角下的信息收集:主机发现

目录 1、利用协议主动探测主机存活 利用ICMP发现主机 利用ARP发现主机 利用NetBIOS协议发现主机 利用TCP/UDP发现主机 利用DNS协议发现主机 利用PRC协议发现主机程序 2、被动主机存活检测 利用Browser主机探测存活主机 利用ip段探测主机存活 利用net命令探测主机存活…

论文笔记 Understanding Electricity-Theft Behavior via Multi-Source Data

WWW 2020 oral 1 INTRO 1.1 背景 1.1.1 窃电 窃电&#xff08;electricity theft&#xff09;指用户为了逃避电费而进行非法操作的一种行为 常用的反窃电方法可分为两类&#xff1a; 基于硬件驱动的反窃电方法 ​​​​​​​电表开盖检测、集中器检测。。。。 硬件驱动的…

18.标题统计

题目 import java.util.Scanner;public class Main {public static void main(String[] args) {Scanner sc new Scanner(System.in);String str sc.nextLine();int res 0;for(int i0;i<str.length();i) {char c str.charAt(i);if(c! && c!\n) {res;}}System.o…

Simply主题 简约风格的Emlog博客模板 响应式布局

主题介绍 Simply是一款简约风格的Emlog博客模板&#xff0c;响应式布局、界面简单大方&#xff0c;实用性强&#xff01; 支持夜间模式&#xff0c;采用localStorage存储配置。IOS系统下支持随系统自动切换浅/深色模式。 文章页支持显示文章字数及阅读时间。 支持http/https …

MCS-51单片机的基本结构

目录 一.单片机的逻辑结构 1.单片机的基本结构 2.引脚 3.中断系统 4.时钟电路 5.时序 6.典型指令的取指、执行时序 7.80C51中定时器/计数器 二.单片机的复位 三.程序的执行方式 1.单步执行方式 2.低功耗操作方式 3.EPROM编程和校验方式 首先补充一个知识点&#x…

JVM工作原理与实战(八):类加载器的分类

专栏导航 JVM工作原理与实战 RabbitMQ入门指南 从零开始了解大数据 目录 专栏导航 前言 一、类加载器介绍 二、类加载器的分类 1.Java代码实现的类加载器 2.Java虚拟机底层源码实现的类加载器 3.默认的类加载器层次&#xff08;JDK8及之前的版本&#xff09; 总结 前言…

听GPT 讲Rust源代码--compiler(15)

File: rust/compiler/rustc_arena/src/lib.rs 在Rust源代码中&#xff0c;rustc_arena/src/lib.rs文件定义了TypedArena&#xff0c;ArenaChunk&#xff0c;DroplessArena和Arena结构体&#xff0c;以及一些与内存分配和容器操作相关的函数。 cold_path<F: FnOnce,drop,new,…

PHP在线sqlite转html表格小功能(sqlite2html)

6KB PHP实现在线sqlite转html表格小功能(支持大文件上传,得到一表一文件) 可自定义&#xff1a;上传限制大小&#xff1b;支持后缀格式!下载格式位压缩包&#xff0c;内含一表一个html文件。 作用&#xff1a;程序员实用工具&#xff0c;上传sqlite数据得到html表格数据供本地…

主流大语言模型从预训练到微调的技术原理

引言 本文设计的内容主要包含以下几个方面&#xff1a; 比较 LLaMA、ChatGLM、Falcon 等大语言模型的细节&#xff1a;tokenizer、位置编码、Layer Normalization、激活函数等。大语言模型的分布式训练技术&#xff1a;数据并行、张量模型并行、流水线并行、3D 并行、零冗余优…

迁移数据mysql到clickhouse

场景&#xff1a; 项目上需要将mysql表中数据迁移到clickhouse。 理论&#xff1a; 借助MaterializeMySQL 说明&#xff1a; 首先该方案实施需要启动mysql的binlog配置否则同步不了&#xff0c;尽管MaterializeMySQL官方说是在实验阶段&#xff0c;不应该在生产上使用&#x…

【详解】静态库和动态库的认识和使用【Linux】

静态库和动态库的认识和使用 静态库和动态库的概述动静态库的实现静态库动态库库文件名称和引入库的名称 静态库和动态库的概述 静态库&#xff08;.a&#xff09;&#xff1a;程序在编译链接的时候把库的代码链接到可执行文件中。程序运行的时候将不再需要静态库 动态库&#…

【InternLM】书生-浦语大模型demo搭建服务接口部署本地映射

目录 前言一、InternLM大模型介绍1-1、大模型简介1-2、InternLM大模型简介1-2-1、InternLM-7B1-2-2、InternLM-20B 二、从0开始搭建InternLM-Chat-7B 智能对话 Demo2-0、环境搭建2-1、创建虚拟环境2-2、导入所需要的包2-3、模型下载2-4、代码克隆2-5、终端运行 三、服务器接口部…

uniapp微信小程序投票系统实战 (SpringBoot2+vue3.2+element plus ) -后端鉴权拦截器实现

锋哥原创的uniapp微信小程序投票系统实战&#xff1a; uniapp微信小程序投票系统实战课程 (SpringBoot2vue3.2element plus ) ( 火爆连载更新中... )_哔哩哔哩_bilibiliuniapp微信小程序投票系统实战课程 (SpringBoot2vue3.2element plus ) ( 火爆连载更新中... )共计21条视频…

C# 自定义配置文件序列化生成+文件格式错误自动回档

文章目录 前言选择Xml简单的Xml使用测试用例简单的写简单的读简单的生成配置修改配置类测试用例运行结果对比 代码逻辑封装逻辑示意封装好的代码测试生成配置文件格式错误测试使用默认值覆盖来解决问题 配置文件人为修改错误如何解决解决方案代码测试用例运行结果 代码封装总结…