新闻详情

新闻详情

首页 / 资讯中心 / 详情

JSqlParser 解析报错 unexpected token 排查指南

发布时间:2026/10/1 6:02:23来源:尧图网络
JSqlParser 解析报错 unexpected token 排查指南
线上一个报表接口突然开始批量报错堆栈最外层是 JSQLParserException往下翻两行就是Caused by: net.sf.jsqlparser.parser.ParseException: Encountered unexpected token: status。最让人抓狂的是把这条 SQL 从日志里粘出来丢进数据库客户端跑得又快又好。这种数据库认、解析器不认的割裂感是我在排查 jsqlparser、ParseException、unexpected token 这类问题时遇到最多的场景。JSqlParser 本身不连数据库它只是一台SQL 语法检查机——把字符串变成 AST抽象语法树。真正需要它的地方往往在你看不见的中间层MyBatis-Plus 的分页插件和多租户插件要改写你的 SQL数据权限组件要在 where 后面插条件血缘分析工具要提取表名SQL 审计要判断是不是全表扫描。这些环节只要解析失败整个链路就断了。这篇内容适合两类人一类是被这个报错卡住正在救火的后端同学另一类是准备把 SQL 解析能力接入自己系统的架构同学。我会把报错信息的读法、根因排查链路、修复方案的分层取舍以及生产环境怎么加固一次讲透。1. 先从报错堆栈里抠出三个关键坐标1.1 别盯着第一行真正的信息在 Caused by很多同学一看报错就去搜异常类名结果搜到一堆不相关的答案。这里得先把异常链捋清楚。CCJSqlParserUtil.parse()这类工具方法抛出的是net.sf.jsqlparser.JSQLParserException它是个包装异常本身几乎不携带任何有用信息message 往往就是一句干巴巴的 Encountered unexpected token。真正描述语法问题的是它内部的net.sf.jsqlparser.parser.ParseException这个是 JavaCC 生成的解析器抛出来的原始异常。所以排查第一步是把日志往下拉找到Caused by:那一行。如果你们的日志框架只打了第一行那得临时把 JSqlParser 相关的 logger 级别调成 DEBUG或者在 catch 块里主动打印e.getCause()。注意有些框架会把 cause 吞掉只保留外层异常。如果你在日志里只看到 JSQLParserException 却看不到 token 细节别急着搜先在本地写个 main 方法把 SQL 跑一遍现场日志不一定信得过。1.2 token 文本、行列号、期望集合这三样缺一不可ParseException 是 JavaCC 标准产物它暴露了几个非常实用的字段。currentToken是出错位置前后的 token 信息currentToken.next才是真正无法识别的那个expectedTokenSequences配合tokenImage能算出解析器本来想看到什么。这套信息在排查时价值极高。比如报错说Encountered unexpected token: status同时期望集合里全是,)asfrom之类的符号那你基本能判定解析器认为status出现的位置不对多半是前面某个标识符被它吞掉了或者status本身被当成了关键字而不是列名。反过来如果期望集合里出现了K_END、K_WHEN这种那大概率是一段 CASE WHEN 写残了。想看期望集合可以这么打印import net.sf.jsqlparser.parser.ParseException; public class TokenDump { public static void dump(ParseException pe) { System.out.println(出错 token pe.currentToken.next.image); System.out.println(是否文件末尾 pe.currentToken.next.image.equals()); System.out.println(行 pe.currentToken.next.beginLine , 列 pe.currentToken.next.beginColumn); String[] images pe.tokenImage; for (int[] seq : pe.expectedTokenSequences) { StringBuilder sb new StringBuilder(); for (int t : seq) { sb.append(images[t]).append( ); } System.out.println(解析器期望: sb.toString().trim()); } } }currentToken.next.image为空字符串时表示已经读到输入末尾也就是 SQL 被截断了或者括号没闭合。我遇到过好几次这种情况最后追下去发现是某个业务方在配置平台里填 SQL 时文本框有长度限制长 SQL 被悄悄截掉了尾巴。1.3 版本迭代会改变报错文案和字段结构JSqlParser 从 4.x 走到 5.x改动不小。5.0 起官方把 JDK 基线抬高到了 11具体以你所用的那个小版本说明为准部分 API 做了调整解析器的开关方法也换过名字。有些老教程里的写法在新版本上编译不过这不是你写错了是版本差异。更隐蔽的是依赖冲突。MyBatis-Plus 的某些版本自带一份 JSqlParser 依赖你自己项目里又显式声明了另一个版本Maven 按最近优先挑了其中一个你以为升级了其实运行时加载的还是旧的。这种情况的表现是本地测试好好的一到测试环境就报解析失败。排查命令mvn dependency:tree -Dincludescom.github.jsqlparser:jsqlparser或者 Gradle 下gradle dependencyInsight --dependency jsqlparser --configuration runtimeClasspath只有确认运行时真正加载的版本后面所有的调优才有意义。这一点我在两个项目上吃过亏值得单独强调。2. 数据库能跑的 SQL解析器为什么跑不动2.1 解析器的语法是白名单数据库的语法是宽容模式用一个生活化的类比数据库像一位干了三十年的老会计你递过去的账本格式稍微不规范他扫一眼就懂了还会顺手帮你纠正JSqlParser 像个刚上岗的严格门卫手里拿着一份语法清单清单上没有的写法一律拦下。这个差异的根源在于两者目标不同。数据库引擎关心的是能不能执行出正确结果所以对语法糖的容忍度很高历史包袱也就背上了——MySQL 里LIMIT 10, 20和LIMIT 20 OFFSET 10都认!和也认。而 JSqlParser 要生成结构化的 AST每种语法都必须有明确的产生式规则没实现就是没实现不会做模糊匹配。所以数据库能跑和解析器能解析是两个完全独立的命题不存在能跑就一定能解析的推论。想通这一点排查时的心态会稳很多。2.2 方言裂缝从 MySQL 特有写法到 PG 类型转换JSqlParser 在语法设计上偏 ANSI SQL同时对主流方言做了不同程度的兼容。兼容到什么程度各版本不一样。下面这张表是我整理的高频踩雷点按方言归类实际项目里对号入座能省不少时间。方言典型写法JSqlParser 常见反应MySQLGROUP_CONCAT(x SEPARATOR ,)SEPARATOR处报 unexpected tokenMySQL# 这是行注释部分版本不识别#开头注释MySQLLIMIT 10, 20老版本需要改写为LIMIT 20 OFFSET 10MySQLON DUPLICATE KEY UPDATE需要较新版本才完整支持PostgreSQLx::numeric老版本需改写为cast(x as numeric)PostgreSQLNULLS LAST版本差异较大需实测Oraclea.id b.id()外连接写法兼容度有限建议改写为 joinOracleCONNECT BY PRIOR需要特定版本与开关Hive / SparkLATERAL VIEW explode(...)基本不支持需要绕开Snowflake / BigQueryQUALIFY、SELECT * EXCEPT(...)基本不支持这张表的核心信息不是哪个版本支持什么而是方言特有语法是 ParseException 的第一大来源。如果你的系统要同时对接多种数据源从一开始就得想清楚是全量解析、部分解析还是只解析公共子集。2.3 占位符与模板表达式是隐形杀手这一条比方言问题更隐蔽因为它跟 SQL 语法本身没关系。MyBatis 的#{}和${}在真正执行前会被替换成?或字面量但如果你在 SQL 还没走完参数替换的阶段就把它交给了 JSqlParser解析器看到#{id}里的#或者{直接就懵了。报错 token 往往就是{或者}。同理?占位符 JSqlParser 是认的它会生成 JdbcParameter 节点但:name这种命名参数在部分版本上不认。还有一类是动态 SQL 标签残留比如TableNameParser之类的工具处理if test...时没洗干净把尖括号带进了 SQL 字符串。处理思路很简单解析必须在参数替换之后、或者使用专门的占位符替换工具先跑一遍。如果只是想做表名提取这类静态分析可以先把#{xxx}用正则替换成?再交给解析器。// 静态分析前的预处理仅用于表名/字段提取等场景 String normalized rawSql .replaceAll(#\\{[^}]*}, ?) .replaceAll(\\$\\{[^}]*}, ?); Statement stmt CCJSqlParserUtil.parse(normalized);注意这个替换只适用于静态分析绝对不能用在真正要执行的 SQL 上否则会引入参数注入风险。3. 按报错 token 反查根因的高频清单3.1 保留字被当成列名或别名这是最经典的场景也是最容易被忽略的。你的表里有个字段叫status、order、key、desc、type、value平时写select status from t一点问题没有忽然某天加了个as order的别名解析就炸了。原因在于 JSqlParser 的词法分析阶段就把一些单词标记成了关键字 token而不是普通标识符。当它在这个位置只接受标识符时关键字就过不去。修复方式有两条路给标识符加引号。MySQL 用反引号order标准 SQL 和 PG 用双引号order。JSqlParser 对这两种引号都有支持但要注意反引号需要解析器开启对应开关部分版本默认关闭得用withSquareBracketQuotation之类的配置具体 API 以版本为准。改别名。这是最省事的做法把as order改成as sort_order不影响业务逻辑一劳永逸。我个人的偏好是第二条。别名对用户不可见改掉零成本而加引号会在不同数据库之间产生兼容问题将来换数据源时又是一堆坑。3.2 函数、运算符与子句的兼容边界函数这块要分三类看。第一类是标准聚合函数count、sum、avg、max、min各版本都稳。第二类是 JSqlParser 内置识别的函数比如now()、current_timestamp这些会被解析成特定节点。第三类是未知函数JSqlParser 的处理策略通常是把它当成Function节点接受下来——能解析但不做语义校验。所以my_udf(a, b)一般不会报错。真正容易炸的是函数后面跟的特殊语法比如GROUP_CONCAT(x SEPARATOR ,)里的SEPARATOR、COUNT(DISTINCT a, b)里多参数 DISTINCT 的老版本兼容性、以及窗口函数OVER (PARTITION BY ... ORDER BY ... ROWS BETWEEN ...)里各种 frame 子句的组合。运算符方面::类型转换、||字符串拼接部分版本会歧义、~正则匹配这三兄弟在不同版本上表现差异明显。遇到报错先把它们改写成函数形式x::int写成cast(x as int)a || b写成concat(a, b)通常能立刻绕过。3.3 注释、Hint、多语句与字符编码这一类问题的共同点是SQL 肉眼看着完全正常但里面混进了不可见或者不被识别的东西。Hint 注释/* INDEX(t idx_a) */这种 Oracle 风格的 hint部分版本解析不完整会在或者括号位置报错。多语句select 1; select 2;用parse()会失败得改用parseStatements()。这是个非常低级的错误但在批量执行场景里特别常见。文件编码SQL 文件如果带 BOM 头第一个 token 前面会多出\uFEFF报错位置永远在第一行第一列附近。用文本编辑器确认一下以 UTF-8 无 BOM 保存就行。全角标点从需求文档或者聊天记录里复制 SQL 时很容易带上全角逗号和全角括号。这两个字符在词法分析阶段就是非法字符。不可见字符零宽空格\u200B、不间断空格\u00A0肉眼完全看不出来可以通过sql.chars()遍历打印码点来定位。提示排查编码类问题有个笨办法但极有效——把可疑 SQL 逐字符打印码点凡是不在 32 到 126 区间加上常规中文允许范围的字符全部人工确认一遍。3.4 拼接残留空串、多余 AND、尾部逗号动态拼 SQL 的系统里这类问题占了相当比例。典型如所有条件都不满足最终拼出select * from t where 11 andSQL 末尾多了个and。这个例子比较极端但拼接逻辑一复杂完全可能出现。列表条件拼成where id in ()括号里空着。order by后面字段列表为空拼出order by解析器期望的是排序字段结果读到末尾报的就是end of input或者报后续语句片段意外出现。用了StringBuilder拼接但没trim()末尾多出换行和制表符虽然通常不影响解析但如果后面紧跟注释符号可能会把下一段有效 SQL 变成注释。排查这类问题最直接的办法是把最终 SQL 完整打进日志注意是完整的不是截断的前 200 字符。我见过不止一次因为日志截断或者用了%200s之类的格式化导致看到的 SQL 是截断版白排查了半天。4. 从报错点到最小复现用例的定位链路4.1 二分法裁剪先缩小到一行拿到一个几百行的动态 SQL别一上来就逐行读。先用二分法砍。具体做法把 SQL 按逗号、按select/from/where关键字切成段先保留前半段加一个能闭合的结构看能不能解析能解析就把后半段加进来不能解析就把这段再对半砍。重复三四轮通常能定位到一个非常小的片段。裁剪时有个技巧要注意保持括号和引号配对。如果你砍掉了半个括号会得到一个全新的解析错误反而干扰判断。所以砍的时候优先砍完整的子句比如整个order by ...段、整个where里的某个条件。4.2 写一个十行代码的复现 main 方法定位到最小片段后别在业务代码里反复改反复跑。新建一个独立的 main 方法把 SQL 直接写死跑起来看结果。这样做的价值在于排除掉参数替换、动态标签、多租户插条件这些干扰因素得到一个确定性的结论。import net.sf.jsqlparser.JSQLParserException; import net.sf.jsqlparser.parser.CCJSqlParserUtil; import net.sf.jsqlparser.statement.Statement; public class ReproMain { public static void main(String[] args) { String sql select id, group_concat(name separator ,) as names from t_user group by id; try { Statement stmt CCJSqlParserUtil.parse(sql, parser - parser.withAllowComplexParsing(true)); System.out.println(解析成功: stmt); } catch (JSQLParserException e) { System.out.println(解析失败cause e.getCause()); Throwable c e.getCause(); while (c ! null) { System.out.println( -- c.getClass().getName() : c.getMessage()); c c.getCause(); } } } }跑通这个 main你就得到了一个可复现的最小案例。这个案例的价值不只是解决当前问题将来升级 JSqlParser 版本时它可以直接变成一个回归测试用例。4.3 用 AST 回写确认解析边界解析成功之后有个很有用的验证手段把 AST 再转回字符串打印出来看它跟你原始 SQL 的差异。Statement stmt CCJSqlParserUtil.parse(sql); System.out.println(stmt.toString()); // 回写回写结果能告诉你解析器是怎么理解你的 SQL 的。如果某个子句在回写结果里消失了说明解析器把它当成注释丢掉了如果某个字段被改写成了别的形式说明它走了不同的产生式分支。我在排查一个CASE WHEN嵌套问题时就是靠回写发现整个else分支被吞掉了进而定位到是括号层级写错。如果是提取表名的场景还可以配合TablesNamesFinder来验证import net.sf.jsqlparser.util.TablesNamesFinder; import java.util.List; TablesNamesFinder finder new TablesNamesFinder(); ListString tables finder.getTableList(stmt); System.out.println(tables);表名列表和预期不符时说明解析结果的 AST 结构有问题这比直接看报错更容易发现静默错误——也就是解析没报错但结果不对的情况。这种情况在生产里比显式报错更危险因为你不容易发现。4.4 现场 SQL 抓取的两个坑第一个坑是日志截断前面已经说过。第二个坑更隐蔽参数化日志打印的不是真实 SQL。很多框架在日志里打的是带?的 SQL 加上参数列表你把它拼起来的时候字符串参数没加引号、日期没转义拼出来的 SQL 和实际执行的不一样。如果你拿这个拼凑版去复现可能会复现出一个假问题。正确的做法是从框架的参数拦截器里拿最终 SQL或者临时开一个 DEBUG 开关让执行层把真正发给数据库的字符串打出来。这个动作虽然麻烦但比在错误的方向上浪费两小时划算得多。5. 修复方案分层从改 SQL 到换解析实现5.1 最小改动优先改 SQL 本身能改 SQL 解决的时候优先改 SQL。因为改 SQL 的收益是确定的、即时的不需要等版本升级也不会影响其他模块。常见的改写对照原写法改写为备注group_concat(x separator ,)group_concat(x)或调整分隔符处理分隔符逻辑挪到业务层x::intcast(x as int)标准写法兼容性最好a.id b.id()a left join b on a.id b.id语义等价解析器友好limit 10, 20limit 20 offset 10标准 SQL 分页写法as orderas sort_order别用保留字做别名# 注释-- 注释或/* 注释 */避免 MySQL 特有注释改的时候有一条原则只改语法形式不改语义。如果有人提议顺手把这条 SQL 优化一下请拒绝——排查阶段混入重构风险会成倍放大。5.2 中间层解析前的预处理与方言适配如果 SQL 来自外部输入或者数据库里的历史配置改不了那就得在解析前做一层预处理。预处理层要做的事包括占位符归一化把#{}换成?、全角标点转半角、去除 BOM 和不可见字符、剔除客户端指令比如DELIMITER、把多语句拆成单条。public class SqlPreprocessor { public static String normalize(String raw) { if (raw null) { return null; } String s raw.replace(\uFEFF, ) .replace(\u200B, ) .replace(\u00A0, ) .replaceAll(#\\{[^}]*}, ?) .replaceAll(\\$\\{[^}]*}, ?); // 全角标点转半角只处理 SQL 里常见的几个 s s.replace(, ,).replace(, ().replace(, )) .replace(, ;).replace(’, \); return s.trim(); } }这层代码看着不高明但它在生产里救火的效果非常直接。要注意的是预处理必须是幂等的、可重复执行的否则多次处理会把 SQL 改坏。另外如果确定只处理某一种方言可以在解析器上开对应的开关。比如处理带方括号引号的 SQL Server 语法Statement stmt CCJSqlParserUtil.parse(sql, parser - parser.withSquareBracketQuotation(true));具体有哪些开关建议直接看所用版本CCJSqlParser类的公开方法列表比翻文档快。5.3 架构层版本对齐、能力边界与降级设计当同一类报错反复出现说明这不是个案而是架构层的取舍问题得往上看。第一件事是版本对齐。把运行时的 JSqlParser 版本固定下来在父 POM 里统一管理禁用传递依赖带来的隐式版本。做法是在依赖里加exclusions把框架自带的 JSqlParser 排除掉只保留显式声明的那一份。第二件事是明确能力边界。想清楚你到底需要解析到什么程度。如果只需要提取表名和字段名做血缘分析那完全没必要要求全量解析——正则加轻量词法扫描在大多数场景下够用而且永远不会因为某个方言函数报错。如果是做 SQL 改写分页、租户隔离那必须全量解析这时候就得接受只支持语法子集这个现实并在入口处做校验和拦截。第三件事是降级设计。解析失败时的行为必须明确不能直接抛异常把请求打挂。合理的降级策略分场景分页改写失败退化成不进行物理分页走内存分页同时打点上报。租户条件注入失败这个不能降级必须拦截并报错因为涉及数据隔离安全。血缘分析失败跳过这条 SQL 并记录不影响主流程。这个区分很重要。涉及安全的解析失败绝不能静默降级这是我在设计评审里反复强调的一条。6. 生产环境里的加固与观测6.1 解析结果缓存与超时保护SQL 解析是纯 CPU 操作正常情况下很快但复杂嵌套 SQL 可能触发解析器的回溯耗时急剧上升。JSqlParser 提供了超时配置超时后会抛异常而不是把线程挂死。如果你的系统里有大量重复 SQL比如固定的报表查询给解析结果加缓存收益很明显。缓存 key 用标准化之后的 SQL 字符串value 是解析后的 Statement。注意 Statement 对象在多线程下不一定线程安全稳妥做法是缓存 SQL 字符串本身或者缓存一个轻量的元数据对象表名列表、字段列表而不是缓存 AST 本体。提示解析超时值不要设得太短。我见过设成 1 秒的配置结果稍微复杂一点的 SQL 全部超时最后被迫改成 10 秒。具体数值建议在压测环境实测后确定。6.2 上线前的 SQL 门禁最有效的加固手段其实是把问题拦在上线前。做法是写一个测试扫描项目里所有的 Mapper XML 或者 SQL 配置文件把里面每一条静态 SQL 都提取出来跑一遍解析。MyBatis 的 XML 里有动态标签可以只跑静态部分或者用参数模拟的方式把标签全部展开成最长的组合。这个测试的价值在于它把偶发的运行时错误变成了必现的构建失败。加了这道门禁之后新人改 SQL 引入不兼容语法时CI 会在合并前就拦下来不用等到线上出问题。代价是维护成本——需要写 XML 解析逻辑需要维护参数组合。我的建议是先做最简单的版本只解析静态 SQL跳过动态标签。这已经能覆盖七八成的问题。6.3 日志里该记什么才够排查出现解析失败时日志里至少要包含三样东西完整的原始 SQL不截断、异常链的完整 cause、以及解析器的版本号。版本号这条容易被忽略但它决定了你去查哪个版本的语法支持列表。可以在应用启动时打印一次System.out.println(JSqlParser version net.sf.jsqlparser.parser.CCJSqlParser.class.getPackage().getImplementationVersion());这个值在 IDE 里跑可能是 null因为不是从 jar 加载的部署到服务器上就正常了。另外建议做一点轻量的埋点按出错 token 分类统计。统计一段时间后你会发现八成的问题集中在那几种语法上接下来就可以针对性地做预处理或者推动业务方改 SQL而不是被动地一条条修。7. 那些同样叫 unexpected token 但完全无关的报错7.1 前端 JavaScript 的 SyntaxError搜unexpected token的时候你会看到大量前端相关的结果比如Uncaught SyntaxError: Invalid or unexpected token。这跟 JSqlParser 毫无关系。前端的这类报错通常来自几个地方字符串里混进了非法字符比如从富文本复制的引号、JSON 解析时遇到了不合法内容、ES 模块语法在不支持的环境里被执行、以及源文件编码和声明不匹配导致中文字符被解析坏。区分方法很简单看堆栈顶部的文件是不是.js/.vue看错误类型是SyntaxError还是ParseException。两者唯一的共同点是英文单词巧合相同。7.2 接口鉴权里的 invalid token另一类高频搜索结果跟鉴权有关比如形如unexpected status 401 unauthorized: invalid token的提示。这说的是身份凭证无效或者过期属于 HTTP 层面的鉴权问题跟语法解析没有任何交集。看到这个报错排查方向应该是凭证是否过期、请求头是否正确携带、服务端时钟是否偏移。如果盲目去搜 SQL 解析的内容会完全跑偏。7.3 快速分流的三步判断法遇到任何带 unexpected token 字样的报错我一般按这三步分流看异常类型全名。含jsqlparser.parser.ParseException的才是 SQL 解析问题含SyntaxError的是脚本语言问题含401/unauthorized的是鉴权问题。看报错位置的坐标系。SQL 解析的报错会带行号列号指向一段 SQL 文本前端报错指向具体的 js 文件行鉴权报错通常没有行列号。看去掉网络层后是否复现。在本地 main 方法里跑一条 SQL 能复现的只可能是解析问题。这三步花不了一分钟但能避免在错误的搜索方向上浪费大量时间。最后分享一个我自己踩出来的习惯每次排查完一个 ParseException我都会把那条出错的最小 SQL 片段和对应的修复方式加进项目里的一个小测试类SqlParseCompatibilityTest。一年多下来这个类攒了四十多条用例覆盖了分页、租户、多数据源、各种方言函数。它的作用在升级 JSqlParser 版本的时候体现得最明显——改个版本号跑一遍哪些语法不兼容一目了然比翻 changelog 靠谱得多。这个做法没有什么技术含量但它实实在在帮我省掉了好几次通宵。
网站建设高端定制企业官网
RELATED

相关资讯

更多精彩内容,欢迎继续阅读

较早相关资讯

最新相关资讯

Codex 终端 AI 编程助手报错排查指南:从配置到网络的高频问题解决 2026/10/1 7:11:45

Codex 终端 AI 编程助手报错排查指南:从配置到网络的高频问题解决

1. Codex 装完却跑不起来,问题到底卡在哪Codex 这类终端里的 AI 编程助手,装完之后敲命令没反应、报一堆看不懂的错,几乎是每个刚上手的人都会经历的阶段。我自己第一次配的时候,光是让它正常连上模型就折腾了大半天,中…

阅读更多 →
基于GAT和GRU的动态信任评估模型DTEM实践详解 2026/10/1 7:11:45

基于GAT和GRU的动态信任评估模型DTEM实践详解

简介:图神经网络(GNN)是处理关系数据的强大范式,它通过消息传递聚合邻居信息,让模型能够学习节点间的复杂依赖。在众多GNN变体中,图注意力网络(GAT)利用注意力机制为不同邻居分配权重…

阅读更多 →
事件驱动模型:油价回落与加息预期降温下黄金价格变化的AI分析框架 2026/10/1 7:11:45

事件驱动模型:油价回落与加息预期降温下黄金价格变化的AI分析框架

【AI摘要】本文通过AI事件驱动模型,结合黄金价格、原油价格、美债收益率、美联储加息概率及经济数据等特征变量,分析油价变化与货币政策预期如何通过通胀、利率和机会成本等传导机制影响黄金价格,并以周二黄金反弹为案例,构建能源…

阅读更多 →
CTF备赛全攻略:从隐写、密码学到PWN的题型识别与解题流程 2026/10/1 7:11:45

CTF备赛全攻略:从隐写、密码学到PWN的题型识别与解题流程

2. 隐写与杂项:性价比最高的分类,从图片、流量、压缩包里挖出 flag2.1 LSB 隐写:最经典的信息隐藏手法2.2 文件分离、文件头修复与压缩包套娃2.3 流量取证、键盘密码、二维码等杂项3. 密码学:会用编码工具只是起点,识别…

阅读更多 →
OpenClaw + MCP:让 AI 助手连接任意工具的终极方案(TaoToken 统一 Key 接入版) 2026/10/1 7:11:38

OpenClaw + MCP:让 AI 助手连接任意工具的终极方案(TaoToken 统一 Key 接入版)

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

阅读更多 →
OpenClaw 通过 Nanobot 源码学习架构(7)Memory:把记忆配置改到 TaoToken 的实操拆解 2026/10/1 7:11:38

OpenClaw 通过 Nanobot 源码学习架构(7)Memory:把记忆配置改到 TaoToken 的实操拆解

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

阅读更多 →

今日资讯

本周资讯

本月资讯

看完文章仍有疑问?

联系尧图顾问,获取一对一建站咨询

立即免费咨询 📞 400-888-8888
📞 ✉