MyBatis-Plus分页拦截器,源码的重构(重构total总数的计算逻辑)

news2024/12/26 21:46:38

 1.1创建ThreadLocal工具类(作为业务逻辑结果存放类)

package org.springblade.sample.utils;

public class QueryContext {
	private static final ThreadLocal<Long> totalInThreadLocal = new ThreadLocal<>();

	public static void setTotalIn(long totalIn) {
		totalInThreadLocal.set(totalIn);
	}

	public static long getTotalIn() {
		return totalInThreadLocal.get() != null ? totalInThreadLocal.get() : 0;
	}

	public static void clear() {
		totalInThreadLocal.remove();
	}
}

2.1重构Mybatis-plus分页查询total总数逻辑(通过实现InnerInterceptor接口进行重构,代码如下,根据注释进行对应业务调整即可)

package org.springblade.sample.config;


import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.metadata.OrderItem;
import com.baomidou.mybatisplus.core.toolkit.*;
import com.baomidou.mybatisplus.extension.parser.JsqlParserGlobal;
import com.baomidou.mybatisplus.extension.plugins.inner.InnerInterceptor;
import com.baomidou.mybatisplus.extension.plugins.pagination.DialectFactory;
import com.baomidou.mybatisplus.extension.plugins.pagination.DialectModel;
import com.baomidou.mybatisplus.extension.plugins.pagination.dialects.IDialect;
import com.baomidou.mybatisplus.extension.toolkit.JdbcUtils;
import com.baomidou.mybatisplus.extension.toolkit.PropertyMapper;
import com.baomidou.mybatisplus.extension.toolkit.SqlParserUtils;
import groovy.util.logging.Slf4j;
import lombok.Data;
import lombok.NoArgsConstructor;
import net.sf.jsqlparser.JSQLParserException;
import net.sf.jsqlparser.expression.Alias;
import net.sf.jsqlparser.expression.Expression;
import net.sf.jsqlparser.schema.Column;
import net.sf.jsqlparser.schema.Table;
import net.sf.jsqlparser.statement.select.*;
import org.apache.ibatis.cache.CacheKey;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.logging.Log;
import org.apache.ibatis.logging.LogFactory;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.mapping.ResultMap;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.springblade.core.tool.utils.StringUtil;
import org.springblade.sample.utils.QueryContext;

import java.sql.SQLException;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

/**
 * 分页拦截器
 * <p>
 * 默认对 left join 进行优化,虽然能优化count,但是加上分页的话如果1对多本身结果条数就是不正确的
 *
 * @author hubin
 * @since 3.4.0
 */
@lombok.extern.slf4j.Slf4j
@Data
@NoArgsConstructor
@org.springframework.context.annotation.Configuration
@Slf4j
public class PaginationInnerInterceptor implements InnerInterceptor {
	/**
	 * 获取jsqlparser中count的SelectItem
	 */
	protected static final List<SelectItem> COUNT_SELECT_ITEM = Collections.singletonList(new SelectExpressionItem(new Column().withColumnName("COUNT(*)")).withAlias(new Alias("total")));
	protected static final Map<String, MappedStatement> countMsCache = new ConcurrentHashMap<>();
	protected final Log logger = LogFactory.getLog(this.getClass());

	/**
	 * 表名
	 */
	private final String V_SAMPLE_COVID19 = "v_sample_covid19";

	private final String T_SAMPLE_COVID19 = "t_sample_covid19";

	private final String T_SAMPLE_FLU = "t_sample_flu";

	private final String T_SAMPLE_HADV = "t_sample_hadv";

	private final String T_SAMPLE_HMPV = "t_sample_hmpv";

	private final String T_SAMPLE_HPIV = "t_sample_hpiv";

	private final String T_SAMPLE_METAV = "t_sample_metav";

	private final String T_SAMPLE_RSV = "t_sample_rsv";


	/**
	 * 溢出总页数后是否进行处理
	 */
	protected boolean overflow;
	/**
	 * 单页分页条数限制
	 */
	protected Long maxLimit;
	/**
	 * 数据库类型
	 * <p>
	 * 查看 {@link #findIDialect(Executor)} 逻辑
	 */
	private DbType dbType;
	/**
	 * 方言实现类
	 * <p>
	 * 查看 {@link #findIDialect(Executor)} 逻辑
	 */
	private IDialect dialect;
	/**
	 * 生成 countSql 优化掉 join
	 * 现在只支持 left join
	 *
	 * @since 3.4.2
	 */
	protected boolean optimizeJoin = true;

	public PaginationInnerInterceptor(DbType dbType) {
		this.dbType = dbType;
	}

	public PaginationInnerInterceptor(IDialect dialect) {
		this.dialect = dialect;
	}

	/***
	 * 重构willDoQuery获取序列未上传数量(根据检索结果进行实时更新)
	 * @param executor      Executor(可能是代理对象)
	 * @param ms            MappedStatement
	 * @param parameter     parameter
	 * @param rowBounds     rowBounds
	 * @param resultHandler resultHandler
	 * @param boundSql      boundSql
	 */
	public boolean willDoQuery(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
		IPage<?> page = ParameterUtils.findPage(parameter).orElse(null);
		if (page == null || page.getSize() < 0 || !page.searchCount() || resultHandler != Executor.NO_RESULT_HANDLER) {
			return true;
		}

		BoundSql countSql;
		BoundSql countSqlIn;
		MappedStatement countMs = buildCountMappedStatement(ms, page.countId());
		if (countMs != null) {
			countSql = countMs.getBoundSql(parameter);
			countSqlIn = countMs.getBoundSql(parameter);
		} else {
			countMs = buildAutoCountMappedStatement(ms);
			String countSqlStr = autoCountSql(page, boundSql.getSql());
			PluginUtils.MPBoundSql mpBoundSql = PluginUtils.mpBoundSql(boundSql);
			countSql = new BoundSql(countMs.getConfiguration(), countSqlStr, mpBoundSql.parameterMappings(), parameter);
			countSqlIn = new BoundSql(countMs.getConfiguration(), countSqlStr + " and seq_num = '0'", mpBoundSql.parameterMappings(), parameter);
			PluginUtils.setAdditionalParameter(countSql, mpBoundSql.additionalParameters());
			PluginUtils.setAdditionalParameter(countSqlIn, mpBoundSql.additionalParameters());
		}

		// 执行第一个查询 (result)
		CacheKey cacheKey = executor.createCacheKey(countMs, parameter, rowBounds, countSql);
		List<Object> result = executor.query(countMs, parameter, rowBounds, resultHandler, cacheKey, countSql);

		long total = 0;

		if (CollectionUtils.isNotEmpty(result)) {
			// 个别数据库 count 没数据不会返回 0
			Object o = result.get(0);
			if (o != null) {
				total = Long.parseLong(o.toString());
			}
		}
		page.setTotal(total);

		long totalIn = 0;

		//获取表名
		String tableName = getTableFromSql(boundSql.getSql());

		// 执行第二个查询 (resultIn)
		if (StringUtil.isNotBlank(tableName)) {
			//根据表名判断是否进行查询
			if (this.suitTableName(tableName)) {
				CacheKey cacheKeyIn = executor.createCacheKey(countMs, parameter, rowBounds, countSqlIn);
				List<Object> resultIn = executor.query(countMs, parameter, rowBounds, resultHandler, cacheKeyIn, countSqlIn);
				log.info("未上传序列数:{}", Long.parseLong(resultIn.get(0).toString()));
				if (CollectionUtils.isNotEmpty(resultIn)) {
					// 个别数据库 count 没数据不会返回 0
					Object o = resultIn.get(0);
					if (o != null) {
						totalIn = Long.parseLong(o.toString());
					}
				}
			}
		}
		// 将 totalIn 设置到 ThreadLocal 中
		QueryContext.setTotalIn(totalIn);
		return continuePage(page);
	}

	@Override
	public void beforeQuery(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql)  {
		IPage<?> page = ParameterUtils.findPage(parameter).orElse(null);
		if (null == page) {
			return;
		}

		// 处理 orderBy 拼接
		boolean addOrdered = false;
		String buildSql = boundSql.getSql();
		List<OrderItem> orders = page.orders();
		if (CollectionUtils.isNotEmpty(orders)) {
			addOrdered = true;
			buildSql = this.concatOrderBy(buildSql, orders);
		}

		// size 小于 0 且不限制返回值则不构造分页sql
		Long _limit = page.maxLimit() != null ? page.maxLimit() : maxLimit;
		if (page.getSize() < 0 && null == _limit) {
			if (addOrdered) {
				PluginUtils.mpBoundSql(boundSql).sql(buildSql);
			}
			return;
		}

		handlerLimit(page, _limit);
		IDialect dialect = findIDialect(executor);

		final Configuration configuration = ms.getConfiguration();
		DialectModel model = dialect.buildPaginationSql(buildSql, page.offset(), page.getSize());
		PluginUtils.MPBoundSql mpBoundSql = PluginUtils.mpBoundSql(boundSql);

		List<ParameterMapping> mappings = mpBoundSql.parameterMappings();
		Map<String, Object> additionalParameter = mpBoundSql.additionalParameters();
		model.consumers(mappings, configuration, additionalParameter);
		mpBoundSql.sql(model.getDialectSql());
		mpBoundSql.parameterMappings(mappings);
	}

	/**
	 * 获取分页方言类的逻辑
	 *
	 * @param executor Executor
	 * @return 分页方言类
	 */
	protected IDialect findIDialect(Executor executor) {
		if (dialect != null) {
			return dialect;
		}
		if (dbType != null) {
			dialect = DialectFactory.getDialect(dbType);
			return dialect;
		}
		return DialectFactory.getDialect(JdbcUtils.getDbType(executor));
	}

	/**
	 * 获取指定的 id 的 MappedStatement
	 *
	 * @param ms      MappedStatement
	 * @param countId id
	 * @return MappedStatement
	 */
	protected MappedStatement buildCountMappedStatement(MappedStatement ms, String countId) {
		if (StringUtils.isNotBlank(countId)) {
			final String id = ms.getId();
			if (!countId.contains(StringPool.DOT)) {
				countId = id.substring(0, id.lastIndexOf(StringPool.DOT) + 1) + countId;
			}
			final Configuration configuration = ms.getConfiguration();
			try {
				return CollectionUtils.computeIfAbsent(countMsCache, countId, key -> configuration.getMappedStatement(key, false));
			} catch (Exception e) {
				logger.warn(String.format("can not find this countId: [\"%s\"]", countId));
			}
		}
		return null;
	}

	/**
	 * 构建 mp 自用自动的 MappedStatement
	 *
	 * @param ms MappedStatement
	 * @return MappedStatement
	 */
	protected MappedStatement buildAutoCountMappedStatement(MappedStatement ms) {
		final String countId = ms.getId() + "_mpCount";
		final Configuration configuration = ms.getConfiguration();
		return CollectionUtils.computeIfAbsent(countMsCache, countId, key -> {
			MappedStatement.Builder builder = new MappedStatement.Builder(configuration, key, ms.getSqlSource(), ms.getSqlCommandType());
			builder.resource(ms.getResource());
			builder.fetchSize(ms.getFetchSize());
			builder.statementType(ms.getStatementType());
			builder.timeout(ms.getTimeout());
			builder.parameterMap(ms.getParameterMap());
			builder.resultMaps(Collections.singletonList(new ResultMap.Builder(configuration, Constants.MYBATIS_PLUS, Long.class, Collections.emptyList()).build()));
			builder.resultSetType(ms.getResultSetType());
			builder.cache(ms.getCache());
			builder.flushCacheRequired(ms.isFlushCacheRequired());
			builder.useCache(ms.isUseCache());
			return builder.build();
		});
	}

	/**
	 * 获取自动优化的 countSql
	 *
	 * @param page 参数
	 * @param sql  sql
	 * @return countSql
	 */
	protected String autoCountSql(IPage<?> page, String sql) {
		if (!page.optimizeCountSql()) {
			return lowLevelCountSql(sql);
		}
		try {
			Select select = (Select) JsqlParserGlobal.parse(sql);
			SelectBody selectBody = select.getSelectBody();
			// https://github.com/baomidou/mybatis-plus/issues/3920  分页增加union语法支持
			if (selectBody instanceof SetOperationList) {
				return lowLevelCountSql(sql);
			}
			PlainSelect plainSelect = (PlainSelect) select.getSelectBody();
			Distinct distinct = plainSelect.getDistinct();
			GroupByElement groupBy = plainSelect.getGroupBy();

			// 包含 distinct、groupBy 不优化
			if (null != distinct || null != groupBy) {
				return lowLevelCountSql(select.toString());
			}

			// 优化 order by 在非分组情况下
			List<OrderByElement> orderBy = plainSelect.getOrderByElements();
			if (CollectionUtils.isNotEmpty(orderBy)) {
				boolean canClean = true;
				for (OrderByElement order : orderBy) {
					// order by 里带参数,不去除order by
					Expression expression = order.getExpression();
					if (!(expression instanceof Column) && expression.toString().contains(StringPool.QUESTION_MARK)) {
						canClean = false;
						break;
					}
				}
				if (canClean) {
					plainSelect.setOrderByElements(null);
				}
			}

			for (SelectItem item : plainSelect.getSelectItems()) {
				if (item.toString().contains(StringPool.QUESTION_MARK)) {
					return lowLevelCountSql(select.toString());
				}
			}

			// 包含 join 连表,进行判断是否移除 join 连表
			if (optimizeJoin && page.optimizeJoinOfCountSql()) {
				List<Join> joins = plainSelect.getJoins();
				if (CollectionUtils.isNotEmpty(joins)) {
					boolean canRemoveJoin = true;
					String whereS = Optional.ofNullable(plainSelect.getWhere()).map(Expression::toString).orElse(StringPool.EMPTY);
					// 不区分大小写
					whereS = whereS.toLowerCase();
					for (Join join : joins) {
						if (!join.isLeft()) {
							canRemoveJoin = false;
							break;
						}
						FromItem rightItem = join.getRightItem();
						String str = "";
						if (rightItem instanceof Table) {
							Table table = (Table) rightItem;
							str = Optional.ofNullable(table.getAlias()).map(Alias::getName).orElse(table.getName()) + StringPool.DOT;
						} else if (rightItem instanceof SubSelect) {
							SubSelect subSelect = (SubSelect) rightItem;
							/* 如果 left join 是子查询,并且子查询里包含 ?(代表有入参) 或者 where 条件里包含使用 join 的表的字段作条件,就不移除 join */
							if (subSelect.toString().contains(StringPool.QUESTION_MARK)) {
								canRemoveJoin = false;
								break;
							}
							str = subSelect.getAlias().getName() + StringPool.DOT;
						}
						// 不区分大小写
						str = str.toLowerCase();

						if (whereS.contains(str)) {
							/* 如果 where 条件里包含使用 join 的表的字段作条件,就不移除 join */
							canRemoveJoin = false;
							break;
						}

						for (Expression expression : join.getOnExpressions()) {
							if (expression.toString().contains(StringPool.QUESTION_MARK)) {
								/* 如果 join 里包含 ?(代表有入参) 就不移除 join */
								canRemoveJoin = false;
								break;
							}
						}
					}

					if (canRemoveJoin) {
						plainSelect.setJoins(null);
					}
				}
			}

			// 优化 SQL
			plainSelect.setSelectItems(COUNT_SELECT_ITEM);
			return select.toString();
		} catch (JSQLParserException e) {
			// 无法优化使用原 SQL
			logger.warn("optimize this sql to a count sql has exception, sql:\"" + sql + "\", exception:\n" + e.getCause());
		} catch (Exception e) {
			logger.warn("optimize this sql to a count sql has error, sql:\"" + sql + "\", exception:\n" + e);
		}
		return lowLevelCountSql(sql);
	}

	/**
	 * 无法进行count优化时,降级使用此方法
	 *
	 * @param originalSql 原始sql
	 * @return countSql
	 */
	protected String lowLevelCountSql(String originalSql) {
		return SqlParserUtils.getOriginalCountSql(originalSql);
	}

	/**
	 * 查询SQL拼接Order By
	 *
	 * @param originalSql 需要拼接的SQL
	 * @return ignore
	 */
	public String concatOrderBy(String originalSql, List<OrderItem> orderList) {
		try {
			Select select = (Select) JsqlParserGlobal.parse(originalSql);
			SelectBody selectBody = select.getSelectBody();
			if (selectBody instanceof PlainSelect) {
				PlainSelect plainSelect = (PlainSelect) selectBody;
				List<OrderByElement> orderByElements = plainSelect.getOrderByElements();
				List<OrderByElement> orderByElementsReturn = addOrderByElements(orderList, orderByElements);
				plainSelect.setOrderByElements(orderByElementsReturn);
				return select.toString();
			} else if (selectBody instanceof SetOperationList) {
				SetOperationList setOperationList = (SetOperationList) selectBody;
				List<OrderByElement> orderByElements = setOperationList.getOrderByElements();
				List<OrderByElement> orderByElementsReturn = addOrderByElements(orderList, orderByElements);
				setOperationList.setOrderByElements(orderByElementsReturn);
				return select.toString();
			} else if (selectBody instanceof WithItem) {
				// todo: don't known how to resole
				return originalSql;
			} else {
				return originalSql;
			}
		} catch (JSQLParserException e) {
			logger.warn("failed to concat orderBy from IPage, exception:\n" + e.getCause());
		} catch (Exception e) {
			logger.warn("failed to concat orderBy from IPage, exception:\n" + e);
		}
		return originalSql;
	}

	protected List<OrderByElement> addOrderByElements(List<OrderItem> orderList, List<OrderByElement> orderByElements) {
		List<OrderByElement> additionalOrderBy = orderList.stream().filter(item -> StringUtils.isNotBlank(item.getColumn())).map(item -> {
			OrderByElement element = new OrderByElement();
			element.setExpression(new Column(item.getColumn()));
			element.setAsc(item.isAsc());
			element.setAscDescPresent(true);
			return element;
		}).collect(Collectors.toList());
		if (CollectionUtils.isEmpty(orderByElements)) {
			return additionalOrderBy;
		}
		// github pull/3550 优化排序,比如:默认 order by id 前端传了name排序,设置为 order by name,id
		additionalOrderBy.addAll(orderByElements);
		return additionalOrderBy;
	}

	/**
	 * count 查询之后,是否继续执行分页
	 *
	 * @param page 分页对象
	 * @return 是否
	 */
	protected boolean continuePage(IPage<?> page) {
		if (page.getTotal() <= 0) {
			return false;
		}
		if (page.getCurrent() > page.getPages()) {
			if (overflow) {
				//溢出总页数处理
				handlerOverflow(page);
			} else {
				// 超过最大范围,未设置溢出逻辑中断 list 执行
				return false;
			}
		}
		return true;
	}

	/**
	 * 处理超出分页条数限制,默认归为限制数
	 *
	 * @param page IPage
	 */
	protected void handlerLimit(IPage<?> page, Long limit) {
		final long size = page.getSize();
		if (limit != null && limit > 0 && (size > limit || size < 0)) {
			page.setSize(limit);
		}
	}

	/**
	 * 处理页数溢出,默认设置为第一页
	 *
	 * @param page IPage
	 */
	protected void handlerOverflow(IPage<?> page) {
		page.setCurrent(1);
	}

	@Override
	public void setProperties(Properties properties) {
		PropertyMapper.newInstance(properties).whenNotBlank("overflow", Boolean::parseBoolean, this::setOverflow).whenNotBlank("dbType", DbType::getDbType, this::setDbType).whenNotBlank("dialect", ClassUtils::newInstance, this::setDialect).whenNotBlank("maxLimit", Long::parseLong, this::setMaxLimit).whenNotBlank("optimizeJoin", Boolean::parseBoolean, this::setOptimizeJoin);
	}

	protected String getTableFromSql(String sql) {
		String regex = "(?i)from\\s+([a-zA-Z0-9_]+)";
		Pattern pattern = Pattern.compile(regex);
		Matcher matcher = pattern.matcher(sql);
		if (matcher.find()) {
			return matcher.group(1);
		}
		return null;
	}

	/**
	 * 判断是否进行未上传序列数计算
	 * @param tableName 表名
	 */
	private boolean suitTableName(String tableName) {
		switch (tableName) {
			case V_SAMPLE_COVID19:
			case T_SAMPLE_RSV:
			case T_SAMPLE_COVID19:
			case T_SAMPLE_FLU:
			case T_SAMPLE_HADV:
			case T_SAMPLE_HMPV:
			case T_SAMPLE_HPIV:
			case T_SAMPLE_METAV:
				return true;
			default:
				return false;
		}
	}
}

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

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

相关文章

项目2路由交换

背景 某学校为满足日常教学生活需求&#xff0c;推动数字校园的建设&#xff0c;学校有办公楼和学生宿舍楼和服务器集群三块区域&#xff0c;请合理规划IP地址和VLAN&#xff0c;实现企业内部能够互联互通现要求外网能通过公网地址访问服务器集群&#xff0c;学生和老师能正常…

知识梳理笔记--Kerberos 协议

Kerberos 协议概述 Kerberos 是一种计算机网络认证协议&#xff0c;旨在为不安全的网络提供强认证服务。它通过中心化的身份验证系统&#xff08;即 Key Distribution Center&#xff0c;KDC&#xff09;来确保通信双方的身份验证和数据加密。Kerberos 协议主要用于确保计算机系…

9个用于测试自动化的最佳AI测试工具(2024)

选择一款优质的基于生成式AI人工智能的测试工具能够确保测试过程的准确性和效率&#xff0c;从而加速整个软件测试周期。相反&#xff0c;设计不佳的测试工具可能无法发现错误&#xff0c;并可能存在安全问题。它们可能产生误报或漏报&#xff0c;误导开发与测试团队&#xff0…

uni-app 跨端开发精美开源UI框架推荐

&#x1f380;&#x1f380;&#x1f380;uni-app 跨端开发系列 &#x1f380;&#x1f380;&#x1f380; 一、uni-app 组成和跨端原理 二、uni-app 各端差异注意事项 三、uni-app 离线本地存储方案 四、uni-app UI库、框架、组件选型指南 五、uni-app 蓝牙开发 六、uni-app …

【1224】数据结构(sizeof/数组的长度定义/读取字符串函数/线性表长度/左值右值/静态变量/指针与引用)

1.对一维整型数组a的正确说明是 #define SIZE 10 (换行) int a[SIZE];说法是否正确&#xff1f; 正确 数组的SIZE可以用宏定义&#xff0c;但不能用变量 2.如有定义&#xff1a;char str[20];&#xff0c;能将从键盘输入的字符串“How are you”保存到 str 数组的语句是&#x…

强化特种作业管理,筑牢安全生产防线

在各类生产经营活动中&#xff0c;特种作业由于其操作的特殊性和高风险性&#xff0c;一直是安全生产管理的重点领域。有效的特种作业管理体系涵盖多个关键方面&#xff0c;从作业人员的资质把控到安全设施的配备维护&#xff0c;再到特种设备的精细管理以及作业流程的严格规范…

(六)循环神经网络_基本的RNN

一、提出背景 前馈神经网络不考虑数据之间的关联性&#xff0c;网络的输出只和当前时刻网络的输入相关。然而&#xff0c;现实问题中存在着很多序列型的数据&#xff08;文本、语音以及视频等&#xff09;。 例如&#xff1a;室外的温度是随着气候的变化而周期性的变化的&…

Dockerfile的用法

Dockerfile的用法 示例 `Dockerfile`使用 `Dockerfile` 创建 Docker 镜像`Dockerfile` 指令详解其他常用指令总结Dockerfile 是一个文本文件,包含了用于创建 Docker 镜像的一系列指令。这些指令描述了镜像的基础、所安装的软件、文件的复制、环境变量的设置以及其他配置。下面…

60.基于SSM的个人网站的设计与实现(项目 + 论文)

项目介绍 本站是一个B/S模式系统&#xff0c;个人网站是在MySQL中建立数据表保存信息&#xff0c;运用SSMVue框架和Java语言编写。并按照软件设计开发流程进行设计实现充分保证系统的稳定性。系统具有界面清晰、操作简单&#xff0c;功能齐全的特点&#xff0c;使得基于SSM的网…

B端UI设计规范是什么?

一、B端UI设计规范是什么&#xff1f; B端UI设计规范是一套针对企业级应用界面设计的全面规则和标准&#xff0c;旨在确保产品界面的一致性、可用性和用户体验。 二、B端UI设计规范要素说明 B端UI设计的基本要素包括设计原则、主题、布局、颜色、字体、图标、按钮和控件、交互…

GPT人工智能在医疗文档中的应用

应用场景 用于文档的整理。主要是针对医疗方面的文档整理。病人在打官司或者办理其他业务时&#xff0c;需要把很多文档整理成册并添加目录、编写概要&#xff08;Summary&#xff09;。这些文档有电子版本的&#xff0c;有纸质的扫描件&#xff0c;还有拍照&#xff08;一般是…

unity 打包出来的所有执行文件内容打包成一个exe程序

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、使用Enigma Virtual Box1.下载安装包&#xff08;根据需要32位还是64位。一般是64位&#xff09;2.改个语言&#xff0c;方便使用&#xff08;改了后重启才…

vtie项目中使用到了TailwindCSS,如何打包成一个单独的CSS文件(优化、压缩)

在不依赖 Vite 或其他构建工具的情况下&#xff0c;使用 TailwindCSS CLI 快速生成独立的 CSS 文件是一种简单高效的方法&#xff0c;适合需要纯样式文件的场景。 这个项目中&#xff0c;使用到了tailwindCss, 需要把里面的样式打包出来&#xff0c;给其他项目用。 使用命令生…

物联网网络中的设备认证方法

论文标题&#xff1a;DEVICE AUTHENTICATION METHOD IN INTERNET OF THINGS NETWORKS&#xff08;物联网网络中的设备认证方法&#xff09; 作者信息&#xff1a; A.Ya. Davletova&#xff0c;West Ukrainian National University, 11, Lvivska Str. Ternopil, 46009, Ukraine…

重温设计模式--迭代器模式

文章目录 迭代器模式&#xff08;Iterator Pattern&#xff09;概述迭代器模式的结构迭代器模式UML图C 代码示例应用场景 迭代器模式&#xff08;Iterator Pattern&#xff09;概述 定义&#xff1a; 迭代器模式是一种行为型设计模式&#xff0c;它提供了一种方法来顺序访问一个…

大数据机器学习算法和计算机视觉应用07:机器学习

Machine Learning Goal of Machine LearningLinear ClassificationSolutionNumerical output example: linear regressionStochastic Gradient DescentMatrix Acceleration Goal of Machine Learning 机器学习的目标 假设现在有一组数据 x i , y i {x_i,y_i} xi​,yi​&…

华院计算参与项目再次被《新闻联播》报道

12月17日&#xff0c;央视《新闻联播》播出我国推进乡村振兴取得积极进展。其中&#xff0c;华院计算参与的江西省防止返贫监测帮扶大数据系统被报道&#xff0c;该系统实现了由原来的“人找人”向“数据找人”的转变&#xff0c;有效提升监测帮扶及时性和有效性&#xff0c;守…

【视觉惯性SLAM:相机成像模型】

相机成像模型介绍 相机成像模型是计算机视觉和图像处理中的核心内容&#xff0c;它描述了真实三维世界如何通过相机映射到二维图像平面。相机成像模型通常包括针孔相机的基本成像原理、数学模型&#xff0c;以及在实际应用中如何处理相机的各种畸变现象。 一、针孔相机成像原…

使用RTP 协议 对 H264 封包和解包,h264的avpacket和NAL的关系

学习内容&#xff1a; 本章探讨如何将h264的 avpacket的视频 数据&#xff0c;通过RTP协议发送到 流媒体 服务器 或者 对端接受者。 前提 我们在将 YUV数据变成avframe后&#xff0c;通过h264 编码变成AVPacket&#xff0c;例如&#xff0c;在安防项目中&#xff0c;或者直播…

python 随笔80%核心笔记(一)

目录 一、海龟 二、pygame 三、函数 四、类与对象 五、列表与元组 六、其他 1、格式化输出 2、最大公约数、最小公倍数 3、print、多变量一起定义赋值、end以及列表的方法 4、序列重复、字符串方法、其他列表方法、input 5、字典的方法、ASCII码转换、返回值、修改私人…