这个教程是基于Windows平台实现的,使用了docker容器,docker容器拉取了一个ubuntu镜像,就相当于直接在windows平台中直接可以从事Linux环境下的开发,不再需要使用虚拟机去配置一个Linux环境。这样做的好处是比较方便快捷,节省电脑的性能。在Ubuntu镜像中部署了vsomeip和CommonAPI。然后又在vscode中添加了docker容器的扩展,这样我们就可以直接使用vscode进行开发了。
下面展示的这张图是Windows下vscode的界面:
当然如果不想使用docker容器的话,可以跳过前面配置docker容器的这些步骤,直接从后面部署环境前的准备工作看起。
下载下面两个安装包
我这里是直接安装Wsl2,第一步中下载成功的安装包,直接安装即可。
这里直接点击可执行文件安装就可以了。
打开Windows终端,输入以下命令拉取一个ubuntu镜像
# 拉取ubuntu镜像
docker pull ubuntu
# 查看所有的镜像
docker ps -a
# 结果
# CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
# a91190b42b59 ubuntu "/bin/bash" 15 hours ago Up 3 minutes ubuntu-test
进入vscode之后在扩展中搜索docker,进行安装
apt update # 先更新一下库
apt install build-essential
apt-get install cmake
apt install git
apt-get install wget
apt-get install zip unzip
apt install default-jre
apt-get install libboost-system-dev libboost-thread-dev libboost-log-dev
这里涉及到三个库,分别是:
# 在ubuntu镜像中创建一个文件夹vsomeip
mkdir vsomeip
# 进入这个文件夹
cd vsomeip
# 使用git将仓库拉取下来
git clone https://github.com/COVESA/vsomeip.git
# 进入下载好的文件中
cd vsomeip
# 新建一个build文件夹
mkdir build
# 进入build文件夹
cd build
# 使用cmake进行编译
cmake -DENABLE_SIGNAL_HANDLING=1 ..
# 使用make进行编译
make -j16 # 后面的-j16的意思是16个cpu核心一起进行编译,会极大的提高效率,如果核心数量不够就酌情使用合适的,或者使用make进行
# 编译完成后进行安装,前一步编译完之后库都在/vsomeip/vsomeip目录下,进行安装之后,就会在/usr/local目录下
make install
# 在Ubuntu镜像中创建一个文件夹CommonAPI_runtime
mkdir CommonAPI_runtime
# 进入这个文件夹
cd CommonAPI_runtime
# 使用git将仓库拉取下来
git clone https://github.com/GENIVI/capicxx-core-runtime.git
# 进入下载好的文件中
cd capicxx-core-runtime/
# 新建一个build文件夹
mkdir build
# 进入build文件夹
cd build
# 使用cmake进行编译
cmake ..
# 使用make进行编译
make -j16
# 进行安装,可做可不做
make install
# 在Ubuntu镜像中创建一个文件夹CommonAPI_SOMEIP
mkdir CommonAPI_SOMEIP
# 进入这个文件夹
cd CommonAPI_SOMEIP
# 使用git将仓库拉取下来
git clone https://github.com/GENIVI/capicxx-someip-runtime.git
# 进入下载好的文件夹
cd capicxx-someip-runtime/
# 新建一个build文件夹
mkdir build
# 进入build文件夹
cd build
# 使用cmake进行编译
cmake -DUSE_INSTALLED_COMMONAPI=OFF ..
# 使用make进行编译
make -j16
# 进行安装,可做可不做
make install
# 在ubuntu镜像中创建一个文件夹CommonAPI_generator
mkdir CommonAPI_generator
# 进入这个文件夹
cd CommonAPI_generator
# 创建一个commonapi-core-generator文件夹
mkdir commonapi-core-generator
# 进入这个文件夹
cd commonapi-core-generator
# 使用wget下载commonapi-core-generator压缩包
wget https://github.com/COVESA/capicxx-core-tools/releases/tag/3.2.0.1
# 下载完成后得到一个压缩包,使用unzip解压缩
unzip commonapi_core_generator.zip
# 解压缩后得到以下文件
artifacts.xml commonapi-core-generator-windows-x86.exe
commonapi-core-generator-linux-x86 commonapi-core-generator-windows-x86.ini configuration
commonapi-core-generator-linux-x86.ini commonapi-core-generator-windows-x86_64.exe features
commonapi-core-generator-linux-x86_64 commonapi-core-generator-windows-x86_64.ini plugins
commonapi-core-generator-linux-x86_64.ini commonapi-core-generator.app
# 使用uname -m查看ubuntu镜像的架构
uanme -m
# 执行后得到的结果
x86_64 # 如果是x86_64就选择以x86_64结尾的可执行文件,如果是x86就选择以x86结尾的可执行文件
# 如果担心后面生成器执行不了,就先修改一下生成器的权限
chmod +x ./commonapi-core-generator-linux-x86_64
这一步官方文档是下载源码,然后使用maven进行构建,我使用这种方式一直没有成功,就换了一种方式
# 进入上一步创建好的文件夹CommonAPI_generator
cd CommonAPI_generator
# 创建一个commonapi-someip-generator文件夹
mkdir commonapi-someip-generator
# 进入这个文件夹
cd commonapi-someip-generator
# 使用wget下载commonapi-someip-generator压缩包
wget https://github.com/COVESA/capicxx-someip-tools/releases/download/3.2.0.1/commonapi_someip_generator.zip
# 下载完成后得到一个压缩包,使用unzip解压缩
unzip commonapi_someip_generator.zip
# 解压缩后得到以下文件
artifacts.xml commonapi-someip-generator-windows-x86.exe
commonapi-someip-generator-linux-x86 commonapi-someip-generator-windows-x86.ini configuration
commonapi-someip-generator-linux-x86.ini commonapi-someip-generator-windows-x86_64.exe features
commonapi-someip-generator-linux-x86_64 commonapi-someip-generator-windows-x86_64.ini org.genivi.commonapi.someip.target
commonapi-someip-generator-linux-x86_64.ini commonapi-someip-generator.app plugins
# 使用uname -m查看ubuntu镜像的架构
uanme -m
# 执行后得到的结果
x86_64 # 如果是x86_64就选择以x86_64结尾的可执行文件,如果是x86就选择以x86结尾的可执行文件
# 如果担心后面生成器执行不了,就先修改一下生成器的权限
chmod +x ./commonapi-someip-generator-linux-x86_64
至此,环境已经全部配置完毕了
# 在Ubuntu镜像中创建文件夹project
mkdir project
# 进入project文件夹
cd project
# 创建fidl、src、build文件夹
mkdir fidl src build
# 创建CMakeLists.txt文件
touch CMakeLists.txt
# 进入fidl文件夹
cd fidl
# 创建HelloWorld.fidl文件和HelloWorld.fdepl文件
touch HelloWorld.fidl HelloWorld.fdepl
# 进入src文件夹
cd ../src
# 创建HelloWorldClient.cpp、HelloWorldService.cpp、HelloWorldStubImpl.hpp、HelloWorldStubImpl.cpp文件
touch HelloWorldClient.cpp HelloWorldService.cpp HelloWorldStubImpl.hpp HelloWorldStubImpl.cpp
# 使用tree命令查看project的目录结构
tree .
# 得到的结果
.
|-- CMakeLists.txt
|-- build
|-- fidl
| |-- HelloWorld.fdepl
| `-- HelloWorld.fidl
`-- src|-- HelloWorldClient.cpp|-- HelloWorldService.cpp|-- HelloWorldStubImpl.cpp`-- HelloWorldStubImpl.hpp
fidl文件为约束服务的文件,参考此HelloWorld.fidl
package commonapiinterface HelloWorld {version {major 1 minor 0}method sayHello {in {String name}out {String message}}
}
配置带有someip标识符的.fdepl文件,参考此HelloWorld.fdepl
import "platform:/plugin/org.genivi.commonapi.someip/deployment/CommonAPI-SOMEIP_deployment_spec.fdepl"
import "HelloWorld.fidl"define org.genivi.commonapi.someip.deployment for interface commonapi.HelloWorld {SomeIpServiceID = 4660method sayHello {SomeIpMethodID = 123
}
// define org.genivi.commonapi.someip.deployment for provider MyService {
// instance commonapi.HelloWorld {
// InstanceId = "test"
// SomeIpInstanceID = 22136
// }
//}define org.genivi.commonapi.someip.deployment for provider as MyService {instance commonapi.HelloWorld {InstanceId = "test"SomeIpInstanceID = 22136}
}
上面的代码中有一部分注释掉了,被注释的那一段是官方文档中的,按照那个运行会出错,下面的是正确的,运行不会出错。两者的区别就是在MyService之前添加了一个as
# 使用前面安装的两个代码生成器针对fidl文件和fdepl文件进行代码生成
./CommonAPI_generator/commonapi-core-generator/commonapi-core-generator-linux-x86_64 -sk ./fidl/HelloWorld.fidl
./CommonAPI_generator/commonapi-someip-generator/commonapi-someip-generator-linux-x86_64 -ll verbose ./fidl/HelloWorld.fdepl
# 执行完后生成了一个新的文件夹src-gen,进入src-gen
cd src-gen
# 使用tree命令查看project文件的目录结构
tree .
# 结果
.
|-- CMakeLists.txt
|-- build
|-- fidl
| |-- HelloWorld.fdepl
| `-- HelloWorld.fidl
|-- src
| |-- HelloWorldClient.cpp
| |-- HelloWorldService.cpp
| |-- HelloWorldStubImpl.cpp
| `-- HelloWorldStubImpl.hpp
`-- src-gen`-- v1`-- commonapi|-- HelloWorld.hpp|-- HelloWorldProxy.hpp|-- HelloWorldProxyBase.hpp|-- HelloWorldSomeIPDeployment.cpp|-- HelloWorldSomeIPDeployment.hpp|-- HelloWorldSomeIPProxy.cpp|-- HelloWorldSomeIPProxy.hpp|-- HelloWorldSomeIPStubAdapter.cpp|-- HelloWorldSomeIPStubAdapter.hpp|-- HelloWorldStub.hpp`-- HelloWorldStubDefault.hpp
# 可以发现使用代码生成器生成了很多的文件,这些文件相当于头文件,供后续编写src中的文件时使用
// HelloWorldClient.cpp
#include
#include
#include
#include
#include using namespace v1_0::commonapi;int main() {std::shared_ptr < CommonAPI::Runtime > runtime = CommonAPI::Runtime::get();std::shared_ptr> myProxy =runtime->buildProxy("local", "test");std::cout << "Checking availability!" << std::endl;while (!myProxy->isAvailable())usleep(10);std::cout << "Available..." << std::endl;CommonAPI::CallStatus callStatus;std::string returnMessage;myProxy->sayHello("Bob", callStatus, returnMessage);std::cout << "Got message: '" << returnMessage << "'\n";return 0;
}
// HelloWorldService.cpp
#include
#include
#include
#include "HelloWorldStubImpl.hpp"using namespace std;int main() {std::shared_ptr runtime = CommonAPI::Runtime::get();std::shared_ptr myService =std::make_shared();runtime->registerService("local", "test", myService);std::cout << "Successfully Registered Service!" << std::endl;while (true) {std::cout << "Waiting for calls... (Abort with CTRL+C)" << std::endl;std::this_thread::sleep_for(std::chrono::seconds(30));}return 0;
}
此文件是为了实现自己在idl中定义的sayHello方法:
// HelloWorldStubImpl.hpp
#ifndef HELLOWORLDSTUBIMPL_H_
#define HELLOWORLDSTUBIMPL_H_#include
#include class HelloWorldStubImpl: public v1_0::commonapi::HelloWorldStubDefault {
public:HelloWorldStubImpl();virtual ~HelloWorldStubImpl();virtual void sayHello(const std::shared_ptr _client,std::string _name, sayHelloReply_t _return);
};
#endif /* HELLOWORLDSTUBIMPL_H_ */
HelloWorldStubImpl.cpp是HelloWorldStubImpl.hpp的具体实现
// HelloWorldStubImpl.cpp
#include "HelloWorldStubImpl.hpp"HelloWorldStubImpl::HelloWorldStubImpl() { }
HelloWorldStubImpl::~HelloWorldStubImpl() { }void HelloWorldStubImpl::sayHello(const std::shared_ptr _client,std::string _name, sayHelloReply_t _reply) {std::stringstream messageStream;messageStream << "Hello " << _name << "!";std::cout << "sayHello('" << _name << "'): '" << messageStream.str() << "'\n";_reply(messageStream.str());
};
cmake_minimum_required(VERSION 2.8) # cmake的最低版本要求,使用apt安装的cmake版本一般是没什么问题的project(my) # 工程名字,随意写就行set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pthread -std=c++11")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -ldl")
set(RUNTIME_PATH /CommonAPI_runtime) # 这里是运行时库的路径,就是安装capicxx-core-runtime的路径
set(SOMEIP_PATH /CommonAPI_SOMEIP) # 这里是someip库的路径,就是安装capicxx-someip-runtime的路径
set(VSOMEIP_PATH /vsomeip/vsomeip) # 这里是vsomeip库的路径,就是安装vsomeip的位置include_directories(src-gensrc${RUNTIME_PATH}/capicxx-core-runtime/include${SOMEIP_PATH}/capicxx-someip-runtime/include${VSOMEIP_PATH}/interface/vsomeip
)
link_directories(${RUNTIME_PATH}/capicxx-core-runtime/build${SOMEIP_PATH}/capicxx-someip-runtime/build${VSOMEIP_PATH}/build
)
add_executable(HelloWorldClientsrc/HelloWorldClient.cppsrc-gen/v1/commonapi/HelloWorldSomeIPProxy.cppsrc-gen/v1/commonapi/HelloWorldSomeIPDeployment.cpp
)
target_link_libraries(HelloWorldClient CommonAPI CommonAPI-SomeIP vsomeip3)
add_executable(HelloWorldServicesrc/HelloWorldService.cppsrc/HelloWorldStubImpl.cpp src-gen/v1/commonapi/HelloWorldSomeIPStubAdapter.cpp#src-gen/v1/commonapi/HelloWorldStubDefault.cppsrc-gen/v1/commonapi/HelloWorldSomeIPDeployment.cpp
)
target_link_libraries(HelloWorldService CommonAPI CommonAPI-SomeIP vsomeip3)
# 进入project目录下的build文件夹中
cd /project/build
# 使用cmake进行编译
cmake ..
# 使用make进行编译
make
# 使用tree命令查看build的目录结构
tree -L 2 # 这里只查看两层的
# 结果
.
|-- CMakeCache.txt
|-- CMakeFiles
| |-- 3.22.1
| |-- CMakeDirectoryInformation.cmake
| |-- CMakeOutput.log
| |-- CMakeTmp
| |-- HelloWorldClient.dir
| |-- HelloWorldService.dir
| |-- Makefile.cmake
| |-- Makefile2
| |-- TargetDirectories.txt
| |-- cmake.check_cache
| `-- progress.marks
|-- HelloWorldClient
|-- HelloWorldService
|-- Makefile
`-- cmake_install.cmake
# 可以发现已经生成了HelloWorldClient、HelloWorldService这两个可执行文件
在vscode中打开两个终端
# 查看设备上运行的docker容器,此时终端还是windows系统的终端
docker ps -a
# 执行结果
# CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
# a91190b42b59 ubuntu "/bin/bash" 15 hours ago Up 3 minutes ubuntu-test
# 在这两个终端中都输入下面指令以进入docker容器
docker exec -it a91190b42b59 /bin/bash # a91190b42b59代表的是容器ID,在设备中使用ID号唯一标识容器
# 进入容器后两个终端都进入到/project/build目录下
cd project/build
# 在一个终端中打开客户端程序,另一个终端中打开服务端程序,也就是在两个终端中分别输入下面指令
./HelloWorldClient
./HelloWorldService
[CAPI][INFO] Loading configuration file '/etc/commonapi.ini'
[CAPI][INFO] Using default binding 'dbus'
[CAPI][INFO] Using default shared library folder '/usr/local/lib/commonapi'
[CAPI][INFO] Registering function for creating "commonapi.HelloWorld:v1_0" stub adapter.
[CAPI][INFO] Registering stub for "local:commonapi.HelloWorld:v1_0:test"
2023-02-05 00:52:11.163749 [info] Parsed vsomeip configuration in 0ms
2023-02-05 00:52:11.165322 [info] Configuration module loaded.
2023-02-05 00:52:11.165978 [info] Initializing vsomeip application "".
2023-02-05 00:52:11.168704 [info] Instantiating routing manager [Host].
2023-02-05 00:52:11.170855 [info] create_local_server Routing endpoint at /tmp/vsomeip-0
2023-02-05 00:52:11.172405 [info] Service Discovery enabled. Trying to load module.
2023-02-05 00:52:11.177469 [info] Service Discovery module loaded.
2023-02-05 00:52:11.178809 [info] Application(unnamed, 0100) is initialized (11, 100).
2023-02-05 00:52:11.181580 [info] Starting vsomeip application "" (0100) using 2 threads I/O nice 255
2023-02-05 00:52:11.183183 [info] main dispatch thread id from application: 0100 () is: 7f6e956ef640 TID: 16751
2023-02-05 00:52:11.183338 [info] shutdown thread id from application: 0100 () is: 7f6e94eee640 TID: 16752
2023-02-05 00:52:11.184302 [info] OFFER(0100): [1234.5678:1.0] (true)
2023-02-05 00:52:11.188145 [info] Listening at /tmp/vsomeip-100
Successfully Registered Service!
Waiting for calls... (Abort with CTRL+C)
2023-02-05 00:52:11.190775 [info] Watchdog is disabled!
2023-02-05 00:52:11.193241 [info] io thread id from application: 0100 () is: 7f6e95ef0640 TID: 16750
2023-02-05 00:52:11.193235 [info] io thread id from application: 0100 () is: 7f6e877fe640 TID: 16754
2023-02-05 00:52:11.195887 [info] vSomeIP 3.1.20.3 | (default)
2023-02-05 00:52:11.196776 [info] Network interface "lo" state changed: up
2023-02-05 00:52:21.201855 [info] vSomeIP 3.1.20.3 | (default)
2023-02-05 00:52:31.205929 [info] vSomeIP 3.1.20.3 | (default)
2023-02-05 00:52:31.990428 [info] Application/Client 0101 is registering.
2023-02-05 00:52:31.991535 [info] Client [100] is connecting to [101] at /tmp/vsomeip-101
2023-02-05 00:52:31.994791 [info] REGISTERED_ACK(0101)
2023-02-05 00:52:31.080177 [info] REQUEST(0101): [1234.5678:1.4294967295]
sayHello('Bob'): 'Hello Bob!'
2023-02-05 00:52:31.089934 [info] RELEASE(0101): [1234.5678]
2023-02-05 00:52:31.092560 [info] Application/Client 0101 is deregistering.
2023-02-05 00:52:31.194883 [info] Client [100] is closing connection to [101]
^C2023-02-05 00:52:37.737567 [info] Stopping vsomeip application "" (0100).
2023-02-05 00:52:37.759176 [info] Exiting vsomeip application...
[CAPI][INFO] Loading configuration file '/etc/commonapi.ini'
[CAPI][INFO] Using default binding 'dbus'
[CAPI][INFO] Using default shared library folder '/usr/local/lib/commonapi'
2023-02-05 00:52:31.973435 [info] Parsed vsomeip configuration in 0ms
2023-02-05 00:52:31.973726 [info] Configuration module loaded.
2023-02-05 00:52:31.973891 [info] Initializing vsomeip application "".
2023-02-05 00:52:31.974102 [info] Instantiating routing manager [Proxy].
2023-02-05 00:52:31.975376 [info] Client [ffff] is connecting to [0] at /tmp/vsomeip-0
2023-02-05 00:52:31.975927 [info] Application(unnamed, ffff) is initialized (11, 100).
2023-02-05 00:52:31.976852 [info] Starting vsomeip application "" (ffff) using 2 threads I/O nice 255
2023-02-05 00:52:31.977723 [info] main dispatch thread id from application: ffff () is: 7f91b1697640 TID: 16758
2023-02-05 00:52:31.977921 [info] shutdown thread id from application: ffff () is: 7f91b0e96640 TID: 16759
Checking availability!
2023-02-05 00:52:31.983733 [info] io thread id from application: ffff () is: 7f91b1e98640 TID: 16757
2023-02-05 00:52:31.983716 [info] io thread id from application: ffff () is: 7f91a3fff640 TID: 16760
2023-02-05 00:52:31.989212 [info] Listening at /tmp/vsomeip-101
2023-02-05 00:52:31.989452 [info] Client 101 () successfully connected to routing ~> registering..
2023-02-05 00:52:31.993875 [info] Application/Client 101 () is registered.
2023-02-05 00:52:31.080970 [info] ON_AVAILABLE(0101): [1234.5678:1.0]
Available...
2023-02-05 00:52:31.083253 [info] Client [101] is connecting to [100] at /tmp/vsomeip-100
Got message: 'Hello Bob!'
2023-02-05 00:52:31.091285 [info] Stopping vsomeip application "" (0101).
2023-02-05 00:52:31.093326 [info] Application/Client 101 () is deregistered.
2023-02-05 00:52:31.096368 [info] Client [101] is closing connection to [100]
刚开始是在VMware虚拟机中的Ubuntu20.04环境中进行编译的,在进行make的时候,执行到最后总是会报错,说是动态库链接失败,报错原因是ubuntu16.10版本之后默认使用PIE了,而这个makefile文件不支持PIE,所以会失败。网上有很多说法是添加-fPIC,我多次尝试也没能解决。当然还有一种解决方法是在makefile文件中的gcc一行加入-no-pie来禁掉PIE,这个方式我没有尝试,因为这个项目是使用cmake进行构建的,这种情况下由cmake生成的makefile文件行数很多,并且修改库文件的makefile文件风险太大,万一改错了一个地方后续可能会有更多的麻烦。。。。。
后续我尝试在另一台云电脑中进行编译,也是ubuntu20.04的环境,编译就一下通过了,再到后面使用docker拉取的ubuntu镜像(22.04版本)中也进行了编译,也通过了。所以。。。。难受
在编译完成这个库之后,我想把这个库的上级目录重新命名一下,操作了之后,再后面编译别的库的时候,编译出错了,原因是别的库需要依赖这个库,而这个库的名字被我改掉了,导致依赖的时候找不到这个库了。教训就是不要轻易改目录名字。。。
这里需要用到两个代码生成器,一个是跟运行时相关的,这个的话根据官方的文档,使用wget命令下载发行版的压缩包解压就能使用。这个我没踩什么大坑,就是在执行的时候使用了x86的生成器,一直不成功,后面才发现我的镜像需要使用x86_64的生成器,使用这个之后就成功了。
踩坑的是第二个代码生成器,他是跟someip相关的,我也是参照的官方文档,他那里是让下载生成器源码,然后使用maven工具相当于是编译吧,具体也不是很懂。我就按照他的步骤去做,结果花了很长时间还一直不成功,到最后总是出错。然后我就去查找有没有发行版本可以直接拿来使用的,结果找到了,找到发行版本使用之后一次成功。(有时候也不能完全信官方文档。。。)
在上一步中,我其实刚开始也是尝试用发行版本解压后的文件去生成代码的,但是一直出错,后面我就尝试按官方的文档编译生成这个代码生成器。结果官方文档也不行,生成时候一直出错。后面只好再换回发行版本,还是报之前的错误,这次我尝试直接把报错信息复制下来去网上搜索看看有没有解决办法,搜了一下发现竟然有
结果是示例代码出了问题,人已经麻了。。。我刚开始出错了没有直接去搜索,因为之前也搜过,这个受众比较少,大家给的解决方案也不多,所以我刚开始就没抱什么希望。跌跌撞撞,坎坎坷坷
我是参考的别人的一篇博客,他一直没有交代他project项目的目录结构,我一直也不是很清晰。就是跟着他的步骤在那里一步步进行。进行到最后使用cmake编译,发生了错误。这才发觉是目录结构有问题,跟CMakeLists.txt文件中的目录结构不一致。这才将src相关的目录全部删除,根据CMakeLists.txt文件中的目录结构重新创建目录结构,最后推演出真实可行的结构。所以说有时候写文档最好先把目录结构列出来,这样会方便读者能有一个比较清楚的结构。
[1] Windows11下安装Docker
[2] 已解决:Ubuntu中make文件出错,提示“最后的链结失败:输出不可表示的节”
[3] 【图文教程】Windows11下安装Docker Desktop
[4] commonapi和vsomeip构建
[5] CommonAPI C++ in 10 minutes (with D-Bus)
[6] CommonAPI C++ in 10 minutes (with SOME/IP)
[7] CommonAPI新版本配置
[8] 由fedpl文件生成代码时发生的错误
上一篇:7 Seata简介