首页 > 其他分享 >基于jeecg-boot的flowable流程收回功能实现(全网首创功能)

基于jeecg-boot的flowable流程收回功能实现(全网首创功能)

时间:2023-10-31 12:37:56浏览次数:37  
标签:row title flowable boot 流程 processInstanceId null id jeecg


更多nbcio-boot功能请看演示系统

gitee源代码地址

在线演示(包括H5) : http://122.227.135.243:9888
       

      对于之前的flowable流程,之前有撤回,拒绝,退回等功能,但都不能满足发起人对于流程收回的功能,发起人收回后可以重新进行流程发起,同时能够支持自定义业务的收回功能。

      从目前开源项目与全网的资料看都没有找到相关资料,所以只能自己来写相应的功能,满足用户的需求了。

版权声明:大家要是单独用我的代码,请注明作者。

     1、首先前端功能

       前端比较简单,只要在已办功能里增加收回菜单功能,同时调用后端代码来实现。

        增加一个菜单按钮

基于jeecg-boot的flowable流程收回功能实现(全网首创功能)_ico

      增加一个收回任务函数

基于jeecg-boot的flowable流程收回功能实现(全网首创功能)_git_02

      完整的代码如下:

<template>
  <a-card :bordered="false">
    <!-- 查询区域 -->
    <div class="table-page-search-wrapper">
      <a-form layout="inline" @keyup.enter.native="handleQuery">
        <a-row :gutter="24">
          <a-col :md="6" :sm="8">
            <a-form-item label="流程名称">
              <a-input placeholder="请输入流程名称" v-model="queryParams.procDefName"></a-input>
            </a-form-item>
          </a-col>
          <a-col :md="8" :sm="24">
            <a-form-item label="接收日期">
              <a-date-picker v-model="queryParams.createTime"  style="width: 100%" placeholder="请输入接收日期"/>
            </a-form-item>
          </a-col>
          <a-col :md="6" :sm="8">
            <span style="float: left;overflow: hidden;" class="table-page-search-submitButtons">
              <a-button type="primary" @click="handleQuery" icon="search">查询</a-button>
              <a-button type="primary" @click="searchReset" icon="reload" style="margin-left: 8px">重置</a-button>
            </span>
          </a-col>
    
        </a-row>
      </a-form>
    </div>
    <!-- 查询区域-END -->
  
    <!-- 操作按钮区域 -->
    <div class="table-operator">
      <a-button type="primary" icon="download" @click="handleExportXls('待办任务')">导出</a-button>
     
      <a-dropdown v-if="selectedRowKeys.length > 0">
        <a-menu slot="overlay">
          <a-menu-item key="1" @click="batchDel"><a-icon type="delete"/>删除</a-menu-item>
        </a-menu>
        <a-button style="margin-left: 8px"> 批量操作 <a-icon type="down" /></a-button>
      </a-dropdown>
    </div>
  
    <!-- table区域-begin -->
    <div>
      <div class="ant-alert ant-alert-info" style="margin-bottom: 16px;">
        <i class="anticon anticon-info-circle ant-alert-icon"></i> 已选择 <a style="font-weight: 600">{{ selectedRowKeys.length }}</a>项
        <a style="margin-left: 24px" @click="onClearSelected">清空</a>
      </div>
  
      <a-table
        ref="table"
        size="middle"
        :scroll="{x:true}"
        bordered
        rowKey="procInsId"
        :columns="columns"
        :dataSource="dataSource"
        :pagination="ipagination"
        :loading="loading"
        :rowSelection="{selectedRowKeys: selectedRowKeys, onChange: onSelectChange}"
        class="j-table-force-nowrap"
        @change="handleTableChange">
        
        <template slot="procDefVersion" slot-scope="text, record, index">
            <el-tag size="medium" >V{{ record.procDefVersion }}</el-tag>
        </template>
        <template slot="startUserName" slot-scope="text, record, index">
           <label>{{record.startUserName}} <el-tag type="info" size="mini">{{record.startDeptName}}</el-tag></label>
        </template>
        <template slot="htmlSlot" slot-scope="text">
          <div v-html="text"></div> 
        </template>
        <template slot="imgSlot" slot-scope="text">
          <span v-if="!text" style="font-size: 12px;font-style: italic;">无图片</span>
          <img v-else :src="getImgView(text)" height="25px" alt="" style="max-width:80px;font-size: 12px;font-style: italic;"/>
        </template>
        <template slot="fileSlot" slot-scope="text">
          <span v-if="!text" style="font-size: 12px;font-style: italic;">无文件</span>
          <a-button
            v-else
            :ghost="true"
            type="primary"
            icon="download"
            size="small"
            @click="downloadFile(text)">
            下载
          </a-button>
        </template>
  
        <span slot="action" slot-scope="text, record">
          <a-dropdown>
            <a class="ant-dropdown-link">更多 <a-icon type="down" /></a>
            <a-menu slot="overlay">
              <a-menu-item>
                <a @click="handleFlowRecord(record)">流转记录</a>
              </a-menu-item>
              <a-menu-item>
                <a @click="handleRecall(record)"> 收回</a>
              </a-menu-item>
              <a-menu-item>
                <a @click="handleRevoke(record)"> 撤回</a>
              </a-menu-item>
            </a-menu>
          </a-dropdown>
        </span>
  
      </a-table>
  
    </div>
  </a-card>
</template>

<script>
  import '@/assets/less/TableExpand.less'
  import { mixinDevice } from '@/utils/mixin'
  import { JeecgListMixin } from '@/mixins/JeecgListMixin'  
  import { finishedList, finishedListNew, getDeployment, delDeployment, addDeployment, 
           updateDeployment, exportDeployment, revokeProcess, recallProcess } from "@/views/flowable/api/finished";
  import moment from 'moment';
export default {
  name: "finishedIndex",
  mixins:[JeecgListMixin, mixinDevice],
  components: {
  },
  data() {
    return {
      // 表头
      columns: [
        {
          title: '#',
          dataIndex: '',
          key:'rowIndex',
          width:60,
          align:"center",
          customRender:function (t,r,index) {
            return parseInt(index)+1;
          }
        },
        {
          title:'任务编号',
          align:"center",
          dataIndex: 'procInsId',
        },
        {
          title:'流程名称',
          align:"center",
          dataIndex: 'procDefName',
        },
        {
          title:'任务节点',
          align:"center",
          dataIndex: 'taskName',
        },
        {
          title:'流程类别',
          align:"center",
          dataIndex: 'category'
        },
        {
          title:'流程版本',
          align:"center",
          dataIndex: 'procDefVersion',
          scopedSlots: { customRender: 'procDefVersion' }
        },
        {
          title:'业务主键',
          align:"center",
          dataIndex: 'businessKey'
        },
        {
          title:'流程发起人',
          align:"center",
          dataIndex: 'startUserName',
          scopedSlots: { customRender: 'startUserName' }
        },
        {
          title:'接收时间',
          align:"center",
          dataIndex: 'createTime'
        },
        {
          title:'审批时间',
          align:"center",
          dataIndex: 'finishTime'
        },
        {
          title:'耗时',
          align:"center",
          dataIndex: 'duration'
        },
        {
          title: '操作',
          dataIndex: 'action',
          align:"center",
          fixed:"right",
          width:147,
          scopedSlots: { customRender: 'action' }
        }
      ],
      // 查询参数
      queryParams: {
        pageNo: 1,
        pageSize: 10,
        name: null,
        category: null,
        key: null,
        tenantId: null,
        deployTime: null,
        derivedFrom: null,
        derivedFromRoot: null,
        parentDeploymentId: null,
        engineVersion: null,
        procDefName: null,
        createTime: null
      },
      url: {
        list: "/flowable/task/finishedListNew",
        deleteBatch: "/flowable/task/deleteBatch",
        exportXlsUrl: "/flowable/task/finishedExportXls",
      },
      dataSource: [], //表格数据源
      /* 表格分页参数 */
      ipagination:{
        current: 1,
        pageSize: 10,
        pageSizeOptions: ['10', '20', '30'],
        showTotal: (total, range) => {
          return range[0] + "-" + range[1] + " 共" + total + "条"
        },
        showQuickJumper: true,
        showSizeChanger: true,
        total: 0
      },
      // 遮罩层
      loading: true,
      // 选中数组
      ids: [],
      // 非单个禁用
      single: true,
      // 非多个禁用
      multiple: true,
      // 显示搜索条件
      showSearch: true,
      // 总条数
      total: 0,
      // 已办任务列表数据
      finishedList: [],
      // 弹出层标题
      title: "",
      // 是否显示弹出层
      open: false,
      src: "",
      // 查询参数
      queryParams: {
        pageNo: 1,
        pageSize: 10,
        name: null,
        category: null,
        key: null,
        tenantId: null,
        deployTime: null,
        derivedFrom: null,
        derivedFromRoot: null,
        parentDeploymentId: null,
        engineVersion: null
      },
      // 表单参数
      form: {},
      // 表单校验
      rules: {
      }
    };
  },
  created() {
    this.getSuperFieldList();
    //this.getList();
  },
  methods: {
    /** 查询流程定义列表 */
    getList() {
      this.loading = true;
      finishedListNew(this.queryParams).then(response => {
        if(response.success) {
           this.dataSource = response.result.records;
           this.total = response.result.total;
           this.ipagination.total = response.result.total;   
           this.loading = false;
         }
        else {
           this.$message.error(response.message)
           this.loading = false;
        }
      });
    },
    // 取消按钮
    cancel() {
      this.open = false;
      this.reset();
    },
    // 表单重置
    reset() {
      this.form = {
        id: null,
        name: null,
        category: null,
        key: null,
        tenantId: null,
        deployTime: null,
        derivedFrom: null,
        derivedFromRoot: null,
        parentDeploymentId: null,
        engineVersion: null
      };
      this.resetForm("form");
    },
    setIcon(val){
      if (val){
        return "el-icon-check";
      }else {
        return "el-icon-time";
      }

    },
    setColor(val){
      if (val){
        return "#2bc418";
      }else {
        return "#b3bdbb";
      }

    },
    initDictConfig(){
    },
    getSuperFieldList(){
      let fieldList=[];
      fieldList.push({type:'string',value:'procInsId',text:'任务编号'})
      fieldList.push({type:'string',value:'procDefName',text:'流程名称'})
      fieldList.push({type:'string',value:'taskName',text:'任务节点'})
      fieldList.push({type:'string',value:'category',text:'流程类别'})
      fieldList.push({type:'string',value: 'procDefVersion',text:'流程版本'})
      fieldList.push({type:'string',value: 'businessKey',text:'业务主键'})
      fieldList.push({type:'string',value:'startUserName',text:'流程发起人'})
      fieldList.push({type:'datetime',value:'createTime',text:'接收时间'})
      fieldList.push({type:'datetime',value:'finishTime',text:'审批时间'})
      fieldList.push({type:'string',value:'duration',text:'耗时'})
      this.superFieldList = fieldList
    },
    /** 搜索按钮操作 */
    handleQuery() {
      this.queryParams.pageNum = 1;
      this.getList();
    },
    /** 重置按钮操作 */
    resetQuery() {
      this.resetForm("queryForm");
      this.handleQuery();
    },
    // 多选框选中数据
    handleSelectionChange(selection) {
      this.ids = selection.map(item => item.id)
      this.single = selection.length!==1
      this.multiple = !selection.length
    },
    /** 新增按钮操作 */
    handleAdd() {
      this.reset();
      this.open = true;
      this.title = "添加流程定义";
    },
    /** 流程流转记录 */
    handleFlowRecord(row){
      this.$router.push({ path: '/flowable/task/record/index',
        query: {
          procInsId: row.procInsId,
          deployId: row.deployId,
          taskId: row.taskId,
          businessKey: row.businessKey,
          category: row.category,
          finished: false
      }})
    },
    /** 撤回任务 */
    handleRevoke(row){
      const params = {
        instanceId: row.procInsId,
        dataId: row.businessKey
      }
      revokeProcess(params).then( res => {
        this.$message.success(res.message);
        this.getList();
      });
    },
    /** 收回任务 */
    handleRecall(row){
      const params = {
        instanceId: row.procInsId,
        dataId: row.businessKey
      }
      recallProcess(params).then( res => {
        this.$message.success(res.message);
        this.getList();
      });
    },
  
    /** 提交按钮 */
    submitForm() {
      this.$refs["form"].validate(valid => {
        if (valid) {
          if (this.form.id != null) {
            updateDeployment(this.form).then(response => {
              this.$message.success("修改成功");
              this.open = false;
              this.getList();
            });
          } else {
            addDeployment(this.form).then(response => {
              this.$message.success("新增成功");
              this.open = false;
              this.getList();
            });
          }
        }
      });
    },
    /** 删除按钮操作 */
    handleDelete(row) {
      const ids = row.id || this.ids;
      const dataid = row.businessKey; 
      this.$confirm('是否确认删除流程定义编号为"' + ids + '"的数据项?', "警告", {
        confirmButtonText: "确定",
        cancelButtonText: "取消",
        type: "warning"
      }).then(function() {
        return delDeployment(ids,dataid);
      }).then(() => {
        this.getList();
        this.$message.success("删除成功");
      })
    },
    /** 导出按钮操作 */
    handleExport() {
      const queryParams = this.queryParams;
      this.$confirm('是否确认导出所有流程定义数据项?', "警告", {
        confirmButtonText: "确定",
        cancelButtonText: "取消",
        type: "warning"
      }).then(function() {
        return exportDeployment(queryParams);
      }).then(response => {
        this.download(response.message);
      })
    }
  }
};
</script>

2、后端代码

    2.1  增加一个收回流程功能recallProcess,具体代码如下:

/**
	 *  发起人收回流程
	 *  add by nbacheng
	 *           
	 * @param FlowTaskVo taskVo
	 *           
	 * @return
	 */
	@Override
	@Transactional
	public Result recallProcess(FlowTaskVo flowTaskVo) {
		// 当前任务 listtask
    	List<Task>  listtask = taskService.createTaskQuery().processInstanceId(flowTaskVo.getInstanceId()).active().list();
        if (listtask == null) {
            throw new CustomException("流程未启动或已执行完成,无法收回");
        }
        
    	if (taskService.createTaskQuery().taskId(listtask.get(0).getId()).singleResult().isSuspended()) {
            throw new CustomException("任务处于挂起状态");
        }
    	
    	List<Task> procInsId = taskService.createNativeTaskQuery().sql("select * from ACT_HI_TASKINST where PROC_INST_ID_ = #{procInsId} ORDER BY START_TIME_ desc").parameter("procInsId", flowTaskVo.getInstanceId()).list();
        
    	SysUser loginUser = iFlowThirdService.getLoginUser();
        String processInstanceId = listtask.get(0).getProcessInstanceId();

        //  获取所有历史任务(按创建时间升序)
        List<HistoricTaskInstance> hisTaskList = historyService.createHistoricTaskInstanceQuery()
        .processInstanceId(processInstanceId).orderByTaskCreateTime()
        .asc()
        .list();
        if (CollectionUtil.isEmpty(hisTaskList) || hisTaskList.size() < 2) {
            log.error("当前流程 【{}】 审批节点 【{}】正在初始节点无法收回", processInstanceId, listtask.get(0).getName());
            throw new FlowableException(String.format("当前流程 【%s】 审批节点【%s】正在初始节点无法收回", processInstanceId, listtask.get(0).getName()));
        }

        //  第一个任务
        HistoricTaskInstance startTask = hisTaskList.get(0);
        //若操作用户不是发起人,不能收回
        if(!StringUtils.equalsAnyIgnoreCase(loginUser.getUsername(), startTask.getAssignee())) {
        	throw new CustomException("操作用户不是发起人,不能收回");
        }
        //  当前任务
        HistoricTaskInstance currentTask = hisTaskList.get(hisTaskList.size() - 1);

        BpmnModel bpmnModel = repositoryService.getBpmnModel(listtask.get(0).getProcessDefinitionId());

        //  获取第一个活动节点
        FlowNode startFlowNode = (FlowNode) bpmnModel.getMainProcess().getFlowElement(startTask.getTaskDefinitionKey());
        //  获取当前活动节点
        FlowNode currentFlowNode = (FlowNode) bpmnModel.getMainProcess().getFlowElement(currentTask.getTaskDefinitionKey());

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

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

        //  完成当前任务
        for(Task task : listtask) {
		    taskService.addComment(task.getId(), listtask.get(0).getProcessInstanceId(),FlowComment.RECALL.getType(), "发起人收回");
		    taskService.setAssignee(task.getId(), startTask.getAssignee());
		    taskService.complete(task.getId());
        }
        

        //  重新查询当前任务
        Task nextTask = taskService.createTaskQuery().processInstanceId(processInstanceId).singleResult();
        if (ObjectUtil.isNotNull(nextTask)) {
            taskService.setAssignee(nextTask.getId(), startTask.getAssignee());
            //taskService.complete(nextTask.getId());;//跳过流程发起节点
        }
        
        //自定义业务处理id
        String dataId = flowTaskVo.getDataId();
        
        // 删除运行和历史的节点信息 
        this.deleteActivity(procInsId.get(1).getTaskDefinitionKey(), flowTaskVo.getInstanceId(), dataId);

        //  恢复原始方向
        currentFlowNode.setOutgoingFlows(originalSequenceFlowList);
        
        //自定义业务处理
        if(StrUtil.isNotBlank(flowTaskVo.getDataId()) && !Objects.equals(flowTaskVo.getDataId(), "null")){
            //如果保存数据前未调用必调的FlowCommonService.initActBusiness方法,就会有问题
            FlowMyBusiness business = flowMyBusinessService.getByDataId(dataId);
            //删除自定义业务任务关联表,以便可以重新发起流程
            if (business != null) {
            	flowMyBusinessService.removeById(business);
            }
	   	}
		return Result.OK("发起人收回成功");
	}

   2.2 调用 删除历史节点信息deleteActivity

/**
     * 删除跳转的历史节点信息
     *
     * @param disActivityId     跳转的节点id
     * @param processInstanceId 流程实例id
     * @param dataId   自定义业务id
     */
    protected void deleteActivity(String disActivityId, String processInstanceId, String dataId) {
        List<ActivityInstance> disActivities = flowTaskMapper
                .queryActivityInstance(disActivityId, processInstanceId, null);

        //删除运行时和历史节点信息
        if (CollectionUtils.isNotEmpty(disActivities)) {
            ActivityInstance activityInstance = disActivities.get(0);
            List<ActivityInstance> datas = flowTaskMapper
                    .queryActivityInstance(disActivityId, processInstanceId, activityInstance.getEndTime());

            //datas.remove(0); //保留流程发起节点信息
            List<String> runActivityIds = new ArrayList<>();
            if (CollectionUtils.isNotEmpty(datas)) {
                datas.forEach(ai -> runActivityIds.add(ai.getId()));
                flowTaskMapper.deleteRunActinstsByIds(runActivityIds);
                flowTaskMapper.deleteHisActinstsByIds(runActivityIds);
            }
            if(dataId != null) {//对于自定义业务, 删除所有相关流程信息
            	//flowTaskMapper.deleteAllHisAndRun(processInstanceId);
                //根据流程实例id 删除去ACT_RU_*与ACT_HI_*流程实例数据
                ProcessInstance processInstance = runtimeService.createProcessInstanceQuery().processInstanceId(processInstanceId).singleResult();
                if (null != processInstance) {
                    runtimeService.deleteProcessInstance(processInstanceId, "流程实例删除");
                    historyService.deleteHistoricProcessInstance(processInstanceId);
                }
            }
        }
    }

2.3 FlowTaskMapper.xml文件如下:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.nbcio.modules.flowable.mapper.FlowTaskMapper">
  <select id="queryActivityInstance" resultType="org.flowable.engine.impl.persistence.entity.ActivityInstanceEntityImpl">
        select t.* from
        act_ru_actinst t
       <where>
           <if test="processInstanceId !=null and processInstanceId != ''" >
              t.PROC_INST_ID_=#{processInstanceId} and ACT_TYPE_ = 'userTask' and  END_TIME_ is not null 
           </if>
           
       </where>
         order by t.END_TIME_ ASC

    </select>
    
    <delete id="deleteRunActinstsByIds" parameterType="java.util.List">
        delete from act_ru_actinst where ID_ in
        <foreach item="item" index="index" collection="list" open="(" separator="," close=")">
            #{item}
        </foreach>
    </delete>

    <delete id="deleteHisActinstsByIds" parameterType="java.util.List">
        delete from act_hi_actinst where ID_ in
        <foreach item="item" index="index" collection="list" open="(" separator="," close=")">
            #{item}
        </foreach>
    </delete>
    
    <delete id="deleteAllHisAndRun" parameterType="String">
      delete  from  act_ru_actinst  where proc_inst_id_ = #{processInstanceId}; 
      delete  from  act_ru_identitylink  where proc_inst_id_ = #{processInstanceId};
      delete  from  act_ru_task  where proc_inst_id_ = #{processInstanceId};
      delete  from  act_ru_variable  where proc_inst_id_ = #{processInstanceId};
      delete  from  act_ru_execution  where proc_inst_id_ = #{processInstanceId};
      delete  from  act_hi_actinst where proc_inst_id_ = #{processInstanceId};
      delete  from  act_hi_comment where proc_inst_id_ = #{processInstanceId};
      delete  from  act_hi_identitylink  where proc_inst_id_ = #{processInstanceId};
      delete  from  act_hi_procinst where proc_inst_id_ = #{processInstanceId};
      delete  from  act_hi_taskinst where proc_inst_id_ = #{processInstanceId};
      delete  from  act_hi_varinst  where proc_inst_id_ = #{processInstanceId};
    </delete>
</mapper>

3、自定义业务与其它流程做分别处理

     其它流程直接删除相关用户任务历史信息,保留初始发送,用户可以直接进行流程重新编辑发送。

     而自定义业务则删除所有实例相关的任务历史信息,不保留任务相关信息,同时删除自定义业务发起时候的写入的关联表,以便用户可以再次发起流程。

标签:row,title,flowable,boot,流程,processInstanceId,null,id,jeecg
From: https://blog.51cto.com/u_15070324/8103569

相关文章

  • java实现文件夹上传功能实例代码(SpringBoot框架)
    前言有时我们后台管理等服务可能会有这样一个简单需求,就是根据文件夹将整个文件夹下的所有资源都上传到我们的服务器上,本人也是搜索了大量资料,最终以最简单便捷的方式实现该功能,具体操作步骤如下一、前端如何设置上传组件并将资源上传到后台服务这里的项目框架为若依VUE版本......
  • SpringBoot中,为什么不直接使用一个Service写功能,而是Service接口+ServiceImpl实现类?
    当项目比较简单的时候,需求明确,变更不频繁或者几乎不怎么修改的时候,用第一种就好了当项目比较复杂,需求变更多的时候,用第二种比较好service层=service接口+serviceImpl实现类这种方式好处:1、解耦合2、便于扩展例如:publicinterfaceHumanService{StringgetName();}@Serv......
  • Gradle8.4构建SpringBoot多模块项目
    Gradle8.4构建SpringBoot多模块项目一、基本1、版本这个版本是Jdk8最后一个SpringBoot版本软件版本Gradle8.4SpringBoot2.7.15JDK82、Gradle基本介绍2.1、使用Wrapper方式构建好处:统一gradle的版本好处:不用安装gradle就可以使用Maven也是一样的......
  • 一文详解 springboot 项目启动时异步执行初始化逻辑
    你知道的越多,你不知道的越多点赞再看,养成习惯文章目录前言代码实现定义异步处理工具类实现java线程池新建AppInit实现ApplicationRunner接口完成启动项目时异步数据初始化前言前面的工作中,为了提高地区数据的响应时间,需要加载全国区划数据到redis中缓存起来,这个过程希......
  • Spring Boot 3系列之一(初始化项目)
    近期,JDK21正式发布,而SpringBoot3也推出已有一段时间。作为这两大技术领域的新一代标杆,它们带来了许多令人振奋的新功能和改进。尽管已有不少博客和文章对此进行了介绍,但对于我们这些身处一线的开发人员来说,有些文章和文档可能一看就会,一写就废。因此,为了更深入地理解JDK21和Spr......
  • SpringBoot事件驱动开发
    应用启动过程生命周期事件感知(9大事件)、应用运行中事件感知(无数种)事件发布:ApplicationEventPublisherAware或注入:ApplicationEventMulticaster事件监听:组件+@EventListener场景:当用户登录后,我们需要为用户增加一个积分、随机获取一张优惠券、增加日志等,传统的开发模式......
  • SpringBoot事件和监听器
    事件和监听器生命周期监听场景:监听应用的生命周期监听器-SpringApplicationRunListener自定义SpringApplicationRunListener来监听事件;1.1.编写SpringApplicationRunListener实现类1.2.在META-INF/spring.factories中配置org.springframework.boot.SpringApplication......
  • [Springboot整合thymeleaf]处理js中的路径问题。
    使用了thymeleaf模板引擎之后,html中的标签,都可以直接替换成th:srcth:href但是处理js的中的资源路径并不是像jsp那么简单了。可以通过以下方式解决。<!--处理路径问题--><scriptth:inline="javascript">varpath=[[${#request.contextPath}]]</script><scriptth:inl......
  • u-boot和bootloader到底有什么区别
    嵌入式软件工程师都听说过u-boot和bootloader,但很多工程师依然不知道他们到底是啥。今天就来简单讲讲 u-boot 和 bootloader 的内容以及区别。BootloaderBootloader从字面上来看就是启动加载的意思。用过电脑的都知道,windows开机时会首先加载bios,然后是系统内核,最后启动完毕......
  • 【Java 进阶篇】深入了解 Bootstrap 组件
    Bootstrap是一个流行的前端框架,提供了丰富的组件,用于创建各种网页元素和交互效果。这些组件可以帮助开发者轻松构建漂亮、响应式的网页,而无需深入的前端开发知识。在本文中,我们将深入探讨Bootstrap中一些常用的组件,适合初学者,帮助他们更好地理解和应用这些元素。什么是Bootstra......