手敲MyBatis(十四章)-解析含标签的动态SQL语句

1.前言

这一章主要的就是要解析动态标签里的Sql语句,然后进行条件语句的拼接,动态标签实现了trim和if标签,所以Sql节点就要加上TrimSqlNode和ifSqlNode,我们最终要获取Sql源,动态Sql语句需要一些处理,所以需要添加DynamicSqlSource来处理动态Sql语句的调用和一些业务逻辑处理。

本章节主要就是要处理如下图片的解析Sql内容,把如下图片的Sql内容更改为能够可执行的Sql语句,这个是目标。

需要注意的是,sql语句不加条件时我们叫静态SQL,当动态语句标签包含的条件语句时,除了trim和if放入到对应的节点里,if里的Sql也要放入静态节点里,最后把这些个节点集合放入到混合节点里,等使用时直接遍历混合节点数据即可,最终调度到不同的节点取出Sql进行拼接即可。

2.xml类图

动态标签我们看作是一个节点,那么我们解析的xml的Sql语句里边就有很多的不同的节点,静态的Sql节点,if节点,trim节点,那么需要把不同节点的信息存储到不同的节点里,这块的功能需要在xml脚本构建类里实现,从这里开始去一点一点构建不同的节点。

节点构建完毕需要获取不同的文本进行SQL语句拼接,拼接完毕放入到DynamicContext的sqlBuilder里, DynamicSqlSource就可以根据DynamicContext直接获取到SQL了。

3.代码

因为我们要处理的是Sql里的内容,所以在代码设计里就是要处理Xml的脚本构建,也就是XMLScriptBuilder类,我们在XMLScriptBuilder类里添加了NodeHandler接口,定义了handleNode方法。

3.1 节点处理器(NodeHandler)

包名路径:package cn.bugstack.mybatis.scripting.xmltags;

然后定义两个实现类,TrimHandler和IfHandler类,TrimHandler主要解析trim标签,IfHandler主要处理if标签内容。

最后再初始化nodeHandler把TrimHandler和IfHandler放入到Map里,留着后面从Map取出使用。

public class XMLScriptBuilder extends BaseBuilder {// 过滤其他public XMLScriptBuilder(Configuration configuration, Element element, Class<?> parameterType) {super(configuration);this.element = element;this.parameterType = parameterType;initNodeHandlerMap();}// step-15新增private void initNodeHandlerMap() {// 9种,实现其中2种 trim/where/set/foreach/if/choose/when/otherwise/bindnodeHandlerMap.put("trim", new TrimHandler());nodeHandlerMap.put("if", new IfHandler());}// 节点处理器private interface NodeHandler {void handleNode(Element nodeToHandle, List<SqlNode> targetContents);}/*** <trim prefix="where" prefixOverrides="AND | OR" suffixOverrides="and">...</trim> 解析 trim 标签信息,把字段 prefix、* prefixOverrides、suffixOverrides 都依次获取出来,使用 TrimSqlNode 构建后存放到 List<SqlNode>   中。* 得到trim的属性*/// step-15新增private class TrimHandler implements NodeHandler {@Overridepublic void handleNode(Element nodeToHandle, List<SqlNode> targetContents) {List<SqlNode> contents = parseDynamicTags(nodeToHandle);MixedSqlNode mixedSqlNode = new MixedSqlNode(contents);String prefix = nodeToHandle.attributeValue("prefix");String prefixOverrides = nodeToHandle.attributeValue("prefixOverrides");String suffix = nodeToHandle.attributeValue("suffix");String suffixOverrides = nodeToHandle.attributeValue("suffixOverrides");TrimSqlNode trim = new TrimSqlNode(configuration, mixedSqlNode, prefix, prefixOverrides, suffix, suffixOverrides);targetContents.add(trim);}}/*** <if test="null != activityId">...</if> 解析if语句标签,与解析 trim 标签类似,* 获取标签配置 test 语句表达式,使用 IfSqlNode 进行构建,构建后存放到 List<SqlNode>  中。* 得到if标签的属性*/// step-15新增private class IfHandler implements NodeHandler {@Overridepublic void handleNode(Element nodeToHandle, List<SqlNode> targetContents) {List<SqlNode> contents = parseDynamicTags(nodeToHandle);MixedSqlNode mixedSqlNode = new MixedSqlNode(contents);// 得到test的判断语句String test = nodeToHandle.attributeValue("test");IfSqlNode ifSqlNode = new IfSqlNode(mixedSqlNode, test);targetContents.add(ifSqlNode);}}}

3.2 SqlNode

我们原来的SqlNode有静态的和混合的实现类,这次我们还要加三个实现类,TextSqlNode和IfSqlNode以及TrimSqlNode。

3.2.1 TextSqlNode

TextSqlNode:此节点处理是否是动态Sql的判断,还有一个是${}的参数替换。

/*** @Author df* @Description: 文本SQL节点(CDATA | TEXT)* @Date 2023/12/22 14:09*/
// step-15新增
public class TextSqlNode implements SqlNode {private String text;private Pattern injectionFilter;public TextSqlNode(String text) {this(text, null);}public TextSqlNode(String text, Pattern injectionFilter) {this.text = text;this.injectionFilter = injectionFilter;}/*** 判断是否是动态sql*/public boolean isDynamic() {DynamicCheckerTokenParser checker = new DynamicCheckerTokenParser();GenericTokenParser parser = createParser(checker);parser.parse(text);return checker.isDynamic();}@Overridepublic boolean apply(DynamicContext context) {GenericTokenParser parser = createParser(new BindingTokenParser(context, injectionFilter));context.appendSql(parser.parse(text));return true;}// 处理${}替换值的情况private GenericTokenParser createParser(TokenHandler handler) {return new GenericTokenParser("${", "}", handler);}private static class BindingTokenParser implements TokenHandler {private DynamicContext context;private Pattern injectionFilter;public BindingTokenParser(DynamicContext context, Pattern injectionFilter) {this.context = context;this.injectionFilter = injectionFilter;}@Overridepublic String handleToken(String content) {Object parameter = context.getBindings();if (parameter == null) {context.getBindings().put("value", null);} else if (SimpleTypeRegistry.isSimpleType(parameter.getClass())) {context.getBindings().put("value", parameter);}// 从缓存里取得值Object value = OgnlCache.getValue(content, context.getBindings());String srtValue = (value == null ? "" : String.valueOf(value));checkInjection(srtValue);return srtValue;}// 检查是否匹配正则表达式private void checkInjection(String value) {if (injectionFilter != null && !injectionFilter.matcher(value).matches()) {throw new RuntimeException("Invalid input. Please conform to regex" + injectionFilter.pattern());}}}/*** 动态SQL检查器*/private static class DynamicCheckerTokenParser implements TokenHandler {private boolean isDynamic;public DynamicCheckerTokenParser() {// Prevent Synthetic Access}public boolean isDynamic() {return isDynamic;}@Overridepublic String handleToken(String content) {// 设置 isDynamic 为 true,即调用了这个类就必定是动态 SQLthis.isDynamic = true;return null;}}
}

3.2.2 IfSqlNode

IfSqlNode:它专门就是处理test的内容判断的,如果满足判断则进入拼接Sql语句

/*** @Author df* @Description: IF SQL 节点* @Date 2023/12/22 15:17*/
// step-15新增
public class IfSqlNode implements SqlNode {private ExpressionEvaluator evaluator;private String test;private SqlNode contents;public IfSqlNode(SqlNode contents, String test) {this.test = test;this.contents = contents;this.evaluator = new ExpressionEvaluator();}/*** <if test="null != activityId">* activity_id = #{activityId}* </if>*/@Overridepublic boolean apply(DynamicContext context) {// 如果满足条件,则apply,并返回trueif (evaluator.evaluateBoolean(test, context.getBindings())) {// 拼接if标签里的Sql语句contents.apply(context);return true;}return false;}
}

ExpressionEvaluator类:处理if的test内容判断的

public class ExpressionEvaluator {// 表达式求布尔值,比如 username == 'xiaofuge'public boolean evaluateBoolean(String expression, Object parameterObject) {// 非常简单,就是调用ognlObject value = OgnlCache.getValue(expression, parameterObject);if (value instanceof Boolean) {// 如果是Booleanreturn (Boolean) value;}if (value instanceof Number) {// 如果是Number,判断不为0return !new BigDecimal(String.valueOf(value)).equals(BigDecimal.ZERO);}// 否则判断不为nullreturn value != null;}
}

 OgnlCache:OGNL缓存,处理if的表达式判断,并把表达式存储起来。

/*** @Author df* @Description: OGNL缓存:http://code.google.com/p/mybatis/issues/detail?id=342* OGNL 是 Object-Graph Navigation Language 的缩写,它是一种功能强大的表达式语言(Expression Language,简称为EL)* 通过它简单一致的表达式语法,可以存取对象的任意属性,调用对象的方法,遍历整个对象的结构图,实现字段类型转化等功能。* 它使用相同的表达式去存取对象的属性。* @Date 2023/12/22 14:45*/
// step-15新增
public class OgnlCache {private static final Map<String, Object> expressionCache = new ConcurrentHashMap<String, Object>();private OgnlCache() {// Prevent Instantiation of Static Class}public static Object getValue(String expression, Object root) {try {Map<Object, OgnlClassResolver> context = Ognl.createDefaultContext(root, new OgnlClassResolver());return Ognl.getValue(parseExpression(expression), context, root);} catch (OgnlException e) {throw new RuntimeException("Error evaluating expression '" + expression + "'. Cause: " + e, e);}}private static Object parseExpression(String expression) throws OgnlException {Object node = expressionCache.get(expression);if (node == null) {node = Ognl.parseExpression(expression);expressionCache.put(expression, node);}return node;}
}

OgnlClassResolver: 自己实现个OgnlClassResolver类加载器

public class OgnlClassResolver implements ClassResolver {private Map<String, Class<?>> classes = new HashMap<String, Class<?>>(101);@Overridepublic Class classForName(String className, Map map) throws ClassNotFoundException {Class<?> result = null;if ((result = classes.get(className)) == null) {try {result = Resources.classForName(className);} catch (ClassNotFoundException e1) {if (className.indexOf('.') == -1) {result = Resources.classForName("java.lang." + className);classes.put("java.lang." + className, result);}}classes.put(className, result);}return result;}
}


3.2.3 TrimSqlNode

TrimSqlNode:trim最主要的就是调用处理if的sql节点处理或拼接,然后得到trim属性,把trim属性前缀或后缀进行拼接Sql处理。

这里的trim的Sql拼接处理都在其内部类实现,是FilteredDynamicContext类。

/*** @Author df* @Description: trim Sql Node 节点解析* @Date 2023/12/22 14:57*/
// step-15新增
public class TrimSqlNode implements SqlNode {private SqlNode contents;private String prefix;private String suffix;private List<String> prefixesToOverride;private List<String> suffixesToOverride;private Configuration configuration;public TrimSqlNode(Configuration configuration, SqlNode contents, String prefix, String prefixesToOverride, String suffix, String suffixesToOverride) {this(configuration, contents, prefix, parseOverrides(prefixesToOverride), suffix, parseOverrides(suffixesToOverride));}protected TrimSqlNode(Configuration configuration, SqlNode contents, String prefix, List<String> prefixesToOverride, String suffix, List<String> suffixesToOverride) {this.contents = contents;this.prefix = prefix;this.prefixesToOverride = prefixesToOverride;this.suffix = suffix;this.suffixesToOverride = suffixesToOverride;this.configuration = configuration;}@Overridepublic boolean apply(DynamicContext context) {FilteredDynamicContext filteredDynamicContext = new FilteredDynamicContext(context);// 得到trim里的内容,进行处理,最后拼接语句// 例如:trim->if->条件语句boolean result = contents.apply(filteredDynamicContext);// 根据trim的属性添加前后缀filteredDynamicContext.applyAll();return result;}/*** <trim prefix="where" prefixOverrides="AND | OR" suffixOverrides="and">** </trim>* 将prefixOverrides以list形式展示*/private static List<String> parseOverrides(String overrides) {if (overrides != null) {final StringTokenizer parser = new StringTokenizer(overrides, "|", false);final List<String> list = new ArrayList<>(parser.countTokens());while (parser.hasMoreTokens()) {list.add(parser.nextToken().toLowerCase(Locale.ENGLISH));}return list;}return Collections.emptyList();}private class FilteredDynamicContext extends DynamicContext {private DynamicContext delegate;private boolean prefixApplied;private boolean suffixApplied;private StringBuilder sqlBuffer;public FilteredDynamicContext(DynamicContext delegate) {super(configuration, null);this.delegate = delegate;this.prefixApplied = false;this.suffixApplied = false;this.sqlBuffer = new StringBuilder();}public void applyAll() {sqlBuffer = new StringBuilder(sqlBuffer.toString().trim());String trimmedUppercaseSql = sqlBuffer.toString().toUpperCase(Locale.ENGLISH);if (trimmedUppercaseSql.length() > 0) {// 动态加前缀applyPrefix(sqlBuffer, trimmedUppercaseSql);// 动态加后缀applySuffix(sqlBuffer, trimmedUppercaseSql);}// 添加完拼接的前后缀,继续拼接完整的Sqldelegate.appendSql(sqlBuffer.toString());}// 获取当前属性@Overridepublic Map<String, Object> getBindings() {return delegate.getBindings();}// 拼接当前Sql@Overridepublic void appendSql(String sql) {sqlBuffer.append(sql);}@Overridepublic String getSql() {return delegate.getSql();}/*** 拼接前缀处理*/private void applyPrefix(StringBuilder sql, String trimmedUppercaseSql) {if (!prefixApplied) {prefixApplied = true;if (prefixesToOverride != null) {for (String toRemove : prefixesToOverride) {if (trimmedUppercaseSql.startsWith(toRemove)) {sql.delete(0, toRemove.trim().length());break;}}}}if (prefix != null) {sql.insert(0, " ");sql.insert(0, prefix);}}/*** 拼接后缀处理*/private void applySuffix(StringBuilder sql, String trimmedUppercaseSql) {if (!suffixApplied) {suffixApplied = true;if (suffixesToOverride != null) {for (String toRemove : suffixesToOverride) {if (trimmedUppercaseSql.endsWith(toRemove) || trimmedUppercaseSql.endsWith(toRemove.trim())) {int start = sql.length() - toRemove.trim().length();int end = sql.length();sql.delete(start, end);break;}}}if (suffix != null) {sql.append(" ");sql.append(suffix);}}}}}

3.3 DynamicSqlSource

在处理完Sql语句需要包装成SqlSource源,之前处理的都是静态源,这次我们需要加动态源类DynamicSqlSource类,实现SqlSource。

1.这个动态源主要是将所有的SqlNode进行处理,拼接为一个处理过的Sql然后直接返回Sql。

2.最后返回SqlSource时判断是否是动态的,是返回DynamicSqlSource,不是返回RawSqlSource

/*** @Author df* @Description: 动态SQL源码* @Date 2023/12/22 11:20*/
// step-15新增
public class DynamicSqlSource implements SqlSource {private Configuration configuration;private SqlNode rootSqlNode;public DynamicSqlSource(Configuration configuration, SqlNode rootSqlNode) {this.configuration = configuration;this.rootSqlNode = rootSqlNode;}@Overridepublic BoundSql getBoundSql(Object parameterObject) {// 生成一个 DynamicContext 动态上下文DynamicContext context = new DynamicContext(configuration, parameterObject);// SqlNode.apply 将 ${} 参数替换掉,不替换 #{} 这种参数rootSqlNode.apply(context);// 调用 SqlSourceBuilderSqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration);Class<?> parameterType = parameterObject == null ? Object.class : parameterObject.getClass();// SqlSourceBuilder.parse 这里返回的是 StaticSqlSource,解析过程就把那些参数都替换成?了,也就是最基本的JDBC的SQL语句。SqlSource sqlSource = sqlSourceParser.parse(context.getSql(), parameterType, context.getBindings());// SqlSource.getBoundSql,非递归调用,而是调用 StaticSqlSource 实现类BoundSql boundSql = sqlSource.getBoundSql(parameterType);for (Map.Entry<String, Object> entry : context.getBindings().entrySet()) {boundSql.setAdditionalParameter(entry.getKey(), entry.getValue());}return boundSql;}// 解析脚本里的动态节点// 更改判断是否动态调用不同的SqlSourcepublic SqlSource parseScriptNode() {// step-15修改List<SqlNode> contents = parseDynamicTags(element);MixedSqlNode rootSqlNode = new MixedSqlNode(contents);SqlSource sqlSource = null;if (isDynamic) {sqlSource = new DynamicSqlSource(configuration, rootSqlNode);} else {sqlSource = new RawSqlSource(configuration, rootSqlNode, parameterType);}return sqlSource;}
}

4.测试准备

dao层:

public interface IActivityDao {Activity queryActivityById(Activity activity);}

 Activity_Mapper.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="cn.bugstack.mybatis.test.dao.IActivityDao"><resultMap id="activityMap" type="cn.bugstack.mybatis.test.po.Activity"><id column="id" property="id"/><result column="activity_id" property="activityId"/><result column="activity_name" property="activityName"/><result column="activity_desc" property="activityDesc"/><result column="create_time" property="createTime"/><result column="update_time" property="updateTime"/></resultMap><select id="queryActivityById" parameterType="cn.bugstack.mybatis.test.po.Activity" resultMap="activityMap">SELECT activity_id, activity_name, activity_desc, create_time, update_timeFROM activity<trim prefix="where" prefixOverrides="AND | OR" suffixOverrides="and"><if test="null != activityId">activity_id = #{activityId}</if></trim></select>
</mapper>

 单元测试

public class ApiTest {private Logger logger = LoggerFactory.getLogger(ApiTest.class);private SqlSession sqlSession;@Beforepublic void init() throws IOException {// 1. 从SqlSessionFactory中获取SqlSessionSqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsReader("mybatis-config-datasource.xml"));sqlSession = sqlSessionFactory.openSession();}@Testpublic void test_queryActivityById() throws IOException {// 2. 获取映射器对象IActivityDao dao = sqlSession.getMapper(IActivityDao.class);// 3. 测试验证Activity req = new Activity();req.setActivityId(100001L);Activity res = dao.queryActivityById(req);logger.info("测试结果:{}", JSON.toJSONString(res));}
}

执行单元测试,结果

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

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

相关文章

SpringCloud系列篇:核心组件之声明式HTTP客户端组件【远程消费】

&#x1f973;&#x1f973;Welcome Huihuis Code World ! !&#x1f973;&#x1f973; 接下来看看由辉辉所写的关于SpringCloud的相关操作吧 目录 &#x1f973;&#x1f973;Welcome Huihuis Code World ! !&#x1f973;&#x1f973; 一. 远程消费组件是什么 二. 远程消…

竞赛练一练 第22期:NOC大赛每日一练,python题目刷题第7天,包含答案解析

题目来自:NOC大赛创客智慧编程赛项Python 复赛模拟题(一) 第一题: 编写一个彩票游戏:随机生成一个不重复的五位数作为彩票号,游戏提示用户输入一个五位整数,然后根据下面的规则判断用户是否能赢得奖金,最后要求输出彩票号和奖金。 (1)若用户输入的数字和彩票的数字完…

c语言考试代码

文章目录 文件操作题 “%”&#xff1a;格式化字符串的起始标志。 “0”&#xff1a;表示使用零来填充输出字段的宽度。 “4”&#xff1a;表示输出字段的宽度为4个字符&#xff0c;如果输出的十六进制数不足4位&#xff0c;则在左边用零进行填充。 “x”&#xff1a;表示以十六…

MySql海量数据存储与优化

一、Mysql架构原理和存储机制 1.体系结构 2.查询缓存 3.存储引擎 存储引擎的分类 innodb&#xff1a;支持事务&#xff0c;具有支持回滚&#xff0c;提交&#xff0c;崩溃恢复等功能&#xff0c;事务安全myisam:不支持事务和外键&#xff0c;查询速度高Memory&#xff1a;利…

Spring Boot实现数据加密脱敏:注解 + 反射 + AOP

文章目录 1. 引言2. 数据加密和脱敏的需求3. Spring Boot项目初始化4. 敏感数据加密注解设计5. 实现加密和脱敏的工具类6. 实体类和加密脱敏注解的使用7. 利用AOP实现加密和脱敏8. 完善AOP切面9. 测试10. 拓展功能与未来展望10.1 加密算法的选择10.2 动态注解配置 11. 总结 &am…

CHS_02.1.1.2+操作系统的特征

CHS_02.1.1.2操作系统的特征 操作系统的四个特征并发这个特征为什么并发性对于操作系统来说是一个很重要的基本特性资源共享虚拟异步性 各位同学 大家好 在这个小节当中 我们会学习 操作系统的四个特征 操作系统有并发 共享 虚拟和异部这四个基本的特征 其中 并发和共享是两个…

机器人的末端执行器由什么零件组成,有什么作用。

问题描述&#xff1a;机器人的末端执行器由什么零件组成&#xff0c;有什么作用。 问题解答&#xff1a; 机器人的末端执行器是机器人机械结构的最末端部分&#xff0c;用于执行具体的任务和与环境进行交互。末端执行器通常由多个零部件组成&#xff0c;其主要作用是完成机器…

log4j RCE漏洞原理分析及检测

实现原理 log4j支持使用表达式的形式打印日志&#xff0c;比如 logger.info("system propety: ${sys:user.dir}");问题就在与表达式支持非常多样&#xff0c;其中有一个jndi就是今天的主题 logger.info("system propety: ${jndi:schema://url}");jdk将从…

学习笔记——C++ do while语句

作用&#xff1a;满足循环条件&#xff0c;执行循环语句 语法&#xff1a;do{循环语句}while{循环条件}&#xff1b; 注意&#xff1a;与while的区别在于do while 会先执行一次循环语句&#xff0c;再判断循环条件。 示例&#xff1a;打印0-9的数字 #include<bits/stdc.h…

图神经网络|9.3 邻接矩阵的变换

由于邻接矩阵中一般不会&#xff08;i,i&#xff09;等于1&#xff0c;除非第i个点上有自环。 而如果用邻接矩阵去乘上特征矩阵&#xff0c;那么将丢失自身向自身的贡献。 此时可以再邻接矩阵的基础上&#xff0c;再加上一个单位阵&#xff0c;从而使得最终的结果包含自身对整体…

RedisInsight - Redis官方可视化工具

一、RedisInsight 简介 RedisInsight 是一个直观高效的 Redis GUI 管理工具&#xff0c;它可以对 Redis 的内存、连接数、命中率以及正常运行时间进行监控&#xff0c;并且可以在界面上使用 CLI 和连接的 Redis 进行交互&#xff08;RedisInsight 内置对 Redis 模块支持&#…

JavaWeb——新闻管理系统(Jsp+Servlet)之jsp新闻新增

java-ee项目结构设计 1.dao:对数据库的访问&#xff0c;实现了增删改查 2.entity:定义了新闻、评论、用户三个实体&#xff0c;并设置对应实体的属性 3.filter&#xff1a;过滤器&#xff0c;设置字符编码都为utf8&#xff0c;防止乱码出现 4.service:业务逻辑处理 5.servlet:处…

一文讲透使用SPSS统计分析软件绘制双轴线图

双轴线图主要用来展示两个因变量和一个自变量的关系&#xff0c;并且两个因变量的数值单位不同时的情形。具体来说&#xff0c;双轴线图是指在一幅图上有一个横轴和两个纵轴&#xff0c;适用于三个变量。两个纵轴分别表示一个变量&#xff0c;横轴变量同时适用于两个纵轴上的变…

【InternLM】Lagent智能体工具调用实践浦语·灵笔(InternLM-XComposer)图文理解创作Demo练习

目录 前言一、Lagent智能体工具1-1、什么是智能体&#xff1f;1-2、Lagent智能体 二、InternLM-XComposer&#xff08;图文理解创作模型介绍&#xff09;三、Lagent调用实践3-0、环境搭建3-1、创建虚拟环境3-2、导入所需要的包3-3、模型下载3-4、Lagent安装3-5、demo运行 四、I…

【Navigation】global_planner 源码解析

全局规划器 global_planner 功能包 文章目录 global_planner 功能包结构1、plan_node.cpp2、planner_core.cpp3、astar.cpp4、dijkstra.cpp5、quadratic_calculator.cpp6、grid_path.cpp7、gradient_path.cpp8、orientation_filter.cpp全局规划大都基于静态地图进行规划,产生路…

完善 Golang Gin 框架的静态中间件:Gin-Static

Gin 是 Golang 生态中目前最受用户欢迎和关注的 Web 框架&#xff0c;但是生态中的 Static 中间件使用起来却一直很不顺手。 所以&#xff0c;我顺手改了它&#xff0c;然后把这个改良版开源了。 写在前面 Gin-static 的改良版&#xff0c;我开源在了 soulteary/gin-static&a…

超维空间M1无人机使用说明书——52、ROS无人机二维码识别与降落

引言&#xff1a;使用二维码引导无人机实现精准降落&#xff0c;首先需要实现对二维码的识别和定位&#xff0c;可以参考博客的二维码识别和定位内容。本小节主要是通过获取拿到的二维码位置&#xff0c;控制无人机全向的移动和降落&#xff0c;分为两种&#xff0c;一种是无人…

C#中CultureInfo.CreateSpecificCulture(String) 方法

目录 一、CultureInfo 类 二、CultureInfo.CreateSpecificCulture(String) 方法 1.定义 2.示例 一、CultureInfo 类 提供有关特定区域性&#xff08;对于非托管代码开发&#xff0c;则称为“区域设置”&#xff09;的信息。 这些信息包括区域性的名称、书写系统、使用的日…

Intel x86架构之APIC

我是在处理一个网卡中断分发问题时看的这些内容&#xff0c;因为是外部中断到处理器的分发问题&#xff0c;因此我关注的重点是I/O APIC和外部设备中断&#xff0c;所以下面这部分内容以及接下来的两篇文章都是从手册里挑着看的。 全文来自Intel开发者手册&#xff1a;Intel? …

Tomcat Notes: Deployment File

This is a personal study notes of Apache Tomcat. Below are main reference material. - YouTube Apache Tomcat Full Tutorial&#xff0c;owed by Alpha Brains Courses. https://www.youtube.com/watch?vrElJIPRw5iM&t801s 1、Tomcat deployment1.1、Two modes of …