ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

PHP-Parser 5.0 升级完全指南:从 4.x 迁移的关键变更、AST 结构调整与实战适配

PHP-Parser 5.0 升级完全指南:从 4.x 迁移的关键变更、AST 结构调整与实战适配 PHP-Parser 5.0 升级完全指南从 4.x 迁移的关键变更、AST 结构调整与实战适配【免费下载链接】PHP-ParserA PHP parser written in PHP项目地址: https://gitcode.com/GitHub_Trending/ph/PHP-Parser本指南以 PHP-Parser 官方升级文档UPGRADE-5.0.md为骨架结合当前仓库PHP-Parser一个用 PHP 编写的 PHP 解析器的实际源码与测试系统梳理从 4.x 升级到 5.0 的全部破坏性变更。读完本文你将掌握新版解析器工厂的三种创建方式、PhpVersion 版本定位机制、AST 节点表示的统一化改动、NodeTraverser 回调顺序变化以及 pretty printer 的格式化行为变化并能够独立完成迁移与测试适配。一、升级概览5.0 的三大主题PHP-Parser 5.0 是一次大规模的架构收敛版本其升级逻辑可以归纳为三条主线统一版本定位移除专用 PHP 5 解析器将目标 PHP 版本提升为解析器构造的一等参数PhpVersion并把工厂方法从单一的create()拆分为三种语义明确的方法。统一 AST 表示大量语义相同、节点不同的历史包袱被清理——throw统一为表达式、解构统一为List_、Name改为字符串存储、嵌套代码块用显式Stmt\Block表示、若干节点被重命名或移动。贴近当代规范pretty printer 默认输出对齐 PSR-12 风格运算符优先级建模更精确同时通过phpVersion选项让输出兼容指定的老版本 PHP。此外token 表示、lexer API、节点遍历回调顺序等基础设施也发生了不兼容的变化。下面逐项展开。二、运行环境与 PHP 版本要求2.1 运行版本要求PHP-Parser 5.0 本身要求PHP 7.4 或更高版本才能运行。但运行所需的 PHP 版本与可解析的 PHP 代码版本是两回事即使运行在较新版本上你依然可以解析面向旧版本 PHP 编写的代码——这正是引入PhpVersion参数的意义。2.2 PHP 5 解析支持的调整专用 PHP 5 解析器Parser\Php5已被移除取而代之的是让 PHP 7/8 解析器接受一个PhpVersion参数。从源码看ParserFactory.php 会根据目标版本选择 lexer 和解析器若目标是宿主版本host version使用普通Lexer否则使用Lexer\Emulative仿真 token以兼容旧版本语法目标版本 8.0时使用Parser\Php8否则使用Parser\Php7。指定较老的PhpVersion时解析行为会按版本回退。这些能力在 PhpVersion.php 中都有对应的判断方法例如行为条件源码方法$foo new Bar()允许赋值引用 newPHP 7.0allowsAssignNewByReference()非法八进制字面量089允许不报错PHP 7.0allowsInvalidOctals()字符串中的 unicode 转义\u{123}不解析PHP 7.0supportsUnicodeEscapes()类型提示被解释为类Name还是内建Identifier按版本如int在 5.6 是类名、在 7.0 是内建类型supportsBuiltinType()见BUILTIN_TYPE_VERSIONS表PhpVersion::fromString(5.6)这类构造方式底层由 fromComponents / fromString 实现内部以PHP_VERSION_ID风格整数如 50600、80000存储。需要注意PHP 5 解析能力并未完全保留以下几类代码无法再按 PHP 5 语义解析$$foo[0]这类在 PHP 5 和 PHP 7 中都有合法但解释不同的变量5.0 只构造 PHP 7 语义的 AST即($$foo)[0]而非${$foo[0]}。global $$var[0]形式的声明在 PHP 7 中不合法会直接产生解析错误仅在错误恢复模式下可以继续往下解析。PHP 7 解析器会接受许多 PHP 5 中非法的结构这一点与旧的专用 PHP 5 解析器情况相同。2.3 受影响的符号清单本次移除涉及以下符号PhpParser\Parser\Php5类被删除PhpParser\Parser\Multiple类被删除虽然与 PHP 5 支持无直接关系但失去 PHP 5 解析器后该功能不再有用ParserFactory::ONLY_PHP5与PREFER_PHP5选项被删除。三、ParserFactory三种新工厂方法替代 create()ParserFactory::create()被移除取而代之的是三个语义更精确的方法在 ParserFactory.php 中均有完整实现方法适用场景说明createForNewestSupportedVersion()不知道待解析代码的 PHP 版本假设太新比太旧更好。当前仓库实现中它委托给PhpVersion::getNewestSupported()当前为 8.4createForHostVersion()解析运行环境自身版本的代码委托给PhpVersion::getHostVersion()取PHP_MAJOR_VERSION/PHP_MINOR_VERSION且不使用任何 token 仿真见 ParserFactory.phpcreateForVersion(PhpVersion $version)明确知道待解析代码的 PHP 版本接受一个PhpVersion对象其中前两个方法自 4.18.0 起就已提供目的是让库作者可以同时兼容 PHP-Parser 4 与 5。重要语义PHP 版本只是一个尽力而为best-effort的弱提示。只要不涉及向后兼容性冲突解析器通常会接受更新版本的代码。例如指定版本8.0时class ReadOnly {}被视为合法类声明因为ReadOnly在 8.0 尚未成为保留字public readonly int $prop会导致解析错误但final public const X Y;在两种情况下都会被接受。迁移示例use PhpParser\ParserFactory; use PhpParser\PhpVersion; $factory new ParserFactory(); # Before $parser $factory-create(ParserFactory::PREFER_PHP7); # After大致等价于 PREFER_PHP7 行为 $parser $factory-createForNewestSupportedVersion(); # 或 $parser $factory-createForHostVersion(); # Before $parser $factory-create(ParserFactory::ONLY_PHP5); # After尽力而为地支持 $parser $factory-createForVersion(PhpVersion::fromString(5.6));仓库测试 ParserFactoryTest.php 用instanceof断言验证了工厂方法返回类型的正确性createForNewestSupportedVersion()返回Php8实例createForHostVersion()返回Parser接口实例。PrettyPrinterTest.php 和 CodeParsingTest.php 也大量使用createForVersion()按版本驱动解析与打印测试说明该方法已成为测试基础设施的核心。四、AST 表示变更五个关键结构调整4.1 throw 语句统一为表达式以前throw $e;使用Stmt\Throw_表示而出现在其他表达式内部如$x ?? throw $e的 throw 使用Expr\Throw_表示。5.0 中Stmt\Throw_被删除throw $e;现在表示为包含Expr\Throw_的Stmt\Expression# Code throw $e; # Before Stmt_Throw( expr: Expr_Variable( name: e ) ) # After Stmt_Expression( expr: Expr_Throw( expr: Expr_Variable( name: e ) ) )如果你曾针对Stmt\Throw_编写 visitor 或 pretty-printer 钩子需要改为处理Stmt\ExpressionExpr\Throw_的组合。4.2 数组解构统一为 List_ 节点以前list($x) $y使用Node\Expr\List_而[$x] $y使用Node\Expr\Array_与创建数组共用同一节点。5.0 中解构一律用Node\Expr\List_表示通过kind属性区分原始语法Node\Expr\List_::KIND_LIST—— 对应list()语法Node\Expr\List_::KIND_ARRAY—— 对应[]语法。这两个常量定义在 List_.php 中。节点对比# Code [$x] $y; # Before Expr_Assign( var: Expr_Array( items: array( 0: Expr_ArrayItem( key: null value: Expr_Variable( name: x ) byRef: false unpack: false ) ) ) expr: Expr_Variable( name: y ) ) # After Expr_Assign( var: Expr_List( items: array( 0: ArrayItem( key: null value: Expr_Variable( name: x ) byRef: false unpack: false ) ) ) expr: Expr_Variable( name: y ) )注意ArrayItem也已经从Node\Expr下移出见下文重命名节点因此 5.0 中它不再继承Node\Expr。4.3 Name 节点parts 数组改为 name 字符串以前Name节点的parts子节点以数组保存按命名空间分隔符拆分的各部分现在改为name子节点保存完整字符串。例如Foo\Bar以前Name(parts: [Foo, Bar])现在Name(name: Foo\Bar)从 Name.php 可以看到public string $name成为唯一核心子节点getSubNodeNames()返回[name]。为兼容迁移$name-getParts()可还原为数组形式实现为explode(\\, $this-name)见 Name.php构造函数同时接受字符串与数组两种形式prepareName 会自动把数组implode(\\, ...)成字符串getParts()自 4.16.0 起就已提供以支持同时兼容 4 与 5 的库。此外Name还提供getFirst()、getLast()、isUnqualified()、isQualified()、slice()、concat()等便捷方法帮助处理名称的常用操作。4.4 嵌套代码块引入显式 Stmt\Block以前{ ... }代码块总是被扁平化合并进父级语句列表。例如while ($x) { $a; { $b; } $c; }与if ($x) { $a; $b; $c; }会得到相同结构。5.0 中嵌套的{ $b; }块用显式的Stmt\Block节点表示但最外层的{ $a; { $b; } $c; }仍以普通数组放在stmts子节点中# Code while ($x) { $a; { $b; } $c; } # After Stmt_While( cond: Expr_Variable( name: x ) stmts: array( 0: Stmt_Expression( expr: Expr_Variable( name: a ) ) 1: Stmt_Block( stmts: array( 0: Stmt_Expression( expr: Expr_Variable( name: b ) ) ) ) 2: Stmt_Expression( expr: Expr_Variable( name: c ) ) ) )Block.php 是标准Stmt子类核心子节点为stmts。这一变化让代码块的词法结构即花括号的嵌套在 AST 中得以保留对格式化保留打印format-preserving pretty printing尤为重要。4.5 注释只挂到最外层节点以前注释会被赋值到起始位置相同的所有节点上现在只赋值给最外层节点# Code // Comment $a $b; # Before Stmt_Expression( expr: Expr_BinaryOp_Plus( left: Expr_Variable( name: a comments: array( 0: // Comment ) ) right: Expr_Variable( name: b ) comments: array( 0: // Comment ) ) comments: array( 0: // Comment ) ) # After Stmt_Expression( expr: Expr_BinaryOp_Plus( left: Expr_Variable( name: a ) right: Expr_Variable( name: b ) ) comments: array( 0: // Comment ) )迁移时遍历 AST 读取注释的逻辑无需大改仍从getComments()取但如果你依赖内层节点也带注释来定位注释归属就需要调整策略。五、节点重命名与层级调整一批节点被重命名或移动旧类名作为别名保留以实现向后兼容但Node::getType()总是返回新名称例如返回ClosureUse而非Expr_ClosureUse旧节点新节点说明Node\Scalar\LNumberNode\Scalar\Int_整数字面量Node\Scalar\DNumberNode\Scalar\Float_浮点字面量Node\Scalar\EncapsedNode\Scalar\InterpolatedString插值字符串Node\Scalar\EncapsedStringPartNode\InterpolatedStringPart不再继承Node\Scalar或Node\ExprNode\Expr\ArrayItemNode\ArrayItem不再继承Node\ExprNode\Expr\ClosureUseNode\ClosureUse不再继承Node\ExprNode\Stmt\DeclareDeclareNode\DeclareItem不再继承Node\StmtNode\Stmt\PropertyPropertyNode\PropertyItem不再继承Node\StmtNode\Stmt\StaticVarNode\StaticVar不再继承Node\StmtNode\Stmt\UseUseNode\UseItem不再继承Node\Stmt这些新节点在仓库目录结构中均已落地例如Node/ArrayItem.php、Node/InterpolatedStringPart.php、Node/PropertyItem.php、Node/UseItem.php等。上表中的后几项ArrayItem、ClosureUse、DeclareItem、PropertyItem、StaticVar、UseItem都从表达式/语句变成了普通节点不再混入表达式或语句语义。六、Modifiers修饰符常量独立成类修饰符标志用于Class_、ClassMethod、Property等的$flags子节点被移到独立的PhpParser\Modifiers类以表明它们被多种节点共用。旧常量已废弃但保留PhpParser\Node\Stmt\Class_::MODIFIER_PUBLIC - PhpParser\Modifiers::PUBLIC PhpParser\Node\Stmt\Class_::MODIFIER_PROTECTED - PhpParser\Modifiers::PROTECTED PhpParser\Node\Stmt\Class_::MODIFIER_PRIVATE - PhpParser\Modifiers::PRIVATE PhpParser\Node\Stmt\Class_::MODIFIER_STATIC - PhpParser\Modifiers::STATIC PhpParser\Node\Stmt\Class_::MODIFIER_ABSTRACT - PhpParser\Modifiers::ABSTRACT PhpParser\Node\Stmt\Class_::MODIFIER_FINAL - PhpParser\Modifiers::FINAL PhpParser\Node\Stmt\Class_::MODIFIER_READONLY - PhpParser\Modifiers::READONLY PhpParser\Node\Stmt\Class_::VISIBILITY_MODIFIER_MASK - PhpParser\Modifiers::VISIBILITY_MASK从 Modifiers.php 源码看该位掩码类还进一步扩展了能力PUBLIC/PROTECTED/PRIVATE取值为 1/2/4STATIC为 8ABSTRACT为 16FINAL为 32READONLY为 64并新增了 PHP 8.4 的PUBLIC_SET/PROTECTED_SET/PRIVATE_SET128/256/512对应不对称可见性。此外还提供VISIBILITY_MASK、VISIBILITY_SET_MASK、toString()以及用于校验修饰符冲突的verifyModifier()/verifyClassModifier()内部会抛出Error。七、节点构造函数类型参数不再接受纯字符串接受类型参数的节点构造函数不再接受普通字符串必须传Identifier、Name或ComplexType。受影响的位置Node\Expr\ArrowFunction的$subNodes[returnType]Node\Expr\Closure的$subNodes[returnType]Node\Stmt\ClassMethod的$subNodes[returnType]Node\Stmt\Function_的$subNodes[returnType]Node\NullableType的$type参数Node\Param的$type参数Node\Stmt\Property的$type参数Node\ClassConst的$type参数要保持旧行为应传Identifier表示内建类型。这一约束与上一节的类型按版本区分为Name还是Identifier见 PhpVersion::supportsBuiltinType相呼应解析器在构造节点时已经完成了按目标版本的分类手工构造节点时也应当显式指定类型节点类型。八、Pretty Printer对齐 PSR-12 与现代排版5.0 的标准 pretty printer 做了多项格式化调整以贴合当代编码规范尤其是 PSR-12且不提供恢复旧行为的选项如果你确实需要旧格式可以覆写对应的格式化方法如pStmt_ClassMethod。8.1 格式细节变化返回类型前的空格移除# Before function test() : Type { } # After function test(): Type { }abstract/final移到可见性修饰符之前# Before public abstract function test(); # After abstract public function test();闭包的use与(之间加空格# Before function () use($var) { }; # After function () use ($var) { };单引号字符串中的反斜杠仅在必要时打印# Before Foo\\Bar; \\\\; # After Foo\Bar; \\\\;else if省略冗余花括号# Before else { if ($x) { // ... } } # After else if ($x) { // ... }8.2 phpVersion 选项pretty printer 现在接受phpVersion选项接收一个PhpVersion对象默认目标为 PHP 7.4见 PrettyPrinterAbstract.php。它会基于该版本做出保证输出代码合法的格式化选择目前控制以下行为PHP 7.0默认默认使用短数组语法[]不影响通过kind属性显式指定语法类型的节点。PHP 7.0默认yield表达式的括号仅在必要时打印旧版总是打印即使 yield 用作语句。PHP 7.1默认解构默认使用短数组语法[]而非list()同样不影响显式指定kind的节点。PHP 7.3默认heredoc/nowdoc 字符串后不再强制换行flexible heredoc/nowdoc 取消了这一要求。PHP 7.3默认heredoc/nowdoc 字符串像普通代码一样缩进。这些判断直接复用 PhpVersion.php 中的supportsShortArraySyntax()、supportsShortArrayDestructuring()、supportsFlexibleHeredoc()等方法。此外PrettyPrinterAbstract构造函数还支持newline\n或\r\n、indent、shortArraySyntax等选项PrettyPrinterAbstract.php。8.3 运算符优先级处理的重构pretty printer 现在更精确地建模运算符优先级对一元运算符会打印更少的不必要括号同时修复了许多语义上必需的括号被遗漏的 bug。为此内部递归方法p()的签名变为protected function p( Node $node, int $precedence self::MAX_PRECEDENCE, int $lhsPrecedence self::MAX_PRECEDENCE, bool $parentFormatPreserved false ): string;$precedence直接父运算符的优先级如果有$lhsPrecedence节点所在位置最近的、位于其左侧的二元运算符的优先级对一元运算符只有$lhsPrecedence相关。该签名在 PrettyPrinterAbstract.php 中与实际实现一致。pretty printer 方法内部的递归调用通常应继续无附加参数地调用p()但参与优先级解析的运算符方法需要调整典型实现如下protected function pExpr_BinaryOp_Plus( BinaryOp\Plus $node, int $precedence, int $lhsPrecedence ): string { return $this-pInfixOp( BinaryOp\Plus::class, $node-left, , $node-right, $precedence, $lhsPrecedence); } protected function pExpr_UnaryPlus( Expr\UnaryPlus $node, int $precedence, int $lhsPrecedence ): string { return $this-pPrefixOp(Expr\UnaryPlus::class, , $node-expr, $precedence, $lhsPrecedence); }新增的$precedence与$lhsPrecedence参数需要透传给pInfixOp()、pPrefixOp()和pPostfixOp()。九、NodeTraverser回调顺序与常量位置变化9.1 leaveNode 改为逆序调用存在多个 visitor 时leaveNode()和afterTraverse()现在按与enterNode()/beforeTraverse()相反的顺序调用# Before $visitor1-enterNode($node); $visitor2-enterNode($node); $visitor1-leaveNode($node); $visitor2-leaveNode($node); # After $visitor1-enterNode($node); $visitor2-enterNode($node); $visitor2-leaveNode($node); $visitor1-leaveNode($node);该行为在 NodeTraverser.php 中有直接实现进入阶段按 visitor 数组顺序遍历L195-L224而离开阶段从最后一个 visitor 开始逆序调用L233 起for (; $visitorIndex 0; --$visitorIndex)afterTraverse同样采用逆序循环L78-L83。这符合后进先出的栈式语义让后注册的 visitor 先完成退出处理。9.2 特殊返回值常量移到 NodeVisitorNodeVisitor的特殊返回值从NodeTraverser移到了NodeVisitor旧名称已废弃但保留PhpParser\NodeTraverser::REMOVE_NODE - PhpParser\NodeVisitor::REMOVE_NODE PhpParser\NodeTraverser::DONT_TRAVERSE_CHILDREN - PhpParser\NodeVisitor::DONT_TRAVERSE_CHILDREN PhpParser\NodeTraverser::DONT_TRAVERSE_CURRENT_AND_CHILDREN - PhpParser\NodeVisitor::DONT_TRAVERSE_CURRENT_AND_CHILDREN PhpParser\NodeTraverser::STOP_TRAVERSAL - PhpParser\NodeVisitor::STOP_TRAVERSALNodeVisitor.php 定义了全部特殊返回值DONT_TRAVERSE_CHILDREN(1)、STOP_TRAVERSAL(2)、REMOVE_NODE(3)、DONT_TRAVERSE_CURRENT_AND_CHILDREN(4)、REPLACE_WITH_NULL(5)语义注释也一并保留。9.3 visitor 可直接传入构造函数visitor 现在可以直接传给NodeTraverser构造函数# Before仍支持 $traverser new NodeTraverser(); $traverser-addVisitor(new NameResolver()); # After $traverser new NodeTraverser(new NameResolver());十、Token 表示与 Lexer API 重构10.1 Token 类token 现在内部用PhpParser\Token类表示其接口与 PHP 8.0 引入的PhpToken一致在 PHP 8.0 上PhpParser\Token继承自PhpToken否则继承自 polyfill 实现见 Token.php 与 TokenPolyfill.php。核心接口class Token { public int $id; public string $text; public int $line; public int $pos; public function is(int|string|array $kind): bool; }token 数组现在是Token对象数组而不是数组与字符串混合的数组并且 token 数组以ID 为 0 的哨兵 token结尾在 Lexer.php 的postprocessTokens()中追加。Token还提供了getEndPos()、getEndLine()等便捷方法Token.php。10.2 Lexer API 精简Lexer API 精简为单一Lexer::tokenize()方法返回 token 数组startLexing()和getNextToken()已移除。主要变化节点 start/end 属性attributes的计算责任从 lexer 移交给了 parserlexer 不再接受 options 数组usedAttributes选项被删除且无替代品parser 现在无条件添加comments、startLine、endLine、startFilePos、endFilePos、startTokenPos、endTokenPos这些属性对最终用户而言通常不再需要直接与Lexer交互ParserFactory会自动创建合适的 lexer 实例无需额外配置。Lexer.php 的tokenize()还接受可选的ErrorHandler并负责报告坏字符与未闭合注释等词法错误同时做若干 token 规范化如 PHP 8.1 的T_AMPERSAND_*消歧、追加哨兵 token。10.3 格式化保留打印format-preserving的样板代码变化由于 token 通过$parser-getTokens()获取格式化保留打印的设置简化如下# Before $lexer new Lexer\Emulative([ usedAttributes [ comments, startLine, endLine, startTokenPos, endTokenPos, ], ]); $parser new Parser\Php7($lexer); $oldStmts $parser-parse($code); $oldTokens $lexer-getTokens(); $traverser new NodeTraverser(); $traverser-addVisitor(new NodeVisitor\CloningVisitor()); $newStmts $traverser-traverse($oldStmts); # After $parser (new ParserFactory())-createForNewestSupportedVersion(); $oldStmts $parser-parse($code); $oldTokens $parser-getTokens(); $traverser new NodeTraverser(new NodeVisitor\CloningVisitor()); $newStmts $traverser-traverse($oldStmts);Parser接口中的getTokens()定义可参考 Parser.php。仓库test/code/formatPreservation/目录下的大量.test用例如basic.test、comments.test、blockConversion.test、nopCommentAtEnd.test等正是对这套流程的回归验证。十一、杂项变更清单除上述大项外还有一批小的破坏性变更需要留意已废弃的Builder\Param::setTypeHint()方法被移除改用Builder\Param::setType()见 Param.php。已废弃的、接收起始行号的Error构造函数被移除改为传入[startLine $startLine]属性。已废弃的Comment::getLine()、Comment::getTokenPos()、Comment::getFilePos()被移除改用Comment::getStartLine()、Comment::getStartTokenPos()、Comment::getStartFilePos()。Comment::getReformattedText()现在会把 CRLF 换行规范化为 LF。Node::getLine()已废弃改用Node::getStartLine()。十二、迁移自检清单结合上文从 4.x 迁移到 5.0 时可以按以下清单逐项排查运行环境确认 PHP 7.4 才能运行本库如依赖Parser\Php5/Parser\Multiple/ONLY_PHP5/PREFER_PHP5需移除或改用createForVersion(PhpVersion::fromString(...))。工厂方法把$factory-create(...)换成createForNewestSupportedVersion()/createForHostVersion()/createForVersion()三者之一。AST 消费者检查对Stmt\Throw_、解构Expr\Array_、Name的parts、扁平化块结构、内层节点注释的依赖按第四节逐项调整。节点类名凡引用旧类名LNumber、DNumber、Encapsed、Expr\ArrayItem、Stmt\UseUse等处建议改用新类名旧名虽为别名仍可用但getType()已返回新名。修饰符常量把Class_::MODIFIER_*换成Modifiers::*。手工构造节点类型参数传Identifier/Name/ComplexType对象而非字符串。pretty printer核对:与返回类型间距、abstract/final位置、闭包use (空格、单引号转义、else if括号等输出变化如自定义了p()、pInfixOp()、pPrefixOp()、pPostfixOp()相关方法需适配新优先级参数按需设置phpVersion选项控制输出兼容性。遍历器确认多 visitor 场景下leaveNode()/afterTraverse()的逆序调用不会破坏你的处理逻辑将特殊返回值常量改用NodeVisitor::*可改用构造函数传 visitor 的写法。token/lexer把$lexer-getTokens()换成$parser-getTokens()删除usedAttributes配置如需旧式逐 token API 请迁移到tokenize()。杂项替换setTypeHint()、旧Error构造、Comment::getLine()系列、Node::getLine()等废弃接口。以上所有变更点的源码依据均可在当前仓库的 UPGRADE-5.0.md、lib/PhpParser/下的实现文件以及test/PhpParser/的测试用例中找到遇到具体疑问时可回到对应文件核对。【免费下载链接】PHP-ParserA PHP parser written in PHP项目地址: https://gitcode.com/GitHub_Trending/ph/PHP-Parser创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表