☰
【C++ 基于微服务的即时通讯系统】第三方服务集成与工程化构建:短信验证码、语音识别与CMake实战
2026/11/1 1:53:45 网站建设 项目流程

🔥草莓熊Lotso:个人主页

❄️个人专栏:《C++知识分享》 《Linux 入门到实践:零基础也能懂》

✨生活是默默的坚持,毅力是永久的享受!

🎬 博主简介:


文章目录

  • 前言:
  • 一、DMS 短信验证码平台集成
    • 1.1 业务流程与平台介绍
    • 1.2 阿里云短信 SDK 安装
      • 安装系统依赖
      • 克隆并编译 SDK
    • 1.3 SDK 二次封装:DMSClient 类
      • 头文件定义
      • 实现代码
    • 1.4 功能测试
  • 二、语音识别平台集成
    • 2.1 应用场景与注意事项
      • 音频格式要求(高频踩坑点)
    • 2.2 语音识别 SDK 基础使用
    • 2.3 SDK 二次封装:ASRClient 类
    • 2.4 功能测试
  • 三、CMake 构建系统入门与实战
    • 3.1 CMake 基础介绍
      • 主要特点
      • 最小示例
    • 3.2 核心语法速览
      • 变量与列表
      • 常用预定义变量
      • 字符串替换
      • 头文件与链接库
      • 消息打印
      • 查找源码文件
      • 条件判断
      • 自定义构建命令
      • 子目录与安装
    • 3.3 实战:ODB 项目的 CMake 构建
      • 项目目录结构
      • 完整 CMakeLists.txt
    • 3.4 多目录嵌套与安装配置
  • 四、核心要点总结
  • 结尾:

前言:

做一套完整的即时通讯系统,光有网络通信和业务逻辑还不够,很多能力直接自研成本太高、周期太长,接入成熟的第三方云服务是更高效的选择。比如手机号注册登录需要短信验证码,语音消息需要语音转文字,这些都可以直接对接大厂的开放平台。与此同时,随着项目模块越来越多,手写 Makefile 维护成本会急剧上升,引入 CMake 做跨平台构建管理是工程化的必经之路。本文就顺着项目落地的顺序,依次拆解三个基建组件:阿里云短信验证码服务的接入与封装、百度语音识别服务的接入与封装,以及 CMake 构建系统的基础用法和实战案例。从 SDK 安装、接口封装到工程化落地,全程附带源码和踩坑记录。


一、DMS 短信验证码平台集成

1.1 业务流程与平台介绍

手机号快捷登录注册,是现在 APP 的标配功能。它的完整业务流程是这样的:

  1. 客户端点击「获取验证码」,向服务端提交手机号
  2. 服务端生成随机验证码和唯一 ID,把手机号 + 验证码发送给短信平台
  3. 短信平台向用户手机下发短信
  4. 用户输入验证码,带着 ID 提交给服务端
  5. 服务端比对 ID、手机号、验证码,一致则校验通过

我们选用阿里云的短信服务(Dysmsapi),稳定性高,到达率有保障,C++ SDK 也比较完善。

1.2 阿里云短信 SDK 安装

安装系统依赖

sudo apt-get install libcurl4-openssl-dev libssl-dev uuid-dev libjsoncpp-dev

克隆并编译 SDK

git clone [https://github.com/aliyun/aliyun-openapi-cpp-sdk.git](https://github.com/aliyun/aliyun-openapi-cpp-sdk.git) # 一键编译安装 core 核心模块 sudo sh easyinstall.sh core

安装完成后,系统会生成libalibabacloud-sdk-core.so动态库和对应的头文件,后续业务代码直接链接即可。

安全提醒:AccessKey ID 和 AccessKey Secret 是访问云 API 的密钥,权限很高。生产环境绝对不要硬编码在代码里,建议通过环境变量或者配置文件读取,并且定期轮换。

1.3 SDK 二次封装:DMSClient 类

原生 SDK 的调用步骤比较繁琐,每次都要手动构造请求、设置一堆参数。我们把它封装成一个DMSClient类,业务层只需要传手机号和验证码就能发送,屏蔽底层细节。

头文件定义

#pragma once #include <string> #include <memory> #include <alibabacloud/core/AlibabaCloud.h> #include <alibabacloud/core/CommonClient.h> #include "logger.hpp" class DMSClient { public: DMSClient(const std::string& access_key_id, const std::string& access_key_secret); ~DMSClient(); // 发送短信验证码,成功返回true bool send(const std::string& phone, const std::string& code); private: std::unique_ptr<AlibabaCloud::CommonClient> _client; };

实现代码

DMSClient::DMSClient(const std::string& access_key_id, const std::string& access_key_secret) { // 初始化SDK AlibabaCloud::InitializeSdk(); // 配置客户端:地域、超时时间 AlibabaCloud::ClientConfiguration configuration("cn-chengdu"); configuration.setConnectTimeout(1500); configuration.setReadTimeout(4000); // 设置凭证 AlibabaCloud::Credentials credential(access_key_id, access_key_secret); _client = std::make_unique<AlibabaCloud::CommonClient>(credential, configuration); } DMSClient::~DMSClient() { // 析构时关闭SDK,释放资源 AlibabaCloud::ShutdownSdk(); } bool DMSClient::send(const std::string& phone, const std::string& code) { // 构造RPC模式的通用请求 AlibabaCloud::CommonRequest request( AlibabaCloud::CommonRequest::RequestPattern::RpcPattern); request.setHttpMethod(AlibabaCloud::HttpRequest::Method::Post); request.setDomain("dysmsapi.aliyuncs.com"); request.setVersion("2017-05-25"); // 设置业务参数 request.setQueryParameter("Action", "SendSms"); request.setQueryParameter("SignName", "bitejiuyeke"); // 短信签名 request.setQueryParameter("TemplateCode", "SMS_465324787"); // 模板CODE request.setQueryParameter("PhoneNumbers", phone); // 目标手机号 // 模板参数,JSON格式 std::string param_code = "{\"code\":\"" + code + "\"}"; request.setQueryParameter("TemplateParam", param_code); // 发起请求 auto response = _client->commonResponse(request); if (!response.isSuccess()) { LOG_ERROR("短信验证码请求失败: {}", response.error().errorMessage()); return false; } return true; }

代码解读:

  • 构造函数里做 SDK 初始化、客户端配置,析构函数做资源释放,遵循 RAII 原则,业务层不用关心初始化和清理
  • 使用CommonClient通用客户端,通过 RPC 模式调用短信 API,这种方式比专用客户端更灵活,对接其他阿里云服务也能用
  • 所有参数通过setQueryParameter设置,签名、模板码这些固定配置可以抽成配置项,不用硬编码

1.4 功能测试

写一个简单的测试程序,结合 gflags 传入密钥参数:

#include "../common/dms.hpp" #include <gflags/gflags.h> DEFINE_string(key_id, "", "平台访问密钥ID"); DEFINE_string(key_secret, "", "平台访问密钥"); DEFINE_bool(run_mode, false, "运行模式:false调试,true发布"); DEFINE_string(log_file, "", "日志文件路径"); DEFINE_int32(log_level, 0, "日志级别"); int main(int argc, char* argv[]) { google::ParseCommandLineFlags(&argc, &argv, true); init_logger(FLAGS_run_mode, FLAGS_log_file, FLAGS_log_level); DMSClient client(FLAGS_key_id, FLAGS_key_secret); client.send("15929917272", "5678"); return 0; }

编译命令:

test: test.cc g++ -std=c++17 $^ -o $@ -lalibabacloud-sdk-core -ljsoncpp -lcurl -lssl -lcrypto -luuid

二、语音识别平台集成

2.1 应用场景与注意事项

在聊天系统里,语音消息是很常用的功能,但纯语音不方便检索、也不方便用户快速浏览。我们在服务端接入语音识别能力,把语音消息自动转成文字,既可以做语音搜索,也能让用户直接看到文字预览。

我们选用百度智能云的语音识别服务,中文识别准确率高,C++ SDK 也比较成熟。

音频格式要求(高频踩坑点)

百度语音识别对音频参数有严格要求,不符合的话识别结果会完全错乱,比如全是“嗯嗯嗯”“嗨嗨嗨”:

  • 采样率:仅支持 16000Hz
  • 位深:16 bits 小端序(s16le)
  • 声道:仅支持单声道
  • 格式:推荐 pcm 原始格式

如果用户上传的是 mp3、amr 等其他格式,必须先用 ffmpeg 转码:

ffmpeg -i input.mp3 -acodec pcm_s16le -f s16le -ac 1 -ar 16000 output.pcm

这行命令就是把任意音频转成 16k 单声道 16 位 pcm 格式,符合接口要求。

2.2 语音识别 SDK 基础使用

百度的 C++ SDK 叫 aip-cpp-sdk,核心类是aip::Speech,直接调用recognize方法就能识别。

基础调用示例:

#include "aip-cpp-sdk/speech.h" void asr(aip::Speech& client) { // 读取音频文件 std::string file_content; aip::get_file_content("16k.pcm", &file_content); // 可选参数,指定识别模型 std::map<std::string, std::string> options; options["dev_pid"] = "1537"; // 普通话输入法模型 // 发起识别请求 Json::Value result = client.recognize(file_content, "pcm", 16000, options); // err_no为0表示成功 if (result["err_no"].asInt() != 0) { std::cout << "识别失败: " << result["err_msg"].asString() << std::endl; return; } // 取第一条识别结果 std::cout << result["result"][0].asString() << std::endl; } int main() { std::string app_id = "你的APPID"; std::string api_key = "你的API_KEY"; std::string secret_key = "你的SECRET_KEY"; aip::Speech client(app_id, api_key, secret_key); asr(client); return 0; }

2.3 SDK 二次封装:ASRClient 类

同样做一层封装,业务层只需要传入语音数据,直接拿到识别结果字符串,出错返回空。

#pragma once #include <string> #include "aip-cpp-sdk/speech.h" #include "logger.hpp" class ASRClient { public: ASRClient(const std::string& app_id, const std::string& api_key, const std::string& secret_key) : _client(app_id, api_key, secret_key) {} // 语音识别,输入pcm音频数据,返回识别后的文字,失败返回空 std::string recognize(const std::string& speech_data) { Json::Value result = _client.recognize(speech_data, "pcm", 16000, aip::null); if (result["err_no"].asInt() != 0) { LOG_ERROR("语音识别失败: {}", result["err_msg"].asString()); return std::string(); } return result["result"][0].asString(); } private: aip::Speech _client; };

代码解读:

  • 封装后业务层完全不用关心 aip 库的接口、参数、JSON 解析,只需要知道「传音频进,出文字」
  • 出错统一打日志并返回空字符串,上层业务判断空就知道识别失败了,处理逻辑非常简洁

2.4 功能测试

#include "../../common/asr.hpp" #include <gflags/gflags.h> DEFINE_string(app_id, "", "语音平台应用ID"); DEFINE_string(api_key, "", "语音平台API密钥"); DEFINE_string(secret_key, "", "语音平台加密密钥"); DEFINE_bool(run_mode, false, "运行模式"); DEFINE_string(log_file, "", "日志文件"); DEFINE_int32(log_level, 0, "日志级别"); int main(int argc, char* argv[]) { google::ParseCommandLineFlags(&argc, &argv, true); init_logger(FLAGS_run_mode, FLAGS_log_file, FLAGS_log_level); ASRClient client(FLAGS_app_id, FLAGS_api_key, FLAGS_secret_key); std::string file_content; aip::get_file_content("16k.pcm", &file_content); std::string res = client.recognize(file_content); if (res.empty()) { return -1; } std::cout << "识别结果: " << res << std::endl; return 0; }

编译命令:

test: test.cc g++ -std=c++17 $^ -o $@ -lfmt -lspdlog -lgflags -lcurl -lcrypto /usr/lib/x86_64-linux-gnu/libjsoncpp.so.1.8.4

三、CMake 构建系统入门与实战

3.1 CMake 基础介绍

CMake 是一个开源、跨平台的构建系统,它不直接编译代码,而是根据CMakeLists.txt配置文件,生成对应平台的构建文件:Linux 下生成 Makefile,Windows 下生成 VS 工程,macOS 下生成 Xcode 工程。

它的核心价值就是一次编写,到处构建,不用为每个平台单独维护一套构建脚本。

主要特点

  • 跨平台:一套配置适配所有主流系统和编译器
  • 管理大型项目:支持多目录、多模块、嵌套构建
  • 高效率:比 autotools 这类老工具构建速度快很多
  • 可扩展:可以编写自定义模块,支持自定义编译步骤
  • 语法简洁:比手写 Makefile 简单直观,维护成本低

最小示例

最简单的 CMakeLists.txt 只需要三行:

# 要求的最低CMake版本 cmake_minimum_required(VERSION 3.0.0) # 项目名称 project(test) # 生成可执行文件 add_executable(main main.cc)

标准构建流程:

mkdir build && cd build cmake .. make

推荐用 build 目录做外编译,生成的中间文件都在 build 里,不会污染源码目录。

3.2 核心语法速览

给大家整理最常用的语法,日常项目基本够用:

变量与列表

# 定义变量 set(variable_name "value") # 列表追加元素 set(src_files "") list(APPEND src_files main.cc child.cc)

常用预定义变量

变量名说明
CMAKE_CXX_STANDARDC++ 标准版本,比如 17
CMAKE_CURRENT_SOURCE_DIR当前 CMakeLists.txt 所在的源码目录
CMAKE_CURRENT_BINARY_DIR当前执行 cmake 命令的构建目录
CMAKE_INSTALL_PREFIXmake install 的默认安装路径

重点区分:SOURCE_DIR是源码路径,BINARY_DIR是构建产物路径。外编译模式下这俩是不一样的,生成文件、找资源的时候千万别搞混。

字符串替换

# 把src_files里的.cc后缀替换成.o,存到obj_files string(REPLACE ".cc" ".o" obj_files ${src_files})

头文件与链接库

# 添加头文件搜索路径 include_directories(/usr/include/xxx) # 给目标添加链接库 target_link_libraries(main spdlog fmt)

消息打印

# 普通提示 message(STATUS "这是普通提示信息") # 致命错误,直接终止cmake message(FATAL_ERROR "这是致命错误")

查找源码文件

# 把src_dir目录下所有源码文件都加到src_files里 aux_source_directory(src_dir src_files)

条件判断

if (NOT EXISTS 文件路径) # 文件不存在时的逻辑 endif()

自定义构建命令

add_custom_command( PRE_BUILD # 编译前执行 COMMAND odb ... # 要执行的命令 DEPENDS student.hxx # 依赖的文件,依赖变了才重新执行 OUTPUT student-odb.cxx # 生成的输出文件 COMMENT "生成ODB框架代码" # 执行时打印的提示 )

这个非常有用,比如生成 protobuf 代码、ODB 代码、flex/bison 代码,都可以用它自动完成。

子目录与安装

# 添加子目录,子目录里也要有CMakeLists.txt add_subdirectory(test2) # 设置安装规则,运行make install时把main安装到bin目录 INSTALL(TARGETS main RUNTIME DESTINATION bin)

3.3 实战:ODB 项目的 CMake 构建

光看语法太抽象,我们拿 ODB 项目练手,这是一个稍微复杂点的场景:需要先通过 ODB 工具生成框架代码,再和业务源码一起编译。

项目目录结构

test2/ ├── CMakeLists.txt ├── entity/ │ └── student.hxx # ODB实体定义 └── source/ └── main.cc # 业务源码

完整 CMakeLists.txt

# 1. 最低版本要求 cmake_minimum_required(VERSION 3.1.3) # 2. 项目名称 project(odb-test) # 3. ODB代码自动生成逻辑 set(odb_path ${CMAKE_CURRENT_SOURCE_DIR}/entity) set(odb_files student.hxx) set(odb_srcs "") foreach(odb_file ${odb_files}) # 替换出对应的生成文件名 string(REPLACE ".hxx" "-odb.hxx" odb_hxx ${odb_file}) string(REPLACE ".hxx" "-odb.cxx" odb_cxx ${odb_file}) # 如果生成文件不存在,就添加自定义命令预先生成 if (NOT EXISTS ${CMAKE_CURRENT_BINARY_DIR}/${odb_cxx}) add_custom_command( PRE_BUILD COMMAND odb -d mysql --std c++11 --generate-query --generate-schema --profile boost/date-time ${odb_path}/${odb_file} DEPENDS ${odb_path}/${odb_file} OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/${odb_cxx} COMMENT "生成ODB框架代码: ${odb_cxx}" ) endif() # 把生成的源码加入列表 list(APPEND odb_srcs ${CMAKE_CURRENT_BINARY_DIR}/${odb_cxx}) endforeach() # 4. 收集业务源码 set(src_files "") aux_source_directory(${CMAKE_CURRENT_SOURCE_DIR}/source src_files) # 5. 声明生成目标 add_executable(main ${src_files} ${odb_srcs}) # 6. 添加头文件路径 include_directories(${CMAKE_CURRENT_SOURCE_DIR}/entity) include_directories(${CMAKE_CURRENT_BINARY_DIR}) # 7. 链接依赖库 target_link_libraries(main odb-mysql odb odb-boost gflags) # 8. 设置安装规则 INSTALL(TARGETS main RUNTIME DESTINATION bin)

代码解读:

  • 用foreach遍历所有实体文件,自动生成对应的 ODB 代码,新增实体文件只需要往列表里加名字就行,不用改构建逻辑
  • PRE_BUILD保证代码生成在编译之前执行,并且只有依赖文件变了才会重新生成,增量编译效率高
  • 生成的文件放在构建目录里,不污染源码目录,符合外编译的最佳实践

3.4 多目录嵌套与安装配置

如果项目有多个模块,可以在上层放一个总控 CMakeLists.txt,用add_subdirectory管理所有子模块:

cmake_minimum_required(VERSION 3.1.3) project(all-test) # 把安装前缀设为构建目录,方便测试 set(CMAKE_INSTALL_PREFIX ${CMAKE_CURRENT_BINARY_DIR}) # 添加所有子模块 add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/test2) # add_subdirectory(user) # add_subdirectory(message) # ...

构建流程:

mkdir build && cd build cmake .. make # 编译所有子模块 make install # 把所有产物安装到指定目录

这样不管项目有多少个子服务,一条 cmake + make 就能全部编译完成,非常方便。


四、核心要点总结

  1. 第三方 SDK 一定要做二次封装:不要把平台相关的调用散落在业务代码里。封装成独立的类,对外只暴露业务语义的接口,以后换平台、升级 SDK,只需要改封装类,业务代码不用动。
  2. 语音识别的坑在音频格式:识别不准、结果乱码,90% 的情况不是平台的问题,是音频参数不符合要求。接入前一定要先把音频转码流程跑通,16k 单声道 16 位 pcm 是硬要求。
  3. CMake 的核心价值是自动化:它不只是替代手写 Makefile,更重要的是能把代码生成、资源编译、多模块管理、安装打包这些流程全部自动化,工程化程度越高,后期维护越省心。
  4. 外编译是最佳实践:永远不要在源码目录直接执行 cmake,单独建 build 目录,生成产物和源码分离,清理方便,也不会误提交生成文件。
  5. 自定义命令是进阶利器:add_custom_command可以把任何外部工具集成到构建流程里,protobuf、ODB、Qt moc 这些代码生成场景都能用,彻底告别手动执行脚本。

结尾:

🍓 我是草莓熊 Lotso!若这篇技术干货帮你打通了学习中的卡点: 👀 【关注】跟我一起深耕技术领域,从基础到进阶,见证每一次成长 ❤️ 【点赞】让优质内容被更多人看见,让知识传递更有力量 ⭐ 【收藏】把核心知识点、实战技巧存好,需要时直接查、随时用 💬 【评论】分享你的经验或疑问(比如曾踩过的技术坑?),一起交流避坑 🗳️ 【投票】用你的选择助力社区内容方向,告诉大家哪个技术点最该重点拆解 技术之路难免有困惑,但同行的人会让前进更有方向~愿我们都能在自己专注的领域里,一步步靠近心中的技术目标!

结语:到这里,短信验证码、语音识别两个第三方服务,以及 CMake 构建系统这三项基建就全部落地了。短信服务支撑手机号注册登录,语音识别提升语音消息的使用体验,CMake 则让整个项目的构建管理更规范、更高效。接下来我们就可以基于这些基建,开始逐个实现各个业务子服务了。后续也会继续分享更多 C++ 微服务、即时通讯系统的实战内容,欢迎关注交流。

✨把这些内容吃透超牛的!放松下吧✨
ʕ˘ᴥ˘ʔ
づきらど

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询