C++ 中的组件化架构Component-Based Architecture


在这里插入图片描述

组件化架构的核心思想

组件化架构的核心思想是将软件系统分解为独立的、可重用的组件或模块,每个组件负责实现特定的功能或服务。这些组件之间通过明确定义的接口进行通信和交互,而不需要了解彼此的内部实现细节。组件化架构的核心思想可以归纳为以下几个方面:

  1. 模块化设计: 将系统划分为小的、独立的模块或组件,每个组件都具有清晰的责任和功能范围。这些组件应该是相对独立的,能够以自包含的方式存在,并且易于理解、维护和测试。
  2. 接口定义: 每个组件都应该有明确定义的接口,用于与其他组件进行通信。接口定义了组件之间的约定和交互方式,使得组件之间的耦合度降低,并且可以更容易地进行替换或升级。
  3. 封装和信息隐藏: 组件的内部实现应该对外部组件隐藏,只暴露必要的接口。这样做可以降低组件之间的耦合度,提高系统的可维护性和可重用性。
  4. 独立开发和测试: 每个组件都可以独立开发、测试和部署,而不会影响其他组件。这样可以加快开发周期,提高开发效率,并且可以更容易地定位和修复bug。
  5. 可重用性和可扩展性: 组件化架构使得组件可以在不同的项目中重用,从而提高了代码的可重用性。同时,通过定义清晰的接口和依赖关系,可以更容易地添加新功能或替换现有的组件,从而提高了系统的可扩展性。

组件化架构与封装

组件化架构看似简单,和C++类的封装也有不少相似之处,但也有一些关键的区别。

  1. 封装的粒度
    • 在C++中,封装通常是指将数据和操作数据的函数捆绑在一起,形成一个类。这种封装是以类为单位的,它将相关的数据和行为组合在一起,以实现数据的隐藏和保护。
    • 在组件化架构中,封装的粒度可能更大。一个组件可以包含多个类,甚至是多个相关的功能模块。组件可以是一个独立的、可部署的单元,它可能包含多个类以及与其他组件的接口。
  2. 独立性和复用性
    • 类的封装更多地关注于单个类的独立性和复用性。一个类封装了特定的功能,可以被其他类或模块调用和复用,但它通常是在同一个应用程序中使用的。
    • 组件化架构更强调独立的组件之间的交互和复用。一个组件可以被多个应用程序或系统使用,它提供了一种更高级别的封装,将多个类或模块组合成一个可独立部署和维护的单元。
  3. 部署和维护
    • 类的封装通常是在编译时静态确定的,类的实现通常包含在编译后的可执行文件中。类的使用是在程序编译时决定的。
    • 组件化架构更注重于动态加载和部署。组件可以在运行时加载和替换,它们可以独立部署和更新,而不需要重新编译整个应用程序。

虽然类的封装是组件化架构的一部分,但组件化架构是更高级别的概念,涉及到更大粒度的封装和更高级别的复用性、独立性以及动态部署和维护。因此,虽然它们有一些相似之处,但它们的目标和应用场景是不同的。

组件之间的互相交互

组件之间的互相交互需要注意以下几点:

  1. 明确定义的接口: 每个组件应该有清晰、明确定义的接口,用于与其他组件进行通信。这些接口应该尽可能简洁清晰,只暴露必要的功能和数据,避免暴露过多的内部细节。
  2. 解耦和低依赖: 组件之间的交互应该尽量解耦,即减少彼此之间的依赖关系。高度耦合的组件之间的修改会产生连锁反应,降低了系统的灵活性和可维护性。因此,在设计组件之间的交互时,应该尽量降低它们之间的依赖关系,通过接口抽象来实现解耦。
  3. 异步通信和事件驱动: 使用异步通信和事件驱动的方式可以降低组件之间的耦合度。通过事件驱动的方式,一个组件可以发布事件,而其他组件可以订阅并响应这些事件,从而实现松散的耦合。
  4. 异常处理和错误传递: 在组件之间的交互过程中,需要考虑异常处理和错误传递的机制。组件之间的错误应该被适当地捕获和处理,避免错误向上传递,导致系统的不稳定性和异常。
  5. 安全性和权限控制: 组件之间的交互可能涉及到敏感数据或者重要操作,因此需要考虑安全性和权限控制的问题。确保只有有权限的组件能够访问和操作相关的数据和功能,防止数据泄露或者恶意操作。

综上所述,组件之间的交互需要在设计阶段就考虑清楚,并采取合适的策略来确保交互的稳定性、安全性和灵活性。

案例:系统管理 - 进程管理和日志管理

1. 分析需求

在许多软件系统中,系统管理是一个至关重要的部分。系统管理涉及诸多方面,其中包括进程管理和日志 管理等。

组件化架构在系统管理方面有诸多优势:

  • 模块化:通过将系统管理功能划分为独立的组件,可以降低系统的复杂性,使得每个组件可以被独立开发、测试和维护。
  • 可维护性:组件化架构使得系统管理功能更容易理解和修改。每个组件都有清晰的责任范围,使得修改和扩展系统变得更加简单和安全。
  • 可重用性:通过将系统管理功能组织为独立的组件,可以更容易地在不同的项目中重用这些组件,从而提高了开发效率。
  • 灵活性:组件化架构使得系统管理功能更加灵活。可以根据需求替换、升级或者扩展特定的组件,而不会影响到系统的其他部分。

2. 开始设计

在设计组件化系统管理框架时,我们可以考虑系统管理可能包含多个组件,每个组件负责不同的功能。本文我们将着重介绍以下两个组件:

进程管理组件

进程管理组件负责监控、创建和终止系统中的进程。它是系统管理框架中的一个核心组件,其主要功能包括:

  • 进程创建和启动:能够创建新的进程并启动执行指定的程序。
  • 进程监控和状态查询:能够监控正在运行的进程,查询其状态并获取相关信息,如进程ID、名称等。
  • 进程终止和资源释放:能够安全地终止正在运行的进程,并释放相关资源,以确保系统资源的有效利用和管理。

日志管理组件

日志管理组件负责记录系统运行时产生的日志信息。它可以提供日志的记录、存储、检索和分析等功能,帮助开发人员监控系统的运行状态、排查问题和分析性能。

以下是日志管理组件可能包含的一些功能:

  • 日志记录:记录系统运行时产生的日志信息,包括普通信息、警告和错误信息等。
  • 日志存储:将日志信息存储到文件、数据库或其他存储介质中,以便后续检索和分析。
  • 日志检索:提供检索和过滤日志的功能,以便开发人员快速定位和查看特定时间段、特定级别或特定关键字的日志信息。

通过将系统管理功能划分为这些独立的组件,我们可以实现一个灵活、可维护和可扩展的系统管理框架。每个组件负责特定的功能,可以被独立开发、测试和维护。同时,这些组件之间通过清晰的接口进行通信,使得系统更加模块化和可重用。

进程管理组件代码示例

process_executor.hpp

#ifndef EXEC_PROCESS_EXECUTOR_HPP
#define EXEC_PROCESS_EXECUTOR_HPP
#include <string>
#include <vector>
#include <memory>
#include <iostream>
#include <sstream>
#include <errno.h>
#include <chrono>
#include <boost/process.hpp>
#include <boost/asio.hpp>
#include <system_error> 
#include "log_manager.hpp"
class ProcessExecutor
{
    enum processstatus
    {
        PROCESS_STATUS_RUNNING = 0,
        PROCESS_STATUS_STOPPED = 1,
        PROCESS_STATUS_ERROR = 2,
        PROCESS_STATUS_UNKNOWN = 3,
    };
public:
    ProcessExecutor(std::string name, std::string bin, std::vector<std::string> args = {}):name_(name), bin_(bin), args_(args),status_(PROCESS_STATUS_UNKNOWN) {
        std::cout << "ProcessExecutor constructor" << std::endl;
        LogManager::getInstance("log.txt").log(LogManager::LogLevel::INFO, "ProcessExecutor constructor");
    }
    ~ProcessExecutor(){
        std::cout << "ProcessExecutor destructor" << std::endl;
        stop();
    }
    void start() {
        if (isRunning()) {
            std::cout << "Process is already running." << std::endl;
            return;
        }
        try {
            // 尝试启动进程
            process_ = boost::process::child(bin_, args_);

            // 检查进程是否成功启动
            if (!process_.running()) {
                std::cerr << "Failed to start process." << std::endl;
                LogManager::getInstance("log.txt").log(LogManager::LogLevel::ERROR, "Failed to start process.");
                status_ = PROCESS_STATUS_ERROR;
                throw std::system_error(errno, std::system_category(), "Failed to start process.");
            } else {
                std::cout << "Process started successfully." << std::endl;
                status_ = PROCESS_STATUS_RUNNING;
            }
        } catch (const boost::process::process_error& e) {
            std::cerr << "Error starting process: " << e.what() << std::endl;
            LogManager::getInstance("log.txt").log(LogManager::LogLevel::ERROR, "Error starting process: " + std::string(e.what()));
            status_ = PROCESS_STATUS_ERROR;
            throw std::system_error(errno, std::system_category(), "Failed to start process.");
        }
    }

    void stop() {
        if (isRunning()) {
            try{
                process_.terminate(); // 尝试正常终止进程

                boost::asio::io_context io_context;
                boost::asio::steady_timer timer(io_context, std::chrono::seconds(5));

                timer.async_wait([&](const boost::system::error_code& /*ec*/) {
                    if (this->isRunning()) {
                        ::kill(process_.id(), SIGKILL);
                        process_.wait(); // 等待进程结束,确保资源被释放
                    }
                });

                io_context.run(); // 启动 ASIO 处理,等待超时或进程结束

                // 如果进程已经正常结束,或在上述检查后判定已结束
                if (!this->isRunning()) {
                    status_ = PROCESS_STATUS_STOPPED;
                    // 获取并处理退出状态
                    std::cout << this->getName() << "Process stopped." << "exit code: " << process_.exit_code() << std::endl;
                } else {
                    std::cerr << "Failed to stop process." << std::endl;
                    LogManager::getInstance("log.txt").log(LogManager::LogLevel::ERROR, "Failed to stop process.");
                }
            } //boost::process::process_error std::exception
            catch (const boost::process::process_error& e ) {
                std::cerr << "Error stopping process: " << e.what() << std::endl;
                status_ = PROCESS_STATUS_ERROR;
                throw std::system_error(errno, std::system_category(), "Failed to stop process.");
                LogManager::getInstance("log.txt").log(LogManager::LogLevel::ERROR, "Error stopping process: " + std::string(e.what()));
            }
            catch (const std::exception& e) {
                std::cerr << "Error stopping process: " << e.what() << std::endl;
                status_ = PROCESS_STATUS_ERROR;
                throw std::system_error(errno, std::system_category(), "Failed to stop process.");
                LogManager::getInstance("log.txt").log(LogManager::LogLevel::ERROR, "Error stopping process: " + std::string(e.what()));
            }
        }
    }
    void restart() {
        stop();
        start();
    }
    bool isRunning() const {
        return process_.valid() && process_.running();
    }
    pid_t getPid() const {
        return process_.id();
    }
    std::string getName() const {
        return name_;
    }
    std::string getBin() const {
        return bin_;
    }
private:
    mutable boost::process::child process_;
    std::string name_;
    std::string bin_;
    std::vector<std::string> args_;
    int status_;
};

#endif //EXEC_PROCESS_EXECUTOR_HPP

processmgr.hpp

#ifndef PROCESS_MGR_HPP
#define PROCESS_MGR_HPP

#include "process_executor.hpp"
class ProcessManager
{
public:
    static ProcessManager& getInstance(){
        static ProcessManager instance;
        return instance;
    }
    ProcessManager(ProcessManager&) = delete;
    ProcessManager& operator=(ProcessManager&) = delete;
    bool addProcess(std::string process_name,std::string bin, std::vector<std::string> args = {});
  
    bool removeProcess(std::string process_name);
    bool removeAllProcess();

    bool startProcess(std::string process_name);
    bool stopProcess(std::string process_name);

    int InspectionApps();

private:
    ProcessManager(){
    }
    ~ProcessManager(){
    }
    bool _startProcess(std::string process_name);
    bool _stopProcess(std::string process_name);

    std::unordered_map <std::string, std::shared_ptr<ProcessExecutor>> process_map_;
};
#endif // PROCESS_MGR_HPP

processmgr.cpp

#include "process_manager.hpp"
#include "boost/filesystem.hpp"
#include <iostream>
#include "log_manager.hpp"
bool ProcessManager::addProcess(std::string process_name, std::string bin, std::vector<std::string> args) {
    boost::filesystem::path p(bin);
    
    if (!p.is_absolute()) {
        bin = (boost::filesystem::current_path() / bin).string();
    }
    std::cout << "Adding process: " << process_name << " " << bin << std::endl;
    // 检查文件是否存在并且可执行
    if (boost::filesystem::exists(p) && access(bin.c_str(), X_OK) == 0) {
        process_map_[process_name] = std::make_shared<ProcessExecutor>(process_name, bin, args);
    } else {
        std::cerr << "The specified binary path is not an executable or does not exist: " << bin << std::endl;
        LogManager::getInstance("log.txt").log(LogManager::LogLevel::ERROR, "The specified binary path is not an executable or does not exist: " + bin);
        return false;
    }
    return true;
}
bool ProcessManager::_startProcess(std::string process_name) {
    try 
    {
        process_map_[process_name]->start();
    } catch (const std::system_error& e) {
        std::cerr << "Error starting process: " << e.what() << std::endl;
        LogManager::getInstance("log.txt").log(LogManager::LogLevel::ERROR, "Error starting process: " + std::string(e.what()));
        return false;
    }

    return true;
}
bool ProcessManager::_stopProcess(std::string process_name) {
    try {
        process_map_[process_name]->stop();
    } catch (const std::system_error& e) {
        std::cerr << "Error stopping process: " << e.what() << std::endl;
        LogManager::getInstance("log.txt").log(LogManager::LogLevel::ERROR, "Error stopping process: " + std::string(e.what()));
        return false;
    }
    return true;
}

bool ProcessManager::startProcess(std::string process_name) {
    auto it = process_map_.find(process_name);
    if (it != process_map_.end()) {
        if (!it->second->isRunning()) {
            return _startProcess(process_name);
        }
    }
    return true;
}
bool ProcessManager::stopProcess(std::string process_name){
    auto it = process_map_.find(process_name);
    if (it != process_map_.end()) {
        if (it->second->isRunning()) {
            return _stopProcess(process_name);
        }
    }
    return true;
}
bool ProcessManager::removeProcess(std::string process_name){
    auto it = process_map_.find(process_name);
    if (it != process_map_.end()) {
        if (it->second->isRunning()) {
            _stopProcess(process_name);
        }
        process_map_.erase(it);
    }
    return true;
}
bool ProcessManager::removeAllProcess(){
    for (auto it = process_map_.begin(); it != process_map_.end(); ++it) {
        if (it->second->isRunning()) {
            _stopProcess(it->first);
        }
    }
    process_map_.clear();
    return true;
}
int ProcessManager::InspectionApps(){
    //std::cout << "InspectionApps size: " << process_map_.size() << std::endl;
    // 检查所有进程的状态
    for (auto it = process_map_.begin(); it != process_map_.end(); ++it) {
        if (!it->second->isRunning()) {
            std::cout << "Process " << it->first << " is not running. Restarting..." << std::endl;
            if(_startProcess(it->first) == false) {
                std::cerr << "Failed to restart process: " << it->first << std::endl;
                LogManager::getInstance("log.txt").log(LogManager::LogLevel::ERROR, "Failed to restart process: " + it->first);
                return -1;
            }
        }
    }
    return 0;
}

ProcessExecutor 类

这个类负责执行系统中的进程管理功能。让我们来分析一下它的主要功能:

  • 构造函数和析构函数:构造函数负责初始化进程管理器,析构函数负责在对象销毁时停止运行中的进程。
  • start() 方法:启动一个新的进程。如果进程已经在运行,它将不执行任何操作,并输出一条提示信息。
  • stop() 方法:停止当前运行的进程。它首先尝试正常终止进程,如果在一定时间内进程没有正常终止,它将发送一个 SIGKILL 信号来强制终止进程。
  • restart() 方法:先停止当前运行的进程,然后再启动一个新的进程。
  • isRunning() 方法:检查当前进程是否正在运行。
  • getPid() 和 getName() 方法:分别返回当前进程的进程ID和名称。

ProcessManager 类

这个类是一个单例类,负责管理系统中的所有进程。它包含以下功能:

  • addProcess() 方法:向进程管理器中添加一个新的进程。
  • removeProcess() 和 removeAllProcess() 方法:分别用于移除单个进程和移除所有进程。
  • startProcess() 和 stopProcess() 方法:分别用于启动和停止指定名称的进程。
  • InspectionApps() 方法:用于检查当前所有进程的状态。

日志管理组件代码示例

#ifndef LOG_MANAGER_HPP
#define LOG_MANAGER_HPP

#include <iostream>
#include <fstream>
#include <string>
#include <ctime>
#include <mutex>
class LogManager {
public:
    enum class LogLevel {
        INFO,
        WARNING,
        ERROR
    };
    // 获取 LogManager 的单例实例
    static LogManager& getInstance(const std::string& filename) {
        static LogManager instance(filename);
        return instance;
    }

    // 防止拷贝和赋值操作
    LogManager(const LogManager&) = delete;
    LogManager& operator=(const LogManager&) = delete;


    // 记录日志信息
    void log(LogLevel level, const std::string& message) {
        // 使用互斥锁保护对日志文件的访问
        std::lock_guard<std::mutex> lock(mutex_);
        std::ofstream file(filename_, std::ios::app);
        if (!file.is_open()) {
            std::cerr << "Error: Unable to open log file." << std::endl;
            return;
        }

        std::string level_str;
        switch (level) {
            case LogLevel::INFO:
                level_str = "[INFO] ";
                break;
            case LogLevel::WARNING:
                level_str = "[WARNING] ";
                break;
            case LogLevel::ERROR:
                level_str = "[ERROR] ";
                break;
        }

        std::time_t now = std::time(nullptr);
        std::string time_str = std::ctime(&now);
        // 删除时间字符串中的换行符
        time_str.erase(std::remove(time_str.begin(), time_str.end(), '\n'), time_str.end());

        file << time_str << " " << level_str << message << std::endl;
        file.close();
    }

    // 检索和过滤日志信息
    void search(const std::string& keyword) {
        // 使用互斥锁保护对日志文件的访问
        std::lock_guard<std::mutex> lock(mutex_);
        std::ifstream file(filename_);
        if (!file.is_open()) {
            std::cerr << "Error: Unable to open log file." << std::endl;
            return;
        }

        std::string line;
        while (std::getline(file, line)) {
            if (line.find(keyword) != std::string::npos) {
                std::cout << line << std::endl;
            }
        }
        file.close();
    }
    // 日志存储:将日志信息存储到文件
    void store(const std::string& message) {
        // 使用互斥锁保护对日志文件的访问
        std::lock_guard<std::mutex> lock(mutex_);
        std::ofstream file(filename_, std::ios::app);
        if (!file.is_open()) {
            std::cerr << "Error: Unable to open log file." << std::endl;
            return;
        }
    
        std::time_t now = std::time(nullptr);
        std::string time_str = std::ctime(&now);
        // 删除时间字符串中的换行符
        time_str.erase(std::remove(time_str.begin(), time_str.end(), '\n'), time_str.end());
    
        file << time_str << " " << message << std::endl;
        file.close();
    }


private:
    LogManager(const std::string& filename) : filename_(filename) {}
    std::string filename_;
    std::mutex mutex_; // 添加互斥锁,用于保护对日志文件的访问
};

#endif // LOG_MANAGER_HPP

这个日志管理组件是一个单例类,它具有以下功能:

  • 构造函数:构造函数是私有的,只能在类内部访问。它接受日志文件的文件名作为参数,并用该文件名初始化日志管理器。
  • getInstance() 方法:getInstance() 方法用于获取 LogManager 的单例实例。如果 LogManager 的实例不存在,则会创建一个新的实例,并返回对该实例的引用。如果 LogManager 的实例已经存在,则会直接返回对该实例的引用。
  • log() 方法:log() 方法用于记录日志信息。它接受日志级别和消息作为参数,并将时间戳、日志级别和消息写入日志文件中。在写入日志文件之前,该方法会获取互斥锁 mutex_,以确保在多线程环境下的线程安全性。
  • search() 方法:search() 方法用于检索和过滤日志信息。它接受一个关键字作为参数,在日志文件中搜索包含该关键字的日志条目,并将匹配的日志信息打印到控制台。在搜索日志文件之前,该方法也会获取互斥锁 mutex_,以确保在多线程环境下的线程安全性。
  • store() 方法:store() 方法用于将日志信息直接存储到日志文件中,而不添加日志级别。它接受一个消息作为参数,并将时间戳和消息写入日志文件中。同样地,在存储日志信息之前,该方法会获取互斥锁 mutex_,以确保在多线程环境下的线程安全性。

这个日志管理组件使用了互斥锁 mutex_ 来保护对日志文件的访问,确保在多线程环境下的线程安全性。通过 getInstance() 方法获取 LogManager 的单例实例,使得该组件在整个程序中只有一个实例存在,确保了日志管理的一致性和可靠性。

mian.cpp

#include "log_manager.hpp"
#include "ProcessMgr/process_manager.hpp"

int main() {

    // 获取 LogManager 的单例实例
    LogManager& logManager = LogManager::getInstance("log.txt");

    // 使用单例实例进行日志记录、存储和检索
    logManager.log(LogManager::LogLevel::INFO, "This is an information message.");
    logManager.log(LogManager::LogLevel::WARNING, "This is a warning message.");
    logManager.log(LogManager::LogLevel::ERROR, "This is an error message.");




    logManager.store("This is a message directly stored in the log.");

    logManager.search("error");

    // 测试进程管理器功能
    ProcessManager& processManager = ProcessManager::getInstance();

    processManager.addProcess("Process1", "/bin/echo", {"Hello from Process1"});
    processManager.addProcess("Process2", "/bin/echo", {"Hello from Process2"});

    processManager.startProcess("Process1");
    processManager.startProcess("Process2");

    std::this_thread::sleep_for(std::chrono::seconds(5));

    processManager.stopProcess("Process1");

    return 0;
}

如图所示,让我们浏览一下这个流程,

  1. 请求组件:流程中的每个决策节点都包含了请求组件的步骤,例如请求进程管理组件或请求日志组件。这体现了组件化架构中的模块化设计,将不同功能的实现封装在独立的组件中。
  2. 解耦:在请求进程管理组件时,如果请求失败,流程会尝试请求日志组件来记录错误信息。这种设计将错误处理逻辑从进程管理组件中分离出来,实现了组件之间的解耦。
  3. 灵活性:流程中的每个决策节点都允许根据需要执行不同的操作,例如启动进程、记录日志或什么都不做。这种灵活性使得系统可以根据具体需求动态地调整行为,而不需要修改整个系统。

综上所述,这个流程很好地体现了组件化架构的设计理念,通过将不同功能的实现封装为独立的组件,并在需要时动态请求组件来完成任务,实现了模块化、解耦和灵活性。

结语

在我们的编程学习之旅中,理解是我们迈向更高层次的重要一步。然而,掌握新技能、新理念,始终需要时间和坚持。从心理学的角度看,学习往往伴随着不断的试错和调整,这就像是我们的大脑在逐渐优化其解决问题的“算法”。

这就是为什么当我们遇到错误,我们应该将其视为学习和进步的机会,而不仅仅是困扰。通过理解和解决这些问题,我们不仅可以修复当前的代码,更可以提升我们的编程能力,防止在未来的项目中犯相同的错误。

我鼓励大家积极参与进来,不断提升自己的编程技术。无论你是初学者还是有经验的开发者,我希望我的博客能对你的学习之路有所帮助。如果你觉得这篇文章有用,不妨点击收藏,或者留下你的评论分享你的见解和经验,也欢迎你对我博客的内容提出建议和问题。每一次的点赞、评论、分享和关注都是对我的最大支持,也是对我持续分享和创作的动力。


阅读我的CSDN主页,解锁更多精彩内容:泡沫的CSDN主页
在这里插入图片描述

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

泡沫o0

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值