目录1. 创建项目目录结构2. 创建数据库初始化脚本3. 创建配置管理模块3.1 yaml4. 测试框架 gtest4.1 认识“单元测试”4.2 安装 gtest4.3 生成单元测试5. 配置 .gitignore6. 日志封装7. 数据库连接池实现1. 创建项目目录结构SPEC.md 请按照文档中 Phase 1 描述先进行 项目目录结构搭建 操作。具体的目录结构请 按照 6 项目目录结构 的描述来构建。此处只是创建目录结构和空文件暂时不编写文件的具体 内容。此时opencode 就会按照SPEC.md 中描述的目录结构进行创建。通过mkdir 创建一系列的目录通过touch 创建一系列的空文件当opencode 完成创建后还会自主使用ls 命令检查当前的目录结构是否正确。完成之后和之前一样记得提交一个git版本并推送。每完成一个Phase都可以提示AI修改SPEC.md把对应的任务标记成完成。2. 创建数据库初始化脚本SPEC.md 请按照文档中 Phase 1 中的描述进行完成 MySQL 数据库初始化脚本 工作。具体需要创建的表结构参考 ## 4. 数据库 Schema 章节。AI根据提示词先编写init.sql 脚本-- 创建数据库 CREATE DATABASE IF NOT EXISTS oj_system DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; USE oj_system; -- 用户表存储用户账号、密码哈希、角色 CREATE TABLE IF NOT EXISTS users ( id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, username VARCHAR(50) NOT NULL UNIQUE, password_hash VARCHAR(255) NOT NULL, role ENUM(user, admin) NOT NULL DEFAULT user, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, INDEX idx_username (username) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COLLATEutf8mb4_unicode_ci; -- 题目表存储题目基本信息、难度、资源限制 CREATE TABLE IF NOT EXISTS problems ( id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, title VARCHAR(200) NOT NULL, description TEXT NOT NULL, difficulty ENUM(easy, medium, hard) NOT NULL DEFAULT medium, time_limit_ms INT UNSIGNED NOT NULL DEFAULT 1000, memory_limit_mb INT UNSIGNED NOT NULL DEFAULT 256, created_by BIGINT UNSIGNED NOT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, INDEX idx_created_by (created_by), FOREIGN KEY (created_by) REFERENCES users(id) ON DELETE CASCADE ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COLLATEutf8mb4_unicode_ci; -- 测试用例表存储题目输入输出、是否示例 CREATE TABLE IF NOT EXISTS testcases ( id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, problem_id BIGINT UNSIGNED NOT NULL, input TEXT NOT NULL, expected_output TEXT NOT NULL, is_sample BOOLEAN NOT NULL DEFAULT FALSE, INDEX idx_problem_id (problem_id), FOREIGN KEY (problem_id) REFERENCES problems(id) ON DELETE CASCADE ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COLLATEutf8mb4_unicode_ci; -- 提交记录表存储代码提交、判题状态、执行资源消耗 CREATE TABLE IF NOT EXISTS submissions ( id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, user_id BIGINT UNSIGNED NOT NULL, problem_id BIGINT UNSIGNED NOT NULL, code TEXT NOT NULL, language VARCHAR(20) NOT NULL, status ENUM(pending, compiling, running, accepted, wrong_answer, time_limit_exceeded, memory_limit_exceeded, runtime_error, compile_error) NOT NULL DEFAULT pending, error_msg TEXT, execution_time_ms INT UNSIGNED, execution_memory_kb INT UNSIGNED, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, INDEX idx_user_id (user_id), INDEX idx_problem_id (problem_id), INDEX idx_status (status), FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE, FOREIGN KEY (problem_id) REFERENCES problems(id) ON DELETE CASCADE ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COLLATEutf8mb4_unicode_ci;注意此时仅仅是生成了数据库脚本并没有真正执行此时的数据库也是空的数据库。我们可以另外起一个终端登陆到数据库上检查。接下来可以直接让opencode 来执行这个数据库初始化脚本。这个过程中opencode 执行的多个操作均因为没有 root 权限而失败。我们可以通过 root 用户运行 opencode 权限或者把当前用户添加到 sudoers 中解决这个问题但是这样做并不合适。LLM 返回的结果可能是“有幻觉”的。万一返回了一些危险命令比如 rm -rf / 并直接执行了在 root 用户下造成的后果会非常严重。完成上述配置后我们可以让opencode 总结下数据库访问方式以备后续开发中使用。把当前数据库的访问方式(用户名密码) 总结写入到 SPEC.md 中## 3. 数据库配置 **连接方式** bash mysql -u wsj -p **数据库信息** - 用户名wsj - 密码空 - Socket/var/run/mysqld/mysqld.sock - 数据库名oj_system ---3. 创建配置管理模块SPEC.md 请开发 Phase 1 这里的“配置管理”功能。相关的代码编写在 src/utils/config.cc 中此时 opencode 就会按照要求完成下列工作在 config.h 和 config.cc 中编写代码进行实现。修改 CMakeLists.txt增加编译相关配置生成 config.yaml 作为配置模板。生成的代码核心是 ConfigManager 单例类以单例模式的方式进行配置的解析管理。# OJ 系统配置文件 # 数据库配置 database: host: localhost username: wsj password: name: oj_system socket: /var/run/mysqld/mysqld.sock # 服务器配置 server: host: 0.0.0.0 port: 8080 # 判题配置 judger: # 代码存放目录 work_dir: /tmp/oj-judger # 默认时间限制毫秒 default_time_limit_ms: 2000 # 默认内存限制MB default_memory_limit_mb: 256 # 日志配置 logger: # 日志级别: trace, debug, info, warn, error, critical level: info # 日志输出目录 dir: logs # 控制台输出 console: true3.1 yamlyaml 是当前一种流行的配置文件组织格式格式以“缩进”方式来组织处一个“树形结构”每一级缩进都相当于一个“子树”每个树的节点都是一个“键值对”形如下列形式server: host: 0.0.0.0 port: 8080 thread_pool_size: 4 database: host: localhost port: 3306 username: wsj password: database: oj_system max_connections: 104. 测试框架 gtest当然光靠肉眼来观察代码不足以验证代码的正确性。我们还可以通过“单元测试”的方式来对代码进行进一步验证。4.1 认识“单元测试”一个项目整体的“正确”意味着项目中的每个模块每个部分都是正确的。与其等待整个项目开发完再验证正确不如每开发一个模块都先验证下这个模块是否正确。这个针对模块验证的过程就是“单元测试”。这是在开发中一种常见的测试方式。在 C 中编写单元测试不必从头开始。google 提供了 google test简称 gtest框架可以让我们更方便的完成单元测试。4.2 安装 gtestsudo apt install libgtest-dev4.3 生成单元测试针对上述配置管理功能的开发基于 gtest 设计单元测试用例。相关代码存放在 tests/unit/ 目录中。此时opencode 会进行下列操作编写单元测试代码修改 CMakeLists.txt添加针对单元测试程序的编译选项。编译并修改编译报错。运行并修改失败的用例。AI虽然功能很强但是也不是无所不能。我们仍然要对AI生成的结果进行检查验证对于不符合要求的地方要能够给出及时的提示和修正。5. 配置 .gitignore这一次进行提交 git 版本时我们发现要提交的内容突然变很多。仔细观察发现这些文件都是 cmake 构建过程中生成的临时文件。这些临时文件原则上是不需要提交到 git 中。只要有 CMakeLists.txt 这一个配置文件都是随时可以生成出上述临时文件的。如何避免提交这些临时文件呢可以通过 git 内置的功能.gitignore 来实现。.gitignore 是一个纯文本文件。我们可以在项目的根目录中创建出这个文件文件的内容按行组织每一行描述了要忽略一类文件针对上述的临时文件来说他们都是处于“build”目录下的因此直接在 .gitignore 中编写build/此时这些临时文件就不会被加入到 git 仓库中了。使用 .gitignore 是开发中的一个良好习惯。确保只提交项目真正关键的代码/配置文件即可。6. 日志封装日志是一个系统中的重要环节是程序员定位问题的重要手段相比于“断点调试”来说日志的普适性是更强的断点一旦把程序“断住”其实影响到了程序原有的执行逻辑。在一个已经改变的逻辑上定位问题本身已经未必“靠谱”了相比之下日志对于程序的侵入性更小能更好的还原出现问题的线索。提示词SPEC.md 请按照“Phase 1”中“日志封装”这样的部分来进行开发。代码放到 backend/logger/logger.cpp 中。AI 按照提示词很快完成开发。封装好的日志会支持日志级别ERROR、WARNING、INFO、DEBUG 等。和前面类似同样可以针对日志生成测试用例进行验证。此时opencode 会按照要求设计测试用例编译调试测试用例通过。根据上述编写的代码基于 gtest 设计测试用例。测试用例代码放到 tests/unit/ 目录中。7. 数据库连接池实现opencode 会按照要求开始进行开发开发出来的连接池对 mysql 原生 API 进行了封装简化了使用提前创建好多个连接后续代码需要使用数据库连接时直接从池子中获取考虑到了线程安全在合理位置进行加锁SPEC.md 根据文档描述完成“Phase1”中的“数据库连接池实现”实现的代码放到 backend/repository/db_pool.cpp 中。开发完毕之后和前面一样让 opencode 编写单元测试。对上述的数据库连接池设计单元测试用例基于 gtest 实现。代码放在 tests/unit/ 中
【4. 搭建基础设施】:创建目录结构、数据库初始化脚本、配置管理模块、.gitignore,安装测试框架 gtest,实现日志封装、数据库连接池
目录1. 创建项目目录结构2. 创建数据库初始化脚本3. 创建配置管理模块3.1 yaml4. 测试框架 gtest4.1 认识“单元测试”4.2 安装 gtest4.3 生成单元测试5. 配置 .gitignore6. 日志封装7. 数据库连接池实现1. 创建项目目录结构SPEC.md 请按照文档中 Phase 1 描述先进行 项目目录结构搭建 操作。具体的目录结构请 按照 6 项目目录结构 的描述来构建。此处只是创建目录结构和空文件暂时不编写文件的具体 内容。此时opencode 就会按照SPEC.md 中描述的目录结构进行创建。通过mkdir 创建一系列的目录通过touch 创建一系列的空文件当opencode 完成创建后还会自主使用ls 命令检查当前的目录结构是否正确。完成之后和之前一样记得提交一个git版本并推送。每完成一个Phase都可以提示AI修改SPEC.md把对应的任务标记成完成。2. 创建数据库初始化脚本SPEC.md 请按照文档中 Phase 1 中的描述进行完成 MySQL 数据库初始化脚本 工作。具体需要创建的表结构参考 ## 4. 数据库 Schema 章节。AI根据提示词先编写init.sql 脚本-- 创建数据库 CREATE DATABASE IF NOT EXISTS oj_system DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; USE oj_system; -- 用户表存储用户账号、密码哈希、角色 CREATE TABLE IF NOT EXISTS users ( id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, username VARCHAR(50) NOT NULL UNIQUE, password_hash VARCHAR(255) NOT NULL, role ENUM(user, admin) NOT NULL DEFAULT user, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, INDEX idx_username (username) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COLLATEutf8mb4_unicode_ci; -- 题目表存储题目基本信息、难度、资源限制 CREATE TABLE IF NOT EXISTS problems ( id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, title VARCHAR(200) NOT NULL, description TEXT NOT NULL, difficulty ENUM(easy, medium, hard) NOT NULL DEFAULT medium, time_limit_ms INT UNSIGNED NOT NULL DEFAULT 1000, memory_limit_mb INT UNSIGNED NOT NULL DEFAULT 256, created_by BIGINT UNSIGNED NOT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, INDEX idx_created_by (created_by), FOREIGN KEY (created_by) REFERENCES users(id) ON DELETE CASCADE ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COLLATEutf8mb4_unicode_ci; -- 测试用例表存储题目输入输出、是否示例 CREATE TABLE IF NOT EXISTS testcases ( id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, problem_id BIGINT UNSIGNED NOT NULL, input TEXT NOT NULL, expected_output TEXT NOT NULL, is_sample BOOLEAN NOT NULL DEFAULT FALSE, INDEX idx_problem_id (problem_id), FOREIGN KEY (problem_id) REFERENCES problems(id) ON DELETE CASCADE ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COLLATEutf8mb4_unicode_ci; -- 提交记录表存储代码提交、判题状态、执行资源消耗 CREATE TABLE IF NOT EXISTS submissions ( id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, user_id BIGINT UNSIGNED NOT NULL, problem_id BIGINT UNSIGNED NOT NULL, code TEXT NOT NULL, language VARCHAR(20) NOT NULL, status ENUM(pending, compiling, running, accepted, wrong_answer, time_limit_exceeded, memory_limit_exceeded, runtime_error, compile_error) NOT NULL DEFAULT pending, error_msg TEXT, execution_time_ms INT UNSIGNED, execution_memory_kb INT UNSIGNED, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, INDEX idx_user_id (user_id), INDEX idx_problem_id (problem_id), INDEX idx_status (status), FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE, FOREIGN KEY (problem_id) REFERENCES problems(id) ON DELETE CASCADE ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COLLATEutf8mb4_unicode_ci;注意此时仅仅是生成了数据库脚本并没有真正执行此时的数据库也是空的数据库。我们可以另外起一个终端登陆到数据库上检查。接下来可以直接让opencode 来执行这个数据库初始化脚本。这个过程中opencode 执行的多个操作均因为没有 root 权限而失败。我们可以通过 root 用户运行 opencode 权限或者把当前用户添加到 sudoers 中解决这个问题但是这样做并不合适。LLM 返回的结果可能是“有幻觉”的。万一返回了一些危险命令比如 rm -rf / 并直接执行了在 root 用户下造成的后果会非常严重。完成上述配置后我们可以让opencode 总结下数据库访问方式以备后续开发中使用。把当前数据库的访问方式(用户名密码) 总结写入到 SPEC.md 中## 3. 数据库配置 **连接方式** bash mysql -u wsj -p **数据库信息** - 用户名wsj - 密码空 - Socket/var/run/mysqld/mysqld.sock - 数据库名oj_system ---3. 创建配置管理模块SPEC.md 请开发 Phase 1 这里的“配置管理”功能。相关的代码编写在 src/utils/config.cc 中此时 opencode 就会按照要求完成下列工作在 config.h 和 config.cc 中编写代码进行实现。修改 CMakeLists.txt增加编译相关配置生成 config.yaml 作为配置模板。生成的代码核心是 ConfigManager 单例类以单例模式的方式进行配置的解析管理。# OJ 系统配置文件 # 数据库配置 database: host: localhost username: wsj password: name: oj_system socket: /var/run/mysqld/mysqld.sock # 服务器配置 server: host: 0.0.0.0 port: 8080 # 判题配置 judger: # 代码存放目录 work_dir: /tmp/oj-judger # 默认时间限制毫秒 default_time_limit_ms: 2000 # 默认内存限制MB default_memory_limit_mb: 256 # 日志配置 logger: # 日志级别: trace, debug, info, warn, error, critical level: info # 日志输出目录 dir: logs # 控制台输出 console: true3.1 yamlyaml 是当前一种流行的配置文件组织格式格式以“缩进”方式来组织处一个“树形结构”每一级缩进都相当于一个“子树”每个树的节点都是一个“键值对”形如下列形式server: host: 0.0.0.0 port: 8080 thread_pool_size: 4 database: host: localhost port: 3306 username: wsj password: database: oj_system max_connections: 104. 测试框架 gtest当然光靠肉眼来观察代码不足以验证代码的正确性。我们还可以通过“单元测试”的方式来对代码进行进一步验证。4.1 认识“单元测试”一个项目整体的“正确”意味着项目中的每个模块每个部分都是正确的。与其等待整个项目开发完再验证正确不如每开发一个模块都先验证下这个模块是否正确。这个针对模块验证的过程就是“单元测试”。这是在开发中一种常见的测试方式。在 C 中编写单元测试不必从头开始。google 提供了 google test简称 gtest框架可以让我们更方便的完成单元测试。4.2 安装 gtestsudo apt install libgtest-dev4.3 生成单元测试针对上述配置管理功能的开发基于 gtest 设计单元测试用例。相关代码存放在 tests/unit/ 目录中。此时opencode 会进行下列操作编写单元测试代码修改 CMakeLists.txt添加针对单元测试程序的编译选项。编译并修改编译报错。运行并修改失败的用例。AI虽然功能很强但是也不是无所不能。我们仍然要对AI生成的结果进行检查验证对于不符合要求的地方要能够给出及时的提示和修正。5. 配置 .gitignore这一次进行提交 git 版本时我们发现要提交的内容突然变很多。仔细观察发现这些文件都是 cmake 构建过程中生成的临时文件。这些临时文件原则上是不需要提交到 git 中。只要有 CMakeLists.txt 这一个配置文件都是随时可以生成出上述临时文件的。如何避免提交这些临时文件呢可以通过 git 内置的功能.gitignore 来实现。.gitignore 是一个纯文本文件。我们可以在项目的根目录中创建出这个文件文件的内容按行组织每一行描述了要忽略一类文件针对上述的临时文件来说他们都是处于“build”目录下的因此直接在 .gitignore 中编写build/此时这些临时文件就不会被加入到 git 仓库中了。使用 .gitignore 是开发中的一个良好习惯。确保只提交项目真正关键的代码/配置文件即可。6. 日志封装日志是一个系统中的重要环节是程序员定位问题的重要手段相比于“断点调试”来说日志的普适性是更强的断点一旦把程序“断住”其实影响到了程序原有的执行逻辑。在一个已经改变的逻辑上定位问题本身已经未必“靠谱”了相比之下日志对于程序的侵入性更小能更好的还原出现问题的线索。提示词SPEC.md 请按照“Phase 1”中“日志封装”这样的部分来进行开发。代码放到 backend/logger/logger.cpp 中。AI 按照提示词很快完成开发。封装好的日志会支持日志级别ERROR、WARNING、INFO、DEBUG 等。和前面类似同样可以针对日志生成测试用例进行验证。此时opencode 会按照要求设计测试用例编译调试测试用例通过。根据上述编写的代码基于 gtest 设计测试用例。测试用例代码放到 tests/unit/ 目录中。7. 数据库连接池实现opencode 会按照要求开始进行开发开发出来的连接池对 mysql 原生 API 进行了封装简化了使用提前创建好多个连接后续代码需要使用数据库连接时直接从池子中获取考虑到了线程安全在合理位置进行加锁SPEC.md 根据文档描述完成“Phase1”中的“数据库连接池实现”实现的代码放到 backend/repository/db_pool.cpp 中。开发完毕之后和前面一样让 opencode 编写单元测试。对上述的数据库连接池设计单元测试用例基于 gtest 实现。代码放在 tests/unit/ 中