【C++项目】从零实现一个在线编译器

在这里插入图片描述

前言

身为一名程序员,想必大家都有接触过像leetcode这样的刷题网站,不知你们在刷题的过程中是否思考过一个问题:它们是如何实现在线编译运行的功能。如果你对此感到好奇,那么本文将一步步带你来实现一个简易在线编译器。

项目概述

项目的基本逻辑:前端用户在网页上输入代码与参数,后端通过多进程的方式来编译运行代码,然后将标准输出、标准错误信息返回给前端页面。

前后端交互数据格式

// 前端发送
{"code": "代码","cpu_limit": "CPU限制","mem_limit": "内存限制"
}// 后端发送
{"reason": "错误原因","status": "状态码","stderr": "错误输出","stdout": "标准输出"
}

使用的第三方库

后端:

  • cpp-httplib:用于处理HTTP请求和响应。

  • jsoncpp:用于解析和生成JSON数据。

  • spdlog:用于日志记录。

前端:

  • jquery:简化JavaScript操作,方便进行DOM操作和Ajax请求。

  • ace:提供代码编辑器功能,支持语法高亮和代码自动完成。

运行效果

具体实现

后端逻辑

后端分为编译模块和运行模块,均使用多进程的方式来运行,并根据用户所选语言的语言来选择不同的编译器和运行方式。后端代码分为四部分:公共模块(工具类)、编译模块、运行模块、编译运行模块(整合编译与运行)。

公共模块

日记类

日记系统对spdlog进行了最低程度的封装,实现了单例日记系统,并定义宏来简化其使用。

//
// Created by lang liu on 24-4-23.
//#pragma once#ifdef DEBUG
#define SPDLOG_ACTIVE_LEVEL SPDLOG_LEVEL_DEBUG
#endif#include <spdlog/spdlog.h>
#include <spdlog/sinks/stdout_color_sinks.h>namespace ns_log {//TODO 初始化日记 完善class Log {public:static Log &getInstance() {std::call_once(_flag, []() {_instance = new Log();});return *_instance;}auto getLogger()->std::shared_ptr<spdlog::logger>{return _logger;}private:Log() {_logger = spdlog::stdout_color_mt("nil");_logger->set_level(spdlog::level::debug);_logger->set_pattern("[%^%l%$] [%Y-%m-%d %H:%M:%S] [%t] [%s:%#] %v");}~Log() {spdlog::drop_all();}private:static std::once_flag _flag;static Log *_instance;std::shared_ptr<spdlog::logger> _logger;};std::once_flag Log::_flag;Log *Log::_instance = nullptr;#define LOG_DEBUG(...)    SPDLOG_LOGGER_DEBUG(Log::getInstance().getLogger(), __VA_ARGS__)
#define LOG_INFO(...)     SPDLOG_LOGGER_INFO(Log::getInstance().getLogger(), __VA_ARGS__)
#define LOG_WARN(...)     SPDLOG_LOGGER_WARN(Log::getInstance().getLogger(), __VA_ARGS__)
#define LOG_ERROR(...)    SPDLOG_LOGGER_ERROR(Log::getInstance().getLogger(), __VA_ARGS__)
#define LOG_CRITICAL(...) SPDLOG_LOGGER_CRITICAL(Log::getInstance().getLogger(), __VA_ARGS__)
}// #else
//[x] 无spdlog
// #include <iostream>
// #include <format>
// #include "util.hpp"// namespace ns_log {//     using namespace ns_util;//     enum {
//         INFO,
//         DEBUG,
//         WARN,
//         ERROR,
//         CRITICAL
//     };//     inline std::ostream &Log(const std::string &level, const std::string &str) {
//         std::string msg = std::format("[{}] [{}] [{}:{}] {}", level, TimeUtil::GetTimeStamp(), __FILE__, __LINE__,
//                                       str);
//         // auto ret = __FILE_NAME__;
//         return std::cout << msg;
//     }// #define LOG_INFO(...)     Log("INFO", __VA_ARGS__)
// #define LOG_DEBUG(...)    Log("DEBUG", __VA_ARGS__)
// #define LOG_WARN(...)     Log("WARN", __VA_ARGS__)
// #define LOG_ERROR(...)    Log("ERROR", __VA_ARGS__)
// #define LOG_CRITICAL(...) Log("CRITICAL", __VA_ARGS__)// }// #endif

工具类

工具类分为时间工具、文件工具、路径工具。

时间工具:时间工具类用于生成时间戳,辅助文件工具生成唯一的文件名(UUID)。

文件工具:用于实现读写、创建、删除文件。

路径工具:用于更改文件的后缀。

//
// Created by lang liu on 24-4-23.
//#ifndef OJ_UTIL_HPP
#define OJ_UTIL_HPP#include "log.hpp"
#include <sys/time.h>
#include <sys/stat.h>
#include <fstream>
#include <atomic>
#include <unordered_map>
#include <filesystem>
#include <vector>namespace ns_util
{using namespace ns_log;// 定义文件后缀名的映射表static inline std::unordered_map<std::string, std::string> suffixTable {{"c_cpp", ".cc"},{"csharp", ".cs"},{"python", ".py"},{"javascript", ".js"}};// 定义可执行文件后缀名的映射表static inline std::unordered_map<std::string, std::string> excuteTable {{"c_cpp", ".exe"},{"csharp", ".cs"},{"javascript", ".js"},{"python", ".py"}};// 时间工具类class TimeUtil{public:// 获取时间戳(秒)static std::string GetTimeStamp(){struct timeval _tv{};gettimeofday(&_tv, nullptr);    // 获取时间戳return std::to_string(_tv.tv_sec);}// 获取时间戳(毫秒),用于生成随机文件名static std::string GetTimeNs(){struct timeval _tv{};gettimeofday(&_tv, nullptr);return std::to_string(_tv.tv_sec * 1000 + _tv.tv_usec / 1000);}};// 文件路径和后缀名变量static std::string path;static std::string srcSuffix;static std::string excuteSuffix;static const std::string temp_path = "./template/";// 路径工具类class PathUtil{public:// 初始化模板路径和后缀名static void InitTemplate(const std::string& lang) {path = temp_path + lang + "/";srcSuffix = suffixTable.at(lang);excuteSuffix = excuteTable.at(lang);}// 添加后缀名到文件名static std::string AddSuffix(const std::string &file_name, const std::string& suffix){std::string path_name = path;path_name += file_name;path_name += suffix;return path_name;}// 获取源文件路径static std::string Src(const std::string& file_name){return AddSuffix(file_name, srcSuffix);}// 获取可执行文件路径static std::string Exe(const std::string& file_name){return AddSuffix(file_name, excuteSuffix);}// 获取编译错误文件路径static std::string CompilerError(const std::string& file_name){return AddSuffix(file_name, ".compile_error");}// 获取标准输入文件路径static std::string Stdin(const std::string& file_name){return AddSuffix(file_name, ".stdin");}// 获取标准输出文件路径static std::string Stdout(const std::string& file_name){return AddSuffix(file_name, ".stdout");}// 获取标准错误文件路径static std::string Stderr(const std::string& file_name){return AddSuffix(file_name, ".stderr");}};// 文件工具类class FileUtil{public:// 移除文件夹中的所有文件(递归)static void RemoveAllFile(const std::string& dir) {std::vector<std::filesystem::path> removeArray;try {for(auto& it : std::filesystem::directory_iterator(dir)) {if(std::filesystem::is_regular_file(it.path()))removeArray.push_back(it.path());else if (std::filesystem::is_directory(it.path()))RemoveAllFile(it.path().string());}for(auto& it : removeArray) {std::filesystem::remove(it);}}catch (const std::filesystem::filesystem_error& e) {LOG_ERROR("移除全部文件失败: {}", e.what());}}// 移除指定文件static void RemoveFile(const std::string& filename) {if(std::filesystem::exists(filename)) {std::filesystem::remove(filename);}}// 检查文件是否存在static bool IsFileExists(const std::string & path_name){struct stat st = {};if(stat(path_name.c_str(), &st) == 0)return true;return false;}// 生成全局唯一的文件名static std::string UniqFileName(){static std::atomic_uint id(0);++id;std::string ms = TimeUtil::GetTimeNs();std::string uniq_id = std::to_string(id);return ms + uniq_id;}// 将内容写入文件static bool WriteFile(const std::string & path_name, const std::string & content){std::ofstream out(path_name);if(!out.is_open()){LOG_ERROR("write file failed!");return false;}out.write(content.c_str(), (int)content.size());out.close();return true;}// 读取文件内容static bool ReadFile(const std::string& path_name, std::string* content){content->clear();std::ifstream in(path_name);if(!in.is_open()){LOG_ERROR("read file({}) failed!", path_name.c_str());return false;}in.seekg(0, std::ios::end);auto size = in.tellg(); // 获取文件大小in.seekg(0, std::ios::beg);content->resize(size);in.read(content->data(), size);return true;}};};#endif //OJ_UTIL_HPP

编译模块

编译模块因为其大部分代码都是相似的,所以这里将其具体执行逻辑分离开来,并使用工厂模式决定实例化不同的类。

编译模块

#pragma once#include <unistd.h>
#include <sys/fcntl.h>#include "log.hpp"
#include "util.hpp"namespace ns_compiler {using namespace ns_log;using namespace ns_util;// 编译器基类class Compiler {public:virtual ~Compiler() = default;// 编译函数,接受文件名作为参数int Compile(const std::string& file_name) {// 打开标准错误和标准输出文件int _stderr = open(PathUtil::Stderr(file_name).c_str(), O_CREAT | O_WRONLY | O_TRUNC, 0644);int _stdout = open(PathUtil::Stdout(file_name).c_str(), O_CREAT | O_WRONLY | O_TRUNC, 0644);// 检查文件是否成功打开if(_stderr < 0 || _stdout < 0) {LOG_ERROR("打开文件失败");return -1;}// 创建子进程进行编译pid_t pid = fork();if(pid < 0 ) {LOG_ERROR("线程创建失败: {}", strerror(errno));return -1;}else if(pid == 0) {// 子进程umask(0); // 重置文件掩码// 重定向标准错误和标准输出到文件dup2(_stderr, STDERR_FILENO);dup2(_stdout, STDOUT_FILENO);// 执行编译命令Execute(file_name);LOG_INFO("exec错误:{}", strerror(errno));return -1; // exec 出错}// 父进程等待子进程结束waitpid(pid, nullptr, 0);// 检查编译后的可执行文件是否存在if(!FileUtil::IsFileExists(PathUtil::Exe(file_name))) {LOG_INFO("文件编译失败");return -2;}return 0; // 编译成功}protected:// 执行具体编译命令的纯虚函数,需由子类实现virtual void Execute(const std::string& filename) = 0;};// C++ 编译器类,继承自 Compilerclass CppCompiler : public Compiler {protected:// 执行 g++ 编译命令void Execute(const std::string& filename) override {execlp("g++", "g++", "-o", PathUtil::Exe(filename).c_str(),PathUtil::Src(filename).c_str(), nullptr);}};// 编译器工厂类,用于创建编译器对象class CompilerFactory {public:// 根据语言创建相应的编译器对象static std::unique_ptr<Compiler> CreateCompiler(const std::string& lang) {if(lang == "c_cpp")return std::make_unique<CppCompiler>();// 如果需要支持其他语言,可以在这里添加相应的编译器创建逻辑elsereturn {};}};
}

运行模块

运行模块与编译模块的逻辑其实相差不大。

#pragma once#include "compiler.hpp"
#include <iostream>
#include <sys/resource.h>
#include <sys/wait.h>using namespace ns_log;
using namespace ns_util;// Runner 基类,负责运行已编译的程序
class Runner {
public:// 运行指定的程序文件,并限制其 CPU 和内存使用int Run(const std::string& filename, const int cpu_limit, const int mem_limit) {// 获取输入、输出和错误文件路径std::string _execute = PathUtil::Exe(filename);std::string _stdin = PathUtil::Stdin(filename);std::string _stdout = PathUtil::Stdout(filename);std::string _stderr = PathUtil::Stderr(filename);umask(0); // 重置文件掩码// 打开标准输入、输出和错误文件int stdin_fd = open(_stdin.c_str(), O_CREAT | O_WRONLY, 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);// 创建子进程以执行程序pid_t pid = fork();if(pid < 0) {LOG_ERROR("fork error");close(stdin_fd);close(stdout_fd);close(stderr_fd);return -1;}else if(pid == 0) {// 子进程dup2(stdin_fd, STDIN_FILENO); // 重定向标准输入到文件dup2(stdout_fd, STDOUT_FILENO); // 重定向标准输出到文件dup2(stderr_fd, STDERR_FILENO); // 重定向标准错误到文件signal(SIGXCPU, [](int sig) { // 处理超时信号std::cerr << "timeout" << std::endl;});SetProcLimit(mem_limit, cpu_limit); // 设置进程资源限制Excute(_execute); // 执行具体的程序LOG_ERROR("execute error");exit(-1); // exec 出错}// 父进程等待子进程完成int status;waitpid(pid, &status, 0);close(stderr_fd);close(stdout_fd);close(stdin_fd);return WEXITSTATUS(status); // 返回子进程的退出状态}virtual ~Runner() = default;protected:// 纯虚函数,由子类实现具体的执行逻辑virtual void Excute(const std::string& filename) = 0;// 设置进程的内存和 CPU 使用限制static void SetProcLimit(const int m_limit, const int c_limit) {rlimit memLimit{};memLimit.rlim_cur = m_limit * 1024; // 内存限制(KB 转换为字节)memLimit.rlim_max = RLIM_INFINITY; // 最大内存限制setrlimit(RLIMIT_AS, &memLimit);rlimit cpuLimit{};cpuLimit.rlim_cur = c_limit; // CPU 时间限制(秒)cpuLimit.rlim_max = RLIM_INFINITY; // 最大 CPU 时间限制setrlimit(RLIMIT_CPU, &cpuLimit);}
};// C++ 运行器类,继承自 Runner
class CppRunner : public Runner {
protected:// 使用 execlp 执行编译后的 C++ 程序void Excute(const std::string& exec) override {execlp(exec.c_str(), exec.c_str(), nullptr);}
};// C# 运行器类,继承自 Runner
class CsharpRunner : public Runner {
protected:// 使用 execlp 执行 C# 程序void Excute(const std::string &filename) override {execlp("dotnet", "dotnet", "run",  filename.c_str(), "--project", path.c_str(), nullptr);}
};// JavaScript 运行器类,继承自 Runner
class JsRunner : public Runner {
protected:// 使用 execlp 执行 JavaScript 程序void Excute(const std::string &filename) override {execlp("node", "node", filename.c_str(), nullptr);}
};// Python 运行器类,继承自 Runner
class PyRunner : public Runner {
protected:// 使用 execlp 执行 Python 程序void Excute(const std::string &filename) override {execlp("python3", "python3", filename.c_str(), nullptr);}
};// 运行器工厂类,用于创建运行器对象
class RunnerFactory {
public:// 根据语言创建相应的运行器对象static std::unique_ptr<Runner> CreateRunner(const std::string& language) {if(language == "c_cpp")return std::make_unique<CppRunner>();else if (language == "csharp")return std::make_unique<CsharpRunner>();else if(language == "python")return std::make_unique<PyRunner>();elsereturn std::make_unique<JsRunner>();}
};

编译运行模块

编译运行模块是编译模块与运行模块的整合,用于解析前端发送的信息,并编译运行,最后将结果返回给前端网页。

#pragma once#include "runner.hpp"
#include "compiler.hpp"
#include "util.hpp"
#include <json/json.h>using namespace ns_log;
using namespace ns_util;
using namespace ns_compiler;// json 输入格式
// code: 代码
// language: 编译器
// cpu_limit: CPU 限制
// mem_limit: 内存限制// json 输出格式
// reason: 错误原因
// status: 状态码
// stderr: 错误输出
// stdout: 标准输出class CompileAndRun
{
private:// 将状态码转换为描述信息static std::string codeToDesc(int status) {static std::unordered_map<int, std::string> errTable = {{-1, "未知错误"},{-2, "编译错误"},{0, "运行成功"},// 更多状态码描述};return errTable[status];}public:// 编译并运行static void Start(std::string& in_json, std::string* out_json){// 转换 JSON 格式Json::Value in_value;Json::Value out_value;Json::Reader reader;if (!reader.parse(in_json, in_value)){LOG_ERROR("CompileAndRun::Start parse json error");return;}// 记录输入的 JSON 数据Json::StyledWriter styledWriter;LOG_DEBUG("in_value: {}", styledWriter.write(in_value));// 从 JSON 中提取参数std::string code = in_value["code"].asString();int cpu_limit = in_value["cpu_limit"].asInt();int mem_limit = in_value["mem_limit"].asInt();std::string lang = in_value["language"].asString();int status_code = 0;// 初始化模板路径和后缀名PathUtil::InitTemplate(lang);std::string file_name = FileUtil::UniqFileName(); // 生成唯一文件名// 创建编译器和运行器对象auto Cp = CompilerFactory::CreateCompiler(lang);auto Rn = RunnerFactory::CreateRunner(lang);if(code.empty()){// 如果代码为空,记录错误并设置状态码LOG_ERROR("CompileAndRun::Start code is empty");status_code = -1;goto END;}// 将代码写入文件if(!FileUtil::WriteFile(PathUtil::Src(file_name), code)){status_code = -1; // 其他错误goto END;}// 编译代码if(Cp){if(Cp->Compile(file_name) < 0) {status_code = -2; // 编译错误goto END;}}// 运行编译后的程序status_code = Rn->Run(file_name, cpu_limit, mem_limit);if(status_code < 0){status_code = -1; // 未知错误goto END;}else if(status_code > 0){goto END;}END:// 构建输出 JSON 数据out_value["status"] = status_code;out_value["reason"] = codeToDesc(status_code);if(status_code == 0){std::string _stdout;FileUtil::ReadFile(PathUtil::Stdout(file_name), &_stdout); // 读取标准输出out_value["stdout"] = _stdout;}else{std::string _stderr;FileUtil::ReadFile(PathUtil::Stderr(file_name), &_stderr); // 读取错误输出out_value["stderr"] = _stderr;}Json::StyledWriter writer;*out_json = writer.write(out_value); // 将结果写入输出 JSONLOG_DEBUG("out_json: {}", *out_json);// 清理生成的文件RemoveFile(file_name);}private:// 移除生成的文件static void RemoveFile(const std::string& filename) {auto filepath = path + filename;auto filesrc = filepath + srcSuffix;auto fileexe = filepath + excuteSuffix;auto fileout = PathUtil::Stderr(filename);auto filein = PathUtil::Stdin(filename);auto fileerr = PathUtil::Stdout(filename);FileUtil::RemoveFile(filesrc);FileUtil::RemoveFile(fileexe);FileUtil::RemoveFile(fileout);FileUtil::RemoveFile(filein);FileUtil::RemoveFile(fileerr);}
};

程序入口

#include "compile_run.hpp"
#include <httplib.h>int main()
{using namespace httplib;using namespace ns_util;// system("pwd");Server svr;svr.set_base_dir("./wwwroot");svr.Post("/run", [](const Request & req, Response &res) {std::string in_json = req.body;std::string out_json;CompileAndRun::Start(in_json, &out_json);res.set_content(out_json, "application/json");});// 启动服务器svr.listen("0.0.0.0", 8080);return 0;
}

总结

这个简易在线编译器只是我用于学习多进程应用与前后端交互而写的超微小项目,不算上前端网页只有500行代码左右。虽然还有很多设计不完善的地方,但凭我现在的水平也暂时想不到更好的解决方案,有什么不足的地方,欢迎讨论。

github:完整代码

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/373663.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Django项目的基本准备工作【1】

【 一 】pip换源 # 1 之前装第三方模块 pip3 install django -i 镜像仓库 ​ # 2 一劳永逸--》整点配置&#xff0c;以后安装模块&#xff0c;自动去配置好的源下载 ###windows 1、文件管理器文件路径地址栏敲&#xff1a;%APPDATA% 回车&#xff0c;快速进入 C:\Users\电脑用…

【Python】已解决:ModuleNotFoundError: No module named ‘nltk’

文章目录 一、分析问题背景二、可能出错的原因三、错误代码示例四、正确代码示例五、注意事项 已解决&#xff1a;ModuleNotFoundError: No module named ‘nltk’ 一、分析问题背景 在使用Python进行自然语言处理或文本分析时&#xff0c;我们经常会用到各种库来辅助我们的工…

C++Windows环境搭建(CLion)

文章目录 CLion下载安装CLion下载CLion安装新建项目新建一个文件基础设置字体设置clion中单工程多main函数设置 参考 CLion下载安装 CLion下载 打开网址&#xff1a;https://www.jetbrains.com/clion/download/ 点击Download进行下载。 CLion安装 双击下载好的安装包&…

1.9-改进的CBOW模型的实现

文章目录 0引言1 CBOW模型的重构1.1模型初始化1.2模型的前向计算1.3模型的反向传播 2总结 0引言 前面讲述了对word2vec高速化的改进&#xff1a; 改进输入侧的计算&#xff0c;变成Embedding&#xff0c;即从权重矩阵中选取特定的行&#xff1b;改进输出侧的计算&#xff0c;包…

100359.统计X和Y频数相等的子矩阵数量

1.题目描述 给你一个二维字符矩阵 grid&#xff0c;其中 grid[i][j] 可能是 X、Y 或 .&#xff0c;返回满足以下条件的子矩阵数量&#xff1a; 包含 grid[0][0]X 和 Y 的频数相等。至少包含一个 X。 示例 1&#xff1a; 输入&#xff1a; grid [["X","Y",…

《UDS协议从入门到精通》系列——图解0x84:安全数据传输

《UDS协议从入门到精通》系列——图解0x84&#xff1a;安全数据传输 一、简介二、数据包格式2.1 服务请求格式2.2 服务响应格式2.2.1 肯定响应2.2.2 否定响应 Tip&#x1f4cc;&#xff1a;本文描述中但凡涉及到其他UDS服务的&#xff0c;均提供专栏内文章链接跳转方式以便快速…

如何利用AI自动生成绘画?5款AI绘画的六大神器!

以下是五款专业级别的AI绘画工具&#xff0c;它们能够帮助用户迅速生成高质量的AI艺术作品&#xff1a; 1.AI先行者&#xff1a; 这是一款流行的 AI 绘画平台&#xff0c;它利用深度学习技术将你的照片或图像转换成艺术风格的绘画作品。你可以在线使用上上传图片并选择喜欢的艺…

react基础语法,模板语法,ui渲染,jsx,useState状态管理

创建一个react应用 这里使用create-react-app的脚手架构建项目&#xff08;结构简洁&#xff0c;基于webpack-cli&#xff09;&#xff0c; npx create-react-app [项目名称] 使用其他脚手架构建项目可以参考&#xff1a;react框架&#xff0c;使用vite和nextjs构建react项目…

解锁短视频运营新高度:视频号矩阵源码,定时自动发布,畅享高效管理

在数字时代浪潮下&#xff0c;短视频已然成为信息传播的重要渠道。对于内容创作者和企业来说&#xff0c;如何高效地管理和运营短视频账号&#xff0c;实现内容的定时自动发布&#xff0c;成为了一个亟待解决的问题。今天&#xff0c;我们将为您揭秘一款短视频运营的新利器——…

蓝卓创始人褚健:工业软件是数字化转型的灵魂和核心驱动力

如果把“工业3.0”简单理解为就是“自动化”&#xff0c;“工业4.0”理解为是“智能化”&#xff0c;那么“智能化”的实现一定要有软件。如同今天的移动互联网&#xff0c;是因为有大量的APP&#xff0c;所以让人们进入了智能时代。映射到工业、制造业领域&#xff0c;就是要依…

第4章 课程发布:模块需求分析,课程预览(模板引擎 静态页面),课程审核,课程发布(分布式事务,页面静态化:熔断降级),课程搜索(es索引)

1 模块需求分析 1.1 模块介绍 课程信息编辑完毕即可发布课程&#xff0c;发布课程相当于一个确认操作&#xff0c;课程发布后学习者在网站可以搜索到课程&#xff0c;然后查看课程的详细信息&#xff0c;进一步选课、支付、在线学习。 下边是课程编辑与发布的整体流程&#…

PHP全域旅游景区导览系统源码小程序

&#x1f30d;【探索无界&#xff0c;畅游无忧】全域旅游景区导览系统小程序全攻略 &#x1f4f1;【一键启动&#xff0c;智能导览在手】 告别纸质地图的繁琐&#xff0c;迎接全域旅游景区导览系统小程序的便捷时代&#xff01;只需轻轻一点&#xff0c;手机瞬间变身私人导游…

C++ 编译体系入门指北

前言 之从入坑C之后&#xff0c;项目中的编译构建就经常跟CMake打交道&#xff0c;但对它缺乏系统的了解&#xff0c;遇到问题又陷入盲人摸象。对C的编译体系是如何发展的&#xff0c;为什么要用CMake&#xff0c;它的运作原理是如何的比较感兴趣&#xff0c;所以就想系统学习…

CentOS7 安装 git 命令

通过yum源install下载的git版本比较低&#xff0c;不推荐此方式安装。 官网下载最新版git源码&#xff1a;Git 1. 解压安装包 tar -xzvf git-2.45.2.tar.gz 2. 安装相关依赖 yum install curl-devel expat-devel gettext-devel openssl-devel zlib-devel gcc perl-ExtUtils…

第三方商城对接重构(HF202407)

文章目录 项目背景一、模块范围二、问题方案1. 商品模块2. 订单模块3. 售后4. 发票5. 结算单 经验总结 项目背景 作为供应商入围第三方商城成功&#xff0c;然后运营了一段时间&#xff0c;第三方通知要重构&#xff0c; 需要重新对接打通接口完成系统对接&#xff0c;能贯穿整…

【QT中实现摄像头播放、以及视频录制】

学习分享 1、效果图2、camerathread.h3、camerathread.cpp4、mainwindow.h5、mainwindow.cpp6、main.cpp 1、效果图 2、camerathread.h #ifndef CAMERATHREAD_H #define CAMERATHREAD_H#include <QObject> #include <QThread> #include <QDebug> #include &…

Mybatis的优缺点及适用场景?

目录 一、什么是Mybatis&#xff1f; 二、Mybatis框架的特点 三、Mybatis框架的优点&#xff1f; 四、MyBatis 框架的缺点&#xff1f; 五、MyBatis 框架适用场合&#xff1f; 六、代码示例 1. 配置文件 mybatis-config.xml 2. 映射文件 UserMapper.xml 3. Java 代码…

coco_eval 使用

参考 coco eval 解析 COCO目标检测比赛中的模型评价指标介绍&#xff01; coco 的评估函数对应的是 pycocotools 中的 cocoeval.py 文件。 从整体上来看&#xff0c;整个 COCOeval 类的框架如图&#xff1a; 基础的用法为 # The usage for CocoEval is as follows: cocoGt…

深入解析视频美颜SDK:开发直播平台主播专用的美颜工具教学

本篇文章&#xff0c;笔者将深入解析视频美颜SDK的原理与应用&#xff0c;帮助开发者打造适用于直播平台的专业美颜工具。 一、视频美颜SDK的基础原理 视频美颜SDK其核心技术包括人脸检测、面部特征点识别、图像增强和特效应用等。 二、视频美颜SDK的开发流程 环境搭建 首先…

Redis+Caffeine 实现两级缓存

RedisCaffeine 实现两级缓存 背景 ​ 事情的开始是这样的&#xff0c;前段时间接了个需求&#xff0c;给公司的商城官网提供一个查询预计送达时间的接口。接口很简单&#xff0c;根据请求传的城市仓库发货时间查询快递的预计送达时间。因为商城下单就会调用这个接口&#xff…