深入实战:使用C++开发高性能RESTful API

🧑 博主简介:CSDN博客专家、CSDN平台优质创作者,高级开发工程师,数学专业,10年以上C/C++, C#, Java等多种编程语言开发经验,拥有高级工程师证书;擅长C/C++、C#等开发语言,熟悉Java常用开发技术,能熟练应用常用数据库SQL server,Oracle,mysql,postgresql等进行开发应用,熟悉DICOM医学影像及DICOM协议,业余时间自学JavaScript,Vue,qt,python等,具备多种混合语言开发能力。撰写博客分享知识,致力于帮助编程爱好者共同进步。欢迎关注、交流及合作,提供技术支持与解决方案。

技术合作请加本人wx(注明来自csdn):xt20160813

深入实战:使用C++开发高性能RESTful API

随着互联网应用的快速发展,RESTful API(Representational State Transfer)成为构建分布式系统和微服务架构的主要方式之一。虽然C++以其卓越的性能和资源控制能力在系统编程中占据重要地位,但相比于其他高级语言(如Python、Java、Go),在RESTful API开发上的应用相对较少。本文将深入探讨如何在C++中开发高性能的RESTful API,解决实际项目中的应用问题,并通过详尽的示例代码,帮助开发者掌握C++ RESTful开发的核心技巧。

目录

  1. RESTful API基础概念
    • 什么是RESTful API
    • RESTful API的特点
    • C++在RESTful API开发中的优势与挑战
  2. 常用C++ RESTful框架与库
    • CppRESTSDK(Casablanca)
    • Pistache
    • Crow
    • restbed
    • 选择合适的框架
  3. 环境搭建与项目初始化
    • 安装必要的工具与库
    • 创建C++ RESTful项目结构
  4. 实战案例:构建一个简易的C++ RESTful API
    • 需求描述
    • 使用Crow框架实现API
    • 详细代码讲解
    • 测试API
  5. 性能优化策略
    • 多线程与异步处理
    • 高效的内存管理
    • 负载均衡与水平扩展
    • 使用缓存提升响应速度
  6. 最佳实践与常见问题
    • 设计良好的API接口
    • 错误处理与日志记录
    • 安全性考虑
    • 常见问题及解决方案
  7. 总结与展望
  8. 参考资料

RESTful API基础概念

什么是RESTful API

RESTful API是基于REST(Representational State Transfer)架构风格构建的应用程序接口。REST由Roy Fielding在其博士论文中提出,强调通过无状态的通信协议(通常是HTTP)来管理和交换资源。RESTful API利用HTTP协议的动词(如GET、POST、PUT、DELETE)来操作资源,具有简单、可扩展和易于维护的特点。

RESTful API的特点

  1. 无状态性:每个请求都是独立的,服务器不保留客户端的状态信息。所有必要的信息都包含在每个请求中。
  2. 统一接口:通过标准化的接口简化系统架构,提升组件之间的独立性。
  3. 资源导向:将系统中的实体抽象为资源,通过URI(统一资源标识符)进行标识和访问。
  4. 多种表现形式:支持JSON、XML等多种数据格式,以满足不同客户端的需求。
  5. 客户端-服务器架构:客户端和服务器分离,互相独立,提升系统的灵活性和可维护性。

C++在RESTful API开发中的优势与挑战

优势

  • 高性能:C++具有接近硬件的执行效率,适用于对性能要求极高的网络应用。
  • 资源控制:精细的内存管理和资源控制能力,适合构建资源受限环境下的应用。
  • 广泛的库支持:丰富的第三方库和框架支持,助力快速开发。

挑战

  • 开发复杂度:相比于高级语言,C++在内存管理、并发编程等方面的复杂性更高。
  • 生态系统:虽然C++生态日益丰富,但在RESTful API开发方面的工具和框架相比其他语言仍略显不足。
  • 学习曲线:需要深入理解C++的底层机制及高级特性,学习曲线较陡。

常用C++ RESTful框架与库

在C++中,有多种框架和库可以用于开发RESTful API。以下是几种常用的框架:

CppRESTSDK(Casablanca)

简介:由微软开发的跨平台C++ REST SDK(简称CppRESTSDK或Casablanca),提供了丰富的HTTP通信、JSON处理、异步编程接口。

特点

  • 跨平台支持(Windows、Linux、macOS)。
  • 内置JSON解析与生成。
  • 支持异步操作,提升性能。
  • 良好的文档与社区支持。

安装方式

可以通过包管理器(如vcpkg)安装:

bash 复制代码
vcpkg install cpprestsdk

Pistache

简介:Pistache是一个轻量级的C++ REST框架,支持快速开发高性能的RESTful服务。

特点

  • 简单易用的API。
  • 高效的HTTP服务器实现。
  • 支持多线程处理。

安装方式

需要从GitHub源码编译安装:

bash 复制代码
git clone https://github.com/oktal/pistache.git
cd pistache
mkdir build && cd build
cmake ..
make
sudo make install

Crow

简介:Crow是一个轻量级的C++微框架,借鉴了Python的Flask,支持路由、模板和JSON处理。

特点

  • 类似Flask的API设计,易于上手。
  • 内置路由系统。
  • 支持多线程和异步处理。

安装方式

Crow是一个头文件库,只需包含相应的头文件即可使用。

cpp 复制代码
// 示例:包含Crow头文件
#include "crow_all.h"

可以通过GitHub获取最新版本:Crow GitHub

restbed

简介:restbed是一个现代化的C++ RESTful框架,支持异步I/O、SSL、路由等功能。

特点

  • 高性能异步网络编程。
  • 丰富的功能支持(如认证、CORS)。
  • 易于扩展和定制。

安装方式

需要从GitHub源码编译安装:

bash 复制代码
git clone https://github.com/Corvusoft/restbed.git
cd restbed
mkdir build && cd build
cmake ..
make
sudo make install

选择合适的框架

选择合适的C++ RESTful框架应根据项目需求、团队熟悉度和框架的特点来定。对于需要跨平台支持和丰富功能的项目,CppRESTSDK是一个不错的选择;而对于追求简洁和快速开发的项目,Crow或Pistache更为适合。


环境搭建与项目初始化

在开始开发C++ RESTful API之前,需要搭建开发环境并初始化项目。

安装必要的工具与库

  1. C++编译器:推荐使用支持C++11及以上标准的编译器,如GCC、Clang或MSVC。
  2. CMake:跨平台的构建工具,用于管理项目构建过程。
  3. 所选框架的依赖库:根据选择的框架,可能需要安装Boost、OpenSSL等库。

以Crow为例,安装步骤如下:

  • 安装C++编译器和CMake

    bash 复制代码
    sudo apt-get update
    sudo apt-get install build-essential cmake
  • 获取Crow库

    Crow是一个头文件库,可以直接下载或使用包管理器:

    bash 复制代码
    git clone https://github.com/CrowCpp/Crow.git
    cd Crow
    # 将crow_all.h复制到项目中
    cp single_include/crow_all.h /path/to/your/project/include/

创建C++ RESTful项目结构

建议采用清晰的项目结构,便于维护和扩展。

复制代码
my_restful_api/
├── CMakeLists.txt
├── include/
│   └── crow_all.h
├── src/
│   └── main.cpp
└── build/

CMakeLists.txt示例

cmake 复制代码
cmake_minimum_required(VERSION 3.5)
project(MyRestfulAPI)

set(CMAKE_CXX_STANDARD 14)

# 包含Crow头文件目录
include_directories(include)

# 添加源文件
add_executable(my_restful_api src/main.cpp)

实战案例:构建一个简易的C++ RESTful API

需求描述

构建一个简单的用户管理API,支持以下操作:

  • 获取用户列表(GET /users)
  • 获取单个用户信息(GET /users/{id})
  • 创建新用户(POST /users)
  • 更新用户信息(PUT /users/{id})
  • 删除用户(DELETE /users/{id})

假设用户数据存储在内存中,使用JSON作为数据交换格式。

使用Crow框架实现API

Crow框架以其简洁的API设计,非常适合快速开发RESTful服务。以下是详细的代码实现。

详细代码讲解
cpp 复制代码
// src/main.cpp
#include "crow_all.h"
#include <unordered_map>
#include <mutex>

struct User {
    int id;
    std::string name;
    std::string email;
};

// 全局用户存储
std::unordered_map<int, User> users;
std::mutex users_mutex;
int current_id = 1;

int main()
{
    crow::SimpleApp app;

    // 获取用户列表
    CROW_ROUTE(app, "/users").methods("GET"_method)([](){
        crow::json::wvalue result;
        {
            std::lock_guard<std::mutex> lock(users_mutex);
            for (const auto& pair : users) {
                crow::json::wvalue user;
                user["id"] = pair.second.id;
                user["name"] = pair.second.name;
                user["email"] = pair.second.email;
                result["users"].push_back(user);
            }
        }
        return crow::response(result);
    });

    // 获取单个用户信息
    CROW_ROUTE(app, "/users/<int>").methods("GET"_method)([](int id){
        crow::json::wvalue result;
        {
            std::lock_guard<std::mutex> lock(users_mutex);
            auto it = users.find(id);
            if (it != users.end()) {
                result["id"] = it->second.id;
                result["name"] = it->second.name;
                result["email"] = it->second.email;
                return crow::response(result);
            }
        }
        return crow::response(404, "User not found");
    });

    // 创建新用户
    CROW_ROUTE(app, "/users").methods("POST"_method)([](const crow::request& req){
        auto x = crow::json::load(req.body);
        if (!x)
            return crow::response(400, "Invalid JSON");
        if (!x.has("name") || !x.has("email"))
            return crow::response(400, "Missing fields");

        User new_user;
        {
            std::lock_guard<std::mutex> lock(users_mutex);
            new_user.id = current_id++;
            new_user.name = x["name"].s();
            new_user.email = x["email"].s();
            users[new_user.id] = new_user;
        }

        crow::json::wvalue result;
        result["id"] = new_user.id;
        result["name"] = new_user.name;
        result["email"] = new_user.email;

        return crow::response(201, result);
    });

    // 更新用户信息
    CROW_ROUTE(app, "/users/<int>").methods("PUT"_method)([](int id, const crow::request& req){
        auto x = crow::json::load(req.body);
        if (!x)
            return crow::response(400, "Invalid JSON");

        std::lock_guard<std::mutex> lock(users_mutex);
        auto it = users.find(id);
        if (it == users.end()) {
            return crow::response(404, "User not found");
        }

        if (x.has("name")) {
            it->second.name = x["name"].s();
        }
        if (x.has("email")) {
            it->second.email = x["email"].s();
        }

        crow::json::wvalue result;
        result["id"] = it->second.id;
        result["name"] = it->second.name;
        result["email"] = it->second.email;

        return crow::response(result);
    });

    // 删除用户
    CROW_ROUTE(app, "/users/<int>").methods("DELETE"_method)([](int id){
        std::lock_guard<std::mutex> lock(users_mutex);
        auto it = users.find(id);
        if (it != users.end()) {
            users.erase(it);
            return crow::response(200, "User deleted successfully");
        }
        return crow::response(404, "User not found");
    });

    // 运行服务器
    app.port(18080).multithreaded().run();
}

代码解释

  1. 引入Crow和必要的库

    cpp 复制代码
    #include "crow_all.h"
    #include <unordered_map>
    #include <mutex>
    • crow_all.h:包含Crow框架的所有功能。
    • unordered_map:用于存储用户数据的哈希表。
    • mutex:用于线程安全的同步控制。
  2. 定义用户结构体

    cpp 复制代码
    struct User {
        int id;
        std::string name;
        std::string email;
    };

    用于表示用户信息,包括ID、姓名和邮箱。

  3. 全局用户存储与同步控制

    cpp 复制代码
    std::unordered_map<int, User> users;
    std::mutex users_mutex;
    int current_id = 1;
    • users:存储用户信息的哈希表,键为用户ID。
    • users_mutex:用于保护users数据结构,确保线程安全。
    • current_id:用于生成唯一的用户ID。
  4. 定义RESTful路由与处理逻辑

    • 获取用户列表(GET /users):

      cpp 复制代码
      CROW_ROUTE(app, "/users").methods("GET"_method)([](){
          crow::json::wvalue result;
          {
              std::lock_guard<std::mutex> lock(users_mutex);
              for (const auto& pair : users) {
                  crow::json::wvalue user;
                  user["id"] = pair.second.id;
                  user["name"] = pair.second.name;
                  user["email"] = pair.second.email;
                  result["users"].push_back(user);
              }
          }
          return crow::response(result);
      });

      遍历所有用户,构建JSON响应返回。

    • 获取单个用户信息(GET /users/{id}):

      cpp 复制代码
      CROW_ROUTE(app, "/users/<int>").methods("GET"_method)([](int id){
          crow::json::wvalue result;
          {
              std::lock_guard<std::mutex> lock(users_mutex);
              auto it = users.find(id);
              if (it != users.end()) {
                  result["id"] = it->second.id;
                  result["name"] = it->second.name;
                  result["email"] = it->second.email;
                  return crow::response(result);
              }
          }
          return crow::response(404, "User not found");
      });

      根据用户ID查找用户信息,若存在则返回JSON响应,否则返回404错误。

    • 创建新用户(POST /users):

      cpp 复制代码
      CROW_ROUTE(app, "/users").methods("POST"_method)([](const crow::request& req){
          auto x = crow::json::load(req.body);
          if (!x)
              return crow::response(400, "Invalid JSON");
          if (!x.has("name") || !x.has("email"))
              return crow::response(400, "Missing fields");
      
          User new_user;
          {
              std::lock_guard<std::mutex> lock(users_mutex);
              new_user.id = current_id++;
              new_user.name = x["name"].s();
              new_user.email = x["email"].s();
              users[new_user.id] = new_user;
          }
      
          crow::json::wvalue result;
          result["id"] = new_user.id;
          result["name"] = new_user.name;
          result["email"] = new_user.email;
      
          return crow::response(201, result);
      });

      解析请求体中的JSON数据,创建新的用户并返回其信息。

    • 更新用户信息(PUT /users/{id}):

      cpp 复制代码
      CROW_ROUTE(app, "/users/<int>").methods("PUT"_method)([](int id, const crow::request& req){
          auto x = crow::json::load(req.body);
          if (!x)
              return crow::response(400, "Invalid JSON");
      
          std::lock_guard<std::mutex> lock(users_mutex);
          auto it = users.find(id);
          if (it == users.end()) {
              return crow::response(404, "User not found");
          }
      
          if (x.has("name")) {
              it->second.name = x["name"].s();
          }
          if (x.has("email")) {
              it->second.email = x["email"].s();
          }
      
          crow::json::wvalue result;
          result["id"] = it->second.id;
          result["name"] = it->second.name;
          result["email"] = it->second.email;
      
          return crow::response(result);
      });

      根据用户ID查找并更新用户信息,返回更新后的数据。

    • 删除用户(DELETE /users/{id}):

      cpp 复制代码
      CROW_ROUTE(app, "/users/<int>").methods("DELETE"_method)([](int id){
          std::lock_guard<std::mutex> lock(users_mutex);
          auto it = users.find(id);
          if (it != users.end()) {
              users.erase(it);
              return crow::response(200, "User deleted successfully");
          }
          return crow::response(404, "User not found");
      });

      根据用户ID删除用户,返回操作结果。

  5. 运行服务器

    cpp 复制代码
    // 运行服务器
    app.port(18080).multithreaded().run();

    配置服务器监听端口18080,启用多线程模式,启动服务。

编译与运行

CMakeLists.txt

cmake 复制代码
cmake_minimum_required(VERSION 3.5)
project(MyRestfulAPI)

set(CMAKE_CXX_STANDARD 14)

# 包含Crow头文件目录
include_directories(include)

# 添加源文件
add_executable(my_restful_api src/main.cpp)

编译步骤

bash 复制代码
mkdir build
cd build
cmake ..
make

运行服务器

bash 复制代码
./my_restful_api

服务器启动后,将监听18080端口,准备接受客户端请求。

测试API

可以使用curl或Postman等工具测试API。

  1. 创建新用户

    bash 复制代码
    curl -X POST -H "Content-Type: application/json" -d '{"name":"Alice","email":"[email protected]"}' http://localhost:18080/users

    响应

    json 复制代码
    {
        "id": 1,
        "name": "Alice",
        "email": "[email protected]"
    }
  2. 获取用户列表

    bash 复制代码
    curl http://localhost:18080/users

    响应

    json 复制代码
    {
        "users": [
            {
                "id": 1,
                "name": "Alice",
                "email": "[email protected]"
            }
        ]
    }
  3. 获取单个用户信息

    bash 复制代码
    curl http://localhost:18080/users/1

    响应

    json 复制代码
    {
        "id": 1,
        "name": "Alice",
        "email": "[email protected]"
    }
  4. 更新用户信息

    bash 复制代码
    curl -X PUT -H "Content-Type: application/json" -d '{"email":"[email protected]"}' http://localhost:18080/users/1

    响应

    json 复制代码
    {
        "id": 1,
        "name": "Alice",
        "email": "[email protected]"
    }
  5. 删除用户

    bash 复制代码
    curl -X DELETE http://localhost:18080/users/1

    响应

    复制代码
    User deleted successfully

性能优化策略

在实际项目中,为了确保C++ RESTful API的高性能,需要对系统进行多层次的优化。以下是常见的性能优化策略:

多线程与异步处理

策略

  • 利用多线程或异步I/O模型处理并发请求,充分利用多核CPU资源。
  • 使用线程池管理线程,避免频繁创建和销毁线程带来的开销。

实现方法

  • 线程池 :如前述实战案例中使用的ThreadPool类。
  • 异步框架:使用Boost.Asio等异步网络库,实现非阻塞I/O和事件驱动模型。

高效的内存管理

策略

  • 使用内存池等内存管理技术,减少动态内存分配和释放的频率。
  • 避免不必要的内存拷贝和对象创建,提升内存利用效率。

实现方法

  • 内存池 :前述案例中的MemoryPool模板类。
  • 使用智能指针 :如std::shared_ptrstd::unique_ptr,自动管理内存生命周期,防止内存泄漏。

负载均衡与水平扩展

策略

  • 将请求分配到多个服务器实例,分担负载,提升系统的并发处理能力。
  • 使用反向代理(如Nginx、HAProxy)实现负载均衡,提升系统的可用性和扩展性。

实现方法

  • 配置Nginx作为反向代理,均衡分配请求到多个C++ RESTful服务器实例。
  • 实现服务注册与发现机制,自动管理服务器实例的动态变化。

使用缓存提升响应速度

策略

  • 对频繁访问的数据进行缓存,减少数据库查询或复杂计算的次数,提升响应速度。
  • 使用内存缓存(如Redis)或本地缓存机制,减轻后端负载。

实现方法

  • 内存缓存 :在C++应用中使用内存数据结构(如std::unordered_map)存储缓存数据。
  • 分布式缓存:集成Redis等分布式缓存系统,通过网络访问提升缓存容量和灵活性。

使用零拷贝技术

策略

  • 通过零拷贝技术减少数据在用户态与内核态之间的拷贝次数,提升数据传输效率。

实现方法

  • sendfile :在Linux中使用sendfile系统调用,从文件描述符直接传输数据到Socket,减少不必要的数据拷贝。

    cpp 复制代码
    off_t offset = 0;
    ssize_t bytesSent = sendfile(clientSockfd, fileFd, &offset, fileSize);
  • mmap :使用mmap将文件或共享内存映射到用户空间,直接在应用程序中访问数据,减少内存拷贝。

    cpp 复制代码
    void* mapped = mmap(NULL, dataSize, PROT_READ, MAP_PRIVATE, dataFd, 0);
    write(clientSockfd, mapped, dataSize);
    munmap(mapped, dataSize);

性能分析与调优

策略

  • 使用性能分析工具(如perfValgrindIntel VTune Profiler)监测系统性能,识别瓶颈。
  • 基于分析结果,针对性地优化代码和系统配置,持续提升性能。

实现方法

  • 使用perf分析CPU性能

    bash 复制代码
    perf record -g ./my_restful_api
    perf report
  • 使用Valgrind检测内存问题

    bash 复制代码
    valgrind --tool=memcheck ./my_restful_api
  • 使用Intel VTune Profiler进行深入分析


最佳实践与常见问题

设计良好的API接口

  • 遵循RESTful原则:确保API设计符合RESTful架构风格,使用正确的HTTP动词和状态码。

  • 清晰的资源命名 :资源的URI应当直观易懂,反映资源的层次结构。

    复制代码
    GET /users - 获取用户列表
    GET /users/{id} - 获取特定用户信息
    POST /users - 创建新用户
    PUT /users/{id} - 更新用户信息
    DELETE /users/{id} - 删除用户
  • 版本控制 :通过URI或HTTP头部管理API版本,确保向后兼容性。

    复制代码
    GET /v1/users

错误处理与日志记录

  • 统一的错误响应 :定义统一的错误响应格式,提供明确的错误信息,便于客户端处理。

    json 复制代码
    {
        "error": {
            "code": 404,
            "message": "User not found"
        }
    }
  • 详细的日志记录 :记录关键操作和错误信息,辅助调试和监控。

    • 日志级别:如INFO、WARNING、ERROR等,区分日志重要性。
    • 日志格式:包含时间戳、日志级别、消息内容等关键信息。

安全性考虑

  • 认证与授权 :确保API接口的访问权限,防止未授权的访问。

    • 使用OAuth2.0、JWT(JSON Web Token)等认证机制。
  • 输入验证:对客户端输入进行严格验证,防止SQL注入、跨站脚本等攻击。

  • 加密传输 :使用HTTPS加密数据传输,保护敏感信息不被窃取。

    cpp 复制代码
    // 使用OpenSSL配置HTTPS
    crow::SSLContext sslContext;
    sslContext.load_cert_chain("server.crt", "server.key");
    app.ssl_context(sslContext);

常见问题及解决方案

  1. 多线程竞争导致的数据不一致

    • 解决方案 :使用互斥锁(std::mutex)、读写锁(std::shared_mutex)等同步机制,确保数据的线程安全访问。
  2. 内存泄漏

    • 解决方案 :使用智能指针(std::shared_ptrstd::unique_ptr)自动管理内存,避免手动分配与释放,减少内存泄漏风险。
  3. 高并发下的性能瓶颈

    • 解决方案:采用多线程、异步I/O和高效的多路复用机制,提升系统的并发处理能力。
  4. API响应时间过长

    • 解决方案:优化数据处理逻辑,使用缓存机制减少重复计算,使用零拷贝技术提升数据传输效率。
  5. 错误处理不当

    • 解决方案:设计统一的错误响应机制,捕获和处理所有可能的异常和错误情况,确保系统的稳定性。

总结与展望

本文通过详细的讲解和实战案例,深入探讨了如何在C++中开发高性能的RESTful API。我们从RESTful API的基础概念入手,介绍了C++常用的RESTful框架与库,并通过一个简易的用户管理API示例,展示了如何使用Crow框架实现各类API操作。随后,我们探讨了多种性能优化策略,包括多线程与异步处理、高效的内存管理、负载均衡与水平扩展、使用缓存与零拷贝技术等,帮助开发者构建高效、稳定的网络应用。

在实际开发过程中,选择合适的框架、合理设计API接口、注重性能优化和安全性,并结合持续的性能分析与调优,是确保C++ RESTful API高效运行的关键。随着C++生态系统的不断发展,未来将有更多高性能、易用的RESTful框架和工具涌现,进一步推动C++在网络应用开发中的广泛应用。

进一步学习

  • 探索其他C++ RESTful框架,如CppRESTSDK、Pistache等,了解它们的特点与适用场景。
  • 学习并实践更高级的性能优化技术,如异步编程模型、GPU加速等。
  • 阅读相关书籍和文档,提升C++网络编程和系统优化的知识水平。

参考资料


标签

C++、RESTful API、网络编程、性能优化、多线程、异步I/O、Crow、CppRESTSDK、Pistache、restbed、微服务

版权声明

本文版权归作者所有,未经允许,请勿转载。

相关推荐
wuqingshun3141592 小时前
蓝桥杯 6. k倍区间
c++·算法·职场和发展·蓝桥杯·深度优先
努力学习的小廉3 小时前
【C++】 —— 笔试刷题day_20
开发语言·c++
西柚小萌新4 小时前
【Python爬虫基础篇】--1.基础概念
开发语言·爬虫·python
涛ing4 小时前
【Linux “less“ 命令详解】
linux·运维·c语言·c++·人工智能·vscode·bash
ghost1434 小时前
C#学习第17天:序列化和反序列化
开发语言·学习·c#
愚润求学4 小时前
【数据结构】红黑树
数据结构·c++·笔记
難釋懷5 小时前
bash的特性-bash中的引号
开发语言·chrome·bash
Hello eveybody6 小时前
C++按位与(&)、按位或(|)和按位异或(^)
开发语言·c++
6v6-博客6 小时前
2024年网站开发语言选择指南:PHP/Java/Node.js/Python如何选型?
java·开发语言·php
Baoing_6 小时前
Next.js项目生成sitemap.xml站点地图
xml·开发语言·javascript