完整网站展示
从文件版出发,完善其功能。

创建用户
首先我们需要创建一个用户用来连接数据库:
sql
mysql> create user 'oj_client'@'%' identified by '123456';
Query OK, 0 rows affected (0.00 sec)
然后创建数据库:
sql
mysql> create database oj;
Query OK, 1 row affected (0.01 sec)
最后授权给新用户:
sql
mysql> grant all on oj.* to 'oj_client'@'%';
Query OK, 0 rows affected (0.00 sec)
我们试着用新用户登录,看看是否能看到oj数据库:

看来是没有问题了。
表结构设计
我们利用workbench来完成表结构的设计。
随后根据之前的描述文件,完成对表结构的设计:


据此开始设计:

录入部分题目
为了方便测试,我们先录入部分题目

执行select * from oj_questions;后,点击Form Editor即可开始插入数据。
我们将第一第二题插入:

选中Apply,即可插入。
我们可以从数据库看看是否成功插入:
sql
mysql> select * from oj_questions\G
*************************** 1. row ***************************
number: 1
title: 判断回文数
star: 简单
desc: 给你一个整数 x ,如果 x 是一个回文整数,返回 true ;否则,返回 false 。
回文数是指正序(从左向右)和倒序(从右向左)读都是一样的整数。
例如,121 是回文,而 123 不是。
示例 1:
输入:x = 121
输出:true
示例 2:
输入:x = -121
输出:false
解释:从左向右读, 为 -121 。 从右向左读, 为 121- 。因此它不是一个回文数。
示例 3:
输入:x = 10
输出:false
解释:从右向左读, 为 01 。因此它不是一个回文数。
进阶:你能不将整数转为字符串来解决这个问题吗?
header: #include <iostream>
#include <string>
#include <vector>
#include <map>
#include <algorithm>
using namespace std;
class Solution
{
public:
bool isPalindrome(int x)
{
return true;
}
};
tail: #ifndef COMPILER_ONLINE
#include "header.cpp"
#endif
void Test1()
{
//匿名对象调用方法
bool ret=Solution().isPalindrome(121);
if(ret)
{
std::cout<<"通过用例1"<<std::endl;
}
else
{
std::cout<<"未通过用例1:"<<121<<std::endl;
}
}
void Test2()
{
bool ret=Solution().isPalindrome(-19);
if(!ret)
{
std::cout<<"通过用例2"<<std::endl;
}
else
{
std::cout<<"未通过用例2:"<<-19<<std::endl;
}
}
int main()
{
Test1();
Test2();
return 0;
}
cpu_limit: 1
mem_limit: 30000
1 row in set (0.00 sec)
没有问题!
继续把第二题页插入:
sql
mysql> select * from oj_questions where number=2\G
*************************** 1. row ***************************
number: 2
title: 求最大值
star: 简单
desc: 求最大值,比如:vector v ={1,2,3,4,5,6,12,3,4,-1};
求最大值, 比如:输出 12
header: #include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
class Solution
{
public:
int Max(const vector<int> &v)
{
//将你的代码写在下面
return 0;
}
};
tail: #ifndef COMPILER_ONLINE
#include "header.cpp"
#endif
void Test1()
{
vector<int> v = {1, 2, 3, 4, 5, 6};
int max = Solution().Max(v);
if (max == 6)
{
std::cout << "Test 1 .... OK" << std::endl;
}
else
{
std::cout << "Test 1 .... Failed" << std::endl;
}
}
void Test2()
{
vector<int> v = {-1, -2, -3, -4, -5, -6};
int max = Solution().Max(v);
if (max == -1)
{
std::cout << "Test 2 .... OK" << std::endl;
}
else
{
std::cout << "Test 2 .... Failed" << std::endl;
}
}
int main()
{
Test1();
Test2();
return 0;
}
cpu_limit: 1
mem_limit: 30000
1 row in set (0.00 sec)
oj_model数据库版设计
由于我们之前是基于MVC结构设计的:
MVC 是一种软件架构模式,核心是把系统分成 3 个部分:
Model(模型):负责数据和业务逻辑(比如代码的编译 / 运行、用户数据存储);
View(视图):负责展示界面(比如用户提交代码的网页、结果展示页面);
Controller(控制器):负责接收用户请求,调用 Model 处理,再把结果传给 View 展示。
那么只需要更改model板块即可。
并且我们保持model接口参数不变,修改其内部实现就可以完成从文件到数据库的转变。这就是解耦的好处啊。
我们先看看原本的Model有什么成员函数:
cpp
class Model
{
private:
bool LoadAllQuestion(const std::string &question_list);
public:
Model();
bool GetAllQuestion(std::vector<Question> *out);
bool GetOneQuestion(const std::string &number, Question *out);
~Model() {};
private:
std::unordered_map<std::string, Question> _questions;
};
显然我们不再需要加载全部题目,因为题目就在数据库中,只需连接数据库。
所以我们能将载入题目、构造函数、析构函数都去掉。
最终结构:
cpp
const std::string oj_questions = "oj_questions";
class Model
{
public:
bool QueryMysql(const std::string &sql, std::vector<Question> *out)
{
}
bool GetAllQuestion(std::vector<Question> *out)
{
std::string sql = "select * from ";
sql += oj_questions;
return QueryMysql(sql, out);
}
bool GetOneQuestion(const std::string &number, Question *out)
{
bool res = false;
std::string sql = "select * from ";
sql += oj_questions;
sql += " where number=";
sql += number;
std::vector<Question> result;
if (QueryMysql(sql, &result))
{
if (result.size() == 1)
{
*out = result[0];
res = true;
}
}
return res;
}
};
QueryMysql实现
要实现QueryMysql必须有Mysql的连接库,可以参考我的MySQL用C/C++连接进行下载,然后学习基本接口使用。
记得include头文件:

接下来获取数据的时候要对照建表的顺序获取:
cpp
const std::string oj_questions = "oj_questions";
const std::string host = "127.0.0.1";
const std::string user = "oj_client";
const std::string password = "123456";
const std::string db = "oj";
const int port = 3306;
bool QueryMysql(const std::string &sql, std::vector<Question> *out)
{
// 初始化句柄
MYSQL *mysql = mysql_init(nullptr);
// 连接数据库
if (mysql_real_connect(mysql, host.c_str(), user.c_str(), password.c_str(), db.c_str(), port, nullptr, 0) == nullptr)
{
LOG(FATAL) << "连接数据库失败\n";
return false;
}
// 设置编码格式,确保无误
mysql_set_character_set(mysql, "utf8mb4");
LOG(INFO) << "连接数据库成功!\n";
// 执行sql语句
if (mysql_query(mysql, sql.c_str()) != 0)
{
LOG(WARNING) << sql << " execute error!\n";
return false;
}
// 提取结果
MYSQL_RES *res = mysql_store_result(mysql);
int rows = mysql_num_rows(res);
Question q;
for (int i = 0; i < rows; i++)
{
MYSQL_ROW line = mysql_fetch_row(res);
q.number = line[0];
q.name = line[1];
q.star = line[2];
q.desc = line[3];
q.header = line[4];
q.tail = line[5];
q.cpu_limit = atoi(line[6]);
q.mem_limit = atoi(line[7]);
out->push_back(q);
}
// 释放结果
mysql_free_result(res);
// 关闭连接
mysql_close(mysql);
return true;
}
注意我的数据库默认配置字符集是utf8mb4,因此设置字符集为utf8mb4,如果是utf8,应当设置为utf8mb3.
最后将之前包含的文件版头文件,改为数据库版头文件:

综合测试
编译后运行oj服务和编译服务:

测试网站功能:

没有乱码。

提交正常。
项目拓展方向
我们的项目自然可以像leetcode一样拓展许多功能,这里提供一些方向:
- 基于注册和登录的录题功能
- 业务拓展,如接入论坛
- 可以将compiler部署到docker上
- 目前的compiler服务是http请求,可以将其设计成远程过程调用,推荐使用rest_rpc
- 继续完善功能,如增加测试用例,增加题目
- 其他
补充顶层makefile发布项目
我们来编写一个makefile,能直接生成发布版本的文件夹:
bash
.PHONY: all
all:
@cd compile_server;\
make;\
cd -;\
cd oj_server;\
make;\
cd -;
.PHONY:output
output:
@mkdir -p output/compile_server;\
mkdir -p output/oj_server;\
cp -rf compile_server/compile_server output/compile_server;\
cp -rf compile_server/temp output/compile_server;\
cp -rf oj_server/conf output/oj_server/;\
cp -rf oj_server/questions output/oj_server/;\
cp -rf oj_server/template output/oj_server/;\
cp -rf oj_server/wwwroot output/oj_server/;\
cp -rf oj_server/oj_server output/oj_server/;
.PHONY:clean
clean:
@cd compile_server;\
make clean;\
cd -;\
cd oj_server;\
make clean;\
cd -;\
rm -rf output;
完整代码
最后将数据库也打包成sql文件,放到仓库中