Console Framework

10 篇文章 0 订阅
6 篇文章 0 订阅
先把标题开着,下面准备实现控制台程序的基本框架,以后就准备直接拷贝使用

我写的这一个Console Framework是一个简单的带参数的控制台程序的基本框架,如果有很好的建议,欢迎大家拍砖!

这简单的框架主要包括以下几个部分:
1)Error的定义和管理,可以参考我的另一篇bolg[url]http://jacky-dai.iteye.com/admin/blogs/1306613[/url]
2)Command Handler的定义和实现;
3)Function Dispatcher的实现;
4) main函数的构建;


看明白这个例子之后,以后只要需要些console app的地方可以直接试用,只要针对不同的Command添加XXhandler, 然后再添加到Dispatcher中去,最后再main中支持相对应的Command

[align=center][size=medium][b]ErrorCode框架定义[/b][/size][/align]

/**************************************************************************************************/
/*!
\file CommonErrors.h

\attention
(c) Jacky Dai 2011~2016
All Rights Reserved.
*/

/**************************************************************************************************/

#ifndef XX_COMMON_ERRORS_H
#define XX_COMMON_ERRORS_H

/*** Include Files ********************************************************************************/

/*** Defines/Macros/Constants/Typedefs ************************************************************/

namespace CommonErrorSpace
{
//basic mask code
const unsigned int COMPONENT_MASK = 0x8000;

//component code
const unsigned int COMPONENT_LOGIN = 0x0001;
const unsigned int COMPONENT_DATAMANAGER = 0x0002;

//
//0x0000XXXX:XXXX means component code
//0xXXXX0000:XXXX means error code of component

//Define some marcos/functions here
#define SYSTEM_ERROR_FLAG (0x4000)
#define SYSTEM_ERROR(code) ((code | SYSTEM_ERROR_FLAG) << 16)
#define ERROR_IS_SYSTEM_ERROR(err) (((err >> 16) & SYSTEM_ERROR_FLAG) != 0)

#define COMPONENT_ERROR(component, code) ((code << 16) | (component & ~(COMPONENT_MASK)))
#define CODE_FROM_ERROR(err) (static_cast<short>(err >> 16))

//All of the error code is here
typedef enum
{
// Hex | Decimal | Descriptio
//Basic error
ERR_OK = 0x00000000, //<< 0x00000000 | 0 | No error.
ERR_SYSTEM = SYSTEM_ERROR(1), //<< 0x00010000 | 65536 | General system error.
ERR_TIMEOUT = SYSTEM_ERROR(2), //<< 0x00020000 | 131072 | Time out.

//login component
LOGIN_ERROR_UNKNOWN_ERROR = COMPONENT_ERROR(COMPONENT_LOGIN, 1), //<< 0x00010001 |65537 | Unrecognized error.
LOGIN_ERROR_UNKNOWN_USER_NAME = COMPONENT_ERROR(COMPONENT_LOGIN, 2), //<< 0x00020001 |131073 | Unrecognized user name, it is case sensitive.

//data manager component
DATAMANAGER_ERROR_UNKNOWN_ERROR = COMPONENT_ERROR(COMPONENT_DATAMANAGER, 1), //<< 0x00010002 |65538 | Unrecognized error.
DATAMANAGER_ERROR_MISSING_CONNECTION = COMPONENT_ERROR(COMPONENT_DATAMANAGER, 2) //<< 0x00020002 |131074 | Connection missed.
} CommonError;


/**************************************************************************************************/
/*!
\class ErrorHelper

Class with commands for translating error codes to useful strings.

*/
/**************************************************************************************************/
class ErrorHelper
{
public:
static const char* getErrorName(CommonError error);
static const char* getErrorDescription(CommonError error);
};

} // CommonErrorSpace

#endif //XX_COMMON_ERRORS_H


/**************************************************************************************************/
/*!
\file CommonErrors.cpp

\attention
(c) Jacky Dai 2011~2016
All Rights Reserved.
*/

/**************************************************************************************************/

/*** Include Files ********************************************************************************/
#include "CommonErrors.h"

/*** Defines/Macros/Constants/Typedefs ************************************************************/

namespace CommonErrorSpace
{
/*** Public Methods ******************************************************************************/
const char* ErrorHelper::getErrorName(CommonError error)
{
const char* result = "UKNOWN";

switch (error)
{
case ERR_OK: result = "ERR_OK"; break;
case ERR_SYSTEM: result = "ERR_SYSTEM"; break;
case ERR_TIMEOUT: result = "ERR_TIMEOUT"; break;
case LOGIN_ERROR_UNKNOWN_ERROR: result = "LOGIN_ERROR_UNKNOWN_ERROR"; break;
case LOGIN_ERROR_UNKNOWN_USER_NAME: result = "LOGIN_ERROR_UNKNOWN_USER_NAME"; break;
case DATAMANAGER_ERROR_UNKNOWN_ERROR: result = "DATAMANAGER_ERROR_UNKNOWN_ERROR"; break;
case DATAMANAGER_ERROR_MISSING_CONNECTION: result = "DATAMANAGER_ERROR_MISSING_CONNECTION"; break;
default: break;
}

return result;
}

const char* ErrorHelper::getErrorDescription(CommonError error)
{
const char *result = "UKNOWN";

switch (error)
{
case ERR_OK: result = "No error."; break;
case ERR_SYSTEM: result = "General system error."; break;
case ERR_TIMEOUT: result = "Time out."; break;
case LOGIN_ERROR_UNKNOWN_ERROR: result = "Unrecognized error."; break;
case LOGIN_ERROR_UNKNOWN_USER_NAME: result = "Unrecognized user name, it is case sensitive."; break;
case DATAMANAGER_ERROR_UNKNOWN_ERROR: result = "Unrecognized error."; break;
case DATAMANAGER_ERROR_MISSING_CONNECTION: result = "Connection missed."; break;
default: break;
}

return result;
}

} // CommonErrorSpace


[align=center][size=medium][b] Command Handler[/b][/size][/align]

/**************************************************************************************************/
/*!
\file CommonHandler.h

\attention
(c) Jacky Dai 2011~2016
All Rights Reserved.
*/

/**************************************************************************************************/
#ifndef COMMAND_HANDLER_H
#define COMMAND_HANDLER_H

#include <windows.h>
#include <string>
#include "CommonErrors.h"
using namespace std;
using namespace CommonErrorSpace;

//
//
class HandlerBase
{
public:
HandlerBase(){}
virtual ~HandlerBase(){};

virtual CommonError Parser(LPCSTR lpCommand) = 0;
virtual VOID ShowInfo() = 0;
virtual VOID HelpInfo(BOOL bDetail) = 0;

protected:
// VOID CatchError(string strMsg)
// {
// cout << "\nError!" << endl;
// cout << strMsg.c_str() << endl;
// }
//
// VOID GetAPIErrorInfo(CommonErrorSpace::CommonError errorCode)
// {
// cout << "\nError!\nCall " << m_strFuncName << "Failed!" << endl;
// cout << "ErrorCode = 0x" << setw(8) << setfill('0') << hex << errorCode << endl;
// cout << CommonErrorSpace::ErrorHelper::getErrorName(errorCode) << endl;
// cout << CommonErrorSpace::ErrorHelper::getErrorDescription(errorCode) << endl;
// }

protected:
string m_strFuncName;
};

//AlgorithmType
//
class LoginHandler : public HandlerBase
{
public:
typedef enum
{
ERR_OK = CommonErrorSpace::ERR_OK,
ERR_SYSTEM = CommonErrorSpace::ERR_SYSTEM,
ERR_TIMEOUT = CommonErrorSpace::ERR_TIMEOUT,
LOGIN_ERROR_UNKNOWN_ERROR = CommonErrorSpace::LOGIN_ERROR_UNKNOWN_ERROR,
LOGIN_ERROR_UNKNOWN_USER_NAME = CommonErrorSpace::LOGIN_ERROR_UNKNOWN_USER_NAME
} Errors;

LoginHandler(string strFuncName = "Login")
{
m_strFuncName = strFuncName;
}

virtual CommonError Parser(LPCSTR lpCommand);
virtual VOID HelpInfo(BOOL bDetail);
virtual VOID ShowInfo();

private:
void Login();

private:
string m_strUserName;
string m_strUserPwd;
};


class DataManagerHandler : public HandlerBase
{
public:
typedef enum
{
ERR_OK = CommonErrorSpace::ERR_OK,
ERR_SYSTEM = CommonErrorSpace::ERR_SYSTEM,
ERR_TIMEOUT = CommonErrorSpace::ERR_TIMEOUT,
DATAMANAGER_ERROR_UNKNOWN_ERROR = CommonErrorSpace::DATAMANAGER_ERROR_UNKNOWN_ERROR,
DATAMANAGER_ERROR_MISSING_CONNECTION = CommonErrorSpace::DATAMANAGER_ERROR_MISSING_CONNECTION
} Errors;

DataManagerHandler(string strFuncName = "DataManager")
{
m_strFuncName = strFuncName;
}

virtual CommonError Parser(LPCSTR lpCommand);
virtual VOID ShowInfo();
virtual VOID HelpInfo(BOOL bDetail);

private:
void DataManager();

private:
string m_strDmName;
string m_strDmPort;
};

#endif//COMMAND_HANDLER_H


#include "CommandHandler.h"
#include<algorithm>
#include <iostream>
using namespace std;

//Login

CommonError LoginHandler::Parser(LPCSTR lpCommand)
{
CommonErrorSpace::CommonError error = static_cast<CommonErrorSpace::CommonError>(ERR_OK);
if (NULL == lpCommand)
{
HelpInfo(TRUE);

error = static_cast<CommonErrorSpace::CommonError>(ERR_SYSTEM);
cout << ErrorHelper::getErrorName(error) << endl;
cout << ErrorHelper::getErrorDescription(error) << endl;

return error;
}
else
{
string strCommand = lpCommand;
string strBKCommand = lpCommand;
transform(strCommand.begin(),strCommand.end(),strCommand.begin(), tolower);

//Get Param1
/
size_t nTypePos = strCommand.find("name=");
size_t nOffSet = 5;
if (nTypePos != string::npos)
{
size_t nCommaPos = strCommand.find_first_of(',', nTypePos);

if (nCommaPos != string::npos)
{
m_strUserName = strBKCommand.substr(nTypePos+nOffSet, nCommaPos-nTypePos-nOffSet);
}
else
{
m_strUserName = strBKCommand.substr(nTypePos+nOffSet);
}

}
else
{
HelpInfo(TRUE);

error = static_cast<CommonErrorSpace::CommonError>(LOGIN_ERROR_UNKNOWN_USER_NAME);
cout << ErrorHelper::getErrorName(error) << endl;
cout << ErrorHelper::getErrorDescription(error) << endl;

return error;
}

//Get Param2
//
nTypePos = strCommand.find("password=");
nOffSet = 9;
if (nTypePos != string::npos)
{
size_t nCommaPos = strCommand.find_first_of(',', nTypePos);

if (nCommaPos != string::npos)
{
m_strUserPwd = strBKCommand.substr(nTypePos+nOffSet, nCommaPos-nTypePos-nOffSet);
}
else
{
m_strUserPwd = strBKCommand.substr(nTypePos+nOffSet);
}

}
else
{
HelpInfo(TRUE);

error = static_cast<CommonErrorSpace::CommonError>(LOGIN_ERROR_UNKNOWN_ERROR);
cout << ErrorHelper::getErrorName(error) << endl;
cout << ErrorHelper::getErrorDescription(error) << endl;

return error;
}

Login(); //All parameters are ok, try to call local API
ShowInfo();

cout << ErrorHelper::getErrorName(error) << endl;
cout << ErrorHelper::getErrorDescription(error) << endl;
return static_cast<CommonErrorSpace::CommonError>(error);
}
}

VOID LoginHandler::HelpInfo(BOOL bDetail)
{
transform(m_strFuncName.begin(), m_strFuncName.end(), m_strFuncName.begin(), toupper);
if (!bDetail)
{
cout << m_strFuncName.c_str() << " Login the server.";
}
else
{
cout << '\n' << m_strFuncName.c_str() << endl;
cout << " [Name=#NAME]" << endl;
cout << " [NAME] User name for server" << endl;
cout << " [Password=#PWD]" << endl;
cout << " [PWD] User password for server" << endl;
}
}

VOID LoginHandler::ShowInfo()
{
cout << "\nSucceed!\n" << endl;

cout << "\nRSP==>\n";
cout << "Login = {" << endl;
cout << " name = " << m_strUserName.c_str() << endl;
cout << " password = " << m_strUserPwd.c_str() << endl;
cout << "}" << endl;
}

void LoginHandler::Login()
{
cout << "Do login state......\n";
}

//DataManager
//
CommonError DataManagerHandler::Parser(LPCSTR lpCommand)
{
CommonErrorSpace::CommonError error = static_cast<CommonErrorSpace::CommonError>(ERR_OK);
if (NULL == lpCommand)
{
HelpInfo(TRUE);

error = static_cast<CommonErrorSpace::CommonError>(ERR_SYSTEM);
cout << ErrorHelper::getErrorName(error) << endl;
cout << ErrorHelper::getErrorDescription(error) << endl;

return error;
}
else
{
string strCommand = lpCommand;
string strBKCommand = lpCommand;
transform(strCommand.begin(),strCommand.end(),strCommand.begin(), tolower);

//Get Param1
/
size_t nTypePos = strCommand.find("name=");
size_t nOffSet = 5;
if (nTypePos != string::npos)
{
size_t nCommaPos = strCommand.find_first_of(',', nTypePos);

if (nCommaPos != string::npos)
{
m_strDmName = strBKCommand.substr(nTypePos+nOffSet, nCommaPos-nTypePos-nOffSet);
}
else
{
m_strDmName = strBKCommand.substr(nTypePos+nOffSet);
}

}
else
{
HelpInfo(TRUE);

error = static_cast<CommonErrorSpace::CommonError>(ERR_TIMEOUT);
cout << ErrorHelper::getErrorName(error) << endl;
cout << ErrorHelper::getErrorDescription(error) << endl;

return error;
}

//Get Param2
//
nTypePos = strCommand.find("port=");
nOffSet = 5;
if (nTypePos != string::npos)
{
size_t nCommaPos = strCommand.find_first_of(',', nTypePos);

if (nCommaPos != string::npos)
{
m_strDmPort = strBKCommand.substr(nTypePos+nOffSet, nCommaPos-nTypePos-nOffSet);
}
else
{
m_strDmPort = strBKCommand.substr(nTypePos+nOffSet);
}

}
else
{
HelpInfo(TRUE);
error = static_cast<CommonErrorSpace::CommonError>(DATAMANAGER_ERROR_MISSING_CONNECTION);
cout << ErrorHelper::getErrorName(error) << endl;
cout << ErrorHelper::getErrorDescription(error) << endl;

return error;
}

DataManager(); //All parameters are ok, try to call local API
ShowInfo();

cout << ErrorHelper::getErrorName(error) << endl;
cout << ErrorHelper::getErrorDescription(error) << endl;
return error;
}
}

VOID DataManagerHandler::ShowInfo()
{
cout << "\nSucceed!\n" << endl;

cout << "\nRSP==>\n";
cout << "DataManager = {" << endl;
cout << " Name = " << m_strDmName.c_str() << endl;
cout << " Port = " << m_strDmPort.c_str() << endl;
cout << "}" << endl;
}

VOID DataManagerHandler::HelpInfo(BOOL bDetail)
{
transform(m_strFuncName.begin(), m_strFuncName.end(), m_strFuncName.begin(), toupper);
if (!bDetail)
{
cout << m_strFuncName.c_str() << " Connect to Data server.";
}
else
{
cout << '\n' << m_strFuncName.c_str() << endl;
cout << " [Name=#NAME]" << endl;
cout << " [NAME] Data server name" << endl;
cout << " [Port=#PORT]" << endl;
cout << " [PORT] Data server port" << endl;
}
}

void DataManagerHandler::DataManager()
{
cout << "Do data manager......\n";
}


[align=center][size=medium][b] Function Dispatcher[/b][/size][/align]

/**************************************************************************************************/
/*!
\file FuncDispatcher.h

\attention
(c) Jacky Dai 2011~2016
All Rights Reserved.
*/

/**************************************************************************************************/
#ifndef FUNC_DISPATCHER_H
#define FUNC_DISPATCHER_H

#include <map>
#include "CommandHandler.h"
using namespace std;

typedef map<string, HandlerBase*> Dispatcher;

class FuncDispatcher
{
public:
FuncDispatcher();
~FuncDispatcher();

VOID Init();
VOID Clear();
VOID ShowHelpInfo(BOOL bDetailInfo);
HandlerBase* FindHandler(LPCSTR lpFuncName);

private:
Dispatcher m_Dispatcher;
};

#endif//FUNC_DISPATCHER_H


#include "FuncDispatcher.h"
#include "CommandHandler.h"
#include <iostream>
using namespace std;

FuncDispatcher::FuncDispatcher()
{
Init();
}

FuncDispatcher::~FuncDispatcher()
{
Clear();
}

VOID FuncDispatcher::Init()
{
m_Dispatcher.insert(make_pair("login", new LoginHandler()));
m_Dispatcher.insert(make_pair("datamanager", new DataManagerHandler()));
}

VOID FuncDispatcher::Clear()
{
Dispatcher::const_iterator iter = m_Dispatcher.begin();
Dispatcher::const_iterator end = m_Dispatcher.end();
for ( ; iter!=end; ++iter)
{
delete iter->second;
}

m_Dispatcher.clear();
}

VOID FuncDispatcher::ShowHelpInfo(BOOL bDetailInfo)
{
Dispatcher::const_iterator iter = m_Dispatcher.begin();
Dispatcher::const_iterator end = m_Dispatcher.end();
for ( ; iter!=end; ++iter)
{
iter->second->HelpInfo(bDetailInfo);
cout << endl;
}
}

HandlerBase* FuncDispatcher::FindHandler(LPCSTR lpFuncName)
{
if (NULL==lpFuncName)
{
return NULL;
}

string strTemp = lpFuncName;
Dispatcher::const_iterator item = m_Dispatcher.find(lpFuncName);
if (item == m_Dispatcher.end())
{
return NULL;
}
else
{
return item->second;
}

return NULL;
}


[align=center][size=medium][b] main函数[/b][/size][/align]

/*************************************************************************************************/
/*!
\file main.cpp

\ Author : Jacky Dai
\ Version : Ver 1.0.0.0
\ Date : 2016-07-11
\ Copyright : All Rights Reserved.
\ Description :
\Usage :
*/
/*************************************************************************************************/
#include "FuncDispatcher.h"
#include<algorithm>
#include <iostream>
using namespace std;

const string STR_HELP_API = "help";
const string STR_LOGIN_API = "login";
const string STR_DATA_MANAGER_API = "datamanager";


FuncDispatcher g_FuncDispatcher;

//
int main(int argc, char** argv)
{
if (1 == argc)
{
//There is no command, so do nothing.
cout << "Do nothing!" << endl;
return 0;
}
else
{
//TODO
//Load default parameters
}

for(int argIndex = 1; argIndex < argc; ++argIndex)
{
HandlerBase* pCommandHandler = NULL;
string strCommandName = argv[argIndex];
string strBKCommandName = argv[argIndex];
transform(strCommandName.begin(),strCommandName.end(),strCommandName.begin(), tolower);
if (STR_HELP_API == strCommandName)
{
if (2 == argc) //Show all help info
{
cout << '\n';
g_FuncDispatcher.ShowHelpInfo(FALSE);
}
else if (3 == argc) //Show single API help info
{
strCommandName = argv[argc-1];
strBKCommandName = argv[argc-1];
transform(strCommandName.begin(),strCommandName.end(),strCommandName.begin(), tolower);
pCommandHandler = g_FuncDispatcher.FindHandler(strCommandName.c_str());
if (pCommandHandler != NULL)
{
pCommandHandler->HelpInfo(TRUE);
}
else
{
cout << "\n\"" << strBKCommandName << "\"" << " is not a supported command!" << endl;
return -1;
}
}
else
{
cout << "\nHelp [CMD]" << endl;
cout << "[CMD] Show detail help information for the command." << endl;
return -1;
}

return 0;
}
else
{
pCommandHandler = g_FuncDispatcher.FindHandler(strCommandName.c_str());
if (NULL == pCommandHandler)
{
cout << "\n\"" << strBKCommandName << "\" is a invalid Command!" << endl;
return -1;
}

if (STR_LOGIN_API==strCommandName
|| STR_DATA_MANAGER_API==strCommandName)
{
if (argc >= 3)
{
argIndex++;
strCommandName = argv[argIndex];
strBKCommandName = argv[argIndex];
pCommandHandler->Parser(strCommandName.c_str());
break;
}
else
{
pCommandHandler->Parser(NULL); //Means the API does not contains parameters
break;
}
}
else if (pCommandHandler != NULL)
{
//other commands
pCommandHandler->ShowInfo();
break;
}
else
{
cout << "Unknown Exception!" << endl;
return -1;
}
}
}

return 0;
}


[align=center][size=medium][b]测试脚本[/b][/size][/align]

@ECHO OFF
ECHO -------------------Unit Test for "Help" Command-----------------------
TITLE "Help" Command Test.

REM /
ECHO -- Case 1:Call all help info
ECHO -- Expected Resut: Show all API help info
REM Show all API help info
REM ---------------------------------------------------------------------------------
ECHO COMMAND:ConsoleFrameworkDemo Help
ConsoleFrameworkDemo Help
IF "%1" == "" PAUSE


REM /
ECHO -- Case 2:Call help info of Login
ECHO -- Expected Resut: Show all Login help info
REM Show all login help info
REM ---------------------------------------------------------------------------------
ECHO COMMAND:ConsoleFrameworkDemo Help Login
ConsoleFrameworkDemo Help login
IF "%1" == "" PAUSE


REM /
ECHO -- Case 3:Call help info of DataManager
ECHO -- Expected Resut: Show all DataManager help info
REM Show all datamanager help info
REM ---------------------------------------------------------------------------------
ECHO COMMAND:ConsoleFrameworkDemo Help DataManager
ConsoleFrameworkDemo Help DataManager
IF "%1" == "" PAUSE


@ECHO OFF
ECHO -------------------Unit Test for "Login" Command-----------------------
TITLE "Login" Command Test.

REM /
ECHO -- Case 1:Does no pass any parameters for Login API
ECHO -- Expected Resut: Show Login usge info
REM Show Login usge info
REM ---------------------------------------------------------------------------------
ECHO COMMAND:ConsoleFrameworkDemo Login
ConsoleFrameworkDemo Login
IF "%1" == "" PAUSE


REM /
ECHO -- Case 2:pass one parameter for Login API
ECHO -- Expected Resut: Show Login usge info
REM Show Login usge info
REM ---------------------------------------------------------------------------------
ECHO COMMAND:ConsoleFrameworkDemo Login Name="Jacky"
ConsoleFrameworkDemo Login Name="Jacky"
IF "%1" == "" PAUSE

REM /
ECHO -- Case 3:pass two parameters for Login API
ECHO -- Expected Resut: Should handler it well
REM Should handler it well
REM ---------------------------------------------------------------------------------
ECHO COMMAND:ConsoleFrameworkDemo Login Name="Jacky",Password="123"
ConsoleFrameworkDemo Login Name="Jacky",Password="123"
IF "%1" == "" PAUSE


@ECHO OFF
ECHO -------------------Unit Test for "DataManager" Command-----------------------
TITLE "DataManager" Command Test.

REM /
ECHO -- Case 1:Does no pass any parameters for DataManager API
ECHO -- Expected Resut: Show DataManager usge info
REM Show DataManager usge info
REM ---------------------------------------------------------------------------------
ECHO COMMAND:ConsoleFrameworkDemo DataManager
ConsoleFrameworkDemo DataManager
IF "%1" == "" PAUSE


REM /
ECHO -- Case 2:pass one parameter for DataManager API
ECHO -- Expected Resut: Show DataManager usge info
REM Show DataManager usge info
REM ---------------------------------------------------------------------------------
ECHO COMMAND:ConsoleFrameworkDemo DataManager Name="Jacky"
ConsoleFrameworkDemo DataManager Name="Jacky"
IF "%1" == "" PAUSE

REM /
ECHO -- Case 3:pass two parameters for DataManager API
ECHO -- Expected Resut: Should handler it well
REM Should handler it well
REM ---------------------------------------------------------------------------------
ECHO COMMAND:ConsoleFrameworkDemo DataManager Name="Jacky",Port="123"
ConsoleFrameworkDemo DataManager Name="Jacky",Port="123"
IF "%1" == "" PAUSE
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值