1.所用技术与开发环境
所用技术
:
C++ STL
标准库
Boost
准标准库
(
字符串切割
)
cpp
-
httplib
第三方开源网络库
ctemplate
第三方开源前端网页渲染库
jsoncpp
第三方开源序列化、反序列化库
负载均衡设计
多进程、多线程
MySQL C connect
Ace
前端在线编辑器
html/css/js/jquery/ajax
2.开发环境
Centos 7
云服务器
vscode
Mysql Workbench
3. 项目宏观结构
我们的项目核心是三个模块
1.
comm
:
公共模块
2.
compile_server
:
编译与运行模块
3.
oj_server
:
获取题目列表,查看题目编写题目界面,负载均衡,其他功能
I. leetcode 结构
只实现类似
leetcode
的题目列表
+
在线编程功能
II. 我们的项目宏观结构
III. 编写思路
1.
先编写
compile_server
2.
oj_server
3.
version1
基于文件版的在线
OJ
4.
前端的页面设计
5.
version2
基于
MySQL
版的在线
OJ
4. compiler 服务设计
提供的服务:编译并运行代码,得到格式化的相关的结果
第一个功能 compiler :编译功能
#pragma once
#include
#include
#include
#include
#include
#include
#include "../comm/util.hpp"
#include "../comm/log.hpp"
//只负责代码的编译
namespace ns_compiler
{
//引入路径拼接功能
using namespace ns_util;
using namespace ns_log;
class Compiler
{
public:
Compiler() = default;
~Compiler() = default;
//返回值:编译成功:True 编译失败:False
//输入参数:编译文件名
// file_name: 123
// 123 ->./temp/123.cpp
// 123 ->./temp/123.exe
// 123 ->./temp/123.stderr
static bool Compile(const std::string &file_name)
{
pid_t pid = fork();
if (pid < 0)
{
LOG(ERROR)<<"内部错误,创建子进程失败"<<"\n";
return false;
}
else if (pid == 0)
{
umask(0);
int _stderr = open(PathUtil::Error(file_name).c_str(), O_CREAT | O_WRONLY, 0644);
if (_stderr < 0)
{
LOG(WARNING)<<"没有形成stderr文件"<<"\n";
exit(1);
}
dup2(_stderr, 2); //将错误信息重定向到文件中
//子进程使用程序替换完成代码的编译功能
execlp("g++","g++", "-o", PathUtil::Exe(file_name).c_str(),PathUtil::Src(file_name).c_str(), "-std=c++11", nullptr /*不要忘记*/);
LOG(ERROR) << "启动编译器g++失败,可能是参数传入有误"<<"\n";
exit(2);
}
else
{
waitpid(pid, nullptr, 0);
//编译是否成功,就看有没有形成同名的可执行文件
if (FileUtil::IsFileExists(PathUtil::Exe(file_name)))
{
LOG(INFO)< return true; } } LOG(ERROR)<<"编译失败,没有形成可执行文件"<<"\n"; return false; } }; } Log 功能 #pragma once #include #include #include "util.hpp" namespace ns_log { //日志等级 enum { INFO, DEBUG, WARNING, ERROR, FATAL }; //LOG()<<"message" inline std::ostream &Log(const std::string &level,const std::string &file_name,const int line) { //添加日志等级 std::string message = "["; message += level; message += "]"; //添加报错文件名称 message += "["; message += file_name; message += "]"; //添加报错行 message += "["; message += std::to_string(line); message += "]"; //日志时间戳 message += "["; message += ns_util::TimeUtil::GetTimeStamp(); message += "]"; std::cout << message; return std::cout; } //开放日志 #define LOG(level) Log(#level,__FILE__,__LINE__) } 第二个功能 runner :运行功能 #pragma once #include #include #include #include #include #include #include #include #include #include "../comm/util.hpp" #include "../comm/log.hpp" namespace ns_runner { class Runner { public: Runner() = default; ~Runner() = default; public: static void SetProcLimit(int cpu_limit,int mem_limit) { //设置CPU时长 struct rlimit _cpu_limit; _cpu_limit.rlim_max = RLIM_INFINITY; _cpu_limit.rlim_cur = cpu_limit; setrlimit(RLIMIT_CPU,&_cpu_limit); //设置内存大小 struct rlimit _mem_limit; _mem_limit.rlim_max = RLIM_INFINITY; _mem_limit.rlim_cur = mem_limit*1024; //转化成KB setrlimit(RLIMIT_AS,&_mem_limit); } //返回值 > 0 ,程序异常,收到信号,返回值就是信号编号 //返回值 == 0 ,正常运行完毕,结果保存到对应的临时文件 //返回值 < 0 ,内部错误 //cpu_limit:该程序运行的时候,可以使用的最大CPU资源上限 //mem_limit:该程序运行的时候,可以使用最大内存 static int Run(const std::string &file_name,int cpu_limit,int mem_limit) { //只考虑是否正确运行,不考虑结果是否正确 /* 一个程序在默认启动的时候 标准输入:不处理 标准输出:结果 标准错误:运行时错误信息 */ std::string _exectue = ns_util::PathUtil::Exe(file_name); std::string _stdin = ns_util::PathUtil::Stdin(file_name); std::string _stdout = ns_util::PathUtil::Stdout(file_name); std::string _stderr = ns_util::PathUtil::Stderr(file_name); //打开临时文件 umask(0); int _stdin_fd = open(_stdin.c_str(),O_CREAT|O_RDONLY,0644); int _stdout_fd = open(_stdout.c_str(),O_CREAT|O_WRONLY,0644); int _stderr_fd = open(_stderr.c_str(),O_CREAT|O_WRONLY,0644); if(_stdin_fd < 0 || _stdout_fd < 0 || _stderr_fd < 0) { ns_log::LOG(ERROR)<<"运行时打开文件失败"<<"\n"; return -1; //文件打开失败 } pid_t pid = fork(); if (pid < 0) { ns_log::LOG(ERROR)<<"运行时创建子进程失败"<<"\n"; close(_stdin_fd); close(_stdout_fd); close(_stderr_fd); } else if (pid == 0) { dup2(_stdin_fd,0); dup2(_stdout_fd,1); dup2(_stderr_fd,2); SetProcLimit(cpu_limit,mem_limit); execl(_exectue.c_str(),_exectue.c_str(),nullptr); exit(1); } else { // std::cout<<"关闭文件描述符"< close(_stdin_fd); close(_stdout_fd); close(_stderr_fd); int status = 0; //进程异常收到信号 waitpid(pid, &status,0); ns_log::LOG(INFO)<<"运行完毕,info:"<< (status & 0x7F) << "\n"; return status & 0x7F; } return 0; } }; } 测试资源限制: #include #include #include #include #include void handler(int signo) { std::cout << "signo : " << signo << std::endl; exit(1); } int main() { //资源不足,导致OS终止进程,是通过信号终止的 for(int i =1; i <= 31; i++) { signal(i, handler); // struct rlimit r; // r.rlim_cur = 1; // r.rlim_max = RLIM_INFINITY; // setrlimit(RLIMIT_CPU, &r); //while(1); struct rlimit r; r.rlim_cur = 1024 * 1024 * 40; //20M r.rlim_max = RLIM_INFINITY; setrlimit(RLIMIT_AS, &r); int count = 0; while(true) { int *p = new int[1024*1024]; count++; std::cout << "size: " << count << std::endl; sleep(1); } return 0; } }// 限 // 内存申请失败 terminate called after throwing an instance of 'std::bad_alloc' what (): std::bad_alloc signo : 6 [ whb@bite - alicloud OnlineJudge ] $ kill - l 1 ) SIGHUP 2 ) SIGINT 3 ) SIGQUIT 4 ) SIGILL 5 ) SIGTRAP 6 ) SIGABRT 7 ) SIGBUS 8 ) SIGFPE 9 ) SIGKILL 10 ) SIGUSR1 11 ) SIGSEGV 12 ) SIGUSR2 13 ) SIGPIPE 14 ) SIGALRM 15 ) SIGTERM 16 ) SIGSTKFLT 17 ) SIGCHLD 18 ) SIGCONT 19 ) SIGSTOP 20 ) SIGTSTP //CPU 使用超时 [ whb@bite - alicloud OnlineJudge ] $ . / a . out signo : 24 [ whb@bite - alicloud OnlineJudge ] $ kill - l 1 ) SIGHUP 2 ) SIGINT 3 ) SIGQUIT 4 ) SIGILL 5 ) SIGTRAP 6 ) SIGABRT 7 ) SIGBUS 8 ) SIGFPE 9 ) SIGKILL 10 ) SIGUSR1 11 ) SIGSEGV 12 ) SIGUSR2 13 ) SIGPIPE 14 ) SIGALRM 15 ) SIGTERM 16 ) SIGSTKFLT 17 ) SIGCHLD 18 ) SIGCONT 19 ) SIGSTOP 20 ) SIGTSTP 21 ) SIGTTIN 22 ) SIGTTOU 23 ) SIGURG 24 ) SIGXCPU 25 ) SIGXFSZ 26 ) SIGVTALRM 27 ) SIGPROF 28 ) SIGWINCH 29 ) SIGIO 30 ) SIGPWR 第三个功能 compile_run :编译并运行功能 #pragma once #include #include #include #include #include "compiler.hpp" #include "complie_run.hpp" #include "../comm/log.hpp" #include "../comm/util.hpp" #include "runner.hpp" namespace ns_compile_and_run { class ComplieAndRun { public: static std::string CodeToDesc(int code, const std::string &file_name) { std::string desc; switch (code) { case 0: desc = "编译运行成功"; break; case -1: desc = "用户提交的代码为空"; break; case -2: desc = "未知错误"; break; case -3: // desc = "代码编译是出现错误"; ns_util::FileUtil::ReadFile(ns_util::PathUtil::Error(file_name), &desc, true); break; case SIGABRT: // 6 desc = "内存超过范围"; break; case SIGXCPU: // 24 desc = "CPU使用超时"; break; case SIGFPE: // 8 desc = "浮点数溢出"; //除0 break; default: desc = "未知错误" + std::to_string(code); break; } return desc; } public: static void RemoveTempFile(const std::string &file_name) { std::vector ns_util::PathUtil::Error(file_name), ns_util::PathUtil::Exe(file_name), ns_util::PathUtil::Stderr(file_name), ns_util::PathUtil::Stdin(file_name), ns_util::PathUtil::Stdout(file_name)}; for(const auto &e :AllTempFile) { if(ns_util::FileUtil::IsFileExists(e)); unlink(e.c_str()); } } /* 输入: code:用户给自己提交的代码 input:用户给自己的代码对应的输入,不作处理(后期可以扩展) cpu_limit:时间复杂度 mem_limit:时间复杂度 输出: status:状态码(必填) reason:请求结果(必填) stdout:程序运行结果(选填) stderr:程序运行完的错误结果(选填) */ //参数 // in_json:{"code":"";"input":"";"cpu_limit":"";"mem_limit":"";} // out_json:{"status":"0";"reason":"";"stdout":"";"stderr":""} static void Start(const std::string &in_json, std::string *out_json) { Json::Value in_vaule; Json::Reader reader; reader.parse(in_json, in_vaule); //最后在差错处理 std::string code = in_vaule["code"].asString(); std::string input = in_vaule["input"].asString(); int cpu_limit = in_vaule["cpu_limit"].asInt(); int mem_limit = in_vaule["mem_limit"].asInt(); Json::Value out_vaule; int status_code = 0; std::string file_name; int run_result = 0; if (code.size() == 0) { // //最后差错处理 // out_vaule["status"] = -1; //代码为空 // out_vaule["reason"] = "用户提交的代码是空的"; // //序列化 // return; status_code = -1; goto END; } //形成唯一文件名 毫秒级时间戳 + 原子性递增唯一值 file_name = ns_util::FileUtil::UniqFileName(); if (!ns_util::FileUtil::WiterFile(ns_util::PathUtil::Src(file_name), code)) //形成临时源src文件 { // out_vaule["status"] = -2; //未知错误 // out_vaule["reason"] = "提交的代码发生了未知错误"; // //序列化 // return; status_code = -2; goto END; } if (!ns_compiler::Compiler::Compile(file_name)) //编译失败 { // out_vaule["status"] = -3; // //编译失败的内容保存到了.error文件中,读取序列化 // out_vaule["reason"] = us_util::FileUtil::ReadFile(us_util::PathUtil::Error(file_name)); // //序列化 // return; status_code = -3; goto END; } run_result = ns_runner::Runner::Run(file_name, cpu_limit, mem_limit); //需要知道时间复杂度和空间复杂度 if (run_result < 0) { // out_vaule["status"] = -2; //未知错误 // out_vaule["reason"] = "发生了未知错误"; // //序列化 // return; status_code = -2; goto END; } else if (run_result > 0) { // out_vaule["status"] = -4; //运行时报错,收到信号 // out_vaule["reason"] = SignoToDesc(); //将信号转化成报错原因; // //序列化 // return; status_code = run_result; goto END; } else { // //运行成功 // out_vaule["status"] = 0; // out_vaule["reason"] = "运行成功"; status_code = 0; } END: out_vaule["status"] = status_code; out_vaule["reason"] = CodeToDesc(status_code,file_name); if (status_code == 0) { //全部成功 std::string _stdout; ns_util::FileUtil::ReadFile(ns_util::PathUtil::Stdout(file_name), &_stdout, true); out_vaule["stdout"] = _stdout; // std::cout<<"标准输出:"<<_stdout< std::string _stderr; ns_util::FileUtil::ReadFile(ns_util::PathUtil::Stderr(file_name), &_stderr, true); out_vaule["stderr"] = _stderr; } //序列化 Json::StyledWriter writer; *out_json = writer.write(out_vaule); RemoveTempFile(file_name); } }; } 5. 基于MVC 结构的oj 服务设计 本质:建立一个小型网站 1. 获取首页,用题目列表充当 2. 编辑区域页面 3. 提交判题功能 ( 编译并运行 ) M : Model , 通常是和数据交互的模块,比如,对题库进行增删改查(文件版, MySQL ) V : view , 通常是拿到数据之后,要进行构建网页,渲染网页内容,展示给用户的 ( 浏览器 ) C : control , 控制器,就是我们的核心业务逻辑 第一个功能:用户请求的服务路由功能 #include #include "../comm/httplib.h" #include "oj_control.hpp" using namespace httplib; int main() { //用户请求的的路由功能 Server svr; ns_control::Control ctrl; //获取所有题目列表 svr.Get("/all_questions",[&ctrl](const Request& req,Response &resq){ //返回一张带有所有题目的html网页 std::string html; ctrl.ALlQuestions(&html); resq.set_content(html,"text/html;charset=utf-8"); }); //获取要根据题目编号,获取题目的内容 svr.Get(R"(/question/(\d+))",[&ctrl](const Request& req,Response &resq){ std::string num = req.matches[1]; std::string html; ctrl.Question(num,&html); resq.set_content(html,"text/html;charset=utf-8"); }); //用户提交代码,使用我们的判题功能() svr.Post(R"(/judge/(\d+))",[&ctrl](const Request& req,Response &resq){ std::string number = req.matches[1]; std::string result_json; ctrl.Judge(number,req.body,&result_json); resq.set_content(result_json,"application/json;charset=utf-8"); // resq.set_content("指明题目的判题:"+num,"text/plain;charset=utf-8"); }); svr.set_base_dir("./wwwroot"); svr.listen("0.0.0.0",8080); return 0; } 第二个功能:model功能,提供对数据的操作 #pragma once //文件版本 #include "../comm/util.hpp" #include "../comm/log.hpp" #include #include #include #include #include #include #include // 根据题目list文件,加载所有的题目信息到内存中 // model: 主要用来和数据进行交互,对外提供访问数据的接口 namespace ns_model { using namespace std; using namespace ns_log; using namespace ns_util; struct Question { std::string number; //题目编号,唯一 std::string title; //题目的标题 std::string star; //难度: 简单 中等 困难 int cpu_limit; //题目的时间要求(S) int mem_limit; //题目的空间要去(KB) std::string desc; //题目的描述 std::string header; //题目预设给用户在线编辑器的代码 std::string tail; //题目的测试用例,需要和header拼接,形成完整代码 }; const std::string questins_list = "./questions/questions.list"; const std::string questins_path = "./questions/"; class Model { private: //题号 : 题目细节 unordered_map public: Model() { assert(LoadQuestionList(questins_list)); } bool LoadQuestionList(const string &question_list) { //加载配置文件: questions/questions.list + 题目编号文件 ifstream in(question_list); if (!in.is_open()) { LOG(FATAL) << " 加载题库失败,请检查是否存在题库文件"<< "\n"; return false; } string line; while (getline(in, line)) { vector StringUtil::SplitString(line, &tokens, " "); // 1 判断回文数 简单 1 30000 if (tokens.size() != 5) { LOG(WARNING) << "加载部分题目失败, 请检查文件格式" << "\n"; continue; } Question q; q.number = tokens[0]; q.title = tokens[1]; //std::cout< q.star = tokens[2]; q.cpu_limit = atoi(tokens[3].c_str()); q.mem_limit = atoi(tokens[4].c_str()); string path = questins_path; path += q.number; path += "/"; FileUtil::ReadFile(path + "desc.txt", &(q.desc), true); FileUtil::ReadFile(path + "header.cpp", &(q.header), true); FileUtil::ReadFile(path + "tail.cpp", &(q.tail), true); questions.insert({q.number, q}); } LOG(INFO) << "加载题库...成功!" << "\n"; in.close(); return true; } bool GetAllQuestions(vector { if (questions.size() == 0) { LOG(ERROR) << "用户获取题库失败"<< "\n"; return false; } for (const auto &q : questions) { out->push_back(q.second); // first: key, second: value } return true; } bool GetOneQuestion(const std::string &number, Question *q) { const auto &iter = questions.find(number); if (iter == questions.end()) { LOG(ERROR) << "用户获取题目失败, 题目编号: " << number << "\n"; return false; } (*q) = iter->second; return true; } ~Model() { } }; } // namespace ns_model 第三个功能:control,逻辑控制模块 #pragma once #include #include #include #include #include #include #include "oj_model.hpp" #include "../comm/log.hpp" #include "../comm/util.hpp" #include "oj_view.hpp" #include "../comm/httplib.h" namespace ns_control { const std::string service_machine = "./conf/service_machine.conf"; //提供服务的主机 class Machine { public: std::string ip; //编译服务的ip int port; //编译服务的端口 uint64_t load; //编译服务的负载 std::mutex *mtx; //mutex禁止拷贝的,使用指针来完成 public: Machine() :ip("") ,port(0) ,load(0) ,mtx(nullptr) { } ~Machine() { } public: void IncLoad() //提升负载 { if(mtx) mtx->lock(); ++load; if(mtx) mtx->unlock(); } void DecLoad() //减少负载 { if(mtx) mtx->lock(); --load; if(mtx) mtx->unlock(); } uint64_t Load() //获取负载 { uint64_t _load = 0; if(mtx) mtx->lock(); _load = load; if(mtx) mtx->unlock(); return _load; } }; //负载均衡模块 class LoadBlance { private: std::vector std::vector std::vector std::mutex mtx; //保证LoadBlance数据安全 public: LoadBlance() { assert(LoadConf(service_machine)); ns_log::LOG(ns_log::INFO)<<" 加载 "< } ~LoadBlance() { } public: bool LoadConf(const std::string &machine_conf) { std::ifstream in(machine_conf); if(!in.is_open()) { ns_log::LOG(ns_log::FATAL)<<"加载配置:"< return false; } std::string line; while(getline(in,line)) { std::vector ns_util::StringUtil::SplitString(line,&tokens,":"); if(tokens.size() != 2) { ns_log::LOG(ns_log::WARNING) <<" 切分 "< continue; } Machine m; m.ip = tokens[0]; m.port = atoi(tokens[1].c_str()); m.load = 0; m.mtx = new std::mutex(); online.push_back(machines.size()); machines.push_back(m); } in.close(); return true; } //id:输出型参数 //m :输出型参数 bool SmartChoice(int* id,Machine **m) { //1.使用选择好的主机(更新该主机的负载) //2.我们需要可能离线该主机 mtx.lock(); //负载均衡的算法 //1.随机数 + hash //2.轮询 + hash int online_num = online.size(); if(online_num == 0) { mtx.unlock(); ns_log::LOG(ns_log::FATAL) << "后端编译服务全部挂掉了,请运维的老铁尽快查看"<<"\n"; return false; } //通过编译找到负载最小的机器 *id = online[0]; *m = &machines[online[0]]; uint64_t min_load = machines[online[0]].Load(); for(int i = 0; i < online_num; ++i) { min_load = min_load < machines[online[i]].Load() ? machines[online[i]].Load() : min_load; *id = online[i]; *m = &machines[online[i]]; } mtx.unlock(); return true; } void OfflineMachine(int which) { mtx.lock(); for(auto iter = online.begin(); iter != online.end(); ++iter) { if(*iter == which) { online.erase(iter); offline.push_back(which); break; } } mtx.unlock(); } void OnlineMachine() { } void ShowMachines() { mtx.lock(); std::cout<<"当前在线主机列表:"; for(auto &id : online) { std::cout << id <<" "; } std::cout< for(auto &id : offline) { std::cout<<"当前离线主机列表:"; std::cout << id << " "; } std::cout< mtx.unlock(); } }; class Control { private: ns_model::Model _model; //提供后台服务 ns_view::View _view; //提供html渲染功能 LoadBlance _load_blance; //提供负载均衡器 public: Control() { } //根据题目数据构建网页 bool ALlQuestions(std::string *html) { std::vector if(_model.GetAllQuestions(&all)) { //获取题目信息成功,将所有的题目数据构建成网页 _view.AllExpandHtml(all,html); } else { *html = "获取题目失败,形成题目列表失败"; return false; } return true; } bool Question(const std::string number,std::string *html) { ns_model::Question q; if(_model.GetOneQuestion(number,&q)) { //获取指定题目成功,将题目数据构建成网页 _view.OneExpandHtml(q,html); } else { *html = "指定题目" + number + "不存在"; return false; } return true; } void Judge(const std::string& number, const std::string in_json,std::string *out_json) { //0.根据题号,直接拿到题目细节 ns_model::Question q; _model.GetOneQuestion(number,&q); //1.in_json进行反序列化,得到题目的id,得到用户提交的源代码,input Json::Reader reader; Json::Value in_value; reader.parse(in_json,in_value); //2.重新拼接用户代码 + 测试用例代码,形成新代码 std::string code = in_value["code"].asString(); Json::Value compile_value; compile_value["input"] = in_value["input"].asString(); compile_value["code"] = code + q.tail; compile_value["cpu_limit"] = q.cpu_limit; compile_value["mem_limit"] = q.mem_limit; Json::FastWriter writer; std::string complie_string = writer.write(compile_value); //3.选择负载最低的主机(差错处理) for( ; ;) { int id = 0; Machine *m = nullptr; if(!_load_blance.SmartChoice(&id,&m)) { break; } ns_log::LOG(ns_log::INFO) <<"选择主机成功"< //4.然后发起http请求,得到结果 httplib::Client cli(m->ip,m->port); m->IncLoad(); if(auto res = cli.Post("/compile_and_run",complie_string, "application/json;charset=utf-8")) { //5.将结果赋值给out_json if(res->status == 200) { *out_json = res->body; m->DecLoad(); break; } m->DecLoad(); } else { //请求失败 ns_log::LOG(ns_log::ERROR)<<"详情:" << id <<":"<< m->ip << ":"< _load_blance.OfflineMachine(id); _load_blance.ShowMachines(); } } } ~Control() { } }; } 附加功能:需要有数据渲染 // 如果后续引入了 ctemplate ,一旦对网页结构进行修改,尽量的每次想看到结果,将 server 重启一下。 ctemplate 有 自己的优化加速策略,可能在内存中存在缓存网页数据(old) 当我们完成全部功能之后,需要注意: 要给编译模块添加—D条件编译掉测试用例中的头文件incldue 6. version1 文件版题目设计 1. 题目的编号 2. 题目的标题 3. 题目的难度 4. 题目的描述,题面 5. 时间要求 ( 内部处理 ) 6. 空间要求 ( 内部处理 ) 两批文件构成 1. 第一个: questions . list : 题目列表(不需要题目的内容) 2. 第二个:题目的描述,题目的预设置代码 ( header . cpp ), 测试用例代码 ( tail . cpp ) 这两个内容是通过题目的编号,产生关联的 1. 当用户提交自己的代码的时候: header . cpp #include #include #include #include #include using namespace std; class Solution{ public: bool isPalindrome(int x) { //将你的代码写在下面 return true; } }; 该题号对应的测试用例 : tail . cpp #ifndef COMPILER_ONLINE #include "header.cpp" #endif void Test1() { // 通过定义临时对象,来完成方法的调用 bool ret = Solution().isPalindrome(121); if(ret){ std::cout << "通过用例1, 测试121通过 ... OK!" << std::endl; } else{ std::cout << "没有通过用例1, 测试的值是: 121" << std::endl; } } void Test2() { // 通过定义临时对象,来完成方法的调用 bool ret = Solution().isPalindrome(-10); if(!ret){ std::cout << "通过用例2, 测试-10通过 ... OK!" << std::endl; } else{ std::cout << "没有通过用例2, 测试的值是: -10" << std::endl; } } int main() { Test1(); Test2(); return 0; } 后端全部写完使用Postman 来测试 7. 前端页面设计 8. version2 MySQL版题目设计 9.综合测试 10.项目扩展思路 精彩文章
发表评论