导入导出带下拉框模版(EasyExcel)

news2025/7/14 5:31:52

前言

        项目进行到新的一个迭代了,赶了1周需求,接口终于处理完了。分享记录下迭代中处理导入、导出、下载模版功能的细节吧。


一、场景

  1. EasyExcel(阿里)实现Excel数据处理
  2. 三层表头,第二、三层表头动态
  3. 数据根据第二、三层表头动态
  4. 导出的模版动态数据区域,下拉选择
  5. 导入4的模版,导入业务数据
    模版效果:
    在这里插入图片描述

        懂的都懂,没有其他啥好说的,直接上代码吧。

二、使用步骤

1.导出

controller导出方法

/**
     * 导出排班计划
     */
    @ResourceAction(id = "exportShiftPlan", name = "导出排班计划")
    @GetMapping("/exportShiftPlan")
    public void exportShiftPlan(PotAttendShiftPlanQueryRequestVo requestVo, HttpServletResponse response) {
        try {
            // 设置响应头
            response.setContentType("application/vnd.ms-excel");
            response.setCharacterEncoding("utf-8");
            String fileName = URLEncoder.encode(DateUtil.format(DateUtil.date(), DatePattern.PURE_DATETIME_PATTERN) + "排班计划导出" + ".xlsx", StandardCharsets.UTF_8).replaceAll("\\+", "%20");
            response.setHeader("Content-disposition", "attachment;filename=" + fileName);

            //组织导出数据
            Map<String, Object> exportDataMap = service.exportShiftPlan(requestVo);
            List<List<String>> headList = (List<List<String>>) exportDataMap.get("headList");
            List<List<Object>> dataList = (List<List<Object>>) exportDataMap.get("dataList");

            ExcelWriterBuilder excelWriterBuilder = EasyExcel.write(response.getOutputStream());
            excelWriterBuilder.head(headList).sheet("排班计划").doWrite(dataList);

        } catch (Exception e) {
            throw new RuntimeException("导出排班计划失败");
        }

    }

exportShiftPlan方法实现

@Override
    public Map<String, Object> exportShiftPlan(PotAttendShiftPlanQueryRequestVo requestVo) {
        Map<String, Object> dataMap = new HashMap<>();

        //处理动态列头
        List<List<String>> headList = new ArrayList<>();

        //前3列姓名、部门、员工账号
        List<String> xmHeard = new ArrayList<>();
        xmHeard.add("姓名");
        headList.add(xmHeard);

        List<String> bmHeard = new ArrayList<>();
        bmHeard.add("部门");
        headList.add(bmHeard);

        List<String> yghHeard = new ArrayList<>();
        yghHeard.add("员工账号");
        headList.add(yghHeard);

        //时间处理
        Date startTime = requestVo.getStartTime();
        Date endTime = requestVo.getEndTime();
        List<DateTime> rangeDateList = DateUtil.rangeToList(startTime, endTime, DateField.DAY_OF_MONTH);
        for (DateTime dateTime : rangeDateList) {
            List<String> dateHead = new ArrayList<>();
            //日期
            dateHead.add(DateUtil.format(dateTime, DatePattern.NORM_DATE_PATTERN));
            //星期
            int dayOfWeek = DateUtil.dayOfWeek(dateTime);
            String weekName = AttendConstant.weekDict.get(String.valueOf(dayOfWeek)).toString();
            dateHead.add(weekName);

            headList.add(dateHead);
        }
		//列头
        dataMap.put("headList", headList);

       //do your code,循环处理组织一行数据放到list,再放到map
       //动态列部分数据根据列头时间循环处理 
       dataMap.put("dataList", dataList);
       

        return dataMap;
    }

2.下载模版

controller方法

/**
     * 下载排班计划导入模版
     *
     * @param response 响应
     */
    @ResourceAction(id = "exportShiftPlanTemplate", name = "导出排班计划模版")
    @GetMapping("/exportShiftPlanTemplate")
    public void exportShiftPlanTemplate(PotAttendShiftPlanQueryRequestVo requestVo, HttpServletResponse response) {
        try {
            // 设置响应头
            response.setContentType("application/vnd.ms-excel");
            response.setCharacterEncoding("utf-8");

            String fileName = BusinessSerialNoUtil.genDateFmtSeqCode(redisService, "", "", 4, CommonConstant.SHIFT_EXPORT_TEMPLATE, "0", CommonConstant.COMPLET_BEFORE, DatePattern.PURE_DATE_PATTERN) + "排班计划模版.xlsx";

            //防止乱码
            fileName = URLEncoder.encode(fileName, StandardCharsets.UTF_8);

            response.setHeader("Content-disposition", "attachment;filename=" + fileName);

            //组织导出数据
            Map<String, Object> exportDataMap = service.exportShiftPlanTemplate(requestVo);
            List<List<String>> headList = (List<List<String>>) exportDataMap.get("headList");
            List<List<Object>> dataList = (List<List<Object>>) exportDataMap.get("dataList");
            List<PotAttendShiftDTO> shiftList = (List<PotAttendShiftDTO>) exportDataMap.get("shiftList");

            ExcelWriterBuilder excelWriterBuilder = EasyExcel.write(response.getOutputStream());

            //动态设置下拉框
            excelWriterBuilder.registerWriteHandler(new CustomCellWriteHeightStrategy((short) 30, (short) 30));
            excelWriterBuilder.registerWriteHandler(new ShiftSheetWriteHandler(shiftList, headList.size(), dataList.size()));

            ExcelWriterSheetBuilder excelWriterSheetBuilder = excelWriterBuilder.head(headList).sheet("排班计划");

            excelWriterSheetBuilder.doWrite(dataList);

        } catch (Exception e) {
            String errMsg = e.getMessage();
            if (StringUtils.isBlank(errMsg)) {
                errMsg = "导出导入模版失败";
            }
            throw new RuntimeException(errMsg);
        }

    }

        实际上就是在导出的基础上增加了一个样式核心拦截、一个数据处理的handler
CustomCellWriteHeightStrategy
        自定义单元格高度策略


/**
 * 自定义单元格高度策略
 * @author zwmac
 */
public class CustomCellWriteHeightStrategy extends AbstractRowHeightStyleStrategy {
    /**
     * 默认高度
     */
    private static final Integer DEFAULT_HEIGHT = 30;
    /**
     * 头部行高
     */
    private Short headRowHeight = DEFAULT_HEIGHT.shortValue();
    /**
     * 内容行高
     */
    private Short contentRowHeight = DEFAULT_HEIGHT.shortValue();

    public CustomCellWriteHeightStrategy(Short headRowHeight, Short contentRowHeight) {
        this.headRowHeight = headRowHeight;
        this.contentRowHeight = contentRowHeight;
    }

    @Override
    protected void setHeadColumnHeight(Row row, int relativeRowIndex) {
        int maxHeight = autoCalMaxHeight(row);

        //row.setHeight((short) (maxHeight * DEFAULT_HEIGHT));
        row.setHeightInPoints(maxHeight * DEFAULT_HEIGHT);
    }

    @Override
    protected void setContentColumnHeight(Row row, int relativeRowIndex) {
        int maxHeight = autoCalMaxHeight(row);

        //row.setHeight((short) (maxHeight * DEFAULT_HEIGHT));
        row.setHeightInPoints(maxHeight * DEFAULT_HEIGHT);
    }

    private int autoCalMaxHeight(Row row) {
        Iterator<Cell> cellIterator = row.cellIterator();
        if (!cellIterator.hasNext()) {
            return 1;
        }

        // 默认为 1行高度
        int maxHeight = 1;
        while (cellIterator.hasNext()) {
            Cell cell = cellIterator.next();
            switch (cell.getCellType()) {
                case STRING:
                    if (cell.getStringCellValue().contains("\n")) {
                        int length = cell.getStringCellValue().split("\n").length;
                        maxHeight = Math.max(maxHeight, length);
                    }
                    break;
                default:
                    break;
            }
        }
        return maxHeight;
    }
}

ShiftSheetWriteHandler
        下拉框处理,这里用的是隐藏域方式,之前其实分享过另一种处理方式,也提到了这种方式,下拉框处理


/**
 * 排班sheetHandler
 *
 * @author zwmac
 */
public class ShiftSheetWriteHandler implements SheetWriteHandler {

    /**
     * 一行的列数
     */
    private Integer rowCelNum = 0;
    /**
     * 行数
     */
    private Integer rowNum = 0;
    /**
     * 班次数据
     */
    private List<PotAttendShiftDTO> shiftList;

/*
    public ShiftSheetWriteHandler(List<PotAttendShiftDTO> shiftList, int rowCelNum) {
        this.shiftList = shiftList;
        this.rowCelNum = rowCelNum;
    }

    @Override
    public void beforeSheetCreate(WriteWorkbookHolder writeWorkbookHolder, WriteSheetHolder writeSheetHolder) {

    }

    @Override
    public void afterSheetCreate(WriteWorkbookHolder writeWorkbookHolder, WriteSheetHolder writeSheetHolder) {
        //班次转map
        Map<String, String[]> mapDropDown = new HashMap<>();
        String[] shiftTimeArr = new String[shiftList.size()];
        for (int i = 0; i < shiftList.size(); i++) {
            PotAttendShiftDTO shift = shiftList.get(i);
            String shiftName = shift.getShiftName();
            List<PotAttendShiftTime> shiftTimeList = shift.getAttendShiftTimeList();
            StringBuffer sbf = new StringBuffer();
            if (CollectionUtil.isNotEmpty(shiftTimeList)) {
                for (int j = 0; j < shiftTimeList.size(); j++) {
                    PotAttendShiftTime shiftTime = shiftTimeList.get(j);
                    String time = DateUtil.format(shiftTime.getStartTime(), "HH:mm") + "-" + DateUtil.format(shiftTime.getEndTime(),"HH:mm");
                    sbf.append(time);
                    if (j != shiftTimeList.size() - 1) {
                        sbf.append("跳");
                    }
                }
            }
            //长度估计是长了,报异常
            //shiftTimeArr[i] = shiftName + " " + sbf;
            shiftTimeArr[i] = shiftName;

        }
        mapDropDown.put("1", shiftTimeArr);

        //获取工作簿
        Sheet sheet = writeSheetHolder.getSheet();
        ///开始设置下拉框
        DataValidationHelper helper = sheet.getDataValidationHelper();

        //设置下拉框
        for (Map.Entry<String, String[]> entry : mapDropDown.entrySet()) {
            *//*起始行、终止行、起始列、终止列  起始行为1即表示表头不设置**//*
            //这里设置65535可能又问题,因为这个是excel的最大行数,如果数据量超过这个数,就会报错
            CellRangeAddressList addressList = new CellRangeAddressList(2, 65535, 3, rowCelNum - 1);
            *//*设置下拉框数据**//*
            DataValidationConstraint constraint = helper.createExplicitListConstraint(entry.getValue());
            DataValidation dataValidation = helper.createValidation(constraint, addressList);

            //阻止输入非下拉选项的值
            dataValidation.setErrorStyle(DataValidation.ErrorStyle.STOP);
            dataValidation.setShowErrorBox(true);
            dataValidation.setSuppressDropDownArrow(true);
            dataValidation.createErrorBox("提示", "输入值与单元格定义格式不一致");
            dataValidation.createPromptBox("填写说明", "填写内容只能为下拉数据集中的类型");

            sheet.addValidationData(dataValidation);
        }
    }*/


    private Map<Integer, List<String>> selectMap;

    private int index;

    private char[] alphabet = new char[]{'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L',
            'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'};

    public ShiftSheetWriteHandler(List<PotAttendShiftDTO> shiftList, int rowCelNum,Integer rowNum) {
        //转map
        Map<Integer, List<String>> shiftMp = transShiftListToMap(shiftList);
        this.selectMap = shiftMp;
        this.index = 0;
        this.rowCelNum = rowCelNum;
        this.rowNum = rowNum;
    }

    private Map<Integer, List<String>> transShiftListToMap(List<PotAttendShiftDTO> shiftList) {
        Map<Integer, List<String>> mapDropDown = new HashMap<>();
        List<String> shiftTimeArr = new ArrayList<>();
        for (int i = 0; i < shiftList.size(); i++) {
            PotAttendShiftDTO shift = shiftList.get(i);
            String shiftName = shift.getShiftName();
            List<PotAttendShiftTime> shiftTimeList = shift.getAttendShiftTimeList();
            StringBuffer sbf = new StringBuffer();
            if (CollectionUtil.isNotEmpty(shiftTimeList)) {
                for (int j = 0; j < shiftTimeList.size(); j++) {
                    PotAttendShiftTime shiftTime = shiftTimeList.get(j);
                    String time = DateUtil.format(shiftTime.getStartTime(), "HH:mm") + "-" + DateUtil.format(shiftTime.getEndTime(), "HH:mm");
                    sbf.append(time);
                    if (j != shiftTimeList.size() - 1) {
                        sbf.append("跳");
                    }
                }
            }
            String fullName = DictTranslateor.translate(AttendConstant.SHIFT_NAME_DICT, shiftName);
            shiftTimeArr.add(fullName + " " + sbf);

        }
        mapDropDown.put(1, shiftTimeArr);
        return mapDropDown;
    }


    @Override
    public void afterSheetCreate(WriteWorkbookHolder writeWorkbookHolder, WriteSheetHolder writeSheetHolder) {
        if (selectMap == null || selectMap.size() == 0) {
            return;
        }
        // 需要设置下拉框的sheet页
        Sheet curSheet = writeSheetHolder.getSheet();
        DataValidationHelper helper = curSheet.getDataValidationHelper();
        String dictSheetName = "字典sheet";

        Workbook workbook = writeWorkbookHolder.getWorkbook();

        // 数据字典的sheet页
        Sheet dictSheet = workbook.createSheet(dictSheetName);
        // 从第二个工作簿开始隐藏,为了用户的友好性,将字典sheet隐藏掉
        this.index++;
        // 设置隐藏
        workbook.setSheetHidden(this.index, true);
        for (Map.Entry<Integer, List<String>> entry : selectMap.entrySet()) {
            // 设置下拉单元格的首行、末行、首列、末列
            CellRangeAddressList rangeAddressList = new CellRangeAddressList(3, 3 + rowNum - 1, 3, rowCelNum - 1);
            int rowLen = entry.getValue().size();
            // 设置字典sheet页的值 每一列一个字典项
            for (int i = 0; i < rowLen; i++) {
                Row row = dictSheet.getRow(i);
                if (row == null) {
                    row = dictSheet.createRow(i);
                }
                row.createCell(entry.getKey()).setCellValue(entry.getValue().get(i));
            }
            String excelColumn = getExcelColumn(entry.getKey());
            // 下拉框数据来源 eg:字典sheet!$B1:$B2
            String refers = dictSheetName + "!$" + excelColumn + "$1:$" + excelColumn + "$" + rowLen;
            // 创建可被其他单元格引用的名称
            Name name = workbook.createName();
            // 设置名称的名字
            name.setNameName("dict" + entry.getKey());
            // 设置公式
            name.setRefersToFormula(refers);
            // 设置引用约束
            DataValidationConstraint constraint = helper.createFormulaListConstraint("dict" + entry.getKey());
            // 设置约束
            DataValidation validation = helper.createValidation(constraint, rangeAddressList);
            if (validation instanceof HSSFDataValidation) {
                validation.setSuppressDropDownArrow(false);
            } else {
                validation.setSuppressDropDownArrow(true);
                validation.setShowErrorBox(true);
            }
            // 阻止输入非下拉框的值
            validation.setErrorStyle(DataValidation.ErrorStyle.STOP);
            validation.createErrorBox("提示", "此值与单元格定义格式不一致!");
            validation.setShowErrorBox(true);
            validation.setSuppressDropDownArrow(true);
            validation.createPromptBox("填写说明", "填写内容只能为下拉数据集中的类型");
            // 添加下拉框约束
            writeSheetHolder.getSheet().addValidationData(validation);
        }


    }

    /**
     * 将数字列转化成为字母列
     *
     * @param num
     * @return
     */
    private String getExcelColumn(int num) {
        String column = "";
        int len = alphabet.length - 1;
        int first = num / len;
        int second = num % len;
        if (num <= len) {
            column = alphabet[num] + "";
        } else {
            column = alphabet[first - 1] + "";
            if (second == 0) {
                column = column + alphabet[len] + "";
            } else {
                column = column + alphabet[second - 1] + "";
            }
        }
        return column;

    }
}

        service实现部分就不贴了,跟导出的思路一致,多了一个下拉框数据shiftList而已。

3、导入

controller方法

/**
     * 导入排班计划2
     *
     * @param file 文件
     */
    @OperateLog(moduleName = moduleName, operateContent = "导入排班计划")
    @RequestMapping(value = "/importShiftPlan/{parkId}", method = RequestMethod.POST)
    public RestResponse importShiftPlan2(@RequestParam("file") MultipartFile file, @PathVariable("parkId") Long parkId) {
        try {
            service.importShiftPlan(file, parkId);
            return RestResponse.success();
        } catch (Exception e) {
            log.error("导入排班计划失败", e);
            throw new RuntimeException("导入排班计划失败");
        }
    }

importShiftPlan方法实现


    @Override
    public void importShiftPlan(MultipartFile file, Long parkId) throws IOException {
        //参数校验
        Assert.notNull(file, "文件为空");
        Assert.notNull(parkId, "园区id为空");

        //解析文件
        //EasyExcel.read(file.getInputStream(), AttendShiftPlanExcelDto.class, new AttendShiftPlanListener()).sheet(0).doRead();
        //List<Map<Integer,Object>> list = EasyExcel.read(file.getInputStream(), AttendShiftPlanExcelDto.class, new AttendShiftPlanListener()).sheet(0).doReadSync();
        //headRowNumber设置表示设置的行与之前的行都是列头,监听里的数据处理就从这个行之后开始读
        AttendShiftPlanListener attendShiftPlanListener = new AttendShiftPlanListener();
        List<Map<Integer, Object>> list = EasyExcel.read(file.getInputStream(), attendShiftPlanListener).sheet(0).headRowNumber(3).doReadSync();
        List<String> headList = attendShiftPlanListener.getHeaders();
        Assert.notEmpty(list, "导入数据为空");
        Assert.notEmpty(headList, "表头信息为空");
		//do your code
		//有表头了,有数据了,剩下的就是大家表演了

    }

AttendShiftPlanListener
        监听处理数据,其实保存数据可以在监听里就处理了。

/**
 * 排班计划导入监听器
 *
 * @author zwmac
 */
@Slf4j
public class AttendShiftPlanListener extends AnalysisEventListener<Map<Integer, String>> {

    private static final int BATCH_COUNT = 5;

    private List<AttendShiftPlanExcelDto> cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);

    @Getter
    private List<String> headers = new ArrayList<>();

    // 存储自定义表头
    Map<Integer, String> customHeaders = new HashMap<>();

    @Override
    public void onException(Exception exception, AnalysisContext context) throws Exception {
        super.onException(exception, context);
    }

    @Override
    public void invokeHead(Map<Integer, ReadCellData<?>> headMap, AnalysisContext context) {
    //列头处理
        Integer rowIndex = context.readRowHolder().getRowIndex();
        if(rowIndex == 1) {
            for (int i = 3; i < headMap.size(); i++) {
                String header = headMap.get(i).getStringValue();
                if (!"null".equals(header) && !isFixedHeader(header)) {
                    customHeaders.put(i, header);
                }
            }
            Map<Integer, String> hdMap = ConverterUtils.convertToStringMap(headMap, context);
            for (int i = 0; i < hdMap.size(); i++) {
                headers.add(hdMap.get(i));
            }
        }

    }

    /**
     * 判断是否是固定表头
     */
    private boolean isFixedHeader(String header) {
        //定义固定表头集合
        List<String> list = Arrays.asList("姓名", "部门", "账号");
        return list.contains(header);
    }

    @Override
    public void invoke(Map<Integer, String> data, AnalysisContext context) {
    //数据处理
        AttendShiftPlanExcelDto shiftPlanExcelDto = new AttendShiftPlanExcelDto();
        StringBuilder sb = new StringBuilder();
        shiftPlanExcelDto.setUserName(data.get(0));
        shiftPlanExcelDto.setOrgName(data.get(1));
        shiftPlanExcelDto.setUserAccount(data.get(2));

        JSONObject jsonObject = new JSONObject();
        for (int i = 0; i < data.size() - 1; i++) {
            jsonObject.put(customHeaders.get(i), data.get(i));
        }
        shiftPlanExcelDto.setShiftPlanDataJson(jsonObject.toJSONString());

        cachedDataList.add(shiftPlanExcelDto);
        if (cachedDataList.size() >= BATCH_COUNT) {
            //saveData();其实这里就可以直接处理保存
            cachedDataList.clear();
        } else {
            log.info("解析到一条数据:{}", data);
        }
    }

    @Override
    public void doAfterAllAnalysed(AnalysisContext context) {

    }
}

AttendShiftPlanExcelDto


import com.alibaba.excel.annotation.ExcelIgnore;
import com.alibaba.excel.annotation.ExcelProperty;
import lombok.Data;

/**
 * @author zwmac
 */
@Data
public class AttendShiftPlanExcelDto {

    @ExcelProperty(value = "姓名", index = 0)
    private String userName;

    @ExcelProperty(value = "部门", index = 1)
    private String orgName;

    @ExcelProperty(value = "账号", index = 2)
    private String userAccount;

    /**
     * 动态数据列
     */
    @ExcelIgnore
    private String shiftPlanDataJson;
}

        前3列是固定的,后面是动态,当成一个字段,实际是json串


总结

  • EasyExcel阿里系的还是挺强,目前我们的微服务也是阿里系的(其实我个人不太喜欢阿里系的微服务)
  • 导出可以使用模版,这里横向、纵向都是动态,感觉太麻烦,还不如编码
  • 其实主要一点还是要自己有大致的思路,自己要敢想
            还是那句话,思路很重要,就分享到这里,希望能帮到大家!uping!

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

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

相关文章

WebMvcConfigurer配置不当导致鉴权失败

最近同事说他们有个新需求&#xff0c;需要对接口进行加解密&#xff0c;所以他给项目配置了一个拦截器&#xff0c;但这个拦截器直接导致了每个接口鉴权失败&#xff0c;每次调用接口都是提示没有session信息。 公司内的所有java项目是公用同一套基础依赖&#xff0c;所以我也…

模具保护器 具体应用在哪些场所

模具监视器&#xff0c;被誉为模具的忠诚守护者&#xff0c;其应用领域遍布各类生产型设备&#xff0c;宛如一道坚实的防线&#xff0c;捍卫着模具的安全与生产的高效。以下是模具监视器在各领域所展现的卓越风采及其非凡功能&#xff1a; 在生产型设备的广阔天地里&#xff0c…

springboot美食菜谱分享平台优化版(源码+sql+论文报告)

绪论 1.1 研究意义 当今社会作为一个飞速的发展社会&#xff0c;网络已经完全渗入人们的生活&#xff0c; 网络信息已成为传播的第一大媒介&#xff0c; 可以毫不夸张说网络资源获取已逐步改变了人们以前的生活方式&#xff0c;网络已成为人们日常&#xff0c;休闲主要工具。…

加密软件有哪些优点?除了对文件加密这几点也很重要

加密软件用于保护数据安全&#xff0c;防止未经授权访问、数据泄露。通过使用加密算法来实现对数据的加密处理&#xff0c;确保数据在传输、存储过程中的机密性与完整性。 加密软件有哪些优点&#xff1f; 1、灵活控制&#xff1a;允许更灵活地配制加密控制条件&#xff0c;满…

夏季河湖防溺水新举措:青犀AI视频智能监控系统保障水域安全

近日一则新闻引起大众关注&#xff0c;有网友发布视频称&#xff0c;假期在逛西湖时&#xff0c;发现水面上“平躺”漂浮着一名游客在等待救援。在事发3分钟内&#xff0c;沿湖救生员成功将落水游客救到了岸边。 随着夏季的到来&#xff0c;雨水增多&#xff0c;各危险水域水位…

Java内存模型,堆、栈和方法区的区别

Java内存管理是Java虚拟机&#xff08;JVM&#xff09;技术的核心之一。了解Java内存管理对于提高程序性能、解决内存泄漏和优化资源利用至关重要。 一、Java内存模型&#xff08;Java Memory Model, JMM&#xff09; Java内存模型描述了Java程序中变量&#xff08;包括实例字…

Flink作业执行之 3.StreamGraph

Flink任务如何跑起来之 3.StreamGraph 1. StreamGraphGenerator 在前文了解Transformation和StreamOperator后。接下来Transformation将转换成StreamGraph&#xff0c;即作业的逻辑拓扑结构。 在env.execute()方法中调用getStreamGraph方法生成StreamGraph实例。StreamGraph…

Electron无感打印 静默打印(vue3 + ts + vite)

&#xff08;electron vue3 项目搭建部分 自行查找其他资源 本文只讲解Electronvue3 如何实现静默打印&#xff09; 第一步获取打印机资源 渲染端代码&#xff08;vue里面&#xff09; // 因使用了vite所以在浏览器中打开 require会报错 只能在electron中 const { ipcRender…

JS实现文字溢出隐藏效果

需求场景 由于项目原因&#xff0c;经常需要使用到canvas来将dom生成为图片供用户保存&#xff0c;但canvas的css属性&#xff08;例如本文实现的文字溢出隐藏效果&#xff09;支持并不全面&#xff0c;所有有些功能只能用JS来实现了 实现思路 用JS循环判断填充文本后的元素…

Ollama在windows上的设置

下载 Download Ollama on macOS 安装&#xff1a;是不可以选择安装路径&#xff0c;系统自动运行&#xff0c;不启动模型不占用GPU 参数设置&#xff1a;windows添加环境变量&#xff08;需要重启ollama&#xff09; 修改模型位置&#xff1a;添加 OLLAMA_MODELS D:\LLM\Oll…

【C++】模板及模板的特化

目录 一&#xff0c;模板 1&#xff0c;函数模板 什么是函数模板 函数模板原理 函数模板的实例化 推演(隐式)实例化 显示实例化 模板的参数的匹配原则 2&#xff0c;类模板 什么是类模板 类模板的实例化 二&#xff0c;模板的特化 1&#xff0c;类模板的特化 全特化…

基于uni-app与图鸟UI打造的各领域移动端模板大赏

随着移动互联网的迅猛发展&#xff0c;各类移动端应用层出不穷&#xff0c;为了帮助企业快速搭建高效、美观的移动平台&#xff0c;我们基于强大的uni-app与图鸟UI&#xff0c;精心打造了不下于40套覆盖多个领域的移动端模板。今天&#xff0c;就让我们一起领略这些模板的风采吧…

django-vue-admin 本地部署

一、项目地址 主分支&#xff1a;master&#xff08;稳定版本&#xff09; 开发分支&#xff1a;develop django-vue3-admin-masterhttps://gitee.com/huge-dream/django-vue3-admin 注意&#xff1a;下载master分支zip代码包&#xff0c;解压后删掉web\src\views\syst…

185.二叉树:二叉搜索树的最近公共祖先(力扣)

代码解决 /*** Definition for a binary tree node.* struct TreeNode {* int val;* TreeNode *left;* TreeNode *right;* TreeNode(int x) : val(x), left(NULL), right(NULL) {}* };*/class Solution { public:// 函数用于寻找二叉搜索树中节点 p 和 q 的最低…

小程序外卖开发中的关键技术与实现方法

小程序外卖服务凭借其便捷性和灵活性&#xff0c;正成为现代餐饮行业的重要组成部分。开发一个功能完善的小程序外卖系统&#xff0c;需要掌握一系列关键技术和实现方法。本文将介绍小程序外卖开发中的核心技术&#xff0c;并提供具体的代码示例&#xff0c;帮助开发者理解和实…

慎投!4区SCISSCI有停刊风险!网传的水刊之王解析大全,真的好投吗?

本周投稿推荐 SSCI • 中科院2区&#xff0c;6.0-7.0&#xff08;录用友好&#xff09; EI • 各领域沾边均可&#xff08;2天录用&#xff09; CNKI • 7天录用-检索&#xff08;急录友好&#xff09; SCI&EI • 4区生物医学类&#xff0c;0.5-1.0&#xff08;录用…

JDBC操作数据库的方法

目录 一、JDBC介绍 二、使用方法&#xff08;以MySQL为例&#xff09; &#xff08;1&#xff09;MySQL的jar包&#xff0c;导入到IDEA &#xff08;2&#xff09;使用代码&#xff0c;操作数据库 1&#xff09;设置数据源 1.创建MysqlDataSource对象&#xff0c;使用set…

【Unity每日一记】FairyGUI为什么能自动生成代码,它的好处是什么

&#x1f468;‍&#x1f4bb;个人主页&#xff1a;元宇宙-秩沅 &#x1f468;‍&#x1f4bb; hallo 欢迎 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! &#x1f468;‍&#x1f4bb; 本文由 秩沅 原创 &#x1f468;‍&#x1f4bb; 收录于专栏&#xff1a;uni…

GPT办公与科研应用、论文撰写、数据分析、机器学习、深度学习及AI绘图高级应用

原文链接&#xff1a;GPT办公与科研应用、论文撰写、数据分析、机器学习、深度学习及AI绘图高级应用https://mp.weixin.qq.com/s?__bizMzUzNTczMDMxMg&mid2247606667&idx3&sn2c5be84dfcd62d748f77b10a731d809d&chksmfa82606ccdf5e97ad1a2a86662c75794033d8e2e…

mmap引起的内存泄漏分析

最近遇到一个内存泄漏问题&#xff0c;由于问题出现在客户端&#xff0c;只能通过客户提供的Log来分析。 根据客户提供的/proc/meminfo数据发现&#xff0c;MemAvailable 由294072kB减小至18128kB&#xff0c;减小约269MB&#xff0c;引起该变化的最直接原因是PageTables由614…