ros2 launch文件编写详解

一个完整的简单的launch文件配置过程

1.编写launch文件

2.配置package.xml

3.配置setup.py(python包)

4.配置CMakeList(C++包)

5.编译+运行

# 在 ROS 2 的 Python 启动文件中,这些导入语句用于引入各类启动模块,以构建和配置节点启动流程
from launch import LaunchDescription
from launch_ros.actions import Nodedef generate_launch_description():return LaunchDescription([Node(package='turtlesim',namespace='turtlesim1',executable='turtlesim_node',name='sim'),Node(package='turtlesim',namespace='turtlesim2',executable='turtlesim_node',name='sim'),Node(package='turtlesim',executable='mimic',name='mimic',remappings=[('/input/pose', '/turtlesim1/turtle1/pose'),('/output/cmd_vel', '/turtlesim2/turtle1/cmd_vel'),])])

package.xml加入以下内容: 

<exec_depend>ros2launch</exec_depend>

CMakeList:  to the end of the file (but before ament_package()).

# Install launch files.
install(DIRECTORYlaunchDESTINATION share/${PROJECT_NAME}/
)

package.list:

import os
from glob import glob
# Other imports ...package_name = 'py_launch_example'setup(# Other parameters ...data_files=[# ... Other data files# Include all launch files.(os.path.join('share', package_name, 'launch'), glob('launch/*'))]
)

 

替换表达式

from launch import LaunchDescription
from launch.actions import IncludeLaunchDescription
from launch.substitutions import PathJoinSubstitution
from launch_ros.substitutions import FindPackageSharedef generate_launch_description():colors = {'background_r': '200'}return LaunchDescription([# 使用PathJoinSubstitution和FindPackageShare动态构建启动文件路径IncludeLaunchDescription(PathJoinSubstitution([FindPackageShare('launch_tutorial'),'launch','example_substitutions.launch.py']),# 传递给被包含启动文件的参数launch_arguments={'turtlesim_ns': 'turtlesim2','use_provided_red': 'True','new_background_r': colors['background_r'],}.items())])

在 Python 中,.items() 是字典(dict)对象的一个方法,用于将字典转换为可迭代的键值对元组列表。

又如:

from launch import LaunchDescription
from launch.actions import DeclareLaunchArgument, ExecuteProcess, TimerAction
from launch.conditions import IfCondition
from launch.substitutions import LaunchConfiguration, PythonExpression
from launch_ros.actions import Nodedef generate_launch_description():# turtlesim_ns = LaunchConfiguration('turtlesim_ns')use_provided_red = LaunchConfiguration('use_provided_red')new_background_r = LaunchConfiguration('new_background_r')return LaunchDescription([DeclareLaunchArgument('turtlesim_ns',default_value='turtlesim1'),DeclareLaunchArgument('use_provided_red',default_value='False'),DeclareLaunchArgument('new_background_r',default_value='200'),Node(package='turtlesim',namespace=turtlesim_ns,executable='turtlesim_node',name='sim'),ExecuteProcess(cmd=[['ros2 service call ',turtlesim_ns,'/spawn ','turtlesim/srv/Spawn ','"{x: 2, y: 2, theta: 0.2}"']],shell=True),ExecuteProcess(cmd=[['ros2 param set ',turtlesim_ns,'/sim background_r ','120']],shell=True),TimerAction(period=2.0,actions=[ExecuteProcess(condition=IfCondition(PythonExpression([new_background_r,' == 200',' and ',use_provided_red])),cmd=[['ros2 param set ',turtlesim_ns,'/sim background_r ',new_background_r]],shell=True),],)])

为什么需要 LaunchConfiguration?

简单说,LaunchConfiguration就像一个 “参数占位符”。当你在 Launch 文件中声明了一个启动参数(比如DeclareLaunchArgument('turtlesim_ns')),LaunchConfiguration可以引用这个参数的值,让你在后续的节点、动作配置中动态使用它,而不用写死具体数值。

比如你声明了一个参数background_r,默认值为 200,但用户启动时可能会通过命令行修改为 255。LaunchConfiguration('background_r')就能自动获取用户输入的最新值,无需修改 Launch 文件代码。

命令行进程调用:

ExecuteProcess(cmd=[['ros2 param set ',turtlesim_ns,'/sim background_r ','120']],shell=True),

条件执行:

TimerAction(period=2.0,actions=[ExecuteProcess(condition=IfCondition(PythonExpression([new_background_r,' == 200',' and ',use_provided_red])),cmd=[['ros2 param set ',turtlesim_ns,'/sim background_r ',new_background_r]],shell=True),],)

格式: 

TimerAction(period = ....  # 多少时间后actions =[ExecuteProcess(condition = IfCondition(......  # 具体条件),cmd = [ [........... # 命令行指令]],shell = True),],)

这段代码的作用是:在启动后 2 秒,检查特定条件是否满足,如果满足则执行一条 ROS 2 命令修改 turtlesim 模拟器的背景红色值

ROS 2 中的事件处理器(Event Handlers)使用示例 


from launch import LaunchDescription
from launch.actions import (DeclareLaunchArgument,EmitEvent,ExecuteProcess,LogInfo,RegisterEventHandler,TimerAction
)
from launch.conditions import IfCondition
from launch.event_handlers import (OnExecutionComplete,OnProcessExit,OnProcessIO,OnProcessStart,OnShutdown
)
from launch.events import Shutdown
from launch.substitutions import (EnvironmentVariable,FindExecutable,LaunchConfiguration,LocalSubstitution,PythonExpression
)
from launch_ros.actions import Nodedef generate_launch_description():turtlesim_ns = LaunchConfiguration('turtlesim_ns')use_provided_red = LaunchConfiguration('use_provided_red')new_background_r = LaunchConfiguration('new_background_r')turtlesim_ns_launch_arg = DeclareLaunchArgument('turtlesim_ns',default_value='turtlesim1')use_provided_red_launch_arg = DeclareLaunchArgument('use_provided_red',default_value='False')new_background_r_launch_arg = DeclareLaunchArgument('new_background_r',default_value='200')turtlesim_node = Node(package='turtlesim',namespace=turtlesim_ns,executable='turtlesim_node',name='sim')spawn_turtle = ExecuteProcess(cmd=[[FindExecutable(name='ros2'),' service call ',turtlesim_ns,'/spawn ','turtlesim/srv/Spawn ','"{x: 2, y: 2, theta: 0.2}"']],shell=True)change_background_r = ExecuteProcess(cmd=[[FindExecutable(name='ros2'),' param set ',turtlesim_ns,'/sim background_r ','120']],shell=True)change_background_r_conditioned = ExecuteProcess(condition=IfCondition(PythonExpression([new_background_r,' == 200',' and ',use_provided_red])),cmd=[[FindExecutable(name='ros2'),' param set ',turtlesim_ns,'/sim background_r ',new_background_r]],shell=True)return LaunchDescription([turtlesim_ns_launch_arg,use_provided_red_launch_arg,new_background_r_launch_arg,turtlesim_node,RegisterEventHandler(OnProcessStart(target_action=turtlesim_node,on_start=[LogInfo(msg='Turtlesim started, spawning turtle'),spawn_turtle])),RegisterEventHandler(OnProcessIO(target_action=spawn_turtle,on_stdout=lambda event: LogInfo(msg='Spawn request says "{}"'.format(event.text.decode().strip())))),RegisterEventHandler(OnExecutionComplete(target_action=spawn_turtle,on_completion=[LogInfo(msg='Spawn finished'),change_background_r,TimerAction(period=2.0,actions=[change_background_r_conditioned],)])),RegisterEventHandler(OnProcessExit(target_action=turtlesim_node,on_exit=[LogInfo(msg=(EnvironmentVariable(name='USER'),' closed the turtlesim window')),EmitEvent(event=Shutdown(reason='Window closed'))])),RegisterEventHandler(OnShutdown(on_shutdown=[LogInfo(msg=['Launch was asked to shutdown: ', LocalSubstitution('event.reason')])])),])

 详解:

这个启动文件创建了一个 turtlesim 模拟器,并实现了以下功能:

  1. 启动 turtlesim 节点后,自动生成一只新乌龟
  2. 捕获生成乌龟的服务响应并打印日志
  3. 乌龟生成完成后,修改模拟器背景颜色
  4. 2 秒后,根据参数条件再次修改背景颜色
  5. 当 turtlesim 窗口关闭时,自动终止整个启动过程
  6. 捕获系统关闭事件并打印关闭原因
1.节点与命令定义
turtlesim_node = Node(package='turtlesim',namespace=turtlesim_ns,executable='turtlesim_node',name='sim'
)
spawn_turtle = ExecuteProcess(...)  # 生成乌龟的服务调用
change_background_r = ExecuteProcess(...)  # 修改背景颜色的命令
change_background_r_conditioned = ExecuteProcess(...)  # 条件性修改背景颜色

这些组件定义了要执行的基本操作,但它们的执行时机由事件处理器控制。

2.事件处理器注册
RegisterEventHandler(OnProcessStart(target_action=turtlesim_node,on_start=[LogInfo(...), spawn_turtle])
)
  • OnProcessStart:当turtlesim_node启动时,触发生成乌龟的命令。

RegisterEventHandler(OnProcessIO(target_action=spawn_turtle,on_stdout=lambda event: LogInfo(...))
)
  • OnProcessIO:捕获spawn_turtle命令的标准输出,提取服务响应信息。

当执行spawn_turtle命令(即调用turtlesim/srv/Spawn服务)时,服务响应会通过标准输出返回。例如:

ros2 service call /turtlesim1/spawn turtlesim/srv/Spawn "{x: 2, y: 2, theta: 0.2}"

服务成功响应后,控制台会打印:

[INFO] [launch]: Spawn request says "Created turtle [turtle2]"

这表明新乌龟已成功生成,并且名称为turtle2

RegisterEventHandler(OnExecutionComplete(target_action=spawn_turtle,on_completion=[LogInfo(...), change_background_r, TimerAction(...)])
)
  • OnExecutionComplete:当spawn_turtle命令完成后,修改背景颜色,并设置一个 2 秒后的延迟动作。

RegisterEventHandler(OnProcessExit(target_action=turtlesim_node,on_exit=[LogInfo(...), EmitEvent(event=Shutdown(...))])
)
  • OnProcessExit:当turtlesim_node退出时,触发系统关闭事件。

RegisterEventHandler(OnShutdown(on_shutdown=[LogInfo(...)])
)
  • OnShutdown:捕获系统关闭事件,打印关闭原因。
3.编译执行: 
ros2 launch launch_tutorial example_event_handlers.launch.py turtlesim_ns:='turtlesim3' use_provided_red:='True' new_background_r:=200

管理大型工程

顶层管理:

from launch import LaunchDescription
from launch.actions import IncludeLaunchDescription
from launch.substitutions import PathJoinSubstitution
from launch_ros.substitutions import FindPackageSharedef generate_launch_description():launch_dir = PathJoinSubstitution([FindPackageShare('launch_tutorial'), 'launch'])return LaunchDescription([IncludeLaunchDescription(PathJoinSubstitution([launch_dir, 'turtlesim_world_1.launch.py'])),IncludeLaunchDescription(PathJoinSubstitution([launch_dir, 'turtlesim_world_2.launch.py'])),IncludeLaunchDescription(PathJoinSubstitution([launch_dir, 'broadcaster_listener.launch.py']),launch_arguments={'target_frame': 'carrot1'}.items()),IncludeLaunchDescription(PathJoinSubstitution([launch_dir, 'mimic.launch.py'])),IncludeLaunchDescription(PathJoinSubstitution([launch_dir, 'fixed_broadcaster.launch.py'])),IncludeLaunchDescription(PathJoinSubstitution([launch_dir, 'turtlesim_rviz.launch.py'])),])

在launch文件设置参数:

from launch import LaunchDescription
from launch.actions import DeclareLaunchArgument
from launch.substitutions import LaunchConfiguration
from launch_ros.actions import Nodedef generate_launch_description():return LaunchDescription([DeclareLaunchArgument('background_r', default_value='0'),DeclareLaunchArgument('background_g', default_value='84'),DeclareLaunchArgument('background_b', default_value='122'),Node(package='turtlesim',executable='turtlesim_node',name='sim',parameters=[{'background_r': LaunchConfiguration('background_r'),'background_g': LaunchConfiguration('background_g'),'background_b': LaunchConfiguration('background_b'),}]),])

通过YAML文件设置参数:

from launch import LaunchDescription
from launch.substitutions import PathJoinSubstitution
from launch_ros.actions import Node
from launch_ros.substitutions import FindPackageSharedef generate_launch_description():return LaunchDescription([Node(package='turtlesim',executable='turtlesim_node',namespace='turtlesim2',name='sim',parameters=[PathJoinSubstitution([FindPackageShare('launch_tutorial'), 'config', 'turtlesim.yaml'])],),])

/turtlesim2/sim:ros__parameters:background_b: 255background_g: 86background_r: 150

整个/turtlesim2/sim代表的是 “位于turtlesim2命名空间下、名为simturtlesim节点”,其后的ros__parameters则是该节点的具体参数配置(如背景色的 RGB 值)

命名空间 :

当启动文件需要包含大量节点时,手动为每个节点单独设置命名空间会变得繁琐且易出错。PushRosNamespace 提供了一种更优雅的解决方案,让你可以为一组节点批量设置命名空间。

首先remove the namespace='turtlesim2' line from the turtlesim_world_2.launch.py 。然后按照以下修改:

from launch.actions import GroupAction
from launch_ros.actions import PushRosNamespace...GroupAction(actions=[PushROSNamespace('turtlesim2'),IncludeLaunchDescription(PathJoinSubstitution([launch_dir, 'turtlesim_world_2.launch.py'])),]),

那么在turtlesim_world_2.launch.py中的每一个节点都有一个turtlesim2前缀的命令空间。

节点复用

from launch import LaunchDescription
from launch.actions import DeclareLaunchArgument
from launch.substitutions import LaunchConfigurationfrom launch_ros.actions import Nodedef generate_launch_description():return LaunchDescription([DeclareLaunchArgument('target_frame', default_value='turtle1',description='Target frame name.',),Node(package='turtle_tf2_py',executable='turtle_tf2_broadcaster',name='broadcaster1',parameters=[{'turtlename': 'turtle1'}],),Node(package='turtle_tf2_py',executable='turtle_tf2_broadcaster',name='broadcaster2',parameters=[{'turtlename': 'turtle2'}],),Node(package='turtle_tf2_py',executable='turtle_tf2_listener',name='listener',parameters=[{'target_frame': LaunchConfiguration('target_frame')}],),])

 话题重映射

from launch import LaunchDescription
from launch_ros.actions import Nodedef generate_launch_description():return LaunchDescription([Node(package='turtlesim',executable='mimic',name='mimic',remappings=[('/input/pose', '/turtle2/pose'),('/output/cmd_vel', '/turtlesim2/turtle1/cmd_vel'),])])

 启动rviz,并按照文件配置:

from launch import LaunchDescription
from launch.substitutions import PathJoinSubstitution
from launch_ros.actions import Node
from launch_ros.substitutions import FindPackageSharedef generate_launch_description():return LaunchDescription([Node(package='rviz2',executable='rviz2',name='rviz2',arguments=['-d', PathJoinSubstitution([FindPackageShare('turtle_tf2_py'), 'rviz', 'turtle_rviz.rviz'])],),])

动态配置节点名:

from launch import LaunchDescription
from launch.actions import DeclareLaunchArgument
from launch.substitutions import EnvironmentVariable, LaunchConfiguration
from launch_ros.actions import Nodedef generate_launch_description():return LaunchDescription([DeclareLaunchArgument('node_prefix',default_value=[EnvironmentVariable('USER'), '_'],description='prefix for node name'),Node(package='turtle_tf2_py',executable='fixed_frame_tf2_broadcaster',name=[LaunchConfiguration('node_prefix'), 'fixed_broadcaster'],),])
  1. 声明一个启动参数node_prefix,默认值为当前系统用户的用户名加上下划线(例如:doubao_)
  2. 启动一个节点,节点名称由node_prefix和固定后缀fixed_broadcaster组成(例如:doubao_fixed_broadcaster)

更新setup.py:

import os
from glob import glob
from setuptools import setup
...data_files=[...(os.path.join('share', package_name, 'launch'),glob('launch/*')),(os.path.join('share', package_name, 'config'),glob('config/*.yaml')),(os.path.join('share', package_name, 'rviz'),glob('config/*.rviz')),],

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

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

相关文章

QT中QTableView+Model+Delegate实现一个demo

一、概述功能: 实现一个查询学生信息的表格&#xff0c;有学号、性别、年龄、班级和分数共5列&#xff0c;针对最后一列分数实现委托代理&#xff0c;要求能编辑和查看该分数列。QTableView实现视图展示uiModel负责数据的构造Delegate是委托&#xff0c;可针对某列数据做自定义…

用latex+vscode写论文

文章目录 前言 一、下载texlive安装包 二、安装texlive 1.安装 2.配置环境变量 3.检查是否安装成功 三、安装vscode 四、vscode中安装latex workshop插件 五、创建latex文档 六、撰写+编译+预览 七、latex workshop常用设置 1.打开设置页面 2.设置自动保存代码 3.设置自动编译代…

监测预警系统:让园区更高效、更安全、更智能

随着城市化进程的加快和产业集聚效应的凸显&#xff0c;园区作为经济发展的重要载体&#xff0c;其规模不断扩大&#xff0c;功能日益复杂。在这一背景下&#xff0c;传统的园区管理模式已难以满足现代园区高效、安全、智能的运营需求。园区监测预警系统作为一种集成了物联网、…

分享一个AutoOff定时动作软件

我们平时在使用电脑的时候有很多需求的功能&#xff0c;比如定时打开程序、定时关闭程序、定时休眠、定时关机等等。如果你也有这样的需求&#xff0c;那么就需要今天这款软件。AutoOff定时动作软件AutoOff这个软件是一款定时的软件&#xff0c;软件大小只有1.1M&#xff0c;而…

RPA软件推荐:提升企业自动化效率

在数字化转型浪潮中&#xff0c;机器人流程自动化&#xff08;RPA&#xff09;已成为企业降本增效的核心工具。它通过模拟人类操作&#xff0c;自动化重复性任务&#xff0c;如数据录入、报表生成和系统集成&#xff0c;显著提升运营效率。面对众多RPA软件&#xff0c;如何选择…

【Qt】QTime::toString(“hh:mm:ss.zzz“) 显示乱码的原因与解决方案

在使用 Qt 编写计时器程序时&#xff0c;我遇到一个很奇怪的问题&#xff1a;使用 QTime::toString("hh:mm:ss.zzz") 格式化时间后&#xff0c;显示出来的是一串乱码&#xff0c;如下所示&#xff1a;本来应该是&#xff1a;但却显示了一堆“〇”或奇怪的符号。问题表…

MSVC编译KDChart过程

MSVC编译KDChart过程 一、下载KDChart源文件 GitHub源文件 查看文件夹里的INSTALL.txt&#xff0c;可以看到需要CMake编译。 中文版 以下是使用 CMake 构建系统安装 KD Chart 的说明。 需要 CMake 3.3 或更高版本。Qt 版本支持&#xff1a;* KD Chart 2.5.0 及以下版本需要…

AI Agent管理后台原型设计全拆解(附3套可复用素材)

最近在做AI方向的产品&#xff0c;越来越能感受到“智能体”这个概念正在从技术圈走向应用层。无论是内部探索项目&#xff0c;还是外部合作需求&#xff0c;很多场景都会提到Agent、助手、知识库这些关键词。我们经常讨论如何打造一个有用的AI Agent&#xff0c;但实际上&…

ABP VNext + Elastic APM:微服务性能监控

ABP VNext Elastic APM&#xff1a;微服务性能监控 &#x1f680; &#x1f4da;目录ABP VNext Elastic APM&#xff1a;微服务性能监控 &#x1f680;一、引言 ✨架构全景图 &#x1f3d7;️二、环境与依赖 &#x1f4e6;三、APM 服务器与 Kibana 快速部署 &#x1f433;Doc…

单片机学习笔记.AD/DA(略含有SPI,用的是普中开发板上的XPT2046芯片)

AD/DA基础知识 硬件电路&#xff1a; 模电运放知识回顾&#xff1a; 虚短&#xff08;Virtual Short&#xff09;定义&#xff1a;运放同相输入端&#xff08;&#xff09;和反相输入端&#xff08;-&#xff09;的电位近似相等&#xff0c;即V V-&#xff0c;仿佛两个输入端短…

避坑指南:VMware安装CentOS常见错误及完美解决方案

1. 引言 虚拟机允许在同一台机子上有不同的操作系统&#xff0c;还可以用于搭建实验环境、软件测试和兼容性测试等。我是主攻大数据方向的会用到Linux操作系统&#xff0c;所以虚拟机对我来说是很有必要的。我把之前的笔记和安装包整理了一下&#xff0c;就有了现在这个教程。…

Python爬虫04_Requests豆瓣电影爬取

一、 爬取豆瓣电影排行榜数据 import requests import json url ‘https://movie.douban.com/j/chart/top_list’ param { ‘type’:‘24’, ‘interval_id’:‘100:90’, ‘action’:‘’, ‘start’:‘0’, #从库中的第几部电影去取 ‘limit’:‘20’, } headers { ‘Use…

工业物联网模块运营指南​

一、运营目标 工业物联网模块运营的核心目标在于通过高效运作,实现提高工业设备运行效率、降低生产成本、推动生产过程智能化管理,进而提升企业的整体竞争力。这一目标是后续所有运营工作的出发点和落脚点,为各项运营环节提供方向指引。 二、关键运营环节及做法 (一)设…

9.项目起步(3)

1项目起步-静态资源初始化 和 Error Lens 安装图片资源和样式资源error lens 安装2项目起步-scss文件自动导入为什么要自动导入自动导入配置scss变量自动导入$xtxColor: #27ba9b; $helpColor: #e26237; $sucColor: #1dc779; $warnColor: #ffb302; $priceColor: #cf4444;css: {p…

MCP提示词工程:上下文注入的艺术与科学

MCP提示词工程&#xff1a;上下文注入的艺术与科学 &#x1f31f; Hello&#xff0c;我是摘星&#xff01; &#x1f308; 在彩虹般绚烂的技术栈中&#xff0c;我是那个永不停歇的色彩收集者。 &#x1f98b; 每一个优化都是我培育的花朵&#xff0c;每一个特性都是我放飞的蝴蝶…

字节跳动GR-3:可泛化、支持长序列复杂操作任务的机器人操作大模型(技术报告解读)

1.总结 GR-3 是一个大规模的视觉 - 语言 - 动作&#xff08;VLA&#xff09;模型。它对新物体、新环境以及含抽象概念的新指令展现出较好的泛化能力。此外&#xff0c;GR-3 支持少量人类轨迹数据的高效微调&#xff0c;可快速且经济地适应新任务。GR-3 在处理长周期和灵巧性任…

713. 乘积小于 K 的子数组

中等 给你一个整数数组 nums 和一个整数 k &#xff0c;请你返回子数组内所有元素的乘积严格小于 k 的连续子数组的数目。 示例 1&#xff1a; 输入&#xff1a;nums [10,5,2,6], k 100 输出&#xff1a;8 解释&#xff1a;8 个乘积小于 100 的子数组分别为&#xff1a;[10…

【算法】 SM2、FSRS、SuperMemo算法实现艾宾浩斯记忆曲线,通过以上算法你也可以开发出单词记忆软件

有那些算法可以实现艾宾浩斯单词记忆 用户: 有那些算法可以实现艾宾浩斯单词记忆 元宝: 以下是基于 艾宾浩斯遗忘曲线 的智能记忆算法实现方案&#xff0c;结合 间隔重复算法 与 现代机器学习技术&#xff0c;提供从理论到实践的完整解决方案&#xff1a; 一、核心算法原理 1. …

SQL167 连续签到领金币

SQL167 连续签到领金币 题目描述 用户行为日志表 tb_user_log iduidartical_idin_timeout_timesign_in110102021-07-07 10:00:002021-07-07 10:00:091210102021-07-08 10:00:002021-07-08 10:00:091310102021-07-09 10:00:002021-07-09 10:00:42141010 2021-07-10 10:00:00 …

PHP性能优化与高并发处理:从基础到高级实践

引言 在当今高流量的互联网环境中,PHP应用的性能优化变得至关重要。本文将全面探讨PHP性能优化的各个层面,从基础优化技巧到高级并发处理方案,帮助开发者构建高性能的PHP应用。 基础性能优化 OPcache配置优化 ; php.ini 推荐OPcache配置 [opcache] opcache.enable=1 opc…