分享一个POI封装的Excel解析工具

前言:

本来我已经很久没做java的项目了,最近手头的项目没啥事又被拉过去搞java了,但是看到这帮人写的代码,心凉了一截,写一个Excel的导入写的 都有很多问题,
写个示范吧:

ExcelUtil util =new ExcelUtil();
util.import(xxx);

看似没啥问题,也不知到搭建项目的是哪个“脑瘫”,这么写如果是多sheet页 每个sheet映射的Entity不一致这个就用不了,因为这个工具栏上面就用了泛型限制,在使用解析方法时,始终是用某一个Entity结构,根本对应不是其他的实体,然后“聪明的这帮人”就有几个sheet页就new几个ExcelUtil,然后我哭了,创建了几次ExcelUtil,这个方法就调用了WorkbookFactory.create(inputStream);这个文件流创建n次的WorkBook,这么搞 内存不搞爆了。
还有就是在文件类型和excel类型一致才设置值,这个脑残啊,你是把问题给“包住了”。一但发现数据少了,从哪里去导呢,要是跟其他数据绑定了 怎么办?
所以写这个我也是没办法,项目有这么个前行者。可能很多开发者说 啥时代了 为啥不用EasyExcel或EasyPoi,中国的系统 大家懂的 都懂 一个excel导入 都能玩出花,一会要合并单元 一会要取颜色标记的哪些值。所以只能用POI。

需要的原料

Excel映射的主要注解类


import com.jysoft.common.utils.poi.ExcelHandlerAdapter;
import org.apache.poi.ss.usermodel.HorizontalAlignment;
import org.apache.poi.ss.usermodel.IndexedColors;
import java.lang.annotation.*;
import java.math.BigDecimal;/*** 自定义导出Excel数据注解* @author 程序员ken*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
@Repeatable(Excel.List.class)
public @interface Excel
{    /*** excel对应表头名称*/public String name() default "";/*** 当值为空时,字段的默认值*/public String defaultValue() default "";@Target(ElementType.FIELD)@Retention(RetentionPolicy.RUNTIME)@Documentedpublic @interface List {Excel[] value();}
}

校验的注解类


import java.lang.annotation.*;@Repeatable(Verify.List.class)
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Verify {/// <summary>///错误信息提示 仅(正则)格式匹配的提示/// </summary>String errorMsg() default "";/*** 是否可以为空* @return*/boolean nullable() default false;/// <summary>/// 文本最大长度 默认是99999/// </summary>int maxLength() default 99999;/// <summary>/// 内容格式校验 默认为空/// </summary>String patternReg() default "";/// <summary>///业务区分 or 业务名称/// </summary>String[] businessDiff() default {};@Target(ElementType.FIELD)@Retention(RetentionPolicy.RUNTIME)public @interface List {Verify[] value();}
}
public class ReflectUtils
{//本来也想分享出来  感觉项目带的很糟糕 还携带了很多项目信息 就不分享了 网上也能找到很多反射相关的工具类
}

校验结果

public class VerifyResult<E> {private  List<E> records;private  String errorInfo;private  boolean existError;private  boolean isNull;private  String sheetName;public VerifyResult(List<E> records, String errorInfo) {this.records = records;this.errorInfo = errorInfo;this.existError = StringUtils.isNotEmpty(errorInfo);}public VerifyResult(List<E> records, String errorInfo,String sheetName) {this.records = records;this.errorInfo = errorInfo;this.existError = StringUtils.isNotEmpty(errorInfo);this.sheetName = sheetName;}public List<E> getRecords() {return records;}public void setRecords(List<E> records) {this.records = records;}public String getErrorInfo() {return errorInfo;}public void setErrorInfo(String errorInfo) {this.errorInfo = errorInfo;}public boolean getIsNull() {this.isNull = ObjectUtil.isEmpty(records);return isNull;}public void setIsNull(boolean isNull) {this.isNull = isNull;}public boolean getExistError() {if(ObjectUtil.isEmpty(records)){this.errorInfo = "导入的excel里数据为空";this.isNull = true;return true;}return existError;}public void setExistError(boolean existError) {this.existError = existError;}public String getSheetName() {return sheetName;}public void setSheetName(String sheetName) {this.sheetName = sheetName;}}

读取解析工具类

import cn.hutool.core.util.ObjectUtil;
import xxxxx.annotation.Excel;
import xxxxx.annotation.Verify;
import xxxxx.funnctions.BiFFunction;
import xxxxx.utils.DateUtils;
import xxxxx.utils.StringUtils;
import xxxxx.utils.reflect.ReflectUtils;
import xxxxx.vo.EntityExcelVo;
import xxxxx.vo.VerifyResult;
import org.apache.commons.collections.map.HashedMap;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.ss.util.CellRangeAddress;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.math.BigDecimal;
import java.text.DecimalFormat;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.*;
import java.util.function.BiFunction;
import java.util.function.Function;/*** Excel相关处理--仅用导入*/
public class ExcelReadUtil {private static final Logger log = LoggerFactory.getLogger(ExcelImportUtil.class);/*** 工作薄对象*/private Workbook wb;/*** 字段 转化映射表*/private static Map<Class<?>, Function<String,Object>> convertValMap =new HashMap();/*** 需要排除列属性*/public String[] excludeFields;/*** 加入集合前 前置回调处理 ==><T,Row,Boolean>*/private BiFunction beforeAddFunc = null;static {convertValMap.put(String.class, String::valueOf);convertValMap.put(Integer.class, Integer::parseInt);convertValMap.put(int.class, Integer::parseInt);convertValMap.put(Float.class, Float::parseFloat);convertValMap.put(float.class, Float::parseFloat);convertValMap.put(Short.class, Short::parseShort);convertValMap.put(short.class, Short::parseShort);convertValMap.put(Long.class, Long::parseLong);convertValMap.put(long.class, Long::parseLong);convertValMap.put(BigDecimal.class, BigDecimal::new);convertValMap.put(Boolean.class, Boolean::parseBoolean);convertValMap.put(boolean.class, Boolean::parseBoolean);convertValMap.put(Date.class, DateUtils::parseDate);convertValMap.put(LocalDate.class, LocalDate::parse);}/*** 初始化 workbook** @param is* @throws IOException*/public ExcelImportUtil(InputStream is) throws IOException {this.wb = WorkbookFactory.create(is);}/*** @param beforeAddFunc<T,Row,Boolean> T 当前数据 Row 当前行 Boolean是否加入集合* @param <classz> 为了让编译器知道当前操作的对象是哪个* @param <T>*public <T> void setBeforeAddFunc(BiFunction<T, Row, Boolean> beforeAddFunc,Class<T> classz) {this.beforeAddFunc = beforeAddFunc;}/*** 读取所有sheet** @param titleNum     标题列位置* @param businessDiff 业务区分 用于校验 如果 没有区分 填null* @param clazz* @param <T>* @return* @throws Exception*/public <T> VerifyResult<T> importExcelAll(int titleNum, String businessDiff, Class<T> clazz) throws Exception {return importExcel(titleNum, businessDiff, null, clazz);}/*** 获取sheet总数** @return* @throws Exception*/public int getSheetTotal() throws Exception {return this.wb.getNumberOfSheets();}/*** @param titleNum     标题列位置* @param businessDiff 业务区分 用于校验 如果 没有区分 填null* @param clazz* @param <T>* @throws Exception*/public <T> VerifyResult<T> importExcelBySheetIndex(int titleNum, String businessDiff, int sheetIndex, Class<T> clazz) throws Exception {String sheetName = this.wb.getSheetName(sheetIndex);VerifyResult<T> tVerifyResult = importExcel(titleNum, businessDiff, sheetName, clazz);tVerifyResult.setSheetName(sheetName);return tVerifyResult;}/*** 对excel表单指定表格索引名转换成list** @param titleNum     标题占用行数* @param businessDiff 业务校验区分 可以为null* @return 转换后集合*/public <T> VerifyResult<T> importExcel(int titleNum, String businessDiff, String parseSheetName, Class<T> clazz) throws Exception {List<T> list = new ArrayList<T>();Map<String, PictureData> pictures = new HashMap<>();Iterator<Sheet> sheetIterator = wb.sheetIterator();if (!sheetIterator.hasNext()) {throw new IOException("文件sheet页(" + parseSheetName + ")不存在");}//仅操作一次Map<String, EntityExcelVo> entityExcelMap = getEntityExcelMap(businessDiff, clazz);Map<Integer, EntityExcelVo> entityExcelPointMap = null;Set<String> strings = entityExcelMap.keySet();//行错误信息StringBuffer rowErrorSbf = new StringBuffer();StringBuffer errorSbf = new StringBuffer();//sheetName+行号Map<String, StringBuffer> errorRow = new HashedMap();//当前sheet 的"合并"列信息Map<Integer, String> curSheetMergeMap = null;Sheet curSheet;String sheetName;int rows;while (sheetIterator.hasNext()) {//当前sheetcurSheet = sheetIterator.next();sheetName = curSheet.getSheetName();curSheetMergeMap = getCurSheetMergeMap(curSheet);entityExcelPointMap = new HashedMap();//如果parseSheetName不为空则解析所有sheetif (parseSheetName != null && !sheetName.equals(parseSheetName)) {continue;}// 获取最后一个非空行的行下标,比如总行数为n,则返回的为n-1rows = curSheet.getLastRowNum();if (rows > 0) {// 定义一个map用于存放excel列的序号和field.Map<String, Integer> cellMap = new HashMap<String, Integer>();// 获取表头Row heard = curSheet.getRow(titleNum);for (int i = 0; i < heard.getPhysicalNumberOfCells(); i++) {Cell cell = heard.getCell(i);if (StringUtils.isNotNull(cell)) {String value = this.getCellValue(heard, i).toString();cellMap.put(value.replaceAll("\n", "").trim(), i);} else {cellMap.put(null, i);}}for (String title : strings) {Integer column = cellMap.get(title);if (column != null) {entityExcelPointMap.put(column, entityExcelMap.get(title));}}int num = titleNum + 1;for (int i = num; i <= rows; i++) {// 从第2行开始取数据,默认第一行是表头.Row row = curSheet.getRow(i);// 判断当前行是否是空行if (isRowEmpty(row)) {continue;}T entity = null;for (Map.Entry<Integer, EntityExcelVo> entry : entityExcelPointMap.entrySet()) {EntityExcelVo excelVo = entry.getValue();// 从map中得到对应列的field.Field field = excelVo.getCurField();Excel attr = excelVo.getExcel();Verify verify = excelVo.getVerify();Object val = this.getCellValue(row, entry.getKey());Optional<T> first = list.stream().findFirst();//是空 且行匹配(合并单元格)if ((val == null || StringUtils.isEmpty(val.toString())) && curSheetMergeMap.containsKey(entry.getKey())&& (list.stream().findAny().isPresent()) &&  {val = ReflectUtils.invokeGetter(list.get(list.size()-1), field.getName());					}// 如果不存在实例则新建.entity = (entity == null ? (T) clazz.newInstance() : entity);// 取得类型,并根据对象类型设置值.Class<?> fieldType = field.getType();if(val!=null && convertValMap.containsKey(fieldType)){val = convertValMap.get(fieldType).apply(val.toString());}String propertyName = field.getName();if (verify != null) {String prefix = "sheetName:" + curSheet.getSheetName() + ",";if (!verify.nullable() && ObjectUtil.isEmpty(val)) {appendIfAbsent(errorRow, sheetName + i, String.format("%s第%d行,%s不能为空\n", prefix, i + 1, attr.name()));} else if (val != null && val instanceof String && ((String) val).length() > verify.maxLength()) {appendIfAbsent(errorRow, sheetName + i, String.format("%s第%d行,%s不得超过%d个字符\n", prefix, i + 1, attr.name(), verify.maxLength()));} else if (val != null && StringUtils.isNotEmpty(verify.patternReg()) && String.valueOf(val).matches(verify.patternReg())) {appendIfAbsent(errorRow, sheetName + i, String.format("%s第%d行,%s%s", prefix, i + 1, attr.name(),StringUtils.isNotEmpty(verify.errorMsg()) ? "格式错误\n" : verify.errorMsg()));}}ReflectUtils.invokeSetter(entity, propertyName, val);}//加入集合前的回调if (this.beforeAddFunc != null) {//返回false则不加入集合(list)if (!((BiFunction<T, Row, Boolean>) this.beforeAddFunc).apply(entity, row)) {errorRow.remove(sheetName + i);continue;}}if (!errorRow.isEmpty()) {errorRow.entrySet().stream().forEach(key -> {if (errorRow.get(key) != null) {errorSbf.append(errorRow.get(key));}});}list.add(entity);}}}return new VerifyResult(list, errorSbf.toString());}/*** 追加内容** @param errorRow* @param sheetRowTxt 所在行* @param msg         错误消息*/private void appendIfAbsent(Map<String, StringBuffer> errorRow, String sheetRowTxt, String msg) {if (!errorRow.containsKey(sheetRowTxt)) {errorRow.put(sheetRowTxt, new StringBuffer());}errorRow.get(sheetRowTxt).append(msg);}/*** 获取字段注解信息*/public Map<String, EntityExcelVo> getEntityExcelMap(String businessId, Class<?> clazz) {Map<String, EntityExcelVo> map = new HashMap<>();List<Field> tempFields = new ArrayList<>();tempFields.addAll(Arrays.asList(clazz.getSuperclass().getDeclaredFields()));tempFields.addAll(Arrays.asList(clazz.getDeclaredFields()));//Excel attr =null;Verify[] verifys = null;Optional<Verify> first = null;EntityExcelVo entityExcelVo = null;Excel[] repeatExcels = null;for (Field field : tempFields) {if (!ArrayUtils.contains(this.excludeFields, field.getName())) {//多注解 校验repeatExcels = field.getAnnotationsByType(Excel.class);if (ObjectUtil.isNotEmpty(repeatExcels)) {field.setAccessible(true);for (Excel repeatExcel : repeatExcels) {entityExcelVo = new EntityExcelVo();entityExcelVo.setExcel(repeatExcel);entityExcelVo.setCurField(field);map.putIfAbsent(repeatExcel.name(), entityExcelVo);//如果有校验规则 添加上verifys = field.getAnnotationsByType(Verify.class);if (verifys != null && verifys.length > 0) {first = Arrays.stream(verifys).filter(p -> businessId == null || p.businessDiff() == null ||Arrays.stream(p.businessDiff()).filter(o -> businessId.equals(o)).count() > 0).findAny();map.get(repeatExcel.name()).setVerify(first.get());}}}}}return map;}/*** 获取单元格值** @param row    获取的行* @param column 获取单元格列号* @return 单元格值*/public Object getCellValue(Row row, int column) {if (row == null) {return row;}Object val = "";try {Cell cell = row.getCell(column);if (StringUtils.isNotNull(cell)) {if (cell.getCellType() == CellType.NUMERIC || cell.getCellType() == CellType.FORMULA) {val = cell.getNumericCellValue();if (DateUtil.isCellDateFormatted(cell)) {val = DateUtil.getJavaDate((Double) val); // POI Excel 日期格式转换} else {if ((Double) val % 1 != 0) {val = new BigDecimal(val.toString());} else {val = new DecimalFormat("0").format(val);}}} else if (cell.getCellType() == CellType.STRING) {val = cell.getStringCellValue();} else if (cell.getCellType() == CellType.BOOLEAN) {val = cell.getBooleanCellValue();} else if (cell.getCellType() == CellType.ERROR) {val = cell.getErrorCellValue();}}} catch (Exception e) {return val;}return val;}/*** 判断是否是空行** @param row 判断的行* @return*/private boolean isRowEmpty(Row row) {if (row == null) {return true;}for (int i = row.getFirstCellNum(); i < row.getLastCellNum(); i++) {Cell cell = row.getCell(i);if (cell != null && cell.getCellType() != CellType.BLANK) {return false;}}return true;}/*** 格式化不同类型的日期对象** @param dateFormat 日期格式* @param val        被格式化的日期对象* @return 格式化后的日期字符*/public String parseDateToStr(String dateFormat, Object val) {if (val == null) {return "";}String str;if (val instanceof Date) {str = DateUtils.parseDateToStr(dateFormat, (Date) val);} else if (val instanceof LocalDateTime) {str = DateUtils.parseDateToStr(dateFormat, DateUtils.toDate((LocalDateTime) val));} else if (val instanceof LocalDate) {str = DateUtils.parseDateToStr(dateFormat, DateUtils.toDate((LocalDate) val));} else {str = val.toString();}return str;}/*** 获取当前sheet单元格合并信息 ==>仅记录列的范围** @param sheet*/public Map<Integer, String> getCurSheetMergeMap(Sheet sheet) {// 获取所有的合并区域List<CellRangeAddress> mergedRegions = sheet.getMergedRegions();Map<Integer, String> map = new HashedMap();for (CellRangeAddress mergedRegion : mergedRegions) {for (int i = mergedRegion.getFirstColumn(); i <= mergedRegion.getLastColumn(); i++) {map.put(i, null);}}return map;}/*** 关闭workbook  (不用也会自动关闭流)*/public void close() {if (this.wb != null) {try {this.wb.close();} catch (IOException e) {e.printStackTrace();}}}}

在这个里面我加了个setBeforeAddFunc方法,其实是函数式接口,就是将当前的Entity加入集合前的操作,如果返回false则不加入集合。作用是对数据格式做一些处理。

使用方法

//伪代码 这边弄一个简单Book类  在
public class Book{private Long id;@Excel(name = "作者")@Verify(maxLength = 120)private String author;@Excel(name = "价格")@Verify(patternReg = "^\\d{2,10}(.)?\\d{2,10}")private BigDecimal price;@Excel(name = "发布时间")@Excel(name = "出版时间")private Date pushTime;
}

上面author校验了字符长度,price使用了正则 现在是数字(可以小数,当然这个写的不是很精准)
,在pushTime上使用两个@Excel 不过name的值不一样 意味着不同模板中表头是“发布时间”或"出版时间"都映射的是pushTime。

public static void main(String[] args) throws Exception {ExcelReadUtil util = new ExcelReadUtil (Files.newInputStream(Paths.get("D:\\desktop-data\\book.xlsx")));VerifyResult<Book> verifyResult = util.importExcelAll(1, null,Book.class);}

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

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

相关文章

【极数系列】Flink配置参数如何获取?(06)

文章目录 gitee码云地址简介概述01 配置值来自.properties文件1.通过路径读取2.通过文件流读取3.通过IO流读取 02 配置值来自命令行03 配置来自系统属性04 注册以及使用全局变量05 Flink获取参数值Demo1.项目结构2.pom.xml文件如下3.配置文件4.项目主类5.运行查看相关日志 gite…

sqli-labs第一关

1.判断是否存在注入&#xff0c;注入是字符型还是数字型? ?id1 and 11 ?id1 and 12 因为输入and 11与and 12 回显正常&#xff0c;所以该地方不是数字型。 ?id1 ?id1-- 输入单引号后报错&#xff0c;在单引号后添加--恢复正常&#xff0c;说明存在字符注入 2.猜解SQL查…

【物联网】物联网技术的起源、发展、重点技术、应用场景与未来演进

物联网技术的起源、发展、重点技术、应用场景与未来演进 物联网&#xff08;IoT, Internet of Things&#xff09;是近年来科技领域中的热门话题&#xff0c;它将物理世界的各种“事物”与互联网连接起来&#xff0c;从而实现了数据的交换和通信。物联网技术的起源可追溯到20世…

【新书推荐】3.7 数据类型转换

本节必须掌握的知识点&#xff1a; 整型提升 浮点型和整型转换 浮点型转换 普通算术类型转换 示例十二 在实际项目应用过程中&#xff0c;我们通常会根据实际需要&#xff0c;对数据进行扩展和截取&#xff0c;我们称之为数据类型转换。对数据类型的转换需要遵循以下规则。 3.7…

毕业论文格式

官方格式 编号格式&#xff1a; 【论文标题设置】论文一二三级标题设置_哔哩哔哩_bilibili 编号和文字的间距太大怎么办&#xff1f;两招轻松解决&#xff01;

倒计时80天

1.J-兔子不会种树_浙江机电职业技术学院第八届新生亮相赛&#xff08;同步赛&#xff09; (nowcoder.com) /****** __----~~~~~~~~~~~------___* . . ~~//...... __--~ ~~…

前端——JavaScript

目录 文章目录 前言 一. JavaScript基础 1.JavaScript基本结构 2. JavaScript 执行过程 3. JavaScript 引入方式 二. JavaScript 语法 1.数据类型 2.变量 2.1 var 关键字定义变量 2.2 let 关键字定义变量 2.3 var 与 let 的区别 3.字符串 3.1定义字符串 3.2 字…

Java中this引用详解

文章目录 一、 为什么要有this引用二、什么是this引用三、this引用的特性四、如何用好this关键字 一、 为什么要有this引用 我们先看一段代码 class Data {public int year;public int month;public int day;public void setDay(int y,int m,int d) {year y;month m;day d…

安卓反编译机制,应用场景以及工具解析

一、引言 随着移动应用的普及&#xff0c;安卓系统成为了市场上的主流操作系统之一。然而&#xff0c;安卓应用的源代码往往受到版权保护&#xff0c;开发者需要对其安全性进行维护。此时&#xff0c;反编译技术应运而生&#xff0c;成为保障应用安全的重要手段。 本文将详细介…

《动手学深度学习(PyTorch版)》笔记4.7

Chapter4 Multilayer Perceptron 4.7 Forward/Backward Propagation and Computational Graphs 本节将通过一些基本的数学和计算图&#xff0c;深入探讨反向传播的细节。首先&#xff0c;我们将重点放在带权重衰减&#xff08; L 2 L_2 L2​正则化&#xff09;的单隐藏层多层…

15. 扩展: Spring Boot CORS支持

Spring Boot CORS支持 跨源资源共享(CORS)是一种安全概念&#xff0c;用于限制Web浏览器中实现的资源。 它可以防止JavaScript代码产生或消耗针对不同来源的请求。 例如&#xff0c;Web应用程序在8080端口上运行&#xff0c;并且使用JavaScript尝试从9090端口使用RESTful Web服…

【教学类-44-04】20240128汉字字帖的字体(一)——文艺空心黑体

背景需求&#xff1a; 【教学类-XX -XX 】20240128名字字卡1.0&#xff08;15CM正方形手工纸、黑体&#xff0c;说明是某个孩子的第几个名字&#xff09;-CSDN博客文章浏览阅读254次&#xff0c;点赞4次&#xff0c;收藏2次。【教学类-XX -XX 】20240128名字字卡1.0&#xff0…

12.Elasticsearch应用(十二)

Elasticsearch应用&#xff08;十二&#xff09; 1.单机ES面临的问题 海量数据存储问题单点故障问题 2.ES集群如何解决上面的问题 海量数据存储解决问题&#xff1a; 将索引库从逻辑上拆分为N个分片&#xff08;Shard&#xff09;&#xff0c;存储到多个节点单点故障问题&a…

鸿蒙(HarmonyOS)项目方舟框架(ArkUI)之CheckboxGroup组件

鸿蒙&#xff08;HarmonyOS&#xff09;项目方舟框架&#xff08;ArkUI&#xff09;之CheckboxGroup组件 一、操作环境 操作系统: Windows 10 专业版、IDE:DevEco Studio 3.1、SDK:HarmonyOS 3.1 二、CheckboxGroup组件 提供多选框组件&#xff0c;通常用于某选项的打开或关…

【python】GtkWindow程序

一、多个GtkWindow 在GTK中&#xff0c;并不推荐使用多个GtkWindow来创建多文档界面&#xff08;MDI&#xff09;&#xff0c;而是推荐使用单个GtkWindow内嵌入的小部件&#xff08;如GtkNotebook&#xff09;来实现类似的效果。然而&#xff0c;如果确实想要创建多个窗口的例…

教育能打破阶层固化吗

中式教育以应试为核心&#xff0c;强调知识的灌输和学生被动接受。随着社会的发展&#xff0c;中式教育的短板逐渐显现&#xff0c;创新能力的缺乏、对记忆的过度依赖、忽视个体差异等问题日益突出。 建议所有大学生都能去看看《上海交通大学生存手册》&#xff0c;它道出了中…

23种设计模式使用场景分析

概述 网上关于设计模式的文章特别多&#xff0c;就不赘述了&#xff0c;我认为在敲代码的时候知道根据当前代码结构选择合适的设计模式是最重要的&#xff0c;知道了要使用哪个设计模式&#xff0c;就可以上网随便百度一下&#xff0c; 简述 对23中设计模式的分类 创建型模式…

日常学习之:vue + django + docker + heroku 对后端项目 / 前后端整体项目进行部署

文章目录 使用 docker 在 heroku 上单独部署 vue 前端使用 docker 在 heroku 上单独部署 django 后端创建 heroku 项目构建 Dockerfile设置 settings.pydatabase静态文件管理安全设置applicaiton & 中间件配置 设置 requirements.txtheroku container 部署应用 前后端分别部…

(自用)learnOpenGL学习总结-高级OpenGL-模板测试

模板测试 模板测试简单来说就是一个mask&#xff0c;根据你的mask来保留或者丢弃片段。 那么可以用来显示什么功能呢&#xff1f;剪切&#xff0c;镂空、透明度等操作。 和深度缓冲的关系是&#xff1a; 先片段着色器&#xff0c;然后进入深度测试&#xff0c;最后加入模板测…

2023年中国工控自动化市场现状及竞争分析,美日占主角,国产品牌初崭头角

工控自动化是一种运用控制理论、仪器仪表理论、计算机和信息技术&#xff0c;对工业生产过程实现检测、控制、优化、调度、管理和决策&#xff0c;达到增加产量、提高质量、降低消耗、确保安全等目的综合性技术。产品应用领域广泛&#xff0c;可分为OEM型行业和项目型行业。 近…